Skip to content

Commit ba4afcc

Browse files
fix(release): judge a .objectstack-sha console bundle against objectui's own build of that commit (#24532)
Fixes #24510 Clause-②: no ## What this changes On a console pin whose objectui commit carries `.objectstack-sha`, `scripts/build-console.sh` stands THIS tree in for objectui's pinned objectstack commit (`OBJECTSTACK_ROOT`). So the spec in objectui's install is this tree's own. The script handed exactly that spec to `assert-console-spec-injection.mjs` as `--vendored`. It equalled `--injected`, so the assertion printed "no observable skew", stamped `skew: false` with null witnesses, and exited 0 before it looked at the bundle at all. `check:console-injection --require-stamp` then replayed "Nothing assertable". Both release paths passed any bundle. - `scripts/build-console.sh`: on the `.objectstack-sha` branch, before the stand-in, it now builds objectui's OWN `@objectstack/*` at its `.objectstack-sha`, by objectui's own recipe (`node scripts/objectstack-pin.mjs build` with `OBJECTSTACK_ROOT` and `OS_SKIP_DTS` unset). It keeps a copy of that cache, the pin script's `.stamp.json` included, at `node_modules/.cache/objectstack-pin-reference` in the objectui build tree, outside the cache the stand-in replaces. That copy's `spec` is passed as `--vendored`. A rerun in the same build tree reuses it while its stamp says `source: pin` at this pin. A pin without `.objectstack-sha` is unchanged: `--vendored` is still objectui's install. - `scripts/assert-console-spec-injection.mjs`: when the `--objectui` tree carries `.objectstack-sha`, `--vendored` must sit beside a pin-script stamp with `source: pin` and `sha` equal to that pin. Anything else is refused as cannot-judge (exit 2), never read as "no skew": the stand-in (`source: external`), the install's own copy (no stamp), or a build of another commit. The stamp entry now records `vendoredFrom` and `vendoredPin`. The verdict text names the pinned reference (`pin-only`, not `published-only`). The probe derivation in `console-spec-probes.mjs` is unchanged. - `scripts/check-console-injection.mjs`: the replay names the pinned reference in its no-skew line and in its red verdict. Self-test battery 15 (17 cases, floor 17; roster floor 12 → 13) covers four things. It refuses the three wrong references. It stamps a real witness against the right one. It goes red on a bundle carrying the pinned spec, at build time and on replay. It passes the honest no-skew case and names the reference in it. - `packages/spec/scripts/build-console-objectstack-root.test.ts`: the existing branch test (it pins this exact branch of `build-console.sh`) now expects three things. The native pin build runs before the stand-in, without `OBJECTSTACK_ROOT` and without `OS_SKIP_DTS`. The reference cache and its pin stamp survive the stand-in. A rerun reuses the reference only while its stamp names the pin. The producer of the vacuity was the wiring in `build-console.sh`, so the fix is there. The assertion's new refusal keeps that wiring from coming back silently. `.github/workflows/release.yml` is untouched: its `check:console-injection --require-stamp` call did not need to change, so this PR touches no governed surface. ## The card's "Wanted", item by item 1. **"the assertion takes its witness from the spec at objectui's `.objectstack-sha`"**: done. `--vendored` is objectui's own build of `2c9bf9430a11`, and the assertion refuses any other reference on such a pin. 2. **"so that the stamp carries a non-null witness"**: done. The stamp is shown before and after below. 3. **"A bundle that does not carry this tree's spec fails both release paths again"**: done. The build path exits 1 and the replay exits 1 on a bundle carrying objectui's own pinned spec. The old code exits 0 on both. The matrix is below. ## Measurements All readings are from objectstack `a5a781bc7` (BASE) and this branch, at `.objectui-sha` `4016370af3c9`, where objectui's `.objectstack-sha` is `2c9bf9430a11` (spec 18.0.0-next.3). This tree's spec is 18.0.0-next.5. The objectui source was the sibling checkout, `build-console.sh` mode 2. **Stamp, real build (`bash scripts/build-console.sh` under `os-verify-lock`):** | | skew | freshWitness | staleDetector | vendoredPin | replay `--require-stamp` | |---|---|---|---|---|---| | BASE `a5a781bc7` (VERDICT 0, 862 s) | `false` | `null` | `null` | absent | exit 0, "Nothing assertable in this stamp" | | this branch `91dc1bab6` (VERDICT 0, 672 s) | `true` | "Error code the write was refused or failed with, as the single-record write door answers it" | "Binds the element to one same-origin API endpoint instead of an object" | `2c9bf9430a11…` | exit 0, "the dist carries this tree's copy" | Build-time verdict on this branch: "Console bundle carries THIS tree's @objectstack/spec, and only it (vendored: @objectstack/spec of objectui's own build of objectstack 2c9bf94)". 16 of 16 injected-only descriptions are present and all 2 pin-only ones are absent. This is the same count os-dev-report 6263864885 measured by hand. **Reference build cost**, from the pin script's own step timings on this shared box: fetch 14.8 s, install 5.0 s, build of the 7 packages 144.7 s, pack 16.5 s, so about 181 s. The rerun in the same tree skipped it: the log has 0 "Building objectui's own" lines, against 1 on the first run. **The negative proof (hypothesis 5).** The red bundle is a scratch `build-console.sh` run with both halves deliberately broken: `OBJECTUI_DEPS_BUILD_CMD` puts objectui's own pin build back into its install after the stand-in, and `OBJECTUI_BUILD_CMD` drops `OBJECTSTACK_SPEC_DIST` and `OBJECTSTACK_CLIENT_DIST`. That is what ships if pack and inject both regress. The run ended with build-time exit 1: "✗ Built console still carries the @objectstack/spec of objectui's own build of objectstack 2c9bf94 … (1 of 2 pin-only descriptions) … Binds the element to one same-origin API endpoint instead of an object". Lock VERDICT command-exit 1. The `import/jobs` canary passed on that same bundle. A first sabotage aimed only `OBJECTSTACK_SPEC_DIST` at the pinned spec while the install stayed this tree. It never produced a bundle: rolldown's MISSING_EXPORT error said this tree's `@objectstack/lint` needs `virtualFieldAggregateRemedy`, which the pinned spec lacks. So the coherent leak is the whole pin, and that is the bundle used here. Then every leg was re-run at the final head, against the saved bundles. These are direct node runs, assertion blob `26acfe988c3b`, BASE blob `4fd59042a9eb`: | leg | bundle | `--vendored` | exit | stamp | |---|---|---|---|---| | new assertion | this tree's | pin reference | **0** | skew true, witnesses above | | new assertion | objectui's own pin | pin reference | **1** | none written | | BASE assertion | this tree's | stand-in (BASE's wiring) | 0 | skew false, null, null | | BASE assertion | objectui's own pin | stand-in (BASE's wiring) | **0**, the vacuity | skew false, null, null | | new assertion | objectui's own pin | stand-in | **2**, refused | none written | | replay, new stamp | objectui's own pin | n/a | **1**, "carries the @objectstack/spec of objectui's own build of objectstack 2c9bf94" | | | replay, BASE stamp | objectui's own pin | n/a | **0**, the vacuity | | | replay, new stamp | this tree's | n/a | **0** | | ## PM mechanism hypotheses 1. **Confirmed**, by the BASE build above. `--vendored` resolved to this tree's packed spec, and the stamp and replay matched the card exactly. 2. **Route: objectui's own pin build, kept before the stand-in.** objectui's cache is replaced by the stand-in, and on a CI runner it is empty before the build anyway. objectui's machine store is not read under `OBJECTSTACK_ROOT`, and the pin script at this pin has no machine-store code at all: its 2 "machine" hits are about npm network settings, against 7 hits in objectui#12972's `f9401e6`. `git show` of this repo's history gives source, while the probes read built `.describe()` text. An npm version is exact only when the pin is a version commit, and objectui's `bump-objectstack.sh` pins any main HEAD. The reference stays a built spec, needs no secret, and fetches objectstack from GitHub like the pin script does by default (`OBJECTSTACK_REPO_URL` overrides it). The cache-HIT replay still reads only the stamp, which now carries real probes. On a HIT nothing is built, and the replay's expiry check still reads only this tree's built spec. 3. **Rule: "no skew" is a pass only against a reference that is not this tree.** On a `.objectstack-sha` pin, that reference is objectui's own build of the pinned commit, proven by its pin stamp. When that build declares the same descriptions as this tree, the bundle carries the same text either way, so it stays an honest exit 0. The stamp then records `vendoredPin`, and the replay prints "no observable skew between the injected and objectui's own build of objectstack SHA". The vacuous case can no longer be produced: a reference that is the stand-in or the install is refused with exit 2 before the bundle is read. Battery 15 pins both. 4. **Out of scope, noted.** The `import/jobs` client canary passed on the pin-native bundle too, so it does not tell an injected client from objectui's own. That is measured above and agrees with 6263864885. No client witness was added: per 6263864885, the two trees' client differs only in a type signature and comments, so a witness would have nothing to tell apart today. See the Acceptance notes. 5. **Done**: red 1 and green 0 on the build path, red 1 and green 0 on replay, against BASE's 0 and 0 on the same bundles. See the matrix. ## Route choice on the four axes - **Real business need.** The spec to judge against is the one a broken pack-or-inject would actually ship. Measured above, that is objectui's own build of its pin. An npm version or a source checkout would be a different spec. - **Long-term soundness.** It reuses objectui's own build recipe and its stamp, so no copy of that build logic lives here. It follows the pin automatically as `.objectstack-sha` moves. The cost is about 3 minutes per console build that misses its cache. - **Keeping AI from writing it wrong.** The assertion refuses the vacuous wiring loudly (exit 2, naming the pin). No version-mapping heuristic is involved that could quietly pick the wrong reference. - **No requirement sprawl.** No new gate, no required-check change, no workflow change, no release act. The existing assertion stops being vacuous. ## Tests and gates The gate union was run after the final commit, at `14d4124e8`. `dispatch-gates.mjs --commands` derived 86 families. 84 exited 0 on the first pass. Two answered exit 3, PREREQUISITE NOT MET, which is not a measurement: - `check-plugin-teardown-shape --self-test` could not reach its fixture commit in a shallow clone. After `git fetch --unshallow origin main` it exited 0. - `check:dual-build-cjs-loads` needs every package's dist. After `turbo run build --filter=!@objectstack/docs --concurrency=2` (76 of 76, 75 cached, VERDICT 0) it exited 0. `dispatch-gates.mjs --ran` then reported "86 derived, 86 run, 0 NOT-MEASURED, 0 UNRUN" (exit 0). This PR's check-runs belong to CI and were not re-run here. - `node scripts/check-console-injection.mjs --self-test`: exit 0, "84 assertions over real fixture trees". - Ablation, in a scratch tree, so the worktree was never mutated. Battery 15 with BASE's assertion swapped in, blob verified equal to the BASE blob: exit 1, 10 failures. The stand-in leg reads "expected 2, got 0". Restored, blob equal to HEAD: exit 0. - `pnpm --filter @objectstack/spec exec vitest run --project repo --maxWorkers=2 scripts/build-console-objectstack-root.test.ts`: 6 of 6 pass. The same file at `24e7c5cab`, before its update, failed 2 of 5. Ablation through `scripts/ablation-replace.mjs`: the reference guard was replaced by `if false`, the anchor went 1 → 0 and the blob changed, and 3 of 6 failed. The restore was proven: blob equal to HEAD, `git diff HEAD` empty. Re-run after the restore: 6 of 6. - `pnpm --filter @objectstack/spec typecheck`: VERDICT 0. `tsc -p tsconfig.scripts.json --listFiles` includes the edited test. - `check:console-injection` exit 0 and `check:console-sha` exit 0 on the real dist. ## Acceptance notes - Every console build that misses its dist cache on a `.objectstack-sha` pin now pays the reference build once, about 181 s on this box. Both workflows hash `scripts/build-console.sh` into their dist-cache key, so every entry stamped by the old script stops being restored. The first run of each workflow after this lands rebuilds cold. - The reference build fetches objectstack at the pinned commit from GitHub, the host the build already clones objectui from. It refuses loudly on any pin-script refusal, since `build-console.sh` runs under `set -e`. - Client canary (hypothesis 4): it is non-discriminating on this branch, as measured above. The reference cache now also holds the pin's own `@objectstack/client`, which is where a derived client witness would come from if one is ever wanted. Owner: none. Not filed: this is an observation with nothing to witness today. - The replay's pass line still says "not the published one" on a pinned stamp. That is wording only, and it was left as is. - Changed lines: 227 (204 added, 23 removed). The three planned scripts account for 191. The other 36 are the existing branch test, which this diff could not leave red. - Local scope: the derived gate families and the files above. The spec package's `local` vitest project, whole `test:repo`, `pnpm lint` and the rest of the farm belong to CI. --- _Generated by [Claude Code](https://claude.ai/code/session_01FM29ZqLTrnPNXxijioZwMp)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 04669a4 commit ba4afcc

