<objective> Build the Anda bridge: the HTTP client implementing BridgePort with shape handshake, bounded reconnect with jitter, and background state feed, plus its two fakes.
Purpose: Deliver the ANDA-02 handshake and the ANDA-03 reconnect and state feed the controller subscribes to. Output: Bridge module, fake HTTP server, in-memory BridgePort fake, and the handshake and reconnect tests. </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: AndaBridge handshake and BridgePort surface</name> <files>src/anda/client.ts, test/support/fake-anda.ts, test/anda/handshake.test.ts</files> <read_first> - src/anda/shape.ts (from plan 03-03; checkAppInformationShape is the only handshake per D-61) - src/core/ports.ts (from plan 03-01; BridgePort and BridgeEvent shapes to implement) - src/core/timeout.ts (per-attempt 2000 ms bound; the client aborts its own request on expiry per F-P3-05) - test/support/fake-execution.ts (call-log and scripted-outcome shape to mirror) </read_first> <behavior> - Test: fake server with the correct shape gives connect; wrong shape gives ANDA_VERSION_MISMATCH with exactly one request (no retry on mismatch) - Test: no credential header is sent on any request (fake records headers; OQ-P3-10 default: credentials not needed in Phase 3) - Test titles cite ANDA-02 </behavior> <action>Write the failing tests first per behavior, then implement AndaBridge in src/anda/client.ts per D-61 and D-62: connect(deadlineMs) runs one GET /.well-known/information over node:http or node:https bounded by a 2000 ms per-attempt timeout, checks the body with the plan 03-03 shape module, and returns on success; a shape mismatch throws ANDA_VERSION_MISMATCH at once with no retry. JSON only. No POST /{id} call exists anywhere in this module (OQ-P3-10 default: RPC body and credential format undocumented, not needed in Phase 3). Every timer is owned by the bridge and cancelled in close(). Then implement test/support/fake-anda.ts as a scripted HTTP server (correct shape, wrong shape, unreachable by closing the socket) recording method, path, and headers so tests assert no credential header was sent. This helper carries no requirement-ID title.</action> <acceptance_criteria> - test/anda/handshake.test.ts asserts connect on correct shape, single-request ANDA_VERSION_MISMATCH on wrong shape, and header absence - src/anda/client.ts contains no POST route string and no credential production </acceptance_criteria> <verify> <automated>npx tsc -p tsconfig.json && node --test dist/test/anda/handshake.test.js</automated> <fails_when>non-zero exit, or the summary shows 0 passed</fails_when> </verify> <done>Handshake green with fail-at-once mismatch and no credentials on the wire.</done> </task>
<task type="tdd"> <name>Task 2: Bounded reconnect with background state feed</name> <files>src/anda/client.ts, test/support/fake-bridge.ts, test/anda/reconnect.test.ts</files> <read_first> - src/core/backoff.ts (from plan 03-01; base 500 ms, cap 30000 ms, full jitter with injected random) - test/core/timeout.test.ts (liveTimers helper for asserting no timer survives close) </read_first> <behavior> - Test: unreachable engine gives ANDA_UNREACHABLE after the deadline with background retries at growing bounded delays (injected random) - Test: recovery publishes up; a mid-run failure publishes down with ANDA_UNREACHABLE and keeps retrying - Test: close() leaves no timer (liveTimers assertion) - Test titles cite ANDA-03 </behavior> <action>Write the failing tests first per behavior, then extend src/anda/client.ts per D-62: connect retries with src/core/backoff.ts until the deadline, then ANDA_UNREACHABLE; after a successful connect a background loop probes on the same backoff schedule, publishing down with ANDA_UNREACHABLE on failure while retrying in the background and up on recovery; close() cancels every timer and aborts any in-flight request (F-P3-05: no timer survives). The bridge never touches the state machine itself; it only publishes events the controller decides on (D-64, plan 03-09). Then implement test/support/fake-bridge.ts as the in-memory BridgePort (setDelay, failNext, calls() log, emit({ kind }) for up, down, mismatch; default zero latency and healthy, which the plan 03-09 startup-budget test requires).</action> <acceptance_criteria> - test/anda/reconnect.test.ts asserts exhaustion code, bounded growing delays, up and down publication, and zero surviving timers - test/support/fake-bridge.ts exposes emit plus the execution-fake control surface </acceptance_criteria> <verify> <automated>npx tsc -p tsconfig.json && node --test dist/test/anda/handshake.test.js dist/test/anda/reconnect.test.js</automated> <fails_when>non-zero exit, or the summary shows 0 passed</fails_when> </verify> <done>Reconnect green with bounded jittered retries and clean close.</done> </task>
</tasks>
<threat_model>
Trust Boundaries
| Boundary | Description |
|---|---|
| Anda engine -> bridge | Untrusted JSON body crosses into the handshake; only the anonymous information route is ever fetched |
| bridge timer -> process | Background retry timers cross into the event loop; close must cancel all of them or the process leaks |
STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|---|---|---|---|---|---|
| T-03-13 | S (Spoofing) | src/anda/client.ts | medium | mitigate | Shape mismatch fails at once with ANDA_VERSION_MISMATCH and no retry; no version claim is accepted without the documented surface (OQ-P3-09) |
| T-03-14 | I (Information Disclosure) | src/anda/client.ts | high | mitigate | No credential header produced in Phase 3; handshake test asserts header absence on every request |
| T-03-15 | D (Denial of Service) | src/anda/client.ts | medium | mitigate | Per-attempt 2000 ms timeout with request abort, deadline-bounded connect, and close() cancelling every timer; QUAL-01 holds |
| </threat_model> |
<verification> npx tsc -p tsconfig.json && node --test dist/test/anda/handshake.test.js dist/test/anda/reconnect.test.js; grep both test files for ANDA-02 and ANDA-03. </verification>
<success_criteria> Handshake fails at once on wrong shape with no credentials sent, and reconnect retries bounded with clean close. </success_criteria>
<output> Create .planning/phases/03-container-runtime-and-bridges/03-06-SUMMARY.md when done </output>