@sixb/core 0.1.9 → 0.1.10

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 (279) hide show
  1. package/dist/actions/index.js +8 -8
  2. package/dist/agents/index.js +16 -15
  3. package/dist/agents/index.js.map +1 -1
  4. package/dist/bootstrap/index.js +20 -19
  5. package/dist/bootstrap/index.js.map +1 -1
  6. package/dist/broker/in-memory.d.ts +8 -2
  7. package/dist/broker/in-memory.d.ts.map +1 -1
  8. package/dist/broker/index.d.ts +4 -2
  9. package/dist/broker/index.d.ts.map +1 -1
  10. package/dist/broker/index.js +7 -3
  11. package/dist/broker/index.js.map +1 -1
  12. package/dist/broker/retention.d.ts +4 -0
  13. package/dist/broker/retention.d.ts.map +1 -0
  14. package/dist/broker/subscriber.d.ts +10 -0
  15. package/dist/broker/subscriber.d.ts.map +1 -0
  16. package/dist/broker/types.d.ts +14 -1
  17. package/dist/broker/types.d.ts.map +1 -1
  18. package/dist/{chunk-k3cqkkn2.js → chunk-1824gssz.js} +30 -65
  19. package/dist/chunk-1824gssz.js.map +12 -0
  20. package/dist/{chunk-h1gfhhbh.js → chunk-28ja2cjs.js} +1 -1
  21. package/dist/chunk-2c4j899j.js.map +1 -1
  22. package/dist/{chunk-ppytyr6w.js → chunk-358caqf5.js} +4 -4
  23. package/dist/chunk-358caqf5.js.map +12 -0
  24. package/dist/{chunk-kxwa347f.js → chunk-3qms4nax.js} +2 -2
  25. package/dist/{chunk-kxwa347f.js.map → chunk-3qms4nax.js.map} +3 -3
  26. package/dist/{chunk-emyyk7dx.js → chunk-5c7ga8zz.js} +22 -145
  27. package/dist/chunk-5c7ga8zz.js.map +12 -0
  28. package/dist/{chunk-hpr1r6av.js → chunk-5p1v802y.js} +46 -39
  29. package/dist/{chunk-hpr1r6av.js.map → chunk-5p1v802y.js.map} +4 -4
  30. package/dist/{chunk-zcnq6105.js → chunk-85cd7e8c.js} +2 -1
  31. package/dist/chunk-85cd7e8c.js.map +10 -0
  32. package/dist/{chunk-1enhpacz.js → chunk-976rxdpx.js} +5 -5
  33. package/dist/{chunk-3f7veca0.js → chunk-9x8dybsb.js} +3 -3
  34. package/dist/chunk-9x8dybsb.js.map +10 -0
  35. package/dist/{chunk-r29fxdhn.js → chunk-aa6z4a19.js} +160 -44
  36. package/dist/chunk-aa6z4a19.js.map +12 -0
  37. package/dist/{chunk-r4tfx1sf.js → chunk-agbbygvh.js} +4 -4
  38. package/dist/{chunk-jjy0fhrn.js → chunk-b0ec55a1.js} +20 -3
  39. package/dist/chunk-b0ec55a1.js.map +12 -0
  40. package/dist/{chunk-xfbydgzy.js → chunk-bn138h9w.js} +7 -8
  41. package/dist/{chunk-xfbydgzy.js.map → chunk-bn138h9w.js.map} +3 -3
  42. package/dist/{chunk-an5tt9y0.js → chunk-ftv470j8.js} +42 -4
  43. package/dist/{chunk-an5tt9y0.js.map → chunk-ftv470j8.js.map} +7 -7
  44. package/dist/{chunk-0w35wavj.js → chunk-g7knyczr.js} +1 -1
  45. package/dist/{chunk-x57ycxq0.js → chunk-gqh45d2n.js} +42 -3
  46. package/dist/{chunk-x57ycxq0.js.map → chunk-gqh45d2n.js.map} +3 -3
  47. package/dist/{chunk-0rdrecsb.js → chunk-kfee1h52.js} +116 -32
  48. package/dist/chunk-kfee1h52.js.map +13 -0
  49. package/dist/{chunk-ypbtykv6.js → chunk-nmbxcaqx.js} +1 -1
  50. package/dist/{chunk-3tgwrcyh.js → chunk-nt99sxd6.js} +2 -2
  51. package/dist/{chunk-3tgwrcyh.js.map → chunk-nt99sxd6.js.map} +3 -3
  52. package/dist/{chunk-vf691g1r.js → chunk-p5vcsw9c.js} +2 -2
  53. package/dist/{chunk-mjbk2djy.js → chunk-p6a4npdx.js} +211 -106
  54. package/dist/{chunk-mjbk2djy.js.map → chunk-p6a4npdx.js.map} +9 -8
  55. package/dist/{chunk-3frsnm7x.js → chunk-qma80ych.js} +4 -6
  56. package/dist/chunk-qma80ych.js.map +11 -0
  57. package/dist/{chunk-qhcpj9wc.js → chunk-qyeptdyv.js} +96 -2
  58. package/dist/chunk-qyeptdyv.js.map +15 -0
  59. package/dist/{chunk-52hbv0ak.js → chunk-r7fj830t.js} +101 -48
  60. package/dist/chunk-r7fj830t.js.map +36 -0
  61. package/dist/{chunk-xyr8kppc.js → chunk-rtk71jzp.js} +1 -1
  62. package/dist/{chunk-m3z3x9v0.js → chunk-t1f4f1ve.js} +1 -1
  63. package/dist/chunk-v2vcg0w6.js +151 -0
  64. package/dist/chunk-v2vcg0w6.js.map +11 -0
  65. package/dist/connectors/connections/authorizations.d.ts.map +1 -1
  66. package/dist/connectors/connections/contracts.d.ts +3 -0
  67. package/dist/connectors/connections/contracts.d.ts.map +1 -1
  68. package/dist/connectors/connections/internal.js +1 -1
  69. package/dist/connectors/connections/request.d.ts +3 -1
  70. package/dist/connectors/connections/request.d.ts.map +1 -1
  71. package/dist/connectors/connections/runs.d.ts.map +1 -1
  72. package/dist/connectors/connections/tokens.d.ts.map +1 -1
  73. package/dist/connectors/connections/validation.d.ts +5 -1
  74. package/dist/connectors/connections/validation.d.ts.map +1 -1
  75. package/dist/connectors/types.d.ts +19 -4
  76. package/dist/connectors/types.d.ts.map +1 -1
  77. package/dist/events/execution.d.ts +1 -1
  78. package/dist/events/execution.d.ts.map +1 -1
  79. package/dist/events/index.js +1 -1
  80. package/dist/events/service.d.ts +2 -2
  81. package/dist/events/service.d.ts.map +1 -1
  82. package/dist/execution/agent.js +16 -15
  83. package/dist/execution/agent.js.map +1 -1
  84. package/dist/execution/primitive.js +16 -15
  85. package/dist/execution/primitive.js.map +2 -2
  86. package/dist/execution/request.js +16 -15
  87. package/dist/execution/request.js.map +2 -2
  88. package/dist/index.d.ts +3 -3
  89. package/dist/index.d.ts.map +1 -1
  90. package/dist/index.js +30 -28
  91. package/dist/index.js.map +1 -1
  92. package/dist/lake-storage/changes.d.ts +5 -0
  93. package/dist/lake-storage/changes.d.ts.map +1 -0
  94. package/dist/lake-storage/in-memory.d.ts +2 -1
  95. package/dist/lake-storage/in-memory.d.ts.map +1 -1
  96. package/dist/lake-storage/index.d.ts +2 -1
  97. package/dist/lake-storage/index.d.ts.map +1 -1
  98. package/dist/lake-storage/index.js +4 -2
  99. package/dist/lake-storage/index.js.map +1 -1
  100. package/dist/lake-storage/types.d.ts +36 -0
  101. package/dist/lake-storage/types.d.ts.map +1 -1
  102. package/dist/logging/internal.js +1 -1
  103. package/dist/materialization/index.d.ts +1 -1
  104. package/dist/materialization/index.d.ts.map +1 -1
  105. package/dist/materialization/model.d.ts +12 -1
  106. package/dist/materialization/model.d.ts.map +1 -1
  107. package/dist/materializer/index.d.ts +1 -1
  108. package/dist/materializer/index.d.ts.map +1 -1
  109. package/dist/materializer/index.js +5 -5
  110. package/dist/materializer/materializer.d.ts +5 -1
  111. package/dist/materializer/materializer.d.ts.map +1 -1
  112. package/dist/materializer/projections/entry-validator.d.ts +2 -1
  113. package/dist/materializer/projections/entry-validator.d.ts.map +1 -1
  114. package/dist/materializer/projections/replace.d.ts.map +1 -1
  115. package/dist/materializer/projections/source-materialization.d.ts +4 -3
  116. package/dist/materializer/projections/source-materialization.d.ts.map +1 -1
  117. package/dist/materializer/shared/normalize.d.ts +2 -1
  118. package/dist/materializer/shared/normalize.d.ts.map +1 -1
  119. package/dist/models/execution/index.js +5 -5
  120. package/dist/objects/index.js +9 -9
  121. package/dist/objects/query/index.js +5 -5
  122. package/dist/pipelines/index.js +6 -6
  123. package/dist/pipelines/run-dispatch.d.ts.map +1 -1
  124. package/dist/projections/internal.js +6 -8
  125. package/dist/projections/internal.js.map +1 -1
  126. package/dist/projections/run-dispatch.d.ts.map +1 -1
  127. package/dist/queues/in-memory.d.ts +7 -3
  128. package/dist/queues/in-memory.d.ts.map +1 -1
  129. package/dist/queues/index.js +1 -1
  130. package/dist/queues/indexed-heap.d.ts +15 -0
  131. package/dist/queues/indexed-heap.d.ts.map +1 -0
  132. package/dist/runtime/host.d.ts.map +1 -1
  133. package/dist/runtime/internal.js +1 -1
  134. package/dist/runtime/ontology-mutations.d.ts +3 -1
  135. package/dist/runtime/ontology-mutations.d.ts.map +1 -1
  136. package/dist/shares/internal.js +4 -4
  137. package/dist/storage/connector-connections/in-memory.d.ts.map +1 -1
  138. package/dist/storage/connector-connections/provider/runs.d.ts.map +1 -1
  139. package/dist/storage/connector-connections/provider.js +2 -2
  140. package/dist/storage/connector-connections/provider.js.map +3 -3
  141. package/dist/storage/connector-connections/types.d.ts +3 -2
  142. package/dist/storage/connector-connections/types.d.ts.map +1 -1
  143. package/dist/storage/index.d.ts +1 -1
  144. package/dist/storage/index.d.ts.map +1 -1
  145. package/dist/storage/index.js +4 -4
  146. package/dist/storage/internal.js +1 -1
  147. package/dist/storage/ontology/in-memory/materializations-state.d.ts.map +1 -1
  148. package/dist/storage/ontology/in-memory/materializations.d.ts +1 -1
  149. package/dist/storage/ontology/in-memory/materializations.d.ts.map +1 -1
  150. package/dist/storage/ontology/in-memory/shared-state.d.ts +15 -2
  151. package/dist/storage/ontology/in-memory/shared-state.d.ts.map +1 -1
  152. package/dist/storage/ontology/in-memory/source-roots.d.ts +6 -0
  153. package/dist/storage/ontology/in-memory/source-roots.d.ts.map +1 -0
  154. package/dist/storage/ontology/in-memory/sources.d.ts.map +1 -1
  155. package/dist/storage/ontology/index.d.ts +1 -1
  156. package/dist/storage/ontology/index.d.ts.map +1 -1
  157. package/dist/storage/ontology/provider.d.ts +16 -0
  158. package/dist/storage/ontology/provider.d.ts.map +1 -1
  159. package/dist/storage/ontology/provider.js +4 -2
  160. package/dist/storage/ontology/provider.js.map +1 -1
  161. package/dist/storage/ontology/sources.d.ts +13 -4
  162. package/dist/storage/ontology/sources.d.ts.map +1 -1
  163. package/dist/storage/projection-runs/lifecycle.d.ts.map +1 -1
  164. package/dist/storage/projection-runs/provider.js +2 -2
  165. package/dist/storage/projection-runs/types.d.ts +2 -0
  166. package/dist/storage/projection-runs/types.d.ts.map +1 -1
  167. package/dist/syncs/index.js +8 -7
  168. package/dist/syncs/index.js.map +1 -1
  169. package/dist/syncs/run-dispatch.d.ts.map +1 -1
  170. package/dist/testing/broker-contract.d.ts.map +1 -1
  171. package/dist/testing/connector-connection-storage-contract.d.ts.map +1 -1
  172. package/dist/testing/index.d.ts +1 -0
  173. package/dist/testing/index.d.ts.map +1 -1
  174. package/dist/testing/index.js +1459 -951
  175. package/dist/testing/index.js.map +9 -8
  176. package/dist/testing/lake-changes-contract.d.ts +6 -0
  177. package/dist/testing/lake-changes-contract.d.ts.map +1 -0
  178. package/dist/testing/materializer-storage-contract.d.ts.map +1 -1
  179. package/dist/testing/ontology-storage-contract.d.ts.map +1 -1
  180. package/dist/testing/projection-run-storage-contract.d.ts.map +1 -1
  181. package/dist/webhooks/connections.d.ts +6 -0
  182. package/dist/webhooks/connections.d.ts.map +1 -0
  183. package/dist/webhooks/index.d.ts +1 -1
  184. package/dist/webhooks/index.d.ts.map +1 -1
  185. package/dist/webhooks/internal.d.ts +2 -0
  186. package/dist/webhooks/internal.d.ts.map +1 -1
  187. package/dist/webhooks/internal.js +54 -9
  188. package/dist/webhooks/internal.js.map +4 -3
  189. package/dist/webhooks/registry.d.ts +1 -1
  190. package/dist/webhooks/registry.d.ts.map +1 -1
  191. package/dist/webhooks/types.d.ts +15 -4
  192. package/dist/webhooks/types.d.ts.map +1 -1
  193. package/dist/workflows/index.js +17 -16
  194. package/dist/workflows/index.js.map +1 -1
  195. package/dist/workflows/run-dispatch.d.ts +3 -1
  196. package/dist/workflows/run-dispatch.d.ts.map +1 -1
  197. package/package.json +1 -1
  198. package/src/broker/in-memory.ts +104 -39
  199. package/src/broker/index.ts +4 -1
  200. package/src/broker/retention.ts +28 -0
  201. package/src/broker/subscriber.ts +21 -0
  202. package/src/broker/types.ts +15 -1
  203. package/src/connectors/builders.ts +0 -6
  204. package/src/connectors/connections/authorizations.ts +11 -5
  205. package/src/connectors/connections/contracts.ts +3 -0
  206. package/src/connectors/connections/request.ts +56 -22
  207. package/src/connectors/connections/runs.ts +1 -0
  208. package/src/connectors/connections/tokens.ts +4 -0
  209. package/src/connectors/connections/validation.ts +91 -5
  210. package/src/connectors/types.ts +19 -4
  211. package/src/events/execution.ts +2 -2
  212. package/src/events/service.ts +3 -3
  213. package/src/index.ts +10 -2
  214. package/src/lake-storage/changes.ts +43 -0
  215. package/src/lake-storage/in-memory.ts +73 -0
  216. package/src/lake-storage/index.ts +4 -0
  217. package/src/lake-storage/types.ts +40 -0
  218. package/src/logging/service.ts +1 -1
  219. package/src/materialization/index.ts +2 -0
  220. package/src/materialization/model.ts +14 -1
  221. package/src/materializer/index.ts +2 -0
  222. package/src/materializer/materializer.ts +10 -0
  223. package/src/materializer/projections/entry-validator.ts +19 -1
  224. package/src/materializer/projections/replace.ts +36 -1
  225. package/src/materializer/projections/source-materialization.ts +26 -6
  226. package/src/materializer/shared/normalize.ts +1 -1
  227. package/src/pipelines/run-dispatch.ts +21 -1
  228. package/src/projections/run-dispatch.ts +30 -68
  229. package/src/queues/in-memory.ts +105 -62
  230. package/src/queues/indexed-heap.ts +74 -0
  231. package/src/runtime/host.ts +0 -14
  232. package/src/runtime/ontology-mutations.ts +4 -0
  233. package/src/storage/connector-connections/in-memory.ts +3 -1
  234. package/src/storage/connector-connections/provider/runs.ts +3 -1
  235. package/src/storage/connector-connections/types.ts +3 -2
  236. package/src/storage/index.ts +1 -0
  237. package/src/storage/ontology/in-memory/materializations-state.ts +5 -18
  238. package/src/storage/ontology/in-memory/materializations.ts +73 -92
  239. package/src/storage/ontology/in-memory/shared-state.ts +26 -1
  240. package/src/storage/ontology/in-memory/source-roots.ts +123 -0
  241. package/src/storage/ontology/in-memory/sources.ts +78 -23
  242. package/src/storage/ontology/index.ts +1 -0
  243. package/src/storage/ontology/provider.ts +70 -0
  244. package/src/storage/ontology/sources.ts +14 -3
  245. package/src/storage/projection-runs/lifecycle.ts +5 -2
  246. package/src/storage/projection-runs/types.ts +4 -3
  247. package/src/syncs/run-dispatch.ts +19 -1
  248. package/src/testing/broker-contract.ts +95 -0
  249. package/src/testing/connector-connection-storage-contract.ts +18 -1
  250. package/src/testing/index.ts +1 -0
  251. package/src/testing/lake-changes-contract.ts +240 -0
  252. package/src/testing/materializer-storage-contract.ts +172 -1
  253. package/src/testing/ontology-storage-contract.ts +7 -0
  254. package/src/testing/projection-run-storage-contract.ts +34 -0
  255. package/src/webhooks/connections.ts +56 -0
  256. package/src/webhooks/index.ts +2 -0
  257. package/src/webhooks/internal.ts +2 -0
  258. package/src/webhooks/registry.ts +3 -4
  259. package/src/webhooks/types.ts +17 -4
  260. package/src/workflows/run-dispatch.ts +42 -29
  261. package/dist/chunk-0rdrecsb.js.map +0 -11
  262. package/dist/chunk-3f7veca0.js.map +0 -10
  263. package/dist/chunk-3frsnm7x.js.map +0 -11
  264. package/dist/chunk-52hbv0ak.js.map +0 -36
  265. package/dist/chunk-emyyk7dx.js.map +0 -14
  266. package/dist/chunk-jjy0fhrn.js.map +0 -12
  267. package/dist/chunk-k3cqkkn2.js.map +0 -12
  268. package/dist/chunk-ppytyr6w.js.map +0 -12
  269. package/dist/chunk-qhcpj9wc.js.map +0 -14
  270. package/dist/chunk-r29fxdhn.js.map +0 -11
  271. package/dist/chunk-zcnq6105.js.map +0 -10
  272. /package/dist/{chunk-h1gfhhbh.js.map → chunk-28ja2cjs.js.map} +0 -0
  273. /package/dist/{chunk-1enhpacz.js.map → chunk-976rxdpx.js.map} +0 -0
  274. /package/dist/{chunk-r4tfx1sf.js.map → chunk-agbbygvh.js.map} +0 -0
  275. /package/dist/{chunk-0w35wavj.js.map → chunk-g7knyczr.js.map} +0 -0
  276. /package/dist/{chunk-ypbtykv6.js.map → chunk-nmbxcaqx.js.map} +0 -0
  277. /package/dist/{chunk-vf691g1r.js.map → chunk-p5vcsw9c.js.map} +0 -0
  278. /package/dist/{chunk-xyr8kppc.js.map → chunk-rtk71jzp.js.map} +0 -0
  279. /package/dist/{chunk-m3z3x9v0.js.map → chunk-t1f4f1ve.js.map} +0 -0
