@vectros-ai/cli 0.16.0 → 0.20.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 +73 -17
- package/CHANGELOG.md +332 -0
- package/README.md +136 -13
- package/THIRD-PARTY-NOTICES.md +4 -4
- package/dist/cli.js +1664 -223
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +1664 -223
- package/dist/cli.mjs.map +1 -1
- package/package.json +3 -3
package/AUTHORING.md
CHANGED
|
@@ -69,6 +69,16 @@ A name must be 2–50 chars: a lowercase letter, then lowercase letters / digits
|
|
|
69
69
|
`dataScope: {}` when a shared/tenant-wide resource is what you intend
|
|
70
70
|
(warnings — they print but don't fail).
|
|
71
71
|
|
|
72
|
+
A schema may also declare `expectedScopeDims` — the ownership dimensions
|
|
73
|
+
its records are typically scoped by, e.g. `expectedScopeDims: [org,
|
|
74
|
+
client]`. Purely advisory (never sent to the platform, never runtime-
|
|
75
|
+
enforced): it lets the lint warn when a role/`accessProfile` clause grants
|
|
76
|
+
`r`/`u`/`d` on that typeName (via a type-qualified action like
|
|
77
|
+
`records:r:case`) but its `dataScope` names only SOME of the declared
|
|
78
|
+
dims — reads never require full-dimension coverage the way creates do, so
|
|
79
|
+
a clause naming one of two declared dims silently leaves the other
|
|
80
|
+
unconstrained. Omit the field entirely and this lint never fires.
|
|
81
|
+
|
|
72
82
|
`validate` accepts a file path **or** a bundled blueprint name.
|
|
73
83
|
|
|
74
84
|
### 3. `plan` — preview before you mint
|
|
@@ -87,6 +97,30 @@ A name must be 2–50 chars: a lowercase letter, then lowercase letters / digits
|
|
|
87
97
|
+ seed 1 record(s)
|
|
88
98
|
```
|
|
89
99
|
|
|
100
|
+
**A blueprint whose `accessProfile` composes roles (`roleIds:`) previews every
|
|
101
|
+
composed role's clauses individually**, not flattened, because two clauses
|
|
102
|
+
sharing an action set can still carry different `dataScope`:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
Plan for blueprint 'my-thing' (v1.0.0) → context 'mcp':
|
|
106
|
+
+ schema 'thing' (2 fields, HYBRID) lookup: [externalId]
|
|
107
|
+
+ context 'mcp' (MCP — my-thing)
|
|
108
|
+
+ principal 'my-thing-sp' (SERVICE)
|
|
109
|
+
+ role 'case-handler' (1 clause, actions [records:cru])
|
|
110
|
+
clause 1: actions [records:cru], dataScope: scope:org=[${{ self.scope.org }}]
|
|
111
|
+
+ role 'hr-admin' (1 clause, actions [records:r])
|
|
112
|
+
clause 1: actions [records:r], dataScope: (none — unconstrained)
|
|
113
|
+
assumable: scope:org=[org_engineering, org_sales]
|
|
114
|
+
+ profile via role(s) [case-handler, hr-admin] (composed — each role's own clauses, concatenated in order)
|
|
115
|
+
+ key my-thing-<machine> (mint 1 scoped ssk_*)
|
|
116
|
+
+ seed 1 record(s)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
A role's `assumable` line only appears when the blueprint's top-level
|
|
120
|
+
`roleAssumable:` map names that role — it's shown because it's
|
|
121
|
+
security-relevant (what a holder of the role may become via `POST
|
|
122
|
+
/v1/auth/token/assume`), not just that the role was declared.
|
|
123
|
+
|
|
90
124
|
`bootstrap` shows the same plan and asks for confirmation before minting
|
|
91
125
|
(`--yes` skips the prompt for CI/agents). ⚠️ **`--no-merge` is NOT a dry run** — it still
|
|
92
126
|
mints/reconciles for real; it only changes where the resulting credential goes, printing the MCP
|
|
@@ -172,8 +206,19 @@ accessProfile:
|
|
|
172
206
|
# capabilities: [member-lifecycle]
|
|
173
207
|
# A SEPARATE gated dimension from allowedActions — named platform effects a
|
|
174
208
|
# resource:ops verb can't express (e.g. creating/removing an identity in
|
|
175
|
-
# your tenant).
|
|
176
|
-
# is hard-rejected, same posture as the
|
|
209
|
+
# your tenant). 'member-lifecycle' and 'delegate-principal-stamp' are
|
|
210
|
+
# admitted today; every other name is hard-rejected, same posture as the
|
|
211
|
+
# scope gate. See "Capabilities" below.
|
|
212
|
+
#
|
|
213
|
+
# Instead of allowedActions (+ optional dataScope/capabilities), accessProfile
|
|
214
|
+
# may declare `roleIds: [<roleId>, ...]` — one or more roles declared below,
|
|
215
|
+
# in `roles:`, composed additively (each role's own clauses, concatenated in
|
|
216
|
+
# the order listed; a single roleId is equivalent to the platform's
|
|
217
|
+
# deprecated singular roleId reference). Mutually exclusive with allowedActions; a roleIds-
|
|
218
|
+
# composed profile has no inline clause of its own, so dataScope/capabilities
|
|
219
|
+
# are rejected alongside it — author those on the referenced role instead:
|
|
220
|
+
# accessProfile:
|
|
221
|
+
# roleIds: [case-handler, hr-admin]
|
|
177
222
|
|
|
178
223
|
roles:
|
|
179
224
|
# Optional. Reusable, identity-agnostic access rules — DISTINCT from
|
|
@@ -247,14 +292,20 @@ namespaces:
|
|
|
247
292
|
membershipTargetField: userId
|
|
248
293
|
membershipLevelField: level # optional; membershipLevels required alongside it
|
|
249
294
|
membershipLevels: [admin, viewer]
|
|
295
|
+
# tenantWide: true # optional, default false. Requests registration visible to
|
|
296
|
+
# # EVERY context in the account, not just this blueprint's own —
|
|
297
|
+
# # see below.
|
|
250
298
|
# Registered in the SAME bootstrap-token phase as issuers, before schemas.
|
|
251
|
-
#
|
|
252
|
-
#
|
|
253
|
-
#
|
|
254
|
-
#
|
|
255
|
-
#
|
|
256
|
-
#
|
|
257
|
-
# silently overwriting it
|
|
299
|
+
# Owned by this blueprint's own contextId (above) by default. `tenantWide: true` opts
|
|
300
|
+
# out of that, but declaring it is not enough on its own: the applying credential must
|
|
301
|
+
# separately hold OWNER-only authority, and `bootstrap`/`apply` refuse to even request
|
|
302
|
+
# it without the explicit `--allow-tenant-wide-namespaces` flag — a blueprint cannot make
|
|
303
|
+
# this happen by itself. Registration is create-once, NOT idempotent server-side: a
|
|
304
|
+
# re-apply matching every declared field converges silently, but one that disagrees
|
|
305
|
+
# with what's already registered fails the apply rather than silently overwriting it —
|
|
306
|
+
# for a tenant-wide namespace this includes a collision with a DIFFERENT blueprint's
|
|
307
|
+
# (or a manual) registration of the same name, which always fails rather than being
|
|
308
|
+
# silently adopted.
|
|
258
309
|
|
|
259
310
|
identities:
|
|
260
311
|
team: # local name — referenced elsewhere as ${{ identities.team }}
|
|
@@ -267,10 +318,15 @@ identities:
|
|
|
267
318
|
# digit (demoOrg, not demo-org) — the same grammar '${{ identities.<name> }}'
|
|
268
319
|
# itself can match. A name outside it is rejected as a parse error at
|
|
269
320
|
# declare time (validate/plan), not silently accepted. Every declared
|
|
270
|
-
# identity is ensure-exist'd (idempotent by
|
|
271
|
-
#
|
|
272
|
-
#
|
|
273
|
-
#
|
|
321
|
+
# identity is ensure-exist'd (idempotent by externalId), whether or not
|
|
322
|
+
# anything below references it, in its OWN creds-bearing pass — under the
|
|
323
|
+
# bootstrap credential, AFTER the app-context, service principal, and any
|
|
324
|
+
# namespaces: above are registered, but BEFORE schemas/accessProfile/roles
|
|
325
|
+
# (not in BLUEPRINT_FIELD_PHASES for that reason). This ordering matters
|
|
326
|
+
# for a non-'user' kind: an 'org'/'client'/registered-namespace identity is
|
|
327
|
+
# created IN that namespace, so it can only succeed once the namespace
|
|
328
|
+
# exists — declare it in namespaces: above (or ensure it's already
|
|
329
|
+
# registered) before referencing it here with a non-'user' kind.
|
|
274
330
|
# Reference the resolved principal id anywhere one is valid — a schema's
|
|
275
331
|
# userId/scopes, an accessProfile/role dataScope or identityOverrides,
|
|
276
332
|
# seed-record ownership — e.g.:
|
|
@@ -411,10 +467,10 @@ boundary, not the file.
|
|
|
411
467
|
An `accessProfile` or role clause may also declare `capabilities: [...]` —
|
|
412
468
|
named platform effects (like creating or removing an identity in your tenant)
|
|
413
469
|
that a `resource:ops` scope token can't express. This rides its own field,
|
|
414
|
-
separate from `allowedActions`, and has its own allowlist:
|
|
415
|
-
`
|
|
416
|
-
including ones the platform's own registry recognizes — is refused,
|
|
417
|
-
fail-closed, deliberately-narrow posture as the scope gate above. `validate`,
|
|
470
|
+
separate from `allowedActions`, and has its own allowlist: **`member-lifecycle`
|
|
471
|
+
and `delegate-principal-stamp` are admitted today.** Every other capability
|
|
472
|
+
name — including ones the platform's own registry recognizes — is refused,
|
|
473
|
+
the same fail-closed, deliberately-narrow posture as the scope gate above. `validate`,
|
|
418
474
|
`plan`, `blueprint-test` and `bootstrap` all report a rejected capability the
|
|
419
475
|
same way they report a rejected scope: nothing mints, and the CLI exits
|
|
420
476
|
non-zero.
|
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,338 @@
|
|
|
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.20.0 — 2026-08-27
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- **The capability gate now admits `delegate-principal-stamp`**, alongside the
|
|
11
|
+
existing `member-lifecycle`. Grantable on an `accessProfile` or role clause's
|
|
12
|
+
`capabilities: [...]`, same as before — a blueprint can now let an admin
|
|
13
|
+
role create a membership-shaped record on behalf of a known third-party
|
|
14
|
+
user, once the clause authorizing the write also explicitly names `userId`
|
|
15
|
+
in its own `dataScope` (the capability itself may be granted on a
|
|
16
|
+
different clause of the same role, e.g. via a separately-composed
|
|
17
|
+
`roleIds` entry — only the write's own verb + `userId` admission must be
|
|
18
|
+
co-located). Bounded by the SAME app context and record type the clause's
|
|
19
|
+
`dataScope` already confines the write to — unlike `delegate-mint`/
|
|
20
|
+
`forensic-read`/`context-directory-read`, this is not tenant-wide, and
|
|
21
|
+
(like `member-lifecycle`) it is self-signup-eligible.
|
|
22
|
+
- **`vectros identity list-all`** — lists every user in the tenant, for the one
|
|
23
|
+
recovery case `identity list` can't reach: a user created but never granted
|
|
24
|
+
access anywhere (the gap between creating a principal and binding it to a
|
|
25
|
+
context). `identity list` is scoped to the caller's own context by design —
|
|
26
|
+
a user with no access grant at all is invisible there no matter how many
|
|
27
|
+
times a create is retried, since a retry with the same `--external-id` just
|
|
28
|
+
collides with the same invisible row. `list-all` authenticates with your own
|
|
29
|
+
login session (an account owner) instead of a context-scoped credential, so
|
|
30
|
+
it can see every user regardless of context. Supports `--external-id`,
|
|
31
|
+
`--tenant`, `--limit`, and `--json`, matching `identity list`'s conventions.
|
|
32
|
+
- **`vectros blueprint validate`/`plan`: new advisory lint —
|
|
33
|
+
`partial-datascope-dimension-coverage`** (needs `@vectros-ai/blueprints` ≥
|
|
34
|
+
0.17.0). Warns when a role/`accessProfile` clause grants `r`/`u`/`d` on a
|
|
35
|
+
typeName (e.g. `records:r:case`) whose schema declares
|
|
36
|
+
`expectedScopeDims`, but the clause's `dataScope` names only SOME of those
|
|
37
|
+
dims — the missing dimension(s) are silently unconstrained for that grant,
|
|
38
|
+
since reads (unlike creates) never require full-dimension `dataScope`
|
|
39
|
+
coverage. Opt-in: a schema with no `expectedScopeDims` declared never
|
|
40
|
+
trips it, and it never fires on an untyped (bare `records:r`) grant.
|
|
41
|
+
Never runtime-enforced — pure authoring-time visibility into a choice
|
|
42
|
+
that's easy to make by omission rather than intent.
|
|
43
|
+
- **`bootstrap`/`blueprint-test` now catch a FRESH (first-apply) seed index
|
|
44
|
+
failure, not just a re-apply's.** A seed create's response always carries
|
|
45
|
+
`PENDING_INDEX` on a first apply (indexing runs async, after the
|
|
46
|
+
response), so the existing `seedIndexFailures` summary previously only
|
|
47
|
+
ever saw a terminal `FAILED` on a later idempotent re-apply — a
|
|
48
|
+
newly-bootstrapped demo whose seeds failed to index looked silently empty
|
|
49
|
+
in search/RAG until the operator happened to re-run bootstrap. Every seed
|
|
50
|
+
still `PENDING_INDEX` after seeding is now re-checked with a bounded,
|
|
51
|
+
concurrent, best-effort poll (default: 3 attempts, 500ms apart, ≤ ~1.5s
|
|
52
|
+
added to the happy path) and folded into the same run-summary warning if
|
|
53
|
+
it settles to `FAILED` within the bound. Deliberately NOT a guarantee — a
|
|
54
|
+
seed still `PENDING_INDEX` when the bound is exhausted stays silent, so a
|
|
55
|
+
genuinely slow index (reconcile/quarantine can stall for minutes) never
|
|
56
|
+
turns a one-shot provisioning command into an unbounded wait.
|
|
57
|
+
- **`vectros issuers` gains `register`/`update`/`delete`** — previously
|
|
58
|
+
read-only (`list`/`get`). Closes the gap where adding a second IdP, or
|
|
59
|
+
editing one issuer's `selfSignupPolicies` after initial setup, required
|
|
60
|
+
hand-authoring a blueprint `issuers:` block and re-running a full apply.
|
|
61
|
+
`register` is idempotent by issuer id (a repeat returns the existing
|
|
62
|
+
registration unchanged, not an error); `update` only ever sends the safe,
|
|
63
|
+
mutable fields (`subClaim`/`emailClaim`/`userinfoUri`/`status`/
|
|
64
|
+
`selfSignupPolicies`) — trust-anchor fields stay immutable via this route,
|
|
65
|
+
matching the platform; `delete` uses the same re-type-the-id confirmation
|
|
66
|
+
as `context destroy` (`--force` for non-interactive use), and is refused
|
|
67
|
+
server-side while any user is bound through the issuer. All three accept
|
|
68
|
+
`--tenant test|live` (like their `list`/`get` siblings) so a context
|
|
69
|
+
living in the `test` tenant is reachable, not just `live`.
|
|
70
|
+
- **New `vectros namespace` command group: `register`/`list`/`get`.**
|
|
71
|
+
Registering a brand-new scope namespace (e.g. a `practice`/`team` entity
|
|
72
|
+
dimension) was blueprint-apply-only before this; there was also no way to
|
|
73
|
+
discover which namespaces already exist without already knowing their
|
|
74
|
+
names. `register --entity-backed` is what makes a namespace a real
|
|
75
|
+
identity-entity dimension that `identity create --type <name>` can create
|
|
76
|
+
entities in. No `update`/`delete` verbs — both are gated to a genuine root
|
|
77
|
+
API key on the platform side with no provisioning-capability door, so
|
|
78
|
+
neither could ever succeed for any credential the CLI holds (documented in
|
|
79
|
+
the command's own module header).
|
|
80
|
+
- **`role create` gains `--assumable <json>`** — a role-level `vectros
|
|
81
|
+
assume` entitlement grant (JSON map of `scope:<namespace>` → allowed
|
|
82
|
+
values), previously blueprint-`roleAssumable:`-only. Independent of
|
|
83
|
+
`--data-scope`: holding broad data-scope reach in a namespace does not by
|
|
84
|
+
itself grant assuming any value in it.
|
|
85
|
+
- **New `vectros assume --context <c> --scope <ns:value>...`** — re-mints
|
|
86
|
+
the caller's own verb token with one or more `identity.<namespace>` values
|
|
87
|
+
switched (`POST /v1/auth/token/assume`), for a caller whose role
|
|
88
|
+
explicitly grants assuming those values. The CLI's first standalone
|
|
89
|
+
command reaching this endpoint; entitlement is checked live against the
|
|
90
|
+
caller's current roles, never a copy frozen into a token at mint time. No
|
|
91
|
+
`--to-keyring` — the response is an ephemeral bearer token with the same
|
|
92
|
+
remaining lifetime as the verb token this command itself mints to make the
|
|
93
|
+
call, not a durable secret.
|
|
94
|
+
|
|
95
|
+
### Changed
|
|
96
|
+
|
|
97
|
+
- **`runLoader`'s `provisioned` is now REQUIRED — the self-provisioning
|
|
98
|
+
fallback is gone.** Internal hardening only, no CLI-facing behavior
|
|
99
|
+
change: every real caller (`vectros bootstrap`, `blueprint-test`) already
|
|
100
|
+
always ran `provisionContext` first and passed the result in, so the
|
|
101
|
+
fallback branch was dead in production. Removing it makes the
|
|
102
|
+
`provisionContext` → (apply-time identity resolution) → `runLoader`
|
|
103
|
+
ordering structurally required instead of convention-enforced — a future
|
|
104
|
+
caller can no longer reproduce the ordering bug the original fix
|
|
105
|
+
closed by skipping the split. `allowTenantWideNamespaces` and
|
|
106
|
+
`confirmExistingPrincipal` moved with it: both now live only on
|
|
107
|
+
`provisionContext`'s own input, where their actual checks have run since
|
|
108
|
+
that original fix — `runLoader`'s own copies of both parameters were already dead
|
|
109
|
+
plumbing before this change.
|
|
110
|
+
- **Apply-time identity resolution now REQUIRES `provisionContext`'s result.**
|
|
111
|
+
Internal hardening only, no CLI-facing behavior change. Closes the other
|
|
112
|
+
half of the ordering gap above: `runLoader` already required
|
|
113
|
+
`provisionContext`'s output, but the step between them — resolving a
|
|
114
|
+
blueprint's declared `identities:` — took a bare client with nothing tying
|
|
115
|
+
it to `provisionContext` having run first, so a caller could still
|
|
116
|
+
reproduce the original ordering bug (a non-`user` identity in a namespace
|
|
117
|
+
the same blueprint also registers racing that registration) with no
|
|
118
|
+
compiler signal. It now derives which client to use internally instead of
|
|
119
|
+
each caller doing so independently.
|
|
120
|
+
|
|
121
|
+
### Fixed
|
|
122
|
+
|
|
123
|
+
- **`bootstrap`'s post-merge hint is now blueprint-specific, and `--client
|
|
124
|
+
code` always shows the `claude mcp add` command.** The restart hint used to
|
|
125
|
+
read "list my tasks" / "add a task to …" for EVERY blueprint (a leftover
|
|
126
|
+
from when only `task-management` existed) — it now names the actual
|
|
127
|
+
blueprint applied. The `claude mcp add` line (the idiomatic Claude Code
|
|
128
|
+
install path) used to print only on `--no-merge` / a declined / a failed
|
|
129
|
+
merge — a successful auto-merge silently hid it; it's now shown there too.
|
|
130
|
+
- Scrubbed internal issue/MR references, review-process citations, and
|
|
131
|
+
backend class names out of `packages/cli`'s comments — visible in the
|
|
132
|
+
published npm package's sourcemaps to anyone who reads mapped source.
|
|
133
|
+
Comment-only; no behavior change.
|
|
134
|
+
- **Keyring secret-store hardening.** A 0-byte salt file removed by
|
|
135
|
+
something else mid-poll (a concurrent cleanup, a manual delete of the
|
|
136
|
+
crash remnant) used to throw a raw `ENOENT` out of `setSecret`; it now
|
|
137
|
+
restarts salt creation instead, since nothing was ever encrypted under an
|
|
138
|
+
empty salt in the first place. Also fixed an order-dependent assumption
|
|
139
|
+
in two `keyring prune` tests (they now pin the processing order they
|
|
140
|
+
depend on, so a future ordering change fails with a diagnosable message
|
|
141
|
+
instead of a confusing downstream one).
|
|
142
|
+
|
|
143
|
+
### Changed
|
|
144
|
+
|
|
145
|
+
- Bundled `@vectros-ai/sdk` refreshed to the current release's staging build.
|
|
146
|
+
|
|
147
|
+
## 0.19.0 — 2026-08-27
|
|
148
|
+
|
|
149
|
+
### Added
|
|
150
|
+
|
|
151
|
+
- **`vectros bootstrap`/`blueprint apply` mint a blueprint's own AccessProfile
|
|
152
|
+
from `accessProfile.roleIds`** (needs `@vectros-ai/blueprints` ≥ 0.16.0) —
|
|
153
|
+
the platform's additive role composition, wired end to end. Roles are
|
|
154
|
+
created BEFORE the AccessProfile mint (reordered from the prior sequence,
|
|
155
|
+
where nothing depended on the order), so a `roleIds` reference always
|
|
156
|
+
resolves on first apply. `vectros blueprint plan` previews the composed
|
|
157
|
+
form (`profile via role(s) [...]`), and the scope gate / footgun lints /
|
|
158
|
+
`blueprint-test` harness assertions all read the composed action set —
|
|
159
|
+
every referenced role's own clauses, concatenated in order — for a
|
|
160
|
+
`roleIds` profile instead of the (now-absent) inline `allowedActions`.
|
|
161
|
+
- **`vectros access grant --role` and `vectros join --role` are now
|
|
162
|
+
repeatable — 2+ roles compose additively**, sending the platform's
|
|
163
|
+
`roleIds` (plural) instead of the deprecated singular `roleId`. `access
|
|
164
|
+
get`/`access list` render the composed role list; `access explain` now
|
|
165
|
+
fetches and concatenates every referenced role's clauses instead of only
|
|
166
|
+
the first (a profile composing 2+ roles previously rendered as "no
|
|
167
|
+
clauses" — the singular `roleId` the old code followed is absent once a
|
|
168
|
+
profile composes more than one). A single `--role` still round-trips
|
|
169
|
+
through the deprecated singular field on an older server response.
|
|
170
|
+
- **An already-registered issuer now actually reconciles on a blueprint
|
|
171
|
+
re-apply** (needs `@vectros-ai/sdk` ≥ 0.41.0 for the
|
|
172
|
+
platform's `PUT /v1/auth/issuers/{issuerId}`) — `subClaim`/
|
|
173
|
+
`emailClaim`/`userinfoUri`/`selfSignupPolicies` now apply on re-apply, the
|
|
174
|
+
same create-then-reconcile shape the AccessProfile mint already used. The
|
|
175
|
+
trust-anchor/routing-pin fields (`issuer`/`jwksUri`/`audience`/`contextId`)
|
|
176
|
+
remain immutable via that route (rotating one still needs delete +
|
|
177
|
+
re-register), and `status` is reconcilable on the platform endpoint but has
|
|
178
|
+
no field in `BlueprintIssuerSchema` to author — `vectros blueprint plan`'s
|
|
179
|
+
legend and the apply-outcome summary both say so explicitly rather than
|
|
180
|
+
overclaiming full reconciliation.
|
|
181
|
+
**Behavior change:** editing a trust-anchor field (`issuer`/`jwksUri`/
|
|
182
|
+
`audience`/`contextId`) on an already-registered issuer and re-running
|
|
183
|
+
`bootstrap` now FAILS the whole run with the platform's 400, instead of
|
|
184
|
+
the prior silent no-op (the edit was simply never applied). This is the
|
|
185
|
+
correct direction — the loader stopped lying about it — but it is a
|
|
186
|
+
disclosed behavior change: a blueprint whose issuer trust anchor
|
|
187
|
+
drifted from its live registration will now block on re-apply where it
|
|
188
|
+
previously succeeded quietly.
|
|
189
|
+
- **A blueprint can now declare `issuers[].userinfoUri`** (needs
|
|
190
|
+
`@vectros-ai/sdk` ≥ 0.41.0, `@vectros-ai/blueprints` ≥ 0.16.0) — the IdP's
|
|
191
|
+
OIDC userinfo endpoint, used as a fallback email-resolution source when a
|
|
192
|
+
presented access token doesn't carry the configured `emailClaim` directly.
|
|
193
|
+
Lets a blueprint declare this server-side fallback instead of requiring a
|
|
194
|
+
manual `PUT /v1/auth/issuers/{issuerId}` outside blueprint authoring.
|
|
195
|
+
`vectros issuers get` now renders it alongside the other issuer fields.
|
|
196
|
+
- **`vectros identity invite --context <c> --role <roleId> --email <e>`** —
|
|
197
|
+
onboards a person into a context (`POST /v1/users/invite`) without a
|
|
198
|
+
hand-crafted `curl` call. `--role` resolves directly to
|
|
199
|
+
`accessProfile.roleId` (a Role's id is already its own user-facing
|
|
200
|
+
identifier — no separate name lookup). `--accept-url` is required unless
|
|
201
|
+
`--no-send-email` is given, in which case the response's raw
|
|
202
|
+
`inviteToken`/`acceptLink` let you deliver the invitation yourself.
|
|
203
|
+
Authorizes exactly like `access grant`/`role create` — an ordinary
|
|
204
|
+
context-pinned token minted from your `vectros login` session, not a root
|
|
205
|
+
key. No separate setup needed.
|
|
206
|
+
|
|
207
|
+
### Fixed
|
|
208
|
+
|
|
209
|
+
- **`createRole` (the loader's role mint) now sends `upsert: true`** — without
|
|
210
|
+
it, an already-existing role's `scopes` were silently left unchanged on a
|
|
211
|
+
blueprint re-apply, so editing a role's `allowedActions`/`dataScope` and
|
|
212
|
+
re-running `bootstrap` never actually took effect, contradicting
|
|
213
|
+
`LoaderResult.roleActions`' own documented "reconciled means updated to
|
|
214
|
+
match" contract (the same false-convergence class this release fixes for
|
|
215
|
+
issuers). Pre-existing, caught in review while building `roleIds`
|
|
216
|
+
composition — now more consequential than before, since a `roleIds`-
|
|
217
|
+
composed AccessProfile ties the blueprint's own automatic service
|
|
218
|
+
credential to a role's live (previously possibly-stale) scope.
|
|
219
|
+
- **The bootstrap loader's scope gate now admits an unqualified `profiles:c`
|
|
220
|
+
when the same clause also declares `capabilities: [member-lifecycle]`** —
|
|
221
|
+
the platform's own documented pattern for a self-signup/admin role that
|
|
222
|
+
invites without raw tenant-wide `users:*` could not previously be
|
|
223
|
+
authored through `vectros blueprint apply`/`validate` at all: the gate's
|
|
224
|
+
action check and its capability check ran fully independently, so a bare
|
|
225
|
+
`profiles:c` always rejected regardless of what capabilities accompanied
|
|
226
|
+
it. The exception is narrow and additive — bare form only (a literal
|
|
227
|
+
qualifier like `profiles:c:usr_<id>` is still rejected unconditionally,
|
|
228
|
+
since `member-lifecycle`'s own elevation requires the fully unqualified
|
|
229
|
+
grant and does nothing to bound a literal-principal risk), same clause
|
|
230
|
+
only (a sibling clause's capability grants no benefit), every
|
|
231
|
+
pre-existing call site unaffected (the new parameter is optional).
|
|
232
|
+
- **`vectros blueprint validate`/`bootstrap`/`apply` no longer reject `${{ member.scope.<namespace> }}`
|
|
233
|
+
(and its `:<level>` form) as an unrecognized placeholder.** This is a real, shipped, server-side
|
|
234
|
+
mechanism — resolved per request from `org_membership`-shaped records, with no dependency on a
|
|
235
|
+
separate, unimplemented token-select step that `${{ self.scope.<ns> }}` needs — the CLI's local
|
|
236
|
+
lint simply hadn't been updated to recognize it, so an otherwise-correct blueprint authored
|
|
237
|
+
against it failed `validate` outright with a false `datascope-unknown-placeholder` error, even
|
|
238
|
+
though the platform itself would have accepted it. Found building a real org-membership-based
|
|
239
|
+
blueprint: a role's `${{ self.scope.org }}` dataScope silently granted nothing (an unresolved
|
|
240
|
+
self-placeholder matches nothing, not everything) because nothing populates that identity value
|
|
241
|
+
by default — switching to `${{ member.scope.org }}` is the actual fix for an org_membership-based
|
|
242
|
+
role, and the lint was wrongly in the way of it.
|
|
243
|
+
- **`vectros access explain` now reports `${{ member.scope.<ns>[:<level>] }}` as undecidable** instead
|
|
244
|
+
of silently mismodeling the clause as an opaque literal string — the clause normalizer had no MEMBER
|
|
245
|
+
case at all before this. Same treatment `${{ under.self.* }}` already gets, via `undecidableDims`
|
|
246
|
+
rather than a false match or a silent denial.
|
|
247
|
+
|
|
248
|
+
### Changed
|
|
249
|
+
|
|
250
|
+
- **`@vectros-ai/sdk` repinned to the latest available `0.41.0` build** —
|
|
251
|
+
picks up the additive role composition's `roleIds` on
|
|
252
|
+
`AccessProfileRequest`/`AccessProfileResponse` and the issuer-update
|
|
253
|
+
surface (`IssuerUpdateRequest`, `client.auth.updateIssuer`). No other wire
|
|
254
|
+
shape changed since the prior pin (verified against the published
|
|
255
|
+
tarballs, not just source ancestry).
|
|
256
|
+
|
|
257
|
+
## 0.18.0
|
|
258
|
+
|
|
259
|
+
### Added
|
|
260
|
+
|
|
261
|
+
- **`vectros blueprint plan` now renders the fully-expanded, effective
|
|
262
|
+
per-clause `dataScope` for every declared role** (needs
|
|
263
|
+
`@vectros-ai/blueprints` ≥ 0.15.0) — not just the role's flattened action
|
|
264
|
+
set. This is the review-legibility mitigation the `fragments:`/
|
|
265
|
+
`dataScopeRef` sugar requires: a clause referencing a fragment has already
|
|
266
|
+
been expanded to a literal `dataScope` by the time the CLI sees it, so the
|
|
267
|
+
plan shows exactly what will be granted, never the DSL indirection that
|
|
268
|
+
produced it. Two clauses sharing an action set can legitimately carry
|
|
269
|
+
different `dataScope`, so this is shown per clause, not flattened across
|
|
270
|
+
the role the way the action set is.
|
|
271
|
+
- **A blueprint's `roleAssumable` map is now wired through
|
|
272
|
+
apply** — `vectros bootstrap`/`blueprint apply` passes
|
|
273
|
+
`roleAssumable.<roleId>` straight through to that role's `createRole`
|
|
274
|
+
call as `assumable`, unchanged. `vectros blueprint plan` discloses the
|
|
275
|
+
grant per role (a security-relevant fact the operator approving the plan
|
|
276
|
+
needs to see, same reasoning as the existing capability disclosure), and
|
|
277
|
+
`vectros role get`'s human view now shows a role's `assumable` grant
|
|
278
|
+
(`--json` already did — it prints the SDK response verbatim). Needs
|
|
279
|
+
`@vectros-ai/blueprints` ≥ 0.15.0 and `@vectros-ai/sdk` ≥ 0.41.0 (the
|
|
280
|
+
first generated SDK build carrying `Role.assumable`).
|
|
281
|
+
|
|
282
|
+
## 0.17.0
|
|
283
|
+
### Added
|
|
284
|
+
|
|
285
|
+
- **`vectros issuers list --context <ctx>` and `vectros issuers get <issuerId>
|
|
286
|
+
--context <ctx>`** — read a tenant's registered token-exchange issuers
|
|
287
|
+
without hand-minting a bootstrap token first. `GET`/`LIST /v1/auth/issuers`
|
|
288
|
+
require provisioning capability AND are context-confined for any non-root
|
|
289
|
+
caller, so `--context` is a required flag, not a default: a provisioning
|
|
290
|
+
token minted with no context pin resolves to the platform's `default`
|
|
291
|
+
context and won't see an issuer registered under any other one. Both verbs
|
|
292
|
+
now mint a single st_* carrying provisioning capability PINNED to
|
|
293
|
+
`--context` — a shape the CLI's auth seam couldn't produce before this.
|
|
294
|
+
- **A blueprint's `namespaces[]` entry can declare `tenantWide: true`** (needs
|
|
295
|
+
`@vectros-ai/blueprints` ≥ 0.14.0), requesting the platform's tenant-wide
|
|
296
|
+
registration form instead of the blueprint's own context. `bootstrap`/`apply`
|
|
297
|
+
now REFUSE such a blueprint before anything provisions — naming the
|
|
298
|
+
namespace(s) — unless invoked with the new `--allow-tenant-wide-namespaces`
|
|
299
|
+
flag: a static, scriptable opt-in, deliberately not an interactive runtime
|
|
300
|
+
prompt. This is the human-deliberateness check only; whether the request
|
|
301
|
+
actually succeeds is still decided server-side by an OWNER-gated platform
|
|
302
|
+
capability the flag has no bearing on — a non-OWNER credential gets the
|
|
303
|
+
existing clear failure regardless of the flag. An ordinary blueprint (no
|
|
304
|
+
`tenantWide` namespace) is completely unaffected.
|
|
305
|
+
- **`vectros blueprint plan`'s namespace preview names a `tenantWide` namespace
|
|
306
|
+
explicitly** (`TENANT-WIDE — visible to every context`, in place of the usual
|
|
307
|
+
`owned by '<contextId>'`), and its legend gains a carve-out: unlike an
|
|
308
|
+
ordinary namespace registration (which converges silently on a matching
|
|
309
|
+
re-apply), a tenant-wide one never adopts an existing registration, even an
|
|
310
|
+
exact field-for-field match — a collision always fails.
|
|
311
|
+
|
|
312
|
+
### Changed
|
|
313
|
+
|
|
314
|
+
- **A namespace-name collision on a `tenantWide` registration is always a hard
|
|
315
|
+
failure, never adopted.** The existing read-back-and-reconcile behavior for
|
|
316
|
+
an ordinary, context-owned namespace (a re-apply matching every declared
|
|
317
|
+
field converges silently) does NOT extend to a tenant-wide one: a tenant-wide
|
|
318
|
+
row is co-owned by no single blueprint, so a field-for-field match is not
|
|
319
|
+
evidence it's safe to treat as "this blueprint's" — it could just as easily
|
|
320
|
+
be a different blueprint's (or a manual) registration that happens to declare
|
|
321
|
+
the same shape. The platform's 400 propagates as-is on any collision.
|
|
322
|
+
|
|
323
|
+
### Fixed
|
|
324
|
+
|
|
325
|
+
- **`bootstrap`/`apply` now register a blueprint's `namespaces[]` BEFORE
|
|
326
|
+
creating any identity it declares in `identities[]`.** Previously, apply-time
|
|
327
|
+
identity resolution ran before namespace registration, so a blueprint
|
|
328
|
+
declaring `identities:` in a namespace it ALSO registers via `namespaces:`
|
|
329
|
+
could never succeed — creating that identity requires the namespace to
|
|
330
|
+
already be registered, and the old ordering always raced the identity create
|
|
331
|
+
ahead of it. The app-context, service principal, and every declared
|
|
332
|
+
namespace are now fully provisioned first; identity resolution (and
|
|
333
|
+
everything downstream) runs after. This is a step-ordering change only — an
|
|
334
|
+
unaffected blueprint (no `identities:` referencing a namespace it also
|
|
335
|
+
registers) sees no behavior change, and no CLI flag or blueprint schema
|
|
336
|
+
changed.
|
|
337
|
+
|
|
6
338
|
## 0.16.0
|
|
7
339
|
|
|
8
340
|
### Added
|