subagent: carry inherited policy overrides in the child session header

Review fix (ds-review-bot critical #2 on #623): the first-turn event stamp
had a durability hole no turn anchoring can close — an idle SessionStart-
style injection persists a complete one-shot turn before any prompt turn
opens, so a crash in that window left a resumable-looking child with no
inherited policy, falling back to a possibly wider deployment default.

The captured overrides now ride the child's creation meta into its
immutable SessionHeader (sandboxMode/approvalPolicy, neutral strings at the
session boundary — the delegationDepth precedent), durable from the moment
the session exists: no listener ordering can starve the baseline and no
crash window can lose it. overrideOf(session) on both policy services
resolves fold(events past header.seedLength) ?? header baseline, validating
against the closed vocabulary on read; stampOverride and the prompt-submit
listener machinery are deleted. The header field rides both persistence
backends (JSONL header line; SQLite sessions columns, SCHEMA_VERSION 11 —
pre-release, no migration). pty-local reads through overrideOf so PTY
spawns see the baseline too.

Red-first: header-durability-before-any-turn test (the injection crash
window shape), baseline/seed-boundary/closed-vocabulary contract tests in
both service suites; the real-wall suite (race, veto, fork stale-seed,
grandchild) re-anchored on header assertions and green. The Agent Note's
Alternatives now records the superseded event-stamping iteration with the
review evidence; bilingual docs updated.
This commit is contained in:
kingwl
2026-07-26 18:16:45 +08:00
parent 166628c0b3
commit c53e9c90db
41 changed files with 387 additions and 264 deletions

View File

@@ -301,15 +301,17 @@ export class SessionPersistenceSqlite extends SessionPersistence implements Pers
private writeRow(meta: SessionHeader): void {
this.db.prepare(`
INSERT INTO sessions
(id, version, created_at, cwd, parent_session, seed_length, delegation_depth, incarnation, revision)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0)
(id, version, created_at, cwd, parent_session, seed_length, delegation_depth, incarnation, revision, sandbox_mode, approval_policy)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?)
ON CONFLICT(id) DO UPDATE SET
version = excluded.version,
created_at = excluded.created_at,
cwd = excluded.cwd,
parent_session = excluded.parent_session,
seed_length = excluded.seed_length,
delegation_depth = excluded.delegation_depth
delegation_depth = excluded.delegation_depth,
sandbox_mode = excluded.sandbox_mode,
approval_policy = excluded.approval_policy
`).run(
meta.id,
meta.version,
@@ -319,6 +321,8 @@ export class SessionPersistenceSqlite extends SessionPersistence implements Pers
meta.seedLength ?? null,
meta.delegationDepth ?? null,
randomUUID(),
meta.sandboxMode ?? null,
meta.approvalPolicy ?? null,
)
}
}

View File

@@ -17,7 +17,7 @@ import type { SessionEvent, SessionId, SessionHeader, SurfaceOp } from '@deepsee
* layout; orthogonal to a session's own `version` (which versions the EVENT
* vocabulary, stored per session in the `sessions` row).
*/
export const SCHEMA_VERSION = 10
export const SCHEMA_VERSION = 11
/** SQLite application id protecting unrelated databases from persistence writes. */
export const SESSION_PERSISTENCE_SQLITE_APPLICATION_ID = 0x44534850
@@ -41,6 +41,10 @@ export interface SessionRow {
/** Monotonic log-change token incremented in each mutating transaction. */
revision: number
delegation_depth: number | null
/** The inherited sandbox-mode delegation baseline, or NULL. */
sandbox_mode: string | null
/** The inherited approval-policy delegation baseline, or NULL. */
approval_policy: string | null
}
/** An `events` table row: one `SessionEvent` mapped 1:1 (`data` is JSON text). */
@@ -124,7 +128,9 @@ function configureDatabase(db: DatabaseSync, path: string, journalMode: JournalM
seed_length INTEGER,
delegation_depth INTEGER,
incarnation TEXT NOT NULL,
revision INTEGER NOT NULL
revision INTEGER NOT NULL,
sandbox_mode TEXT,
approval_policy TEXT
) STRICT;
CREATE TABLE IF NOT EXISTS events (
@@ -181,6 +187,8 @@ export function rowToMeta(row: SessionRow): SessionHeader {
...row.parent_session !== null ? { parentSession: row.parent_session as SessionId } : {},
...row.seed_length !== null ? { seedLength: row.seed_length } : {},
...row.delegation_depth !== null ? { delegationDepth: row.delegation_depth } : {},
...row.sandbox_mode !== null ? { sandboxMode: row.sandbox_mode } : {},
...row.approval_policy !== null ? { approvalPolicy: row.approval_policy } : {},
}
}