@sema-agent/server 7.130.0 → 7.132.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 (294) hide show
  1. package/MIGRATION.md +396 -0
  2. package/README.md +13 -6
  3. package/README.zh-CN.md +9 -5
  4. package/USAGE.md +187 -50
  5. package/dist/approval-card.d.ts +3 -1
  6. package/dist/approval-card.js +2 -3
  7. package/dist/audit.d.ts +7 -10
  8. package/dist/audit.js +4 -1
  9. package/dist/auth-bridge.d.ts +4 -0
  10. package/dist/auth-bridge.js +10 -11
  11. package/dist/bench/s1/live-deps.js +2 -0
  12. package/dist/boot/deployment-mods.d.ts +31 -0
  13. package/dist/boot/deployment-mods.js +26 -0
  14. package/dist/boot/execution-env.d.ts +1 -1
  15. package/dist/boot/execution-env.js +19 -6
  16. package/dist/boot/memory-boundary.d.ts +2 -2
  17. package/dist/boot/read-deny-anchor-audit.d.ts +17 -0
  18. package/dist/boot/read-deny-anchor-audit.js +36 -0
  19. package/dist/boot/resolve-spec.d.ts +1 -5
  20. package/dist/boot/resolve-spec.js +56 -61
  21. package/dist/boot/runner-deps.d.ts +19 -8
  22. package/dist/boot/runner-deps.js +14 -2
  23. package/dist/boot/session-faces.d.ts +1 -7
  24. package/dist/boot/stage-02-web-admin-pull.js +9 -1
  25. package/dist/boot/stage-03-store-backend.d.ts +1 -1
  26. package/dist/boot/stage-03-store-backend.js +2 -2
  27. package/dist/boot/stage-04-stores.d.ts +1 -1
  28. package/dist/boot/stage-05-execution-env.d.ts +4 -4
  29. package/dist/boot/stage-05-execution-env.js +7 -10
  30. package/dist/boot/stage-06-runners.d.ts +2 -2
  31. package/dist/boot/stage-06-runners.js +5 -4
  32. package/dist/boot/stage-07-capability-layer.d.ts +2 -2
  33. package/dist/boot/stage-07-capability-layer.js +3 -3
  34. package/dist/boot/stage-08-reapers.d.ts +2 -2
  35. package/dist/boot/stage-09-leader.d.ts +3 -9
  36. package/dist/boot/stage-10-http-server.d.ts +3 -9
  37. package/dist/boot/stage-10-http-server.js +12 -10
  38. package/dist/boot/stores.d.ts +1 -1
  39. package/dist/boot/stores.js +5 -6
  40. package/dist/boot/web-admin.d.ts +0 -3
  41. package/dist/boot/web-admin.js +35 -35
  42. package/dist/brain.js +17 -22
  43. package/dist/budget.js +5 -4
  44. package/dist/caller-text-echo.d.ts +20 -0
  45. package/dist/caller-text-echo.js +24 -0
  46. package/dist/capabilities/brand-identity.d.ts +26 -0
  47. package/dist/capabilities/brand-identity.js +27 -0
  48. package/dist/capabilities/builtin-tools.js +2 -1
  49. package/dist/capabilities/center-plugins.js +1 -1
  50. package/dist/capabilities/center-prompts.d.ts +32 -59
  51. package/dist/capabilities/center-prompts.js +18 -32
  52. package/dist/capabilities/composite-members.d.ts +1 -0
  53. package/dist/capabilities/composite-members.js +1 -0
  54. package/dist/capabilities/engine-capabilities.js +2 -1
  55. package/dist/capabilities/hands-lane.d.ts +9 -6
  56. package/dist/capabilities/hands-lane.js +3 -3
  57. package/dist/capabilities/host-tool-names.d.ts +17 -0
  58. package/dist/capabilities/host-tool-names.js +15 -0
  59. package/dist/capabilities/mcp-probe.d.ts +14 -1
  60. package/dist/capabilities/mcp-probe.js +22 -133
  61. package/dist/capabilities/repo-tools.js +4 -3
  62. package/dist/capabilities/scenarios.d.ts +42 -47
  63. package/dist/capabilities/scenarios.js +19 -35
  64. package/dist/capabilities/select-environment-tool.js +2 -1
  65. package/dist/capabilities/send-user-file-tool.d.ts +0 -1
  66. package/dist/capabilities/send-user-file-tool.js +2 -2
  67. package/dist/capabilities/skills.d.ts +19 -4
  68. package/dist/capabilities/skills.js +30 -10
  69. package/dist/capabilities/tool-defer.js +2 -1
  70. package/dist/config-catalog.js +22 -11
  71. package/dist/config-invariants.js +2 -2
  72. package/dist/config-lkg.d.ts +9 -9
  73. package/dist/config-lkg.js +20 -4
  74. package/dist/config-provider.d.ts +3 -1
  75. package/dist/config-provider.js +14 -6
  76. package/dist/config-types.d.ts +13 -9
  77. package/dist/config.d.ts +2 -2
  78. package/dist/config.js +34 -20
  79. package/dist/device-ws-protocol.d.ts +1 -1
  80. package/dist/device-ws-protocol.js +1 -0
  81. package/dist/elicitation.d.ts +7 -0
  82. package/dist/elicitation.js +20 -5
  83. package/dist/env-name-allowlist-knobs.d.ts +12 -2
  84. package/dist/env-name-allowlist-knobs.js +5 -4
  85. package/dist/exec-bounds.d.ts +15 -3
  86. package/dist/exec-bounds.js +4 -0
  87. package/dist/execution-lane-caps.d.ts +9 -2
  88. package/dist/execution-lane-caps.js +11 -0
  89. package/dist/hooks/classic-stations.d.ts +86 -0
  90. package/dist/hooks/classic-stations.js +27 -0
  91. package/dist/hooks/hook-llm.d.ts +8 -5
  92. package/dist/hooks/hook-llm.js +80 -37
  93. package/dist/hooks/hook-runner.d.ts +273 -75
  94. package/dist/hooks/hook-runner.js +293 -317
  95. package/dist/http/admission.d.ts +8 -2
  96. package/dist/http/admission.js +103 -59
  97. package/dist/http/approval-sweeps.js +59 -18
  98. package/dist/http/checkpoint-ctx.d.ts +36 -18
  99. package/dist/http/checkpoint-ctx.js +14 -10
  100. package/dist/http/claimed-run.d.ts +13 -0
  101. package/dist/http/claimed-run.js +15 -0
  102. package/dist/http/device-placement.js +5 -10
  103. package/dist/http/dispatch.d.ts +0 -4
  104. package/dist/http/dispatch.js +6 -10
  105. package/dist/http/durable-run-leg.d.ts +2 -1
  106. package/dist/http/durable-run-leg.js +5 -3
  107. package/dist/http/health-self-report.d.ts +7 -5
  108. package/dist/http/health-self-report.js +4 -18
  109. package/dist/http/principal-gate.d.ts +23 -6
  110. package/dist/http/principal-gate.js +24 -5
  111. package/dist/http/refusal-body.d.ts +21 -0
  112. package/dist/http/refusal-body.js +4 -0
  113. package/dist/http/request-guards.d.ts +27 -0
  114. package/dist/http/request-guards.js +24 -3
  115. package/dist/http/resume-legs.d.ts +25 -0
  116. package/dist/http/resume-legs.js +48 -26
  117. package/dist/http/routes/approvals-assistant.js +1 -1
  118. package/dist/http/routes/capabilities.js +7 -17
  119. package/dist/http/routes/diagnostics.js +1 -1
  120. package/dist/http/routes/rules.js +2 -2
  121. package/dist/http/routes/runs.js +13 -17
  122. package/dist/http/routes/session-sync.js +4 -2
  123. package/dist/http/routes/sessions.js +23 -11
  124. package/dist/http/routes/shared-memory.js +2 -13
  125. package/dist/http/routes/side-query.js +2 -3
  126. package/dist/http/routes/tasks.js +34 -19
  127. package/dist/http/scenario-read-face.d.ts +14 -0
  128. package/dist/http/scenario-read-face.js +5 -0
  129. package/dist/http/send.d.ts +13 -1
  130. package/dist/http/send.js +12 -1
  131. package/dist/http/server.d.ts +37 -23
  132. package/dist/http/server.js +30 -4
  133. package/dist/http/stored-body-replay.d.ts +10 -3
  134. package/dist/http/stored-body-replay.js +27 -6
  135. package/dist/http/submit-ctx.d.ts +25 -0
  136. package/dist/http/submit-ctx.js +39 -0
  137. package/dist/http/wire-types.d.ts +18 -5
  138. package/dist/index.d.ts +3 -2
  139. package/dist/index.js +3 -2
  140. package/dist/lane-home-probe.d.ts +43 -0
  141. package/dist/lane-home-probe.js +35 -0
  142. package/dist/limit-refusal.d.ts +47 -0
  143. package/dist/limit-refusal.js +12 -0
  144. package/dist/main.js +1 -1
  145. package/dist/mcp-assertion-header.d.ts +3 -11
  146. package/dist/mcp-assertion-header.js +3 -9
  147. package/dist/mcp-tool-faces.d.ts +5 -0
  148. package/dist/mcp-tool-faces.js +5 -1
  149. package/dist/memory-scope.d.ts +1 -1
  150. package/dist/model-entry-refusal.d.ts +1 -7
  151. package/dist/model-provider.d.ts +39 -12
  152. package/dist/model-provider.js +19 -2
  153. package/dist/model-route-endpoint.d.ts +52 -12
  154. package/dist/model-route-endpoint.js +48 -26
  155. package/dist/model-route-gap.d.ts +5 -4
  156. package/dist/model-route-gap.js +1 -1
  157. package/dist/model-select.d.ts +58 -0
  158. package/dist/model-select.js +31 -0
  159. package/dist/namespaced-rule-name.d.ts +7 -4
  160. package/dist/namespaced-rule-name.js +4 -3
  161. package/dist/observability/cost-taxonomy.d.ts +7 -7
  162. package/dist/observability/fail-open.d.ts +53 -12
  163. package/dist/observability/fail-open.js +47 -7
  164. package/dist/observability/metrics.js +3 -1
  165. package/dist/observability/prompt-manifest.d.ts +47 -82
  166. package/dist/observability/prompt-manifest.js +119 -17
  167. package/dist/observability/run-terminal.js +14 -6
  168. package/dist/observability/secret-env-scrub.d.ts +13 -1
  169. package/dist/observability/secret-env-scrub.js +13 -1
  170. package/dist/observability/tool-trace.d.ts +16 -21
  171. package/dist/observability/tool-trace.js +51 -42
  172. package/dist/per-task-image.d.ts +7 -1
  173. package/dist/per-task-image.js +3 -3
  174. package/dist/plain-string-record.d.ts +25 -0
  175. package/dist/plain-string-record.js +32 -0
  176. package/dist/plugins/blob-backend.d.ts +68 -13
  177. package/dist/plugins/blob-backend.js +53 -10
  178. package/dist/plugins/checkpoint-store-sql.d.ts +14 -4
  179. package/dist/plugins/checkpoint-store-sql.js +39 -26
  180. package/dist/plugins/k8s-bg-scripts.d.ts +11 -0
  181. package/dist/plugins/k8s-bg-scripts.js +22 -0
  182. package/dist/plugins/local-checkpoint-store.js +5 -2
  183. package/dist/plugins/local-session-store.js +3 -3
  184. package/dist/plugins/one-time-migrations.d.ts +147 -91
  185. package/dist/plugins/one-time-migrations.js +372 -208
  186. package/dist/plugins/permission-rule-store-sql.d.ts +13 -7
  187. package/dist/plugins/permission-rule-store-sql.js +4 -18
  188. package/dist/plugins/pg-session-storage.d.ts +4 -5
  189. package/dist/plugins/pg-session-storage.js +7 -17
  190. package/dist/plugins/remote-env-adb.d.ts +6 -0
  191. package/dist/plugins/remote-env-adb.js +9 -4
  192. package/dist/plugins/remote-env-device.js +3 -0
  193. package/dist/plugins/remote-env-e2b.d.ts +1 -1
  194. package/dist/plugins/remote-env-e2b.js +21 -9
  195. package/dist/plugins/remote-env-host.d.ts +9 -9
  196. package/dist/plugins/remote-env-host.js +10 -32
  197. package/dist/plugins/remote-env-k8s.d.ts +95 -26
  198. package/dist/plugins/remote-env-k8s.js +265 -90
  199. package/dist/plugins/remote-env-local-docker.d.ts +20 -1
  200. package/dist/plugins/remote-env-local-docker.js +77 -9
  201. package/dist/plugins/remote-env-ssh.d.ts +15 -0
  202. package/dist/plugins/remote-env-ssh.js +109 -3
  203. package/dist/plugins/remote-shell.d.ts +0 -1
  204. package/dist/plugins/remote-shell.js +1 -2
  205. package/dist/plugins/retention-store-sql.js +6 -2
  206. package/dist/plugins/schema-consistency.d.ts +54 -0
  207. package/dist/plugins/schema-consistency.js +52 -0
  208. package/dist/plugins/session-epoch-anchor.d.ts +13 -0
  209. package/dist/plugins/session-epoch-anchor.js +23 -0
  210. package/dist/plugins/shared-memory-store-sql.d.ts +9 -3
  211. package/dist/plugins/shared-memory-store-sql.js +5 -16
  212. package/dist/plugins/sql-driver.d.ts +141 -0
  213. package/dist/plugins/sql-driver.js +115 -1
  214. package/dist/plugins/sql-errors.d.ts +41 -0
  215. package/dist/plugins/sql-errors.js +36 -0
  216. package/dist/plugins/store-backend.js +2 -1
  217. package/dist/plugins/tidb-session-storage.d.ts +7 -11
  218. package/dist/plugins/tidb-session-storage.js +6 -16
  219. package/dist/plugins/tidb-session-store.js +1 -1
  220. package/dist/prompts-domain-validate.d.ts +17 -61
  221. package/dist/prompts-domain-validate.js +9 -119
  222. package/dist/question.d.ts +18 -9
  223. package/dist/question.js +21 -13
  224. package/dist/request-key-closure.d.ts +4 -4
  225. package/dist/request-key-closure.js +18 -13
  226. package/dist/rule-entry-list.d.ts +134 -24
  227. package/dist/rule-entry-list.js +162 -30
  228. package/dist/run-local.d.ts +3 -3
  229. package/dist/run-local.js +26 -25
  230. package/dist/runs.d.ts +48 -2
  231. package/dist/runs.js +61 -2
  232. package/dist/server-secret-env.d.ts +44 -18
  233. package/dist/server-secret-env.js +49 -8
  234. package/dist/session-incarnation.d.ts +15 -5
  235. package/dist/session-incarnation.js +16 -9
  236. package/dist/session-titler.js +12 -2
  237. package/dist/spec-fields.d.ts +34 -18
  238. package/dist/spec-fields.js +46 -53
  239. package/dist/sql-engine-posture.d.ts +29 -4
  240. package/dist/sql-engine-posture.js +36 -1
  241. package/dist/stored-request-hooks-m20.d.ts +23 -0
  242. package/dist/stored-request-hooks-m20.js +124 -0
  243. package/dist/stored-request-tool-axes-m19.d.ts +20 -5
  244. package/dist/stored-request-tool-axes-m19.js +30 -2
  245. package/dist/task-a2a.d.ts +9 -1
  246. package/dist/task-a2a.js +73 -47
  247. package/dist/task-mcp.d.ts +54 -102
  248. package/dist/task-mcp.js +194 -92
  249. package/dist/task-settings.d.ts +47 -25
  250. package/dist/task-settings.js +46 -61
  251. package/dist/text-bidi.d.ts +3 -46
  252. package/dist/text-bidi.js +3 -3
  253. package/dist/tool-approval.d.ts +1 -1
  254. package/dist/trace/core-keyset-guard.d.ts +24 -4
  255. package/dist/trace/engine-notice-wire.d.ts +11 -3
  256. package/dist/trace/engine-notice-wire.js +8 -1
  257. package/dist/trace/ledger-events.d.ts +3 -1
  258. package/dist/trace/ledger-sink.d.ts +29 -0
  259. package/dist/trace/ledger-sink.js +15 -1
  260. package/dist/trace/model-segment.js +1 -1
  261. package/dist/trace/project.d.ts +25 -6
  262. package/dist/trace/project.js +68 -22
  263. package/dist/trace/wire-projection-faces.d.ts +80 -2
  264. package/dist/trace/wire-projection-faces.js +23 -1
  265. package/dist/user-authored-text.d.ts +7 -0
  266. package/dist/user-authored-text.js +3 -0
  267. package/dist/web-admin-client/apply-effective.js +10 -5
  268. package/dist/web-admin-client/candidate-gate.d.ts +6 -6
  269. package/dist/web-admin-client/candidate-gate.js +3 -3
  270. package/dist/web-admin-client/http-client.js +0 -6
  271. package/dist/web-admin-client/read-warnings.d.ts +13 -0
  272. package/dist/web-admin-client/read-warnings.js +10 -0
  273. package/dist/web-admin-client/skills-mcp.d.ts +3 -0
  274. package/dist/web-admin-client/skills-mcp.js +9 -6
  275. package/dist/web-admin-client/types.d.ts +3 -4
  276. package/dist/workspace-cache.d.ts +35 -0
  277. package/dist/workspace-cache.js +36 -0
  278. package/dist/workspace-space.d.ts +3 -2
  279. package/dist/workspace-space.js +2 -0
  280. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +8 -1
  281. package/package.json +4 -4
  282. package/scripts/drain-check-local.mjs +37 -3
  283. package/scripts/drain-check.mysql.sql +30 -2
  284. package/scripts/drain-check.pg.sql +29 -2
  285. package/dist/capabilities/memory-notice.d.ts +0 -138
  286. package/dist/capabilities/memory-notice.js +0 -64
  287. package/dist/capabilities/prompt.d.ts +0 -17
  288. package/dist/capabilities/prompt.js +0 -6
  289. package/dist/mcp-content-origin.d.ts +0 -34
  290. package/dist/mcp-content-origin.js +0 -10
  291. package/dist/tool-axis-words.d.ts +0 -27
  292. package/dist/tool-axis-words.js +0 -9
  293. package/dist/wire-name-bound.d.ts +0 -14
  294. package/dist/wire-name-bound.js +0 -13
