@abloatai/ablo 0.36.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 (597) hide show
  1. package/AGENTS.md +2 -2
  2. package/CHANGELOG.md +71 -2013
  3. package/NOTICE +2 -2
  4. package/README.md +25 -71
  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 -114
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +3 -163
  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 +1 -1
  59. package/docs/api-keys.md +7 -6
  60. package/docs/api.md +10 -10
  61. package/docs/client-behavior.md +5 -5
  62. package/docs/coordination.md +52 -62
  63. package/docs/data-sources.md +1 -1
  64. package/docs/examples/agent-human.md +4 -4
  65. package/docs/examples/ai-sdk-tool.md +1 -1
  66. package/docs/examples/existing-python-backend.md +15 -4
  67. package/docs/examples/nextjs.md +27 -6
  68. package/docs/examples/server-agent.md +2 -2
  69. package/docs/how-it-works.md +4 -4
  70. package/docs/identity.md +2 -1
  71. package/docs/integration-guide.md +24 -13
  72. package/docs/internal/README.md +18 -0
  73. package/docs/internal/agent-fleet-coordination-design.md +171 -0
  74. package/docs/internal/agent-orchestration.md +58 -0
  75. package/docs/internal/commit-identifiers.md +91 -0
  76. package/docs/internal/concurrency-open-decisions.md +37 -0
  77. package/docs/internal/data-source-reverse-channel.md +150 -0
  78. package/docs/internal/per-field-conflict-detection.md +165 -0
  79. package/docs/internal/postgres-replication.md +64 -0
  80. package/docs/internal/serializable-schema.md +119 -0
  81. package/docs/internal/structure.md +32 -0
  82. package/docs/mcp.md +2 -2
  83. package/docs/migration.md +3 -3
  84. package/docs/quickstart.md +2 -2
  85. package/docs/react.md +5 -5
  86. package/docs/schema-contract.md +3 -3
  87. package/docs/sessions.md +91 -37
  88. package/examples/README.md +2 -2
  89. package/examples/data-source/README.md +1 -1
  90. package/examples/data-source/ablo-driver.ts +1 -1
  91. package/examples/data-source/customer-server.ts +1 -1
  92. package/examples/data-source/run.ts +1 -1
  93. package/examples/data-source/schema.ts +1 -1
  94. package/examples/quickstart.ts +2 -2
  95. package/llms.txt +8 -8
  96. package/package.json +63 -166
  97. package/bin/ablo.cjs +0 -39
  98. package/dist/BaseSyncedStore.d.ts +0 -843
  99. package/dist/BaseSyncedStore.js +0 -1971
  100. package/dist/Database.d.ts +0 -323
  101. package/dist/Database.js +0 -1502
  102. package/dist/InstanceCache.d.ts +0 -237
  103. package/dist/InstanceCache.js +0 -1166
  104. package/dist/LazyReferenceCollection.d.ts +0 -177
  105. package/dist/LazyReferenceCollection.js +0 -461
  106. package/dist/Model.d.ts +0 -454
  107. package/dist/Model.js +0 -919
  108. package/dist/ModelRegistry.d.ts +0 -225
  109. package/dist/ModelRegistry.js +0 -539
  110. package/dist/NetworkMonitor.d.ts +0 -28
  111. package/dist/NetworkMonitor.js +0 -79
  112. package/dist/RuntimeContext.d.ts +0 -52
  113. package/dist/RuntimeContext.js +0 -80
  114. package/dist/SyncClient.d.ts +0 -541
  115. package/dist/SyncClient.js +0 -2202
  116. package/dist/adapters/alwaysOnline.d.ts +0 -14
  117. package/dist/adapters/alwaysOnline.js +0 -17
  118. package/dist/adapters/inMemoryStorage.d.ts +0 -31
  119. package/dist/adapters/inMemoryStorage.js +0 -110
  120. package/dist/ai-sdk/coordinatedTool.d.ts +0 -120
  121. package/dist/ai-sdk/coordinatedTool.js +0 -134
  122. package/dist/ai-sdk/coordinationContext.d.ts +0 -46
  123. package/dist/ai-sdk/coordinationContext.js +0 -106
  124. package/dist/ai-sdk/index.d.ts +0 -121
  125. package/dist/ai-sdk/index.js +0 -121
  126. package/dist/ai-sdk/wrap.d.ts +0 -65
  127. package/dist/ai-sdk/wrap.js +0 -39
  128. package/dist/auth/index.d.ts +0 -1
  129. package/dist/auth/index.js +0 -8
  130. package/dist/batching/index.d.ts +0 -55
  131. package/dist/batching/index.js +0 -147
  132. package/dist/client/Ablo.d.ts +0 -231
  133. package/dist/client/Ablo.js +0 -160
  134. package/dist/client/abloClient.d.ts +0 -309
  135. package/dist/client/abloClient.js +0 -13
  136. package/dist/client/clientPrelude.d.ts +0 -52
  137. package/dist/client/clientPrelude.js +0 -60
  138. package/dist/client/consoleLogger.d.ts +0 -35
  139. package/dist/client/consoleLogger.js +0 -44
  140. package/dist/client/coreClient.d.ts +0 -60
  141. package/dist/client/coreClient.js +0 -118
  142. package/dist/client/createInternalComponents.d.ts +0 -50
  143. package/dist/client/createInternalComponents.js +0 -98
  144. package/dist/client/createModelProxy.d.ts +0 -248
  145. package/dist/client/createModelProxy.js +0 -884
  146. package/dist/client/humans.d.ts +0 -69
  147. package/dist/client/humans.js +0 -78
  148. package/dist/client/modelRegistration.d.ts +0 -10
  149. package/dist/client/modelRegistration.js +0 -312
  150. package/dist/client/options.d.ts +0 -461
  151. package/dist/client/options.js +0 -7
  152. package/dist/client/reactiveEngine.d.ts +0 -53
  153. package/dist/client/reactiveEngine.js +0 -688
  154. package/dist/client/resourceTypes.d.ts +0 -12
  155. package/dist/client/resourceTypes.js +0 -10
  156. package/dist/client/schemaConfig.d.ts +0 -44
  157. package/dist/client/schemaConfig.js +0 -185
  158. package/dist/client/storeCluster.d.ts +0 -47
  159. package/dist/client/storeCluster.js +0 -118
  160. package/dist/client/storeLifecycle.d.ts +0 -61
  161. package/dist/client/storeLifecycle.js +0 -231
  162. package/dist/client/validateAbloOptions.d.ts +0 -42
  163. package/dist/client/validateAbloOptions.js +0 -43
  164. package/dist/client/wsMutationExecutor.d.ts +0 -27
  165. package/dist/client/wsMutationExecutor.js +0 -72
  166. package/dist/context.d.ts +0 -42
  167. package/dist/context.js +0 -81
  168. package/dist/coordination/ClaimLog.d.ts +0 -26
  169. package/dist/coordination/ClaimLog.js +0 -32
  170. package/dist/coordination/index.d.ts +0 -1
  171. package/dist/coordination/index.js +0 -8
  172. package/dist/core/index.d.ts +0 -33
  173. package/dist/core/index.js +0 -48
  174. package/dist/docs/catalog.d.ts +0 -72
  175. package/dist/docs/catalog.js +0 -230
  176. package/dist/docs/index.d.ts +0 -10
  177. package/dist/docs/index.js +0 -10
  178. package/dist/environment.d.ts +0 -1
  179. package/dist/environment.js +0 -8
  180. package/dist/interfaces/index.d.ts +0 -311
  181. package/dist/interfaces/index.js +0 -9
  182. package/dist/keys/index.d.ts +0 -1
  183. package/dist/keys/index.js +0 -8
  184. package/dist/mutators/RecordingMutation.d.ts +0 -36
  185. package/dist/mutators/RecordingMutation.js +0 -182
  186. package/dist/mutators/Transaction.d.ts +0 -40
  187. package/dist/mutators/Transaction.js +0 -58
  188. package/dist/mutators/UndoManager.d.ts +0 -258
  189. package/dist/mutators/UndoManager.js +0 -658
  190. package/dist/mutators/defineMutators.d.ts +0 -60
  191. package/dist/mutators/defineMutators.js +0 -18
  192. package/dist/mutators/inverseOp.d.ts +0 -126
  193. package/dist/mutators/inverseOp.js +0 -71
  194. package/dist/mutators/mutateActions.d.ts +0 -45
  195. package/dist/mutators/mutateActions.js +0 -105
  196. package/dist/mutators/readerActions.d.ts +0 -33
  197. package/dist/mutators/readerActions.js +0 -57
  198. package/dist/mutators/undoApply.d.ts +0 -51
  199. package/dist/mutators/undoApply.js +0 -117
  200. package/dist/policy/index.d.ts +0 -21
  201. package/dist/policy/index.js +0 -20
  202. package/dist/query/QueryProcessor.d.ts +0 -75
  203. package/dist/query/QueryProcessor.js +0 -255
  204. package/dist/query/client.d.ts +0 -64
  205. package/dist/query/client.js +0 -138
  206. package/dist/query/types.d.ts +0 -85
  207. package/dist/query/types.js +0 -16
  208. package/dist/react/AbloProvider.d.ts +0 -242
  209. package/dist/react/AbloProvider.js +0 -458
  210. package/dist/react/ClientSideSuspense.d.ts +0 -36
  211. package/dist/react/ClientSideSuspense.js +0 -17
  212. package/dist/react/DefaultFallback.d.ts +0 -24
  213. package/dist/react/DefaultFallback.js +0 -43
  214. package/dist/react/context.d.ts +0 -55
  215. package/dist/react/context.js +0 -29
  216. package/dist/react/createAbloReact.d.ts +0 -56
  217. package/dist/react/createAbloReact.js +0 -51
  218. package/dist/react/index.d.ts +0 -62
  219. package/dist/react/index.js +0 -69
  220. package/dist/react/internalContext.d.ts +0 -33
  221. package/dist/react/internalContext.js +0 -3
  222. package/dist/react/useAblo.d.ts +0 -82
  223. package/dist/react/useAblo.js +0 -120
  224. package/dist/react/useCurrentUserId.d.ts +0 -22
  225. package/dist/react/useCurrentUserId.js +0 -34
  226. package/dist/react/useErrorListener.d.ts +0 -20
  227. package/dist/react/useErrorListener.js +0 -38
  228. package/dist/react/useMutationFailureListener.d.ts +0 -26
  229. package/dist/react/useMutationFailureListener.js +0 -38
  230. package/dist/react/useMutators.d.ts +0 -56
  231. package/dist/react/useMutators.js +0 -84
  232. package/dist/react/useReactive.d.ts +0 -35
  233. package/dist/react/useReactive.js +0 -123
  234. package/dist/react/useSyncStatus.d.ts +0 -59
  235. package/dist/react/useSyncStatus.js +0 -76
  236. package/dist/react/useUndoScope.d.ts +0 -34
  237. package/dist/react/useUndoScope.js +0 -81
  238. package/dist/schema/coordination.d.ts +0 -112
  239. package/dist/schema/coordination.js +0 -133
  240. package/dist/schema/ddl.d.ts +0 -97
  241. package/dist/schema/ddl.js +0 -491
  242. package/dist/schema/ddlLock.d.ts +0 -35
  243. package/dist/schema/ddlLock.js +0 -46
  244. package/dist/schema/diff.d.ts +0 -225
  245. package/dist/schema/diff.js +0 -289
  246. package/dist/schema/generate.d.ts +0 -19
  247. package/dist/schema/generate.js +0 -86
  248. package/dist/schema/index.d.ts +0 -42
  249. package/dist/schema/index.js +0 -80
  250. package/dist/schema/queries.d.ts +0 -201
  251. package/dist/schema/queries.js +0 -144
  252. package/dist/schema/select.d.ts +0 -40
  253. package/dist/schema/select.js +0 -90
  254. package/dist/schema/serialize.d.ts +0 -115
  255. package/dist/schema/serialize.js +0 -265
  256. package/dist/schema/sugar.d.ts +0 -109
  257. package/dist/schema/sugar.js +0 -83
  258. package/dist/schema/syncDeltaRow.d.ts +0 -6
  259. package/dist/schema/syncDeltaRow.js +0 -6
  260. package/dist/server/adapter.d.ts +0 -173
  261. package/dist/server/adapter.js +0 -18
  262. package/dist/server/commit.d.ts +0 -107
  263. package/dist/server/commit.js +0 -1
  264. package/dist/server/index.d.ts +0 -14
  265. package/dist/server/index.js +0 -2
  266. package/dist/server/readConfig.d.ts +0 -80
  267. package/dist/server/readConfig.js +0 -8
  268. package/dist/server/storageMode.d.ts +0 -23
  269. package/dist/server/storageMode.js +0 -17
  270. package/dist/source/adapter.d.ts +0 -83
  271. package/dist/source/adapter.js +0 -24
  272. package/dist/source/adapters/drizzle.d.ts +0 -48
  273. package/dist/source/adapters/drizzle.js +0 -219
  274. package/dist/source/adapters/kysely.d.ts +0 -42
  275. package/dist/source/adapters/kysely.js +0 -205
  276. package/dist/source/adapters/kyselyMutationCore.d.ts +0 -76
  277. package/dist/source/adapters/kyselyMutationCore.js +0 -125
  278. package/dist/source/adapters/memory.d.ts +0 -13
  279. package/dist/source/adapters/memory.js +0 -130
  280. package/dist/source/adapters/prisma.d.ts +0 -63
  281. package/dist/source/adapters/prisma.js +0 -202
  282. package/dist/source/conformance.d.ts +0 -37
  283. package/dist/source/conformance.js +0 -215
  284. package/dist/source/connector.d.ts +0 -95
  285. package/dist/source/connector.js +0 -266
  286. package/dist/source/connectorProtocol.d.ts +0 -154
  287. package/dist/source/connectorProtocol.js +0 -163
  288. package/dist/source/contract.d.ts +0 -195
  289. package/dist/source/contract.js +0 -164
  290. package/dist/source/factory.d.ts +0 -92
  291. package/dist/source/factory.js +0 -286
  292. package/dist/source/idempotency.d.ts +0 -61
  293. package/dist/source/idempotency.js +0 -144
  294. package/dist/source/index.d.ts +0 -23
  295. package/dist/source/index.js +0 -30
  296. package/dist/source/migrations.d.ts +0 -21
  297. package/dist/source/migrations.js +0 -103
  298. package/dist/source/next.d.ts +0 -32
  299. package/dist/source/next.js +0 -25
  300. package/dist/source/pushQueue.d.ts +0 -134
  301. package/dist/source/pushQueue.js +0 -256
  302. package/dist/source/signing.d.ts +0 -92
  303. package/dist/source/signing.js +0 -162
  304. package/dist/source/types.d.ts +0 -401
  305. package/dist/source/types.js +0 -59
  306. package/dist/storeContract.d.ts +0 -145
  307. package/dist/storeContract.js +0 -12
  308. package/dist/stores/DatabaseManager.d.ts +0 -107
  309. package/dist/stores/DatabaseManager.js +0 -388
  310. package/dist/stores/ObjectStore.d.ts +0 -115
  311. package/dist/stores/ObjectStore.js +0 -393
  312. package/dist/stores/ObjectStoreContract.d.ts +0 -38
  313. package/dist/stores/ObjectStoreContract.js +0 -1
  314. package/dist/stores/StoreManager.d.ts +0 -114
  315. package/dist/stores/StoreManager.js +0 -304
  316. package/dist/stores/SyncActionStore.d.ts +0 -99
  317. package/dist/stores/SyncActionStore.js +0 -506
  318. package/dist/stores/openIDBWithTimeout.d.ts +0 -65
  319. package/dist/stores/openIDBWithTimeout.js +0 -153
  320. package/dist/stores/syncAction.d.ts +0 -26
  321. package/dist/stores/syncAction.js +0 -16
  322. package/dist/surface.d.ts +0 -36
  323. package/dist/surface.js +0 -75
  324. package/dist/sync/BootstrapFetcher.d.ts +0 -284
  325. package/dist/sync/BootstrapFetcher.js +0 -964
  326. package/dist/sync/ConnectionManager.d.ts +0 -8
  327. package/dist/sync/ConnectionManager.js +0 -8
  328. package/dist/sync/OnDemandLoader.d.ts +0 -231
  329. package/dist/sync/OnDemandLoader.js +0 -743
  330. package/dist/sync/SubscriptionManager.d.ts +0 -159
  331. package/dist/sync/SubscriptionManager.js +0 -243
  332. package/dist/sync/SyncWebSocket.d.ts +0 -173
  333. package/dist/sync/SyncWebSocket.js +0 -438
  334. package/dist/sync/awaitClaimGrant.d.ts +0 -6
  335. package/dist/sync/awaitClaimGrant.js +0 -6
  336. package/dist/sync/bootstrapApply.d.ts +0 -73
  337. package/dist/sync/bootstrapApply.js +0 -73
  338. package/dist/sync/commitFrames.d.ts +0 -8
  339. package/dist/sync/commitFrames.js +0 -8
  340. package/dist/sync/contextPorts.d.ts +0 -18
  341. package/dist/sync/contextPorts.js +0 -31
  342. package/dist/sync/createClaimStream.d.ts +0 -7
  343. package/dist/sync/createClaimStream.js +0 -7
  344. package/dist/sync/createPresenceStream.d.ts +0 -69
  345. package/dist/sync/createPresenceStream.js +0 -200
  346. package/dist/sync/createSnapshot.d.ts +0 -29
  347. package/dist/sync/createSnapshot.js +0 -118
  348. package/dist/sync/credentialLifecycle.d.ts +0 -7
  349. package/dist/sync/credentialLifecycle.js +0 -7
  350. package/dist/sync/deltaPipeline.d.ts +0 -114
  351. package/dist/sync/deltaPipeline.js +0 -278
  352. package/dist/sync/groupChange.d.ts +0 -116
  353. package/dist/sync/groupChange.js +0 -244
  354. package/dist/sync/participants.d.ts +0 -132
  355. package/dist/sync/participants.js +0 -346
  356. package/dist/sync/persistedPrefix.d.ts +0 -12
  357. package/dist/sync/persistedPrefix.js +0 -22
  358. package/dist/sync/schemaDrift.d.ts +0 -55
  359. package/dist/sync/schemaDrift.js +0 -53
  360. package/dist/sync/schemas.d.ts +0 -71
  361. package/dist/sync/schemas.js +0 -94
  362. package/dist/sync/syncCursor.d.ts +0 -40
  363. package/dist/sync/syncCursor.js +0 -55
  364. package/dist/sync/syncPlan.d.ts +0 -54
  365. package/dist/sync/syncPlan.js +0 -50
  366. package/dist/sync/wsFrameHandlers.d.ts +0 -8
  367. package/dist/sync/wsFrameHandlers.js +0 -8
  368. package/dist/syncLog/contract.d.ts +0 -20
  369. package/dist/syncLog/contract.js +0 -19
  370. package/dist/syncLog/index.d.ts +0 -1
  371. package/dist/syncLog/index.js +0 -1
  372. package/dist/transaction/ablo.d.ts +0 -88
  373. package/dist/transaction/ablo.js +0 -33
  374. package/dist/transaction/auth/apiKey.d.ts +0 -152
  375. package/dist/transaction/auth/apiKey.js +0 -419
  376. package/dist/transaction/auth/bootstrapScope.d.ts +0 -15
  377. package/dist/transaction/auth/bootstrapScope.js +0 -1
  378. package/dist/transaction/auth/capability.d.ts +0 -212
  379. package/dist/transaction/auth/capability.js +0 -224
  380. package/dist/transaction/auth/credentialEndpoint.d.ts +0 -61
  381. package/dist/transaction/auth/credentialEndpoint.js +0 -86
  382. package/dist/transaction/auth/credentialPolicy.d.ts +0 -148
  383. package/dist/transaction/auth/credentialPolicy.js +0 -125
  384. package/dist/transaction/auth/credentialSource.d.ts +0 -30
  385. package/dist/transaction/auth/credentialSource.js +0 -55
  386. package/dist/transaction/auth/hostedEndpoints.d.ts +0 -21
  387. package/dist/transaction/auth/hostedEndpoints.js +0 -21
  388. package/dist/transaction/auth/identity.d.ts +0 -55
  389. package/dist/transaction/auth/identity.js +0 -210
  390. package/dist/transaction/auth/index.d.ts +0 -162
  391. package/dist/transaction/auth/index.js +0 -304
  392. package/dist/transaction/auth/schemas.d.ts +0 -59
  393. package/dist/transaction/auth/schemas.js +0 -85
  394. package/dist/transaction/auth/sessionMint.d.ts +0 -28
  395. package/dist/transaction/auth/sessionMint.js +0 -85
  396. package/dist/transaction/coordination/awaitClaimGrant.d.ts +0 -56
  397. package/dist/transaction/coordination/awaitClaimGrant.js +0 -124
  398. package/dist/transaction/coordination/claimHeartbeatLoop.d.ts +0 -84
  399. package/dist/transaction/coordination/claimHeartbeatLoop.js +0 -108
  400. package/dist/transaction/coordination/claimMeta.d.ts +0 -49
  401. package/dist/transaction/coordination/claimMeta.js +0 -52
  402. package/dist/transaction/coordination/createClaimStream.d.ts +0 -64
  403. package/dist/transaction/coordination/createClaimStream.js +0 -475
  404. package/dist/transaction/coordination/events.d.ts +0 -74
  405. package/dist/transaction/coordination/events.js +0 -7
  406. package/dist/transaction/coordination/index.d.ts +0 -19
  407. package/dist/transaction/coordination/index.js +0 -45
  408. package/dist/transaction/coordination/locator.d.ts +0 -104
  409. package/dist/transaction/coordination/locator.js +0 -102
  410. package/dist/transaction/coordination/schema.d.ts +0 -1536
  411. package/dist/transaction/coordination/schema.js +0 -1177
  412. package/dist/transaction/coordination/targetConflict.d.ts +0 -2
  413. package/dist/transaction/coordination/targetConflict.js +0 -107
  414. package/dist/transaction/coordination/trace.d.ts +0 -78
  415. package/dist/transaction/coordination/trace.js +0 -138
  416. package/dist/transaction/durableWrites.d.ts +0 -62
  417. package/dist/transaction/durableWrites.js +0 -71
  418. package/dist/transaction/environment.d.ts +0 -105
  419. package/dist/transaction/environment.js +0 -108
  420. package/dist/transaction/errorCodes.d.ts +0 -403
  421. package/dist/transaction/errorCodes.js +0 -484
  422. package/dist/transaction/errors.d.ts +0 -428
  423. package/dist/transaction/errors.js +0 -686
  424. package/dist/transaction/footprint.d.ts +0 -111
  425. package/dist/transaction/footprint.js +0 -0
  426. package/dist/transaction/index.d.ts +0 -20
  427. package/dist/transaction/index.js +0 -20
  428. package/dist/transaction/keys/index.d.ts +0 -87
  429. package/dist/transaction/keys/index.js +0 -207
  430. package/dist/transaction/log/syncDeltaRow.d.ts +0 -158
  431. package/dist/transaction/log/syncDeltaRow.js +0 -95
  432. package/dist/transaction/logPosition.d.ts +0 -97
  433. package/dist/transaction/logPosition.js +0 -125
  434. package/dist/transaction/logger.d.ts +0 -16
  435. package/dist/transaction/logger.js +0 -7
  436. package/dist/transaction/observability.d.ts +0 -53
  437. package/dist/transaction/observability.js +0 -19
  438. package/dist/transaction/persistence.d.ts +0 -12
  439. package/dist/transaction/persistence.js +0 -11
  440. package/dist/transaction/plugin.d.ts +0 -285
  441. package/dist/transaction/plugin.js +0 -106
  442. package/dist/transaction/policy/types.d.ts +0 -217
  443. package/dist/transaction/policy/types.js +0 -126
  444. package/dist/transaction/resources/functionalUpdate.d.ts +0 -79
  445. package/dist/transaction/resources/functionalUpdate.js +0 -87
  446. package/dist/transaction/resources/httpResources.d.ts +0 -321
  447. package/dist/transaction/resources/httpResources.js +0 -7
  448. package/dist/transaction/resources/modelOperations.d.ts +0 -427
  449. package/dist/transaction/resources/modelOperations.js +0 -12
  450. package/dist/transaction/resources/mutationOptions.d.ts +0 -66
  451. package/dist/transaction/resources/mutationOptions.js +0 -9
  452. package/dist/transaction/resources/where.d.ts +0 -101
  453. package/dist/transaction/resources/where.js +0 -115
  454. package/dist/transaction/resources/writeOptionsSchema.d.ts +0 -47
  455. package/dist/transaction/resources/writeOptionsSchema.js +0 -73
  456. package/dist/transaction/schema/field.d.ts +0 -120
  457. package/dist/transaction/schema/field.js +0 -265
  458. package/dist/transaction/schema/fieldRef.d.ts +0 -38
  459. package/dist/transaction/schema/fieldRef.js +0 -11
  460. package/dist/transaction/schema/loadStrategy.d.ts +0 -45
  461. package/dist/transaction/schema/loadStrategy.js +0 -46
  462. package/dist/transaction/schema/model.d.ts +0 -379
  463. package/dist/transaction/schema/model.js +0 -123
  464. package/dist/transaction/schema/openapi.d.ts +0 -58
  465. package/dist/transaction/schema/openapi.js +0 -501
  466. package/dist/transaction/schema/relation.d.ts +0 -204
  467. package/dist/transaction/schema/relation.js +0 -104
  468. package/dist/transaction/schema/residency.d.ts +0 -29
  469. package/dist/transaction/schema/residency.js +0 -25
  470. package/dist/transaction/schema/roles.d.ts +0 -249
  471. package/dist/transaction/schema/roles.js +0 -230
  472. package/dist/transaction/schema/schema.d.ts +0 -351
  473. package/dist/transaction/schema/schema.js +0 -325
  474. package/dist/transaction/schema/tenancy.d.ts +0 -139
  475. package/dist/transaction/schema/tenancy.js +0 -190
  476. package/dist/transaction/transactionLayer.d.ts +0 -82
  477. package/dist/transaction/transactionLayer.js +0 -24
  478. package/dist/transaction/transactions/settlement/commitEnvelope.d.ts +0 -143
  479. package/dist/transaction/transactions/settlement/commitEnvelope.js +0 -161
  480. package/dist/transaction/transactions/settlement/httpCommitEnvelope.d.ts +0 -53
  481. package/dist/transaction/transactions/settlement/httpCommitEnvelope.js +0 -207
  482. package/dist/transaction/transactions/settlement/idempotencyKey.d.ts +0 -10
  483. package/dist/transaction/transactions/settlement/idempotencyKey.js +0 -9
  484. package/dist/transaction/transactions/settlement/pendingWrite.d.ts +0 -112
  485. package/dist/transaction/transactions/settlement/pendingWrite.js +0 -20
  486. package/dist/transaction/transport/commitFrames.d.ts +0 -90
  487. package/dist/transaction/transport/commitFrames.js +0 -134
  488. package/dist/transaction/transport/connectionManager.d.ts +0 -215
  489. package/dist/transaction/transport/connectionManager.js +0 -673
  490. package/dist/transaction/transport/credentialLifecycle.d.ts +0 -177
  491. package/dist/transaction/transport/credentialLifecycle.js +0 -324
  492. package/dist/transaction/transport/heartbeat.d.ts +0 -65
  493. package/dist/transaction/transport/heartbeat.js +0 -93
  494. package/dist/transaction/transport/httpClient.d.ts +0 -131
  495. package/dist/transaction/transport/httpClient.js +0 -146
  496. package/dist/transaction/transport/httpOptions.d.ts +0 -33
  497. package/dist/transaction/transport/httpOptions.js +0 -12
  498. package/dist/transaction/transport/httpTransport.d.ts +0 -8
  499. package/dist/transaction/transport/httpTransport.js +0 -1388
  500. package/dist/transaction/transport/networkProbe.d.ts +0 -84
  501. package/dist/transaction/transport/networkProbe.js +0 -207
  502. package/dist/transaction/transport/wsFrameHandlers.d.ts +0 -128
  503. package/dist/transaction/transport/wsFrameHandlers.js +0 -429
  504. package/dist/transaction/transport/wsTransport.d.ts +0 -574
  505. package/dist/transaction/transport/wsTransport.js +0 -1023
  506. package/dist/transaction/types/assertExact.d.ts +0 -17
  507. package/dist/transaction/types/assertExact.js +0 -1
  508. package/dist/transaction/types/global.d.ts +0 -107
  509. package/dist/transaction/types/global.js +0 -40
  510. package/dist/transaction/types/index.d.ts +0 -205
  511. package/dist/transaction/types/index.js +0 -56
  512. package/dist/transaction/types/modelData.d.ts +0 -10
  513. package/dist/transaction/types/modelData.js +0 -9
  514. package/dist/transaction/types/participant.d.ts +0 -20
  515. package/dist/transaction/types/participant.js +0 -10
  516. package/dist/transaction/types/streams.d.ts +0 -550
  517. package/dist/transaction/types/streams.js +0 -11
  518. package/dist/transaction/utils/asyncIterator.d.ts +0 -34
  519. package/dist/transaction/utils/asyncIterator.js +0 -135
  520. package/dist/transaction/utils/duration.d.ts +0 -50
  521. package/dist/transaction/utils/duration.js +0 -77
  522. package/dist/transaction/utils/json.d.ts +0 -57
  523. package/dist/transaction/utils/json.js +0 -276
  524. package/dist/transaction/wire/accountResponses.d.ts +0 -420
  525. package/dist/transaction/wire/accountResponses.js +0 -290
  526. package/dist/transaction/wire/auth.d.ts +0 -56
  527. package/dist/transaction/wire/auth.js +0 -63
  528. package/dist/transaction/wire/bootstrapReason.d.ts +0 -9
  529. package/dist/transaction/wire/bootstrapReason.js +0 -8
  530. package/dist/transaction/wire/claimEvent.d.ts +0 -76
  531. package/dist/transaction/wire/claimEvent.js +0 -73
  532. package/dist/transaction/wire/claims.d.ts +0 -530
  533. package/dist/transaction/wire/claims.js +0 -327
  534. package/dist/transaction/wire/commit.d.ts +0 -603
  535. package/dist/transaction/wire/commit.js +0 -321
  536. package/dist/transaction/wire/delta.d.ts +0 -250
  537. package/dist/transaction/wire/delta.js +0 -147
  538. package/dist/transaction/wire/errorEnvelope.d.ts +0 -72
  539. package/dist/transaction/wire/errorEnvelope.js +0 -123
  540. package/dist/transaction/wire/feedCursor.d.ts +0 -60
  541. package/dist/transaction/wire/feedCursor.js +0 -82
  542. package/dist/transaction/wire/feedEvent.d.ts +0 -204
  543. package/dist/transaction/wire/feedEvent.js +0 -65
  544. package/dist/transaction/wire/frames.d.ts +0 -194
  545. package/dist/transaction/wire/frames.js +0 -50
  546. package/dist/transaction/wire/inboundFrames.d.ts +0 -562
  547. package/dist/transaction/wire/inboundFrames.js +0 -116
  548. package/dist/transaction/wire/index.d.ts +0 -54
  549. package/dist/transaction/wire/index.js +0 -83
  550. package/dist/transaction/wire/listEnvelope.d.ts +0 -37
  551. package/dist/transaction/wire/listEnvelope.js +0 -42
  552. package/dist/transaction/wire/modelMutations.d.ts +0 -31
  553. package/dist/transaction/wire/modelMutations.js +0 -52
  554. package/dist/transaction/wire/modelResponses.d.ts +0 -85
  555. package/dist/transaction/wire/modelResponses.js +0 -43
  556. package/dist/transaction/wire/modelShape.d.ts +0 -78
  557. package/dist/transaction/wire/modelShape.js +0 -74
  558. package/dist/transaction/wire/protocol.d.ts +0 -38
  559. package/dist/transaction/wire/protocol.js +0 -38
  560. package/dist/transaction/wire/protocolVersion.d.ts +0 -73
  561. package/dist/transaction/wire/protocolVersion.js +0 -83
  562. package/dist/transactions/mutations/MutationQueue.d.ts +0 -661
  563. package/dist/transactions/mutations/MutationQueue.js +0 -2807
  564. package/dist/transactions/mutations/MutationStore.d.ts +0 -20
  565. package/dist/transactions/mutations/MutationStore.js +0 -53
  566. package/dist/transactions/mutations/UnconfirmedWrites.d.ts +0 -82
  567. package/dist/transactions/mutations/UnconfirmedWrites.js +0 -104
  568. package/dist/transactions/mutations/coalesceRules.d.ts +0 -58
  569. package/dist/transactions/mutations/coalesceRules.js +0 -140
  570. package/dist/transactions/mutations/commitLatency.d.ts +0 -52
  571. package/dist/transactions/mutations/commitLatency.js +0 -130
  572. package/dist/transactions/mutations/commitOutboxStore.d.ts +0 -28
  573. package/dist/transactions/mutations/commitOutboxStore.js +0 -26
  574. package/dist/transactions/mutations/commitPayload.d.ts +0 -165
  575. package/dist/transactions/mutations/commitPayload.js +0 -152
  576. package/dist/transactions/mutations/deltaConfirmation.d.ts +0 -63
  577. package/dist/transactions/mutations/deltaConfirmation.js +0 -235
  578. package/dist/transactions/mutations/durableWriteStore.d.ts +0 -14
  579. package/dist/transactions/mutations/durableWriteStore.js +0 -12
  580. package/dist/transactions/mutations/optimisticApply.d.ts +0 -49
  581. package/dist/transactions/mutations/optimisticApply.js +0 -65
  582. package/dist/transactions/mutations/replayValidation.d.ts +0 -187
  583. package/dist/transactions/mutations/replayValidation.js +0 -164
  584. package/dist/utils/mobxSetup.d.ts +0 -53
  585. package/dist/utils/mobxSetup.js +0 -330
  586. package/dist/views/QueryView.d.ts +0 -79
  587. package/dist/views/QueryView.js +0 -218
  588. package/dist/views/ViewRegistry.d.ts +0 -20
  589. package/dist/views/ViewRegistry.js +0 -55
  590. package/dist/views/incrementalView.d.ts +0 -45
  591. package/dist/views/incrementalView.js +0 -69
  592. package/dist/webhooks/events.d.ts +0 -43
  593. package/dist/webhooks/events.js +0 -42
  594. package/dist/webhooks/index.d.ts +0 -8
  595. package/dist/webhooks/index.js +0 -8
  596. package/dist/wire/index.d.ts +0 -1
  597. package/dist/wire/index.js +0 -8
