Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 134 additions & 3 deletions packages/spec/scripts/check-react-blocks-declaration-parity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import {
REPO_MANIFEST_RELATIVE,
manifestPrescription,
repoManifestIsCheckedIn,
repoManifestPath,
} from './manifest-prescription';

const HERE = path.dirname(fileURLToPath(import.meta.url));
const PKG = path.resolve(HERE, '..');
Expand Down Expand Up @@ -296,9 +302,14 @@ describe('check:react-declaration-parity — the gate CAN go red (#4690)', () =>
expect(status, output).toBe(1);
expect(output).toMatch(/did NOT run/);
expect(output).not.toMatch(/skipping/);
// The refusal has to be actionable: the manifest's producer lives in another repo.
expect(output).toContain('pnpm sdui:manifest');
expect(output).toContain('OBJECTUI_ROOT=../objectui');
// The refusal has to be actionable. WHICH prescription is actionable depends on
// the repository the script is standing in, so this only asserts the branch that
// is true HERE — a manifest is checked in at the root — and the prescription's
// own tests below cover both. Asserting the dump path unconditionally is how
// #16715 stayed invisible: the assertion agreed with the prose, and both were
// stale in the same direction.
expect(output).toContain('MANIFEST="$PWD/sdui.manifest.json"');
expect(output).toContain('pnpm --filter @objectstack/spec check:react-declaration-parity');
});

it('a MANIFEST path that does not exist fails loudly, naming the path', { timeout: SPAWN_TIMEOUT_MS }, () => {
Expand Down Expand Up @@ -491,3 +502,123 @@ describe('check:react-declaration-parity — the node contract, and its calibrat
expect(status, output).toBe(0);
});
});

