@vectros-ai/cli 0.24.0 → 0.26.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 CHANGED
@@ -8,7 +8,7 @@ ready-to-use data model + a narrow `ssk_*` scoped key — no root key, no manual
8
8
  admin steps. `vectros blueprint apply <file> --tenant test|live` applies the same
9
9
  blueprint WITHOUT minting a key, for re-applying a change from CI.
10
10
 
11
- This guide is the authoring loop. Everything here except `bootstrap` and `blueprint apply` is
11
+ This guide is the authoring loop. Everything here except `blueprint-test`, `bootstrap` and `blueprint apply` is
12
12
  **creds-free and offline** — you can iterate on a blueprint with no account.
13
13
 
14
14
  > The examples use the bare `vectros` binary — install it once with
@@ -22,7 +22,7 @@ vectros blueprint init my-thing # 1. scaffold a starter
22
22
  # …edit ./my-thing.blueprint.yaml…
23
23
  vectros blueprint validate ./my-thing.blueprint.yaml # 2. structural + scope-gate + lint (offline)
24
24
  vectros blueprint plan ./my-thing.blueprint.yaml # 3. preview what it provisions (offline)
25
- vectros blueprint-test ./my-thing.blueprint.yaml --env staging # 4. prove it live (creds; auto-teardown)
25
+ vectros blueprint-test ./my-thing.blueprint.yaml --tenant test # 4. prove it live (creds; auto-teardown)
26
26
  vectros bootstrap --blueprint ./my-thing.blueprint.yaml # 5. apply for real + mint a key (needs a bridge token)
27
27
  vectros blueprint apply ./my-thing.blueprint.yaml --tenant test # 5'. apply for real, no key (CI: add --yes --confirm-existing-principal)
28
28
  ```
@@ -50,13 +50,15 @@ A name must be 2–50 chars: a lowercase letter, then lowercase letters / digits
50
50
  `validate` runs three layers and exits non-zero on any failure:
51
51
 
52
52
  1. **Structural** — the file parses against the blueprint schema (readable
53
- `path: message` errors, e.g. `schemas[0].fields[1]: Required`).
53
+ `path: message` errors, e.g. `schemas[0].fields[1].fieldType: Required`).
54
54
  2. **Scope + capability gates** — the requested `allowedActions` are data-plane
55
55
  only. This is the trust boundary: `keys`/`users`/`admin`/`app-contexts`/
56
56
  `namespaces` and the `*` wildcard are **hard-rejected, no override**.
57
57
  `profiles` is a narrower exception — `r`/`u`/`d` are admitted, and `c` only
58
- when qualified `profiles:c:self` (create a profile for yourself; a bare
59
- `profiles:c`, or one naming another principal, still hard-rejects).
58
+ as `profiles:c:self` (create a profile for yourself) or as a bare
59
+ `profiles:c` on a clause that also declares the `member-lifecycle`
60
+ capability; a bare `profiles:c` without it, and `profiles:c:usr_<id>` naming
61
+ another principal, hard-reject.
60
62
  `scripts` is the other — `scripts:x:<script-name>` (execute one named
61
63
  script) is admitted, and nothing else on that resource is: a bare
62
64
  `scripts:x`, a wildcard `scripts:x:*`, and `scripts:c` (authoring) all
@@ -95,12 +97,14 @@ A name must be 2–50 chars: a lowercase letter, then lowercase letters / digits
95
97
 
96
98
  ```
97
99
  Plan for blueprint 'my-thing' (v1.0.0) → context 'mcp':
98
- + schema 'thing' (1 field, HYBRID) lookup: [title]
99
- + context 'mcp' (MCP — my-thing)
100
+ ...
100
101
  + principal 'my-thing-sp' (SERVICE)
102
+ + context 'mcp' (MCP — my-thing)
103
+ + schema 'thing' (1 field, HYBRID) lookup: [title]
101
104
  + profile scopes [records:r, records:c, records:u, search:r, schemas:r] (data-plane ✓)
102
105
  + key my-thing-<machine> (mint 1 scoped ssk_*)
103
- + seed 1 record(s)
106
+ + seed 1 record
107
+ 6 step(s). This is a preview — nothing was minted.
104
108
  ```
105
109
 
106
110
  **A blueprint whose `accessProfile` composes roles (`roleIds:`) previews every
@@ -109,9 +113,10 @@ sharing an action set can still carry different `dataScope`:
109
113
 
110
114
  ```
111
115
  Plan for blueprint 'my-thing' (v1.0.0) → context 'mcp':
112
- + schema 'thing' (1 field, HYBRID) lookup: [title]
113
- + context 'mcp' (MCP — my-thing)
116
+ ...
114
117
  + principal 'my-thing-sp' (SERVICE)
118
+ + context 'mcp' (MCP — my-thing)
119
+ + schema 'thing' (1 field, HYBRID) lookup: [title]
115
120
  + role 'case-handler' (1 clause, actions [records:cru])
116
121
  clause 1: actions [records:cru], dataScope: scope:org=[${{ self.scope.org }}]
117
122
  + role 'hr-admin' (1 clause, actions [records:r])
@@ -119,7 +124,8 @@ sharing an action set can still carry different `dataScope`:
119
124
  assumable: scope:org=[org_engineering, org_sales]
