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
73 changes: 50 additions & 23 deletions docs/releases-maintenance.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,33 +108,55 @@ job) pins this: it asserts the file's **bytes** across a refused run, because th
broken and the fixed script both exit non-zero here and only the file tells them
apart.

#### After the pin moves: run the declaration-parity ratchet (#5960)
#### After the pin moves: regenerate the committed manifest (#5960, #12924)

The bump has a second half, and it is not optional:

```bash
pnpm sdui:manifest # rebuild objectui at the new pin, dump sdui.manifest.json, run the ratchet
node scripts/gen-sdui-manifest-node.mjs \
--objectui-version {the @object-ui version the new pin ships}
```

`sdui.manifest.json` is objectui's registry-inputs dump — the right-hand side of
ADR-0082 D4's spec↔registry **declaration-parity ratchet** (`scripts/gen-sdui-manifest.sh`
produces it and runs the ratchet against it; note it is *not* `scripts/build-console.sh`,
which the ADR named until #5960 corrected it). That file changes when — and only when —
this pin moves, so **the pin bump is the ratchet's trigger, and its only one.**

This is an **on-demand gate by decision** (maintainer ruling, 2026-08-07). No workflow
produces the manifest and none should: the only producer drives a Playwright chromium
over objectui's built console and reads `window.__MANIFEST`, so wiring it into this
repo's CI would put a full objectui build plus a browser download on every matching PR.
The trade is deliberate — honest on-demand coverage over expensive full coverage —
and it is safe because the gate has no unearned green: since #4690 a missing, unreadable,
malformed or empty manifest **exits 1** rather than skipping. The one failure mode left is
that nobody runs it, which is what this step exists to close. `scripts/bump-objectui.sh`
and `scripts/build-console.sh` both print the reminder when they finish.

Needs a Playwright browser (`pnpm exec playwright install chromium-headless-shell`). When
the ratchet fires, the fix is a spec/overlay edit or an explicit `--update` to re-accept
the baseline — see ADR-0082 D4 and its addendum 2.
Read that version from the objectui checkout's `packages/core/package.json` — the bump
already required that checkout. **That command writes the two tracked files, and nothing
else writes them:** the repo-root `sdui.manifest.json` and its provenance record
`scripts/sdui-manifest.record.json`. It installs the published `@object-ui/*` packages
into a temp dir and serializes the registry through `@objectstack/sdui-parser`, so it
needs neither a browser nor an objectui build (it does need
`packages/sdui-parser/dist` — `pnpm --filter @objectstack/sdui-parser build`).

Two required lint steps read that tracked pair on **every PR**, so skipping this step
reds the bump PR rather than leaving it quietly uncovered:

- `node scripts/check-sdui-manifest.mjs` — shape, `sha256` against the record, and the
record's `objectuiSha` against the live `.objectui-sha`. Moving the pin is what makes
the last one fail, and its failure text prints the exact regeneration call.
- ADR-0082 D4's spec↔registry **declaration-parity ratchet**, which `lint.yml` runs
`--strict` with `MANIFEST="$PWD/sdui.manifest.json"`. It is baseline-ratcheted, so only
divergence new since `packages/spec/react-declaration-parity.baseline.json` reds.

**`pnpm sdui:manifest` is a different command and does not do this.** It
(`scripts/gen-sdui-manifest.sh`) builds objectui at the pin, drives a Playwright chromium
over the built console to read `window.__MANIFEST`, writes
`packages/console/dist/sdui.manifest.json` — **gitignored** — and runs the same parity
ratchet against that untracked copy. It is an independent read of the registry straight
from a browser, and `cut-rc.yml` runs it once per release cut; what it never does is
touch the tracked pair, so it cannot clear either gate above. Note the browser dump lives
in `scripts/gen-sdui-manifest.sh`, *not* `scripts/build-console.sh`, which the ADR named
until #5960 corrected it.

