@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
@@ -8,7 +8,7 @@ import { approvalPayloadFingerprint } from "../../plugins/approval-payload-finge
8
8
  import { carriesDeploymentListQuestion, standingAnswerDurablyRecordable } from "../../tool-approval.js";
9
9
  import { APPROVAL_GATE_KINDS } from "../../approval-content-kind.js";
10
10
  import { isParkedRunStatus } from "../../plugins/store-contracts.js";
11
- import { principalFrom, ownerOfCheckpointScope, scopeCellOf, CHECKPOINT_PUBLIC_SCOPE, PRINCIPAL_TOKEN_HEADER, APPROVAL_MAC_HEADER, APPROVAL_MAC_KID_HEADER } from "../../security.js";
11
+ import { principalFrom, scopeCellOf, CHECKPOINT_PUBLIC_SCOPE, PRINCIPAL_TOKEN_HEADER, APPROVAL_MAC_HEADER, APPROVAL_MAC_KID_HEADER } from "../../security.js";
12
12
  import { keyAdmits } from "../../owner-faces.js";
13
13
  import { personOf } from "../../owner-brands.js";
14
14
  import { editedPlanBinding, verifyDirectDoorProof } from "../../principal-jwt.js";
@@ -23,7 +23,7 @@ import { sleep } from "../sse-log.js";
23
23
  import { bindSseLifecycle } from "../sse-lifecycle.js";
24
24
  import { sendJson, sendError, sseHeaders, logRequestError, SSE_MAX_STREAM_MS, SSE_HEARTBEAT_IDLE_MS } from "../send.js";
25
25
  import { gatedSubject, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
26
- import { admitRunById, admitSessionById, callerEntry, callerOf, callerOfDecision, deciderSubjectOf, enter, judgeRun, protocolEntry, readVisibleRun, refuse, withCaller } from "../owner-access.js";
26
+ import { admitRunById, admitSessionById, callerEntry, callerOf, callerOfDecision, deciderSubjectOf, enter, latestRunIdOf, listRunsOfScope, protocolEntry, readSessionAgent, readVisibleRun, refuse, runFactsOf, withCaller } from "../owner-access.js";
27
27
  import { governanceOriginOf } from "../../runtime-governance.js";
28
28
  import { deploymentListAsks } from "../../approval.js";
29
29
  import { grantBindingOf } from "../../standing-answer-grant.js";
@@ -40,7 +40,6 @@ import { seatText } from "../../trace/exit-seat.js";
40
40
  import { approvalQueueKindOfDoorId } from "../../approval-queue-kind.js";
41
41
  import { defaultPeerInboundFace, parsePeerInboundId, peerBoxScope } from "../../peer-inbound-face.js";
42
42
  import { RESUME_RESTATABLE_KEYS, readResumeRestatement } from "../resume-restatement.js";
43
- import { readSessionAgent } from "../../session-agent.js";
44
43
  const DECIDE_BODY_DECLARED_KEYS = [
45
44
  "answer", "boundCallId", "boundInputHash", "checkpointToken", "decidedBy", "decidedVia", "decision", "feedback", "reason", "remember", "updatedInput",
46
45
  ...RESUME_RESTATABLE_KEYS,
@@ -280,8 +279,8 @@ async function decideIdempotentReplay(cs, args) {
280
279
  };
281
280
  }
282
281
  async function gateObjectivePreview(reader, runStore, agentStore, gate, runSessionId) {
283
- const previewOf = (taskId) => readVisibleRun(reader, runStore, taskId);
284
- const own = typeof runSessionId === "string" ? ((await runStore.getActiveTaskId(runSessionId)) ?? (await runStore.latestRunForSession(runSessionId))?.taskId) : undefined;
282
+ const previewOf = async (taskId) => (await readVisibleRun(reader, runStore, taskId))?.row;
283
+ const own = typeof runSessionId === "string" ? ((await runStore.getActiveTaskId(runSessionId)) ?? (await latestRunIdOf(runStore, runSessionId))) : undefined;
285
284
  if (own !== undefined)
286
285
  return (await previewOf(own))?.objectivePreview ?? null;
287
286
  if (agentStore === undefined)
@@ -462,7 +461,7 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
462
461
  if (typeof host === "string")
463
462
  byGate.set(host, s);
464
463
  });
465
- const runs = await runStore.listRuns({ owner: ownerOfCheckpointScope(scope), limit: 100 });
464
+ const runs = await listRunsOfScope(runStore, scope, 100);
466
465
  const tasks = runs
467
466
  .filter((r) => r.status === "running" || isParkedRunStatus(r.status))
