docs(747): re-verify the Gitea 1.25.4-pinned CI claims on 1.27.1, and measure the merge-gate semantics that were source-attested #867

Merged
timothy merged 6 commits from fix/747-gitea-127-reverify into main 2026-08-28 18:51:14 +02:00
Owner

fixes #747

The instance moved 1.25.4 → 1.27.1 during #743, leaving a set of deliberately version-pinned CI
claims dated rather than wrong. This re-verifies them.

Population derived from the tree, not from the issue. #747 names 9 decision keys. Auditing exactly
those would repeat the defect the issue is about (an issue's file list is not the population), so the
population came from git ls-files — ~21 claim sites across workflows, scripts, tests, docs and records.

Re-confirmed unchanged on 1.27.1

Claim Evidence
an if:-skipped job reports skipped, distinct from success job Build & push image (amd64) on PR #829 / #828 heads
compare/{base}...{head} serves no files keys exactly ['commits','total_commits']
no agent-side cancel route (REST route only) POST .../cancel → 404; swagger has no cancel path
branches: [main] suppresses the run off a non-main base PR #863: 13 pull_request contexts, no review-verdict context

Newly measured, replacing source-attestation

All arms ran on throwaway scratch bases; main's rule was never PATCHed (updated_at still 2026-08-05).

Arm Configuration Admin force_merge, required context absent
A block_admin_merge_override: false 200 — merged
B flip only that field to true 405 — the control making A attributable
C enable_bypass_allowlist: true, empty list 200 — merged

Plus, on an ordinary merge (block_admin_merge_override at its false default): context absent → 405;
posting only that context as success and repeating → 200. So absence blocks an ordinary merge without
needing the override field; the field governs the force path only.

Two consequences: the 1.27 bypass allowlist is not the better-targeted substitute #747 hoped for, and
the PR API reports mergeable: true while such a merge is refused — it does not track required-context state.

Left explicitly dated, each with its reason

  • push-supersession auto-cancel — the cheap disposable-probe route is gone (no workflow triggers on a
    non-main push; main refuses direct pushes). It can still occur via a merge or a v* tag push.
  • the pull_request_target overlap probes — not re-run either.
  • --depth=1 no-merge-base; and ci.actions-credential-scoping's scope enum / reqRepoWriter / 403.
  • review-verdict.yml's three dated comments — #763 is in-progress on that file and its paging work
    overlaps them, so it is deliberately untouched here.

Verification

1083 script tests pass · decisions-validate: OK · all touched records parse under PyYAML · doc-narrative clean.
Five independent adversarial review rounds — 21 → 12 → 9 → 6 → 2 findings, all addressed. Rounds 1–3 each found that the corrections carried new defects, always the same class (prose re-deriving relationships between experimental arms); that was fixed structurally by replacing the narration with the arm table above, after which round 4 verified every table cell and round 5 returned two Low, non-blocking findings and declared it mergeable.
Reviewer independence caveat: Codex was rate-limited throughout, so all rounds were same-model-family
as the implementer rather than cross-family.

fixes #747 The instance moved 1.25.4 → 1.27.1 during #743, leaving a set of deliberately version-pinned CI claims **dated rather than wrong**. This re-verifies them. **Population derived from the tree, not from the issue.** #747 names 9 decision keys. Auditing exactly those would repeat the defect the issue is about (`an issue's file list is not the population`), so the population came from `git ls-files` — ~21 claim sites across workflows, scripts, tests, docs and records. ### Re-confirmed unchanged on 1.27.1 | Claim | Evidence | |---|---| | an `if:`-skipped job reports `skipped`, distinct from `success` | job `Build & push image (amd64)` on PR #829 / #828 heads | | `compare/{base}...{head}` serves no `files` | keys exactly `['commits','total_commits']` | | no agent-side cancel route (**REST route only**) | `POST .../cancel` → 404; swagger has no cancel path | | `branches: [main]` suppresses the run off a non-main base | PR #863: 13 `pull_request` contexts, no review-verdict context | ### Newly measured, replacing source-attestation All arms ran on **throwaway scratch bases**; `main`'s rule was never PATCHed (`updated_at` still 2026-08-05). | Arm | Configuration | Admin `force_merge`, required context absent | |---|---|---| | A | `block_admin_merge_override: false` | **200 — merged** | | B | flip only that field to `true` | **405** — the control making A attributable | | C | `enable_bypass_allowlist: true`, empty list | **200 — merged** | Plus, on an **ordinary** merge (`block_admin_merge_override` at its `false` default): context absent → 405; posting only that context as `success` and repeating → 200. So absence blocks an ordinary merge without needing the override field; the field governs the force path only. Two consequences: the 1.27 bypass allowlist is **not** the better-targeted substitute #747 hoped for, and the PR API reports `mergeable: true` while such a merge is refused — it does not track required-context state. ### Left explicitly dated, each with its reason - push-supersession auto-cancel — the cheap disposable-probe route is gone (no workflow triggers on a non-`main` push; `main` refuses direct pushes). It can still occur via a merge or a `v*` tag push. - the `pull_request_target` overlap probes — not re-run either. - `--depth=1` no-merge-base; and `ci.actions-credential-scoping`'s scope enum / `reqRepoWriter` / 403. - `review-verdict.yml`'s three dated comments — **#763 is in-progress on that file** and its paging work overlaps them, so it is deliberately untouched here. ### Verification 1083 script tests pass · `decisions-validate: OK` · all touched records parse under PyYAML · doc-narrative clean. Five independent adversarial review rounds — 21 → 12 → 9 → 6 → 2 findings, all addressed. Rounds 1–3 each found that the *corrections* carried new defects, always the same class (prose re-deriving relationships between experimental arms); that was fixed structurally by replacing the narration with the arm table above, after which round 4 verified every table cell and round 5 returned two Low, non-blocking findings and declared it mergeable. **Reviewer independence caveat:** Codex was rate-limited throughout, so all rounds were same-model-family as the implementer rather than cross-family.
timothy added 6 commits 2026-08-28 18:25:48 +02:00
The instance moved 1.25.4 -> 1.27.1 during #743, which left a set of deliberately
version-pinned CI claims dated rather than wrong. The population was re-derived from
`git ls-files` instead of from the issue's own 9-key list, which found ~21 claim sites
across workflows, scripts, tests, docs and records.

Re-confirmed unchanged on 1.27.1:
  * an `if:`-skipped job still reports commit-status `skipped`, a distinct state
  * `compare/{base}...{head}` still serves no `files`
  * no agent-side cancel route (POST 404, and live swagger has no cancel path at all)
  * `review-verdict.yml`'s `branches: [main]` still suppresses the run off a non-main base

Re-derived from a constructed corpus rather than a sample, because two arbitrary PRs
showed only two of the values: the PR file-status vocabulary is changed/added/renamed/
deleted over 200 PRs. All four were already in the closed allow-list, so that set is now
evidenced rather than partly presumed.

Newly measured, replacing source-attestation. All three arms ran on throwaway
scratch-base protection rules, so `main`'s rule was never touched (its updated_at is
unchanged at 2026-08-05):
  * `block_admin_merge_override: false` lets an admin force_merge past an ABSENT
    required context (HTTP 200); flipping only that field to `true` refuses the
    identical call (HTTP 405) - the negative control that makes it attributable
  * an absent required context BLOCKS, so the merge gate does not fail open when
    review-verdict.yml never runs
  * `enable_bypass_allowlist` is NOT a better-targeted substitute: enabled with an
    empty username list, the admin force_merge still succeeded
  * trap: the PR API reports `mergeable: true` while that same merge is refused,
    because the field reflects conflict-freedom and ignores branch protection

