@abloatai/ablo 0.34.0 → 0.35.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 (483) hide show
  1. package/AGENTS.md +4 -1
  2. package/CHANGELOG.md +684 -5
  3. package/README.md +39 -22
  4. package/dist/BaseSyncedStore.d.ts +152 -44
  5. package/dist/BaseSyncedStore.js +300 -184
  6. package/dist/Database.d.ts +9 -24
  7. package/dist/Database.js +37 -22
  8. package/dist/InstanceCache.d.ts +25 -4
  9. package/dist/InstanceCache.js +48 -15
  10. package/dist/LazyReferenceCollection.d.ts +3 -3
  11. package/dist/LazyReferenceCollection.js +4 -4
  12. package/dist/Model.d.ts +6 -6
  13. package/dist/Model.js +10 -10
  14. package/dist/ModelRegistry.d.ts +4 -4
  15. package/dist/ModelRegistry.js +3 -3
  16. package/dist/{SyncEngineContext.d.ts → RuntimeContext.d.ts} +20 -13
  17. package/dist/{SyncEngineContext.js → RuntimeContext.js} +11 -12
  18. package/dist/SyncClient.d.ts +42 -32
  19. package/dist/SyncClient.js +166 -110
  20. package/dist/ai-sdk/coordinatedTool.d.ts +2 -2
  21. package/dist/ai-sdk/coordinatedTool.js +1 -1
  22. package/dist/ai-sdk/coordinationContext.d.ts +2 -2
  23. package/dist/ai-sdk/coordinationContext.js +1 -1
  24. package/dist/ai-sdk/wrap.d.ts +3 -3
  25. package/dist/ai-sdk/wrap.js +2 -2
  26. package/dist/auth/index.d.ts +1 -156
  27. package/dist/auth/index.js +8 -301
  28. package/dist/cli.cjs +3459 -1126
  29. package/dist/client/Ablo.d.ts +42 -287
  30. package/dist/client/Ablo.js +118 -963
  31. package/dist/client/abloClient.d.ts +309 -0
  32. package/dist/client/abloClient.js +13 -0
  33. package/dist/client/clientPrelude.d.ts +52 -0
  34. package/dist/client/clientPrelude.js +60 -0
  35. package/dist/client/consoleLogger.d.ts +2 -2
  36. package/dist/client/coreClient.d.ts +60 -0
  37. package/dist/client/coreClient.js +118 -0
  38. package/dist/client/createInternalComponents.d.ts +4 -4
  39. package/dist/client/createInternalComponents.js +9 -8
  40. package/dist/client/createModelProxy.d.ts +78 -373
  41. package/dist/client/createModelProxy.js +114 -86
  42. package/dist/client/humans.d.ts +48 -0
  43. package/dist/client/humans.js +52 -0
  44. package/dist/client/modelRegistration.d.ts +1 -1
  45. package/dist/client/modelRegistration.js +9 -9
  46. package/dist/client/options.d.ts +73 -17
  47. package/dist/client/reactiveEngine.d.ts +48 -0
  48. package/dist/client/reactiveEngine.js +910 -0
  49. package/dist/client/resourceTypes.d.ts +9 -250
  50. package/dist/client/resourceTypes.js +8 -5
  51. package/dist/client/schemaConfig.d.ts +4 -4
  52. package/dist/client/schemaConfig.js +6 -2
  53. package/dist/client/validateAbloOptions.d.ts +3 -2
  54. package/dist/client/validateAbloOptions.js +1 -1
  55. package/dist/client/wsMutationExecutor.d.ts +3 -3
  56. package/dist/client/wsMutationExecutor.js +3 -3
  57. package/dist/context.d.ts +9 -9
  58. package/dist/context.js +10 -9
  59. package/dist/coordination/ClaimLog.d.ts +26 -0
  60. package/dist/coordination/ClaimLog.js +32 -0
  61. package/dist/coordination/index.d.ts +1 -15
  62. package/dist/coordination/index.js +8 -31
  63. package/dist/core/DatabaseManager.js +1 -1
  64. package/dist/core/QueryView.d.ts +1 -1
  65. package/dist/core/QueryView.js +1 -1
  66. package/dist/core/StoreManager.d.ts +4 -23
  67. package/dist/core/StoreManager.js +5 -55
  68. package/dist/core/index.d.ts +2 -2
  69. package/dist/core/index.js +2 -2
  70. package/dist/core/storeContract.d.ts +2 -2
  71. package/dist/docs/catalog.d.ts +72 -0
  72. package/dist/docs/catalog.js +227 -0
  73. package/dist/docs/index.d.ts +10 -0
  74. package/dist/docs/index.js +10 -0
  75. package/dist/environment.d.ts +1 -40
  76. package/dist/environment.js +8 -37
  77. package/dist/index.d.ts +40 -34
  78. package/dist/index.js +26 -20
  79. package/dist/interfaces/index.d.ts +44 -134
  80. package/dist/keys/index.d.ts +1 -77
  81. package/dist/keys/index.js +8 -190
  82. package/dist/mutators/{RecordingTransaction.d.ts → RecordingMutation.d.ts} +4 -4
  83. package/dist/mutators/{RecordingTransaction.js → RecordingMutation.js} +2 -2
  84. package/dist/mutators/Transaction.d.ts +1 -1
  85. package/dist/mutators/Transaction.js +1 -1
  86. package/dist/mutators/UndoManager.d.ts +6 -6
  87. package/dist/mutators/UndoManager.js +5 -5
  88. package/dist/mutators/defineMutators.d.ts +3 -3
  89. package/dist/mutators/defineMutators.js +1 -1
  90. package/dist/mutators/inverseOp.js +2 -2
  91. package/dist/mutators/mutateActions.d.ts +3 -3
  92. package/dist/mutators/mutateActions.js +1 -1
  93. package/dist/mutators/readerActions.d.ts +1 -1
  94. package/dist/mutators/undoApply.d.ts +1 -1
  95. package/dist/mutators/undoApply.js +1 -1
  96. package/dist/policy/index.d.ts +2 -2
  97. package/dist/policy/index.js +1 -1
  98. package/dist/query/client.d.ts +2 -2
  99. package/dist/query/client.js +4 -4
  100. package/dist/query/types.d.ts +6 -41
  101. package/dist/query/types.js +2 -2
  102. package/dist/react/AbloProvider.d.ts +6 -8
  103. package/dist/react/AbloProvider.js +5 -7
  104. package/dist/react/context.d.ts +1 -1
  105. package/dist/react/context.js +1 -1
  106. package/dist/react/index.d.ts +5 -5
  107. package/dist/react/index.js +3 -3
  108. package/dist/react/internalContext.d.ts +1 -1
  109. package/dist/react/useAblo.d.ts +3 -3
  110. package/dist/react/useAblo.js +1 -1
  111. package/dist/react/useCurrentUserId.js +1 -1
  112. package/dist/react/useErrorListener.js +1 -1
  113. package/dist/react/useMutationFailureListener.d.ts +2 -2
  114. package/dist/react/useMutationFailureListener.js +1 -1
  115. package/dist/react/useMutators.d.ts +3 -3
  116. package/dist/react/useMutators.js +3 -3
  117. package/dist/react/useUndoScope.d.ts +5 -5
  118. package/dist/react/useUndoScope.js +1 -1
  119. package/dist/schema/coordination.d.ts +69 -10
  120. package/dist/schema/coordination.js +86 -9
  121. package/dist/schema/ddl.js +2 -2
  122. package/dist/schema/diff.d.ts +1 -1
  123. package/dist/schema/generate.js +1 -1
  124. package/dist/schema/index.d.ts +10 -10
  125. package/dist/schema/index.js +18 -18
  126. package/dist/schema/queries.d.ts +27 -27
  127. package/dist/schema/queries.js +23 -23
  128. package/dist/schema/select.d.ts +3 -3
  129. package/dist/schema/select.js +3 -3
  130. package/dist/schema/serialize.d.ts +15 -6
  131. package/dist/schema/serialize.js +17 -3
  132. package/dist/schema/sugar.d.ts +6 -7
  133. package/dist/schema/sugar.js +9 -12
  134. package/dist/schema/syncDeltaRow.d.ts +4 -152
  135. package/dist/schema/syncDeltaRow.js +4 -105
  136. package/dist/server/adapter.d.ts +18 -1
  137. package/dist/server/commit.d.ts +10 -16
  138. package/dist/server/index.d.ts +1 -1
  139. package/dist/server/index.js +1 -1
  140. package/dist/server/readConfig.d.ts +1 -1
  141. package/dist/source/adapters/drizzle.d.ts +1 -1
  142. package/dist/source/adapters/drizzle.js +2 -2
  143. package/dist/source/adapters/kysely.d.ts +1 -1
  144. package/dist/source/adapters/kysely.js +1 -1
  145. package/dist/source/adapters/kyselyMutationCore.d.ts +1 -1
  146. package/dist/source/adapters/kyselyMutationCore.js +2 -2
  147. package/dist/source/adapters/memory.js +1 -1
  148. package/dist/source/adapters/prisma.d.ts +8 -3
  149. package/dist/source/adapters/prisma.js +1 -1
  150. package/dist/source/connector.js +1 -1
  151. package/dist/source/connectorProtocol.d.ts +2 -8
  152. package/dist/source/connectorProtocol.js +3 -2
  153. package/dist/source/contract.d.ts +29 -17
  154. package/dist/source/contract.js +27 -22
  155. package/dist/source/factory.d.ts +1 -1
  156. package/dist/source/footprint.d.ts +111 -0
  157. package/dist/source/footprint.js +0 -0
  158. package/dist/source/idempotency.js +2 -2
  159. package/dist/source/index.d.ts +1 -0
  160. package/dist/source/index.js +3 -0
  161. package/dist/source/next.d.ts +1 -1
  162. package/dist/source/signing.d.ts +9 -2
  163. package/dist/source/signing.js +4 -1
  164. package/dist/source/types.d.ts +6 -4
  165. package/dist/source/types.js +1 -1
  166. package/dist/stores/ObjectStore.d.ts +1 -1
  167. package/dist/stores/SyncActionStore.d.ts +1 -1
  168. package/dist/stores/SyncActionStore.js +2 -10
  169. package/dist/stores/syncAction.d.ts +26 -0
  170. package/dist/stores/syncAction.js +16 -0
  171. package/dist/surface.d.ts +3 -3
  172. package/dist/surface.js +6 -4
  173. package/dist/sync/BootstrapFetcher.d.ts +123 -6
  174. package/dist/sync/BootstrapFetcher.js +492 -66
  175. package/dist/sync/ConnectionManager.d.ts +6 -198
  176. package/dist/sync/ConnectionManager.js +6 -677
  177. package/dist/sync/OnDemandLoader.d.ts +2 -2
  178. package/dist/sync/OnDemandLoader.js +60 -21
  179. package/dist/sync/SubscriptionManager.d.ts +13 -2
  180. package/dist/sync/SubscriptionManager.js +23 -5
  181. package/dist/sync/SyncWebSocket.d.ts +27 -510
  182. package/dist/sync/SyncWebSocket.js +76 -954
  183. package/dist/sync/awaitClaimGrant.d.ts +4 -44
  184. package/dist/sync/awaitClaimGrant.js +4 -109
  185. package/dist/sync/commitFrames.d.ts +6 -40
  186. package/dist/sync/commitFrames.js +6 -97
  187. package/dist/sync/contextPorts.d.ts +18 -0
  188. package/dist/sync/contextPorts.js +31 -0
  189. package/dist/sync/createClaimStream.d.ts +5 -49
  190. package/dist/sync/createClaimStream.js +5 -469
  191. package/dist/sync/createPresenceStream.d.ts +26 -4
  192. package/dist/sync/createPresenceStream.js +28 -20
  193. package/dist/sync/createSnapshot.d.ts +2 -2
  194. package/dist/sync/createSnapshot.js +1 -1
  195. package/dist/sync/credentialLifecycle.d.ts +5 -173
  196. package/dist/sync/credentialLifecycle.js +5 -320
  197. package/dist/sync/deltaPipeline.d.ts +1 -1
  198. package/dist/sync/participants.d.ts +5 -4
  199. package/dist/sync/participants.js +29 -22
  200. package/dist/sync/schemaDrift.d.ts +55 -0
  201. package/dist/sync/schemaDrift.js +53 -0
  202. package/dist/sync/schemas.d.ts +21 -32
  203. package/dist/sync/schemas.js +26 -17
  204. package/dist/sync/syncPlan.d.ts +3 -3
  205. package/dist/sync/wsFrameHandlers.d.ts +6 -114
  206. package/dist/sync/wsFrameHandlers.js +6 -392
  207. package/dist/testing/fixtures/bootstrap.d.ts +1 -1
  208. package/dist/testing/fixtures/deltas.d.ts +1 -1
  209. package/dist/testing/fixtures/httpResponses.d.ts +70 -0
  210. package/dist/testing/fixtures/httpResponses.js +90 -0
  211. package/dist/testing/fixtures/models.js +1 -1
  212. package/dist/testing/helpers/wait.js +1 -1
  213. package/dist/testing/mocks/MockMutationExecutor.d.ts +2 -2
  214. package/dist/testing/mocks/MockMutationExecutor.js +8 -14
  215. package/dist/testing/mocks/MockSyncContext.d.ts +11 -11
  216. package/dist/testing/mocks/MockSyncContext.js +10 -9
  217. package/dist/testing/mocks/MockSyncStore.js +1 -1
  218. package/dist/testing/mocks/MockWebSocket.d.ts +2 -2
  219. package/dist/transaction/ablo.d.ts +88 -0
  220. package/dist/transaction/ablo.js +33 -0
  221. package/dist/{client/auth.d.ts → transaction/auth/apiKey.d.ts} +43 -8
  222. package/dist/{client/auth.js → transaction/auth/apiKey.js} +19 -0
  223. package/dist/transaction/auth/bootstrapScope.d.ts +15 -0
  224. package/dist/transaction/auth/bootstrapScope.js +1 -0
  225. package/dist/transaction/auth/capability.d.ts +177 -0
  226. package/dist/transaction/auth/capability.js +199 -0
  227. package/dist/{auth → transaction/auth}/credentialSource.d.ts +8 -1
  228. package/dist/{client → transaction/auth}/identity.d.ts +8 -7
  229. package/dist/{client → transaction/auth}/identity.js +1 -1
  230. package/dist/transaction/auth/index.d.ts +162 -0
  231. package/dist/transaction/auth/index.js +304 -0
  232. package/dist/{auth → transaction/auth}/schemas.d.ts +1 -1
  233. package/dist/{auth → transaction/auth}/schemas.js +13 -13
  234. package/dist/{client → transaction/auth}/sessionMint.d.ts +8 -8
  235. package/dist/{client → transaction/auth}/sessionMint.js +4 -7
  236. package/dist/transaction/coordination/awaitClaimGrant.d.ts +49 -0
  237. package/dist/transaction/coordination/awaitClaimGrant.js +112 -0
  238. package/dist/transaction/coordination/claimMeta.d.ts +49 -0
  239. package/dist/transaction/coordination/claimMeta.js +52 -0
  240. package/dist/transaction/coordination/createClaimStream.d.ts +64 -0
  241. package/dist/transaction/coordination/createClaimStream.js +475 -0
  242. package/dist/transaction/coordination/events.d.ts +74 -0
  243. package/dist/transaction/coordination/events.js +7 -0
  244. package/dist/transaction/coordination/index.d.ts +19 -0
  245. package/dist/transaction/coordination/index.js +44 -0
  246. package/dist/transaction/coordination/locator.d.ts +83 -0
  247. package/dist/transaction/coordination/locator.js +82 -0
  248. package/dist/transaction/coordination/schema.d.ts +1473 -0
  249. package/dist/{coordination → transaction/coordination}/schema.js +490 -55
  250. package/dist/transaction/coordination/targetConflict.d.ts +2 -0
  251. package/dist/transaction/coordination/targetConflict.js +103 -0
  252. package/dist/{coordination → transaction/coordination}/trace.d.ts +7 -18
  253. package/dist/{coordination → transaction/coordination}/trace.js +18 -25
  254. package/dist/transaction/durableWrites.d.ts +62 -0
  255. package/dist/{client → transaction}/durableWrites.js +28 -3
  256. package/dist/transaction/environment.d.ts +105 -0
  257. package/dist/transaction/environment.js +108 -0
  258. package/dist/{errorCodes.d.ts → transaction/errorCodes.d.ts} +11 -11
  259. package/dist/{errorCodes.js → transaction/errorCodes.js} +35 -12
  260. package/dist/{errors.d.ts → transaction/errors.d.ts} +37 -13
  261. package/dist/{errors.js → transaction/errors.js} +85 -16
  262. package/dist/transaction/index.d.ts +20 -0
  263. package/dist/transaction/index.js +20 -0
  264. package/dist/transaction/keys/index.d.ts +87 -0
  265. package/dist/transaction/keys/index.js +207 -0
  266. package/dist/transaction/log/syncDeltaRow.d.ts +158 -0
  267. package/dist/transaction/log/syncDeltaRow.js +95 -0
  268. package/dist/{sync/syncPosition.d.ts → transaction/logPosition.d.ts} +22 -8
  269. package/dist/{sync/syncPosition.js → transaction/logPosition.js} +15 -6
  270. package/dist/transaction/logger.d.ts +16 -0
  271. package/dist/transaction/logger.js +7 -0
  272. package/dist/transaction/observability.d.ts +53 -0
  273. package/dist/transaction/observability.js +19 -0
  274. package/dist/transaction/plugin.d.ts +192 -0
  275. package/dist/transaction/plugin.js +87 -0
  276. package/dist/{policy → transaction/policy}/types.d.ts +3 -3
  277. package/dist/{policy → transaction/policy}/types.js +2 -0
  278. package/dist/transaction/resources/httpResources.d.ts +266 -0
  279. package/dist/transaction/resources/httpResources.js +7 -0
  280. package/dist/transaction/resources/modelOperations.d.ts +319 -0
  281. package/dist/transaction/resources/modelOperations.js +12 -0
  282. package/dist/transaction/resources/mutationOptions.d.ts +66 -0
  283. package/dist/transaction/resources/mutationOptions.js +9 -0
  284. package/dist/transaction/resources/where.d.ts +85 -0
  285. package/dist/transaction/resources/where.js +70 -0
  286. package/dist/{client → transaction/resources}/writeOptionsSchema.d.ts +2 -6
  287. package/dist/{client → transaction/resources}/writeOptionsSchema.js +9 -5
  288. package/dist/{schema → transaction/schema}/field.d.ts +5 -5
  289. package/dist/{schema → transaction/schema}/field.js +5 -5
  290. package/dist/transaction/schema/loadStrategy.d.ts +45 -0
  291. package/dist/transaction/schema/loadStrategy.js +46 -0
  292. package/dist/{schema → transaction/schema}/model.d.ts +50 -35
  293. package/dist/{schema → transaction/schema}/model.js +30 -20
  294. package/dist/transaction/schema/openapi.d.ts +57 -0
  295. package/dist/transaction/schema/openapi.js +340 -0
  296. package/dist/{schema → transaction/schema}/relation.d.ts +14 -14
  297. package/dist/{schema → transaction/schema}/relation.js +7 -7
  298. package/dist/{schema → transaction/schema}/residency.d.ts +0 -9
  299. package/dist/{schema → transaction/schema}/residency.js +0 -5
  300. package/dist/{schema → transaction/schema}/roles.d.ts +5 -5
  301. package/dist/{schema → transaction/schema}/roles.js +5 -5
  302. package/dist/{schema → transaction/schema}/schema.d.ts +12 -10
  303. package/dist/{schema → transaction/schema}/schema.js +4 -3
  304. package/dist/{schema → transaction/schema}/tenancy.d.ts +1 -1
  305. package/dist/{schema → transaction/schema}/tenancy.js +7 -4
  306. package/dist/transaction/transactionLayer.d.ts +82 -0
  307. package/dist/transaction/transactionLayer.js +24 -0
  308. package/dist/{transactions → transaction/transactions/settlement}/commitEnvelope.d.ts +4 -5
  309. package/dist/{transactions → transaction/transactions/settlement}/commitEnvelope.js +9 -13
  310. package/dist/{transactions → transaction/transactions/settlement}/httpCommitEnvelope.d.ts +9 -2
  311. package/dist/{transactions → transaction/transactions/settlement}/httpCommitEnvelope.js +5 -9
  312. package/dist/{transactions/durableWriteStore.d.ts → transaction/transactions/settlement/pendingWrite.d.ts} +10 -36
  313. package/dist/transaction/transactions/settlement/pendingWrite.js +20 -0
  314. package/dist/transaction/transport/commitFrames.d.ts +90 -0
  315. package/dist/transaction/transport/commitFrames.js +134 -0
  316. package/dist/transaction/transport/connectionManager.d.ts +215 -0
  317. package/dist/transaction/transport/connectionManager.js +673 -0
  318. package/dist/transaction/transport/credentialLifecycle.d.ts +177 -0
  319. package/dist/transaction/transport/credentialLifecycle.js +324 -0
  320. package/dist/{sync → transaction/transport}/heartbeat.d.ts +3 -1
  321. package/dist/{sync → transaction/transport}/heartbeat.js +6 -4
  322. package/dist/{client → transaction/transport}/httpClient.d.ts +59 -16
  323. package/dist/{client → transaction/transport}/httpClient.js +5 -5
  324. package/dist/transaction/transport/httpOptions.d.ts +33 -0
  325. package/dist/transaction/transport/httpOptions.js +12 -0
  326. package/dist/{client → transaction/transport}/httpTransport.js +171 -85
  327. package/dist/{sync/NetworkProbe.d.ts → transaction/transport/networkProbe.d.ts} +7 -4
  328. package/dist/{sync/NetworkProbe.js → transaction/transport/networkProbe.js} +14 -13
  329. package/dist/transaction/transport/wsFrameHandlers.d.ts +128 -0
  330. package/dist/transaction/transport/wsFrameHandlers.js +429 -0
  331. package/dist/transaction/transport/wsTransport.d.ts +576 -0
  332. package/dist/transaction/transport/wsTransport.js +1017 -0
  333. package/dist/transaction/types/assertExact.d.ts +17 -0
  334. package/dist/transaction/types/assertExact.js +1 -0
  335. package/dist/{types → transaction/types}/global.d.ts +17 -2
  336. package/dist/{types → transaction/types}/global.js +2 -1
  337. package/dist/{types → transaction/types}/index.d.ts +14 -46
  338. package/dist/{types → transaction/types}/index.js +7 -16
  339. package/dist/{types → transaction/types}/streams.d.ts +63 -45
  340. package/dist/{utils → transaction/utils}/json.d.ts +18 -0
  341. package/dist/transaction/utils/json.js +276 -0
  342. package/dist/transaction/wire/accountResponses.d.ts +351 -0
  343. package/dist/transaction/wire/accountResponses.js +255 -0
  344. package/dist/transaction/wire/auth.d.ts +49 -0
  345. package/dist/transaction/wire/auth.js +57 -0
  346. package/dist/transaction/wire/claimEvent.d.ts +76 -0
  347. package/dist/transaction/wire/claimEvent.js +73 -0
  348. package/dist/transaction/wire/claims.d.ts +463 -0
  349. package/dist/transaction/wire/claims.js +229 -0
  350. package/dist/{wire → transaction/wire}/commit.d.ts +125 -193
  351. package/dist/{wire → transaction/wire}/commit.js +68 -47
  352. package/dist/{wire → transaction/wire}/delta.d.ts +66 -17
  353. package/dist/{wire → transaction/wire}/delta.js +37 -13
  354. package/dist/transaction/wire/errorEnvelope.d.ts +72 -0
  355. package/dist/{wire → transaction/wire}/errorEnvelope.js +36 -5
  356. package/dist/transaction/wire/feedCursor.d.ts +60 -0
  357. package/dist/transaction/wire/feedCursor.js +82 -0
  358. package/dist/transaction/wire/feedEvent.d.ts +177 -0
  359. package/dist/transaction/wire/feedEvent.js +39 -0
  360. package/dist/transaction/wire/frames.d.ts +194 -0
  361. package/dist/transaction/wire/frames.js +50 -0
  362. package/dist/transaction/wire/inboundFrames.d.ts +552 -0
  363. package/dist/transaction/wire/inboundFrames.js +116 -0
  364. package/dist/transaction/wire/index.d.ts +50 -0
  365. package/dist/transaction/wire/index.js +74 -0
  366. package/dist/{wire → transaction/wire}/listEnvelope.d.ts +13 -14
  367. package/dist/transaction/wire/listEnvelope.js +42 -0
  368. package/dist/transaction/wire/modelResponses.d.ts +85 -0
  369. package/dist/transaction/wire/modelResponses.js +43 -0
  370. package/dist/transactions/{TransactionQueue.d.ts → mutations/MutationQueue.d.ts} +79 -38
  371. package/dist/transactions/{TransactionQueue.js → mutations/MutationQueue.js} +110 -59
  372. package/dist/transactions/{TransactionStore.d.ts → mutations/MutationStore.d.ts} +8 -8
  373. package/dist/transactions/{TransactionStore.js → mutations/MutationStore.js} +2 -2
  374. package/dist/transactions/{coalesceRules.d.ts → mutations/coalesceRules.d.ts} +10 -10
  375. package/dist/transactions/{coalesceRules.js → mutations/coalesceRules.js} +1 -1
  376. package/dist/transactions/mutations/commitLatency.d.ts +52 -0
  377. package/dist/transactions/mutations/commitLatency.js +130 -0
  378. package/dist/transactions/{commitOutboxStore.d.ts → mutations/commitOutboxStore.d.ts} +1 -5
  379. package/dist/transactions/{commitOutboxStore.js → mutations/commitOutboxStore.js} +1 -1
  380. package/dist/transactions/{commitPayload.d.ts → mutations/commitPayload.d.ts} +16 -15
  381. package/dist/transactions/{commitPayload.js → mutations/commitPayload.js} +12 -12
  382. package/dist/transactions/{deltaConfirmation.d.ts → mutations/deltaConfirmation.d.ts} +11 -11
  383. package/dist/transactions/{deltaConfirmation.js → mutations/deltaConfirmation.js} +7 -7
  384. package/dist/transactions/mutations/durableWriteStore.d.ts +14 -0
  385. package/dist/transactions/mutations/durableWriteStore.js +12 -0
  386. package/dist/transactions/{optimisticApply.d.ts → mutations/optimisticApply.d.ts} +7 -7
  387. package/dist/transactions/{replayValidation.d.ts → mutations/replayValidation.d.ts} +3 -3
  388. package/dist/transactions/{replayValidation.js → mutations/replayValidation.js} +4 -3
  389. package/dist/utils/mobxSetup.d.ts +1 -1
  390. package/dist/utils/mobxSetup.js +5 -2
  391. package/dist/webhooks/events.d.ts +2 -2
  392. package/dist/wire/index.d.ts +1 -34
  393. package/dist/wire/index.js +8 -49
  394. package/docs/agent-messaging.md +3 -3
  395. package/docs/agents.md +19 -12
  396. package/docs/api-keys.md +8 -4
  397. package/docs/api.md +22 -18
  398. package/docs/audit.md +2 -0
  399. package/docs/cli.md +31 -3
  400. package/docs/client-behavior.md +8 -6
  401. package/docs/concurrency-convention.md +30 -24
  402. package/docs/coordination.md +48 -38
  403. package/docs/data-sources.md +3 -1
  404. package/docs/debugging.md +5 -3
  405. package/docs/deployment.md +267 -0
  406. package/docs/examples/agent-human.md +49 -42
  407. package/docs/examples/ai-sdk-tool.md +69 -44
  408. package/docs/examples/existing-python-backend.md +8 -6
  409. package/docs/examples/nextjs.md +129 -47
  410. package/docs/examples/scoped-agent.md +45 -44
  411. package/docs/examples/server-agent.md +46 -26
  412. package/docs/groups.md +32 -29
  413. package/docs/guarantees.md +4 -2
  414. package/docs/how-it-works.md +9 -7
  415. package/docs/idempotency.md +126 -0
  416. package/docs/identity.md +58 -54
  417. package/docs/index.md +172 -84
  418. package/docs/integration-guide.md +17 -16
  419. package/docs/interaction-model.md +6 -4
  420. package/docs/mcp.md +41 -16
  421. package/docs/migration.md +63 -5
  422. package/docs/operating-on-your-database.md +111 -0
  423. package/docs/projects.md +2 -0
  424. package/docs/quickstart.md +22 -5
  425. package/docs/react.md +12 -10
  426. package/docs/schema-contract.md +5 -3
  427. package/docs/session-settings.md +108 -0
  428. package/docs/sessions.md +3 -1
  429. package/docs/webhooks.md +3 -1
  430. package/llms.txt +47 -17
  431. package/package.json +10 -8
  432. package/dist/agent/Agent.d.ts +0 -366
  433. package/dist/agent/Agent.js +0 -514
  434. package/dist/agent/index.d.ts +0 -115
  435. package/dist/agent/index.js +0 -128
  436. package/dist/agent/session.d.ts +0 -93
  437. package/dist/agent/session.js +0 -149
  438. package/dist/agent/types.d.ts +0 -68
  439. package/dist/agent/types.js +0 -9
  440. package/dist/client/durableWrites.d.ts +0 -21
  441. package/dist/coordination/schema.d.ts +0 -722
  442. package/dist/schema/openapi.d.ts +0 -29
  443. package/dist/schema/openapi.js +0 -124
  444. package/dist/transactions/durableWriteStore.js +0 -30
  445. package/dist/utils/json.js +0 -88
  446. package/dist/wire/errorEnvelope.d.ts +0 -55
  447. package/dist/wire/frames.d.ts +0 -197
  448. package/dist/wire/frames.js +0 -49
  449. package/dist/wire/listEnvelope.js +0 -18
  450. /package/dist/{client → transaction/auth}/credentialEndpoint.d.ts +0 -0
  451. /package/dist/{client → transaction/auth}/credentialEndpoint.js +0 -0
  452. /package/dist/{auth → transaction/auth}/credentialPolicy.d.ts +0 -0
  453. /package/dist/{auth → transaction/auth}/credentialPolicy.js +0 -0
  454. /package/dist/{auth → transaction/auth}/credentialSource.js +0 -0
  455. /package/dist/{client → transaction/auth}/hostedEndpoints.d.ts +0 -0
  456. /package/dist/{client → transaction/auth}/hostedEndpoints.js +0 -0
  457. /package/dist/{client → transaction/coordination}/claimHeartbeatLoop.d.ts +0 -0
  458. /package/dist/{client → transaction/coordination}/claimHeartbeatLoop.js +0 -0
  459. /package/dist/{client → transaction}/persistence.d.ts +0 -0
  460. /package/dist/{client → transaction}/persistence.js +0 -0
  461. /package/dist/{client → transaction/resources}/functionalUpdate.d.ts +0 -0
  462. /package/dist/{client → transaction/resources}/functionalUpdate.js +0 -0
  463. /package/dist/{transactions → transaction/transactions/settlement}/idempotencyKey.d.ts +0 -0
  464. /package/dist/{transactions → transaction/transactions/settlement}/idempotencyKey.js +0 -0
  465. /package/dist/{client → transaction/transport}/httpTransport.d.ts +0 -0
  466. /package/dist/{types → transaction/types}/modelData.d.ts +0 -0
  467. /package/dist/{types → transaction/types}/modelData.js +0 -0
  468. /package/dist/{types → transaction/types}/participant.d.ts +0 -0
  469. /package/dist/{types → transaction/types}/participant.js +0 -0
  470. /package/dist/{types → transaction/types}/streams.js +0 -0
  471. /package/dist/{utils → transaction/utils}/asyncIterator.d.ts +0 -0
  472. /package/dist/{utils → transaction/utils}/asyncIterator.js +0 -0
  473. /package/dist/{utils → transaction/utils}/duration.d.ts +0 -0
  474. /package/dist/{utils → transaction/utils}/duration.js +0 -0
  475. /package/dist/{wire → transaction/wire}/bootstrapReason.d.ts +0 -0
  476. /package/dist/{wire → transaction/wire}/bootstrapReason.js +0 -0
  477. /package/dist/{wire → transaction/wire}/protocol.d.ts +0 -0
  478. /package/dist/{wire → transaction/wire}/protocol.js +0 -0
  479. /package/dist/{wire → transaction/wire}/protocolVersion.d.ts +0 -0
  480. /package/dist/{wire → transaction/wire}/protocolVersion.js +0 -0
  481. /package/dist/transactions/{UnconfirmedWrites.d.ts → mutations/UnconfirmedWrites.d.ts} +0 -0
  482. /package/dist/transactions/{UnconfirmedWrites.js → mutations/UnconfirmedWrites.js} +0 -0
  483. /package/dist/transactions/{optimisticApply.js → mutations/optimisticApply.js} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,12 +1,691 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.35.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 6be1d99: The `@abloatai/ablo/agent` entry point is removed, along with the single name it exported, `Agent`.
