Host abstraction, phase 1b: container lifecycle and exit watching behind the host layer #75

Merged
jknapp merged 4 commits from refactor/host-lifecycle into main 2026-11-13 22:29:32 +00:00
Owner

Phase 1b of the AWS host design (docs/superpowers/specs/2026-11-13-aws-ecs-host-design.md, "Host abstraction"). Refactor only; behaviour should not change. It finishes phase 1: apart from base-image migration, nothing outside host/ and docker/ knows the container runs on Docker.

What changed

  • Lifecycle behind host::lifecycle::ProjectHost (one Docker variant). It owns ensure_started, find_current, stop, remove, discard_project_state and remove_legacy_leftovers.
    • ensure_started is the old find → snapshot-and-recreate → create → start block from start_project_container_locked, moved verbatim with its comments.
    • Start, stop, Reset, remove and reconcile keep only the host-neutral steps: validation, status, sessions, auth bridge and syncs.
  • Exit watcher. container_watch.rs no longer uses bollard. Docker's die stream, ping and the positive-exit inspect moved to docker/events.rs and are reached through host::watch (ExitSource, ContainerTarget::has_exited). An AWS host can feed it by polling.
  • No more bare container ids. The auth bridge, browser view (including proxy and popout), file viewer, marketplace sync and the file/inspect commands now take a ContainerTarget. The started target flows to the auth bridge, marketplace sync and Bedrock credential sync.
  • Helpers moved. create_container_for_project and default_docker_socket moved into docker/; migration used both, and its duplicate socket helper is gone.
  • New boundary test. Non-test code outside host/ and docker/ may not call lifecycle functions or build ContainerTarget::docker(id). Migration is the single, documented exception. I checked with a planted probe file that it flags real calls and ignores comments and test modules.
  • CLAUDE.md documents the lifecycle and watch modules.

Verification

  • cargo test --lib: 912 passed, 0 failed.
  • Live tests against Docker 29.9.3:
    • New ignored test host::lifecycle::tests::a_project_container_round_trips_through_the_host takes a throwaway project (fresh UUID) through create, start, exec as claude, stop, has_exited, restart on the same container, remove and discard. It passes, the progress messages match, and the daemon's container and volume counts are unchanged afterwards.
    • The existing live watcher test and the file-commands container-rules test also pass.
  • Independent review with git access, comparing against 3ec100e: no runtime behaviour changes. It covered start order, which errors abort and which are ignored, progress text, the Reset/remove reports, stop, reconcile, watcher semantics and container-id ordering.
  • One theoretical difference: the watcher now counts only real die reports as a productive connection for backoff. The old code counted any event. Docker's filter means only die events arrive, so this can't happen in practice.
  • Not checked: I didn't open the app's UI by hand.

Next

Phase 2: the in-container triple-c-agent and an SSM port-forward client, which an AWS ContainerTarget variant needs.

🤖 Generated with Claude Code

