@sema-agent/server 7.43.0 → 7.44.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 (345) hide show
  1. package/USAGE.md +18 -0
  2. package/dist/adoption/plan.js +31 -192
  3. package/dist/adoption/quiesce.js +3 -86
  4. package/dist/adoption/runner.js +3 -140
  5. package/dist/adoption/sql.js +0 -74
  6. package/dist/adoption/wire.js +0 -80
  7. package/dist/approval-ask-machine.js +0 -75
  8. package/dist/approval-card.js +0 -323
  9. package/dist/approval-deny-reasons.js +0 -42
  10. package/dist/approval-hmac.js +0 -35
  11. package/dist/approval-reconciler.js +8 -166
  12. package/dist/approval.js +4 -67
  13. package/dist/audit.js +1 -44
  14. package/dist/auth-bridge.js +4 -47
  15. package/dist/auth-keys.js +0 -23
  16. package/dist/bake-runner/main.js +4 -65
  17. package/dist/bake-runner/protocol.js +3 -58
  18. package/dist/bake-runner/runner.js +5 -91
  19. package/dist/bench/l8/artifact.js +2 -61
  20. package/dist/bench/l8/escape.js +0 -25
  21. package/dist/bench/l8/index.js +0 -14
  22. package/dist/bench/l8/probes.js +0 -60
  23. package/dist/bench/l8/run-probes.js +2 -45
  24. package/dist/bench/s1/arms.js +14 -189
  25. package/dist/bench/s1/live-deps.js +11 -253
  26. package/dist/bench/s1/oracle.js +1 -36
  27. package/dist/bench/s1/repair-oracle-adapter.js +0 -30
  28. package/dist/bench/s1/reviewer.js +0 -34
  29. package/dist/bench/s1/row.js +0 -62
  30. package/dist/bench/s1/run-firm.js +4 -70
  31. package/dist/bench/s1/runner-ctx.js +0 -40
  32. package/dist/bench/s1/tasks.js +0 -115
  33. package/dist/boot/adoption.js +0 -20
  34. package/dist/boot/budget-tracing.js +3 -55
  35. package/dist/boot/config-center.js +43 -551
  36. package/dist/boot/coordinators.js +1 -69
  37. package/dist/boot/crash-last.js +0 -18
  38. package/dist/boot/deferred-sandbox-path-env.js +3 -119
  39. package/dist/boot/execution-env.js +5 -171
  40. package/dist/boot/governance-seams.js +0 -92
  41. package/dist/boot/leader.js +0 -69
  42. package/dist/boot/limit-sync.js +0 -2
  43. package/dist/boot/memory-boundary.js +3 -91
  44. package/dist/boot/org-memory.js +1 -25
  45. package/dist/boot/parked-revive-gate.js +0 -150
  46. package/dist/boot/permission-rules-audit.js +1 -56
  47. package/dist/boot/reapers.js +15 -273
  48. package/dist/boot/resolve-spec.js +9 -768
  49. package/dist/boot/retention-lane.js +4 -87
  50. package/dist/boot/runner-deps.js +3 -297
  51. package/dist/boot/runtime-caps.js +0 -40
  52. package/dist/boot/session-faces.js +2 -133
  53. package/dist/boot/shutdown.js +9 -83
  54. package/dist/boot/side-query-lane.js +2 -137
  55. package/dist/boot/stores.js +15 -215
  56. package/dist/boot/task-list-lane.js +0 -18
  57. package/dist/boot/webfetch-summarize-lane.js +0 -47
  58. package/dist/boot/workflow-orchestration.js +3 -99
  59. package/dist/boot-reclaim.js +0 -20
  60. package/dist/bounded-session-map.js +0 -19
  61. package/dist/brain.js +2 -139
  62. package/dist/budget.js +9 -244
  63. package/dist/capabilities/builtin-tools.js +0 -3
  64. package/dist/capabilities/center-plugins.js +6 -51
  65. package/dist/capabilities/center-prompts.js +6 -71
  66. package/dist/capabilities/code-review-council.js +7 -40
  67. package/dist/capabilities/collab-workflows.js +1 -44
  68. package/dist/capabilities/hands-lane.js +0 -65
  69. package/dist/capabilities/memory-notice.js +0 -70
  70. package/dist/capabilities/prompt.js +0 -14
  71. package/dist/capabilities/prompts/code-review.js +0 -14
  72. package/dist/capabilities/prompts/identity.js +0 -6
  73. package/dist/capabilities/prompts/team.js +0 -4
  74. package/dist/capabilities/repo-tools.js +0 -34
  75. package/dist/capabilities/sandbox-file-send.js +3 -92
  76. package/dist/capabilities/scenarios.d.ts +0 -1
  77. package/dist/capabilities/scenarios.js +18 -279
  78. package/dist/capabilities/select-environment-tool.js +0 -32
  79. package/dist/capabilities/send-user-file-tool.js +2 -66
  80. package/dist/capabilities/skills.d.ts +1 -2
  81. package/dist/capabilities/skills.js +3 -21
  82. package/dist/capabilities/team.d.ts +3 -11
  83. package/dist/capabilities/team.js +3 -51
  84. package/dist/capabilities/tool-defer.js +0 -3
  85. package/dist/config-center/apply-effective.js +15 -330
  86. package/dist/config-center/apply-ledger.js +5 -38
  87. package/dist/config-center/facade.js +0 -41
  88. package/dist/config-center/hot-keys-registry.js +0 -20
  89. package/dist/config-center/http-client.js +2 -125
  90. package/dist/config-center/mcp-revocation.js +2 -34
  91. package/dist/config-center/read-face.js +0 -59
  92. package/dist/config-center/restart-signal.js +1 -85
  93. package/dist/config-center/skills-mcp.d.ts +1 -1
  94. package/dist/config-center/skills-mcp.js +9 -76
  95. package/dist/config-center/stage-limits.js +8 -35
  96. package/dist/config-invariants.js +0 -16
  97. package/dist/config-lkg.js +0 -42
  98. package/dist/config-provider.js +3 -186
  99. package/dist/config-types.js +0 -5
  100. package/dist/config.js +107 -1145
  101. package/dist/degenerate-instrument.js +3 -67
  102. package/dist/deployment-governance.js +0 -124
  103. package/dist/digest-form.js +0 -12
  104. package/dist/elicitation.js +3 -86
  105. package/dist/env-facts.js +7 -75
  106. package/dist/fleet/fleet-bus.js +34 -507
  107. package/dist/fleet/fleet-reconciler.js +9 -149
  108. package/dist/fleet/fleet-terminal-window.js +10 -178
  109. package/dist/fleet/subagent-tail-bus.js +3 -72
  110. package/dist/fleet-client.js +10 -70
  111. package/dist/fleet-lease.js +5 -79
  112. package/dist/git-api-kind.js +0 -3
  113. package/dist/governance-ask-marks.js +2 -78
  114. package/dist/hooks/branch-transcript.js +0 -74
  115. package/dist/hooks/cc-agent-hook-prompt.js +0 -29
  116. package/dist/hooks/cc-stop-prompt.js +1 -46
  117. package/dist/hooks/hook-llm.js +1 -53
  118. package/dist/hooks/hook-runner.js +20 -414
  119. package/dist/http/active-run-conflict.js +4 -129
  120. package/dist/http/cursor-fingerprint.d.ts +5 -0
  121. package/dist/http/cursor-fingerprint.js +5 -0
  122. package/dist/http/idempotency.js +0 -37
  123. package/dist/http/principal-gate.js +3 -40
  124. package/dist/http/route-ctx.js +0 -9
  125. package/dist/http/routes/a2a-serve.js +7 -319
  126. package/dist/http/routes/admin-config-refresh.js +0 -4
  127. package/dist/http/routes/admin-drain.js +0 -10
  128. package/dist/http/routes/adoption.js +1 -29
  129. package/dist/http/routes/agents-roster.js +1 -47
  130. package/dist/http/routes/approvals-assistant.js +32 -395
  131. package/dist/http/routes/attachments.js +4 -22
  132. package/dist/http/routes/capabilities.js +3 -420
  133. package/dist/http/routes/diagnostics.js +1 -81
  134. package/dist/http/routes/fleet.js +9 -185
  135. package/dist/http/routes/images.js +14 -239
  136. package/dist/http/routes/leader.js +0 -13
  137. package/dist/http/routes/memory-bundle.js +1 -61
  138. package/dist/http/routes/memory-policy.js +9 -99
  139. package/dist/http/routes/notify-wake.js +3 -37
  140. package/dist/http/routes/observability.js +2 -19
  141. package/dist/http/routes/retention-ops.js +2 -34
  142. package/dist/http/routes/rules.js +1 -93
  143. package/dist/http/routes/runs.js +64 -873
  144. package/dist/http/routes/session-sync.js +19 -258
  145. package/dist/http/routes/sessions-list.js +17 -43
  146. package/dist/http/routes/sessions.js +34 -226
  147. package/dist/http/routes/shared-memory.js +5 -36
  148. package/dist/http/routes/side-query.js +1 -87
  149. package/dist/http/routes/tasks.js +52 -722
  150. package/dist/http/routes/trace-usage.js +32 -195
  151. package/dist/http/routes/workflows.js +19 -195
  152. package/dist/http/run-meta.js +0 -6
  153. package/dist/http/send.js +0 -32
  154. package/dist/http/server.js +92 -1597
  155. package/dist/http/sse-lifecycle.js +2 -13
  156. package/dist/http/sse-log.js +3 -48
  157. package/dist/http/tar.js +5 -21
  158. package/dist/http/verify-rounds.js +0 -5
  159. package/dist/http/wire-gate.js +0 -9
  160. package/dist/http/workspace-content.js +0 -10
  161. package/dist/images/bake-validate.js +1 -70
  162. package/dist/images/manifest.js +1 -6
  163. package/dist/index.js +0 -21
  164. package/dist/key-resolver.js +2 -17
  165. package/dist/leader/diffout.js +1 -20
  166. package/dist/leader/diffup.js +0 -47
  167. package/dist/leader/endpoint.js +2 -57
  168. package/dist/leader/fanout.js +3 -45
  169. package/dist/leader/grader-env-factory.js +3 -72
  170. package/dist/leader/leader.js +5 -156
  171. package/dist/leader/merge.js +8 -115
  172. package/dist/leader/planner.js +3 -54
  173. package/dist/leader/repair-oracle.js +1 -60
  174. package/dist/leader/repair-wire.js +2 -79
  175. package/dist/leader/wire.js +8 -307
  176. package/dist/lsp/e2b-bridge.js +4 -64
  177. package/dist/lsp/e2b-manager.js +6 -94
  178. package/dist/lsp/lsp-frames.js +0 -12
  179. package/dist/lsp/manager.js +4 -96
  180. package/dist/lsp/ws-transport.js +5 -55
  181. package/dist/lsp-evict.js +1 -15
  182. package/dist/main.js +57 -765
  183. package/dist/memory-bundle-engine.js +0 -55
  184. package/dist/memory-export.js +0 -4
  185. package/dist/memory-posture.js +1 -15
  186. package/dist/memory-scope.js +11 -148
  187. package/dist/memory-sync-client.js +2 -44
  188. package/dist/memory-sync.js +1 -80
  189. package/dist/model-select.js +3 -80
  190. package/dist/observability/cost-quota.js +1 -17
  191. package/dist/observability/cost-taxonomy.js +0 -34
  192. package/dist/observability/fail-open.js +7 -86
  193. package/dist/observability/logger.js +0 -6
  194. package/dist/observability/metrics.js +0 -94
  195. package/dist/observability/otel-exporter.js +3 -13
  196. package/dist/observability/principal-context.js +0 -9
  197. package/dist/observability/prompt-manifest.js +1 -37
  198. package/dist/observability/rate-limit.js +0 -4
  199. package/dist/observability/secret-env-scrub.js +2 -56
  200. package/dist/observability/tool-trace.js +1 -70
  201. package/dist/orchestration/hardened-vm-runner.js +4 -118
  202. package/dist/orchestration/hardened-vm-worker-runner.js +1 -26
  203. package/dist/orchestration/hardened-vm-worker.js +0 -27
  204. package/dist/orchestration/subagent-steer.js +1 -45
  205. package/dist/orchestration/workflow-agent-steer.js +1 -80
  206. package/dist/orchestration/workflow-completion-inbox.js +32 -285
  207. package/dist/orchestration/workflow-notify-journal.js +16 -259
  208. package/dist/org-memory-admission.js +3 -47
  209. package/dist/parent-watch.js +2 -48
  210. package/dist/parked-decide.js +1 -109
  211. package/dist/per-task-image.js +0 -57
  212. package/dist/plan-cache-probe.js +3 -27
  213. package/dist/plugins/adoption-log-sql.js +2 -119
  214. package/dist/plugins/approval-ask-store-memory.js +3 -38
  215. package/dist/plugins/approval-ask-store-sql.js +6 -188
  216. package/dist/plugins/approval-exemption-store.js +2 -28
  217. package/dist/plugins/background-agent-store-sql.js +4 -105
  218. package/dist/plugins/background-shell-support.js +14 -122
  219. package/dist/plugins/blob-backend.js +6 -169
  220. package/dist/plugins/breaker-state-sql.js +8 -46
  221. package/dist/plugins/caching-session-store.js +4 -106
  222. package/dist/plugins/checkpoint-store-sql.js +18 -505
  223. package/dist/plugins/e2b-orphan-reclaim.js +0 -45
  224. package/dist/plugins/file-outcome-sink.js +0 -9
  225. package/dist/plugins/file-resume-anchor-store.js +4 -43
  226. package/dist/plugins/file-run-store.js +26 -364
  227. package/dist/plugins/file-snapshot-store-sql.js +10 -181
  228. package/dist/plugins/fork-routing-session-store.js +8 -111
  229. package/dist/plugins/host-platform.js +2 -91
  230. package/dist/plugins/image-bake-store-sql.js +7 -250
  231. package/dist/plugins/image-index-sql.js +4 -123
  232. package/dist/plugins/k8s-bg-scripts.js +4 -88
  233. package/dist/plugins/k8s-exec-protocol.js +0 -41
  234. package/dist/plugins/leader-run-store-sql.js +0 -101
  235. package/dist/plugins/local-checkpoint-store.js +8 -128
  236. package/dist/plugins/local-session-store.js +34 -296
  237. package/dist/plugins/local-task-attachment-store.js +2 -16
  238. package/dist/plugins/mailbox-store-sql.js +8 -65
  239. package/dist/plugins/memory-embedder-fingerprint.js +5 -166
  240. package/dist/plugins/memory-embedder.js +4 -70
  241. package/dist/plugins/memory-engine-pg.js +6 -170
  242. package/dist/plugins/memory-engine-tidb.js +7 -155
  243. package/dist/plugins/memory-engine-vector-util.js +0 -10
  244. package/dist/plugins/memory-key-guards.js +0 -34
  245. package/dist/plugins/memory-origin-law.js +0 -187
  246. package/dist/plugins/memory-resume-anchor-store.js +0 -17
  247. package/dist/plugins/memory-run-store.js +12 -89
  248. package/dist/plugins/memory-session-policy-store.js +0 -17
  249. package/dist/plugins/memory-sync-store-pg.js +4 -49
  250. package/dist/plugins/memory-sync-store-tidb.js +3 -35
  251. package/dist/plugins/outcome-ledger-sql.js +3 -97
  252. package/dist/plugins/permission-rule-store-file.js +5 -133
  253. package/dist/plugins/permission-rule-store-sql.d.ts +3 -0
  254. package/dist/plugins/permission-rule-store-sql.js +50 -307
  255. package/dist/plugins/pg-cost-quota.js +0 -7
  256. package/dist/plugins/pg-pool.js +0 -92
  257. package/dist/plugins/pg-rate-limiter.js +2 -13
  258. package/dist/plugins/pg-safe-json.js +4 -40
  259. package/dist/plugins/pg-session-storage.js +25 -189
  260. package/dist/plugins/posix-shell-fs.js +1 -31
  261. package/dist/plugins/remote-env-adb.js +12 -101
  262. package/dist/plugins/remote-env-e2b.js +40 -371
  263. package/dist/plugins/remote-env-file-error.js +0 -37
  264. package/dist/plugins/remote-env-host.js +63 -483
  265. package/dist/plugins/remote-env-k8s.js +32 -326
  266. package/dist/plugins/remote-env-local-docker.js +24 -157
  267. package/dist/plugins/remote-env-ssh.js +20 -128
  268. package/dist/plugins/remote-scratchpad.js +2 -32
  269. package/dist/plugins/remote-shell.js +1 -32
  270. package/dist/plugins/resume-anchor-store-sql.js +0 -11
  271. package/dist/plugins/retention-lane-store-sql.js +0 -108
  272. package/dist/plugins/retention-store-sql.js +4 -383
  273. package/dist/plugins/roster-store-sql.js +0 -55
  274. package/dist/plugins/run-store-sql.js +14 -251
  275. package/dist/plugins/s3-presign.js +2 -49
  276. package/dist/plugins/scheduler-support.js +3 -80
  277. package/dist/plugins/send-file-ledger.js +4 -53
  278. package/dist/plugins/send-user-file.js +4 -94
  279. package/dist/plugins/session-placement.js +1 -89
  280. package/dist/plugins/session-policy-store-sql.js +3 -81
  281. package/dist/plugins/session-store.js +0 -57
  282. package/dist/plugins/shared-memory-store-sql.js +4 -167
  283. package/dist/plugins/sql-driver.js +0 -17
  284. package/dist/plugins/sql-errors.js +0 -7
  285. package/dist/plugins/sql-escape.js +0 -8
  286. package/dist/plugins/sql-row-helpers.js +0 -25
  287. package/dist/plugins/store-backend.js +42 -222
  288. package/dist/plugins/store-contracts.js +2 -46
  289. package/dist/plugins/task-attachment-store.js +3 -49
  290. package/dist/plugins/task-list-store-sql.js +0 -79
  291. package/dist/plugins/tidb-cost-quota.js +1 -4
  292. package/dist/plugins/tidb-pool.js +1 -207
  293. package/dist/plugins/tidb-rate-limiter.js +3 -9
  294. package/dist/plugins/tidb-session-storage.js +4 -70
  295. package/dist/plugins/tidb-session-store.js +19 -341
  296. package/dist/plugins/tool-result-store-sql.js +4 -179
  297. package/dist/plugins/usage-window-store-sql.js +0 -8
  298. package/dist/plugins/web-search.js +10 -132
  299. package/dist/plugins/workflow-journal-store-sql.js +2 -58
  300. package/dist/plugins/workflow-run-store-sql.js +6 -91
  301. package/dist/plugins/worktree-isolation.js +6 -126
  302. package/dist/plugins/write-behind-counter.js +16 -75
  303. package/dist/principal-jwt.js +5 -60
  304. package/dist/project-memory.js +15 -146
  305. package/dist/prompts-domain-validate.js +1 -51
  306. package/dist/question.js +2 -111
  307. package/dist/resource-suspend.js +0 -18
  308. package/dist/router/route-orchestration.js +0 -77
  309. package/dist/rules-consent.d.ts +44 -5
  310. package/dist/rules-consent.js +75 -215
  311. package/dist/run-local.js +14 -381
  312. package/dist/runs.js +29 -617
  313. package/dist/runtime-caps-resolver.js +6 -132
  314. package/dist/runtime-governance.js +1 -232
  315. package/dist/sandbox-pkg-source.js +0 -37
  316. package/dist/sealed-key.js +3 -68
  317. package/dist/security.js +6 -291
  318. package/dist/session-leaf-bus.js +0 -32
  319. package/dist/session-sync-content.js +1 -69
  320. package/dist/session-sync-kernel.js +3 -60
  321. package/dist/session-sync.js +3 -66
  322. package/dist/session-titler.js +7 -41
  323. package/dist/session-watch.js +9 -73
  324. package/dist/shared-memory-scope-authorizer.js +0 -18
  325. package/dist/sighup-idle.js +1 -12
  326. package/dist/spec-fields.js +4 -143
  327. package/dist/store-live-probe.js +3 -49
  328. package/dist/task-a2a.js +1 -126
  329. package/dist/task-cwd.js +1 -103
  330. package/dist/task-mcp.js +1 -89
  331. package/dist/task-settings.js +10 -300
  332. package/dist/task-workflow.js +8 -75
  333. package/dist/tool-approval.d.ts +28 -1
  334. package/dist/tool-approval.js +70 -1430
  335. package/dist/trace/artifacts.js +5 -20
  336. package/dist/trace/engine-notice-wire.js +6 -138
  337. package/dist/trace/ledger-sink.js +7 -96
  338. package/dist/trace/project.js +4 -448
  339. package/dist/trace/redact.js +9 -77
  340. package/dist/turn-activity.js +1 -27
  341. package/dist/usage-analytics.js +4 -34
  342. package/dist/wall-clock-jump-guard.js +1 -68
  343. package/package.json +3 -3
  344. package/dist/capabilities/scenario-alias.d.ts +0 -27
  345. package/dist/capabilities/scenario-alias.js +0 -61