120
125
  + profile via role(s) [case-handler, hr-admin] (composed — each role's own clauses, concatenated in order)
121
126
  + key my-thing-<machine> (mint 1 scoped ssk_*)
122
- + seed 1 record(s)
127
+ + seed 1 record
128
+ 8 step(s). This is a preview — nothing was minted.
123
129
  ```
124
130
 
125
131
  A role's `assumable` line only appears when the blueprint's top-level
@@ -162,13 +168,13 @@ everything it created:
162
168
 
163
169
  ```
164
170
  vectros login # sign in once (or pass --token)
165
- vectros blueprint-test ./my-thing.blueprint.yaml --env staging
171
+ vectros blueprint-test ./my-thing.blueprint.yaml --tenant test
166
172
  vectros blueprint-test task-management --keep # leave the graph in place to inspect it
167
173
  ```
168
174
 
169
175
  Teardown is the default and runs even if an assertion fails. It is **created-only** — a pre-apply
170
176
  snapshot means it never deletes a schema or AccessProfile that already existed, so it's safe to point at
171
- a shared staging tenant. (It does **not** delete the app-context: that's idempotent and reused.) Exit
177
+ a tenant other people use. (It does **not** delete the app-context: that's idempotent and reused.) Exit
172
178
  `0` = applied + every assertion passed; `1` = an assertion failed or apply errored; `2` = no credential,
173
179
  or the blueprint asked for control-plane scope (the same gate `validate` enforces). Run this before you
174
180
  ship a blueprint — it catches an unusable or mis-scoped credential that the offline checks can't.
@@ -301,10 +307,11 @@ namespaces:
301
307
  # (user, record, document, entity, self, tenant,
302
308
  # context, scope, versions, lookup) are rejected.
303
309
  # org/client are NOT in that set -- reserved namespace
304
- # names, not built-ins, registered like any other. They
305
- # already exist tenant-wide at rank 1000/2000 (below) --
306
- # pick a different specificityRank, and note a
307
- # context-owned registration shadows the tenant-wide
310
+ # names, not built-ins, registered like any other.
311
+ # Signup does not register them. An older account may
312
+ # still hold tenant-wide rows at rank 1000/2000 (below),
313
+ # so pick a different specificityRank there; a
314
+ # context-owned registration shadows a tenant-wide
308
315
  # one for this context's own callers.
309
316
  specificityRank: 500 # 0..1_000_000, unique among this blueprint's namespaces
310
317
  entityBacked: true # optional
@@ -364,6 +371,16 @@ identities:
364
371
  YAML or JSON are both accepted (chosen by extension). The bundled library is
365
372
  authored in TypeScript; YAML is the authoring affordance for your own.
366
373
 
374
+ ### Inputs — values you supply when you install
375
+
376
+ A blueprint can declare a top-level `inputs:` block (each input has a `type` of `string`, `number` or `boolean`, and may
377
+ have `required`, `default` and `description`) and use the values in any string with `${{ inputs.<name> }}`. The built-ins
378
+ `${{ vectros.context }}` and `${{ vectros.suffix }}` are the blueprint's literal `contextId` and a short stable token derived
379
+ from it. Supply values to `validate`, `plan` and `apply` with `--set name=value` (repeatable) or `--values <file>` (a YAML or
380
+ JSON map); `--set` wins over `--values`, which wins over the declared `default`. An unknown input, a missing value with no
381
+ default, or a value of the wrong type stops the command before any request is sent. Write `$${{` for a literal `${{`. The
382
+ `@vectros-ai/blueprints` README describes the block in full.
383
+
367
384
  ### Choosing lookup indexes — a permanent decision
368
385
 
369
386
  `lookupFields` are how you reach records *directly* — by exact value, by an
@@ -421,7 +438,6 @@ each tell you "records with this status" or "records with this area", but never
421
438
 
422
439
  ```yaml
423
440
  lookupFields:
424
- - externalId
425
441
  - { fieldNames: [status, area], sortBy: createdAt, allowOverflow: true }
426
442
  ```
427
443
 
@@ -549,8 +565,9 @@ Omit it and the call is a 403 that cannot tell you the grant is what's missing.
549
565
  See *The scope gate* for why the bare and wildcard forms are refused.
550
566
 
551
567
  **Triggers are the one block an apply reconciles by absence.** Remove an entry
552
- and re-apply, and its rule and access profile are deleted, so a trigger you
553
- removed cannot keep acting. Its service principal is kept, so re-adding the same
568
+ and re-run `vectros bootstrap`, or run `vectros blueprint apply --prune`, and its rule and
569
+ access profile are deleted, so a trigger you removed cannot keep acting.
570
+ `vectros blueprint apply` without `--prune` deletes nothing. Its service principal is kept, so re-adding the same
554
571
  trigger reattaches to one identity. Removing a `scripts:` entry deletes nothing
555
572
  — see *The idempotency contract*, below, for why.
556
573
 
package/CHANGELOG.md CHANGED
@@ -3,6 +3,113 @@
3
3
  All notable changes to `@vectros-ai/cli` are documented here.
4
4
  This project adheres to [Semantic Versioning](https://semver.org).
5
5
 
6
+ ## 0.26.0 — 2026-10-25
7
+
8
+ ### Added
9
+
10
+ - **`vectros whoami --tenant test|live`** shows the session for that tenant (default `live`). The output gains a `tenant type`
11
+ line and `--json` gains `tenantType`. The test tenant shows your own user id there, which needs platform 0.47.0 or later and
12
+ shows `—` on an earlier platform. Any other value stops with a usage error (exit 2).
13
+ - **`vectros access list --principal` prints a note on standard error naming the context it searched.** The command searches
14
+ the `default` context, and depending on the caller the platform may not look beyond it, so an empty result means none in
15
+ `default`; the note gives the `vectros access get --principal … --context <c>` command to check another. Standard output
16
+ and `--json` are unchanged. A `2>&1 | jq` pipeline fails on the note; redirect standard error instead.
17
+
18
+ ### Fixed
19
+
20
+ - **A blueprint file whose trigger `dataScope` uses `${{ input.userId }}` or `${{ input.scope.<namespace> }}` loads in
21
+ `validate`, `plan`, `apply` and `bootstrap`.** The platform resolves these placeholders against the record that fires the
22
+ trigger.
23
+
24
+ ### Changed
25
+
26
+ - **`vectros --version` reports SDK 0.48.0.**
27
+ - **`vectros blueprint apply` stops with the platform's message when another request changes the same item at the same
28
+ moment.** When apply updates a schema, role or trigger rule, the platform answers `409` (`VERSION_CONFLICT`) instead of
29
+ overwriting the other change. Run apply again once the other change has finished.
30
+ - **`vectros provider-config create` and `update` stop with the platform's `503` when it cannot store the provider key.**
31
+ Nothing is saved; run the command again shortly.
32
+ - **The bundled `@vectros-ai/blueprints` is 0.20.3, and the package description covers the whole command set.**
33
+ - **The comments in the shipped code describe the code as it is today.** Behaviour is unchanged, except for the text of the
34
+ error for a missing Cognito client id.
35
+
36
+ ### Documentation
37
+
38
+ - **The README documents `vectros login`, `logout`, `whoami` and `switch`, and `vectros keyring prune --dry-run`.**
39
+ - **The README says `vectros blueprint-test` exits `1` for a blueprint that cannot be read, and that
40
+ `vectros provider-config create` and `update` stop with the platform's `503` when it cannot store the provider key.**
41
+ - **The authoring guide describes the `inputs:` block of a blueprint: its types, `required` and `default`, `--set` and
42
+ `--values`, and the `vectros.context` and `vectros.suffix` built-ins.**
43
+ - **The authoring guide shows an abbreviated `plan` output, with the steps in the order they run and the closing step count, and
44
+ uses `--tenant test` for `blueprint-test`.**
45
+
46
+ ## 0.25.0 — 2026-10-12
47
+
48
+ ### Breaking changes
49
+
50
+ - **⚠️ BREAKING: `vectros provider-config create|update` no longer have `--platform-token-factor` and
51
+ `--platform-output-weight`, and `PLATFORM_BILLED` is no longer a billing mode.** A provider you configure bills you
52
+ directly, so `CUSTOMER_DIRECT` is the only mode, and `--billing-mode` is optional on `create` and defaults to it. A
53
+ script that passes either removed flag stops with commander's `unknown option` error (exit 1).
54
+ `--billing-mode PLATFORM_BILLED` stops locally (exit 2, nothing sent) with a message that the mode is retired, and any
55
+ other value is refused locally with a message that the mode must be `CUSTOMER_DIRECT`. The key-value output of `get`,
56
+ `create` and `update` no longer lists the two rates. **Migration:** delete the two flags from your scripts; for a
57
+ `PLATFORM_BILLED` config that already exists, `vectros provider-config update <id> --billing-mode CUSTOMER_DIRECT`
58
+ moves it to the only supported mode.
59
+
60
+ ### Added
61
+
62
+ - **`vectros identity create|list|get|delete|invite` accept `--tenant test|live`**, as `identity list-all`, `context`,
63
+ `key` and the other tenant-aware verbs do.
64
+
65
+ ### Changed
66
+
67
+ - **`vectros blueprint --help`, `AUTHORING.md` and the comments `vectros blueprint init` writes into the scaffold state what the scope gate enforces.** They say a
68
+ bare `profiles:c` is accepted only when the same clause declares the `member-lifecycle` capability (and is rejected
69
+ without it, and as `profiles:c:usr_<id>` always), that `scripts:x:<script-name>` is admitted, and that
70
+ `member-lifecycle` and `delegate-principal-stamp` are the two capabilities the gate admits. The scaffold's content is
71
+ otherwise the same.
72
+ - **`vectros provider-config` help and README state what the platform accepts for a config you set up yourself
73
+ (platform 0.47.0 and later).** Such a config takes only `--billing-mode CUSTOMER_DIRECT`, the
74
+ `byo-model-inference:customer-key` waiver type and a plain `https://` endpoint on port 443; the platform refuses any
75
+ other waiver type and any other port with a `400`. On `update` only a change to one of these is refused; a value the
76
+ config already holds is accepted. Changing an endpoint's host needs `--api-key-secret-file` again. An account owner
77
+ signs the risk waiver in the developer portal (Model Providers); the CLI does not sign it. An account that has accepted
78
+ the Business Associate Agreement with Vectros in the developer portal also needs an owner to attest each config in the
79
+ portal before a call can use it; the CLI does not attest configs.
80
+ - **`vectros provider-config create --alias` and `update --alias` state the alias rule (platform 0.47.0 and later).** A
81
+ new or changed alias is 1 to 64 lowercase letters, digits, `-` or `_`; the platform answers `400` with the rule
82
+ otherwise. An alias a config already holds is accepted. `update --provider-type` help also states that changing the
83
+ type of an existing config needs `--endpoint` stated in the same command (`--endpoint ''` means the provider's
84
+ default); the platform answers `400` otherwise.
85
+ - **`vectros context destroy` says a blueprint's service principal stays.** The teardown covers the context's own data;
86
+ the service principal a blueprint bootstrap created is tenant-wide, so it is left in place. The command's help names `identity delete`, and its success output names `identity list-all` and `identity delete`
87
+ (with `--tenant test` for a test-tenant context) for the cleanup.
88
+ - **`vectros --version` reports SDK 0.47.0.**
89
+ - **The bundled `@vectros-ai/blueprints` is 0.20.2** (documentation and comments only; behaviour is unchanged).
90
+ - **Source comments state their rules themselves.** Comments that pointed at internal design documents now
91
+ say what the rule is, so the source maps that ship with the package read on their own. No behavior changes.
92
+
93
+ ### Fixed
94
+
95
+ - **`vectros access grant --actions` and `vectros join --actions` work.** Both sent an empty `roleIds` list next to the
96
+ inline actions, and the platform answers that with `400` ("'roleIds' must name at least one role when present"), so
97
+ only `--role` worked. The request now carries `roleIds` only when you pass `--role`, and `scopes` only when you pass
98
+ `--actions`.
99
+ - **`--principal me` (and `join --as me`, `access explain --principal me`) resolve your own principal in the tenant you
100
+ target (needs platform 0.47.0 or later).** Your principal in the test tenant has a
101
+ different id from your live user, so `access grant --principal me --tenant test` can now join you to your own context.
102
+ On an earlier platform the live tenant behaves as before and the test tenant typically stops with a message that names
103
+ `vectros identity list-all --tenant test` and the flag to pass the id with. The "your user id" line of `whoami`, which
104
+ shows the live tenant, uses the same lookup.
105
+ - **A login session that has expired or been revoked says so and exits 2.** A command that needs sign-in says the session has expired or was revoked and names `vectros login`;
106
+ it exited 1 with the raw `token endpoint HTTP 400` response. The "no credential" message names the same command.
107
+ - **A refresh token the sign-in service rotates is kept,** so the next command finds its session valid.
108
+ - **`vectros bootstrap` and `blueprint apply` check for an existing service principal before creating anything.** A
109
+ refusal leaves nothing behind and a re-run starts clean; the plan preview lists the principal before the context. If
110
+ creating the context then fails (for example a plan limit), the principal this run created stays, and the re-run asks
111
+ you to confirm using it: pass `--confirm-existing-principal`.
112
+
6
113
  ## 0.24.0 — 2026-10-01
7
114
 
8
115
  ### Added
package/README.md CHANGED
@@ -5,6 +5,8 @@
5
5
 
6
6
  The Vectros provisioning CLI — the `vectros` binary:
7
7
 
8
+ - **`vectros login`** / **`logout`** / **`whoami`** — sign in through your browser (`--no-browser` prints the URL instead, for headless machines), clear the stored session, and show the signed-in session for a tenant (`--tenant test|live`, default `live`; `--json`).
9
+ - **`vectros switch <alias>`** — shortcut for `keyring use`.
8
10
  - **`vectros bootstrap`** — provision a least-privilege MCP credential (+ optional blueprint).
9
11
  - **`vectros blueprint`** — author + inspect blueprints (`init` / `validate` / `plan` / `list`, creds-free), and
10
12
  **`apply`** one into a tenant without minting a key (for CI).
@@ -14,7 +16,7 @@ The Vectros provisioning CLI — the `vectros` binary:
14
16
  - **`vectros provider-config`** — create, list, get, update, and delete BYOM (bring-your-own-model) tenant-level provider configs.
15
17
  - **`vectros domains`** — register a domain, list your registrations, and prove DNS TXT ownership.
16
18
  - **`vectros namespace`** — register a new scope namespace, and list/get existing ones.
17
- - **`vectros identity`** — manage tenant-wide principals (users, orgs, clients, and namespaces you register).
19
+ - **`vectros identity`** — manage principals: users (tenant-wide) and identity entities in the namespaces you register (`org`, `client`, or your own).
18
20
  - **`vectros role`** — define reusable, context-scoped access rules.
19
21
  - **`vectros scripts`** — push, list, inspect, and delete the versioned script source a trigger
20
22
  fires, or your own app executes directly.
@@ -210,8 +212,8 @@ vectros blueprint-test ./my-thing.blueprint.yaml --tenant test
210
212
  vectros blueprint-test task-management --keep
211
213
  ```
212
214
 
213
- Exit `0` = applied and every assertion passed; `1` = an assertion failed or apply
214
- errored; `2` = no credential, or the blueprint requested control-plane scope.
215
+ Exit `0` = applied and every assertion passed; `1` = an assertion failed, apply
216
+ errored, or the blueprint could not be read; `2` = no credential, or the scope gate or the capability gate refused the blueprint.
215
217
  The app-context is intentionally **not** deleted (it's idempotent and reused).
216
218
 
217
219
  ## `vectros context` — app contexts
@@ -225,7 +227,7 @@ vectros context create my-app --name "My App" # idempotent — re-running conv
225
227
  vectros context list # every context in the tenant
226
228
  vectros context list --mine # only contexts you hold an access profile in
227
229
  vectros context get my-app
228
- vectros context destroy my-app # irreversible — asks you to re-type the id
230
+ vectros context destroy my-app # irreversible — asks you to re-type the id (a blueprint's service principal stays: see `identity delete`)
229
231
  ```
230
232
 
231
233
  | Subcommand | Flags | Notes |
@@ -233,7 +235,7 @@ vectros context destroy my-app # irreversible — asks you to r
233
235
  | `create <contextId>` | `--name <name>` (defaults to the id), `--tenant test\|live` | Idempotent by `contextId`; re-running converges. |
234
236
  | `list` | `--mine`, `--tenant test\|live` | Default lists every context in the tenant; `--mine` narrows to contexts you hold an active access profile in. |
235
237
  | `get <contextId>` | `--tenant test\|live` | |
236
- | `destroy <contextId>` | `--force`, `--tenant test\|live` | **Irreversible.** Permanently erases the context and ALL of its records, documents, folders, schemas, roles, and access profiles, and revokes its scoped keys up front. Interactively you must re-type the context id to confirm; `--force` skips the prompt and is **required** when stdin is not a TTY (scripts/CI). Reports `status: purging` — the teardown itself runs asynchronously. |
238
+ | `destroy <contextId>` | `--force`, `--tenant test\|live` | **Irreversible.** Permanently erases the context and ALL of its records, documents, folders, schemas, roles, and access profiles, and revokes its scoped keys up front. Interactively you must re-type the context id to confirm; `--force` skips the prompt and is **required** when stdin is not a TTY (scripts/CI). Reports `status: purging` — the teardown itself runs asynchronously. A blueprint's service principal belongs to the whole tenant and is NOT removed; delete it with `identity delete` (find it with `identity list-all`). |
237
239
 
238
240
  All four accept `--json` for machine-readable output, plus the common auth
239
241
  flags (`--base-url`, `--token`).
@@ -285,7 +287,7 @@ platform-billed models. A tenant may hold more than one, each independently sele
285
287
 
286
288
  ```bash
287
289
  vectros provider-config create --tenant live --alias primary \
288
- --provider-type ANTHROPIC_NATIVE --billing-mode CUSTOMER_DIRECT \
290
+ --provider-type ANTHROPIC_NATIVE \
289
291
  --waiver-type byo-model-inference:customer-key \
290
292
  --api-key-secret-file ./anthropic-key.txt
291
293
  vectros provider-config list --tenant live
@@ -296,10 +298,10 @@ vectros provider-config delete pc_abc123 # re-types the id to confirm
296
298
 
297
299
  | Subcommand | Flags | Notes |
298
300
  |---|---|---|
299
- | `create` | `--alias`, `--provider-type ANTHROPIC_NATIVE\|OPENAI_COMPATIBLE`, `--billing-mode PLATFORM_BILLED\|CUSTOMER_DIRECT`, `--waiver-type`, `--api-key-secret-file <path>` (all required); `--endpoint`, `--declared-processing-region`, `--default-provider-model-id`, `--platform-token-factor`, `--platform-output-weight` (required together, only under `PLATFORM_BILLED`), `--status`, `--tenant test\|live` | NOT idempotent — a repeat `--alias` in the same tenant 400s. `--endpoint` is required for `OPENAI_COMPATIBLE` (no default — every self-hosted deployment is a distinct URL) and an optional override for `ANTHROPIC_NATIVE`; it must be an `https://` URL whose host resolves to a public address. `--status ACTIVE` (the default) requires the tenant already hold a granted waiver for `--waiver-type`. |
301
+ | `create` | `--alias`, `--provider-type ANTHROPIC_NATIVE\|OPENAI_COMPATIBLE`, `--waiver-type`, `--api-key-secret-file <path>` (all required); `--billing-mode CUSTOMER_DIRECT` (optional: the default, and the only value), `--endpoint`, `--declared-processing-region`, `--default-provider-model-id`, `--status`, `--tenant test\|live` | NOT idempotent — a repeat `--alias` in the same tenant 400s. `--alias` is 1 to 64 lowercase letters, digits, `-` or `_`; the platform refuses anything else with a `400` (callers must send it exactly, the match is case-sensitive). `--endpoint` is required for `OPENAI_COMPATIBLE` (no default — every self-hosted deployment is a distinct URL) and an optional override for `ANTHROPIC_NATIVE`; it must be an `https://` URL whose host resolves to a public address. `--status ACTIVE` (the default) requires the tenant already hold a granted waiver for `--waiver-type`. |
300
302
  | `list` | `--tenant test\|live` | Every config in one tenant — there is no combined test+live view. |
301
303
  | `get <id>` | | One config's metadata. The secret is never returned by the platform, on this or any other verb. |
302
- | `update <id>` | Same fields as `create` except `--tenant`, all optional | Partial update — an omitted field (`--api-key-secret-file` included) leaves the stored value unchanged. Switching `--billing-mode CUSTOMER_DIRECT` clears the platform pricing fields server-side regardless of what else is sent. |
304
+ | `update <id>` | Same fields as `create` except `--tenant`, all optional | Partial update — an omitted field leaves the stored value unchanged. A new `--alias` must follow the same rule as on `create`; an alias the config already holds is accepted, so a config created before the rule keeps working. Changing `--provider-type` of an existing config needs `--endpoint` stated in the same command (`--endpoint ''` means the provider's default); without it the platform answers `400`, because a stored endpoint would otherwise keep overriding the Anthropic default. One exception to the omitted-field rule: `--endpoint` naming a different host also needs `--api-key-secret-file` (the platform answers `400` rather than send the stored key to a new host). |
303
305
  | `delete <id>` | `--force` | Re-types the id to confirm (mirrors `issuers delete`); `--force` skips the prompt and is required when stdin is not a TTY. |
304
306
 
305
307
  **There is deliberately no `--api-key-secret <value>` flag** — a live provider credential on the
@@ -307,6 +309,28 @@ command line is visible in shell history and the process list. Pass `--api-key-s
307
309
  <path>` (`-` for stdin instead of a file), the same shape `issuers verify
308
310
  --idp-token-file` already uses for a different live credential.
309
311
 
312
+ **Billing mode.** `CUSTOMER_DIRECT` (your provider bills you) is the only billing mode: Vectros does not bill
313
+ for a model provider you configure. `--billing-mode PLATFORM_BILLED` is refused locally as retired, and the old
314
+ `--platform-token-factor` and `--platform-output-weight` flags no longer exist (the CLI reports them as unknown
315
+ options). A script that passed `--billing-mode CUSTOMER_DIRECT` keeps working, and the flag may be left out.
316
+
317
+ **What the platform accepts for a config you set up yourself (platform 0.47.0 and later).** An account that sets a
318
+ config up itself is refused by the platform with a `400` for anything but `--billing-mode CUSTOMER_DIRECT`, the
319
+ `byo-model-inference:customer-key` waiver type, and a plain `https://` endpoint on port 443 (no credentials,
320
+ query or fragment). On `update`, only a **change** to one of those is refused; a value the config already
321
+ holds is accepted. Changing an endpoint's host needs the key again (`--api-key-secret-file`). The key must
322
+ be printable ASCII, at most 4096 characters. A tenant holds at most 25 provider configs. If the platform cannot store the key it answers `503`, nothing is saved, and the command stops with that message; run it again shortly.
323
+
324
+ The CLI does not sign the risk waiver: an account owner signs it in the developer portal, under **Model
325
+ Providers**, for each environment (live or test). A config is usable only while that waiver is in force for
326
+ your account. It is not in force when it is neither signed nor granted to your account, when it was signed
327
+ against a text that has since changed, or when it has been revoked. A waiver that Vectros granted to your tenant,
328
+ for which your tenant has no signed acceptance, is not affected by any of these. The portal shows which of these
329
+ applies. An account that has accepted the Business Associate Agreement with Vectros in the developer portal needs one more thing before a call can
330
+ use a config: an account owner attests that config in the developer portal (Model Providers). Until then the
331
+ platform refuses the call with a `403` that says it needs a BAA-holder attestation. The CLI does not attest
332
+ configs.
333
+
310
334
  All five accept `--json`, plus the common auth flags (`--base-url`, `--token`).
311
335
 
312
336
  ## `vectros domains` — domain ownership verification
@@ -366,13 +390,18 @@ No `update`/`delete` — both routes require a genuine root API key on the
366
390
  platform side, which this CLI never holds, so neither could ever succeed
367
391
  here. All three subcommands accept `--json`.
368
392
 
369
- ## `vectros identity` — tenant-wide principals
393
+ ## `vectros identity` — principals
370
394
 
371
395
  Manage the principals a context's access profiles and roles reference: users
372
396
  (the one fixed principal surface, `--type user`) and namespaced identity
373
397
  entities (`org`, `client`, or any namespace you registered, via `--type
374
- <namespace>`). Identities are **tenant-wide and orthogonal to context** — a
375
- blueprint or role *references* one by id, it never creates one.
398
+ <namespace>`). Users, and entities in a tenant-wide namespace, are **tenant-wide**:
399
+ `context destroy` leaves them in place. An entity in a namespace registered to one
400
+ context (a blueprint registers its namespaces to its own context by default) belongs
401
+ to that context and is erased with it. A blueprint can declare identities in its
402
+ `identities:` block; apply ensures every declared identity exists (created under the bootstrap credential,
403
+ before the context's own credentials), and where an entity lives follows its namespace, as above. A role
404
+ references an identity by id.
376
405
 
377
406
  ```bash
378
407
  vectros identity create --type user --external-id alice@example.com --email alice@example.com
@@ -386,22 +415,22 @@ vectros identity invite --context my-app --role editor --email bob@example.com \
386
415
 
387
416
  | Subcommand | Flags | Notes |
388
417
  |---|---|---|
389
- | `create` | `--type <user\|namespace>` (required), `--external-id <id>` (required — your stable idempotency key) | Idempotent, upsert-by-`externalId`. |
418
+ | `create` | `--type <user\|namespace>` (required), `--external-id <id>` (required — your stable idempotency key), `--tenant test\|live` | Idempotent, upsert-by-`externalId`. |
390
419
  | | `--type user` only: `--email <email>`, `--service` (mint a SERVICE principal; default HUMAN) | `--name`/`--scope` are rejected for `--type user`. |
391
420
  | | entity namespaces only: `--name <name>`, `--scope <ns:value>` (repeatable, up to 2 parent ownership edges) | `--email`/`--service` are rejected for an entity namespace. |
392
421
  | | both: `--metadata <json>` | Free-form JSON object. |
393
- | `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. |
422
+ | `list` | `--type <user\|namespace>` (required), `--external-id <id>` (filter), `--limit <n>`, `--tenant test\|live` | 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. |
394
423
  | `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. |
395
- | `get` | `--type <user\|namespace>` (required), `--id <vectrosId>` (required) | `--id` is the Vectros-assigned id from `create`/`list`, not the `--external-id`. |
396
- | `delete` | `--type <user\|namespace>` (required), `--id <vectrosId>` (required) | |
397
- | `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` (and an `acceptLink` when you passed `--accept-url`) for you to deliver yourself. Whenever it is supplied, the accept URL must be an `https://` URL with a domain-name host, never an IP literal. |
424
+ | `get` | `--type <user\|namespace>` (required), `--id <vectrosId>` (required), `--tenant test\|live` | `--id` is the Vectros-assigned id from `create`/`list`, not the `--external-id`. |
425
+ | `delete` | `--type <user\|namespace>` (required), `--id <vectrosId>` (required), `--tenant test\|live` | The way to remove the service principal a blueprint bootstrap created: `context destroy` removes the context's data but leaves that principal, because principals belong to the whole tenant. Find its id with `identity list-all`, and delete it only if nothing else uses it. |
426
+ | `invite` | `--context <c>`, `--role <roleId>`, `--email <e>` (all required); `--first-name <n>`, `--accept-url <url>`, `--no-send-email`, `--tenant test\|live` | 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` (and an `acceptLink` when you passed `--accept-url`) for you to deliver yourself. Whenever it is supplied, the accept URL must be an `https://` URL with a domain-name host, never an IP literal. |
398
427
 
399
428
  `create`/`list`/`list-all`/`get`/`invite` accept `--json`; `delete` does not.
400
429
 
401
430
  ## `vectros role` — reusable, context-scoped access rules
402
431
 
403
432
  A Role is a reusable, identity-agnostic scope rule bound to a principal later
404
- via `vectros access grant --role <roleId>` — the same rule can be shared
433
+ via `vectros access grant --principal <p> --context <c> --role <roleId>` — the same rule can be shared
405
434
  across many principals. `role create` builds a **single-clause** role from a
406
435
  flat `--actions` list; author a multi-clause role (several rules unioned
407
436
  together) in a blueprint's `roles:` block instead.
@@ -514,15 +543,27 @@ vectros access grant --principal me --context my-app --role editor
514
543
  vectros access grant --principal usr_<id> --context my-app --actions records:r,search:r \
515
544
  --scope org:<orgId>
516
545
  vectros access list --context my-app # who's in this context
517
- vectros access list --principal me # which contexts I'm bound to
546
+ vectros access list --principal me # which contexts I'm bound to (see the note below)
518
547
  vectros access get --principal me --context my-app
519
548
  vectros access explain --principal me --context my-app
520
549
  vectros access revoke --principal usr_<id> --context my-app
521
550
  ```
522
551
 
552
+ `access list --principal` searches one context, `default`, because the command's token is pinned to it:
553
+ depending on the caller the platform may not look beyond that context, so a grant you hold in another
554
+ context can be missing from the list, and an empty list means no grant in `default`, not no grants
555
+ anywhere. The command says so on standard error, so `--json` output on standard output stays a bare array,
556
+ and it drops the line when a row from another context shows the lookup did span contexts. To see a grant
557
+ in a specific context, run `vectros access get --principal me --context <context-id>` (add `--tenant test`
558
+ for the test tenant), or `vectros access list --context <context-id>` for everyone in it.
559
+
560
+ The line is also written with `--json`, and there is no switch to turn it off. A script that merges standard
561
+ error into standard output (`2>&1`) will see one extra line and a `2>&1 | jq` pipeline will fail on it;
562
+ redirect standard error (`2>/dev/null`) to drop it.
563
+
523
564
  `--principal` accepts `usr_<userId>`, `key_<keyId>`, or the alias `me` (your
524
565
  own signed-in user, resolved from your login) — every `access` subcommand
525
- shares this grammar.
566
+ shares this grammar. In the **test tenant** your principal has a different id from your live user, so `me` resolves it per tenant: add `--tenant test` and it still works, e.g. `vectros access grant --principal me --context <context-id> --role <role> --tenant test`. This needs platform 0.47.0 or later; on an earlier platform the command typically stops and points you to `vectros identity list-all --tenant test` (an owner login), which shows the id to pass as `--principal usr_<id>`.
526
567
 
527
568
  | Subcommand | Flags | Notes |
528
569
  |---|---|---|
@@ -537,7 +578,7 @@ shares this grammar.
537
578
  ## `vectros join` — enroll and keep the credential, in one step
538
579
 
539
580
  `join` is a **composition verb**: everything it does already exists as a
540
- discrete command (`access grant`, `key issue`, `keyring add`) — `join` runs
581
+ discrete command (`access grant`, then `key issue --to-keyring`) — `join` runs
541
582
  them in order against one context and records the outcome as a keyring
542
583
  entry, so `vectros switch <alias>` (and the MCP server / recall hook) can act
543
584
  as that principal from then on.
@@ -555,11 +596,12 @@ vectros join my-app --as usr_<id> --role editor --alias teammate
555
596
  | `--actions <csv>` | Inline single-clause grant. XOR with `--role`. |
556
597
  | `--as me\|usr_<id>` (default `me`) | Which principal to enroll — `me` resolves to your own signed-in user. |
557
598
  | `--scope <ns:value>` | Repeatable. Places the member under a scope for this context (an identity override on the profile) so their key can WRITE that scope's data. |
558
- | `--alias <name>` | Keyring entry name (defaults to the context id). |
599
+ | `--alias <name>` | Keyring entry name (defaults to the context id; with `--tenant test` the entry is stored as `<alias>.test`). |
600
+ | `--tenant test\|live` (default `live`) | Which tenant the context lives in. |
559
601
  | `--activate` / `--no-activate` | Force (or skip) making the freshly-stored key this machine's active identity. |
560
602
 
561
603
  Re-joining an alias whose secret is still held locally is idempotent: it
562
- re-grants (a no-op upsert) and re-activates without churning the key. Only
604
+ re-grants (a no-op upsert) and re-activates without churning the key (a `--tenant test` join does not displace an active live identity unless you pass `--activate`). Only
563
605
  when the local secret is missing does it rotate to obtain a fresh one.
564
606
 
565
607
  ## `vectros key` — the scoped API key (`ssk_*`) lifecycle
@@ -583,7 +625,8 @@ vectros key rotate --principal usr_<id> --context my-app # revok
583
625
  is optional and stripped if present) — narrower than `access`'s grammar: there
584
626
  is no `me` alias on any `key` subcommand, and a key never authenticates as a
585
627
  `key_*` principal. Look your own id up first with `vectros identity list
586
- --type user` (or `vectros whoami`), or grant/issue via `vectros join`, which
628
+ --type user` (or `vectros whoami`; both show your live tenant, and for the test tenant
629
+ use `vectros whoami --tenant test` or `vectros identity list-all --tenant test`), or grant/issue via `vectros join`, which
587
630
  does resolve `--as me`.
588
631
 
589
632
  A minted secret has exactly two destinations, and the flags for choosing one
@@ -601,7 +644,7 @@ are shared by `issue` and `rotate`:
601
644
  | `issue` | `--principal <p>`, `--context <c>` (required); `--name <n>` (default `default`), `--label <l>`, `--tenant test\|live`, destination flags above | Idempotent on `(tenant, context, principal, name)` — re-issuing an existing tuple returns the key **without** the secret (never re-disclosed); the CLI detects that and points you at `rotate`. |
602
645
  | `list` | `--principal <p>`, `--context <c>`, `--tenant test\|live` **or** `--all-contexts` (owner-only; spans both tenants, not combinable with `--tenant`) | **The default lists only ONE context's keys** (the token's context — `default`, or `--context`), not your whole account — easy to miss since the active-key limit is tenant-wide across all contexts. `--all-contexts` is the true account-wide view. |
603
646
  | `get <keyId>` | `--context <c>`, `--tenant test\|live` | `--context` pins the verb token to the key's context — omit it for a key bound to a non-default context (e.g. one `bootstrap` minted) and the lookup 404s. |
604
- | `revoke <keyId>` | `--context <c>`, `--tenant test\|live` | Soft-delete; ~5 minutes to take effect (authorizer cache). Same `--context` pinning caveat as `get`. |
647
+ | `revoke <keyId>` | `--context <c>`, `--tenant test\|live` | Soft-delete; it takes about 5 minutes to take effect. Same `--context` pinning caveat as `get`. |
605
648
  | `rotate` | `--principal <p>`, `--context <c>` (required); `--name <n>`, `--force-new`, `--tenant test\|live`, destination flags above | Revokes the matching active key (by the exact `(context, principal, name)` tuple), then mints a fresh one — there is no dedicated rotate endpoint. **Default-name guard:** if no key is currently active under `--name` (default `default`) but the principal/context has *other* active keys, `rotate` refuses rather than silently minting an unrelated second key — pass the right `--name` or `--force-new` to proceed anyway. |
606
649
 
607
650
  `issue`/`rotate` have no `--json` (use `--format json` instead); `list`/`get`
@@ -612,7 +655,7 @@ accept `--json`, `revoke` does not.
612
655
  A machine can hold several Vectros identities (one per context or principal).
613
656
  The keyring stores them as named entries plus one **active** pointer, so you can
614
657
  switch between them without re-issuing keys. `vectros join <context>` adds an
615
- entry and activates it; `vectros key issue`/`key rotate` add one when passed
658
+ entry and activates it (a `--tenant test` join keeps an active live identity unless you pass `--activate`); `vectros key issue`/`key rotate` add one when passed
616
659
  `--to-keyring` (which stores the new secret instead of printing it); `vectros switch
617
660
  <alias>` flips the active one.
618
661
 
@@ -623,6 +666,7 @@ vectros keyring show # the active entry's fields + its key
623
666
  vectros keyring show --format raw # JUST the secret, for scripts (see below)
624
667
  vectros keyring doctor # is the keyring healthy? which keys are live?
625
668
  vectros keyring prune # drop entries whose stored secret is gone
669
+ vectros keyring prune --dry-run # report what would be removed, remove nothing
626
670
  vectros keyring remove <alias> # drop one entry + its secret
627
671
  ```
628
672