cfceb8452b
The parent implementation introduced sandboxMode and approvalPolicy as generic SessionHeader fields, then propagated those fields through both persistence backends, session-query indexes, collision checks, policy-specific seed-boundary folds, catalogs, and a broad test matrix. That storage plane is unnecessary: Session already accepts a validated constructor seed, and persistence captures that seed when the session is announced before committing its first batch. Capture each parent override synchronously at delegation, append source-tagged sandbox/mode and approval/policy records after the optional fork prefix, and create the child with that combined seed. Keeping header.seedLength at the original fork-prefix length preserves lineage while ordinary last-event-wins folds make the inherited records outrank stale parent history and remain subordinate to later child switches. Unswitched parents still stamp nothing, so children continue to follow deployment defaults. Remove the generic header fields and every persistence/query/schema branch built around them. Collapse the inheritance suite from ten leaking scenarios to four owned-context cases covering real filesystem confinement, stale fork precedence, delegation-time capture, and the no-override path. The assembled headless snapshot now asserts the persisted inheritance event directly. This keeps the security behavior while restoring policy ownership to the existing event log and deleting the speculative durability machinery that the original tests did not exercise.
72 lines
3.2 KiB
TypeScript
72 lines
3.2 KiB
TypeScript
/**
|
|
* Per-session sandbox-mode override: the session log as the store. A runtime
|
|
* switch (a UI policy control or test scenario) is recorded as one
|
|
* `sandbox/mode` event on the session it applies to;
|
|
* `effective = fold(events) ?? the deployment default`, so an override
|
|
* survives restart by replay, two sessions can never see each other's state,
|
|
* and there is no external config store. The event is log-only (the
|
|
* `approval/*` precedent): the model learns the mode from the boundary
|
|
* markers in the enforcing tools, never from the event itself. EXECUTION
|
|
* honors the fold through `ctx.sandboxPolicy.resolve()` — it stamps the mode
|
|
* together with the calling session's workspace root onto each capability
|
|
* call, weakest-precedence beneath an escalation grant.
|
|
*
|
|
* The override is policy state shared by every enforcing family (bash and
|
|
* filesystem alike), so it lives here in the policy package rather than in any
|
|
* one capability's seam.
|
|
*
|
|
* @module dsh-sandbox-policy/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 {
|
|
/**
|
|
* The session's sandbox mode was switched — log-only (like `approval/*`;
|
|
* NOT a surface event, carries no `surfaceOp`): durable and replayable,
|
|
* never in the model transcript. The LAST such event is the session's
|
|
* override ({@link effectiveSandboxMode}). `source: 'delegation'` marks
|
|
* an override seeded into a child; an absent source is a runtime switch.
|
|
*/
|
|
'sandbox/mode': {
|
|
mode: SandboxMode
|
|
/** Marks an override seeded into a child at delegation. */
|
|
source?: 'delegation'
|
|
}
|
|
}
|
|
}
|
|
|
|
/** 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 `sandbox/mode` event in the
|
|
* log, or undefined when the session never switched (callers apply the
|
|
* deployment default). The pure fold — resume needs no catch-up machinery
|
|
* because replaying the log IS the 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 === 'sandbox/mode') return event.data.mode
|
|
}
|
|
return undefined
|
|
}
|
|
|
|
/**
|
|
* THE write path for a session's sandbox-mode override: appends exactly one
|
|
* `sandbox/mode` event — the switch IS its event; nothing mutates mode state
|
|
* out of band. Takes effect on the session's next confined call (bash or fs)
|
|
* — the consumers fold on every read.
|
|
* @param session - the session the override belongs to.
|
|
* @param mode - the mode every subsequent confined call in this session runs
|
|
* under (until the next switch).
|
|
*/
|
|
export function setSandboxMode(session: Session, mode: SandboxMode): void {
|
|
session.append('sandbox/mode', { mode })
|
|
}
|