@sema-agent/core 7.56.0 → 7.57.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 (173) hide show
  1. package/CHANGELOG.md +541 -0
  2. package/LICENSE +1 -1
  3. package/dist/agents/org-delivery-gate.js +3 -4
  4. package/dist/agents/peer-held-queue.d.ts +69 -49
  5. package/dist/agents/peer-held-queue.js +255 -106
  6. package/dist/agents/peer-session-drain.js +63 -43
  7. package/dist/agents/send-message-tool.js +3 -2
  8. package/dist/agents/subagent.js +8 -8
  9. package/dist/brain/anthropic.js +16 -8
  10. package/dist/brain/open-responses.js +10 -8
  11. package/dist/brain/openai.js +11 -9
  12. package/dist/brain/provider-dialect.d.ts +3 -2
  13. package/dist/brain/provider-dialect.js +2 -2
  14. package/dist/brain/request-params.d.ts +6 -0
  15. package/dist/brain/request-params.js +14 -1
  16. package/dist/brain/stream-engine.d.ts +9 -0
  17. package/dist/brain/stream-engine.js +24 -10
  18. package/dist/brain/stream-shared.d.ts +5 -4
  19. package/dist/brain/stream-shared.js +4 -4
  20. package/dist/core/allow-entry.d.ts +3 -10
  21. package/dist/core/allow-entry.js +17 -25
  22. package/dist/core/approval-object.d.ts +21 -5
  23. package/dist/core/approval-object.js +44 -17
  24. package/dist/core/ask-origin.d.ts +2 -2
  25. package/dist/core/auto-compaction.d.ts +71 -6
  26. package/dist/core/auto-compaction.js +42 -26
  27. package/dist/core/background-agent-store.d.ts +6 -2
  28. package/dist/core/background-agent-store.js +1 -1
  29. package/dist/core/checkpoint-requirements.d.ts +7 -1
  30. package/dist/core/checkpoint-requirements.js +2 -0
  31. package/dist/core/checkpoint-store.d.ts +17 -29
  32. package/dist/core/checkpoint-store.js +19 -11
  33. package/dist/core/cross-boundary-taint.js +4 -2
  34. package/dist/core/engine-notice.d.ts +3 -2
  35. package/dist/core/engine-notice.js +6 -0
  36. package/dist/core/file-history-store.d.ts +14 -8
  37. package/dist/core/file-history-store.js +14 -4
  38. package/dist/core/gate-lanes.js +2 -3
  39. package/dist/core/hooks.d.ts +2 -2
  40. package/dist/core/mailbox-store.d.ts +62 -1
  41. package/dist/core/mailbox-store.js +59 -0
  42. package/dist/core/mcp-name-judge.d.ts +46 -0
  43. package/dist/core/mcp-name-judge.js +61 -0
  44. package/dist/core/mcp-probe.d.ts +1 -1
  45. package/dist/core/mcp-probe.js +2 -5
  46. package/dist/core/mcp.js +10 -30
  47. package/dist/core/memory-engine/consolidation-driver.js +4 -3
  48. package/dist/core/memory-engine/distiller.js +26 -17
  49. package/dist/core/memory-engine/layout.js +5 -2
  50. package/dist/core/park-selfcheck.js +2 -0
  51. package/dist/core/permission-rule-consent.d.ts +5 -3
  52. package/dist/core/permission-rule-consent.js +20 -15
  53. package/dist/core/permission-rule-model.d.ts +43 -2
  54. package/dist/core/permission-rule-model.js +52 -8
  55. package/dist/core/permission-rule-org.js +1 -1
  56. package/dist/core/protocol-http-auth.d.ts +16 -2
  57. package/dist/core/protocol-http-auth.js +42 -11
  58. package/dist/core/protocol-naming.d.ts +20 -0
  59. package/dist/core/protocol-naming.js +20 -3
  60. package/dist/core/read-only-shell.d.ts +20 -5
  61. package/dist/core/read-only-shell.js +29 -14
  62. package/dist/core/remote-env.d.ts +37 -1
  63. package/dist/core/remote-env.js +11 -0
  64. package/dist/core/resume-startup-cause.d.ts +24 -2
  65. package/dist/core/resume-startup-cause.js +20 -1
  66. package/dist/core/roles.js +50 -7
  67. package/dist/core/runner/compact-session.d.ts +10 -0
  68. package/dist/core/runner/compact-session.js +202 -0
  69. package/dist/core/runner/compaction-call-options.d.ts +2 -2
  70. package/dist/core/runner/compaction-seams.d.ts +21 -1
  71. package/dist/core/runner/compaction-seams.js +11 -8
  72. package/dist/core/runner/contracts.d.ts +23 -16
  73. package/dist/core/runner/derived-route-fallback.d.ts +13 -0
  74. package/dist/core/runner/derived-route-fallback.js +6 -0
  75. package/dist/core/runner/env-capability-census.d.ts +3 -2
  76. package/dist/core/runner/env-capability-census.js +9 -3
  77. package/dist/core/runner/permission-rule-lanes.d.ts +7 -7
  78. package/dist/core/runner/permission-rule-lanes.js +17 -11
  79. package/dist/core/runner/prepare-acquire-reconcile.d.ts +3 -0
  80. package/dist/core/runner/prepare-acquire-reconcile.js +2 -1
  81. package/dist/core/runner/prepare-config-doors.d.ts +6 -0
  82. package/dist/core/runner/prepare-config-doors.js +4 -3
  83. package/dist/core/runner/prepare-hands-readface.d.ts +3 -1
  84. package/dist/core/runner/prepare-hands-readface.js +2 -0
  85. package/dist/core/runner/prepare-park-ask.d.ts +2 -0
  86. package/dist/core/runner/prepare-park-ask.js +2 -2
  87. package/dist/core/runner/prepare-policy-chain.js +2 -4
  88. package/dist/core/runner/prepare-task.d.ts +4 -1
  89. package/dist/core/runner/prepare-task.js +23 -27
  90. package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +13 -0
  91. package/dist/core/runner/prepare-tool-disclosure-mount.js +13 -7
  92. package/dist/core/runner/rewind-files.d.ts +2 -5
  93. package/dist/core/runner/rewind-files.js +9 -51
  94. package/dist/core/runner/run-leg.js +10 -21
  95. package/dist/core/runner/run-notification-lane.d.ts +2 -0
  96. package/dist/core/runner/run-notification-lane.js +2 -2
  97. package/dist/core/runner/run-recovery-lanes.js +3 -4
  98. package/dist/core/runner/run-settle-and-teardown.js +5 -11
  99. package/dist/core/runner/run-terminal-adoption.js +3 -4
  100. package/dist/core/runner/run-turn-boundary.js +3 -5
  101. package/dist/core/runner/runtask.d.ts +8 -1
  102. package/dist/core/runner/runtask.js +28 -34
  103. package/dist/core/runner/send-message-continuation.js +1 -1
  104. package/dist/core/runner/session-verb-doors.d.ts +28 -0
  105. package/dist/core/runner/session-verb-doors.js +48 -0
  106. package/dist/core/runner/session-write-ledger.d.ts +30 -0
  107. package/dist/core/runner/session-write-ledger.js +101 -0
  108. package/dist/core/runner/stream-settle-backstop.js +11 -14
  109. package/dist/core/runner/sub-agent-deny-lists.d.ts +25 -3
  110. package/dist/core/runner/sub-agent-deny-lists.js +21 -10
  111. package/dist/core/runner/teardown-bounded.d.ts +1 -0
  112. package/dist/core/runner/teardown-bounded.js +1 -0
  113. package/dist/core/runner-deps.d.ts +2 -2
  114. package/dist/core/secret-env.d.ts +4 -0
  115. package/dist/core/secret-env.js +3 -0
  116. package/dist/core/session-policy-store.d.ts +7 -2
  117. package/dist/core/session-policy-store.js +1 -1
  118. package/dist/core/shell-wrapper-projection.d.ts +1 -1
  119. package/dist/core/store-contracts/background-agent-store-contract.js +1 -0
  120. package/dist/core/store-contracts/checkpoint-store-contract.js +2 -1
  121. package/dist/core/store-contracts/file-history-store-contract.js +9 -0
  122. package/dist/core/store-contracts/mailbox-store-contract.js +49 -1
  123. package/dist/core/task-registry-agent.d.ts +8 -4
  124. package/dist/core/task-registry-agent.js +14 -8
  125. package/dist/core/task-registry-shared.d.ts +35 -6
  126. package/dist/core/task-registry-shared.js +11 -0
  127. package/dist/core/task-registry.d.ts +1 -1
  128. package/dist/core/task-registry.js +1 -1
  129. package/dist/core/tool-catalog-entries.d.ts +6 -0
  130. package/dist/core/tool-catalog-entries.js +1 -0
  131. package/dist/core/tool-name-aliases.d.ts +32 -2
  132. package/dist/core/tool-name-aliases.js +23 -0
  133. package/dist/core/tool-name-readings.d.ts +4 -0
  134. package/dist/core/tool-name-readings.js +1 -0
  135. package/dist/core/tool-policy.d.ts +10 -6
  136. package/dist/core/tool-policy.js +4 -7
  137. package/dist/core/trace.d.ts +4 -1
  138. package/dist/core/trace.js +2 -1
  139. package/dist/core/untrusted-egress.d.ts +20 -0
  140. package/dist/core/untrusted-egress.js +77 -3
  141. package/dist/engine/harness/agent-harness.d.ts +7 -5
  142. package/dist/engine/harness/agent-harness.js +11 -2
  143. package/dist/engine/loop/agent-loop.d.ts +1 -4
  144. package/dist/engine/loop/agent-loop.js +26 -4
  145. package/dist/index.d.ts +12 -7
  146. package/dist/index.js +7 -4
  147. package/dist/internal/harness.d.ts +2 -1
  148. package/dist/internal/harness.js +1 -0
  149. package/dist/primitives/credential-ref.d.ts +89 -0
  150. package/dist/primitives/credential-ref.js +125 -0
  151. package/dist/primitives/index.d.ts +2 -0
  152. package/dist/primitives/index.js +1 -0
  153. package/dist/stores/file/checkpoint-store.d.ts +0 -4
  154. package/dist/stores/file/checkpoint-store.js +0 -1
  155. package/dist/stores/file/file-history-store.js +12 -4
  156. package/dist/stores/file/mailbox-store.d.ts +2 -1
  157. package/dist/stores/file/mailbox-store.js +26 -1
  158. package/dist/tools/fs/bash-readonly-classifier.d.ts +15 -19
  159. package/dist/tools/fs/bash-readonly-classifier.js +5 -10
  160. package/dist/tools/fs/fs-bash.js +10 -3
  161. package/dist/tools/fs/pattern-kill-guard.d.ts +3 -2
  162. package/dist/tools/fs/pattern-kill-guard.js +68 -36
  163. package/dist/tools/fs/shell-read-check.d.ts +10 -0
  164. package/dist/tools/fs/shell-read-check.js +7 -1
  165. package/dist/tools/fs/shell-read-identity.d.ts +12 -9
  166. package/dist/tools/fs/shell-read-identity.js +9 -4
  167. package/dist/tools/web.js +9 -7
  168. package/package.json +1 -1
  169. package/test/export-surface.snapshot.json +79 -3
  170. package/wire-changes/core-stores-sql@7.57.0.json +29 -0
  171. package/wire-changes/core@7.57.0.json +1487 -0
  172. package/wire-changes/mods-kernel@7.57.0.json +29 -0
  173. package/wire-changes/primitives@7.57.0.json +245 -0
