ceremony/changelog.d/200.md
cluade-reviewer-andresmgsl 634e7a3528
All checks were successful
CI / test (pull_request) Successful in 3m4s
CI / release-exercise (pull_request) Successful in 11s
CI / self-guards (pull_request) Successful in 6s
CI / action-exercise (pull_request) Successful in 6s
CI / docs-sync-exercise (pull_request) Successful in 6s
Refs guard / refs-not-closing (pull_request) Has been skipped
labels / labels (pull_request) Successful in 8s
fix(upstream-delta): the object is mandatory, the scan covers every governed surface, and the teeth drive the real guard (#200)
@codex-reviewer-andresmgsl's five points. Three were correctness, and one of
them found that my must-fail cases could not fail.

1. THE OBJECT IS MANDATORY. UNVERIFIABLE-HERE is gone: a missing ref, an absent
   object and a non-ancestor are three distinct refusals. The ref is now the
   FULL 40-character SHA, and ci.yml fetches exactly that object before the
   suite. "Runs offline" means the TEST reads local evidence; it never meant CI
   may omit the evidence and pass.

2. THE SCAN COVERS WHAT THE INVENTORY CLAIMS. It walked shell under four globs
   and never looked at workflows, .github/labels.conf or drills/ — three
   categories the inventory governs. Widened, and it immediately found four
   real blind spots on merged main: refs-not-closing's declaration,
   labels.yml's inline forge decision, refs-guard.yml's GitHub-only scheduling
   and release-exercise.yml's pinned CEREMONY_FORGE. All four are now inventory
   entries with the issue that removes them, because a delta location with no
   exit is indistinguishable from one nobody noticed. A file that DECLARES a
   client is no longer exempt as a "consumer" — only files that merely CALL the
   shim are.

3. THE TEETH NOW DRIVE THE GUARD. They asserted the predicates separately and
   never invoked no_unlisted, so the guard could have been `return 0` and both
   must-fail rows would still have passed. SCAN_ROOT is a parameter now and the
   cases build a tree, add an unlisted decider — shell AND workflow, so
   coverage cannot regress to the old glob — and assert the real top-level
   check fails naming it. Replacing no_unlisted with `return 0` reds five.

4. PATH MATCHING, NOT PREFIX MATCHING. `drills/` accepted `drills-old/x` and
   `lib/forge.sh` accepted `lib/forge.sh.backup`. Exact for files, `dir/` for
   directories, with both negative boundaries covered.

5. THE IMMUTABLE SHA IS CAPTURED AT FETCH. The runbook now takes
   upstream_sha=$(git rev-parse gh/main) once and merges and records that
   value. This is not hypothetical: while this PR was in review upstream moved
   from 8c3a4d1 to 08e2912, and re-reading gh/main at recording time would have
   written a commit this tree does not contain. I caught that by walking into
   it.

test/run.sh 29/29; upstream-delta 21/21; shellcheck 0.10.0, actionlint,
changelog-armed clean.

Refs #200
2026-08-05 13:51:21 +00:00

2 KiB

Added

  • docs/UPSTREAM-SYNC.md — the recurring upstream sync as a runbook: the standing resolutions, which side wins each and the issue that decided it (#200).

  • It names the step the 0.6.0 sync nearly shipped without: auditing what the merge brought in that did not conflict. git merge asks no question about a function upstream added to a file this tree owns (#200).

  • It records that the same mechanic applies to state, not just to call sites: a resolved region can silently remove a producer whose consumers auto-merged, and every one of those consumers degrades to empty rather than erroring (#200).

  • It says to verify with the runner's tooling, because "green locally" was wrong three times in one sync — untracked files, a pinned linter, and a pinned jq whose empty-input exit code differs (#200).

  • It says every branch open across a sync is stale afterwards — Forgejo never re-tests an open PR when main moves, so a prior approval is evidence about a tree that no longer exists (#200).

  • It says to audit post-merge runs by executed steps rather than colour, and to inventory what the sync changed about workflow triggers and jobs first (#200).

  • .upstream-ref records the upstream commit this tree carries, in machine-readable form beside the CHANGELOG's prose (#200).

  • test/upstream-delta.test.sh fails the PR that scatters forge branching into a file the inventory does not name — across shell, workflows, labels.conf and drills/, not shell alone (#200).

  • It refuses when the recorded commit is missing, absent from the object store, or not an ancestor — three distinct refusals, none of them a skip. ci.yml fetches that exact object so the test reads local evidence without CI omitting it (#200).

  • Its mutation cases drive the real check against a constructed tree, so replacing the guard with return 0 reds five of them (#200).

  • docs/CONSUMERS.md states that two ceremonies answer to the same version number, and how a consumer says which one it pinned (#200).