@sema-agent/server 7.135.0 → 7.137.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 (342) hide show
  1. package/MIGRATION.md +237 -2
  2. package/USAGE.md +146 -41
  3. package/deploy/sema-up/README.md +15 -1
  4. package/deploy/sema-up/chart/templates/NOTES.txt +1 -1
  5. package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +7 -1
  6. package/deploy/sema-up/chart/values.yaml +1 -1
  7. package/deploy/sema-up/stack/sema-stack-cluster.yaml +7 -1
  8. package/dist/approval-card.d.ts +60 -4
  9. package/dist/approval-card.js +18 -1
  10. package/dist/approval-content-kind.d.ts +7 -0
  11. package/dist/approval-content-kind.js +8 -1
  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/auth-bridge.d.ts +34 -5
  18. package/dist/auth-bridge.js +22 -5
  19. package/dist/boot/budget-tracing.d.ts +1 -2
  20. package/dist/boot/budget-tracing.js +2 -2
  21. package/dist/boot/execution-env.d.ts +24 -1
  22. package/dist/boot/execution-env.js +69 -6
  23. package/dist/boot/org-memory.js +1 -1
  24. package/dist/boot/reapers.d.ts +3 -0
  25. package/dist/boot/reapers.js +15 -1
  26. package/dist/boot/resolve-spec.d.ts +4 -0
  27. package/dist/boot/resolve-spec.js +24 -22
  28. package/dist/boot/runner-deps.js +4 -2
  29. package/dist/boot/stage-03-store-backend.d.ts +1 -1
  30. package/dist/boot/stage-04-stores.d.ts +2 -2
  31. package/dist/boot/stage-05-execution-env.d.ts +4 -2
  32. package/dist/boot/stage-05-execution-env.js +11 -2
  33. package/dist/boot/stage-06-runners.d.ts +4 -2
  34. package/dist/boot/stage-06-runners.js +5 -1
  35. package/dist/boot/stage-07-capability-layer.d.ts +5 -2
  36. package/dist/boot/stage-07-capability-layer.js +1 -1
  37. package/dist/boot/stage-08-reapers.d.ts +5 -2
  38. package/dist/boot/stage-08-reapers.js +2 -1
  39. package/dist/boot/stage-09-leader.d.ts +5 -2
  40. package/dist/boot/stage-10-http-server.d.ts +5 -2
  41. package/dist/boot/stage-10-http-server.js +3 -2
  42. package/dist/boot/stores.d.ts +1 -1
  43. package/dist/boot/stores.js +2 -3
  44. package/dist/boot/web-admin.js +10 -17
  45. package/dist/brain.js +12 -3
  46. package/dist/broker/broker.d.ts +89 -0
  47. package/dist/broker/broker.js +287 -0
  48. package/dist/broker/constants.d.ts +39 -0
  49. package/dist/broker/constants.js +15 -0
  50. package/dist/broker/http1.d.ts +111 -0
  51. package/dist/broker/http1.js +256 -0
  52. package/dist/broker/lease-book.d.ts +87 -0
  53. package/dist/broker/lease-book.js +142 -0
  54. package/dist/broker/lease-file.d.ts +60 -0
  55. package/dist/broker/lease-file.js +127 -0
  56. package/dist/broker/main.d.ts +2 -0
  57. package/dist/broker/main.js +20 -0
  58. package/dist/budget.d.ts +3 -3
  59. package/dist/budget.js +4 -0
  60. package/dist/capabilities/composite-members.d.ts +2 -1
  61. package/dist/capabilities/composite-members.js +2 -1
  62. package/dist/capabilities/engine-capabilities.js +1 -0
  63. package/dist/capabilities/mcp-probe.d.ts +5 -4
  64. package/dist/capabilities/scenarios.d.ts +23 -8
  65. package/dist/capabilities/scenarios.js +13 -8
  66. package/dist/capabilities/skills.d.ts +28 -5
  67. package/dist/capabilities/skills.js +8 -0
  68. package/dist/capability-facts.d.ts +3 -7
  69. package/dist/capability-facts.js +3 -5
  70. package/dist/center-credential-renewer.js +5 -1
  71. package/dist/center-credential.d.ts +18 -4
  72. package/dist/center-credential.js +18 -3
  73. package/dist/checkpoint-ctx-seal-m28.d.ts +3 -10
  74. package/dist/checkpoint-ctx-seal-m28.js +1 -1
  75. package/dist/checkpoint-identity.d.ts +3 -3
  76. package/dist/config-catalog.js +21 -16
  77. package/dist/config-d-migration.js +1 -1
  78. package/dist/config-invariants.d.ts +1 -1
  79. package/dist/config-invariants.js +8 -0
  80. package/dist/config-lkg.d.ts +0 -38
  81. package/dist/config-lkg.js +1 -83
  82. package/dist/config-types.d.ts +21 -4
  83. package/dist/config.d.ts +1 -1
  84. package/dist/config.js +60 -16
  85. package/dist/deployment-governance.d.ts +2 -2
  86. package/dist/deployment-governance.js +7 -9
  87. package/dist/deployment-shape.d.ts +1 -1
  88. package/dist/device-ws-protocol.d.ts +1 -0
  89. package/dist/device-ws-protocol.js +2 -1
  90. package/dist/elicitation.d.ts +8 -7
  91. package/dist/elicitation.js +9 -8
  92. package/dist/file-history-disclosure.d.ts +8 -4
  93. package/dist/file-history-disclosure.js +24 -6
  94. package/dist/fleet-client.d.ts +32 -1
  95. package/dist/fleet-client.js +24 -4
  96. package/dist/fleet-lease.d.ts +7 -3
  97. package/dist/fleet-lease.js +31 -23
  98. package/dist/hooks/classic-stations.d.ts +11 -54
  99. package/dist/hooks/hook-llm.js +14 -8
  100. package/dist/hooks/hook-runner.d.ts +4 -4
  101. package/dist/hooks/hook-runner.js +2 -2
  102. package/dist/http/admission.js +10 -7
  103. package/dist/http/approval-sweeps.js +4 -3
  104. package/dist/http/caller-trust.d.ts +2 -1
  105. package/dist/http/caller-trust.js +1 -3
  106. package/dist/http/checkpoint-ctx.d.ts +76 -16
  107. package/dist/http/checkpoint-ctx.js +45 -1
  108. package/dist/http/dispatch.js +24 -19
  109. package/dist/http/durable-run-leg.d.ts +5 -1
  110. package/dist/http/durable-run-leg.js +1 -1
  111. package/dist/http/health-self-report.d.ts +2 -2
  112. package/dist/http/mcp-remote-text.d.ts +5 -0
  113. package/dist/http/mcp-remote-text.js +7 -0
  114. package/dist/http/principal-gate.d.ts +15 -10
  115. package/dist/http/principal-gate.js +48 -9
  116. package/dist/http/resume-ctx-read.d.ts +2 -16
  117. package/dist/http/resume-ctx-read.js +2 -3
  118. package/dist/http/resume-legs.d.ts +2 -20
  119. package/dist/http/resume-legs.js +52 -38
  120. package/dist/http/route-ctx.d.ts +8 -2
  121. package/dist/http/routes/approvals-assistant.d.ts +0 -2
  122. package/dist/http/routes/approvals-assistant.js +65 -16
  123. package/dist/http/routes/capabilities.js +11 -1
  124. package/dist/http/routes/mcp-reconnect.d.ts +1 -1
  125. package/dist/http/routes/mcp-reconnect.js +5 -7
  126. package/dist/http/routes/runs.js +4 -3
  127. package/dist/http/routes/session-sync.js +70 -12
  128. package/dist/http/routes/sessions.js +20 -15
  129. package/dist/http/routes/tasks.js +8 -6
  130. package/dist/http/server.d.ts +4 -0
  131. package/dist/http/stored-body-replay.d.ts +3 -3
  132. package/dist/http/stored-body-replay.js +6 -1
  133. package/dist/http/submit-ctx.js +1 -1
  134. package/dist/http/wire-types.d.ts +57 -30
  135. package/dist/identity-source.d.ts +65 -0
  136. package/dist/identity-source.js +45 -0
  137. package/dist/index.d.ts +1 -0
  138. package/dist/index.js +1 -0
  139. package/dist/memory-store-refusal.d.ts +3 -3
  140. package/dist/model-entry-refusal.d.ts +2 -0
  141. package/dist/model-entry-refusal.js +11 -0
  142. package/dist/model-route-gap.d.ts +49 -4
  143. package/dist/model-route-gap.js +34 -1
  144. package/dist/model-select.d.ts +6 -17
  145. package/dist/model-select.js +0 -3
  146. package/dist/namespaced-rule-name.d.ts +1 -1
  147. package/dist/object-store-buckets.d.ts +13 -0
  148. package/dist/object-store-buckets.js +3 -0
  149. package/dist/observability/fail-open.d.ts +19 -7
  150. package/dist/observability/fail-open.js +19 -7
  151. package/dist/observability/metrics.js +2 -2
  152. package/dist/observability/principal-context.d.ts +4 -1
  153. package/dist/observability/principal-context.js +6 -1
  154. package/dist/observability/run-terminal.js +1 -0
  155. package/dist/operator-ask.d.ts +2 -2
  156. package/dist/org-delivery-gate.js +1 -1
  157. package/dist/org-memory-admission.d.ts +2 -2
  158. package/dist/org-memory-admission.js +39 -2
  159. package/dist/parked-decide.d.ts +4 -3
  160. package/dist/parked-decide.js +29 -9
  161. package/dist/peer-directory.d.ts +2 -1
  162. package/dist/peer-directory.js +7 -5
  163. package/dist/permission-rule-list.d.ts +2 -2
  164. package/dist/plugins/approval-ask-store-sql.d.ts +8 -0
  165. package/dist/plugins/approval-ask-store-sql.js +3 -0
  166. package/dist/plugins/blob-backend.js +2 -1
  167. package/dist/plugins/checkpoint-index-columns.d.ts +50 -1
  168. package/dist/plugins/checkpoint-index-columns.js +28 -0
  169. package/dist/plugins/checkpoint-store-sql.d.ts +46 -12
  170. package/dist/plugins/checkpoint-store-sql.js +64 -35
  171. package/dist/plugins/file-history-store-sql.d.ts +6 -0
  172. package/dist/plugins/file-history-store-sql.js +37 -37
  173. package/dist/plugins/file-run-store.js +8 -2
  174. package/dist/plugins/k8s-exec-protocol.d.ts +69 -13
  175. package/dist/plugins/k8s-exec-protocol.js +116 -28
  176. package/dist/plugins/k8s-pod-delete-failure.d.ts +25 -0
  177. package/dist/plugins/k8s-pod-delete-failure.js +17 -0
  178. package/dist/plugins/k8s-pod-reaper.d.ts +112 -0
  179. package/dist/plugins/k8s-pod-reaper.js +122 -0
  180. package/dist/plugins/local-checkpoint-store.d.ts +3 -6
  181. package/dist/plugins/local-checkpoint-store.js +7 -6
  182. package/dist/plugins/memory-run-store.js +2 -0
  183. package/dist/plugins/one-time-migrations.d.ts +49 -37
  184. package/dist/plugins/one-time-migrations.js +87 -128
  185. package/dist/plugins/permission-rule-store-file.js +2 -2
  186. package/dist/plugins/permission-rule-store-sql.d.ts +17 -17
  187. package/dist/plugins/permission-rule-store-sql.js +16 -17
  188. package/dist/plugins/pg-pool.js +4 -0
  189. package/dist/plugins/pg-session-storage.js +2 -2
  190. package/dist/plugins/remote-env-adb.d.ts +3 -0
  191. package/dist/plugins/remote-env-adb.js +1 -0
  192. package/dist/plugins/remote-env-device.d.ts +3 -0
  193. package/dist/plugins/remote-env-device.js +4 -0
  194. package/dist/plugins/remote-env-e2b.d.ts +3 -0
  195. package/dist/plugins/remote-env-e2b.js +1 -0
  196. package/dist/plugins/remote-env-file-error.d.ts +8 -1
  197. package/dist/plugins/remote-env-file-error.js +7 -1
  198. package/dist/plugins/remote-env-host.d.ts +7 -1
  199. package/dist/plugins/remote-env-host.js +7 -1
  200. package/dist/plugins/remote-env-k8s.d.ts +154 -14
  201. package/dist/plugins/remote-env-k8s.js +300 -21
  202. package/dist/plugins/remote-env-local-docker.d.ts +3 -0
  203. package/dist/plugins/remote-env-local-docker.js +1 -0
  204. package/dist/plugins/remote-env-ssh.d.ts +3 -0
  205. package/dist/plugins/remote-env-ssh.js +1 -0
  206. package/dist/plugins/run-store-sql.js +10 -4
  207. package/dist/plugins/s3-answer.d.ts +15 -0
  208. package/dist/plugins/s3-answer.js +5 -0
  209. package/dist/plugins/session-ownership.d.ts +17 -3
  210. package/dist/plugins/session-ownership.js +19 -5
  211. package/dist/plugins/snapshot-bucket-presence.d.ts +39 -4
  212. package/dist/plugins/snapshot-bucket-presence.js +35 -7
  213. package/dist/plugins/sql-errors.d.ts +60 -0
  214. package/dist/plugins/sql-errors.js +64 -1
  215. package/dist/plugins/store-contracts.d.ts +10 -1
  216. package/dist/plugins/store-fault.d.ts +4 -0
  217. package/dist/plugins/store-fault.js +10 -0
  218. package/dist/plugins/tidb-pool.js +8 -1
  219. package/dist/plugins/tidb-session-store.js +2 -2
  220. package/dist/plugins/web-search.d.ts +1 -1
  221. package/dist/plugins/web-search.js +2 -2
  222. package/dist/request-declaration-refusal.d.ts +94 -0
  223. package/dist/request-declaration-refusal.js +78 -0
  224. package/dist/request-image-urls.d.ts +6 -0
  225. package/dist/request-image-urls.js +27 -0
  226. package/dist/request-key-closure.d.ts +5 -0
  227. package/dist/request-key-closure.js +5 -2
  228. package/dist/request-value-closure.d.ts +31 -2
  229. package/dist/request-value-closure.js +7 -1
  230. package/dist/rule-entry-list.d.ts +3 -2
  231. package/dist/rule-entry-list.js +2 -2
  232. package/dist/rules-consent.js +7 -4
  233. package/dist/run-local.d.ts +2 -2
  234. package/dist/runs.js +2 -1
  235. package/dist/runtime-caps-resolver.js +1 -1
  236. package/dist/runtime-governance.d.ts +5 -5
  237. package/dist/sandbox-git-lease.d.ts +49 -0
  238. package/dist/sandbox-git-lease.js +60 -0
  239. package/dist/sandbox-git-plane.d.ts +84 -0
  240. package/dist/sandbox-git-plane.js +105 -0
  241. package/dist/sandbox-pkg-source.d.ts +28 -1
  242. package/dist/sandbox-pkg-source.js +1 -2
  243. package/dist/sealed-key.d.ts +19 -0
  244. package/dist/sealed-key.js +47 -4
  245. package/dist/security.d.ts +16 -4
  246. package/dist/security.js +8 -3
  247. package/dist/server-secret-env.d.ts +6 -0
  248. package/dist/server-secret-env.js +14 -0
  249. package/dist/session-error-wire.d.ts +14 -6
  250. package/dist/session-error-wire.js +2 -1
  251. package/dist/session-incarnation.d.ts +16 -5
  252. package/dist/session-incarnation.js +7 -4
  253. package/dist/session-policy-wire.d.ts +1 -1
  254. package/dist/session-policy-wire.js +2 -2
  255. package/dist/session-row.d.ts +3 -17
  256. package/dist/session-row.js +4 -0
  257. package/dist/session-sync.d.ts +15 -3
  258. package/dist/session-sync.js +5 -2
  259. package/dist/spec-fields.d.ts +43 -13
  260. package/dist/spec-fields.js +33 -12
  261. package/dist/stored-request-prompt-profile-m30.d.ts +49 -0
  262. package/dist/stored-request-prompt-profile-m30.js +25 -0
  263. package/dist/task-a2a.d.ts +52 -81
  264. package/dist/task-a2a.js +219 -89
  265. package/dist/task-mcp.d.ts +7 -15
  266. package/dist/task-mcp.js +16 -51
  267. package/dist/task-settings.d.ts +2 -2
  268. package/dist/tool-approval.d.ts +35 -12
  269. package/dist/tool-approval.js +59 -39
  270. package/dist/tool-rule-names.d.ts +5 -0
  271. package/dist/trace/core-keyset-guard.d.ts +5 -5
  272. package/dist/trace/engine-notice-wire.d.ts +1 -1
  273. package/dist/trace/engine-notice-wire.js +2 -0
  274. package/dist/trace/exit-seat.js +37 -13
  275. package/dist/trace/ledger-sink.d.ts +4 -5
  276. package/dist/trace/ledger-sink.js +3 -7
  277. package/dist/trace/project.d.ts +37 -3
  278. package/dist/trace/project.js +20 -6
  279. package/dist/trace/redact.d.ts +40 -12
  280. package/dist/trace/redact.js +332 -128
  281. package/dist/trace/self-minted-wire.d.ts +641 -0
  282. package/dist/trace/self-minted-wire.js +201 -0
  283. package/dist/trace/wire-projection-faces.d.ts +5 -3
  284. package/dist/trace/wire-projection-faces.js +7 -3
  285. package/dist/url-credential-refusal.d.ts +52 -0
  286. package/dist/url-credential-refusal.js +14 -0
  287. package/dist/url-refusal.d.ts +13 -4
  288. package/dist/url-refusal.js +8 -2
  289. package/dist/web-admin-client/apply-effective.js +0 -1
  290. package/dist/web-admin-client/center-request.d.ts +20 -2
  291. package/dist/web-admin-client/center-request.js +40 -5
  292. package/dist/web-admin-client/http-client.js +6 -6
  293. package/dist/web-admin-client/types.d.ts +5 -5
  294. package/dist/workspace-space.d.ts +27 -0
  295. package/dist/workspace-space.js +26 -0
  296. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +11 -5
  297. package/package.json +4 -3
  298. package/scripts/drain-check-local.mjs +2 -1
  299. package/scripts/drain-check.mysql.sql +3 -1
  300. package/scripts/drain-check.pg.sql +3 -1
  301. package/skills/schedule.md +1 -1
  302. package/wire-changes/server@7.104.0.json +134 -0
  303. package/wire-changes/server@7.105.0.json +53 -0
  304. package/wire-changes/server@7.106.0.json +475 -0
  305. package/wire-changes/server@7.107.0.json +64 -0
  306. package/wire-changes/server@7.108.0.json +153 -0
  307. package/wire-changes/server@7.109.0.json +46 -0
  308. package/wire-changes/server@7.110.0.json +90 -0
  309. package/wire-changes/server@7.111.0.json +86 -0
  310. package/wire-changes/server@7.112.0.json +60 -0
  311. package/wire-changes/server@7.113.0.json +26 -0
  312. package/wire-changes/server@7.114.0.json +26 -0
  313. package/wire-changes/server@7.115.0.json +97 -0
  314. package/wire-changes/server@7.116.0.json +218 -0
  315. package/wire-changes/server@7.116.1.json +26 -0
  316. package/wire-changes/server@7.117.0.json +65 -0
  317. package/wire-changes/server@7.118.0.json +129 -0
  318. package/wire-changes/server@7.119.0.json +65 -0
  319. package/wire-changes/server@7.120.0.json +89 -0
  320. package/wire-changes/server@7.121.0.json +219 -0
  321. package/wire-changes/server@7.122.0.json +54 -0
  322. package/wire-changes/server@7.123.0.json +26 -0
  323. package/wire-changes/server@7.124.0.json +336 -0
  324. package/wire-changes/server@7.124.1.json +26 -0
  325. package/wire-changes/server@7.125.0.json +111 -0
  326. package/wire-changes/server@7.126.0.json +285 -0
  327. package/wire-changes/server@7.127.0.json +77 -0
  328. package/wire-changes/server@7.127.1.json +26 -0
  329. package/wire-changes/server@7.127.2.json +53 -0
  330. package/wire-changes/server@7.128.0.json +26 -0
  331. package/wire-changes/server@7.129.0.json +106 -0
  332. package/wire-changes/server@7.129.1.json +26 -0
  333. package/wire-changes/server@7.130.0.json +26 -0
  334. package/wire-changes/server@7.131.0.json +118 -0
  335. package/wire-changes/server@7.132.0.json +226 -0
  336. package/wire-changes/server@7.133.0.json +1155 -0
  337. package/wire-changes/server@7.134.0.json +107 -0
  338. package/wire-changes/server@7.135.0.json +174 -0
  339. package/wire-changes/server@7.136.0.json +448 -0
  340. package/wire-changes/server@7.137.0.json +825 -0
  341. package/dist/url-credential.d.ts +0 -52
  342. package/dist/url-credential.js +0 -49
