@abloatai/ablo 0.35.0 → 0.36.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 (215) hide show
  1. package/CHANGELOG.md +713 -629
  2. package/README.md +56 -519
  3. package/bin/ablo.cjs +39 -0
  4. package/dist/BaseSyncedStore.d.ts +24 -4
  5. package/dist/BaseSyncedStore.js +53 -37
  6. package/dist/Database.d.ts +8 -20
  7. package/dist/Database.js +61 -59
  8. package/dist/InstanceCache.d.ts +6 -2
  9. package/dist/InstanceCache.js +18 -16
  10. package/dist/Model.d.ts +17 -7
  11. package/dist/Model.js +17 -7
  12. package/dist/ModelRegistry.d.ts +4 -0
  13. package/dist/ModelRegistry.js +17 -15
  14. package/dist/NetworkMonitor.d.ts +3 -1
  15. package/dist/NetworkMonitor.js +7 -5
  16. package/dist/SyncClient.d.ts +5 -15
  17. package/dist/SyncClient.js +60 -57
  18. package/dist/client/Ablo.js +30 -19
  19. package/dist/client/createInternalComponents.d.ts +4 -0
  20. package/dist/client/createInternalComponents.js +8 -2
  21. package/dist/client/createModelProxy.d.ts +21 -1
  22. package/dist/client/createModelProxy.js +123 -57
  23. package/dist/client/humans.d.ts +30 -9
  24. package/dist/client/humans.js +45 -19
  25. package/dist/client/reactiveEngine.d.ts +16 -11
  26. package/dist/client/reactiveEngine.js +113 -335
  27. package/dist/client/storeCluster.d.ts +47 -0
  28. package/dist/client/storeCluster.js +118 -0
  29. package/dist/client/storeLifecycle.d.ts +61 -0
  30. package/dist/client/storeLifecycle.js +231 -0
  31. package/dist/context.d.ts +13 -0
  32. package/dist/context.js +23 -0
  33. package/dist/core/index.d.ts +1 -1
  34. package/dist/docs/catalog.js +6 -3
  35. package/dist/index.d.ts +4 -2
  36. package/dist/index.js +4 -2
  37. package/dist/query/client.d.ts +3 -0
  38. package/dist/query/client.js +6 -5
  39. package/dist/react/AbloProvider.d.ts +13 -1
  40. package/dist/react/AbloProvider.js +5 -2
  41. package/dist/react/context.d.ts +2 -2
  42. package/dist/react/createAbloReact.d.ts +56 -0
  43. package/dist/react/createAbloReact.js +51 -0
  44. package/dist/react/index.d.ts +1 -0
  45. package/dist/react/index.js +3 -0
  46. package/dist/react/useAblo.d.ts +9 -2
  47. package/dist/react/useAblo.js +25 -7
  48. package/dist/schema/coordination.js +5 -1
  49. package/dist/schema/index.d.ts +1 -0
  50. package/dist/schema/index.js +4 -0
  51. package/dist/schema/select.js +3 -0
  52. package/dist/schema/serialize.js +3 -0
  53. package/dist/source/adapter.d.ts +7 -5
  54. package/dist/source/adapter.js +7 -5
  55. package/dist/source/index.d.ts +1 -1
  56. package/dist/source/index.js +1 -1
  57. package/dist/{core/storeContract.d.ts → storeContract.d.ts} +5 -5
  58. package/dist/{core → stores}/DatabaseManager.d.ts +3 -1
  59. package/dist/{core → stores}/DatabaseManager.js +13 -12
  60. package/dist/{core → stores}/StoreManager.d.ts +5 -3
  61. package/dist/{core → stores}/StoreManager.js +24 -22
  62. package/dist/stores/SyncActionStore.d.ts +3 -1
  63. package/dist/stores/SyncActionStore.js +9 -7
  64. package/dist/sync/BootstrapFetcher.d.ts +4 -0
  65. package/dist/sync/BootstrapFetcher.js +28 -26
  66. package/dist/sync/OnDemandLoader.d.ts +3 -0
  67. package/dist/sync/OnDemandLoader.js +1 -0
  68. package/dist/sync/bootstrapApply.d.ts +3 -0
  69. package/dist/sync/bootstrapApply.js +2 -2
  70. package/dist/sync/deltaPipeline.d.ts +13 -12
  71. package/dist/sync/deltaPipeline.js +21 -4
  72. package/dist/sync/groupChange.d.ts +3 -0
  73. package/dist/sync/groupChange.js +16 -14
  74. package/dist/sync/participants.d.ts +19 -2
  75. package/dist/sync/participants.js +3 -1
  76. package/dist/sync/schemas.d.ts +2 -1
  77. package/dist/sync/schemas.js +3 -3
  78. package/dist/syncLog/contract.d.ts +20 -0
  79. package/dist/syncLog/contract.js +19 -0
  80. package/dist/syncLog/index.d.ts +1 -0
  81. package/dist/syncLog/index.js +1 -0
  82. package/dist/transaction/auth/capability.d.ts +35 -0
  83. package/dist/transaction/auth/capability.js +25 -0
  84. package/dist/transaction/coordination/awaitClaimGrant.d.ts +7 -0
  85. package/dist/transaction/coordination/awaitClaimGrant.js +12 -0
  86. package/dist/transaction/coordination/claimHeartbeatLoop.d.ts +34 -0
  87. package/dist/transaction/coordination/claimHeartbeatLoop.js +20 -0
  88. package/dist/transaction/coordination/index.d.ts +4 -4
  89. package/dist/transaction/coordination/index.js +4 -3
  90. package/dist/transaction/coordination/locator.d.ts +23 -2
  91. package/dist/transaction/coordination/locator.js +22 -2
  92. package/dist/transaction/coordination/schema.d.ts +125 -62
  93. package/dist/transaction/coordination/schema.js +228 -64
  94. package/dist/transaction/coordination/targetConflict.js +32 -28
  95. package/dist/transaction/errorCodes.d.ts +2 -2
  96. package/dist/transaction/errorCodes.js +13 -9
  97. package/dist/transaction/plugin.d.ts +95 -2
  98. package/dist/transaction/plugin.js +21 -2
  99. package/dist/transaction/resources/httpResources.d.ts +57 -2
  100. package/dist/transaction/resources/modelOperations.d.ts +148 -40
  101. package/dist/transaction/resources/where.d.ts +16 -0
  102. package/dist/transaction/resources/where.js +45 -0
  103. package/dist/transaction/schema/field.d.ts +12 -18
  104. package/dist/transaction/schema/fieldRef.d.ts +38 -0
  105. package/dist/transaction/schema/fieldRef.js +11 -0
  106. package/dist/transaction/schema/openapi.d.ts +16 -15
  107. package/dist/transaction/schema/openapi.js +186 -25
  108. package/dist/transaction/schema/relation.d.ts +7 -2
  109. package/dist/transaction/schema/schema.d.ts +27 -0
  110. package/dist/transaction/schema/schema.js +20 -0
  111. package/dist/transaction/transactions/settlement/commitEnvelope.d.ts +1 -1
  112. package/dist/transaction/transactions/settlement/pendingWrite.d.ts +1 -1
  113. package/dist/transaction/transport/httpClient.d.ts +9 -1
  114. package/dist/transaction/transport/httpClient.js +1 -0
  115. package/dist/transaction/transport/httpTransport.js +156 -44
  116. package/dist/transaction/transport/wsTransport.d.ts +2 -4
  117. package/dist/transaction/transport/wsTransport.js +16 -10
  118. package/dist/transaction/types/streams.d.ts +10 -0
  119. package/dist/transaction/utils/duration.d.ts +25 -0
  120. package/dist/transaction/utils/duration.js +32 -0
  121. package/dist/transaction/wire/accountResponses.d.ts +69 -0
  122. package/dist/transaction/wire/accountResponses.js +36 -1
  123. package/dist/transaction/wire/auth.d.ts +9 -2
  124. package/dist/transaction/wire/auth.js +7 -1
  125. package/dist/transaction/wire/claims.d.ts +164 -97
  126. package/dist/transaction/wire/claims.js +126 -28
  127. package/dist/transaction/wire/commit.d.ts +1 -1
  128. package/dist/transaction/wire/feedEvent.d.ts +27 -0
  129. package/dist/transaction/wire/feedEvent.js +27 -1
  130. package/dist/transaction/wire/frames.d.ts +2 -2
  131. package/dist/transaction/wire/inboundFrames.d.ts +12 -2
  132. package/dist/transaction/wire/index.d.ts +10 -6
  133. package/dist/transaction/wire/index.js +12 -3
  134. package/dist/transaction/wire/modelMutations.d.ts +31 -0
  135. package/dist/transaction/wire/modelMutations.js +52 -0
  136. package/dist/transaction/wire/modelShape.d.ts +78 -0
  137. package/dist/transaction/wire/modelShape.js +74 -0
  138. package/dist/transactions/mutations/MutationQueue.d.ts +6 -0
  139. package/dist/transactions/mutations/MutationQueue.js +55 -45
  140. package/dist/transactions/mutations/commitPayload.d.ts +3 -2
  141. package/dist/transactions/mutations/commitPayload.js +5 -5
  142. package/dist/transactions/mutations/deltaConfirmation.d.ts +4 -0
  143. package/dist/transactions/mutations/deltaConfirmation.js +10 -8
  144. package/dist/transactions/mutations/replayValidation.d.ts +2 -1
  145. package/dist/transactions/mutations/replayValidation.js +3 -2
  146. package/dist/{core → views}/QueryView.d.ts +1 -1
  147. package/dist/{core → views}/QueryView.js +1 -1
  148. package/dist/{core → views}/ViewRegistry.d.ts +1 -1
  149. package/dist/{core/queryUtils.d.ts → views/incrementalView.d.ts} +6 -6
  150. package/dist/{core/queryUtils.js → views/incrementalView.js} +6 -6
  151. package/docs/agents.md +1 -1
  152. package/docs/api-keys.md +6 -6
  153. package/docs/api.md +5 -43
  154. package/docs/audit.md +4 -3
  155. package/docs/cli.md +11 -11
  156. package/docs/client-behavior.md +4 -4
  157. package/docs/concurrency-convention.md +28 -42
  158. package/docs/coordination.md +235 -83
  159. package/docs/data-sources.md +4 -4
  160. package/docs/debugging.md +34 -12
  161. package/docs/deployment.md +8 -8
  162. package/docs/examples/scoped-agent.md +3 -3
  163. package/docs/groups.md +57 -3
  164. package/docs/guarantees.md +37 -10
  165. package/docs/how-it-works.md +29 -5
  166. package/docs/idempotency.md +6 -6
  167. package/docs/identity.md +22 -23
  168. package/docs/index.md +8 -8
  169. package/docs/integration-guide.md +14 -3
  170. package/docs/mcp.md +7 -7
  171. package/docs/migration.md +34 -15
  172. package/docs/projects.md +1 -1
  173. package/docs/react.md +19 -8
  174. package/docs/sessions.md +1 -1
  175. package/docs/webhooks.md +9 -9
  176. package/llms.txt +4 -4
  177. package/package.json +13 -20
  178. package/dist/cli.cjs +0 -288600
  179. package/dist/testing/fixtures/bootstrap.d.ts +0 -49
  180. package/dist/testing/fixtures/bootstrap.js +0 -59
  181. package/dist/testing/fixtures/deltas.d.ts +0 -83
  182. package/dist/testing/fixtures/deltas.js +0 -136
  183. package/dist/testing/fixtures/httpResponses.d.ts +0 -70
  184. package/dist/testing/fixtures/httpResponses.js +0 -90
  185. package/dist/testing/fixtures/models.d.ts +0 -83
  186. package/dist/testing/fixtures/models.js +0 -272
  187. package/dist/testing/helpers/reactWrapper.d.ts +0 -69
  188. package/dist/testing/helpers/reactWrapper.js +0 -67
  189. package/dist/testing/helpers/syncEngineHarness.d.ts +0 -54
  190. package/dist/testing/helpers/syncEngineHarness.js +0 -73
  191. package/dist/testing/helpers/wait.d.ts +0 -30
  192. package/dist/testing/helpers/wait.js +0 -49
  193. package/dist/testing/index.d.ts +0 -23
  194. package/dist/testing/index.js +0 -33
  195. package/dist/testing/mocks/FakeDatabase.d.ts +0 -18
  196. package/dist/testing/mocks/FakeDatabase.js +0 -10
  197. package/dist/testing/mocks/MockMutationExecutor.d.ts +0 -87
  198. package/dist/testing/mocks/MockMutationExecutor.js +0 -186
  199. package/dist/testing/mocks/MockNetworkMonitor.d.ts +0 -20
  200. package/dist/testing/mocks/MockNetworkMonitor.js +0 -46
  201. package/dist/testing/mocks/MockSyncContext.d.ts +0 -51
  202. package/dist/testing/mocks/MockSyncContext.js +0 -72
  203. package/dist/testing/mocks/MockSyncStore.d.ts +0 -88
  204. package/dist/testing/mocks/MockSyncStore.js +0 -171
  205. package/dist/testing/mocks/MockWebSocket.d.ts +0 -71
  206. package/dist/testing/mocks/MockWebSocket.js +0 -118
  207. package/docs/interaction-model.md +0 -99
  208. /package/dist/{core → query}/QueryProcessor.d.ts +0 -0
  209. /package/dist/{core → query}/QueryProcessor.js +0 -0
  210. /package/dist/{core/storeContract.js → storeContract.js} +0 -0
  211. /package/dist/{core → stores}/openIDBWithTimeout.d.ts +0 -0
  212. /package/dist/{core → stores}/openIDBWithTimeout.js +0 -0
  213. /package/dist/{source → transaction}/footprint.d.ts +0 -0
  214. /package/dist/{source → transaction}/footprint.js +0 -0
  215. /package/dist/{core → views}/ViewRegistry.js +0 -0
