@sema-agent/server 7.151.0 → 7.152.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 (286) hide show
  1. package/MIGRATION.md +266 -0
  2. package/README.md +15 -3
  3. package/README.zh-CN.md +10 -2
  4. package/USAGE.md +93 -64
  5. package/deploy/sema-up/README.md +5 -4
  6. package/deploy/sema-up/cluster-up.sh +6 -6
  7. package/deploy/sema-up/kube-up.sh +5 -5
  8. package/deploy/sema-up/lib/flagshape.sh +36 -8
  9. package/deploy/sema-up/machines.example.yaml +3 -1
  10. package/deploy/sema-up/sema-up.sh +22 -16
  11. package/dist/approval-ask-audit-store.d.ts +1 -1
  12. package/dist/approval-ask-machine.d.ts +4 -10
  13. package/dist/approval-ask-retention.d.ts +1 -1
  14. package/dist/approval-card.d.ts +10 -12
  15. package/dist/approval-gate-currency.d.ts +8 -8
  16. package/dist/approval-gate-currency.js +3 -4
  17. package/dist/approval-hmac.d.ts +17 -1
  18. package/dist/approval-hmac.js +7 -1
  19. package/dist/approval-queue-kind.d.ts +1 -1
  20. package/dist/approval.d.ts +2 -2
  21. package/dist/approval.js +2 -2
  22. package/dist/bind-host.d.ts +1 -3
  23. package/dist/bind-host.js +2 -3
  24. package/dist/boot/coordinators.d.ts +1 -1
  25. package/dist/boot/coordinators.js +9 -10
  26. package/dist/boot/device-lane.js +2 -0
  27. package/dist/boot/execution-env.d.ts +1 -0
  28. package/dist/boot/execution-env.js +31 -5
  29. package/dist/boot/leader.d.ts +31 -30
  30. package/dist/boot/leader.js +38 -28
  31. package/dist/boot/leg-path-adjudication.d.ts +40 -0
  32. package/dist/boot/leg-path-adjudication.js +18 -0
  33. package/dist/boot/parked-revive-gate.js +3 -15
  34. package/dist/boot/reapers.d.ts +1 -1
  35. package/dist/boot/reapers.js +1 -1
  36. package/dist/boot/resolve-spec.js +12 -26
  37. package/dist/boot/runner-deps.d.ts +39 -35
  38. package/dist/boot/runner-deps.js +92 -89
  39. package/dist/boot/shutdown.d.ts +6 -0
  40. package/dist/boot/shutdown.js +10 -4
  41. package/dist/boot/stage-02-web-admin-pull.js +4 -5
  42. package/dist/boot/stage-05-execution-env.js +2 -1
  43. package/dist/boot/stage-06-runners.d.ts +2 -0
  44. package/dist/boot/stage-06-runners.js +10 -4
  45. package/dist/boot/stage-07-capability-layer.d.ts +1 -0
  46. package/dist/boot/stage-07-capability-layer.js +1 -1
  47. package/dist/boot/stage-08-reapers.d.ts +1 -0
  48. package/dist/boot/stage-09-leader.d.ts +1 -0
  49. package/dist/boot/stage-09-leader.js +2 -2
  50. package/dist/boot/stage-10-http-server.d.ts +8 -0
  51. package/dist/boot/stage-10-http-server.js +11 -15
  52. package/dist/boot/stores.js +7 -4
  53. package/dist/boot/web-admin.js +2 -7
  54. package/dist/capabilities/approvals-push.d.ts +3 -3
  55. package/dist/capabilities/scenarios.d.ts +28 -13
  56. package/dist/capabilities/scenarios.js +2 -2
  57. package/dist/capability-facts.d.ts +22 -20
  58. package/dist/capability-facts.js +18 -34
  59. package/dist/card-rule-offers.d.ts +32 -0
  60. package/dist/card-rule-offers.js +130 -0
  61. package/dist/config-catalog.js +12 -11
  62. package/dist/config-dir-trust.js +4 -2
  63. package/dist/config-invariants.d.ts +18 -7
  64. package/dist/config-invariants.js +51 -8
  65. package/dist/config-lkg.d.ts +5 -19
  66. package/dist/config-lkg.js +8 -62
  67. package/dist/config-provider.d.ts +1 -1
  68. package/dist/config-root-migration.d.ts +2 -2
  69. package/dist/config-types.d.ts +28 -38
  70. package/dist/config.d.ts +7 -5
  71. package/dist/config.js +55 -16
  72. package/dist/control-socket.d.ts +51 -0
  73. package/dist/control-socket.js +115 -0
  74. package/dist/deployment-governance.d.ts +2 -1
  75. package/dist/device-ws-hub.d.ts +7 -0
  76. package/dist/device-ws-hub.js +7 -1
  77. package/dist/elicitation.js +2 -2
  78. package/dist/entry-problems.d.ts +40 -3
  79. package/dist/entry-problems.js +3 -1
  80. package/dist/execution-env-refusal.d.ts +8 -2
  81. package/dist/execution-env-refusal.js +3 -0
  82. package/dist/fleet/engine-signal.d.ts +12 -29
  83. package/dist/fleet/engine-signal.js +1 -7
  84. package/dist/fleet-client.js +2 -5
  85. package/dist/gate-settlement.d.ts +1 -8
  86. package/dist/gate-settlement.js +0 -4
  87. package/dist/history-anchor.js +1 -1
  88. package/dist/hitl-delivery.d.ts +2 -3
  89. package/dist/hitl-delivery.js +0 -2
  90. package/dist/hooks/hook-llm.js +4 -2
  91. package/dist/hooks/hook-runner.d.ts +3 -2
  92. package/dist/hooks/hook-runner.js +16 -1
  93. package/dist/host-decision.d.ts +11 -11
  94. package/dist/http/active-run-conflict.d.ts +35 -2
  95. package/dist/http/active-run-conflict.js +48 -19
  96. package/dist/http/approval-sweeps.d.ts +1 -1
  97. package/dist/http/approval-sweeps.js +5 -2
  98. package/dist/http/checkpoint-ctx.d.ts +2 -0
  99. package/dist/http/checkpoint-ctx.js +1 -0
  100. package/dist/http/dispatch.d.ts +6 -2
  101. package/dist/http/dispatch.js +25 -5
  102. package/dist/http/durable-run-leg.js +0 -1
  103. package/dist/http/health-reader.d.ts +16 -9
  104. package/dist/http/health-reader.js +2 -1
  105. package/dist/http/health-self-report.js +1 -1
  106. package/dist/http/owner-access.d.ts +2 -3
  107. package/dist/http/owner-access.js +3 -4
  108. package/dist/http/principal-gate.d.ts +49 -21
  109. package/dist/http/principal-gate.js +61 -11
  110. package/dist/http/request-channel.d.ts +23 -0
  111. package/dist/http/request-channel.js +9 -0
  112. package/dist/http/request-guards.js +17 -2
  113. package/dist/http/resume-ctx-read.js +20 -3
  114. package/dist/http/resume-legs.d.ts +2 -2
  115. package/dist/http/resume-legs.js +111 -41
  116. package/dist/http/route-ctx.d.ts +73 -7
  117. package/dist/http/route-ctx.js +17 -0
  118. package/dist/http/route-table.d.ts +4 -0
  119. package/dist/http/route-table.js +4 -0
  120. package/dist/http/routes/admin-config-refresh.js +3 -3
  121. package/dist/http/routes/admin-drain.d.ts +3 -0
  122. package/dist/http/routes/admin-drain.js +7 -7
  123. package/dist/http/routes/admin-memory-optout.js +3 -3
  124. package/dist/http/routes/adoption.js +3 -5
  125. package/dist/http/routes/approvals-assistant.d.ts +2 -1
  126. package/dist/http/routes/approvals-assistant.js +95 -48
  127. package/dist/http/routes/capabilities.d.ts +4 -0
  128. package/dist/http/routes/capabilities.js +30 -5
  129. package/dist/http/routes/config-catalog.js +2 -2
  130. package/dist/http/routes/devices.js +1 -1
  131. package/dist/http/routes/diagnostics.d.ts +3 -3
  132. package/dist/http/routes/diagnostics.js +19 -7
  133. package/dist/http/routes/images.js +10 -10
  134. package/dist/http/routes/memory-bundle.js +3 -3
  135. package/dist/http/routes/memory-compliance.js +3 -3
  136. package/dist/http/routes/memory-consolidation.js +2 -2
  137. package/dist/http/routes/memory-origin.js +3 -3
  138. package/dist/http/routes/memory-policy.js +2 -2
  139. package/dist/http/routes/observability.js +3 -3
  140. package/dist/http/routes/retention-ops.js +3 -3
  141. package/dist/http/routes/rules.js +5 -5
  142. package/dist/http/routes/run-permission-mode.d.ts +2 -1
  143. package/dist/http/routes/run-permission-mode.js +2 -2
  144. package/dist/http/routes/runs.d.ts +4 -9
  145. package/dist/http/routes/runs.js +73 -33
  146. package/dist/http/routes/session-sync.d.ts +18 -6
  147. package/dist/http/routes/session-sync.js +38 -36
  148. package/dist/http/routes/sessions.js +20 -9
  149. package/dist/http/routes/tasks.js +28 -37
  150. package/dist/http/routes/workflows.js +1 -1
  151. package/dist/http/server.d.ts +9 -3
  152. package/dist/http/server.js +15 -3
  153. package/dist/http/submit-ctx.d.ts +1 -1
  154. package/dist/http/submit-ctx.js +2 -0
  155. package/dist/human-facing-args.d.ts +33 -0
  156. package/dist/human-facing-args.js +33 -0
  157. package/dist/index.d.ts +1 -1
  158. package/dist/k8s-api-credentials.d.ts +29 -0
  159. package/dist/k8s-api-credentials.js +25 -0
  160. package/dist/lane-home-probe.d.ts +1 -1
  161. package/dist/lane-home-probe.js +2 -1
  162. package/dist/leader/diffup.d.ts +19 -0
  163. package/dist/leader/diffup.js +7 -0
  164. package/dist/leader/leader.d.ts +114 -10
  165. package/dist/leader/leader.js +69 -5
  166. package/dist/leader/repair-wire.js +2 -2
  167. package/dist/leader/run-entry.d.ts +2 -1
  168. package/dist/leader/run-entry.js +1 -1
  169. package/dist/leader/wire.d.ts +42 -132
  170. package/dist/leader/wire.js +81 -67
  171. package/dist/memory-scope.d.ts +20 -9
  172. package/dist/memory-scope.js +15 -3
  173. package/dist/model-entry-headers.d.ts +9 -10
  174. package/dist/model-entry-headers.js +0 -4
  175. package/dist/model-entry-refusal.d.ts +7 -4
  176. package/dist/model-entry-refusal.js +11 -5
  177. package/dist/model-plane-face.d.ts +1 -1
  178. package/dist/model-plane-face.js +3 -1
  179. package/dist/model-route-endpoint.d.ts +13 -7
  180. package/dist/model-route-endpoint.js +14 -11
  181. package/dist/model-select.d.ts +22 -0
  182. package/dist/model-select.js +9 -0
  183. package/dist/model-unreachable-notice.d.ts +11 -0
  184. package/dist/model-unreachable-notice.js +4 -4
  185. package/dist/object-store-buckets.d.ts +640 -6
  186. package/dist/object-store-buckets.js +166 -0
  187. package/dist/observability/fail-open.d.ts +20 -16
  188. package/dist/observability/fail-open.js +20 -16
  189. package/dist/observability/logger.js +3 -1
  190. package/dist/operator-roster.d.ts +68 -0
  191. package/dist/operator-roster.js +32 -0
  192. package/dist/orchestration/workflow-park-decision.d.ts +1 -1
  193. package/dist/outbound-dispatcher.d.ts +71 -8
  194. package/dist/outbound-dispatcher.js +48 -7
  195. package/dist/owner-faces.d.ts +1 -1
  196. package/dist/parked-decide.d.ts +2 -2
  197. package/dist/permission-mode-admission.d.ts +59 -26
  198. package/dist/permission-mode-admission.js +37 -10
  199. package/dist/plugins/approval-ask-store-file.d.ts +4 -0
  200. package/dist/plugins/approval-ask-store-file.js +3 -0
  201. package/dist/plugins/approval-ask-store-memory.d.ts +4 -0
  202. package/dist/plugins/approval-ask-store-memory.js +7 -1
  203. package/dist/plugins/approval-ask-store-sql.d.ts +26 -2
  204. package/dist/plugins/approval-ask-store-sql.js +12 -1
  205. package/dist/plugins/blob-backend.d.ts +83 -7
  206. package/dist/plugins/blob-backend.js +57 -6
  207. package/dist/plugins/checkpoint-index-columns.d.ts +20 -16
  208. package/dist/plugins/checkpoint-index-columns.js +3 -6
  209. package/dist/plugins/checkpoint-store-face.d.ts +119 -61
  210. package/dist/plugins/checkpoint-store-face.js +134 -55
  211. package/dist/plugins/landed-attendants.d.ts +2 -2
  212. package/dist/plugins/local-checkpoint-store.d.ts +13 -3
  213. package/dist/plugins/local-checkpoint-store.js +38 -49
  214. package/dist/plugins/one-time-migrations.d.ts +51 -21
  215. package/dist/plugins/one-time-migrations.js +67 -45
  216. package/dist/plugins/pg-pool.js +11 -9
  217. package/dist/plugins/remote-env-host.d.ts +29 -10
  218. package/dist/plugins/remote-env-host.js +99 -19
  219. package/dist/plugins/remote-env-k8s.d.ts +68 -2
  220. package/dist/plugins/remote-env-k8s.js +107 -24
  221. package/dist/plugins/replica-registration.d.ts +1 -1
  222. package/dist/plugins/replica-registration.js +1 -1
  223. package/dist/plugins/s3-presign.d.ts +23 -0
  224. package/dist/plugins/s3-presign.js +37 -3
  225. package/dist/plugins/scheduler-support.d.ts +2 -2
  226. package/dist/plugins/scheduler-support.js +0 -3
  227. package/dist/plugins/schema-indexes.js +2 -0
  228. package/dist/plugins/send-file-ledger.d.ts +2 -2
  229. package/dist/plugins/send-file-ledger.js +2 -2
  230. package/dist/plugins/send-user-file.d.ts +3 -4
  231. package/dist/plugins/send-user-file.js +56 -52
  232. package/dist/plugins/session-blob-claim-token.d.ts +15 -0
  233. package/dist/plugins/session-blob-claim-token.js +20 -0
  234. package/dist/plugins/session-blob-namespace.d.ts +46 -0
  235. package/dist/plugins/session-blob-namespace.js +116 -0
  236. package/dist/plugins/snapshot-bucket-presence.d.ts +25 -2
  237. package/dist/plugins/snapshot-bucket-presence.js +8 -2
  238. package/dist/plugins/sql-json-column.d.ts +1 -1
  239. package/dist/plugins/store-backend.d.ts +44 -2
  240. package/dist/plugins/store-backend.js +75 -53
  241. package/dist/plugins/task-attachment-store.d.ts +2 -1
  242. package/dist/plugins/task-attachment-store.js +5 -1
  243. package/dist/plugins/tidb-pool.js +19 -24
  244. package/dist/plugins/workspace-custody.d.ts +4 -3
  245. package/dist/question.d.ts +2 -1
  246. package/dist/question.js +5 -13
  247. package/dist/request-form.d.ts +6 -1
  248. package/dist/request-form.js +4 -1
  249. package/dist/rule-issue-reading.d.ts +1 -1
  250. package/dist/rules-consent.d.ts +1 -1
  251. package/dist/run-local.js +8 -9
  252. package/dist/runs.d.ts +17 -8
  253. package/dist/runs.js +15 -2
  254. package/dist/runtime-governance.d.ts +2 -2
  255. package/dist/sema-config-dir.d.ts +2 -1
  256. package/dist/session-sync.d.ts +27 -11
  257. package/dist/session-sync.js +15 -7
  258. package/dist/stores-sql/blob-index-sql.d.ts +30 -9
  259. package/dist/stores-sql/blob-index-sql.js +40 -21
  260. package/dist/stores-sql/checkpoint-store-sql.d.ts +54 -27
  261. package/dist/stores-sql/checkpoint-store-sql.js +70 -146
  262. package/dist/stores-sql/file-history-store-sql.d.ts +64 -16
  263. package/dist/stores-sql/file-history-store-sql.js +100 -28
  264. package/dist/stores-sql/retention-store-sql.js +3 -9
  265. package/dist/task-settings.d.ts +6 -5
  266. package/dist/task-settings.js +3 -3
  267. package/dist/tool-approval.d.ts +342 -518
  268. package/dist/tool-approval.js +498 -1207
  269. package/dist/trace/core-keyset-guard.d.ts +1 -1
  270. package/dist/trace/ledger-events.d.ts +3 -3
  271. package/dist/trace/project.d.ts +2 -3
  272. package/dist/trace/redact.js +3 -2
  273. package/dist/trace/self-minted-wire.d.ts +287 -157
  274. package/dist/trace/self-minted-wire.js +93 -48
  275. package/dist/web-admin-client/apply-effective.js +2 -2
  276. package/dist/web-admin-client/read-warnings.d.ts +2 -2
  277. package/dist/web-admin-client/read-warnings.js +1 -1
  278. package/dist/web-admin-client/types.d.ts +1 -1
  279. package/dist/workspace-space.d.ts +10 -4
  280. package/dist/workspace-space.js +4 -1
  281. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +1 -1
  282. package/package.json +6 -2
  283. package/scripts/embed-minimal.mjs +3 -3
  284. package/wire-changes/server@7.152.0.json +900 -0
  285. package/dist/config-d-migration.d.ts +0 -37
  286. package/dist/config-d-migration.js +0 -60
