@abloatai/ablo 0.56.0 → 0.58.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 (82) hide show
  1. package/AGENTS.md +10 -4
  2. package/CHANGELOG.md +428 -10
  3. package/LICENSE +1 -1
  4. package/NOTICE +3 -3
  5. package/README.md +2 -1
  6. package/dist/ai-sdk.d.ts +1 -1
  7. package/dist/ai-sdk.d.ts.map +1 -1
  8. package/dist/context/evidence.d.ts +6 -8
  9. package/dist/context/evidence.d.ts.map +1 -1
  10. package/dist/context/evidence.js +6 -20
  11. package/dist/context/evidence.js.map +1 -1
  12. package/dist/context/index.d.ts +23 -0
  13. package/dist/context/index.d.ts.map +1 -0
  14. package/dist/context/index.js +26 -0
  15. package/dist/context/index.js.map +1 -0
  16. package/dist/context/onChange.d.ts +9 -0
  17. package/dist/context/onChange.d.ts.map +1 -0
  18. package/dist/context/onChange.js +37 -0
  19. package/dist/context/onChange.js.map +1 -0
  20. package/dist/source-conformance.d.ts +1 -1
  21. package/dist/source-conformance.d.ts.map +1 -1
  22. package/dist/source-conformance.js +1 -1
  23. package/dist/source-conformance.js.map +1 -1
  24. package/dist/source-drizzle.d.ts +1 -1
  25. package/dist/source-drizzle.d.ts.map +1 -1
  26. package/dist/source-drizzle.js +1 -1
  27. package/dist/source-drizzle.js.map +1 -1
  28. package/dist/source-kysely.d.ts +1 -1
  29. package/dist/source-kysely.d.ts.map +1 -1
  30. package/dist/source-kysely.js +1 -1
  31. package/dist/source-kysely.js.map +1 -1
  32. package/dist/source-next.d.ts +1 -1
  33. package/dist/source-next.d.ts.map +1 -1
  34. package/dist/source-next.js +1 -1
  35. package/dist/source-next.js.map +1 -1
  36. package/docs/agent-integration-decision-guide.md +123 -0
  37. package/docs/agents.md +74 -13
  38. package/docs/api-keys.md +6 -6
  39. package/docs/api.md +117 -43
  40. package/docs/branch-development.md +23 -4
  41. package/docs/cli.md +16 -9
  42. package/docs/client-behavior.md +21 -15
  43. package/docs/concurrency-convention.md +67 -77
  44. package/docs/context.md +56 -31
  45. package/docs/coordination.md +115 -36
  46. package/docs/customer-organizations.md +49 -31
  47. package/docs/data-sources.md +12 -6
  48. package/docs/debugging.md +1 -1
  49. package/docs/examples/agent-human.md +6 -18
  50. package/docs/examples/coordination-conformance.md +69 -0
  51. package/docs/examples/existing-document-pipeline.md +488 -0
  52. package/docs/examples/existing-python-backend.md +10 -13
  53. package/docs/examples/nextjs.md +49 -6
  54. package/docs/examples/scoped-agent.md +18 -1
  55. package/docs/examples/server-agent.md +2 -2
  56. package/docs/groups.md +19 -139
  57. package/docs/guarantees.md +5 -6
  58. package/docs/identity.md +2 -1
  59. package/docs/index.md +5 -0
  60. package/docs/integration-guide.md +46 -19
  61. package/docs/integrations/sandbox-runtime.md +148 -0
  62. package/docs/integrations.md +9 -0
  63. package/docs/operating-on-your-database.md +7 -0
  64. package/docs/quickstart.md +19 -13
  65. package/docs/react.md +9 -9
  66. package/docs/schema-contract.md +14 -13
  67. package/docs/session-settings.md +9 -0
  68. package/docs/sessions.md +1 -1
  69. package/examples/README.md +2 -2
  70. package/examples/agent-turn.ts +1 -1
  71. package/examples/data-source/customer-server.ts +12 -5
  72. package/examples/expensive-agent-turn.ts +1 -1
  73. package/llms.txt +72 -10
  74. package/package.json +6 -6
  75. package/dist/context/sources.d.ts +0 -21
  76. package/dist/context/sources.d.ts.map +0 -1
  77. package/dist/context/sources.js +0 -36
  78. package/dist/context/sources.js.map +0 -1
  79. package/dist/context.d.ts +0 -22
  80. package/dist/context.d.ts.map +0 -1
  81. package/dist/context.js +0 -33
  82. package/dist/context.js.map +0 -1
