@sema-agent/core 5.56.0 → 5.58.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/CHANGELOG.md +122 -0
  2. package/dist/agents/cascade.d.ts +1 -1
  3. package/dist/agents/cumulative-stats.d.ts +1 -1
  4. package/dist/agents/observer.d.ts +2 -2
  5. package/dist/agents/peer-admission.d.ts +1 -1
  6. package/dist/agents/retain-ledger.d.ts +2 -2
  7. package/dist/agents/roster-store.d.ts +8 -8
  8. package/dist/agents/send-message-tool.d.ts +13 -2
  9. package/dist/agents/send-message-tool.js +34 -12
  10. package/dist/agents/subagent-steps.d.ts +1 -1
  11. package/dist/agents/subagent.d.ts +13 -13
  12. package/dist/agents/team.d.ts +15 -6
  13. package/dist/agents/team.js +1 -0
  14. package/dist/agents/tool-filter.d.ts +2 -2
  15. package/dist/agents/verify.d.ts +1 -1
  16. package/dist/bench/metrics.d.ts +35 -35
  17. package/dist/brain/anthropic.js +15 -5
  18. package/dist/brain/circuit-breaker.js +2 -1
  19. package/dist/brain/degrading.d.ts +1 -1
  20. package/dist/brain/degrading.js +4 -1
  21. package/dist/brain/errors.d.ts +3 -3
  22. package/dist/brain/failover.js +16 -1
  23. package/dist/brain/open-responses.js +15 -5
  24. package/dist/brain/openai.js +16 -5
  25. package/dist/brain/reasoning.d.ts +2 -2
  26. package/dist/brain/repetition.d.ts +1 -1
  27. package/dist/brain/request-params.d.ts +30 -27
  28. package/dist/brain/request-params.js +1 -7
  29. package/dist/brain/route-adjudicator.d.ts +190 -0
  30. package/dist/brain/route-adjudicator.js +189 -0
  31. package/dist/brain/route-conformance.d.ts +55 -0
  32. package/dist/brain/route-conformance.js +136 -0
  33. package/dist/brain/routing.js +8 -3
  34. package/dist/brain/status-sink.d.ts +2 -2
  35. package/dist/brain/stream-shared.d.ts +1 -1
  36. package/dist/config/catalog.d.ts +5 -5
  37. package/dist/core/arg-summary.d.ts +4 -4
  38. package/dist/core/ask-class.d.ts +2 -2
  39. package/dist/core/ask-question.d.ts +1 -1
  40. package/dist/core/auto-compaction.d.ts +15 -15
  41. package/dist/core/auto-mode.d.ts +5 -5
  42. package/dist/core/background-agent-store.d.ts +20 -20
  43. package/dist/core/background-shell.d.ts +4 -4
  44. package/dist/core/checkpoint-store.d.ts +35 -27
  45. package/dist/core/context-edit.d.ts +1 -1
  46. package/dist/core/context-guard.d.ts +1 -1
  47. package/dist/core/exec-output-tail.d.ts +6 -6
  48. package/dist/core/file-snapshot-store.d.ts +8 -8
  49. package/dist/core/git-worktree-env.d.ts +3 -3
  50. package/dist/core/governance-codes.js +2 -0
  51. package/dist/core/hooks.d.ts +73 -33
  52. package/dist/core/hooks.js +87 -25
  53. package/dist/core/image-downsample.d.ts +1 -1
  54. package/dist/core/locked-config.d.ts +1 -1
  55. package/dist/core/lsp.d.ts +1 -1
  56. package/dist/core/mailbox-store.d.ts +1 -1
  57. package/dist/core/mcp.d.ts +3 -3
  58. package/dist/core/mcp.js +4 -4
  59. package/dist/core/memory-engine/consolidation-driver.d.ts +207 -0
  60. package/dist/core/memory-engine/consolidation-driver.js +378 -0
  61. package/dist/core/memory-engine/consolidation.d.ts +46 -2
  62. package/dist/core/memory-engine/consolidation.js +1 -0
  63. package/dist/core/memory-engine/data-plane.d.ts +1 -1
  64. package/dist/core/memory-engine/distiller.d.ts +550 -0
  65. package/dist/core/memory-engine/distiller.js +598 -0
  66. package/dist/core/memory-engine/dual-root.d.ts +1 -1
  67. package/dist/core/memory-engine/engine.d.ts +62 -8
  68. package/dist/core/memory-engine/engine.js +40 -4
  69. package/dist/core/memory-engine/file-backend.d.ts +1 -1
  70. package/dist/core/memory-engine/index.d.ts +4 -2
  71. package/dist/core/memory-engine/index.js +4 -2
  72. package/dist/core/memory-engine/origin-clearance.d.ts +1 -1
  73. package/dist/core/memory-engine/scope-contract.d.ts +4 -4
  74. package/dist/core/memory-engine/sync-client.d.ts +16 -16
  75. package/dist/core/memory-engine/sync.d.ts +4 -4
  76. package/dist/core/memory-recall.d.ts +1 -1
  77. package/dist/core/memory.d.ts +2 -2
  78. package/dist/core/permission-rule-consent.d.ts +230 -36
  79. package/dist/core/permission-rule-consent.js +258 -54
  80. package/dist/core/permission-rule-model.d.ts +295 -97
  81. package/dist/core/permission-rule-model.js +132 -41
  82. package/dist/core/permission-rules.d.ts +9 -9
  83. package/dist/core/remote-env.d.ts +8 -8
  84. package/dist/core/roles.d.ts +3 -3
  85. package/dist/core/roles.js +1 -0
  86. package/dist/core/runner/assemble-result.d.ts +2 -2
  87. package/dist/core/runner/compaction-call-options.d.ts +3 -3
  88. package/dist/core/runner/memory-consolidation-driver.d.ts +49 -0
  89. package/dist/core/runner/memory-consolidation-driver.js +60 -0
  90. package/dist/core/runner/memory-consolidation.d.ts +1 -1
  91. package/dist/core/runner/prepare-config-doors.d.ts +3 -3
  92. package/dist/core/runner/prepare-task.d.ts +21 -21
  93. package/dist/core/runner/prepare-task.js +54 -18
  94. package/dist/core/runner/prepare-workspace-restore.d.ts +2 -2
  95. package/dist/core/runner/runtask.d.ts +15 -12
  96. package/dist/core/runner/runtask.js +48 -0
  97. package/dist/core/runner/session-rule-policy.d.ts +1 -1
  98. package/dist/core/runner/teardown-bounded.d.ts +1 -1
  99. package/dist/core/runner/tool-disclosure.d.ts +2 -2
  100. package/dist/core/runner/turn-attachments.d.ts +11 -11
  101. package/dist/core/scheduler.d.ts +10 -5
  102. package/dist/core/secret-env.d.ts +1 -1
  103. package/dist/core/sensitive-path-policy.d.ts +1 -1
  104. package/dist/core/session-policy-store.d.ts +2 -2
  105. package/dist/core/session-reconcile.d.ts +2 -2
  106. package/dist/core/session-store.d.ts +3 -3
  107. package/dist/core/session.d.ts +1 -1
  108. package/dist/core/shutdown-debug.d.ts +2 -2
  109. package/dist/core/side-query.d.ts +14 -7
  110. package/dist/core/spec-contract.d.ts +1 -1
  111. package/dist/core/store-contracts/contract-harness.d.ts +2 -2
  112. package/dist/core/store-contracts/contract-kit-version.d.ts +2 -2
  113. package/dist/core/store-contracts/mailbox-store-contract.d.ts +1 -1
  114. package/dist/core/store-contracts/mailbox-store-contract.js +1 -1
  115. package/dist/core/task-notification.d.ts +5 -5
  116. package/dist/core/task-registry-agent.d.ts +12 -12
  117. package/dist/core/task-registry-monitor.d.ts +1 -1
  118. package/dist/core/task-registry-shared.d.ts +41 -41
  119. package/dist/core/task-registry.d.ts +12 -12
  120. package/dist/core/tool-detach.d.ts +2 -2
  121. package/dist/core/tool-errors.d.ts +3 -3
  122. package/dist/core/tool-policy.d.ts +55 -28
  123. package/dist/core/tool-result-budget.d.ts +1 -1
  124. package/dist/core/tool-result-store.d.ts +2 -2
  125. package/dist/core/tools.d.ts +1 -1
  126. package/dist/core/trace.d.ts +26 -23
  127. package/dist/core/types.d.ts +155 -70
  128. package/dist/core/untrusted-egress.d.ts +1 -1
  129. package/dist/core/untrusted-text.d.ts +7 -7
  130. package/dist/core/wiring-manifest.d.ts +5 -5
  131. package/dist/core/workflow-journal-store.d.ts +14 -14
  132. package/dist/core/workflow-run-store-contract.d.ts +1 -1
  133. package/dist/core/workflow-run-store-contract.js +1 -1
  134. package/dist/core/workflow-run-store.d.ts +4 -4
  135. package/dist/engine/compaction/compaction.d.ts +3 -3
  136. package/dist/engine/compaction/utils.d.ts +2 -2
  137. package/dist/engine/execution-env/kill-tree.d.ts +1 -1
  138. package/dist/engine/execution-env/node-execution-env.d.ts +8 -8
  139. package/dist/engine/harness/agent-harness.d.ts +6 -6
  140. package/dist/engine/harness/agent-harness.js +26 -1
  141. package/dist/engine/harness/messages.d.ts +1 -1
  142. package/dist/engine/harness/types.d.ts +15 -11
  143. package/dist/engine/llm/types.d.ts +79 -14
  144. package/dist/engine/loop/agent-loop.d.ts +3 -3
  145. package/dist/engine/loop/types.d.ts +4 -4
  146. package/dist/engine/lsp/node-lsp-manager.d.ts +2 -2
  147. package/dist/engine/session/import-validate.d.ts +1 -1
  148. package/dist/engine/session/log-digest.d.ts +1 -1
  149. package/dist/engine/session/memory-repo.d.ts +2 -2
  150. package/dist/engine/session/session.d.ts +4 -4
  151. package/dist/fixtures/index.d.ts +4 -4
  152. package/dist/index.d.ts +8 -4
  153. package/dist/index.js +5 -2
  154. package/dist/internal/llm.d.ts +1 -1
  155. package/dist/orchestration/goal.d.ts +1 -1
  156. package/dist/orchestration/run-spec.d.ts +1 -1
  157. package/dist/orchestration/run-workflow-tool.d.ts +12 -12
  158. package/dist/orchestration/workflow-governance.d.ts +4 -4
  159. package/dist/orchestration/workflow-observe.d.ts +1 -1
  160. package/dist/orchestration/workflow-script-runner.d.ts +1 -1
  161. package/dist/orchestration/workflow-script-store.d.ts +9 -9
  162. package/dist/orchestration/workflow-size-guideline.d.ts +1 -1
  163. package/dist/orchestration/workflow-types.d.ts +5 -5
  164. package/dist/orchestration/workflow.d.ts +10 -10
  165. package/dist/prompt-assembly/artifact-store.d.ts +1 -1
  166. package/dist/prompt-assembly/artifact.d.ts +1 -1
  167. package/dist/prompt-assembly/assemble.d.ts +1 -1
  168. package/dist/prompt-assembly/composer.d.ts +2 -2
  169. package/dist/prompt-assembly/epoch.d.ts +2 -2
  170. package/dist/prompt-assembly/event-registry.d.ts +1 -1
  171. package/dist/prompt-assembly/explain.d.ts +3 -3
  172. package/dist/prompt-assembly/tool-catalog.d.ts +1 -1
  173. package/dist/prompt-assembly/turn-snapshot.d.ts +4 -4
  174. package/dist/prompt-assembly/types.d.ts +12 -12
  175. package/dist/prompts/coordinator.d.ts +1 -1
  176. package/dist/prompts/default.d.ts +10 -10
  177. package/dist/prompts/default.js +2 -0
  178. package/dist/prompts/simple-sections.d.ts +3 -3
  179. package/dist/prompts/supervisor.d.ts +2 -2
  180. package/dist/scenarios/full-body.d.ts +3 -3
  181. package/dist/scenarios/scenario-registry.d.ts +6 -2
  182. package/dist/scenarios/scenario-registry.js +4 -2
  183. package/dist/stores/cc/sidecar-transcript.d.ts +3 -3
  184. package/dist/stores/file/fs-atomic.d.ts +2 -2
  185. package/dist/stores/file/index.d.ts +1 -1
  186. package/dist/stores/file/session-store.d.ts +2 -2
  187. package/dist/stores/file/workflow-journal-store.d.ts +4 -4
  188. package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -1
  189. package/dist/tools/fs/encoding.d.ts +4 -4
  190. package/dist/tools/fs/fs-bash.d.ts +3 -3
  191. package/dist/tools/fs/fs-pdf.d.ts +1 -1
  192. package/dist/tools/fs/fs-shared.d.ts +6 -6
  193. package/dist/tools/fs/index.d.ts +2 -2
  194. package/dist/tools/fs/index.js +8 -1
  195. package/dist/tools/fs/notebook.d.ts +1 -1
  196. package/dist/tools/fs/pdf.d.ts +1 -1
  197. package/dist/tools/fs/read-deny.d.ts +1 -1
  198. package/dist/tools/fs/safety.d.ts +9 -9
  199. package/dist/tools/fs/search.d.ts +2 -2
  200. package/dist/tools/monitor.d.ts +3 -3
  201. package/dist/tools/scheduler-tools.js +28 -6
  202. package/dist/tools/task-list.d.ts +2 -2
  203. package/dist/tools/web.d.ts +19 -4
  204. package/dist/tools/web.js +8 -2
  205. package/dist/tools/worktree.d.ts +5 -5
  206. package/dist/tools/worktree.js +2 -2
  207. package/package.json +1 -1
  208. package/test/export-surface.snapshot.json +74 -3
