@abloatai/ablo 0.10.0 → 0.11.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 (94) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +2 -1
  3. package/dist/BaseSyncedStore.d.ts +75 -0
  4. package/dist/BaseSyncedStore.js +193 -8
  5. package/dist/Database.d.ts +10 -2
  6. package/dist/Database.js +15 -1
  7. package/dist/SyncClient.d.ts +12 -1
  8. package/dist/SyncClient.js +110 -26
  9. package/dist/agent/Agent.d.ts +9 -9
  10. package/dist/agent/Agent.js +16 -16
  11. package/dist/agent/index.d.ts +1 -1
  12. package/dist/agent/index.js +2 -2
  13. package/dist/agent/types.d.ts +1 -1
  14. package/dist/agent/types.js +1 -1
  15. package/dist/ai-sdk/{intent-broadcast.d.ts → claim-broadcast.d.ts} +10 -10
  16. package/dist/ai-sdk/{intent-broadcast.js → claim-broadcast.js} +6 -6
  17. package/dist/ai-sdk/coordination-context.d.ts +9 -9
  18. package/dist/ai-sdk/coordination-context.js +8 -8
  19. package/dist/ai-sdk/index.d.ts +1 -1
  20. package/dist/ai-sdk/index.js +1 -1
  21. package/dist/ai-sdk/wrap.d.ts +4 -4
  22. package/dist/ai-sdk/wrap.js +4 -4
  23. package/dist/api/index.d.ts +2 -2
  24. package/dist/cli.cjs +254 -48
  25. package/dist/client/Ablo.d.ts +30 -63
  26. package/dist/client/Ablo.js +108 -102
  27. package/dist/client/ApiClient.d.ts +6 -5
  28. package/dist/client/ApiClient.js +83 -62
  29. package/dist/client/createModelProxy.d.ts +16 -54
  30. package/dist/client/createModelProxy.js +44 -16
  31. package/dist/client/httpClient.d.ts +2 -0
  32. package/dist/client/httpClient.js +1 -1
  33. package/dist/client/index.d.ts +3 -3
  34. package/dist/client/writeOptionsSchema.d.ts +4 -4
  35. package/dist/client/writeOptionsSchema.js +4 -4
  36. package/dist/coordination/schema.d.ts +249 -38
  37. package/dist/coordination/schema.js +172 -39
  38. package/dist/core/index.d.ts +2 -2
  39. package/dist/core/index.js +4 -4
  40. package/dist/errorCodes.d.ts +9 -9
  41. package/dist/errorCodes.js +15 -15
  42. package/dist/errors.d.ts +51 -2
  43. package/dist/errors.js +94 -5
  44. package/dist/interfaces/index.d.ts +8 -4
  45. package/dist/policy/index.d.ts +1 -1
  46. package/dist/policy/types.d.ts +13 -13
  47. package/dist/policy/types.js +8 -8
  48. package/dist/react/AbloProvider.d.ts +51 -4
  49. package/dist/react/AbloProvider.js +95 -11
  50. package/dist/react/context.d.ts +26 -9
  51. package/dist/react/context.js +2 -2
  52. package/dist/react/index.d.ts +4 -4
  53. package/dist/react/index.js +4 -4
  54. package/dist/react/useAblo.js +5 -5
  55. package/dist/react/{useIntent.d.ts → useClaim.d.ts} +9 -9
  56. package/dist/react/useClaim.js +42 -0
  57. package/dist/schema/index.js +1 -1
  58. package/dist/schema/sugar.d.ts +3 -3
  59. package/dist/schema/sugar.js +3 -3
  60. package/dist/schema/sync-delta-wire.d.ts +8 -8
  61. package/dist/server/commit.d.ts +2 -2
  62. package/dist/sync/AreaOfInterestManager.d.ts +162 -0
  63. package/dist/sync/AreaOfInterestManager.js +233 -0
  64. package/dist/sync/BootstrapHelper.d.ts +9 -1
  65. package/dist/sync/BootstrapHelper.js +15 -5
  66. package/dist/sync/NetworkProbe.d.ts +1 -1
  67. package/dist/sync/NetworkProbe.js +1 -1
  68. package/dist/sync/SyncWebSocket.d.ts +59 -25
  69. package/dist/sync/SyncWebSocket.js +123 -26
  70. package/dist/sync/awaitClaimGrant.d.ts +40 -0
  71. package/dist/sync/awaitClaimGrant.js +86 -0
  72. package/dist/sync/createClaimStream.d.ts +34 -0
  73. package/dist/sync/{createIntentStream.js → createClaimStream.js} +92 -81
  74. package/dist/sync/createPresenceStream.js +3 -2
  75. package/dist/sync/participants.d.ts +10 -10
  76. package/dist/sync/participants.js +17 -10
  77. package/dist/sync/schemas.d.ts +8 -8
  78. package/dist/transactions/TransactionQueue.d.ts +12 -0
  79. package/dist/transactions/TransactionQueue.js +126 -8
  80. package/dist/types/global.d.ts +10 -10
  81. package/dist/types/global.js +3 -3
  82. package/dist/types/index.d.ts +9 -7
  83. package/dist/types/index.js +2 -2
  84. package/dist/types/streams.d.ts +114 -98
  85. package/dist/types/streams.js +1 -1
  86. package/dist/utils/asyncIterator.d.ts +1 -1
  87. package/dist/utils/asyncIterator.js +1 -1
  88. package/dist/wire/frames.d.ts +2 -2
  89. package/docs/migration.md +52 -0
  90. package/package.json +3 -2
  91. package/dist/react/useIntent.js +0 -42
  92. package/dist/sync/awaitIntentGrant.d.ts +0 -40
  93. package/dist/sync/awaitIntentGrant.js +0 -62
  94. package/dist/sync/createIntentStream.d.ts +0 -34
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { syncGroupInputSchema } from '../schema/roles.js';
2
3
  /**
3
4
  * Coordination wire schema — the ONE canonical source for the three layers
4
5
  * that keep humans and agents from clobbering each other on a shared row.
@@ -6,7 +7,7 @@ import { z } from 'zod';
6
7
  * one decision") for the conceptual model. The layers, outer-to-inner:
7
8
  *
8
9
  * 1. PRESENCE (observation) — who is working where; NEVER enforces.
9
- * 2. PESSIMISTIC (claims/leases) — `intent_begin`/`intent_abandon`;
10
+ * 2. PESSIMISTIC (claims/leases) — `claim_begin`/`claim_abandon`;
10
11
  * mutual exclusion between participants.
11
12
  * 3. OPTIMISTIC (stale-context) — `readAt` + `onStale` write-guard;
12
13
  * last-writer-wins lost-update detection.
@@ -14,8 +15,8 @@ import { z } from 'zod';
14
15
  * Both the SDK (`types/streams.ts`) and the sync-server (`hub/types.ts`,
15
16
  * `presence/*`) derive their TypeScript types from THESE schemas via
16
17
  * `z.infer`, instead of re-declaring overlapping shapes. That collapses the
17
- * field drift this surface accreted — e.g. the SDK's intent view dropping
18
- * `status`/`error`, `onStale` declared 5×, `IntentStatus` declared 2× — into
18
+ * field drift this surface accreted — e.g. the SDK's claim view dropping
19
+ * `status`/`error`, `onStale` declared 5×, `ClaimStatus` declared 2× — into
19
20
  * a single definition that the wire ingest can also validate at runtime.
20
21
  */