8
+
9
+ There is no separate agent SDK, and in practice there has not been one for some time: an agent is
10
+ the same client with a different transport. `Ablo({ schema, apiKey, transport: 'http' })` returns a
11
+ stateless, request/response client whose credential is its identity, carrying the same typed
12
+ `ablo.<model>` surface a browser or a server action uses, minus the parts that need a socket — the
13
+ `local` reads and `onChange`, which are absent from the type rather than failing at runtime. A
14
+ second entry point offered a second way in and, mostly, a second thing to keep in step with the
15
+ first. If you imported from `@abloatai/ablo/agent`, import from `@abloatai/ablo` and select the
16
+ transport.
17
+
18
+ `ClaimEvent.participantKind` is also removed. A claim event is one transition in a lease's
19
+ lifecycle — acquired, queued, granted, lost, rejected, expired — and it identifies the actor that
20
+ moved it. Whether that actor was a person, an agent, or the system is a property of the holder, not
21
+ of the transition, and stating it on every event meant maintaining the same fact in two shapes. Read
22
+ it where the holder is described instead: `ablo.<model>.claim.state({ id })` returns the current
23
+ holder with its `participantKind`, which is what the "an agent is editing this" affordance in a UI
24
+ has always read.
25
+
26
+ - 1b12959: The client is now the coordination layer, and the things built on top of it are
27
+ capabilities you install rather than parts of the client you get whether you use
28
+ them or not.
29
+
30
+ ```ts
31
+ import { Ablo, humans } from '@abloatai/ablo';
32
+
33
+ const ablo = Ablo({ schema, apiKey, plugins: [humans()] });
34
+ ```
35
+
36
+ `humans()` declares the local, watchable copy: the offline store, live queries,
37
+ presence, and the framework bindings — everything that exists because a person
38
+ is looking at the screen. What remains underneath is what coordination actually
39
+ needs: commit, read, observe, claim. Omit `plugins` and `humans()` is installed
40
+ for you, so an application that renders anything today keeps working with no
41
+ change; naming it in the list only makes visible what was already true.
42
+
43
+ There is no `agents()` counterpart, and the absence is the point. An agent is
44
+ not a special caller that needs its own mode — it is the ordinary one, and what
45
+ it needs is the client without the materialiser. That is the same observation
46
+ behind removing the `/agent` entry point in this release: there was never a
47
+ second SDK to enter, only a client with a transport that does not open a socket.
48
+ `Ablo({ schema, apiKey, transport: 'http' })` is the whole of it, and because
49
+ `humans()` requires a duplex connection, asking for it on that client fails while
50
+ the client is being constructed, with an error naming the plugin, rather than
51
+ resolving to a presence stream that can never receive a frame. A plugin listed
52
+ twice fails the same way.
53
+
54
+ The rearrangement is also why the reactive `ablo.<model>` surface composes from
55
+ the transport-independent one rather than restating it — the note on the renamed
56
+ local reads describes what that buys — and why `track`, a durable premise that
57
+ keeps no local copy, sits in the core where an agent can reach it without
58
+ installing a materialiser it has no use for.
59
+
60
+ This is a seam, not a finished migration. `humans()` today constructs the
61
+ presence stream and declares the constraint that keeps it off a stateless
62
+ client; the rest of the materialiser — the store, the bootstrap, the delta
63
+ pipeline, the framework bindings — still lives in the composition root and moves
64
+ behind the same door as the plugin context grows to carry what each piece needs.
65
+ The contract those pieces will arrive through is the one published here, so a
66
+ capability written against it now keeps working as they do.
67
+
68
+ - 6be1d99: The shape of a claim's `meta` is now declared once, on `Register`, beside your
69
+ schema:
70
+
71
+ ```ts
72
+ declare module '@abloatai/ablo' {
73
+ interface Register {
74
+ Schema: typeof schema;
75
+ ClaimMeta: { blocks: string[] };
76
+ }
77
+ }
78
+ ```
79
+
80
+ From then on `claim.state({ id })?.target.meta`, every entry in `claim.queue`,
81
+ and the claim you hold under `await using` all read that shape, and the `typeof`
82
+ guard each of those reads used to need is gone. The same declaration types what
83
+ you write — `claim({ id, meta: { blocks } })` — so the writer and every reader
84
+ agree by construction rather than by convention, which is the half a per-call
85
+ type argument could never reach: naming the shape at the read site said nothing
86
+ about what the write site was allowed to put there.
87
+
88
+ Prefer the registration to the per-call `claim.state<MyMeta>({ id })`, because
89
+ one declaration is one place to change when the metadata grows a field. The
90
+ type argument remains supported and is still the honest answer for a program
91
+ carrying more than one metadata shape; only its default moved, from a bare
92
+ record to whatever you registered. A program that registers nothing sees no
93
+ change at all — `target.meta` stays the loose record it has always been.
94
+
95
+ What travels between participants is untouched. Claim metadata is carried
96
+ verbatim and never interpreted, and the protocol keeps accepting any object it
97
+ is given, so a peer on a newer build can send a field this one has never heard
98
+ of and it still arrives intact. The declaration is a promise about what your own
99
+ program writes, not a filter on what it receives.
100
+
101
+ - e4b538b: A model's conflict stance can be written as a chain through one name.
102
+
103
+ ```ts
104
+ import { coordination, model, z } from '@abloatai/ablo/schema';
105
+
106
+ export const cards = model(
107
+ {
108
+ title: z.string(),
109
+ },
110
+ {
111
+ conflict: coordination.humansOverwrite().agentsReject(),
112
+ }
113
+ );
114
+ ```
115
+
116
+ The nine disposition rules were the only loose functions left in the schema DSL.
117
+ Everything else there arrives under a noun that owns it — `relation.belongsTo`,
118
+ `field.string`, `mutable.lazy` — but declaring that humans overwrite and agents
119
+ yield meant importing three separate names to say one thing, and a schema with a
120
+ couple of stances spent more of its import list on conflict rules than on the rest
121
+ of the language combined.
122
+
123
+ So the rules now hang off `coordination`, and each one returns an axis that is
124
+ still open to the next, the way a field chains its own modifiers. The rules
125
+ themselves are unchanged, and each remains the single place its disposition map is
126
+ written; the chain delegates to the standalone function rather than restating it.
127
+ Those standalone functions stay exported, and `coordination()` still merges them,
128
+ which is the form to reach for when the rules are assembled at runtime rather than
129
+ written out:
130
+
131
+ ```ts
132
+ const stance = coordination(humansOverwrite(), agentsReject());
133
+ ```
134
+
135
+ Both forms produce the same plain, serializable map keyed by the committer's
136
+ participant kind — `{ user: 'overwrite', agent: 'reject' }` — and the engine reads
137
+ it at the commit chokepoint exactly as before. A kind left unnamed still falls
138
+ through to the engine default, and a kind named twice still takes the later rule.
139
+
140
+ - 6be1d99: `captureTransactionFailure` on the observability provider is renamed to `captureMutationFailure`.
141
+ If you pass an `observability` provider to `Ablo(...)`, rename that one method; its argument is
142
+ unchanged.
143
+
144
+ The rename frees a word that had been doing two jobs. A transaction, in this system, is a unit of
145
+ write that settles — the thing you author, hand to the engine, and get a receipt for. But the same
146
+ word also named the client's optimistic queue, the buffer of writes that have been applied locally
147
+ and are still waiting for the server to agree. The callback belonged to the second meaning: it
148
+ fires when a queued write has exhausted its attempts and will never land, so the local change is
149
+ rolled back and something upstream needs to hear about it. Calling that a transaction failure
150
+ invited the reading that a settlement had failed, which is a different event with a different
151
+ remedy — and as the settlement layer grew a vocabulary of its own, the collision stopped being
152
+ cosmetic.
153
+
154
+ There is no compatibility alias, and the reason is worth stating, because it is the opposite of
155
+ the usual one. `captureMutationFailure` is a required member of the provider interface. A provider
156
+ still carrying the old spelling therefore does not satisfy that interface at all: the compiler
157
+ stops the build and names the missing member, at the line where the provider is constructed. An
158
+ optional alias would have made that build succeed while the engine went on calling a method the
159
+ provider does not implement — quiet in exactly the way a renamed callback must not be. The
160
+ compiler is the better messenger here, and this note is the rest of the message.
161
+
162
+ - 6be1d99: `debug`, `logLevel`, and `persistence` are no longer accepted on the stateless HTTP client. They
163
+ remain exactly where they have always worked — on the default client, over the socket.
164
+
165
+ They were reachable on `Ablo({ transport: 'http' })` only because that client's options type was
166
+ assembled from the whole composition bag rather than from the fields the transport actually reads,
167
+ and nothing on the HTTP path ever read them. `persistence` configures a local store a stateless
168
+ client does not keep. `debug` and `logLevel` turn on the `[Ablo]` coordination trace, which
169
+ narrates presence, claim grants, and queueing — frames that arrive over a socket an HTTP client
170
+ never opens. Passing any of the three was accepted and did nothing, which is the kind of option
171
+ that costs a reader an afternoon before they conclude the feature is broken.
172
+
173
+ If you passed one to an HTTP client, delete it: nothing changes, because nothing was happening.
174
+ If what you wanted was the trace from a server-side worker, it is not a setting you were missing —
175
+ a stateless agent holds no live session to narrate, and its writes are visible in the audit log and
176
+ in `ablo logs` instead.
177
+
178
+ - 6be1d99: A model's load strategy is now `'instant'` or `'lazy'`, and nothing else. The
179
+ word `'manual'` is gone, along with the `mutable.manual(...)` helper that set
180
+ it.
181
+
182
+ The strategy had drifted into two declarations that never agreed. The authoring
183
+ side, reached through `@abloatai/ablo/schema`, offered `'instant' | 'lazy' |
184
+ 'manual'`. The runtime side, reached through `@abloatai/ablo/core` under the
185
+ same name, offered `instant`, `lazy`, `partial`, `explicitlyRequested`, and
186
+ `local`. Importing `LoadStrategy` from the two paths gave you two different
187
+ types, and no compiler anywhere would tell you which one you had.
188
+
189
+ Only `instant` and `lazy` were ever real. `'manual'` promised loading a model
190
+ "only when you explicitly call `sync.model.load()`", a method that was never
191
+ built; a model declared manual resolved to `lazy` and loaded on first access
192
+ like any other. `partial` had live branches in the client but no way to declare
193
+ it, and `explicitlyRequested` and `local` had neither. So the correction is
194
+ mostly the removal of vocabulary that described behavior the engine did not
195
+ have — a model that said `manual` was already behaving as `lazy`, and continues
196
+ to, which is why this changes what your schema is allowed to say rather than
197
+ what your program does.
198
+
199
+ Replace `load: 'manual'` with `load: 'lazy'`, and `mutable.manual(...)` with
200
+ `mutable.lazy(...)`. `readOnly.internal(...)` stays, and its guarantee is
201
+ unchanged and worth restating plainly: it is a write boundary, not a read one.
202
+ Clients cannot commit to an internal model, and the server rejects any attempt.
203
+ Its rows still reach a client that reads the model.
204
+
205
+ `LoadStrategy` now has one declaration behind both import paths. It reads as a
206
+ value where the engine branches on it — `LoadStrategy.instant` — and as the
207
+ plain string an author types in `model(…, { load: 'instant' })`, and the two
208
+ can no longer name different sets.
209
+
210
+ Two names join it on `@abloatai/ablo/schema`. `DEFAULT_LOAD_STRATEGY` is the
211
+ strategy a model gets when it declares none, and `loadsAtBootstrap(load)`
212
+ answers whether a model's rows arrive in the bootstrap payload or on first
213
+ access. The predicate is exported because both halves of the system have to
214
+ agree on it: the client builds its bootstrap subscription from the schema and
215
+ the server assembles the payload from the same schema, and they had been
216
+ asking the question in three different spellings — `load !== 'lazy'` in one
217
+ place, `load === 'instant'` in another. Against two members those agree. A
218
+ third member would have had one side enrolling it and the other withholding it,
219
+ which surfaces as rows that never arrive, a long way from the line responsible.
220
+
221
+ - The three synchronous reads on `ablo.<model>` are renamed. `get(id)`, `getAll(options)`,
222
+ and `getCount(options)` become `local.retrieve(id)`, `local.list(options)`, and
223
+ `local.count(options)`.
224
+
225
+ The old names were a pair that carried no information. `get` and `retrieve` are
226
+ synonyms in English, so a reader who found `ablo.tasks.get(id)` beside
227
+ `ablo.tasks.retrieve({ id })` had no way to tell from the words which one waited
228
+ on the network and which one did not — the difference had to be memorised, and
229
+ it was memorised imperfectly. Two bugs reached review that way, both of them a
230
+ synchronous read standing in for an asynchronous one against a graph that had
231
+ not warmed up yet, both of them invisible in the diff because the line read
232
+ exactly like the call it should have been.
233
+
234
+ The new names say the thing out loud. Every verb is the verb it already was on
235
+ the asynchronous side; the only addition anywhere in the design is the word
236
+ `local`, and it is a narrowing rather than a claim about its sibling.
237
+ `retrieve` consults the local graph first and falls back to the network;
238
+ `local.retrieve` is restricted to what is already resident, which is also why it
239
+ can hand back a value instead of a promise. There is nothing to await. Reading
240
+ `ablo.tasks.local.list({ where })`, you know both what it does and what it costs
241
+ without leaving the line.
242
+
243
+ The reads themselves are unchanged — same options, same lifecycle `state`
244
+ filter, same reactivity inside `useAblo` selectors and in render. Only the path
245
+ to them moved, and it is the only path: the local reads are not also exposed at
246
+ the top level, because a namespace that duplicates what sits beside it stops
247
+ disambiguating anything.
248
+
249
+ Underneath, the rename arrives with a structural change that will keep the
250
+ surface honest from here on. The reactive `ablo.<model>` type was hand-written as
251
+ a second list that happened to agree with the transport-independent one, and
252
+ nothing linked them: a verb added to one said nothing about the other. It is now
253
+ composed from that base rather than restated, so a verb added there arrives on
254
+ the reactive client on its own. `track` — registering a durable premise on a
255
+ row — is the first to arrive that way.
256
+
257
+ Migrating is mechanical, but reach for the compiler rather than a search. `getAll`
258
+ and `getCount` are distinctive enough to find by name; `get` is not, and in a
259
+ codebase of any size it hides among the `Map.get` and `headers.get` calls that a
260
+ search cannot tell it from. Removing the old names from the type turns the
261
+ question into a list of type errors, each one a site that genuinely needs to
262
+ move.
263
+
264
+ - 0002877: A capability is declared in the vocabulary you write, and every other spelling
265
+ of it derives from that declaration.
266
+
267
+ ```ts
268
+ import { grantedOperations, modelWireNames } from '@abloatai/ablo/auth';
269
+
270
+ const operations = grantedOperations(
271
+ { documents: ['read', 'update'] },
272
+ modelWireNames(schema.models)
273
+ );
274
+ ```
275
+
276
+ The same grant used to be spelled five times: a literal union in the resource
277
+ types, a `z.array(z.string())` on the wire, a field-by-field parser in the mint
278
+ route, an object literal in the response type, and a hand-written `model.verb`
279
+ array at every caller that mints without the SDK. Nothing failed when those
280
+ drifted apart. The drift surfaced later as `capability_scope_denied` on a grant
281
+ the caller was certain it held, at a call site with no view of the spelling that
282
+ had gone stale.
283
+
284
+ They are now one module on `@abloatai/ablo/auth`. `capabilityOperationSchema`
285
+ holds the verb vocabulary; `grantedOperationSchema` derives the wire pattern
286
+ `<model>.<verb>` from it as a template literal, so tightening the verbs cannot
287
+ leave a stale regex or a stale published contract behind. `CapabilityCan<S>`
288
+ narrows the declaration to one schema's models, so `can: { tasks: ['update'] }`
289
+ fails to compile against a schema with no `tasks` model.
290
+
291
+ `modelWireNames` is the derivation worth knowing about, because it is the one
292
+ people got wrong by hand. A model whose type name is overridden — schema key
293
+ `documents`, type name `Document` — must be granted as `document.update`, and a
294
+ caller who works that out by eye learns otherwise at enforcement time. Pass your
295
+ schema's models and the map is built for you. It is a required argument rather
296
+ than an optional one: an omitted map would mint the schema key verbatim, which
297
+ is right for most models and wrong for exactly the ones that need it.
298
+
299
+ Two more derivations ride along. `expandReadYourWrites` appends `<model>.read`
300
+ for every model the grant can write, because a scoped agent that may update a
301
+ row has to be able to read it or the read gate starves the writes the grant
302
+ allows — write verbs stay the source of truth, reads are derived and deduped,
303
+ and a model the grant cannot write stays unreadable. And a grant is now checked
304
+ against the schema **at the mint**: `capabilityModelAliases` accepts any of the
305
+ three names one model answers to (type name, schema key, table name), and
306
+ `unresolvableOperations` reports the ones that name nothing, so a typo is a
307
+ rejected mint rather than a credential that looks healthy until its first write.
308
+
309
+ ### Breaking
310
+
311
+ `ExchangeApiKeyRequest.operations` narrows from `readonly string[]` to
312
+ `readonly GrantedOperation[]`. If you assembled that array by hand, the compiler
313
+ will now reject any entry that is not `<model>.<verb>` — including the `'*'`
314
+ wildcard, which was never a member of the wire form and is no longer typable as
315
+ one. Build the array with `grantedOperations(can, modelWireNames(schema.models))`
316
+ instead of composing the strings; that is the same call the SDK makes, and it
317
+ resolves the type-name override you would otherwise have to remember.
318
+
319
+ - feb88a2: Three error codes carried the word "intent", left over from the vocabulary the
320
+ coordination layer used before it settled on claims. The word had come to mean
321
+ two unrelated things in the same registry, so both are now named for what they
322
+ are.
323
+
324
+ `invalid_intent` is removed. It was raised when a claim request failed to name
325
+ the model and row it targeted — which is what `malformed_claim` already
326
+ described, in the same words, for the same failure arriving over the WebSocket
327
+ instead of over HTTP. The two have been merged into `malformed_claim`; a caller
328
+ switching on `invalid_intent` should switch on `malformed_claim` instead, and
329
+ the HTTP status is 400 either way.
330
+
331
+ `slide_intent_missing_deck_id` and `slide_intent_unknown_sibling` are renamed to
332
+ `slide_position_missing_deck_id` and `slide_position_unknown_sibling`. These were
333
+ never about claims at all: they report that a slide created at a position stated
334
+ relative to its neighbours — "before this one" — could not have that position
335
+ resolved, because no deck was named or because the neighbour is not in it. An
336
+ earlier rename had swept the word "claim" through their descriptions and left
337
+ them reading as contention errors, so their messages are rewritten to say what
338
+ actually went wrong.
339
+
340
+ `malformed_claim` moves from the `claim` category to `validation`, alongside
341
+ `malformed_subscription`. The code, its 400 status, and its meaning are
342
+ unchanged — only the grouping it appears under in the error documentation. The
343
+ `claim` category now holds only genuine contention, which is what its name
344
+ promised.
345
+
346
+ - e4b538b: `model()` now takes a shape and one options object. Relations, which used to hold a
347
+ positional argument of their own in between, are the `relations` key on those options.
348
+
349
+ ```ts
350
+ documents: model(
351
+ {
352
+ title: z.string(),
353
+ projectId: z.string(),
354
+ },
355
+ {
356
+ relations: { project: relation.belongsTo('projects', 'projectId') },
357
+ conflict: coordination(humansOverwrite(), agentsReject()),
358
+ load: 'lazy',
359
+ }
360
+ ),
361
+ ```
362
+
363
+ The middle argument was the problem. Most models have no relations at all, yet nearly
364
+ every model has something to say about itself — a table it maps to, a load strategy, a
365
+ conflict disposition — and the only way to reach that third argument was to write the
366
+ second one first. So the schema filled up with empty braces standing in for edges that
367
+ did not exist. In this repository alone the placeholder appeared 104 times, which made
368
+ `{}` the single most common thing anyone ever passed in that position: more often than
369
+ any real relations object. A reader opening a schema met the empty braces before they
370
+ met the model.
371
+
372
+ Nothing about relations themselves changes. They are built with the same
373
+ `relation.belongsTo` and `relation.hasMany` factories, they carry the same options, and
374
+ the engine reads them exactly as before — to index foreign keys, to order inserts so a
375
+ parent lands before its children, and to generate the accessors behind `task.project`.
376
+ They have simply stopped being a position and become a name, which is what they always
377
+ were to everything downstream.
378
+
379
+ Migrating is mechanical, and the compiler finds every site. A call that passed an empty
380
+ placeholder drops it:
381
+
382
+ ```ts
383
+ -model({ title: z.string() }, {}, { groups: { root: 'workspace' } }) +
384
+ model({ title: z.string() }, { groups: { root: 'workspace' } });
385
+ ```
386
+
387
+ A call that passed real relations folds them in under their own key:
388
+
389
+ ```ts
390
+ - model({ title: z.string(), projectId: z.string() }, {
391
+ - project: relation.belongsTo('projects', 'projectId'),
392
+ - }, { load: 'lazy' })
393
+ + model({ title: z.string(), projectId: z.string() }, {
394
+ + relations: { project: relation.belongsTo('projects', 'projectId') },
395
+ + load: 'lazy',
396
+ + })
397
+ ```
398
+
399
+ A call that passed only fields is untouched.
400
+
401
+ - e4b538b: `app.current_user_id` is now a reserved session setting, so a schema can no
402
+ longer claim it — and a read that cannot be scoped to the person asking says so
403
+ with `user_scope_not_enforced` instead of quietly returning rows.
404
+
405
+ Ablo's direct-write connection already applied a fixed bundle of settings before
406
+ your DML — the organization, the project, the environment, the sandbox, the
407
+ acting participant — and `sessionSettings` let a schema forward any of those
408
+ into a differently-named setting its own policies read. What a schema could
409
+ never do was reassign one of the engine's own settings, because that would let a
410
+ schema push relax the scoping under which Ablo writes. `RESERVED_SESSION_SETTINGS`
411
+ is the list that prevents it, and the engine now sets one more: the acting user.
412
+
413
+ The setting carries a person's id when a person is behind the write, and `'*'`
414
+ when the request comes from a backend credential acting as the organization
415
+ itself. Absent identity writes the empty string rather than leaving the previous
416
+ value on a pooled connection, so a policy reading it denies rather than
417
+ inheriting. Your own policies are welcome to read it; they simply cannot be the
418
+ thing that decides what it contains.
419
+
420
+ If your schema mapped `app.current_user_id` through `sessionSettings`, that entry
421
+ is now dropped at authoring time and at runtime, and the engine's own value
422
+ applies instead. Map a different name if your policies need a value you control.
423
+
424
+ The new error code covers the one arrangement where that boundary cannot hold. A
425
+ plane served from its retained log carries the organization and the project on
426
+ every row but not the owner, so a rule that names a person has nothing to act on
427
+ there. Rather than fold the rows and return a plausible answer, such a read is
428
+ declined whole — a member reading a colleague's private records would otherwise
429
+ be indistinguishable from a member reading their own. Reads made by a credential
430
+ acting for the organization are unaffected, as is every plane served from its
431
+ tables.
432
+
433
+ All of this now has a page. The seam has been in the SDK since 0.32.0 and under
434
+ this name since 0.33.0, documented both times only in a release note — which is
435
+ a poor place to keep a feature rather than announce one:
436
+ a reader with row-level-security policies had no way to discover that Ablo sets
437
+ an identity context at all, let alone that they could point their own policies
438
+ at it. **Session Settings** states what the engine sets before every write, which
439
+ identities a mapping may name, which settings are reserved and why, and what
440
+ happens on a plane served from its log. Read it with `npx ablo docs
441
+ session-settings`, or on the site.
442
+
443
+ - 5972a69: `causedByTaskId` is removed from `MutationOptions` — and so from the per-verb params that
444
+ compose it, `ModelCreateParams`, `ModelUpdateParams`, and `ModelDeleteParams` — together with
445
+ the seven `turn_*` error codes that existed to police it. It also leaves `CommitContext`, where
446
+ the server carried it from the wire to the stored row. The field was never usable for its stated
447
+ purpose: setting it was the one reliable way to have a write refused.
448
+
449
+ It was the surviving half of an agent-turn protocol whose other half was never built. The idea
450
+ had been that an agent would open a turn, write under it, and close it, so that an auditor could
451
+ later ask what an agent did _because_ a person asked it to — a prompt at the root of a tree of
452
+ changes. What actually shipped was only the check. Every commit that carried a `causedByTaskId`
453
+ was validated against a task record, and nothing in the system has ever opened one, so the record
454
+ was never there. A caller who supplied the field had the entire batch rejected with
455
+ `turn_validation_failed`; a caller who left it null passed straight through. The prudent thing to
456
+ do with a documented, public option was to never touch it, and the field spent its life as
457
+ plumbing threaded through the wire protocol, the settlement envelope, the stored row, and the
458
+ replication consumer, carrying null on every path.
459
+
460
+ Nothing is lost by its removal, because write attribution never depended on it. A delta already
461
+ records who made the change and on whose authority — the actor, the `onBehalfOf` principal behind
462
+ a delegated write, the capability that authorized it, and the claim the write was made under. Those
463
+ answer "who did this and by what right" without asking the caller to maintain a lifecycle the
464
+ engine never opened on their behalf. If your code passed `causedByTaskId`, delete the argument;
465
+ if it branched on `turn_validation_failed`, that branch was unreachable and can go with it.
466
+
467
+ The removed codes are `turn_validation_failed`, `turn_open_failed`, `turn_close_failed`,
468
+ `turn_not_found`, `turn_foreign_agent`, `parent_turn_not_found`, and `parent_turn_foreign_agent`.
469
+ `ERROR_CONTRACT_VERSION` moves to `2026-07-19`, as it does whenever a code leaves the registry.
470
+ The stored-row schema loses its provenance slice with them: `deltaProvenanceSchema` and the
471
+ `DeltaProvenance` type are gone, and `syncDeltaRowSchema` is now the core and attribution slices
472
+ composed, which is what it had always described in practice. On the wire the field was optional
473
+ and nullable, so a client that still sends it is accepted and ignored, and a client reading deltas
474
+ sees one fewer always-null key.
475
+
476
+ The storage goes with it. The `caused_by_task_id` column leaves `sync_deltas`, `agent_actions_log`,
477
+ and `source_write_intents`, and the two tables that were never written — `agent_tasks` and
478
+ `agent_task_prompts` — are dropped along with the `agent_surface` enum that typed them. This looked
479
+ at first like a separate versioned migration, because the audit chain hashes the lineage id into
480
+ every row's signature and rewriting those signatures is not something a release should do quietly.
481
+ It turned out not to need rewriting. Every row was hashed with a null in that position, so the
482
+ canonical byte layout the stored hashes were computed against is preserved exactly by passing
483
+ `NULL::text` where the column used to be read — which is what the migration does, in the append
484
+ trigger and in the rechain function, before the column is dropped. The signature is untouched and
485
+ every historical `row_hash` still verifies. The `packages/audit-chain` reproduction freezes the same
486
+ position, so the two implementations cannot drift apart on it.
487
+
488
+ - f0f4226: Eight names that were renamed in an earlier release, and have shipped since as
489
+ aliases beside the names that replaced them, are removed.
490
+
491
+ | Removed | Use |
492
+ | ----------------------- | ------------------------ |
493
+ | `CommitOutboxRecord` | `PendingWrite` |
494
+ | `CommitOutboxStore` | `DurableWriteStore` |
495
+ | `MeshParticipantStatus` | `ParticipantStatus` |
496
+ | `planeSchema` | `residencySchema` |
497
+ | `SchemaPlane` | `ModelResidency` |
498
+ | `DEFAULT_PLANE` | `DEFAULT_RESIDENCY` |
499
+ | `DELTA_RESIDENCY` | `DELTA_PHYSICAL_STORAGE` |
500
+ | `InferModel` | `Model` |
501
+
502
+ Each is a one-for-one substitution — the alias and its replacement were the same
503
+ type or the same value, so nothing about your program's behavior changes with
504
+ the name. `InferModel` is the one worth a sentence: `Model<typeof schema,
505
+ 'tasks'>` reads as the domain rather than the machinery, which is why it became
506
+ the published spelling, and the old name is gone from `@abloatai/ablo/schema`
507
+ and from the `Ablo.Schema` namespace both. The type it named still exists
508
+ underneath, because `Model` is defined in terms of it rather than as a second
509
+ name for the same idea.
510
+
511
+ An alias earns one release of overlap. That is what the surface snapshot in
512
+ this repository records and enforces: a published name may leave only from a
513
+ release that shipped it marked deprecated, which is the release in which a
514
+ reader could have found out. These eight were marked in 0.34.1, so this is the
515
+ release that may drop them, and dropping them is what keeps the rule a rule
516
+ rather than a preamble on a list that only grows.
517
+
518
+ `DELTA_RESIDENCY` is the one whose replacement is not merely a rename.
519
+ Residency and classification are separate axes — where a column physically
520
+ lives, and how sensitive its contents are — and one constant named as though
521
+ they were the same question. `DELTA_PHYSICAL_STORAGE` answers the first;
522
+ `DELTA_DATA_CLASSIFICATION`, which has been exported alongside it, answers the
523
+ second. If you were reading `DELTA_RESIDENCY` to decide handling rather than
524
+ placement, the classification constant is the one you wanted.
525
+
526
+ - 320b8d7: `ablo docs` reads the documentation for the version you installed, and the
527
+ public docs routes now serve the whole corpus instead of two thirds of it.
528
+
529
+ A documentation URL always describes the newest release. A package in
530
+ `node_modules` is frozen at whatever version was published. Those two facts
531
+ drift apart the moment a project pins a dependency, and the drift is invisible
532
+ from both ends: the docs are correct, the code is correct, and the agent reading
533
+ one to write the other produces a call that does not exist. `get`, `getAll`, and
534
+ `getCount` became `retrieve` and `list` in 0.35.0, so an assistant working in a
535
+ project still on 0.34 reads the current page, writes `retrieve`, and watches it
536
+ fail against the package sitting beside it.
537
+
538
+ The documentation already travelled in the npm tarball; nothing read it there.
539
+ `ablo docs` lists every page with what it covers, and `ablo docs <page>` prints
540
+ one as markdown — from the files shipped alongside the code they describe, so
541
+ they cannot disagree with it, and with no network at all, which is the condition
542
+ most agent work now runs under. `ablo docs --json` gives the same list to a
543
+ program. A new `@abloatai/ablo/docs` entry point exposes the catalog for
544
+ anything that wants to build its own reader.
545
+
546
+ The same catalog now answers `/api/docs/*`. That surface had been maintained by
547
+ hand and had fallen twenty-one pages behind: `coordination`, the page explaining
548
+ how claims work, returned a 404 while the site published it, and so did
549
+ `sessions`, `webhooks`, `migration`, `deployment`, and every example but two.
550
+ Nothing failed when the list went stale, which is why it stayed stale. Pages are
551
+ now discovered rather than listed, so one is reachable the moment it is written,
552
+ and only what ships in the package is reachable at all.
553
+
554
+ One address changed meaning. `/api/docs/agents` returns the Agents guide, the
555
+ page the docs index has always linked under that name; the install playbook that
556
+ briefly answered there is at `/api/docs/AGENTS.md`, where it also answered
557
+ before.
558
+
559
+ - 0002877: `GET /v1/logs` answers two questions now — what changed, and who is working on
560
+ what — through one envelope and one cursor.
561
+
562
+ ```ts
563
+ import { feedEventSchema, parseFeedCursor } from '@abloatai/ablo/wire';
564
+
565
+ const page = await fetch(`${baseUrl}/v1/logs?after=${cursor}`, {
566
+ headers: { authorization: `Bearer ${apiKey}` },
567
+ }).then((r) => r.json());
568
+
569
+ for (const event of page.data) {
570
+ const entry = feedEventSchema.parse(event);
571
+ if (entry.object === 'log_event') applyChange(entry);
572
+ }
573
+ cursor = page.next_cursor;
574
+ ```
575
+
576
+ An entry is discriminated on `object`, so a reader that knows only the arm it
577
+ came for parses the whole page and skips the rest — which is what lets a second
578
+ arm be added to a feed that callers are already following. The two arms stay
579
+ separately sequenced on purpose. A claim is a lease rather than a settled fact,
580
+ and allocating its positions from the delta sequence would put ephemeral leases
581
+ into the log that clients materialise rows from and that WAL-echo promotion and
582
+ compaction operate over, where a burst of claim churn would be indistinguishable
583
+ from committed change. They share a reading, not a sequence.
584
+
585
+ That is why the cursor carries a position for each, encoded `"<log>.<claims>"`.
586
+ A bare `"<log>"` still parses and resumes at claim position zero, which is the
587
+ right answer for every cursor issued before this existed: a caller who never
588
+ asked for claim events has no claim position to preserve. `parseFeedCursor`,
589
+ `formatFeedCursor`, and `feedCursorAdvanced` are exported from
590
+ `@abloatai/ablo/wire` alongside `feedEventSchema`, `logListResponseSchema`, and
591
+ `claimEventSchema`, so a program that builds its own reader states the grammar
592
+ once. `claimRecordSchema` and `heldClaimStatusSchema` join them on
593
+ `@abloatai/ablo/coordination` — the one claim record, and the peer-visible
594
+ projection of it.
595
+
596
+ `GET /v1/claims` gains the filters the audit log already had: `actorId`,
597
+ `actorKind`, `onBehalfOfId`, and `capabilityId`, beside the existing `model` and
598
+ `id`. The names are audit's names deliberately. Asking what an agent is doing
599
+ and asking what it did should not require two vocabularies for one idea, and now
600
+ the tense is the only thing that differs.
601
+
602
+ The claim arm has no producer yet. Claim transitions are broadcast and recorded
603
+ nowhere, so there is no sequence to read a position from, and the route emits
604
+ only `log_event`s today — with no request parameter for selecting arms, because
605
+ a knob that cannot be honoured is how a contract comes to describe a server that
606
+ does not exist. What ships here is the definition that producer will fill: the
607
+ union parses today's pages unchanged, and the cursor already carries the second
608
+ position, so a follower written now keeps working when the arm arrives.
609
+
610
+ ### The cursor fix
611
+
612
+ A malformed `after` is now an error rather than a position. The previous reader
613
+ was `parseInt(raw, 10)` with a `NaN` falling back to zero, so a truncated or
614
+ garbled cursor resumed from the beginning of the log and replayed it in full —
615
+ while looking exactly like a working follow, with the damage scaling to how long
616
+ the log had been running. Such a request is now declined with `invalid_request`,
617
+ naming the `after` parameter and how to recover. `ablo logs` carried the mirror
618
+ of the same bug: it read the cursor with `Number(...)`, which is harmless while a
619
+ cursor is a bare delta id and silently wrong the moment it has two parts —
620
+ `Number('42.10')` is `42.1`, which re-serialises as `'42.1'` and resumes eight
621
+ claim positions late. The cursor is now a string end to end, passed back exactly
622
+ as it was issued.
623
+
624
+ - 6be1d99: `legacyCompatibleCommitReceiptSchema` is removed from `@abloatai/ablo/wire`. The WebSocket client
625
+ now parses a commit acknowledgement with `commitReceiptSchema`, the canonical one, directly.
626
+
627
+ It was a compatibility decoder rather than a contract: a `z.preprocess` step that supplied fields
628
+ older servers omitted and coerced their string `lastSyncId` into a number before handing the result
629
+ to the canonical schema, which did the real work. It was careful in one respect worth recording —
630
+ it never invented a source correlation, so an old or malformed queued receipt still failed closed
631
+ rather than being promoted to something the server had not said.
632
+
633
+ The servers it decoded for predate the commit contract, and the hosted engine has not spoken that
634
+ shape in some time. Keeping the wrapper meant every receipt on the socket path went through a
635
+ normalizer for a case that no longer arrives, and it left two spellings of "a receipt" on a
636
+ published surface whose entire purpose is to have one.
637
+
638
+ This matters only if you run a self-hosted engine older than the commit contract: its
639
+ acknowledgements will now fail to parse, and the write is reported as `commit_no_result` — pending
640
+ and safe to retry — rather than being silently normalized. Upgrade the engine. If you imported the
641
+ schema to parse receipts yourself, `commitReceiptSchema` is the replacement and always was the one
642
+ doing the parsing.
643
+
644
+ ### Patch Changes
645
+
646
+ - 320b8d7: `ablo login`, `ablo mode`, and `ablo status` now say what the key in your hand
647
+ can do, and `status` labels the plane your credential reaches as `acts on`
648
+ rather than `env`.
649
+
650
+ Logging in provisions a pair — a secret sandbox key and a restricted production
651
+ one — so that a stolen CLI config cannot deploy to production. That protection
652
+ was deliberate and it was also silent. `login` confirmed the sandbox and moved
653
+ on, `mode production` printed a checkmark, and `status` showed the production
654
+ key as twelve characters of prefix. Nothing said the key could not push, so the
655
+ first notice was a 403 in the middle of a deploy, at the moment it cost the
656
+ most to learn.
657
+
658
+ Each of those three commands now names the capability at the point it hands you
659
+ the credential: the key rows in `status` carry what each key does beside how
660
+ long it lasts, the line under `push` says which secret key deploys, and
661
+ `ablo status --json` reports `effectiveKey.kind` so a pipeline can check before
662
+ it pushes instead of after it fails. The wording has one definition site, so the
663
+ command that stores a key, the command that switches to it, and the command
664
+ that reports it cannot describe it three different ways.
665
+
666
+ The renamed `status` line is the same fix in smaller form. `mode` and `env`
667
+ printed as peers and were not: one is the environment you selected, the other is
668
+ the one your credential actually reaches, and when they diverged neither word
669
+ said which was which. `acts on` states the fact. The `--json` output is
670
+ unchanged apart from the added `kind`.
671
+
672
+ ## 0.34.1
673
+
674
+ ### Patch Changes
675
+
676
+ Connecting a database whose tables are owned by an earlier integration's role now just works, with no manual step. When `ablo connect --apply` publishes your tables and grants the writer role access to them, Postgres reserves both operations for each table's owner — so if you reach that owner only through a membership that doesn't inherit its privileges, which is the ordinary shape on managed Postgres where a top role administers everything else without holding superuser, the run would otherwise stop partway through. Apply now clears that itself. It grants your admin inheritance of the owning role as the first step of the plan — shown in the preview and covered by the same confirmation you already give, `GRANT <owner> TO <your-admin> WITH INHERIT TRUE`, the per-membership inheritance Postgres 16 introduced — so the admin acts with the owner's authority for the rest of the setup, with no ownership change and reversible by `WITH INHERIT FALSE`. You approve the plan; you never run a line of SQL. The previous release had only detected the situation and suggested reassigning the table's ownership, which is itself reserved for the current owner and so couldn't run from where you stood; apply now does the one thing that can, and does it for you. Only when your admin genuinely can't take that authority — when it isn't a member with admin option of the owning role — does apply stop, and then it names the exact grant an authorized role must run, or the drop for the idempotency ledger. The same handling covers that ledger, which had kept an older, stricter ownership test that could turn away an admin who in fact inherited the owner.
677
+
678
+ The line printed once a database registers has been rewritten to say what happened and what comes next — that your database is connected, that reads follow its replication stream while writes go through Ablo and land in your own tables, and that `ablo connect check` verifies the connection at any time — in place of the internal shorthand it printed before.
679
+
680
+ A new page, **Operating on Your Database**, sets out the safety model for working on a live database through Ablo: which actions run freely because they are read-only or reversible, which to verify against the database first, and which change the database itself and belong to a human — written so a person or an agent can sort any action into the right one and stop guessing which move is the dangerous one.
681
+
3
682
  ## 0.34.0
