@abloatai/transaction 0.46.0 → 0.48.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 (91) hide show
  1. package/dist/ai-sdk/modelTools.d.ts.map +1 -1
  2. package/dist/ai-sdk/modelTools.js +1 -2
  3. package/dist/ai-sdk/modelTools.js.map +1 -1
  4. package/dist/ai-sdk/updateTool.d.ts.map +1 -1
  5. package/dist/ai-sdk/updateTool.js +0 -1
  6. package/dist/ai-sdk/updateTool.js.map +1 -1
  7. package/dist/auth/capability.d.ts +8 -0
  8. package/dist/auth/capability.d.ts.map +1 -1
  9. package/dist/auth/capability.js +2 -0
  10. package/dist/auth/capability.js.map +1 -1
  11. package/dist/auth/capabilityLifecycle.d.ts +4 -0
  12. package/dist/auth/capabilityLifecycle.d.ts.map +1 -1
  13. package/dist/auth/deliveryPartition.d.ts +14 -0
  14. package/dist/auth/deliveryPartition.d.ts.map +1 -0
  15. package/dist/auth/deliveryPartition.js +16 -0
  16. package/dist/auth/deliveryPartition.js.map +1 -0
  17. package/dist/auth/identity.d.ts +2 -0
  18. package/dist/auth/identity.d.ts.map +1 -1
  19. package/dist/auth/identity.js +3 -0
  20. package/dist/auth/identity.js.map +1 -1
  21. package/dist/auth/schemas.d.ts +8 -0
  22. package/dist/auth/schemas.d.ts.map +1 -1
  23. package/dist/auth/schemas.js +3 -0
  24. package/dist/auth/schemas.js.map +1 -1
  25. package/dist/auth/sessionMint.d.ts.map +1 -1
  26. package/dist/auth/sessionMint.js +1 -0
  27. package/dist/auth/sessionMint.js.map +1 -1
  28. package/dist/branches.d.ts +61 -12
  29. package/dist/branches.d.ts.map +1 -1
  30. package/dist/branches.js +25 -3
  31. package/dist/branches.js.map +1 -1
  32. package/dist/errorCodes.d.ts +18 -2
  33. package/dist/errorCodes.d.ts.map +1 -1
  34. package/dist/errorCodes.js +28 -12
  35. package/dist/errorCodes.js.map +1 -1
  36. package/dist/footprint.d.ts +10 -22
  37. package/dist/footprint.d.ts.map +1 -1
  38. package/dist/footprint.js +14 -24
  39. package/dist/footprint.js.map +1 -1
  40. package/dist/pricing.d.ts +21 -2
  41. package/dist/pricing.d.ts.map +1 -1
  42. package/dist/pricing.js +32 -8
  43. package/dist/pricing.js.map +1 -1
  44. package/dist/resources/httpResources.d.ts +0 -1
  45. package/dist/resources/httpResources.d.ts.map +1 -1
  46. package/dist/resources/modelOperations.d.ts +13 -3
  47. package/dist/resources/modelOperations.d.ts.map +1 -1
  48. package/dist/schema/openapi.js +4 -4
  49. package/dist/schema/openapi.js.map +1 -1
  50. package/dist/source/index.d.ts +1 -1
  51. package/dist/source/index.d.ts.map +1 -1
  52. package/dist/source/migrations.d.ts +1 -1
  53. package/dist/source/migrations.d.ts.map +1 -1
  54. package/dist/source/migrations.js +18 -7
  55. package/dist/source/migrations.js.map +1 -1
  56. package/dist/transport/httpTransport.d.ts.map +1 -1
  57. package/dist/transport/httpTransport.js +5 -10
  58. package/dist/transport/httpTransport.js.map +1 -1
  59. package/dist/transport/wsTransport.d.ts +8 -0
  60. package/dist/transport/wsTransport.d.ts.map +1 -1
  61. package/dist/transport/wsTransport.js +8 -0
  62. package/dist/transport/wsTransport.js.map +1 -1
  63. package/dist/wire/dataSourceResponses.d.ts +17 -1
  64. package/dist/wire/dataSourceResponses.d.ts.map +1 -1
  65. package/dist/wire/dataSourceResponses.js +18 -0
  66. package/dist/wire/dataSourceResponses.js.map +1 -1
  67. package/dist/wire/index.d.ts +2 -2
  68. package/dist/wire/index.d.ts.map +1 -1
  69. package/dist/wire/index.js +1 -1
  70. package/dist/wire/index.js.map +1 -1
  71. package/package.json +1 -1
  72. package/src/ai-sdk/modelTools.ts +1 -2
  73. package/src/ai-sdk/updateTool.ts +0 -1
  74. package/src/auth/capability.ts +2 -0
  75. package/src/auth/deliveryPartition.ts +18 -0
  76. package/src/auth/identity.ts +5 -0
  77. package/src/auth/schemas.ts +3 -0
  78. package/src/auth/sessionMint.ts +1 -0
  79. package/src/branches.ts +27 -3
  80. package/src/errorCodes.ts +113 -17
  81. package/src/footprint.ts +18 -27
  82. package/src/pricing.ts +36 -8
  83. package/src/resources/httpResources.ts +0 -1
  84. package/src/resources/modelOperations.ts +14 -3
  85. package/src/schema/openapi.ts +4 -4
  86. package/src/source/index.ts +1 -1
  87. package/src/source/migrations.ts +21 -7
  88. package/src/transport/httpTransport.ts +5 -10
  89. package/src/transport/wsTransport.ts +18 -0
  90. package/src/wire/dataSourceResponses.ts +20 -0
  91. package/src/wire/index.ts +2 -0
