0dcf5cc1369fe48fca6ceb9b620da3cdf5535a46
2
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
0dcf5cc136 |
feat(pr-gatekeeper): review a staged diff pre-push, for GitOps repos with no PR
infra#470 resolved a doctrine conflict: root rule 5 wants a PR for risk/medium+, and mathias/infra forbids branches because Flux watches main — a branch there can be linted but never reconciled, so a PR reviews a state that was never deployed. The agreed lane is that such work stays on main and gets a recorded adversarial review pass BEFORE the push. That lane names this agent as its reviewer, and the definition only knew how to review an open PR. A lane whose reviewer cannot see the diff resolves to "the author was careful", which is what rule 5 was written to replace. ONE AGENT, TWO MODES — NOT A SECOND FILE Only three of twelve sections were PR-specific: the diff command, the CI step, and the verdict. Everything else — the iron laws, "the test is the claim", the three attack habits, probing without causing the incident, escalation — is diff-agnostic. Forking a near-duplicate 226-line definition would give the estate two implementations of one review standard, and they would drift; the same argument tests/litellm-validate_test.sh makes about its own consolidation. WHAT DIFFERS, AND WHY - Diff: three commands, run all of them. Which one holds the change is itself information — work that is neither staged nor committed is a working tree, not a diff under review. Untracked files appear in neither diff, so `git status --porcelain` is how a control that exists only in an unstaged file gets noticed. - CI: it CANNOT have run, nothing is pushed. So "CI absent" must not block here — that rule is for a pushed branch with no runs, which is a platform failure; unpushed with no runs is arithmetic. Instead run the local gate and check its EXIT CODE, because a grep over gate output has hidden a failing test in this estate before. - Verdict: nothing to merge, no PR to close, no branch to delete. The entire output is a comment on the issue. That is what makes the lane RECORDED rather than a conversation — a verdict living only in a chat transcript is indistinguishable from no review having happened. The report must state that it carries strictly less external evidence than PR mode: "nothing I could find blocks this push", never "CI confirms this". TDD, and one vacuous assertion caught Five assertions written first, all red. One passed spuriously — an unscoped grep for "do not merge" matched line 202, the ESCALATION step talking about a PR. A pass for entirely the wrong reason, which is what rule 9 is about. Now scoped to the pre-push section, and the comment records why, so nobody re-loosens it. agents_test 19/19. Repo check exit 0, 45 skills, manifest matches tag. Refs mathias/infra#470 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c25175e4b3 |
feat(agents): ship pr-gatekeeper from the repo, not one host's ~/.claude (#51)
pr-gatekeeper blocked dispatch#75 five times. Every block was a real,
executable escape — twelve pathspec bypasses, arbitrary command
execution via `git -c alias`, `--upload-pack='rm …'`, a write primitive
through `--output=`, `${IFS}` word-splitting past the policy, and a path
denylist that had been a no-op in the live flow since it was written.
task check was green on all five.
The agent definition existed in no repository. A code search across the
estate found only references to it — comments in dispatch source, a
local-dev sprint doc — while the file itself lived at
~/.claude/agents/pr-gatekeeper.md on koala alone: unversioned, on one
host, one rm from gone. local-dev AGENT.md rule 8 now requires routing
control code through it, so doctrine depended on an artifact with no
history.
PORTED, PLUS WHAT THE FIVE ROUNDS ACTUALLY TAUGHT
The file already had flip-testing. Three habits that did the finding were
implied at best, and are now explicit (§4b):
- Verify by EXECUTION, not by reading the implementation. Every finding
came from running the command in a scratch repo and looking at the
filesystem. The matcher said one thing and the filesystem another,
five times.
- Read the SURVIVORS. After an allow/deny sweep, read what is still
permitted, one at a time. This caught `git commit -F /etc/passwd` —
exfiltration wearing a message flag — which neither the review nor
the differential found, because both implementations agreed and both
were wrong. Agreement is not correctness.
- Grade a tool on its EXIT CODE, never by grepping output. A crashed
linter greps as clean.
Plus rule 9 in §4: a test whose name claims more than it proves is worse
than no test, because the name wins in the reader's head.
AND THE INCIDENT THIS AGENT CAUSED (§4c)
On 2026-09-19 a probe of this agent's own making printed /proc/$PPID/environ
and `ps e` raw, and a `git credential fill` consulted the host's real
store. Seven live credentials reached the transcript and need rotation
(infra#445). The finding was correct; the method was not. Versioning the
file without that lesson would have shipped the incident along with the
agent. So: a probe that reads an environment prints a count, never the
buffer; invented sentinels only; isolated HOME for anything touching
git's credential subsystem; prove a primitive and stop.
DOCTOR
New `shadow` check: a real file in ~/.claude/agents with the same name as
a shipped agent wins over the shipped one and nothing says so. Same
silent-drift shape as the existing `outside` and `unmanaged` checks — it
works, so nothing complains, while serving content nobody updates. A
symlink is managed wiring and is not flagged.
The host-local copy on koala has been moved aside, so the shipped file is
what runs once the plugin updates.
TESTS
14 assertions, each named for what it observes rather than what it
guarantees — a grep proves a phrase is present, not that a procedure is
followed, and naming it otherwise would be the defect rule 9 exists for.
Flip-tested both directions: removing the doctor check, and altering the
probe-safety phrase, each turn the suite red.
`agents` added to task check's root-entry allowlist. Leaving it out made
check_tolerates_unverifiable_test.sh fail, which is #23's stray-entry
guard working exactly as designed — an undeclared root entry loads in no
harness and errors in none either.
Closes #51
Bump-Type: minor
|