@@ -0,0 +1,116 @@
1
+ import { presignS3Url } from "./s3-presign.js";
2
+ import { createEgressFetch } from "../egress-redirect.js";
3
+ import { buildDisplaySafeUrl } from "../trace/redact.js";
4
+ import { SESSION_BLOB_CLAIM_KEY } from "../object-store-buckets.js";
5
+ import { sessionBlobKeyPrefixOf } from "./blob-backend.js";
6
+ import { StoreBootRefusalError } from "./store-contracts.js";
7
+ import { SNAPSHOT_BUCKET_PROBE_TIMEOUT_MS } from "./snapshot-bucket-presence.js";
8
+ import { recordFailOpen } from "../observability/fail-open.js";
9
+ export const SESSION_BLOB_CLAIM_SETTLE_MS = 1_000;
10
+ export class SessionBlobNamespaceRefusedError extends StoreBootRefusalError {
11
+ constructor(message) {
12
+ super(message);
13
+ this.name = "SessionBlobNamespaceRefusedError";
14
+ }
15
+ }
16
+ function namespaceText(store) {
17
+ const prefix = store.keyPrefix === undefined || store.keyPrefix === "" ? "unset" : JSON.stringify(store.keyPrefix);
18
+ return `bucket "${store.bucket}" under SESSION_SNAPSHOT_PREFIX (${prefix}) at MINIO_ENDPOINT → ${buildDisplaySafeUrl(store.endpoint)}`;
19
+ }
20
+ function shownToken(body) {
21
+ const t = body.trim();
22
+ return /^[A-Za-z0-9._-]{1,64}$/.test(t) ? t : `${JSON.stringify(t.slice(0, 64))} (not a claim token)`;
23
+ }
24
+ export function buildSessionBlobNamespaceForeignText(store, holder, mine) {
25
+ const claim = `${store.bucket}/${sessionBlobKeyPrefixOf(store, "claim")}`;
26
+ return (`The session blob namespace — ${namespaceText(store)} — belongs to another database: its claim object ${claim} names ${shownToken(holder)}, this database is ${mine}. ` +
27
+ `Attachments and file history keep their bytes there under content addresses, and each engine deletes the ones its own database no longer references, so two databases on one namespace delete each other's bytes. ` +
28
+ `Next: give this engine its own SESSION_SNAPSHOT_PREFIX (to keep the bytes this database already references, copy the objects under the current prefix to the new one first); ` +
29
+ `if the database named in the claim no longer uses this bucket, delete ${claim} and start again.`);
30
+ }
31
+ export function buildSessionBlobNamespaceUnprovenText(store, detail) {
32
+ const claim = `${store.bucket}/${sessionBlobKeyPrefixOf(store, "claim")}`;
33
+ return (`Could not prove that the session blob namespace — ${namespaceText(store)} — belongs to this database (${detail}): without that proof the attachment and file-history sweeps could delete another database's bytes, so nothing there is deleted until it is proven. ` +
34
+ `Next: check that MINIO_ENDPOINT answers and that the engine's key may GetObject and PutObject ${claim}.`);
35
+ }
36
+ class ClaimUnproven extends Error {
37
+ detail;
38
+ constructor(detail) {
39
+ super(detail);
40
+ this.detail = detail;
41
+ }
42
+ }
43
+ export function createSessionBlobNamespace(store, opts = {}) {
44
+ const egressFetch = createEgressFetch("declare the final object-store address as MINIO_ENDPOINT / S3_ENDPOINT", opts.fetchImpl);
45
+ const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
46
+ const key = sessionBlobKeyPrefixOf(store, "claim");
47
+ let opened;
48
+ let inFlight;
49
+ const url = (op, method) => presignS3Url({ op, endpoint: store.endpoint, bucket: store.bucket, key, method, accessKey: store.accessKey, secretKey: store.secretKey, ...(store.region ? { region: store.region } : {}) });
50
+ const readClaim = async () => {
51
+ const res = await egressFetch(url("blob-claim/claim:GET", "GET"), { method: "GET", signal: AbortSignal.timeout(SNAPSHOT_BUCKET_PROBE_TIMEOUT_MS) });
52
+ if (res.status === 404)
53
+ return undefined;
54
+ if (!res.ok)
55
+ throw new ClaimUnproven(`claim read answered HTTP ${res.status}`);
56
+ return (await res.text()).trim();
57
+ };
58
+ const attempt = async (mine) => {
59
+ try {
60
+ let holder = await readClaim();
61
+ if (holder === undefined) {
62
+ const res = await egressFetch(url("blob-claim/claim:PUT", "PUT"), { method: "PUT", body: mine, headers: { "if-none-match": "*" }, signal: AbortSignal.timeout(SNAPSHOT_BUCKET_PROBE_TIMEOUT_MS) });
63
+ if (!res.ok && res.status !== 412)
64
+ return { state: "unproven", detail: `claim write answered HTTP ${res.status}` };
65
+ await sleep(SESSION_BLOB_CLAIM_SETTLE_MS);
66
+ holder = await readClaim();
67
+ if (holder === undefined)
68
+ return { state: "unproven", detail: "the claim just written does not read back" };
69
+ }
70
+ return holder === mine ? { state: "mine" } : { state: "foreign", holder };
71
+ }
72
+ catch (err) {
73
+ return { state: "unproven", detail: err instanceof ClaimUnproven ? err.detail : `claim request failed: ${err instanceof Error ? err.message : String(err)}` };
74
+ }
75
+ };
76
+ const claimOnce = (token) => (inFlight ??= attempt(token).finally(() => {
77
+ inFlight = undefined;
78
+ }));
79
+ return {
80
+ store,
81
+ claimOnce,
82
+ bind(token) {
83
+ opened = token;
84
+ },
85
+ async establish() {
86
+ const token = opened;
87
+ if (token === undefined)
88
+ throw new SessionBlobNamespaceRefusedError(`the session blob namespace — ${namespaceText(store)} — has no claim token bound (StoreBackend.ensureSchema() binds it) — refusing to open`);
89
+ const r = await claimOnce(token);
90
+ switch (r.state) {
91
+ case "mine":
92
+ return;
93
+ case "foreign":
94
+ throw new SessionBlobNamespaceRefusedError(buildSessionBlobNamespaceForeignText(store, r.holder, token));
95
+ case "unproven":
96
+ recordFailOpen("server.session-blob-namespace.claim-unproven", `bucket=${store.bucket} prefix=${store.keyPrefix === undefined || store.keyPrefix === "" ? "unset" : JSON.stringify(store.keyPrefix)} endpoint=${buildDisplaySafeUrl(store.endpoint)} ${r.detail}`);
97
+ return;
98
+ default: {
99
+ const unknown = r;
100
+ throw new Error(`unknown claim reading ${JSON.stringify(unknown)}`);
101
+ }
102
+ }
103
+ },
104
+ async assertOwned() {
105
+ if (opened === undefined)
106
+ throw new Error(`refusing to delete: the session blob namespace — ${namespaceText(store)} — has no claim token (the store was not opened: StoreBackend.ensureSchema() binds this database's)`);
107
+ const r = await claimOnce(opened);
108
+ if (r.state === "mine")
109
+ return;
110
+ throw new Error(r.state === "foreign"
111
+ ? `refusing to delete: ${buildSessionBlobNamespaceForeignText(store, r.holder, opened)}`
112
+ : `refusing to delete: ${buildSessionBlobNamespaceUnprovenText(store, r.detail)}`);
113
+ },
114
+ };
115
+ }
116
+ //# sourceMappingURL=session-blob-namespace.js.map
@@ -9,7 +9,9 @@
9
9
  * 被读成「这个空间头一回」,从空目录起;run 末的保存虽失败,报的却是一条不点名桶的 curl 404,没有一行说「桶没建」。