Keeping that browser dump **out of per-PR CI** is a maintainer ruling (2026-08-07): it
would put a full objectui build plus a browser download on every matching PR. #12924
changed the ratchet's input, not that ruling — the manifest is checked in now, so the
ratchet runs per-PR off the tracked artefact with no browser anywhere. Neither gate has
an unearned green: since #4690 a missing, unreadable, malformed or empty manifest
**exits 1** rather than skipping.

`scripts/bump-objectui.sh` and `scripts/build-console.sh` both print the reminder when
they finish. When the ratchet fires, the fix is a spec/overlay edit or an explicit
`--update` to re-accept the baseline — see ADR-0082 D4 and its addendum 2. The browser
dump additionally needs a Playwright browser
(`pnpm exec playwright install chromium-headless-shell`).

#### If the dispatch container's Playwright browser doesn't match the revision

Expand Down Expand Up @@ -253,8 +275,13 @@ So the process is:
`@objectstack/console` changeset for `OLD_PIN..NEW_PIN` from objectui's own
declared changesets (`scripts/objectui-changeset-digest.mjs`). This is the
mechanism that keeps the release record honest, and it is the whole of it.
4. **Then the declaration-parity ratchet** — see "After the pin moves" above; the
bump is that ratchet's only trigger.
4. **Then the committed manifest is regenerated in that same PR.**
`node scripts/gen-sdui-manifest-node.mjs --objectui-version {the @object-ui version
the new pin ships}` rewrites the tracked `sdui.manifest.json` and
`scripts/sdui-manifest.record.json`; `scripts/check-sdui-manifest.mjs` reds the bump
PR until it does, and ADR-0082 D4's declaration-parity ratchet reads that same tracked
artefact. `pnpm sdui:manifest` writes neither file — it dumps to the gitignored
`packages/console/dist/`. See "After the pin moves" above.
5. **Releases build against the pin as committed.** Both `cut-rc.yml` and
`release.yml` read `.objectui-sha` and build the Console SPA at it. Neither
resolves objectui `main`; neither moves the pin.
Expand Down
29 changes: 19 additions & 10 deletions scripts/build-console.sh
Original file line number Diff line number Diff line change
Expand Up @@ -274,17 +274,26 @@ node "${FRAMEWORK_ROOT}/scripts/assert-console-spec-injection.mjs" \
BYTES="$(du -sk "$TARGET" 2>/dev/null | awk '{print $1}')"
echo "✓ @objectstack/console dist ready (${BYTES} KB) from objectui@${PINNED_SHA:0:12}"