@@ -2,8 +2,21 @@
2
2
 
3
3
  > One account, one schema, and a session scoped to the customer whose data it may read.
4
4
 
5
- Your customers live in your database, in a table you already have. Ablo reaches
6
- one of them by the groups a session is minted with.
5
+ Serving many customers from one backend has two shapes, and the first question
6
+ is whether isolating them is a security boundary or a routing convenience.
7
+
8
+ **One Ablo organization per customer** is the hard boundary. Every row carries
9
+ the organization, and the engine compares it on every read and every write,
10
+ below your code. Choose it when one customer reading another's rows would be an
11
+ incident.
12
+
13
+ **One organization, customers as rows told apart by sync groups** is delivery
14
+ and read routing. It is declarative, it depends on every model being covered,
15
+ and it is not enforced on every path. Choose it when cross-customer reads are
16
+ tolerable or intentional, not when they are a breach.
17
+
18
+ The rest of this page is the second shape. Read *Where the boundary is enforced*
19
+ before you rely on it.
7
20
 
8
21
  ```ts
9
22
  // 1. src/ablo/schema.ts — your customer table is a scope root.
@@ -82,29 +95,33 @@ session adds is which customer the person in front of it may read.
82
95
 
83
96
  ## Where the boundary is enforced
84
97
 
85
- Two mechanisms do different jobs, and it is worth knowing which is which before
86
- you rely on either.
98
+ Two mechanisms do different jobs, and the difference is the whole of this page.
87
99
 
88
100
  **Your account is the tenant boundary.** Every row Ablo stores carries your
89
- organization and project, and row-level security compares both against the
90
- credential on every read and every write. A client cannot reach past it by
91
- asking, because the values come from the key rather than the request.
92
-
93
- **Sync groups are the cut inside your account.** They decide which of your own
94
- rows a session is delivered and which it may read back over HTTP. This is the
95
- boundary between one of your customers and the next, and it is the one your
96
- schema declares.
97
-
98
- Reads go through it. `list` and `get` are cut to the session's groups, live
99
- delivery is cut to the same set, and the initial load is the intersection of
100
- what the session asked for with what it was minted with.
101
-
102
- Coordination reads are cut more coarsely today. Listing claims and reading
103
- presence are scoped to your account and plane rather than to the session's
104
- groups, so one customer's session can see that a row is claimed and who is
105
- present, though never the row's contents. Treat row ids and participant ids on
106
- those two surfaces as visible across your customers until this page says
107
- otherwise.
101
+ organization, project, and branch, and all three are compared on every read and
102
+ every write, from the credential rather than the request. A client cannot reach
103
+ past them by asking. This is the boundary that holds unconditionally.
104
+
105
+ **Sync groups are a cut inside your account, and they are not applied
106
+ everywhere.** They decide which changes are delivered and which rows a
107
+ log-served read returns. That is routing. It is not a universal authorization
108
+ boundary, and the gaps are specific:
109
+
110
+ | Path | Group cut applied |
111
+ |---|---|
112
+ | Live delivery and fan-out | Yes |
113
+ | HTTP read on a log-served plane (a connected database) | Yes |
114
+ | HTTP read on a hosted or direct-query plane | **No.** Scoped by organization |
115
+ | Writes | **No.** The groups are recorded on the change, never checked against the row |
116
+ | Claim listings and presence | Yes |
117
+
118
+ So a session cut to one customer, on a hosted plane, can read another
119
+ customer's rows over HTTP; and on any plane it can write to them. What stops it
120
+ today is the organization, which both customers share under this shape.
121
+
122
+ If isolating your customers is a security requirement, give each one its own
123
+ Ablo organization. The stronger row-and-subject authorization that would make
124
+ this shape safe on every path is not in the engine yet.
108
125
 
109
126
  ## Naming a group
110
127
 
@@ -120,16 +137,17 @@ Resolve `member.customerId` from the membership you just authenticated on the
120
137
  server. A signed-in person can put any value in a request body, and the session
121
138
  you mint is what decides what they can read.
122
139
 
123
- ## When a customer really is an account
140
+ ## When a customer should be its own organization
124
141
 
125
- There is one shape where each of your customers should be its own Ablo
126
- organization: when each is a separate paying business that signs in to Ablo
127
- itself, holds its own subscription, and invites its own developers. That is what
128
- an identity provider looks like, and it is what the `organization:act-as` scope
129
- on a secret key exists for.
142
+ Whenever their isolation has to hold. Give each customer its own Ablo
143
+ organization when one of them reading or writing another's rows would be an
144
+ incident rather than a bug, when you cannot audit group coverage across every
145
+ model, or when a customer is a separate paying business that signs in to Ablo
146
+ itself and invites its own developers.
130
147
 
131
- It is rare, and it is not what a platform serving customers from one product
132
- looks like. If your customers never see Ablo, they belong in your schema.
148
+ Your backend then names the customer's organization on the mint, which takes a
149
+ secret key carrying `organization:act-as`. The customer never sees Ablo; the
150
+ scope exists because the session leaves the organization the key belongs to.
133
151
 
134
152
  ## Onboarding a customer
135
153
 
@@ -110,12 +110,12 @@ migrations — your migration tool stays in charge of the shape of your database
110
110
  Ablo only writes rows into tables you already have, through a role scoped to
111
111
  exactly that.
112
112
 
113
- > **Just trying Ablo?** You don't need a database to start. Pass an `apiKey` only,
114
- > and Ablo keeps your rows in its own log so you can build the whole app today.
115
- > `ablo dev` gives each Git branch its own isolated plane.
116
- > Keep it hosted with no database, or point that branch at a separate/local
117
- > Postgres. Connect your production root (below) when you're ready for its
118
- > database to be the system of record.
113
+ > **Just trying Ablo?** Start on a throwaway Postgres rather than your production
114
+ > one. `ablo dev` gives each Git branch its own isolated plane, so point that
115
+ > branch at a separate or local database, build against it, and connect your
116
+ > production root (below) when you're ready for its database to be the system of
117
+ > record. A branch with nothing connected refuses a schema push, which is the
118
+ > first thing you'll hit if you skip this.
119
119
 
120
120
  Connecting sets up two capabilities on your Postgres: **logical replication**, so
121
121
  Ablo can read and confirm, and a **scoped DML role**, so Ablo can write. `ablo
