@abloatai/transaction 0.48.0 → 0.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (257) hide show
  1. package/CONVENTIONS.md +7 -7
  2. package/README.md +1 -1
  3. package/dist/ai-sdk/modelTools.d.ts +1 -1
  4. package/dist/ai-sdk/modelTools.js +1 -1
  5. package/dist/ai-sdk/updateTool.d.ts +2 -2
  6. package/dist/ai-sdk/updateTool.d.ts.map +1 -1
  7. package/dist/auth/bootstrapScope.d.ts +1 -1
  8. package/dist/auth/capability.d.ts +32 -10
  9. package/dist/auth/capability.d.ts.map +1 -1
  10. package/dist/auth/capability.js +9 -3
  11. package/dist/auth/capability.js.map +1 -1
  12. package/dist/auth/capabilityLifecycle.d.ts +2 -2
  13. package/dist/auth/capabilityLifecycle.d.ts.map +1 -1
  14. package/dist/auth/capabilityLifecycle.js +3 -2
  15. package/dist/auth/capabilityLifecycle.js.map +1 -1
  16. package/dist/auth/identity.d.ts +3 -0
  17. package/dist/auth/identity.d.ts.map +1 -1
  18. package/dist/auth/identity.js +13 -1
  19. package/dist/auth/identity.js.map +1 -1
  20. package/dist/auth/index.d.ts +2 -2
  21. package/dist/auth/index.d.ts.map +1 -1
  22. package/dist/auth/index.js +1 -1
  23. package/dist/auth/index.js.map +1 -1
  24. package/dist/auth/schemas.d.ts +18 -1
  25. package/dist/auth/schemas.d.ts.map +1 -1
  26. package/dist/auth/schemas.js +3 -1
  27. package/dist/auth/schemas.js.map +1 -1
  28. package/dist/auth/sessionMint.d.ts.map +1 -1
  29. package/dist/auth/sessionMint.js +0 -1
  30. package/dist/auth/sessionMint.js.map +1 -1
  31. package/dist/commitRecordRuntime.d.ts +30 -0
  32. package/dist/commitRecordRuntime.d.ts.map +1 -0
  33. package/dist/commitRecordRuntime.js +188 -0
  34. package/dist/commitRecordRuntime.js.map +1 -0
  35. package/dist/coordination/index.d.ts +3 -3
  36. package/dist/coordination/index.d.ts.map +1 -1
  37. package/dist/coordination/index.js +2 -2
  38. package/dist/coordination/index.js.map +1 -1
  39. package/dist/coordination/locator.d.ts +5 -0
  40. package/dist/coordination/locator.d.ts.map +1 -1
  41. package/dist/coordination/locator.js +12 -0
  42. package/dist/coordination/locator.js.map +1 -1
  43. package/dist/coordination/schema.d.ts +205 -15
  44. package/dist/coordination/schema.d.ts.map +1 -1
  45. package/dist/coordination/schema.js +88 -47
  46. package/dist/coordination/schema.js.map +1 -1
  47. package/dist/coordination/targetConflict.js +1 -1
  48. package/dist/durableWrites.d.ts +3 -3
  49. package/dist/durableWrites.d.ts.map +1 -1
  50. package/dist/durableWrites.js +1 -1
  51. package/dist/errorCodes.d.ts +20 -1
  52. package/dist/errorCodes.d.ts.map +1 -1
  53. package/dist/errorCodes.js +44 -10
  54. package/dist/errorCodes.js.map +1 -1
  55. package/dist/errorObservation.d.ts +82 -0
  56. package/dist/errorObservation.d.ts.map +1 -0
  57. package/dist/errorObservation.js +101 -0
  58. package/dist/errorObservation.js.map +1 -0
  59. package/dist/errors.d.ts +2 -0
  60. package/dist/errors.d.ts.map +1 -1
  61. package/dist/errors.js +6 -1
  62. package/dist/errors.js.map +1 -1
  63. package/dist/index.d.ts +5 -2
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +1 -1
  66. package/dist/index.js.map +1 -1
  67. package/dist/internal/read-set.d.ts +8 -0
  68. package/dist/internal/read-set.d.ts.map +1 -0
  69. package/dist/internal/read-set.js +7 -0
  70. package/dist/internal/read-set.js.map +1 -0
  71. package/dist/logger.d.ts +1 -1
  72. package/dist/observability.d.ts +1 -1
  73. package/dist/observability.js +1 -1
  74. package/dist/readSetContext.d.ts +65 -0
  75. package/dist/readSetContext.d.ts.map +1 -0
  76. package/dist/readSetContext.js +121 -0
  77. package/dist/readSetContext.js.map +1 -0
  78. package/dist/resources/functionalUpdate.d.ts +13 -3
  79. package/dist/resources/functionalUpdate.d.ts.map +1 -1
  80. package/dist/resources/functionalUpdate.js.map +1 -1
  81. package/dist/resources/httpResources.d.ts +23 -7
  82. package/dist/resources/httpResources.d.ts.map +1 -1
  83. package/dist/resources/modelOperations.d.ts +2 -2
  84. package/dist/resources/modelOperations.js +1 -1
  85. package/dist/resources/mutationOptions.d.ts +1 -1
  86. package/dist/resources/mutationOptions.js +1 -1
  87. package/dist/resources/where.d.ts +1 -1
  88. package/dist/resources/where.js +1 -1
  89. package/dist/resources/writeOptionsSchema.d.ts +39 -4
  90. package/dist/resources/writeOptionsSchema.d.ts.map +1 -1
  91. package/dist/resources/writeOptionsSchema.js +12 -6
  92. package/dist/resources/writeOptionsSchema.js.map +1 -1
  93. package/dist/schema/index.js +1 -1
  94. package/dist/schema/index.js.map +1 -1
  95. package/dist/schema/openapi.d.ts.map +1 -1
  96. package/dist/schema/openapi.js +41 -7
  97. package/dist/schema/openapi.js.map +1 -1
  98. package/dist/server/commit.d.ts +7 -1
  99. package/dist/server/commit.d.ts.map +1 -1
  100. package/dist/server/index.d.ts +1 -1
  101. package/dist/server/index.d.ts.map +1 -1
  102. package/dist/server/index.js +1 -1
  103. package/dist/server/index.js.map +1 -1
  104. package/dist/source/adapters/kysely.d.ts.map +1 -1
  105. package/dist/source/adapters/kysely.js +11 -4
  106. package/dist/source/adapters/kysely.js.map +1 -1
  107. package/dist/source/adapters/kyselyMutationCore.d.ts.map +1 -1
  108. package/dist/source/adapters/kyselyMutationCore.js +22 -4
  109. package/dist/source/adapters/kyselyMutationCore.js.map +1 -1
  110. package/dist/source/contract.d.ts +2 -0
  111. package/dist/source/contract.d.ts.map +1 -1
  112. package/dist/source/types.d.ts +1 -1
  113. package/dist/testing/fixtures/httpResponses.d.ts +30 -1
  114. package/dist/testing/fixtures/httpResponses.d.ts.map +1 -1
  115. package/dist/testing/fixtures/httpResponses.js +48 -1
  116. package/dist/testing/fixtures/httpResponses.js.map +1 -1
  117. package/dist/transactionLayer.d.ts +1 -1
  118. package/dist/transactions/{settlement → confirmation}/commitEnvelope.d.ts +4 -2
  119. package/dist/transactions/confirmation/commitEnvelope.d.ts.map +1 -0
  120. package/dist/transactions/{settlement → confirmation}/commitEnvelope.js +8 -3
  121. package/dist/transactions/confirmation/commitEnvelope.js.map +1 -0
  122. package/dist/transactions/confirmation/httpCommitEnvelope.d.ts.map +1 -0
  123. package/dist/transactions/confirmation/httpCommitEnvelope.js.map +1 -0
  124. package/dist/transactions/confirmation/idempotencyKey.d.ts.map +1 -0
  125. package/dist/transactions/confirmation/idempotencyKey.js.map +1 -0
  126. package/dist/transactions/{settlement → confirmation}/pendingWrite.d.ts +3 -2
  127. package/dist/transactions/confirmation/pendingWrite.d.ts.map +1 -0
  128. package/dist/transactions/confirmation/pendingWrite.js.map +1 -0
  129. package/dist/transport/commitFrames.d.ts +1 -0
  130. package/dist/transport/commitFrames.d.ts.map +1 -1
  131. package/dist/transport/commitFrames.js +8 -7
  132. package/dist/transport/commitFrames.js.map +1 -1
  133. package/dist/transport/connectionManager.d.ts +1 -1
  134. package/dist/transport/connectionManager.js +1 -1
  135. package/dist/transport/httpClient.d.ts +23 -9
  136. package/dist/transport/httpClient.d.ts.map +1 -1
  137. package/dist/transport/httpClient.js +137 -15
  138. package/dist/transport/httpClient.js.map +1 -1
  139. package/dist/transport/httpOptions.d.ts +9 -1
  140. package/dist/transport/httpOptions.d.ts.map +1 -1
  141. package/dist/transport/httpTransport.d.ts +3 -0
  142. package/dist/transport/httpTransport.d.ts.map +1 -1
  143. package/dist/transport/httpTransport.js +109 -22
  144. package/dist/transport/httpTransport.js.map +1 -1
  145. package/dist/transport/wsFrameHandlers.d.ts +3 -1
  146. package/dist/transport/wsFrameHandlers.d.ts.map +1 -1
  147. package/dist/transport/wsFrameHandlers.js +38 -17
  148. package/dist/transport/wsFrameHandlers.js.map +1 -1
  149. package/dist/transport/wsTransport.d.ts +3 -0
  150. package/dist/transport/wsTransport.d.ts.map +1 -1
  151. package/dist/transport/wsTransport.js +32 -1
  152. package/dist/transport/wsTransport.js.map +1 -1
  153. package/dist/utils/json.d.ts +1 -1
  154. package/dist/utils/json.js +1 -1
  155. package/dist/wire/accountResponses.d.ts +1 -1
  156. package/dist/wire/auth.d.ts +2 -2
  157. package/dist/wire/commit.d.ts +1652 -213
  158. package/dist/wire/commit.d.ts.map +1 -1
  159. package/dist/wire/commit.js +196 -132
  160. package/dist/wire/commit.js.map +1 -1
  161. package/dist/wire/delta.d.ts +1 -1
  162. package/dist/wire/errorEnvelope.d.ts +1 -0
  163. package/dist/wire/errorEnvelope.d.ts.map +1 -1
  164. package/dist/wire/errorEnvelope.js +1 -0
  165. package/dist/wire/errorEnvelope.js.map +1 -1
  166. package/dist/wire/feedCursor.d.ts +1 -1
  167. package/dist/wire/feedCursor.js +1 -1
  168. package/dist/wire/feedEvent.d.ts +3 -3
  169. package/dist/wire/frames.d.ts +8 -4
  170. package/dist/wire/frames.d.ts.map +1 -1
  171. package/dist/wire/frames.js +6 -3
  172. package/dist/wire/frames.js.map +1 -1
  173. package/dist/wire/inboundFrames.d.ts +44 -4
  174. package/dist/wire/inboundFrames.d.ts.map +1 -1
  175. package/dist/wire/index.d.ts +6 -4
  176. package/dist/wire/index.d.ts.map +1 -1
  177. package/dist/wire/index.js +6 -5
  178. package/dist/wire/index.js.map +1 -1
  179. package/dist/wire/modelMutations.d.ts +35 -0
  180. package/dist/wire/modelMutations.d.ts.map +1 -1
  181. package/dist/wire/modelMutations.js +9 -2
  182. package/dist/wire/modelMutations.js.map +1 -1
  183. package/dist/wire/modelResponses.d.ts +12 -1
  184. package/dist/wire/modelResponses.d.ts.map +1 -1
  185. package/dist/wire/modelResponses.js +12 -1
  186. package/dist/wire/modelResponses.js.map +1 -1
  187. package/package.json +12 -2
  188. package/src/ai-sdk/modelTools.ts +1 -1
  189. package/src/ai-sdk/updateTool.ts +2 -2
  190. package/src/auth/bootstrapScope.ts +1 -1
  191. package/src/auth/capability.ts +13 -4
  192. package/src/auth/capabilityLifecycle.ts +3 -2
  193. package/src/auth/identity.ts +16 -1
  194. package/src/auth/index.ts +2 -0
  195. package/src/auth/schemas.ts +3 -0
  196. package/src/auth/sessionMint.ts +0 -1
  197. package/src/commitRecordRuntime.ts +245 -0
  198. package/src/coordination/index.ts +24 -1
  199. package/src/coordination/locator.ts +17 -0
  200. package/src/coordination/schema.ts +114 -49
  201. package/src/coordination/targetConflict.ts +1 -1
  202. package/src/durableWrites.ts +3 -3
  203. package/src/errorCodes.ts +107 -11
  204. package/src/errorObservation.ts +121 -0
  205. package/src/errors.ts +5 -1
  206. package/src/index.ts +8 -1
  207. package/src/internal/read-set.ts +23 -0
  208. package/src/logger.ts +1 -1
  209. package/src/observability.ts +1 -1
  210. package/src/readSetContext.ts +195 -0
  211. package/src/resources/functionalUpdate.ts +16 -4
  212. package/src/resources/httpResources.ts +27 -8
  213. package/src/resources/modelOperations.ts +2 -2
  214. package/src/resources/mutationOptions.ts +1 -1
  215. package/src/resources/where.ts +1 -1
  216. package/src/resources/writeOptionsSchema.ts +30 -12
  217. package/src/schema/index.ts +1 -1
  218. package/src/schema/openapi.ts +48 -7
  219. package/src/server/commit.ts +7 -1
  220. package/src/server/index.ts +5 -1
  221. package/src/source/adapters/kysely.ts +12 -4
  222. package/src/source/adapters/kyselyMutationCore.ts +28 -4
  223. package/src/source/types.ts +1 -1
  224. package/src/testing/fixtures/httpResponses.ts +60 -1
  225. package/src/transactionLayer.ts +1 -1
  226. package/src/transactions/{settlement → confirmation}/commitEnvelope.ts +13 -3
  227. package/src/transport/commitFrames.ts +7 -5
  228. package/src/transport/connectionManager.ts +1 -1
  229. package/src/transport/httpClient.ts +260 -29
  230. package/src/transport/httpOptions.ts +9 -1
  231. package/src/transport/httpTransport.ts +139 -27
  232. package/src/transport/wsFrameHandlers.ts +46 -8
  233. package/src/transport/wsTransport.ts +42 -1
  234. package/src/utils/json.ts +1 -1
  235. package/src/wire/commit.ts +248 -147
  236. package/src/wire/errorEnvelope.ts +1 -0
  237. package/src/wire/feedCursor.ts +1 -1
  238. package/src/wire/frames.ts +9 -4
  239. package/src/wire/index.ts +38 -5
  240. package/src/wire/modelMutations.ts +16 -2
  241. package/src/wire/modelResponses.ts +14 -1
  242. package/dist/transactions/settlement/commitEnvelope.d.ts.map +0 -1
  243. package/dist/transactions/settlement/commitEnvelope.js.map +0 -1
  244. package/dist/transactions/settlement/httpCommitEnvelope.d.ts.map +0 -1
  245. package/dist/transactions/settlement/httpCommitEnvelope.js.map +0 -1
  246. package/dist/transactions/settlement/idempotencyKey.d.ts.map +0 -1
  247. package/dist/transactions/settlement/idempotencyKey.js.map +0 -1
  248. package/dist/transactions/settlement/pendingWrite.d.ts.map +0 -1
  249. package/dist/transactions/settlement/pendingWrite.js.map +0 -1
  250. /package/dist/transactions/{settlement → confirmation}/httpCommitEnvelope.d.ts +0 -0
  251. /package/dist/transactions/{settlement → confirmation}/httpCommitEnvelope.js +0 -0
  252. /package/dist/transactions/{settlement → confirmation}/idempotencyKey.d.ts +0 -0
  253. /package/dist/transactions/{settlement → confirmation}/idempotencyKey.js +0 -0
  254. /package/dist/transactions/{settlement → confirmation}/pendingWrite.js +0 -0
  255. /package/src/transactions/{settlement → confirmation}/httpCommitEnvelope.ts +0 -0
  256. /package/src/transactions/{settlement → confirmation}/idempotencyKey.ts +0 -0
  257. /package/src/transactions/{settlement → confirmation}/pendingWrite.ts +0 -0