4
683
 
5
684
  ### Minor Changes
6
685
 
7
686
  A long-running actor has a stale-context problem the per-commit read gate never reaches. The `reads` guard is a premise for the commit in hand: you declare what you looked at, the server checks it at commit, and the premise is gone. That fits an actor that reads and writes in one breath, not one that reads a row, works for minutes — an LLM call, a fetch, a human's turn — and only then writes. By the time it commits, the premise it would have declared is already old, and there was no commit in between on which to hear that the ground had shifted. This release adds `track`, the durable half of the same idea. `ablo.<model>.track({ id })` registers a read-dependency that persists on the server; the next time you commit anything, a change that landed on the tracked row since you registered rides back on the receipt's `notifications` — the same `StaleNotification` an `onStale: 'notify'` premise hands you, arriving on the write you were going to make anyway. You can also register one as part of a write, `track: [{ group: 'deck:abc' }]` alongside the batch, the standing-subscription companion to the single-commit `reads`. A track is idempotent — registering the same target again refreshes the one subscription rather than stacking duplicates — it re-baselines after it fires so a given change notifies once, and it never notifies you of your own writes, since the signal is about what others did. Delivery is on your next commit's receipt; a track does not yet push out of band between commits, so it sharpens the write-time freshness check rather than replacing a live subscription.