package/CHANGELOG.md CHANGED
@@ -1,2038 +1,96 @@
1
1
  # Changelog
2
2
 
3
- ## 0.36.0
3
+ ## 0.37.0
4
4
 
5
5
  ### Minor Changes
6
6
 
7
- - 816081b: `claim({ id })` now means the same thing on every transport: serialize me
8
- behind whoever holds this row. The stateless HTTP client used to reject a
9
- contended claim with `claim_queued` and leave the wait to you; now it holds
10
- your place in the server's FIFO line — heartbeating the queued ticket under
11
- the hood — and resolves to the same held claim the realtime client hands
12
- back, with the row re-read after the grant so your snapshot reflects what the
13
- previous holder committed. The contended-agent snippet is copy-pastable
14
- between a browser tab and a Lambda without edits. Shape the wait the same way
15
- on either transport: the new `waitTimeoutMs` caps it (rejects `grant_timeout`
16
- and leaves the line), the new `signal` cancels it from outside (an
17
- `AbortSignal` — a cancelled agent task or an unmounted component takes its
18
- queued claim with it, rejecting `claim_wait_aborted`), `maxQueueDepth` bounds
19
- the line you'll join, and `queue: false` is now the try-claim: a held
20
- target resolves `null` — a declined try is an expected outcome, not an
21
- error — so claim-or-skip dedup reads `if (!claim) return` with no
22
- try/catch. (Previously it rejected with `entity_claimed`; a _write_ to a
23
- held row still does.)
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.
24
10
 
