@sixb/core 0.1.2 → 0.1.3

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 (264) hide show
  1. package/dist/actions/index.js +16 -13
  2. package/dist/actions/index.js.map +1 -1
  3. package/dist/agents/adapters.d.ts +11 -1
  4. package/dist/agents/adapters.d.ts.map +1 -1
  5. package/dist/agents/context.js +1 -1
  6. package/dist/agents/errors.d.ts +1 -1
  7. package/dist/agents/errors.d.ts.map +1 -1
  8. package/dist/agents/execution.d.ts +3 -0
  9. package/dist/agents/execution.d.ts.map +1 -1
  10. package/dist/agents/index.d.ts +6 -3
  11. package/dist/agents/index.d.ts.map +1 -1
  12. package/dist/agents/index.js +33 -22
  13. package/dist/agents/index.js.map +1 -1
  14. package/dist/agents/prompt.d.ts +8 -2
  15. package/dist/agents/prompt.d.ts.map +1 -1
  16. package/dist/agents/request.d.ts.map +1 -1
  17. package/dist/agents/streams.js +2 -2
  18. package/dist/agents/thread-context-projection.d.ts +19 -0
  19. package/dist/agents/thread-context-projection.d.ts.map +1 -0
  20. package/dist/agents/tool-definition.d.ts.map +1 -1
  21. package/dist/agents/tool-result.d.ts +6 -0
  22. package/dist/agents/tool-result.d.ts.map +1 -0
  23. package/dist/agents/types.d.ts +37 -1
  24. package/dist/agents/types.d.ts.map +1 -1
  25. package/dist/agents/validation.d.ts +1 -1
  26. package/dist/agents/validation.d.ts.map +1 -1
  27. package/dist/auth/index.js +7 -5
  28. package/dist/auth/index.js.map +1 -1
  29. package/dist/authorization/index.js +1 -1
  30. package/dist/bootstrap/index.js +27 -24
  31. package/dist/bootstrap/index.js.map +1 -1
  32. package/dist/{chunk-0mbx247x.js → chunk-177ed2mp.js} +241 -45
  33. package/dist/chunk-177ed2mp.js.map +46 -0
  34. package/dist/{chunk-sa4340y1.js → chunk-1ywq4hzk.js} +7 -7
  35. package/dist/{chunk-zg9yryhz.js → chunk-261ag4t4.js} +3 -3
  36. package/dist/{chunk-k9pts48t.js → chunk-3rsf30p4.js} +5 -5
  37. package/dist/{chunk-8ryen3p3.js → chunk-4aw0jwk0.js} +1 -1
  38. package/dist/{chunk-bh6k3nc6.js → chunk-4p9rh7jj.js} +1 -1
  39. package/dist/{chunk-34bqh420.js → chunk-4z3n1dzt.js} +59 -21
  40. package/dist/{chunk-34bqh420.js.map → chunk-4z3n1dzt.js.map} +4 -4
  41. package/dist/chunk-96swp2wd.js +47 -0
  42. package/dist/chunk-96swp2wd.js.map +10 -0
  43. package/dist/{chunk-n8herkry.js → chunk-bv41fmjn.js} +11 -11
  44. package/dist/{chunk-7pc8b818.js → chunk-c6cfqydk.js} +1 -1
  45. package/dist/{chunk-0k4avqr2.js → chunk-c8bw2b6c.js} +2 -2
  46. package/dist/{chunk-jaj13078.js → chunk-d2adj15j.js} +3 -33
  47. package/dist/{chunk-jaj13078.js.map → chunk-d2adj15j.js.map} +3 -4
  48. package/dist/{chunk-c6qd89g5.js → chunk-d6y2wsfg.js} +3 -3
  49. package/dist/chunk-dysw7nsm.js.map +1 -1
  50. package/dist/{chunk-st4xgxey.js → chunk-ettb0cy4.js} +150 -23
  51. package/dist/chunk-ettb0cy4.js.map +45 -0
  52. package/dist/{chunk-nnyp53zt.js → chunk-f1cyvnrk.js} +1 -1
  53. package/dist/chunk-fmx1sz2h.js.map +2 -2
  54. package/dist/{chunk-bct7hmfc.js → chunk-km4rkj4t.js} +3 -3
  55. package/dist/chunk-kqxgf6kv.js +898 -0
  56. package/dist/chunk-kqxgf6kv.js.map +15 -0
  57. package/dist/{chunk-qvybnnpe.js → chunk-n5x5y80a.js} +2 -2
  58. package/dist/{chunk-4x43bkbp.js → chunk-pxtrxm3p.js} +1 -1
  59. package/dist/chunk-qer6am8w.js +133 -0
  60. package/dist/chunk-qer6am8w.js.map +11 -0
  61. package/dist/{chunk-85j5ybfj.js → chunk-rdeagz6b.js} +2 -2
  62. package/dist/{chunk-85j5ybfj.js.map → chunk-rdeagz6b.js.map} +3 -3
  63. package/dist/chunk-rhrbz7d4.js +36 -0
  64. package/dist/chunk-rhrbz7d4.js.map +10 -0
  65. package/dist/{chunk-663csy96.js → chunk-t0jjkep3.js} +2 -1
  66. package/dist/chunk-t0jjkep3.js.map +10 -0
  67. package/dist/{chunk-5egfka8j.js → chunk-ttba8x6z.js} +2 -2
  68. package/dist/{chunk-nten49ta.js → chunk-wb4051cx.js} +3 -3
  69. package/dist/{chunk-nten49ta.js.map → chunk-wb4051cx.js.map} +1 -1
  70. package/dist/{chunk-57r1cpxx.js → chunk-xc7hq65b.js} +3 -2
  71. package/dist/{chunk-57r1cpxx.js.map → chunk-xc7hq65b.js.map} +3 -3
  72. package/dist/{chunk-ys1nse2v.js → chunk-xmggaxg4.js} +256 -231
  73. package/dist/{chunk-ys1nse2v.js.map → chunk-xmggaxg4.js.map} +8 -10
  74. package/dist/{chunk-g9t3kdtx.js → chunk-z8j83gzd.js} +1 -1
  75. package/dist/{chunk-57zpb99z.js → chunk-zpw39qhj.js} +1 -1
  76. package/dist/execution/agent.js +23 -20
  77. package/dist/execution/agent.js.map +1 -1
  78. package/dist/execution/primitive.js +23 -20
  79. package/dist/execution/primitive.js.map +2 -2
  80. package/dist/execution/request.js +24 -21
  81. package/dist/execution/request.js.map +2 -2
  82. package/dist/index.d.ts +3 -3
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +32 -29
  85. package/dist/index.js.map +1 -1
  86. package/dist/materialization/model.d.ts +4 -0
  87. package/dist/materialization/model.d.ts.map +1 -1
  88. package/dist/materializer/edits/apply.d.ts +6 -1
  89. package/dist/materializer/edits/apply.d.ts.map +1 -1
  90. package/dist/materializer/edits/working-state.d.ts +2 -0
  91. package/dist/materializer/edits/working-state.d.ts.map +1 -1
  92. package/dist/materializer/effective/resolve.d.ts +3 -1
  93. package/dist/materializer/effective/resolve.d.ts.map +1 -1
  94. package/dist/materializer/execution/work-records.d.ts.map +1 -1
  95. package/dist/materializer/index.js +14 -11
  96. package/dist/materializer/index.js.map +1 -1
  97. package/dist/materializer/projections/replacement-plan.d.ts.map +1 -1
  98. package/dist/materializer/shared/normalize.d.ts.map +1 -1
  99. package/dist/objects/index.js +17 -14
  100. package/dist/objects/index.js.map +1 -1
  101. package/dist/objects/query/index.js +2 -2
  102. package/dist/pipelines/index.js +14 -12
  103. package/dist/pipelines/index.js.map +1 -1
  104. package/dist/projections/builders.d.ts +19 -6
  105. package/dist/projections/builders.d.ts.map +1 -1
  106. package/dist/projections/index.d.ts +2 -1
  107. package/dist/projections/index.d.ts.map +1 -1
  108. package/dist/projections/internal.d.ts +4 -1
  109. package/dist/projections/internal.d.ts.map +1 -1
  110. package/dist/projections/internal.js +17 -11
  111. package/dist/projections/internal.js.map +1 -1
  112. package/dist/projections/timestamp.d.ts +3 -0
  113. package/dist/projections/timestamp.d.ts.map +1 -0
  114. package/dist/projections/types.d.ts +9 -0
  115. package/dist/projections/types.d.ts.map +1 -1
  116. package/dist/projections/validation.d.ts.map +1 -1
  117. package/dist/queues/index.d.ts +1 -1
  118. package/dist/queues/index.d.ts.map +1 -1
  119. package/dist/queues/types.d.ts +7 -0
  120. package/dist/queues/types.d.ts.map +1 -1
  121. package/dist/runtime/ai-cost.d.ts +8 -0
  122. package/dist/runtime/ai-cost.d.ts.map +1 -0
  123. package/dist/storage/agents/in-memory.d.ts +18 -4
  124. package/dist/storage/agents/in-memory.d.ts.map +1 -1
  125. package/dist/storage/agents/index.d.ts +1 -1
  126. package/dist/storage/agents/index.d.ts.map +1 -1
  127. package/dist/storage/agents/provider.d.ts +31 -0
  128. package/dist/storage/agents/provider.d.ts.map +1 -1
  129. package/dist/storage/agents/provider.js +18 -7
  130. package/dist/storage/agents/provider.js.map +1 -1
  131. package/dist/storage/agents/types.d.ts +70 -4
  132. package/dist/storage/agents/types.d.ts.map +1 -1
  133. package/dist/storage/ai-cost/analytics.d.ts +61 -0
  134. package/dist/storage/ai-cost/analytics.d.ts.map +1 -0
  135. package/dist/storage/ai-cost/codec.d.ts +14 -0
  136. package/dist/storage/ai-cost/codec.d.ts.map +1 -0
  137. package/dist/storage/ai-cost/errors.d.ts +7 -0
  138. package/dist/storage/ai-cost/errors.d.ts.map +1 -0
  139. package/dist/storage/ai-cost/in-memory.d.ts +32 -0
  140. package/dist/storage/ai-cost/in-memory.d.ts.map +1 -0
  141. package/dist/storage/ai-cost/index.d.ts +6 -0
  142. package/dist/storage/ai-cost/index.d.ts.map +1 -0
  143. package/dist/storage/ai-cost/provider.d.ts +7 -0
  144. package/dist/storage/ai-cost/provider.d.ts.map +1 -0
  145. package/dist/storage/ai-cost/provider.js +132 -0
  146. package/dist/storage/ai-cost/provider.js.map +10 -0
  147. package/dist/storage/ai-cost/types.d.ts +154 -0
  148. package/dist/storage/ai-cost/types.d.ts.map +1 -0
  149. package/dist/storage/ai-cost/validation.d.ts +7 -0
  150. package/dist/storage/ai-cost/validation.d.ts.map +1 -0
  151. package/dist/storage/in-memory/index.d.ts +5 -0
  152. package/dist/storage/in-memory/index.d.ts.map +1 -1
  153. package/dist/storage/index.d.ts +3 -1
  154. package/dist/storage/index.d.ts.map +1 -1
  155. package/dist/storage/index.js +22 -15
  156. package/dist/storage/index.js.map +1 -1
  157. package/dist/storage/ontology/in-memory/materializations.d.ts.map +1 -1
  158. package/dist/storage/ontology/materializations.d.ts +3 -0
  159. package/dist/storage/ontology/materializations.d.ts.map +1 -1
  160. package/dist/storage/operation-scope.d.ts.map +1 -1
  161. package/dist/storage/operation-scope.js +1 -1
  162. package/dist/storage/types.d.ts +2 -0
  163. package/dist/storage/types.d.ts.map +1 -1
  164. package/dist/storage/workflow-runs/provider.js +6 -5
  165. package/dist/storage/workflow-runs/provider.js.map +1 -1
  166. package/dist/syncs/index.js +14 -12
  167. package/dist/syncs/index.js.map +1 -1
  168. package/dist/testing/agent-storage-contract.d.ts +3 -2
  169. package/dist/testing/agent-storage-contract.d.ts.map +1 -1
  170. package/dist/testing/ai-cost-storage-contract.d.ts +12 -0
  171. package/dist/testing/ai-cost-storage-contract.d.ts.map +1 -0
  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 +2098 -1468
  175. package/dist/testing/index.js.map +6 -5
  176. package/dist/testing/materializer-storage-contract.d.ts.map +1 -1
  177. package/dist/webhooks/internal.js +12 -10
  178. package/dist/webhooks/internal.js.map +2 -2
  179. package/dist/workers/index.js +12 -2
  180. package/dist/workers/index.js.map +3 -3
  181. package/dist/workers/queue-worker.d.ts +2 -0
  182. package/dist/workers/queue-worker.d.ts.map +1 -1
  183. package/dist/workflows/execution.d.ts +3 -0
  184. package/dist/workflows/execution.d.ts.map +1 -1
  185. package/dist/workflows/index.js +24 -21
  186. package/dist/workflows/index.js.map +1 -1
  187. package/package.json +7 -1
  188. package/src/agents/adapters.ts +64 -23
  189. package/src/agents/errors.ts +2 -2
  190. package/src/agents/execution.ts +14 -2
  191. package/src/agents/index.ts +18 -1
  192. package/src/agents/prompt.ts +33 -5
  193. package/src/agents/request.ts +5 -0
  194. package/src/agents/thread-context-projection.ts +93 -0
  195. package/src/agents/tool-definition.ts +17 -2
  196. package/src/agents/tool-result.ts +61 -0
  197. package/src/agents/types.ts +47 -1
  198. package/src/agents/validation.ts +1 -1
  199. package/src/index.ts +13 -0
  200. package/src/materialization/model.ts +4 -0
  201. package/src/materializer/edits/apply.ts +55 -2
  202. package/src/materializer/edits/operations.ts +8 -1
  203. package/src/materializer/edits/working-state.ts +7 -0
  204. package/src/materializer/effective/resolve.ts +78 -9
  205. package/src/materializer/execution/work-records.ts +3 -1
  206. package/src/materializer/projections/entry-validator.ts +23 -1
  207. package/src/materializer/projections/replacement-plan.ts +2 -0
  208. package/src/materializer/shared/normalize.ts +16 -0
  209. package/src/projections/builders.ts +100 -35
  210. package/src/projections/index.ts +6 -0
  211. package/src/projections/internal.ts +4 -1
  212. package/src/projections/registry.ts +7 -0
  213. package/src/projections/revision.ts +4 -0
  214. package/src/projections/timestamp.ts +66 -0
  215. package/src/projections/types.ts +7 -0
  216. package/src/projections/validation.ts +24 -0
  217. package/src/queues/index.ts +1 -0
  218. package/src/queues/types.ts +8 -0
  219. package/src/runtime/ai-cost.ts +15 -0
  220. package/src/storage/agents/in-memory.ts +171 -4
  221. package/src/storage/agents/index.ts +5 -0
  222. package/src/storage/agents/provider.ts +225 -0
  223. package/src/storage/agents/types.ts +77 -3
  224. package/src/storage/ai-cost/analytics.ts +602 -0
  225. package/src/storage/ai-cost/codec.ts +136 -0
  226. package/src/storage/ai-cost/errors.ts +12 -0
  227. package/src/storage/ai-cost/in-memory.ts +164 -0
  228. package/src/storage/ai-cost/index.ts +35 -0
  229. package/src/storage/ai-cost/provider.ts +20 -0
  230. package/src/storage/ai-cost/types.ts +193 -0
  231. package/src/storage/ai-cost/validation.ts +337 -0
  232. package/src/storage/in-memory/index.ts +50 -0
  233. package/src/storage/index.ts +37 -0
  234. package/src/storage/ontology/in-memory/materializations.ts +1 -0
  235. package/src/storage/ontology/materializations.ts +3 -0
  236. package/src/storage/operation-scope.ts +1 -0
  237. package/src/storage/types.ts +2 -0
  238. package/src/testing/agent-storage-contract.ts +341 -2
  239. package/src/testing/ai-cost-storage-contract.ts +320 -0
  240. package/src/testing/index.ts +5 -0
  241. package/src/testing/materializer-storage-contract.ts +120 -2
  242. package/src/workers/queue-worker.ts +14 -1
  243. package/src/workflows/execution.ts +31 -10
  244. package/dist/chunk-0mbx247x.js.map +0 -43
  245. package/dist/chunk-663csy96.js.map +0 -10
  246. package/dist/chunk-st4xgxey.js.map +0 -45
  247. package/dist/chunk-vp1vc6ee.js +0 -35
  248. package/dist/chunk-vp1vc6ee.js.map +0 -11
  249. /package/dist/{chunk-sa4340y1.js.map → chunk-1ywq4hzk.js.map} +0 -0
  250. /package/dist/{chunk-zg9yryhz.js.map → chunk-261ag4t4.js.map} +0 -0
  251. /package/dist/{chunk-k9pts48t.js.map → chunk-3rsf30p4.js.map} +0 -0
  252. /package/dist/{chunk-8ryen3p3.js.map → chunk-4aw0jwk0.js.map} +0 -0
  253. /package/dist/{chunk-bh6k3nc6.js.map → chunk-4p9rh7jj.js.map} +0 -0
  254. /package/dist/{chunk-n8herkry.js.map → chunk-bv41fmjn.js.map} +0 -0
  255. /package/dist/{chunk-7pc8b818.js.map → chunk-c6cfqydk.js.map} +0 -0
  256. /package/dist/{chunk-0k4avqr2.js.map → chunk-c8bw2b6c.js.map} +0 -0
  257. /package/dist/{chunk-c6qd89g5.js.map → chunk-d6y2wsfg.js.map} +0 -0
  258. /package/dist/{chunk-nnyp53zt.js.map → chunk-f1cyvnrk.js.map} +0 -0
  259. /package/dist/{chunk-bct7hmfc.js.map → chunk-km4rkj4t.js.map} +0 -0
  260. /package/dist/{chunk-qvybnnpe.js.map → chunk-n5x5y80a.js.map} +0 -0
  261. /package/dist/{chunk-4x43bkbp.js.map → chunk-pxtrxm3p.js.map} +0 -0
  262. /package/dist/{chunk-5egfka8j.js.map → chunk-ttba8x6z.js.map} +0 -0
  263. /package/dist/{chunk-g9t3kdtx.js.map → chunk-z8j83gzd.js.map} +0 -0
  264. /package/dist/{chunk-57zpb99z.js.map → chunk-zpw39qhj.js.map} +0 -0
