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:
Tianyi Cui
2026-07-27 03:52:41 +08:00
parent cb1864795e
commit 776d666246
32 changed files with 223 additions and 294 deletions

View File

@@ -132,7 +132,7 @@ interface SubprocessSpawnSpec {
## Handles: streams, readers, and tree-scoped termination
A spawn returns a live handle immediately. Collect-mode readers take whole-stream byte offsets and never consume, so independent readers cannot steal one another's deltas; piped streams belong to the caller. Termination is tree-scoped on every platform: `kill(signal)` sends one signal Node-style, `terminate()` 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.
A spawn returns a live handle immediately. Collect-mode readers take whole-stream byte offsets and never consume, so independent readers cannot steal one another's deltas; piped streams belong to the caller. Termination is tree-scoped on every platform: `kill(signal)` sends one signal Node-style, `terminate()` escalates SIGTERM→grace→SIGKILL, and `waitForExit()` observes the whole tree — enough for a consumer to build its own teardown ladder (the ACP backend's stdin-EOF-first `disposeAcpChild` is the template).
```ts type-equiv
/**
@@ -177,15 +177,6 @@ interface SubprocessHandle {
* @returns `true` when the tree exited, `false` when the signal aborted first.
*/
waitForExit(signal?: AbortSignal): Promise<boolean>
/**
* Tear the child down to quiescence, resolving only after exit: close stdin
* (when this handle owns a piped one) and allow cooperative flush for
* `eofGraceMs`, then SIGTERM with a `graceMs` window (POSIX), then forced
* tree termination with a final bounded `graceMs` wait.
* @param graces - the ladder's two windows, from the consumer's Config.
* @throws when the child still has not exited `graceMs` after the forced tier.
*/
dispose(graces: SubprocessDisposeGraces): Promise<void>
}
```
@@ -233,31 +224,6 @@ interface SubprocessCollectedOutputs {
}
```
```ts type-equiv
/**
* The two grace periods of the cooperative dispose ladder
* ({@link SubprocessHandle.dispose}). Consumers carry them as defaulted,
* validated Config fields, so teardown timing is deployment-tunable and this
* seam hardcodes nothing.
*/
interface SubprocessDisposeGraces {
/**
* Tier-1 window (ms): after stdin EOF, how long the child gets to quiesce
* ON ITS OWN — flush durable state, tear down its own descendants — before
* escalation to platform termination. Usually WIDER than
* {@link SubprocessDisposeGraces.graceMs}: a cooperative child's EOF-driven
* teardown may itself wait on a signal-trapping grandchild plus a final
* flush.
*/
eofGraceMs: number
/**
* Termination confirmation window (ms): POSIX applies it after `SIGTERM`
* and again after `SIGKILL`; Windows applies it after the forced tree
* termination.
*/
graceMs: number
}
```
## Outcomes carry exit facts only