@sema-agent/server 7.137.0 → 7.138.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 (209) hide show
  1. package/MIGRATION.md +328 -139
  2. package/README.md +1 -0
  3. package/README.zh-CN.md +1 -0
  4. package/USAGE.md +67 -20
  5. package/dist/approval-card.d.ts +21 -14
  6. package/dist/approval-card.js +1 -0
  7. package/dist/audit.js +1 -0
  8. package/dist/background-agent-name.d.ts +5 -0
  9. package/dist/background-agent-name.js +5 -0
  10. package/dist/boot/budget-tracing.d.ts +1 -12
  11. package/dist/boot/deployment-mods.d.ts +46 -16
  12. package/dist/boot/deployment-mods.js +82 -6
  13. package/dist/boot/governance-seams.d.ts +10 -17
  14. package/dist/boot/governance-seams.js +5 -17
  15. package/dist/boot/leader.d.ts +3 -0
  16. package/dist/boot/leader.js +1 -0
  17. package/dist/boot/org-delivery.js +15 -2
  18. package/dist/boot/resolve-spec.d.ts +3 -0
  19. package/dist/boot/resolve-spec.js +23 -12
  20. package/dist/boot/retention-lane.d.ts +11 -5
  21. package/dist/boot/retention-lane.js +2 -2
  22. package/dist/boot/runner-deps.d.ts +7 -3
  23. package/dist/boot/runner-deps.js +1 -0
  24. package/dist/boot/stage-04-stores.d.ts +1 -12
  25. package/dist/boot/stage-05-execution-env.d.ts +3 -12
  26. package/dist/boot/stage-05-execution-env.js +7 -4
  27. package/dist/boot/stage-06-runners.d.ts +3 -12
  28. package/dist/boot/stage-06-runners.js +5 -2
  29. package/dist/boot/stage-07-capability-layer.d.ts +3 -12
  30. package/dist/boot/stage-07-capability-layer.js +2 -2
  31. package/dist/boot/stage-08-reapers.d.ts +3 -12
  32. package/dist/boot/stage-09-leader.d.ts +3 -12
  33. package/dist/boot/stage-09-leader.js +2 -2
  34. package/dist/boot/stage-10-http-server.d.ts +3 -12
  35. package/dist/boot/stage-10-http-server.js +4 -2
  36. package/dist/boot/web-admin.d.ts +5 -2
  37. package/dist/boot/web-admin.js +3 -3
  38. package/dist/brain.js +5 -5
  39. package/dist/budget.d.ts +17 -15
  40. package/dist/budget.js +2 -2
  41. package/dist/capabilities/mcp-probe.d.ts +36 -11
  42. package/dist/capabilities/mcp-probe.js +44 -5
  43. package/dist/capability-facts.d.ts +6 -0
  44. package/dist/capability-facts.js +5 -0
  45. package/dist/checkpoint-identity.d.ts +13 -6
  46. package/dist/config-catalog.js +7 -1
  47. package/dist/config-types.d.ts +15 -7
  48. package/dist/config.js +8 -5
  49. package/dist/deployment-shape.d.ts +6 -6
  50. package/dist/gate-settlement.d.ts +71 -0
  51. package/dist/gate-settlement.js +40 -0
  52. package/dist/hooks/classic-stations.d.ts +1 -0
  53. package/dist/hooks/classic-stations.js +1 -0
  54. package/dist/hooks/command-spawn.d.ts +2 -2
  55. package/dist/hooks/hook-runner.js +14 -14
  56. package/dist/http/admission.js +4 -2
  57. package/dist/http/dispatch.d.ts +4 -0
  58. package/dist/http/dispatch.js +3 -0
  59. package/dist/http/durable-run-leg.js +4 -2
  60. package/dist/http/memory-compliance-failure-reply.js +3 -0
  61. package/dist/http/resume-legs.d.ts +5 -11
  62. package/dist/http/resume-legs.js +136 -91
  63. package/dist/http/route-ctx.d.ts +30 -51
  64. package/dist/http/route-ctx.js +2 -5
  65. package/dist/http/routes/a2a-serve.js +10 -4
  66. package/dist/http/routes/agents-roster.d.ts +1 -1
  67. package/dist/http/routes/agents-roster.js +5 -1
  68. package/dist/http/routes/approvals-assistant.js +6 -6
  69. package/dist/http/routes/capabilities.js +12 -10
  70. package/dist/http/routes/mcp-reconnect.d.ts +3 -2
  71. package/dist/http/routes/mcp-reconnect.js +7 -5
  72. package/dist/http/routes/memory-consolidation.d.ts +1 -1
  73. package/dist/http/routes/memory-origin.d.ts +1 -1
  74. package/dist/http/routes/rules.js +3 -3
  75. package/dist/http/routes/runs.js +11 -7
  76. package/dist/http/routes/sessions.js +6 -4
  77. package/dist/http/routes/side-query.js +2 -3
  78. package/dist/http/routes/tasks.js +792 -780
  79. package/dist/http/server.d.ts +31 -25
  80. package/dist/http/wire-types.d.ts +14 -6
  81. package/dist/identity-source.d.ts +1 -1
  82. package/dist/index.d.ts +2 -0
  83. package/dist/index.js +1 -0
  84. package/dist/leader/wire.d.ts +7 -0
  85. package/dist/leader/wire.js +5 -4
  86. package/dist/mcp-assertion-header.d.ts +51 -40
  87. package/dist/mcp-assertion-header.js +10 -11
  88. package/dist/mcp-authorization-face.d.ts +27 -0
  89. package/dist/mcp-authorization-face.js +15 -0
  90. package/dist/memory-operator-faces.d.ts +13 -4
  91. package/dist/memory-operator-faces.js +1 -17
  92. package/dist/memory-sync-client.js +72 -57
  93. package/dist/memory-sync.d.ts +1 -1
  94. package/dist/model-route-gap.d.ts +6 -22
  95. package/dist/model-route-gap.js +7 -5
  96. package/dist/mods-declaration.d.ts +110 -0
  97. package/dist/mods-declaration.js +109 -0
  98. package/dist/observability/err-text.d.ts +5 -0
  99. package/dist/observability/err-text.js +9 -0
  100. package/dist/observability/fail-open.d.ts +5 -1
  101. package/dist/observability/fail-open.js +5 -1
  102. package/dist/observability/secret-env-scrub.d.ts +1 -1
  103. package/dist/observability/secret-env-scrub.js +1 -1
  104. package/dist/parked-decide.d.ts +15 -2
  105. package/dist/parked-decide.js +14 -8
  106. package/dist/peer-seat.d.ts +150 -0
  107. package/dist/peer-seat.js +239 -0
  108. package/dist/permission-rule-vocab.d.ts +7 -7
  109. package/dist/permission-rule-vocab.js +3 -3
  110. package/dist/plain-string-record.d.ts +38 -0
  111. package/dist/plain-string-record.js +38 -0
  112. package/dist/plugins/background-agent-store-sql.js +6 -3
  113. package/dist/plugins/caching-session-store.d.ts +5 -0
  114. package/dist/plugins/caching-session-store.js +6 -0
  115. package/dist/plugins/checkpoint-store-sql.d.ts +15 -20
  116. package/dist/plugins/checkpoint-store-sql.js +8 -8
  117. package/dist/plugins/file-history-store-sql.js +3 -2
  118. package/dist/plugins/k8s-pod-reaper.d.ts +2 -8
  119. package/dist/plugins/k8s-pod-reaper.js +3 -3
  120. package/dist/plugins/local-checkpoint-store.d.ts +1 -11
  121. package/dist/plugins/local-checkpoint-store.js +0 -1
  122. package/dist/plugins/local-session-store.d.ts +66 -27
  123. package/dist/plugins/local-session-store.js +60 -28
  124. package/dist/plugins/mailbox-store-sql.d.ts +11 -3
  125. package/dist/plugins/mailbox-store-sql.js +103 -15
  126. package/dist/plugins/memory-sync-store-pg.d.ts +16 -5
  127. package/dist/plugins/memory-sync-store-tidb.d.ts +3 -3
  128. package/dist/plugins/one-time-migrations.d.ts +6 -3
  129. package/dist/plugins/one-time-migrations.js +3 -4
  130. package/dist/plugins/permission-rule-store-sql.js +21 -13
  131. package/dist/plugins/pg-session-storage.d.ts +10 -2
  132. package/dist/plugins/pg-session-storage.js +18 -5
  133. package/dist/plugins/remote-env-adb.d.ts +3 -1
  134. package/dist/plugins/remote-env-adb.js +1 -1
  135. package/dist/plugins/remote-env-device.d.ts +3 -1
  136. package/dist/plugins/remote-env-device.js +1 -1
  137. package/dist/plugins/remote-env-e2b.d.ts +2 -0
  138. package/dist/plugins/remote-env-e2b.js +1 -1
  139. package/dist/plugins/remote-env-host.d.ts +3 -1
  140. package/dist/plugins/remote-env-host.js +1 -1
  141. package/dist/plugins/remote-env-k8s.d.ts +3 -1
  142. package/dist/plugins/remote-env-k8s.js +1 -1
  143. package/dist/plugins/remote-env-local-docker.d.ts +2 -0
  144. package/dist/plugins/remote-env-local-docker.js +1 -1
  145. package/dist/plugins/remote-env-ssh.d.ts +3 -1
  146. package/dist/plugins/remote-env-ssh.js +1 -1
  147. package/dist/plugins/retention-store-sql.d.ts +14 -2
  148. package/dist/plugins/retention-store-sql.js +6 -0
  149. package/dist/plugins/session-fork.d.ts +10 -13
  150. package/dist/plugins/session-fork.js +5 -25
  151. package/dist/plugins/session-ownership.d.ts +7 -0
  152. package/dist/plugins/session-ownership.js +5 -0
  153. package/dist/plugins/session-workspace-cell.d.ts +17 -0
  154. package/dist/plugins/session-workspace-cell.js +38 -0
  155. package/dist/plugins/store-backend.js +7 -7
  156. package/dist/plugins/tidb-pool.js +6 -0
  157. package/dist/plugins/tidb-session-store.d.ts +17 -2
  158. package/dist/plugins/tidb-session-store.js +16 -5
  159. package/dist/plugins/tool-result-store-sql.d.ts +13 -6
  160. package/dist/plugins/tool-result-store-sql.js +8 -7
  161. package/dist/plugins/worktree-isolation.d.ts +3 -2
  162. package/dist/plugins/worktree-isolation.js +1 -7
  163. package/dist/project-identity.d.ts +1 -14
  164. package/dist/project-identity.js +0 -4
  165. package/dist/rules-consent.js +6 -4
  166. package/dist/run-local.js +12 -6
  167. package/dist/runs.d.ts +7 -1
  168. package/dist/runs.js +6 -1
  169. package/dist/sandbox-git-plane.d.ts +2 -2
  170. package/dist/server-secret-env.d.ts +6 -3
  171. package/dist/server-secret-env.js +4 -2
  172. package/dist/session-incarnation.d.ts +4 -5
  173. package/dist/session-incarnation.js +2 -2
  174. package/dist/session-workspace.d.ts +130 -0
  175. package/dist/session-workspace.js +83 -0
  176. package/dist/spec-fields.d.ts +1 -1
  177. package/dist/standing-answer-grant.d.ts +2 -13
  178. package/dist/standing-answer-grant.js +0 -7
  179. package/dist/task-a2a.d.ts +44 -14
  180. package/dist/task-a2a.js +46 -20
  181. package/dist/task-mcp.d.ts +33 -20
  182. package/dist/task-mcp.js +46 -22
  183. package/dist/tool-approval.d.ts +32 -8
  184. package/dist/tool-approval.js +49 -12
  185. package/dist/trace/core-keyset-guard.d.ts +33 -18
  186. package/dist/trace/engine-notice-wire.d.ts +1 -1
  187. package/dist/trace/engine-notice-wire.js +2 -0
  188. package/dist/trace/exit-seat.d.ts +20 -1
  189. package/dist/trace/exit-seat.js +3 -0
  190. package/dist/trace/extension-call-face.d.ts +33 -0
  191. package/dist/trace/extension-call-face.js +39 -0
  192. package/dist/trace/project.d.ts +25 -7
  193. package/dist/trace/project.js +29 -3
  194. package/dist/trace/redact.js +42 -13
  195. package/dist/trace/self-minted-wire.d.ts +129 -10
  196. package/dist/trace/self-minted-wire.js +62 -3
  197. package/dist/trace/wire-projection-faces.d.ts +6 -6
  198. package/dist/trace/wire-projection-faces.js +6 -5
  199. package/dist/user-authored-text.d.ts +2 -2
  200. package/dist/user-authored-text.js +16 -1
  201. package/dist/web-admin-client/center-request.d.ts +3 -2
  202. package/dist/web-admin-client/center-request.js +2 -3
  203. package/dist/web-admin-client/skills-mcp.d.ts +8 -4
  204. package/dist/web-admin-client/skills-mcp.js +31 -18
  205. package/dist/web-admin-client/types.d.ts +5 -0
  206. package/package.json +3 -3
  207. package/wire-changes/server@7.138.0.json +446 -0
  208. package/dist/http/mcp-remote-text.d.ts +0 -5
  209. package/dist/http/mcp-remote-text.js +0 -7