Phase 1b of the AWS host design (`docs/superpowers/specs/2026-11-13-aws-ecs-host-design.md`, "Host abstraction"). **Refactor only; behaviour should not change.** It finishes phase 1: apart from base-image migration, nothing outside `host/` and `docker/` knows the container runs on Docker. ## What changed - **Lifecycle behind `host::lifecycle::ProjectHost`** (one Docker variant). It owns `ensure_started`, `find_current`, `stop`, `remove`, `discard_project_state` and `remove_legacy_leftovers`. - `ensure_started` is the old find → snapshot-and-recreate → create → start block from `start_project_container_locked`, moved verbatim with its comments. - Start, stop, Reset, remove and reconcile keep only the host-neutral steps: validation, status, sessions, auth bridge and syncs. - **Exit watcher.** `container_watch.rs` no longer uses bollard. Docker's `die` stream, ping and the positive-exit inspect moved to `docker/events.rs` and are reached through `host::watch` (`ExitSource`, `ContainerTarget::has_exited`). An AWS host can feed it by polling. - **No more bare container ids.** The auth bridge, browser view (including proxy and popout), file viewer, marketplace sync and the file/inspect commands now take a `ContainerTarget`. The started target flows to the auth bridge, marketplace sync and Bedrock credential sync. - **Helpers moved.** `create_container_for_project` and `default_docker_socket` moved into `docker/`; migration used both, and its duplicate socket helper is gone. - **New boundary test.** Non-test code outside `host/` and `docker/` may not call lifecycle functions or build `ContainerTarget::docker(id)`. Migration is the single, documented exception. I checked with a planted probe file that it flags real calls and ignores comments and test modules. - **CLAUDE.md** documents the lifecycle and watch modules. ## Verification - `cargo test --lib`: 912 passed, 0 failed. - **Live tests against Docker 29.9.3:** - New ignored test `host::lifecycle::tests::a_project_container_round_trips_through_the_host` takes a throwaway project (fresh UUID) through create, start, exec as `claude`, stop, `has_exited`, restart on the same container, remove and discard. It passes, the progress messages match, and the daemon's container and volume counts are unchanged afterwards. - The existing live watcher test and the file-commands container-rules test also pass. - **Independent review with git access**, comparing against 3ec100e: no runtime behaviour changes. It covered start order, which errors abort and which are ignored, progress text, the Reset/remove reports, stop, reconcile, watcher semantics and container-id ordering. - **One theoretical difference:** the watcher now counts only real `die` reports as a productive connection for backoff. The old code counted any event. Docker's filter means only `die` events arrive, so this can't happen in practice. - **Not checked:** I didn't open the app's UI by hand. ## Next Phase 2: the in-container `triple-c-agent` and an SSM port-forward client, which an AWS `ContainerTarget` variant needs. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jknapp added 4 commits 2026-11-13 19:23:56 +00:00
container_watch keeps all of its logic (sweep, compare-and-set settle,
backoff, cancellation) but no longer touches bollard: reachability, the
death stream and the positive-exit check come from host::watch::ExitSource
and ContainerTarget::has_exited, implemented for Docker in docker/events.rs.
An AWS host can supply deaths by polling. container_watch.rs leaves the
boundary test's allowlist.

Verified with the live end-to-end watcher test against a real daemon.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Functions that took a bare container id and built
`ContainerTarget::docker(id)` internally now take a target, and their
callers resolve one from the project (`ContainerTarget::for_project`) or
carry the one they already hold. Long-lived holders (auth bridge poller,
browser-view proxy/session, file viewer commands) store the target.

Collapses the string/target wrapper pairs: `detect::detect` now takes a
target (`detect_in` removed) and `tunnel_connection_with_prelude` takes a
target (`tunnel_into_with_prelude` removed).

No behaviour change. Two temporary Docker constructions remain on the
container-start path, marked "phase 1b": the auth bridge start in
`start_project_container_locked` and `marketplace::spawn_project_sync`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Move the project container lifecycle behind host::lifecycle::ProjectHost
Secret Scan / scan (push) Successful in 7s
Build App (Preview) / compute-version (pull_request) Successful in 6s
Secret Scan / scan (pull_request) Successful in 6s
Build App (Preview) / create-release (pull_request) Successful in 2s
Build App (Preview) / build-macos (pull_request) Successful in 3m14s
Build App (Preview) / test (pull_request) Successful in 4m24s
Build App (Preview) / build-windows (pull_request) Successful in 6m19s
Build App (Preview) / build-linux (pull_request) Successful in 8m32s
Build App (Preview) / prune-previews (pull_request) Successful in 14s
7c004fed46
Phase 1b of the AWS host work. ProjectHost (one Docker variant) owns
ensure_started (find → snapshot-and-recreate → create → start, moved
verbatim from start_project_container_locked), find_current, stop, remove,
discard_project_state and remove_legacy_leftovers. Start, stop, Reset,
remove and reconcile keep only host-neutral steps and ask the host for the
rest; the started ContainerTarget now flows to the auth bridge, marketplace
sync and Bedrock credential sync.

- create_container_for_project and default_docker_socket move to docker/
  (migration used both; its duplicate socket helper is gone).
- sync_bedrock_credentials and marketplace::spawn_project_sync take a
  ContainerTarget.
- New boundary test: no lifecycle calls or ContainerTarget::docker(id) in
  non-test code outside host/ and docker/ (migration excepted).
- Ignored live test round-trips a throwaway project container through the
  host; passes against Docker 29.9.3 and leaves nothing behind.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jknapp merged commit 36a6c869c9 into main 2026-11-13 22:29:32 +00:00
jknapp deleted branch refactor/host-lifecycle 2026-11-13 22:29:32 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: CyberCoveLLC/Triple-C#75