fix(knowledge): pair instruction-selection evidence and close cache/consumer gaps (#2672) - #2781
Conversation
…onsumer gaps (#2672) - knowledge-injector context cache key now fingerprints every payload input (briefing, rejected lessons, run memory, escalations, latest drift report), read once per invocation and shared with the assembly path; a changed instruction set invalidates the cache on identical conversational context - new paired cached-vs-uncached evaluation: /swarm memory evaluate --instruction-pairing writes .swarm/memory/instruction-pairing-report.json with per-pair quality/latency/cache-reads/uncached-cost/rendered-prefix, retained negative results, explicit measurement denominators, and no percentage/savings fields; instruction_set_digest feeds #2503 lineage - measured reachability dispositions for the six bundled skills (src/config/bundled-skill-dispositions.ts), all reachable with verified consumers incl. the .claude adapter trees - six named consumer-control tests + guards: unreachable-and-unretired fails closed (missing literal hit can never delete); retirement requires full inventory parity; runtime-closure scan extended to all consumer trees - docs: cache key + invalidation inputs, pairing denominators, disposition table; release fragment
…nal-critic F2) - existsSync(join(ROOT, consumer)) replaces the hand-converted backslash form that failed on ubuntu (join normalizes separators per platform) - instruction-pairing harness clears swarmState.activeAgent for its synthetic sessions in finally (invariant-8 hygiene, final-critic F4)
Drift check reportFound 2 drift finding(s): 0 error, 0 warning, 2 notice. required-check-contract (2)
|
…ction-selection-caching
There was a problem hiding this comment.
🔵 Needs a closer look
It modifies the core per-turn knowledge-injector cache path with cross-platform and latency implications that cannot be fully verified here, so final human review is warranted despite only minor nits found.
Pull request overview
This PR is the capstone of Workstream E (#2672). It closes three gaps in instruction selection/caching: (1) the architect knowledge-injector's context cache was keyed only on conversational context + corpus generation, so a changed embedded input (curator briefing, rejected lessons, run-memory summary, escalations, latest drift report) was re-served stale; (2) no surface paired cached-vs-uncached instruction outcomes; (3) bundled-skill consumption was asserted only via inventory lists and a scan rooted at .opencode/skills/, missing .claude/.agents consumer trees.
Changes:
- Add a payload-input fingerprint to the injector's cache key, reading each embedded input once per invocation and sharing it with the miss-path assembly (no double reads).
- Add
src/memory/instruction-pairing.ts+/swarm memory evaluate --instruction-pairing, producing a durable.swarm/memory/instruction-pairing-report.jsonwith absolute (no-percentage) paired measurements and retained negative results. - Add measured reachability dispositions for the six named bundled skills plus consumer-control tests and a tree-wide closure scan; docs, release fragment, and retention-registry row updated.
File summaries
| File | Description |
|---|---|
src/hooks/knowledge-injector.ts |
Adds payload-input fingerprint to the cache key; shares single pre-read across the miss path |
src/memory/instruction-pairing.ts |
New paired cached-vs-uncached evaluation runner over the real injector hook |
src/memory/index.ts |
Re-exports the new pairing API from the memory barrel |
src/commands/memory.ts |
Wires the --instruction-pairing flag and summary/JSON output |
src/config/bundled-skill-dispositions.ts |
New measured dispositions for the six bundled skills (all reachable, verified) |
tests/unit/skills/bundled-skill-consumer-controls.test.ts |
Six per-skill controls + retirement/parity guards |
tests/unit/skills/bundled-skill-runtime-closure.test.ts |
Tree-wide consumer-closure scan across all skill trees + src |
tests/unit/memory/instruction-pairing.test.ts |
Contract tests for the pairing report (has unused imports) |
tests/unit/hooks/knowledge-injector-cache-fingerprint.test.ts |
Cache invalidation regression tests (has unused imports) |
tests/unit/commands/memory.test.ts |
Usage-string update for the new flag |
scripts/retention-registry.data.ts |
Registers the new report artifact row (category 7) |
docs/{configuration,skills,commands}.md, docs/observability-retention-registry.md, docs/releases/pending/2672-*.md |
Documents cache key/invalidation, pairing denominators, dispositions, release note |
I verified all nine disposition consumer files contain the required file:.swarm/bundled-skills/<slug>/SKILL.md runtime directive, that buildContextCacheKey has a single updated caller, and that the injector reads are placed after the non-architect early returns so only the architect path pays the added I/O.
Review details
- Files reviewed: 16/16 changed files
- Comments generated: 3
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| if (parsed.instructionPairing) { | ||
| // #2672: paired cached-vs-uncached instruction-selection control. The | ||
| // durable report lands at .swarm/memory/instruction-pairing-report.json | ||
| // and the summary carries absolute measurements only (no percentages). | ||
| const pairingReport = await runInstructionSelectionPairing({ | ||
| directory, | ||
| writeReport: true, | ||
| }); | ||
| if (parsed.json) | ||
| return `${JSON.stringify(pairingReport, null, 2)} | ||
| `; | ||
| const negativeCount = pairingReport.pairs.filter( | ||
| (pair) => pair.negative_result, | ||
| ).length; | ||
| return [ | ||
| '## Instruction Selection Pairing (#2672)', | ||
| '', | ||
| `- Paired tasks: \`${pairingReport.pairs.length}\``, | ||
| `- Negative results retained: \`${negativeCount}\``, | ||
| `- Cache invalidation verified: \`${pairingReport.cache_invalidation.verified}\``, | ||
| `- Instruction set digest: \`${pairingReport.identity.instruction_set_digest.slice(0, 16)}\``, | ||
| '- Report: `.swarm/memory/instruction-pairing-report.json`', | ||
| '', | ||
| 'Per-pair latency, cache reads, uncached cost, and rendered prefix', | ||
| 'lengths are in the report with their measurement denominators;', | ||
| 'use `/swarm memory evaluate --instruction-pairing --json` for the full report.', | ||
| ].join('\n'); |
…aph (#2672) - state-mock-transitive-stubs + system-enhancer plan mock gain the bindings knowledge-injector needs (the barrel now re-exports instruction-pairing, widening every barrel consumer's import graph) - registry args + regenerated docs/commands.md carry --instruction-pairing byte-exactly from the generator
Swarm PR Review — #2781Scope: merge-base Pipeline: 6 base explorer lanes (intent-architecture, correctness-state, tests-falsifiability, security-trust, reliability-performance, compatibility-delivery) → 3 consolidated risk-family micro-lanes (11 families evaluated) → 22 candidates → 4 independent reviewer passes → critic challenge on all HIGH/MEDIUM findings. Confirmed findings (post-critic)F-1 (MEDIUM) — Paired quality metric is saturated by construction, doesn't differentiate per task F-2 (MEDIUM) — Dead, containment-bypassing F-3 (MEDIUM) — New cache-fingerprint/pairing tests never assert the positive cache-hit signal F-4 (LOW) — PR body's F-5 (LOW) — F-6 (LOW, latent/unreachable) — Non-session-scoped concurrency hazards in the pairing test harness F-7 (LOW) — F-8 (LOW) — Cross-session injector cache is not session-keyed (pre-existing, not introduced) Other LOW-severity / advisory items (reviewer-confirmed, not critic-escalated — full detail available on request)
Suppressed / disproved
Verdict: REQUEST_CHANGESNo CRITICAL or unresolved HIGH findings. Recommend resolving before merge: F-1 (fix or scope-down the paired-quality metric's saturated corpus), F-2 (delete or wire the dead Full candidate ledger, per-lane evidence, and reviewer/critic transcripts available on request. Generated by a Profile-B (Claude Code native subagents) swarm-pr-review: 6 base lanes, 3 micro-lanes covering all 11 risk families, 4 reviewer passes, 1 critic pass — 22 candidates tracked to final disposition. 🤖 Generated with Claude Code |
Swarm PR Review — #2781 (re-review at current head)Scope: merge-base Verdict: APPROVE — no CRITICAL or HIGH-surviving findings; all 16 verified findings are MEDIUM/LOW/INFO and non-blocking. Nine are actionable and are being resolved in a follow-up feedback pass on this PR before merge (tracked as PRR-001..016 below); the rest are by-design/disclosed dispositions. Verified findings (actionable, being fixed)
Verified findings (dispositioned without code change)
Disproved candidates (transparency)
AttestationAll 11 micro families settled: 8 MATCHED (untrusted-input-boundaries, concurrency-state, dependencies-build-release, api-schema-migrations, test-infrastructure, privacy-observability, generated-provenance, unclassified-risk) with per-family rows; 3 NOT_TRIGGERED with absence evidence (auth-identity-secrets, subprocess-platform, ui-accessibility-i18n). Obligation check: all four issue-#2672 ACs delivered and wired (AC1 pairing report, AC2 negative retention + no-percent deep-scan, AC3/AC4 six dispositions + fail-closed controls); non-goals respected (no #2503 duplication — harness-optimizer diff empty; no skill deletions; no percentage claims). The actionable findings above are being resolved now via the feedback protocol on this same PR; merge follows after CI re-greens. |
- PRR-001: reportPath containment — absolute/.. paths now rejected - PRR-002: resolve pre-existing merge-conflict markers in the retention docs table (both rows kept; both exist in the data registry) - PRR-003: command-branch coverage in a colocated new test file (memory.test.ts is FR-006-over-cap and must not grow) - PRR-004: drop unused mkdtempSync/tmpdir imports - PRR-009/012/016: docs + JSDoc clarifications (flag exclusivity, label charset constraint, instruction_set_digest scope) - PRR-011: skills.md parallel-work-check row gains the swarm-pr-feedback by-name mention Dispositions (no code change): PRR-005/006 safe-direction over-invalidation (design), PRR-007 accepted coverage gap (3/5 classes + C2), PRR-008 wiring would break the identical-outcome contract, PRR-010 repo pattern precedent, PRR-013 single-threaded CLI, PRR-014 append-only events file + basis disclosure, PRR-015 disclosed honest-negative contract.
Feedback closure ledger — PR #2781 review findingsFix commit
Gates after fixes: biome ci 0, tsc 0, check:retention 0 (123 rows), check:test-file-cap 0, check:test-tmpdir 0, docs regen drift 0, retention-rows 17/0, new command test 1/0, pairing 6/0, fingerprint 4/0. Reviewer APPROVE + final critic APPROVE at |
🤖 Multi-Stage PR ReviewPipeline: MiniMax-M2.7-highspeed (orientation) (context pack) → MiniMax-M2.7-highspeed (explorer) + MiniMax-M2.7-highspeed (explorer B) (parallel explore, distinct lenses) → GLM-5-turbo (critique) ↔ GLM-5-turbo (critique) (cross-critique) → MiniMax-M2.7-highspeed (fallback arbiter) (arbiter: blind-spot + synthesize) PR Reviewer — opencode-swarm🔍 PR Intent
📦 Implementation SummaryThe PR adds:
✅ /
|
| Obligation | Status | Evidence (file:line) |
|---|---|---|
| O-001 | SUPPORTED |
src/hooks/knowledge-injector.ts:93–165 — five inputs read once, fingerprinted, shared with assembly; cache key at :1177 gains instructionFingerprint; briefing/rejected/run-memory/drift/escalation all covered |
| O-002 | SUPPORTED |
src/memory/instruction-pairing.ts:435–577 — runInstructionSelectionPairing runs sequential tasks, two-arm measurement, invalidation protocol, digest, report write; src/commands/memory.ts:522–550 wires --instruction-pairing |
| O-003 | SUPPORTED |
src/config/bundled-skill-dispositions.ts:44–73 — all 6 slugs present, all reachable, consumers listed with verified paths |
| O-004 | SUPPORTED |
tests/unit/skills/bundled-skill-consumer-controls.test.ts:155–321 — 6 named controls + 2 guard tests; unreachable-and-unretired fails, partial retirement fails |
| O-005 | SUPPORTED |
tests/unit/skills/bundled-skill-runtime-closure.test.ts:169–229 — scan added over 4 consumer trees including .claude and .agents |
| O-006 | SUPPORTED |
scripts/retention-registry.data.ts:3138–3164 — instruction-pairing-report row present |
| O-007 | PARTIALLY_SUPPORTED |
docs/observability-retention-registry.md — new row registered but a pre-existing row is silently dropped (see CRITICAL below) |
🚨 Confirmed Findings
[CRITICAL] Retention registry doc out of sync: pr-feedback-loop-state row silently dropped during merge
- Location:
docs/observability-retention-registry.md— entirepr-feedback-loop-staterow absent from rendered table - Why it matters: The CI gate
bun run check:retentionenforces that every row ID inscripts/retention-registry.data.tsappears verbatim indocs/observability-retention-registry.md. Thepr-feedback-loop-staterow is present in the data file (under#2502ownership) but missing from the rendered doc table. The diff itself reveals the cause: three-way merge conflict markers (<<<<<<< HEAD,=======,>>>>>>> origin/main) are embedded in the doc around thepr-feedback-loop-state/speckit-checkoff-ledgerarea, confirming the row was dropped when the merge conflict was resolved. - Evidence: Diff hunk shows
pr-feedback-loop-staterow with<<<<<<< HEAD/=======/>>>>>>> origin/mainconflict markers framing its deletion, followed byspeckit-checkoff-ledgeralso marked for deletion — yet the final rendered doc showsspeckit-checkoff-ledgerpresent butpr-feedback-loop-stateabsent. The data filescripts/retention-registry.data.tsstill contains thepr-feedback-loop-staterow (verified by grep against the full file). The doc row that belongs there per the gate's row-id presence check is simply gone. - Fix direction: Re-add the
pr-feedback-loop-staterow todocs/observability-retention-registry.mdwith its original content:Then remove the conflict markers.| `pr-feedback-loop-state` | .swarm/pr-feedback-loop-state.json + pr-feedback-evidence/{seq}.json + pr-feedback-loop-cleanups/ | operational | one rewritten state (200-correlation FIFO) + one evidence JSON per oversight dispatch + one receipt per cancellation (per-trigger) | full-file: Zod-validated state read | retained — cross-run idempotency basis (digests/budgets); close: neither | not a defect — this-gate; direct-file exemption (#2502) |
🔬 Unverified but Plausible Risks
_ None that survive the structural check — all speculative concerns either have runtime guards or were verified by the test suite's mutation probes (per PR acceptance criteria)._
🧪 Test / Coverage Gaps
- Gap:
verifyCacheInvalidation()ininstruction-pairing.tsuses the samesessionTempRootMap asrunTaskPairing, with matching session IDs. If called concurrently (not currently the case — sequential inrunInstructionSelectionPairing), they would race on the same map keys. No test exercises concurrent invocation.- Severity: Low — API is internal, sequential call pattern is documented,
finallyblocks are correct for the current call graph. - What would verify it: A test that interleaves
runTaskPairingandverifyCacheInvalidationon overlapping session IDs.
- Severity: Low — API is internal, sequential call pattern is documented,
📋 Shipped-vs-Claimed Gaps
_ None — all seven obligations are delivered and grounded in the diff._
📝 Merge Recommendation
[BLOCK]
The retention registry CI gate (bun run check:retention) will fail at the shipped head because the pr-feedback-loop-state row ID is absent from docs/observability-retention-registry.md. This is a mechanical regression introduced by a failed three-way merge that was never caught.
| Check | Result |
|---|---|
| No CRITICAL findings | ❌ (retention-registry doc out of sync) |
| No unresolved STEALTH_CHANGE | ✅ |
| No UNSUPPORTED obligations | ✅ |
| Test coverage adequate | ✅ |
| No hardcoded secrets | ✅ |
| All async errors handled | ✅ |
| Input validation present | ✅ |
| No broken agent role boundaries | ✅ |
| Prompt format contracts intact | ✅ |
| Lockfile consistent | ✅ |
🔁 Validation provenance
| Finding | Disposition | Reason |
|---|---|---|
pr-feedback-loop-state doc row dropped |
KEPT — CRITICAL | Diff contains merge conflict markers framing the row's deletion; doc table renders row absent; data file still has it; gate will fail |
verifyCacheInvalidation / runTaskPairing Map race |
DROPPED | Sequential call graph, correct finally cleanup, internal API, no concurrent invocation |
| Pair test message carries wrong user text | DROPPED | quality_outcome: 'identical' by design on offline corpus — lastUserMessage does not affect the quality signal |
getLiveContextModelIdentity / getLiveContextWindow stubs unused |
DROPPED | Safe no-op stubs; pattern consistent with existing stubs in state-mock-transitive-stubs.ts |
--instruction-pairing and --json conflict |
DROPPED | Mutually exclusive by design per PR contract; --json controls output format only |
| New session state not cleaned up | DROPPED | finally block explicitly calls swarmState.activeAgent.delete(sessionId) and sessionTempRoot.delete(sessionId) |
Blind-spot: pr-feedback-loop-state doc row missing |
ADDED | Found independently during diff reading — same finding as confirmed CRITICAL above |
Approve once the pr-feedback-loop-state row is restored to docs/observability-retention-registry.md and conflict markers are removed.
🔒 Reviewed by a 3-model cross-family adversarial debate (architect → dual-lens parallel explorers → cross-critique → arbiter) for high recall with low false-positive noise. Findings are advisory — verify before acting.
Closes #2672
PR head: 0c4d07d
Summary
Workstream E capstone (#2672, PR 09 of 09): paired cached-vs-uncached outcome evidence for instruction selection, instruction-cache invalidation on instruction-set change, and measured reachability dispositions plus consumer controls for the six bundled skills named by the issue.
Root Cause
Three gaps. (1) The architect knowledge-injector's context cache was keyed on the conversational context plus the knowledge corpus generation only; the cached instruction text also embeds the curator briefing, rejected lessons, run-memory summary, escalations, and the latest drift report — inputs read only on the miss path — so a changed instruction input was re-served stale while the context key held (reproduced: briefing v1 still injected after the file changed to v2 on identical context). (2) No surface anywhere paired cached vs uncached instruction paths on the same task/model/budget, and the memory evaluation comparison schema hardcodes cost as unavailable. (3) Bundled-skill consumption was asserted only by inventory lists and a closure scan rooted at
.opencode/skills/<bundled-slug>/, leaving the.claudeconsumer trees (commit-pr, editing-skills) outside every control and nothing failing when a skill loses its last consumer.Fix
src/hooks/knowledge-injector.ts: the cache key now includes a payload-input fingerprint — every input the cached text embeds is read ONCE per invocation (shared with the assembly path, so nothing is read twice) and hashed canonically (raw bytes for the briefing, last-20 rejected entries, run-memory summary string, escalations via the_internalsseam, latest drift report asphase+ key-sorted stable JSON), each fail-open to0.src/memory/instruction-pairing.ts(new, re-exported from the memory barrel): the paired cached-vs-uncached control — deterministic offline tasks materialized into disposable temp stores via the store's own JSONL writer with full entry schema and__PAIRING_LABEL_<label>__sentinels, hive disabled, the REAL injector hook per arm (cold instance = regeneration reference; warm instance replaying identical context = cached arm, hit detected by events-file delta). Reports per pair: quality (selected labels vs expected), per-arm latency, cache reads with basis, uncached cost (paired attribution), rendered prefix chars at the host-renderable carrier boundary,quality_outcomeandnegative_result(identical-outcome pairs are RETAINED negatives), plus explicit measurement denominators — and structurally NO percentage/savings fields.identity.instruction_set_digestchanges when the instruction set changes and is the handle a HarnessOpt lineage record ([Workstream F] PR 11 of 21: Ship the governed HarnessOpt capstone with held-out validation #2503, the governed held-out owner) can reference; no harness-optimizer code was modified.src/commands/memory.ts:/swarm memory evaluate --instruction-pairingruns the pairing and writes.swarm/memory/instruction-pairing-report.json.src/config/bundled-skill-dispositions.ts(new): measured dispositions for exactly the six skills — allreachable, with their verified consumer files.tests/unit/skills/bundled-skill-consumer-controls.test.ts(new): six named per-skill controls plus the two guards — a skill with zero references and no retirement FAILS the control (a missing literal search hit can never delete), and retirement requires full inventory parity (BUNDLED_PROJECT_SKILLS, package.json#files, package-smoke) plus zero live references.tests/unit/skills/bundled-skill-runtime-closure.test.tsgains the tree-wide consumer scan (all three skill trees + src, self-reference skip) that closes the scan-root gap.docs/configuration.md(cache key + invalidation inputs; pairing denominators),docs/skills.md(disposition table + retirement rule),docs/commands.md(flag), and the release fragmentdocs/releases/pending/2672-instruction-selection-paired-evidence.md.Recurrence Prevention (defect class)
.claudeconsumers were invisible pre-fix) and by mutation (neutering the unreachable guard flips the AC6 check to exit 1).Invariant audit
bun:imports;bun run buildOK;node --input-type=module -e "await import('./dist/index.js')"OK; bundle-portability 10/10; bundle-plugin-shape 2/2..swarm/memory/; fixture stores live inos.tmpdir()mkdtemp roots, never under the caller's.swarm/(reviewer + critic verified)._internals/DI seams, no newmock.moduletargets (check:mock-cleanupand the allowlist ratchet incheck:invariantsboth exit 0); per-file isolation throughout; temp paths viaos.tmpdir()+path.join.pairing-*sessions inswarmState.activeAgentand now deletes them infinally(final-critic-driven fix); final critic verified zero residual keys empirically.role:'system'construction in plugin injection (reviewer + critic verified the harness's system-role entries are host INPUT, not plugin injection).bun run drift:check --enforcefindings are the pre-existing main-tree class (verified by stash A/B on a clean tree).docs/releases/pending/2672-instruction-selection-paired-evidence.md; no version/CHANGELOG/manifest edits.Test plan
CI-gate registration:
bun run check:retention-> 123 rows pass (new rowinstruction-pairing-report);bun run check:test-tmpdir-> 0 violations; all 24 quality-job gates re-ran green locally.Regression test:
bun test tests/unit/hooks/knowledge-injector-cache-fingerprint.test.ts-> 4 pass / 0 fail (stable-input cache persistence; briefing/run-memory/rejected-lesson invalidation)Pairing contract:
bun test tests/unit/memory/instruction-pairing.test.ts-> 6 pass / 0 failConsumer controls:
bun test tests/unit/skills/bundled-skill-consumer-controls.test.ts-> 9 pass / 0 failClosure extension:
bun test tests/unit/skills/bundled-skill-runtime-closure.test.ts-> 9 pass / 0 failImpacted suite:
bun test tests/unit/commands/memory.test.ts-> 19 pass / 0 fail; all 12knowledge-injector*files green per-file (C8, base and head)Frozen acceptance checks: 8/8 PASS (C2 DISCRIMINATING RED->GREEN; six NEW-SURFACE ERROR->GREEN with mandatory mutation probes; C8 PRESERVING GREEN->GREEN via a sanctioned CHECK_WRONG AMEND for a trace-harness worktree-junction defect — assertions unchanged)
Lint/type/build:
bunx @biomejs/biome@2.3.14 ci .exit 0 (4 pre-existing warnings in untouched files);bunx tsc --noEmitexit 0;bun run build+node --input-type=module -e "await import('./dist/index.js')"OK; bundle-portability 10/10, bundle-plugin-shape 2/2;bun run check:invariants,check:events,check:mock-cleanup,check:test-file-capall exit 0.bun run drift:check --enforcereports one PRE-EXISTING error (.github/workflows/pr-standards.ymlWORKFLOW_CHANGED_AFTER_CAPTURE) reproduced identically on a clean main tree — untouched by this PR.Deferred-work scan: scan-deferred.sh -> clean
Regression Protection
Acceptance Criteria -> Evidence
bun test tests/unit/memory/instruction-pairing.test.ts6/0; report fields verified in source and by the critic's own probe (cache_reads=1 basis=events-file-delta vs 0)handleMemoryEvaluateCommand(['--instruction-pairing'])end-to-end -> summary +.swarm/memory/instruction-pairing-report.jsonnegative_result: truepairs retained withnegative_results_retained: trueRisk and Rollback
fix/issue-2672-instruction-selection-caching; no migrations, no config flags, no state changes to undo (the report file is disposable output).quality_outcome'identical' by design (honest negative); provider-side cache-read tokens are not captured offline (the issue's evidence boundary permits reporting unavailable fields as such — the report's denominators state what is measured).Waivers (or none)
none
Merge status
Awaiting explicit user approval; not merged. (The branch merged current main after review to clear the pre-existing release-owner-guard false positive — PR #2770 class, zero release-owned files in the effective diff; all three gates re-stamped APPROVE at the merged head.) Full-resolution gate ladder at the shipped head: plan-critic APPROVE (Rounds 1-2, re-stamped Rounds 3-4 after harness-lock fallbacks, disclosed in 06), independent implementation review APPROVE (Rounds 1-2, cross-model MiniMax-M3 swarm-reviewer with its own re-runs and mutation probes), final critic APPROVE (Rounds 1-2; fallback dispatch from the pinned Kimi-K3 swarm-critic after repeated harness
database is lockedfailures — disclosed in 09).