EPIC B · #3308 · 2026-08-10

The gate against "never fires" was never fired.

The 2026-08-08 audit found one defect thirty-four times: OrchestKit writes to CC contracts nobody verified against the binary. The fix — a script that derives the contract from the binary — was written, and is good. Grepping all 31 workflows for it returned zero hits. Nothing ever ran it.

The chain, and where it breaks

derive-cc-output-keys.mjs  ──reads──▶  the shipped binary
          │
          └──writes──▶  src/hooks/bin/cc-output-keys.generated.mjs
                              │
                              └──CI validates ork against this──▶ green

nothing re-runs step one. CI has been checking OrchestKit against a
snapshot no process refreshes — correct only for as long as it
happens to still be current.

Why a floor-pinned host would have been worse than nothing

The check is missing-only, so an old binary passes vacuously

derive-cc-output-keys.mjs:141
  binaryEvents.filter(e => !allowed.has(e))

It fails only when the binary names an event the allow-list lacks. An older binary names a subset, so the difference is empty and the gate prints OK. Every other CC install in this repo is pinned to the support floor — correct for reproducible jobs, fatal here. A floor-pinned host would not merely miss newer drift; it would report a pass it did not earn, which is the exact failure the audit was about.

So this probe installs the newest published release and accepts that its version moves underneath it. That is the point, not a compromise.

Five candidate hosts, all rejected

workflowwhy not
plugin-validation.ymlinstalls CC, but the job is continue-on-error: true — a drift exit would be swallowed. Also floor-pinned.
claude-release-watch.ymlinstalls CC, but its header forbids automatic triggers, and it is structurally unschedulable: its text matches the LLM-in-CI guard's signature list.
contract-parity.ymlsemantically right, installs no binary, no schedule.
hook-contract.ymlpath filters can never match the derive script; no dispatch trigger either.
cc-support-window-bump.ymlfires only after a human has already recorded the new version in-repo.

Hence a new file, modeled on labs-version-probe.yml — which exists for precisely this reason: that guard is file-scoped and textual, so a scheduled job must live in a file that does not quote the pattern. This one describes the policy strictly by reference.

Failure is loud here, unlike the precedent

exit 0   OK              every event the binary names is in the allow-list
exit 1   DRIFT           fails the job — hooks are writing to a contract
                         the binary contradicts
exit 2   CANNOT-OBSERVE  fails the job — the binary was not found, so the
                         gate saw nothing

labs-version-probe.yml deliberately never fails its nightly, because pin drift is informational. Both exits here are actionable, and exit 2 especially: a contract gate that cannot observe must never look green.

The open question this run answers

Nobody knows if the binary lands where the script looks

findBinary() searches only ~/.local/share/claude/versions/<x.y.z>. No workflow references that path, and whether the npm install populates it on a GitHub runner was not determinable from the repo. If it does not, this gate returns CANNOT-OBSERVE forever — a gate that never fires, shipped while fixing gates that never fire.

So the layout step prints the directory listing unconditionally, before the check runs. A CANNOT-OBSERVE result therefore names the actual layout instead of leaving the next person to repeat the experiment.

Two bugs caught before shipping

1. Actions runs `run:` with bash -e, so a non-zero exit would kill the step
   BEFORE the summary was written — a red X with no explanation of which
   failure it was. Fixed with `|| rc=$?`, and the comment says why so the
   next editor does not "simplify" it back.

2. A `case` with duplicated patterns and `;&` fallthrough. Replaced with
   plain if/elif.

Verification

actionlint .github/workflows/cc-contract-probe.yml   exit 0
   (first pass flagged SC2129 — individual redirects; fixed to one block)

bash tests/unit/test-no-llm-in-ci.sh
   PASS: all 7 LLM workflow(s) are manual-only (46 scanned)
   ← this file is scanned and NOT classified as an LLM workflow,
     so its schedule is legal

What remains unproven, deliberately. This PR cannot verify that the runner populates the lookup path, because workflow_dispatch only becomes available once the file is on the default branch. The honest sequence is merge, dispatch, read the layout summary, and fix the install or the lookup if it reports CANNOT-OBSERVE — rather than asserting here that it works. If the first scheduled run fails that way, that is the gate doing its job: it is designed to be loud about not being able to see, which is the one thing the thirty-four defects had in common.