@@ -9,6 +9,7 @@ import { sendJson, sendError, isSseResponse, endSseWithError } from "./send.js";
9
9
  import { admissionRefusalOf, readinessOf, healthReadinessKeys, sendReadyz, sendBillableRefusal, billableRefusalOf, billableRefusalFrame } from "./readiness.js";
10
10
  import { ENGINE_SIGNAL_KEY, buildEngineSignal, fleetMembershipKeys, healthConfigLedgerKeys, healthDrainingKeys, mintEngineSignal, restartActionable } from "../fleet/engine-signal.js";
11
11
  import { buildHealthSelfReport } from "./health-self-report.js";
12
+ import { deploymentModPackages as deploymentModPackagesFact } from "../capability-facts.js";
12
13
  import { actsForDeployment, admitRunIdentity, authorized, systemFor, gatedPrincipal, sendPrincipalRefusal, sendUnauthorized, traceTokenPresented, verifiedIdentitySource, hasServiceAuth, devOpenDeployment } from "./principal-gate.js";
13
14
  const PROCESS_STARTED_AT_MS = Date.now() - Math.round(process.uptime() * 1000);
14
15
  export function readCredentialOk(req, config) {
@@ -127,6 +128,7 @@ export function createDispatch(ctx) {
127
128
  if (req.method === "GET" && url === "/health") {
128
129
  const signal = mintEngineSignal(deps);
129
130
  const engineSignal = buildEngineSignal(signal);
131
+ const declaredMods = deploymentModPackagesFact(deps);
130
132
  const configHash = createHash("sha256")
131
133
  .update(JSON.stringify({ model: deps.config.model.id, sessionBackend: deps.config.sessionBackend, dbBackend: deps.config.dbBackend, dataRoot: deps.config.localDataRoot, configProvider: deps.config.configProvider, remoteExec: deps.config.remoteExec?.provider, port: deps.config.port }))
132
134
  .digest("hex")
@@ -149,6 +151,7 @@ export function createDispatch(ctx) {
149
151
  startedAt: PROCESS_STARTED_AT_MS,
150
152
  port: req.socket.localPort,
151
153
  ...(deps.config.directDoorActive ? { directDoorActive: true } : {}),
154
+ ...(declaredMods.available ? { mods: { deploymentPackages: declaredMods.count } } : {}),
152
155
  ...buildHealthSelfReport(deps),
153
156
  ...(signal.restart ? { ...(restartActionable(signal) ? { restartRequired: true } : {}), restart: signal.restart } : {}),
154
157
  ...(() => {
@@ -1,6 +1,7 @@
1
1
  import { uuidv7 } from "@sema-agent/core";
2
2
  import { heldWorkflowParkDecisions } from "../parked-decide.js";
3
3
  import { HEARTBEAT_MS, runInBackground } from "../runs.js";
4
+ import { heldFor } from "../peer-seat.js";
4
5
  import { fleetRunPublisher, fleetRunLabels } from "../fleet/fleet-bus.js";
5
6
  import { cascadeConfig, runMeta } from "./run-meta.js";
6
7
  import { declaredApprovalFrameFamilies, resolveStreamApprovalGate } from "../tool-approval.js";
@@ -13,6 +14,7 @@ import { persistSubmitCtx } from "./submit-ctx.js";
13
14
  import { releaseClaimedRun } from "./claimed-run.js";
14
15
  import { serviceVersion } from "../service-version.js";
15
16
  import { headReadOf } from "../session-incarnation.js";
17
+ import { createSessionWorkspaceRecorder, legSessionWitnessOf, sessionWorkspaceStoreOf } from "../session-workspace.js";
16
18
  export async function mintAndDriveDurableRun(ctx, runStore, args) {
17
19
  const { deps } = ctx;
18
20
  const { inflightRuns, preemptableRuns, steerableRuns } = ctx.registry;
@@ -78,7 +80,7 @@ export async function mintAndDriveDurableRun(ctx, runStore, args) {
78
80
  sessionId,
79
81
  ...fleetRunLabels(args.prepared.spec.objective),
80
82
  });
81
- void runInBackground(args.prepared.runner, { ...args.prepared.spec, sessionId }, runStore, taskId, deps.metrics, args.prepared.auth?.principal, args.prepared.verify, args.prepared.cascade ? cascadeConfig(deps.config.cascadeLadder, args.prepared.spec.limits?.maxCostUsd) : undefined, deps.instrumentDegenerate, deps.planCacheProbe, deps.config.traceThinking, inflightRuns, preemptableRuns, steerableRuns, deps.modelUsage, deps.elicitation, args.elicitOwner, captureTurnAnchor, fleetPub, captureUserMessageAnchor, deps.question, deps.workflowCompletionInbox, deps.subagentSteerRegistry, (m, x) => deps.logger?.info?.(m, x), deps.sendUserFile, deps.promptManifests, deps.toolApproval
83
+ void heldFor(args.prepared.peerLease, () => runInBackground(args.prepared.runner, { ...args.prepared.spec, sessionId }, runStore, taskId, deps.metrics, args.prepared.auth?.principal, args.prepared.verify, args.prepared.cascade ? cascadeConfig(deps.config.cascadeLadder, args.prepared.spec.limits?.maxCostUsd) : undefined, deps.instrumentDegenerate, deps.planCacheProbe, deps.config.traceThinking, inflightRuns, preemptableRuns, steerableRuns, deps.modelUsage, deps.elicitation, args.elicitOwner, captureTurnAnchor, fleetPub, captureUserMessageAnchor, deps.question, deps.workflowCompletionInbox, deps.subagentSteerRegistry, (m, x) => deps.logger?.info?.(m, x), deps.sendUserFile, deps.promptManifests, deps.toolApproval
82
84
  ? {
83
85
  coordinator: deps.toolApproval,
84
86
  streamApprovalOn: resolveStreamApprovalGate({
@@ -91,7 +93,7 @@ export async function mintAndDriveDurableRun(ctx, runStore, args) {
91
93
  windowMarginMs: deps.config.streamAskWindowMarginMs,
92
94
  declaredFamilies: declaredApprovalFrameFamilies(args.prepared.body.settings),
93
95
  }
94
- : undefined, deps.checkpointStore, args.prepared.engineNotices, trustedInternals, deps.userInputLedger, args.prepared.acceptance);
96
+ : undefined, deps.checkpointStore, args.prepared.engineNotices, trustedInternals, deps.userInputLedger, args.prepared.acceptance, createSessionWorkspaceRecorder(sessionWorkspaceStoreOf(deps.sessionStorage), legSessionWitnessOf(args.prepared.spec))));
95
97
  return { status: 202, body: { taskId, sessionId, status: "running", ...(args.prepared.warnings !== undefined ? { warnings: args.prepared.warnings } : {}) } };
96
98
  }
97
99
  //# sourceMappingURL=durable-run-leg.js.map
@@ -8,6 +8,9 @@ function sentCoreCode(res, coreCode, coreMessage, audience) {
8
8
  case "config.memory_erasure_request":
9
9
  sendError(res, 400, "config.memory_erasure_request", textFor(audience, coreMessage, "session memory erasure refused — the erasure request is not valid; correct it and resend"));
10
10
  return true;
11
+ case "config.memory_erasure_scope":
12
+ sendError(res, 400, "config.memory_erasure_scope", textFor(audience, coreMessage, "session memory erasure refused — the request reaches memory outside the scopes it may erase; nothing was erased"));
13
+ return true;
11
14
  case "memory.erasure_evidence_unavailable":
12
15
  sendError(res, 409, "memory.erasure_evidence_unavailable", textFor(audience, coreMessage, "session memory erasure refused — this deployment's memory store cannot produce erasure evidence; resend with allowUnevidenced: true to erase without evidence, or ask the operator"));
13
16
  return true;
@@ -18,7 +18,7 @@ import type { CheckpointStoreFull } from "../plugins/store-backend.js";
18
18
  import type { HostDecision } from "../host-decision.js";
19
19
  import { type GateSighting } from "../approval-gate-currency.js";
20
20
  import { type PlanApprovePermissionModeAfter } from "../task-settings.js";
21
- import type { RouteCtxBase, DriveResumeArgs, HostLegOutcome, PreparedPlanReview, ResumeLegOutcome, WorkflowDecisionDelivery } from "./route-ctx.js";
21
+ import type { RouteCtxBase, DriveResumeArgs, PreparedPlanReview, ResumeLegOutcome, WorkflowDecisionDelivery } from "./route-ctx.js";
22
22
  import type { RunRegistry, RouteHelpers, RouteLegs } from "./route-ctx.js";
23
23
  import type { createRequestGuards } from "./request-guards.js";
24
24
  import type { FlatServiceDeps } from "./server.js";
@@ -59,12 +59,9 @@ export declare function createResumeLegs(ctx: {
59
59
  resumePreempted: (sessionId: string, req: IncomingMessage | undefined, acceptEarly?: boolean, decider?: {
60
60
  principal?: string;
61
61
  explicitOperator: boolean;
62
- }) => Promise<{
63
- status: number;
64
- body: object;
65
- }>;
66
- startFreshHostTurn: (sessionId: string, objective: string, principal: string | undefined, req: IncomingMessage, workflowDecision: WorkflowDecisionDelivery, runId: string) => Promise<HostLegOutcome>;
67
- resumeWake: (sessionId: string, message: string | undefined, caller: string | undefined, req: IncomingMessage | undefined, acceptEarly?: boolean, workflowDecision?: WorkflowDecisionDelivery, ownerLeaseAdmittedFor?: string) => Promise<HostLegOutcome>;
62
+ }) => Promise<ResumeLegOutcome>;
63
+ startFreshHostTurn: (sessionId: string, objective: string, principal: string | undefined, req: IncomingMessage, workflowDecision: WorkflowDecisionDelivery, runId: string) => Promise<ResumeLegOutcome>;
64
+ resumeWake: (sessionId: string, message: string | undefined, caller: string | undefined, req: IncomingMessage | undefined, acceptEarly?: boolean, workflowDecision?: WorkflowDecisionDelivery, ownerLeaseAdmittedFor?: string) => Promise<ResumeLegOutcome>;
68
65
  preparePlanReview: (sessionId: string, log?: {
69
66
  taskId?: string;
70
67
  principalPresent?: boolean;
@@ -81,9 +78,6 @@ export declare function createResumeLegs(ctx: {
81
78
  readonly ok: true;
82
79
  readonly prepared: PreparedPlanReview;
83
80
  }>;
84
- drivePlanReview: (prepared: PreparedPlanReview, decision: "approve" | "edit" | "reject", editedPlan: string | undefined, reason: string | undefined, hostDecision: HostDecision, req: IncomingMessage | undefined, acceptEarly?: boolean, permissionModeAfter?: PlanApprovePermissionModeAfter) => Promise<{
85
- status: number;
86
- body: object;
87
- }>;
81
+ drivePlanReview: (prepared: PreparedPlanReview, decision: "approve" | "edit" | "reject", editedPlan: string | undefined, reason: string | undefined, hostDecision: HostDecision, req: IncomingMessage | undefined, acceptEarly?: boolean, permissionModeAfter?: PlanApprovePermissionModeAfter) => Promise<ResumeLegOutcome>;
88
82
  };
89
83
  //# sourceMappingURL=resume-legs.d.ts.map
@@ -4,6 +4,7 @@ import { uuidv7, resumeWithVerification, CheckpointError, defaultTaskRegistry, t
4
4
  import { decideParkedAgent, denyWorkflowParkForked, denyWorkflowParksUnreadable, findParkOrigin, heldWorkflowParkDecisions, planWorkflowParkedDecide } from "../parked-decide.js";
5
5
  import { parkDecisionFingerprint } from "../orchestration/workflow-park-decision.js";
6
6
  import { headReadOf } from "../session-incarnation.js";
7
+ import { createSessionWorkspaceRecorder, legSessionWitnessOf, sessionWorkspaceStoreOf } from "../session-workspace.js";
7
8
  const DELIVERY_NOT_RECORDED = "workflow park decide: this delivery no longer holds the host session (its claim was released) — the decision was not recorded and this delivery was not started";
8
9
  const headUnavailable = async (sessionId) => {
9
10
  throw new Error(`parked decide cannot verify which occupant session ${sessionId} belongs to: this deployment's session store has no head read (headOf; every built-in store has one) — refusing rather than resuming unverified`);
@@ -38,7 +39,10 @@ import { errorText } from "../observability/err-text.js";
38
39
  import { recordFailOpen } from "../observability/fail-open.js";
39
40
  import { verifyRoundsFromBody } from "./verify-rounds.js";
40
41
  import { planReviewDecisionExitsPlan, storedBodyPermissionMode } from "../task-settings.js";
41
- import { acceptedResumingReceipt, acceptedTaskIdOf, buildHostLegOutcome } from "./route-ctx.js";
42
+ import { acceptedResumingReceipt, acceptedTaskIdOf } from "./route-ctx.js";
43
+ import { heldFor } from "../peer-seat.js";
44
+ import { NO_SETTLEMENT_EVIDENCE, buildSettlementEvidence, decisionCommittedOf } from "../gate-settlement.js";
45
+ import { durableRowIdentity } from "../checkpoint-identity.js";
42
46
  import { mintAndDriveDurableRun } from "./durable-run-leg.js";
43
47
  import { createSessionsLocal } from "./routes/sessions.js";
44
48
  import { createSessionSyncLocal } from "./routes/session-sync.js";
@@ -75,6 +79,8 @@ const WORKER_SWAP_REDEEMABLE = {
75
79
  ledger_event_unknown: true,
76
80
  real_approval_damaged: false,
77
81
  real_approval_forged: false,
82
+ tool_identity_missing: true,
83
+ bound_input_hash_missing: false,
78
84
  requirements_damaged: false,
79
85
  read_face_damaged: false,
80
86
  read_face_forged: false,
@@ -125,9 +131,9 @@ const CHECKPOINT_ERROR_DISPOSITION = {
125
131
  "checkpoint.gate_mismatch": "terminal",
126
132
  "checkpoint.reopen_failed": "terminal",
127
133
  "checkpoint.cwd_conflicts_restore": "terminal",
134
+ "steering.duplicate_input_id": "retriable",
128
135
  "checkpoint.already_exists": "terminal",
129
136
  "checkpoint.execution_outcome_conflict": "terminal",
130
- "steering.duplicate_input_id": "terminal",
131
137
  "steering.invalid_content": "terminal",
132
138
  "steering.queue_full": "terminal",
133
139
  };
@@ -159,6 +165,10 @@ const CHECKPOINT_ERROR_FIELD_ROLE = {
159
165
  hostDecision: "outcome_cell",
160
166
  approver: "outcome_cell",
161
167
  feedback: "outcome_cell",
168
+ outcome: "outcome_cell",
169
+ updatedInput: "outcome_cell",
170
+ decision: "outcome_cell",
171
+ reason: "outcome_cell",
162
172
  taskConfig: "config",
163
173
  deps: "config",
164
174
  inheritedGate: "config",
@@ -365,9 +375,7 @@ export function createResumeLegs(ctx) {
365
375
  }
366
376
  : undefined;
367
377
  const noteLateSettleIfAccepted = (out) => {
368
- if (lateSettleSink === undefined || lateSettleAnchor === undefined || out.status !== 200)
369
- return;
370
- if (out.body?.errorCode === "cancelled" && out.decisionCommitted !== true)
378
+ if (lateSettleSink === undefined || lateSettleAnchor === undefined || !decisionCommittedOf(out))
371
379
  return;
372
380
  try {
373
381
  lateSettleSink.recordLateSettle({ ...lateSettleAnchor, outcome: decision === "approve" ? "allowed" : "denied", decidedBy: hostDecision.decidedBy, ...(hostDecision.approver !== undefined ? { approver: hostDecision.approver } : {}), settledAtMs: Date.now() });
@@ -383,6 +391,7 @@ export function createResumeLegs(ctx) {
383
391
  if (parkedForkLeaseDenial)
384
392
  return parkedForkLeaseDenial;
385
393
  const ownerLeaseAdmittedHere = req !== undefined;
394
+ const captureSelectedGate = () => durableRowIdentity("decision-receipt", cp.checkpointId, token);
386
395
  if (req !== undefined) {
387
396
  const parkedAgentStore = deps.backgroundAgentStore;
388
397
  const parkedSurface = deps.parkedReviveSurface;
@@ -412,7 +421,7 @@ export function createResumeLegs(ctx) {
412
421
  ...(deps.parkedReviveInheritedGate ? { rebuildInheritedGate: deps.parkedReviveInheritedGate } : {}),
413
422
  ...(deps.toolApproval ? { approverSeatFor: windowZeroSeatOf(deps.toolApproval) } : {}),
414
423
  ...(deps.logger ? { warn: parkWarn } : {}),
415
- }, parkedReq, { handle: origin.handle, row: origin.row }));
424
+ }, { ...parkedReq, selectedGate: captureSelectedGate() }, { handle: origin.handle, row: origin.row }));
416
425
  void ctxPromise.catch(() => { });
417
426
  noteLateSettleIfAccepted(parked);
418
427
  return parked;
@@ -440,7 +449,7 @@ export function createResumeLegs(ctx) {
440
449
  if (held === null || held.fingerprint !== fingerprint)
441
450
  return undefined;
442
451
  const run = await runStore.getRun(held.deliveredTaskId);
443
- return run != null && !isTerminalRunStatus(run.status) ? acceptedResumingReceipt(held.deliveredTaskId, held.hostSessionId) : undefined;
452
+ return run != null && !isTerminalRunStatus(run.status) ? acceptedResumingReceipt(held.deliveredTaskId, held.hostSessionId, NO_SETTLEMENT_EVIDENCE) : undefined;
444
453
  };
445
454
  const replay = await liveReceiptOf(await decisions.get(token));
446
455
  if (replay !== undefined)
@@ -486,42 +495,48 @@ export function createResumeLegs(ctx) {
486
495
  const resolved = await resolveResumeSpecOrBlocked(ctx, req, auth);
487
496
  if (!resolved.ok)
488
497
  return resolved.answer;
489
- const { spec, engineNotices } = resolved;
490
- const pendingTool = cp.pendingAction.toolName;
491
- if (decision === "approve" && pendingTool === ASK_USER_QUESTION_TOOL_NAME && !answer) {
492
- return { status: 400, body: refusalBody({ errorCode: "request.field_conflict", message: "pending action is AskUserQuestion — approve must carry body.answer ({ answers: [{ header, selected: string[], note? }] })", facts: { ...fieldConflictFacts("ask_answer", ["decision", "answer"]) } }) };
498
+ try {
499
+ const { spec, engineNotices } = resolved;
500
+ const pendingTool = cp.pendingAction.toolName;
501
+ if (decision === "approve" && pendingTool === ASK_USER_QUESTION_TOOL_NAME && !answer) {
502
+ return { status: 400, body: refusalBody({ errorCode: "request.field_conflict", message: "pending action is AskUserQuestion — approve must carry body.answer ({ answers: [{ header, selected: string[], note? }] })", facts: { ...fieldConflictFacts("ask_answer", ["decision", "answer"]) } }) };
503
+ }
504
+ if (answer && (decision !== "approve" || pendingTool !== ASK_USER_QUESTION_TOOL_NAME)) {
505
+ return { status: 400, body: refusalBody({ errorCode: "request.field_conflict", message: `body.answer is only valid when approving a pending AskUserQuestion (decision: "${decision}", pending: "${pendingTool ?? "unknown"}")`, facts: { ...fieldConflictFacts("ask_answer", ["answer", "decision"]) } }) };
506
+ }
507
+ const { objective: resumeObjective, sessionId: _sessionId, ...taskConfig } = spec;
508
+ const pa = cp.pendingAction;
509
+ const persistedCallId = pa.kind === "tool_approval" ? pa.toolCallId : "";
510
+ const persistedHash = pa.kind === "tool_approval" ? (pa.boundInputHash ?? "") : "";
511
+ const outcome = {
512
+ gate: "policy_ask",
513
+ boundCallId: binding?.boundCallId ?? persistedCallId,
514
+ boundInputHash: binding?.boundInputHash ?? persistedHash,
515
+ decision: decision === "approve" ? "allow" : "deny",
516
+ ...(decision === "approve" && !isEngineQuestionRow(pa) && binding?.updatedInput !== undefined ? { updatedInput: binding.updatedInput } : {}),
517
+ ...(reason ? { reason } : {}),
518
+ ...(binding?.feedback !== undefined ? { feedback: binding.feedback } : {}),
519
+ ...(answer !== undefined ? { answer } : {}),
520
+ hostDecision,
521
+ };
522
+ const verifyRounds = verifyRoundsFromBody(ctx.body);
523
+ const driven = await driveResumeIntoRunLog({ peerLease: resolved.peerLease, runner: runnerFor(spec), token, sessionId: cp.sessionId, principal: auth.principal, fleetScope: resumeFleetScope(req, auth), taskConfig, resumeObjective, outcome, commitment: { kind: "gate_resolved", ...captureSelectedGate() }, verifyRounds, resumeCtx: { sessionId, ctx }, ...(engineNotices.length > 0 ? { engineNotices } : {}), ...(boundGrant ? { onResumeCommitted: boundGrant } : {}), ...(acceptEarly === true ? { acceptEarly: true } : {}), ...(req !== undefined ? { callerInitiated: true } : {}), ...(ownerLeaseAdmittedHere ? { ownerLeaseAdmitted: true } : {}) });
524
+ noteLateSettleIfAccepted(driven);
525
+ return driven;
493
526
  }
494
- if (answer && (decision !== "approve" || pendingTool !== ASK_USER_QUESTION_TOOL_NAME)) {
495
- return { status: 400, body: refusalBody({ errorCode: "request.field_conflict", message: `body.answer is only valid when approving a pending AskUserQuestion (decision: "${decision}", pending: "${pendingTool ?? "unknown"}")`, facts: { ...fieldConflictFacts("ask_answer", ["answer", "decision"]) } }) };
527
+ finally {
528
+ resolved.peerLease.release();
496
529
  }
497
- const { objective: resumeObjective, sessionId: _sessionId, ...taskConfig } = spec;
498
- const pa = cp.pendingAction;
499
- const persistedCallId = pa.kind === "tool_approval" ? pa.toolCallId : "";
500
- const persistedHash = pa.kind === "tool_approval" ? (pa.boundInputHash ?? "") : "";
501
- const outcome = {
502
- gate: "policy_ask",
503
- boundCallId: binding?.boundCallId ?? persistedCallId,
504
- boundInputHash: binding?.boundInputHash ?? persistedHash,
505
- decision: decision === "approve" ? "allow" : "deny",
506
- ...(decision === "approve" && !isEngineQuestionRow(pa) && binding?.updatedInput !== undefined ? { updatedInput: binding.updatedInput } : {}),
507
- ...(reason ? { reason } : {}),
508
- ...(binding?.feedback !== undefined ? { feedback: binding.feedback } : {}),
509
- ...(answer !== undefined ? { answer } : {}),
510
- hostDecision,
511
- };
512
- const verifyRounds = verifyRoundsFromBody(ctx.body);
513
- const driven = await driveResumeIntoRunLog({ runner: runnerFor(spec), token, sessionId: cp.sessionId, principal: auth.principal, fleetScope: resumeFleetScope(req, auth), taskConfig, resumeObjective, outcome, verifyRounds, resumeCtx: { sessionId, ctx }, ...(engineNotices.length > 0 ? { engineNotices } : {}), ...(boundGrant ? { onResumeCommitted: boundGrant } : {}), ...(acceptEarly === true ? { acceptEarly: true } : {}), ...(req !== undefined ? { callerInitiated: true } : {}), ...(ownerLeaseAdmittedHere ? { ownerLeaseAdmitted: true } : {}) });
514
- noteLateSettleIfAccepted(driven);
515
- return driven;
516
530
  }
517
531
  const ONE_SHOT_SUBMIT_KEYS = ["resumeAt", "restoreFiles", "acceptPartialRestore", "rewindFilesTo", "rewindFiles", "userAttachments", "images"];
518
532
  const ONE_SHOT_SUBMIT_STRIP = Object.fromEntries(ONE_SHOT_SUBMIT_KEYS.map((k) => [k, undefined]));
519
- const resolveResumeSpec = async (ctx, req, auth) => {
533
+ const resolveResumeSpec = async (ctx, req, auth, lease) => {
520
534
  const engineNotices = [];
521
535
  const spec = await deps.resolveSpec({ ...ctx.body, ...ONE_SHOT_SUBMIT_STRIP }, req, auth, {
522
536
  leg: "resume",
523
537
  onNotice: (n) => engineNotices.push(n),
524
538
  ...(ctx.unattendedLeg === true ? { unattendedLeg: true } : {}),
539
+ lease,
525
540
  });
526
541
  return { spec, engineNotices };
527
542
  };
@@ -533,11 +548,13 @@ export function createResumeLegs(ctx) {
533
548
  const replay = storedBodyReplayRefusal(ctx, deps.config);
534
549
  if (replay !== undefined)
535
550
  return { ok: false, answer: refuseStoredBodyReplay(auth.sessionId, replay) };
551
+ const lease = deps.peerDesk.lease("resume");
536
552
  try {
537
- const { spec, engineNotices } = await resolveResumeSpec(ctx, req, auth);
538
- return { ok: true, spec, engineNotices };
553
+ const { spec, engineNotices } = await resolveResumeSpec(ctx, req, auth, lease);
554
+ return { ok: true, spec, engineNotices, peerLease: lease };
539
555
  }
540
556
  catch (e) {
557
+ lease.release();
541
558
  if (e instanceof HttpError) {
542
559
  return { ok: false, answer: resumeRefusalWithCard(resumeBlockedByPolicyAnswer(httpErrorRefusal(e)), true) };
543
560
  }
@@ -546,11 +563,11 @@ export function createResumeLegs(ctx) {
546
563
  };
547
564
  async function driveResumeIntoRunLog(args) {
548
565
  if (args.acceptEarly !== true)
549
- return await driveResumeLeg(args);
566
+ return await heldFor(args.peerLease, () => driveResumeLeg(args));
550
567
  let settleAccepted;
551
568
  let wasAccepted = false;
552
569
  const accepted = new Promise((resolve) => { settleAccepted = resolve; });
553
- const leg = driveResumeLeg(args, (receipt) => { wasAccepted = true; settleAccepted?.(receipt); });
570
+ const leg = heldFor(args.peerLease, () => driveResumeLeg(args, (receipt) => { wasAccepted = true; settleAccepted?.(receipt); }));
554
571
  void leg.then((out) => {
555
572
  if (wasAccepted && out.status >= 400) {
556
573
  deps.logger?.warn?.("resume_leg_failed_after_accept", { sessionId: args.sessionId, status: out.status, body: out.body });
@@ -889,6 +906,7 @@ export function createResumeLegs(ctx) {
889
906
  const driveResume = async () => {
890
907
  resumeLegLive = true;
891
908
  const tailContentMode = resumeTaskConfig.forwardSubagentEvents === true ? "on" : "progress_only";
909
+ const resumeWorkspace = createSessionWorkspaceRecorder(sessionWorkspaceStoreOf(deps.sessionStorage), legSessionWitnessOf(resumeTaskConfig));
892
910
  const stream = await legRunner.resumeStream(token, outcome, { ...resumeTaskConfig, signal: cancelCtrl.signal, preemptSignal: preemptCtrl.signal }, {
893
911
  ...launchInternals,
894
912
  onForwardEvent: (e) => {
@@ -951,12 +969,14 @@ export function createResumeLegs(ctx) {
951
969
  if (taskId)
952
970
  steerableRuns.set(taskId, stream);
953
971
  if (!taskId || !rs) {
972
+ for await (const ev of stream)
973
+ await resumeWorkspace.observe(ev);
954
974
  const rNoRow = await stream.result();
955
975
  if (await reopenedAfterLeg(rNoRow, cancelCtrl.cause() !== undefined))
956
976
  reopenConfirmedPark = true;
957
977
  return sealResultForExit(rNoRow);
958
978
  }
959
- onAccepted?.(acceptedResumingReceipt(taskId, sessionId));
979
+ onAccepted?.(acceptedResumingReceipt(taskId, sessionId, buildSettlementEvidence({ at: "engine_accepted", commitment: args.commitment })));
960
980
  let causeAtDone;
961
981
  for await (const ev of stream) {
962
982
  if (ev.type === "done")
@@ -964,6 +984,7 @@ export function createResumeLegs(ctx) {
964
984
  recordTurnActivity(taskId);
965
985
  fleetPub?.onEvent(ev);
966
986
  await deps.userInputLedger?.observeLegEvent(stream, ev);
987
+ await resumeWorkspace.observe(ev);
967
988
  await appendPromptManifest(append, deps.promptManifests, taskId);
968
989
  switch (ev.type) {
969
990
  case "text_delta":
@@ -1221,13 +1242,13 @@ export function createResumeLegs(ctx) {
1221
1242
  settleFleet(checkpointReopened ? reparkStatus : resumePlane.status, fleetRunResiduals(sealed));
1222
1243
  return {
1223
1244
  status: 200,
1224
- decisionCommitted: true,
1225
1245
  body: {
1226
1246
  taskId, sessionId, status: resumePlane.status,
1227
1247
  bindingEnforced: true,
1228
1248
  ...(resumePlane.errorCode ? { errorCode: resumePlane.errorCode } : {}),
1229
1249
  ...(resumePlane.errorMessage ? { errorMessage: redactErrorMessage(resumePlane.errorMessage) ?? "" } : {}),
1230
1250
  ...(checkpointReopened ? { retriable: true } : {}),
1251
+ ...buildSettlementEvidence({ at: "engine_terminal", commitment: args.commitment }),
1231
1252
  },
1232
1253
  };
1233
1254
  }
@@ -1302,10 +1323,16 @@ export function createResumeLegs(ctx) {
1302
1323
  const resolved = await resolveResumeSpecOrBlocked(ctx, req, auth);
1303
1324
  if (!resolved.ok)
1304
1325
  return resolved.answer;
1305
- const { spec, engineNotices } = resolved;
1306
- const { objective: resumeObjective, sessionId: _sessionId, ...taskConfig } = spec;
1307
- const verifyRounds = verifyRoundsFromBody(ctx.body);
1308
- return driveResumeIntoRunLog({ runner: runnerFor(spec), token, sessionId: cp.sessionId, principal: auth.principal, fleetScope: resumeFleetScope(req, auth), taskConfig, resumeObjective, outcome: { gate: "resource_limit", decision: "continue" }, verifyRounds, resumeCtx: { sessionId, ctx }, ...(engineNotices.length > 0 ? { engineNotices } : {}), ...(acceptEarly === true ? { acceptEarly: true } : {}), ...(req !== undefined ? { callerInitiated: true } : {}) });
1326
+ try {
1327
+ const { spec, engineNotices } = resolved;
1328
+ const { objective: resumeObjective, sessionId: _sessionId, ...taskConfig } = spec;
1329
+ const verifyRounds = verifyRoundsFromBody(ctx.body);
1330
+ const selectedGate = durableRowIdentity("decision-receipt", cp.checkpointId, token);
1331
+ return await driveResumeIntoRunLog({ peerLease: resolved.peerLease, runner: runnerFor(spec), token, sessionId: cp.sessionId, principal: auth.principal, fleetScope: resumeFleetScope(req, auth), taskConfig, resumeObjective, outcome: { gate: "resource_limit", decision: "continue" }, commitment: { kind: "gate_resolved", ...selectedGate }, verifyRounds, resumeCtx: { sessionId, ctx }, ...(engineNotices.length > 0 ? { engineNotices } : {}), ...(acceptEarly === true ? { acceptEarly: true } : {}), ...(req !== undefined ? { callerInitiated: true } : {}) });
1332
+ }
1333
+ finally {
1334
+ resolved.peerLease.release();
1335
+ }
1309
1336
  }
1310
1337
  async function startFreshHostTurn(sessionId, objective, principal, req, workflowDecision, runId) {
1311
1338
  const cs = deps.checkpointStore;
@@ -1338,23 +1365,28 @@ export function createResumeLegs(ctx) {
1338
1365
  const resolved = await resolveResumeSpecOrBlocked(ctx, req, auth);
1339
1366
  if (!resolved.ok)
1340
1367
  return resolved.answer;
1341
- const { spec, engineNotices } = resolved;
1342
- const hostSpec = { ...spec, objective, sessionId };
1343
- const out = await mintAndDriveDurableRun(routeCtxBase, deps.runStore, {
1344
- prepared: { spec: hostSpec, runner: runnerFor(hostSpec), auth, body: ctx.body, acceptance: createRunAcceptance({}, { objective }), ...(engineNotices.length > 0 ? { engineNotices } : {}) },
1345
- fleetScope: resumeFleetScope(req, auth),
1346
- elicitOwner: principal ?? null,
1347
- source: "workflow_parked_decide",
1348
- identity: runIdentityOf(req, deps.config, principal ?? null),
1349
- workflowDecision,
1350
- });
1351
- if (out.existing) {
1352
- throw new Error(`decide host turn: createRun observed an existing row ${out.existing.taskId} for a freshly minted taskId (no idempotency key was sent)`);
1368
+ try {
1369
+ const { spec, engineNotices } = resolved;
1370
+ const hostSpec = { ...spec, objective, sessionId };
1371
+ const out = await mintAndDriveDurableRun(routeCtxBase, deps.runStore, {
1372
+ prepared: { spec: hostSpec, runner: runnerFor(hostSpec), auth, body: ctx.body, acceptance: createRunAcceptance({}, { objective }), peerLease: resolved.peerLease, ...(engineNotices.length > 0 ? { engineNotices } : {}) },
1373
+ fleetScope: resumeFleetScope(req, auth),
1374
+ elicitOwner: principal ?? null,
1375
+ source: "workflow_parked_decide",
1376
+ identity: runIdentityOf(req, deps.config, principal ?? null),
1377
+ workflowDecision,
1378
+ });
1379
+ if (out.existing) {
1380
+ throw new Error(`decide host turn: createRun observed an existing row ${out.existing.taskId} for a freshly minted taskId (no idempotency key was sent)`);
1381
+ }
1382
+ if (out.status !== 202)
1383
+ return { status: out.status, body: out.body };
1384
+ const minted = out.body;
1385
+ return acceptedResumingReceipt(minted.taskId, minted.sessionId, NO_SETTLEMENT_EVIDENCE);
1386
+ }
1387
+ finally {
1388
+ resolved.peerLease.release();
1353
1389
  }
1354
- if (out.status !== 202)
1355
- return { status: out.status, body: out.body };
1356
- const minted = out.body;
1357
- return acceptedResumingReceipt(minted.taskId, minted.sessionId);
1358
1390
  }
1359
1391
  async function resumeWake(sessionId, message, caller, req, acceptEarly, workflowDecision, ownerLeaseAdmittedFor) {
1360
1392
  const cs = deps.checkpointStore;
@@ -1418,26 +1450,33 @@ export function createResumeLegs(ctx) {
1418
1450
  const resolved = await resolveResumeSpecOrBlocked(ctx, req, auth);
1419
1451
  if (!resolved.ok)
1420
1452
  return resolved.answer;
1421
- const { spec, engineNotices } = resolved;
1422
- const { objective: resumeObjective, sessionId: _sessionId, ...taskConfig } = spec;
1423
- return buildHostLegOutcome(await driveResumeIntoRunLog({
1424
- runner: runnerFor(spec),
1425
- token,
1426
- sessionId: cp.sessionId,
1427
- principal: auth.principal,
1428
- fleetScope: resumeFleetScope(req, auth),
1429
- taskConfig,
1430
- resumeObjective,
1431
- outcome: { gate: "wake", ...(message !== undefined ? { message: { text: message, trusted } } : {}) },
1432
- verifyRounds: undefined,
1433
- resumeCtx: { sessionId, ctx },
1434
- mintFreshRun: { source: "wake", identity: runIdentityOf(req, deps.config, auth.principal ?? null) },
1435
- ...(workflowDecision !== undefined ? { workflowDecision } : {}),
1436
- ...(engineNotices.length > 0 ? { engineNotices } : {}),
1437
- ...(acceptEarly === true ? { acceptEarly: true } : {}),
1438
- ...(req !== undefined ? { callerInitiated: true } : {}),
1439
- ...(ownerLeaseAdmittedFor !== undefined && auth.principal === ownerLeaseAdmittedFor ? { ownerLeaseAdmitted: true } : {}),
1440
- }));
1453
+ try {
1454
+ const { spec, engineNotices } = resolved;
1455
+ const { objective: resumeObjective, sessionId: _sessionId, ...taskConfig } = spec;
1456
+ return await driveResumeIntoRunLog({
1457
+ peerLease: resolved.peerLease,
1458
+ runner: runnerFor(spec),
1459
+ token,
1460
+ sessionId: cp.sessionId,
1461
+ principal: auth.principal,
1462
+ fleetScope: resumeFleetScope(req, auth),
1463
+ taskConfig,
1464
+ resumeObjective,
1465
+ outcome: { gate: "wake", ...(message !== undefined ? { message: { text: message, trusted } } : {}) },
1466
+ commitment: { kind: "delivered" },
1467
+ verifyRounds: undefined,
1468
+ resumeCtx: { sessionId, ctx },
1469
+ mintFreshRun: { source: "wake", identity: runIdentityOf(req, deps.config, auth.principal ?? null) },
1470
+ ...(workflowDecision !== undefined ? { workflowDecision } : {}),
1471
+ ...(engineNotices.length > 0 ? { engineNotices } : {}),
1472
+ ...(acceptEarly === true ? { acceptEarly: true } : {}),
1473
+ ...(req !== undefined ? { callerInitiated: true } : {}),
1474
+ ...(ownerLeaseAdmittedFor !== undefined && auth.principal === ownerLeaseAdmittedFor ? { ownerLeaseAdmitted: true } : {}),
1475
+ });
1476
+ }
1477
+ finally {
1478
+ resolved.peerLease.release();
1479
+ }
1441
1480
  }
1442
1481
  async function preparePlanReview(sessionId, log, decider, sighting) {
1443
1482
  const cs = deps.checkpointStore;
@@ -1530,18 +1569,24 @@ export function createResumeLegs(ctx) {
1530
1569
  const resolved = await resolveResumeSpecOrBlocked(rebuildCtx, req, auth);
1531
1570
  if (!resolved.ok)
1532
1571
  return resolved.answer;
1533
- const { spec, engineNotices } = resolved;
1534
- const { objective: resumeObjective, sessionId: _sessionId, ...taskConfig } = spec;
1535
- const verifyRounds = verifyRoundsFromBody(ctx.body);
1536
- const outcome = {
1537
- gate: "plan_review",
1538
- decision,
1539
- ...(editedPlan !== undefined ? { editedPlan } : {}),
1540
- ...(reason ? { reason } : {}),
1541
- hostDecision,
1542
- };
1543
- const out = await driveResumeIntoRunLog({ runner: runnerFor(spec), token, sessionId: cp.sessionId, principal: auth.principal, fleetScope: resumeFleetScope(req, auth), taskConfig, resumeObjective, outcome, verifyRounds, resumeCtx: { sessionId, ctx: rebuildCtx }, ...(engineNotices.length > 0 ? { engineNotices } : {}), ...(acceptEarly === true ? { acceptEarly: true } : {}), ...(req !== undefined ? { callerInitiated: true } : {}) });
1544
- return out;
1572
+ try {
1573
+ const { spec, engineNotices } = resolved;
1574
+ const { objective: resumeObjective, sessionId: _sessionId, ...taskConfig } = spec;
1575
+ const verifyRounds = verifyRoundsFromBody(ctx.body);
1576
+ const outcome = {
1577
+ gate: "plan_review",
1578
+ decision,
1579
+ ...(editedPlan !== undefined ? { editedPlan } : {}),
1580
+ ...(reason ? { reason } : {}),
1581
+ hostDecision,
1582
+ };
1583
+ const selectedGate = durableRowIdentity("decision-receipt", cp.checkpointId, token);
1584
+ const out = await driveResumeIntoRunLog({ peerLease: resolved.peerLease, runner: runnerFor(spec), token, sessionId: cp.sessionId, principal: auth.principal, fleetScope: resumeFleetScope(req, auth), taskConfig, resumeObjective, outcome, commitment: { kind: "gate_resolved", ...selectedGate }, verifyRounds, resumeCtx: { sessionId, ctx: rebuildCtx }, ...(engineNotices.length > 0 ? { engineNotices } : {}), ...(acceptEarly === true ? { acceptEarly: true } : {}), ...(req !== undefined ? { callerInitiated: true } : {}) });
1585
+ return out;
1586
+ }
1587
+ finally {
1588
+ resolved.peerLease.release();
1589
+ }
1545
1590
  }
1546
1591
  const routeCtxBase = {
1547
1592
  deps,