Skip to content

Commit 36f13bb

Browse files
os-billclaude
andauthored
fix(scripts): the manifest generator defaults its @object-ui version from the PIN, not the record (#18875)
Fixes #18819 Clause-②: no ## The defect, re-measured on this branch's base `origin/main` `36583e989b`. The generator read its two provenance fields at two different moments: ``` :129 const version = arg('--objectui-version') ?? record.objectuiPackagesVersion; :199 objectuiSha: pin, :200 objectuiPackagesVersion: version, ``` `objectuiSha` is re-read from the LIVE `.objectui-sha`; `objectuiPackagesVersion` fell back to the value already sitting in `scripts/sdui-manifest.record.json`. A regeneration after a pin bump therefore wrote the NEW pin under the OLD version string — a record whose two objectui fields describe two different commits, which is exactly the record `scripts/check-sdui-manifest.mjs` check 4 rejects. The person that reds is the one who followed the documented bump procedure, so the producer is where it gets fixed. ## Step one, before writing any fix: is that path reachable today? The counterfactual on `:129` is that the defect is unreachable if `arg('--objectui-version')` is never empty. It is reachable. **Paths, not counts.** **No automated caller exists at all.** No `package.json` script, no workflow step and no shell line executes this generator. `scripts/bump-objectui.sh` and `scripts/build-console.sh` only `echo` the command. Every invocation is therefore a person or an agent typing one of the spellings the tree documents. Of those spellings, six omit `--objectui-version`: | path | spelling | |:---|:---| | `scripts/check-sdui-manifest.mjs:228` | `Regenerate: node scripts/gen-sdui-manifest-node.mjs` — printed when the artefact is missing | | `scripts/check-sdui-manifest.mjs:275` | `Regenerate: node scripts/gen-sdui-manifest-node.mjs` — printed on a sha256 mismatch | | `scripts/sdui-manifest.record.json:10` | `Regenerate + re-record: node scripts/gen-sdui-manifest-node.mjs` | | `packages/spec/scripts/manifest-prescription.ts:81` | ``regenerate it and its record with `node scripts/gen-sdui-manifest-node.mjs` `` | | `scripts/gen-sdui-manifest-node.mjs:8` | the bare usage line | | `scripts/gen-sdui-manifest-node.mjs:9` | the `--modules-root DIR` offline usage line | Two of those six are printed **by the gate itself**. The remaining spellings do carry the flag: `AGENTS.md:156`, `docs/releases-maintenance.md:116` and `:279`, `scripts/bump-objectui.sh:11` and `:739`, `scripts/build-console.sh:297`, `scripts/check-sdui-manifest.mjs:301` `:303` `:320`. The other paths that name the generator carry no command at all and are listed here for completeness: `.github/workflows/lint.yml:799` (a comment), `packages/spec/scripts/check-generated.ts:391`, `packages/spec/scripts/check-react-blocks-declaration-parity.ts:85`, and `packages/spec/scripts/check-react-blocks-declaration-parity.test.ts:565` `:569` (a pin test on the prescription text). ## A or B — decided by that reading, not by preference Zero automated call sites means option B's stated cost ("every call site has to change") is nearly zero *in code*. But B is not free: it would turn all six documented no-flag spellings into refusals, including the `--modules-root` offline mode, and it would refuse even when the answer is sitting in the objectui checkout the bump already required. So: **A for the default, B for the residue.** - `--objectui-version` still wins when passed. - Otherwise the version is what `packages/core/package.json` DECLARES at the pinned commit, read through `check-sdui-manifest.mjs`'s exported `readPinnedDeclaredVersion` — the same function check 4 judges the written record with, imported rather than re-typed. Producer and gate now read one oracle; two readers of two oracles is where this drift came from. - When that oracle cannot answer (no objectui checkout, or one that does not carry the pin) the generator REFUSES and names both remedies. An unread version is never converted into a version string — Route and surface ownership §3, prefer failing to falling back, and Prime Directive #12's ban on a lenient `??` fallback in place of fixing the producer. Importing the gate's exported oracle changes no gate: `scripts/check-sdui-manifest.mjs` is untouched in this diff, and its CLI body is behind `isEntrypoint(import.meta.url)`, so importing it runs nothing. ## Verification Every leg below ran against a fixture tree in a scratchpad, or against the real repo with a trap and a proved restore. `.objectui-sha` and `scripts/sdui-manifest.record.json` are read-only this round; neither is in this diff. ### LIT — pin bumped, `--objectui-version` not passed. Both legs read. A synthetic objectui checkout with two commits (`a183cfbf…` declaring `17.6.0`, `51ecffcc…` declaring `18.0.0-fixture`), a stub `@object-ui` install behind `--modules-root`, and a fixture record naming the OLD sha at `17.6.0`. The fixture pin is the NEW sha. | leg | generator | record written | `check-sdui-manifest.mjs` | |:---|:---|:---|:---| | before | `36583e989b` | `51ecffccfb32 / 17.6.0` — new pin, **old version** | **exit 1**, check 4 red | | after | this branch | `51ecffccfb32 / 18.0.0-fixture` | **exit 0**, green | The before leg's gate output is check 4 naming the defect verbatim: ``` the record's two objectui fields describe different commits: objectuiPackagesVersion is "17.6.0", but objectui 51ecffccfb32… — the pin the record itself names — declares "18.0.0-fixture" in packages/core/package.json ``` ### DARK — pin not bumped. Reads zero, on the REAL artefact. Not a fixture: the real `scripts/gen-sdui-manifest-node.mjs`, the real pin `53ded82bf7a494f54e344e19099dbf00854b8694`, and the real published `@object-ui/*` 17.6.0 set installed once and reused by both legs so the install cannot be the variable. The base generator was swapped in on disk, its arrival proved by anchor counts before the leg ran (`?? record.objectuiPackagesVersion` = 1, `readPinnedDeclaredVersion` = 0, and a `git hash-object` differing from the HEAD blob), then restored. ``` leg after (fixed) : 57 components, 66910 bytes, sha256 49211fee7792cf51174930dc2c1be2169d5175f77a83491f68f90b3f0c19e69d leg before (base) : 57 components, 66910 bytes, sha256 49211fee7792cf51174930dc2c1be2169d5175f77a83491f68f90b3f0c19e69d cmp: BYTE-IDENTICAL ``` That sha256 is also the one the tracked record already carries, and the tracked `sdui.manifest.json` HEAD blob is still `78f870e42fe9` — unmoved. Restore proved by state, not by an exit code: `git diff HEAD` empty and `git status --porcelain` empty afterwards. ### Two further legs - **Refusal residue.** Pin bumped, no flag, oracle unreachable (`OBJECTUI_ROOT` at a directory with no `.git`): base generator exits 0 and silently writes the drifted record; this branch exits 1, writes no manifest, and leaves the record untouched. - **Explicit precedence.** `--objectui-version 99.1.2-explicit` is honoured unchanged on both legs; the success line now also names where the version came from. ### Gates - `node scripts/check-sdui-manifest.mjs` — **exit 0** on the clean tree, and its check 4 leg resolved for real: "@object-ui 17.6.0 is the version objectui 53ded82… declares in packages/core/package.json (read from /home/user/objectui)". - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 26 commands; all 26 run, **all exit 0**, and `--ran` with exit codes recorded reconciles: "26 derived famil(ies) accounted for — 26 run, 0 NOT-MEASURED (a DERIVED zero)". - The seven roster families the tool flags as "roster under scripts, which one of your paths is in — silence is not evidence in EITHER direction" were run too. Six exit 0. `pnpm check:published-readme-exports` exits **3 — PREREQUISITE NOT MET** (46 packages unbuilt); that is NOT MEASURED, not a finding, and this diff moves no package source and no README. - `pnpm lint` (`eslint . --no-inline-config`), the whole repo, **exit 0** at `1c3a721ac1`. - `pnpm check:nul-bytes` exit 0; an independent control-byte scan of the changed file is clean. No changeset: nothing published moves. `scripts/**` appears in no package's `files[]` and the root package is `private`, so no released package's contents change. ## Acceptance notes Noted, not filed — none is a reproducible defect, a declared-contract violation, or a metadata-authoring trap, so none earns a card under Prime Directive #10: - `scripts/check-sdui-manifest.mjs` check 4's header paragraph, and its check-4 failure text at `:318-319`, both explain the producer default this PR removes ("The generator REUSES the version already recorded when `--objectui-version` is not passed"). After this lands those sentences describe history rather than current behaviour. The gate is fenced read-only for this card, so they are left exactly as they are. The check itself stays load-bearing — it still catches a hand-edited record, an explicitly-wrong `--objectui-version`, and any other producer. - `packages/spec/scripts/check-react-blocks-declaration-parity.ts:88-89` says the node producer "enumerates the PUBLISHED `@object-ui/*` packages at the version named in `scripts/sdui-manifest.record.json`". That sentence is wrong-by-default after this change. Not fixed in place: editing it pulls `packages/spec` into the diff and with it a whole gate family this card does not otherwise owe, against the bounded-in-place-fix condition of adding no new verification surface. - The residual hole this PR deliberately does not close: an operator who passes `--objectui-version` **explicitly and wrongly** still writes a drifted record. The generator could warn when an explicit value disagrees with the resolvable pin oracle. Check 4 already reds the resulting record, so this is a loudness improvement, not a correctness gap. - #17735 is not addressed here — whether the published tarballs at a version were built from the pinned commit is that card's producer question, and it carries `needs-user-decision`. This change moves the version's SOURCE and nothing about that relation. #18814's check 4 is not modified here either. Fenced files honoured: `scripts/sdui-manifest.record.json` and `.objectui-sha` are held by open PR #18723 and are read-only in this round. The real DARK leg did rewrite the record as a side effect of running the generator, exactly as warned; both were restored from HEAD and the restore is proved by an empty `git diff HEAD`, never by an exit code. --- _Generated by [Claude Code](https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0b31d90 commit 36f13bb

1 file changed

Lines changed: 50 additions & 13 deletions

File tree

‎scripts/gen-sdui-manifest-node.mjs‎

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* gen-sdui-manifest-node — regenerate the repo-root `sdui.manifest.json` from
66
* objectui's PUBLISHED registry packages, without a browser.
77
*
8-
* node scripts/gen-sdui-manifest-node.mjs # temp npm install (network)
8+
* node scripts/gen-sdui-manifest-node.mjs # temp npm install (network); version from the PIN
99
* node scripts/gen-sdui-manifest-node.mjs --modules-root DIR # use a preinstalled node_modules parent
1010
* node scripts/gen-sdui-manifest-node.mjs --objectui-version V # override the version to install
1111
*
@@ -48,15 +48,33 @@
4848
* tombstone there; this header is what both of them now send readers to for
4949
* the readings.
5050
*
51-
* ## Versioning contract
51+
* ## Versioning contract — ONE oracle, shared with the gate
5252
*
5353
* The manifest must describe the registry the SHIPPED console runs — i.e. the
5454
* `@object-ui/*` version that `.objectui-sha` ships. This script installs that
55-
* version from npm; the version is read from `scripts/sdui-manifest.record.json`
56-
* unless `--objectui-version` overrides it. After a pin bump, pass the version
57-
* the new pin carries (its `packages/core/package.json` in the objectui
58-
* checkout the bump already required) — `check-sdui-manifest.mjs` goes red on
59-
* a moved pin until this script has been re-run and the record re-written.
55+
* version from npm. Which version that is has exactly one source:
56+
* `--objectui-version` when it is passed, otherwise what
57+
* `packages/core/package.json` DECLARES at the pinned commit — read through
58+
* `check-sdui-manifest.mjs`'s exported `readPinnedDeclaredVersion`, the SAME
59+
* function its check 4 judges the written record with, imported rather than
60+
* re-typed.
61+
*
62+
* ⛔ The version is NOT defaulted from `scripts/sdui-manifest.record.json`.
63+
* That default is what this file was fixed for: `objectuiSha` below is re-read
64+
* from the LIVE pin while `objectuiPackagesVersion` fell back to the value
65+
* already in the record, so the two fields were read at two different moments.
66+
* A regeneration after a pin bump therefore wrote the NEW pin under the OLD
67+
* version string — a record whose two objectui fields describe two different
68+
* commits, which check 4 reds. The person it reds is the one who followed the
69+
* documented bump procedure, so the producer is where it is fixed. Two readers
70+
* consulting two oracles is precisely where that drift came from; there is now
71+
* one oracle and both read it.
72+
*
73+
* When the oracle cannot answer — no objectui checkout, or one that does not
74+
* carry the pinned commit — this script REFUSES. An unread version is never
75+
* converted into a version string (Route & surface ownership §3: prefer failing
76+
* to falling back). Point `OBJECTUI_ROOT` at a checkout that carries the pin,
77+
* or pass `--objectui-version` explicitly.
6078
*
6179
* ## Preconditions (all loud)
6280
*
@@ -71,6 +89,8 @@ import { tmpdir } from 'node:os';
7189
import { dirname, join } from 'node:path';
7290
import { pathToFileURL, fileURLToPath } from 'node:url';
7391

92+
import { readPinnedDeclaredVersion } from './check-sdui-manifest.mjs';
93+
7494
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
7595
const RECORD_PATH = join(ROOT, 'scripts', 'sdui-manifest.record.json');
7696
const OUT_PATH = join(ROOT, 'sdui.manifest.json');
@@ -126,12 +146,26 @@ if (existsSync(RECORD_PATH)) {
126146
}
127147
}
128148

