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
45 changes: 44 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,21 @@ jobs:
- '.github/workflows/**'
- '.github/prompts/**'
- 'skills/objectui/**'
# turbo.json's `globalDependencies`: every build task hashes
# them, so they move what the ui jobs build, the SDUI manifest's
# regeneration included (#26137).
- 'tsconfig.json'
- 'tsconfig.base.json'
- 'tsconfig.test.base.json'
- 'tsup.config.ts'
# The committed SDUI manifest, its record, its producer and its
# offline gate. Build & E2E UI regenerates the manifest and
# compares it with the committed bytes (#26137), so a diff that
# moves only one of these still runs that comparison.
- 'sdui.manifest.json'
- 'scripts/sdui-manifest.record.json'
- 'scripts/gen-sdui-manifest-node.mjs'
- 'scripts/check-sdui-manifest.mjs'
# The seven packages objectui installs from this tree (the set its
# `.objectstack-pin` used to pack). A change here compiles and builds
# the Console in the same PR — the check the retired Spec Shape Gate
Expand Down Expand Up @@ -3734,6 +3749,10 @@ jobs:
# - Build & E2E UI: n=51, min 106 s, median 170 s, p95 423 s, max 457 s
# (7.6 min) => max(22.9, 22.6) => 25, down from objectui's 30 plus the
# 15 the port added for the in-job build, which no derivation backed.
# Re-derived for the SDUI manifest regeneration (objectstack#26137),
# which adds 213 s cold (the 27 @object-ui builds plus the enumeration,
# measured on main 81bba6c3f): 457 + 213 = 670 s (11.2 min)
# => max(33.5, 26.2) => 35.
# Re-derive when a job's work changes; ui/scripts/__tests__/
# turbo-cache-save-bound.test.ts holds both values.
lint-ui:
Expand Down Expand Up @@ -4063,7 +4082,7 @@ jobs:
if: ${{ !cancelled() && needs.filter.outputs.ui_tree != 'false' && (needs.filter.outputs.ui != 'false' || needs.filter.outputs.ui_consumers != 'false') }}
runs-on: ubuntu-latest
# Derived above Lint UI.
timeout-minutes: 25
timeout-minutes: 35
permissions:
contents: read
env:
Expand Down Expand Up @@ -4122,6 +4141,30 @@ jobs:
--filter=@objectstack/sdui-parser... \
--filter=@objectstack/types...

# The repo-root SDUI manifest, regenerated from this tree and compared
# with the committed bytes (#26137). The generator builds the registry
# with turbo: the 36 tasks of the step above replay, and the 27
# @object-ui packages build (3m25s cold, measured on 4 cores, on main
# 81bba6c3f), then it enumerates the registry under plain Node (~8 s; ~6 s
# for the whole run when nothing moved). It serialises through the
# @objectstack/sdui-parser the step above built, so a parser edit moves
# this verdict too, which no committed digest does. On merge_group it runs
# on the queue's combined tree, the one run that sees two entries' effect
# on the manifest together. Only the artefact is compared: the records the
# generator rewrites beside it are held by `check-sdui-manifest` in
# lint.yml. The Console build below reads the registry through its src/
# aliases, so the dist this step builds does not reach it.
- name: The committed SDUI manifest is the one this tree generates
run: |
node scripts/gen-sdui-manifest-node.mjs
if ! git diff --quiet -- sdui.manifest.json; then
echo "::error file=sdui.manifest.json::sdui.manifest.json is not the manifest this tree generates. Run 'node scripts/gen-sdui-manifest-node.mjs' and commit what it writes."
git diff --stat -- sdui.manifest.json
git diff -- sdui.manifest.json | head -n 200
exit 1
fi
echo "sdui.manifest.json is byte-identical to this tree's regeneration."