@@ -10,6 +10,8 @@ import type {
10
10
  ProjectionCommitResult,
11
11
  ProjectionExecution,
12
12
  ProjectionMaterializationIdentity,
13
+ ProjectionSourceBase,
14
+ ProjectionSourceDeletion,
13
15
  ProjectionSourceEntry,
14
16
  ProjectionSourceRef,
15
17
  ProjectionSourceReplacement,
@@ -19,6 +21,7 @@ import type {
19
21
  OntologyCommitRecord,
20
22
  OntologyCommitWrite,
21
23
  OntologyMaterializationStorage,
24
+ OntologySourceRecord,
22
25
  OntologyStorage,
23
26
  } from "../../storage/ontology"
24
27
  import type { MaterializerContext, MaterializerStorage } from "../context"
@@ -60,7 +63,8 @@ interface PreparedProjectionReplacement {
60
63
  readonly source: ProjectionSourceRef
61
64
  readonly datasetVersion: PinnedDatasetVersion
62
65
  readonly execution: ProjectionExecution
63
- readonly entries: AsyncIterable<ProjectionSourceEntry>
66
+ readonly entries: AsyncIterable<ProjectionSourceEntry | ProjectionSourceDeletion>
67
+ readonly base?: ProjectionSourceBase
64
68
  readonly signal?: AbortSignal
65
69
  readonly resolved: ResolvedSourceProjection
66
70
  readonly projectionKind: "object" | "link"
@@ -128,6 +132,7 @@ function prepareProjectionReplacement(
128
132
  datasetVersion,
129
133
  execution,
130
134
  entries: raw.input.entries,
135
+ ...(raw.input.base ? { base: Object.freeze({ ...raw.input.base }) } : {}),
131
136
  resolved,
132
137
  projectionKind,
133
138
  runIdentity,
@@ -237,6 +242,7 @@ async function prepareProjectionCandidate(
237
242
  source: command.source,
238
243
  })
239
244
  validateProjectionWatermark(active, command.datasetVersion)
245
+ assertDeltaBase(active, command)
240
246
  return {
241
247
  materializationId: context.materializationId(),
242
248
  createdAt: context.clock().toISOString(),
@@ -263,6 +269,7 @@ async function stageProjectionCandidate(
263
269
  ownershipHash: command.resolved.ownershipHash,
264
270
  createdAt: candidate.createdAt,
265
271
  entries: command.entries,
272
+ ...(command.base ? { base: command.base } : {}),
266
273
  validateEntry: createProjectionEntryValidator(context.ontology, command.resolved),
267
274
  }
268
275
  const staged = await stageCandidateEntries(context, input, command.signal)
@@ -312,6 +319,14 @@ async function executeProjectionTransaction(
312
319
  )
313
320
  if (replay) return projectionReplayResult(replay, command)
314
321
 
322
+ if (command.base) {
323
+ const active = await storage.ontology.sources.getActive({
324
+ projectId: context.projectId,
325
+ source: command.source,
326
+ })
327
+ validateProjectionWatermark(active, command.datasetVersion)
328
+ assertDeltaBase(active, command)
329
+ }
315
330
  const origin = projectionOrigin(command)
316
331
  throwIfAborted(command.signal)
317
332
  const session = await storage.ontology.materializations.begin({
@@ -478,3 +493,23 @@ function validateProjectionWatermark(
478
493
  if (!active) return
479
494
  assertPinnedDatasetWatermark(active.datasetVersion, next, "Projection replacement")
480
495
  }
496
+
497
+ function assertDeltaBase(
498
+ active: OntologySourceRecord | null,
499
+ command: PreparedProjectionReplacement
500
+ ): void {
501
+ if (
502
+ command.base &&
503
+ (!active ||
504
+ active.materializationId !== command.base.materializationId ||
505
+ active.lastCommitId !== command.base.lastCommitId ||
506
+ active.projectionRevision !== command.runIdentity.projectionRevision ||
507
+ active.ontologyRevision !== command.runIdentity.ontologyRevision ||
508
+ active.ownershipHash !== command.runIdentity.ownershipHash)
509
+ ) {
510
+ throw new MaterializationConflictError(
511
+ "source-materialization",
512
+ "Projection delta no longer matches the active source or its definition."
513
+ )
514
+ }
515
+ }
@@ -1,16 +1,19 @@
1
+ import { MaterializationValidationError } from "../../materialization/errors"
1
2
  import type {
2
3
  PinnedDatasetVersion,
3
4
  ProjectionExecution,
5
+ ProjectionSourceBase,
6
+ ProjectionSourceDeletion,
4
7
  ProjectionSourceEntry,
5
8
  ProjectionSourceRef,
6
9
  } from "../../materialization/model"
7
10
  import { utf8JsonByteLength } from "../../materialization/refs"
8
- import type { StageSourceAssertion } from "../../storage/ontology"
11
+ import type { StageSourceAssertion, StageSourceRoot } from "../../storage/ontology"
9
12
  import type { MaterializerContext } from "../context"
10
13
  import { throwIfAborted } from "../shared/abort"
11
14
  import { chunkBySize } from "../shared/chunking"
12
- import { normalizeProjectionSourceEntry } from "../shared/normalize"
13
- import type { ProjectionEntryValidator } from "./entry-validator"
15
+ import { normalizeProjectionEntity, normalizeProjectionSourceEntry } from "../shared/normalize"
16
+ import { type ProjectionEntryValidator, validateProjectionRootRef } from "./entry-validator"
14
17
 
15
18
  export interface StagedProjectionMaterialization {
16
19
  readonly rootCount: number
@@ -37,7 +40,8 @@ export async function stageProjectionMaterialization(
37
40
  readonly projectionRevision: string
38
41
  readonly ownershipHash: string
39
42
  readonly createdAt: string
40
- readonly entries: AsyncIterable<ProjectionSourceEntry>
43
+ readonly entries: AsyncIterable<ProjectionSourceEntry | ProjectionSourceDeletion>
44
+ readonly base?: ProjectionSourceBase
41
45
  readonly validateEntry: ProjectionEntryValidator
42
46
  readonly signal?: AbortSignal
43
47
  }
@@ -54,13 +58,28 @@ export async function stageProjectionMaterialization(
54
58
  ownershipHash: input.ownershipHash,
55
59
  ontologyRevision: context.projectionRegistry.ontologyRevision,
56
60
  createdAt: input.createdAt,
61
+ ...(input.base ? { base: input.base } : {}),
57
62
  })
58
63
 
59
64
  let rootCount = 0
60
65
  let assertionCount = 0
61
- async function* assertions(): AsyncIterable<StageSourceAssertion> {
66
+ async function* assertions(): AsyncIterable<StageSourceAssertion | StageSourceRoot> {
62
67
  for await (const rawEntry of input.entries) {
63
68
  throwIfAborted(input.signal)
69
+ if ("deleted" in rawEntry) {
70
+ if (!input.base || rawEntry.deleted !== true) {
71
+ throw new MaterializationValidationError(
72
+ "Explicit root deletions require a pinned source base."
73
+ )
74
+ }
75
+ const root = normalizeProjectionEntity(rawEntry.root)
76
+ validateProjectionRootRef(
77
+ context.projectionRegistry.resolveSource(input.source.projectionId),
78
+ root
79
+ )
80
+ yield { root, stagingOrdinal: rootCount++ }
81
+ continue
82
+ }
64
83
  const entry = input.validateEntry(normalizeProjectionSourceEntry(rawEntry))
65
84
  const stagingOrdinal = rootCount
66
85
  for (const assertion of entry.assertions) {
@@ -82,7 +101,8 @@ export async function stageProjectionMaterialization(
82
101
  source: input.source,
83
102
  materializationId: input.materializationId,
84
103
  execution: input.execution,
85
- rows,
104
+ rows: rows.filter((row): row is StageSourceAssertion => "assertion" in row),
105
+ deletions: rows.filter((row) => !("assertion" in row)),
86
106
  })
87
107
  }
88
108
 
@@ -344,7 +344,7 @@ export function normalizeProjectionExecution(input: ProjectionExecution): Projec
344
344
  })
345
345
  }
346
346
 
347
- function normalizeProjectionEntity(entity: ProjectionEntityRef): ProjectionEntityRef {
347
+ export function normalizeProjectionEntity(entity: ProjectionEntityRef): ProjectionEntityRef {
348
348
  return entity.kind === "object"
349
349
  ? Object.freeze({ kind: "object", ref: normalizeObjectRef(entity.ref) })
350
350
  : Object.freeze({ kind: "link", ref: normalizeLinkRef(entity.ref) })
@@ -101,7 +101,10 @@ export async function dispatchPipelineRun(
101
101
  input: DispatchPipelineRunInput
102
102
  ): Promise<PipelineRunRequestResult> {
103
103
  const runId = createPipelineRunId(input.runId)
104
- const persisted = await persistPipelineRun(input, runId)
104
+ let persisted = await persistPipelineRun(input, runId)
105
+ if (!persisted.publish && input.validateExistingRun && persisted.run.status === "queued") {
106
+ persisted = await reusePipelineRun(input, persisted.run)
107
+ }
105
108
  if (!persisted.publish) return existingPipelineRunResult(persisted.run)
106
109
  return publishPipelineRun(input, persisted)
107
110
  }
@@ -155,6 +158,15 @@ async function reusePipelineRun(
155
158
  ): Promise<PersistedPipelineRun> {
156
159
  assertExistingRunMatchesRequest(existing, input)
157
160
  await input.validateExistingRun?.(existing, input.storage)
161
+ if (input.validateExistingRun && existing.status === "queued") {
162
+ const execution = await input.storage.executions.getById({
163
+ projectId: input.projectId,
164
+ id: existing.executionId,
165
+ })
166
+ if (!execution)
167
+ throw new PipelineRunError(`[Sixb] Execution '${existing.executionId}' was not found.`)
168
+ return { publish: true, run: existing, execution, queuedAt: existing.queuedAt, created: false }
169
+ }
158
170
  if (
159
171
  existing.status !== "failed" ||
160
172
  existing.error?.code !== "queue.enqueue_failed" ||
@@ -200,6 +212,11 @@ async function publishPipelineRun(
200
212
  },
201
213
  ],
202
214
  })
215
+ if (!job || job.id !== persisted.run.id) {
216
+ throw new PipelineRunError(
217
+ `[Sixb] Queue did not acknowledge Pipeline run '${persisted.run.id}'.`
218
+ )
219
+ }
203
220
  return {
204
221
  pipelineId: persisted.run.pipelineId,
205
222
  runId: persisted.run.id,
@@ -208,6 +225,9 @@ async function publishPipelineRun(
208
225
  created: persisted.created,
209
226
  }
210
227
  } catch (error) {
228
+ // Automatic consumers retain the batch and retry. Keep uncertain delivery queued:
229
+ // a lost reply does not prove that the queue rejected the job.
230
+ if (input.validateExistingRun) throw error
211
231
  const failedAt = new Date()
212
232
  const failure = toEnqueueFailure(error, persisted.run, failedAt)
213
233
  const failed = await requirePipelineRunStorage(input.storage).finish({
@@ -1,7 +1,4 @@
1
1
  import { randomUUID } from "node:crypto"
2
- import { reportRunFailure } from "../error-reporting/capability"
3
- import { createSixbError, toSixbFailure } from "../errors/internal"
4
- import type { SixbFailure } from "../errors/types"
5
2
  import type { RunDispatcher } from "../execution/dispatch"
6
3
  import { createPrimitiveExecutionRecord } from "../execution/durable"
7
4
  import type { DatasetProducer, LakeStorage } from "../lake-storage"
@@ -17,11 +14,7 @@ import {
17
14
  import { getProjectionRegistry } from "./capability"
18
15
  import { ProjectionValidationError } from "./errors"
19
16
  import { createProjectionRunId } from "./run-id"
20
- import {
21
- PROJECTION_RUN_FAILURE_CODES,
22
- type ProjectionDefinition,
23
- type ProjectionRunFailureCode,
24
- } from "./types"
17
+ import type { ProjectionDefinition } from "./types"
25
18
 
26
19
  /** Target maximum mapped points per telemetry commit; a single-property projection reads 500 rows. */
27
20
  export const PROJECTION_TELEMETRY_BATCH_SIZE = 500
@@ -106,7 +99,7 @@ export class ProjectionRunDispatcher implements ProjectionRunDispatchPort {
106
99
  const runId = createProjectionRunId(this.dependencies.id, identity)
107
100
  const projectionRuns = requireProjectionRunStorage(this.dependencies.storage)
108
101
  const existing = await projectionRuns.getById({ projectId: this.dependencies.id, id: runId })
109
- const persisted = existing
102
+ let persisted = existing
110
103
  ? await reuseProjectionRun(this.dependencies, existing, identity)
111
104
  : await persistProjectionRun({
112
105
  dependencies: this.dependencies,
@@ -120,6 +113,9 @@ export class ProjectionRunDispatcher implements ProjectionRunDispatchPort {
120
113
  ),
121
114
  })
122
115
 
116
+ if (!persisted.publish && persisted.run.status === "queued") {
117
+ persisted = await reuseProjectionRun(this.dependencies, persisted.run, identity)
118
+ }
123
119
  if (!persisted.publish) return existingResult(persisted.run)
124
120
  return publishProjectionRun(this.dependencies, input.metadata, persisted)
125
121
  }
@@ -195,6 +191,9 @@ async function reuseProjectionRun(
195
191
  identity: ProjectionRunRecord["identity"]
196
192
  ): Promise<PersistedProjectionRun> {
197
193
  await assertExistingRun(dependencies.storage, existing, identity)
194
+ if (existing.status === "queued") {
195
+ return { publish: true, run: existing, queuedAt: existing.queuedAt, created: false }
196
+ }
198
197
  if (
199
198
  existing.status !== "failed" ||
200
199
  existing.error?.code !== "queue.enqueue_failed" ||
@@ -228,45 +227,29 @@ async function publishProjectionRun(
228
227
  metadata: Readonly<Record<string, string>> | undefined,
229
228
  persisted: Extract<PersistedProjectionRun, { readonly publish: true }>
230
229
  ): Promise<ProjectionRunDispatchResult> {
231
- try {
232
- const [job] = await dependencies.queues.projections.enqueue({
233
- projectId: dependencies.id,
234
- jobs: [
235
- {
236
- id: persisted.run.id,
237
- type: "projection.run.requested",
238
- payload: { runId: persisted.run.id },
239
- ...(metadata === undefined ? {} : { metadata }),
240
- },
241
- ],
242
- })
243
- return {
244
- projectionId: persisted.run.identity.projectionId,
245
- runId: persisted.run.id,
246
- queuedAt: persisted.queuedAt.toISOString(),
247
- ...(job?.id ? { jobId: job.id } : {}),
248
- created: persisted.created,
249
- }
250
- } catch (error) {
251
- const failedAt = new Date()
252
- const failure = toEnqueueFailure(error, persisted.run, failedAt)
253
- const failed = await requireProjectionRunStorage(dependencies.storage).failEnqueue({
254
- projectId: dependencies.id,
255
- id: persisted.run.id,
256
- finishedAt: failedAt,
257
- error: failure,
258
- })
259
- reportRunFailure(dependencies, error, {
260
- projectId: dependencies.id,
261
- runKind: "projection",
262
- run: {
263
- runId: failed.id,
264
- projectionId: failed.identity.projectionId,
265
- projectionKind: failed.identity.projectionKind,
230
+ // Keep the run queued on uncertain publication; the caller retries the same identity.
231
+ const [job] = await dependencies.queues.projections.enqueue({
232
+ projectId: dependencies.id,
233
+ jobs: [
234
+ {
235
+ id: persisted.run.id,
236
+ type: "projection.run.requested",
237
+ payload: { runId: persisted.run.id },
238
+ ...(metadata === undefined ? {} : { metadata }),
266
239
  },
267
- failure,
268
- })
269
- throw error
240
+ ],
241
+ })
242
+ if (!job || job.id !== persisted.run.id) {
243
+ throw new ProjectionRunError(
244
+ `[Sixb] Queue did not acknowledge Projection run '${persisted.run.id}'.`
245
+ )
246
+ }
247
+ return {
248
+ projectionId: persisted.run.identity.projectionId,
249
+ runId: persisted.run.id,
250
+ queuedAt: persisted.queuedAt.toISOString(),
251
+ ...(job?.id ? { jobId: job.id } : {}),
252
+ created: persisted.created,
270
253
  }
271
254
  }
272
255
 
@@ -478,24 +461,3 @@ function assertDispatchInput(input: ProjectionRunDispatchInput): void {
478
461
  throw new ProjectionValidationError("[Sixb] Projection dataset version timestamp is invalid.")
479
462
  }
480
463
  }
481
-
482
- function toEnqueueFailure(
483
- error: unknown,
484
- run: ProjectionRunRecord,
485
- at: Date
486
- ): SixbFailure<ProjectionRunFailureCode> {
487
- const enqueueError = createSixbError(
488
- "queue.enqueue_failed",
489
- `[Sixb] Could not enqueue Projection run '${run.id}'.`,
490
- {
491
- cause: error,
492
- details: {
493
- projectionId: run.identity.projectionId,
494
- projectionKind: run.identity.projectionKind,
495
- runId: run.id,
496
- phase: "enqueue",
497
- },
498
- }
499
- )
500
- return toSixbFailure(enqueueError, { allowedCodes: PROJECTION_RUN_FAILURE_CODES, at })
501
- }
@@ -1,6 +1,7 @@
1
1
  import { randomUUID } from "node:crypto"
2
2
  import type { SixbErrorCode } from "../errors/types"
3
3
  import { QueueError } from "./errors"
4
+ import { IndexedHeap } from "./indexed-heap"
4
5
  import type {
5
6
  ActionQueueJobFailureCode,
6
7
  ActionRunRequestedQueueJob,
@@ -29,6 +30,9 @@ interface QueueRecord<TQueueJob extends QueueJob = QueueJob> {
29
30
  readonly sequence: number
30
31
  readonly queueId: string
31
32
  job: TQueueJob
33
+ availableAtMs: number
34
+ readonly createdAtMs: number
35
+ leaseExpiryMs: number | null
32
36
  // A leased job stays "queued" until a worker completes or fails it.
33
37
  state: QueueRecordState
34
38
  failure: QueueJobFailure | null
@@ -63,25 +67,15 @@ function clearLease(record: QueueRecord): void {
63
67
  record.leaseId = null
64
68
  record.claimedAt = null
65
69
  record.leaseExpiresAt = null
70
+ record.leaseExpiryMs = null
66
71
  }
67
72
 
68
73
  // Best-effort ordering is part of the contract; this keeps the in-memory provider deterministic.
69
74
  function compareQueueRecords(left: QueueRecord, right: QueueRecord): number {
70
- const availableDifference =
71
- parseTimestamp(left.job.availableAt, "job.availableAt") -
72
- parseTimestamp(right.job.availableAt, "job.availableAt")
73
-
74
- if (availableDifference !== 0) {
75
- return availableDifference
76
- }
77
-
78
- const createdDifference =
79
- parseTimestamp(left.job.createdAt, "job.createdAt") -
80
- parseTimestamp(right.job.createdAt, "job.createdAt")
81
-
82
- if (createdDifference !== 0) {
83
- return createdDifference
84
- }
75
+ const availableDifference = left.availableAtMs - right.availableAtMs
76
+ if (availableDifference !== 0) return availableDifference
77
+ const createdDifference = left.createdAtMs - right.createdAtMs
78
+ if (createdDifference !== 0) return createdDifference
85
79
 
86
80
  return left.sequence - right.sequence
87
81
  }
@@ -107,11 +101,18 @@ function createQueueJob<TQueueJob extends QueueJob>(
107
101
  }
108
102
 
109
103
  function leaseExpiresAtMs(record: QueueRecord): number | null {
110
- if (!record.leaseId || !record.leaseExpiresAt) {
111
- return null
112
- }
104
+ return record.leaseExpiryMs
105
+ }
113
106
 
114
- return parseTimestamp(record.leaseExpiresAt, "leaseExpiresAt")
107
+ function eligibleAt(record: QueueRecord): number {
108
+ return Math.max(record.availableAtMs, record.leaseExpiryMs ?? -Infinity)
109
+ }
110
+
111
+ interface QueueLane {
112
+ // Terminal records remain here for caller-id deduplication, never in the delivery indexes.
113
+ readonly records: Map<string, QueueRecord>
114
+ readonly ready: IndexedHeap<QueueRecord>
115
+ readonly waiting: IndexedHeap<QueueRecord>
115
116
  }
116
117
 
117
118
  function toClaimedQueueJob<TQueueJob extends QueueJob>(
@@ -130,27 +131,26 @@ function toClaimedQueueJob<TQueueJob extends QueueJob>(
130
131
  }
131
132
 
132
133
  class InMemoryQueueStore {
133
- private readonly recordsByProject = new Map<string, QueueRecord[]>()
134
+ private readonly projects = new Map<string, Map<string, QueueLane>>()
134
135
  private nextSequence = 1
135
136
 
136
137
  add<TQueueJob extends QueueJob>(projectId: string, queueId: string, job: TQueueJob): void {
137
- const records = this.getOrCreateRecords(projectId)
138
- records.push({
139
- sequence: this.nextSequence,
138
+ const lane = this.lane(projectId, queueId)
139
+ const record: QueueRecord = {
140
+ sequence: this.nextSequence++,
140
141
  queueId,
141
142
  job,
143
+ availableAtMs: parseTimestamp(job.availableAt, "job.availableAt"),
144
+ createdAtMs: parseTimestamp(job.createdAt, "job.createdAt"),
145
+ leaseExpiryMs: null,
142
146
  state: "queued",
143
147
  failure: null,
144
148
  leaseId: null,
145
149
  claimedAt: null,
146
150
  leaseExpiresAt: null,
147
- })
148
- this.nextSequence += 1
149
- }
150
-
151
- list<TQueueJob extends QueueJob>(projectId: string, queueId: string): QueueRecord<TQueueJob>[] {
152
- const records = this.recordsByProject.get(projectId) ?? []
153
- return records.filter((record) => record.queueId === queueId) as QueueRecord<TQueueJob>[]
151
+ }
152
+ lane.records.set(job.id, record)
153
+ this.schedule(projectId, record, Date.now())
154
154
  }
155
155
 
156
156
  find<TQueueJob extends QueueJob>(
@@ -158,20 +158,67 @@ class InMemoryQueueStore {
158
158
  queueId: string,
159
159
  jobId: string
160
160
  ): QueueRecord<TQueueJob> | null {
161
- return (
162
- this.list<TQueueJob>(projectId, queueId).find((record) => record.job.id === jobId) ?? null
163
- )
161
+ return (this.projects.get(projectId)?.get(queueId)?.records.get(jobId) ??
162
+ null) as QueueRecord<TQueueJob> | null
164
163
  }
165
164
 
166
- private getOrCreateRecords(projectId: string): QueueRecord[] {
167
- const existing = this.recordsByProject.get(projectId)
168
- if (existing) {
169
- return existing
165
+ schedule(projectId: string, record: QueueRecord, now: number): void {
166
+ const lane = this.lane(projectId, record.queueId)
167
+ if (record.state !== "queued") {
168
+ lane.ready.delete(record)
169
+ lane.waiting.delete(record)
170
+ } else if (eligibleAt(record) <= now) {
171
+ lane.waiting.delete(record)
172
+ lane.ready.set(record)
173
+ } else {
174
+ lane.ready.delete(record)
175
+ lane.waiting.set(record)
176
+ }
177
+ }
178
+
179
+ takeReady<TQueueJob extends QueueJob>(
180
+ projectId: string,
181
+ queueId: string,
182
+ now: number,
183
+ limit: number
184
+ ): QueueRecord<TQueueJob>[] {
185
+ const lane = this.projects.get(projectId)?.get(queueId)
186
+ if (!lane) return []
187
+ // Wake delayed jobs and expired leases, then order all ready jobs by the original contract.
188
+ // A lease expiry controls eligibility, not the priority of a redelivered job.
189
+ while (lane.waiting.peek() && eligibleAt(lane.waiting.peek()!) <= now) {
190
+ lane.ready.set(lane.waiting.pop()!)
170
191
  }
192
+ const selected: QueueRecord[] = []
193
+ while (selected.length < limit) {
194
+ const record = lane.ready.peek()
195
+ if (!record || record.availableAtMs > now) break
196
+ lane.ready.pop()
197
+ // A backwards wall-clock adjustment can make a previously expired lease live again.
198
+ if (eligibleAt(record) > now) lane.waiting.set(record)
199
+ else selected.push(record)
200
+ }
201
+ return selected as QueueRecord<TQueueJob>[]
202
+ }
171
203
 
172
- const records: QueueRecord[] = []
173
- this.recordsByProject.set(projectId, records)
174
- return records
204
+ private lane(projectId: string, queueId: string): QueueLane {
205
+ let queues = this.projects.get(projectId)
206
+ if (!queues) {
207
+ queues = new Map()
208
+ this.projects.set(projectId, queues)
209
+ }
210
+ let lane = queues.get(queueId)
211
+ if (!lane) {
212
+ lane = {
213
+ records: new Map(),
214
+ ready: new IndexedHeap(compareQueueRecords),
215
+ waiting: new IndexedHeap(
216
+ (left, right) => eligibleAt(left) - eligibleAt(right) || left.sequence - right.sequence
217
+ ),
218
+ }
219
+ queues.set(queueId, lane)
220
+ }
221
+ return lane
175
222
  }
176
223
  }
177
224
 
@@ -219,8 +266,8 @@ class InMemoryQueue<TQueueJob extends QueueJob, TFailureCode extends SixbErrorCo
219
266
  assertNonEmpty(params.projectId, "projectId")
220
267
  assertNonEmpty(params.workerId, "workerId")
221
268
 
222
- const limit = params.limit ?? 1
223
- if (limit <= 0) {
269
+ const limit = Math.trunc(params.limit ?? 1)
270
+ if (!(limit > 0)) {
224
271
  return []
225
272
  }
226
273
 
@@ -228,24 +275,11 @@ class InMemoryQueue<TQueueJob extends QueueJob, TFailureCode extends SixbErrorCo
228
275
  assertPositiveNumber(leaseMs, "leaseMs")
229
276
 
230
277
  const now = Date.now()
231
- const claimable = this.store
232
- .list<TQueueJob>(params.projectId, this.queueId)
233
- .filter((record) => {
234
- if (record.state !== "queued") {
235
- return false
236
- }
237
-
238
- if (parseTimestamp(record.job.availableAt, "job.availableAt") > now) {
239
- return false
240
- }
241
-
242
- const expiresAt = leaseExpiresAtMs(record)
243
- return expiresAt === null || expiresAt <= now
244
- })
245
- .sort(compareQueueRecords)
246
- .slice(0, limit)
247
-
248
278
  const claimedAt = new Date(now).toISOString()
279
+ // Validate before removing candidates, and cache the same millisecond precision we return.
280
+ const leaseExpiry = new Date(now + leaseMs)
281
+ const leaseExpiresAt = leaseExpiry.toISOString()
282
+ const claimable = this.store.takeReady<TQueueJob>(params.projectId, this.queueId, now, limit)
249
283
 
250
284
  return claimable.map((record) => {
251
285
  // Attempts count claims so redelivery after lease expiry or retry is visible to workers.
@@ -255,7 +289,9 @@ class InMemoryQueue<TQueueJob extends QueueJob, TFailureCode extends SixbErrorCo
255
289
  }
256
290
  record.leaseId = randomUUID()
257
291
  record.claimedAt = claimedAt
258
- record.leaseExpiresAt = new Date(now + leaseMs).toISOString()
292
+ record.leaseExpiresAt = leaseExpiresAt
293
+ record.leaseExpiryMs = leaseExpiry.getTime()
294
+ this.store.schedule(params.projectId, record, now)
259
295
 
260
296
  return toClaimedQueueJob(record)
261
297
  })
@@ -265,6 +301,7 @@ class InMemoryQueue<TQueueJob extends QueueJob, TFailureCode extends SixbErrorCo
265
301
  const record = this.requireActiveLease(params)
266
302
  record.state = "completed"
267
303
  clearLease(record)
304
+ this.store.schedule(params.projectId, record, Date.now())
268
305
  }
269
306
 
270
307
  async retry(params: {
@@ -275,13 +312,15 @@ class InMemoryQueue<TQueueJob extends QueueJob, TFailureCode extends SixbErrorCo
275
312
  }): Promise<void> {
276
313
  const record = this.requireActiveLease(params)
277
314
  const availableAt = params.availableAt ?? new Date().toISOString()
278
- parseTimestamp(availableAt, "availableAt")
315
+ const availableAtMs = parseTimestamp(availableAt, "availableAt")
279
316
 
317
+ record.availableAtMs = availableAtMs
280
318
  record.job = {
281
319
  ...record.job,
282
320
  availableAt,
283
321
  }
284
322
  clearLease(record)
323
+ this.store.schedule(params.projectId, record, Date.now())
285
324
  }
286
325
 
287
326
  async fail(params: {
@@ -294,6 +333,7 @@ class InMemoryQueue<TQueueJob extends QueueJob, TFailureCode extends SixbErrorCo
294
333
  record.state = "failed"
295
334
  record.failure = structuredClone(params.failure)
296
335
  clearLease(record)
336
+ this.store.schedule(params.projectId, record, Date.now())
297
337
  }
298
338
 
299
339
  async renewLease(params: {
@@ -319,7 +359,10 @@ class InMemoryQueue<TQueueJob extends QueueJob, TFailureCode extends SixbErrorCo
319
359
  return null
320
360
  }
321
361
 
322
- record.leaseExpiresAt = new Date(now + params.leaseMs).toISOString()
362
+ const leaseExpiry = new Date(now + params.leaseMs)
363
+ record.leaseExpiresAt = leaseExpiry.toISOString()
364
+ record.leaseExpiryMs = leaseExpiry.getTime()
365
+ this.store.schedule(params.projectId, record, now)
323
366
  return toClaimedQueueJob(record)
324
367
  }
325
368