@@ -8,6 +8,7 @@ import { brainToRuntime } from "./runtime.js";
8
8
  import { defuseFenceMarkers, sanitizeUntrustedText } from "./untrusted-text.js";
9
9
  import { createSafeNotifier, observeThenableRejection } from "./safe-notify.js";
10
10
  import { thrownText } from "./thrown-value.js";
11
+ import { egressThrownText } from "./untrusted-egress.js";
11
12
  export const DEFAULT_COMPACTION_INSTRUCTIONS = "Preserve verbatim every code, identifier, name, number, file path, error message, credential " +
12
13
  "reference, and explicit user instruction or decision mentioned anywhere in the conversation. " +
13
14
  "When in doubt, keep the specific detail rather than generalizing it.";
@@ -21,9 +22,33 @@ export function sanitizeCompactionInstructions(text) {
21
22
  : defused;
22
23
  }
23
24
  const COMPACTION_MANUAL_CANCEL_FLAG = "semaCompactionManualCancel";
25
+ const PASS_CUT = Symbol("compaction-pass-cut");
26
+ async function untilPassCut(start, signal) {
27
+ if (signal?.aborted === true)
28
+ return PASS_CUT;
29
+ if (signal === undefined)
30
+ return await start();
31
+ let release = () => undefined;
32
+ const cut = new Promise((resolve) => {
33
+ const onAbort = () => resolve(PASS_CUT);
34
+ signal.addEventListener("abort", onAbort, { once: true });
35
+ release = () => signal.removeEventListener("abort", onAbort);
36
+ });
37
+ try {
38
+ return await Promise.race([Promise.resolve(start()), cut]);
39
+ }
40
+ finally {
41
+ release();
42
+ }
43
+ }
24
44
  export function isCompactionManualCancel(err) {
25
45
  return (typeof err === "object" && err !== null && err[COMPACTION_MANUAL_CANCEL_FLAG] === true);
26
46
  }
