@abloatai/ablo 0.37.1 → 0.38.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
@@ -13,10 +13,10 @@ Don't hand-write the integration. Run the CLI; it generates the current-API sche
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
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.)
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
- - **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
+ - **No database yet?** Run `npx ablo dev --no-watch --branch <name>` to create an isolated non-root branch and obtain its expiring `sk_test_` 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`.
18
- - **Push your schema — REQUIRED before any write works.** The server keeps its OWN copy of the schema. After you create or edit `ablo/schema.ts`, run `npx ablo push` (one-shot) or `npx ablo dev --no-watch`. **Skip this and every write to a new or changed model fails with `server_execute_unknown_model`.** (Plain `ablo dev` watches forever — never run it bare in an agent.)
19
- - **Other long-running:** `npx ablo logs --no-follow` (default tails forever). `npx ablo mode test|live` ALWAYS pass the argument. `status`, `push`, `pull`, `check`, `generate`, `docs` are one-shot — safe as-is.
18
+ - **Prepare a branch and push your schema — REQUIRED before any write works.** The server keeps its OWN copy of the schema. After you create or edit `ablo/schema.ts`, run `npx ablo dev --no-watch --branch <git-or-ci-ref>`: it ensures an isolated branch, mints a temporary branch credential, writes it to gitignored `.env.local`, pushes once, and exits. **Skip this and every write to a new or changed model fails with `server_execute_unknown_model`.** Plain `ablo dev` watches forever — never run it bare in an agent. `ablo push` is the lower-level one-shot command for an already selected key, including reviewed production deploys.
19
+ - **Other long-running:** `npx ablo logs --no-follow` (default tails forever). `status`, `branch check`, `push`, `pull`, `check`, `generate`, `docs`, and `dev --no-watch` are one-shot.
20
20
 
21
21
  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.
22
22
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,105 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.38.0
4
+
5
+ ### Branch-isolated development
6
+
7
+ Ablo branches replace the former shared Sandbox mode. Every project has a
8
+ protected Production root and can have multiple isolated development, preview,
9
+ test, or long-lived branches. Credentials are bound to immutable branch
10
+ identities, so changing a slug or request parameter cannot redirect a write.
11
+
12
+ `npx ablo dev` now discovers the current Git or CI branch, ensures the matching
13
+ Ablo branch, mints an expiring branch credential, writes it to the gitignored
14
+ local environment, pushes the schema, and watches for changes. Use
15
+ `npx ablo dev --no-watch --branch <name>` for a one-shot agent or CI setup.
16
+
17
+ The CLI also provides `ablo branch list`, `create`, `ensure`, `status`, `check`,
18
+ `credential`, and `delete`. The public HTTP and OpenAPI contracts expose the
19
+ same lifecycle for other languages and deployment systems.
20
+
21
+ This release removes the CLI mode switch and the dashboard Sandbox surface.
22
+ Production remains the protected root; a development credential cannot select
23
+ another branch or gain production authority.
24
+
25
+ Source adapters and PostgreSQL footprint helpers now select immutable branches.
26
+ If you construct `FootprintPlane` or `SourceRequestContext`, replace
27
+ `environment`, `mode`, and `sandboxId` with `branchId`.
28
+
29
+ ### Temporal and Inngest integration guides
30
+
31
+ New runnable examples and documentation show how Temporal and Inngest
32
+ workflows use Ablo without introducing another authority path. Temporal
33
+ activities and Inngest steps create the Ablo client, perform authoritative
34
+ reads, acquire claims for long-running work, and submit idempotent writes
35
+ through the same transaction API as every other caller.
36
+
37
+ The integrations keep each product in its proper role: Temporal and Inngest
38
+ own durable execution, scheduling, retries, and workflow history; Ablo owns
39
+ shared-data authority, claims, conflicts, idempotency, settlement, and ordered
40
+ observation. Workflow code does not open WebSockets or hold live client state.
41
+
42
+ ### Database adapter foundation, starting with PostgreSQL
43
+
44
+ Customer-database adapters now declare their database, ORM binding, and
45
+ observation strategy as separate axes. The existing Prisma, Drizzle, and Kysely
46
+ integrations are accurately identified as PostgreSQL bindings using either a
47
+ transactional outbox or PostgreSQL WAL.
48
+
49
+ All built-in adapters are constructed through one validating factory and the
50
+ shared conformance suite checks that the three axes are present. Impossible
51
+ capability combinations fail during adapter construction instead of silently
52
+ advertising guarantees the database path cannot provide.
53
+
54
+ PostgreSQL is the first database profile on this axis. The contract leaves room
55
+ for additional databases without pretending their transaction, observation,
56
+ and change-capture guarantees are interchangeable with PostgreSQL.
57
+
58
+ ### Generated language SDK foundation
59
+
60
+ The HTTP API is now explicitly treated as Ablo's language-neutral product
61
+ boundary. Its OpenAPI artifact follows the current Ablo version and every
62
+ operation has a stable, unique name suitable for deterministic Python and Go
63
+ generation.
64
+
65
+ The OpenAPI generator and drift check are restored as repository and CI gates.
66
+ Future language clients will generate their transport, wire models, and error
67
+ decoding from this contract, retaining handwritten code only for thin
68
+ language-idiomatic resource and claim façades.
69
+
70
+ The artifact now names shared claim, receipt, cursor, page, and error schemas;
71
+ references the canonical error envelope from every operation; publishes typed
72
+ pagination parameters and explicit union discriminators; and normalizes Zod's
73
+ JSON Schema output to the portable subset accepted by the Python and Go
74
+ generator candidates. CI also validates the rendered OpenAPI 3.1 document with
75
+ an independent, pinned Redocly CLI.
76
+
77
+ ### AI SDK tools over the transaction API
78
+
79
+ `@abloatai/ablo/ai-sdk` now exposes small tool adapters for authoritative
80
+ reads, idempotent creates, concurrency-safe updates, and claimed deletes. These
81
+ helpers use the caller's existing typed Ablo model resource; they do not add an
82
+ agent runtime or a second transport.
83
+
84
+ The public surface follows the model verbs directly: `readTool`, `createTool`,
85
+ `updateTool`, and `deleteTool`. AI SDK metadata and approval policy pass through
86
+ to each tool, destructive tools require approval by default, and cancellation
87
+ stops queued claim acquisition.
88
+
89
+ This deliberately replaces the previously published `coordinatedTool` naming:
90
+ use `updateTool`, `UpdateToolModel`, `UpdateToolOptions`, `UpdateToolResult`,
91
+ `UpdateStrategy`, and the `status` result field. The former
92
+ `coordinatedTool`, `CoordinatedModel`, `CoordinatedToolOptions`,
93
+ `CoordinatedWriteResult`, and `CoordinationStrategy` exports are removed rather
94
+ than retained as legacy aliases.
95
+
96
+ Queued tool writes now use Ablo's server-owned FIFO claim queue rather than
97
+ recreating coordination with client-side polling.
98
+
99
+ Internal job dispatch, concrete worker tools, prompts, sandboxes, and model
100
+ selection remain application concerns and are not part of the public Ablo
101
+ agent surface.
102
+
3
103
  ## 0.37.1
4
104
 
5
105
  ### A clearer introduction to Ablo
@@ -64,16 +164,3 @@ Use `authEndpoint` for the browser credential endpoint. The supported routes
64
164
  are `/v1/ephemeral_keys` and `/v1/capabilities`; the former `apiKey` endpoint
65
165
  option and legacy route aliases are removed. Credential callbacks now use the
66
166
  `CredentialProvider` type.
67
-
68
- ### Verified throughput without dropping coordination guarantees
69
-
70
- The strict AWS benchmark sustained more than 10,000 committed operations per
71
- second across create, mixed-create, update, and delete workloads, with zero
72
- write errors and sub-second publication drain.
73
-
74
- The result covers the documented single-plane, 12-client, 500-operation test
75
- topology. Atomic commits, authorization, idempotency, conflict handling, audit
76
- delivery, ordered observation, replay, and authoritative confirmation remained
77
- enabled throughout the run.
78
-
79
- Release notes are generated from the repository changesets.
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  </p>
4
4
 
5
5
  <p align="center">
6
- <strong>Coordination infrastructure for humans, agents, and backend systems.</strong>
6
+ <strong>The transaction layer for AI agents.</strong>
7
7
  </p>
8
8
 
9
9
  <p align="center">
@@ -22,28 +22,56 @@
22
22
 
23
23
  ---
24
24
 
25
- Ablo is a TypeScript framework and API for applications where AI agents,
26
- people, and backend services work on the same data. It provides typed reads and
27
- writes, claims, safe retries, authoritative confirmation, live updates, and
28
- attribution while your Postgres remains the source of truth.
25
+ Safely coordinate AI agents, humans, workflows, and services writing to the
26
+ same database.
29
27
 
30
- The SDK is backed by a pure HTTP transaction API, so Ablo works in agents,
31
- servers, jobs, command-line tools, and interactive applications without
32
- requiring a browser or reactive client.
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.
33
32
 
34
33
  ## Why Ablo
35
34
 
36
- Humans coordinate shared work naturally. We see that somebody is editing,
37
- agree on who takes which part, wait our turn, and look again before continuing.
35
+ Software used to have one writer: a human clicking through an application. AI
36
+ applications now have humans, agents, workflows, and services acting
37
+ concurrently. Databases keep transactions consistent. They do not coordinate
38
+ autonomous work that reads now, reasons for thirty seconds, and writes later.
38
39
 
39
- Agents do not have that awareness. Two agents can read the same row, think for
40
- thirty seconds, and overwrite each other. An agent can act on information that
41
- changed while it was reasoning without causing a database conflict at all.
40
+ Humans handle this naturally. We see that somebody is editing, agree on who
41
+ takes which part, wait our turn, and look again before continuing. Ablo gives
42
+ software actors those same capabilities: bounded authority, shared ownership,
43
+ fresh context, safe handoffs, and an attributed record of what happened.
42
44
 
43
- Ablo gives agents the same practical capabilities humans rely on: see who is
44
- working, claim a row or field, wait fairly, receive fresh state, prove what
45
- they may do, and leave an attributed record. Humans and backend services use
46
- the same rules, so there is no separate agent write path.
45
+ ## Start
46
+
47
+ ```sh
48
+ npm install @abloatai/ablo
49
+ npx ablo init
50
+ npx ablo dev
51
+ ```
52
+
53
+ `ablo dev` prepares an isolated Ablo branch for your Git branch, writes its
54
+ temporary credential to gitignored `.env.local`, pushes the schema, and watches
55
+ for changes.
56
+
57
+ Read and write through the transaction layer:
58
+
59
+ ```ts
60
+ const order = await ablo.orders.get({ id: orderId });
61
+
62
+ if (!order) throw new Error('Order not found');
63
+ await ablo.orders.update({
64
+ id: order.id,
65
+ data: { status: 'approved' },
66
+ wait: 'confirmed',
67
+ });
68
+ ```
69
+
70
+ `confirmed` means the authoritative database reported the change back. The
71
+ same commit can be retried safely if the caller loses its connection.
72
+
73
+ When work takes thirty seconds instead of one request, coordinate before the
74
+ agent starts reasoning:
47
75
 
48
76
  ```ts
