# spec-review-fable-real — adversarial deep-pass of `spec-presence-not-age-adopt-13-07-2026.md`

> **Engine:** `/fable-real reviewer` (single top-model deep-pass, Opus 1M; Fable outage → single Opus).
> **Target:** `.claude/workflows/runs/2026-07-13-presence-not-age-adopt/spec-presence-not-age-adopt-13-07-2026.md`
> **Provenance verified against disk:** broadcast `ab6c387e`, directed `6c32df89`, sub-reviewer-{1..5}, `hmw.js`, `memory-archive-gate.ps1`, `memory-budget.json`, `reinject-ledger.md`, `mfe-eval.ps1`, `ultra-on.md`, `session-start.md`, `agents/README.md`.

---

## VERDICT: PASS-WITH-FIXES  (critical 0 / major 2 / minor 5)

The spec is faithful to the broadcast's 3 function-floors + directed rec-3, correctly classifies necessity per lane (D1/D2 MUST, D3 SHOULD, alias N/A), and is honest about its own residual limits. All D1 factual claims and all D2/D3 anchors verify against disk. Two MAJOR items are **verification-completeness gaps** (not code defects) that could let an incomplete deliverable pass its own acceptance gate; both are fixable by tightening the acceptance criteria, not by redesign. No blocker.

---

## MAJOR (fix before HMW execution)

### M1 — D2 acceptance criterion (4) grep MISSES 2 of the 3 doc lines it must guard  [spec:106 vs spec:63-67]
The spec's D2 doc-sync (spec:64-65) explicitly requires editing three current-behavior lines. Its acceptance criterion (4) verifies the sync with:
```
rg -i "fail-soft.*default subagent\|degrade về default subagent"
```
Tested against the three actual target lines on disk:

| Doc line (verified on disk) | matches spec regex? |
|---|---|
| `.claude/commands/ultra-on.md:21` — "role lạ → default subagent + cảnh báo" | **NO** (no "fail-soft"; not "degrade về…") |
| `.claude/commands/ultra-on.md:26` — "degrade về default subagent + WARN (fail-soft…)" | yes |
| `.claude/agents/README.md:208` — "…default subagent + WARN (fail-soft, S4c)." | **NO** ("fail-soft" is AFTER "default subagent", so `fail-soft.*default subagent` fails; no "degrade") |

So the criterion catches only **1 of 3**. It actually **narrowed** lane-4's already-imperfect regex (lane 4 had a 3rd alt `default subagent.*(fail-soft|WARN)` that caught README:208 and ultra-on:26 — but even lane 4 missed ultra-on:21's "cảnh báo" phrasing). Consequence: the grep can return **"0 hits = PASS"** while `ultra-on.md:21` and/or `README.md:208` remain un-synced → the acceptance criterion cannot detect a miss of its own MUST deliverable (a false-negative gate).

**Fix (acceptance-only, no redesign):** verify by grepping the bare token `-i "default subagent"` across the live-doc set {ultra-on.md, agents/README.md, harness-11-engine.md, fable-real-runbook.md}, then human-classify every hit as either (a) a synced STOP-HARD line, or (b) the KNOWN frozen-history hit the spec deliberately keeps — `harness-11-engine.md:~345` 🧊 saga note (spec:66). A narrow ordered-pattern regex cannot distinguish current-behavior from history and cannot enumerate all phrasings ("cảnh báo" vs "WARN"); the criterion must be "bare-token grep + classify," not a single pattern.

### M2 — D3 "skip protected" is incompatible with the existing contiguous-prefix byte model; spec under-specifies the required refactor + no acceptance tests it  [spec:76-95 vs memory-archive-gate.ps1:149-165]
The current drain (memory-archive-gate.ps1:153-162) is a **contiguous top-prefix cut**: it walks `for ($move=1; $move -le ($entryCount-$keepFloor); $move++)`, cuts at `$cutLine = $markers[$move]`, and estimates `$afterEst = $bytes - $prefixBytes` where `$prefixBytes = Σ lines[0..cutLine-1]` (a single contiguous top region). D3 (spec:89-91) says "walk position-order **BUT SKIP protected entries**; only LOW-VALUE can enter the move-set."

Skipping a *middle* protected entry makes the move-set **non-contiguous**, which the contiguous `$prefixBytes`/`$cutLine` model cannot express. Concrete trap: if an implementer adds a naive `if ($protected[$move]) { continue }` to the existing loop, the cut still happens at `$markers[$moveCount]`, so the protected entry — sitting **above** the cut line — is **still archived** while the code believes it was excluded. That is a silent correctness inversion (D3 would archive exactly what it set out to protect).

