@abloatai/ablo 0.35.0 → 0.37.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 (622) hide show
  1. package/AGENTS.md +2 -2
  2. package/CHANGELOG.md +71 -1929
  3. package/NOTICE +2 -2
  4. package/README.md +23 -532
  5. package/assets/banner.png +0 -0
  6. package/dist/auth.d.ts +2 -0
  7. package/dist/auth.d.ts.map +1 -0
  8. package/dist/auth.js +2 -0
  9. package/dist/auth.js.map +1 -0
  10. package/dist/client.d.ts +3 -0
  11. package/dist/client.d.ts.map +1 -0
  12. package/dist/client.js +3 -0
  13. package/dist/client.js.map +1 -0
  14. package/dist/coordination.d.ts +2 -0
  15. package/dist/coordination.d.ts.map +1 -0
  16. package/dist/coordination.js +2 -0
  17. package/dist/coordination.js.map +1 -0
  18. package/dist/index.d.ts +3 -112
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +3 -161
  21. package/dist/index.js.map +1 -0
  22. package/dist/react.d.ts +4 -0
  23. package/dist/react.d.ts.map +1 -0
  24. package/dist/react.js +3 -0
  25. package/dist/react.js.map +1 -0
  26. package/dist/schema.d.ts +2 -0
  27. package/dist/schema.d.ts.map +1 -0
  28. package/dist/schema.js +2 -0
  29. package/dist/schema.js.map +1 -0
  30. package/dist/server.d.ts +2 -0
  31. package/dist/server.d.ts.map +1 -0
  32. package/dist/server.js +2 -0
  33. package/dist/server.js.map +1 -0
  34. package/dist/source-conformance.d.ts +2 -0
  35. package/dist/source-conformance.d.ts.map +1 -0
  36. package/dist/source-conformance.js +2 -0
  37. package/dist/source-conformance.js.map +1 -0
  38. package/dist/source-drizzle.d.ts +2 -0
  39. package/dist/source-drizzle.d.ts.map +1 -0
  40. package/dist/source-drizzle.js +2 -0
  41. package/dist/source-drizzle.js.map +1 -0
  42. package/dist/source-kysely.d.ts +2 -0
  43. package/dist/source-kysely.d.ts.map +1 -0
  44. package/dist/source-kysely.js +2 -0
  45. package/dist/source-kysely.js.map +1 -0
  46. package/dist/source-next.d.ts +2 -0
  47. package/dist/source-next.d.ts.map +1 -0
  48. package/dist/source-next.js +2 -0
  49. package/dist/source-next.js.map +1 -0
  50. package/dist/source.d.ts +2 -0
  51. package/dist/source.d.ts.map +1 -0
  52. package/dist/source.js +2 -0
  53. package/dist/source.js.map +1 -0
  54. package/dist/wire.d.ts +2 -0
  55. package/dist/wire.d.ts.map +1 -0
  56. package/dist/wire.js +2 -0
  57. package/dist/wire.js.map +1 -0
  58. package/docs/agents.md +2 -2
  59. package/docs/api-keys.md +13 -12
  60. package/docs/api.md +15 -53
  61. package/docs/audit.md +4 -3
  62. package/docs/cli.md +11 -11
  63. package/docs/client-behavior.md +9 -9
  64. package/docs/concurrency-convention.md +28 -42
  65. package/docs/coordination.md +228 -86
  66. package/docs/data-sources.md +5 -5
  67. package/docs/debugging.md +34 -12
  68. package/docs/deployment.md +8 -8
  69. package/docs/examples/agent-human.md +4 -4
  70. package/docs/examples/ai-sdk-tool.md +1 -1
  71. package/docs/examples/existing-python-backend.md +15 -4
  72. package/docs/examples/nextjs.md +27 -6
  73. package/docs/examples/scoped-agent.md +3 -3
  74. package/docs/examples/server-agent.md +2 -2
  75. package/docs/groups.md +57 -3
  76. package/docs/guarantees.md +37 -10
  77. package/docs/how-it-works.md +32 -8
  78. package/docs/idempotency.md +6 -6
  79. package/docs/identity.md +24 -24
  80. package/docs/index.md +8 -8
  81. package/docs/integration-guide.md +38 -16
  82. package/docs/internal/README.md +18 -0
  83. package/docs/internal/agent-fleet-coordination-design.md +171 -0
  84. package/docs/internal/agent-orchestration.md +58 -0
  85. package/docs/internal/commit-identifiers.md +91 -0
  86. package/docs/internal/concurrency-open-decisions.md +37 -0
  87. package/docs/internal/data-source-reverse-channel.md +150 -0
  88. package/docs/internal/per-field-conflict-detection.md +165 -0
  89. package/docs/internal/postgres-replication.md +64 -0
  90. package/docs/internal/serializable-schema.md +119 -0
  91. package/docs/internal/structure.md +32 -0
  92. package/docs/mcp.md +9 -9
  93. package/docs/migration.md +37 -18
  94. package/docs/projects.md +1 -1
  95. package/docs/quickstart.md +2 -2
  96. package/docs/react.md +24 -13
  97. package/docs/schema-contract.md +3 -3
  98. package/docs/sessions.md +91 -37
  99. package/docs/webhooks.md +9 -9
  100. package/examples/README.md +2 -2
  101. package/examples/data-source/README.md +1 -1
  102. package/examples/data-source/ablo-driver.ts +1 -1
  103. package/examples/data-source/customer-server.ts +1 -1
  104. package/examples/data-source/run.ts +1 -1
  105. package/examples/data-source/schema.ts +1 -1
  106. package/examples/quickstart.ts +2 -2
  107. package/llms.txt +12 -12
  108. package/package.json +64 -174
  109. package/dist/BaseSyncedStore.d.ts +0 -823
  110. package/dist/BaseSyncedStore.js +0 -1955
  111. package/dist/Database.d.ts +0 -335
  112. package/dist/Database.js +0 -1500
  113. package/dist/InstanceCache.d.ts +0 -233
  114. package/dist/InstanceCache.js +0 -1164
  115. package/dist/LazyReferenceCollection.d.ts +0 -177
  116. package/dist/LazyReferenceCollection.js +0 -461
  117. package/dist/Model.d.ts +0 -444
  118. package/dist/Model.js +0 -909
  119. package/dist/ModelRegistry.d.ts +0 -221
  120. package/dist/ModelRegistry.js +0 -537
  121. package/dist/NetworkMonitor.d.ts +0 -26
  122. package/dist/NetworkMonitor.js +0 -77
  123. package/dist/RuntimeContext.d.ts +0 -52
  124. package/dist/RuntimeContext.js +0 -80
  125. package/dist/SyncClient.d.ts +0 -551
  126. package/dist/SyncClient.js +0 -2199
  127. package/dist/adapters/alwaysOnline.d.ts +0 -14
  128. package/dist/adapters/alwaysOnline.js +0 -17
  129. package/dist/adapters/inMemoryStorage.d.ts +0 -31
  130. package/dist/adapters/inMemoryStorage.js +0 -110
  131. package/dist/ai-sdk/coordinatedTool.d.ts +0 -120
  132. package/dist/ai-sdk/coordinatedTool.js +0 -134
  133. package/dist/ai-sdk/coordinationContext.d.ts +0 -46
  134. package/dist/ai-sdk/coordinationContext.js +0 -106
  135. package/dist/ai-sdk/index.d.ts +0 -121
  136. package/dist/ai-sdk/index.js +0 -121
  137. package/dist/ai-sdk/wrap.d.ts +0 -65
  138. package/dist/ai-sdk/wrap.js +0 -39
  139. package/dist/auth/index.d.ts +0 -1
  140. package/dist/auth/index.js +0 -8
  141. package/dist/batching/index.d.ts +0 -55
  142. package/dist/batching/index.js +0 -147
  143. package/dist/cli.cjs +0 -288600
  144. package/dist/client/Ablo.d.ts +0 -231
  145. package/dist/client/Ablo.js +0 -149
  146. package/dist/client/abloClient.d.ts +0 -309
  147. package/dist/client/abloClient.js +0 -13
  148. package/dist/client/clientPrelude.d.ts +0 -52
  149. package/dist/client/clientPrelude.js +0 -60
  150. package/dist/client/consoleLogger.d.ts +0 -35
  151. package/dist/client/consoleLogger.js +0 -44
  152. package/dist/client/coreClient.d.ts +0 -60
  153. package/dist/client/coreClient.js +0 -118
  154. package/dist/client/createInternalComponents.d.ts +0 -46
  155. package/dist/client/createInternalComponents.js +0 -92
  156. package/dist/client/createModelProxy.d.ts +0 -228
  157. package/dist/client/createModelProxy.js +0 -818
  158. package/dist/client/humans.d.ts +0 -48
  159. package/dist/client/humans.js +0 -52
  160. package/dist/client/modelRegistration.d.ts +0 -10
  161. package/dist/client/modelRegistration.js +0 -312
  162. package/dist/client/options.d.ts +0 -461
  163. package/dist/client/options.js +0 -7
  164. package/dist/client/reactiveEngine.d.ts +0 -48
  165. package/dist/client/reactiveEngine.js +0 -910
  166. package/dist/client/resourceTypes.d.ts +0 -12
  167. package/dist/client/resourceTypes.js +0 -10
  168. package/dist/client/schemaConfig.d.ts +0 -44
  169. package/dist/client/schemaConfig.js +0 -185
  170. package/dist/client/validateAbloOptions.d.ts +0 -42
  171. package/dist/client/validateAbloOptions.js +0 -43
  172. package/dist/client/wsMutationExecutor.d.ts +0 -27
  173. package/dist/client/wsMutationExecutor.js +0 -72
  174. package/dist/context.d.ts +0 -29
  175. package/dist/context.js +0 -58
  176. package/dist/coordination/ClaimLog.d.ts +0 -26
  177. package/dist/coordination/ClaimLog.js +0 -32
  178. package/dist/coordination/index.d.ts +0 -1
  179. package/dist/coordination/index.js +0 -8
  180. package/dist/core/DatabaseManager.d.ts +0 -105
  181. package/dist/core/DatabaseManager.js +0 -387
  182. package/dist/core/QueryProcessor.d.ts +0 -75
  183. package/dist/core/QueryProcessor.js +0 -255
  184. package/dist/core/QueryView.d.ts +0 -79
  185. package/dist/core/QueryView.js +0 -218
  186. package/dist/core/StoreManager.d.ts +0 -112
  187. package/dist/core/StoreManager.js +0 -302
  188. package/dist/core/ViewRegistry.d.ts +0 -20
  189. package/dist/core/ViewRegistry.js +0 -55
  190. package/dist/core/index.d.ts +0 -33
  191. package/dist/core/index.js +0 -48
  192. package/dist/core/openIDBWithTimeout.d.ts +0 -65
  193. package/dist/core/openIDBWithTimeout.js +0 -153
  194. package/dist/core/queryUtils.d.ts +0 -45
  195. package/dist/core/queryUtils.js +0 -69
  196. package/dist/core/storeContract.d.ts +0 -145
  197. package/dist/core/storeContract.js +0 -12
  198. package/dist/docs/catalog.d.ts +0 -72
  199. package/dist/docs/catalog.js +0 -227
  200. package/dist/docs/index.d.ts +0 -10
  201. package/dist/docs/index.js +0 -10
  202. package/dist/environment.d.ts +0 -1
  203. package/dist/environment.js +0 -8
  204. package/dist/interfaces/index.d.ts +0 -311
  205. package/dist/interfaces/index.js +0 -9
  206. package/dist/keys/index.d.ts +0 -1
  207. package/dist/keys/index.js +0 -8
  208. package/dist/mutators/RecordingMutation.d.ts +0 -36
  209. package/dist/mutators/RecordingMutation.js +0 -182
  210. package/dist/mutators/Transaction.d.ts +0 -40
  211. package/dist/mutators/Transaction.js +0 -58
  212. package/dist/mutators/UndoManager.d.ts +0 -258
  213. package/dist/mutators/UndoManager.js +0 -658
  214. package/dist/mutators/defineMutators.d.ts +0 -60
  215. package/dist/mutators/defineMutators.js +0 -18
  216. package/dist/mutators/inverseOp.d.ts +0 -126
  217. package/dist/mutators/inverseOp.js +0 -71
  218. package/dist/mutators/mutateActions.d.ts +0 -45
  219. package/dist/mutators/mutateActions.js +0 -105
  220. package/dist/mutators/readerActions.d.ts +0 -33
  221. package/dist/mutators/readerActions.js +0 -57
  222. package/dist/mutators/undoApply.d.ts +0 -51
  223. package/dist/mutators/undoApply.js +0 -117
  224. package/dist/policy/index.d.ts +0 -21
  225. package/dist/policy/index.js +0 -20
  226. package/dist/query/client.d.ts +0 -61
  227. package/dist/query/client.js +0 -137
  228. package/dist/query/types.d.ts +0 -85
  229. package/dist/query/types.js +0 -16
  230. package/dist/react/AbloProvider.d.ts +0 -230
  231. package/dist/react/AbloProvider.js +0 -455
  232. package/dist/react/ClientSideSuspense.d.ts +0 -36
  233. package/dist/react/ClientSideSuspense.js +0 -17
  234. package/dist/react/DefaultFallback.d.ts +0 -24
  235. package/dist/react/DefaultFallback.js +0 -43
  236. package/dist/react/context.d.ts +0 -55
  237. package/dist/react/context.js +0 -29
  238. package/dist/react/index.d.ts +0 -61
  239. package/dist/react/index.js +0 -66
  240. package/dist/react/internalContext.d.ts +0 -33
  241. package/dist/react/internalContext.js +0 -3
  242. package/dist/react/useAblo.d.ts +0 -75
  243. package/dist/react/useAblo.js +0 -102
  244. package/dist/react/useCurrentUserId.d.ts +0 -22
  245. package/dist/react/useCurrentUserId.js +0 -34
  246. package/dist/react/useErrorListener.d.ts +0 -20
  247. package/dist/react/useErrorListener.js +0 -38
  248. package/dist/react/useMutationFailureListener.d.ts +0 -26
  249. package/dist/react/useMutationFailureListener.js +0 -38
  250. package/dist/react/useMutators.d.ts +0 -56
  251. package/dist/react/useMutators.js +0 -84
  252. package/dist/react/useReactive.d.ts +0 -35
  253. package/dist/react/useReactive.js +0 -123
  254. package/dist/react/useSyncStatus.d.ts +0 -59
  255. package/dist/react/useSyncStatus.js +0 -76
  256. package/dist/react/useUndoScope.d.ts +0 -34
  257. package/dist/react/useUndoScope.js +0 -81
  258. package/dist/schema/coordination.d.ts +0 -112
  259. package/dist/schema/coordination.js +0 -129
  260. package/dist/schema/ddl.d.ts +0 -97
  261. package/dist/schema/ddl.js +0 -491
  262. package/dist/schema/ddlLock.d.ts +0 -35
  263. package/dist/schema/ddlLock.js +0 -46
  264. package/dist/schema/diff.d.ts +0 -225
  265. package/dist/schema/diff.js +0 -289
  266. package/dist/schema/generate.d.ts +0 -19
  267. package/dist/schema/generate.js +0 -86
  268. package/dist/schema/index.d.ts +0 -41
  269. package/dist/schema/index.js +0 -76
  270. package/dist/schema/queries.d.ts +0 -201
  271. package/dist/schema/queries.js +0 -144
  272. package/dist/schema/select.d.ts +0 -40
  273. package/dist/schema/select.js +0 -87
  274. package/dist/schema/serialize.d.ts +0 -115
  275. package/dist/schema/serialize.js +0 -262
  276. package/dist/schema/sugar.d.ts +0 -109
  277. package/dist/schema/sugar.js +0 -83
  278. package/dist/schema/syncDeltaRow.d.ts +0 -6
  279. package/dist/schema/syncDeltaRow.js +0 -6
  280. package/dist/server/adapter.d.ts +0 -173
  281. package/dist/server/adapter.js +0 -18
  282. package/dist/server/commit.d.ts +0 -107
  283. package/dist/server/commit.js +0 -1
  284. package/dist/server/index.d.ts +0 -14
  285. package/dist/server/index.js +0 -2
  286. package/dist/server/readConfig.d.ts +0 -80
  287. package/dist/server/readConfig.js +0 -8
  288. package/dist/server/storageMode.d.ts +0 -23
  289. package/dist/server/storageMode.js +0 -17
  290. package/dist/source/adapter.d.ts +0 -81
  291. package/dist/source/adapter.js +0 -22
  292. package/dist/source/adapters/drizzle.d.ts +0 -48
  293. package/dist/source/adapters/drizzle.js +0 -219
  294. package/dist/source/adapters/kysely.d.ts +0 -42
  295. package/dist/source/adapters/kysely.js +0 -205
  296. package/dist/source/adapters/kyselyMutationCore.d.ts +0 -76
  297. package/dist/source/adapters/kyselyMutationCore.js +0 -125
  298. package/dist/source/adapters/memory.d.ts +0 -13
  299. package/dist/source/adapters/memory.js +0 -130
  300. package/dist/source/adapters/prisma.d.ts +0 -63
  301. package/dist/source/adapters/prisma.js +0 -202
  302. package/dist/source/conformance.d.ts +0 -37
  303. package/dist/source/conformance.js +0 -215
  304. package/dist/source/connector.d.ts +0 -95
  305. package/dist/source/connector.js +0 -266
  306. package/dist/source/connectorProtocol.d.ts +0 -154
  307. package/dist/source/connectorProtocol.js +0 -163
  308. package/dist/source/contract.d.ts +0 -195
  309. package/dist/source/contract.js +0 -164
  310. package/dist/source/factory.d.ts +0 -92
  311. package/dist/source/factory.js +0 -286
  312. package/dist/source/footprint.d.ts +0 -111
  313. package/dist/source/footprint.js +0 -0
  314. package/dist/source/idempotency.d.ts +0 -61
  315. package/dist/source/idempotency.js +0 -144
  316. package/dist/source/index.d.ts +0 -23
  317. package/dist/source/index.js +0 -30
  318. package/dist/source/migrations.d.ts +0 -21
  319. package/dist/source/migrations.js +0 -103
  320. package/dist/source/next.d.ts +0 -32
  321. package/dist/source/next.js +0 -25
  322. package/dist/source/pushQueue.d.ts +0 -134
  323. package/dist/source/pushQueue.js +0 -256
  324. package/dist/source/signing.d.ts +0 -92
  325. package/dist/source/signing.js +0 -162
  326. package/dist/source/types.d.ts +0 -401
  327. package/dist/source/types.js +0 -59
  328. package/dist/stores/ObjectStore.d.ts +0 -115
  329. package/dist/stores/ObjectStore.js +0 -393
  330. package/dist/stores/ObjectStoreContract.d.ts +0 -38
  331. package/dist/stores/ObjectStoreContract.js +0 -1
  332. package/dist/stores/SyncActionStore.d.ts +0 -97
  333. package/dist/stores/SyncActionStore.js +0 -504
  334. package/dist/stores/syncAction.d.ts +0 -26
  335. package/dist/stores/syncAction.js +0 -16
  336. package/dist/surface.d.ts +0 -36
  337. package/dist/surface.js +0 -75
  338. package/dist/sync/BootstrapFetcher.d.ts +0 -280
  339. package/dist/sync/BootstrapFetcher.js +0 -962
  340. package/dist/sync/ConnectionManager.d.ts +0 -8
  341. package/dist/sync/ConnectionManager.js +0 -8
  342. package/dist/sync/OnDemandLoader.d.ts +0 -228
  343. package/dist/sync/OnDemandLoader.js +0 -742
  344. package/dist/sync/SubscriptionManager.d.ts +0 -159
  345. package/dist/sync/SubscriptionManager.js +0 -243
  346. package/dist/sync/SyncWebSocket.d.ts +0 -173
  347. package/dist/sync/SyncWebSocket.js +0 -438
  348. package/dist/sync/awaitClaimGrant.d.ts +0 -6
  349. package/dist/sync/awaitClaimGrant.js +0 -6
  350. package/dist/sync/bootstrapApply.d.ts +0 -70
  351. package/dist/sync/bootstrapApply.js +0 -73
  352. package/dist/sync/commitFrames.d.ts +0 -8
  353. package/dist/sync/commitFrames.js +0 -8
  354. package/dist/sync/contextPorts.d.ts +0 -18
  355. package/dist/sync/contextPorts.js +0 -31
  356. package/dist/sync/createClaimStream.d.ts +0 -7
  357. package/dist/sync/createClaimStream.js +0 -7
  358. package/dist/sync/createPresenceStream.d.ts +0 -69
  359. package/dist/sync/createPresenceStream.js +0 -200
  360. package/dist/sync/createSnapshot.d.ts +0 -29
  361. package/dist/sync/createSnapshot.js +0 -118
  362. package/dist/sync/credentialLifecycle.d.ts +0 -7
  363. package/dist/sync/credentialLifecycle.js +0 -7
  364. package/dist/sync/deltaPipeline.d.ts +0 -113
  365. package/dist/sync/deltaPipeline.js +0 -261
  366. package/dist/sync/groupChange.d.ts +0 -113
  367. package/dist/sync/groupChange.js +0 -242
  368. package/dist/sync/participants.d.ts +0 -115
  369. package/dist/sync/participants.js +0 -344
  370. package/dist/sync/persistedPrefix.d.ts +0 -12
  371. package/dist/sync/persistedPrefix.js +0 -22
  372. package/dist/sync/schemaDrift.d.ts +0 -55
  373. package/dist/sync/schemaDrift.js +0 -53
  374. package/dist/sync/schemas.d.ts +0 -70
  375. package/dist/sync/schemas.js +0 -94
  376. package/dist/sync/syncCursor.d.ts +0 -40
  377. package/dist/sync/syncCursor.js +0 -55
  378. package/dist/sync/syncPlan.d.ts +0 -54
  379. package/dist/sync/syncPlan.js +0 -50
  380. package/dist/sync/wsFrameHandlers.d.ts +0 -8
  381. package/dist/sync/wsFrameHandlers.js +0 -8
  382. package/dist/testing/fixtures/bootstrap.d.ts +0 -49
  383. package/dist/testing/fixtures/bootstrap.js +0 -59
  384. package/dist/testing/fixtures/deltas.d.ts +0 -83
  385. package/dist/testing/fixtures/deltas.js +0 -136
  386. package/dist/testing/fixtures/httpResponses.d.ts +0 -70
  387. package/dist/testing/fixtures/httpResponses.js +0 -90
  388. package/dist/testing/fixtures/models.d.ts +0 -83
  389. package/dist/testing/fixtures/models.js +0 -272
  390. package/dist/testing/helpers/reactWrapper.d.ts +0 -69
  391. package/dist/testing/helpers/reactWrapper.js +0 -67
  392. package/dist/testing/helpers/syncEngineHarness.d.ts +0 -54
  393. package/dist/testing/helpers/syncEngineHarness.js +0 -73
  394. package/dist/testing/helpers/wait.d.ts +0 -30
  395. package/dist/testing/helpers/wait.js +0 -49
  396. package/dist/testing/index.d.ts +0 -23
  397. package/dist/testing/index.js +0 -33
  398. package/dist/testing/mocks/FakeDatabase.d.ts +0 -18
  399. package/dist/testing/mocks/FakeDatabase.js +0 -10
  400. package/dist/testing/mocks/MockMutationExecutor.d.ts +0 -87
  401. package/dist/testing/mocks/MockMutationExecutor.js +0 -186
  402. package/dist/testing/mocks/MockNetworkMonitor.d.ts +0 -20
  403. package/dist/testing/mocks/MockNetworkMonitor.js +0 -46
  404. package/dist/testing/mocks/MockSyncContext.d.ts +0 -51
  405. package/dist/testing/mocks/MockSyncContext.js +0 -72
  406. package/dist/testing/mocks/MockSyncStore.d.ts +0 -88
  407. package/dist/testing/mocks/MockSyncStore.js +0 -171
  408. package/dist/testing/mocks/MockWebSocket.d.ts +0 -71
  409. package/dist/testing/mocks/MockWebSocket.js +0 -118
  410. package/dist/transaction/ablo.d.ts +0 -88
  411. package/dist/transaction/ablo.js +0 -33
  412. package/dist/transaction/auth/apiKey.d.ts +0 -152
  413. package/dist/transaction/auth/apiKey.js +0 -419
  414. package/dist/transaction/auth/bootstrapScope.d.ts +0 -15
  415. package/dist/transaction/auth/bootstrapScope.js +0 -1
  416. package/dist/transaction/auth/capability.d.ts +0 -177
  417. package/dist/transaction/auth/capability.js +0 -199
  418. package/dist/transaction/auth/credentialEndpoint.d.ts +0 -61
  419. package/dist/transaction/auth/credentialEndpoint.js +0 -86
  420. package/dist/transaction/auth/credentialPolicy.d.ts +0 -148
  421. package/dist/transaction/auth/credentialPolicy.js +0 -125
  422. package/dist/transaction/auth/credentialSource.d.ts +0 -30
  423. package/dist/transaction/auth/credentialSource.js +0 -55
  424. package/dist/transaction/auth/hostedEndpoints.d.ts +0 -21
  425. package/dist/transaction/auth/hostedEndpoints.js +0 -21
  426. package/dist/transaction/auth/identity.d.ts +0 -55
  427. package/dist/transaction/auth/identity.js +0 -210
  428. package/dist/transaction/auth/index.d.ts +0 -162
  429. package/dist/transaction/auth/index.js +0 -304
  430. package/dist/transaction/auth/schemas.d.ts +0 -59
  431. package/dist/transaction/auth/schemas.js +0 -85
  432. package/dist/transaction/auth/sessionMint.d.ts +0 -28
  433. package/dist/transaction/auth/sessionMint.js +0 -85
  434. package/dist/transaction/coordination/awaitClaimGrant.d.ts +0 -49
  435. package/dist/transaction/coordination/awaitClaimGrant.js +0 -112
  436. package/dist/transaction/coordination/claimHeartbeatLoop.d.ts +0 -50
  437. package/dist/transaction/coordination/claimHeartbeatLoop.js +0 -88
  438. package/dist/transaction/coordination/claimMeta.d.ts +0 -49
  439. package/dist/transaction/coordination/claimMeta.js +0 -52
  440. package/dist/transaction/coordination/createClaimStream.d.ts +0 -64
  441. package/dist/transaction/coordination/createClaimStream.js +0 -475
  442. package/dist/transaction/coordination/events.d.ts +0 -74
  443. package/dist/transaction/coordination/events.js +0 -7
  444. package/dist/transaction/coordination/index.d.ts +0 -19
  445. package/dist/transaction/coordination/index.js +0 -44
  446. package/dist/transaction/coordination/locator.d.ts +0 -83
  447. package/dist/transaction/coordination/locator.js +0 -82
  448. package/dist/transaction/coordination/schema.d.ts +0 -1473
  449. package/dist/transaction/coordination/schema.js +0 -1013
  450. package/dist/transaction/coordination/targetConflict.d.ts +0 -2
  451. package/dist/transaction/coordination/targetConflict.js +0 -103
  452. package/dist/transaction/coordination/trace.d.ts +0 -78
  453. package/dist/transaction/coordination/trace.js +0 -138
  454. package/dist/transaction/durableWrites.d.ts +0 -62
  455. package/dist/transaction/durableWrites.js +0 -71
  456. package/dist/transaction/environment.d.ts +0 -105
  457. package/dist/transaction/environment.js +0 -108
  458. package/dist/transaction/errorCodes.d.ts +0 -403
  459. package/dist/transaction/errorCodes.js +0 -480
  460. package/dist/transaction/errors.d.ts +0 -428
  461. package/dist/transaction/errors.js +0 -686
  462. package/dist/transaction/index.d.ts +0 -20
  463. package/dist/transaction/index.js +0 -20
  464. package/dist/transaction/keys/index.d.ts +0 -87
  465. package/dist/transaction/keys/index.js +0 -207
  466. package/dist/transaction/log/syncDeltaRow.d.ts +0 -158
  467. package/dist/transaction/log/syncDeltaRow.js +0 -95
  468. package/dist/transaction/logPosition.d.ts +0 -97
  469. package/dist/transaction/logPosition.js +0 -125
  470. package/dist/transaction/logger.d.ts +0 -16
  471. package/dist/transaction/logger.js +0 -7
  472. package/dist/transaction/observability.d.ts +0 -53
  473. package/dist/transaction/observability.js +0 -19
  474. package/dist/transaction/persistence.d.ts +0 -12
  475. package/dist/transaction/persistence.js +0 -11
  476. package/dist/transaction/plugin.d.ts +0 -192
  477. package/dist/transaction/plugin.js +0 -87
  478. package/dist/transaction/policy/types.d.ts +0 -217
  479. package/dist/transaction/policy/types.js +0 -126
  480. package/dist/transaction/resources/functionalUpdate.d.ts +0 -79
  481. package/dist/transaction/resources/functionalUpdate.js +0 -87
  482. package/dist/transaction/resources/httpResources.d.ts +0 -266
  483. package/dist/transaction/resources/httpResources.js +0 -7
  484. package/dist/transaction/resources/modelOperations.d.ts +0 -319
  485. package/dist/transaction/resources/modelOperations.js +0 -12
  486. package/dist/transaction/resources/mutationOptions.d.ts +0 -66
  487. package/dist/transaction/resources/mutationOptions.js +0 -9
  488. package/dist/transaction/resources/where.d.ts +0 -85
  489. package/dist/transaction/resources/where.js +0 -70
  490. package/dist/transaction/resources/writeOptionsSchema.d.ts +0 -47
  491. package/dist/transaction/resources/writeOptionsSchema.js +0 -73
  492. package/dist/transaction/schema/field.d.ts +0 -126
  493. package/dist/transaction/schema/field.js +0 -265
  494. package/dist/transaction/schema/loadStrategy.d.ts +0 -45
  495. package/dist/transaction/schema/loadStrategy.js +0 -46
  496. package/dist/transaction/schema/model.d.ts +0 -379
  497. package/dist/transaction/schema/model.js +0 -123
  498. package/dist/transaction/schema/openapi.d.ts +0 -57
  499. package/dist/transaction/schema/openapi.js +0 -340
  500. package/dist/transaction/schema/relation.d.ts +0 -199
  501. package/dist/transaction/schema/relation.js +0 -104
  502. package/dist/transaction/schema/residency.d.ts +0 -29
  503. package/dist/transaction/schema/residency.js +0 -25
  504. package/dist/transaction/schema/roles.d.ts +0 -249
  505. package/dist/transaction/schema/roles.js +0 -230
  506. package/dist/transaction/schema/schema.d.ts +0 -324
  507. package/dist/transaction/schema/schema.js +0 -305
  508. package/dist/transaction/schema/tenancy.d.ts +0 -139
  509. package/dist/transaction/schema/tenancy.js +0 -190
  510. package/dist/transaction/transactionLayer.d.ts +0 -82
  511. package/dist/transaction/transactionLayer.js +0 -24
  512. package/dist/transaction/transactions/settlement/commitEnvelope.d.ts +0 -143
  513. package/dist/transaction/transactions/settlement/commitEnvelope.js +0 -161
  514. package/dist/transaction/transactions/settlement/httpCommitEnvelope.d.ts +0 -53
  515. package/dist/transaction/transactions/settlement/httpCommitEnvelope.js +0 -207
  516. package/dist/transaction/transactions/settlement/idempotencyKey.d.ts +0 -10
  517. package/dist/transaction/transactions/settlement/idempotencyKey.js +0 -9
  518. package/dist/transaction/transactions/settlement/pendingWrite.d.ts +0 -112
  519. package/dist/transaction/transactions/settlement/pendingWrite.js +0 -20
  520. package/dist/transaction/transport/commitFrames.d.ts +0 -90
  521. package/dist/transaction/transport/commitFrames.js +0 -134
  522. package/dist/transaction/transport/connectionManager.d.ts +0 -215
  523. package/dist/transaction/transport/connectionManager.js +0 -673
  524. package/dist/transaction/transport/credentialLifecycle.d.ts +0 -177
  525. package/dist/transaction/transport/credentialLifecycle.js +0 -324
  526. package/dist/transaction/transport/heartbeat.d.ts +0 -65
  527. package/dist/transaction/transport/heartbeat.js +0 -93
  528. package/dist/transaction/transport/httpClient.d.ts +0 -123
  529. package/dist/transaction/transport/httpClient.js +0 -145
  530. package/dist/transaction/transport/httpOptions.d.ts +0 -33
  531. package/dist/transaction/transport/httpOptions.js +0 -12
  532. package/dist/transaction/transport/httpTransport.d.ts +0 -8
  533. package/dist/transaction/transport/httpTransport.js +0 -1276
  534. package/dist/transaction/transport/networkProbe.d.ts +0 -84
  535. package/dist/transaction/transport/networkProbe.js +0 -207
  536. package/dist/transaction/transport/wsFrameHandlers.d.ts +0 -128
  537. package/dist/transaction/transport/wsFrameHandlers.js +0 -429
  538. package/dist/transaction/transport/wsTransport.d.ts +0 -576
  539. package/dist/transaction/transport/wsTransport.js +0 -1017
  540. package/dist/transaction/types/assertExact.d.ts +0 -17
  541. package/dist/transaction/types/assertExact.js +0 -1
  542. package/dist/transaction/types/global.d.ts +0 -107
  543. package/dist/transaction/types/global.js +0 -40
  544. package/dist/transaction/types/index.d.ts +0 -205
  545. package/dist/transaction/types/index.js +0 -56
  546. package/dist/transaction/types/modelData.d.ts +0 -10
  547. package/dist/transaction/types/modelData.js +0 -9
  548. package/dist/transaction/types/participant.d.ts +0 -20
  549. package/dist/transaction/types/participant.js +0 -10
  550. package/dist/transaction/types/streams.d.ts +0 -540
  551. package/dist/transaction/types/streams.js +0 -11
  552. package/dist/transaction/utils/asyncIterator.d.ts +0 -34
  553. package/dist/transaction/utils/asyncIterator.js +0 -135
  554. package/dist/transaction/utils/duration.d.ts +0 -25
  555. package/dist/transaction/utils/duration.js +0 -45
  556. package/dist/transaction/utils/json.d.ts +0 -57
  557. package/dist/transaction/utils/json.js +0 -276
  558. package/dist/transaction/wire/accountResponses.d.ts +0 -351
  559. package/dist/transaction/wire/accountResponses.js +0 -255
  560. package/dist/transaction/wire/auth.d.ts +0 -49
  561. package/dist/transaction/wire/auth.js +0 -57
  562. package/dist/transaction/wire/bootstrapReason.d.ts +0 -9
  563. package/dist/transaction/wire/bootstrapReason.js +0 -8
  564. package/dist/transaction/wire/claimEvent.d.ts +0 -76
  565. package/dist/transaction/wire/claimEvent.js +0 -73
  566. package/dist/transaction/wire/claims.d.ts +0 -463
  567. package/dist/transaction/wire/claims.js +0 -229
  568. package/dist/transaction/wire/commit.d.ts +0 -603
  569. package/dist/transaction/wire/commit.js +0 -321
  570. package/dist/transaction/wire/delta.d.ts +0 -250
  571. package/dist/transaction/wire/delta.js +0 -147
  572. package/dist/transaction/wire/errorEnvelope.d.ts +0 -72
  573. package/dist/transaction/wire/errorEnvelope.js +0 -123
  574. package/dist/transaction/wire/feedCursor.d.ts +0 -60
  575. package/dist/transaction/wire/feedCursor.js +0 -82
  576. package/dist/transaction/wire/feedEvent.d.ts +0 -177
  577. package/dist/transaction/wire/feedEvent.js +0 -39
  578. package/dist/transaction/wire/frames.d.ts +0 -194
  579. package/dist/transaction/wire/frames.js +0 -50
  580. package/dist/transaction/wire/inboundFrames.d.ts +0 -552
  581. package/dist/transaction/wire/inboundFrames.js +0 -116
  582. package/dist/transaction/wire/index.d.ts +0 -50
  583. package/dist/transaction/wire/index.js +0 -74
  584. package/dist/transaction/wire/listEnvelope.d.ts +0 -37
  585. package/dist/transaction/wire/listEnvelope.js +0 -42
  586. package/dist/transaction/wire/modelResponses.d.ts +0 -85
  587. package/dist/transaction/wire/modelResponses.js +0 -43
  588. package/dist/transaction/wire/protocol.d.ts +0 -38
  589. package/dist/transaction/wire/protocol.js +0 -38
  590. package/dist/transaction/wire/protocolVersion.d.ts +0 -73
  591. package/dist/transaction/wire/protocolVersion.js +0 -83
  592. package/dist/transactions/mutations/MutationQueue.d.ts +0 -655
  593. package/dist/transactions/mutations/MutationQueue.js +0 -2797
  594. package/dist/transactions/mutations/MutationStore.d.ts +0 -20
  595. package/dist/transactions/mutations/MutationStore.js +0 -53
  596. package/dist/transactions/mutations/UnconfirmedWrites.d.ts +0 -82
  597. package/dist/transactions/mutations/UnconfirmedWrites.js +0 -104
  598. package/dist/transactions/mutations/coalesceRules.d.ts +0 -58
  599. package/dist/transactions/mutations/coalesceRules.js +0 -140
  600. package/dist/transactions/mutations/commitLatency.d.ts +0 -52
  601. package/dist/transactions/mutations/commitLatency.js +0 -130
  602. package/dist/transactions/mutations/commitOutboxStore.d.ts +0 -28
  603. package/dist/transactions/mutations/commitOutboxStore.js +0 -26
  604. package/dist/transactions/mutations/commitPayload.d.ts +0 -164
  605. package/dist/transactions/mutations/commitPayload.js +0 -152
  606. package/dist/transactions/mutations/deltaConfirmation.d.ts +0 -59
  607. package/dist/transactions/mutations/deltaConfirmation.js +0 -233
  608. package/dist/transactions/mutations/durableWriteStore.d.ts +0 -14
  609. package/dist/transactions/mutations/durableWriteStore.js +0 -12
  610. package/dist/transactions/mutations/optimisticApply.d.ts +0 -49
  611. package/dist/transactions/mutations/optimisticApply.js +0 -65
  612. package/dist/transactions/mutations/replayValidation.d.ts +0 -186
  613. package/dist/transactions/mutations/replayValidation.js +0 -163
  614. package/dist/utils/mobxSetup.d.ts +0 -53
  615. package/dist/utils/mobxSetup.js +0 -330
  616. package/dist/webhooks/events.d.ts +0 -43
  617. package/dist/webhooks/events.js +0 -42
  618. package/dist/webhooks/index.d.ts +0 -8
  619. package/dist/webhooks/index.js +0 -8
  620. package/dist/wire/index.d.ts +0 -1
  621. package/dist/wire/index.js +0 -8
  622. package/docs/interaction-model.md +0 -99
