T-TOFK-command-seam-streaming-doors
State: closed/done · Impact: medium · Complexity: medium
The command seam is capture-only, so every site that streams live output
(stdio: "inherit") or manages a long-running process spawns on its own under an
@sdlc:ignore-spawn exemption. Those sites can’t take a fake runner in tests, and
they each hand-roll timeouts, cwd, env and failure handling. Giving the seam
streaming and managed-process doors makes the exemption rare and meaningful again.
solutions/ontological/lib/util/command.ts re-exports @sksizer/command-seam’s
capture doors (spawnRunner/runChecked and their async twins), and its header
says streaming and long-lived sites “stay on their own spawns”. On main, 8 files
carry 9 @sdlc:ignore-spawn markers. Paths are under solutions/ontological/
unless they start at the repo root:
lib/services/dashboard/server.ts (two), lib/services/docs/site/build.ts,
lib/services/docs/site/subsites.ts, lib/services/host/hosts/terminal.ts,
lib/services/quality/ops/run.ts, scripts/release-lib.ts, scripts/supervise.ts,
and the repo-root scripts/install-desktop-apps.ts. The unmerged stack adds more
once it lands, among them workflow/ops/run.ts, session/ops/attach.ts and
host/hosts/codex.ts; re-take the inventory when starting.
solutions/ontological/scripts/check_command_seam.sh matches raw spawn names
textually. It already ignores member calls (deps.spawnSync(...)), but it can’t
tell a bare call of a locally named function (spawnSync('orca'), where
spawnSync is an injected parameter) from a real child_process call. That
bare-call false positive is what the Router hit (commit c9d173300).
Proposed
Section titled “Proposed”@sksizer/command-seam exports a streaming door, whose run inherits stdio and
resolves to { exitCode }, and a managed-process door, which returns a handle
with pid, kill() and an exited promise. Each has the same injectable
factory shape as spawnRunner, and async variants exist where callers need
them. The streaming and long-running sites use these doors, and only genuinely
unrepresentable sites keep @sdlc:ignore-spawn.
Approach
Section titled “Approach”- Add the two doors, plus fakes for tests, to
packages/ts/command-seam/.solutions/ontologicaldepends on it viaworkspace:*, so this lands in the same branch with no publish step. Publishing a new version for the external consumers that pin^0.1.0is a separate follow-up. - Re-export them from
lib/util/command.tsand update its header comment. - Move each exempted site onto the matching door, with a unit test per site using the fake.
- Make
check_command_seam.shimport-aware: flag a barespawnSync(/spawn(/exec*(/fork(only in a file that pulls that name fromchild_processornode:child_process, whether by ESimportor by a CommonJSrequire(...)destructure (solutions/ontological/scripts/build-npm-wrapper.tsuses the latter), and always flagBun.spawn*. Missing therequireform would be a false negative, which is worse than the false positive being fixed. A locally named function or parameter that happens to share the name then passes.
| Area | Note |
|---|---|
packages/ts/command-seam/ | the new doors and their fakes (workspace package) |
solutions/ontological/lib/util/command.ts | re-exports and header |
solutions/ontological/lib/services/ | the exempted streaming and long-running sites |
solutions/ontological/scripts/ | check_command_seam.sh’s bare-call false positive; release-lib.ts and supervise.ts sites |
scripts/install-desktop-apps.ts | repo-level streaming site |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
@sksizer/command-seamexports a streaming door and a managed-process door, each with a test fake, re-exported fromlib/util/command.ts. - AC-2:
grep -rln '@sdlc:ignore-spawn'oversolutions/ontological/lib/,solutions/ontological/scripts/(excludingcheck_command_seam.shitself) and the repo-rootscripts/lists at most 3 files, each with a comment saying why the doors don’t fit. - AC-3: every migrated site has a unit test that injects the fake and runs no real process.
- AC-4:
check_command_seam.shpasses on a file that calls a parameter namedspawnSyncbare with nochild_processimport, and still fails on a file that importsspawnSyncfromnode:child_processand calls it, and on one that destructures it fromrequire('node:child_process').
Out of scope
Section titled “Out of scope”- Embedding PTYs or terminal emulation (hosts link out to the terminal).
- Changing the capture doors’ existing API.
Dependencies
Section titled “Dependencies”- none
Discovery context
Section titled “Discovery context”- Raised while reviewing the Router MVP (sdlc/router commit c9d173300), where
injected
spawnSync/spawnAsyncdependencies called bare had to be renamed to avoid the guard’s false positive.