@abloatai/ablo 0.16.1 → 0.16.2
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/CHANGELOG.md +24 -1
- package/dist/auth/index.d.ts +12 -7
- package/dist/auth/index.js +8 -2
- package/dist/cli.cjs +5 -4
- package/docs/coordination.md +67 -0
- package/docs/identity.md +34 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.16.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- **`mintUserSessionKey`: name the shared-schema binding around the project.** The
|
|
8
|
+
two flat options added in 0.16.0 (`schemaOwnerOrgId` + `schemaProjectId`) are
|
|
9
|
+
replaced by one project-centric option — `schemaProject: { organizationId, projectId }` —
|
|
10
|
+
naming "the project that owns the schema" as a single concept. The wire format
|
|
11
|
+
is unchanged (the SDK still sends the same keys), so no server redeploy is
|
|
12
|
+
needed. Released as a patch: the replaced options shipped in 0.16.0 and have no
|
|
13
|
+
external consumers yet.
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
// before
|
|
17
|
+
mintUserSessionKey({ organizationId, schemaOwnerOrgId, schemaProjectId, ... });
|
|
18
|
+
// after
|
|
19
|
+
mintUserSessionKey({
|
|
20
|
+
organizationId, // data org
|
|
21
|
+
schemaProject: { organizationId, projectId }, // the project that owns the schema
|
|
22
|
+
...
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
3
26
|
## 0.16.1
|
|
4
27
|
|
|
5
28
|
### Patch Changes
|
|
@@ -10,7 +33,7 @@
|
|
|
10
33
|
`ABLO_AUTH_URL`), while the human approval page (`/cli`), sign-up, and the
|
|
11
34
|
key-handoff route (`/api/cli/provision-key`) go to the dashboard host
|
|
12
35
|
(`www.abloatai.com`, new override `ABLO_DASHBOARD_URL`). Previously every step
|
|
13
|
-
ran against `www`, where the
|
|
36
|
+
ran against `www`, where the device endpoints no longer resolve —
|
|
14
37
|
producing "Couldn't start login… Is the dashboard reachable?". The CLI now also
|
|
15
38
|
builds the approval URL itself rather than trusting the server's
|
|
16
39
|
`verification_uri`, which (being a relative `/cli`) resolved against the auth
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -38,13 +38,18 @@ export interface MintUserSessionRequest {
|
|
|
38
38
|
* `Stripe-Account` analogue. Requires the `sk_` to carry
|
|
39
39
|
* `ephemeral:mint-any-org`; omit to mint into the key's own org. */
|
|
40
40
|
readonly organizationId?: string;
|
|
41
|
-
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
|
|
47
|
-
readonly
|
|
41
|
+
/** SHARED SCHEMA — point this session's SCHEMA at the project that owns it,
|
|
42
|
+
* while its DATA stays scoped to `organizationId`. Use this for org-per-customer
|
|
43
|
+
* isolation: keep one schema project, and every customer's session resolves its
|
|
44
|
+
* schema from it instead of re-pushing the schema into each customer's org.
|
|
45
|
+
* Requires the `sk_` to carry `ephemeral:mint-any-org`. Omit for the default
|
|
46
|
+
* (the session resolves its schema from its own org). */
|
|
47
|
+
readonly schemaProject?: {
|
|
48
|
+
/** The org that owns the schema project. */
|
|
49
|
+
readonly organizationId: string;
|
|
50
|
+
/** The project the schema was pushed under. */
|
|
51
|
+
readonly projectId: string;
|
|
52
|
+
};
|
|
48
53
|
readonly syncGroups?: readonly string[];
|
|
49
54
|
readonly ttlSeconds: number;
|
|
50
55
|
readonly label?: string;
|
package/dist/auth/index.js
CHANGED
|
@@ -108,8 +108,14 @@ export async function mintUserSessionKey(options) {
|
|
|
108
108
|
body: JSON.stringify({
|
|
109
109
|
user: { id: options.userId },
|
|
110
110
|
...(options.organizationId ? { organizationId: options.organizationId } : {}),
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
// Flattened to the existing wire keys — the public param is project-centric,
|
|
112
|
+
// the transport contract is unchanged (no coordinated server deploy needed).
|
|
113
|
+
...(options.schemaProject
|
|
114
|
+
? {
|
|
115
|
+
schemaProjectId: options.schemaProject.projectId,
|
|
116
|
+
schemaOwnerOrgId: options.schemaProject.organizationId,
|
|
117
|
+
}
|
|
118
|
+
: {}),
|
|
113
119
|
...(options.syncGroups ? { syncGroups: options.syncGroups } : {}),
|
|
114
120
|
ttlSeconds: options.ttlSeconds,
|
|
115
121
|
...(options.label ? { label: options.label } : {}),
|
package/dist/cli.cjs
CHANGED
|
@@ -280564,7 +280564,8 @@ function parseProjectFlag(argv) {
|
|
|
280564
280564
|
const eq = argv.find((a) => a.startsWith("--project="));
|
|
280565
280565
|
return eq ? eq.slice("--project=".length) || void 0 : void 0;
|
|
280566
280566
|
}
|
|
280567
|
-
async function deviceLogin(argv) {
|
|
280567
|
+
async function deviceLogin(argv, deps = {}) {
|
|
280568
|
+
const openUrl = deps.openUrl ?? openBrowser;
|
|
280568
280569
|
Ie(`${brand("ablo")} login`);
|
|
280569
280570
|
const requested = parseProjectFlag(argv) ?? getActiveProject()?.slug;
|
|
280570
280571
|
const targetProject = requested === DEFAULT_PROFILE ? void 0 : requested;
|
|
@@ -280599,7 +280600,7 @@ async function deviceLogin(argv) {
|
|
|
280599
280600
|
Me(`${import_picocolors7.default.bold(code.user_code)}
|
|
280600
280601
|
|
|
280601
280602
|
${import_picocolors7.default.dim(url)}`, "Approve in your browser");
|
|
280602
|
-
|
|
280603
|
+
openUrl(url);
|
|
280603
280604
|
const s = Y2();
|
|
280604
280605
|
s.start("Waiting for approval\u2026");
|
|
280605
280606
|
let pollMs = (code.interval ?? 5) * 1e3;
|
|
@@ -280690,8 +280691,8 @@ ${import_picocolors7.default.dim(url)}`, "Approve in your browser");
|
|
|
280690
280691
|
`${import_picocolors7.default.green("\u2713")} Logged in ${import_picocolors7.default.dim("(sandbox)")}${where}. Run ${import_picocolors7.default.bold("npx ablo push")} to push your schema.`
|
|
280691
280692
|
);
|
|
280692
280693
|
}
|
|
280693
|
-
async function login(argv = []) {
|
|
280694
|
-
await deviceLogin(argv);
|
|
280694
|
+
async function login(argv = [], deps = {}) {
|
|
280695
|
+
await deviceLogin(argv, deps);
|
|
280695
280696
|
}
|
|
280696
280697
|
function logout() {
|
|
280697
280698
|
const removed = clearCredential();
|
package/docs/coordination.md
CHANGED
|
@@ -67,6 +67,73 @@ anything extra.
|
|
|
67
67
|
|
|
68
68
|
---
|
|
69
69
|
|
|
70
|
+
## Declaring conflict behaviour in the schema (Axis 3)
|
|
71
|
+
|
|
72
|
+
The two enforcement layers above are decided **per write** (claim a row, or pass
|
|
73
|
+
`readAt` + `onStale`). You can also declare a model's **default** conflict
|
|
74
|
+
disposition once, in the schema, so every commit to that model is governed
|
|
75
|
+
without per-call wiring. This is the third coordination axis — orthogonal to
|
|
76
|
+
`policy` (who may read a row) and `groups` (which delta channels it fans into).
|
|
77
|
+
|
|
78
|
+
Add a `conflict` map to `model(...)`, keyed by the **committer's** participant
|
|
79
|
+
kind (`user` / `agent` / `system`), with the same `onStale` vocabulary as values:
|
|
80
|
+
|
|
81
|
+
| value | meaning |
|
|
82
|
+
|---|---|
|
|
83
|
+
| `'overwrite'` | the write wins; that committer is never blocked. |
|
|
84
|
+
| `'reject'` | the write is refused; that committer yields to a held claim / stale snapshot. |
|
|
85
|
+
| `'notify'` | hold the write and hand back the current value so the committer re-reads and re-applies (stale writes only). |
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import { model, field } from '@abloatai/ablo/schema';
|
|
89
|
+
|
|
90
|
+
export const card = model('card', {
|
|
91
|
+
title: field.string(),
|
|
92
|
+
}, {
|
|
93
|
+
// "a human's edit always wins (never blocked); an agent yields"
|
|
94
|
+
conflict: { user: 'overwrite', agent: 'reject' },
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Composable helpers
|
|
99
|
+
|
|
100
|
+
For the same map with an authoring surface that reads like the rest of the DSL,
|
|
101
|
+
use the disposition helpers and the `coordination(...)` combinator (a `cn`/`cx`
|
|
102
|
+
for conflict policy — later rules win on key collisions):
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { model, field, coordination, humansOverwrite, agentsReject } from '@abloatai/ablo/schema';
|
|
106
|
+
|
|
107
|
+
export const card = model('card', {
|
|
108
|
+
title: field.string(),
|
|
109
|
+
}, {
|
|
110
|
+
conflict: coordination(humansOverwrite(), agentsReject()),
|
|
111
|
+
// → { user: 'overwrite', agent: 'reject' }
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Helpers, one per disposition: `humansOverwrite` / `humansReject` / `humansNotify`,
|
|
116
|
+
`agentsOverwrite` / `agentsReject` / `agentsNotify`,
|
|
117
|
+
`systemOverwrite` / `systemReject` / `systemNotify`.
|
|
118
|
+
|
|
119
|
+
### How it relates to per-write coordination
|
|
120
|
+
|
|
121
|
+
- The `conflict` map is **pure, serializable data**: it ships in your pushed
|
|
122
|
+
schema (`npx ablo push`) and the engine interprets it at the commit
|
|
123
|
+
chokepoint — there is no per-model code on the server.
|
|
124
|
+
- An **omitted committer kind falls through to the engine default**: reject, and
|
|
125
|
+
honor a per-write `onStale: 'notify'`. Declaring `conflict` is purely
|
|
126
|
+
additive — existing schemas behave exactly as before.
|
|
127
|
+
- It sets the **default** disposition; a per-write `onStale` and a held `claim`
|
|
128
|
+
still apply on top as described above. Think of `conflict` as "the house rule
|
|
129
|
+
for this model," and `claim` / `onStale` as what an individual write does
|
|
130
|
+
within it.
|
|
131
|
+
|
|
132
|
+
The `ConflictAxis` type (also available as `Ablo.Conflict.Axis`) and the
|
|
133
|
+
`interpretConflictAxis` interpreter are exported for composing custom policies.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
70
137
|
## The claim state object
|
|
71
138
|
|
|
72
139
|
The claim state object is the live record that a participant is coordinating work on
|
package/docs/identity.md
CHANGED
|
@@ -156,6 +156,40 @@ The identity is a **participant** — and a participant is either a human
|
|
|
156
156
|
[Agents are participants too](#agents-are-participants-too) below. Everything in
|
|
157
157
|
the next two sections applies to both.
|
|
158
158
|
|
|
159
|
+
## Your schema lives in a project; your users commit to it
|
|
160
|
+
|
|
161
|
+
The default is simple: your schema lives in a **project**, you push it once, and
|
|
162
|
+
every session you mint resolves against it. Your end-users **don't have Ablo
|
|
163
|
+
accounts** — your server's `sk_` mints an `ek_` per user, and by default that
|
|
164
|
+
session lands in your project's own org. All your users share one schema, one
|
|
165
|
+
data tenant, isolated from each other by sync-groups. That's the whole story for
|
|
166
|
+
most apps.
|
|
167
|
+
|
|
168
|
+
**Add-on — org-per-customer isolation.** If you need each customer to be its own
|
|
169
|
+
hard tenant (separate row-level isolation, optionally a separate database) you'd
|
|
170
|
+
otherwise have to re-push your schema into every customer's org. Instead, keep one
|
|
171
|
+
project as the home of your schema and point each customer's session's *schema*
|
|
172
|
+
at it while its *data* stays in the customer's org:
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
await mintUserSessionKey({
|
|
176
|
+
apiKey: platformKey, // sk_ with the `ephemeral:mint-any-org` scope
|
|
177
|
+
userId,
|
|
178
|
+
organizationId, // DATA → this customer's org (RLS-isolated tenant)
|
|
179
|
+
schemaProject: { // SCHEMA → the project that owns your schema
|
|
180
|
+
organizationId: schemaOwnerOrgId,
|
|
181
|
+
projectId: schemaProjectId,
|
|
182
|
+
},
|
|
183
|
+
ttlSeconds: 3600,
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Server-side, the model **shape** loads from your schema project but column
|
|
188
|
+
enrichment and the tenant connection still target `organizationId` — so the
|
|
189
|
+
shared schema only *describes* the shape; the data plane stays the customer's and
|
|
190
|
+
can't cross-leak. Omit these fields for the default above. Requires a platform
|
|
191
|
+
`sk_` with `ephemeral:mint-any-org`.
|
|
192
|
+
|
|
159
193
|
## The two halves of scoping
|
|
160
194
|
|
|
161
195
|
Scoping is two declarations that meet in the middle. One describes the
|