Remove the one-consumer bounded-read primitive and shared terminal lifecycle controller, make terminal cleanup one awaited provider operation, and reuse one Code Runtime contract suite. Keep only reproduced cancellation and policy fixes; defer unproven replacement, prompt-attribution, and streaming-frame concerns to scoped markers.
Delete kill(signal?) from SubprocessHandle: consumers stop a process only
through terminate()'s tree-scoped SIGTERM→graceMs→SIGKILL escalation
(idempotent, also driven by the spec's abort signal, a no-op once the tree
is gone). The single-signal verb had exactly one consumer family —
lsp-local — and what it bought there was a private re-implementation of
the same escalation. The internal kill closure stays in spawn.ts as the
dispose ladder's tier primitive; terminate() now routes through it too.
lsp-local collapses onto the seam's escalation:
- LspConnection replaces its terminate()/kill() pair with one terminate()
that delegates to handle.terminate(). Behavior change: the
framing-failure path terminates instead of instant SIGKILL, so a
misbehaving server now gets SIGTERM plus the killGraceMs window to
flush before SIGKILL.
- ConnectionSpec.pipeDrainGraceMs becomes killGraceMs: one grace, the
spawn spec's graceMs, drives both the escalation window and post-exit
pipe draining (the provider already passed killGraceMs for it).
- LspInstance.forceTerminate() drops its hand-rolled bounded first wait
(LSP_KILL_GRACE) and escalateProcessTree (deleted with its export and
unit test): the seam's escalation already commits to SIGKILL after
killGraceMs, so only the unbounded quiescence awaits stay load-bearing.
Tests: kill()-shaped spawn specs become terminate()-shaped or fold into
the terminate() suites (group-wide delivery; the settled no-op case was
already pinned by 'terminate() after the tree died'); tree-survivor
coverage is intact. A stderr-'inherit' disposition test completes the
stdout/stderr symmetry so the scoped subprocess+lsp coverage gate stands
alone instead of leaning on subagent-acp's cross-package runs.
Docs: SubprocessHandle type-equiv block, seam/impl/group READMEs, and the
consumer-migration Agent Note lose the kill(signal?) vocabulary (zh pairs
re-recorded); cordis api/services catalogs regenerated.
Drop SubprocessSpawnSpec.dshEnv and splitEnvChannels(); childEnv() is now
scrubbed-base + explicit entries with no namespace validation. The invariant
dropped is the reserved-namespace check on explicit entries (DSH_* rejected
from env, non-DSH_* rejected from dshEnv). Explicit-entry trust already
covers it: an explicit credential-shaped entry has always merged after the
scrub as a deliberate caller opt-in, and an explicit DSH_* entry is the same
deliberate act — the staleness invariant lives entirely in scrubbedParentEnv
dropping AMBIENT credential-shaped and DSH_* names, which stays. The
validation's only observed effect was rejecting legitimate explicit entries:
both recent CI breakages (DSH_GATE_CONCURRENCY exported into every job
crashing lsp specs, DSH_PERMISSION_MODE in acp config.env crashing the
child spawn) were this check firing on values a caller meant to pass, each
fixed by routing around the bureaucracy the seam itself imposed.
The bash seam keeps its own request/spec dshEnv field: that is bash-owned
trusted-plugin vocabulary (the ctx.bashEnv collected overlay) whose merge-last
position guarantees a caller env entry cannot displace a managed fact;
bash-local now flattens ENV_OVERRIDES -> spec.env -> spec.dshEnv into the
seam's one env map. subagent-acp and lsp-local pass their single config env
map straight through. DshEnvironment/DshEnvironmentKey/DSH_ENV_PREFIX stay on
the subprocess seam as the namespace vocabulary (bash re-exports them;
scrubbedParentEnv filters on the prefix).
Tests: the two channel-rejection specs and the splitEnvChannels partition
spec are deleted; one spawn spec now proves an explicit DSH_* env entry
reaches the child while an ambient one is scrubbed; the acp/lsp forwarding
specs keep their MOCK_ECHO_ENV / LSP_FAKE_ECHO_ENV assertions with the split
comments rewritten to merge-after-scrub. Docs (en+zh, re-recorded) and the
owning Agent Notes updated; cordis api/services catalogs regenerated.
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.
The parent branch's zh translations were recorded against its pre-reshape
en text; this PR's Node-ward en deltas get minimal zh updates across the
subprocess catalog and the subprocess/lsp-local/subagent READMEs (all
type-equiv blocks byte-identical), with pairing records re-recorded.
Master made bilingual pairing mandatory repo-wide; the subprocess family
docs get their zh counterparts: new pairs for the core-data subprocess
catalog and the three subprocess READMEs (switcher lines added on the en
side), and minimal zh updates for the packages/bash/bash-local README
deltas this PR made, with pairing records recorded.