Repository navigation
Commit e2aaaf1
fix(sdui): the lockstep and manifest gates judge the in-tree ui/ copy when .objectui-sha is absent (#25833)
<!-- claude-projects-attribution: {"github_login":"os-tesla"} -->
_Requested by **Tesla** · [project
thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6JaoAqVfFyhsxKuFcEP9YTBj25)_
Part of #25712 (4b PR 2a: the two SDUI gates keep the required lane
green when the pin goes).
Part of objectstack#25677.
Clause-②: no
Metadata-protocol: none
## Why
The sitting merges the cutover PR #25825 (adds `ui/`), and then 4a
#25745 (deletes `.objectui-sha`). Two steps of `lint.yml` job
`lint-repo-tooling` (the required **Lint & Repo Gates** lane) read that
pin, and without it both exit 1. Measured on the sitting tree (cutover
`c326d2fdf1` + 4a `50bd417d30` with its README apply steps 2–3 + `main`
`b00fc55c7d`) without this PR:
- `pnpm check:sdui-lockstep`: `REFUSED — .objectui-sha could not be
read`, exit 1.
- `node scripts/check-sdui-manifest.mjs`: `✗ .objectui-sha is MISSING —
cannot judge freshness.`, exit 1.
This PR gives each gate, and the manifest generator, a second oracle:
the in-tree copy under `ui/`. It is green on `main` as it is, so it can
merge right after the cutover and before 4a, and `main` is never red. It
deletes nothing: `packages/sdui-parser/objectui-lockstep.json`, the pin
and every check stay. Unifying the two parsers is a later PR.
## What changes
The mode is decided by `.objectui-sha` alone.
**With the pin (today's `main`): zero behaviour change.** The same
record, checks, messages and output. Proven by golden harnesses,
captured before and after and compared byte for byte:
- lockstep: every `judge()` outcome over the existing fixtures;
- manifest: `checkTree` over 14 fixtures × 2 option sets;
- the real `main` output of both gates.
The existing self-test rows are unchanged and pass.
**With no pin:**
- `scripts/check-sdui-lockstep.mjs` compares `packages/sdui-parser/src`
with `ui/packages/sdui-parser/src` directly. It uses the same three
readings the record holds today: the grammar region of `parse.ts`, the
diagnostic code set, and the `not-a-container` containment predicate.
The record is not consulted.
- `scripts/check-sdui-manifest.mjs`:
- Checks 1–2 (shape, tamper) are unchanged, held against the in-tree
record fields.
- Check 3 becomes a **registry source digest**. It is a sha256 over the
`src/` files (tests excluded) of the `@object-ui/*` workspace closure of
`@object-ui/core` and the generator's 16 registry modules. It is read
from the git index (`git ls-files -s`: sorted paths, committed blob ids,
no generated or untracked files), so the lint lane builds nothing.
- A mismatch prints the one command that clears it: `node
scripts/gen-sdui-manifest-node.mjs`.
- There is no check 4. The in-tree `@object-ui/core` version moves with
every release of the fixed group while the registry does not, so it is
recorded and printed as provenance only.
- `scripts/gen-sdui-manifest-node.mjs`, with no pin:
- It builds the registry closure itself with turbo (a cache hit when
nothing moved), then enumerates from `ui/apps/console` exactly as
before.
- It refuses registry sources that the index does not hold.
- It writes the record's `inTree` block and an in-tree `//` note.
- **Pin absent and `ui/` absent:** both gates exit 1, and the message
names both `.objectui-sha` and the in-tree path.
## Record fields added, and what stays untouched
The new block is `inTree: { modulesRoot, objectuiWorkspaceVersion,
registryPackages, registrySources, registrySourcesSha256 }`. It is
written only by an in-tree run.
The pin-mode fields (`objectuiSha`, `source`, `modulesRoot`,
`objectuiWorkspaceVersion`, `generator`, `generatedAt`, `sha256`,
`components`) are untouched. An in-tree run carries the pin-keyed ones
only while the artefact stays byte-identical to the one they describe.
**This PR commits no regenerated artefact.** `sdui.manifest.json` and
`scripts/sdui-manifest.record.json` are unchanged, per the dispatcher's
ruling below.
## The committed manifest describes the pin, not the frozen registry
(measured)
On the sitting tree, the in-tree generator exits 0 and writes **106**
components (`1f6e1ccb925a…`). The committed artefact has **107**
(`d1fde278ffd6…`). The registry really moved between pin `b41ed7f689`
and freeze `9e1fe03286`:
- objectui 610d9ef (#13475, objectui#13449) retired the raw-HTML `html`
node type.
- objectui 46c56ee (#13483, objectui#13386) reworded plugin-detail's
`sections` input description.
One committed artefact cannot be true for both the pin and the in-tree
registry. So the regeneration rides with the pin deletion, in 4a: a
ready patch and its measurements are with the dispatcher. Until 4a
applies it, 4a's lint lane reds with exactly this:
```
✗ check-sdui-manifest:
record is missing `inTree`.
The record is keyed to the one producer, which reads the in-tree console build (ui/apps/console). Regenerate:
node scripts/gen-sdui-manifest-node.mjs
```
The registry source digest is identical on cutover `c326d2fdf1` alone
and on the sitting tree: 27 packages, 922 files,
`35ef1abd88b0011a06bfc895b3091d1f8ecf319c3f78d2bfb0d9feb4e53f3bbe`. So
the regenerated record holds through both states. Two generator runs on
the sitting tree were byte-identical, artefact and record.
## Known cost
After 4a, a UI PR that touches a registry source reds **Lint & Repo
Gates** until its author runs `node scripts/gen-sdui-manifest-node.mjs`
and commits the two files. Measured: **96 of objectui's last 200
non-merge commits** (2026-10-20..25) touched the digested set, so about
half of UI PRs would pay that one command.
The follow-up that removes the tax is a regenerate-and-compare step in
**Build & E2E UI** (`ci.yml`), which has the console build in hand. It
is the next 4b dispatch. That compare must ignore the record's
`generatedAt` date (the artefact itself is byte-deterministic).
## Verification
| Gate | `main` (this branch) | sitting tree + this PR |
|---|---|---|
| `pnpm check:sdui-lockstep` (self-test, then the gate) | 0 (pin mode) |
0 (in-tree: 214 grammar lines, blob `0131f27cf86d`, 25 codes, predicate
`76c18fb95d1f`) |
| `node scripts/check-sdui-manifest.mjs --self-test` | 0 (22 cases) | 0
(22 cases) |
| `node scripts/check-sdui-manifest.mjs` | 0 (pin mode, 107 components)
| 1 before 4a's regeneration, as above; 0 after it |
- New self-test rows:
- lockstep battery `The in-tree oracle (no pin: the merged layout)`, 18
cases;
- manifest, 9 in-tree rows, including an in-tree producer run through a
stub turbo.
- Ablations: 8 of 9 targeted ablations red the rows. The ninth drops
`inTree` from the required list; the row stays red because the
field-level check catches the same thing.
- Derived gates (`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`): 35 families, all exit 0 at head
`a6ee32ec`; `--ran` reconciles 35 run, 0 NOT-MEASURED, 0 UNRUN. ESLint
on the three changed files: 0.
- Git children over self-test fixtures run with `gitFreeEnv()` (#16644).
## Deviations from the order
1. **No regenerated artefact in this PR** (dispatcher ruling, after the
106/107 measurement above). The order asked that the generator reproduce
the committed `sdui.manifest.json` byte for byte on the sitting tree; it
cannot, so the regeneration moves to 4a.
2. **No in-tree version judgment** (accepted):
`objectuiWorkspaceVersion` is provenance, printed beside the live
`ui/packages/core` version.
3. **The generator builds the registry itself in-tree** (accepted), so
regeneration is one command with no prior step.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01A6SEitNngAMugGbuJHWsDS
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent b00fc55 commit e2aaaf1
3 files changed
Lines changed: 993 additions & 136 deletions
0 commit comments