@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.
- package/AGENTS.md +10 -4
- package/CHANGELOG.md +428 -10
- package/LICENSE +1 -1
- package/NOTICE +3 -3
- package/README.md +2 -1
- package/dist/ai-sdk.d.ts +1 -1
- package/dist/ai-sdk.d.ts.map +1 -1
- package/dist/context/evidence.d.ts +6 -8
- package/dist/context/evidence.d.ts.map +1 -1
- package/dist/context/evidence.js +6 -20
- package/dist/context/evidence.js.map +1 -1
- package/dist/context/index.d.ts +23 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +26 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context/onChange.d.ts +9 -0
- package/dist/context/onChange.d.ts.map +1 -0
- package/dist/context/onChange.js +37 -0
- package/dist/context/onChange.js.map +1 -0
- package/dist/source-conformance.d.ts +1 -1
- package/dist/source-conformance.d.ts.map +1 -1
- package/dist/source-conformance.js +1 -1
- package/dist/source-conformance.js.map +1 -1
- package/dist/source-drizzle.d.ts +1 -1
- package/dist/source-drizzle.d.ts.map +1 -1
- package/dist/source-drizzle.js +1 -1
- package/dist/source-drizzle.js.map +1 -1
- package/dist/source-kysely.d.ts +1 -1
- package/dist/source-kysely.d.ts.map +1 -1
- package/dist/source-kysely.js +1 -1
- package/dist/source-kysely.js.map +1 -1
- package/dist/source-next.d.ts +1 -1
- package/dist/source-next.d.ts.map +1 -1
- package/dist/source-next.js +1 -1
- package/dist/source-next.js.map +1 -1
- package/docs/agent-integration-decision-guide.md +123 -0
- package/docs/agents.md +74 -13
- package/docs/api-keys.md +6 -6
- package/docs/api.md +117 -43
- package/docs/branch-development.md +23 -4
- package/docs/cli.md +16 -9
- package/docs/client-behavior.md +21 -15
- package/docs/concurrency-convention.md +67 -77
- package/docs/context.md +56 -31
- package/docs/coordination.md +115 -36
- package/docs/customer-organizations.md +49 -31
- package/docs/data-sources.md +12 -6
- package/docs/debugging.md +1 -1
- package/docs/examples/agent-human.md +6 -18
- package/docs/examples/coordination-conformance.md +69 -0
- package/docs/examples/existing-document-pipeline.md +488 -0
- package/docs/examples/existing-python-backend.md +10 -13
- package/docs/examples/nextjs.md +49 -6
- package/docs/examples/scoped-agent.md +18 -1
- package/docs/examples/server-agent.md +2 -2
- package/docs/groups.md +19 -139
- package/docs/guarantees.md +5 -6
- package/docs/identity.md +2 -1
- package/docs/index.md +5 -0
- package/docs/integration-guide.md +46 -19
- package/docs/integrations/sandbox-runtime.md +148 -0
- package/docs/integrations.md +9 -0
- package/docs/operating-on-your-database.md +7 -0
- package/docs/quickstart.md +19 -13
- package/docs/react.md +9 -9
- package/docs/schema-contract.md +14 -13
- package/docs/session-settings.md +9 -0
- package/docs/sessions.md +1 -1
- package/examples/README.md +2 -2
- package/examples/agent-turn.ts +1 -1
- package/examples/data-source/customer-server.ts +12 -5
- package/examples/expensive-agent-turn.ts +1 -1
- package/llms.txt +72 -10
- package/package.json +6 -6
- package/dist/context/sources.d.ts +0 -21
- package/dist/context/sources.d.ts.map +0 -1
- package/dist/context/sources.js +0 -36
- package/dist/context/sources.js.map +0 -1
- package/dist/context.d.ts +0 -22
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js +0 -33
- 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
|
-
|
|
6
|
-
|
|
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
|
|
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
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
**Sync groups are
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
|
140
|
+
## When a customer should be its own organization
|
|
124
141
|
|
|
125
|
-
|
|
126
|
-
organization
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
132
|
-
|
|
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
|
|
package/docs/data-sources.md
CHANGED
|
@@ -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?**
|
|
114
|
-
>
|
|
115
|
-
>
|
|
116
|
-
>
|
|
117
|
-
>
|
|
118
|
-
>
|
|
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
|
|
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 `
|
|
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
|
-
//
|
|
53
|
-
const record = await ablo.records.
|
|
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
|
-
//
|
|
71
|
-
//
|
|
72
|
-
//
|
|
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.
|