Left explicitly dated rather than silently refreshed, each with its reason:
push-supersession auto-cancel is no longer observable on this repo (no workflow
triggers on a non-main push, and main refuses direct pushes), and the scope-enum /
reqRepoWriter / write:package-403 items need a scoped PAT rather than a read-only probe.

review-verdict.yml carries three more 1.25.4-dated comments and is deliberately
untouched here: #763 is in-progress on that file and its paging work overlaps them.

Refs: #747
Decisions-Edit: yes
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Round 1 of independent review returned 21 findings. Nearly all were one mechanism:
a conclusion stated more broadly than the single configuration it rested on. Fixed
as a class rather than site by site.

Corrections of fact:
  * ci-image.yml's push trigger is `main` + `docker/ci/**` with deliberately NO
    `tags:` key - it was described as "main plus v* tags", which conflated it with
    docker-build.yml and contradicted ci.toolchain-image-publish-is-a-dispatch
  * push-supersession is not "no longer observable": a PR merge IS a push to main,
    so two merges landing close together still produce it. What is gone is the
    ability to reproduce it ON DEMAND
  * the scope-enum and reqRepoWriter items were established by reading Gitea source
    at the version tag, not by a scoped PAT - only the write:package 403 needs one.
    The invented reason contradicted that record's own Measured-vs-inferred block

Self-contradiction removed: "the gate does not fail open on absence" was stated
unqualified three lines below ARM A, which showed an absent context force-merging
with block_admin_merge_override:false. Now two claims - absence blocks an ORDINARY
merge; only the override field blocks a FORCED one.

