docs(decisions): D24 says "Patchwarden never approves" but #129 shipped a guarded contract_publish APPROVED adapter — reconcile the authority record + clarify approver identity #132

Closed
opened 2026-06-23 19:50:24 +02:00 by claude · 0 comments
Collaborator

The inconsistency (governance-doc ↔ shipped code)

The canonical authority record now disagrees with itself and with the code. Patchwarden's entire value is the precision of its authority boundary, so this matters more here than in an ordinary repo.

  • D24 (docs/decisions.md, D24 bounds): "D20 intact: Patchwarden never merges/approves; it emits the verdict + handoff. The controller acts; Patchwarden vouches." and "it is not Patchwarden merging itself."
  • D20 (detail section): "The only APPROVED adapter belongs to src/patchwarden/contract_publish.py after a passing deterministic Contract Run and explicit operator workflow opt-in … D20 lint confines that surface to contract_publish.py + forgejo_client.py and still bans merge endpoints."
  • Shipped (#129): publish-contract --execute --approve-on-pass calls client.ensure_pr_approval(...) → Patchwarden's own CLI posts a visible APPROVED review (guarded by the controller_approval verdict).

So D24 asserts "Patchwarden never approves," while D20-detail + the #129 code say "the deterministic contract_publish adapter may post a guarded APPROVED review, opt-in." Both can't be literally true.

The deeper question this surfaces (the real architectural one)

In publish-contract --approve-on-pass, whose identity posts the visible APPROVED review? The approval is written via FORGEJO_TOKEN.

  • If that token is patchwarden-bot (id9) → the "controller acts; Patchwarden vouches" separation in D24 has effectively collapsed: Patchwarden is the visible approver. That's a real evolution of D20, not a wording nit.
  • If the token is a separate controller identity (ClawSweeper / codex-controller) → the separation holds, and contract_publish is just the mechanism the external controller drives.

The decision record doesn't say which, so a reader can't tell whether the D20/D24 sensor-vs-actor invariant actually survived #129. (no-self-approval only guarantees approver ≠ PR author — it does not guarantee approver ≠ Patchwarden.)

Proposed reconciliation (operator to ratify; ~edit is small)

Clarify D24 (and/or a short D26) to state the actual four-way boundary precisely:

  1. Merge — never, by anyone called Patchwarden; D20-lint enforced. (unchanged)
  2. Reviewer-lane approval — never; lanes are sensors. (unchanged)
  3. Deterministic contract_publish approvalpermitted under guard: opt-in (--approve-on-pass), passing exact-head Contract Run, ready zero-blocker controller_approval, no-self-approval, non-hard-manual, fail-closed.
  4. Visible-controller approval by an external actor (ClawSweeper/OpenClaw consuming the verdict) — the target end-state (PW-G012, still partial).

And state explicitly which identity posts the #3 approval, and whether #3 is the intended steady state or a transitional dogfood mechanism on the way to #4. If #3 posts under patchwarden-bot, either (a) amend D24's "never approves" to "never merges; approves only via the guarded deterministic adapter," or (b) require #3 to run under a non-Patchwarden controller identity to preserve the separation.

Not urgent / nothing is broken in the code — the guards are sound (verified in #127). This is about keeping the canonical authority record honest now that real approval power shipped. — claude (architect loop)

## The inconsistency (governance-doc ↔ shipped code) The canonical authority record now disagrees with itself and with the code. Patchwarden's entire value is the *precision* of its authority boundary, so this matters more here than in an ordinary repo. - **D24** (`docs/decisions.md`, D24 bounds): *"**D20 intact**: Patchwarden never merges/approves; it emits the verdict + handoff. The controller acts; Patchwarden vouches."* and *"it is **not** Patchwarden merging itself."* - **D20** (detail section): *"The only `APPROVED` adapter belongs to `src/patchwarden/contract_publish.py` after a passing deterministic Contract Run and explicit operator workflow opt-in … D20 lint confines that surface to `contract_publish.py` + `forgejo_client.py` and still bans merge endpoints."* - **Shipped (#129)**: `publish-contract --execute --approve-on-pass` calls `client.ensure_pr_approval(...)` → Patchwarden's own CLI posts a **visible APPROVED review** (guarded by the controller_approval verdict). So D24 asserts "Patchwarden never approves," while D20-detail + the #129 code say "the deterministic `contract_publish` adapter *may* post a guarded APPROVED review, opt-in." Both can't be literally true. ## The deeper question this surfaces (the real architectural one) In `publish-contract --approve-on-pass`, **whose identity posts the visible APPROVED review?** The approval is written via `FORGEJO_TOKEN`. - If that token is **patchwarden-bot (id9)** → the "controller acts; Patchwarden vouches" separation in D24 has effectively collapsed: Patchwarden *is* the visible approver. That's a real evolution of D20, not a wording nit. - If the token is a **separate controller identity** (ClawSweeper / codex-controller) → the separation holds, and `contract_publish` is just the mechanism the external controller drives. The decision record doesn't say which, so a reader can't tell whether the D20/D24 sensor-vs-actor invariant actually survived #129. (`no-self-approval` only guarantees approver ≠ PR author — it does **not** guarantee approver ≠ Patchwarden.) ## Proposed reconciliation (operator to ratify; ~edit is small) Clarify D24 (and/or a short D26) to state the *actual* four-way boundary precisely: 1. **Merge** — never, by anyone called Patchwarden; D20-lint enforced. (unchanged) 2. **Reviewer-lane approval** — never; lanes are sensors. (unchanged) 3. **Deterministic `contract_publish` approval** — *permitted under guard*: opt-in (`--approve-on-pass`), passing exact-head Contract Run, ready zero-blocker `controller_approval`, no-self-approval, non-hard-manual, fail-closed. 4. **Visible-controller approval by an external actor** (ClawSweeper/OpenClaw consuming the verdict) — the target end-state (PW-G012, still `partial`). And state explicitly **which identity** posts the #3 approval, and whether #3 is the intended steady state or a transitional dogfood mechanism on the way to #4. If #3 posts under patchwarden-bot, either (a) amend D24's "never approves" to "never *merges*; approves only via the guarded deterministic adapter," or (b) require #3 to run under a non-Patchwarden controller identity to preserve the separation. Not urgent / nothing is broken in the code — the guards are sound (verified in #127). This is about keeping the canonical authority record honest now that real approval power shipped. — claude (architect loop)
Sign in to join this conversation.
No labels
agent/claude-code
agent/codex
agent/gemini
agent/hermes
agent/iskra
agent/ollama
agent/patchwarden
area:business-model
area:competitive
area:discovery
area:forgejo
area:metrics
area:product-strategy
area:v0-core
cagan-grade-approved
client:platform
dependency/blocked
dependency/blocks-others
dependency/cross-repo
dependency/needs-confirmation
domain:agents
domain:ci
domain:docs
domain:forgejo
domain:infra
domain:memory
domain:runtime
domain:signal
domain:ux
flow/architecture
flow/blocked
flow/deployed
flow/done
flow/implementation
flow/intake
flow/maintained
flow/observed
flow/ready
flow/refining
flow/retired
flow/review
judge/codex-candidate
judge/hermes-candidate
judge/low-confidence
judge/needs-refinement
judge/operator-needed
judge/p0
judge/p1
judge/p2
judge/p3
judge/park
judge/patchwarden-candidate
judge/stale-priority
kind/adr
kind/bug
kind/chore
kind/feature
kind/infra
kind/ops
kind/refactor
kind/research
kind:artifact
kind:decision
kind:dogfood
kind:epic
kind:implementation
kind:research
merge/auto
merge/manual
merge/manual-dependency-conflict
merge/manual-failing-tests
merge/manual-merge-conflict
merge/manual-missing-review
merge/manual-operator-preference
merge/manual-red-zone
merge/manual-security-sensitive
merge/manual-unclear-scope
merge/manual-unknown
mode:operator-only
mode:patchwarden-iskra-approved
mode:safe-auto
observed/erroring
observed/needs-followup
observed/pending
observed/retire-candidate
observed/unused
observed/used
priority:p0
priority:p1
priority:p2
priority:p3
ready-for-agent
review:claude-reviewed
review:codex-reviewed
review:dziadek-reviewed
review:needs-human
safety:external-write
safety:no-prod-mutation
safety:prod-impact
safety:secret-touch
size/large
size/medium
size/small
size/tiny
size/unknown
source/adr
source/agent-generated
source/manual
source/operator-chat
source/voice-note
status:blocked
status:blocked-on-discovery
status:cagan-grade-review-pending
status:codex-ready
status:merged:pending-evidence
status:needs-evidence
status:needs-operator-decision
status:operator-needed
status:parked
tier:0-anchor
tier:0-platform-substrate
tier:1-core
tier:1-iskra-value-layer
tier:2-supporting
tier:2-tools-products-modules
type:bug
type:chore
type:docs
type:feat
type:policy
type:research
wave:1-foundation
wave:2-positioning
wave:3-validation
wave:4-economics
wave:5-operating
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
pdurlej/patchwarden#132
No description provided.