# ADR-0080/0081: the public-tier SDUI manifest and the spec↔registry react-block
# declaration-parity ratchet are intentionally NOT generated here — they require a
# real browser (Playwright) to enumerate the console registry, and the console
# build must not drag in a browser dependency. Regenerate them on demand instead:
# pnpm sdui:manifest (see scripts/gen-sdui-manifest.sh)
# ADR-0080/0081: neither SDUI manifest is generated here, and the spec↔registry
# react-block declaration-parity ratchet is not run here either. Two different files
# wear that name, and the difference is what the reminder below exists to carry:
#
# sdui.manifest.json (repo root) + scripts/sdui-manifest.record.json — TRACKED,
# written only by `node scripts/gen-sdui-manifest-node.mjs`, which installs the
# published @object-ui packages (no browser). `scripts/check-sdui-manifest.mjs`
# in the required lint job reds once the pin moves and they have not followed,
# and ADR-0082 D4's ratchet reads the tracked manifest on every PR (#12924).
# packages/console/dist/sdui.manifest.json — GITIGNORED, written by
# `pnpm sdui:manifest` (scripts/gen-sdui-manifest.sh), which needs a real browser
# to enumerate the console registry. Nothing gates it; the console build must not
# drag in a browser dependency, so it stays on demand (#5960).
#
# The reminder names the TRIGGER, not just the command (#5960): `pnpm objectui:refresh`
# runs bump-objectui.sh and then this script, so this is the last output an operator
# sees while moving the pin — and the pin bump is the ratchet's only trigger, by
# decision. bump-objectui.sh prints the same step; this repeats it because that one
# has scrolled past a whole console build by now.
# sees while moving the pin. bump-objectui.sh prints the same step; this repeats it
# because that one has scrolled past a whole console build by now.
echo "ℹ SDUI manifest + declaration-parity ratchet are decoupled from the console build."
echo " Run 'pnpm sdui:manifest' whenever you move the objectui pin — that is the"
echo " ratchet's only trigger, on demand by decision (#5960). Requires Playwright."
echo " Moved the objectui pin? Regenerate the TRACKED manifest — the required lint gate:"
echo " node scripts/gen-sdui-manifest-node.mjs --objectui-version {the version the new pin ships}"
echo " It writes sdui.manifest.json + scripts/sdui-manifest.record.json. 'pnpm sdui:manifest'"
echo " writes neither: it is the browser dump to the gitignored packages/console/dist/."
90 changes: 57 additions & 33 deletions scripts/bump-objectui.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,24 @@
# scripts/bump-objectui.sh --no-commit # update files only, don't commit
# scripts/bump-objectui.sh --no-changeset # skip the @objectstack/console changeset
#
# After the bump — the second half of the pin-update procedure (#5960):
# pnpm sdui:manifest # dump objectui's sdui.manifest.json and run the
# # spec↔registry declaration-parity ratchet (ADR-0082 D4).
# # The pin bump is that ratchet's ONLY trigger; it is an
# # on-demand gate by decision, never a CI job. Needs
# # Playwright chromium. This script prints the reminder.
# After the bump — the second half of the pin-update procedure (#5960, #12924):
# node scripts/gen-sdui-manifest-node.mjs \
# --objectui-version {the @object-ui version the new pin ships}
#
# That call rewrites the two TRACKED files, and nothing else writes them: the
# repo-root `sdui.manifest.json` and its provenance record
# `scripts/sdui-manifest.record.json`. The required lint job reds the bump PR until
# it has run — `scripts/check-sdui-manifest.mjs` asserts the record's pin equals
# `.objectui-sha` — and ADR-0082 D4's spec↔registry declaration-parity ratchet reads
# that same tracked artefact on every PR. No browser and no objectui build; it
# installs the published @object-ui packages into a temp dir. This script prints the
# reminder on its way out.
#
# ⛔ `pnpm sdui:manifest` is a DIFFERENT command and does not clear that gate: it
# dumps objectui's registry from a real browser to the gitignored
# `packages/console/dist/sdui.manifest.json` and ratchets against that untracked
# copy. An independent read of the registry, worth running when you hold an objectui
# checkout and a Playwright chromium — but it writes neither tracked file.
#
# The pin must name a commit that is on objectui MAIN. This script WARNS — it
# does not refuse — when the revision being pinned is not reachable from the
Expand Down Expand Up @@ -686,40 +698,52 @@ if [[ -n "$CS_FILE" ]]; then
echo "→ wrote changeset $(basename "$CS_FILE") (@objectstack/console: ${BUMP})"
fi

