@sema-agent/core 7.5.2 → 7.6.1

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 (138) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/dist/agents/agent-transcript-tool.d.ts +2 -2
  3. package/dist/agents/cascade.d.ts +4 -5
  4. package/dist/agents/cascade.js +12 -10
  5. package/dist/agents/repair-loop.d.ts +7 -5
  6. package/dist/agents/repair-loop.js +13 -15
  7. package/dist/agents/retain-ledger.d.ts +2 -3
  8. package/dist/agents/send-message-tool.d.ts +2 -2
  9. package/dist/agents/session-util.d.ts +2 -2
  10. package/dist/agents/subagent.d.ts +27 -46
  11. package/dist/agents/subagent.js +119 -105
  12. package/dist/agents/suspend-guard.d.ts +31 -19
  13. package/dist/agents/suspend-guard.js +14 -8
  14. package/dist/agents/teacher.d.ts +2 -2
  15. package/dist/agents/teacher.js +9 -9
  16. package/dist/agents/team.d.ts +6 -5
  17. package/dist/agents/team.js +10 -8
  18. package/dist/agents/verify.d.ts +8 -9
  19. package/dist/agents/verify.js +17 -17
  20. package/dist/core/a2a.js +2 -1
  21. package/dist/core/agent-definition.d.ts +172 -0
  22. package/dist/core/agent-definition.js +1 -0
  23. package/dist/core/ask-origin.d.ts +60 -7
  24. package/dist/core/ask-origin.js +26 -1
  25. package/dist/core/checkpoint-store.d.ts +78 -76
  26. package/dist/core/checkpoint-store.js +17 -1
  27. package/dist/core/delegation-frames.d.ts +298 -0
  28. package/dist/core/delegation-frames.js +21 -0
  29. package/dist/core/engine-notice.d.ts +555 -0
  30. package/dist/core/engine-notice.js +55 -0
  31. package/dist/core/gate-fold.d.ts +12 -0
  32. package/dist/core/gate-fold.js +158 -0
  33. package/dist/core/gate-lanes.d.ts +93 -0
  34. package/dist/core/gate-lanes.js +626 -0
  35. package/dist/core/gate-outcome.d.ts +189 -0
  36. package/dist/core/gate-outcome.js +70 -0
  37. package/dist/core/hands-band.d.ts +134 -0
  38. package/dist/core/hands-band.js +1 -0
  39. package/dist/core/hooks.d.ts +22 -177
  40. package/dist/core/hooks.js +53 -851
  41. package/dist/core/mcp-failure.d.ts +142 -0
  42. package/dist/core/mcp-failure.js +145 -0
  43. package/dist/core/mcp-server-spec.d.ts +217 -0
  44. package/dist/core/mcp-server-spec.js +1 -0
  45. package/dist/core/mcp.d.ts +21 -77
  46. package/dist/core/mcp.js +76 -150
  47. package/dist/core/model-seat.d.ts +99 -0
  48. package/dist/core/model-seat.js +1 -0
  49. package/dist/core/pause-registry.d.ts +131 -0
  50. package/dist/core/pause-registry.js +27 -0
  51. package/dist/core/reminder-mint.d.ts +10 -0
  52. package/dist/core/reminder-mint.js +3 -0
  53. package/dist/core/runner/assemble-result.d.ts +32 -41
  54. package/dist/core/runner/assemble-result.js +55 -74
  55. package/dist/core/runner/contracts.d.ts +427 -69
  56. package/dist/core/runner/denial-limit-arms.d.ts +1 -1
  57. package/dist/core/runner/denial-limit-arms.js +3 -3
  58. package/dist/core/runner/gate-exit.d.ts +242 -0
  59. package/dist/core/runner/gate-exit.js +124 -0
  60. package/dist/core/runner/park-commit.d.ts +17 -23
  61. package/dist/core/runner/park-commit.js +14 -15
  62. package/dist/core/runner/prepare-ask-lane.d.ts +0 -3
  63. package/dist/core/runner/prepare-ask-lane.js +3 -5
  64. package/dist/core/runner/prepare-boundary-parks.d.ts +3 -6
  65. package/dist/core/runner/prepare-boundary-parks.js +3 -3
  66. package/dist/core/runner/prepare-caps-and-workflow.d.ts +2 -7
  67. package/dist/core/runner/prepare-caps-and-workflow.js +1 -1
  68. package/dist/core/runner/prepare-delegation-surface.d.ts +2 -7
  69. package/dist/core/runner/prepare-gate-stations.d.ts +4 -7
  70. package/dist/core/runner/prepare-gate-stations.js +29 -54
  71. package/dist/core/runner/prepare-inherited-gate.js +1 -1
  72. package/dist/core/runner/prepare-memory.d.ts +44 -26
  73. package/dist/core/runner/prepare-park-ask.d.ts +2 -4
  74. package/dist/core/runner/prepare-park-ask.js +5 -5
  75. package/dist/core/runner/prepare-task.d.ts +2 -2
  76. package/dist/core/runner/prepare-task.js +8 -9
  77. package/dist/core/runner/prepare-wiring-manifest.d.ts +7 -15
  78. package/dist/core/runner/prepare-wiring-manifest.js +9 -10
  79. package/dist/core/runner/runtask.d.ts +20 -102
  80. package/dist/core/runner/runtask.js +119 -121
  81. package/dist/core/runner/terminal-projection.d.ts +22 -0
  82. package/dist/core/runner/terminal-projection.js +28 -0
  83. package/dist/core/runner-deps.d.ts +1416 -0
  84. package/dist/core/runner-deps.js +1 -0
  85. package/dist/core/runtime-caps.d.ts +164 -0
  86. package/dist/core/runtime-caps.js +1 -0
  87. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +4 -1
  88. package/dist/core/store-contracts/checkpoint-store-contract.js +8 -2
  89. package/dist/core/task-event.d.ts +910 -0
  90. package/dist/core/task-event.js +1 -0
  91. package/dist/core/task-limits.d.ts +110 -0
  92. package/dist/core/task-limits.js +1 -0
  93. package/dist/core/task-result.d.ts +809 -0
  94. package/dist/core/task-result.js +1 -0
  95. package/dist/core/task-spec.d.ts +1370 -0
  96. package/dist/core/task-spec.js +1 -0
  97. package/dist/core/task-stream.d.ts +382 -0
  98. package/dist/core/task-stream.js +1 -0
  99. package/dist/core/terminal-cause.d.ts +137 -0
  100. package/dist/core/terminal-cause.js +9 -0
  101. package/dist/core/tool-policy.d.ts +43 -139
  102. package/dist/core/tool-policy.js +79 -112
  103. package/dist/core/tool-spec.d.ts +1174 -0
  104. package/dist/core/tool-spec.js +1 -0
  105. package/dist/core/types.d.ts +27 -7789
  106. package/dist/core/types.js +2 -76
  107. package/dist/core/warm-resume.d.ts +2 -2
  108. package/dist/core/wiring-manifest.d.ts +6 -3
  109. package/dist/core/workflow-journal-store.js +3 -4
  110. package/dist/engine/harness/agent-harness.d.ts +1 -1
  111. package/dist/index.d.ts +12 -8
  112. package/dist/index.js +9 -6
  113. package/dist/orchestration/builtin-workflows.d.ts +2 -2
  114. package/dist/orchestration/builtin-workflows.js +1 -1
  115. package/dist/orchestration/goal.d.ts +2 -2
  116. package/dist/orchestration/goal.js +8 -7
  117. package/dist/orchestration/run-spec.d.ts +2 -2
  118. package/dist/orchestration/run-spec.js +5 -3
  119. package/dist/orchestration/run-workflow-tool.d.ts +4 -4
  120. package/dist/orchestration/run-workflow-tool.js +4 -4
  121. package/dist/orchestration/workflow-governance.d.ts +4 -4
  122. package/dist/orchestration/workflow-governance.js +4 -2
  123. package/dist/orchestration/workflow-primitives.d.ts +1 -1
  124. package/dist/orchestration/workflow-primitives.js +1 -1
  125. package/dist/orchestration/workflow.d.ts +15 -4
  126. package/dist/orchestration/workflow.js +64 -39
  127. package/dist/prompts/supervisor.d.ts +1 -1
  128. package/dist/prompts/supervisor.js +3 -3
  129. package/dist/scenarios/scenario-registry.d.ts +3 -3
  130. package/dist/scenarios/scenario-registry.js +1 -1
  131. package/dist/scenarios/teacher-quickstart.d.ts +2 -2
  132. package/dist/server/http.d.ts +2 -2
  133. package/dist/stores/file/fs-atomic.d.ts +88 -12
  134. package/dist/stores/file/fs-atomic.js +184 -55
  135. package/dist/stores/file/index.d.ts +1 -0
  136. package/dist/stores/file/index.js +1 -0
  137. package/package.json +3 -1
  138. package/test/export-surface.snapshot.json +82 -22
