baldart 4.89.2 → 4.92.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/VERSION +1 -1
  3. package/framework/.claude/agents/api-perf-cost-auditor.md +9 -0
  4. package/framework/.claude/agents/code-reviewer.md +9 -0
  5. package/framework/.claude/agents/codebase-architect.md +6 -0
  6. package/framework/.claude/agents/coder.md +1 -1
  7. package/framework/.claude/agents/markup-fidelity-verifier.md +24 -1
  8. package/framework/.claude/agents/qa-sentinel.md +9 -0
  9. package/framework/.claude/agents/security-reviewer.md +9 -0
  10. package/framework/.claude/agents/ui-expert.md +49 -0
  11. package/framework/.claude/agents/visual-fidelity-verifier.md +16 -12
  12. package/framework/.claude/skills/e2e-review/CHANGELOG.md +49 -0
  13. package/framework/.claude/skills/e2e-review/SKILL.md +178 -24
  14. package/framework/.claude/skills/new/CHANGELOG.md +29 -0
  15. package/framework/.claude/skills/new/SKILL.md +44 -2
  16. package/framework/.claude/skills/new/references/completeness.md +1 -0
  17. package/framework/.claude/skills/new/references/implement.md +19 -3
  18. package/framework/.claude/skills/new/references/review-cycle.md +23 -1
  19. package/framework/.claude/skills/new/references/team-mode.md +4 -0
  20. package/framework/.claude/skills/new/scripts/verify-item.sh +54 -0
  21. package/framework/.claude/skills/new2/CHANGELOG.md +30 -0
  22. package/framework/.claude/skills/new2/SKILL.md +35 -8
  23. package/framework/.claude/skills/ui-implement/CHANGELOG.md +15 -0
  24. package/framework/.claude/skills/ui-implement/SKILL.md +31 -6
  25. package/framework/.claude/skills/ui-implement/references/integration.md +12 -3
  26. package/framework/.claude/skills/ui-implement/scripts/check-bindings.mjs +103 -0
  27. package/framework/.claude/workflows/new-card-review.js +19 -5
  28. package/framework/.claude/workflows/new-final-review.js +24 -9
  29. package/framework/.claude/workflows/new2.js +50 -4
  30. package/framework/agents/code-search-protocol.md +24 -0
  31. package/framework/agents/design-system-protocol.md +27 -0
  32. package/framework/docs/WORKFLOWS.md +9 -4
  33. package/framework/scripts/structural-compare.mjs +178 -0
  34. package/package.json +1 -1
@@ -2,6 +2,36 @@
2
2
 
