Repository navigation
ci: hourly Spec Shape Gate on main -- objectui main compiled against the spec built from the commit, read by the release gate, red attributed to the changing lane - #23283
Conversation
…the spec built from the commit, read by the release gate, red attributed to the changing lane Ruling B (comment 6145029395): the spec shape gate moves from objectui to objectstack. A new scheduled workflow clones objectui at main, injects the spec built and packed from this commit into its pnpm virtual store, runs objectui's type check, and reports which objectui file fails against which objectstack commit. An intentional retirement registered in scripts/ci/spec-shape-retirements.json reads as known instead of red; a new break is attributed to the objectstack commits since the last green run and the domain lane of the card each pull fixes. scheduled-full-run.mjs registers the workflow in RELEASE_GATE_WORKFLOWS so the publish lane reads it. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013zGKMG1cRedVzWtqGGGZW9
…t (PR 23160 -> objectui 12325) -- the positive control's second reading Run 1 of the gate on this PR (empty registry) went red on exactly that retirement, in six objectui files. This commit registers it: five patterns, each a file or glob plus the TS code and the message fragment naming the retired arm, so the same diagnostics read as known and a new break in the same files stays red. Also rewords one message the cli-command-ids gate read as a quoted command phrase. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013zGKMG1cRedVzWtqGGGZW9
Contract reviewServed-tier: Reviewed in seat by the dispatching skills seat (served tier read from ① Derived judgments
② Semver levelNone owed: the diff ships in no package ( ③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
维护者速读(终稿)· PR #23283 · head
|
… objectstack (objectui#12352) (objectstack-ai#12596) Fixes objectstack-ai#12352 Clause-②: no Executes ruling B on objectstack-ai/objectstack#23238 (comment 6145029395, batch 336 item 3, maintainer 「其他同意」 2026-10-13), the objectui half: "the spec shape gate moves to objectstack … objectui drops its copy" once the objectstack side agrees for two consecutive hourly runs. The objectstack half landed as objectstack-ai/objectstack#23283 (`18cd410dc4`); the card shows four agreeing pairs. ## The successor - **The gate:** objectstack's `.github/workflows/spec-shape-gate.yml` (`Spec Shape Gate`), hourly on objectstack `main` at `:52`. It clones objectui `main`, injects the spec built from the objectstack commit with objectstack's own `scripts/ci/spec-shape-gate.mjs inject`, and runs this repository's `TURBO_FORCE=true pnpm type-check --continue`. It is registered in objectstack's `RELEASE_GATE_WORKFLOWS` (`scripts/ci/scheduled-full-run.mjs`), so its scheduled run is read by the release gate, and a red is attributed to the objectstack lane that moved the shape. - **Intentional retirements** are registered from now on in objectstack's `scripts/ci/spec-shape-retirements.json`, one entry per retirement naming the objectui card that removes the downstream use; the gate reads a matching diagnostic as known, not new. - The successor calls none of the files deleted here: its `inject` and `report` are objectstack's own port (read on objectstack `origin/main` at `6362c66ca`). ## What changed | Path | Change | Why | |:--|:--|:--| | `.github/workflows/spec-main-shape-gate.yml` | deleted | the copy the ruling drops | | `scripts/spec-main-shape-gate.mjs` | deleted | its script; no other file imports it | | `scripts/__tests__/spec-main-shape-gate.test.ts` | deleted | its pin test | | `scripts/dependabot-merge-gate.mjs` | `NOT_A_GATE` entry and the `REQUIRED_CONTEXTS` note removed | no workflow produces the context any more; the partition test (`the gate names checks that no pull-request workflow produces`) refuses a classified name nothing produces, and the note said "the reason is written there" about the entry being removed | | `scripts/__tests__/dependabot-merge-gate.test.ts` | the objectui#12240 describe block removed | it pinned the `NOT_A_GATE` entry above; the general partition test now holds the absence | | `scripts/check-required-check-set.mjs` | `WATCHED_CONTEXTS` tombstone rewritten | it pointed at the deleted workflow's header and described re-enrolment; it now says the workflow is gone and the name must not come back to the ruleset (a required context nothing reports stalls the queue at the ruleset timeout, objectui#3523) | | `.github/workflows/repo-gates.yml` | header comment only | it said the gate "keeps its own workflow for its hourly run on `main`" | | `content/docs/guide/ci-cd-pipeline.md` | inventory row removed; Repo Gates paragraph updated; the `Spec Main Shape Gate` section replaced by a short `Spec Shape Gate — moved to objectstack` section | the page's pins require every bare workflow filename it names to exist; the new section names the successor, the registry, and why `turbo.json` keeps the marker | | `scripts/__tests__/ci-cd-pipeline-doc.test.ts` | the two `SWEEP_DECLARED_NON_RUN_COMMANDS` entries keyed on the deleted workflow removed | the map is asserted as an exact set; with no section, both entries would read as stale | ## Kept on purpose - **`turbo.json` `globalDependencies` keeps `node_modules/@objectstack/spec/.spec-main-shape-gate.json`.** Who writes the marker: before this change, objectui's `inject` and objectstack's `inject`; after it, objectstack's `inject` still writes the same-named marker into every spec copy it replaces in an objectui install (`MARKER_FILE` in objectstack's `scripts/ci/spec-shape-gate.mjs`, whose header says the spelling "stays objectui's … for as long as objectui's turbo config names it"). Who reads it: turbo, through this entry, and nothing else in this tree (`git grep` for the marker name outside the deleted files hits only `turbo.json`; `scripts/select-type-check-scope.mjs` reads `globalDependencies` generically as root-change globs). The function still holds: a local reproduction of the successor gate in a linked worktree shares `.turbo/cache` with its siblings, and the marker keeps a forced injected run from leaving a verdict an installed tree replays (objectui#12114). Removing it would leave objectstack's header describing a coupling that no longer exists. Its in-tree pin lived in the deleted test, so the entry is now unpinned here; the new docs section carries the reason. - **`scripts/__tests__/select-type-check-scope.test.ts`** still feeds the two deleted paths as input strings to the selector: that case reproduces PR objectstack-ai#12262's diff, turbo's answer is stubbed to an empty list, and nothing reads those paths from disk. The case stays true. - **`scripts/__tests__/check-required-check-set.test.ts` and `fixtures/required-check-set/live-2026-10-12.json`** are a dated reading of the objectui#12240 window and a pin that the name is neither pinned nor watched; both stay true. The seven ruled names are unchanged. - **The objectui#12240 window paragraph in the `check-required-check-set.mjs` header** is history and stays true. - **`packages/**`, `CHANGELOG.md` and landed `.changeset/*` mentions** are other lanes' history and are not touched (listed in the report). ## Safety readings (2026-10-16, before the first push) - **Live ruleset:** `GET /repos/objectstack-ai/objectui/rules/branches/main`, evaluated by `node scripts/check-required-check-set.mjs --fixture` on the saved answer: `INTACT`, seven required contexts, `Spec Main Shape Gate` not among them. So deleting the workflow cannot leave a required context unreported. (The script's own live read answered 401 in this container for want of a token, exit 2, a reading not taken; the same endpoint read through `gh api` answered.) - **Agreement, re-read at start:** the last four `schedule` runs on `main` of both gates all concluded `success` (objectui `38833480846`, `38826070331`, `38819949451`, `38813070302`; objectstack `38833492264`, `38826210209`, `38820098793`, `38813219037`). objectui#12325 is closed. ## Changeset None. `node scripts/check-changeset-presence.mjs` on this diff: "No source or published contract of a released package changed in this range, so no changeset is owed" (exit 0). The diff touches workflows, scripts and docs only. ## Governed surface `node scripts/check-governed-queue-guard.mjs --test` on all nine changed paths: `GOVERNED`, 2 of 9 paths on `.github/workflows/**` (exit 3). This PR stays a draft; it is a dispatched PR, so the claiming seat requests the review and lands it after an authorised approval. ## Verification at `3e9675e20` `node ../objectstack/scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectui` refuses by design (the script derives only objectstack's families), so the local set was hand-derived from this repository's own workflows and tests: - `pnpm exec vitest run --maxWorkers=2` over 89 test files: every test under `scripts/__tests__/` that names `repo-gates.yml`, the deleted workflow, `dependabot-merge-gate`, `check-required-check-set`, `ci-cd-pipeline.md` or `workflow-checks`, plus every test that reads `.github/workflows/`: `Test Files 89 passed (89)`, `Tests 3340 passed (3340)`, lock verdict `command-exit 0`. - `pnpm type-check:scripts` (tsc over `scripts/`, the pin tests included): exit 0. - `pnpm exec eslint` on the four changed script and test files: exit 0. - The install-free Repo Gates steps, each exit 0: `check-control-bytes`, `check-action-ref-convention`, `check-pre-install-import-graph` (self-test and run), `check-shell-escape-residue`, `check-doc-links`, `check-doc-fence-languages` (self-test and run), `check-doc-example-ids`, `check-doc-component-types`; plus `check-new-cross-file-line-citations` (0 new), `check-required-check-set --self-test`, `check-changeset-presence`. - NOT MEASURED locally, CI's: the full `pnpm test`, `pnpm lint`, the turbo type-check, `Build Docs`, `Build & E2E`, and the Repo Gates steps that need a build (doc snippet and skill example compiles). The new docs section adds no fenced block. ## Acceptance notes - objectstack's `scripts/ci/spec-shape-retirements.json` still carries the entry registered to objectui#12325, although that card closed on 2026-10-14 and the file's `$comment` says an entry is "deleted when that card closes". The objectui runs have been green since, so the entry matches nothing today; a later break of the same pattern would read as known. An observation about the objectstack side, not acted on here. - The `turbo.json` marker entry has no in-tree pin after this change (see "Kept on purpose"). - Several `packages/**` comments describe the gate in the present tense ("compiled … by the Spec Main Shape Gate against `main`"). The compile they describe still happens, now as objectstack's `Spec Shape Gate`; only the name is stale. Other lanes' files, left alone. ## 维护者速读(草稿) - **改了什么:** 删掉 objectui 自己那份 spec 形状门(workflow、脚本、它的 pin 测试),并把五处提到它的说明改成「门已搬到 objectstack」。 - **为什么改:** 维护者已裁(objectstack-ai/objectstack#23238 裁决 B):形状门搬到上游 objectstack,objectui 的副本在两边连续一致后删除;卡上记了四对一致的小时运行。两份同时在跑只会让人读错哪份是权威。 - **风险与代价(含回滚):** 线上必需检查名单里已没有这个检查名(本 PR 开出前实读),删掉不会卡住合并队列;后继门在 objectstack 每小时跑、发版前必过,红灯归改动 spec 的那条 lane。回滚 = revert 本 PR,门恢复原样。 - **席位意见:** - **你要做的:** 看一眼删除范围与保留的 `turbo.json` 标记理由,批准后由认领席落地。 --- _Generated by [Claude Code](https://claude.ai/code/session_016PVjjuVPte5XEJvaQx1E4t)_ Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: os-zhuang <jack@objectstack.ai>
Fixes #23238
Clause-②: no
Executes ruling B on this card (comment 6145029395, batch 336 item 3, maintainer 「其他同意」 2026-10-13T11:58Z): the spec shape gate moves from objectui to objectstack -- hourly on
mainwith the two-speed full battery, read by the release gate, a red attributed to the lane that moved the shape; objectui drops its copy after two consecutive agreeing hourly runs. The mechanism of ruling C on #19011 (consumer compilation as the one shape gate) is unchanged; only where it runs and who owns a red changed. Governed surface:.github/workflows/**is Tier H, so this PR stays a draft and lands only on the maintainer's approval. Not touched, by the card's terms:.github/workflows/release.yml(in flight under another card) and every objectui file (the objectui half is the objectuidomain:devxlane's card).What changed
.github/workflows/spec-shape-gate.yml(new). One job,Spec Shape Gate: checkout objectstack, checkoutobjectstack-ai/objectuiatmainunderobjectui/, resolve both shas once, setup Node 22 + the repo'ssetup-pnpmcomposite, restore one pnpm store keyed on both lockfiles, run the gate script's self-test,pnpm install --filter @objectstack/spec...,pnpm --filter @objectstack/spec build,npm pack --ignore-scripts(the file set a release from this commit would ship), install objectui, inject the tarball into objectui's pnpm virtual store,TURBO_FORCE=true pnpm type-check --continuewith the exit code captured before any pipe, then an always-run report step that carries the verdict. Triggers:scheduleat52 * * * *(off ci.yml's and lint.yml's:00),workflow_dispatch, and apull_requesttrigger path-filtered to the gate's own three files -- so a change to the gate, a registration included, is exercised on its own PR and no other PR pays the ~13 minutes (two-speed rule 1). Nomerge_group. Permissions are reads only (contents,actions,pull-requests,issues). Concurrency keys ongithub.event_nameso a dispatch and the hourly run onmainnever cancel each other; only PR runs cancel in progress.scripts/ci/spec-shape-gate.mjs(new). The objectstack port of objectui'sscripts/spec-main-shape-gate.mjswith the direction reversed:injectwrites into the DOWNSTREAM clone (--downstream), reads the dependencies the spec was built against from THIS checkout (--upstream-checkout), proves every workspace consumer resolves the injected copy, and re-points a store sibling the packed manifest's range refuses (the zod case). Workspace consumers and the turbo name-to-directory map are now derived from the downstream'spnpm-workspace.yamlrather than a hard-coded directory list. The artifact-reuse half is dropped: the spec is always the one from the tree under test.reportparses the turbo log in both of turbo's log orders, dedupes the two tsc programs of one task, reads the retirement registry, splits diagnostics intoknown(registered) and new, attributes a new red to the commit range since this workflow's last green run onmainand thedomain:*lane of the card each pull fixes (unattributedwhen it cannot read one), writes the run summary, emits one annotation per new diagnostic (objectui path and objectstack sha in the title, nofile=-- the platform re-homes an unresolvablefile=onto the workflow file), and exits 0 green or known-only, 1 new break, 2 reading not taken.--self-test: 60 cases in 7 pinned batteries with the verdict-flag handshake (thecheck-agent-model-declared.mjsshape, copied not imported).scripts/ci/spec-shape-retirements.json(new). The intentional-retirement registry: one entry per retirement withretired,objectstack_pr,objectstack_commit(40-hex),downstream_card,registered_atand non-emptymatches, each pattern a repo-relative objectuifile(glob allowed) plus at least one ofcode/message. A file-only pattern, an emptymatches, an unknown key or a malformed field is refused and the whole run is exit 2 -- the registry cannot become an ignore-all switch, and a registry that cannot be read is not an empty one. Entries that matched nothing in a run are listed for pruning. The first commit of this PR ships it EMPTY (positive control run 1, expected red); the second commit registers PR feat(spec)!: retire the view-levelapidata source — a view binds its object or static rows; an API call is the page element's binding (#23082) #23160 -> the view-levelapidata source is retired in @objectstack/spec (objectstack#23082): delete theViewData.provider === 'api'readers, keep the approvals inbox's code-builtApiDataSourceobjectui#12325 (run 2, expectedknown).scripts/ci/scheduled-full-run.mjs.RELEASE_GATE_WORKFLOWSgains{ file: 'spec-shape-gate.yml', name: 'Spec Shape Gate' }, sorelease.yml's existingfull-batteryjob (--release-gate, unchanged) now refuses a publish unless this gate's newestschedulerun at or before the version commit concludedsuccesstoo. The self-test gains six cases (the recorded 2026-10-12 listing judged explicitly over the two workflows it recorded, a firing control that the default roster refuses that same listing namingSpec Shape Gatealone, a red / a missing / a dispatch-only Spec Shape Gate run on synthetic data, and a pin that every registered workflow file exists); floors rose 6→8, 14→17, 5→6, none lowered.package.json:check:spec-shape-gatealias (node scripts/ci/spec-shape-gate.mjs --self-test), run by the workflow's self-test step, so the script is a discoverable gate family andcheck-self-test-wiredsees its self-test run in CI.The PM's mechanism hypotheses, measured
scheduled-full-run.mjsenumeratesRELEASE_GATE_WORKFLOWSby workflow FILE and readsGET /actions/workflows/{file}/runs?branch=main&event={schedule|push}&status=completed, picks the nearest commit with a non-cancelled run and the newest run on it, and requiressuccessfrom every workflow. Registration is one row there; aknown-only run concludessuccess, so a registered retirement does not block a release and an unregistered break does. Confirmed; floors raised, none lowered.ci.ymlandlint.ymlcarryschedule: '0 * * * *'; this gate is its own workflow file at:52, not inci.yml(nocheck-expected-skipsroster change needed: a workflow-levelpaths:filter creates no check-run on a PR, so nothing shows as skipped).actions/checkoutwithrepository: objectstack-ai/objectui,ref: main,path: objectui,fetch-depth: 1. Measured on objectstack'spnpm-workspace.yaml: every glob is rooted atpackages/,apps/orexamples/, soobjectui/is never swept into this repository's install.successrun onmain(schedule/workflow_dispatch/push) that is not this run and not this head; range = the compare APIbase...head; per commit the(#N)squash suffix -> pull labels + the pull body's closing keywords -> card labels;unattributedwith the reason otherwise. Failure shape, printed not guessed: no earlier green (the first runs) -> "no range could be named"; compare truncated at 250 -> marked; pull-request lookups capped at 40 per run (printed when reached). A PR run has no range and says so. The verdict never depends on the attribution.pull_requestpath filter makes this PR's own runs the measurement.scripts/check-agent-model-declared.mjs; no shared assertion module imported.实测分钟数 -- measured minutes
Measured on this PR's own
Spec Shape Gateruns (thepull_requestpath filter makes them the measurement): run 1 38411582655 (head1c3bc822, empty registry) -- job wall 10.4 min; run 2 38413661014 (headfde9ac1c38, PR #23160 registered) -- job wall 13.9 min. 10.4 and 13.9 minutes of oneubuntu-latestrunner per run; the uncached downstream build plus typecheck is 8.3 to 11.3 of them (build spec 63-83 s, type-check objectui 498-677 s); hourly onmainthat is roughly 250 to 335 runner-minutes a day on the public repo. Both runs restored the pnpm store from ci.yml's existing entries (objectui install 11-14 s); the save step runs only onmainruns. Reported to the maintainer under the ruling's fork clause (the seat's ACCEPT on #23238 and the final 维护者速读 below); this PR takes no fallback to option C.阳性对照 -- positive control, two runs
maincarries since PR feat(spec)!: retire the view-levelapidata source — a view binds its object or static rows; an API call is the page element's binding (#23082) #23160 (view-levelprovider: 'api'), the same diagnostics objectui's hourly run shows --packages/react/src/hooks/useViewData.tsTS2367 andpackages/core/src/adapters/resolveDataSource.tsTS2678 / TS2339.apidata source — a view binds its object or static rows; an API call is the page element's binding (#23082) #23160 -> the view-levelapidata source is retired in @objectstack/spec (objectstack#23082): delete theViewData.provider === 'api'readers, keep the approvals inbox's code-builtApiDataSourceobjectui#12325): expected the same diagnostics read asknown (registered: objectstack-ai/objectui#12325), zero new, job green.failure,Reportstepfailure(exit 1), 10 new-break annotations (spec shape break: objectui PATH:LINE (objectstack f435d694e9de), GitHub's 10-per-step cap; the full table is in the run summary) overpackages/core/src/adapters/resolveDataSource.ts,packages/react/src/hooks/useViewData.ts,packages/plugin-calendar/src/ObjectCalendar.tsx,packages/plugin-gantt/src/ObjectGantt.tsxand twopackages/types/src/__tests__/*.test.tsfixtures -- every row the PR feat(spec)!: retire the view-levelapidata source — a view binds its object or static rows; an API call is the page element's binding (#23082) #23160 retirement; run 2 38413661014 -- jobsuccess, report exit 0, 0 new-break annotations (the only failure-level annotations left are turbo's own task-failure lines), theknowntable in the run summary.Verification
Local:
node scripts/ci/spec-shape-gate.mjs --self-test(60/60 across 7 batteries),node scripts/ci/scheduled-full-run.mjs --self-testand--check-concurrency,pnpm check:nul-bytes, and the gate list derived bynode scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(58 commands) with exit codes captured before any pipe; the per-command table is in the report. The diff touches no published package, so the local build/test scope ① ② is empty. The downstream compile itself is measured by this PR's own CI runs, not locally.维护者速读(草稿)
改了什么
把「拿平台当前代码编一份 spec、去编译下游 objectui」这道检查,从 objectui 搬到 objectstack:每小时在
main上跑一次,结果进发版前必过的清单;红的那次会写明是哪些 objectstack 提交、哪条车道改坏了接口;有意的退役在「退役登记」里记一行,对应的报错就显示「已登记」而不再翻红。objectui 那边的同款检查暂不动,等这边连续两小时的结果和那边一致后由 objectui 车道删除。为什么改
维护者裁决 B。现状是下游每个 PR 替上游做一次约 13 分钟的体检,红总在上游合并之后才出现在下游,而且有意的退役会让它连红好几天、盖住真正的意外破坏。搬到平台这边、定时跑、发版前必过,问题记在改动方名下,像出厂检验。
风险与代价(含回滚)
scripts/ci/scheduled-full-run.mjs里RELEASE_GATE_WORKFLOWS的那一行去掉,发版门禁即回到只读 CI 与 Lint 两道的状态;objectui 侧的检查在此期间一直在跑,不会出现空档。席位意见
(留空)
你要做的
.github/workflows/**是 Tier H,需要你的一次 approve),之后由席位落地。Acceptance notes
.github:actions/setup-noderegisters atscproblem matcher that parses the echoed typecheck log a second time with paths that do not resolve. Handled here by::remove-matcher owner=tsc::before the log is echoed. Observation on objectui's side, not filed; carrier: the objectuidomain:devxdeletion card, which removes that workflow anyway.scheduled-full-run-card.ymlfiles a card for a red hourlyCI/Lint & Type Checkrun with a fixeddomain:devxlabel. This gate's red is reported in the run summary and annotations with its own lane attribution and files no card; wiring it into the card filer would label the card with the wrong lane. Recorded as an open question in the report, not changed here (the card's "no other check" term).scripts/ci/scheduled-full-run.mjs --release-gatereadsscheduleandpushevents; this workflow has nopushtrigger, so only its hourly runs are read by the release gate, and aworkflow_dispatchrun is attributed but not read -- the same rule the other two workflows live under.release.yml'sfull-batteryjob comment still names two workflows in prose; the script is the authority and is unchanged in release.yml by the card's terms (in flight under another card). Noted for that card's carrier.Generated by Claude Code