subprocess: one explicit env channel on the spawn spec
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.
This commit is contained in:
@@ -13,7 +13,7 @@ English | [中文](2026-07-26-subprocess-seam.zh.md)
|
||||
A new `subprocess/` capability family owns "run and manage a process"; the bash family keeps "run a bash command" and consumes it:
|
||||
|
||||
- **`@deepseek-ai/dsh-subprocess` (interface)** — the abstract `SubprocessService` owning `ctx.subprocess` with one method, `spawn(spec): SubprocessHandle`, and the shared vocabulary: the fully-explicit `SubprocessSpawnSpec` (argv, cwd, per-stream stdio dispositions, grace — no defaults; deployment-varying knobs stay with the calling seam's config, per the `dsh-bash` request/spec template and the no-hidden-defaults rule), `SubprocessHandle` with non-consuming offset-based readers, `SubprocessOutcome` with deliberately no timeout/cancel classification, and the shared scrub plus `DSH_ENV_PREFIX`/`DshEnvironment`/`CollectedOutput` types. `argv` is never shell-interpreted. (The [consumer-migration Agent Note](2026-07-26-subprocess-consumer-migration.md) later widened the stdio and termination vocabulary Node-ward.)
|
||||
- **`@deepseek-ai/dsh-subprocess-local` (implementation)** — `LocalSubprocessService` over the former `run.ts` plumbing (`spawn.ts`): detached groups, tail-keep truncation with private bounded spill files, credential scrub with the two-channel `DSH_*` merge, group kill escalation, and disposal that kills and joins every still-running managed process. It has no config; every limit arrives on the spec. The terminal `ENV_OVERRIDES` (`TERM=dumb` etc.) did NOT move — that is bash-tool presentation policy and stays in `dsh-bash-local`, merged through the ordinary env channel.
|
||||
- **`@deepseek-ai/dsh-subprocess-local` (implementation)** — `LocalSubprocessService` over the former `run.ts` plumbing (`spawn.ts`): detached groups, tail-keep truncation with private bounded spill files, credential scrub with the explicit-env merge after it, group kill escalation, and disposal that kills and joins every still-running managed process. It has no config; every limit arrives on the spec. The terminal `ENV_OVERRIDES` (`TERM=dumb` etc.) did NOT move — that is bash-tool presentation policy and stays in `dsh-bash-local`, merged through the ordinary env channel.
|
||||
- **`dsh-bash-local` (consumer)** — `inject: ['subprocess']`; maps each resolved `BashExecSpec` onto a `SubprocessSpawnSpec` (`['bash', '-c', command]`), keeps its config, `resolve()` defaulting, fused-deadline `timedOut`/`aborted` classification, the `[stderr]`-marked background read merge with its consuming cursor, and the `onProcessDone` subclass hook. `dsh-bash-sandbox` is unchanged apart from redeclaring the inherited inject; it still wraps at the command-string level and re-enters the inherited spawn path.
|
||||
- **`dsh-bash` (seam)** — re-exports the moved vocabulary from `dsh-subprocess`, so no bash consumer changes an import; `BashExecRequest`/`BashExecSpec`/`BashProcess` and the sandbox facts remain bash-owned.
|
||||
|
||||
@@ -29,7 +29,7 @@ Background-process lifetime moved from the executor to the subprocess service: t
|
||||
|
||||
**Put `run_in_background`/task semantics into the process seam instead.** Rejected: that boundary already exists — `ctx.tasks` owns ids, ownership, and notices, and the bash tool adapts a `BashProcess` into task hooks. The process seam sits *below* the bash executor, not beside the task registry.
|
||||
|
||||
**Move `ENV_OVERRIDES` (TERM=dumb, PAGER=cat …) into the subprocess service.** Rejected: a generic subprocess service must not impose terminal presentation policy on non-terminal consumers; the scrub and `DSH_*` channel rules are security/identity invariants and stay, but terminal friendliness is the bash tool's choice, expressed through the ordinary env channel where an explicit caller entry still wins.
|
||||
**Move `ENV_OVERRIDES` (TERM=dumb, PAGER=cat …) into the subprocess service.** Rejected: a generic subprocess service must not impose terminal presentation policy on non-terminal consumers; the ambient scrub (credential-shaped and `DSH_*` names) is a security/identity invariant and stays, but terminal friendliness is the bash tool's choice, expressed through the spec's explicit env where a caller's own entry still wins.
|
||||
|
||||
## Consequences
|
||||
|
||||
|
||||
Reference in New Issue
Block a user