Uh oh!
There was an error while loading. Please reload this page.
feat(conformance): the one screen, and self-healing where a PR is honest (backend#1608 items 3+4) - #227
Conversation
…on drift (backend#1608) Item 3 of the harness: "one screen". The matrix has existed since .github#223, but nothing published it when the fleet was GREEN — the drift comment fires only on findings (deliberately: an all-clear per run trains people to ignore the issue, backend#1344), so a conformant fleet produced a step summary on a run nobody opens. "Where does the fleet stand?" was still answered by running the script by hand, which is the thing the ticket set out to remove. Split the two roles: the issue BODY is the current state, rewritten every scheduled/manual run; the COMMENTS stay drift-only and remain the history. The body is rewritten regardless of outcome, which is what makes staleness mean anything. The audit is weekly, so a timestamp older than ~8 days means the audit itself stopped and conformance is UNKNOWN — the backend#1530 cron-watchdog contract. That only holds if a RED run rewrites the body too; otherwise a fleet that broke in January still shows January's green and merely looks stale-ish. Hence always(), not gated on exit_code. Also fixed while here: the drift comment was posting to backend#1415, which is CLOSED. Comments on a closed issue still post, so it looked like it worked — but a closed issue cannot be pinned, drops out of default issue views, and notifies nobody not already subscribed. Drift has been reporting into a drawer. Now backend#1781, open and pinnable. The verdict is DERIVED from the counts rather than from the exit code alone: a clean exit alongside a non-zero unreadable/findings count renders "Inconsistent result — treat as UNKNOWN". The script does not produce that pair today, which is why it is worth pinning — "every repo read" printed directly above "3 unreadable" is the most confidently wrong thing this screen could say. Report heading "Repo conformance drift" -> "Repo conformance": it now renders under a verdict line that is often "Conformant", and a heading asserting drift above a green matrix contradicts itself on the one screen people should trust. Evidence: selftest 123 pass / 0 fail; actionlint and shellcheck clean; the body rendered against a LIVE fleet audit (20/20 evaluated, 0 unreadable, 0 findings); all five verdict paths exercised (clean / drift / unevaluable / empty exit code / inconsistent), and the >60k truncation guard verified to keep the body under GitHub's 65536 limit — a rejected edit would leave the previous body in place, which is a stale green presented as current. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…R is honest (backend#1608) Item 4: the self-healing half. Detection alone still leaves the manual work; it only makes it visible. WHAT IT WILL NOT DO, which is most of the design. The ask was "open the missing-piece PR per repo", but a pull request is the right instrument for exactly one of the five families: copies REMEDIABLE. Byte-identical by definition, and the guard already holds the canonical bytes, so the fix is exact rather than generated. callers NOT generated. Caller content is repo-specific — measured 2026-08-12, all EIGHT sampled repos have a different code-quality-caller.yml because each passes its own toolchain inputs. A generated caller would be a plausible file that is wrong for that repo, which is worse than an absent one that at least reports as a finding. protection, NOT remediable BY PR at all. These are API settings, not required_checks, files in the tree; no commit can change them, so a PR rulesets claiming to fix them would be theatre. And within copies, only entries marked `required`. `divergent` records a written reason why a repo differs — cli pins actions/stale@v11 where canon pins v9, and the newer pin may well be the better one. Silently overwriting a recorded decision would destroy the judgement the inventory exists to hold. Same for `exempt`. Both are reported, never rewritten. Dispatch-only, gated on `github.event_name == 'workflow_dispatch' && inputs .create-prs == true` — the same expression standards-sync.yml uses, whose remediation path this mirrors throughout (422-means-reuse, sha-refresh, PR reuse, actor assignment). Writing to twenty repos is not something a cron may decide to do, and a PR-triggered audit that wrote to the fleet would be a supply-chain hole. Remediation failures go in their OWN list, not `unreadable`: the exit path derives "caller/copy state UNKNOWN" by subtracting the protection and ruleset lists from `unreadable`, so a failed WRITE pushed in there is reported as a failed READ — the wrong diagnosis on the line an operator acts from. They exit 2, because "I tried to fix it and could not" is not the same as "there was drift". Landed on the item-3 branch deliberately: it touches the same two files, so a separate PR would either conflict or be a stacked PR, and the standard forbids stacking. Evidence: selftest 132 pass / 0 fail (up from 123). Five mutations, each caught by its intended case and only that case — enqueue a `divergent` copy (the safety property, caught structurally via AST rather than grep); return None on a failed write; treat any branch-create failure as "already exists"; always send `sha=`; skip the existing-PR check. Against the LIVE fleet: --create-prs reports "nothing to remediate" and writes nothing, and with cli's genuinely-drifted stale-backlog.yml flipped to `required` it selects exactly that one file (blob 4e4246398130 vs canonical 14d689e) — verified with the writer replaced by a recorder, so nothing was written. shellcheck and actionlint clean. The workflow header said REPORT-ONLY and no longer is; updated in the same commit, along with the token scopes that implies. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
LukasWodka
commented
Aug 12, 2026
Item 4 added to this PR (7208ce6)Why here and not a separate PR: item 4 touches the same two files as item 3, so a separate PR would either conflict or be stacked on this one — and the standard forbids stacking. Retitled to cover both. What item 4 will NOT do — most of the designThe ask was "open the missing-piece PR per repo", but a PR is the right instrument for exactly one of the five families:
And within SafetyDispatch-only, gated on Remediation failures go in their own list, not Evidence
The workflow header said REPORT-ONLY and no longer is — updated in the same commit, along with the token scopes that implies. |
ruff E402: the new remediation cases imported ast/inspect/textwrap/os mid-file. Hoisted and the underscore aliases dropped with them. Verified against the pinned toolchain rather than a local one: ruff 0.15.20 with --select E4,E7,E9,F, which is what code-quality.yml runs. My first local pass used a newer ruff with default rules and reported UP037 on pre-existing lines while missing this — the wrong version answering a different question. That is backend#1606 in miniature: `.github` has no `make check`, so there is no local command that runs what CI runs, and the first honest answer arrives red on a PR. Selftest still 132 pass / 0 fail. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
… failure a read failure Two Bugbot findings on #227, both real. 1. THE COMMONEST DISPATCH WOULD HAVE FAILED. remediate_copies read the file on a just-created branch and took a 404 as proof of absence, then issued a sha-less PUT. For a DRIFTED copy the file exists on the base, so that 404 is the eventual-consistency window standards-sync.py already pays for -- and the sha-less write is rejected 422. Fresh branch + drifted file is the single most likely way this feature is ever invoked, and it would have failed every time. Ported _read_head_file's contract as _read_copy_on_head: `remediable` now records, per copy, whether the file exists on the base (drifted yes, missing no). When the base has it AND the ref was just created, a 404 cannot be true -- retry with backoff, fail closed if it never appears. When the base does not have it, one confirming re-read still guards against a single blip. A REUSED branch is deliberately NOT treated as fresh: it may have been cut before the file existed on the base, so its 404 is honest and permanent, and retrying-then-failing-closed would strand that repo forever. That is standards-sync's own #197, avoided rather than rediscovered. Found while fixing it: `branch_is_fresh` was referenced and never assigned. The new test raised it as a NameError rather than a reviewer finding it later. 2. EXIT 2 HAD TWO CAUSES AND ONE VOCABULARY. Remediation failures already exited 2, but every exit-2 message describes unread repos or schema failure, so a dispatch whose PRs failed to open was headlined on the conformance issue as "repos that could not be read are NOT known to comply" -- a true sentence about something that did not happen, sending the reader at the wrong problem. The count is now its own step output and its own verdict line. Evidence: selftest 136 pass / 0 fail (from 132). Two mutations: treat every 404 as absence -> 2 red (both retry cases); treat a reused branch as fresh -> 1 red (the strand-forever case). All four exit-2/1/0 verdict paths rendered and checked. Pinned ruff 0.15.20 --select E4,E7,E9,F clean, actionlint and shellcheck clean. Live re-verify against the fleet still selects exactly cli's drifted stale-backlog.yml, now correctly tagged as present-on-base. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`wd-body.md` is written into the workspace by caller-drift.yml's watchdog step at runtime. It got picked up by a `git add -A` while I was rendering the body locally to test it. Harmless on a runner, but it would have been a checked-in file that looks like a report and is stale the moment it lands. Caught by another session diffing #227's file list against theirs, not by any check here — .gitignore now covers it so the next local render cannot repeat it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…ilures (Bugbot #227) - Remediation-failure verdict no longer claims 'the fleet was read successfully' when UNREADABLE>0 (both counts can be set together); it names the unreadable tally so the headline can't contradict the numbers beside it. - Exit-2 tracking-issue comment and the final fail step now name remediation failure as a cause of exit 2, not just inventory/read failure, so a failed create-prs dispatch isn't misdiagnosed as 'repos could not be read'. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
LukasWodka
commented
Aug 12, 2026
bugbot run |
Uh oh!
There was an error while loading. Please reload this page.
…thub/workflows/ writes (Bugbot #227) The callers create-prs writes live under .github/workflows/, which GitHub refuses to write with Contents:RW alone — it needs the separate 'workflow' scope (classic PAT) or fine-grained Workflows:write. The note claimed the same scopes as standards-sync.yml, but that writes CLAUDE.md, not workflow files. Without this the documented token fails every remediation PUT closed and opens no PR. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
LukasWodka
commented
Aug 12, 2026
bugbot run |
Uh oh!
There was an error while loading. Please reload this page.
…ad repos (Bugbot #227) die() exits 2 for a bad inventory or a failed org enumeration too, where UNREADABLE is 0 — the old code-2 verdict always blamed 'repos that could not be read', misdirecting the reader. Now the base verdict is cause-agnostic and a refinement names the actual cause from the UNREADABLE count (read failure vs a die-path that couldn't run to completion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
LukasWodka
commented
Aug 12, 2026
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 8ec0d8b. Configure here.
The `audit` job was reporting 16 false findings — "add-to-kanban.yml has DRIFTED (blob 603751a vs 3b6d693)" for every repo — and `gate` is a required check, so this PR could not merge. Nothing was wrong with the fleet. `copies:` compares the canonical file from THIS checkout against each repo's state read over the API, so a branch whose checkout predates a copies-file bump reports the whole fleet as drifted from it. The fleet bumped actions/add-to-project v1.0.2 -> v2.0.0 (backend#1816), develop followed at 14:45, and this branch still carried the old blob. Merging develop brings the canonical copy forward; the 16 findings are expected to clear on the next audit run. Worth recording, because it will recur: every in-flight branch in this repo goes red the moment a `copies:` file moves, and the redness is an artifact of the branch's checkout rather than a fleet problem. That is the same asymmetry from the other direction as the ordering hazard on backend#1816 — arm the contract only once the fleet complies, and re-merge in-flight branches when the canonical copy moves. Refs backend#1608, backend#1816 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
shujaatTracebloc
left a comment
There was a problem hiding this comment.
Approving the code. The red is not this PR's — and it will not clear by re-running, so the note that checks were "settling post-#231" needs correcting.
Where the red comes from
audit exits 1 with 16 findings. All sixteen are the same file with the same pair of blobs:
add-to-kanban.yml has DRIFTED (blob 603751a070de vs 3b6d693535ef)
Sixteen repos all sitting on the identical blob is not sixteen repos drifting — it is the hub moving and the fleet standing still. And it did:
#231 merged, bumping canonical add-to-kanban.yml to add-to-project v2.0.0 | 14:45:33Z |
| #227's audit ran and found 16 drifted copies | 14:46:41Z |
68 seconds. #231 changed the canonical copy and repo-inventory.yml and did not propagate the file to the 16 repos that hold copies of it. #227's four files (caller-drift.yml, .gitignore, caller-drift.py, caller-drift-selftest.py) don't touch add-to-kanban.yml at all — it inherited the drift and, because it edits contract files, the gate correctly refuses to let a contract change ride over an unclean audit.
So the gate is working. It is reporting real fleet state that arrived a minute before it looked.
Worth flagging separately: #231 merged through this same gate, red
gh pr checks 231 shows audit fail and gate fail, no override label, merged to develop. So the conformance gate is not enforced as a required check — which means the guard this PR extends can be walked past, and the PR that introduced the drift is what walked past it. That is a bigger finding than anything in this diff and probably wants its own issue.
Ordering, because there's a bootstrap knot
The one-dispatch fix for those 16 copies is the --create-prs path in this PR, and workflow_dispatch inputs are read from the default branch — so create-prs: true isn't dispatchable until this merges. Sequencing that works:
- merge this (the same route #231 took)
- dispatch
caller-drift.ymlwithcreate-prs: true→ one PR per drifted repo,divergent/exemptuntouched - those merge → audit greens → the gate becomes load-bearing again
Doing it the other way means 16 hand-written PRs to unblock the tool that would have written them.
On the diff itself
The remediation boundary is the right call, and the reasoning for it is the part I'd have pushed back on if it were missing: callers measured as eight different files across eight repos means a generated caller is a plausible file that is wrong, which is worse than the absent one that at least reports. And protection/rulesets being API state means a PR claiming to fix them is theatre.
Two things I checked rather than took on trust:
- the
divergent/exemptguard is asserted structurally, by parsingmain()and requiring everyremediablemutation to sit inside thestate == "required"branch — plus a second assertion that the branch does enqueue, so the first can't pass vacuously. A grep for "divergent" near "remediable" would have passed whatever the code did. This is the assertion that matters most, since the failure it prevents is silently overwriting a recorded human decision. - the fresh-vs-reused branch distinction in
_read_copy_on_head.expect_file=on_base and branch_is_freshis the load-bearing half: on a fresh branch a 404 for a drifted copy is provably a lie, so retry-then-fail-closed is right; on a reused branch that 404 is honest and permanent, and failing closed there is what stranded standards-sync (#197). Conflating them would break either the commonest dispatch or every re-dispatch. Both directions are pinned.
Exit 2 for remediation_failures with its own output rather than folding into unreadable is also correct — "could not write" reported as "could not read" sends the operator to the wrong problem.
Two nits, neither blocking
scripts/caller-drift.py—remediable: "dict[tuple[str, str], list[str]]"is wrong; the code appends(copy_name, bool)andremediate_copiesdeclareslist[tuple[str, bool]]. Should bedict[tuple[str, str], list[tuple[str, bool]]]..gitignore—wd-body.mdandbody.mdare unanchored, so they match at any depth. Both are written at the workspace root;/wd-body.mdand/body.mdavoid silently swallowing a legitimately committedbody.mdlater.body.mdis a generic enough name to be worth anchoring.
Uh oh!
There was an error while loading. Please reload this page.
…be read" (#242) Bugbot Medium on the staging promotion PR #238. `unreadable` is the MERGED list -- protection and ruleset read failures are folded into it (caller-drift.py:2000-2001). The watchdog headline read that count as repos that were never read, so a clean caller/copy/quality audit with a single failed protection or ruleset read was announced as "N repo(s) could not be read and are NOT known to comply": a true count under a false name, sending the reader to look for repos nobody had touched. The script already decomposes this correctly for its own report and issue body (the `caller_failed = len(unreadable) - protection - ruleset` split at :2035 and :2152). Only the workflow consumed the merged number. So this exports the split that already existed rather than inventing one -- same move as `remediation_failures`, which got its own output for exactly this reason (#227). Adds `caller_unreadable`, `protection_unreadable` and `ruleset_unreadable` outputs; the watchdog composes them into one clause naming each failing read type, falling back to the merged count so a future read type cannot produce an empty sentence. FIXED IN BOTH PLACES. The "Remediation failed" verdict carried the same wording, so the phrase is now built ONCE above the verdicts and quoted twice -- fixing only the reported line would have left its sibling saying "repo(s) could not be read", which is the exact half-fix this repo keeps re-learning. Verified by running the composed shell over every shape: caller-only -> "3 repo(s) could not be read (caller/copy state UNKNOWN)" protection-only -> "2 branch-protection read(s) failed (protection state UNKNOWN)" ruleset-only -> "1 ruleset read(s) failed (ruleset state UNKNOWN)" all three -> the three clauses, joined split absent -> "7 read(s) failed" (fallback) caller-drift selftest 162 pass / 0 fail; actionlint clean; ruff clean.
The gap
Item 3 of the harness is "one screen". The matrix has existed since #223 — but nothing published it when the fleet was green.
The drift comment fires only on findings, and that is deliberate: an all-clear per run trains people to ignore the issue (backend#1344, five silent failures nobody read). The unintended consequence is that a conformant fleet produced a step summary on a run nobody opens. "Where does the fleet stand?" was still answered by running the script by hand — the exact thing the ticket set out to remove.
The fix: split the two roles
The body is rewritten regardless of outcome, and that is what makes staleness mean anything. The audit is weekly, so a timestamp older than ~8 days means the audit itself stopped and conformance is UNKNOWN — the backend#1530 cron-watchdog contract. That only holds if a red run rewrites the body too; otherwise a fleet that broke in January still shows January's green and merely looks stale-ish. Hence
always(), not gated onexit_code.Two bugs found while building it
1. The drift comment was posting to a closed issue. backend#1415 is
CLOSED. Comments on a closed issue still post, so this looked like it worked — but a closed issue cannot be pinned, drops out of every default issue view, and notifies nobody not already subscribed. Drift has been reporting into a drawer. Now backend#1781, open and pinnable.2.
WATCHDOG_ISSUEwas defined in one step'senv:and used in another, where it is undefined — underset -uthat step dies. Loud rather than silent, but the drift comment would never post. Hoisted to job level, which is also this repo's stated rule for inputs reaching the shell.The verdict is derived, not asserted
A clean exit code alongside a non-zero unreadable/findings count now renders "Inconsistent result — treat as UNKNOWN". The script does not produce that pair today, which is exactly why it is worth pinning:
✅ every repo readprinted directly above3 unreadableis the most confidently wrong thing this screen could say, and the headline should stay true if the exit-code contract ever changes.Also: report heading
Repo conformance drift→Repo conformance. It now renders under a verdict line that is often "Conformant", and a heading asserting drift above a green matrix contradicts itself on the one screen people are meant to trust.Evidence
actionlintandshellcheckcleanevaluated=20, unreadable=0, findings=0After merge
backend#1781 still reads "Awaiting first sweep". Trigger Actions → Caller inventory drift → Run workflow once to populate it immediately rather than waiting for Monday — that also proves the
issues: writepath works with the real token, which local rendering cannot.Parent: backend#1608 (item 3). Remaining after this: item 4, the
create-prsself-healing dispatch.Note
Medium Risk
Manual dispatch can push workflow files across the org (needs Contents/PR/workflow token scopes), but scope is limited to required copies and failures fail closed; issue-body rewrites affect the fleet visibility surface on every weekly run.
Overview
Adds the org conformance “one screen” and optional self-healing for canonical workflow copies, extending
caller-driftbeyond report-only drift detection.On every scheduled or manual audit (not PR/push), a new step rewrites backend#1781’s issue body with timestamp, counts, a derived verdict (conformant / drift / unevaluable / inconsistent / remediation failed), and the full matrix. Drift still lands as comments only on failure. Drift reporting moves from closed #1415 to open #1781;
WATCHDOG_ISSUEis job-level so both reporting steps see it underset -u. Bodies truncate before GitHub’s size limit so a failed edit cannot leave a stale green matrix.Manual dispatch gains a
create-prsinput (explicitworkflow_dispatchonly—never cron/PR). When enabled,caller-drift.py --create-prsopens/refreshes PRs that realign inventoryrequiredcopies only;divergent/exemptare never rewritten. Remediation uses branch reuse, content PUTs with correct SHAs, retries for fresh-branch 404s, and exit 2 +remediation_failuresoutput when writes fail (separate from unreadable repos). Report heading becomes “Repo conformance” so green runs don’t say “drift” above a clean matrix.Selftests add AST checks that only
requiredcopies enter remediation plus stubbedremediate_copiespaths;.gitignoreignores workflow scratchwd-body.md/body.md.Reviewed by Cursor Bugbot for commit ba8ea38. Bugbot is set up for automated code reviews on this repo. Configure here.