@sema-agent/server 7.149.0 → 7.150.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 (194) hide show
  1. package/MIGRATION.md +122 -9
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/USAGE.md +39 -15
  5. package/dist/approval-card.d.ts +1 -1
  6. package/dist/approval-gate-currency.d.ts +1 -1
  7. package/dist/audit.d.ts +1 -1
  8. package/dist/audit.js +2 -2
  9. package/dist/background-session-reap.d.ts +1 -1
  10. package/dist/boot/deferred-sandbox-path-env.d.ts +5 -3
  11. package/dist/boot/deferred-sandbox-path-env.js +1 -2
  12. package/dist/boot/parked-revive-gate.d.ts +5 -5
  13. package/dist/boot/parked-revive-gate.js +2 -3
  14. package/dist/boot/resolve-spec.d.ts +4 -5
  15. package/dist/boot/resolve-spec.js +61 -25
  16. package/dist/boot/retention-lane.d.ts +1 -1
  17. package/dist/boot/session-faces.js +21 -21
  18. package/dist/boot/stage-03-store-backend.js +2 -2
  19. package/dist/boot/stage-06-runners.js +1 -2
  20. package/dist/boot/stage-07-capability-layer.js +1 -1
  21. package/dist/boot/stage-10-http-server.d.ts +33 -1
  22. package/dist/boot/stage-10-http-server.js +9 -4
  23. package/dist/boot/web-admin.d.ts +1 -1
  24. package/dist/capability-facts.d.ts +65 -56
  25. package/dist/capability-facts.js +42 -35
  26. package/dist/config-catalog.js +1 -1
  27. package/dist/config-types.d.ts +12 -11
  28. package/dist/deployment-governance.d.ts +7 -28
  29. package/dist/deployment-governance.js +1 -14
  30. package/dist/device-ws-hub.d.ts +1 -0
  31. package/dist/device-ws-hub.js +64 -28
  32. package/dist/device-ws-protocol.d.ts +20 -6
  33. package/dist/device-ws-protocol.js +9 -2
  34. package/dist/engine-identity.d.ts +7 -1
  35. package/dist/engine-identity.js +5 -0
  36. package/dist/field-conflict.d.ts +2 -2
  37. package/dist/fleet/engine-signal.d.ts +14 -1
  38. package/dist/fleet/engine-signal.js +5 -1
  39. package/dist/fleet/fleet-reconciler.d.ts +1 -1
  40. package/dist/fleet/serving-lease.d.ts +18 -3
  41. package/dist/fleet/serving-lease.js +81 -35
  42. package/dist/fleet-client.d.ts +13 -11
  43. package/dist/fleet-client.js +53 -33
  44. package/dist/history-anchor.js +1 -1
  45. package/dist/hitl-delivery.d.ts +1 -3
  46. package/dist/hitl-delivery.js +1 -1
  47. package/dist/http/active-run-conflict.d.ts +3 -7
  48. package/dist/http/active-run-conflict.js +2 -2
  49. package/dist/http/admission.js +2 -2
  50. package/dist/http/approval-sweeps.js +2 -1
  51. package/dist/http/caller-trust.d.ts +2 -2
  52. package/dist/http/caller-trust.js +3 -3
  53. package/dist/http/checkpoint-ctx.d.ts +18 -63
  54. package/dist/http/checkpoint-ctx.js +1 -24
  55. package/dist/http/claim-heartbeat.d.ts +4 -4
  56. package/dist/http/claim-heartbeat.js +3 -3
  57. package/dist/http/dispatch.js +2 -2
  58. package/dist/http/durable-run-leg.js +4 -4
  59. package/dist/http/owner-access.d.ts +243 -19
  60. package/dist/http/owner-access.js +184 -5
  61. package/dist/http/readiness.d.ts +4 -2
  62. package/dist/http/resume-ctx-read.js +16 -1
  63. package/dist/http/resume-legs.js +18 -14
  64. package/dist/http/route-ctx.d.ts +23 -11
  65. package/dist/http/routes/a2a-serve.js +15 -14
  66. package/dist/http/routes/approvals-assistant.d.ts +3 -2
  67. package/dist/http/routes/approvals-assistant.js +8 -9
  68. package/dist/http/routes/capabilities.js +4 -3
  69. package/dist/http/routes/config-catalog.d.ts +1 -1
  70. package/dist/http/routes/mcp-reconnect.js +2 -2
  71. package/dist/http/routes/memory-origin.d.ts +1 -1
  72. package/dist/http/routes/notify-wake.js +2 -3
  73. package/dist/http/routes/projects.d.ts +1 -0
  74. package/dist/http/routes/projects.js +5 -3
  75. package/dist/http/routes/retention-ops.d.ts +1 -1
  76. package/dist/http/routes/run-permission-mode.d.ts +1 -1
  77. package/dist/http/routes/run-permission-mode.js +2 -2
  78. package/dist/http/routes/runs.d.ts +1 -1
  79. package/dist/http/routes/runs.js +23 -30
  80. package/dist/http/routes/session-memory-sync.js +4 -5
  81. package/dist/http/routes/session-sync.js +3 -7
  82. package/dist/http/routes/sessions.d.ts +1 -1
  83. package/dist/http/routes/sessions.js +18 -44
  84. package/dist/http/routes/tasks.js +9 -8
  85. package/dist/http/routes/trace-usage.js +14 -14
  86. package/dist/http/routes/workflows.d.ts +1 -0
  87. package/dist/http/routes/workflows.js +20 -38
  88. package/dist/http/server.d.ts +8 -4
  89. package/dist/http/server.js +5 -1
  90. package/dist/http/stored-body-replay.d.ts +3 -24
  91. package/dist/http/stored-body-replay.js +2 -12
  92. package/dist/http/submit-ctx.d.ts +1 -2
  93. package/dist/http/submit-ctx.js +3 -2
  94. package/dist/http/submit-replay.d.ts +15 -2
  95. package/dist/http/submit-replay.js +10 -4
  96. package/dist/http/wire-types.d.ts +6 -3
  97. package/dist/leader/endpoint.js +9 -8
  98. package/dist/leg-facts.d.ts +8 -2
  99. package/dist/leg-facts.js +1 -0
  100. package/dist/mcp-authorization-face.d.ts +18 -14
  101. package/dist/mcp-authorization-face.js +3 -2
  102. package/dist/memory-store-refusal.d.ts +1 -1
  103. package/dist/observability/corrupt-read-seat.d.ts +1 -1
  104. package/dist/observability/fail-open.d.ts +5 -5
  105. package/dist/observability/fail-open.js +5 -5
  106. package/dist/observability/logger.d.ts +7 -0
  107. package/dist/observability/logger.js +3 -1
  108. package/dist/observability/run-terminal.d.ts +1 -1
  109. package/dist/owner-faces.d.ts +27 -3
  110. package/dist/owner-faces.js +2 -0
  111. package/dist/peer-seat.d.ts +1 -1
  112. package/dist/peer-seat.js +52 -59
  113. package/dist/plugins/exec-path-law.d.ts +34 -0
  114. package/dist/plugins/exec-path-law.js +13 -0
  115. package/dist/plugins/file-run-store.js +1 -1
  116. package/dist/plugins/local-checkpoint-store.d.ts +4 -1
  117. package/dist/plugins/local-checkpoint-store.js +3 -0
  118. package/dist/plugins/memory-run-store.js +1 -1
  119. package/dist/plugins/one-time-migrations.d.ts +74 -49
  120. package/dist/plugins/one-time-migrations.js +134 -69
  121. package/dist/plugins/pg-pool.js +4 -0
  122. package/dist/plugins/posix-shell-fs.js +4 -2
  123. package/dist/plugins/remote-env-adb.js +3 -2
  124. package/dist/plugins/remote-env-device.js +8 -6
  125. package/dist/plugins/remote-env-e2b.d.ts +3 -1
  126. package/dist/plugins/remote-env-e2b.js +11 -8
  127. package/dist/plugins/remote-env-host.d.ts +1 -1
  128. package/dist/plugins/remote-env-host.js +20 -11
  129. package/dist/plugins/remote-env-k8s.js +3 -2
  130. package/dist/plugins/remote-env-local-docker.js +3 -2
  131. package/dist/plugins/remote-env-ssh.js +4 -3
  132. package/dist/plugins/remote-shell.d.ts +3 -1
  133. package/dist/plugins/remote-shell.js +3 -3
  134. package/dist/plugins/replica-registration.d.ts +35 -0
  135. package/dist/plugins/replica-registration.js +48 -0
  136. package/dist/plugins/retention-lane-store-sql.d.ts +1 -1
  137. package/dist/plugins/run-store-sql.d.ts +1 -1
  138. package/dist/plugins/run-store-sql.js +2 -2
  139. package/dist/plugins/scenario-prefix-switch-m42.d.ts +6 -12
  140. package/dist/plugins/scenario-prefix-switch-m42.js +7 -5
  141. package/dist/plugins/schema-consistency.d.ts +7 -5
  142. package/dist/plugins/schema-consistency.js +29 -16
  143. package/dist/plugins/schema-drift-sentence.js +1 -1
  144. package/dist/plugins/serving-requirement.d.ts +49 -0
  145. package/dist/plugins/serving-requirement.js +53 -0
  146. package/dist/plugins/stop-then-start-sentence.d.ts +42 -3
  147. package/dist/plugins/stop-then-start-sentence.js +44 -0
  148. package/dist/plugins/store-backend.d.ts +1 -1
  149. package/dist/plugins/store-contracts.d.ts +12 -2
  150. package/dist/plugins/store-contracts.js +3 -0
  151. package/dist/plugins/tidb-pool.js +4 -0
  152. package/dist/process-instance.d.ts +3 -0
  153. package/dist/process-instance.js +7 -0
  154. package/dist/project-context.d.ts +66 -0
  155. package/dist/project-context.js +70 -0
  156. package/dist/question.d.ts +9 -21
  157. package/dist/question.js +28 -19
  158. package/dist/run-cancel-card.d.ts +1 -1
  159. package/dist/run-cancel-card.js +2 -1
  160. package/dist/security.d.ts +1 -1
  161. package/dist/session-agent.d.ts +1 -10
  162. package/dist/session-agent.js +0 -6
  163. package/dist/session-incarnation.d.ts +18 -4
  164. package/dist/session-incarnation.js +4 -1
  165. package/dist/session-notify-door.d.ts +4 -4
  166. package/dist/session-notify-door.js +5 -6
  167. package/dist/session-rules-seat.d.ts +4 -3
  168. package/dist/session-rules-seat.js +3 -11
  169. package/dist/session-sync.js +3 -3
  170. package/dist/stores-sql/checkpoint-store-sql.d.ts +21 -2
  171. package/dist/stores-sql/checkpoint-store-sql.js +106 -18
  172. package/dist/stores-sql/retention-store-sql.d.ts +1 -1
  173. package/dist/task-cwd.d.ts +4 -3
  174. package/dist/task-settings.d.ts +13 -0
  175. package/dist/task-settings.js +13 -7
  176. package/dist/tool-approval.d.ts +7 -6
  177. package/dist/tool-approval.js +41 -37
  178. package/dist/trace/engine-notice-wire.d.ts +14 -0
  179. package/dist/trace/engine-notice-wire.js +18 -1
  180. package/dist/trace/project.d.ts +4 -3
  181. package/dist/trace/project.js +3 -2
  182. package/dist/trace/projection-table.d.ts +33 -2
  183. package/dist/trace/self-minted-wire.d.ts +150 -17
  184. package/dist/trace/self-minted-wire.js +54 -16
  185. package/dist/trace/tool-card.d.ts +3 -0
  186. package/dist/trace/tool-card.js +24 -0
  187. package/dist/trace/tool-output-media.d.ts +2 -1
  188. package/dist/unattended-permission-mode.d.ts +1 -1
  189. package/dist/web-admin-client/apply-effective.d.ts +1 -1
  190. package/dist/web-admin-client/effective-keys.d.ts +1 -1
  191. package/dist/web-admin-client/skills-mcp.d.ts +1 -1
  192. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +57 -29
  193. package/package.json +2 -2
  194. package/wire-changes/server@7.150.0.json +335 -0