@@ -6,7 +6,7 @@
6
6
  * what the model needs) — dropping the head as it overflows, plus a count of dropped bytes for a visible marker.
7
7
  *
8
8
  * This lives in `src/core/` — NOT `src/vendor/` — on purpose: it is OUR robustness invariant, shared by the
9
- * (currently-vendored) NodeExecutionEnv AND a downstream exec adapter (service host/ssh/adb/docker/k8s, [267]).
9
+ * (currently-vendored) NodeExecutionEnv AND a downstream exec adapter (service host/ssh/adb/docker/k8s).
10
10
  * One implementation + ONE model-facing marker text = zero drift, and the public export points at a STABLE path
11
11
  * that survives the planned de-vendoring (when NodeExecutionEnv is naturalized out of `src/vendor/`, nothing rebinds).
12
12
  */
@@ -35,18 +35,18 @@ export declare class RollingTailBuffer {
35
35
  text: string;
36
36
  droppedBytes: number;
37
37
  };
38
- /** RB-234 ([1937]): the retained window's RAW bytes — cursor slicing must happen in the byte domain
38
+ /** RB-234: the retained window's RAW bytes — cursor slicing must happen in the byte domain
39
39
  * (re-encoding a lossily-decoded string inflates every invalid byte to a 3-byte U+FFFD and lands
40
40
  * byte cursors on the wrong offsets). */
