@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
@@ -25,6 +25,7 @@ import {
25
25
  resolveEffectiveLink,
26
26
  resolveEffectiveLinkSlot,
27
27
  resolveEffectiveObject,
28
+ storedObjectEditedAt,
28
29
  usableLinkSlotOverride,
29
30
  } from "../effective/resolve"
30
31
  import { linkCardinality } from "./read-set"
@@ -33,6 +34,8 @@ export interface WorkingObject {
33
34
  source: MaterializationObjectState["source"]
34
35
  originalOverride: StoredObjectOverride | null
35
36
  override: ObjectOverride | null
37
+ originalEditedAt: Readonly<Record<string, string>>
38
+ editedAt: Record<string, string>
36
39
  before: MaterializationObjectState["effective"]
37
40
  latestTelemetry: StoredTelemetryPoint[]
38
41
  }
@@ -112,11 +115,14 @@ function workingLinkSlotFromState(scope: MaterializationLinkScopeState): Working
112
115
  }
113
116
 
114
117
  export function workingObjectFromState(object: MaterializationObjectState): WorkingObject {
118
+ const editedAt = storedObjectEditedAt(object.override)
115
119
  return {
116
120
  ref: object.ref,
117
121
  source: object.source,
118
122
  originalOverride: object.override,
119
123
  override: object.override?.value ?? null,
124
+ originalEditedAt: editedAt,
125
+ editedAt: { ...editedAt },
120
126
  before: object.effective,
121
127
  latestTelemetry: [...object.latestTelemetry],
122
128
  }
@@ -141,6 +147,7 @@ export function resolveObject(
141
147
  primaryPropertyId: ontology.getPrimaryPropertyId(working.ref.objectTypeId),
142
148
  source: working.source,
143
149
  override: working.override,
150
+ editedAt: working.editedAt,
144
151
  latestTelemetry: working.latestTelemetry,
145
152
  })
146
153
  }