package/CHANGELOG.md CHANGED
@@ -1,1954 +1,96 @@
1
1
  # Changelog
2
2
 
3
- ## 0.35.0
3
+ ## 0.37.0
4
4
 
5
5
  ### Minor Changes
6
6
 
7
- - 6be1d99: The `@abloatai/ablo/agent` entry point is removed, along with the single name it exported, `Agent`.
7
+ - f60ed16: Harden browser authentication around one typed credential endpoint contract,
8
+ full-plane persistence isolation, awaited terminal cleanup, actual credential
9
+ expiry, and least-privilege human sessions.
8
10
 
9
- There is no separate agent SDK, and in practice there has not been one for some time: an agent is
10
- the same client with a different transport. `Ablo({ schema, apiKey, transport: 'http' })` returns a
11
- stateless, request/response client whose credential is its identity, carrying the same typed
12
- `ablo.<model>` surface a browser or a server action uses, minus the parts that need a socket — the
13
- `local` reads and `onChange`, which are absent from the type rather than failing at runtime. A
14
- second entry point offered a second way in and, mostly, a second thing to keep in step with the
15
- first. If you imported from `@abloatai/ablo/agent`, import from `@abloatai/ablo` and select the
16
- transport.
11
+ Human session minting now requires a non-empty schema-typed `can` grant. It
12
+ accepts concrete model operations and has no all-data wildcard. Browser
13
+ credentials remain short-lived, refreshable, and isolated from long-lived
14
+ server secrets.
17
15
 
18
- `ClaimEvent.participantKind` is also removed. A claim event is one transition in a lease's
19
- lifecycle acquired, queued, granted, lost, rejected, expired — and it identifies the actor that
20
- moved it. Whether that actor was a person, an agent, or the system is a property of the holder, not
21
- of the transition, and stating it on every event meant maintaining the same fact in two shapes. Read
22
- it where the holder is described instead: `ablo.<model>.claim.state({ id })` returns the current
23
- holder with its `participantKind`, which is what the "an agent is editing this" affordance in a UI
24
- has always read.
16
+ Endpoint URLs move from `apiKey` to `authEndpoint`. The canonical session and
17
+ capability mint routes are `/v1/ephemeral_keys` and `/v1/capabilities`; legacy
18
+ route aliases are removed.
25
19
 
26
- - 1b12959: The client is now the coordination layer, and the things built on top of it are
27
- capabilities you install rather than parts of the client you get whether you use
28
- them or not.
20
+ Credential providers now use the `CredentialProvider` type. The former
21
+ `ApiKeySetter` export is removed.
29
22
 
30
- ```ts
31
- import { Ablo, humans } from '@abloatai/ablo';
23
+ - 16cc7d1: Claims are field-granular. The `path` and `range` claim targets are removed, and
24
+ a claim narrows to a whole field or set of fields and no finer. Two agents on the
25
+ same row proceed concurrently when they hold different fields, and serialize when
26
+ they share one.
32
27
 
33
- const ablo = Ablo({ schema, apiKey, plugins: [humans()] });
34
- ```
28
+ This is a breaking removal (the `path` and `range` claim options, the
29
+ `TargetRange` type, and sub-field conflict semantics are gone) and a deliberate
30
+ one. A claim must not promise finer exclusion than the write path can deliver,
31
+ and the smallest thing a write addresses is a whole field: nothing writes part of
32
+ a value. `path`/`range` let two writers hold disjoint spans of one field and told
33
+ them it was safe, which it is not until concurrent edits to one field can be
34
+ reconciled (operational transformation). Until that lands, field is the floor;
35
+ when it lands, sub-field targets return, working.
35
36
 
36
- `humans()` declares the local, watchable copy: the offline store, live queries,
37
- presence, and the framework bindings everything that exists because a person
38
- is looking at the screen. What remains underneath is what coordination actually
39
- needs: commit, read, observe, claim. Omit `plugins` and `humans()` is installed
40
- for you, so an application that renders anything today keeps working with no
41
- change; naming it in the list only makes visible what was already true.
37
+ If you narrowed a claim by `path` or `range`, claim the field the position lives
38
+ in instead: `fields: ['content']`. To describe a sub-field region to peers for
39
+ display, put it in `meta` that promises nothing about exclusion.
42
40
 
43
- There is no `agents()` counterpart, and the absence is the point. An agent is
44
- not a special caller that needs its own mode — it is the ordinary one, and what
45
- it needs is the client without the materialiser. That is the same observation
46
- behind removing the `/agent` entry point in this release: there was never a
47
- second SDK to enter, only a client with a transport that does not open a socket.
48
- `Ablo({ schema, apiKey, transport: 'http' })` is the whole of it, and because
49
- `humans()` requires a duplex connection, asking for it on that client fails while
50
- the client is being constructed, with an error naming the plugin, rather than
51
- resolving to a presence stream that can never receive a frame. A plugin listed
52
- twice fails the same way.
41
+ - 08a3cad: Launch the branded Ablo package as the single package application developers
42
+ install. The root serves headless HTTP callers, while `/client` and `/react`
43
+ serve WebSocket-backed reactive applications.
53
44
 
54
- The rearrangement is also why the reactive `ablo.<model>` surface composes from
55
- the transport-independent one rather than restating it — the note on the renamed
56
- local reads describes what that buys — and why `track`, a durable premise that
57
- keeps no local copy, sits in the core where an agent can reach it without
58
- installing a materialiser it has no use for.
45
+ The public surface now provides:
59
46
 
60
- This is a seam, not a finished migration. `humans()` today constructs the
61
- presence stream and declares the constraint that keeps it off a stateless
62
- client; the rest of the materialiser — the store, the bootstrap, the delta
63
- pipeline, the framework bindings still lives in the composition root and moves
64
- behind the same door as the plugin context grows to carry what each piece needs.
65
- The contract those pieces will arrive through is the one published here, so a
66
- capability written against it now keeps working as they do.
47
+ - `@abloatai/ablo` for agents, services, workers, jobs, and backend code;
48
+ - `@abloatai/ablo/client` for live local state;
49
+ - `@abloatai/ablo/react` for React bindings;
50
+ - branded schema, source-adapter, server, authorization, coordination, and wire
51
+ subpaths; and
52
+ - `/source/next`, `/source/drizzle`, `/source/kysely`, and
53
+ `/source/conformance` for Data Source integrations.
67
54
 
68
- - 6be1d99: The shape of a claim's `meta` is now declared once, on `Register`, beside your
69
- schema:
55
+ Authoritative reads use `model.get({ id })`; reactive snapshots use
56
+ `model.local.get(id)`.
70
57
 
71
- ```ts
72
- declare module '@abloatai/ablo' {
73
- interface Register {
74
- Schema: typeof schema;
75
- ClaimMeta: { blocks: string[] };
76
- }
77
- }
78
- ```
58
+ Ablo is now presented as the transaction and coordination API for state
59
+ operated by humans, services, tools, and AI agents. Realtime synchronization
60
+ remains available as a client capability rather than defining the product.
61
+ Live applications no longer accept HTTP transport configuration, and the old
62
+ sync-engine package and compatibility paths are removed.
79
63
 
80
- From then on `claim.state({ id })?.target.meta`, every entry in `claim.queue`,
81
- and the claim you hold under `await using` all read that shape, and the `typeof`
82
- guard each of those reads used to need is gone. The same declaration types what
83
- you write — `claim({ id, meta: { blocks } })` — so the writer and every reader
84
- agree by construction rather than by convention, which is the half a per-call
85
- type argument could never reach: naming the shape at the read site said nothing
86
- about what the write site was allowed to put there.
64
+ The public repository preserves the workspace structure used for development
65
+ instead of flattening sources into a generated package. The branded banner,
66
+ documentation, examples, license, notices, and release automation remain part
67
+ of the repository.
87
68
 
88
- Prefer the registration to the per-call `claim.state<MyMeta>({ id })`, because
89
- one declaration is one place to change when the metadata grows a field. The
90
- type argument remains supported and is still the honest answer for a program
91
- carrying more than one metadata shape; only its default moved, from a bare
92
- record to whatever you registered. A program that registers nothing sees no
93
- change at all — `target.meta` stays the loose record it has always been.
69
+ The new package pages direct application developers to the branded Ablo
70
+ entrypoints while still documenting where integration authors can find the
71
+ lower-level transaction and interactive-client contracts.
94
72
 
95
- What travels between participants is untouched. Claim metadata is carried
96
- verbatim and never interpreted, and the protocol keeps accepting any object it
97
- is given, so a peer on a newer build can send a field this one has never heard
98
- of and it still arrives intact. The declaration is a promise about what your own
99
- program writes, not a filter on what it receives.
73
+ - f60ed16: Improve confirmed commit throughput and live-client materialization without
74
+ weakening atomic writes, ordered observation, audit delivery, or replay.
100
75
 
101
- - e4b538b: A model's conflict stance can be written as a chain through one name.
76
+ The certified AWS benchmark sustained more than 10,000 committed operations per
77
+ second for homogeneous creates, mixed creates, updates, and deletes with zero
78
+ write errors and sub-second publication drain. The result covers the documented
79
+ single-plane, 12-client, 500-operation benchmark topology rather than claiming
80
+ universal production capacity.
102
81
 
