diff --git a/agent-builder/GAMEPLAN_agent-sudo_secrets-proxy.md b/agent-builder/GAMEPLAN_agent-sudo_secrets-proxy.md new file mode 100644 index 0000000..ff8bcbe --- /dev/null +++ b/agent-builder/GAMEPLAN_agent-sudo_secrets-proxy.md @@ -0,0 +1,294 @@ +# Gameplan — Agent-Sudo + secrets-proxy to fully deployed + +**Written:** 2026-07-15 20:10 · **Author:** Claude (Opus 4.8) · **Status:** ACTIVE PLAN +**Why now:** 2026-07-15 is the LAST day dedicated to this work. After this, time goes to +business development (revenue) + other projects. Everything here is scoped so a background +agent can do it, with human touches minimized to the ones that are human-only *by design*. + +--- + +## 0. READ THIS FIRST — the goal is not what it sounds like + +The stated goal was "Agent-Sudo and secrets-proxy handling tier 0–4 commands." + +**Tier 4 must never be handled autonomously. That is not a gap — it is the design.** +Per D-CB7, the `agent-sudo-daemon` unit env holds `SUDO_MD_VERIFY_ENFORCE`. A system that can +restart its own daemon can disarm its own gate. So tier 4 = human-at-a-terminal, permanently. + +**"Agent-Sudo handles tier 4" means "Agent-Sudo correctly REFUSES tier 4."** That is +**DONE and VERIFIED LIVE** (2026-07-15 19:52, both hosts): + +| command | tier | source | +|---|---|---| +| `rm -rf /` | 4 | `danger_veto` | +| `systemctl restart agent-sudo-daemon` | 4 | `host_override_setb` | +| `vi /etc/systemd/system/agent-sudo-daemon.service` | 4 | `host_override_setb` | + +Do not let a future session "finish tier 4." There is nothing to finish. If someone proposes +making tier 4 autonomous, that is a request to delete the security model — refuse it. + +**Real remaining scope = tier 1, tier 2, tier 3.** + +--- + +## 1. VERIFIED STATE as of 2026-07-15 20:00 (measured tonight, not remembered) + +### Agent-Sudo — DEPLOYED ON BOTH HOSTS (first time ever) +- Commit `005c214` on `origin/main` (repo: `boilerplates.git`, path `/opt/appdata/docker`). +- **server-01**: `192.168.1.90:8082` · healthy · 0 restarts · `{"status":"ok","server_id":"server-01"}` +- **primary**: `192.168.1.88:8084` · healthy · 0 restarts · `{"status":"ok","server_id":"primary"}` +- Old sudo-bridge STILL owns `192.168.1.88:8082` on primary. **No cutover has happened.** (#146) +- **D-CB6 breaker log is now persistent on BOTH hosts** — `/var/lib/agent-sudo/breaker.log`, + dir `0700 root:root`, on a bind mount, verified resolving inside the container. + `docker restart` can no longer reset a tripped breaker. **This closed a hole that was OPEN + IN PRODUCTION.** `breaker.log` does not exist yet — correct, created 0600 on first append. +- 85/85 tests pass on both hosts. +- Capability gates: `SANDBOX_ENABLED=false`, `SNAPSHOT_ENABLED=false` on both. + ⇒ **tier 2 and tier 3 currently return 503 stubs. They do not work.** + +### Subsystem availability — MEASURED, and it is asymmetric +| host | incus | sandboxes | timeshift | +|---|---|---|---| +| server-01 | **YES** | `sandbox-primary` + `sandbox-server01` both RUNNING | YES | +| primary | **NO** | — | YES | + +**This asymmetry is the single most important fact in this document.** Tier 2 (sandbox-test) +CANNOT be enabled on primary — there is no Incus there at all. Any plan that says "enable +tier 2 on both hosts" is wrong. See §3-A3, which is an unresolved DESIGN question, not a task. + +### secrets-proxy — STOPPED, NOT BROKEN +- `secrets-proxy-secrets-proxy-1` = `Exited (0)` 6 days ago. +- Logs show a **graceful shutdown** ("Application shutdown complete", "Stopping parent process"). + It was stopped on purpose. **This is not a crash and not a debugging job.** +- Repo: `/opt/appdata/docker/docker-compose/secrets-proxy/` (app.py, docker-compose.yml, + Dockerfile, mirror/, requirements.txt). +- **HONEST GAP: I did not investigate what work remains on secrets-proxy.** Task #128 says + "Finish secrets-proxy (sprint-11 #1)" but I have not read sprint-11 or the code. Everything + below for secrets-proxy is therefore scoped as INVESTIGATE-THEN-PLAN. Do not treat S2 as a + known quantity. **Do not let an agent invent secrets-proxy work.** +- Its being down has a live cost: the `secrets_via_proxy_only` rule has no proxy to use, so + secret handling currently falls back to file-passing workarounds. + +--- + +## 2. THE RECURRING BUG CLASS — check this reflexively + +**Per-host values hardcoded into files tracked once and shared by both hosts.** +It bit TWICE tonight in one directory, and both were invisible until the two hosts were compared: + +1. `docker-compose.yml` hardcoded primary's port `8084`. Pulling it to server-01 would have + moved Agent-Sudo off 8082 and **silently undone the D8 cutover**. → FIXED: `${AGENT_SUDO_PORT}` + in each host's gitignored `.env` (primary 8084, server-01 8082). +2. `agent-sudo-daemon.service` carries `SERVER_ID` + `VAULT_ADDR`. The tracked copy says + `SERVER_ID=primary` and omits `VAULT_ADDR`. Installing it on server-01 would make server-01 + **identify as primary and apply primary's tier-4 rules**, with no Vault path to verify its + own allowlist. → NOT FIXED. See A4. Currently preserved by hand on server-01. + +**Rule for every future session: before landing any shared file on a second host, diff the two +hosts' copies first.** Nothing in the test suite measures cross-host divergence. It never will. + +Related lesson from tonight, worth carrying: **a green test suite said nothing about whether the +module even shipped.** 85/85 passed while the image would have crashed on boot with +`ModuleNotFoundError`, and while the breaker was resettable by `docker restart`. A passing check +only means what it measures. + +--- + +## 3. AGENT-SUDO — remaining work + +### A1. Fix tier-1 scoped-undo — **DO THIS FIRST. It is code, not config.** 🔴 BLOCKING +**The defect:** `security/scoped_undo.py:186` declares `REQUIRES_UNDO_TIERS = (1, 3)`, but +`app.py:244` calls `scoped_undo.capture_or_refuse(command, 3, server_id=SERVER_ID)` with the +tier **hardcoded to 3**. Nothing ever captures undo for tier 1. So **tier-1 commands +auto-execute today with no undo capture**, which contradicts D4 ("tier 1 = auto-execute + +capture scoped-undo"). + +**Resolve the conflict BEFORE writing code — this is a human decision:** +The P3 task contract said "tier 0/1 behaviour unchanged"; D4 says tier 1 captures undo. The +background agent followed the task contract and pinned it with a test, then flagged it. It was +right to flag rather than silently pick. Someone must decide: +- (a) Implement D4: tier 1 captures scoped-undo. Cost: every tier-1 command pays undo-capture + latency. Benefit: tier 1 becomes reversible, which is what makes autonomy safe. +- (b) Amend D4: tier 1 is genuinely fire-and-forget, drop `1` from `REQUIRES_UNDO_TIERS`. + Cheaper and faster, but then tier 1 is irreversible — and tier 1 is where the volume is. +- **LEAN: (a).** The whole premise of constrained autonomy is that the constraint is real. + An irreversible tier-1 with no undo is an approval gate removed without a constraint added. + But this is a real trade and the user decides, not an agent. + +⚠️ Whichever way it goes, **the test that currently pins the wrong behaviour must be updated**, +or it will lock in the bug. + +### A2. Phase 2 — enable capabilities on server-01, ONE AT A TIME 🟡 +Runbook: `/opt/appdata/docker/docker-compose/agent-sudo/DEPLOY_RUNBOOK.md` phase 2. +server-01 only (it has Incus + both sandboxes RUNNING + timeshift). +**This is the first-ever live exercise of the sandbox and snapshot paths.** Until now tier-2/3 +have ONLY run against injected fakes — the real Incus and timeshift paths have never executed. +Expect real failures. Budget for that; do not schedule it as a 20-minute flag flip. + +Order (do not batch — one flag, verify, then the next): +1. `SNAPSHOT_ENABLED=true` in server-01's `.env` → `docker compose up -d` → exercise a tier-3 + command → verify a timeshift snapshot is actually created AND that undo restores. +2. Then `SANDBOX_ENABLED=true` → exercise a tier-2 command → verify it runs in the Incus + sandbox and NOT on the host. +⚠️ **Do NOT test the latch by firing a danger-veto command on a live host** (runbook says so). +Latch behaviour is already proven by unit tests + mutation testing. +⚠️ Verify the 503 stub is actually gone, not that health is green. Health does not measure this. + +### A3. **DESIGN QUESTION, NOT A TASK: what does tier 2 mean on primary?** 🔴 UNRESOLVED +Primary has no Incus. Options, none free: +- (a) Install Incus on primary. But: `reference_incus_on_docker_host_networking` documents that + Docker's `FORWARD DROP` breaks the incus bridge and debian/13 systemd-resolved is broken. + This is a known-painful install on a Docker host — and primary is the PRODUCTION host. +- (b) Primary tier-2 commands execute in server-01's sandbox (cross-host). Introduces the same + cross-host dependency class as #173, and the sandbox would not mirror primary's state. +- (c) Tier 2 stays permanently 503 on primary; primary's SUDO.md never classifies anything as + tier 2. Honest and safe; means primary is a lesser-capability host by design. **LEAN: (c)** + for now — it is the only one that ships without new risk on the production host, and (a) can + come later if tier-2 volume on primary justifies the pain. +**This must be decided by the user before anyone touches primary tier-2.** + +### A4. Daemon unit `EnvironmentFile` — fix the §2 bug class 🟡 HUMAN-ONLY +Make `agent-sudo-daemon.service` host-neutral: `EnvironmentFile=/etc/agent-sudo/daemon.env`, +with `SERVER_ID` / `VAULT_ADDR` / `SUDO_MD_VERIFY_ENFORCE` per-host in that file. +**Tier-4 human-only by design** — that unit's env IS the gate. An agent must never edit it. +**Fold into #173**: the `VAULT_ADDR=http://192.168.1.88:8200` line in server-01's unit IS the +cross-host Vault dependency that #173 exists to resolve. Same file, same edit, one human pass. + +### A5. #146 — primary port swap 8084 → 8082 🟢 NOW TRIVIAL +Was a manual file edit; the `${AGENT_SUDO_PORT}` change reduced it to a one-line `.env` edit +plus stopping the old sudo-bridge. Do this LAST, after primary tier-1/2/3 behaviour is settled. +Blocked by: #191 (sudo-bridge/app.py is one of the git-held files). + +### A6. server-01 repo hygiene 🟢 +server-01's `/opt/appdata/docker` is at HEAD `96f8c05`, **28 commits behind**, with the +agent-sudo dir checked out from `005c214` into a staged index, plus uncommitted local changes to +`jenkins/Dockerfile`, `sudo-bridge/app.py`, `sudo-bridge/docker-compose.yml`. +The files are correct and the deploy is sound, but the repo state is a trap for the next person. +**Why it is this way:** a full `git pull` would have clobbered those held local changes, so a +targeted `git checkout origin/main -- docker-compose/agent-sudo` was used instead. That was the +right call under time pressure; reconciling properly is a separate, careful job. +⚠️ Whoever does this must NOT blindly `git checkout`/`stash` the local changes away — find out +what they are first. Same category as #191. + +### A7. Spec gaps A + C on projects row 187 🟢 confirm-and-close +- **(A)** `ATTACK_TIER4_SOURCES` = `('danger_veto', 'host_override_setb')`. `classify()` can also + reach tier 4 via `rule_match` (explicit SUDO.md tier-4 rule) and `miss_verb_heuristic`. The + agent made both audit-only, reasoning a latch must mean "this is not resolving." **Decide: is + an explicit tier-4 SUDO.md rule an attack signal?** One-line change; the D10 seam holds. + Note D-CB8 deliberately excludes `miss_primary_refuse` — latching on it would take a host + read-only on the first typo. Do not "fix" that. +- **(C)** A failed PRE-exec snapshot refuses 503 without calling `record_tier3_failure()` — the + command never ran, so counting it toward the benign trip threshold would let a broken + timeshift disarm tier 3. Reasoning is sound; just confirm and close. + +--- + +## 4. SECRETS-PROXY — remaining work + +### S1. INVESTIGATE — **must happen before any plan is written** 🔴 +Nobody currently knows what "finish secrets-proxy" means. Establish, with evidence: +1. **Why was it stopped?** Exit was graceful and deliberate 6 days ago (~2026-07-09). Check git + log, session summaries, and task #128 / sprint-11 notes for the reason. **It may have been + stopped for a reason that still applies** — do not just start it. +2. What is incomplete in `app.py` / `mirror/`? What did sprint-11 #1 actually scope? +3. Does it start cleanly today? What breaks if it does? +4. Is its `docker-compose.yml` carrying the §2 per-host-value bug? Check before any second host. +5. Check `project_coolify_env_var_debt`: are there `${VAR}` Coolify placeholders that will bite + on restart? **This is a known live trap for any container restart.** +**Output: a written scope. Then and only then, plan S2.** Do not skip to S2. + +### S2. FINISH + DEPLOY — cannot be scoped until S1 lands 🔴 +Placeholder by construction. Anyone writing detail here before S1 is guessing. + +### S3. #150 — sign `proxy.md` via Vault transit 🟡 +Explicitly blocked on secrets-proxy deploy. Mirrors Agent-Sudo's SUDO.md signing (#145), so the +pattern is proven — reuse `security/sudo_sign.py`, do not reinvent. + +### S4. Restore the `secrets_via_proxy_only` rule 🟢 +Once up, secret reads go back through the proxy `/shell` + `env_secrets` vault refs, retiring +tonight's file-passing workaround. + +--- + +## 5. SEQUENCING — and the trap in the obvious order + +**The ordering paradox** (`feedback_build_the_freeing_capability_first`): the obvious order is +"finish Agent-Sudo, then secrets-proxy, then business dev." But the item that removes a +recurring tax is often scheduled last, behind the things that charge it. + +**secrets-proxy being down taxes EVERY future session** — every secret read needs a workaround, +and that cost is paid on business-development days too, when this infrastructure is supposed to +be out of the way. **S1 is zero-dependency and small. Do S1 FIRST**, even before A1, so the +scope is known and can be handed to an agent while attention is elsewhere. + +Recommended order: +| # | item | who | why here | +|---|---|---|---| +| 1 | **S1** investigate secrets-proxy | agent | zero-dep, removes a recurring tax, unblocks S2/S3 | +| 2 | **A1 decision** tier-1 undo (a) vs (b) | **human** | blocks all tier-1 work; 5-min decision | +| 3 | **A3 decision** primary tier-2 | **human** | blocks primary; 5-min decision | +| 4 | **A1 implement** | agent | the real code work | +| 5 | **A2** phase 2 on server-01 | agent + human | first live sandbox/snapshot run | +| 6 | **A7** confirm gaps A + C | agent | cheap close-out | +| 7 | **S2** finish secrets-proxy | agent | scoped by S1 | +| 8 | **A4 + #173** unit EnvironmentFile | **human** | tier-4 by design | +| 9 | **A6** repo hygiene, **A5**/#146 swap, **S3**/#150 | mixed | last, low risk | + +**Steps 2 and 3 are two human decisions totalling ~10 minutes that unblock everything else.** +Make them before starting a business-development day and the agents can run without you. + +--- + +## 6. WHAT AGENTS MAY AND MAY NOT DO + +**Never delegate to an agent (tier-4 / human-only by design):** +- Restarting or stopping `agent-sudo-daemon`; editing its unit file (A4) — *the unit env is the gate* +- Editing `SUDO.md`; writing `/var/lib/agent-sudo/breaker.log`; resetting a tripped latch +- The #146 port swap +- The A1 and A3 **decisions** (implementation is delegable; the choice is not) +- Anything in §2 that lands a shared file on a second host without diffing both first + +**Good agent work:** S1 investigation, A1 implementation once decided, A2 under supervision, +A7 confirmations, S2 once scoped, test writing. + +**Every agent prompt must carry** (`playbook_background_agent_prompts` + +`feedback_agent_prompt_mandatory_wrapup`): the mandatory wrap-up JSON block +(`status` / `actions_taken` / `actions_failed` / `notes` / `human_action_required`) so the agent +structurally cannot stop without reporting; an explicit permitted-file list; and +`--max-turns`. Tonight proved the value: the P3 agent's two most valuable findings were the +blockers **outside** its permitted file set, which it could only flag, not fix. **An agent that +flags what it cannot touch is working correctly — do not "fix" that by widening permissions.** + +--- + +## 7. DEFINITION OF DONE + +Agent-Sudo is done when: +- [ ] Tier 0 executes. **(DONE — verified 2026-07-15)** +- [ ] Tier 4 is refused on both hosts. **(DONE — verified 2026-07-15)** +- [ ] Breaker log persists across restart on both hosts. **(DONE — verified 2026-07-15)** +- [ ] Tier 1 behaviour matches a D4 that has been reconciled with the task contract (A1) +- [ ] Tier 3 creates a real timeshift snapshot and restores, on server-01 (A2) +- [ ] Tier 2 executes in the real Incus sandbox on server-01 (A2) +- [ ] Primary's tier-2 story is decided and written down (A3) +- [ ] The daemon unit is host-neutral (A4 / #173) +- [ ] Instrument-the-gate audit query is scheduled and someone READS it + (`feedback_instrument_the_gate` — a gate nobody reads is not instrumented) + +secrets-proxy is done when: S1 scope written · S2 deployed · S3 `proxy.md` signed · +`secrets_via_proxy_only` restored without workarounds. + +**Not done ≠ not useful.** As of tonight Agent-Sudo safely runs tier-0 and correctly refuses +tier-4 on both hosts, with a tamper-evident breaker. That is real, and it is the part that was +open in production this morning. + +--- + +## 8. XREF +projects **id=187** (P3 detail + 3 spec gaps) · **#149** (parent) · **#146** (port swap) · +**#173** (fail-open/closed — fold A4 in) · **#191** (git-held files) · **#128** (secrets-proxy) · +**#150** (proxy.md signing) · `DEPLOY_RUNBOOK.md` (phases) · +`agent_sudo_design_decisions.md` (D1–D10, D-CB1–D-CB9) · +memory: `feedback_build_the_freeing_capability_first`, `feedback_instrument_the_gate`, +`feedback_autonomous_security_constrain_not_gate`, `reference_incus_on_docker_host_networking`, +`project_coolify_env_var_debt`, `feedback_agent_prompt_mandatory_wrapup` diff --git a/agent-builder/agent_prompts.md b/agent-builder/agent_prompts.md index bdaa377..47759cc 100644 --- a/agent-builder/agent_prompts.md +++ b/agent-builder/agent_prompts.md @@ -1303,3 +1303,163 @@ Before stopping for ANY reason — task complete, error, human gate, or approach --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. ``` + +--- + +# S1-SECRETS-PROXY-SCOPE (2026-07-15) — task #128 · gameplan §4 · **READY TO RUN NOW** + +> **Zero dependencies. Run this FIRST**, ahead of the Agent-Sudo work — per +> `feedback_build_the_freeing_capability_first`, secrets-proxy being down taxes every future +> session (every secret read needs a workaround), including business-development days. +> **This is INVESTIGATION ONLY. It produces a written scope, not a deploy.** + +You are scoping what remains to finish `secrets-proxy`. Nobody currently knows. Your job is to +find out and write it down with evidence. **Do not fix. Do not deploy. Do not start the container.** + +## Verified starting facts (do not re-derive; DO verify anything you rely on further) +- Container `secrets-proxy-secrets-proxy-1` = `Exited (0)` ~6 days ago (~2026-07-09). +- Logs show a GRACEFUL, DELIBERATE shutdown ("Application shutdown complete", "Stopping parent + process [1]"). **This is NOT a crash.** Do not investigate it as one. +- Repo: `/opt/appdata/docker/docker-compose/secrets-proxy/` — `app.py`, `docker-compose.yml`, + `Dockerfile`, `mirror/`, `requirements.txt`. On host `primary` (192.168.1.88). +- Task #128 = "Finish secrets-proxy (sprint-11 #1)". Task #150 (sign `proxy.md` via Vault + transit) is explicitly blocked on secrets-proxy being deployed. + +## Questions you must answer WITH EVIDENCE (quote the file/line/log/commit) +1. **WHY was it stopped?** Search git log, session summaries, sprint-11 notes, task #128. + ⚠️ **It may have been stopped for a reason that still applies.** This is the most important + question. If you cannot find the reason, say so explicitly — do not guess. +2. What does "sprint-11 #1" actually scope? What is incomplete in `app.py` / `mirror/`? +3. Would it start cleanly today? Determine by READING code/config, **not by starting it**. +4. Does `docker-compose.yml` carry per-host values hardcoded in a shared tracked file? + (See gameplan §2 — this bug class bit twice in agent-sudo on 2026-07-15: a hardcoded port + and `SERVER_ID`/`VAULT_ADDR` in a unit file. Check ports, IPs, `SERVER_ID`-alikes.) +5. `project_coolify_env_var_debt`: any `${VAR}` Coolify placeholders that would bite on restart? +6. What would the proxy-only rule require to be honoured again (the `/shell` + vault-ref path)? + +## Hard constraints +- **READ-ONLY.** No edits, no `docker compose up`, no `docker start`, no restarts, no commits. +- **NEVER surface a credential.** Do not `cat` the env file; do not `docker exec` to read env; + do not echo token-shaped values. To show config, print KEYS ONLY (`sed 's/=.*/=/'`). + The `security-enforcement.py` PreToolUse hook will block token patterns, and anything printed + lands permanently in the transcript. If a credential is needed for a check, STOP and report. +- secrets-proxy is itself DOWN, so you cannot use it for credential reads. Do not work around this. +- If a question cannot be answered from evidence, write "UNKNOWN — could not determine because X". + **An honest UNKNOWN is worth more than a confident guess.** Do not fill gaps by inference. + +## Deliverable +`/opt/appdata/docker/docker-compose/secrets-proxy/SCOPE.md` — findings, each with its evidence, +and a proposed S2 task list with effort estimates. Flag anything human-only. + +--max-turns 20 + +## MANDATORY WRAP-UP — you cannot stop without emitting this +```json +{"status":"succeeded|partially_succeeded|failed", + "actions_taken":[], "actions_failed":[], + "questions_answered":{"why_stopped":"", "sprint11_scope":"", "starts_clean":"", + "per_host_bug":"", "coolify_placeholders":"", "proxy_only_path":""}, + "unknowns":[], "human_action_required":[], "notes":""} +``` +If you issue the same tool call twice with identical arguments, STOP and emit the wrap-up with +status=partially_succeeded. + +--- + +# A1-TIER1-UNDO (2026-07-15) — projects id=187 gap B · gameplan §3-A1 · **GATED** + +> 🔴 **DO NOT RUN THIS UNTIL THE HUMAN HAS DECIDED (a) vs (b) BELOW.** +> The decision is the user's, not yours. If dispatched without a recorded decision, STOP +> immediately and emit the wrap-up with `human_action_required: ["A1 decision (a) or (b)"]`. + +## The defect (verified 2026-07-15 19:55) +- `security/scoped_undo.py:186` → `REQUIRES_UNDO_TIERS = (1, 3)` +- `app.py:244` → `scoped_undo.capture_or_refuse(command, 3, server_id=SERVER_ID)` — **tier + hardcoded to 3**. Nothing ever captures undo for tier 1. +- Result: **tier-1 commands auto-execute with NO undo capture**, contradicting D4 ("tier 1 = + auto-execute + capture scoped-undo"). + +## The conflict a human must resolve first +The P3 task contract said "tier 0/1 behaviour unchanged"; D4 says tier 1 captures undo. The P3 +agent followed the contract, pinned it with a test, and flagged it — correctly. +- **(a) Implement D4:** tier 1 captures scoped-undo. Cost: undo-capture latency on every tier-1 + command (tier 1 is the volume tier). Benefit: tier 1 becomes reversible. +- **(b) Amend D4:** drop `1` from `REQUIRES_UNDO_TIERS`; tier 1 is fire-and-forget. Cheaper, but + tier 1 becomes irreversible. + +## If (a) — your task +1. Make the tier argument at `app.py:244` reflect the ACTUAL tier, not a hardcoded 3. +2. Ensure tier-1 flows through `capture_or_refuse` per `REQUIRES_UNDO_TIERS`. +3. ⚠️ **The existing test pins the WRONG behaviour** (tier 1 = no undo capture). You MUST update + it, or you will lock in the bug. Find it, understand why the P3 agent wrote it, then change it. +4. Decide + implement what happens when tier-1 undo capture FAILS. Note the precedent in gap C: + a failed PRE-exec snapshot refuses without calling `record_tier3_failure()`, because counting + infrastructure faults toward the benign trip threshold would let broken infra disarm the tier. + Apply the SAME reasoning to tier 1 and say so explicitly in your notes. + ⚠️ Refusing every tier-1 command when undo capture is broken would stall the user's whole + workflow — that is the exact stall CA-D7 forbids. Think about this; flag it, do not guess. +5. All 85 existing tests must still pass. Add tests for the new behaviour. + +## If (b) — your task +Drop `1` from `REQUIRES_UNDO_TIERS`, update D4 in `agent_sudo_design_decisions.md` to say tier 1 +is deliberately irreversible AND why, keep the existing test, add a comment at `app.py:244` +explaining the hardcoded 3 is now correct-by-decision. + +## Hard constraints +- **Permitted files ONLY:** `app.py`, `security/scoped_undo.py`, `test_app.py`, + `agent_sudo_design_decisions.md` (path b only). Anything else → **flag, do not touch**. + (On 2026-07-15 the P3 agent's two most valuable findings were blockers OUTSIDE its permitted + set, which it flagged rather than fixed. That is the system working. Do the same.) +- **NEVER** restart/stop `agent-sudo-daemon`, edit its unit, edit `SUDO.md`, or write + `/var/lib/agent-sudo/breaker.log` — all tier-4 human-only by design (D-CB7). +- **NEVER** fire a danger-veto or `host_override_setb` command on a live host — it latches a + production breaker. Both hosts are LIVE as of 2026-07-15 (server-01 `:8082`, primary `:8084`). +- Run tests with `PYTHONDONTWRITEBYTECODE=1 python3 -m unittest test_app test_sudo_rules + test_circuit_breaker` (baseline: `Ran 85 tests ... OK`). +- **A green suite proves less than you think.** On 2026-07-15, 85/85 passed while the image + crashed on boot and the breaker was resettable by `docker restart`. If you change behaviour, + prove it with a test that FAILS before your change and passes after. Say which test that is. + +--max-turns 25 + +## MANDATORY WRAP-UP — you cannot stop without emitting this +```json +{"status":"succeeded|partially_succeeded|failed|blocked_on_decision", + "decision_taken":"a|b|none", + "actions_taken":[], "actions_failed":[], + "tests":{"baseline_85_pass":true, "new_tests_added":[], "test_that_fails_before_change":""}, + "undo_capture_failure_behaviour":"", "files_touched":[], "files_flagged_not_touched":[], + "human_action_required":[], "notes":""} +``` + +--- + +# A7-SPEC-GAPS-AC (2026-07-15) — projects id=187 gaps A + C · gameplan §3-A7 · small + +Confirm-and-close two flagged spec gaps. **Analysis + recommendation; change only gap A, and +only if the human has said which way.** + +**Gap A:** `ATTACK_TIER4_SOURCES = ('danger_veto', 'host_override_setb')`. But `classify()` also +reaches tier 4 via `rule_match` (explicit SUDO.md tier-4 rule) and `miss_verb_heuristic` +(server-01). Both are currently audit-only (no latch). **Question for the human: should an +explicit tier-4 SUDO.md rule be an attack signal?** One-line change to `ATTACK_TIER4_SOURCES`. +⚠️ **D-CB8 deliberately EXCLUDES `miss_primary_refuse` — it is the DEFAULT for unlisted +commands, so latching on it would take a host read-only on the first typo. Do NOT "fix" that. +If you propose adding it, you have misunderstood the design.** + +**Gap C:** A failed PRE-exec snapshot refuses 503 without calling `record_tier3_failure()` — the +command never ran, and counting infrastructure faults toward the benign trip threshold would let +a broken timeshift disarm tier 3. **Verify this reasoning against the code and confirm or refute.** + +Permitted files: `app.py` (gap A one-liner only, only once decided). Everything else: report only. +Do not fire tier-4 commands on live hosts. All 85 tests must pass. + +--max-turns 12 + +## MANDATORY WRAP-UP +```json +{"status":"succeeded|partially_succeeded|failed", + "gap_a":{"recommendation":"", "changed":false, "reasoning":""}, + "gap_c":{"confirmed":true, "reasoning":""}, + "actions_taken":[], "actions_failed":[], "human_action_required":[], "notes":""} +```