129-
const version = arg('--objectui-version') ?? record.objectuiPackagesVersion;
149+
// The version this run installs AND records. `--objectui-version` wins; the
150+
// default is what the PIN declares, through check 4's own oracle — never the
151+
// version already sitting in the record (see the versioning contract above).
152+
const explicitVersion = arg('--objectui-version');
153+
let version = explicitVersion;
154+
let versionSource = '--objectui-version, as passed';
130155
if (!version) {
131-
fail(
132-
'no --objectui-version and no version in scripts/sdui-manifest.record.json.\n' +
133-
" Pass the @object-ui/* version the pin ships (the objectui checkout's packages/core/package.json).",
134-
);
156+
const leg = readPinnedDeclaredVersion(pin, { root: ROOT });
157+
if (leg.status !== 'resolved') {
158+
fail(
159+
`no --objectui-version, and the pinned commit's declared version could not be read: ${leg.reason}\n` +
160+
' ⛔ This script does NOT fall back to the version in scripts/sdui-manifest.record.json: after a pin\n' +
161+
' bump that re-records the NEW pin under the OLD version string, and check-sdui-manifest.mjs check 4\n' +
162+
' reds exactly that record. Either:\n' +
163+
` - point OBJECTUI_ROOT at an objectui checkout carrying ${pin.slice(0, 12)}…, or\n` +
164+
' - pass --objectui-version {the @object-ui version that pin ships} explicitly.',
165+
);
166+
}
167+
version = leg.version;
168+
versionSource = `packages/core/package.json at objectui ${pin.slice(0, 12)}… (read from ${leg.where})`;
135169
}
136170

137171
let modulesRoot = arg('--modules-root');
@@ -205,4 +239,7 @@ const nextRecord = {
205239
};
206240
writeFileSync(RECORD_PATH, JSON.stringify(nextRecord, null, 2) + '\n');
207241
console.error(`✓ wrote sdui.manifest.json (${count} components, ${Buffer.byteLength(json)} bytes, sha256 ${sha256.slice(0, 12)}…)`);
208-
console.error(`✓ re-recorded scripts/sdui-manifest.record.json at pin ${pin.slice(0, 12)} / @object-ui ${version}`);
242+
console.error(
243+
`✓ re-recorded scripts/sdui-manifest.record.json at pin ${pin.slice(0, 12)} / @object-ui ${version}\n` +
244+
` (version from ${versionSource})`,
245+
);

0 commit comments

Comments
 (0)