- name: Build the Console for E2E
env:
VITE_BASE_PATH: /console/
Expand Down
16 changes: 7 additions & 9 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -966,15 +966,13 @@ jobs:
# repo-root `sdui.manifest.json` is absent or unreadable, so nothing in
# the production path ever says "the gate lost its whitelist" — this step
# is the thing that notices. Offline record checks only (existence, shape,
# sha256 vs the provenance record, record pin vs `.objectui-sha`): the
# regenerate-and-compare half runs where a regeneration can (the Node
# generator reads objectui's BUILT tree at the pin, which only
# `pnpm objectui:build` materialises — see
# scripts/gen-sdui-manifest-node.mjs), and a pin bump goes red HERE until
# it has been re-run — the same moment the lockstep step above forces the
# parser parity re-verification, with an objectui checkout guaranteed in
# hand. Unfiltered for the same reason as its neighbour: filtering to the
# artefact would go dormant on exactly the PR that moves the pin.
# sha256 vs the provenance record, and with no pin each registry
# package's source digest vs its record): the regenerate-and-compare half
# runs where the registry is built, as ci.yml's Build & E2E UI step "The
# committed SDUI manifest is the one this tree generates" (#26137), on
# pull_request and on the merge queue's combined tree. Unfiltered:
# filtering it to the artefact would go dormant on exactly the PR that
# moves the artefact's inputs and not the artefact.
# Direct invocation per the GATE INVOCATION IDIOM at the top of this file.
- name: SDUI manifest is present, intact and fresh at the objectui pin
id: sdui-manifest-is-present-intact-and-fresh
Expand Down
10 changes: 10 additions & 0 deletions docs/releases-maintenance.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,16 @@ the same package, or a PR that changes the artefact, still conflict once, and th
later one merges `main` and re-runs the command. ⛔ There is no npm install route to the manifest; that was
deleted by ruling 丙 on #17735.

CI also regenerates the manifest and compares it with the committed bytes (#26137):
the `Build & E2E UI` step "The committed SDUI manifest is the one this tree
generates" runs the same command after the registry is built, on every PR that
touches `ui/`, one of the seven packages objectui consumes (the parser among them)
or the manifest's own files, and again on the merge queue's combined tree. A
difference fails the step with the diff and the command to run. That covers what
the source digests cannot see: an edit to `@objectstack/sdui-parser`, to a package
the registry imports, and two queued PRs whose effect on the manifest only shows
together.

**One producer, one artefact.** `packages/console/build-dist.sh` copies the tracked
`sdui.manifest.json` into `packages/console/dist/`, so the published console carries the
same bytes CI gates.
Expand Down
29 changes: 17 additions & 12 deletions scripts/check-sdui-manifest.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,11 @@
* line, and so does a PR that moves the artefact (it re-records every
* package against the new sha256): those still conflict, on purpose,
* because only a regeneration on the merged tree can say what the
* manifest of the combination is. What is NOT forced is the case where
* two PRs on different packages each leave the artefact unchanged and
* their combination would not; the next regeneration of any registry
* package sees the combination.
* manifest of the combination is. What is NOT forced HERE is the case
* where two PRs on different packages each leave the artefact unchanged
* and their combination would not. The regeneration in ci.yml (below)
* runs on the merge queue's combined tree, and that is where it is
* caught (#26137).
* 4. none. The in-tree `@object-ui/core` version moves with every release
* of the one fixed version group while the registry does not, so the
* record carries it as provenance and the success line prints it.
Expand All @@ -98,14 +99,18 @@
*
* ## What this deliberately does NOT do, and where that risk is held
*
* No per-PR regeneration: that would put an objectui clone and build (network,
* minutes) inside a required lint job, a network dependency this repo
* declines in a lint job. Under an unchanged pin the pinned
* commit's registry is immutable, so content drift per-PR is not a live axis.
* The residual axis — this repo's OWN adapter (`manifestFromConfigs`) changing
* while the pin stands still — is held by `packages/sdui-parser`'s unit suite, and by regeneration being byte-deterministic (two runs
* over one built tree, identical bytes), so the remedy this gate prints always
* converges.
* No regeneration in this gate: it runs in a required lint job that builds
* nothing, and the registry takes minutes to build. The regeneration runs where
* the registry is built: ci.yml's Build & E2E UI step "The committed SDUI
* manifest is the one this tree generates" (#26137) runs the generator and
* compares the committed artefact with its output byte for byte, on
* pull_request and on the merge queue's combined tree. It serialises through
* the built `@objectstack/sdui-parser`, so an edit to this repo's OWN adapter
* (`manifestFromConfigs`) moves its verdict, which no digest here does.
* Regeneration is byte-deterministic (two runs over one built tree, identical
* bytes), so that comparison and the remedy this gate prints both converge.
* With a pin, the pinned commit's registry is immutable, so content drift
* per-PR is not a live axis there.
*
* Check 4 relates two fields of the record; it does not re-run the producer.
* That the artefact describes the pinned SOURCE (rather than a tarball objectui
Expand Down
2 changes: 1 addition & 1 deletion ui/scripts/__tests__/turbo-cache-save-bound.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ const CEILINGS: Record<string, number> = {
[DEPS_JOB]: 25,
[UI_JOBS.typeCheck]: 40,
[UI_JOBS.lint]: 40,
[UI_JOBS.buildE2e]: 25,
[UI_JOBS.buildE2e]: 35,
};

function job(key: string): Job {
Expand Down
Loading