@@ -11,6 +11,7 @@ import type {
11
11
  import { linkRefKey } from "../../materialization/refs"
12
12
  import type {
13
13
  StoredLinkSlotOverride,
14
+ StoredObjectOverride,
14
15
  StoredSourceLinkAssertion,
15
16
  StoredSourceObjectAssertion,
16
17
  StoredTelemetryPoint,
@@ -46,9 +47,10 @@ export function resolveEffectiveObject(input: {
46
47
  readonly primaryPropertyId: string
47
48
  readonly source: StoredSourceObjectAssertion | null
48
49
  readonly override: ObjectOverride | null
50
+ readonly editedAt: Readonly<Record<string, string>>
49
51
  readonly latestTelemetry: readonly StoredTelemetryPoint[]
50
52
  }): ResolvedObjectValue | null {
51
- const properties = resolveObjectAuthority(input.source, input.override)
53
+ const properties = resolveObjectAuthority(input.source, input.override, input.editedAt)
52
54
  if (!properties) return null
53
55
 
54
56
  properties[input.primaryPropertyId] = input.ref.primaryId
@@ -58,31 +60,98 @@ export function resolveEffectiveObject(input: {
58
60
 
59
61
  function resolveObjectAuthority(
60
62
  source: StoredSourceObjectAssertion | null,
61
- override: ObjectOverride | null
63
+ override: ObjectOverride | null,
64
+ editedAt: Readonly<Record<string, string>>
62
65
  ): Record<string, JsonValue> | null {
63
66
  switch (override?.kind) {
64
67
  case "delete":
65
68
  return null
66
69
  case "create":
67
- return { ...override.properties }
70
+ if (!source) return { ...override.properties }
71
+ return resolveSourceAndEdits(source, override, editedAt)
68
72
  case "patch":
69
73
  if (!source) return null
70
- return applyObjectPatch(source.assertion.properties, override)
74
+ return resolveSourceAndEdits(source, override, editedAt)
71
75
  default:
72
76
  if (!source) return null
73
77
  return { ...source.assertion.properties }
74
78
  }
75
79
  }
76
80
 
77
- function applyObjectPatch(
78
- sourceProperties: Readonly<Record<string, JsonValue>>,
79
- override: Extract<ObjectOverride, { readonly kind: "patch" }>
81
+ function resolveSourceAndEdits(
82
+ source: StoredSourceObjectAssertion,
83
+ override: Exclude<ObjectOverride, { readonly kind: "delete" }>,
84
+ editedAt: Readonly<Record<string, string>>
80
85
  ): Record<string, JsonValue> {
81
- const properties = { ...sourceProperties, ...override.set }
82
- for (const propertyId of override.unset) delete properties[propertyId]
86
+ const properties = { ...source.assertion.properties }
87
+ const absentSourcePropertyIds = new Set(source.assertion.absentSourcePropertyIds ?? [])
88
+
89
+ for (const propertyId of objectEditCandidateIds(override, editedAt)) {
90
+ const edit = objectEditCandidate(override, propertyId)
91
+ const editTimestamp = editedAt[propertyId]
92
+ const hasSourceCandidate =
93
+ Object.hasOwn(source.assertion.properties, propertyId) ||
94
+ absentSourcePropertyIds.has(propertyId)
95
+ const sourceWins =
96
+ hasSourceCandidate &&
97
+ source.assertion.sourceUpdatedAt !== undefined &&
98
+ editTimestamp !== undefined &&
99
+ source.assertion.sourceUpdatedAt.localeCompare(editTimestamp) >= 0
100
+
101
+ if (!sourceWins) applyObjectEditCandidate(properties, propertyId, edit)
102
+ }
83
103
  return properties
84
104
  }
85
105
 
106
+ interface ObjectPropertyCandidate {
107
+ readonly present: boolean
108
+ readonly value?: JsonValue
109
+ }
110
+
111
+ function objectEditCandidateIds(
112
+ override: Exclude<ObjectOverride, { readonly kind: "delete" }>,
113
+ editedAt: Readonly<Record<string, string>>
114
+ ): string[] {
115
+ const ids = new Set(Object.keys(editedAt))
116
+ if (override.kind === "create") {
117
+ for (const propertyId of Object.keys(override.properties)) ids.add(propertyId)
118
+ } else {
119
+ for (const propertyId of Object.keys(override.set)) ids.add(propertyId)
120
+ for (const propertyId of override.unset) ids.add(propertyId)
121
+ }
122
+ return [...ids]
123
+ }
124
+
125
+ function objectEditCandidate(
126
+ override: Exclude<ObjectOverride, { readonly kind: "delete" }>,
127
+ propertyId: string
128
+ ): ObjectPropertyCandidate {
129
+ const values = override.kind === "create" ? override.properties : override.set
130
+ return Object.hasOwn(values, propertyId)
131
+ ? { present: true, value: values[propertyId] }
132
+ : { present: false }
133
+ }
134
+
135
+ function applyObjectEditCandidate(
136
+ properties: Record<string, JsonValue>,
137
+ propertyId: string,
138
+ candidate: ObjectPropertyCandidate
139
+ ): void {
140
+ if (candidate.present) properties[propertyId] = candidate.value as JsonValue
141
+ else delete properties[propertyId]
142
+ }
143
+
144
+ export function storedObjectEditedAt(stored: StoredObjectOverride | null): Record<string, string> {
145
+ if (!stored || stored.value.kind === "delete") return {}
146
+ const editedAt = { ...(stored.editedAt ?? {}) }
147
+ const candidateIds =
148
+ stored.value.kind === "create"
149
+ ? Object.keys(stored.value.properties)
150
+ : [...Object.keys(stored.value.set), ...stored.value.unset]
151
+ for (const propertyId of candidateIds) editedAt[propertyId] ??= stored.updatedAt
152
+ return editedAt
153
+ }
154
+
86
155
  function applyLatestTelemetry(
87
156
  properties: Record<string, JsonValue>,
88
157
  latestTelemetry: readonly StoredTelemetryPoint[]
@@ -22,7 +22,8 @@ export function appendObjectOverrideWork(
22
22
  ): void {
23
23
  if (
24
24
  stableJsonStringify(working.originalOverride?.value ?? null) ===
25
- stableJsonStringify(working.override)
25
+ stableJsonStringify(working.override) &&
26
+ stableJsonStringify(working.originalEditedAt) === stableJsonStringify(working.editedAt)
26
27
  ) {
27
28
  return
28
29
  }
@@ -32,6 +33,7 @@ export function appendObjectOverrideWork(
32
33
  value: {
33
34
  ref: working.ref,
34
35
  value: working.override,
36
+ editedAt: working.editedAt,
35
37
  expectedLastCommitId: working.originalOverride?.lastCommitId ?? null,
36
38
  lastCommitId: identity.commitId,
37
39
  updatedAt: identity.committedAt,
@@ -118,7 +118,29 @@ function validateObjectAssertion(
118
118
  `Projection '${resolved.projectionId}' asserted unowned property '${assertion.ref.objectTypeId}.${propertyId}'.`
119
119
  )
120
120
  }
121
- return { ...assertion, properties }
121
+ const conflictResolution = resolved.definition.conflictResolution ?? { strategy: "editsWin" }
122
+ if (conflictResolution.strategy !== "mostRecent") {
123
+ return { kind: "object", ref: assertion.ref, properties }
124
+ }
125
+ if (assertion.sourceUpdatedAt === undefined) {
126
+ throw new MaterializationValidationError(
127
+ `Projection '${resolved.projectionId}' requires source update timestamp ` +
128
+ `'${conflictResolution.sourceTimestamp}' on every object assertion.`
129
+ )
130
+ }
131
+ const primaryPropertyId = ontology.getPrimaryPropertyId(assertion.ref.objectTypeId)
132
+ const absentSourcePropertyIds = [...owned]
133
+ .filter(
134
+ (propertyId) => propertyId !== primaryPropertyId && !Object.hasOwn(properties, propertyId)
135
+ )
136
+ .sort()
137
+ return {
138
+ kind: "object",
139
+ ref: assertion.ref,
140
+ properties,
141
+ sourceUpdatedAt: assertion.sourceUpdatedAt,
142
+ ...(absentSourcePropertyIds.length === 0 ? {} : { absentSourcePropertyIds }),
143
+ }
122
144
  }
123
145
 
124
146
  function validateLinkAssertion(
@@ -33,6 +33,7 @@ import {
33
33
  resolveEffectiveLink,
34
34
  resolveEffectiveLinkSlotMember,
35
35
  resolveEffectiveObject,
36
+ storedObjectEditedAt,
36
37
  usableLinkSlotOverride,
37
38
  } from "../effective/resolve"
38
39
  import { validateEffectiveObject } from "../effective/validate"
@@ -374,6 +375,7 @@ function resolveReplacementObject(
374
375
  primaryPropertyId: context.ontology.getPrimaryPropertyId(state.ref.objectTypeId),
375
376
  source: state.candidateSource,
376
377
  override: state.override?.value ?? null,
378
+ editedAt: storedObjectEditedAt(state.override),
377
379
  latestTelemetry: state.latestTelemetry,
378
380
  })
379
381
  }
@@ -25,6 +25,7 @@ import {
25
25
  projectionEntityKey,
26
26
  telemetryPointKey,
27
27
  } from "../../materialization/refs"
28
+ import { parseDatasetTimestamp } from "../../projections/timestamp"
28
29
 
29
30
  export function normalizeJsonProperties(
30
31
  properties: Readonly<Record<string, unknown>>,
@@ -67,6 +68,16 @@ function normalizeTimestamp(value: string, label: string): string {
67
68
  return new Date(milliseconds).toISOString()
68
69
  }
69
70
 
71
+ function normalizeProjectionSourceTimestamp(value: string): string {
72
+ const timestamp = parseDatasetTimestamp(value)
73
+ if (!timestamp) {
74
+ throw new MaterializationValidationError(
75
+ "Projection source update timestamp must be a valid timestamp."
76
+ )
77
+ }
78
+ return timestamp.toISOString()
79
+ }
80
+
70
81
  function normalizePropertyIds(values: readonly string[], label: string): readonly string[] {
71
82
  const seen = new Set<string>()
72
83
  const normalized: string[] = []
@@ -347,6 +358,11 @@ function normalizeProjectionAssertion(
347
358
  kind: "object",
348
359
  ref: normalizeObjectRef(assertion.ref),
349
360
  properties: normalizeJsonProperties(assertion.properties),
361
+ ...(assertion.sourceUpdatedAt !== undefined
362
+ ? {
363
+ sourceUpdatedAt: normalizeProjectionSourceTimestamp(assertion.sourceUpdatedAt),
364
+ }
365
+ : {}),
350
366
  })
351
367
  : Object.freeze({
352
368
  kind: "link",
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import type {
8
+ DatasetColumnDefinitionOf,
8
9
  DatasetColumnNameOf,
9
10
  DatasetColumnType,
10
11
  DatasetColumnTypeOf,
@@ -21,6 +22,7 @@ import type {
21
22
  ObjectProjectionTarget,
22
23
  ProjectionDefinition,
23
24
  ProjectionTarget,
25
+ SourceEditConflictResolution,
24
26
  TelemetryProjectionDefinition,
25
27
  } from "./types"
26
28
  import {
@@ -153,6 +155,20 @@ type DateLikeDatasetColumnNameOf<TDataset extends DatasetDefinition> =
153
155
  : never
154
156
  }[DatasetColumnNameOf<TDataset>]
155
157
 
158
+ type TimestampDatasetColumnNameOf<TDataset extends DatasetDefinition> =
159
+ string extends DatasetColumnNameOf<TDataset>
160
+ ? string
161
+ : {
162
+ [TColumnName in DatasetColumnNameOf<TDataset>]: DatasetColumnTypeOf<
163
+ TDataset,
164
+ TColumnName
165
+ > extends "timestamp"
166
+ ? DatasetColumnDefinitionOf<TDataset, TColumnName> extends { readonly nullable: true }
167
+ ? never
168
+ : TColumnName
169
+ : never
170
+ }[DatasetColumnNameOf<TDataset>]
171
+
156
172
  type ProjectionMappingFor<TObjectType extends ObjectType, TDataset extends DatasetDefinition> = {
157
173
  readonly [TPropertyId in PropertyIdOf<TObjectType>]?: DatasetColumnNameCompatibleWithSchema<
158
174
  TDataset,
@@ -239,7 +255,7 @@ type ProjectionForeignKeyTokenInput<
239
255
  )
240
256
  }
241
257
 
242
- type ProjectionForeignKeyInput<
258
+ export type ProjectionForeignKeyInput<
243
259
  TObjectType extends ObjectType,
244
260
  TDataset extends DatasetDefinition,
245
261
  TLinkId extends LinkIdOf<TObjectType>,
@@ -280,16 +296,27 @@ interface ObjectProjectionMappingBuilder<
280
296
  > {
281
297
  properties<const TMapping extends ProjectionMappingFor<TObjectType, TDataset>>(
282
298
  mapping: ExactProjectionMapping<TObjectType, TMapping>
283
- ): ObjectProjectionDefinition & ObjectProjectionLinkBuilder<TObjectType, TDataset>
299
+ ): ObjectProjectionBuilder<TObjectType, TDataset>
284
300
  }
285
301
 
286
- interface ObjectProjectionLinkBuilder<
302
+ export type ObjectProjectionConflictResolution<TDataset extends DatasetDefinition> =
303
+ | { readonly strategy: "editsWin" }
304
+ | {
305
+ readonly strategy: "mostRecent"
306
+ readonly sourceTimestamp: TimestampDatasetColumnNameOf<TDataset>
307
+ }
308
+
309
+ export interface ObjectProjectionBuilder<
287
310
  TObjectType extends ObjectType,
288
311
  TDataset extends DatasetDefinition,
289
- > {
312
+ > extends ObjectProjectionDefinition {
313
+ readonly conflictResolution: SourceEditConflictResolution
290
314
  withLinks(
291
315
  mapping: { [K in LinkIdOf<TObjectType>]?: ProjectionForeignKeyInput<TObjectType, TDataset, K> }
292
- ): ObjectProjectionDefinition
316
+ ): ObjectProjectionBuilder<TObjectType, TDataset>
317
+ resolveConflicts(
318
+ resolution: ObjectProjectionConflictResolution<TDataset>
319
+ ): ObjectProjectionBuilder<TObjectType, TDataset>
293
320
  }
294
321
 
295
322
  /**
@@ -352,41 +379,17 @@ function buildObjectProjection<const TObjectType extends ObjectType>(
352
379
  return {
353
380
  properties<const TMapping extends ProjectionMappingFor<TObjectType, TDataset>>(
354
381
  mapping: ExactProjectionMapping<TObjectType, TMapping>
355
- ): ObjectProjectionDefinition & ObjectProjectionLinkBuilder<TObjectType, TDataset> {
382
+ ): ObjectProjectionBuilder<TObjectType, TDataset> {
356
383
  const propertyMapping = mapping as Record<string, string>
357
384
  validatePropertyMapping(objectType, propertyMapping)
358
385
 
359
- const definition: ObjectProjectionDefinition = {
360
- _tag: "ObjectProjectionDefinition",
386
+ return objectProjectionBuilder({
361
387
  id,
362
- objectTypeId: objectType.id,
388
+ objectType,
363
389
  datasetId,
364
- properties: { ...propertyMapping },
390
+ propertyMapping,
365
391
  links: {},
366
- }
367
-
368
- return Object.assign(definition, {
369
- withLinks(
370
- linkMapping: {
371
- [K in LinkIdOf<TObjectType>]?: ProjectionForeignKeyInput<TObjectType, TDataset, K>
372
- }
373
- ): ObjectProjectionDefinition {
374
- const loweredLinkMapping = lowerForeignKeyMapping(linkMapping)
375
- const validatedLinks = validateAndLowerLinkMapping(
376
- objectType,
377
- loweredLinkMapping,
378
- propertyMapping
379
- )
380
-
381
- return {
382
- _tag: "ObjectProjectionDefinition",
383
- id,
384
- objectTypeId: objectType.id,
385
- datasetId,
386
- properties: { ...propertyMapping },
387
- links: validatedLinks,
388
- }
389
- },
392
+ conflictResolution: { strategy: "editsWin" },
390
393
  })
391
394
  },
392
395
  }
@@ -394,6 +397,60 @@ function buildObjectProjection<const TObjectType extends ObjectType>(
394
397
  }
395
398
  }
396
399
 
400
+ function objectProjectionBuilder<
401
+ TObjectType extends ObjectType,
402
+ TDataset extends DatasetDefinition,
403
+ >(input: {
404
+ readonly id: string
405
+ readonly objectType: TObjectType
406
+ readonly datasetId: string
407
+ readonly propertyMapping: Readonly<Record<string, string>>
408
+ readonly links: Readonly<Record<string, ForeignKeyDescriptor>>
409
+ readonly conflictResolution: SourceEditConflictResolution
410
+ }): ObjectProjectionBuilder<TObjectType, TDataset> {
411
+ const definition: ObjectProjectionDefinition & {
412
+ readonly conflictResolution: SourceEditConflictResolution
413
+ } = {
414
+ _tag: "ObjectProjectionDefinition",
415
+ id: input.id,
416
+ objectTypeId: input.objectType.id,
417
+ datasetId: input.datasetId,
418
+ properties: { ...input.propertyMapping },
419
+ links: { ...input.links },
420
+ conflictResolution: { ...input.conflictResolution },
421
+ }
422
+
423
+ return Object.assign(definition, {
424
+ withLinks(
425
+ linkMapping: {
426
+ [K in LinkIdOf<TObjectType>]?: ProjectionForeignKeyInput<TObjectType, TDataset, K>
427
+ }
428
+ ): ObjectProjectionBuilder<TObjectType, TDataset> {
429
+ const loweredLinkMapping = lowerForeignKeyMapping(linkMapping)
430
+ const validatedLinks = validateAndLowerLinkMapping(
431
+ input.objectType,
432
+ loweredLinkMapping,
433
+ input.propertyMapping as Record<string, string>
434
+ )
435
+ return objectProjectionBuilder({ ...input, links: validatedLinks })
436
+ },
437
+ resolveConflicts(
438
+ resolution: ObjectProjectionConflictResolution<TDataset>
439
+ ): ObjectProjectionBuilder<TObjectType, TDataset> {
440
+ validateConflictResolution(resolution)
441
+ return objectProjectionBuilder({
442
+ ...input,
443
+ conflictResolution: resolution as SourceEditConflictResolution,
444
+ })
445
+ },
446
+ })
447
+ }
448
+
449
+ function validateConflictResolution(resolution: SourceEditConflictResolution): void {
450
+ if (resolution.strategy === "editsWin") return
451
+ assertNonEmpty(resolution.sourceTimestamp, "source timestamp field")
452
+ }
453
+
397
454
  // ── Telemetry projection ────────────────────────────────────
398
455
 
399
456
  interface TelemetryProjectionSourceBuilder<TPropertyToken extends TelemetryPropertyToken> {
@@ -672,10 +729,18 @@ export function isObjectProjectionDefinition(value: unknown): value is ObjectPro
672
729
  typeof value.objectTypeId === "string" &&
673
730
  typeof value.datasetId === "string" &&
674
731
  isRecord(value.properties) &&
675
- isRecord(value.links)
732
+ isRecord(value.links) &&
733
+ isSourceEditConflictResolution(value.conflictResolution)
676
734
  )
677
735
  }
678
736
 
737
+ function isSourceEditConflictResolution(value: unknown): boolean {
738
+ if (value === undefined) return true
739
+ if (!isRecord(value) || typeof value.strategy !== "string") return false
740
+ if (value.strategy === "editsWin") return value.sourceTimestamp === undefined
741
+ return value.strategy === "mostRecent" && typeof value.sourceTimestamp === "string"
742
+ }
743
+
679
744
  /** Runtime type guard for {@link LinkProjectionDefinition}. */
680
745
  export function isLinkProjectionDefinition(value: unknown): value is LinkProjectionDefinition {
681
746
  if (!isRecord(value)) return false
@@ -1,5 +1,10 @@
1
1
  // ── Builders ─────────────────────────────────────────────────
2
2
 
3
+ export type {
4
+ ObjectProjectionBuilder,
5
+ ObjectProjectionConflictResolution,
6
+ ProjectionForeignKeyInput,
7
+ } from "./builders"
3
8
  export {
4
9
  categorizeProjections,
5
10
  defineProjection,
@@ -24,6 +29,7 @@ export type {
24
29
  ProjectionKind,
25
30
  ProjectionTarget,
26
31
  ProjectionTargetByKind,
32
+ SourceEditConflictResolution,
27
33
  TelemetryProjectionDefinition,
28
34
  } from "./types"
29
35
  // ── Validation ───────────────────────────────────────────────
@@ -7,7 +7,9 @@ import type { ProjectionDispatchDescriptor } from "./types"
7
7
  *
8
8
  * `projectionTargetOf` derives the object types a projection writes, to record them on the run.
9
9
  * `validateTelemetryProjectionFieldMapping` re-checks a telemetry mapping against the dataset schema
10
- * before writing. Both are plumbing, so they live here instead of on the `@sixb/core` root.
10
+ * before writing. `parseDatasetTimestamp` keeps worker timestamps aligned with the materializer's
11
+ * authoritative validation. These are plumbing, so they live here instead of on the
12
+ * `@sixb/core` root.
11
13
  */
12
14
  export { projectionTargetOf } from "./builders"
13
15
  export {
@@ -27,6 +29,7 @@ export {
27
29
  ProjectionRunDispatcher,
28
30
  } from "./run-dispatch"
29
31
  export { createProjectionRunId } from "./run-id"
32
+ export { parseDatasetTimestamp } from "./timestamp"
30
33
  export { validateTelemetryProjectionFieldMapping } from "./validation"
31
34
 
32
35
  export type { ProjectionDispatchDescriptor, ProjectionMaterializationIdentity, ProjectionRegistry }
@@ -241,6 +241,13 @@ function cloneObjectProjectionDefinition(
241
241
  },
242
242
  ])
243
243
  ),
244
+ conflictResolution:
245
+ projection.conflictResolution?.strategy === "mostRecent"
246
+ ? {
247
+ strategy: "mostRecent",
248
+ sourceTimestamp: projection.conflictResolution.sourceTimestamp,
249
+ }
250
+ : { strategy: "editsWin" },
244
251
  }
245
252
  }
246
253
 
@@ -147,6 +147,7 @@ function normalizeProjectionDefinition(projection: ProjectionDefinition): JsonVa
147
147
  },
148
148
  ])
149
149
  ),
150
+ conflictResolution: projection.conflictResolution ?? { strategy: "editsWin" },
150
151
  }
151
152
  }
152
153
  if (projection._tag === "LinkProjectionDefinition") {
@@ -177,6 +178,9 @@ function projectionColumns(projection: ProjectionDefinition): Set<string> {
177
178
  ...Object.values(projection.links).flatMap((link) =>
178
179
  link.sourceField === undefined ? [] : [link.sourceField]
179
180
  ),
181
+ ...(projection.conflictResolution?.strategy === "mostRecent"
182
+ ? [projection.conflictResolution.sourceTimestamp]
183
+ : []),
180
184
  ])
181
185
  }
