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