@@ -0,0 +1,15 @@
1
+ export async function releaseClaimedRun(runStore, taskId, why, onFailure) {
2
+ try {
3
+ await runStore.setTerminal(taskId, "failed", null, why);
4
+ }
5
+ catch (err) {
6
+ onFailure(err, "terminal");
7
+ }
8
+ try {
9
+ await runStore.releaseIdemKey(taskId);
10
+ }
11
+ catch (err) {
12
+ onFailure(err, "idem_key");
13
+ }
14
+ }
15
+ //# sourceMappingURL=claimed-run.js.map
@@ -2,6 +2,7 @@ import { createCodedHttpError, ssoVerifiedScope } from "../security.js";
2
2
  import { gatedPrincipal } from "./principal-gate.js";
3
3
  import { DEVICE_ERROR_CODES, assertDeviceIdShape, } from "../device-store.js";
4
4
  import { buildDeviceRejection } from "../device-enrollment.js";
5
+ import { releaseClaimedRun } from "./claimed-run.js";
5
6
  export function deviceOwnerDomainOf(req, config) {
6
7
  const subject = gatedPrincipal(req, config);
7
8
  if (subject === undefined)
@@ -89,16 +90,10 @@ export async function commitPlacement(ctx, runStore, taskId, intent) {
89
90
  if (intent === undefined)
90
91
  return null;
91
92
  const enrollment = ctx.deviceEnrollment;
92
- const releaseRun = async (why) => {
93
- try {
94
- await runStore.setTerminal(taskId, "failed", null, why);
95
- await runStore.releaseIdemKey(taskId);
96
- }
97
- catch (err) {
98
- ctx.logger?.warn("device_placement_run_release_failed", { taskId, err: err instanceof Error ? err.message : String(err) });
99
- ctx.metrics?.inc("device_placement_run_release_failed_total");
100
- }
101
- };
93
+ const releaseRun = (why) => releaseClaimedRun(runStore, taskId, why, (err, step) => {
94
+ ctx.logger?.warn("device_placement_run_release_failed", { taskId, step, err: err instanceof Error ? err.message : String(err) });
95
+ ctx.metrics?.inc("device_placement_run_release_failed_total");
96
+ });
102
97
  if (!enrollment) {
103
98
  await releaseRun("device placement could not be committed: the device lane's admission chain is not wired");
104
99
  throw new Error("commitPlacement: a device placement intent was minted without a DeviceEnrollment — assembly bug (both are read from the same ServiceDeps)");
@@ -18,10 +18,6 @@ import type { IncomingMessage, ServerResponse } from "node:http";
18
18
  import type { ServiceConfig } from "../config-types.js";
19
19
  import type { RouteCtxBase, RunRegistry } from "./route-ctx.js";
20
20
  import type { FlatServiceDeps } from "./server.js";
21
- /** 「这台部署配了任何一把 service 凭证吗」—— fail-closed 写门族的真值源。**单一属主**:`handle()` 的
22
- * 凭证门、[ref] 的 parked 赎回席、以及 [ref] 的 consolidation 阀门启动期告警读同一句(手抄迟早
23
- * 只改一处,而这条判据决定的是「无凭证部署上写口开不开」)。导出仅为最后一个消费点(main.ts)。 */
24
- export declare function hasServiceAuth(config: ServiceConfig): boolean;
25
21
  /**
26
22
  * 🔴 S-672(test T-72 [ref];主会话裁 ①,分诊 [ref])—— 全局服务凭据门**之前**那几族只读路
27
23
  * (`route-table.ts` 里 `position: "read-credential"` 的行)的**唯一**鉴权谓词:「服务凭据 ∨ 已验 principal」。
@@ -8,11 +8,8 @@ import { sendJson, sendError, isSseResponse, endSseWithError } from "./send.js";
8
8
  import { admissionRefusalOf, readinessOf, healthReadinessKeys, sendReadyz, sendBillableRefusal, billableRefusalOf } from "./readiness.js";
9
9
  import { ENGINE_SIGNAL_KEY, buildEngineSignal, fleetMembershipKeys, healthConfigLedgerKeys, healthDrainingKeys, mintEngineSignal, restartActionable } from "../fleet/engine-signal.js";
10
10
  import { buildHealthSelfReport } from "./health-self-report.js";
11
- import { authorized, systemFor, gatedPrincipal, sendPrincipalRefusal, sendUnauthorized, verifiedIdentitySource } from "./principal-gate.js";
11
+ import { authorized, systemFor, gatedPrincipal, sendPrincipalRefusal, sendUnauthorized, verifiedIdentitySource, hasServiceAuth, devOpenDeployment } from "./principal-gate.js";
12
12
  const PROCESS_STARTED_AT_MS = Date.now() - Math.round(process.uptime() * 1000);
13
- export function hasServiceAuth(config) {
14
- return Boolean(config.authToken) || Object.keys(config.authTokens ?? {}).length > 0;
15
- }
16
13
  export function readCredentialOk(req, config) {
17
14
  if (!hasServiceAuth(config))
18
15
  return true;
@@ -149,9 +146,9 @@ export function createDispatch(ctx) {
149
146
  configHash,
150
147
  pid: process.pid,
151
148
  startedAt: PROCESS_STARTED_AT_MS,
152
- port: deps.config.port,
149
+ port: req.socket.localPort,
153
150
  ...(deps.config.directDoorActive ? { directDoorActive: true } : {}),
154
- ...buildHealthSelfReport(deps, hasServiceAuth(deps.config)),
151
+ ...buildHealthSelfReport(deps),
155
152
  ...(signal.restart ? { ...(restartActionable(signal) ? { restartRequired: true } : {}), restart: signal.restart } : {}),
156
153
  ...(() => {
157
154
  const configHandoff = deps.configHandoffState?.();
@@ -188,10 +185,9 @@ export function createDispatch(ctx) {
188
185
  sendError(res, 404, "feature.metrics_disabled", "metrics disabled");
189
186
  return;
190
187
  }
191
- const { authToken: t, metricsToken: mt } = deps.config;
192
- const anyT = Boolean(t) || Object.keys(deps.config.authTokens ?? {}).length > 0;
193
- const devOpen = !anyT && !mt && !deps.config.requirePrincipal;
194
- const ok = devOpen || (anyT && systemFor(req, deps.config) !== undefined) || (!!mt && authorized(req, mt));
188
+ const mt = deps.config.metricsToken;
189
+ const devOpen = devOpenDeployment(deps) && !mt;
190
+ const ok = devOpen || systemFor(req, deps.config) !== undefined || (!!mt && authorized(req, mt));
195
191
  if (!ok) {
196
192
  sendUnauthorized(res, "operator");
197
193
  return;
@@ -72,7 +72,8 @@ export type DurableRunLegOutcome = {
72
72
  /**
73
73
  * 建行 + 驱动。返回 `202` = 新行已建并在跑(体带 `taskId`/`sessionId`);`409` = 会话被别的 run 占着,
74
74
  * **没有**建行(体 = `buildActiveRunConflict` 的既有形,含 `errorCode: "conflict.session_active_run"`);
75
- * `existing` = 这次提交的钥匙(`idem.key` / `clientTaskId`)已经指向一条行,没建新行。
75
+ * `existing` = 这次提交的钥匙(`idem.key` / `clientTaskId`)已经指向一条行,没建新行;落点拒(device 车道)与续跑输入一行装不下
76
+ * (`413 request.payload_too_large`,`submit-ctx.ts`)= 行建了又撤了(终结 + 放认领 + 放幂等键),什么都没跑。
76
77
  * 协议渲染归调用方(HTTP `sendJson` / A2A 协议对象 / `/decide` 的受理体)。
77
78
  */
78
79
  export declare function mintAndDriveDurableRun(ctx: RouteCtxBase, runStore: RunStore, args: DurableRunLegArgs): Promise<DurableRunLegOutcome>;
@@ -8,7 +8,7 @@ import { commitPlacement, devicePlacementErrorBody } from "./device-placement.js
8
8
  import { commitSessionInheritEnv } from "./session-inherit-env.js";
9
9
  import { clearTurnActivity, readTurnActivityMs } from "../turn-activity.js";
10
10
  import { recordResourceWindow } from "../resource-window.js";
11
- import { buildCheckpointCtx } from "./checkpoint-ctx.js";
11
+ import { persistSubmitCtx } from "./submit-ctx.js";
12
12
  import { serviceVersion } from "../service-version.js";
13
13
  export async function mintAndDriveDurableRun(ctx, runStore, args) {
14
14
  const { deps } = ctx;
@@ -28,10 +28,12 @@ export async function mintAndDriveDurableRun(ctx, runStore, args) {
28
28
  const placementReject = await commitPlacement(deps, runStore, taskId, args.prepared.bindingIntent);
29
29
  if (placementReject)
30
30
  return { status: placementReject.status, body: devicePlacementErrorBody(placementReject) };
31
- commitSessionInheritEnv(deps, args.prepared.spec.sessionId, args.prepared.body);
32
31
  if (deps.checkpointStore) {
33
- await deps.checkpointStore.putCtx(sessionId, buildCheckpointCtx({ body: args.prepared.body, memoryScope: args.prepared.auth?.memoryScope, writerVersion: serviceVersion() }));
32
+ const ctxRefusal = await persistSubmitCtx(deps.checkpointStore, deps, { runStore, taskId }, sessionId, { body: args.prepared.body, memoryScope: args.prepared.auth?.memoryScope, writerVersion: serviceVersion() });
33
+ if (ctxRefusal)
34
+ return ctxRefusal;
34
35
  }
36
+ commitSessionInheritEnv(deps, args.prepared.spec.sessionId, args.prepared.body);
35
37
  const anchorOwner = args.prepared.auth?.principal ?? null;
36
38
  const getLeafId = deps.sessionStorage?.getLeafId?.bind(deps.sessionStorage);
37
39
  const captureTurnAnchor = deps.resumeAnchorStore && getLeafId
@@ -4,10 +4,12 @@
4
4
  *
5
5
  * ## 规则(一条):自报位是**投影**,不是第二份规则
6
6
  * 每一位读的是**门本身读的那只谓词 / 那只取值口**,本模块不判任何东西:
7
- * · `auth.required` = 全局凭据门的武装谓词(`hasServiceAuth`,由 `dispatch.ts` 交进来 —— 门与本位读同一次求值的同一只函数);
8
- * · `auth.kinds` = {@link IDENTITY_SOURCES}(`verifiedIdentitySource` 的回值闭集)里,这台部署的装配**能**回的那几个词:
7
+ * · `auth.kinds` = `IDENTITY_SOURCES`(`verifiedIdentitySource` 的回值闭集)里,这台部署的装配**能**回的那几个词 —— 取唯一读法 `wiredIdentitySources`
8
+ * (`principal-gate.ts`;「这台部署有没有身份边界」的门 `devOpenDeployment` 读同一只):
9
9
  * `service_token` ⇐ 全局门武装(`systemFor` 只在配了 service token 时回得出值)· `sso` ⇐ 鉴权桥装了(入口预认证只在
10
10
  * `webAdminJwtVerifier` 在场时挂身份)· `direct_door_jwt` ⇐ 直连门开着(`verifiedIdentitySource` 那一臂的第一个合取项);
11
+ * · `auth.required` = kinds 里有没有 `service_token` —— 那一词接没接线就是全局凭据门的武装谓词(`hasServiceAuth`;`wiredIdentitySources` 的
12
+ * `service_token` 臂读的正是它),所以 `required` 与 `kinds` 是同一次求值的两个投影,不是两个来源;
11
13
  * · `auth.identityAttach` = kinds 里有没有**带调用方身份**的一类({@link carriesCallerIdentity},穷举):`service_token` 认的是系统
12
14
  * (`systemFor` 回系统名或 null),不是人;
13
15
  * · `operator` = 能力事实 `operatorLane`(operator 面族的 501 门读的同一只;名单本身不上 wire);
@@ -56,8 +58,8 @@ export interface HealthSelfReport {
56
58
  };
57
59
  }
58
60
  /**
59
- * 三键的**唯一铸点**(`build*`:纯数据)。`credentialGateArmed` 由 `dispatch.ts` 交进来 —— 它是全局凭据门自己的那一句
60
- * (`hasServiceAuth`),本模块不另求一遍(另求就是第二个写者)。
61
+ * 三键的**唯一铸点**(`build*`:纯数据)。只读装配本身:`auth` 三位都从一次 `wiredIdentitySources` 求值投出(`required` = 其中有 `service_token`),
62
+ * 不收调用方递进来的第二份布尔 —— 一个事实一次求值。
61
63
  */
62
- export declare function buildHealthSelfReport(deps: Pick<FlatServiceDeps, "config" | "webAdminJwtVerifier" | "sessionStorage" | "runStore">, credentialGateArmed: boolean): HealthSelfReport;
64
+ export declare function buildHealthSelfReport(deps: Pick<FlatServiceDeps, "config" | "webAdminJwtVerifier" | "sessionStorage" | "runStore">): HealthSelfReport;
63
65
  //# sourceMappingURL=health-self-report.d.ts.map
@@ -1,4 +1,4 @@
1
- import { IDENTITY_SOURCES } from "./principal-gate.js";
1
+ import { wiredIdentitySources } from "./principal-gate.js";
2
2
  import { operatorLane, sessionList } from "../capability-facts.js";
3
3
  import { authBridgeIssuerOf } from "../auth-bridge.js";
4
4
  import { centerLaneConfigured } from "../web-admin-client/center-request.js";
@@ -19,29 +19,15 @@ function carriesCallerIdentity(kind) {
19
19
  }
20
20
  }
21
21
  }
22
- function identitySourceWired(kind, a) {
23
- switch (kind) {
24
- case "service_token":
25
- return a.credentialGateArmed;
26
- case "sso":
27
- return a.bridgeWired;
28
- case "direct_door_jwt":
29
- return a.directDoorActive;
30
- default: {
31
- const unknownKind = kind;
32
- throw new Error(`health self-report: unknown identity source ${String(unknownKind)}`);
33
- }
34
- }
35
- }
36
- export function buildHealthSelfReport(deps, credentialGateArmed) {
22
+ export function buildHealthSelfReport(deps) {
37
23
  const { config } = deps;
38
24
  const bridgeWired = deps.webAdminJwtVerifier !== undefined;
39
- const kinds = IDENTITY_SOURCES.filter((k) => identitySourceWired(k, { credentialGateArmed, bridgeWired, directDoorActive: config.directDoorActive }));
25
+ const kinds = wiredIdentitySources(deps);
40
26
  const issuer = authBridgeIssuerOf(config)?.issuer;
41
27
  const webBaseUrl = config.webAdmin?.webBaseUrl;
42
28
  const sessionsVisible = centerLaneConfigured(config) && config.fleetAdvertiseAddress !== undefined && bridgeWired && sessionList(deps).available ? (config.requirePrincipal ? true : "unknown") : false;
43
29
  return {
44
- auth: { required: credentialGateArmed, kinds, identityAttach: kinds.some(carriesCallerIdentity) ? "available" : "unavailable" },
30
+ auth: { required: kinds.includes("service_token"), kinds, identityAttach: kinds.some(carriesCallerIdentity) ? "available" : "unavailable" },
45
31
  operator: operatorLane(deps).available ? "present" : "absent",
46
32
  center: {
47
33
  ...(issuer !== undefined ? { issuer: buildDisplaySafeUrl(issuer) } : {}),
@@ -92,20 +92,37 @@ export declare function sendUnauthorized(res: ServerResponse, face: Unauthorized
92
92
  * 那样「谁多给了 operator 一条路」在代码上看得见,而不是被偷偷塞进共享谓词里。
93
93
  */
94
94
  export declare function runOwnerReadable(principal: string | undefined, owner: string | null, config: ServiceConfig): boolean;
95
+ /** 「这台部署配了任何一把 service 凭证吗」—— fail-closed 写门族的真值源,也是身份来源 `service_token` 接没接线的那一句。**单一属主**:`handle()` 的
96
+ * 凭证门、[ref] 的 parked 赎回席、[ref] 的 consolidation 阀门启动期告警、以及下面 {@link wiredIdentitySources} 读同一句(手抄迟早只改一处,而这条判据
97
+ * 决定的是「无凭证部署上写口开不开」)。住在本叶(与 {@link systemFor} 同处)而不是 `dispatch.ts`:身份原语只依赖本叶。 */
98
+ export declare function hasServiceAuth(config: ServiceConfig): boolean;
99
+ /** {@link wiredIdentitySources} 读的装配:部署配置 + 入口预认证的鉴权桥(装了才挂身份;`FlatServiceDeps` 结构上满足)。 */
100
+ export interface IdentityWiring {
101
+ readonly config: ServiceConfig;
102
+ readonly webAdminJwtVerifier?: object | undefined;
103
+ }
95
104
  /**
96
- * 🔴 **「这台部署有没有身份边界」的唯一判据**(S-1452)。dev-open = 没配 service token(单一或 per-system)、没配只读 trace token、
97
- * 也没开 `REQUIRE_PRINCIPAL`:请求里没有任何东西替主体头作保,头是谁都能自报的**标签**(过滤 / 展示用),不是身份。
98
- * 三处读它、不各写一份:会话列表的 fleet-wide 第一臂(`isFleetWide`)、会话写面的 fleet-wide 第一臂(`sessionOwnerScopeForWrite`)、
99
- * 会话面的属主谓词({@link sessionOwnerReadable})—— 于是「调用方是谁」在列表与单会话面上只有一只读法。
105
+ * 🔴 **「这台部署的装配能回哪几个身份来源」的唯一读法**({@link IDENTITY_SOURCES} 里接了线的那几个词,序同表)。两处读它、不各写一份:
106
+ * `/health.auth`(自报位,`http/health-self-report.ts`:`kinds` 与 `required` 同一次求值 —— `required` = 其中有 `service_token`)与 {@link devOpenDeployment}(门)
107
+ * —— 于是「这台部署认不认身份、门武没武装」在自报面与门上只有一只答案。
100
108
  */
101
- export declare function devOpenDeployment(config: ServiceConfig): boolean;
109
+ export declare function wiredIdentitySources(w: IdentityWiring): readonly IdentitySource[];
110
+ /**
111
+ * 🔴 **「这台部署有没有身份边界」的唯一判据**(S-1452;S-1509 改读身份来源)。dev-open = 装配**没接任何身份来源**({@link wiredIdentitySources} 为空:
112
+ * 没配 service token、没装鉴权桥、没开直连门)、**也没声明**只读 trace token 或 `REQUIRE_PRINCIPAL`:请求里没有任何东西替主体头作保,头是谁都能自报的
113
+ * **标签**(过滤 / 展示用),不是身份。装了鉴权桥的部署(SSO 单开)带登录令牌的调用方是验过的身份 ⇒ 不是 dev-open(修前只看 token / `REQUIRE_PRINCIPAL`,
114
+ * 这一形被读成 dev-open,任何人 fleet-wide 读全部会话)。
115
+ * 四处读它、不各写一份:会话列表的 fleet-wide 第一臂(`isFleetWide`)、会话写面的 fleet-wide 第一臂(`sessionOwnerScopeForWrite`)、
116
+ * 会话面的属主谓词({@link sessionOwnerReadable})、`/metrics` 族的免凭据臂(`dispatch.ts`)—— 于是「调用方是谁」在列表、单会话面与指标面上只有一只读法。
117
+ */
118
+ export declare function devOpenDeployment(w: IdentityWiring): boolean;
102
119
  /**
103
120
  * **「这个调用方能不能动这只会话」的唯一谓词**(会话面:视图 / head / 事件流 / 记忆状态与自助抹除 / 改名置顶 / 后台任务 / MCP 重连 /
104
121
  * 面板的上一腿 MCP 投影)。= {@link devOpenDeployment} ∨ {@link runOwnerReadable}:没有身份边界的部署上不钉任何人(与会话列表同读,
105
122
  * 列表把这只会话列给谁,谁就读得到它);有身份边界的部署上就是 run 面那一条谓词,一字不变。
106
123
  * run 面(poll / events / cancel …)不读本谓词 —— 那是 run 资源的门,另一只。
107
124
  */
108
- export declare function sessionOwnerReadable(principal: string | undefined, owner: string | null, config: ServiceConfig): boolean;
125
+ export declare function sessionOwnerReadable(principal: string | undefined, owner: string | null, w: IdentityWiring): boolean;
109
126
  /** The EXPLICIT operator gate (IMAGE-API-DESIGN.md §P2.4a MUST-FIX) — NOT the bare `isOperator` (whose empty-list
110
127
  * "true-for-all" is an unrestricted-write exec door the instant OPERATOR_PRINCIPALS is empty). Requires a non-empty
111
128
  * operator set AND a present principal IN it. The same form the preempt/resume endpoints already use.
@@ -93,12 +93,31 @@ export function runOwnerReadable(principal, owner, config) {
93
93
  return true;
94
94
  return !config.requirePrincipal && owner === "anon:shell-live" && principal === undefined;
95
95
  }
96
- export function devOpenDeployment(config) {
97
- const anyServiceToken = Boolean(config.authToken) || Object.keys(config.authTokens ?? {}).length > 0;
98
- return !anyServiceToken && !config.traceToken && !config.requirePrincipal;
96
+ export function hasServiceAuth(config) {
97
+ return Boolean(config.authToken) || Object.keys(config.authTokens ?? {}).length > 0;
98
+ }
99
+ function identitySourceWired(kind, w) {
100
+ switch (kind) {
101
+ case "service_token":
102
+ return hasServiceAuth(w.config);
103
+ case "sso":
104
+ return w.webAdminJwtVerifier !== undefined;
105
+ case "direct_door_jwt":
106
+ return w.config.directDoorActive;
107
+ default: {
108
+ const unknownKind = kind;
109
+ throw new Error(`identity sources: unknown identity source ${String(unknownKind)}`);
110
+ }
111
+ }
112
+ }
113
+ export function wiredIdentitySources(w) {
114
+ return IDENTITY_SOURCES.filter((k) => identitySourceWired(k, w));
115
+ }
116
+ export function devOpenDeployment(w) {
117
+ return wiredIdentitySources(w).length === 0 && !w.config.traceToken && !w.config.requirePrincipal;
99
118
  }
100
- export function sessionOwnerReadable(principal, owner, config) {
101
- return devOpenDeployment(config) || runOwnerReadable(principal, owner, config);
119
+ export function sessionOwnerReadable(principal, owner, w) {
120
+ return devOpenDeployment(w) || runOwnerReadable(principal, owner, w.config);
102
121
  }
103
122
  export function explicitOperatorOk(principal, operatorPrincipals) {
104
123
  return operatorPrincipals.length > 0 && principal !== undefined && operatorPrincipals.includes(principal);
@@ -0,0 +1,21 @@
1
+ /**
2
+ * **全仓唯一的拒体组句器**(零依赖叶子模块 —— `http/checkpoint-ctx.ts` 的续跑折叠也读它,而那个模块经编译产物进随包排空脚本,闭包只许 node 内建 / 引擎包根)。
3
+ *
4
+ * 一只拒 = 三件事:机读码、给人读的句子、拒的机读事实(`field` / `reason` / `count` / `cap` / `detail` …;缺席 = 没有可机读的事实)。
5
+ * 拒体 = `{ error, errorCode, ...facts }`(机读事实原样随体、排在最后)。整面拒(`http/send.ts` 的 `sendError` / `sendHttpError`)、把同一只拒当**数据**带进 200 体的面板
6
+ * (`GET /v1/sessions/:id/mcp` 的 `scenarioRefusal`)、续跑把一只拒折成 409 的 `resume_blocked_by_policy`(`http/checkpoint-ctx.ts`)读的都是它 —— 同一只拒在几处渲出的成员逐字同。
7
+ * 判据只交这份数据,不自己拼体。
8
+ */
9
+ export interface Refusal {
10
+ readonly errorCode: string;
11
+ readonly message: string;
12
+ readonly facts?: Readonly<Record<string, unknown>>;
13
+ }
14
+ /** {@link refusalBody} 的产物形:`error` + `errorCode` 恒在,机读事实随后。 */
15
+ export type RefusalBody = {
16
+ readonly error: string;
17
+ readonly errorCode: string;
18
+ } & Record<string, unknown>;
19
+ /** 拒体:`{ error, errorCode, ...facts }`。 */
20
+ export declare function refusalBody(r: Refusal): RefusalBody;
21
+ //# sourceMappingURL=refusal-body.d.ts.map
@@ -0,0 +1,4 @@
1
+ export function refusalBody(r) {
2
+ return { error: r.message, errorCode: r.errorCode, ...r.facts };
3
+ }
4
+ //# sourceMappingURL=refusal-body.js.map
@@ -7,6 +7,33 @@ import type { IncomingMessage, ServerResponse } from "node:http";
7
7
  import type { FlatServiceDeps } from "./server.js";
8
8
  import type { LeaseRefusalAdmission } from "../fleet-lease.js";
9
9
  import { type QuotaDecision } from "../observability/cost-quota.js";
10
+ import { type SqlWallSource, type SqlWallsReading } from "../plugins/sql-driver.js";
11
+ /**
12
+ * 受理口对一份提交体的库墙判决:被哪一堵墙拒、量到多少、门限多少。提交体要整只落进库 —— 会话日志的用户消息、durable 审批部署上的续跑输入 ——
13
+ * 所以每堵墙各量各的、**量的是店真正发出去的东西**,`null` 的墙不参与(PG 两堵都是 `null`;InnoDB 的行墙是 `null`、包墙在;TiDB 两堵都在):
14
+ * · `row` —— 行里存的是体的**原字节**(不转义):体的 JSON 字节 ≤ `rowWallPayloadBytes(行墙, "json")`(行墙 − JSON 行的行内余量);
15
+ * · `packet` —— 体经 mysql2 以一只字符串字面进语句,`" ' \` 与控制字符各多一个反斜杠字节:**转义后的语句字节**
16
+ * (`escapedStatementBytes`,镜像被钉在 mysql2 自己的 `escape` 上)≤ `packetWallStatementBytes(包墙)`(包墙 − 语句余量)。提交体按原样进语句、
17
+ * 没有 blob 十六进制字面的 2× 膨胀,所以不折半;但引号 / 反斜杠密集的体会比自己的字节数长(至多 2×),这正是不能量原字节的原因。
18
+ * 两腿的折算与 SQL 快照 blob 的缺省帽读同一份(`plugins/sql-driver.ts`,余量各只在那里定义一次)。两腿都拒时报行墙那条(行墙句逐字沿用)。
19
+ * 入参是**已判好的墙**(`sql-driver.ts` `sqlWallsOf` —— 事实缺席时的落点只在那里定义,与 blob 帽同一只判官、同一个方向);本函数不读事实、不判缺席,
20
+ * 墙从哪来(`sources` 里**被拒的那一堵**的出处)原样带进判决(`wallSource`),拒句按它分两形:库报的墙点名库的上限与旋钮,假定的墙(事实还没建立,或事实在场但这一堵读失败、退了文档缺省)只说「库的这个上限没读到、按最保守的墙拒」。
21
+ * 两堵墙都不存在(PG / 没有 SQL 后端)⇒ `null`。一堵墙「够不着」(行墙门限 ≥ 请求体上限;包墙门限 ≥ 请求体上限的 2×,即转义上界也到不了)时那一腿不量,
22
+ * 两腿都够不着就连 `JSON.stringify` 都不调 —— 默认的 PG / MySQL 8(包墙 64 MiB)/ 无 SQL 后端部署零额外开销。
23
+ * 受理口(`admission.ts` `prepareSpec`)按它答 413 `request.payload_too_large`,句子按 `by` × `wallSource` 点名真正拒绝的那堵墙;两位都是闭集,加一臂 = 编译红。
24
+ */
25
+ export interface SubmitBodyRefusal {
26
+ readonly by: "row" | "packet";
27
+ /** 这一腿量到的字节:`row` = 体的 JSON 字节;`packet` = 转义后的语句字节。 */
28
+ readonly bytes: number;
29
+ /** 这一腿的门限(同单位)。 */
30
+ readonly limitBytes: number;
31
+ /** 拒它的那堵墙(`maxRowBytes` / `maxPacketBytes`),进句子。 */
32
+ readonly wallBytes: number;
33
+ /** 拒它的那堵墙从哪来(`sqlWallsOf` 读数 `sources` 里同一堵的出处;上 wire 为拒体的 `wallSource`):`reported` = 库自己报的;`assumed` = 不是库报的(事实还没建立,或这一堵读失败、退了文档缺省)。 */
34
+ readonly wallSource: SqlWallSource;
35
+ }
36
+ export declare function refuseSubmitBody(walls: SqlWallsReading, body: unknown): SubmitBodyRefusal | null;
10
37
  /**
11
38
  * S-866 ③ —— 租约拒绝的应答形({@link leaseRefusalOf} 的产物;`{status, body}` 交回形 + 镜像头用的 `retryAfterSec`)。
12
39
  * 体键序固定 `error, errorCode, …`(冻结门 `FROZEN_RESULT_BODY` 按这个字面形锚)。
@@ -4,7 +4,28 @@ import { RUN_NOT_FOUND_MESSAGE, SESSION_NOT_FOUND_MESSAGE } from "./route-ctx.js
4
4
  import { sendError } from "./send.js";
5
5
  import { authorized, systemFor, gatedPrincipal, explicitOperatorOk, runOwnerReadable, sessionOwnerReadable, devOpenDeployment, sendPrincipalRefusal } from "./principal-gate.js";
6
6
  import { quotaWindowResetsAt } from "../observability/cost-quota.js";
7
+ import { escapedStatementBytes, packetWallStatementBytes, rowWallPayloadBytes } from "../plugins/sql-driver.js";
7
8
  const MAX_BODY = 8 * 1024 * 1024;
9
+ export function refuseSubmitBody(walls, body) {
10
+ const rowWall = walls.maxRowBytes;
11
+ const packetWall = walls.maxPacketBytes;
12
+ const row = rowWall === null ? null : { wallBytes: rowWall, limitBytes: rowWallPayloadBytes(rowWall, "json") };
13
+ const packet = packetWall === null ? null : { wallBytes: packetWall, limitBytes: packetWallStatementBytes(packetWall) };
14
+ const liveRow = row !== null && row.limitBytes < MAX_BODY ? row : null;
15
+ const livePacket = packet !== null && packet.limitBytes < 2 * MAX_BODY ? packet : null;
16
+ if (liveRow === null && livePacket === null)
17
+ return null;
18
+ const json = JSON.stringify(body);
19
+ const jsonBytes = Buffer.byteLength(json, "utf8");
20
+ if (liveRow !== null && jsonBytes > liveRow.limitBytes)
21
+ return { by: "row", bytes: jsonBytes, ...liveRow, wallSource: walls.sources.maxRowBytes };
22
+ if (livePacket !== null && jsonBytes * 2 > livePacket.limitBytes) {
23
+ const sentBytes = escapedStatementBytes(json);
24
+ if (sentBytes > livePacket.limitBytes)
25
+ return { by: "packet", bytes: sentBytes, ...livePacket, wallSource: walls.sources.maxPacketBytes };
26
+ }
27
+ return null;
28
+ }
8
29
  export function leaseRefusalOf(adm) {
9
30
  switch (adm.reason) {
10
31
  case "budget_exhausted":
@@ -151,7 +172,7 @@ export function createRequestGuards(ctx) {
151
172
  }
152
173
  function isFleetWide(req) {
153
174
  const tt = deps.config.traceToken;
154
- return devOpenDeployment(deps.config) || systemFor(req, deps.config) !== undefined || (!!tt && authorized(req, tt));
175
+ return devOpenDeployment(deps) || systemFor(req, deps.config) !== undefined || (!!tt && authorized(req, tt));
155
176
  }
156
177
  function sessionOwnerScope(req) {
157
178
  const fleetWide = isFleetWide(req);
@@ -159,7 +180,7 @@ export function createRequestGuards(ctx) {
159
180
  return { fleetWide, gateOwner };
160
181
  }
161
182
  function sessionOwnerScopeForWrite(req) {
162
- const fleetWide = devOpenDeployment(deps.config) || systemFor(req, deps.config) !== undefined;
183
+ const fleetWide = devOpenDeployment(deps) || systemFor(req, deps.config) !== undefined;
163
184
  const gateOwner = fleetWide ? null : (gatedPrincipal(req, deps.config) || null);
164
185
  return { fleetWide, gateOwner };
165
186
  }
@@ -179,7 +200,7 @@ export function createRequestGuards(ctx) {
179
200
  return ownerGate(req, res, (principal) => runOwnerReadable(principal, owner, deps.config), () => sendError(res, 404, "not_found.run", RUN_NOT_FOUND_MESSAGE));
180
201
  }
181
202
  function sessionOwnerOk(req, res, owner) {
182
- return ownerGate(req, res, (principal) => sessionOwnerReadable(principal, owner, deps.config), () => sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE));
203
+ return ownerGate(req, res, (principal) => sessionOwnerReadable(principal, owner, deps), () => sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE));
183
204
  }
184
205
  const sessionAssertAbsentWarned = new Set();
185
206
  function runSessionAcceptOk(req, res, run, face, notFoundError = RUN_NOT_FOUND_MESSAGE) {
@@ -22,12 +22,37 @@ import { type TrustedRunInternals } from "./durable-run-leg.js";
22
22
  import type { RunRegistry, RouteHelpers, RouteLegs } from "./route-ctx.js";
23
23
  import type { createRequestGuards } from "./request-guards.js";
24
24
  import type { FlatServiceDeps } from "./server.js";
25
+ import { type ResumeRefusalAnswer } from "./checkpoint-ctx.js";
25
26
  /**
26
27
  * 🔴 **一次 `CheckpointError` 拒的处置 —— 唯一读口**:{@link CHECKPOINT_ERROR_DISPOSITION} 的读法(doctrine 在那张表头)。resume 腿的 catch 臂只调它一次,
27
28
  * 行的落点与 wire 的 `retriable` 都读这一个结果。纯函数,钉直接调。运行期遇到表外的码(装树 core 比本二进制多一个码的版本偏斜 —— 型面上拼不出来)
28
29
  * ⇒ `"terminal"`:与 `by_reason` 认不得的词同一条 fail-closed 方向(不宽容默认成可重试),也与修前手列串的缺省逐字相同。
29
30
  */
30
31
  export declare function resumeRefusalDisposition(e: CheckpointError): "cancelled" | "retriable" | "terminal";
32
+ /**
33
+ * 🔴 **引擎拒一次续跑(`CheckpointError`)的 HTTP 答复 —— 唯一组句器**(S-1690 / S-1692,core 7.52.0;五条续跑腿〔`/decide` · `/resume` · `/wake` · `plan_review` ·
34
+ * workflow 决定在宿主会话上起的新一跑〕同经 `driveResumeLeg` 的 catch 臂到这里,SLA 拒扫腿与 PARKED 赎回席经 `resumeCheckpoint` 同得)。
35
+ *
36
+ * 规则(一条):**一次拒 = 一个答复形,与「谁发现的」无关**。引擎在认领前 / CAS 前拒的,与本服务在重放存量体之前自己拒的(`resume_blocked_by_policy`,
37
+ * `checkpoint-ctx.ts` `resumeBlockedByPolicyAnswer`;续跑入口上卡仍 pending ⇒ 同样带 `retriable:true`)读同一组机读键;体经拒体组句器 `refusalBody` 铸,
38
+ * `retriable` 经续跑家族的唯一写点 `resumeRefusalWithCard` 追加。
39
+ * 一处如实例外:后台停驻子代的 parked 赎回不经本函数 —— 引擎的赎回回执只交成因词,`parked-decide.ts` 答 `422 parked_resume.startup_failed`,体只带 `cause`:
40
+ * · `errorCode` —— 引擎码的原码镜像(`checkpoint.invalid_outcome` 按决断成因改铸,{@link decisionRefusalCauseOf});
41
+ * · 引擎 `detail` 的三个机读判别位原样过境,键名与 `resume_blocked_by_policy` 同义:`detail.code` ⇒ `blockedBy`(挡住它的准入码 = 同一份坏配置在新跑上报的 `errorCode`;
42
+ * 今天只有 `checkpoint.config_refused` 带它)· `detail.field` ⇒ `field`(哪一侧 / 哪一格)· `detail.reason` ⇒ `reason`(同码多臂时哪一臂,闭集 `CheckpointErrorReason`)。
43
+ * 缺席不造键(引擎没给就是没给)。`detail` 的其余成员(`requires` / `differs`)不在这组判别位里,不上 wire;`retryAfterMs` 折成族口径的 `retryAfterSec`(下面);
44
+ * · `retriable` —— 只在处置读口答 `retriable` 时在场(卡仍 pending、行重新 park;**不是**「原样重试会成功」—— 契约附录 A.1 注那一个意思,壳按它决定卡还挂不挂着)。
45
+ * 出路(改那一格再决 / 只能在停它的那一版 worker 上跑完或取消 / 等一会)由 `errorCode` + `field` + `blockedBy` + `reason` 与引擎的句子说;
46
+ * · `error` —— 引擎的句子原样(它点名条目、哪一臂与出路);本层不另铸第二句;
47
+ * · `taskId`(抢到行才在)/ `retryAfterSec`(两码闭集)/ 两版本键(只在 `checkpoint.unsupported_version`:调用方按那一码交 `buildCheckpointVersionKeys` 的产物,本函数原样铺)—— 键律照旧。
48
+ * 纯函数(钉直接调)。`retriable` 由调用方传入 —— 行的落点与 wire 的 `retriable` 读同一次求值(catch 臂一处),本函数不另判。
49
+ */
50
+ export declare function checkpointRefusalAnswer(e: CheckpointError, at: {
51
+ readonly gate: string;
52
+ readonly retriable: boolean;
53
+ readonly taskId?: string;
54
+ readonly versionKeys: Readonly<Record<string, string>>;
55
+ }): ResumeRefusalAnswer;
31
56
  export declare function createResumeLegs(ctx: {
32
57
  deps: FlatServiceDeps;
33
58
  runnerFor: FlatServiceDeps["runnerFor"];
@@ -22,7 +22,7 @@ import { TERMINAL_PARK_GATE_KINDS } from "../plugins/checkpoint-store-sql.js";
22
22
  import { withPrincipal } from "../observability/principal-context.js";
23
23
  import { withModelReachScope } from "../model-reach-scope.js";
24
24
  import { createInstructionSelectionLeg } from "../instruction-selection.js";
25
- import { modelTextEventData, turnEndEventData, contextUsageEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskStartedEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, toolRosterDeltaEventData, toolDisclosureEventData, humanInputEventData, appendModelUsageDelta, appendPromptManifest, attachModelUsage } from "../trace/project.js";
25
+ import { modelTextEventData, turnEndEventData, contextUsageEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskStartedEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, compactionStartedEventData, workspaceChangedEventData, wiringManifestEventData, toolRosterDeltaEventData, toolDisclosureEventData, humanInputEventData, appendModelUsageDelta, appendPromptManifest, attachModelUsage } from "../trace/project.js";
26
26
  import { redactSecrets } from "../trace/redact.js";
27
27
  import { buildRunObjectivePreview } from "../user-authored-text.js";
28
28
  import { createForwardAppendFailedNote, createLedgerSink, createSerialLedgerAppend } from "../trace/ledger-sink.js";
@@ -45,9 +45,10 @@ import { commitSessionInheritEnv } from "./session-inherit-env.js";
45
45
  import { ASK_USER_QUESTION_TOOL_NAME } from "../approval-content-kind.js";
46
46
  import { deploymentListAsks } from "../approval.js";
47
47
  import { readResumeCtx, resumeCtxCorruptAnswer } from "./resume-ctx-read.js";
48
- import { buildCheckpointVersionKeys, resumeBlockedByPolicyAnswer, storedBodyReplayAnswer, storedBodyReplayLogFields, withPermissionModeRewritten } from "./checkpoint-ctx.js";
48
+ import { buildCheckpointVersionKeys, resumeBlockedByPolicyAnswer, resumeRefusalWithCard, storedBodyReplayAnswer, storedBodyReplayLogFields, withPermissionModeRewritten } from "./checkpoint-ctx.js";
49
+ import { refusalBody } from "./refusal-body.js";
49
50
  import { storedBodyReplayRefusal } from "./stored-body-replay.js";
50
- import { httpErrorCode } from "./send.js";
51
+ import { httpErrorRefusal } from "./send.js";
51
52
  import { createRunAcceptance } from "../run-acceptance.js";
52
53
  function legMayHaveReopenedCheckpoint(r) {
53
54
  return r.terminal.kind === "failed" && nestedPauseOf(r.terminal) === undefined;
@@ -77,6 +78,15 @@ const WORKER_SWAP_REDEEMABLE = {
77
78
  installation_record_absent: true,
78
79
  installation_record_misaligned: false,
79
80
  installation_record_invalid: false,
81
+ unknown_name: true,
82
+ unsupported_pattern: true,
83
+ not_a_list: false,
84
+ not_a_string: false,
85
+ invalid_record: false,
86
+ renamed_mount: true,
87
+ registry_occupied: true,
88
+ unknown_model_ref: true,
89
+ not_a_model_ref: true,
80
90
  };
81
91
  function checkpointRowRedeemableElsewhere(reason) {
82
92
  return reason !== undefined && WORKER_SWAP_REDEEMABLE[reason] === true;
@@ -136,6 +146,7 @@ const CHECKPOINT_ERROR_FIELD_ROLE = {
136
146
  hostDecision: "outcome_cell",
137
147
  approver: "outcome_cell",
138
148
  taskConfig: "config",
149
+ deps: "config",
139
150
  inheritedGate: "config",
140
151
  };
141
152
  const FIELD_ROLE_CAUSE = {
@@ -161,6 +172,27 @@ function decisionRefusalCauseOf(e, gate) {
161
172
  function notResumableAnswer() {
162
173
  return { status: 409, body: { error: "run is not in a resumable (suspended) state (already resumed, decided, or expired)", errorCode: "conflict.not_resumable" } };
163
174
  }
175
+ export function checkpointRefusalAnswer(e, at) {
176
+ const refusalCause = e.code === "checkpoint.invalid_outcome" ? decisionRefusalCauseOf(e, at.gate) : undefined;
177
+ const detail = e.detail;
178
+ return resumeRefusalWithCard({
179
+ status: e.code === "checkpoint.not_found" ? 404 : 409,
180
+ body: refusalBody({
181
+ message: e.message,
182
+ errorCode: e.code === "checkpoint.invalid_outcome" ? (refusalCause !== undefined ? WIRE_CODE_OF_DECISION_REFUSAL[refusalCause] : UNCLASSIFIED_INVALID_OUTCOME_CODE) : e.code,
183
+ facts: {
184
+ ...(at.taskId !== undefined ? { taskId: at.taskId } : {}),
185
+ ...(detail?.code !== undefined ? { blockedBy: detail.code } : {}),
186
+ ...(detail?.field ? { field: detail.field } : {}),
187
+ ...(detail?.reason !== undefined ? { reason: detail.reason } : {}),
188
+ ...((e.code === "resume.usage_window_exhausted" || e.code === "resume.preflight_rejected") && typeof detail?.retryAfterMs === "number" && Number.isFinite(detail.retryAfterMs) && detail.retryAfterMs > 0
189
+ ? { retryAfterSec: Math.max(1, Math.ceil(detail.retryAfterMs / 1000)) }
190
+ : {}),
191
+ ...at.versionKeys,
192
+ },
193
+ }),
194
+ }, at.retriable);
195
+ }
164
196
  export function createResumeLegs(ctx) {
165
197
  const { deps, runnerFor, idemCache, inflightRuns, preemptableRuns, steerableRuns, wakeParkMints, counters, readJson, readRawBody, rateLimited, quotaExceeded, ownerQuotaDenied, leaseDenied, ownerLeaseDenied, usageWindowDenied, safeDecode, isFleetWide, runOwnerOk, sessionOwnerOk, sessionOwnerScope, sessionOwnerScopeForWrite, runSessionAcceptOk, prepareSpec, finalizeTaskResult, ownerCostQuotaDenied, ownerFleetLeaseDenied, usageWindowDenialFor } = ctx;
166
198
  const resumeFleetScope = (req, auth) => req ? (gatedPrincipal(req, deps.config) ?? "default") : (auth.principal ?? "default");
@@ -422,11 +454,11 @@ export function createResumeLegs(ctx) {
422
454
  noteLateSettleIfAccepted(driven);
423
455
  return driven;
424
456
  }
425
- const ONE_SHOT_HISTORY_KEYS = ["resumeAt", "restoreFiles", "acceptPartialRestore", "rewindFilesTo", "rewindFiles"];
426
- const ONE_SHOT_HISTORY_STRIP = Object.fromEntries(ONE_SHOT_HISTORY_KEYS.map((k) => [k, undefined]));
457
+ const ONE_SHOT_SUBMIT_KEYS = ["resumeAt", "restoreFiles", "acceptPartialRestore", "rewindFilesTo", "rewindFiles", "userAttachments", "images"];
458
+ const ONE_SHOT_SUBMIT_STRIP = Object.fromEntries(ONE_SHOT_SUBMIT_KEYS.map((k) => [k, undefined]));
427
459
  const resolveResumeSpec = async (ctx, req, auth) => {
428
460
  const engineNotices = [];
429
- const spec = await deps.resolveSpec({ ...ctx.body, ...ONE_SHOT_HISTORY_STRIP }, req, auth, {
461
+ const spec = await deps.resolveSpec({ ...ctx.body, ...ONE_SHOT_SUBMIT_STRIP }, req, auth, {
430
462
  leg: "resume",
431
463
  onNotice: (n) => engineNotices.push(n),
432
464
  ...(ctx.unattendedLeg === true ? { unattendedLeg: true } : {}),
@@ -435,10 +467,10 @@ export function createResumeLegs(ctx) {
435
467
  };
436
468
  const refuseStoredBodyReplay = (sessionId, refusal) => {
437
469
  deps.logger?.warn?.("resume_refused_stored_body", { sessionId, ...storedBodyReplayLogFields(refusal) });
438
- return storedBodyReplayAnswer(refusal, serviceVersion());
470
+ return storedBodyReplayAnswer(refusal, serviceVersion(), true);
439
471
  };
440
472
  const resolveResumeSpecOrBlocked = async (ctx, req, auth) => {
441
- const replay = storedBodyReplayRefusal(ctx, deps.config.autonomy);
473
+ const replay = storedBodyReplayRefusal(ctx, deps.config);
442
474
  if (replay !== undefined)
443
475
  return { ok: false, answer: refuseStoredBodyReplay(auth.sessionId, replay) };
444
476
  try {
@@ -447,7 +479,7 @@ export function createResumeLegs(ctx) {
447
479
  }
448
480
  catch (e) {
449
481
  if (e instanceof HttpError) {
450
- return { ok: false, answer: resumeBlockedByPolicyAnswer(e.message, httpErrorCode(e.status, e.code), e.extra) };
482
+ return { ok: false, answer: resumeRefusalWithCard(resumeBlockedByPolicyAnswer(httpErrorRefusal(e)), true) };
451
483
  }
452
484
  throw e;
453
485
  }
@@ -909,6 +941,10 @@ export function createResumeLegs(ctx) {
909
941
  await flush();
910
942
  await append("compaction_outcome", compactionOutcomeEventData(ev));
911
943
  break;
944
+ case "compaction_started":
945
+ await flush();
946
+ await append("compaction_started", compactionStartedEventData(ev));
947
+ break;
912
948
  case "workspace_changed":
913
949
  await flush();
914
950
  await append("workspace_changed", workspaceChangedEventData(ev));
@@ -932,6 +968,7 @@ export function createResumeLegs(ctx) {
932
968
  break;
933
969
  }
934
970
  case "human_input":
971
+ anchor.onHumanInput(ev);
935
972
  await flush();
936
973
  await append("human_input", humanInputEventData(ev));
937
974
  break;
@@ -1027,7 +1064,6 @@ export function createResumeLegs(ctx) {
1027
1064
  }
1028
1065
  catch (e) {
1029
1066
  if (e instanceof CheckpointError) {
1030
- const refusalCause = e.code === "checkpoint.invalid_outcome" ? decisionRefusalCauseOf(e, outcome.gate) : undefined;
1031
1067
  const disposition = resumeRefusalDisposition(e);
1032
1068
  if (disposition === "cancelled") {
1033
1069
  await settleCardForCancel();
@@ -1060,21 +1096,7 @@ export function createResumeLegs(ctx) {
1060
1096
  settleFleet("failed");
1061
1097
  }
1062
1098
  }
1063
- const status = e.code === "checkpoint.not_found" ? 404 : 409;
1064
- return {
1065
- status,
1066
- body: {
1067
- ...(taskId !== undefined ? { taskId } : {}),
1068
- error: e.message,
1069
- errorCode: e.code === "checkpoint.invalid_outcome" ? (refusalCause !== undefined ? WIRE_CODE_OF_DECISION_REFUSAL[refusalCause] : UNCLASSIFIED_INVALID_OUTCOME_CODE) : e.code,
1070
- ...(e.detail?.field ? { field: e.detail.field } : {}),
1071
- ...((e.code === "resume.usage_window_exhausted" || e.code === "resume.preflight_rejected") && typeof e.detail?.retryAfterMs === "number" && Number.isFinite(e.detail.retryAfterMs) && e.detail.retryAfterMs > 0
1072
- ? { retryAfterSec: Math.max(1, Math.ceil(e.detail.retryAfterMs / 1000)) }
1073
- : {}),
1074
- ...(retriable ? { retriable: true } : {}),
1075
- ...(e.code === "checkpoint.unsupported_version" ? buildCheckpointVersionKeys(resumeCtx.ctx.writerVersion, serviceVersion()) : {}),
1076
- },
1077
- };
1099
+ return checkpointRefusalAnswer(e, { gate: outcome.gate, retriable, ...(taskId !== undefined ? { taskId } : {}), versionKeys: e.code === "checkpoint.unsupported_version" ? buildCheckpointVersionKeys(resumeCtx.ctx.writerVersion, serviceVersion()) : {} });
1078
1100
  }
1079
1101
  const thrownCause = cancelCtrl.cause();
1080
1102
  if (thrownCause !== undefined) {
@@ -1400,7 +1422,7 @@ export function createResumeLegs(ctx) {
1400
1422
  return refuse(resumeCtxCorruptAnswer(ctxRead.where));
1401
1423
  if (ctxRead.kind === "absent")
1402
1424
  return refuse({ status: 409, body: { error: "resume context missing — cannot rebuild task config", errorCode: "conflict.resume_context_unavailable" } });
1403
- const replay = storedBodyReplayRefusal(ctxRead.ctx, deps.config.autonomy);
1425
+ const replay = storedBodyReplayRefusal(ctxRead.ctx, deps.config);
1404
1426
  if (replay !== undefined)
1405
1427
  return refuse(refuseStoredBodyReplay(sessionId, replay));
1406
1428
  return { ok: true, prepared: { sessionId, token, cp, ctx: ctxRead.ctx } };