@@ -1,17 +1,30 @@
1
1
  import type { Pool as MySqlPool } from "mysql2/promise";
2
2
  import type { Pool as PgPool } from "pg";
3
3
  import { type ExecutionOutcomeRecordWord, type GateOutcome, type Checkpoint, type CheckpointGate, type CheckpointState, type CheckpointStore, type CheckpointSummary, type CheckpointToken, type PendingSteerInput, type ResumeOutcome, type ResolvedOutcome, type ResolveExpectation, type ReopenReason, type RiskDescriptor, type StoreDurability, type StoreFidelity, type ToolApprovalCard } from "@sema-agent/core";
4
- import { type ParkRowFacts, type RuleOfferProjection } from "../approval-card.js";
4
+ import { type ParkRowFacts, type RuleOfferProjection, type SkillCardFacts } from "../approval-card.js";
5
5
  import { type SqlDialect, type SqlDriver } from "./sql-driver.js";
6
6
  import type { SealedCheckpointCtx } from "../http/checkpoint-ctx.js";
7
- /** M28 店腿的读数(两孪生同形;随 M28 删):扫到的可读行 · 回写成的行 · 回写成的行里被改写的值 · 读不出的行 · 封好之后一行装不下的行。 */
8
- export interface CtxRowsRewriteM28 {
7
+ /** ctx 行改写店腿的读数(两孪生同形):扫到的可读行 · 回写成的行 · 回写成的行里改写函数报的改动数 · 读不出的行 · 改写之后一行装不下的行。 */
8
+ export interface CtxRowsRewrite {
9
9
  readonly scanned: number;
10
10
  readonly rewritten: number;
11
11
  readonly valuesRewritten: number;
12
12
  readonly unreadable: number;
13
13
  readonly tooLarge: number;
14
14
  }
15
+ /** 一行的改写:交回新的落盘形(只有封口模块的铸点产得出 —— 品牌型)与这一行的改动数;`undefined` = 这一行不写。 */
16
+ export type CtxRowRewrite = (sessionId: string, ctx: object) => {
17
+ readonly ctx: SealedCheckpointCtx;
18
+ readonly values: number;
19
+ } | undefined;
20
+ /**
21
+ * 🔴 **一次性迁移的 ctx 行改写店腿**(两孪生:`SqlCheckpointStore` / `LocalCheckpointStore` 的 `rewriteCtxRows`;逐行判词两孪生同):扫全部 `checkpoint_ctx` 行、
22
+ * 交给 `rewrite`、交回新体的行原地回写(续跑时钟不动)。店不知道改写是什么 —— 用它的迁移各自带行改写:M28(凭据位明文封好)、M30(退役请求键改写)。
23
+ * 窗与删法逐条在 `plugins/one-time-migrations.ts` 登记表;登记表里最后一条用它的迁移删码时,本腿、读数型与扫描页一起删。
24
+ */
25
+ export interface CheckpointCtxRowsRewriter {
26
+ rewriteCtxRows(rewrite: CtxRowRewrite): Promise<CtxRowsRewrite>;
27
+ }
15
28
  export { approvalPayloadFingerprint } from "./approval-payload-fingerprint.js";
16
29
  /**
17
30
  * [ref] D-1 (§3 invariant #3 — crash-safe reaper backstop): an ABSOLUTE upper bound on a pending
@@ -43,8 +56,11 @@ export declare function tokenFingerprint(token: string): string;
43
56
  *
44
57
  * 🔴 [ref]:行**顶层**另带 {@link ParkRowFacts}(`requiresRealApproval` / `ruleOffersAbsence`,真才带、缺席 ≠ false)——
45
58
  * 型与值都只有一个来源:`approval-card.ts` 的 {@link parkRowFacts}(两只店各读自己手里的那只寄存行 `pendingAction`,
46
- * 交给它、原样展开)。继承而不是在这里逐键再写一遍:core 给这一臂再加一位同类事实时,改的只有那张 Pick 表。 */
47
- export interface PendingCheckpoint extends ParkRowFacts {
59
+ * 交给它、原样展开)。继承而不是在这里逐键再写一遍:core 给这一臂再加一位同类事实时,改的只有那张 Pick 表。
60
+ *
61
+ * 技能卡三位({@link SkillCardFacts}:`boundGrantDigest` / `boundGrants` / `classifierNoRuling`)另有一只投影 {@link skillCardFacts},输入是 core 的检查点摘要而不是
62
+ * 寄存行原值(清单只在哈希等于摘要时才配上 wire,校验点单点在 core —— 见 {@link skillCardFacts} 顶注);同一张行上与收件箱行逐字同值,在场才带。 */
63
+ export interface PendingCheckpoint extends ParkRowFacts, SkillCardFacts {
48
64
  sessionId: string;
49
65
  /**
50
66
  * 这道停驻门的**非秘密稳定身份**(core `Checkpoint.checkpointId`,引擎每次停驻铸)—— 三处 durable 读面(本行 = `GET /v1/approvals` 的 `pending[]`
@@ -660,6 +676,12 @@ export declare class SqlCheckpointStore implements CheckpointStore {
660
676
  * approval gate (facet `B`) is not here: core refuses `sla_timeout` on it pre-CAS, and reapExpired voids it. The filter is
661
677
  * in SQL, before the LIMIT: `limit` bounds the per-tick deny-resume work (each is a model run), never hides a row behind
662
678
  * rows the sweep does not own.
679
+ *
680
+ * Order ({@link CHECKPOINT_SWEEP_ORDER_SQL}): rows whose `approval_card` column carries a word come first, then rows whose card
681
+ * is NULL, each by deadline. A NULL card on an approval gate is either a gate the engine gave no card (a real SLA row) or a
682
+ * row a replica that does not know the column wrote during a mixed-version window — possibly a question, which the engine
683
+ * refuses to deny (`sla_timeout`) and which the next boot's M29 backfill settles. The column cannot tell the two apart, so the
684
+ * order does: rows whose facet is certain take the window first and a straggler cannot crowd them out.
663
685
  */
664
686
  listExpiredApprovalGates(cutoff: number, limit?: number): Promise<Array<{
665
687
  sessionId: string;
@@ -676,6 +698,21 @@ export declare class SqlCheckpointStore implements CheckpointStore {
676
698
  * 真正需要它的读面是**已决行**的那一条:`findDecidedApprovalsForBinding`(decide 幂等回放),见那里。
677
699
  */
678
700
  listPending(scope?: string): Promise<PendingCheckpoint[]>;
701
+ /**
702
+ * 🔴 [ref] —— 运维队列行的**技能卡三位**({@link SkillCardFacts})。值与收件箱行同源:core `summarizeCheckpoint` 对同一行投的摘要,经 {@link skillCardFacts} 这一只投影。
703
+ *
704
+ * 为什么不像 {@link parkRowFacts} 那几位一样直接读 `pendingAction` 子树原值:三位里的**清单** `boundGrants` 在寄存行上是未经哈希校验的原值,core 只在摘要里「哈希等于摘要」
705
+ * 才发布它(「never a list beside a digest that binds another」);校验读口与哈希函数都不在 core 的公开导出面上,本仓既算不出也不该再写一份 ——
706
+ * 于是要拿到校验过的清单,只有对这一行跑 core 的 `summarizeCheckpoint`,而它吃的是整只 {@link Checkpoint}(不是子树)。
707
+ *
708
+ * 读放大(如实):**只对行上真带三位之一的行**(摘要是串,或成因词键在场 —— {@link parkRowCarriesSkillCardFacts})多读该行一次整只快照;普通卡(绝大多数行)零额外读,
709
+ * 主 SELECT 仍只取 `pendingAction` 子树(NP-3 不退)。这是一个**只增不改的代价**:带三位之一的待决行数 × 读次数(`/stream` 每连接每 3s 一拍)。
710
+ * 🚧 清单校验读口成为 core 公开导出的那一版,本读口与门一并退役,三位改走 `parkRowFacts` 读原值经 core 的读口 —— 零额外读。
711
+ *
712
+ * 失败方向与 {@link readParkRowActionCell} 逐字同一条判据(展示/分诊材料、行级容忍 + 响亮、同一个 tag):整只快照在场却不是一只合法的检查点(读回的体坏了,或 core 的摘要读不动它)
713
+ * ⇒ 只丢**这一行**的三位;行已不在待决(两次读之间被决/过期)⇒ 三位缺席;`not-text`(读腿漏了投影)原样抛。
714
+ */
715
+ private readParkedSkillCardFacts;
679
716
  /**
680
717
  * [ref] #2 (scheduler seam, core 1.105): a scope's PENDING checkpoints as lightweight
681
718
  * {@link CheckpointSummary} rows in ONE query — the cross-task triage view a supervisor scheduler needs
@@ -808,17 +845,14 @@ export declare class SqlCheckpointStore implements CheckpointStore {
808
845
  putCtx(sessionId: string, ctx: SealedCheckpointCtx): Promise<void>;
809
846
  getCtx<T extends object = object>(sessionId: string): Promise<T | null>;
810
847
  /**
811
- * 🔴 **一次性迁移 M28 的店腿**(窗与删法见 `plugins/one-time-migrations.ts` 登记表 M28;编排 = `checkpoint-ctx-seal-m28.ts`,行改写 =
812
- * `http/checkpoint-ctx.ts` 的 `sealStoredCheckpointCtxM28`;本店不知道「封」是什么,只负责扫与回写):按 `session_id` 键集分页扫 `checkpoint_ctx`
848
+ * 🔴 **一次性迁移的 ctx 行改写店腿**({@link CheckpointCtxRowsRewriter};用它的迁移与各自的行改写见那里,窗与删法见 `plugins/one-time-migrations.ts` 登记表;
849
+ * 本店不知道改写是什么,只负责扫与回写):按 `session_id` 键集分页扫 `checkpoint_ctx`
813
850
  * (每页 {@link CTX_REWRITE_PAGE} 行,只取会话 id 与 ctx 文本),行体经读腿**同一只**判形({@link decodeCtxColumn})—— 读不出 ⇒ 计数、跳过、不动;
814
851
  * `rewrite` 交回新体 ⇒ 经写腿同一只编码与形闸,**按原文 CAS** 回写(MySQL 协议两边 `CAST(… AS BINARY)`,与 M17 / M18 同一条理由:连接排序规则大小写
815
852
  * 不敏感;PG `text` 等值)。`updated_at_ms` **不动**(收割按它判,迁移不许延长或缩短一行的寿命)。CAS 打空(扫描与回写之间这一行被续跑腿 / 别的副本写过)
816
- * ⇒ 不计、不重试(新写者是本版就已封好;是旧版副本就由之后的起服再扫到)。一行装不下(封签比明文长)⇒ 计 `tooLarge`、那一行留原样,其余照扫。
853
+ * ⇒ 不计、不重试(新写者是本版写下的、已是新形;是旧版副本就由之后的起服再扫到)。一行装不下(如封签比明文长)⇒ 计 `tooLarge`、那一行留原样,其余照扫。
817
854
  */
818
- rewriteCtxRowsM28(rewrite: (sessionId: string, ctx: object) => {
819
- readonly ctx: SealedCheckpointCtx;
820
- readonly values: number;
821
- } | undefined): Promise<CtxRowsRewriteM28>;
855
+ rewriteCtxRows(rewrite: CtxRowRewrite): Promise<CtxRowsRewrite>;
822
856
  /**
823
857
  * GC ctx rows whose lifecycle is over (bound to the checkpoint, don't leak):
824
858
  * a session with NO pending checkpoint, NO active run, AND not touched since `cutoff`.
@@ -1,9 +1,9 @@
1
1
  import { parkedApprovalCard } from "../approval-content-kind.js";
2
2
  import { createHash } from "node:crypto";
3
- import { CHECKPOINT_FACET_SQL, buildCheckpointIndexCells } from "./checkpoint-index-columns.js";
4
- import { CheckpointError, isGateKind, MAX_RULE_TEXT_CHARS, validatePendingSteer, appendPendingSteer, winnerFromOutcome, summarizeCheckpoint, MAX_SUPPORTED_CHECKPOINT_VERSION, ExecutionOutcomeConflictError, executionRecordDisposition, executionVerdict, } from "@sema-agent/core";
3
+ import { CHECKPOINT_FACET_SQL, CHECKPOINT_SWEEP_ORDER_SQL, CheckpointIndexRefusedError, judgeCheckpointIndexCells } from "./checkpoint-index-columns.js";
4
+ import { CheckpointError, isGateKind, MAX_RULE_TEXT_CHARS, validatePendingSteer, appendPendingSteer, winnerFromOutcome, summarizeCheckpoint, checkpointRevOf, MAX_SUPPORTED_CHECKPOINT_VERSION, ExecutionOutcomeConflictError, executionRecordDisposition, executionVerdict, } from "@sema-agent/core";
5
5
  import { redactDeep, redactSecrets } from "../trace/redact.js";
6
- import { MAX_RULE_OFFERS, MAX_RULE_OFFER_BATCH_MEMBERS, parkRowFacts, readRuleOfferList } from "../approval-card.js";
6
+ import { MAX_RULE_OFFERS, MAX_RULE_OFFER_BATCH_MEMBERS, parkRowCarriesSkillCardFacts, parkRowFacts, readRuleOfferList, skillCardFacts } from "../approval-card.js";
7
7
  import { assertJsonColumnWritable, createObjectColumnReader, decodeJsonColumn, decodeNullableJsonColumn, foldJsonColumn, isJsonColumnBodyProblem, jsonColumnText, readJsonArrayColumn, readJsonObjectColumn, readNonNullJsonValueColumn, } from "./sql-json-column.js";
8
8
  import { recordFailOpen } from "../observability/fail-open.js";
9
9
  import { durableRowIdentity, withoutCheckpointToken } from "../checkpoint-identity.js";
@@ -170,18 +170,16 @@ function readRiskDescriptorCell(cell) {
170
170
  return null;
171
171
  }
172
172
  }
173
- function readParkRowFactsCell(cell) {
174
- let pendingAction;
173
+ function readParkRowActionCell(cell) {
175
174
  try {
176
- pendingAction = decodeNullableJsonColumn(cell, readPendingActionMemberBody, "checkpoint.checkpoint $.pendingAction (operator queue row)");
175
+ return decodeNullableJsonColumn(cell, readPendingActionMemberBody, "checkpoint.checkpoint $.pendingAction (operator queue row)");
177
176
  }
178
177
  catch (e) {
179
178
  if (!isJsonColumnBodyProblem(e))
180
179
  throw e;
181
180
  recordFailOpen("server.approvals.park-row-blob-unreadable");
182
- return {};
181
+ return undefined;
183
182
  }
184
- return parkRowFacts(pendingAction);
185
183
  }
186
184
  const PARK_ROW_PENDING_ACTION = { key: "pendingAction", as: "pending_action" };
187
185
  const PARK_ROW_CHECKPOINT_ID = { key: "checkpointId", as: "checkpoint_id" };
@@ -383,7 +381,10 @@ export class SqlCheckpointStore {
383
381
  return { sql: `SELECT ${select} FROM checkpoint WHERE ${where.join(" AND ")} ORDER BY created_at_ms ${dir}, token ${dir} LIMIT 1`, params };
384
382
  }
385
383
  async put(token, cp) {
386
- const cells = buildCheckpointIndexCells(cp);
384
+ const judged = judgeCheckpointIndexCells(cp, this.db.dialect);
385
+ if (judged.kind !== "cells")
386
+ throw new CheckpointIndexRefusedError(cp.sessionId, judged);
387
+ const cells = judged.cells;
387
388
  const pa = cp.pendingAction;
388
389
  const toolInput = boundedToolInput(pa?.args);
389
390
  const ruleOffers = boundedRuleOffers(pa?.ruleOffers);
@@ -411,7 +412,7 @@ export class SqlCheckpointStore {
411
412
  cells.risk_descriptor === null ? null : this.json(cells.risk_descriptor, "risk descriptor"),
412
413
  ruleOffers === undefined ? null : this.json(ruleOffers, "rule offers"),
413
414
  cells.has_bidi_controls,
414
- cp.rev ?? 0,
415
+ checkpointRevOf(cp),
415
416
  cp.reopenReason ?? null,
416
417
  executionOutcome,
417
418
  executionAtMs,
@@ -543,7 +544,7 @@ export class SqlCheckpointStore {
543
544
  return res.affected;
544
545
  }
545
546
  async listExpiredApprovalGates(cutoff, limit = 100) {
546
- const { rows } = await this.db.query(this.q(`SELECT session_id, scope FROM checkpoint WHERE status = 'pending' AND deadline IS NOT NULL AND deadline <= ? AND ${CHECKPOINT_FACET_SQL.tidb} = 'A' ORDER BY deadline ASC, token ASC LIMIT ?`, `SELECT session_id, scope FROM checkpoint WHERE status = 'pending' AND deadline IS NOT NULL AND deadline <= $1 AND ${CHECKPOINT_FACET_SQL.pg} = 'A' ORDER BY deadline ASC, token ASC LIMIT $2`), [cutoff, limit]);
547
+ const { rows } = await this.db.query(this.q(`SELECT session_id, scope FROM checkpoint WHERE status = 'pending' AND deadline IS NOT NULL AND deadline <= ? AND ${CHECKPOINT_FACET_SQL.tidb} = 'A' ORDER BY ${CHECKPOINT_SWEEP_ORDER_SQL} LIMIT ?`, `SELECT session_id, scope FROM checkpoint WHERE status = 'pending' AND deadline IS NOT NULL AND deadline <= $1 AND ${CHECKPOINT_FACET_SQL.pg} = 'A' ORDER BY ${CHECKPOINT_SWEEP_ORDER_SQL} LIMIT $2`), [cutoff, limit]);
547
548
  return rows.map((r) => ({ sessionId: String(r.session_id), scope: String(r.scope) }));
548
549
  }
549
550
  async listPending(scope) {
@@ -555,7 +556,7 @@ export class SqlCheckpointStore {
555
556
  const { rows } = scope
556
557
  ? await this.db.query(`${base}${this.q(" AND c.scope=?", " AND c.scope=$1")} ORDER BY c.created_at_ms ASC`, [scope])
557
558
  : await this.db.query(`${base} ORDER BY c.created_at_ms ASC`);
558
- const out = rows.flatMap((r) => {
559
+ const entries = rows.flatMap((r) => {
559
560
  const gateKind = queueRowGateKind(r.gate_kind);
560
561
  if (gateKind === undefined)
561
562
  return [];
@@ -564,28 +565,56 @@ export class SqlCheckpointStore {
564
565
  const boundInputHash = r.bound_input_hash ?? null;
565
566
  const ruleOffers = readRuleOffersCell(r.rule_suggestions);
566
567
  const approvalCard = parkedApprovalCard({ kind: gateKind, approvalCard: r.approval_card ?? undefined });
567
- return [{
568
- sessionId: String(r.session_id),
569
- ...durableRowIdentity("approvals-queue", readCheckpointIdCell(r[PARK_ROW_CHECKPOINT_ID.as]), r.token),
570
- scope: String(r.scope),
571
- toolName,
572
- toolCallId,
573
- ...(toolCallId !== null ? { boundCallId: toolCallId } : {}),
574
- ...(boundInputHash !== null ? { boundInputHash } : {}),
575
- gateKind,
576
- taskId: r.task_id ?? null,
577
- input: refusing("a tool input the operator would be approving unseen", () => decodeToolInputColumn(r.tool_input)) ?? null,
578
- createdAt: Number(r.created_at_ms),
579
- deadline: r.deadline == null ? null : Number(r.deadline),
580
- riskDescriptor: readRiskDescriptorCell(r.risk_descriptor),
581
- ...(ruleOffers !== undefined ? { ruleOffers } : {}),
582
- ...(Number(r.has_bidi_controls) === 1 ? { hasBidiControls: true } : {}),
583
- ...(approvalCard !== undefined ? { approvalCard } : {}),
584
- ...readParkRowFactsCell(r[PARK_ROW_PENDING_ACTION.as]),
585
- }];
568
+ const pendingAction = readParkRowActionCell(r[PARK_ROW_PENDING_ACTION.as]);
569
+ const row = {
570
+ sessionId: String(r.session_id),
571
+ ...durableRowIdentity("approvals-queue", readCheckpointIdCell(r[PARK_ROW_CHECKPOINT_ID.as]), r.token),
572
+ scope: String(r.scope),
573
+ toolName,
574
+ toolCallId,
575
+ ...(toolCallId !== null ? { boundCallId: toolCallId } : {}),
576
+ ...(boundInputHash !== null ? { boundInputHash } : {}),
577
+ gateKind,
578
+ taskId: r.task_id ?? null,
579
+ input: refusing("a tool input the operator would be approving unseen", () => decodeToolInputColumn(r.tool_input)) ?? null,
580
+ createdAt: Number(r.created_at_ms),
581
+ deadline: r.deadline == null ? null : Number(r.deadline),
582
+ riskDescriptor: readRiskDescriptorCell(r.risk_descriptor),
583
+ ...(ruleOffers !== undefined ? { ruleOffers } : {}),
584
+ ...(Number(r.has_bidi_controls) === 1 ? { hasBidiControls: true } : {}),
585
+ ...(approvalCard !== undefined ? { approvalCard } : {}),
586
+ ...parkRowFacts(pendingAction),
587
+ };
588
+ return [{ row, token: String(r.token), readSkillCard: parkRowCarriesSkillCardFacts(pendingAction) }];
586
589
  });
590
+ const out = [];
591
+ for (const e of entries)
592
+ out.push(e.readSkillCard ? { ...e.row, ...(await this.readParkedSkillCardFacts(e.token)) } : e.row);
587
593
  return out.sort((a, b) => (b.riskDescriptor?.severity ?? 0) - (a.riskDescriptor?.severity ?? 0) || a.createdAt - b.createdAt);
588
594
  }
595
+ async readParkedSkillCardFacts(token) {
596
+ const { rows } = await this.db.query(this.q(`SELECT ${jsonColumnText("tidb", "checkpoint")} FROM checkpoint WHERE token = ? AND status = 'pending'`, `SELECT ${jsonColumnText("pg", "checkpoint")} FROM checkpoint WHERE token = $1 AND status = 'pending'`), [token]);
597
+ const r = rows[0];
598
+ if (r === undefined)
599
+ return {};
600
+ let cp;
601
+ try {
602
+ cp = decodeCheckpointColumn(r.checkpoint, "(operator queue row, skill-card facts)");
603
+ }
604
+ catch (e) {
605
+ if (!isJsonColumnBodyProblem(e))
606
+ throw e;
607
+ recordFailOpen("server.approvals.park-row-blob-unreadable");
608
+ return {};
609
+ }
610
+ try {
611
+ return skillCardFacts(summarizeCheckpoint(cp));
612
+ }
613
+ catch {
614
+ recordFailOpen("server.approvals.park-row-blob-unreadable");
615
+ return {};
616
+ }
617
+ }
589
618
  async listByScope(scope) {
590
619
  const { rows } = await this.db.query(this.q(`SELECT ${jsonColumnText("tidb", "checkpoint")}, status FROM checkpoint WHERE status = 'pending' AND scope = ? ORDER BY created_at_ms ASC LIMIT 500`, `SELECT ${jsonColumnText("pg", "checkpoint")}, status FROM checkpoint WHERE status = 'pending' AND scope = $1 ORDER BY created_at_ms ASC LIMIT 500`), [scope]);
591
620
  const out = [];
@@ -709,7 +738,7 @@ export class SqlCheckpointStore {
709
738
  const { rows } = await this.db.query(this.q(`SELECT ${jsonColumnText("tidb", "ctx")} FROM checkpoint_ctx WHERE session_id=?`, `SELECT ${jsonColumnText("pg", "ctx")} FROM checkpoint_ctx WHERE session_id=$1`), [sessionId]);
710
739
  return rows[0] ? refusing("a resume context nobody can read", () => decodeCtxColumn(rows[0].ctx, `for session ${JSON.stringify(sessionId)}`)) : null;
711
740
  }
712
- async rewriteCtxRowsM28(rewrite) {
741
+ async rewriteCtxRows(rewrite) {
713
742
  const out = { scanned: 0, rewritten: 0, valuesRewritten: 0, unreadable: 0, tooLarge: 0 };
714
743
  let after = "";
715
744
  for (;;) {
@@ -719,7 +748,7 @@ export class SqlCheckpointStore {
719
748
  after = sessionId;
720
749
  let ctx;
721
750
  try {
722
- ctx = decodeCtxColumn(row.ctx, `for session ${JSON.stringify(sessionId)} (M28 scan)`);
751
+ ctx = decodeCtxColumn(row.ctx, `for session ${JSON.stringify(sessionId)} (ctx row rewrite scan)`);
723
752
  }
724
753
  catch {
725
754
  out.unreadable += 1;
@@ -730,13 +759,13 @@ export class SqlCheckpointStore {
730
759
  if (next === undefined)
731
760
  continue;
732
761
  const text = this.json(next.ctx, "checkpoint ctx");
733
- assertJsonColumnWritable(text, createObjectColumnReader(), `checkpoint_ctx.ctx for session ${JSON.stringify(sessionId)} (M28 write)`);
762
+ assertJsonColumnWritable(text, createObjectColumnReader(), `checkpoint_ctx.ctx for session ${JSON.stringify(sessionId)} (ctx row rewrite)`);
734
763
  let affected;
735
764
  try {
736
765
  ({ affected } = await this.db.query(this.q("UPDATE checkpoint_ctx SET ctx = ? WHERE session_id = ? AND CAST(CAST(ctx AS CHAR) AS BINARY) = CAST(? AS BINARY)", "UPDATE checkpoint_ctx SET ctx = $1::jsonb WHERE session_id = $2 AND ctx::text = $3"), [text, sessionId, String(row.ctx)]));
737
766
  }
738
767
  catch (e) {
739
- const folded = rowTooLargeOrSelf(this.db.dialect, e, `checkpoint_ctx.ctx for session ${JSON.stringify(sessionId)} (M28 write)`);
768
+ const folded = rowTooLargeOrSelf(this.db.dialect, e, `checkpoint_ctx.ctx for session ${JSON.stringify(sessionId)} (ctx row rewrite)`);
740
769
  if (!(folded instanceof StoreRowTooLargeError))
741
770
  throw folded;
742
771
  out.tooLarge += 1;
@@ -165,6 +165,12 @@ export declare class SqlFileHistoryStore implements FileHistoryStore {
165
165
  /** core 7.55.0 `FileHistoryExportResult`:本店的图在 scope 锁内一事务物化,读得出就是整图(`{ ok: true, history }`;空 scope ⇒ `history: null`);
166
166
  * 库读失败照常抛(读故障不是答案)—— 本店没有「撕裂 / 读不出的边界」这一形(边界与映射同事务落),所以不答 `{ ok: false }`。 */
167
167
  exportHistory(scope: string): Promise<FileHistoryExportResult>;
168
+ /**
169
+ * Fetch + content-address verify EVERY blob the envelope references from the source, one at a time (memory bounded by one blob) —— 首推与同图重推共用的**唯一**取值校验;
170
+ * 任何一枚取不出 / 核不过 ⇒ 整信封典型拒,什么都不发布。`onVerified` = 校验过的这一枚拿来做什么:首推传「续租式 upsert」(delete-in-use 租约 —— 发布前的孤儿清扫不许收走
171
+ * 这次导入正要引用的字节);同图重推不传 —— 已发布的图的版本行本身就是引用,租约无事可护,而回写会把每只对象重传一遍(MinIO 的 `putBlob` 是整体 PUT)。
172
+ */
173
+ private verifySourceBlobs;
168
174
  importHistory(scope: string, data: FileHistoryExport, srcGetBlob: (hash: string) => Promise<Uint8Array | undefined>): Promise<FileHistoryResult>;
169
175
  }
170
176
  /** MySQL-protocol (TiDB) binding. */
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- import { captureFileBackupViaEnv, applyFileRestoreViaEnv, fileHistoryModeProvenDifferent, worldStillMatchesMint, boundaryPublishVerdict, fileHistoryDescribeTrackKey, fileHistoryTrackKeyOf, fileHistoryResolveTrackKey, fileHistoryExportDigest, validateFileHistoryExport, resolveFileHistoryRetention, fileHistoryBoundariesToKeep, } from "@sema-agent/core";
2
+ import { captureFileBackupViaEnv, applyFileRestoreViaEnv, fileHistoryModeProvenDifferent, worldStillMatchesMint, annulProofFailure, boundaryPublishVerdict, fileHistoryDescribeTrackKey, fileHistoryTrackKeyOf, fileHistoryResolveTrackKey, fileHistoryExportDigest, validateFileHistoryExport, resolveFileHistoryRetention, fileHistoryBoundariesToKeep, } from "@sema-agent/core";
3
3
  import { BlobTooLargeError, SqlBlobBackend } from "./blob-backend.js";
4
4
  import { mysqlDriver, pgDriver } from "./sql-driver.js";
5
5
  import { isMissingColumnError } from "./sql-errors.js";
@@ -149,15 +149,8 @@ export class SqlFileHistoryStore {
149
149
  const baseAvailable = truthy(t.rows[0].base_available);
150
150
  const mintedBlobHash = v1.rows[0]?.blob_hash != null ? String(v1.rows[0].blob_hash) : null;
151
151
  const proof = await worldStillMatchesMint(verify.env, absPath, root, baseAvailable, mintedBlobHash, verify.signal);
152
- if (!proof.unchanged) {
153
- return {
154
- ok: false,
155
- error: {
156
- code: "conflict",
157
- message: `first-touch record for ${absPath} is NOT retractable: ${proof.reason} — the write this retraction compensates for may have changed the file, and deleting the only baseline of a modified file is worse than keeping a stale record`,
158
- },
159
- };
160
- }
152
+ if (proof.verdict !== "unchanged")
153
+ return annulProofFailure(proof, absPath);
161
154
  }
162
155
  const conn = await this.db.connect();
163
156
  try {
@@ -441,7 +434,7 @@ export class SqlFileHistoryStore {
441
434
  let targetVersion = boundary.get(t.keyHash);
442
435
  if (targetVersion === undefined) {
443
436
  if (!t.baseAvailable) {
444
- ledger.refused.push({ path: t.trackKey, reason: "base_unavailable" });
437
+ ledger.refused.push({ path: t.trackKey, reason: "base_unavailable", class: "base_unavailable" });
445
438
  continue;
446
439
  }
447
440
  targetVersion = 1;
@@ -452,12 +445,12 @@ export class SqlFileHistoryStore {
452
445
  continue;
453
446
  }
454
447
  if (rec.mode !== undefined && !(Number.isInteger(rec.mode) && rec.mode >= 0 && rec.mode <= 0o7777)) {
455
- ledger.refused.push({ path: t.trackKey, reason: `recorded mode ${String(rec.mode)} is not a valid POSIX permission value — refusing to apply a corrupted permission record` });
448
+ ledger.failed.push({ path: t.trackKey, reason: `recorded mode ${String(rec.mode)} is not a valid POSIX permission value (store integrity) — refusing to apply a corrupted permission record` });
456
449
  continue;
457
450
  }
458
451
  const resolved = fileHistoryResolveTrackKey(root, t.trackKey, t.keyDesc);
459
452
  if (!resolved.ok) {
460
- ledger.refused.push({ path: t.trackKey, reason: resolved.reason });
453
+ ledger.refused.push({ path: t.trackKey, reason: resolved.reason, class: resolved.class });
461
454
  continue;
462
455
  }
463
456
  let applied;
@@ -488,7 +481,7 @@ export class SqlFileHistoryStore {
488
481
  else if (applied.outcome === "identical")
489
482
  ledger.identicalSkipped.push(t.trackKey);
490
483
  else if (applied.outcome === "refused")
491
- ledger.refused.push({ path: t.trackKey, reason: applied.reason });
484
+ ledger.refused.push({ path: t.trackKey, reason: applied.reason, class: applied.class });
492
485
  else
493
486
  ledger.failed.push({ path: t.trackKey, reason: applied.reason });
494
487
  if ((applied.outcome === "applied" || applied.outcome === "identical") && applied.modeNotPreserved === true)
@@ -716,6 +709,29 @@ export class SqlFileHistoryStore {
716
709
  conn.release();
717
710
  }
718
711
  }
712
+ async verifySourceBlobs(data, srcGetBlob, onVerified) {
713
+ const needed = new Set();
714
+ for (const t of data.tracked)
715
+ for (const v of t.versions)
716
+ if (v.blobHash !== null)
717
+ needed.add(v.blobHash);
718
+ for (const hash of needed) {
719
+ let bytes;
720
+ try {
721
+ bytes = await srcGetBlob(hash);
722
+ }
723
+ catch (err) {
724
+ return { ok: false, error: { code: "adopt_failed", message: `source blob ${hash} fetch failed: ${foldCaughtFailure(err).text}` } };
725
+ }
726
+ if (bytes === undefined)
727
+ return { ok: false, error: { code: "adopt_failed", message: `source blob ${hash} is missing — the envelope's graph cannot be completed, so nothing is published` } };
728
+ if (sha256hex(bytes) !== hash) {
729
+ return { ok: false, error: { code: "unsupported_format", message: `source blob ${hash} fails content-address integrity — refused, nothing published` } };
730
+ }
731
+ await onVerified?.(hash, bytes);
732
+ }
733
+ return { ok: true };
734
+ }
719
735
  async importHistory(scope, data, srcGetBlob) {
720
736
  try {
721
737
  const shaped = validateFileHistoryExport(data);
@@ -733,31 +749,15 @@ export class SqlFileHistoryStore {
733
749
  if (!exported.ok)
734
750
  return exported;
735
751
  const existing = exported.history;
736
- if (existing !== null) {
737
- if (fileHistoryExportDigest(existing) === fileHistoryExportDigest(data))
738
- return { ok: true };
752
+ const sameGraph = existing !== null && fileHistoryExportDigest(existing) === fileHistoryExportDigest(data);
753
+ if (existing !== null && !sameGraph) {
739
754
  return { ok: false, error: { code: "conflict", message: `importHistory target "${scope}" already holds a DIFFERENT history — a first import requires an empty target and a retry is legal only for the identical envelope (atomic visibility prevents half-graphs; this law prevents publishing the wrong complete graph over locally-grown history)` } };
740
755
  }
741
- const needed = new Set();
742
- for (const t of data.tracked)
743
- for (const v of t.versions)
744
- if (v.blobHash !== null)
745
- needed.add(v.blobHash);
746
- for (const hash of needed) {
747
- let bytes;
748
- try {
749
- bytes = await srcGetBlob(hash);
750
- }
751
- catch (err) {
752
- return { ok: false, error: { code: "adopt_failed", message: `source blob ${hash} fetch failed: ${foldCaughtFailure(err).text}` } };
753
- }
754
- if (bytes === undefined)
755
- return { ok: false, error: { code: "adopt_failed", message: `source blob ${hash} is missing — the envelope's graph cannot be completed, so nothing is published` } };
756
- if (sha256hex(bytes) !== hash) {
757
- return { ok: false, error: { code: "unsupported_format", message: `source blob ${hash} fails content-address integrity — refused, nothing published` } };
758
- }
759
- await this.blobs.putBlob(hash, bytes);
760
- }
756
+ const verified = await this.verifySourceBlobs(data, srcGetBlob, sameGraph ? undefined : (hash, bytes) => this.blobs.putBlob(hash, bytes));
757
+ if (!verified.ok)
758
+ return verified;
759
+ if (sameGraph)
760
+ return { ok: true };
761
761
  const conn = await this.db.connect();
762
762
  try {
763
763
  await conn.begin();
@@ -9,6 +9,7 @@ import { recordFailOpen } from "../observability/fail-open.js";
9
9
  import { replayJsonlSkipping } from "../observability/corrupt-read-seat.js";
10
10
  import { RUN_HOLDER_DEATH_CODES, RUN_ORPHANED_ERROR, RUN_STALLED_ERROR, StoreBootRefusalError, buildRunOrphanedError, readLegacyHolderDeathCode } from "./store-contracts.js";
11
11
  import { notifyRunTerminal } from "../observability/run-terminal.js";
12
+ import { readRunIdentityCells, runIdentityCells } from "../identity-source.js";
12
13
  import { createInProcessSpaceCommitTable, fenceSpaceCommitAtTerminal, SPACE_COMMIT_RECORD_SCHEMA } from "../workspace-space-commit.js";
13
14
  const ownerEq = (a, b) => (a ?? null) === (b ?? null);
14
15
  const isParseableDate = (v) => typeof v === "string" && !Number.isNaN(Date.parse(v));
@@ -140,16 +141,19 @@ export class FileRunStore {
140
141
  return this.spaceCommits.abandonSpaceCommit(spaceKey, taskId);
141
142
  }
142
143
  toRow(d) {
144
+ const { identitySource, identityScope, identityPrincipalInc, ...rest } = d;
143
145
  return {
144
- ...d,
146
+ ...rest,
145
147
  idemKey: typeof d.idemKey === "string" ? d.idemKey : null,
146
148
  idemBodyFingerprint: typeof d.idemBodyFingerprint === "string" ? d.idemBodyFingerprint : null,
149
+ identity: readRunIdentityCells(identitySource, identityScope, identityPrincipalInc),
147
150
  createdAt: new Date(d.createdAt),
148
151
  updatedAt: new Date(d.updatedAt),
149
152
  };
150
153
  }
151
154
  toDisk(r) {
152
- return { ...r, createdAt: r.createdAt.toISOString(), updatedAt: r.updatedAt.toISOString() };
155
+ const { identity, ...rest } = r;
156
+ return { ...rest, ...runIdentityCells(identity), createdAt: r.createdAt.toISOString(), updatedAt: r.updatedAt.toISOString() };
153
157
  }
154
158
  toRecord(r) {
155
159
  return {
@@ -162,6 +166,7 @@ export class FileRunStore {
162
166
  errorCode: r.errorCode,
163
167
  jobId: r.jobId,
164
168
  source: r.source,
169
+ identity: r.identity,
165
170
  instanceId: r.instanceId,
166
171
  cancelRequested: r.cancelRequested,
167
172
  objectivePreview: r.objectivePreview,
@@ -376,6 +381,7 @@ export class FileRunStore {
376
381
  objectivePreview: meta.objectivePreview ?? null,
377
382
  idemKey,
378
383
  idemBodyFingerprint: meta.idem?.bodyFingerprint ?? null,
384
+ identity: meta.identity ?? null,
379
385
  createdAt: now,
380
386
  updatedAt: now,
381
387
  cancelRequested: false,
@@ -1,9 +1,51 @@
1
1
  /**
2
- * The confined, ADDITIVE pod-spec patch ({@link PodSpecPatch}/{@link applyPodSpecPatch}) + the exec-socket TLS
3
- * shape ({@link execSocketTlsOptions}) + the v4 channel status-frame parse ({@link exitCodeFromStatus}) — the
4
- * k8s adapter's protocol/patch layer, independent of pod lifecycle. Split out of `remote-env-k8s.ts` ([ref]
5
- * A13, internal-lossless — `remote-env-k8s.ts` re-exports every symbol below unchanged).
2
+ * The identity labels every sandbox pod carries — the ONE spelling (RBAC selectors, the reaper's listing and the egress self-check key on
3
+ * them; a pod-spec patch can add labels, never set or replace one of these).
6
4
  */
5
+ export declare const SANDBOX_POD_LABELS: {
6
+ readonly app: "sema-sandbox";
7
+ readonly "managed-by": "sema-server";
8
+ };
9
+ /**
10
+ * The pod's owning run: label key, value = the server run id (the task id) of the TOP-LEVEL run whose env created the pod. Minted only by the
11
+ * adapter from `K8sEnvConfig.runId` (the per-run binding decides who has one); absent ⇒ the pod names no run, and the reaper removes it only once
12
+ * its containers have ended. An identity label: a patch can neither set nor replace it.
13
+ */
14
+ export declare const SANDBOX_RUN_LABEL = "sema.io/run";
15
+ /**
16
+ * The pod's own reaper hold: annotation key, value = the decimal milliseconds the reaper must wait after the owning run turned terminal before it
17
+ * removes the pod (`k8s-pod-reaper.ts` `sandboxRunEndHoldMs` of the CREATING replica's data bound). Written once, by the adapter, on every pod that
18
+ * carries {@link SANDBOX_RUN_LABEL} (the hold is only read for those); the reaper reads this and nothing else — never its own replica's config, so a
19
+ * rolling change of the snapshot bound never shortens another replica's pod. A pod-spec patch reaches labels only, never annotations.
20
+ */
21
+ export declare const SANDBOX_RUN_END_HOLD_ANNOTATION = "sema.io/run-end-hold-ms";
22
+ /** The two per-pod Secret seats: `env` = the pod's env Secret (the values `K8S_SANDBOX_ENV` names); `lease` = the member-credential broker's lease Secret. */
23
+ export type SandboxSecretSeatKind = "env" | "lease";
24
+ /**
25
+ * Per seat, the secrets verbs the adapter sends for it in the sandbox namespace — the ONE table, and each row is exactly that seat's calls:
26
+ * `env` = `create` (the env Secret, POSTed after its pod, never touched again); `lease` = `create` (the lease Secret) + `patch` (the lease rewrite,
27
+ * a merge-patch of `broker.json`). Nothing else: the adapter never reads a Secret back (no `get` — which would also let the service account read
28
+ * every Secret in the namespace) and never deletes one (no `delete`) — each Secret is owned by its pod (`ownerReferences`, `blockOwnerDeletion:
29
+ * false`), so the cluster's garbage collector removes it with the pod under its own identity. A seat in play asks only its own row: the boot gate
30
+ * (`K8S_SANDBOX_ENV` non-empty) asks `env`, the member-credential plane judge asks `lease` — an env-only deployment is never asked for `patch`.
31
+ * Readers: the SelfSubjectAccessReview (`readSandboxSecretSeat`), the refusal sentences that name the Role rule, the plane's off-reason text, the
32
+ * catalog sentence. `test/k8s-sandbox-secret.test.ts` pins row ≡ calls per seat, both directions.
33
+ */
34
+ export declare const SANDBOX_SECRET_SEAT_VERBS: {
35
+ readonly env: readonly ["create"];
36
+ readonly lease: readonly ["create", "patch"];
37
+ };
38
+ export type SandboxSecretVerb = (typeof SANDBOX_SECRET_SEAT_VERBS)[SandboxSecretSeatKind][number];
39
+ export declare function isLabelValue(v: string): boolean;
40
+ /**
41
+ * The volume names the adapter itself mints on a sandbox pod — the ONE table (a patch volume / volumeMount must not reuse one: it would collide
42
+ * with, or mount, the adapter's own volume). Today: the space's build-cache claim and the member-credential broker's lease volume. A volume the
43
+ * adapter adds later is added HERE, and the patch judge below refuses it with no second guard.
44
+ */
45
+ export declare const ADAPTER_VOLUME_NAMES: {
46
+ readonly cache: "ws-cache";
47
+ readonly brokerLease: "sema-broker-lease";
48
+ };
7
49
  /**
8
50
  * The confined, ADDITIVE pod-spec patch. Every field is purely additive —
9
51
  * none can override the hard isolation invariants. This is the seam the RSI L8 immutable-mount probe + the
@@ -22,19 +64,32 @@ export interface PodSpecPatch {
22
64
  /** Extra labels merged into metadata.labels (e.g. a grader/probe tag). */
23
65
  labels?: Record<string, string>;
24
66
  }
25
- /**
26
- * Apply a {@link PodSpecPatch} to a sandbox pod object IN PLACE — purely ADDITIVE. Volumes + volumeMounts are
27
- * APPENDED (never replaced); securityContext objects are shallow-MERGED onto any existing; labels are merged. It
28
- * deliberately touches ONLY spec.volumes / spec.securityContext / containers[0].{volumeMounts,securityContext} /
29
- * metadata.labels — so the hard isolation invariants (runtimeClassName, automountServiceAccountToken,
30
- * restartPolicy, activeDeadlineSeconds, image, command) are preserved by construction. Pure + exported for testing.
31
- */
32
- export declare function applyPodSpecPatch(pod: {
67
+ type SandboxPodShape = {
33
68
  metadata: {
34
69
  labels?: Record<string, string>;
35
70
  };
36
71
  spec: Record<string, unknown>;
37
- }, patch: PodSpecPatch): void;
72
+ };
73
+ /** Which pod a patch lands on: one that carries the broker container or not. */
74
+ export interface PodPatchPresence {
75
+ readonly broker: boolean;
76
+ }
77
+ /**
78
+ * Apply a {@link PodSpecPatch} to a sandbox pod object IN PLACE — purely ADDITIVE, through the one field table
79
+ * ({@link POD_SPEC_PATCH_FIELDS}): a key outside the table is refused; EVERY present field is judged before ANY lands (a refusal leaves the pod
80
+ * byte-identical). Volumes + volumeMounts are APPENDED (never replaced); securityContext objects are shallow-MERGED onto any existing; labels are
81
+ * merged under the identity keys. It touches ONLY spec.volumes / spec.securityContext / containers[0].{volumeMounts,securityContext} /
82
+ * metadata.labels — so the hard isolation invariants (runtimeClassName, automountServiceAccountToken, restartPolicy, activeDeadlineSeconds, image,
83
+ * command) are preserved by construction. Throws (the caller surfaces it as a provisioning failure). Pure + exported for testing.
84
+ * `presence.broker` ⇒ the table's broker column judges first (see {@link POD_SPEC_PATCH_FIELDS}).
85
+ */
86
+ export declare function applyPodSpecPatch(pod: SandboxPodShape, patch: PodSpecPatch, presence?: PodPatchPresence): void;
87
+ /**
88
+ * The patch judge alone — the same one {@link applyPodSpecPatch} runs — for a pod that carries the broker: the refusal sentence, or `undefined`
89
+ * when the patch may land. The boot reads it on the deployment's static patch (a refusal turns the member-credential plane off,
90
+ * `pod_patch_reaches_broker`); the adapter runs it again on every pod it builds with the broker.
91
+ */
92
+ export declare function podSpecPatchBrokerRefusal(patch: PodSpecPatch | undefined): string | undefined;
38
93
  /**
39
94
  * TLS options for the exec WebSocket, shaped for BOTH runtimes (2026-06-11 in-cluster drill finding):
40
95
  * Bun's `ws` shim IGNORES node-ws's top-level `ca`/`rejectUnauthorized` (handshake fails against a
@@ -50,4 +105,5 @@ export declare function exitCodeFromStatus(statusJson: string): {
50
105
  } | {
51
106
  error: string;
52
107
  };
108
+ export {};
53
109
  //# sourceMappingURL=k8s-exec-protocol.d.ts.map