@@ -3,12 +3,12 @@ import {
3
3
  createPrimitiveExecutionRecord,
4
4
  ensureExecutionRecord,
5
5
  executionRecordInputFromRuntime
6
- } from "./chunk-8ryen3p3.js";
6
+ } from "./chunk-4aw0jwk0.js";
7
7
  import {
8
8
  actionRunParamsEqual,
9
9
  actionSubjectsEqual,
10
10
  isTerminalActionRun
11
- } from "./chunk-ys1nse2v.js";
11
+ } from "./chunk-xmggaxg4.js";
12
12
  import {
13
13
  ACTION_RUN_FAILURE_CODES,
14
14
  parseActionRunFailure
@@ -31,7 +31,7 @@ import {
31
31
  } from "./chunk-dktq02ne.js";
32
32
  import {
33
33
  assertAuthorized
34
- } from "./chunk-34bqh420.js";
34
+ } from "./chunk-4z3n1dzt.js";
35
35
  import {
36
36
  createSixbError,
37
37
  toSixbFailure
@@ -3,7 +3,7 @@
3
3
  "sources": ["../src/storage/projection-runs/errors.ts", "../src/projections/types.ts", "../src/storage/projection-runs/types.ts", "../src/storage/projection-runs/lifecycle.ts", "../src/storage/projection-runs/provider.ts"],
4
4
  "sourcesContent": [
5
5
  "import {\n MaterializationConflictError,\n type MaterializationConflictKind,\n} from \"../../materialization/errors\"\n\n/**\n * Base error for projection-run storage operations.\n */\nexport class ProjectionRunError extends MaterializationConflictError {\n readonly name = \"ProjectionRunError\"\n\n constructor(message: string, kind: MaterializationConflictKind = \"run-correlation\") {\n super(kind, message)\n }\n}\n",
6
- "import type { SixbErrorCode } from \"../errors/types\"\n\n/** Error codes a projection run can persist and expose through its public contract. */\nexport const PROJECTION_RUN_FAILURE_CODES = [\n \"internal.unexpected\",\n \"runtime.cancelled\",\n \"queue.enqueue_failed\",\n \"projection.execution_failed\",\n] as const satisfies readonly [SixbErrorCode, ...SixbErrorCode[]]\n\nexport type ProjectionRunFailureCode = (typeof PROJECTION_RUN_FAILURE_CODES)[number]\n\n/**\n * Lowered, serializable definition interfaces for projection DSL.\n *\n * These are the runtime-inspectable data structures that projection builders\n * lower into. They use plain string ids (no tokens, no methods) so they can\n * be stored, serialized, and introspected by tooling.\n */\n\n/**\n * Describes how a foreign key column in a dataset resolves to a link target.\n *\n * The source can be either a projected source property or a raw dataset field.\n * Its value equals the target object's primary property value, creating an\n * implicit link between source and target.\n *\n * `targetObjectTypeId` declares the concrete target type for this projection.\n * It must be the same type or a subtype (via `extends`) of the link's declared\n * target at the ontology level. Validation of the subtype relationship happens\n * at Sixb startup (Increment 7), not at DSL build time.\n */\nexport interface ForeignKeyDescriptor {\n readonly linkId: string\n /** Object property holding the target id. Mutually exclusive with sourceField. */\n readonly sourcePropertyId?: string\n /** Dataset column holding the target id. Mutually exclusive with sourcePropertyId. */\n readonly sourceField?: string\n readonly targetObjectTypeId: string\n}\n\n/**\n * Lowered, serializable definition for projecting a dataset into object instances.\n *\n * Each row contributes one complete object root to an authoritative replacement snapshot, with\n * properties mapped from dataset columns to object type properties. Duplicate roots are rejected.\n * FK links are resolved from source property or dataset field values to target object primary\n * values.\n */\nexport interface ObjectProjectionDefinition {\n readonly _tag: \"ObjectProjectionDefinition\"\n readonly id: string\n readonly objectTypeId: string\n readonly datasetId: string\n /** Maps object type property id -> dataset column name. */\n readonly properties: Readonly<Record<string, string>>\n /** Maps link id -> FK descriptor. Empty `{}` when no FK links are projected. */\n readonly links: Readonly<Record<string, ForeignKeyDescriptor>>\n}\n\n/**\n * Lowered, serializable definition for projecting a join dataset into link instances.\n *\n * Each row in the join dataset contributes one complete link root to an authoritative replacement\n * snapshot, identified by its source, link id, and target. Duplicate roots are rejected.\n */\nexport interface LinkProjectionDefinition {\n readonly _tag: \"LinkProjectionDefinition\"\n readonly id: string\n readonly linkId: string\n readonly sourceObjectTypeId: string\n readonly targetObjectTypeId: string\n readonly datasetId: string\n /** Dataset column holding the source object's primary property value. */\n readonly sourceField: string\n /** Dataset column holding the target object's primary property value. */\n readonly targetField: string\n}\n\n/**\n * Lowered, serializable definition for projecting dataset rows into telemetry history.\n *\n * Each row in the dataset becomes one telemetry point for a single object type property.\n * The target object type and property are fixed by the telemetry property token used by\n * the builder; the point mapping selects the dataset columns for object id, timestamp,\n * value, and optional unit.\n */\nexport interface TelemetryProjectionDefinition {\n readonly _tag: \"TelemetryProjectionDefinition\"\n readonly id: string\n readonly objectTypeId: string\n readonly propertyId: string\n readonly datasetId: string\n /** Dataset column holding the target object's primary id. */\n readonly objectIdField: string\n /** Dataset column holding the telemetry observation timestamp. */\n readonly atField: string\n /** Dataset column holding the telemetry value. */\n readonly valueField: string\n /** Dataset column holding the telemetry unit, when the property requires one. */\n readonly unitField?: string\n}\n\n/** Union of all projection definition types. */\nexport type ProjectionDefinition =\n | ObjectProjectionDefinition\n | LinkProjectionDefinition\n | TelemetryProjectionDefinition\n\nexport interface ProjectionOwnership {\n readonly objects: readonly {\n readonly objectTypeId: string\n readonly existence: boolean\n readonly propertyIds: readonly string[]\n }[]\n readonly links: readonly {\n readonly sourceObjectTypeId: string\n readonly linkId: string\n }[]\n readonly telemetry: readonly {\n readonly objectTypeId: string\n readonly propertyId: string\n }[]\n}\n\n/** Canonical correlation between a projection kind and its materialization protocol. */\nexport type ProjectionProtocolIdentity =\n | { readonly projectionKind: \"object\"; readonly protocol: \"replacement\" }\n | { readonly projectionKind: \"link\"; readonly protocol: \"replacement\" }\n | { readonly projectionKind: \"telemetry\"; readonly protocol: \"telemetry\" }\n\nexport type ProjectionKind = ProjectionProtocolIdentity[\"projectionKind\"]\n\nexport interface ObjectProjectionTarget {\n readonly objectTypeId: string\n}\n\nexport interface LinkProjectionTarget {\n readonly sourceObjectTypeId: string\n readonly targetObjectTypeId: string\n}\n\nexport interface ProjectionTargetByKind {\n readonly object: ObjectProjectionTarget\n readonly link: LinkProjectionTarget\n readonly telemetry: ObjectProjectionTarget\n}\n\n/** Object types affected by one projection, correlated with its projection kind. */\nexport type ProjectionTarget<TKind extends ProjectionKind = ProjectionKind> =\n ProjectionTargetByKind[TKind]\n\n/**\n * Frozen semantic identity used to dispatch one registered projection.\n *\n * Dataset version metadata is attached only when a committed dataset event is routed. Keeping the\n * registry-owned fields together prevents the orchestrator from recomputing ontology semantics.\n */\nexport type ProjectionDispatchDescriptor = ProjectionDispatchDescriptorBase &\n ProjectionProtocolIdentity\n\ninterface ProjectionDispatchDescriptorBase {\n readonly projectionId: string\n readonly datasetId: string\n readonly ontologyRevision: string\n readonly projectionRevision: string\n readonly ownershipHash: string\n}\n\nexport interface ResolvedProjection<TDefinition extends ProjectionDefinition> {\n readonly projectionId: string\n readonly datasetId: string\n readonly projectionRevision: string\n readonly ownershipHash: string\n readonly ownership: ProjectionOwnership\n readonly definition: TDefinition\n}\n",
6
+ "import type { SixbErrorCode } from \"../errors/types\"\n\n/** Error codes a projection run can persist and expose through its public contract. */\nexport const PROJECTION_RUN_FAILURE_CODES = [\n \"internal.unexpected\",\n \"runtime.cancelled\",\n \"queue.enqueue_failed\",\n \"projection.execution_failed\",\n] as const satisfies readonly [SixbErrorCode, ...SixbErrorCode[]]\n\nexport type ProjectionRunFailureCode = (typeof PROJECTION_RUN_FAILURE_CODES)[number]\n\n/**\n * Lowered, serializable definition interfaces for projection DSL.\n *\n * These are the runtime-inspectable data structures that projection builders\n * lower into. They use plain string ids (no tokens, no methods) so they can\n * be stored, serialized, and introspected by tooling.\n */\n\n/**\n * Describes how a foreign key column in a dataset resolves to a link target.\n *\n * The source can be either a projected source property or a raw dataset field.\n * Its value equals the target object's primary property value, creating an\n * implicit link between source and target.\n *\n * `targetObjectTypeId` declares the concrete target type for this projection.\n * It must be the same type or a subtype (via `extends`) of the link's declared\n * target at the ontology level. Validation of the subtype relationship happens\n * at Sixb startup (Increment 7), not at DSL build time.\n */\nexport interface ForeignKeyDescriptor {\n readonly linkId: string\n /** Object property holding the target id. Mutually exclusive with sourceField. */\n readonly sourcePropertyId?: string\n /** Dataset column holding the target id. Mutually exclusive with sourcePropertyId. */\n readonly sourceField?: string\n readonly targetObjectTypeId: string\n}\n\n/** Resolves competing source-projection and managed-edit values for projected properties. */\nexport type SourceEditConflictResolution =\n | { readonly strategy: \"editsWin\" }\n | { readonly strategy: \"mostRecent\"; readonly sourceTimestamp: string }\n\n/**\n * Lowered, serializable definition for projecting a dataset into object instances.\n *\n * Each row contributes one complete object root to an authoritative replacement snapshot, with\n * properties mapped from dataset columns to object type properties. Duplicate roots are rejected.\n * FK links are resolved from source property or dataset field values to target object primary\n * values.\n */\nexport interface ObjectProjectionDefinition {\n readonly _tag: \"ObjectProjectionDefinition\"\n readonly id: string\n readonly objectTypeId: string\n readonly datasetId: string\n /** Maps object type property id -> dataset column name. */\n readonly properties: Readonly<Record<string, string>>\n /** Maps link id -> FK descriptor. Empty `{}` when no FK links are projected. */\n readonly links: Readonly<Record<string, ForeignKeyDescriptor>>\n /** Defaults to `editsWin` when omitted by a legacy lowered definition. */\n readonly conflictResolution?: SourceEditConflictResolution\n}\n\n/**\n * Lowered, serializable definition for projecting a join dataset into link instances.\n *\n * Each row in the join dataset contributes one complete link root to an authoritative replacement\n * snapshot, identified by its source, link id, and target. Duplicate roots are rejected.\n */\nexport interface LinkProjectionDefinition {\n readonly _tag: \"LinkProjectionDefinition\"\n readonly id: string\n readonly linkId: string\n readonly sourceObjectTypeId: string\n readonly targetObjectTypeId: string\n readonly datasetId: string\n /** Dataset column holding the source object's primary property value. */\n readonly sourceField: string\n /** Dataset column holding the target object's primary property value. */\n readonly targetField: string\n}\n\n/**\n * Lowered, serializable definition for projecting dataset rows into telemetry history.\n *\n * Each row in the dataset becomes one telemetry point for a single object type property.\n * The target object type and property are fixed by the telemetry property token used by\n * the builder; the point mapping selects the dataset columns for object id, timestamp,\n * value, and optional unit.\n */\nexport interface TelemetryProjectionDefinition {\n readonly _tag: \"TelemetryProjectionDefinition\"\n readonly id: string\n readonly objectTypeId: string\n readonly propertyId: string\n readonly datasetId: string\n /** Dataset column holding the target object's primary id. */\n readonly objectIdField: string\n /** Dataset column holding the telemetry observation timestamp. */\n readonly atField: string\n /** Dataset column holding the telemetry value. */\n readonly valueField: string\n /** Dataset column holding the telemetry unit, when the property requires one. */\n readonly unitField?: string\n}\n\n/** Union of all projection definition types. */\nexport type ProjectionDefinition =\n | ObjectProjectionDefinition\n | LinkProjectionDefinition\n | TelemetryProjectionDefinition\n\nexport interface ProjectionOwnership {\n readonly objects: readonly {\n readonly objectTypeId: string\n readonly existence: boolean\n readonly propertyIds: readonly string[]\n }[]\n readonly links: readonly {\n readonly sourceObjectTypeId: string\n readonly linkId: string\n }[]\n readonly telemetry: readonly {\n readonly objectTypeId: string\n readonly propertyId: string\n }[]\n}\n\n/** Canonical correlation between a projection kind and its materialization protocol. */\nexport type ProjectionProtocolIdentity =\n | { readonly projectionKind: \"object\"; readonly protocol: \"replacement\" }\n | { readonly projectionKind: \"link\"; readonly protocol: \"replacement\" }\n | { readonly projectionKind: \"telemetry\"; readonly protocol: \"telemetry\" }\n\nexport type ProjectionKind = ProjectionProtocolIdentity[\"projectionKind\"]\n\nexport interface ObjectProjectionTarget {\n readonly objectTypeId: string\n}\n\nexport interface LinkProjectionTarget {\n readonly sourceObjectTypeId: string\n readonly targetObjectTypeId: string\n}\n\nexport interface ProjectionTargetByKind {\n readonly object: ObjectProjectionTarget\n readonly link: LinkProjectionTarget\n readonly telemetry: ObjectProjectionTarget\n}\n\n/** Object types affected by one projection, correlated with its projection kind. */\nexport type ProjectionTarget<TKind extends ProjectionKind = ProjectionKind> =\n ProjectionTargetByKind[TKind]\n\n/**\n * Frozen semantic identity used to dispatch one registered projection.\n *\n * Dataset version metadata is attached only when a committed dataset event is routed. Keeping the\n * registry-owned fields together prevents the orchestrator from recomputing ontology semantics.\n */\nexport type ProjectionDispatchDescriptor = ProjectionDispatchDescriptorBase &\n ProjectionProtocolIdentity\n\ninterface ProjectionDispatchDescriptorBase {\n readonly projectionId: string\n readonly datasetId: string\n readonly ontologyRevision: string\n readonly projectionRevision: string\n readonly ownershipHash: string\n}\n\nexport interface ResolvedProjection<TDefinition extends ProjectionDefinition> {\n readonly projectionId: string\n readonly datasetId: string\n readonly projectionRevision: string\n readonly ownershipHash: string\n readonly ownership: ProjectionOwnership\n readonly definition: TDefinition\n}\n",
7
7
  "import type { SixbFailure } from \"../../errors/types\"\nimport type {\n PinnedDatasetVersion,\n ProjectionExecution,\n ProjectionMaterializationIdentity,\n ProjectionRunTerminalDecision,\n} from \"../../materialization/model\"\nimport type {\n LinkProjectionTarget,\n ObjectProjectionTarget,\n ProjectionKind,\n ProjectionRunFailureCode,\n ProjectionTarget,\n} from \"../../projections/types\"\n\nexport type { ProjectionRunFailureCode } from \"../../projections/types\"\nexport { PROJECTION_RUN_FAILURE_CODES } from \"../../projections/types\"\n\nexport type ProjectionRunStatus = \"queued\" | \"running\" | \"succeeded\" | \"failed\" | \"cancelled\"\n\nexport interface ProjectionRunProgress {\n /** Physical immutable-version rows consumed, before normalization or semantic materialization. */\n readonly sourceRowsRead: number\n /** Physical rows intentionally skipped because a required mapped value was blank. */\n readonly sourceRowsSkipped: number\n}\n\nconst progressKeyFlags: Record<keyof ProjectionRunProgress, true> = {\n sourceRowsRead: true,\n sourceRowsSkipped: true,\n}\n\nexport const PROJECTION_RUN_PROGRESS_KEYS = Object.keys(\n progressKeyFlags\n) as readonly (keyof ProjectionRunProgress)[]\n\nexport function zeroProjectionRunProgress(): ProjectionRunProgress {\n const progress = {} as Record<keyof ProjectionRunProgress, number>\n for (const key of PROJECTION_RUN_PROGRESS_KEYS) progress[key] = 0\n return progress\n}\n\nexport interface ProjectionTelemetryCheckpoint {\n readonly fixedBatchSize: number\n readonly nextBatchOrdinal: number\n readonly nextRowOffset: number\n readonly inputExhausted: boolean\n}\n\n/**\n * The object a telemetry batch is waiting for, and when this run first found it missing.\n *\n * A projection's dataset can legitimately be materialized before the objects it references, so\n * the run waits instead of failing. The wait needs a start, and the run has no other durable one:\n * `startedAt` is when the run began, which on a batched projection is long before the batch that\n * cannot commit, and `attempt` counts every reclaim including the ones an unreachable queue caused.\n *\n * Cleared by any committed batch, so a run that is making progress never carries a stale wait.\n */\nexport interface ProjectionMissingTarget {\n readonly objectTypeId: string\n readonly objectId: string\n /** The batch that could not commit because of it. */\n readonly batchOrdinal: number\n readonly firstSeenAt: Date\n}\n\ninterface ProjectionRunRecordBase {\n readonly id: string\n readonly projectId: string\n readonly executionId: string\n readonly status: ProjectionRunStatus\n readonly attempt: number\n readonly progress: ProjectionRunProgress\n readonly queuedAt: Date\n readonly startedAt?: Date\n readonly finishedAt?: Date\n readonly error?: SixbFailure<ProjectionRunFailureCode>\n}\n\ntype ProjectionIdentity<TKind extends ProjectionKind> = Extract<\n ProjectionMaterializationIdentity,\n { readonly projectionKind: TKind }\n>\n\nexport type ObjectProjectionRunRecord = ProjectionRunRecordBase & {\n readonly identity: ProjectionIdentity<\"object\">\n readonly target: ObjectProjectionTarget\n readonly telemetryCheckpoint?: never\n readonly missingTarget?: never\n}\n\nexport type LinkProjectionRunRecord = ProjectionRunRecordBase & {\n readonly identity: ProjectionIdentity<\"link\">\n readonly target: LinkProjectionTarget\n readonly telemetryCheckpoint?: never\n readonly missingTarget?: never\n}\n\nexport type TelemetryProjectionRunRecord = ProjectionRunRecordBase & {\n readonly identity: ProjectionIdentity<\"telemetry\">\n readonly target: ObjectProjectionTarget\n readonly telemetryCheckpoint: ProjectionTelemetryCheckpoint\n /**\n * Only telemetry carries this: `MaterializationObjectNotFoundError` is raised by telemetry\n * validation alone, and only a batched protocol has a checkpoint to anchor the wait to.\n */\n readonly missingTarget?: ProjectionMissingTarget\n}\n\n/** Durable projection lifecycle state. The execution token is deliberately returned separately. */\nexport type ProjectionRunRecord =\n | ObjectProjectionRunRecord\n | LinkProjectionRunRecord\n | TelemetryProjectionRunRecord\n\nexport function projectionRunObjectTypesVisible(\n run: ProjectionRunRecord,\n canView: (objectTypeId: string) => boolean\n): boolean {\n const target: ProjectionTarget = run.target\n if (\"sourceObjectTypeId\" in target) {\n return canView(target.sourceObjectTypeId) && canView(target.targetObjectTypeId)\n }\n return canView(target.objectTypeId)\n}\n\ninterface ProjectionRunAdmissionBase {\n readonly id: string\n readonly projectId: string\n readonly executionId: string\n readonly queuedAt?: Date\n}\n\nexport type QueueProjectionRunInput =\n | (ProjectionRunAdmissionBase & {\n readonly identity: ProjectionIdentity<\"object\">\n readonly target: ObjectProjectionRunRecord[\"target\"]\n readonly fixedBatchSize?: never\n })\n | (ProjectionRunAdmissionBase & {\n readonly identity: ProjectionIdentity<\"link\">\n readonly target: LinkProjectionRunRecord[\"target\"]\n readonly fixedBatchSize?: never\n })\n | (ProjectionRunAdmissionBase & {\n readonly identity: ProjectionIdentity<\"telemetry\">\n readonly target: TelemetryProjectionRunRecord[\"target\"]\n readonly fixedBatchSize: number\n })\n\nexport type StartOrReclaimProjectionRunInput = QueueProjectionRunInput extends infer TInput\n ? TInput extends QueueProjectionRunInput\n ? Omit<TInput, \"executionId\" | \"queuedAt\"> & { readonly startedAt?: Date }\n : never\n : never\n\nexport interface FailProjectionRunEnqueueInput {\n readonly id: string\n readonly projectId: string\n readonly finishedAt?: Date\n readonly error: SixbFailure<ProjectionRunFailureCode>\n}\n\nexport interface ProjectionRunClaim {\n readonly run: ProjectionRunRecord\n readonly execution: ProjectionExecution\n}\n\nexport interface LockProjectionRunForMaterializationInput {\n readonly id: string\n readonly projectId: string\n readonly executionToken: string\n readonly identity: ProjectionMaterializationIdentity\n}\n\nexport interface UpdateProjectionRunInput extends LockProjectionRunForMaterializationInput {\n readonly progress: Partial<ProjectionRunProgress>\n}\n\nexport type FinishProjectionRunInput = ProjectionRunTerminalDecision &\n LockProjectionRunForMaterializationInput & {\n readonly finishedAt?: Date\n readonly progress?: Partial<ProjectionRunProgress>\n }\n\nexport interface AdvanceProjectionTelemetryCheckpointInput\n extends LockProjectionRunForMaterializationInput {\n /** Must equal the run's current nextBatchOrdinal. */\n readonly batchOrdinal: number\n /** Physical dataset rows consumed by this batch, including skipped rows. */\n readonly batchRowCount: number\n /** Physical rows intentionally skipped inside this batch. */\n readonly batchRowsSkipped: number\n /** True when this batch consumed the final row of the immutable dataset version. */\n readonly inputExhausted: boolean\n}\n\nexport interface RecordProjectionMissingTargetInput\n extends LockProjectionRunForMaterializationInput {\n readonly missingTarget: ProjectionMissingTarget\n}\n\nexport interface ListProjectionRunsInput {\n readonly projectId: string\n readonly projectionId?: string\n readonly projectionKind?: ProjectionKind\n readonly datasetId?: string\n readonly datasetVersionId?: string\n /**\n * Viewable object type ids. A run is included only if every target type is present. Omit for an\n * unrestricted internal execution; an empty set matches no runs.\n */\n readonly objectTypeIds?: readonly string[]\n readonly statuses?: readonly ProjectionRunStatus[]\n readonly startedAfter?: Date\n readonly startedBefore?: Date\n readonly limit?: number\n readonly offset?: number\n readonly order?: \"asc\" | \"desc\"\n}\n\nexport interface ListProjectionRunsResult {\n readonly runs: readonly ProjectionRunRecord[]\n readonly hasMore: boolean\n readonly total: number\n}\n\nexport interface ListLatestProjectionRunsInput {\n readonly projectId: string\n readonly projectionIds: readonly string[]\n}\n\nexport interface ListLatestProjectionRunsResult {\n readonly runs: readonly ProjectionRunRecord[]\n}\n\nexport interface ProjectionRunStorage {\n queue(input: QueueProjectionRunInput): Promise<ProjectionRunRecord>\n startOrReclaim(input: StartOrReclaimProjectionRunInput): Promise<ProjectionRunClaim>\n failEnqueue(input: FailProjectionRunEnqueueInput): Promise<ProjectionRunRecord>\n /** Transactionally fences the current execution before ontology materialization writes. */\n lockForMaterialization(\n input: LockProjectionRunForMaterializationInput\n ): Promise<ProjectionRunRecord>\n /** Updates physical source progress only. */\n update(input: UpdateProjectionRunInput): Promise<ProjectionRunRecord>\n /**\n * Advances telemetry progress atomically with the corresponding ontology commit.\n *\n * Clears any recorded missing target: a batch that commits is a batch that was not waiting.\n */\n advanceTelemetryCheckpoint(\n input: AdvanceProjectionTelemetryCheckpointInput\n ): Promise<TelemetryProjectionRunRecord>\n /** Records the object a telemetry batch is waiting for, so the wait has a durable start. */\n recordMissingTarget(\n input: RecordProjectionMissingTargetInput\n ): Promise<TelemetryProjectionRunRecord>\n /** Telemetry success also records the worker's EOF observation atomically. */\n finish(input: FinishProjectionRunInput): Promise<ProjectionRunRecord>\n getById(params: { projectId: string; id: string }): Promise<ProjectionRunRecord | null>\n list(input: ListProjectionRunsInput): Promise<ListProjectionRunsResult>\n listLatestByProjectionIds(\n input: ListLatestProjectionRunsInput\n ): Promise<ListLatestProjectionRunsResult>\n}\n\nexport type {\n LinkProjectionTarget,\n ObjectProjectionTarget,\n PinnedDatasetVersion,\n ProjectionExecution,\n ProjectionKind,\n ProjectionMaterializationIdentity,\n ProjectionTarget,\n}\n",
8
8
  "import { parseSixbFailure } from \"../../errors/internal\"\nimport type { SixbFailure } from \"../../errors/types\"\nimport type {\n ProjectionMaterializationIdentity,\n ProjectionRunTerminalDecision,\n} from \"../../materialization/model\"\nimport type {\n ProjectionKind,\n ProjectionRunFailureCode,\n ProjectionTarget,\n} from \"../../projections/types\"\nimport { PROJECTION_RUN_FAILURE_CODES } from \"../../projections/types\"\nimport { ProjectionRunError } from \"./errors\"\nimport type {\n AdvanceProjectionTelemetryCheckpointInput,\n FailProjectionRunEnqueueInput,\n FinishProjectionRunInput,\n ProjectionMissingTarget,\n ProjectionRunClaim,\n ProjectionRunProgress,\n ProjectionRunRecord,\n ProjectionRunStatus,\n ProjectionTelemetryCheckpoint,\n QueueProjectionRunInput,\n StartOrReclaimProjectionRunInput,\n TelemetryProjectionRunRecord,\n} from \"./types\"\nimport { PROJECTION_RUN_PROGRESS_KEYS, zeroProjectionRunProgress } from \"./types\"\n\nexport type StoredProjectionRunRecord = ProjectionRunRecord & {\n readonly executionToken?: string\n}\n\nexport interface PersistedProjectionRunRecord {\n readonly id: string\n readonly projectId: string\n readonly executionId: string\n readonly projectionId: string\n readonly projectionKind: ProjectionKind\n readonly protocol?: \"replacement\" | \"telemetry\"\n readonly datasetId: string\n readonly datasetVersionId: string\n readonly datasetVersionCreatedAt?: string\n readonly ontologyRevision?: string\n readonly projectionRevision?: string\n readonly ownershipHash?: string\n readonly objectTypeId?: string\n readonly sourceObjectTypeId?: string\n readonly targetObjectTypeId?: string\n readonly status: ProjectionRunStatus\n readonly queuedAt: Date\n readonly startedAt?: Date\n readonly finishedAt?: Date\n readonly attempt: number\n readonly executionToken?: string\n readonly fixedBatchSize?: number\n readonly nextBatchOrdinal?: number\n readonly nextRowOffset?: number\n readonly inputExhausted?: boolean\n readonly missingTargetObjectTypeId?: string\n readonly missingTargetObjectId?: string\n readonly missingTargetBatchOrdinal?: number\n readonly missingTargetFirstSeenAt?: Date\n readonly progress: ProjectionRunProgress\n readonly error?: unknown\n}\n\nexport interface ProjectionTelemetryAdvance {\n readonly progress: ProjectionRunProgress\n readonly checkpoint: ProjectionTelemetryCheckpoint\n}\n\nexport interface ProjectionRunFinishPlan {\n readonly status: Exclude<ProjectionRunStatus, \"queued\" | \"running\">\n readonly finishedAt: Date\n readonly progress: ProjectionRunProgress\n readonly inputExhausted?: true\n readonly error?: SixbFailure<ProjectionRunFailureCode>\n}\n\n// ── Scalar and identity validation ──────────────────────────\n\nexport function assertProjectionRunNonEmpty(value: string, fieldName: string): void {\n if (typeof value !== \"string\" || value.trim().length === 0) {\n throw new ProjectionRunError(`[Sixb] Projection run ${fieldName} must not be empty.`)\n }\n}\n\nexport function assertProjectionRunCounter(value: number, fieldName: string): void {\n if (!Number.isSafeInteger(value) || value < 0) {\n throw new ProjectionRunError(\n `[Sixb] Projection run ${fieldName} must be a non-negative safe integer.`\n )\n }\n}\n\nexport function assertProjectionRunListWindow(value: number | undefined, fieldName: string): void {\n if (value !== undefined && (!Number.isSafeInteger(value) || value < 0)) {\n throw new ProjectionRunError(`[Sixb] Projection run list ${fieldName} must be >= 0.`)\n }\n}\n\nexport function addProjectionRunCounter(left: number, right: number, fieldName: string): number {\n const result = left + right\n if (!Number.isSafeInteger(result)) {\n throw new ProjectionRunError(`[Sixb] Projection run ${fieldName} exceeds safe integer range.`)\n }\n return result\n}\n\nexport function assertProjectionRunIdentity(identity: ProjectionMaterializationIdentity): void {\n assertProjectionRunNonEmpty(identity.projectionId, \"projectionId\")\n if (!isProjectionKind(identity.projectionKind)) {\n throw new ProjectionRunError(\n \"[Sixb] Projection run projectionKind must be 'object', 'link', or 'telemetry'.\"\n )\n }\n if (identity.protocol !== \"replacement\" && identity.protocol !== \"telemetry\") {\n throw new ProjectionRunError(\n \"[Sixb] Projection run protocol must be 'replacement' or 'telemetry'.\"\n )\n }\n assertProjectionRunNonEmpty(identity.datasetVersion.datasetId, \"datasetId\")\n assertProjectionRunNonEmpty(identity.datasetVersion.versionId, \"datasetVersionId\")\n assertCanonicalTimestamp(identity.datasetVersion.createdAt, \"datasetVersionCreatedAt\")\n assertProjectionRunNonEmpty(identity.ontologyRevision, \"ontologyRevision\")\n assertProjectionRunNonEmpty(identity.projectionRevision, \"projectionRevision\")\n assertProjectionRunNonEmpty(identity.ownershipHash, \"ownershipHash\")\n if ((identity.protocol === \"telemetry\") !== (identity.projectionKind === \"telemetry\")) {\n throw new ProjectionRunError(\n `[Sixb] Projection run protocol '${identity.protocol}' is incompatible with kind '${identity.projectionKind}'.`\n )\n }\n}\n\nexport function assertProjectionRunIdentityMatches(\n record: Pick<ProjectionRunRecord, \"id\" | \"identity\">,\n identity: ProjectionMaterializationIdentity\n): void {\n if (projectionRunIdentitiesEqual(record.identity, identity)) return\n throw new ProjectionRunError(\n `[Sixb] Projection run '${record.id}' materialization identity does not match.`\n )\n}\n\nexport function assertProjectionRunTargetMatches(\n record: Pick<ProjectionRunRecord, \"id\" | \"target\">,\n target: ProjectionTarget\n): void {\n if (projectionTargetsEqual(record.target, target)) return\n throw new ProjectionRunError(\n `[Sixb] Projection run '${record.id}' target object types do not match.`\n )\n}\n\nexport function assertProjectionRunStartInput(input: StartOrReclaimProjectionRunInput): void {\n assertProjectionRunNonEmpty(input.id, \"id\")\n assertProjectionRunNonEmpty(input.projectId, \"projectId\")\n assertProjectionRunIdentityAndTarget(input)\n if (input.startedAt) assertProjectionRunDate(input.startedAt, \"startedAt\")\n}\n\nexport function assertProjectionRunQueueInput(input: QueueProjectionRunInput): void {\n assertProjectionRunNonEmpty(input.id, \"id\")\n assertProjectionRunNonEmpty(input.projectId, \"projectId\")\n assertProjectionRunNonEmpty(input.executionId, \"executionId\")\n assertProjectionRunIdentityAndTarget(input)\n if (input.queuedAt) assertProjectionRunDate(input.queuedAt, \"queuedAt\")\n}\n\nfunction assertProjectionRunIdentityAndTarget(\n input: QueueProjectionRunInput | StartOrReclaimProjectionRunInput\n): void {\n assertProjectionRunIdentity(input.identity)\n assertProjectionTarget(input.identity.projectionKind, input.target)\n if (input.identity.projectionKind === \"telemetry\") {\n const telemetry = input as ProjectionRunIdentityInput<\"telemetry\">\n assertPositiveCounter(telemetry.fixedBatchSize, \"fixedBatchSize\")\n } else if (input.fixedBatchSize !== undefined) {\n throw new ProjectionRunError(\n \"[Sixb] Replacement projection runs cannot declare a telemetry fixedBatchSize.\"\n )\n }\n}\n\nexport function planProjectionRunReclaim(\n record: StoredProjectionRunRecord,\n input: StartOrReclaimProjectionRunInput\n): { readonly attempt: number } {\n if (record.status !== \"queued\" && record.status !== \"running\") {\n throw new ProjectionRunError(\n `[Sixb] Projection run '${record.id}' cannot start from status '${record.status}'.`\n )\n }\n assertProjectionRunIdentityMatches(record, input.identity)\n assertProjectionRunTargetMatches(record, input.target)\n if (record.telemetryCheckpoint?.fixedBatchSize !== input.fixedBatchSize) {\n throw new ProjectionRunError(\n `[Sixb] Projection run '${input.id}' fixed batch size does not match.`\n )\n }\n return { attempt: record.status === \"queued\" ? 1 : nextProjectionRunAttempt(record.attempt) }\n}\n\n// ── Lifecycle transitions ───────────────────────────────────\n\nexport function createProjectionRunRecord(\n input: QueueProjectionRunInput,\n now: Date = new Date()\n): ProjectionRunRecord {\n assertProjectionRunQueueInput(input)\n const base = {\n id: input.id,\n projectId: input.projectId,\n executionId: input.executionId,\n status: \"queued\" as const,\n attempt: 0,\n progress: zeroProjectionRunProgress(),\n queuedAt: new Date(input.queuedAt ?? now),\n }\n switch (input.identity.projectionKind) {\n case \"object\": {\n const object = input as ProjectionRunAdmission<\"object\">\n return { ...base, identity: object.identity, target: object.target }\n }\n case \"link\": {\n const link = input as ProjectionRunAdmission<\"link\">\n return { ...base, identity: link.identity, target: link.target }\n }\n case \"telemetry\": {\n const telemetry = input as ProjectionRunAdmission<\"telemetry\">\n return {\n ...base,\n identity: telemetry.identity,\n target: telemetry.target,\n telemetryCheckpoint: {\n fixedBatchSize: telemetry.fixedBatchSize,\n nextBatchOrdinal: 0,\n nextRowOffset: 0,\n inputExhausted: false,\n },\n }\n }\n }\n}\n\nexport function assertProjectionRunRunning(\n record: Pick<ProjectionRunRecord, \"id\" | \"projectId\" | \"status\">\n): void {\n if (record.status === \"running\") return\n if (record.status !== \"queued\") {\n throw new ProjectionRunError(`[Sixb] Projection run '${record.id}' is already terminal.`)\n }\n throw new ProjectionRunError(\n `[Sixb] Projection run '${record.id}' for project '${record.projectId}' is not running.`\n )\n}\n\nexport function assertProjectionRunAttempt(\n record: StoredProjectionRunRecord,\n input: {\n readonly id: string\n readonly executionToken: string\n readonly identity: ProjectionMaterializationIdentity\n }\n): asserts record is StoredProjectionRunRecord & { readonly executionToken: string } {\n assertProjectionRunNonEmpty(input.executionToken, \"executionToken\")\n assertProjectionRunIdentity(input.identity)\n assertProjectionRunRunning(record)\n assertProjectionRunIdentityMatches(record, input.identity)\n if (record.executionToken !== input.executionToken) {\n throw staleProjectionRunExecution(input.id)\n }\n}\n\nexport function nextProjectionRunAttempt(current: number): number {\n assertProjectionRunCounter(current, \"attempt\")\n return addProjectionRunCounter(current, 1, \"attempt\")\n}\n\nexport function mergeProjectionRunProgress(\n current: ProjectionRunProgress,\n patch: Partial<ProjectionRunProgress>\n): ProjectionRunProgress {\n const progress = { ...current }\n for (const key of PROJECTION_RUN_PROGRESS_KEYS) {\n const value = patch[key]\n if (value === undefined) continue\n assertProjectionRunCounter(value, key)\n if (value < current[key]) {\n throw new ProjectionRunError(`[Sixb] Projection run ${key} must not decrease.`)\n }\n progress[key] = value\n }\n assertProjectionRunProgress(progress)\n return progress\n}\n\nexport function assertGenericProgressDoesNotAdvanceTelemetry(\n record: Pick<ProjectionRunRecord, \"id\" | \"identity\" | \"progress\">,\n patch: Partial<ProjectionRunProgress>\n): void {\n if (record.identity.protocol !== \"telemetry\") return\n for (const key of PROJECTION_RUN_PROGRESS_KEYS) {\n if (patch[key] !== undefined && patch[key] !== record.progress[key]) {\n throw new ProjectionRunError(\n `[Sixb] Telemetry projection run '${record.id}' progress can only advance with its checkpoint.`\n )\n }\n }\n}\n\nexport function advanceProjectionTelemetry(\n record: ProjectionRunRecord,\n input: AdvanceProjectionTelemetryCheckpointInput\n): ProjectionTelemetryAdvance {\n const telemetry = requireTelemetryProjectionRun(record)\n const checkpoint = telemetry.telemetryCheckpoint\n assertProjectionRunCounter(input.batchOrdinal, \"batchOrdinal\")\n assertPositiveCounter(input.batchRowCount, \"batchRowCount\")\n assertProjectionRunCounter(input.batchRowsSkipped, \"batchRowsSkipped\")\n if (input.batchRowsSkipped > input.batchRowCount) {\n throw new ProjectionRunError(\n `[Sixb] Telemetry projection run '${record.id}' skipped rows exceed its batch row count.`\n )\n }\n if (input.batchOrdinal !== checkpoint.nextBatchOrdinal) {\n throw new ProjectionRunError(\n `[Sixb] Telemetry projection run '${record.id}' expected batch ordinal ${checkpoint.nextBatchOrdinal}, got ${input.batchOrdinal}.`\n )\n }\n if (input.batchRowCount > checkpoint.fixedBatchSize) {\n throw new ProjectionRunError(\n `[Sixb] Telemetry projection run '${record.id}' batch exceeds its fixed size.`\n )\n }\n if (!input.inputExhausted && input.batchRowCount !== checkpoint.fixedBatchSize) {\n throw new ProjectionRunError(\n `[Sixb] Telemetry projection run '${record.id}' cannot advance past a partial non-final batch.`\n )\n }\n if (checkpoint.inputExhausted) {\n throw new ProjectionRunError(\n `[Sixb] Telemetry projection run '${record.id}' has already exhausted its input.`\n )\n }\n\n const nextRowOffset = addProjectionRunCounter(\n checkpoint.nextRowOffset,\n input.batchRowCount,\n \"nextRowOffset\"\n )\n return {\n progress: {\n sourceRowsRead: nextRowOffset,\n sourceRowsSkipped: addProjectionRunCounter(\n telemetry.progress.sourceRowsSkipped,\n input.batchRowsSkipped,\n \"sourceRowsSkipped\"\n ),\n },\n checkpoint: {\n ...checkpoint,\n nextBatchOrdinal: addProjectionRunCounter(checkpoint.nextBatchOrdinal, 1, \"nextBatchOrdinal\"),\n nextRowOffset,\n inputExhausted: input.inputExhausted,\n },\n }\n}\n\nexport function planProjectionRunFinish(\n record: ProjectionRunRecord,\n input: FinishProjectionRunInput,\n now: Date = new Date()\n): ProjectionRunFinishPlan {\n assertFinishDecision(record, input)\n const patch = input.progress ?? {}\n assertGenericProgressDoesNotAdvanceTelemetry(record, patch)\n const progress = mergeProjectionRunProgress(record.progress, patch)\n const finishedAt = new Date(input.finishedAt ?? now)\n assertProjectionRunDate(finishedAt, \"finishedAt\")\n return {\n status: input.status,\n finishedAt,\n progress,\n ...(input.status === \"succeeded\" && input.protocol === \"telemetry\"\n ? { inputExhausted: true as const }\n : {}),\n ...(input.status === \"succeeded\" || input.error === undefined\n ? {}\n : { error: parseSixbFailure(input.error, PROJECTION_RUN_FAILURE_CODES) }),\n }\n}\n\nexport function finishProjectionRunRecord(\n record: StoredProjectionRunRecord,\n input: FinishProjectionRunInput,\n now: Date = new Date()\n): StoredProjectionRunRecord {\n const plan = planProjectionRunFinish(record, input, now)\n const terminal = {\n executionToken: undefined,\n status: plan.status,\n finishedAt: plan.finishedAt,\n progress: plan.progress,\n error: plan.error,\n }\n if (plan.inputExhausted) {\n const telemetry = requireTelemetryProjectionRun(record)\n return {\n ...telemetry,\n ...terminal,\n telemetryCheckpoint: { ...telemetry.telemetryCheckpoint, inputExhausted: true },\n }\n }\n return { ...record, ...terminal }\n}\n\nexport function failProjectionRunEnqueue(\n record: StoredProjectionRunRecord,\n input: FailProjectionRunEnqueueInput,\n now: Date = new Date()\n): StoredProjectionRunRecord {\n if (record.status !== \"queued\") {\n throw new ProjectionRunError(\n `[Sixb] Projection run '${record.id}' cannot fail enqueue from status '${record.status}'.`\n )\n }\n const error = parseSixbFailure(input.error, PROJECTION_RUN_FAILURE_CODES)\n if (error.code !== \"queue.enqueue_failed\") {\n throw new ProjectionRunError(\n `[Sixb] Projection run '${record.id}' enqueue failure must use code 'queue.enqueue_failed'.`\n )\n }\n const finishedAt = new Date(input.finishedAt ?? now)\n assertProjectionRunDate(finishedAt, \"finishedAt\")\n return {\n ...record,\n status: \"failed\",\n finishedAt,\n error,\n }\n}\n\nexport function canRequeueProjectionRunAfterEnqueueFailure(\n record: ProjectionRunRecord,\n input: QueueProjectionRunInput\n): boolean {\n return (\n record.status === \"failed\" &&\n record.error?.code === \"queue.enqueue_failed\" &&\n record.error.retryable &&\n record.executionId === input.executionId &&\n projectionRunIdentitiesEqual(record.identity, input.identity) &&\n projectionTargetsEqual(record.target, input.target) &&\n record.telemetryCheckpoint?.fixedBatchSize === input.fixedBatchSize\n )\n}\n\n// ── Persistence mapping ─────────────────────────────────────\n\nexport function restoreProjectionRun(row: PersistedProjectionRunRecord): StoredProjectionRunRecord {\n assertProjectionRunNonEmpty(row.id, \"id\")\n assertProjectionRunNonEmpty(row.projectId, \"projectId\")\n assertProjectionRunNonEmpty(row.executionId, \"executionId\")\n assertProjectionRunCounter(row.attempt, \"attempt\")\n assertProjectionRunProgress(row.progress)\n assertValidDate(row.queuedAt, \"queuedAt\")\n if (row.startedAt) assertValidDate(row.startedAt, \"startedAt\")\n if (row.finishedAt) assertValidDate(row.finishedAt, \"finishedAt\")\n assertPersistedLifecycle(row)\n\n const identity = restoreIdentity(row)\n const base = {\n id: row.id,\n projectId: row.projectId,\n executionId: row.executionId,\n identity,\n status: row.status,\n queuedAt: new Date(row.queuedAt),\n startedAt: row.startedAt ? new Date(row.startedAt) : undefined,\n finishedAt: row.finishedAt ? new Date(row.finishedAt) : undefined,\n attempt: row.attempt,\n progress: { ...row.progress },\n error:\n row.error === undefined\n ? undefined\n : parseSixbFailure(row.error, PROJECTION_RUN_FAILURE_CODES),\n executionToken: row.executionToken,\n }\n\n if (identity.projectionKind === \"link\") {\n if (!row.sourceObjectTypeId || !row.targetObjectTypeId || row.objectTypeId) {\n throw invalidProjectionRunTarget(row.id)\n }\n assertNoCheckpoint(row)\n return {\n ...base,\n identity,\n target: {\n sourceObjectTypeId: row.sourceObjectTypeId,\n targetObjectTypeId: row.targetObjectTypeId,\n },\n }\n }\n if (!row.objectTypeId || row.sourceObjectTypeId || row.targetObjectTypeId) {\n throw invalidProjectionRunTarget(row.id)\n }\n if (identity.projectionKind === \"telemetry\") {\n const telemetryCheckpoint = restoreCheckpoint(row)\n if (row.progress.sourceRowsRead !== telemetryCheckpoint.nextRowOffset) {\n throw new ProjectionRunError(\n `[Sixb] Telemetry projection run '${row.id}' progress does not match its checkpoint.`\n )\n }\n if (row.status === \"succeeded\" && !telemetryCheckpoint.inputExhausted) {\n throw incompleteProjectionRun(row.id)\n }\n const missingTarget = restoreMissingTarget(row, telemetryCheckpoint)\n return {\n ...base,\n identity,\n target: { objectTypeId: row.objectTypeId },\n telemetryCheckpoint,\n ...(missingTarget ? { missingTarget } : {}),\n }\n }\n assertNoCheckpoint(row)\n return { ...base, identity, target: { objectTypeId: row.objectTypeId } }\n}\n\nexport function publicProjectionRunRecord(record: StoredProjectionRunRecord): ProjectionRunRecord {\n const cloned = structuredClone(record)\n Reflect.deleteProperty(cloned, \"executionToken\")\n return cloned\n}\n\nexport function createProjectionRunClaim(record: StoredProjectionRunRecord): ProjectionRunClaim {\n assertProjectionRunRunning(record)\n const executionToken = requireProjectionRunExecutionToken(record)\n return {\n run: publicProjectionRunRecord(record),\n execution: { projectionRunId: record.id, executionToken },\n }\n}\n\nexport function requireProjectionRunExecutionToken(record: StoredProjectionRunRecord): string {\n if (record.executionToken) return record.executionToken\n throw incompleteProjectionRun(record.id)\n}\n\nexport function staleProjectionRunExecution(id: string): ProjectionRunError {\n return new ProjectionRunError(\n `[Sixb] Projection run '${id}' execution token is stale.`,\n \"execution-lost\"\n )\n}\n\nexport function projectionRunNotFound(projectId: string, id: string): ProjectionRunError {\n return new ProjectionRunError(\n `[Sixb] Projection run '${id}' not found for project '${projectId}'.`\n )\n}\n\nexport function immutableDatasetVersionConflict(\n identity: ProjectionMaterializationIdentity\n): ProjectionRunError {\n return new ProjectionRunError(\n `[Sixb] Dataset version '${identity.datasetVersion.versionId}' reused an immutable dataset version id with different metadata.`\n )\n}\n\n// ── Private helpers ─────────────────────────────────────────\n\nfunction assertPositiveCounter(value: number, fieldName: string): void {\n if (!Number.isSafeInteger(value) || value <= 0) {\n throw new ProjectionRunError(\n `[Sixb] Projection run ${fieldName} must be a positive safe integer.`\n )\n }\n}\n\nfunction assertCanonicalTimestamp(value: string, fieldName: string): void {\n const milliseconds = Date.parse(value)\n if (!Number.isFinite(milliseconds) || new Date(milliseconds).toISOString() !== value) {\n throw new ProjectionRunError(\n `[Sixb] Projection run ${fieldName} must be a canonical UTC timestamp.`\n )\n }\n}\n\nfunction assertValidDate(value: Date, fieldName: string): void {\n if (!Number.isFinite(value.getTime())) {\n throw new ProjectionRunError(`[Sixb] Projection run persisted ${fieldName} is invalid.`)\n }\n}\n\nfunction assertPersistedLifecycle(row: PersistedProjectionRunRecord): void {\n const hasError = row.error !== undefined\n\n if (row.status === \"queued\") {\n if (\n row.attempt !== 0 ||\n row.startedAt !== undefined ||\n row.finishedAt !== undefined ||\n row.executionToken !== undefined ||\n hasError\n ) {\n throw incompleteProjectionRun(row.id)\n }\n return\n }\n\n if (row.status === \"running\") {\n if (\n row.attempt < 1 ||\n row.startedAt === undefined ||\n row.finishedAt !== undefined ||\n row.executionToken === undefined ||\n hasError\n ) {\n throw incompleteProjectionRun(row.id)\n }\n return\n }\n\n if (row.finishedAt === undefined || row.executionToken !== undefined) {\n throw incompleteProjectionRun(row.id)\n }\n if (row.error !== undefined) {\n const failure = parseSixbFailure(row.error, PROJECTION_RUN_FAILURE_CODES)\n if (failure.code === \"queue.enqueue_failed\") {\n if (\n !failure.retryable ||\n row.status !== \"failed\" ||\n row.attempt !== 0 ||\n row.startedAt !== undefined\n ) {\n throw incompleteProjectionRun(row.id)\n }\n return\n }\n }\n if (row.attempt < 1 || row.startedAt === undefined || (row.status === \"succeeded\" && hasError)) {\n throw incompleteProjectionRun(row.id)\n }\n}\n\nfunction assertProjectionRunDate(value: Date, fieldName: string): void {\n if (!Number.isFinite(value.getTime())) {\n throw new ProjectionRunError(`[Sixb] Projection run ${fieldName} is invalid.`)\n }\n}\n\nfunction isProjectionKind(value: unknown): value is ProjectionKind {\n return value === \"object\" || value === \"link\" || value === \"telemetry\"\n}\n\nfunction assertProjectionTarget(kind: ProjectionKind, target: ProjectionTarget): void {\n if (kind === \"link\") {\n if (!(\"sourceObjectTypeId\" in target)) {\n throw new ProjectionRunError(\n \"[Sixb] Link projection runs must declare source and target object types.\"\n )\n }\n assertProjectionRunNonEmpty(target.sourceObjectTypeId, \"sourceObjectTypeId\")\n assertProjectionRunNonEmpty(target.targetObjectTypeId, \"targetObjectTypeId\")\n return\n }\n if (!(\"objectTypeId\" in target)) {\n throw new ProjectionRunError(\n \"[Sixb] Object and telemetry projection runs must declare one object type.\"\n )\n }\n assertProjectionRunNonEmpty(target.objectTypeId, \"objectTypeId\")\n}\n\nfunction projectionRunIdentitiesEqual(\n left: ProjectionMaterializationIdentity,\n right: ProjectionMaterializationIdentity\n): boolean {\n return (\n left.projectionId === right.projectionId &&\n left.projectionKind === right.projectionKind &&\n left.protocol === right.protocol &&\n left.datasetVersion.datasetId === right.datasetVersion.datasetId &&\n left.datasetVersion.versionId === right.datasetVersion.versionId &&\n left.datasetVersion.createdAt === right.datasetVersion.createdAt &&\n left.ontologyRevision === right.ontologyRevision &&\n left.projectionRevision === right.projectionRevision &&\n left.ownershipHash === right.ownershipHash\n )\n}\n\nfunction projectionTargetsEqual(left: ProjectionTarget, right: ProjectionTarget): boolean {\n if (\"sourceObjectTypeId\" in left || \"sourceObjectTypeId\" in right) {\n return (\n \"sourceObjectTypeId\" in left &&\n \"sourceObjectTypeId\" in right &&\n left.sourceObjectTypeId === right.sourceObjectTypeId &&\n left.targetObjectTypeId === right.targetObjectTypeId\n )\n }\n return left.objectTypeId === right.objectTypeId\n}\n\nfunction assertProjectionRunProgress(progress: ProjectionRunProgress): void {\n for (const key of PROJECTION_RUN_PROGRESS_KEYS) {\n assertProjectionRunCounter(progress[key], key)\n }\n if (progress.sourceRowsSkipped > progress.sourceRowsRead) {\n throw new ProjectionRunError(\n \"[Sixb] Projection run sourceRowsSkipped must not exceed sourceRowsRead.\"\n )\n }\n}\n\nfunction assertFinishDecision(\n record: ProjectionRunRecord,\n input: ProjectionRunTerminalDecision\n): void {\n if (input.status !== \"succeeded\" && input.status !== \"failed\" && input.status !== \"cancelled\") {\n throw new ProjectionRunError(\n `[Sixb] Projection run '${record.id}' finish status must be terminal.`\n )\n }\n if (input.protocol !== \"replacement\" && input.protocol !== \"telemetry\") {\n throw new ProjectionRunError(`[Sixb] Projection run '${record.id}' finish protocol is invalid.`)\n }\n if (input.protocol !== record.identity.protocol) {\n throw new ProjectionRunError(\n `[Sixb] Projection run '${record.id}' finish protocol does not match its identity.`\n )\n }\n const inputExhausted = \"inputExhausted\" in input ? input.inputExhausted : undefined\n if (input.status === \"succeeded\" && input.protocol === \"telemetry\") {\n if (inputExhausted === true) return\n throw new ProjectionRunError(\n `[Sixb] Telemetry projection run '${record.id}' cannot succeed before input exhaustion.`\n )\n }\n if (inputExhausted !== undefined) {\n throw new ProjectionRunError(\n `[Sixb] Projection run '${record.id}' contains telemetry-only completion metadata.`\n )\n }\n}\n\nfunction restoreIdentity(row: PersistedProjectionRunRecord): ProjectionMaterializationIdentity {\n if (\n !row.protocol ||\n !row.datasetVersionCreatedAt ||\n !row.ontologyRevision ||\n !row.projectionRevision ||\n !row.ownershipHash\n ) {\n throw incompleteProjectionRun(row.id)\n }\n const identity = {\n projectionId: row.projectionId,\n projectionKind: row.projectionKind,\n protocol: row.protocol,\n datasetVersion: {\n datasetId: row.datasetId,\n versionId: row.datasetVersionId,\n createdAt: row.datasetVersionCreatedAt,\n },\n ontologyRevision: row.ontologyRevision,\n projectionRevision: row.projectionRevision,\n ownershipHash: row.ownershipHash,\n } as ProjectionMaterializationIdentity\n assertProjectionRunIdentity(identity)\n return identity\n}\n\nfunction restoreCheckpoint(row: PersistedProjectionRunRecord): ProjectionTelemetryCheckpoint {\n if (\n row.fixedBatchSize === undefined ||\n row.nextBatchOrdinal === undefined ||\n row.nextRowOffset === undefined ||\n row.inputExhausted === undefined\n ) {\n throw new ProjectionRunError(\n `[Sixb] Telemetry projection run '${row.id}' has incomplete checkpoint state.`\n )\n }\n assertPositiveCounter(row.fixedBatchSize, \"fixedBatchSize\")\n assertProjectionRunCounter(row.nextBatchOrdinal, \"nextBatchOrdinal\")\n assertProjectionRunCounter(row.nextRowOffset, \"nextRowOffset\")\n return {\n fixedBatchSize: row.fixedBatchSize,\n nextBatchOrdinal: row.nextBatchOrdinal,\n nextRowOffset: row.nextRowOffset,\n inputExhausted: row.inputExhausted,\n }\n}\n\n/**\n * A wait is either fully persisted or absent. A partially written one would read as \"waiting since\n * the epoch\" or \"waiting for nothing\", and both would decide a run's fate.\n */\nfunction restoreMissingTarget(\n row: PersistedProjectionRunRecord,\n checkpoint: ProjectionTelemetryCheckpoint\n): ProjectionMissingTarget | undefined {\n const columns = [\n row.missingTargetObjectTypeId,\n row.missingTargetObjectId,\n row.missingTargetBatchOrdinal,\n row.missingTargetFirstSeenAt,\n ]\n if (columns.every((column) => column === undefined)) return undefined\n if (\n row.missingTargetObjectTypeId === undefined ||\n row.missingTargetObjectId === undefined ||\n row.missingTargetBatchOrdinal === undefined ||\n row.missingTargetFirstSeenAt === undefined\n ) {\n throw new ProjectionRunError(\n `[Sixb] Telemetry projection run '${row.id}' has a partially persisted missing target.`\n )\n }\n const missingTarget: ProjectionMissingTarget = {\n objectTypeId: row.missingTargetObjectTypeId,\n objectId: row.missingTargetObjectId,\n batchOrdinal: row.missingTargetBatchOrdinal,\n firstSeenAt: new Date(row.missingTargetFirstSeenAt),\n }\n assertValidDate(missingTarget.firstSeenAt, \"missingTarget.firstSeenAt\")\n assertProjectionRunCounter(missingTarget.batchOrdinal, \"missingTarget.batchOrdinal\")\n if (missingTarget.batchOrdinal !== checkpoint.nextBatchOrdinal) {\n throw new ProjectionRunError(\n `[Sixb] Telemetry projection run '${row.id}' is waiting on a batch it has already passed.`\n )\n }\n if (missingTarget.objectTypeId !== row.objectTypeId) {\n throw new ProjectionRunError(\n `[Sixb] Telemetry projection run '${row.id}' is waiting on an object type it does not write.`\n )\n }\n return missingTarget\n}\n\nfunction assertNoCheckpoint(row: PersistedProjectionRunRecord): void {\n if (\n row.fixedBatchSize === undefined &&\n row.nextBatchOrdinal === undefined &&\n row.nextRowOffset === undefined &&\n row.inputExhausted === undefined\n ) {\n return\n }\n throw new ProjectionRunError(\n `[Sixb] Replacement projection run '${row.id}' contains a telemetry checkpoint.`\n )\n}\n\nfunction incompleteProjectionRun(id: string): ProjectionRunError {\n return new ProjectionRunError(\n `[Sixb] Projection run '${id}' has incomplete materialization state.`\n )\n}\n\nfunction invalidProjectionRunTarget(id: string): ProjectionRunError {\n return new ProjectionRunError(`[Sixb] Projection run '${id}' has an invalid target.`)\n}\n\n/**\n * Validates a wait before it is stored, so every adapter refuses the same shapes and the SQL\n * `CHECK` behind them never has to be the thing that catches it.\n *\n * The ordinal has to be the batch the run is actually stuck on: anchored to a stale one, a wait\n * would survive the progress that resolved it and fail a run that is working. The object type\n * has to be the one this run writes, since that is the only type its telemetry can reference.\n */\nexport function assertProjectionMissingTarget(\n record: TelemetryProjectionRunRecord,\n missingTarget: ProjectionMissingTarget\n): ProjectionMissingTarget {\n assertProjectionRunNonEmpty(missingTarget.objectTypeId, \"missingTarget.objectTypeId\")\n assertProjectionRunNonEmpty(missingTarget.objectId, \"missingTarget.objectId\")\n assertProjectionRunCounter(missingTarget.batchOrdinal, \"missingTarget.batchOrdinal\")\n if (Number.isNaN(missingTarget.firstSeenAt.getTime())) {\n throw new ProjectionRunError(\"[Sixb] Projection run missingTarget.firstSeenAt is invalid.\")\n }\n if (missingTarget.objectTypeId !== record.target.objectTypeId) {\n throw new ProjectionRunError(\n `[Sixb] Telemetry projection run '${record.id}' cannot wait on '${missingTarget.objectTypeId}'; it writes '${record.target.objectTypeId}'.`\n )\n }\n if (missingTarget.batchOrdinal !== record.telemetryCheckpoint.nextBatchOrdinal) {\n throw new ProjectionRunError(\n `[Sixb] Telemetry projection run '${record.id}' cannot wait on batch ${missingTarget.batchOrdinal}; it is at ${record.telemetryCheckpoint.nextBatchOrdinal}.`\n )\n }\n return missingTarget\n}\n\nexport function requireTelemetryProjectionRun<TRecord extends ProjectionRunRecord>(\n record: TRecord\n): TRecord & TelemetryProjectionRunRecord {\n if (isTelemetryProjectionRun(record)) return record as TRecord & TelemetryProjectionRunRecord\n throw new ProjectionRunError(\n `[Sixb] Projection run '${record.id}' does not have a telemetry checkpoint.`\n )\n}\n\ntype ProjectionRunIdentityInput<TKind extends ProjectionKind> = Extract<\n QueueProjectionRunInput | StartOrReclaimProjectionRunInput,\n { readonly identity: { readonly projectionKind: TKind } }\n>\n\ntype ProjectionRunAdmission<TKind extends ProjectionKind> = Extract<\n QueueProjectionRunInput,\n { readonly identity: { readonly projectionKind: TKind } }\n>\n\nfunction isTelemetryProjectionRun(\n record: ProjectionRunRecord\n): record is TelemetryProjectionRunRecord {\n return record.identity.projectionKind === \"telemetry\"\n}\n",
9
9
  "import type { ExecutionStorage } from \"../executions\"\nimport { findPrimitiveRunExecution } from \"../executions/run-link\"\nimport { ProjectionRunError } from \"./errors\"\n\nexport * from \"./lifecycle\"\n\n/** Validate the semantic link between a durable Projection run and its immutable execution. */\nexport async function assertProjectionRunDurableExecution(input: {\n readonly executions: ExecutionStorage\n readonly projectId: string\n readonly executionId: string\n readonly runId: string\n readonly projectionId: string\n readonly datasetId: string\n readonly datasetVersionId: string\n}): Promise<void> {\n const execution = await findPrimitiveRunExecution({\n executions: input.executions,\n projectId: input.projectId,\n executionId: input.executionId,\n primitive: { kind: \"projection\", id: input.projectionId, runId: input.runId },\n sourceTypes: [\"datasetVersion\"],\n })\n if (\n !execution ||\n execution.source.type !== \"datasetVersion\" ||\n execution.source.datasetId !== input.datasetId ||\n execution.source.versionId !== input.datasetVersionId\n ) {\n throw new ProjectionRunError(\n `[Sixb] Execution '${input.executionId}' does not authorize Projection run '${input.runId}'.`\n )\n }\n}\n"
@@ -1,11 +1,14 @@
1
1
  // @bun
2
+ import {
3
+ parseDatasetTimestamp
4
+ } from "./chunk-96swp2wd.js";
2
5
  import {
3
6
  ensureExecutionRecord,
4
7
  executionRecordInputFromRuntime
5
- } from "./chunk-8ryen3p3.js";
8
+ } from "./chunk-4aw0jwk0.js";
6
9
  import {
7
10
  isStorageSerializationFailure
8
- } from "./chunk-ys1nse2v.js";
11
+ } from "./chunk-xmggaxg4.js";
9
12
  import {
10
13
  createCommitId,
11
14
  createEventId,
@@ -56,7 +59,7 @@ import {
56
59
  isProjectionDefinition,
57
60
  resolveExecutionScopeAuthorization,
58
61
  validateProjectionsAtStartup
59
- } from "./chunk-34bqh420.js";
62
+ } from "./chunk-4z3n1dzt.js";
60
63
  import {
61
64
  assertJsonValue,
62
65
  cloneJsonValue,
@@ -169,7 +172,8 @@ function normalizeProjectionDefinition(projection) {
169
172
  sourceField: link.sourceField ?? null,
170
173
  targetObjectTypeId: link.targetObjectTypeId
171
174
  }
172
- ]))
175
+ ])),
176
+ conflictResolution: projection.conflictResolution ?? { strategy: "editsWin" }
173
177
  };
