@sema-agent/server 7.144.0 → 7.146.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 (305) hide show
  1. package/MIGRATION.md +621 -0
  2. package/README.md +3 -0
  3. package/README.zh-CN.md +1 -0
  4. package/USAGE.md +233 -54
  5. package/deploy/facts/CHANGES.json +25 -0
  6. package/deploy/facts/README.md +57 -0
  7. package/deploy/facts/lanes.json +54 -0
  8. package/deploy/facts/lanes.schema.json +57 -0
  9. package/deploy/facts/sandbox-rbac.json +113 -0
  10. package/deploy/facts/sandbox-rbac.schema.json +74 -0
  11. package/deploy/facts/sealed-key.json +9 -0
  12. package/deploy/facts/sealed-key.schema.json +52 -0
  13. package/deploy/sema-up/chart/templates/_helpers.tpl +19 -0
  14. package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +47 -7
  15. package/deploy/sema-up/chart/templates/server-v4pro.yaml +4 -0
  16. package/deploy/sema-up/chart/templates/server.yaml +4 -0
  17. package/deploy/sema-up/chart/values.yaml +7 -0
  18. package/deploy/sema-up/stack/sema-stack-cluster.yaml +30 -14
  19. package/dist/adoption/import-limits.d.ts +6 -0
  20. package/dist/adoption/import-limits.js +7 -0
  21. package/dist/adoption/import.d.ts +62 -0
  22. package/dist/adoption/import.js +107 -0
  23. package/dist/adoption/wire.d.ts +52 -0
  24. package/dist/adoption/wire.js +9 -0
  25. package/dist/approval-ask-machine.d.ts +2 -2
  26. package/dist/approval-card.d.ts +3 -12
  27. package/dist/approval-card.js +0 -4
  28. package/dist/approval-gate-currency.d.ts +1 -1
  29. package/dist/approval-gate-currency.js +1 -1
  30. package/dist/approval-reconciler.d.ts +1 -1
  31. package/dist/audit.d.ts +2 -21
  32. package/dist/audit.js +0 -9
  33. package/dist/boot/deployment-mods.d.ts +11 -1
  34. package/dist/boot/deployment-mods.js +15 -2
  35. package/dist/boot/device-lane.js +2 -0
  36. package/dist/boot/engine-lease.d.ts +193 -21
  37. package/dist/boot/engine-lease.js +146 -20
  38. package/dist/boot/execution-env.d.ts +4 -0
  39. package/dist/boot/execution-env.js +12 -0
  40. package/dist/boot/exit-boundary.d.ts +50 -0
  41. package/dist/boot/exit-boundary.js +32 -0
  42. package/dist/boot/exit-last.d.ts +20 -4
  43. package/dist/boot/exit-last.js +21 -4
  44. package/dist/boot/governance-seams.js +1 -0
  45. package/dist/boot/memory-consolidation.d.ts +29 -17
  46. package/dist/boot/memory-consolidation.js +26 -15
  47. package/dist/boot/reapers.d.ts +12 -4
  48. package/dist/boot/reapers.js +39 -15
  49. package/dist/boot/resolve-spec.d.ts +2 -0
  50. package/dist/boot/resolve-spec.js +38 -15
  51. package/dist/boot/runner-deps.js +1 -1
  52. package/dist/boot/session-faces.d.ts +1 -1
  53. package/dist/boot/session-faces.js +6 -4
  54. package/dist/boot/shutdown.js +27 -9
  55. package/dist/boot/stage-01-config.js +1 -1
  56. package/dist/boot/stage-03-store-backend.d.ts +1 -0
  57. package/dist/boot/stage-03-store-backend.js +2 -2
  58. package/dist/boot/stage-04-stores.d.ts +1 -0
  59. package/dist/boot/stage-05-execution-env.d.ts +3 -0
  60. package/dist/boot/stage-05-execution-env.js +3 -3
  61. package/dist/boot/stage-06-runners.d.ts +3 -0
  62. package/dist/boot/stage-06-runners.js +11 -10
  63. package/dist/boot/stage-07-capability-layer.d.ts +3 -0
  64. package/dist/boot/stage-07-capability-layer.js +7 -2
  65. package/dist/boot/stage-08-reapers.d.ts +3 -0
  66. package/dist/boot/stage-08-reapers.js +3 -3
  67. package/dist/boot/stage-09-leader.d.ts +4 -1
  68. package/dist/boot/stage-10-http-server.d.ts +4 -1
  69. package/dist/boot/stage-10-http-server.js +4 -3
  70. package/dist/boot/stores.d.ts +1 -0
  71. package/dist/boot/stores.js +35 -19
  72. package/dist/boot/workflow-orchestration.d.ts +1 -1
  73. package/dist/boot/workflow-orchestration.js +2 -1
  74. package/dist/capabilities/composite-members.d.ts +3 -1
  75. package/dist/capabilities/composite-members.js +3 -1
  76. package/dist/capabilities/scenarios.d.ts +2 -2
  77. package/dist/capability-facts.d.ts +18 -1
  78. package/dist/capability-facts.js +25 -7
  79. package/dist/checkpoint-ctx-seal-m28.d.ts +1 -1
  80. package/dist/config-catalog.js +13 -4
  81. package/dist/config-d-migration.d.ts +3 -2
  82. package/dist/config-provider.d.ts +2 -2
  83. package/dist/config-types.d.ts +43 -4
  84. package/dist/config.d.ts +6 -1
  85. package/dist/config.js +67 -11
  86. package/dist/deleted-session-ids.d.ts +19 -0
  87. package/dist/deleted-session-ids.js +26 -0
  88. package/dist/deploy-facts.d.ts +155 -0
  89. package/dist/deploy-facts.js +94 -0
  90. package/dist/device-ws-hub.d.ts +48 -3
  91. package/dist/device-ws-hub.js +48 -2
  92. package/dist/error-code-family.d.ts +35 -0
  93. package/dist/error-code-family.js +26 -0
  94. package/dist/fleet/drain-entry.d.ts +39 -3
  95. package/dist/fleet/drain-entry.js +6 -0
  96. package/dist/fleet/fleet-bus.d.ts +15 -4
  97. package/dist/fleet/fleet-bus.js +17 -3
  98. package/dist/hooks/branch-transcript.d.ts +1 -1
  99. package/dist/hooks/branch-transcript.js +1 -0
  100. package/dist/hooks/classic-stations.d.ts +25 -8
  101. package/dist/hooks/classic-stations.js +29 -3
  102. package/dist/hooks/hook-runner.d.ts +33 -16
  103. package/dist/hooks/hook-runner.js +166 -73
  104. package/dist/hooks/hook-source.d.ts +14 -2
  105. package/dist/hooks/hook-source.js +8 -0
  106. package/dist/http/active-run-conflict.d.ts +1 -1
  107. package/dist/http/active-run-conflict.js +1 -1
  108. package/dist/http/admission.js +13 -2
  109. package/dist/http/checkpoint-ctx.d.ts +60 -71
  110. package/dist/http/checkpoint-ctx.js +35 -40
  111. package/dist/http/dispatch.js +18 -2
  112. package/dist/http/durable-run-leg.d.ts +5 -0
  113. package/dist/http/durable-run-leg.js +8 -6
  114. package/dist/http/execution-context.d.ts +4 -0
  115. package/dist/http/execution-context.js +9 -0
  116. package/dist/http/health-self-report.d.ts +6 -2
  117. package/dist/http/health-self-report.js +3 -2
  118. package/dist/http/memory-compliance-failure-reply.d.ts +1 -1
  119. package/dist/http/memory-compliance-failure-reply.js +2 -15
  120. package/dist/http/memory-control-plane-corrupt-reply.d.ts +25 -0
  121. package/dist/http/memory-control-plane-corrupt-reply.js +19 -0
  122. package/dist/http/principal-gate.d.ts +35 -12
  123. package/dist/http/principal-gate.js +16 -12
  124. package/dist/http/request-guards.d.ts +1 -1
  125. package/dist/http/request-guards.js +5 -5
  126. package/dist/http/resume-legs.d.ts +22 -0
  127. package/dist/http/resume-legs.js +31 -20
  128. package/dist/http/route-ctx.d.ts +26 -10
  129. package/dist/http/route-table.js +2 -0
  130. package/dist/http/routes/adoption.d.ts +11 -0
  131. package/dist/http/routes/adoption.js +80 -6
  132. package/dist/http/routes/agents-roster.js +1 -0
  133. package/dist/http/routes/approvals-assistant.d.ts +20 -1
  134. package/dist/http/routes/approvals-assistant.js +29 -7
  135. package/dist/http/routes/background.js +1 -0
  136. package/dist/http/routes/capabilities.js +4 -1
  137. package/dist/http/routes/diagnostics.d.ts +9 -14
  138. package/dist/http/routes/diagnostics.js +10 -20
  139. package/dist/http/routes/engine.d.ts +31 -0
  140. package/dist/http/routes/engine.js +81 -0
  141. package/dist/http/routes/memory-bundle.js +3 -0
  142. package/dist/http/routes/memory-consolidation.js +3 -5
  143. package/dist/http/routes/memory-origin.d.ts +1 -1
  144. package/dist/http/routes/memory-origin.js +2 -4
  145. package/dist/http/routes/memory-policy.js +5 -1
  146. package/dist/http/routes/observability.js +3 -6
  147. package/dist/http/routes/rules.d.ts +1 -1
  148. package/dist/http/routes/rules.js +5 -2
  149. package/dist/http/routes/runs.js +7 -3
  150. package/dist/http/routes/session-sync.js +24 -25
  151. package/dist/http/routes/sessions-list.d.ts +1 -1
  152. package/dist/http/routes/sessions.js +4 -0
  153. package/dist/http/routes/tasks.js +35 -14
  154. package/dist/http/routes/trace-usage.js +1 -1
  155. package/dist/http/run-concurrency-cap.d.ts +86 -0
  156. package/dist/http/run-concurrency-cap.js +54 -0
  157. package/dist/http/run-meta.d.ts +14 -7
  158. package/dist/http/run-meta.js +5 -2
  159. package/dist/http/server.d.ts +27 -16
  160. package/dist/http/server.js +21 -3
  161. package/dist/http/stored-body-replay.d.ts +9 -6
  162. package/dist/http/stored-body-replay.js +19 -5
  163. package/dist/http/submit-ctx.d.ts +12 -0
  164. package/dist/http/submit-ctx.js +23 -0
  165. package/dist/http/wire-types.d.ts +8 -3
  166. package/dist/json-body-limit.d.ts +8 -0
  167. package/dist/json-body-limit.js +2 -0
  168. package/dist/leader/diffout.d.ts +1 -1
  169. package/dist/local-data-root.d.ts +2 -2
  170. package/dist/memory-operator-faces.d.ts +9 -4
  171. package/dist/memory-scope-stores.d.ts +9 -7
  172. package/dist/memory-scope-stores.js +3 -14
  173. package/dist/memory-scope.d.ts +36 -0
  174. package/dist/memory-scope.js +32 -1
  175. package/dist/net-address.d.ts +53 -0
  176. package/dist/net-address.js +111 -0
  177. package/dist/observability/fail-open.d.ts +28 -12
  178. package/dist/observability/fail-open.js +28 -12
  179. package/dist/orchestration/workflow-notify-journal.js +2 -3
  180. package/dist/org-delivery-gate.js +1 -1
  181. package/dist/peer-directory.d.ts +6 -12
  182. package/dist/peer-directory.js +5 -1
  183. package/dist/plugins/approval-ask-store-file.d.ts +16 -2
  184. package/dist/plugins/approval-ask-store-file.js +21 -1
  185. package/dist/plugins/approval-ask-store-memory.d.ts +8 -2
  186. package/dist/plugins/approval-ask-store-memory.js +11 -8
  187. package/dist/plugins/caching-session-store.d.ts +6 -1
  188. package/dist/plugins/caching-session-store.js +6 -0
  189. package/dist/plugins/checkpoint-store-face.d.ts +426 -0
  190. package/dist/plugins/checkpoint-store-face.js +152 -0
  191. package/dist/plugins/file-run-store.d.ts +13 -7
  192. package/dist/plugins/file-run-store.js +12 -13
  193. package/dist/plugins/fork-routing-session-store.d.ts +6 -2
  194. package/dist/plugins/fork-routing-session-store.js +2 -2
  195. package/dist/plugins/imported-layer-root-retirement.d.ts +10 -8
  196. package/dist/plugins/local-checkpoint-store.d.ts +16 -5
  197. package/dist/plugins/local-checkpoint-store.js +25 -6
  198. package/dist/plugins/local-session-store.d.ts +80 -21
  199. package/dist/plugins/local-session-store.js +79 -22
  200. package/dist/plugins/memory-engine-pg.d.ts +29 -6
  201. package/dist/plugins/memory-engine-pg.js +96 -214
  202. package/dist/plugins/memory-engine-sql-apply.d.ts +121 -0
  203. package/dist/plugins/memory-engine-sql-apply.js +358 -0
  204. package/dist/plugins/memory-engine-tidb.d.ts +35 -9
  205. package/dist/plugins/memory-engine-tidb.js +97 -227
  206. package/dist/plugins/memory-key-guards.d.ts +1 -1
  207. package/dist/plugins/memory-origin-law.d.ts +3 -3
  208. package/dist/plugins/memory-run-store.d.ts +2 -0
  209. package/dist/plugins/memory-run-store.js +6 -0
  210. package/dist/plugins/one-time-migrations.d.ts +32 -56
  211. package/dist/plugins/one-time-migrations.js +27 -119
  212. package/dist/plugins/permission-rule-store-file.d.ts +17 -3
  213. package/dist/plugins/permission-rule-store-file.js +8 -1
  214. package/dist/plugins/pg-pool.js +1 -1
  215. package/dist/plugins/pg-session-storage.d.ts +17 -4
  216. package/dist/plugins/pg-session-storage.js +8 -4
  217. package/dist/plugins/run-store-sql.d.ts +4 -0
  218. package/dist/plugins/run-store-sql.js +5 -2
  219. package/dist/plugins/schema-ensure-legs.js +2 -2
  220. package/dist/plugins/session-fork.d.ts +31 -9
  221. package/dist/plugins/session-fork.js +3 -2
  222. package/dist/plugins/session-placement.d.ts +4 -2
  223. package/dist/plugins/session-store.d.ts +1 -1
  224. package/dist/plugins/sql-json-column.d.ts +5 -1
  225. package/dist/plugins/store-backend.d.ts +11 -8
  226. package/dist/plugins/store-backend.js +8 -7
  227. package/dist/plugins/store-contracts.d.ts +1 -11
  228. package/dist/plugins/store-contracts.js +0 -9
  229. package/dist/plugins/store-sweep.d.ts +63 -0
  230. package/dist/plugins/store-sweep.js +48 -0
  231. package/dist/plugins/tidb-pool.js +1 -1
  232. package/dist/plugins/tidb-session-store.d.ts +16 -3
  233. package/dist/plugins/tidb-session-store.js +8 -4
  234. package/dist/prompt-screen.d.ts +18 -0
  235. package/dist/prompt-screen.js +5 -0
  236. package/dist/rules-consent.d.ts +56 -51
  237. package/dist/rules-consent.js +86 -14
  238. package/dist/run-acceptance.d.ts +40 -10
  239. package/dist/run-acceptance.js +38 -5
  240. package/dist/run-cancel-context.d.ts +4 -1
  241. package/dist/run-cancel-context.js +2 -1
  242. package/dist/runs.js +2 -2
  243. package/dist/sandbox-egress-policy.js +6 -11
  244. package/dist/sandbox-rbac-rules.d.ts +88 -0
  245. package/dist/sandbox-rbac-rules.js +80 -0
  246. package/dist/sealed-key-layout.d.ts +17 -0
  247. package/dist/sealed-key-layout.js +4 -0
  248. package/dist/sealed-key.js +5 -5
  249. package/dist/security.d.ts +48 -9
  250. package/dist/security.js +9 -2
  251. package/dist/session-agent.d.ts +39 -0
  252. package/dist/session-agent.js +16 -0
  253. package/dist/session-error-wire.d.ts +4 -4
  254. package/dist/session-notify-door.d.ts +5 -3
  255. package/dist/session-resident-background.d.ts +57 -0
  256. package/dist/session-resident-background.js +20 -0
  257. package/dist/session-sync-kernel.d.ts +53 -14
  258. package/dist/session-sync-kernel.js +70 -1
  259. package/dist/session-sync.d.ts +5 -3
  260. package/dist/session-workspace.d.ts +1 -0
  261. package/dist/session-workspace.js +1 -0
  262. package/dist/sighup-idle.d.ts +9 -7
  263. package/dist/sighup-idle.js +4 -5
  264. package/dist/spec-fields.d.ts +16 -0
  265. package/dist/spec-fields.js +7 -0
  266. package/dist/stored-request-env-credentials-m38.d.ts +3 -0
  267. package/dist/stored-request-env-credentials-m38.js +8 -0
  268. package/dist/stored-request-prompt-profile-m30.d.ts +1 -1
  269. package/dist/{plugins → stores-sql}/checkpoint-store-sql.d.ts +33 -429
  270. package/dist/{plugins → stores-sql}/checkpoint-store-sql.js +70 -209
  271. package/dist/{plugins → stores-sql}/mailbox-store-sql.d.ts +2 -2
  272. package/dist/{plugins → stores-sql}/mailbox-store-sql.js +4 -4
  273. package/dist/{plugins → stores-sql}/roster-store-sql.d.ts +2 -2
  274. package/dist/{plugins → stores-sql}/roster-store-sql.js +1 -1
  275. package/dist/{plugins → stores-sql}/tool-result-store-sql.d.ts +2 -2
  276. package/dist/{plugins → stores-sql}/tool-result-store-sql.js +6 -6
  277. package/dist/{plugins → stores-sql}/workflow-journal-store-sql.d.ts +7 -12
  278. package/dist/{plugins → stores-sql}/workflow-journal-store-sql.js +3 -8
  279. package/dist/task-settings.d.ts +59 -4
  280. package/dist/task-settings.js +50 -4
  281. package/dist/tool-approval.d.ts +2 -2
  282. package/dist/trace/core-keyset-guard.d.ts +8 -7
  283. package/dist/trace/engine-notice-wire.d.ts +1 -1
  284. package/dist/trace/engine-notice-wire.js +2 -0
  285. package/dist/trace/exit-seat.d.ts +6 -0
  286. package/dist/trace/exit-seat.js +1 -0
  287. package/dist/trace/model-segment.d.ts +3 -2
  288. package/dist/trace/model-segment.js +16 -8
  289. package/dist/trace/project.js +9 -0
  290. package/dist/trace/redact.d.ts +79 -4
  291. package/dist/trace/redact.js +523 -123
  292. package/dist/trace/self-minted-wire.d.ts +391 -1
  293. package/dist/trace/self-minted-wire.js +145 -0
  294. package/dist/trace/wire-projection-faces.d.ts +1 -1
  295. package/dist/trace/wire-projection-faces.js +1 -1
  296. package/dist/usage-analytics.d.ts +4 -0
  297. package/package.json +12 -4
  298. package/scripts/drain-check-local.mjs +8 -29
  299. package/scripts/drain-check.mysql.sql +3 -24
  300. package/scripts/drain-check.pg.sql +3 -24
  301. package/skills/update-config.md +1 -1
  302. package/wire-changes/server@7.145.0.json +146 -0
  303. package/wire-changes/server@7.146.0.json +3221 -0
  304. package/dist/approval-batch-census.d.ts +0 -27
  305. package/dist/approval-batch-census.js +0 -27
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
- import { ControlPlaneCorruptError } from "@sema-agent/core";
3
2
  import { sendJson, sendError } from "../send.js";
