Merge main into develop — resolve the 17-file divergence, unblocking #284 - #302
Merged
Merged
Conversation
fix: three P0s, and twelve rules that promised enforcement and delivered nothing (GT-572, GT-595, GT-604)
…the exit taxonomy (GT-580)
Closes the two open criteria of GT-580.
CRITERION 2 — `--format json` writes DATA ONLY to stdout.
Output formatting and process exits were ALREADY centralized: `installMachineChannelGuard`
(main.ts, armed before the command graph boots) reroutes every non-JSON stdout write to
stderr while a machine format is active, and `exit-codes.ts` + `BaseEvolithCommand.handleError`
map a thrown error onto the taxonomy in one place. What was missing was proof that the
centralization actually holds across the CLI: the existing subprocess spec pipes two
hand-picked commands. `machine-channel.registry-sweep.spec.ts` asks commander — the CLI's
own registry, built from the real AppModule — which commands declare `--format`, and sweeps
all 32 in real subprocesses with stdout and stderr on separate pipes. Each must hand back
exactly one parseable JSON document and exit from the taxonomy. The enumeration has a floor,
so a sweep over nothing fails instead of passing vacuously.
Observed red: commenting out `installMachineChannelGuard()` turns `agents` (a clack banner)
and `init-wizard` (a progress line) DIRTY while the other thirty stay clean.
CRITERION 3 — a ruleset with Rego parity fails a command that exits outside the taxonomy.
- `src/rulesets/cli/exit-code-taxonomy.rules.json` — CLI-EXIT-01 (no exit outside
{0,1,2,3}), CLI-EXIT-02 (the scan must be non-vacuous), CLI-EXIT-03 (the taxonomy may
not be widened to absorb an offender — otherwise CLI-EXIT-01 has a one-line escape).
- `src/rulesets/opa/cli-exit-code-taxonomy.rego` + tests — the same three ids decided over
the fact document the CLI's own producer emits. Aggregated into `main.rego`, and scoped
to `input.core.cli` so a satellite evaluation stays silent.
- `CliExitTaxonomyRuleHandler` — the native half, so `evolith validate` evaluates the rules
instead of reporting three blocking rules that never ran.
Observed red, end to end: planting `process.exit(7)` in a real CLI command file makes
`evolith validate --core .` emit a BLOCKING CLI-EXIT-01 naming the file, and the CLI exits 2
(BLOCKED). The same file drives the committed producer and the committed .rego to the same
verdict in `exit-code-taxonomy-ruleset.spec.ts`.
Derived artifacts moved with the corpus: native-handler 151 -> 154, ISO/IEC 5055 mapping and
the inventory summary regenerated.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
GT-580's exit-code taxonomy guards catch a status outside {0,1,2,3}, but
three commands stayed inside the taxonomy while contradicting their own
--format json envelope: gate-status exited 0 on an INTERNAL_ERROR envelope,
alias exited 1 (tool failure) instead of 3 (invalid input) on a usage
error, and init-wizard's catch-all collapsed a NonInteractiveError to
INTERNAL_ERROR/1 instead of preserving its VALIDATION_FAILED/3
classification. The registry sweep spec now asserts envelope.success
against status === 0 for every --format json command, which also caught
sdlc generate exiting 0 with success:false — its JSON branch returned
before reaching the process.exitCode assignment below it.
The registry sweep and Rego-parity ruleset landed in PR #278 close the stdout/stderr-purity half of criterion 2 (machine-channel.registry-sweep.spec.ts, 32/32 --format json commands clean) and criterion 3 (exit-code-taxonomy.rules.json + cli-exit-code-taxonomy.rego, opa test 7/7) — the taxonomy is now governed rather than merely scanned. Records the four envelope/exit-code mismatches the strengthened sweep found and fixed along the way. --format ndjson streaming and the Tracker cross-repo follow-up remain open, so status stays IN-PROGRESS.
09-reconcile-maturity.mjs --check failed CI's Validate documentation job: rulesetCount was stale at 174, one behind the exit-code-taxonomy.rules.json that PR #278 added. Regenerated via 09-reconcile-maturity.mjs.
…match it then found CI's required Test job turned up two things the strengthened sweep assertion missed or got wrong: 1. The assertion itself was too strict. `validate`/`evaluate` legitimately pair `createSuccessEnvelope` with exit 2 (BLOCKED) — the tool succeeded and produced a verdict, and the verdict happens to block. success:true does not imply exit 0; it only rules out TOOL_FAILURE/INVALID_INPUT, which success itself denies happened. Rewrote the assertion: success:true allows OK or BLOCKED, success:false must never be OK and, where the envelope names an ADR-0073 error.code, must match exitCodeForErrorCode's mapping for that code exactly — the same function BaseEvolithCommand.handleError already uses, so the sweep checks commands against the taxonomy's own source of truth instead of a second copy of it. 2. With that corrected, the sweep found a fifth real mismatch: `topology phase-artifacts` with no --phase emits an INVALID_PHASE envelope but hardcoded exit 1 (tool failure) instead of the 3 the taxonomy assigns that code. Fixed the same way as the other four: exitCodeForErrorCode instead of a literal. Updated the command's own unit spec, which had asserted the wrong exit code. Verified: full CLI suite 101 suites/1446 tests, tsc --noEmit clean.
…berg-90c1b4 GT-580: routes 5 exit-code/envelope taxonomy mismatches through the existing exitCodeForErrorCode/resolveExitCode/carriesCliExitCode helpers (sdlc gate-status, alias, init-wizard, sdlc generate, topology phase-artifacts), strengthens the registry sweep to assert envelope/exit-code agreement for all 32 --format json commands, and closes GT-580 criteria 2+3 (JSON-channel purity + Rego-governed taxonomy). All 7 required checks green.
The test passed --architecture and --arch-level, neither of which exist on the validate command (real flags are -a/--arch and -t/--topology). Commander rejected the unknown option before any command code ran, exiting 1 instead of the [0,2] pass/blocked verdict the test expected.
…e snapshot it guards
`native-evaluability-snapshot.json` declared in its own header that it was a
capture. Nothing captured it — there was no capture script in the repository. It
was hand-maintained, and it drifted: it said `documentation-only: 129` while Core
pinned 136, and it kept calling rules `unimplemented-native` after they got
handlers.
The guard was blind to that by construction. `iso-5055-mapping.test.mjs` compared
the snapshot's counts against six numbers typed into the test — the same six that
are literals inside the snapshot. Expected value and actual value were copies of
each other, so it passed for as long as the file went untouched, whatever Core
actually pinned. Its comment claimed it read `rule-corpus-triage.spec.ts`; it
never opened that file.
Drift does not stay contained. `build-iso-5055-mapping.mjs` stamps
`nativeEvaluability` onto every row of the mapping FROM the snapshot, so a stale
class is laundered into a 391-row artifact and overstates the handler backlog.
Three checks replace the six literals, each in the job that can afford it:
- `rule-corpus-triage.spec.ts` (core-domain jest, required on main) asserts the
committed snapshot against a FRESHLY COMPUTED triage — counts and the class of
every rule, in both directions. The real guard: the only place with the
dependencies to recompute the truth.
- `iso-5055-mapping.test.mjs` runs in the documentation job, which has no
node_modules, so it reads Core's counts OUT OF the spec that pins them.
Parsing a source file is a real coupling, stated rather than hidden: a missing
or reshaped literal THROWS, because failing to find the source of truth must
never read as agreement with it. Plus: the snapshot's header must agree with
its own body, and every class stamped into the mapping must match the snapshot.
- `capture-native-evaluability-snapshot.mjs --check` is the end-to-end
re-derivation.
The capture script is the missing half. It runs Core's REAL triage through
ts-node, resolved via `createRequire` from a package that DECLARES ts-node rather
than relying on workspace hoisting — otherwise a required check would be green
locally and red on a clean runner. Nothing is reimplemented: a second classifier
would be a second source of truth, which is the defect being removed. That
required moving the measurement out of the spec, where only jest could reach it,
into `test/rule-corpus-triage.ts` — that unreachability is why the snapshot was
maintained by hand.
It reads the committed file ONCE and keeps both views of that read. `--check`
decides whether what is on disk equals a fresh capture, so it must compare the
bytes it parsed; the first draft did `existsSync` then two separate reads, which
CodeQL correctly flagged, and which could report a diff it had not measured.
Order is written into both READMEs and into CI: recapture, THEN rebuild.
Rebased onto main, which moved three times while this was open — each time moving
the very numbers this branch pins (GT-595 and GT-632 closed twelve rules, GT-580
added three). Re-extracted against main's current spec, which now carries
`enforce` in the loader, then recaptured and rebuilt in order: 389 rules triaged,
`native-handler` 154, backlog 48; mapping 391 rows.
Also: the mapping guard ran in NO workflow. It does now. And `.gitignore` matched
`node_modules/` as a directory only, so the symlink a fresh worktree needs was
left untracked-but-visible and `git add -A` staged a link to an absolute path.
Verified: rule-corpus-triage 23/23; mapping guard 9/9; both `--check` modes
current; docs 01 and bilingual 04 green. Negative-tested both failure modes —
reverting a count goes red, and building the mapping from a stale snapshot goes
red on the stamp test.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
fix(standards): make the evaluability guard read Core, and capture the snapshot it guards
…a35ab fix(cli): use real --arch flag in E2E architecture validation test
… actually live at
The dead-reference ratchet was at 309 against a budget of 305, so
`Governance guards (GT-578)` has been red on main. Five of those dead references
are one transcription slip repeated: the closure records for GT-424, GT-621,
GT-629, GT-630 and GT-632 recorded their self-test as
node --test 34-check-skill-registry-parity.test.mjs
with no directory. Every one of those files exists at
`.harness/scripts/ci/<name>.test.mjs` — the `files` list in the same record names
the full path — so the evidence pointed at something real and quoted a command
that cannot run. That is exactly what the guard is for, and it is a repair rather
than a re-write: the command now names what was actually executed.
309 -> 304, which is under the existing budget, so the job goes green on the
repair alone.
The budget stays at 305 in this commit ON PURPOSE. The comment above that flag
records the rule and the burn behind it: it was once lowered to 303 from the
guard's computed basis, the runner then counted 305, and the job went red. Only a
count this job PRINTS may lower it. This branch's own run will print one; lowering
follows from that number, not from my machine's.
Verified: 41-validate-evidence-commands 304 dead / 0 failed; its self-test 34/34;
08-validate-tracking green over 574 closure records; the JSON still parses.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…nt this job printed The five repaired closure records took the count to 304, and the budget stayed at 305 in that commit on purpose: the comment above the flag records that it was once lowered from the guard's computed basis, the runner then disagreed, and the job went red. The rule it draws is that only a count THIS JOB prints may lower it. Run 30498743635 printed `304 dead (budget 305)`. That is the authorisation, so the budget moves now and not before. My local count also said 304 — worth noting and explicitly NOT the reason: the agreement is a coincidence of this checkout, and the printed line is the evidence. Leaving the slack would have meant one more dead reference could land silently, which is the whole failure mode a ratchet exists to prevent. Verified locally at the new setting: `--strict --max-dead 304` exits 0 with `304 dead (budget 304)`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
fix(evidence): qualify five recorded test commands with the path they actually live at
…his repo has
264 of the 304 dead references were one event: the `src/` refactor moved every
workspace, and the closure evidence kept quoting the pre-refactor layout. It is
the largest single block of dead evidence in the board and it had never been
migrated, so `41-validate-evidence-commands` has been carrying it as permanent
budget.
Rewritten only where the SAME artifact moved or was renamed, and only where the
new target was verified to exist in this checkout before a byte was written:
• workspaces gained the `src/` prefix — sdk/cli, apps/core-api and the six
packages (174 references);
• the retired `@evolith/*` scope became `@beyondnet/evolith-*`, with
`@evolith/core-api` becoming the bare `core-api` its package.json declares
and `@evolith/smart-cli` becoming `@beyondnet/evolith-cli` (29);
• `.harness/scripts/{validate-docs,check-bilingual-parity}.mjs` moved into
`ci/` with numeric prefixes; `review-input.test.mjs` into `ci/agentic/` (41);
• `reference/operations/` -> `product/operations/`,
`reference/infrastructure/docker-compose.yml` -> `product/infra/`,
`.bmad-core/agents/` -> `reference/core/foundations/agent-skills/`, and the
gap catalog into `gaps/` (18);
• source files quoted as targets gained the same `src/` prefix (19).
A further 27 read `npm run --workspace <path> <script>`, which npm accepts but
the guard parses by taking the token after `run` as the script name. Reordered to
the canonical `npm run <script> --workspace <path>`: same invocation, now
verifiable. The parser limitation is real and belongs in the guard — noted for
the .harness follow-up, since S-16 blocks editing it from here.
WHAT WAS DELIBERATELY LEFT DEAD (40)
A recorded command may be repaired when its artifact MOVED. It must not be
rewritten when the artifact was deleted, replaced, or absorbed — that would be
inventing evidence, which is worse than a red guard. So these stay:
• 15 `.harness/bin/opa` — a vendored, gitignored binary; environment-sensitive,
not a wrong path;
• 4 `.harness/scripts/*check-orphan-bilingual*` — absorbed into
`04-check-bilingual-parity`, not moved, so pointing them at 04 would claim a
command that was never run;
• 6 deleted artifacts (deploy/kubernetes/*, reference/infrastructure/helm/*);
• 5 root-level `npm test` / `test:cov` recorded without the workspace they were
run in, which cannot be inferred;
• 2 `pnpm info` / `yarn info` the guard mis-parses as npm scripts — valid
commands, second guard limitation;
• 2 `packages/mcp-tools`, 1 `apps/agent-sandbox`, 1 `dist/main.js`, and 4 prose
fragments the extractor reads as commands.
MEASURED, on this checkout, against origin/main:
dead 304 -> 40
executed 58 -> 83 (more commands resolve, so more actually run)
failed 11 -> 6 (five were failing BECAUSE of the stale path)
The remaining 6 failures are untouched by this change and were failing before it:
the bilingual terminology lint (red on main independently), a cross-repo
`--consumer` path, root cleanliness in a worktree, a validator that crashes, and
one command with prose appended to it.
The budget stays at 304. Only a count THIS JOB prints may lower it — the rule the
comment in ci-cd.yml records from a burn. This branch's run will print one.
Verified: 08-validate-tracking green over 632 gaps and 574 closure records;
41-validate-evidence-commands self-test 34/34; the JSON parses and its top-level
shape is unchanged (asserted by the migration itself).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…t this job printed Run 30500947483 printed `40 dead (budget 304)`. That is the authorisation, so the budget moves now and not in the commit that did the repair — the same two-step as 305 -> 304, and for the same reason: the comment above this flag records that the budget was once set from a derived figure, the runner disagreed, and the job went red. My local count also said 40. That agreement is a coincidence of this checkout and is explicitly not the evidence; the printed line is. Leaving 304 would have parked 264 references' worth of slack in a ratchet whose entire job is to refuse slack. Also recorded above the flag: what the remaining 40 ARE, so the next person does not read a non-zero budget as unfinished migration. They are not path rot. 15 reference a vendored gitignored binary, 4 name a script absorbed into another rather than moved, 6 name deleted artifacts, 5 are root-level `npm test` recorded without their workspace, 2 are valid pnpm/yarn commands this guard mis-parses as npm, and the rest are build output and prose the extractor reads as commands. Two of those are guard limitations, and they are named as the next real move. Verified locally at the new setting: `--strict --max-dead 40` exits 0 with `40 dead (budget 40)`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
fix(evidence): migrate the recorded closure commands onto the paths this repo has
…urity wave, 1.2.2 (GT-634) Separated from PR #277 on purpose: that branch also carries the GT-633 standards work, which now needs reconciling against #279 (the same fix landed on develop independently). None of that touches these six files, and an irreversible publish should not wait on a design merge. THE DEFECT. `@beyondnet/evolith-cli@1.2.1` and `@beyondnet/evolith-mcp@1.2.1` both declared `@beyondnet/evolith-sdk: ^1.1.0`, which resolves to EXACTLY 1.1.0: the SDK publishes 1.0.0, 1.1.0 and 2.0.0 with nothing in between, and a caret cannot cross a major. sdk@1.1.0 is from 2026-07-18; sdk@2.0.0 shipped the security wave on 2026-07-27; both consumers were published on 2026-07-28, AFTER 2.0.0 existed, still on the 1.x line. So `npm install @beyondnet/evolith-cli@latest` installed a pre-wave SDK, and the exposure GT-570 declared closed for "anyone installing latest" was not closed for this dependency. The lockfile made it concrete rather than cosmetic: it pinned the registry tarball of sdk@1.1.0, with integrity hashes, at src/sdk/cli/node_modules and src/packages/mcp-server/node_modules. A clean `npm ci` fetched the pre-wave SDK and nested it inside each consumer, where it SHADOWS the workspace link — the top-level symlink to the local 2.0.0 is what a developer reads, the nested 1.1.0 is what a fresh install resolves. Same class as GT-625. Both entries disappear once the range is ^2.0.0. Patch, not minor, and the reason is checked rather than assumed: neither package re-exports the SDK and both use only `EvolithRestClient`, so 2.0.0's breaking change (payload types re-exported from core-domain, `.passed` -> `.verdict`, `'info'` severity retired) reaches no public API of either. ALSO HERE, because it blocked the rehearsal: check-install-smoke was reporting the npm version rather than the artifact. `--pack-destination` is not honoured identically across npm majors when the cwd is a workspace package — npm 11 writes to the destination, npm 10 (the Node 20 runner) writes to the package directory, and both print the same filename. The first 1.2.2 dry run (30499155684) failed on that with a perfectly good tarball. It now looks in both and moves a stray tarball out of the source tree. VERIFIED ON THIS BRANCH, with an install rather than a range: - plan-npm-release -> 2 of 8 WILL PUBLISH, in dependency order - clean install of beyondnet-evolith-cli-1.2.2.tgz in a directory that has never seen this workspace resolves @beyondnet/evolith-sdk@2.0.0; same for beyondnet-evolith-mcp-1.2.2.tgz - check:install-smoke -> 36 specifiers resolve, binary boots printing 1.2.2 - check:release-drift -> passed - CLI jest 1436/1436 (99 suites), mcp-server 433/433 (53 suites) - lockfile diff is two versions, two ranges, two nested removals; produced by `npm install --package-lock-only` on develop's lockfile, not hand-merged The `!` marks the transitive major for consumers who reach the SDK through these packages; neither package's own API changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both were caught by required checks on PR #282, and both are the gates working. `maturity-reconciliation.json` records `cliPackage: @beyondnet/evolith-cli@1.2.1`, so the version bump made it stale and `Validate documentation` went red at "Reconcile maturity evidence". Regenerated, then the executive summary re-checked in that order — it was already current, since the field it reads did not move. WORTH RECORDING, because it is a hole in a declaration this repository added yesterday: `09-reconcile-maturity.mjs` reads `src/sdk/cli/package.json`, and the GT-630 chain declares that link's `consumes` as the gap board plus the maturity assessment ONLY. So the CLI manifest is an UNDECLARED INPUT to a derived artifact — exactly the "generator whose output does not depend only on its declared consumes" case the fixed-point pass exists to catch, and here it was caught by staleness on a runner instead. The chain declaration lives on the branch of PR #277; the `consumes` entry belongs there rather than in a release commit. `evolith-machine-contracts.json` pins `producer.version` to the CLI's version, and `10-validate-contract-conformance.mjs` fails when they diverge (GT-563 records that this check was silently crashing until recently, so it is newly effective). Bumped to 1.2.2. The contract itself is unchanged: `contractVersion` is untouched and all four schema hashes still resolve. Verified: 10-validate-contract-conformance -> 4 schemas hashed and resolved; contracts jest 104/104; 01-validate-docs 1491 files; 08-validate-tracking; 46-validate-derived-artifact-order -> 3 links current and at a fixed point. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rsion (GT-634)
Third and last version-pinned artifact the 1.2.2 bump moves.
`generate-product-inventory.mjs` stamps `@beyondnet/evolith-cli@<version>` into
`product/products/smart-cli/product-inventory.{md,es.md}`, and
`11-validate-product-docs.mjs` runs its `--check`, so `Validate documentation`
went red at "Validate product documentation synchronization". Regenerated: the
diff is the version and the counts the generator recomputes.
The staleness was reported with an EMPTY detail — `Product inventory is stale: `
— because the guard interpolates the child's stdout and the child writes its
reason to stderr. Not fixed here (it is not a release change) but worth naming:
a guard that says something is stale without saying how is a guard you have to
re-derive by hand.
WHAT I DELIBERATELY DID NOT COMMIT. `coverage-dashboard.mjs --check` also reports
drift locally, and regenerating it replaces MEASURED coverage — "88.09% statements
· 88.38% lines" — with "pending a coverage run", because this machine has no
coverage data. That is a regression dressed as a fix. The docs job on the runner
runs a build and the exploration suite before that check and passed green on
develop at 20:56Z, so the report is current where it is measured. Same for
`inventory-summary.{md,es.md}`, whose 167 -> 174 ruleset count belongs to whoever
landed #279, not to this release.
Verified with only this staged: 11-validate-product-docs OK (CLI 1.2.2, no
placeholders), 10-validate-contract-conformance OK (4 schemas), 09-reconcile
--check OK.
Honest note on method: I ran the docs job's fifteen validators in one local loop
to find every pin at once instead of one CI round each. Three of them reported
failure inside that loop and pass individually, including under the same output
redirection — so the loop, not the checks, is what was wrong. I have not explained
it, and the runner is the oracle for this job, not my loop.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…sh, and said so wrongly
ROOT CAUSE, measured rather than guessed — and my two earlier explanations were
both WRONG, recorded here so they are not re-attempted.
`npm-release.yml`'s rehearsal mode runs `npm publish --dry-run`, which exports
`npm_config_dry_run=true` into the environment of `prepublishOnly`. This guard runs
there, and its nested `npm pack` and `npm install` INHERIT it: pack prints the
tarball name and writes no file, install exits 0 and installs nothing. So the guard
declared a perfectly good artifact "NOT installable", twice, in runs 30499155684
and 30499540347.
npm_config_dry_run=true npm pack --pack-destination <dir>
-> prints "beyondnet-evolith-cli-1.2.2.tgz", leaves <dir> EMPTY
Verified on npm 11 and npm 10. What I claimed before and was wrong about:
`--pack-destination` is NOT ignored on the runner, and npm majors do NOT disagree
about it — npm 10 honours it exactly like npm 11. I reproduced that with
`npx npm@10` instead of continuing to reason about it.
THE CONSEQUENCE IS BIGGER THAN THE BUG. The rehearsal mode has never exercised
this gate: under dry_run it always failed here, and the real publish of 2026-07-28
passed only because no dry-run flag was in the environment. The workflow's safety
model says "the default invocation exercises the whole path — resolve, build, pack,
verify — and publishes nothing", and for this step that was not true.
Packing and installing into a throwaway directory is the guard's MEASUREMENT, not
part of the publish being rehearsed, so `npm_config_dry_run` is stripped for every
child in the `run` helper rather than at one call site, and `--dry-run=false` is
passed explicitly to the pack.
Verified by reproducing the runner condition locally, which is what the two failed
attempts lacked: `npm_config_dry_run=true node scripts/check-install-smoke.mjs`
passes — 36 specifiers resolve, the installed binary boots printing 1.2.2 — and so
does the plain invocation. The tarball location is also discovered rather than
predicted now, and a failure prints what each candidate directory holds.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…er defects Both defects made `41-validate-evidence-commands` report `dead` for a command that runs perfectly well. That is the worst way for this guard to be wrong: a false dead reads as stale evidence and invites someone to "repair" a record that was already correct — and 27 records were reordered in the last change partly to work around the first of these. 1. THE SCRIPT NAME WAS THE FLAG'S VALUE. `npm run --workspace src/sdk/cli test` is valid npm. The parser took the first non-dash token after `run`, which is the value of `--workspace`, and then reported that the script "src/sdk/cli" is not declared. Flags that consume the next token are now declared as data (`VALUE_TAKING_FLAGS`) and skipped, and the SUBCOMMAND is found the same way, so `npm --workspace w run test` — flag before the subcommand — parses too. 2. `pnpm`/`yarn` ARE NOT `npm`. `pnpm info <pkg>` queries the registry. The parser read `info` as a script name and failed because no `"info"` script is declared. Built-in subcommands of the three package managers are now declared as data. The fix must not turn the check OFF, so a genuinely undeclared script in the same flag-first shape is asserted to STILL be dead, and a bad workspace behind a built-in subcommand is asserted to still be reported. A FIXTURE THAT MADE TWO TESTS VACUOUS, FOUND BY CHECKING THE TESTS FAIL FIRST The undeclared-script branch is guarded by `pkg?.scripts`, and the fixture root declared none — so every assertion about an undeclared ROOT script was skipped entirely and passed without exercising anything. Written against the old parser, the six new tests failed 3 of 6. With `scripts` present in the fixture root they fail 5 of 6, which is what they are worth. The sixth passes either way and is kept as a regression guard, not claimed as a defect proof. old parser: 35 pass / 5 fail new parser: 40 pass / 0 fail Dead references 40 -> 38: only the two pnpm/yarn commands move today, because the `--workspace` records were already reordered. Defect 1's value is preventive — the canonical form is no longer the ONLY form this guard can read, so the next record written the other way is not misreported as stale evidence. Verified: self-test 40/40 and 5/6 red against the old parser; 42-validate-guard-denominators green (59 guards classified, 39/39 scanners refuse a zero-element scan). The budget stays at 40. Only a count this job prints may lower it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
fix(deps)!: the CLI and MCP must resolve the SDK that carries the security wave, 1.2.2 (GT-634)
… this job printed Run 30502009205 printed `38 dead (budget 40)`. Same two-step as 305 -> 304 and 304 -> 40, for the same reason recorded above the flag: only a count this job prints may lower it. The two references that stopped being dead are the `pnpm info` / `yarn info` commands the guard used to mis-parse as npm scripts. They were always valid; the guard was wrong about them, and the fix means the budget can drop without anyone touching the evidence. Worth recording what did NOT move: the flag-value defect recovered nothing today, because those records had already been reordered to suit the broken parser. Its value is preventive, and a budget that only ever moves on recovered references would price that at zero. Verified locally at the new setting: `--strict --max-dead 38` exits 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
fix(guard): stop the evidence guard blaming the evidence for two parser defects
…ing GT-624
GT-624's first criterion (deprecate 1.1.0) was the small half. The wave of
2026-07-23 sat unpublished until 2026-07-27 while SECURITY.md declared the 1.1.x
line "actively patched" — four days, on a public registry, with the CHANGELOG
naming the vulnerable files. Nothing detected it; an audit did. This is the gate.
`48-validate-security-publish-lag` asks the REGISTRY what is published, finds the
commit where each package's published version was SET, and fails on any later
commit whose type or scope marks it as security.
THE REGISTRY, NOT A TAG, and that is measured rather than assumed: the newest `v*`
tag here is `v1.1.0` while npm serves `@beyondnet/evolith-cli@1.2.2`. Three
releases shipped untagged, so a git-tag oracle would have reported a four-release
lag that does not exist and flagged everything since 1.1.0.
TWO FALSE NEGATIVES, found while building it, each pinned by a fixture:
1. `git log -S'"version": "<published>"'` matches the commit that DELETED the
string as well as the one that added it. On a just-bumped package the
boundary landed on the bump itself, skipping the window.
2. Taking the NEWEST commit at the published version let a later manifest edit
that keeps the version — a dependency bump — push the boundary forward and
hide everything before it.
The boundary is now the commit where the version was SET. Denominator: 47 commits
examined across 8 packages, against 31 for the first implementation — 16 commits
the first version would have skipped in silence.
The marker is STRUCTURAL — type `security(...)`, or scope `security`/`sec` — and
refuses prose deliberately. `fix(deps)!: … the SDK that carries the security wave`
is a dependency fix; a gate that fires on any subject mentioning security gets
switched off. A unit test pins that exact subject as NOT a marker. The guard also
reports the `security(...)` type separately, because per GT-623 it is not a
Conventional Commits type and release-please derives no bump from it — a commit
that announces itself as security and cannot move a version is GT-570's failure
mode by another route.
Criterion (c) in this repository's strong form: `43-validate-guard-negative-
fixtures` OBSERVED it refuse the empty fixture, 37/37 (was 36). Registered in
`guard-classification.mjs`, so `42` counts it — 60 guards classified, 40/40
scanners refuse a zero-element scan.
Also recorded on the board rather than tidied away: two packages were deliberately
NOT deprecated. `evolith-sdk@1.1.0` is what the published line actually RESOLVED
(GT-634, fixed in 1.2.2) and `evolith-contracts@1.1.0` is that package's only
version, so deprecating it offers no upgrade path.
Verified: 12/12 fixtures over real git repositories; guard green on this repo;
08-validate-tracking (632 gaps, 575 closure records), 41 at 40 dead against a
budget of 40, 04 bilingual parity, 01-validate-docs 1491 files, 46 chain at a
fixed point. Counters 592/632 -> 593/632, pending 22 -> 21.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…fact chain GT-598 left one thing unenforced. `build-iso-5055-mapping.mjs` stamps `nativeEvaluability` onto every row of a 391-row artifact FROM `native-evaluability-snapshot.json`, so rebuilding before recapturing launders a stale classification into the mapping and overstates the handler backlog by exactly the rules Core has closed since the last capture. Both artifacts pass their own `--check` at that moment, which is precisely the failure that one-artifact-at-a-time checking cannot see — the reason this guard exists. Two links added, in order. The edge is real and enforced: swapping them is rejected with "ISO/IEC 5055 corpus mapping consumes native-evaluability-snapshot.json, which a LATER link writes." The snapshot link is the same shape as the ABAC one — derived from the runtime through ts-node, so its declared inputs are Core's triage sources rather than a data file. The corpus half matters too: `documentation-only` moved 129 -> 136 purely because seven generated ADR rulesets appeared, and nothing noticed for as long as the file was hand-maintained. THE SELF-TEST HAD TO CHANGE, AND ONE CHANGE IS WORTH MORE THAN THIS COMMIT Adding links broke six of its eight tests, and only one of those breakages was about the new links: the fixture stubs every REAL link that is not under test, so two links without stubs failed the shape check first. The rest were positions typed as literals — `link 2 of 3`, `2. node 09-reconcile-maturity.mjs` — which made an unrelated addition look like a regression in the reconciler. Those positions are now DERIVED from CHAIN by producer name, so the next link added ahead of the reconciler does not break assertions that are not about it. The fixture stubs are generated from one helper and carry a note to keep them in step with CHAIN. Also strengthened while here: the byte-identical test now covers the snapshot and the mapping. The snapshot is the sharper case — its producer stamps `capturedOn` and that date is deliberately STICKY when the classification has not changed. Were it not, every replay would rewrite the date and the fixed-point pass would report drift daily. That property now has a test instead of a comment. Verified: chain guard green at 5 links, 7 artifacts, fixed point holds; self-test 8/8; swapping the pair fails as designed; 42-validate-guard-denominators green over 59 guards. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
feat(guard): declare the capture -> mapping order in the derived-artifact chain
…sh-gate feat(ci): fail when a security fix is committed and unpublished, closing GT-624
…d nothing else Everything else that was on the `claude/happy-rubin-634587` branch has landed from other sessions, so this carries only what the board is still missing. The branch is abandoned rather than merged, and what superseded each piece is recorded here so the history is not a mystery: - the capture script and the extracted triage -> PR #276 - the two derived-artifact chain links -> PR #287 (identical to the specification: same producer, checkArgs, the three `consumes` sources and both written artifacts, in the position after the ABAC link) - the SDK range and the 1.2.2 release -> PR #285 - GT-624's deprecations and its gate -> PR #288 GT-633 (DONE) — the failure mode is the content, not the fix: a guard whose expected value was a copy of its actual value, and a derived artifact that launders a stale input into one five times its size. Recorded with how it actually landed, because three sessions converged on it: #276 and #287 on main, and a SECOND independent implementation on develop as #279 — an in-spec renderer instead of a standalone script. Both recaptured the same numbers, which corroborates the measurement; two generators for one artifact is this row's own defect one level up, and reconciling them is the open blocker on develop -> main. That reconciliation is deliberately NOT done here: choosing which generator survives belongs to the sessions that wrote the two halves. GT-634 (PENDING, and it stays that way) — 1.2.2 is on main and NOT published. A range in the repository is not a range on the registry; until npm serves it, the published `latest` of the CLI and the MCP still installs the 2026-07-18 SDK. GT-635 (DONE) — closed the way the row asked, by someone else, within the hour. `f34a10f1` migrated the recorded commands onto the paths this repository has and `600dd7a9` moved the budget 304 -> 40 from the count that produced: 269 of the 309 dead references were REPAIRED, not exempted. The row's value was making a permanently red job visible, not owning the repair, and the record says so. Verified: 08-validate-tracking (635 gaps, 611/611 EN/ES sections, 577 closure records), 41 at 38 dead on a clean checkout against a budget of 40 — these two new closure records add none — 01-validate-docs (1491 files), 04 bilingual parity, 46 chain current and at a fixed point. Counters 593/632 -> 595/635. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
docs(gaps): register GT-633, GT-634 and GT-635 on main
…ctrine `coverage.mjs` states the rule for guards: zero scanned is a failure, because a check that scanned nothing did not run. Nothing states it for the guard's TEST, and the failure has the same shape one level up — a test whose fixture never reaches the branch under test passes, and reads as proof. Observed on 2026-07-30 (GT-633) while fixing two parser defects in `41-validate-evidence-commands`. Six tests were written for the fix; three of them asserted that a command is NOT reported dead, and passed against the UNFIXED parser too, because the branch that reports it dead is guarded by `pkg?.scripts` and the fixture's root package.json declared no `scripts`. The assertions were true, the fix was real, and those tests proved nothing about it. The rule now written down next to its sibling: when a test is written to pin a fix, RUN IT AGAINST THE UNFIXED CODE AND WATCH IT FAIL. A test that passes before the fix is the vacuous pass this file already describes, wearing a green tick from the test runner instead of from a guard. Its other half is recorded too, because it is the part that gets skipped: making the fixture live took those six from 3-of-6 red to 5-of-6 red, and the sixth passes either way. That one is kept and LABELLED a regression guard rather than counted as proof — when a test cannot be made to fail, say so in the test instead of letting the count imply coverage it does not have. This is doctrine for whoever writes the next guard, which is why it lives here rather than in the GT-633 board entry: a gap row is read once, this file is read by everyone who reaches for `assertScanned`. Verified: coverage.test.mjs 12/12; the module still loads and exports ZeroCoverageError, assertScanned, assertScannedPerSource, scanned; 42-validate-guard-denominators green over 60 guards; 01-validate-docs green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… framing was wrong 1.2.2 shipped from main with provenance (run 30548042781). Verified against the REGISTRY rather than the workflow's tick: `npm view` returns 1.2.2 for both packages, both declare `@beyondnet/evolith-sdk: ^2.0.0`, both carry dist.attestations. The check that matters: installing @beyondnet/evolith-cli@latest into a directory that has never seen this workspace resolves sdk@2.0.0 and the binary boots. Before this release the same install pulled the 2026-07-18 build. CRITERION 2 REFUTED THE ROW'S OWN PREMISE, which is the finding worth keeping. "Verify the wave's fixes are present rather than assumed" was written to stop a version number standing in for evidence. Doing it: NONE of the seven security-wave commits (1b077ab, 3a39dd0, 60bfc63, 3f31fa6, 90eacdb, 4f29e11, 8ad4e2d) touches src/packages/sdk-client — zero files each, checked one by one — and the wave's release commit 21205d2 names the three packages it affected: evolith-mcp (15 files), evolith-cli (7), evolith-agent-runtime (1). The SDK was republished alongside them, not fixed. So sdk@1.1.0 predates the wave chronologically and carries none of it. There was no security exposure through this dependency. What WAS real, and what the row should have said: a caret range under 1.x cannot cross a major, so both consumers were pinned a MAJOR behind on a contract dependency, and the lockfile nested the registry tarball inside each consumer where it shadowed the workspace link — the GT-625 class. The hedge written in when the row was opened (it measured dates and resolution, not contents) is what made the correction cheap instead of embarrassing. AND A CORRECTION TO MY OWN EARLIER WORK, made rather than left standing: the GT-624 deprecations of evolith-core-domain, evolith-core and evolith-infra-providers at 1.1.0 claimed "Security fixes landed in 1.2.0". The same evidence refutes it. All three registry messages were rewritten to say those versions were republished alongside the wave, not fixed by it. A false security claim on a public registry is worse than no claim. Criterion 3: sdk@1.1.0 deprecated, with the real reason (`.passed` -> `verdict`) and an explicit "not a security advisory". Verified after `npm cache clean --force` — the first read-back served the previous message from cache. Left open on purpose and recorded: cli@1.2.1 and mcp@1.2.1 are not deprecated (stale contract, not exposure, and one patch old is an owner's call), and contracts@1.1.0 still has no successor to point a deprecation at. Verified: 08-validate-tracking (635 gaps, 611/611 EN/ES, 578 closure records), 41 at 38 dead on a clean checkout against a budget of 40, 01-validate-docs (1491 files), 04 bilingual parity, 46 chain at a fixed point, 48-validate-security- publish-lag green. Counters 595/635 -> 596/635. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
docs(gaps): close GT-634 — 1.2.2 is published, and the row's security framing was wrong
…doctrine docs(harness): record the fixture-vacuity case in the anti-vacuous doctrine
…ky-date bug it exposed Converges main onto the GT-633 reconciliation already on develop (#294), so the standards half of develop -> main stops being a design conflict: one renderer in `test/rule-corpus-triage.ts`, two callers — the spec pins it byte-for-byte, the capture script writes it and `--check`s it for the GT-630 chain and docs.yml. AND IT EXPOSED A REAL BUG IN THAT RECONCILIATION, which is why this is not a copy-paste. The driver decided sticky-vs-fresh `capturedOn` by comparing `JSON.stringify(previous.counts)` against a counts object rebuilt in KNOWN_CLASSES order, while the renderer emits them in CLASS_ORDER — a different order for the same six numbers. So "changed" was ALWAYS true and the date was rewritten on every run. That would have failed the GT-630 fixed-point replay on any day after a capture. It passed on develop only because both runs happened on the same date and stamped the same value — the precise shape of green this chain exists to distrust. It surfaced here because main's committed snapshot carries 2026-07-29: the first run rewrote it to 2026-07-30, the second run rewrote it back. The comparison is now order-insensitive, and observed: with the classification unchanged, `capturedOn` stays 2026-07-29 across runs and the replay is byte-identical. NOTE: develop still carries the buggy comparison. It is invisible there today and will bite tomorrow; it needs the same one-function fix, which is a follow-up against develop rather than something to smuggle into a main-targeted change. Numbers on this base are main's own, measured rather than copied: native-handler 154 (develop computes 151, having fewer handler closures landed), corpus 389, mapping 391 rows. Verified: capture --check faithful, replay byte-identical, build --check up to date (391 rules, 37 mapped), iso-5055-mapping guard 9/9, chain 5 links current and at a fixed point, core-domain jest 1446 passed / 128 suites, tsc --noEmit clean, 01/04/08 green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…-on-main fix(standards): bring the one-renderer design to main, and fix a sticky-date bug it exposed
A gap id is chosen by reading the highest `GT-*` on whichever branch you are on, and nothing contrasts that with any other branch. Two parallel sessions therefore allocate the same number and one finds out at merge time. IT ALREADY HAPPENED, which is why this is a row and not a note. `8449af3d` on develop reads "renumber the ratchet fix GT-634 -> GT-637, ID collision with develop": that row and GT-634 on main took the same id within hours, from sessions that could not see each other. The renumber is manual and LOSSY, and that costs more than the clash. An id lives in the board row, the catalog anchor, the closure-evidence record, cross-references from other rows in both languages, commit messages and PR bodies — and only the first three are mechanically checkable. `08-validate-tracking` cannot help by construction: it validates within one working tree, and branches are outside its world. Nor is it an isolated slip. In the 2026-07-30 develop -> main merge the same convergent-duplication pattern appears three times: GT-633 was fixed twice by two sessions, the evidence guard's parser was fixed twice, and this id was allocated twice. A merge does not fix that. THE ID FOR THIS ROW was allocated by taking the UNION of ids across main and develop rather than the maximum on one — main was at 635, develop already had 636 and 637 — which is exactly the workaround the fix should make unnecessary. Doing it any other way would have reproduced the defect while registering it. Registered PENDING, P2/S, with criteria that ask for a check at PR time naming both users of a colliding id, and a negative fixture OBSERVED red rather than declared able to fail. Verified: 08-validate-tracking (636 gaps, 612/612 EN/ES sections, 578 closure records), 01-validate-docs (1491 files), 04 bilingual parity, 41 at 38 dead on a clean checkout against a budget of 40, 46 chain current and at a fixed point. Counters 596/635 -> 596/636, pending 21 -> 22. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
docs(gaps): register GT-638 — the board has no id allocator
…nothing shows work in flight On 2026-07-30 the same work was done twice, THREE separate times, by sessions that could not see each other: 1. GT-633 was fixed twice — a standalone capture script on main (#276) and an independent in-spec renderer on develop (#279). Both correct, both recapturing the same numbers. Two generators for one artifact is GT-633's own defect one level up, so a THIRD session spent a full reconciliation (#294..#297) collapsing them into one renderer. 2. The evidence guard's parser was fixed twice — `d1ea72a3` on main ("stop the evidence guard blaming the evidence for two parser defects") and `5acb29ed` on develop ("the ratchet was stuck by two bugs in itself"). 3. A GT id was allocated twice, forcing the renumber behind GT-638. THE COST IS NOT THE MERGE CONFLICT. It is the duplicated work that happened before anyone reached the conflict, plus the reconciliation it then required — and that reconciliation introduced a defect of its own: an order-sensitive comparison that rewrote the capture date on every run, invisible for a day because both runs stamped the same date. Three duplications, one repair session, one new bug. Why the board cannot see it: a row carries a status but not WHO is working it or WHERE. Two sessions can both read `GT-633 · PENDING` and both start, correctly. `08-validate-tracking` validates within one working tree by construction and cannot know another branch exists. AND THIS IS NOT A MISSING CONVENTION — the single-driver rule for gap waves is already established practice. What is missing is any mechanism that makes it observable, so on a busy day it degrades silently and nobody learns until the merge. That is why the criteria ask for a claim DERIVED from open pull requests rather than a rule written down again. Distinct from GT-638, and the row says so: that one is allocating a NAME twice, this one is doing the WORK twice. Same coordination gap; the id collision is its cheapest symptom. Id allocated by union across main and develop, per GT-638. Verified: 08-validate-tracking, 01-validate-docs (1491 files), 04 bilingual parity, 41 at 38 dead on a clean checkout against a budget of 40, 46 chain current and at a fixed point. Counters 596/636 -> 596/637, pending 22 -> 23. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
docs(gaps): register GT-639 — the same gap gets fixed twice, because nothing shows work in flight
A gap id is chosen by reading the highest `GT-*` on whichever branch you are on, and nothing contrasts that with any other branch. `8449af3d` had to renumber GT-634 -> GT-637 for exactly this. `08-validate-tracking` cannot help: it validates within ONE working tree by construction, and branches are outside its world. This guard is the part that needs two. `49-validate-gap-id-allocation` compares every id's **Title:** on HEAD against the title the same id carries on the base branch: absent from base -> newly allocated, fine present, same title -> the same gap, edited; fine present, DIFFERENT title -> one number, two gaps THE TITLE IS THE DISCRIMINATOR ON PURPOSE. "The id exists on base" is the normal case for 515 rows and says nothing; what makes it a defect is the id naming a different gap on each side. IT FOUND A SECOND COLLISION ON ITS FIRST REAL RUN, which nobody knew about. Against origin/develop it reports GT-633: there it is "`evolith init` scaffolds a repository that cannot pass its own governance on the first run" (c8270e4, 2026-07-29); on main it is the tautological-guard row (c322127, 2026-07-30). Two gaps, one number, and the merge had not surfaced it. By the guard's own advice the NEWER row is the one to renumber — which is mine. It refuses to guess which side is wrong. A retitle and a collision are indistinguishable from outside, so it names both titles and both refs and says in the failure that it should not guess. Wired into `Governance guards (GT-578)` with an explicit `git fetch` of the base: a shallow CI checkout does not have it, and a guard that failed for THAT reason would teach people to ignore it. Anti-vacuous: a missing catalog, an unreadable base ref, and a parse yielding zero ids on either side are all hard failures. "Unable to answer" is not "nothing to report" — a quiet skip is how the original collision reached main. WHAT IT STILL CANNOT SEE, said out loud rather than implied: an id allocated on a branch with no open PR. It compares HEAD against ONE base, so two feature branches that both take GT-640 stay invisible to each other until the first merges. The `--verbose` output warns on every newly allocated id, and closing that remainder needs the open-PR claim set tracked in GT-639. Verified: 13/13 fixtures over real git repositories (not a mock of `git show`); 43-validate-guard-negative-fixtures 38/38 observed red, up from 37; 42-validate-guard-denominators 61 guards classified; 08-validate-tracking (637 gaps, 579 closure records), 41 at 38 dead against a budget of 40, 01-validate-docs (1491 files), 04 bilingual parity, 46 chain at a fixed point. Counters 596/637 -> 597/637. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…uard feat(ci): one gap id, one gap — the allocator guard, closing GT-638
`49-validate-gap-id-allocation` reported on its first real run that `GT-633` names
two different gaps: on develop it is "`evolith init` scaffolds a repository that
cannot pass its own governance on the first run" (`c8270e48`, 2026-07-29), on main
it is the tautological-guard row (`c3221276`, 2026-07-30). Per the guard's own
advice the NEWER row moves, and that is mine.
THIS COMMIT IS THE COST GT-638 DESCRIBES, paid once so it is on the record. The id
had to be changed in:
- the board row and its anchor, EN and ES
- the catalog heading, EN and ES
- the closure-evidence record
- cross-references from GT-638 and GT-639, EN and ES, including the narrative
that describes this very collision — updated to say how it was resolved rather
than left describing a state that no longer exists
- three source comments (`capture-native-evaluability-snapshot.mjs`,
`test/rule-corpus-triage.ts`, `rule-corpus-triage.spec.ts`)
and it CANNOT be changed in the commit messages and PR bodies that already name
GT-633 — #294 through #300 among them. That is the lossy half, and it is why the
row asks for allocation to be right the first time rather than repairable.
Historical mentions are kept and marked ("registered as GT-633 at the time")
rather than rewritten, so the record still reads correctly against those commits.
VERIFIED THE COLLISION IS GONE, not assumed: `49-validate-gap-id-allocation
--base origin/develop` now reports 0 collisions and lists GT-640 among the newly
allocated ids, where before it named GT-633 on both sides. Also 08-validate-
tracking, 01-validate-docs (1491 files), 04 bilingual parity, core-domain
rule-corpus-triage 22/22, iso-5055-mapping guard 9/9, 46 chain at a fixed point.
NOT DONE HERE: develop carries the same three source comments, and after this
lands they will name GT-640 on main and GT-633 on develop — where GT-633 is the
scaffolding gap. Whoever resolves the develop -> main merge should take main's
version of those files; noted on #284.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
docs(gaps): renumber GT-633 → GT-640, the collision the new guard found
Unblocks #284. main was 45 commits ahead, develop 30, and 17 files conflicted. Every resolution below was MEASURED or derived, not chosen by preference. STANDARDS (GT-640, was GT-633) — took main's files wholesale: they carry the one-renderer reconciliation, the GT-640 renumber and the sticky-date fix that develop's copy still lacked. The two derived JSONs were REGENERATED on the merged corpus rather than merged; the capture reports 389 rules / native-handler 154, which is main's number because main is where those handler closures landed. CLOSURE REGISTRY — 91 records differed, because BOTH branches independently migrated the recorded commands onto real paths (the duplication GT-639 records, again). Chosen by measurement rather than by argument: built both candidate unions and ran the guard against each with the same parser. prefer develop's migration -> 19 dead on a clean checkout prefer main's migration -> 22 develop's is kept. Union first, so all 582 records survive — the 3 only develop had (GT-633, GT-636, GT-637) and the 5 only main had (GT-624, GT-634, GT-635, GT-638, GT-640), verified present by id after the merge rather than assumed. GUARD 41 — both sides fixed the same two parser defects independently, with different helpers. Took main's `firstOperand`: it handles `--` and the VALUE of every value-taking flag, while develop's `npmScriptOperand` knew only `--workspace`, so `npm run --prefix ./x build` would have read `./x` as the script name. develop's now-unused helper is deleted rather than left as a second parser for one job. RATCHET BUDGET — 19, the number this tree actually counts, not either side's (21 on develop, 38 on main). Both were measured against their own corpus and their own parser; neither describes the merged one. BOARD + CATALOG — union of rows and entries, eight new ids across both sides, and the counters RECOMPUTED from the merged table: 600 / 640 done, 14 in progress, 22 pending, 4 deferred. Never merged as text. WORKFLOWS — union of steps. docs.yml took main's telling of the GT-598 comment (both sides added the same two steps); ci-cd.yml took develop's ratchet history, which owns the GT-637 fixes. MATURITY REPORTS — regenerated in GT-630 order, not merged. CLI E2E — kept develop's GT-636 comment over identical code. Verified on the merged tree: core-domain jest 1455 passed / 129 suites, tsc --noEmit clean, mcp-server suite green, guard 41 self-tests 45/45, guard 49 13/13, iso-5055-mapping 9/9, 43-validate-guard-negative-fixtures 38/38 observed red, 46 chain current and at a fixed point, 01/04/08/49 green, 41 within its new budget. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
📊 Bilingual Coverage ImpactPR Changes
Repository Coverage
To create skeletons: node .harness/scripts/generate-es-skeleton.mjs <file.md>Generated by GitHub Actions |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
mainwas 45 commits ahead,develop30, and 17 files conflicted. This mergesmainintodevelopso #284 (develop→main) becomes clean. Every resolution was measured or derived, not chosen by preference.Closure registry — chosen by measurement
91 records differed, because both branches independently migrated the recorded commands onto real paths — the duplication GT-639 records, again. Rather than argue, I built both candidate unions and ran the guard against each with the same parser:
develop's is kept. Union first, so all 582 records survive — the 3 only develop had (GT-633, GT-636, GT-637) and the 5 only main had (GT-624, GT-634, GT-635, GT-638, GT-640), verified present by id rather than assumed.
Guard 41 — the more careful parser
Both sides fixed the same two parser defects independently, with different helpers. Took main's
firstOperand: it handles--and the value of every value-taking flag, while develop'snpmScriptOperandknew only--workspace, sonpm run --prefix ./x buildwould have read./xas the script name. develop's now-unused helper is deleted rather than left as a second parser for one job.Ratchet budget — 19
The number this tree actually counts, not either side's (21 on develop, 38 on main). Both were measured against their own corpus and their own parser; neither describes the merged one.
The rest
native-handler154.docs.ymltook main's telling of the GT-598 comment;ci-cd.ymltook develop's ratchet history, which owns the GT-637 fixes.Verified on the merged tree
core-domain jest 1455 passed / 129 suites ·
tsc --noEmitclean · mcp-server green · guard 41 self-tests 45/45 · guard 49 13/13 ·iso-5055-mapping9/9 ·43-validate-guard-negative-fixtures38/38 observed red ·46chain current and at a fixed point · 01/04/08/49 green · 41 within its new budget.Evolith Core Quality Gates
01-validate-docs.mjsgreen.04-check-bilingual-parity.mjsgreen.Linked ADRs / Issues
Conventional Commits
🤖 Generated with Claude Code