@@ -78,7 +78,7 @@ export { projectResponseSchema, projectListResponseSchema, provisionedKeySchema,
78
78
  // The datasource routes' responses — the `ablo connect` surface: register,
79
79
  // validate, locate, list, deregister. What the server, the CLI, and the
80
80
  // dashboard agree on.
81
- export { READINESS_ITEMS, READINESS_ADVISORY_ITEMS, isReadinessItem, readinessFailureSchema, readinessAdvisorySchema, datasourceSummarySchema, datasourceListResponseSchema, datasourceValidationResponseSchema, datasourceLocationResponseSchema, datasourceDisconnectedResponseSchema, } from './dataSourceResponses.js';
81
+ export { READINESS_ITEMS, READINESS_ADVISORY_ITEMS, isReadinessItem, readinessFailureSchema, readinessAdvisorySchema, datasourceSummarySchema, datasourceListResponseSchema, datasourceValidationResponseSchema, datasourceLocationResponseSchema, datasourceResnapshotResponseSchema, datasourceDisconnectedResponseSchema, } from './dataSourceResponses.js';
82
82
  // The inbound socket surface: every frame the server can send, and how each
83
83
  // one's payload is validated.
84
84
  export { WS_INBOUND_FRAMES, wsInboundEnvelopeSchema, isKnownInboundFrame, isSchemaValidatedFrame, } from './inboundFrames.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/wire/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/wire/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EACL,mBAAmB,EACnB,aAAa,EACb,aAAa,EACb,6BAA6B,GAC9B,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGrE,8EAA8E;AAC9E,8BAA8B;AAC9B,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEnD,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,cAAc,GACf,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAG7D,6EAA6E;AAC7E,2EAA2E;AAC3E,2EAA2E;AAC3E,cAAc;AACd,OAAO,EACL,qBAAqB,EACrB,yBAAyB,EACzB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,aAAa,CAAC;AAErB,2EAA2E;AAC3E,8EAA8E;AAC9E,gFAAgF;AAChF,OAAO,EACL,gBAAgB,EAChB,8BAA8B,EAC9B,wBAAwB,EACxB,2BAA2B,EAC3B,yBAAyB,EACzB,uBAAuB,EACvB,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,sBAAsB,CAAC;AAY9B,6EAA6E;AAC7E,8EAA8E;AAC9E,gDAAgD;AAChD,OAAO,EACL,gCAAgC,EAChC,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,mBAAmB,EACnB,2BAA2B,EAC3B,2BAA2B,EAC3B,2BAA2B,EAC3B,eAAe,EACf,0BAA0B;AAC1B,8EAA8E;AAC9E,oEAAoE;AACpE,2BAA2B,EAC3B,yBAAyB,EACzB,mBAAmB,GACpB,MAAM,aAAa,CAAC;AAiBrB,kFAAkF;AAClF,0EAA0E;AAC1E,kFAAkF;AAClF,wDAAwD;AACxD,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,EACpB,uBAAuB,EACvB,qBAAqB,EACrB,qBAAqB,EACrB,WAAW,GACZ,MAAM,YAAY,CAAC;AAapB,6EAA6E;AAC7E,OAAO,EACL,SAAS,EACT,uBAAuB,EACvB,mBAAmB,EACnB,mBAAmB,EACnB,kBAAkB,EAClB,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,aAAa,EACb,kBAAkB,EAClB,aAAa,EACb,WAAW,EACX,sBAAsB;AACtB,4EAA4E;AAC5E,4EAA4E;AAC5E,0CAA0C;AAC1C,aAAa,GACd,MAAM,cAAc,CAAC;AAGtB,8EAA8E;AAC9E,4EAA4E;AAC5E,4EAA4E;AAC5E,wDAAwD;AACxD,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,4BAA4B,EAC5B,mBAAmB,GACpB,MAAM,eAAe,CAAC;AAEvB,+EAA+E;AAC/E,+EAA+E;AAC/E,6EAA6E;AAC7E,6DAA6D;AAC7D,OAAO;AACL,qEAAqE;AACrE,0EAA0E;AAC1E,yEAAyE;AACzE,oBAAoB,EACpB,cAAc,EACd,UAAU,EACV,iBAAiB,EACjB,kBAAkB,EAClB,2BAA2B,EAC3B,eAAe,EACf,oBAAoB,EACpB,gBAAgB,EAChB,2BAA2B,EAC3B,yBAAyB,EACzB,0BAA0B,EAC1B,yBAAyB,EACzB,8BAA8B,EAC9B,uBAAuB,EACvB,yBAAyB,EACzB,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,aAAa,CAAC;AAmBrB,yEAAyE;AACzE,yDAAyD;AACzD,OAAO,EACL,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,qBAAqB,CAAC;AAG7B,gFAAgF;AAChF,wEAAwE;AACxE,OAAO,EACL,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,iBAAiB,CAAC;AAQzB,6EAA6E;AAC7E,OAAO,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAGjE,4EAA4E;AAC5E,qEAAqE;AACrE,OAAO,EACL,qBAAqB,EACrB,yBAAyB,EACzB,oBAAoB,EACpB,0BAA0B,EAC1B,sBAAsB,EACtB,yBAAyB,EACzB,wBAAwB,EACxB,WAAW,EACX,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,yBAAyB,EACzB,gBAAgB,EAChB,oBAAoB,EACpB,0BAA0B,EAC1B,gBAAgB,EAChB,4BAA4B,EAC5B,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,uBAAuB,CAAC;AAqB/B,2EAA2E;AAC3E,wEAAwE;AACxE,sBAAsB;AACtB,OAAO,EACL,eAAe,EACf,wBAAwB,EACxB,eAAe,EACf,sBAAsB,EACtB,uBAAuB,EACvB,uBAAuB,EACvB,4BAA4B,EAC5B,kCAAkC,EAClC,gCAAgC,EAChC,kCAAkC,EAClC,oCAAoC,GACrC,MAAM,0BAA0B,CAAC;AAclC,4EAA4E;AAC5E,8BAA8B;AAC9B,OAAO,EACL,iBAAiB,EACjB,uBAAuB,EACvB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,oBAAoB,CAAC;AAS5B,+EAA+E;AAC/E,qDAAqD;AACrD,OAAO,EACL,sBAAsB,EACtB,yBAAyB,EACzB,uBAAuB,EACvB,4BAA4B,GAC7B,MAAM,WAAW,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abloatai/transaction",
3
- "version": "0.46.0",
3
+ "version": "0.48.0",
4
4
  "description": "The headless Ablo transaction client and canonical contracts for reads, commits, settlement, claims, and durable observation.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -100,7 +100,6 @@ export function createTool<TInput, T, CreateInput>(
100
100
  const row = await model.create({
101
101
  data: options.data(input),
102
102
  ...(id !== undefined ? { id } : {}),
103
- wait: 'confirmed',
104
103
  });
105
104
  return { status: 'created', row };
106
105
  },