@@ -475,6 +475,12 @@ directly by other application code is visible only if that code writes the same
475
475
  outbox record in its transaction. Native WAL observation sees both Ablo and
476
476
  external writes.
477
477
 
478
+ Endpoint events use a versioned envelope. Version 2 freezes `syncGroups` in the
479
+ writing transaction; version 1 is retained only to decode events written by an
480
+ older adapter during a rolling upgrade. Poll requests keep `cursor` (where to
481
+ read) separate from `acknowledgedThrough` (what Ablo has durably accepted), and
482
+ the built-in adapters prune acknowledged rows in bounded batches.
483
+
478
484
  ## Next steps
479
485
 
480
486
  - [Quickstart](./quickstart.md) — connect and write through `ablo.<model>`.
package/docs/debugging.md CHANGED
@@ -86,7 +86,7 @@ Read it as the lifecycle of one claim:
86
86
  - **`queued … position N of M`:** the row was held, so you're waiting in the FIFO line. This is the "an agent is waiting behind a claim" moment; it re-logs only when your position changes, so you can watch it advance.
87
87
  - **`granted … your turn`:** you reached the head of the line; the lease is now yours and the row may have changed while you waited.
88
88
  - **`rejected … held by <who>`:** your claim was refused because someone else holds it (and the model's policy didn't let you in).