@@ -1,8 +1,12 @@
1
1
  import { createLogger } from "../observability/logger.js";
2
2
  import { DEPLOYMENT_SHAPE_PREDICATES } from "../deployment-shape.js";
3
- import { FACES, fleetOn, listFilterOf, ownerCellOf, qualsOf, visibleOn } from "../owner-faces.js";
3
+ import { engineScopeOf } from "../engine-identity.js";
4
+ import { sameOccupant } from "../execution-placement.js";
5
+ import { sessionAgentOf } from "../session-agent.js";
6
+ import { deletionHeadReadOf, sessionFaceOfHead } from "../session-incarnation.js";
7
+ import { FACES, fleetOn, listFilterOf, ownerCellOf, qualsOf, recoveredSubject, visibleOn } from "../owner-faces.js";
4
8
  import { mintOwnedRow, partsOf, personOf, subjectOf } from "../owner-brands.js";
5
- import { sessionCellFor } from "../security.js";
9
+ import { claimCallerOf, ownerOfCheckpointScope, sessionCellFor } from "../security.js";
6
10
  import { openFaceFaultWire, SessionOwnerUnreadableError } from "../session-error-wire.js";
7
11
  import { seatText } from "../trace/exit-seat.js";
8
12
  import { actsForDeployment, explicitOperatorOk, gatedSubject, sendPrincipalRefusal, sendUnauthorized, traceTokenPresented } from "./principal-gate.js";