@@ -156,7 +155,7 @@ export function deleteTool<TInput, T, Fields = T>(
156
155
  };
157
156
  }
158
157
  try {
159
- await model.delete({ id, claim, wait: 'confirmed' });
158
+ await model.delete({ id, claim });
160
159
  return { status: 'deleted', id };
161
160
  } finally {
162
161
  await claim.release();
@@ -184,7 +184,6 @@ export function updateTool<
184
184
  id,
185
185
  data: options.apply(current, input),
186
186
  claim,
187
- wait: 'confirmed',
188
187
  });
189
188
  return { status: 'written', row };
190
189
  } finally {
@@ -27,6 +27,7 @@ import { z } from 'zod';
27
27
  import { participantKindSchema } from '../coordination/schema.js';
28
28
  import { syncGroupInputSchema } from '../schema/roles.js';
29
29
  import { authTokenSchema } from './token.js';
30
+ import { deliveryPartitionRouteSchema } from './deliveryPartition.js';
30
31
 
31
32
  /**
32
33
  * The verbs a grant can name — the whole vocabulary, in one place. Every other
@@ -253,6 +254,7 @@ export const capabilityScopeSchema = z.object({
253
254
  operations: z.array(grantedOperationSchema),
254
255
  participantKind: participantKindSchema,
255
256
  participantId: z.string().min(1),
257
+ deliveryPartition: deliveryPartitionRouteSchema.nullable().default(null),
256
258
  });
257
259
  export type CapabilityScope = z.infer<typeof capabilityScopeSchema>;
258
260
 
@@ -0,0 +1,18 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * Opaque gateway-routing coordinate resolved by the server.
5
+ *
6
+ * Clients echo this on the WebSocket upgrade; gateways still authenticate the
7
+ * credential and recompute ownership, so it is routing metadata rather than an
8
+ * authorization claim.
9
+ */
10
+ export const deliveryPartitionRouteSchema = z.object({
11
+ index: z.number().int().nonnegative(),
12
+ count: z.number().int().min(2),
13
+ }).refine((route) => route.index < route.count, {
14
+ message: 'delivery partition index must be less than count',
15
+ path: ['index'],
16
+ });
17
+
18
+ export type DeliveryPartitionRoute = z.infer<typeof deliveryPartitionRouteSchema>;
@@ -33,6 +33,7 @@ import type { Logger } from '../logger.js';
33
33
  import type { AuthCredentialSource } from '../auth/credentialSource.js';
34
34
  import type { CredentialProvider } from './credentialResult.js';
35
35
  import { resolveApiKeyValue, resolveBootstrapBaseUrl } from './apiKey.js';
36
+ import type { DeliveryPartitionRoute } from './deliveryPartition.js';
36
37
 
37
38
  export interface IdentityResolveInput {
38
39
  readonly options: {
@@ -69,6 +70,7 @@ export interface ResolvedIdentity {
69
70
  readonly capabilityToken: string | undefined;
70
71
  readonly syncGroups: readonly string[] | undefined;
71
72
  readonly participantKind: ParticipantKind;
73
+ readonly deliveryPartition: DeliveryPartitionRoute | null;
72
74
  /** Set only on the hosted-cloud path; the caller keeps it to stop refreshes on shutdown. */
73
75
  readonly refreshScheduler: RefreshScheduler | null;
74
76
  }
@@ -192,6 +194,7 @@ export async function resolveParticipantIdentity(
192
194
  capabilityToken: cred.getBearer,
193
195
  syncGroups: options.syncGroups,
194
196
  participantKind: kind,
197
+ deliveryPartition: null,
195
198
  refreshScheduler: null,
196
199
  };
197
200
  }
@@ -242,6 +245,7 @@ async function resolveViaIdentity(
242
245
  capabilityToken: bearer,
243
246
  syncGroups: mergedSyncGroups,
244
247
  participantKind: identity.participantKind,
248
+ deliveryPartition: identity.deliveryPartition,
245
249
  refreshScheduler: null,
246
250
  };
247
251
  }
@@ -325,6 +329,7 @@ async function resolveHosted(input: HostedInput): Promise<ResolvedIdentity> {
325
329
  capabilityToken: exchange.token,
326
330
  syncGroups: exchange.scope.syncGroups,
327
331
  participantKind: input.kind,
332
+ deliveryPartition: exchange.scope.deliveryPartition,
328
333
  refreshScheduler,
329
334
  };
330
335
  }
