From 9366754bd004d4d4f981673685739ee1c04ff0cd Mon Sep 17 00:00:00 2001 From: Qwynn Marcelle Date: Wed, 12 Aug 2026 20:37:48 -0400 Subject: [PATCH 1/3] ci(meta-337): make a missing parity receipt fail instead of skipping to green MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `parity-receipt-reproduction` gated both of its substantive steps on `steps.check-receipt.outputs.has_receipt == 'true'`. On a branch with no committed receipt the two steps skipped, the job concluded `success`, and the log said "skipping reproduction check" — a green result that had reproduced nothing. That is tolerable while the job is advisory. It is not tolerable once the check is required, which META-337 intends: at that point the cheapest way to satisfy a failing parity gate is to delete the receipt, and the gate would reward it. "Missing is not green" has to hold for the gate itself, not only for the product code it inspects. Absence of the receipt is now a hard failure, and the two reproduction steps run unconditionally. The failure output names the invariant, the authority, the evidence that discharges it, and what must not be weakened to recover green, so an implementation agent can act on it without a human relay. This changes CI only. No product code, no packaging, and no adapter behavior is touched. Refs META-337. --- .github/workflows/ci.yml | 45 ++++++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 44b3446..5954390 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,24 +77,51 @@ jobs: - name: Install dependencies run: npm ci - - name: Check for committed parity receipt - id: check-receipt + # The receipt must be present, not merely checked for. + # + # This step previously set `has_receipt=false` and let the two steps below + # skip, so a branch with no committed receipt produced a **green** job that + # had reproduced nothing. That is survivable while the job is advisory, but + # it becomes a merge-authorizing false green the moment the check is + # required: the cheapest way to satisfy a failing parity gate would be to + # delete the receipt. Absence is now a failure. + - name: Require a committed parity receipt run: | - if [ -f docs/migration/parity-receipt.json ]; then - echo "has_receipt=true" >> $GITHUB_OUTPUT - else - echo "has_receipt=false" >> $GITHUB_OUTPUT - echo "No committed parity receipt found — skipping reproduction check." + set -euo pipefail + if [ ! -f docs/migration/parity-receipt.json ]; then + echo "::error file=docs/migration/parity-receipt.json::Missing committed parity receipt" + { + echo "## parity-receipt-reproduction failed" + echo + echo "**Invariant.** The committed parity receipt at" + echo "\`docs/migration/parity-receipt.json\` is reproducible from the source" + echo "it names." + echo + echo "**Why it matters.** This job exists to prove the committed receipt is" + echo "not a stale artifact. With no receipt there is nothing to reproduce, and" + echo "a green result would assert a reproduction that never ran. Missing is" + echo "not green." + echo + echo "**Authority.** Repository CI, reported by \`github-actions\` as" + echo "\`parity-receipt-reproduction\`." + echo + echo "**Evidence required.** Either restore the committed receipt, or — if the" + echo "migration it records is genuinely retired — remove this job in the same" + echo "change that removes the receipt, so the deletion is a reviewable" + echo "decision rather than a silent loss of coverage." + echo + echo "**Do not weaken.** Do not restore the previous skip-if-absent behavior" + echo "to recover green." + } >> "$GITHUB_STEP_SUMMARY" + exit 1 fi - name: Reproduce parity receipt - if: steps.check-receipt.outputs.has_receipt == 'true' run: | TARGET_SHA=$(node -p "require('./docs/migration/parity-receipt.json').refs.targetSha") node scripts/migration/verify-clone-parity.mjs --target-ref "$TARGET_SHA" --out .parity-ci - name: Compare CI receipt against committed reference - if: steps.check-receipt.outputs.has_receipt == 'true' run: | node scripts/migration/verify-receipt.mjs \ docs/migration/parity-receipt.json \ From 35d11d6af68ebaf5a545415fd6dd139ba47b3fcb Mon Sep 17 00:00:00 2001 From: Qwynn Marcelle Date: Wed, 12 Aug 2026 21:09:10 -0400 Subject: [PATCH 2/3] ci(META-337): pin the receipt gate to bash explicitly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The step's failure semantics depend on `set -euo pipefail`. GitHub-hosted runners default `run:` to bash today, but that is an implementation detail, and this step exists precisely to stop a gate from passing without doing work — resting its own strictness on an undeclared default is the same bet the change removes. Addresses the Sourcery review note on the step. --- .github/workflows/ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5954390..d418673 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -86,6 +86,11 @@ jobs: # required: the cheapest way to satisfy a failing parity gate would be to # delete the receipt. Absence is now a failure. - name: Require a committed parity receipt + # Explicit, because the script below depends on `set -euo pipefail`. + # Runners default to bash today, but that is an implementation detail, + # and a gate whose failure mode is "silently did not fail" should not + # rest on one. + shell: bash run: | set -euo pipefail if [ ! -f docs/migration/parity-receipt.json ]; then From 37d4c1d7d81cf8074ff452f6e30bad6549edbc4f Mon Sep 17 00:00:00 2001 From: Qwynn Marcelle Date: Wed, 12 Aug 2026 21:14:16 -0400 Subject: [PATCH 3/3] docs(review): correct the required-context table against the live API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The §2 table claimed three required contexts. The API returns five — `standard-candidate-consumption` and `SonarCloud Code Analysis` were promoted without the change reaching this file. This is the case §2 already legislates for ("the API is right and this file is stale — treat that as a documentation defect, and correct it in the PR that noticed"), exercised for the first time. The drift is its own argument: the stale table said `SonarCloud Code Analysis` was non-blocking, so a reader trusting it would have concluded PR #12 was mergeable while it was blocked on exactly that context. Corrected: - §2 required-context row and the sentence restating it. - §2 "Observed gap" — `standard-candidate-consumption` is now required, leaving `parity-receipt-reproduction` as the only CI job whose failure does not block. That is this PR's subject, so the section now names the dependency rather than describing a gap that has since narrowed. - §2 and §4 PR #12 observations re-dated to the protection in force when they were taken. They are kept, not rewritten — the mechanism they demonstrate is unaffected, only the non-blocking claim expired. - Provenance line re-measured: 2026-08-13 against `main` at `70cfd57`. No protection setting was changed by this commit. It only makes the file agree with what the API already returns. --- docs/review/merge-policy.md | 61 ++++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/docs/review/merge-policy.md b/docs/review/merge-policy.md index f5f5049..b56b6d1 100644 --- a/docs/review/merge-policy.md +++ b/docs/review/merge-policy.md @@ -56,7 +56,7 @@ Values below were read from the GitHub branch-protection API for `main`. | Setting | Measured value | | --- | --- | -| Required status checks | `build-and-smoke (20)`, `build-and-smoke (22)`, **`Greptile Review`** | +| Required status checks | `build-and-smoke (20)`, `build-and-smoke (22)`, **`Greptile Review`**, `standard-candidate-consumption`, `SonarCloud Code Analysis` | | `strict` (branch must be up to date) | `true` | | `required_conversation_resolution` | `true` | | `required_approving_review_count` | `0` | @@ -65,9 +65,10 @@ Values below were read from the GitHub branch-protection API for `main`. | `allow_force_pushes` / `allow_deletions` | `false` / `false` | | Repository rulesets | none (`[]`) | -So a merge into `main` requires: the two `build-and-smoke` contexts and -`Greptile Review` green on an up-to-date head, and **every conversation resolved**. -No review approval is required. +So a merge into `main` requires: the two `build-and-smoke` contexts, +`Greptile Review`, `standard-candidate-consumption`, and `SonarCloud Code Analysis` +green on an up-to-date head, and **every conversation resolved**. No review approval +is required. `Greptile Review` was added to the required contexts on 2026-08-12 after the calibration in [`calibration-2026-08.md`](calibration-2026-08.md), and is pinned to @@ -81,26 +82,32 @@ on a head carrying a P1 finding. The semantic half of the gate is two are only meaningful together, and neither is a substitute for reading the findings. -This was observed working end to end on PR #12 at head `76d495d`: all three required -contexts `success`, `SonarCloud Code Analysis` **failing but not required and -therefore not blocking**, and `mergeStateStatus=BLOCKED` on a single unresolved -Greptile P1 thread. +This was observed working end to end on PR #12 at head `76d495d`, **under the +three-context protection in force on 2026-08-12**: all three required contexts +`success`, `SonarCloud Code Analysis` failing but not required and therefore not +blocking, and `mergeStateStatus=BLOCKED` on a single unresolved Greptile P1 thread. -### Observed gap — recorded, not acted on here +That observation is kept as the record of the mechanism, not as a description of +current state: `SonarCloud Code Analysis` has since been promoted, and the same +failure on the same PR blocks today. What the observation still shows — that status +and conversation resolution are separate halves of the gate — is unaffected. -CI produces four check runs, but only two are required: +### Observed gap — narrowed since first recorded + +CI produces four check runs. Three are required: | Check run | Required? | | --- | --- | | `build-and-smoke (20)` | yes | | `build-and-smoke (22)` | yes | -| `parity-receipt-reproduction` | **no** | -| `standard-candidate-consumption` | **no** | +| `standard-candidate-consumption` | yes — promoted since first recorded | +| `parity-receipt-reproduction` | **no** — promotion is META-337 | -A failure in either unrequired job does not block a merge today. That is a separate -governance decision from the reviewer question this document's owner issue covers, -and it is recorded here so it is not mistaken for a setting someone already chose -deliberately. Changing it is out of scope for META-322. +`parity-receipt-reproduction` is the one CI job whose failure still does not block a +merge. Promoting it is META-337's subject, and this PR is its prerequisite: the job +could conclude `success` without reproducing anything, so requiring it first would +have made a false green merge-authorizing. Absence is now a failure, which is what +makes the promotion safe to make. ### Refreshing this section @@ -118,11 +125,21 @@ is stale — treat that as a documentation defect, and correct it in the PR that noticed. A merge-eligibility claim that has drifted from the setting it describes is worse than no claim, because it will be believed. -Measured on 2026-08-12 against `main` at `f61e0cb`, immediately after -`Greptile Review` was added to the required contexts. The earlier reading in -[`calibration-2026-08.md`](calibration-2026-08.md) — `main` at `a31242b`, two -required contexts — is the pre-policy baseline and is kept there as the *before* -half of the record, not as a description of current state. +Measured on 2026-08-13 against `main` at `70cfd57`. The two preceding readings are +kept as the *before* halves of the record, not as descriptions of current state: +`main` at `f61e0cb` on 2026-08-12 (three required contexts, immediately after +`Greptile Review` was promoted), and `main` at `a31242b` in +[`calibration-2026-08.md`](calibration-2026-08.md) (two required contexts, +pre-policy baseline). + +The 2026-08-13 reading was taken because this section had already drifted: it +claimed three required contexts while the API returned five — +`standard-candidate-consumption` and `SonarCloud Code Analysis` had been promoted +without the change reaching this file. Corrected here under the rule directly above, +which is the first time that rule has been exercised. The drift is itself the +argument for the rule: the stale table said `SonarCloud Code Analysis` was +non-blocking, and a reader trusting it would have concluded PR #12 was mergeable +while it was in fact blocked on exactly that context. ## 3. Conversation resolution @@ -187,7 +204,7 @@ First post-policy proof, PR #12 (META-285) at head `76d495d`, base `f61e0cb`: | --- | --- | | `build-and-smoke (20)` / `(22)` | success / success | | `Greptile Review` | success — `5 files reviewed, 0 comments added` on this head | -| `SonarCloud Code Analysis` | **failure — not a required context, did not block** | +| `SonarCloud Code Analysis` | **failure — not a required context *at that time*, did not block. It is required as of the 2026-08-13 reading in §2, and the same failure blocks today** | | `Sourcery review` | success (not required) | | Unresolved threads | 1 — a Greptile P1 on `scripts/migration/verify-receipt.mjs` | | `mergeStateStatus` | **`BLOCKED`** |