468
467
  .map((r) => {
@@ -511,7 +510,7 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
511
510
  let flagged = await runStore.requestPreempt(taskId, run.owner);
512
511
  const PREEMPT_RECLASSIFY_ATTEMPTS = 3;
513
512
  for (let attempt = 0; !flagged; attempt++) {
514
- const now = await runStore.getRun(taskId);
513
+ const now = (await readVisibleRun(preemptEntry, runStore, taskId))?.row;
515
514
  if (now !== undefined && isParkedRunStatus(now.status)) {
516
515
  sendError(res, 409, "conflict.already_suspended", "task is already suspended", { taskId, status: now.status });
517
516
  return;
@@ -661,7 +660,7 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
661
660
  }
662
661
  const sighting = typeof body.checkpointId === "string" ? { face: "checkpointId", value: body.checkpointId } : undefined;
663
662
  const decisionEntry = callerEntry(req, deps, match.decl.id);
664
- const run = await runStore.getRun(taskId);
663
+ const run = await runFactsOf(runStore, taskId);
665
664
  if (!run) {
666
665
  if (!deps.config.directDoorActive)
667
666
  deps.logger?.info?.("decide_404", { reason: "no-row", taskId, principalPresent: principal !== undefined });
@@ -694,7 +693,7 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
694
693
  singleUse = proof.singleUse === undefined ? undefined : { proof: proof.singleUse, store };
695
694
  }
696
695
  const deciderEntry = withCaller(decisionEntry, callerOfDecision(decisionEntry.caller, deciderPrincipal, deps.config));
697
- const decisionAdmitted = judgeRun(deciderEntry, run);
696
+ const decisionAdmitted = await readVisibleRun(deciderEntry, runStore, taskId);
698
697
  if (decisionAdmitted === undefined) {
699
698
  deps.logger?.info?.("decide_404", { reason: "owner-mismatch", taskId, principalPresent: principal !== undefined });
700
699
  refuse(deciderEntry, res, "not_visible");
@@ -37,7 +37,7 @@ import { judgeAutoModeArming } from "../../auto-mode-face.js";
37
37
  import { coercePermissionModeStrict, PERMISSION_MODE_WORDS, taskHooksHonored } from "../../task-settings.js";
38
38
  import { gatedPerson, sendPrincipalRefusal } from "../principal-gate.js";
39
39
  import { readAdoptionImportCapability } from "../../adoption/import.js";
40
- import { headReadOf } from "../../session-incarnation.js";
40
+ import { headReadPresent } from "../../session-incarnation.js";
41
41
  import { callerTrust } from "../caller-trust.js";
42
42
  import { sessionRulesNamespaceFormsHonored } from "../../session-policy-wire.js";
43
43
  function routePairingOf(deps, name, m) {
@@ -207,6 +207,7 @@ export async function handleCapabilities(req, res, match, ctx) {
207
207
  sideQuery: true,
208
208
  agentRoster: facts.agentRoster(deps).available,
209
209
  projects: facts.projectRegistry(deps).available,
210
+ projectInheritance: facts.projectRegistry(deps).available,
210
211
  retention: retentionAdvertisement(deps.config),
211
212
  peerLane: Boolean(deps.peerDirectory),
212
213
  scenarioSubject: true,
@@ -214,12 +215,12 @@ export async function handleCapabilities(req, res, match, ctx) {
214
215
  sessionSearch: facts.sessionList(deps).available,
215
216
  sessionFork: facts.sessionFork(deps).available,
216
217
  sessionDelete: facts.sessionDelete(deps).available,
217
- resumeAt: Boolean(deps.resumeAnchorStore && headReadOf(deps.sessionStorage)),
218
+ resumeAt: Boolean(deps.resumeAnchorStore && headReadPresent(deps.sessionStorage)),
218
219
  promptSuggestions: true,
219
220
  sessionPolicy: facts.sessionPolicy(deps).available,
220
221
  sessionRulesNamespaceForms: sessionRulesNamespaceFormsHonored(),
221
222
  restoreFiles: Boolean(deps.fileHistoryStore),
222
- rewindFilesTo: Boolean(deps.fileHistoryStore && deps.resumeAnchorStore && headReadOf(deps.sessionStorage)),
223
+ rewindFilesTo: Boolean(deps.fileHistoryStore && deps.resumeAnchorStore && headReadPresent(deps.sessionStorage)),
223
224
  sessionRewindFiles: facts.sessionRewindFiles(deps).available,
224
225
  sessionRewindFilesPreview: facts.sessionRewindFilesPreview(deps).available,
225
226
  fileHistoryCapture: fileHistoryCaptureOf(deps.fileHistoryStore, deps.config.fileHistoryCapture),
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DESIGN-278 §5 S2([ref])—— `GET /v1/config/catalog`:配置目录自描述端点(operator lane)。
2
+ * [ref] §5 S2([ref])—— `GET /v1/config/catalog`:配置目录自描述端点(operator lane)。
3
3
  *
4
4
  * TOB 运维在 web 上要一个**不撒谎**的配置目录:哪个键、什么腿(env/center)、改了何时生效
5
5
  * (hot/restart-片名/deploy/inert)、本机实际值 —— 今天这些事实散在 config.ts 数千行与三份文档里,
@@ -2,7 +2,7 @@ import { z } from "zod";
2
2
  import { isParkedRunStatus, isTerminalRunStatus, runProvenElsewhere } from "../../plugins/store-contracts.js";
3
3
  import { DeadlineExceeded, MCP_DIAL_DEADLINE_MS, raceDeadline } from "../mcp-dial-deadline.js";
4
4
  import { sendJson, sendError } from "../send.js";
5
- import { admitSessionById, enter } from "../owner-access.js";
5
+ import { admitSessionById, enter, runFactsOf } from "../owner-access.js";
6
6
  import { assertNever } from "../route-ctx.js";
7
7
  import * as facts from "../../capability-facts.js";
8
8
  import { seatText } from "../../trace/exit-seat.js";
@@ -118,7 +118,7 @@ export async function handleMcpReconnect(req, res, match, ctx) {
118
118
  }
119
119
  }
120
120
  }
121
- const run = activeTaskId === undefined ? undefined : await runStore.getRun(activeTaskId);
121
+ const run = activeTaskId === undefined ? undefined : await runFactsOf(runStore, activeTaskId);
122
122
  if (run && isParkedRunStatus(run.status)) {
123
123
  sendError(res, 409, "steering.not_running", "run is waiting on a pending decision — the MCP re-dial verb needs a live turn; a resumed continuation dials every declared server afresh (the re-dial applies to the current turn only and is not persisted), so resume this run first — a re-dial cannot be queued behind the decision");
124
124
  return;
@@ -116,7 +116,7 @@ export declare function buildClearanceRowWire(r: OriginClearanceRow): {
116
116
  baseRev: string;
117
117
  origin: {
118
118
  at: number;
119
- cause?: "static" | "unattributed" | "observed" | "derived" | undefined;
119
+ cause?: "static" | "observed" | "derived" | "unattributed" | undefined;
120
120
  taint: "external";
121
121
  };
122
122
  };
@@ -1,6 +1,6 @@
1
1
  import { sendJson, sendError } from "../send.js";
2
2
  import { gatedSubject, sendPrincipalRefusal } from "../principal-gate.js";
3
- import { admitSessionRow, enter, refuse } from "../owner-access.js";
3
+ import { admitSessionById, enter, refuse } from "../owner-access.js";
4
4
  import { sendResumeOutcome } from "../route-ctx.js";
5
5
  import { assertNever } from "../route-ctx.js";
6
6
  import * as facts from "../../capability-facts.js";
@@ -54,8 +54,7 @@ export async function handleNotifyWake(req, res, match, ctx) {
54
54
  sendError(res, 501, "capability.session_ownership_required", "external notify requires the session-ownership face (sessionStorage.headOf)");
55
55
  return;
56
56
  }
57
- const notifyHead = await owned.sessionStorage.headOf(notifySession);
58
- const notifyAdmitted = await admitSessionRow(notifyEntry, res, owned.sessionStorage, notifySession, notifyHead);
57
+ const notifyAdmitted = await admitSessionById(notifyEntry, res, owned.sessionStorage, notifySession);
59
58
  if (notifyAdmitted === undefined)
60
59
  return;
61
60
  const payload = {
@@ -48,6 +48,7 @@ export declare const PROJECT_ROW_DISPOSITIONS: {
48
48
  readonly registeredBy: "forward";
49
49
  readonly registeredAt: "forward";
50
50
  readonly archivedAt: "forward";
51
+ readonly contentAttestedAt: "forward";
51
52
  };
52
53
  /** 项目行的键全集(处置表的键 = wire 上可能出现的每一个键;自铸登记表 `trace/self-minted-wire.ts` 的 `projectRowKey` 按它 `satisfies`)。 */
53
54
  export type ProjectRowKey = keyof typeof PROJECT_ROW_DISPOSITIONS;
@@ -4,6 +4,7 @@ import { sendUnauthorized } from "../principal-gate.js";
4
4
  import { routeListFilter } from "../owner-access.js";
5
5
  import { assertNever } from "../route-ctx.js";
6
6
  import * as facts from "../../capability-facts.js";
7
+ import { projectVisible } from "../../project-context.js";
7
8
  import { projectByTable } from "../../trace/projection-table.js";
8
9
  export const PROJECT_NOT_FOUND_MESSAGE = "no such project";
9
10
  export const PROJECT_ROW_DISPOSITIONS = {
@@ -19,6 +20,7 @@ export const PROJECT_ROW_DISPOSITIONS = {
19
20
  registeredBy: "forward",
20
21
  registeredAt: "forward",
21
22
  archivedAt: "forward",
23
+ contentAttestedAt: "forward",
22
24
  };
23
25
  export function projectRowOf(id, registration) {
24
26
  return projectByTable("project", PROJECT_ROW_DISPOSITIONS, registration, { id });
@@ -37,8 +39,8 @@ function readArchivedQuery(raw) {
37
39
  export async function handleProjects(req, res, match, ctx) {
38
40
  const { deps } = ctx;
39
41
  const { safeDecode } = ctx.helpers;
40
- const { fleetWide, gateOwner } = routeListFilter(req, deps, match.decl.id);
41
- if (!fleetWide && gateOwner === null) {
42
+ const viewer = routeListFilter(req, deps, match.decl.id);
43
+ if (!viewer.fleetWide && viewer.gateOwner === null) {
42
44
  sendUnauthorized(res, "identity");
43
45
  return;
44
46
  }
@@ -47,7 +49,7 @@ export async function handleProjects(req, res, match, ctx) {
47
49
  sendError(res, 501, "capability.unavailable", "the project registry is not available on this deployment: projects are published only by the configuration center, and no center configuration is applied here");
48
50
  return;
49
51
  }
50
- const visible = (registration) => fleetWide || registration.registeredBy === gateOwner;
52
+ const visible = (registration) => projectVisible(registration, viewer);
51
53
  switch (match.decl.id) {
52
54
  case "projects-list": {
53
55
  const archived = readArchivedQuery(new URLSearchParams(match.search).get("archived"));
@@ -1,5 +1,5 @@
1
1
  /**
2
- * [ref] 车2 —— **托管留存的 operator 治理面**(设计稿 `docs/DESIGN-270-retention-lane.md` §5/§6)。
2
+ * [ref] 车2 —— **托管留存的 operator 治理面**(设计稿 `docs/[ref]-retention-lane.md` §5/§6)。
3
3
  *
4
4
  * · `PUT /v1/ops/retention/holds/:domain` —— 放置 legal hold;
5
5
  * · `DELETE /v1/ops/retention/holds/:domain` —— 解除;
@@ -43,7 +43,7 @@ export type PermissionModePersistMiss = (typeof PERMISSION_MODE_PERSIST_MISSES)[
43
43
  * · run 行仍在本副本(`runProvenElsewhere` 的反面 —— 别的副本续跑了它,续跑的 CAS 赢点把行上的副本改成它自己);
44
44
  * · 本副本这条 run 此刻登记的活流仍是接下切档的那一只(本副本上续跑的新腿登记的是另一只流对象;腿收场时按对象身份摘掉自己的)。
45
45
  */
46
- export declare function switchLegHolds<S extends object>(fenced: FencedRun, thisInstanceId: string | undefined, liveNow: S | undefined, switched: S): boolean;
46
+ export declare function switchLegHolds<S extends object>(fenced: Pick<FencedRun, "instanceId">, thisInstanceId: string | undefined, liveNow: S | undefined, switched: S): boolean;
47
47
  /** 本判官改了词时回执旁的那一格(`admission`)。 */
48
48
  interface PermissionModeAdmissionNote {
49
49
  readonly requested: PermissionMode;
@@ -7,7 +7,7 @@ import * as facts from "../../capability-facts.js";
7
7
  import { PERMISSION_MODE_WORDS, coercePermissionModeStrict, parseTaskSettings } from "../../task-settings.js";
8
8
  import { admitPermissionModeWord } from "../../permission-mode-admission.js";
9
9
  import { readResumeCtx, resumeCtxCorruptAnswer, resumeCtxUnreadableAnswer, resumeCtxUnsealableAnswer } from "../resume-ctx-read.js";
10
- import { sealCheckpointCtx, withPermissionModeRewritten } from "../checkpoint-ctx.js";
10
+ import { sealCheckpointCtx } from "../checkpoint-ctx.js";
11
11
  import { serviceVersion } from "../../service-version.js";
12
12
  import { recordFailOpen } from "../../observability/fail-open.js";
13
13
  export const PERMISSION_MODE_PERSIST_MISSES = ["claim_released", "context_absent", "store_failed"];
@@ -114,7 +114,7 @@ export async function handlePermissionMode(req, res, match, ctx) {
114
114
  }
115
115
  let persistMiss;
116
116
  try {
117
- const next = sealCheckpointCtx({ ...withPermissionModeRewritten(stored, { ...stored.body, permissionMode: admitted }), writerVersion: serviceVersion() }, deps.checkpointCtxSealing, sessionId);
117
+ const next = sealCheckpointCtx({ ...stored, body: { ...stored.body, permissionMode: admitted }, writerVersion: serviceVersion() }, deps.checkpointCtxSealing, sessionId);
118
118
  const rewrite = await checkpointStore.rewriteCtxWhileClaimed(sessionId, next, { fence: runStore, taskId, legHolds: (fenced) => switchLegHolds(fenced, deps.instanceId, ctx.registry.steerableRuns.get(taskId), live) });
119
119
  persistMiss = rewrite === "written" ? undefined : rewrite;
120
120
  }
@@ -13,7 +13,7 @@ export declare function handleRuns(req: IncomingMessage, res: ServerResponse, ma
13
13
  /** 同上纪律(子代理 / 任务句柄 / HITL 应答段)。 */
14
14
  export declare function handleRunVerbs(req: IncomingMessage, res: ServerResponse, match: RouteMatch<RunVerbsRouteId>, ctx: RouteCtx): Promise<void>;
15
15
  /**
16
- * DESIGN-269 车2 件2 —— `POST /v1/runs` 的**建行 + 驱动**段,从路由体里原样搬出来的具名腿。
16
+ * [ref] 车2 件2 —— `POST /v1/runs` 的**建行 + 驱动**段,从路由体里原样搬出来的具名腿。
17
17
  *
18
18
  * 🔴 为什么提取:A2A 的 `message/send`(routes/a2a-serve.ts)必须走**同一条**提交链 —— 第二条提交
19
19
  * 路径就是第二个写者,而这条链上挂着 durable taskId 幂等重放、lease/治理窗 pre-admission、
@@ -30,16 +30,15 @@ import { ActorAssertionWire } from "@sema-agent/settings-schema";
30
30
  import * as facts from "../../capability-facts.js";
31
31
  import { assertNever } from "../route-ctx.js";
32
32
  import { readRunFace } from "../run-read-face.js";
33
- import { replayOfExistingRun } from "../submit-replay.js";
33
+ import { replayOfExistingRun, replayOfKey } from "../submit-replay.js";
34
34
  import { mintAndDriveDurableRun } from "../durable-run-leg.js";
35
35
  import { SUBMISSION_CLAIM_FACE } from "../execution-context.js";
36
- import { admitRunById, admitRunRow, enter, runRowAt } from "../owner-access.js";
36
+ import { admitRunById, admitRunByIdem, enter, readVisibleRun, sessionFactsReadOf } from "../owner-access.js";
37
37
  import { keyAdmission, ownerCellOf, recoveredSubject } from "../../owner-faces.js";
38
38
  import { personOf } from "../../owner-brands.js";
39
39
  import { withSubject } from "../../observability/principal-context.js";
40
40
  import { readResumeCtx, resumeCtxUnreadableAnswer } from "../resume-ctx-read.js";
41
41
  import { storedBodyReplayAnswer, storedBodyReplayLogFields } from "../checkpoint-ctx.js";
42
- import { headReadOf } from "../../session-incarnation.js";
43
42
  import { serviceVersion } from "../../service-version.js";
44
43
  import { storedBodyReplayRefusal } from "../stored-body-replay.js";
45
44
  import { haltBodyIssue, steerBodyKeyIssue } from "../../request-key-closure.js";
@@ -106,7 +105,7 @@ function sendHandleMiss(res, run, thisInstanceId) {
106
105
  }
107
106
  return false;
108
107
  }
109
- async function streamRunEvents(req, res, deps, runStore, taskId) {
108
+ async function streamRunEvents(req, res, deps, runStore, entry, taskId) {
110
109
  await streamSseLog(req, res, {
111
110
  preamble: async (signal) => {
112
111
  const gate = resolveStreamApprovalGate({
@@ -135,10 +134,7 @@ async function streamRunEvents(req, res, deps, runStore, taskId) {
135
134
  return buildApprovalPreambleSseFrames(frames);
136
135
  },
137
136
  onPreambleFailure: (reason, err) => deps.logger?.warn?.("approval_preamble_failed", { taskId, reason, face: "run.events", error: err instanceof Error ? err.message : String(err ?? "") }),
138
- statusOf: async (id) => {
139
- const run = await runStore.getRun(id);
140
- return run ? runRowAt("run-events", runStore, run) : undefined;
141
- },
137
+ statusOf: async (id) => (await readVisibleRun(entry, runStore, id))?.row,
142
138
  getEvents: (id, after) => runStore.getEvents(id, after),
143
139
  formatEvent: (ev) => {
144
140
  const type = normalizeRunEventType(ev.type);
@@ -208,9 +204,9 @@ export async function handleRuns(req, res, match, ctx) {
208
204
  return;
209
205
  }
210
206
  const inflight = idemCache.peek(inflightKeyOf(idem, "durable", clientTaskId));
211
- const keyedRow = inflight ? undefined : await runStore.getRunByIdem(idem.key);
212
- if (inflight || keyedRow) {
213
- const resp = inflight ? await inflight : await replayOfExistingRun(keyedRow, { subject: submitter, idem, ...(clientTaskId !== undefined ? { clientTaskId } : {}) }, "durable", deps, runStore);
207
+ const replayed = inflight ? undefined : await replayOfKey({ idemKey: idem.key }, { subject: submitter, idem, ...(clientTaskId !== undefined ? { clientTaskId } : {}) }, "durable", deps, runStore);
208
+ const resp = inflight ? await inflight : replayed;
209
+ if (resp !== undefined) {
214
210
  sendJson(res, resp.status, resp.body);
215
211
  return;
216
212
  }
@@ -246,26 +242,23 @@ export async function handleRuns(req, res, match, ctx) {
246
242
  const entry = enter(req, res, deps, match.decl.id);
247
243
  if (entry === undefined)
248
244
  return;
249
- let run;
250
- let pathId;
245
+ let admitted;
251
246
  if (match.decl.id === "runs-by-idem") {
252
247
  const rawKey = new URLSearchParams(match.search).get("idempotencyKey");
253
248
  if (!rawKey) {
254
249
  sendError(res, 400, "request.query_invalid", "idempotencyKey is required: pass the raw Idempotency-Key the submission was sent with");
255
250
  return;
256
251
  }
257
- run = await runStore.getRunByIdem(idemKeyOf(rawKey, idemCallerOf(req, ctx), "run"));
252
+ admitted = await admitRunByIdem(entry, res, runStore, idemKeyOf(rawKey, idemCallerOf(req, ctx), "run"));
258
253
  }
259
254
  else {
260
- pathId = match.params.taskId;
261
- run = await runStore.getRun(pathId);
255
+ admitted = await admitRunById(entry, res, runStore, match.params.taskId);
262
256
  }
263
- const admitted = await admitRunRow(entry, res, runStore, run, pathId);
264
257
  if (admitted === undefined)
265
258
  return;
266
259
  const taskId = admitted.row.taskId;
267
260
  if (match.decl.id === "run-events") {
268
- await streamRunEvents(req, res, deps, runStore, taskId);
261
+ await streamRunEvents(req, res, deps, runStore, entry, taskId);
269
262
  }
270
263
  else {
271
264
  sendJson(res, 200, await readRunFace(deps, runStore, admitted.row));
@@ -296,7 +289,7 @@ export async function handleRuns(req, res, match, ctx) {
296
289
  if (token) {
297
290
  const won = (await expireCardUnderStoredScope(cs, token)) === CARD_EXPIRE_OUTCOME.expired;
298
291
  if (!won) {
299
- const now = await rs.getRun(taskId);
292
+ const now = (await readVisibleRun(entry, rs, taskId))?.row;
300
293
  const st = now?.status;
301
294
  if (st !== undefined && (isParkedRunStatus(st) || st === "running")) {
302
295
  sendError(res, 409, "conflict.approval_settled", "pending approval was settled concurrently (decided or expired) — re-check the run and retry cancel if it is still active", { taskId, status: st });
@@ -310,7 +303,7 @@ export async function handleRuns(req, res, match, ctx) {
310
303
  else {
311
304
  const claimed = await rs.markResuming(taskId, deps.instanceId ?? "default");
312
305
  if (!claimed) {
313
- const now = await rs.getRun(taskId);
306
+ const now = (await readVisibleRun(entry, rs, taskId))?.row;
314
307
  const st = now?.status;
315
308
  if (st !== undefined && (st === "running" || isParkedRunStatus(st))) {
316
309
  sendError(res, 409, "conflict.approval_settled", "pending approval was settled concurrently (decided or expired) — re-check the run and retry cancel if it is still active", { taskId, status: st });
@@ -350,7 +343,7 @@ export async function handleRuns(req, res, match, ctx) {
350
343
  sendError(res, 500, "internal.cancel_not_terminalized", `cancel progressed (${note}) but could not terminalize the run row (retried) — retry cancel; a stuck row is reaped after the stale window (${e instanceof Error ? e.message : String(e)})`, { taskId });
351
344
  return;
352
345
  }
353
- const finalRow = await rs.getRun(taskId).catch(() => undefined);
346
+ const finalRow = (await readVisibleRun(entry, rs, taskId).catch(() => undefined))?.row;
354
347
  if (finalRow && finalRow.errorCode !== "cancelled" && isTerminalRunStatus(finalRow.status)) {
355
348
  sendJson(res, 202, { taskId, status: finalRow.status, errorCode: finalRow.errorCode ?? null, cancelRequested: finalRow.cancelRequested, note: `session unlocked; the run was terminalized concurrently (${finalRow.errorCode ?? finalRow.status}) before this cancel's write — reporting the actual ledger state` });
356
349
  return;
@@ -361,7 +354,7 @@ export async function handleRuns(req, res, match, ctx) {
361
354
  let flagged = await runStore.requestCancel(taskId, run.owner);
362
355
  const CANCEL_RECLASSIFY_ATTEMPTS = 3;
363
356
  for (let attempt = 0; !flagged; attempt++) {
364
- const now = await runStore.getRun(taskId);
357
+ const now = (await readVisibleRun(entry, runStore, taskId))?.row;
365
358
  if (now !== undefined && isParkedRunStatus(now.status)) {
366
359
  await cancelSuspended(now.result);
367
360
  return;
@@ -505,7 +498,7 @@ export async function handleRuns(req, res, match, ctx) {
505
498
  }
506
499
  if (code !== "steering.not_running")
507
500
  throw e;
508
- const freshBeforePark = await runStore.getRun(taskId).catch(() => undefined);
501
+ const freshBeforePark = (await readVisibleRun(entry, runStore, taskId).catch(() => undefined))?.row;
509
502
  const raced = await tryPark();
510
503
  if (raced === "parked") {
511
504
  sendParked(freshBeforePark?.status ?? run.status);
@@ -554,8 +547,8 @@ export async function handleRuns(req, res, match, ctx) {
554
547
  }
555
548
  return;
556
549
  }
557
- const sessionHeadOf = headReadOf(deps.sessionStorage);
558
- if (deps.checkpointStore && sessionHeadOf) {
550
+ const sessionFactsOf = sessionFactsReadOf(deps.sessionStorage);
551
+ if (deps.checkpointStore && sessionFactsOf) {
559
552
  const activeTaskId = await runStore.getActiveTaskId?.(run.sessionId).catch(() => undefined);
560
553
  if (activeTaskId !== undefined && activeTaskId !== null && activeTaskId !== taskId) {
561
554
  sendNotRunning(`run is ${run.status} and its session has an ACTIVE run (${activeTaskId}) — steer that run instead`);
@@ -605,7 +598,7 @@ export async function handleRuns(req, res, match, ctx) {
605
598
  mintResult.duplicateKey = true;
606
599
  return;
607
600
  }
608
- const leafId = await sessionHeadOf(run.sessionId).then((h) => h?.leafId).catch(() => undefined);
601
+ const leafId = await sessionFactsOf(run.sessionId).then((h) => h?.leafId).catch(() => undefined);
609
602
  if (leafId === undefined || leafId === null)
610
603
  return;
611
604
  const resumeRead = await readResumeCtx(cs.getCtx(run.sessionId), { sessionId: run.sessionId, opener: deps.checkpointCtxSealing });
@@ -779,7 +772,7 @@ export async function handleRuns(req, res, match, ctx) {
779
772
  catch (e) {
780
773
  if (e.code !== "steering.not_running")
781
774
  throw e;
782
- const freshRun = await runStore.getRun(taskId).catch(() => undefined);
775
+ const freshRun = (await readVisibleRun(entry, runStore, taskId).catch(() => undefined))?.row;
783
776
  sendNoLiveTurn(freshRun?.status ?? run.status, true);
784
777
  return;
785
778
  }
@@ -1509,9 +1502,9 @@ async function createDurableRunHeld(req, res, ctx, prepared, idem) {
1509
1502
  const submitter = gatedSubject(req, deps.config);
1510
1503
  const caller = { subject: submitter, idem, ...(clientTaskId !== undefined ? { clientTaskId } : {}) };
1511
1504
  if (clientTaskId) {
1512
- const existing = await runStore.getRun(clientTaskId);
1513
- if (existing)
1514
- return await replayOfExistingRun(existing, caller, "durable", deps, runStore);
1505
+ const replayed = await replayOfKey({ taskId: clientTaskId }, caller, "durable", deps, runStore);
1506
+ if (replayed !== undefined)
1507
+ return replayed;
1515
1508
  }
1516
1509
  if ((await leaseDenied(req, res, seat)) || (await usageWindowDenied(req, res, seat)))
1517
1510
  return null;
@@ -1,6 +1,6 @@
1
1
  import { memorySyncReportOf } from "@sema-agent/core";
2
2
  import { sendJson, sendError } from "../send.js";
3
- import { admitSessionById, enter } from "../owner-access.js";
3
+ import { admitSessionById, enter, latestRunIdOf, readVisibleSession } from "../owner-access.js";
4
4
  import { assertNever } from "../route-ctx.js";
5
5
  import * as facts from "../../capability-facts.js";
6
6
  import { leafOfAdmittedHead } from "../../session-incarnation.js";
@@ -94,7 +94,6 @@ export async function handleSessionMemorySync(req, res, match, ctx) {
94
94
  sendError(res, 400, "request.path_malformed", "session id segment is not valid percent-encoding");
95
95
  return;
96
96
  }
97
- const readHead = () => sessionStorage.headOf(sessionId);
98
97
  const admitted = await admitSessionById(entry, res, sessionStorage, sessionId);
99
98
  if (admitted === undefined)
100
99
  return;
@@ -122,10 +121,10 @@ export async function handleSessionMemorySync(req, res, match, ctx) {
122
121
  const quiet = async () => {
123
122
  if ((await runStore.getActiveTaskId(sessionId)) !== undefined)
124
123
  return false;
125
- if ((await runStore.latestRunForSession(sessionId))?.taskId !== observedTaskId)
124
+ if ((await latestRunIdOf(runStore, sessionId)) !== observedTaskId)
126
125
  return false;
127
- const now = await readHead();
128
- return now !== undefined && leafOfAdmittedHead(now) === sourceRevision;
126
+ const now = await readVisibleSession(entry, sessionStorage, sessionId);
127
+ return now !== undefined && leafOfAdmittedHead(now.row) === sourceRevision;
129
128
  };
130
129
  const fenced = () => reportOf({ target, sourceRevision, scopes, refused: { code: "session_active", message: SESSION_ACTIVE_SYNC_MESSAGE } });
131
130
  let report;
@@ -7,10 +7,9 @@ import { classifySyncRelationshipByIds, createStagedLogFeed, MalformedImportEntr
7
7
  import { isUuidShape } from "../../security.js";
8
8
  import { sendJson, sendError, sendSessionError, sendSessionPolicyError, logRequestError, msg } from "../send.js";
9
9
  import { StampedSessionArchiveError } from "../../session-error-wire.js";
10
- import { readSessionAgent } from "../../session-agent.js";
11
10
  import { buildDisplaySafeUrl } from "../../trace/redact.js";
12
11
  import { storeFaultClassOf } from "../../plugins/store-fault.js";
13
- import { enter, judgeSession, refuse } from "../owner-access.js";
12
+ import { enter, readSessionAgent, readSyncGate, refuse } from "../owner-access.js";
14
13
  import { listFilterOf, ownerKeyOf, sameKey } from "../../owner-faces.js";
15
14
  const SYNC_BLOB_MAX_BYTES = 64 * 1024 * 1024;
16
15
  const SYNC_NDJSON_LINE_MAX_BYTES = 8 * 1024 * 1024;
@@ -18,7 +17,6 @@ import { SHA256_HEX_RE } from "../../digest-form.js";
18
17
  import { assertNever } from "../route-ctx.js";
19
18
  import { readSessionRulesRecords, describeSessionRulesRecordProblem } from "../../session-policy-wire.js";
20
19
  import { readSessionPinned, readSessionTitle } from "../../session-row.js";
21
- import { deletionHeadReadOf, sessionFaceOfHead } from "../../session-incarnation.js";
22
20
  import { defaultTaskRegistry } from "@sema-agent/core";
23
21
  import { SessionActiveRunError, SessionForkInProgressError, SessionWriterPresentError } from "../../execution-placement.js";
24
22
  import { probeSessionChildren, sessionTenantScope } from "../../session-children-retire.js";
@@ -94,10 +92,8 @@ export async function handleSessionSync(req, res, match, ctx) {
94
92
  return;
95
93
  }
96
94
  const judgeSessionGate = async () => {
97
- const face = sessionFaceOfHead(sessionId, await sessionStorage.headOf(sessionId));
98
- const held = face ?? sessionFaceOfHead(sessionId, await deletionHeadReadOf(sessionStorage)?.(sessionId));
99
- const mayTouch = held !== undefined && (await judgeSession(syncEntry, sessionStorage, sessionId, held)) !== undefined;
100
- return { face, occupant: held, mayPull: face !== undefined && mayTouch, mayPush: held === undefined || mayTouch };
95
+ const { face, occupant, mayTouch } = await readSyncGate(syncEntry, sessionStorage, sessionId);
96
+ return { face, occupant, mayPull: face !== undefined && mayTouch, mayPush: occupant === undefined || mayTouch };
101
97
  };
102
98
  let gate;
103
99
  try {
@@ -26,7 +26,7 @@ export interface SessionsLocal {
26
26
  degraded?: boolean;
27
27
  }>;
28
28
  }>;
29
- /** DESIGN-269 件4:`/a2a` 面板的短 TTL single-flight 缓存。与 `mcpStatusCache` **分开一张表**而不是
29
+ /** [ref] 件4:`/a2a` 面板的短 TTL single-flight 缓存。与 `mcpStatusCache` **分开一张表**而不是
30
30
  * 共用带前缀的键:两张面板的体不同形(`peers` vs `servers`),塞进一张表就得靠 unknown 兜住两形,
31
31
  * 于是「取到的是哪一形」变成运行期约定 —— 两张表让它回到类型上。 */
32
32
  a2aStatusCache: Map<string, {