4
3
  import { sentMemoryStoreRefusal } from "../memory-store-refusal-reply.js";
4
+ import { sentControlPlaneCorrupt } from "../memory-control-plane-corrupt-reply.js";
5
5
  import { buildOriginFence, readOriginAware } from "../../memory-origin-fence.js";
6
6
  import { readLayerSeat, sentMemoryLayerRefusal } from "../memory-layer-reply.js";
7
7
  import { gatedPrincipal, explicitOperatorOk, sendOperatorPrincipalsRequired, sendPrincipalRefusal } from "../principal-gate.js";
@@ -59,10 +59,8 @@ function sentClassifiedFailure(res, ctx, face, err) {
59
59
  }
60
60
  if (face !== "clearances" && sentMemoryStoreRefusal(res, err, ctx.deps.logger))
61
61
  return true;
62
- if (err instanceof ControlPlaneCorruptError) {
63
- sendError(res, 500, "internal.memory_control_plane_corrupt", err.message);
62
+ if (sentControlPlaneCorrupt(res, err, { kind: "operator" }))
64
63
  return true;
65
- }
66
64
  ctx.deps.logger?.warn?.(`memory_origin_${face}_failed`, { err: String(err) });
67
65
  return false;
68
66
  }
@@ -1,4 +1,4 @@
1
- import { formatUserScope } from "@sema-agent/core";
1
+ import { formatUserScope, isReplicableScope } from "@sema-agent/core";
2
2
  import { parseMemorySyncRequest } from "../../memory-sync.js";