41
41
  bytes(): Buffer;
42
- /** RB-234 ([1937]): head-eviction count ALONE — the retention window is [headDroppedBytes, pushed-total)
42
+ /** RB-234: head-eviction count ALONE — the retention window is [headDroppedBytes, pushed-total)
43
43
  * in the accepted-byte domain; source skips must not shift this origin. */
44
44
  headDroppedBytes(): number;
45
- /** RB-234 ([1937]): source-skip count alone (see {@link recordSkippedBytes}) — poll disclosure reports
45
+ /** RB-234: source-skip count alone (see {@link recordSkippedBytes}) — poll disclosure reports
46
46
  * the per-poll increment of this figure instead of silently absorbing it into the window origin. */
47
47
  sourceSkippedBytes(): number;
48
48
  }
49
- /** RB-234 ([1937]): per-stream cursor state for incremental (poll-style) consumption of a
49
+ /** RB-234: per-stream cursor state for incremental (poll-style) consumption of a
50
50
  * {@link RollingTailBuffer}. All counters live in the ACCEPTED-byte domain (bytes actually pushed);
51
51
  * source skips are tracked separately for disclosure. Module-level export (not on the package
52
52
  * surface) so the slice arithmetic is unit-pinnable without an 8MB integration harness. */
@@ -65,7 +65,7 @@ export interface StreamCursorState {
65
65
  /** Fresh cursor state over a new tail. */
66
66
  export declare function newStreamCursorState(tail?: RollingTailBuffer): StreamCursorState;
67
67
  /**
68
- * RB-234 ([1937]): slice the undelivered increment out of a rolling tail — in the BYTE domain, decoded
68
+ * RB-234: slice the undelivered increment out of a rolling tail — in the BYTE domain, decoded
69
69
  * incrementally. Replaces the former decode→re-encode→byte-offset-subarray shape, which mixed two
70
70
  * domains: byte cursors landed inside a re-encoded string whose length differs from the raw stream as
71
71
  * soon as any invalid-UTF-8 byte sits in the window (each inflates to a 3-byte U+FFFD), making every
@@ -1,5 +1,5 @@
1
1
  /** rel path of `child` under `root` in "/" form (win root) / verbatim form (posix root); null when not under.
2
- * codex 1.237 F7: fold "\" only under a WIN-family root ({@link isWinFormPath}); a POSIX tree may legally
2
+ * fold "\" only under a WIN-family root ({@link isWinFormPath}); a POSIX tree may legally
3
3
  * contain literal backslashes in FILE NAMES, and folding them corrupted both the manifest key and the
4
4
  * restore target. */
5
5
  declare function relUnder(root: string, child: string): string | null;
@@ -41,20 +41,20 @@ export interface FileSnapshotStore {
41
41
  reap(scope: string, keepKeys: string[]): Promise<number>;
42
42
  /** Every snapshot key in `scope` (order unspecified). The enumeration counterpart of {@link reap} (which
43
43
  * already iterates the same set internally) — exposed for a cross-backend session EXPORT (service 2c
44
- * session-sync, [266]): list a session's snapshot keys to move its {entries + file snapshots} to another
44
+ * session-sync): list a session's snapshot keys to move its {entries + file snapshots} to another
45
45
  * backend. A durable backend implements it as a `SELECT key WHERE scope = ?`. */
46
46
  listKeys(scope: string): Promise<string[]>;
47
47
  /**
48
- * 2c session-sync ([271]): the manifest (`relPath → blobHash`) for `(scope, key)`, or `null` if absent — to
48
+ * 2c session-sync: the manifest (`relPath → blobHash`) for `(scope, key)`, or `null` if absent — to
49
49
  * EXPORT a snapshot's content to another backend. Pairs with {@link getBlob} for CONTENT-ADDRESSED (deduped)
50
50
  * blob transfer: the caller fetches each distinct hash ONCE across all of a session's snapshots. Optional — a
51
51
  * backend that supports cross-backend snapshot export implements it.
52
52
  */
53
53
  exportManifest?(scope: string, key: string): Promise<Map<string, string> | null>;
54
- /** 2c session-sync ([271]): the content-addressed bytes for `hash` (undefined if absent). */
54
+ /** 2c session-sync: the content-addressed bytes for `hash` (undefined if absent). */
55
55
  getBlob?(hash: string): Promise<Uint8Array | undefined>;
56
56
  /**
57
- * 2c session-sync ([273]): STORE a snapshot INTO this store from another store's {@link exportManifest} +
57
+ * 2c session-sync: STORE a snapshot INTO this store from another store's {@link exportManifest} +
58
58
  * {@link getBlob} output — the import-side mirror that closes the cross-backend transfer loop. For each DISTINCT
59
59
  * blob hash in `manifest`, fetch its bytes via `srcGetBlob`, VERIFY content-address integrity
60
60
  * (`sha256(bytes) === hash`), and store it (content-addressed dedup); THEN record `(scope, key) → manifest`.
@@ -70,7 +70,7 @@ export interface FileSnapshotStore {
70
70
  */
71
71
  importManifest?(scope: string, key: string, manifest: Map<string, string>, srcGetBlob: (hash: string) => Promise<Uint8Array | undefined>): Promise<FileSnapshotResult>;
72
72
  /**
73
- * 2c session-sync ([277]): STORE a single content-addressed blob — the symmetric WRITE side of {@link getBlob},
73
+ * 2c session-sync: STORE a single content-addressed blob — the symmetric WRITE side of {@link getBlob},
74
74
  * for a two-phase PUSH (upload blobs, THEN import the manifest that references them). VERIFIES content-address
75
75
  * integrity (`sha256(bytes) === hash`) so a corrupt/mismatched upload can never contaminate the store (a later
76
76
  * getBlob/restore would otherwise return wrong content); a mismatch → `read_failed`, nothing stored. IMMUTABLE +
@@ -105,7 +105,7 @@ export declare const DEFAULT_SNAPSHOT_BOUNDS: FileSnapshotBounds;
105
105
  * bugs the dual-review fixed). Fail-CLOSED: an enumerate/read/bound failure returns an error and the caller must
106
106
  * NOT persist a partial manifest. Never throws.
107
107
  *
108
- * [789] vanished-file tolerance: a file that DISAPPEARED between enumerate and read (`not_found`) is OMITTED
108
+ * vanished-file tolerance: a file that DISAPPEARED between enumerate and read (`not_found`) is OMITTED
109
109
  * from the manifest instead of failing the whole capture. This is semantically SAFE — the E19 snapshot runs
110
110
  * concurrently with a live tree (the `done` frame is emitted BEFORE the snapshot, so shell-side per-turn
111
111
  * markers, `.git/index.lock`, editor swap files etc. legitimately vanish mid-walk), and omitting a
@@ -128,7 +128,7 @@ export declare function captureManifest(env: ExecutionEnv, root: string, bounds:
128
128
  }>;
129
129
  /**
130
130
  * CONVERGE the tree under `root` to `manifest`, loading each captured file's bytes via `getBlob`. **The
131
- * env-operating + security-critical half a durable backend MUST reuse.** Order is load-bearing (codex BLOCKER +
131
+ * env-operating + security-critical half a durable backend MUST reuse.** Order is load-bearing (+
132
132
  * Opus M1): PHASE 1 removes every current symlink (neutralizes a `dir -> /outside` ancestor that writeFile would
133
133
  * otherwise follow OUT of root) + every file created since; PHASE 2 writes each captured file, clearing a
134
134
  * directory that now occupies a file's path first. Never throws.
@@ -23,8 +23,8 @@ export interface AddWorktreeOptions {
23
23
  * when its `ctx.isolation === "worktree"` and returns the result (with `destroy`); for a non-isolated task it
24
24
  * returns the SHARED base env (no `destroy`) so the Runner never tears the base down.
25
25
  *
26
- * ISOLATE-ONLY (clay): the runtime never merges — the orchestrator script reads each worktree's result and
27
- * decides verify/merge in userland. Caveats (codex review): `git worktree` isolates the git WORKING COPY, not
26
+ * ISOLATE-ONLY: the runtime never merges — the orchestrator script reads each worktree's result and
27
+ * decides verify/merge in userland. Caveats: `git worktree` isolates the git WORKING COPY, not
28
28
  * arbitrary shell access (bash can still reach the host — sandbox separately); UNCOMMITTED changes in the base
29
29
  * repo are absent in the child (detached HEAD); a worktree orphaned by a process crash is reaped by
30
30
  * {@link pruneWorktrees} (userland — there is no Runner hook for post-kill cleanup).
@@ -41,7 +41,7 @@ export declare function addWorktree(baseEnv: ExecutionEnv, opts: AddWorktreeOpti
41
41
  * HRD-PTL-11: the exec Result used to be discarded, so every caller had to ASSUME the prune landed — and
42
42
  * ExitWorktree's "already gone externally" leg told the model "stale git worktree metadata was pruned"
43
43
  * on a claim it could not have checked. Same fail-loud treatment {@link removeWorktree} already got
44
- * (server [1796]②): report the outcome, let the caller decide what to say. Never throws (a throwing env
44
+ * Report the outcome, let the caller decide what to say. Never throws (a throwing env
45
45
  * lands on the same `{ok:false}`), so a caller that ignores the value behaves exactly as before. */
46
46
  export declare function pruneWorktrees(baseEnv: ExecutionEnv, repoRoot: string): Promise<{
47
47
  ok: true;
@@ -17,11 +17,13 @@ export const NON_GOVERNANCE_MEMORY_CODES = new Set([
17
17
  "memory.session_polluted",
18
18
  "memory.harvest_quarantined",
19
19
  "memory.delegation_static_mark_waived",
20
+ "memory.consolidation_driver_superseded",
20
21
  "memory.challenge_sweep_failed",
21
22
  "memory.lineage_settle_failed",
22
23
  "memory.challenge_ledger_corrupt",
23
24
  "memory.consolidation_recommended",
24
25
  "memory.consolidation_committed",
26
+ "memory.consolidation_incomplete",
25
27
  "memory.consolidation_conflict",
26
28
  "memory.consolidation_refused",
27
29
  "memory.consolidation_disabled",
@@ -161,7 +161,7 @@ export interface Hooks {
161
161
  */
162
162
  stopFailure?(ctx: StopFailureContext): void | Promise<void>;
163
163
  /**
164
- * design/134 R5 (CC PermissionDenied parity, SDK 0.3.202 — service [418]③): runs when the tool gate's
164
+ * design/134 R5 (CC PermissionDenied parity, SDK 0.3.202): runs when the tool gate's
165
165
  * adjudicate chain DENY-SHORT-CIRCUITS a tool call — a ToolPolicy deny, a gate tighten (egress /
166
166
  * irreversibility / coarse shellGate) whose `ask` resolved to deny (headless auto-deny included), or the
167
167
  * plan-mode write-deny. Closes the observation blind spot where a blocked call fires NEITHER postToolUse
@@ -525,7 +525,7 @@ export interface StopHookContext {
525
525
  /** Consecutive blocks so far in this run (resets when a stop() call lets the run proceed). */
526
526
  consecutiveBlocks: number;
527
527
  /**
528
- * RB-177 ([1730], measured downstream) — a view of the conversation this hook is being asked to judge.
528
+ * RB-177 (measured downstream) — a view of the conversation this hook is being asked to judge.
529
529
  *
530
530
  * Until now a Stop hook received two counters and nothing else. That is enough for a hook that asks
531
531
  * "have I blocked too often?", and structurally insufficient for the entire class of hooks that ask
@@ -732,7 +732,7 @@ export interface HookInvocationIdentity {
732
732
  * other absence-signaling face this module mints — "absent member" must be a fact about THIS object,
733
733
  * and a frozen object with an ordinary prototype still answers `insideFork`/`agentName` reads (and
734
734
  * `in` probes) from `Object.prototype`, so a prototype write elsewhere in the process could hand
735
- * every leg a delegation axis the mint never stamped (codex r1). `agentName` is sanitized here — it
735
+ * every leg a delegation axis the mint never stamped. `agentName` is sanitized here — it
736
736
  * is the only member whose value a model influences.
737
737
  */
738
738
  export declare function mintHookInvocationIdentity(facts: HookInvocationIdentity): HookInvocationIdentity;
@@ -986,20 +986,33 @@ export type OrgGateVerdict = {
986
986
  /** The overlay's own disclosure lines (engine/deployment-authored), for the operator channel. */
987
987
  disclosures: readonly string[];
988
988
  };
989
+ /** One member of a {@link PersistedRuleHit} coverage set: a rule's canonical text plus (optionally)
990
+ * its add-dot identity. */
991
+ export interface PersistedRuleHitRule {
992
+ /** The canonical text of one admitting rule. */
993
+ readonly rule: string;
994
+ /** The rule's LIVE add dots (tombstoned adds already filtered by the store's read). Omitted by a
995
+ * lane that has the text but not the dots; the evidence then reports `"not_reported"`. */
996
+ readonly dots?: readonly import("./permission-rule-model.js").RuleDot[];
997
+ }
989
998
  /**
990
- * design/252 G-2 — the richer answer a persisted-rule lane may give instead of the bare rule text (see
991
- * `ToolGateInput.persistedRules.admits`): the same canonical text, plus the matched rule's add dots.
999
+ * design/252 G-2, re-formed by design/375 (BREAKING) — the structured answer a persisted-rule lane
1000
+ * gives: the COVERAGE SET that admits the call. A single rule admitting the whole command is a
1001
+ * one-element set (the historical case); a compound admitted by the conjunction of per-segment rules
1002
+ * (design/375 §5.1) is the set of those rules, in segment order. `rules` is non-empty by contract —
1003
+ * an empty set covers nothing and is not a hit (the normalizer reads it as a clean negative).
992
1004
  *
993
- * Why the dots and not an id: a persisted allow rule HAS no scalar id — it is a (text, scope) group
994
- * whose identity is the set of add dots that redeemed it, and two concurrently approved records for one
995
- * text each carry their own. The set is the identity; a joined string would not be one.
1005
+ * Why dots and not an id: a persisted allow rule HAS no scalar id — it is a (text, scope) group whose
1006
+ * identity is the set of add dots that redeemed it, and two concurrently approved records for one text
1007
+ * each carry their own. The set is the identity; a joined string would not be one.
1008
+ *
1009
+ * The pre-375 single-rule object shape (`{ rule, dots? }`) is NOT accepted: on this loosening seam an
1010
+ * unrecognized answer degrades to a clean negative (more asks), never to a match — the safe failure
1011
+ * shape for a lane that missed the same-window upgrade.
996
1012
  */
997
1013
  export interface PersistedRuleHit {
998
- /** The canonical text of the admitting rule the same value the bare-string shape returns. */
999
- readonly rule: string;
1000
- /** The matched rule's LIVE add dots (tombstoned adds already filtered by the store's read). Omitted
1001
- * by a lane that has the text but not the dots; the evidence then reports `"not_reported"`. */
1002
- readonly dots?: readonly import("./permission-rule-model.js").RuleDot[];
1014
+ /** The coverage set every rule the decision stands on. Non-empty. */
1015
+ readonly rules: readonly PersistedRuleHitRule[];
1003
1016
  }
1004
1017
  /**
1005
1018
  * design/252 G-2 — the answer a lane gives when it could not READ its store.
@@ -1014,8 +1027,20 @@ export interface PersistedRuleHit {
1014
1027
  export interface PersistedRuleUnreadable {
1015
1028
  readonly unreadable: true;
1016
1029
  }
1030
+ /**
1031
+ * design/375 §5.2② — the CLEAN NEGATIVE that still carries the per-segment coverage table: no rule
1032
+ * admits the whole call, and THESE segments are the ones an eligible rule already covers. Minted by
1033
+ * the lane from the SAME store read that adjudicated the call (snapshot = adjudication snapshot, no
1034
+ * second read), and consumed only by the suggestion face — which puts on the batch offer exactly the
1035
+ * segments this table calls uncovered. Its DECISION meaning is identical to "no rule": the seat is
1036
+ * advisory, and a lane that omits it (or an unreadable one) degrades to all-uncovered, which merely
1037
+ * over-offers (every offered rule still passes the validator at redemption).
1038
+ */
1039
+ export interface PersistedRuleCoverage {
1040
+ readonly segmentCoverage: readonly import("./permission-rule-model.js").SegmentCoverage[];
1041
+ }
1017
1042
  /** Every shape a lane may answer with. A bare string stays valid and unchanged. */
1018
- export type PersistedRuleAnswer = string | PersistedRuleHit | PersistedRuleUnreadable | undefined;
1043
+ export type PersistedRuleAnswer = string | PersistedRuleHit | PersistedRuleCoverage | PersistedRuleUnreadable | undefined;
1019
1044
  /**
1020
1045
  * design/252 review r3 — read a foreign {@link OrgGateVerdict} the way the personal-rule answer is read:
1021
1046
  * OWN DATA properties only, never the prototype chain, never an accessor.
@@ -1044,9 +1069,15 @@ export declare function normalizeOrgGateVerdict(answer: unknown, unreadable: str
1044
1069
  * Normalize the accepted {@link ToolGateInput.persistedRules} answers into one reading.
1045
1070
  *
1046
1071
  * `{}` = a clean negative (no rule admits this call). `{ unreadable: true }` = the lane could not read
1047
- * its source. `{ hit }` = a match. An answer outside every accepted shape a number, `null`, an object
1048
- * with no `rule`is read as a clean negative rather than a match: this is a LOOSENING seam, so an
1049
- * answer nobody can name degrades toward asking, never toward an allow built on it.
1072
+ * its source. `{ hit }` = a match, whose `rules` is the non-empty coverage set (design/375). An answer
1073
+ * outside every accepted shape — a number, `null`, an object with neither `rules` nor `unreadable`, an
1074
+ * EMPTY `rules` array, the retired pre-375 `{ rule, dots? }` single-rule object is read as a clean
1075
+ * negative rather than a match: this is a LOOSENING seam, so an answer nobody can name degrades toward
1076
+ * asking, never toward an allow built on it. All-or-nothing across the set's TEXTS for the same
1077
+ * reason: a set with one unreadable member is a different (smaller) claim than the lane made, and a
1078
+ * decision must not stand on a claim nobody made — one bad member drops the whole answer to the clean
1079
+ * negative. Per-member DOTS stay individually optional (identity lost ⇒ `"not_reported"`, exactly the
1080
+ * single-rule contract).
1050
1081
  *
1051
1082
  * The hit's dots are COPIED, not aliased. The array travels onto an ask that may sit in front of a
1052
1083
  * person for a long time; a lane that retains and mutates its own array would otherwise change what the
@@ -1055,6 +1086,7 @@ export declare function normalizeOrgGateVerdict(answer: unknown, unreadable: str
1055
1086
  export declare function normalizePersistedRuleHit(hit: PersistedRuleAnswer): {
1056
1087
  hit?: PersistedRuleHit;
1057
1088
  unreadable?: true;
1089
+ coverage?: readonly import("./permission-rule-model.js").SegmentCoverage[];
1058
1090
  };
1059
1091
  /** Inputs to the two-phase tool gate. `adjudicate`/`resolveAsk` are pre-bound to the task abort signal. */
1060
1092
  export interface ToolGateInput {
@@ -1104,7 +1136,7 @@ export interface ToolGateInput {
1104
1136
  /** The combined tool-policy check (abort-bound), or undefined when no policy is wired (→ allow). */
1105
1137
  adjudicate?: (req: ToolCallRequest) => Promise<PermissionResult>;
1106
1138
  /** Resolve an `ask` to allow/deny via `onAsk` (abort-bound). Required iff a decision can be `ask`.
1107
- * G1 three-value ([871]①): the result may carry `approverUnavailable` — the live approver affirmed no
1139
+ * G1 three-value: the result may carry `approverUnavailable` — the live approver affirmed no
1108
1140
  * operator is reachable for THIS ask; the gate then re-routes onto the durable park leg (below),
1109
1141
  * keeping the carried fail-closed deny when no park is armed. */
1110
1142
  resolveAsk: (decision: PermissionResult, req: ToolCallRequest) => Promise<ResolvedAsk>;
@@ -1163,7 +1195,11 @@ export interface ToolGateInput {
1163
1195
  probeReason?: string,
1164
1196
  /** backlog #239: the STRUCTURED cause (the surviving ask's `probeCause`) — same durable-route errand
1165
1197
  * as `probeReason`, already validated at the gate. */
1166
- probeCause?: import("./checkpoint-store.js").ProbeCause) => Promise<ToolGateResult["suspend"] | ParkAttemptFailed | undefined>;
1198
+ probeCause?: import("./checkpoint-store.js").ProbeCause,
1199
+ /** design/375 §5.2②: the surviving ask's engine-stamped `segmentCoverage` — threaded so the park
1200
+ * mint's offer factory reads the same coverage snapshot on the durable route as the synchronous
1201
+ * mints read off the decision (absent ⇒ all segments uncovered ⇒ over-offer, the safe way). */
1202
+ segmentCoverage?: readonly import("./permission-rule-model.js").SegmentCoverage[]) => Promise<ToolGateResult["suspend"] | ParkAttemptFailed | undefined>;
1167
1203
  /**
1168
1204
  * design/174 — route a policy `ask` on the reserved question tool to this run's CONTENT-ask channel
1169
1205
  * before it can become a park or a refusal. Called in the `ask` branch with the FINAL post-hook,
@@ -1268,7 +1304,7 @@ export interface ToolGateInput {
1268
1304
  */
1269
1305
  writeProtectionCheck?: (toolName: string, args: unknown) => import("./write-protect.js").WriteProtectedHit | null;
1270
1306
  /**
1271
- * design/143 批2 ([672]-A, CC 2.1.207 auto mode): when present, a surviving `ask` is routed to the
1307
+ * design/143 批2 (A, CC 2.1.207 auto mode): when present, a surviving `ask` is routed to the
1272
1308
  * small-model policy CLASSIFIER before any human/durable resolution:
1273
1309
  * - verdict `allow` → the ask resolves to allow (`decisionReason:"classifier"`) — no suspend, no onAsk;
1274
1310
  * - verdict `block` → deny (`decisionReason:"classifier"`, `source:"classifier"`; CC `automode-blocked`);
@@ -1337,22 +1373,26 @@ export interface ToolGateInput {
1337
1373
  * bullets above are what keep integrity/hook/mandated asks out of that set.
1338
1374
  */
1339
1375
  persistedRules?: {
1340
- /** The rule that admits this call, or `undefined`. Must not throw: a store that cannot be read
1341
- * answers "no rule" and discloses on its own channel (fail-closed = more asks).
1376
+ /** The rule COVERAGE that admits this call, or `undefined`. Must not throw: a store that cannot
1377
+ * be read answers "no rule" and discloses on its own channel (fail-closed = more asks).
1342
1378
  *
1343
- * TWO ACCEPTED SHAPES (design/252 G-2, additive a lane returning the bare string is unchanged
1344
- * and stays supported): the canonical rule TEXT, or a {@link PersistedRuleHit} carrying that same
1345
- * text plus the matched rule's add DOTS. The dots are the rule's durable identity, and the text
1346
- * is not one (it reaches a card sanitized and capped); a lane that can name them lets the
1347
- * shadowed-ask evidence say WHICH stored rule matched rather than merely what it looked like.
1348
- * Widening the RETURN is why this is additive for implementers: every existing lane satisfies
1349
- * the union unchanged, and the gate reads both through one normalizer. */
1379
+ * TWO ACCEPTED SHAPES (design/252 G-2 / design/375 BREAKING): the canonical rule TEXT (a
1380
+ * single-rule hit with no identity), or a {@link PersistedRuleHit} carrying the coverage SET —
1381
+ * each member's canonical text plus its add DOTS. A single rule admitting the whole command is a
1382
+ * one-element set; a compound admitted by the per-segment conjunction (design/375 §5.1) is the
1383
+ * set of the covering rules. The dots are a rule's durable identity, and the text is not one (it
1384
+ * reaches a card sanitized and capped); a lane that can name them lets the shadowed-ask evidence
1385
+ * say WHICH stored rules matched rather than merely what they looked like. The retired pre-375
1386
+ * single-rule object shape (`{ rule, dots? }`) is read as a clean negative by the normalizer —
1387
+ * a stale lane degrades toward asking, never toward an allow. */
1350
1388
  admits: (req: ToolCallRequest) => Promise<PersistedRuleAnswer>;
1351
- /** Observation sink for an ask a rule resolved — the allow-side disclosure. Never affects the outcome. */
1389
+ /** Observation sink for an ask a rule coverage resolved — the allow-side disclosure. `rules` is
1390
+ * the coverage set's canonical texts in reporting order (design/375: a conjunction names every
1391
+ * rule the decision stands on). Never affects the outcome. */
1352
1392
  onResolved?: (info: {
1353
1393
  toolName: string;
1354
1394
  toolCallId: string;
1355
- rule: string;
1395
+ rules: readonly string[];
1356
1396
  }) => void;
1357
1397
  };
1358
1398
  /**
@@ -1380,7 +1420,7 @@ export interface ToolGateInput {
1380
1420
  /** True iff the reserved question-tool NAME on this leg resolves to the ENGINE's own content-ask
1381
1421
  * tool. It is the exemption's real predicate: the name alone is not, because a caller may mount
1382
1422
  * its own tool under that name when the first-party one is not mounted, and such a tool has
1383
- * arbitrary side effects (codex review, confirmed MED). Absent ⇒ `false` ⇒ the call is governed
1423
+ * arbitrary side effects (confirmed MED). Absent ⇒ `false` ⇒ the call is governed
1384
1424
  * like any other, which is the fail-closed default. */
1385
1425
  contentAskToolMounted?: boolean;
1386
1426
  /** Disclosure sink for the unavailable arm (trace `permission.org_snapshot_unavailable`). Never
@@ -1398,7 +1438,7 @@ export interface ToolGateInput {
1398
1438
  * checkpoint store that explicitly declares `redecision.reopen`). Absent ⇒ the whole step does not
1399
1439
  * exist and the decision path is byte-identical to a build without it.
1400
1440
  *
1401
- * Semantics (clay's ruling): inside a declared sandbox, an engine-authored "confirm a potentially
1441
+ * Semantics (the ruling): inside a declared sandbox, an engine-authored "confirm a potentially
1402
1442
  * destructive local action" ask is answered by containment itself — auto-allow + durable
1403
1443
  * disclosure (`permission.sandbox_admitted`), never a headless deny that pollutes the model.
1404
1444
  * STRICT dominance governs: the leg admits only when EVERY surviving ask on the call is classified
@@ -253,17 +253,7 @@ export function normalizeOrgGateVerdict(answer, unreadable) {
253
253
  return clean({ status: "unavailable", disclosures: [unreadable] });
254
254
  return clean({ status: "available", verdict: clean({ behavior: behavior, rule }), ...revisionCell });
255
255
  }
256
- export function normalizePersistedRuleHit(hit) {
257
- if (typeof hit === "string")
258
- return { hit: { rule: hit } };
259
- if (!isPlainOwnRecord(hit))
260
- return {};
261
- if (ownDataValue(hit, "unreadable") === true)
262
- return { unreadable: true };
263
- const rule = ownDataValue(hit, "rule");
264
- if (typeof rule !== "string")
265
- return {};
266
- const dots = ownDataValue(hit, "dots");
256
+ function copiedUsableDots(dots) {
267
257
  let copied;
268
258
  try {
269
259
  if (Array.isArray(dots)) {
@@ -279,7 +269,53 @@ export function normalizePersistedRuleHit(hit) {
279
269
  copied = undefined;
280
270
  }
281
271
  const usable = copied?.every((d) => typeof d.actor === "string" && typeof d.counter === "number" && Number.isFinite(d.counter)) === true && copied.length > 0;
282
- return { hit: { rule, ...(usable ? { dots: copied } : {}) } };
272
+ return usable ? { dots: copied } : {};
273
+ }
274
+ export function normalizePersistedRuleHit(hit) {
275
+ if (typeof hit === "string")
276
+ return { hit: { rules: [{ rule: hit }] } };
277
+ if (!isPlainOwnRecord(hit))
278
+ return {};
279
+ if (ownDataValue(hit, "unreadable") === true)
280
+ return { unreadable: true };
281
+ const rulesRaw = ownDataValue(hit, "rules");
282
+ if (rulesRaw === undefined) {
283
+ const coverageRaw = ownDataValue(hit, "segmentCoverage");
284
+ let rows;
285
+ try {
286
+ if (Array.isArray(coverageRaw)) {
287
+ const walked = [];
288
+ for (let i = 0; i < coverageRaw.length; i++) {
289
+ const r = coverageRaw[i];
290
+ walked.push(isPlainOwnRecord(r) ? { segment: ownDataValue(r, "segment"), covered: ownDataValue(r, "covered") } : { segment: undefined, covered: undefined });
291
+ }
292
+ rows = walked.every((r) => typeof r.segment === "string" && typeof r.covered === "boolean") ? walked : undefined;
293
+ }
294
+ }
295
+ catch {
296
+ rows = undefined;
297
+ }
298
+ return rows !== undefined && rows.length > 0 ? { coverage: rows.map((r) => ({ segment: r.segment, covered: r.covered })) } : {};
299
+ }
300
+ let members;
301
+ try {
302
+ if (Array.isArray(rulesRaw)) {
303
+ const walked = [];
304
+ for (let i = 0; i < rulesRaw.length; i++) {
305
+ const m = rulesRaw[i];
306
+ walked.push(isPlainOwnRecord(m) ? { rule: ownDataValue(m, "rule"), dots: ownDataValue(m, "dots") } : { rule: undefined, dots: undefined });
307
+ }
308
+ members = walked;
309
+ }
310
+ }
311
+ catch {
312
+ members = undefined;
313
+ }
314
+ if (members === undefined || members.length === 0)
315
+ return {};
316
+ if (!members.every((m) => typeof m.rule === "string"))
317
+ return {};
318
+ return { hit: { rules: members.map((m) => ({ rule: m.rule, ...copiedUsableDots(m.dots) })) } };
283
319
  }
284
320
  function preToolUseCrashReason(subject, err) {
285
321
  const raw = err instanceof Error ? err.message.trim() || err.name : String(err);
@@ -672,6 +708,7 @@ export async function runToolGate(input) {
672
708
  }
673
709
  const persistedRuleMandate = persistedRuleMandateOf(input);
674
710
  let personalEvidence = { dotsAbsent: input.persistedRules === undefined ? "not_wired" : "not_adjudicated" };
711
+ let laneCoverage;
675
712
  if (input.persistedRules &&
676
713
  !orgRealApprovalRequired &&
677
714
  decision.action === "ask" &&
@@ -689,21 +726,44 @@ export async function runToolGate(input) {
689
726
  }
690
727
  return await settleOrgVerdictWithin(pendingHit.then(normalizePersistedRuleHit).catch(() => ({ unreadable: true })), { unreadable: true }, { ...(input.abortSignal !== undefined ? { signal: input.abortSignal } : {}), timeoutMs: ORG_ADJUDICATION_TIMEOUT_MS });
691
728
  })();
692
- const hitEntry = answer.hit;
693
- const hit = hitEntry?.rule;
729
+ const hitRules = answer.hit?.rules;
730
+ laneCoverage = answer.coverage;
694
731
  personalEvidence = { dotsAbsent: answer.unreadable === true ? "unavailable" : "no_match" };
695
- if (hit !== undefined && persistedRuleMandate === undefined && decision.matchedAskRule === undefined) {
732
+ const disclosedRuleSet = (rules) => {
733
+ const shown = rules.slice(0, 5).map((r) => inlineUntrusted(r.rule, 200));
734
+ const rest = rules.length - shown.length;
735
+ return rest > 0 ? `${shown.join(", ")} and ${rest} more` : shown.join(", ");
736
+ };
737
+ const unionDotsOf = (rules) => {
738
+ const seen = new Set();
739
+ const union = [];
740
+ for (const r of rules) {
741
+ if (r.dots === undefined || r.dots.length === 0)
742
+ return { dotsAbsent: "not_reported" };
743
+ for (const d of r.dots) {
744
+ const key = JSON.stringify([d.actor, d.counter]);
745
+ if (!seen.has(key)) {
746
+ seen.add(key);
747
+ union.push(d);
748
+ }
749
+ }
750
+ }
751
+ return { dots: union };
752
+ };
753
+ if (hitRules !== undefined && persistedRuleMandate === undefined && decision.matchedAskRule === undefined) {
696
754
  decision = {
697
755
  action: "allow",
698
- message: `a persisted allow rule (${hit}) covers this call`,
756
+ message: hitRules.length === 1
757
+ ? `a persisted allow rule (${disclosedRuleSet(hitRules)}) covers this call`
758
+ : `persisted allow rules (${disclosedRuleSet(hitRules)}) cover every segment of this call`,
699
759
  decisionReason: "persisted_rule",
700
760
  ...(policyRewrite !== undefined ? { updatedInput: policyRewrite } : {}),
701
761
  };
702
- await notifier.notifyAsync(() => input.persistedRules?.onResolved?.({ toolName: req.toolName, toolCallId, rule: hit }), "toolGate.persistedRuleResolved");
762
+ await notifier.notifyAsync(() => input.persistedRules?.onResolved?.({ toolName: req.toolName, toolCallId, rules: hitRules.map((r) => r.rule) }), "toolGate.persistedRuleResolved");
703
763
  }
704
- else if (hit !== undefined) {
705
- personalEvidence = hitEntry?.dots !== undefined && hitEntry.dots.length > 0 ? { dots: hitEntry.dots } : { dotsAbsent: "not_reported" };
706
- const shownRule = inlineUntrusted(hit, 200);
764
+ else if (hitRules !== undefined) {
765
+ personalEvidence = unionDotsOf(hitRules);
766
+ const shownRule = disclosedRuleSet(hitRules);
707
767
  const mandateNoun = persistedRuleMandate === "operator_always"
708
768
  ? "this deployment mandates per-call confirmation for shell commands (shellGate: always)"
709
769
  : persistedRuleMandate !== undefined
@@ -712,12 +772,14 @@ export async function runToolGate(input) {
712
772
  decision = {
713
773
  ...decision,
714
774
  persistedRuleShadowed: shownRule,
715
- message: `${decision.message !== undefined ? `${decision.message} ` : ""}(a persisted allow rule (${shownRule}) matches this call but does not clear the ask — ${mandateNoun})`,
775
+ message: hitRules.length === 1
776
+ ? `${decision.message !== undefined ? `${decision.message} ` : ""}(a persisted allow rule (${shownRule}) matches this call but does not clear the ask — ${mandateNoun})`
777
+ : `${decision.message !== undefined ? `${decision.message} ` : ""}(persisted allow rules (${shownRule}) match this call but do not clear the ask — ${mandateNoun})`,
716
778
  };
717
779
  }
718
780
  }
719
781
  if (decision.action === "ask") {
720
- decision = { ...decision, ruleEvidence: mintRuleEvidence(personalEvidence) };
782
+ decision = { ...decision, ruleEvidence: mintRuleEvidence(personalEvidence), ...(laneCoverage !== undefined ? { segmentCoverage: laneCoverage } : {}) };
721
783
  }
722
784
  if (input.autoMode &&
723
785
  !orgRealApprovalRequired &&
@@ -779,7 +841,7 @@ export async function runToolGate(input) {
779
841
  ? { origin: orgAskOrigin !== undefined ? `org_${orgAskOrigin}` : "policy" }
780
842
  : undefined;
781
843
  if (suspendAsk && decision.action === "ask") {
782
- const suspended = await suspendAsk(req, currentInput, safety, undefined, realApprovalOf(decision), decision.action === "ask" ? decision.persistedRuleShadowed : undefined, decision.action === "ask" ? decision.decisionReason : undefined, decision.action === "ask" ? decision.probeReason : undefined, decision.action === "ask" ? decision.probeCause : undefined);
844
+ const suspended = await suspendAsk(req, currentInput, safety, undefined, realApprovalOf(decision), decision.action === "ask" ? decision.persistedRuleShadowed : undefined, decision.action === "ask" ? decision.decisionReason : undefined, decision.action === "ask" ? decision.probeReason : undefined, decision.action === "ask" ? decision.probeCause : undefined, decision.action === "ask" ? decision.segmentCoverage : undefined);
783
845
  if (suspended) {
784
846
  if ("parkFailed" in suspended)
785
847
  parkFailed = suspended.parkFailed;
@@ -798,7 +860,7 @@ export async function runToolGate(input) {
798
860
  req.args = outcome.presentedInput;
799
861
  }
800
862
  if (suspendAsk && outcome.parkDeclined && parkFailed === undefined) {
801
- const suspended = await suspendAsk(req, currentInput, safety, true, realApprovalOf(decision), decision.action === "ask" ? decision.persistedRuleShadowed : undefined, decision.action === "ask" ? decision.decisionReason : undefined, decision.action === "ask" ? decision.probeReason : undefined, decision.action === "ask" ? decision.probeCause : undefined);
863
+ const suspended = await suspendAsk(req, currentInput, safety, true, realApprovalOf(decision), decision.action === "ask" ? decision.persistedRuleShadowed : undefined, decision.action === "ask" ? decision.decisionReason : undefined, decision.action === "ask" ? decision.probeReason : undefined, decision.action === "ask" ? decision.probeCause : undefined, decision.action === "ask" ? decision.segmentCoverage : undefined);
802
864
  if (suspended) {
803
865
  if ("parkFailed" in suspended)
804
866
  parkFailed = suspended.parkFailed;
@@ -830,7 +892,7 @@ export async function runToolGate(input) {
830
892
  askDenyResolution = resolved.resolution;
831
893
  decision = resolved;
832
894
  if (resolved.action === "deny" && resolved.approverUnavailable === true && suspendAsk && parkFailed === undefined) {
833
- const suspended = await suspendAsk(req, currentInput, safety, true, realApprovalOf(askBeforeResolve), askBeforeResolve.action === "ask" ? askBeforeResolve.persistedRuleShadowed : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.decisionReason : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.probeReason : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.probeCause : undefined);
895
+ const suspended = await suspendAsk(req, currentInput, safety, true, realApprovalOf(askBeforeResolve), askBeforeResolve.action === "ask" ? askBeforeResolve.persistedRuleShadowed : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.decisionReason : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.probeReason : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.probeCause : undefined, askBeforeResolve.action === "ask" ? askBeforeResolve.segmentCoverage : undefined);
834
896
  if (suspended) {
835
897
  if ("parkFailed" in suspended)
836
898
  parkFailed = suspended.parkFailed;
@@ -36,7 +36,7 @@ export declare const IMAGE_MAX_HEIGHT = 2000;
36
36
  * replaced by an explanatory text block with the size + path; a within-limit image passes through unchanged.
37
37
  */
38
38
  /**
39
- * Optional image-resize seam (design/116 review CONFIRM-1, clay 2026-07-02 加 seam 保体验): given an
39
+ * Optional image-resize seam (design/116 review CONFIRM-1, 裁定 2026-07-02 加 seam 保体验): given an
40
40
  * over-limit image, return a smaller re-encoded one — or undefined when it can't. CC resizes with sharp
41
41
  * (imageResizer.ts: fit within 2000x2000, JPEG quality ladder) so the model still SEES a degraded image;
42
42
  * core must not hard-depend on a native image library, so the capability is injected (or auto-detected).
@@ -46,7 +46,7 @@ export interface LockedPreflight {
46
46
  * always enforced, resolved ONCE here. With `toolPolicy` locked, the deployment's own
47
47
  * `deps.toolPolicy` is the whole answer (a spec-supplied policy has already been refused). */
48
48
  toolPolicy: ToolPolicy | undefined;
49
- /** F-012 (codex round 8): the effective resume-edit re-adjudication OVERRIDE, resolved lock-aware in
49
+ /** F-012: the effective resume-edit re-adjudication OVERRIDE, resolved lock-aware in
50
50
  * the SAME slot as {@link toolPolicy}. With `toolPolicy` locked, a spec-supplied
51
51
  * `basePolicyForResumeEdit` has already been refused wholesale, so this is the deployment's own
52
52
  * `deps.basePolicyForResumeEdit` (or undefined — the caller-policy fallback then applies at the use
@@ -50,7 +50,7 @@ export interface LspSymbolInfo {
50
50
  positionUnknown?: boolean;
51
51
  }
52
52
  /**
53
- * RB-232(黑板 [1937]): WHY a "none" result was "none". Absent `reason` = a GENUINE empty answer (the server
53
+ * RB-232: WHY a "none" result was "none". Absent `reason` = a GENUINE empty answer (the server
54
54
  * answered null/[] — today's behavior byte-for-byte, and what an external manager returning bare
55
55
  * `{kind:"none"}` keeps getting). The failure reasons exist because one literal "No results." used to cover
56
56
  * both real emptiness and every transport-level fault — a findReferences that TIMED OUT read as "no
@@ -110,7 +110,7 @@ export interface MailboxStore {
110
110
  maxAgeMs?: number;
111
111
  }): Promise<number>;
112
112
  }
113
- /** RB-250② (2026-07-28, 黑板 [1937]) class fix — the box age for `reap` is the MAX `sentAt`, not the
113
+ /** RB-250② (2026-07-28) class fix — the box age for `reap` is the MAX `sentAt`, not the
114
114
  * last array element: `sentAt` is caller-supplied, so append order need not be time order, and taking
115
115
  * the tail let a box whose freshest message was mid-array be swept — both bundled backends discarded
116
116
  * a brand-new message while the CC adapter (stores/cc/mailbox-store.ts, the in-package reference)
@@ -209,7 +209,7 @@ export interface MaterializedMcp {
209
209
  */
210
210
  statuses: McpServerStatus[];
211
211
  /**
212
- * [1605] RefreshMcpTools (CC 2.1.218 parity, cli-锚形) — re-read the tool list of one server (or
212
+ * RefreshMcpTools (CC 2.1.218 parity, anchored to the CLI form) — re-read the tool list of one server (or
213
213
  * every CONNECTED server when `server` is omitted) over the EXISTING connection. NEVER dials or
214
214
  * re-dials: a dead/never-connected server reports `not_connected`; a live listTools failure
215
215
  * reports `failed` (the connection is left as-is). A refreshed entry carries the NEW intake
@@ -225,7 +225,7 @@ export interface MaterializedMcp {
225
225
  refresh: (server?: string) => Promise<McpRefreshResult[]>;
226
226
  dispose: () => Promise<void>;
227
227
  }
228
- /** [1605] One per-server entry of {@link MaterializedMcp.refresh}. `added`/`removed` are namespaced
228
+ /** One per-server entry of {@link MaterializedMcp.refresh}. `added`/`removed` are namespaced
229
229
  * (`mcp__<server>__<tool>`) names; `tools`/`axes`/`dropped` are present only on `"refreshed"`.
230
230
  * The model-facing refresh receipt renders each `dropped` entry with ITS OWN `reason` (schema
231
231
  * gate, same-listing name collision, …) — never a blanket label; a consumer matching that receipt
@@ -654,7 +654,7 @@ export declare function listEntryFingerprint(entry: unknown): string | undefined
654
654
  * page cap — a bounded multiplier, not a new unbounded surface. Per-entry admission stays where it
655
655
  * already is (the intake schema gate, which also drops a name a later page repeats).
656
656
  *
657
- * TIME, the reason `budgetMs` exists (codex review, confirmed): the page cap bounds REQUESTS, not
657
+ * TIME, the reason `budgetMs` exists (confirmed): the page cap bounds REQUESTS, not
658
658
  * latency. Giving every page its own full request timeout would let a server that stalls each page hold
659
659
  * a connect (or a refresh) for up to twenty timeouts — twenty minutes at the SDK's 60s default, where
660
660
  * an unpaginated listing cost one. So the WALK gets the budget a single listing used to have: page one