21
22
  // ─────────────────────────────────────────────────────────────────────────
@@ -30,7 +31,39 @@ export const targetRangeSchema = z.object({
30
31
  });
31
32
  export const participantKindSchema = z.enum(['user', 'agent', 'system']);
32
33
  /**
33
- * What a claim / intent / activity points at. The common locator shared by
34
+ * Wire-tolerant participant kind for INGEST. The claim/presence streams
35
+ * historically labelled a non-agent participant `'human'`, while the
36
+ * capability/identity/lease surfaces all say `'user'` — the same participant,
37
+ * two dialects. This normalizes the legacy `'human'` to the canonical `'user'`
38
+ * on read so every consumer switches on ONE vocabulary. Producers emit
39
+ * canonical {@link participantKindSchema} values; this only forgives an older
40
+ * frame still carrying `'human'`. Additive — never widens the output union.
41
+ */
42
+ export const wireParticipantKindSchema = z.preprocess((value) => (value === 'human' ? 'user' : value), participantKindSchema);
43
+ /**
44
+ * Resolve a peer's kind from an inbound presence/claim frame. Prefers the
45
+ * server-stamped `participantKind` (normalized via
46
+ * {@link wireParticipantKindSchema}); frames from servers that predate the
47
+ * field fall back to the lossy `isAgent` boolean — which can say 'agent' or
48
+ * 'user' but never 'system' (the flatten this field exists to remove).
49
+ */
50
+ export function participantKindFromWire(wireKind, isAgent) {
51
+ const parsed = wireParticipantKindSchema.safeParse(wireKind);
52
+ if (parsed.success)
53
+ return parsed.data;
54
+ return isAgent ? 'agent' : 'user';
55
+ }
56
+ /**
57
+ * The peer-visible explanation a claim/claim carries, lifted from its opaque
58
+ * `meta.description`. One place for the `typeof meta?.description === 'string'`
59
+ * unfold that the claim/claim/presence surfaces each re-implemented — callers
60
+ * with an explicit `description` field still prefer it (`explicit ?? fromMeta`).
61
+ */
62
+ export function descriptionFromMeta(meta) {
63
+ return typeof meta?.description === 'string' ? meta.description : undefined;
64
+ }
65
+ /**
66
+ * What a claim / claim / activity points at. The common locator shared by
34
67
  * all three layers — an entity, optionally narrowed to a path, range, or
35
68
  * field, with opaque app metadata.
36
69
  */
@@ -64,34 +97,58 @@ export const writeGuardSchema = z.object({
64
97
  bypass: z.boolean().optional(),
65
98
  });
66
99
  // ─────────────────────────────────────────────────────────────────────────
67
- // Layer 2 — PESSIMISTIC claim / intent-lease
100
+ // Layer 2 — PESSIMISTIC claim / claim-lease
68
101
  // ─────────────────────────────────────────────────────────────────────────
