@sema-agent/server 7.136.0 → 7.138.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 (327) hide show
  1. package/MIGRATION.md +501 -139
  2. package/README.md +1 -0
  3. package/README.zh-CN.md +1 -0
  4. package/USAGE.md +149 -34
  5. package/deploy/sema-up/README.md +14 -0
  6. package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +7 -1
  7. package/deploy/sema-up/stack/sema-stack-cluster.yaml +7 -1
  8. package/dist/approval-card.d.ts +21 -14
  9. package/dist/approval-card.js +1 -0
  10. package/dist/approval-content-kind.d.ts +6 -0
  11. package/dist/approval-content-kind.js +7 -0
  12. package/dist/approval-gate-currency.d.ts +48 -6
  13. package/dist/approval-gate-currency.js +16 -9
  14. package/dist/approval-hmac.d.ts +13 -1
  15. package/dist/approval-hmac.js +1 -1
  16. package/dist/approval.js +3 -3
  17. package/dist/audit.js +1 -0
  18. package/dist/auth-bridge.js +2 -2
  19. package/dist/background-agent-name.d.ts +5 -0
  20. package/dist/background-agent-name.js +5 -0
  21. package/dist/boot/budget-tracing.d.ts +1 -12
  22. package/dist/boot/deployment-mods.d.ts +46 -16
  23. package/dist/boot/deployment-mods.js +82 -6
  24. package/dist/boot/execution-env.d.ts +24 -1
  25. package/dist/boot/execution-env.js +69 -6
  26. package/dist/boot/governance-seams.d.ts +10 -17
  27. package/dist/boot/governance-seams.js +5 -17
  28. package/dist/boot/leader.d.ts +3 -0
  29. package/dist/boot/leader.js +1 -0
  30. package/dist/boot/org-delivery.js +15 -2
  31. package/dist/boot/reapers.d.ts +3 -0
  32. package/dist/boot/reapers.js +15 -1
  33. package/dist/boot/resolve-spec.d.ts +7 -0
  34. package/dist/boot/resolve-spec.js +36 -26
  35. package/dist/boot/retention-lane.d.ts +11 -5
  36. package/dist/boot/retention-lane.js +2 -2
  37. package/dist/boot/runner-deps.d.ts +7 -3
  38. package/dist/boot/runner-deps.js +1 -0
  39. package/dist/boot/stage-03-store-backend.d.ts +1 -1
  40. package/dist/boot/stage-04-stores.d.ts +2 -13
  41. package/dist/boot/stage-05-execution-env.d.ts +6 -13
  42. package/dist/boot/stage-05-execution-env.js +17 -5
  43. package/dist/boot/stage-06-runners.d.ts +6 -13
  44. package/dist/boot/stage-06-runners.js +10 -3
  45. package/dist/boot/stage-07-capability-layer.d.ts +7 -13
  46. package/dist/boot/stage-07-capability-layer.js +3 -3
  47. package/dist/boot/stage-08-reapers.d.ts +7 -13
  48. package/dist/boot/stage-08-reapers.js +2 -1
  49. package/dist/boot/stage-09-leader.d.ts +7 -13
  50. package/dist/boot/stage-09-leader.js +2 -2
  51. package/dist/boot/stage-10-http-server.d.ts +7 -13
  52. package/dist/boot/stage-10-http-server.js +6 -3
  53. package/dist/boot/stores.d.ts +1 -1
  54. package/dist/boot/web-admin.d.ts +5 -2
  55. package/dist/boot/web-admin.js +3 -3
  56. package/dist/brain.js +5 -5
  57. package/dist/broker/broker.d.ts +89 -0
  58. package/dist/broker/broker.js +287 -0
  59. package/dist/broker/constants.d.ts +39 -0
  60. package/dist/broker/constants.js +15 -0
  61. package/dist/broker/http1.d.ts +111 -0
  62. package/dist/broker/http1.js +256 -0
  63. package/dist/broker/lease-book.d.ts +87 -0
  64. package/dist/broker/lease-book.js +142 -0
  65. package/dist/broker/lease-file.d.ts +60 -0
  66. package/dist/broker/lease-file.js +127 -0
  67. package/dist/broker/main.d.ts +2 -0
  68. package/dist/broker/main.js +20 -0
  69. package/dist/budget.d.ts +17 -15
  70. package/dist/budget.js +2 -2
  71. package/dist/capabilities/composite-members.d.ts +2 -1
  72. package/dist/capabilities/composite-members.js +2 -1
  73. package/dist/capabilities/engine-capabilities.js +1 -0
  74. package/dist/capabilities/mcp-probe.d.ts +36 -11
  75. package/dist/capabilities/mcp-probe.js +44 -5
  76. package/dist/capabilities/scenarios.d.ts +23 -8
  77. package/dist/capabilities/scenarios.js +13 -8
  78. package/dist/capabilities/skills.d.ts +28 -5
  79. package/dist/capabilities/skills.js +8 -0
  80. package/dist/capability-facts.d.ts +6 -0
  81. package/dist/capability-facts.js +5 -0
  82. package/dist/center-credential-renewer.js +5 -1
  83. package/dist/checkpoint-ctx-seal-m28.d.ts +3 -10
  84. package/dist/checkpoint-ctx-seal-m28.js +1 -1
  85. package/dist/checkpoint-identity.d.ts +13 -6
  86. package/dist/config-catalog.js +16 -7
  87. package/dist/config-types.d.ts +24 -7
  88. package/dist/config.d.ts +1 -1
  89. package/dist/config.js +42 -13
  90. package/dist/deployment-governance.d.ts +2 -2
  91. package/dist/deployment-governance.js +7 -9
  92. package/dist/deployment-shape.d.ts +6 -6
  93. package/dist/device-ws-protocol.d.ts +1 -0
  94. package/dist/device-ws-protocol.js +2 -1
  95. package/dist/file-history-disclosure.d.ts +4 -2
  96. package/dist/file-history-disclosure.js +2 -0
  97. package/dist/gate-settlement.d.ts +71 -0
  98. package/dist/gate-settlement.js +40 -0
  99. package/dist/hooks/classic-stations.d.ts +12 -54
  100. package/dist/hooks/classic-stations.js +1 -0
  101. package/dist/hooks/command-spawn.d.ts +2 -2
  102. package/dist/hooks/hook-runner.js +14 -14
  103. package/dist/http/admission.js +14 -9
  104. package/dist/http/approval-sweeps.js +4 -3
  105. package/dist/http/caller-trust.d.ts +2 -1
  106. package/dist/http/caller-trust.js +1 -3
  107. package/dist/http/checkpoint-ctx.d.ts +48 -7
  108. package/dist/http/checkpoint-ctx.js +40 -0
  109. package/dist/http/dispatch.d.ts +4 -0
  110. package/dist/http/dispatch.js +18 -10
  111. package/dist/http/durable-run-leg.d.ts +5 -1
  112. package/dist/http/durable-run-leg.js +5 -3
  113. package/dist/http/health-self-report.d.ts +2 -2
  114. package/dist/http/memory-compliance-failure-reply.js +3 -0
  115. package/dist/http/principal-gate.d.ts +15 -10
  116. package/dist/http/principal-gate.js +48 -9
  117. package/dist/http/resume-legs.d.ts +7 -31
  118. package/dist/http/resume-legs.js +185 -126
  119. package/dist/http/route-ctx.d.ts +38 -53
  120. package/dist/http/route-ctx.js +2 -5
  121. package/dist/http/routes/a2a-serve.js +10 -4
  122. package/dist/http/routes/agents-roster.d.ts +1 -1
  123. package/dist/http/routes/agents-roster.js +5 -1
  124. package/dist/http/routes/approvals-assistant.js +24 -10
  125. package/dist/http/routes/capabilities.js +23 -11
  126. package/dist/http/routes/mcp-reconnect.d.ts +3 -2
  127. package/dist/http/routes/mcp-reconnect.js +7 -7
  128. package/dist/http/routes/memory-consolidation.d.ts +1 -1
  129. package/dist/http/routes/memory-origin.d.ts +1 -1
  130. package/dist/http/routes/rules.js +3 -3
  131. package/dist/http/routes/runs.js +13 -8
  132. package/dist/http/routes/session-sync.js +42 -6
  133. package/dist/http/routes/sessions.js +6 -3
  134. package/dist/http/routes/side-query.js +2 -3
  135. package/dist/http/routes/tasks.js +794 -780
  136. package/dist/http/server.d.ts +35 -25
  137. package/dist/http/stored-body-replay.d.ts +3 -3
  138. package/dist/http/stored-body-replay.js +6 -1
  139. package/dist/http/wire-types.d.ts +66 -33
  140. package/dist/identity-source.d.ts +65 -0
  141. package/dist/identity-source.js +45 -0
  142. package/dist/index.d.ts +2 -0
  143. package/dist/index.js +1 -0
  144. package/dist/leader/wire.d.ts +7 -0
  145. package/dist/leader/wire.js +5 -4
  146. package/dist/mcp-assertion-header.d.ts +51 -40
  147. package/dist/mcp-assertion-header.js +10 -11
  148. package/dist/mcp-authorization-face.d.ts +27 -0
  149. package/dist/mcp-authorization-face.js +15 -0
  150. package/dist/memory-operator-faces.d.ts +13 -4
  151. package/dist/memory-operator-faces.js +1 -17
  152. package/dist/memory-store-refusal.d.ts +3 -3
  153. package/dist/memory-sync-client.js +72 -57
  154. package/dist/memory-sync.d.ts +1 -1
  155. package/dist/model-entry-refusal.d.ts +2 -0
  156. package/dist/model-entry-refusal.js +11 -0
  157. package/dist/model-route-gap.d.ts +6 -22
  158. package/dist/model-route-gap.js +7 -5
  159. package/dist/model-select.d.ts +6 -17
  160. package/dist/model-select.js +0 -3
  161. package/dist/mods-declaration.d.ts +110 -0
  162. package/dist/mods-declaration.js +109 -0
  163. package/dist/namespaced-rule-name.d.ts +1 -1
  164. package/dist/observability/err-text.d.ts +5 -0
  165. package/dist/observability/err-text.js +9 -0
  166. package/dist/observability/fail-open.d.ts +17 -1
  167. package/dist/observability/fail-open.js +17 -1
  168. package/dist/observability/metrics.js +1 -1
  169. package/dist/observability/run-terminal.js +1 -0
  170. package/dist/observability/secret-env-scrub.d.ts +1 -1
  171. package/dist/observability/secret-env-scrub.js +1 -1
  172. package/dist/operator-ask.d.ts +2 -2
  173. package/dist/org-delivery-gate.js +1 -1
  174. package/dist/org-memory-admission.d.ts +2 -2
  175. package/dist/org-memory-admission.js +39 -2
  176. package/dist/parked-decide.d.ts +15 -2
  177. package/dist/parked-decide.js +17 -10
  178. package/dist/peer-directory.js +2 -0
  179. package/dist/peer-seat.d.ts +150 -0
  180. package/dist/peer-seat.js +239 -0
  181. package/dist/permission-rule-list.d.ts +2 -2
  182. package/dist/permission-rule-vocab.d.ts +7 -7
  183. package/dist/permission-rule-vocab.js +3 -3
  184. package/dist/plain-string-record.d.ts +38 -0
  185. package/dist/plain-string-record.js +38 -0
  186. package/dist/plugins/background-agent-store-sql.js +6 -3
  187. package/dist/plugins/caching-session-store.d.ts +5 -0
  188. package/dist/plugins/caching-session-store.js +6 -0
  189. package/dist/plugins/checkpoint-store-sql.d.ts +34 -29
  190. package/dist/plugins/checkpoint-store-sql.js +14 -14
  191. package/dist/plugins/file-history-store-sql.js +10 -16
  192. package/dist/plugins/file-run-store.js +8 -2
  193. package/dist/plugins/k8s-exec-protocol.d.ts +69 -13
  194. package/dist/plugins/k8s-exec-protocol.js +116 -28
  195. package/dist/plugins/k8s-pod-delete-failure.d.ts +25 -0
  196. package/dist/plugins/k8s-pod-delete-failure.js +17 -0
  197. package/dist/plugins/k8s-pod-reaper.d.ts +106 -0
  198. package/dist/plugins/k8s-pod-reaper.js +122 -0
  199. package/dist/plugins/local-checkpoint-store.d.ts +4 -17
  200. package/dist/plugins/local-checkpoint-store.js +3 -4
  201. package/dist/plugins/local-session-store.d.ts +66 -27
  202. package/dist/plugins/local-session-store.js +60 -28
  203. package/dist/plugins/mailbox-store-sql.d.ts +11 -3
  204. package/dist/plugins/mailbox-store-sql.js +103 -15
  205. package/dist/plugins/memory-run-store.js +2 -0
  206. package/dist/plugins/memory-sync-store-pg.d.ts +16 -5
  207. package/dist/plugins/memory-sync-store-tidb.d.ts +3 -3
  208. package/dist/plugins/one-time-migrations.d.ts +34 -12
  209. package/dist/plugins/one-time-migrations.js +51 -9
  210. package/dist/plugins/permission-rule-store-file.js +2 -2
  211. package/dist/plugins/permission-rule-store-sql.d.ts +17 -17
  212. package/dist/plugins/permission-rule-store-sql.js +37 -30
  213. package/dist/plugins/pg-pool.js +4 -0
  214. package/dist/plugins/pg-session-storage.d.ts +10 -2
  215. package/dist/plugins/pg-session-storage.js +20 -7
  216. package/dist/plugins/remote-env-adb.d.ts +6 -1
  217. package/dist/plugins/remote-env-adb.js +2 -1
  218. package/dist/plugins/remote-env-device.d.ts +6 -1
  219. package/dist/plugins/remote-env-device.js +5 -1
  220. package/dist/plugins/remote-env-e2b.d.ts +5 -0
  221. package/dist/plugins/remote-env-e2b.js +2 -1
  222. package/dist/plugins/remote-env-file-error.d.ts +8 -1
  223. package/dist/plugins/remote-env-file-error.js +7 -1
  224. package/dist/plugins/remote-env-host.d.ts +10 -2
  225. package/dist/plugins/remote-env-host.js +8 -2
  226. package/dist/plugins/remote-env-k8s.d.ts +140 -11
  227. package/dist/plugins/remote-env-k8s.js +277 -15
  228. package/dist/plugins/remote-env-local-docker.d.ts +5 -0
  229. package/dist/plugins/remote-env-local-docker.js +2 -1
  230. package/dist/plugins/remote-env-ssh.d.ts +6 -1
  231. package/dist/plugins/remote-env-ssh.js +2 -1
  232. package/dist/plugins/retention-store-sql.d.ts +14 -2
  233. package/dist/plugins/retention-store-sql.js +6 -0
  234. package/dist/plugins/run-store-sql.js +10 -4
  235. package/dist/plugins/session-fork.d.ts +10 -13
  236. package/dist/plugins/session-fork.js +5 -25
  237. package/dist/plugins/session-ownership.d.ts +24 -3
  238. package/dist/plugins/session-ownership.js +24 -5
  239. package/dist/plugins/session-workspace-cell.d.ts +17 -0
  240. package/dist/plugins/session-workspace-cell.js +38 -0
  241. package/dist/plugins/sql-errors.d.ts +23 -2
  242. package/dist/plugins/sql-errors.js +15 -0
  243. package/dist/plugins/store-backend.js +7 -7
  244. package/dist/plugins/store-contracts.d.ts +10 -1
  245. package/dist/plugins/store-fault.d.ts +2 -2
  246. package/dist/plugins/store-fault.js +4 -2
  247. package/dist/plugins/tidb-pool.js +13 -0
  248. package/dist/plugins/tidb-session-store.d.ts +17 -2
  249. package/dist/plugins/tidb-session-store.js +18 -7
  250. package/dist/plugins/tool-result-store-sql.d.ts +13 -6
  251. package/dist/plugins/tool-result-store-sql.js +8 -7
  252. package/dist/plugins/worktree-isolation.d.ts +3 -2
  253. package/dist/plugins/worktree-isolation.js +1 -7
  254. package/dist/project-identity.d.ts +1 -14
  255. package/dist/project-identity.js +0 -4
  256. package/dist/request-key-closure.js +1 -1
  257. package/dist/request-value-closure.d.ts +31 -2
  258. package/dist/request-value-closure.js +7 -1
  259. package/dist/rule-entry-list.d.ts +3 -2
  260. package/dist/rule-entry-list.js +2 -2
  261. package/dist/rules-consent.js +12 -7
  262. package/dist/run-local.d.ts +2 -2
  263. package/dist/run-local.js +12 -6
  264. package/dist/runs.d.ts +7 -1
  265. package/dist/runs.js +8 -2
  266. package/dist/runtime-governance.d.ts +5 -5
  267. package/dist/sandbox-git-lease.d.ts +49 -0
  268. package/dist/sandbox-git-lease.js +60 -0
  269. package/dist/sandbox-git-plane.d.ts +84 -0
  270. package/dist/sandbox-git-plane.js +105 -0
  271. package/dist/security.d.ts +16 -4
  272. package/dist/security.js +8 -3
  273. package/dist/server-secret-env.d.ts +12 -3
  274. package/dist/server-secret-env.js +18 -2
  275. package/dist/session-error-wire.d.ts +14 -6
  276. package/dist/session-error-wire.js +2 -1
  277. package/dist/session-incarnation.d.ts +4 -5
  278. package/dist/session-incarnation.js +2 -2
  279. package/dist/session-policy-wire.d.ts +1 -1
  280. package/dist/session-policy-wire.js +2 -2
  281. package/dist/session-sync.d.ts +1 -1
  282. package/dist/session-workspace.d.ts +130 -0
  283. package/dist/session-workspace.js +83 -0
  284. package/dist/spec-fields.d.ts +44 -14
  285. package/dist/spec-fields.js +33 -12
  286. package/dist/standing-answer-grant.d.ts +2 -13
  287. package/dist/standing-answer-grant.js +0 -7
  288. package/dist/stored-request-prompt-profile-m30.d.ts +49 -0
  289. package/dist/stored-request-prompt-profile-m30.js +25 -0
  290. package/dist/task-a2a.d.ts +44 -14
  291. package/dist/task-a2a.js +46 -20
  292. package/dist/task-mcp.d.ts +34 -21
  293. package/dist/task-mcp.js +46 -22
  294. package/dist/task-settings.d.ts +2 -2
  295. package/dist/tool-approval.d.ts +43 -9
  296. package/dist/tool-approval.js +57 -15
  297. package/dist/tool-rule-names.d.ts +5 -0
  298. package/dist/trace/core-keyset-guard.d.ts +34 -19
  299. package/dist/trace/engine-notice-wire.d.ts +1 -1
  300. package/dist/trace/engine-notice-wire.js +4 -0
  301. package/dist/trace/exit-seat.d.ts +20 -1
  302. package/dist/trace/exit-seat.js +5 -2
  303. package/dist/trace/extension-call-face.d.ts +33 -0
  304. package/dist/trace/extension-call-face.js +39 -0
  305. package/dist/trace/ledger-sink.d.ts +4 -5
  306. package/dist/trace/ledger-sink.js +3 -7
  307. package/dist/trace/project.d.ts +62 -10
  308. package/dist/trace/project.js +49 -9
  309. package/dist/trace/redact.d.ts +18 -6
  310. package/dist/trace/redact.js +154 -60
  311. package/dist/trace/self-minted-wire.d.ts +500 -7
  312. package/dist/trace/self-minted-wire.js +182 -1
  313. package/dist/trace/wire-projection-faces.d.ts +7 -5
  314. package/dist/trace/wire-projection-faces.js +9 -4
  315. package/dist/user-authored-text.d.ts +2 -2
  316. package/dist/user-authored-text.js +16 -1
  317. package/dist/web-admin-client/apply-effective.js +0 -1
  318. package/dist/web-admin-client/center-request.d.ts +23 -4
  319. package/dist/web-admin-client/center-request.js +42 -8
  320. package/dist/web-admin-client/http-client.js +6 -6
  321. package/dist/web-admin-client/skills-mcp.d.ts +8 -4
  322. package/dist/web-admin-client/skills-mcp.js +31 -18
  323. package/dist/web-admin-client/types.d.ts +10 -5
  324. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +11 -5
  325. package/package.json +3 -3
  326. package/wire-changes/server@7.137.0.json +825 -0
  327. package/wire-changes/server@7.138.0.json +446 -0