Coherent skip requires replacing the prefix-sum with **per-entry byte accumulation** (`afterEst = bytes − Σ bytes(each low-value entry actually chosen to move)`), summed over a possibly non-contiguous set — which the spec's pseudocode gestures at ("accumulate until est < lowMark") but never states as a refactor of the `$prefixBytes` logic. Compounding factor: the `---`-double-count (memory-archive-gate.ps1:80 counts `---` separators as markers; [TAILOR] note :305-306 concedes marker-count only ≈ entry-count) means `$markers[$k+1]-1` spans and the `keep_floor`/`entryCount` arithmetic are already loose, and the new `$protected` hashtable is keyed on those same double-counted indices — a value token after a `---` inside one logical entry flags the `---`-started pseudo-span, not the `##`-headed span, so the head can be proposed while the tail is "protected."

**Bounded:** the script is DRY-RUN (never cuts; `afterEst` is already flagged approximate with the `~` prefix and the [TAILOR] estimate note), so worst case is a *misleading proposal number*, not data loss. **But** none of the D3 acceptance criteria (spec:107) test after-est coherence or the interleaved-protected case, so a broken estimate would pass all listed checks.

**Fix (spec + acceptance):** (a) state explicitly that protected entries are excluded **from the cut**, not just from a flag, and that `afterEst` must sum the bytes of the individually-chosen low-value entries (non-contiguous), not a top-prefix; (b) add an acceptance case: a fixture where a protected entry is **interleaved between two low-value entries** must show the protected entry is NOT in the move-set AND `afterEst` reflects only the moved low-value bytes; (c) either fix or explicitly account for the `---` double-count in the `$protected` span mapping (the spec currently defers it to "future code-quality pass," spec:97 — acceptable only if D3's partition is proven `---`-robust, which it is not shown to be).

---

## MINOR

### m1 — D2 load-bearing premise is an unverifiable inference (conclusion still correct)  [spec:39]
The spec states a per-lane throw inside `taskList.map` "risks being swallowed to `null` by the tail `return results.filter(Boolean)`." **`parallel()` is a Workflow-runtime builtin — it is NOT defined anywhere in the repo** (grep across the tree returns 0 real definitions). So the swallow-to-null premise **cannot be verified from source**; it rests entirely on the `hmw.js:152` comment "lọc null nếu agent lỗi/null." The up-front design decision is nonetheless **correct and robust under both truth-values**: if `parallel()` swallows → up-front avoids the silent drop; if `parallel()` propagates → up-front is harmlessly redundant (throws even earlier, identical halt). The spec hedges appropriately ("risks"). Also a wording imprecision: a throw in the **outer** map callback `(t,i)=>()=>{…}` runs synchronously during array construction, **before** `parallel()` — it would NOT be swallowed; only a throw in the **inner thunk** is subject to `parallel()`. The spec conflates the two, but the practical target (the inner thunk, where today's fail-soft lives at :106-107) and the conclusion (validate up-front) are right. Recommend: relabel the premise "inference from the :152 comment, not proven — up-front chosen for halt-independence from unverifiable runtime semantics."

### m2 — D3 does NOT close the leak that justifies it over COMPLIANT (fully disclosed)  [spec:96 / lane 2 §3]
The lane-2/lane-3 case for D3 being more than "quick re-verify" is the **spine + paraphrased-anti-pattern silent leak**: high-value entries carrying none of the 9 `value_protect` tokens drain UNFLAGGED. D3's hard-skip reuses the **identical 9-token grep**, so it leaves that exact leak open — it only upgrades already-token-flagged entries from advisory→hard-skip. The residual guarantee stays 100% procedural (em-main value-scan), the same as pre-D3. The spec is explicitly honest about this (spec:96 "do NOT claim 'now fully value-gated' — paraphrase leak remains; procedural em-main-scan is still the true guarantee"), so this is **not a defect** — but it means D3 is closer to "safe defense-in-depth codify" than "closes a real gap," which strengthens lane-5's position that D3 is legitimately SHOULD / deferrable. No action required beyond keeping the honest caveat prominent.

### m3 — D3 acceptance (1) is only meaningful with a token-bearing fixture  [spec:107]
"high-value entry at TOP is NOT in proposed move-set" only holds if the fixture entry carries a `value_protect` token (D3 keys on the grep). A spine-style untokenized "high-value" fixture would still be proposed (the un-fixable-by-D3 case, see m2) and the test would fail for the wrong reason. Spec should require the D3(1) fixture's high-value entry to contain a `value_protect` pattern.

### m4 — D2 stub test proves the predicate, not the in-context integration  [spec:106 crit (2)]
Because `hmw.js` is not node-runnable, the stub (lane 4:154-160) replicates the `badRoles` filter standalone. It proves the predicate is correct but not that `A.taskList`/`VALID_ROLES` resolve at the insertion point. I manually confirmed both are in scope (VALID_ROLES `hmw.js:22`, `A` `hmw.js:75`, insertion after `:89`), so it is fine in practice — but the acceptance does not mechanically cover integration. `node --check` (crit 1) is syntax-only and does not catch a ReferenceError. Note only.

### m5 — D1 mislabels the CG-1 session column (verdict still correct)  [spec:31]
CG-1's `reinjected` column enforces "max 1 reinject per N=3 sessions" — a reinject-**event rate-limiter / loop-breaker**, which the spec calls a "last-seen debounce." It is not literally a last-seen-presence mark. But the drop-date test the spec applies yields the correct answer (dropping the column changes only the debounce/termination count, never the *what-to-reinject* decision → not an age-rank selector → OK per broadcast:68 carve-out). Label imprecise; the "OK / not a red-flag" verdict is sound.

---

## Positive validations (claims that resisted adversarial scrutiny)

- **D1 factual base fully verified on disk:** reinject predicate is presence-only (`reinject-ledger.md:12` "ĐÃ-từng-tồn-tại ∩ CÒN-giá-trị ∩ RỚT-khỏi-L1", no date term); `mfe-eval.ps1:183-187` age-band is FLAG-only (`$oldN` computed :185, printed :187 "KEPT status-driven age-blind", **no downstream consumer** — grep of `$oldN`/`markDates`/`age-band`/`KEPT` returns only the compute+print sites); **`Sort-Object` sweep = exactly 3 hits** (archive-gate:106 `Name`, measure:10 `Name`, selfimprove-audit:292 `LastWriteTime` = latest-run presence) → **zero date-rank in any reinject/archive DECISION path**, matching lanes 1+3.
- **D2 doc-sync anchors all exist at cited lines:** `ultra-on.md:21`+`:26` ✓, `README.md:208` ✓; and **`session-start.md` correctly has NO fail-soft rule** — line 32 already says "thiếu/sai vai → em main hỏi anh, KHÔNG tự chọn" (roster 12). The spec's "NO change to session-start (grep-verified)" (spec:68) is independently confirmed; the original task premise that session-start had a fail-soft rule is correctly rejected.
- **D2 `badRoles` filter is correct** (`x.raw != null && x.raw !== '' && !VALID_ROLES.includes(x.raw)`): preserves null / undefined(omitted) / '' as legitimate role-less inherit-lead (H6.2) and throws only on non-empty invalid — verified across all 4 cases. Insertion point (after `:89`, before `:91`) is genuinely BEFORE `parallel()` (:104) and AFTER the `checkpointApproved` tripwire (:83-85), mirroring the existing up-front guard. D2b deletion of the `:107` WARN is correct (dead after the up-front throw). `node --check` was empirically confirmed to have teeth (broken.js→exit 1) and hmw.js + an edited copy both pass (exit 0), so acceptance crit (1) "exit-class identical" is satisfiable and meaningful.
- **Scope discipline intact:** spec forbids removing `keep_floor`/`value_protect`/mfe-age-band (spec:117) and forbids rewriting the reinject predicate — matches lane-5's anti-regression guard (row G). No scope drift beyond the 4 deliverables. D3 stays a bounded reorder with no value-scoring numeric (spec:118).
- **3 mandatory honest-notes present** (spec:19-23) and required by D1+D4 acceptance: basis = ONE occurrence proactively fixed; SPECIFIC-APPLICATION not new rule; FUNCTION not FORM. Faithful to broadcast §5.
- **Necessity tiering matches the convergent ensemble:** D1 MUST (docs), D2 MUST (code, directed), D3 SHOULD (bounded), alias N/A (`/fable-real` vs `/fable-clone` = depth-toggle, not a conversational-vs-ensemble alias). No Smart-Friend lowering: the two MAJORs are raised despite the spec's high overall quality.

---

## Bottom line for HMW
Proceed. Apply M1 (replace the D2 crit-4 regex with bare-token grep + classify) and M2 (specify D3 non-contiguous byte-accounting + protected-exclusion-from-the-cut + add the interleaved-protected acceptance case) before running the D2/D3 acceptance gates. The 5 minors are notes/relabels, not blockers.
