fix(deps)!: the CLI and MCP must resolve the SDK that carries the security wave, 1.2.2 (GT-634) - #282
Merged
Merged
Conversation
…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>
|
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 |
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>
5 tasks
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.
Pull Request Summary
The published CLI and MCP install an SDK built before the security wave they claim to carry. Fixes it and prepares 1.2.2 of both.
@beyondnet/evolith-cli@1.2.1and@beyondnet/evolith-mcp@1.2.1both declare@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. The dates are the finding:sdk@1.1.0sdk@2.0.0cli@1.2.1,mcp@1.2.1So the exposure GT-570 declared closed "for anyone installing
latest" was not closed for this dependency.The lockfile made it concrete, not cosmetic
package-lock.jsonpinned the registry tarball ofsdk@1.1.0, with integrity hashes, atsrc/sdk/cli/node_modules/@beyondnet/evolith-sdkandsrc/packages/mcp-server/node_modules/@beyondnet/evolith-sdk. A cleannpm cifetched 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, and the nested 1.1.0 is what a fresh install resolves. Same class as GT-625 — the workspace hiding what a real install does — and the reason nothing caught it. Both entries disappear once the range is^2.0.0.Patch, and the reason is checked
Neither package re-exports the SDK and both use only
EvolithRestClient, so 2.0.0's breaking change (payload types re-exported fromcore-domain,.passed→.verdict,'info'severity retired) reaches no public API of either. The!in the commit marks the transitive major for anyone who reaches the SDK through these packages.Also here, because it blocked the rehearsal
check-install-smokewas reporting the npm version rather than the artifact:--pack-destinationis 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 checks both and moves a stray tarball out of the source tree.Why this is separate from #277
That branch also carries the GT-633 standards work, which now needs reconciling against #279 — the same fix landed on
developindependently, with matching numbers, but a second snapshot generator. None of that touches the six files here, and an irreversible publish should not wait on a design merge.Verified — with an install, not a range
plan-npm-release.mjs→ 2 of 8 WILL PUBLISH, in dependency order; the other 6 correctly reported as already on the registrybeyondnet-evolith-cli-1.2.2.tgzin a directory that has never seen this workspace resolves@beyondnet/evolith-sdk@2.0.0; same forbeyondnet-evolith-mcp-1.2.2.tgzcheck:install-smoke→ 36@beyondnet/*specifiers resolve, installed binary boots printing 1.2.2check:release-drift→ passednpm install --package-lock-onlyondevelop's lockfile, not hand-mergedEvolith Core Quality Gates
reference/document changed; the changelogs are English-only by existing convention.01-validate-docs.mjsunaffected — no Markdown underreference/touched.Linked ADRs / Issues
Conventional Commits
🤖 Generated with Claude Code