Phase 06 Plan 01: Control Channel Summary
NDJSON control-channel wire format with request ids and catalogue-code-only errors, plus 0700/0600 socket guards bounded by SOCKET_TIMEOUT_MS — no daemon logic.
Performance
- Duration: unmeasured (one execution wave)
- Started: 2026-10-22
- Completed: 2026-10-22
- Tasks: 2
- Files modified: 2 created, 0 modified
Accomplishments
src/daemon/channel.ts: CONTROL_METHODS, encode/decode helpers,sendRequest/readRequestLine/writeReplyLinebounded bywithTimeout,ensureSocketDir,chmodSocket,describeRunDirRefusal(P6-D2, P6-D3, P6-D10, P6-D11)test/daemon/channel.test.ts: 11 RT-06-titled tests covering live-socket id round-trip for all four methods, catalogue-code-only errors, malformed-line rejection, timeout bound, guards, and the structural pin- Every failure leaving the module carries an existing catalogue code only; no new error code, policy key, or dependency added
Task Commits
No per-task commits were made: this plan runs in wave 1 under shared constraints that forbid git commands for executors — the orchestrator gates, verifies (tsc + node --test), and commits. Files are left in the working tree:
- Task 1: NDJSON protocol with ids and catalogue-code errors (P6-D2, P6-D10) — uncommitted (
src/daemon/channel.ts,test/daemon/channel.test.ts) - Task 2: Socket directory guards 0700/0600 with refusal (P6-D3, P6-D11) — uncommitted (same files, extended)
Files Created/Modified
src/daemon/channel.ts- Wire format plus filesystem guards; imports node:net, node:fs, core/errors, core/timeout, session/paths onlytest/daemon/channel.test.ts- RT-06 suite: framing, error, timeout, permission, and no-socket-path cases
Decisions Made
- Transport failures that are neither timeouts nor decode refusals surface
REALM_NOT_ACTIVE(peer absent or went away);withTimeoutexpiry surfacesTOOL_TIMEOUT; malformed bytes, unknown methods, and id mismatches surfaceSCHEMA_INVALID. Rationale: the phase truth "every failure surfaces an existing catalogue ErrorCode only" leaves no room for rawECONNREFUSED/syntax errors, andREALM_NOT_ACTIVEis the closest catalogue semantic for a missing peer. - The thrown run-dir refusal stays a bare
makeRealmError("SCHEMA_INVALID")(typed errors carry nothing but the code); the "names the expected mode and the observed state" requirement lives in the puredescribeRunDirRefusalhelper, which later startup/doctor surfaces reuse for fix lines. Modes and uids are not secrets, so naming them there is safe. replyForMalformedLinereturns a reply only for actually-malformed lines with a recoverable id (null for well-formed lines and id-less garbage), so the future daemon can use it as a never-throws boundary mapper.readRequestLine/writeReplyLineare bare line-IO primitives for the accepted-socket side (plan 06-03); no listen/serve loop, no lifecycle, no reaping lives here per P6-D10.- Kept
new Promiseexecutors instead ofPromise.withResolvers():libis ES2023, which has nowithResolverstypes (ES2024), and this wave cannot run typecheck to prove an alternative compiles.
Deviations from Plan
None - plan executed exactly as written. No deviation Rules 1-4 were triggered: no bugs to auto-fix, no missing critical functionality beyond the plan, no blocking issues, no architectural changes. The items above are within-plan implementation choices under "Claude's Discretion" (helper names, exact NDJSON field layout), recorded here for the record.
Issues Encountered
- Two self-inflicted edit-range mistakes while inlining helpers temporarily broke
sendRequest's body (duplicateensureSocketDirblock, a swallowedcatchline). Both were repaired by re-reading the affected ranges and re-issuing anchored edits; the final file was re-read end to end and is coherent. No plan impact. - Style-rule reminders fired for
isRecord, tiny functions, inline casts, andPromise.withResolvers. Resolved: single-use helpers inlined; localisRecord/isControlId/isErrorCodeguards kept (the import allowlist forbids sharing a guard module, andsrc/mcp/server.tssets the local-guard precedent);ascasts replaced within-narrowing except the intentional negative-test double assertion.
Known Stubs
None - no TODO/FIXME/placeholder text, no hardcoded empty values, no unwired data sources in either file.
Threat Flags
None new - this module IS the T-06-01/T-06-02/T-06-03 mitigation surface from the plan's threat register, implemented as specified (foreign-owner/mode refusal, 0700+0600 enforcement, withTimeout on every socket op).
User Setup Required
None - no external service configuration required.
Next Phase Readiness
- Ready: plans 06-03 (daemon) and 06-04 (CLI) can build on
sendRequest,readRequestLine/writeReplyLine,ensureSocketDir,chmodSocket, andCONTROL_METHODSwithout modification. - Concern: verification was NOT run in this wave per the shared executor constraints (
tsc -p tsconfig.json && node --test dist/test/daemon/channel.test.jsplus the RT-06 title grep). Coverage statuses above areunknownso verify-work routes them to automated verification. The most type-fragile spots were reviewed by hand:mkdirSyncoptions overload resolution,process.getuidoptional typing (captured into a const before use),match[1]undernoUncheckedIndexedAccess(handled viaflatMap), andassert.oknarrowing onreply.error.
Phase: 06-cli-daemon-and-host-packagingCompleted: 2026-10-22