@abloatai/transaction 0.47.0 → 0.49.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 (270) 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/branches.d.ts +61 -12
  32. package/dist/branches.d.ts.map +1 -1
  33. package/dist/branches.js +25 -3
  34. package/dist/branches.js.map +1 -1
  35. package/dist/commitRecordRuntime.d.ts +30 -0
  36. package/dist/commitRecordRuntime.d.ts.map +1 -0
  37. package/dist/commitRecordRuntime.js +188 -0
  38. package/dist/commitRecordRuntime.js.map +1 -0
  39. package/dist/coordination/index.d.ts +3 -3
  40. package/dist/coordination/index.d.ts.map +1 -1
  41. package/dist/coordination/index.js +2 -2
  42. package/dist/coordination/index.js.map +1 -1
  43. package/dist/coordination/locator.d.ts +5 -0
  44. package/dist/coordination/locator.d.ts.map +1 -1
  45. package/dist/coordination/locator.js +12 -0
  46. package/dist/coordination/locator.js.map +1 -1
  47. package/dist/coordination/schema.d.ts +205 -15
  48. package/dist/coordination/schema.d.ts.map +1 -1
  49. package/dist/coordination/schema.js +88 -47
  50. package/dist/coordination/schema.js.map +1 -1
  51. package/dist/coordination/targetConflict.js +1 -1
  52. package/dist/durableWrites.d.ts +3 -3
  53. package/dist/durableWrites.d.ts.map +1 -1
  54. package/dist/durableWrites.js +1 -1
  55. package/dist/errorCodes.d.ts +35 -2
  56. package/dist/errorCodes.d.ts.map +1 -1
  57. package/dist/errorCodes.js +67 -19
  58. package/dist/errorCodes.js.map +1 -1
  59. package/dist/errorObservation.d.ts +82 -0
  60. package/dist/errorObservation.d.ts.map +1 -0
  61. package/dist/errorObservation.js +101 -0
  62. package/dist/errorObservation.js.map +1 -0
  63. package/dist/errors.d.ts +2 -0
  64. package/dist/errors.d.ts.map +1 -1
  65. package/dist/errors.js +6 -1
  66. package/dist/errors.js.map +1 -1
  67. package/dist/footprint.d.ts +10 -22
  68. package/dist/footprint.d.ts.map +1 -1
  69. package/dist/footprint.js +12 -24
  70. package/dist/footprint.js.map +1 -1
  71. package/dist/index.d.ts +5 -2
  72. package/dist/index.d.ts.map +1 -1
  73. package/dist/index.js +1 -1
  74. package/dist/index.js.map +1 -1
  75. package/dist/internal/read-set.d.ts +8 -0
  76. package/dist/internal/read-set.d.ts.map +1 -0
  77. package/dist/internal/read-set.js +7 -0
  78. package/dist/internal/read-set.js.map +1 -0
  79. package/dist/logger.d.ts +1 -1
  80. package/dist/observability.d.ts +1 -1
  81. package/dist/observability.js +1 -1
  82. package/dist/readSetContext.d.ts +54 -0
  83. package/dist/readSetContext.d.ts.map +1 -0
  84. package/dist/readSetContext.js +98 -0
  85. package/dist/readSetContext.js.map +1 -0
  86. package/dist/resources/functionalUpdate.d.ts +13 -3
  87. package/dist/resources/functionalUpdate.d.ts.map +1 -1
  88. package/dist/resources/functionalUpdate.js.map +1 -1
  89. package/dist/resources/httpResources.d.ts +23 -7
  90. package/dist/resources/httpResources.d.ts.map +1 -1
  91. package/dist/resources/modelOperations.d.ts +2 -2
  92. package/dist/resources/modelOperations.js +1 -1
  93. package/dist/resources/mutationOptions.d.ts +1 -1
  94. package/dist/resources/mutationOptions.js +1 -1
  95. package/dist/resources/where.d.ts +1 -1
  96. package/dist/resources/where.js +1 -1
  97. package/dist/resources/writeOptionsSchema.d.ts +39 -4
  98. package/dist/resources/writeOptionsSchema.d.ts.map +1 -1
  99. package/dist/resources/writeOptionsSchema.js +12 -6
  100. package/dist/resources/writeOptionsSchema.js.map +1 -1
  101. package/dist/schema/index.js +1 -1
  102. package/dist/schema/index.js.map +1 -1
  103. package/dist/schema/openapi.d.ts.map +1 -1
  104. package/dist/schema/openapi.js +45 -11
  105. package/dist/schema/openapi.js.map +1 -1
  106. package/dist/server/commit.d.ts +7 -1
  107. package/dist/server/commit.d.ts.map +1 -1
  108. package/dist/server/index.d.ts +1 -1
  109. package/dist/server/index.d.ts.map +1 -1
  110. package/dist/server/index.js +1 -1
  111. package/dist/server/index.js.map +1 -1
  112. package/dist/source/adapters/kysely.d.ts.map +1 -1
  113. package/dist/source/adapters/kysely.js +11 -4
  114. package/dist/source/adapters/kysely.js.map +1 -1
  115. package/dist/source/adapters/kyselyMutationCore.d.ts.map +1 -1
  116. package/dist/source/adapters/kyselyMutationCore.js +14 -2
  117. package/dist/source/adapters/kyselyMutationCore.js.map +1 -1
  118. package/dist/source/contract.d.ts +2 -0
  119. package/dist/source/contract.d.ts.map +1 -1
  120. package/dist/source/index.d.ts +1 -1
  121. package/dist/source/index.d.ts.map +1 -1
  122. package/dist/source/types.d.ts +1 -1
  123. package/dist/testing/fixtures/httpResponses.d.ts +30 -1
  124. package/dist/testing/fixtures/httpResponses.d.ts.map +1 -1
  125. package/dist/testing/fixtures/httpResponses.js +48 -1
  126. package/dist/testing/fixtures/httpResponses.js.map +1 -1
  127. package/dist/transactionLayer.d.ts +1 -1
  128. package/dist/transactions/{settlement → confirmation}/commitEnvelope.d.ts +4 -2
  129. package/dist/transactions/confirmation/commitEnvelope.d.ts.map +1 -0
  130. package/dist/transactions/{settlement → confirmation}/commitEnvelope.js +8 -3
  131. package/dist/transactions/confirmation/commitEnvelope.js.map +1 -0
  132. package/dist/transactions/confirmation/httpCommitEnvelope.d.ts.map +1 -0
  133. package/dist/transactions/confirmation/httpCommitEnvelope.js.map +1 -0
  134. package/dist/transactions/confirmation/idempotencyKey.d.ts.map +1 -0
  135. package/dist/transactions/confirmation/idempotencyKey.js.map +1 -0
  136. package/dist/transactions/{settlement → confirmation}/pendingWrite.d.ts +3 -2
  137. package/dist/transactions/confirmation/pendingWrite.d.ts.map +1 -0
  138. package/dist/transactions/confirmation/pendingWrite.js.map +1 -0
  139. package/dist/transport/commitFrames.d.ts +1 -0
  140. package/dist/transport/commitFrames.d.ts.map +1 -1
  141. package/dist/transport/commitFrames.js +8 -7
  142. package/dist/transport/commitFrames.js.map +1 -1
  143. package/dist/transport/connectionManager.d.ts +1 -1
  144. package/dist/transport/connectionManager.js +1 -1
  145. package/dist/transport/httpClient.d.ts +23 -9
  146. package/dist/transport/httpClient.d.ts.map +1 -1
  147. package/dist/transport/httpClient.js +134 -15
  148. package/dist/transport/httpClient.js.map +1 -1
  149. package/dist/transport/httpOptions.d.ts +9 -1
  150. package/dist/transport/httpOptions.d.ts.map +1 -1
  151. package/dist/transport/httpTransport.d.ts +3 -0
  152. package/dist/transport/httpTransport.d.ts.map +1 -1
  153. package/dist/transport/httpTransport.js +109 -22
  154. package/dist/transport/httpTransport.js.map +1 -1
  155. package/dist/transport/wsFrameHandlers.d.ts +3 -1
  156. package/dist/transport/wsFrameHandlers.d.ts.map +1 -1
  157. package/dist/transport/wsFrameHandlers.js +38 -17
  158. package/dist/transport/wsFrameHandlers.js.map +1 -1
  159. package/dist/transport/wsTransport.d.ts +3 -0
  160. package/dist/transport/wsTransport.d.ts.map +1 -1
  161. package/dist/transport/wsTransport.js +32 -1
  162. package/dist/transport/wsTransport.js.map +1 -1
  163. package/dist/utils/json.d.ts +1 -1
  164. package/dist/utils/json.js +1 -1
  165. package/dist/wire/accountResponses.d.ts +1 -1
  166. package/dist/wire/auth.d.ts +2 -2
  167. package/dist/wire/commit.d.ts +1652 -213
  168. package/dist/wire/commit.d.ts.map +1 -1
  169. package/dist/wire/commit.js +196 -132
  170. package/dist/wire/commit.js.map +1 -1
  171. package/dist/wire/delta.d.ts +1 -1
  172. package/dist/wire/errorEnvelope.d.ts +1 -0
  173. package/dist/wire/errorEnvelope.d.ts.map +1 -1
  174. package/dist/wire/errorEnvelope.js +1 -0
  175. package/dist/wire/errorEnvelope.js.map +1 -1
  176. package/dist/wire/feedCursor.d.ts +1 -1
  177. package/dist/wire/feedCursor.js +1 -1
  178. package/dist/wire/feedEvent.d.ts +3 -3
  179. package/dist/wire/frames.d.ts +8 -4
  180. package/dist/wire/frames.d.ts.map +1 -1
  181. package/dist/wire/frames.js +6 -3
  182. package/dist/wire/frames.js.map +1 -1
  183. package/dist/wire/inboundFrames.d.ts +44 -4
  184. package/dist/wire/inboundFrames.d.ts.map +1 -1
  185. package/dist/wire/index.d.ts +6 -4
  186. package/dist/wire/index.d.ts.map +1 -1
  187. package/dist/wire/index.js +6 -5
  188. package/dist/wire/index.js.map +1 -1
  189. package/dist/wire/modelMutations.d.ts +35 -0
  190. package/dist/wire/modelMutations.d.ts.map +1 -1
  191. package/dist/wire/modelMutations.js +9 -2
  192. package/dist/wire/modelMutations.js.map +1 -1
  193. package/dist/wire/modelResponses.d.ts +12 -1
  194. package/dist/wire/modelResponses.d.ts.map +1 -1
  195. package/dist/wire/modelResponses.js +12 -1
  196. package/dist/wire/modelResponses.js.map +1 -1
  197. package/package.json +12 -2
  198. package/src/ai-sdk/modelTools.ts +1 -1
  199. package/src/ai-sdk/updateTool.ts +2 -2
  200. package/src/auth/bootstrapScope.ts +1 -1
  201. package/src/auth/capability.ts +13 -4
  202. package/src/auth/capabilityLifecycle.ts +3 -2
  203. package/src/auth/identity.ts +16 -1
  204. package/src/auth/index.ts +2 -0
  205. package/src/auth/schemas.ts +3 -0
  206. package/src/auth/sessionMint.ts +0 -1
  207. package/src/branches.ts +27 -3
  208. package/src/commitRecordRuntime.ts +245 -0
  209. package/src/coordination/index.ts +24 -1
  210. package/src/coordination/locator.ts +17 -0
  211. package/src/coordination/schema.ts +114 -49
  212. package/src/coordination/targetConflict.ts +1 -1
  213. package/src/durableWrites.ts +3 -3
  214. package/src/errorCodes.ts +205 -25
  215. package/src/errorObservation.ts +121 -0
  216. package/src/errors.ts +5 -1
  217. package/src/footprint.ts +16 -27
  218. package/src/index.ts +8 -1
  219. package/src/internal/read-set.ts +18 -0
  220. package/src/logger.ts +1 -1
  221. package/src/observability.ts +1 -1
  222. package/src/readSetContext.ts +164 -0
  223. package/src/resources/functionalUpdate.ts +16 -4
  224. package/src/resources/httpResources.ts +27 -8
  225. package/src/resources/modelOperations.ts +2 -2
  226. package/src/resources/mutationOptions.ts +1 -1
  227. package/src/resources/where.ts +1 -1
  228. package/src/resources/writeOptionsSchema.ts +30 -12
  229. package/src/schema/index.ts +1 -1
  230. package/src/schema/openapi.ts +52 -11
  231. package/src/server/commit.ts +7 -1
  232. package/src/server/index.ts +5 -1
  233. package/src/source/adapters/kysely.ts +12 -4
  234. package/src/source/adapters/kyselyMutationCore.ts +14 -2
  235. package/src/source/index.ts +1 -1
  236. package/src/source/types.ts +1 -1
  237. package/src/testing/fixtures/httpResponses.ts +60 -1
  238. package/src/transactionLayer.ts +1 -1
  239. package/src/transactions/{settlement → confirmation}/commitEnvelope.ts +13 -3
  240. package/src/transport/commitFrames.ts +7 -5
  241. package/src/transport/connectionManager.ts +1 -1
  242. package/src/transport/httpClient.ts +256 -29
  243. package/src/transport/httpOptions.ts +9 -1
  244. package/src/transport/httpTransport.ts +139 -27
  245. package/src/transport/wsFrameHandlers.ts +46 -8
  246. package/src/transport/wsTransport.ts +42 -1
  247. package/src/utils/json.ts +1 -1
  248. package/src/wire/commit.ts +248 -147
  249. package/src/wire/errorEnvelope.ts +1 -0
  250. package/src/wire/feedCursor.ts +1 -1
  251. package/src/wire/frames.ts +9 -4
  252. package/src/wire/index.ts +38 -5
  253. package/src/wire/modelMutations.ts +16 -2
  254. package/src/wire/modelResponses.ts +14 -1
  255. package/dist/transactions/settlement/commitEnvelope.d.ts.map +0 -1
  256. package/dist/transactions/settlement/commitEnvelope.js.map +0 -1
  257. package/dist/transactions/settlement/httpCommitEnvelope.d.ts.map +0 -1
  258. package/dist/transactions/settlement/httpCommitEnvelope.js.map +0 -1
  259. package/dist/transactions/settlement/idempotencyKey.d.ts.map +0 -1
  260. package/dist/transactions/settlement/idempotencyKey.js.map +0 -1
  261. package/dist/transactions/settlement/pendingWrite.d.ts.map +0 -1
  262. package/dist/transactions/settlement/pendingWrite.js.map +0 -1
  263. /package/dist/transactions/{settlement → confirmation}/httpCommitEnvelope.d.ts +0 -0
  264. /package/dist/transactions/{settlement → confirmation}/httpCommitEnvelope.js +0 -0
  265. /package/dist/transactions/{settlement → confirmation}/idempotencyKey.d.ts +0 -0
  266. /package/dist/transactions/{settlement → confirmation}/idempotencyKey.js +0 -0
  267. /package/dist/transactions/{settlement → confirmation}/pendingWrite.js +0 -0
  268. /package/src/transactions/{settlement → confirmation}/httpCommitEnvelope.ts +0 -0
  269. /package/src/transactions/{settlement → confirmation}/idempotencyKey.ts +0 -0
  270. /package/src/transactions/{settlement → confirmation}/pendingWrite.ts +0 -0
@@ -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,
@@ -644,7 +657,7 @@ export function abloOpenApi(options: SchemaToOpenApiOptions = {}): Json {
644
657
  '/v1/branches/{id}/status': {
645
658
  get: {
646
659
  tags: ['branches'],
647
- summary: 'Diagnose one branch plane',
660
+ summary: 'Diagnose one branch',
648
661
  description:
649
662
  'Returns branch lifecycle, active schema, compatibility with the parent schema, safe datasource coordinates, and readiness blockers.',
650
663
  parameters: [idParam()],
@@ -699,7 +712,7 @@ export function abloOpenApi(options: SchemaToOpenApiOptions = {}): Json {
699
712
  description:
700
713
  'One round trip per cadence for a worker holding many rows, instead of ' +
701
714
  'one per row. Takes only `ttl`; the leases are whichever ones your ' +
702
- 'credential holds on this plane.',
715
+ 'credential holds on this branch.',
703
716
  requestBody: optionalJsonBody(derive(claimHeartbeatRequestSchema, 'input')),
704
717
  responses: {
705
718
  '200': namedResp(
@@ -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: {
@@ -812,7 +823,7 @@ export function abloOpenApi(options: SchemaToOpenApiOptions = {}): Json {
812
823
  tags: ['schema'],
813
824
  summary: 'What the models look like',
814
825
  description:
815
- "The schema deployed on your credential's plane: every model with its " +
826
+ "The schema deployed on your credential's branch: every model with its " +
816
827
  'fields, their types, and its relations. Read this when you have no ' +
817
828
  'local schema declaration to read types from — it is what makes a ' +
818
829
  'field typo a local check rather than a rejected write. Each model ' +
@@ -835,7 +846,7 @@ export function abloOpenApi(options: SchemaToOpenApiOptions = {}): Json {
835
846
  'How a caller without a socket learns what its peers did. Omit ' +
836
847
  '`after` for the most recent entries, then copy each page\'s ' +
837
848
  '`next_cursor` back as `after` to walk forward. Scope is taken from ' +
838
- 'your key — organization, plane and sync groups — so a caller cannot ' +
849
+ 'your key — organization, branch and sync groups — so a caller cannot ' +
839
850
  'widen what it sees by asking.',
840
851
  parameters: queryParams(logQuerySchema),
841
852
  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
  }
@@ -192,5 +192,5 @@ export {
192
192
  type FootprintArtifact,
193
193
  type FootprintKind,
194
194
  type FootprintNames,
195
- type FootprintPlane,
195
+ type DataSourceIdentity,
196
196
  } from '../footprint.js';
@@ -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 & {
@@ -8,7 +8,12 @@
8
8
  */
9
9
 
10
10
  import { z } from 'zod';
11
- import { readDependencySchema, trackDependencySchema } from '../../coordination/schema.js';
11
+ import {
12
+ MAX_READ_SET_ENTRIES,
13
+ readDependencyListSchema,
14
+ readSetProjectionEntryCount,
15
+ trackDependencyListSchema,
16
+ } from '../../coordination/schema.js';
12
17
  import { wireCommitOperationSchema } from '../../wire/frames.js';
13
18
  import { correlationIdSchema } from '../../wire/commit.js';
14
19
  import { idempotencyKeySchema } from './idempotencyKey.js';
@@ -41,6 +46,7 @@ export const durableCommitOperationSchema = wireCommitOperationSchema
41
46
  id: true,
42
47
  input: true,
43
48
  transactionId: true,
49
+ claimId: true,
44
50
  readAt: true,
45
51
  onStale: true,
46
52
  fenceToken: true,
@@ -50,6 +56,7 @@ export const durableCommitOperationSchema = wireCommitOperationSchema
50
56
  id: z.string().min(1),
51
57
  input: z.record(z.string(), z.unknown()).optional(),
52
58
  transactionId: z.string().min(1).optional(),
59
+ claimId: z.string().min(1).optional(),
53
60
  options: z
54
61
  .strictObject({
55
62
  idempotencyKey: z.string().min(1).max(255).nullable().optional(),
@@ -62,8 +69,11 @@ export type DurableCommitOperation = z.infer<typeof durableCommitOperationSchema
62
69
  export type DurableCommitOperationInput = z.input<typeof durableCommitOperationSchema>;
63
70
 
64
71
  const durableCommitOptionsSchema = z.strictObject({
65
- reads: z.array(readDependencySchema).nullable().optional(),
66
- track: z.array(trackDependencySchema).nullable().optional(),
72
+ reads: readDependencyListSchema.nullable().optional(),
73
+ track: trackDependencyListSchema.nullable().optional(),
74
+ }).refine((value) => readSetProjectionEntryCount(value) <= MAX_READ_SET_ENTRIES, {
75
+ path: ['reads'],
76
+ message: `reads and track may contain at most ${MAX_READ_SET_ENTRIES} entries combined`,
67
77
  });
68
78
 
69
79
  export const commitOutboxScopeSchema = z.strictObject({
@@ -53,6 +53,7 @@ export interface CommitFrameOperation {
53
53
  readonly id: string;
54
54
  readonly input?: Record<string, unknown>;
55
55
  readonly transactionId?: string;
56
+ readonly claimId?: string | null;
56
57
  readonly readAt?: number | null;
57
58
  readonly onStale?: OnStaleMode | null;
58
59
  readonly fenceToken?: number | null;
@@ -80,17 +81,18 @@ export function buildCommitFrame(
80
81
  id: op.id,
81
82
  input: op.input,
82
83
  transactionId: op.transactionId,
84
+ claimId: op.claimId,
83
85
  readAt: op.readAt,
84
86
  onStale: op.onStale,
85
87
  fenceToken: op.fenceToken,
86
88
  })),
87
89
  clientTxId,
88
90
  };
89
- // The batch premise: the rows or groups the writer read before committing.
90
- if (reads && reads.length > 0) payload.reads = [...reads];
91
- // Durable premises the writer is registering: the rows or groups it
92
- // wants to keep hearing about after this commit, delivered on a future receipt.
93
- if (track && track.length > 0) payload.track = [...track];
91
+ // Preserve declaration at the wire boundary. `null`, an explicit empty list,
92
+ // and omission are distinct inputs even when today's server treats each as
93
+ // "none"; the WebSocket path must not silently erase a field the caller set.
94
+ if (reads !== undefined) payload.reads = reads === null ? null : [...reads];
95
+ if (track !== undefined) payload.track = track === null ? null : [...track];
94
96
  return { type: 'commit', payload };
95
97
  }
96
98
 
@@ -15,7 +15,7 @@
15
15
  * first successful connect and disposed on teardown.
16
16
  *
17
17
  * This is a plain state machine, not a reactive object: it lives in the
18
- * settlement core, which carries no reactivity runtime (ADR 0016). A consumer
18
+ * confirmation core, which carries no reactivity runtime (ADR 0016). A consumer
19
19
  * that wants to render recovery progress mirrors the transitions through
20
20
  * {@link ConnectionCallbacks.onStateChange} into its own observable state —
21
21
  * which is what the reactive engine's store does.