3
3
  Formato: [Keep a Changelog](https://keepachangelog.com/) · [SemVer](https://semver.org/).
4
4
 
5
+ ## 1.2.0 — 2026-07-02
6
+
7
+ - **Resume loop HARD-BOUNDED (Step 5.3)**: `resumeMaxAttempts = 3` con contatore
8
+ esplicito + stall detection (stesso set di card incomplete = stop immediato).
9
+ Un `degraded` persistente è un outage/blocker strutturale, non converge iterando
10
+ (~0–200k+ token a ciclo).
11
+
12
+ ## 1.1.1 — 2026-07-02
13
+
14
+ - Fix B4 (location parallela in `new2.js`): il preflight creava il tracker in
15
+ `/tmp/batch-tracker-…` (volatile — la causa dei 2/3 run con "tracker LOST");
16
+ ora la location DURABILE `$(git -C $MAIN rev-parse --git-common-dir)/baldart/
17
+ run/…` allineata a new/SKILL.md § Context Tracking. Anche il riferimento del
18
+ merge allo stash Phase-0 aggiornato.
19
+ - Cap adattivo del fan-out nei workflow review (`new-card-review.js` /
20
+ `new-final-review.js`): 5 → 2 al primo transient rate-limit (era fisso 3).
21
+
22
+ ## 1.1.0 — 2026-07-02
23
+
24
+ - **Promossa a motore di default di `/new -auto`** (delegation gate v4.90.0):
25
+ non più solo variante sperimentale A/B. Nuovo Step 1.5 (contratto della
26
+ delega: grammar residua, mai `-auto`/`-auto-ship`/`-inline`; batch delegati
27
+ sempre migration-free — trovare una migrazione dichiarata su run delegata =
28
+ HALT + handback a `/new -inline`, mai una domanda).
29
+ - `new2.js`: lane di fedeltà strutturale route-free per card con mockup
30
+ (markup-fidelity-verifier + obbligo Lane-Ledger: verifier assente →
31
+ `verification-coverage-gap`, card mai auto-DONE) + binding gate deterministico
32
+ nel brief di implement + campi cardGraph `designSrcDir`/`designHtml`/
33
+ `hasBindings` (T1.A).
34
+
5
35
  ## 1.0.0 — 2026-07-01
6
36
 
7
37
  - Baseline: versioning per-skill introdotto al framework v4.82.0.
@@ -1,15 +1,18 @@
1
1
  ---
2
2
  name: new2
3
3
  effort: high
4
- version: 1.0.0
4
+ version: 1.2.0
5
5
  description: >
6
- EXPERIMENTAL workflow-hosted variant of /new (A/B testing). Implements one or
6
+ Workflow-hosted batch engine for /new. Implements one or
7
7
  more backlog cards end-to-end by delegating the WHOLE batch to a background
8
8
  dynamic workflow — so subagent output never enters the main orchestrator
9
9
  context. The batch runs autonomously (zero AskUserQuestion during the run): every
10
10
  /new gate is replaced by a deterministic policy + a self-healing resolution pass;
11
11
  in interactive mode an optional post-batch escape hatch can hand the hard-case
12
- follow-ups to /new for the real human gate. Claude-only (needs the Workflow tool).
12
+ follow-ups to /new for the real human gate. Since v4.90.0 this is ALSO the
13
+ default engine of `/new -auto` (its delegation gate invokes this skill when the
14
+ Workflow tool + new2.js are present — measured driver: 53–59% orchestrator
15
+ turn-tax on inline -auto runs). Claude-only (needs the Workflow tool).
13
16
  Usage: /new2 CARD-IDS (same arg grammar as /new). Triggers on:
14
17
  /new2, "implementa le card con workflow", "new2".
15
18
  ---
@@ -71,6 +74,19 @@ Also verify the workflow script is linked: if `.claude/workflows/new2.js` is abs
71
74
  print the same guidance and HALT (the framework did not link it — Codex tree, or a
72
75
  pre-v4.16 install).
73
76
 
77
+ ### Step 1.5 — Called by `/new -auto` (delegation note, v4.90.0)
78
+
79
+ `/new`'s AUTONOMOUS OFF-CONTEXT DELEGATION GATE invokes this skill with the same
80
+ residual grammar (card IDs + `-full` + `-stats` + `effort=`) — never
81
+ `-auto`/`-auto-ship`/`-inline` (this skill IS autonomous by contract; the
82
+ `AUTO_SHIP` deploy boundary stays in `/new`'s classic path). `/new` only
83
+ delegates **migration-free** batches (it probes `migration_plan.required`
84
+ itself), so Step 3.5's one interactive question cannot fire on a delegated run —
85
+ if you nonetheless find a declared, unapplied migration in a delegated batch,
86
+ HALT and hand back to `/new` classic (`"batch con migrazione dichiarata — riesegui
87
+ /new <cards> -auto -inline"`) instead of asking. Everything else is identical to a
88
+ direct `/new2` invocation.
89
+
74
90
  ### Step 2 — Parse the invocation (same grammar as `/new`)
75
91
 
76
92
  Parse `CARD-IDS` exactly as `/new` does — space-separated, hyphen-range
@@ -239,11 +255,22 @@ returns when the batch is done. It returns:
239
255
  prints nothing, set the epic card `status: DONE` + `completed_date` + note
240
256
  `"epic-closure gate — all children DONE (post-run, new2 skill)"`, folded into the SAME
241
257
  reconciliation commit. If any child is still open, leave the epic untouched.
242
- 3. **Resume if degraded.** If `degraded` is true, re-invoke the workflow with
243
- `Workflow({ scriptPath, resumeFromRunId })` (same `args` + the new `ts`). The
244
- per-card **skip-completed** guard makes the resume idempotent — already-committed
245
- cards are skipped, only the incomplete/blocked ones run. Repeat until `degraded`
246
- is false (or the same cards stall twice surface to the user).
258
+ 3. **Resume if degraded — HARD-BOUNDED (v4.92.0).** If `degraded` is true, re-invoke
259
+ the workflow with `Workflow({ scriptPath, resumeFromRunId })` (same `args` + the
260
+ new `ts`). The per-card **skip-completed** guard makes the resume idempotent —
261
+ already-committed cards are skipped, only the incomplete/blocked ones run.
262
+ Bounds (BOTH enforced, count explicitly do not rely on remembering):
263
+ - **`resumeMaxAttempts = 3`** total re-invocations per batch. Keep a literal
264
+ counter in your working notes (`resume 1/3`, `2/3`, …); at 3 still `degraded`
265
+ → STOP resuming: surface `"batch stalled after 3 resumes — manual
266
+ intervention needed"` with the incomplete card list (their follow-ups are
267
+ already on disk from step 1 — nothing is lost by stopping).
268
+ - **Stall detection**: if a resume completes with the SAME incomplete card set
269
+ as the previous attempt (zero progress), stop immediately even before the
270
+ cap — a second identical pass will not converge either.
271
+ An unbounded resume loop re-pays the whole batch-loop cost per cycle (~0–200k+
272
+ tokens each) with no new information; a persistent `degraded` is an outage or a
273
+ structural blocker, not something iteration fixes.
247
274
  3b. **Escape-hatch escalation for the hard cases (INTERACTIVE mode only — the `new2`
248
275
  "relaxation").** `new2` is autonomous *during the batch* — but a genuinely-blocked
249
276
  card (the workflow rolled it back / left it `IN_PROGRESS`, DoD not met) is exactly the
@@ -2,6 +2,21 @@
2
2
 
3
3
  Formato: [Keep a Changelog](https://keepachangelog.com/) · [SemVer](https://semver.org/).
4
4
 
5
+ ## 1.1.0 — 2026-07-02
6
+
7
+ - **Binding-compliance gate (Step 5b)**: nuovo script deterministico
8
+ `scripts/check-bindings.mjs` — ogni `component_bindings` entry `reuse`/
9
+ `reuse-variant` deve comparire (import o uso JSX) nei file toccati dalla build;
10
+ violazione = `binding-not-honored` (Critical, 1 retry a `ui-expert`, poi
11
+ `blocked`). Chiude il buco "la mappa è autoritativa ma nessuno verifica che la
12
+ build l'abbia onorata". Fail-open sugli errori interni dello script.
13
+ - **Build-with-eyes nel briefing (Step 4)**: il brief di `ui-expert` ora porta
14
+ l'obbligo del self-check in-build (`ui-expert.md` § "Build-with-eyes": check
15
+ strutturale deterministico via `structural-compare.mjs` + check visivo con
16
+ screenshot Playwright quando catturabile, max 2 iterazioni, degrado onesto su
17
+ runtime non-multimodale) — costruisci-bene a monte invece di
18
+ cattura-e-ripara a valle.
19
+
5
20
  ## 1.0.0 — 2026-07-01
6
21
 
7
22
  - Skill introdotta (framework v4.82.0). Estrae da `/new` il playbook mockup→code
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: ui-implement
3
3
  effort: medium
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  description: >
6
6
  Implement an ALREADY-APPROVED mockup into the codebase end-to-end and
7
7
  self-verify its fidelity. Takes a mockup in any channel — a Claude Design
@@ -123,11 +123,15 @@ Each step is a delegation. Read the cited SSOT; do not paraphrase it.
123
123
  4. **Implement — spawn `ui-expert`.** Build via the `ui-expert` agent with a
124
124
  briefing that carries: the chosen build workflow (Step 1), the mockup path(s),
125
125
  the registry-first cascade declaration requirement, the design-source
126
- declaration requirement (when code-src exists), the `component_bindings`, and
127
- the i18n / states-taxonomy / token-cascade constraints. All of these are
128
- `ui-expert.md`'s own contracts the briefing points at them; it does not
129
- re-teach them. (Codex / no-subagents the skill performs the build inline
130
- following the same `ui-expert.md` contracts.)
126
+ declaration requirement (when code-src exists), the `component_bindings`, the
127
+ **build-with-eyes self-check obligation** (`ui-expert.md` § "Build-with-eyes"
128
+ bounded structural + visual self-check BEFORE returning; the completion
129
+ report must carry its one-line outcome), and the i18n / states-taxonomy /
130
+ token-cascade constraints. All of these are `ui-expert.md`'s own contracts
131
+ the briefing points at them; it does not re-teach them. (Codex /
132
+ no-subagents → the skill performs the build inline following the same
133
+ `ui-expert.md` contracts; a non-multimodal runtime degrades the visual
134
+ self-check to a logged skip.)
131
135
 
132
136
  5. **Ground the design-source declaration** *(only when the card has
133
137
  `links.design_src` / `mockups/_src/` exists)*. After the agent's first
@@ -137,6 +141,27 @@ Each step is a delegation. Read the cited SSOT; do not paraphrase it.
137
141
  re-spawn. Log `design-source: grounded` | `rejected (<reason>)`. (This is the
138
142
  Step 7b grounding lifted out of `/new`.)
139
143
 
144
+ 5b. **Binding-compliance gate (deterministic — only when the card carries
145
+ `component_bindings`).** The bindings are authoritative (Step 3), but until
146
+ now nothing verified the built code actually HONORED them. Run:
147
+
148
+ ```bash
149
+ node .claude/skills/ui-implement/scripts/check-bindings.mjs \
150
+ --bindings-json '<the card's component_bindings serialized to JSON>' \
151
+ --files <the build's touched implementation files>
152
+ ```
153
+
154
+ One JSON line on stdout; exit `3` = violations. Every `reuse`/`reuse-variant`
155
+ binding whose component appears in NO touched file (import or JSX usage) is a
156
+ **`binding-not-honored`** finding (Critical, UI domain): re-spawn `ui-expert`
157
+ ONCE with the violation list ("the binding map is authoritative — use
158
+ `<Component>` for region `<region>`, do not re-invent it"); still violated →
159
+ the card is `blocked` (this is a fidelity-contract breach, not a taste issue).
160
+ `action: new` entries are informational here — their governance belongs to the
161
+ Post-Intervention Coherence Check (Step 6). Script errors fail OPEN (`skipped`,
162
+ exit 0) — the gate is a safety net, never a tribunal. Log
163
+ `bindings: ok (N honored)` | `violations (<components>)` | `skipped`.
164
+
140
165
  6. **Post-Intervention Coherence Check (BLOCKING).** Reconcile the design-system
141
166
  sources in the SAME change — CITE `design-system-protocol.md`
142
167
  § "Post-Intervention Coherence Check": INDEX (`DS_INDEX_DRIFT`), component spec
@@ -34,6 +34,9 @@ only the input source and the return shape differ.
34
34
  "findings": [ /* gating findings, canonical severity taxonomy */ ],
35
35
  "ds_drift_codes": [ "DS_INDEX_DRIFT" | "DS_COMPONENT_STALE" | "DS_TOKENS_DRIFT" ],
36
36
  "design_system_coherence": { /* INDEX / components / tokens / mirror reconciled */ },
37
+ "bindings": { /* Step 5b check-bindings.mjs one-line JSON (skill ≥1.1.0); absent when the card has no component_bindings */ },
38
+ "lane_coverage": "complete" | "incomplete",
39
+ "lanes": { /* forwarded from the internal /e2e-review report (skill ≥1.1.0, e2e-review ≥1.1.0) */ },
37
40
  "report_path": ".baldart/e2e-review/<id>/report.json"
38
41
  }
39
42
  ```
@@ -85,8 +88,14 @@ same opt-in contract as `/prd` → `/ds-handoff`.
85
88
  ## `new2` (workflow) note
86
89
 
87
90
  `new2.js` hosts the batch in a background workflow and reads `implement.md` /
88
- `review-cycle.md` for the per-card semantics. In this release `new2` keeps its
89
- **inline** implement+verify path (the fallback prose stays valid), so no `new2.js`
90
- change is required. Full `new2` parity — delegating the UI card's implement+verify
91
+ `review-cycle.md` for the per-card semantics. `new2` keeps its **inline**
92
+ implement path (the fallback prose stays valid), with two T1.A parallels wired in
93
+ directly (v4.90.0): the mockup card's implement brief carries the
94
+ **binding-compliance gate** (same `check-bindings.mjs` script as Step 5b), and a
95
+ **route-free structural fidelity lane** (`markup-fidelity-verifier`) runs after
96
+ implement for every card with a code-form mockup — its Lane-Ledger obligation
97
+ (`design-system-protocol.md` § 6): verifier unavailable → `verification-coverage-gap`
98
+ residual, the card cannot auto-DONE. Browser lanes (visual/quality) remain
99
+ `/new`-path only. Full `new2` parity — delegating the UI card's implement+verify
91
100
  off-context via `workflow()` — is a follow-up, to be taken after the context-economy
92
101
  measurement (`SKILL.md` scope note + the plan's measure-first gate).
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env node
2
+ // check-bindings.mjs — deterministic `binding-not-honored` gate (T1.A, framework v4.90.0).
3
+ //
4
+ // A card's `component_bindings` (card-schema.md § component_bindings) is AUTHORITATIVE
5
+ // over the mockup: each `action: reuse | reuse-variant` entry promises that the bound
6
+ // registry component is actually USED in the implementation. Until now nothing verified
7
+ // that promise after the build — a ui-expert that ignored a binding and re-invented the
8
+ // primitive sailed through (the verifier trio checks fidelity-to-mockup, not
9
+ // binding-compliance). This script closes that gap without a model call.
10
+ //
11
+ // Contract:
12
+ // node check-bindings.mjs --bindings-json '<json array>' --files <f1> <f2> ...
13
+ // node check-bindings.mjs --bindings-file <path.json> --files <f1> <f2> ...
14
+ //
15
+ // - `--bindings-json` is the card's `component_bindings` serialized to JSON by the
16
+ // caller (the orchestrator/agent already has the card in context; YAML parsing is
17
+ // deliberately NOT re-implemented here — the deterministic part is the code check).
18
+ // - `--files` are the implementation files the card touched (the build's scopeFiles).
19
+ // - Prints ONE JSON line to stdout:
20
+ // { status: "ok" | "violations" | "skipped",
21
+ // checked, honored, violations:[{component, region, action}],
22
+ // results:[{component, region, action, status, matched_in:[...]}] }
23
+ // - Exit codes: 0 = ok/skipped (nothing to gate), 3 = violations (gate fires).
24
+ // Internal errors NEVER crash the caller: fail-open → {status:"skipped"} exit 0
25
+ // (same philosophy as the framework-edit-gate: a safety net, not a tribunal).
26
+ //
27
+ // Matching rule (deterministic, conservative — prefer false-negative over false-block):
28
+ // an entry with action reuse/reuse-variant is HONORED when the component identifier
29
+ // appears in ≥1 provided file as an import specifier OR a JSX opening tag
30
+ // (`<Component` / `<NS.Component`). `action: new` entries are reported as
31
+ // informational (`new-component`) and never count as violations here — the
32
+ // Post-Intervention Coherence Check owns their governance.
33
+
34
+ import fs from 'node:fs';
35
+
36
+ function emit(obj, code) {
37
+ process.stdout.write(JSON.stringify(obj) + '\n');
38
+ process.exit(code);
39
+ }
40
+
41
+ function parseArgs(argv) {
42
+ const a = { bindingsJson: null, bindingsFile: null, files: [] };
43
+ for (let i = 2; i < argv.length; i++) {
44
+ if (argv[i] === '--bindings-json') a.bindingsJson = argv[++i];
45
+ else if (argv[i] === '--bindings-file') a.bindingsFile = argv[++i];
46
+ else if (argv[i] === '--files') {
47
+ while (i + 1 < argv.length && !argv[i + 1].startsWith('--')) a.files.push(argv[++i]);
48
+ }
49
+ }
50
+ return a;
51
+ }
52
+
53
+ function main() {
54
+ const args = parseArgs(process.argv);
55
+ let raw = args.bindingsJson;
56
+ if (!raw && args.bindingsFile) {
57
+ if (!fs.existsSync(args.bindingsFile)) emit({ status: 'skipped', reason: 'bindings file not found: ' + args.bindingsFile }, 0);
58
+ raw = fs.readFileSync(args.bindingsFile, 'utf8');
59
+ }
60
+ if (!raw) emit({ status: 'skipped', reason: 'no --bindings-json / --bindings-file' }, 0);
61
+
62
+ let bindings;
63
+ try { bindings = JSON.parse(raw); } catch (e) { emit({ status: 'skipped', reason: 'bindings JSON unparsable: ' + e.message }, 0); }
64
+ if (!Array.isArray(bindings) || bindings.length === 0) emit({ status: 'ok', checked: 0, honored: 0, violations: [], results: [], note: 'no bindings to check' }, 0);
65
+
66
+ const sources = [];
67
+ for (const f of args.files) {
68
+ try {
69
+ if (fs.existsSync(f) && fs.statSync(f).isFile()) sources.push({ path: f, text: fs.readFileSync(f, 'utf8') });
70
+ } catch { /* unreadable file → treated as absent, fail-open per-file */ }
71
+ }
72
+ if (sources.length === 0) emit({ status: 'skipped', reason: 'no readable implementation files passed via --files' }, 0);
73
+
74
+ const results = [];
75
+ for (const b of bindings) {
76
+ const component = String(b.component || b.name || '').trim();
77
+ const action = String(b.action || 'reuse').trim();
78
+ const region = String(b.region || '').trim();
79
+ if (!component) { results.push({ component: '(missing)', region, action, status: 'skipped-malformed', matched_in: [] }); continue; }
80
+ if (action === 'new') { results.push({ component, region, action, status: 'new-component', matched_in: [] }); continue; }
81
+
82
+ // Import specifier or JSX opening tag, word-bounded. Conservative: any hit counts.
83
+ const esc = component.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
84
+ const importRe = new RegExp(`import[^;]*\\b${esc}\\b[^;]*from`, 'm');
85
+ const jsxRe = new RegExp(`<(?:[A-Za-z_$][\\w$]*\\.)?${esc}[\\s/>]`);
86
+ const bareRe = new RegExp(`\\b${esc}\\b`);
87
+ const matched = sources.filter((s) => importRe.test(s.text) || jsxRe.test(s.text) || (/\.(css|scss)$/.test(s.path) && bareRe.test(s.text)));
88
+ results.push({
89
+ component, region, action,
90
+ status: matched.length ? 'honored' : 'not-found',
91
+ matched_in: matched.map((s) => s.path),
92
+ });
93
+ }
94
+
95
+ const violations = results.filter((r) => r.status === 'not-found').map(({ component, region, action }) => ({ component, region, action }));
96
+ const honored = results.filter((r) => r.status === 'honored').length;
97
+ emit(
98
+ { status: violations.length ? 'violations' : 'ok', checked: results.length, honored, violations, results },
99
+ violations.length ? 3 : 0
100
+ );
101
+ }
102
+
103
+ try { main(); } catch (e) { emit({ status: 'skipped', reason: 'internal error: ' + (e && e.message) }, 0); }
@@ -46,18 +46,28 @@ const protocolRef = '.claude/skills/new/references/review-cycle.md'
46
46
  // (kills agents mid-work). Cap concurrent agents at 3 — a rolling worker pool (NOT chunked barriers,
47
47
  // which would block fast finders behind the slow background Codex poll). Mirrors the team-mode coder
48
48
  // cap so review + implementation obey the same ceiling.
49
- const MAX_PARALLEL = 3
50
- async function parallelCapped(thunks, cap) {
49
+ // ADAPTIVE since v4.91.0 (mirrors new-final-review.js): start at 5; on the FIRST rate-limit
50
+ // transient anywhere in the run drop to 2 (below the old fixed 3 — when the shared org limit
51
+ // is hot, narrower is what keeps reviewers alive; when it is not, the fixed cap was pure
52
+ // queue-wait tax). Surplus workers retire at their next pickup.
53
+ const CAP = { value: 5 }
54
+ const MAX_PARALLEL = CAP.value // kept for arg-compat at call sites; capNow() is authoritative
55
+ const capNow = () => CAP.value
56
+ function noteRateLimitPressure(src) {
57
+ if (CAP.value > 2) { CAP.value = 2; log(`parallelCapped: rate-limit transient (${src}) → cap 5→2 for the rest of the run`) }
58
+ }
59
+ async function parallelCapped(thunks, _cap) {
51
60
  if (!Array.isArray(thunks) || !thunks.length) return []
52
61
  const results = new Array(thunks.length)
53
62
  let next = 0
54
- const worker = async () => {
63
+ const worker = async (idx) => {
55
64
  while (next < thunks.length) {
65
+ if (idx >= capNow()) return // adaptive shrink
56
66
  const i = next++
57
67
  try { results[i] = await thunks[i]() } catch (_) { results[i] = null }
58
68
  }
59
69
  }
60
- await Promise.all(Array.from({ length: Math.min(cap, thunks.length) }, worker))
70
+ await Promise.all(Array.from({ length: Math.min(capNow(), thunks.length) }, (_, idx) => worker(idx)))
61
71
  return results
62
72
  }
63
73
 
@@ -79,7 +89,11 @@ async function agentSafe(prompt, opts, maxAttempts) {
79
89
  try {
80
90
  const o = i === 0 ? opts : Object.assign({}, opts, { label: `${(opts && opts.label) || 'agent'}~retry${i}` })
81
91
  return await agent(prompt, o)
82
- } catch (e) { lastErr = e; if (!isTransient(e)) throw e } // permanent error → surface
92
+ } catch (e) {
93
+ lastErr = e
94
+ if (!isTransient(e)) throw e // permanent error → surface
95
+ noteRateLimitPressure((opts && opts.label) || 'agent') // v4.91.0 — shrink the pool for the rest of the run
96
+ }
83
97
  }
84
98
  const err = new Error('transient-exhausted: ' + String((lastErr && lastErr.message) || lastErr))
85
99
  err.transientExhausted = true
@@ -53,22 +53,33 @@ const editableFiles = Array.isArray(a.editableFiles) ? a.editableFiles.filter(Bo
53
53
  const tcCmds = (cfg.toolchain && cfg.toolchain.commands) || {}
54
54
  const tc = (key, fallback) => (tcCmds[key] && String(tcCmds[key]).trim()) || fallback
55
55
 
56
- // Concurrency cap (v4.53.6 API rate-limit guardrail; mirrors new-card-review.js + the team-mode
57
- // coder cap). The org-level rate limit is SHARED across every terminal/session; a wide agent fan-out
58
- // saturates it and the server throttles. Cap concurrent agents at 3 via a rolling worker pool (NOT
59
- // chunked barriers those would block fast finders behind the slow background Codex poll).
60
- const MAX_PARALLEL = 3
61
- async function parallelCapped(thunks, cap) {
56
+ // Concurrency cap (v4.53.6 guardrail, ADAPTIVE since v4.91.0 mirrors new-card-review.js + the
57
+ // team-mode coder cap). The org-level rate limit is SHARED across every terminal/session; a wide
58
+ // agent fan-out saturates it and the server throttles (the v4.66.0 transient-kill class). The old
59
+ // fixed cap of 3 protected against that but taxed EVERY run's wall-clock, including the majority
60
+ // with no rate pressure (measured: review fan-out ≈ 50 min on a small batch, mostly queue wait).
61
+ // Adaptive policy: start at 5; on the FIRST rate-limit transient seen anywhere in the run, drop to
62
+ // 2 for the remainder (BELOW the old cap — when the org limit is hot, narrower is what saves the
63
+ // reviewers). Rolling worker pool (NOT chunked barriers — those would block fast finders behind
64
+ // the slow background Codex poll); workers above the current cap retire at their next pickup.
65
+ const CAP = { value: 5 }
66
+ const MAX_PARALLEL = CAP.value // kept for arg-compat at call sites; capNow() is authoritative
67
+ const capNow = () => CAP.value
68
+ function noteRateLimitPressure(src) {
69
+ if (CAP.value > 2) { CAP.value = 2; log(`parallelCapped: rate-limit transient (${src}) → cap 5→2 for the rest of the run`) }
70
+ }
71
+ async function parallelCapped(thunks, _cap) {
62
72
  if (!Array.isArray(thunks) || !thunks.length) return []
63
73
  const results = new Array(thunks.length)
64
74
  let next = 0
65
- const worker = async () => {
75
+ const worker = async (idx) => {
66
76
  while (next < thunks.length) {
77
+ if (idx >= capNow()) return // adaptive shrink: retire surplus workers at pickup time
67
78
  const i = next++
68
79
  try { results[i] = await thunks[i]() } catch (_) { results[i] = null }
69
80
  }
70
81
  }
71
- await Promise.all(Array.from({ length: Math.min(cap, thunks.length) }, worker))
82
+ await Promise.all(Array.from({ length: Math.min(capNow(), thunks.length) }, (_, idx) => worker(idx)))
72
83
  return results
73
84
  }
74
85
 
@@ -91,7 +102,11 @@ async function agentSafe(prompt, opts, maxAttempts) {
91
102
  try {
92
103
  const o = i === 0 ? opts : Object.assign({}, opts, { label: `${(opts && opts.label) || 'agent'}~retry${i}` })
93
104
  return await agent(prompt, o)
94
- } catch (e) { lastErr = e; if (!isTransient(e)) throw e } // permanent error → surface
105
+ } catch (e) {
106
+ lastErr = e
107
+ if (!isTransient(e)) throw e // permanent error → surface
108
+ noteRateLimitPressure((opts && opts.label) || 'agent') // v4.91.0 — shrink the pool for the rest of the run
109
+ }
95
110
  }
96
111
  const err = new Error('transient-exhausted: ' + String((lastErr && lastErr.message) || lastErr))
97
112
  err.transientExhausted = true
@@ -203,6 +203,12 @@ const PREFLIGHT_SCHEMA = {
203
203
  // F-016 — ACs whose only implementation file is outside the card MAY-EDIT,
204
204
  // pre-classified deferred-by-policy (never routed to resolve).
205
205
  policyDeferredACs: { type: 'array', items: { type: 'object', additionalProperties: true } },
206
+ // T1.A (v4.90.0) — the mockup's code-form sources, so the workflow can run the
207
+ // route-free structural fidelity lane (markup-fidelity-verifier) without
208
+ // re-reading the card. Empty string when absent.
209
+ designSrcDir: { type: 'string', description: "abs path of links.design_src / mockups/_src dir ('' when absent)" },
210
+ designHtml: { type: 'string', description: "abs path of an .html links.design mockup ('' when absent)" },
211
+ hasBindings: { type: 'boolean', description: 'card carries a non-empty component_bindings map' },
206
212
  },
207
213
  },
208
214
  },
@@ -277,7 +283,7 @@ try {
277
283
  preflight = await agentSafe(
278
284
  `You are the deterministic PRE-FLIGHT for an autonomous /new batch (variant new2). Follow ${REF}/setup.md (Phase 0 + Pre-flight) and ${REF}/implement.md (Phase 1 depends-on gate) for the SEMANTICS, but replace EVERY AskUserQuestion with the deterministic policy below. You run all git/bash yourself (the workflow cannot).\n\n` +
279
285
  `${projectBrief}\n\nCards in batch (Read each YAML):\n${cardPaths.join('\n')}\nCard IDs: ${cardIds.join(' ')}\n\n` +
280
- `Create/maintain the recovery tracker at /tmp/batch-tracker-${firstCard}.md (per setup.md § Context Tracking).\n\n` +
286
+ `Create/maintain the recovery tracker at "$(git -C ${MAIN} rev-parse --git-common-dir)/baldart/run/batch-tracker-${firstCard}.md" (mkdir -p its dir first; per setup.md § Context Tracking — DURABLE location, v4.91.0: /tmp is volatile and a paused/overnight run lost it — B4).\n\n` +
281
287
  `ROLE BOUNDARY (specialization integrity): you are the OPS/GIT agent. You NEVER edit source or doc files — any needed content change belongs to the coder specialist; report it instead.\n\n` +
282
288
  `DETERMINISTIC GATE POLICIES (NO user prompts):\n` +
283
289
  `• G1 dirty-tree (main repo ${MAIN}): partition framework-managed noise exactly as setup.md step 3 ($METRICS=${METRICS}, .baldart/generated|state.json|skill-conflicts.json — NOT overlays/). Genuine user work → auto-stash 'baldart-new2-${firstCard}' (main checkout) and record the label. Never commit/abort/prompt.\n` +
@@ -286,7 +292,7 @@ try {
286
292
  g3Bullet +
287
293
  `• G4 card-field validation (setup.md 1b/1c, card-schema.md consumer contract): card missing acceptance_criteria/files_likely_touched/scope → EXCLUDE (excluded[] + reason). \`requirements\` is CONDITIONAL, not an auto-exclude: if it is missing BUT acceptance_criteria + scope are both present and non-empty, FAITHFULLY DERIVE it (a concrete restatement/decomposition of the existing AC + scope — faithful, never inventing new scope) and WRITE it back to the card YAML on disk in ${MAIN}/${paths.backlog_dir || 'backlog'} (F-040: main repo, not the worktree copy; the card artifact is NOT a source/doc file, so this is allowed despite the role boundary), logging \`[BACKFILL] <id>: requirements=<N items, AC+scope-derived>\`. Only EXCLUDE for missing requirements when acceptance_criteria OR scope is ALSO empty (a genuinely thin card). Never HALT for one bad card.\n` +
288
294
  `• G5 depends-on: a card whose depends_on names a non-DONE card NOT in this batch → EXCLUDE it AND every in-batch card that transitively depends on it.\n` +
289
- `• cardGraph (REQUIRED, F-021): for every runnable card return { id, dependsOn:[IN-BATCH deps only], ownerAgent (the card's owner_agent; G25 unknown→'coder'), hasMockup (BOOLEAN — true IFF the card has a non-empty links.design OR links.design_src; drives the mockup-first build override — implement.md §6a), reviewProfile (the card's review_profile; default 'balanced'), policyDeferredACs, alreadyCommitted, alreadyCommittedSha, isEpic (implement.md §6b epic guard: id ends '-00' OR filename ends '-epic.yml' OR group.is_epic:true OR review_profile 'skip' with no requirements), filesLikelyTouched (verbatim from the YAML) }.\n` +
295
+ `• cardGraph (REQUIRED, F-021): for every runnable card return { id, dependsOn:[IN-BATCH deps only], ownerAgent (the card's owner_agent; G25 unknown→'coder'), hasMockup (BOOLEAN — true IFF the card has a non-empty links.design OR links.design_src; drives the mockup-first build override — implement.md §6a), designSrcDir (ABS path of links.design_src / the PRD mockups/_src dir; '' when absent), designHtml (ABS path when links.design is an .html file; '' otherwise), hasBindings (BOOLEAN — non-empty component_bindings), reviewProfile (the card's review_profile; default 'balanced'), policyDeferredACs, alreadyCommitted, alreadyCommittedSha, isEpic (implement.md §6b epic guard: id ends '-00' OR filename ends '-epic.yml' OR group.is_epic:true OR review_profile 'skip' with no requirements), filesLikelyTouched (verbatim from the YAML) }.\n` +
290
296
  `• B1/F-026 idempotency (per card, AFTER the worktree exists): set alreadyCommitted:true (+ alreadyCommittedSha) IFF ALL hold: (a) a commit referencing the card id exists in ${TRUNK}..HEAD of the worktree; (b) the card's validation_commands re-run GREEN right now; (c) NO open follow-up card for it exists in ${paths.backlog_dir || 'backlog'}. On a FRESH worktree ${TRUNK}..HEAD is empty → all false, zero extra work.\n` +
291
297
  `• F-016 AC↔ownership consistency: for each acceptance_criterion, derive the file(s) it requires editing. If those files are NOT a subset of the card's MAY-EDIT/files_likely_touched → add the AC to policyDeferredACs:[{n,text,owningCard|owningFile,reason}] (it will become ONE follow-up, never a resolve). Do the same for any AC whose remedy is an owner-gated infra action (remote db push / deploy / secret / DNS).\n` +
292
298
  (migrationApplied
@@ -594,10 +600,15 @@ async function runCard(cardId, cardPath) {
594
600
  const phase1Brief = architectOk
595
601
  ? `Phase 1 already ran as specialist agents. READ the architecture baseline at ${baselinePath} BEFORE writing any code — it is your codebase context; do NOT redo the exploration.${auditCorrections.length ? `\nPlan-audit corrections (apply them as amendments to this briefing — do NOT modify the backlog YAML):\n${auditCorrections.map((c, i) => ` ${i + 1}. ${c}`).join('\n')}` : ''}`
596
602
  : `Phase 1 fallback: the architect specialist was unavailable — do the Phase 1 claim+architect exploration yourself per ${REF}/implement.md and persist the baseline to ${baselinePath} before coding.`
603
+ // T1.A (v4.90.0) — mockup cards: binding-compliance is verified deterministically by the
604
+ // check-bindings.mjs script (ui-implement Step 5b is the SSOT; this is the new2 parallel).
605
+ const bindingBit = node.hasMockup && node.hasBindings
606
+ ? `\nBINDING GATE (deterministic — ui-implement Step 5b parallel): after building, run \`node "$(ls .claude/skills/ui-implement/scripts/check-bindings.mjs .framework/framework/.claude/skills/ui-implement/scripts/check-bindings.mjs 2>/dev/null | head -1)" --bindings-json '<the card component_bindings as JSON>' --files <your touched implementation files>\`. Exit 3 = a reuse/reuse-variant binding is NOT used in the code: the map is AUTHORITATIVE — fix it before returning (one pass). Include the script's one-line JSON output as bindingCheck in your return.\n`
607
+ : ''
597
608
  let impl
598
609
  try {
599
610
  impl = await agentSafe(
600
- `Implement card ${cardId} per ${REF}/implement.md Phase 2 — you ARE the owner_agent '${ownerAgent}' — and ${REF}/completeness.md (Phase 2.5 + 2.5b AC-closure ledger). Run all gates/bash yourself.\n\n${phase1Brief}\n\n${cardBrief}\n\n` +
611
+ `Implement card ${cardId} per ${REF}/implement.md Phase 2 — you ARE the owner_agent '${ownerAgent}' — and ${REF}/completeness.md (Phase 2.5 + 2.5b AC-closure ledger). Run all gates/bash yourself.\n${bindingBit}\n${phase1Brief}\n\n${cardBrief}\n\n` +
601
612
  `POLICIES: G26 Phase-2 lint/tsc/test/build failing after the module's retry cap → buildBlocked:true + blockedGate. Build the AC Closure Ledger (one row per AC: implemented|unmet|policy-deferred). DO NOT silently defer; report unmet rows (excluding policy-deferred). Persist the diff to /tmp/diff-${cardId}.txt.\n\n` +
602
613
  `E4 OWNERSHIP RECONCILE (implement.md §11b — do this BEFORE returning): the card's MAY-EDIT includes files_likely_touched ∪ paths NAMED EXPLICITLY in this card's acceptance_criteria/definition_of_done (e.g. an ADR the DoD says to update, the data-model / ER doc for a schema change). Editing THOSE is in-scope. For any OTHER dirty file outside MAY-EDIT (another card's file, or unrelated): \`git checkout -- <file>\` to revert it (NEVER leave it orphaned), list it in revertedOutOfOwnership. Set fileDiffViolation:true ONLY if such an edit genuinely could not be reverted (then say why in note) — it is no longer a silent label.\n\n` +
603
614
  `Return: { epic, buildBlocked, blockedGate, unmetACs:[{n,text}], scopeFiles, mayEditPaths, revertedOutOfOwnership:[paths], fileDiffViolation, note }`,
@@ -642,6 +653,41 @@ async function runCard(cardId, cardPath) {
642
653
  if (r.status !== 'resolved') { deferredOpen = true; deferredClasses.add(r.deferralClass || 'unresolved') }
643
654
  }
644
655
 
656
+ // --- T1.A (v4.90.0): route-free structural fidelity lane — the new2 parallel of
657
+ // /e2e-review Phase 2.7. new2 has NO browser lanes (visual/quality verify runs only on
658
+ // the /new path), so the code-form structural check is its ONE per-card fidelity lane.
659
+ // Running it is a Lane-Ledger obligation (design-system-protocol § 6. Verification-Lane
660
+ // Ledger): a mockup card with a code-form source may NOT complete with zero fidelity
661
+ // verification (the FEAT-0064 under-run). Verifier unavailable → verification-coverage-gap
662
+ // residual + the card cannot be auto-DONE — an under-run blocks, never passes silently.
663
+ if (node.hasMockup && (node.designSrcDir || node.designHtml)) {
664
+ const structSrc = node.designSrcDir
665
+ ? { level: 'design_src', design_src_dir: node.designSrcDir }
666
+ : { level: 'html', mockup_path: node.designHtml }
667
+ let mfv = null
668
+ try {
669
+ mfv = await agentSafe(
670
+ `Compare the implementation's code structure against the mockup's code structure and return JSON per your output schema (route-free — no browser, no dev server; cd into ${sharedCtx.worktreePath}).\n\nInput payload:\n${JSON.stringify({ card_id: cardId, route: '(card-level)', mockup_source: structSrc, implementation_files: scopeFiles, tolerance: 'strict' })}\n\nReturn STRICT JSON only: { findings:[{category,severity,description,expected,actual,fix_hint}] }`,
671
+ { label: `fidelity:${cardId}:structural`, phase: 'Implement', agentType: 'markup-fidelity-verifier',
672
+ schema: { type: 'object', required: ['findings'], additionalProperties: true, properties: { findings: { type: 'array', items: { type: 'object', additionalProperties: true } } } } }
673
+ )
674
+ } catch (e) {
675
+ if (e && e.transientExhausted) noteDegraded('outage')
676
+ }
677
+ if (!mfv) {
678
+ g('fidelity-structural', 'UNDERRUN', 'planned lane never executed (verifier unavailable) — verification-coverage-gap, card cannot auto-DONE')
679
+ deferredOpen = true; deferredClasses.add('unresolved')
680
+ residuals.push({ card: cardId, kind: 'verification-coverage-gap', evidence: 'structural fidelity lane planned but not executed (markup-fidelity-verifier unavailable)', materialized: false })
681
+ } else {
682
+ const gating = (mfv.findings || []).filter((f) => /critical|major/i.test(String(f.severity || '')))
683
+ g('fidelity-structural', gating.length ? 'BLOCKED' : 'PASS', gating.length ? `${gating.length} gating structural finding(s)` : `ran — ${(mfv.findings || []).length} finding(s), none gating`)
684
+ for (const f of gating) {
685
+ const r = await resolve('blocker', cardId, `structural fidelity (${f.category || 'layout'}): ${f.description || ''} — expected: ${f.expected || '(see mockup)'}`, { mayEditPaths: mayEdit, scopeFiles, domain: 'ui' })
686
+ if (r.status !== 'resolved') { deferredOpen = true; deferredClasses.add(r.deferralClass || 'unresolved') }
687
+ }
688
+ }
689
+ }
690
+
645
691
  // --- Review fan-out (F-024/F-025): specialized agents, trimmed by review_profile. ---
646
692
  // G5 — scopeFiles tokens alone miss a security card whose files don't carry them. Also
647
693
  // trigger security-reviewer on the card's RAW owner_agent and its brief (title/requirements).
@@ -1106,7 +1152,7 @@ if (!committed.length) {
1106
1152
  } else {
1107
1153
  try {
1108
1154
  mergeResult = await agentSafe(
1109
- `Auto-merge the batch worktree to ${TRUNK} per ${REF}/merge-cleanup.md: Phase 6 via the DETERMINISTIC script merge-worktree.sh, then Phase 6b status reconciliation + Phase 6c hygiene. Run git yourself.\n\n${projectBrief}\nWorktree: ${sharedCtx.worktreePath}\nBranch: ${sharedCtx.branch}\nmerge_strategy: ${mergeStrategy}\nCommitted cards: ${committed.map((r) => r.card).join(' ')}\nPhase-0 stash to restore (if any): see /tmp/batch-tracker-${firstCard}.md.\n\n` +
1155
+ `Auto-merge the batch worktree to ${TRUNK} per ${REF}/merge-cleanup.md: Phase 6 via the DETERMINISTIC script merge-worktree.sh, then Phase 6b status reconciliation + Phase 6c hygiene. Run git yourself.\n\n${projectBrief}\nWorktree: ${sharedCtx.worktreePath}\nBranch: ${sharedCtx.branch}\nmerge_strategy: ${mergeStrategy}\nCommitted cards: ${committed.map((r) => r.card).join(' ')}\nPhase-0 stash to restore (if any): see "$(git -C ${MAIN} rev-parse --git-common-dir)/baldart/run/batch-tracker-${firstCard}.md".\n\n` +
1110
1156
  `PHASE 6 — run the SSOT script (the merge analogue of setup-worktree.sh; NOT a hand-rolled merge, NOT /mw inline):\n` +
1111
1157
  ` MERGE_SH="$(ls .framework/framework/.claude/skills/worktree-manager/scripts/merge-worktree.sh .claude/skills/worktree-manager/scripts/merge-worktree.sh 2>/dev/null | head -1)"\n` +
1112
1158
  ` bash "$MERGE_SH" --worktree ${sharedCtx.worktreePath} --manifest /tmp/new2-merge-status-${firstCard}.txt --skip-checks\n` +
@@ -91,6 +91,30 @@ Before picking a tier, classify the query:
91
91
  ~8–12M tokens with three coders independently re-discovering one NUL byte in a
92
92
  single multi-hundred-KB `data.ts`.)
93
93
 
94
+ ## Large-file read discipline (hot files)
95
+
96
+ A **hot file** is any source file **> 800 lines** that multiple agents touch in the
97
+ same run (a form client, a data layer, a locale dictionary). Forensics on real
98
+ `/new` batches showed hot-file FULL re-reads are the dominant repeated-read cost —
99
+ NOT the framework modules (read 1× each): one 2110-line client component (~29k
100
+ tokens per full view) was read **28 times** across architect → coder → fixer →
101
+ verifier spawns in a single run, because each fresh context re-read it whole.
102
+
103
+ Rules (every agent, every runtime):
104
+
105
+ 1. **Map once, range-read after.** The `codebase-architect` baseline carries a
106
+ `## Hot-File Map` for every in-scope file > 800 lines (per-symbol line ranges —
107
+ see `codebase-architect.md` § terse contract). When you need a hot file and a
108
+ map exists, `Read` the **target range only** (offset/limit around the symbol),
109
+ never the whole file.
110
+ 2. **No map → locate first, then range-read.** `rg -n "<symbol>"` (or LSP
111
+ go-to-definition when the flag is on) to find the line, then `Read` a bounded
112
+ window around it. A whole-file `Read` of a hot file is justified ONLY when the
113
+ edit genuinely spans dispersed regions of it — say so in one line when you do it.
114
+ 3. **Briefs pass paths, not bodies.** An orchestrator briefing an agent passes the
115
+ baseline **path** (e.g. `/tmp/arch-baseline-<CARD-ID>.md`), never its inlined
116
+ content — and never inlines a hot file into a prompt.
117
+
94
118
  ## Fallback rules
95
119
 
96
120
  - LSP tool not loaded / plugin missing → silently fall back to Grep with the
@@ -654,6 +654,33 @@ card's `owner_agent: coder` (correct for its logic/review ownership) sent the UI
654
654
  `coder`. `owner_agent` still governs the review/ownership semantics; it just does not pick
655
655
  the build agent when a mockup is present.
656
656
 
657
+ ### 6. Verification-Lane Ledger — planned vs executed (no silent lane under-run)
658
+
659
+ Checks 1–4 above each guard ONE lane. This check guards the **whole matrix**: the run
660
+ that silently executes *fewer lanes than it planned*. The driving lesson (FEAT-0064,
661
+ post-mortem of run `a73a52d0`): a mockup card went through "fidelity review" in which
662
+ **only** the structural verifier ever spawned — no `visual-fidelity-verifier`, no
663
+ `ui-quality-critic` — and nothing in the verdict recorded the under-run; the escaped
664
+ polish surfaced later as a hand-worked bug card. A lane that is planned and then not
665
+ executed is invisible to per-lane guards, so the ledger is reconciled globally:
666
+
667
+ - **At plan time** the verifying orchestrator (`/e2e-review` Phase 2; the degraded
668
+ direct-trio path in `/ui-implement`; the `new2.js` structural lane) derives, per
669
+ route/surface, the set of REQUIRED lanes — `structural` (when a code-form mockup
670
+ exists), `visual` per in-scope viewport, `quality` (when a quality oracle exists),
671
+ `functional` (when scenarios exist) — and persists it.
672
+ - **At verdict time** it reconciles planned vs executed. Every planned lane must be
673
+ `ran` or `skipped(<authorized reason>)` where the authorized reasons are ONLY the
674
+ ones this protocol enumerates (pixel-diff pre-filter pass, `render_unavailable` with
675
+ structural covered, `no_quality_oracle`, harness-render fidelity guard,
676
+ desktop-only route for the responsive pass). A planned lane that is simply
677
+ **`not_run`** synthesizes a **`verification-coverage-gap`** (Critical) — the matrix
678
+ twin of `coverage-gap`: not fixable by an agent, bypasses the self-heal loop, maps
679
+ to `blocked`. A verdict of `passed` with an unreconciled lane ledger is a protocol
680
+ violation, and no self-served "accepted (harness limit)" style verdict exists —
681
+ accepting an un-run lane requires the explicit override path (human reason) or the
682
+ caller's escalation, never the verifier's own discretion.
683
+
657
684
  ### Gating philosophy (calibration)
658
685
 
659
686
  Vision-model reliability research (*MLLM as a UI Judge*, 2025) shows multimodal judges are