Overclaims scoped to their evidence:
  * the 200-PR file-status pass is a large recency-biased SAMPLE, not a constructed
    case; `copied`/`modified` not appearing does not show they are never emitted.
    The prior pass that saw only changed/added was 30 PRs, not 2
  * the cancel-route re-probe covered the REST route and swagger only; the MCP
    cancel_run and web-UI/CSRF findings stay 1.25.4-dated. DELETE /actions/runs/{run}
    was read from swagger, not probed - no claim that it frees a runner slot
  * bypass-allowlist result holds for ONE configuration (empty list, override false);
    a non-empty list was untested, and it cannot distinguish "does not govern admins"
    from "empty list treated as unconfigured"
  * branches:[main] re-confirmed at the STATUS level; the run list was not enumerated
  * `skipped` sampled on two heads (#829, #828), not "every PR head"

Causal claims dropped where only an observation was made: `mergeable` reflecting
conflict-freedom, and the vocabulary delta being caused by sampling rather than by
the version change.

The push-supersession finding is mirrored into ci.verdict-write-retarget-fence, the
record that owns the claim, so a catalog reader does not re-open it next sweep.

Partially declined with evidence: the ~100-col rewrap. The baseline already carries
406/2486 such lines in ci-cd.md and 25/79 in main-direct-push-disabled, so the
premise does not hold; the one genuine outlier (a 432-col insertion) is wrapped.

Refs: #747
Decisions-Edit: yes
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Round 2 reviewed the round-1 fix commit and returned 12 findings. The pattern was
that the CORRECTIONS were themselves new claims, and inherited the same defect they
were fixing.

Two were the same class as round 1, reintroduced by the fix:
  * "the pull_request_target half IS re-measured at 1.27.1" was unsourced. That
    record's only 1.27.1 datum is a timeline-endpoint measurement on PR #752; every
    overlap/serialization probe in it is 1.25.4-era (PR #722, runs 7520/7521,
    7528/7529). #747 did not re-measure it - now says so, in both copies
  * replacing "no longer observable" with "can only arise from two merges to main"
    substituted a new closed set that forgets a ref class: docker-build.yml also
    triggers on `v*` tag pushes, which are outside main's branch protection. The
    honest statement is that the cheap disposable-scratch-branch probe is gone, not
    that the event cannot occur

Control bound to the right arm: the mechanics field attached "posting the context ->
200" immediately after arm B's force-merge 405, so it read as controlling that. The
control was run on PR #863's ORDINARY merge. Arm B's force-merge 405 has no control,
and the record now says so.

Ordinary-merge arm pinned to its field value: it ran on scratch/747-base with
block_admin_merge_override at its false default, which is what makes the claim
well-defined - absence blocks an ordinary merge WITHOUT needing the override field;
the field governs the force path only (arm A false -> merged, arm B true -> refused).
The heading no longer asserts "only the override field blocks a FORCED one" as a
general mechanism.

Incomplete fixes completed:
  * the cancel-route record's TITLE still claimed 1.27.1 for the MCP and web-UI legs
    the body had just marked un-re-run; title and its workflow-process.md index copy
    now scope the re-probe to the REST route
  * pr-changed-files.sh's parenthetical governed a compound sentence including the
    `--depth=1` merge-base clause, which was never re-probed; now split

Also: artifact inventory reconciled (four scratch bases - one ordinary-merge pair plus
three force-merge arms - not "a pair per arm", which implied four arms); the
gate-trigger caveat moved out of the middle of a four-item enumeration it had broken;
a dangling "the 1.25.4-dated note" correcting a belief about a sentence this change
itself deleted; and two negative universals softened to what was observed.

Refs: #747
Decisions-Edit: yes
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Round 3 returned 9 findings and confirmed the pattern held for a third time: the
corrections themselves carried new defects. Three rounds, one mechanism - prose
re-deriving relationships BETWEEN experimental arms, which regenerates a fresh
mis-binding every time it is reworded. Per `fix-the-boundary-not-the-site`, this
removes the mechanism rather than patching a fourth set of sentences.

The five measurements are now a table in release.main-direct-push-disabled: one row
per scratch base, each naming its rule config, merge kind, PR and result. A row is
self-binding, so no wording can attach a control to the wrong arm or invent a
single-field mutation. The prose that remains states only what specific row PAIRS
license, and names the pairs.

Round-3 findings, all addressed:
  * a `v*` tag push is NOT a route to a SUPERSEDED push run - supersession is
    per-ref and a release tag is pushed once. The round-2 correction had swapped one
    wrong closed set for another, and deleted the accurate alternative (adding a
    throwaway trigger) on the way out. Both restored, both copies
  * the body claimed "the control that makes it attributable" for arms A/B while the
    mechanics field, fixed in the same commit, said arm B's 405 has no control. The
    table plus "the pair is the evidence, neither arm alone" replaces both
  * "the IDENTICAL call with only that field flipped" described a single-rule
    mutation that never happened: each arm had its own base, rule, head and PR, and
    arm A merged so its PR could not be re-used. Corrected in all four sites
  * the `mergeable` trap's nearest antecedent was arm C, which returned 200; it is
    now bound to PR #863, the ordinary-merge row
  * round-2's --depth=1 split was applied to pr-changed-files.sh but not to
    ci.exemption-provenance, so the currency claim still reached a clause never
    re-probed. Now split there too
  * "#747 carried out that re-verification across the CI docs" was a completeness
    claim; it was not a corpus sweep. Replaced by an enumeration of what WAS
    re-probed plus an explicit note that other 1.25.4-dated claims remain, including
    the creator-attribution measurement the H10 allow-list rests on
  * three non-blocking: a negative universal about "every other measurement here",
    an on-demand/arrangeable framing tension across two copies, and the one #747
    claim that did not name its artifact

Refs: #747
Decisions-Edit: yes
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Round 4 verified all five table cells against the raw measurements and found the
primary data correct - the structural fix held. Its six findings were prose only,
and the trend across rounds is 21 -> 12 -> 9 -> 6.

The one that mattered: the table's preamble said every row was a SEPARATE scratch
base with its own rule, head and PR. That is false for row 1c, which the table itself
labels "same base/PR" - five rows, four bases - and it contradicted this record's own
mechanics field ("Four SEPARATE"). Worse, taken literally it destroyed the control the
next paragraph rests on: if 1c had its own base and PR, "the single variable between
them is the context" would be false and row 1's 405 would not be attributable. Rows 1
and 1c are now stated as the same base and PR, re-merged after posting the context,
which is exactly what makes 1c a control.

Also fixed:
  * release.main-direct-push-disabled was listed among the 1.25.4-pinned claims #747
    "re-probed". Nothing in that record was 1.25.4-dated - its mechanics opens "Gitea
    1.27.1" and the override claim was source-attested against 1.27, never measured at
    any version. #747 measured it for the FIRST time, which the same paragraph already
    says correctly further down; it was listed twice, once in the wrong bucket
  * residual single-mutation shorthand: it is the RULES of A and B that differ only in
    that field, not the arms, which also differ in base, PR and head
  * mechanics lacked the body's "except the control step" carve-out, so it read as
    claiming the context was never posted on any base
  * "(both measured, #698)" dangled after round 2 split that sentence in two
  * two orphan half-lines left by earlier surgical edits

Refs: #747
Decisions-Edit: yes
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
docs(747): row 1's merge was refused, so 1c re-attempts it rather than re-merging
Build ErsatzTV Image / CI toolchain image resolves (pull_request) Successful in 7s
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 11s
PR Gates / Docs update reminder (pull_request) Successful in 15s
Build ErsatzTV Image / Delimiter ban (release path) (pull_request) Successful in 23s
PR Gates / decisions lifecycle (pull_request) Successful in 16s
PR Gates / Fix proofs (Proves trailers) (pull_request) Successful in 19s
Review verdict / Set review-verdict status (pull_request_target) Successful in 12s
review-verdict/h10 Review-verdict: MERGEABLE @ ab00c6b (base: main)
PR Gates / Script lint and tests (ruff + pytest) (pull_request) Successful in 6m41s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m47s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 6m6s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Skipped
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 6m16s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 9s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 5s
ab00c6bfb8
Round 5 returned two Low findings and declared the change mergeable. Both fixed:
"1c is row 1 re-merged" was literally false - row 1's ordinary merge was REFUSED
with 405, which the table two lines below states, so nothing was merged there to
re-merge. 1c re-attempts the same ordinary merge after posting the context. The
mechanics field already worded this precisely; the body was the looser of the two.

Also joined the 52-col orphan this session's own edit left in
ci.actions-credential-scoping, and rewrapped the paragraph the fix lengthened.

Refs: #747
Decisions-Edit: yes
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Author
Owner

Review-verdict: MERGEABLE @ ab00c6b

Five independent adversarial rounds (21/12/9/6/2 findings), all addressed; round 5 returned two Low non-blocking items, both since fixed. CAVEAT: Codex was rate-limited throughout, so every round was same-model-family as the implementer, not cross-family.

Review-verdict: MERGEABLE @ ab00c6b Five independent adversarial rounds (21/12/9/6/2 findings), all addressed; round 5 returned two Low non-blocking items, both since fixed. CAVEAT: Codex was rate-limited throughout, so every round was same-model-family as the implementer, not cross-family.
timothy merged commit 5fb9c8537a into main 2026-08-28 18:51:14 +02:00
timothy deleted branch fix/747-gitea-127-reverify 2026-08-28 18:51:15 +02:00
Sign in to join this conversation.