69
102
  /**
70
- * Lifecycle of an intent — the Stripe `PaymentIntent.status` shape. Absent on
103
+ * Lifecycle of an claim — the Stripe `PaymentIntent.status` shape. Absent on
71
104
  * the wire ⇒ `'active'` (additive back-compat). The server stamps `'active'`
72
- * on `intent_begin` and emits a single terminal frame (`committed` /
105
+ * on `claim_begin` and emits a single terminal frame (`committed` /
73
106
  * `canceled` / `expired`) as the claim ends, so contenders learn *how* it
74
107
  * resolved, not merely that it vanished.
75
108
  */
76
- export const intentStatusSchema = z.enum([
109
+ export const claimStatusSchema = z.enum([
77
110
  'active',
78
111
  'committed',
79
112
  'expired',
80
113
  'canceled',
81
114
  ]);
115
+ const wireClaimBaseSchema = targetRefSchema.extend({
116
+ claimId: z.string(),
117
+ /** Verb the agent expects: 'update' | 'create' | 'editing' | 'reviewing' … */
118
+ action: z.string(),
119
+ /** Server-stamped declaration time (epoch ms). */
120
+ declaredAt: z.number(),
121
+ /** Server-computed TTL deadline (epoch ms). Readers treat as advisory. */
122
+ expiresAt: z.number(),
123
+ status: claimStatusSchema.optional(),
124
+ });
125
+ export const wireClaimSummarySchema = wireClaimBaseSchema.pick({
126
+ claimId: true,
127
+ action: true,
128
+ declaredAt: true,
129
+ expiresAt: true,
130
+ entityType: true,
131
+ entityId: true,
132
+ field: true,
133
+ meta: true,
134
+ });
82
135
  /** Why a claim ended in a non-success terminal state. */
83
- export const intentErrorSchema = z.object({
136
+ export const claimErrorSchema = z.object({
84
137
  code: z.string(),
85
138
  message: z.string().optional(),
86
139
  /** Participant already holding the target (conflict rejections). */
87
140
  heldBy: z.string().optional(),
88
- heldByIntentId: z.string().optional(),
141
+ heldByClaimId: z.string().optional(),
89
142
  heldByExpiresAt: z.number().optional(),
143
+ /** Rich holder context for conflict rejections. Additive: older frames omit it. */
144
+ heldByClaim: wireClaimSummarySchema.optional(),
145
+ /** Optional conflict-policy explanation. Additive: older frames omit it. */
146
+ policyReason: z.string().optional(),
90
147
  });
91
148
  /**
92
- * A declared pending-mutation intent — the unit broadcast in presence
93
- * `activeIntents`. Clients supply the descriptive `targetRef` fields, an
94
- * `action`, and a chosen `intentId`; the SERVER stamps `declaredAt` /
149
+ * A declared pending-mutation claim — the unit broadcast in presence
150
+ * `activeClaims`. Clients supply the descriptive `targetRef` fields, an
151
+ * `action`, and a chosen `claimId`; the SERVER stamps `declaredAt` /
95
152
  * `expiresAt` and may set `status` / `error`.
96
153
  *
97
154
  * `status` and `error` are OPTIONAL: this single shape serves both the
@@ -100,61 +157,132 @@ export const intentErrorSchema = z.object({
100
157
  * was used, so the two prior copies collapse into this one without breaking
101
158
  * SDK consumers.
102
159
  */
103
- export const intentClaimSchema = targetRefSchema.extend({
104
- intentId: z.string(),
105
- /** Verb the agent expects: 'update' | 'create' | 'editing' | 'reviewing' … */
160
+ export const wireClaimSchema = wireClaimBaseSchema.extend({
161
+ error: claimErrorSchema.optional(),
162
+ });
163
+ export const claimRejectionSchema = z.object({
164
+ claimId: z.string(),
165
+ reason: z.string(),
166
+ target: targetRefSchema.optional(),
167
+ heldBy: z.string().optional(),
168
+ heldByClaimId: z.string().optional(),
169
+ heldByExpiresAt: z.number().optional(),
170
+ heldByClaim: wireClaimSummarySchema.optional(),
171
+ policyReason: z.string().optional(),
172
+ });
173
+ /**
174
+ * What a {@link ModelClaim} points at — the SDK-facing target locator, keyed by
175
+ * `model`/`id` (the `ablo.<model>` vocabulary) rather than the wire's
176
+ * `entityType`/`entityId`. Structurally the public `ModelTarget`.
177
+ */
178
+ export const modelTargetSchema = z
179
+ .object({
180
+ model: z.string(),
181
+ id: z.string(),
182
+ path: z.string().optional(),
183
+ range: targetRangeSchema.optional(),
184
+ field: z.string().optional(),
185
+ meta: z.record(z.string(), z.unknown()).optional(),
186
+ })
187
+ .readonly();
188
+ /**
189
+ * A claim as surfaced to SDK callers and the HTTP claim routes
190
+ * (`ablo.<model>.claim.state`, `/v1/claims`) — the resolved, peer-readable
191
+ * view of one active or queued claim. The ONE canonical shape: the client
192
+ * (`Ablo.ts`) derives its `ModelClaim` from this, and the sync-server's two
193
+ * route copies adopt it once the engine dist is rebuilt.
194
+ *
195
+ * `expiresAt` is **epoch-ms** (a number) here — the same representation as the
196
+ * WS `WireClaim`, so there is ONE timestamp encoding across wire, SDK, HTTP,
197
+ * and errors (Stripe-style integer unix timestamps; no ISO string anywhere).
198
+ * `participantKind` ingests via {@link wireParticipantKindSchema} so a legacy
199
+ * `'human'` frame normalizes to `'user'`.
200
+ */
201
+ export const modelClaimSchema = z
202
+ .object({
203
+ id: z.string(),
204
+ actor: z.string(),
205
+ participantKind: wireParticipantKindSchema,
106
206
  action: z.string(),
107
- /** Server-stamped declaration time (epoch ms). */
108
- declaredAt: z.number(),
109
- /** Server-computed TTL deadline (epoch ms). Readers treat as advisory. */
207
+ description: z.string().optional(),
208
+ field: z.string().optional(),
209
+ status: z.enum(['active', 'queued']).optional(),
210
+ position: z.number().optional(),
110
211
  expiresAt: z.number(),
111
- status: intentStatusSchema.optional(),
112
- error: intentErrorSchema.optional(),
113
- });
212
+ target: modelTargetSchema,
213
+ })
214
+ .readonly();
114
215
  /**
115
- * `intent_begin` payload (client → server). The descriptive target + action,
216
+ * `claim_begin` payload (client → server). The descriptive target + action,
116
217
  * plus an optional duration hint and the opt-in fair-queue flag. The server
117
218
  * stamps the lifecycle/timestamp fields, so they are NOT part of the inbound
118
219
  * shape — this is exactly what the WS ingest validates.
119
220
  */
120
- export const intentBeginPayloadSchema = targetRefSchema.extend({
121
- intentId: z.string(),
221
+ export const claimBeginPayloadSchema = targetRefSchema.extend({
222
+ claimId: z.string(),
122
223
  action: z.string(),
123
224
  /** Hint for `expiresAt`; the server caps it. */
124
225
  estimatedMs: z.number().optional(),
125
226
  /**
126
227
  * Opt into the fair wait queue: when the target is already held, the server
127
- * enqueues this claim (FIFO) and replies `intent_queued` → later
128
- * `intent_granted`, instead of `intent_rejected`. Clients that set this MUST
228
+ * enqueues this claim (FIFO) and replies `claim_queued` → later
229
+ * `claim_granted`, instead of `claim_rejected`. Clients that set this MUST
129
230
  * handle the grant.
130
231
  */
131
232
  queue: z.boolean().optional(),
132
233
  });
133
234
  /**
134
- * `intent_abandon` payload (client → server). `entityType`/`entityId` are
135
- * carried so the server can DEQUEUE a still-*waiting* (not held) intent from
136
- * the FIFO line — the held-intent path needs only `intentId`. (The previous
235
+ * `claim_abandon` payload (client → server). `entityType`/`entityId` are
236
+ * carried so the server can DEQUEUE a still-*waiting* (not held) claim from
237
+ * the FIFO line — the held-claim path needs only `claimId`. (The previous
137
238
  * wire type omitted these two even though the handler reads them; the schema
138
239
  * documents what the code actually uses.)
139
240
  */
140
- export const intentAbandonPayloadSchema = z.object({
141
- intentId: z.string(),
241
+ export const claimAbandonPayloadSchema = z.object({
242
+ claimId: z.string(),
142
243
  entityType: z.string().optional(),
143
244
  entityId: z.string().optional(),
144
245
  });
145
246
  /**
146
- * `intent_reorder` payload (client → server). A privileged participant (e.g. a
247
+ * `claim_reorder` payload (client → server). A privileged participant (e.g. a
147
248
  * supervisor over its sub-agents) re-ranks the FIFO wait queue for an entity:
148
- * `order` lists waiters by `heldBy`+`intentId` in the desired priority. Waiters
249
+ * `order` lists waiters by `heldBy`+`claimId` in the desired priority. Waiters
149
250
  * not listed keep their relative order behind the listed ones. The server gates
150
- * who may call this; an unauthorized sender is dropped. Unlike `intent_abandon`
251
+ * who may call this; an unauthorized sender is dropped. Unlike `claim_abandon`
151
252
  * (acts on the caller's own entry), reorder acts on OTHER participants' queue
152
253
  * positions — hence the authorization gate.
153
254
  */
154
- export const intentReorderPayloadSchema = z.object({
255
+ export const claimReorderPayloadSchema = z.object({
155
256
  entityType: z.string(),
156
257
  entityId: z.string(),
157
- order: z.array(z.object({ heldBy: z.string(), intentId: z.string() })),
258
+ order: z.array(z.object({ heldBy: z.string(), claimId: z.string() })),
259
+ });
260
+ // ─────────────────────────────────────────────────────────────────────────
261
+ // Read interest — area-of-interest navigation (update_subscription)
262
+ // ─────────────────────────────────────────────────────────────────────────
263
+ /**
264
+ * `update_subscription` payload (client → server). Replaces the connection's
265
+ * connection-level read interest with the COMPLETE set of sync groups — the
266
+ * READ counterpart to a claim (no write-claim, no TTL). Each entry is a
267
+ * {@link syncGroupInputSchema} (`'default'` or a branded `kind:id`), so a
268
+ * malformed group is rejected at ingest instead of being silently indexed.
269
+ * This is untrusted client input, so the element type is strict.
270
+ */
271
+ export const updateSubscriptionPayloadSchema = z.object({
272
+ syncGroups: z.array(syncGroupInputSchema),
273
+ });
274
+ /**
275
+ * `subscription_ack` payload (server → client). Echoes the connection's
276
+ * effective read set after the update (unchanged on rejection — the update is
277
+ * atomic). `error` is present iff `success` is false (e.g. a scoped key
278
+ * requesting a group outside its grant). `syncGroups` is lenient
279
+ * (`z.string()`) here, not branded: it is the server's own echo for display,
280
+ * not untrusted input, and includes base anchors like `org:<id>`.
281
+ */
282
+ export const subscriptionAckPayloadSchema = z.object({
283
+ success: z.boolean(),
284
+ syncGroups: z.array(z.string()),
285
+ error: z.object({ code: z.string(), message: z.string() }).optional(),
158
286
  });
159
287
  // ─────────────────────────────────────────────────────────────────────────
160
288
  // Commit operation — carries the optimistic write-guard (Layer 3)
@@ -191,7 +319,7 @@ export const presenceActivitySchema = targetRefSchema.extend({
191
319
  });
192
320
  /**
193
321
  * Full `presence_update` frame as the server broadcasts it. The activity +
194
- * `activeIntents` are the observation surface for the other two layers —
322
+ * `activeClaims` are the observation surface for the other two layers —
195
323
  * rendered, never acted on as enforcement.
196
324
  */
197
325
  export const presenceUpdateFrameSchema = z.object({
@@ -204,6 +332,11 @@ export const presenceUpdateFrameSchema = z.object({
204
332
  customStatus: z.string().optional(),
205
333
  activity: presenceActivitySchema.optional(),
206
334
  isAgent: z.boolean().optional(),
207
- activeIntents: z.array(intentClaimSchema).optional(),
335
+ /**
336
+ * Server-stamped canonical kind. Additive — older servers omit it and
337
+ * readers fall back to `isAgent` (see {@link participantKindFromWire}).
338
+ */
339
+ participantKind: wireParticipantKindSchema.optional(),
340
+ activeClaims: z.array(wireClaimSchema).optional(),
208
341
  delegatedFrom: z.string().nullish(),
209
342
  });
@@ -23,6 +23,6 @@ export { computeFKDepthPriority, type InternalAbloOptions } from '../client/Ablo
23
23
  export type { SyncLogger, SyncObservabilityProvider, MutationExecutor, MutationDispatcher, SessionErrorDetector, OnlineStatusProvider, CommitResult, MutationOperation, } from '../interfaces/index.js';
24
24
  export { SyncWebSocket, type SyncDelta, type SyncWebSocketOptions, } from '../sync/SyncWebSocket.js';
25
25
  export { BootstrapHelper } from '../sync/BootstrapHelper.js';
26
- export { createIntentStream, type AttachableIntentStream, type IntentStreamConfig, } from '../sync/createIntentStream.js';
27
- export { awaitIntentGrant, type GrantTransport, } from '../sync/awaitIntentGrant.js';
26
+ export { createClaimStream, type AttachableClaimStream, type ClaimStreamConfig, } from '../sync/createClaimStream.js';
27
+ export { awaitClaimGrant, type GrantTransport, } from '../sync/awaitClaimGrant.js';
28
28
  export { LoadStrategy } from '../types/index.js';
@@ -30,13 +30,13 @@ export { computeFKDepthPriority } from '../client/Ablo.js';
30
30
  // multi-agent demo harnesses.
31
31
  export { SyncWebSocket, } from '../sync/SyncWebSocket.js';
32
32
  export { BootstrapHelper } from '../sync/BootstrapHelper.js';
33
- // Intent coordination primitives (the lower-level pieces behind the
33
+ // Claim coordination primitives (the lower-level pieces behind the
34
34
  // consumer-facing `ablo.<model>.claim`). The stream factory builds the
35
- // announce/await machinery on a SyncWebSocket; `awaitIntentGrant` is the
35
+ // announce/await machinery on a SyncWebSocket; `awaitClaimGrant` is the
36
36
  // fair-queue grant coordinator. Exposed on /core for framework-level
37
37
  // orchestration and e2e harnesses — NOT on the consumer `.` root.
38
- export { createIntentStream, } from '../sync/createIntentStream.js';
39
- export { awaitIntentGrant, } from '../sync/awaitIntentGrant.js';
38
+ export { createClaimStream, } from '../sync/createClaimStream.js';
39
+ export { awaitClaimGrant, } from '../sync/awaitClaimGrant.js';
40
40
  // Schema/model load strategy enum — referenced by model registration in
41
41
  // framework code.
42
42
  export { LoadStrategy } from '../types/index.js';
@@ -37,9 +37,9 @@ import { z } from 'zod';
37
37
  * code, a changed HTTP status, an envelope field. Emitted in `errors.json`
38
38
  * and on the `Ablo-Version` response header so a consumer can detect drift.
39
39
  */
40
- export declare const ERROR_CONTRACT_VERSION = "2026-06-02";
40
+ export declare const ERROR_CONTRACT_VERSION = "2026-06-13";
41
41
  /** Coarse grouping for metrics dashboards and docs sectioning. */
42
- export type ErrorCategory = 'auth' | 'permission' | 'capability' | 'claim' | 'conflict' | 'validation' | 'not_found' | 'tenant' | 'schema' | 'intent' | 'bootstrap' | 'transport' | 'rate_limit' | 'server' | 'client';
42
+ export type ErrorCategory = 'auth' | 'permission' | 'capability' | 'claim' | 'conflict' | 'validation' | 'not_found' | 'tenant' | 'schema' | 'claim' | 'bootstrap' | 'transport' | 'rate_limit' | 'server' | 'client';
43
43
  /**
44
44
  * The closed taxonomy of *how a failure recovers* — one rung above the raw
45
45
  * `code`. Where `code` says **what** went wrong, `RecoveryClass` says **what
@@ -151,8 +151,8 @@ export declare const ERROR_CODES: {
151
151
  readonly claim_conflict: ErrorCodeSpec;
152
152
  readonly claim_lost: ErrorCodeSpec;
153
153
  readonly entity_claimed: ErrorCodeSpec;
154
- readonly intent_conflict: ErrorCodeSpec;
155
154
  readonly malformed_claim: ErrorCodeSpec;
155
+ readonly malformed_subscription: ErrorCodeSpec;
156
156
  readonly model_claimed: ErrorCodeSpec;
157
157
  readonly model_claimed_timeout: ErrorCodeSpec;
158
158
  readonly model_claim_not_configured: ErrorCodeSpec;
@@ -207,11 +207,11 @@ export declare const ERROR_CODES: {
207
207
  readonly required_field_added: ErrorCodeSpec;
208
208
  readonly enum_value_removed: ErrorCodeSpec;
209
209
  readonly risky_cast: ErrorCodeSpec;
210
- readonly intent_lease_unavailable: ErrorCodeSpec;
211
- readonly intent_not_wired: ErrorCodeSpec;
212
- readonly intent_queued: ErrorCodeSpec;
213
- readonly intent_wait_aborted: ErrorCodeSpec;
214
- readonly intent_wait_poll_interval_required: ErrorCodeSpec;
210
+ readonly claim_lease_unavailable: ErrorCodeSpec;
211
+ readonly claim_not_wired: ErrorCodeSpec;
212
+ readonly claim_queued: ErrorCodeSpec;
213
+ readonly claim_wait_aborted: ErrorCodeSpec;
214
+ readonly claim_wait_poll_interval_required: ErrorCodeSpec;
215
215
  readonly grant_timeout: ErrorCodeSpec;
216
216
  readonly slide_intent_missing_deck_id: ErrorCodeSpec;
217
217
  readonly slide_intent_unknown_sibling: ErrorCodeSpec;
@@ -311,7 +311,7 @@ export declare const ERROR_CODES: {
311
311
  readonly upload_items_required: ErrorCodeSpec;
312
312
  readonly presigned_url_failed: ErrorCodeSpec;
313
313
  readonly task_id_required: ErrorCodeSpec;
314
- readonly intent_id_required: ErrorCodeSpec;
314
+ readonly claim_id_required: ErrorCodeSpec;
315
315
  readonly commit_operation_action_required: ErrorCodeSpec;
316
316
  readonly commit_operation_unsupported: ErrorCodeSpec;
317
317
  readonly usage_invalid: ErrorCodeSpec;
@@ -37,7 +37,7 @@ import { z } from 'zod';
37
37
  * code, a changed HTTP status, an envelope field. Emitted in `errors.json`
38
38
  * and on the `Ablo-Version` response header so a consumer can detect drift.
39
39
  */
40
- export const ERROR_CONTRACT_VERSION = '2026-06-02';
40
+ export const ERROR_CONTRACT_VERSION = '2026-06-13';
41
41
  /**
42
42
  * The closed taxonomy of *how a failure recovers* — one rung above the raw
43
43
  * `code`. Where `code` says **what** went wrong, `RecoveryClass` says **what
@@ -141,7 +141,7 @@ export const ERROR_CODES = {
141
141
  byo_role_unreadable: wire('permission', 403, false, 'The direct Postgres connector role could not be introspected.'),
142
142
  byo_tenant_tables_unforced_rls: wire('permission', 403, false, 'Tenant tables do not have RLS forced under the direct Postgres connector role.'),
143
143
  byo_host_not_allowed: wire('permission', 403, false, 'The direct Postgres connector host resolves to a private, loopback, or link-local address and cannot be used.'),
144
- // ── claim / intent conflict (409) ──────────────────────────────────
144
+ // ── claim / claim conflict (409) ──────────────────────────────────
145
145
  // Held-claim rejections are NOT queue-retryable (gRPC FAILED_PRECONDITION /
146
146
  // ABORTED semantics; Replicache/Zero SETTLE a rejected mutation — reject the
147
147
  // caller, roll back the optimistic effect — instead of resending it).
@@ -154,8 +154,8 @@ export const ERROR_CODES = {
154
154
  claim_conflict: wire('claim', 409, false, 'The target entity is claimed by another participant.'),
155
155
  claim_lost: wire('claim', 409, false, 'A previously held claim was lost before the write applied.'),
156
156
  entity_claimed: wire('claim', 409, false, 'The target entity is currently claimed; write was blocked.'),
157
- intent_conflict: wire('claim', 409, false, 'An intent on the target conflicts with an active intent (server-internal alias of claim_conflict).'),
158
157
  malformed_claim: wire('claim', 400, false, 'The claim payload was malformed.'),
158
+ malformed_subscription: wire('validation', 400, false, 'The update_subscription payload was malformed; expected { syncGroups: string[] }.'),
159
159
  model_claimed: wire('claim', 409, false, 'The model instance is claimed by another participant.'),
160
160
  model_claimed_timeout: wire('claim', 409, false, 'Timed out waiting for a model claim to clear.'),
161
161
  model_claim_not_configured: client('claim', 'Claiming was requested on a model that has no claim configuration.'),
@@ -164,7 +164,7 @@ export const ERROR_CODES = {
164
164
  idempotency_conflict: wire('conflict', 409, false, 'The same Idempotency-Key was reused with a different request body.'),
165
165
  idempotency_key_too_long: wire('validation', 400, false, 'The supplied Idempotency-Key exceeds the maximum length.'),
166
166
  // ── validation (400 / 422) ─────────────────────────────────────────
167
- write_options_invalid: client('validation', 'The write options (`idempotencyKey` / `label` / `wait` / `readAt` / `onStale` / `intent`) failed validation against the write-options schema.'),
167
+ write_options_invalid: client('validation', 'The write options (`idempotencyKey` / `label` / `wait` / `readAt` / `onStale` / `claim`) failed validation against the write-options schema.'),
168
168
  source_operation_id_required: client('validation', 'A data-source operation arrived without the entity `id` it targets.'),
169
169
  source_adapter_misconfigured: client('validation', 'The data-source ORM adapter could not map a schema model onto the backing client (missing delegate or model).'),
170
170
  source_event_invalid: client('validation', 'A data-source outbox event could not be built — the operation carries no entity id and none was supplied.'),
@@ -222,15 +222,15 @@ export const ERROR_CODES = {
222
222
  required_field_added: client('schema', 'Migration adds a new required field.'),
223
223
  enum_value_removed: client('schema', 'Migration removes an enum value (destructive classification).'),
224
224
  risky_cast: client('schema', 'Migration would perform a risky column type cast.'),
225
- // ── intent / lease (409 / transport) ───────────────────────────────
226
- intent_lease_unavailable: wire('intent', 503, true, 'The intent-lease coordination subsystem is unavailable; retry.'),
227
- intent_not_wired: client('intent', 'Intent support was used but is not wired in this runtime.'),
228
- intent_queued: wire('intent', 409, true, 'The intent was queued behind an active lease holder.'),
229
- intent_wait_aborted: wire('intent', 409, true, 'Waiting for the intent lease was aborted.'),
230
- intent_wait_poll_interval_required: client('intent', 'A poll interval is required when waiting on an intent.'),
231
- grant_timeout: wire('intent', 504, true, 'Timed out waiting for a capability grant.'),
232
- slide_intent_missing_deck_id: wire('intent', 400, false, 'A slide intent was missing its deck id.'),
233
- slide_intent_unknown_sibling: wire('intent', 400, false, 'A slide intent referenced an unknown sibling slide.'),
225
+ // ── claim / lease (409 / transport) ───────────────────────────────
226
+ claim_lease_unavailable: wire('claim', 503, true, 'The claim-lease coordination subsystem is unavailable; retry.'),
227
+ claim_not_wired: client('claim', 'Claim support was used but is not wired in this runtime.'),
228
+ claim_queued: wire('claim', 409, true, 'The claim was queued behind an active lease holder.'),
229
+ claim_wait_aborted: wire('claim', 409, true, 'Waiting for the claim lease was aborted.'),
230
+ claim_wait_poll_interval_required: client('claim', 'A poll interval is required when waiting on an claim.'),
231
+ grant_timeout: wire('claim', 504, true, 'Timed out waiting for a capability grant.'),
232
+ slide_intent_missing_deck_id: wire('claim', 400, false, 'A slide claim was missing its deck id.'),
233
+ slide_intent_unknown_sibling: wire('claim', 400, false, 'A slide claim referenced an unknown sibling slide.'),
234
234
  // ── bootstrap (transport) ──────────────────────────────────────────
235
235
  bootstrap_fetch_timeout: wire('bootstrap', 504, true, 'The bootstrap fetch timed out.'),
236
236
  bootstrap_offline: wire('bootstrap', 503, true, 'Bootstrap could not run because the client is offline.'),
@@ -333,7 +333,7 @@ export const ERROR_CODES = {
333
333
  upload_items_required: wire('validation', 400, false, 'The request must include a non-empty items array.'),
334
334
  presigned_url_failed: wire('server', 500, true, 'Failed to generate a presigned upload URL.'),
335
335
  task_id_required: wire('validation', 400, false, 'A task id is required for this request.'),
336
- intent_id_required: wire('validation', 400, false, 'An intent id is required for this request.'),
336
+ claim_id_required: wire('validation', 400, false, 'An claim id is required for this request.'),
337
337
  commit_operation_action_required: wire('validation', 400, false, 'A commit operation is missing its `action`.'),
338
338
  commit_operation_unsupported: wire('validation', 400, false, 'A commit operation used an unsupported `action`.'),
339
339
  usage_invalid: wire('validation', 400, false, 'The usage request was invalid.'),
@@ -348,7 +348,7 @@ export const ERROR_CODES = {
348
348
  parent_turn_foreign_agent: wire('permission', 403, false, 'The parent turn belongs to a different agent.'),
349
349
  turn_not_found: wire('not_found', 404, false, 'The referenced turn does not exist.'),
350
350
  turn_foreign_agent: wire('permission', 403, false, 'The turn belongs to a different agent.'),
351
- invalid_intent: wire('validation', 400, false, 'The intent request was invalid.'),
351
+ invalid_intent: wire('validation', 400, false, 'The claim request was invalid.'),
352
352
  schema_too_large: wire('validation', 413, false, 'The submitted schema exceeds the maximum size.'),
353
353
  invalid_schema: wire('validation', 400, false, 'The submitted schema could not be parsed.'),
354
354
  incompatible_change: wire('conflict', 409, false, 'The schema change is incompatible with the current schema.'),
package/dist/errors.d.ts CHANGED
@@ -19,6 +19,7 @@
19
19
  * Both work on every subclass.
20
20
  */
21
21
  import type { ErrorCode } from './errorCodes.js';
22
+ import { type WireClaimSummary, type ModelClaim, type ModelTarget, type ParticipantKind } from './coordination/schema.js';
22
23
  export type { ErrorCode, WireErrorCode, ErrorCategory, ErrorCodeSpec, RecoveryClass } from './errorCodes.js';
23
24
  export { ERROR_CODES, ERROR_CONTRACT_VERSION, errorCodeSpec, isRetryableCode, classifyRecovery, recoveryClassSchema, RECOVERY_CLASSES, } from './errorCodes.js';
24
25
  /** Common shape for all errors thrown by this SDK. */
@@ -155,6 +156,32 @@ export declare class AbloStaleContextError extends AbloError {
155
156
  }>;
156
157
  });
157
158
  }
159
+ export interface ClaimContext {
160
+ readonly id?: string;
161
+ readonly claimId?: string;
162
+ readonly actor?: string;
163
+ readonly participantKind?: ParticipantKind;
164
+ readonly action?: string;
165
+ readonly description?: string;
166
+ readonly field?: string;
167
+ readonly status?: string;
168
+ readonly position?: number;
169
+ /** Epoch-ms the claim expires. One timestamp encoding everywhere. */
170
+ readonly expiresAt?: number;
171
+ readonly declaredAt?: number;
172
+ readonly entityType?: string;
173
+ readonly entityId?: string;
174
+ readonly target?: Partial<ModelTarget>;
175
+ readonly meta?: Record<string, unknown>;
176
+ }
177
+ export type ClaimErrorClaim = WireClaimSummary | ClaimContext;
178
+ export declare function formatClaimedErrorMessage(args: {
179
+ readonly targetLabel: string;
180
+ readonly heldBy?: string;
181
+ readonly claim?: ClaimErrorClaim;
182
+ readonly policyReason?: string;
183
+ readonly fallback?: string;
184
+ }): string;
158
185
  /**
159
186
  * The target entity is currently claimed by another participant and the caller
160
187
  * asked the SDK not to read/write through that claim.
@@ -164,15 +191,36 @@ export declare class AbloStaleContextError extends AbloError {
164
191
  */
165
192
  export declare class AbloClaimedError extends AbloError {
166
193
  readonly type: "AbloClaimedError";
167
- readonly claims?: ReadonlyArray<unknown>;
194
+ readonly claims?: ReadonlyArray<ClaimErrorClaim>;
168
195
  constructor(message: string, options?: {
169
196
  code?: ErrorCode;
170
197
  httpStatus?: number;
171
198
  requestId?: string;
172
199
  cause?: unknown;
173
- claims?: ReadonlyArray<unknown>;
200
+ claims?: ReadonlyArray<ClaimErrorClaim>;
174
201
  });
175
202
  }
203
+ /**
204
+ * The `/`-joined human label for a claim target — `model/id/field`, dropping
205
+ * absent parts, falling back to `'target'`. The one place this join lived in
206
+ * three copies (client `Ablo`, HTTP `ApiClient`, `awaitClaimGrant`).
207
+ */
208
+ export declare function claimTargetLabel(target: {
209
+ readonly model?: string;
210
+ readonly id?: string;
211
+ readonly field?: string;
212
+ }): string;
213
+ /**
214
+ * Build the {@link AbloClaimedError} for a contended `ablo.<model>` write — the
215
+ * single factory shared by the realtime client (`Ablo`) and the HTTP client
216
+ * (`ApiClient`), which carried byte-identical copies. The first claim is the
217
+ * holder whose metadata shapes the message.
218
+ */
219
+ export declare function claimedError(target: {
220
+ readonly model?: string;
221
+ readonly id?: string;
222
+ readonly field?: string;
223
+ }, claims: readonly ModelClaim[], code: 'model_claimed' | 'model_claimed_timeout' | 'queue_too_deep'): AbloClaimedError;
176
224
  /**
177
225
  * Structured description of the capability an agent would need to
178
226
  * satisfy a denied request. Mirrors the x402 `paymentRequirements`
@@ -304,6 +352,7 @@ export declare function errorFromWire(message: string, opts?: {
304
352
  httpStatus?: number;
305
353
  requestId?: string;
306
354
  requiredCapability?: RequiredCapability;
355
+ claims?: ReadonlyArray<ClaimErrorClaim>;
307
356
  }): AbloError;
308
357
  /**
309
358
  * Translate an HTTP response into the appropriate typed error.