Repository navigation
feat(ui): the signed-out invitation landing names the inviter and the organization from the invitation preview #26598
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # The PREVENTION half of the governed-surface regime (#11704). The rule, the | |
| # event split, the ruled approval predicate and the exit contract all live in | |
| # `scripts/pm/check-governed-queue-guard.mjs`; that header is authoritative and | |
| # this file is the invocation. Only the wiring decisions are argued here. | |
| # | |
| # Its sibling `check:pm-governed-merges` is the DETECTION half — a report-only | |
| # post-merge audit. Three incidents (#9550, #10580, the #9319 landing) each | |
| # ended with a governed diff enqueued or merged with zero reviews and each was | |
| # caught by ACCIDENT, which is the expected behaviour of a regime whose only | |
| # pre-merge line of defence is seat discipline. This is the missing refusal. | |
| name: Governed Surface Guard | |
| on: | |
| # BOTH legs are load-bearing, and they mean DIFFERENT things — see the script | |
| # header. `merge_group` is the leg that REFUSES: the queue build is the last | |
| # thing between a speculative merge and `main`, and it is the path a seat | |
| # uses. `pull_request` is an EARLY WARNING that deliberately exits 0, because | |
| # a governed PR held as a draft for the maintainer to merge by hand is the | |
| # regime's healthy end state, and a check that reddens on the healthy case is | |
| # the permanently-red gate the 2026-08-18 ruling retired. | |
| pull_request: | |
| # ⛔ NO `branches:` filter either — the same permanent-pending wedge as the | |
| # `paths:` note below, reached through the other axis of this same trigger: | |
| # `paths:` decides which CHANGES start the workflow, `branches:` decides | |
| # which BASE branches do. A `branches: [main]` filter means a PR based on a | |
| # feature branch does not start this workflow at all, so `Governed Surface | |
| # Queue Guard` publishes NO check run there — an ABSENCE, which branch | |
| # protection holds as permanently pending rather than as a skip. A required | |
| # context must report for ANY base, and this one is required: enrolling it | |
| # in `scripts/check-required-contexts.mjs` brought this file under that | |
| # registry's assertion 7c, which is the same defect #16482 cleared from | |
| # `ci.yml` and `lint.yml` before this workflow was registered. | |
| # Naming `types:` REPLACES GitHub's default set rather than extending it, | |
| # so all three defaults are restated here (#8304). `ready_for_review` is | |
| # the addition and it is the point: flipping a governed draft to ready is | |
| # the first move of the exact sequence this guard exists to interrupt, and | |
| # it is not in the default set — without it the warning would not re-fire | |
| # at the one moment a seat most needs to read it. That same flip is the | |
| # moment an approver taps merge, and the re-fired run holds this REQUIRED | |
| # context `in_progress` for its whole wall (a direct merge is refused | |
| # meanwhile) — which is why this leg is kept cheap rather than dropped: it | |
| # checks out commits and trees only and installs nothing unless the diff | |
| # needs the generator toolchain (the two checkout steps below). | |
| types: [opened, synchronize, reopened, ready_for_review] | |
| merge_group: | |
| # ⛔ NO `paths:` filter, on either leg, and this is not an oversight. A skipped | |
| # job counts as SUCCESS in branch protection, so a path filter would hand the | |
| # queue a green "Governed Surface Queue Guard" for a PR the filter mis-scoped — | |
| # the #4928 shape, on the one check whose entire job is to refuse. The path | |
| # test belongs INSIDE the script, where "nothing governed" is a verdict that | |
| # says so and costs zero API calls. (`merge_group` has no paths support at all, | |
| # so a filter would also make the two legs disagree about what they cover.) | |
| concurrency: | |
| group: governed-surface-guard-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| # The default read scopes and nothing beyond them. `pull-requests: read` is | |
| # what the review list needs; there is no write scope here, and adding one to | |
| # make this check "fix" anything would be a widening no ruling covers. | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| jobs: | |
| governed-surface-guard: | |
| # ⚠️ THIS LITERAL IS THE CHECK-RUN NAME branch protection would pin, and it | |
| # is duplicated in `CHECK_CONTEXT_NAME` in the script — deliberately, and | |
| # pinned in both directions: the script's `--self-test` reads THIS FILE and | |
| # fails if the two ever disagree. Renaming a job silently detaches a | |
| # required context (#6865); this is the cheap half of the two-step that | |
| # makes that impossible to do by accident. | |
| # | |
| # ⛔ Making it a required context is NOT this PR's step. Branch protection | |
| # is the maintainer's: it takes a `REQUIRED_CONTEXTS` row in | |
| # `scripts/check-required-contexts.mjs` PLUS the entry in Settings → | |
| # Rulesets, in one sitting (#6865). Either half alone is an outage — a row | |
| # without the setting reads as `direction A — registered here, NOT in the | |
| # live required set`, which is #5617's own defect. | |
| name: Governed Surface Queue Guard | |
| runs-on: ubuntu-latest | |
| # JOB WALL = CHECKOUT BUDGET (20) + BODY BUDGET, the split lint.yml's four | |
| # Type Check lanes use; `typecheck-source-gates` there argues it, holds the | |
| # pooled numbers for this same full-history fetch, and says what a timed-out | |
| # checkout step reads as (`failure`) against the wall (`cancelled`). | |
| # Window here: the 300 most recent completed runs of this workflow, | |
| # 2026-10-05T14:57Z → 2026-10-06T18:59Z, every attempt. Checkout: p50 31 s | |
| # / p90 156 s / p99 595 s, and the old 10-minute wall cancelled 4 runs | |
| # behind it (3 still inside the checkout), each a required context gone | |
| # red with no guard verdict. Body after the checkout, successful runs | |
| # (n = 288): max 1.1 minutes; 2× is 2.2, and never below 10. 20 + 10 = 30. | |
| timeout-minutes: 30 | |
| steps: | |
| # The QUEUE leg's checkout, unchanged: the refusing leg reads the queue | |
| # head with the whole tree on disk. | |
| - name: Checkout repository | |
| if: github.event_name != 'pull_request' | |
| uses: actions/checkout@v7 | |
| # CHECKOUT BUDGET: lint.yml's `typecheck-source-gates` sizes it. | |
| timeout-minutes: 20 | |
| with: | |
| # A governed-surface question answered over a truncated history | |
| # answers with SILENCE, and silence reads as compliance (#9902). The | |
| # merge group's base sha can predate a shallow fetch's floor, so the | |
| # whole history is the only depth that cannot under-report. The | |
| # script refuses outright (exit 1) if either sha is missing, rather | |
| # than diffing what it happens to have. | |
| fetch-depth: 0 | |
| # The EARLY-WARNING leg's checkout: the same full history, without file | |
| # contents. The judgment reads commits and trees only (`merge-base`, | |
| # `log`, `diff-tree --name-only`), and the only files it and the | |
| # self-test open live under `scripts/` and `.github/` — so a blobless | |
| # fetch plus a sparse tree of those two answers byte-identically. | |
| # Measured 2026-10-26 on this repository: a blobless full-history clone | |
| # took 7 s for 105 MiB against 58 s for 956 MiB with contents, and the | |
| # judgment on PR #25965 printed the same bytes on both checkouts. ⛔ Still | |
| # `fetch-depth: 0` — the #9902 reason above holds on this leg too. A diff | |
| # that needs the generator toolchain gets the full tree back below. | |
| - name: Checkout repository (commits and trees only) | |
| if: github.event_name == 'pull_request' | |
| uses: actions/checkout@v7 | |
| timeout-minutes: 20 | |
| with: | |
| fetch-depth: 0 | |
| filter: blob:none | |
| sparse-checkout: | | |
| scripts | |
| .github | |
| # Pinned to the same major and spelling as every other setup-node in this | |
| # repo, reading `.nvmrc` like `.github/actions/setup-workspace` does. The | |
| # Part-of Closing-Keyword Guard records why `node-version-file` once died | |
| # here: `actions/setup-node@v5` defaulted `package-manager-cache: true`, | |
| # which shells out to pnpm and hard-errors when pnpm is not yet on PATH. | |
| # That guard runs this same v7 with `node-version-file` and no pnpm at | |
| # all, green. This job DOES acquire pnpm, but strictly after this step, | |
| # so nothing here may start depending on pnpm existing yet. | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version-file: .nvmrc | |
| # The self-test runs FIRST and in its own step, because it is the | |
| # precondition for trusting the run after it: this guard's predicates | |
| # decide whether a merge lands, so a rotted predicate must redden here | |
| # rather than quietly wave a governed diff through. `bash -e` aborting the | |
| # step on the first failure is the correct semantics for a | |
| # precondition — the collector idiom next door in lint.yml is for | |
| # INDEPENDENT self-tests, which these are not. | |
| - name: Guard predicate self-test | |
| run: node scripts/pm/check-governed-queue-guard.mjs --self-test | |
| # ── the generator toolchain — the #11705 rows can now actually recompute ─ | |
| # | |
| # The register's `verify` rows (generator-owned files inside `skills/**`) | |
| # recompute by running the tree's OWN generator through `pnpm … exec tsx`. | |
| # Until now this job installed nothing, so that recompute answered "the | |
| # generator toolchain is not available in this environment" on every run | |
| # and the path stayed governed — a spec PR carrying its regenerated | |
| # `references/_index.md` needed a pinned maintainer approval at merge-group | |
| # time even though the seat-side `--test` lifted the same diff in a dev | |
| # container. The script header filed that trade as not taken; the | |
| # maintainer took it (2026-09-01, verbatim): | |
| # | |
| # > 纯生成的指针行(spec 源变更后再生成的 references/_index.md) 不需要我审核吧 | |
| # | |
| # ⭐ NO `--filter` here, deliberately. The set of packages the register's | |
| # recompute needs is `GENERATED_SURFACE_EXCEPTIONS[].verify.pkg`, and a | |
| # filter list in this file would be a SECOND copy of it — one that goes | |
| # stale silently the day a row is added, in the direction that reads as | |
| # compliance (the new row's generator fails to spawn, the path stays | |
| # governed, and nothing says the install was the reason). A full install | |
| # restates nothing, so a register row reaches this job for free — the same | |
| # reason the script imports the predicate instead of reimplementing it. | |
| # The self-test pins that: every `verify.pkg` in the register must be | |
| # covered by the install line below. | |
| # | |
| # ⛔ NO build step, and that is measured rather than assumed: both `verify` | |
| # rows run through `tsx`, which reads the generator's TypeScript sources | |
| # and their relative `./lib/*` imports directly — nothing consults any | |
| # package's `dist/`. Measured on a warm store: install 7s, the skill-refs | |
| # `--check` 1s. A build would be pure cost with nothing depending on it. | |
| # | |
| # ⭐ EVERY step in this block is `continue-on-error` — this is the load- | |
| # bearing half, not defensive decoration. The guard's own promise is that | |
| # "a diff that touches nothing governed is never blocked by an outage", and | |
| # a hard-failing install would newly make EVERY ordinary queue build depend | |
| # on the npm registry and the cache service. With `continue-on-error`, a | |
| # broken install degrades to exactly the behaviour this job had before this | |
| # block existed: the generator cannot spawn, `runSinkGenerator` returns its | |
| # stated "toolchain is not available" reason, and the path stays GOVERNED. | |
| # Fail-closed on the governed path, unaffected on the clear one, and the | |
| # verdict stays inside the script — where the exit contract lives — instead | |
| # of becoming an opaque red step nobody can read a governance answer out of. | |
| # | |
| # Both legs install WHEREVER THE RECOMPUTE CAN CHANGE THE ANSWER. The | |
| # `pull_request` leg is a FORECAST of the queue verdict ("the merge-queue | |
| # run of this same check will REFUSE it"), so a leg that cannot recompute | |
| # forecasts a refusal that will not happen — the false-alarm direction, | |
| # which trains seats to hold PRs that need no holding. Same diff, same | |
| # answer, on both legs and at the seat. But a diff that touches no | |
| # register path never runs a generator at all, so on that leg the script | |
| # is asked first (`--toolchain-needed`, the same predicate the lift | |
| # consults) and the toolchain is acquired only on `true`. The queue leg | |
| # never asks, so its output stays empty and it always installs; a | |
| # question that fails leaves it empty too, which also installs. | |
| - name: Does this diff need the generator toolchain? | |
| id: toolchain | |
| if: github.event_name == 'pull_request' | |
| continue-on-error: true | |
| run: node scripts/pm/check-governed-queue-guard.mjs --toolchain-needed >> "$GITHUB_OUTPUT" | |
| # The generators read the whole tree, which the sparse checkout above | |
| # left out; the blobs arrive on demand from the partial clone's remote. | |
| - name: Restore the full tree for the generator recompute | |
| if: github.event_name == 'pull_request' && steps.toolchain.outputs.needed != 'false' | |
| continue-on-error: true | |
| run: git sparse-checkout disable | |
| - name: Setup pnpm | |
| if: steps.toolchain.outputs.needed != 'false' | |
| uses: ./.github/actions/setup-pnpm | |
| continue-on-error: true | |
| - name: Get pnpm store directory | |
| if: steps.toolchain.outputs.needed != 'false' | |
| shell: bash | |
| continue-on-error: true | |
| run: | | |
| echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV | |
| # Deliberately the same key as `.github/actions/setup-workspace`'s: one | |
| # store, one entry. This job only READS it -- it runs on `pull_request` | |
| # and `merge_group` alone, and no run on either event saves the store. | |
| # The chain stays spelled out here rather than calling that composite: | |
| # `check-governed-queue-guard.mjs --self-test` pins every toolchain step | |
| # one by one, each gated on the question above and each degrading on | |
| # its own. | |
| - name: Restore pnpm cache | |
| if: steps.toolchain.outputs.needed != 'false' | |
| uses: actions/cache/restore@v6 | |
| continue-on-error: true | |
| with: | |
| path: ${{ env.STORE_PATH }} | |
| key: ${{ runner.os }}-pnpm-store-v3-${{ hashFiles('**/pnpm-lock.yaml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pnpm-store-v3- | |
| - name: Install dependencies | |
| if: steps.toolchain.outputs.needed != 'false' | |
| continue-on-error: true | |
| run: pnpm install --frozen-lockfile | |
| # The live judgment. Everything it reads arrives through `env:` or the | |
| # event payload on disk — no `${{ }}` interpolation into the shell line, | |
| # so no PR-controlled text ever becomes part of a command. | |
| # | |
| # GITHUB_TOKEN is the workflow's own default token at the read scopes | |
| # declared above; it is what makes the review read possible at all, and | |
| # an unreadable review list is a REFUSAL with its own exit code, never a | |
| # pass. ⛔ Fail-open is wrong in this file specifically — it exists | |
| # because every other layer in this chain failed open. | |
| - name: Governed surfaces may not enter the merge queue unreviewed | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: node scripts/pm/check-governed-queue-guard.mjs |