@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
@@ -1,4 +1,14 @@
1
+ import { type ObjectStoreOp } from "../object-store-buckets.js";
2
+ /** The signed URL's lifetime under row `op` — the ONE reading of the table's TTL column. A row with no `knob` has a fixed TTL: the table's number, and a
3
+ * call that brings one is refused (its number would be a second source). A knob row takes `requested` (the configured value the call brings) clamped to
4
+ * the row's `maxSec` (the row's policy cap) — a call that brings none, or a value that is not a positive number of seconds (`0` is no lifetime at all),
5
+ * takes the row's `defaultSec` — the reading the `SESSION_SNAPSHOT_TTL_SEC` loader gives its `0` (any other non-positive spelling no longer loads: the configuration is refused) — and a row with no default needs a positive one.
6
+ * Every row, one answer for the SigV4 ceiling ({@link SIGV4_PRESIGN_MAX_SEC}): a lifetime over it is refused, never clamped (the knobs are refused above it
7
+ * when the configuration loads, so a call that gets here is a defect). */
8
+ export declare function rowTtlSec(op: ObjectStoreOp, requested: number | undefined): number;
1
9
  export interface S3PresignParams {
10
+ /** The object-store action table row this request is (its action must be `method`). */
11
+ op: ObjectStoreOp;
2
12
  /** Base endpoint, e.g. `http://minio.minio.svc.cluster.local:9000` (the host the SANDBOX will reach). */
3
13
  endpoint: string;
4
14
  bucket: string;
@@ -8,6 +18,7 @@ export interface S3PresignParams {
8
18
  accessKey: string;
9
19
  secretKey: string;
10
20
  region?: string;
21
+ /** The configured TTL (seconds) of a knob row; a fixed-TTL row takes none (see {@link rowTtlSec}). */
11
22
  expiresSec?: number;
12
23
  /** Override the signing clock (tests). Defaults to now. */
13
24
  now?: Date;
@@ -33,6 +44,8 @@ export declare function presignS3BucketUrl(p: S3BucketPresignParams): string;
33
44
  /** ListObjectsV2 presign(D-1 孤儿对象 GC,clay 拍 2026-07-28):GET `/<bucket>/?list-type=2&prefix=…`。
34
45
  * bucket 级 GET 是**列举能力**——只给 server 端 sweep 用,URL 绝不外发(与 copy 面同纪律)。 */
35
46
  export declare function presignS3ListUrl(p: {
47
+ /** The object-store action table row this listing is (a LIST row). */
48
+ op: ObjectStoreOp;
36
49
  endpoint: string;
37
50
  bucket: string;
38
51
  prefix: string;
@@ -45,6 +58,8 @@ export declare function presignS3ListUrl(p: {
45
58
  now?: Date;
46
59
  }): string;
47
60
  export interface S3CopyPresignParams {
61
+ /** The object-store action table row this copy is (a COPY row). */
62
+ op: ObjectStoreOp;
48
63
  /** INTERNAL endpoint — CopyObject is a SERVER-side request with adapter creds; never hand this URL out. */
49
64
  endpoint: string;
50
65
  /** Destination coordinates (the PUT target). */
@@ -69,4 +84,12 @@ export declare function presignS3CopyUrl(p: S3CopyPresignParams): {
69
84
  url: string;
70
85
  headers: Record<string, string>;
71
86
  };
87
+ /** The URL of an object on an anonymous-read bucket (no signature, no expiry): `<endpoint>/<bucket>/<key>`, each key segment URI-encoded. `op` names
88
+ * the table's anonymous GET row; any other row is refused. */
89
+ export declare function buildPublicObjectUrl(p: {
90
+ op: ObjectStoreOp;
91
+ endpoint: string;
92
+ bucket: string;
93
+ key: string;
94
+ }): string;
72
95
  //# sourceMappingURL=s3-presign.d.ts.map
@@ -1,4 +1,30 @@
1
1
  import { createHash, createHmac } from "node:crypto";
2
+ import { SIGV4_PRESIGN_MAX_SEC, objectStoreActionOf } from "../object-store-buckets.js";
3
+ function assertRow(op, verb, signing) {
4
+ const row = objectStoreActionOf(op);
5
+ if (row.action !== verb)
6
+ throw new Error(`object-store seat: ${op} is a ${row.action} row — refusing to build a ${verb} request under it`);
7
+ if ((row.ttl === "unsigned") !== (signing === "anonymous")) {
8
+ throw new Error(`object-store seat: ${op} is ${row.ttl === "unsigned" ? "an anonymous" : "a signed"} row — refusing to build ${signing === "anonymous" ? "an anonymous" : "a signed"} URL under it`);
9
+ }
10
+ }
11
+ export function rowTtlSec(op, requested) {
12
+ const ttl = objectStoreActionOf(op).ttl;
13
+ if (ttl === "unsigned")
14
+ throw new Error(`object-store seat: ${op} is unsigned — it has no URL lifetime`);
15
+ if (ttl.knob === undefined) {
16
+ if (requested !== undefined)
17
+ throw new Error(`object-store seat: ${op} has a fixed TTL (${ttl.defaultSec} s, the table's) — refusing a call-site TTL of ${requested} s`);
18
+ return ttl.defaultSec;
19
+ }
20
+ const sec = requested !== undefined && requested > 0 ? requested : ttl.defaultSec;
21
+ if (sec === undefined)
22
+ throw new Error(`object-store seat: ${op} takes its TTL from ${ttl.knob} or its call — none was given`);
23
+ if (sec > SIGV4_PRESIGN_MAX_SEC) {
24
+ throw new Error(`object-store seat: ${op} was asked for a ${sec} s URL — a SigV4 presigned URL lives at most ${SIGV4_PRESIGN_MAX_SEC} s (7 days); ${ttl.knob} is refused above that when the configuration loads`);
25
+ }
26
+ return ttl.maxSec !== undefined ? Math.min(sec, ttl.maxSec) : sec;
27
+ }
2
28
  function uriEncode(s, encodeSlash) {
3
29
  let out = "";
4
30
  for (const ch of Buffer.from(s, "utf8")) {
@@ -57,18 +83,21 @@ function presignCanonical(p) {
57
83
  return `${url.origin}${canonicalUri}?${canonicalQuery}&X-Amz-Signature=${signature}`;
58
84
  }
59
85
  export function presignS3Url(p) {
86
+ assertRow(p.op, p.method, "signed");
87
+ const expiresSec = rowTtlSec(p.op, p.expiresSec);
60
88
  if (p.key === "") {
61
89
  throw new Error("presignS3Url: refusing to sign an empty key (single-object contract) — use presignS3BucketUrl for bucket-level operations");
62
90
  }
63
91
  if (p.key.startsWith("/") || p.key.split("/").some((seg) => seg === "." || seg === "..")) {
64
92
  throw new Error(`presignS3Url: refusing to sign a key with a traversal segment: ${p.key}`);
65
93
  }
66
- return presignCanonical({ ...p, canonicalUri: `/${uriEncode(p.bucket, true)}/${uriEncode(p.key, false)}` });
94
+ return presignCanonical({ ...p, expiresSec, canonicalUri: `/${uriEncode(p.bucket, true)}/${uriEncode(p.key, false)}` });
67
95
  }
68
96
  export function presignS3BucketUrl(p) {
69
97
  return presignCanonical({ ...p, canonicalUri: `/${uriEncode(p.bucket, true)}/` });
70
98
  }
71
99
  export function presignS3ListUrl(p) {
100
+ assertRow(p.op, "LIST", "signed");
72
101
  return presignCanonical({
73
102
  endpoint: p.endpoint,
74
103
  canonicalUri: `/${uriEncode(p.bucket, true)}/`,
@@ -76,7 +105,7 @@ export function presignS3ListUrl(p) {
76
105
  accessKey: p.accessKey,
77
106
  secretKey: p.secretKey,
78
107
  ...(p.region ? { region: p.region } : {}),
79
- ...(p.expiresSec ? { expiresSec: p.expiresSec } : {}),
108
+ expiresSec: rowTtlSec(p.op, p.expiresSec),
80
109
  ...(p.now ? { now: p.now } : {}),
81
110
  extraQuery: {
82
111
  "list-type": "2",
@@ -87,6 +116,7 @@ export function presignS3ListUrl(p) {
87
116
  });
88
117
  }
89
118
  export function presignS3CopyUrl(p) {
119
+ assertRow(p.op, "COPY", "signed");
90
120
  for (const [label, key] of [
91
121
  ["destination", p.key],
92
122
  ["source", p.sourceKey],
@@ -105,10 +135,14 @@ export function presignS3CopyUrl(p) {
105
135
  accessKey: p.accessKey,
106
136
  secretKey: p.secretKey,
107
137
  ...(p.region !== undefined ? { region: p.region } : {}),
108
- ...(p.expiresSec !== undefined ? { expiresSec: p.expiresSec } : {}),
138
+ expiresSec: rowTtlSec(p.op, p.expiresSec),
109
139
  ...(p.now !== undefined ? { now: p.now } : {}),
110
140
  signedHeaders: { "x-amz-copy-source": copySource },
111
141
  });
112
142
  return { url, headers: { "x-amz-copy-source": copySource } };
113
143
  }
144
+ export function buildPublicObjectUrl(p) {
145
+ assertRow(p.op, "GET", "anonymous");
146
+ return `${p.endpoint.replace(/\/+$/, "")}/${p.bucket}/${p.key.split("/").map(encodeURIComponent).join("/")}`;
147
+ }
114
148
  //# sourceMappingURL=s3-presign.js.map
@@ -47,8 +47,8 @@ export interface SchedulerStoreLog {
47
47
  * 的 `record` 可能是任意内容 —— 只报下标):
48
48
  * · `unreadable-file` ⇒ warn(整份文件不是 JSON 数组:读成零条,写者一律不写 —— 见 `mutateSchedulerStore`);
49
49
  * · `malformed-record` ⇒ warn(这一条读不懂、被跳过;下一个写者落盘时它就没了);
50
- * · `stale-session-record` ⇒ info(会话链上过期 ≥ 7 天的记录,读时丢、下一个写者落盘);
51
- * · `one-time-migration` ⇒ info(SS-M3 的读时改写收据:durable 记录换上新键,下一个写者落盘)。
50
+ * · `stale-session-record` ⇒ info(会话链上过期 ≥ 7 天的记录,读时丢、下一个写者落盘)。
51
+ * (settings-schema 26.0.0 删了调度店的读时重铸一步与它的收据臂:仍按会话链键存的 durable 记录逐字读出、不告警 —— 没有日志可写。)
52
52
  */
53
53
  export declare function logSchedulerWarnings(log: SchedulerStoreLog, path: string, warnings: readonly SchedulerParseWarning[]): void;
54
54
  /**
@@ -75,9 +75,6 @@ export function logSchedulerWarnings(log, path, warnings) {
75
75
  case "stale-session-record":
76
76
  log.info("scheduler_store_record_stale", { path, index: w.index, id: w.id, note: "a session-chained record whose next run is ≥ 7 days past — dropped on read (the next write persists the drop)" });
77
77
  break;
78
- case "one-time-migration":
79
- log.info("scheduler_store_record_migrated", { path, index: w.index, migration: w.receipt.migration, appliedAs: w.receipt.appliedAs, note: "settings-schema one-time migration rewrote this record on read (the next write persists it)" });
80
- break;
81
78
  default: {
82
79
  const unhandled = w;
83
80
  throw new Error(`scheduler store: unknown read warning ${JSON.stringify(unhandled)}`);
@@ -20,6 +20,8 @@ export const CENTRAL_SCHEMA_INDEXES = [
20
20
  { table: "file_history_boundary_file", name: "idx_file_history_bf_key", columns: ["scope", "key_hash"] },
21
21
  { table: "file_history_boundary", name: "uq_file_history_boundary_seq", columns: ["scope", "publish_seq"], unique: true },
22
22
  { table: "file_history_blob", name: "idx_file_history_blob_created", columns: ["created_at"] },
23
+ { table: "file_history_blob_grant", name: "idx_file_history_blob_grant_granted", columns: ["granted_at"] },
24
+ { table: "file_history_blob_grant", name: "idx_file_history_blob_grant_blob", columns: ["blob_hash"] },
23
25
  { table: "workflow_run", name: "idx_wfrun_scope_created", columns: ["scope", "created_at_ms"] },
24
26
  { table: "workflow_run", name: "idx_wfrun_scope_status", columns: ["scope", "status"] },
25
27
  { table: "workflow_agent_session", name: "idx_wfagentsess_run", columns: ["run_id"] },
@@ -55,8 +55,8 @@ export declare class FileSendFileLedger implements SendFileLedger {
55
55
  }
56
56
  export interface LedgerRecordingDeps {
57
57
  ledger: SendFileLedger;
58
- /** Best-effort reclaim of the uploaded object when the record fails (issuer.deleteObject). */
59
- deleteObject: (bucket: string, key: string) => Promise<void>;
58
+ /** Best-effort reclaim of the uploaded object when the record fails (issuer.deleteObject; `track` = the link's track). */
59
+ deleteObject: (bucket: string, key: string, track: IssuedFileLink["track"]) => Promise<void>;
60
60
  warn?: (msg: string, meta?: Record<string, unknown>) => void;
61
61
  now?: () => number;
62
62
  newId?: () => string;
@@ -130,10 +130,10 @@ export function withLedgerRecording(send, deps) {
130
130
  let reclaimed = true;
131
131
  try {
132
132
  try {
133
- await deps.deleteObject(link.bucket, link.key);
133
+ await deps.deleteObject(link.bucket, link.key, link.track);
134
134
  }
135
135
  catch {
136
- await deps.deleteObject(link.bucket, link.key);
136
+ await deps.deleteObject(link.bucket, link.key, link.track);
137
137
  }
138
138
  }
139
139
  catch (de) {
@@ -1,5 +1,3 @@
1
- /** SigV4 physical ceiling (AWS: X-Amz-Expires max 604800s = 7 days). */
2
- export declare const PRESIGN_MAX_TTL_SEC = 604800;
3
1
  /**
4
2
  * Multi-tenant governance (clay ruling 2026-07-14, blackboard [ref]② variant a with a HASHED segment): every
5
3
  * object key carries a per-tenant SCOPE SEGMENT so tenants can be listed/revoked by prefix on the store itself,
@@ -102,8 +100,9 @@ export interface SendUserFileIssuer {
102
100
  * semantics as issue() — prepare and finalize share ONE key, so the segment is fixed at prepare time. */
103
101
  prepareDirectUpload(filename: string | undefined, ttlSec?: number, scope?: string): PreparedDirectUpload;
104
102
  /** Best-effort object reclaim (ledger fail-loud path: an uploaded object whose ledger record failed would be
105
- * an unmanageable orphan — delete it). Signed DELETE against the INTERNAL endpoint with adapter creds. */
106
- deleteObject(bucket: string, key: string): Promise<void>;
103
+ * an unmanageable orphan — delete it). Signed DELETE against the INTERNAL endpoint with adapter creds. `track` = the link's track
104
+ * (which object-store action row the delete is: the permanent track's bucket or the private one). */
105
+ deleteObject(bucket: string, key: string, track: IssuedFileLink["track"]): Promise<void>;
107
106
  /** The public endpoint web clients need for rendering context (bootstrap/capabilities透出). */
108
107
  readonly publicEndpoint: string | undefined;
109
108
  }
@@ -1,10 +1,10 @@
1
1
  import { createHmac } from "node:crypto";
2
2
  import { uuidv7 } from "@sema-agent/core";
3
3
  import { stripControlAndBidi } from "@sema-agent/settings-schema";
4
- import { presignS3CopyUrl, presignS3Url } from "./s3-presign.js";
4
+ import { buildPublicObjectUrl, presignS3CopyUrl, presignS3Url, rowTtlSec } from "./s3-presign.js";
5
+ import { SENDFILE_STAGING_SEGMENT } from "../object-store-buckets.js";
5
6
  import { OBJECT_STORE_IO_TIMEOUT_MS } from "./blob-backend.js";
6
7
  import { createEgressFetch } from "../egress-redirect.js";
7
- export const PRESIGN_MAX_TTL_SEC = 604_800;
8
8
  export function scopeSegment(secretKey, scope) {
9
9
  if (scope === undefined || scope === "")
10
10
  return "_";
@@ -30,23 +30,27 @@ export function sanitizeSendFileName(name) {
30
30
  export function createSendUserFileIssuer(cfg, deps) {
31
31
  const f = createEgressFetch("declare the final object-store address as MINIO_ENDPOINT / S3_ENDPOINT", deps?.fetchImpl);
32
32
  const bounded = () => ({ signal: AbortSignal.timeout(OBJECT_STORE_IO_TIMEOUT_MS) });
33
- const upload = deps?.upload ??
34
- (async (bucket, key, bytes) => {
35
- const url = presignS3Url({
36
- endpoint: cfg.endpoint,
37
- bucket,
38
- key,
39
- method: "PUT",
40
- accessKey: cfg.accessKey,
41
- secretKey: cfg.secretKey,
42
- ...(cfg.region !== undefined ? { region: cfg.region } : {}),
43
- expiresSec: 600,
44
- ...(deps?.now !== undefined ? { now: deps.now() } : {}),
45
- });
46
- const res = await f(url, { method: "PUT", body: Buffer.from(bytes), ...bounded() });
47
- if (!res.ok)
48
- throw new Error(`SendUserFile upload failed: ${res.status} ${res.statusText} (bucket=${bucket})`);
33
+ const trackOps = (track) => track === "public"
34
+ ? { put: "sendfile/public:PUT", copy: "sendfile/public:COPY", head: "sendfile/public:HEAD", delete: "sendfile/public:DELETE" }
35
+ : { put: "sendfile/private:PUT", copy: "sendfile/private:COPY", head: "sendfile/private:HEAD", delete: "sendfile/private:DELETE" };
36
+ const upload = async (op, bucket, key, bytes) => {
37
+ if (deps?.upload)
38
+ return deps.upload(bucket, key, bytes);
39
+ const url = presignS3Url({
40
+ op,
41
+ endpoint: cfg.endpoint,
42
+ bucket,
43
+ key,
44
+ method: "PUT",
45
+ accessKey: cfg.accessKey,
46
+ secretKey: cfg.secretKey,
47
+ ...(cfg.region !== undefined ? { region: cfg.region } : {}),
48
+ ...(deps?.now !== undefined ? { now: deps.now() } : {}),
49
49
  });
50
+ const res = await f(url, { method: "PUT", body: Buffer.from(bytes), ...bounded() });
51
+ if (!res.ok)
52
+ throw new Error(`SendUserFile upload failed: ${res.status} ${res.statusText} (bucket=${bucket})`);
53
+ };
50
54
  const issue = async (bytes, filename, ttlSec, scope) => {
51
55
  if (cfg.publicEndpoint === undefined) {
52
56
  throw new Error("SendUserFile is not configured for public links: set S3_PUBLIC_ENDPOINT (the S3/MinIO base URL users can reach; falls back to S3_ENDPOINT when that is already public)");
@@ -60,16 +64,15 @@ export function createSendUserFileIssuer(cfg, deps) {
60
64
  const scopeField = scope !== undefined && scope !== "" ? { scope } : {};
61
65
  if (ttl === 0) {
62
66
  const key = `${seg}/${uuidv7()}/${name}`;
63
- await upload(cfg.publicBucket, key, bytes);
64
- const url = `${publicBase}/${cfg.publicBucket}/${key.split("/").map(encodeURIComponent).join("/")}`;
67
+ await upload(trackOps("public").put, cfg.publicBucket, key, bytes);
68
+ const url = buildPublicObjectUrl({ op: "sendfile/public:GET", endpoint: publicBase, bucket: cfg.publicBucket, key });
65
69
  return { url, filename: name, size: bytes.byteLength, ttlSec: 0, track: "public", bucket: cfg.publicBucket, key, ...scopeField };
66
70
  }
67
- if (ttl > PRESIGN_MAX_TTL_SEC) {
68
- throw new Error(`SendUserFile ttl ${ttl}s exceeds the SigV4 presign ceiling (${PRESIGN_MAX_TTL_SEC}s = 7 days) — use ttl=0 for a permanent link`);
69
- }
71
+ const expiresSec = rowTtlSec("sendfile/private:GET", ttl);
70
72
  const key = `${cfg.privateKeyPrefix}${seg}/${uuidv7()}/${name}`;
71
- await upload(cfg.privateBucket, key, bytes);
73
+ await upload(trackOps("presigned").put, cfg.privateBucket, key, bytes);
72
74
  const url = presignS3Url({
75
+ op: "sendfile/private:GET",
73
76
  endpoint: publicBase,
74
77
  bucket: cfg.privateBucket,
75
78
  key,
@@ -77,10 +80,10 @@ export function createSendUserFileIssuer(cfg, deps) {
77
80
  accessKey: cfg.accessKey,
78
81
  secretKey: cfg.secretKey,
79
82
  ...(cfg.region !== undefined ? { region: cfg.region } : {}),
80
- expiresSec: ttl,
83
+ expiresSec,
81
84
  ...(deps?.now !== undefined ? { now: deps.now() } : {}),
82
85
  });
83
- return { url, filename: name, size: bytes.byteLength, ttlSec: ttl, track: "presigned", bucket: cfg.privateBucket, key, ...scopeField };
86
+ return { url, filename: name, size: bytes.byteLength, ttlSec: expiresSec, track: "presigned", bucket: cfg.privateBucket, key, ...scopeField };
84
87
  };
85
88
  const prepareDirectUpload = (filename, ttlSec, scope) => {
86
89
  if (cfg.publicEndpoint === undefined) {
@@ -89,19 +92,20 @@ export function createSendUserFileIssuer(cfg, deps) {
89
92
  const ttl = ttlSec ?? cfg.defaultTtlSec;
90
93
  if (!Number.isInteger(ttl) || ttl < 0)
91
94
  throw new Error(`SendUserFile ttl must be a non-negative INTEGER of seconds (0 = permanent; SigV4 X-Amz-Expires is integer-only); got ${ttl}`);
92
- if (ttl > PRESIGN_MAX_TTL_SEC) {
93
- throw new Error(`SendUserFile ttl ${ttl}s exceeds the SigV4 presign ceiling (${PRESIGN_MAX_TTL_SEC}s = 7 days) — use ttl=0 for a permanent link`);
94
- }
95
+ if (ttl > 0)
96
+ rowTtlSec("sendfile/private:GET", ttl);
95
97
  const name = sanitizeSendFileName(filename);
96
98
  const publicBase = cfg.publicEndpoint.replace(/\/+$/, "");
97
99
  const putBase = (cfg.sandboxPutEndpoint ?? cfg.publicEndpoint).replace(/\/+$/, "");
98
100
  const bucket = ttl === 0 ? cfg.publicBucket : cfg.privateBucket;
101
+ const finalOps = trackOps(ttl === 0 ? "public" : "presigned");
99
102
  const seg = scopeSegment(cfg.secretKey, scope);
100
103
  const scopeField = scope !== undefined && scope !== "" ? { scope } : {};
101
104
  const key = ttl === 0 ? `${seg}/${uuidv7()}/${name}` : `${cfg.privateKeyPrefix}${seg}/${uuidv7()}/${name}`;
102
105
  const stagingBucket = cfg.privateBucket;
103
- const stagingKey = `${cfg.privateKeyPrefix}staging/${seg}/${uuidv7()}`;
106
+ const stagingKey = `${cfg.privateKeyPrefix}${SENDFILE_STAGING_SEGMENT}${seg}/${uuidv7()}`;
104
107
  const putUrl = presignS3Url({
108
+ op: "sendfile/staging:PUT",
105
109
  endpoint: putBase,
106
110
  bucket: stagingBucket,
107
111
  key: stagingKey,
@@ -109,13 +113,13 @@ export function createSendUserFileIssuer(cfg, deps) {
109
113
  accessKey: cfg.accessKey,
110
114
  secretKey: cfg.secretKey,
111
115
  ...(cfg.region !== undefined ? { region: cfg.region } : {}),
112
- expiresSec: 120,
113
116
  ...(deps?.now !== undefined ? { now: deps.now() } : {}),
114
117
  });
115
118
  const verifySize = async (maxBytes) => {
116
119
  if (deps?.verify)
117
120
  return deps.verify(bucket, key, maxBytes);
118
- const sign = (method, b, k) => presignS3Url({
121
+ const sign = (op, method, b, k) => presignS3Url({
122
+ op,
119
123
  endpoint: cfg.endpoint,
120
124
  bucket: b,
121
125
  key: k,
@@ -123,11 +127,10 @@ export function createSendUserFileIssuer(cfg, deps) {
123
127
  accessKey: cfg.accessKey,
124
128
  secretKey: cfg.secretKey,
125
129
  ...(cfg.region !== undefined ? { region: cfg.region } : {}),
126
- expiresSec: 60,
127
130
  });
128
- const reclaim = async (b, k) => {
131
+ const reclaim = async (op, b, k) => {
129
132
  try {
130
- const r = await f(sign("DELETE", b, k), { method: "DELETE", ...bounded() });
133
+ const r = await f(sign(op, "DELETE", b, k), { method: "DELETE", ...bounded() });
131
134
  return r.ok || r.status === 404;
132
135
  }
133
136
  catch {
@@ -137,28 +140,29 @@ export function createSendUserFileIssuer(cfg, deps) {
137
140
  const outcome = (ok, what) => (ok ? `${what} reclaimed` : `${what} reclaim FAILED (orphan; operator cleanup needed)`);
138
141
  let head;
139
142
  try {
140
- head = await f(sign("HEAD", stagingBucket, stagingKey), { method: "HEAD", ...bounded() });
143
+ head = await f(sign("sendfile/staging:HEAD", "HEAD", stagingBucket, stagingKey), { method: "HEAD", ...bounded() });
141
144
  }
142
145
  catch (e) {
143
- const r = await reclaim(stagingBucket, stagingKey);
146
+ const r = await reclaim("sendfile/staging:DELETE", stagingBucket, stagingKey);
144
147
  throw new Error(`SendUserFile upload verification failed: store unreachable during HEAD (${e instanceof Error ? e.message : String(e)}) — ${outcome(r, "staged object")}`);
145
148
  }
146
149
  if (head.status === 404)
147
150
  throw new Error("SendUserFile upload verification failed: object not found after upload (HEAD 404)");
148
151
  if (!head.ok) {
149
- const r = await reclaim(stagingBucket, stagingKey);
152
+ const r = await reclaim("sendfile/staging:DELETE", stagingBucket, stagingKey);
150
153
  throw new Error(`SendUserFile upload verification failed: store error (HEAD ${head.status}) — ${outcome(r, "staged object")}`);
151
154
  }
152
155
  const staged = Number.parseInt(head.headers.get("content-length") ?? "", 10);
153
156
  if (!Number.isFinite(staged) || staged < 0) {
154
- const r = await reclaim(stagingBucket, stagingKey);
157
+ const r = await reclaim("sendfile/staging:DELETE", stagingBucket, stagingKey);
155
158
  throw new Error(`SendUserFile upload verification failed: store returned no content-length — ${outcome(r, "staged object")}`);
156
159
  }
157
160
  if (staged > maxBytes) {
158
- await reclaim(stagingBucket, stagingKey);
161
+ await reclaim("sendfile/staging:DELETE", stagingBucket, stagingKey);
159
162
  throw new Error(`file exceeds the ${maxBytes >= 1024 * 1024 ? `${Math.floor(maxBytes / (1024 * 1024))} MiB` : `${maxBytes} byte`} per-file cap (uploaded object was ${staged} bytes — deleted)`);
160
163
  }
161
164
  const copy = presignS3CopyUrl({
165
+ op: finalOps.copy,
162
166
  endpoint: cfg.endpoint,
163
167
  bucket,
164
168
  key,
@@ -167,7 +171,6 @@ export function createSendUserFileIssuer(cfg, deps) {
167
171
  accessKey: cfg.accessKey,
168
172
  secretKey: cfg.secretKey,
169
173
  ...(cfg.region !== undefined ? { region: cfg.region } : {}),
170
- expiresSec: 60,
171
174
  ...(deps?.now !== undefined ? { now: deps.now() } : {}),
172
175
  });
173
176
  try {
@@ -177,40 +180,41 @@ export function createSendUserFileIssuer(cfg, deps) {
177
180
  throw new Error(`copy returned ${res.status}`);
178
181
  }
179
182
  catch (e) {
180
- const r1 = await reclaim(stagingBucket, stagingKey);
181
- const r2 = await reclaim(bucket, key);
183
+ const r1 = await reclaim("sendfile/staging:DELETE", stagingBucket, stagingKey);
184
+ const r2 = await reclaim(finalOps.delete, bucket, key);
182
185
  throw new Error(`SendUserFile upload verification failed: staging promotion failed (${e instanceof Error ? e.message : String(e)}) — ${outcome(r1 && r2, "uploaded object")}`);
183
186
  }
184
- await reclaim(stagingBucket, stagingKey);
187
+ await reclaim("sendfile/staging:DELETE", stagingBucket, stagingKey);
185
188
  let fin;
186
189
  try {
187
- fin = await f(sign("HEAD", bucket, key), { method: "HEAD", ...bounded() });
190
+ fin = await f(sign(finalOps.head, "HEAD", bucket, key), { method: "HEAD", ...bounded() });
188
191
  }
189
192
  catch (e) {
190
- const r = await reclaim(bucket, key);
193
+ const r = await reclaim(finalOps.delete, bucket, key);
191
194
  throw new Error(`SendUserFile upload verification failed: store unreachable during final HEAD (${e instanceof Error ? e.message : String(e)}) — ${outcome(r, "object")}`);
192
195
  }
193
196
  if (!fin.ok) {
194
- const r = await reclaim(bucket, key);
197
+ const r = await reclaim(finalOps.delete, bucket, key);
195
198
  throw new Error(`SendUserFile upload verification failed: final object HEAD ${fin.status} — ${outcome(r, "object")}`);
196
199
  }
197
200
  const size = Number.parseInt(fin.headers.get("content-length") ?? "", 10);
198
201
  if (!Number.isFinite(size) || size < 0) {
199
- const r = await reclaim(bucket, key);
202
+ const r = await reclaim(finalOps.delete, bucket, key);
200
203
  throw new Error(`SendUserFile upload verification failed: store returned no content-length — ${outcome(r, "object")}`);
201
204
  }
202
205
  if (size > maxBytes) {
203
- await reclaim(bucket, key);
206
+ await reclaim(finalOps.delete, bucket, key);
204
207
  throw new Error(`file exceeds the ${maxBytes >= 1024 * 1024 ? `${Math.floor(maxBytes / (1024 * 1024))} MiB` : `${maxBytes} byte`} per-file cap (uploaded object was ${size} bytes — deleted)`);
205
208
  }
206
209
  return size;
207
210
  };
208
211
  const finalize = (size) => {
209
212
  if (ttl === 0) {
210
- const url = `${publicBase}/${bucket}/${key.split("/").map(encodeURIComponent).join("/")}`;
213
+ const url = buildPublicObjectUrl({ op: "sendfile/public:GET", endpoint: publicBase, bucket, key });
211
214
  return { url, filename: name, size, ttlSec: 0, track: "public", bucket, key, ...scopeField };
212
215
  }
213
216
  const url = presignS3Url({
217
+ op: "sendfile/private:GET",
214
218
  endpoint: publicBase,
215
219
  bucket,
216
220
  key,
@@ -225,10 +229,11 @@ export function createSendUserFileIssuer(cfg, deps) {
225
229
  };
226
230
  return { putUrl, verifySize, finalize };
227
231
  };
228
- const deleteObject = async (bucket, key) => {
232
+ const deleteObject = async (bucket, key, track) => {
229
233
  if (deps?.remove)
230
234
  return deps.remove(bucket, key);
231
235
  const url = presignS3Url({
236
+ op: trackOps(track).delete,
232
237
  endpoint: cfg.endpoint,
233
238
  bucket,
234
239
  key,
@@ -236,7 +241,6 @@ export function createSendUserFileIssuer(cfg, deps) {
236
241
  accessKey: cfg.accessKey,
237
242
  secretKey: cfg.secretKey,
238
243
  ...(cfg.region !== undefined ? { region: cfg.region } : {}),
239
- expiresSec: 60,
240
244
  ...(deps?.now !== undefined ? { now: deps.now() } : {}),
241
245
  });
242
246
  const res = await f(url, { method: "DELETE", ...bounded() });
@@ -0,0 +1,15 @@
1
+ /**
2
+ * This database's claim token on the session blob namespace (`session-blob-namespace.ts`): one row, minted once per database (absent ⇒ the first writer's
3
+ * stays; every replica of the database reads the same), never rewritten. The store's open reads it (`StoreBackend.ensureSchema()`) and hands it to the
4
+ * namespace's claim; nothing else reads or writes the table.
5
+ */
6
+ import type { SqlDialect, SqlExec } from "./sql-driver.js";
7
+ /** The table that keeps this database's claim token (one row, `id = 1`). */
8
+ export declare const SESSION_BLOB_NAMESPACE_CLAIM_TABLE = "blob_namespace_claim";
9
+ /** The create statement (both dialects; idempotent). The central schema statements read it (`tidb-pool.ts` / `pg-pool.ts`). */
10
+ export declare function sessionBlobNamespaceClaimDdl(dialect: SqlDialect): string;
11
+ /** This database's claim token: minted if absent, then read (the store's open; a failure is the open's failure). */
12
+ export declare function ensureSessionBlobClaimToken(db: SqlExec & {
13
+ readonly dialect: SqlDialect;
14
+ }): Promise<string>;
15
+ //# sourceMappingURL=session-blob-claim-token.d.ts.map
@@ -0,0 +1,20 @@
1
+ import { randomUUID } from "node:crypto";
2
+ export const SESSION_BLOB_NAMESPACE_CLAIM_TABLE = "blob_namespace_claim";
3
+ export function sessionBlobNamespaceClaimDdl(dialect) {
4
+ return dialect === "mysql"
5
+ ? `CREATE TABLE IF NOT EXISTS ${SESSION_BLOB_NAMESPACE_CLAIM_TABLE} (id SMALLINT NOT NULL PRIMARY KEY, claim_token VARCHAR(64) NOT NULL, minted_at_ms BIGINT NOT NULL) COLLATE utf8mb4_bin`
6
+ : `CREATE TABLE IF NOT EXISTS ${SESSION_BLOB_NAMESPACE_CLAIM_TABLE} (id SMALLINT NOT NULL PRIMARY KEY, claim_token VARCHAR(64) COLLATE "C" NOT NULL, minted_at_ms BIGINT NOT NULL)`;
7
+ }
8
+ const MINT_SQL = {
9
+ mysql: `INSERT INTO ${SESSION_BLOB_NAMESPACE_CLAIM_TABLE} (id, claim_token, minted_at_ms) VALUES (1, ?, ?) ON DUPLICATE KEY UPDATE id = id`,
10
+ pg: `INSERT INTO ${SESSION_BLOB_NAMESPACE_CLAIM_TABLE} (id, claim_token, minted_at_ms) VALUES (1, $1, $2) ON CONFLICT (id) DO NOTHING`,
11
+ };
12
+ export async function ensureSessionBlobClaimToken(db) {
13
+ await db.query(MINT_SQL[db.dialect], [randomUUID(), Date.now()]);
14
+ const { rows } = await db.query(`SELECT claim_token FROM ${SESSION_BLOB_NAMESPACE_CLAIM_TABLE} WHERE id = 1`);
15
+ const token = rows[0]?.claim_token;
16
+ if (typeof token !== "string" || token === "")
17
+ throw new Error(`${SESSION_BLOB_NAMESPACE_CLAIM_TABLE} holds no claim token after the mint`);
18
+ return token;
19
+ }
20
+ //# sourceMappingURL=session-blob-claim-token.js.map
@@ -0,0 +1,46 @@
1
+ import { type SessionBlobStoreConfig } from "./blob-backend.js";
2
+ import { StoreBootRefusalError } from "./store-contracts.js";
3
+ /** After a create-only write, how long to wait before reading the claim back (once per namespace, on its first claim). */
4
+ export declare const SESSION_BLOB_CLAIM_SETTLE_MS = 1000;
5
+ /** One attempt's reading (closed set). */
6
+ export type SessionBlobClaimReading = {
7
+ readonly state: "mine";
8
+ } | {
9
+ readonly state: "foreign";
10
+ readonly holder: string;
11
+ } | {
12
+ readonly state: "unproven";
13
+ readonly detail: string;
14
+ };
15
+ /** The store open's refusal: the namespace is another database's (or the store was opened without binding this database's token). */
16
+ export declare class SessionBlobNamespaceRefusedError extends StoreBootRefusalError {
17
+ constructor(message: string);
18
+ }
19
+ /** The live namespace seat of one database over one bucket × prefix. */
20
+ export interface SessionBlobNamespace {
21
+ /** The bucket config the namespace's ports are built from (`createSessionBlobBackend`). */
22
+ readonly store: SessionBlobStoreConfig;
23
+ /** Bind this database's claim token (`ensureSessionBlobClaimToken`, read by the store's `ensureSchema()`). */
24
+ bind(token: string): void;
25
+ /** One attempt of the claim under `token` (no retry). */
26
+ claimOnce(token: string): Promise<SessionBlobClaimReading>;
27
+ /** Boot, under the bound token, one attempt: `mine` ⇒ resolves; `foreign` ⇒ refuses; `unproven` ⇒ resolves through the registered F-class arm
28
+ * (deletes stay refused until a later reading is `mine`: {@link assertOwned}). */
29
+ establish(): Promise<void>;
30
+ /** Every delete's gate: reads the claim afresh (never remembered) and resolves only when it names this database; rejects otherwise — a namespace with no
31
+ * bound token (the store's `ensureSchema()` never ran) has nothing to prove with. */
32
+ assertOwned(): Promise<void>;
33
+ }
34
+ export interface SessionBlobNamespaceOptions {
35
+ /** Test seam; production = the global fetch (through the egress-redirect rule). */
36
+ readonly fetchImpl?: typeof fetch;
37
+ /** Test seam for the settle wait; production = a timer. */
38
+ readonly sleep?: (ms: number) => Promise<void>;
39
+ }
40
+ /** The refusal when another database holds the namespace (the one sentence; the boot refusal and a refused delete both say it). */
41
+ export declare function buildSessionBlobNamespaceForeignText(store: SessionBlobStoreConfig, holder: string, mine: string): string;
42
+ /** A delete's refusal when the claim could not be read or written (the delete is retried by its sweep / release on its next call, and goes through once
43
+ * the claim reads as this database's). */
44
+ export declare function buildSessionBlobNamespaceUnprovenText(store: SessionBlobStoreConfig, detail: string): string;
45
+ export declare function createSessionBlobNamespace(store: SessionBlobStoreConfig, opts?: SessionBlobNamespaceOptions): SessionBlobNamespace;
46
+ //# sourceMappingURL=session-blob-namespace.d.ts.map