From d5e524f2d6631cc3950f7049169bb9d5b00b52f2 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 9 Sep 2026 20:08:35 +0000 Subject: [PATCH 1/2] feat(cli)!: rename the --json payload key specVersionGap to protocolVersionGap The advisory's axis moved to `manifest.engines.protocol` in #13860; the published key name lagged one release behind it. A key spelled `specVersion*` invites the inference that a writable `manifest.specVersion` exists, and because `ManifestSchema` is not `.strict()` and drops unknown keys with nothing said (#14192), acting on that inference yields a manifest that looks normal and whose line never took effect. One stroke, no alias, no dual-key window. Value shape unchanged. The three in-repo e2e suites that pinned the old key move with it; zero external consumers were measured. Claude-Session: https://claude.ai/code/session_015QE8qk46e5CHJxyQEUjbf8 Co-authored-by: Claude --- .changeset/protocol-version-gap-key-rename.md | 44 +++++++++++++++++++ packages/cli/src/commands/compile.ts | 8 ++-- packages/cli/src/commands/validate.ts | 19 +++++--- .../build-json-advisory-parity.e2e.test.ts | 2 +- ...ild-json-undeclared-key-parity.e2e.test.ts | 2 +- .../validate-json-warning-parity.e2e.test.ts | 6 +-- 6 files changed, 66 insertions(+), 15 deletions(-) create mode 100644 .changeset/protocol-version-gap-key-rename.md diff --git a/.changeset/protocol-version-gap-key-rename.md b/.changeset/protocol-version-gap-key-rename.md new file mode 100644 index 0000000000..a7db3a8d46 --- /dev/null +++ b/.changeset/protocol-version-gap-key-rename.md @@ -0,0 +1,44 @@ +--- +"@objectstack/cli": minor +--- + +feat(cli)!: the `--json` payload key `specVersionGap` is renamed to `protocolVersionGap` (#14261) + +**BREAKING** — a published machine surface changes a key name. `os validate --json` and +`os build --json` emit **`protocolVersionGap`** where they emitted `specVersionGap`. A +consumer reading `specVersionGap` reads `undefined` after this release and must switch to +the new name. There is **no alias and no dual-key transition window**: one axis, one name. + +The value shape is unchanged — `null` when the app's declared compatibility range admits +the installed `@objectstack/spec`, otherwise the same advisory record with the same +members. Nothing else on either payload moves: no other key is added, removed or +reshaped, and the text faces of both commands are byte-identical. + +## Why the name had to move + +The axis this advisory reports moved in **#13860**: it used to read the undeclared +`manifest.specVersion` and now reads `manifest.engines.protocol`, which is declared +(`PluginEnginesSchema`), stamped by every scaffold, and enforced at boot. The published +key name stayed behind for one release, deliberately — renaming a machine face with +pinned consumers is a break, and no ruling covered it at the time. + +Leaving it is a correctness problem, not untidiness. A key spelled `specVersion*` invites +the reader — an AI agent above all — to infer that a writable `manifest.specVersion` +exists. `ManifestSchema` is not `.strict()` and **silently drops unknown keys** (#14192), +so acting on that inference does not produce an error: it produces a manifest that looks +entirely normal and whose `specVersion` line never took effect. That is the same +ghost-key breadcrumb mechanism that caused #13860 in the first place, left standing on +the output side. + +## What a consumer should do + +```diff +- if (payload.specVersionGap) { … } ++ if (payload.protocolVersionGap) { … } +``` + +The breaking surface was measured before the rename and is closed inside this repository: +the only consumers of the old key were three in-repo e2e suites, which move in this same +change; **zero external consumers were found**. Graded `minor` by the maintainer's +explicit grading of 2026-09-02; the banner above carries the breaking-ness the level +cannot. diff --git a/packages/cli/src/commands/compile.ts b/packages/cli/src/commands/compile.ts index 667638f2a9..bbb743b63e 100644 --- a/packages/cli/src/commands/compile.ts +++ b/packages/cli/src/commands/compile.ts @@ -908,9 +908,11 @@ export default class Compile extends Command { // Same key `os validate --json` uses, so a CI consumer reads one shape // from either command rather than learning two. conversions: conversionNotices, - // Published key name kept; the axis behind it moved to - // `manifest.engines.protocol` (#13860). See validate.ts. - specVersionGap: protocolGap, + // [#14261] Renamed from the retired `specVersion*` spelling: one + // axis, one name. The axis itself moved to + // `manifest.engines.protocol` in #13860; the published key name + // follows it here. Value shape unchanged. See validate.ts. + protocolVersionGap: protocolGap, stats, duration: timer.elapsed(), }, 0, { compact: true }); diff --git a/packages/cli/src/commands/validate.ts b/packages/cli/src/commands/validate.ts index 4cccf0fc09..057339febb 100644 --- a/packages/cli/src/commands/validate.ts +++ b/packages/cli/src/commands/validate.ts @@ -541,12 +541,17 @@ export default class Validate extends Command { // this one. warnings: warningsSoFar(), conversions: conversionNotices, - // The payload key keeps its published name. The AXIS it reports + // [#14261] The key now spells the axis it reports. That axis // moved from the undeclared `manifest.specVersion` to - // `manifest.engines.protocol` (#13860), but this is a machine face - // with pinned consumers, and renaming it is a break nobody asked - // for. Its value shape is unchanged. - specVersionGap: protocolGap, + // `manifest.engines.protocol` in #13860, and the published key + // name lagged one release behind it. A key spelled `specVersion*` + // invites the inference that `manifest.specVersion` is writable; + // `ManifestSchema` is not `.strict()` and drops unknown keys with + // nothing said (#14192), so acting on that inference produces a + // manifest that looks fine and whose line never took effect. The + // rename is one stroke, no alias, no dual-key window; its value + // shape is unchanged. + protocolVersionGap: protocolGap, duration: timer.elapsed(), }, // `--strict` means one thing — "treat warnings as errors" — and it now @@ -556,8 +561,8 @@ export default class Validate extends Command { // its `⚠` block while the payload carries them under `conversions`. // Gating on the payload field would have left `--json --strict` at 0 // for a config whose only advisories are conversion notices — the - // same divergence one collection narrower. `specVersionGap` stays out - // on both faces; it is never gated by `--strict` (see below). + // same divergence one collection narrower. `protocolVersionGap` stays + // out on both faces; it is never gated by `--strict` (see below). // // `valid: true` beside a 1 is not a contradiction, it is the text // face verbatim: that path prints "Validation passed" and THEN fails diff --git a/packages/cli/test/build-json-advisory-parity.e2e.test.ts b/packages/cli/test/build-json-advisory-parity.e2e.test.ts index e48b4c4a74..b2ac90cbee 100644 --- a/packages/cli/test/build-json-advisory-parity.e2e.test.ts +++ b/packages/cli/test/build-json-advisory-parity.e2e.test.ts @@ -310,7 +310,7 @@ describe('#11727 — `os build --json` carries the capability-provider and packa 'runtimeModule', 'runtimeModuleSize', 'size', - 'specVersionGap', + 'protocolVersionGap', 'stats', 'success', 'warnings', diff --git a/packages/cli/test/build-json-undeclared-key-parity.e2e.test.ts b/packages/cli/test/build-json-undeclared-key-parity.e2e.test.ts index ed75a6571c..4403919b47 100644 --- a/packages/cli/test/build-json-undeclared-key-parity.e2e.test.ts +++ b/packages/cli/test/build-json-undeclared-key-parity.e2e.test.ts @@ -256,7 +256,7 @@ describe('#11643 — `os build --json` carries the undeclared-authoring-key warn 'runtimeModule', 'runtimeModuleSize', 'size', - 'specVersionGap', + 'protocolVersionGap', 'stats', 'success', 'warnings', diff --git a/packages/cli/test/validate-json-warning-parity.e2e.test.ts b/packages/cli/test/validate-json-warning-parity.e2e.test.ts index 89b7d29226..6f19fab9ef 100644 --- a/packages/cli/test/validate-json-warning-parity.e2e.test.ts +++ b/packages/cli/test/validate-json-warning-parity.e2e.test.ts @@ -35,7 +35,7 @@ * * The text face folds two more advisory streams into the same `⚠` block that * the JSON payload carries as its own top-level fields instead — `conversions` - * (ADR-0087 D2 load-time conversion notices) and `specVersionGap`. Those are a + * (ADR-0087 D2 load-time conversion notices) and `protocolVersionGap`. Those are a * declared difference in SHAPE, not a drop: the information is reachable on both * faces. Rather than silently ignoring them, every fixture ASSERTS both are * empty, so the exact set equality below is honest about its scope — and if a @@ -242,12 +242,12 @@ describe('#10953 — text and --json carry the same warning set', () => { const payload = JSON.parse(json.stdout) as { warnings?: unknown; conversions?: unknown; - specVersionGap?: unknown; + protocolVersionGap?: unknown; }; // Scope declaration, asserted rather than assumed — see the header. expect(payload.conversions, 'fixture must raise no conversion notices').toEqual([]); - expect(payload.specVersionGap, 'fixture must raise no spec-version gap').toBeNull(); + expect(payload.protocolVersionGap, 'fixture must raise no protocol-version gap').toBeNull(); const lines = textWarnings(text.stdout); const messages = jsonWarnings(payload); From 141f5faf66d5de0fe1b73fbd62b373189b9a6fa8 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 9 Sep 2026 20:38:27 +0000 Subject: [PATCH 2/2] chore(changeset): answer the ADR-0087 disposition question for the key rename `check-adr-0087-registration` requires a declared-breaking changeset to state its ledger disposition in writing. The renamed member is a CLI `--json` output key emitted from an inline object literal: no Zod schema, no `packages/spec` declaration, no stored representation, so `objectstack migrate meta` has nothing to reach. Claude-Session: https://claude.ai/code/session_015QE8qk46e5CHJxyQEUjbf8 Co-authored-by: Claude --- .changeset/protocol-version-gap-key-rename.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.changeset/protocol-version-gap-key-rename.md b/.changeset/protocol-version-gap-key-rename.md index a7db3a8d46..aa5a7b884b 100644 --- a/.changeset/protocol-version-gap-key-rename.md +++ b/.changeset/protocol-version-gap-key-rename.md @@ -2,6 +2,8 @@ "@objectstack/cli": minor --- + + feat(cli)!: the `--json` payload key `specVersionGap` is renamed to `protocolVersionGap` (#14261) **BREAKING** — a published machine surface changes a key name. `os validate --json` and