@vectros-ai/cli 0.16.0 → 0.19.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 +56 -11
- package/CHANGELOG.md +191 -0
- package/README.md +66 -7
- package/THIRD-PARTY-NOTICES.md +4 -4
- package/dist/cli.js +895 -161
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +895 -161
- package/dist/cli.mjs.map +1 -1
- package/package.json +3 -3
package/AUTHORING.md
CHANGED
|
@@ -87,6 +87,30 @@ A name must be 2–50 chars: a lowercase letter, then lowercase letters / digits
|
|
|
87
87
|
+ seed 1 record(s)
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
+
**A blueprint whose `accessProfile` composes roles (`roleIds:`) previews every
|
|
91
|
+
composed role's clauses individually**, not flattened, because two clauses
|
|
92
|
+
sharing an action set can still carry different `dataScope`:
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
Plan for blueprint 'my-thing' (v1.0.0) → context 'mcp':
|
|
96
|
+
+ schema 'thing' (2 fields, HYBRID) lookup: [externalId]
|
|
97
|
+
+ context 'mcp' (MCP — my-thing)
|
|
98
|
+
+ principal 'my-thing-sp' (SERVICE)
|
|
99
|
+
+ role 'case-handler' (1 clause, actions [records:cru])
|
|
100
|
+
clause 1: actions [records:cru], dataScope: scope:org=[${{ self.scope.org }}]
|
|
101
|
+
+ role 'hr-admin' (1 clause, actions [records:r])
|
|
102
|
+
clause 1: actions [records:r], dataScope: (none — unconstrained)
|
|
103
|
+
assumable: scope:org=[org_engineering, org_sales]
|
|
104
|
+
+ profile via role(s) [case-handler, hr-admin] (composed — each role's own clauses, concatenated in order)
|
|
105
|
+
+ key my-thing-<machine> (mint 1 scoped ssk_*)
|
|
106
|
+
+ seed 1 record(s)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
A role's `assumable` line only appears when the blueprint's top-level
|
|
110
|
+
`roleAssumable:` map names that role — it's shown because it's
|
|
111
|
+
security-relevant (what a holder of the role may become via `POST
|
|
112
|
+
/v1/auth/token/assume`), not just that the role was declared.
|
|
113
|
+
|
|
90
114
|
`bootstrap` shows the same plan and asks for confirmation before minting
|
|
91
115
|
(`--yes` skips the prompt for CI/agents). ⚠️ **`--no-merge` is NOT a dry run** — it still
|
|
92
116
|
mints/reconciles for real; it only changes where the resulting credential goes, printing the MCP
|
|
@@ -174,6 +198,16 @@ accessProfile:
|
|
|
174
198
|
# resource:ops verb can't express (e.g. creating/removing an identity in
|
|
175
199
|
# your tenant). Only 'member-lifecycle' is admitted today; every other name
|
|
176
200
|
# is hard-rejected, same posture as the scope gate. See "Capabilities" below.
|
|
201
|
+
#
|
|
202
|
+
# Instead of allowedActions (+ optional dataScope/capabilities), accessProfile
|
|
203
|
+
# may declare `roleIds: [<roleId>, ...]` — one or more roles declared below,
|
|
204
|
+
# in `roles:`, composed additively (each role's own clauses, concatenated in
|
|
205
|
+
# the order listed; a single roleId is equivalent to the platform's
|
|
206
|
+
# deprecated singular roleId reference). Mutually exclusive with allowedActions; a roleIds-
|
|
207
|
+
# composed profile has no inline clause of its own, so dataScope/capabilities
|
|
208
|
+
# are rejected alongside it — author those on the referenced role instead:
|
|
209
|
+
# accessProfile:
|
|
210
|
+
# roleIds: [case-handler, hr-admin]
|
|
177
211
|
|
|
178
212
|
roles:
|
|
179
213
|
# Optional. Reusable, identity-agnostic access rules — DISTINCT from
|
|
@@ -247,14 +281,20 @@ namespaces:
|
|
|
247
281
|
membershipTargetField: userId
|
|
248
282
|
membershipLevelField: level # optional; membershipLevels required alongside it
|
|
249
283
|
membershipLevels: [admin, viewer]
|
|
284
|
+
# tenantWide: true # optional, default false. Requests registration visible to
|
|
285
|
+
# # EVERY context in the account, not just this blueprint's own —
|
|
286
|
+
# # see below.
|
|
250
287
|
# Registered in the SAME bootstrap-token phase as issuers, before schemas.
|
|
251
|
-
#
|
|
252
|
-
#
|
|
253
|
-
#
|
|
254
|
-
#
|
|
255
|
-
#
|
|
256
|
-
#
|
|
257
|
-
# silently overwriting it
|
|
288
|
+
# Owned by this blueprint's own contextId (above) by default. `tenantWide: true` opts
|
|
289
|
+
# out of that, but declaring it is not enough on its own: the applying credential must
|
|
290
|
+
# separately hold OWNER-only authority, and `bootstrap`/`apply` refuse to even request
|
|
291
|
+
# it without the explicit `--allow-tenant-wide-namespaces` flag — a blueprint cannot make
|
|
292
|
+
# this happen by itself. Registration is create-once, NOT idempotent server-side: a
|
|
293
|
+
# re-apply matching every declared field converges silently, but one that disagrees
|
|
294
|
+
# with what's already registered fails the apply rather than silently overwriting it —
|
|
295
|
+
# for a tenant-wide namespace this includes a collision with a DIFFERENT blueprint's
|
|
296
|
+
# (or a manual) registration of the same name, which always fails rather than being
|
|
297
|
+
# silently adopted.
|
|
258
298
|
|
|
259
299
|
identities:
|
|
260
300
|
team: # local name — referenced elsewhere as ${{ identities.team }}
|
|
@@ -267,10 +307,15 @@ identities:
|
|
|
267
307
|
# digit (demoOrg, not demo-org) — the same grammar '${{ identities.<name> }}'
|
|
268
308
|
# itself can match. A name outside it is rejected as a parse error at
|
|
269
309
|
# declare time (validate/plan), not silently accepted. Every declared
|
|
270
|
-
# identity is ensure-exist'd (idempotent by
|
|
271
|
-
#
|
|
272
|
-
#
|
|
273
|
-
#
|
|
310
|
+
# identity is ensure-exist'd (idempotent by externalId), whether or not
|
|
311
|
+
# anything below references it, in its OWN creds-bearing pass — under the
|
|
312
|
+
# bootstrap credential, AFTER the app-context, service principal, and any
|
|
313
|
+
# namespaces: above are registered, but BEFORE schemas/accessProfile/roles
|
|
314
|
+
# (not in BLUEPRINT_FIELD_PHASES for that reason). This ordering matters
|
|
315
|
+
# for a non-'user' kind: an 'org'/'client'/registered-namespace identity is
|
|
316
|
+
# created IN that namespace, so it can only succeed once the namespace
|
|
317
|
+
# exists — declare it in namespaces: above (or ensure it's already
|
|
318
|
+
# registered) before referencing it here with a non-'user' kind.
|
|
274
319
|
# Reference the resolved principal id anywhere one is valid — a schema's
|
|
275
320
|
# userId/scopes, an accessProfile/role dataScope or identityOverrides,
|
|
276
321
|
# seed-record ownership — e.g.:
|
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,197 @@
|
|
|
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.19.0 — 2026-08-27
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- **`vectros bootstrap`/`blueprint apply` mint a blueprint's own AccessProfile
|
|
11
|
+
from `accessProfile.roleIds`** (needs `@vectros-ai/blueprints` ≥ 0.16.0) —
|
|
12
|
+
the platform's additive role composition, wired end to end. Roles are
|
|
13
|
+
created BEFORE the AccessProfile mint (reordered from the prior sequence,
|
|
14
|
+
where nothing depended on the order), so a `roleIds` reference always
|
|
15
|
+
resolves on first apply. `vectros blueprint plan` previews the composed
|
|
16
|
+
form (`profile via role(s) [...]`), and the scope gate / footgun lints /
|
|
17
|
+
`blueprint-test` harness assertions all read the composed action set —
|
|
18
|
+
every referenced role's own clauses, concatenated in order — for a
|
|
19
|
+
`roleIds` profile instead of the (now-absent) inline `allowedActions`.
|
|
20
|
+
- **`vectros access grant --role` and `vectros join --role` are now
|
|
21
|
+
repeatable — 2+ roles compose additively**, sending the platform's
|
|
22
|
+
`roleIds` (plural) instead of the deprecated singular `roleId`. `access
|
|
23
|
+
get`/`access list` render the composed role list; `access explain` now
|
|
24
|
+
fetches and concatenates every referenced role's clauses instead of only
|
|
25
|
+
the first (a profile composing 2+ roles previously rendered as "no
|
|
26
|
+
clauses" — the singular `roleId` the old code followed is absent once a
|
|
27
|
+
profile composes more than one). A single `--role` still round-trips
|
|
28
|
+
through the deprecated singular field on an older server response.
|
|
29
|
+
- **An already-registered issuer now actually reconciles on a blueprint
|
|
30
|
+
re-apply** (needs `@vectros-ai/sdk` ≥ 0.41.0 for the
|
|
31
|
+
platform's `PUT /v1/auth/issuers/{issuerId}`) — `subClaim`/
|
|
32
|
+
`emailClaim`/`userinfoUri`/`selfSignupPolicies` now apply on re-apply, the
|
|
33
|
+
same create-then-reconcile shape the AccessProfile mint already used. The
|
|
34
|
+
trust-anchor/routing-pin fields (`issuer`/`jwksUri`/`audience`/`contextId`)
|
|
35
|
+
remain immutable via that route (rotating one still needs delete +
|
|
36
|
+
re-register), and `status` is reconcilable on the platform endpoint but has
|
|
37
|
+
no field in `BlueprintIssuerSchema` to author — `vectros blueprint plan`'s
|
|
38
|
+
legend and the apply-outcome summary both say so explicitly rather than
|
|
39
|
+
overclaiming full reconciliation.
|
|
40
|
+
**Behavior change:** editing a trust-anchor field (`issuer`/`jwksUri`/
|
|
41
|
+
`audience`/`contextId`) on an already-registered issuer and re-running
|
|
42
|
+
`bootstrap` now FAILS the whole run with the platform's 400, instead of
|
|
43
|
+
the prior silent no-op (the edit was simply never applied). This is the
|
|
44
|
+
correct direction — the loader stopped lying about it — but it is a
|
|
45
|
+
disclosed behavior change: a blueprint whose issuer trust anchor
|
|
46
|
+
drifted from its live registration will now block on re-apply where it
|
|
47
|
+
previously succeeded quietly.
|
|
48
|
+
- **A blueprint can now declare `issuers[].userinfoUri`** (needs
|
|
49
|
+
`@vectros-ai/sdk` ≥ 0.41.0, `@vectros-ai/blueprints` ≥ 0.16.0) — the IdP's
|
|
50
|
+
OIDC userinfo endpoint, used as a fallback email-resolution source when a
|
|
51
|
+
presented access token doesn't carry the configured `emailClaim` directly.
|
|
52
|
+
Lets a blueprint declare this server-side fallback instead of requiring a
|
|
53
|
+
manual `PUT /v1/auth/issuers/{issuerId}` outside blueprint authoring.
|
|
54
|
+
`vectros issuers get` now renders it alongside the other issuer fields.
|
|
55
|
+
- **`vectros identity invite --context <c> --role <roleId> --email <e>`** —
|
|
56
|
+
onboards a person into a context (`POST /v1/users/invite`) without a
|
|
57
|
+
hand-crafted `curl` call. `--role` resolves directly to
|
|
58
|
+
`accessProfile.roleId` (a Role's id is already its own user-facing
|
|
59
|
+
identifier — no separate name lookup). `--accept-url` is required unless
|
|
60
|
+
`--no-send-email` is given, in which case the response's raw
|
|
61
|
+
`inviteToken`/`acceptLink` let you deliver the invitation yourself.
|
|
62
|
+
Authorizes exactly like `access grant`/`role create` — an ordinary
|
|
63
|
+
context-pinned token minted from your `vectros login` session, not a root
|
|
64
|
+
key. No separate setup needed.
|
|
65
|
+
|
|
66
|
+
### Fixed
|
|
67
|
+
|
|
68
|
+
- **`createRole` (the loader's role mint) now sends `upsert: true`** — without
|
|
69
|
+
it, an already-existing role's `scopes` were silently left unchanged on a
|
|
70
|
+
blueprint re-apply, so editing a role's `allowedActions`/`dataScope` and
|
|
71
|
+
re-running `bootstrap` never actually took effect, contradicting
|
|
72
|
+
`LoaderResult.roleActions`' own documented "reconciled means updated to
|
|
73
|
+
match" contract (the same false-convergence class this release fixes for
|
|
74
|
+
issuers). Pre-existing, caught in review while building `roleIds`
|
|
75
|
+
composition — now more consequential than before, since a `roleIds`-
|
|
76
|
+
composed AccessProfile ties the blueprint's own automatic service
|
|
77
|
+
credential to a role's live (previously possibly-stale) scope.
|
|
78
|
+
- **The bootstrap loader's scope gate now admits an unqualified `profiles:c`
|
|
79
|
+
when the same clause also declares `capabilities: [member-lifecycle]`** —
|
|
80
|
+
the platform's own documented pattern for a self-signup/admin role that
|
|
81
|
+
invites without raw tenant-wide `users:*` could not previously be
|
|
82
|
+
authored through `vectros blueprint apply`/`validate` at all: the gate's
|
|
83
|
+
action check and its capability check ran fully independently, so a bare
|
|
84
|
+
`profiles:c` always rejected regardless of what capabilities accompanied
|
|
85
|
+
it. The exception is narrow and additive — bare form only (a literal
|
|
86
|
+
qualifier like `profiles:c:usr_<id>` is still rejected unconditionally,
|
|
87
|
+
since `member-lifecycle`'s own elevation requires the fully unqualified
|
|
88
|
+
grant and does nothing to bound a literal-principal risk), same clause
|
|
89
|
+
only (a sibling clause's capability grants no benefit), every
|
|
90
|
+
pre-existing call site unaffected (the new parameter is optional).
|
|
91
|
+
- **`vectros blueprint validate`/`bootstrap`/`apply` no longer reject `${{ member.scope.<namespace> }}`
|
|
92
|
+
(and its `:<level>` form) as an unrecognized placeholder.** This is a real, shipped, server-side
|
|
93
|
+
mechanism — resolved per request from `org_membership`-shaped records, with no dependency on a
|
|
94
|
+
separate, unimplemented token-select step that `${{ self.scope.<ns> }}` needs — the CLI's local
|
|
95
|
+
lint simply hadn't been updated to recognize it, so an otherwise-correct blueprint authored
|
|
96
|
+
against it failed `validate` outright with a false `datascope-unknown-placeholder` error, even
|
|
97
|
+
though the platform itself would have accepted it. Found building a real org-membership-based
|
|
98
|
+
blueprint: a role's `${{ self.scope.org }}` dataScope silently granted nothing (an unresolved
|
|
99
|
+
self-placeholder matches nothing, not everything) because nothing populates that identity value
|
|
100
|
+
by default — switching to `${{ member.scope.org }}` is the actual fix for an org_membership-based
|
|
101
|
+
role, and the lint was wrongly in the way of it.
|
|
102
|
+
- **`vectros access explain` now reports `${{ member.scope.<ns>[:<level>] }}` as undecidable** instead
|
|
103
|
+
of silently mismodeling the clause as an opaque literal string — the clause normalizer had no MEMBER
|
|
104
|
+
case at all before this. Same treatment `${{ under.self.* }}` already gets, via `undecidableDims`
|
|
105
|
+
rather than a false match or a silent denial.
|
|
106
|
+
|
|
107
|
+
### Changed
|
|
108
|
+
|
|
109
|
+
- **`@vectros-ai/sdk` repinned to the latest available `0.41.0` build** —
|
|
110
|
+
picks up the additive role composition's `roleIds` on
|
|
111
|
+
`AccessProfileRequest`/`AccessProfileResponse` and the issuer-update
|
|
112
|
+
surface (`IssuerUpdateRequest`, `client.auth.updateIssuer`). No other wire
|
|
113
|
+
shape changed since the prior pin (verified against the published
|
|
114
|
+
tarballs, not just source ancestry).
|
|
115
|
+
|
|
116
|
+
## 0.18.0
|
|
117
|
+
|
|
118
|
+
### Added
|
|
119
|
+
|
|
120
|
+
- **`vectros blueprint plan` now renders the fully-expanded, effective
|
|
121
|
+
per-clause `dataScope` for every declared role** (needs
|
|
122
|
+
`@vectros-ai/blueprints` ≥ 0.15.0) — not just the role's flattened action
|
|
123
|
+
set. This is the review-legibility mitigation the `fragments:`/
|
|
124
|
+
`dataScopeRef` sugar requires: a clause referencing a fragment has already
|
|
125
|
+
been expanded to a literal `dataScope` by the time the CLI sees it, so the
|
|
126
|
+
plan shows exactly what will be granted, never the DSL indirection that
|
|
127
|
+
produced it. Two clauses sharing an action set can legitimately carry
|
|
128
|
+
different `dataScope`, so this is shown per clause, not flattened across
|
|
129
|
+
the role the way the action set is.
|
|
130
|
+
- **A blueprint's `roleAssumable` map is now wired through
|
|
131
|
+
apply** — `vectros bootstrap`/`blueprint apply` passes
|
|
132
|
+
`roleAssumable.<roleId>` straight through to that role's `createRole`
|
|
133
|
+
call as `assumable`, unchanged. `vectros blueprint plan` discloses the
|
|
134
|
+
grant per role (a security-relevant fact the operator approving the plan
|
|
135
|
+
needs to see, same reasoning as the existing capability disclosure), and
|
|
136
|
+
`vectros role get`'s human view now shows a role's `assumable` grant
|
|
137
|
+
(`--json` already did — it prints the SDK response verbatim). Needs
|
|
138
|
+
`@vectros-ai/blueprints` ≥ 0.15.0 and `@vectros-ai/sdk` ≥ 0.41.0 (the
|
|
139
|
+
first generated SDK build carrying `Role.assumable`).
|
|
140
|
+
|
|
141
|
+
## 0.17.0
|
|
142
|
+
### Added
|
|
143
|
+
|
|
144
|
+
- **`vectros issuers list --context <ctx>` and `vectros issuers get <issuerId>
|
|
145
|
+
--context <ctx>`** — read a tenant's registered token-exchange issuers
|
|
146
|
+
without hand-minting a bootstrap token first. `GET`/`LIST /v1/auth/issuers`
|
|
147
|
+
require provisioning capability AND are context-confined for any non-root
|
|
148
|
+
caller, so `--context` is a required flag, not a default: a provisioning
|
|
149
|
+
token minted with no context pin resolves to the platform's `default`
|
|
150
|
+
context and won't see an issuer registered under any other one. Both verbs
|
|
151
|
+
now mint a single st_* carrying provisioning capability PINNED to
|
|
152
|
+
`--context` — a shape the CLI's auth seam couldn't produce before this.
|
|
153
|
+
- **A blueprint's `namespaces[]` entry can declare `tenantWide: true`** (needs
|
|
154
|
+
`@vectros-ai/blueprints` ≥ 0.14.0), requesting the platform's tenant-wide
|
|
155
|
+
registration form instead of the blueprint's own context. `bootstrap`/`apply`
|
|
156
|
+
now REFUSE such a blueprint before anything provisions — naming the
|
|
157
|
+
namespace(s) — unless invoked with the new `--allow-tenant-wide-namespaces`
|
|
158
|
+
flag: a static, scriptable opt-in, deliberately not an interactive runtime
|
|
159
|
+
prompt. This is the human-deliberateness check only; whether the request
|
|
160
|
+
actually succeeds is still decided server-side by an OWNER-gated platform
|
|
161
|
+
capability the flag has no bearing on — a non-OWNER credential gets the
|
|
162
|
+
existing clear failure regardless of the flag. An ordinary blueprint (no
|
|
163
|
+
`tenantWide` namespace) is completely unaffected.
|
|
164
|
+
- **`vectros blueprint plan`'s namespace preview names a `tenantWide` namespace
|
|
165
|
+
explicitly** (`TENANT-WIDE — visible to every context`, in place of the usual
|
|
166
|
+
`owned by '<contextId>'`), and its legend gains a carve-out: unlike an
|
|
167
|
+
ordinary namespace registration (which converges silently on a matching
|
|
168
|
+
re-apply), a tenant-wide one never adopts an existing registration, even an
|
|
169
|
+
exact field-for-field match — a collision always fails.
|
|
170
|
+
|
|
171
|
+
### Changed
|
|
172
|
+
|
|
173
|
+
- **A namespace-name collision on a `tenantWide` registration is always a hard
|
|
174
|
+
failure, never adopted.** The existing read-back-and-reconcile behavior for
|
|
175
|
+
an ordinary, context-owned namespace (a re-apply matching every declared
|
|
176
|
+
field converges silently) does NOT extend to a tenant-wide one: a tenant-wide
|
|
177
|
+
row is co-owned by no single blueprint, so a field-for-field match is not
|
|
178
|
+
evidence it's safe to treat as "this blueprint's" — it could just as easily
|
|
179
|
+
be a different blueprint's (or a manual) registration that happens to declare
|
|
180
|
+
the same shape. The platform's 400 propagates as-is on any collision.
|
|
181
|
+
|
|
182
|
+
### Fixed
|
|
183
|
+
|
|
184
|
+
- **`bootstrap`/`apply` now register a blueprint's `namespaces[]` BEFORE
|
|
185
|
+
creating any identity it declares in `identities[]`.** Previously, apply-time
|
|
186
|
+
identity resolution ran before namespace registration, so a blueprint
|
|
187
|
+
declaring `identities:` in a namespace it ALSO registers via `namespaces:`
|
|
188
|
+
could never succeed — creating that identity requires the namespace to
|
|
189
|
+
already be registered, and the old ordering always raced the identity create
|
|
190
|
+
ahead of it. The app-context, service principal, and every declared
|
|
191
|
+
namespace are now fully provisioned first; identity resolution (and
|
|
192
|
+
everything downstream) runs after. This is a step-ordering change only — an
|
|
193
|
+
unaffected blueprint (no `identities:` referencing a namespace it also
|
|
194
|
+
registers) sees no behavior change, and no CLI flag or blueprint schema
|
|
195
|
+
changed.
|
|
196
|
+
|
|
6
197
|
## 0.16.0
|
|
7
198
|
|
|
8
199
|
### Added
|
package/README.md
CHANGED
|
@@ -9,11 +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 registered third-party IdP issuers (token-exchange trust anchors) for a context.
|
|
12
13
|
- **`vectros identity`** — manage tenant-wide principals (users, orgs, clients, and namespaces you register).
|
|
13
14
|
- **`vectros role`** — define reusable, context-scoped access rules.
|
|
14
15
|
- **`vectros access`** — bind principals to contexts, and inspect what a binding grants.
|
|
15
16
|
- **`vectros key`** — issue, list, revoke, and rotate scoped API keys (`ssk_*`).
|
|
16
17
|
- **`vectros keyring`** — hold several identities on one machine, pick which is active, and let other tools resolve keys from one place.
|
|
18
|
+
- **`vectros join`** — enroll a principal in a context and keep the resulting credential in the keyring, in one step.
|
|
17
19
|
|
|
18
20
|
```bash
|
|
19
21
|
npx -y @vectros-ai/cli bootstrap # provision a credential
|
|
@@ -144,6 +146,26 @@ vectros context destroy my-app # irreversible — asks you to r
|
|
|
144
146
|
All four accept `--json` for machine-readable output, plus the common auth
|
|
145
147
|
flags (`--base-url`, `--token`).
|
|
146
148
|
|
|
149
|
+
## `vectros issuers` — registered third-party IdP issuers
|
|
150
|
+
|
|
151
|
+
Read the third-party identity-provider registrations a context trusts for
|
|
152
|
+
[RFC 8693 token exchange](https://www.rfc-editor.org/rfc/rfc8693) — the
|
|
153
|
+
`issuer`/`jwksUri`/`audience` a blueprint's `issuers[]` entries register, plus
|
|
154
|
+
any self-signup policy attached to one. Read-only: registering an issuer is
|
|
155
|
+
done through a blueprint (`bootstrap`/`blueprint-test`), not this command.
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
vectros issuers list --context my-app
|
|
159
|
+
vectros issuers get primary --context my-app
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
| Subcommand | Flags | Notes |
|
|
163
|
+
|---|---|---|
|
|
164
|
+
| `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. |
|
|
166
|
+
|
|
167
|
+
Both accept `--json`, plus the common auth flags (`--base-url`, `--token`).
|
|
168
|
+
|
|
147
169
|
## `vectros identity` — tenant-wide principals
|
|
148
170
|
|
|
149
171
|
Manage the principals a context's access profiles and roles reference: users
|
|
@@ -158,6 +180,8 @@ vectros identity create --type org --external-id acme-inc --name "Acme Inc"
|
|
|
158
180
|
vectros identity list --type user
|
|
159
181
|
vectros identity get --type org --id <vectrosId>
|
|
160
182
|
vectros identity delete --type org --id <vectrosId>
|
|
183
|
+
vectros identity invite --context my-app --role editor --email bob@example.com \
|
|
184
|
+
--accept-url https://my-app.example.com/accept
|
|
161
185
|
```
|
|
162
186
|
|
|
163
187
|
| Subcommand | Flags | Notes |
|
|
@@ -169,8 +193,9 @@ vectros identity delete --type org --id <vectrosId>
|
|
|
169
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. |
|
|
170
194
|
| `get` | `--type <user\|namespace>` (required), `--id <vectrosId>` (required) | `--id` is the Vectros-assigned id from `create`/`list`, not the `--external-id`. |
|
|
171
195
|
| `delete` | `--type <user\|namespace>` (required), `--id <vectrosId>` (required) | |
|
|
196
|
+
| `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. |
|
|
172
197
|
|
|
173
|
-
`create`/`list`/`get` accept `--json`; `delete` does not.
|
|
198
|
+
`create`/`list`/`get`/`invite` accept `--json`; `delete` does not.
|
|
174
199
|
|
|
175
200
|
## `vectros role` — reusable, context-scoped access rules
|
|
176
201
|
|
|
@@ -194,7 +219,7 @@ vectros role delete --context my-app --role-id editor
|
|
|
194
219
|
|---|---|---|
|
|
195
220
|
| `create` | `--context <c>`, `--role-id <id>`, `--name <n>`, `--actions <csv>` (all required); `--data-scope <json>`, `--description <text>` | Idempotent by `roleId`. `--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). |
|
|
196
221
|
| `list` | `--context <c>` (required) | |
|
|
197
|
-
| `get` | `--context <c>`, `--role-id <id>` (both required) | |
|
|
222
|
+
| `get` | `--context <c>`, `--role-id <id>` (both required) | Also shows the role's `assumable` grant (which values, per namespace, a holder of this role may become via `POST /v1/auth/token/assume`), if any is set. `role create` has no `--assumable` flag today — author it via a blueprint's `roleAssumable:` block or a direct API call. |
|
|
198
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. |
|
|
199
224
|
|
|
200
225
|
`create`/`list`/`get` accept `--json`; `delete` does not.
|
|
@@ -224,14 +249,42 @@ shares this grammar.
|
|
|
224
249
|
|
|
225
250
|
| Subcommand | Flags | Notes |
|
|
226
251
|
|---|---|---|
|
|
227
|
-
| `grant` | `--principal <p>`, `--context <c>` (required); exactly one of `--role <roleId>` / `--actions <csv>`; `--scope <ns:value>` (repeatable), `--identity-overrides <json>` | Idempotent **upsert** — re-granting an existing binding updates it to the newly-declared shape (role/actions/overrides), it does not no-op. `--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. |
|
|
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>` | 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. |
|
|
228
253
|
| `revoke` | `--principal <p>`, `--context <c>` (required) | |
|
|
229
254
|
| `list` | Exactly one of `--context <c>` / `--principal <p>` | `--context` lists the context's members; `--principal` lists every context that principal is bound to. |
|
|
230
255
|
| `get` | `--principal <p>`, `--context <c>` (required) | |
|
|
231
|
-
| `explain` | `--principal <p>`, `--context <c>` (required) | Resolves the binding's clauses (following
|
|
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). |
|
|
232
257
|
|
|
233
258
|
`grant`/`list`/`get`/`explain` accept `--json`; `revoke` does not.
|
|
234
259
|
|
|
260
|
+
## `vectros join` — enroll and keep the credential, in one step
|
|
261
|
+
|
|
262
|
+
`join` is a **composition verb**: everything it does already exists as a
|
|
263
|
+
discrete command (`access grant`, `key issue`, `keyring add`) — `join` runs
|
|
264
|
+
them in order against one context and records the outcome as a keyring
|
|
265
|
+
entry, so `vectros switch <alias>` (and the MCP server / recall hook) can act
|
|
266
|
+
as that principal from then on.
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
vectros join my-app --role editor # enroll "me" via a role
|
|
270
|
+
vectros join my-app --role case-handler --role hr-admin # compose 2+ roles additively
|
|
271
|
+
vectros join my-app --actions records:cru,search:r --scope org:<orgId>
|
|
272
|
+
vectros join my-app --as usr_<id> --role editor --alias teammate
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
| Flag | Notes |
|
|
276
|
+
|---|---|
|
|
277
|
+
| `--role <roleId>` | Repeatable — 2+ compose additively, same semantics as `access grant --role`. XOR with `--actions`. |
|
|
278
|
+
| `--actions <csv>` | Inline single-clause grant. XOR with `--role`. |
|
|
279
|
+
| `--as me\|usr_<id>` (default `me`) | Which principal to enroll — `me` resolves to your own signed-in user. |
|
|
280
|
+
| `--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. |
|
|
281
|
+
| `--alias <name>` | Keyring entry name (defaults to the context id). |
|
|
282
|
+
| `--activate` / `--no-activate` | Force (or skip) making the freshly-stored key this machine's active identity. |
|
|
283
|
+
|
|
284
|
+
Re-joining an alias whose secret is still held locally is idempotent: it
|
|
285
|
+
re-grants (a no-op upsert) and re-activates without churning the key. Only
|
|
286
|
+
when the local secret is missing does it rotate to obtain a fresh one.
|
|
287
|
+
|
|
235
288
|
## `vectros key` — the scoped API key (`ssk_*`) lifecycle
|
|
236
289
|
|
|
237
290
|
Mint, inspect, revoke, and rotate the scoped keys (`ssk_*`) that authenticate
|
|
@@ -368,9 +421,15 @@ already holds — those verbs can only disclose or redistribute access, never
|
|
|
368
421
|
manufacture new reach. `profiles:c` (creating a profile for an arbitrary,
|
|
369
422
|
caller-chosen principal name) still hard-rejects — **except** `profiles:c:self`,
|
|
370
423
|
which the platform resolves from the credential's own identity at request
|
|
371
|
-
time, never from anything the blueprint declares
|
|
372
|
-
|
|
373
|
-
|
|
424
|
+
time, never from anything the blueprint declares, and **except a bare
|
|
425
|
+
`profiles:c` on a clause that also declares the `member-lifecycle` capability**
|
|
426
|
+
(see Capabilities, below) — that combination is admitted because
|
|
427
|
+
`member-lifecycle` already grants unbounded principal-creation reach, so
|
|
428
|
+
gating bare `profiles:c` behind it adds no further technical bound but keeps
|
|
429
|
+
the same explicit, fail-closed opt-in signal every other admission here
|
|
430
|
+
requires. A literal-qualified `profiles:c:usr_<id>` naming another principal
|
|
431
|
+
still hard-rejects regardless of `member-lifecycle`, same as every other
|
|
432
|
+
control-plane scope.
|
|
374
433
|
|
|
375
434
|
### Capabilities (`granted_capabilities`) — a separate, equally-gated dimension
|
|
376
435
|
|
package/THIRD-PARTY-NOTICES.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# Third-party notices
|
|
2
2
|
|
|
3
3
|
`@vectros-ai/cli` compiles the following third-party packages directly into its
|
|
4
|
-
published
|
|
5
|
-
|
|
4
|
+
published bundle. Their licenses require that these notices travel with the code,
|
|
5
|
+
so they are reproduced in full below.
|
|
6
6
|
|
|
7
|
-
This file is GENERATED by `pipelines/third_party_notices.mjs` from
|
|
8
|
-
|
|
7
|
+
This file is GENERATED by `pipelines/third_party_notices.mjs` from `packages/cli/tsup.config.ts`'s
|
|
8
|
+
`noExternal` list. Do not edit it by hand — run
|
|
9
9
|
`node pipelines/third_party_notices.mjs --write`. CI fails if it is stale, because a
|
|
10
10
|
notices file that silently drifts is worse than none: it asserts a compliance that
|
|
11
11
|
nobody checked.
|