@abloatai/ablo 0.47.0 → 0.49.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 CHANGED
@@ -11,7 +11,7 @@ Don't hand-write the integration. Run the CLI; it generates the current-API sche
11
11
  - **Read the docs for THIS version:** `npx ablo docs` lists every page, `npx ablo docs <page>` prints one. They ship inside the installed package, so they describe the code in `node_modules` and work with no network. Read them instead of a docs URL — a website describes the newest release, so against a pinned version it will hand you a call your package doesn't have (`retrieve`/`list` replaced `get`/`getAll`/`getCount` in 0.35.0).
12
12
  - **Scaffold:** `npx ablo init --yes` — flag-driven, never prompts. Override defaults with `--framework <nextjs|vite|remix|vanilla>`, `--auth <apikey|…>`, `--no-agent`, `--no-pull`, `--no-install`, `--no-login`. (Plain `ablo init` needs a TTY and will **HANG** in an agent/CI run — always pass `--yes`.)
13
13
  - **Auth:** set `ABLO_API_KEY` in the environment. Do **NOT** run `ablo login` — it opens a browser device flow and blocks an agent.
14
- - **Connect your database — logical replication (the primary path):** `npx ablo connect` prints the setup SQL (`wal_level=logical`, a publication, a `REPLICATION` role); `npx ablo connect register` registers the source with Ablo in one step. Ablo **consumes your Postgres' logical-replication stream** it never runs DDL on, writes to, owns, or migrates your database, and your application keeps the write path. Registration **is** the enable; there is no tier or flag to pick. (Ablo hosts only the transaction log + coordination, never your rows.)
14
+ - **Connect your database — logical replication (the primary path):** `npx ablo connect apply --url <postgres-url>` provisions scoped roles and replication using the admin credential supplied for that command. Model writes then go through Ablo and land in your Postgres; its change stream confirms them. Ablo does not own your rows or run application-schema migrations. Your ORM remains responsible for tables, columns, and constraints.
15
15
  - **Fallback — signed Data Source endpoint** (DB can't grant a `REPLICATION` role): the generated `ablo/data-source.ts` exposes one route; Ablo sends signed requests and your app touches its own DB. **Only in this mode** does `npx ablo migrate` provision the adapter's bookkeeping tables (`ablo_outbox`, `ablo_idempotency`) plus your Ablo models — it does **not** touch your other tables. Keep your own migrations (drizzle-kit / prisma migrate) for auth and anything outside the Ablo schema.
16
16
  - **No database yet?** Run `npx ablo dev --no-watch --branch <name>` to create an isolated non-root branch and obtain its expiring `sk_` credential. The branch uses a throwaway hosted data plane; Production remains the protected root. There is no shared Sandbox mode.
17
17
  - **Adopt an existing DB schema:** `npx ablo pull prisma [path]` / `pull drizzle <module>` (lossless) or `pull` (live DB, lossy). Writes `ablo/schema.ts`.
@@ -21,7 +21,7 @@ Don't hand-write the integration. Run the CLI; it generates the current-API sche
21
21
 
22
22
  When you use the signed-endpoint fallback, the generated `ablo/data-source.ts` is the whole endpoint and needs no hand-editing: `dataSourceNext({ schema, apiKey, adapter: prismaDataSource(prisma, schema) })` (or `drizzleDataSource(db, schema)`). The adapter owns commit / idempotency / outbox.
23
23
 
24
- **Working on a real database?** Ablo never runs DDL, migrates, or drops on your database, and every model write is claim-checked and reversible in the log so reads and `ablo.<model>.update(...)` are yours to run freely, while raw DDL (`ALTER TABLE …`) and a `--yes` cutover belong to a human. When you're unsure whether a write fits, `npx ablo check` reports the live column-by-column fit read-only, before anything runs. Full sorting rule: [Operating on Your Database](./docs/operating-on-your-database.md).
24
+ **Working on a real database?** Plain model writes are last-write-wins when no active claim applies. Use a functional update, a held claim, or `readAt` when a result depends on an earlier value. Reads are safe to inspect; raw application DDL (`ALTER TABLE …`) and a `--yes` connection cutover belong to a human. When you're unsure whether a write fits, `npx ablo check` reports the live column-by-column fit read-only, before anything runs. Full sorting rule: [Operating on Your Database](./docs/operating-on-your-database.md).
25
25
 
26
26
  ## Rule
27
27
 
package/CHANGELOG.md CHANGED
@@ -1,16 +1,235 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.49.0
4
+
5
+ ### An agent can tell Ablo what it read before it writes
6
+
7
+ An agent reads a row, spends a model call deciding what to do, and then writes.
8
+ Another agent can change that row while the model is still thinking, and the
9
+ write lands anyway, on top of a decision that is no longer true. Pass the rows
10
+ the decision was based on:
11
+
12
+ ```ts
13
+ const task = await ablo.tasks.get({ id: taskId });
14
+ await ablo.tasks.update({
15
+ id: task.id,
16
+ data: { status: 'done', result: `Completed: ${task.title}` },
17
+ reads: [task],
18
+ });
19
+ ```
20
+
21
+ If either row moved while the agent was thinking, the write is refused instead
22
+ of overwriting. The rows carry that evidence themselves, so there is nothing to
23
+ set up around your agent and no wrapper to run it inside. One row or several,
24
+ the same row you are writing or a different one, all use `reads`. Rows an agent
25
+ read without passing stay out of it, so `reads` says what the decision rested on
26
+ rather than everything the agent happened to look at.
27
+
28
+ `idempotencyKey` stays a separate option. It gives a write one stable identity if
29
+ the agent retries, which is a different question from what the write assumed.
30
+
31
+ ### A claim holds while an agent thinks
32
+
33
+ Agents that take minutes per turn can now hold work safely. A claim waits its
34
+ turn or skips, expires on its own, and keeps itself alive with a heartbeat while
35
+ the agent works.
36
+
37
+ If an agent loses its claim during a model call, its final write is refused. Two
38
+ agents cannot both believe they own the same task and both write, and a slow
39
+ agent cannot land its answer on top of whoever picked the work up after it. Ablo
40
+ decides who holds the claim, so an agent cannot assert one it does not have.
41
+
42
+ ### An agent can read back what it committed
43
+
44
+ Ask what happened to a write, using the same key the agent wrote with:
45
+
46
+ ```ts
47
+ const record = await ablo.commits.get({ id: commitId });
48
+ ```
49
+
50
+ The answer says who committed, what they intended, whether it is confirmed, and
51
+ which claim protected it. `commits.list` walks the history a page at a time, so
52
+ one agent can review what another already did before repeating it.
53
+
54
+ What an agent sent is not kept. Prompts, reasoning, and your customers' row
55
+ values are removed before the record is stored, so reading history back never
56
+ replays an agent's inputs. Records are kept for 90 days, and permanently for a
57
+ database you connected.
58
+
59
+ ### An agent can check what its key allows before it acts
60
+
61
+ An agent holding a key can now ask what that key permits and get the answer from
62
+ Ablo, whether it keeps a connection open or calls over HTTP for a single turn.
63
+ An agent that mints a narrower key for a sub-task can confirm what it handed
64
+ over.
65
+
66
+ ### A refused action says which permission was missing
67
+
68
+ When Ablo refuses, the error names the permission the agent needed. An agent can
69
+ report exactly what it lacked, or request it, instead of retrying a call that
70
+ will never succeed.
71
+
72
+ ### Models named in camelCase resolve when writing to your own database
73
+
74
+ A model whose key mixes capital letters did not match its declared name when the
75
+ write went to your database directly, so those writes could not find their
76
+ target. They resolve now.
77
+
78
+ ### `ablo connect apply` says when a database is already connected
79
+
80
+ Connecting a database that another project already owns reported a missing table
81
+ mapping, which described a symptom rather than the reason the command could not
82
+ continue. It now says the database is already connected. Nothing is written
83
+ while it checks, and a project with no models still gets its preflight.
84
+
85
+ **Action required.** Install this version rather than a tarball or a Git
86
+ dependency. This release pairs the SDK with the engine running behind
87
+ `api.abloatai.com`, which is already serving it, so there is nothing to
88
+ coordinate on your side.
89
+
90
+ ## 0.48.0
91
+
92
+ ### A branch is unbound until you connect a database to it
93
+
94
+ A branch keeps its own storage, and it does not quietly get Ablo's. Until a
95
+ database is connected to that branch, a request needing one fails with
96
+ `no_data_source_registered` and says what to do:
97
+
98
+ ```
99
+ This branch is not connected to your database yet.
100
+ Run `ablo connect` for this branch, then retry.
101
+ ```
102
+
103
+ The old `test_database_not_registered` is gone. It described a sandbox that no
104
+ longer exists, it arrived on requests that had nothing to do with a test
105
+ database, and its advice pointed at options the SDK had already removed. A
106
+ branch created by `ablo dev` now reports its state plainly rather than looking
107
+ ready and then refusing the first schema push.
108
+
109
+ **Action required.** Replace any handler matching `test_database_not_registered`
110
+ with `no_data_source_registered`. The new code carries the same 4xx meaning and
111
+ a recovery path a caller can act on.
112
+
113
+ ### Failures say which branch they happened on
114
+
115
+ An unconnected branch previously returned its refusal with nothing written
116
+ server-side, so a support question about one customer's branch could not be
117
+ answered from logs at all. These now carry the request, organization, project,
118
+ branch, key kind and storage state, indexed in Sentry, so a failure can be
119
+ looked up by branch instead of reconstructed from database tables.
120
+
121
+ ### Replication uses your branch's own publication and slot
122
+
123
+ Registration, validation, writer checks, replication, drift detection and
124
+ `ablo connect` all require the branch-scoped names recorded when the source was
125
+ registered. Nothing falls back to a shared `ablo_publication` or `ablo_slot`
126
+ any more, so two branches on one database can never quietly share a stream.
127
+ `ablo connect scan` reports legacy unsuffixed objects as retired.
128
+
129
+ Once your engine is on this release and `ablo connect check` is clean, the
130
+ temporary alias can go:
131
+
132
+ ```sql
133
+ DROP PUBLICATION IF EXISTS "ablo_publication";
134
+ ```
135
+
136
+ ### `ablo dev --local` connects a branch to the database on your machine
137
+
138
+ The rule above raises a fair question: if a branch is unbound until a database
139
+ is connected, what connects one during development? `--local` does.
140
+
141
+ ```bash
142
+ npx ablo dev --local
143
+ ```
144
+
145
+ It registers a connector-only endpoint for that exact branch and opens a
146
+ long-lived secure connector. Your database stays where it is: Ablo receives an
147
+ endpoint descriptor and a signing key, never a connection string, and reaches
148
+ your source back through the connector rather than dialling it. `DATABASE_URL`
149
+ is read from `.env.local` into the handler running on your machine and goes no
150
+ further.
151
+
152
+ The branch is then connected like any other, so schema pushes, reads and writes
153
+ behave the way they will in production. Because the connector is long-lived,
154
+ `--local` cannot be combined with `--no-watch`.
155
+
156
+ ### Renamed
157
+
158
+ `FootprintPlane` is now `DataSourceIdentity`, with the same three fields. The
159
+ old name described an internal layout; the new one describes what it
160
+ identifies.
161
+
3
162
  ## 0.47.0
4
163
 
5
- ### Minor Changes
164
+ ### Local Postgres works with Ablo Cloud
165
+
166
+ Run `npx ablo dev --local` to serve the generated signed Data Source handler
167
+ over an outbound, protocol-scoped connector. Postgres remains private on the
168
+ developer's machine and its connection string never leaves the app process.
169
+ The Data Source guide now explains exactly which writes are visible without
170
+ WAL, and the public error reference includes actionable `source_connector_*`
171
+ codes for every connector lifecycle failure.
172
+
173
+ ### Awaiting a model write now means it is confirmed
174
+
175
+ `create`, `update`, and `delete` change local reactive state immediately and
176
+ return a promise with a single meaning: the write reached authoritative
177
+ confirmation. An interface stays responsive without awaiting anything, and code
178
+ that needs to know a write survived can await the same call it already makes.
179
+
180
+ The `wait` option is gone from the client and from individual model calls.
181
+ Awaiting a model write always waits for confirmation, so there is nothing left
182
+ to configure. Explicit control over a queued versus confirmed receipt remains on
183
+ `commits.create`, which still hands back the receipt and its confirmation
184
+ separately.
185
+
186
+ **Action required.** Remove `wait` from `Ablo({ ... })` and from every
187
+ `create`, `update`, and `delete` call.
188
+
189
+ - `wait: 'confirmed'` behaves identically once removed.
190
+ - `wait: 'queued'` on a call you never awaited behaves identically once removed.
191
+ - `wait: 'queued'` on a call you did await now waits for confirmation. Move to
192
+ `commits.create` if the queued receipt was the reason for the option.
193
+
194
+ ### Customer branches connect before accepting a schema
195
+
196
+ A customer branch now remains in provisioning until it has an active Data
197
+ Source. Ablo does not invent internal storage for customer data: it reads the
198
+ customer's database through WAL and writes through the separately scoped DML
199
+ credential (or uses the explicitly registered signed endpoint fallback).
200
+
201
+ Database validation now uses the branch-scoped publication and replication slot
202
+ persisted with that Data Source. It no longer falls back to the shared
203
+ `ablo_publication` / `ablo_slot` names, so `ablo connect check` validates the
204
+ same objects that `ablo connect apply` created.
205
+
206
+ The sandbox-only `test_database_not_registered` error has been removed. An
207
+ unconnected customer branch now consistently returns `no_data_source_registered`
208
+ with the `ablo connect` recovery step.
209
+
210
+ **Action required for type imports.** `FootprintPlane` has been removed. Import
211
+ `DataSourceIdentity` from `@abloatai/ablo/source` instead; its fields remain
212
+ `organizationId`, optional `projectId`, and `branchId`.
213
+
214
+ ### The CLI names the problem it actually hit
215
+
216
+ A refused push no longer reports every failure as a missing `schema:push`
217
+ capability. That advice was wrong for most refusals: a database privilege error,
218
+ a row-level security misconfiguration, and an unregistered development database
219
+ each need a different fix, and none of them is a different API key. Each now
220
+ leads with the server's own message and the remedy for that specific cause.
6
221
 
7
- - 101ca2c: Make schema model writes optimistic with one stable promise contract: local reactive state changes immediately, while awaiting `create`, `update`, or `delete` always waits for authoritative confirmation. Remove the model-level and client-level `wait` options; explicit queued-versus-confirmed receipt control remains on `commits.create`.
222
+ Project names also resolve correctly under a branch-bound key. Listing projects
223
+ is a management operation that such a key is deliberately not allowed to
224
+ perform, so `ablo status` and `ablo push` reported a correctly minted key's
225
+ project as `unnamed` alongside a permission error. The name now comes from the
226
+ stored management credential.
8
227
 
9
- ### Patch Changes
228
+ ### Deprecations
10
229
 
11
- - Updated dependencies [101ca2c]
12
- - @abloatai/transaction@0.47.0
13
- - @abloatai/humans@0.47.0
230
+ `METER_EVENT_COUNTS` is deprecated in favour of its per-surface members, and the
231
+ `DatasourceResnapshotResponse` type and its schema are deprecated. All three
232
+ still ship and still work; they will be removed in a later release.
14
233
 
15
234
  ## 0.46.0
16
235
 
@@ -321,7 +540,7 @@ through the same transaction API as every other caller.
321
540
 
322
541
  The integrations keep each product in its proper role: Temporal and Inngest
323
542
  own durable execution, scheduling, retries, and workflow history; Ablo owns
324
- shared-data authority, claims, conflicts, idempotency, settlement, and ordered
543
+ shared-data authority, claims, conflicts, idempotency, confirmation, and ordered
325
544
  observation. Workflow code does not open WebSockets or hold live client state.
326
545
 
327
546
  ### Database adapter foundation, starting with PostgreSQL
@@ -425,7 +644,7 @@ Install `@abloatai/ablo` as the single public SDK:
425
644
  - `@abloatai/ablo/react` provides the React bindings.
426
645
 
427
646
  Every entrypoint uses the same schema, capabilities, commits, claims,
428
- idempotency, settlement, and ordered changes. Authoritative reads use
647
+ idempotency, confirmation, and ordered changes. Authoritative reads use
429
648
  `model.get({ id })`; local reactive snapshots use `model.local.get(id)`.
430
649
 
431
650
  ### Coordination now matches the unit applications can safely write
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  </p>
4
4
 
5
5
  <p align="center">
6
- <strong>The transaction layer for AI agents.</strong>
6
+ <strong>Collaboration infrastructure for AI agents.</strong>
7
7
  </p>
8
8
 
9
9
  <p align="center">
@@ -22,13 +22,12 @@
22
22
 
23
23
  ---
24
24
 
25
- Safely coordinate AI agents, humans, workflows, and services writing to the
26
- same database.
25
+ Ablo is collaboration infrastructure for AI agents: one API for agents, apps,
26
+ and services to claim, change, and confirm the same rows.
27
27
 
28
- Ablo is an authoritative transaction layer for shared application state. Every
29
- write goes through one typed API where authority, idempotency, conflicts,
30
- ordering, and confirmation can be enforced. Your Postgres remains the source
31
- of truth.
28
+ Every write goes through it, so authority, idempotency, conflicts, ordering,
29
+ and confirmation are enforced in one place. Your Postgres remains the source of
30
+ truth.
32
31
 
33
32
  ## Why Ablo
34
33
 
@@ -54,7 +53,7 @@ npx ablo dev
54
53
  temporary credential to gitignored `.env.local`, pushes the schema, and watches
55
54
  for changes.
56
55
 
57
- Read and write through the transaction layer:
56
+ Read and write through one typed API:
58
57
 
59
58
  ```ts
60
59
  const order = await ablo.orders.get({ id: orderId });
@@ -114,6 +113,22 @@ authority, commits, claims, and ordered changes.
114
113
  Read the [Quickstart](https://docs.abloatai.com/quickstart), browse
115
114
  [docs.abloatai.com](https://docs.abloatai.com), or run `npx ablo docs`.
116
115
 
116
+ ## Navigating the source
117
+
118
+ This repository preserves the package ownership boundaries instead of
119
+ flattening the implementation into `packages/ablo`:
120
+
121
+ - `packages/ablo` is the branded public facade. Its files mostly re-export the
122
+ package that owns each API.
123
+ - `packages/transaction` owns the shared model-operation contracts and the
124
+ stateless HTTP implementation.
125
+ - `packages/humans` owns the reactive WebSocket/local/React implementation.
126
+
127
+ That means searching only inside `packages/ablo/src` will not find the
128
+ implementation of `create`, `update`, `delete`, or `claim`. Read the
129
+ **[source code map](./CODEMAP.md)** for a verb-by-verb ownership table and
130
+ guided call traces for both the default and reactive clients.
131
+
117
132
  ## Contributing
118
133
 
119
134
  Ablo is free and open source. You can help by
@@ -12,7 +12,7 @@ HTTP, or be replayed from the sync cursor.
12
12
  | Need | Use | Why |
13
13
  | --- | --- | --- |
14
14
  | "I am holding this row because..." | `claim({ description, meta })` | Live and low-latency. Peers see it through presence while the claim exists. |
15
- | "Remember this handoff/status/request" | A `messages` model | Durable row. Ordered in `sync_deltas`, replayed after reconnect, readable by HTTP agents. |
15
+ | "Remember this handoff/status/request" | A `messages` model | Durable row, replayed after reconnect and readable by HTTP agents. |
16
16
 
17
17
  Claim context is ephemeral. If a participant was offline, reconnected later, or
18
18
  only uses `transport: "http"`, it can miss claim/presence frames. Message rows
@@ -141,6 +141,6 @@ their cursor and see the rows they missed while offline.
141
141
  ## Retention
142
142
 
143
143
  Deleting or archiving old `messages` rows is your app's policy. The sync log is
144
- still durable audit/history: `sync_deltas` has no message-specific TTL. That is
144
+ still durable audit/history: messages have no message-specific TTL. That is
145
145
  useful for coordination and compliance, but a chat-scale product should plan
146
146
  retention before writing high-volume conversation traffic.
package/docs/api.md CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  > The per-method reference for every model call an agent or an interface can make.
4
4
 
5
- > **Upgrading?** Every breaking change and its migration is on the
6
- > [Version History & Migration Guide](./migration.md).
5
+ > **Upgrading?** Follow the version-matched workflow in the
6
+ > [Upgrade Guide](./migration.md), then read the intervening changelog entries.
7
7
 
8
8
  This is the per-method reference for reading and writing rows that stay in
9
9
  sync across sessions. You declare your models once, then call the same
package/docs/cli.md CHANGED
@@ -122,7 +122,7 @@ bypasses profiles for project/branch administration; the runtime key remains
122
122
  | `ablo init` | Scaffold `ablo/` (`schema.ts`, client, optional Data Source / agent / component), write `.env`, install the SDK. Offers to log in at the end. |: |
123
123
  | `ablo login` / `logout` / `whoami` / `status` | Authentication, exact credential identity, and readiness (above). | `whoami --key-env <NAME>`, `whoami --json`, `status --json` |
124
124
  | `ablo projects list\|create\|use\|rename` | Manage projects and the active one (see [Projects](#projects)). Each project's keys/schema/data are isolated. | `--name "<display>"` (create/rename) |
125
- | `ablo dev` | **Hosted**: ensure an isolated Git branch, wire its temporary key, push, then watch `ablo/schema.ts`. | `--branch <slug>`, `--branch-ttl-hours <1-168>`, `--no-watch`, `--schema`, `--export`, `--url` |
125
+ | `ablo dev` | Ensure an isolated Git branch, wire its temporary key, push, then watch `ablo/schema.ts`. `--local` also serves local Postgres over an outbound signed connector. | `--branch <slug>`, `--branch-ttl-hours <1-168>`, `--local`, `--source <path>`, `--no-watch`, `--schema`, `--export`, `--url` |
126
126
  | `ablo branch list\|status\|check\|create\|ensure\|credential\|delete` | Manage and diagnose immutable branch planes and expiring credentials. | Run `ablo branch --help`; use `--json` for automation. |
127
127
  | `ablo logs` | Tail the resolved runtime credential's branch activity. Follows by default. | `-n, --tail <N>`, `--since <dur\|ts>`, `--model`, `--op`, `--json`, `--no-follow` |
128
128
  | `ablo push` | **Hosted**: upload the schema to Ablo; the server diffs, migrates, and activates it. | `--force`, `--rename old:new`, `--backfill model.field=value`, `--schema`, `--export`, `--url` |
@@ -166,8 +166,16 @@ npx ablo dev # discover from Git, push + watch
166
166
  npx ablo dev --branch preview-pr-482 # explicit branch
167
167
  npx ablo dev --no-watch # prepare, push once, exit
168
168
  npx ablo dev --branch-ttl-hours 24 # change temporary-key lifetime
169
+ npx ablo dev --local # keep Postgres private on localhost
169
170
  ```
170
171
 
172
+ `--local` loads `ablo/data-source.ts` (override with `--source <path>`), registers
173
+ the branch as connector-only, and opens an outbound authenticated WebSocket to
174
+ Ablo. Commit, load, list, and outbox-event requests run through the same signed
175
+ Data Source handler as production; no database credential leaves your process
176
+ and no public tunnel is opened. Because the connector is long-lived, `--local`
177
+ cannot be combined with `--no-watch`.
178
+
171
179
  It does not start your app, run migrations, create a database-provider branch,
172
180
  or copy production rows. Read [Branch-first development](./branch-development.md)
173
181
  for the exact discovery order, CI flow, database boundary, and troubleshooting.