Files
claude-projects/agent-builder/GAMEPLAN_agent-sudo_secrets-proxy.md
T
Backtalk6858 1d37e453ec docs: gameplan to fully-deployed Agent-Sudo + secrets-proxy, plus S1/A1/A7 agent prompts
Agent-Sudo went live on both hosts tonight (005c214 in the boilerplates repo). This
records what "done" actually means and what is left, since dedicated time on this ends
today and the rest has to run via background agents.

Corrects the goal: tier 4 is human-only BY DESIGN (D-CB7) — the daemon unit env holds
SUDO_MD_VERIFY_ENFORCE, so a system that can restart its own daemon can disarm its own
gate. "Handling tier 4" means refusing it, which is done and verified. Real remaining
scope is tiers 1-3.

Records the measured Incus asymmetry (server-01 has it, primary does not), which makes
tier 2 on primary an open design question rather than a task; the tier-1 scoped-undo
defect (app.py:244 hardcodes tier 3 while REQUIRES_UNDO_TIERS is (1,3)); and the
per-host-values-in-a-shared-tracked-file bug class that bit twice in one directory.

secrets-proxy is deliberately scoped as investigate-then-plan: it exited gracefully and
nobody knows why, so S1 answers that before anyone writes S2.

Refs #128, #146, #149, #150, #173, #191, projects id=187.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 20:13:46 -05:00

295 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 04 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` (D1D10, D-CB1D-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`