@@ -96,6 +100,11 @@ export const ROUTE_FACES = {
96
100
  "assistant-task-preempt": "run.preempt",
97
101
  "assistant-task-resume": "run.resume",
98
102
  "assistant-task-plan-review": "run.decision",
103
+ "workflows-list": "workflow.read",
104
+ "workflow-detail": "workflow.read",
105
+ "workflow-journal": "workflow.read",
106
+ "workflow-stream": "workflow.read",
107
+ "workflow-agent-steer": "workflow.steer",
99
108
  "sessions-list": "session.list",
100
109
  "sendfile-links": "list.read",
101
110
  "fleet-stream": "list.read",
@@ -276,6 +285,9 @@ export function refuse(entry, res, reason, id) {
276
285
  case "deleted_false":
277
286
  sendJson(res, 200, { deleted: false });
278
287
  return;
288
+ case "workflow_missing":
289
+ sendError(res, 404, "not_found.workflow", "workflow not found");
290
+ return;
279
291
  case "run_exists":
280
292
  case "attach_absent":
281
293
  case "omit":
@@ -290,8 +302,10 @@ function rowRead(reading) {
290
302
  return reading !== undefined && reading !== null;
291
303
  }
292
304
  function verdictOn(entry, cell) {
305
+ return verdictOnQuals(entry, qualsOf(entry.caller, cell));
306
+ }
307
+ function verdictOnQuals(entry, quals) {
293
308
  const face = FACES[entry.face];
294
- const quals = qualsOf(entry.caller, cell);
295
309
  if (face.operator && quals.has("operator"))
296
310
  return quals;
297
311
  if (doorRefusal(face, entry.face, entry.caller) !== undefined)
@@ -325,14 +339,23 @@ export async function admitRunRow(entry, res, runStore, row, id) {
325
339
  refuse(entry, res, "session_mismatch", id);
326
340
  return undefined;
327
341
  }
342
+ return observedAdmission(entry, runStore, admitted);
343
+ }
344
+ async function observedAdmission(entry, runStore, admitted) {
328
345
  return { ...admitted, row: mintOwnedRow(await runRowAt(entry.mouth, runStore, admitted.row)) };
329
346
  }
330
347
  export async function admitRunById(entry, res, runStore, taskId) {
331
348
  return admitRunRow(entry, res, runStore, await runStore.getRun(taskId), taskId);
332
349
  }
350
+ export async function admitRunByIdem(entry, res, runStore, idemKey) {
351
+ return admitRunRow(entry, res, runStore, await runStore.getRunByIdem(idemKey));
352
+ }
333
353
  export async function readVisibleRun(entry, runStore, taskId) {
334
354
  const row = await runStore.getRun(taskId);
335
- return rowRead(row) && seesRun(entry, row.owner) ? runRowAt(entry.mouth, runStore, row) : undefined;
355
+ if (!rowRead(row))
356
+ return undefined;
357
+ const admitted = judgeRun(entry, row);
358
+ return admitted === undefined ? undefined : observedAdmission(entry, runStore, admitted);
336
359
  }
337
360
  export function seesRun(entry, owner) {
338
361
  const verdict = verdictOn(entry, ownerCellOf(owner));
@@ -371,9 +394,25 @@ export async function admitSessionRow(entry, res, store, sessionId, reading) {
371
394
  export async function judgeSessionRow(entry, store, sessionId, reading) {
372
395
  return judge(entry, reading, await sessionCellFor(entry.face, entry.caller, store, sessionId, reading.owner));
373
396
  }
397
+ export async function judgeSessionById(entry, store, sessionId) {
398
+ const head = await readOwnerCell(entry, sessionId, () => store.headOf(sessionId));
399
+ return sessionRowPresent(head) ? judgeSessionRow(entry, store, sessionId, head) : undefined;
400
+ }
374
401
  export async function admitSessionById(entry, res, store, sessionId) {
402
+ const verdict = await judgeSessionById(entry, store, sessionId);
403
+ if (verdict === undefined) {
404
+ refuse(entry, res, "missing");
405
+ return undefined;
406
+ }
407
+ return deliverVerdict(entry, res, verdict);
408
+ }
409
+ export async function readVisibleSession(entry, store, sessionId) {
410
+ const verdict = await judgeSessionById(entry, store, sessionId);
411
+ return verdict === undefined || verdict === "door" || verdict === "not_visible" ? undefined : verdict;
412
+ }
413
+ export async function sessionOwnerUnchanged(entry, store, sessionId, admittedOwner) {
375
414
  const head = await readOwnerCell(entry, sessionId, () => store.headOf(sessionId));
376
- return admitSessionRow(entry, res, store, sessionId, head);
415
+ return sessionRowPresent(head) && head.owner === admittedOwner;
377
416
  }
378
417
  export async function judgeSession(entry, store, sessionId, reading) {
379
418
  if (!sessionRowPresent(reading))
@@ -390,4 +429,144 @@ export function entryIsFleet(entry) {
390
429
  export function routeListFilter(req, w, route) {
391
430
  return listFilterOf(faceOfRoute(route), callerOf(req, w));
392
431
  }
432
+ export function sealRouteDeps(deps) {
433
+ return deps;
434
+ }
435
+ export async function runFactsOf(store, taskId) {
436
+ const row = await store.getRun(taskId);
437
+ return rowRead(row) ? { status: row.status, instanceId: row.instanceId, sessionId: row.sessionId } : undefined;
438
+ }
439
+ export async function latestRunIdOf(store, sessionId) {
440
+ return (await store.latestRunForSession(sessionId))?.taskId;
441
+ }
442
+ export function sessionFactsReadOf(store) {
443
+ const headOf = store?.headOf;
444
+ if (typeof headOf !== "function")
445
+ return undefined;
446
+ return async (sessionId) => {
447
+ const head = await headOf.call(store, sessionId);
448
+ return rowRead(head) ? { get leafId() { return head.leafId; } } : undefined;
449
+ };
450
+ }
451
+ export async function readSessionAgent(store, sessionId) {
452
+ const placementOf = store?.placementOf;
453
+ if (placementOf === undefined)
454
+ return undefined;
455
+ return sessionAgentOf(await placementOf.call(store, sessionId));
456
+ }
457
+ export function occupantRecheckOf(store) {
458
+ const headOf = store?.headOf;
459
+ if (typeof headOf !== "function")
460
+ return undefined;
461
+ return async (sessionId, occupant) => {
462
+ const head = await headOf.call(store, sessionId);
463
+ if (!rowRead(head))
464
+ return "gone";
465
+ return sameOccupant(occupant, head) ? "same" : "replaced";
466
+ };
467
+ }
468
+ export async function engineRulesKeyOf(store, sessionId, principal) {
469
+ if (principal === undefined || typeof store?.headOf !== "function")
470
+ return principal;
471
+ const owner = (await store.headOf(sessionId))?.owner;
472
+ if (owner === undefined || owner === null)
473
+ return principal;
474
+ return personOf(recoveredSubject(owner)) === principal ? owner : principal;
475
+ }
476
+ export async function readVisibleLatestRun(entry, store, sessionId) {
477
+ const latest = await store.latestRunForSession(sessionId);
478
+ return rowRead(latest) && seesRun(entry, latest.owner) ? latest : undefined;
479
+ }
480
+ export function runListOwnerOf(filter, named) {
481
+ return filter.gateOwner ?? named;
482
+ }
483
+ export function listVisibleRuns(store, filter, named, query) {
484
+ const owner = runListOwnerOf(filter, named);
485
+ return store.listRuns({ ...query, ...(owner ? { owner } : {}) });
486
+ }
487
+ export function listRunsOfScope(store, scope, limit) {
488
+ return store.listRuns({ owner: ownerOfCheckpointScope(scope), limit });
489
+ }
490
+ export function replayHitOf(subject, config, row, clientTaskId) {
491
+ const sameRun = clientTaskId === undefined || clientTaskId === row.taskId;
492
+ return sameRun && seesRunAs("run.replay", claimCallerOf(subject, config), row.owner) ? { kind: "visible", row } : { kind: "occupied" };
493
+ }
494
+ export async function readReplayHit(subject, config, store, key, clientTaskId) {
495
+ const row = "idemKey" in key ? await store.getRunByIdem(key.idemKey) : await store.getRun(key.taskId);
496
+ return rowRead(row) ? replayHitOf(subject, config, row, clientTaskId) : undefined;
497
+ }
498
+ export async function admitSessionView(entry, res, store, sessionAudit, sessionId, readOpts) {
499
+ const gated = store !== undefined && typeof store.headOf === "function";
500
+ if (gated && (await admitSessionById(entry, res, store, sessionId)) === undefined)
501
+ return undefined;
502
+ let audit;
503
+ try {
504
+ audit = await sessionAudit(sessionId, readOpts);
505
+ }
506
+ catch (e) {
507
+ if (gated)
508
+ throw e;
509
+ throw new Error(`session view read failed before ownership could be established: ${e instanceof Error ? e.message : String(e)}`, { cause: e });
510
+ }
511
+ if ((await admitSessionRow(entry, res, store, sessionId, audit)) === undefined)
512
+ return undefined;
513
+ return audit;
514
+ }
515
+ export async function readSyncGate(entry, store, sessionId) {
516
+ const live = await store.headOf(sessionId);
517
+ const face = rowRead(live) ? sessionFaceOfHead(sessionId, live) : undefined;
518
+ const held = face === undefined ? await deletionHeadReadOf(store)(sessionId) : undefined;
519
+ const occupant = face ?? (rowRead(held) ? sessionFaceOfHead(sessionId, held) : undefined);
520
+ const mayTouch = occupant !== undefined && (await judgeSession(entry, store, sessionId, occupant)) !== undefined;
521
+ return { face, occupant, mayTouch };
522
+ }
523
+ export async function toolResultRefOwned(store, ref, run) {
524
+ const provenance = store?.ownerOf ? await store.ownerOf(ref) : undefined;
525
+ return provenance !== undefined && provenance.sessionId === run.sessionId && (provenance.taskId === undefined || provenance.taskId === run.taskId);
526
+ }
527
+ export async function readVisibleLeaderRow(store, id, visible) {
528
+ const row = await store.getRun(id);
529
+ return row !== undefined && visible(row.owner) ? row : undefined;
530
+ }
531
+ export const WORKFLOW_SESSION_ASSERTION = {
532
+ "workflow-detail": { phase: "accept", label: "workflow.detail" },
533
+ "workflow-journal": { phase: "accept", label: "workflow.journal" },
534
+ "workflow-stream": { phase: "accept", label: "workflow.stream" },
535
+ "workflow-agent-steer": { phase: "accept", label: "workflow.agent-steer" },
536
+ };
537
+ export function listVisibleWorkflows(entry, store, opts) {
538
+ return store.listByScope(engineScopeOf(entry.caller.subject), opts);
539
+ }
540
+ export async function judgeWorkflowRun(entry, store, runId) {
541
+ const row = await store.get(runId);
542
+ if (!rowRead(row))
543
+ return "missing";
544
+ const engineCaller = { ...entry.caller, subject: recoveredSubject(engineScopeOf(entry.caller.subject)) };
545
+ const verdict = verdictOnQuals(entry, qualsOf(engineCaller, ownerCellOf(row.scope)));
546
+ if (verdict === "door" || verdict === "not_visible")
547
+ return verdict;
548
+ const rule = Object.hasOwn(WORKFLOW_SESSION_ASSERTION, entry.mouth) ? WORKFLOW_SESSION_ASSERTION[entry.mouth] : undefined;
549
+ if (rule === undefined)
550
+ throw new Error(`owner access: mouth ${entry.mouth} judges a workflow row but is not registered in WORKFLOW_SESSION_ASSERTION`);
551
+ return sessionAsserted(rule, entry.req, row.originatingSessionId, verdict.has("operator")) ? { row, quals: verdict } : "session_mismatch";
552
+ }
553
+ export async function admitWorkflowRun(entry, res, store, runId) {
554
+ const verdict = await judgeWorkflowRun(entry, store, runId);
555
+ switch (verdict) {
556
+ case "missing":
557
+ case "not_visible":
558
+ case "session_mismatch":
559
+ refuse(entry, res, verdict);
560
+ return undefined;
561
+ case "door":
562
+ sendDoorRefusal(entry, res, doorRefusal(FACES[entry.face], entry.face, entry.caller));
563
+ return undefined;
564
+ default:
565
+ return verdict;
566
+ }
567
+ }
568
+ export async function admitSessionForDeletion(entry, res, store, sessionId) {
569
+ const placementOf = store.placementOf?.bind(store);
570
+ return admitSessionById(entry, res, { headOf: deletionHeadReadOf(store), ...(placementOf !== undefined ? { placementOf } : {}) }, sessionId);
571
+ }
393
572
  //# sourceMappingURL=owner-access.js.map
@@ -72,7 +72,8 @@ export declare function sendReadyz(res: ServerResponse, v: ReadinessVerdict): vo
72
72
  export declare const BILLABLE_KINDS: readonly ["submit", "hitl-answer", "resume"];
73
73
  export type BillableKind = (typeof BILLABLE_KINDS)[number];
74
74
  /**
75
- * 计费面的拒因(**数组序 = 优先序**):就绪原因({@link READINESS_REASONS})∪ `lease_expired`(服务租约过期且中心不可达满 `T_max`)。
75
+ * 计费面的拒因(**数组序 = 优先序**):就绪原因({@link READINESS_REASONS})∪ `lease_expired`(服务租约过期且中心不可达满 `T_max` —— 含从起步起一直没听到
76
+ * 中心的成员:推定在围栏内,钟从本进程起点)。
76
77
  * `lease_expired` 刻意不是就绪原因:它不翻 `/readyz`(中心不可达不是本实例的病,摘它出路由只会把取消 / HITL / 读面一起摘掉)。
77
78
  */
78
79
  export declare const ADMISSION_REFUSALS: readonly ["draining", "model_roster_pending", "lease_expired"];
@@ -80,7 +81,8 @@ export type AdmissionRefusal = (typeof ADMISSION_REFUSALS)[number];
80
81
  /**
81
82
  * 计费面请求的**唯一**准入判(dispatch 的计费门、a2a `message/send`、PARKED 赎回席同调):按 {@link ADMISSION_REFUSALS} 序找第一条
82
83
  * 成立且按 {@link ADMISSION_TABLE} 拒这一种的拒因;没有 ⇒ `undefined`(放行)。入参是**判决**而不是 deps:就绪判由调用点经唯一谓词
83
- * {@link readinessOf} 现读(模块边界上那一只 —— 谓词单源钉注入它,任何读点自己读 `drainState` 都会当场红),租约读数取 `deps.servingLease`。
84
+ * {@link readinessOf} 现读(模块边界上那一只 —— 谓词单源钉注入它,任何读点自己读 `drainState` 都会当场红),租约读数经唯一读者
85
+ * `fleet/engine-signal.ts` 的 `memberLeaseOf`(只有 fleet 成员有租约;成员没有任何中心读数之前是推定的 `expired`,同一臂)。
84
86
  */
85
87
  export declare function admissionRefusalOf(verdict: ReadinessVerdict, lease: ServingLeaseView | undefined, kind: BillableKind): BillableRefusal | undefined;
86
88
  /** 计费提交门的拒绝(首条 reason 定码)。`retryAfterSec` 由发送形(`sendBillableRefusal`)镜像成头;交回
@@ -1,12 +1,27 @@
1
1
  import { openCheckpointCtx } from "./checkpoint-ctx.js";
2
2
  import { storeFaultClassOf } from "../plugins/store-fault.js";
3
3
  import { refusalBody } from "./refusal-body.js";
4
+ import { frozenProjectContextOf } from "../project-context.js";
4
5
  export async function readResumeCtx(load, opening) {
5
6
  const read = await readStoredCtx(load);
6
7
  if (read.kind !== "ok")
7
8
  return read;
8
9
  const opened = openCheckpointCtx(read.ctx, opening.opener, opening.sessionId);
9
- return opened.kind === "ok" ? { kind: "ok", ctx: opened.ctx } : { kind: "unsealable", field: opened.field, reason: opened.reason };
10
+ if (opened.kind !== "ok")
11
+ return { kind: "unsealable", field: opened.field, reason: opened.reason };
12
+ const frozen = frozenProjectContextOf(opened.ctx.projectContext);
13
+ switch (frozen.kind) {
14
+ case "absent":
15
+ return { kind: "ok", ctx: opened.ctx };
16
+ case "malformed":
17
+ return { kind: "corrupt", where: `checkpoint ctx for session ${JSON.stringify(opening.sessionId)}: projectContext` };
18
+ case "ok":
19
+ return { kind: "ok", ctx: { ...opened.ctx, projectContext: frozen.context } };
20
+ default: {
21
+ const unhandled = frozen;
22
+ throw new Error(`resume-ctx-read: unplaced frozen project context reading ${JSON.stringify(unhandled)}`);
23
+ }
24
+ }
10
25
  }
11
26
  async function readStoredCtx(load) {
12
27
  let stored;
@@ -3,7 +3,7 @@ import { createHash } from "node:crypto";
3
3
  import { uuidv7, resumeWithVerification, resumeCascade, CheckpointError, judgeCheckpointRow, defaultTaskRegistry, taskNotificationDedupKey, validatePendingSteer, GLOBAL_USAGE_KEY } from "@sema-agent/core";
4
4
  import { decideParkedAgent, deliverToHoldingRun, denyWorkflowParkForked, denyWorkflowParksUnreadable, findParkOrigin, heldWorkflowParkDecisions, planWorkflowParkedDecide } from "../parked-decide.js";
5
5
  import { parkDecisionFingerprint } from "../orchestration/workflow-park-decision.js";
6
- import { headReadOf } from "../session-incarnation.js";
6
+ import { runFactsOf, sealRouteDeps, sessionFactsReadOf } from "./owner-access.js";
7
7
  import { createSessionWorkspaceRecorder, sessionWorkspaceStoreOf } from "../session-workspace.js";
8
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";
9
9
  import { HttpError, checkpointPartsOf, decodeCheckpointScope, scopeCellOf, CHECKPOINT_PUBLIC_SCOPE } from "../security.js";
@@ -15,7 +15,7 @@ import { commitParkRow } from "../park-row-commit.js";
15
15
  import { atHostBoundary, failureCodeOf, nestedPauseOf, sealResultForExit, outwardPlane, outwardThrownMessage } from "../terminal.js";
16
16
  import { voidVerifierPauseCard } from "../verifier-pause-card.js";
17
17
  import { buildCancelledLedgerRow, CANCELLED_CODE, CARD_SETTLED_BY_CANCEL_MESSAGE, createRunAbort, mintCancelledResult, withCardSettledByCancelMessage } from "../run-cancel-context.js";
18
- import { isTerminalRunStatus, runProvenElsewhere } from "../plugins/store-contracts.js";
18
+ import { createLegCtxClaim, isTerminalRunStatus } from "../plugins/store-contracts.js";
19
19
  import { readTurnActivityMs, recordTurnActivity } from "../turn-activity.js";
20
20
  import { recordResumeResourceWindow } from "../resource-window.js";
21
21
  import { emitPendingWorkflowCompletions, taskNotificationInboxEntry, taskNotificationStreamKeyOf, NotifiedKeys } from "../orchestration/workflow-completion-inbox.js";
@@ -66,7 +66,7 @@ import { deploymentListAsks } from "../approval.js";
66
66
  import { isParkLedgerEventType } from "../trace/ledger-events.js";
67
67
  import { readResumeCtx, resumeCtxCorruptAnswer, resumeCtxUnreadableAnswer, resumeCtxUnsealableAnswer, resumeStoreFailureOf } from "./resume-ctx-read.js";
68
68
  import { objectiveWithheldIn } from "./submit-ctx.js";
69
- import { buildCheckpointVersionKeys, resumeAssemblyRefusalAnswer, resumeRefusalWithCard, sealCheckpointCtx, storedBodyReplayAnswer, storedBodyReplayLogFields, withPermissionModeRewritten } from "./checkpoint-ctx.js";
69
+ import { buildCheckpointVersionKeys, resumeAssemblyRefusalAnswer, resumeRefusalWithCard, sealCheckpointCtx, storedBodyReplayAnswer, storedBodyReplayLogFields } from "./checkpoint-ctx.js";
70
70
  import { refusalBody, resumeRefusalFacts } from "./refusal-body.js";
71
71
  import { storedBodyReplayRefusal } from "./stored-body-replay.js";
72
72
  import { restateResumeCtx } from "./resume-restatement.js";
@@ -580,7 +580,7 @@ export function createResumeLegs(ctx) {
580
580
  const liveReceiptOf = async (held) => {
581
581
  if (held === null || held.fingerprint !== fingerprint)
582
582
  return undefined;
583
- const run = await runStore.getRun(held.deliveredTaskId);
583
+ const run = await runFactsOf(runStore, held.deliveredTaskId);
584
584
  return run != null && !isTerminalRunStatus(run.status) ? acceptedResumingReceipt(held.deliveredTaskId, held.hostSessionId, NO_SETTLEMENT_EVIDENCE) : undefined;
585
585
  };
586
586
  const replay = await liveReceiptOf(await decisions.get(token));
@@ -690,6 +690,7 @@ export function createResumeLegs(ctx) {
690
690
  leg: "resume",
691
691
  onNotice: (n) => engineNotices.push(n),
692
692
  ...(ctx.unattendedLeg === true ? { unattendedLeg: true } : {}),
693
+ ...(ctx.projectContext !== undefined ? { projectContext: ctx.projectContext } : {}),
693
694
  lease,
694
695
  execution,
695
696
  });
@@ -762,10 +763,10 @@ export function createResumeLegs(ctx) {
762
763
  };
763
764
  const commitResumeCtx = () => Promise.resolve().then(async () => {
764
765
  const sealed = sealCheckpointCtx({ ...resumeCtx.ctx, writerVersion: serviceVersion() }, deps.checkpointCtxSealing, resumeCtx.sessionId);
765
- const claim = claimedRow && taskId !== undefined && deps.runStore !== undefined ? { fence: deps.runStore, taskId, legHolds: (run) => !runProvenElsewhere(run, deps.instanceId) } : undefined;
766
+ const claim = heldLeg !== undefined && taskId !== undefined && deps.runStore !== undefined ? createLegCtxClaim(deps.runStore, taskId, heldLeg) : undefined;
766
767
  const landed = await deps.checkpointStore.putCtx(resumeCtx.sessionId, sealed, claim);
767
768
  if (landed === "claim_released")
768
- resumeCtxNotPersisted("claim_released: this run no longer holds the session claim (settled or reaped before the write-back landed) — nothing written");
769
+ resumeCtxNotPersisted("claim_released: this leg no longer holds the run (settled, reaped, or claimed again by a later leg before the write-back landed) — nothing written");
769
770
  }).catch((e) => resumeCtxNotPersisted(errorText(e)));
770
771
  const legKey = createHash("sha256").update(String(token)).digest("hex");
771
772
  const streamApprovalOn = resolveStreamApprovalGate({
@@ -1148,9 +1149,9 @@ export function createResumeLegs(ctx) {
1148
1149
  cancelLedgerAppend = append;
1149
1150
  await openEngineNoticePort(append);
1150
1151
  const anchorOwner = subject ?? null;
1151
- const headOf = headReadOf(deps.sessionStorage);
1152
- const captureTurnAnchor = deps.resumeAnchorStore && headOf
1153
- ? async (eventId) => { const leaf = (await headOf(sessionId))?.leafId; if (leaf)
1152
+ const sessionFactsOf = sessionFactsReadOf(deps.sessionStorage);
1153
+ const captureTurnAnchor = deps.resumeAnchorStore && sessionFactsOf
1154
+ ? async (eventId) => { const leaf = (await sessionFactsOf(sessionId))?.leafId; if (leaf)
1154
1155
  await deps.resumeAnchorStore.put(sessionId, eventId, leaf, anchorOwner); }
1155
1156
  : undefined;
1156
1157
  const captureUserMessageAnchor = deps.resumeAnchorStore && taskId && freshRunClaimed
@@ -1416,9 +1417,12 @@ export function createResumeLegs(ctx) {
1416
1417
  const withElicitation = () => deps.elicitation && taskId
1417
1418
  ? deps.elicitation.runWithContext({ taskId, owner: subject ?? null, transport: hitlTransport, abortSignal: cancelCtrl.signal }, withApproval)
1418
1419
  : withApproval();
1420
+ const withQuestion = () => deps.question && taskId
1421
+ ? deps.question.runWithContext({ taskId, owner: subject ?? null, transport: hitlTransport, abortSignal: cancelCtrl.signal }, withElicitation)
1422
+ : withElicitation();
1419
1423
  return await (deps.sendUserFile && taskId
1420
- ? deps.sendUserFile.runWithContext({ taskId, emit: async (frame) => { const { type, ...rest } = frame; await append(type, rest); } }, withElicitation)
1421
- : withElicitation());
1424
+ ? deps.sendUserFile.runWithContext({ taskId, emit: async (frame) => { const { type, ...rest } = frame; await append(type, rest); } }, withQuestion)
1425
+ : withQuestion());
1422
1426
  })));
1423
1427
  }
1424
1428
  catch (e) {
@@ -1877,7 +1881,7 @@ export function createResumeLegs(ctx) {
1877
1881
  throw new Error("drivePlanReview: permissionModeAfter was passed for a task whose stored permission mode is not \"plan\" — the caller must refuse this before any state changes (see the plan_review route's pre-nonce gate)");
1878
1882
  }
1879
1883
  const approvedMode = planReviewDecisionExitsPlan(decision) && effMode === "plan" ? (permissionModeAfter ?? "default") : undefined;
1880
- const rebuildCtx = approvedMode === undefined ? ctx : withPermissionModeRewritten(ctx, { ...ctx.body, permissionMode: approvedMode });
1884
+ const rebuildCtx = approvedMode === undefined ? ctx : { ...ctx, body: { ...ctx.body, permissionMode: approvedMode } };
1881
1885
  const resolved = await resolveResumeSpecOrBlocked(rebuildCtx, req, auth);
1882
1886
  if (!resolved.ok)
1883
1887
  return resolved.answer;
@@ -1907,10 +1911,10 @@ export function createResumeLegs(ctx) {
1907
1911
  activeTaskIdOf: async (sessionId) => deps.runStore?.getActiveTaskId?.(sessionId),
1908
1912
  liveRuns: steerableRuns,
1909
1913
  inbox: deps.workflowCompletionInbox,
1910
- headOf: headReadOf(deps.sessionStorage),
1914
+ sessionStorage: deps.sessionStorage,
1911
1915
  });
1912
1916
  const routeCtxBase = {
1913
- deps,
1917
+ deps: sealRouteDeps(deps),
1914
1918
  registry: { idemCache, inflightRuns, preemptableRuns, steerableRuns, wakeParkMints, counters },
1915
1919
  helpers: { readJson, readRawBody, rateLimited, quotaExceeded, ownerQuotaDenied, leaseDenied, ownerLeaseDenied, usageWindowDenied, admitRun, safeDecode },
1916
1920
  local: { images: createImagesLocal(deps), sessions: createSessionsLocal(), sessionSync: createSessionSyncLocal(), capabilities: createCapabilitiesLocal() },
@@ -39,6 +39,7 @@ import type { RememberLaneCannotRecord } from "../parked-decide.js";
39
39
  import type { RowWayOut } from "./resume-legs.js";
40
40
  import type { KeyCaller } from "../owner-faces.js";
41
41
  import type { Subject } from "../owner-brands.js";
42
+ import type { RouteBackend, RouteRunStore, RouteSessionStore } from "./owner-access.js";
42
43
  /** 跨域可变状态 = lens1 §C2 实测的那 8 条「运行期登记簿」,一个对象。
43
44
  * [ref]:`cancelledViaVerb`(按 taskId 键控的「这次是取消动词」旁表)删除 —— 中止成因改挂在每条腿自己的
44
45
  * {@link RunAbort} 句柄上(`inflightRuns` 的值),同寿同删,不再有一张与腿不同寿的旁表。 */
@@ -199,14 +200,6 @@ export interface CheckpointCtx {
199
200
  * 传原 ctx / 其展开 ⇒ 标随行。缺席 = 不是无人值守腿(HTTP 提交腿;7.106.0 前落盘的行 —— 那时这些腿不铸模式,checkpoint 里也没有)。
200
201
  */
201
202
  unattendedLeg?: true;
202
- /**
203
- * 体里**模式词的读法记号**(闭集只有当前一词 `"entry_word"`):在场 = 这一行体的顶层 `permissionMode` 是这条 run 模式词唯一的源(提交入口盖过运维
204
- * `AUTONOMY=plan` 的地板;`plan_review` 写回改它),续跑照它读。**服务端写、不来自请求体**。缺席(7.121.x 及以前写下的行 —— 那时运维的 plan 由治理层每段重钳)
205
- * 而本部署此刻有入口地板、体的词被地板钳会变 ⇒ 续跑腿响亮拒、行字节不动(规则与删除条件的属主 = `http/checkpoint-ctx.ts` 顶注「模式词问」)。
206
- * 写点两个(写模式词的人盖记号,同模块):`buildCheckpointCtx`(每一次新的持久化写都写)与 `withPermissionModeRewritten`(`plan_review` 改词的写回);
207
- * 不改词的续跑腿写回传原 ctx ⇒ 记号随行。
208
- */
209
- permissionModeReading?: import("./checkpoint-ctx.js").PermissionModeReading;
210
203
  /**
211
204
  * 🔴 [ref] —— **写这份上下文的服务版本**(`package.json` 的 `version`),**服务端写、不来自请求体**(与 {@link unattendedLeg} 同为 `body` 的兄弟键)。
212
205
  * 写点两个:`buildCheckpointCtx`(每一次新的持久化写)与续跑腿的写回({@link DriveResumeArgs.resumeCtx} 的唯一写点,CAS 赢点盖本进程的版本 —— 接下来写下一张停车行的就是它)。
@@ -226,6 +219,14 @@ export interface CheckpointCtx {
226
219
  * 缺席 = 体里的目标就是交付的那一份(腿上没有屏)。
227
220
  */
228
221
  promptScreened?: true;
222
+ /**
223
+ * 这条 run 继承到的**项目上下文**(`project-context.ts` 的 {@link import("../project-context.js").ProjectContext}:项目 id · 继承的 `cwd` · 项目简报)——
224
+ * 新提交腿在装配器头部判一次,**服务端写、不来自请求体**(与 {@link memoryScope} 同为 `body` 的兄弟键,先例同形:提交腿推导、落盘,续跑原样读回)。
225
+ * 写点:`buildCheckpointCtx`(提交腿那一笔,经 `persistSubmissionCtx` 读这条腿的部署事实);leader worker 的写者不带(它的 spec 不经新提交腿的判)。续跑腿的写回传原 ctx / 其展开 ⇒ 随行。
226
+ * 读者只有一只:`resume-legs.ts` `resolveResumeSpec` 把它原样递给装配器(`opts.projectContext`)—— 续跑腿不再读登记簿、不再推观者(停驻期间登记被改 / 被删 / 看不见了,续跑照旧用这一份)。
227
+ * 缺席 = 没继承(含 7.150.0 之前写下的行:不回填,续跑不继承 = 那时的行为)。
228
+ */
229
+ projectContext?: import("../project-context.js").ProjectContext;
229
230
  }
230
231
  /**
231
232
  * `/decide` 第三条车道交给宿主投递腿(wake:`DriveResumeArgs` / fresh:`DurableRunLegArgs`)的**一件**:这一跑在内存里带着的决定,
@@ -442,13 +443,13 @@ export interface RouteLegs {
442
443
  /** POST /v1/tasks · /v1/tasks/stream · /v1/runs 的共同前段:读体 → 校验 → resolveSpec。null = 已应答(400/…)。
443
444
  *
444
445
  * `body` = **已解析好的**请求体,给那些体不是「这条 HTTP 请求的 JSON 正文」的调用方
445
- * (DESIGN-269 车2:A2A JSON-RPC 腿已经把正文读成了一条 `message/send` 请求,再 `readJson(req)`
446
+ * ([ref] 车2:A2A JSON-RPC 腿已经把正文读成了一条 `message/send` 请求,再 `readJson(req)`
446
447
  * 一次只会读到空流)。缺席 ⇒ 照旧自己读体,既有三个调用方一字不改。
447
448
  * ⚠️ 这是**入口形**的分歧,不是校验面的分歧:传进来的体与自己读的体走**同一段**校验/授权/
448
449
  * resolveSpec —— 分叉出第二条校验路径正是本参数存在的理由的反面。
449
450
  *
450
451
  * `onTypedFailure` = 「typed 拒绝**别写响应**,交回给我」。默认(缺席)行为不变:`HttpError` 走
451
- * `sendError` 连 `extra` 一起回显。DESIGN-269 车2 的 A2A 腿必须传它 —— 那些 `extra`(尤其
452
+ * `sendError` 连 `extra` 一起回显。[ref] 车2 的 A2A 腿必须传它 —— 那些 `extra`(尤其
452
453
  * `scenario_unknown` 的**全部场景名**)对壳是指路材料,对一个外部 peer 是内部词表外泄。 */
453
454
  prepareSpec(req: IncomingMessage, res: ServerResponse, body?: TaskRequestBody, onTypedFailure?: (err: import("../security.js").HttpError) => void,
454
455
  /** [ref] 件C:**腿身份**(不是能力表态)。`liveStream: true` 只由 `POST /v1/tasks/stream` 的装配点按
@@ -600,8 +601,19 @@ export declare function sendResumeOutcome(res: ServerResponse, out: {
600
601
  }, logger: {
601
602
  info?: (msg: string, fields?: Record<string, unknown>) => void;
602
603
  } | undefined, extraBody?: Record<string, unknown>): void;
604
+ /**
605
+ * 🔴 **路由层的服务 deps(类型墙,[ref] 第二阶段)**:run 店 / 会话店上带属主的读口(按 id / 按键 / 按会话 / 列表 —— `owner-access.ts` 的 {@link RouteRunStore} /
606
+ * {@link RouteSessionStore})、会话视图读 `sessionAudit` 与后端铸这两只店的工厂(`backend.run()` / `backend.session()`,{@link RouteBackend})在型上调不了:路由在不在照判(能力事实只问方法在不在,见证随视图封着),带属主的行只经属主存取器读。
607
+ * 值上就是服务的那一只 deps,只在 `owner-access.ts` 的 `sealRouteDeps` 铸一次;视图是完整 deps 的子型(交给持认领腿的写口照常)。
608
+ */
609
+ export type RouteDeps = Omit<FlatServiceDeps, "runStore" | "sessionStorage" | "sessionAudit" | "backend"> & {
610
+ runStore?: RouteRunStore;
611
+ sessionStorage?: RouteSessionStore;
612
+ sessionAudit?: never;
613
+ backend?: RouteBackend;
614
+ };
603
615
  export interface RouteCtxBase {
604
- deps: FlatServiceDeps;
616
+ deps: RouteDeps;
605
617
  registry: RunRegistry;
606
618
  helpers: RouteHelpers;
607
619
  local: RouteLocals;
@@ -4,12 +4,13 @@ import { memoRedactedRead, redactErrorMessage } from "../../observability/run-te
4
4
  import { isParkedRunStatus, isTerminalRunStatus } from "../../plugins/store-contracts.js";
5
5
  import { HttpError } from "../../security.js";
6
6
  import { idemCallerOf, idemIdentityOf, idemKeyOf, inflightKeyOf } from "../idempotency.js";
7
- import { isRunExistsConflictBody, replayOfExistingRun } from "../submit-replay.js";
7
+ import { answerReplay, isRunExistsConflictBody } from "../submit-replay.js";
8
8
  import { isActiveRunConflictBody } from "../active-run-conflict.js";
9
9
  import { sendJson, sendError, sendHttpError } from "../send.js";
10
10
  import { admissionRefusalOf, readinessOf, sendBillableRefusal } from "../readiness.js";
11
+ import { memberLeaseOf } from "../../fleet/engine-signal.js";
11
12
  import { gatedPerson, gatedSubject, sendPrincipalRefusal, verifiedIdentitySource } from "../principal-gate.js";
12
- import { callerOf, protocolEntry, readVisibleRun, runRowAt } from "../owner-access.js";
13
+ import { callerOf, protocolEntry, readReplayHit, readVisibleRun, runRowAt } from "../owner-access.js";
13
14
  import { assertNever } from "../route-ctx.js";
14
15
  import * as facts from "../../capability-facts.js";
15
16
  import { createDurableRun } from "./runs.js";
@@ -181,7 +182,7 @@ function rpcErrorFromTypedFailure(err) {
181
182
  }
182
183
  function submitUnavailable(res, ctx) {
183
184
  const { deps } = ctx;
184
- const refusal = admissionRefusalOf(readinessOf(deps), deps.servingLease?.(), "submit");
185
+ const refusal = admissionRefusalOf(readinessOf(deps), memberLeaseOf(deps), "submit");
185
186
  if (refusal === undefined)
186
187
  return false;
187
188
  sendBillableRefusal(res, refusal);
@@ -197,15 +198,14 @@ function readBlocking(params) {
197
198
  throw new RpcError(JSONRPC_INVALID_PARAMS, "params.configuration.blocking must be a boolean");
198
199
  return c.blocking;
199
200
  }
200
- async function waitForRun(ctx, store, res, taskId, budgetMs) {
201
+ async function waitForRun(ctx, entry, store, res, taskId, budgetMs) {
201
202
  const { deps } = ctx;
202
203
  const deadline = Date.now() + budgetMs;
203
204
  let last = null;
204
205
  for (;;) {
205
206
  let run;
206
207
  try {
207
- const raw = await store.getRun(taskId);
208
- run = raw ? await runRowAt("run.a2a", store, raw) : null;
208
+ run = (await readVisibleRun(entry, store, taskId))?.row ?? null;
209
209
  }
210
210
  catch (e) {
211
211
  deps.logger?.warn?.("a2a_serve_wait_read_failed", { error: e?.message });
@@ -254,10 +254,11 @@ async function handleMessageSend({ req, res, ctx, params, serve, runStore }) {
254
254
  ...(contextId !== undefined ? { sessionId: contextId } : {}),
255
255
  ...(scenario !== undefined ? { scenario } : {}),
256
256
  };
257
- const keyed = (await runStore.getRunByIdem(idem.key).catch(() => undefined)) ?? (await runStore.getRun(body.taskId).catch(() => undefined));
258
- const replay = keyed ? await replayOfExistingRun(keyed, { subject: submitter, clientTaskId: body.taskId, idem }, "durable", deps, runStore) : undefined;
259
- if (keyed && replay?.status === 202) {
260
- return taskFromRun(await runRowAt("run.a2a", runStore, keyed));
257
+ const keyed = (await readReplayHit(submitter, deps.config, runStore, { idemKey: idem.key }, body.taskId).catch(() => undefined)) ??
258
+ (await readReplayHit(submitter, deps.config, runStore, { taskId: body.taskId }, body.taskId).catch(() => undefined));
259
+ const replay = keyed ? await answerReplay(keyed, { subject: submitter, clientTaskId: body.taskId, idem }, "durable", runStore) : undefined;
260
+ if (keyed?.kind === "visible" && replay?.status === 202) {
261
+ return taskFromRun(await runRowAt("run.a2a", runStore, keyed.row));
261
262
  }
262
263
  const inflight = replay === undefined ? ctx.registry.idemCache.peek(inflightKeyOf(idem, "durable", body.taskId)) : undefined;
263
264
  let receipt;
@@ -298,7 +299,7 @@ async function handleMessageSend({ req, res, ctx, params, serve, runStore }) {
298
299
  }
299
300
  const taskId = receiptBody.taskId;
300
301
  const sessionId = receiptBody.sessionId;
301
- const run = await waitForRun(ctx, runStore, res, taskId, blocking ? serve.blockingWaitMs : 0);
302
+ const run = await waitForRun(ctx, protocolEntry(req, deps, "run.a2a", callerOf(req, deps)), runStore, res, taskId, blocking ? serve.blockingWaitMs : 0);
302
303
  if (run)
303
304
  return taskFromRun(run);
304
305
  return {
@@ -330,10 +331,10 @@ function durableIdempotencyTaskId(scope, messageId) {
330
331
  async function handleTasksGet({ req, ctx, params, runStore }) {
331
332
  const { deps } = ctx;
332
333
  const id = asNonEmptyString(params.id, "params.id", MAX_TASK_ID_CHARS);
333
- const run = await readVisibleRun(protocolEntry(req, deps, "run.a2a", callerOf(req, deps)), runStore, id);
334
- if (!run)
334
+ const admitted = await readVisibleRun(protocolEntry(req, deps, "run.a2a", callerOf(req, deps)), runStore, id);
335
+ if (!admitted)
335
336
  throw new RpcError(A2A_TASK_NOT_FOUND, A2A_TASK_NOT_FOUND_MESSAGE);
336
- return taskFromRun(run);
337
+ return taskFromRun(admitted.row);
337
338
  }
338
339
  async function handleExtendedCard({ req, ctx, serve }) {
339
340
  return buildAgentCard(serve, cardReaderOf(req, ctx.deps.config), A2A_METHODS);
@@ -2,12 +2,13 @@ import type { IncomingMessage, ServerResponse } from "node:http";
2
2
  import type { QuestionAnswer } from "@sema-agent/core";
3
3
  import type { CheckpointStoreFull } from "../../plugins/store-backend.js";
4
4
  import { type ToolApprovalCoordinator } from "../../tool-approval.js";
5
+ import { type RouteSessionStore } from "../owner-access.js";
5
6
  import { type GovernancePosture } from "../../runtime-governance.js";
6
7
  import { type GateIntentView } from "../../approval.js";
7
8
  import { type RouteCtx, type RouteDecl, type RouteMatch, type RouteIdsOf } from "../route-ctx.js";
8
9
  import { type KeyClosureIssue } from "../../request-key-closure.js";
9
10
  import { type PeerInboundPendingRow } from "../../peer-inbound-face.js";
10
- import { type PlacementReadingSessionStore, type SessionAgent } from "../../session-agent.js";
11
+ import type { SessionAgent } from "../../session-agent.js";
11
12
  /** 族的成员 = 路由表行 id(改名 / 删行 ⇒ 下表的键当场编译红)。 */
12
13
  export type DecisionDoorId = Extract<ApprovalsAssistantRouteId, "approval-decide" | "assistant-task-plan-review">;
13
14
  /** 一道门的静态描述 —— 只写**这道门自己**的东西;路径、路径段、别门的键一概不在这里写(从来源读)。 */
@@ -164,7 +165,7 @@ peerInbound: (() => readonly PeerInboundPendingRow[]) | undefined,
164
165
  * 🔴 [ref]:停驻行 `agent` 的会话店面 —— 与 `GET /v1/approvals` 同一只(`deps.sessionStorage`),经同一只整批投影 {@link projectDurableQueue}。
165
166
  * `undefined` / 店不交 `placementOf` = 这台部署不存委派元组 ⇒ 没有一帧带 `agent`(帧字节与 [ref] 之前逐字相同)。位置在 `scope` 之前、与 `live` 并列:每个生产调用点都必须表态。
166
167
  */
167
- sessions: PlacementReadingSessionStore | undefined, scope: string | undefined,
168
+ sessions: Pick<RouteSessionStore, "placementOf"> | undefined, scope: string | undefined,
168
169
  /** [ref]②:出身归因的部署侧一半 + 三张审批名单(问的属主)。缺席 ⇒ 归不出治理出身(键缺席,与 best-effort 同方向)、没有一行读作
169
170
  * 部署名单的问(记不住,方向是多问)。**位置在 `pollMs` 之前**是刻意的:`pollMs` 只有测试传,而本参数是每个生产调用点都必须给的那一个。 */
170
171
  governance?: PendingProjectionDeployment, pollMs?: number): Promise<void>;