@vectros-ai/cli 0.24.0 → 0.25.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
@@ -55,8 +55,10 @@ A name must be 2–50 chars: a lowercase letter, then lowercase letters / digits
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
@@ -301,10 +303,11 @@ namespaces:
301
303
  # (user, record, document, entity, self, tenant,
302
304
  # context, scope, versions, lookup) are rejected.
303
305
  # 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
306
+ # names, not built-ins, registered like any other.
307
+ # Signup does not register them. An older account may
308
+ # still hold tenant-wide rows at rank 1000/2000 (below),
309
+ # so pick a different specificityRank there; a
310
+ # context-owned registration shadows a tenant-wide
308
311
  # one for this context's own callers.
309
312
  specificityRank: 500 # 0..1_000_000, unique among this blueprint's namespaces
310
313
  entityBacked: true # optional
@@ -549,8 +552,9 @@ Omit it and the call is a 403 that cannot tell you the grant is what's missing.
549
552
  See *The scope gate* for why the bare and wildcard forms are refused.
550
553
 
551
554
  **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
555
+ and re-run `vectros bootstrap`, or run `vectros blueprint apply --prune`, and its rule and
556
+ access profile are deleted, so a trigger you removed cannot keep acting.
557
+ `vectros blueprint apply` without `--prune` deletes nothing. Its service principal is kept, so re-adding the same
554
558
  trigger reattaches to one identity. Removing a `scripts:` entry deletes nothing
555
559
  — see *The idempotency contract*, below, for why.
556
560
 
package/CHANGELOG.md CHANGED
@@ -3,6 +3,73 @@
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.25.0 — 2026-10-12
7
+
8
+ ### Breaking changes
9
+
10
+ - **⚠️ BREAKING: `vectros provider-config create|update` no longer have `--platform-token-factor` and
11
+ `--platform-output-weight`, and `PLATFORM_BILLED` is no longer a billing mode.** A provider you configure bills you
12
+ directly, so `CUSTOMER_DIRECT` is the only mode, and `--billing-mode` is optional on `create` and defaults to it. A
13
+ script that passes either removed flag stops with commander's `unknown option` error (exit 1).
14
+ `--billing-mode PLATFORM_BILLED` stops locally (exit 2, nothing sent) with a message that the mode is retired, and any
15
+ other value is refused locally with a message that the mode must be `CUSTOMER_DIRECT`. The key-value output of `get`,
16
+ `create` and `update` no longer lists the two rates. **Migration:** delete the two flags from your scripts; for a
17
+ `PLATFORM_BILLED` config that already exists, `vectros provider-config update <id> --billing-mode CUSTOMER_DIRECT`
18
+ moves it to the only supported mode.
19
+
20
+ ### Added
21
+
22
+ - **`vectros identity create|list|get|delete|invite` accept `--tenant test|live`**, as `identity list-all`, `context`,
23
+ `key` and the other tenant-aware verbs do.
24
+
25
+ ### Changed
26
+
27
+ - **`vectros blueprint --help`, `AUTHORING.md` and the comments `vectros blueprint init` writes into the scaffold state what the scope gate enforces.** They say a
28
+ bare `profiles:c` is accepted only when the same clause declares the `member-lifecycle` capability (and is rejected
29
+ without it, and as `profiles:c:usr_<id>` always), that `scripts:x:<script-name>` is admitted, and that
30
+ `member-lifecycle` and `delegate-principal-stamp` are the two capabilities the gate admits. The scaffold's content is
31
+ otherwise the same.
32
+ - **`vectros provider-config` help and README state what the platform accepts for a config you set up yourself
33
+ (platform 0.47.0 and later).** Such a config takes only `--billing-mode CUSTOMER_DIRECT`, the
34
+ `byo-model-inference:customer-key` waiver type and a plain `https://` endpoint on port 443; the platform refuses any
35
+ other waiver type and any other port with a `400`. On `update` only a change to one of these is refused; a value the
36
+ config already holds is accepted. Changing an endpoint's host needs `--api-key-secret-file` again. An account owner
37
+ signs the risk waiver in the developer portal (Model Providers); the CLI does not sign it. An account that has accepted
38
+ the Business Associate Agreement with Vectros in the developer portal also needs an owner to attest each config in the
39
+ portal before a call can use it; the CLI does not attest configs.
40
+ - **`vectros provider-config create --alias` and `update --alias` state the alias rule (platform 0.47.0 and later).** A
41
+ new or changed alias is 1 to 64 lowercase letters, digits, `-` or `_`; the platform answers `400` with the rule
42
+ otherwise. An alias a config already holds is accepted. `update --provider-type` help also states that changing the
43
+ type of an existing config needs `--endpoint` stated in the same command (`--endpoint ''` means the provider's
44
+ default); the platform answers `400` otherwise.
45
+ - **`vectros context destroy` says a blueprint's service principal stays.** The teardown covers the context's own data;
46
+ 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`
47
+ (with `--tenant test` for a test-tenant context) for the cleanup.
48
+ - **`vectros --version` reports SDK 0.47.0.**
49
+ - **The bundled `@vectros-ai/blueprints` is 0.20.2** (documentation and comments only; behaviour is unchanged).
50
+ - **Source comments state their rules themselves.** Comments that pointed at internal design documents now
51
+ say what the rule is, so the source maps that ship with the package read on their own. No behavior changes.
52
+
53
+ ### Fixed
54
+
55
+ - **`vectros access grant --actions` and `vectros join --actions` work.** Both sent an empty `roleIds` list next to the
56
+ inline actions, and the platform answers that with `400` ("'roleIds' must name at least one role when present"), so
57
+ only `--role` worked. The request now carries `roleIds` only when you pass `--role`, and `scopes` only when you pass
58
+ `--actions`.
59
+ - **`--principal me` (and `join --as me`, `access explain --principal me`) resolve your own principal in the tenant you
60
+ target (needs platform 0.47.0 or later).** Your principal in the test tenant has a
61
+ different id from your live user, so `access grant --principal me --tenant test` can now join you to your own context.
62
+ On an earlier platform the live tenant behaves as before and the test tenant typically stops with a message that names
63
+ `vectros identity list-all --tenant test` and the flag to pass the id with. The "your user id" line of `whoami`, which
64
+ shows the live tenant, uses the same lookup.
65
+ - **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`;
66
+ it exited 1 with the raw `token endpoint HTTP 400` response. The "no credential" message names the same command.
67
+ - **A refresh token the sign-in service rotates is kept,** so the next command finds its session valid.
68
+ - **`vectros bootstrap` and `blueprint apply` check for an existing service principal before creating anything.** A
69
+ refusal leaves nothing behind and a re-run starts clean; the plan preview lists the principal before the context. If
70
+ creating the context then fails (for example a plan limit), the principal this run created stays, and the re-run asks
71
+ you to confirm using it: pass `--confirm-existing-principal`.
72
+
6
73
  ## 0.24.0 — 2026-10-01