@@ -1,35 +1,125 @@
1
- export function applyPodSpecPatch(pod, patch) {
2
- assertHardeningOnly(patch.podSecurityContext, "podSecurityContext");
3
- if (patch.podSecurityContext !== undefined && "sysctls" in patch.podSecurityContext) {
4
- throw new Error("podSpecPatch.podSecurityContext rejected — `sysctls` is not a patch key (the pod's sysctls are minted only by the lane's IPv6 posture, K8S_SANDBOX_IPV6)");
1
+ import { BROKER_VOLUME_NAME } from "../broker/constants.js";
2
+ export const SANDBOX_POD_LABELS = { app: "sema-sandbox", "managed-by": "sema-server" };
3
+ export const SANDBOX_RUN_LABEL = "sema.io/run";
4
+ export const SANDBOX_RUN_END_HOLD_ANNOTATION = "sema.io/run-end-hold-ms";
5
+ export const SANDBOX_SECRET_SEAT_VERBS = { env: ["create"], lease: ["create", "patch"] };
6
+ const IDENTITY_LABEL_KEYS = new Set([...Object.keys(SANDBOX_POD_LABELS), SANDBOX_RUN_LABEL]);
7
+ const LABEL_VALUE = /^(?:[A-Za-z0-9](?:[-A-Za-z0-9_.]{0,61}[A-Za-z0-9])?)?$/;
8
+ export function isLabelValue(v) {
9
+ return LABEL_VALUE.test(v);
10
+ }
11
+ export const ADAPTER_VOLUME_NAMES = { cache: "ws-cache", brokerLease: BROKER_VOLUME_NAME };
12
+ const RESERVED_VOLUME_NAMES = new Set(Object.values(ADAPTER_VOLUME_NAMES));
13
+ const agentContainerOf = (pod) => pod.spec.containers?.[0];
14
+ const POD_SPEC_PATCH_FIELDS = {
15
+ volumes: {
16
+ judge: (volumes) => {
17
+ assertVolumesPodLocal(volumes, "volumes");
18
+ for (const v of volumes)
19
+ assertPatchVolumeName(v?.name, "volumes");
20
+ },
21
+ apply: (pod, volumes) => {
22
+ if (volumes.length > 0)
23
+ pod.spec.volumes = [...(pod.spec.volumes ?? []), ...volumes];
24
+ },
25
+ withBroker: "allowed",
26
+ },
27
+ volumeMounts: {
28
+ judge: (mounts) => {
29
+ for (const m of mounts)
30
+ assertPatchVolumeName(m?.name, "volumeMounts");
31
+ },
32
+ apply: (pod, mounts) => {
33
+ const container = agentContainerOf(pod);
34
+ if (container && mounts.length > 0)
35
+ container.volumeMounts = [...(container.volumeMounts ?? []), ...mounts];
36
+ },
37
+ withBroker: "allowed",
38
+ },
39
+ podSecurityContext: {
40
+ judge: (sc) => {
41
+ assertHardeningOnly(sc, "podSecurityContext");
42
+ if ("sysctls" in sc) {
43
+ throw new Error("podSpecPatch.podSecurityContext rejected — `sysctls` is not a patch key (the pod's sysctls are minted only by the lane's IPv6 posture, K8S_SANDBOX_IPV6)");
44
+ }
45
+ },
46
+ apply: (pod, sc) => {
47
+ pod.spec.securityContext = { ...(pod.spec.securityContext ?? {}), ...sc };
48
+ },
49
+ withBroker: {
50
+ refused: "podSpecPatch.podSecurityContext is not allowed on a pod that carries the member-credential broker — every pod-level key would reach the broker container too; put the hardening into containerSecurityContext (it lands on the agent container only)",
51
+ },
52
+ },
53
+ containerSecurityContext: {
54
+ judge: (sc) => assertHardeningOnly(sc, "containerSecurityContext"),
55
+ apply: (pod, sc) => {
56
+ const container = agentContainerOf(pod);
57
+ if (container)
58
+ container.securityContext = { ...(container.securityContext ?? {}), ...sc };
59
+ },
60
+ withBroker: "allowed",
61
+ },
62
+ labels: {
63
+ judge: () => { },
64
+ apply: (pod, labels) => {
65
+ const merged = { ...pod.metadata.labels };
66
+ for (const [k, v] of Object.entries(labels))
67
+ if (!IDENTITY_LABEL_KEYS.has(k))
68
+ merged[k] = v;
69
+ pod.metadata.labels = merged;
70
+ },
71
+ withBroker: "allowed",
72
+ },
73
+ };
74
+ const PATCH_FIELD_ORDER = Object.keys(POD_SPEC_PATCH_FIELDS);
75
+ export function applyPodSpecPatch(pod, patch, presence = { broker: false }) {
76
+ const present = judgePodSpecPatch(patch, presence);
77
+ for (const k of present)
78
+ applyField(k, pod, patch);
79
+ }
80
+ export function podSpecPatchBrokerRefusal(patch) {
81
+ if (patch === undefined)
82
+ return undefined;
83
+ try {
84
+ judgePodSpecPatch(patch, { broker: true });
85
+ return undefined;
86
+ }
87
+ catch (e) {
88
+ return e instanceof Error ? e.message : String(e);
5
89
  }
6
- assertHardeningOnly(patch.containerSecurityContext, "containerSecurityContext");
7
- if (patch.labels) {
8
- const identity = {};
9
- for (const k of ["app", "managed-by"]) {
10
- const v = pod.metadata.labels?.[k];
11
- if (v !== undefined)
12
- identity[k] = v;
90
+ }
91
+ function judgePodSpecPatch(patch, presence) {
92
+ for (const key of Object.keys(patch)) {
93
+ if (!Object.hasOwn(POD_SPEC_PATCH_FIELDS, key)) {
94
+ throw new Error(`podSpecPatch rejected — ${JSON.stringify(key)} is not a patch field (the patch fields are ${PATCH_FIELD_ORDER.join(" / ")}; any other key is refused, never ignored)`);
13
95
  }
14
- pod.metadata.labels = { ...pod.metadata.labels, ...patch.labels, ...identity };
15
96
  }
16
- const spec = pod.spec;
17
- assertVolumesPodLocal(patch.volumes, "volumes");
18
- if (patch.volumes?.length)
19
- spec.volumes = [...(spec.volumes ?? []), ...patch.volumes];
20
- if (patch.podSecurityContext)
21
- spec.securityContext = { ...(spec.securityContext ?? {}), ...patch.podSecurityContext };
22
- const container = spec.containers?.[0];
23
- if (container) {
24
- if (patch.volumeMounts?.length)
25
- container.volumeMounts = [...(container.volumeMounts ?? []), ...patch.volumeMounts];
26
- if (patch.containerSecurityContext)
27
- container.securityContext = { ...(container.securityContext ?? {}), ...patch.containerSecurityContext };
97
+ const present = PATCH_FIELD_ORDER.filter((k) => patch[k] !== undefined);
98
+ if (presence.broker) {
99
+ for (const k of present) {
100
+ const column = POD_SPEC_PATCH_FIELDS[k].withBroker;
101
+ if (column !== "allowed")
102
+ throw new Error(column.refused);
103
+ }
104
+ }
105
+ for (const k of present)
106
+ judgeField(k, patch);
107
+ return present;
108
+ }
109
+ function judgeField(key, patch) {
110
+ POD_SPEC_PATCH_FIELDS[key].judge(patch[key]);
111
+ }
112
+ function applyField(key, pod, patch) {
113
+ POD_SPEC_PATCH_FIELDS[key].apply(pod, patch[key]);
114
+ }
115
+ function assertPatchVolumeName(name, where) {
116
+ if (typeof name !== "string" || name === "")
117
+ throw new Error(`podSpecPatch.${where} rejected — every entry needs a non-empty string \`name\``);
118
+ if (RESERVED_VOLUME_NAMES.has(name)) {
119
+ throw new Error(`podSpecPatch.${where} rejected — volume name ${JSON.stringify(name)} is reserved (the adapter mints it on the pod itself: ${[...RESERVED_VOLUME_NAMES].join(" / ")}); name the patch volume something else`);
28
120
  }
29
121
  }
30
122
  function assertHardeningOnly(sc, where) {
31
- if (!sc)
32
- return;
33
123
  const bad = (msg) => {
34
124
  throw new Error(`podSpecPatch.${where} rejected — it may only HARDEN, never weaken isolation: ${msg}`);
35
125
  };
@@ -53,8 +143,6 @@ function assertHardeningOnly(sc, where) {
53
143
  bad('seccompProfile.type:"Unconfined" (disables syscall filtering)');
54
144
  }
55
145
  function assertVolumesPodLocal(volumes, where) {
56
- if (!volumes?.length)
57
- return;
58
146
  const POD_LOCAL = new Set(["emptyDir", "configMap", "secret", "projected", "downwardAPI", "ephemeral"]);
59
147
  for (const v of volumes) {
60
148
  const vol = v;
@@ -0,0 +1,25 @@
1
+ /** A pod DELETE the API answered with something other than "deleted" (2xx) or "not there" (404): a Role without `delete`, a 5xx — the pod is still in the namespace. */
2
+ export declare class PodDeleteRefused extends Error {
3
+ readonly status: number;
4
+ constructor(status: number);
5
+ }
6
+ /** 删失败的原因码(闭集两形):API 回了 2xx / 404 以外的答复 ⇒ `http_<状态>`;请求本身抛(传输失败 / 超时)⇒ `request_failed`。 */
7
+ export declare function podDeleteCauseOf(error: unknown): string;
8
+ /** 一次删失败的读数(座自己填 `why` 与 `effect`)。 */
9
+ export interface PodDeleteFailure {
10
+ readonly namespace: string;
11
+ readonly pod: string;
12
+ /** 这只 pod 为什么要删(每个座一个闭集词)。 */
13
+ readonly why: string;
14
+ /** 被抛出的那一只(只读它的类型取原因码,其文字不上任何一行)。 */
15
+ readonly error: unknown;
16
+ /** 留下什么(运维行的后果句,英文,不带值)。 */
17
+ readonly effect: string;
18
+ /** 运维行座:可缺 / 可没有 `error` 档(计数不依赖座)。 */
19
+ readonly logger?: {
20
+ error?(event: string, fields?: Record<string, unknown>): void;
21
+ };
22
+ }
23
+ /** 上报一次删 pod 失败(计数 + 一行 error)。不抛。 */
24
+ export declare function reportPodDeleteFailed(f: PodDeleteFailure): void;
25
+ //# sourceMappingURL=k8s-pod-delete-failure.d.ts.map
@@ -0,0 +1,17 @@
1
+ import { recordFailOpen } from "../observability/fail-open.js";
2
+ export class PodDeleteRefused extends Error {
3
+ status;
4
+ constructor(status) {
5
+ super(`pod delete answered HTTP ${status}`);
6
+ this.status = status;
7
+ }
8
+ }
9
+ export function podDeleteCauseOf(error) {
10
+ return error instanceof PodDeleteRefused ? `http_${error.status}` : "request_failed";
11
+ }
12
+ export function reportPodDeleteFailed(f) {
13
+ const cause = podDeleteCauseOf(f.error);
14
+ recordFailOpen("server.sandbox-pod.delete-failed", `namespace=${f.namespace} pod=${f.pod} why=${f.why} cause=${cause}`);
15
+ f.logger?.error?.("sandbox_pod_delete_failed", { pod: f.pod, namespace: f.namespace, why: f.why, cause, effect: f.effect });
16
+ }
17
+ //# sourceMappingURL=k8s-pod-delete-failure.js.map
@@ -0,0 +1,106 @@
1
+ /**
2
+ * k8s 沙箱 Pod 的 reaper —— server 建出来、却没被自己的 run 收尾删掉的 Pod 由它删(该 Pod 的 Secret 以 `ownerReferences` 属于它,随它由集群 GC)。
3
+ *
4
+ * 为什么要它:`activeDeadlineSeconds` 只让容器到点终止、Pod 进 `Failed`,**不删 Pod 对象**;run 收尾的 `destroy()` 是删 Pod 的正常路,
5
+ * 而 server 进程崩溃 / 收尾里删失败(`destroy` 的删是吞错的)时 Pod 对象(连同它的 Secret)就一直留着。
6
+ *
7
+ * 法(只删两形;判决链的缺省臂是「不动作」,删必须被显式选中 —— 销毁不可逆,拿不准恒不删,与 E2B 孤儿回收同一方向纪律):
8
+ * ② `status.phase ∈ {Succeeded, Failed}` —— 容器已全部结束,删它不掐断任何东西;不看 run、不看标签;
9
+ * ① 带 run 标签(`sema.io/run`,只有顶层 run 的 Pod 带)、run 行在 server 店里**已是终态**,且自 run 行**转终态的时刻**(行上的 `updatedAt` ——
10
+ * 三只 run 店的终态写当拍盖它)起超过**这只 Pod 自带的**上界:`max(租约上限 + 宽限 = 6 min, 工作区快照时限 + 引擎拆环境放行 `teardownLegBudget()`)`
11
+ * ({@link sandboxRunEndHoldMs};租约上限 = 凭据代理的租约律那一只 `LEASE_TTL_MAX_MS`,本模块不另立)。
12
+ * 第二项是 run 收尾的 `destroy()` 在终态之后还可能在 Pod 上存快照的最长时间(引擎的拆环境腿按 `teardownLegBudget()` 放行、run 随即落终态,快照按数据时限接着跑)——
13
+ * 那个数据时限是**建这只 Pod 的副本**的配置,所以上界**随 Pod 走、写一次读一次**:适配器建 Pod 时按自己的快照时限算一次、写进注解
14
+ * `sema.io/run-end-hold-ms`(`k8s-exec-protocol.ts` `SANDBOX_RUN_END_HOLD_ANNOTATION`),回收器只读 Pod 自带的这一格,**从不**读回收器所在副本的配置
15
+ * (列举是整个命名空间的 Pod:滚动改 `WORKSPACE_SNAPSHOT_TIMEOUT_MS` 时,一个时限短的副本不得替时限长的副本判它的 Pod)。
16
+ * 带 run 标签的 Pod 上注解缺席(不是本服务写的:标签与注解由建 Pod 的适配器在同一个条件下一起写)、不是十进制整数、不在 [租约上限 + 宽限, 快照时限上限 + 引擎拆环境放行] 里
17
+ * ⇒ 不按 ① 删(`hold_unreadable`;这只 Pod 由自己的 `activeDeadlineSeconds` 到点后命中 ② 收)。7.136.x 及以前建的 Pod 没有 run 标签(标签与注解都是本版起写)⇒ 落在下面
18
+ * 「不带 run 标签」一类(`no_run_label`),同样只按 ② 收。持久化开 / 关两种部署同一条律。
19
+ * **活 run 的 Pod 不由本模块判**:run 行非终态(活 / 停驻)、查不到、读抛、词表外 ⇒ 不删 —— 它由自己的截止(`activeDeadlineSeconds` 到点
20
+ * ⇒ 之后命中 ②)与取消 / 收尾路径收;本模块从不按 Pod 存在时长判活。
21
+ * 不带 run 标签的 Pod(委派子代 / 服务自己的 run / 没有 run id)只按 ② 删:会话级后台子代刻意活过父 run 的回合,「会话里没有非终态 run」
22
+ * 推不出「这只 Pod 没人用」。
23
+ *
24
+ * 没有 run 店(无存储后端的部署)⇒ ② 照删;带 run 标签的 Pod 跳过 `run_store_absent`(查不了 run ⇒ 不删)。
25
+ * 每轮一行结构化日志 `sandbox_pod_reap_round`(扫了几只、按闭集原因分的删 / 跳过计数),每删一只一行 `sandbox_pod_reaped`;**列举失败**这一轮一只都删不了 ——
26
+ * F 类兜底口 `server.sandbox-pod-reaper.list-failed` 计一次 + 一行 warn `sandbox_pod_reap_list_failed`(下一轮一个周期后照常再列);**某一只删失败**不挡其余 ——
27
+ * 与连接清理同一只上报口 `reportPodDeleteFailed`(F 类计数 `server.sandbox-pod.delete-failed` + 一行 error `sandbox_pod_delete_failed`,`why=reaper_<删它的理由>`,下一轮再判再删)。
28
+ * 每副本起服后第一个维护 tick 跑一次、此后每 {@link SANDBOX_POD_REAP_PERIOD_MS} 一次(`boot/reapers.ts`);多副本同时删同一只 ⇒ 404 计 `gone`。
29
+ */
30
+ import type { Logger } from "../observability/logger.js";
31
+ import type { Metrics } from "../observability/metrics.js";
32
+ /** ① 的宽限(在租约上限之外再等的一段)。常量。 */
33
+ export declare const SANDBOX_POD_REAP_GRACE_MS = 60000;
34
+ /** ① 的上界:`max(租约上限 + 宽限, 快照时限 + 引擎拆环境放行)`。唯一的调用方 = 适配器建 Pod 的那一拍(快照时限 = 建它的副本的 `dataTimeoutMs`),结果写进 Pod 的注解。 */
35
+ export declare function sandboxRunEndHoldMs(snapshotTimeoutMs: number): number;
36
+ /** 一个副本两轮回收之间的间隔。常量。 */
37
+ export declare const SANDBOX_POD_REAP_PERIOD_MS: number;
38
+ /** 列举出来的一只沙箱 Pod(只带判官要读的几样;时刻是毫秒)。 */
39
+ export interface SandboxPodListing {
40
+ readonly name: string;
41
+ /** 标签 `sema.io/run`:顶层 run 的 id;缺席 = 这只 Pod 不点名 run。 */
42
+ readonly runId?: string;
43
+ /** 注解 `sema.io/run-end-hold-ms` 的原串(建它的副本写的 ① 上界);缺席 = Pod 上没有这一格(或不是串)。判官自己读它({@link judgeSandboxPod})。 */
44
+ readonly runEndHold?: string;
45
+ readonly phase?: string;
46
+ /** `metadata.deletionTimestamp` 在场:已在删除中。 */
47
+ readonly terminating: boolean;
48
+ }
49
+ /** reaper 对沙箱命名空间的窄面(真实装配 = `remote-env-k8s.ts` 的 `createSandboxPodApi`;测试注入假面)。 */
50
+ export interface SandboxPodApi {
51
+ readonly namespace: string;
52
+ /** 按身份标签列本命名空间的沙箱 Pod。失败抛。 */
53
+ list(): Promise<SandboxPodListing[]>;
54
+ /** 删一只;404 ⇒ `gone`。其余失败抛。 */
55
+ delete(name: string): Promise<"deleted" | "gone">;
56
+ }
57
+ /** 一行 run 在判官眼里的两位。状态词按**串**读:滚动升级里新副本写下的词本进程可能不认(`runStatusClassOf` 答 `undefined` ⇒ 不删)。
58
+ * `updatedAt` = 行最后一次被写的时刻(ISO);终态行上它就是转终态那一拍(之后改它的写只会让 reaper 等得更久)。 */
59
+ export interface SandboxPodRunRow {
60
+ readonly status: string;
61
+ readonly updatedAt: string;
62
+ }
63
+ /** run 店的窄读口(三只 run 店共有的 `getRun`)。 */
64
+ export interface SandboxPodRunReader {
65
+ getRun(taskId: string): Promise<SandboxPodRunRow | null | undefined>;
66
+ }
67
+ /** 一只 Pod 的 run 读数(闭集):读到行 / 店答没有这一行 / 读抛 / 本部署没有 run 店可问。 */
68
+ export type SandboxPodRunReading = {
69
+ readonly found: SandboxPodRunRow;
70
+ } | {
71
+ readonly absent: true;
72
+ } | {
73
+ readonly failed: true;
74
+ } | {
75
+ readonly storeAbsent: true;
76
+ };
77
+ export type SandboxPodDeleteReason = "containers_ended" | "run_ended_past_hold";
78
+ export type SandboxPodSkipReason = "terminating" | "no_run_label" | "run_store_absent" | "run_unread" | "run_read_failed" | "run_status_unknown" | "run_live" | "run_end_unreadable" | "hold_unreadable" | "within_run_end_hold";
79
+ export type SandboxPodVerdict = {
80
+ readonly delete: SandboxPodDeleteReason;
81
+ } | {
82
+ readonly skip: SandboxPodSkipReason;
83
+ };
84
+ /** 一只 Pod 删不删(纯判官;法见文件头)。`now` 毫秒;① 的上界只读 Pod 自带的注解(`pod.runEndHold`),没有第二个来源。 */
85
+ export declare function judgeSandboxPod(pod: SandboxPodListing, run: SandboxPodRunReading, now: number): SandboxPodVerdict;
86
+ /** 一轮的读数(轮次行与计数的来源)。`deleted` / `skipped` 只列出现过的原因。 */
87
+ export interface SandboxPodReapRound {
88
+ readonly listed: boolean;
89
+ readonly scanned: number;
90
+ readonly deleted: Partial<Record<SandboxPodDeleteReason, number>>;
91
+ readonly skipped: Partial<Record<SandboxPodSkipReason, number>>;
92
+ /** 判了删、删时已不在(404)。 */
93
+ readonly gone: number;
94
+ readonly deleteFailed: number;
95
+ }
96
+ export interface SandboxPodReaper {
97
+ /** 一轮:列举 → 逐只判 → 删被判删的。不抛(列举失败 ⇒ `listed: false` + F 计数 + 一行 warn)。`runs` 缺席 = 本部署没有 run 店(② 照删)。 */
98
+ reapOnce(runs: SandboxPodRunReader | undefined): Promise<SandboxPodReapRound>;
99
+ }
100
+ export declare function createSandboxPodReaper(deps: {
101
+ readonly api: SandboxPodApi;
102
+ readonly logger?: Pick<Logger, "info" | "warn" | "error">;
103
+ readonly metrics?: Pick<Metrics, "inc">;
104
+ readonly now?: () => number;
105
+ }): SandboxPodReaper;
106
+ //# sourceMappingURL=k8s-pod-reaper.d.ts.map
@@ -0,0 +1,122 @@
1
+ import { recordFailOpen } from "../observability/fail-open.js";
2
+ import { reportPodDeleteFailed } from "./k8s-pod-delete-failure.js";
3
+ import { runStatusClassOf } from "./store-contracts.js";
4
+ import { LEASE_TTL_MAX_MS } from "../broker/lease-file.js";
5
+ import { WORKSPACE_SNAPSHOT_TIMEOUT_MS_RANGE } from "../workspace-space.js";
6
+ import { teardownLegBudget } from "@sema-agent/core";
7
+ export const SANDBOX_POD_REAP_GRACE_MS = 60_000;
8
+ export function sandboxRunEndHoldMs(snapshotTimeoutMs) {
9
+ return Math.max(LEASE_TTL_MAX_MS + SANDBOX_POD_REAP_GRACE_MS, snapshotTimeoutMs + teardownLegBudget());
10
+ }
11
+ const RUN_END_HOLD_READABLE = { min: LEASE_TTL_MAX_MS + SANDBOX_POD_REAP_GRACE_MS, max: WORKSPACE_SNAPSHOT_TIMEOUT_MS_RANGE.max + teardownLegBudget() };
12
+ function runEndHoldOf(raw) {
13
+ if (raw === undefined || !/^[0-9]{1,16}$/.test(raw))
14
+ return undefined;
15
+ const ms = Number(raw);
16
+ return Number.isSafeInteger(ms) && ms >= RUN_END_HOLD_READABLE.min && ms <= RUN_END_HOLD_READABLE.max ? ms : undefined;
17
+ }
18
+ export const SANDBOX_POD_REAP_PERIOD_MS = 5 * 60_000;
19
+ const CLASS_VERDICT = {
20
+ live: "live",
21
+ parked: "live",
22
+ succeeded: "terminal",
23
+ unsuccessful: "terminal",
24
+ };
25
+ export function judgeSandboxPod(pod, run, now) {
26
+ if (pod.terminating)
27
+ return { skip: "terminating" };
28
+ if (pod.phase === "Succeeded" || pod.phase === "Failed")
29
+ return { delete: "containers_ended" };
30
+ if (pod.runId === undefined)
31
+ return { skip: "no_run_label" };
32
+ if ("storeAbsent" in run)
33
+ return { skip: "run_store_absent" };
34
+ if ("failed" in run)
35
+ return { skip: "run_read_failed" };
36
+ if ("absent" in run)
37
+ return { skip: "run_unread" };
38
+ const cls = runStatusClassOf(run.found.status);
39
+ if (cls === undefined)
40
+ return { skip: "run_status_unknown" };
41
+ if (CLASS_VERDICT[cls] === "live")
42
+ return { skip: "run_live" };
43
+ const endedAt = Date.parse(run.found.updatedAt);
44
+ if (!Number.isFinite(endedAt))
45
+ return { skip: "run_end_unreadable" };
46
+ const hold = runEndHoldOf(pod.runEndHold);
47
+ if (hold === undefined)
48
+ return { skip: "hold_unreadable" };
49
+ if (now - endedAt <= hold)
50
+ return { skip: "within_run_end_hold" };
51
+ return { delete: "run_ended_past_hold" };
52
+ }
53
+ export function createSandboxPodReaper(deps) {
54
+ const now = deps.now ?? Date.now;
55
+ return {
56
+ async reapOnce(runs) {
57
+ const deleted = {};
58
+ const skipped = {};
59
+ let gone = 0;
60
+ let deleteFailed = 0;
61
+ let pods;
62
+ try {
63
+ pods = await deps.api.list();
64
+ }
65
+ catch (e) {
66
+ const error = e instanceof Error ? e.message : String(e);
67
+ recordFailOpen("server.sandbox-pod-reaper.list-failed", `namespace=${deps.api.namespace} ${error}`);
68
+ deps.logger?.warn("sandbox_pod_reap_list_failed", {
69
+ namespace: deps.api.namespace,
70
+ error,
71
+ effect: "sandbox pods were not deleted this round — ended pods and pods of ended runs (with their Secrets) stay until a later round lists them (one period later)",
72
+ });
73
+ return { listed: false, scanned: 0, deleted, skipped, gone, deleteFailed };
74
+ }
75
+ const reads = new Map();
76
+ const readRun = (runId) => {
77
+ if (runs === undefined)
78
+ return Promise.resolve({ storeAbsent: true });
79
+ let r = reads.get(runId);
80
+ if (r === undefined) {
81
+ r = runs.getRun(runId).then((row) => (row === undefined || row === null ? { absent: true } : { found: row }), () => ({ failed: true }));
82
+ reads.set(runId, r);
83
+ }
84
+ return r;
85
+ };
86
+ const at = now();
87
+ for (const pod of pods) {
88
+ const reading = pod.runId === undefined ? { absent: true } : await readRun(pod.runId);
89
+ const verdict = judgeSandboxPod(pod, reading, at);
90
+ if ("skip" in verdict) {
91
+ skipped[verdict.skip] = (skipped[verdict.skip] ?? 0) + 1;
92
+ continue;
93
+ }
94
+ try {
95
+ const outcome = await deps.api.delete(pod.name);
96
+ if (outcome === "gone") {
97
+ gone++;
98
+ continue;
99
+ }
100
+ deleted[verdict.delete] = (deleted[verdict.delete] ?? 0) + 1;
101
+ deps.metrics?.inc("sandbox_pods_reaped_total", { reason: verdict.delete });
102
+ deps.logger?.info("sandbox_pod_reaped", { namespace: deps.api.namespace, pod: pod.name, reason: verdict.delete, ...(pod.runId !== undefined ? { run: pod.runId } : {}) });
103
+ }
104
+ catch (e) {
105
+ deleteFailed++;
106
+ reportPodDeleteFailed({
107
+ namespace: deps.api.namespace,
108
+ pod: pod.name,
109
+ why: `reaper_${verdict.delete}`,
110
+ error: e,
111
+ effect: "the pod, and the Secret it owns, stay in the sandbox namespace; the reaper tries this pod again next round (one period later), until then it holds static data only",
112
+ logger: deps.logger,
113
+ });
114
+ }
115
+ }
116
+ const round = { listed: true, scanned: pods.length, deleted, skipped, gone, deleteFailed };
117
+ deps.logger?.info("sandbox_pod_reap_round", { namespace: deps.api.namespace, ...round });
118
+ return round;
119
+ },
120
+ };
121
+ }
122
+ //# sourceMappingURL=k8s-pod-reaper.js.map
@@ -1,5 +1,5 @@
1
1
  import { type Checkpoint, type CheckpointSummary, type CheckpointToken, type PendingSteerInput, type ReopenReason, type ResolveExpectation, type ExecutionOutcomeRecordWord, type GateOutcome, type ResumeOutcome, type StoreDurability, type StoreFidelity } from "@sema-agent/core";
2
- import { type CheckpointAskCandidate, type CtxRowsRewriteM28, type DecidedApprovalRecord, type PendingCheckpoint, type PendingRowBinding } from "./checkpoint-store-sql.js";
2
+ import { type CheckpointAskCandidate, type CtxRowRewrite, type CtxRowsRewrite, type DecidedApprovalRecord, type PendingCheckpoint, type PendingRowBinding } from "./checkpoint-store-sql.js";
3
3
  import type { SealedCheckpointCtx } from "../http/checkpoint-ctx.js";
4
4
  export interface LocalCheckpointStoreOptions {
5
5
  /** taskId join for the pending card (the local FileRunStore's `getActiveTaskId`). Absent ⇒ `taskId: null`. */
@@ -32,17 +32,7 @@ export declare class LocalCheckpointStore {
32
32
  */
33
33
  readonly fidelity: StoreFidelity;
34
34
  /**
35
- * `CheckpointStore.redecision` 声明(core 5.22.0 F-012 L2/L3;[ref] clay 裁放行)——**如实按内核判**,
36
- * 与 durability/fidelity 同一条判据:内核 core `FileCheckpointStore` **自己**声明 `{ reopen: true }`,
37
- * 本类的 {@link LocalCheckpointStore.reopen} 纯委派给它 ⇒ 抄内核的读数是诚实的。包装类**不继承**被
38
- * 包装者的声明(委派不是子类),所以这一格必须自己写。`validatingLease` 不声明(内核也没声明)。
39
- * 翻向钉:`wiring-governance-operator.test.ts`(修前恰红,与本声明同 commit)。
40
- */
41
- readonly redecision: {
42
- readonly reopen: true;
43
- };
44
- /**
45
- * `CheckpointStore.execution` 声明(core 7.7.0 [ref];[ref])——**必填**,与上面三格同一条判据:**如实
35
+ * `CheckpointStore.execution` 声明(core 7.7.0 [ref];[ref])——**必填**,与上面两格同一条判据:**如实
46
36
  * 按内核判**。内核 core `FileCheckpointStore` 自己声明 `{ outcome: true }`,并把执行记录落成 ledger 的
47
37
  * 一个新事件种类(`execution`,与 resolve 同款「先 append+fsync 再翻内存」的崩溃序),
48
38
  * {@link LocalCheckpointStore.recordExecutionOutcome} 与 {@link LocalCheckpointStore.reopen} 都是纯委派
@@ -213,15 +203,12 @@ export declare class LocalCheckpointStore {
213
203
  */
214
204
  getCtx<T extends object = object>(sessionId: string): Promise<T | null>;
215
205
  /**
216
- * 🔴 **一次性迁移 M28 的店腿**(local 孪生;窗与删法见 `plugins/one-time-migrations.ts` 登记表 M28,判词与 SQL 孪生 `rewriteCtxRowsM28` 逐条同):
206
+ * 🔴 **一次性迁移的 ctx 行改写店腿**(local 孪生;`checkpoint-store-sql.ts` `CheckpointCtxRowsRewriter`,判词与 SQL 孪生 `rewriteCtxRows` 逐条同):
217
207
  * 逐档读(信封经读腿**同一只**判形 —— 读不出 ⇒ 计数、跳过、**不删**),`rewrite` 交回新体 ⇒ 信封原样(`sessionId` / `updatedAt` 不动:收割按 `updatedAt` 判)
218
208
  * 只换 `ctx`,经写腿同一只形闸、同一只原子写落回原档。封签绑的是信封里的 `sessionId`(与 `getCtx` 认行的那一格同一个)。
219
209
  * 数据根独占 + 起服在任何读写者之前跑 ⇒ 本车道没有并发写者,不需要 CAS;没有一行装不下这回事(`tooLarge` 恒 0)。
220
210
  */
221
- rewriteCtxRowsM28(rewrite: (sessionId: string, ctx: object) => {
222
- readonly ctx: SealedCheckpointCtx;
223
- readonly values: number;
224
- } | undefined): Promise<CtxRowsRewriteM28>;
211
+ rewriteCtxRows(rewrite: CtxRowRewrite): Promise<CtxRowsRewrite>;
225
212
  /** GC ctx files whose lifecycle is over — same three guards as the SQL twins (stale ∧ no pending ∧ no active run). */
226
213
  reapCtx(cutoff: number): Promise<number>;
227
214
  /**
@@ -49,7 +49,6 @@ export class LocalCheckpointStore {
49
49
  retention = UNMANAGED_RETENTION;
50
50
  durability = "durable";
51
51
  fidelity = "json";
52
- redecision = { reopen: true };
53
52
  execution = { outcome: true };
54
53
  inner;
55
54
  scopesPath;
@@ -327,7 +326,7 @@ export class LocalCheckpointStore {
327
326
  const rec = refusingCtx(sessionId, () => decodeJsonColumn(readFileSync(p, "utf8"), createCtxRecordReader(), `checkpoint-ctx ${ctxFileOf(sessionId)} for session ${JSON.stringify(sessionId)}`));
328
327
  return rec.sessionId === sessionId ? rec.ctx : null;
329
328
  }
330
- async rewriteCtxRowsM28(rewrite) {
329
+ async rewriteCtxRows(rewrite) {
331
330
  const out = { scanned: 0, rewritten: 0, valuesRewritten: 0, unreadable: 0, tooLarge: 0 };
332
331
  for (const f of readdirSync(this.ctxDir)) {
333
332
  if (!f.endsWith(".json"))
@@ -335,7 +334,7 @@ export class LocalCheckpointStore {
335
334
  const p = join(this.ctxDir, f);
336
335
  let rec;
337
336
  try {
338
- rec = decodeJsonColumn(readFileSync(p, "utf8"), createCtxRecordReader(), `checkpoint-ctx ${f} (M28 scan)`);
337
+ rec = decodeJsonColumn(readFileSync(p, "utf8"), createCtxRecordReader(), `checkpoint-ctx ${f} (ctx row rewrite scan)`);
339
338
  }
340
339
  catch {
341
340
  out.unreadable += 1;
@@ -346,7 +345,7 @@ export class LocalCheckpointStore {
346
345
  if (next === undefined)
347
346
  continue;
348
347
  const text = JSON.stringify({ sessionId: rec.sessionId, updatedAt: rec.updatedAt, ctx: next.ctx });
349
- assertJsonColumnWritable(text, createCtxRecordReader(), `checkpoint ctx for session ${JSON.stringify(rec.sessionId)} (local M28 write)`);
348
+ assertJsonColumnWritable(text, createCtxRecordReader(), `checkpoint ctx for session ${JSON.stringify(rec.sessionId)} (local ctx row rewrite)`);
350
349
  atomicWriteFile(this.tmpDir, p, text);
351
350
  out.rewritten += 1;
352
351
  out.valuesRewritten += next.values;