8
687
 
9
- 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.
688
+ The model-level presence verb is renamed from `watch` to `join`. It read like a data subscription but delivered presence — who else is on a set of rows and what they hold — so it now says what it does. `ablo.<model>.join(ids, { ttl })` opens the participant handle, with `.peers`, the scoped claim stream, and `await using` disposal unchanged; the handle's `status` was already `'joined'` and the layer beneath always called itself join, so the verb now matches the thing it returns. `onChange` remains the way to hear a row's _values_ change, and `track` is the durable read-dependency for actors — three distinct jobs that the one overloaded `watch` used to blur. The React hook follows: `useWatch` becomes `useJoin`, the `WatchOptions` / `UseWatchOptions` / `UseWatchReturn` types become `JoinOptions` / `UseJoinOptions` / `UseJoinReturn`, and the error code `model_watch_not_configured` is now `model_join_not_configured`. There is no compatibility alias — rename the call sites and the type imports. The migration guide carries the mechanical diff.
10
689
 
11
690
  ### Patch Changes
12
691
 
@@ -20,9 +699,9 @@ The declarative seam introduced in 0.32.0 for carrying tenant identity into your
20
699
 
21
700
  ```ts
22
701
  defineSchema({
23
- models: { document: { /* … */ } },
702
+ models: { document: {/* … */} },
24
703
  sessionSettings: { 'app.current_org': 'orgId' },
25
- })
704
+ });
26
705
  ```
