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

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
persistence.md: 4ec967873e946c8f185f8a8f497f2af4a363474e
persistence.zh.md: 3030ff2fe949cb02385331800d826df227e3d6cd
persistence.md: 0c8d067fc28ee9075354bf32e511e72d66ef530d
persistence.zh.md: fcb07486a401283ff52e64a4d8ed2f25e2218187

View File

@@ -72,12 +72,30 @@ interface SessionHeader {
* resume — a runtime-only depth would reset a resumed child to top-level.
*/
readonly delegationDepth?: number
/**
* The sandbox-mode override inherited from the delegating parent at
* creation (the delegation-inheritance baseline). A neutral string here:
* the policy owner (`dsh-sandbox-policy`) validates it against its closed
* vocabulary on every read, this being a durable boundary. Absent for
* top-level sessions and for children of unswitched parents, which keep
* following the LIVE deployment default. Header-carried (the
* `delegationDepth` precedent) so the baseline is durable from the creation
* moment — no first-turn event survives every crash window, because an
* idle injection can persist a complete turn before any prompt turn opens.
*/
readonly sandboxMode?: string
/**
* The approval-policy override inherited from the delegating parent at
* creation. Same contract as {@link SessionHeader.sandboxMode}; validated
* by `dsh-user-approval` on read.
*/
readonly approvalPolicy?: string
}
```
## `CreateSessionOptions` — seeding and metadata
Creating a `Session` through the store takes a `seed` (replay/fork an existing event log) and `meta` (the storage-level fields the store folds into a `SessionHeader`). The store fills in `version`/`id` and defaults `createdAt`; the caller supplies the validated absolute `cwd`, the `parentSession` lineage, the `seedLength` seed boundary, the `delegationDepth`, and — only when reconstructing a persisted session — the original `createdAt` to preserve it.
Creating a `Session` through the store takes a `seed` (replay/fork an existing event log) and `meta` (the storage-level fields the store folds into a `SessionHeader`). The store fills in `version`/`id` and defaults `createdAt`; the caller supplies the validated absolute `cwd`, the `parentSession` lineage, the `seedLength` seed boundary, the `delegationDepth`, the inherited `sandboxMode`/`approvalPolicy` delegation baselines, and — only when reconstructing a persisted session — the original `createdAt` to preserve it.
```ts type-equiv
/**
@@ -98,6 +116,8 @@ interface CreateSessionOptions {
readonly createdAt?: number
readonly seedLength?: number
readonly delegationDepth?: number
readonly sandboxMode?: string
readonly approvalPolicy?: string
}
}
```

View File

@@ -72,12 +72,30 @@ interface SessionHeader {
* resume — a runtime-only depth would reset a resumed child to top-level.
*/
readonly delegationDepth?: number
/**
* The sandbox-mode override inherited from the delegating parent at
* creation (the delegation-inheritance baseline). A neutral string here:
* the policy owner (`dsh-sandbox-policy`) validates it against its closed
* vocabulary on every read, this being a durable boundary. Absent for
* top-level sessions and for children of unswitched parents, which keep
* following the LIVE deployment default. Header-carried (the
* `delegationDepth` precedent) so the baseline is durable from the creation
* moment — no first-turn event survives every crash window, because an
* idle injection can persist a complete turn before any prompt turn opens.
*/
readonly sandboxMode?: string
/**
* The approval-policy override inherited from the delegating parent at
* creation. Same contract as {@link SessionHeader.sandboxMode}; validated
* by `dsh-user-approval` on read.
*/
readonly approvalPolicy?: string
}
```
## `CreateSessionOptions`seed 与元数据
通过 store 创建 `Session` 时会接收 `seed`(回放/fork 现有事件日志)与 `meta`store 折叠进 `SessionHeader` 的存储层字段。store 填充 `version`/`id` 并为 `createdAt` 提供默认值;调用方提供已校验的绝对 `cwd`、`parentSession` 谱系、`seedLength` 种子边界、`delegationDepth`,以及——仅在重建已持久化会话时——需要保留的原始 `createdAt`。
通过 store 创建 `Session` 时会接收 `seed`(回放/fork 现有事件日志)与 `meta`store 折叠进 `SessionHeader` 的存储层字段。store 填充 `version`/`id` 并为 `createdAt` 提供默认值;调用方提供已校验的绝对 `cwd`、`parentSession` 谱系、`seedLength` 种子边界、`delegationDepth`、继承的 `sandboxMode`/`approvalPolicy` 委派基线,以及——仅在重建已持久化会话时——需要保留的原始 `createdAt`。
```ts type-equiv
/**
@@ -98,6 +116,8 @@ interface CreateSessionOptions {
readonly createdAt?: number
readonly seedLength?: number
readonly delegationDepth?: number
readonly sandboxMode?: string
readonly approvalPolicy?: string
}
}
```