25
- For callers that manage the wait themselves, the HTTP client gains an
26
- `ablo.claims` namespace the ticket surface: `retrieve({ claimId })` polls a
27
- ticket to its grant (fence token included), `heartbeat({ claimId, ttl? })`
28
- keeps a held or queued lease alive by its id, `heartbeatAll({ ttl? })`
29
- extends every lease your identity holds in one round trip, and
30
- `release({ claimId })` gives a ticket back, promoting the waiters behind you.
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.
31
15
 
32
- Claims can also target part of a row — a `path` into a document, a `range` of
33
- text, a `field` or set of `fields` and two claims on non-overlapping parts
34
- of the same row are both granted. The engine has enforced this all along; it
35
- is now documented (the coordination reference leads with it), proven by
36
- grant-level tests, and gated so the docs can never again describe a narrower
37
- target than the wire accepts. The part names also read your schema now:
38
- `field` and `fields` autocomplete from the model's own field names, and an
39
- app-defined part — a cell, a section — has an explicit spelling: `part('B2')`
40
- (exported from the package root), which will become the only spelling for
41
- non-field names once the typed react binding removes the last loose arm.
42
- Inverted ranges (`startLine > endLine`) are rejected at the boundary instead
43
- of granting a lease that excludes no one.
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.
44
19
 
45
- The claim options themselves now show their structure: every option sits on
46
- one of four axes — what you claim (`field`/`fields`/`path`/`range`), what
47
- others see (`description`/`meta`), how you wait
48
- (`queue`/`maxQueueDepth`/`waitTimeoutMs`/`signal`), and how long you hold
49
- (`ttl`/`heartbeat`) — and the docs and the type read in that order. The
50
- lease axis slims from four members to two:
51
- `heartbeat: { every?, onBeat?, onLost? }` carries the cadence and both
52
- callbacks in one place, with `true` and a bare duration still the
53
- shorthands. Removed: the flat `onHeartbeat` and `onHeartbeatLost` options —
54
- move each callback onto the plan, unchanged: `heartbeat: { onBeat, onLost }`.
20
+ Credential providers now use the `CredentialProvider` type. The former
21
+ `ApiKeySetter` export is removed.
55
22
 
56
- The react layer gains its typed binding: `createAbloReact(schema)` (from
57
- `@abloatai/ablo/react`) returns `{ AbloProvider, useAblo }` with the schema
58
- captured once the one-binding-file convention of tRPC and typed
59
- react-redux hooks. Components import `useAblo` from your `lib/ablo` and
60
- never spell a type argument; the global `AbloProvider`/`useAblo<R>` keep
61
- working unchanged.
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.
62
27
 
63
- Coordination errors now name the next call: `grant_timeout` says how to
64
- rejoin or proceed, `stale_context` points at the functional update, and
65
- `queue_too_deep` distinguishes its claim meaning from its write meaning.
66
- Removed: the `claim_wait_poll_interval_required` error code no code path
67
- ever raised it, so no caller can be affected.
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.
68
36
 
69
- - 7950669: A new `@abloatai/ablo/syncLog` subpath exposes the canonical sync-log contracts:
70
- the Zod schemas and types for a delta's position and identity (`deltaPosition`,
71
- `clientMutationId`, `sourceCorrelationId`, `sourceChangeId`, `replicationLsn`),
72
- and the `commitDispatchMarker` that names the delta range a commit wrote. These
73
- are the single definition the client and server both read, so a tool that
74
- follows or reconciles the log validates positions and markers against the same
75
- shapes the engine enforces.
76
- - 61e4daf: The `/testing` subpath no longer ships. It carried the package's own test
77
- scaffolding — mocks, fixtures, and harnesses built on internals that were never
78
- a stable contract. If your tests need a stand-in for the write path, implement
79
- the public `MutationExecutor` interface from the `/core` subpath; a recording
80
- executor is about eighty lines and stays type-checked against the same contract
81
- the real client uses. For storage-adapter authors, `/source/conformance` remains
82
- the supported suite for proving an adapter correct.
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.
83
40
 
84
- ## 0.35.0
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.
85
44
 