103
- ```ts
104
- import { coordination, model, z } from '@abloatai/ablo/schema';
105
-
106
- export const cards = model(
107
- {
108
- title: z.string(),
109
- },
110
- {
111
- conflict: coordination.humansOverwrite().agentsReject(),
112
- }
113
- );
114
- ```
115
-
116
- The nine disposition rules were the only loose functions left in the schema DSL.
117
- Everything else there arrives under a noun that owns it — `relation.belongsTo`,
118
- `field.string`, `mutable.lazy` — but declaring that humans overwrite and agents
119
- yield meant importing three separate names to say one thing, and a schema with a
120
- couple of stances spent more of its import list on conflict rules than on the rest
121
- of the language combined.
122
-
123
- So the rules now hang off `coordination`, and each one returns an axis that is
124
- still open to the next, the way a field chains its own modifiers. The rules
125
- themselves are unchanged, and each remains the single place its disposition map is
126
- written; the chain delegates to the standalone function rather than restating it.
127
- Those standalone functions stay exported, and `coordination()` still merges them,
128
- which is the form to reach for when the rules are assembled at runtime rather than
129
- written out:
130
-
131
- ```ts
132
- const stance = coordination(humansOverwrite(), agentsReject());
133
- ```
134
-
135
- Both forms produce the same plain, serializable map keyed by the committer's
136
- participant kind — `{ user: 'overwrite', agent: 'reject' }` — and the engine reads
137
- it at the commit chokepoint exactly as before. A kind left unnamed still falls
138
- through to the engine default, and a kind named twice still takes the later rule.
139
-
140
- - 6be1d99: `captureTransactionFailure` on the observability provider is renamed to `captureMutationFailure`.
141
- If you pass an `observability` provider to `Ablo(...)`, rename that one method; its argument is
142
- unchanged.
143
-
144
- The rename frees a word that had been doing two jobs. A transaction, in this system, is a unit of
145
- write that settles — the thing you author, hand to the engine, and get a receipt for. But the same
146
- word also named the client's optimistic queue, the buffer of writes that have been applied locally
147
- and are still waiting for the server to agree. The callback belonged to the second meaning: it
148
- fires when a queued write has exhausted its attempts and will never land, so the local change is
149
- rolled back and something upstream needs to hear about it. Calling that a transaction failure
150
- invited the reading that a settlement had failed, which is a different event with a different
151
- remedy — and as the settlement layer grew a vocabulary of its own, the collision stopped being
152
- cosmetic.
153
-
154
- There is no compatibility alias, and the reason is worth stating, because it is the opposite of
155
- the usual one. `captureMutationFailure` is a required member of the provider interface. A provider
156
- still carrying the old spelling therefore does not satisfy that interface at all: the compiler
157
- stops the build and names the missing member, at the line where the provider is constructed. An
158
- optional alias would have made that build succeed while the engine went on calling a method the
159
- provider does not implement — quiet in exactly the way a renamed callback must not be. The
160
- compiler is the better messenger here, and this note is the rest of the message.
161
-
162
- - 6be1d99: `debug`, `logLevel`, and `persistence` are no longer accepted on the stateless HTTP client. They
163
- remain exactly where they have always worked — on the default client, over the socket.
164
-
165
- They were reachable on `Ablo({ transport: 'http' })` only because that client's options type was
166
- assembled from the whole composition bag rather than from the fields the transport actually reads,
167
- and nothing on the HTTP path ever read them. `persistence` configures a local store a stateless
168
- client does not keep. `debug` and `logLevel` turn on the `[Ablo]` coordination trace, which
169
- narrates presence, claim grants, and queueing — frames that arrive over a socket an HTTP client
170
- never opens. Passing any of the three was accepted and did nothing, which is the kind of option
171
- that costs a reader an afternoon before they conclude the feature is broken.
172
-
173
- If you passed one to an HTTP client, delete it: nothing changes, because nothing was happening.
174
- If what you wanted was the trace from a server-side worker, it is not a setting you were missing —
175
- a stateless agent holds no live session to narrate, and its writes are visible in the audit log and
176
- in `ablo logs` instead.
177
-
178
- - 6be1d99: A model's load strategy is now `'instant'` or `'lazy'`, and nothing else. The
179
- word `'manual'` is gone, along with the `mutable.manual(...)` helper that set
180
- it.
181
-
182
- The strategy had drifted into two declarations that never agreed. The authoring
183
- side, reached through `@abloatai/ablo/schema`, offered `'instant' | 'lazy' |
184
- 'manual'`. The runtime side, reached through `@abloatai/ablo/core` under the
185
- same name, offered `instant`, `lazy`, `partial`, `explicitlyRequested`, and
186
- `local`. Importing `LoadStrategy` from the two paths gave you two different
187
- types, and no compiler anywhere would tell you which one you had.
188
-
189
- Only `instant` and `lazy` were ever real. `'manual'` promised loading a model
190
- "only when you explicitly call `sync.model.load()`", a method that was never
191
- built; a model declared manual resolved to `lazy` and loaded on first access
192
- like any other. `partial` had live branches in the client but no way to declare
193
- it, and `explicitlyRequested` and `local` had neither. So the correction is
194
- mostly the removal of vocabulary that described behavior the engine did not
195
- have — a model that said `manual` was already behaving as `lazy`, and continues
196
- to, which is why this changes what your schema is allowed to say rather than
197
- what your program does.
198
-
199
- Replace `load: 'manual'` with `load: 'lazy'`, and `mutable.manual(...)` with
200
- `mutable.lazy(...)`. `readOnly.internal(...)` stays, and its guarantee is
201
- unchanged and worth restating plainly: it is a write boundary, not a read one.
202
- Clients cannot commit to an internal model, and the server rejects any attempt.
203
- Its rows still reach a client that reads the model.
204
-
205
- `LoadStrategy` now has one declaration behind both import paths. It reads as a
206
- value where the engine branches on it — `LoadStrategy.instant` — and as the
207
- plain string an author types in `model(…, { load: 'instant' })`, and the two
208
- can no longer name different sets.
209
-
210
- Two names join it on `@abloatai/ablo/schema`. `DEFAULT_LOAD_STRATEGY` is the
211
- strategy a model gets when it declares none, and `loadsAtBootstrap(load)`
212
- answers whether a model's rows arrive in the bootstrap payload or on first
213
- access. The predicate is exported because both halves of the system have to
214
- agree on it: the client builds its bootstrap subscription from the schema and
215
- the server assembles the payload from the same schema, and they had been
216
- asking the question in three different spellings — `load !== 'lazy'` in one
217
- place, `load === 'instant'` in another. Against two members those agree. A
218
- third member would have had one side enrolling it and the other withholding it,
219
- which surfaces as rows that never arrive, a long way from the line responsible.
220
-
221
- - The three synchronous reads on `ablo.<model>` are renamed. `get(id)`, `getAll(options)`,
222
- and `getCount(options)` become `local.retrieve(id)`, `local.list(options)`, and
223
- `local.count(options)`.
224
-
225
- The old names were a pair that carried no information. `get` and `retrieve` are
226
- synonyms in English, so a reader who found `ablo.tasks.get(id)` beside
227
- `ablo.tasks.retrieve({ id })` had no way to tell from the words which one waited
228
- on the network and which one did not — the difference had to be memorised, and
229
- it was memorised imperfectly. Two bugs reached review that way, both of them a
230
- synchronous read standing in for an asynchronous one against a graph that had
231
- not warmed up yet, both of them invisible in the diff because the line read
232
- exactly like the call it should have been.
233
-
234
- The new names say the thing out loud. Every verb is the verb it already was on
235
- the asynchronous side; the only addition anywhere in the design is the word
236
- `local`, and it is a narrowing rather than a claim about its sibling.
237
- `retrieve` consults the local graph first and falls back to the network;
238
- `local.retrieve` is restricted to what is already resident, which is also why it
239
- can hand back a value instead of a promise. There is nothing to await. Reading
240
- `ablo.tasks.local.list({ where })`, you know both what it does and what it costs
241
- without leaving the line.
242
-
243
- The reads themselves are unchanged — same options, same lifecycle `state`
244
- filter, same reactivity inside `useAblo` selectors and in render. Only the path
245
- to them moved, and it is the only path: the local reads are not also exposed at
246
- the top level, because a namespace that duplicates what sits beside it stops
247
- disambiguating anything.
248
-
249
- Underneath, the rename arrives with a structural change that will keep the
250
- surface honest from here on. The reactive `ablo.<model>` type was hand-written as
251
- a second list that happened to agree with the transport-independent one, and
252
- nothing linked them: a verb added to one said nothing about the other. It is now
253
- composed from that base rather than restated, so a verb added there arrives on
254
- the reactive client on its own. `track` — registering a durable premise on a
255
- row — is the first to arrive that way.
256
-
257
- Migrating is mechanical, but reach for the compiler rather than a search. `getAll`
258
- and `getCount` are distinctive enough to find by name; `get` is not, and in a
259
- codebase of any size it hides among the `Map.get` and `headers.get` calls that a
260
- search cannot tell it from. Removing the old names from the type turns the
261
- question into a list of type errors, each one a site that genuinely needs to
262
- move.
263
-
264
- - 0002877: A capability is declared in the vocabulary you write, and every other spelling
265
- of it derives from that declaration.
266
-
267
- ```ts
268
- import { grantedOperations, modelWireNames } from '@abloatai/ablo/auth';
269
-
270
- const operations = grantedOperations(
271
- { documents: ['read', 'update'] },
272
- modelWireNames(schema.models)
273
- );
274
- ```
275
-
276
- The same grant used to be spelled five times: a literal union in the resource
277
- types, a `z.array(z.string())` on the wire, a field-by-field parser in the mint
278
- route, an object literal in the response type, and a hand-written `model.verb`
279
- array at every caller that mints without the SDK. Nothing failed when those
280
- drifted apart. The drift surfaced later as `capability_scope_denied` on a grant
281
- the caller was certain it held, at a call site with no view of the spelling that
282
- had gone stale.
283
-
284
- They are now one module on `@abloatai/ablo/auth`. `capabilityOperationSchema`
285
- holds the verb vocabulary; `grantedOperationSchema` derives the wire pattern
286
- `<model>.<verb>` from it as a template literal, so tightening the verbs cannot
287
- leave a stale regex or a stale published contract behind. `CapabilityCan<S>`
288
- narrows the declaration to one schema's models, so `can: { tasks: ['update'] }`
289
- fails to compile against a schema with no `tasks` model.
290
-
291
- `modelWireNames` is the derivation worth knowing about, because it is the one
292
- people got wrong by hand. A model whose type name is overridden — schema key
293
- `documents`, type name `Document` — must be granted as `document.update`, and a
294
- caller who works that out by eye learns otherwise at enforcement time. Pass your
295
- schema's models and the map is built for you. It is a required argument rather
296
- than an optional one: an omitted map would mint the schema key verbatim, which
297
- is right for most models and wrong for exactly the ones that need it.
298
-
299
- Two more derivations ride along. `expandReadYourWrites` appends `<model>.read`
300
- for every model the grant can write, because a scoped agent that may update a
301
- row has to be able to read it or the read gate starves the writes the grant
302
- allows — write verbs stay the source of truth, reads are derived and deduped,
303
- and a model the grant cannot write stays unreadable. And a grant is now checked
304
- against the schema **at the mint**: `capabilityModelAliases` accepts any of the
305
- three names one model answers to (type name, schema key, table name), and
306
- `unresolvableOperations` reports the ones that name nothing, so a typo is a
307
- rejected mint rather than a credential that looks healthy until its first write.
308
-
309
- ### Breaking
310
-
311
- `ExchangeApiKeyRequest.operations` narrows from `readonly string[]` to
312
- `readonly GrantedOperation[]`. If you assembled that array by hand, the compiler
313
- will now reject any entry that is not `<model>.<verb>` — including the `'*'`
314
- wildcard, which was never a member of the wire form and is no longer typable as
315
- one. Build the array with `grantedOperations(can, modelWireNames(schema.models))`
316
- instead of composing the strings; that is the same call the SDK makes, and it
317
- resolves the type-name override you would otherwise have to remember.
318
-
319
- - feb88a2: Three error codes carried the word "intent", left over from the vocabulary the
320
- coordination layer used before it settled on claims. The word had come to mean
321
- two unrelated things in the same registry, so both are now named for what they
322
- are.
323
-
324
- `invalid_intent` is removed. It was raised when a claim request failed to name
325
- the model and row it targeted — which is what `malformed_claim` already
326
- described, in the same words, for the same failure arriving over the WebSocket
327
- instead of over HTTP. The two have been merged into `malformed_claim`; a caller
328
- switching on `invalid_intent` should switch on `malformed_claim` instead, and
329
- the HTTP status is 400 either way.
330
-
331
- `slide_intent_missing_deck_id` and `slide_intent_unknown_sibling` are renamed to
332
- `slide_position_missing_deck_id` and `slide_position_unknown_sibling`. These were
333
- never about claims at all: they report that a slide created at a position stated
334
- relative to its neighbours — "before this one" — could not have that position
335
- resolved, because no deck was named or because the neighbour is not in it. An
336
- earlier rename had swept the word "claim" through their descriptions and left
337
- them reading as contention errors, so their messages are rewritten to say what
338
- actually went wrong.
339
-
340
- `malformed_claim` moves from the `claim` category to `validation`, alongside
341
- `malformed_subscription`. The code, its 400 status, and its meaning are
342
- unchanged — only the grouping it appears under in the error documentation. The
343
- `claim` category now holds only genuine contention, which is what its name
344
- promised.
345
-
346
- - e4b538b: `model()` now takes a shape and one options object. Relations, which used to hold a
347
- positional argument of their own in between, are the `relations` key on those options.
348
-
349
- ```ts
350
- documents: model(
351
- {
352
- title: z.string(),
353
- projectId: z.string(),
354
- },
355
- {
356
- relations: { project: relation.belongsTo('projects', 'projectId') },
357
- conflict: coordination(humansOverwrite(), agentsReject()),
358
- load: 'lazy',
359
- }
360
- ),
361
- ```
362
-
363
- The middle argument was the problem. Most models have no relations at all, yet nearly
364
- every model has something to say about itself — a table it maps to, a load strategy, a
365
- conflict disposition — and the only way to reach that third argument was to write the
366
- second one first. So the schema filled up with empty braces standing in for edges that
367
- did not exist. In this repository alone the placeholder appeared 104 times, which made
368
- `{}` the single most common thing anyone ever passed in that position: more often than
369
- any real relations object. A reader opening a schema met the empty braces before they
370
- met the model.
371
-
372
- Nothing about relations themselves changes. They are built with the same
373
- `relation.belongsTo` and `relation.hasMany` factories, they carry the same options, and
374
- the engine reads them exactly as before — to index foreign keys, to order inserts so a
375
- parent lands before its children, and to generate the accessors behind `task.project`.
376
- They have simply stopped being a position and become a name, which is what they always
377
- were to everything downstream.
378
-
379
- Migrating is mechanical, and the compiler finds every site. A call that passed an empty
380
- placeholder drops it:
381
-
382
- ```ts
383
- -model({ title: z.string() }, {}, { groups: { root: 'workspace' } }) +
384
- model({ title: z.string() }, { groups: { root: 'workspace' } });
385
- ```
386
-
387
- A call that passed real relations folds them in under their own key:
388
-
389
- ```ts
390
- - model({ title: z.string(), projectId: z.string() }, {
391
- - project: relation.belongsTo('projects', 'projectId'),
392
- - }, { load: 'lazy' })
393
- + model({ title: z.string(), projectId: z.string() }, {
394
- + relations: { project: relation.belongsTo('projects', 'projectId') },
395
- + load: 'lazy',
396
- + })
397
- ```
398
-
399
- A call that passed only fields is untouched.
400
-
401
- - e4b538b: `app.current_user_id` is now a reserved session setting, so a schema can no
402
- longer claim it — and a read that cannot be scoped to the person asking says so
403
- with `user_scope_not_enforced` instead of quietly returning rows.
404
-
405
- Ablo's direct-write connection already applied a fixed bundle of settings before
406
- your DML — the organization, the project, the environment, the sandbox, the
407
- acting participant — and `sessionSettings` let a schema forward any of those
408
- into a differently-named setting its own policies read. What a schema could
409
- never do was reassign one of the engine's own settings, because that would let a
410
- schema push relax the scoping under which Ablo writes. `RESERVED_SESSION_SETTINGS`
411
- is the list that prevents it, and the engine now sets one more: the acting user.
412
-
413
- The setting carries a person's id when a person is behind the write, and `'*'`
414
- when the request comes from a backend credential acting as the organization
415
- itself. Absent identity writes the empty string rather than leaving the previous
416
- value on a pooled connection, so a policy reading it denies rather than
417
- inheriting. Your own policies are welcome to read it; they simply cannot be the
418
- thing that decides what it contains.
419
-
420
- If your schema mapped `app.current_user_id` through `sessionSettings`, that entry
421
- is now dropped at authoring time and at runtime, and the engine's own value
422
- applies instead. Map a different name if your policies need a value you control.
423
-
424
- The new error code covers the one arrangement where that boundary cannot hold. A
425
- plane served from its retained log carries the organization and the project on
426
- every row but not the owner, so a rule that names a person has nothing to act on
427
- there. Rather than fold the rows and return a plausible answer, such a read is
428
- declined whole — a member reading a colleague's private records would otherwise
429
- be indistinguishable from a member reading their own. Reads made by a credential
430
- acting for the organization are unaffected, as is every plane served from its
431
- tables.
432
-
433
- All of this now has a page. The seam has been in the SDK since 0.32.0 and under
434
- this name since 0.33.0, documented both times only in a release note — which is
435
- a poor place to keep a feature rather than announce one:
436
- a reader with row-level-security policies had no way to discover that Ablo sets
437
- an identity context at all, let alone that they could point their own policies
438
- at it. **Session Settings** states what the engine sets before every write, which
439
- identities a mapping may name, which settings are reserved and why, and what
440
- happens on a plane served from its log. Read it with `npx ablo docs
441
- session-settings`, or on the site.
442
-
443
- - 5972a69: `causedByTaskId` is removed from `MutationOptions` — and so from the per-verb params that
444
- compose it, `ModelCreateParams`, `ModelUpdateParams`, and `ModelDeleteParams` — together with
445
- the seven `turn_*` error codes that existed to police it. It also leaves `CommitContext`, where
446
- the server carried it from the wire to the stored row. The field was never usable for its stated
447
- purpose: setting it was the one reliable way to have a write refused.
448
-
449
- It was the surviving half of an agent-turn protocol whose other half was never built. The idea
450
- had been that an agent would open a turn, write under it, and close it, so that an auditor could
451
- later ask what an agent did _because_ a person asked it to — a prompt at the root of a tree of
452
- changes. What actually shipped was only the check. Every commit that carried a `causedByTaskId`
453
- was validated against a task record, and nothing in the system has ever opened one, so the record
454
- was never there. A caller who supplied the field had the entire batch rejected with
455
- `turn_validation_failed`; a caller who left it null passed straight through. The prudent thing to
456
- do with a documented, public option was to never touch it, and the field spent its life as
457
- plumbing threaded through the wire protocol, the settlement envelope, the stored row, and the
458
- replication consumer, carrying null on every path.
459
-
460
- Nothing is lost by its removal, because write attribution never depended on it. A delta already
461
- records who made the change and on whose authority — the actor, the `onBehalfOf` principal behind
462
- a delegated write, the capability that authorized it, and the claim the write was made under. Those
463
- answer "who did this and by what right" without asking the caller to maintain a lifecycle the
464
- engine never opened on their behalf. If your code passed `causedByTaskId`, delete the argument;
465
- if it branched on `turn_validation_failed`, that branch was unreachable and can go with it.
466
-
467
- The removed codes are `turn_validation_failed`, `turn_open_failed`, `turn_close_failed`,
468
- `turn_not_found`, `turn_foreign_agent`, `parent_turn_not_found`, and `parent_turn_foreign_agent`.
469
- `ERROR_CONTRACT_VERSION` moves to `2026-07-19`, as it does whenever a code leaves the registry.
470
- The stored-row schema loses its provenance slice with them: `deltaProvenanceSchema` and the
471
- `DeltaProvenance` type are gone, and `syncDeltaRowSchema` is now the core and attribution slices
472
- composed, which is what it had always described in practice. On the wire the field was optional
473
- and nullable, so a client that still sends it is accepted and ignored, and a client reading deltas
474
- sees one fewer always-null key.
475
-
476
- The storage goes with it. The `caused_by_task_id` column leaves `sync_deltas`, `agent_actions_log`,
477
- and `source_write_intents`, and the two tables that were never written — `agent_tasks` and
478
- `agent_task_prompts` — are dropped along with the `agent_surface` enum that typed them. This looked
479
- at first like a separate versioned migration, because the audit chain hashes the lineage id into
480
- every row's signature and rewriting those signatures is not something a release should do quietly.
481
- It turned out not to need rewriting. Every row was hashed with a null in that position, so the
482
- canonical byte layout the stored hashes were computed against is preserved exactly by passing
483
- `NULL::text` where the column used to be read — which is what the migration does, in the append
484
- trigger and in the rechain function, before the column is dropped. The signature is untouched and
485
- every historical `row_hash` still verifies. The `packages/audit-chain` reproduction freezes the same
486
- position, so the two implementations cannot drift apart on it.
487
-
488
- - f0f4226: Eight names that were renamed in an earlier release, and have shipped since as
489
- aliases beside the names that replaced them, are removed.
490
-
491
- | Removed | Use |
492
- | ----------------------- | ------------------------ |
493
- | `CommitOutboxRecord` | `PendingWrite` |
494
- | `CommitOutboxStore` | `DurableWriteStore` |
495
- | `MeshParticipantStatus` | `ParticipantStatus` |
496
- | `planeSchema` | `residencySchema` |
497
- | `SchemaPlane` | `ModelResidency` |
498
- | `DEFAULT_PLANE` | `DEFAULT_RESIDENCY` |
499
- | `DELTA_RESIDENCY` | `DELTA_PHYSICAL_STORAGE` |
500
- | `InferModel` | `Model` |
501
-
502
- Each is a one-for-one substitution — the alias and its replacement were the same
503
- type or the same value, so nothing about your program's behavior changes with
504
- the name. `InferModel` is the one worth a sentence: `Model<typeof schema,
505
- 'tasks'>` reads as the domain rather than the machinery, which is why it became
506
- the published spelling, and the old name is gone from `@abloatai/ablo/schema`
507
- and from the `Ablo.Schema` namespace both. The type it named still exists
508
- underneath, because `Model` is defined in terms of it rather than as a second
509
- name for the same idea.
510
-
511
- An alias earns one release of overlap. That is what the surface snapshot in
512
- this repository records and enforces: a published name may leave only from a
513
- release that shipped it marked deprecated, which is the release in which a
514
- reader could have found out. These eight were marked in 0.34.1, so this is the
515
- release that may drop them, and dropping them is what keeps the rule a rule
516
- rather than a preamble on a list that only grows.
517
-
518
- `DELTA_RESIDENCY` is the one whose replacement is not merely a rename.
519
- Residency and classification are separate axes — where a column physically
520
- lives, and how sensitive its contents are — and one constant named as though
521
- they were the same question. `DELTA_PHYSICAL_STORAGE` answers the first;
522
- `DELTA_DATA_CLASSIFICATION`, which has been exported alongside it, answers the
523
- second. If you were reading `DELTA_RESIDENCY` to decide handling rather than
524
- placement, the classification constant is the one you wanted.
525
-
526
- - 320b8d7: `ablo docs` reads the documentation for the version you installed, and the
527
- public docs routes now serve the whole corpus instead of two thirds of it.
528
-
529
- A documentation URL always describes the newest release. A package in
530
- `node_modules` is frozen at whatever version was published. Those two facts
531
- drift apart the moment a project pins a dependency, and the drift is invisible
532
- from both ends: the docs are correct, the code is correct, and the agent reading
533
- one to write the other produces a call that does not exist. `get`, `getAll`, and
534
- `getCount` became `retrieve` and `list` in 0.35.0, so an assistant working in a
535
- project still on 0.34 reads the current page, writes `retrieve`, and watches it
536
- fail against the package sitting beside it.
537
-
538
- The documentation already travelled in the npm tarball; nothing read it there.
539
- `ablo docs` lists every page with what it covers, and `ablo docs <page>` prints
540
- one as markdown — from the files shipped alongside the code they describe, so
541
- they cannot disagree with it, and with no network at all, which is the condition
542
- most agent work now runs under. `ablo docs --json` gives the same list to a
543
- program. A new `@abloatai/ablo/docs` entry point exposes the catalog for
544
- anything that wants to build its own reader.
545
-
546
- The same catalog now answers `/api/docs/*`. That surface had been maintained by
547
- hand and had fallen twenty-one pages behind: `coordination`, the page explaining
548
- how claims work, returned a 404 while the site published it, and so did
549
- `sessions`, `webhooks`, `migration`, `deployment`, and every example but two.
550
- Nothing failed when the list went stale, which is why it stayed stale. Pages are
551
- now discovered rather than listed, so one is reachable the moment it is written,
552
- and only what ships in the package is reachable at all.
553
-
554
- One address changed meaning. `/api/docs/agents` returns the Agents guide, the
555
- page the docs index has always linked under that name; the install playbook that
556
- briefly answered there is at `/api/docs/AGENTS.md`, where it also answered
557
- before.
558
-
559
- - 0002877: `GET /v1/logs` answers two questions now — what changed, and who is working on
560
- what — through one envelope and one cursor.
561
-
562
- ```ts
563
- import { feedEventSchema, parseFeedCursor } from '@abloatai/ablo/wire';
564
-
565
- const page = await fetch(`${baseUrl}/v1/logs?after=${cursor}`, {
566
- headers: { authorization: `Bearer ${apiKey}` },
567
- }).then((r) => r.json());
568
-
569
- for (const event of page.data) {
570
- const entry = feedEventSchema.parse(event);
571
- if (entry.object === 'log_event') applyChange(entry);
572
- }
573
- cursor = page.next_cursor;
574
- ```
575
-
576
- An entry is discriminated on `object`, so a reader that knows only the arm it
577
- came for parses the whole page and skips the rest — which is what lets a second
578
- arm be added to a feed that callers are already following. The two arms stay
579
- separately sequenced on purpose. A claim is a lease rather than a settled fact,
580
- and allocating its positions from the delta sequence would put ephemeral leases
581
- into the log that clients materialise rows from and that WAL-echo promotion and
582
- compaction operate over, where a burst of claim churn would be indistinguishable
583
- from committed change. They share a reading, not a sequence.
584
-
585
- That is why the cursor carries a position for each, encoded `"<log>.<claims>"`.
586
- A bare `"<log>"` still parses and resumes at claim position zero, which is the
587
- right answer for every cursor issued before this existed: a caller who never
588
- asked for claim events has no claim position to preserve. `parseFeedCursor`,
589
- `formatFeedCursor`, and `feedCursorAdvanced` are exported from
590
- `@abloatai/ablo/wire` alongside `feedEventSchema`, `logListResponseSchema`, and
591
- `claimEventSchema`, so a program that builds its own reader states the grammar
592
- once. `claimRecordSchema` and `heldClaimStatusSchema` join them on
593
- `@abloatai/ablo/coordination` — the one claim record, and the peer-visible
594
- projection of it.
595
-
596
- `GET /v1/claims` gains the filters the audit log already had: `actorId`,
597
- `actorKind`, `onBehalfOfId`, and `capabilityId`, beside the existing `model` and
598
- `id`. The names are audit's names deliberately. Asking what an agent is doing
599
- and asking what it did should not require two vocabularies for one idea, and now
600
- the tense is the only thing that differs.
601
-
602
- The claim arm has no producer yet. Claim transitions are broadcast and recorded
603
- nowhere, so there is no sequence to read a position from, and the route emits
604
- only `log_event`s today — with no request parameter for selecting arms, because
605
- a knob that cannot be honoured is how a contract comes to describe a server that
606
- does not exist. What ships here is the definition that producer will fill: the
607
- union parses today's pages unchanged, and the cursor already carries the second
608
- position, so a follower written now keeps working when the arm arrives.
609
-
610
- ### The cursor fix
611
-
612
- A malformed `after` is now an error rather than a position. The previous reader
613
- was `parseInt(raw, 10)` with a `NaN` falling back to zero, so a truncated or
614
- garbled cursor resumed from the beginning of the log and replayed it in full —
615
- while looking exactly like a working follow, with the damage scaling to how long
616
- the log had been running. Such a request is now declined with `invalid_request`,
617
- naming the `after` parameter and how to recover. `ablo logs` carried the mirror
618
- of the same bug: it read the cursor with `Number(...)`, which is harmless while a
619
- cursor is a bare delta id and silently wrong the moment it has two parts —
620
- `Number('42.10')` is `42.1`, which re-serialises as `'42.1'` and resumes eight
621
- claim positions late. The cursor is now a string end to end, passed back exactly
622
- as it was issued.
623
-
624
- - 6be1d99: `legacyCompatibleCommitReceiptSchema` is removed from `@abloatai/ablo/wire`. The WebSocket client
625
- now parses a commit acknowledgement with `commitReceiptSchema`, the canonical one, directly.
626
-
627
- It was a compatibility decoder rather than a contract: a `z.preprocess` step that supplied fields
628
- older servers omitted and coerced their string `lastSyncId` into a number before handing the result
629
- to the canonical schema, which did the real work. It was careful in one respect worth recording —
630
- it never invented a source correlation, so an old or malformed queued receipt still failed closed
631
- rather than being promoted to something the server had not said.
632
-
633
- The servers it decoded for predate the commit contract, and the hosted engine has not spoken that
634
- shape in some time. Keeping the wrapper meant every receipt on the socket path went through a
635
- normalizer for a case that no longer arrives, and it left two spellings of "a receipt" on a
636
- published surface whose entire purpose is to have one.
637
-
638
- This matters only if you run a self-hosted engine older than the commit contract: its
639
- acknowledgements will now fail to parse, and the write is reported as `commit_no_result` — pending
640
- and safe to retry — rather than being silently normalized. Upgrade the engine. If you imported the
641
- schema to parse receipts yourself, `commitReceiptSchema` is the replacement and always was the one
642
- doing the parsing.
643
-
644
- ### Patch Changes
645
-
646
- - 320b8d7: `ablo login`, `ablo mode`, and `ablo status` now say what the key in your hand
647
- can do, and `status` labels the plane your credential reaches as `acts on`
648
- rather than `env`.
649
-
650
- Logging in provisions a pair — a secret sandbox key and a restricted production
651
- one — so that a stolen CLI config cannot deploy to production. That protection
652
- was deliberate and it was also silent. `login` confirmed the sandbox and moved
653
- on, `mode production` printed a checkmark, and `status` showed the production
654
- key as twelve characters of prefix. Nothing said the key could not push, so the
655
- first notice was a 403 in the middle of a deploy, at the moment it cost the
656
- most to learn.
657
-
658
- Each of those three commands now names the capability at the point it hands you
659
- the credential: the key rows in `status` carry what each key does beside how
660
- long it lasts, the line under `push` says which secret key deploys, and
661
- `ablo status --json` reports `effectiveKey.kind` so a pipeline can check before
662
- it pushes instead of after it fails. The wording has one definition site, so the
663
- command that stores a key, the command that switches to it, and the command
664
- that reports it cannot describe it three different ways.
665
-
666
- The renamed `status` line is the same fix in smaller form. `mode` and `env`
667
- printed as peers and were not: one is the environment you selected, the other is
668
- the one your credential actually reaches, and when they diverged neither word
669
- said which was which. `acts on` states the fact. The `--json` output is
670
- unchanged apart from the added `kind`.
671
-
672
- ## 0.34.1
673
-
674
- ### Patch Changes
675
-
676
- Connecting a database whose tables are owned by an earlier integration's role now just works, with no manual step. When `ablo connect --apply` publishes your tables and grants the writer role access to them, Postgres reserves both operations for each table's owner — so if you reach that owner only through a membership that doesn't inherit its privileges, which is the ordinary shape on managed Postgres where a top role administers everything else without holding superuser, the run would otherwise stop partway through. Apply now clears that itself. It grants your admin inheritance of the owning role as the first step of the plan — shown in the preview and covered by the same confirmation you already give, `GRANT <owner> TO <your-admin> WITH INHERIT TRUE`, the per-membership inheritance Postgres 16 introduced — so the admin acts with the owner's authority for the rest of the setup, with no ownership change and reversible by `WITH INHERIT FALSE`. You approve the plan; you never run a line of SQL. The previous release had only detected the situation and suggested reassigning the table's ownership, which is itself reserved for the current owner and so couldn't run from where you stood; apply now does the one thing that can, and does it for you. Only when your admin genuinely can't take that authority — when it isn't a member with admin option of the owning role — does apply stop, and then it names the exact grant an authorized role must run, or the drop for the idempotency ledger. The same handling covers that ledger, which had kept an older, stricter ownership test that could turn away an admin who in fact inherited the owner.
677
-
678
- The line printed once a database registers has been rewritten to say what happened and what comes next — that your database is connected, that reads follow its replication stream while writes go through Ablo and land in your own tables, and that `ablo connect check` verifies the connection at any time — in place of the internal shorthand it printed before.
679
-
680
- A new page, **Operating on Your Database**, sets out the safety model for working on a live database through Ablo: which actions run freely because they are read-only or reversible, which to verify against the database first, and which change the database itself and belong to a human — written so a person or an agent can sort any action into the right one and stop guessing which move is the dangerous one.
681
-
682
- ## 0.34.0
683
-
684
- ### Minor Changes
685
-
686
- A long-running actor has a stale-context problem the per-commit read gate never reaches. The `reads` guard is a premise for the commit in hand: you declare what you looked at, the server checks it at commit, and the premise is gone. That fits an actor that reads and writes in one breath, not one that reads a row, works for minutes — an LLM call, a fetch, a human's turn — and only then writes. By the time it commits, the premise it would have declared is already old, and there was no commit in between on which to hear that the ground had shifted. This release adds `track`, the durable half of the same idea. `ablo.<model>.track({ id })` registers a read-dependency that persists on the server; the next time you commit anything, a change that landed on the tracked row since you registered rides back on the receipt's `notifications` — the same `StaleNotification` an `onStale: 'notify'` premise hands you, arriving on the write you were going to make anyway. You can also register one as part of a write, `track: [{ group: 'deck:abc' }]` alongside the batch, the standing-subscription companion to the single-commit `reads`. A track is idempotent — registering the same target again refreshes the one subscription rather than stacking duplicates — it re-baselines after it fires so a given change notifies once, and it never notifies you of your own writes, since the signal is about what others did. Delivery is on your next commit's receipt; a track does not yet push out of band between commits, so it sharpens the write-time freshness check rather than replacing a live subscription.
687
-
688
- The model-level presence verb is renamed from `watch` to `join`. It read like a data subscription but delivered presence — who else is on a set of rows and what they hold — so it now says what it does. `ablo.<model>.join(ids, { ttl })` opens the participant handle, with `.peers`, the scoped claim stream, and `await using` disposal unchanged; the handle's `status` was already `'joined'` and the layer beneath always called itself join, so the verb now matches the thing it returns. `onChange` remains the way to hear a row's _values_ change, and `track` is the durable read-dependency for actors — three distinct jobs that the one overloaded `watch` used to blur. The React hook follows: `useWatch` becomes `useJoin`, the `WatchOptions` / `UseWatchOptions` / `UseWatchReturn` types become `JoinOptions` / `UseJoinOptions` / `UseJoinReturn`, and the error code `model_watch_not_configured` is now `model_join_not_configured`. There is no compatibility alias — rename the call sites and the type imports. The migration guide carries the mechanical diff.
689
-
690
- ### Patch Changes
691
-
692
- `ablo connect --apply` now checks, before it runs any grants, that you can actually grant on the tables you're publishing — and stops with the one-line fix if you can't, instead of failing partway through. The setup grants the writer role access to each published table, an operation Postgres reserves for the table's owner, so a table left owned by an earlier integration's role used to abort the run midway with a bare `must be owner of table …` and no guidance. Apply now names the offending tables and their owner up front and prints the exact `ALTER TABLE … OWNER TO` to reassign them to your admin — metadata only, your rows and row-level-security policies untouched. The check understands inherited role membership, so an admin that inherits the owning role — the ordinary managed-Postgres case — is left to proceed rather than stopped needlessly; only a membership that can't act as the owner is flagged.
693
-
694
- ## 0.33.0
695
-
696
- ### Minor Changes
697
-
698
- The declarative seam introduced in 0.32.0 for carrying tenant identity into your row-level-security policies has a clearer name and a simpler shape. `tenantContext` is now `sessionSettings`, and instead of a list of `{ guc, from }` pairs it is a plain map from the Postgres session setting your policies read to the Ablo identity that fills it:
699
-
700
- ```ts
701
- defineSchema({
702
- models: { document: {/* … */} },
703
- sessionSettings: { 'app.current_org': 'orgId' },
704
- });
705
- ```
706
-
707
- The setting name is the key, so a setting takes exactly one source and a duplicate is unrepresentable rather than something to validate away. If you adopted `tenantContext` in 0.32.0, rename it to `sessionSettings` and turn each `{ guc: 'app.current_org', from: 'orgId' }` into `'app.current_org': 'orgId'`; the exported names followed the rename — `TenantContextMapping` and `TenantContextSource` became `SessionSettings` and `SessionSettingSource`, and `RESERVED_TENANT_CONTEXT_GUCS` became `RESERVED_SESSION_SETTINGS`. The meaning is unchanged: Ablo fills only settings it resolves from your authenticated identity, never from client-supplied data, so a mapping can forward the tenant Ablo already trusts but can never widen a writer's scope, and settings the engine reserves for itself — `row_security`, the timeouts — are still refused at definition time. Schemas pushed before the rename keep parsing.
708
-
709
- Claims gain a fairness backstop, off by default. Claims coordinate long work by waiting rather than locking: when another participant holds a row, your `claim` joins a fair FIFO line behind it, and the holder keeps its lease alive for as long as the work runs by beating on a cadence. Nothing bounded a hold, so a holder that kept beating — and never read the `queueDepth` pressure signal each beat returns, the cue to checkpoint and release when others are waiting — could keep a contended row indefinitely, and the line behind it had no recourse but that holder's goodwill. A deployment can now configure, per model, how long a single holding may last while contenders are actually queued; a holder that runs past that fair share is preempted at the server on its next beat, which arrives as the `claim_lost` you already handle — abandon or re-claim, exactly as when a lease lapses. The guarantee is deliberately narrow: a holder with no one waiting is never preempted however long it runs, so open-ended solo work is untouched, and preemption is a fairness decision, never a correctness one — a preempted holder's next write is still checked at commit against the row's version and fencing token, so the worst it can cause is a clean re-read, never a lost update.
710
-
711
- ### Patch Changes
712
-
713
- Connecting a database a second time now behaves the way you'd expect. `ablo connect --apply` publishes exactly the tables you name with `--tables`, and until now it assumed that publication didn't exist yet — so on a database that had already been connected, the step to publish your tables quietly did nothing, and the writer role ended up granted on one set of tables while Ablo was still reading a different, older set. Registration then refused the writer as "not ready," which was correct but baffling: the two halves disagreed and nothing said so. Apply now reconciles the publication to match your `--tables` on every run — adding what's newly named, dropping what's no longer there, the same declarative model a CDC tool like Debezium uses — and prints what it's about to add or drop before it touches anything, so narrowing the set is a decision you see rather than a surprise.
714
-
715
- When a readiness check does fail, the reason is finally visible. The direct-write preflight has always returned a precise checklist — which privilege is missing, on which table, and the exact grant to fix it — but the CLI was reading it from the wrong place in the error and rendered a blank message that sent you in circles. The checklist now prints under the failure, one line per unmet requirement, with the fix.
716
-
717
- ## 0.32.0
718
-
719
- ### Minor Changes
720
-
721
- The last release folded connecting a database into a single command. This one finishes the thought: once a database is connected, your application holds exactly one Ablo secret — `ABLO_API_KEY` — and nothing else.
722
-
723
- The scoped roles Ablo needs to read and write your database are real Postgres credentials, but they were never meant to live in your environment. `ablo connect --apply` creates them from a one-time admin connection, hands them to Ablo, and discards the admin credential; from then on Ablo holds and rotates them. What changed in this release is the last place that still asked you to keep a database credential: the health check. `ablo connect --check` used to require the two scoped connection strings in your environment so it could dial them from your laptop. Now it asks Ablo to check the database it already holds, from the same infrastructure replication actually runs on, and needs nothing but your API key. If your own machine can't reach your database — an IPv6-only host, an IP allowlist, a VPN — that no longer matters, because your machine was never the right place to check from. And when nothing is connected yet, the check says so plainly and points you at `ablo connect --apply` rather than failing for a missing environment variable.
724
-
725
- The `DATABASE_URL` fallback for the replication credential, deprecated in 0.31.0 with a warning, is removed as promised. Reading a scoped connection string from the generic `DATABASE_URL` risked quietly validating against your application's own database, so the replication role is read only from `ABLO_REPLICATION_DATABASE_URL` now. If you register a database by hand instead of using `--apply`, you set that and `ABLO_WRITE_DATABASE_URL` just long enough to register — Ablo takes them over, and your app is back to holding only its API key. `DATABASE_URL` keeps its one honest job: the transient admin credential for `--apply`.
726
-
727
- The other half of holding one key is trusting that Ablo's writer stays inside the rules your database already enforces. Ablo writes through a scoped role that is subject to your row-level security — but many applications don't carry a tenant in a column; they set it once per request as a session variable, `SET app.current_org = '…'`, and every policy reads that. That was invisible to Ablo: its writer opened its own transaction and never set the variable, so on the first insert every policy evaluated against an empty tenant and the write failed closed — silent, total, and correct, but baffling from the outside. You can now declare the mapping in your schema, and Ablo's writer sets the variable per transaction before it touches a row:
728
-
729
- ```ts
730
- defineSchema({
731
- models: { document: {/* … */} },
732
- tenantContext: [{ guc: 'app.current_org', from: 'orgId' }],
733
- });
734
- ```
735
-
736
- The `from` side is a closed set — `orgId`, `projectId`, `environment`, and the other identifiers Ablo establishes for the write from your credential. It is deliberately not free-form: a mapping can only pass through a value Ablo has already authenticated, never let a caller name its own tenant, so it can narrow what the writer sees but never widen it. Settings Ablo reserves for itself are refused at definition time. Your policies stay the sole authority on what the writer may touch; this only gives them the context they were written to read.
737
-
738
- Reading the setup SQL before you grant it is the whole compliance story of `ablo connect`, so the SQL has to match its narration — and in a few places it didn't. `--tables documents` promised least privilege but granted the reader `SELECT` on every table in the schema, present and future, and the writer every sequence; both are now scoped to exactly the tables you name, with schema-wide access surviving only in whole-database mode, where it's honest. A `REVOKE CREATE ON SCHEMA public FROM PUBLIC` that had crept into the script is gone — a vendor recipe must not alter the permissions of roles it doesn't own as a side effect, and modern Postgres does it by default anyway. The row-level-security narration no longer implies protection that isn't there: it distinguishes tables that have policies from those that don't, and reports honest per-table coverage rather than a blanket claim. The bookkeeping table Ablo keeps in your database, `ablo_idempotency`, now ages its rows out on a bounded window with one documented statement to prune it, instead of growing without end. And a ledger left from an earlier integration, owned by another role, no longer makes a harmless re-run fail with `must be owner of table ablo_idempotency` — Postgres checks ownership before it checks whether the column being added already exists, so re-runs now touch the table only when there is genuinely something to change, and a foreign-owned ledger stops the run early with the fix spelled out.
739
-
740
- Two smaller things. `ablo connect --apply` no longer reports success against a database where replication never turned on: it reads `wal_level`, recognizes your provider from the host, and on a managed platform that won't accept `ALTER SYSTEM` shows the real path — the console toggle, the parameter group — and exits non-zero instead of printing an un-runnable statement and a green checkmark. And `ablo disconnect` is the counterpart to connect: it removes a database, scoped to exactly the project and environment it shows you, so tearing down a sandbox connection never reaches a sibling's.
741
-
742
- Where this is heading: fewer keys, held more briefly, with your database's own rules the final word on what Ablo's writer can do. Tenant-context mapping is the first declarative seam where your schema tells Ablo how your database governs itself; more of that — which columns a writer may set, which policies it must satisfy — will move into the schema over time, so that granting Ablo a role stays something you can read, reason about, and revoke.
743
-
744
- ## 0.31.0
745
-
746
- ### Minor Changes
747
-
748
- Ablo's relationship with your database is unusual, and this release is about making the setup match it. You write through Ablo — `ablo.<model>.create`, `.update`, `.delete` — and Ablo lands each change in your own Postgres through a scoped role, then reads it back off your write-ahead log to confirm it landed. Your rows never leave your database; Ablo keeps only the ordered log that lets everyone writing to that database take turns. So "connecting a database" really means handing Ablo two narrow keys: one role that can stream and read, and one that can write rows and nothing else.
749
-
750
- Handing over those keys used to be a chore — paste some SQL, wire two connection strings into your environment, then run a second command to register them. This release folds it into one step. Point `ablo connect --apply` at an admin connection — pass it with `--url`, or let it use the `DATABASE_URL` you already have — and it runs the whole ceremony: creates the two scoped roles, publishes your tables, turns on logical decoding where the provider allows, and registers both roles with Ablo. The admin credential is used once and discarded. Nothing is written to your `.env`; Ablo generates and holds the scoped passwords itself, and your app keeps only its `ABLO_API_KEY`. When a password needs replacing, `ablo connect --rotate` mints a new pair and re-registers them in the same step.
751
-
752
- The shape is deliberate, and it follows one rule: a credential can only have one owner. The admin connection is yours — it's the key to your whole database, so Ablo uses it once and never stores it. The two scoped roles are Ablo's, which is why Ablo, not you, generates their passwords: a credential Ablo owns is one it can rotate, revoke, and audit, and one you can read as plain SQL before you grant it. The same rule is why the scoped strings moved out of the generic `DATABASE_URL` into names of their own — `ABLO_REPLICATION_DATABASE_URL` for the reader, `ABLO_WRITE_DATABASE_URL` for the writer. Nearly every app already sets `DATABASE_URL` to reach its own database, so a replication-only credential left there breaks the app's writes, and a `--check` that quietly falls through to it can report success against the wrong database entirely. `--check` still reads the old name, with a warning, through 0.31.x, and stops in 0.32.0; `--register` refuses it today. The one job `DATABASE_URL` keeps is the honest one: the one-time admin input to `--apply`.
753
-
754
- Where this is heading: connecting should eventually be a single sentence to an agent — "connect my Postgres" — with the CLI recognizing your provider, doing everything your credentials allow, and asking once for the single privileged step it can't do for you (a reboot to enable logical replication, an OAuth grant). `--rotate` is the first piece of the credential lifecycle that vision needs, and the `ABLO_`-prefixed names clear away the last fossils of the old dial-in era. The direction is steady: fewer keys, held more briefly, with the one irreversible act always surfaced for a human to approve.
755
-
756
- ## 0.30.2
757
-
758
- ### Patch Changes
759
-
760
- - **`ablo connect --apply` sets up your database for you.** Connecting a database used to mean pasting a block of SQL into a psql prompt. Now one command does it. `--apply` creates the two scoped roles, publishes your tables, turns on logical replication where your provider allows, and points `DATABASE_URL` and `ABLO_WRITE_DATABASE_URL` at the new least-privilege roles. Then it checks its own work, reconnecting as the replication role to confirm the database is ready.
761
-
762
- Nothing leaves your machine. It runs against the admin credential already in `DATABASE_URL`, asks before it changes anything, and writes the passwords it generates to your environment file rather than printing them. Every step is safe to run twice. When a managed provider needs a restart to finish — RDS, Neon — it does everything else and hands you that one step, with the exact fix. Prefer to run the SQL yourself? `ablo connect` still prints it, and `--show-sql` shows it inline first.
763
-
764
- ## 0.30.1
765
-
766
- ### Patch Changes
767
-
768
- - **Writes land in your own database.** Connect a database with `ablo connect` and the models you already read become writable. `ablo.<model>.create`, `.update`, and `.delete` route through Ablo's commit chokepoint and into your database — no connection string in the client. A claim you hold is checked at the chokepoint before the write applies, so agents and people take turns on a row whether they reach it by reading from Ablo or by writing back to your own database.
769
-
770
- A write returns as soon as it is durably accepted; the receipt calls that `queued`. For the authoritative outcome, pass `wait: 'confirmed'`. Ablo watches the change actually land in your database — the write-ahead-log echo for a replicated source, the event feed for a signed endpoint — and promotes the receipt to `confirmed` before the call returns. The code is the same either way; only the feed Ablo waits on differs.
771
-
772
- - **A clearer error when a table isn't ready yet.** `ablo push` records your models, but a plane's physical tables are created separately, out of band — so a model can exist in the schema before its table does. Read or write one in that window and you now get a typed `model_not_provisioned` error (HTTP 409) that says what's missing and how to fix it, instead of an opaque failure.
773
-
774
- ## 0.30.0
775
-
776
- ### Minor Changes
777
-
778
- - **The `databaseUrl` client option is gone.** Your database connects to Ablo out of band, not through the client. Ablo is Stripe-shaped: the client only ever talks to the engine. You build it with `Ablo({ schema, apiKey })` — add `transport: 'http'` for a stateless worker — and it reaches Ablo over WebSocket or HTTP. It never opens a Postgres connection of its own. How your data connects is a separate, one-time step, and it takes one of two shapes:
779
-
780
- - **Logical replication, the default.** `ablo connect` registers your database, and Ablo tails its write-ahead log. Your rows stay where they are; Ablo reads the change stream and keeps its own log.
781
- - **A signed endpoint, the fallback.** When a database can't grant a `REPLICATION` role, your app exposes a signed HTTP endpoint for Ablo to call. Same principle — the credentials stay with your app.
782
-
783
- With no connection string in the client, `ablo init` no longer scaffolds the `--storage direct` connector, the last thing that wrote `databaseUrl: process.env.DATABASE_URL` into generated code. Run `ablo init --storage replication` (the default) or `--storage endpoint`.
784
-
785
- - **A claim's work label is `description`, not `reason`.** A claim is an advisory lock: you take it on a row before editing, and other participants — agents or people — see it's held and wait. The free-text note that says what you're doing is now `description`, the line a peer reads when it lands on a row you hold: `ablo.<model>.claim({ id, description: 'Renaming the task to match the brief' })`. The old `reason` option is gone, with no alias, so a call still passing it won't type-check — rename it. (This is a different field from the `reason` on a rejected or lost claim — `'conflict'`, `'expired'`, `'preempted'` — which explains why coordination said no, and hasn't changed.)
786
-
787
- - **Claim a key Ablo hasn't synced: `ablo.<model>.claim(id)`.** Claims now come in two shapes, matched to whether Ablo holds the row. The object form, `claim({ id })`, loads the row from Ablo's synced pool and returns a `HeldClaim<T>` with its `.data`. The new string form, `claim(id)`, locks a row that lives only in your database — one Ablo has never synced — skipping the pool load and the `entity_not_found` throw, and returns a `HeldLease`: a `HeldClaim` without the data. This is Rung 0 of the connect ladder: coordination with no replication grant, no schema change, no `organization_id` column — so you can coordinate agents against your database before wiring any sync at all. `await using` releases either shape at end of scope, and the object form is untouched, so nothing you've already written changes.
788
-
789
- ### Patch Changes
790
-
791
- - **`ablo push` defers provisioning instead of failing when a role can't run DDL.** When the engine role bound to a plane can't create or alter tables, `push` no longer aborts. It records the schema and defers the table changes for a role that can run them — so your models still register, rather than the whole command stalling on a privilege the plane may never grant the engine.
792
-
793
- - **Schema-drift stops false-alarming on `selectModels` / `omitModels` clients.** A projected client carries a deliberate subset of the schema, so its hash differs from the full one by design. The check now compares against the projection's own source hash — so a correctly projected app no longer sees a phantom drift warning at startup.
794
-
795
- ## 0.29.3
796
-
797
- ### Patch Changes
798
-
799
- - **`ablo connect` validates from Ablo's network when your machine can't reach the database.** Replication runs from Ablo's infrastructure, not from your laptop, so a database that is IPv6-only (Supabase direct hosts), IP-allowlisted, or behind a VPN can be perfectly replicable even when every local dial fails. When `--check` can't connect at all, it now asks the engine to run the same readiness checklist from its own network instead of reporting a false failure; `--register` no longer blocks on local unreachability and lets the registration preflight decide. A host that _was_ reached and rejected the connection — bad credentials, a TLS error, a Postgres error — still fails locally, because the engine would see exactly the same thing.
800
-
801
- ## 0.29.2
802
-
803
- ### Patch Changes
804
-
805
- - **Switching to a project you never minted a key for no longer locks you out.** Project-management commands — `ablo projects list`, `create`, `rename`, and `use` — are organization-level operations, so they now accept any of the organization's stored keys (the active project's key first, then the `default` profile, then any profile still holding an unexpired key) instead of strictly the active project's key. Selecting a keyless project could previously leave `ablo projects use default` unauthorized — the one command that would undo the switch. Data commands keep the strict resolver, so this permissive fallback never routes a read or write through an unintended project.
806
-
807
- ## 0.29.1
808
-
809
- ### Patch Changes
810
-
811
- - **The `omitModels` doc example uses generic model names.** The schema-projection example in the `omitModels` JSDoc and the API docs now reads `omitModels(full, ['reports', 'reportSections'])` — an illustrative parent/child pair rather than application-specific model names. Documentation only; no API or behavior change.
812
-
813
- ## 0.29.0
814
-
815
- ### Minor Changes
816
-
817
- - **`omitModels()` projects a schema by exclusion — the companion to `selectModels`.** Keep every model except the named ones, so one app can be the general case while a separate app owns the models it drops. The suite shell narrows with `omitModels(full, ['reports', 'reportSections'])` while the standalone app selects those same models. Validation matches `selectModels`: relations into the omitted set are dropped, and a dropped `parent` edge throws, so a model whose scope routes through an omitted parent can't be silently kept. Each app now binds its own schema projection rather than sharing one global registration.
818
-
819
- - **Every application model has one typed access path: `ablo.<model>`.** Both the WebSocket and stateless HTTP clients return bare rows from `retrieve` and accept the same typed create/update/delete forms. The schema-less `Ablo({ schema: null })` overload, the public `.model(name)` accessors, the transport-envelope types, and the hidden capability CRUD client are removed. Select `transport: 'http'` for workers without changing model syntax, use `commits.create` only for atomic multi-row writes, and `sessions.create` to mint a scoped user or agent credential.
820
-
821
- - **The commit wire names only the protocol that actually runs.** `@abloatai/ablo/wire` exports `CommitMessage`, `MutationResultMessage`, `commitOperationSchema`, and `commitPayloadSchema` directly. The prospective v2 commit contract published under `@abloatai/ablo/commit` in 0.28.0 — its `CommitReceipt` type, the `legacyCommit*` wire aliases, and the unsent named-mutation frame — is removed, so there is one contract to implement and test. Idempotency-key sealing and fencing move to `transactions/idempotencyKey.ts` (ADR 0006: the v1/v2 boundary lives at ingress, not in the client). Local `useMutators` calls still compose normal transactions; this removes only the dead server wire path.
822
-
823
- - **Protocol support is backed by codecs, not a numeric claim (ADR 0005).** The wire package publishes an explicit supported-version manifest; the server must provide a decoder for every entry. Durable HTTP writes seal their original protocol version and replay with it after an upgrade. Application-schema hashes are an advisory bootstrap warning rather than an exact WebSocket gate, so additive schema versions can overlap through an expand/contract rollout.
824
-
825
- - **The injected write-storage option is `durableWrites`, and `DurableWriteStore` is its interface.** The `commitOutbox` client option and `CommitOutboxStore` interface introduced in 0.28.0 are renamed. `durableWritesConfigSchema`, `durableWriteStoreSchema`, `pendingWriteSchema`, and the `DurableWritesConfig` / `DurableWriteStore` / `PendingWrite` types are exported from the package root, so a workflow-, SQLite-, or filesystem-backed adapter validates against the shipped schema instead of an interface you re-declare. The old `commitOutbox` / `CommitOutboxStore` names remain as deprecated aliases through the next major release.
826
-
827
- - **`Register` registers your `Schema` and `UserMeta` — those are the two keys.** The `Presence` and `Claims` registration keys and their `ResolvePresence` / `ResolveClaims` resolver types are gone, matching the removed presence and claim React placeholders. Read presence through `usePeers` or `useWatch`, and row claims through `ablo.<model>.claim`. The default resolver now carries a `SchemaRecord` fallback, so a shared package typechecks standalone with no app registration in scope.
828
-
829
- - **One signed adapter export: `dataSource`.** The `abloSource` alias is removed, and the parallel `DataSource*` type names (`DataSourceOptions`, `DataSourceCommitParams`, `DataSourceHandlerContext`, and the rest) collapse into the canonical `Source*` types — one name per shape. Endpoints already built with `dataSource()` are unchanged; only the duplicate names are gone.
830
-
831
- - **The claim type surface is reached through `Ablo.Claim.*`.** `ClaimOptions`, `ClaimParams`, `ClaimLookupParams`, `ClaimReorderParams`, `Claim`, `ClaimHeartbeat`, `ClaimHeartbeatOptions`, and `ModelOperations` are namespace members rather than top-level exports; `HeldClaim` stays as a deprecated top-level alias because 0.20.1 documented it. The internal construction types `InternalAbloOptions` and `HttpClaimApi` are no longer exported — the public surface names only what an application builds against.
832
-
833
- ### Patch Changes
834
-
835
- - **Removed React placeholders that had no working runtime.** `usePresence`, `useClaim`, `SyncGroupProvider`, and `useSyncGroup` are no longer exported. Use `usePeers` or `useWatch` for presence and scoped participation, and `ablo.<model>.claim` for row claims. The unused `Register.Presence` / `Register.Claims` resolver types are gone with them.
836
-
837
- ## 0.28.0
838
-
839
- ### Minor Changes
840
-
841
- - **Commits are now crash-durable — a sealed envelope replays the exact request after a restart, on both transports.** Before a batch reaches the wire, the WebSocket client seals it into one atomic outbox record: the stable idempotency key, the exact JSON-normalized ordered operations, and the source mutations it supersedes. Recovery replays the sealed request as it was sent — never re-projected from model state in the new process — and a multi-operation commit is only ever replayed whole, so an atomic A+B can't come back later as A and B separately.
842
-
843
- - **The stateless HTTP client replays its outbox on startup.** It persists the exact request (method, path, body, idempotency key) and re-sends unacknowledged envelopes before its first new request. A definitive rejection (a 4xx other than 429) settles the envelope instead of retrying it.
844
-
845
- - **New `commitOutbox` client option + `CommitOutboxStore` interface.** Browsers default to strict IndexedDB storage when persistence is on; Node agents and workers can inject workflow-, SQLite-, or filesystem-backed storage so commits survive process restarts. The client's transaction journal now writes with strict durability, so an acknowledged outbox record means disk-backed.
846
-
847
- - **Replay is fenced and fails closed.** Reusing an idempotency key with a different request is rejected on both transports; replay is scoped to the actor and server that sealed the envelope and bounded to the server's idempotency retention, so a stale envelope is held for review instead of re-sent; a write queued behind an ambiguous predecessor waits for it to settle rather than overtaking it; and a persistence adapter that cannot guarantee durability reports failure instead of phantom success. Generated create ids are sealed with the request, so a crash mid-create recovers the same row.
848
-
849
- - **`@abloatai/ablo/commit` publishes the transport-independent v2 commit contract.** Branded identifiers, ordered operations, explicit write preconditions (`unchanged_since`, `version_matches`, `claim_fence`), and a discriminated `CommitReceipt` — `committed`, `conflicted` (carrying the smallest current state needed to reconcile), or `rejected` (a typed error plus the capability a retry would need). This release publishes the contract so tooling and tests can validate against it today; the engine's live commit path still speaks the current protocol, and the cutover to v2 ships server-side.
850
-
851
- - **New wire exports.** `@abloatai/ablo/wire` gains the matching `CommitRequestMessage` / `CommitResultMessage` frames, re-exports the current transport's operation schemas under `legacyCommit*` names, and exports `BootstrapReason` — the machine-readable reasons a live delta stream must resume via catch-up (`too_far_behind`, `too_many_deltas`, `missing_entities`, `stream_gap`).
852
-
853
- - **Reactive reads are typed as what they actually return: snapshot rows, not model instances.** `useAblo` selectors now receive `AbloReads<Schema>` — model reads typed as the row's data fields plus schema computeds, without relation accessors or model methods. Reading `row.layers` inside a selector is a compile error instead of a silent `undefined` at runtime; compose relations through a dedicated hook or selector instead. The new `Row<'slides'>` / `InferRow` types are the snapshot-row companion to `Model<'slides'>`, mirroring what `toReactiveSnapshot()` produces. This is a type-level change only: selectors that compiled against relation accessors now fail to compile, and each of those reads was already returning `undefined` — the new errors point at real bugs. Runtime behavior is unchanged.
854
-
855
- ### Patch Changes
856
-
857
- - **The resume cursor advances only through the contiguous durable prefix of a delta frame.** A frame spanning several local stores commits per store, and those transactions can fail independently — taking the maximum id from any successful store could permanently skip an earlier failed delta on every later catch-up.
858
-
859
- - **Conflicting undo-scope options warn instead of being silently ignored.** A scope keeps the options it was created with; the silent version of that rule could leave a surface believing it enabled stream recording when it hadn't.
860
-
861
- - **Docs and error copy name the real HTTP mount.** REST paths are documented as `/api/v1/...` — the OpenAPI server URLs carry the `/api` mount — and the `jwt_issuer_untrusted` message no longer points at an endpoint that doesn't exist.
862
-
863
- - **The resume cursor advances only through the contiguous durable prefix of a delta frame.** A frame spanning several local stores commits per store, and those transactions can fail independently — taking the maximum id from any successful store could permanently skip an earlier failed delta on every later catch-up.
864
-
865
- - **Conflicting undo-scope options warn instead of being silently ignored.** A scope keeps the options it was created with; the silent version of that rule could leave a surface believing it enabled stream recording when it hadn't.
866
-
867
- - **Docs and error copy name the real HTTP mount.** REST paths are documented as `/api/v1/...` — the OpenAPI server URLs carry the `/api` mount — and the `jwt_issuer_untrusted` message no longer points at an endpoint that doesn't exist.
868
-
869
- ## 0.27.0
870
-
871
- ### Minor Changes
872
-
873
- - **Claims can now be held for long-running work by heartbeating — on both transports.** A claim's TTL is crash cleanup, not a work estimate; work that outlives it (a 30-minute report, a long agent run) keeps its lease by beating. `claim({ heartbeat: true })` beats automatically until release; `held.heartbeat()` beats by hand. A lapsed lease answers the next beat with `AbloClaimedError` (`claim_lost`) — for a socketless worker, the failed beat _is_ the loss notification, and any write attempted under the old lease is independently rejected by its `readAt` guard.
874
-
875
- - **Beats carry progress and pressure.** `heartbeat({ details })` stores lightweight progress as the claim's peer-visible `meta.progress` (last beat wins, via `claim.state`); every beat's answer reports `queueDepth` — how many participants wait in line behind the lease — observable per auto-beat with the new `onHeartbeat` claim option.
876
-
877
- - **`ablo.claims.heartbeatAll({ ttl })`** extends every lease the credential holds in one request (`POST /api/v1/claims/heartbeat`) — the stateless twin of the WebSocket keepalive, for workers holding many rows.
878
-
879
- - **Keepalive renewals extend but never shorten a lease**, so an explicit work-duration `ttl` now survives pings and brief reconnects instead of collapsing to the liveness window.
880
-
881
- - **README.** The quick start leads with a runnable example, and a new _Background workers_ section shows the enqueue-on-your-own-queue / heartbeat-the-claim pattern end to end.
882
-
883
- ## 0.26.0
884
-
885
- ### Minor Changes
886
-
887
- - **`ablo connect --register` works against any deployment.** Registering your database as an Ablo data source over logical replication is now a first-class path everywhere — the gate that could refuse a new connection-string registration is gone. Point `ablo connect --register` at your Postgres and Ablo begins consuming its replication stream while your application keeps owning the write path; registering the source _is_ the enable, with no tier or flag to choose. The signed `dataSource()` endpoint remains the explicit fallback for databases that can't grant replication.
888
-
889
- - **More engine failures surface as typed errors you can branch on**, instead of opaque `500`s. New `AbloError` codes: `schema_provisioning_forbidden` (`403` — a push tried to create tables in a database where the engine isn't permitted to run DDL), `request_too_large` (`413` — a query or commit body exceeded the size limit), `commit_operation_invalid` (`400` — a malformed commit operation), and `upload_not_configured` (`503` — an upload was attempted with no blob store wired).
890
-
891
- - **`@abloatai/ablo/wire` now exports the protocol schemas.** `commitOperationSchema`, `commitPayloadSchema`, and the frame schemas let tooling and tests validate client↔engine frames against the same contract the engine enforces.
892
-
893
- - **Protocol version negotiation is surfaced.** `@abloatai/ablo/wire` exports `PROTOCOL_VERSION`, `MIN_SUPPORTED_PROTOCOL_VERSION`, `WS_CLOSE_PROTOCOL_VERSION`, `PROTOCOL_VERSION_HEADER`, `protocolVersionProblem`, and `ERROR_CONTRACT_VERSION` — the single integer the client and server compare on connect to detect an incompatible peer and fail with a clear reason instead of a cryptic wire error.
894
-
895
- - **`@abloatai/ablo/coordination` exposes the conflict-policy vocabulary** — `defaultPolicy`, `capabilityPreemptPolicy`, and `interpretConflictAxis` — for server-side consumers building on the claim model. `@abloatai/ablo/schema` gains the role primitives (`identityRole`, `entityRole`, and their types and schemas) for declaring identity- and entity-scoped roles.
896
-
897
- - **Removed unused API surface.** Dropped the `@abloatai/ablo/server/next` subpath export and the unused `MutationDispatcher` interface (plus its `MockMutationDispatcher` test double) and the offline-mutation-queue internals behind them — none had documented consumers. Every supported entry point (`@abloatai/ablo`, `/ai-sdk`, `/react`, `/source`, `/coordination`, `/schema`, `/keys`, `/auth`, `/wire`, …) is unchanged.
898
-
899
- - **Internal:** the SDK's largest modules — the `Ablo` client, the data-source runtime, the transaction queue, and the WebSocket sync loop — were decomposed into cohesive leaf modules. No public runtime API or import path changed.
900
-
901
- - **CLI & docs.** `ablo push` prints a calmer, information-first deploy banner (a production push still requires typing the project name to confirm). The README and `AGENTS.md` now lead with the one-path logical-replication model and clarify that sandbox is test-mode only — in production your rows stay in your database and Ablo holds only the transaction log.
902
-
903
- ## 0.25.0
904
-
905
- ### Minor Changes
906
-
907
- - ca30064: Logical replication is now the documented default storage path, with self-service data-source registration from the CLI.
908
- - **`ablo connect --register`** — registers your database as an Ablo data source over logical replication in one step: it runs the same pre-flight replication probe `ablo connect` uses (server reachable, `REPLICATION` privilege, `wal_level=logical`, publication/slot creatable), and on success `POST`s the connection to the engine's `/api/v1/datasources`. The `ek_`-authed call scopes the source to your org automatically; the password is stored decomposed as a secret, never echoed back. This is the "registration is the enable" path — there is no separate tier or flag to pick.
909
- - **`ablo init` leads with logical replication** — the default storage mode is now `replication` (was `endpoint`); the generated env + next-steps point at `ablo connect` / `ablo connect --register`. The signed-endpoint and direct modes remain as the explicit fallback / legacy options.
910
- - **`ablo status`** — a data-plane diagnostic that probes whether your registered source is reachable and replicating (failure-only reporting, so it never falsely reports healthy).
911
- - **`ablo push`** — minor guard/UX refinements (deploy-target clarity).
912
- - **Docs (README, `docs/data-sources.md`, `llms.txt`)** rewritten to the one-path model: Ablo consumes your database's logical-replication stream and your application owns the write path. The security wording is precise — a logical-replication connection requires the `REPLICATION` privilege (it is **not** a read-only SQL account), so reviews are not misled by a "read-only" claim.
913
-
914
- No breaking changes to the SDK runtime API. The server-side WAL read cutover these CLI changes support ships by deploying the engine, not this package.
915
-
916
- ## 0.24.0
917
-
918
- ### Minor Changes
919
-
920
- - Multi-agent coordination for AI SDK tools + safer `ablo push`.
921
- - **`coordinatedTool` (`@abloatai/ablo/ai-sdk`)** — one call turns an Ablo model
922
- write into a Vercel AI SDK `tool()` with concurrency coordination handled, so an
923
- AI agent can contribute to shared state without clobbering concurrent writers.
924
- Three strategies: `merge` (default — delegates to the functional update's
925
- compare-and-swap + backoff, self-healing accumulate), `claim` (mutual exclusion,
926
- returns a `claimed` signal the model retries on), and `queue` (SQS-style
927
- poll-acquire over HTTP). The `ai-sdk` entry now also documents the canonical
928
- multi-agent coordination model (surface-the-signal + back-off laws, strategy
929
- table).
930
- - **`ablo push` guards** — `--dry-run`/`--plan` prints the deploy target, a
931
- model-level diff vs the deployed schema, and git state, then exits without
932
- applying. Production deploys now require a typed confirmation (and refuse an
933
- uncommitted schema unless `--allow-dirty`); sandbox confirms interactively.
934
- `--yes`/`-y` skips confirmation for CI.
935
-
936
- ## 0.23.0
937
-
938
- ### Minor Changes
939
-
940
- - 2807efb: `create` now returns the created row, not a `CommitReceipt`.
941
-
942
- The WebSocket client's `create` already returned the row (`T`); the HTTP client
943
- and the `.model(name)` accessor returned a `CommitReceipt`, so "create returns
944
- the thing I created" only held on one transport. Both now return the confirmed,
945
- authoritative server row (framework defaults like `createdAt`/`createdBy`
946
- included). For an idempotent re-create of an existing caller-supplied id, the
947
- EXISTING row is returned (not the input).
948
-
949
- BREAKING (HTTP / `.model()` callers only): `await ablo.<model>.create(...)` now
950
- resolves to the row instead of `{ status, lastSyncId, ... }`. Code that ignored
951
- the return value, or that read `.id` (the row carries `id` too), is unaffected;
952
- code that read `lastSyncId` / `serverTxId` / `status` off a typed model create
953
- should use the raw `commits.create(...)` resource, which still returns a
954
- `CommitReceipt`. WebSocket-client callers are unaffected (already returned `T`).
955
-
956
- - 2807efb: `delete` is idempotent — deleting an already-absent row is a no-op success, not
957
- an error.
958
-
959
- The WebSocket client's `delete` threw `entity_not_found` when the row wasn't in
960
- the local pool, while the HTTP client returned without error — so "delete this"
961
- was a hard edge on one transport. Both now agree: a row that isn't present is
962
- already gone, so the delete succeeds with no effect. This is AIP-135's
963
- recommended behavior for client-assigned-id / declarative APIs (Ablo is exactly
964
- that), and it makes delete safe to retry and to race (two actors deleting the
965
- same row). The deliberate "loud 0-row" assertion in `@ablo/slides-sdk` is
966
- unchanged (it keeps its own `allowMissing` opt-out).
967
-
968
- - 2807efb: `retrieve` reports a missing row as `data: undefined` instead of throwing.
969
-
970
- The HTTP client previously threw `model_not_found` for a missing row while the
971
- WebSocket client returned `T | undefined` — so the obvious read ("does this row
972
- exist?") was a hard edge an agent had to wrap in `try/catch` on one transport
973
- only. Both transports now agree: an absent row is data-absence, not an error.
974
- `ModelRead.data` is now `T | undefined` (matching the documented `.data?.x`
975
- usage). Taking a `claim` on a row that doesn't exist still throws
976
- `AbloNotFoundError` — a claim has nothing to hold.
977
-
978
- ## 0.22.1
979
-
980
- ### Patch Changes
981
-
982
- - Expose the functional `update(id, current => next)` overload on the stateless
983
- HTTP client type (`HttpModelClient` / `AbloHttpClient`).
984
-
985
- 0.22.0 wired the functional update at runtime on every transport and added the
986
- overload to `ModelOperations` (WebSocket) and `ModelClient`, but the
987
- `Ablo({ transport: 'http' })` client resolves its models to `HttpModelClient`,
988
- whose `update` type still declared only the `update({ id, data })` form. So
989
- server-side agents — the primary callers — saw a type error on
990
- `update(id, fn)` even though it worked. Add the overload to that type.
991
-
992
- ## 0.22.0
993
-
994
- ### Minor Changes
995
-
996
- - Add the functional update form: `ablo.<model>.update(id, current => next)`.
997
-
998
- The `setState(prev => next)` of the data layer. Pass a function of the latest
999
- row and the SDK owns everything that used to be the caller's problem under
1000
- contention: it reads the freshest row, runs your updater, writes it as a
1001
- compare-and-swap against the row's watermark, and re-reads + re-runs on any
1002
- concurrent write. No claim, no per-participant identity, and no
1003
- `stale_context` / `claim_*` codes ever surface — correctness rides on the
1004
- watermark, so concurrent writers reconcile instead of silently clobbering. The
1005
- write either lands or throws a single `AbloContentionError` once its reconcile
1006
- budget is spent.
1007
-
1008
- Identical guarantee on both transports (HTTP and WebSocket share one reconcile
1009
- loop). Return `null`/`undefined` from the updater to skip the write. Tune with
1010
- `{ retries, signal }`. Exports: `AbloContentionError`, `ModelUpdater`,
1011
- `ContentionOptions`, `DEFAULT_CONTENTION_RETRIES`.
1012
-
1013
- The classic `update({ id, data })` form is unchanged.
1014
-
1015
- ## 0.21.0
1016
-
1017
- ### Minor Changes
1018
-
1019
- - Coordination observability now fires on BOTH transports. Previously `captureClaim`/`captureConflict` were emitted only by the WebSocket transport, so a `ClaimLog` (or any `observability` provider) handed to a stateless HTTP client — the transport server-side agents use via `Ablo({ transport: 'http' })` — stayed empty, and even on WebSocket a hard commit rejection went unrecorded. Fixed:
1020
- - **HTTP transport now emits.** `Ablo({ transport: 'http', observability })` records `claim` acquisition (`captureClaim`) and coordination-conflict rejections (`captureConflict`, code `stale_context` / `claim_conflict` / `entity_claimed`) on BOTH HTTP write doors (`commits.create` and per-model `ablo.<model>.update/create/delete`). The conflict names the collided rows — from the server's `conflicts` detail when present, otherwise the ops the write attempted. `observability` is now a documented option on the HTTP client.
1021
- - **WebSocket rejections now recorded.** A commit rejected by the conflict policy (`mutation_result` `success: false` with a coordination code) now calls `captureConflict`, mirroring the existing notify-on-success path. So `ClaimLog.collisions()` no longer silently misses rejected writes.
1022
-
1023
- Net effect: a `ClaimLog` behaves identically regardless of transport — `entries`, `collisions()`, and `onChange` reflect the real coordination timeline for headless agent evals and live activity feeds alike.
1024
-
1025
- ## 0.20.2
1026
-
1027
- ### Patch Changes
1028
-
1029
- - Extend the `HeldClaim` return type to the HTTP transport. 0.20.1 fixed `await using` on the WebSocket client's `claim()` but missed the stateless HTTP client (`HttpClaimApi`) used by server-side agents, which still returned the looser `Claim<T>`. Both transports' `claim()` now return `HeldClaim<T>`, so `await using held = await ablo.<model>.claim(...)` typechecks regardless of transport.
1030
-
1031
- ## 0.20.1
1032
-
1033
- ### Patch Changes
1034
-
1035
- - Fix `await using held = await ablo.<model>.claim(...)` failing to typecheck. `claim()` now returns a `HeldClaim<T>` — a `Claim<T>` with `data`, `release`, `revoke`, and the async disposer made `Required` (they're optional on the base `Claim<T>`, which also models observed peer claims that lack them). A held claim is therefore assignable to `AsyncDisposable`, so the `await using` auto-release pattern compiles. Observed claim surfaces still return the looser `Claim<T>`. `HeldClaim` is exported.
1036
-
1037
- ## 0.20.0
1038
-
1039
- ### Minor Changes
1040
-
1041
- - Reactive reads now work out of the box. A read like `useAblo((a) => a.documents.get(id))` re-renders when a live delta updates the row — including in-place field updates (the common collaborative case), which previously fired no reaction and left the UI silently stale.
1042
-
1043
- Two changes make this work:
1044
- - **Models are reactive by default.** Schema fields are now MobX-observable without opting in. `json` fields stay `observable.ref` (one atom for the whole blob, not a deep atom tree per node), so the default is cheap. Opt out per model with `lazyObservable: false` for very large read-only list models where the QueryView's entry-replaced reactivity is enough.
1045
- - **`useAblo` returns a plain row snapshot** (via the new `Model.toReactiveSnapshot()`) instead of the live model instance. Reading the fields inside the tracked function is what subscribes the reaction (MobX tracks property access, not values), and the fresh snapshot identity lets the hook detect the change. Consumers get plain row objects and never touch a MobX observable directly.
1046
-
1047
- Also new: `deepEqual` and `stableStringify` exports for comparing `field.json()` values. A `jsonb`-backed json field round-trips with reordered object keys (Postgres `jsonb` does not preserve key order), so a naive `JSON.stringify(a) === JSON.stringify(b)` comparison is unreliable when reconciling against external state (e.g. a rich-text editor). These helpers compare key-order-insensitively.
1048
-
1049
- ## 0.19.0
1050
-
1051
- ### Minor Changes
1052
-
1053
- - **Claim observability — a `ClaimLog` you can print or assert on.** A new
1054
- `observability` provider hook lets you tap every claim event and stale-write
1055
- collision the client sees. Hand `new ClaimLog()` to `Ablo({ observability })`
1056
- and it collects an ordered, readable log — `formatClaim` / `formatConflict`
1057
- render one line per event, and `collisions()` returns the conflicts for eval
1058
- assertions. New exports: `ClaimLog`, `formatClaim`, `formatConflict`,
1059
- `noopObservability`, and the types `ClaimLogEntry`, `ClaimEvent`,
1060
- `ConflictEvent`, `SyncObservabilityProvider`. Spread `noopObservability` to
1061
- override only the hooks you care about.
1062
-
1063
- **AWS-shaped CLI credential store + `ablo config`.** Local CLI state is now split
1064
- into two files, matching `~/.aws/config` vs `~/.aws/credentials`: `config.json`
1065
- holds non-secret settings (active environment + active project) and is safe to
1066
- print or let an agent read; `credentials.json` holds the keys (0600, never
1067
- printed), keyed by project profile then environment. Per-project profiles follow
1068
- Stripe's model — `ablo projects use <slug>` selects the active profile, and a
1069
- key's project is fixed at mint so selecting a project never re-scopes an existing
1070
- key. `ablo status` now reports the resolved profile and environment.
1071
-
1072
- **Schema JSON-column reconciliation.** `generateJsonColumnReconciliation` (new
1073
- export) emits the DDL to reconcile JSON-backed columns when adopting or evolving
1074
- an existing schema.
1075
-
1076
- **Breaking (0.x):**
1077
- - The claim handle type `ClaimHandle` is renamed to **`Claim`**, and its
1078
- identifier field is `id` (was `claimId`). Update type imports and any code
1079
- reading `.claimId`.
1080
- - The ai-sdk `claimBroadcastMiddleware` (and `./ai-sdk/claim-broadcast`) is
1081
- removed — coordination broadcast is handled by `coordinationContextMiddleware`.
1082
- Import `ClaimTarget` from the package root or `@abloatai/ablo` ai-sdk's
1083
- `coordination-context` instead of `claim-broadcast`. The inline-claim option is
1084
- `reason` (not the pre-0.12 `action`); the ai-sdk docs are corrected to match.
1085
-
1086
- ## 0.18.0
1087
-
1088
- ### Minor Changes
1089
-
1090
- - **Client observability — `debug` / `logLevel`, off by default.** The SDK used to
1091
- emit a `debug` line per model and per property during schema registration (a
1092
- firehose). It now defaults to a quiet `warn` threshold and exposes two new
1093
- `Ablo()` options to opt back in:
1094
- - `logLevel: 'debug' | 'info' | 'warn' | 'error' | 'silent'` — `'info'` surfaces
1095
- coordination and connection events without the per-model registration noise;
1096
- `'debug'` is everything. Precedence: explicit `logLevel` → `debug: true` →
1097
- `ABLO_LOG_LEVEL` env → default `warn`. Supplying your own `logger` bypasses both.
1098
- - `debug: boolean` — shorthand for `logLevel: 'debug'`.
1099
-
1100
- Coordination is now traceable at `info`: claims that are **rejected** or **lost**
1101
- (preempted/expired), and your position **advancing in a claim queue**, each log
1102
- once per change with a readable target (`documents:abc.title`) — quiet lowercase
1103
- lines, no shouty tags.
1104
-
1105
- **New: canonical wire-egress contract export.** `errorEnvelope`, `statusForType`,
1106
- and the `ErrorEnvelope` type are now exported from the package root. Server
1107
- consumers (e.g. a self-hosted sync server) can assert against the one source of
1108
- truth for the error-envelope shape and the `AbloError`-subclass→HTTP-status
1109
- table instead of keeping a copy that silently drifts.
1110
-
1111
- **Structured CLI error rendering.** CLI failures render as a titled block with a
1112
- reason code and per-code remediation (`--verbose` for the stack) instead of a
1113
- console wall-of-text; `AbloError.toString()` produces a leak-proof one-liner.
1114
-
1115
- **`ABLO_API_KEY` resolution + sandbox key scopes.** The key is now resolved from
1116
- `.env.local` / `.env` (not just the process env), and sandbox keys are granted
1117
- `schema:push` by default so `ablo push` works out of the box in a fresh sandbox.
1118
-
1119
- ## 0.17.0
1120
-
1121
- ### Minor Changes
1122
-
1123
- - **Bring-your-own database is now one model.** Ablo connects to your Postgres and
1124
- never operates it. There used to be two confusing BYO paths, and the
1125
- connection-string one would create roles, force row-level security, transfer
1126
- table ownership, and push you to run `ablo migrate` before anything worked. That
1127
- cascade is gone. Ablo now follows the shape every serious "sync over your own
1128
- Postgres" engine uses (ElectricSQL, PowerSync, Zero): it reads your database via
1129
- Postgres logical replication and never runs DDL, creates roles, forces RLS, or
1130
- rewrites your `DATABASE_URL`. You own your schema; Ablo reads it.
1131
- - **New: `ablo connect`.** One command prints the exact, copy-pasteable setup for
1132
- your own Postgres — enable `wal_level=logical`, create the `ablo_publication`
1133
- publication and a least-privilege `ablo_replicator` role — and
1134
- `ablo connect --check` validates readiness (wal level, publication, replication
1135
- grant, replica identity). This is the single supported way to connect a real
1136
- database.
1137
- - **`ablo migrate` left the happy path.** It no longer creates roles, transfers
1138
- ownership, or rewrites your connection string, and `ablo dev` no longer attempts
1139
- a scoped-role creation on every watch loop. `migrate` is now an optional escape
1140
- hatch for generating starter DDL (`--dry-run` prints the SQL).
1141
- - **Clearer failures.** `ablo push` permission errors lead with the server's actual
1142
- reason code and per-code remediation instead of a generic "needs `schema:push`
1143
- scope," and the schema-conflict message names which environment/version a prior
1144
- push came from and when.
1145
- - **Logical-replication runtime is in Preview.** The setup (`ablo connect`) and the
1146
- connection model are live; the server-side WAL consumer that streams your changes
1147
- is implemented and journey-tested but not yet generally available.
1148
-
1149
- The previous connection-string-operate and adapter/outbox modes are demoted to a
1150
- clearly-labeled **Legacy / not recommended** section — they still work, but new
1151
- integrations should use logical replication.
1152
-
1153
- ## 0.16.3
82
+ Live clients now defer reactive model activation until state reaches a
83
+ consumer-visible boundary and keep cache eviction work bounded under sustained
84
+ ingestion. Optimistic state and actively observed models remain immediately
85
+ reactive.
1154
86
 
1155
87
  ### Patch Changes
1156
88
 
1157
- - **Docs.** The bundled SDK docs are now the single source for the documentation
1158
- site, and several pages were expanded or corrected:
1159
- - The sessions/identity model is reframed around **projects** — push one schema
1160
- to a project, mint an `ek_` per user (your users need no Ablo account), and
1161
- all of them commit to that one schema. Per-customer org isolation
1162
- (`schemaProject`) is presented as the add-on it is, not the default.
1163
- - The declarative `conflict` schema axis (Axis 3) is now documented.
1164
- - The agent docs were corrected to the current claim vocabulary
1165
- (`reason`/`queue`, not the pre-0.12.0 `action`/`wait`).
1166
-
1167
- No code changes.
1168
-
1169
- ## 0.16.2
1170
-
1171
- ### Patch Changes
1172
-
1173
- - **`mintUserSessionKey`: name the shared-schema binding around the project.** The
1174
- two flat options added in 0.16.0 (`schemaOwnerOrgId` + `schemaProjectId`) are
1175
- replaced by one project-centric option — `schemaProject: { organizationId, projectId }` —
1176
- naming "the project that owns the schema" as a single concept. The wire format
1177
- is unchanged (the SDK still sends the same keys), so no server redeploy is
1178
- needed. Released as a patch: the replaced options shipped in 0.16.0 and have no
1179
- external consumers yet.
1180
-
1181
- ```ts
1182
- // before
1183
- mintUserSessionKey({ organizationId, schemaOwnerOrgId, schemaProjectId, ... });
1184
- // after
1185
- mintUserSessionKey({
1186
- organizationId, // data org
1187
- schemaProject: { organizationId, projectId }, // the project that owns the schema
1188
- ...
1189
- });
1190
- ```
1191
-
1192
- ## 0.16.1
1193
-
1194
- ### Patch Changes
1195
-
1196
- - **Fix `ablo login` against the standalone auth server.** The device flow now
1197
- targets two origins instead of one: the RFC 8628 device endpoints
1198
- (`/api/auth/device/*`) go to the identity server (`auth.abloatai.com`, override
1199
- `ABLO_AUTH_URL`), while the human approval page (`/cli`), sign-up, and the
1200
- key-handoff route (`/api/cli/provision-key`) go to the dashboard host
1201
- (`www.abloatai.com`, new override `ABLO_DASHBOARD_URL`). Previously every step
1202
- ran against `www`, where the device endpoints no longer resolve —
1203
- producing "Couldn't start login… Is the dashboard reachable?". The CLI now also
1204
- builds the approval URL itself rather than trusting the server's
1205
- `verification_uri`, which (being a relative `/cli`) resolved against the auth
1206
- server's origin to a 404.
1207
-
1208
- ## 0.16.0
1209
-
1210
- ### Minor Changes
1211
-
1212
- - **Axis 3 — declare write-conflict behaviour in the schema (new).** A model can now
1213
- state what happens when a commit collides with a foreign claim or a stale snapshot —
1214
- per committer kind (`user` / `agent` / `system`) — right next to its fields, using the
1215
- same `overwrite | reject | notify` vocabulary as the `onStale` write guard. It is a
1216
- third axis, orthogonal to `policy` (read access) and `groups` (delta routing).
1217
- - **`conflict` on `model()`** — a plain, serializable disposition map. Pure data, so it
1218
- round-trips through the schema registry to the server; the generic engine interprets it
1219
- at the commit chokepoint (no per-model logic in the engine).
1220
-
1221
- ```ts
1222
- // "a human's edit always wins (never blocked); an agent yields"
1223
- conflict: { user: 'overwrite', agent: 'reject' }
1224
- ```
1225
-
1226
- - **Composable authoring helpers (new, from `@abloatai/ablo/schema`)** — disposition
1227
- functions plus a `cn`/`cx`-style combinator, so conflict policy reads like the rest of
1228
- the DSL (`relation.belongsTo()`) and like modern config (`plugins: [admin(), …]`):
1229
-
1230
- ```ts
1231
- import { coordination, humansOverwrite, agentsReject } from '@abloatai/ablo/schema';
1232
-
1233
- conflict: coordination(humansOverwrite(), agentsReject());
1234
- // → { user: 'overwrite', agent: 'reject' }
1235
- ```
1236
-
1237
- Exports: `coordination`, `humansOverwrite` / `humansReject` / `humansNotify`,
1238
- `agentsOverwrite` / `agentsReject` / `agentsNotify`,
1239
- `systemOverwrite` / `systemReject` / `systemNotify`, and the `ConflictRule` type.
1240
-
1241
- - An omitted committer kind falls through to the engine default (reject; honor
1242
- `onStale: 'notify'`), so this is fully additive — existing schemas are unchanged.
1243
- New public types `ConflictAxis` (also `Ablo.Conflict.Axis`) and the
1244
- `interpretConflictAxis` interpreter are exported for custom policy composition.
1245
-
1246
- - **First-party shared schema for ephemeral keys (new).** `mintUserSessionKey` now accepts
1247
- `schemaProjectId` + `schemaOwnerOrgId`, binding the minted `ek_` to a schema owner-org +
1248
- project so **schema** resolves org-independently (one schema serves all of an integrator's
1249
- end-user orgs) while **data** stays scoped to `organizationId`. Requires the `sk_` to carry
1250
- `ephemeral:mint-any-org`; omit both for the existing per-org (BYO) behaviour.
1251
-
1252
- ## 0.15.1
1253
-
1254
- ### Patch Changes
1255
-
1256
- - Loud 0-row writes: surface unmatched UPDATE/DELETE ids and add `AbloNotFoundError`
1257
-
1258
- A commit now reports the ids of any UPDATE/DELETE that matched zero rows on
1259
- `CommitReceipt.missingIds`, and the new exported `AbloNotFoundError` lets typed
1260
- write wrappers throw instead of silently treating a missed write as success.
1261
- Additive and back-compatible (the field is omitted when nothing missed). This
1262
- unblocks the slides-sdk name-addressing / own-your-id work, which relies on a
1263
- loud failure when a stale id is written.
1264
-
1265
- ## 0.15.0
1266
-
1267
- ### Minor Changes
1268
-
1269
- - **Notify-instead-of-abort: non-coercive conflict handling + read-set (the "did anything I looked at change?" layer).**
1270
-
1271
- The principle: on a stale-context conflict the engine now **surfaces the current state and lets the actor — agent or human — resolve it**, instead of forcing an outcome. See `docs/concurrency-convention.md`.
1272
-
1273
- **`onStale` redesigned — Stripe-aligned values (BREAKING).**
1274
-
1275
- The mode set is now `'reject' | 'overwrite' | 'notify'`. Each value names its outcome:
1276
- - **`notify` (new, non-coercive)** — the conflicting write is **held** (not applied) and the commit returns a `StaleNotification` carrying the conflicting field's _current_ value, so the actor reconciles and re-commits rather than losing work. The rest of the batch still commits.
1277
- - **`overwrite`** (was `force`) — blind last-writer-wins, no signal.
1278
- - **`reject`** (default, unchanged) — throws `AbloStaleContextError`.
1279
-
1280
- Migration:
1281
- - `onStale: 'force'` → `onStale: 'overwrite'`.
1282
- - `onStale: 'flag'` / `onStale: 'merge'` → `onStale: 'notify'` (both removed; `notify` is the single hold-and-surface mode).
1283
-
1284
- **`StaleNotification` — the new advisory signal.** New public type + `staleNotificationSchema`:
1285
- `{ object: 'stale_notification', model, id, readAt, observedSyncId, conflictingFields, currentValues, writtenBy, group? }`. Delivered two ways:
1286
- - on the receipt — `CommitReceipt.notifications` (and `CommitResult.notifications`);
1287
- - on a new SDK event — **`conflict:notified`** `{ clientTxId, notifications }` (mirrors `reconciliation:needed` / `sync:rollback`).
1288
-
1289
- **Read-set (`reads[]`) — declare what you looked at, not just what you write (new).** A commit may carry batch-level read dependencies; a moved premise fires that entry's `onStale` over the whole batch (`notify` holds every write + notifies, `reject` aborts, `overwrite` proceeds). Two granularities:
1290
- - **Row** — `{ model, id, readAt, fields? }`: did this row (optionally these fields) change?
1291
- - **Group** — `{ group, readAt }`: did anything in this sync group (`deck:abc`, `org:X`) change? — the same unit a participant watches and claims.
1292
-
1293
- New public type `ReadDependency` + `readDependencySchema`; available on `ablo.commits.create({ operations, reads })` and the lower-level write options. This closes the gap the write-target check alone could not: a premise that changed without the written row changing.
1294
-
1295
- **Conflict policy.** `ConflictDecision` gains `{ action: 'notify' }`; `defaultPolicy` maps `onStale: 'notify'` → notify-and-hold, everything else → reject. `StaleContextConflict.requestedMode` is added so custom policies can honor the caller's declared intent.
1296
-
1297
- - **Data Source reverse-channel connector (new).** A customer Data Source can now **dial out** to the engine over a single outbound WebSocket (`ablo.source.v1` subprotocol) instead of exposing an inbound HTTP endpoint — the deployment shape private/VPC stores need.
1298
- - **`createSourceConnector({ apiKey, handler, baseURL? })`** (new public API, exported from the root and `/source`) — opens one outbound socket (Node global `WebSocket`, no new dependency), with reconnect/backoff, and serves the customer's existing Data Source `handler`.
1299
- - Server side: a connector registry + `/v1/source/listen` upgrade route bridge requests down / responses up, teed into `SourceClient` through the storage resolver.
1300
- - **Trust model unchanged:** the Standard-Webhooks HMAC is signed _above_ the transport, so the socket carries the signed envelope byte-for-byte and the customer's `verifyAbloSourceRequest` is untouched. Transport changes, trust model doesn't.
1301
- - Opt-in per source via `reverse_channel_prod` (migration `20260622150000`); gated in `authorizeUpgrade`.
1302
-
1303
- ## 0.14.0
1304
-
1305
- ### Minor Changes
1306
-
1307
- - Claim API consistency + coordination docs
1308
- - **React:** document `useWatch` (scoped presence + read-interest, with `claim`/`hydrate`/`paused` options) and `usePeers` (read-only presence) — previously exported but undocumented.
1309
- - **HTTP claim surface:** `HttpClaimApi` is now a mechanically derived async projection of the reactive `ClaimApi` (`AwaitedClaimMethod`), so the two transports can never drift. No behavior change — the only difference remains the `Promise` wrapper that statelessness forces on `state`/`queue`/`reorder`.
1310
- - **Naming:** unified the claim read verb to `state` across every layer (the internal `ModelCollaboration.observe` is now `state`, matching the public `ablo.<model>.claim.state({ id })`).
1311
- - **Docs:** corrected the `Claim` object reference — the field is `reason` (serialized on the wire as `action`), and `createdAt`/`expiresAt` are `number` (epoch-ms), not strings; corrected the claim options to `reason` and `queue`.
1312
-
1313
- ## 0.13.0
1314
-
1315
- ### Minor Changes
1316
-
1317
- - Schema authoring: split model routing into two orthogonal axes — `policy` (row access) and `groups` (sync-group routing).
1318
-
1319
- **Breaking (schema authoring).** The flat, collision-prone model options are replaced by two namespaced ones:
1320
- - **`policy`** — row-access / tenant isolation (named after Postgres/Supabase RLS policies: the rule that scopes which rows a tenant may read). A discriminated union on `by` replaces the old `orgScoped` / `scopedVia` / `orgColumn` trio:
1321
- - `{ by: 'column' }` — row-local tenancy column (the default when omitted; column name still overridable).
1322
- - `{ by: 'parent', fk, parent }` — inherit tenancy through a foreign key when the table has no tenancy column of its own (e.g. `slide_layers` → `slides`).
1323
- - Type `TenancyInput` is renamed `PolicyInput`; `policyInputSchema` / `resolvePolicy` are now exported.
1324
- - **`groups: { root, grants, roles }`** — which delta channels a row fans into (orthogonal to `policy`, which governs read access). One namespaced object replaces the old flat `scope` / `grants` / `entityRoles`:
1325
- - `root` (was `scope`) — mark a model a scope root; its records form the group `<kind>:<id>`. Renamed so it no longer collides with the old `scopedVia` tenancy sugar or the inner `grants.scope` relation name.
1326
- - `grants` — a membership edge granting an identity access to a scope root.
1327
- - `roles` (was `entityRoles`) — explicit non-relational record→group roles; accepts one role or an array.
1328
- - `groupsInputSchema` / `GroupsInput` are now exported.
1329
-
1330
- **CLI.** `config.json` now stores per-project profile key pairs (`profiles: Record<string, ProfileKeys>`) instead of a single top-level pair; older flat layouts are folded into the active profile automatically on read, so existing logins keep working. `login` / `projects` updated to the profile model.
1331
-
1332
- ## 0.12.0
1333
-
1334
- ### Minor Changes
1335
-
1336
- - Canonicalize the claim API to one vocabulary, plus DX fixes (breaking).
1337
- - BREAKING: claim phase field `action` → `reason` on every claim surface
1338
- (`Claim`, `ClaimHandle`, `ClaimCreateOptions`, `ModelClaim`, ...). The wire
1339
- is unchanged (still `action`, healed on read) — no server redeploy needed.
1340
- - BREAKING: claim contention flag `wait` → `queue` (one word everywhere).
1341
- - BREAKING: React hook `useParticipant` → `useWatch` (aligns with `ablo.<model>.watch`).
1342
- - `ClaimDeclaration.ttlSeconds` is now `number` (was a `Duration`).
1343
- - Docs: `retrieve` HTTP envelope (`.data`/`.stamp`) called out; `syncGroups`
1344
- reworded (provisional, not deprecated); `orgScoped` cross-tenant security
1345
- warning; React error strings point at `<AbloProvider>`.
1346
-
1347
- ## 0.11.2
1348
-
1349
- ### Patch Changes
1350
-
1351
- - a35d935: Fix stream-recorded undo capturing the wrong "before" value for updates. A second
1352
- update to the same field before the first sync-ack re-captured the original
1353
- pre-session value (first-old-wins + clear-only-on-ack), so undo of a quick second
1354
- edit jumped all the way back instead of one step. The queue now re-baselines a
1355
- field's tracked `.old` once its before-image is frozen into the committed
1356
- transaction.
1357
-
1358
- Also close the create/update undo asymmetry: an update whose written key had no
1359
- in-place mutation produced an empty `previousData`, which made the inverse
1360
- un-revertible (a create's `delete` inverse never is). Before-image capture now
1361
- falls back to the last loaded/acked snapshot.
1362
-
1363
- Internally, the two undo paths (stream-recorded and manual `RecordingTransaction`)
1364
- now share one before-image implementation via `Model.capturePreviousValues` /
1365
- `Model.consumeModifiedFields`, so they can no longer drift.
1366
-
1367
- - One-correct-way consolidation (breaking; no external consumers yet, so released as a patch):
1368
- - Credentials collapse to a single `apiKey` — a string, or a `() => Promise<string | null>` that
1369
- fetches a per-user token. Removed `getToken` / `authEndpoint` / public `authToken`.
1370
- - `ablo.<model>.watch(ids, { ttl })` replaces the top-level `ablo.participants.join({ scope })` —
1371
- model-scoped read-interest + presence (WebSocket only).
1372
- - Read claim-gating is `ifClaimed: 'return' | 'fail'` (removed `'wait'`); waiting is the claim
1373
- primitive's job (`ablo.<model>.claim`).
1374
- - The stateless client is `Ablo({ transport: 'http' })`; `createAbloHttpClient` is no longer a
1375
- public export (the factory uses it internally).
1376
- - Read-option types renamed: `ServerReadOptions` (server `retrieve`/`list`) and `LocalReadOptions`
1377
- (local `get`/`getAll`).
1378
- - `defineSchema` throws a clear error on a reserved-field collision; the MCP/docs API surface is
1379
- now compile-time bound to the real exported types (can't drift).
1380
-
1381
- ## 0.11.1
1382
-
1383
- ### Patch Changes
1384
-
1385
- - 7f91f6e: DX hardening from a real onboarding session — onboarding, CLI, coordination, types, and docs.
1386
-
1387
- **Client behavior**
1388
- - `databaseUrl` is now an explicit, server-only option: `Ablo(...)` no longer auto-reads `process.env.DATABASE_URL`. A stray `DATABASE_URL` (common — Prisma/Drizzle/docker set it) no longer silently flips the client into connection-string mode; a one-time warning points at the explicit option. Passing `databaseUrl: process.env.DATABASE_URL` explicitly is unchanged.
1389
- - Claims/presence are now observable from any client (including Node agents): reading a row enters its entity sync group (read-interest) and claiming pins it (write-intent), so `ablo.<model>.claim.state({ id })` reports co-participants without any manual subscribe step — whether the observer arrives before the claim (live delta) or after it (subscribe-time backfill). The claim **holder** now also sees its own claim via `claim.state`. **Requires a coordinated `sync-server` deploy** (the subscribe-time claim backfill + the entity-scope subscription gate that lets an org-authority agent key narrow into a row's group live server-side); the client package change alone does not deliver cross-client agent observation.
1390
-
1391
- **CLI**
1392
- - `ablo init` detects the `src/app` layout (routes + the `@/ablo` import alias resolve correctly), writes the **real** stored sandbox key into `.env.local` instead of a placeholder, and scaffolds `ablo/register.ts` (a regular module, not a colliding `ablo.d.ts`).
1393
- - `ablo <command> --help` / `-h` now prints usage instead of erroring with "unknown flag", and `migrate` is listed in the top-level help.
1394
- - `ablo dev --no-watch` now exits after one push instead of watching forever.
1395
-
1396
- **Types**
1397
- - Name the client with `typeof sync` (the value-inferred idiom, like tRPC's `typeof appRouter` / Drizzle's `typeof db`) — `ReturnType<typeof Ablo>` collapses to the untyped client and should not be used. No bespoke client-type generic is needed.
1398
- - `model_claim_not_configured` message clarified: claiming needs no per-model schema configuration; every model is claimable through the standard client.
1399
-
1400
- **Docs**
1401
- - Reconciled the self-contradictory `databaseUrl` story (it is an explicit, server-only option, not auto-read from the environment; consistent casing), documented that the sandbox can host rows (apiKey only, no database), explained why a localhost Postgres can't be the system of record, and led the connect-your-database flow with `ablo pull`/`ablo check` over `ablo migrate`. Fixed stale `api.md` vocabulary (`object: 'claim'`, `participantKind: 'user' | 'agent' | 'system'`).
1402
-
1403
- - 7f91f6e: Docs: document the completed `intent` → `claim` rename. Adds a 0.11.0 migration entry (`useIntent` → `useClaim`, `Register.Intents` → `Register.Claims`, `Ablo.Intent.*` → `Ablo.Claim.*`, and the coordinated client/server deploy for the `claim_*` wire frames), a `useClaim` section in the React reference, and fixes the stale `participantKind` union to the canonical `'user' | 'agent' | 'system'`.
1404
-
1405
- ## 0.11.0
1406
-
1407
- ### Minor Changes
1408
-
1409
- - Canonical `claim` vocabulary, sync-group area-of-interest, and richer claim-rejection errors.
1410
- - **`intent` → `claim` everywhere.** The coordination primitive is now a `Claim` across the public surface: `useClaim` replaces `useIntent`, the `Ablo.Claim.*` namespace replaces `Ablo.Intent.*`, and module augmentation registers `Claims` instead of `Intents` on the `Register` interface. The underlying wire frames moved from `intent_*` to `claim_*` — clients and servers must run a `claim_*`-aware build together.
1411
- - **Sync-group area of interest.** A client's read interest is no longer frozen at connect: the new `update_subscription` frame drives live re-indexing, and `enterScope` / `leaveScope` / `pinScope` / `unpinScope` let a store narrow or widen what it streams. `AreaOfInterestManager` adds hysteresis (warm-TTL), claim-pinning, reconcile coalescing, and an LRU cap so narrowing the view never shrinks the write allowlist.
1412
- - **Richer claim-rejection errors.** Rejections (over WebSocket and HTTP) now carry `heldByClaim` and `policyReason`, and `AbloClaimedError` exposes a typed `claims` array so callers can see exactly who holds the contested rows.
1413
- - **Coordination vocabulary consolidation.** Participant identity is canonical `user` | `agent` | `system`; the server stamps `participantKind` on every presence emit and clients read it, so non-human peers surface correctly.
1414
-
1415
- ## 0.10.1
1416
-
1417
- ### Patch Changes
1418
-
1419
- - Docs: add the 0.10.0 entry to the Version History & Migration Guide — the `test`/`live` → `sandbox`/`production` environment enum rename (key prefixes unchanged) and the new `transport: 'http'` stateless client.
1420
-
1421
- ## 0.10.0
1422
-
1423
- ### Minor Changes
1424
-
1425
- - Rename environment enum values to `production` and `sandbox` while preserving the existing `*_live_`/`*_test_` key prefix format.
1426
-
1427
- ### Patch Changes
1428
-
1429
- - Stateless HTTP transport for server-side actors, and a canonical environment vocabulary.
1430
- - **`Ablo({ transport: 'http' })`** returns a stateless `AbloHttpClient` for agents, workers, and serverless — the same `ablo.<model>` surface and coordination plane with no websocket: each call is one HTTP round-trip and identity rides the Bearer credential. The return type narrows so stateful-only APIs (`get`/`getAll`/`onChange`) are compile errors instead of latent runtime gaps.
1431
- - **Canonical `production` / `sandbox` environments** (new `environment.ts`, exported from the root): `sk_test_` / `sk_live_` remain the wire-level key prefixes but now map to `production` / `sandbox` everywhere — key parsing, source `mode`, and the CLI (which drops the legacy test/live config migration).
1432
- - **Source-mode commit scoping**: `commit` now forwards `projectId`, `accountScope`, and `environment` to customer storage resolvers, so per-project and sandbox/production traffic can be routed to distinct stores.
1433
- - **Fixes**: the WebSocket bearer credential is sent in the `ablo.bearer.<token>` subprotocol (never in the URL or proxy logs); `Model` no longer fabricates an `updatedAt` of "now" for records that arrive with only `createdAt`.
1434
-
1435
- ## 0.9.15
1436
-
1437
- ### Patch Changes
1438
-
1439
- - Package metadata: set the npm description to "The Collaboration Layer For AI Agents" (matching the GitHub repo About) so it stops reverting to the old "State control API…" text on publish.
1440
-
1441
- ## 0.9.14
1442
-
1443
- ### Patch Changes
1444
-
1445
- - README: replace the `schema -> ablo.<model>...` pseudo-diagram with a real typed snippet (`create`/`retrieve`/`update`/`claim`), and tidy the Get-started line.
1446
-
1447
- ## 0.9.13
1448
-
1449
- ### Patch Changes
1450
-
1451
- - Per-project axis: schemas, planes, routing, and enforcement scoped per project. Adds the control plane, per-project key scoping with identity threading, a `remove_model` gate, and the CLI/docs to drive it.
1452
-
1453
- ## 0.9.12
1454
-
1455
- ### Patch Changes
1456
-
1457
- - README: point the Docs / Quickstart / API header links at `docs.abloatai.com` (the real docs) instead of `abloatai.com`, which 307-redirects to the marketing site.
1458
-
1459
- ## 0.9.11
1460
-
1461
- ### Patch Changes
1462
-
1463
- - `Model<'name'>` type helper via the `Register` binding — name your model in one parameter (`Model<'tasks'>`) instead of restating `typeof schema`; `Model<S, 'name'>` is also supported and `InferModel` is deprecated. CLI: retire the stale `dev` wording from the login outro and `push` header. Docs: cover the `Register` binding end-to-end and document the `pk_` publishable key + the `/api/v1/commits` HTTP path.
1464
- - 3024593: Fix `sessions.create({ user })` 403 — user sessions now mint via the sk\_-gated ephemeral-key door
1465
- - `sessions.create({ user })` mints an `ek_` user session via `/auth/ephemeral-keys` (was wrongly routed through `/auth/capability`, which rejects human participants — writes were being attributed to agents).
1466
- - Control-plane calls always present your original `sk_`, never the client's exchanged sync credential.
1467
- - `sessions.create({ agent, can })` no longer requires hand-built `syncGroups` — the org anchor is the server default — and the `can` allowlist is now honored at commit time (model-alias matching).
1468
- - New: `ablo.organizationId` (resolved after `ready()`), `ablo status --json`, typed sync-group inputs (`SyncGroupInput` + `invalid_sync_group` rejection for malformed groups).
1469
-
1470
- ## 0.9.10
1471
-
1472
- ### Patch Changes
1473
-
1474
- - README: add a centered brand header (Ablo banner, tagline, doc nav links, and status badges).
1475
-
1476
- ## 0.9.9
1477
-
1478
- ### Patch Changes
1479
-
1480
- - Docs: version history & migration guide refinements plus changelog, audit, and link fixes.
1481
-
1482
- ## 0.9.8
1483
-
1484
- ### Patch Changes
1485
-
1486
- - Docs: add a Version History & Migration Guide, bring the changelog current, and sync doc trees. Drop the dormant `causedByTaskId` from the audit-row docs and fix the `ablo mode` argument vocabulary.
1487
-
1488
- ## 0.9.7
1489
-
1490
- ### Patch Changes
1491
-
1492
- - Docs: fix the `commits.create` operation shape to the public `{ action, model, data }` form.
1493
-
1494
- ## 0.9.6
1495
-
1496
- ### Patch Changes
1497
-
1498
- - CLI quickstart simplification (3 commands). `init` now owns login, `migrate` is dropped from the direct-`databaseUrl` quickstart (dev handles it), and the `dev` command is renamed to `push` for honest naming with headless-safe login. **Note:** `ablo dev` is now `ablo push` — update any scripts. Also fixes 3 production bugs surfaced by the new end-to-end journey test harness.
1499
-
1500
- ## 0.9.5
1501
-
1502
- ### Patch Changes
1503
-
1504
- - Scoped-role automation + tenant-routing fix. `ablo migrate` now auto-creates the RLS-gated scoped role (zero SQL) with a log-safe SCRAM-SHA-256 password verifier, plus a Neon/Supabase scoped-role `databaseUrl` recipe. Fix a jsonb double-encode that corrupted per-tenant routing and silently fell back to the shared pool.
1505
-
1506
- ## 0.9.4
1507
-
1508
- ### Patch Changes
1509
-
1510
- - Sync-position correctness + CLI hardening. Consolidate five scattered sync cursors into one typed `syncPosition` (persisted/applied/acked with a derived `readFloor`), fixing a claim taken right after an ack-confirmed write reading stale against that write's own delta. Add transaction ack-confirmation, schema DDL-first-push, and a reworked CLI (config/dev/login/mode/drizzle-pull).
1511
-
1512
- ## 0.9.3
1513
-
1514
- ### Patch Changes
1515
-
1516
- - Onboarding: quickstart leads with your-own-database (Drizzle Data Source), drop Ablo-managed mode, add `ablo push` step; context7 library-claim config.
1517
-
1518
- ## 0.9.2
1519
-
1520
- ### Patch Changes
1521
-
1522
- - Developer-onboarding overhaul so an LLM or a person gets a working integration on the first try.
1523
- - **`ablo init` scaffolds a project that builds and is current-API.** The Next.js scaffold now ships `app/providers.tsx` + an `app/api/ablo-session` route, uses `useAblo` (the removed `withSync` is gone), object-param verbs, and never bundles your `sk_` key into the browser. The webhook receiver moved off the `[...all]` catch-all.
1524
- - **Agent docs are accurate and ship.** `AGENTS.md`, `llms.txt`, and `llms-full.txt` are on the 0.9.x API (object-param `create`/`update`/`delete`/`retrieve`, disposable `await using claim`, `AbloProvider client` prop), lead with `ablo init`, and `AGENTS.md` now ships in the package.
1525
- - **`ablo push` is self-documenting.** Writing to a model the server hasn't seen now fails with an error that tells you to run `ablo push` (the `server_execute_unknown_model` / `unknown_model` messages), instead of a cryptic "unknown model."
1526
- - **`intents` is deprecated in favor of `claim`** everywhere the docs and the MCP scaffold/prompts teach or generate coordination; the public `ablo.intents` accessor is marked `@internal`.
1527
- - Docs say Node 24+, and the `drizzle-orm` peer floor is `>=0.44`.
1528
-
1529
- - a88747a: Remove the `turn` primitive and the agent-work `tasks` resource from the client surface — the SDK is now purely `ablo.<model>` + `claim`.
1530
-
1531
- **Breaking**
1532
- - `engine.beginTurn()`, the `Turn` handle interface, and the `Ablo.Turn` type are removed. `AbloApi.beginTurn` and the HTTP client's `beginTurn` are gone too.
1533
- - `CommitCreateOptions.causedByTaskId` is removed. (Lineage is no longer stamped from the client.)
1534
- - The engine no longer exposes a `protocol` accessor or a public `tasks` work-unit resource. `ablo.tasks` is, and always was, the schema `tasks` model proxy.
1535
- - The **`agent().run()` helper and the low-level agent/task type family are removed**: `AbloApi.agent(id, options)` and `AbloApi.tasks` (the `TaskResource`), plus the exported types `Agent`, `AgentOptions`, `AgentRunOptions`, `AgentRunResult`/`Done`/`Failed`/`Cancelled`, `AgentRunStatus`, `AgentRunContext`, `AgentModelClient`, `AgentModelReadOptions`, `AgentModelMutationOptions`, `AgentIntentOptions`, `AgentIntentInput`, `Task`, `TaskResource`, `TaskCreateOptions`, `TaskCloseOptions`, `TaskCloseResult` (and the `Ablo.*` namespace aliases for all of them). The `Ablo.Auth.Agent` principal constructor and the schema-backed `tasks` model are unaffected.
1536
-
1537
- **Why**
1538
-
1539
- `turn`/`agent_tasks` was a second coordination-and-attribution mechanism living alongside `claim`. It is redundant on the client:
1540
- - `claim` already serializes writers **and** carries the causal link — its `intent` id rides on every guarded write.
1541
- - The server stamps `actor` / `onBehalfOf` / `capabilityId` onto each delta from the auth context.
1542
- - Per-run token/cost is recorded in Langfuse, not the `agent_tasks` table.
1543
-
1544
- So the only thing the client lost is the audit pane's "show everything this exact prompt produced" filter, which keyed off `caused_by_task_id`; new writes leave that column null.
1545
-
1546
- **Migration**
1547
-
1548
- Agents stop opening/closing tasks — just issue `ablo.<model>` writes (schema-backed) or `ablo.commits.create(...)` (schema-less) under a `claim`. Replace `Ablo({ apiKey }).agent(id, opts).run(prompt, handler)` with: mint a scoped credential via `sessions.create({ agent })`, then `claim` the row and `update` / `commits.create`.
1549
-
1550
- The **server** `agent_tasks` table, the `caused_by_task_id` delta column, the `/api/sync/commit` wire field, and the `agent_actions_log` compliance hash-chain remain in place but **dormant** (client writes leave the field null) — they are load-bearing for the tamper-evident audit chain and historical-row audit JOINs, so they are intentionally NOT dropped. The dead `/v1/tasks` + `/api/agent/turn` route handlers ARE removed (zero live callers).
1551
-
1552
- ## 0.9.1
1553
-
1554
- ### Patch Changes
1555
-
1556
- - 90b656c: `drizzleDataSource` now takes `(db, schema)` and derives snake_case columns from your schema, so it composes with `ablo migrate` with no parallel Drizzle table. Update calls from `drizzleDataSource(db, tables)` → `drizzleDataSource(db, schema)`. Also adds the `snakeToCamel` export and provisions the adapter's `ablo_outbox` / `ablo_idempotency` tables via `ablo migrate`.
1557
-
1558
- ## 0.9.0
1559
-
1560
- A single options object for every model verb, and a disposable `claim` handle.
1561
-
1562
- ### Breaking Changes
1563
-
1564
- - **One options object per verb.** `create`, `update`, `delete`, and the async
1565
- server `retrieve` each take a single options object instead of positional
1566
- arguments, so the id, the data, and every modifier live as named siblings:
1567
- `create({ data, id? })`, `update({ id, data, ...options })`,
1568
- `delete({ id, ...options })`, `retrieve({ id, ...options })`. Reactive local
1569
- reads stay on `get(id)` (synchronous) —
1570
- `useAblo((ablo) => ablo.tasks.get(id))`.
1571
-
1572
- ```diff
1573
- - await ablo.tasks.update(id, { status: 'done' }, { wait: 'confirmed' })
1574
- + await ablo.tasks.update({ id, data: { status: 'done' }, wait: 'confirmed' })
1575
-
1576
- - await ablo.tasks.retrieve(id)
1577
- + await ablo.tasks.retrieve({ id })
1578
-
1579
- - useAblo((ablo) => ablo.tasks.retrieve(id)) ?? serverTask
1580
- + useAblo((ablo) => ablo.tasks.get(id)) ?? serverTask
1581
- ```
1582
-
1583
- - **`claim` returns a disposable handle** instead of taking a callback. The
1584
- handle exposes the fresh row on `.data` and is released on scope exit
1585
- (`await using`) or explicitly via `.release()`. `claim.state`, `claim.queue`,
1586
- `claim.release`, and `claim.reorder` also take the options object.
1587
-
1588
- ```diff
1589
- - await ablo.tasks.claim(id, async (task) => {
1590
- - await ablo.tasks.update(task.id, { status: 'in_review' })
1591
- - })
1592
- + await using claim = await ablo.tasks.claim({ id })
1593
- + const task = claim.data
1594
- + await ablo.tasks.update({ id: task.id, data: { status: 'in_review' } })
1595
- ```
1596
-
1597
- ## 0.8.0
1598
-
1599
- A callable `claim` coordination namespace and bring-your-own-database support
1600
- via a new `databaseUrl` option.
1601
-
1602
- ### Minor Changes
1603
-
1604
- - **Callable `claim` coordination namespace.** Taking a claim and inspecting its
1605
- state now live under one accessor: `claim(id, work)` acquires a claim and runs
1606
- `work` while it's held, and `claim.state(id)`, `claim.queue(id)`,
1607
- `claim.release(id)`, and `claim.reorder(id, order)` cover the surrounding
1608
- lifecycle. The README leads with the problem (who is allowed to act, and in
1609
- what order) and the Quick Start now demonstrates `claim` directly.
1610
-
1611
- - **Bring-your-own-database via `databaseUrl`.** Point a project at your own
1612
- Postgres with `Ablo({ schema, apiKey, databaseUrl })`. Ablo writes synced rows
1613
- back into your database, so your data stays canonical. Server-side only;
1614
- defaults to `process.env.DATABASE_URL`. See the data-sources guide for setup
1615
- and role requirements.
1616
-
1617
- ### Breaking
1618
-
1619
- - The flat coordination methods `claimState`, `queue`, `release`, and `reorder`
1620
- are removed in favor of the `claim` namespace above.
1621
-
1622
- ```diff
1623
- - await ablo.task.claimState(id)
1624
- - await ablo.task.release(id)
1625
- + await ablo.task.claim.state(id)
1626
- + await ablo.task.claim.release(id)
1627
- ```
1628
-
1629
- ## 0.7.0
1630
-
1631
- ### Minor Changes
1632
-
1633
- - Structured error contract, schema/migration engine, and a full `ablo` CLI.
1634
- - **Structured error contract across HTTP + WS planes.** A closed, canonical
1635
- error-code registry is now the `code` tier of a Stripe-style error model. A
1636
- single HTTP egress funnel converts every throw to a canonical
1637
- `{ type, code, message, doc_url, request_id, ...details }` envelope; the WS
1638
- plane narrows mutation/claim error codes to the same union.
1639
- - **Versioned contract + drift guard.** `ERROR_CONTRACT_VERSION` (date-based)
1640
- ships in `errors.json` and on the `Ablo-Version` response header, so consumers
1641
- detect contract changes without diffing docs. Generated `errors.mdx` /
1642
- `errors.json` plus a CI drift guard keep the docs, OpenAPI spec, and SDK from
1643
- silently diverging from the registry.
1644
- - **Always-on request correlation.** Every response carries a `req_…` request id
1645
- (honoring an inbound `x-request-id`), stamped into the envelope's `request_id`.
1646
- - **OpenAPI parity.** The stale `{ error, reason }` schema is replaced by the
1647
- canonical envelope plus a generated `ErrorCode` enum.
1648
-
1649
- CLI + schema:
1650
- - **Schema diff + migration planning engine** (`generateProvisionPlan` /
1651
- `generateMigrationPlan` in `@abloatai/ablo/schema`) — pure diff, classify,
1652
- apply, and constant-value backfill for required-field migrations.
1653
- - **`ablo generate`** — emit TypeScript types from the pushed schema.
1654
- - **Full `ablo` CLI suite**, Stripe-CLI-shaped: `init`, `login` / `logout` /
1655
- `status`, `mode [test|live]`, `dev` (push schema to the test sandbox + watch),
1656
- `logs` (tail your scope's commit activity), and the data-source commands below.
1657
- Authentication is the OAuth 2.0 device flow; `login` provisions and stores a
1658
- test and a live key, and `mode` switches the active one.
1659
- - **Database-URL structure (bring-your-own-database).** The CLI is split by where
1660
- it writes:
1661
- - `ablo pull` / `ablo check` / `ablo migrate` operate on **your own
1662
- `DATABASE_URL`** — `pull` introspects it to emit `defineSchema(...)` from
1663
- existing tables (read-only, like `prisma db pull`), `check` verifies tables
1664
- fit the schema with no DDL, and `migrate` applies DDL to `DATABASE_URL`.
1665
- - `ablo schema push` / `ablo dev` target the **hosted** test/live sandbox; the
1666
- server diffs, migrates, and activates the uploaded schema. `dev` never
1667
- touches live data.
1668
-
1669
- **BREAKING** — removed the legacy React hooks `useQuery` / `useOne` / `useMutate`
1670
- / `useReader`. Use `useAblo()` + `ablo.<model>.*` instead. The `MutateActions`,
1671
- `ReaderActions`, and `ReaderFindOptions` types are still re-exported for callers
1672
- that referenced them.
1673
-
1674
- ## 0.6.0
1675
-
1676
- ### Minor Changes
1677
-
1678
- - 0f663e7: Coordination surface: fair queue, reactive wait-line, and lease renewal.
1679
- - **Claims acquire through a server FIFO queue.** On contention a claim waits its turn and re-reads before proceeding; reads are never blocked. Writes blocked by another participant's claim throw a typed `AbloBusyError`.
1680
- - **`ablo.<model>.queue(id)`** — reactive read of the wait-line behind a row: who's queued, their action, and FIFO position. Synced to peers like `activity(id)`.
1681
- - **Backpressure on `claim`** — `{ wait: false }` skips instead of waiting if the row is already held (claim-or-skip dedup); `{ maxQueueDepth: n }` bails with `AbloBusyError('queue_too_deep')` rather than joining a line already that deep.
1682
- - **Lease renewal** — a held claim renews automatically while the holder's connection is alive, so you never size a TTL; it lapses only after the holder goes silent. A queued claim that's abandoned is dequeued (no ghost waiters).
1683
- - **Reads are never gated by a claim**, including for agents.
1684
- - Intent vocabulary cleanup: a waiting claim is an `Intent` with `status: 'queued'` (`position` carries its place in line). Removed the unbuilt `whenFree`.
1685
-
1686
- - **BREAKING — API renames** (apply when upgrading from 0.5.1):
1687
- - Change-listeners renamed to `.onChange(...)`: `ablo.<model>.subscribe(cb)`, `presence.subscribe()`, `intents.subscribe()` → `.onChange(...)`. (`subscribe` is reserved for an upcoming scope-grant verb.)
1688
- - Row-access API renamed Resource → Model: `Ablo.Resource.*` → `Ablo.Model.*`, `ablo.resource(name)` → `ablo.model(name)`, `ModelTarget.resource` → `ModelTarget.model`, error code `resource_not_found` → `model_not_found`.
1689
-
1690
- ## 0.5.1
1691
-
1692
- ### Patch Changes
1693
-
1694
- - Docs: add a React quick-start (provider + `useAblo`), plain-language rewrite, and a "Set up with Claude Code" section.
1695
-
1696
- ## 0.5.0
1697
-
1698
- ### Minor Changes
1699
-
1700
- - 9154c1b: Rename intent handle methods to a clearer claim vocabulary; add `AbloProvider` `bootstrapMode`.
1701
-
1702
- BREAKING — on the model intent handle (`ablo.<model>.intent(id)`):
1703
- `acquire`→`claim`, `acquireOrAwait`→`claimOrWait`, `settled`→`whenFree`,
1704
- `release`→`finish`, `revoke`→`cancel`. The lower-level `IntentHandle` /
1705
- `IntentLeaseHandle` (`ablo.intents.*`) are unchanged.
1706
-
1707
- Also: `AbloProvider` gains a `bootstrapMode` prop (`'full' | 'none'`) to skip the
1708
- baseline pull on read-light pages; `StaleContextConflict` gains an optional
1709
- `conflictingFields`; README + JSDoc clarity pass and a new HTTP API section.
1710
-
1711
- ## 0.4.0
1712
-
1713
- ### Minor Changes
1714
-
1715
- - Per-entity coordination intents on the model accessor.
1716
-
1717
- Coordinate writes to an entity through the same accessor you read it with —
1718
- `ablo.<model>.intent(id)`, returning a `ModelIntentHandle`. Intent state is one
1719
- self-describing object (`{ object: 'intent', id, status, target, action, heldBy,
1720
- participantKind, createdAt?, expiresAt? }`) with a single lifecycle:
1721
- `status: 'active' | 'committed' | 'expired' | 'canceled'`. An `active` intent is
1722
- the lock.
1723
-
1724
- ### Added
1725
- - `ablo.<model>.intent(id)` → `ModelIntentHandle<T>`, beside `create` / `update`
1726
- / `retrieve` / `load` on every model.
1727
- - Read side (any participant, synchronous + reactive): `current` (the holder's
1728
- intent, or `null`), `status` (`'idle'` when free), `settled()`.
1729
- - Write side (the holder): `acquire()`, `acquireOrAwait()`, lease-guarded
1730
- `update()`, `release()`, `revoke()`.
1731
- - `AsyncDisposable`: `await using lock = ablo.<model>.intent(id)` auto-releases
1732
- on scope exit.
1733
- - `acquireOrAwait()` — serialize-on-contention: take the lease, or wait out the
1734
- current holder, re-read the changed row, then take it. The caller never branches
1735
- on who holds the target — it just gets the target safely. Bind it to an agent's
1736
- write-tool boundary so agents never reason about coordination.
1737
- - New exports: `ModelIntentHandle`, `ModelIntentAcquireOptions`.
1738
-
1739
- ### Changed
1740
- - `acquire()` is fire-and-forget over the socket — it does not throw on conflict.
1741
- Resolve contention with `acquireOrAwait()` (wait) or read `current` for a
1742
- reactive "who's editing" badge, rather than catching a rejection.
1743
-
1744
- ### Deprecated
1745
- - Participant-level `intents.claim()` / `onRejected()` and the `intent_rejected`
1746
- wire frame still work but are superseded by the per-model handle. Their removal
1747
- is a future breaking change.
1748
-
1749
- ## Unreleased
1750
-
1751
- Schema-driven identity sync-group composition, plus a terser capability surface.
1752
-
1753
- The convention for deriving a participant's allowed sync-groups from its identity is now declared on the consumer's schema as an open registration. Consumers with a `{ regionId, customerId }` identity shape declare their own roles instead of receiving any built-in prefixes from the SDK.
1754
-
1755
- Capability fields shed their redundant `allowed` prefix to match the surrounding vocabulary — capability inputs always describe what the bearer _can_ touch, so the prefix was doing no disambiguation work for the consumer.
1756
-
1757
- ### Added
1758
-
1759
- - `DefineSchemaOptions.identityRoles?: readonly IdentityRole[]` — open registration of identity-anchored sync-group roles on `defineSchema(...)`. Each `IdentityRole` declares `{ kind, template, extract }`: a diagnostic label, a `'<prefix>:{id}'` template, and a pure extractor function from an opaque identity context to zero-or-more ids. No closed enum; consumers fully control both the template strings and the extraction logic.
1760
- - `composeIdentitySyncGroups(identity, schema)` exported from `@abloatai/ablo/schema` — walks the schema's registered `identityRoles`, calls each extractor, and substitutes ids into templates. Stable, deduped output. Returns `[]` when no roles are registered.
1761
- - `Schema.identityRoles: readonly IdentityRole[]` — the registered list, accessible on every `defineSchema(...)` result.
1762
- - New exported types: `IdentityRole`, `IdentityContext`.
1763
-
1764
- ### Breaking
1765
-
1766
- - `capabilities.create({ allowedSyncGroups, allowedOperations })` → `capabilities.create({ syncGroups, operations })`. Both fields renamed at every public surface — capability create input, capability retrieve response, capability record, Identity returned from `AuthProvider`. Hard rename, no alias. Update the call sites; the field semantics are unchanged.
1767
-
1768
- ```ts
1769
- // Before
1770
- await api.capabilities.create({
1771
- allowedSyncGroups: ['org:acme'],
1772
- allowedOperations: ['tasks.update'],
1773
- lease: '10m',
1774
- });
1775
-
1776
- // After
1777
- await api.capabilities.create({
1778
- syncGroups: ['org:acme'],
1779
- operations: ['tasks.update'],
1780
- lease: '10m',
1781
- });
1782
- ```
1783
-
1784
- ### Changed
1785
-
1786
- - `docs/integration-guide.md` §1 now shows `identityRoles` in the canonical `defineSchema` example plus a "Declaring scope on a model" subsection covering `orgScoped` / `scopedVia` / `syncGroupFormat`. `docs/capabilities.md`, `docs/api.md`, `docs/mcp.md`, and `AGENTS.md` cross-reference the `identityRoles` section and use the renamed fields throughout.
1787
-
1788
- ## 0.3.0 (2026-04-22)
1789
-
1790
- Umbrella `<AbloProvider>` for React apps. One provider component now owns the full lifecycle — singleton rotation on auth change, Strict-Mode-safe bootstrap, `beforeunload` cleanup, session-expiry IndexedDB wipe, post-bootstrap hooks, mesh client construction. Replaces the ad-hoc provider glue every consumer had to write themselves.
1791
-
1792
- Declarative props absorb every class of lifecycle glue; the status hook returns a tagged union so impossible states are unrepresentable. The reference integration shrank from 515 LOC of hand-rolled singleton/AbortController/beforeunload/reaction-bridge wiring to a 60-LOC thin wrapper that just passes props through.
1793
-
1794
- ### Added
1795
-
1796
- - `<AbloProvider>` — umbrella provider at `@abloatai/ablo/react`. Props include data config (`schema`, `url`, `userId`, `organizationId`), auth (`capabilityToken` / `apiKey` / session cookie fallback), declarative behavior (`preventUnsavedChanges`, `lostConnectionTimeout`, `postBootstrap`), callbacks (`onSessionExpired`, `onError`, `resolveUsers`), and DI escape hatches.
1797
- - `<SyncGroupProvider id="matter:...">` + `useSyncGroup()` — per-entity scope context.
1798
- - `<ClientSideSuspense fallback={...}>` — gate renders until the engine reports `connected`. Phase-1 non-Suspense; phase-2 upgrades to real Suspense.
1799
- - `useSyncStatus()` rewritten as a tagged union: `{ name: 'initial' | 'connecting' | 'connected' | 'reconnecting' | 'disconnected' | 'needs-auth', ... }`. Impossible states are unrepresentable.
1800
- - `useCurrentUserId()` — returns the `userId` prop. Replaces downstream consumers' defineProperty hacks on the store.
1801
- - `useErrorListener(cb)` — imperative error callback (Sentry/Datadog).
1802
- - `useSync<R>()` and `useSyncStore<T>()` accept generic parameters so consumers can widen to their concrete schema types without `as unknown` casts at call sites.
1803
- - `BaseSyncedStore.purge()` / `SyncEngine.purge()` — disconnect + wipe every `ablo_*` / `ablo-*` IndexedDB. Called automatically on session expiry.
1804
- - `SyncEngine.onSessionError(listener)` — subscribe to session-error events. Multiple subscribers supported.
1805
- - Commit payload projection built into `TransactionQueue`. Mutations are automatically projected onto the model's schema-declared fields (dropping framework internals `__class` / `__typename` / `clientId` / `syncStatus` and anything not declared), with `field.json()` values auto-stringified for TEXT columns and `undefined` dropped on updates. No config port, no consumer hook — the SDK derives correct wire payloads from the schema alone. Apps that previously maintained hand-rolled extractor tables can delete them entirely.
1806
-
1807
- ### Breaking (continued)
1808
-
1809
- - Removed `SyncEngineConfig.extractCreateInput` and `SyncEngineConfig.buildUpdateInput`. The SDK's built-in projection replaces them. Consumers who passed these in `configOverrides` should delete the override; the default now covers 100% of identity-column mutations. The `configOverrides` prop still exists but its remaining fields are all deprecated (see below) and scheduled for removal in v0.4.
1810
-
1811
- ### Deprecated (vestigial — removal in v0.4)
1812
-
1813
- - `SyncEngineConfig.modelCreatePriority`, `defaultCreatePriority`, `defaultNonCreatePriority` — never read at runtime.
1814
- - `SyncEngineConfig.batchableModels` — never read at runtime.
1815
- - `SyncEngineConfig.dedicatedDeleteModels` — never read at runtime.
1816
- - `SyncEngineConfig.preserveCaseModels` — never read at runtime.
1817
- - `SyncEngineConfig.essentialFields` — used only in debug logging, no behavioral effect.
1818
- - `SyncEngineConfig.classNameFallbackMap` — dead path; `ModelRegistry.registerModelsFromSchema` registers by constructor identity, bypassing the class-name fallback entirely.
1819
-
1820
- ### Breaking
1821
-
1822
- - Removed `<SyncProvider>` — folded into `<AbloProvider>`. Migrate by swapping the provider and passing `userId`/`organizationId`/`url` instead of a pre-constructed store.
1823
- - Removed `createAbloContext()` factory and its returned `AbloProvider` / `useAblo` / `useParticipant` triple. Mesh is now always-on inside `<AbloProvider>`; `useAblo()` and `useParticipant(opts)` are always available. Schema-typed mesh hooks are on the roadmap.
1824
- - Removed `withSync` (no-op alias of `observer`). Import `observer` from `mobx-react-lite` directly if needed.
1825
- - Removed `useSyncContext` from the public surface (never used outside the SDK's test helpers).
1826
- - `useSyncStatus()` return shape changed from six booleans to a tagged union. Migration: `const { isReady } = useSyncStatus()` → `const status = useSyncStatus(); const isReady = status.name === 'connected'`.
1827
- - `SyncStoreContract` gained six sync-status getters and a `syncStatus` field. Third-party classes implementing the contract must add these (additive for callers).
1828
-
1829
- ### Migration
1830
-
1831
- ```tsx
1832
- // Before (0.2.x)
1833
- const { AbloProvider, useAblo, useParticipant } = createAbloContext<typeof schema>();
1834
-
1835
- function Root() {
1836
- const sync = createSyncEngine({ url, schema, user });
1837
- const ablo = new Ablo({ schema });
1838
- return (
1839
- <SyncProvider store={sync._store} organizationId={orgId}>
1840
- <AbloProvider ablo={ablo}>
1841
- <App />
1842
- </AbloProvider>
1843
- </SyncProvider>
1844
- );
1845
- }
1846
-
1847
- // After (0.3.0)
1848
- function Root() {
1849
- return (
1850
- <AbloProvider
1851
- schema={schema}
1852
- url={url}
1853
- userId={userId}
1854
- organizationId={orgId}
1855
- preventUnsavedChanges
1856
- onSessionExpired={() => router.replace('/signin')}
1857
- >
1858
- <ClientSideSuspense fallback={<Skeleton />}>
1859
- <App />
1860
- </ClientSideSuspense>
1861
- </AbloProvider>
1862
- );
1863
- }
1864
- ```
1865
-
1866
- No breaking change to `useQuery` / `useOne` / `useMutate` / `useReader` / `useMutators` / `useUndoScope` / `usePresence` / `useIntent` — call sites remain source-compatible.
1867
-
1868
- ## 0.2.1 (2026-04-22)
1869
-
1870
- React bindings hardening. Fixes two infinite-loop classes that surfaced in downstream apps as React error #185 ("Maximum update depth exceeded"), and exposes sync-status reactivity as a first-class observable + hook.
1871
-
1872
- ### Fixed
1873
-
1874
- - **`useQuery` / `useOne` no longer loop on `getSnapshot`.** The `useSyncExternalStore` adapter was returning a fresh `view.results.slice()` on every call, which React's post-commit consistency check interpreted as "store updated mid-render" — scheduling another render, another snapshot, another mismatch, ad infinitum. The snapshot is now cached in a ref and only refreshed inside the subscribe callback right before `onChange()` fires. Affected every tree with multiple simultaneous `useQuery` subscribers.
1875
-
1876
- ### Added
1877
-
1878
- - **`BaseSyncedStore` sync status is now properly observable.** `syncStatus` and `dataReady` are annotated `observable`; `isReady`, `isSyncing`, `isOffline`, `isReconnecting`, `isError`, `hasUnsyncedChanges` are `computed`. Before, these were plain getters over plain fields — `reaction(() => store.isReady, ...)` silently never fired. Existing `observer` / `reaction` call sites that relied on the implicit `pool.size` trigger will continue to work; new call sites should read these observables directly.
1879
- - **`useSyncStatus()` React hook.** Returns `{ isReady, isSyncing, isOffline, isReconnecting, isError, hasUnsyncedChanges }` as a reactive snapshot, bridged via `useSyncExternalStore` with a correctly-cached snapshot. Replaces hand-rolled `reaction` bridges in consumer providers. See `docs/react.md`.
1880
- - **`SyncStoreContract` surfaces the status getters** so TypeScript autocomplete works from the `useSyncContext()` return value without a cast.
1881
-
1882
- ### Documentation
1883
-
1884
- - **`llms.txt` and `docs/react.md`** gained a "Common pitfalls" section covering the three traps this release addresses: don't wrap providers in `observer()`, `getSnapshot` must return a cached reference, and sync-status fields are real observables (don't watch `pool.size` as a proxy).
1885
-
1886
- ### Migration
1887
-
1888
- No breaking changes. Optional: replace any local `reaction(() => store.isReady, setReady, { fireImmediately: true })` bridges in your own providers with `const { isReady } = useSyncStatus()` for consumers below the store provider.
1889
-
1890
- ## 0.2.0 (2026-04-21)
1891
-
1892
- Mesh SDK — the canonical agent-multiplayer surface. Locked at this release; further work is consolidation, not expansion.
1893
-
1894
- ### What's frozen
1895
-
1896
- The SDK covers exactly three integration shapes. Each has a canonical example in [`examples/`](./examples/):
1897
-
1898
- 1. **Server agent** — `new Ablo({ schema })` reads `ABLO_API_KEY`, joins and works. ([`examples/server-agent.ts`](./examples/server-agent.ts))
1899
- 2. **Browser app** — server mints a scoped capability, browser holds it via `new Ablo({ schema, capabilityToken })`. No API key in bundle, no session cookies, no allowed-origins registration required. Stripe `client_secret` shape. ([`examples/browser-app.ts`](./examples/browser-app.ts))
1900
- 3. **Sub-agent** — `parent.join(child, opts)` attenuates from the parent's capability. ([`examples/sub-agent.ts`](./examples/sub-agent.ts))
1901
-
1902
- ### Ergonomics (package-wide)
1903
-
1904
- - **`Ablo` class** — `import Ablo from '@abloatai/ablo'` / `new Ablo({ schema })`. Matches `new Stripe()` / `new OpenAI()` / `new Anthropic()` pattern. `createMesh(opts)` stays available as the functional alias.
1905
- - **Model-scoped joins** — `ablo.matters.join(id, { label })` desugars to the generic `join`. Proxy-based so the namespace adapts to any schema. Collisions with reserved admin fields (`roles`, `members`, `audit`, `capabilities`) throw at construction time.
1906
- - **Flat scope form** — `scope: { matters: id }` alongside the array form.
1907
- - **`as` alias** — `{ as: session({...}) }` replaces the security-jargon `onBehalfOf`; both still accepted.
1908
- - **Auto-connect** — `join()` returns a connected participant. `autoConnect: false` to opt out.
1909
- - **Duration strings** — `ttl: '3m'`, `ttlSeconds: '24h'` accepted alongside numbers.
1910
- - **Descriptive generics** — every public type uses `TSchema` / `TAgent` / `ModelName` instead of `S` / `A` / `K`. Zero `unknown` in public types.
1911
-
1912
- ### Coordination primitives
1913
-
1914
- - **Presence verbs** — `participant.presence.editing(target)` / `viewing(target)` / `idle()`. Plus `update({...})` escape hatch for custom actions.
1915
- - **Intent verbs** — `participant.intents.editing(target, opts)` / `writing(target, opts)`. Returns an `IntentHandle` with `Symbol.asyncDispose` so `await using work = ...` auto-revokes.
1916
- - **Snapshots** — `const snap = await participant.snapshot({ clauses: [id] })`. Flat shape: `snap.clauses[id]` (typed from schema via `InferModel`, not `unknown`), `snap.stamp`, `snap.signal` (AbortSignal).
1917
- - **Async iterables** — `for await (const peers of participant.presence)`, `for await (const openIntents of participant.intents)`, `for await (const delta of participant.deltas)`.
1918
-
1919
- ### Env / config
1920
-
1921
- - `ABLO_API_KEY` — required for server-side use.
1922
- - `baseURL` — optional override for private deployments / local-dev (defaults to `wss://api.abloatai.com`).
1923
- - `organizationId` — **no longer required** in `createMesh`. The API key or session binds the caller to one org; the capability mint response echoes it back.
1924
- - `createMeshFromEnv` — removed. `new Ablo({ schema })` auto-reads env.
1925
-
1926
- ### Test coverage
1927
-
1928
- - 53 mesh unit tests across 8 suites (`__tests__/unit/mesh/`)
1929
- - New E2E test `e2e-browser-capability-token.ts` proves the server-mints / browser-holds flow end-to-end
1930
- - Existing 12 mesh E2E tests (token refresh, watermark, chinese wall, etc.) still pass
1931
-
1932
- ---
1933
-
1934
- ## 0.1.0 (2026-04-10)
1935
-
1936
- Initial release.
1937
-
1938
- ### Features
1939
-
1940
- - **Schema DSL**: Zero-codegen schema definition with full TypeScript inference (`defineSchema`, `field`, `relation`)
1941
- - **React Hooks**: `useModels`, `useModel`, `useMutations`, `withSync` for reactive data binding
1942
- - **Consumer API**: `createSyncEngine()` — one-liner setup that hides all internal wiring
1943
- - **Offline-first**: IndexedDB persistence with automatic offline mutation queue and FK-safe flush
1944
- - **Real-time sync**: WebSocket delta streaming with optimistic updates and rollback
1945
- - **AI Agent SDK**: `SyncAgent` for backend/AI agent participation as first-class sync citizens
1946
- - **Pluggable auth**: `AuthProvider` interface with built-in API key, JWT, and session providers
1947
- - **Security**: IndexedDB cleanup on session expiry and sync group revocation
1948
- - **Testing utilities**: `@abloatai/ablo/testing` subpath with mocks, fixtures, and harness
1949
-
1950
- ### Test Coverage
89
+ - Updated dependencies [f60ed16]
90
+ - Updated dependencies [16cc7d1]
91
+ - Updated dependencies [08a3cad]
92
+ - Updated dependencies [f60ed16]
93
+ - @abloatai/transaction@0.37.0
94
+ - @abloatai/humans@0.37.0
1951
95
 
1952
- - 231 unit/integration/property/contract tests
1953
- - 50 E2E tests against real Go server + PostgreSQL + Redis
1954
- - Property-based testing via fast-check
96
+ Release notes are generated from the repository changesets.