@@ -1,10 +1,10 @@
1
1
  /**
2
- * Small, self-contained helpers for sorting, filtering, and binary insertion.
3
- * The incrementally-updated views that implement {@link IncrementalView} rely on
4
- * these for their ordering and matching, so keeping the rules in one place
5
- * ensures every view sorts and filters identically. The functions here work on
6
- * plain arrays and values — they hold no reference to models, pools, or the
7
- * reactivity system.
2
+ * The {@link IncrementalView} contract the interface a live view implements
3
+ * to receive add/update/remove notifications together with the sorting,
4
+ * matching, and binary-insertion rules every view shares. Keeping the rules in
5
+ * one place ensures every view sorts and filters identically. The functions
6
+ * here work on plain arrays and values — they hold no reference to models,
7
+ * pools, or the reactivity system.
8
8
  */
9
9
  /**
10
10
  * Compares two values for sorting, tolerating `null` and `undefined`, which
package/docs/agents.md CHANGED
@@ -47,7 +47,7 @@ and `claim`. It does **not** expose the stateful-only surface (`get` /
47
47
  live connection, so with `transport: 'http'` the return type narrows and they
48
48
  are a *compile error*, not a runtime surprise.
49
49
 
50
- ## Coordination claim, queue, reorder
50
+ ## Coordination: claim, queue, reorder
51
51
 
52
52
  The differentiator. A claim is a **durable lease + FIFO wait-line** on a row —
53
53
  "who's working on this, who's waiting" — and it's request/response, so an agent
package/docs/api-keys.md CHANGED
@@ -21,9 +21,9 @@ Pick your row:
21
21
 
22
22
  | Where your code runs | What to pass | Example |
23
23
  |---|---|---|
24
- | **Server / worker / CLI** (can hold a secret) | your secret `sk_` it defaults to `ABLO_API_KEY`, so usually pass **nothing** | `Ablo({ schema })` |
25
- | **Browser read-only** | a publishable `pk_` (safe to ship, like a Stripe `pk_`) | `Ablo({ schema, apiKey: process.env.NEXT_PUBLIC_ABLO_PUBLISHABLE_KEY })` |
26
- | **Browser writing as the signed-in user** | `authEndpoint` the route on your own backend that mints a short-lived per-user token | `Ablo({ schema, authEndpoint: '/api/ablo-session' })` |
24
+ | **Server / worker / CLI** (can hold a secret) | your secret `sk_`: it defaults to `ABLO_API_KEY`, so usually pass **nothing** | `Ablo({ schema })` |
25
+ | **Browser: read-only** | a publishable `pk_` (safe to ship, like a Stripe `pk_`) | `Ablo({ schema, apiKey: process.env.NEXT_PUBLIC_ABLO_PUBLISHABLE_KEY })` |
26
+ | **Browser: writing as the signed-in user** | `authEndpoint`: the route on your own backend that mints a short-lived per-user token | `Ablo({ schema, authEndpoint: '/api/ablo-session' })` |
27
27
 
28
28
  That's the whole story: one knob, filled by audience.
29
29
 
@@ -31,8 +31,8 @@ That's the whole story: one knob, filled by audience.
31
31
 
32
32
  | Stripe | Ablo | Where it goes |
33
33
  |---|---|---|
34
- | publishable `pk_` (client-safe) | `pk_` | browser read-only |
35
- | secret `sk_` (server, full) | `sk_` | server full authority |
34
+ | publishable `pk_` (client-safe) | `pk_` | browser: read-only |
35
+ | secret `sk_` (server, full) | `sk_` | server: full authority |
36
36
  | restricted `rk_` (granular) | `rk_` | scoped agent sessions (`sessions.create({ agent, can })`) |
37
37
  | ephemeral key (client, customer-scoped) | `ek_` | per-user browser sessions (`sessions.create({ user })`) |
38
38
 
@@ -74,7 +74,7 @@ Use API keys from trusted (server-side) runtimes:
74
74
 
75
75
  Never ship a secret API key to a browser bundle.
76
76
 
77
- ## Publishable key (`pk_`) browser-safe, read-only
77
+ ## Publishable key (`pk_`): browser-safe, read-only
78
78
 
79
79
  For a read-only browser experience, a publishable key is safe to ship in the
80
80
  bundle. Like a Stripe `pk_` or a Supabase anon key, it is long-lived,
package/docs/api.md CHANGED
@@ -120,49 +120,11 @@ blocks), and `ablo.<model>.claim.release({ id })` releases it early. The full
120
120
  coordination surface is `claim.state({ id })` / `claim.queue({ id })` /
121
121
  `claim.release({ id })` / `claim.reorder({ id, order })` hanging off `claim`.
122
122
 
123
- ### The Claim State Object
124
-
125
- | Field | Type | Description |
126
- |---|---|---|
127
- | `object` | `'claim'` | String representing the object's type. |
128
- | `id` | string | Unique identifier for the claim. |
129
- | `status` | `'active' \| 'queued' \| 'committed' \| 'expired' \| 'canceled'` | The whole lifecycle, in one field. `active` is the holder; `queued` is a waiter in the FIFO line behind it. |
130
- | `target` | `{ type, id, field? }` | What is being coordinated. |
131
- | `description` | string | Peer-visible phrase for the work in progress — `'editing'`, `'writing'`, `'reviewing the risk section'`. Defaults to `'editing'`, and rides back in the rejection a blocked writer receives. |
132
- | `heldBy` | string | Participant id holding the claim. |
133
- | `participantKind` | `'user' \| 'agent' \| 'system'` | Who's behind it — a human (`user`), an AI (`agent`), or automated infrastructure (`system`). |
134
- | `createdAt` | number? | Ms-epoch the holder opened it. Optional — derived shapes may omit it. |
135
- | `expiresAt` | number | Ms-epoch at which the server auto-expires it if the holder doesn't finish. |
136
-
137
- ```json
138
- {
139
- "object": "claim",
140
- "id": "claim_3MtwBwLkdIwHu7ix",
141
- "status": "active",
142
- "target": { "type": "weatherReports", "id": "report_stockholm", "field": "status" },
143
- "description": "editing",
144
- "heldBy": "agent:report-writer",
145
- "participantKind": "agent",
146
- "expiresAt": 1716580000000
147
- }
148
- ```
149
-
150
- ### Lifecycle
151
-
152
- ```
153
- claim({ id }) update({ id }) lands
154
- (free) ───────────▶ active ───────────────────────▶ committed
155
-
156
- ┌───────────┴───────────┐
157
- ▼ ▼
158
- canceled expired
159
- (release w/o write) (TTL; holder died)
160
- ```
161
-
162
- A target is free when `ablo.<model>.claim.state({ id })` is `null`. Terminal
163
- states drop out of the live stream, so a present claim is either `active` (the
164
- holder) or `queued` (waiting in the FIFO line behind the holder; see
165
- `claim.queue({ id })`).
123
+ The fields on a claim, its lifecycle diagram, and the full method surface are in
124
+ [Coordination](./coordination.md#the-claim-state-object), which is where that
125
+ object is defined. Note that the entity half of `target` is spelled `model`/`id`
126
+ on the SDK's model surface and `type`/`id` on the claim handle and the wait
127
+ line.
166
128
 
167
129
  ### Reading and claiming
168
130
 
package/docs/audit.md CHANGED
@@ -23,11 +23,12 @@ with which key — and the chain columns that make the log tamper-evident:
23
23
  capabilityId: string | null, // the API key/capability used for the write
24
24
  capabilityLabel: string | null, // its human-readable name, for scanning the log
25
25
  delegationChainRootUserId: string | null, // always points at a human
26
- actionType: string, // e.g. 'weatherReport.update'
27
- modelName: string, // e.g. 'claude-opus-4-8'
26
+ actionType: 'I' | 'U' | 'D', // insert, update, delete
27
+ modelName: string, // the model that changed, e.g. 'orders'
28
+ modelId: string, // the row that changed
28
29
  confirmationState: 'auto' | 'previewed' | 'approved' | 'required_human_approval' | 'auto_historical',
29
30
  diffSummary: unknown,
30
- // chain columns carried on every stored row, checked by verify (below)
31
+ // chain columns, carried on every stored row and checked by verify (below)
31
32
  chainSeq: number,
32
33
  prevHash: string,
33
34
  rowHash: string,
package/docs/cli.md CHANGED
@@ -94,18 +94,18 @@ profiles entirely: it acts in whatever project it was minted for.
94
94
 
95
95
  | Command | What it does | Flags |
96
96
  | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
97
- | `ablo init` | Scaffold `ablo/` (`schema.ts`, client, optional Data Source / agent / component), write `.env`, install the SDK. Offers to log in at the end. | |
98
- | `ablo login` / `logout` / `status` | Authentication & status (above). | |
99
- | `ablo mode [sandbox\|production]` | Switch active environment. | |
97
+ | `ablo init` | Scaffold `ablo/` (`schema.ts`, client, optional Data Source / agent / component), write `.env`, install the SDK. Offers to log in at the end. |: |
98
+ | `ablo login` / `logout` / `status` | Authentication & status (above). |: |
99
+ | `ablo mode [sandbox\|production]` | Switch active environment. |: |
100
100
  | `ablo projects list\|create\|use\|rename` | Manage projects and the active one (see [Projects](#projects)). Each project's keys/schema/data are isolated. | `--name "<display>"` (create/rename) |
101
- | `ablo dev` | **Hosted** push the schema to your test sandbox, then watch `ablo/schema.ts` and re-push on save. | `--no-watch`, `--schema <path>`, `--export <name>`, `--url <url>` |
101
+ | `ablo dev` | **Hosted**: push the schema to your test sandbox, then watch `ablo/schema.ts` and re-push on save. | `--no-watch`, `--schema <path>`, `--export <name>`, `--url <url>` |
102
102
  | `ablo logs` | Tail your scope's commit activity (`stripe logs tail`). Follows by default. | `-n, --tail <N>`, `--since <dur\|ts>`, `--model`, `--op`, `--json`, `--no-follow`, `--mode sandbox\|production` |
103
- | `ablo push` | **Hosted** upload the schema to Ablo; the server diffs, migrates, and activates it. | `--force`, `--rename old:new`, `--backfill model.field=value`, `--schema`, `--export`, `--url` |
104
- | `ablo migrate` | **Direct Postgres** provision just the synced models (plus the adapter's `ablo_outbox` / `ablo_idempotency`) in your own `DATABASE_URL`. Leaves your other tables alone. | `--dry-run`, `--output <file>`, `--schema`, `--export` |
105
- | `ablo pull` | **Direct Postgres** generate `defineSchema(...)` from your existing tables (read-only, like `prisma db pull`). | `--out <path>`, `--app-schema <name>`, `--import <pkg>`, `--force` |
106
- | `ablo check` | **Direct Postgres** verify your _existing_ tables fit the schema (read-only, no schema changes). | `--schema <path>`, `--export <name>`, `--app-schema <name>` |
103
+ | `ablo push` | **Hosted**: upload the schema to Ablo; the server diffs, migrates, and activates it. | `--force`, `--rename old:new`, `--backfill model.field=value`, `--schema`, `--export`, `--url` |
104
+ | `ablo migrate` | **Direct Postgres**: provision just the synced models (plus the adapter's `ablo_outbox` / `ablo_idempotency`) in your own `DATABASE_URL`. Leaves your other tables alone. | `--dry-run`, `--output <file>`, `--schema`, `--export` |
105
+ | `ablo pull` | **Direct Postgres**: generate `defineSchema(...)` from your existing tables (read-only, like `prisma db pull`). | `--out <path>`, `--app-schema <name>`, `--import <pkg>`, `--force` |
106
+ | `ablo check` | **Direct Postgres**: verify your _existing_ tables fit the schema (read-only, no schema changes). | `--schema <path>`, `--export <name>`, `--app-schema <name>` |
107
107
  | `ablo generate` | Emit TypeScript types from the schema. | `--out <path>`, `--schema`, `--export` |
108
- | `ablo docs` | Read these pages for the version you installed offline, no network (see [`ablo docs`](#ablo-docs)). | `--json` |
108
+ | `ablo docs` | Read these pages for the version you installed: offline, no network (see [`ablo docs`](#ablo-docs)). | `--json` |
109
109
 
110
110
  ## `ablo docs`
111
111
 
@@ -241,7 +241,7 @@ The one type map, shared by both paths (there is no second mapping):
241
241
  | Zod | Postgres |
242
242
  | ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------- |
243
243
  | `z.string()` | `TEXT` |
244
- | `z.number()` | `DOUBLE PRECISION` never `INTEGER`; a Zod number may be fractional, and truncating is silent data loss |
244
+ | `z.number()` | `DOUBLE PRECISION`: never `INTEGER`; a Zod number may be fractional, and truncating is silent data loss |
245
245
  | `z.boolean()` | `BOOLEAN` |
246
246
  | `z.date()` | `TIMESTAMPTZ` |
247
247
  | `z.enum([...])` | `TEXT` + a `CHECK (col IN (...))` constraint |
@@ -296,7 +296,7 @@ migration can't leave clients gated against tables that don't match.
296
296
 
297
297
  | Variable | Purpose | Default |
298
298
  | ------------------------------------- | ------------------------------------------------------------------------ | -------------------------- |
299
- | `ABLO_API_KEY` | Authenticate without `ablo login` (CI). Always overrides the stored key. | |
299
+ | `ABLO_API_KEY` | Authenticate without `ablo login` (CI). Always overrides the stored key. |: |
300
300
  | `ABLO_API_URL` | Control-plane / API host (`push`, `dev`, `status`). | `https://api.abloatai.com` |
301
301
  | `ABLO_AUTH_URL` | Dashboard origin for `ablo login`'s device flow. | `https://abloatai.com` |
302
302
  | `ABLO_CONFIG_DIR` / `XDG_CONFIG_HOME` | Where the credential file lives. | `~/.config/ablo` |
@@ -34,7 +34,7 @@ Common options:
34
34
  | `baseURL` | Override the hosted sync endpoint for staging or private deployments. |
35
35
  | `persistence` | `memory` by default. Use `indexeddb` for a durable browser cache that survives reloads. |
36
36
  | `durableWrites` | Optional crash recovery for unacknowledged agent/worker writes. Independent of the default memory cache; accepts `{ store, namespace? }`. |
37
- | `transport` | `'websocket'` (default) is the live, stateful client a persistent socket, a local synced pool, and `onChange` subscriptions. `'http'` returns the **stateless** client for server-side actors (agents, workers, serverless): the same `ablo.<model>` read/write/claim surface, but each call is one HTTP round-trip with no socket. Under `'http'` the return type narrows to `AbloHttpClient`, so stateful-only methods (the `local` reads, `onChange`, `join`) are compile errors rather than runtime gaps. |
37
+ | `transport` | `'websocket'` (default) is the live, stateful client: a persistent socket, a local synced pool, and `onChange` subscriptions. `'http'` returns the **stateless** client for server-side actors (agents, workers, serverless): the same `ablo.<model>` read/write/claim surface, but each call is one HTTP round-trip with no socket. Under `'http'` the return type narrows to `AbloHttpClient`, so stateful-only methods (the `local` reads, `onChange`, `join`) are compile errors rather than runtime gaps. |
38
38
  | `fetch` | Custom fetch implementation for tests or non-standard runtimes. |
39
39
  | `defaultHeaders` | Extra headers attached to every HTTP request. |
40
40
  | `defaultQuery` | Extra query parameters attached to every HTTP request. |
@@ -218,8 +218,8 @@ Only these imports are public SemVer surface:
218
218
  - `@abloatai/ablo`
219
219
  - `@abloatai/ablo/schema`
220
220
  - `@abloatai/ablo/react`
221
- - `@abloatai/ablo/testing`
222
221
 
223
222
  `dataSource(...)` is exported from the root package for customer-owned storage
224
- adapters. Everything outside the four import paths is internal to Ablo-owned
225
- apps and infrastructure.
223
+ adapters. Everything outside the three import paths is internal to Ablo-owned
224
+ apps and infrastructure. For adapter authors, `@abloatai/ablo/source/conformance`
225
+ is the suite that proves a storage adapter behaves correctly.
@@ -2,9 +2,9 @@
2
2
 
3
3
  > The governing rule for how Ablo resolves concurrent writes to shared state.
4
4
 
5
- > The governing convention for how Ablo resolves concurrent writes to shared
6
- > state, and the boundaries of that convention. This is the contract; the
7
- > three-layer mechanics live in [`coordination.md`](./coordination.md).
5
+ This page is the contract: the `onStale` dispositions, what a conflict is
6
+ checked against, and where the convention stops. The three-layer mechanics of
7
+ claiming live in [Coordination](./coordination.md).
8
8
 
9
9
  ---
10
10
 
@@ -27,8 +27,8 @@ moments in time:
27
27
 
28
28
  | form | when | mechanism |
29
29
  |---|---|---|
30
- | **Claim** | *prospective* before you act | reserve the row; others queue. Coordinate so the conflict never forms. |
31
- | **Notification** | *in-flight* after a concurrent change | surface the changed value; the actor resolves and re-issues. |
30
+ | **Claim** | *prospective*: before you act | reserve the row; others queue. Coordinate so the conflict never forms. |
31
+ | **Notification** | *in-flight*: after a concurrent change | surface the changed value; the actor resolves and re-issues. |
32
32
 
33
33
  Use a claim when you will hold the row across a slow read→reason→write gap. Use a
34
34
  notification when you didn't, and the premise moved under you.
@@ -42,9 +42,9 @@ when it goes stale. Three modes, split by whether they **force** an outcome:
42
42
 
43
43
  | mode | coercive? | what the engine does | who resolves | use when |
44
44
  |---|---|---|---|---|
45
- | `notify` | **No** surface + delegate | Holds the write (does **not** apply it); returns a `StaleNotification` with the current value. | The actor (agent or human) reconciles and re-issues. | The aligned mode: tell the actor what changed, let it solve. |
46
- | `reject` | **Yes** force-abort | Throws `AbloStaleContextError`; the batch is discarded. | The caller retries from scratch. | Hard invariants; legacy/strict callers. The current default. |
47
- | `overwrite` | **Yes** force-clobber | Overwrites blindly last-writer-wins; **no** signal. | Nobody. | You genuinely own the field and concurrent values are noise. |
45
+ | `notify` | **No**: surface + delegate | Holds the write (does **not** apply it); returns a `StaleNotification` with the current value. | The actor (agent or human) reconciles and re-issues. | The aligned mode: tell the actor what changed, let it solve. |
46
+ | `reject` | **Yes**: force-abort | Throws `AbloStaleContextError`; the batch is discarded. | The caller retries from scratch. | Hard invariants; legacy/strict callers. The current default. |
47
+ | `overwrite` | **Yes**: force-clobber | Overwrites blindly last-writer-wins; **no** signal. | Nobody. | You genuinely own the field and concurrent values are noise. |
48
48
 
49
49
  > `notify` is the convention. `reject` and `overwrite` are escape hatches for the
50
50
  > two ends — "never let this be wrong" and "never bother me." They are not the
@@ -82,9 +82,9 @@ reads: [
82
82
  ]
83
83
  ```
84
84
 
85
- - **Row** did this specific row (optionally these fields) change? The literal
85
+ - **Row:** did this specific row (optionally these fields) change? The literal
86
86
  per-object premise.
87
- - **Group** did *anything* in this sync group change? `group` is a sync-group
87
+ - **Group:** did *anything* in this sync group change? `group` is a sync-group
88
88
  key (`workspace:abc`, `document:s1`, `org:X`) — the same unit a participant **watches
89
89
  and claims**. This is the more Ablo-native granularity.
90
90
 
@@ -108,13 +108,13 @@ Shape (canonical in `coordination/schema.ts`):
108
108
 
109
109
  | field | meaning |
110
110
  |---|---|
111
- | `object` | Stripe-style type tag `'stale_notification'` |
111
+ | `object` | Stripe-style type tag: `'stale_notification'` |
112
112
  | `model`, `id` | the conflicting row (for a group dep, both are the group key) |
113
113
  | `group?` | set when this is a group-scoped notification |
114
114
  | `readAt` | the watermark the committer reasoned against |
115
- | `observedSyncId` | the newest delta on the premise re-read at/after this |
115
+ | `observedSyncId` | the newest delta on the premise: re-read at/after this |
116
116
  | `conflictingFields` | fields that moved (empty for group / whole-entity) |
117
- | `currentValues` | the live values of those fields the premise to reconcile against (empty for group) |
117
+ | `currentValues` | the live values of those fields: the premise to reconcile against (empty for group) |
118
118
  | `writtenBy` | `{ kind, id }` of the concurrent author, reported faithfully |
119
119
 
120
120
  Only `notify` produces a notification (the write was held). `reject` throws and
@@ -169,7 +169,7 @@ What the convention **guarantees**, and where it **stops**:
169
169
  or human) owns the resolution. The engine does not distinguish them — it is
170
170
  actor-neutral by design.
171
171
 
172
- 2. **Truthfulness.** `currentValues` / `observedSyncId` reflect committed state at
172
+ 2. **Truthfulness:** `currentValues` / `observedSyncId` reflect committed state at
173
173
  detection time, inside the same transaction as the write. A notification is
174
174
  never speculative.
175
175
 
@@ -185,13 +185,11 @@ What the convention **guarantees**, and where it **stops**:
185
185
  so they must not be gated by `notify`.
186
186
 
187
187
  5. **Defaults.** A plain write (no `readAt`) is last-writer-wins with **no**
188
- check. A guarded write with `readAt` but no `onStale` defaults to `reject`
189
- (back-compat). *Open decision (§7).*
188
+ check. A guarded write with `readAt` but no `onStale` defaults to `reject`.
190
189
 
191
190
  6. **Policy seam.** Custom `ConflictPolicy` functions see **write-target**
192
191
  conflicts (`stale_context` / `claim_held`). **Batch-premise** conflicts are
193
- currently resolved directly via each entry's `onStale`, not through the policy
194
- seam. *Open decision (§7).*
192
+ resolved directly via each entry's `onStale`, not through the policy seam.
195
193
 
196
194
  7. **Claims win when held.** A non-holder writing to a claimed row is rejected
197
195
  (`AbloClaimedError`) regardless of `readAt` — the prospective form takes
@@ -200,29 +198,17 @@ What the convention **guarantees**, and where it **stops**:
200
198
 
201
199
  ---
202
200
 
203
- ## 7. Open decisions (bounded, not yet made)
201
+ ## 7. What this convention does not cover
204
202
 
205
- These are deliberately left open; they change behavior and are the user's call.
203
+ Three limits worth knowing before you rely on it.
206
204
 
207
- - **Default disposition for agents.** Should an agent-participant guarded write
208
- default to `notify` (philosophy-aligned: surface, don't overwrite) instead of
209
- `reject` (back-compat)? Trade-off: alignment vs. a behavior change for existing
210
- agent callers.
211
- - **Batch premises through the policy seam.** Should premise conflicts also
212
- pass through `ConflictPolicy` (requires a group-aware conflict shape), or stay
213
- on the direct `onStale` mapping?
214
-
215
- ---
216
-
217
- ## 8. Out of scope
218
-
219
- - Irreversible external side-effects (§6.4) — not gated by this convention.
220
- - Cross-object *serializability proof*. A batch premise is a sound check, not
221
- a full precedence-graph guarantee; it catches only what the caller declared.
222
- A caller that declares nothing gets **no check at all** — not write-target
223
- checking, which needs a `readAt` to check against. A plain write is
224
- last-writer-wins, as §6.5 says. The floor is zero, and closing that gap is the
225
- subject of ADR 0018.
226
- - Identity → participant-kind mapping. `writtenBy.kind` reports whatever
227
- authenticated (an `sk_` key resolves to `system`, not `agent`); how identities
228
- map to kinds is a separate concern.
205
+ - **Irreversible external side-effects.** Emails, payments, and third-party
206
+ calls are not gated by this convention (§6.4). The engine cannot hold or undo
207
+ them, so never place one behind `notify`.
208
+ - **A caller that declares nothing gets no check.** The batch premise catches
209
+ only what you declared. Write-target checking needs a `readAt` to compare
210
+ against, so a plain write with neither is last-writer-wins (§6.5). What you
211
+ declare is what is protected.
212
+ - **`writtenBy.kind` reports what authenticated, not what you meant.** An `sk_`
213
+ key resolves to `system`, not `agent`. How identities map to participant kinds
214
+ is a separate concern from this convention.