@@ -28,6 +28,7 @@ import {
28
28
  AbloClaimedError,
29
29
  AbloContentionError,
30
30
  } from '../errors.js';
31
+ import type { ReadDependency } from '../coordination/schema.js';
31
32
 
32
33
  /**
33
34
  * The functional form of an update: given the freshly-read row, return the
@@ -38,8 +39,8 @@ export type ModelUpdater<T> = (
38
39
  current: T,
39
40
  ) => Partial<T> | null | undefined | Promise<Partial<T> | null | undefined>;
40
41
 
41
- /** Tuning for the functional update's internal reconcile loop. */
42
- export interface ContentionOptions {
42
+ /** Options for the functional `update(id, reducer, options)` form. */
43
+ export interface FunctionalUpdateOptions<Dependency = ReadDependency> {
43
44
  /**
44
45
  * Max reconcile rounds under contention before throwing
45
46
  * {@link AbloContentionError}. Each round re-reads the latest row and re-runs
@@ -48,8 +49,19 @@ export interface ContentionOptions {
48
49
  readonly retries?: number;
49
50
  /** Abort the reconcile loop (e.g. the request was cancelled). */
50
51
  readonly signal?: AbortSignal;
52
+ /**
53
+ * Cross-target state this reducer decision depends on. Captured point rows
54
+ * are resolved by the public client; canonical dependencies remain the
55
+ * low-level escape hatch. Dependencies survive reconcilable CAS failures and
56
+ * are consumed only with the successful attempt.
57
+ */
58
+ readonly reads?: readonly Dependency[] | null;
51
59
  }
52
60
 
61
+ /** @deprecated Use {@link FunctionalUpdateOptions}. */
62
+ export type ContentionOptions<Dependency = ReadDependency> =
63
+ FunctionalUpdateOptions<Dependency>;
64
+
53
65
  /** Reconcile rounds before a hot row is declared permanently contended. */
54
66
  export const DEFAULT_CONTENTION_RETRIES = 16;
55
67
 
@@ -104,9 +116,9 @@ export interface ReconcileTransport<T, R> {
104
116
  * both transports so the guarantee is provably identical. Returns the write's
105
117
  * result, or `undefined` when the updater opted out of writing.
106
118
  */
107
- export async function reconcileFunctionalUpdate<T, R>(
119
+ export async function reconcileFunctionalUpdate<T, R, Read = ReadDependency>(
108
120
  updater: ModelUpdater<T>,
109
- options: ContentionOptions | undefined,
121
+ options: FunctionalUpdateOptions<Read> | undefined,
110
122
  transport: ReconcileTransport<T, R>,
111
123
  ): Promise<R | undefined> {
112
124
  const retries = options?.retries ?? DEFAULT_CONTENTION_RETRIES;
@@ -15,14 +15,19 @@ import type { ClaimHeartbeatReply, ClaimState } from '../wire/claims.js';
15
15
  import type {
16
16
  ClientCommitReceipt,
17
17
  CommitWait,
18
+ CommitRecord,
19
+ CommitRecordList,
20
+ CommitRecordListOptions,
21
+ CommitRecordWhere,
18
22
  } from '../wire/commit.js';
19
23
  import type { LogListResponse, LogQuery } from '../wire/feedEvent.js';
20
- // Re-exported, not redeclared. `wire/commit.ts` owns the settlement vocabulary
24
+ import type { ModelListEvidence } from '../wire/modelResponses.js';
25
+ // Re-exported, not redeclared. `wire/commit.ts` owns the commit-status vocabulary
21
26
  // and derives the waitable subset from it; this module serves that name to SDK
22
27
  // consumers. Restating the subset here as its own union produced a type that
23
28
  // matched the canonical one only by both happening to list the same two
24
29
  // strings — and would have silently disagreed with the runtime `wait`
25
- // validator the moment a third settlement state existed.
30
+ // validator the moment a third commit status existed.
26
31
  export type { CommitWait };
27
32
  // `ModelTarget` (the `model` and `id` locator) and `ModelClaim` (the resolved
28
33
  // claim view) are defined in `../coordination/schema`, derived from a single
@@ -39,7 +44,7 @@ import type { SyncGroupInput } from '../schema/roles.js';
39
44
  import type {
40
45
  CapabilityCan,
41
46
  CapabilityOperation,
42
- CapabilityScope,
47
+ EffectiveAuthority,
43
48
  } from '../auth/capability.js';
44
49
  import type {
45
50
  Claim,
@@ -48,7 +53,7 @@ import type {
48
53
  Duration,
49
54
  HeldClaim,
50
55
  } from '../types/streams.js';
51
- import type { ModelUpdater, ContentionOptions } from './functionalUpdate.js';
56
+ import type { ModelUpdater, FunctionalUpdateOptions } from './functionalUpdate.js';
52
57
  import type {
53
58
  ClaimOptions,
54
59
  ClaimAttemptEvent,
@@ -93,6 +98,12 @@ export interface HttpTransportRead<T = Record<string, unknown>> {
93
98
  readonly claims: readonly ModelClaim[];
94
99
  }
95
100
 
101
+ /** @internal Exact collection envelope retained until the typed facade captures evidence. */
102
+ export interface HttpTransportList<T = Record<string, unknown>> {
103
+ readonly data: readonly T[];
104
+ readonly evidence?: readonly ModelListEvidence[];
105
+ }
106
+
96
107
  export type IfClaimedPolicy = 'return' | 'fail';
97
108
 
98
109
  export interface ClaimedOptions {
@@ -160,6 +171,8 @@ export interface CommitOperationInput {
160
171
  readonly id?: string | null;
161
172
  readonly data?: Record<string, unknown> | null;
162
173
  readonly transactionId?: string | null;
174
+ /** Claim identity derived from a held claim; not an application id. */
175
+ readonly claimId?: string | null;
163
176
  readonly readAt?: number | null;
164
177
  readonly onStale?: OnStaleMode | null;
165
178
  /** Fencing token (Option B) from the batch's claim handle; server-validated. */
@@ -207,6 +220,8 @@ export type CommitReceipt = ClientCommitReceipt;
207
220
 
208
221
  export interface CommitResource {
209
222
  create(options: CommitCreateOptions): Promise<CommitReceipt>;
223
+ get(options: { readonly id: string }): Promise<CommitRecord | null>;
224
+ list(options?: CommitRecordListOptions): Promise<CommitRecordList>;
210
225
  }
211
226
 
212
227
  export interface HttpLogListOptions
@@ -276,6 +291,10 @@ export interface ModelMutationOptions extends ClaimedOptions {
276
291
  readonly idempotencyKey?: string | null;
277
292
  readonly readAt?: number | null;
278
293
  readonly onStale?: OnStaleMode | null;
294
+ /** Commit-lifetime read dependencies checked before this mutation lands. */
295
+ readonly reads?: readonly ReadDependency[] | null;
296
+ /** Persisted read dependencies registered by this mutation. */
297
+ readonly track?: readonly TrackDependency[] | null;
279
298
  readonly claim?: Claim | ClaimOptions | null;
280
299
  /** Fencing token (Option B) from the claim; server-validated at commit. */
281
300
  readonly fenceToken?: number | null;
@@ -332,7 +351,7 @@ export interface HttpTransportModel<
332
351
  * and `limit`. The typed public client always exposes `ablo.<model>.list`;
333
352
  * this protocol shape is private transport machinery.
334
353
  */
335
- list(options?: ServerReadOptions<T>): Promise<T[]>;
354
+ list(options?: ServerReadOptions<T>): Promise<HttpTransportList<T>>;
336
355
  /**
337
356
  * Creates a row and returns the confirmed server row, including framework
338
357
  * defaults such as `createdAt` and `createdBy`. Matches the stateful client's
@@ -354,7 +373,7 @@ export interface HttpTransportModel<
354
373
  update(
355
374
  id: string,
356
375
  updater: ModelUpdater<T>,
357
- options?: ContentionOptions,
376
+ options?: FunctionalUpdateOptions,
358
377
  ): Promise<CommitReceipt | undefined>;
359
378
  delete(params: ModelMutationOptions & { readonly id: string }): Promise<CommitReceipt>;
360
379
  /**
@@ -475,7 +494,7 @@ export interface AbloSession {
475
494
  organizationId: string;
476
495
  /** The grant this token carries, on both axes — the same shape the key row
477
496
  * stores and the gates enforce. */
478
- scope: CapabilityScope;
497
+ scope: EffectiveAuthority;
479
498
  userMeta: Record<string, unknown>;
480
499
  }
481
500
 
@@ -492,7 +511,7 @@ export interface SessionRotation {
492
511
  token: string;
493
512
  expiresAt: string | null;
494
513
  organizationId: string;
495
- scope: CapabilityScope;
514
+ scope: EffectiveAuthority;
496
515
  rotatedFrom: {
497
516
  id: string;
498
517
  expiresAt: string;
@@ -3,7 +3,7 @@
3
3
  * caller passes to a read, a write, or a claim.
4
4
  *
5
5
  * These types describe the *change or the query being requested*, never a local
6
- * copy of the rows it touches, so they sit in the settlement core and are shared
6
+ * copy of the rows it touches, so they sit in the commit core and are shared
7
7
  * by every transport and every caller (ADR 0013 §4, ADR 0016). The factory that
8
8
  * binds them to reactive model instances — `createModelProxy` — stays with the
9
9
  * reactive consumer, along with `ModelOperations` and `ModelCollaboration`,
@@ -562,7 +562,7 @@ export interface ModelRetrieveParams extends ServerRetrieveOptions {
562
562
  * Options shared by schema model writes.
563
563
  *
564
564
  * Reactive clients apply the row change optimistically before returning from
565
- * the call. The returned promise has one stable settlement contract across
565
+ * the call. The returned promise has one stable confirmation contract across
566
566
  * reactive and stateless clients: it resolves only after authoritative
567
567
  * confirmation. Callers that need an earlier queued receipt use the lower-level
568
568
  * `commits.create` resource instead.
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Every field here defines, orders, settles, or authorises *the change itself*
5
5
  * — request identity, commit disposition, fencing, and the premise it rests on.
6
- * None of it touches a local copy of rows, so it belongs with the settlement
6
+ * None of it touches a local copy of rows, so it belongs with the commit
7
7
  * core rather than the reactive consumer (ADR 0013 §4, ADR 0016).
8
8
  */
9
9
 
@@ -10,7 +10,7 @@
10
10
  * key — for example, fetching every block whose `sectionId` falls in a set of ids.
11
11
  *
12
12
  * These types describe the *request*, not any local copy of the rows it returns,
13
- * so they live with the settlement core rather than the reactive consumer.
13
+ * so they live with the commit core rather than the reactive consumer.
14
14
  */
15
15
 
16
16
  import { z } from 'zod';
@@ -20,7 +20,15 @@ import { z } from 'zod';
20
20
  import type { MutationOptions } from '../resources/mutationOptions.js';
21
21
  import { AbloValidationError } from '../errors.js';
22
22
  import { commitWaitSchema } from '../wire/commit.js';
23
- import { onStaleModeSchema } from '../coordination/schema.js';
23
+ import {
24
+ onStaleModeSchema,
25
+ MAX_READ_SET_ENTRIES,
26
+ readDependencyListSchema,
27
+ readSetWatermarkSchema,
28
+ readSetProjectionEntryCount,
29
+ trackDependencyListSchema,
30
+ } from '../coordination/schema.js';
31
+ import type { AssertExact } from '../types/assertExact.js';
24
32
 
25
33
  // Re-exported, not redeclared. `coordination/schema.ts` owns this enum — it is
26
34
  // what the wire schemas and the server validate against — while the published
@@ -38,7 +46,7 @@ export const writeOptionsSchema = z.object({
38
46
  /** Resolve when queued locally (default) or once the server confirms. */
39
47
  wait: commitWaitSchema.optional(),
40
48
  /** Stale guard: the sync watermark the caller's reasoning was based on. */
41
- readAt: z.number().int().nonnegative().nullish(),
49
+ readAt: readSetWatermarkSchema.nullish(),
42
50
  /** What the server does when the target moved past `readAt`. */
43
51
  onStale: onStaleModeSchema.nullish(),
44
52
  /** The held claim's fencing token (Option B), sourced from the claim handle
@@ -49,6 +57,15 @@ export const writeOptionsSchema = z.object({
49
57
  /** The claim this write belongs to — either a claim id, or a live claim
50
58
  * handle whose `release`/`revoke` functions are preserved untouched. */
51
59
  claim: z.union([z.string(), z.looseObject({ id: z.string() })]).nullish(),
60
+ /** Low-level claim identity carried after a live handle is normalized. */
61
+ claimRef: z.union([z.string(), z.object({ id: z.string() })]).nullish(),
62
+ /** Commit-lifetime entries in the write's ReadSet. */
63
+ reads: readDependencyListSchema.nullish(),
64
+ /** Persisted entries projected from the write's ReadSet. */
65
+ track: trackDependencyListSchema.nullish(),
66
+ }).refine((value) => readSetProjectionEntryCount(value) <= MAX_READ_SET_ENTRIES, {
67
+ path: ['reads'],
68
+ message: `reads and track may contain at most ${MAX_READ_SET_ENTRIES} entries combined`,
52
69
  });
53
70
 
54
71
  export type WriteOptionsInput = z.infer<typeof writeOptionsSchema>;
@@ -78,14 +95,15 @@ export function assertWriteOptions(value: unknown, context?: string): void {
78
95
  }
79
96
 
80
97
  // ── Drift guard ──────────────────────────────────────────────────────────────
81
- // Compile-time proof that `writeOptionsSchema` stays assignment-compatible
82
- // with the canonical `MutationOptions` interface. If either side changes
83
- // shape, this stops compiling the schema and the interface can never
84
- // silently diverge.
85
- type _AssertOptionsMatchSchema = MutationOptions extends WriteOptionsInput ? true : never;
86
- type _AssertSchemaMatchesOptions = WriteOptionsInput extends MutationOptions ? true : never;
87
- const _writeOptionsContractInSync: [_AssertOptionsMatchSchema, _AssertSchemaMatchesOptions] = [
88
- true,
89
- true,
90
- ];
98
+ // `claim` is the one high-level handle normalized before MutationOptions. Every
99
+ // other key is the canonical interface itself. Compare keys exactly: mutual
100
+ // assignment between all-optional objects is vacuous and failed to catch three
101
+ // missing members in this schema.
102
+ type WriteOptionsContract = MutationOptions & {
103
+ readonly claim?: string | { readonly id: string } | null;
104
+ };
105
+ const _writeOptionsContractInSync: AssertExact<
106
+ keyof WriteOptionsInput,
107
+ keyof WriteOptionsContract
108
+ > = true;
91
109
  void _writeOptionsContractInSync;
@@ -275,6 +275,6 @@ export {
275
275
 
276
276
  // The OpenAPI generator describes the language-agnostic `/v1` surface — the
277
277
  // primitive itself, which a Python or Go caller consumes with no TypeScript
278
- // anywhere — so it lives in the settlement core (ADR 0016). Re-exported here so
278
+ // anywhere — so it lives in the confirmation core (ADR 0016). Re-exported here so
279
279
  // the `@abloatai/ablo/schema` import path keeps resolving.
280
280
  export { abloOpenApi, schemaToOpenApi, type SchemaToOpenApiOptions } from './openapi.js';
@@ -29,7 +29,13 @@ import { ABLO_HOSTED_HTTP_BASE_URL } from '../auth/hostedEndpoints.js';
29
29
  // one definition site, so the documented surface cannot drift from the
30
30
  // enforced one.
31
31
  import { z } from 'zod';
32
- import { commitRequestSchema, commitReceiptSchema } from '../wire/commit.js';
32
+ import {
33
+ commitRequestSchema,
34
+ commitReceiptSchema,
35
+ commitRecordSchema,
36
+ commitRecordListSchema,
37
+ commitRecordWhereSchema,
38
+ } from '../wire/commit.js';
33
39
  import {
34
40
  claimRequestSchema,
35
41
  claimHeartbeatRequestSchema,
@@ -154,7 +160,9 @@ const ABLO_OPERATION_IDS: Readonly<Record<string, string>> = {
154
160
  'POST /v1/capabilities/{id}/rotate': 'rotateCapability',
155
161
  'GET /v1/schema': 'getSchema',
156
162
  'GET /v1/logs': 'listLogEntries',
163
+ 'GET /v1/commits': 'listCommits',
157
164
  'POST /v1/commits': 'commit',
165
+ 'GET /v1/commits/{id}': 'getCommit',
158
166
  };
159
167
 
160
168
  function fieldSchema(f: FieldMeta): Json {
@@ -317,6 +325,11 @@ function queryParams(schema: z.ZodType): Json[] {
317
325
  }));
318
326
  }
319
327
 
328
+ const commitRecordListQuerySchema = commitRecordWhereSchema.safeExtend({
329
+ cursor: z.string().min(1).optional(),
330
+ limit: z.number().int().min(1).max(100).optional(),
331
+ });
332
+
320
333
  /**
321
334
  * Both release routes answer in one shape. `released` distinguishes "this call
322
335
  * ended your lease" from "there was nothing of yours to end" — both success,
@@ -736,11 +749,9 @@ export function abloOpenApi(options: SchemaToOpenApiOptions = {}): Json {
736
749
  tags: ['claims'],
737
750
  summary: 'Heartbeat a claim by id — held or queued',
738
751
  description:
739
- 'The beat a waiter needs: a queued caller holds nothing but the ' +
740
- '`claimId` it was handed at enqueue, and an entry that stops beating ' +
741
- 'drops out of the line on TTL. The reply doubles as the wait poll — ' +
742
- '`queued` means still in line, `held` means the grant landed, at which ' +
743
- 'point `GET /v1/claims/{claimId}` carries the fence token.',
752
+ 'Keep a held or queued claim active. Branch on the returned status: ' +
753
+ '`queued` is still waiting and `held` has been granted. Retrieve the ' +
754
+ 'claim after a grant before writing.',
744
755
  parameters: [claimIdParam()],
745
756
  requestBody: optionalJsonBody(derive(claimHeartbeatRequestSchema, 'input')),
746
757
  responses: {
@@ -849,6 +860,12 @@ export function abloOpenApi(options: SchemaToOpenApiOptions = {}): Json {
849
860
  },
850
861
  },
851
862
  '/v1/commits': {
863
+ get: {
864
+ tags: ['commits'],
865
+ summary: 'List commit records',
866
+ parameters: queryParams(commitRecordListQuerySchema),
867
+ responses: { '200': jsonResp('Tenant-scoped commit records', derive(commitRecordListSchema, 'output')) },
868
+ },
852
869
  post: {
853
870
  tags: ['commits'],
854
871
  summary: 'Commit a batch of operations atomically, and/or register durable premises',
@@ -857,6 +874,14 @@ export function abloOpenApi(options: SchemaToOpenApiOptions = {}): Json {
857
874
  responses: { '200': commitReceipt() },
858
875
  },
859
876
  },
877
+ '/v1/commits/{id}': {
878
+ get: {
879
+ tags: ['commits'],
880
+ summary: 'Retrieve a commit record',
881
+ parameters: [idParam()],
882
+ responses: { '200': jsonResp('The commit record, or null when absent', derive(commitRecordSchema.nullable(), 'output')) },
883
+ },
884
+ },
860
885
  };
861
886
 
862
887
  applyOperationIds(paths, ABLO_OPERATION_IDS);
@@ -864,7 +889,7 @@ export function abloOpenApi(options: SchemaToOpenApiOptions = {}): Json {
864
889
 
865
890
  return envelope(
866
891
  options,
867
- 'The Ablo transaction layer: commit, read, and claim. `{model}` is any model ' +
892
+ 'Ablo collaboration infrastructure: commit, read, and claim. `{model}` is any model ' +
868
893
  'from your pushed schema — the routes are the same whichever it is. ' +
869
894
  'Authenticate every request with your API key as a Bearer token.',
870
895
  paths,
@@ -962,6 +987,12 @@ export function schemaToOpenApi<S extends SchemaRecord>(
962
987
  }
963
988
 
964
989
  paths['/v1/commits'] = {
990
+ get: {
991
+ tags: ['commits'],
992
+ summary: 'List commit records',
993
+ parameters: queryParams(commitRecordListQuerySchema),
994
+ responses: { '200': jsonResp('Tenant-scoped commit records', derive(commitRecordListSchema, 'output')) },
995
+ },
965
996
  post: {
966
997
  tags: ['commits'],
967
998
  summary: 'Commit a batch of operations atomically, and/or register durable premises',
@@ -970,7 +1001,17 @@ export function schemaToOpenApi<S extends SchemaRecord>(
970
1001
  responses: { '200': commitReceipt() },
971
1002
  },
972
1003
  };
1004
+ paths['/v1/commits/{id}'] = {
1005
+ get: {
1006
+ tags: ['commits'],
1007
+ summary: 'Retrieve a commit record',
1008
+ parameters: [idParam()],
1009
+ responses: { '200': jsonResp('The commit record, or null when absent', derive(commitRecordSchema.nullable(), 'output')) },
1010
+ },
1011
+ };
1012
+ operationIds['GET /v1/commits'] = 'listCommits';
973
1013
  operationIds['POST /v1/commits'] = 'commit';
1014
+ operationIds['GET /v1/commits/{id}'] = 'getCommit';
974
1015
  applyOperationIds(paths, operationIds);
975
1016
  attachCanonicalErrors(paths);
976
1017
 
@@ -17,6 +17,7 @@ import type { ParticipantKind, ConfirmationState } from '../log/syncDeltaRow.js'
17
17
  import type { ParticipantRef } from '../wire/delta.js';
18
18
  import type { CommitExecutionResultInput } from '../wire/commit.js';
19
19
  import type { ReadDependency, TrackDependency } from '../coordination/schema.js';
20
+ import type { EffectiveAuthority } from '../auth/capability.js';
20
21
 
21
22
  export interface CommitContext {
22
23
  participantId: string;
@@ -25,6 +26,11 @@ export interface CommitContext {
25
26
  * structured attribution rather than a string-prefix convention.
26
27
  */
27
28
  participantKind: ParticipantKind;
29
+ /** Effective data grant resolved from the authenticated identity at commit time. */
30
+ authority?: EffectiveAuthority;
31
+ /** Server request identity and ingress used for physical-attempt evidence. */
32
+ requestId?: string;
33
+ transport?: 'http' | 'websocket' | 'internal';
28
34
  organizationId: string;
29
35
  /** Immutable branch selected by the authenticated credential. */
30
36
  branchId: string;
@@ -90,7 +96,7 @@ export interface CommitContext {
90
96
 
91
97
  /**
92
98
  * The server execution receipt persisted in `mutation_log`. Its runtime schema
93
- * lives with the HTTP/WS settlement contract so queued correlation cannot drift
99
+ * lives with the HTTP/WS confirmation contract so queued correlation cannot drift
94
100
  * between cache, transport, and client.
95
101
  */
96
102
  export type CommitExecutionResult = CommitExecutionResultInput;
@@ -21,6 +21,10 @@ export type {
21
21
  AdapterResolver,
22
22
  } from './adapter.js';
23
23
  export type { CommitContext, CommitExecutionResult } from './commit.js';
24
- export { commitExecutionResultSchema } from '../wire/commit.js';
24
+ export {
25
+ commitExecutionResultSchema,
26
+ commitRecordSchema,
27
+ type CommitRecord,
28
+ } from '../wire/commit.js';
25
29
  export { storageModeSchema, type StorageMode } from './storageMode.js';
26
30
  export type { ColumnOverride, BootstrapModel } from './readConfig.js';
@@ -338,15 +338,23 @@ export function kyselyDirectMutation<S extends SchemaRecord>(
338
338
  db: KyselyLike,
339
339
  schema: Schema<S>,
340
340
  ): MutationAdapter {
341
+ const markerModels = new Map<string, string>();
342
+ for (const [key, definition] of Object.entries(schema.models)) {
343
+ const typename = definition.typename || key;
344
+ markerModels.set(key, typename);
345
+ markerModels.set(key.toLowerCase(), typename);
346
+ markerModels.set(typename, typename);
347
+ markerModels.set(typename.toLowerCase(), typename);
348
+ }
341
349
  return createKyselyMutationAdapter(
342
350
  db,
343
351
  createKyselyMutationCore(db, schema),
344
352
  'direct',
345
353
  {
346
- markerModelFor: (operationModel) => {
347
- const typename = schema.models[operationModel]?.typename;
348
- return typename && typename.length > 0 ? typename : operationModel;
349
- },
354
+ markerModelFor: (operationModel) =>
355
+ markerModels.get(operationModel) ??
356
+ markerModels.get(operationModel.toLowerCase()) ??
357
+ operationModel,
350
358
  },
351
359
  );
352
360
  }
@@ -100,11 +100,23 @@ function buildColumnMaps(schema: Schema): ReadonlyMap<string, ModelColumns> {
100
100
  }
101
101
  const orgColumn = tenancyColumn(model.tenancy);
102
102
  if (orgColumn) register('organizationId', orgColumn);
103
- out.set(key, {
103
+ const columns = {
104
104
  table: model.tableName ?? key,
105
105
  fieldToColumn,
106
106
  columnToField,
107
- });
107
+ };
108
+ // The public clients normalize operation model names to lowercase before
109
+ // they cross the wire. Preserve the authored key for direct adapter users,
110
+ // but resolve the same lowercase key/typename aliases as the server's
111
+ // schema-derived ModelMap. Without these aliases, a camelCase schema key
112
+ // such as `collaborationWorkItems` validates at the server boundary and
113
+ // then fails here as an unknown customer model.
114
+ out.set(key, columns);
115
+ out.set(key.toLowerCase(), columns);
116
+ if (model.typename) {
117
+ out.set(model.typename, columns);
118
+ out.set(model.typename.toLowerCase(), columns);
119
+ }
108
120
  }
109
121
  return out;
110
122
  }
@@ -191,7 +203,13 @@ export function createKyselyMutationCore<S extends SchemaRecord>(
191
203
  .where('id', '=', id)
192
204
  .returningAll()
193
205
  .execute();
194
- return deleted[0] ? toFields(columns, deleted[0]) : { id };
206
+ if (!deleted[0]) {
207
+ throw new AbloValidationError(
208
+ `${operation.type} on "${operation.model}/${id}" matched no source row`,
209
+ { code: 'entity_not_found' },
210
+ );
211
+ }
212
+ return toFields(columns, deleted[0]);
195
213
  }
196
214
 
197
215
  if (operation.type === 'CREATE') {
@@ -214,7 +232,13 @@ export function createKyselyMutationCore<S extends SchemaRecord>(
214
232
  .where('id', '=', id)
215
233
  .returningAll()
216
234
  .execute();
217
- return updated[0] ? toFields(columns, updated[0]) : { id, ...input };
235
+ if (!updated[0]) {
236
+ throw new AbloValidationError(
237
+ `${operation.type} on "${operation.model}/${id}" matched no source row`,
238
+ { code: 'mutate_update_entity_not_found' },
239
+ );
240
+ }
241
+ return toFields(columns, updated[0]);
218
242
  },
219
243
  };
220
244
  }
@@ -152,7 +152,7 @@ export interface SourceEvent {
152
152
  */
153
153
  readonly organizationId?: string;
154
154
  /**
155
- * @deprecated Legacy echo identity. It is not trusted for queued settlement;
155
+ * @deprecated Legacy echo identity. It is not trusted for queued confirmation;
156
156
  * use `correlationId` and `transactionId` for mediated endpoint writes.
157
157
  */
158
158
  readonly clientTxId?: string;
@@ -17,6 +17,8 @@
17
17
 
18
18
  import { listEnvelope } from '@abloatai/transaction/wire';
19
19
  import type {
20
+ CommitAck,
21
+ CommitReceiptWire,
20
22
  ModelReadResponse,
21
23
  ModelListResponse,
22
24
  ClaimAcquiredResponse,
@@ -25,6 +27,55 @@ import type {
25
27
  ClaimHeartbeatReply,
26
28
  } from '@abloatai/transaction/wire';
27
29
  import type { ModelClaim } from '@abloatai/transaction/coordination';
30
+ import type { EffectiveAuthority } from '@abloatai/transaction/auth';
31
+
32
+ /** Stable server-authored times shared by commit boundary fixtures. */
33
+ export const COMMIT_FIXTURE_TIMES = {
34
+ createdAt: '2026-08-05T10:00:00.000Z',
35
+ statusAt: '2026-08-05T10:00:00.058Z',
36
+ } as const;
37
+
38
+ /** Canonical authenticated authority for transport-boundary test responses. */
39
+ export const EFFECTIVE_AUTHORITY_FIXTURE = {
40
+ organizationId: 'org-1',
41
+ projectId: 'project-1',
42
+ branchId: 'branch-1',
43
+ syncGroups: ['org:org-1'],
44
+ operations: [],
45
+ participantKind: 'agent',
46
+ participantId: 'agent-1',
47
+ deliveryPartition: null,
48
+ } satisfies EffectiveAuthority;
49
+
50
+ /** A canonical hosted confirmation returned by HTTP or WebSocket. */
51
+ export function confirmedCommitReceiptResponse(args: {
52
+ clientTxId: string;
53
+ lastSyncId?: number;
54
+ serverTxId?: string;
55
+ ops?: number;
56
+ }): CommitReceiptWire {
57
+ const lastSyncId = args.lastSyncId ?? 0;
58
+ return {
59
+ object: 'commit_receipt',
60
+ clientTxId: args.clientTxId,
61
+ serverTxId: args.serverTxId ?? String(lastSyncId),
62
+ ...COMMIT_FIXTURE_TIMES,
63
+ success: true,
64
+ authority: EFFECTIVE_AUTHORITY_FIXTURE,
65
+ status: 'confirmed',
66
+ lastSyncId,
67
+ ops: args.ops ?? 1,
68
+ } satisfies CommitReceiptWire;
69
+ }
70
+
71
+ /** Canonical normalized acknowledgement for mutation-executor test doubles. */
72
+ export function confirmedCommitAck(lastSyncId = 0): CommitAck {
73
+ return {
74
+ status: 'confirmed',
75
+ statusAt: COMMIT_FIXTURE_TIMES.statusAt,
76
+ lastSyncId,
77
+ } satisfies CommitAck;
78
+ }
28
79
 
29
80
  /** `GET /v1/models/{model}/{id}`. Pass `data: null` for a miss. */
30
81
  export function modelReadResponse(args: {
@@ -51,14 +102,22 @@ export function modelListResponse(args: {
51
102
  hasMore?: boolean;
52
103
  nextCursor?: string | null;
53
104
  stamp?: number;
105
+ evidence?: readonly { id: string; stamp: number }[];
54
106
  }): ModelListResponse {
107
+ const stamp = args.stamp ?? 0;
108
+ const inferredEvidence = args.data.flatMap((row): { id: string; stamp: number }[] => {
109
+ if (typeof row !== 'object' || row === null) return [];
110
+ const id = (row as { id?: unknown }).id;
111
+ return typeof id === 'string' ? [{ id, stamp }] : [];
112
+ });
55
113
  return {
56
114
  object: 'list',
57
115
  model: args.model,
58
116
  data: [...args.data],
59
117
  has_more: args.hasMore ?? false,
60
118
  next_cursor: args.nextCursor ?? null,
61
- stamp: args.stamp ?? 0,
119
+ stamp,
120
+ evidence: [...(args.evidence ?? inferredEvidence)],
62
121
  } satisfies ModelListResponse;
63
122
  }
64
123
 
@@ -30,7 +30,7 @@ import type { ModelScope } from './types/index.js';
30
30
 
31
31
  /**
32
32
  * A commit's durable acceptance — `{ status: 'queued' }` with the correlation
33
- * the settlement feed later confirms. The wire layer owns the receipt fields;
33
+ * the confirmation feed later confirms. The wire layer owns the receipt fields;
34
34
  * the seam adds the client idempotency identity needed for a safe wait/retry.
35
35
  */
36
36
  export type CommitReceipt = ClientCommitReceipt & {