release: align main — the CLI and MCP resolve the SDK that carries the security wave, 1.2.2 (GT-634) - #284
Conversation
The GT-595 config-shaped slice gave MTN-05 and GIT-08 real handlers, and the first thing they reported was that `evolith init` produces a repository that fails its own first `evolith validate` with two REAL verdicts — not skips. Both were pinned by name in rule-applicability.integration.spec.ts so that closing them would be visible. They are closed here, by different means, because they are different kinds of defect. GIT-08 was a SCAFFOLD gap. Conventional Commits bind from the first commit and the corpus already states the convention and its type list verbatim, so there is nothing to decide and nothing to invent. `init` now emits all three legs the handler reads: commitlint.config.mjs, the commitlint packages in devDependencies, and — with `--features hooks` — a .husky/commit-msg that runs `npx --no-install commitlint`, so a missing tool is an ERROR rather than the silent skip GT-623 found. When hooks are not selected the config is still written and the result carries a warning saying nothing runs it, rather than leaving a config that looks like enforcement and is not. It is runtime-independent on purpose: commitlint is a Node tool, so a .NET or Python satellite carries a tooling-only package.json — which is what such repositories do in practice and what GIT-08's handler actually reads. Emitting the config alone would have left the rule failing for every non-Node runtime. MTN-05 was NOT a scaffold gap, and emitting a `spec.boundedContexts` stanza would have been the wrong fix. The rule's own description says the strategy MUST be defined "before Phase 2 Design", and a phase-0 scaffold has no bounded contexts to declare; satisfying it at init would have meant writing an invented persistence decision into every new repository, after which MTN-05 would pass for a reason nobody chose. Worse, per the handler's own note the two vocabularies are irreconcilable — evolith-yaml.schema.json restricts `persistence` to a database-engine enum under additionalProperties:false — so any value written either breaks the contract schema or cannot match the rule. It is annotated `appliesFromSdlcPhase: 2` instead, which is the applicability fact the rule always stated in prose. The rule is DEFERRED, not weakened: a repository that has reached Design is still failed by it for the same reason, and that is asserted rather than assumed. The pinned assertion now reads [] — every remaining blocking finding on a fresh satellite is the GT-595 blocking-and-skipped invariant, not a verdict against anything the user did. Verified: core-domain 1442 passed, infra-providers 129 passed, sdk/cli 1436 passed, ruleset schema gate 168/168, native-evaluator parity 451 assertions, doc validation 1491 files. Confirmed per-rule rather than by the suite alone: GIT-08 now executes and PASSES (typescript and python), and MTN-05 is reported not-applicable rather than silently passing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The init-scaffold defect that GT-595 exposed now has a row of its own rather than living only in a pinned test assertion. Registered as GT-633 (P1/S, `Core Domain`) and closed on 05c3f62. The row records the part that is not obvious from the diff: the two halves were closed by OPPOSITE means. GIT-08 was a scaffold gap and nothing else, so `init` now emits the config, the package and the hook. MTN-05 was not — its own text defers it to Phase 2 Design, and satisfying it at init would have written an invented persistence decision into every new repository, so it is annotated `appliesFromSdlcPhase: 2` instead. A board that recorded only "both fixed" would lose exactly the distinction worth keeping. Also recorded, because it nearly produced a false CLOSURE: the premise did not reproduce on first contact. The suite resolves core-domain through the workspace symlink to a `dist/` that was nine days stale and predated the GT-595 handlers, so both rules reported `skipped` and the pinned assertion already read `[]`. Same untracked-build-state class as GT-625 and GT-632, but inverted — here the stale artifact would have "proved" the gap closed before a line was written. Counters recomputed rather than incremented by hand: 633 gaps EN/ES, 609/609 catalog sections, 575 closure records. The executive summary and maturity reconciliation were regenerated, and their diff is exactly the three +1 deltas, which is what shows nothing else drifted. Verified: 08-validate-tracking, 05-validate-executive-summary, 09-reconcile-maturity --check, 01-validate-docs (1491 files) and 04-check-bilingual-parity all exit 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Brings in the follow-on GT-595 left pinned: a freshly initialized satellite failed MTN-05 and GIT-08 with REAL verdicts, and the assertion in rule-applicability.integration.spec.ts named them so that closing them would be visible. It now reads []. Closed by opposite means on purpose. GIT-08 was a scaffold gap — `init` emits commitlint.config.mjs, the commitlint packages, and a commit-msg hook that fails rather than skips when the tool is absent. MTN-05 was not: its own text defers it to Phase 2 Design, so it carries `appliesFromSdlcPhase: 2` rather than having the scaffold invent a persistence decision no team made. Registered and closed on the board as GT-633 (633 gaps, 575 closure records). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…otation The exploration tester's anti-drift check went red on `construction` and `qa`. Regenerated from the live capture rather than hand-edited, which is the whole point of those documents. The drift is exactly `rulesTotal: 178 -> 177`, in six captured responses and nothing else. That single decrement IS the MTN-05 annotation: on the phase-0 satellite the tester scaffolds, the rule moved out of the evaluated denominator and into `rulesNotApplicable`. One rule left, the one annotated, and no other captured surface behaviour moved — so the regeneration doubles as evidence that the change does what it claims and no more. `findings = 0` throughout: no CLI/MCP/REST divergence was introduced. Verified: test:exploration 6/6, 01-validate-docs 1491 files, 04-check-bilingual-parity 1492 scanned. Reproducing this locally first needed @nestjs/cache-manager, which is declared in core-api and mcp-server but absent from the shared node_modules — an install gap in that tree, not a code defect; CI installs it from the lockfile. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…tself
Investigating why GT-633's PR had `Governance guards (GT-578)` in the fail
column surfaced an uncommitted, unfinished refactor already sitting in this
worktree: `resolveCommand` called `npmScriptOperand(...)`, a function that was
never defined, so the guard crashed with a ReferenceError on every invocation
-- even bare `--report` mode, before `--strict`/`--max-dead` were ever reached.
309 was never a real measurement of the evidence corpus; it was whatever count
the guard produced before it hit the crash path on a given run.
Restoring that function (equivalent to the pre-refactor inline behaviour)
dropped the count to 78 and exposed a second, real bug the first one had been
masking: the corpus uses `npm run --workspace <path> <script>` throughout --
the `--workspace` flag BEFORE the script name, a form `npm` itself accepts --
and the resolver's script-name search had no notion that `--workspace`
consumes the next token, so it took the workspace path itself as the script
name and reported it "not declared". Fixed, dropping the count to 52.
The rest is the data migration the guard's own docstring already described:
~290 pre-`src/`-refactor and pre-`product/`-move paths recorded when they were
correct. 18 renames applied here, each verified against a real successor file
present on disk before being written -- `product/infra/docker-compose.yml`,
`product/operations/**`, `.harness/scripts/ci/agentic/review-*.test.mjs`,
`src/tests/contract/roundtrip-gate-evaluate.mjs`,
`reference/core/control-center/gaps/gap-reference-catalog{,.es}.md` -- plus one
bare-filename fix (`chat.command.ts` -> its real path) once confirmed to exist.
That took 52 -> 21.
A third, unrelated defect found on the way: `.harness/bin/opa` (15 of the
original 309, the single largest bucket) is gitignored and downloaded by a
separate CI step this guard's own job never runs. Same finding as GT-632 on
guard 47 -- a gitignored artifact absent on a clean checkout mistaken for a
wrong path -- but narrower here: that guard could confirm a path's ancestor was
real; this one cannot confirm the leaf exists at all, so the added verdict is
`unchecked`, not `resolved`.
`--max-dead` moves from 305 to 21, proven exact (not padded) by observing the
guard fail at 20. The residual 21 is retired-artifact or prose-misparse debt,
each individually confirmed rather than force-mapped to a plausible target: a
consolidated `.bmad-core/AGENTS.md`, the retired `evolith-bff` gateway, the
retired `packages/mcp-tools`, two k8s manifests with no path-equivalent
successor, a retired sandbox app, three root-level scripts predating
workspaces, and three records where `vision/` / `tools/list` are prose, not
paths.
5 new self-tests cover both bugs and their negative twins, so the fix cannot
blind the guard: 39/39 pass. Verified: `08-validate-tracking`, guards 42-45 in
the same CI job, and a full `--execute --verbose` run -- 6 pre-existing
executed failures, none among the paths touched here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The dead-reference ratchet fix gets a row of its own rather than living only in a workflow comment and a code diff. Registered as GT-634 (P1/S, `.harness`) and closed on 5acb29e. The row records what the diff alone would not: the guard was never really measuring 305 of evidence-corpus debt — a crashing ReferenceError and a `--workspace`-before- script-name misparse were BUGS IN THE GUARD, and only after fixing both did the true count (309, unchanged) become visible for the first time. Separately, it records that the remaining 21 were deliberately NOT force-mapped to a plausible-looking target — a wrong rename would corrupt the record it claims to verify, which is worse than an honest residual — and names each retired artifact by id so the reasoning survives past this session. Counters recomputed, not hand-incremented: 634 gaps EN/ES, 610/610 catalog sections, 576 closure records. Maturity reconciliation and the executive summary regenerated in the correct order (reconcile first, summary second — running them the other way crashes with a drift error) and their diff is exactly the expected deltas. Verified: 08-validate-tracking, 05-validate-executive-summary, 09-reconcile-maturity --check, 01-validate-docs (1491 files) and 04-check-bilingual-parity all exit 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…read Core (#279) `native-evaluability-snapshot.json` was 12 rules behind Core and nothing said so. Its own `validation` field promised it reproduced the counts pinned by `rule-corpus-triage.spec.ts`, but the only guard over it — the test named "the evaluability snapshot still matches the class counts pinned by Core" — asserted `evaluability.counts` against six literals typed into the test file. It compared the snapshot with a second copy of the snapshot and never with Core, so it passed while the pin moved 139 -> 151 and the published handler backlog overstated the work by twelve rules. Recapture, from the live triage rather than by hand: corpus 379 -> 386 (the 7 ADR rulesets 0118..0124 were absent) native-handler 139 -> 151 documentation-only 129 -> 136 unimplemented-native 60 -> 48 The per-rule diff is closures, not a renumbering: HXA-01/02/04/05 (GT-632) and MTN-05, GIT-08, SEC-RL-01/02, ED-R04..06, DAM-R05 (GT-595) are `native-handler` now. `handlerBacklog` re-derives with them — realBacklogSize 60 -> 48, remainderToAuthor -> 38, `not-in-snapshot` back to 2 from 9. The guard is rebuilt at both ends, and neither end is a copy: - `rule-corpus-triage.spec.ts` RENDERS the snapshot from the same `CLASSIFIED` array its assertions measure, and pins it byte-for-byte — the `--check` shape `build-iso-5055-mapping.mjs` already uses for its own outputs. Recapture with UPDATE_EVALUABILITY_SNAPSHOT=1. A hand-edit now fails jest. - `iso-5055-mapping.test.mjs` parses the pinned `expect(SUMMARY.byClass)` object out of Core's suite instead of restating it. A pin it cannot find is a failure, never a skip, so the test cannot go green by losing its authority. Both were verified to go red by injecting drift from each side. Also corrects the two READMEs, which published the same stale figures in prose (60-rule backlog, "17 of 60", 381 rules), and states why the backlog moved so it does not read as a re-cut denominator. Note that `build-iso-5055-mapping.mjs --check` was already failing before this change: the corpus had grown by seven rulesets and the table had not been regenerated. Verified: node --test src/rulesets/standards/iso-5055-mapping.test.mjs (7/7), npx jest src/application/validators/rule-corpus-triage.spec.ts (22/22), build-iso-5055-mapping.mjs --check. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…275 Investigation only, at the user's explicit request -- no fix applied. Each of the three CI checks still red on PR #275 (Security Audit, E2E Tests, Evolith Core Validation) is root-caused with evidence, not assumed pre-existing on the strength of "it was probably already broken": - Security Audit: 31 npm audit findings (29 high, 2 moderate) in transitive deps of jest-snapshot/fast-uri. No package.json or lockfile touched here. - E2E Tests: one test of 132, "should validate architecture with --arch flag", exits 1 where it expects 0 or 2. #273's own CI run -- the same branch's prior merged PR, before any commit in this session existed -- shows the identical failure by name and by count. - Evolith Core Validation: fails at Bilingual Terminology Lint, 5 inconsistencies across four .es.md files, none touched by any commit in this PR (checked with git log per file, not assumed). Left PENDING with unchecked acceptance criteria: fixing the npm audit findings risks a breaking dependency bump, the E2E exit-code mismatch needs a decision about which side (CLI or test) is wrong, and the terminology fixes need a human call on which suggestions are correct versus a deliberate exception -- none of that is this closure's call to make unilaterally. Counters recomputed: 635 gaps EN/ES (594 done, 23 pending), 611/611 catalog sections, 576 closure records (unchanged -- GT-636 is open, so it carries none). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Continuing GT-636's investigation into action, at the user's explicit request. Bilingual Terminology Lint (4 files, 5 flagged terms): two were genuine untranslated prose, corrected directly -- "database" -> "base de datos" in 0124-credential-secret-management-standard.es.md, "ports and adapters" -> "puertos y adaptadores" in level-3-components/README.es.md. The other three were NOT prose gaps and translating them blindly would have made the documents worse, not better: gap-tracking.es.md's "Portability" cites ISO/IEC 25010:2023's own English characteristic name inside a clause renaming it to "Flexibility" -- translating only that one word while its three neighbors (Usability, Interaction capability, Flexibility) stayed English would read as broken, not fixed. maturity-assessment.es.md's "Modular Monolith" / "Microservices" are two cells in an 8-row topology-name column where the other six rows (Distributed Modules, Serverless, Edge Computing, Event-Driven, Data Mesh) are correctly left in their canonical English form -- translating only two would have broken that table's own consistency. All three are marked with backticks instead, which the linter already excludes from prose scanning by design (it strips inline code before matching, per its own header comment) -- an honest "this is a cited identifier, not narrative" markup, not a workaround. E2E Tests (1 test of 132): `should validate architecture with --arch flag` called `validate --architecture --arch-level F1`. Neither flag ever existed -- `grep -rn "arch-level" src/sdk/cli/src/` returns nothing, and `validate --help` confirms the real flag is `-a, --arch` (a boolean, no level argument). Commander rejected `--architecture` as unknown and exited 1, indistinguishable from a real crash under this test's own `[0, 2]` contract. Reproducing this locally required `npm run build` (not `tsc -b` directly) so `prebuild` fires `copy-rulesets` -- my first repro attempt skipped that lifecycle hook and hit an unrelated RULESET_NOT_FOUND that made the actual defect harder to see. Fixed to `validate --satellite <path> --arch`; the full suite now passes 132/132, confirmed with the exact CI command (`npm run test:e2e`). Security Audit: investigated, not fixed here. The isolated repro (a scratch worktree with a REAL node_modules copy, not the per-package symlink farm this session built earlier for workspace testing -- that farm was confirmed to corrupt `npm ls`'s own graph display, though not `npm audit`'s severity count, which matched at 31 in both environments) traced the two real chains: `babel-plugin-istanbul` (via `ts-jest`/`@jest/transform`) and `eslint`'s own bundled `minimatch` both carry a nested, un-hoistable `brace-expansion@1.1.16` that the repo's existing root `overrides.brace-expansion: "2.1.2"` does not reach, plus `fast-uri@3.1.2` under `ajv` (safely bumpable to 3.1.4, in-range of ajv's own `^3.0.1`). The plain `npm audit fix` cannot even attempt these: it fails immediately on an unrelated `@typescript-eslint` peer conflict. Every one of the 31 is a devDependency (jest/eslint/@nestjs-cli/babel toolchain) with no production runtime exposure, and the real fix is either a new targeted nested override (unverified against every workspace's lint/test run) or a major bump of core tooling across ~10 workspaces -- exactly the class of hard-to-reverse, broad-blast-radius change this session should not push unilaterally at the end of a long run without dedicated regression testing. Left as accepted, tracked debt in GT-636 rather than forced through. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Terminology and E2E were real bugs, not accepted debt -- corrected in the prior commit. Security Audit stays open, deliberately, with the deeper forensic detail this session's investigation actually found: two independent un-hoistable brace-expansion@1.1.16 chains the repo's own existing override doesn't reach, plus a safely-bumpable fast-uri, all devDependency-only with no production runtime exposure -- and why forcing either fix (an unverified nested override or a multi-workspace major bump) isn't something to push through solo at this hour without real regression testing. Status PENDING -> IN-PROGRESS; 3 of 4 acceptance criteria ticked. Counters: 15 in progress (was 14), 22 pending (was 23), total unchanged at 635. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…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>
…s supposed to fix npm audit --audit-level=high reported 31 findings (29 high, 2 moderate). 29 of the 29 high traced to ONE root cause, not 29 independent problems: GHSA-mh99-v99m-4gvg is a brace-expansion DoS advisory affecting every published version <=5.0.7, patched only in 5.0.8 -- released after this repo's existing `overrides.brace-expansion: "2.1.2"` was set, so that override was already insufficient against the CURRENT advisory before this investigation began. The "high" severity propagates from that one leaf up through the entire jest/eslint/@nestjs/cli devDependency toolchain via `via:` chains, which is why the finding list reads as jest-snapshot/babel-jest/ ts-jest/eslint/etc. all being independently vulnerable when they are not. Root `overrides.brace-expansion` bumped 2.1.2 -> 5.0.8. Separately, `overrides.fast-uri: "3.1.4"` added -- in-range of ajv's own declared `^3.0.1`, unrelated to the brace-expansion chain. Getting the override to actually apply took more than editing the number. A plain `npm install` left 4 of the tree's 10 brace-expansion positions (eslint, @eslint/config-array, @eslint/eslintrc, fork-ts-checker-webpack- plugin, each via their own un-hoisted minimatch) pinned to whatever the lockfile already recorded -- the specific `packages` entries had to be deleted from package-lock.json before a fresh install would re-resolve that position at all, and it took two rounds (the first re-resolution landed on an intermediate 1.1.17, itself still vulnerable). A more targeted attempt -- scoping the override under each affected parent, matching this repo's own existing `eslint.ajv` pattern -- made it WORSE: once a package has ANY nested override object, npm stops the plain top-level rule from cascading into that package's subtree at all, silently blocking the fix instead of applying it. The working fix is the flat top-level override alone, no nested entry competing for the same package name. Verified in an isolated clean environment (a scratch worktree with a REAL, non-symlinked node_modules -- this session's own earlier per-package symlink farm, built for testing @beyondnet/* packages against local sources, was independently confirmed to corrupt `npm ls`'s graph display, though not `npm audit`'s severity count): `npm audit --audit-level=high` exits 0 (checked as a real exit code, not just the printed summary, which was once observed stale). 2 moderate findings remain, deliberately not chased: a Windows-only path-traversal in @hono/node-server's serve-static, irrelevant to this repo's Linux CI/deployment, whose fix requires bumping an EXACT-PINNED production dependency (@modelcontextprotocol/sdk) rather than a devDependency override. Re-verified beyond the audit, after rebuilding every workspace dependency exactly as the CI job's own "Build workspace dependencies" step does: core-domain 1442/1442, CLI unit 1436/1436, CLI E2E 132/132, mcp-server 433/437 (4 pre-existing skips) -- all green against the fixed tree. 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)
The security audit fix this closure originally left open turned out to trace to one root cause (a brace-expansion advisory the repo's own override predates), not 29 independent problems. Recorded here in enough detail that the shape of the fix -- why a plain override edit didn't work, why a scoped override made it worse, why two rounds of lockfile-entry deletion were needed -- survives past this session, since the failure modes were the non-obvious part, not the final two-line diff. Status IN-PROGRESS -> DONE, all 4 acceptance criteria ticked, closure record added referencing d85c621. Counters: 595 done (was 594), 14 in progress (was 15), total unchanged at 635, 577 closure records (was 576). 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. |
…rifying CI never triggered on the previous push for over an hour (a genuine webhook gap, not just a slow queue -- confirmed by comparing against other branches' run timestamps in the same window). Manually dispatching sdk-cli-ci.yml and ci-cd.yml surfaced a real regression: `Governance guards (GT-578)` now failed at 23 dead references against the --max-dead 21 this session pinned only two commits earlier. Both new ones were self-inflicted, from GT-636's own validationCommands: `npm run test:e2e (src/sdk/cli, exact CI command)` -- the workspace name was prose in a parenthetical, not a real --workspace argument, so the extractor saw a bare `npm run test:e2e` with no such script at <root>/package.json. `sdk/cli unit: npm run test:unit` -- the "sdk/cli unit:" label prefix parsed as a binary invocation (`binRaw: "sdk/cli"`), not as descriptive text. Both commands are real and already verified to pass; only their phrasing was wrong. Corrected to `npm run test:e2e --workspace src/sdk/cli (exact CI command)` and `npm run test:unit --workspace src/sdk/cli`, both of which the extractor resolves correctly. Ratchet back to exactly 21 -- passes at 21, fails at 20, same as before this session's own record introduced the drift. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… with develop `origin/develop` registered its own GT-634 while this branch was in flight -- `fix(deps)!: the CLI and MCP must resolve the SDK that carries the security wave, 1.2.2 (GT-634)` -- an unrelated fix (a caret range that pinned a pre-security-wave SDK). Same number, two different gaps, discovered while preparing this branch's merge to develop: `git log --all` and every board file across every branch were scanned for the true maximum in use anywhere (636, this branch's own GT-636), landing on 637 as the free number. Pure rename, no content change: every "GT-634"/"gt-634" referring to the dead-reference ratchet fix, across the board rows, catalog entries, the closure evidence record, the ci-cd.yml ratchet-step comment, and the guard's own docstring, becomes "GT-637". Re-verified after the rename: the ratchet still measures exactly 21 (unchanged -- only text moved) and the guard's own 39 self-tests still pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…arson-721318 # Conflicts: # reference/core/control-center/maturity-reports/maturity-reconciliation.json
…op merge The merge conflict on this generated file was resolved with --ours as a placeholder; this replaces it with the real regeneration, which correctly picks up develop's CLI 1.2.1 -> 1.2.2 version bump. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
fix(init): a freshly scaffolded satellite passes its own governance (GT-633)
…ectory
`git push` crashed inside the pre-push hook's repo-optimization step, in this
worktree specifically: `fs.mkdirSync ENOTDIR '.../recursing-carson-721318/.git/
evolith-quarantine/69e8ec9d'`. `.git` at a worktree's root is a plain text
redirect file (`gitdir: <real-gitdir>`), not a directory, and
02-optimize-repo.mjs assumed otherwise in two places:
1. `path.join(root, ".git", "evolith-quarantine")` assumed `.git` was always a
writable directory to build a subtree under. Fixed by resolving the real
git-dir via `git rev-parse --git-dir` -- which is `.git` in a normal
checkout and the per-worktree directory under `.git/worktrees/<name>` here
-- with `path.resolve`, not `path.join`: `--git-dir` prints a RELATIVE path
in a normal checkout but an ABSOLUTE one in a worktree, and `path.join`
would have mangled the absolute case by concatenating it onto `root`.
2. Fixing (1) surfaced a worse defect, verified live: once the crash stopped,
the quarantine loop saw `.git` itself as an unrecognised untracked FILE
(only `allowedDirectories` lists it, not `allowedFiles`) and moved it into
quarantine -- severing this worktree from git entirely. Recoverable, since
quarantine moves rather than deletes, but confirmed by reproducing it and
then restoring `.git` by hand before writing the real fix: exclude `.git`
unconditionally, by name, before the directory/file allowlist split.
The identical root cause was independently present in
03-validate-root-cleanliness.mjs, a separate script with its own duplicated
allowlist -- confirmed failing in this same worktree ("Unauthorized file
found in root: .git") before the same by-name exclusion fixed it too. Lower
blast radius than the pre-push script (CI always checks out fresh, never a
worktree, so this specific guard never hits it there), but the same class of
bug and already fully understood, so fixed alongside it.
Also added two legitimately-tracked root files the first script's allowlist
was missing (evolith.yaml, commitlint.config.mjs), clearing an unrelated
warning this same run surfaced.
Re-verified: both scripts pass clean in this worktree (0 quarantined, 32 root
entries inspected), 01-validate-docs (1491 files) and 08-validate-tracking
(635 gaps, 577 closure records) unaffected.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…1318 fix(ci): root-cleanliness guards assumed .git is always a directory
…implementations GT-633 was fixed twice, in parallel, by sessions that could not see each other. One shipped a standalone capture script reachable by the derived-artifact chain guard and the documentation job (#276, #287, on main); the other rendered the document inside `rule-corpus-triage.spec.ts`, where jest can pin it byte-for-byte (#279, on develop). Both were right about their half and both recaptured the same numbers — which is mutual corroboration of the measurement. But TWO GENERATORS FOR ONE ARTIFACT IS GT-633's OWN DEFECT ONE LEVEL UP: whichever ran last would win and the other's `--check` would go red for no reason a reader could see. That is what blocked develop -> main. The renderer now lives ONCE, in `test/rule-corpus-triage.ts`, with two callers: - `rule-corpus-triage.spec.ts` PINS it byte-for-byte (it can recompute); - `capture-native-evaluability-snapshot.mjs` WRITES it and `--check`s it, which is what the GT-630 chain and docs.yml invoke. The spec no longer writes. `UPDATE_EVALUABILITY_SNAPSHOT=1` is gone: a second writer is how this artifact ended up with two generators, and the surviving one is the command the chain guard already runs. RESOLVING IT SURFACED THE SAME DEFECT IN MINIATURE, twice, and both are fixed: 1. The spec asserted `SUMMARY.byClass` against an INLINE literal sitting beside the `PINNED_CLASS_COUNTS` constant that the dependency-free guard in src/rulesets/standards reads out of this file. Two copies of six numbers, and editing either left the other silently disagreeing. Now one declaration, asserted against and read by both. 2. My own first pass DELETED `PINNED_CLASS_COUNTS` while lifting the loader out. The documentation-job guard THREW rather than passing — "Could not find the PINNED_CLASS_COUNTS literal ... update this parser rather than deleting the check" — which is exactly the behaviour that fix was built for. Restored. The sticky `capturedOn` survives the port: the script renders once with the previous date and replaces that single field only when the CLASSIFICATION moved, so the chain's fixed-point replay stays byte-identical without a second ts-node run. Also converged onto main's shape so the branch merge is trivial rather than conflicted: `iso-5055-mapping.test.mjs`, both standards READMEs, the 5-link chain guard with its fixtures, and the two GT-598 steps in docs.yml. NOTE ON THE NUMBERS: this branch computes native-handler 151, and main pins 154. Not a measurement bug — main has more handler closures landed. The pin and the byte-for-byte snapshot assertion are what will catch it when the branches meet, which is the point of having one renderer. Verified: capture --check faithful (386 rules), build --check up to date (388 rules), replay byte-identical, iso-5055-mapping guard 9/9, chain guard 5 links current and at a fixed point with its own suite 8/8, core-domain jest 1444 passed / 128 suites, tsc --noEmit clean, 01/04/08/09 green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…derer fix(standards): one renderer, two callers — reconcile the two GT-633 implementations
Conflict map for this merge — standards resolved, the rest attributedThe GT-633 design collision is gone: two independent implementations (a standalone capture script on
The remaining conflicts are not GT-633 and are not mine to resolve. Attribution and the resolution I would recommend:
One thing worth flagging beyond the merge
Also, the same convergent-duplication pattern shows up three times in this merge alone: GT-633 fixed twice, the evidence-guard parser fixed twice, and a GT id allocated twice. That is a process signal, not three coincidences. |
Ports to develop the bug found while bringing the same design to main (#295). `classificationChanged` compared `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 it always reported "changed", `capturedOn` was refreshed on every run, and the GT-630 fixed-point replay would have failed on any day after a capture. WHY IT WAS INVISIBLE HERE. The reconciliation (#294) landed and was verified on the same day the snapshot was captured, so both runs stamped 2026-07-30 and the bytes matched. A green that depends on the date being unchanged is exactly the kind this chain exists to distrust. It surfaced on main only because that branch's committed snapshot carried 2026-07-29, so the two runs disagreed out loud. OBSERVED BOTH WAYS rather than declared, by forcing the committed date to 2026-07-28 and running the capture with the classification unchanged: without the fix -> capturedOn becomes 2026-07-30 (drift) with the fix -> capturedOn stays 2026-07-28 (sticky, as designed) Verified after restoring the committed snapshot: `--check` passes, the replay is byte-identical, the derived-artifact chain reports 5 links current and at a fixed point, and the ISO/IEC 5055 mapping guard is 9/9. Only the script changed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
fix(standards): the sticky capture date was rewritten on every run
…for byte COMMENTS ONLY. No logic changes, and the diff is 3 insertions / 7 deletions in a single comment block. The sticky-date fix was written twice — once on main (#295) and once here (#296) — with the same logic and DIFFERENT prose, because the develop copy added where the bug had been found. That left `capture-native-evaluability-snapshot.mjs` conflicting between the branches over ten lines of comment, after the whole point of the exercise had been to stop this file needing a decision at merge time. Taking main's text verbatim, so the file is byte-identical on both branches and drops out of the develop -> main conflict set entirely. Where the bug was found is recorded in #296's commit message and PR, which is the right place for it: a comment that differs per branch is a merge conflict pretending to be context. Verified that behaviour is untouched: `--check` passes, the replay is byte-identical, and the derived-artifact chain reports 5 links current and at a fixed point. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
chore(standards): align the capture script's comment with main, byte for byte
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>
…n-2026-07-30 Merge main into develop — resolve the 17-file divergence, unblocking #284
📊 Bilingual Coverage ImpactPR Changes
Repository Coverage
To create skeletons: node .harness/scripts/generate-es-skeleton.mjs <file.md>Generated by GitHub Actions |
Pull Request Summary
Aligns
mainwithdevelopso 1.2.2 can be published frommain, with provenance pointing at the default branch rather than a feature branch.Two independent pieces of work:
1. The release (#282) —
@beyondnet/evolith-cliand@beyondnet/evolith-mcp1.2.2 (GT-634). Both declared@beyondnet/evolith-sdk: ^1.1.0, which resolves to exactly 1.1.0 — the SDK publishes1.0.0,1.1.0,2.0.0and nothing in between, and a caret cannot cross a major.sdk@1.1.0is from 2026-07-18;sdk@2.0.0shipped 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. The lockfile made it concrete: it pinned the registry tarball ofsdk@1.1.0nested inside each consumer, where it shadows the workspace link, sonpm ciresolved the pre-wave SDK while every local run read the symlink to 2.0.0.Also in that PR, three version-pinned artifacts the bump moves (
maturity-reconciliation.json,evolith-machine-contracts.jsonproducer version,product-inventory.{md,es.md}) and a real fix to the release gate:2. #279 — the evaluability capture (GT-633), from a parallel session. Already merged and validated on
develop; it rides along under the normal develop → main flow. Its recapture (native-handler139 → 151,documentation-only129 → 136, backlog 60 → 48, corpus 379 → 386) was independently reproduced in the other branch of this work and the numbers agree.Verified
All 7 required checks green on
87ebd609before #282 merged. On the release branch:plan-npm-release→ 2 of 8 WILL PUBLISH; a clean install of both 1.2.2 tarballs, in a directory that has never seen this workspace, resolvessdk@2.0.0;check:install-smokepasses both plainly and withnpm_config_dry_run=true(the runner condition, reproduced locally); CLI jest 1436/1436, mcp-server 433/433.After this merges
npm-release.ymlonmain:dry_run=truefirst, thendry_run=falseto publish@beyondnet/evolith-mcp@1.2.2and@beyondnet/evolith-cli@1.2.2with provenance.Evolith Core Quality Gates
.mdand.es.mdupdated wherever a document changed (product inventory; fix(standards): generate the evaluability capture and make its guard read Core #279's standards READMEs).01-validate-docs.mjsgreen.04-check-bilingual-parity.mjsgreen.Linked ADRs / Issues
Conventional Commits
🤖 Generated with Claude Code