@sixb/core 0.1.13 → 0.1.15

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 (236) hide show
  1. package/dist/actions/index.js +22 -22
  2. package/dist/agents/index.js +34 -34
  3. package/dist/agents/index.js.map +1 -1
  4. package/dist/agents/streams/index.js +2 -2
  5. package/dist/agents/streams/internal.js +2 -2
  6. package/dist/auth/errors.d.ts +12 -0
  7. package/dist/auth/errors.d.ts.map +1 -1
  8. package/dist/auth/index.d.ts +1 -1
  9. package/dist/auth/index.d.ts.map +1 -1
  10. package/dist/auth/index.js +7 -5
  11. package/dist/auth/index.js.map +1 -1
  12. package/dist/auth/runtime.d.ts.map +1 -1
  13. package/dist/auth/strategy.d.ts +2 -0
  14. package/dist/auth/strategy.d.ts.map +1 -1
  15. package/dist/auth/strategy.js +4 -2
  16. package/dist/auth/strategy.js.map +1 -1
  17. package/dist/auth/types.d.ts +8 -0
  18. package/dist/auth/types.d.ts.map +1 -1
  19. package/dist/authorization/index.js +2 -2
  20. package/dist/bootstrap/index.js +37 -37
  21. package/dist/{chunk-693f3zn8.js → chunk-1jvq82rp.js} +3 -3
  22. package/dist/{chunk-0321g649.js → chunk-2jntasmh.js} +13 -2
  23. package/dist/{chunk-0321g649.js.map → chunk-2jntasmh.js.map} +3 -3
  24. package/dist/{chunk-cvrbhvtr.js → chunk-2tz5vnvz.js} +28 -28
  25. package/dist/{chunk-cvrbhvtr.js.map → chunk-2tz5vnvz.js.map} +1 -1
  26. package/dist/{chunk-898y24dd.js → chunk-3c90jvfj.js} +2 -2
  27. package/dist/{chunk-vn3yh259.js → chunk-4gmng3vf.js} +1 -1
  28. package/dist/{chunk-91wthfwe.js → chunk-4jph1t3v.js} +7 -7
  29. package/dist/{chunk-znw0zvy4.js → chunk-4wn7bxxa.js} +25 -7
  30. package/dist/chunk-4wn7bxxa.js.map +12 -0
  31. package/dist/{chunk-3qpcrcj8.js → chunk-6aawea28.js} +2 -4
  32. package/dist/chunk-6aawea28.js.map +10 -0
  33. package/dist/{chunk-rzkr70gr.js → chunk-6fsr7y93.js} +5 -1
  34. package/dist/{chunk-rzkr70gr.js.map → chunk-6fsr7y93.js.map} +3 -3
  35. package/dist/{chunk-y487ywrv.js → chunk-6q7c68yx.js} +4 -4
  36. package/dist/{chunk-ypj2wqtj.js → chunk-70jmg2zx.js} +2 -2
  37. package/dist/{chunk-s24qbp32.js → chunk-7jc21943.js} +3 -3
  38. package/dist/{chunk-rzcjp5m2.js → chunk-8fjmqjyx.js} +1 -1
  39. package/dist/{chunk-m8zkvep1.js → chunk-8w27xf9e.js} +1 -1
  40. package/dist/{chunk-z9h698s8.js → chunk-a4g5yjm0.js} +1 -1
  41. package/dist/{chunk-gmt22nxx.js → chunk-adbve9zt.js} +1 -1
  42. package/dist/{chunk-mf6hwpzh.js → chunk-dqkd3kdj.js} +41 -23
  43. package/dist/{chunk-mf6hwpzh.js.map → chunk-dqkd3kdj.js.map} +3 -3
  44. package/dist/{chunk-gbcrz0gr.js → chunk-ex8s8wsk.js} +6 -6
  45. package/dist/{chunk-4tnrzqqh.js → chunk-fj1dvp37.js} +1 -1
  46. package/dist/{chunk-gc226ten.js → chunk-fjw417jf.js} +127 -69
  47. package/dist/{chunk-gc226ten.js.map → chunk-fjw417jf.js.map} +4 -4
  48. package/dist/{chunk-ht4a7mjg.js → chunk-g4rbm4a6.js} +42 -8
  49. package/dist/{chunk-ht4a7mjg.js.map → chunk-g4rbm4a6.js.map} +3 -3
  50. package/dist/{chunk-qma80ych.js → chunk-hn2r7z41.js} +3 -2
  51. package/dist/chunk-hn2r7z41.js.map +11 -0
  52. package/dist/{chunk-bdhfn0ww.js → chunk-kkqwzy6h.js} +1 -1
  53. package/dist/{chunk-bep5rrdq.js → chunk-mmac1mp6.js} +2 -2
  54. package/dist/{chunk-w9bp61e7.js → chunk-n5t4mq6z.js} +5 -5
  55. package/dist/{chunk-n33e8dz8.js → chunk-p0nbkys4.js} +1 -1
  56. package/dist/{chunk-04h4t2b5.js → chunk-py2dw8cw.js} +3 -3
  57. package/dist/{chunk-jhyc777j.js → chunk-q3grgzp1.js} +2 -2
  58. package/dist/{chunk-9pfw47qx.js → chunk-qne45xqq.js} +53 -48
  59. package/dist/{chunk-9pfw47qx.js.map → chunk-qne45xqq.js.map} +7 -7
  60. package/dist/{chunk-cbn96bve.js → chunk-r5sc1d2b.js} +2 -2
  61. package/dist/{chunk-y6vnvayj.js → chunk-vjcd8d27.js} +1 -1
  62. package/dist/{chunk-jmwy25mf.js → chunk-wd34wnsy.js} +6 -6
  63. package/dist/{chunk-63fp9x3q.js → chunk-wem5vqjw.js} +7 -7
  64. package/dist/{chunk-darm7eeg.js → chunk-xr7y35yj.js} +3 -3
  65. package/dist/{chunk-a69fkrtr.js → chunk-y0zmae99.js} +5 -5
  66. package/dist/{chunk-xgptywp0.js → chunk-y2aq6ag9.js} +1 -1
  67. package/dist/{chunk-2hbhn1ff.js → chunk-yfppyaty.js} +1 -1
  68. package/dist/{chunk-rqtdtm43.js → chunk-z708gemf.js} +129 -70
  69. package/dist/{chunk-rqtdtm43.js.map → chunk-z708gemf.js.map} +5 -5
  70. package/dist/deploy/define.d.ts +4 -0
  71. package/dist/deploy/define.d.ts.map +1 -0
  72. package/dist/deploy/index.d.ts +3 -0
  73. package/dist/deploy/index.d.ts.map +1 -0
  74. package/dist/deploy/index.js +10 -0
  75. package/dist/deploy/index.js.map +10 -0
  76. package/dist/deploy/internal.d.ts +3 -0
  77. package/dist/deploy/internal.d.ts.map +1 -0
  78. package/dist/deploy/internal.js +244 -0
  79. package/dist/deploy/internal.js.map +11 -0
  80. package/dist/deploy/services.d.ts +8 -0
  81. package/dist/deploy/services.d.ts.map +1 -0
  82. package/dist/deploy/types.d.ts +309 -0
  83. package/dist/deploy/types.d.ts.map +1 -0
  84. package/dist/deploy/validation.d.ts +7 -0
  85. package/dist/deploy/validation.d.ts.map +1 -0
  86. package/dist/error-reporting/internal.js +3 -3
  87. package/dist/errors/catalog.d.ts +4 -0
  88. package/dist/errors/catalog.d.ts.map +1 -1
  89. package/dist/errors/internal.js +1 -1
  90. package/dist/events/index.js +3 -3
  91. package/dist/execution/agent.js +33 -33
  92. package/dist/execution/primitive.js +29 -29
  93. package/dist/execution/request.js +31 -31
  94. package/dist/execution/request.js.map +1 -1
  95. package/dist/index.js +49 -49
  96. package/dist/index.js.map +1 -1
  97. package/dist/maintenance/ontology-maintenance.d.ts +2 -0
  98. package/dist/maintenance/ontology-maintenance.d.ts.map +1 -1
  99. package/dist/maintenance/types.d.ts +8 -2
  100. package/dist/maintenance/types.d.ts.map +1 -1
  101. package/dist/materialization/dataset-watermark.d.ts +6 -1
  102. package/dist/materialization/dataset-watermark.d.ts.map +1 -1
  103. package/dist/materialization/index.js +1 -1
  104. package/dist/materialization/model.d.ts +6 -0
  105. package/dist/materialization/model.d.ts.map +1 -1
  106. package/dist/materializer/index.js +18 -18
  107. package/dist/materializer/index.js.map +1 -1
  108. package/dist/materializer/projections/finish-run.d.ts.map +1 -1
  109. package/dist/materializer/projections/replace.d.ts.map +1 -1
  110. package/dist/materializer/projections/source-materialization.d.ts +6 -2
  111. package/dist/materializer/projections/source-materialization.d.ts.map +1 -1
  112. package/dist/models/execution/index.js +14 -14
  113. package/dist/objects/index.js +21 -21
  114. package/dist/objects/query/index.js +15 -15
  115. package/dist/pipelines/index.js +19 -19
  116. package/dist/projections/internal.js +16 -16
  117. package/dist/projections/types.d.ts +1 -1
  118. package/dist/projections/types.d.ts.map +1 -1
  119. package/dist/queues/in-memory.d.ts +1 -1
  120. package/dist/queues/in-memory.d.ts.map +1 -1
  121. package/dist/runtime/internal.js +19 -19
  122. package/dist/shares/internal.js +14 -14
  123. package/dist/shares/internal.js.map +1 -1
  124. package/dist/storage/action-runs/failure.js +2 -2
  125. package/dist/storage/ai-limits/enforcement.js +2 -2
  126. package/dist/storage/auth/errors.d.ts +1 -1
  127. package/dist/storage/auth/errors.d.ts.map +1 -1
  128. package/dist/storage/auth/in-memory/auth-storage.d.ts +2 -1
  129. package/dist/storage/auth/in-memory/auth-storage.d.ts.map +1 -1
  130. package/dist/storage/auth/types.d.ts +15 -7
  131. package/dist/storage/auth/types.d.ts.map +1 -1
  132. package/dist/storage/connector-connections/provider.js +1 -1
  133. package/dist/storage/file-upload-sessions/provider.js +5 -5
  134. package/dist/storage/index.d.ts +2 -2
  135. package/dist/storage/index.d.ts.map +1 -1
  136. package/dist/storage/index.js +29 -29
  137. package/dist/storage/index.js.map +1 -1
  138. package/dist/storage/internal.js +1 -1
  139. package/dist/storage/ontology/in-memory/sources.d.ts +6 -3
  140. package/dist/storage/ontology/in-memory/sources.d.ts.map +1 -1
  141. package/dist/storage/ontology/index.d.ts +1 -1
  142. package/dist/storage/ontology/index.d.ts.map +1 -1
  143. package/dist/storage/ontology/provider.d.ts +16 -3
  144. package/dist/storage/ontology/provider.d.ts.map +1 -1
  145. package/dist/storage/ontology/provider.js +8 -4
  146. package/dist/storage/ontology/provider.js.map +1 -1
  147. package/dist/storage/ontology/sources.d.ts +36 -8
  148. package/dist/storage/ontology/sources.d.ts.map +1 -1
  149. package/dist/storage/projection-runs/in-memory.d.ts +3 -1
  150. package/dist/storage/projection-runs/in-memory.d.ts.map +1 -1
  151. package/dist/storage/projection-runs/index.d.ts +1 -1
  152. package/dist/storage/projection-runs/index.d.ts.map +1 -1
  153. package/dist/storage/projection-runs/lifecycle.d.ts +12 -0
  154. package/dist/storage/projection-runs/lifecycle.d.ts.map +1 -1
  155. package/dist/storage/projection-runs/provider.js +10 -4
  156. package/dist/storage/projection-runs/provider.js.map +1 -1
  157. package/dist/storage/projection-runs/types.d.ts +32 -1
  158. package/dist/storage/projection-runs/types.d.ts.map +1 -1
  159. package/dist/storage/workflow-runs/provider.js +5 -5
  160. package/dist/syncs/index.js +20 -20
  161. package/dist/testing/auth-storage-contract.d.ts.map +1 -1
  162. package/dist/testing/index.js +381 -171
  163. package/dist/testing/index.js.map +5 -5
  164. package/dist/testing/ontology-storage-contract.d.ts.map +1 -1
  165. package/dist/testing/projection-run-storage-contract.d.ts.map +1 -1
  166. package/dist/webhooks/internal.js +15 -15
  167. package/dist/webhooks/internal.js.map +1 -1
  168. package/dist/workers/index.js +1 -1
  169. package/dist/workflows/index.js +34 -34
  170. package/package.json +13 -1
  171. package/src/auth/errors.ts +23 -0
  172. package/src/auth/index.ts +1 -1
  173. package/src/auth/runtime.ts +56 -22
  174. package/src/auth/strategy.ts +2 -0
  175. package/src/auth/types.ts +8 -0
  176. package/src/deploy/define.ts +6 -0
  177. package/src/deploy/index.ts +41 -0
  178. package/src/deploy/internal.ts +7 -0
  179. package/src/deploy/services.ts +51 -0
  180. package/src/deploy/types.ts +338 -0
  181. package/src/deploy/validation.ts +261 -0
  182. package/src/errors/catalog.ts +4 -0
  183. package/src/maintenance/ontology-maintenance.ts +32 -2
  184. package/src/maintenance/types.ts +8 -2
  185. package/src/materialization/dataset-watermark.ts +6 -4
  186. package/src/materialization/model.ts +7 -0
  187. package/src/materializer/README.md +26 -6
  188. package/src/materializer/projections/finish-run.ts +129 -26
  189. package/src/materializer/projections/replace.ts +67 -49
  190. package/src/materializer/projections/source-materialization.ts +11 -6
  191. package/src/projections/types.ts +1 -0
  192. package/src/storage/auth/errors.ts +0 -1
  193. package/src/storage/auth/in-memory/auth-storage.ts +44 -65
  194. package/src/storage/auth/types.ts +15 -7
  195. package/src/storage/index.ts +6 -1
  196. package/src/storage/ontology/in-memory/sources.ts +119 -55
  197. package/src/storage/ontology/index.ts +4 -1
  198. package/src/storage/ontology/provider.ts +64 -7
  199. package/src/storage/ontology/sources.ts +42 -8
  200. package/src/storage/projection-runs/in-memory.ts +41 -2
  201. package/src/storage/projection-runs/index.ts +2 -0
  202. package/src/storage/projection-runs/lifecycle.ts +61 -4
  203. package/src/storage/projection-runs/types.ts +41 -1
  204. package/src/testing/auth-storage-contract.ts +104 -130
  205. package/src/testing/ontology-storage-contract.ts +141 -15
  206. package/src/testing/projection-run-storage-contract.ts +174 -1
  207. package/dist/chunk-3qpcrcj8.js.map +0 -10
  208. package/dist/chunk-qma80ych.js.map +0 -11
  209. package/dist/chunk-znw0zvy4.js.map +0 -12
  210. /package/dist/{chunk-693f3zn8.js.map → chunk-1jvq82rp.js.map} +0 -0
  211. /package/dist/{chunk-898y24dd.js.map → chunk-3c90jvfj.js.map} +0 -0
  212. /package/dist/{chunk-vn3yh259.js.map → chunk-4gmng3vf.js.map} +0 -0
  213. /package/dist/{chunk-91wthfwe.js.map → chunk-4jph1t3v.js.map} +0 -0
  214. /package/dist/{chunk-y487ywrv.js.map → chunk-6q7c68yx.js.map} +0 -0
  215. /package/dist/{chunk-ypj2wqtj.js.map → chunk-70jmg2zx.js.map} +0 -0
  216. /package/dist/{chunk-s24qbp32.js.map → chunk-7jc21943.js.map} +0 -0
  217. /package/dist/{chunk-rzcjp5m2.js.map → chunk-8fjmqjyx.js.map} +0 -0
  218. /package/dist/{chunk-m8zkvep1.js.map → chunk-8w27xf9e.js.map} +0 -0
  219. /package/dist/{chunk-z9h698s8.js.map → chunk-a4g5yjm0.js.map} +0 -0
  220. /package/dist/{chunk-gmt22nxx.js.map → chunk-adbve9zt.js.map} +0 -0
  221. /package/dist/{chunk-gbcrz0gr.js.map → chunk-ex8s8wsk.js.map} +0 -0
  222. /package/dist/{chunk-4tnrzqqh.js.map → chunk-fj1dvp37.js.map} +0 -0
  223. /package/dist/{chunk-bdhfn0ww.js.map → chunk-kkqwzy6h.js.map} +0 -0
  224. /package/dist/{chunk-bep5rrdq.js.map → chunk-mmac1mp6.js.map} +0 -0
  225. /package/dist/{chunk-w9bp61e7.js.map → chunk-n5t4mq6z.js.map} +0 -0
  226. /package/dist/{chunk-n33e8dz8.js.map → chunk-p0nbkys4.js.map} +0 -0
  227. /package/dist/{chunk-04h4t2b5.js.map → chunk-py2dw8cw.js.map} +0 -0
  228. /package/dist/{chunk-jhyc777j.js.map → chunk-q3grgzp1.js.map} +0 -0
  229. /package/dist/{chunk-cbn96bve.js.map → chunk-r5sc1d2b.js.map} +0 -0
  230. /package/dist/{chunk-y6vnvayj.js.map → chunk-vjcd8d27.js.map} +0 -0
  231. /package/dist/{chunk-jmwy25mf.js.map → chunk-wd34wnsy.js.map} +0 -0
  232. /package/dist/{chunk-63fp9x3q.js.map → chunk-wem5vqjw.js.map} +0 -0
  233. /package/dist/{chunk-darm7eeg.js.map → chunk-xr7y35yj.js.map} +0 -0
  234. /package/dist/{chunk-a69fkrtr.js.map → chunk-y0zmae99.js.map} +0 -0
  235. /package/dist/{chunk-xgptywp0.js.map → chunk-y2aq6ag9.js.map} +0 -0
  236. /package/dist/{chunk-2hbhn1ff.js.map → chunk-yfppyaty.js.map} +0 -0