49
77
  await using claim = await ablo.orders.claim({ id: orderId });
@@ -51,33 +79,42 @@ await using claim = await ablo.orders.claim({ id: orderId });
51
79
  const priced = await pricingAgent(claim.data);
52
80
 
53
81
  await ablo.orders.update({
54
- id: orderId,
82
+ id: claim.data.id,
55
83
  data: { total: priced.total, status: 'repriced' },
56
84
  claim,
57
85
  wait: 'confirmed',
58
86
  });
59
87
  ```
60
88
 
61
- The claim releases automatically. Overlapping work takes turns, stale work is
62
- rejected, retries are safe, and `confirmed` means the authoritative database
63
- reported the change back.
89
+ Another actor touching the same work waits fairly and receives fresh state when
90
+ its turn begins. If the agent fails, the claim releases automatically. If its
91
+ context became stale, the write is rejected instead of silently overwriting
92
+ work it never saw.
64
93
 
65
- ## Start
94
+ If you use AI SDK, expose the same operation as a typed model tool:
66
95
 
67
- ```sh
68
- npm install @abloatai/ablo
69
- npx ablo init
70
- npx ablo push
96
+ ```ts
97
+ import { updateTool } from '@abloatai/ablo/ai-sdk';
98
+
99
+ const approveOrder = updateTool(ablo.orders, {
100
+ description: 'Approve an order after reviewing it.',
101
+ inputSchema: z.object({ orderId: z.string() }),
102
+ id: ({ orderId }) => orderId,
103
+ apply: () => ({ status: 'approved' }),
104
+ });
71
105
  ```