174
178
  }
175
179
  if (projection._tag === "LinkProjectionDefinition") {
@@ -196,7 +200,8 @@ function projectionColumns(projection) {
196
200
  if (projection._tag === "ObjectProjectionDefinition") {
197
201
  return new Set([
198
202
  ...Object.values(projection.properties),
199
- ...Object.values(projection.links).flatMap((link) => link.sourceField === undefined ? [] : [link.sourceField])
203
+ ...Object.values(projection.links).flatMap((link) => link.sourceField === undefined ? [] : [link.sourceField]),
204
+ ...projection.conflictResolution?.strategy === "mostRecent" ? [projection.conflictResolution.sourceTimestamp] : []
200
205
  ]);
201
206
  }
202
207
  if (projection._tag === "LinkProjectionDefinition") {
@@ -424,7 +429,11 @@ function cloneObjectProjectionDefinition(projection) {
424
429
  ...descriptor.sourceField !== undefined ? { sourceField: descriptor.sourceField } : {},
425
430
  targetObjectTypeId: descriptor.targetObjectTypeId
426
431
  }
427
- ]))
432
+ ])),
433
+ conflictResolution: projection.conflictResolution?.strategy === "mostRecent" ? {
434
+ strategy: "mostRecent",
435
+ sourceTimestamp: projection.conflictResolution.sourceTimestamp
436
+ } : { strategy: "editsWin" }
428
437
  };