@@ -128,7 +128,7 @@ interface BaseAbandonSourceMaterializationInput {
128
128
  readonly abandonedAt: string
129
129
  }
130
130
 
131
- /** Abandon the current execution's exact candidate after ingress or semantic failure. */
131
+ /** Abandon the current execution's exact candidate, e.g. an adopted one whose delta base moved. */
132
132
  export interface AbandonSourceMaterializationCandidateInput
133
133
  extends BaseAbandonSourceMaterializationInput {
134
134
  readonly kind: "candidate"
@@ -136,20 +136,38 @@ export interface AbandonSourceMaterializationCandidateInput
136
136
  }
137
137
 
138
138
  /**
139
- * Reclaim a logical run after redelivery. The current execution may abandon the prior execution's
140
- * staging/ready candidate, but never one already owned by its own token.
139
+ * Release whatever candidate a run still holds, whichever execution staged it. Called in the same
140
+ * transaction as the run's terminal transition, so no candidate outlives its run.
141
141
  */
142
- export interface ReclaimSourceMaterializationInput extends BaseAbandonSourceMaterializationInput {
143
- readonly kind: "reclaim"
142
+ export interface AbandonRunSourceMaterializationInput
143
+ extends BaseAbandonSourceMaterializationInput {
144
+ readonly kind: "run"
144
145
  }
145
146
 
146
147
  export type AbandonSourceMaterializationInput =
147
148
  | AbandonSourceMaterializationCandidateInput
148
- | ReclaimSourceMaterializationInput
149
+ | AbandonRunSourceMaterializationInput
150
+
151
+ export interface AdoptSourceMaterializationInput {
152
+ readonly projectId: string
153
+ readonly source: ProjectionSourceRef
154
+ readonly execution: ProjectionExecution
155
+ readonly adoptedAt: string
156
+ }
157
+
158
+ export interface AdoptedSourceMaterialization {
159
+ readonly record: OntologySourceRecord
160
+ /**
161
+ * Where staging resumes. Every root before this ordinal is complete; the root at it may be
162
+ * partial and is staged again, which staging accepts for identical rows. Equals the root count
163
+ * once the candidate is ready.
164
+ */
165
+ readonly resumeStagingOrdinal: number
166
+ }
149
167
 
150
168
  export interface CleanupTerminalSourceMaterializationsInput {
151
169
  readonly projectId: string
152
- /** Exclusive cutoff for retired roots in terminal manifests, and for empty terminal manifests. */
170
+ /** Exclusive cutoff for retired roots in superseded manifests, and for empty ones. */
153
171
  readonly terminalBefore: string
154
172
  /** Maximum total assertion, root-reference, and manifest deletions performed by one call. */
155
173
  readonly limit: number
@@ -161,6 +179,12 @@ export interface CleanupTerminalSourceMaterializationsResult {
161
179
  readonly materializationsDeleted: number
162
180
  }
163
181
 
182
+ export interface PurgeAbandonedSourceMaterializationsInput {
183
+ readonly projectId: string
184
+ /** Maximum total assertion, root-reference, and manifest deletions performed by one call. */
185
+ readonly limit: number
186
+ }
187
+
164
188
  export interface SummarizeTerminalSourceMaterializationsInput {
165
189
  readonly projectId: string
166
190
  }
@@ -195,11 +219,21 @@ export interface OntologySourceStorage {
195
219
  stageRows(input: StageSourceRowsInput): Promise<StageSourceRowsResult>
196
220
  markReady(input: MarkSourceMaterializationReadyInput): Promise<OntologySourceRecord>
197
221
  getActive(input: GetActiveOntologySourceInput): Promise<OntologySourceRecord | null>
222
+ /** Hands a redelivered run's candidate to its current execution; null when it has none. */
223
+ adopt(input: AdoptSourceMaterializationInput): Promise<AdoptedSourceMaterialization | null>
198
224
  abandon(input: AbandonSourceMaterializationCandidateInput): Promise<OntologySourceRecord>
199
- abandon(input: ReclaimSourceMaterializationInput): Promise<OntologySourceRecord | null>
225
+ abandon(input: AbandonRunSourceMaterializationInput): Promise<OntologySourceRecord | null>
226
+ /** Retired roots of superseded manifests, after their retention. */
200
227
  cleanupTerminal(
201
228
  input: CleanupTerminalSourceMaterializationsInput
202
229
  ): Promise<CleanupTerminalSourceMaterializationsResult>
230
+ /**
231
+ * Abandoned candidates, whole and oldest first. Nothing reads an abandoned candidate, so it has
232
+ * no retention; deleting one manifest at a time keeps each delete on the source-version index.
233
+ */
234
+ purgeAbandoned(
235
+ input: PurgeAbandonedSourceMaterializationsInput
236
+ ): Promise<CleanupTerminalSourceMaterializationsResult>
203
237
  summarizeTerminal(
204
238
  input: SummarizeTerminalSourceMaterializationsInput
205
239
  ): Promise<TerminalSourceMaterializationSummary>
@@ -26,8 +26,10 @@ import {
26
26
  finishProjectionRunRecord,
27
27
  immutableDatasetVersionConflict,
28
28
  mergeProjectionRunProgress,
29
+ parseProjectionAttemptFailure,
29
30
  planProjectionRunReclaim,
30
31
  projectionRunNotFound,
32
+ projectionRunSupersedes,
31
33
  publicProjectionRunRecord,
32
34
  requireTelemetryProjectionRun,
33
35
  type StoredProjectionRunRecord,
@@ -36,6 +38,7 @@ import {
36
38
  import {
37
39
  type AdvanceProjectionTelemetryCheckpointInput,
38
40
  type FailProjectionRunEnqueueInput,
41
+ type FindSupersedingProjectionRunInput,
39
42
  type FinishProjectionRunInput,
40
43
  type ListLatestProjectionRunsInput,
41
44
  type ListLatestProjectionRunsResult,
@@ -47,6 +50,7 @@ import {
47
50
  type ProjectionRunStorage,
48
51
  projectionRunObjectTypesVisible,
49
52
  type QueueProjectionRunInput,
53
+ type RecordProjectionAttemptFailureInput,
50
54
  type RecordProjectionMissingTargetInput,
51
55
  type StartOrReclaimProjectionRunInput,
52
56
  type TelemetryProjectionRunRecord,
@@ -60,6 +64,10 @@ function projectionRunKey(projectId: string, id: string): string {
60
64
  return JSON.stringify([projectId, id])
61
65
  }
62
66
 
67
+ function compareText(left: string, right: string): number {
68
+ return left < right ? -1 : left > right ? 1 : 0
69
+ }
70
+
63
71
  function compareRuns(a: ProjectionRunRecord, b: ProjectionRunRecord, order: "asc" | "desc") {
64
72
  return compareStartedAt(a, b, order)
65
73
  }
@@ -152,7 +160,6 @@ export class InMemoryProjectionRunStorage implements ProjectionRunStorage {
152
160
  executionToken,
153
161
  startedAt: existing.startedAt ?? new Date(input.startedAt ?? new Date()),
154
162
  finishedAt: undefined,
155
- error: undefined,
156
163
  }
157
164
 
158
165
  this.rows.set(key, structuredClone(record))
@@ -246,6 +253,17 @@ export class InMemoryProjectionRunStorage implements ProjectionRunStorage {
246
253
  })
247
254
  }
248
255
 
256
+ async recordAttemptFailure(
257
+ input: RecordProjectionAttemptFailureInput
258
+ ): Promise<ProjectionRunRecord> {
259
+ return this.runRootOperation(() => {
260
+ const existing = this.requireExecution(input)
261
+ const next = { ...existing, error: parseProjectionAttemptFailure(existing, input.error) }
262
+ this.rows.set(projectionRunKey(input.projectId, input.id), structuredClone(next))
263
+ return publicProjectionRunRecord(next)
264
+ })
265
+ }
266
+
249
267
  async finish(input: FinishProjectionRunInput): Promise<ProjectionRunRecord> {
250
268
  return this.runRootOperation(() => {
251
269
  const existing = this.requireExecution(input)
@@ -311,12 +329,33 @@ export class InMemoryProjectionRunStorage implements ProjectionRunStorage {
311
329
  })
312
330
  }
313
331
 
332
+ async findSupersedingRun(
333
+ input: FindSupersedingProjectionRunInput
334
+ ): Promise<ProjectionRunRecord | null> {
335
+ return this.runRootOperation(() => {
336
+ const run = this.rows.get(projectionRunKey(input.projectId, input.id))
337
+ if (!run) throw projectionRunNotFound(input.projectId, input.id)
338
+ const newest = [...this.rows.values()]
339
+ .filter((candidate) => projectionRunSupersedes(candidate, run))
340
+ .sort(
341
+ (left, right) =>
342
+ compareText(
343
+ right.identity.datasetVersion.createdAt,
344
+ left.identity.datasetVersion.createdAt
345
+ ) || compareText(right.id, left.id)
346
+ )[0]
347
+ return newest ? publicProjectionRunRecord(newest) : null
348
+ })
349
+ }
350
+
314
351
  async listLatestByProjectionIds(
315
352
  input: ListLatestProjectionRunsInput
316
353
  ): Promise<ListLatestProjectionRunsResult> {
317
354
  return this.runRootOperation(() => {
318
355
  const runs = latestStartedAtByOwnerId(
319
- [...this.rows.values()].filter((record) => record.projectId === input.projectId),
356
+ [...this.rows.values()].filter(
357
+ (record) => record.projectId === input.projectId && record.status !== "superseded"
358
+ ),
320
359
  input.projectionIds,
321
360
  (record) => record.identity.projectionId
322
361
  )
@@ -3,6 +3,7 @@ export { InMemoryProjectionRunStorage } from "./in-memory"
3
3
  export type {
4
4
  AdvanceProjectionTelemetryCheckpointInput,
5
5
  FailProjectionRunEnqueueInput,
6
+ FindSupersedingProjectionRunInput,
6
7
  FinishProjectionRunInput,
7
8
  LinkProjectionRunRecord,
8
9
  LinkProjectionTarget,
@@ -27,6 +28,7 @@ export type {
27
28
  ProjectionTarget,
28
29
  ProjectionTelemetryCheckpoint,
29
30
  QueueProjectionRunInput,
31
+ RecordProjectionAttemptFailureInput,
30
32
  RecordProjectionMissingTargetInput,
31
33
  StartOrReclaimProjectionRunInput,
32
34
  TelemetryProjectionRunRecord,
@@ -442,6 +442,15 @@ export function failProjectionRunEnqueue(
442
442
  }
443
443
  }
444
444
 
445
+ /** Validates the failure of an attempt that leaves its run running, before it is stored. */
446
+ export function parseProjectionAttemptFailure(
447
+ record: Pick<ProjectionRunRecord, "id" | "projectId" | "status">,
448
+ error: SixbFailure<ProjectionRunFailureCode>
449
+ ): SixbFailure<ProjectionRunFailureCode> {
450
+ assertProjectionRunRunning(record)
451
+ return parseSixbFailure(error, PROJECTION_RUN_FAILURE_CODES)
452
+ }
453
+
445
454
  export function canRequeueProjectionRunAfterEnqueueFailure(
446
455
  record: ProjectionRunRecord,
447
456
  input: QueueProjectionRunInput
@@ -457,6 +466,34 @@ export function canRequeueProjectionRunAfterEnqueueFailure(
457
466
  )
458
467
  }
459
468
 
469
+ /**
470
+ * Statuses of a run that still stands to replace its projection's source. A later run in one of
471
+ * them makes an earlier run stale; a failed or cancelled one replaces nothing.
472
+ */
473
+ export const SUPERSEDING_PROJECTION_RUN_STATUSES = [
474
+ "queued",
475
+ "running",
476
+ "succeeded",
477
+ ] as const satisfies readonly ProjectionRunStatus[]
478
+
479
+ /**
480
+ * Whether `newer` makes `run` stale. Dataset version timestamps are canonical UTC ISO strings, so
481
+ * they order as text; SQL adapters compare them under a binary collation for the same reason.
482
+ */
483
+ export function projectionRunSupersedes(
484
+ newer: ProjectionRunRecord,
485
+ run: ProjectionRunRecord
486
+ ): boolean {
487
+ return (
488
+ run.identity.protocol === "replacement" &&
489
+ newer.identity.protocol === "replacement" &&
490
+ newer.projectId === run.projectId &&
491
+ newer.identity.projectionId === run.identity.projectionId &&
492
+ newer.identity.datasetVersion.createdAt > run.identity.datasetVersion.createdAt &&
493
+ (SUPERSEDING_PROJECTION_RUN_STATUSES as readonly ProjectionRunStatus[]).includes(newer.status)
494
+ )
495
+ }
496
+
460
497
  // ── Persistence mapping ─────────────────────────────────────
461
498
 
462
499
  export function restoreProjectionRun(row: PersistedProjectionRunRecord): StoredProjectionRunRecord {
@@ -616,8 +653,7 @@ function assertPersistedLifecycle(row: PersistedProjectionRunRecord): void {
616
653
  row.attempt < 1 ||
617
654
  row.startedAt === undefined ||
618
655
  row.finishedAt !== undefined ||
619
- row.executionToken === undefined ||
620
- hasError
656
+ row.executionToken === undefined
621
657
  ) {
622
658
  throw incompleteProjectionRun(row.id)
623
659
  }
@@ -641,7 +677,12 @@ function assertPersistedLifecycle(row: PersistedProjectionRunRecord): void {
641
677
  return
642
678
  }
643
679
  }
644
- if (row.attempt < 1 || row.startedAt === undefined || (row.status === "succeeded" && hasError)) {
680
+ if (
681
+ row.attempt < 1 ||
682
+ row.startedAt === undefined ||
683
+ ((row.status === "succeeded" || row.status === "superseded") && hasError) ||
684
+ (row.status === "superseded" && row.protocol !== "replacement")
685
+ ) {
645
686
  throw incompleteProjectionRun(row.id)
646
687
  }
647
688
  }
@@ -652,6 +693,17 @@ function assertProjectionRunDate(value: Date, fieldName: string): void {
652
693
  }
653
694
  }
654
695
 
696
+ function isProjectionRunTerminalStatus(
697
+ status: unknown
698
+ ): status is ProjectionRunFinishPlan["status"] {
699
+ return (
700
+ status === "succeeded" ||
701
+ status === "failed" ||
702
+ status === "cancelled" ||
703
+ status === "superseded"
704
+ )
705
+ }
706
+
655
707
  function isProjectionKind(value: unknown): value is ProjectionKind {
656
708
  return value === "object" || value === "link" || value === "telemetry"
657
709
  }
@@ -722,7 +774,7 @@ function assertFinishDecision(
722
774
  record: ProjectionRunRecord,
723
775
  input: ProjectionRunTerminalDecision
724
776
  ): void {
725
- if (input.status !== "succeeded" && input.status !== "failed" && input.status !== "cancelled") {
777
+ if (!isProjectionRunTerminalStatus(input.status)) {
726
778
  throw new ProjectionRunError(
727
779
  `[Sixb] Projection run '${record.id}' finish status must be terminal.`
728
780
  )
@@ -735,6 +787,11 @@ function assertFinishDecision(
735
787
  `[Sixb] Projection run '${record.id}' finish protocol does not match its identity.`
736
788
  )
737
789
  }
790
+ if (input.status === "superseded" && input.protocol !== "replacement") {
791
+ throw new ProjectionRunError(
792
+ `[Sixb] Telemetry projection run '${record.id}' cannot be superseded.`
793
+ )
794
+ }
738
795
  const inputExhausted = "inputExhausted" in input ? input.inputExhausted : undefined
739
796
  if (input.status === "succeeded" && input.protocol === "telemetry") {
740
797
  if (inputExhausted === true) return
@@ -16,7 +16,18 @@ import type {
16
16
  export type { ProjectionRunFailureCode } from "../../projections/types"
17
17
  export { PROJECTION_RUN_FAILURE_CODES } from "../../projections/types"
18
18
 
19
- export type ProjectionRunStatus = "queued" | "running" | "succeeded" | "failed" | "cancelled"
19
+ /**
20
+ * `superseded` ends a replacement run whose output would be stale on arrival: a run of the same
21
+ * projection pinned to a later dataset version exists and has not failed. Telemetry runs append
22
+ * readings rather than replace a source, so none is ever superseded.
23
+ */
24
+ export type ProjectionRunStatus =
25
+ | "queued"
26
+ | "running"
27
+ | "succeeded"
28
+ | "failed"
29
+ | "cancelled"
30
+ | "superseded"
20
31
 
21
32
  export interface ProjectionRunProgress {
22
33
  /** Physical immutable-version rows consumed, before normalization or semantic materialization. */
@@ -76,6 +87,11 @@ interface ProjectionRunRecordBase {
76
87
  readonly queuedAt: Date
77
88
  readonly startedAt?: Date
78
89
  readonly finishedAt?: Date
90
+ /**
91
+ * The run's most recent failure, read with its status: on a `running` run, why its last attempt
92
+ * failed while the queue retries it; on a `failed` or `cancelled` run, why it ended. Cleared when
93
+ * the run succeeds or is superseded, so a present error always describes something current.
94
+ */
79
95
  readonly error?: SixbFailure<ProjectionRunFailureCode>
80
96
  }
81
97
 
@@ -202,6 +218,17 @@ export interface RecordProjectionMissingTargetInput
202
218
  readonly missingTarget: ProjectionMissingTarget
203
219
  }
204
220
 
221
+ export interface RecordProjectionAttemptFailureInput
222
+ extends LockProjectionRunForMaterializationInput {
223
+ readonly error: SixbFailure<ProjectionRunFailureCode>
224
+ }
225
+
226
+ export interface FindSupersedingProjectionRunInput {
227
+ readonly projectId: string
228
+ /** The run that may be superseded. */
229
+ readonly id: string
230
+ }
231
+
205
232
  export interface ListProjectionRunsInput {
206
233
  readonly projectId: string
207
234
  readonly projectionId?: string
@@ -258,10 +285,23 @@ export interface ProjectionRunStorage {
258
285
  recordMissingTarget(
259
286
  input: RecordProjectionMissingTargetInput
260
287
  ): Promise<TelemetryProjectionRunRecord>
288
+ /**
289
+ * Records the failure of the current attempt as the `error` of a run that stays `running` to be
290
+ * retried. Fenced like every other write: a delivery that lost the run cannot overwrite the
291
+ * failure of the one that holds it now. A reclaim keeps it until the run ends.
292
+ */
293
+ recordAttemptFailure(input: RecordProjectionAttemptFailureInput): Promise<ProjectionRunRecord>
261
294
  /** Telemetry success also records the worker's EOF observation atomically. */
262
295
  finish(input: FinishProjectionRunInput): Promise<ProjectionRunRecord>
296
+ /**
297
+ * The newest replacement run of the same projection that makes this one stale: pinned to a
298
+ * later dataset version, and queued, running, or succeeded. A failed or cancelled run replaces
299
+ * nothing, so it supersedes nothing either. Null for a telemetry run.
300
+ */
301
+ findSupersedingRun(input: FindSupersedingProjectionRunInput): Promise<ProjectionRunRecord | null>
263
302
  getById(params: { projectId: string; id: string }): Promise<ProjectionRunRecord | null>
264
303
  list(input: ListProjectionRunsInput): Promise<ListProjectionRunsResult>
304
+ /** Superseded runs are skipped: the run that superseded them is the one that matters. */
265
305
  listLatestByProjectionIds(
266
306
  input: ListLatestProjectionRunsInput
267
307
  ): Promise<ListLatestProjectionRunsResult>