<objective> Build the AgentOS supervisor behind its port and the hardening profile verifier. No concrete driver binding in this plan.
Purpose: Deliver RT-04 supervision with bounded crash policy and the RT-01 conformance check that gates container use. Output: Supervisor module, profile verifier, one fake, and two test files. </objective>
<execution_context> @~/.claude/gsd-core/workflows/execute-plan.md @~/.claude/gsd-core/templates/summary.md </execution_context>
<context> @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/phases/03-container-runtime-and-bridges/03-CONTEXT.md @.planning/phases/03-container-runtime-and-bridges/03-PATTERNS.md </context>
<tasks>
<task type="tdd"> <name>Task 1: AgentOsDriver port and crash-loop supervisor</name> <files>src/runtime/agentos.ts, test/support/fake-agentos.ts, test/runtime/agentos-supervisor.test.ts</files> <read_first> - src/core/ports.ts (from plan 03-01; port style: interface only, no environment field) - src/core/backoff.ts (from plan 03-01; base 500 ms, cap 8000 ms, full jitter with injected random and sleep) - src/core/timeout.ts (every driver call raced against a timeout per D-59 and QUAL-01) - test/support/fake-execution.ts (scripted outcomes plus per-call recording to mirror) </read_first> <behavior> - Test: crash (alive false or start reject) restarts after a backoff from the shared schedule - Test: fifth consecutive failure throws AGENTOS_CRASH_LOOP (retryable); counter resets after 60000 ms of continuous alive (injected clock) - Test: hung driver call times out instead of hanging the supervisor - Test titles cite RT-04 </behavior> <action>Write the failing tests first per behavior, then implement per D-59 and D-60 in src/runtime/agentos.ts: AgentOsDriver port with start(), alive(), stop(); the supervisor owns restart policy with maximum 5 consecutive attempts, the fifth holding and throwing AGENTOS_CRASH_LOOP, the counter resetting after 60000 ms of continuous alive, and every driver call raced against a timeout. AgentOS lifecycle goes only through the supervisor and its port; nothing else starts or stops the process. The concrete driver is not written: the library registry entry is unverified (OQ-P3-06 default: no dependency added; an npm view read would precede any add) and the in-sandbox channel is undocumented (OQ-P3-05 default: port only, binding not written, reported as blocked). Hosted mode is not implemented (S-11); native sidecar stays open (OQ-P3-07). Then implement test/support/fake-agentos.ts with scripted start, alive, and stop outcomes and per-call recording.</action> <acceptance_criteria> - test/runtime/agentos-supervisor.test.ts asserts bounded restart, AGENTOS_CRASH_LOOP on the fifth consecutive failure, counter reset after the stable window, and timeout on a hung driver - No file in this plan imports or names an AgentOS package </acceptance_criteria> <verify> <automated>npx tsc -p tsconfig.json && node --test dist/test/runtime/agentos-supervisor.test.js</automated> <fails_when>non-zero exit, or the summary shows 0 passed</fails_when> </verify> <done>Supervisor green with bounded crash policy and an explicit unbound driver gap.</done> </task>
<task type="tdd"> <name>Task 2: Hardening profile and fail-closed verifier</name> <files>src/runtime/hardening.ts, test/runtime/hardening.test.ts</files> <read_first> - src/core/states.ts (freeze plus as const pattern for the frozen HARDENING_PROFILE) - src/core/policy.ts checkExecPolicy (decision-on-shape style to mirror for verifyInspect) - docs/integration/podman.md Q1 and Q2 (hardening flags and limits sourced from the man pages; OQ-P3-14 defaults to fail-closed verification) </read_first> <behavior> - Test: one failing case per D-51 property (privileged, rootless, CapDrop/CapAdd, no-new-privileges, read-only root, tmpfs /tmp and /run, PID 512, memory 1 GiB, CPU 2, network none, digest, mount set, socket path) - Test: conforming inspect yields an empty list; missing or unknown field fails closed - Test titles cite RT-01 </behavior> <action>Write the failing tests first per behavior, then implement HARDENING_PROFILE and verifyInspect(profile, inspect) in src/runtime/hardening.ts per D-51: not privileged; rootless from info; CapDrop exactly ["ALL"] with empty CapAdd; no-new-privileges set; read-only root with tmpfs at /tmp and /run; PID 512, memory 1 GiB, CPU 2 (Claude-discretion defaults held in this one constant); network none; image digest equals the pinned digest; mount set exactly the canonical workspace root and the realm state dir with no mount path being or containing docker.sock or podman.sock. Returns the failed property names, empty on conformance; any unknown or missing field counts as a failure. Freeze the profile.</action> <acceptance_criteria> - test/runtime/hardening.test.ts covers every D-51 property plus the conforming-empty and missing-fails-closed cases - No capability beyond the ALL drop is verified (per the D-51 no-capability-verified rule) </acceptance_criteria> <verify> <automated>npx tsc -p tsconfig.json && node --test dist/test/runtime/hardening.test.js</automated> <fails_when>non-zero exit, or the summary shows 0 passed</fails_when> </verify> <done>Verifier green with fail-closed semantics on every property.</done> </task>
</tasks>
<threat_model>
Trust Boundaries
| Boundary | Description |
|---|---|
| driver -> supervisor | Untrusted liveness answers cross into restart policy; a lying alive cannot suppress the timeout race |
| libpod inspect -> verifier | Untrusted container description crosses into the conformance decision; missing fields must fail, never default to safe |
STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|---|---|---|---|---|---|
| T-03-16 | D (Denial of Service) | src/runtime/agentos.ts | medium | mitigate | Bounded backoff with 5-attempt cap then AGENTOS_CRASH_LOOP; every driver call raced against a timeout so a hung driver cannot hang supervision (QUAL-01) |
| T-03-17 | T (Tampering) | src/runtime/hardening.ts | high | mitigate | verifyInspect fails closed on any unknown or missing field; digest must equal the pinned digest; socket-bearing mounts rejected by path rule |
| T-03-18 | E (Elevation of Privilege) | src/runtime/hardening.ts | high | mitigate | Privileged, capability, no-new-privileges, read-only, and user checks all enforced; network none so no egress path exists |
| </threat_model> |
<verification> npx tsc -p tsconfig.json && node --test dist/test/runtime/agentos-supervisor.test.js dist/test/runtime/hardening.test.js; grep both test files for RT-04 and RT-01. </verification>
<success_criteria> Crash loop bounded with stable-window reset, and every hardening property verified fail-closed. </success_criteria>
<output> Create .planning/phases/03-container-runtime-and-bridges/03-07-SUMMARY.md when done </output>