@@ -47,11 +47,21 @@ import type { PermissionResult } from "./tool-policy.js";
47
47
  * an instruction rather than hesitation.
48
48
  * - `denial_limit_fallback` — the auto-mode classifier reached its denial bound and handed the call
49
49
  * to a person; the classifier stands BEFORE this ask, never after it.
50
- * - `policy` — everything else: a deployment `ToolPolicy` ask, a mode-default ask, or one of the
51
- * engine's own post-fold tightens (egress / irreversibility / peer referral / write protection).
52
- * This is the hesitation family the one an armed classifier exists to resolve.
50
+ * - `shell_gate_tighten` — the gate's own irreversibility tighten raised the ask over a tier the COARSE
51
+ * `shellGate` doctrine installed (a shell command under `shellGate: "classify"`/`"always"`), not an
52
+ * explicit per-tool mark. The gate records that its own tighten spoke; a policy stamping
53
+ * `decisionReason: "safety"` on its own ask does not become one.
54
+ * - `safety_tighten` — the gate's own post-fold tightens over an EXPLICIT fact of the call: the tool's
55
+ * egress mark, its irreversibility mark, a peer-message referral in auto mode, a write onto the
56
+ * write-protected table. Same recording rule as above.
57
+ * - `policy` — everything else: a deployment `ToolPolicy` ask or a mode-default ask. This is the
58
+ * hesitation family — the one an armed classifier exists to resolve. The two tighten words are
59
+ * classifier-eligible too (arming auto mode is the deployment's explicit choice to let the
60
+ * classifier be the explicit ask-resolution for those classes); they are separate words so a deny
61
+ * observer or an approval card can say WHICH engine layer raised the question, which one word
62
+ * could not.
53
63
  */