72
106
 
107
+ Ablo supplies `readTool`, `createTool`, `updateTool`, and `deleteTool` over the
108
+ same authoritative resources. AI SDK keeps ownership of the model loop and tool
109
+ execution.
110
+
73
111
  Use `@abloatai/ablo` for agents and backend code,
74
112
  `@abloatai/ablo/client` for live applications, and
75
113
  `@abloatai/ablo/react` for React. All entrypoints share the same schema,
76
114
  authority, commits, claims, and ordered changes.
77
115
 
78
- Read the [Quickstart](./docs/quickstart.md), browse
116
+ Read the [Quickstart](https://docs.abloatai.com/quickstart), browse
79
117
  [docs.abloatai.com](https://docs.abloatai.com), or run `npx ablo docs`.
80
- Coding agents can read `node_modules/@abloatai/ablo/llms.txt`.
81
118
 
82
119
  ## Contributing
83
120
 
@@ -0,0 +1,2 @@
1
+ export * from '@abloatai/transaction/ai-sdk';
2
+ //# sourceMappingURL=ai-sdk.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-sdk.d.ts","sourceRoot":"","sources":["../src/ai-sdk.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC"}
package/dist/ai-sdk.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from '@abloatai/transaction/ai-sdk';
2
+ //# sourceMappingURL=ai-sdk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-sdk.js","sourceRoot":"","sources":["../src/ai-sdk.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC"}
package/docs/agents.md CHANGED
@@ -6,7 +6,7 @@ An agent is a **reactive** participant: it wakes on something happening, reads
6
6
  what it needs, writes a result, and goes idle. That's a request/response
7
7
  workload — so agents talk to Ablo over **plain HTTP**, holding no WebSocket. The
8
8
  credential *is* the identity; the server resolves the org, scope, and actor from
9
- the key on every request (the Stripe server-SDK / Liveblocks-node shape).
9
+ the key on every request.
10
10
 
11
11
  Agents get the stateless plane (HTTP). People — when you add the `humans()`
12
12
  plugin — get the live plane (WebSocket: presence, optimistic, sub-100ms).
@@ -31,7 +31,7 @@ import { schema } from "./schema";
31
31
  const ablo = Ablo({ schema, apiKey: process.env.ABLO_API_KEY, transport: "http" });
32
32
 
33
33
  // Reads + writes, fully typed off your schema.
34
- // `retrieve` resolves to the row, or `undefined` when none matches.
34
+ // `get` resolves to the row, or `undefined` when none matches.
35
35
  const open = await ablo.tasks.list({ where: { status: "todo" } });
36
36
 
37
37
  const task = await ablo.tasks.get({ id: open[0].id });
@@ -41,11 +41,57 @@ console.log(task.title);
41
41
  await ablo.tasks.update({ id: task.id, data: { status: "done" } });
42
42
  ```
43
43
 
44
- It exposes `retrieve` / `list` / `create` / `update` / `delete`, plus `commits`
45
- and `claim`. It does **not** expose the stateful-only surface (`get` /
46
- `local` reads, `onChange` live subscription) those need a
47
- live connection, so with `transport: 'http'` the return type narrows and they
48
- are a *compile error*, not a runtime surprise.
44
+ It exposes `get` / `list` / `create` / `update` / `delete`, plus `commits`
45
+ and `claim`. It does **not** expose stateful-only `local` reads or `onChange`
46
+ subscriptions. Those need a live connection, so with `transport: 'http'` they
47
+ are compile errors rather than runtime surprises.
48
+
49
+ ## AI SDK tools
50
+
51
+ Keep AI SDK in charge of the model loop and expose only the Ablo operations the
52
+ model needs:
53
+
54
+ ```ts
55
+ import { generateText } from 'ai';
56
+ import {
57
+ createTool,
58
+ deleteTool,
59
+ readTool,
60
+ updateTool,
61
+ } from '@abloatai/ablo/ai-sdk';
62
+
63
+ const tools = {
64
+ getTask: readTool(ablo.tasks, {
65
+ description: 'Read the current task.',
66
+ inputSchema: z.object({ taskId: z.string() }),
67
+ id: ({ taskId }) => taskId,
68
+ }),
69
+ createTask: createTool(ablo.tasks, {
70
+ description: 'Create a task.',
71
+ inputSchema: z.object({ requestId: z.string(), title: z.string() }),
72
+ id: ({ requestId }) => requestId,
73
+ data: ({ title }) => ({ title, status: 'todo' }),
74
+ }),
75
+ updateTask: updateTool(ablo.tasks, {
76
+ description: 'Update a task without overwriting concurrent work.',
77
+ inputSchema: z.object({ taskId: z.string(), status: z.string() }),
78
+ id: ({ taskId }) => taskId,
79
+ apply: (_current, { status }) => ({ status }),
80
+ }),
81
+ deleteTask: deleteTool(ablo.tasks, {
82
+ description: 'Delete a task after taking its claim.',
83
+ inputSchema: z.object({ taskId: z.string() }),
84
+ id: ({ taskId }) => taskId,
85
+ // Destructive tools require AI SDK approval by default.
86
+ }),
87
+ };
88
+
89
+ await generateText({ model, messages, tools });
90
+ ```
91
+
92
+ These are adapters over the same typed resources used by ordinary backend
93
+ code. Ablo does not own the planner, prompt system, memory, provider, worker,
94
+ or workflow runtime.
49
95
 
50
96
  ## Coordination: claim, queue, reorder
51
97
 
@@ -59,7 +105,11 @@ clobber the same record.
59
105
  await using claim = await ablo.tasks.claim({ id: taskId });
60
106
  const task = claim.data;
61
107
  // …no one else can hold this row while you work…
62
- await ablo.tasks.update({ id: task.id, data: { status: "in_review" } });
108
+ await ablo.tasks.update({
109
+ id: task.id,
110
+ data: { status: "in_review" },
111
+ claim,
112
+ });
63
113
 
64
114
  await ablo.tasks.claim.state({ id: taskId }); // who holds it now (or null)
65
115
  await ablo.tasks.claim.queue({ id: taskId }); // the FIFO wait-line behind the holder
@@ -111,5 +161,5 @@ agents costs nothing on the live plane — that capacity stays for humans.
111
161
  `onChange` (live subscriptions) and the `local` reads (local synced-pool
112
162
  reads) require a WebSocket and a local store — they're for interactive UIs, not
113
163
  stateless agents. An agent reacts to an external trigger (a job/queue/webhook),
114
- then reads with `list`/`retrieve`. See [client behavior](/client-behavior) for
164
+ then reads with `list`/`get`. See [client behavior](/client-behavior) for
115
165
  the full surface and [guarantees](/guarantees) for the coordination semantics.
package/docs/api-keys.md CHANGED
@@ -27,6 +27,11 @@ Pick your row:
27
27
 
28
28
  That's the whole story: one knob, filled by audience.
29
29
 
30
+ The `mk_` credential created by `ablo login` is different: it is a CLI
31
+ control-plane credential, not an application API key. It can manage projects
32
+ and branches and exchange for a branch-bound runtime key. Do not pass it to
33
+ `Ablo(...)` or put it in `ABLO_API_KEY`.
34
+
30
35
  **Coming from Stripe? It's the same key model, same prefixes:**
31
36
 
32
37
  | Stripe | Ablo | Where it goes |
@@ -36,7 +41,14 @@ That's the whole story: one knob, filled by audience.
36
41
  | restricted `rk_` (granular) | `rk_` | scoped agents (`agents.create({ can })`) |
37
42
  | ephemeral key (client, customer-scoped) | `ek_` | per-user browser sessions (`sessions.create({ user, can })`) |
38
43
 
39
- Mode lives in the prefix too `sk_test_` / `sk_live_` — exactly like Stripe. The
44
+ Ablo also has one credential class that Stripe does not need:
45
+
46
+ | Prefix | Purpose | Mode | Stored where |
47
+ |---|---|---|---|
48
+ | `mk_` | project and branch management | none | CLI credential store or `ABLO_MANAGEMENT_KEY` |
49
+
50
+ Trust class lives in the prefix too — `sk_test_` / `sk_live_` — exactly like Stripe.
51
+ It does not select a branch; the immutable server-side binding does that. The
40
52
  `apiKey` resolver fetching an `ek_` is Ablo's ephemeral-key flow: server mints, client holds.
41
53
 
42
54
  **Why a function for browser writes?** Anything you ship to a browser must be public, and a
@@ -91,21 +103,28 @@ cannot reach any control-plane operation. The moment the browser needs to write
91
103
  on a specific user's behalf, mint a short-lived `ek_` user session from your
92
104
  backend instead (see the Sessions guide).
93
105
 
94
- ## Sandboxes and production
106
+ ## Branches and production
95
107
 
96
- Test and live keys are the same shape; the prefix names the environment:
108
+ A branch is your project at full strength over its own rows: the same models,
109
+ the same schema, the same claims and the same rules production runs.
97
110
 
98
- - `sk_test_…` a key bound to a **sandbox**. Its reads and writes are isolated
99
- to that sandbox and are invisible to live keys (and to other sandboxes).
111
+ Production is the project's root branch. Development branches are isolated
112
+ children, and a key's immutable branch binding decides which rows, schema,
113
+ claims, and log it can reach:
114
+
115
+ - `sk_test_…` — a key bound to a development branch. Its reads and
116
+ writes are invisible to production and to other branches.
100
117
  - `sk_live_…` — a key against your live data.
101
118
 
102
- Every org has a default sandbox, plus any number of additional
103
- sandboxes you create. **Data is isolated per sandbox; the schema is one
104
- definition serving both.** A sandbox reads the production schema until it is
105
- pushed one of its own, so your test and live keys see the same models and only
106
- the rows differ how Stripe separates sandbox and production data while keeping
107
- the API shape identical. A schema change reaches production when you push it
108
- with a live key ([Deployment](./deployment.md)).
119
+ `npx ablo dev` derives a branch from Git, ensures the matching child, and mints
120
+ an expiring `sk_test_` key for it. The credential carries the immutable branch
121
+ id; changing a slug in a request cannot change its authority. A child receives
122
+ the parent's active schema when it is created and owns its artifact after that.
123
+ A schema change reaches production only through the reviewed live-key path in
124
+ [Deployment](./deployment.md).
125
+
126
+ The shared default sandbox is no longer part of the development workflow.
127
+ Branch identity is required for newly provisioned CLI and runtime credentials.
109
128
 
110
129
  ## Scopes
111
130
 
@@ -114,25 +133,30 @@ only what its job needs. A secret key with **no scopes** has full org authority
114
133
  (the default for a `sk_live_` backend key); a key with a non-empty scope set is
115
134
  restricted to exactly those grants:
116
135
 
117
- - `schema:push` — author the org schema (`ablo schema push`, `ablo dev`). A
118
- high-risk, org-wide grant: because schema is shared, a push affects the live
119
- table shape. A full-authority key has it implicitly; a *restricted* key (such
120
- as a sandbox key) needs it granted explicitly.
121
- - `sandbox:<id>`identifies which sandbox the key belongs to. (The key's data
122
- isolation comes from that sandbox binding, not from this scope string.)
136
+ - `schema:push` — author the schema artifact on the key's bound plane
137
+ (`ablo push`, `ablo dev`). A production push is high-risk because it changes
138
+ the live contract; a child push remains inside that branch. A
139
+ full-authority key has it implicitly; a restricted key needs it explicitly.
140
+ - `project:manage`list, create, and rename projects.
141
+ - `branch:manage` list, create, and delete child branches and mint their
142
+ temporary credentials.
143
+
144
+ Both management scopes are explicit grants on `mk_` credentials. Runtime
145
+ `sk_`, `rk_`, `pk_`, and `ek_` credentials cannot become management
146
+ credentials through an empty scope set or a CLI fallback.
123
147
 
124
- A key minted from the default sandbox carries `schema:push`, so
125
- `ablo dev` works out of the box. Keys from other sandboxes are **data-only** by
126
- default enable "schema authoring" when minting if you want that key to push
127
- schema too. Hand data-only keys to embedded apps and CI agents; reserve
128
- schema-authoring keys for the developer running `ablo dev`.
148
+ Branch binding remains an authority boundary even when a key has no granular
149
+ scope strings: a temporary child key can act only inside that child. It cannot
150
+ manage siblings or gain root authority.
129
151
 
130
152
  ### `ablo dev`
131
153
 
132
154
  ```sh
133
- ABLO_API_KEY=sk_test_… npx ablo dev
155
+ npx ablo login
156
+ npx ablo dev
134
157
  ```
135
158
 
136
- Pushes your `ablo/schema.ts` to the test sandbox, prints the one line you need
137
- in `.env.local`, and re-pushes on every save. It refuses `sk_live_` keys so a
138
- tight save loop can never churn production data.
159
+ The stored `mk_` project credential is used only to ensure the Git-derived child and
160
+ mint an expiring branch credential. `dev` writes that temporary key to
161
+ gitignored `.env.local`, pushes `ablo/schema.ts` to the child, and re-pushes on
162
+ every save. See [Branch-first development](./branch-development.md).