10
10
  *
11
11
  * ## 形(一条规则,不加特例)
12
- * · 核的是 {@link MinioBlobBackend.probeBucket}(presigned 列举 `max-keys=1`);**只有** 404 + `NoSuchBucket` 是「缺」,其余证明不了的形(连不上 / 403 / 5xx / 没有
12
+ * · 核的是 {@link MinioBlobBackend.probeBucket}(presigned 列举 `max-keys=1`,列举前缀 = 角色行的 `probePrefix`:会话桶 = 退役快照纪元的根 `{legacyBlobRoot}`,
13
+ * 工作区桶 = 适配器写入的那只前缀 `<WORKSPACE_SNAPSHOT_PREFIX>/`,与适配器同一读 `workspaceSnapshotPrefixOf` —— 按前缀授列举的最小权限凭据对探针与对适配器同答;
14
+ * 两行各是对象存储动作表的一行 `boot-probe/*:LIST`);**只有** 404 + `NoSuchBucket` 是「缺」,其余证明不了的形(连不上 / 403 / 5xx / 没有
13
15
  * NoSuchBucket 的 404)⇒ **不拒**、照常起服,走登记过的 F 类兜底口 `recordFailOpen("server.snapshot-bucket.presence-unproven")`(计数 + 探针行 + 记录器的一次性 warn;
14
16
  * 三问写在该 tag 的 note 里)—— 拒启要有证据,没有证据只留痕;
15
17
  * · 缺 = 配置好的店资源还没就绪,与「库连不上」同一类处境:在 `STORE_LOCK_WAIT_MS` 窗内重试({@link openWithinWait},同一只窗、同一只节拍、同一条窗尾规则 —— 编排里
@@ -36,6 +38,16 @@ export declare const SNAPSHOT_BUCKET_ROLES: {
36
38
  readonly defaultName: "session-snapshots";
37
39
  readonly consequence: "every snapshot-offload write and SendUserFile private-track request would answer 404 NoSuchBucket (and the orphan sweep would log one every minute, forever)";
38
40
  readonly createHint: "; this version's sema-up scripts create it — re-run them on an existing sema-up deployment";
41
+ readonly probeOp: "boot-probe/session:LIST";
42
+ readonly probePrefix: (store: {
43
+ endpoint: string;
44
+ bucket: string;
45
+ accessKey: string;
46
+ secretKey: string;
47
+ region?: string;
48
+ keyPrefix?: string;
49
+ presignTtlSec?: number;
50
+ }) => string;
39
51
  };
40
52
  readonly workspace: {
41
53
  readonly noun: "workspace snapshot bucket";
@@ -43,9 +55,20 @@ export declare const SNAPSHOT_BUCKET_ROLES: {
43
55
  readonly defaultName: "workspaces";
44
56
  readonly consequence: "no workspace snapshot could be saved or restored (every run would start from an empty /workspace and every run-end save would fail on a bare 404), and no line would say the bucket is missing";
45
57
  readonly createHint: "";
58
+ readonly probeOp: "boot-probe/workspace:LIST";
59
+ readonly probePrefix: (store: {
60
+ endpoint: string;
61
+ bucket: string;
62
+ accessKey: string;
63
+ secretKey: string;
64
+ region?: string;
65
+ keyPrefix?: string;
66
+ presignTtlSec?: number;
67
+ }) => string;
46
68
  };
47
69
  };
48
- /** boot 期单次探针的上界(ms):黑洞端点(丢包不拒连)不许把起服拖到对象存储 IO 的 120s;到点 ⇒ `unknown`(证明不了缺桶 ⇒ 不拒、报一次)。 */
70
+ /** boot 期单次探针的上界(ms):黑洞端点(丢包不拒连)不许把起服拖到对象存储 IO 的 120s;到点 ⇒ `unknown`(证明不了缺桶 ⇒ 不拒、报一次)。
71
+ * 会话 blob 命名空间的认领请求(`session-blob-namespace.ts`,起服的那一次与每次删除前的重读)读同一只数:起服对快照桶的探针只有这一个上界。 */
49
72
  export declare const SNAPSHOT_BUCKET_PROBE_TIMEOUT_MS = 10000;
50
73
  /** 缺桶的拒启句(唯一铸点)。 */
51
74
  /** @param endpoint `MINIO_ENDPOINT` 的**显示安全形**(调用方过 {@link buildDisplaySafeUrl};本函数不碰原串)。 */
@@ -1,5 +1,6 @@
1
1
  import { buildDisplaySafeUrl } from "../trace/redact.js";
2
- import { MinioBlobBackend } from "./blob-backend.js";
2
+ import { MinioBlobBackend, sessionBlobKeyPrefixOf } from "./blob-backend.js";
3
+ import { workspaceSnapshotPrefixOf } from "../workspace-space.js";
3
4
  import { recordFailOpen } from "../observability/fail-open.js";
4
5
  import { SESSION_SNAPSHOT_BUCKET_DEFAULT, WORKSPACE_SNAPSHOT_BUCKET_DEFAULT } from "../object-store-buckets.js";
5
6
  import { isStoreOpenWindowExpired, openWithinWait } from "./store-open-wait.js";
@@ -10,6 +11,8 @@ export const SNAPSHOT_BUCKET_ROLES = {
10
11
  defaultName: SESSION_SNAPSHOT_BUCKET_DEFAULT,
11
12
  consequence: "every snapshot-offload write and SendUserFile private-track request would answer 404 NoSuchBucket (and the orphan sweep would log one every minute, forever)",
12
13
  createHint: "; this version's sema-up scripts create it — re-run them on an existing sema-up deployment",
14
+ probeOp: "boot-probe/session:LIST",
15
+ probePrefix: (store) => sessionBlobKeyPrefixOf(store, "legacy-blobs"),
13
16
  },
14
17
  workspace: {
15
18
  noun: "workspace snapshot bucket",
@@ -17,6 +20,8 @@ export const SNAPSHOT_BUCKET_ROLES = {
17
20
  defaultName: WORKSPACE_SNAPSHOT_BUCKET_DEFAULT,
18
21
  consequence: "no workspace snapshot could be saved or restored (every run would start from an empty /workspace and every run-end save would fail on a bare 404), and no line would say the bucket is missing",
19
22
  createHint: "",
23
+ probeOp: "boot-probe/workspace:LIST",
24
+ probePrefix: (store) => `${workspaceSnapshotPrefixOf(store)}/`,
20
25
  },
21
26
  };
22
27
  export const SNAPSHOT_BUCKET_PROBE_TIMEOUT_MS = 10_000;
@@ -34,7 +39,8 @@ export function buildSnapshotBucketRefusal(bucket, endpoint, waitedMs, role = "s
34
39
  }
35
40
  export const SNAPSHOT_BUCKET_PROBE_PENDING = "probe still pending when the window ended";
36
41
  export async function ensureSnapshotBucket(store, waitMs, logger, clock, role = "session") {
37
- const probe = new MinioBlobBackend({ endpoint: store.endpoint, bucket: store.bucket, accessKey: store.accessKey, secretKey: store.secretKey, ...(store.region ? { region: store.region } : {}), ...(store.keyPrefix ? { keyPrefix: store.keyPrefix } : {}) });
42
+ const roleRow = SNAPSHOT_BUCKET_ROLES[role];
43
+ const probe = new MinioBlobBackend({ endpoint: store.endpoint, bucket: store.bucket, accessKey: store.accessKey, secretKey: store.secretKey, ...(store.region ? { region: store.region } : {}), keyPrefix: roleRow.probePrefix(store), ops: { list: roleRow.probeOp } });
38
44
  let verdict;
39
45
  try {
40
46
  verdict = await openWithinWait(async () => {
@@ -347,7 +347,7 @@ export declare const readNonNullJsonValueColumn: JsonColumnReader<unknown>;
347
347
  * **一只 JSON 数组**的承载形读者(内容判据归各列自己的既有属主,这里只判承载形)。
348
348
  *
349
349
  * 🔴 为什么在属主里而不是各店各写一遍([ref] 车③a 把车② 的店内私有形上收):今天有三个消费方
350
- * (checkpoint 的 `rule_suggestions` / `pending_steer_queue`、memory-engine 的 `embedding`),判据逐字
350
+ * (checkpoint 的 `pending_steer_queue`、image-index 的 `bands`、memory-engine 的 `embedding`),判据逐字
351
351
  * 相同,而「这一格是不是一只数组」恰好是坏体四形的落点 —— 三份拷贝迟早有一份忘了判 `null` 字面。
352
352
  */
353
353
  export declare const readJsonArrayColumn: JsonColumnReader<unknown[]>;
@@ -27,6 +27,7 @@ import { TiDBResumeAnchorStore, PgResumeAnchorStore } from "./resume-anchor-stor
27
27
  import { SqlSessionPolicyStore } from "../stores-sql/session-policy-store-sql.js";
28
28
  import { SqlFileHistoryStore } from "../stores-sql/file-history-store-sql.js";
29
29
  import { type LandedAttendantSeat } from "./landed-attendants.js";
30
+ import { type SessionBlobNamespace } from "./session-blob-namespace.js";
30
31
  import type { ServiceConfig } from "../config-types.js";
31
32
  import { TiDBRunStore, PgRunStore } from "./run-store-sql.js";
32
33
  import { TiDBCheckpointStore, PgCheckpointStore } from "../stores-sql/checkpoint-store-sql.js";
@@ -112,9 +113,32 @@ export type SqlBlobPutResult = {
112
113
  export interface ServiceBlobExchange extends FileHistoryBlobExchange {
113
114
  put(hash: string, bytes: Uint8Array): Promise<SqlBlobPutResult>;
114
115
  }
116
+ /**
117
+ * 一次会话同步推送 / 拉取**落在哪一格**(落点):会话 id + 落点属主格 + 门读时这只 id 有没有住客。路由在门判之后经 `landingOf`(`http/routes/session-sync.ts`)铸一次,
118
+ * 导入租约、blob 授予、blob 可见性三处读同一只;店不解释属主(只按字节存比,无主格记域键 `""`)。
119
+ * · `occupied` = 门读时这只 id 被占着(活行 ∪ 在删行,「被占着」的唯一读法)。只有被占着的 id,它的历史图才是来历:空闲 id 上残留的图(删后残留 / 迟到的重放落下的)
120
+ * 不是任何一位调用方持有过字节的证明,所以空闲落点的可见集只读授予。
121
+ */
122
+ export interface SyncLanding {
123
+ readonly sessionId: string;
124
+ readonly owner: string | null;
125
+ readonly occupied: boolean;
126
+ }
127
+ /**
128
+ * blob 的**按落点来历**面(SQL 两孪生有;与 {@link ServiceBlobExchange} 并列,不塞进 core 的交换组 —— core 判官只交回组的三员)。律:一枚 blob 对一个落点可见
129
+ * ⇔ (落点被占着 ∧ 这只会话的图引用它) ∨ 这个落点自己上传过它(授予),且字节在场;字节仍全局去重一份,可见性另表 —— 两个事实两张表。
130
+ * · `putAt`:先核内容地址 → 再写字节(交换组同一写路径)→ **字节写成才**写授予(`granted_at = now`,单调)。答形 = {@link SqlBlobPutResult}(413 / 400 / 502 三臂不变);授予写失败 ⇒ 写失败臂。
131
+ * · `visibleAt`:`hashes` 里对这个落点可见的那几枚。先算候选(图项 ∪ 授予项)、只对候选问字节在不在 —— 耗时只随这个落点自己的状态变,不随别人存过什么变。
132
+ */
133
+ export interface ServiceBlobLanding {
134
+ putAt(landing: SyncLanding, hash: string, bytes: Uint8Array): Promise<SqlBlobPutResult>;
135
+ visibleAt(landing: SyncLanding, hashes: readonly string[]): Promise<ReadonlySet<string>>;
136
+ }
115
137
  export type ServiceFileHistoryStore = FileHistoryStore & {
116
138
  /** The blob exchange (SQL twins only; core's local File store carries none). Narrowed per {@link ServiceBlobExchange}. */
117
139
  readonly blobExchange?: ServiceBlobExchange;
140
+ /** The per-landing provenance face over the same blob domain (SQL twins only) — see {@link ServiceBlobLanding}. The sync routes read blobs only through it. */
141
+ readonly blobLanding?: ServiceBlobLanding;
118
142
  /** E21 purge / overwrite-dst sync wipe: drop the WHOLE scope incl. v1 baselines + DV-14 markers
119
143
  * (`reap(scope, [])` deliberately retains those while the scope lives — not a purge). SQL twins only. */
120
144
  deleteBySession?(scope: string): Promise<number>;
@@ -225,8 +249,8 @@ export interface StoreBackend {
225
249
  * (它承诺的是「稍后还能批、崩了行还在」)。机器钉 = `wiring-governance-operator.test.ts` 的
226
250
  * 「三腿 approvalAsk 全持久」格。
227
251
  *
228
- * 无 backend 的 env-only worker 压根没有 `StoreBackend` ⇒ 协调器拿到 undefined askStore ⇒ 车2 的
229
- * D1 逐字现行为(store 缺席 = 现行 `tool_approval` 活卡腿一字不变)。 */
252
+ * 无 backend 的 env-only worker 压根没有 `StoreBackend` ⇒ 呈卡协议不上场 ⇒ 起服不构造审批协调器(没有「无账协调器」这一形,
253
+ * `boot/coordinators.ts`)。 */
230
254
  approvalAsk(): ApprovalAskStoreType;
231
255
  /**
232
256
  * [ref] 车二(core 5.18.0 [ref] + 5.22.0 [ref]):**持久化权限规则店**(「不再询问」车道)
@@ -303,6 +327,10 @@ export interface StoreBackend {
303
327
  * epoch is gone (its manifest index table retired with it), so EVERY row in that domain is an orphan and its
304
328
  * BYTES (incl. MinIO objects) would otherwise outlive the epoch forever. SQL backends only; reaper-driven. */
305
329
  legacySnapshotBlobSweep?(): Promise<number>;
330
+ /** The session blob namespace this backend's database holds (`session-blob-namespace.ts`): `ensureSchema()` proves it (another database's ⇒ refuse to open),
331
+ * and every session blob port of this deployment is built from it (file history and the retired epoch here, attachments at boot). SQL backends with object
332
+ * storage configured; absent otherwise (no object storage, or the local backend — its bytes are on its own disk). */
333
+ sessionBlobNamespace?(): SessionBlobNamespace | undefined;
306
334
  /** 🔴 **一次性迁移** —— 落在已写进库里的字节上的退役,升一次级改完、兼容臂当场删干净(硬 breaking 纪律:持久数据面 =
307
335
  * 响亮拒 + 一次性迁移,窗过即删,永不长期双读)。逐条语义、幂等与拒启臂见该模块头注。
308
336
  *
@@ -449,11 +477,23 @@ export interface StoreBackend {
449
477
  fileUsageWindowStore?(): UsageWindowStore;
450
478
  /** **local 后端专有**:构造时交给两半店(会话店顶层、检查点店)的那一份声明(唯一读者 `localStoreDeclarationOf`)—— 装配值从这里读回,不重算。SQL 后端省略。 */
451
479
  localStoreDeclaration?(): LocalStoreDeclaration;
480
+ /**
481
+ * **本进程此刻持锁独占的物理数据根**(正读数;本机控制 socket 开不开口只读它 —— `control-socket.ts` 的前提律):打开成功的 local 后端 ⇒ 引擎 File 后端取锁的那只根
482
+ * (绝对物理路径,`dataDirLayout.physicalRoot` 同一只),`close()` 之后 ⇒ `undefined`;打开那一刻被拒的 local 后端 ⇒ `undefined`(它的 `kind` 也是 `"local"`,所以判「独占」不读 `kind`)。
483
+ * SQL 后端省略(省略 = 不独占任何数据根,同 `undefined`)。
484
+ */
485
+ exclusiveDataRoot?(): string | undefined;
452
486
  /**
453
487
  * 会话店活引用判的 workflow 那一腿(会话种类的老化;`local-session-live-reference.ts`):本后端的会话店读不到 workflow run 店时(local:那只店在 boot 第 6 段才铸),
454
488
  * 第 6 段把这台部署的 workflow run 店交过来(不起 workflow ⇒ `"none"`);绑一次。SQL 两孪生不实现(它们的判是一句 SQL,读 `workflow_run` 表)。
455
489
  */
456
490
  bindWorkflowRunReader?(reader: LocalWorkflowRunsReader | "none"): void;
491
+ /**
492
+ * 「不再询问」规则 project root 的铸卡快照读法(跑这条 run 的副本的会话 cwd 登记;与活卡铸点同一只函数)—— 第 6 段绑一次,本后端的 checkpoint 店写口 `put`
493
+ * 在停驻那一刻读它落快照(SQL 列 `card_scope_root` / local 快照索引)。未绑 / 不实现(嵌入方的替身后端)⇒ 不可知 ⇒ 不落快照(兑付答 `scope-unresolved`:只会拒)。
494
+ * 本仓三只后端都实现;形同 {@link bindWorkflowRunReader} 的晚绑。
495
+ */
496
+ bindCardScopeRoot?(read: (sessionId: string) => string | undefined): void;
457
497
  }
458
498
  /**
459
499
  * 托管留存执行器的**组合**:引擎座表那一半(`SqlRetentionStore`,事务属主)+ 部署那一半(hold / 审计 / 认领判 / 十三张部署表,
@@ -554,6 +594,8 @@ export interface LocalStoreBackend extends StoreBackend {
554
594
  localStoreDeclaration(): LocalStoreDeclaration;
555
595
  /** local 后端恒有(会话店活引用判的 workflow 晚绑座;接口上可选的那一口在这里必在)。 */
556
596
  bindWorkflowRunReader(reader: LocalWorkflowRunsReader | "none"): void;
597
+ /** local 后端恒有这一口(接口上可选的那一口在这里必在);答 `undefined` 的是拒启的那一只与关过的那一只(见 {@link StoreBackend.exclusiveDataRoot})。 */
598
+ exclusiveDataRoot(): string | undefined;
557
599
  }
558
600
  /** 引擎 File 后端上会话店之外、本仓没有自己一份口的店(run-local 的 Runner 接它们;工具结果 / 用量窗 / 会话规则走既有的 `toolResult()` / `fileUsageWindowStore()` / `sessionPolicy()` 口)。 */
559
601
  export type LocalEngineFileStores = Pick<FileStorageBackend, "checkpointStore" | "backgroundAgentStore" | "mailboxStore" | "rosterStore">;
@@ -44,10 +44,12 @@ import { TiDBResumeAnchorStore, PgResumeAnchorStore } from "./resume-anchor-stor
44
44
  import { TiDBSessionPolicyStore, PgSessionPolicyStore, SqlSessionPolicyStore } from "../stores-sql/session-policy-store-sql.js";
45
45
  import { SQL_SESSION_ROOT_SEAT } from "./session-row-sql.js";
46
46
  import { TiDBWorkflowJournalStore, PgWorkflowJournalStore, SqlWorkflowJournalStore } from "../stores-sql/workflow-journal-store-sql.js";
47
- import { TiDBFileHistoryStore, PgFileHistoryStore, FILE_HISTORY_BLOB_KEY_PREFIX, SqlFileHistoryStore } from "../stores-sql/file-history-store-sql.js";
47
+ import { TiDBFileHistoryStore, PgFileHistoryStore, SqlFileHistoryStore, SqlBlobExchange, SqlBlobLanding } from "../stores-sql/file-history-store-sql.js";
48
48
  import { createLocalLandedAttendantSeat, createSqlLandedAttendantSeat } from "./landed-attendants.js";
49
- import { SqlBlobIndex } from "../stores-sql/blob-index-sql.js";
50
- import { MinioBlobBackend } from "./blob-backend.js";
49
+ import { SqlBlobIndex, blobGcCutoffAt } from "../stores-sql/blob-index-sql.js";
50
+ import { createSessionBlobBackend } from "./blob-backend.js";
51
+ import { createSessionBlobNamespace } from "./session-blob-namespace.js";
52
+ import { ensureSessionBlobClaimToken } from "./session-blob-claim-token.js";
51
53
  import { buildDisplaySafeUrl } from "../trace/redact.js";
52
54
  import { fileMemoryControlDirFor, memoryEngineRootFor } from "../memory-scope.js";
53
55
  import { createTidbPool, ensureSchema as ensureTidbSchema } from "./tidb-pool.js";
@@ -92,6 +94,7 @@ import { PgMailboxStore, TiDBMailboxStore } from "../stores-sql/mailbox-store-sq
92
94
  import { PgRosterStore, TiDBRosterStore } from "../stores-sql/roster-store-sql.js";
93
95
  import { TiDBSessionStorage } from "../stores-sql/tidb-session-storage.js";
94
96
  import { PgUsageWindowStore, TiDBUsageWindowStore } from "../stores-sql/usage-window-store-sql.js";
97
+ import { createCardScopeRootSeat } from "./checkpoint-store-face.js";
95
98
  export function createRunStallWiring(config, observers) {
96
99
  const count = observers?.runStallVerdicts;
97
100
  return { windowMs: () => config.runStaleSec * 1000, onVerdicts: (landed) => count?.(landed) };
@@ -99,19 +102,10 @@ export function createRunStallWiring(config, observers) {
99
102
  export function workflowJournalOf(config, backend) {
100
103
  return config.selfOrchestrationEnabled && backend ? backend.workflowJournal() : undefined;
101
104
  }
102
- function fileHistoryOptions(config) {
103
- const s = config.snapshotBlobStore;
104
- const placement = s
105
+ function fileHistoryOptions(config, namespace) {
106
+ const placement = namespace
105
107
  ? {
106
- blobs: new MinioBlobBackend({
107
- endpoint: s.endpoint,
108
- bucket: s.bucket,
109
- accessKey: s.accessKey,
110
- secretKey: s.secretKey,
111
- ...(s.region ? { region: s.region } : {}),
112
- keyPrefix: `${s.keyPrefix ?? ""}${FILE_HISTORY_BLOB_KEY_PREFIX}`,
113
- ...(s.presignTtlSec ? { presignTtlSec: s.presignTtlSec } : {}),
114
- }),
108
+ blobs: createSessionBlobBackend(namespace, "file-history"),
115
109
  }
116
110
  : {
117
111
  ...(config.snapshotBlobSqlMaxBytes !== undefined ? { blobSqlMaxBytes: config.snapshotBlobSqlMaxBytes } : {}),
@@ -119,35 +113,21 @@ function fileHistoryOptions(config) {
119
113
  };
120
114
  return { ...config.fileHistory, ...placement };
121
115
  }
122
- function legacyBlobIndex(config, dialect, pool) {
123
- const s = config.snapshotBlobStore;
124
- const bytes = s
125
- ? new MinioBlobBackend({
126
- endpoint: s.endpoint,
127
- bucket: s.bucket,
128
- accessKey: s.accessKey,
129
- secretKey: s.secretKey,
130
- ...(s.region ? { region: s.region } : {}),
131
- ...(s.keyPrefix ? { keyPrefix: s.keyPrefix } : {}),
132
- ...(s.presignTtlSec ? { presignTtlSec: s.presignTtlSec } : {}),
133
- })
134
- : undefined;
116
+ function legacyBlobIndex(config, dialect, pool, namespace) {
117
+ const bytes = namespace ? createSessionBlobBackend(namespace, "legacy-blobs") : undefined;
135
118
  return new SqlBlobIndex(dialect, pool, { table: "snapshot_blob", ...(bytes !== undefined ? { bytes } : {}), log: createLogger(config.logLevel) });
136
119
  }
137
- async function legacySweepImpl(config, dialect, pool) {
120
+ async function legacySweepImpl(config, dialect, pool, namespace) {
138
121
  const sql = "SELECT blob_hash FROM snapshot_blob";
139
- let orphans;
140
- if (dialect === "mysql") {
141
- const [rows] = await pool.query(sql);
142
- orphans = rows.map((r) => String(r.blob_hash));
143
- }
144
- else {
122
+ const collect = async () => {
123
+ if (dialect === "mysql") {
124
+ const [rows] = await pool.query(sql);
125
+ return rows.map((r) => String(r.blob_hash));
126
+ }
145
127
  const r = await pool.query(sql);
146
- orphans = r.rows.map((row) => row.blob_hash);
147
- }
148
- if (orphans.length === 0)
149
- return 0;
150
- return legacyBlobIndex(config, dialect, pool).sweep(orphans);
128
+ return r.rows.map((row) => row.blob_hash);
129
+ };
130
+ return legacyBlobIndex(config, dialect, pool, namespace).sweep(collect, blobGcCutoffAt(Date.now()));
151
131
  }
152
132
  export function createSqlRetentionCallHooks(db, opts, input) {
153
133
  const deployment = createRetentionDeploymentHooks(db.dialect, opts, input);
@@ -264,9 +244,16 @@ class TiDBBackend {
264
244
  this.config = config;
265
245
  this.observers = observers;
266
246
  this.onWarn = onWarn;
247
+ this.blobNamespaceInst = config.snapshotBlobStore ? createSessionBlobNamespace(config.snapshotBlobStore) : undefined;
248
+ }
249
+ async ensureSchema() {
250
+ await ensureTidbSchema(this.pool);
251
+ if (this.blobNamespaceInst)
252
+ this.blobNamespaceInst.bind(await ensureSessionBlobClaimToken(mysqlDriver(this.pool)));
267
253
  }
268
- ensureSchema() { return ensureTidbSchema(this.pool); }
269
254
  close() { return this.pool.end(); }
255
+ sessionBlobNamespace() { return this.blobNamespaceInst; }
256
+ blobNamespaceInst;
270
257
  run() { return (this.runStoreInst ??= new TiDBRunStore(this.pool, createRunStallWiring(this.config, this.observers))); }
271
258
  runStoreInst;
272
259
  resumeAnchor() { return (this.resumeAnchorInst ??= new TiDBResumeAnchorStore(this.pool)); }
@@ -280,14 +267,14 @@ class TiDBBackend {
280
267
  userInputLedger() { return new TiDBUserInputLedgerStore(this.pool); }
281
268
  sessionPolicy() { return (this.sessionPolicyInst ??= new TiDBSessionPolicyStore(this.pool, SQL_SESSION_ROOT_SEAT, policyStoreLoggerOf(this.onWarn))); }
282
269
  sessionPolicyInst;
283
- fileHistory() { return (this.fileHistoryInst ??= new TiDBFileHistoryStore(this.pool, fileHistoryOptions(this.config))); }
270
+ fileHistory() { return (this.fileHistoryInst ??= new TiDBFileHistoryStore(this.pool, fileHistoryOptions(this.config, this.sessionBlobNamespace()))); }
284
271
  fileHistoryInst;
285
272
  landedAttendants() { return (this.landedAttendantsInst ??= sqlLandedAttendantsOf(mysqlDriver(this.pool), this.fileHistory(), this.sessionPolicy())); }
286
273
  landedAttendantsInst;
287
274
  memoryOptOutGrant() { return new TiDBMemoryOptOutGrantStore(this.pool); }
288
275
  sessionCaptureRecords() { return () => (this.captureRecordsInst ??= new TiDBSessionCaptureRecordStore(this.pool)); }
289
276
  captureRecordsInst;
290
- legacySnapshotBlobSweep() { return legacySweepImpl(this.config, "mysql", this.pool); }
277
+ legacySnapshotBlobSweep() { return legacySweepImpl(this.config, "mysql", this.pool, this.sessionBlobNamespace()); }
291
278
  runOneTimeMigrations() { return runSqlOneTimeMigrations("mysql", this.pool, buildSqlOneTimeMigrationInput(this.config)); }
292
279
  workflowJournal() { return new TiDBWorkflowJournalStore(this.pool); }
293
280
  outcomeSink() { return new TiDBOutcomeLedger(this.pool); }
@@ -298,7 +285,9 @@ class TiDBBackend {
298
285
  workflowParkDecisions(records) { return (this.parkDecisionsInst = decisionStoreBoundOnce(this.parkDecisionsInst, records, () => new SqlWorkflowParkDecisionStore(this.run(), sqlRecordFenceOf(records)))); }
299
286
  parkDecisionsInst;
300
287
  sharedMemoryStore(authorizer) { return new TiDBSharedMemoryStore(this.pool, authorizer); }
301
- checkpoint(logger) { return new TiDBCheckpointStore(this.pool, logger, { retention: sqlStoreRetention(this.config.retentionSweep) }); }
288
+ cardScopeRootSeat = createCardScopeRootSeat();
289
+ bindCardScopeRoot(read) { this.cardScopeRootSeat.bind(read); }
290
+ checkpoint(logger) { return new TiDBCheckpointStore(this.pool, logger, { retention: sqlStoreRetention(this.config.retentionSweep), cardScopeRoot: this.cardScopeRootSeat }); }
302
291
  checkpointReadView() { return this.checkpoint(); }
303
292
  toolResult() { return new TiDBToolResultStore(this.pool, { retention: sqlStoreRetention(this.config.retentionSweep) }); }
304
293
  imageIndex() { return new TiDBImageIndex(this.pool); }
@@ -318,13 +307,13 @@ class TiDBBackend {
318
307
  async dbNowMs() { const [rows] = await this.pool.query("SELECT ROUND(UNIX_TIMESTAMP(NOW(3))*1000) AS ms"); return Number(rows[0]?.ms); }
319
308
  }
320
309
  sealStoreClass(TiDBBackend, "tidb-backend", {
321
- ensureSchema: "boot", close: "boot", run: "exit", resumeAnchor: "exit", approvalExemption: "exit", approvalNonce: "exit", orgDeliveryQuota: "exit",
310
+ ensureSchema: "boot", close: "boot", sessionBlobNamespace: "exit", run: "exit", resumeAnchor: "exit", approvalExemption: "exit", approvalNonce: "exit", orgDeliveryQuota: "exit",
322
311
  approvalAsk: "exit", permissionRule: "exit", sendFileLedger: "exit", userInputLedger: "exit", sessionPolicy: "exit", fileHistory: "exit",
323
312
  memoryOptOutGrant: "exit", sessionCaptureRecords: "exit", legacySnapshotBlobSweep: "exit", runOneTimeMigrations: "boot", workflowJournal: "exit",
324
313
  outcomeSink: "exit", workflowRun: "exit", completionInbox: "exit", notifyJournal: "exit", workflowAgentSessionIndex: "exit", workflowParkDecisions: "exit",
325
314
  sharedMemoryStore: "exit", checkpoint: "exit", checkpointReadView: "exit", toolResult: "exit", imageIndex: "exit", imageBake: "exit", leaderRun: "exit",
326
315
  device: "exit", retention: "exit", sessionEraser: "exit", retentionLane: "exit", breaker: "exit", costQuota: "exit", rateLimiter: "exit", session: "exit",
327
- adoptionLog: "exit", mysqlPool: "boot", pgPool: "boot", dbNowMs: "exit", landedAttendants: "exit",
316
+ adoptionLog: "exit", mysqlPool: "boot", pgPool: "boot", dbNowMs: "exit", landedAttendants: "exit", bindCardScopeRoot: "exit",
328
317
  });
329
318
  class PgBackend {
330
319
  pool;
@@ -337,9 +326,16 @@ class PgBackend {
337
326
  this.config = config;
338
327
  this.observers = observers;
339
328
  this.onWarn = onWarn;
329
+ this.blobNamespaceInst = config.snapshotBlobStore ? createSessionBlobNamespace(config.snapshotBlobStore) : undefined;
330
+ }
331
+ async ensureSchema() {
332
+ await ensurePgSchema(this.pool);
333
+ if (this.blobNamespaceInst)
334
+ this.blobNamespaceInst.bind(await ensureSessionBlobClaimToken(pgDriver(this.pool)));
340
335
  }
341
- ensureSchema() { return ensurePgSchema(this.pool); }
342
336
  close() { return this.pool.end(); }
337
+ sessionBlobNamespace() { return this.blobNamespaceInst; }
338
+ blobNamespaceInst;
343
339
  run() { return (this.runStoreInst ??= new PgRunStore(this.pool, createRunStallWiring(this.config, this.observers))); }
344
340
  runStoreInst;
345
341
  resumeAnchor() { return (this.resumeAnchorInst ??= new PgResumeAnchorStore(this.pool)); }
@@ -353,14 +349,14 @@ class PgBackend {
353
349
  userInputLedger() { return new PgUserInputLedgerStore(this.pool); }
354
350
  sessionPolicy() { return (this.sessionPolicyInst ??= new PgSessionPolicyStore(this.pool, SQL_SESSION_ROOT_SEAT, policyStoreLoggerOf(this.onWarn))); }
355
351
  sessionPolicyInst;
356
- fileHistory() { return (this.fileHistoryInst ??= new PgFileHistoryStore(this.pool, fileHistoryOptions(this.config))); }
352
+ fileHistory() { return (this.fileHistoryInst ??= new PgFileHistoryStore(this.pool, fileHistoryOptions(this.config, this.sessionBlobNamespace()))); }
357
353
  fileHistoryInst;
358
354
  landedAttendants() { return (this.landedAttendantsInst ??= sqlLandedAttendantsOf(pgDriver(this.pool), this.fileHistory(), this.sessionPolicy())); }
359
355
  landedAttendantsInst;
360
356
  memoryOptOutGrant() { return new PgMemoryOptOutGrantStore(this.pool); }
361
357
  sessionCaptureRecords() { return () => (this.captureRecordsInst ??= new PgSessionCaptureRecordStore(this.pool)); }
362
358
  captureRecordsInst;
363
- legacySnapshotBlobSweep() { return legacySweepImpl(this.config, "pg", this.pool); }
359
+ legacySnapshotBlobSweep() { return legacySweepImpl(this.config, "pg", this.pool, this.sessionBlobNamespace()); }
364
360
  runOneTimeMigrations() { return runSqlOneTimeMigrations("pg", this.pool, buildSqlOneTimeMigrationInput(this.config)); }
365
361
  workflowJournal() { return new PgWorkflowJournalStore(this.pool); }
366
362
  outcomeSink() { return new PgOutcomeLedger(this.pool); }
@@ -371,7 +367,9 @@ class PgBackend {
371
367
  workflowParkDecisions(records) { return (this.parkDecisionsInst = decisionStoreBoundOnce(this.parkDecisionsInst, records, () => new SqlWorkflowParkDecisionStore(this.run(), sqlRecordFenceOf(records)))); }
372
368
  parkDecisionsInst;
373
369
  sharedMemoryStore(authorizer) { return new PgSharedMemoryStore(this.pool, authorizer); }
374
- checkpoint(logger) { return new PgCheckpointStore(this.pool, logger, { retention: sqlStoreRetention(this.config.retentionSweep) }); }
370
+ cardScopeRootSeat = createCardScopeRootSeat();
371
+ bindCardScopeRoot(read) { this.cardScopeRootSeat.bind(read); }
372
+ checkpoint(logger) { return new PgCheckpointStore(this.pool, logger, { retention: sqlStoreRetention(this.config.retentionSweep), cardScopeRoot: this.cardScopeRootSeat }); }
375
373
  checkpointReadView() { return this.checkpoint(); }
376
374
  toolResult() { return new PgToolResultStore(this.pool, { retention: sqlStoreRetention(this.config.retentionSweep) }); }
377
375
  imageIndex() { return new PgImageIndex(this.pool); }
@@ -391,13 +389,13 @@ class PgBackend {
391
389
  async dbNowMs() { const res = await this.pool.query("SELECT ROUND(EXTRACT(EPOCH FROM now())*1000) AS ms"); return Number(res.rows[0]?.ms); }
392
390
  }
393
391
  sealStoreClass(PgBackend, "pg-backend", {
394
- ensureSchema: "boot", close: "boot", run: "exit", resumeAnchor: "exit", approvalExemption: "exit", approvalNonce: "exit", orgDeliveryQuota: "exit",
392
+ ensureSchema: "boot", close: "boot", sessionBlobNamespace: "exit", run: "exit", resumeAnchor: "exit", approvalExemption: "exit", approvalNonce: "exit", orgDeliveryQuota: "exit",
395
393
  approvalAsk: "exit", permissionRule: "exit", sendFileLedger: "exit", userInputLedger: "exit", sessionPolicy: "exit", fileHistory: "exit",
396
394
  memoryOptOutGrant: "exit", sessionCaptureRecords: "exit", legacySnapshotBlobSweep: "exit", runOneTimeMigrations: "boot", workflowJournal: "exit",
397
395
  outcomeSink: "exit", workflowRun: "exit", completionInbox: "exit", notifyJournal: "exit", workflowAgentSessionIndex: "exit", workflowParkDecisions: "exit",
398
396
  sharedMemoryStore: "exit", checkpoint: "exit", checkpointReadView: "exit", toolResult: "exit", imageIndex: "exit", imageBake: "exit", leaderRun: "exit",
399
397
  device: "exit", retention: "exit", sessionEraser: "exit", retentionLane: "exit", breaker: "exit", costQuota: "exit", rateLimiter: "exit", session: "exit",
400
- adoptionLog: "exit", mysqlPool: "boot", pgPool: "boot", dbNowMs: "exit", landedAttendants: "exit",
398
+ adoptionLog: "exit", mysqlPool: "boot", pgPool: "boot", dbNowMs: "exit", landedAttendants: "exit", bindCardScopeRoot: "exit",
401
399
  });
402
400
  export function localSessionRowsOf(backend) {
403
401
  return backend instanceof LocalBackend ? backend.session() : undefined;
@@ -568,7 +566,12 @@ class LocalBackend {
568
566
  ...importedLayerRoot,
569
567
  };
570
568
  }
569
+ closing = false;
570
+ exclusiveDataRoot() {
571
+ return this.closing ? undefined : this.fileBackend.root;
572
+ }
571
573
  async close() {
574
+ this.closing = true;
572
575
  await this.sessionStore.dispose();
573
576
  this.runStore.dispose();
574
577
  this.resumeAnchorStore.dispose();
@@ -612,8 +615,11 @@ class LocalBackend {
612
615
  durability: this.storeDeclaration.durability,
613
616
  getActiveTaskId: (sessionId) => this.runStore.getActiveTaskId(sessionId),
614
617
  readSessionOwner: async (sessionId) => (await this.sessionStore.headOf(sessionId))?.owner,
618
+ cardScopeRoot: this.cardScopeRootSeat,
615
619
  }));
616
620
  }
621
+ cardScopeRootSeat = createCardScopeRootSeat();
622
+ bindCardScopeRoot(read) { this.cardScopeRootSeat.bind(read); }
617
623
  checkpoint() {
618
624
  const cs = this.checkpointStoreOf();
619
625
  if (!this.checkpointProbeWired) {
@@ -709,6 +715,9 @@ class RefusedLocalBackend {
709
715
  async close() {
710
716
  await this.release();
711
717
  }
718
+ exclusiveDataRoot() {
719
+ return undefined;
720
+ }
712
721
  run() { return this.refused(); }
713
722
  resumeAnchor() { return this.refused(); }
714
723
  approvalExemption() { return this.refused(); }
@@ -732,6 +741,7 @@ class RefusedLocalBackend {
732
741
  fileUsageWindowStore() { return this.refused(); }
733
742
  localStoreDeclaration() { return this.refused(); }
734
743
  bindWorkflowRunReader() { this.refused(); }
744
+ bindCardScopeRoot() { this.refused(); }
735
745
  mysqlPool() { return undefined; }
736
746
  pgPool() { return undefined; }
737
747
  }
@@ -802,6 +812,16 @@ export async function openStoreBackendWithFallback(config, logger, observers, lo
802
812
  sqlEngine === undefined
803
813
  ? await attempt()
804
814
  : await openWithinWait(attempt, config.storeLockWaitMs, (w) => logger.warn("store_sql_open_wait", { engine: sqlEngine, endpoint: buildDisplaySafeUrl(sqlStoreCoordinatesAsUrl(config)), waitMs: w.waitMs, error: w.error }));
815
+ const namespace = backend?.sessionBlobNamespace?.();
816
+ if (namespace !== undefined) {
817
+ try {
818
+ await namespace.establish();
819
+ }
820
+ catch (refusal) {
821
+ await backend?.close().catch(() => undefined);
822
+ throw refusal;
823
+ }
824
+ }
805
825
  }
806
826
  catch (err) {
807
827
  void inFlight?.close().catch(() => undefined);
@@ -859,13 +879,15 @@ sealStoreClass(SqlCheckpointStore, "sql-checkpoint-store", {
859
879
  reap: "exit", reapExpired: "exit", listExpiredApprovalGates: "exit", listPending: "exit", listByScope: "exit", listPendingSessionIds: "exit",
860
880
  findPendingTokenBySession: "exit", findCheckpointCandidatesForAsk: "exit", findDecidedApprovalsForBinding: "exit", peekScopeByToken: "exit",
861
881
  peekPendingScope: "exit", hostSessionOfCard: "exit", putCtx: "exit", getCtx: "exit", rewriteCtxWhileClaimed: "exit", rewriteCtxRows: "exit", reapCtx: "exit",
862
- deleteBySession: "exit",
882
+ deleteBySession: "exit", cardScopeRootOf: "exit",
863
883
  });
864
884
  sealStoreClass(SqlFileHistoryStore, "sql-file-history-store", {
865
885
  trackEdit: "exit", annulTrack: "exit", snapshot: "exit", restore: "exit", canRestore: "exit", reap: "exit", deleteBySession: "exit", sweepOrphanBlobs: "exit",
866
886
  adoptScope: "exit", exportHistory: "exit", importHistory: "exit", importFailureOf: "exit", prepareImport: "exit",
867
887
  lockScopeOn: "in-store", publishImportOn: "in-store", deleteScopeOn: "in-store",
868
888
  });
889
+ sealStoreClass(SqlBlobExchange, "sql-blob-exchange", { put: "exit", get: "exit", has: "exit" });
890
+ sealStoreClass(SqlBlobLanding, "sql-blob-landing", { putAt: "exit", visibleAt: "exit" });
869
891
  sealStoreClass(TiDBMailboxStore, "tidb-mailbox-store", {
870
892
  append: "exit", claimLease: "exit", ack: "exit", moveDecision: "exit", releaseLease: "exit", peekCount: "exit", drop: "exit", reap: "exit",
871
893
  retireRecipient: "exit", listRetiredRecipients: "exit",
@@ -83,7 +83,8 @@ export interface TaskAttachmentStore {
83
83
  deleteBySession(sessionId: string): Promise<number>;
84
84
  /** TTL 收割:未绑定且 created_at_ms < olderThanMs 的行。返回删除数。 */
85
85
  reapUnbound(olderThanMs: number): Promise<number>;
86
- /** D-1 孤儿**对象**彻底 GC(clay 拍 2026-07-28):列举对象存储 × meta 行对账,零行且过 grace 的对象删。
86
+ /** D-1 孤儿**对象**彻底 GC(clay 拍 2026-07-28):列举对象存储 × meta 行对账,零行且过 grace 的对象删。只看本域写得出的名(内容地址);
87
+ * 「零行」只对本库成立 ⇒ 命名空间必须只属本库(字节口的删除等命名空间归属证明,`session-blob-namespace.ts`)。
87
88
  * 可选——local 店(meta+bytes 同盘同步删)无此孤儿类。 */
88
89
  sweepOrphanObjects?(graceMs: number, nowMs?: number): Promise<number>;
89
90
  }