27
706
 
28
707
  The setting name is the key, so a setting takes exactly one source and a duplicate is unrepresentable rather than something to validate away. If you adopted `tenantContext` in 0.32.0, rename it to `sessionSettings` and turn each `{ guc: 'app.current_org', from: 'orgId' }` into `'app.current_org': 'orgId'`; the exported names followed the rename — `TenantContextMapping` and `TenantContextSource` became `SessionSettings` and `SessionSettingSource`, and `RESERVED_TENANT_CONTEXT_GUCS` became `RESERVED_SESSION_SETTINGS`. The meaning is unchanged: Ablo fills only settings it resolves from your authenticated identity, never from client-supplied data, so a mapping can forward the tenant Ablo already trusts but can never widen a writer's scope, and settings the engine reserves for itself — `row_security`, the timeouts — are still refused at definition time. Schemas pushed before the rename keep parsing.
@@ -49,9 +728,9 @@ The other half of holding one key is trusting that Ablo's writer stays inside th
49
728
 
50
729
  ```ts
51
730
  defineSchema({
52
- models: { document: { /* … */ } },
731
+ models: { document: {/* … */} },
53
732
  tenantContext: [{ guc: 'app.current_org', from: 'orgId' }],
54
- })
733
+ });
55
734
  ```
56
735
 
57
736
  The `from` side is a closed set — `orgId`, `projectId`, `environment`, and the other identifiers Ablo establishes for the write from your credential. It is deliberately not free-form: a mapping can only pass through a value Ablo has already authenticated, never let a caller name its own tenant, so it can narrow what the writer sees but never widen it. Settings Ablo reserves for itself are refused at definition time. Your policies stay the sole authority on what the writer may touch; this only gives them the context they were written to read.