86
- ### Minor Changes
87
-
88
- The `@abloatai/ablo/agent` entry point is removed, along with the single name it exported, `Agent`.
89
-
90
- There is no separate agent SDK, and in practice there has not been one for some time: an agent is
91
- the same client with a different transport. `Ablo({ schema, apiKey, transport: 'http' })` returns a
92
- stateless, request/response client whose credential is its identity, carrying the same typed
93
- `ablo.<model>` surface a browser or a server action uses, minus the parts that need a socket — the
94
- `local` reads and `onChange`, which are absent from the type rather than failing at runtime. A
95
- second entry point offered a second way in and, mostly, a second thing to keep in step with the
96
- first. If you imported from `@abloatai/ablo/agent`, import from `@abloatai/ablo` and select the
97
- transport.
98
-
99
- `ClaimEvent.participantKind` is also removed. A claim event is one transition in a lease's
100
- lifecycle — acquired, queued, granted, lost, rejected, expired — and it identifies the actor that
101
- moved it. Whether that actor was a person, an agent, or the system is a property of the holder, not
102
- of the transition, and stating it on every event meant maintaining the same fact in two shapes. Read
103
- it where the holder is described instead: `ablo.<model>.claim.state({ id })` returns the current
104
- holder with its `participantKind`, which is what the "an agent is editing this" affordance in a UI
105
- has always read.
106
-
107
- The client is now the coordination layer, and the things built on top of it are
108
- capabilities you install rather than parts of the client you get whether you use
109
- them or not.
110
-
111
- ```ts
112
- import { Ablo, humans } from '@abloatai/ablo';
113
-
114
- const ablo = Ablo({ schema, apiKey, plugins: [humans()] });
115
- ```
116
-
117
- `humans()` declares the local, watchable copy: the offline store, live queries,
118
- presence, and the framework bindings — everything that exists because a person
119
- is looking at the screen. What remains underneath is what coordination actually
120
- needs: commit, read, observe, claim. Omit `plugins` and `humans()` is installed
121
- for you, so an application that renders anything today keeps working with no
122
- change; naming it in the list only makes visible what was already true.
123
-
124
- There is no `agents()` counterpart, and the absence is the point. An agent is
125
- not a special caller that needs its own mode — it is the ordinary one, and what
126
- it needs is the client without the materialiser. That is the same observation
127
- behind removing the `/agent` entry point in this release: there was never a
128
- second SDK to enter, only a client with a transport that does not open a socket.
129
- `Ablo({ schema, apiKey, transport: 'http' })` is the whole of it, and because
130
- `humans()` requires a duplex connection, asking for it on that client fails while
131
- the client is being constructed, with an error naming the plugin, rather than
132
- resolving to a presence stream that can never receive a frame. A plugin listed
133
- twice fails the same way.
134
-
135
- The rearrangement is also why the reactive `ablo.<model>` surface composes from
136
- the transport-independent one rather than restating it — the note on the renamed
137
- local reads describes what that buys — and why `track`, a durable premise that
138
- keeps no local copy, sits in the core where an agent can reach it without
139
- installing a materialiser it has no use for.
140
-
141
- This is a seam, not a finished migration. `humans()` today constructs the
142
- presence stream and declares the constraint that keeps it off a stateless
143
- client; the rest of the materialiser — the store, the bootstrap, the delta
144
- pipeline, the framework bindings — still lives in the composition root and moves
145
- behind the same door as the plugin context grows to carry what each piece needs.
146
- The contract those pieces will arrive through is the one published here, so a
147
- capability written against it now keeps working as they do.
148
-
149
- The shape of a claim's `meta` is now declared once, on `Register`, beside your
150
- schema:
151
-
152
- ```ts
153
- declare module '@abloatai/ablo' {
154
- interface Register {
155
- Schema: typeof schema;
156
- ClaimMeta: { blocks: string[] };
157
- }
158
- }
159
- ```
160
-
161
- From then on `claim.state({ id })?.target.meta`, every entry in `claim.queue`,
162
- and the claim you hold under `await using` all read that shape, and the `typeof`
163
- guard each of those reads used to need is gone. The same declaration types what
164
- you write — `claim({ id, meta: { blocks } })` — so the writer and every reader
165
- agree by construction rather than by convention, which is the half a per-call
166
- type argument could never reach: naming the shape at the read site said nothing
167
- about what the write site was allowed to put there.
168
-
169
- Prefer the registration to the per-call `claim.state<MyMeta>({ id })`, because
170
- one declaration is one place to change when the metadata grows a field. The
171
- type argument remains supported and is still the honest answer for a program
172
- carrying more than one metadata shape; only its default moved, from a bare
173
- record to whatever you registered. A program that registers nothing sees no
174
- change at all — `target.meta` stays the loose record it has always been.
175
-
176
- What travels between participants is untouched. Claim metadata is carried
177
- verbatim and never interpreted, and the protocol keeps accepting any object it
178
- is given, so a peer on a newer build can send a field this one has never heard
179
- of and it still arrives intact. The declaration is a promise about what your own
180
- program writes, not a filter on what it receives.
181
-
182
- A model's conflict stance can be written as a chain through one name.
183
-
184
- ```ts
185
- import { coordination, model, z } from '@abloatai/ablo/schema';
186
-
187
- export const cards = model(
188
- {
189
- title: z.string(),
190
- },
191
- {
192
- conflict: coordination.humansOverwrite().agentsReject(),
193
- }
194
- );
195
- ```
196
-
197
- The nine disposition rules were the only loose functions left in the schema DSL.
198
- Everything else there arrives under a noun that owns it — `relation.belongsTo`,
199
- `field.string`, `mutable.lazy` — but declaring that humans overwrite and agents
200
- yield meant importing three separate names to say one thing, and a schema with a
201
- couple of stances spent more of its import list on conflict rules than on the rest
202
- of the language combined.
203
-
204
- So the rules now hang off `coordination`, and each one returns an axis that is
205
- still open to the next, the way a field chains its own modifiers. The rules
206
- themselves are unchanged, and each remains the single place its disposition map is
207
- written; the chain delegates to the standalone function rather than restating it.
208
- Those standalone functions stay exported, and `coordination()` still merges them,
209
- which is the form to reach for when the rules are assembled at runtime rather than
210
- written out:
211
-
212
- ```ts
213
- const stance = coordination(humansOverwrite(), agentsReject());
214
- ```
215
-
216
- Both forms produce the same plain, serializable map keyed by the committer's
217
- participant kind — `{ user: 'overwrite', agent: 'reject' }` — and the engine reads
218
- it at the commit chokepoint exactly as before. A kind left unnamed still falls
219
- through to the engine default, and a kind named twice still takes the later rule.
220
-
221
- `captureTransactionFailure` on the observability provider is renamed to `captureMutationFailure`.
222
- If you pass an `observability` provider to `Ablo(...)`, rename that one method; its argument is
223
- unchanged.
224
-
225
- The rename frees a word that had been doing two jobs. A transaction, in this system, is a unit of
226
- write that settles — the thing you author, hand to the engine, and get a receipt for. But the same
227
- word also named the client's optimistic queue, the buffer of writes that have been applied locally
228
- and are still waiting for the server to agree. The callback belonged to the second meaning: it
229
- fires when a queued write has exhausted its attempts and will never land, so the local change is
230
- rolled back and something upstream needs to hear about it. Calling that a transaction failure
231
- invited the reading that a settlement had failed, which is a different event with a different
232
- remedy — and as the settlement layer grew a vocabulary of its own, the collision stopped being
233
- cosmetic.
234
-
235
- There is no compatibility alias, and the reason is worth stating, because it is the opposite of
236
- the usual one. `captureMutationFailure` is a required member of the provider interface. A provider
237
- still carrying the old spelling therefore does not satisfy that interface at all: the compiler
238
- stops the build and names the missing member, at the line where the provider is constructed. An
239
- optional alias would have made that build succeed while the engine went on calling a method the
240
- provider does not implement — quiet in exactly the way a renamed callback must not be. The
241
- compiler is the better messenger here, and this note is the rest of the message.
242
-
243
- `debug`, `logLevel`, and `persistence` are no longer accepted on the stateless HTTP client. They
244
- remain exactly where they have always worked — on the default client, over the socket.
245
-
246
- They were reachable on `Ablo({ transport: 'http' })` only because that client's options type was
247
- assembled from the whole composition bag rather than from the fields the transport actually reads,
248
- and nothing on the HTTP path ever read them. `persistence` configures a local store a stateless
249
- client does not keep. `debug` and `logLevel` turn on the `[Ablo]` coordination trace, which
250
- narrates presence, claim grants, and queueing — frames that arrive over a socket an HTTP client
251
- never opens. Passing any of the three was accepted and did nothing, which is the kind of option
252
- that costs a reader an afternoon before they conclude the feature is broken.
253
-
254
- If you passed one to an HTTP client, delete it: nothing changes, because nothing was happening.
255
- If what you wanted was the trace from a server-side worker, it is not a setting you were missing —
256
- a stateless agent holds no live session to narrate, and its writes are visible in the audit log and
257
- in `ablo logs` instead.
258
-
259
- A model's load strategy is now `'instant'` or `'lazy'`, and nothing else. The
260
- word `'manual'` is gone, along with the `mutable.manual(...)` helper that set
261
- it.
262
-
263
- The strategy had drifted into two declarations that never agreed. The authoring
264
- side, reached through `@abloatai/ablo/schema`, offered `'instant' | 'lazy' |
265
- 'manual'`. The runtime side, reached through `@abloatai/ablo/core` under the
266
- same name, offered `instant`, `lazy`, `partial`, `explicitlyRequested`, and
267
- `local`. Importing `LoadStrategy` from the two paths gave you two different
268
- types, and no compiler anywhere would tell you which one you had.
269
-
270
- Only `instant` and `lazy` were ever real. `'manual'` promised loading a model
271
- "only when you explicitly call `sync.model.load()`", a method that was never
272
- built; a model declared manual resolved to `lazy` and loaded on first access
273
- like any other. `partial` had live branches in the client but no way to declare
274
- it, and `explicitlyRequested` and `local` had neither. So the correction is
275
- mostly the removal of vocabulary that described behavior the engine did not
276
- have — a model that said `manual` was already behaving as `lazy`, and continues
277
- to, which is why this changes what your schema is allowed to say rather than
278
- what your program does.
279
-
280
- Replace `load: 'manual'` with `load: 'lazy'`, and `mutable.manual(...)` with
281
- `mutable.lazy(...)`. `readOnly.internal(...)` stays, and its guarantee is
282
- unchanged and worth restating plainly: it is a write boundary, not a read one.
283
- Clients cannot commit to an internal model, and the server rejects any attempt.
284
- Its rows still reach a client that reads the model.
285
-
286
- `LoadStrategy` now has one declaration behind both import paths. It reads as a
287
- value where the engine branches on it — `LoadStrategy.instant` — and as the
288
- plain string an author types in `model(…, { load: 'instant' })`, and the two
289
- can no longer name different sets.
290
-
291
- Two names join it on `@abloatai/ablo/schema`. `DEFAULT_LOAD_STRATEGY` is the
292
- strategy a model gets when it declares none, and `loadsAtBootstrap(load)`
293
- answers whether a model's rows arrive in the bootstrap payload or on first
294
- access. The predicate is exported because both halves of the system have to
295
- agree on it: the client builds its bootstrap subscription from the schema and
296
- the server assembles the payload from the same schema, and they had been
297
- asking the question in three different spellings — `load !== 'lazy'` in one
298
- place, `load === 'instant'` in another. Against two members those agree. A
299
- third member would have had one side enrolling it and the other withholding it,
300
- which surfaces as rows that never arrive, a long way from the line responsible.
301
-
302
- - The three synchronous reads on `ablo.<model>` are renamed. `get(id)`, `getAll(options)`,
303
- and `getCount(options)` become `local.retrieve(id)`, `local.list(options)`, and
304
- `local.count(options)`.
305
-
306
- The old names were a pair that carried no information. `get` and `retrieve` are
307
- synonyms in English, so a reader who found `ablo.tasks.get(id)` beside
308
- `ablo.tasks.retrieve({ id })` had no way to tell from the words which one waited
309
- on the network and which one did not — the difference had to be memorised, and
310
- it was memorised imperfectly. Two bugs reached review that way, both of them a
311
- synchronous read standing in for an asynchronous one against a graph that had
312
- not warmed up yet, both of them invisible in the diff because the line read
313
- exactly like the call it should have been.
314
-
315
- The new names say the thing out loud. Every verb is the verb it already was on
316
- the asynchronous side; the only addition anywhere in the design is the word
317
- `local`, and it is a narrowing rather than a claim about its sibling.
318
- `retrieve` consults the local graph first and falls back to the network;
319
- `local.retrieve` is restricted to what is already resident, which is also why it
320
- can hand back a value instead of a promise. There is nothing to await. Reading
321
- `ablo.tasks.local.list({ where })`, you know both what it does and what it costs
322
- without leaving the line.
323
-
324
- The reads themselves are unchanged — same options, same lifecycle `state`
325
- filter, same reactivity inside `useAblo` selectors and in render. Only the path
326
- to them moved, and it is the only path: the local reads are not also exposed at
327
- the top level, because a namespace that duplicates what sits beside it stops
328
- disambiguating anything.
329
-
330
- Underneath, the rename arrives with a structural change that will keep the
331
- surface honest from here on. The reactive `ablo.<model>` type was hand-written as
332
- a second list that happened to agree with the transport-independent one, and
333
- nothing linked them: a verb added to one said nothing about the other. It is now
334
- composed from that base rather than restated, so a verb added there arrives on
335
- the reactive client on its own. `track` — registering a durable premise on a
336
- row — is the first to arrive that way.
337
-
338
- Migrating is mechanical, but reach for the compiler rather than a search. `getAll`
339
- and `getCount` are distinctive enough to find by name; `get` is not, and in a
340
- codebase of any size it hides among the `Map.get` and `headers.get` calls that a
341
- search cannot tell it from. Removing the old names from the type turns the
342
- question into a list of type errors, each one a site that genuinely needs to
343
- move.
344
-
345
- A capability is declared in the vocabulary you write, and every other spelling
346
- of it derives from that declaration.
347
-
348
- ```ts
349
- import { grantedOperations, modelWireNames } from '@abloatai/ablo/auth';
350
-
351
- const operations = grantedOperations(
352
- { documents: ['read', 'update'] },
353
- modelWireNames(schema.models)
354
- );
355
- ```
356
-
357
- The same grant used to be spelled five times: a literal union in the resource
358
- types, a `z.array(z.string())` on the wire, a field-by-field parser in the mint
359
- route, an object literal in the response type, and a hand-written `model.verb`
360
- array at every caller that mints without the SDK. Nothing failed when those
361
- drifted apart. The drift surfaced later as `capability_scope_denied` on a grant
362
- the caller was certain it held, at a call site with no view of the spelling that
363
- had gone stale.
364
-
365
- They are now one module on `@abloatai/ablo/auth`. `capabilityOperationSchema`
366
- holds the verb vocabulary; `grantedOperationSchema` derives the wire pattern
367
- `<model>.<verb>` from it as a template literal, so tightening the verbs cannot
368
- leave a stale regex or a stale published contract behind. `CapabilityCan<S>`
369
- narrows the declaration to one schema's models, so `can: { tasks: ['update'] }`
370
- fails to compile against a schema with no `tasks` model.
371
-
372
- `modelWireNames` is the derivation worth knowing about, because it is the one
373
- people got wrong by hand. A model whose type name is overridden — schema key
374
- `documents`, type name `Document` — must be granted as `document.update`, and a
375
- caller who works that out by eye learns otherwise at enforcement time. Pass your
376
- schema's models and the map is built for you. It is a required argument rather
377
- than an optional one: an omitted map would mint the schema key verbatim, which
378
- is right for most models and wrong for exactly the ones that need it.
379
-
380
- Two more derivations ride along. `expandReadYourWrites` appends `<model>.read`
381
- for every model the grant can write, because a scoped agent that may update a
382
- row has to be able to read it or the read gate starves the writes the grant
383
- allows — write verbs stay the source of truth, reads are derived and deduped,
384
- and a model the grant cannot write stays unreadable. And a grant is now checked
385
- against the schema **at the mint**: `capabilityModelAliases` accepts any of the
386
- three names one model answers to (type name, schema key, table name), and
387
- `unresolvableOperations` reports the ones that name nothing, so a typo is a
388
- rejected mint rather than a credential that looks healthy until its first write.
389
-
390
- ### Breaking
391
-
392
- `ExchangeApiKeyRequest.operations` narrows from `readonly string[]` to
393
- `readonly GrantedOperation[]`. If you assembled that array by hand, the compiler
394
- will now reject any entry that is not `<model>.<verb>` — including the `'*'`
395
- wildcard, which was never a member of the wire form and is no longer typable as
396
- one. Build the array with `grantedOperations(can, modelWireNames(schema.models))`
397
- instead of composing the strings; that is the same call the SDK makes, and it
398
- resolves the type-name override you would otherwise have to remember.
399
-
400
- Three error codes carried the word "intent", left over from the vocabulary the
401
- coordination layer used before it settled on claims. The word had come to mean
402
- two unrelated things in the same registry, so both are now named for what they
403
- are.
404
-
405
- `invalid_intent` is removed. It was raised when a claim request failed to name
406
- the model and row it targeted — which is what `malformed_claim` already
407
- described, in the same words, for the same failure arriving over the WebSocket
408
- instead of over HTTP. The two have been merged into `malformed_claim`; a caller
409
- switching on `invalid_intent` should switch on `malformed_claim` instead, and
410
- the HTTP status is 400 either way.
411
-
412
- `slide_intent_missing_deck_id` and `slide_intent_unknown_sibling` are renamed to
413
- `position_missing_parent` and `position_unknown_sibling`. These were never about
414
- claims at all: they report that a row created at a position stated relative to
415
- its neighbours — "before this one" — could not have that position resolved,
416
- because the parent it belongs to was not named or because the neighbour is not
417
- in it. An earlier rename had swept the word "claim" through their descriptions
418
- and left them reading as contention errors. The slide and the deck have gone the
419
- same way as the intent: nothing in the rule is particular to a deck, and a
420
- registry that every schema's callers switch on should not name one consumer's
421
- models, so the codes and their messages now speak of a row and its parent. Both
422
- remain `validation` errors carrying a 400.
423
-
424
- `malformed_claim` moves from the `claim` category to `validation`, alongside
425
- `malformed_subscription`. The code, its 400 status, and its meaning are
426
- unchanged — only the grouping it appears under in the error documentation. The
427
- `claim` category now holds only genuine contention, which is what its name
428
- promised.
429
-
430
- `model()` now takes a shape and one options object. Relations, which used to hold a
431
- positional argument of their own in between, are the `relations` key on those options.
432
-
433
- ```ts
434
- documents: model(
435
- {
436
- title: z.string(),
437
- projectId: z.string(),
438
- },
439
- {
440
- relations: { project: relation.belongsTo('projects', 'projectId') },
441
- conflict: coordination(humansOverwrite(), agentsReject()),
442
- load: 'lazy',
443
- }
444
- ),
445
- ```
446
-
447
- The middle argument was the problem. Most models have no relations at all, yet nearly
448
- every model has something to say about itself — a table it maps to, a load strategy, a
449
- conflict disposition — and the only way to reach that third argument was to write the
450
- second one first. So the schema filled up with empty braces standing in for edges that
451
- did not exist. In this repository alone the placeholder appeared 104 times, which made
452
- `{}` the single most common thing anyone ever passed in that position: more often than
453
- any real relations object. A reader opening a schema met the empty braces before they
454
- met the model.
455
-
456
- Nothing about relations themselves changes. They are built with the same
457
- `relation.belongsTo` and `relation.hasMany` factories, they carry the same options, and
458
- the engine reads them exactly as before — to index foreign keys, to order inserts so a
459
- parent lands before its children, and to generate the accessors behind `task.project`.
460
- They have simply stopped being a position and become a name, which is what they always
461
- were to everything downstream.
462
-
463
- Migrating is mechanical, and the compiler finds every site. A call that passed an empty
464
- placeholder drops it:
465
-
466
- ```ts
467
- -model({ title: z.string() }, {}, { groups: { root: 'workspace' } }) +
468
- model({ title: z.string() }, { groups: { root: 'workspace' } });
469
- ```
470
-
471
- A call that passed real relations folds them in under their own key:
472
-
473
- ```ts
474
- - model({ title: z.string(), projectId: z.string() }, {
475
- - project: relation.belongsTo('projects', 'projectId'),
476
- - }, { load: 'lazy' })
477
- + model({ title: z.string(), projectId: z.string() }, {
478
- + relations: { project: relation.belongsTo('projects', 'projectId') },
479
- + load: 'lazy',
480
- + })
481
- ```
482
-
483
- A call that passed only fields is untouched.
484
-
485
- `app.current_user_id` is now a reserved session setting, so a schema can no
486
- longer claim it — and a read that cannot be scoped to the person asking says so
487
- with `user_scope_not_enforced` instead of quietly returning rows.
488
-
489
- Ablo's direct-write connection already applied a fixed bundle of settings before
490
- your DML — the organization, the project, the environment, the sandbox, the
491
- acting participant — and `sessionSettings` let a schema forward any of those
492
- into a differently-named setting its own policies read. What a schema could
493
- never do was reassign one of the engine's own settings, because that would let a
494
- schema push relax the scoping under which Ablo writes. `RESERVED_SESSION_SETTINGS`
495
- is the list that prevents it, and the engine now sets one more: the acting user.
496
-
497
- The setting carries a person's id when a person is behind the write, and `'*'`
498
- when the request comes from a backend credential acting as the organization
499
- itself. Absent identity writes the empty string rather than leaving the previous
500
- value on a pooled connection, so a policy reading it denies rather than
501
- inheriting. Your own policies are welcome to read it; they simply cannot be the
502
- thing that decides what it contains.
503
-
504
- If your schema mapped `app.current_user_id` through `sessionSettings`, that entry
505
- is now dropped at authoring time and at runtime, and the engine's own value
506
- applies instead. Map a different name if your policies need a value you control.
507
-
508
- The new error code covers the one arrangement where that boundary cannot hold. A
509
- plane served from its retained log carries the organization and the project on
510
- every row but not the owner, so a rule that names a person has nothing to act on
511
- there. Rather than fold the rows and return a plausible answer, such a read is
512
- declined whole — a member reading a colleague's private records would otherwise
513
- be indistinguishable from a member reading their own. Reads made by a credential
514
- acting for the organization are unaffected, as is every plane served from its
515
- tables.
516
-
517
- All of this now has a page. The seam has been in the SDK since 0.32.0 and under
518
- this name since 0.33.0, documented both times only in a release note — which is
519
- a poor place to keep a feature rather than announce one:
520
- a reader with row-level-security policies had no way to discover that Ablo sets
521
- an identity context at all, let alone that they could point their own policies
522
- at it. **Session Settings** states what the engine sets before every write, which
523
- identities a mapping may name, which settings are reserved and why, and what
524
- happens on a plane served from its log. Read it with `npx ablo docs
525
- session-settings`, or on the site.
526
-
527
- `causedByTaskId` is removed from `MutationOptions` — and so from the per-verb params that
528
- compose it, `ModelCreateParams`, `ModelUpdateParams`, and `ModelDeleteParams` — together with
529
- the seven `turn_*` error codes that existed to police it. It also leaves `CommitContext`, where
530
- the server carried it from the wire to the stored row. The field was never usable for its stated
531
- purpose: setting it was the one reliable way to have a write refused.
532
-
533
- It was the surviving half of an agent-turn protocol whose other half was never built. The idea
534
- had been that an agent would open a turn, write under it, and close it, so that an auditor could
535
- later ask what an agent did _because_ a person asked it to — a prompt at the root of a tree of
536
- changes. What actually shipped was only the check. Every commit that carried a `causedByTaskId`
537
- was validated against a task record, and nothing in the system has ever opened one, so the record
538
- was never there. A caller who supplied the field had the entire batch rejected with
539
- `turn_validation_failed`; a caller who left it null passed straight through. The prudent thing to
540
- do with a documented, public option was to never touch it, and the field spent its life as
541
- plumbing threaded through the wire protocol, the settlement envelope, the stored row, and the
542
- replication consumer, carrying null on every path.
543
-
544
- Nothing is lost by its removal, because write attribution never depended on it. A delta already
545
- records who made the change and on whose authority — the actor, the `onBehalfOf` principal behind
546
- a delegated write, the capability that authorized it, and the claim the write was made under. Those
547
- answer "who did this and by what right" without asking the caller to maintain a lifecycle the
548
- engine never opened on their behalf. If your code passed `causedByTaskId`, delete the argument;
549
- if it branched on `turn_validation_failed`, that branch was unreachable and can go with it.
550
-
551
- The removed codes are `turn_validation_failed`, `turn_open_failed`, `turn_close_failed`,
552
- `turn_not_found`, `turn_foreign_agent`, `parent_turn_not_found`, and `parent_turn_foreign_agent`.
553
- `ERROR_CONTRACT_VERSION` moves to `2026-07-19`, as it does whenever a code leaves the registry.
554
- The stored-row schema loses its provenance slice with them: `deltaProvenanceSchema` and the
555
- `DeltaProvenance` type are gone, and `syncDeltaRowSchema` is now the core and attribution slices
556
- composed, which is what it had always described in practice. On the wire the field was optional
557
- and nullable, so a client that still sends it is accepted and ignored, and a client reading deltas
558
- sees one fewer always-null key.
559
-
560
- The storage goes with it. The `caused_by_task_id` column leaves `sync_deltas`, `agent_actions_log`,
561
- and `source_write_intents`, and the two tables that were never written — `agent_tasks` and
562
- `agent_task_prompts` — are dropped along with the `agent_surface` enum that typed them. This looked
563
- at first like a separate versioned migration, because the audit chain hashes the lineage id into
564
- every row's signature and rewriting those signatures is not something a release should do quietly.
565
- It turned out not to need rewriting. Every row was hashed with a null in that position, so the
566
- canonical byte layout the stored hashes were computed against is preserved exactly by passing
567
- `NULL::text` where the column used to be read — which is what the migration does, in the append
568
- trigger and in the rechain function, before the column is dropped. The signature is untouched and
569
- every historical `row_hash` still verifies. The `packages/audit-chain` reproduction freezes the same
570
- position, so the two implementations cannot drift apart on it.
571
-
572
- Eight names that were renamed in an earlier release, and have shipped since as
573
- aliases beside the names that replaced them, are removed.
574
-
575
- | Removed | Use |
576
- | ----------------------- | ------------------------ |
577
- | `CommitOutboxRecord` | `PendingWrite` |
578
- | `CommitOutboxStore` | `DurableWriteStore` |
579
- | `MeshParticipantStatus` | `ParticipantStatus` |
580
- | `planeSchema` | `residencySchema` |
581
- | `SchemaPlane` | `ModelResidency` |
582
- | `DEFAULT_PLANE` | `DEFAULT_RESIDENCY` |
583
- | `DELTA_RESIDENCY` | `DELTA_PHYSICAL_STORAGE` |
584
- | `InferModel` | `Model` |
585
-
586
- Each is a one-for-one substitution — the alias and its replacement were the same
587
- type or the same value, so nothing about your program's behavior changes with
588
- the name. `InferModel` is the one worth a sentence: `Model<typeof schema,
589
- 'tasks'>` reads as the domain rather than the machinery, which is why it became
590
- the published spelling, and the old name is gone from `@abloatai/ablo/schema`
591
- and from the `Ablo.Schema` namespace both. The type it named still exists
592
- underneath, because `Model` is defined in terms of it rather than as a second
593
- name for the same idea.
594
-
595
- An alias earns one release of overlap. That is what the surface snapshot in
596
- this repository records and enforces: a published name may leave only from a
597
- release that shipped it marked deprecated, which is the release in which a
598
- reader could have found out. These eight were marked in 0.34.1, so this is the
599
- release that may drop them, and dropping them is what keeps the rule a rule
600
- rather than a preamble on a list that only grows.
601
-
602
- `DELTA_RESIDENCY` is the one whose replacement is not merely a rename.
603
- Residency and classification are separate axes — where a column physically
604
- lives, and how sensitive its contents are — and one constant named as though
605
- they were the same question. `DELTA_PHYSICAL_STORAGE` answers the first;
606
- `DELTA_DATA_CLASSIFICATION`, which has been exported alongside it, answers the
607
- second. If you were reading `DELTA_RESIDENCY` to decide handling rather than
608
- placement, the classification constant is the one you wanted.
609
-
610
- `ablo docs` reads the documentation for the version you installed, and the
611
- public docs routes now serve the whole corpus instead of two thirds of it.
612
-
613
- A documentation URL always describes the newest release. A package in
614
- `node_modules` is frozen at whatever version was published. Those two facts
615
- drift apart the moment a project pins a dependency, and the drift is invisible
616
- from both ends: the docs are correct, the code is correct, and the agent reading
617
- one to write the other produces a call that does not exist. `get`, `getAll`, and
618
- `getCount` became `retrieve` and `list` in 0.35.0, so an assistant working in a
619
- project still on 0.34 reads the current page, writes `retrieve`, and watches it
620
- fail against the package sitting beside it.
621
-
622
- The documentation already travelled in the npm tarball; nothing read it there.
623
- `ablo docs` lists every page with what it covers, and `ablo docs <page>` prints
624
- one as markdown — from the files shipped alongside the code they describe, so
625
- they cannot disagree with it, and with no network at all, which is the condition
626
- most agent work now runs under. `ablo docs --json` gives the same list to a
627
- program. A new `@abloatai/ablo/docs` entry point exposes the catalog for
628
- anything that wants to build its own reader.
629
-
630
- The same catalog now answers `/api/docs/*`. That surface had been maintained by
631
- hand and had fallen twenty-one pages behind: `coordination`, the page explaining
632
- how claims work, returned a 404 while the site published it, and so did
633
- `sessions`, `webhooks`, `migration`, `deployment`, and every example but two.
634
- Nothing failed when the list went stale, which is why it stayed stale. Pages are
635
- now discovered rather than listed, so one is reachable the moment it is written,
636
- and only what ships in the package is reachable at all.
637
-
638
- One address changed meaning. `/api/docs/agents` returns the Agents guide, the
639
- page the docs index has always linked under that name; the install playbook that
640
- briefly answered there is at `/api/docs/AGENTS.md`, where it also answered
641
- before.
642
-
643
- `GET /v1/logs` answers two questions now — what changed, and who is working on
644
- what — through one envelope and one cursor.
645
-
646
- ```ts
647
- import { feedEventSchema, parseFeedCursor } from '@abloatai/ablo/wire';
648
-
649
- const page = await fetch(`${baseUrl}/v1/logs?after=${cursor}`, {
650
- headers: { authorization: `Bearer ${apiKey}` },
651
- }).then((r) => r.json());
652
-
653
- for (const event of page.data) {
654
- const entry = feedEventSchema.parse(event);
655
- if (entry.object === 'log_event') applyChange(entry);
656
- }
657
- cursor = page.next_cursor;
658
- ```
659
-
660
- An entry is discriminated on `object`, so a reader that knows only the arm it
661
- came for parses the whole page and skips the rest — which is what lets a second
662
- arm be added to a feed that callers are already following. The two arms stay
663
- separately sequenced on purpose. A claim is a lease rather than a settled fact,
664
- and allocating its positions from the delta sequence would put ephemeral leases
665
- into the log that clients materialise rows from and that WAL-echo promotion and
666
- compaction operate over, where a burst of claim churn would be indistinguishable
667
- from committed change. They share a reading, not a sequence.
668
-
669
- That is why the cursor carries a position for each, encoded `"<log>.<claims>"`.
670
- A bare `"<log>"` still parses and resumes at claim position zero, which is the
671
- right answer for every cursor issued before this existed: a caller who never
672
- asked for claim events has no claim position to preserve. `parseFeedCursor`,
673
- `formatFeedCursor`, and `feedCursorAdvanced` are exported from
674
- `@abloatai/ablo/wire` alongside `feedEventSchema`, `logListResponseSchema`, and
675
- `claimEventSchema`, so a program that builds its own reader states the grammar
676
- once. `claimRecordSchema` and `heldClaimStatusSchema` join them on
677
- `@abloatai/ablo/coordination` — the one claim record, and the peer-visible
678
- projection of it.
679
-
680
- `GET /v1/claims` gains the filters the audit log already had: `actorId`,
681
- `actorKind`, `onBehalfOfId`, and `capabilityId`, beside the existing `model` and
682
- `id`. The names are audit's names deliberately. Asking what an agent is doing
683
- and asking what it did should not require two vocabularies for one idea, and now
684
- the tense is the only thing that differs.
685
-
686
- The claim arm has no producer yet. Claim transitions are broadcast and recorded
687
- nowhere, so there is no sequence to read a position from, and the route emits
688
- only `log_event`s today — with no request parameter for selecting arms, because
689
- a knob that cannot be honoured is how a contract comes to describe a server that
690
- does not exist. What ships here is the definition that producer will fill: the
691
- union parses today's pages unchanged, and the cursor already carries the second
692
- position, so a follower written now keeps working when the arm arrives.
693
-
694
- ### The cursor fix
695
-
696
- A malformed `after` is now an error rather than a position. The previous reader
697
- was `parseInt(raw, 10)` with a `NaN` falling back to zero, so a truncated or
698
- garbled cursor resumed from the beginning of the log and replayed it in full —
699
- while looking exactly like a working follow, with the damage scaling to how long
700
- the log had been running. Such a request is now declined with `invalid_request`,
701
- naming the `after` parameter and how to recover. `ablo logs` carried the mirror
702
- of the same bug: it read the cursor with `Number(...)`, which is harmless while a
703
- cursor is a bare delta id and silently wrong the moment it has two parts —
704
- `Number('42.10')` is `42.1`, which re-serialises as `'42.1'` and resumes eight
705
- claim positions late. The cursor is now a string end to end, passed back exactly
706
- as it was issued.
707
-
708
- `legacyCompatibleCommitReceiptSchema` is removed from `@abloatai/ablo/wire`. The WebSocket client
709
- now parses a commit acknowledgement with `commitReceiptSchema`, the canonical one, directly.
710
-
711
- It was a compatibility decoder rather than a contract: a `z.preprocess` step that supplied fields
712
- older servers omitted and coerced their string `lastSyncId` into a number before handing the result
713
- to the canonical schema, which did the real work. It was careful in one respect worth recording —
714
- it never invented a source correlation, so an old or malformed queued receipt still failed closed
715
- rather than being promoted to something the server had not said.
716
-
717
- The servers it decoded for predate the commit contract, and the hosted engine has not spoken that
718
- shape in some time. Keeping the wrapper meant every receipt on the socket path went through a
719
- normalizer for a case that no longer arrives, and it left two spellings of "a receipt" on a
720
- published surface whose entire purpose is to have one.
721
-
722
- This matters only if you run a self-hosted engine older than the commit contract: its
723
- acknowledgements will now fail to parse, and the write is reported as `commit_no_result` — pending
724
- and safe to retry — rather than being silently normalized. Upgrade the engine. If you imported the
725
- schema to parse receipts yourself, `commitReceiptSchema` is the replacement and always was the one
726
- doing the parsing.
727
-
728
- ### Patch Changes
729
-
730
- `ablo login`, `ablo mode`, and `ablo status` now say what the key in your hand
731
- can do, and `status` labels the plane your credential reaches as `acts on`
732
- rather than `env`.
733
-
734
- Logging in provisions a pair — a secret sandbox key and a restricted production
735
- one — so that a stolen CLI config cannot deploy to production. That protection
736
- was deliberate and it was also silent. `login` confirmed the sandbox and moved
737
- on, `mode production` printed a checkmark, and `status` showed the production
738
- key as twelve characters of prefix. Nothing said the key could not push, so the
739
- first notice was a 403 in the middle of a deploy, at the moment it cost the
740
- most to learn.
741
-
742
- Each of those three commands now names the capability at the point it hands you
743
- the credential: the key rows in `status` carry what each key does beside how
744
- long it lasts, the line under `push` says which secret key deploys, and
745
- `ablo status --json` reports `effectiveKey.kind` so a pipeline can check before
746
- it pushes instead of after it fails. The wording has one definition site, so the
747
- command that stores a key, the command that switches to it, and the command
748
- that reports it cannot describe it three different ways.
749
-
750
- The renamed `status` line is the same fix in smaller form. `mode` and `env`
751
- printed as peers and were not: one is the environment you selected, the other is
752
- the one your credential actually reaches, and when they diverged neither word
753
- said which was which. `acts on` states the fact. The `--json` output is
754
- unchanged apart from the added `kind`.
755
-
756
- ## 0.34.1
757
-
758
- ### Patch Changes
759
-
760
- 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.
761
-
762
- 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.
763
-
764
- 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.
765
-
766
- ## 0.34.0
767
-
768
- ### Minor Changes
769
-
770
- 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.
771
-
772
- 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.
773
-
774
- ### Patch Changes
775
-
776
- `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.
777
-
778
- ## 0.33.0
779
-
780
- ### Minor Changes
781
-
782
- 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:
783
-
784
- ```ts
785
- defineSchema({
786
- models: { document: {/* … */} },
787
- sessionSettings: { 'app.current_org': 'orgId' },
788
- });
789
- ```
790
-
791
- 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.
792
-
793
- 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.
794
-
795
- ### Patch Changes
796
-
797
- 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.
798
-
799
- 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.
800
-
801
- ## 0.32.0
802
-
803
- ### Minor Changes
804
-
805
- 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.
806
-
807
- 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.
808
-
809
- 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`.
810
-
811
- 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:
812
-
813
- ```ts
814
- defineSchema({
815
- models: { document: {/* … */} },
816
- tenantContext: [{ guc: 'app.current_org', from: 'orgId' }],
817
- });
818
- ```
819
-
820
- 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.
821
-
822
- 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.
823
-
824
- 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.
825
-
826
- 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.
827
-
828
- ## 0.31.0
829
-
830
- ### Minor Changes
831
-
832
- 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.
833
-
834
- 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.
835
-
836
- 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`.
837
-
838
- 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.
839
-
840
- ## 0.30.2
841
-
842
- ### Patch Changes
843
-
844
- - **`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.
845
-
846
- 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.
847
-
848
- ## 0.30.1
849
-
850
- ### Patch Changes
851
-
852
- - **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.
853
-
854
- 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.
855
-
856
- - **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.
857
-
858
- ## 0.30.0
859
-
860
- ### Minor Changes
861
-
862
- - **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:
863
-
864
- - **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.
865
- - **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.
866
-
867
- 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`.
868
-
869
- - **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.)
870
-
871
- - **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.
872
-
873
- ### Patch Changes
874
-
875
- - **`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.
876
-
877
- - **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.
878
-
879
- ## 0.29.3
880
-
881
- ### Patch Changes
882
-
883
- - **`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.
884
-
885
- ## 0.29.2
886
-
887
- ### Patch Changes
888
-
889
- - **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.
890
-
891
- ## 0.29.1
892
-
893
- ### Patch Changes
894
-
895
- - **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.
896
-
897
- ## 0.29.0
898
-
899
- ### Minor Changes
900
-
901
- - **`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.
902
-
903
- - **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.
904
-
905
- - **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.
906
-
907
- - **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.
908
-
909
- - **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.
910
-
911
- - **`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.
912
-
913
- - **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.
914
-
915
- - **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.
916
-
917
- ### Patch Changes
918
-
919
- - **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.
920
-
921
- ## 0.28.0
922
-
923
- ### Minor Changes
924
-
925
- - **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.
926
-
927
- - **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.
928
-
929
- - **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.
930
-
931
- - **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.
932
-
933
- - **`@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.
934
-
935
- - **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`).
936
-
937
- - **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.
938
-
939
- ### Patch Changes
940
-
941
- - **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.
942
-
943
- - **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.
944
-
945
- - **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.
946
-
947
- - **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.
948
-
949
- - **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.
950
-
951
- - **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.
952
-
953
- ## 0.27.0
954
-
955
- ### Minor Changes
956
-
957
- - **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.
958
-
959
- - **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.
960
-
961
- - **`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.
962
-
963
- - **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.
964
-
965
- - **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.
966
-
967
- ## 0.26.0
968
-
969
- ### Minor Changes
970
-
971
- - **`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.
972
-
973
- - **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).
974
-
975
- - **`@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.
976
-
977
- - **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.
978
-
979
- - **`@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.
980
-
981
- - **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.
982
-
983
- - **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.
984
-
985
- - **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.
986
-
987
- ## 0.25.0
988
-
989
- ### Minor Changes
990
-
991
- - ca30064: Logical replication is now the documented default storage path, with self-service data-source registration from the CLI.
992
- - **`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.
993
- - **`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.
994
- - **`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).
995
- - **`ablo push`** — minor guard/UX refinements (deploy-target clarity).
996
- - **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.
997
-
998
- 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.
999
-
1000
- ## 0.24.0
1001
-
1002
- ### Minor Changes
1003
-
1004
- - Multi-agent coordination for AI SDK tools + safer `ablo push`.
1005
- - **`coordinatedTool` (`@abloatai/ablo/ai-sdk`)** — one call turns an Ablo model
1006
- write into a Vercel AI SDK `tool()` with concurrency coordination handled, so an
1007
- AI agent can contribute to shared state without clobbering concurrent writers.
1008
- Three strategies: `merge` (default — delegates to the functional update's
1009
- compare-and-swap + backoff, self-healing accumulate), `claim` (mutual exclusion,
1010
- returns a `claimed` signal the model retries on), and `queue` (SQS-style
1011
- poll-acquire over HTTP). The `ai-sdk` entry now also documents the canonical
1012
- multi-agent coordination model (surface-the-signal + back-off laws, strategy
1013
- table).
1014
- - **`ablo push` guards** — `--dry-run`/`--plan` prints the deploy target, a
1015
- model-level diff vs the deployed schema, and git state, then exits without
1016
- applying. Production deploys now require a typed confirmation (and refuse an
1017
- uncommitted schema unless `--allow-dirty`); sandbox confirms interactively.
1018
- `--yes`/`-y` skips confirmation for CI.
1019
-
1020
- ## 0.23.0
1021
-
1022
- ### Minor Changes
1023
-
1024
- - 2807efb: `create` now returns the created row, not a `CommitReceipt`.
1025
-
1026
- The WebSocket client's `create` already returned the row (`T`); the HTTP client
1027
- and the `.model(name)` accessor returned a `CommitReceipt`, so "create returns
1028
- the thing I created" only held on one transport. Both now return the confirmed,
1029
- authoritative server row (framework defaults like `createdAt`/`createdBy`
1030
- included). For an idempotent re-create of an existing caller-supplied id, the
1031
- EXISTING row is returned (not the input).
1032
-
1033
- BREAKING (HTTP / `.model()` callers only): `await ablo.<model>.create(...)` now
1034
- resolves to the row instead of `{ status, lastSyncId, ... }`. Code that ignored
1035
- the return value, or that read `.id` (the row carries `id` too), is unaffected;
1036
- code that read `lastSyncId` / `serverTxId` / `status` off a typed model create
1037
- should use the raw `commits.create(...)` resource, which still returns a
1038
- `CommitReceipt`. WebSocket-client callers are unaffected (already returned `T`).
1039
-
1040
- - 2807efb: `delete` is idempotent — deleting an already-absent row is a no-op success, not
1041
- an error.
1042
-
1043
- The WebSocket client's `delete` threw `entity_not_found` when the row wasn't in
1044
- the local pool, while the HTTP client returned without error — so "delete this"
1045
- was a hard edge on one transport. Both now agree: a row that isn't present is
1046
- already gone, so the delete succeeds with no effect. This is AIP-135's
1047
- recommended behavior for client-assigned-id / declarative APIs (Ablo is exactly
1048
- that), and it makes delete safe to retry and to race (two actors deleting the
1049
- same row). The deliberate "loud 0-row" assertion in `@ablo/slides-sdk` is
1050
- unchanged (it keeps its own `allowMissing` opt-out).
1051
-
1052
- - 2807efb: `retrieve` reports a missing row as `data: undefined` instead of throwing.
1053
-
1054
- The HTTP client previously threw `model_not_found` for a missing row while the
1055
- WebSocket client returned `T | undefined` — so the obvious read ("does this row
1056
- exist?") was a hard edge an agent had to wrap in `try/catch` on one transport
1057
- only. Both transports now agree: an absent row is data-absence, not an error.
1058
- `ModelRead.data` is now `T | undefined` (matching the documented `.data?.x`
1059
- usage). Taking a `claim` on a row that doesn't exist still throws
1060
- `AbloNotFoundError` — a claim has nothing to hold.
1061
-
1062
- ## 0.22.1
1063
-
1064
- ### Patch Changes
1065
-
1066
- - Expose the functional `update(id, current => next)` overload on the stateless
1067
- HTTP client type (`HttpModelClient` / `AbloHttpClient`).
1068
-
1069
- 0.22.0 wired the functional update at runtime on every transport and added the
1070
- overload to `ModelOperations` (WebSocket) and `ModelClient`, but the
1071
- `Ablo({ transport: 'http' })` client resolves its models to `HttpModelClient`,
1072
- whose `update` type still declared only the `update({ id, data })` form. So
1073
- server-side agents — the primary callers — saw a type error on
1074
- `update(id, fn)` even though it worked. Add the overload to that type.
1075
-
1076
- ## 0.22.0
1077
-
1078
- ### Minor Changes
1079
-
1080
- - Add the functional update form: `ablo.<model>.update(id, current => next)`.
1081
-
1082
- The `setState(prev => next)` of the data layer. Pass a function of the latest
1083
- row and the SDK owns everything that used to be the caller's problem under
1084
- contention: it reads the freshest row, runs your updater, writes it as a
1085
- compare-and-swap against the row's watermark, and re-reads + re-runs on any
1086
- concurrent write. No claim, no per-participant identity, and no
1087
- `stale_context` / `claim_*` codes ever surface — correctness rides on the
1088
- watermark, so concurrent writers reconcile instead of silently clobbering. The
1089
- write either lands or throws a single `AbloContentionError` once its reconcile
1090
- budget is spent.
1091
-
1092
- Identical guarantee on both transports (HTTP and WebSocket share one reconcile
1093
- loop). Return `null`/`undefined` from the updater to skip the write. Tune with
1094
- `{ retries, signal }`. Exports: `AbloContentionError`, `ModelUpdater`,
1095
- `ContentionOptions`, `DEFAULT_CONTENTION_RETRIES`.
1096
-
1097
- The classic `update({ id, data })` form is unchanged.
1098
-
1099
- ## 0.21.0
1100
-
1101
- ### Minor Changes
1102
-
1103
- - 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:
1104
- - **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.
1105
- - **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.
1106
-
1107
- 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.
1108
-
1109
- ## 0.20.2
1110
-
1111
- ### Patch Changes
1112
-
1113
- - 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.
1114
-
1115
- ## 0.20.1
1116
-
1117
- ### Patch Changes
1118
-
1119
- - 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.
1120
-
1121
- ## 0.20.0
1122
-
1123
- ### Minor Changes
1124
-
1125
- - 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.
1126
-
1127
- Two changes make this work:
1128
- - **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.
1129
- - **`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.
1130
-
1131
- 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.
1132
-
1133
- ## 0.19.0
1134
-
1135
- ### Minor Changes
1136
-
1137
- - **Claim observability — a `ClaimLog` you can print or assert on.** A new
1138
- `observability` provider hook lets you tap every claim event and stale-write
1139
- collision the client sees. Hand `new ClaimLog()` to `Ablo({ observability })`
1140
- and it collects an ordered, readable log — `formatClaim` / `formatConflict`
1141
- render one line per event, and `collisions()` returns the conflicts for eval
1142
- assertions. New exports: `ClaimLog`, `formatClaim`, `formatConflict`,
1143
- `noopObservability`, and the types `ClaimLogEntry`, `ClaimEvent`,
1144
- `ConflictEvent`, `SyncObservabilityProvider`. Spread `noopObservability` to
1145
- override only the hooks you care about.
1146
-
1147
- **AWS-shaped CLI credential store + `ablo config`.** Local CLI state is now split
1148
- into two files, matching `~/.aws/config` vs `~/.aws/credentials`: `config.json`
1149
- holds non-secret settings (active environment + active project) and is safe to
1150
- print or let an agent read; `credentials.json` holds the keys (0600, never
1151
- printed), keyed by project profile then environment. Per-project profiles follow
1152
- Stripe's model — `ablo projects use <slug>` selects the active profile, and a
1153
- key's project is fixed at mint so selecting a project never re-scopes an existing
1154
- key. `ablo status` now reports the resolved profile and environment.
1155
-
1156
- **Schema JSON-column reconciliation.** `generateJsonColumnReconciliation` (new
1157
- export) emits the DDL to reconcile JSON-backed columns when adopting or evolving
1158
- an existing schema.
1159
-
1160
- **Breaking (0.x):**
1161
- - The claim handle type `ClaimHandle` is renamed to **`Claim`**, and its
1162
- identifier field is `id` (was `claimId`). Update type imports and any code
1163
- reading `.claimId`.
1164
- - The ai-sdk `claimBroadcastMiddleware` (and `./ai-sdk/claim-broadcast`) is
1165
- removed — coordination broadcast is handled by `coordinationContextMiddleware`.
1166
- Import `ClaimTarget` from the package root or `@abloatai/ablo` ai-sdk's
1167
- `coordination-context` instead of `claim-broadcast`. The inline-claim option is
1168
- `reason` (not the pre-0.12 `action`); the ai-sdk docs are corrected to match.
1169
-
1170
- ## 0.18.0
1171
-
1172
- ### Minor Changes
1173
-
1174
- - **Client observability — `debug` / `logLevel`, off by default.** The SDK used to
1175
- emit a `debug` line per model and per property during schema registration (a
1176
- firehose). It now defaults to a quiet `warn` threshold and exposes two new
1177
- `Ablo()` options to opt back in:
1178
- - `logLevel: 'debug' | 'info' | 'warn' | 'error' | 'silent'` — `'info'` surfaces
1179
- coordination and connection events without the per-model registration noise;
1180
- `'debug'` is everything. Precedence: explicit `logLevel` → `debug: true` →
1181
- `ABLO_LOG_LEVEL` env → default `warn`. Supplying your own `logger` bypasses both.
1182
- - `debug: boolean` — shorthand for `logLevel: 'debug'`.
1183
-
1184
- Coordination is now traceable at `info`: claims that are **rejected** or **lost**
1185
- (preempted/expired), and your position **advancing in a claim queue**, each log
1186
- once per change with a readable target (`documents:abc.title`) — quiet lowercase
1187
- lines, no shouty tags.
1188
-
1189
- **New: canonical wire-egress contract export.** `errorEnvelope`, `statusForType`,
1190
- and the `ErrorEnvelope` type are now exported from the package root. Server
1191
- consumers (e.g. a self-hosted sync server) can assert against the one source of
1192
- truth for the error-envelope shape and the `AbloError`-subclass→HTTP-status
1193
- table instead of keeping a copy that silently drifts.
45
+ The public surface now provides:
1194
46
 
1195
- **Structured CLI error rendering.** CLI failures render as a titled block with a
1196
- reason code and per-code remediation (`--verbose` for the stack) instead of a
1197
- console wall-of-text; `AbloError.toString()` produces a leak-proof one-liner.
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.
1198
54
 
1199
- **`ABLO_API_KEY` resolution + sandbox key scopes.** The key is now resolved from
1200
- `.env.local` / `.env` (not just the process env), and sandbox keys are granted
1201
- `schema:push` by default so `ablo push` works out of the box in a fresh sandbox.
55
+ Authoritative reads use `model.get({ id })`; reactive snapshots use
56
+ `model.local.get(id)`.
1202
57
 
1203
- ## 0.17.0
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.
1204
63
 
1205
- ### Minor Changes
1206
-
1207
- - **Bring-your-own database is now one model.** Ablo connects to your Postgres and
1208
- never operates it. There used to be two confusing BYO paths, and the
1209
- connection-string one would create roles, force row-level security, transfer
1210
- table ownership, and push you to run `ablo migrate` before anything worked. That
1211
- cascade is gone. Ablo now follows the shape every serious "sync over your own
1212
- Postgres" engine uses (ElectricSQL, PowerSync, Zero): it reads your database via
1213
- Postgres logical replication and never runs DDL, creates roles, forces RLS, or
1214
- rewrites your `DATABASE_URL`. You own your schema; Ablo reads it.
1215
- - **New: `ablo connect`.** One command prints the exact, copy-pasteable setup for
1216
- your own Postgres — enable `wal_level=logical`, create the `ablo_publication`
1217
- publication and a least-privilege `ablo_replicator` role — and
1218
- `ablo connect --check` validates readiness (wal level, publication, replication
1219
- grant, replica identity). This is the single supported way to connect a real
1220
- database.
1221
- - **`ablo migrate` left the happy path.** It no longer creates roles, transfers
1222
- ownership, or rewrites your connection string, and `ablo dev` no longer attempts
1223
- a scoped-role creation on every watch loop. `migrate` is now an optional escape
1224
- hatch for generating starter DDL (`--dry-run` prints the SQL).
1225
- - **Clearer failures.** `ablo push` permission errors lead with the server's actual
1226
- reason code and per-code remediation instead of a generic "needs `schema:push`
1227
- scope," and the schema-conflict message names which environment/version a prior
1228
- push came from and when.
1229
- - **Logical-replication runtime is in Preview.** The setup (`ablo connect`) and the
1230
- connection model are live; the server-side WAL consumer that streams your changes
1231
- is implemented and journey-tested but not yet generally available.
1232
-
1233
- The previous connection-string-operate and adapter/outbox modes are demoted to a
1234
- clearly-labeled **Legacy / not recommended** section — they still work, but new
1235
- integrations should use logical replication.
1236
-
1237
- ## 0.16.3
1238
-
1239
- ### Patch Changes
1240
-
1241
- - **Docs.** The bundled SDK docs are now the single source for the documentation
1242
- site, and several pages were expanded or corrected:
1243
- - The sessions/identity model is reframed around **projects** — push one schema
1244
- to a project, mint an `ek_` per user (your users need no Ablo account), and
1245
- all of them commit to that one schema. Per-customer org isolation
1246
- (`schemaProject`) is presented as the add-on it is, not the default.
1247
- - The declarative `conflict` schema axis (Axis 3) is now documented.
1248
- - The agent docs were corrected to the current claim vocabulary
1249
- (`reason`/`queue`, not the pre-0.12.0 `action`/`wait`).
1250
-
1251
- No code changes.
1252
-
1253
- ## 0.16.2
1254
-
1255
- ### Patch Changes
1256
-
1257
- - **`mintUserSessionKey`: name the shared-schema binding around the project.** The
1258
- two flat options added in 0.16.0 (`schemaOwnerOrgId` + `schemaProjectId`) are
1259
- replaced by one project-centric option — `schemaProject: { organizationId, projectId }` —
1260
- naming "the project that owns the schema" as a single concept. The wire format
1261
- is unchanged (the SDK still sends the same keys), so no server redeploy is
1262
- needed. Released as a patch: the replaced options shipped in 0.16.0 and have no
1263
- external consumers yet.
1264
-
1265
- ```ts
1266
- // before
1267
- mintUserSessionKey({ organizationId, schemaOwnerOrgId, schemaProjectId, ... });
1268
- // after
1269
- mintUserSessionKey({
1270
- organizationId, // data org
1271
- schemaProject: { organizationId, projectId }, // the project that owns the schema
1272
- ...
1273
- });
1274
- ```
1275
-
1276
- ## 0.16.1
1277
-
1278
- ### Patch Changes
1279
-
1280
- - **Fix `ablo login` against the standalone auth server.** The device flow now
1281
- targets two origins instead of one: the RFC 8628 device endpoints
1282
- (`/api/auth/device/*`) go to the identity server (`auth.abloatai.com`, override
1283
- `ABLO_AUTH_URL`), while the human approval page (`/cli`), sign-up, and the
1284
- key-handoff route (`/api/cli/provision-key`) go to the dashboard host
1285
- (`www.abloatai.com`, new override `ABLO_DASHBOARD_URL`). Previously every step
1286
- ran against `www`, where the device endpoints no longer resolve —
1287
- producing "Couldn't start login… Is the dashboard reachable?". The CLI now also
1288
- builds the approval URL itself rather than trusting the server's
1289
- `verification_uri`, which (being a relative `/cli`) resolved against the auth
1290
- server's origin to a 404.
1291
-
1292
- ## 0.16.0
1293
-
1294
- ### Minor Changes
1295
-
1296
- - **Axis 3 — declare write-conflict behaviour in the schema (new).** A model can now
1297
- state what happens when a commit collides with a foreign claim or a stale snapshot —
1298
- per committer kind (`user` / `agent` / `system`) — right next to its fields, using the
1299
- same `overwrite | reject | notify` vocabulary as the `onStale` write guard. It is a
1300
- third axis, orthogonal to `policy` (read access) and `groups` (delta routing).
1301
- - **`conflict` on `model()`** — a plain, serializable disposition map. Pure data, so it
1302
- round-trips through the schema registry to the server; the generic engine interprets it
1303
- at the commit chokepoint (no per-model logic in the engine).
1304
-
1305
- ```ts
1306
- // "a human's edit always wins (never blocked); an agent yields"
1307
- conflict: { user: 'overwrite', agent: 'reject' }
1308
- ```
1309
-
1310
- - **Composable authoring helpers (new, from `@abloatai/ablo/schema`)** — disposition
1311
- functions plus a `cn`/`cx`-style combinator, so conflict policy reads like the rest of
1312
- the DSL (`relation.belongsTo()`) and like modern config (`plugins: [admin(), …]`):
1313
-
1314
- ```ts
1315
- import { coordination, humansOverwrite, agentsReject } from '@abloatai/ablo/schema';
1316
-
1317
- conflict: coordination(humansOverwrite(), agentsReject());
1318
- // → { user: 'overwrite', agent: 'reject' }
1319
- ```
1320
-
1321
- Exports: `coordination`, `humansOverwrite` / `humansReject` / `humansNotify`,
1322
- `agentsOverwrite` / `agentsReject` / `agentsNotify`,
1323
- `systemOverwrite` / `systemReject` / `systemNotify`, and the `ConflictRule` type.
1324
-
1325
- - An omitted committer kind falls through to the engine default (reject; honor
1326
- `onStale: 'notify'`), so this is fully additive — existing schemas are unchanged.
1327
- New public types `ConflictAxis` (also `Ablo.Conflict.Axis`) and the
1328
- `interpretConflictAxis` interpreter are exported for custom policy composition.
1329
-
1330
- - **First-party shared schema for ephemeral keys (new).** `mintUserSessionKey` now accepts
1331
- `schemaProjectId` + `schemaOwnerOrgId`, binding the minted `ek_` to a schema owner-org +
1332
- project so **schema** resolves org-independently (one schema serves all of an integrator's
1333
- end-user orgs) while **data** stays scoped to `organizationId`. Requires the `sk_` to carry
1334
- `ephemeral:mint-any-org`; omit both for the existing per-org (BYO) behaviour.
1335
-
1336
- ## 0.15.1
1337
-
1338
- ### Patch Changes
1339
-
1340
- - Loud 0-row writes: surface unmatched UPDATE/DELETE ids and add `AbloNotFoundError`
1341
-
1342
- A commit now reports the ids of any UPDATE/DELETE that matched zero rows on
1343
- `CommitReceipt.missingIds`, and the new exported `AbloNotFoundError` lets typed
1344
- write wrappers throw instead of silently treating a missed write as success.
1345
- Additive and back-compatible (the field is omitted when nothing missed). This
1346
- unblocks the slides-sdk name-addressing / own-your-id work, which relies on a
1347
- loud failure when a stale id is written.
1348
-
1349
- ## 0.15.0
1350
-
1351
- ### Minor Changes
1352
-
1353
- - **Notify-instead-of-abort: non-coercive conflict handling + read-set (the "did anything I looked at change?" layer).**
1354
-
1355
- 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`.
1356
-
1357
- **`onStale` redesigned — Stripe-aligned values (BREAKING).**
1358
-
1359
- The mode set is now `'reject' | 'overwrite' | 'notify'`. Each value names its outcome:
1360
- - **`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.
1361
- - **`overwrite`** (was `force`) — blind last-writer-wins, no signal.
1362
- - **`reject`** (default, unchanged) — throws `AbloStaleContextError`.
1363
-
1364
- Migration:
1365
- - `onStale: 'force'` → `onStale: 'overwrite'`.
1366
- - `onStale: 'flag'` / `onStale: 'merge'` → `onStale: 'notify'` (both removed; `notify` is the single hold-and-surface mode).
1367
-
1368
- **`StaleNotification` — the new advisory signal.** New public type + `staleNotificationSchema`:
1369
- `{ object: 'stale_notification', model, id, readAt, observedSyncId, conflictingFields, currentValues, writtenBy, group? }`. Delivered two ways:
1370
- - on the receipt — `CommitReceipt.notifications` (and `CommitResult.notifications`);
1371
- - on a new SDK event — **`conflict:notified`** `{ clientTxId, notifications }` (mirrors `reconciliation:needed` / `sync:rollback`).
1372
-
1373
- **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:
1374
- - **Row** — `{ model, id, readAt, fields? }`: did this row (optionally these fields) change?
1375
- - **Group** — `{ group, readAt }`: did anything in this sync group (`deck:abc`, `org:X`) change? — the same unit a participant watches and claims.
1376
-
1377
- 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.
1378
-
1379
- **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.
1380
-
1381
- - **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.
1382
- - **`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`.
1383
- - Server side: a connector registry + `/v1/source/listen` upgrade route bridge requests down / responses up, teed into `SourceClient` through the storage resolver.
1384
- - **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.
1385
- - Opt-in per source via `reverse_channel_prod` (migration `20260622150000`); gated in `authorizeUpgrade`.
1386
-
1387
- ## 0.14.0
1388
-
1389
- ### Minor Changes
1390
-
1391
- - Claim API consistency + coordination docs
1392
- - **React:** document `useWatch` (scoped presence + read-interest, with `claim`/`hydrate`/`paused` options) and `usePeers` (read-only presence) — previously exported but undocumented.
1393
- - **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`.
1394
- - **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 })`).
1395
- - **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`.
1396
-
1397
- ## 0.13.0
1398
-
1399
- ### Minor Changes
1400
-
1401
- - Schema authoring: split model routing into two orthogonal axes — `policy` (row access) and `groups` (sync-group routing).
1402
-
1403
- **Breaking (schema authoring).** The flat, collision-prone model options are replaced by two namespaced ones:
1404
- - **`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:
1405
- - `{ by: 'column' }` — row-local tenancy column (the default when omitted; column name still overridable).
1406
- - `{ 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`).
1407
- - Type `TenancyInput` is renamed `PolicyInput`; `policyInputSchema` / `resolvePolicy` are now exported.
1408
- - **`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`:
1409
- - `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.
1410
- - `grants` — a membership edge granting an identity access to a scope root.
1411
- - `roles` (was `entityRoles`) — explicit non-relational record→group roles; accepts one role or an array.
1412
- - `groupsInputSchema` / `GroupsInput` are now exported.
1413
-
1414
- **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.
1415
-
1416
- ## 0.12.0
1417
-
1418
- ### Minor Changes
1419
-
1420
- - Canonicalize the claim API to one vocabulary, plus DX fixes (breaking).
1421
- - BREAKING: claim phase field `action` → `reason` on every claim surface
1422
- (`Claim`, `ClaimHandle`, `ClaimCreateOptions`, `ModelClaim`, ...). The wire
1423
- is unchanged (still `action`, healed on read) — no server redeploy needed.
1424
- - BREAKING: claim contention flag `wait` → `queue` (one word everywhere).
1425
- - BREAKING: React hook `useParticipant` → `useWatch` (aligns with `ablo.<model>.watch`).
1426
- - `ClaimDeclaration.ttlSeconds` is now `number` (was a `Duration`).
1427
- - Docs: `retrieve` HTTP envelope (`.data`/`.stamp`) called out; `syncGroups`
1428
- reworded (provisional, not deprecated); `orgScoped` cross-tenant security
1429
- warning; React error strings point at `<AbloProvider>`.
1430
-
1431
- ## 0.11.2
1432
-
1433
- ### Patch Changes
1434
-
1435
- - a35d935: Fix stream-recorded undo capturing the wrong "before" value for updates. A second
1436
- update to the same field before the first sync-ack re-captured the original
1437
- pre-session value (first-old-wins + clear-only-on-ack), so undo of a quick second
1438
- edit jumped all the way back instead of one step. The queue now re-baselines a
1439
- field's tracked `.old` once its before-image is frozen into the committed
1440
- transaction.
1441
-
1442
- Also close the create/update undo asymmetry: an update whose written key had no
1443
- in-place mutation produced an empty `previousData`, which made the inverse
1444
- un-revertible (a create's `delete` inverse never is). Before-image capture now
1445
- falls back to the last loaded/acked snapshot.
1446
-
1447
- Internally, the two undo paths (stream-recorded and manual `RecordingTransaction`)
1448
- now share one before-image implementation via `Model.capturePreviousValues` /
1449
- `Model.consumeModifiedFields`, so they can no longer drift.
1450
-
1451
- - One-correct-way consolidation (breaking; no external consumers yet, so released as a patch):
1452
- - Credentials collapse to a single `apiKey` — a string, or a `() => Promise<string | null>` that
1453
- fetches a per-user token. Removed `getToken` / `authEndpoint` / public `authToken`.
1454
- - `ablo.<model>.watch(ids, { ttl })` replaces the top-level `ablo.participants.join({ scope })` —
1455
- model-scoped read-interest + presence (WebSocket only).
1456
- - Read claim-gating is `ifClaimed: 'return' | 'fail'` (removed `'wait'`); waiting is the claim
1457
- primitive's job (`ablo.<model>.claim`).
1458
- - The stateless client is `Ablo({ transport: 'http' })`; `createAbloHttpClient` is no longer a
1459
- public export (the factory uses it internally).
1460
- - Read-option types renamed: `ServerReadOptions` (server `retrieve`/`list`) and `LocalReadOptions`
1461
- (local `get`/`getAll`).
1462
- - `defineSchema` throws a clear error on a reserved-field collision; the MCP/docs API surface is
1463
- now compile-time bound to the real exported types (can't drift).
1464
-
1465
- ## 0.11.1
1466
-
1467
- ### Patch Changes
1468
-
1469
- - 7f91f6e: DX hardening from a real onboarding session — onboarding, CLI, coordination, types, and docs.
1470
-
1471
- **Client behavior**
1472
- - `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.
1473
- - 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.
1474
-
1475
- **CLI**
1476
- - `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`).
1477
- - `ablo <command> --help` / `-h` now prints usage instead of erroring with "unknown flag", and `migrate` is listed in the top-level help.
1478
- - `ablo dev --no-watch` now exits after one push instead of watching forever.
1479
-
1480
- **Types**
1481
- - 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.
1482
- - `model_claim_not_configured` message clarified: claiming needs no per-model schema configuration; every model is claimable through the standard client.
1483
-
1484
- **Docs**
1485
- - 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'`).
1486
-
1487
- - 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'`.
1488
-
1489
- ## 0.11.0
1490
-
1491
- ### Minor Changes
1492
-
1493
- - Canonical `claim` vocabulary, sync-group area-of-interest, and richer claim-rejection errors.
1494
- - **`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.
1495
- - **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.
1496
- - **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.
1497
- - **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.
1498
-
1499
- ## 0.10.1
1500
-
1501
- ### Patch Changes
1502
-
1503
- - 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.
1504
-
1505
- ## 0.10.0
1506
-
1507
- ### Minor Changes
1508
-
1509
- - Rename environment enum values to `production` and `sandbox` while preserving the existing `*_live_`/`*_test_` key prefix format.
1510
-
1511
- ### Patch Changes
1512
-
1513
- - Stateless HTTP transport for server-side actors, and a canonical environment vocabulary.
1514
- - **`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.
1515
- - **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).
1516
- - **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.
1517
- - **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`.
1518
-
1519
- ## 0.9.15
1520
-
1521
- ### Patch Changes
1522
-
1523
- - 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.
1524
-
1525
- ## 0.9.14
1526
-
1527
- ### Patch Changes
1528
-
1529
- - README: replace the `schema -> ablo.<model>...` pseudo-diagram with a real typed snippet (`create`/`retrieve`/`update`/`claim`), and tidy the Get-started line.
1530
-
1531
- ## 0.9.13
1532
-
1533
- ### Patch Changes
1534
-
1535
- - 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.
1536
-
1537
- ## 0.9.12
1538
-
1539
- ### Patch Changes
1540
-
1541
- - 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.
1542
-
1543
- ## 0.9.11
1544
-
1545
- ### Patch Changes
1546
-
1547
- - `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.
1548
- - 3024593: Fix `sessions.create({ user })` 403 — user sessions now mint via the sk\_-gated ephemeral-key door
1549
- - `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).
1550
- - Control-plane calls always present your original `sk_`, never the client's exchanged sync credential.
1551
- - `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).
1552
- - New: `ablo.organizationId` (resolved after `ready()`), `ablo status --json`, typed sync-group inputs (`SyncGroupInput` + `invalid_sync_group` rejection for malformed groups).
1553
-
1554
- ## 0.9.10
1555
-
1556
- ### Patch Changes
1557
-
1558
- - README: add a centered brand header (Ablo banner, tagline, doc nav links, and status badges).
1559
-
1560
- ## 0.9.9
1561
-
1562
- ### Patch Changes
1563
-
1564
- - Docs: version history & migration guide refinements plus changelog, audit, and link fixes.
1565
-
1566
- ## 0.9.8
1567
-
1568
- ### Patch Changes
1569
-
1570
- - 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.
1571
-
1572
- ## 0.9.7
1573
-
1574
- ### Patch Changes
1575
-
1576
- - Docs: fix the `commits.create` operation shape to the public `{ action, model, data }` form.
1577
-
1578
- ## 0.9.6
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.
1579
68
 
1580
- ### Patch Changes
1581
-
1582
- - 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.
1583
-
1584
- ## 0.9.5
1585
-
1586
- ### Patch Changes
1587
-
1588
- - 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.
1589
-
1590
- ## 0.9.4
1591
-
1592
- ### Patch Changes
1593
-
1594
- - 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).
1595
-
1596
- ## 0.9.3
1597
-
1598
- ### Patch Changes
1599
-
1600
- - Onboarding: quickstart leads with your-own-database (Drizzle Data Source), drop Ablo-managed mode, add `ablo push` step; context7 library-claim config.
1601
-
1602
- ## 0.9.2
1603
-
1604
- ### Patch Changes
1605
-
1606
- - Developer-onboarding overhaul so an LLM or a person gets a working integration on the first try.
1607
- - **`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.
1608
- - **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.
1609
- - **`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."
1610
- - **`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`.
1611
- - Docs say Node 24+, and the `drizzle-orm` peer floor is `>=0.44`.
1612
-
1613
- - a88747a: Remove the `turn` primitive and the agent-work `tasks` resource from the client surface — the SDK is now purely `ablo.<model>` + `claim`.
1614
-
1615
- **Breaking**
1616
- - `engine.beginTurn()`, the `Turn` handle interface, and the `Ablo.Turn` type are removed. `AbloApi.beginTurn` and the HTTP client's `beginTurn` are gone too.
1617
- - `CommitCreateOptions.causedByTaskId` is removed. (Lineage is no longer stamped from the client.)
1618
- - 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.
1619
- - 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.
1620
-
1621
- **Why**
1622
-
1623
- `turn`/`agent_tasks` was a second coordination-and-attribution mechanism living alongside `claim`. It is redundant on the client:
1624
- - `claim` already serializes writers **and** carries the causal link — its `intent` id rides on every guarded write.
1625
- - The server stamps `actor` / `onBehalfOf` / `capabilityId` onto each delta from the auth context.
1626
- - Per-run token/cost is recorded in Langfuse, not the `agent_tasks` table.
1627
-
1628
- 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.
1629
-
1630
- **Migration**
1631
-
1632
- 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`.
1633
-
1634
- 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).
1635
-
1636
- ## 0.9.1
1637
-
1638
- ### Patch Changes
1639
-
1640
- - 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`.
1641
-
1642
- ## 0.9.0
1643
-
1644
- A single options object for every model verb, and a disposable `claim` handle.
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.
1645
72
 
1646
- ### Breaking Changes
73
+ - f60ed16: Improve confirmed commit throughput and live-client materialization without
74
+ weakening atomic writes, ordered observation, audit delivery, or replay.
1647
75
 
1648
- - **One options object per verb.** `create`, `update`, `delete`, and the async
1649
- server `retrieve` each take a single options object instead of positional
1650
- arguments, so the id, the data, and every modifier live as named siblings:
1651
- `create({ data, id? })`, `update({ id, data, ...options })`,
1652
- `delete({ id, ...options })`, `retrieve({ id, ...options })`. Reactive local
1653
- reads stay on `get(id)` (synchronous) —
1654
- `useAblo((ablo) => ablo.tasks.get(id))`.
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.
1655
81
 
1656
- ```diff
1657
- - await ablo.tasks.update(id, { status: 'done' }, { wait: 'confirmed' })
1658
- + await ablo.tasks.update({ id, data: { status: 'done' }, wait: 'confirmed' })
1659
-
1660
- - await ablo.tasks.retrieve(id)
1661
- + await ablo.tasks.retrieve({ id })
1662
-
1663
- - useAblo((ablo) => ablo.tasks.retrieve(id)) ?? serverTask
1664
- + useAblo((ablo) => ablo.tasks.get(id)) ?? serverTask
1665
- ```
1666
-
1667
- - **`claim` returns a disposable handle** instead of taking a callback. The
1668
- handle exposes the fresh row on `.data` and is released on scope exit
1669
- (`await using`) or explicitly via `.release()`. `claim.state`, `claim.queue`,
1670
- `claim.release`, and `claim.reorder` also take the options object.
1671
-
1672
- ```diff
1673
- - await ablo.tasks.claim(id, async (task) => {
1674
- - await ablo.tasks.update(task.id, { status: 'in_review' })
1675
- - })
1676
- + await using claim = await ablo.tasks.claim({ id })
1677
- + const task = claim.data
1678
- + await ablo.tasks.update({ id: task.id, data: { status: 'in_review' } })
1679
- ```
1680
-
1681
- ## 0.8.0
1682
-
1683
- A callable `claim` coordination namespace and bring-your-own-database support
1684
- via a new `databaseUrl` option.
1685
-
1686
- ### Minor Changes
1687
-
1688
- - **Callable `claim` coordination namespace.** Taking a claim and inspecting its
1689
- state now live under one accessor: `claim(id, work)` acquires a claim and runs
1690
- `work` while it's held, and `claim.state(id)`, `claim.queue(id)`,
1691
- `claim.release(id)`, and `claim.reorder(id, order)` cover the surrounding
1692
- lifecycle. The README leads with the problem (who is allowed to act, and in
1693
- what order) and the Quick Start now demonstrates `claim` directly.
1694
-
1695
- - **Bring-your-own-database via `databaseUrl`.** Point a project at your own
1696
- Postgres with `Ablo({ schema, apiKey, databaseUrl })`. Ablo writes synced rows
1697
- back into your database, so your data stays canonical. Server-side only;
1698
- defaults to `process.env.DATABASE_URL`. See the data-sources guide for setup
1699
- and role requirements.
1700
-
1701
- ### Breaking
1702
-
1703
- - The flat coordination methods `claimState`, `queue`, `release`, and `reorder`
1704
- are removed in favor of the `claim` namespace above.
1705
-
1706
- ```diff
1707
- - await ablo.task.claimState(id)
1708
- - await ablo.task.release(id)
1709
- + await ablo.task.claim.state(id)
1710
- + await ablo.task.claim.release(id)
1711
- ```
1712
-
1713
- ## 0.7.0
1714
-
1715
- ### Minor Changes
1716
-
1717
- - Structured error contract, schema/migration engine, and a full `ablo` CLI.
1718
- - **Structured error contract across HTTP + WS planes.** A closed, canonical
1719
- error-code registry is now the `code` tier of a Stripe-style error model. A
1720
- single HTTP egress funnel converts every throw to a canonical
1721
- `{ type, code, message, doc_url, request_id, ...details }` envelope; the WS
1722
- plane narrows mutation/claim error codes to the same union.
1723
- - **Versioned contract + drift guard.** `ERROR_CONTRACT_VERSION` (date-based)
1724
- ships in `errors.json` and on the `Ablo-Version` response header, so consumers
1725
- detect contract changes without diffing docs. Generated `errors.mdx` /
1726
- `errors.json` plus a CI drift guard keep the docs, OpenAPI spec, and SDK from
1727
- silently diverging from the registry.
1728
- - **Always-on request correlation.** Every response carries a `req_…` request id
1729
- (honoring an inbound `x-request-id`), stamped into the envelope's `request_id`.
1730
- - **OpenAPI parity.** The stale `{ error, reason }` schema is replaced by the
1731
- canonical envelope plus a generated `ErrorCode` enum.
1732
-
1733
- CLI + schema:
1734
- - **Schema diff + migration planning engine** (`generateProvisionPlan` /
1735
- `generateMigrationPlan` in `@abloatai/ablo/schema`) — pure diff, classify,
1736
- apply, and constant-value backfill for required-field migrations.
1737
- - **`ablo generate`** — emit TypeScript types from the pushed schema.
1738
- - **Full `ablo` CLI suite**, Stripe-CLI-shaped: `init`, `login` / `logout` /
1739
- `status`, `mode [test|live]`, `dev` (push schema to the test sandbox + watch),
1740
- `logs` (tail your scope's commit activity), and the data-source commands below.
1741
- Authentication is the OAuth 2.0 device flow; `login` provisions and stores a
1742
- test and a live key, and `mode` switches the active one.
1743
- - **Database-URL structure (bring-your-own-database).** The CLI is split by where
1744
- it writes:
1745
- - `ablo pull` / `ablo check` / `ablo migrate` operate on **your own
1746
- `DATABASE_URL`** — `pull` introspects it to emit `defineSchema(...)` from
1747
- existing tables (read-only, like `prisma db pull`), `check` verifies tables
1748
- fit the schema with no DDL, and `migrate` applies DDL to `DATABASE_URL`.
1749
- - `ablo schema push` / `ablo dev` target the **hosted** test/live sandbox; the
1750
- server diffs, migrates, and activates the uploaded schema. `dev` never
1751
- touches live data.
1752
-
1753
- **BREAKING** — removed the legacy React hooks `useQuery` / `useOne` / `useMutate`
1754
- / `useReader`. Use `useAblo()` + `ablo.<model>.*` instead. The `MutateActions`,
1755
- `ReaderActions`, and `ReaderFindOptions` types are still re-exported for callers
1756
- that referenced them.
1757
-
1758
- ## 0.6.0
1759
-
1760
- ### Minor Changes
1761
-
1762
- - 0f663e7: Coordination surface: fair queue, reactive wait-line, and lease renewal.
1763
- - **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`.
1764
- - **`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)`.
1765
- - **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.
1766
- - **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).
1767
- - **Reads are never gated by a claim**, including for agents.
1768
- - Intent vocabulary cleanup: a waiting claim is an `Intent` with `status: 'queued'` (`position` carries its place in line). Removed the unbuilt `whenFree`.
1769
-
1770
- - **BREAKING — API renames** (apply when upgrading from 0.5.1):
1771
- - Change-listeners renamed to `.onChange(...)`: `ablo.<model>.subscribe(cb)`, `presence.subscribe()`, `intents.subscribe()` → `.onChange(...)`. (`subscribe` is reserved for an upcoming scope-grant verb.)
1772
- - 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`.
1773
-
1774
- ## 0.5.1
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.
1775
86
 
1776
87
  ### Patch Changes
1777
88
 
1778
- - Docs: add a React quick-start (provider + `useAblo`), plain-language rewrite, and a "Set up with Claude Code" section.
1779
-
1780
- ## 0.5.0
1781
-
1782
- ### Minor Changes
1783
-
1784
- - 9154c1b: Rename intent handle methods to a clearer claim vocabulary; add `AbloProvider` `bootstrapMode`.
1785
-
1786
- BREAKING — on the model intent handle (`ablo.<model>.intent(id)`):
1787
- `acquire`→`claim`, `acquireOrAwait`→`claimOrWait`, `settled`→`whenFree`,
1788
- `release`→`finish`, `revoke`→`cancel`. The lower-level `IntentHandle` /
1789
- `IntentLeaseHandle` (`ablo.intents.*`) are unchanged.
1790
-
1791
- Also: `AbloProvider` gains a `bootstrapMode` prop (`'full' | 'none'`) to skip the
1792
- baseline pull on read-light pages; `StaleContextConflict` gains an optional
1793
- `conflictingFields`; README + JSDoc clarity pass and a new HTTP API section.
1794
-
1795
- ## 0.4.0
1796
-
1797
- ### Minor Changes
1798
-
1799
- - Per-entity coordination intents on the model accessor.
1800
-
1801
- Coordinate writes to an entity through the same accessor you read it with —
1802
- `ablo.<model>.intent(id)`, returning a `ModelIntentHandle`. Intent state is one
1803
- self-describing object (`{ object: 'intent', id, status, target, action, heldBy,
1804
- participantKind, createdAt?, expiresAt? }`) with a single lifecycle:
1805
- `status: 'active' | 'committed' | 'expired' | 'canceled'`. An `active` intent is
1806
- the lock.
1807
-
1808
- ### Added
1809
- - `ablo.<model>.intent(id)` → `ModelIntentHandle<T>`, beside `create` / `update`
1810
- / `retrieve` / `load` on every model.
1811
- - Read side (any participant, synchronous + reactive): `current` (the holder's
1812
- intent, or `null`), `status` (`'idle'` when free), `settled()`.
1813
- - Write side (the holder): `acquire()`, `acquireOrAwait()`, lease-guarded
1814
- `update()`, `release()`, `revoke()`.
1815
- - `AsyncDisposable`: `await using lock = ablo.<model>.intent(id)` auto-releases
1816
- on scope exit.
1817
- - `acquireOrAwait()` — serialize-on-contention: take the lease, or wait out the
1818
- current holder, re-read the changed row, then take it. The caller never branches
1819
- on who holds the target — it just gets the target safely. Bind it to an agent's
1820
- write-tool boundary so agents never reason about coordination.
1821
- - New exports: `ModelIntentHandle`, `ModelIntentAcquireOptions`.
1822
-
1823
- ### Changed
1824
- - `acquire()` is fire-and-forget over the socket — it does not throw on conflict.
1825
- Resolve contention with `acquireOrAwait()` (wait) or read `current` for a
1826
- reactive "who's editing" badge, rather than catching a rejection.
1827
-
1828
- ### Deprecated
1829
- - Participant-level `intents.claim()` / `onRejected()` and the `intent_rejected`
1830
- wire frame still work but are superseded by the per-model handle. Their removal
1831
- is a future breaking change.
1832
-
1833
- ## Unreleased
1834
-
1835
- Schema-driven identity sync-group composition, plus a terser capability surface.
1836
-
1837
- 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.
1838
-
1839
- 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.
1840
-
1841
- ### Added
1842
-
1843
- - `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.
1844
- - `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.
1845
- - `Schema.identityRoles: readonly IdentityRole[]` — the registered list, accessible on every `defineSchema(...)` result.
1846
- - New exported types: `IdentityRole`, `IdentityContext`.
1847
-
1848
- ### Breaking
1849
-
1850
- - `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.
1851
-
1852
- ```ts
1853
- // Before
1854
- await api.capabilities.create({
1855
- allowedSyncGroups: ['org:acme'],
1856
- allowedOperations: ['tasks.update'],
1857
- lease: '10m',
1858
- });
1859
-
1860
- // After
1861
- await api.capabilities.create({
1862
- syncGroups: ['org:acme'],
1863
- operations: ['tasks.update'],
1864
- lease: '10m',
1865
- });
1866
- ```
1867
-
1868
- ### Changed
1869
-
1870
- - `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.
1871
-
1872
- ## 0.3.0 (2026-04-22)
1873
-
1874
- 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.
1875
-
1876
- 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.
1877
-
1878
- ### Added
1879
-
1880
- - `<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.
1881
- - `<SyncGroupProvider id="matter:...">` + `useSyncGroup()` — per-entity scope context.
1882
- - `<ClientSideSuspense fallback={...}>` — gate renders until the engine reports `connected`. Phase-1 non-Suspense; phase-2 upgrades to real Suspense.
1883
- - `useSyncStatus()` rewritten as a tagged union: `{ name: 'initial' | 'connecting' | 'connected' | 'reconnecting' | 'disconnected' | 'needs-auth', ... }`. Impossible states are unrepresentable.
1884
- - `useCurrentUserId()` — returns the `userId` prop. Replaces downstream consumers' defineProperty hacks on the store.
1885
- - `useErrorListener(cb)` — imperative error callback (Sentry/Datadog).
1886
- - `useSync<R>()` and `useSyncStore<T>()` accept generic parameters so consumers can widen to their concrete schema types without `as unknown` casts at call sites.
1887
- - `BaseSyncedStore.purge()` / `SyncEngine.purge()` — disconnect + wipe every `ablo_*` / `ablo-*` IndexedDB. Called automatically on session expiry.
1888
- - `SyncEngine.onSessionError(listener)` — subscribe to session-error events. Multiple subscribers supported.
1889
- - 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.
1890
-
1891
- ### Breaking (continued)
1892
-
1893
- - 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.
1894
-
1895
- ### Deprecated (vestigial — removal in v0.4)
1896
-
1897
- - `SyncEngineConfig.modelCreatePriority`, `defaultCreatePriority`, `defaultNonCreatePriority` — never read at runtime.
1898
- - `SyncEngineConfig.batchableModels` — never read at runtime.
1899
- - `SyncEngineConfig.dedicatedDeleteModels` — never read at runtime.
1900
- - `SyncEngineConfig.preserveCaseModels` — never read at runtime.
1901
- - `SyncEngineConfig.essentialFields` — used only in debug logging, no behavioral effect.
1902
- - `SyncEngineConfig.classNameFallbackMap` — dead path; `ModelRegistry.registerModelsFromSchema` registers by constructor identity, bypassing the class-name fallback entirely.
1903
-
1904
- ### Breaking
1905
-
1906
- - Removed `<SyncProvider>` — folded into `<AbloProvider>`. Migrate by swapping the provider and passing `userId`/`organizationId`/`url` instead of a pre-constructed store.
1907
- - 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.
1908
- - Removed `withSync` (no-op alias of `observer`). Import `observer` from `mobx-react-lite` directly if needed.
1909
- - Removed `useSyncContext` from the public surface (never used outside the SDK's test helpers).
1910
- - `useSyncStatus()` return shape changed from six booleans to a tagged union. Migration: `const { isReady } = useSyncStatus()` → `const status = useSyncStatus(); const isReady = status.name === 'connected'`.
1911
- - `SyncStoreContract` gained six sync-status getters and a `syncStatus` field. Third-party classes implementing the contract must add these (additive for callers).
1912
-
1913
- ### Migration
1914
-
1915
- ```tsx
1916
- // Before (0.2.x)
1917
- const { AbloProvider, useAblo, useParticipant } = createAbloContext<typeof schema>();
1918
-
1919
- function Root() {
1920
- const sync = createSyncEngine({ url, schema, user });
1921
- const ablo = new Ablo({ schema });
1922
- return (
1923
- <SyncProvider store={sync._store} organizationId={orgId}>
1924
- <AbloProvider ablo={ablo}>
1925
- <App />
1926
- </AbloProvider>
1927
- </SyncProvider>
1928
- );
1929
- }
1930
-
1931
- // After (0.3.0)
1932
- function Root() {
1933
- return (
1934
- <AbloProvider
1935
- schema={schema}
1936
- url={url}
1937
- userId={userId}
1938
- organizationId={orgId}
1939
- preventUnsavedChanges
1940
- onSessionExpired={() => router.replace('/signin')}
1941
- >
1942
- <ClientSideSuspense fallback={<Skeleton />}>
1943
- <App />
1944
- </ClientSideSuspense>
1945
- </AbloProvider>
1946
- );
1947
- }
1948
- ```
1949
-
1950
- No breaking change to `useQuery` / `useOne` / `useMutate` / `useReader` / `useMutators` / `useUndoScope` / `usePresence` / `useIntent` — call sites remain source-compatible.
1951
-
1952
- ## 0.2.1 (2026-04-22)
1953
-
1954
- 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.
1955
-
1956
- ### Fixed
1957
-
1958
- - **`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.
1959
-
1960
- ### Added
1961
-
1962
- - **`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.
1963
- - **`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`.
1964
- - **`SyncStoreContract` surfaces the status getters** so TypeScript autocomplete works from the `useSyncContext()` return value without a cast.
1965
-
1966
- ### Documentation
1967
-
1968
- - **`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).
1969
-
1970
- ### Migration
1971
-
1972
- 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.
1973
-
1974
- ## 0.2.0 (2026-04-21)
1975
-
1976
- Mesh SDK — the canonical agent-multiplayer surface. Locked at this release; further work is consolidation, not expansion.
1977
-
1978
- ### What's frozen
1979
-
1980
- The SDK covers exactly three integration shapes. Each has a canonical example in [`examples/`](./examples/):
1981
-
1982
- 1. **Server agent** — `new Ablo({ schema })` reads `ABLO_API_KEY`, joins and works. ([`examples/server-agent.ts`](./examples/server-agent.ts))
1983
- 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))
1984
- 3. **Sub-agent** — `parent.join(child, opts)` attenuates from the parent's capability. ([`examples/sub-agent.ts`](./examples/sub-agent.ts))
1985
-
1986
- ### Ergonomics (package-wide)
1987
-
1988
- - **`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.
1989
- - **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.
1990
- - **Flat scope form** — `scope: { matters: id }` alongside the array form.
1991
- - **`as` alias** — `{ as: session({...}) }` replaces the security-jargon `onBehalfOf`; both still accepted.
1992
- - **Auto-connect** — `join()` returns a connected participant. `autoConnect: false` to opt out.
1993
- - **Duration strings** — `ttl: '3m'`, `ttlSeconds: '24h'` accepted alongside numbers.
1994
- - **Descriptive generics** — every public type uses `TSchema` / `TAgent` / `ModelName` instead of `S` / `A` / `K`. Zero `unknown` in public types.
1995
-
1996
- ### Coordination primitives
1997
-
1998
- - **Presence verbs** — `participant.presence.editing(target)` / `viewing(target)` / `idle()`. Plus `update({...})` escape hatch for custom actions.
1999
- - **Intent verbs** — `participant.intents.editing(target, opts)` / `writing(target, opts)`. Returns an `IntentHandle` with `Symbol.asyncDispose` so `await using work = ...` auto-revokes.
2000
- - **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).
2001
- - **Async iterables** — `for await (const peers of participant.presence)`, `for await (const openIntents of participant.intents)`, `for await (const delta of participant.deltas)`.
2002
-
2003
- ### Env / config
2004
-
2005
- - `ABLO_API_KEY` — required for server-side use.
2006
- - `baseURL` — optional override for private deployments / local-dev (defaults to `wss://api.abloatai.com`).
2007
- - `organizationId` — **no longer required** in `createMesh`. The API key or session binds the caller to one org; the capability mint response echoes it back.
2008
- - `createMeshFromEnv` — removed. `new Ablo({ schema })` auto-reads env.
2009
-
2010
- ### Test coverage
2011
-
2012
- - 53 mesh unit tests across 8 suites (`__tests__/unit/mesh/`)
2013
- - New E2E test `e2e-browser-capability-token.ts` proves the server-mints / browser-holds flow end-to-end
2014
- - Existing 12 mesh E2E tests (token refresh, watermark, chinese wall, etc.) still pass
2015
-
2016
- ---
2017
-
2018
- ## 0.1.0 (2026-04-10)
2019
-
2020
- Initial release.
2021
-
2022
- ### Features
2023
-
2024
- - **Schema DSL**: Zero-codegen schema definition with full TypeScript inference (`defineSchema`, `field`, `relation`)
2025
- - **React Hooks**: `useModels`, `useModel`, `useMutations`, `withSync` for reactive data binding
2026
- - **Consumer API**: `createSyncEngine()` — one-liner setup that hides all internal wiring
2027
- - **Offline-first**: IndexedDB persistence with automatic offline mutation queue and FK-safe flush
2028
- - **Real-time sync**: WebSocket delta streaming with optimistic updates and rollback
2029
- - **AI Agent SDK**: `SyncAgent` for backend/AI agent participation as first-class sync citizens
2030
- - **Pluggable auth**: `AuthProvider` interface with built-in API key, JWT, and session providers
2031
- - **Security**: IndexedDB cleanup on session expiry and sync group revocation
2032
- - **Testing utilities**: `@abloatai/ablo/testing` subpath with mocks, fixtures, and harness
2033
-
2034
- ### 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
2035
95
 
2036
- - 231 unit/integration/property/contract tests
2037
- - 50 E2E tests against real Go server + PostgreSQL + Redis
2038
- - Property-based testing via fast-check
96
+ Release notes are generated from the repository changesets.