429
438
  }
430
439
  function cloneLinkProjectionDefinition(projection) {
@@ -882,7 +891,7 @@ function appendItem(chunk, item) {
882
891
 
883
892
  // src/materializer/execution/work-records.ts
884
893
  function appendObjectOverrideWork(items, working, identity) {
885
- if (stableJsonStringify(working.originalOverride?.value ?? null) === stableJsonStringify(working.override)) {
894
+ if (stableJsonStringify(working.originalOverride?.value ?? null) === stableJsonStringify(working.override) && stableJsonStringify(working.originalEditedAt) === stableJsonStringify(working.editedAt)) {
886
895
  return;
887
896
  }
888
897
  if (working.override) {
@@ -891,6 +900,7 @@ function appendObjectOverrideWork(items, working, identity) {
891
900
  value: {
892
901
  ref: working.ref,
893
902
  value: working.override,
903
+ editedAt: working.editedAt,
894
904
  expectedLastCommitId: working.originalOverride?.lastCommitId ?? null,
895
905
  lastCommitId: identity.commitId,
896
906
  updatedAt: identity.committedAt
@@ -1160,6 +1170,13 @@ function normalizeTimestamp(value, label) {
1160
1170
  }
1161
1171
  return new Date(milliseconds).toISOString();
1162
1172
  }
1173
+ function normalizeProjectionSourceTimestamp(value) {
1174
+ const timestamp = parseDatasetTimestamp(value);
1175
+ if (!timestamp) {
1176
+ throw new MaterializationValidationError("Projection source update timestamp must be a valid timestamp.");
1177
+ }
1178
+ return timestamp.toISOString();
1179
+ }
1163
1180
  function normalizePropertyIds(values, label) {
1164
1181
  const seen = new Set;
1165
1182
  const normalized = [];
@@ -1369,7 +1386,10 @@ function normalizeProjectionAssertion(assertion) {
1369
1386
  return assertion.kind === "object" ? Object.freeze({
1370
1387
  kind: "object",
1371
1388
  ref: normalizeObjectRef(assertion.ref),
1372
- properties: normalizeJsonProperties(assertion.properties)
1389
+ properties: normalizeJsonProperties(assertion.properties),
1390
+ ...assertion.sourceUpdatedAt !== undefined ? {
1391
+ sourceUpdatedAt: normalizeProjectionSourceTimestamp(assertion.sourceUpdatedAt)
1392
+ } : {}
1373
1393
  }) : Object.freeze({
1374
1394
  kind: "link",
1375
1395
  ref: normalizeLinkRef(assertion.ref),
@@ -1526,35 +1546,76 @@ function usableLinkSlotOverride(stored) {
1526
1546
  throw new MaterializationConflictError("effective-state", `Cardinality-one link slot ${scopeLabel} could not be migrated automatically because it ` + "contains multiple legacy upsert overrides. Consolidate the stored overrides and clear " + "the migration conflict marker before materializing this scope.");
1527
1547
  }
1528
1548
  function resolveEffectiveObject(input) {
1529
- const properties = resolveObjectAuthority(input.source, input.override);
1549
+ const properties = resolveObjectAuthority(input.source, input.override, input.editedAt);
1530
1550
  if (!properties)
1531
1551
  return null;
1532
1552
  properties[input.primaryPropertyId] = input.ref.primaryId;
1533
1553
  applyLatestTelemetry(properties, input.latestTelemetry);
1534
1554
  return { ref: input.ref, properties };
1535
1555
  }
1536
- function resolveObjectAuthority(source, override) {
1556
+ function resolveObjectAuthority(source, override, editedAt) {
1537
1557
  switch (override?.kind) {
1538
1558
  case "delete":
1539
1559
  return null;
1540
1560
  case "create":
1541
- return { ...override.properties };
1561
+ if (!source)
1562
+ return { ...override.properties };
1563
+ return resolveSourceAndEdits(source, override, editedAt);
1542
1564
  case "patch":
1543
1565
  if (!source)
1544
1566
  return null;
1545
- return applyObjectPatch(source.assertion.properties, override);
1567
+ return resolveSourceAndEdits(source, override, editedAt);
1546
1568
  default:
1547
1569
  if (!source)
1548
1570
  return null;
1549
1571
  return { ...source.assertion.properties };
1550
1572
  }
1551
1573
  }
1552
- function applyObjectPatch(sourceProperties, override) {
1553
- const properties = { ...sourceProperties, ...override.set };
1554
- for (const propertyId of override.unset)
1555
- delete properties[propertyId];
1574
+ function resolveSourceAndEdits(source, override, editedAt) {
1575
+ const properties = { ...source.assertion.properties };
1576
+ const absentSourcePropertyIds = new Set(source.assertion.absentSourcePropertyIds ?? []);
1577
+ for (const propertyId of objectEditCandidateIds(override, editedAt)) {
1578
+ const edit = objectEditCandidate(override, propertyId);
1579
+ const editTimestamp = editedAt[propertyId];
1580
+ const hasSourceCandidate = Object.hasOwn(source.assertion.properties, propertyId) || absentSourcePropertyIds.has(propertyId);
1581
+ const sourceWins = hasSourceCandidate && source.assertion.sourceUpdatedAt !== undefined && editTimestamp !== undefined && source.assertion.sourceUpdatedAt.localeCompare(editTimestamp) >= 0;
1582
+ if (!sourceWins)
1583
+ applyObjectEditCandidate(properties, propertyId, edit);
1584
+ }
1556
1585
  return properties;
1557
1586
  }
1587
+ function objectEditCandidateIds(override, editedAt) {
1588
+ const ids = new Set(Object.keys(editedAt));
1589
+ if (override.kind === "create") {
1590
+ for (const propertyId of Object.keys(override.properties))
1591
+ ids.add(propertyId);
1592
+ } else {
1593
+ for (const propertyId of Object.keys(override.set))
1594
+ ids.add(propertyId);
1595
+ for (const propertyId of override.unset)
1596
+ ids.add(propertyId);
1597
+ }
1598
+ return [...ids];
1599
+ }
1600
+ function objectEditCandidate(override, propertyId) {
1601
+ const values = override.kind === "create" ? override.properties : override.set;
1602
+ return Object.hasOwn(values, propertyId) ? { present: true, value: values[propertyId] } : { present: false };
1603
+ }
1604
+ function applyObjectEditCandidate(properties, propertyId, candidate) {
1605
+ if (candidate.present)
1606
+ properties[propertyId] = candidate.value;
1607
+ else
1608
+ delete properties[propertyId];
1609
+ }
1610
+ function storedObjectEditedAt(stored) {
1611
+ if (!stored || stored.value.kind === "delete")
1612
+ return {};
1613
+ const editedAt = { ...stored.editedAt ?? {} };
1614
+ const candidateIds = stored.value.kind === "create" ? Object.keys(stored.value.properties) : [...Object.keys(stored.value.set), ...stored.value.unset];
1615
+ for (const propertyId of candidateIds)
1616
+ editedAt[propertyId] ??= stored.updatedAt;
1617
+ return editedAt;
1618
+ }
1558
1619
  function applyLatestTelemetry(properties, latestTelemetry) {
1559
1620
  for (const point of latestTelemetry)
1560
1621
  properties[point.series.propertyId] = point.value;
@@ -1683,11 +1744,14 @@ function workingLinkSlotFromState(scope) {
1683
1744
  };
1684
1745
  }
1685
1746
  function workingObjectFromState(object) {
1747
+ const editedAt = storedObjectEditedAt(object.override);
1686
1748
  return {
1687
1749
  ref: object.ref,
1688
1750
  source: object.source,
1689
1751
  originalOverride: object.override,
1690
1752
  override: object.override?.value ?? null,
1753
+ originalEditedAt: editedAt,
1754
+ editedAt: { ...editedAt },
1691
1755
  before: object.effective,
1692
1756
  latestTelemetry: [...object.latestTelemetry]
1693
1757
  };
@@ -1707,6 +1771,7 @@ function resolveObject(ontology, working) {
1707
1771
  primaryPropertyId: ontology.getPrimaryPropertyId(working.ref.objectTypeId),
1708
1772
  source: working.source,
1709
1773
  override: working.override,
1774
+ editedAt: working.editedAt,
1710
1775
  latestTelemetry: working.latestTelemetry
1711
1776
  });
1712
1777
  }
@@ -2136,7 +2201,7 @@ function applyObjectEdit(input) {
2136
2201
  next = applyObjectUpsert(input, input.operation);
2137
2202
  break;
2138
2203
  case "object.patch":
2139
- next = applyObjectPatch2(input, input.operation);
2204
+ next = applyObjectPatch(input, input.operation);
2140
2205
  break;
2141
2206
  case "object.delete":
2142
2207
  next = applyObjectDelete(input);
@@ -2147,7 +2212,47 @@ function applyObjectEdit(input) {
2147
2212
  default:
2148
2213
  throw new MaterializationValidationError("Expected an object edit operation.");
2149
2214
  }
2150
- return transition(current, next);
2215
+ const editedAt = applyObjectEditTimes(input, next);
2216
+ const authority = transition(current, next);
2217
+ return {
2218
+ ...authority,
2219
+ editedAt,
2220
+ changed: authority.changed || !sameStringRecord(input.editedAt, editedAt)
2221
+ };
2222
+ }
2223
+ function applyObjectEditTimes(input, next) {
2224
+ if (!next || next.kind === "delete")
2225
+ return {};
2226
+ const editedAt = input.operation.kind === "object.create" || input.authority?.kind === "delete" ? {} : { ...input.editedAt };
2227
+ switch (input.operation.kind) {
2228
+ case "object.create":
2229
+ case "object.upsert": {
2230
+ const properties = input.normalizedProperties ?? input.operation.properties;
2231
+ for (const propertyId of Object.keys(properties))
2232
+ editedAt[propertyId] = input.committedAt;
2233
+ break;
2234
+ }
2235
+ case "object.patch":
2236
+ for (const propertyId of input.operation.reset)
2237
+ delete editedAt[propertyId];
2238
+ for (const propertyId of input.operation.unset)
2239
+ editedAt[propertyId] = input.committedAt;
2240
+ for (const propertyId of Object.keys(input.normalizedSet ?? input.operation.set)) {
2241
+ editedAt[propertyId] = input.committedAt;
2242
+ }
2243
+ break;
2244
+ case "object.delete":
2245
+ return editedAt;
2246
+ case "object.restore":
2247
+ break;
2248
+ }
2249
+ return editedAt;
2250
+ }
2251
+ function sameStringRecord(left, right) {
2252
+ const leftKeys = Object.keys(left);
2253
+ if (leftKeys.length !== Object.keys(right).length)
2254
+ return false;
2255
+ return leftKeys.every((key) => left[key] === right[key]);
2151
2256
  }
2152
2257
  function applyObjectCreate(input, operation) {
2153
2258
  if (input.sourceProperties || input.authority) {
@@ -2178,7 +2283,7 @@ function mergeUpsertIntoPatch(current, properties, hasSource) {
2178
2283
  delete created[propertyId];
2179
2284
  return { kind: "create", properties: { ...created, ...properties } };
2180
2285
  }
2181
- function applyObjectPatch2(input, operation) {
2286
+ function applyObjectPatch(input, operation) {
2182
2287
  const current = input.authority;
2183
2288
  if (!input.effectiveExists && current?.kind !== "patch") {
2184
2289
  throw new MaterializationValidationError(`Object patch requires an effective object for ${operation.ref.objectTypeId}:${operation.ref.primaryId}.`);
@@ -2304,10 +2409,12 @@ function applyObjectOperation(context, state, operation, identity, journal) {
2304
2409
  operation,
2305
2410
  sourceProperties: working.source?.assertion.properties ?? null,
2306
2411
  authority: working.override,
2412
+ editedAt: working.editedAt,
2413
+ committedAt: identity.committedAt,
2307
2414
  effectiveExists: currentEffective !== null,
2308
2415
  ...normalized
2309
2416
  });
2310
- applyObjectTransition(context, state, working, transition2.next, journal);
2417
+ applyObjectTransition(context, state, working, transition2.next, transition2.editedAt, journal);
2311
2418
  return objectOperationOutcome(operation, working, transition2.changed, context, identity);
2312
2419
  }
2313
2420
  function requireWorkingObject(state, operation) {
@@ -2334,9 +2441,11 @@ function validateObjectOperation(context, operation) {
2334
2441
  return {};
2335
2442
  }
2336
2443
  }
2337
- function applyObjectTransition(context, state, working, next, journal) {
2444
+ function applyObjectTransition(context, state, working, next, nextEditedAt, journal) {
2338
2445
  const previous = working.override;
2446
+ const previousEditedAt = working.editedAt;
2339
2447
  working.override = next;
2448
+ working.editedAt = { ...nextEditedAt };
2340
2449
  try {
2341
2450
  const resolved = resolveObject(context.ontology, working);
2342
2451
  if (resolved)
@@ -2344,11 +2453,13 @@ function applyObjectTransition(context, state, working, next, journal) {
2344
2453
  validateWorkingCardinality(state.links.slots);
2345
2454
  } catch (error) {
2346
2455
  working.override = previous;
2456
+ working.editedAt = previousEditedAt;
2347
2457
  throw error;
2348
2458
  }
2349
2459
  journal?.push({
2350
2460
  restore: () => {
2351
2461
  working.override = previous;
2462
+ working.editedAt = previousEditedAt;
2352
2463
  }
2353
2464
  });
2354
2465
  }
@@ -3035,7 +3146,22 @@ function validateObjectAssertion(ontology, resolved, policy, assertion) {
3035
3146
  continue;
3036
3147
  throw new MaterializationValidationError(`Projection '${resolved.projectionId}' asserted unowned property '${assertion.ref.objectTypeId}.${propertyId}'.`);
3037
3148
  }
3038
- return { ...assertion, properties };
3149
+ const conflictResolution = resolved.definition.conflictResolution ?? { strategy: "editsWin" };
3150
+ if (conflictResolution.strategy !== "mostRecent") {
3151
+ return { kind: "object", ref: assertion.ref, properties };
3152
+ }
3153
+ if (assertion.sourceUpdatedAt === undefined) {
3154
+ throw new MaterializationValidationError(`Projection '${resolved.projectionId}' requires source update timestamp ` + `'${conflictResolution.sourceTimestamp}' on every object assertion.`);
3155
+ }
3156
+ const primaryPropertyId = ontology.getPrimaryPropertyId(assertion.ref.objectTypeId);
3157
+ const absentSourcePropertyIds = [...owned].filter((propertyId) => propertyId !== primaryPropertyId && !Object.hasOwn(properties, propertyId)).sort();
3158
+ return {
3159
+ kind: "object",
3160
+ ref: assertion.ref,
3161
+ properties,
3162
+ sourceUpdatedAt: assertion.sourceUpdatedAt,
3163
+ ...absentSourcePropertyIds.length === 0 ? {} : { absentSourcePropertyIds }
3164
+ };
3039
3165
  }
3040
3166
  function validateLinkAssertion(ontology, resolved, policy, assertion) {
3041
3167
  const ownershipKey = linkOwnershipKey(assertion.ref.source.objectTypeId, assertion.ref.linkId);
@@ -3283,6 +3409,7 @@ function resolveReplacementObject(context, state) {
3283
3409
  primaryPropertyId: context.ontology.getPrimaryPropertyId(state.ref.objectTypeId),
3284
3410
  source: state.candidateSource,
3285
3411
  override: state.override?.value ?? null,
3412
+ editedAt: storedObjectEditedAt(state.override),
3286
3413
  latestTelemetry: state.latestTelemetry
3287
3414
  });
3288
3415
  }
@@ -4171,4 +4298,4 @@ function createOntologyMaterializer(input) {
4171
4298
  }
4172
4299
  export { computeOntologyRevision, computeProjectionRevision, computeProjectionOwnershipHash, ProjectionRegistry, createActionIdempotencyKey, createRuntimeIdempotencyKey, createProjectionIdempotencyKey, createRuntimeTelemetryIdempotencyKey, createProjectionTelemetryIdempotencyKey, createCommitIdentity, timestampCommitIdentity, createTimedCommitIdentity, createProjectionMaterializationId, createLinkScopeFingerprint, normalizeJsonProperties, normalizeOntologyEditCommit, normalizeProjectionExecution, normalizeProjectionSourceEntry, normalizeTelemetryAppend, OntologyMaterializer, createOntologyMaterializer };
4173
4300
 
4174
- //# debugId=E9E5C3996D8CF74864756E2164756E21
4301
+ //# debugId=E68F145E5D3DE1BE64756E2164756E21