47
+ const COMPACTION_FAILURE_REASON_MAX = 512;
48
+ export function compactionFailureReason(err) {
49
+ const words = egressThrownText("same-principal", err);
50
+ return words.length > COMPACTION_FAILURE_REASON_MAX ? `${words.slice(0, COMPACTION_FAILURE_REASON_MAX)}…` : words;
51
+ }
27
52
  function summarizationCharsCapacity(model) {
28
53
  return model.contextWindow > 0
29
54
  ? model.contextWindow * (model.charsPerToken ?? DEFAULT_CHARS_PER_TOKEN)
@@ -161,29 +186,15 @@ export async function maybeCompact(opts) {
161
186
  let providerSummary;
162
187
  if (opts.summaryProvider && !forceRealSummary && !prep.value.isSplitTurn) {
163
188
  const providerModifiedFiles = computeFileLists(prep.value.fileOps).modifiedFilesByRecency;
164
- const signal = opts.signal;
165
- const cut = Symbol("cut");
166
- let release = () => undefined;
167
- const cutByAbort = new Promise((resolve) => {
168
- if (signal === undefined || signal.aborted)
169
- return;
170
- const onAbort = () => resolve(cut);
171
- signal.addEventListener("abort", onAbort, { once: true });
172
- release = () => signal.removeEventListener("abort", onAbort);
173
- });
189
+ const summaryProvider = opts.summaryProvider;
190
+ const boundary = prep.value;
174
191
  let consulted;
175
192
  try {
176
- consulted =
177
- signal?.aborted === true
178
- ? cut
179
- : await Promise.race([
180
- Promise.resolve(opts.summaryProvider({
181
- messagesToSummarize: prep.value.messagesToSummarize,
182
- previousSummary: prep.value.previousSummary,
183
- modifiedFiles: providerModifiedFiles,
184
- })),
185
- cutByAbort,
186
- ]);
193
+ consulted = await untilPassCut(() => summaryProvider({
194
+ messagesToSummarize: boundary.messagesToSummarize,
195
+ previousSummary: boundary.previousSummary,
196
+ modifiedFiles: providerModifiedFiles,
197
+ }), opts.signal);
187
198
  const trimmed = typeof consulted === "string" ? consulted.trim() : "";
188
199
  if (trimmed) {
189
200
  providerSummary = trimmed;
@@ -191,10 +202,7 @@ export async function maybeCompact(opts) {
191
202
  }
192
203
  catch {
193
204
  }
194
- finally {
195
- release();
196
- }
197
- if (consulted === cut) {
205
+ if (consulted === PASS_CUT) {
198
206
  throw Object.assign(new CompactionError("aborted", "compaction cancelled while the summary provider was answering — nothing was persisted"), opts.manualCancelSignal?.aborted === true ? { [COMPACTION_MANUAL_CANCEL_FLAG]: true } : {});
199
207
  }
200
208
  }
@@ -298,7 +306,12 @@ export async function maybeCompact(opts) {
298
306
  });
299
307
  }
300
308
  }
301
- const auth = await opts.getApiKeyAndHeaders?.(summaryModel);
309
+ const getApiKeyAndHeaders = opts.getApiKeyAndHeaders;
310
+ const authed = getApiKeyAndHeaders === undefined ? undefined : await untilPassCut(() => getApiKeyAndHeaders(summaryModel), opts.signal);
311
+ if (authed === PASS_CUT) {
312
+ throw Object.assign(new CompactionError("aborted", "compaction cancelled while the summary model's credentials were being fetched — nothing was summarized or persisted"), opts.manualCancelSignal?.aborted === true ? { [COMPACTION_MANUAL_CANCEL_FLAG]: true } : {});
313
+ }
314
+ const auth = authed;
302
315
  const summarySignal = opts.signal;
303
316
  const runtime = brainToRuntime(opts.brain);
304
317
  const forkCandidate = opts.compactionModel === undefined ? opts.forkContext?.() : undefined;
@@ -601,6 +614,9 @@ export function mintCompactedEvent(comp, trigger, identity) {
601
614
  ...identity,
602
615
  };
603
616
  }
617
+ export function manualCompactOutcomeOf(comp) {
618
+ return comp.compacted ? "compacted" : comp.blocked ? "blocked" : comp.disabled ? "disabled" : "noop";
619
+ }
604
620
  export function mintCompactionStartedEvent(start, identity) {
605
621
  return { type: "compaction_started", trigger: start.trigger, pass: start.pass, startedAt: start.startedAt, ...identity };
606
622
  }
@@ -37,7 +37,11 @@ export interface BackgroundAgentRecord {
37
37
  * keys on THIS field (listBySession matches it as a second arm), not on an alias walk. */
38
38
  rootSessionId?: string;
39
39
  parentTaskId?: string;
40
- name?: string;
40
+ /** The agent's name — the spawner's explicit one, or the one the engine derives from `handle`
41
+ * (`engineAgentName`) when the spawn named none. Written by the registration that creates the row, so
42
+ * every row core writes carries one; a revival re-reads it verbatim. A host store that reads a row an
43
+ * earlier version wrote without one derives it from `handle` with the same function. */
44
+ name: string;
41
45
  agentType?: string;
42
46
  description?: string;
43
47
  /** Revival lookup key (CC meta-sidecar parity); populated when known, never required. The team
@@ -180,7 +184,7 @@ export interface BackgroundAgentRowSummary {
180
184
  sessionId?: string;
181
185
  parentSessionId?: string;
182
186
  rootSessionId?: string;
183
- name?: string;
187
+ name: string;
184
188
  agentType?: string;
185
189
  status: BackgroundAgentRecord["status"];
186
190
  spawnedAt: number;
@@ -183,7 +183,7 @@ function toSummary(r) {
183
183
  ...(r.sessionId !== undefined ? { sessionId: r.sessionId } : {}),
184
184
  ...(r.parentSessionId !== undefined ? { parentSessionId: r.parentSessionId } : {}),
185
185
  ...(r.rootSessionId !== undefined ? { rootSessionId: r.rootSessionId } : {}),
186
- ...(r.name !== undefined ? { name: r.name } : {}),
186
+ name: r.name,
187
187
  ...(r.agentType !== undefined ? { agentType: r.agentType } : {}),
188
188
  status: r.status,
189
189
  spawnedAt: r.spawnedAt,
@@ -6,7 +6,7 @@ import { type StoreFidelity, type Checkpoint, type CheckpointFeature, type Check
6
6
  * had carried beside the one its word now names, and the members added after the table, each a capability a reader
7
7
  * must have. Downstream reads them for display only (`CheckpointError.detail.requires`).
8
8
  */
9
- export declare const REQUIREMENT_WORDS: readonly ["resource", "binding", "token", "orgAdmission", "f012", "realApproval", "face", "requirements_digest", "org_admission_inherited", "org_write_grant", "org_governed", "delegation_provenance", "skill_grants", "skill_card_binding", "hands_coordinates", "hands_home", "path_rule_anchors", "path_rule_installations", "permission_mode", "plan_mode_restore", "persisted_rule_mandate", "deployment_authored", "call_names", "write_face", "denial_limit_fallback", "leg_scope", "tool_ceiling", "tool_lists_judged", "own_tool_lists", "approval_targets", "cross_boundary", "mount_site", "skill_scopes"];
9
+ export declare const REQUIREMENT_WORDS: readonly ["resource", "binding", "token", "orgAdmission", "f012", "realApproval", "face", "requirements_digest", "org_admission_inherited", "org_write_grant", "org_governed", "delegation_provenance", "skill_grants", "skill_card_binding", "hands_coordinates", "hands_home", "path_rule_anchors", "path_rule_installations", "permission_mode", "plan_mode_restore", "persisted_rule_mandate", "deployment_authored", "call_names", "write_face", "denial_limit_fallback", "leg_scope", "tool_ceiling", "tool_lists_judged", "own_tool_lists", "approval_targets", "approval_target_reads", "cross_boundary", "mount_site", "skill_scopes"];
10
10
  export type RequirementWord = (typeof REQUIREMENT_WORDS)[number];
11
11
  /** Runtime twin of {@link RequirementWord}. */
12
12
  export declare function isRequirementWord(word: unknown): word is RequirementWord;
@@ -332,6 +332,9 @@ export declare const PENDING_ACTION_REQUIREMENTS: {
332
332
  readonly targets: {
333
333
  readonly word: "approval_targets";
334
334
  };
335
+ readonly targetReads: {
336
+ readonly word: "approval_target_reads";
337
+ };
335
338
  readonly hasBidiControls: {
336
339
  readonly ignorable: "shown, never decided (card, listing, transcript or reported count): every read of the name renders, carries transcript or counts";
337
340
  };
@@ -844,6 +847,9 @@ export declare const CHECKPOINT_REQUIREMENTS: {
844
847
  readonly targets: {
845
848
  readonly word: "approval_targets";
846
849
  };
850
+ readonly targetReads: {
851
+ readonly word: "approval_target_reads";
852
+ };
847
853
  readonly hasBidiControls: {
848
854
  readonly ignorable: "shown, never decided (card, listing, transcript or reported count): every read of the name renders, carries transcript or counts";
849
855
  };
@@ -32,6 +32,7 @@ export const REQUIREMENT_WORDS = Object.freeze([
32
32
  "tool_lists_judged",
33
33
  "own_tool_lists",
34
34
  "approval_targets",
35
+ "approval_target_reads",
35
36
  "cross_boundary",
36
37
  "mount_site",
37
38
  "skill_scopes",
@@ -133,6 +134,7 @@ export const PENDING_ACTION_REQUIREMENTS = {
133
134
  names: { word: "call_names" },
134
135
  engineMounted: { word: "mount_site" },
135
136
  targets: { word: "approval_targets" },
137
+ targetReads: { word: "approval_target_reads" },
136
138
  hasBidiControls: SHOWN,
137
139
  batchToolCallIds: DECISION_READ,
138
140
  completedCallIds: SHOWN,
@@ -907,6 +907,8 @@ export type PendingAction = {
907
907
  /** The approval object's TARGETS (`approvalTargetsOf`): per reading of the path slot, where it resolved when the card
908
908
  * was asked; absent ⇒ not judged. A role resolving elsewhere at the execution vetoes the call (`resume.target_moved`). */
909
909
  targets?: import("./approval-object.js").ApprovalTargets;
910
+ /** The same member's shell reading (`approvalTargetsOf`): per read candidate the shell read station judged for the parked bytes, where it landed (digests); absent ⇒ not judged. A read landing elsewhere at the execution vetoes the call (`resume.target_moved`). Word `approval_target_reads`. */
911
+ targetReads?: import("./approval-object.js").ApprovalTargetReads;
910
912
  /**
911
913
  * #438 (additive; no checkpoint-version bump — the `reminderMark` precedent: an optional field an
912
914
  * older reader ignores. Unlike that precedent seat — which resume DOES read, through its
@@ -2771,19 +2773,6 @@ export interface CheckpointStore {
2771
2773
  * that still matches.
2772
2774
  */
2773
2775
  readonly fidelity?: StoreFidelity;
2774
- /**
2775
- * F-012 L2/L3 — the RE-DECISION capability declaration: whether this store's `reopen` (and, when
2776
- * present, a durable validating-lease discipline) actually works — a promise, not a method-presence
2777
- * sniff (`reopen` is optional and a stub that returns `false` still satisfies the type). The
2778
- * sandbox-admission mode's pre-flight REQUIRES `redecision.reopen === true` (its boundary asks park
2779
- * to a human and must be re-decidable); absent reads fail-closed as "no declared re-decision face"
2780
- * and the mode refuses to arm, loudly. Same "declaration, never duck-typing" posture as
2781
- * {@link retention}/{@link durability}/{@link fidelity}.
2782
- */
2783
- readonly redecision?: {
2784
- reopen: boolean;
2785
- validatingLease?: boolean;
2786
- };
2787
2776
  /**
2788
2777
  * The EXECUTION-OUTCOME declaration — REQUIRED, and required to be exactly `{ outcome: true }`: the
2789
2778
  * store promises that {@link recordExecutionOutcome} is the real CAS this contract describes (the
@@ -2846,9 +2835,12 @@ export interface CheckpointStore {
2846
2835
  * `resolve` CAS *before* the workspace `resumeVM` runs; if that restore then fails (`resume.env_failed`)
2847
2836
  * the pending action never executed yet the checkpoint is consumed — without this the suspended work is
2848
2837
  * lost to a forced "re-initiate" (fine for a short approval, ruinous for a long unattended task).
2849
- * Reopening lets a retry re-resume the SAME work. Optional: a store that omits it keeps the prior (lossy)
2850
- * behavior, and the runner degrades gracefully. Safe by the `status='resolved'` guard — only the run that
2851
- * consumed the checkpoint (and then failed to restore) can reopen it, and only while it is still `resolved`.
2838
+ * Reopening lets a retry re-resume the SAME work. REQUIRED — the verb is the contract (no declaration stands
2839
+ * for it; the store seat, {@link resolveCheckpointStore}, refuses a store that does not carry it): a claimed
2840
+ * resume that ends before its decided action is disposed — a failed restore, a refusal at the leg's prepare, an
2841
+ * abort, a leg that returns failed — returns the row here, and the engine has no lane for a store that cannot. Safe by
2842
+ * the `status='resolved'` guard — only the run that consumed the checkpoint (and then failed to restore) can
2843
+ * reopen it, and only while it is still `resolved`.
2852
2844
  *
2853
2845
  * **design/80 D-1 (reopen-by-reason):** the `reason` distinguishes the two reopen triggers (both shipped at
2854
2846
  * runtask.ts) so re-resume can validate per reason (§3 inv #1): `env_failed` (the action is still valid,
@@ -2863,7 +2855,7 @@ export interface CheckpointStore {
2863
2855
  * already ran or re-decide one already refused. Answers `false` like every other predicate miss; the
2864
2856
  * reasons and the winner are untouched.
2865
2857
  */
2866
- reopen?(token: CheckpointToken, scope: string, reason: ReopenReason): Promise<boolean>;
2858
+ reopen(token: CheckpointToken, scope: string, reason: ReopenReason): Promise<boolean>;
2867
2859
  /**
2868
2860
  * Record how the DECIDED action was finally disposed on the resumed leg — the {@link GateOutcome} its
2869
2861
  * `tool_end` frame carried (see {@link CheckpointRow.executionOutcome} for what the record means and why
@@ -2987,9 +2979,8 @@ export interface CheckpointStore {
2987
2979
  * needed semantics; "who beat me / with what" is claimant-identity territory this contract
2988
2980
  * does not mint.
2989
2981
  * 7. **No declaration bit** — presence of the method IS the contract (an implementation
2990
- * obligation at the same level as `resolve`'s CAS promise); no `redecision`-style declaration,
2991
- * because that family guards against a stub satisfying a type, and a `claimTerminal` stub has
2992
- * no such shape — the return form itself carries the obligation.
2982
+ * obligation at the same level as `resolve`'s CAS promise), as it is for the required {@link reopen}:
2983
+ * the return form itself carries the obligation.
2993
2984
  *
2994
2985
  * Deployment correspondence (an ask-row store implementing the same law maps its own vocabulary):
2995
2986
  * token+scope ↔ the ask id (+ its run binding); pending/resolved/expired ↔ the ask row's
@@ -3079,12 +3070,13 @@ export declare function resolveCheckpointStore(spec: {
3079
3070
  }, deps: {
3080
3071
  checkpointStore?: CheckpointStore;
3081
3072
  }): CheckpointStore | undefined;
3082
- /** The ONE refusal of a store seat whose {@link CheckpointStore} does not declare `execution = { outcome: true }`
3083
- * (named by seat and by the store's constructor when it has one). Same door, same code, as the retired-`null`
3084
- * refusal above: a configuration the run cannot honour is refused at the first read, never carried. */
3085
- export declare function checkpointStoreExecutionUndeclared(seat: "TaskSpec.checkpointStore" | "RunnerDeps.checkpointStore", store: object): Error & {
3073
+ /** The ONE refusal of a store seat whose {@link CheckpointStore} lacks a required member (the {@link CheckpointStore.execution}
3074
+ * declaration, the {@link CheckpointStore.reopen} verb) — every missing one named, with the seat and the store's constructor when it
3075
+ * has one — or `undefined` when the store carries them all. Same door, same code, as the retired-`null` refusal below: a configuration
3076
+ * the run cannot honour is refused at the first read, never carried. */
3077
+ export declare function checkpointStoreIncomplete(seat: "TaskSpec.checkpointStore" | "RunnerDeps.checkpointStore", store: object): (Error & {
3086
3078
  code: "config.invalid_checkpoint_store";
3087
- };
3079
+ }) | undefined;
3088
3080
  /** The ONE refusal of the retired `checkpointStore: null` spelling (the resolver and the workflow baseline overlay both throw it). */
3089
3081
  export declare function retiredCheckpointStoreNull(): Error & {
3090
3082
  code: "config.invalid_checkpoint_store";
@@ -3182,10 +3174,6 @@ export declare class InMemoryCheckpointStore implements CheckpointStore {
3182
3174
  /** Honest declaration on the fidelity axis: `put`/`get` are `structuredClone`, so a row keeps the
3183
3175
  * whole structured-clone domain — a `Date` filed here is a `Date` when it comes back. */
3184
3176
  readonly fidelity: "structured-clone";
3185
- /** F-012 L2: this store's `reopen` is a real resolved→pending CAS (below) — declared, not sniffed. */
3186
- readonly redecision: {
3187
- readonly reopen: true;
3188
- };
3189
3177
  /** The execution-outcome record verb below is the contract's CAS, and `reopen` honours the record. */
3190
3178
  readonly execution: {
3191
3179
  readonly outcome: true;
@@ -375,22 +375,31 @@ export function resolveCheckpointStore(spec, deps) {
375
375
  if (spec.checkpointStore === "disabled")
376
376
  return undefined;
377
377
  if (spec.checkpointStore !== undefined) {
378
- if (spec.checkpointStore.execution?.outcome !== true)
379
- throw checkpointStoreExecutionUndeclared("TaskSpec.checkpointStore", spec.checkpointStore);
378
+ const refused = checkpointStoreIncomplete("TaskSpec.checkpointStore", spec.checkpointStore);
379
+ if (refused !== undefined)
380
+ throw refused;
380
381
  return spec.checkpointStore;
381
382
  }
382
- if (deps.checkpointStore !== undefined && deps.checkpointStore?.execution?.outcome !== true) {
383
- throw checkpointStoreExecutionUndeclared("RunnerDeps.checkpointStore", deps.checkpointStore);
383
+ if (deps.checkpointStore !== undefined) {
384
+ const refused = checkpointStoreIncomplete("RunnerDeps.checkpointStore", deps.checkpointStore);
385
+ if (refused !== undefined)
386
+ throw refused;
384
387
  }
385
388
  return deps.checkpointStore;
386
389
  }
387
- export function checkpointStoreExecutionUndeclared(seat, store) {
388
- const ctor = store?.constructor?.name;
390
+ const REQUIRED_STORE_MEMBERS = [
391
+ { carried: (store) => store?.execution?.outcome === true, lacks: "does not declare `execution = { outcome: true }`", cost: "the execution-outcome record (recordExecutionOutcome, and reopen refusing a recorded row) — without it no reader can settle how a resumed approval was disposed" },
392
+ { carried: (store) => typeof store?.reopen === "function", lacks: "does not carry the `reopen` verb", cost: "the claim's reversal (reopen) — without it a resume the engine claimed and then refused before its decided action was disposed would consume the approval" },
393
+ ];
394
+ export function checkpointStoreIncomplete(seat, store) {
395
+ const read = store;
396
+ const missing = REQUIRED_STORE_MEMBERS.filter((m) => !m.carried(read));
397
+ if (missing.length === 0)
398
+ return undefined;
399
+ const ctor = read?.constructor?.name;
389
400
  const name = typeof ctor === "string" && ctor.length > 0 && ctor !== "Object" ? ` (${ctor})` : "";
390
- const e = new Error(`${seat}: the wired CheckpointStore${name} does not declare \`execution = { outcome: true }\` — the execution-outcome ` +
391
- "record (recordExecutionOutcome, and reopen refusing a recorded row) is a required member of the store contract; " +
392
- "a store without it cannot record how a resumed approval was disposed, so the run is refused here rather than " +
393
- "resolving rows no reader can settle. Implement the verb and add the declaration (the in-tree stores and the " +
401
+ const e = new Error(`${seat}: the wired CheckpointStore${name} ${missing.map((m) => m.lacks).join(" and ")} — ${missing.map((m) => m.cost).join("; ")}. ` +
402
+ "Each is a required member of the store contract, so the run is refused here. Implement it (the in-tree stores and the " +
394
403
  "store-contract kit are the reference).");
395
404
  e.code = "config.invalid_checkpoint_store";
396
405
  return e;
@@ -548,7 +557,6 @@ export class InMemoryCheckpointStore {
548
557
  retention = "none";
549
558
  durability = "process-local";
550
559
  fidelity = "structured-clone";
551
- redecision = { reopen: true };
552
560
  execution = { outcome: true };
553
561
  cps = new Map();
554
562
  fault = null;
@@ -1,4 +1,5 @@
1
1
  import { hasConversationContent } from "./session.js";
2
+ import { egressText } from "./untrusted-egress.js";
2
3
  export const CROSS_BOUNDARY_TAINT_BITS = Object.freeze(["targetExecutionArmed", "offTargetReadObserved"]);
3
4
  export const CROSS_BOUNDARY_POSTURES = Object.freeze(["single_user", "multi_tenant"]);
4
5
  const postureSeatIsTheSet = true;
@@ -152,13 +153,14 @@ export function crossBoundaryPostureUndeclaredNotice(at) {
152
153
  };
153
154
  }
154
155
  export function crossBoundaryRecordUnwrittenNotice(at) {
156
+ const cause = egressText("same-principal", at.cause).slice(0, 300);
155
157
  return {
156
158
  code: "config.cross_boundary_record_unwritten",
157
- message: `The session's cross-boundary record could not be written at ${at.phase} (${at.cause.slice(0, 300)}) — ` +
159
+ message: `The session's cross-boundary record could not be written at ${at.phase} (${cause}) — ` +
158
160
  (!at.governed
159
161
  ? "the tree is not governed: no judgment reads the record, so its failure refused nothing; the tree's growth, or the leg's end, writes next, and a failure of this same state is not reported again."
160
162
  : `the tree is governed: ${at.phase === "prepare" ? "the leg's next write (an exit before its call runs, a suspend before its row, its end) owes the record again; if none lands, " : ""}the next leg on this session reads the content after its newest record as off-target, so its device calls are put to a person.`),
161
- detail: { sessionId: at.sessionId, runId: at.runId, phase: at.phase, governed: at.governed, cause: at.cause.slice(0, 300) },
163
+ detail: { sessionId: at.sessionId, runId: at.runId, phase: at.phase, governed: at.governed, cause },
162
164
  };
163
165
  }
164
166
  const NO_LEGS = Object.freeze([]);
@@ -400,8 +400,9 @@ export interface EngineNotice {
400
400
  * is silent; a broken one must not look the same). Once per prepared leg;
401
401
  * `detail: { problem, sessionId, runId }`.
402
402
  * - `"config.execution_env_capability_invalid"` — a remote env DECLARES a boolean capability key
403
- * (`isolation`, `suspendable`, `canonicalPathAuthoritative`) with a non-boolean value; ABSENCE is a
404
- * supported reading of each and stays silent. Once per key per run; `detail: { key, valueType }`. Also minted for
403
+ * (`isolation`, `suspendable`, `canonicalPathAuthoritative`, `sharesProcessTable`) with a non-boolean value; ABSENCE is a
404
+ * supported reading of the first three and stays silent, and is announced for `sharesProcessTable`, which has no
405
+ * absent reading (`valueType: "undefined"`). Once per key per run; `detail: { key, valueType }`. Also minted for
405
406
  * `withheldEnv` (key `"withheldEnv"`) when it is neither a list of findings nor `"unknown"`, read at a shell call of
406
407
  * a root leg (once per run).
407
408
  * - `"config.secret_env_scrubbed"` (#1081) — the D1 fail-closed `scrub` policy, or the deployment's
@@ -1,8 +1,14 @@
1
+ import { egressAccount, egressText } from "./untrusted-egress.js";
1
2
  let malformedNoticeSeatAnnounced = false;
2
3
  export function __resetMalformedNoticeSeatAnnouncement() {
3
4
  malformedNoticeSeatAnnounced = false;
4
5
  }
5
6
  export function deliverEngineNotice(onNotice, notice) {
7
+ notice = {
8
+ ...notice,
9
+ message: typeof notice.message === "string" ? egressText("same-principal", notice.message) : notice.message,
10
+ ...(notice.detail !== undefined ? { detail: egressAccount("same-principal", notice.detail) } : {}),
11
+ };
6
12
  if (notice.sessionId === undefined && typeof notice.detail?.["sessionId"] === "string") {
7
13
  notice = { ...notice, sessionId: notice.detail["sessionId"] };
8
14
  }
@@ -114,11 +114,13 @@ export interface FileHistoryError {
114
114
  * whose history cannot be judged) must NOT wear this
115
115
  * code or the failure disappears into that silence — those are `adopt_failed`, which the
116
116
  * ADOPTION FAMILY shares: `importHistory` publishes under the same target law and answers the
117
- * same code for the same non-conflict failures, and `exportHistory` answers it for a history it cannot
118
- * read whole (its `unsupported_format` names a bad ENVELOPE or
119
- * work its shape drove; a fault in the PUBLISH step itself — the disk refusing a good graph —
120
- * answers `adopt_failed`); `unsupported_format`
121
- * = a foreign-epoch or wrong-version envelope; the rest name the verb that failed. */
117
+ * same code for the same non-conflict failures — a source that cannot supply a referenced blob (it is
118
+ * absent, its read throws, or its bytes do not hash to their address), a target whose history cannot
119
+ * be judged, a fault in staging or publishing a good graph (the disk refusing it) — and `exportHistory`
120
+ * answers it for a history it cannot read whole. `unsupported_format` is the import DOOR's refusal and
121
+ * nothing else: a foreign-epoch or wrong-version envelope, or an envelope whose content the closed sets
122
+ * (or the backend's own record-naming rules) cannot hold, refused before any blob is fetched; the rest
123
+ * name the verb that failed. */
122
124
  code: (typeof FILE_HISTORY_ERROR_CODES)[number];
123
125
  message: string;
124
126
  /** The backend's judgment of a FAULT — it could not do what it was asked — as a {@link GovernanceRetryClass}
@@ -330,8 +332,9 @@ export declare function fileHistoryExportDigest(data: FileHistoryExport): string
330
332
  * The import DOOR's structural judgment, shared by every backend (a second implementation is a second
331
333
  * opinion about what is safe to accept): the envelope must carry THIS format version and every row
332
334
  * must be shaped and self-described from the closed sets — `unsupported_format` otherwise, never a
333
- * coercion. Backends call this BEFORE fetching a single blob, so a foreign-epoch payload costs
334
- * nothing and changes nothing.
335
+ * coercion and never a throw (a payload that cannot be read is refused in the same word). Backends call
336
+ * this BEFORE fetching a single blob, so a foreign-epoch payload costs nothing and changes nothing, and
337
+ * `unsupported_format` is a word only this door (and a backend's own record-naming rule) says.
335
338
  */
336
339
  export declare function validateFileHistoryExport(data: FileHistoryExport): FileHistoryResult;
337
340
  /**
@@ -568,7 +571,10 @@ export interface FileHistoryStore {
568
571
  * re-publish). A non-empty target with any other content ⇒ typed `conflict` refusal — atomic
569
572
  * visibility prevents half-graphs, this law prevents publishing the WRONG complete graph over
570
573
  * locally-grown history. A target whose history cannot be judged ⇒ `adopt_failed`, before any blob is
571
- * fetched; a source that cannot supply a referenced blob ⇒ `adopt_failed`. `exportHistory` answers a
574
+ * fetched; a source that cannot supply a referenced blob — absent, its read throws, or bytes that fail
575
+ * their content address — ⇒ `adopt_failed`, and so is a fault in staging or publishing the graph.
576
+ * `unsupported_format` is the door's refusal alone ({@link validateFileHistoryExport}, before any blob
577
+ * is fetched). `exportHistory` answers a
572
578
  * {@link FileHistoryExportResult}: the whole graph, `null` for nothing to export, or `adopt_failed` with the
573
579
  * fault for a history it cannot read whole (an unreadable tracked set, boundaries that cannot be judged) —
574
580
  * never a partial graph, and never a `null` that hides a fault.
@@ -158,6 +158,14 @@ export function fileHistoryExportDigest(data) {
158
158
  return createHash("sha256").update(JSON.stringify(canonical)).digest("hex");
159
159
  }
160
160
  export function validateFileHistoryExport(data) {
161
+ try {
162
+ return judgeFileHistoryEnvelope(data);
163
+ }
164
+ catch (err) {
165
+ return { ok: false, error: { code: "unsupported_format", message: `file-history import payload cannot be read: ${thrownText(err)}` } };
166
+ }
167
+ }
168
+ function judgeFileHistoryEnvelope(data) {
161
169
  const bad = (message) => ({ ok: false, error: { code: "unsupported_format", message } });
162
170
  if (data === null || typeof data !== "object")
163
171
  return bad("file-history import payload is not an envelope");
@@ -182,6 +190,8 @@ export function validateFileHistoryExport(data) {
182
190
  if (typeof t.baseAvailable !== "boolean" || !Array.isArray(t.versions))
183
191
  return bad(`tracked row ${t.relOrAbsPath} is missing its base-availability state or version chain`);
184
192
  for (const v of t.versions) {
193
+ void v?.backupTime;
194
+ void v?.parentIdent;
185
195
  if (typeof v?.version !== "number" || !Number.isInteger(v.version) || v.version < 1)
186
196
  return bad(`tracked row ${t.relOrAbsPath} carries a non-integer version number`);
187
197
  if (v.blobHash !== null && (typeof v.blobHash !== "string" || !/^[0-9a-f]{64}$/.test(v.blobHash)))
@@ -874,7 +884,7 @@ export class InMemoryFileHistoryStore {
874
884
  }
875
885
  }
876
886
  catch (err) {
877
- return { ok: false, error: { code: "unsupported_format", message: `importHistory failed: ${thrownText(err)}`, ...caughtFault(err) } };
887
+ return { ok: false, error: { code: "adopt_failed", message: `importHistory failed: ${thrownText(err)}`, ...caughtFault(err) } };
878
888
  }
879
889
  }
880
890
  async importStaged(scope, data, srcGetBlob, needed) {
@@ -888,12 +898,12 @@ export class InMemoryFileHistoryStore {
888
898
  bytes = await srcGetBlob(hash);
889
899
  }
890
900
  catch (err) {
891
- return { ok: false, error: { code: "unsupported_format", message: `source blob ${hash} fetch failed: ${thrownText(err)}`, ...caughtFault(err) } };
901
+ return { ok: false, error: { code: "adopt_failed", message: `source blob ${hash} fetch failed: ${thrownText(err)}`, ...caughtFault(err) } };
892
902
  }
893
903
  if (bytes === undefined)
894
904
  return { ok: false, error: { code: "adopt_failed", message: `source blob ${hash} is missing — the envelope's graph cannot be completed, so nothing is published` } };
895
905
  if (createHash("sha256").update(bytes).digest("hex") !== hash) {
896
- return { ok: false, error: { code: "unsupported_format", message: `source blob ${hash} fails content-address integrity — refused, nothing published` } };
906
+ return { ok: false, error: { code: "adopt_failed", message: `source blob ${hash} fails content-address integrity — refused, nothing published` } };
897
907
  }
898
908
  staged.set(hash, bytes);
899
909
  }
@@ -920,7 +930,7 @@ export class InMemoryFileHistoryStore {
920
930
  return { ok: true };
921
931
  }
922
932
  catch (err) {
923
- return { ok: false, error: { code: "unsupported_format", message: `importHistory failed: ${thrownText(err)}`, ...caughtFault(err) } };
933
+ return { ok: false, error: { code: "adopt_failed", message: `importHistory failed: ${thrownText(err)}`, ...caughtFault(err) } };
924
934
  }
925
935
  }
926
936
  }
@@ -7,7 +7,7 @@ import { classifierNoRulingArm, settleNoRulingAsk } from "./classifier-unavailab
7
7
  import { SETTLEMENT_NOBODY_JUDGED } from "./gate-outcome.js";
8
8
  import { inlineUntrusted } from "./untrusted-text.js";
9
9
  import { ASK_USER_QUESTION_TOOL_NAME, isEngineQuestionFace } from "./ask-question.js";
10
- import { engineMountedAs } from "./tool-catalog-entries.js";
10
+ import { routesContent } from "./tool-catalog-entries.js";
11
11
  import { applyPersistedTightening, disclosedRuleSet } from "./persisted-rule-arms.js";
12
12
  import { skillAllowDecision, skillRowRefusal } from "./skill-grant.js";
13
13
  import { allowRuleShadowOf, withAllowRuleShadow } from "./allow-entry.js";
@@ -27,7 +27,6 @@ import { readStationAnswer } from "./shell-read-silence.js";
27
27
  import { agentIdOf, judgedInputOf, raiseToolCheck } from "./mods/stations.js";
28
28
  import { extensionAllowOf, readToolChecks } from "./tool-check-reading.js";
29
29
  import { policyThrowLine, reportPolicyThrow } from "./runner/policy-throw.js";
30
- const routesContent = engineMountedAs(["ask-user-question", "structured-output", "report-blocked"]);
31
30
  function parkWith(suspendAsk, parkArgs, carry) {
32
31
  return suspendAsk(...parkArgs, carry);
33
32
  }
@@ -336,7 +335,7 @@ export async function runGateLanes(pass) {
336
335
  const command = typeof args === "object" && args !== null && !Array.isArray(args) ? args[commandParam] : undefined;
337
336
  const backgrounded = typeof args === "object" && args !== null && args.run_in_background === true;
338
337
  const shape = typeof command === "string" ? readShellCommand(command) : undefined;
339
- if (typeof command === "string" && shape !== undefined && !backgrounded && readOnlyShellVerdict(command, shape, input.trackedCwd?.()).readOnly) {
338
+ if (typeof command === "string" && shape !== undefined && !backgrounded && readOnlyShellVerdict(command, { shape, cwd: input.trackedCwd?.() }).readOnly) {
340
339
  pass.decision = {
341
340
  action: "allow",
342
341
  message: "the command is read-only (upstream's read-only command tables) — allowed without asking",
@@ -1252,8 +1252,8 @@ export interface ToolGateInput extends PermissionSeatInputs, SkillGateSeats, All
1252
1252
  /**
1253
1253
  * F-012 L2 — the SANDBOX ADMISSION leg. Present ONLY when the runner armed the mode: the execution
1254
1254
  * env DECLARES isolation (`RemoteExecutionEnv.capabilities.isolation === true` — a Runner-resolved
1255
- * fact, never caller self-report) AND the park prerequisites hold (durable park configured + a
1256
- * checkpoint store that explicitly declares `redecision.reopen`). Absent ⇒ the whole step does not
1255
+ * fact, never caller self-report) AND the park prerequisites hold (durable park configured + a wired
1256
+ * checkpoint store — every store carries the required `reopen`, read at its seat). Absent ⇒ the whole step does not
1257
1257
  * exist and the decision path is byte-identical to a build without it.
1258
1258
  *
1259
1259
  * Semantics (the ruling): inside a declared sandbox, an engine-authored "confirm a potentially