182
186
  if (projection._tag === "LinkProjectionDefinition") {
@@ -0,0 +1,66 @@
1
+ const DATASET_TIMESTAMP =
2
+ /^(\d{4})-(\d{1,2})-(\d{1,2})(?:[ T](\d{1,2}):(\d{2})(?::(\d{2})(?:\.(\d{1,9}))?)?)?([zZ]|[+-]\d{2}:?\d{2})?$/
3
+
4
+ interface TimestampComponents {
5
+ readonly year: number
6
+ readonly month: number
7
+ readonly day: number
8
+ readonly hour: number
9
+ readonly minute: number
10
+ readonly second: number
11
+ readonly milliseconds: number
12
+ }
13
+
14
+ /** Parses a projection dataset timestamp, interpreting zone-less values as UTC. */
15
+ export function parseDatasetTimestamp(value: unknown): Date | null {
16
+ if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value
17
+ if (typeof value !== "string") return null
18
+
19
+ const match = DATASET_TIMESTAMP.exec(value.trim())
20
+ if (!match) return null
21
+ const [, year, month, day, hour, minute, second, fraction, zone] = match
22
+ const offsetMinutes = parseZoneOffsetMinutes(zone)
23
+ if (offsetMinutes === null) return null
24
+
25
+ const components: TimestampComponents = {
26
+ year: Number(year),
27
+ month: Number(month),
28
+ day: Number(day),
29
+ hour: hour === undefined ? 0 : Number(hour),
30
+ minute: minute === undefined ? 0 : Number(minute),
31
+ second: second === undefined ? 0 : Number(second),
32
+ milliseconds: fraction === undefined ? 0 : Math.trunc(Number(`0.${fraction}`) * 1000),
33
+ }
34
+ const wallClock = Date.UTC(
35
+ components.year,
36
+ components.month - 1,
37
+ components.day,
38
+ components.hour,
39
+ components.minute,
40
+ components.second,
41
+ components.milliseconds
42
+ )
43
+ if (!wallClockMatchesComponents(wallClock, components)) return null
44
+ return new Date(wallClock - offsetMinutes * 60_000)
45
+ }
46
+
47
+ function parseZoneOffsetMinutes(zone: string | undefined): number | null {
48
+ if (zone === undefined || zone === "Z" || zone === "z") return 0
49
+ const digits = zone.slice(1).replace(":", "")
50
+ const hours = Number(digits.slice(0, 2))
51
+ const minutes = Number(digits.slice(2, 4))
52
+ if (hours > 23 || minutes > 59) return null
53
+ return (zone[0] === "-" ? -1 : 1) * (hours * 60 + minutes)
54
+ }
55
+
56
+ function wallClockMatchesComponents(wallClock: number, components: TimestampComponents): boolean {
57
+ const date = new Date(wallClock)
58
+ return (
59
+ date.getUTCFullYear() === components.year &&
60
+ date.getUTCMonth() === components.month - 1 &&
61
+ date.getUTCDate() === components.day &&
62
+ date.getUTCHours() === components.hour &&
63
+ date.getUTCMinutes() === components.minute &&
64
+ date.getUTCSeconds() === components.second
65
+ )
66
+ }
@@ -39,6 +39,11 @@ export interface ForeignKeyDescriptor {
39
39
  readonly targetObjectTypeId: string
40
40
  }
41
41
 
42
+ /** Resolves competing source-projection and managed-edit values for projected properties. */
43
+ export type SourceEditConflictResolution =
44
+ | { readonly strategy: "editsWin" }
45
+ | { readonly strategy: "mostRecent"; readonly sourceTimestamp: string }
46
+
42
47
  /**
43
48
  * Lowered, serializable definition for projecting a dataset into object instances.
44
49
  *
@@ -56,6 +61,8 @@ export interface ObjectProjectionDefinition {
56
61
  readonly properties: Readonly<Record<string, string>>
57
62
  /** Maps link id -> FK descriptor. Empty `{}` when no FK links are projected. */
58
63
  readonly links: Readonly<Record<string, ForeignKeyDescriptor>>
64
+ /** Defaults to `editsWin` when omitted by a legacy lowered definition. */
65
+ readonly conflictResolution?: SourceEditConflictResolution
59
66
  }
