docs: agent-sudo grill-me complete — D1-D10, P0-P5 build prompts, comparison
- Close Agent-Sudo (infra id=176) design grill-me; gap #5 cross-cutting locked - Add P0-P5 phased background-agent build prompts (Opus 4.8 upgrades applied) - Add agent_sudo_vs_sudo_bridge.md comparison doc - Update context.md: sprint day 11 session 4, Coolify/N8N/port-8084 corrections Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -866,3 +866,259 @@ You are a database maintenance agent. The automation_ideas table has a required_
|
||||
--max-turns 15
|
||||
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.
|
||||
```
|
||||
|
||||
---
|
||||
---
|
||||
|
||||
# AGENT-SUDO BUILD — Phased Prompts P0–P5
|
||||
# Generated: 2026-07-08 (Opus 4.8 / high). Builds projects id=176 (infrastructure_projects DB).
|
||||
# Design is LOCKED in agent_sudo_design_decisions.md (D1–D10 + gap-#5 cross-cutting). These prompts IMPLEMENT that design — they do not re-open it.
|
||||
# DO NOT launch any agent yet — this set is pending user review.
|
||||
|
||||
## What is different from the Sonnet-era prompts above (Opus 4.8 upgrades)
|
||||
1. **Locked-design guardrail** — each prompt opens with a "DESIGN IS LOCKED — DO NOT REDESIGN" block pointing at agent_sudo_design_decisions.md. Agents implement; they never re-decide architecture, tiers, ports, or security model.
|
||||
2. **Contract, not brittle script** — each prompt states the *contract* (what must be true when done: schema, API surface, file locations, invariants) plus a *reference approach*. The agent writes the code to satisfy the contract instead of pasting a fragile literal script. (The Sonnet prompts embedded whole verbatim `.py` files that rotted.)
|
||||
3. **Right-sized turn budgets** — per-phase, not a flat 15.
|
||||
4. **4-level test gate + agent_test_results logging baked in** — P2 and P4 log to `agent_test_results` (api_business) per the testing methodology. The Sonnet set omitted this.
|
||||
5. **Per-phase abort/rollback + idempotent pre-flight** — each prompt has explicit pre-flight checks (safe to re-run) and an abort/rollback clause matching the gap-#5 blast-radius matrix.
|
||||
6. **Structural wrap-up + loop detection retained.**
|
||||
|
||||
## SHARED CONVENTIONS (referenced by every prompt below as "the shared block")
|
||||
|
||||
### Verified infra facts (live-checked 2026-07-08 — do not re-derive; verify only if a pre-flight says to)
|
||||
- **Postgres** (primary): container `postgres-lggkk0kcgwko440kk04wowgk` — resolve dynamically: `docker ps --format '{{.Names}}' | grep '^postgres-'`. Peer auth inside the container works: `docker exec <pg> psql -U postgres -d <db>` needs NO password.
|
||||
- **DBs:** `projects` DB → table `infrastructure_projects` (Agent-Sudo = id=176). `api_business` DB → tables `agent_test_results` ✅, `automation_ideas`, `behavior_changes`. `sudo_bridge` DB → `executions`, `allowlist_changes` (both have `server_id` column, default 'primary').
|
||||
- **Old bridges (being replaced):** primary `sudo-bridge-drbjegv07256ki2lpfyr00n8` on **8082**; server-01 bridge on **8082** (@192.168.1.90). Shared image `gitea.local/backtalk6858/sudo-bridge:latest`. API keys: Vault `secret/data/sudo-bridge` (primary), `secret/data/sudo-bridge-server01` (server-01, field `api_key`).
|
||||
- **Agent-Sudo parallel port = 8084** (⚠️ CORRECTION to design-doc D8 which said 8083 — 8083 is taken by bitwarden-bridge; 8084/8085/8086/8087 are free on primary. Pre-flight must re-confirm free before binding.)
|
||||
- **Both hosts:** LMDE 7 "gigi" = Debian 13 (trixie), kernel 6.12.90+deb13.1-amd64.
|
||||
- **Coolify is RETIRED** (control plane stopped 2026-06-25). Deploy Agent-Sudo as **plain docker-compose** using the server-01 template (`/opt/appdata/docker/non-docker-python-scripts/Docker Template/docker-compose.yml`, ports bound to the host LAN IP, no coolify network, no Traefik labels) driven by **Jenkins/Gitea** — NEVER Coolify. `coolify-proxy` is a standalone Traefik (name kept for cloudflared) — leave it alone.
|
||||
- **Vault:** container `vault-iwaulpoi5hwirdlogshmul40` (IP drifts — resolve via `docker inspect ... NetworkSettings.Networks`). Primary AppRole creds: `/opt/appdata/docker/docker-compose/vault/approle/{role-id,secret-id}`. AppRole-only, orphan tokens, revoke-self after use (playbook_vault_token_rotation Step 0).
|
||||
|
||||
### Privileged-execution path on server-01 (READ — this is the bootstrapping reality)
|
||||
Agent-Sudo does not exist yet, so build agents that need root on server-01 use the **existing** `sudo-bridge-server01` (`http://192.168.1.90:8082`, Bearer `secret/data/sudo-bridge-server01#api_key`). Rules:
|
||||
- `GET /allowlist` before any `POST /allowlist` (409 = already exists). New rules via `POST /allowlist` only (allowlist.json is root:root — cannot be edited directly).
|
||||
- `sudo-bridge` uses `subprocess.run(shlex.split())` — **NO shell**: no `&&`, `|`, `$()`, `>`. Each command is a separate `POST /exec`. File writes = `python3 -c "..."` with base64 (single argv).
|
||||
- **⚠️ TIER-2 NTFY APPROVAL IS BROKEN** (behavior_changes id=3 — notifications never arrive, command times out at 900s). **Do NOT design a build step that blocks on a tier-2 approval.** If a required privileged command would be tier-2, STOP and record it under `actions_failed` as "needs manual authorization: <command>" so the reviewer can pre-add it as tier-1 or run it interactively. Prefer tier-1-eligible commands; batch anything needing genuine escalation into an explicit list for the user.
|
||||
|
||||
### Mandatory wrap-up (every agent, on success OR failure, as the final message)
|
||||
```
|
||||
{
|
||||
"status": "succeeded|partially_succeeded|failed",
|
||||
"actions_taken": ["action — outcome"],
|
||||
"actions_failed": ["action — reason (incl. any 'needs manual authorization' commands)"],
|
||||
"notes": "phase-specific confirmations + anything the next phase needs"
|
||||
}
|
||||
```
|
||||
### Loop guard (every agent)
|
||||
If you issue the same tool call/command twice with identical arguments, STOP and emit the wrap-up with status=partially_succeeded. Respect the per-phase `--max-turns`.
|
||||
|
||||
---
|
||||
|
||||
## P0 — DB foundation (`command_audit` + training-signal schema)
|
||||
**Blast radius: ZERO** (additive DDL only, no server touched). Safe to run first, independent of P1–P5. `--max-turns 10`.
|
||||
|
||||
```
|
||||
You are a database schema agent on the primary server (192.168.1.88). Create the audit + training-data foundation for Agent-Sudo (projects id=176).
|
||||
|
||||
## DESIGN IS LOCKED — DO NOT REDESIGN
|
||||
The Agent-Sudo design is fixed in agent_sudo_design_decisions.md. Your job is only to create the schema below. Do not add, rename, or "improve" tiers, columns beyond the contract, or tables. If the contract seems wrong, record it in notes and proceed with the contract as written.
|
||||
|
||||
## Use the shared block (verified facts, postgres access, wrap-up, loop guard).
|
||||
|
||||
## Contract — what must be true when you finish
|
||||
1. In the `projects` DB, a table `command_audit` exists with AT LEAST these columns (types are guidance; match intent):
|
||||
- id BIGSERIAL PK
|
||||
- ts TIMESTAMPTZ DEFAULT now()
|
||||
- server_id TEXT NOT NULL -- 'primary' | 'server-01'
|
||||
- command TEXT NOT NULL
|
||||
- decision_type TEXT NOT NULL -- 'rule_match' | 'sandbox_test' | 'ai_eval_proposal' | 'execution' | 'circuit_breaker' | 'tier4_refuse'
|
||||
- assigned_tier SMALLINT -- 0..4, null if refused pre-tier
|
||||
- matched_rule TEXT -- the SUDO.md glob/rule that matched, if any
|
||||
- host_override BOOLEAN DEFAULT false -- did a per-host override change the tier (D5)
|
||||
- evidence JSONB -- sandbox exit/stderr, ai-eval reasoning, etc.
|
||||
- human_verdict TEXT -- for ai_eval_proposal review: 'accepted'|'re_tiered'|'rejected'|null (the GOLD training label, gap-#5 item 3)
|
||||
- exit_code INTEGER
|
||||
- duration_ms INTEGER
|
||||
- rollback_taken BOOLEAN DEFAULT false
|
||||
- verify_passed BOOLEAN -- post-exec auto-verify result (tier 3)
|
||||
- training_signal BOOLEAN DEFAULT true -- include this row in the local-model training export
|
||||
- Helpful indexes: (server_id, ts), (decision_type), (assigned_tier).
|
||||
2. Confirm `agent_test_results` already exists in `api_business` (it does — just verify + print its columns; do NOT recreate).
|
||||
3. Nothing else is modified. Existing tables untouched.
|
||||
|
||||
## Reference approach
|
||||
Write the DDL as `CREATE TABLE IF NOT EXISTS ...` (idempotent) and run it via `docker exec <pg> psql -U postgres -d projects -f <file>` (use `docker cp` of a .sql file, not a heredoc — heredocs over docker exec are unreliable). Use IF NOT EXISTS everywhere so a re-run is a no-op.
|
||||
|
||||
## Pre-flight (idempotent — safe on re-run)
|
||||
- Resolve the postgres container. Confirm `projects` and `api_business` DBs are reachable.
|
||||
- `SELECT to_regclass('public.command_audit')` — if it already exists, verify its columns match the contract and only ADD missing columns (ALTER TABLE ... ADD COLUMN IF NOT EXISTS); do not drop/recreate.
|
||||
|
||||
## Abort / rollback
|
||||
- Abort if either DB is unreachable (emit failed wrap-up; touch nothing).
|
||||
- Rollback if needed = `DROP TABLE command_audit` (this table is brand-new and empty; safe). Never drop agent_test_results.
|
||||
|
||||
## Wrap-up notes must confirm: command_audit columns created/verified; agent_test_results columns; row counts (should be 0 for command_audit).
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## P1 — Incus + command-surface mirrors on server-01
|
||||
**Blast radius: server-01 sandbox-side only** (no production, no live data). `--max-turns 30` (many discrete privileged steps).
|
||||
|
||||
```
|
||||
You are an infrastructure agent building the sandbox substrate for Agent-Sudo on server-01 (192.168.1.90).
|
||||
|
||||
## DESIGN IS LOCKED — DO NOT REDESIGN (agent_sudo_design_decisions.md → D1, D7)
|
||||
Container tech = Incus. Base = images:debian/13. Storage = btrfs pool (fallback: dir backend if root is not btrfs). Network = NAT via default incusbr0 (ZERO inbound LAN exposure — the sandboxes must NOT be reachable on 192.168.1.x). Two containers, BOTH on server-01: `sandbox-server01` (mirrors server-01) and `sandbox-primary` (mirrors PRIMARY's command surface, but runs on server-01 — no testing ever happens on primary). Limits each: 2 vCPU / 2GB RAM / 10GB disk. Mirror fidelity = COMMAND-SURFACE only (same OS/packages/paths/users/sudoers STRUCTURE — never secrets, never live data, never running prod containers).
|
||||
|
||||
## Use the shared block. Privileged server-01 ops go through sudo-bridge-server01 per the shared block's rules. HEED the tier-2-NTFY-broken warning: installing Incus needs root apt — if `apt-get install -y incus` cannot run as a tier-1 allowlist entry, DO NOT block on tier-2; record it under actions_failed as "needs manual authorization: <cmd>" and continue with whatever you can (e.g. produce the inventory + build script) so the reviewer can authorize the installs in one batch.
|
||||
|
||||
## Contract — what must be true when you finish (or be cleanly deferred with a manual-auth list)
|
||||
1. **Read-only host inventory captured** (the mirror spec, P1 prerequisite): for BOTH hosts — `dpkg --get-selections`, users/uids + groups + sudoers STRUCTURE (never secret values), and the relevant dir tree skeleton (`/opt/appdata`, `/data/coolify`, `/etc/...` — directory structure/paths only, NOT file contents). Save each host's inventory to a file on server-01 under `/opt/appdata/docker/agent-sudo/inventory/{primary,server-01}.json`. (Primary inventory is read-only from primary; you may gather it via the shared block's primary access.)
|
||||
2. **Incus installed + initialized** on server-01: btrfs storage pool created (or `dir` fallback, noted), `incusbr0` NAT bridge present, both operations idempotent.
|
||||
3. **`build-sandbox.sh <primary|server-01>`** written to `/opt/appdata/docker/agent-sudo/build-sandbox.sh` — ONE parameterized script that, given a host arg, launches/refreshes the matching Incus container from images:debian/13, applies that host's package set + path skeleton + users/sudoers structure from the inventory, sets the 2vCPU/2GB/10GB limits, and attaches only incusbr0. Re-running it rebuilds the container from baseline (D1 script-rebuildable; D5 loop will later append missing deps to this script).
|
||||
4. **Both containers built** and confirmed: `incus list` shows `sandbox-server01` and `sandbox-primary` RUNNING, each with NO forwarded LAN ports, each able to reach the internet (apt) but not reachable from 192.168.1.x.
|
||||
|
||||
## Reference approach
|
||||
Prefer `incus` over legacy lxc. Gather inventories with read-only commands (dpkg, getent passwd/group, `cat /etc/sudoers.d/` STRUCTURE — pattern the rules, do not copy secrets). Generate build-sandbox.sh so it is declarative and re-runnable. Verify isolation by confirming the container's IP is on the incusbr0 subnet and no `incus config device` proxy/nic maps a LAN port.
|
||||
|
||||
## Pre-flight (idempotent)
|
||||
- `which incus` — skip install if present. `incus storage list` / `incus network list` — skip create if present. `incus list` — if a target container exists, refresh via build-sandbox.sh rather than duplicate.
|
||||
- Confirm server-01 root fs is btrfs (`stat -f -c %T /`) to decide pool backend.
|
||||
|
||||
## Abort / rollback
|
||||
- Rollback = `incus delete --force sandbox-primary sandbox-server01` and remove the btrfs pool; nothing production is affected.
|
||||
- Abort (record + stop) if: btrfs AND dir backend both fail; or Incus install needs tier-2 auth you cannot get (defer with manual-auth list).
|
||||
|
||||
## Wrap-up notes must confirm: incus version; pool backend chosen; both containers' isolation (no LAN port, on incusbr0); build-sandbox.sh path; any "needs manual authorization" install commands.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## P2 — Agent-Sudo service (drop-in sudo-bridge replacement)
|
||||
**Blast radius: NONE while built** (binds 8084; old bridge keeps 8082). `--max-turns 25`.
|
||||
|
||||
```
|
||||
You are a service-implementation agent. Build the Agent-Sudo Python service — a DROP-IN replacement for sudo-bridge — as source in a Gitea repo. Do NOT deploy it live in this phase (that is P4).
|
||||
|
||||
## DESIGN IS LOCKED — DO NOT REDESIGN (agent_sudo_design_decisions.md → D4, D5, D8, D9)
|
||||
Tier model 0–4 (0 auto-safe / 1 auto-reversible / 2 sandbox-first / 3 snapshot+execute / 4 hard-refuse). NO human-approval gate — the old tier-2 NTFY approve/deny is ELIMINATED, not ported. Matching engine = ordered rule list, most-specific-first (first-match-wins), fnmatch globs, regex danger-veto = auto-Tier-4, optional per-host tier override + verb class per rule. Miss behavior: primary → hard-refuse (Tier-4); server-01 → verb-heuristic default. Dumb root executor + unprivileged brain stays (separation of privilege). Response contract identical to today's bridge.
|
||||
|
||||
## Use the shared block.
|
||||
|
||||
## Contract — what must be true when you finish
|
||||
1. A Gitea repo (e.g. `agent-sudo` under Backtalk6858, mirror the existing sudo-bridge repo's layout) contains:
|
||||
- `app.py` — unprivileged FastAPI brain. Endpoints preserve the OLD contract EXACTLY: `POST /exec` (Bearer) → `{exit_code, output, ...}`; `GET/POST /allowlist`; `GET /health`. REMOVE `/approve/{token}` and `/deny/{token}` and all APPROVAL_TIMEOUT logic (D4). A Tier-4 refuse returns 403 (same shape as today's allowlist-miss). Tiers 0–3 execute without any human gate.
|
||||
- `daemon` (root executor) — keep the existing dumb-root-daemon model: independent re-validation of danger-veto + allowlist before running; `subprocess.run(shlex.split())`, no shell. It must NOT trust the brain — it re-checks.
|
||||
- `SUDO.md` — the rule file: ordered globs → tier, with optional `host:` override and `verb:` class. Migrate the current allowlist.json into it per the D4 remap (old tier-1 → new 0/1 by read-vs-write; old tier-2 → new 2 or 3 by reproducible-vs-live-service; danger vetoes → 4). Seed with the researched common-command corpus (D5) — but every seeded rule with any uncertainty starts in `proposed` (inactive) state.
|
||||
- Config for `server_id` ('primary'|'server-01'), the 8084 bind, and audit → `command_audit` (projects DB) with JSONL-first→Postgres fail-soft (same resilience as today).
|
||||
- `docker-compose.yml` from the server-01 template (plain compose, host-LAN-bound, no Coolify), + `Jenkinsfile` for build→push→deploy.
|
||||
2. Every decision path writes a `command_audit` row with the right `decision_type` (rule_match / execution / tier4_refuse / …) and training fields (P0 schema, gap-#5 item 3).
|
||||
3. Unit-level self-check: a small `test_classify.py` that asserts representative commands land in the expected tier per host (read → 0; reversible write → 1; reproducible unknown → 2; docker/systemctl live-op → 3; danger regex → 4; primary miss → 4; server-01 miss → verb-heuristic). Log the run to `agent_test_results` (api_business) per the testing methodology (Structure + Smoke levels apply here).
|
||||
|
||||
## Reference approach
|
||||
Start from the CURRENT sudo-bridge app.py + sudo_bridge_daemon.py as the skeleton (same repo layout, same daemon socket model, same audit resilience) and SUBTRACT the approval-gate, ADD the 0–4 tiering + per-host override + verb-heuristic + SUDO.md loader. You are refactoring a known-good service, not greenfielding. Do not paste a giant literal here — read the existing files, then write the evolved versions.
|
||||
|
||||
## Pre-flight (idempotent)
|
||||
- Read the existing sudo-bridge repo/files first (structure + contract). Confirm the 0–4 remap against the live allowlist.json entries.
|
||||
- Confirm 8084 is free on both hosts (design-doc D8 said 8083 — that is WRONG, bitwarden-bridge owns it).
|
||||
|
||||
## Abort / rollback
|
||||
- This phase writes only to a Gitea repo + runs a local classifier test. Rollback = delete the repo/branch. Nothing runs on a host. Abort if the old bridge source can't be read (needed as the contract reference).
|
||||
|
||||
## Wrap-up notes must confirm: repo URL; endpoints present/removed; SUDO.md rule count (active vs proposed); test_classify pass/fail per tier; agent_test_results row id.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## P3 — Vault AppRole + Timeshift automation + AI evaluator
|
||||
**Blast radius: server-01 config + additive Vault policy** (existing roles untouched). `--max-turns 25`.
|
||||
|
||||
```
|
||||
You are a security-infrastructure agent. Stand up Agent-Sudo's autonomous-security pieces (agent_sudo_design_decisions.md → D5, D9). NOTE: Bitwarden and OS-sudo-password are DROPPED from the design — do not build them.
|
||||
|
||||
## DESIGN IS LOCKED — DO NOT REDESIGN
|
||||
Security = constrain-not-gate + execute-vs-expand split. Scoped short-TTL Vault AppRole (reuse existing rotate-and-revoke machinery; policy reads ONLY the one Agent-Sudo secret path). Signed/read-only SUDO.md via Vault transit (allowlist can't be silently widened). Scoped-undo rollback (file backup+restore / package record+remove / container via Jenkins redeploy) captured BEFORE execution; Timeshift snapshot on Tier-3; NEVER auto-restore Timeshift on primary (D3). AI evaluator produces a PROPOSED tier + audit evidence, writes the rule INACTIVE on both hosts, fires a LOW-priority NTFY (non-blocking — this is the async config review, NOT an execution gate).
|
||||
|
||||
## Use the shared block.
|
||||
|
||||
## Contract — what must be true when you finish
|
||||
1. **Vault:** a new `agent-sudo` policy (read-only on exactly one secret path, e.g. `secret/data/agent-sudo`) + an AppRole bound to it, created via the rotate-and-revoke playbook (orphan tokens, revoke-self). Existing policies/roles (claude-policy, n8n-*) are provably untouched. Smoke-test: login → read the one path → confirm it CANNOT read another path → revoke.
|
||||
2. **SUDO.md signing:** SUDO.md is signed with Vault transit; the daemon verifies the signature before loading (tamper → refuse to load, alert). Provide the sign + verify helpers.
|
||||
3. **Scoped-undo library:** a module the service calls to capture a command-specific undo BEFORE a Tier-1/3 execution (file backup, package record, container-redeploy marker). If undo can't be captured → refuse to execute (per gap-#5 blast-radius).
|
||||
4. **Timeshift automation:** take-snapshot helper for Tier-3 on server-01; on primary it may TAKE a snapshot but MUST refuse to auto-restore (hard-coded host guard).
|
||||
5. **AI evaluator:** given an unknown reproducible command → route to the sandbox (P1) → analyze audit → emit a PROPOSED tier + evidence into SUDO.md (inactive) + write a `command_audit` ai_eval_proposal row + fire ONE low-priority NTFY pointing at the review conversation. It must be structurally impossible for the evaluator to mark a rule `active` (only human review does that — execute-vs-expand split).
|
||||
|
||||
## Reference approach
|
||||
Reuse the existing AppRole tooling verbatim (playbook_vault_token_rotation Step 0). For transit, use the existing Vault transit engine. Keep the evaluator's "propose only" boundary enforced in code (no active-flag write path).
|
||||
|
||||
## Pre-flight (idempotent)
|
||||
- Check whether the agent-sudo policy/role already exist (skip-create). Confirm Vault transit engine is enabled (enable idempotently if not).
|
||||
- Confirm Timeshift present on server-01 (it is) and identify primary's snapshot config WITHOUT enabling any auto-restore.
|
||||
|
||||
## Abort / rollback
|
||||
- Rollback = delete the agent-sudo policy + role (additive — existing auth unaffected), remove the helper scripts. Abort if AppRole login smoke-test fails, or if the least-privilege check shows the role can read a second path (security failure — do not proceed).
|
||||
|
||||
## Wrap-up notes must confirm: policy least-privilege proof (can read own path, cannot read another); transit sign+verify works; scoped-undo capture tested; primary auto-restore guard verified; evaluator cannot self-activate a rule.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## P4 — Deploy both servers (parallel), run the 4-level gate
|
||||
**Blast radius: server-01 = the only autonomous live flip; primary flip is MANUAL/user** (D8). `--max-turns 20`.
|
||||
|
||||
```
|
||||
You are a deployment + validation agent. Deploy Agent-Sudo in PARALLEL with the old bridge and run the full 4-level test gate. server-01 FIRST; primary is user-manual.
|
||||
|
||||
## DESIGN IS LOCKED — DO NOT REDESIGN (agent_sudo_design_decisions.md → D8)
|
||||
Order: server-01 FIRST, primary LAST. Agent-Sudo stands up on port 8084 while the old bridge keeps serving 8082 UNTOUCHED. Shadow-validate (4-level gate on 8084 + replay known-safe commands through BOTH 8082 and 8084 and diff results). Atomic swap ONLY when green: flip Agent-Sudo to 8082, demote old bridge to 8084 hot-standby (kept, not deleted), 14-day retention. server-01 swap = autonomous. **PRIMARY swap = MANUAL by the user — you must NOT flip primary; you prepare it and hand off.**
|
||||
|
||||
## Use the shared block.
|
||||
|
||||
## Contract — what must be true when you finish
|
||||
1. **server-01:** Agent-Sudo deployed on 8084 via Jenkins/Gitea (plain compose). 4-level gate run and logged to `agent_test_results`:
|
||||
- L1 Structure (compose valid, container up, /health 200) · L2 Deployment (endpoints present, auth works) · L3 Smoke (a tier-0 read, a tier-1 reversible write in sandbox) · L4 Assertion (replay a set of known-safe commands through 8082 AND 8084, assert identical exit_code/output; assert a danger command → 403 tier-4; assert removed /approve endpoint → 404).
|
||||
- If ALL green → atomic swap on server-01 (8084→8082, old bridge→8084 standby). If ANY red → do NOT swap; leave 8082 serving; report.
|
||||
2. **primary:** deploy Agent-Sudo on 8084 in parallel and run L1–L4 in shadow (diff against 8082) — but DO NOT swap. Produce a one-paragraph hand-off: exact manual command(s) for the user to flip primary, and the exact rollback command (flip back to old bridge on 8082).
|
||||
3. NTFY on pass and fail per testing methodology.
|
||||
|
||||
## Reference approach
|
||||
Reuse the existing Jenkins job pattern for the old bridge (Jenkinsfile.server01 already exists in the sudo-bridge repo) as the deploy mechanism, pointed at the new image. Replay set = the current allowlist.json's known-safe (read) entries — these must behave identically on both ports.
|
||||
|
||||
## Pre-flight (idempotent)
|
||||
- Confirm 8084 free on the target host. Confirm old bridge on 8082 is healthy (the safety net) BEFORE deploying alongside. Confirm P0/P1/P2/P3 artifacts exist.
|
||||
|
||||
## Abort / rollback
|
||||
- server-01 rollback = flip 8082 back to the old bridge (one Jenkins/compose action); Agent-Sudo returns to 8084. Old bridge retained 14 days.
|
||||
- ABORT the swap on ANY L4 divergence on known-safe replay. Never touch primary's 8082. Abort if old bridge isn't healthy at start (no safety net).
|
||||
|
||||
## Wrap-up notes must confirm: server-01 gate results L1–L4 + swap done/blocked; primary shadow results + the exact manual swap + rollback commands for the user; agent_test_results row ids.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## P5 — Decommission old sudo-bridge
|
||||
**Blast radius: removes the fallback** — runs ONLY after a clean 14-day window + explicit user go. `--max-turns 12`.
|
||||
|
||||
```
|
||||
You are a decommission agent. Retire the OLD sudo-bridge on both servers after Agent-Sudo has run clean for the retention window. DO NOT run this without an explicit user go + a clean window.
|
||||
|
||||
## DESIGN IS LOCKED — DO NOT REDESIGN (agent_sudo_design_decisions.md → D8; project_coolify_traefik_retirement)
|
||||
Old bridge has been on 8084 hot-standby since P4's swap, 14-day retention. Coolify is RETIRED (control plane down since 2026-06-25) — there is NOTHING to deregister and nothing will resurrect a removed container. Decommission = plain docker stop + rm.
|
||||
|
||||
## Use the shared block.
|
||||
|
||||
## Contract — what must be true when you finish
|
||||
1. Pre-condition PROVEN before removing anything: Agent-Sudo has been serving 8082 on the target host for ≥14 days with NO Tier-3 incident in `command_audit` and a healthy /health. If not proven → abort, remove nothing.
|
||||
2. On each host (server-01 first, primary last — primary only with explicit user confirmation): `docker stop` then `docker rm` the old `sudo-bridge*` container; optionally remove its `/data/coolify/services/<uuid>/` dir; remove the old image ONLY if unused. The old bridge's restart policy is `unless-stopped` — a removed container is NOT recreated (Coolify is down), so stop+rm is permanent and clean.
|
||||
3. Update projects id=176 status → completed; note the decommission in context.md.
|
||||
|
||||
## Pre-flight (idempotent)
|
||||
- Query `command_audit` for the retention window: any circuit-breaker trips or Tier-3 failures? Confirm Agent-Sudo /health on 8082. Confirm the old bridge is the 8084 standby (not still on 8082).
|
||||
|
||||
## Abort / rollback
|
||||
- If ANYTHING in the window looks wrong → abort, keep the old bridge. "Rollback" after removal = the retained image can be re-pulled + re-deployed to 8082 (so keep the image until the window is unambiguously clean).
|
||||
- Never decommission primary without explicit user confirmation in the run request.
|
||||
|
||||
## Wrap-up notes must confirm: window-clean proof (incident count = 0); what was stopped/removed per host; image retained or removed; id=176 status.
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user