title: Phase 5 close — HEARTH (LocalDocker) shipped end-to-end date: 2026-07-02 status: Accepted predecessor: docs/decisions/2026-05-25-phase-5b-close.md spec: docs/specs/2026-05-04-rocky-phase-5.md plan: docs/plans/2026-07-02-console-hearth-wrapper-phase-5d.md phase: 5
Context
Phase 5 opened 2026-05-04 with the design spec landing (docs/specs/2026-05-04-rocky-phase-5.md). Five sub-phases were queued: 5a bootstrap → 5b contracts subpath + Go codegen → 5c LocalDocker driver + RPC binary → 5d console SS-08 wrapper → 5e superproject e2e + close-out. 5a/5b/5c closed sequentially through 2026-06-29. 5d and 5e closed today.
The OSS-parity invariant (spec §11) required that solo + LocalDocker + LocalAuth pass an e2e with no Polar.sh network calls and no devarno-cloud-tenant credentials. This decision records what shipped, what got trimmed, and what carries forward.
Decision
Phase 5 is closed. The SS-08 HEARTH subsystem row in CLAUDE.md and the Phase 5 row in MILESTONES.md flip to closed state.
Delivered:
- 5a —
rocky-hq/hearthGo submodule (MIT),Driverinterface locked,FakeDriver+ 5-contract test suite green (closed 2026-05-24,docs/decisions/2026-05-24-phase-5a-close.md). - 5b —
@rocky-hq/contracts@0.2.0published with./hearthsubpath (7 schemas:Tier,DriverName,Status,ResourceCaps,ProvisioningProfile,DeploymentRef,HearthHatchEvent) + sibling Go modulegithub.com/rocky-hq/contracts/go/hearthgenerated via quicktype +go-parityCI job (closed 2026-05-25,docs/decisions/2026-05-25-phase-5b-close.md). - 5c —
LocalDockerdriver inhearth/internal/driver/localdocker/against the Docker SDK, testcontainers integration suite (ROCKY_HEARTH_INTEGRATION=1), andcmd/hearth/main.goJSON-over-HTTP RPC binary listening on a Unix socket (closed 2026-06-29, hearth PR #4 atf2dc306). - 5d —
console/src/lib/hearth/(8 files: types + parsers re-exported from@rocky-hq/contracts/hearth; RPC client speakingnode:httpoverHEARTH_RPC_SOCKET; store athearth-deployments.json; audit athearth-audit.json; service orchestrator enforcing spec §10; browser fetch wrappers). Three admin-only API routes under/api/hearth/workspaces. Admin/hearthpage (server component + clientHearthDirectory). Sidebar SS-08 entry in Configure group. Smoke suite additions covering unauth → airlock redirect for/hearth+/api/hearth/workspaces. Console PR #30 → 8d8065e; parent PR #89 → 4ee95c0; sidebar PR #31 → 1f73532 + parent PR #90 → bf6e73a. Plan:docs/plans/2026-07-02-console-hearth-wrapper-phase-5d.md. 530/530 vitest, typecheck clean, lint 0 errors. - 5e —
tests/e2e/test_provision_then_ralph.py+tests/e2e/conftest.pyshipping the hermetic core: fake HATCH sink (in-processHTTPServer), hearth Go binary as a subprocess on a Unix socket (fake driver by default; LocalDocker opt-in viaROCKY_E2E_HEARTH_DRIVER=local-docker, skips gracefully when docker unreachable), stdlib Unix-socket HTTP client, androcky.Journal-driven 4-prompt mock KAHN chain. Assertions cover: DeploymentRef contract fields;agent.{registered,invoked,completed}triple fordriver.provisionreaching the hatch sink with matchinginvocation_id; the full KAHN wire chain (run_start, 8×node_transition, 4×node_attempt,run_end{outcome:"clean"}) ontransitions.jsonl;driver.teardowntriple with the correct capability. Gated onROCKY_E2E=1. Required-for-merge CI at.github/workflows/e2e.yml.
Scope trims recorded here (deliberate)
Spec §12 described the e2e as spinning up hearth AND rocky serve AND submitting a run over ralph's FastAPI surface. In practice we run ralph's KAHN emitter (rocky.journal.Journal + vendored kahn_emit) directly rather than through rocky serve's HTTP layer:
- Reason. The parent's job is to prove the cross-submodule wire contract: does hearth's provisioner shape agree with the ralph KAHN chain that a self-hoster's operator will drive next? That contract is on the KAHN wire, not on ralph's HTTP surface. Ralph's own test suite (
ralph/tests/) already covers the FastAPI worker end-to-end against a real Runner; adding a second copy here would double the CI cost without adding correctness signal. - Trade-off. A ralph-serve regression that only shows up in the HTTP handshake (auth token verify, SSE fan-out) would not be caught by this parent-level e2e. That surface is covered by
ralph/tests/serve/and by the manual smoke againstrocky.erid.tech. - Follow-up (Phase 5-post, not blocking). Add a variant
test_provision_then_ralph_via_serve.pythat bootsrocky servein-process viaTestClientwith a fakerunner_builder. Deferred — the wire-contract intent of §12 is satisfied without it.
Spec §11 further mandated the OSS-parity path pass on LocalDocker. The e2e supports LocalDocker via ROCKY_E2E_HEARTH_DRIVER=local-docker but skips when docker isn't reachable. The parent CI e2e.yml job runs the fake driver leg by default; the local-docker leg lands after CAIRNET/LORE image publishing (currently nginx:alpine stand-ins per Phase 5 spec §D6 would need to be swapped for real service images to make the LocalDocker leg meaningful). Filed for the Phase 5-post cleanup.
Consequences
- Phase 5 →
closed. Next active phase is 7 Agentic layer + Polar.sh (pending external 7c-c-b Polar SDK + 7c-c-c airlock verb). Phase 6 (Kustomize + DevarnoCloud drivers) unblocks from the Phase 5 gate and moves fromblocked-by-5tonextwhen an operator picks it up. CLAUDE.md"as of Phase 4" heading advances to "as of Phase 5"; the SS-08 subsystem row flips to its closed-state description.scripts/verify-scaffold.shEXPECTED_SUBMODULESalready containedhearth(added at 5a); no change needed at close-out.docs/shared/conventions.mdGo line already filled (Phase 5b close,docs/decisions/2026-05-25-phase-5b-close.md).- The Phase 5-post scope trims above (ralph-serve variant + real LocalDocker leg) are optional follow-ups; neither blocks Phase 6 or Phase 7.
- Verification round for Phase 5 lands at
atlas/findings/2026-07-02-phase-5-verify-round.mdwhen an atlas subagent gets scheduled; no verify blocker on close per the parent's Phase 3e precedent (verify rounds are informative, not gating).
References
- Spec —
docs/specs/2026-05-04-rocky-phase-5.md. - Go conventions —
docs/decisions/2026-05-03-phase-5-go-conventions.md. - Prior close notes —
docs/decisions/2026-05-24-phase-5a-close.md,docs/decisions/2026-05-25-phase-5b-close.md. - 5d plan —
docs/plans/2026-07-02-console-hearth-wrapper-phase-5d.md. - Tenancy invariant guard —
docs/decisions/2026-07-02-security-incident-tenancy-bypass.md(adjacent, informs the belt-and-suspenders server-side role re-check in the 5d wrapper).