@abloatai/ablo 0.31.0 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/AGENTS.md +3 -3
  2. package/CHANGELOG.md +27 -0
  3. package/README.md +1 -1
  4. package/dist/cli.cjs +1607 -1212
  5. package/dist/coordination/schema.d.ts +4 -2
  6. package/dist/coordination/schema.js +23 -9
  7. package/dist/errorCodes.d.ts +1 -0
  8. package/dist/errorCodes.js +2 -1
  9. package/dist/schema/index.d.ts +1 -1
  10. package/dist/schema/index.js +1 -1
  11. package/dist/schema/schema.d.ts +57 -0
  12. package/dist/schema/schema.js +53 -0
  13. package/dist/schema/select.js +1 -0
  14. package/dist/schema/serialize.d.ts +3 -1
  15. package/dist/schema/serialize.js +7 -1
  16. package/dist/source/adapters/drizzle.js +4 -3
  17. package/dist/source/adapters/kysely.js +4 -4
  18. package/dist/source/adapters/prisma.js +3 -3
  19. package/dist/source/idempotency.d.ts +19 -4
  20. package/dist/source/idempotency.js +19 -4
  21. package/dist/source/migrations.d.ts +4 -1
  22. package/dist/source/migrations.js +36 -5
  23. package/docs/agent-messaging.md +1 -2
  24. package/docs/api.md +4 -4
  25. package/docs/audit.md +35 -23
  26. package/docs/client-behavior.md +7 -4
  27. package/docs/concurrency-convention.md +3 -3
  28. package/docs/data-sources.md +34 -32
  29. package/docs/examples/agent-human.md +2 -2
  30. package/docs/examples/ai-sdk-tool.md +1 -1
  31. package/docs/examples/nextjs.md +1 -1
  32. package/docs/examples/server-agent.md +2 -2
  33. package/docs/groups.md +160 -0
  34. package/docs/guarantees.md +9 -7
  35. package/docs/how-it-works.md +110 -0
  36. package/docs/integration-guide.md +2 -2
  37. package/docs/interaction-model.md +3 -3
  38. package/docs/quickstart.md +6 -6
  39. package/docs/webhooks.md +23 -22
  40. package/llms.txt +10 -4
  41. package/package.json +1 -1
package/AGENTS.md CHANGED
@@ -10,7 +10,7 @@ Don't hand-write the integration. Run the CLI; it generates the current-API sche
10
10
 
11
11
  - **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`.)
12
12
  - **Auth:** set `ABLO_API_KEY` in the environment. Do **NOT** run `ablo login` — it opens a browser device flow and blocks an agent.
13
- - **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.)
13
+ - **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
14
  - **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.
15
15
  - **No database yet?** A sandbox `sk_test` key holds throwaway **test data** (Stripe-test-mode style) so you can try Ablo before connecting your own Postgres. Test-mode only — in production every row lives in your database.
16
16
  - **Adopt an existing DB schema:** `npx ablo pull prisma [path]` / `pull drizzle <module>` (lossless) or `pull` (live DB, lossy). Writes `ablo/schema.ts`.
@@ -33,7 +33,7 @@ Every model verb takes ONE options object. The common loop:
33
33
 
34
34
  1. **Read** the row — `await ablo.<model>.retrieve({ id })` (async; from the server) or `await ablo.<model>.list({ where })` for many. In React render, read synchronously with `useAblo((a) => a.<model>.get(id))`.
35
35
  2. **See who's active** (optional) — `ablo.<model>.claim.state({ id })` (synchronous; never blocks).
36
- 3. **Claim** the row before changing it — `await using claim = await ablo.<model>.claim({ id, reason?, ttl? })`. If someone else holds it, this waits for them, then gives you the fresh row on `claim.data`. The claim auto-releases when it goes out of scope (`await using`).
36
+ 3. **Claim** the row before changing it — `await using claim = await ablo.<model>.claim({ id, description?, ttl? })`. If someone else holds it, this waits for them, then gives you the fresh row on `claim.data`. The claim auto-releases when it goes out of scope (`await using`).
37
37
  4. **Write** — `await ablo.<model>.update({ id: claim.data.id, data })`. Because you hold the claim, the write is rejected if the row changed underneath you.
38
38
 
39
39
  Keep coding assistants on this schema-backed path.
@@ -61,7 +61,7 @@ if (!report) throw new Error('Report not found');
61
61
  // row before resolving. Auto-released at the end of this scope (`await using`).
62
62
  await using claim = await ablo.weatherReports.claim({
63
63
  id: 'report_stockholm',
64
- reason: 'forecasting',
64
+ description: 'forecasting',
65
65
  ttl: '2m',
66
66
  });
67
67
  const claimed = claim.data;
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.32.0
4
+
5
+ ### Minor Changes
6
+
7
+ 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.
8
+
9
+ 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.
10
+
11
+ 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`.
12
+
13
+ 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:
14
+
15
+ ```ts
16
+ defineSchema({
17
+ models: { document: { /* … */ } },
18
+ tenantContext: [{ guc: 'app.current_org', from: 'orgId' }],
19
+ })
20
+ ```
21
+
22
+ 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.
23
+
24
+ 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.
25
+
26
+ 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.
27
+
28
+ 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.
29
+
3
30
  ## 0.31.0
4
31
 
5
32
  ### Minor Changes
package/README.md CHANGED
@@ -465,7 +465,7 @@ connects:
465
465
 
466
466
  | | How Ablo connects to your Postgres | Use when |
467
467
  | --- | --- | --- |
468
- | **`ablo connect`** (primary) | Sets up logical replication and a scoped writer role (`npx ablo connect --apply` does it end to end). Ablo writes your rows through the writer role and reads them back over the WAL to confirm — it writes rows but runs no DDL and owns no schema. | Your database can grant a `REPLICATION` role (most can). |
468
+ | **`ablo connect`** (primary) | Sets up logical replication and a scoped writer role (`npx ablo connect apply` does it end to end). Ablo writes your rows through the writer role and reads them back over the WAL to confirm — it writes rows but runs no DDL and owns no schema. | Your database can grant a `REPLICATION` role (most can). |
469
469
  | **Signed endpoint** (fallback) | Your app exposes one route built from an ORM adapter (`prismaDataSource` / `drizzleDataSource`); Ablo writes and confirms through it. Needs no replication setup. | Your database **can't** grant a replication role (a locked-down managed DB). |
470
470
 
471
471
  Your database is the system of record. See