89
- - **`lost`:** you held the lease and it was taken (preempted by a higher-priority writer, or it expired).
89
+ - **`lost`:** you held the lease and it ended (the queue fairness ceiling advanced, or it expired).
90
90
  - **`released`:** you (or `await using`'s scope exit) gave the lease back.
91
91
 
92
92
  ## Where the logs run
@@ -25,7 +25,7 @@ a typed error if the row moved underneath you while the agent was busy.
25
25
  ## Schema-Backed Worker
26
26
 
27
27
  The worker uses the same schema client the app uses. It reads the record from the
28
- server with `get({ id })`, claims the row, and writes through
28
+ server with `read({ id })`, claims the row, and writes through
29
29
  `ablo.records.update(...)` with a stale-check so a concurrent edit can't be
30
30
  overwritten.
31
31
 
@@ -49,8 +49,8 @@ const ablo = Ablo({
49
49
  export async function markDone(recordId: string) {
50
50
  await ablo.ready();
51
51
 
52
- // get({ id }) is an async server read — await it.
53
- const record = await ablo.records.get({ id: recordId });
52
+ // read({ id }) is an async server read — await it.
53
+ const record = await ablo.records.read({ id: recordId });
54
54
  if (!record) return { status: 'not_found' };
55
55
 
56
56
  try {
@@ -67,21 +67,9 @@ export async function markDone(recordId: string) {
67
67
  await using claim = acquired;
68
68
  if (claim.data.status === 'done') return { status: 'noop' };
69
69
 
70
- // Inside an active claim, `update` is stale-checked automatically: the SDK
71
- // attaches the claim's snapshot version as `readAt` and sets
72
- // `onStale: 'reject'`. The write below is therefore equivalent to passing
73
- // those options yourself:
74
- //
75
- // ablo.records.update({
76
- // id: claim.data.id,
77
- // data: { status: 'done' },
78
- // readAt: <claim snapshot version>,
79
- // onStale: 'reject',
80
- // });
81
- //
82
- // If a newer version landed mid-run, the row no longer matches `readAt`, so
83
- // the server rejects this commit with AbloStaleContextError (caught below)
84
- // instead of clobbering that edit.
70
+ // The claim handle carries its acquisition snapshot. If a newer version
71
+ // somehow lands mid-run, the server rejects this commit with
72
+ // AbloStaleContextError instead of clobbering that edit.
85
73
  const updated = await ablo.records.update({
86
74
  id: claim.data.id,
87
75
  data: { status: 'done' },
@@ -0,0 +1,69 @@
1
+ # Verify hosted coordination separately
2
+
3
+ > Prove claim behavior once, without coupling the proof to a document, workflow, or GraphQL schema.
4
+
5
+ Use two test layers when adopting Ablo behind an existing application:
6
+
7
+ | Proof | Responsibility |
8
+ |---|---|
9
+ | Domain contract | State transitions, stale evidence, provenance, idempotency, and old/new path parity. |
10
+ | Hosted coordination conformance | Participant identity, exclusion, heartbeat, release, and lease-expiry recovery. |
11
+
12
+ The runnable hosted proof is
13
+ [`examples/coordination-conformance`](../../../../examples/coordination-conformance/README.md).
14
+ It creates a temporary test branch that inherits an existing non-production
15
+ schema. It does not push a schema or change domain rows.
16
+
17
+ ## File structure
18
+
19
+ ```text
20
+ src/conformance/index.ts
21
+ -> src/conformance/claimExclusion.ts
22
+ -> src/conformance/contract.ts
23
+ src/runtime/index.ts
24
+ -> src/runtime/client.ts
25
+ -> src/runtime/config.ts
26
+ live/index.ts
27
+ -> live/claimAndExit.ts
28
+ ```
29
+
30
+ The conformance operation depends on a narrow structural claim interface. The
31
+ runtime child supplies the real Ablo model resource. Domain examples depend on
32
+ the same narrow behavior without inheriting this runner's branch or credential
33
+ setup.
34
+
35
+ ## Run
36
+
37
+ First run the deterministic structure and configuration checks:
38
+
39
+ ```bash
40
+ cd examples/coordination-conformance
41
+ npm test
42
+ npm run typecheck
43
+ ```
44
+
45
+ Then name an existing model on the CLI login's dedicated non-production
46
+ project:
47
+
48
+ ```bash
49
+ ABLO_CONFORMANCE_MODEL=existingModel npm run test:live
50
+ ```
51
+
52
+ The model name supplies only a typed claim namespace. Every claim target is a
53
+ new random identifier; the proof creates no model row. The disposable branch
54
+ is deleted even when an assertion fails.
55
+
56
+ The runtime deliberately calls `model.claim(id, options)`. This identifier
57
+ overload is row-free. `model.claim({ id, ...options })` is the row-backed form:
58
+ it reads the model row and is not interchangeable in a coordination-only
59
+ rollout.
60
+
61
+ ## What this lets domain examples omit
62
+
63
+ A document-processing example does not need its own branch provisioning,
64
+ session delegation, heartbeat, or process-death fixture. It must still test its
65
+ own behavior when a claim is won, skipped, released after failure, and combined
66
+ with changing evidence.
67
+
68
+ This separation prevents a vertical testcase from force-replacing an inherited
69
+ schema merely to re-prove generic lease behavior.