Merge branch 'codex/invariant-package-registration-gate' into codex/package-invariant-checks

This commit is contained in:
Tianyi Cui
2026-07-20 19:55:46 +08:00
398 changed files with 15485 additions and 8603 deletions
+1 -1
View File
@@ -29,7 +29,7 @@ Implementations subclass `BashExecutor` and implement the abstract methods. Disp
`BashExecRequest` (command, workdir?, timeoutMs?, stdoutMaxBytes?, signal?, stdin?, env?, dshEnv?, sandboxMode?) resolves to `BashExecSpec` (command, workdir, timeoutMs, stdoutMaxBytes, signal?, stdin?, env?, dshEnv?, sandboxMode) before execution. `stdoutMaxBytes` is a trusted foreground-run capture budget for consumers that must parse complete bounded stdout; the model-facing bash tool does not expose it. `sandboxMode` is optional on the request and required-but-nullable on the resolved spec: it carries an approved one-shot escalation or the session's standing override; a sandboxing executor stamps its configured default when absent, while a non-sandboxing executor carries the field and confines nothing.
The seam also owns the per-session mode override vocabulary: the log-only `'bash/sandbox-mode'` session event, the pure `effectiveSandboxMode(events)` fold, and the `setSandboxMode(session, mode)` write path. `run()` returns `BashRunResult`; `start()` returns `BashProcess`, whose incremental read and kill methods are adapted by `dsh-tool-bash` into a generic task registration. A sandboxing executor stamps `BashSandboxInfo` on foreground results and settled process handles. See `src/types.ts` and [core-data-structures/bash.md](../../../docs/core-data-structures/bash.md).
The per-session sandbox-mode override vocabulary (the `'sandbox/mode'` event, the `effectiveSandboxMode(events)` fold, and the `setSandboxMode(session, mode)` write path) is NOT here — it is policy state shared by every enforcing family, owned by [`@deepseek-ai/dsh-sandbox-policy`](../../sandbox/sandbox-policy/). `run()` returns `BashRunResult`; `start()` returns `BashProcess`, whose incremental read and kill methods are adapted by `dsh-tool-bash` into a generic task registration. A sandboxing executor stamps `BashSandboxInfo` on foreground results and settled process handles. See `src/types.ts` and [core-data-structures/bash.md](../../../docs/core-data-structures/bash.md).
`stdin` and ordinary `env` are set by in-process plugins (the hooks bridges, native plugins) to feed a hook command its JSON payload and `CLAUDE_PROJECT_DIR`/`CLAUDE_PLUGIN_ROOT` values. `dshEnv` is a separate trusted overlay restricted by type to managed keys; the exported `DSH_ENV_PREFIX` is the single source for that namespace, its `DshEnvironmentKey` template type, executor scrubbing, registry validation, derived built-in names, and model guidance. Model bash uses the current snapshot collected by `ctx.bashEnv`. Implementations remove inherited managed keys, reject those names in ordinary `env`, then merge `dshEnv`, so an omitted current fact cannot fall back to stale ambient state. The model-facing tool exposes none of these as parameters. All three remain optional on the resolved spec; absent means no input/overlay. See [the bash-stdin-env Agent Note](../../../.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.md) and [the session environment Agent Note](../../../.agents/notes/implemented/feature/2026-07-10-agent-session-identity-and-log-location.md).
-2
View File
@@ -29,13 +29,11 @@
"peerDependencies": {
"@deepseek-ai/dsh-invariants": "^0.0.1",
"@deepseek-ai/dsh-sandbox": "^0.0.1",
"@deepseek-ai/dsh-session": "^0.0.1",
"cordis": "^4.0.0-rc.7"
},
"devDependencies": {
"@deepseek-ai/dsh-invariants": "workspace:^",
"@deepseek-ai/dsh-sandbox": "workspace:^",
"@deepseek-ai/dsh-session": "workspace:^",
"cordis": "^4.0.0-rc.7"
}
}
-1
View File
@@ -10,7 +10,6 @@ import type { SandboxMode } from '@deepseek-ai/dsh-sandbox'
import type { BashExecRequest, BashExecSpec, BashProcess, BashRunResult } from './types.ts'
export { DSH_ENV_PREFIX } from './types.ts'
export { SANDBOX_MODES, effectiveSandboxMode, setSandboxMode } from './session-mode.ts'
export type {
BashExecRequest,
BashExecSpec,
+3 -13
View File
@@ -1,9 +1,7 @@
/** Package-owned session-event invariants for the bash seam. @module @deepseek-ai/dsh-bash/invariant */
/** Package-owned invariant companion for the bash seam. @module @deepseek-ai/dsh-bash/invariant */
import type { Context } from 'cordis'
import type { Session, SessionEvent } from '@deepseek-ai/dsh-session'
import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'
import { SANDBOX_MODES } from './session-mode.ts'
const PACKAGE_NAME = '@deepseek-ai/dsh-bash'
@@ -12,16 +10,8 @@ export const name = 'bash-invariant'
/** Service required before the companion can reserve package ownership. */
export const inject = ['invariants']
/** Install validation for the durable sandbox-mode vocabulary. */
const install: InvariantInstaller = (ctx, fail) => {
ctx.on('internal/dispatch', (_mode, eventName, args) => {
if (eventName !== 'session/event') return
const event = (args as [Session, SessionEvent])[1]
if (event.type === 'bash/sandbox-mode' && !SANDBOX_MODES.includes(event.data.mode)) {
fail(`bash/sandbox-mode carries unknown mode ${JSON.stringify(event.data.mode)}`)
}
}, { global: true })
}
/** No runtime invariant: this stateless seam owns request/result types, while executors and policy own observations. */
const install: InvariantInstaller = () => {}
/**
* Register the bash invariant companion.
-52
View File
@@ -1,52 +0,0 @@
/**
* Per-session sandbox-mode override stored as log-only events. Folding the log
* isolates sessions and survives replay; the tool stamps the override onto
* each call unless an approved one-shot escalation outranks it, and the
* executor default applies when neither exists. The model receives neither the
* event nor a standing-mode notice; denial results name the effective mode.
* @module dsh-bash/session-mode
*/
import type { Session, SessionEvent } from '@deepseek-ai/dsh-session'
import type { SandboxMode } from '@deepseek-ai/dsh-sandbox'
declare module '@deepseek-ai/dsh-session' {
interface SessionEventMap {
/**
* Durable log-only sandbox-mode override; never a surface event or model
* message. Execution and ACP option reporting fold the latest event through
* {@link effectiveSandboxMode} without adding a prompt notice.
*/
'bash/sandbox-mode': { mode: SandboxMode }
}
}
/** Every {@link SandboxMode}, for option advertisement and runtime validation of untrusted mode strings. */
export const SANDBOX_MODES: readonly SandboxMode[] = ['read-only', 'workspace-write', 'danger-full-access']
/**
* The session's sandbox-mode override: the last `bash/sandbox-mode` event in
* the log, or undefined when the session never switched and callers should use
* the executor default. Replay needs no separate catch-up state.
* @param events - session events in log order (other event types are skipped).
* @returns the mode of the last switch event, or undefined without one.
*/
export function effectiveSandboxMode(events: readonly SessionEvent[]): SandboxMode | undefined {
for (let index = events.length - 1; index >= 0; index -= 1) {
const event = events[index] as SessionEvent
if (event.type === 'bash/sandbox-mode') return event.data.mode
}
return undefined
}
/**
* Append one `bash/sandbox-mode` event as the only override write path.
* Execution and ACP option reporting fold it on read; prompt assembly does not
* consume it.
* @param session - the session the override belongs to.
* @param mode - the mode every subsequent bash call in this session runs
* under (until the next switch).
*/
export function setSandboxMode(session: Session, mode: SandboxMode): void {
session.append('bash/sandbox-mode', { mode })
}
@@ -1,33 +0,0 @@
import { describe, expect, it } from 'vitest'
import { Context } from 'cordis'
import type { Session, SessionEvent } from '@deepseek-ai/dsh-session'
import InvariantService from '@deepseek-ai/dsh-invariants'
import * as BashInvariant from '@deepseek-ai/dsh-bash/invariant'
async function setup(): Promise<Context> {
const ctx = new Context()
await ctx.plugin(InvariantService)
await ctx.plugin(BashInvariant)
return ctx
}
function modeEvent(mode: string): SessionEvent {
return { type: 'bash/sandbox-mode', seq: 0, time: 0, data: { mode } } as SessionEvent
}
describe('bash invariants', () => {
it('accepts the closed sandbox vocabulary and ignores unrelated events', async () => {
const ctx = await setup()
expect(() => { ctx.emit('session/event', {} as Session, modeEvent('workspace-write')) }).not.toThrow()
expect(() => { ctx.emit('session/event', {} as Session, {
type: 'turn/start', seq: 0, time: 0, data: {},
} as SessionEvent) }).not.toThrow()
expect(() => { ctx.emit('tools/change') }).not.toThrow()
})
it('rejects an unknown durable sandbox mode', async () => {
const ctx = await setup()
expect(() => { ctx.emit('session/event', {} as Session, modeEvent('host-root')) })
.toThrow(/unknown mode "host-root"/)
})
})
-3
View File
@@ -17,9 +17,6 @@
{
"path": "../../sandbox/sandbox"
},
{
"path": "../../core/session"
},
{
"path": "../../support/invariants"
}