54
- export declare const ASK_ORIGINS: readonly ["content_question", "unresolvable", "org_unavailable", "org_rule", "hook", "ask_rule", "denial_limit_fallback", "policy"];
64
+ export declare const ASK_ORIGINS: readonly ["content_question", "unresolvable", "org_unavailable", "org_rule", "hook", "ask_rule", "denial_limit_fallback", "shell_gate_tighten", "safety_tighten", "policy"];
55
65
  export type AskOrigin = (typeof ASK_ORIGINS)[number];
56
66
  /** Whether a value is a member of the closed origin set. Exported because the word crosses process
57
67
  * boundaries: a host validating one it received must not hand-roll the set. */
@@ -77,6 +87,10 @@ export interface AskOriginFacts {
77
87
  * read-back off the decision (a policy can put `decisionReason: "org_rule"` on its own ask; the
78
88
  * gate's cell cannot be written by a policy). */
79
89
  readonly org: "rule" | "unavailable" | undefined;
90
+ /** Which of the gate's OWN post-fold tightens minted the standing ask, if one did — again the gate's
91
+ * record of its own layer (a tighten replaces an `allow` wholesale, so at most one fires per pass),
92
+ * never a read-back of `decisionReason: "safety"` off a decision a policy could have composed. */
93
+ readonly tightened: "shell_gate" | "safety" | undefined;
80
94
  }
81
95
  /**
82
96
  * The ONE derivation of an ask's origin, from the surviving decision plus the gate's own facts. The
@@ -89,6 +103,39 @@ export interface AskOriginFacts {
89
103
  export declare function askOriginOf(decision: Extract<PermissionResult, {
90
104
  action: "ask";
91
105
  }>, facts: AskOriginFacts): AskOrigin;
106
+ /**
107
+ * Which origins IMPLY the `requiresRealApproval` bit, and WHERE that implication is verified — the
108
+ * consistency table {@link askOriginOf} reads. An ask of a must-carry origin exists so that no automatic
109
+ * lane clears it (the org's asks: "no configuration clears this, a person nods"; the classifier's
110
+ * fallback: no automatic lane may re-clear what the classifier already refused), so the bit must ride
111
+ * it. The bit is NOT derived from this table: the write sites each have their own reason, and a
112
+ * table-derived bit would have to either strip a safety policy's mandate or make every hook ask
113
+ * non-budgetable. The table only refuses the one shape that is a defect: a must-carry origin without
114
+ * the bit — and says where that refusal can honestly happen:
115
+ * - `"at_derivation"` — the origin is derived from a GATE fact only the engine writes (`facts.org`),
116
+ * so a missing bit is an engine defect and {@link askOriginOf} fails loud;
117
+ * - `"at_mint"` — the origin is derived from a decision MEMBER a policy may compose
118
+ * (`denialLimitFallback`: a self-declared one opts the policy's own ask out of automatic
119
+ * resolution, the safe direction, not a defect), so the invariant is pinned at the engine's two
120
+ * mint sites (the gate's fallback re-mint and the inherited station's) rather than thrown at
121
+ * derivation, where a policy's own object could trip it;
122
+ * - `false` — the origin says nothing about the bit: a `policy` ask may carry it (a safety policy's
123
+ * own mandate) or not, and both are legal.
124
+ */
125
+ export declare const ORIGIN_IMPLIES_REAL_APPROVAL: {
126
+ readonly content_question: false;
127
+ readonly unresolvable: false;
128
+ readonly org_unavailable: "at_derivation";
129
+ readonly org_rule: "at_derivation";
130
+ readonly hook: false;
131
+ readonly ask_rule: false;
132
+ readonly denial_limit_fallback: "at_mint";
133
+ readonly shell_gate_tighten: false;
134
+ readonly safety_tighten: false;
135
+ readonly policy: false;
136
+ };
137
+ /** The fence over the consistency table: `never` while every origin has a row. */
138
+ export type RealApprovalTableCoversEveryOrigin = AssertAllKeysHandled<Exclude<AskOrigin, keyof typeof ORIGIN_IMPLIES_REAL_APPROVAL>>;
92
139
  /**
93
140
  * The auto-mode CLASSIFIER's table: may a live classifier verdict resolve an ask of this origin?
94
141
  *
@@ -107,9 +154,13 @@ export declare function askOriginOf(decision: Extract<PermissionResult, {
107
154
  * whether the person is asked at all, which is the rule's whole content.
108
155
  * - `denial_limit_fallback`: the classifier already spoke (its bound is what minted this ask); an
109
156
  * allow here would un-say the fallback, a block would count it twice.
110
- * - `policy`: YES — including the engine's own safety tightens and a safety policy's
111
- * `requiresRealApproval` ask (a live verdict is judgment, and arming auto mode is the deployment's
112
- * explicit choice to let the classifier be the explicit ask-resolution for those classes).
157
+ * - `shell_gate_tighten` / `safety_tighten`: YES — the engine's own tightens are hesitation the
158
+ * deployment armed the classifier to resolve (the classify doctrine's whole point for the shell
159
+ * tier; the safety marks make the ask NON-BUDGETABLE at the durable park, a different axis from who
160
+ * may answer it live).
161
+ * - `policy`: YES — including a safety policy's `requiresRealApproval` ask (a live verdict is
162
+ * judgment, and arming auto mode is the deployment's explicit choice to let the classifier be the
163
+ * explicit ask-resolution for those classes).
113
164
  * Exclusion is skip-not-deny: the ask flows the original chain (park / approver / headless deny) —
114
165
  * narrower autonomy, never a lost ask.
115
166
  */
@@ -121,6 +172,8 @@ declare const CLASSIFIER_MAY_ANSWER: {
121
172
  readonly hook: false;
122
173
  readonly ask_rule: false;
123
174
  readonly denial_limit_fallback: false;
175
+ readonly shell_gate_tighten: true;
176
+ readonly safety_tighten: true;
124
177
  readonly policy: true;
125
178
  };
126
179
  /** The fence over the classifier table: `never` while every origin has a row (pinned by a type test). */
@@ -1,9 +1,16 @@
1
- export const ASK_ORIGINS = ["content_question", "unresolvable", "org_unavailable", "org_rule", "hook", "ask_rule", "denial_limit_fallback", "policy"];
1
+ export const ASK_ORIGINS = ["content_question", "unresolvable", "org_unavailable", "org_rule", "hook", "ask_rule", "denial_limit_fallback", "shell_gate_tighten", "safety_tighten", "policy"];
2
2
  const ASK_ORIGIN_SET = new Set(ASK_ORIGINS);
3
3
  export function isAskOrigin(v) {
4
4
  return ASK_ORIGIN_SET.has(v);
5
5
  }
6
6
  export function askOriginOf(decision, facts) {
7
+ const origin = originLadder(decision, facts);
8
+ if (ORIGIN_IMPLIES_REAL_APPROVAL[origin] === "at_derivation" && decision.requiresRealApproval !== true) {
9
+ throw new Error(`ask origin "${origin}" requires the requiresRealApproval bit on the decision it was derived from, and the decision carries none — an engine mint site dropped the bit`);
10
+ }
11
+ return origin;
12
+ }
13
+ function originLadder(decision, facts) {
7
14
  if (facts.contentQuestion)
8
15
  return "content_question";
9
16
  if (facts.markedUnresolvable)
@@ -18,8 +25,24 @@ export function askOriginOf(decision, facts) {
18
25
  return "ask_rule";
19
26
  if (decision.denialLimitFallback !== undefined)
20
27
  return "denial_limit_fallback";
28
+ if (facts.tightened === "shell_gate")
29
+ return "shell_gate_tighten";
30
+ if (facts.tightened === "safety")
31
+ return "safety_tighten";
21
32
  return "policy";
22
33
  }
34
+ export const ORIGIN_IMPLIES_REAL_APPROVAL = {
35
+ content_question: false,
36
+ unresolvable: false,
37
+ org_unavailable: "at_derivation",
38
+ org_rule: "at_derivation",
39
+ hook: false,
40
+ ask_rule: false,
41
+ denial_limit_fallback: "at_mint",
42
+ shell_gate_tighten: false,
43
+ safety_tighten: false,
44
+ policy: false,
45
+ };
23
46
  const CLASSIFIER_MAY_ANSWER = {
24
47
  content_question: false,
25
48
  unresolvable: false,
@@ -28,6 +51,8 @@ const CLASSIFIER_MAY_ANSWER = {
28
51
  hook: false,
29
52
  ask_rule: false,
30
53
  denial_limit_fallback: false,
54
+ shell_gate_tighten: true,
55
+ safety_tighten: true,
31
56
  policy: true,
32
57
  };
33
58
  export function classifierMayAnswer(origin) {
@@ -1,3 +1,4 @@
1
+ import type { CheckpointPause } from "./pause-registry.js";
1
2
  import type { SystemInjectionPriority } from "./task-notification.js";
2
3
  import { type QuestionAnswer } from "./ask-question.js";
3
4
  import type { ReadEntry } from "../tools/fs/safety.js";
@@ -439,10 +440,21 @@ export declare function buildRiskDescriptor(input: {
439
440
  * refuses to redeem such a row (the governed boundary must not vanish with a deployment's wiring).
440
441
  */
441
442
  export interface RealApprovalGateBit {
442
- /** The three members of the closed ask-origin set that can mint the bit derived FROM that set, so
443
- * the two vocabularies cannot drift into two spellings of one word. */
444
- origin: Extract<import("./ask-origin.js").AskOrigin, "org_rule" | "org_unavailable" | "policy">;
443
+ /** The parked ask's ORIGIN word a projection of the same closed set the card, the observer and the
444
+ * `tool_end` frame carry ({@link import("./ask-origin.js").AskOrigin}), never a second vocabulary. It
445
+ * is the ask's ROUTING word: a marked call reads `unresolvable` even when the org governs it, because
446
+ * the ladder routes a marked call to the park before anything else. Rows minted before the projection
447
+ * carried a three-word subset of this set (`org_rule` / `org_unavailable` / `policy`), so every stored
448
+ * value is still a member. */
449
+ origin: import("./ask-origin.js").AskOrigin;
450
+ /** The ORG FACT the ask was minted under (`"rule"` = an org rule asked, `"unavailable"` = org adjudication
451
+ * unreachable), kept beside the routing word because routing can outrank the word (a marked call reads
452
+ * `unresolvable`) while the governance stays. The #120 governed mark and the resume belts read THIS
453
+ * member through {@link realApprovalOrgFact} (absent on rows minted before it: the org words imply it). */
454
+ org?: "rule" | "unavailable";
445
455
  }
456
+ /** The ONE read of a bit's governed fact (the member; the two org origin words for rows minted before it). */
457
+ export declare function realApprovalOrgFact(bit: RealApprovalGateBit | undefined): "rule" | "unavailable" | undefined;
446
458
  export type CheckpointGate =
447
459
  /** F4: a human (or any external authority) must allow/deny a pending tool call. design/80 §D-E:
448
460
  * carries an OPTIONAL display-only {@link RiskDescriptor} (severity/axes/summary) for the supervisor
@@ -594,66 +606,38 @@ export type ResumeOutcome = {
594
606
  */
595
607
  answer?: QuestionAnswer;
596
608
  /**
597
- * WHAT ENDED THIS APPROVAL`"human"` (a person gave the final verdict), `"timeout"` (the
598
- * configured window elapsed with no answer), or `"aborted"` (every other non-human end: the run
599
- * was cancelled, the connection dropped, the store gave way, retries ran out). See
600
- * {@link import("./tool-policy.js").ApprovalSettledBy} for the vocabulary itself.
609
+ * The host's DECISION FACTSwho ended this wait: a person (`"person"`, the decide an operator made
610
+ * through the deployment's approval channel, with the attribution that channel reports), or the
611
+ * host's own SLA sweep at the row's deadline (`"sla_timeout"` FACET A of the deadline contract; only
612
+ * a sweep may say it, and a sweep cannot `allow`). REQUIRED: a decide that names nobody is not a
613
+ * record of anything that happened, and core does not guess.
601
614
  *
602
- * Attribution travels WITH the decision because it is a property OF the decision, not something
603
- * a later reader can reconstruct: by the time an approval reaches a consumer, "denied" and "the
604
- * window closed" have already collapsed into the same `decision:"deny"`, and only the caller that
605
- * settled it still knows which happened. Core does not derive it, does not default it, and never
606
- * infers it from some other outcome — it carries what the settling caller wrote onto the
607
- * `tool_end` frame of the call this resume resolves.
615
+ * The host supplies FACTS, never the settlement WORD: core mints the record
616
+ * ({@link import("./gate-outcome.js").GateOutcome} `human_allowed` / `human_refused` / `park_sla_expired`,
617
+ * joined with the row's persisted `origin`) at the resume entry, writes it onto the resolved row
618
+ * (`resolvedOutcome.gateOutcome`) and projects it onto the resolved call's `tool_end` frame. A caller
619
+ * that supplies `gateOutcome` itself is refused pre-CAS.
608
620
  *
609
- * **Fill it at every settlement site.** A deployment resolving an approval from several places
610
- * (an operator decide, an SLA sweep at the deadline, a cancel path) names the cause at each of
611
- * them. Deriving it once, centrally, from the last outcome at hand is precisely how a window that
612
- * elapsed gets reported to a person as another person's refusal.
621
+ * `approver` the attribution the approval channel reports for the party that ended the wait. Core
622
+ * AUTHENTICATES NOTHING: the identity work is the deployment's (its card, its signature check, its
623
+ * console). What core owns is that the claim is well-formed: a plain string, bounded, free of control
624
+ * characters, refused pre-CAS (`checkpoint.invalid_outcome`) otherwise refused rather than dropped,
625
+ * because a settlement that silently lost its attributed party reads as unattributed.
613
626
  *
614
- * OPTIONAL and additive: absent means the caller named no source (a caller written before this
615
- * field existed), and a consumer must NOT read a semantic out of the absence — core neither
616
- * substitutes a default nor guesses. A value outside the three words is refused pre-CAS
617
- * (`checkpoint.invalid_outcome`), like every other out-of-domain field on this arm: a settlement
618
- * source nobody can read is worse than none at all. So is one that contradicts its own decision:
619
- * `decision:"allow"` accepts only `"human"` or omission, because "nobody answered, therefore it
620
- * ran" is not a record of anything that happened (a deployment whose CONFIGURATION auto-allows an
621
- * elapsed window omits the field — no one ended that wait).
622
- *
623
- * SCOPE — this rides the settlement, NOT the persisted winner: `winnerFromOutcome` records the
624
- * decision (and its rewrite/answer/reason), which is what an `env_failed` replay must reproduce
625
- * exactly, and the source is not part of that authorization. A replay therefore states its own
626
- * source and is not held to the first one. Binding it into the persisted winner would also make a
627
- * re-supply that merely OMITS the field fail closed on the recovery path, which is the wrong place
628
- * to add a new refusal; carrying it into the durable row is a store-shape change owned jointly
629
- * with the backends and belongs to its own change, not to this one.
627
+ * SCOPE the facts ride the settlement, not the persisted winner: `winnerFromOutcome` records what an
628
+ * `env_failed` replay must reproduce exactly, and who reported a settlement is not part of that
629
+ * authorization; a replay states its own facts.
630
630
  */
631
- settledBy?: import("./tool-policy.js").ApprovalSettledBy;
631
+ hostDecision: {
632
+ decidedBy: "person" | "sla_timeout";
633
+ approver?: string;
634
+ };
632
635
  /**
633
- * design/252 G-7 WHOSE settlement: the identifier the approval channel reports for the party
634
- * that ended this wait, transcribed onto the resolved call's `tool_end` frame beside
635
- * {@link settledBy}.
636
- *
637
- * Core AUTHENTICATES NOTHING here. On the durable lane the identity work is entirely the
638
- * deployment's — its approval card, its signature check, its operator console decide who this
639
- * was; a library holding no identity surface has nothing to check the claim against and does not
640
- * pretend otherwise. What core owns is that the claim is well-formed: a plain string, bounded,
641
- * free of control characters, refused pre-CAS (`checkpoint.invalid_outcome`) otherwise, like
642
- * every other out-of-domain field on this arm. Refused rather than dropped, because a settlement
643
- * that silently lost its attributed party reads as unattributed — the misreport this seat exists
644
- * to end.
645
- *
646
- * SCOPE — like {@link settledBy}, this rides the SETTLEMENT and not the persisted winner:
647
- * `winnerFromOutcome` records what an `env_failed` replay must reproduce exactly, and who
648
- * reported a settlement is not part of that authorization. A replay states its own attribution
649
- * and is not held to the first one; binding it into the durable row would also make a re-supply
650
- * that merely omits the field fail closed on the recovery path. Carrying it into the row is a
651
- * store-shape change owned jointly with the backends and belongs to its own change.
652
- *
653
- * OPTIONAL and additive: absent means the caller named nobody, which is not the same as "nobody
654
- * approved this" and must not be read as one.
636
+ * CORE-MINTED, on the persisted twin only: the settlement record this decide produced. Present on
637
+ * the row's `resolvedOutcome` after the resolve, and on the `tool_end` frame of the resolved call.
638
+ * Never accepted from a caller (refused pre-CAS) — the host does not mint words.
655
639
  */
656
- approver?: string;
640
+ gateOutcome?: import("./gate-outcome.js").GateOutcome;
657
641
  }
658
642
  /** design/74: continue a resource-suspended run with the next slice's allowance. NO decision payload and
659
643
  * NO budget figure — the allowance is computed from {@link Checkpoint.resourceLedger}, so money never
@@ -874,6 +858,13 @@ export type PendingAction = {
874
858
  kind: "resource_limit";
875
859
  reason: ResourceLimitReason;
876
860
  }
861
+ /** design/76 §2.5: paused at a POST-PREDICTION REVIEW (`CheckpointGate.kind === "needs_review"`). There is NO
862
+ * pending tool to resolve — a human/judge reviews the buffered predicted state-diff (a PROFILE-held REF, never
863
+ * stored here) — so this arm has no fields. Its OWN member: before the pause registry a `needs_review` row
864
+ * borrowed another arm's seat, and the gate × pendingAction pairing lived in a comment. */
865
+ | {
866
+ kind: "review";
867
+ }
877
868
  /** design/80 D-B (r3 — an EXPLICIT arm, NOT a reused `resource_limit` placeholder): paused at a PRE-ACTION
878
869
  * PLAN REVIEW (`CheckpointGate.kind === "plan_review"`). There is NO pending tool to resolve — the human
879
870
  * reviews the PLAN, not a tool call — so this arm has no tool fields. Every consumer that reads the
@@ -1496,6 +1487,11 @@ export interface ResolvedOutcome {
1496
1487
  * the same decision" for no delivery benefit, since a replay is produced by re-presenting the same
1497
1488
  * outcome, not by reconstructing an equivalent one. */
1498
1489
  reason?: string;
1490
+ /** The settlement record core minted for this decide ({@link import("./gate-outcome.js").GateOutcome}):
1491
+ * the row's own copy of what the resolved call's `tool_end` frame carries, written with the winner so
1492
+ * an audit reading the row reads the same record the stream carried. NOT part of the replay equality
1493
+ * (`when` differs per replay; the record is a consequence of the decision, not its substance). */
1494
+ gateOutcome?: import("./gate-outcome.js").GateOutcome;
1499
1495
  }
1500
1496
  /**
1501
1497
  * design/80 D-1: why a consumed checkpoint was {@link CheckpointStore.reopen | reopened} (resolved →
@@ -1579,7 +1575,9 @@ export type TerminalClaimOutcome = {
1579
1575
  };
1580
1576
  /** A persisted suspension point: enough to resume a task on any replica. `status` drives the 3-state
1581
1577
  * machine (pending → resolved | expired) that makes resume idempotent (§5). */
1582
- export interface Checkpoint {
1578
+ /** A durable checkpoint: the row's fields ({@link CheckpointRow}) plus its registry-bound pause pair. */
1579
+ export type Checkpoint = CheckpointRow & CheckpointPause;
1580
+ export interface CheckpointRow {
1583
1581
  token: CheckpointToken;
1584
1582
  /** Non-secret stable identity ({@link mintCheckpointId}) — the display/correlation twin of
1585
1583
  * the secret `token` (identification vs capability, separate axes). Present on every row minted
@@ -1600,8 +1598,6 @@ export interface Checkpoint {
1600
1598
  sessionId: string;
1601
1599
  /** The session leaf the suspend happened at — the resume point (CAS write base, §5). */
1602
1600
  leafId: string;
1603
- gate: CheckpointGate;
1604
- pendingAction: PendingAction;
1605
1601
  state: CheckpointState;
1606
1602
  /** design/74 R3-B: cross-slice resource accounting. Present for a `resource_limit` gate (debited at the
1607
1603
  * suspend's `put`; read on resume to size the next slice + aggregate the final stats). **design/80 D-E-core
@@ -2075,15 +2071,15 @@ export declare class CheckpointError extends Error {
2075
2071
  * re-fetch the pending list), `"boundInputHash"` (the input you reviewed has changed under the same
2076
2072
  * action: re-review), or `"answer"` (the content-ask answer is missing, or was attached to a
2077
2073
  * decision that cannot carry one: re-decide with a well-formed answer — ruled 2026-08-04), or
2078
- * `"settledBy"` (the settlement source is outside the three-word vocabulary: re-decide naming one
2079
- * of them, or omit the field), or — design/252 G-7 — `"approver"` (the attribution is not a
2074
+ * `"hostDecision"` (the decision facts are missing, name a decider outside `person`/`sla_timeout`, pair
2075
+ * an SLA timeout with an allow, or try to supply the minted word), or — design/252 G-7 — `"approver"` (the attribution is not a
2080
2076
  * well-formed identifier: re-decide with a plain, bounded, control-character-free string, or omit
2081
2077
  * it). The throw
2082
2078
  * sites always knew which arm fired; this carries that fact instead of dropping it (ruled
2083
2079
  * 2026-08-03, cross-repo HITL discussion). Additive and optional — `code` remains the only REQUIRED
2084
2080
  * discriminant. */
2085
2081
  readonly detail?: {
2086
- field?: "boundCallId" | "boundInputHash" | "answer" | "settledBy" | "approver";
2082
+ field?: "boundCallId" | "boundInputHash" | "answer" | "hostDecision" | "approver";
2087
2083
  /** #449 G1 — on `resume.usage_window_exhausted`: how long until the binding window frees
2088
2084
  * (the same wait a suspended row carries as `gate.resumeAfterMs`). design/380 O2 — on a
2089
2085
  * retry-later `resume.preflight_rejected`: the deployment preflight's own wait hint, when it
@@ -2257,15 +2253,15 @@ export declare class CheckpointError extends Error {
2257
2253
  * re-fetch the pending list), `"boundInputHash"` (the input you reviewed has changed under the same
2258
2254
  * action: re-review), or `"answer"` (the content-ask answer is missing, or was attached to a
2259
2255
  * decision that cannot carry one: re-decide with a well-formed answer — ruled 2026-08-04), or
2260
- * `"settledBy"` (the settlement source is outside the three-word vocabulary: re-decide naming one
2261
- * of them, or omit the field), or — design/252 G-7 — `"approver"` (the attribution is not a
2256
+ * `"hostDecision"` (the decision facts are missing, name a decider outside `person`/`sla_timeout`, pair
2257
+ * an SLA timeout with an allow, or try to supply the minted word), or — design/252 G-7 — `"approver"` (the attribution is not a
2262
2258
  * well-formed identifier: re-decide with a plain, bounded, control-character-free string, or omit
2263
2259
  * it). The throw
2264
2260
  * sites always knew which arm fired; this carries that fact instead of dropping it (ruled
2265
2261
  * 2026-08-03, cross-repo HITL discussion). Additive and optional — `code` remains the only REQUIRED
2266
2262
  * discriminant. */
2267
2263
  detail?: {
2268
- field?: "boundCallId" | "boundInputHash" | "answer" | "settledBy" | "approver";
2264
+ field?: "boundCallId" | "boundInputHash" | "answer" | "hostDecision" | "approver";
2269
2265
  /** #449 G1 — on `resume.usage_window_exhausted`: how long until the binding window frees
2270
2266
  * (the same wait a suspended row carries as `gate.resumeAfterMs`). design/380 O2 — on a
2271
2267
  * retry-later `resume.preflight_rejected`: the deployment preflight's own wait hint, when it
@@ -2674,22 +2670,28 @@ export interface CheckpointStore {
2674
2670
  * The SINGLE resolution of "which CheckpointStore does this run use" (ruled 2026-08-04; re-homed
2675
2671
  * from prepare-task with the design/173 wiring manifest so the static self-check half shares it
2676
2672
  * without a runtime cycle). The spec seat is three-valued — a store wins, `undefined` falls through
2677
- * to the deployment's `RunnerDeps.checkpointStore`, and an explicit `null` means this run has NO
2678
- * store at all (see `TaskSpec.checkpointStore`: the per-run off switch for the durable machine, so
2679
- * a machine-started run cannot mint a checkpoint nobody will ever resolve).
2673
+ * to the deployment's `RunnerDeps.checkpointStore`, and the explicit word `"disabled"` means this run
2674
+ * has NO store at all (see `TaskSpec.checkpointStore`: the per-run off switch for the durable
2675
+ * machine, so a machine-started run cannot mint a checkpoint nobody will ever resolve).
2680
2676
  *
2681
- * Written as an explicit `=== null` test rather than the obvious `spec.checkpointStore ??
2682
- * deps.checkpointStore` BECAUSE `??` treats `null` exactly like `undefined`: under a bare nullish
2683
- * coalesce the null falls through and the run is handed the very deployment store the caller asked
2684
- * it not to have, re-arming every suspend leg silently, since the resulting run looks perfectly
2685
- * normal right up until it parks forever. Every read of the pair goes through here so that trap has
2686
- * ONE place to be wrong instead of nine.
2677
+ * The off switch is a WORD, not `null`, on purpose: `??` treats `null` exactly like `undefined`, so
2678
+ * the old null sentinel silently fell through a bare coalesce to the very deployment store the
2679
+ * caller asked not to have re-arming every suspend leg while the run looked perfectly normal right
2680
+ * up until it parked forever and every read of the pair had to remember a special `=== null` test.
2681
+ * A string is never nullish, so the ordinary coalesce is the whole rule. The retired spelling is
2682
+ * REFUSED loudly here (the one read every site goes through) rather than read as either value: a
2683
+ * caller still writing `null` is told the word to write, at the first prepare, instead of being
2684
+ * handed a store it asked not to have or a silent off switch it did not spell.
2687
2685
  */
2688
2686
  export declare function resolveCheckpointStore(spec: {
2689
- checkpointStore?: CheckpointStore | null;
2687
+ checkpointStore?: CheckpointStore | "disabled";
2690
2688
  }, deps: {
2691
2689
  checkpointStore?: CheckpointStore;
2692
2690
  }): CheckpointStore | undefined;
2691
+ /** The ONE refusal of the retired `checkpointStore: null` spelling (the resolver and the workflow baseline overlay both throw it). */
2692
+ export declare function retiredCheckpointStoreNull(): Error & {
2693
+ code: "config.invalid_checkpoint_store";
2694
+ };
2693
2695
  /**
2694
2696
  * design/80 D-1 (persist-winner): derive the durable {@link ResolvedOutcome} winner from a resume
2695
2697
  * `outcome`, or `undefined` when the outcome binds to no pending action. A `policy_ask` resume names a
@@ -178,6 +178,13 @@ export function buildRiskDescriptor(input) {
178
178
  return { severity: 2, axes: {}, toolName: typeof toolName === "string" ? toolName : "" };
179
179
  }
180
180
  }
181
+ export function realApprovalOrgFact(bit) {
182
+ if (bit === undefined)
183
+ return undefined;
184
+ if (bit.org !== undefined)
185
+ return bit.org;
186
+ return bit.origin === "org_rule" ? "rule" : bit.origin === "org_unavailable" ? "unavailable" : undefined;
187
+ }
181
188
  export const CURRENT_CHECKPOINT_VERSION = 1;
182
189
  export const RESOURCE_CHECKPOINT_VERSION = 2;
183
190
  export const BINDING_CHECKPOINT_VERSION = 3;
@@ -356,10 +363,18 @@ function sameValueAt(a, b, depth, aToB, bToA) {
356
363
  }
357
364
  }
358
365
  export function resolveCheckpointStore(spec, deps) {
359
- if (spec.checkpointStore === null)
366
+ if (Object.is(spec.checkpointStore, null))
367
+ throw retiredCheckpointStoreNull();
368
+ if (spec.checkpointStore === "disabled")
360
369
  return undefined;
361
370
  return spec.checkpointStore ?? deps.checkpointStore;
362
371
  }
372
+ export function retiredCheckpointStoreNull() {
373
+ const e = new Error('TaskSpec.checkpointStore: null is not a value any more — write checkpointStore: "disabled" to run without a ' +
374
+ "checkpoint store (the per-run durable off switch), or leave the field unset to use the deployment's store.");
375
+ e.code = "config.invalid_checkpoint_store";
376
+ return e;
377
+ }
363
378
  export function winnerFromOutcome(outcome) {
364
379
  if (outcome.gate === "plan_review" || outcome.gate === "dry_run_review") {
365
380
  const editedPlan = outcome.gate === "plan_review" ? outcome.editedPlan : undefined;
@@ -377,6 +392,7 @@ export function winnerFromOutcome(outcome) {
377
392
  decision: outcome.decision,
378
393
  ...(outcome.updatedInput === undefined ? {} : { updatedInput: outcome.updatedInput }),
379
394
  ...(outcome.answer === undefined ? {} : { answer: outcome.answer }),
395
+ ...(outcome.gateOutcome === undefined ? {} : { gateOutcome: outcome.gateOutcome }),
380
396
  ...(outcome.reason === undefined ? {} : { reason: outcome.reason }),
381
397
  };
382
398
  }