refactor(subprocess): the dispose ladder moves to its one consumer
SubprocessHandle loses dispose(graces) and SubprocessDisposeGraces: the stdin-EOF→SIGTERM→SIGKILL sequence is teardown POLICY encoding one consumer's cooperation shape, not process vocabulary — the seam keeps kill/terminate/waitForExit, and waitForExit(signal?) is the quiescence probe a consumer ladder needs. dsh-subagent-acp owns disposeAcpChild() over those public verbs (tier tests move into its suite; a never-exits stub pins the fail-loud path); dsh-subprocess-local sheds the ladder, its deadline import, and the dsh-timeout dependency. Every future backend now owes four verbs and no teardown policy. New bilingual ladder-ownership Agent Note records the decision; catalogs regenerated.
This commit is contained in:
@@ -9,7 +9,7 @@ The subprocess seam (`ctx.subprocess`). The abstract `SubprocessService` exposes
|
||||
- `spawn(spec)` returns immediately with a live handle; `done` resolves at process close with exit facts (`SubprocessOutcome` carries no output and no cause classification) and rejects only for spawn-level failures.
|
||||
- The spec is fully explicit — argv, cwd, per-stream stdio dispositions, grace — because deployment-varying defaults belong to the calling seam's config, not to a hidden subprocess-service default (the `dsh-bash` request/spec split is the owning template). `argv` is never shell-interpreted; a consumer that wants a shell passes `['bash', '-c', command]` itself.
|
||||
- Stdio is Node-shaped per stream: `'pipe'` hands the caller the raw stream for its own protocol framing (LSP JSON-RPC, ACP ndjson), `'inherit'` passes the parent descriptor through for diagnostics, and collect mode (`{ maxBytes, spill? }`) buffers a bounded tail with an optional full-stream spill file. Collect readers take whole-stream byte offsets and never consume, so independent readers cannot steal one another's deltas; a read whose offset slid out of the in-memory tail is `lossy` and points at the spill file when one exists. Collected output stays readable after settlement.
|
||||
- Termination is tree-scoped on every platform (POSIX detached groups with direct-child fallback; Windows `taskkill /T`): `kill(signal)` sends one signal Node-style and is a no-op after settlement, `terminate()` (and the spec's abort signal) escalates SIGTERM→grace→SIGKILL, `waitForExit()` observes the whole tree, and `dispose(graces)` runs the cooperative stdin-EOF→SIGTERM→SIGKILL ladder out-of-process children need — the manager reacts but never classifies why (callers own deadlines and cause classification).
|
||||
- Termination is tree-scoped on every platform (POSIX detached groups with direct-child fallback; Windows `taskkill /T`): `kill(signal)` sends one signal Node-style and is a no-op after settlement, `terminate()` (and the spec's abort signal) escalates SIGTERM→grace→SIGKILL, and `waitForExit(signal?)` observes whole-tree liveness so a consumer-owned teardown ladder holds each tier on real quiescence — the manager reacts but never classifies why (callers own deadlines, teardown ladders, and cause classification).
|
||||
- `scrubbedParentEnv()` / `SENSITIVE_ENV_PATTERN` are the one shared scrub definition: ambient credential-shaped and `DSH_*` names are dropped, explicit `env` merges after the scrub (a deliberately forwarded key survives), and `dshEnv` carries current harness facts on its own validated channel; `splitEnvChannels()` partitions a consumer config's single mixed env map onto those two channels (lsp-local servers and the ACP backend expose one map, and a configured `DSH_*` fact must ride the managed channel the ordinary one rejects). Spawners that cannot route through the service (node-pty backends, SDK-managed transports) import the scrub.
|
||||
- Disposal of the service terminates all still-running managed processes and awaits their exit.
|
||||
|
||||
@@ -26,4 +26,4 @@ No direct invalidation; the named consumers own any request-prefix changes.
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
- **node-pty and SDK-managed spawns share only the scrub** — the PTY backend's terminal fork and the MCP SDK's own stdio transport cannot route their spawns through this seam (the library owns the fork/spawn call); they import `scrubbedParentEnv` so the environment policy stays single-sourced.
|
||||
- **The dispose ladder assumes stdin-EOF cooperation** — a child that quiesces on a different signal (SIGHUP conventions, control sockets) needs its own tier-1 before the generic ladder fits.
|
||||
- **Teardown ladders are consumer-owned** — the seam ships signalling verbs and the tree-liveness wait, not a canned quiesce sequence; each out-of-process consumer encodes its child's cooperation shape itself (the ACP backend's stdin-EOF-first ladder is the in-repo template).
|
||||
|
||||
Reference in New Issue
Block a user