7
74
 
8
75
  ### Added
package/README.md CHANGED
@@ -14,7 +14,7 @@ The Vectros provisioning CLI — the `vectros` binary:
14
14
  - **`vectros provider-config`** — create, list, get, update, and delete BYOM (bring-your-own-model) tenant-level provider configs.
15
15
  - **`vectros domains`** — register a domain, list your registrations, and prove DNS TXT ownership.
16
16
  - **`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).
17
+ - **`vectros identity`** — manage principals: users (tenant-wide) and identity entities in the namespaces you register (`org`, `client`, or your own).
18
18
  - **`vectros role`** — define reusable, context-scoped access rules.
19
19
  - **`vectros scripts`** — push, list, inspect, and delete the versioned script source a trigger
20
20
  fires, or your own app executes directly.
@@ -211,7 +211,7 @@ vectros blueprint-test task-management --keep
211
211
  ```
212
212
 
213
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.
214
+ errored; `2` = no credential, or the scope gate or the capability gate refused the blueprint.
215
215
  The app-context is intentionally **not** deleted (it's idempotent and reused).
216
216
 
217
217
  ## `vectros context` — app contexts
@@ -225,7 +225,7 @@ vectros context create my-app --name "My App" # idempotent — re-running conv
225
225
  vectros context list # every context in the tenant
226
226
  vectros context list --mine # only contexts you hold an access profile in
227
227
  vectros context get my-app
228
- vectros context destroy my-app # irreversible — asks you to re-type the id
228
+ vectros context destroy my-app # irreversible — asks you to re-type the id (a blueprint's service principal stays: see `identity delete`)
229
229
  ```
230
230
 
231
231
  | Subcommand | Flags | Notes |
@@ -233,7 +233,7 @@ vectros context destroy my-app # irreversible — asks you to r
233
233
  | `create <contextId>` | `--name <name>` (defaults to the id), `--tenant test\|live` | Idempotent by `contextId`; re-running converges. |
234
234
  | `list` | `--mine`, `--tenant test\|live` | Default lists every context in the tenant; `--mine` narrows to contexts you hold an active access profile in. |
235
235
  | `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. |
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. 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
237
 
238
238
  All four accept `--json` for machine-readable output, plus the common auth
239
239
  flags (`--base-url`, `--token`).
@@ -285,7 +285,7 @@ platform-billed models. A tenant may hold more than one, each independently sele
285
285
 
286
286
  ```bash
287
287
  vectros provider-config create --tenant live --alias primary \
288
- --provider-type ANTHROPIC_NATIVE --billing-mode CUSTOMER_DIRECT \
288
+ --provider-type ANTHROPIC_NATIVE \
289
289
  --waiver-type byo-model-inference:customer-key \
290
290
  --api-key-secret-file ./anthropic-key.txt
291
291
  vectros provider-config list --tenant live
@@ -296,10 +296,10 @@ vectros provider-config delete pc_abc123 # re-types the id to confirm
296
296
 
297
297
  | Subcommand | Flags | Notes |
298
298
  |---|---|---|
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`. |
299
+ | `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
300
  | `list` | `--tenant test\|live` | Every config in one tenant — there is no combined test+live view. |
301
301
  | `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. |
302
+ | `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
303
  | `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
304
 
305
305
  **There is deliberately no `--api-key-secret <value>` flag** — a live provider credential on the
@@ -307,6 +307,28 @@ command line is visible in shell history and the process list. Pass `--api-key-s
307
307
  <path>` (`-` for stdin instead of a file), the same shape `issuers verify
308
308
  --idp-token-file` already uses for a different live credential.
309
309
 
310
+ **Billing mode.** `CUSTOMER_DIRECT` (your provider bills you) is the only billing mode: Vectros does not bill
311
+ for a model provider you configure. `--billing-mode PLATFORM_BILLED` is refused locally as retired, and the old
312
+ `--platform-token-factor` and `--platform-output-weight` flags no longer exist (the CLI reports them as unknown
313
+ options). A script that passed `--billing-mode CUSTOMER_DIRECT` keeps working, and the flag may be left out.
314
+
315
+ **What the platform accepts for a config you set up yourself (platform 0.47.0 and later).** An account that sets a
316
+ config up itself is refused by the platform with a `400` for anything but `--billing-mode CUSTOMER_DIRECT`, the
317
+ `byo-model-inference:customer-key` waiver type, and a plain `https://` endpoint on port 443 (no credentials,
318
+ query or fragment). On `update`, only a **change** to one of those is refused; a value the config already
319
+ holds is accepted. Changing an endpoint's host needs the key again (`--api-key-secret-file`). The key must
320
+ be printable ASCII, at most 4096 characters. A tenant holds at most 25 provider configs.
321
+
322
+ The CLI does not sign the risk waiver: an account owner signs it in the developer portal, under **Model
323
+ Providers**, for each environment (live or test). A config is usable only while that waiver is in force for
324
+ your account. It is not in force when it is neither signed nor granted to your account, when it was signed
325
+ against a text that has since changed, or when it has been revoked. A waiver that Vectros granted to your tenant,
326
+ for which your tenant has no signed acceptance, is not affected by any of these. The portal shows which of these
327
+ applies. An account that has accepted the Business Associate Agreement with Vectros in the developer portal needs one more thing before a call can
328
+ use a config: an account owner attests that config in the developer portal (Model Providers). Until then the
329
+ platform refuses the call with a `403` that says it needs a BAA-holder attestation. The CLI does not attest
330
+ configs.
331
+
310
332
  All five accept `--json`, plus the common auth flags (`--base-url`, `--token`).
311
333
 
312
334
  ## `vectros domains` — domain ownership verification
@@ -366,13 +388,18 @@ No `update`/`delete` — both routes require a genuine root API key on the
366
388
  platform side, which this CLI never holds, so neither could ever succeed
367
389
  here. All three subcommands accept `--json`.
368
390
 
369
- ## `vectros identity` — tenant-wide principals
391
+ ## `vectros identity` — principals
370
392
 
371
393
  Manage the principals a context's access profiles and roles reference: users
372
394
  (the one fixed principal surface, `--type user`) and namespaced identity
373
395
  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.
396
+ <namespace>`). Users, and entities in a tenant-wide namespace, are **tenant-wide**:
397
+ `context destroy` leaves them in place. An entity in a namespace registered to one
398
+ context (a blueprint registers its namespaces to its own context by default) belongs
399
+ to that context and is erased with it. A blueprint can declare identities in its
400
+ `identities:` block; apply ensures every declared identity exists (created under the bootstrap credential,
401
+ before the context's own credentials), and where an entity lives follows its namespace, as above. A role
402
+ references an identity by id.
376
403
 
377
404
  ```bash
378
405
  vectros identity create --type user --external-id alice@example.com --email alice@example.com
@@ -386,22 +413,22 @@ vectros identity invite --context my-app --role editor --email bob@example.com \
386
413
 
387
414
  | Subcommand | Flags | Notes |
388
415
  |---|---|---|
389
- | `create` | `--type <user\|namespace>` (required), `--external-id <id>` (required — your stable idempotency key) | Idempotent, upsert-by-`externalId`. |
416
+ | `create` | `--type <user\|namespace>` (required), `--external-id <id>` (required — your stable idempotency key), `--tenant test\|live` | Idempotent, upsert-by-`externalId`. |
390
417
  | | `--type user` only: `--email <email>`, `--service` (mint a SERVICE principal; default HUMAN) | `--name`/`--scope` are rejected for `--type user`. |
391
418
  | | entity namespaces only: `--name <name>`, `--scope <ns:value>` (repeatable, up to 2 parent ownership edges) | `--email`/`--service` are rejected for an entity namespace. |
392
419
  | | 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. |
420
+ | `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
421
  | `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. |
422
+ | `get` | `--type <user\|namespace>` (required), `--id <vectrosId>` (required), `--tenant test\|live` | `--id` is the Vectros-assigned id from `create`/`list`, not the `--external-id`. |
423
+ | `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. |
424
+ | `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
425
 
399
426
  `create`/`list`/`list-all`/`get`/`invite` accept `--json`; `delete` does not.
400
427
 
401
428
  ## `vectros role` — reusable, context-scoped access rules
402
429
 
403
430
  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
431
+ via `vectros access grant --principal <p> --context <c> --role <roleId>` — the same rule can be shared
405
432
  across many principals. `role create` builds a **single-clause** role from a
406
433
  flat `--actions` list; author a multi-clause role (several rules unioned
407
434
  together) in a blueprint's `roles:` block instead.
@@ -522,7 +549,7 @@ vectros access revoke --principal usr_<id> --context my-app
522
549
 
523
550
  `--principal` accepts `usr_<userId>`, `key_<keyId>`, or the alias `me` (your
524
551
  own signed-in user, resolved from your login) — every `access` subcommand
525
- shares this grammar.
552
+ 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
553
 
527
554
  | Subcommand | Flags | Notes |
528
555
  |---|---|---|
@@ -537,7 +564,7 @@ shares this grammar.
537
564
  ## `vectros join` — enroll and keep the credential, in one step
538
565
 
539
566
  `join` is a **composition verb**: everything it does already exists as a
540
- discrete command (`access grant`, `key issue`, `keyring add`) — `join` runs
567
+ discrete command (`access grant`, then `key issue --to-keyring`) — `join` runs
541
568
  them in order against one context and records the outcome as a keyring
542
569
  entry, so `vectros switch <alias>` (and the MCP server / recall hook) can act
543
570
  as that principal from then on.
@@ -555,11 +582,12 @@ vectros join my-app --as usr_<id> --role editor --alias teammate
555
582
  | `--actions <csv>` | Inline single-clause grant. XOR with `--role`. |
556
583
  | `--as me\|usr_<id>` (default `me`) | Which principal to enroll — `me` resolves to your own signed-in user. |
557
584
  | `--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). |
585
+ | `--alias <name>` | Keyring entry name (defaults to the context id; with `--tenant test` the entry is stored as `<alias>.test`). |
586
+ | `--tenant test\|live` (default `live`) | Which tenant the context lives in. |
559
587
  | `--activate` / `--no-activate` | Force (or skip) making the freshly-stored key this machine's active identity. |
560
588
 
561
589
  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
590
+ 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
591
  when the local secret is missing does it rotate to obtain a fresh one.
564
592
 
565
593
  ## `vectros key` — the scoped API key (`ssk_*`) lifecycle
@@ -583,7 +611,8 @@ vectros key rotate --principal usr_<id> --context my-app # revok
583
611
  is optional and stripped if present) — narrower than `access`'s grammar: there
584
612
  is no `me` alias on any `key` subcommand, and a key never authenticates as a
585
613
  `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
614
+ --type user` (or `vectros whoami`; both show your live tenant, and for the test tenant
615
+ use `vectros identity list-all --tenant test`), or grant/issue via `vectros join`, which
587
616
  does resolve `--as me`.
588
617
 
589
618
  A minted secret has exactly two destinations, and the flags for choosing one
@@ -612,7 +641,7 @@ accept `--json`, `revoke` does not.
612
641
  A machine can hold several Vectros identities (one per context or principal).
613
642
  The keyring stores them as named entries plus one **active** pointer, so you can
614
643
  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
644
+ 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
645
  `--to-keyring` (which stores the new secret instead of printing it); `vectros switch
617
646
  <alias>` flips the active one.
618
647