Adopt AI_INFRA 2026-07-13 broadcast ab6c387e (presence-not-age selector, type=update) + directed 6c32df89 rec-3, via /fable-clone 5-lane reviewer ensemble (wf_b621aac4-f0b) -> spec -> /fable-real deep-pass (PASS-WITH-FIXES, M1+M2 applied) -> HMW execute (em-main solo; governance single-writer D9). D2 (hmw.js): unknown-role fail-soft-WARN -> up-front STOP-HARD throw (before parallel; preserves null/'' role-less inherit-lead path). node --check + stub 7/7. Doc-sync 5 live lines (ultra-on/README/harness-11-engine/runbook). D3 (memory-archive-gate.ps1): value_protect advisory-flag -> pre-selection HARD-SKIP (value-primary; heading-only spans; non-contiguous byte accum) + value-floor WARN. Fault-inject ALL PASS + real regression A7 242/242. DRY-RUN. D1 (reinject-ledger.md): presence re-verify stamp + 3 honest-notes + BUILD-GAP. Re-verify: reinject (i) + MFE age-band (iii) already COMPLIANT; only the archive-gate age-trace needed hardening. D3 = defense-in-depth codify, NOT leak-closure (9-token grep unchanged; em-main value-scan stays the guarantee). adap-report + email AI_INFRA (8b9dc5165d5a); inbox STAGE-2 processed. No new User-Mark (codify-only). #53 garble x1 (lane-4) recovered from disk, 0 loss. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
15 KiB
sub-reviewer-2 — LENS 2 / FLOOR (ii) archive VALUE-GATE vs FIFO-by-date [CRUX]
Run: 2026-07-13-presence-not-age-adopt · Lane: 2 of 5 · Lens: archive value-gate vs FIFO-by-date Broadcast: ab6c387e (presence-not-age, type=update) · Directed: 6c32df89 · Reviewer: adversarial, value-vs-age lens
VERDICT: NEEDS-FIX — necessity = SHOULD (minimal ~10-line value-primary drain reorder; DRY-RUN preserved)
SE's memory-archive-gate.ps1 PASS-1 planner is functionally a FIFO-queue-with-a-value-overlay, NOT a genuine value-gate. The base drain ordering is OLDEST-by-position (file position used as an age-proxy), and value_protect is bolted on as an ADVISORY flag that never changes the proposed move-set. The exact (iii) red-flag signature ("oldest-first to decide cool-down") is present in the code, its comments, and its resolve-string. DRY-RUN + keep_floor + em-main-final + the advisory flag together form a functional backstop (no automatic age-cut ever happens), which is why this is NEEDS-FIX/SHOULD and not RED-FLAG/MUST — but the floor asks the decision-step ordering itself to be value-first, and today it is age-first. The minimal fix promotes value_protect from advisory-flag to a hard-skip in the drain-candidate loop, making the printed PROPOSAL value-gated by construction (position demoted to a within-low-value tiebreak).
Evidence (file:line + short quote)
1. The base drain ordering is OLDEST-first (age-proxy = file position)
scripts/memory-archive-gate.ps1:137— comment: "how many OLDEST entries to move?"scripts/memory-archive-gate.ps1:138— comment: "Move oldest entries one-by-one"scripts/memory-archive-gate.ps1:153-162— the loop walksfor ($move = 1; $move -le ($entryCount - $keepFloor); $move++)and cuts$prefixBytes=lines[0..cutLine-1](the TOP of the file). It accumulates the move-set strictly top-downward until$est -lt $lowMark. There is no value term in this loop; the ONLY ranking key is position (markers[$move]).scripts/memory-archive-gate.ps1:182— the human-facing resolution literally reads "move $moveCount oldest -> curate L1->L2 by hand".scripts/memory-archive-gate.ps1:50—keep_floorprotects the mirror-image age axis: "A5: never auto-drain below N newest" (protect newest-5 = a pure recency protection). budget.json:38keep_floor_entries: 5.
→ Both ends of the base mechanism are AGE-based: drain-oldest (top) + protect-newest (bottom-5). Position is the age-proxy (the convention = newest appended at bottom). This is exactly the (iii) pattern: "any place that SORTS/FILTERS by ... 'oldest-first' to DECIDE ... cool-down = RED FLAG."
2. value_protect is ADVISORY ONLY — it does not change the move-set
scripts/memory-archive-gate.ps1:53-56— "If a planned MOVE would archive one OUT of L1-hot regardless of age, FLAG it ... ADVISORY FLAG ONLY - em-main decides (no auto-exclude); keep_floor stays the recency axis."scripts/memory-archive-gate.ps1:167-176— the value scan runs AFTER$moveCountis already computed (lines 149-165). It reads the already-chosen moved prefix ($cutLine = $markers[$moveCount]) and only appends$valHits. It does not feed back into$moveCountor the drain loop.scripts/memory-archive-gate.ps1:188-190— the flag is aWrite-Outputonly: "-> KEEP in L1 regardless of age (do NOT age-archive); em-main decides". No exclusion.- budget.json:39 confirms by design: "the patterns below are an advisory grep-hint for the DRY-RUN planner to FLAG protected entries, NOT an enforced auto-exclude".
→ So the PROPOSAL (the thing printed and handed to em-main) is generated by pure FIFO-by-age; value is a post-hoc annotation on that FIFO set, not the gate that selects it.
3. The advisory value-detector has coverage holes that the age-base can drain through
value_protect.patterns (budget.json:40) = ["gotcha #","anti-pattern","recurring","lost-update","race","bai hoc","lesson","guard","root-cause","silent-fail"] — a 9-token substring grep. Two concrete leak paths where a high-value OLD entry is proposed for drain without a flag (so em-main gets no signal):
- Spine sections carry no token. In a reviewer/lead
MEMORY.mdthe TOP-of-file spine is## 🎯 Role baseline,## 📋 6-category checklist,## 🧠 ... review essentials. These are the highest-value keep-forever sections but contain none of the 9 tokens. Being at the TOP (oldest position) they are the FIRST drain candidates, and they are UNFLAGGED → the FIFO proposal targets the spine and the advisory backstop is silent. (Verify: the injected reviewer MEMORY.md "🎯 Role baseline" body has nogotcha #/race/lesson/etc.) - Paraphrased anti-patterns. An entry describing a lost-update as "hai lượt lưu liên tiếp đè nhau" without the literal
race/lost-update/lessontoken is high-value but UNFLAGGED → drainable by age. This is the precise "silent age-creep" the broadcast warns of at ab6c387e:44 ("'sắp theo ngày' ... lặng-lẽ đưa một tiêu-chí đã-bị-cấm quay trở lại đúng khâu ra-quyết-định").
→ The functional backstop (em-main honoring flags) can therefore silently fail for exactly the class floor (ii) protects. The value-gate must be the PRIMARY filter, not an opt-in-by-human-attention overlay.
Broadcast self-check (iii) applied — the drop-the-date-column test
The archive-gate has no literal date column; file position IS the age-column (drain top-first, protect bottom-5). Run the broadcast's test: remove position-as-age from the selector input. Today the drain has nothing else to rank by — behavior collapses (it can't choose a move-set). Per the rubric ab6c387e:68, position is being used for age-RANK (which entry to cool), NOT merely for PRESENCE/last-seen → "date used for age-RANK = fix." This is a genuine (iii) hit on the base ordering. (Contrast lane-1's age-band, which is print-only and passes the same test.)
Self-refutation (I must beat BOTH directions before settling)
Direction A — "I'm UNDER-fixing; this should be COMPLIANT / quick re-verify only"
The case for COMPLIANT: The script is FLAG-ONLY, DRY-RUN (header :5-8, :86) and never cuts anything — "auto-WRITE of rules = top hazard" (:7). The real selector per the broadcast is em-main, who reads the value_protect flags and decides by value; the script is a byte-budget smoke-detector, not the selector. SE already adopted the 2026-06-20 principle this broadcast references — budget.json:39 explicitly cites "mark RC-...10-29-11 (time/age=false-proxy) applied to the memory layer" and :67 states "age is a FLAG, never a cut ... drops only on status-change ... NOT by age." honest-note-3 (ab6c387e:94) says floor = FUNCTION not FORM; the FUNCTION (high-value kept regardless of age) is achieved when em-main honors the flags. Broadcast pre-classifies this: "already-presence-based → quick re-verify only" (honest-note-2). Forcing a code reorder on a DRY-RUN planner is FORM-policing → over-engineering a type=UPDATE.
Why it fails (rebuttal): The stated policy (budget.json:67 "never by age") is presence-based, but the mechanism that implements it (the PASS-1 drain loop) is age-first with value as advisory — a policy/mechanism gap. Floor (ii)+(iii) do not merely ask "is high-value ever auto-cut?" (answer: no, DRY-RUN); they ask the decision-step ordering itself to not be oldest-first (ab6c387e:66,75). SE's ordering IS oldest-first by its own comments (:137-138,182). Relying on em-main to correct a mechanically-age-ranked proposal is precisely the "easy default that silently re-admits the banned criterion" the broadcast names (:44). And §3 shows the advisory backstop has real holes (spine + paraphrase) → the FUNCTION can silently fail, so "function is met" is not safely true. COMPLIANT overstates it.
Direction B — "I'm OVER-fixing; this should be RED-FLAG / MUST / rip out FIFO"
The case for RED-FLAG/MUST: (iii) is a mandatory function-floor ("sàn chức-năng bắt-buộc", ab6c387e:46) and calls oldest-first-to-cool a "cờ đỏ" (:66). The code + comments + resolve-string all carry that signature verbatim. So compliance is mandatory and the base FIFO must be redesigned now.
Why it fails (rebuttal): RED-FLAG/MUST would overstate a DRY-RUN planner that never cuts, that already has a value axis (partial compliance, not naked FIFO), and whose final decision is a value-aware human. The broadcast itself scopes this as proactive-prevention, ONE occurrence already fixed, not a spreading incident (honest-note-1, :90) and type=UPDATE "re-verify CHỈ phần bộ chọn." There is no live harm to block. Ripping out position-ordering entirely is also wrong: after value-filtering, you still need some order among equally-low-value candidates, and position-as-tiebreak among already-low-value entries is defensible ("date used only to determine PRESENCE = OK", :68). So the honest necessity is SHOULD, and the fix is a minimal value-primary reorder, not a rewrite.
Settle
Between A and B: the base ordering is a real (iii) hit (not COMPLIANT), but DRY-RUN + existing value_protect + em-main form a functional (if leaky) backstop with no automatic harm (not RED-FLAG/MUST). → NEEDS-FIX, necessity SHOULD, minimal value-primary reorder that keeps DRY-RUN/keep_floor/em-main-final intact.
Concrete change (minimal — value becomes the PRIMARY drain filter)
File: scripts/memory-archive-gate.ps1, PASS-1 drain block (currently :141-176). Keep DRY-RUN, keep_floor, strike gating, and the printed-proposal contract; change only WHICH entries enter the move-set: hard-skip value_protect-matching entries, so position only orders the LOW-VALUE remainder.
Pseudocode (lead may reshape — FORM self-determined, honest-note-3):
# NEW (before the drain loop): flag each entry span as value-protected
# entry k spans lines [ markers[k] .. markers[k+1]-1 ] (last entry -> end of file)
$protected = @{} # k -> $true if the entry body carries any $valPatterns token
for ($k = 0; $k -lt $entryCount; $k++) {
$start = $markers[$k]
$end = if ($k -lt $entryCount-1) { $markers[$k+1]-1 } else { $lines.Count-1 }
for ($li = $start; $li -le $end; $li++) {
foreach ($vp in $valPatterns) { if ($lines[$li] -like "*$vp*") { $protected[$k] = $true; break } }
if ($protected[$k]) { break }
}
}
# CHANGED drain loop: walk oldest-position-first BUT SKIP protected entries.
# Only LOW-VALUE entries (and never the newest keep_floor) can enter the move-set.
# Accumulate moved bytes over the SKIP-filtered candidates until est < lowMark.
# If the low-value candidates are exhausted and still >= lowMark -> $warnValueFloor = $true
# ("over-cap but every drainable entry is value-protected -> SPLIT/condense by hand")
# -- mirrors the existing keep_floor WARN at :180; do NOT propose a protected entry.
Resolution-string change (:182): drop the word "oldest" → e.g. "move $moveCount lowest-value (oldest-first tiebreak) -> curate by hand", so the human-facing plan no longer reads as an age-queue. Add a WARN value-floor hit branch alongside the existing WARN keep-floor hit (:180).
Net effect: the PROPOSAL itself is value-gated (a recurring-bug/anti-pattern/root-cause entry is never proposed for cool-down regardless of position); position survives only as a tiebreak among equally-low-value entries. This converts "FIFO-queue-with-value-overlay" → "value-gate with position tiebreak," removing the (iii) red-flag signature from the decision step while preserving DRY-RUN/keep_floor/em-main-final. ~10-15 LOC, no new file, no hub-org mirroring.
Companion doc-stamps (honest-notes are mandatory, ab6c387e:88-94):
- budget.json — update
value_protect._note(:39) +age_bandcaveat (:67) to state the selector is value-PRIMARY (hard-skip), position only a within-low-value tiebreak; and that the grep is a lower-bound signal, so em-main must value-scan the whole proposed set, not only flagged lines. - Keep the 3 broadcast honest-notes once (may be a single cross-lane block synthesized by lead): basis = ONE occurrence proactively fixed / SPECIFIC-APPLICATION not new rule / FUNCTION not FORM.
Residual honesty (do NOT overclaim the fix): the value-signal is still a 9-token substring grep; the hard-skip narrows but does not eliminate the paraphrase leak (§3). The fix makes the DEFAULT value-first (structural), but em-main-value-scan remains the true guarantee (procedural). State this in the note — do not claim "now fully value-gated."
Measurable acceptance criteria (fault-injection, not happy-path)
Use -RepoRoot <temp-tree> (the script's own param :29) to build synthetic MEMORY.md fixtures — proves teeth, per feedback_faultinjection_proves_teeth.
- High-value-at-top is NOT proposed. Fixture: over-cap MEMORY.md with a
## ... gotcha #99 root-cause ...entry at the TOP (oldest position) + several low-value entries below (outside keep_floor). Run gate. ASSERT: the top high-value entry is NOT in the proposed move-set; a low-value entry is. (Baseline today: it IS in the move prefix, only advisory-flagged.) - Permutation-invariance (broadcast drop-date test). Two fixtures with the SAME entries but POSITIONS permuted, value-markers preserved. ASSERT: both runs propose the same value-CLASS to drain (the low-value entries), not "whatever sits at top." (Baseline: proposal changes with position → age-ranked.)
- Value-floor WARN fires. Fixture: over-cap where EVERY non-keep_floor entry carries a value marker. ASSERT: output prints a
value-floor hit ... condense by handWARN and proposes moving ZERO protected entries (moveCount over protected = 0). (Baseline: proposes draining a protected entry with only an advisory note.) - DRY-RUN + keep_floor preserved. ASSERT: no MEMORY.md/archive file mutated by any run; newest-
keep_floorentries never proposed; over-cap still exits 0 (FLAG not error), A7 exit-2-on-pointer-fail unchanged (:295). - A7 pass-2 byte-unchanged behavior. ASSERT:
git difftouches only the PASS-1 block + resolve-string + budget.json note; PASS-2 integrity gate (:209-295) untouched.
One-line finding
SE memory-archive-gate.ps1 drains OLDEST-by-position (age-proxy) as the base ordering (:137-138,153-162,182) with value_protect only an advisory post-hoc flag (:53-56,188-190) — a FIFO-queue-with-value-overlay carrying the exact (iii) red-flag signature; DRY-RUN + em-main + advisory-flag are a leaky backstop (spine + paraphrase entries drain unflagged), so NEEDS-FIX/SHOULD via a ~10-line hard-skip that makes the PROPOSAL value-primary.