@lostgradient/weft 0.23.0 → 0.24.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 (295) hide show
  1. package/README.md +72 -4
  2. package/dist/cli/codegen-emit-dedup.d.ts +81 -0
  3. package/dist/cli/codegen-emit-dedup.js +45 -0
  4. package/dist/cli/codegen-emit-registry.d.ts +90 -0
  5. package/dist/cli/codegen-emit-registry.js +50 -0
  6. package/dist/cli/codegen-emit.d.ts +26 -39
  7. package/dist/cli/codegen-emit.js +4 -27
  8. package/dist/cli/codegen-validate.d.ts +49 -0
  9. package/dist/cli/codegen-validate.js +186 -0
  10. package/dist/cli/codegen.js +6 -87
  11. package/dist/cli/generated/operation-catalog.snapshot.json +287 -3
  12. package/dist/cli/generated/operation-client.generated.d.ts +324 -10
  13. package/dist/cli/generated/operation-client.generated.js +12 -0
  14. package/dist/cli/output.js +1 -1
  15. package/dist/cli/storage-factory.js +2 -0
  16. package/dist/cli/workflow-commands.js +2 -0
  17. package/dist/cli-main.js +1 -1
  18. package/dist/core/application-payload-digest.d.ts +42 -0
  19. package/dist/core/application-payload-digest.js +76 -0
  20. package/dist/core/application-primitive-abort.d.ts +50 -0
  21. package/dist/core/application-primitive-abort.js +40 -0
  22. package/dist/core/application-primitive-attempt-registry.d.ts +74 -0
  23. package/dist/core/application-primitive-attempt-registry.js +73 -0
  24. package/dist/core/application-primitive-codec.d.ts +36 -0
  25. package/dist/core/application-primitive-codec.js +56 -0
  26. package/dist/core/application-primitive-commit.d.ts +67 -0
  27. package/dist/core/application-primitive-commit.js +66 -0
  28. package/dist/core/application-primitive-guards.d.ts +62 -0
  29. package/dist/core/application-primitive-guards.js +89 -0
  30. package/dist/core/application-primitive-payload.d.ts +29 -0
  31. package/dist/core/application-primitive-payload.js +65 -0
  32. package/dist/core/application-primitive-timing.d.ts +18 -0
  33. package/dist/core/application-primitive-timing.js +22 -0
  34. package/dist/core/atomic-state.js +2 -0
  35. package/dist/core/catalog/codec.d.ts +40 -0
  36. package/dist/core/catalog/codec.js +45 -0
  37. package/dist/core/catalog/errors.d.ts +104 -0
  38. package/dist/core/catalog/errors.js +41 -0
  39. package/dist/core/catalog/index.d.ts +25 -0
  40. package/dist/core/catalog/index.js +15 -0
  41. package/dist/core/catalog/reference-counts.d.ts +105 -0
  42. package/dist/core/catalog/reference-counts.js +28 -0
  43. package/dist/core/catalog/removal.d.ts +52 -0
  44. package/dist/core/catalog/removal.js +14 -0
  45. package/dist/core/catalog/storage-io.d.ts +78 -0
  46. package/dist/core/catalog/storage-io.js +106 -0
  47. package/dist/core/catalog/types.d.ts +108 -0
  48. package/dist/core/catalog/types.js +0 -0
  49. package/dist/core/catalog/workflow-catalog.d.ts +165 -0
  50. package/dist/core/catalog/workflow-catalog.js +198 -0
  51. package/dist/core/compare-codepoint.d.ts +13 -0
  52. package/dist/core/compare-codepoint.js +7 -0
  53. package/dist/core/compression.js +2 -0
  54. package/dist/core/context/attributes.d.ts +1 -1
  55. package/dist/core/context/durable-operations.d.ts +2 -2
  56. package/dist/core/context/index.js +5 -3
  57. package/dist/core/context/state-namespace.d.ts +1 -1
  58. package/dist/core/contract/build.d.ts +64 -0
  59. package/dist/core/contract/build.js +99 -0
  60. package/dist/core/contract/compatibility.d.ts +187 -0
  61. package/dist/core/contract/compatibility.js +37 -0
  62. package/dist/core/contract/failure.d.ts +64 -0
  63. package/dist/core/contract/failure.js +4 -0
  64. package/dist/core/contract/hash.d.ts +92 -0
  65. package/dist/core/contract/hash.js +34 -0
  66. package/dist/core/contract/index.d.ts +21 -0
  67. package/dist/core/contract/index.js +22 -0
  68. package/dist/core/contract/limits.d.ts +74 -0
  69. package/dist/core/contract/limits.js +1 -0
  70. package/dist/core/contract/manifest-parse-schema.d.ts +37 -0
  71. package/dist/core/contract/manifest-parse-schema.js +106 -0
  72. package/dist/core/contract/manifest-parse.d.ts +76 -0
  73. package/dist/core/contract/manifest-parse.js +189 -0
  74. package/dist/core/contract/manifest.d.ts +56 -0
  75. package/dist/core/contract/manifest.js +31 -0
  76. package/dist/core/contract/normalize.d.ts +97 -0
  77. package/dist/core/contract/normalize.js +127 -0
  78. package/dist/core/contract/revision.d.ts +29 -0
  79. package/dist/core/contract/revision.js +5 -0
  80. package/dist/core/contract/types.d.ts +274 -0
  81. package/dist/core/contract/types.js +1 -0
  82. package/dist/core/engine/catalog-activation.d.ts +40 -0
  83. package/dist/core/engine/catalog-activation.js +17 -0
  84. package/dist/core/engine/catalog-events.d.ts +36 -0
  85. package/dist/core/engine/catalog-events.js +15 -0
  86. package/dist/core/engine/catalog-readiness.d.ts +49 -0
  87. package/dist/core/engine/catalog-readiness.js +59 -0
  88. package/dist/core/engine/catalog-removal.d.ts +132 -0
  89. package/dist/core/engine/catalog-removal.js +70 -0
  90. package/dist/core/engine/construction.d.ts +1 -1
  91. package/dist/core/engine/construction.js +4 -2
  92. package/dist/core/engine/disposal.js +9 -0
  93. package/dist/core/engine/engine-workflows-namespace.d.ts +130 -0
  94. package/dist/core/engine/engine-workflows-namespace.js +49 -0
  95. package/dist/core/engine/finalizer-metadata.d.ts +22 -0
  96. package/dist/core/engine/finalizer-metadata.js +12 -0
  97. package/dist/core/engine/handle-result.js +2 -0
  98. package/dist/core/engine/index.d.ts +83 -0
  99. package/dist/core/engine/index.js +61 -0
  100. package/dist/core/engine/internals.d.ts +44 -1
  101. package/dist/core/engine/internals.js +6 -0
  102. package/dist/core/engine/lifecycle/start.js +3 -0
  103. package/dist/core/engine/registration.d.ts +76 -0
  104. package/dist/core/engine/registration.js +12 -6
  105. package/dist/core/engine/source-registration.d.ts +42 -0
  106. package/dist/core/engine/source-registration.js +36 -0
  107. package/dist/core/engine/source-resolution.d.ts +85 -0
  108. package/dist/core/engine/source-resolution.js +119 -0
  109. package/dist/core/engine/validation.js +2 -2
  110. package/dist/core/events/catalog-events.d.ts +117 -0
  111. package/dist/core/events/catalog-events.js +62 -0
  112. package/dist/core/events/event-map.d.ts +6 -0
  113. package/dist/core/events/index.d.ts +1 -0
  114. package/dist/core/events/index.js +1 -0
  115. package/dist/core/mailbox-admission.d.ts +26 -0
  116. package/dist/core/mailbox-admission.js +130 -0
  117. package/dist/core/mailbox-claims.d.ts +251 -0
  118. package/dist/core/mailbox-claims.js +0 -0
  119. package/dist/core/mailbox-codec.d.ts +32 -0
  120. package/dist/core/mailbox-codec.js +221 -0
  121. package/dist/core/mailbox-contract.d.ts +290 -0
  122. package/dist/core/mailbox-contract.js +0 -0
  123. package/dist/core/mailbox-delivery.d.ts +37 -0
  124. package/dist/core/mailbox-delivery.js +193 -0
  125. package/dist/core/mailbox-guards.d.ts +78 -0
  126. package/dist/core/mailbox-guards.js +58 -0
  127. package/dist/core/mailbox-index-codec.d.ts +41 -0
  128. package/dist/core/mailbox-index-codec.js +71 -0
  129. package/dist/core/mailbox-internals.d.ts +156 -0
  130. package/dist/core/mailbox-internals.js +154 -0
  131. package/dist/core/mailbox-maintenance.d.ts +29 -0
  132. package/dist/core/mailbox-maintenance.js +196 -0
  133. package/dist/core/mailbox-settlement.d.ts +69 -0
  134. package/dist/core/mailbox-settlement.js +206 -0
  135. package/dist/core/mailbox-storage.d.ts +117 -0
  136. package/dist/core/mailbox-storage.js +115 -0
  137. package/dist/core/mailbox-transition-helpers.d.ts +70 -0
  138. package/dist/core/mailbox-transition-helpers.js +43 -0
  139. package/dist/core/mailbox-transitions-recovery.d.ts +28 -0
  140. package/dist/core/mailbox-transitions-recovery.js +64 -0
  141. package/dist/core/mailbox-transitions.d.ts +138 -0
  142. package/dist/core/mailbox-transitions.js +180 -0
  143. package/dist/core/mailbox-types.d.ts +406 -0
  144. package/dist/core/mailbox-types.js +16 -0
  145. package/dist/core/mailbox-validation.d.ts +65 -0
  146. package/dist/core/mailbox-validation.js +73 -0
  147. package/dist/core/mailbox-waits.d.ts +50 -0
  148. package/dist/core/mailbox-waits.js +97 -0
  149. package/dist/core/mailbox.d.ts +163 -0
  150. package/dist/core/mailbox.js +211 -0
  151. package/dist/core/outbox-claims.d.ts +307 -0
  152. package/dist/core/outbox-claims.js +0 -0
  153. package/dist/core/outbox-codec.d.ts +24 -0
  154. package/dist/core/outbox-codec.js +257 -0
  155. package/dist/core/outbox-contract.d.ts +346 -0
  156. package/dist/core/outbox-contract.js +0 -0
  157. package/dist/core/outbox-delivery.d.ts +34 -0
  158. package/dist/core/outbox-delivery.js +149 -0
  159. package/dist/core/outbox-drain.d.ts +29 -0
  160. package/dist/core/outbox-drain.js +158 -0
  161. package/dist/core/outbox-enqueue.d.ts +24 -0
  162. package/dist/core/outbox-enqueue.js +115 -0
  163. package/dist/core/outbox-guards.d.ts +76 -0
  164. package/dist/core/outbox-guards.js +60 -0
  165. package/dist/core/outbox-index-codec.d.ts +33 -0
  166. package/dist/core/outbox-index-codec.js +71 -0
  167. package/dist/core/outbox-internals.d.ts +99 -0
  168. package/dist/core/outbox-internals.js +159 -0
  169. package/dist/core/outbox-listing.d.ts +12 -0
  170. package/dist/core/outbox-listing.js +38 -0
  171. package/dist/core/outbox-maintenance.d.ts +28 -0
  172. package/dist/core/outbox-maintenance.js +191 -0
  173. package/dist/core/outbox-operations.d.ts +18 -0
  174. package/dist/core/outbox-operations.js +46 -0
  175. package/dist/core/outbox-runner.d.ts +30 -0
  176. package/dist/core/outbox-runner.js +217 -0
  177. package/dist/core/outbox-settlement.d.ts +57 -0
  178. package/dist/core/outbox-settlement.js +183 -0
  179. package/dist/core/outbox-storage.d.ts +107 -0
  180. package/dist/core/outbox-storage.js +127 -0
  181. package/dist/core/outbox-transition-helpers.d.ts +83 -0
  182. package/dist/core/outbox-transition-helpers.js +63 -0
  183. package/dist/core/outbox-transitions-recovery.d.ts +30 -0
  184. package/dist/core/outbox-transitions-recovery.js +42 -0
  185. package/dist/core/outbox-transitions.d.ts +145 -0
  186. package/dist/core/outbox-transitions.js +236 -0
  187. package/dist/core/outbox-types.d.ts +400 -0
  188. package/dist/core/outbox-types.js +20 -0
  189. package/dist/core/outbox-validation.d.ts +101 -0
  190. package/dist/core/outbox-validation.js +127 -0
  191. package/dist/core/outbox-waits.d.ts +39 -0
  192. package/dist/core/outbox-waits.js +88 -0
  193. package/dist/core/outbox.d.ts +156 -0
  194. package/dist/core/outbox.js +234 -0
  195. package/dist/core/registry-limits.d.ts +49 -0
  196. package/dist/core/registry-limits.js +10 -0
  197. package/dist/core/registry-schema-conversion.d.ts +20 -0
  198. package/dist/core/registry-schema-conversion.js +22 -0
  199. package/dist/core/registry-snapshot.d.ts +91 -42
  200. package/dist/core/registry-snapshot.js +39 -77
  201. package/dist/core/registry-workflow-contract-draft.d.ts +32 -0
  202. package/dist/core/registry-workflow-contract-draft.js +28 -0
  203. package/dist/core/registry-workflow-manifest.d.ts +122 -0
  204. package/dist/core/registry-workflow-manifest.js +92 -0
  205. package/dist/core/source/errors.d.ts +55 -0
  206. package/dist/core/source/errors.js +13 -0
  207. package/dist/core/source/index.d.ts +19 -0
  208. package/dist/core/source/index.js +4 -0
  209. package/dist/core/source/resolvers.d.ts +45 -0
  210. package/dist/core/source/resolvers.js +9 -0
  211. package/dist/core/source/types.d.ts +211 -0
  212. package/dist/core/source/types.js +0 -0
  213. package/dist/core/source/validate.d.ts +91 -0
  214. package/dist/core/source/validate.js +62 -0
  215. package/dist/core/source/workflow-source.d.ts +43 -0
  216. package/dist/core/source/workflow-source.js +16 -0
  217. package/dist/core/types/workflow-registries.d.ts +7 -0
  218. package/dist/core/types/workflow-registry.d.ts +12 -0
  219. package/dist/core/versioning.js +2 -0
  220. package/dist/core/weft-error.d.ts +1 -1
  221. package/dist/core/weft-error.js +9 -1
  222. package/dist/http.js +2 -2
  223. package/dist/index.d.ts +23 -2
  224. package/dist/index.js +50 -0
  225. package/dist/indexeddb.js +1 -1
  226. package/dist/json-schema.js +3 -3
  227. package/dist/mcp/resources.js +2 -0
  228. package/dist/runtime/portable.d.ts +9 -1
  229. package/dist/server/fault-to-json-rpc.js +3 -1
  230. package/dist/server/handler/auth-context-principal.js +4 -0
  231. package/dist/server/json-rpc-http.js +2 -0
  232. package/dist/server/json-rpc-websocket.js +6 -4
  233. package/dist/server/operation-catalog/index.d.ts +1 -1
  234. package/dist/server/operation-catalog/pipeline-helpers.js +2 -0
  235. package/dist/server/operation-catalog/types.d.ts +17 -0
  236. package/dist/server/operation-fault.d.ts +3 -0
  237. package/dist/server/operation-fault.js +3 -1
  238. package/dist/server/operations/activate-workflow-revision.d.ts +30 -0
  239. package/dist/server/operations/activate-workflow-revision.js +69 -0
  240. package/dist/server/operations/fleet-events-subscription.js +1 -1
  241. package/dist/server/operations/fork-workflow.js +1 -1
  242. package/dist/server/operations/get-active-workflow-revision.d.ts +32 -0
  243. package/dist/server/operations/get-active-workflow-revision.js +46 -0
  244. package/dist/server/operations/get-catalog-diagnostics.d.ts +85 -0
  245. package/dist/server/operations/get-catalog-diagnostics.js +57 -0
  246. package/dist/server/operations/get-registry.js +17 -4
  247. package/dist/server/operations/get-workflow-revision.d.ts +25 -0
  248. package/dist/server/operations/get-workflow-revision.js +50 -0
  249. package/dist/server/operations/install-workflow-revision.d.ts +27 -0
  250. package/dist/server/operations/install-workflow-revision.js +46 -0
  251. package/dist/server/operations/list-workflow-revisions.d.ts +20 -0
  252. package/dist/server/operations/list-workflow-revisions.js +40 -0
  253. package/dist/server/operations/start-or-signal-workflow.js +1 -1
  254. package/dist/server/operations/start-workflow.js +1 -1
  255. package/dist/server/operations/static-registrations.js +36 -0
  256. package/dist/server/operations/storage.js +2 -2
  257. package/dist/server/operations/submit-review-decision.js +1 -1
  258. package/dist/server/operations/workflow-catalog-operation-helpers.d.ts +59 -0
  259. package/dist/server/operations/workflow-catalog-operation-helpers.js +135 -0
  260. package/dist/server/runtime/websocket-upgrade.js +2 -0
  261. package/dist/storage/bun-sql.js +49 -6
  262. package/dist/storage/catalog-keys.d.ts +45 -0
  263. package/dist/storage/catalog-keys.js +6 -0
  264. package/dist/storage/compressed-storage.js +1 -1
  265. package/dist/storage/derived-operations.js +1 -1
  266. package/dist/storage/index.d.ts +26 -1
  267. package/dist/storage/interface.d.ts +26 -1
  268. package/dist/storage/interface.js +1 -1
  269. package/dist/storage/key-encoding.d.ts +15 -0
  270. package/dist/storage/key-encoding.js +3 -0
  271. package/dist/storage/key-prefixes.d.ts +1 -1
  272. package/dist/storage/key-prefixes.js +15 -0
  273. package/dist/storage/lmdb.d.ts +26 -1
  274. package/dist/storage/lmdb.js +1 -1
  275. package/dist/storage/mailbox-keys.d.ts +80 -0
  276. package/dist/storage/mailbox-keys.js +14 -0
  277. package/dist/storage/memory.js +1 -1
  278. package/dist/storage/neon.js +2 -2
  279. package/dist/storage/node-sqlite-loader.d.ts +1 -1
  280. package/dist/storage/node-sqlite-loader.js +2 -2
  281. package/dist/storage/node-sqlite.js +51 -8
  282. package/dist/storage/outbox-keys.d.ts +77 -0
  283. package/dist/storage/outbox-keys.js +14 -0
  284. package/dist/storage/postgres.js +2 -2
  285. package/dist/storage/resolve.js +1 -1
  286. package/dist/storage/scoped-storage.js +1 -1
  287. package/dist/storage/storage-configuration.d.ts +6 -0
  288. package/dist/storage/testing.js +1 -1
  289. package/dist/storage/turso.js +2 -2
  290. package/dist/version.d.ts +1 -1
  291. package/dist/version.js +1 -1
  292. package/dist/web-extension.js +1 -1
  293. package/dist/worker/manifest/registry-contract-builder.d.ts +21 -3
  294. package/dist/worker/manifest/registry-contract-builder.js +40 -31
  295. package/package.json +1 -1
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Encoding and fail-closed decoding for the application mailbox's index
3
+ * records (WFT-84): the per-mailbox header, idempotency bindings, and the
4
+ * entries of the delivery, listing, and terminal-retention indexes.
5
+ *
6
+ * Kept apart from the command-record codec so each stays under the repository's
7
+ * file ceiling while carrying its full rationale.
8
+ *
9
+ * @module core/mailbox-index-codec
10
+ */
11
+ import type { ApplicationCommandIdempotencyRecord, MailboxRecord } from './mailbox-types.ts';
12
+ /**
13
+ * Decode the per-mailbox header holding the FIFO allocator and backlog counter.
14
+ *
15
+ * @throws {PersistedDataCorruptError} When the stored bytes are malformed.
16
+ */
17
+ export declare function decodeMailboxRecord(bytes: Uint8Array, key: string): MailboxRecord;
18
+ /**
19
+ * Encode the per-mailbox header.
20
+ */
21
+ export declare function encodeMailboxRecord(record: MailboxRecord): Uint8Array;
22
+ /**
23
+ * Decode an idempotency index record.
24
+ *
25
+ * @throws {PersistedDataCorruptError} When the stored bytes are malformed.
26
+ */
27
+ export declare function decodeApplicationCommandIdempotencyRecord(bytes: Uint8Array, key: string): ApplicationCommandIdempotencyRecord;
28
+ /**
29
+ * Encode an idempotency index record.
30
+ */
31
+ export declare function encodeApplicationCommandIdempotencyRecord(record: ApplicationCommandIdempotencyRecord): Uint8Array;
32
+ /**
33
+ * Decode the command id stored in a FIFO delivery-index entry.
34
+ *
35
+ * @throws {PersistedDataCorruptError} When the entry is not a non-empty string.
36
+ */
37
+ export declare function decodeApplicationReadyEntry(bytes: Uint8Array, key: string): string;
38
+ /**
39
+ * Encode a FIFO delivery-index entry.
40
+ */
41
+ export declare function encodeApplicationReadyEntry(commandId: string): Uint8Array;
@@ -0,0 +1,71 @@
1
+ import { KEYS } from "../storage/interface.js";
2
+ import {
3
+ fail,
4
+ isRecordObject,
5
+ ownKey,
6
+ readIdentifier,
7
+ readInteger,
8
+ readString,
9
+ readVersion
10
+ } from "./application-primitive-codec.js";
11
+ import { decode, encode } from "./codec.js";
12
+ import { MAILBOX_RECORD_VERSION } from "./mailbox-types.js";
13
+ export function decodeMailboxRecord(bytes, key) {
14
+ let decoded;
15
+ try {
16
+ decoded = decode(bytes);
17
+ } catch {
18
+ fail(key);
19
+ }
20
+ if (!isRecordObject(decoded))
21
+ fail(key);
22
+ readVersion(decoded, key);
23
+ const namespace = readString(decoded, "namespace", key), resourceId = readString(decoded, "resourceId", key);
24
+ if (ownKey(() => KEYS.applicationMailbox(namespace, resourceId), key) !== key)
25
+ fail(key);
26
+ const nextSequence = readInteger(decoded, "nextSequence", key), openCount = readInteger(decoded, "openCount", key), admittedCount = readInteger(decoded, "admittedCount", key);
27
+ if (nextSequence !== admittedCount || openCount > admittedCount)
28
+ fail(key);
29
+ return {
30
+ recordVersion: MAILBOX_RECORD_VERSION,
31
+ namespace,
32
+ resourceId,
33
+ nextSequence,
34
+ openCount,
35
+ admittedCount
36
+ };
37
+ }
38
+ export function encodeMailboxRecord(record) {
39
+ return encode(record);
40
+ }
41
+ export function decodeApplicationCommandIdempotencyRecord(bytes, key) {
42
+ let decoded;
43
+ try {
44
+ decoded = decode(bytes);
45
+ } catch {
46
+ fail(key);
47
+ }
48
+ if (!isRecordObject(decoded))
49
+ fail(key);
50
+ readVersion(decoded, key);
51
+ return {
52
+ recordVersion: MAILBOX_RECORD_VERSION,
53
+ commandId: readIdentifier(decoded.commandId, key),
54
+ identityDigest: readString(decoded, "identityDigest", key)
55
+ };
56
+ }
57
+ export function encodeApplicationCommandIdempotencyRecord(record) {
58
+ return encode(record);
59
+ }
60
+ export function decodeApplicationReadyEntry(bytes, key) {
61
+ let decoded;
62
+ try {
63
+ decoded = decode(bytes);
64
+ } catch {
65
+ fail(key);
66
+ }
67
+ return readIdentifier(decoded, key);
68
+ }
69
+ export function encodeApplicationReadyEntry(commandId) {
70
+ return encode(commandId);
71
+ }
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Shared runtime context and projections for the application command mailbox
3
+ * (WFT-84).
4
+ *
5
+ * The public class and the maintenance pass both need the same bound storage
6
+ * keys, resolved policy, clock, commit path, and attempt-controller registry.
7
+ * Passing that context explicitly — rather than reaching into the class — keeps
8
+ * `mailbox-maintenance.ts` a set of ordinary functions that tests
9
+ * can drive directly.
10
+ *
11
+ * @module core/mailbox-internals
12
+ */
13
+ import type { BatchOperation, ConditionalBatchCondition, Storage } from '../storage/interface.ts';
14
+ import { AttemptRegistry } from './application-primitive-attempt-registry.ts';
15
+ import type { ApplicationCommandReceipt, MailboxEventSink } from './mailbox-contract.ts';
16
+ import { type MailboxKeys } from './mailbox-storage.ts';
17
+ import type { ApplicationCommandRecord, ApplicationCommandTerminalRecord } from './mailbox-types.ts';
18
+ import type { ResolvedMailboxPolicy } from './mailbox-validation.ts';
19
+ import { WeftError } from './weft-error.ts';
20
+ /**
21
+ * How many times a transition re-reads durable state and retries after losing a
22
+ * compare-and-swap.
23
+ *
24
+ * The mailbox header is deliberately a per-mailbox hot key — admission and every
25
+ * terminal transition both touch it, which is what makes backlog accounting
26
+ * exact — so a busy mailbox does see contention. The ceiling exists so a
27
+ * pathological loop surfaces as an error instead of spinning forever.
28
+ */
29
+ export declare const MAX_MAILBOX_TRANSITION_ATTEMPTS = 25;
30
+ /**
31
+ * How many scan pages one maintenance pass may walk.
32
+ *
33
+ * The pass reaches the whole keyspace by paging, but stops after this many pages
34
+ * so a very large mailbox cannot make one call run unboundedly. The next pass
35
+ * starts again from the beginning and picks up whatever is still due.
36
+ */
37
+ export declare const MAILBOX_MAINTENANCE_MAX_PAGES = 200;
38
+ /** Everything a mailbox operation needs that is fixed at construction. */
39
+ export type MailboxRuntime = {
40
+ readonly storage: Storage;
41
+ readonly events: MailboxEventSink | undefined;
42
+ readonly policy: ResolvedMailboxPolicy;
43
+ readonly keys: MailboxKeys;
44
+ readonly now: () => number;
45
+ readonly generateId: () => string;
46
+ /**
47
+ * Abort controllers for attempts claimed in this process, keyed by attempt
48
+ * token — shared across every handle onto the same mailbox, so a cancellation
49
+ * raised through one handle reaches a claimant holding another.
50
+ */
51
+ readonly attemptControllers: AttemptRegistry;
52
+ /**
53
+ * Record an attempt this handle now owns, or report that disposal already won.
54
+ *
55
+ * Registering ownership in the caller's `await` continuation would race
56
+ * disposal: `dispose()` could run between the claim resolving and the token
57
+ * being recorded, see nothing to abort, and leave a live claim from a disposed
58
+ * mailbox. Doing both under one synchronous call closes that window.
59
+ */
60
+ readonly adoptAttempt: (attemptToken: string) => (() => void) | null;
61
+ /**
62
+ * Where the previous maintenance pass stopped, when its page cap cut it short.
63
+ *
64
+ * Process-local rather than durable: it is an optimisation for walking a very
65
+ * large keyspace across successive calls, and losing it on restart only means
66
+ * the next pass starts from the beginning, which is always correct.
67
+ */
68
+ readonly readMaintenanceCursor: () => string | undefined;
69
+ readonly writeMaintenanceCursor: (cursor: string | undefined) => void;
70
+ };
71
+ /**
72
+ * Attempt controllers, shared per `(storage, namespace, resourceId)` within one
73
+ * process.
74
+ *
75
+ * Two `Mailbox` handles onto the same durable mailbox are the same
76
+ * mailbox. Giving each its own registry would make the documented in-process
77
+ * cancellation channel silently fail whenever the claimant and the canceller
78
+ * held different handles — the claimant's signal would never fire and it would
79
+ * learn about cancellation only through renewal, which is supposed to be the
80
+ * *cross-process* fallback. Keyed by `Storage` identity in a `WeakMap` so a
81
+ * discarded backend takes its registries with it.
82
+ */
83
+ /** The registry-scope tag for the mailbox, so it never shares a registry with another primitive. */
84
+ export declare const MAILBOX_PRIMITIVE = "mailbox";
85
+ /**
86
+ * Thrown when a transition keeps losing its compare-and-swap.
87
+ *
88
+ * Surfacing this beats looping forever: it means durable contention on this
89
+ * mailbox is real, and the caller — not a hidden retry loop — decides whether to
90
+ * back off, shed load, or shard the resource.
91
+ *
92
+ * @example
93
+ * ```ts
94
+ * import { MailboxContentionError } from '@lostgradient/weft';
95
+ *
96
+ * const error = new MailboxContentionError('admit', null);
97
+ * console.log(error.code); // 'MailboxContentionError'
98
+ * ```
99
+ */
100
+ export declare class MailboxContentionError extends WeftError<'MailboxContentionError'> {
101
+ /** The mailbox operation that could not commit. */
102
+ readonly operation: string;
103
+ /** The command the operation targeted, or `null` for mailbox-wide operations. */
104
+ readonly commandId: string | null;
105
+ constructor(operation: string, commandId: string | null);
106
+ }
107
+ export declare function toApplicationCommandReceipt(record: ApplicationCommandRecord): ApplicationCommandReceipt;
108
+ /**
109
+ * The durable fleet event that describes a transition.
110
+ *
111
+ * A retry is distinguished from an initial admission even though both land in
112
+ * `accepted`, so a consumer reading the feed can tell redelivery from first
113
+ * delivery without diffing receipts. The payload is deliberately bounded: no
114
+ * command payload, no failure details, nothing unbounded.
115
+ */
116
+ export declare function describeCommandTransition(previous: ApplicationCommandRecord | null, next: ApplicationCommandRecord): {
117
+ readonly kind: string;
118
+ readonly payload: unknown;
119
+ };
120
+ /**
121
+ * Abort and forget the process-local controller for one attempt.
122
+ *
123
+ * Releasing a lease must never leave a live controller behind: the signal is
124
+ * attempt-scoped, so a later attempt on the same command gets a fresh one.
125
+ */
126
+ export declare function releaseAttemptController(runtime: MailboxRuntime, attemptToken: string, reason: string, commandId?: string): void;
127
+ /**
128
+ * Release every attempt this process holds for one command.
129
+ *
130
+ * For an observer that finds the command gone — retired by retention in
131
+ * another process, whose maintenance cannot reach this registry — every local
132
+ * attempt on it is over, whatever token it held.
133
+ */
134
+ export declare function releaseAttemptsForCommand(runtime: MailboxRuntime, commandId: string, reason: string, currentToken?: string, observedAt?: number): void;
135
+ /**
136
+ * Commit one command transition together with the index maintenance and
137
+ * backlog accounting it implies.
138
+ *
139
+ * Terminalizing a command decrements the mailbox's open count in the same
140
+ * conditional batch, so `capacity()` can never drift from the records it
141
+ * describes. Admission is the one transition that builds its own header
142
+ * operation, because it also allocates the FIFO sequence.
143
+ *
144
+ * Returns `false` when a compare-and-swap was lost; the caller re-reads and
145
+ * re-decides rather than retrying blindly with stale bytes.
146
+ */
147
+ export declare function commitCommandTransition(runtime: MailboxRuntime, options: {
148
+ readonly previous: ApplicationCommandRecord | null;
149
+ readonly expectedBytes: Uint8Array | null;
150
+ readonly next: ApplicationCommandRecord;
151
+ readonly now: number;
152
+ readonly extraConditions?: readonly ConditionalBatchCondition[] | undefined;
153
+ readonly extraOperations?: readonly BatchOperation[] | undefined;
154
+ }): Promise<boolean>;
155
+ /** Whether a record occupies one of the four terminal dispositions. */
156
+ export declare function isTerminalRecord(record: ApplicationCommandRecord): record is ApplicationCommandTerminalRecord;
@@ -0,0 +1,154 @@
1
+ import { AttemptRegistry } from "./application-primitive-attempt-registry.js";
2
+ import {
3
+ commitMailboxTransition,
4
+ headerOperation,
5
+ loadMailboxHeader,
6
+ planCommandTransition
7
+ } from "./mailbox-storage.js";
8
+ import { isApplicationCommandLeased } from "./mailbox-types.js";
9
+ import { WeftError } from "./weft-error.js";
10
+ export const MAX_MAILBOX_TRANSITION_ATTEMPTS = 25, MAILBOX_MAINTENANCE_MAX_PAGES = 200, MAILBOX_PRIMITIVE = "mailbox";
11
+
12
+ export class MailboxContentionError extends WeftError {
13
+ operation;
14
+ commandId;
15
+ constructor(operation, commandId) {
16
+ super("MailboxContentionError", `Application mailbox ${operation}${commandId === null ? "" : ` for command "${commandId}"`} lost its storage precondition after ${MAX_MAILBOX_TRANSITION_ATTEMPTS} attempts.`);
17
+ this.operation = operation;
18
+ this.commandId = commandId;
19
+ }
20
+ }
21
+ function receiptLeaseFields(record) {
22
+ if (!isApplicationCommandLeased(record))
23
+ return {};
24
+ return {
25
+ claimedAt: record.claimedAt,
26
+ visibilityExpiresAt: record.visibilityExpiresAt,
27
+ lastActivityAt: record.lastActivityAt,
28
+ progress: record.progress
29
+ };
30
+ }
31
+ function receiptTerminalFields(record) {
32
+ if (!isTerminalRecord(record))
33
+ return record.state === "cancellation-requested" ? {
34
+ cancellationRequestedAt: record.cancellationRequestedAt,
35
+ cancellationReason: record.cancellationReason
36
+ } : {};
37
+ return {
38
+ cancellationRequestedAt: record.cancellationRequestedAt,
39
+ cancellationReason: record.cancellationReason,
40
+ terminalAt: record.terminalAt,
41
+ outcome: record.outcome,
42
+ failure: record.failure,
43
+ cleanupPending: record.cleanupPending
44
+ };
45
+ }
46
+ function deepFreeze(value) {
47
+ if (typeof value !== "object" || value === null || Object.isFrozen(value))
48
+ return value;
49
+ Object.freeze(value);
50
+ for (const nested of Object.values(value))
51
+ deepFreeze(nested);
52
+ return value;
53
+ }
54
+ export function toApplicationCommandReceipt(record) {
55
+ return deepFreeze({
56
+ commandId: record.commandId,
57
+ namespace: record.namespace,
58
+ resourceId: record.resourceId,
59
+ sequence: record.sequence,
60
+ state: record.state,
61
+ caller: record.caller,
62
+ target: record.target,
63
+ kind: record.kind,
64
+ payloadDigest: record.payloadDigest,
65
+ payloadForm: record.payload.form,
66
+ payloadMediaType: record.payloadMediaType,
67
+ payloadSchema: record.payloadSchema,
68
+ idempotencyKey: record.idempotencyKey,
69
+ causation: record.causation,
70
+ acceptedAt: record.acceptedAt,
71
+ availableAt: record.availableAt,
72
+ absoluteDeadlineAt: record.absoluteDeadlineAt,
73
+ attempt: record.attempt,
74
+ retryCount: record.retryCount,
75
+ maxAttempts: record.maxAttempts,
76
+ generation: record.generation,
77
+ ...receiptLeaseFields(record),
78
+ ...receiptTerminalFields(record)
79
+ });
80
+ }
81
+ const STATE_EVENT_KINDS = {
82
+ accepted: "mailbox:command-accepted",
83
+ available: "mailbox:command-available",
84
+ claimed: "mailbox:command-claimed",
85
+ "cancellation-requested": "mailbox:command-cancellation-requested",
86
+ applied: "mailbox:command-applied",
87
+ rejected: "mailbox:command-rejected",
88
+ cancelled: "mailbox:command-cancelled",
89
+ "dead-lettered": "mailbox:command-dead-lettered"
90
+ };
91
+ export function describeCommandTransition(previous, next) {
92
+ return {
93
+ kind: previous !== null && next.state === "accepted" && next.retryCount > previous.retryCount ? "mailbox:command-retry-scheduled" : STATE_EVENT_KINDS[next.state],
94
+ payload: {
95
+ namespace: next.namespace,
96
+ resourceId: next.resourceId,
97
+ commandId: next.commandId,
98
+ sequence: next.sequence,
99
+ state: next.state,
100
+ commandKind: next.kind,
101
+ target: next.target,
102
+ attempt: next.attempt,
103
+ retryCount: next.retryCount,
104
+ generation: next.generation,
105
+ previousState: previous === null ? null : previous.state
106
+ }
107
+ };
108
+ }
109
+ export function releaseAttemptController(runtime, attemptToken, reason, commandId) {
110
+ const registration = runtime.attemptControllers.get(attemptToken);
111
+ if (registration === void 0)
112
+ return;
113
+ if (commandId !== void 0 && registration.subjectId !== commandId)
114
+ return;
115
+ runtime.attemptControllers.delete(attemptToken);
116
+ registration.release();
117
+ if (!registration.controller.signal.aborted)
118
+ registration.controller.abort(Error(reason));
119
+ }
120
+ export function releaseAttemptsForCommand(runtime, commandId, reason, currentToken, observedAt) {
121
+ for (const attemptToken of runtime.attemptControllers.tokensFor(commandId)) {
122
+ const registration = runtime.attemptControllers.get(attemptToken);
123
+ if (registration === void 0 || attemptToken === currentToken)
124
+ continue;
125
+ if (registration.committedSerial === null)
126
+ continue;
127
+ if (observedAt !== void 0 && registration.committedSerial > observedAt)
128
+ continue;
129
+ releaseAttemptController(runtime, attemptToken, reason);
130
+ }
131
+ }
132
+ export async function commitCommandTransition(runtime, options) {
133
+ const closing = options.previous !== null && !isTerminalRecord(options.previous) && isTerminalRecord(options.next), extraConditions = [...options.extraConditions ?? []], extraOperations = [...options.extraOperations ?? []];
134
+ if (closing) {
135
+ const header = await loadMailboxHeader(runtime.storage, runtime.keys, runtime.policy.namespace, runtime.policy.resourceId);
136
+ extraConditions.push({ key: runtime.keys.header, expectedValue: header.bytes });
137
+ extraOperations.push(headerOperation(runtime.keys, {
138
+ ...header.record,
139
+ openCount: Math.max(0, header.record.openCount - 1)
140
+ }));
141
+ }
142
+ return commitMailboxTransition(runtime.storage, runtime.events, planCommandTransition(runtime.keys, {
143
+ previous: options.previous,
144
+ expectedBytes: options.expectedBytes,
145
+ next: options.next,
146
+ event: describeCommandTransition(options.previous, options.next),
147
+ now: options.now,
148
+ extraConditions,
149
+ extraOperations
150
+ }));
151
+ }
152
+ export function isTerminalRecord(record) {
153
+ return record.state === "applied" || record.state === "rejected" || record.state === "cancelled" || record.state === "dead-lettered";
154
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The bounded maintenance pass for the application command mailbox (WFT-84).
3
+ *
4
+ * Maintenance is the only thing that advances time-driven work: releasing a
5
+ * delayed command, reclaiming an expired lease at its original FIFO position,
6
+ * dead-lettering a command past its absolute deadline, and retiring terminal
7
+ * receipts past their retention window. Nothing here runs on a hidden timer — a
8
+ * host with `backgroundTasks: 'manual'` calls `runMaintenance()` and gets
9
+ * exactly one deterministic pass.
10
+ *
11
+ * Every pass is bounded. It walks the command keyspace in pages of
12
+ * `maintenanceBatchSize` records, up to {@link MAILBOX_MAINTENANCE_MAX_PAGES}
13
+ * pages per call, carrying its cursor to the next call when the cap cuts it
14
+ * short; and it retires at most `maintenanceBatchSize` terminal receipts. A large
15
+ * mailbox drains across several calls instead of one unbounded sweep.
16
+ *
17
+ * @module core/mailbox-maintenance
18
+ */
19
+ import type { MailboxMaintenanceReport } from './mailbox-contract.ts';
20
+ import { type MailboxRuntime } from './mailbox-internals.ts';
21
+ /**
22
+ * Run one bounded maintenance pass.
23
+ *
24
+ * The scan decodes every command record it visits, so a single corrupt record
25
+ * raises `PersistedDataCorruptError` and halts the pass. That is deliberate: a
26
+ * mailbox whose durable state is untrustworthy must not keep reclaiming leases
27
+ * and dead-lettering commands around the damage.
28
+ */
29
+ export declare function runMailboxMaintenance(runtime: MailboxRuntime, now: number): Promise<MailboxMaintenanceReport>;
@@ -0,0 +1,196 @@
1
+ import { formatSortableStorageTimestamp, storageConditionalBatch } from "../storage/interface.js";
2
+ import { leaseCommitSerial } from "./application-primitive-attempt-registry.js";
3
+ import { decodeApplicationCommandRecord } from "./mailbox-codec.js";
4
+ import {
5
+ decodeApplicationCommandIdempotencyRecord,
6
+ decodeApplicationReadyEntry
7
+ } from "./mailbox-index-codec.js";
8
+ import {
9
+ commitCommandTransition,
10
+ isTerminalRecord,
11
+ MAILBOX_MAINTENANCE_MAX_PAGES,
12
+ MailboxContentionError,
13
+ MAX_MAILBOX_TRANSITION_ATTEMPTS,
14
+ releaseAttemptController,
15
+ releaseAttemptsForCommand
16
+ } from "./mailbox-internals.js";
17
+ import { loadCommand } from "./mailbox-storage.js";
18
+ import { recoverExpiredCommand } from "./mailbox-transitions-recovery.js";
19
+ import { isCommandPastDeadline, releaseWaitingCommand } from "./mailbox-transitions.js";
20
+ import { isApplicationCommandLeased } from "./mailbox-types.js";
21
+ function classify(record, now) {
22
+ if (isTerminalRecord(record))
23
+ return null;
24
+ if (isCommandPastDeadline(record, now))
25
+ return "recover";
26
+ if (record.state === "accepted" && now >= record.availableAt)
27
+ return "release";
28
+ if (isApplicationCommandLeased(record) && now >= record.visibilityExpiresAt)
29
+ return "recover";
30
+ return null;
31
+ }
32
+ function countTransition(counters, previous, next) {
33
+ if (next.state === "available" && previous.state === "accepted")
34
+ counters.released += 1;
35
+ else if (next.state === "accepted")
36
+ counters.reclaimed += 1;
37
+ else if (next.state === "dead-lettered")
38
+ counters.deadLettered += 1;
39
+ else if (next.state === "cancelled")
40
+ counters.cancelled += 1;
41
+ }
42
+ function reconcileLocalAttempts(runtime, commandId, record, observedAt) {
43
+ releaseAttemptsForCommand(runtime, commandId, "This attempt is no longer the current lease on its command.", record !== void 0 && isApplicationCommandLeased(record) ? record.attemptToken : void 0, observedAt);
44
+ }
45
+ async function advanceCommand(runtime, commandId, now, counters) {
46
+ for (let attempt = 1;attempt <= MAX_MAILBOX_TRANSITION_ATTEMPTS; attempt += 1) {
47
+ const observedAt = leaseCommitSerial(), loaded = await loadCommand(runtime.storage, runtime.keys, commandId);
48
+ if (loaded === null) {
49
+ reconcileLocalAttempts(runtime, commandId, void 0, observedAt);
50
+ return;
51
+ }
52
+ const due = classify(loaded.record, now);
53
+ if (due === null) {
54
+ reconcileLocalAttempts(runtime, commandId, loaded.record, observedAt);
55
+ return;
56
+ }
57
+ const transition = due === "release" ? releaseWaitingCommand(loaded.record, now) : recoverExpiredCommand(loaded.record, {
58
+ now,
59
+ retryBackoffMs: runtime.policy.retryBackoffMs,
60
+ maxRetryBackoffMs: runtime.policy.maxRetryBackoffMs
61
+ });
62
+ if (!transition.ok)
63
+ return;
64
+ if (!await commitCommandTransition(runtime, {
65
+ previous: loaded.record,
66
+ expectedBytes: loaded.bytes,
67
+ next: transition.next,
68
+ now
69
+ }))
70
+ continue;
71
+ if (isApplicationCommandLeased(loaded.record))
72
+ releaseAttemptController(runtime, loaded.record.attemptToken, "The application mailbox reclaimed this attempt after its lease expired.");
73
+ countTransition(counters, loaded.record, transition.next);
74
+ return;
75
+ }
76
+ throw new MailboxContentionError("maintenance", commandId);
77
+ }
78
+ async function retireOneReceipt(runtime, indexKey, indexBytes) {
79
+ const commandId = commandIdFromTerminalKey(indexKey), observedAt = leaseCommitSerial(), loaded = commandId === null ? null : await loadCommand(runtime.storage, runtime.keys, commandId);
80
+ if (commandId === null || loaded !== null && !ownsTerminalEntry(runtime, indexKey, loaded)) {
81
+ await discardTerminalEntry(runtime, indexKey, indexBytes);
82
+ return !1;
83
+ }
84
+ const operations = [{ type: "delete", key: indexKey }];
85
+ if (loaded === null)
86
+ return storageConditionalBatch(runtime.storage, [{ key: indexKey, expectedValue: indexBytes }], operations);
87
+ operations.push({ type: "delete", key: runtime.keys.command(commandId) });
88
+ const auxiliary = await ownedAuxiliaryEntries(runtime, loaded);
89
+ operations.push(...auxiliary.operations);
90
+ const retired = await storageConditionalBatch(runtime.storage, [
91
+ { key: runtime.keys.command(commandId), expectedValue: loaded.bytes },
92
+ ...auxiliary.conditions
93
+ ], operations);
94
+ if (retired)
95
+ releaseAttemptsForCommand(runtime, commandId, "This command was retired; its attempt is over.", void 0, observedAt);
96
+ return retired;
97
+ }
98
+ async function ownedAuxiliaryEntries(runtime, loaded) {
99
+ const conditions = [], operations = [], sequenceKey = runtime.keys.bySequence(loaded.record.sequence), sequenceBytes = await runtime.storage.get(sequenceKey);
100
+ if (sequenceBytes !== null && decodeApplicationReadyEntry(sequenceBytes, sequenceKey) === loaded.record.commandId) {
101
+ conditions.push({ key: sequenceKey, expectedValue: sequenceBytes });
102
+ operations.push({ type: "delete", key: sequenceKey });
103
+ }
104
+ if (loaded.record.idempotencyKey === void 0)
105
+ return { conditions, operations };
106
+ const bindingKey = runtime.keys.idempotency(loaded.record.idempotencyKey), bindingBytes = await runtime.storage.get(bindingKey);
107
+ if (bindingBytes !== null && decodeApplicationCommandIdempotencyRecord(bindingBytes, bindingKey).commandId === loaded.record.commandId) {
108
+ conditions.push({ key: bindingKey, expectedValue: bindingBytes });
109
+ operations.push({ type: "delete", key: bindingKey });
110
+ }
111
+ return { conditions, operations };
112
+ }
113
+ function ownsTerminalEntry(runtime, indexKey, loaded) {
114
+ return isTerminalRecord(loaded.record) && runtime.keys.terminal(loaded.record.terminalAt, loaded.record.commandId) === indexKey;
115
+ }
116
+ async function discardTerminalEntry(runtime, indexKey, indexBytes) {
117
+ await storageConditionalBatch(runtime.storage, [{ key: indexKey, expectedValue: indexBytes }], [{ type: "delete", key: indexKey }]);
118
+ }
119
+ async function retireTerminalReceipts(runtime, now, counters) {
120
+ const horizon = now - runtime.policy.terminalRetentionMs;
121
+ if (horizon < 0)
122
+ return;
123
+ const expired = [], malformed = [];
124
+ for await (const [key, value] of runtime.storage.scan(runtime.keys.terminalPrefix, {
125
+ limit: runtime.policy.maintenanceBatchSize
126
+ })) {
127
+ const terminalAt = parseTerminalAt(key, runtime.keys.terminalPrefix);
128
+ if (terminalAt === null) {
129
+ malformed.push([key, value]);
130
+ continue;
131
+ }
132
+ if (terminalAt >= horizon)
133
+ break;
134
+ expired.push([key, value]);
135
+ }
136
+ for (const [indexKey, bytes] of malformed)
137
+ await discardTerminalEntry(runtime, indexKey, bytes);
138
+ for (const [indexKey, bytes] of expired)
139
+ if (await retireOneReceipt(runtime, indexKey, bytes))
140
+ counters.retired += 1;
141
+ }
142
+ function parseTerminalAt(key, prefix) {
143
+ const suffix = key.slice(prefix.length), separator = suffix.indexOf(":");
144
+ if (separator === -1)
145
+ return null;
146
+ const segment = suffix.slice(0, separator), parsed = Number(segment);
147
+ return Number.isSafeInteger(parsed) && formatSortableStorageTimestamp(parsed) === segment ? parsed : null;
148
+ }
149
+ function commandIdFromTerminalKey(key) {
150
+ const separator = key.lastIndexOf(":");
151
+ if (separator === -1)
152
+ return null;
153
+ try {
154
+ return decodeURIComponent(key.slice(separator + 1));
155
+ } catch {
156
+ return null;
157
+ }
158
+ }
159
+ async function collectDueCommands(runtime, now, startAfter) {
160
+ const batchSize = runtime.policy.maintenanceBatchSize, due = [];
161
+ let cursor = startAfter;
162
+ for (let page = 0;page < MAILBOX_MAINTENANCE_MAX_PAGES; page += 1) {
163
+ let seen = 0;
164
+ const options = cursor === void 0 ? { limit: batchSize } : { limit: batchSize, gt: cursor }, observedAt = leaseCommitSerial();
165
+ for await (const [key, value] of runtime.storage.scan(runtime.keys.commandPrefix, options)) {
166
+ seen += 1;
167
+ cursor = key;
168
+ const record = decodeApplicationCommandRecord(value, key);
169
+ reconcileLocalAttempts(runtime, record.commandId, record, observedAt);
170
+ if (classify(record, now) !== null)
171
+ due.push(record.commandId);
172
+ }
173
+ if (seen < batchSize)
174
+ return { due, nextCursor: void 0 };
175
+ }
176
+ return { due, nextCursor: cursor };
177
+ }
178
+ export async function runMailboxMaintenance(runtime, now) {
179
+ const counters = {
180
+ released: 0,
181
+ reclaimed: 0,
182
+ deadLettered: 0,
183
+ cancelled: 0,
184
+ retired: 0
185
+ }, previousCursor = runtime.readMaintenanceCursor(), scan = await collectDueCommands(runtime, now, previousCursor);
186
+ try {
187
+ for (const commandId of scan.due)
188
+ await advanceCommand(runtime, commandId, now, counters);
189
+ } catch (error) {
190
+ runtime.writeMaintenanceCursor(previousCursor);
191
+ throw error;
192
+ }
193
+ runtime.writeMaintenanceCursor(scan.nextCursor);
194
+ await retireTerminalReceipts(runtime, now, counters);
195
+ return Object.freeze({ ...counters });
196
+ }