docs(agent-sudo): lock D-CB5-D-CB9, add the P3-WIRING agent prompt, record P3 state

- agent_sudo_design_decisions.md: D-CB5 (3 tier-3 fails / 15min sliding / per-host),
  D-CB6 (append-only breaker log, state always replayed — tamper-EVIDENT beats
  tamper-PROOF when the daemon is root), D-CB7 (Set B host_override in code),
  D-CB8 (which tier-4 sources latch), D-CB9 (trip escalation). Each with rationale,
  rejected alternatives, and accepted costs.
- agent_prompts.md: add the P3-WIRING prompt. Flags a real NAME COLLISION — the
  existing "P3" section is the OLD P3 (Vault AppRole + Timeshift + evaluator, done
  as #142); P3 was redefined after D-CB1-D-CB9 were locked. Running the old one
  would redo finished work. Today's inline agent prompts were never persisted, which
  is why this one is.
- context.md: P3 code complete but UNDEPLOYED — the server-01 tier-0 breaker.log hole
  is open in production until DEPLOY_RUNBOOK.md runs. Two verified deploy blockers
  (Dockerfile never COPYs circuit_breaker.py/security/; compose has no
  /var/lib/agent-sudo mount => breaker resettable by docker restart, the exact D-CB6
  bypass). Both were invisible to an 85/85-green suite.
- context.md: MEMORY_DIR answered — do NOT repoint embed_memory_dir.py; its
  claude_memories table has no reader. Recall corpus now includes design docs;
  context.md stays excluded until #192 prunes it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Backtalk6858
2026-07-15 19:04:38 -05:00
parent dcc4cc4ef2
commit c0293fa96f
3 changed files with 275 additions and 6 deletions
+104
View File
@@ -1199,3 +1199,107 @@ Old bridge has been on 8084 hot-standby since P4's swap, 14-day retention. Cooli
## Wrap-up notes must confirm: window-clean proof (incident count = 0); what was stopped/removed per host; image retained or removed; id=176 status.
```
---
# P3-WIRING — app.py tier-2/3 + circuit-breaker (2026-07-15) — projects id=187, task #149 part B
> **⚠️ NAME COLLISION — READ THIS.** The "P3" section ABOVE (Vault AppRole + Timeshift + AI
> evaluator) is the **OLD P3** and is **COMPLETE** (task #142). P3 was **redefined** after its
> D-CB1…D-CB9 decisions were locked on 2026-07-15. THIS is the current P3. Do not run the old one.
**Blast radius: ZERO.** Code + tests only. No deploy, no restart, no git push. Agent-Sudo is LIVE
and ENFORCING on both hosts; editing these files does not deploy them, and it must stay that way.
**Human-gate protocol:** deploying this code requires restarting `agent-sudo-daemon`, which D-CB7
classifies **tier-4 / human-only on both hosts** — the Set B override exists precisely to stop an
agent doing it. The agent must therefore STOP at any human-only step and report it, not route
around it. Resume the agent afterwards with SendMessage (its context survives).
`--max-turns 25`
```
You are wiring the LAST piece of Agent-Sudo P3: tier-2 and tier-3 execution plus the circuit-breaker into app.py. Narrow scope: `/opt/appdata/docker/docker-compose/agent-sudo/app.py` and its tests ONLY. You will NOT modify sudo_rules.py or circuit_breaker.py (both are DONE and their APIs are LOCKED). You will NOT deploy, restart, redeploy, or git push.
## THE DESIGN IS LOCKED — DO NOT REDESIGN
Read `/home/administrator/Desktop/claude/agent-builder/agent_sudo_design_decisions.md` — sections D2, D3, D5, D10 and **D-CB1 through D-CB9**. Implement exactly that. If you believe a decision is wrong, implement it as written and say so in the wrap-up `notes` — do NOT silently pick a different reading. (Two previous agents on this project each caught a real spec contradiction that way; it is the behaviour we want.)
## Context — current state, do not re-derive
- `app.py` is UNTOUCHED: zero `circuit_breaker` imports. The wiring site is ~lines 161209, and the two capability stubs are at ~186191:
if d.tier == 2 and not SANDBOX_ENABLED: -> raise HTTPException(503, 'Tier-2 sandbox pipeline not enabled on this host (requires P3)')
if d.tier == 3 and not SNAPSHOT_ENABLED: -> raise HTTPException(503, 'Tier-3 snapshot pipeline not enabled on this host (requires P3)')
`SANDBOX_ENABLED` / `SNAPSHOT_ENABLED` are read from env at ~lines 4950.
- `sudo_rules.classify(command, server_id)` returns a Decision with `.tier`, `.source`, `.matched_rule`, `.host_override`, `.verb`, `.state`. **Preserve that contract.** Its three tier-4 sources are `danger_veto`, `miss_primary_refuse`, `host_override_setb`.
- `circuit_breaker.py` is DONE, 28/28 tests pass. **LOCKED API — do not rename:**
replay(host) -> BreakerState # ONLY source of truth; state is NEVER stored, always replayed from the log
allows(tier, host) -> (bool, str)
record_tier3_failure(host, cmd_id) -> BreakerState
record_tier4_attempt(host, cmd_id) -> BreakerState # ALWAYS attack-latches
reset(host, actor) -> BreakerState
classify_trip(signal) -> str # D10 seam
Constants: BENIGN_FAIL_THRESHOLD=3, BENIGN_WINDOW_SECONDS=900, BASE_COOLDOWN_SECONDS=600, MAX_COOLDOWN_SECONDS=14400, TRIP_ESCALATION_WINDOW_SECONDS=21600, TRIP_ESCALATION_THRESHOLD=4.
- Existing security modules already present in `agent-sudo/security/`: `timeshift.py` (raises `PrimaryRestoreForbidden`), `scoped_undo.py` (`capture_or_refuse`), `evaluator.py` (`_append_proposed_rule`, propose-only — no activation path), `sudo_sign.py`, `vault_setup.py`. READ them before wiring; do not reimplement what exists.
## D-CB8 IS THE LOAD-BEARING SUBTLETY — GET THIS RIGHT
`record_tier4_attempt()` ALWAYS attack-latches. But app.py reaches tier 4 from THREE sources, and `miss_primary_refuse` is the DEFAULT for any unlisted command. If every tier-4 latched, one typo would take the host read-only until a human reset — verified against the 2026-07-15 session, which refused every privileged command with `source=miss_primary_refuse` and would have latched on the first one and stayed latched all day.
Therefore: **ONLY `danger_veto` and `host_override_setb` call `record_tier4_attempt()`. `miss_primary_refuse` is AUDIT-ONLY — log it to command_audit, refuse the command, but do NOT touch the breaker.** The seam holds because only the CALLERS change, never the primitive.
## Contract — what must be true when you finish
1. app.py imports circuit_breaker and consults `allows(tier, host)` BEFORE executing anything. If denied, refuse with a clear error naming the breaker state + cause; do not execute.
2. Tier-2 routes through the sandbox-test path when `SANDBOX_ENABLED`, else the 503 stub stays. Tier-3 routes through the snapshot/scoped-undo path (`scoped_undo.capture_or_refuse`, `timeshift`) when `SNAPSHOT_ENABLED`, else the 503 stub stays. **The stubs must remain the behaviour when the capability is off** — this is what makes the change safe to land undeployed.
3. A tier-3 execution FAILURE calls `record_tier3_failure(host, cmd_id)`.
4. Tier-4 from `danger_veto` or `host_override_setb` calls `record_tier4_attempt(host, cmd_id)`. Tier-4 from `miss_primary_refuse` does NOT.
5. `timeshift.PrimaryRestoreForbidden` must still be honoured — never auto-restore primary.
6. Every decision + outcome is logged to `command_audit` (D2/D3: this is the local model's training data — decision_type is one of 'rule_match' | 'sandbox_test' | 'ai_eval_proposal' | 'execution' | 'circuit_breaker' | 'tier4_refuse').
7. All existing app.py behaviour for tier 0/1 is unchanged.
## Step-by-step
1. Read app.py in full, then circuit_breaker.py, sudo_rules.py, and the security/ modules. Read the design doc sections named above.
2. Wire the contract above. Match the existing code's style and error-handling idiom.
3. Write/extend `test_app.py` (create if absent; if it exists, ADD and keep all existing tests green). Must cover: breaker denies before execution; tier-3 failure records; **danger_veto latches**; **host_override_setb latches**; **miss_primary_refuse does NOT latch** (the D-CB8 test — most important); 503 stubs still returned when capabilities are off; tier 0/1 unaffected.
4. Run `cd /opt/appdata/docker/docker-compose/agent-sudo && PYTHONDONTWRITEBYTECODE=1 python3 -m unittest test_app -v` AND re-run `test_sudo_rules` + `test_circuit_breaker` to prove no regression. Paste all three verbatim summary lines in the wrap-up.
5. **Mutation-check your own suite** (both previous agents did this and it caught real gaps): break the D-CB8 branch so `miss_primary_refuse` latches, confirm the suite FAILS, restore, confirm it passes. A test that cannot fail is not evidence.
6. Write `/opt/appdata/docker/docker-compose/agent-sudo/DEPLOY_RUNBOOK.md` — the exact commands the HUMAN must run to deploy, per host, in order, with the rollback command for each and how to verify success. This is a deliverable, not an action: do NOT run any of it.
## HUMAN-GATE PROTOCOL — how to stop (this is a feature, not a failure)
Some steps are human-only BY DESIGN (D-CB7 Set B): restarting/stopping `agent-sudo-daemon`, editing its unit file, editing SUDO.md, writing breaker.log, and the primary port swap (#146). You must NOT attempt these, work around them, or stall waiting.
If you hit one, STOP IMMEDIATELY and emit the wrap-up with `status="partially_succeeded"` and populate `human_action_required`. The human will perform it and RESUME you — so `notes` must contain exactly what you were doing and what you need re-checked on resume.
## MANDATORY WRAP-UP (required regardless of success or failure)
Before stopping for ANY reason — task complete, error, human gate, or approaching turn limit — output this JSON as your final message. Do not stop without it.
{
"status": "succeeded|partially_succeeded|failed",
"actions_taken": ["action 1 — outcome"],
"actions_failed": ["action — reason"],
"human_action_required": [
{"server": "primary (192.168.1.88) | server-01 (192.168.1.90)",
"command": "exact command to run",
"why": "why an agent may not do this",
"verify": "how the human confirms it worked"}
],
"notes": "verbatim unittest summary lines for test_app, test_sudo_rules, test_circuit_breaker; verbatim mutant-run summary; confirmation that miss_primary_refuse does NOT latch while danger_veto and host_override_setb DO; where DEPLOY_RUNBOOK.md was written; exactly where to resume you if paused; anything ambiguous in the locked spec and what you chose"
}
`human_action_required` MUST be `[]` when empty — never omit the key.
## Constraints
- ONLY `app.py`, `test_app.py`, `DEPLOY_RUNBOOK.md`. Nothing else.
- Do NOT modify sudo_rules.py or circuit_breaker.py. Do NOT deploy, restart, redeploy, or git push.
- stdlib + the project's existing deps only.
- Do NOT create `/var/lib/agent-sudo/breaker.log` on the host.
- Use `PYTHONDONTWRITEBYTECODE=1` when running python (a `__pycache__` permission error previously masqueraded as a syntax error).
--max-turns 25
If you issue the same tool call or command twice with identical arguments, STOP immediately and output the mandatory wrap-up with status=partially_succeeded.
```