fix(fabrika-cli): scope the exit-code alignment guard to the shipped verb registry (#5213) - #5295
Conversation
…verb registry (#5213) The coverage check scanned directories holding a codes.ts, so a group with no table was invisible to it. eval was that group: no table, every refusal on 1, and registered in neither ALIGNED_GROUPS nor UNALIGNED_GROUPS — the loosest exit discipline in the package sitting under a guard that reported green over it. Scope now comes from registry.ts, so a group is checked because the CLI ships it. coverageGaps reds on any group it cannot classify, from either side, and throws rather than reporting an all-clear from an empty scan (ADR 0092). UNTABLED_GROUPS records adr and spend as tracked gaps (#5294) instead of leaving them silent. eval gets a codes.ts: MALFORMED_DOCUMENT and ZERO_SCOPE imported from the base, INTEGRITY_VIOLATION and RUNS_NOT_EXECUTED on its own. All 21 process.exit sites are re-seated — proven outcomes onto the table, usage errors and failed reads onto verb.ts's FAILED.
No preview deploy
|
|
review-code: PASS @ a6f7d48 — merge-ready Verified against issue #5213's acceptance criteria, one criterion at a time, from the PR head Routing. §CP, both axes — not control plane. Acceptance criteria
The central claim: the guard can now SEE — verified as a class, not as an instanceThis is guard blindness — the green was affirmative and wrong — so it was gated on whether the
The 21 re-seated exit sites — audited individually, and a correction to the PR bodyEvery site read at head and judged proven-outcome vs failure-to-invoke. The seating is correct at
Total is 21 either way; the split is not. The five read-failed catch arms are The six Consumers of the old codes: none found. Nothing outside the package scripts Finding (non-blocking, but please fix the body before merge). The mapping table in Deviations (§DEV)
Other sub-gates
Notes, not gates
review-code does not merge. This is a merge-ready signal, nothing more. Verdict-written: 2026-08-10T06:14:14Z |
Fixes #5213
What changed
Two defects, one root. The root was the guard's scope.
The guard can now see a group that ships no table.
codeTableGroupsInfinds directories holding acodes.ts, and the coverage test used that as its scope. A group with no table was therefore not in the set being checked, so it could sit in no registry and the assertion stayed green — over the group with the loosest exit discipline in the package. Scope now comes fromregistry.ts: a group is checked because the CLI ships it.coverageGapsreports four gaps and the test reds on each:unclassified— a group shipped by the registry, or carrying a table on disk, that no registry here classifies. This is the blindness, pinned.unshipped— a registration for a group the CLI no longer ships.untabledWithTable/tableMissing— the untabled record gone stale in either direction.It throws
ZeroCoverageScoperather than reporting no gaps when either side of the scan is empty (ADR 0092) — a registry that failed to load would otherwise report zero groups and therefore zero gaps.UNTABLED_GROUPSrecordsadrandspendas the two remaining registered groups with no table, each with the reason it is still unchecked and a tracking issue. That is what lets the guard treat any other untabled group as a failure instead of silence.evalgets a table and its refusals get seated on it.packages/fabrika-cli/src/eval/codes.tsimportsMALFORMED_DOCUMENTandZERO_SCOPEfrom the base rather than restating the numerals (thereview-uidiscipline), and addsINTEGRITY_VIOLATION = 12andRUNS_NOT_EXECUTED = 13on its own account.evalis registered inALIGNED_GROUPS.All 21
process.exitsites ineval/command.tsare re-seated.GATE_FAIL_EXIT_CODE = 1is gone:MALFORMED_DOCUMENT(a named JSON artifact read in full that does not conform)ZERO_SCOPE(an eval set that decodes and carries zero cases)INTEGRITY_VIOLATION(keeps: decodes, breaks its own integrity rules — its own seat because no decoder change helps)RUNS_NOT_EXECUTED(run: the suite completed, some planned runs did not execute)FAILEDfromverb.ts(six flag-value usage errors, and five catch-arms —check,report,cases,run,keeps— where the read failed before the verb could judge anything) — correctly still1, which is what the convention reserves it forThe regression test fails against pre-fix code
Verified by removing
eval: EVAL_SEATSfromALIGNED_GROUPSand re-running:That is the acceptance criterion's own check. The old assertion could not produce it: with no
eval/codes.tson disk,evalwas not inonDisk, soonDisk == registeredheld and the suite was green. Three further tests pin the property directly rather than the instance — a shipped group with no table and no registration is reported, a table on disk that no registry classifies is reported, and an empty scan throws instead of returning no gaps.Verification
pnpm typecheck— 31/31 tasks passpackages/fabrika-clisuite — 195 files, 2825 tests passpnpm lint:worktree— cleanDeviations
adrandspendare recorded, not fixed. Both allocate proven codes per-verb rather than from a group table, andadralready collides with itself (relate-verb.tsseatsNO_SUBJECT = 3,sweep-verb.tsseats it4). The issue's triage note scoped them out; the change makes them visible inUNTABLED_GROUPSwith a stated reason. Follow-up filed as fabrikaadrandspendallocate exit codes per-verb instead of from a group table #5294. Naming them "untabled" is an admission of a tracked gap, and the guard asserts each entry carries a reason and that none of them actually ships a table.eval/command.tsstill callsprocess.exitdirectly rather than returningVerbOutcomes throughverb.ts'sanswer/refuse. The issue notesevalimports nothing fromverb.ts; this importsFAILEDand seats every refusal on a named constant, which is what the acceptance criteria ask for. Converting the five verbs to pureVerbOutcome-returning cores with a thin adapter is a larger refactor and is not in this diff.verb.ts's reserved-code convention, ADR 0092's zero-scope rule) rather than making a new one.