@@ -5,23 +5,13 @@ export async function handleLeader(req, res, url, ctx) {
5
5
  await handleLeaderBody(req, res, url, ctx, miss);
6
6
  return !miss.fell;
7
7
  }
8
- /** 路由体 = 从 `server.ts` 的 `handle()` 里**整段剪切**过来的原文(唯一改动:统一去缩进两格)。
9
- * 注意本域的落空语义:`deps.leaderEndpoint` 在但 URL 不匹配、或 endpoint 返回 undefined,都要**继续**
10
- * 往下走(原文的 if 块不 return),故只有真的应答过才算已处理——由末尾的 `miss.fell` 如实表达。 */
11
8
  async function handleLeaderBody(req, res, url, ctx, miss) {
12
9
  const { deps } = ctx;
13
10
  const { readJson, rateLimited, quotaExceeded, leaseDenied } = ctx.helpers;
14
- // v2 leader endpoint (design/50): POST /v1/leader (async, like /v1/runs) + GET /v1/leader/:id.
15
11
  if (deps.leaderEndpoint) {
16
12
  const isLeaderPost = req.method === "POST" && url === "/v1/leader";
17
13
  const isLeaderGet = req.method === "GET" && /^\/v1\/leader\/[^/]+$/.test(url);
18
14
  if (isLeaderPost || isLeaderGet) {
19
- // BL-4: gate the leader route through the SAME identity layer as /v1/runs — previously it skipped
20
- // authorize/requirePrincipal entirely and recorded no owner, so any service-token holder could submit
21
- // and read ANY tenant's leader run. Enforce requirePrincipal (401 before any work), then pass the
22
- // resolved principal so the endpoint records it as the run owner (POST) and 404s non-owners (GET).
23
- // 🔴 F-fix: VERIFIED identity (gatedPrincipal) for the leader-run owner — a spoofable header would let a
24
- // direct-door caller submit/read another tenant's leader run (the recorded owner gates the GET 404).
25
15
  if (deps.config.requirePrincipal && !gatedPrincipal(req, deps.config)) {
26
16
  sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
27
17
  return;
@@ -38,9 +28,6 @@ async function handleLeaderBody(req, res, url, ctx, miss) {
38
28
  sendError(res, 400, "request.invalid_json", "invalid JSON body");
39
29
  return;
40
30
  }
41
- // #193 车4 件1:`Idempotency-Key` 头接到 leader 的 durable 提交幂等上(与 POST /v1/runs、
42
- // POST /v1/images/bakes 同一条头名)。原始头值原样递给 endpoint —— 按提交者 scope + 定宽
43
- // hash 都发生在那一层(`leaderIdemKey`),路由层不做语义加工。
44
31
  const r = await deps.leaderEndpoint.handle("POST", url, body, requester, headerStr(req.headers["idempotency-key"]) ?? null);
45
32
  if (r) {
46
33
  sendJson(res, r.status, r.body);
@@ -3,29 +3,8 @@ import { sendJson, sendError } from "../send.js";
3
3
  import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
4
4
  export const MEMORY_BUNDLE_EXPORT_PATH = "/v1/memory/export";
5
5
  export const MEMORY_BUNDLE_IMPORT_PATH = "/v1/memory/import";
6
- /** export 请求体 —— **形**验(义归 core;见文件头「验型的分权」)。 */
7
6
  const ExportRequestSchema = z.object({ scopes: z.array(z.string().min(1)).min(1) });
8
- /** import 请求体 —— 只断言 `bundle` 是个**对象**;整包判决是 core 的单一属主面。
9
- * `z.record(z.string(), z.unknown())` 而不是 `z.object({...})`:在本层写出 bundle 的字段表就是复制
10
- * core 的 schema(第二真源),而 `z.unknown()` 的 record 恰好表达「结构由下游判」。 */
11
7
  const ImportRequestSchema = z.object({ bundle: z.record(z.string(), z.unknown()) });
12
- /**
13
- * core 抛出的**已分类**错误 → HTTP 状态。闭集穷举 `switch`:core 新增一个码 ⇒ 这里落 `undefined`
14
- * ⇒ 走 500 fail-loud(而不是被某个默认臂静默塞进 4xx)。安全轴上的静默默认值正是本仓禁止的东西。
15
- *
16
- * · `config.memory_{export,import}_request` = **调用方**给的请求参数不合法(非空/去重/可序列化)⇒ 400;
17
- * · `memory.export_incomplete` = 店**声明自己说不清**(chain-degraded / 无导出复合面)⇒ 409:不是请求
18
- * 的错、也不是服务器故障,是**当前状态**下这个包铸不出来(去修 chain 或换后端后重试即改判);
19
- * · `memory.import_rejected` = 请求**形**合法但**内容**不可接受(整包拒批,零落地)⇒ 422 —— 与 400
20
- * 分家是承重的:400 说"你的 HTTP 请求写错了",422 说"包本身被判不合格",两句话的运维动作不同。
21
- *
22
- * 🔴 每一支都把码**再写一遍字面量**(而不是 `sendError(res, status, code, …)` 把变量透下去):错误码
23
- * 目录门(test/error-code-catalog-live.test.ts)是**扫源码字面量**的,变量态的码整条对它隐形 ⇒ 这四个
24
- * 码会「wire 上发得出来、附录 A 里查不到」。同门另有一张中继登记表可走,但那张表是**有上限的棘轮**
25
- * (「码不写字面量」不许变成默认写法),而这里本来就该是一张闭集词表 —— 写字面量既过门又把闭集写实。
26
- */
27
- // ⚠️ 形参叫 `coreCode` 而不是 `code`:错误体自 core 3.0.0 起**只有** `errorCode`,而 test/error-code-key-gate
28
- // 扫的是「还在发 legacy `code` 键」的站点(文本判据)——一个叫 `code` 的形参会被它当成那种站点收走。
29
8
  function sentCoreCode(res, coreCode, message) {
30
9
  switch (coreCode) {
31
10
  case "config.memory_export_request":
@@ -44,36 +23,19 @@ function sentCoreCode(res, coreCode, message) {
44
23
  return false;
45
24
  }
46
25
  }
47
- /** core 的抛出形 = `Error` + `.code`(engine.js 逐字)。裸 as-cast 禁令下的判别式读法。 */
48
26
  function codeOf(err) {
49
27
  if (!(err instanceof Error))
50
28
  return undefined;
51
29
  const { code } = err;
52
30
  return typeof code === "string" ? code : undefined;
53
31
  }
54
- /**
55
- * 引擎调用的统一收尾:已分类的码**原样**上 wire(码与 core 的诊断文案都不改写 —— 那句文案是运维排障
56
- * 的第一手材料,判据帖 G3 就断言它含 "chain-degraded");未分类的一律 500 且**不回显**内部文本。
57
- * 返回 true = 已应答。
58
- */
59
32
  function sentClassifiedFailure(res, ctx, face, err) {
60
33
  const code = codeOf(err);
61
34
  if (code !== undefined && sentCoreCode(res, code, err instanceof Error ? err.message : String(err)))
62
35
  return true;
63
36
  ctx.deps.logger?.warn?.(`memory_bundle_${face}_failed`, { err: String(err) });
64
- return false; // 未分类 ⇒ 调用方发它自己那句 500(见下面两处的**内联字面量**)
37
+ return false;
65
38
  }
66
- /**
67
- * `MemoryImportReport` → wire 的**显式白名单**投影。
68
- *
69
- * 🔴 为什么是显式白名单而不是整行透传:报告是**治理处置**的清单,core 日后加一个新的处置座(例如又一
70
- * 类"被扣下的行"),整行透传会让它**静默**上 wire 而没人给它写过契约;白名单让它变成**编译错误**
71
- * (`test/memory-bundle-http.test.ts` 的差集断言 = `wire-whitelist-exhaustiveness` 同款门形)。
72
- * 今天**每一个**键都在场:每一条都是 operator 判「这次导入到底发生了什么」必须看到的东西,一个都
73
- * 不该省 —— 「每个否定处置都被报告,绝不静默」是 core 对这份报告的原话。
74
- */
75
- // 🔴 **不要**给这个函数标 `: Record<string, unknown>` 返回型:那会让 `keyof ReturnType<…>` 塌成 `string`,
76
- // 上面那道编译期差集门当场变成**恒真**(vacuous)—— 写这条钉时正踩到过。返回型交给推断,键集才是字面的。
77
39
  export function buildImportReportWire(r) {
78
40
  return {
79
41
  v: r.v,
@@ -107,35 +69,21 @@ async function handleMemoryBundleBody(req, res, url, ctx, miss) {
107
69
  miss.fell = true;
108
70
  return;
109
71
  }
110
- // ── 身份(401)── direct-door safe:验证过的身份,不是可伪造的头。
111
72
  const principal = gatedPrincipal(req, deps.config);
112
73
  if (deps.config.requirePrincipal && !principal) {
113
74
  sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
114
75
  return;
115
76
  }
116
- // ── 授权(403)── 治理面 fail-closed,与 adoption / retention-ops 同一条门。
117
- // ⚠️ 文案写**内联字面量**而不是抽个模块常量:文案冻结门(test/api-error-text-freeze.test.ts)是**扫源码
118
- // 文本**的,把 message 换成一个常量名的写法它结构性够不到 ⇒ 这句 wire 文案就没有被冻住。
119
- // ⚠️ 本段注释里也不许照抄一个带状态码与引号码名的调用样例 —— 那道门的提取器会把**注释里**的样例
120
- // 当成一条真站点收走(写这条注时真的踩到过:凭空多出一个 `403:code` 对)。
121
- // 两口同受众同理由,所以两处逐字同一句(族断言按去重集合比,同串出现两次不改判)。
122
77
  if (!explicitOperatorOk(principal, deps.config.operatorPrincipals)) {
123
78
  sendError(res, 403, "auth.operator_only", "memory governance bundles are an operator-only deployment action");
124
79
  return;
125
80
  }
126
- // ── 能力(501)── 在授权之后:部署形态不对越权者泄漏。
127
- // ⚠️ 与 `capability.memory_store_required`(GET 导出面要 DB 平面)**刻意不合并**:两码的运维动作相反
128
- // (那条要 pg|tidb,本条要引擎在场且后端带 bundle 复合面),消费端真会分支——附录 A 判据②。
129
- // 🔴 缺席有**两个**成因,文案两个都点名(codex [medium] 后修:此前只说「引擎没接线」,而 pg/tidb 部署
130
- // 是引擎接了、后端没那两个可选面 —— 只说一半会让运维去查一个根本没问题的旋钮)。两因同码:消费端
131
- // 的分支一样(本部署做不了 bundle,换部署形态),分成两码只会让码表长一倍而判据不变(判据②)。
132
81
  const engineExport = deps.memoryBundleExport;
133
82
  const engineImport = deps.memoryBundleImport;
134
83
  if (engineExport === undefined || engineImport === undefined) {
135
84
  sendError(res, 501, "capability.memory_engine_required", "memory governance bundles are not available on this deployment — they require a wired memory engine (MEMORY_ENGINE=on) whose backend implements the v2-c bundle composite (exportSnapshotOf / importBundleCommit); the file memory backend does, the pg/tidb memory backends do not");
136
85
  return;
137
86
  }
138
- // ── 验型(400)── 体读的 413/400(超长 / 坏 JSON)由顶层 typed catch 统一处理(鉴权已完成)。
139
87
  const raw = await ctx.helpers.readJson(req);
140
88
  if (isExport) {
141
89
  const parsed = ExportRequestSchema.safeParse(raw);
@@ -144,14 +92,10 @@ async function handleMemoryBundleBody(req, res, url, ctx, miss) {
144
92
  return;
145
93
  }
146
94
  try {
147
- // 🔴 bundle **原样**下发(含 `doc` 披露段)—— 投影它就是替铸包方复述披露,见文件头。
148
95
  const bundle = await engineExport(parsed.data.scopes);
149
96
  sendJson(res, 200, bundle);
150
97
  }
151
98
  catch (err) {
152
- // 未分类 ⇒ 500,文案是**内联整串**(不是 `memory bundle ${face} failed` 模板):冻结门的模板臂只
153
- // 钉到第一个 `${` 为止,"memory bundle " 这个前缀会把「哪一半炸了」留在门外,而那正是消费端能从
154
- // 500 上读到的全部信息。同族先例:"memory export failed" / "memory sync failed"(memory-policy.ts)。
155
99
  if (!sentClassifiedFailure(res, ctx, "export", err))
156
100
  sendError(res, 500, "internal.error", "memory bundle export failed");
157
101
  }
@@ -163,10 +107,6 @@ async function handleMemoryBundleBody(req, res, url, ctx, miss) {
163
107
  return;
164
108
  }
165
109
  try {
166
- // 整包判决(结构/完整性/授权信封)全在 core:本层递交的就是调用方给的那个对象。
167
- // 递交的是**未校验**的对象:整包判决(结构/完整性/授权信封)是 core 的单一属主面,所以 seam 的
168
- // 入参类型就写 `unknown` —— 在本层把它断言成 MemoryExportBundle 是**说谎**(我们并不知道它是),
169
- // 而说谎的那一下正好把类型检查关灯(type-hygiene 门抓的就是这个)。
170
110
  const report = await engineImport(parsed.data.bundle);
171
111
  sendJson(res, 200, buildImportReportWire(report));
172
112
  }
@@ -3,22 +3,7 @@ import { parseMemorySyncRequest } from "../../memory-sync.js";
3
3
  import { sendJson, sendError } from "../send.js";
4
4
  import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
5
5
  const ORG_PREFIX = "org:";
6
- /**
7
- * #271 件3(#275 A2 车立的欠账,[4163] 我方认领)—— `GET /v1/memory/export` 的 keyset 分页上限。
8
- *
9
- * 在此之前本面是**整盘导出**:一个几千条目的盘就是一个几 MB 的响应体,壳侧(sema-web 只读页)
10
- * 无从分次要 —— 那是它真实撞到的天花板。分页是 **additive** 的:`?limit=` 缺席 ⇒ 旧契约逐字不变
11
- * (全量数组、响应键一个不多),所以现行消费端零破坏;要分页的调用方自己带 `limit` 进来。
12
- *
13
- * ⚠️ 诚实记档:本面的分页封的是**回体**(wire 天花板),不是 DB 读 —— reader
14
- * (`exportMemoryScope`)仍是整 scope 一次读出。真正的下推需要 core 的 `MemoryBackend.listHeaders`
15
- * 长出分页参数(上游接缝,源头修复纪律:不在本层用别的口硬凑)。壳侧的痛点(响应体大小/首屏)由
16
- * 本形解决,库侧的读放大另案。
17
- */
18
6
  export const MEMORY_EXPORT_MAX_LIMIT = 500;
19
- /** 游标 = 条目 `id` 的不透明 base64url(声明序=id 升序;id 是 entry 平面的 PK ⇒ 唯一,单键即全序,
20
- * 不像 roster 的 `(spawnedAt, handle)` 需要 tiebreak)。`before` / `nextBefore` 是本仓分页的
21
- * **同一套词**(roster 先例):调用方只把上一页的 `nextBefore` 原样回传,不解读内容。 */
22
7
  function encodeExportCursor(c) {
23
8
  return Buffer.from(JSON.stringify(c)).toString("base64url");
24
9
  }
@@ -37,38 +22,14 @@ function decodeExportCursor(raw) {
37
22
  return { ok: false, why: "cursor payload lacks a non-empty `id`" };
38
23
  return { ok: true, cursor: { id } };
39
24
  }
40
- /** 本面是否让目录参与判决。闭集穷举 switch:新增第三个模式是**编译**错误,不会静默落进某个默认臂
41
- * ——这条门在授权轴上,静默默认值正是它不许有的东西。 */
42
25
  function directoryDecidesThisFace(mode) {
43
26
  switch (mode) {
44
27
  case "enforce":
45
28
  return true;
46
29
  case "audit":
47
- return false; // 见 gateMemoryScope 顶注的 codex R1 段
30
+ return false;
48
31
  }
49
32
  }
50
- /**
51
- * memory-policy 两面(export 读 / sync 写)共用的属主门 —— design/170 件A §7「第二消费者收编」。
52
- *
53
- * 收编前本门把 `org:*` 写死 operator-only(注逐字「membership waits for the S3 登记簿」),于是同一进程里
54
- * 「谁属于 org:acme」有两个答案:core 准入门问授权目录、本面问 operatorPrincipals。收编后 `org:` 成员性
55
- * 只由 `deps.orgMemoryDirectory` 回答(与准入 seam 同一个实例、同一份缓存)。
56
- *
57
- * 判决序(前两级刻意在目录之前 —— 它们不需要目录也成立,顺带让常路零额外 I/O):
58
- * ① 本人 user 盘逐字匹配 ⇒ 通过;② 显式 operator ⇒ 通过(ops/迁移面不依赖目录在场);
59
- * ③ `org:` 键 + 目录在场 + **enforce 模式** ⇒ 查目录:瞬时不可用照原样上抛;取数成功但无该键 ⇒
60
- * `not_found`;`need==="write"` 另要条目 `write === true`(读授予不隐含写授予,与准入面 writeScope
61
- * 收窄同源);④ 其余(`userproj:` 等目录没有答案的键、无目录源的部署、audit 模式)⇒ `not_found`。
62
- *
63
- * **audit 模式为什么在本面等于「不接目录」**(codex 对抗审 R1,2026-08-07):`audit` 是 operator 表态
64
- * 「这份目录还没被验证过,先别拿它做判决」。拿未验证的目录去开真数据面(读)甚至写面,是这条表态能
65
- * 造成的最严重后果——方向恰好反了;`audit=零行为变化` 的公开契约在本面因此读作「逐字保持收编前的
66
- * operator-only」,连额外那一跳目录 I/O 和 enforce 才有的 503 响应形都不引入。org 面的诊断信号属于
67
- * 准入侧的 `memory_admission_total{outcome,mode}`,不在本面另开一路。
68
- *
69
- * 拒绝一律 `not_found` 而非 403:本面的零存在性 oracle 纪律不变(与 run/trace 属主门同口径),
70
- * 「不是成员」和「没这个 scope」对调用方必须不可区分。
71
- */
72
33
  async function gateMemoryScope(deps, principal, scope, need) {
73
34
  if (scope === formatUserScope(principal))
74
35
  return { kind: "allow" };
@@ -86,18 +47,16 @@ async function gateMemoryScope(deps, principal, scope, need) {
86
47
  return { kind: "not_found" };
87
48
  return need === "write" && grant.write !== true ? { kind: "not_found" } : { kind: "allow" };
88
49
  }
89
- /** 门的拒绝臂下发:瞬时 ⇒ 503 `memory.admission_required` + `retryAfterSec`(秒上取整,永不 0 —— C6;
90
- * 族形与 sync 提交腿的同码响应逐字一致);终局 ⇒ 404 零 oracle。返回 true = 已应答,调用方须 return。 */
91
50
  function sendScopeGateRefusal(res, gate) {
92
51
  if (gate.kind === "allow")
93
52
  return false;
94
53
  if (gate.kind === "unavailable") {
95
54
  const retryAfterSec = Math.max(1, Math.ceil(gate.retryAfterMs / 1000));
96
- res.setHeader("retry-after", String(retryAfterSec)); // 体里有等待提示 ⇒ 标准头同值(与同步提交腿的同码应答逐字一致)
55
+ res.setHeader("retry-after", String(retryAfterSec));
97
56
  sendError(res, 503, "memory.admission_required", "org memory directory unavailable — authorization could not be established", { retryAfterSec });
98
57
  return true;
99
58
  }
100
- sendError(res, 404, "not_found.memory_scope", "not found"); // owner gate: no existence oracle
59
+ sendError(res, 404, "not_found.memory_scope", "not found");
101
60
  return true;
102
61
  }
103
62
  export async function handleMemoryPolicy(req, res, url, ctx) {
@@ -105,27 +64,11 @@ export async function handleMemoryPolicy(req, res, url, ctx) {
105
64
  await handleMemoryPolicyBody(req, res, url, ctx, miss);
106
65
  return !miss.fell;
107
66
  }
108
- /** 路由体 = 从 `server.ts` 的 `handle()` 里**整段剪切**过来的原文(唯一改动:统一去缩进两格)。裸 `return;`
109
- * 的语义原样 =「本域已应答」;走到函数尾才是「没匹配上」,置 `miss.fell` 交回装配器继续下一个域。
110
- * 跨域的东西(deps / 助手 / 单域状态)一律在首行解构出来,于是下面每一行都逐字不变。 */
111
67
  async function handleMemoryPolicyBody(req, res, url, ctx, miss) {
112
68
  const { deps } = ctx;
113
69
  const { readJson } = ctx.helpers;
114
- // 142-S5 §1.4 — GET /v1/memory/export?scope=<key>: per-scope memory export (传输中立包=条目全量数组;
115
- // C-additive 落点). After the global service-credential gate (a credential/SSO-JWT is already
116
- // verified); the OWNER gate here is the tenant boundary: the verified principal may export exactly its OWN
117
- // user disk (`scope === formatUserScope(principal)` — core's mint, byte-identical to memoryScopeFor's) or be
118
- // an explicit operator (explicitOperatorOk, ops/migration face), or — design/170 件A §7 收编 — a
119
- // DIRECTORY MEMBER of an `org:` key (the same org-memory directory instance core's admission seam reads,
120
- // so one process holds ONE answer to "who belongs to org:acme"). Anything else = 404, NEVER 403 (zero
121
- // existence oracle, run/trace owner-gate parity); a directory that cannot answer = 503, never 404.
122
- // `userproj:*` stays operator-only by construction (the directory has no answer for that key shape).
123
- // proj:* keys of repo-backed projects never live in this DB (铁律: git is their authority) — an owner
124
- // asking for one just gets an empty set, honestly.
125
70
  if (url === "/v1/memory/export" && req.method === "GET") {
126
- // Auth BEFORE availability (501-before-auth precedent): the backend posture must not oracle
127
- // to a caller who couldn't read anything anyway.
128
- const principal = gatedPrincipal(req, deps.config); // direct-door safe: verified identity, never the spoofable header
71
+ const principal = gatedPrincipal(req, deps.config);
129
72
  if (!principal) {
130
73
  sendError(res, 401, "auth.unauthorized", "unauthorized");
131
74
  return;
@@ -134,7 +77,7 @@ async function handleMemoryPolicyBody(req, res, url, ctx, miss) {
134
77
  sendError(res, 501, "capability.memory_store_required", "memory export requires a DB memory backend (MEMORY_ENGINE_BACKEND=pg|tidb)");
135
78
  return;
136
79
  }
137
- const q = new URL(req.url ?? "", "http://x").searchParams; // route-level `url` is query-stripped
80
+ const q = new URL(req.url ?? "", "http://x").searchParams;
138
81
  const scope = q.get("scope");
139
82
  if (!scope) {
140
83
  sendError(res, 400, "request.query_invalid", "missing ?scope=<key>");
@@ -142,18 +85,12 @@ async function handleMemoryPolicyBody(req, res, url, ctx, miss) {
142
85
  }
143
86
  if (sendScopeGateRefusal(res, await gateMemoryScope(deps, principal, scope, "read")))
144
87
  return;
145
- // 分页参数的验型排在**属主门之后**(与 roster 的「先门后参」同序):越界者恒 404,再坏的查询串
146
- // 也不许把它换成 400 —— 那就是一格「这个 scope 存在」的信号。
147
88
  const limitRaw = q.get("limit");
148
89
  if (limitRaw !== null && !(/^[0-9]+$/.test(limitRaw) && Number(limitRaw) >= 1 && Number(limitRaw) <= MEMORY_EXPORT_MAX_LIMIT)) {
149
90
  sendError(res, 400, "request.query_invalid", `limit must be a decimal integer in 1..${MEMORY_EXPORT_MAX_LIMIT}`);
150
91
  return;
151
92
  }
152
93
  const beforeRaw = q.get("before");
153
- // 🔴 `before` 必须与 `limit` 同行(codex 对抗复审 H2):否则「缺席 limit = 旧契约逐字不变」有两个
154
- // 破口 —— 合法游标会让调用方拿到**过滤过的**子集却按全量读,坏游标会把旧行为的 200 全量变 400。
155
- // 取**响亮拒**而不是「静默忽略 before」:后者让「分页时忘了带 limit」的调用方悄悄收到整盘,
156
- // 正是本件要消灭的那个天花板。两个参数都不带的旧调用**一个字节都没碰到这条腿**。
157
94
  if (beforeRaw !== null && limitRaw === null) {
158
95
  sendError(res, 400, "request.query_invalid", "before requires an explicit limit — without one this endpoint returns the whole scope by contract");
159
96
  return;
@@ -165,26 +102,19 @@ async function handleMemoryPolicyBody(req, res, url, ctx, miss) {
165
102
  }
166
103
  try {
167
104
  const entries = await deps.memoryExport(scope);
168
- // 🔴 缺席 limit = **旧契约逐字不变**:同一个数组、同一个顺序、响应键一个不多(sema-web 现行
169
- // 消费零破坏)。分页面才排序/切片 —— 两条腿在真部署上给出同一个序(reader 是
170
- // exportMemoryScope,两方言读面都 ORDER BY id),但冻结面不靠"碰巧相同",它就不动。
171
- if (limitRaw === null) { // `before` 无 limit 已在上面 400 掉 ⇒ 这里等价于「两个分页参数都不在场」
105
+ if (limitRaw === null) {
172
106
  sendJson(res, 200, { scope, exportedAt: new Date().toISOString(), entries });
173
107
  return;
174
108
  }
175
- // 声明序 = **id 升序**(entry 平面的既有确定序;id 是 PK ⇒ 唯一,单键游标无同值 tiebreak 洞)。
176
- // 本面自己排,不继承 reader 的顺序:游标的正确性(无重无漏)是本面的承诺,不是上游的巧合。
177
109
  const sorted = [...entries].sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
178
110
  const after = decoded?.ok === true ? sorted.filter((e) => e.id > decoded.cursor.id) : sorted;
179
- const page = after.slice(0, Number(limitRaw)); // limitRaw 在此必然在场且已验型(上面两道门)
111
+ const page = after.slice(0, Number(limitRaw));
180
112
  const last = page[page.length - 1];
181
113
  const hasMore = last !== undefined && after.length > page.length;
182
114
  sendJson(res, 200, {
183
115
  scope,
184
116
  exportedAt: new Date().toISOString(),
185
117
  entries: page,
186
- // `truncated` 与 `nextBefore` 同生同灭,但**两个都要有**:前者回答「这是全量吗」(缺席 limit
187
- // 的旧契约给的就是全量,所以带 limit 时必须显式说不是),后者回答「从哪儿接着要」。
188
118
  ...(hasMore ? { truncated: true, nextBefore: encodeExportCursor({ id: last.id }) } : {}),
189
119
  });
190
120
  }
@@ -194,14 +124,8 @@ async function handleMemoryPolicyBody(req, res, url, ctx, miss) {
194
124
  }
195
125
  return;
196
126
  }
197
- // 142-S2.5 — POST /v1/memory/sync/:scope: the central-side sync face(设计 §2.5;裁定
198
- // plan 随数据走——reconcile/nextSyncBaseline 是 core 纯函数,由持数据的本 server 在 performMemorySync
199
- // (memory-sync.ts) 里直接调用;此处只持 HTTP 半场). Gate ORDER mirrors the export face verbatim:
200
- // auth (401) → backend posture (501, file 形态诚实拒绝——单用户 file 面自己就是 TOC 侧,没有中心
201
- // 权威半场可服务) → owner gate (404 zero-oracle: verified principal 的 user 盘逐字匹配 / explicit
202
- // operator / `org:` 键的目录成员且条目 write:true;userproj: 仍 operator-only) → 验型 (422 typed)。
203
127
  if (req.method === "POST" && url.startsWith("/v1/memory/sync/")) {
204
- const principal = gatedPrincipal(req, deps.config); // direct-door safe: verified identity, never the spoofable header
128
+ const principal = gatedPrincipal(req, deps.config);
205
129
  if (!principal) {
206
130
  sendError(res, 401, "auth.unauthorized", "unauthorized");
207
131
  return;
@@ -210,8 +134,6 @@ async function handleMemoryPolicyBody(req, res, url, ctx, miss) {
210
134
  sendError(res, 501, "capability.memory_store_required", "memory sync requires a DB memory backend (MEMORY_ENGINE_BACKEND=pg|tidb)");
211
135
  return;
212
136
  }
213
- // :scope rides the path percent-encoded (scope keys carry `:`/`@`/`/`); route-level `url` is
214
- // query-stripped but NOT decoded. A malformed escape is a client error, typed 400.
215
137
  let scope;
216
138
  try {
217
139
  scope = decodeURIComponent(url.slice("/v1/memory/sync/".length));
@@ -224,11 +146,9 @@ async function handleMemoryPolicyBody(req, res, url, ctx, miss) {
224
146
  sendError(res, 400, "memory_sync_invalid_scope", "missing :scope");
225
147
  return;
226
148
  }
227
- // 写面:门与 export 面同一个 `gateMemoryScope`,只在 `need` 上分家 —— org 条目须 `write === true`
228
- // 才放行(读授予不隐含写授予)。属主/operator 两级与读面逐字同判。
229
149
  if (sendScopeGateRefusal(res, await gateMemoryScope(deps, principal, scope, "write")))
230
150
  return;
231
- const body = await readJson(req); // 413/400 (oversize / bad JSON) via the typed top-level catch — auth already done
151
+ const body = await readJson(req);
232
152
  const parsed = parseMemorySyncRequest(body, scope);
233
153
  if (!parsed.ok) {
234
154
  sendError(res, 422, "memory_sync_invalid_body", parsed.error);
@@ -239,22 +159,12 @@ async function handleMemoryPolicyBody(req, res, url, ctx, miss) {
239
159
  sendJson(res, 200, out);
240
160
  }
241
161
  catch (err) {
242
- // applyPatches 可能已落库而 putCursor 失败——500 是诚实信号:客户端按同一 baseRevs 重放即
243
- // 幂等自愈(identical revs 全程 no-op),绝不静默吞。
244
162
  deps.logger?.warn?.("memory_sync_failed", { scope, peer: parsed.value.peer, err: String(err) });
245
163
  sendError(res, 500, "internal.error", "memory sync failed");
246
164
  }
247
165
  return;
248
166
  }
249
- // Effective server-enforced governance (CLI /permissions + /auto-mode). READ-only — a cloud
250
- // supervisor shell CANNOT change server-side gating (the CLI's "permission modes" are tighten-only client
251
- // cosmetics; the authoritative policy lives here, applied live in resolveSpec). Worker-global config (same for
252
- // every principal), non-secret config surface like /v1/capabilities, so a principal is OPTIONAL (only enforced
253
- // under REQUIRE_PRINCIPAL). Lets the client render the REAL autonomy band + command rules instead of fabricated ones.
254
167
  if (url === "/v1/policy" && req.method === "GET") {
255
- // F-fix: gatedPrincipal (VERIFIED) for the presence gate — on a direct door the legit caller authenticates by
256
- // JWT (no x-agent-principal header), so a `principalFrom` gate would 401 a valid reader. Worker-global config
257
- // (no per-tenant leak either way); the correctness/consistency twin of the run-owner gates. (BFF/gated: same.)
258
168
  const principal = gatedPrincipal(req, deps.config);
259
169
  if (deps.config.requirePrincipal && !principal) {
260
170
  sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
@@ -1,33 +1,17 @@
1
1
  import { taskNotificationInboxEntry } from "../../orchestration/workflow-completion-inbox.js";
2
2
  import { sendJson, sendError } from "../send.js";
3
3
  import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
4
- /**
5
- * `POST /v1/sessions/:id/wake` 的路由形 —— **单源导出**,因为它有两个消费者:本模块的分发,
6
- * 和 `server.ts` 的 `isBillableSubmitPath`(wake 会烧模型,必须吃 drain / model-roster-pending /
7
- * 无 service-token 三道提交门)。
8
- *
9
- * 🔴 为什么提成常量:2026-07-31 的缝合审查出这两处**曾经不同步** —— 匹配形写死在本文件里,
10
- * 名单在 server.ts 里,wake 从来没进过名单,于是同一条 `driveResumeIntoRunLog` 模型腿
11
- * 从 `/v1/assistant/tasks/:id/resume` 进来受三道门管、从 wake 进来一道都不受。
12
- * 共用一个常量之后,再想漏就得两处一起改。
13
- */
14
4
  export const SESSION_WAKE_RE = /^\/v1\/sessions\/[^/]+\/wake$/;
15
5
  export async function handleNotifyWake(req, res, url, ctx) {
16
6
  const miss = { fell: false };
17
7
  await handleNotifyWakeBody(req, res, url, ctx, miss);
18
8
  return !miss.fell;
19
9
  }
20
- /** 路由体 = 从 `server.ts` 的 `handle()` 里**整段剪切**过来的原文(唯一改动:统一去缩进两格)。裸 `return;`
21
- * = 「本域已应答」;走到函数尾才是「没匹配上」。跨域的东西一律在首行解构出来。 */
22
10
  async function handleNotifyWakeBody(req, res, url, ctx, miss) {
23
11
  const { deps } = ctx;
24
12
  const { steerableRuns } = ctx.registry;
25
13
  const { readJson, rateLimited, quotaExceeded, leaseDenied } = ctx.helpers;
26
14
  const { resumeWake } = ctx.legs;
27
- // design/144 §2 server 半场(external-notification-bridge 的 HTTP 入站门):把「会话外事件」
28
- // (daemon job 终态等)注入目标 session。live(本副本活流)→ core `TaskStream.notify()`(task_type=
29
- // "external" core 铸造,sanitize/围栏/三态投递全在 core);idle → inbox park(external 独立键域,
30
- // [769]① 防预占)→ 下次开流 drain。帧契约=[446] bg_notification 同族。入站鉴权在此(core 只管注入段)。
31
15
  if (req.method === "POST" && /^\/v1\/sessions\/[^/]+\/notify$/.test(url)) {
32
16
  if (rateLimited(req, res) || quotaExceeded(req, res))
33
17
  return;
@@ -49,8 +33,6 @@ async function handleNotifyWakeBody(req, res, url, ctx, miss) {
49
33
  sendError(res, 400, "request.field_invalid", "task_id must be a non-empty string of at most 190 characters");
50
34
  return;
51
35
  }
52
- // [5028]/A-002.1 同纪律双向锚:satisfies 保「表内无词表外的词」(core 删/改词 ⇒ 编译红),
53
- // 穷举断言保「词表无表外的词」(core 加词 ⇒ 编译红)——单靠 satisfies 只有前半,加词会静默 400 拒合法值。
54
36
  const NOTIFY_STATUSES = ["completed", "failed", "killed", "cancelled", "event"];
55
37
  const _notifyExhaustive = true;
56
38
  void _notifyExhaustive;
@@ -74,13 +56,11 @@ async function handleNotifyWakeBody(req, res, url, ctx, miss) {
74
56
  sendError(res, 400, "request.field_invalid", "source must be a string of at most 190 characters when present");
75
57
  return;
76
58
  }
77
- // 入站 owner 门(fail-closed,exemptions 路由同款):未知 session 404;他人 session 404 无 oracle;
78
- // store 错误 500 传播绝不放行;无 ownerOf 面(env-only)=501 诚实(入站门没有地基就不开门)。
79
59
  if (!deps.sessionStorage?.ownerOf) {
80
60
  sendError(res, 501, "capability.session_ownership_required", "external notify requires the session-ownership face (sessionStorage.ownerOf)");
81
61
  return;
82
62
  }
83
- const notifyOwner = await deps.sessionStorage.ownerOf(notifySession); // throws → outer 500(fail-closed)
63
+ const notifyOwner = await deps.sessionStorage.ownerOf(notifySession);
84
64
  if (notifyOwner === undefined) {
85
65
  sendError(res, 404, "not_found.session", "session not found");
86
66
  return;
@@ -98,7 +78,6 @@ async function handleNotifyWakeBody(req, res, url, ctx, miss) {
98
78
  ...(nb.seq !== undefined ? { seq: nb.seq } : {}),
99
79
  ...(nb.source !== undefined ? { source: nb.source } : {}),
100
80
  };
101
- // live 半场:目标 session 的活流在本副本 → core notify verb(task_type="external" core 铸,不可伪装内部帧)。
102
81
  const liveTaskId = await deps.runStore?.getActiveTaskId?.(notifySession).catch(() => undefined);
103
82
  const liveStream = liveTaskId !== undefined && liveTaskId !== null ? steerableRuns.get(liveTaskId) : undefined;
104
83
  if (liveStream) {
@@ -108,17 +87,13 @@ async function handleNotifyWakeBody(req, res, url, ctx, miss) {
108
87
  return;
109
88
  }
110
89
  catch (e) {
111
- // 只有「流已结束」竞态才回落 park;core 的明确拒绝(notify.invalid_payload 等)原样 4xx——
112
- // 把合同拒绝误 park = 无效通知洗成 202(fail-closed 谱系,复审 finding)。
113
90
  const code = e.code;
114
91
  if (typeof code === "string" && code.startsWith("notify.")) {
115
92
  sendError(res, 400, code, e instanceof Error ? e.message : "invalid notification");
116
93
  return;
117
94
  }
118
- /* 其余=活流刚结束竞态 → durable park(与 steer 的 live→suspended 同姿势) */
119
95
  }
120
96
  }
121
- // idle 半场:park 进 inbox(external 键域;redact/cap 在 taskNotificationInboxEntry 内)→ 下次开流 drain。
122
97
  if (!deps.workflowCompletionInbox) {
123
98
  sendError(res, 501, "capability.workflow_store_required", "external notify park requires the workflow completion inbox (WORKFLOW_RUN_STORE)");
124
99
  return;
@@ -128,16 +103,13 @@ async function handleNotifyWakeBody(req, res, url, ctx, miss) {
128
103
  return;
129
104
  }
130
105
  if (req.method === "POST" && SESSION_WAKE_RE.test(url)) {
131
- // design/144 §3(core 1.283 wake arm):对 task_done 纯 park 的「消息唤醒」。非门决策——不 allow/deny
132
- // 任何 pending 动作,只解除 park 并把消息(或已 park 的 pendingSteer)作为续跑首轮输入。有未决门 =
133
- // core `wake.gate_pending` 拒(永不绕审批);空 park + 无 message = `wake.nothing_to_deliver` 拒。
134
106
  if (!deps.checkpointStore) {
135
107
  sendError(res, 501, "capability.checkpoint_store_required", "wake requires the checkpoint store");
136
108
  return;
137
109
  }
138
110
  if (rateLimited(req, res) || quotaExceeded(req, res) || (await leaseDenied(req, res)))
139
- return; // 续跑=烧模型
140
- const principal = gatedPrincipal(req, deps.config); // direct-door-secure(trusted 承载 operator 权威)
111
+ return;
112
+ const principal = gatedPrincipal(req, deps.config);
141
113
  if (deps.config.requirePrincipal && principal === undefined) {
142
114
  sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
143
115
  return;
@@ -155,12 +127,6 @@ async function handleNotifyWakeBody(req, res, url, ctx, miss) {
155
127
  sendError(res, 400, "request.field_invalid", "message must be a non-empty string when present");
156
128
  return;
157
129
  }
158
- // 🕐 **#316 同族登记(本批不改行为)**:wake 与 legacy `/decide` 走同一条 `driveResumeIntoRunLog`
159
- // 模型腿 —— 响应之前同步跑完整条续跑,时长 = 模型往返、无服务端上限([4664]① 定谳的同族三腿之一)。
160
- // 本批只把 legacy decide 改成 200 受理形(那是 [4660] 雪崩实测首环);本腿是 wire BREAKING,按能力
161
- // 显式表态制([3780] 裁 A)候消费方认领后同形改造 —— 改法 = `resumeWake` 透传 `acceptEarly: true`。
162
- // ⚠️ 本腿多一条判据要一起想:它走 `mintFreshRun`(铸新 run 行),受理体里的 `taskId` 因此是**新铸的**
163
- // 那个 id —— 改造时受理形必须带上它,否则壳没有可 poll 的锚。
164
130
  const out = await resumeWake(wakeSession, wakeBody.message, principal, req);
165
131
  sendJson(res, out.status, out.body);
166
132
  return;
@@ -5,23 +5,8 @@ export async function handleObservability(req, res, url, ctx) {
5
5
  await handleObservabilityBody(req, res, url, ctx, miss);
6
6
  return !miss.fell;
7
7
  }
8
- /** 路由体 = 从 `server.ts` 的 `handle()` 里**整段剪切**过来的原文(唯一改动:统一去缩进两格)。裸 `return;`
9
- * 的语义原样 =「本域已应答」;走到函数尾才是「没匹配上」,置 `miss.fell` 交回装配器继续下一个域。
10
- * 跨域的东西(deps / 助手 / 单域状态)一律在首行解构出来,于是下面每一行都逐字不变。 */
11
8
  async function handleObservabilityBody(req, res, url, ctx, miss) {
12
9
  const { deps } = ctx;
13
- // Per-principal cumulative cost usage (CLI /cost + /usage + status usage tab). The READ twin of
14
- // the cost-quota GATE (quotaExceeded): `check` is read-only (does NOT consume). Principal-scoped — a caller
15
- // only ever sees its OWN spend (keyed exactly like the gate). When no cumulative quota is configured
16
- // (deps.costQuota absent) → enabled:false (NOT an error) so the CLI shows "no cumulative quota; per-task cap
17
- // = $X" instead of failing. ⚠️ in-memory CostQuota is per-replica (undercounts across replicas) — the TiDB
18
- // twin (deps.costQuota = TiDBCostQuota) is the cross-replica-accurate backend; both satisfy `.check()`.
19
- // P2 (design/73 §7.2 dashboard floor): READ-ONLY per-(taskSignature, model) outcome aggregate — pass-rate
20
- // + mean cost over the graded ledger rows (the (b)-emitter data: make-real/TB harness runs + any future
21
- // goal面). 🔒 Multi-tenant: operator-only — rows are NOT owner-keyed (mechanical facts keyed by signature
22
- // ×model), so any-tenant access would leak cross-tenant task/perf shape; single-user turnkey is open (the
23
- // sole user IS the operator). ?signature= filters to one aggregation key; ?includeAborted=true adds the
24
- // infra-aborted rows (infra-health view — NEVER the training view, council blocker 3).
25
10
  if (url === "/v1/outcomes" && req.method === "GET") {
26
11
  if (deps.config.requirePrincipal) {
27
12
  const principal = gatedPrincipal(req, deps.config);
@@ -34,7 +19,7 @@ async function handleObservabilityBody(req, res, url, ctx, miss) {
34
19
  sendError(res, 501, "capability.outcome_ledger_required", "no queryable outcome ledger on this worker (File sink — read the JSONL dataset directly)");
35
20
  return;
36
21
  }
37
- const q = new URL(req.url ?? "/", "http://localhost").searchParams; // `url` is query-stripped upstream
22
+ const q = new URL(req.url ?? "/", "http://localhost").searchParams;
38
23
  const sig = q.get("signature") ?? undefined;
39
24
  try {
40
25
  const rows = await deps.outcomeSink.summary({ ...(sig ? { taskSignature: sig } : {}), includeAborted: q.get("includeAborted") === "true" });
@@ -46,10 +31,8 @@ async function handleObservabilityBody(req, res, url, ctx, miss) {
46
31
  }
47
32
  return;
48
33
  }
49
- // (用量统计查询面 /v1/usage/{summary,series,breakdown} 从这里上移到全局 service-credential 门之前——
50
- // usage 门自身认 traceToken,但挂在全局门之后时 traceToken-only 调用在全局门就 401=死代码。)
51
34
  if (url === "/v1/usage" && req.method === "GET") {
52
- const principal = gatedPrincipal(req, deps.config); // direct-door safe: crypto-verified principal, never the spoofable header
35
+ const principal = gatedPrincipal(req, deps.config);
53
36
  if (!principal) {
54
37
  sendError(res, 401, "auth.principal_required", `missing principal header '${deps.config.principalHeader}'`);
55
38
  return;