@@ -7,6 +7,7 @@ import {
7
7
  } from './capability.js';
8
8
  import { AbloAuthenticationError } from '../errors.js';
9
9
  import { authTokenSchema } from './token.js';
10
+ import { deliveryPartitionRouteSchema } from './deliveryPartition.js';
10
11
 
11
12
  // Not a second enum. The auth responses carry the same participant vocabulary
12
13
  // the coordination plane parses, so they validate against the same schema — a
@@ -24,6 +25,7 @@ export const IdentityResolveResponseSchema = z.object({
24
25
  branchId: z.string().min(1).nullable().default(null),
25
26
  branchRoot: z.boolean().default(false),
26
27
  syncGroups: z.array(z.string()),
28
+ deliveryPartition: deliveryPartitionRouteSchema.nullable().default(null),
27
29
  userMeta: z.record(z.string(), z.unknown()),
28
30
  });
29
31
 
@@ -46,6 +48,7 @@ export const EphemeralKeyResponseSchema = z.object({
46
48
  branchId: z.string().min(1).nullable().default(null),
47
49
  branchRoot: z.boolean().default(false),
48
50
  syncGroups: z.array(z.string()),
51
+ deliveryPartition: deliveryPartitionRouteSchema.nullable().default(null),
49
52
  /** Effective operation grant stored on the credential. Empty ONLY for a
50
53
  * control-plane-only session, which grants no data operations by design. */
51
54
  operations: z.array(grantedOperationSchema),
@@ -98,6 +98,7 @@ export async function mintSession<S extends SchemaRecord>(
98
98
  operations: res.operations,
99
99
  participantKind: 'user',
100
100
  participantId: res.participantId,
101
+ deliveryPartition: res.deliveryPartition,
101
102
  },
102
103
  userMeta: params.userMeta ?? { id: res.participantId },
103
104
  };
package/src/branches.ts CHANGED
@@ -87,6 +87,28 @@ export const branchParentCompatibilitySchema = z.enum([
87
87
  ]);
88
88
  export type BranchParentCompatibility = z.infer<typeof branchParentCompatibilitySchema>;
89
89
 
90
+ /**
91
+ * The one ownership/storage vocabulary for durable branch data.
92
+ * Missing configuration is explicit (`unbound`), never inferred as hosted.
93
+ */
94
+ export const branchStorageSchema = z.discriminatedUnion('kind', [
95
+ z.object({ kind: z.literal('unbound') }).strict(),
96
+ z.object({
97
+ kind: z.literal('customer'),
98
+ transport: z.enum(['direct', 'endpoint']),
99
+ status: z.enum(['unverified', 'active', 'rejected']),
100
+ }).strict(),
101
+ z.object({
102
+ kind: z.literal('internal'),
103
+ implementation: z.enum(['log', 'tables']),
104
+ }).strict(),
105
+ z.object({
106
+ kind: z.literal('blocked'),
107
+ reason: z.literal('orphaned_external_marker'),
108
+ }).strict(),
109
+ ]);
110
+ export type BranchStorage = z.infer<typeof branchStorageSchema>;
111
+
90
112
  export const branchStatusSchemaSummarySchema = z
91
113
  .object({
92
114
  active: z.boolean(),
@@ -101,8 +123,8 @@ export const branchStatusSchemaSummarySchema = z
101
123
 
102
124
  export const branchStatusDataSourceSchema = z
103
125
  .object({
104
- kind: z.enum(['hosted', 'direct', 'endpoint']),
105
- status: z.enum(['unverified', 'active', 'rejected']).nullable(),
126
+ connection: z.enum(['direct', 'endpoint']),
127
+ status: z.enum(['unverified', 'active', 'rejected']),
106
128
  host: z.string().nullable(),
107
129
  database: z.string().nullable(),
108
130
  cursor: z.string().nullable(),
@@ -111,7 +133,8 @@ export const branchStatusDataSourceSchema = z
111
133
  last_success_at: z.string().nullable(),
112
134
  last_error: z.string().nullable(),
113
135
  })
114
- .strict();
136
+ .strict()
137
+ .nullable();
115
138
 
116
139
  export const branchStatusBlockerSchema = z
117
140
  .object({
@@ -127,6 +150,7 @@ export const branchStatusResponseSchema = z
127
150
  branch: branchResponseSchema,
128
151
  ready: z.boolean(),
129
152
  schema: branchStatusSchemaSummarySchema,
153
+ storage: branchStorageSchema,
130
154
  data_source: branchStatusDataSourceSchema,
131
155
  blockers: z.array(branchStatusBlockerSchema).readonly(),
132
156
  })
package/src/errorCodes.ts CHANGED
@@ -39,7 +39,7 @@ import { z } from 'zod';
39
39
  * error documentation and returned on the `Ablo-Version` response header, so a
40
40
  * consumer can detect when its expected contract has drifted from the server's.
41
41
  */
42
- export const ERROR_CONTRACT_VERSION = '2026-07-23';
42
+ export const ERROR_CONTRACT_VERSION = '2026-08-03';
43
43
 
44
44
  /** A coarse grouping of error codes, used to organize metrics and documentation. */
45
45
  export type ErrorCategory =
@@ -319,6 +319,18 @@ export const ERROR_CODES = {
319
319
  false,
320
320
  'This deployment does not accept direct connection-string data sources. Register a signed Data Source endpoint instead.'
321
321
  ),
322
+ source_connector_localhost_required: wire(
323
+ 'validation',
324
+ 400,
325
+ false,
326
+ 'A connector-only development Data Source must use a localhost descriptor. Use `ablo dev --local`; use the ordinary signed HTTPS endpoint registration for a deployed handler.'
327
+ ),
328
+ source_connector_unauthenticated: wire(
329
+ 'auth',
330
+ 401,
331
+ false,
332
+ 'The localhost Data Source connector could not authenticate. Rerun `ablo dev --local` so it mints and uses a fresh branch-bound secret key.'
333
+ ),
322
334
 
323
335
  // ── permission / capability (403) ──────────────────────────────────
324
336
  capability_scope_denied: wire(
@@ -339,12 +351,6 @@ export const ERROR_CODES = {
339
351
  false,
340
352
  'This capability cannot be used — it is unknown, revoked, or expired. Request a fresh grant.'
341
353
  ),
342
- test_database_not_registered: wire(
343
- 'permission',
344
- 403,
345
- false,
346
- 'Test mode requires a registered dev database for this org — run `npx ablo init` to register one for your test key.'
347
- ),
348
354
  tenant_routing_failed: wire(
349
355
  'server',
350
356
  500,
@@ -373,7 +379,7 @@ export const ERROR_CODES = {
373
379
  'permission',
374
380
  403,
375
381
  false,
376
- "The database host resolves to a private, loopback, or link-local address, which Ablo's servers will not connect to. Use a publicly resolvable host."
382
+ "The database host resolves to a private, loopback, or link-local address, which Ablo's servers will not connect to directly. Use a publicly resolvable direct endpoint, private networking, or `ablo dev --local` with a signed Data Source reverse channel."
377
383
  ),
378
384
  connected_database_unreachable: wire(
379
385
  'tenant',
@@ -699,6 +705,18 @@ export const ERROR_CODES = {
699
705
  false,
700
706
  'The participant is not allowed to execute this mutator.'
701
707
  ),
708
+ source_connector_requires_secret_key: wire(
709
+ 'permission',
710
+ 403,
711
+ false,
712
+ 'A Data Source reverse channel must authenticate with the branch-bound secret (`sk_`) key created by `ablo dev`; browser, ephemeral, restricted, and management keys cannot serve a database.'
713
+ ),
714
+ source_connector_production_not_enabled: wire(
715
+ 'permission',
716
+ 403,
717
+ false,
718
+ 'This production Data Source has not opted into reverse-channel transport. `ablo dev --local` is for child branches; use a direct production endpoint or explicitly enable the supported production reverse-channel route.'
719
+ ),
702
720
 
703
721
  // ── not found (404) ────────────────────────────────────────────────
704
722
  entity_not_found: wire(
@@ -723,7 +741,13 @@ export const ERROR_CODES = {
723
741
  'not_found',
724
742
  404,
725
743
  false,
726
- 'No database is connected to this plane yet, so there is nothing to check. Connect one with `ablo connect apply`, then run the check again.'
744
+ 'This branch is not connected to your database yet. Run `ablo connect` for a cloud-reachable direct Postgres endpoint, or `ablo dev --local` to register and serve a localhost Data Source, then retry.'
745
+ ),
746
+ source_connector_no_source_registered: wire(
747
+ 'not_found',
748
+ 404,
749
+ false,
750
+ 'The branch has no endpoint Data Source for this connector to serve. Run `ablo dev --local` with the current CLI, which registers the connector-only source before opening the socket.'
727
751
  ),
728
752
  task_id_missing: wire(
729
753
  'server',
@@ -978,7 +1002,55 @@ export const ERROR_CODES = {
978
1002
  'transport',
979
1003
  503,
980
1004
  true,
981
- 'A network error occurred while talking to the data source. Check connectivity and retry.'
1005
+ 'A network error occurred while talking to the data source. For localhost mode, keep `ablo dev --local` running and check its connector output; for a direct source, check database connectivity and retry.'
1006
+ ),
1007
+ source_request_failed: wire(
1008
+ 'transport',
1009
+ 502,
1010
+ true,
1011
+ 'The signed Data Source returned a failure without a more specific code. Inspect the `ablo dev --local` process or deployed endpoint logs, then retry with the same idempotency key.'
1012
+ ),
1013
+ source_connector_not_attached: wire(
1014
+ 'transport',
1015
+ 503,
1016
+ true,
1017
+ 'This branch uses localhost connector-only storage, but no connector is attached. Start or restart `ablo dev --local` and keep that process running.'
1018
+ ),
1019
+ source_connector_timeout: wire(
1020
+ 'transport',
1021
+ 504,
1022
+ true,
1023
+ 'The local Data Source handler did not answer before the connector deadline. Check the local Postgres connection and handler logs; the same idempotent request may be retried.'
1024
+ ),
1025
+ source_connector_send_failed: wire(
1026
+ 'transport',
1027
+ 502,
1028
+ true,
1029
+ 'The connector socket closed while Ablo was forwarding a signed Data Source request. Keep `ablo dev --local` running; Ablo retries after the connector reconnects.'
1030
+ ),
1031
+ source_connector_disconnected: wire(
1032
+ 'transport',
1033
+ 503,
1034
+ true,
1035
+ 'The localhost Data Source connector disconnected with a request in flight. The connector reconnects automatically; retry after it reports ready.'
1036
+ ),
1037
+ source_connector_superseded: wire(
1038
+ 'transport',
1039
+ 503,
1040
+ true,
1041
+ 'A newer localhost Data Source connector replaced this one. Stop duplicate `ablo dev --local` processes and use the newest process; in-flight requests are safe to retry.'
1042
+ ),
1043
+ source_connector_shutdown: wire(
1044
+ 'transport',
1045
+ 503,
1046
+ true,
1047
+ 'The Ablo service shut down the connector while a request was in flight. The connector reconnects automatically after the service returns.'
1048
+ ),
1049
+ source_connector_protocol_error: wire(
1050
+ 'transport',
1051
+ 400,
1052
+ false,
1053
+ 'The CLI and Ablo service disagreed on the Data Source connector frame protocol, or a malformed frame arrived. Upgrade the Ablo CLI and SDK together, then restart `ablo dev --local`.'
982
1054
  ),
983
1055
  source_unreachable: wire(
984
1056
  'transport',
@@ -990,7 +1062,19 @@ export const ERROR_CODES = {
990
1062
  'transport',
991
1063
  503,
992
1064
  false,
993
- "The plane's data source is not in a verified, resolvable state. Fix its registration, secret, or configuration; Ablo will not fall through to hosted storage."
1065
+ "This branch's database connection is not ready. Check its credentials and configuration; Ablo will not send reads or writes anywhere else."
1066
+ ),
1067
+ source_connector_handler_error: wire(
1068
+ 'server',
1069
+ 500,
1070
+ true,
1071
+ 'The local signed Data Source handler threw while processing a request. Read the `ablo dev --local` error immediately above it, fix the database or adapter failure, and retry.'
1072
+ ),
1073
+ source_connector_internal_error: wire(
1074
+ 'server',
1075
+ 500,
1076
+ true,
1077
+ 'Ablo could not establish the Data Source reverse channel because of an internal service failure. Retry; if it persists, report the request id.'
994
1078
  ),
995
1079
  identity_network_error: wire(
996
1080
  'transport',
@@ -1046,6 +1130,12 @@ export const ERROR_CODES = {
1046
1130
  true,
1047
1131
  'The server is at connection capacity. Retry shortly — transient and not specific to your credentials.'
1048
1132
  ),
1133
+ delivery_partition_mismatch: wire(
1134
+ 'transport',
1135
+ 503,
1136
+ true,
1137
+ 'The connection reached a gateway that does not own its delivery partition. Retry through the cell router.'
1138
+ ),
1049
1139
  fetch_unavailable: client(
1050
1140
  'transport',
1051
1141
  'This environment provides no `fetch` implementation, so HTTP requests cannot be made. Run on a platform with `fetch` (Node 18+, modern browsers) or supply a polyfill.'
@@ -1134,7 +1224,7 @@ export const ERROR_CODES = {
1134
1224
  ),
1135
1225
  db_identity_mismatch: client(
1136
1226
  'client',
1137
- 'The local database belongs to a different authenticated identity or data plane.'
1227
+ 'The local database is connected to a different organization, project, or branch.'
1138
1228
  ),
1139
1229
  db_cleanup_failed: client(
1140
1230
  'client',
@@ -1240,6 +1330,12 @@ export const ERROR_CODES = {
1240
1330
  false,
1241
1331
  "The request targeted a project the caller's key is not scoped to."
1242
1332
  ),
1333
+ branch_scope_denied: wire(
1334
+ 'permission',
1335
+ 403,
1336
+ false,
1337
+ "The request targeted a branch the caller's key is not scoped to."
1338
+ ),
1243
1339
  project_slug_taken: wire(
1244
1340
  'validation',
1245
1341
  409,
@@ -1294,19 +1390,19 @@ export const ERROR_CODES = {
1294
1390
  'tenant',
1295
1391
  400,
1296
1392
  false,
1297
- "This model is scoped by its connected data source (`policy: { by: 'source' }`), so its tenant is resolved from the source registration rather than a row column. Enforcing that resolution requires the write-through connect path, which is not enabled on this plane yet so the model cannot be served through the tenant-scoped read or bootstrap path without risking a cross-tenant read. If this model lives on a log plane, scope it with `by: 'column'` or `by: 'parent'` instead."
1393
+ "This model gets its tenant identity from the connected Data Source, but that connection is not ready for scoped reads yet. Ablo refused the read to prevent data from crossing tenant boundaries. Connect the branch and retry; for internally logged models, use `by: 'column'` or `by: 'parent'`."
1298
1394
  ),
1299
1395
  user_scope_not_enforced: wire(
1300
1396
  'tenant',
1301
1397
  400,
1302
1398
  false,
1303
- "Rows in this model belong to one person rather than to the whole organization, and that boundary is kept where the rows are stored. This plane is currently served from its log, which carries the organization and the project on every row but not the owner — so the boundary cannot be applied here, and serving the read would show one member another member's private records. It was declined whole and nothing was returned. Serve the plane from its tables, or read with a credential that acts for the organization rather than for a person."
1399
+ "Rows in this model belong to one person rather than the whole organization, but the current read source does not carry the owner identity needed to enforce that boundary. Ablo returned nothing to prevent one member from seeing another member's private records. Use a read source that preserves owner identity, or a credential that acts for the organization."
1304
1400
  ),
1305
1401
  model_not_provisioned: wire(
1306
1402
  'tenant',
1307
1403
  409,
1308
1404
  false,
1309
- "This model is in the plane's registered schema, but its table has not been provisioned yet. Registering a schema with `ablo push` records the model; a plane's physical tables are created separately, out of band, so a model can appear in the schema before its table exists. Provision the plane's tables, then retry the read."
1405
+ "This model is in the branch's registered schema, but its table does not exist in the connected database yet. `ablo push` records the model but does not change a customer's database. Apply the table migration in your database, then retry the read."
1310
1406
  ),
1311
1407
  schema_table_invalid: wire('schema', 500, false, "The model's table identifier is invalid."),
1312
1408
  schema_scope_invalid: wire(
@@ -1480,7 +1576,7 @@ export const ERROR_CODES = {
1480
1576
  'not_found',
1481
1577
  404,
1482
1578
  false,
1483
- 'No claim of yours exists with the given id. It was released, it expired, or it was never acquired on this plane.'
1579
+ 'No claim of yours exists with the given id. It was released, expired, or belongs to a different branch.'
1484
1580
  ),
1485
1581
  invalid_participant_kind: wire(
1486
1582
  'validation',
@@ -1547,7 +1643,7 @@ export const ERROR_CODES = {
1547
1643
  'conflict',
1548
1644
  409,
1549
1645
  false,
1550
- 'A connected log plane has live rows for a mapped model that disappeared. Declare a rename or an explicit drop/reset before advancing the schema.'
1646
+ 'This branch has live replicated rows for a mapped model that disappeared. Declare a rename or an explicit drop/reset before advancing the schema.'
1551
1647
  ),
1552
1648
  } as const satisfies Record<string, ErrorCodeSpec>;
1553
1649
 
package/src/footprint.ts CHANGED
@@ -51,25 +51,14 @@ export interface FootprintArtifact {
51
51
 
52
52
  // ── The names, for the code that creates and reads these objects ────────────
53
53
 
54
- /**
55
- * The publication Ablo's replication reads from. Constant by design: a
56
- * publication declares what streams, and several readers are meant to share one
57
- * (`publication_names` is a per-stream option, not slot state).
58
- */
54
+ /** Prefix used to derive a branch-scoped publication name. The exact unsuffixed
55
+ * name is retained only so `ablo connect scan` can identify an old installation. */
59
56
  export const ABLO_PUBLICATION = 'ablo_publication';
60
- /**
61
- * The replication slot that holds Ablo's position in the write-ahead log.
62
- *
63
- * Constant, and it should not be — a slot stores ONE position, so two
64
- * connections sharing this name compete for the same marker. It is why a
65
- * database can be connected to one plane at a time. ADR 0020 derives it per
66
- * connection; {@link isValidReplicationSlotName} is the check a derived name
67
- * has to pass.
68
- */
57
+ /** Prefix used to derive the branch-scoped replication slot. */
69
58
  export const ABLO_REPLICATION_SLOT = 'ablo_slot';
70
- /** The least-privilege login Ablo reads with. Per-connection under ADR 0020. */
59
+ /** Prefix used to derive the branch-scoped least-privilege replication login. */
71
60
  export const ABLO_REPLICATION_ROLE = 'ablo_replicator';
72
- /** The separate least-privilege login used only for row writes. */
61
+ /** Prefix used to derive the branch-scoped login used only for row writes. */
73
62
  export const ABLO_WRITE_ROLE = 'ablo_writer';
74
63
  /** The bookkeeping table that makes a retried write land once. */
75
64
  export const ABLO_IDEMPOTENCY_TABLE = 'ablo_idempotency';
@@ -90,15 +79,15 @@ export function isValidReplicationSlotName(name: string): boolean {
90
79
 
91
80
  // ── Per-connection names (ADR 0020) ─────────────────────────────────────────
92
81
 
93
- /** The immutable branch coordinates a connection is identified by. */
94
- export interface FootprintPlane {
82
+ /** The immutable branch coordinates that identify one customer Data Source. */
83
+ export interface DataSourceIdentity {
95
84
  readonly organizationId: string;
96
85
  readonly branchId: string;
97
86
  /** Omitted for the organization-default project. */
98
87
  readonly projectId?: string;
99
88
  }
100
89
 
101
- /** The names one connection owns. Nothing here is shared with another plane. */
90
+ /** The names one connection owns. Nothing here is shared with another Data Source. */
102
91
  export interface FootprintNames {
103
92
  readonly slot: string;
104
93
  readonly publication: string;
@@ -114,7 +103,7 @@ export interface FootprintNames {
114
103
  const SUFFIX_LENGTH = 16;
115
104
 
116
105
  /**
117
- * FNV-1a over the plane, in hex.
106
+ * FNV-1a over the Data Source identity, in hex.
118
107
  *
119
108
  * Deliberately not a cryptographic hash: this module is imported by the CLI,
120
109
  * which runs in environments without `node:crypto` guaranteed, and the property
@@ -123,11 +112,14 @@ const SUFFIX_LENGTH = 16;
123
112
  * over different seeds give the sixteen hex characters, which is ample for the
124
113
  * handful of planes any one database is ever connected to.
125
114
  */
126
- function planeDigest(plane: FootprintPlane): string {
115
+ function dataSourceDigest(identity: DataSourceIdentity): string {
127
116
  // The organization-default project may be omitted or repeated as the
128
117
  // organization id; both spell the same branch coordinates.
129
- const project = plane.projectId === plane.organizationId ? '' : (plane.projectId ?? '');
130
- const key = [plane.organizationId, project, plane.branchId].join('');
118
+ const project =
119
+ identity.projectId === identity.organizationId ? '' : (identity.projectId ?? '');
120
+ // Delimit the coordinates. Concatenation alone makes structurally different
121
+ // planes such as ("ab", "c") and ("a", "bc") hash the same input.
122
+ const key = [identity.organizationId, project, identity.branchId].join('\0');
131
123
 
132
124
  const round = (seed: number): string => {
133
125
  let hash = seed;
@@ -146,16 +138,15 @@ function planeDigest(plane: FootprintPlane): string {
146
138
  * The objects this connection owns, named so no other connection can claim them.
147
139
  *
148
140
  * A slot stores ONE position, so two connections sharing a name compete for the
149
- * same marker and Postgres reports nothing which is why the constants these
150
- * replace made a database connectable to one plane at a time. The publication
141
+ * same marker and Postgres reports nothing. The publication
151
142
  * and roles are derived from the same digest so a database's footprint reads as
152
143
  * one set per connection rather than a mix of shared and private objects.
153
144
  *
154
145
  * Stable: the same plane always derives the same names, so re-running setup is a
155
146
  * no-op rather than a second installation.
156
147
  */
157
- export function footprintNamesFor(plane: FootprintPlane): FootprintNames {
158
- const suffix = planeDigest(plane);
148
+ export function footprintNamesFor(identity: DataSourceIdentity): FootprintNames {
149
+ const suffix = dataSourceDigest(identity);
159
150
  const names: FootprintNames = {
160
151
  slot: `${ABLO_REPLICATION_SLOT}_${suffix}`,
161
152
  publication: `${ABLO_PUBLICATION}_${suffix}`,
package/src/pricing.ts CHANGED
@@ -23,7 +23,7 @@
23
23
  * rather than billed.
24
24
  *
25
25
  * The allowance a tier advertises is DERIVED, never stored: "the floor covers
26
- * 49.5M operations" is {@link opsForUsd} applied to the floor. Storing it
26
+ * 10M operations" is {@link opsForUsd} applied to the floor. Storing it
27
27
  * alongside the floor is how a published price silently stops matching the
28
28
  * invoice, because raising one number does not fail anything that pins the
29
29
  * other.
@@ -47,7 +47,7 @@ export type { MeterEvent, PlanTier, RateBracket };
47
47
  * could observe the difference. It is emitted into the generated pricing
48
48
  * documentation so a stale copy is identifiable on sight.
49
49
  */
50
- export const PRICING_VERSION = '2026-07-27';
50
+ export const PRICING_VERSION = '2026-07-31';
51
51
 
52
52
  /**
53
53
  * Resolve a stored plan string (`stripe_subscription.plan`) to a tier.
@@ -82,12 +82,40 @@ export const METER_EVENT_AXIS: Record<MeterEvent, BillableAxis | null> = {
82
82
 
83
83
  /** How each meter reads on an invoice line and in the pricing table. */
84
84
  export const METER_EVENT_LABEL: Record<MeterEvent, string> = {
85
- 'api.commit_ops': 'commit operations',
85
+ 'api.commit_ops': 'writes',
86
86
  'api.model_reads': 'model reads',
87
87
  'api.claim_creates': 'claim creates',
88
88
  'api.bootstraps': 'bootstraps',
89
89
  };
90
90
 
91
+ /**
92
+ * What each meter counts, in the words a customer is quoted. It lives beside
93
+ * the label because a meter's name and its definition are one fact: a page that
94
+ * renamed the line without restating what it counts would be describing a
95
+ * different meter.
96
+ *
97
+ * These say **write**, never *commit*. A commit is the protocol's atomic batch
98
+ * and it belongs in the protocol reference; the surface a customer buys against
99
+ * is `create` / `update` / `delete`, which the front door calls writing. Pricing
100
+ * copy is read by people who have never opened the protocol reference, and a
101
+ * word they have not met cannot clarify what they are paying for.
102
+ *
103
+ * All three read as "someone did something", because they are one price and a
104
+ * reader who has to hold three different shapes in mind will assume there are
105
+ * three different charges. Each line then answers the question that particular
106
+ * meter provokes: whether batching is cheaper, whether cached reads count, and
107
+ * whether holding a claim costs more than taking one.
108
+ */
109
+ export const METER_EVENT_COUNTS: Record<MeterEvent, string> = {
110
+ 'api.commit_ops':
111
+ 'One every time someone changes something. Adding, editing, and removing all count the same, and changing 500 things is 500 operations however they are sent.',
112
+ 'api.model_reads':
113
+ 'One every time someone reads something the engine answers. Reads a client already has locally never reach us and never count.',
114
+ 'api.claim_creates':
115
+ 'One every time someone takes ownership of a row so others wait their turn. Holding it and releasing it are free.',
116
+ 'api.bootstraps': 'Recorded, never charged. Connecting a client is free.',
117
+ };
118
+
91
119
  /** The meters a customer is charged for, in declaration order. */
92
120
  export const BILLABLE_METER_EVENTS: readonly MeterEvent[] = (
93
121
  meterEventSchema.options as readonly MeterEvent[]
@@ -185,8 +213,8 @@ export const planDefinitionSchema = z.object({
185
213
  hardCapOps: z.number().int().positive().nullable(),
186
214
  /**
187
215
  * A hard daily stop, which is a burst guard rather than an allowance. A month
188
- * of operations is a comfortable month of building and about ten minutes of
189
- * one agent at full rate, so a monthly cap alone lets a runaway loop erase the
216
+ * of operations is a comfortable month of building and about a minute of one
217
+ * agent at full rate, so a monthly cap alone lets a runaway loop erase the
190
218
  * whole month before anyone notices. The daily figure is what turns that into
191
219
  * a floor on how fast the month can be spent. `null` on metered tiers, which
192
220
  * are billed rather than stopped.
@@ -214,8 +242,8 @@ export const PLANS = z
214
242
  label: 'Free',
215
243
  summary: 'Build against the real engine without a card.',
216
244
  monthlyMinimumUsd: 0,
217
- hardCapOps: 1_000_000,
218
- hardCapOpsPerDay: 100_000,
245
+ hardCapOps: 100_000,
246
+ hardCapOpsPerDay: 10_000,
219
247
  storageGib: 1,
220
248
  maxConcurrentConnections: 25,
221
249
  contractPriced: false,
@@ -225,7 +253,7 @@ export const PLANS = z
225
253
  tier: 'scale',
226
254
  label: 'Scale',
227
255
  summary: 'Production traffic, metered above a monthly floor.',
228
- monthlyMinimumUsd: 99,
256
+ monthlyMinimumUsd: 20,
229
257
  hardCapOps: null,
230
258
  hardCapOpsPerDay: null,
231
259
  storageGib: 50,