Phase 4 of PRD 0015. End-to-end test against real Docker:
- Brings up a real pipelock sidecar via the production
DockerPipelockProxy bring-up + pipelock_tls_init.
- Calls apply_allowlist_change to add a new host.
- Polls the live /etc/pipelock.yaml until the new host shows up
(bridging the docker-restart window).
- Verifies api_allowlist contains both old + new hosts and
tls_interception block is preserved.
- Smaller cases: invalid hostname raises, missing sidecar raises,
fetch_current_allowlist returns one-per-line format.
Skipped under GITEA_ACTIONS because pipelock_tls_init bind-mounts a
host path that doesn't share fs in the runner, matching the
existing pipelock smoke test's skip pattern.
Drive-by fix: fetch_current_yaml now uses `docker cp` (daemon-API
tarball copy) instead of `docker exec cat` because the pipelock
image is distroless and has no shell utilities.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Phase 3 of PRD 0015. Adds the proactive `pipelock edit` path,
mirroring routes edit from PRD 0014:
- discover_pipelock_slugs() lists running pipelock sidecars.
- operator_edit_allowlist(slug, new) wraps apply_allowlist_change
and writes an audit entry tagged ACTION_OPERATOR_EDIT.
- New 'p' keybinding in the main TUI: discover slugs, prompt if
multiple, fetch current allowlist, open in $EDITOR, apply on
save.
- Extracts shared scaffolding into _operator_edit_flow used by
both routes-edit and pipelock-edit — DRY without sacrificing
the per-verb status-line copy.
- Footer updated.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Phase 2 of PRD 0015. dashboard.approve() now dispatches on the
proposal's tool:
cred-proxy-block → apply_routes_change (from PRD 0014)
pipelock-block → apply_allowlist_change (new in PRD 0015)
capability-block → no-op (lands in PRD 0016)
PipelockApplyError joins CredProxyApplyError under the ApplyError
tuple the TUI catches: failures keep the proposal pending and the
status line surfaces the message; no response is written and no
audit entry is appended.
Tests: existing TestApproveReject stubs both apply paths; new
TestPipelockApplyWiring covers the call wiring, failure-propagation,
and real-diff-in-audit invariants.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Phase 1 of PRD 0015. New module
claude_bottle/backend/docker/pipelock_apply.py:
- fetch_current_yaml(slug): docker exec cat of the live
/etc/pipelock.yaml.
- fetch_current_allowlist(slug): parses the yaml, extracts
api_allowlist, renders as one-per-line for the operator/agent.
- parse_allowlist_content / render_allowlist_content: one-per-line
with `#` comments + blank-line tolerance, conservative hostname
validation.
- apply_allowlist_change(slug, new): parses new hosts, fetches +
parses current yaml, swaps api_allowlist, re-renders via
pipelock_render_yaml, docker cp into sidecar, docker restart.
Returns (before, after) as one-per-line strings for the audit diff.
- PipelockApplyError: caller surfaces to operator without crashing
the dashboard.
v1 uses restart, not SIGHUP — pipelock has no in-process reload
hook; adding one is the PRD's open question. Restart drops in-flight
outbound calls and the agent retries pick up the restarted proxy.
Yaml roundtrip is covered by tests: parse(render(cfg)) preserves
all fields pipelock_render_yaml emits, including tls_interception
+ passthrough_domains.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds PRD 0015, the second remediation engine in the stuck-agent
recovery flow (overview in PRD 0012, foundation in PRD 0013). Wires
the pipelock block path with restart-based reload: supervisor writes
the new allowlist on approval and restarts pipelock, proactive
pipelock edit TUI verb, pipelock audit log filled in. SIGHUP reload
for pipelock is deferred to a follow-up.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>