3
3
  import { memorySyncIdentityRule } from "../../memory-sync-identity.js";
4
4
  import * as facts from "../../capability-facts.js";
@@ -160,6 +160,10 @@ export async function handleMemoryPolicy(req, res, match, ctx) {
160
160
  sendError(res, 400, "memory_sync_invalid_scope", "missing :scope");
161
161
  return;
162
162
  }
163
+ if (!isReplicableScope(scope)) {
164
+ sendError(res, 400, "memory_sync_invalid_scope", "a userlocal: memory layer never leaves the store it is written in — it is not synced");
165
+ return;
166
+ }
163
167
  if (sendScopeGateRefusal(res, await gateMemoryScope(deps, principal, scope, "write")))
164
168
  return;
165
169
  const body = await readJson(req);
@@ -12,12 +12,9 @@ export async function handleObservability(req, res, match, ctx) {
12
12
  sendOperatorPrincipalsRequired(res);
13
13
  return;
14
14
  }
15
- if (deps.config.requirePrincipal) {
16
- const principal = gatedPrincipal(req, deps.config);
17
- if (!explicitOperatorOk(principal, deps.config)) {
18
- sendError(res, 403, "auth.operator_only", "outcomes view is operator-only on a multi-tenant worker");
19
- return;
20
- }
15
+ if (!explicitOperatorOk(gatedPrincipal(req, deps.config), deps.config)) {
16
+ sendError(res, 403, "auth.operator_only", "the outcomes view is operator-only");
17
+ return;
21
18
  }
22
19
  if (!fact.available) {
23
20
  switch (fact.why) {
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * POST /v1/rules → 写一条**收紧**规则(deny/ask)进 durable 分区,单步、无票仪式
5
5
  * POST /v1/rules/cc-import/prepare → 预览 + 一张**一次性、principal 绑定、有期、载荷绑定**的票
6
- * POST /v1/rules/cc-import/redeem → 原子消费票 → confirm → 批量兑付进店
6
+ * POST /v1/rules/cc-import/redeem → 原子消费票 → confirm → 批量兑付进店 → 回执 `{ members, rev }`(与 local-import 口同一张,{@link sendImportReceipt})
7
7
  * GET /v1/rules → 列出自己名下**活着**的规则(keyset 分页,游标绑 rev)
8
8
  * DELETE /v1/rules → 按 (rule, scope) 内容撤销,**恒经** core `removePersistedRule`
9
9
  *
@@ -159,7 +159,7 @@ export async function handleRules(req, res, match, ctx) {
159
159
  }
160
160
  const redeemed = await lane.redeemLocalImport(principal, parsed.data.ticket);
161
161
  if (redeemed.ok) {
162
- sendJson(res, 200, { members: redeemed.outcome.members, rev: redeemed.outcome.rev });
162
+ sendImportReceipt(res, redeemed.outcome);
163
163
  return;
164
164
  }
165
165
  logger.info("local rule import ticket refused", { reason: redeemed.reason, detail: redeemed.detail, retryable: redeemed.reason !== "ticket-unusable" && redeemed.reason !== "in-flight" && redeemed.retryable === true });
@@ -218,7 +218,10 @@ export async function handleRules(req, res, match, ctx) {
218
218
  sendError(res, 404, "not_found.rule_ticket", "no such import ticket");
219
219
  return;
220
220
  }
221
- sendJson(res, 200, { result: redeemed.result });
221
+ sendImportReceipt(res, redeemed.outcome);
222
+ }
223
+ function sendImportReceipt(res, outcome) {
224
+ sendJson(res, 200, { members: outcome.members, rev: outcome.rev });
222
225
  }
223
226
  function resolveTargetPrincipal(requested, caller, config) {
224
227
  if (requested === undefined || requested === caller)
@@ -4,7 +4,7 @@ import { z } from "zod";
4
4
  import { uuidv7, mintCheckpointToken, CheckpointError, policyCannotAnswer, validatePendingSteer, TASK_OUTPUT_REPLAY_KINDS, isSystemInjectionPriority } from "@sema-agent/core";
5
5
  import { isTerminalRunStatus, isParkedRunStatus, runProvenElsewhere } from "../../plugins/store-contracts.js";
6
6
  import { askOwnerAdmits, askRowCarriesOwner } from "../../plugins/approval-ask-store-sql.js";
7
- import { LIVE_RUN_GATE_KINDS, TERMINAL_PARK_GATE_KINDS } from "../../plugins/checkpoint-store-sql.js";
7
+ import { LIVE_RUN_GATE_KINDS, TERMINAL_PARK_GATE_KINDS } from "../../plugins/checkpoint-store-face.js";
8
8
  import { redactSecrets } from "../../trace/redact.js";
9
9
  import { isUuidV7, encodeCheckpointScope } from "../../security.js";
10
10
  import { redactSteerIn, STEER_IN_MAX_CHARS, STEER_IN_MAX_REQUEST_CHARS } from "../../orchestration/workflow-agent-steer.js";
@@ -1578,7 +1578,7 @@ export async function handleRunVerbs(req, res, match, ctx) {
1578
1578
  export async function createDurableRun(req, res, ctx, prepared, idem) {
1579
1579
  const { deps } = ctx;
1580
1580
  const { idemCache } = ctx.registry;
1581
- const { leaseDenied, usageWindowDenied } = ctx.helpers;
1581
+ const { leaseDenied, usageWindowDenied, admitRun } = ctx.helpers;
1582
1582
  const source = ctx.req.source;
1583
1583
  const runs = facts.durableRuns(deps);
1584
1584
  if (!runs.available) {
@@ -1600,6 +1600,9 @@ export async function createDurableRun(req, res, ctx, prepared, idem) {
1600
1600
  }
1601
1601
  if ((await leaseDenied(req, res)) || (await usageWindowDenied(req, res)))
1602
1602
  return null;
1603
+ const runSlot = admitRun(req, res);
1604
+ if (runSlot === undefined)
1605
+ return null;
1603
1606
  return await idemCache.run(idem !== undefined ? inflightKeyOf(idem, "durable", clientTaskId) : undefined, async () => {
1604
1607
  const out = await mintAndDriveDurableRun({ deps, registry: ctx.registry, helpers: ctx.helpers, local: ctx.local, legs: ctx.legs }, runStore, {
1605
1608
  prepared,
@@ -1609,9 +1612,10 @@ export async function createDurableRun(req, res, ctx, prepared, idem) {
1609
1612
  elicitOwner: submitter ?? null,
1610
1613
  source,
1611
1614
  identity: runIdentityOf(req, deps.config, prepared.auth?.principal ?? null),
1615
+ runSlot,
1612
1616
  });
1613
1617
  return out.existing ? await replayOfExistingRun(out.existing, caller, "durable", deps, runStore) : out;
1614
- });
1618
+ }).finally(() => runSlot.release());
1615
1619
  }
1616
1620
  function clientTaskIdOf(body) {
1617
1621
  const raw = typeof body === "object" && body !== null ? body.taskId : undefined;
@@ -1,18 +1,18 @@
1
1
  import { once } from "node:events";
2
- import { randomBytes } from "node:crypto";
3
- import { sessionLogDigest, sessionLogDigestsComparable, StreamingImportValidator, validateFileHistoryExport } from "@sema-agent/core";
2
+ import { sessionLogDigest, sessionLogDigestsComparable, validateFileHistoryExport } from "@sema-agent/core";
4
3
  import { StringDecoder } from "node:string_decoder";
5
4
  import { FileHistoryUnreadableError, blobGetterOf, exportSessionManifest, exportedFileHistory, fileHistoryImportFace, replayFileHistory, replayPolicyRecords, wipeSessionAttendants } from "../../session-sync.js";
6
5
  import * as facts from "../../capability-facts.js";
7
- import { classifySyncRelationshipByIds, readImportAnchors, SyncConflictError } from "../../session-sync-kernel.js";
6
+ import { classifySyncRelationshipByIds, createStagedLogFeed, MalformedImportEntryError, newStagingToken, readImportAnchors, SyncConflictError } from "../../session-sync-kernel.js";
8
7
  import { isUuidShape, legacyUnownedSessionOk, sessionOwnerCellOf } from "../../security.js";
9
8
  import { sendJson, sendError, sendSessionError, sendSessionPolicyError, logRequestError, msg } from "../send.js";
9
+ import { StampedSessionArchiveError } from "../../session-error-wire.js";
10
+ import { readSessionAgent } from "../../session-agent.js";
10
11
  import { buildDisplaySafeUrl } from "../../trace/redact.js";
11
12
  import { storeFaultClassOf } from "../../plugins/store-fault.js";
12
13
  import { fleetViewOf, gatedPrincipal, explicitOperatorOk, sendUnauthorized, sessionCallerOf } from "../principal-gate.js";
13
14
  const SYNC_BLOB_MAX_BYTES = 64 * 1024 * 1024;
14
15
  const SYNC_NDJSON_LINE_MAX_BYTES = 8 * 1024 * 1024;
15
- const SYNC_IMPORT_BATCH = 500;
16
16
  import { SHA256_HEX_RE } from "../../digest-form.js";
17
17
  import { assertNever, SESSION_NOT_FOUND_MESSAGE } from "../route-ctx.js";
18
18
  import { readSessionRulesRecords, describeSessionRulesRecordProblem } from "../../session-policy-wire.js";
@@ -359,6 +359,16 @@ export async function handleSessionSync(req, res, match, ctx) {
359
359
  const dstIds = (await sessionStorage.listEntryIds?.(sessionId)) ?? null;
360
360
  const relA = classifySyncRelationshipByIds(entryIds, dstIds);
361
361
  if ((relA.relation === "fork" || relA.relation === "stale") && resolution !== "overwrite-dst") {
362
+ if (sessionStorage.assertArchiveAnonymous !== undefined && (await readSessionAgent(sessionStorage, sessionId)) === undefined) {
363
+ try {
364
+ await sessionStorage.assertArchiveAnonymous(sessionId);
365
+ }
366
+ catch (e) {
367
+ if (e instanceof StampedSessionArchiveError && sendSessionError(res, e, "import"))
368
+ return;
369
+ throw e;
370
+ }
371
+ }
362
372
  sendError(res, 409, "conflict", "sync conflict", { relation: relA });
363
373
  return;
364
374
  }
@@ -489,7 +499,7 @@ export async function handleSessionSync(req, res, match, ctx) {
489
499
  }
490
500
  const importingPrincipal = scope.fleetWide ? (owner ?? null) : scope.gateOwner;
491
501
  const operatorOk = explicitOperatorOk(gatedPrincipal(req, deps.config), deps.config);
492
- const token = randomBytes(8).toString("hex");
502
+ const token = newStagingToken();
493
503
  const handle = sessionStorage.beginImportStaging(sessionId, token);
494
504
  const stagingId = handle.stagingId;
495
505
  importLeases.set(sessionId, { at: Date.now(), stagingId });
@@ -515,13 +525,8 @@ export async function handleSessionSync(req, res, match, ctx) {
515
525
  sendError(res, 404, "not_found.staging", "staging not found");
516
526
  return;
517
527
  }
518
- const validator = new StreamingImportValidator();
528
+ const feed = createStagedLogFeed(pending.handle);
519
529
  let lineBuf = "";
520
- let pendingBatch = [];
521
- const flush = async () => { if (pendingBatch.length) {
522
- await pending.handle.appendBatch(pendingBatch);
523
- pendingBatch = [];
524
- } };
525
530
  class PrecheckError extends Error {
526
531
  status;
527
532
  constructor(status, msg) {
@@ -530,12 +535,7 @@ export async function handleSessionSync(req, res, match, ctx) {
530
535
  }
531
536
  }
532
537
  const streamedIds = new Set();
533
- const onEntry = (e) => {
534
- if (!e || typeof e !== "object" || typeof e.id !== "string")
535
- throw new PrecheckError(422, "malformed entry (missing id)");
536
- streamedIds.add(e.id);
537
- validator.step(e);
538
- };
538
+ const onEntry = async (e) => { streamedIds.add(await feed.push(e)); };
539
539
  const utf8 = new StringDecoder("utf8");
540
540
  try {
541
541
  for await (const chunk of req) {
@@ -556,10 +556,7 @@ export async function handleSessionSync(req, res, match, ctx) {
556
556
  catch {
557
557
  throw new PrecheckError(400, "malformed NDJSON line");
558
558
  }
559
- onEntry(e);
560
- pendingBatch.push(e);
561
- if (pendingBatch.length >= SYNC_IMPORT_BATCH)
562
- await flush();
559
+ await onEntry(e);
563
560
  }
564
561
  }
565
562
  lineBuf += utf8.end();
@@ -572,11 +569,9 @@ export async function handleSessionSync(req, res, match, ctx) {
572
569
  catch {
573
570
  throw new PrecheckError(400, "malformed NDJSON line");
574
571
  }
575
- onEntry(e);
576
- pendingBatch.push(e);
572
+ await onEntry(e);
577
573
  }
578
- await flush();
579
- validator.finish();
574
+ await feed.finish();
580
575
  if (streamedIds.size !== pending.declaredEntryIds.length || !pending.declaredEntryIds.every((id) => streamedIds.has(id))) {
581
576
  throw new PrecheckError(422, "streamed entries do not match the Phase-A declared entryIds");
582
577
  }
@@ -588,6 +583,10 @@ export async function handleSessionSync(req, res, match, ctx) {
588
583
  sendError(res, e.status, "invalid_entries", e.message);
589
584
  return;
590
585
  }
586
+ if (e instanceof MalformedImportEntryError) {
587
+ sendError(res, 422, "invalid_entries", e.message);
588
+ return;
589
+ }
591
590
  if (sendSessionError(res, e, "import"))
592
591
  return;
593
592
  throw e;
@@ -2,7 +2,7 @@
2
2
  * [ref] A9 域模块 —— 会话列表 / 发件链接列表。
3
3
  *
4
4
  * 路由:`GET /v1/sessions`(CC /resume picker,keyset 分页)、`GET /v1/sendfile-links`。
5
- * owner-scope 与 trace 列表同一权威模型(`fleetViewOf` 读档:service 凭据不报主体 / 只读 trace 凭据 / dev-open = fleet-wide;带验过的主体 = 钉住那位主体)。本域零可变状态。
5
+ * owner-scope 与 trace 列表同一权威模型(`fleetViewOf` 读档:service 凭据不报主体〔部署声明 `SERVICE_TOKEN_ACTS_FOR_DEPLOYMENT=false` 时不算〕/ 只读 trace 凭据 / dev-open = fleet-wide;带验过的主体 = 钉住那位主体)。本域零可变状态。
6
6
  */
7
7
  import type { IncomingMessage, ServerResponse } from "node:http";
8
8
  import { type SessionListItem } from "../../security.js";
@@ -8,6 +8,7 @@ import { recordFailOpen } from "../../observability/fail-open.js";
8
8
  import { MCP_DIAL_DEADLINE_MS, raceDeadline } from "../mcp-dial-deadline.js";
9
9
  import { sentMemoryComplianceFailure } from "../memory-compliance-failure-reply.js";
10
10
  import { readLayerSeat, sentMemoryLayerRefusal } from "../memory-layer-reply.js";
11
+ import { DEPLOYMENT_SHAPE_PREDICATES } from "../../deployment-shape.js";
11
12
  import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS } from "../../task-settings.js";
12
13
  import { readWritableSessionPermissionRules } from "../../session-policy-wire.js";
13
14
  import { sendJson, sendError, sendSessionError, sendSessionPolicyError, sseHeaders, httpErrorBody } from "../send.js";
@@ -615,6 +616,7 @@ export async function handleSessions(req, res, match, ctx) {
615
616
  memoryStatusCache.delete(cacheKey); });
616
617
  }
617
618
  const s = await pending;
619
+ const workerPathsDisclosed = DEPLOYMENT_SHAPE_PREDICATES.callerOwnsWorkerDisk.holds(deps.config);
618
620
  sendJson(res, 200, {
619
621
  sessionId,
620
622
  autoConsolidationArmed: deps.memoryAutoConsolidationArmed,
@@ -624,6 +626,8 @@ export async function handleSessions(req, res, match, ctx) {
624
626
  ...(s.foldedCount !== undefined ? { foldedCount: s.foldedCount } : {}),
625
627
  ...(s.optOutSource !== undefined ? { optOutSource: s.optOutSource } : {}),
626
628
  ...(s.lastCaptureAt !== undefined ? { lastCaptureAt: s.lastCaptureAt } : {}),
629
+ ...(s.writeHome !== undefined && workerPathsDisclosed ? { writeHome: s.writeHome } : {}),
630
+ ...(s.planes !== undefined && workerPathsDisclosed ? { planes: s.planes.map((row) => ({ plane: row.plane, ...(row.status.writeHome !== undefined ? { writeHome: row.status.writeHome } : {}) })) } : {}),
627
631
  });
628
632
  }
629
633
  catch (err) {
@@ -5,7 +5,7 @@ import { graftParkToolCallId, markChildrenStoppedByUserOnAbort, syncRefusalHttpS
5
5
  import { atHostBoundary, sealResultForExit, outwardPlane } from "../../terminal.js";
6
6
  import { voidVerifierPauseCard } from "../../verifier-pause-card.js";
7
7
  import { withPrincipal } from "../../observability/principal-context.js";
8
- import { fleetRunPublisher, fleetRunLabels, fleetRunResiduals } from "../../fleet/fleet-bus.js";
8
+ import { fleetRunPublisher, fleetRunLabels, fleetRunResiduals, nameFleetRowOnDelivery } from "../../fleet/fleet-bus.js";
9
9
  import { defaultSubagentTailBus } from "../../fleet/subagent-tail-bus.js";
10
10
  import { createLiveModelText, modelStreamOfTask } from "../../trace/model-segment.js";
11
11
  import { emitPendingWorkflowCompletions, taskNotificationInboxEntry, taskNotificationStreamKeyOf, NotifiedKeys } from "../../orchestration/workflow-completion-inbox.js";
@@ -17,7 +17,7 @@ import { recordResourceWindow } from "../../resource-window.js";
17
17
  import { redactSecrets } from "../../trace/redact.js";
18
18
  import { writeLiveFrame, writeLiveHeartbeat } from "../live-frame.js";
19
19
  import { contextUsageEventData, turnEndEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskStartedEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, compactionStartedEventData, workspaceChangedEventData, wiringManifestFaceEventData, toolRosterDeltaEventData, toolProgressEventData, toolDisclosureEventData, humanInputEventData, appendModelUsageDelta, attachModelUsage, liveIdentityFields } from "../../trace/project.js";
20
- import { cascadeConfig, runMeta } from "../run-meta.js";
20
+ import { cascadeConfig, fillRunPreviewOnDelivery, runMeta } from "../run-meta.js";
21
21
  import { idemCallerOf, inflightKeyOf, submissionIdemOf } from "../idempotency.js";
22
22
  import { replayOfExistingRun } from "../submit-replay.js";
23
23
  import { httpErrorCode, sendJson, sendError, sseHeaders } from "../send.js";
@@ -33,8 +33,7 @@ import { failOpenTagForDroppedFrame, recordFailOpen } from "../../observability/
33
33
  import { buildSyncSubmitBody } from "../sync-submit-body.js";
34
34
  import { assertNever } from "../route-ctx.js";
35
35
  import * as facts from "../../capability-facts.js";
36
- import { persistSubmitCtx } from "../submit-ctx.js";
37
- import { serviceVersion } from "../../service-version.js";
36
+ import { persistSubmissionCtx } from "../submit-ctx.js";
38
37
  import { withModelReachScope } from "../../model-reach-scope.js";
39
38
  import { createInstructionSelectionLeg } from "../../instruction-selection.js";
40
39
  import { fieldConflictFacts } from "../../field-conflict.js";
@@ -77,7 +76,7 @@ const MULTI_ATTEMPT_NO_EVENT_CHANNEL_NOTICE = "verify / cascade ran with their n
77
76
  export async function handleTasks(req, res, match, ctx) {
78
77
  const { deps } = ctx;
79
78
  const { idemCache, inflightRuns, steerableRuns, counters } = ctx.registry;
80
- const { readJson, rateLimited, quotaExceeded, leaseDenied, usageWindowDenied } = ctx.helpers;
79
+ const { readJson, rateLimited, quotaExceeded, leaseDenied, usageWindowDenied, admitRun } = ctx.helpers;
81
80
  const { prepareSpec, finalizeTaskResult } = ctx.legs;
82
81
  const reqState = ctx.req;
83
82
  const source = ctx.req.source;
@@ -145,10 +144,14 @@ export async function handleTasks(req, res, match, ctx) {
145
144
  }
146
145
  if (rateLimited(req, res) || quotaExceeded(req, res) || (await leaseDenied(req, res)) || (await usageWindowDenied(req, res)))
147
146
  return;
147
+ let runSlot = undefined;
148
148
  const prepared = await prepareSpec(req, res, keyedBody, undefined, { liveStream });
149
149
  if (!prepared)
150
150
  return;
151
151
  try {
152
+ runSlot = admitRun(req, res);
153
+ if (runSlot === undefined)
154
+ return;
152
155
  const declaredFamilies = declaredApprovalFrameFamilies(prepared.body.settings);
153
156
  const approvalLegInput = (streamApprovalOn) => {
154
157
  const legWalltimeMs = typeof prepared.spec.limits?.maxWalltimeMs === "number" ? prepared.spec.limits.maxWalltimeMs : undefined;
@@ -185,7 +188,7 @@ export async function handleTasks(req, res, match, ctx) {
185
188
  sseHeaders(res, earlyDurableTid ? { "x-task-id": earlyDurableTid } : undefined);
186
189
  if (earlyDurableTid)
187
190
  writeMetaFrame(res, earlyDurableTid, prepared.spec.sessionId, prepared.warnings);
188
- const ac = createRunAbort();
191
+ const ac = createRunAbort(prepared.auth?.principal ?? null);
189
192
  let closed = false;
190
193
  let detachLogged = false;
191
194
  let disconnectParkGrace;
@@ -326,9 +329,10 @@ export async function handleTasks(req, res, match, ctx) {
326
329
  return placementReject;
327
330
  }
328
331
  durableTaskId = tid;
332
+ fillRunPreviewOnDelivery(prepared.acceptance, deps.runStore, tid);
329
333
  }
330
334
  if (deps.checkpointStore) {
331
- const ctxRefusal = await persistSubmitCtx(deps.checkpointStore, deps, durableTaskId !== undefined && deps.runStore ? { runStore: deps.runStore, taskId: durableTaskId } : undefined, prepared.spec.sessionId, { body: prepared.body, memoryScope: prepared.auth?.memoryScope, writerVersion: serviceVersion() });
335
+ const ctxRefusal = await persistSubmissionCtx(deps.checkpointStore, deps, durableTaskId !== undefined && deps.runStore ? { runStore: deps.runStore, taskId: durableTaskId } : undefined, prepared);
332
336
  if (ctxRefusal) {
333
337
  writeLiveFrame(res, { type: "done", result: doneResultOfOutcome(ctxRefusal) });
334
338
  return ctxRefusal;
@@ -355,14 +359,17 @@ export async function handleTasks(req, res, match, ctx) {
355
359
  }, HEARTBEAT_MS);
356
360
  durableHeartbeat.unref?.();
357
361
  inflightRuns.set(tid, ac);
362
+ runSlot?.release();
358
363
  void Promise.resolve(rs.isCancelRequested?.(tid, owner)).then((c) => { if (c && !ac.signal.aborted)
359
364
  abortFromVerb(); }).catch(() => undefined);
360
365
  }
361
366
  let fleetPub;
362
367
  try {
363
368
  fleetPub = durableTaskId
364
- ? fleetRunPublisher(deps.fleetBus, { runId: durableTaskId, scope: gatedPrincipal(req, deps.config) ?? "default", rootTaskId: durableTaskId, sessionId: prepared.spec.sessionId, ...fleetRunLabels(prepared.spec.objective) })
369
+ ? fleetRunPublisher(deps.fleetBus, { runId: durableTaskId, scope: gatedPrincipal(req, deps.config) ?? "default", rootTaskId: durableTaskId, sessionId: prepared.spec.sessionId, ...fleetRunLabels(prepared.acceptance.objectiveAtSubmit) })
365
370
  : undefined;
371
+ if (fleetPub)
372
+ nameFleetRowOnDelivery(prepared.acceptance, fleetPub);
366
373
  }
367
374
  catch (e) {
368
375
  if (durableHeartbeat)
@@ -499,7 +506,7 @@ export async function handleTasks(req, res, match, ctx) {
499
506
  userMsgEntryId = promptEntryId;
500
507
  const causeAtDone = ev.type === "done" ? ac.cause() : undefined;
501
508
  await deps.userInputLedger?.observeLegEvent(liveStream, ev);
502
- prepared.acceptance.observe(ev);
509
+ await prepared.acceptance.observe(ev);
503
510
  await liveWorkspace.observe(ev);
504
511
  if (ev.type === "done") {
505
512
  finalResult = await mintSubmitExitResult(ev.result, deps.checkpointStore);
@@ -847,7 +854,10 @@ export async function handleTasks(req, res, match, ctx) {
847
854
  };
848
855
  counters.uncountedBillableInflight++;
849
856
  try {
850
- resp = await idemCache.run(inflightKey, async () => withRunReceipt(await runStreamSubmitLeg()));
857
+ const settling = idemCache.run(inflightKey, async () => withRunReceipt(await runStreamSubmitLeg()));
858
+ if (!ranLive)
859
+ runSlot?.release();
860
+ resp = await settling;
851
861
  }
852
862
  catch (e) {
853
863
  deps.logger?.error("stream_error", { closed, err: redactHead(e instanceof Error ? e.message : String(e), 2000) });
@@ -894,14 +904,15 @@ export async function handleTasks(req, res, match, ctx) {
894
904
  if (placementReject)
895
905
  return placementReject;
896
906
  durableTaskId = tid;
907
+ fillRunPreviewOnDelivery(prepared.acceptance, deps.runStore, tid);
897
908
  }
898
909
  if (deps.checkpointStore) {
899
- const ctxRefusal = await persistSubmitCtx(deps.checkpointStore, deps, durableTaskId !== undefined && deps.runStore ? { runStore: deps.runStore, taskId: durableTaskId } : undefined, prepared.spec.sessionId, { body: prepared.body, memoryScope: prepared.auth?.memoryScope, writerVersion: serviceVersion() });
910
+ const ctxRefusal = await persistSubmissionCtx(deps.checkpointStore, deps, durableTaskId !== undefined && deps.runStore ? { runStore: deps.runStore, taskId: durableTaskId } : undefined, prepared);
900
911
  if (ctxRefusal)
901
912
  return ctxRefusal;
902
913
  }
903
914
  commitSessionInheritEnv(deps, prepared.spec.sessionId, prepared.body);
904
- const syncCancelCtrl = createRunAbort();
915
+ const syncCancelCtrl = createRunAbort(principal ?? null);
905
916
  markChildrenStoppedByUserOnAbort(syncCancelCtrl.signal, durableTaskId ?? prepared.spec.sessionId, prepared.auth?.principal);
906
917
  if (durableTaskId && deps.runStore) {
907
918
  const rs = deps.runStore;
@@ -917,11 +928,13 @@ export async function handleTasks(req, res, match, ctx) {
917
928
  }, HEARTBEAT_MS);
918
929
  durableHeartbeat.unref?.();
919
930
  inflightRuns.set(tid, syncCancelCtrl);
931
+ runSlot?.release();
920
932
  void Promise.resolve(rs.isCancelRequested?.(tid, owner)).then((c) => { if (c && !syncCancelCtrl.signal.aborted)
921
933
  abortFromVerb(); }).catch(() => undefined);
922
934
  }
923
935
  else if (durableTaskId) {
924
936
  inflightRuns.set(durableTaskId, syncCancelCtrl);
937
+ runSlot?.release();
925
938
  }
926
939
  const specWithSignal = { ...prepared.spec, signal: syncCancelCtrl.signal, ...(durableTaskId ? { taskId: durableTaskId } : {}) };
927
940
  const observeSyncLegEvent = (ev) => {
@@ -940,7 +953,7 @@ export async function handleTasks(req, res, match, ctx) {
940
953
  const syncWorkspace = createSessionWorkspaceRecorder(sessionWorkspaceStoreOf(deps.sessionStorage), prepared.legFacts?.sessionWitness);
941
954
  for await (const ev of stream) {
942
955
  observeSyncLegEvent(ev);
943
- prepared.acceptance.observe(ev);
956
+ await prepared.acceptance.observe(ev);
944
957
  await syncWorkspace.observe(ev);
945
958
  }
946
959
  return stream.result();
@@ -998,12 +1011,20 @@ export async function handleTasks(req, res, match, ctx) {
998
1011
  noteRunOutcome(result);
999
1012
  return { status: 200, body: withSyncSubmitKeys(buildSyncSubmitBody(exitResult)) };
1000
1013
  };
1001
- const resp = await idemCache.run(inflightKey, async () => withRunReceipt(await runSyncSubmitLeg()));
1014
+ let ranSyncLeg = false;
1015
+ const settling = idemCache.run(inflightKey, async () => {
1016
+ ranSyncLeg = true;
1017
+ return withRunReceipt(await runSyncSubmitLeg());
1018
+ });
1019
+ if (!ranSyncLeg)
1020
+ runSlot?.release();
1021
+ const resp = await settling;
1002
1022
  sendSubmitResult(res, resp);
1003
1023
  }
1004
1024
  return;
1005
1025
  }
1006
1026
  finally {
1027
+ runSlot?.release();
1007
1028
  prepared.peerLease.release();
1008
1029
  }
1009
1030
  }
@@ -102,7 +102,7 @@ export async function handleTraceUsage(req, res, match, ctx) {
102
102
  };
103
103
  const now = Date.now();
104
104
  const from = parseT(q.get("from"), now - 7 * 86_400_000);
105
- const to = parseT(q.get("to"), now);
105
+ const to = parseT(q.get("to"), now + 1);
106
106
  if (Number.isNaN(from) || Number.isNaN(to) || from >= to) {
107
107
  sendError(res, 400, "request.query_invalid", "invalid from/to (ISO-8601 or epoch-ms; from < to)");
108
108
  return;
@@ -0,0 +1,86 @@
1
+ /**
2
+ * 在飞 run 并发帽 —— `RUN_MAX_CONCURRENT`(本副本全局)/ `RUN_MAX_CONCURRENT_PER_PRINCIPAL`(同一已验提交主体),缺省 0 = 关。
3
+ *
4
+ * ## 为什么有这一只
5
+ * 共享一台 GPU / 一只小模型网关的部署,调用方自己限并发不可靠(多个调用方、重试风暴、脚本一把梭)。`RATE_LIMIT_RPM` 是速率桶(每分钟几条),
6
+ * 不是「此刻有几条在跑」;一条 run 可以跑几十分钟,速率桶放行的新提交会在同一台机器上叠起来。本帽数的就是后一个量。
7
+ *
8
+ * ## 数什么
9
+ * **同一份**在飞集:`inflightRuns ∪ steerableRuns`({@link liveRunIdsOf} —— `drainState.inflight` 读的也是它;`isRunLiveOnThisReplica`(`http/server.ts`)是同一并集的逐 id 判,
10
+ * 另写成两只 `has` 的析取,不经本函数;计数只有这一处写法,不另造第二份)。按主体数时读 `inflightRuns` 值上的 `owner`({@link RunAbort.owner} —— 句柄与腿同寿,不另起旁表);
11
+ * 只在 `steerableRuns` 里的键没有属主可读,只计入全局(今天每条可 steer 的腿都同时登记在 `inflightRuns`,这是形式上的保守)。
12
+ * 数的是**本副本**:副本之间不共享,多副本部署的整体并发 = 各副本的帽之和。
13
+ *
14
+ * ## 判点(哪几扇门判、哪几扇不判)
15
+ * 判 = **新提交要起一条 run** 的三个提交门(`routes/runs.ts` 的 `createDurableRun` —— `POST /v1/runs` 与 A2A `message/send` 共用的建行腿、
16
+ * `routes/tasks.ts` 的 `POST /v1/tasks` 与 `POST /v1/tasks/stream`),且排在**幂等重放之后**(同键 / 自带 taskId 的重试拿回自己的回执,不吃新 429)。
17
+ * 不判 = 已有 run 的延续与作答:续跑、唤醒、审批 / 问答作答、`/decide` —— 它们不是新提交,在飞时照样占一格(被数;没有 run 行的老形续跑不登记进在飞集,不被数),
18
+ * 但拒它们会把一张已经在等人的卡卡死。所以帽闸的是**新提交**,不是在飞总数的硬上限:一条停驻的 run 续跑回来时,在飞数可以越过帽。
19
+ * leader 口的后台 run 不在 `inflightRuns` 里(drain 靠 `uncountedBillableInflight` 看见它),本帽不数它也不判它。
20
+ * 位置的两个如实:三个口的帽都排在受理漏斗(`prepareSpec`,可能为新会话做登记)**之后**、建行与 SSE 头之前 —— `POST /v1/runs` 与 A2A 口因自带 `taskId` 的重放在漏斗之后才答、帽必须排在它后面
21
+ * (与 lease / 治理窗的 429 同位),`POST /v1/tasks` 两口与它们同位;拒时漏斗已做的会话登记不撤。同键的并发重复提交若赶在首发还在受理(尚未落行、尚未进在飞合流)的窗里到达,按新提交判,
22
+ * 帽满时与其它准入门(速率 / 配额)同形可能拿到一次 429 + `Retry-After`,按它重投即拿回首发的回执。
23
+ *
24
+ * ## 席位(为什么放行不是「读一下数就完了」)
25
+ * 判完帽到这条 run 登记进 `inflightRuns` 之间隔着若干次 await(建行、执行放置、续跑输入落库 …)。并发一阵提交若都只「读数」,会全部在登记之前读到同一个数、
26
+ * 一起放行 —— 越帽。所以放行即**占一枚席位**({@link RunSlot}),在飞数 = 在飞集 + 未放的席位。席位在这条 run 登记进在飞集的那一拍放掉:
27
+ * 三个提交门同形:建行腿 `mintAndDriveDurableRun` 在 `runInBackground`(返回前同步登记)返回后就地放(`runSlot` 入参),`POST /v1/tasks` / `/v1/tasks/stream` 两腿在各自的
28
+ * `inflightRuns.set` 之后就地放;提交门的 `finally` 总再放一次兜底(早退、被别的门拒、抛 —— `release` 幂等)。登记到放席位之间没有 await,别的提交读不到
29
+ * 「既在集里又占着席位」的中间态,所以不重复计。
30
+ * 两个尾巴:合流进别人在飞建行的重复提交(同键赛过 `peek`)自己不建行 —— `POST /v1/tasks` 一族席位当场放回;`POST /v1/runs`(与 A2A 提交)的席位到这一次提交的整段结清(`.finally`)才放,
31
+ * 合流的那一次要等共享铸件结清,帽边上可多答一次 429;没有 run store 的部署上同步 / 流式腿根本不登记进在飞集
32
+ * (drain 靠 `uncountedBillableInflight` 数它们),它们的席位就活到请求结束 —— 帽照样数得到它们。
33
+ *
34
+ * ## 拒的形
35
+ * 429 `limit.rate_exceeded`(沿用现有限流码,不新铸一枚:消费端的分支同「稍后再来」)+ 体里机读 `reason`(闭集 {@link RATE_EXCEEDED_REASONS},今天一词)
36
+ * + `retryAfterSec`(固定 {@link RUN_CONCURRENCY_RETRY_AFTER_SEC});头 `Retry-After` 与体里同值,同其它限流 / 配额门的发送形(`res.setHeader("retry-after")` + `sendError`)。
37
+ * 拒在建行、认领会话与 SSE 头之前(受理漏斗已做的会话登记见上面「位置的两个如实」)。
38
+ *
39
+ * ## 谁需要 / 谁受伤 / 补偿
40
+ * 需要:共享一台 GPU 或一只小模型网关的家用 / 小团队部署。受伤:超帽者拿 429(新行为,缺省关)。补偿:缺省 0 = 关(升级零变化)+ `Retry-After` 给退避。
41
+ * 无主体请求(无头 service 凭据、`REQUIRE_PRINCIPAL=false` 的单用户部署)只受全局帽 —— 按主体帽对它不判(没有主体可分账)。
42
+ */
43
+ import type { IncomingMessage, ServerResponse } from "node:http";
44
+ import type { RunAbort } from "../run-cancel-context.js";
45
+ import { type RefusalBody } from "./refusal-body.js";
46
+ import type { FlatServiceDeps } from "./server.js";
47
+ /**
48
+ * 本副本在飞 run 的 id 集 —— `inflightRuns`(durable 腿)∪ `steerableRuns`(活流)的**唯一**并集写法:
49
+ * `drainState.inflight`(排空等多久)、`isRunLiveOnThisReplica`(这条 run 是不是在本副本跑)与并发帽读的是同一份。
50
+ * 参数取 `ReadonlyMap`:读法不改登记簿。
51
+ */
52
+ export declare function liveRunIdsOf(inflightRuns: ReadonlyMap<string, unknown>, steerableRuns: ReadonlyMap<string, unknown>): Set<string>;
53
+ /** 并发帽拒的机读 `reason` 词。 */
54
+ export declare const RUN_CONCURRENCY_CAP_REASON: "run_concurrency_cap";
55
+ /**
56
+ * 429 `limit.rate_exceeded` 体上机读 `reason` 的**闭集**(穷举读它;加一种拒因 ⇒ 这里加一词,`switch` 的消费端编译红)。
57
+ * 其余 `limit.rate_exceeded` 的产地(速率桶、赎回准入、MCP 探活、烘焙提交)不带 `reason` —— 读法是「有 `reason` 才分」。
58
+ */
59
+ export declare const RATE_EXCEEDED_REASONS: readonly ["run_concurrency_cap"];
60
+ export type RateExceededReason = (typeof RATE_EXCEEDED_REASONS)[number];
61
+ /** 退避提示:固定 5 秒(是提示不是承诺 —— 在飞的 run 可能再跑很久;帽满只说「稍后再来」)。 */
62
+ export declare const RUN_CONCURRENCY_RETRY_AFTER_SEC = 5;
63
+ /** 一枚席位:放行与登记进在飞集之间占着的那一格。`release` 幂等(登记点放一次,提交门的 `finally` 再放一次兜底)。 */
64
+ export interface RunSlot {
65
+ release(): void;
66
+ }
67
+ /** 并发帽拒的 `{status, body, retryAfterSec}`(体经全仓唯一组句器;状态与退避供发送口用)。 */
68
+ export declare function runConcurrencyRefusalOf(): {
69
+ readonly status: 429;
70
+ readonly body: RefusalBody;
71
+ readonly retryAfterSec: number;
72
+ };
73
+ export interface RunConcurrencyGate {
74
+ /**
75
+ * 提交门的并发帽准入:放行 ⇒ 交回席位(调用点登记那一拍 / `finally` 放);超帽 ⇒ 已应答 429(+ `Retry-After`),交回 `undefined`,调用点 `return`。
76
+ * 位序:在幂等重放、`rateLimited` / `quotaExceeded` / `leaseDenied` / `usageWindowDenied` 之后,任何建行 / 认领 / SSE 头之前。
77
+ */
78
+ admitRun(req: IncomingMessage, res: ServerResponse): RunSlot | undefined;
79
+ }
80
+ /** 装配口:`deps` 只读 `config`(两旋钮,每次判时现读)与 `logger`;在飞集由 server 装配层交来(两张登记簿本身,不拷)。 */
81
+ export declare function createRunConcurrencyGate(ctx: {
82
+ deps: Pick<FlatServiceDeps, "config" | "logger">;
83
+ inflightRuns: ReadonlyMap<string, RunAbort>;
84
+ steerableRuns: ReadonlyMap<string, unknown>;
85
+ }): RunConcurrencyGate;
86
+ //# sourceMappingURL=run-concurrency-cap.d.ts.map
@@ -0,0 +1,54 @@
1
+ import { gatedPrincipal } from "./principal-gate.js";
2
+ import { refusalBody } from "./refusal-body.js";
3
+ import { sendError } from "./send.js";
4
+ export function liveRunIdsOf(inflightRuns, steerableRuns) {
5
+ return new Set([...inflightRuns.keys(), ...steerableRuns.keys()]);
6
+ }
7
+ export const RUN_CONCURRENCY_CAP_REASON = "run_concurrency_cap";
8
+ export const RATE_EXCEEDED_REASONS = [RUN_CONCURRENCY_CAP_REASON];
9
+ export const RUN_CONCURRENCY_RETRY_AFTER_SEC = 5;
10
+ const UNTRACKED_SLOT = { release: () => undefined };
11
+ export function runConcurrencyRefusalOf() {
12
+ return {
13
+ status: 429,
14
+ body: refusalBody({
15
+ errorCode: "limit.rate_exceeded",
16
+ message: "too many runs are in flight on this server right now — nothing was started; retry after the Retry-After interval",
17
+ facts: { reason: RUN_CONCURRENCY_CAP_REASON, retryAfterSec: RUN_CONCURRENCY_RETRY_AFTER_SEC },
18
+ }),
19
+ retryAfterSec: RUN_CONCURRENCY_RETRY_AFTER_SEC,
20
+ };
21
+ }
22
+ export function createRunConcurrencyGate(ctx) {
23
+ const { deps, inflightRuns, steerableRuns } = ctx;
24
+ const seats = new Set();
25
+ function admitRun(req, res) {
26
+ const { runMaxConcurrent: globalCap, runMaxConcurrentPerPrincipal: ownCap } = deps.config;
27
+ if (globalCap === 0 && ownCap === 0)
28
+ return UNTRACKED_SLOT;
29
+ const owner = gatedPrincipal(req, deps.config);
30
+ const live = liveRunIdsOf(inflightRuns, steerableRuns);
31
+ const inFlight = live.size + seats.size;
32
+ let scope;
33
+ if (globalCap > 0 && inFlight >= globalCap)
34
+ scope = "global";
35
+ else if (ownCap > 0 && owner !== undefined) {
36
+ const holders = [...[...live].map((id) => inflightRuns.get(id)?.owner), ...[...seats].map((seat) => seat.owner)];
37
+ if (holders.filter((o) => o === owner).length >= ownCap)
38
+ scope = "principal";
39
+ }
40
+ if (scope !== undefined) {
41
+ deps.logger?.info?.("run_concurrency_cap_refused", { scope, inFlight, cap: scope === "global" ? globalCap : ownCap, hasPrincipal: owner !== undefined });
42
+ const refusal = runConcurrencyRefusalOf();
43
+ const { error, errorCode, ...extra } = refusal.body;
44
+ res.setHeader("retry-after", String(refusal.retryAfterSec));
45
+ sendError(res, refusal.status, errorCode, error, extra);
46
+ return undefined;
47
+ }
48
+ const seat = { owner };
49
+ seats.add(seat);
50
+ return { release: () => void seats.delete(seat) };
51
+ }
52
+ return { admitRun };
53
+ }
54
+ //# sourceMappingURL=run-concurrency-cap.js.map