60
67
 
61
68
  /**
@@ -315,6 +315,30 @@ export function validateProjectionsAtStartup(
315
315
  }
316
316
 
317
317
  const datasetColumnNames = new Set(dataset.schema.columns.map((column) => column.name))
318
+ const conflictResolution = projection.conflictResolution ?? { strategy: "editsWin" }
319
+ if (conflictResolution.strategy === "mostRecent") {
320
+ const sourceTimestamp = dataset.schema.columns.find(
321
+ (column) => column.name === conflictResolution.sourceTimestamp
322
+ )
323
+ if (!sourceTimestamp) {
324
+ throw new ProjectionValidationError(
325
+ `${prefix}: source timestamp "${conflictResolution.sourceTimestamp}" references unknown ` +
326
+ `dataset column on dataset "${projection.datasetId}"`
327
+ )
328
+ }
329
+ if (sourceTimestamp.type !== "timestamp") {
330
+ throw new ProjectionValidationError(
331
+ `${prefix}: source timestamp "${conflictResolution.sourceTimestamp}" must reference a ` +
332
+ `timestamp dataset column`
333
+ )
334
+ }
335
+ if (sourceTimestamp.nullable === true) {
336
+ throw new ProjectionValidationError(
337
+ `${prefix}: source timestamp "${conflictResolution.sourceTimestamp}" must reference a ` +
338
+ `non-null timestamp dataset column`
339
+ )
340
+ }
341
+ }
318
342
  const propertyIds = new Set(objectType.properties.map((p) => p.id))
319
343
  for (const [propId, columnName] of Object.entries(projection.properties)) {
320
344
  if (!propertyIds.has(propId)) {
@@ -4,6 +4,7 @@ export { InMemoryQueues } from "./in-memory"
4
4
  export type {
5
5
  ActionQueueJobFailureCode,
6
6
  ActionRunRequestedQueueJob,
7
+ AgentAiUsageAccountingPayload,
7
8
  AgentAiUsageRecordPayload,
8
9
  AgentAiUsageRecordRequestedQueueJob,
9
10
  AgentQueueJob,