/**
* THE REFUSAL'S PRESCRIPTION IS PROBED, AND BOTH WORLDS ARE PINNED (#16715).
*
* The gate's refusal text used to be a constant asserting "this repository contains
* no copy of it" and sending the reader to build objectui and dump one in a browser.
* That was false from #13446 onward — `sdui.manifest.json` is checked in at the root
* and `lint.yml` runs this gate against it — and it is read at the exact moment
* someone is deciding whether the gate can run at all. Twice measured (#16489 / PR
* #16697, and PR #16777) a dev believed it and filed a locally-runnable gate as
* `EXTERNAL_INPUT_REQUIRED` / NOT MEASURED; one of those declarations reached a
* deliverable, on a head where setting one variable gave exit 0.
*
* ⭐ THE ONE-BRANCH SHAPE IS THE DEFECT, so testing one branch would rebuild it. The
* probe is a real filesystem check against a temp root here — both answers are
* producible — and the wiring (that the script probes at all, and prints the
* paste-ready command in THIS repository) is asserted through a real spawn above and
* below. Neither branch may assert the other's world: that is what each `not` pins.
*/
describe('check:react-declaration-parity — the prescription is probed, not asserted (#16715)', () => {
/** A root that has a manifest, and one that does not — the two worlds, on disk. */
function withRoots(fn: (roots: { withManifest: string; without: string }) => void): void {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'react-parity-prescription-'));
try {
const withManifest = path.join(dir, 'has-one');
const without = path.join(dir, 'has-none');
fs.mkdirSync(withManifest);
fs.mkdirSync(without);
fs.writeFileSync(path.join(withManifest, REPO_MANIFEST_RELATIVE), '{"components":{}}', 'utf8');
fn({ withManifest, without });
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
}

it('the probe answers from the filesystem, both ways', () => {
withRoots(({ withManifest, without }) => {
expect(repoManifestIsCheckedIn(withManifest)).toBe(true);
expect(repoManifestIsCheckedIn(without)).toBe(false);
expect(repoManifestPath(withManifest)).toBe(path.join(withManifest, 'sdui.manifest.json'));
});
});

it('BRANCH: manifest checked in — hands over a paste-ready command, not a production errand', () => {
withRoots(({ withManifest }) => {
const text = manifestPrescription({ repoRoot: withManifest, checkedIn: true });
// The command CI runs, spelled as CI spells it, plus the `cd` that makes `$PWD`
// true — so the block works pasted from any working directory.
expect(text).toContain('MANIFEST="$PWD/sdui.manifest.json"');
expect(text).toContain(`cd ${withManifest}`);
expect(text).toContain('pnpm --filter @objectstack/spec check:react-declaration-parity');
expect(text).toContain('--baseline react-declaration-parity.baseline.json --strict');
expect(text).toContain(path.join(withManifest, 'sdui.manifest.json'));
// ⭐ The negative half: the false sentence, and the errand it justified, are gone.
expect(text).not.toContain('contains no copy of it');
expect(text).not.toContain('pnpm objectui:build');
expect(text).not.toContain('OBJECTUI_ROOT=../objectui');
// ⭐ And it must name the RIGHT regenerator. `pnpm sdui:manifest` is
// scripts/gen-sdui-manifest.sh, whose TARGET is packages/console/dist — it never
// writes the root artefact or scripts/sdui-manifest.record.json.
// `node scripts/gen-sdui-manifest-node.mjs` owns both, which is what the record's
// own `generator` field and check-sdui-manifest.mjs both say. Sending a reader to
// the wrong one is this card's defect class again: a confident false claim about
// the tree, in the text consulted when deciding what to run.
expect(text).toContain('gen-sdui-manifest-node.mjs');
// The wrong tool may appear ONLY inside the correction that names it wrong —
// nowhere else, command line or prose. The LOOKAHEAD is what enforces the whole of
// that sentence, and it was measured before it was written: the original defective
// claim ('`pnpm sdui:manifest` rewrites it when .objectui-sha moves') was itself
// MID-LINE, so a line-anchored form does not match it at all and would have left
// this comment promising more than the code delivers — the same over-claim, one
// layer up, in a card about exactly that. A flat `not.toContain` is not available
// either: it would forbid the correction itself. This assertion belongs to the
// present branch alone — the absent branch legitimately opens command lines with
// that spelling, so it must not be hoisted out of this leg.
expect(text).not.toMatch(/pnpm sdui:manifest(?!` does NOT rewrite)/);
expect(text).toContain('`pnpm sdui:manifest` does NOT rewrite this file');
// …and it says, in words, what the two devs got wrong.
expect(text).toMatch(/NOT MEASURED/);
});
});

it('BRANCH: no manifest — still hands over the whole production path', () => {
withRoots(({ without }) => {
const text = manifestPrescription({ repoRoot: without, checkedIn: false });
expect(text).toContain('pnpm objectui:build');
expect(text).toContain('pnpm sdui:manifest');
expect(text).toContain('OBJECTUI_ROOT=../objectui');
expect(text).toContain('MANIFEST=/path/to/sdui.manifest.json');
// It names the path it actually looked at, so "no copy" is a reading and not a
// claim about repositories in general — the exact over-reach that caused #16715.
expect(text).toContain(path.join(without, 'sdui.manifest.json'));
// ⭐ The negative half: it must not tell a reader a file is there when it is not.
expect(text).not.toContain('IS checked in');
expect(text).not.toContain('MANIFEST="$PWD/sdui.manifest.json"');
});
});

it('the two branches are genuinely different text, not one string with a toggle', () => {
withRoots(({ withManifest }) => {
const present = manifestPrescription({ repoRoot: withManifest, checkedIn: true });
const absent = manifestPrescription({ repoRoot: withManifest, checkedIn: false });
expect(present).not.toEqual(absent);
});
});

/**
* The wiring, end to end: the script must PROBE, and in this repository the probe
* must find the committed manifest. The unit tests above would all pass against a
* script that never called the function.
*/
it('the real gate prints the checked-in branch in THIS repository', { timeout: SPAWN_TIMEOUT_MS }, () => {
const { status, output } = runExit({});
expect(status, output).toBe(1);
expect(output).toContain('IS checked in at this repository\'s root');
expect(output).toContain('MANIFEST="$PWD/sdui.manifest.json"');
expect(output).not.toContain('contains no copy of it');
expect(output).not.toContain('OBJECTUI_ROOT=../objectui');
});
});
71 changes: 36 additions & 35 deletions packages/spec/scripts/check-react-blocks-declaration-parity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,22 @@
// (scripts/gen-sdui-manifest.sh) is the wrapper that builds objectui at
// `.objectui-sha`, dumps the manifest, and then runs THIS gate against it.
//
// This repository carries no manifest to fall back on. Measured, so the next reader
// does not have to re-derive it: `packages/console/dist/` is gitignored (the package
// tracks 4 files, none of them a dist), `scripts/build-console.sh` deliberately does
// not produce one — it must not drag a browser into the console build, and says so —
// and the published `@objectstack/console` tarball has none either (16.1.0: 513
// files, zero `sdui` matches; its `dist/manifest.json` is the PWA manifest), so even
// the CLI's `@objectstack/console/dist/sdui.manifest.json` fallback resolves to
// nothing.
// NOTHING HERE PRODUCES one — but since #13446 one is CHECKED IN. The production
// half is unchanged and still measured: `packages/console/dist/` is gitignored (the
// package tracks 4 files, none of them a dist), `scripts/build-console.sh`
// deliberately does not produce one — it must not drag a browser into the console
// build, and says so — and the published `@objectstack/console` tarball has none
// either (16.1.0: 513 files, zero `sdui` matches; its `dist/manifest.json` is the PWA
// manifest), so even the CLI's `@objectstack/console/dist/sdui.manifest.json`
// fallback resolves to nothing.
//
// What DID change is the fallback: `sdui.manifest.json` is committed at the
// repository root, `scripts/sdui-manifest.record.json` pins it to the `.objectui-sha`
// it was dumped from, and `lint.yml` runs this gate against it on every PR
// (`MANIFEST="$PWD/sdui.manifest.json"`). ⛔ So do not read a non-zero exit here as
// "no input available": the input is in the tree, and the refusal text now probes for
// it rather than asserting its absence (#16715 — `./manifest-prescription.ts`, which
// carries what that stale assertion cost).
//
// Therefore "no manifest" never means "nothing to check" here. It means THIS GATE
// DID NOT RUN — reported as exit 1, not as a `⚠` and exit 0. Until #4690 the two
Expand Down Expand Up @@ -113,7 +121,10 @@
process.env.OS_EAGER_SCHEMAS = '1';

import fs from 'fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { z } from 'zod';
import { manifestPrescription, repoManifestIsCheckedIn } from './manifest-prescription';
import { REACT_BLOCKS } from '../src/ui/react-blocks';
import { ComponentPropsMap } from '../src/ui/component.zod';
import { PageComponentSchema } from '../src/ui/page.zod';
Expand Down Expand Up @@ -294,35 +305,25 @@ function manifestInputs(manifest: any, schemaType: string): string[] | null {
}

/**
* The prescription every "could not run" exit carries.
* The repository root, resolved from THIS FILE's own location.
*
* Not from `process.cwd()` and not from an env var: the prescription's whole job is
* to tell a reader where the manifest is, and a cwd-derived answer would be wrong for
* exactly the reader who is lost. `packages/spec/scripts/` → three levels up.
*/
const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..');

/**
* The prescription every "could not run" exit carries — probed, not asserted.
*
* A refusal is only better than a skip if the reader can act on it. The manifest's
* provenance is two repos away from whoever hits this — it is dumped from objectui's
* registry in a browser — so the exit that replaced the skip has to hand over the
* whole path, not just the missing variable's name.
* Text and probe live in `./manifest-prescription.ts` so both branches (a manifest
* checked in at the root, and none) are unit-testable without a repository that has
* to be in two states at once. #16715 is what a single unprobed branch cost.
*/
const MANIFEST_PRESCRIPTION = [
'',
' The registry side of this comparison is objectui\'s sdui.manifest.json, and this',
' repository contains no copy of it: packages/console/dist/ is gitignored, the console',
' build deliberately does not produce one (it must not pull in a browser), and the',
' published @objectstack/console ships none either. Produce one, then re-run:',
'',
' pnpm objectui:build # build + vendor the console at the pinned .objectui-sha',
' pnpm sdui:manifest # dump the manifest in a browser AND run this ratchet',
'',
' Against a sibling objectui checkout, point the build at it first:',
'',
' OBJECTUI_ROOT=../objectui pnpm objectui:build && pnpm sdui:manifest',
'',
' Or, with a manifest already in hand:',
'',
' MANIFEST=/path/to/sdui.manifest.json \\',
' pnpm --filter @objectstack/spec check:react-declaration-parity \\',
' --baseline react-declaration-parity.baseline.json --strict',
'',
' (the dump needs a browser: pnpm exec playwright install chromium-headless-shell)',
].join('\n');
const MANIFEST_PRESCRIPTION = manifestPrescription({
repoRoot: REPO_ROOT,
checkedIn: repoManifestIsCheckedIn(REPO_ROOT),
});

/**
* Exit loudly because the gate could not run — never because it ran and disagreed.
Expand Down
111 changes: 111 additions & 0 deletions packages/spec/scripts/manifest-prescription.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
//
// The prescription every "could not run" exit of `check:react-declaration-parity`
// carries — and the PROBE that decides which prescription is true here.
//
// A refusal is only better than a skip if the reader can act on it. The manifest's
// provenance is two repos away from whoever hits this — it is dumped from objectui's
// registry in a browser — so the exit that replaced the skip has to hand over the
// whole path, not just the missing variable's name.
//
// WHY THIS IS A FUNCTION OF A PROBE AND NOT A CONSTANT (#16715).
//
// The single constant this file replaced asserted, unconditionally, that "this
// repository contains no copy of it" and sent the reader off to build objectui and
// dump one in a browser. That stopped being true at #13446, when `sdui.manifest.json`
// was checked in at the repository root and `lint.yml` began running this gate
// against it with `MANIFEST="$PWD/sdui.manifest.json"`.
//
// The prose kept its old certainty, and it is read at the exact moment a reader is
// deciding whether the gate can run at all — the shape that gets believed. Measured
// cost, twice: on #16489 / PR #16697 and again on PR #16777 a dev read it, reported
// the gate as `EXTERNAL_INPUT_REQUIRED` / NOT MEASURED, and one of those declarations
// reached a deliverable — on a head where setting that one variable and re-running
// gave exit 0 and "no new DECLARATION divergence vs accepted baseline".
//
// So: neither branch may assert the other's world. When the file is there the reader
// gets a command to paste; only when it is genuinely absent does the objectui dump
// path appear. Both branches are pinned in
// `check-react-blocks-declaration-parity.test.ts` — one branch tested is how this
// defect was built in the first place.

import fs from 'node:fs';
import path from 'node:path';

/** Where the manifest is committed, relative to the repository root. */
export const REPO_MANIFEST_RELATIVE = 'sdui.manifest.json';

/**
* Is a manifest checked in at `repoRoot`?
*
* A filesystem probe and nothing else: no env var, no config key, no build state.
* The question is "can the reader point MANIFEST at a file that exists right now",
* and only the filesystem answers that.
*/
export function repoManifestPath(repoRoot: string): string {
return path.join(repoRoot, REPO_MANIFEST_RELATIVE);
}

export function repoManifestIsCheckedIn(repoRoot: string): boolean {
return fs.existsSync(repoManifestPath(repoRoot));
}

/**
* The prescription text, for whichever world the probe found.
*
* `repoRoot` is printed as a `cd` so the block is paste-ready from any working
* directory while still spelling the variable the way CI does — `$PWD` is only
* correct once you are standing at the root, so the command that sets it says how
* to get there.
*/
export function manifestPrescription(opts: { repoRoot: string; checkedIn: boolean }): string {
const { repoRoot, checkedIn } = opts;

if (checkedIn) {
return [
'',
" The registry side of this comparison is objectui's sdui.manifest.json, and a copy",
` IS checked in at this repository's root (${repoManifestPath(repoRoot)}) — the very`,
' artefact CI feeds this gate. Nothing has to be produced. Run it as CI runs it:',
'',
` cd ${repoRoot}`,
' MANIFEST="$PWD/sdui.manifest.json" \\',
' pnpm --filter @objectstack/spec check:react-declaration-parity \\',
' --baseline react-declaration-parity.baseline.json --strict',
'',
' That is a complete local run: no browser, no objectui checkout, no dump step. So a',
' non-zero exit from THIS gate is a reading, not an unavailable input — do not report',
' it as EXTERNAL_INPUT_REQUIRED or NOT MEASURED (#16715).',
'',
' The committed manifest tracks the objectui pin: when .objectui-sha moves, regenerate it',
' and its record with `node scripts/gen-sdui-manifest-node.mjs` (check-sdui-manifest.mjs',
' prints the exact call). ⛔ `pnpm sdui:manifest` does NOT rewrite this file: it dumps to',
' packages/console/dist/, not to the root.',
].join('\n');
}

return [
'',
" The registry side of this comparison is objectui's sdui.manifest.json, and no copy of",
` it is checked in at ${repoManifestPath(repoRoot)}. Nothing here can produce one:`,
' packages/console/dist/ is gitignored, the console build deliberately does not produce',
' one (it must not pull in a browser), and the published @objectstack/console ships none',
' either. Produce one, then re-run:',
'',
` cd ${repoRoot}`,
' pnpm objectui:build # build + vendor the console at the pinned .objectui-sha',
' pnpm sdui:manifest # dump the manifest in a browser AND run this ratchet',
'',
' Against a sibling objectui checkout, point the build at it first:',
'',
' OBJECTUI_ROOT=../objectui pnpm objectui:build && pnpm sdui:manifest',
'',
' Or, with a manifest already in hand:',
'',
' MANIFEST=/path/to/sdui.manifest.json \\',
' pnpm --filter @objectstack/spec check:react-declaration-parity \\',
' --baseline react-declaration-parity.baseline.json --strict',
'',
' (the dump needs a browser: pnpm exec playwright install chromium-headless-shell)',
].join('\n');
}
Loading
Loading