4 files changed

Lines changed: 204 additions & 23 deletions

File tree

‎packages/spec/scripts/build-console-objectstack-root.test.ts‎

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,12 @@
2222
// exported: the pin script packs whole packages and refuses one whose
2323
// `types` entry is missing, and a typeless tree did exactly that (consumer
2424
// pointer on objectstack#24306, measured again on this branch);
25-
// 5. stop on the pin script's refusal, before the install.
25+
// 5. stop on the pin script's refusal, before the install;
26+
// 6. first build objectui's OWN pin (the pin script with no OBJECTSTACK_ROOT
27+
// and no OS_SKIP_DTS) and keep that cache, its stamp included, outside the
28+
// one the stand-in replaces: once this tree stands in, objectui's install
29+
// is this tree, so only that build can be the injection assertion's
30+
// reference. A rerun reuses it while its stamp names the pin.
2631
//
2732
// ## How it runs: the real script, a fake world
2833
//
@@ -60,10 +65,17 @@ if [[ "\${1:-}" == "install" ]]; then exit 42; fi
6065
exit 0
6166
`;
6267

63-
/** A pin script that HAS the input: it reads OBJECTSTACK_ROOT and records it. */
68+
/** A pin script that HAS the input: it records OBJECTSTACK_ROOT and leaves the
69+
* cache the real one does, its stamp at the root, unless told to refuse. */
6470
const PIN_WITH_HOOK = `import fs from 'node:fs';
65-
fs.appendFileSync(process.env.SHIM_LOG, 'pin ' + process.argv.slice(2).join(' ') + ' | OBJECTSTACK_ROOT=' + (process.env.OBJECTSTACK_ROOT ?? 'unset') + ' | cwd=' + process.cwd() + '\\n');
66-
process.exit(Number(process.env.FAKE_PIN_EXIT ?? '0'));
71+
const root = process.env.OBJECTSTACK_ROOT;
72+
fs.appendFileSync(process.env.SHIM_LOG, 'pin ' + process.argv.slice(2).join(' ') + ' | OBJECTSTACK_ROOT=' + (root ?? 'unset') + ' | OS_SKIP_DTS=' + (process.env.OS_SKIP_DTS ?? 'unset') + ' | cwd=' + process.cwd() + '\\n');
73+
if (process.env.FAKE_PIN_EXIT) process.exit(Number(process.env.FAKE_PIN_EXIT));
74+
const cache = 'node_modules/.cache/objectstack';
75+
fs.rmSync(cache, { recursive: true, force: true });
76+
fs.mkdirSync(cache + '/spec', { recursive: true });
77+
const sha = fs.readFileSync('.objectstack-sha', 'utf8').trim();
78+
fs.writeFileSync(cache + '/.stamp.json', JSON.stringify(root ? { source: 'external', root } : { source: 'pin', sha }));
6779
`;
6880

6981
/** A pin script from before the input existed: it builds its own pin, always. */
@@ -158,6 +170,8 @@ function buildConsole(pin: string, env: Record<string, string> = {}): Run {
158170
}
159171

160172
const closureBuilds = (log: string[]) => log.filter((l) => l.startsWith('pnpm exec turbo run build'));
173+
const pinBuilds = (log: string[]) => log.filter((l) => l.startsWith('pin build'));
174+
const reference = (pin: string) => path.join(FW, '.cache', `objectui-${pin.slice(0, 12)}`, 'node_modules/.cache/objectstack-pin-reference');
161175
const installs = (log: string[]) => log.filter((l) => l.startsWith('pnpm install'));
162176

163177
describe('build-console.sh: the .objectstack-sha branch', () => {
@@ -181,13 +195,25 @@ describe('build-console.sh: the .objectstack-sha branch', () => {
181195
expect(run.status, run.output).toBe(42);
182196
expect(run.log).toEqual([
183197
'pnpm exec turbo run build --filter=@objectstack/client --filter=@objectstack/spec --output-logs=new-only | OS_SKIP_DTS=unset',
184-
`pin build | OBJECTSTACK_ROOT=${FW} | cwd=${run.buildRoot}`,
198+
`pin build | OBJECTSTACK_ROOT=unset | OS_SKIP_DTS=unset | cwd=${run.buildRoot}`,
199+
`pin build | OBJECTSTACK_ROOT=${FW} | OS_SKIP_DTS=unset | cwd=${run.buildRoot}`,
185200
'pnpm install --frozen-lockfile --prefer-offline --config.production=false | OS_SKIP_DTS=unset',
186201
]);
202+
// The pin's own cache survived the stand-in that replaced it: the reference.
203+
expect(JSON.parse(fs.readFileSync(path.join(reference(HOOK_PIN), '.stamp.json'), 'utf8'))).toEqual({ source: 'pin', sha: 'a'.repeat(40) });
204+
expect(fs.existsSync(path.join(reference(HOOK_PIN), 'spec'))).toBe(true);
205+
});
206+
207+
it("a rerun reuses the reference while its stamp names the pin, and rebuilds it when it does not", () => {
208+
expect(pinBuilds(buildConsole(HOOK_PIN).log)).toEqual([expect.stringContaining(`OBJECTSTACK_ROOT=${FW}`)]);
209+
fs.writeFileSync(path.join(reference(HOOK_PIN), '.stamp.json'), JSON.stringify({ source: 'pin', sha: 'b'.repeat(40) }));
210+
expect(pinBuilds(buildConsole(HOOK_PIN).log)).toHaveLength(2);
187211
});
188212

189213
it('an inherited OS_SKIP_DTS does not reach the closure build, and the script says so', () => {
214+
fs.rmSync(reference(HOOK_PIN), { recursive: true, force: true });
190215
const run = buildConsole(HOOK_PIN, { OS_SKIP_DTS: '1' });
216+
expect(pinBuilds(run.log)[0]).toContain('OBJECTSTACK_ROOT=unset | OS_SKIP_DTS=unset');
191217
expect(run.status, run.output).toBe(42);
192218
expect(closureBuilds(run.log)).toEqual([
193219
'pnpm exec turbo run build --filter=@objectstack/client --filter=@objectstack/spec --output-logs=new-only | OS_SKIP_DTS=unset',

‎scripts/assert-console-spec-injection.mjs‎

Lines changed: 64 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
//
1717
// injected = this framework tree's packages/spec (what must be bundled)
1818
// vendored = the @objectstack/spec objectui's own lockfile installed
19-
// (what gets bundled when the injection is missing or broken)
19+
// (what gets bundled when the injection is missing or broken);
20+
// on a pin carrying `.objectstack-sha`, objectui's own build of
21+
// that commit — see readPinnedReference for why only that one
2022
//
2123
// ## The test is two-sided, because one side is not enough
2224
//
@@ -73,8 +75,9 @@
7375
// --assets <built console dist/assets>
7476
//
7577
// Exit: 0 = injection proven (or no skew to prove) · 1 = injection failed
76-
// 2 = inconclusive / cannot run
78+
// 2 = inconclusive / cannot run (including a reference that is this tree)
7779

80+
import fs from 'node:fs';
7881
import path from 'node:path';
7982

8083
import {
@@ -107,6 +110,50 @@ function parseArgs(argv) {
107110

108111
const args = parseArgs(process.argv);
109112

113+
/**
114+
* The objectstack commit --vendored was built at, when objectui pins one; null otherwise.
115+
*
116+
* A pin carrying `.objectstack-sha` installs @objectstack/* by commit, and
117+
* build-console.sh stands THIS tree in for that commit, so the spec objectui's
118+
* install holds is this tree's own, packed. As --vendored it equals --injected:
119+
* both pools are empty, the no-skew branch below passes, and the stamp records no
120+
* witness for ANY bundle. What a broken pack-or-inject ships there is objectui's
121+
* own build of the pinned commit, so that build is the only reference taken on
122+
* such a pin. It is known by the stamp objectui's scripts/objectstack-pin.mjs
123+
* writes at the root of the cache it packed into (`<cache>/.stamp.json` beside
124+
* `<cache>/spec`): `source: 'pin'`, `sha` equal to the pin. A stand-in, a copy
125+
* with no such stamp (the install's node_modules/@objectstack/spec) or a build of
126+
* another commit is refused as cannot-judge, never read as "no skew".
127+
*/
128+
function readPinnedReference(objectuiDir, vendoredDir) {
129+
const pinFile = path.join(objectuiDir, '.objectstack-sha');
130+
if (!fs.existsSync(pinFile)) return null;
131+
const pin = fs.readFileSync(pinFile, 'utf8').trim();
132+
const stampFile = path.join(path.dirname(vendoredDir), '.stamp.json');
133+
let stamp = null;
134+
try {
135+
stamp = JSON.parse(fs.readFileSync(stampFile, 'utf8'));
136+
} catch {
137+
// No readable stamp is refused below, with the rest.
138+
}
139+
if (stamp?.source !== 'pin' || stamp?.sha !== pin) {
140+
const found = stamp ? `source ${JSON.stringify(stamp.source)} at ${String(stamp.sha).slice(0, 12)}` : 'no readable stamp';
141+
fail(
142+
`objectui pins objectstack ${pin.slice(0, 12)} (.objectstack-sha), so --vendored must be objectui's own\n` +
143+
` build of that commit, and \`${stampFile}\` says ${found}. On this pin objectui's install is\n` +
144+
" this tree (the OBJECTSTACK_ROOT stand-in), and judged against itself this check would pass any\n" +
145+
" bundle. build-console.sh keeps that build before standing this tree in; pass its spec.",
146+
);
147+
}
148+
return pin;
149+
}
150+
151+
const vendoredPin = readPinnedReference(path.resolve(args.objectui), path.resolve(args.vendored));
152+
const vendoredName = vendoredPin
153+
? `@objectstack/spec of objectui's own build of objectstack ${vendoredPin.slice(0, 12)}`
154+
: 'PUBLISHED @objectstack/spec';
155+
const vendoredOnly = vendoredPin ? 'pin-only' : 'published-only';
156+
110157
const assetsDir = path.resolve(args.assets);
111158
// The dist root is the assets dir's parent: build-console.sh passes
112159
// `<dist>/assets`, so this is where the .objectui-sha stamp already lives.
@@ -162,6 +209,8 @@ function stamp(skew) {
162209
{
163210
name: '@objectstack/spec',
164211
injectedFrom: path.resolve(args.injected),
212+
vendoredFrom: path.resolve(args.vendored),
213+
vendoredPin,
165214
skew,
166215
freshWitness,
167216
staleDetector,
@@ -175,8 +224,11 @@ function stamp(skew) {
175224
}
176225
}
177226

227+
// With a pinned reference this is the honest case, never the vacuous one: an
228+
// independently built spec of the pinned commit declares the same descriptions as
229+
// this tree's, so a bundle carrying either carries the same text.
178230
if (freshCounts.pool === 0 && staleCounts.pool === 0) {
179-
console.log('✓ Injected and vendored @objectstack/spec declare the same descriptions');
231+
console.log(`✓ Injected and vendored @objectstack/spec declare the same descriptions${vendoredPin ? ` (vendored: ${vendoredName})` : ''}`);
180232
console.log(' — no observable skew, so nothing for this check to assert.');
181233
stamp(false);
182234
process.exit(0);
@@ -193,7 +245,7 @@ if (staleCounts.pool > 0 && staleDetector === null) {
193245
console.error(" it could look for is in the bundle, and objectui's own source at the pin carries");
194246
console.error(' each one too, so none can tell the published spec from objectui\'s own text.');
195247
console.error(' The injection is UNVERIFIED by this check.');
196-
console.error(` published-only descriptions: ${staleCounts.pool}, all of them carried by objectui's source`);
248+
console.error(` ${vendoredOnly} descriptions: ${staleCounts.pool}, all of them carried by objectui's source`);
197249
console.error(` first of them: "${staleHostCarried[0]}"`);
198250
process.exit(2);
199251
}
@@ -204,19 +256,19 @@ if (freshPresent !== true && stalePresent !== true) {
204256
console.error('✗ Neither spec appears in the built console — no @objectstack/spec');
205257
console.error(' content matched. The injection is UNVERIFIED by this check.');
206258
console.error(` injected-only descriptions in the bundle: ${freshCounts.inBundle} of ${freshCounts.pool}`);
207-
console.error(` published-only descriptions in the bundle: ${staleCounts.inBundle} of ${staleCounts.pool}`);
259+
console.error(` ${vendoredOnly} descriptions in the bundle: ${staleCounts.inBundle} of ${staleCounts.pool}`);
208260
for (const line of [...freshNote(' '), ...staleNote(' ')]) console.error(line);
209261
process.exit(2);
210262
}
211263

212264
if (stalePresent === true) {
213-
console.error("✗ Built console still carries the PUBLISHED @objectstack/spec.");
214-
console.error(' The console resolved spec from objectui\'s lockfile, so any authorable');
215-
console.error(' key this framework declared after the last spec publish is unreachable');
265+
console.error(`✗ Built console still carries the ${vendoredName}.`);
266+
console.error(` The console resolved spec from objectui's ${vendoredPin ? 'own pin' : 'lockfile'}, so any authorable`);
267+
console.error(` key this framework declared after ${vendoredPin ? 'that commit' : 'the last spec publish'} is unreachable`);
216268
console.error(' in the Studio designer — the defect objectstack#8134 exists to end.');
217269
console.error('');
218270
console.error(` Text found in the bundle that ONLY the vendored spec has (${staleCounts.inBundle} of`);
219-
console.error(` ${staleCounts.pool} published-only descriptions), the first of them:`);
271+
console.error(` ${staleCounts.pool} ${vendoredOnly} descriptions), the first of them:`);
220272
console.error(` "${staleDetector}"`);
221273
if (staleCounts.hostCarried > 0) {
222274
console.error('');
@@ -241,16 +293,16 @@ if (freshPresent !== true) {
241293
process.exit(2);
242294
}
243295

244-
console.log("✓ Console bundle carries THIS tree's @objectstack/spec, and only it.");
296+
console.log(`✓ Console bundle carries THIS tree's @objectstack/spec, and only it${vendoredPin ? ` (vendored: ${vendoredName})` : ''}.`);
245297
console.log(` present (injected only): "${freshWitness}"`);
246298
console.log(` — ${freshCounts.inBundle} of ${freshCounts.pool} injected-only descriptions are in the bundle`);
247299
for (const line of freshNote(' ')) console.log(line);
248300
if (staleDetector) {
249301
console.log(` absent (vendored only): "${staleDetector}"`);
250302
if (staleCounts.hostCarried === 0) {
251-
console.log(` — and all ${staleCounts.pool} published-only descriptions are absent`);
303+
console.log(` — and all ${staleCounts.pool} ${vendoredOnly} descriptions are absent`);
252304
} else {
253-
console.log(` — and ${staleCounts.pool - staleCounts.hostCarried} of ${staleCounts.pool} published-only descriptions are absent;`);
305+
console.log(` — and ${staleCounts.pool - staleCounts.hostCarried} of ${staleCounts.pool} ${vendoredOnly} descriptions are absent;`);
254306
for (const line of staleNote(' ')) console.log(line);
255307
}
256308
}

‎scripts/build-console.sh‎

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,10 @@ echo "→ Console will bundle @objectstack/spec from ${SPEC_PKG}"
338338
# exactly as before. A pin with it but without the hook refuses, like the two
339339
# vite.config.ts guards above, rather than letting objectui build its own pin.
340340
OBJECTSTACK_PIN_SCRIPT="${BUILD_ROOT}/scripts/objectstack-pin.mjs"
341+
# The spec the injection assertion at the end judges the bundle against: objectui's
342+
# own install, on a pin without `.objectstack-sha` (the block below says what
343+
# replaces it on a pin with one).
344+
VENDORED_SPEC="${BUILD_ROOT}/node_modules/@objectstack/spec"
341345
if [[ -f "${BUILD_ROOT}/.objectstack-sha" ]]; then
342346
if [[ ! -f "$OBJECTSTACK_PIN_SCRIPT" ]] || ! grep -q "OBJECTSTACK_ROOT" "$OBJECTSTACK_PIN_SCRIPT"; then
343347
echo "✗ objectui@${PINNED_SHA:0:12} carries .objectstack-sha but has no OBJECTSTACK_ROOT hook in"
@@ -383,6 +387,29 @@ if [[ -f "${BUILD_ROOT}/.objectstack-sha" ]]; then
383387
(cd "$FRAMEWORK_ROOT" && unset OS_SKIP_DTS && pnpm exec turbo run build "${OBJECTSTACK_TURBO_FILTERS[@]}" --output-logs=new-only)
384388
fi
385389

390+
# The REFERENCE for the injection assertion. Once this tree stands in below,
391+
# objectui's install IS this tree, so its spec equals --injected and the
392+
# assertion would pass any bundle with no witness. What a broken pack-or-inject
393+
# ships here is objectui's own build of the commit its .objectstack-sha names, so
394+
# build that first by objectui's own recipe (the pin script without
395+
# OBJECTSTACK_ROOT, declarations included as it requires) and keep a copy, the
396+
# pin's .stamp.json with it, outside the cache the stand-in replaces. The
397+
# assertion refuses any other reference on such a pin. Reused while its stamp
398+
# names this pin: the build of a fixed commit does not change.
399+
OBJECTSTACK_PIN_SHA="$(tr -d '[:space:]' < "${BUILD_ROOT}/.objectstack-sha")"
400+
PIN_REFERENCE="${BUILD_ROOT}/node_modules/.cache/objectstack-pin-reference"
401+
REFERENCE_SHA="$(node -e '
402+
try { const s = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8")); if (s.source === "pin") process.stdout.write(String(s.sha)); } catch {}
403+
' "${PIN_REFERENCE}/.stamp.json")"
404+
if [[ "$REFERENCE_SHA" != "$OBJECTSTACK_PIN_SHA" ]]; then
405+
echo "→ Building objectui's own @objectstack/* at its .objectstack-sha ${OBJECTSTACK_PIN_SHA:0:12}, the reference the spec assertion judges this bundle against..."
406+
(cd "$BUILD_ROOT" && env -u OBJECTSTACK_ROOT -u OS_SKIP_DTS node scripts/objectstack-pin.mjs build)
407+
rm -rf "$PIN_REFERENCE" "${PIN_REFERENCE}.partial"
408+
cp -R "${BUILD_ROOT}/node_modules/.cache/objectstack" "${PIN_REFERENCE}.partial"
409+
mv "${PIN_REFERENCE}.partial" "$PIN_REFERENCE"
410+
fi
411+
VENDORED_SPEC="${PIN_REFERENCE}/spec"
412+
386413
# The pin script refuses (exit 1, reason on stderr) on a missing entry point,
387414
# and on two dependency mismatches between these packages and objectui's pin:
388415
# a packed manifest whose dependency NAMES differ from what objectui's lockfile
@@ -484,11 +511,11 @@ fi
484511
# --objectui is the checkout just built: objectui's own registry mirrors spec
485512
# descriptions, so text its tracked source carries is not evidence of either spec.
486513
# On a pin that installs @objectstack/* from this tree (the OBJECTSTACK_ROOT block
487-
# above), --vendored IS this tree's spec, packed: the script then reports no
488-
# skew, because objectui's install no longer holds a published spec to leak.
514+
# above), objectui's install is this tree's spec, packed, so --vendored is the
515+
# reference that block kept instead: objectui's own build of its .objectstack-sha.
489516
node "${FRAMEWORK_ROOT}/scripts/assert-console-spec-injection.mjs" \
490517
--injected "$SPEC_PKG" \
491-
--vendored "${BUILD_ROOT}/node_modules/@objectstack/spec" \
518+
--vendored "$VENDORED_SPEC" \
492519
--objectui "$BUILD_ROOT" \
493520
--assets "${TARGET}/assets"
494521

0 commit comments

Comments
 (0)