@vectros-ai/cli 0.19.0 → 0.21.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/AUTHORING.md +150 -9
- package/CHANGELOG.md +339 -0
- package/README.md +155 -24
- package/dist/cli.js +8058 -4835
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +8058 -4835
- package/dist/cli.mjs.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -9,9 +9,13 @@ The Vectros provisioning CLI — the `vectros` binary:
|
|
|
9
9
|
- **`vectros blueprint`** — author + inspect blueprints (`init` / `validate` / `plan` / `list`), creds-free.
|
|
10
10
|
- **`vectros blueprint-test`** — apply a blueprint to a live env, assert it provisioned a working credential, then tear it down.
|
|
11
11
|
- **`vectros context`** — create, list, and destroy app contexts.
|
|
12
|
-
- **`vectros issuers`** — inspect
|
|
12
|
+
- **`vectros issuers`** — register, update, delete, and inspect third-party IdP issuers (token-exchange trust anchors) for a context.
|
|
13
|
+
- **`vectros namespace`** — register a new scope namespace, and list/get existing ones.
|
|
13
14
|
- **`vectros identity`** — manage tenant-wide principals (users, orgs, clients, and namespaces you register).
|
|
14
15
|
- **`vectros role`** — define reusable, context-scoped access rules.
|
|
16
|
+
- **`vectros scripts`** — push, list, inspect, and delete the versioned script source a trigger
|
|
17
|
+
fires, or your own app executes directly.
|
|
18
|
+
- **`vectros assume`** — switch identity namespace values on your own token, per a role's `assumable` grant.
|
|
15
19
|
- **`vectros access`** — bind principals to contexts, and inspect what a binding grants.
|
|
16
20
|
- **`vectros key`** — issue, list, revoke, and rotate scoped API keys (`ssk_*`).
|
|
17
21
|
- **`vectros keyring`** — hold several identities on one machine, pick which is active, and let other tools resolve keys from one place.
|
|
@@ -148,23 +152,61 @@ flags (`--base-url`, `--token`).
|
|
|
148
152
|
|
|
149
153
|
## `vectros issuers` — registered third-party IdP issuers
|
|
150
154
|
|
|
151
|
-
|
|
155
|
+
Manage the third-party identity-provider registrations a context trusts for
|
|
152
156
|
[RFC 8693 token exchange](https://www.rfc-editor.org/rfc/rfc8693) — the
|
|
153
157
|
`issuer`/`jwksUri`/`audience` a blueprint's `issuers[]` entries register, plus
|
|
154
|
-
any self-signup policy attached to one.
|
|
155
|
-
|
|
158
|
+
any self-signup policy attached to one. Everything a blueprint's `issuers:`
|
|
159
|
+
block can do is available standalone here too — adding a second IdP, or
|
|
160
|
+
editing one issuer's `selfSignupPolicies` after initial setup, no longer
|
|
161
|
+
requires hand-authoring a blueprint and re-running a full apply.
|
|
156
162
|
|
|
157
163
|
```bash
|
|
164
|
+
vectros issuers register --context my-app --issuer-id auth0-prod \
|
|
165
|
+
--issuer https://your-tenant.us.auth0.com/ \
|
|
166
|
+
--jwks-uri https://your-tenant.us.auth0.com/.well-known/jwks.json \
|
|
167
|
+
--audience https://api.your-app.example.com \
|
|
168
|
+
--captured-claim name --captured-claim phone_number
|
|
158
169
|
vectros issuers list --context my-app
|
|
159
170
|
vectros issuers get primary --context my-app
|
|
171
|
+
vectros issuers update primary --context my-app --status suspended
|
|
172
|
+
vectros issuers delete primary --context my-app # re-types the id to confirm
|
|
160
173
|
```
|
|
161
174
|
|
|
162
175
|
| Subcommand | Flags | Notes |
|
|
163
176
|
|---|---|---|
|
|
177
|
+
| `register` | `--context`, `--issuer-id`, `--issuer`, `--jwks-uri`, `--audience` (all required); `--sub-claim`, `--email-claim`, `--userinfo-uri`, `--self-signup-policy <type:roleId>` (repeatable), `--captured-claim <name>` (repeatable), `--tenant test\|live` | Idempotent by `--issuer-id` within the tenant — a repeat returns the existing registration unchanged, not an error. `--captured-claim` names additional OIDC claims to capture beyond the email claim on every successful token exchange, stored as your tenant's golden identity-provider-asserted copy for that user; omit it to capture nothing beyond email. At most 20 names of at most 64 characters, no duplicates. |
|
|
178
|
+
| `update <issuerId>` | `--context <contextId>` (required); `--sub-claim`, `--email-claim`, `--userinfo-uri`, `--status active\|suspended`, `--self-signup-policy <type:roleId>` (repeatable), `--clear-self-signup-policies`, `--captured-claim <name>` (repeatable), `--clear-captured-claims`, `--tenant test\|live` | Omitting `--captured-claim` leaves the stored list UNCHANGED; `--clear-captured-claims` sends an explicit empty list (capture nothing beyond email). The two are mutually exclusive, and the same absent-vs-clear split applies to `--self-signup-policy`. Only ever sends the SAFE, mutable fields — `issuer`/`jwks-uri`/`audience`/`contextId` are trust-anchor fields, immutable via this route on the platform side; rotating one is `delete` + `register`. |
|
|
179
|
+
| `delete <issuerId>` | `--context <contextId>` (required), `--force`, `--tenant test\|live` | Re-types the issuer id to confirm (mirrors `context destroy`); `--force` skips the prompt and is required when stdin isn't a TTY. Refused server-side while any user was ever created or matched through this issuer. |
|
|
164
180
|
| `list` | `--context <contextId>` (required), `--tenant test\|live` | Every issuer registered in that context. `--context` has no default — an issuer read is confined to the context you name; there's no tenant-wide "every issuer in every context" view. |
|
|
165
|
-
| `get <issuerId>` | `--context <contextId>` (required), `--tenant test\|live` | One issuer's full registration, including its self-signup policies. |
|
|
181
|
+
| `get <issuerId>` | `--context <contextId>` (required), `--tenant test\|live` | One issuer's full registration, including its self-signup policies and captured claims. |
|
|
166
182
|
|
|
167
|
-
|
|
183
|
+
All five accept `--json`, plus the common auth flags (`--base-url`, `--token`).
|
|
184
|
+
|
|
185
|
+
## `vectros namespace` — scope-namespace registrations
|
|
186
|
+
|
|
187
|
+
Register and inspect the namespaces a `scope:<namespace>` value can belong
|
|
188
|
+
to — whether values in that namespace must resolve to a real identity entity
|
|
189
|
+
(`entityBacked`, the same field `identity create --type <namespace>`
|
|
190
|
+
relies on) or are free-form strings. Registering a brand-new namespace (e.g.
|
|
191
|
+
a `practice`/`team` entity dimension) was blueprint-apply-only before this
|
|
192
|
+
command existed.
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
vectros namespace register practice --context my-app --specificity-rank 1500 --entity-backed
|
|
196
|
+
vectros namespace list # tenant-wide registrations
|
|
197
|
+
vectros namespace list --context my-app # just my-app's own
|
|
198
|
+
vectros namespace get practice --context my-app
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
| Subcommand | Flags | Notes |
|
|
202
|
+
|---|---|---|
|
|
203
|
+
| `register <name>` | `--context`, `--specificity-rank <n>` (both required); `--entity-backed`, `--default-schema-id <id>` | NOT idempotent — a repeat name 400s (unlike `issuers register`). `--context` has no tenant-wide option here — a confined credential may only register against its own bound context. |
|
|
204
|
+
| `list` | `--context <contextId>` (optional) | Omit for every tenant-wide registration; supply for one context's own. |
|
|
205
|
+
| `get <name>` | `--context <contextId>` (optional) | Same two-mode read as `list`. |
|
|
206
|
+
|
|
207
|
+
No `update`/`delete` — both routes require a genuine root API key on the
|
|
208
|
+
platform side, which this CLI never holds, so neither could ever succeed
|
|
209
|
+
here. All three subcommands accept `--json`.
|
|
168
210
|
|
|
169
211
|
## `vectros identity` — tenant-wide principals
|
|
170
212
|
|
|
@@ -190,12 +232,13 @@ vectros identity invite --context my-app --role editor --email bob@example.com \
|
|
|
190
232
|
| | `--type user` only: `--email <email>`, `--service` (mint a SERVICE principal; default HUMAN) | `--name`/`--scope` are rejected for `--type user`. |
|
|
191
233
|
| | entity namespaces only: `--name <name>`, `--scope <ns:value>` (repeatable, up to 2 parent ownership edges) | `--email`/`--service` are rejected for an entity namespace. |
|
|
192
234
|
| | both: `--metadata <json>` | Free-form JSON object. |
|
|
193
|
-
| `list` | `--type <user\|namespace>` (required), `--external-id <id>` (filter), `--limit <n>` | Omit `--limit` and the CLI drains every page for the complete set; pass `--limit` (1–100) and it returns exactly one page of that size instead. |
|
|
235
|
+
| `list` | `--type <user\|namespace>` (required), `--external-id <id>` (filter), `--limit <n>` | Scoped to your own context — see `list-all` below for a user created but never granted access anywhere. Omit `--limit` and the CLI drains every page for the complete set; pass `--limit` (1–100) and it returns exactly one page of that size instead. |
|
|
236
|
+
| `list-all` | `--external-id <id>` (filter), `--tenant test\|live`, `--limit <n>` | **Users only, and requires an account owner login.** Lists every user in the tenant, regardless of app-context access — for recovering a user `identity list` can never show you: one created but not (yet) granted access anywhere, so no context-scoped view can ever reach it, and a `create` retry with the same `--external-id` just collides with the same invisible row. Find it here by `--external-id`, then grant it access or `identity delete` it with an ordinary credential. |
|
|
194
237
|
| `get` | `--type <user\|namespace>` (required), `--id <vectrosId>` (required) | `--id` is the Vectros-assigned id from `create`/`list`, not the `--external-id`. |
|
|
195
238
|
| `delete` | `--type <user\|namespace>` (required), `--id <vectrosId>` (required) | |
|
|
196
239
|
| `invite` | `--context <c>`, `--role <roleId>`, `--email <e>` (all required); `--first-name <n>`, `--accept-url <url>`, `--no-send-email` | Onboards a person into a context by email (`POST /v1/users/invite`) — no root key required, your `vectros login` session already carries enough scope. `--role` is an existing Role's own id (no separate lookup). `--accept-url` is required unless `--no-send-email` is given, in which case the response carries the raw `inviteToken`/`acceptLink` for you to deliver yourself. |
|
|
197
240
|
|
|
198
|
-
`create`/`list`/`get`/`invite` accept `--json`; `delete` does not.
|
|
241
|
+
`create`/`list`/`list-all`/`get`/`invite` accept `--json`; `delete` does not.
|
|
199
242
|
|
|
200
243
|
## `vectros role` — reusable, context-scoped access rules
|
|
201
244
|
|
|
@@ -210,6 +253,10 @@ vectros role create --context my-app --role-id editor --name Editor \
|
|
|
210
253
|
--actions records:cru,search:r
|
|
211
254
|
vectros role create --context my-app --role-id own-org --name "Own org" \
|
|
212
255
|
--actions records:r --data-scope '{"scope:org":["<orgId>", null]}'
|
|
256
|
+
vectros role create --context my-app --role-id hr-admin --name "HR Admin" \
|
|
257
|
+
--actions records:cru --assumable '{"scope:org":["org_engineering", "org_sales"]}'
|
|
258
|
+
vectros role create --context my-app --role-id delegator --name Delegator \
|
|
259
|
+
--actions profiles:c --assignable-role editor --assignable-role own-org
|
|
213
260
|
vectros role list --context my-app
|
|
214
261
|
vectros role get --context my-app --role-id editor
|
|
215
262
|
vectros role delete --context my-app --role-id editor
|
|
@@ -217,13 +264,87 @@ vectros role delete --context my-app --role-id editor
|
|
|
217
264
|
|
|
218
265
|
| Subcommand | Flags | Notes |
|
|
219
266
|
|---|---|---|
|
|
220
|
-
| `create` | `--context <c>`, `--role-id <id>`, `--name <n>`, `--actions <csv>` (all required); `--data-scope <json>`, `--description <text
|
|
221
|
-
| `list` | `--context <c>` (required) | |
|
|
222
|
-
| `get` | `--context <c>`, `--role-id <id>` (both required) | Also shows the role's `assumable` grant
|
|
223
|
-
| `delete` | `--context <c>`, `--role-id <id>` (both required) | No cascade — any access profile still referencing the role fails to mint (not silently downgraded) until reassigned. |
|
|
267
|
+
| `create` | `--context <c>`, `--role-id <id>`, `--name <n>`, `--actions <csv>` (all required); `--data-scope <json>`, `--assumable <json>`, `--assignable-role <roleId>` (repeatable), `--description <text>`, `--tenant test\|live` | Idempotent by `roleId`. `--assignable-role` restricts WHICH named roles this clause may compose into a delegated AccessProfile — orthogonal to `--data-scope`, which bounds data reach rather than role composition. Omit it for no restriction; the platform rejects an empty list, so there is no way to spell "compose nothing". Once a clause carries the restriction it can compose only the roles it names, so name every roleId it still needs before adding the flag. `access explain` reports the restriction for clauses that carry one. `--data-scope` confines the clause to owned data — a JSON map of ownership dimension → allowed values, ANDed across keys (e.g. `{"userId":["${{ self.userId }}"]}`, or `{"scope:org":["<orgId>", null]}` — the `null` entry additionally admits scope-less/tenant-shared records). Keys must be `userId` or `scope:<namespace>` (the legacy flat `orgId`/`clientId` keys are rejected server-side). `--assumable` is a SEPARATE, role-level grant — a JSON map of `scope:<namespace>` → allowed values naming which values a holder of this role may switch into via `vectros assume`; holding broad `--data-scope` reach in a namespace does not by itself grant assuming any value in it. |
|
|
268
|
+
| `list` | `--context <c>` (required); `--tenant test\|live` | Add `--tenant test` to read a context that lives in the test tenant — a blueprint applied there is otherwise unreadable. |
|
|
269
|
+
| `get` | `--context <c>`, `--role-id <id>` (both required); `--tenant test\|live` | Also shows the role's `assumable` grant, if any is set. |
|
|
270
|
+
| `delete` | `--context <c>`, `--role-id <id>` (both required); `--tenant test\|live` | No cascade — any access profile still referencing the role fails to mint (not silently downgraded) until reassigned. |
|
|
224
271
|
|
|
225
272
|
`create`/`list`/`get` accept `--json`; `delete` does not.
|
|
226
273
|
|
|
274
|
+
## `vectros scripts` — the script source a trigger fires, or your app runs directly
|
|
275
|
+
|
|
276
|
+
A stored script has two consumers, and this is how the source gets there for both. A **trigger rule**
|
|
277
|
+
names it in `scriptRef` and runs it under the trigger's own grant; that `scriptRef` is validated when
|
|
278
|
+
the rule is DECLARED rather than when it fires, so a script has to exist before any rule can name it.
|
|
279
|
+
**Your own app** runs a stored version directly through `POST /v1/scripts/execute`, under its own
|
|
280
|
+
credential, committing everything the script wrote as a single transaction.
|
|
281
|
+
|
|
282
|
+
Direct execution is separately granted: the calling role needs `scripts:x`, and without it the call
|
|
283
|
+
is a `403` that cannot say the missing grant is the reason. The platform accepts it bare (every
|
|
284
|
+
script in the context) or qualified to one name (`scripts:x:create-case`). A **blueprint** may use
|
|
285
|
+
only the qualified form — its scope gate refuses a bare `scripts:x` and there is no wildcard — so a
|
|
286
|
+
blueprint that ships a script grants exactly the one it ships. See *The scope gate* below, and
|
|
287
|
+
[`AUTHORING.md`](./AUTHORING.md) for where that grant goes in a blueprint.
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
vectros scripts push notify-assignee --context my-app --source-file ./notify.js
|
|
291
|
+
vectros scripts push notify-assignee --context my-app --source-file ./notify.js --if-changed
|
|
292
|
+
vectros scripts list --context my-app # every script, newest first
|
|
293
|
+
vectros scripts list --context my-app --name notify-assignee # every version of one name
|
|
294
|
+
vectros scripts get 3b1c9f2e7a5d… --context my-app # one version, with its source
|
|
295
|
+
vectros scripts delete 3b1c9f2e7a5d… --context my-app # ONE version (prompts)
|
|
296
|
+
vectros scripts delete 3b1c9f2e7a5d… --context my-app --force # ...or skip the prompt, e.g. in CI
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
| Subcommand | Flags | Notes |
|
|
300
|
+
|---|---|---|
|
|
301
|
+
| `push` | `--context <c>`, `--source-file <path>` (both required); `--input-contract <text>` or `--input-contract-file <path>`, `--if-changed`, `--tenant <test\|live>` | **Always stores a new version by default** — pushing is not idempotent, and each push gets the next server-assigned version number, which the output reports. `--if-changed` reads the current version first and pushes only on a genuine difference; use it where the same push re-runs, e.g. in CI. `--source-file` is capped at 300,000 UTF-8 bytes and the input contract at 50,000, both checked locally so an oversized push fails immediately instead of sending the body. The input contract is descriptive only — nothing validates a firing against it. Nothing parses the source either, so a syntax error surfaces the first time the version runs. |
|
|
302
|
+
| `list` | `--context <c>` (required); `--name <name>`, `--tenant <test\|live>` | With `--name`, every version of that name, oldest first; without, every script in the context, newest first. The human view shows a byte count instead of the source — a listing would otherwise print the full text of every version. `--json` includes the source. |
|
|
303
|
+
| `get` | `--context <c>` (required) + the version id; `--tenant <test\|live>` | Shows one version, including its full source. Version ids are opaque — a deterministic hex string, not a prefixed or random id — so copy them from `scripts list`. |
|
|
304
|
+
| `delete` | `--context <c>` (required) + the version id; `--force`, `--tenant <test\|live>` | Deletes ONE version; other versions of the same name are untouched. **Irreversible — the stored source is the only copy** — so it asks you to type the version id back before proceeding. `--force` skips that, and is REQUIRED in a non-interactive shell rather than the prompt being silently waived there. `--json` emits `{id, deleted}` on stdout; the warning always goes to stderr, so it stays visible without breaking a pipe. ⚠️ Deleting a name's LATEST version reuses its identity — the next push of that name recomputes the same version number and the same id, so a pinned reference silently resolves to different source. |
|
|
305
|
+
|
|
306
|
+
There is no `scripts update` — a stored version is immutable, and the platform's update route always
|
|
307
|
+
rejects. Supersede a version by pushing a new one.
|
|
308
|
+
|
|
309
|
+
`--context` is required on all four. Scripts are context-scoped and a trigger resolves its
|
|
310
|
+
`scriptRef` inside its own context, so a defaulted context would push where nobody asked and leave
|
|
311
|
+
the rule that wanted it still unresolvable. `--tenant` defaults to `live`; pass `--tenant test` to
|
|
312
|
+
reach the versions a `blueprint-test --tenant test` run created.
|
|
313
|
+
|
|
314
|
+
⚠️ **`delete` is not reference-checked.** Nothing verifies whether a trigger rule still points at the
|
|
315
|
+
version being removed, and the two outcomes differ: a rule pinned to that version fails on every
|
|
316
|
+
subsequent firing, while a rule on `version: latest` silently resolves to the PREVIOUS version and
|
|
317
|
+
carries on running older code with nothing to indicate it. This CLI has no verb that lists trigger
|
|
318
|
+
rules, so it cannot answer that question for you — check against the API before removing a version a
|
|
319
|
+
rule may still use.
|
|
320
|
+
|
|
321
|
+
A blueprint can ship a script instead of pushing one — see `scripts:` in
|
|
322
|
+
[`@vectros-ai/blueprints`](https://www.npmjs.com/package/@vectros-ai/blueprints). An apply is
|
|
323
|
+
convergent (an unchanged source pushes nothing) and never deletes: removing a `scripts:` entry
|
|
324
|
+
leaves every version in place, which is why this command group exists to remove one deliberately.
|
|
325
|
+
|
|
326
|
+
## `vectros assume` — switch identity namespace values on your own token
|
|
327
|
+
|
|
328
|
+
Re-mints your own verb token with one or more `identity.<namespace>` values
|
|
329
|
+
switched — the "org switcher" flow, named (like the platform endpoint it
|
|
330
|
+
calls) after AWS STS `AssumeRole`. Only works if a role you currently hold
|
|
331
|
+
explicitly grants assuming the requested value(s) via that role's own
|
|
332
|
+
`--assumable` grant (checked live against your CURRENT roles, never a value
|
|
333
|
+
frozen into your token at mint time).
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
vectros assume --context my-app --scope org:org_sales
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
| Flags | Notes |
|
|
340
|
+
|---|---|
|
|
341
|
+
| `--context <c>` (required), `--scope <ns:value>` (repeatable, at least one required) | Each `--scope` becomes one `scope:<namespace>` entry in the request. No artificial cap on how many namespaces you name — the platform requires a SINGLE role to jointly grant the whole combination and rejects an unsatisfiable one itself. |
|
|
342
|
+
|
|
343
|
+
Prints the re-minted `access_token`/`expires_in`, plus the resolved `identity` and `allowedActions` the platform returns alongside it — what you actually assumed, without decoding the token (accepts `--json`) — there
|
|
344
|
+
is no `--to-keyring` here, since the result is an ephemeral bearer token
|
|
345
|
+
with the same remaining lifetime as the verb token this command itself
|
|
346
|
+
mints to make the call, not a durable secret the keyring is built to hold.
|
|
347
|
+
|
|
227
348
|
## `vectros access` — bind principals to contexts
|
|
228
349
|
|
|
229
350
|
The binding layer between an identity and a context: `access grant` joins a
|
|
@@ -249,11 +370,11 @@ shares this grammar.
|
|
|
249
370
|
|
|
250
371
|
| Subcommand | Flags | Notes |
|
|
251
372
|
|---|---|---|
|
|
252
|
-
| `grant` | `--principal <p>`, `--context <c>` (required); exactly one of `--role <roleId>` (repeatable — 2+ compose additively) / `--actions <csv>`; `--scope <ns:value>` (repeatable), `--identity-overrides <json
|
|
253
|
-
| `revoke` | `--principal <p>`, `--context <c>` (required) | |
|
|
254
|
-
| `list` | Exactly one of `--context <c>` / `--principal <p
|
|
255
|
-
| `get` | `--principal <p>`, `--context <c>` (required) | |
|
|
256
|
-
| `explain` | `--principal <p>`, `--context <c>` (required) | Resolves the binding's clauses (following every referenced role and concatenating their clauses, if bound via one or more) and renders a concrete **access matrix** — a canned set of record-ownership archetypes (yours alone, another user's, org-shared, scope-less, …) evaluated against the clauses. A client-side preview for authoring-time sanity, not the platform's own evaluator: it models READ reachability only — a `c`/`u`/`d` action shown as granted is advisory, not a guarantee a specific write will succeed, since the platform's write rule (a clause must explicitly name a dimension to place a value into it) diverges from its read rule (an unmentioned dimension never narrows reads). |
|
|
373
|
+
| `grant` | `--principal <p>`, `--context <c>` (required); exactly one of `--role <roleId>` (repeatable — 2+ compose additively) / `--actions <csv>`; `--scope <ns:value>` (repeatable), `--identity-overrides <json>`, `--tenant test\|live` | Idempotent **upsert** — re-granting an existing binding updates it to the newly-declared shape (role(s)/actions/overrides), it does not no-op. Repeating `--role` composes: `--role case-handler --role hr-admin` grants each role's own clauses, concatenated in the order given — never merged, so each clause keeps meaning exactly what its own author wrote. `--scope`/`--identity-overrides` extend the principal's stamped identity (e.g. `--scope org:<id>` → `scope:org`) — a credential can only stamp ownership its identity carries, so granting write access to org-scoped data requires that scope on the profile. When both are given, `--identity-overrides` wins on key conflicts. |
|
|
374
|
+
| `revoke` | `--principal <p>`, `--context <c>` (required); `--tenant test\|live` | |
|
|
375
|
+
| `list` | Exactly one of `--context <c>` / `--principal <p>`; `--tenant test\|live` | `--context` lists the context's members; `--principal` lists every context that principal is bound to. |
|
|
376
|
+
| `get` | `--principal <p>`, `--context <c>` (required); `--tenant test\|live` | |
|
|
377
|
+
| `explain` | `--principal <p>`, `--context <c>` (required); `--tenant test\|live` | Resolves the binding's clauses (following every referenced role and concatenating their clauses, if bound via one or more) and renders a concrete **access matrix** — a canned set of record-ownership archetypes (yours alone, another user's, org-shared, scope-less, …) evaluated against the clauses. A client-side preview for authoring-time sanity, not the platform's own evaluator: it models READ reachability only — a `c`/`u`/`d` action shown as granted is advisory, not a guarantee a specific write will succeed, since the platform's write rule (a clause must explicitly name a dimension to place a value into it) diverges from its read rule (an unmentioned dimension never narrows reads). |
|
|
257
378
|
|
|
258
379
|
`grant`/`list`/`get`/`explain` accept `--json`; `revoke` does not.
|
|
259
380
|
|
|
@@ -409,11 +530,11 @@ CLI runs under an elevated ephemeral bridge token. So the trust boundary is
|
|
|
409
530
|
**this binary, not the blueprint**: a hardcoded **scope gate** bounds every
|
|
410
531
|
mint to a data-plane allowlist (`records` / `schemas` / `search` / `documents`
|
|
411
532
|
/ `folders` / `inference` / `entities`). Most control-plane scope (keys /
|
|
412
|
-
app-contexts / users / billing / admin / namespaces) or a
|
|
413
|
-
**hard-rejects** — the CLI mints nothing and exits non-zero. There is
|
|
533
|
+
app-contexts / users / billing / admin / namespaces / triggers) or a
|
|
534
|
+
`*` wildcard **hard-rejects** — the CLI mints nothing and exits non-zero. There is
|
|
414
535
|
intentionally **no override flag**.
|
|
415
536
|
|
|
416
|
-
**
|
|
537
|
+
**Two narrow, deliberate exceptions. The first:** `profiles` (which configures who has
|
|
417
538
|
access to what — control-plane by nature) admits its `r`/`u`/`d` operations,
|
|
418
539
|
because the platform enforces an unconditional check that a write to an
|
|
419
540
|
AccessProfile can never grant more scope than the requesting credential
|
|
@@ -431,16 +552,26 @@ requires. A literal-qualified `profiles:c:usr_<id>` naming another principal
|
|
|
431
552
|
still hard-rejects regardless of `member-lifecycle`, same as every other
|
|
432
553
|
control-plane scope.
|
|
433
554
|
|
|
555
|
+
**The second exception:** `scripts:x:<script-name>` — execute one named script — is admitted, so a
|
|
556
|
+
blueprint that ships a script in its `scripts:` block can also grant permission to run it. Execution
|
|
557
|
+
consults no data scope at all, and every write the script makes is still checked one by one against
|
|
558
|
+
the caller's own permissions, so this widens *what can be composed*, never *what data is reachable*.
|
|
559
|
+
Everything else on the resource still hard-rejects: a bare `scripts:x`, because it would mean "run
|
|
560
|
+
every script in this context, including ones added later that your reviewer never saw"; `scripts:x:*`,
|
|
561
|
+
because there is **no wildcard form** — the qualifier is compared to the script name exactly, so such
|
|
562
|
+
a grant would store cleanly and then refuse every execution; and `scripts:c`, authoring, which stays
|
|
563
|
+
a design-time admin surface. `triggers` is unchanged.
|
|
564
|
+
|
|
434
565
|
### Capabilities (`granted_capabilities`) — a separate, equally-gated dimension
|
|
435
566
|
|
|
436
567
|
A blueprint's `accessProfile` and role clauses may also declare
|
|
437
568
|
`capabilities: [...]` — named platform effects (like creating or removing an
|
|
438
569
|
identity in your tenant) that reach across a partition boundary in a way no
|
|
439
570
|
`resource:ops` verb can express. This rides a field entirely separate from
|
|
440
|
-
`allowedActions`, and it has its own allowlist:
|
|
441
|
-
admitted today.** Every other capability name —
|
|
442
|
-
own registry recognizes — is refused, the same
|
|
443
|
-
posture as the scope gate above. `vectros blueprint validate`/`plan` and
|
|
571
|
+
`allowedActions`, and it has its own allowlist: **`member-lifecycle` and
|
|
572
|
+
`delegate-principal-stamp` are admitted today.** Every other capability name —
|
|
573
|
+
including ones the platform's own registry recognizes — is refused, the same
|
|
574
|
+
fail-closed, deliberately-widen posture as the scope gate above. `vectros blueprint validate`/`plan` and
|
|
444
575
|
`bootstrap` all report a rejected capability the same way they report a
|
|
445
576
|
rejected scope: nothing mints, and the CLI exits non-zero.
|
|
446
577
|
|