# --- The other half of the pin-update procedure (#5960) ----------------------
# ADR-0082 D4's spec↔registry declaration-parity ratchet reads objectui's
# `sdui.manifest.json`, and that file changes when — and only when — this pin
# moves. So the pin bump is the ratchet's trigger, and the ratchet has no
# AUTOMATIC one at all: `packages/console/dist/` is gitignored and the published
# @objectstack/console tarball ships no manifest, so the file exists only where
# somebody dumped it. Producing it in this repo's CI was considered and REJECTED
# (#5960) — it would put a full objectui build plus a chromium download on every
# matching PR.
# --- The other half of the pin-update procedure (#5960, #12924) --------------
# The pin bump is what makes the committed manifest stale, and regenerating it is
# what this prints. Which command writes what is the whole of it:
#
# node scripts/gen-sdui-manifest-node.mjs -> sdui.manifest.json (repo root) and
# scripts/sdui-manifest.record.json
# — both TRACKED, and gated per PR
# pnpm sdui:manifest (gen-sdui-manifest.sh) -> packages/console/dist/sdui.manifest.json
# — GITIGNORED, and gated nowhere
#
# So only the first one can turn the bump PR green. `scripts/check-sdui-manifest.mjs`
# in the required lint job reds while the record's pin trails `.objectui-sha`, and
# ADR-0082 D4's declaration-parity ratchet runs `--strict` against the tracked
# manifest on every PR (#12924 checked it in; before that the ratchet had no
# automatic run at all, which is the world the reminder here used to describe).
#
# Producing the BROWSER dump in this repo's per-PR CI was considered and REJECTED
# (#5960) — a full objectui build plus a chromium download on every matching PR —
# and that ruling stands; what changed is the ratchet's input, not the ruling.
#
# ⚠️ Corrected 2026-08-30 (#13091): this comment used to read "measured on
# origin/main, no workflow runs `pnpm sdui:manifest`, no workflow installs
# Playwright for it". Both halves stopped being true on 2026-08-10, when
# `.github/workflows/cut-rc.yml` landed doing both, one step apart, as the last
# check before publish. The trigger claim above is unaffected — cut-rc is a
# `workflow_dispatch`-only lane a human starts by typing the version — but "no
# workflow" was an absolute about the whole workflow set, and that is the shape
# that rotted. Read a workflow's `on:` block rather than a file count. ADR-0082
# addendum 2 carries the full correction.
#
# Deliberately a REMINDER, not a hard gate: a machine without Playwright must
# still be able to move the pin, and hard-failing here would be the rejected
# CI cost wearing a local disguise. The gate itself cannot go falsely green —
# since #4690 a missing or unusable manifest exits 1 instead of skipping — so
# the only failure mode left is "nobody ran it", which is what this prints to
# prevent. Printed on BOTH exits below: --no-commit still moved the pin.
# check before publish — a `workflow_dispatch`-only lane a human starts by typing
# the version. "No workflow" was an absolute about the whole workflow set, and that
# is the shape that rotted. Read a workflow's `on:` block rather than a file count.
# ADR-0082 addendum 2 carries the full correction.
#
# Deliberately a REMINDER, not a hard gate: a machine that cannot reach npm must
# still be able to move the pin, and hard-failing here would be CI cost wearing a
# local disguise. Nothing is lost by that, because the gate cannot go falsely green
# — since #4690 a missing or unusable manifest exits 1 instead of skipping, and
# since #12924 the bump PR itself reds until the regeneration lands. Printed on
# BOTH exits below: --no-commit still moved the pin.
print_sdui_next_step() {
echo
echo "→ NEXT STEP — run the declaration-parity ratchet (ADR-0082 D4):"
echo " pnpm sdui:manifest"
echo " It rebuilds objectui at the new pin, dumps packages/console/dist/sdui.manifest.json"
echo " and ratchets spec↔registry declaration parity. A pin bump is its only trigger:"
echo " it is an on-demand gate by decision (#5960), never a CI job."
echo " Needs a Playwright browser — 'pnpm exec playwright install chromium-headless-shell'."
echo "→ NEXT STEP — regenerate the committed SDUI manifest (required lint gate):"
echo " node scripts/gen-sdui-manifest-node.mjs \\"
echo " --objectui-version {the @object-ui version the new pin ships}"
echo " Read that version from the objectui checkout's packages/core/package.json."
echo " It rewrites the tracked sdui.manifest.json and scripts/sdui-manifest.record.json;"
echo " scripts/check-sdui-manifest.mjs reds this bump PR until it does, and ADR-0082 D4's"
echo " declaration-parity ratchet reads the same tracked artefact. No browser needed."
echo " ⛔ 'pnpm sdui:manifest' does NOT write those files — it is the separate browser"
echo " dump to the gitignored packages/console/dist/ (needs Playwright chromium)."
echo " Procedure: docs/releases-maintenance.md → 'After the pin moves'."
}

Expand Down
Loading