Skip to content

fix(deps)!: the CLI and MCP must resolve the SDK that carries the security wave, 1.2.2 (GT-634) - #285

Merged
beyondnetPeru merged 4 commits into
mainfrom
release/npm-1.2.2-to-main
Jul 30, 2026
Merged

fix(deps)!: the CLI and MCP must resolve the SDK that carries the security wave, 1.2.2 (GT-634)#285
beyondnetPeru merged 4 commits into
mainfrom
release/npm-1.2.2-to-main

Conversation

@beyondnetPeru

Copy link
Copy Markdown
Contributor

Pull Request Summary

The 1.2.2 release, straight onto main, so it can be published with provenance pointing at the default branch.

Why not developmain (#284). That merge is blocked, and not by anything in this release: the same GT-633 fix landed twice by different routes — on main as #276 (the capture-script approach, b0824a64) and on develop as #279 (an in-spec renderer, d11b9899). They conflict on five src/rulesets/standards files. That reconciliation is a design decision about which generator survives, it belongs to the people who wrote the two halves, and it should not sit in the critical path of an irreversible publish. These four commits touch none of those files and cherry-pick onto main cleanly.

The defect (GT-634)

@beyondnet/evolith-cli@1.2.1 and @beyondnet/evolith-mcp@1.2.1 both declare @beyondnet/evolith-sdk: ^1.1.0, which resolves to exactly 1.1.0 — the SDK publishes 1.0.0, 1.1.0, 2.0.0 and nothing between, and a caret cannot cross a major.

artifact published
sdk@1.1.0 2026-07-18 five days before the security wave
sdk@2.0.0 2026-07-27 shipped the wave (closed GT-570)
cli@1.2.1, mcp@1.2.1 2026-07-28 published after 2.0.0 existed, still on 1.x

The lockfile made it concrete rather than cosmetic: it pinned the registry tarball of sdk@1.1.0, with integrity hashes, nested at src/sdk/cli/node_modules/@beyondnet/evolith-sdk and the same under mcp-server. 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.

Patch, not minor: 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.

Three version-pinned artifacts, and one broken gate

The bump moves maturity-reconciliation.json (cliPackage), evolith-machine-contracts.json (producer.version) and product-inventory.{md,es.md}. Each was found by a required check going red, and each is carried here.

check-install-smoke could not run under the release rehearsal, and reported a good artifact as broken. npm publish --dry-run exports npm_config_dry_run=true into prepublishOnly, where this guard runs; its nested npm pack/npm install inherited it, so pack printed a filename and wrote nothing. Measured, not guessed — and two earlier explanations of mine were wrong and are recorded in the commit so they are not re-attempted (npm 10 does honour --pack-destination). The consequence is bigger than the bug: dry_run had never exercised this gate, and the 2026-07-28 publish passed it only because no dry-run flag was in the environment.

Verified on this exact base

  • plan-npm-release.mjs2 of 8 WILL PUBLISH, in dependency order
  • clean install of both 1.2.2 tarballs, in a directory that has never seen this workspace, resolves @beyondnet/evolith-sdk@2.0.0
  • check:install-smoke passes plainly and under npm_config_dry_run=true — the runner condition, reproduced locally
  • 09-reconcile-maturity --check, 10-validate-contract-conformance, 11-validate-product-docs green; 41-validate-evidence-commands --strict --max-dead 40 green against main's new budget
  • CLI jest 1436/1436 (99 suites), mcp-server 433/433 (53 suites)

After this merges

npm-release.yml on main: dry_run=true first, then dry_run=false to publish @beyondnet/evolith-mcp@1.2.2 and @beyondnet/evolith-cli@1.2.2 with provenance.

Evolith Core Quality Gates

  • Bilingual Parity: product-inventory.md and .es.md both regenerated.
  • Validation Scripts: 01-validate-docs.mjs green.
  • Bilingual Validation: 04-check-bilingual-parity.mjs green.
  • Agnosticism: no new technology dependency; this narrows an existing range.

Linked ADRs / Issues

  • Link to ADR: n/a
  • Link to Issue: GT-634; removes GT-624's remaining deprecation blocker

Conventional Commits

  • Commits follow Conventional Commits.

🤖 Generated with Claude Code

beyondnetPeru and others added 4 commits July 29, 2026 19:04
…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>
@beyondnetPeru
beyondnetPeru requested a review from a team as a code owner July 30, 2026 00:05
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@github-actions

Copy link
Copy Markdown

📊 Bilingual Coverage Impact

PR Changes

  • Paired EN/ES files modified: 2
  • New EN files needing ES translation: 1

Repository Coverage

Metric Value
Total EN files 524
Total ES files 498
Paired files 0
Coverage 0%

⚠️ Action required: 1 new EN file(s) added without ES counterparts.

To create skeletons:

node .harness/scripts/generate-es-skeleton.mjs <file.md>

Generated by GitHub Actions

@beyondnetPeru
beyondnetPeru merged commit f84fe73 into main Jul 30, 2026
34 of 36 checks passed
@beyondnetPeru
beyondnetPeru deleted the release/npm-1.2.2-to-main branch July 30, 2026 19:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant