@abloatai/ablo 0.29.3 → 0.30.1

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 (118) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +3 -2
  3. package/dist/BaseSyncedStore.js +3 -1
  4. package/dist/Database.js +29 -2
  5. package/dist/SyncClient.d.ts +5 -3
  6. package/dist/SyncClient.js +8 -4
  7. package/dist/ai-sdk/coordinatedTool.d.ts +0 -1
  8. package/dist/ai-sdk/coordinatedTool.js +0 -1
  9. package/dist/ai-sdk/coordinationContext.js +1 -5
  10. package/dist/cli.cjs +377 -84
  11. package/dist/client/Ablo.d.ts +1 -1
  12. package/dist/client/Ablo.js +24 -36
  13. package/dist/client/auth.d.ts +8 -18
  14. package/dist/client/auth.js +16 -96
  15. package/dist/client/createModelProxy.d.ts +16 -11
  16. package/dist/client/createModelProxy.js +162 -20
  17. package/dist/client/httpClient.d.ts +1 -1
  18. package/dist/client/httpTransport.js +263 -89
  19. package/dist/client/options.d.ts +0 -15
  20. package/dist/client/resourceTypes.d.ts +13 -26
  21. package/dist/client/schemaConfig.js +5 -0
  22. package/dist/client/writeOptionsSchema.d.ts +1 -0
  23. package/dist/client/writeOptionsSchema.js +11 -2
  24. package/dist/client/wsMutationExecutor.d.ts +3 -5
  25. package/dist/coordination/schema.d.ts +17 -13
  26. package/dist/coordination/schema.js +36 -10
  27. package/dist/errorCodes.d.ts +9 -4
  28. package/dist/errorCodes.js +10 -5
  29. package/dist/errors.js +4 -9
  30. package/dist/index.d.ts +4 -0
  31. package/dist/interfaces/index.d.ts +31 -19
  32. package/dist/schema/index.d.ts +1 -1
  33. package/dist/schema/index.js +4 -1
  34. package/dist/schema/schema.d.ts +13 -0
  35. package/dist/schema/select.js +7 -0
  36. package/dist/schema/syncDeltaRow.d.ts +31 -13
  37. package/dist/schema/syncDeltaRow.js +33 -15
  38. package/dist/schema/tenancy.d.ts +52 -16
  39. package/dist/schema/tenancy.js +86 -25
  40. package/dist/server/commit.d.ts +8 -24
  41. package/dist/server/index.d.ts +1 -0
  42. package/dist/server/index.js +1 -0
  43. package/dist/server/readConfig.d.ts +10 -0
  44. package/dist/source/adapter.d.ts +26 -15
  45. package/dist/source/adapter.js +5 -3
  46. package/dist/source/adapters/drizzle.js +35 -9
  47. package/dist/source/adapters/kysely.d.ts +33 -61
  48. package/dist/source/adapters/kysely.js +170 -177
  49. package/dist/source/adapters/kyselyMutationCore.d.ts +76 -0
  50. package/dist/source/adapters/kyselyMutationCore.js +125 -0
  51. package/dist/source/adapters/memory.d.ts +1 -1
  52. package/dist/source/adapters/memory.js +24 -10
  53. package/dist/source/adapters/prisma.js +35 -8
  54. package/dist/source/conformance.d.ts +26 -28
  55. package/dist/source/conformance.js +142 -69
  56. package/dist/source/contract.d.ts +46 -4
  57. package/dist/source/contract.js +66 -5
  58. package/dist/source/factory.d.ts +3 -3
  59. package/dist/source/factory.js +24 -3
  60. package/dist/source/idempotency.d.ts +46 -0
  61. package/dist/source/idempotency.js +129 -0
  62. package/dist/source/index.d.ts +6 -4
  63. package/dist/source/index.js +5 -3
  64. package/dist/source/migrations.d.ts +9 -5
  65. package/dist/source/migrations.js +39 -6
  66. package/dist/source/types.d.ts +88 -14
  67. package/dist/source/types.js +16 -0
  68. package/dist/surface.d.ts +1 -1
  69. package/dist/surface.js +0 -1
  70. package/dist/sync/BootstrapFetcher.js +7 -0
  71. package/dist/sync/SyncWebSocket.d.ts +5 -27
  72. package/dist/sync/awaitClaimGrant.d.ts +6 -0
  73. package/dist/sync/awaitClaimGrant.js +12 -2
  74. package/dist/sync/commitFrames.d.ts +2 -4
  75. package/dist/sync/commitFrames.js +1 -0
  76. package/dist/sync/createClaimStream.d.ts +1 -1
  77. package/dist/sync/createClaimStream.js +13 -15
  78. package/dist/sync/deltaPipeline.d.ts +1 -1
  79. package/dist/sync/deltaPipeline.js +1 -1
  80. package/dist/sync/participants.d.ts +2 -3
  81. package/dist/sync/participants.js +2 -2
  82. package/dist/sync/wsFrameHandlers.js +35 -15
  83. package/dist/testing/mocks/MockMutationExecutor.d.ts +21 -0
  84. package/dist/testing/mocks/MockMutationExecutor.js +53 -1
  85. package/dist/transactions/TransactionQueue.d.ts +61 -9
  86. package/dist/transactions/TransactionQueue.js +502 -114
  87. package/dist/transactions/commitEnvelope.d.ts +4 -0
  88. package/dist/transactions/commitEnvelope.js +25 -0
  89. package/dist/transactions/commitPayload.d.ts +23 -5
  90. package/dist/transactions/commitPayload.js +3 -0
  91. package/dist/transactions/deltaConfirmation.d.ts +2 -1
  92. package/dist/transactions/deltaConfirmation.js +22 -4
  93. package/dist/transactions/durableWriteStore.d.ts +8 -1
  94. package/dist/transactions/httpCommitEnvelope.d.ts +3 -1
  95. package/dist/transactions/httpCommitEnvelope.js +30 -0
  96. package/dist/transactions/replayValidation.d.ts +4 -0
  97. package/dist/transactions/replayValidation.js +6 -0
  98. package/dist/types/streams.d.ts +29 -17
  99. package/dist/wire/commit.d.ts +671 -0
  100. package/dist/wire/commit.js +300 -0
  101. package/dist/wire/delta.d.ts +4 -0
  102. package/dist/wire/delta.js +9 -0
  103. package/dist/wire/frames.d.ts +17 -45
  104. package/dist/wire/index.d.ts +2 -0
  105. package/dist/wire/index.js +4 -0
  106. package/docs/agent-messaging.md +4 -5
  107. package/docs/agents.md +1 -2
  108. package/docs/api-keys.md +1 -2
  109. package/docs/client-behavior.md +4 -6
  110. package/docs/coordination.md +64 -9
  111. package/docs/data-sources.md +4 -7
  112. package/docs/integration-guide.md +6 -8
  113. package/docs/migration.md +0 -3
  114. package/docs/quickstart.md +7 -8
  115. package/docs/schema-contract.md +7 -8
  116. package/package.json +4 -4
  117. package/dist/client/registerDataSource.d.ts +0 -19
  118. package/dist/client/registerDataSource.js +0 -66
@@ -0,0 +1,300 @@
1
+ /**
2
+ * Canonical runtime contracts for commit settlement.
3
+ *
4
+ * A commit crosses several boundaries during its lifetime: the server's
5
+ * execution cache, the HTTP/WS receipt, and the client's acknowledgement
6
+ * tracker. Those boundaries intentionally have different envelopes, but they
7
+ * all compose the same settlement vocabulary from this module:
8
+ *
9
+ * - `confirmed` — the authoritative change is visible at a sync watermark.
10
+ * - `queued` — a connected source durably accepted the write and only its
11
+ * correlated authoritative source delta (WAL for direct, endpoint event
12
+ * for endpoint-only), identified by `correlationId`, may promote it.
13
+ *
14
+ * Keeping the variants discriminated here makes an impossible receipt (most
15
+ * importantly, `queued` without a correlation) unrepresentable at every
16
+ * untrusted boundary without collapsing the distinct lifecycle envelopes into
17
+ * one giant object.
18
+ */
19
+ import { z } from 'zod';
20
+ import { staleNotificationSchema } from '../coordination/schema.js';
21
+ /** Matches the permanent source/idempotency-key ceiling. */
22
+ export const COMMIT_CORRELATION_ID_MAX_LENGTH = 255;
23
+ /** Opaque server-authored identity shared by a queued receipt and its authoritative source delta. */
24
+ export const correlationIdSchema = z.string().min(1).max(COMMIT_CORRELATION_ID_MAX_LENGTH);
25
+ export const commitStatusSchema = z.enum(['queued', 'confirmed']);
26
+ const queuedSettlementShape = {
27
+ status: z.literal('queued'),
28
+ correlationId: correlationIdSchema,
29
+ };
30
+ const confirmedSettlementShape = {
31
+ status: z.literal('confirmed'),
32
+ correlationId: correlationIdSchema.optional(),
33
+ };
34
+ const queuedSettlementSchema = z.strictObject(queuedSettlementShape);
35
+ const confirmedSettlementSchema = z.strictObject(confirmedSettlementShape);
36
+ /** The one settlement discriminant shared by every commit boundary. */
37
+ export const commitSettlementSchema = z.discriminatedUnion('status', [
38
+ queuedSettlementSchema,
39
+ confirmedSettlementSchema,
40
+ ]);
41
+ const missingIdsSchema = z.array(z.string().min(1));
42
+ const notificationsSchema = z.array(staleNotificationSchema);
43
+ const successfulReceiptCommonShape = {
44
+ object: z.literal('commit_receipt'),
45
+ /** Optional convenience id used by some HTTP surfaces. */
46
+ id: z.string().min(1).optional(),
47
+ clientTxId: z.string().min(1),
48
+ serverTxId: z.string().min(1),
49
+ success: z.literal(true),
50
+ lastSyncId: z.number().int().nonnegative(),
51
+ ops: z.number().int().nonnegative(),
52
+ notifications: notificationsSchema.optional(),
53
+ missingIds: missingIdsSchema.optional(),
54
+ };
55
+ const queuedCommitReceiptSchema = z
56
+ .object({
57
+ ...successfulReceiptCommonShape,
58
+ ...queuedSettlementShape,
59
+ })
60
+ .superRefine((receipt, context) => {
61
+ if (receipt.lastSyncId !== 0) {
62
+ context.addIssue({
63
+ code: 'custom',
64
+ path: ['lastSyncId'],
65
+ message: 'A queued source receipt cannot claim a confirmed sync watermark',
66
+ });
67
+ }
68
+ });
69
+ const confirmedCommitReceiptSchema = z
70
+ .object({
71
+ ...successfulReceiptCommonShape,
72
+ ...confirmedSettlementShape,
73
+ })
74
+ .superRefine((receipt, context) => {
75
+ if (receipt.correlationId && receipt.lastSyncId <= 0) {
76
+ context.addIssue({
77
+ code: 'custom',
78
+ path: ['lastSyncId'],
79
+ message: 'A source-confirmed receipt requires a positive sync watermark',
80
+ });
81
+ }
82
+ });
83
+ /**
84
+ * Successful HTTP/WS receipt. This schema is deliberately strict about the
85
+ * settlement discriminant. Zod's default object behavior strips additive
86
+ * fields, which is forward compatible without allowing server-internal
87
+ * recovery evidence to leak through a parsed wire receipt.
88
+ */
89
+ export const commitReceiptSchema = z.discriminatedUnion('status', [
90
+ queuedCommitReceiptSchema,
91
+ confirmedCommitReceiptSchema,
92
+ ]);
93
+ /**
94
+ * Compatibility decoder for pre-contract WebSocket acknowledgements. It
95
+ * supplies fields old servers omitted and normalizes their string sync ids,
96
+ * then hands the result to the exact same canonical receipt schema. It never
97
+ * invents a source correlation: an old/malformed queued receipt still fails
98
+ * closed.
99
+ */
100
+ export const legacyCompatibleCommitReceiptSchema = z.preprocess((value) => {
101
+ if (typeof value !== 'object' || value === null)
102
+ return value;
103
+ const receipt = value;
104
+ if (receipt.success !== true)
105
+ return value;
106
+ const rawLastSyncId = receipt.lastSyncId;
107
+ const numericLastSyncId = typeof rawLastSyncId === 'string' && rawLastSyncId.trim().length > 0
108
+ ? Number(rawLastSyncId)
109
+ : rawLastSyncId;
110
+ const normalizedLastSyncId = typeof numericLastSyncId === 'number' &&
111
+ Number.isSafeInteger(numericLastSyncId) &&
112
+ numericLastSyncId >= 0
113
+ ? numericLastSyncId
114
+ : undefined;
115
+ return {
116
+ ...receipt,
117
+ object: receipt.object ?? 'commit_receipt',
118
+ status: receipt.status ?? 'confirmed',
119
+ serverTxId: typeof receipt.serverTxId === 'string'
120
+ ? receipt.serverTxId
121
+ : String(normalizedLastSyncId ?? 0),
122
+ ops: typeof receipt.ops === 'number' && Number.isSafeInteger(receipt.ops) ? receipt.ops : 0,
123
+ lastSyncId: normalizedLastSyncId ?? 0,
124
+ };
125
+ }, commitReceiptSchema);
126
+ const requiredCapabilitySchema = z.custom((value) => typeof value === 'object' &&
127
+ value !== null &&
128
+ typeof value.scope === 'string', { message: 'requiredCapability must contain a scope' });
129
+ const errorCodeSchema = z.custom((value) => typeof value === 'string' && value.length > 0, { message: 'commit rejection code must be a non-empty string' });
130
+ /** The failure arm is separate: queued/confirmed always imply `success:true`. */
131
+ export const rejectedCommitReceiptSchema = z.object({
132
+ object: z.literal('commit_receipt'),
133
+ clientTxId: z.string(),
134
+ serverTxId: z.string(),
135
+ success: z.literal(false),
136
+ status: z.literal('rejected'),
137
+ lastSyncId: z.number().int().nonnegative().optional(),
138
+ ops: z.number().int().nonnegative().optional(),
139
+ error: z.object({
140
+ code: errorCodeSchema,
141
+ message: z.string(),
142
+ field: z.string().optional(),
143
+ requiredCapability: requiredCapabilitySchema.optional(),
144
+ }),
145
+ });
146
+ export const mutationResultPayloadSchema = z.union([
147
+ commitReceiptSchema,
148
+ rejectedCommitReceiptSchema,
149
+ ]);
150
+ export const mutationResultMessageSchema = z.object({
151
+ type: z.literal('mutation_result'),
152
+ payload: mutationResultPayloadSchema,
153
+ });
154
+ const confirmationTransactionIdsSchema = z
155
+ .array(z.string().min(1))
156
+ .min(1)
157
+ .refine((ids) => new Set(ids).size === ids.length, {
158
+ message: 'confirmation transaction ids must be unique',
159
+ });
160
+ /**
161
+ * Server-internal execution result persisted in `mutation_log`.
162
+ *
163
+ * `status` remains optional only for pre-settlement hosted rows. At runtime an
164
+ * omitted value has the established meaning `confirmed`; new source-forwarded
165
+ * rows must use the explicit queued arm with both public correlation and exact
166
+ * internal storage ids.
167
+ */
168
+ const rawCommitExecutionResultSchema = z
169
+ .strictObject({
170
+ lastSyncId: z.number().int().nonnegative(),
171
+ firstSyncId: z.number().int().nonnegative(),
172
+ status: commitStatusSchema.optional(),
173
+ correlationId: correlationIdSchema.optional(),
174
+ confirmationTransactionIds: confirmationTransactionIdsSchema.optional(),
175
+ notifications: notificationsSchema.optional(),
176
+ missingIds: missingIdsSchema.optional(),
177
+ })
178
+ .superRefine((result, context) => {
179
+ const validRange = (result.firstSyncId === 0 && result.lastSyncId === 0) ||
180
+ (result.firstSyncId > 0 && result.firstSyncId <= result.lastSyncId);
181
+ if (!validRange) {
182
+ context.addIssue({
183
+ code: 'custom',
184
+ path: ['firstSyncId'],
185
+ message: 'CommitResult sync range is inconsistent',
186
+ });
187
+ }
188
+ if (result.status === 'queued') {
189
+ if (!result.correlationId) {
190
+ context.addIssue({
191
+ code: 'custom',
192
+ path: ['correlationId'],
193
+ message: 'A queued source result requires a correlationId',
194
+ });
195
+ }
196
+ if (!result.confirmationTransactionIds) {
197
+ context.addIssue({
198
+ code: 'custom',
199
+ path: ['confirmationTransactionIds'],
200
+ message: 'A queued source result requires durable confirmation ids',
201
+ });
202
+ }
203
+ if (result.firstSyncId !== 0 || result.lastSyncId !== 0) {
204
+ context.addIssue({
205
+ code: 'custom',
206
+ path: ['lastSyncId'],
207
+ message: 'A queued source result cannot claim a confirmed sync range',
208
+ });
209
+ }
210
+ }
211
+ if (result.confirmationTransactionIds && !result.correlationId) {
212
+ context.addIssue({
213
+ code: 'custom',
214
+ path: ['correlationId'],
215
+ message: 'Confirmation transaction ids require their source correlation',
216
+ });
217
+ }
218
+ if (result.correlationId && !result.confirmationTransactionIds) {
219
+ context.addIssue({
220
+ code: 'custom',
221
+ path: ['confirmationTransactionIds'],
222
+ message: 'A source correlation requires exact confirmation transaction ids',
223
+ });
224
+ }
225
+ if (result.correlationId && result.status === undefined) {
226
+ context.addIssue({
227
+ code: 'custom',
228
+ path: ['status'],
229
+ message: 'Source-correlated results require explicit settlement status',
230
+ });
231
+ }
232
+ if (result.status === 'confirmed' &&
233
+ result.correlationId &&
234
+ (result.firstSyncId <= 0 || result.lastSyncId <= 0)) {
235
+ context.addIssue({
236
+ code: 'custom',
237
+ path: ['lastSyncId'],
238
+ message: 'A source-confirmed result requires a durable positive sync range',
239
+ });
240
+ }
241
+ });
242
+ export const commitExecutionResultSchema = rawCommitExecutionResultSchema.transform((result) => ({
243
+ ...result,
244
+ status: result.status ?? 'confirmed',
245
+ }));
246
+ const ackCommonShape = {
247
+ lastSyncId: z.number().int().nonnegative(),
248
+ notifications: notificationsSchema.optional(),
249
+ missingIds: missingIdsSchema.optional(),
250
+ };
251
+ /** Normalized acknowledgement handed from a mutation transport to the queue. */
252
+ export const commitAckSchema = z.discriminatedUnion('status', [
253
+ z
254
+ .strictObject({
255
+ ...ackCommonShape,
256
+ ...queuedSettlementShape,
257
+ })
258
+ .refine(({ lastSyncId }) => lastSyncId === 0, {
259
+ path: ['lastSyncId'],
260
+ message: 'A queued acknowledgement cannot claim a sync watermark',
261
+ }),
262
+ z
263
+ .strictObject({
264
+ ...ackCommonShape,
265
+ ...confirmedSettlementShape,
266
+ })
267
+ .refine(({ correlationId, lastSyncId }) => correlationId === undefined || lastSyncId > 0, {
268
+ path: ['lastSyncId'],
269
+ message: 'A source-confirmed acknowledgement requires a sync watermark',
270
+ }),
271
+ ]);
272
+ const legacyMutationCommitResultSchema = z.strictObject({
273
+ ...ackCommonShape,
274
+ status: z.undefined().optional(),
275
+ correlationId: z.undefined().optional(),
276
+ });
277
+ /**
278
+ * Compatibility boundary for injected/older mutation executors. New
279
+ * transports return `commitAckSchema`; a legacy `{lastSyncId}` is normalized
280
+ * to explicit confirmed settlement before queue code branches on it.
281
+ */
282
+ export const mutationCommitResultSchema = z
283
+ .union([commitAckSchema, legacyMutationCommitResultSchema])
284
+ .transform((result) => (result.status ? result : { ...result, status: 'confirmed' }));
285
+ /**
286
+ * Public SDK projection returned by `ablo.commits` and model mutations.
287
+ *
288
+ * This intentionally does not compose `commitSettlementSchema`: on the WS
289
+ * facade, `wait:'queued'` currently means locally sealed/enqueued, before a
290
+ * server has necessarily accepted the write. Renaming that public state to
291
+ * `enqueued` is a separate protocol change; the authoritative settlement
292
+ * union above is reserved for server acknowledgements.
293
+ */
294
+ export const clientCommitReceiptSchema = z.strictObject({
295
+ id: z.string().min(1),
296
+ status: commitStatusSchema,
297
+ lastSyncId: z.number().int().nonnegative().optional(),
298
+ notifications: notificationsSchema.optional(),
299
+ missingIds: missingIdsSchema.optional(),
300
+ });
@@ -105,6 +105,7 @@ export declare const syncDeltaWireCoreSchema: z.ZodObject<{
105
105
  previousData: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodRecord<z.ZodString, z.ZodUnknown>, z.ZodString]>>>;
106
106
  syncGroups: z.ZodArray<z.ZodString>;
107
107
  transactionId: z.ZodOptional<z.ZodString>;
108
+ correlationId: z.ZodOptional<z.ZodString>;
108
109
  createdAt: z.ZodString;
109
110
  }, z.core.$strip>;
110
111
  export type SyncDeltaWireCore = z.infer<typeof syncDeltaWireCoreSchema>;
@@ -130,6 +131,7 @@ export declare const clientSyncDeltaSchema: z.ZodObject<{
130
131
  previousData: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodRecord<z.ZodString, z.ZodUnknown>, z.ZodString]>>>;
131
132
  syncGroups: z.ZodArray<z.ZodString>;
132
133
  transactionId: z.ZodOptional<z.ZodString>;
134
+ correlationId: z.ZodOptional<z.ZodString>;
133
135
  createdAt: z.ZodString;
134
136
  createdBy: z.ZodOptional<z.ZodString>;
135
137
  metadata: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodRecord<z.ZodString, z.ZodUnknown>, z.ZodString]>>>;
@@ -158,7 +160,9 @@ export declare const serverSyncDeltaSchema: z.ZodObject<{
158
160
  data: z.ZodNullable<z.ZodUnion<readonly [z.ZodRecord<z.ZodString, z.ZodUnknown>, z.ZodString]>>;
159
161
  previousData: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodRecord<z.ZodString, z.ZodUnknown>, z.ZodString]>>>;
160
162
  syncGroups: z.ZodArray<z.ZodString>;
163
+ correlationId: z.ZodOptional<z.ZodString>;
161
164
  createdAt: z.ZodString;
165
+ projectId: z.ZodString;
162
166
  transactionId: z.ZodNullable<z.ZodString>;
163
167
  createdBy: z.ZodNullable<z.ZodObject<{
164
168
  kind: z.ZodEnum<{
@@ -24,6 +24,7 @@
24
24
  * up into the schema DSL for them.
25
25
  */
26
26
  import { z } from 'zod';
27
+ import { correlationIdSchema } from './commit.js';
27
28
  // ── Shared participant vocabulary (mirrors the corresponding Postgres enums) ──
28
29
  /** `participant_kind` — who a delta is attributed to. */
29
30
  export const participantKindSchema = z.enum(['user', 'agent', 'system']);
@@ -78,6 +79,8 @@ export const syncDeltaWireCoreSchema = z.object({
78
79
  previousData: wireDeltaDataSchema.optional(),
79
80
  syncGroups: z.array(z.string()),
80
81
  transactionId: z.string().optional(),
82
+ /** Opaque source-batch identity present only on decoded customer-WAL echoes. */
83
+ correlationId: correlationIdSchema.optional(),
81
84
  createdAt: z.string(),
82
85
  });
83
86
  /**
@@ -99,6 +102,12 @@ export const clientSyncDeltaSchema = syncDeltaWireCoreSchema.extend({
99
102
  * projection also narrows `transactionId` to nullable, matching the stored column.
100
103
  */
101
104
  export const serverSyncDeltaSchema = syncDeltaWireCoreSchema.extend({
105
+ /** The plane project the delta was committed on (`''` = the org's default
106
+ * project). Server-only: the broadcaster uses it to fan a mixed-project batch
107
+ * out ONLY to same-project subscribers. It rides on the frame but the client's
108
+ * `clientSyncDeltaSchema` (a non-strict object) strips it — a client only ever
109
+ * receives its own project's deltas, so the value is redundant to it. */
110
+ projectId: z.string(),
102
111
  transactionId: z.string().nullable(),
103
112
  /** @deprecated Duplicates `actor`; read `actor` instead. */
104
113
  createdBy: participantRefSchema.nullable(),
@@ -13,8 +13,8 @@
13
13
  * server to update together.
14
14
  */
15
15
  import { z } from 'zod';
16
- import type { OnStaleMode, StaleNotification, ReadDependency } from '../coordination/index.js';
17
- import type { ErrorCode, RequiredCapability } from '../errors.js';
16
+ import type { OnStaleMode, ReadDependency } from '../coordination/index.js';
17
+ import type { MutationResultMessageWire } from './commit.js';
18
18
  /**
19
19
  * A single operation within a {@link CommitMessage} batch. Each operation is
20
20
  * the smallest unit the server applies atomically — one create, update,
@@ -55,6 +55,15 @@ export interface CommitOperation {
55
55
  * framework identities; a bypass requested by an agent is ignored.
56
56
  */
57
57
  bypass?: boolean | null;
58
+ /**
59
+ * The monotonic fencing token of the held claim this write was issued under
60
+ * ({@link https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html Kleppmann fencing}).
61
+ * The server rejects the write if a later holder already advanced this
62
+ * entity's persisted high-water past the token — closing the "lapsed holder
63
+ * resumes after its successor came and went" residual that the live-lease
64
+ * claim guard cannot see. Absent on every unclaimed write.
65
+ */
66
+ fenceToken?: number | null;
58
67
  }
59
68
  /**
60
69
  * Runtime validator for {@link CommitOperation}. Both commit transports — the
@@ -83,6 +92,7 @@ export declare const commitOperationSchema: z.ZodObject<{
83
92
  id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
84
93
  input: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
85
94
  transactionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
95
+ fenceToken: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
86
96
  bypass: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
87
97
  }, z.core.$strip>;
88
98
  /**
@@ -138,6 +148,7 @@ export declare const commitPayloadSchema: z.ZodObject<{
138
148
  id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
139
149
  input: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
140
150
  transactionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
151
+ fenceToken: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
141
152
  bypass: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
142
153
  }, z.core.$strip>>;
143
154
  clientTxId: z.ZodString;
@@ -163,47 +174,8 @@ export declare const commitPayloadSchema: z.ZodObject<{
163
174
  }, z.core.$strip>]>>>>;
164
175
  }, z.core.$strip>;
165
176
  /**
166
- * The server's acknowledgement of a {@link CommitMessage}. Its payload mirrors
167
- * the commit-receipt shape, so a commit acknowledged over a WebSocket, over the
168
- * HTTP `/v1/commits` endpoint, or read back from a persisted job result all
169
- * carry the same fields.
170
- *
171
- * `object`, `status`, and `ops` are optional in the type but the server always
172
- * populates them, so a current client can rely on them being present.
177
+ * The server's acknowledgement of a {@link CommitMessage}. Runtime shape and
178
+ * TypeScript type are both owned by `wire/commit.ts`; HTTP, WebSocket, and
179
+ * cached replay no longer maintain parallel receipt declarations.
173
180
  */
174
- export interface MutationResultMessage {
175
- type: 'mutation_result';
176
- payload: {
177
- object?: 'commit_receipt';
178
- clientTxId: string;
179
- serverTxId: string;
180
- success: boolean;
181
- status?: 'confirmed' | 'rejected';
182
- lastSyncId?: number;
183
- ops?: number;
184
- /**
185
- * Notifications for operations that used `onStale: 'notify'` and whose
186
- * premise changed while the batch was being applied. Present only on a
187
- * successful acknowledgement that resolved such a conflict; the client
188
- * surfaces each one through its `conflict:notified` event and the commit
189
- * receipt rather than failing the write.
190
- */
191
- notifications?: StaleNotification[];
192
- /**
193
- * Ids of update or delete targets that matched no rows — because they do
194
- * not exist or fall outside the caller's organization. Present and
195
- * non-empty only when a write missed. The client raises a not-found error
196
- * for the affected caller rather than treating the no-op as a success.
197
- */
198
- missingIds?: string[];
199
- error?: {
200
- code: ErrorCode;
201
- message: string;
202
- field?: string;
203
- /** The capability the commit required but the caller lacked. Present when
204
- * the commit was denied for want of a capability, so the client can tell
205
- * the caller exactly what to obtain. */
206
- requiredCapability?: RequiredCapability;
207
- };
208
- };
209
- }
181
+ export type MutationResultMessage = MutationResultMessageWire;
@@ -25,6 +25,8 @@ export { commitOperationSchema, commitPayloadSchema, } from './frames.js';
25
25
  export { PROTOCOL_VERSION, MIN_SUPPORTED_PROTOCOL_VERSION, DEFAULT_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS, WS_CLOSE_PROTOCOL_VERSION, PROTOCOL_VERSION_HEADER, protocolVersionProblem, resolveProtocolVersion, } from './protocolVersion.js';
26
26
  export type { SupportedProtocolVersion, ProtocolVersionProblem, } from './protocolVersion.js';
27
27
  export type { CommitOperation, CommitMessage, MutationResultMessage, } from './frames.js';
28
+ export { COMMIT_CORRELATION_ID_MAX_LENGTH, correlationIdSchema, commitStatusSchema, commitSettlementSchema, commitReceiptSchema, legacyCompatibleCommitReceiptSchema, rejectedCommitReceiptSchema, mutationResultPayloadSchema, mutationResultMessageSchema, commitAckSchema, mutationCommitResultSchema, } from './commit.js';
29
+ export type { CorrelationId, CommitStatus, CommitSettlement, CommitReceiptWire, RejectedCommitReceiptWire, MutationResultPayload, MutationResultMessageWire, CommitAck, MutationCommitResultInput, MutationCommitResult, } from './commit.js';
28
30
  export { participantKindSchema, confirmationStateSchema, syncDeltaActionSchema, wireDeltaDataSchema, participantRefSchema, syncDeltaWireCoreSchema, clientSyncDeltaSchema, serverSyncDeltaSchema, } from './delta.js';
29
31
  export type { ParticipantKind, ConfirmationState, SyncDeltaAction, WireDeltaData, ParticipantRef, SyncDeltaWireCore, ClientSyncDelta, ServerSyncDelta, } from './delta.js';
30
32
  export { AbloError, AbloAuthenticationError, AbloPermissionError, AbloValidationError, AbloRateLimitError, AbloIdempotencyError, AbloConnectionError, AbloServerError, AbloStaleContextError, AbloClaimedError, CapabilityError, SyncSessionError, docUrlForCode, translateHttpError, errorFromWire, toAbloError, ERROR_CONTRACT_VERSION, errorCodeSpec, } from '../errors.js';
@@ -27,6 +27,10 @@ export { commitOperationSchema, commitPayloadSchema, } from './frames.js';
27
27
  // confirm they can speak to each other, plus the WebSocket close code used to
28
28
  // reject a mismatch. See protocolVersion.ts for the changelog and deploy rules.
29
29
  export { PROTOCOL_VERSION, MIN_SUPPORTED_PROTOCOL_VERSION, DEFAULT_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS, WS_CLOSE_PROTOCOL_VERSION, PROTOCOL_VERSION_HEADER, protocolVersionProblem, resolveProtocolVersion, } from './protocolVersion.js';
30
+ // Commit settlement backbone. The transport receipt, server execution cache,
31
+ // and normalized client acknowledgement are different envelopes composed from
32
+ // this one discriminated settlement vocabulary.
33
+ export { COMMIT_CORRELATION_ID_MAX_LENGTH, correlationIdSchema, commitStatusSchema, commitSettlementSchema, commitReceiptSchema, legacyCompatibleCommitReceiptSchema, rejectedCommitReceiptSchema, mutationResultPayloadSchema, mutationResultMessageSchema, commitAckSchema, mutationCommitResultSchema, } from './commit.js';
30
34
  // The read-path delta contract: the shape the server broadcasts to clients as the
31
35
  // payload of a `delta` or `sync_response` frame, together with the shared
32
36
  // participant vocabulary it carries. Both ends derive their delta type from these
@@ -61,22 +61,21 @@ npx ablo push
61
61
 
62
62
  ## Server-side agent
63
63
 
64
- Most server-side agents use the direct database path. Pass the schema, API key,
65
- database URL, and transport selector:
64
+ Most server-side agents use the stateless HTTP transport. Pass the schema, API
65
+ key, and transport selector:
66
66
 
67
67
  ```ts
68
68
  const ablo = Ablo({
69
69
  schema,
70
70
  apiKey: process.env.ABLO_API_KEY,
71
- databaseUrl: process.env.DATABASE_URL,
72
71
  transport: "http",
73
72
  });
74
73
 
75
74
  await ablo.ready();
76
75
  ```
77
76
 
78
- `databaseUrl` is server-only. Browser clients must not receive it; live UIs use
79
- the default WebSocket transport with a minted user/session token.
77
+ The secret `apiKey` is server-only. Browser clients must not receive it; live UIs
78
+ use the default WebSocket transport with a minted user/session token.
80
79
 
81
80
  If your backend mints restricted agent tokens, register the database once from a
82
81
  secret-key server process as above. Workers using the restricted token can then
package/docs/agents.md CHANGED
@@ -75,8 +75,7 @@ reconnects or be readable by HTTP agents. The recipe is a `messages` model
75
75
  scoped by the same syncGroup field as the work row, with `aboutIntentId` linking
76
76
  a message back to the claim it discusses.
77
77
 
78
- See [Agent Messaging](/agent-messaging) for the schema and direct
79
- `databaseUrl` setup.
78
+ See [Agent Messaging](/agent-messaging) for the schema and setup details.
80
79
 
81
80
  ## Humans + agents on one state
82
81
 
package/docs/api-keys.md CHANGED
@@ -47,7 +47,7 @@ tab-focus / network-online / device-wake). This is the Stripe ephemeral-key / Su
47
47
  session model. For a read-only app you don't need any of this — just the `pk_` above.
48
48
 
49
49
  Server-side, because `apiKey` defaults to `process.env.ABLO_API_KEY`, most backend and agent
50
- code passes nothing. The secret `sk_` (and `databaseUrl`) are **server-only** — never in a
50
+ code passes nothing. The secret `sk_` is **server-only** — never in a
51
51
  browser bundle. There is no `getToken` or `as` option — `apiKey` (the key a server holds)
52
52
  and `authEndpoint` (the mint route a browser points at) are the two credential
53
53
  knobs, and you set exactly one.
@@ -58,7 +58,6 @@ knobs, and you set exactly one.
58
58
  |---|---|---|
59
59
  | Human end-user session | `await server.sessions.create({ user: { id } })` | `ek_` (full user authority) |
60
60
  | Scoped delegated agent | `await server.sessions.create({ agent: { id }, can: { Task: ['update'] } })` | `rk_` (scoped to `can`) |
61
- | Connect Ablo to your own Postgres | `Ablo({ schema, apiKey, databaseUrl })` (server-only) | dedicated tenant |
62
61
 
63
62
  The principal kind comes from *which* shape you pass — `{ user }` → `user`, `{ agent, can }` → `agent`.
64
63
 
@@ -29,7 +29,6 @@ Common options:
29
29
  |---|---|
30
30
  | `schema` | Required for typed model clients. |
31
31
  | `apiKey` | Bearer credential for trusted server runtimes. Defaults to `ABLO_API_KEY` when available. |
32
- | `databaseUrl` | Optional, server-only. Registers your Postgres directly (the connection-string path). Pass it explicitly — it is **not** auto-read from the environment. Omit it for a signed Data Source endpoint or the hosted sandbox. The SDK throws if it sees this in a browser. |
33
32
  | `baseURL` | Override the hosted sync endpoint for staging or private deployments. |
34
33
  | `persistence` | `memory` by default. Use `indexeddb` for a durable browser cache that survives reloads. |
35
34
  | `durableWrites` | Optional crash recovery for unacknowledged agent/worker writes. Independent of the default memory cache; accepts `{ store, namespace? }`. |
@@ -39,11 +38,10 @@ Common options:
39
38
  | `defaultQuery` | Extra query parameters attached to every HTTP request. |
40
39
  | `dangerouslyAllowBrowser` | Required before sending an API key from browser code. Prefer a server route instead. |
41
40
 
42
- `databaseUrl` is an optional, server-only constructor option. It is **not**
43
- auto-read from the environment pass it explicitly to register your Postgres
44
- directly (the connection-string path). Omit it when you expose a signed
45
- [Data Source](./data-sources.md) endpoint, or when trying Ablo against the hosted
46
- sandbox.
41
+ Your database connects out of band through logical replication (`npx ablo
42
+ connect`) or a signed [Data Source](./data-sources.md) endpoint so the client
43
+ holds only `apiKey`, never a connection string. See
44
+ [Connect Your Database](./data-sources.md) for the full setup.
47
45
 
48
46
  ## Model Methods
49
47