Skip to content

fix(service-queue): compare the publish idempotency window as instants, not strings #3429

fix(service-queue): compare the publish idempotency window as instants, not strings

fix(service-queue): compare the publish idempotency window as instants, not strings #3429

# 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:
branches:
- main
# 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.
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
timeout-minutes: 10
steps:
- name: Checkout repository
uses: actions/checkout@v7
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
# Pinned to the same major and spelling as every other setup-node in this
# repo, and it keeps its literal `node-version` (rather than
# `node-version-file`) for the measured reason the Part-of Closing-Keyword
# Guard records: `actions/setup-node@v5`+ defaults
# `package-manager-cache: true`, which shells out to pnpm and hard-errors
# before any `run:` line when pnpm is not yet on PATH. This job now DOES
# acquire pnpm, but strictly after this step — the same order lint.yml has
# been green on for months — so nothing here may start depending on pnpm
# existing yet.
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: '22'
# 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. 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.
- name: Setup pnpm
uses: ./.github/actions/setup-pnpm
continue-on-error: true
- name: Get pnpm store directory
shell: bash
continue-on-error: true
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
# Deliberately the same key as lint.yml's: one store, one entry, and this
# job is a cache READER in the steady state rather than a second writer.
- name: Setup pnpm cache
uses: actions/cache@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
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