From 58973d5b7ad79f92c1cbf41d727cd1f7f8a701be Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 12 Oct 2026 14:20:11 +0000 Subject: [PATCH] ci(spec-main-shape-gate): drop the merge_group trigger now the context is out of the required set (objectui#12252) Executes the follow-up the maintainer's ruling B on objectui#12240 left open (ACCEPT 6125070195, answer B). The ruleset no longer lists the context, so a verdict on a queue build is read by nobody while every merge group pays a runner for it. pull_request (the advisory signal a human reads), the hourly schedule on main and workflow_dispatch stay. - spec-main-shape-gate.yml: `merge_group` leaves `on:`; the header's bullet on it, the artifact-trust sentence, and the concurrency and timeout-minutes comments that reasoned about queue builds follow. - spec-main-shape-gate.test.ts: the trigger pin becomes its inverse, "re-subscribe merge_group before re-enrolling". The derived floor in merge-queue-reporting.test.ts already refuses a required name whose workflow lacks the trigger. - ci-cd-pipeline.md: only the sentences that said the gate runs on the queue. Nothing the gate verifies changes. Claude-Session: https://claude.ai/code/session_01LqNgdPZB2Jnr5HpGbaqLeB Co-authored-by: Claude --- .github/workflows/spec-main-shape-gate.yml | 46 ++++++++++--------- content/docs/guide/ci-cd-pipeline.md | 28 +++++------ .../__tests__/spec-main-shape-gate.test.ts | 31 ++++++++++--- 3 files changed, 64 insertions(+), 41 deletions(-) diff --git a/.github/workflows/spec-main-shape-gate.yml b/.github/workflows/spec-main-shape-gate.yml index ba27aba46c..b02b42c866 100644 --- a/.github/workflows/spec-main-shape-gate.yml +++ b/.github/workflows/spec-main-shape-gate.yml @@ -51,13 +51,18 @@ name: Spec Main Shape Gate # request of that lane arms the queue while the red stands. # - `pull_request` posts the same verdict as an advisory check. A human reads # it; nothing waits for it. -# - `merge_group` stays subscribed, deliberately. The ruled order is merge -# first, ruleset edit second, and until that edit the queue still requires -# this name: a workflow that stopped reporting on `merge_group` would stall -# every queue build at the ruleset's 60-minute timeout with nothing red to -# point at (objectui#3523 -- subscribe first, enrol second, and the mirror -# image on the way out). It also keeps the context REQUIRABLE, so -# re-enrolling it stays a settings edit. +# - `merge_group` is NOT subscribed (objectui#12252), the other half of the +# same ruling: the context is not in the queue's required set, so a +# verdict on a queue build would be read by nobody while every merge group +# paid a runner for it. (Whether the ruleset says so is +# `scripts/check-required-check-set.mjs`'s reading, ⛔ not this comment's.) +# ⛔ Re-subscribe `merge_group` BEFORE re-enrolling the context, never +# after: a required context that never reports on a queue build stalls +# it at the ruleset's 60-minute timeout with nothing red to point at +# (objectui#3523 -- subscribe first, enrol second). `merge-queue-reporting` +# derives that floor from `REQUIRED_CONTEXTS`, and +# `scripts/__tests__/spec-main-shape-gate.test.ts` pins the absence, so +# re-enrolling edits both on purpose rather than by accident. # # ⚠️ Requiredness is repository SETTINGS (GitHub ruleset 11776024), which no # file in this tree can set, so "advisory" is not a mechanism in this file. @@ -101,13 +106,13 @@ name: Spec Main Shape Gate # before, so `inject` still reads the dependencies the spec resolves. # # Only an artifact from a `main` run of THIS repository is reused. A pull -# request or a queue build runs the workflow file of its own branch, so an -# artifact it uploaded is a file anybody with a branch could have shaped; a -# run on `main` executes reviewed YAML. The lookup is a READ (`actions: read`); -# nothing here writes through the token, and the upload uses the runner's own -# artifact credential. A lookup that fails, a download that fails, or an -# artifact whose recorded sha is not the one resolved above all mean BUILD -- -# never a verdict about a spec this job did not hold. +# request (or a manual dispatch on a branch) runs the workflow file of its own +# branch, so an artifact it uploaded is a file anybody with a branch could have +# shaped; a run on `main` executes reviewed YAML. The lookup is a READ +# (`actions: read`); nothing here writes through the token, and the upload uses +# the runner's own artifact credential. A lookup that fails, a download that +# fails, or an artifact whose recorded sha is not the one resolved above all +# mean BUILD -- never a verdict about a spec this job did not hold. # # ⚠️ What this buys is the build and the pack, not the compile: the forced # typecheck below is the bulk of this job and is deliberately never cached. @@ -126,8 +131,6 @@ name: Spec Main Shape Gate on: pull_request: branches: [main, develop] - merge_group: - types: [checks_requested] workflow_dispatch: {} # The full battery's hourly run on `main` (objectui#12214). Off the hour on # purpose, and on a different minute from `ci.yml`'s. @@ -144,9 +147,10 @@ permissions: concurrency: group: spec-main-shape-gate-${{ github.ref }} - # Superseded pull-request runs are cancelled; a QUEUE build never is. A - # cancelled check is not `success`, and on a queue build that is a dequeue - # rather than a saved runner minute. + # Superseded pull-request runs are cancelled; a run on `main` (the hourly + # `schedule`, or a dispatch) never is. A cancelled check is not `success`, and + # the scheduled run is the one place this gate's verdict is waited on, so a + # cancellation there is a missing reading rather than a saved runner minute. cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: @@ -156,8 +160,8 @@ jobs: # Sized against measurements, not taste: the spec's own build and this # repository's forced (uncached) build + typecheck each cost minutes, and the # runner is slower than the box they were measured on. A ceiling this job can - # reach turns a verdict into `cancelled`, which the merge queue cannot tell - # from `failure` and which reports nothing a reader can act on. + # reach turns a verdict into `cancelled`, which reads as a red on a pull + # request or on `main` and reports nothing a reader can act on. timeout-minutes: 45 steps: diff --git a/content/docs/guide/ci-cd-pipeline.md b/content/docs/guide/ci-cd-pipeline.md index c5637e2997..ed9c67ee15 100644 --- a/content/docs/guide/ci-cd-pipeline.md +++ b/content/docs/guide/ci-cd-pipeline.md @@ -29,7 +29,7 @@ one has its own section below. | `changeset-presence.yml` | Changeset Declaration | PR to `main`, `develop` — **no path filter**; merge-queue builds | **Yes** — when a released package's `src/` changed and no changeset was added | | `repo-gates.yml` | Repo Gates | Push / PR to `main`, `develop` — **no path filter**; merge-queue builds; manual | **Yes** — fifteen gates as steps of one job ([#12214](https://github.com/objectstack-ai/objectui/issues/12214)); the job is red when any step is, and `scripts/dependabot-merge-gate.mjs` declares the context in `REQUIRED_CONTEXTS` | | `line-citation-gate.yml` | Line Citation Gate | PR to `main`, `develop` — **no path filter**; manual | No — **report-only** while it beds in; it exits 0 whatever it finds, and exits 1 only when one of its own synthetic controls fails. It declares no `merge_group` trigger, so it cannot be a required context in its current state | -| `spec-main-shape-gate.yml` | Spec Main Shape Gate | PR to `main`, `develop` — **no path filter**; merge-queue builds; manual; hourly `schedule` on `main` | No — **advisory** since [#12240](https://github.com/objectstack-ai/objectui/issues/12240): the maintainer's ruling took the context out of the required set, and `scripts/dependabot-merge-gate.mjs` classifies it `NOT_A_GATE`. Its verdict is unchanged — a shape break is a real red naming the objectui file and the objectstack commit ([#9860](https://github.com/objectstack-ai/objectui/issues/9860)) — and a red on the hourly `schedule` run on `main` is a main-red for the `domain:devx` lane | +| `spec-main-shape-gate.yml` | Spec Main Shape Gate | PR to `main`, `develop` — **no path filter**; manual; hourly `schedule` on `main` | No — **advisory** since [#12240](https://github.com/objectstack-ai/objectui/issues/12240): the maintainer's ruling took the context out of the required set, and `scripts/dependabot-merge-gate.mjs` classifies it `NOT_A_GATE`. Its verdict is unchanged — a shape break is a real red naming the objectui file and the objectstack commit ([#9860](https://github.com/objectstack-ai/objectui/issues/9860)) — and a red on the hourly `schedule` run on `main` is a main-red for the `domain:devx` lane | | `governed-surface-guard.yml` | Governed Surface Queue Guard | PR to `main`, `develop` (incl. `ready_for_review`) — **no path filter**; merge-queue builds | **Yes on a queue build only** — a governed-surface diff with no authorized approval record (on any commit) is refused there, and so is any merge group whose queued pull requests still carry `needs:contract-review`; on the pull request itself it is deliberately green and prints an early warning | | `performance-budget.yml` | Bundle Analysis | Push / PR touching `packages/**`, `apps/console/**`, `pnpm-lock.yaml` | **Yes** — the console entry gzip budget | | `lockfile-integrity.yml` | Lockfile Integrity Check | PR to `main`, `develop` touching `pnpm-lock.yaml` or the gate's own two files; manual | No — **deliberately not a blocking context** ([#8326](https://github.com/objectstack-ai/objectui/issues/8326)); it names the packages and the Dependabot merge gate classifies it `NOT_A_GATE` | @@ -2082,7 +2082,7 @@ Uses [Lychee](https://github.com/lycheeverse/lychee) with configuration from `ly ## Spec Main Shape Gate (`spec-main-shape-gate.yml`) -**Trigger:** PR to `main`, `develop` — **no path filter**; merge-queue builds; manual; hourly `schedule` on `main`. +**Trigger:** PR to `main`, `develop` — **no path filter**; manual; hourly `schedule` on `main`. **Appears as:** Spec Main Shape Gate. It builds `@objectstack/spec` from objectstack `main` at a git sha and compiles this repository @@ -2130,11 +2130,11 @@ of it the job needs. `objectstack-spec-`, with the sha it was packed at, kept one day. Every other run first looks for that artifact at the sha it just resolved and injects it instead of building again; the clone and the spec's own install still run, because the injection reads the dependencies the spec's -build resolved. Only an artifact from a `main` run of this repository is reused: a pull request or -a queue build runs its own branch's workflow file, so what it could upload is not evidence about a -spec. A failed lookup or download, or an artifact recording another sha, means *build*. What this -saves is the build and the pack — the forced compile below is most of the job and is never cached; -the measured split is on #12214's pull request, not restated here. +build resolved. Only an artifact from a `main` run of this repository is reused: a pull request +(or a manual dispatch on a branch) runs its own branch's workflow file, so what it could upload is +not evidence about a spec. A failed lookup or download, or an artifact recording another sha, means +*build*. What this saves is the build and the pack — the forced compile below is most of the job and +is never cached; the measured split is on #12214's pull request, not restated here. **The compile is `TURBO_FORCE=true pnpm type-check --continue`, and every word of it is load-bearing.** That task dependsOn `^build`, so the job builds every workspace package against @@ -2160,8 +2160,8 @@ every line locally, and on a GitHub Actions runner a `##[group]:` output emitted bare beneath it — with the *failing* task announced by a bare colourised header and no group at all. Only the second ever reaches this job, and a prefix-only reading has nothing to match in it. `scripts/__tests__/spec-main-shape-gate.test.ts` pins the bypass, the completeness -flag, the unfiltered trigger, the queue subscription and both attribution carriers, each with the -firing control that proves the assertion is not satisfied by something else. +flag, the unfiltered trigger, the absence of a queue subscription and both attribution carriers, +each with the firing control that proves the assertion is not satisfied by something else. **How it became a required check, and how it left the set.** Requiredness is a GitHub ruleset — repository settings — which `scripts/check-required-check-set.mjs` can read and nothing in this tree @@ -2188,10 +2188,12 @@ what changed is who waits for its verdict. hide the verdict and change nothing about who waits for it. - The name moved back to `NOT_A_GATE` in `scripts/dependabot-merge-gate.mjs`, so a Dependabot bump does not wait for it either, and `scripts/check-required-check-set.mjs` no longer watches it. -- `merge_group` stays subscribed. The maintainer removes the context from the ruleset in the same - sitting as that pull request's merge, and until that edit the queue still requires the name: a - workflow that stopped reporting on queue builds would stall each of them at the ruleset's - 60-minute timeout. The subscription also keeps the context requirable. +- It does not run on queue builds. [#12252](https://github.com/objectstack-ai/objectui/issues/12252) + dropped the `merge_group` trigger once the context was out of the queue's required set: a verdict + on a merge group is read by nobody, and every merge group paid a runner for it. The way back is + two edits in this order — re-subscribe `merge_group`, then enrol the context — because a required + context that never reports on a queue build stalls it at the ruleset's 60-minute timeout. The + test file pins the absence, so re-enrolling edits both on purpose. Whether the context is enrolled today is not answered here; the patrol's reading answers it. diff --git a/scripts/__tests__/spec-main-shape-gate.test.ts b/scripts/__tests__/spec-main-shape-gate.test.ts index e9c07d7f6c..251e2c8b27 100644 --- a/scripts/__tests__/spec-main-shape-gate.test.ts +++ b/scripts/__tests__/spec-main-shape-gate.test.ts @@ -75,11 +75,27 @@ describe('the gate exists and is wired the way a requirable check has to be', () expect(trigger.filtered, 'a path filter would make this context unrequirable').toBe(false); }); - it('subscribes merge_group, so a maintainer can enrol it without stalling the queue', () => { - // The #3523 ordering: subscribe first (a pure addition), enrol second. The - // reverse order is the state in which every queued pull request burns the - // ruleset's 60-minute timeout with nothing red to point at. - expect(subscribesMergeGroup(workflow as NonNullable)).toBe(true); + it('does not subscribe merge_group — re-subscribe merge_group before re-enrolling the context (objectui#12252)', () => { + // The #3523 ordering, on the way out. The context left the queue's required + // set (objectui#12240), so a queue build's verdict on this gate is read by + // nobody while every merge group pays a runner for it; the hourly run on + // `main` and the advisory run on a pull request are where it is read. + // + // This is the ruling's in-tree lock, not a ban on the trigger. The way back + // is two edits in this order — subscribe first (a pure addition), enrol + // second — and the second is the case the derived floor in + // `merge-queue-reporting.test.ts` refuses on its own: a name in + // `REQUIRED_CONTEXTS` whose workflow lacks the trigger. The reverse order is + // the state in which every queued pull request burns the ruleset's + // 60-minute timeout with nothing red to point at. + expect( + subscribesMergeGroup(workflow as NonNullable), + `${WORKFLOW} subscribes \`merge_group\` again, so every merge group runs a gate whose ` + + `verdict nothing waits for (objectui#12252). If the context is being RE-ENROLLED on a ` + + `ruling, subscribing first is the right order (objectui#3523): replace this pin with the ` + + `subscription it then owes, and enrol second — the in-tree half is \`REQUIRED_CONTEXTS\` ` + + `in scripts/dependabot-merge-gate.mjs. Otherwise drop the trigger.`, + ).toBe(false); }); it('runs both halves of the gate script', () => { @@ -120,8 +136,9 @@ describe('the spec is built only when objectstack main moved (objectui#12214)', }); it('reuses only an artifact a main run of THIS repository built, named by the resolved sha', () => { - // A pull request or queue build runs its own branch's YAML, so what it - // uploads is not evidence about a spec; a main run executes reviewed YAML. + // A pull request (or a manual dispatch on a branch) runs its own branch's + // YAML, so what it uploads is not evidence about a spec; a main run + // executes reviewed YAML. const lookup = step('Look for the spec a main run already built at that commit'); expect(lookup).toContain('actions/artifacts?name=objectstack-spec-$UPSTREAM_SHA'); expect(lookup).toContain('.workflow_run.head_branch == "main"');