@vectros-ai/cli 0.21.0 → 0.22.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 +41 -8
- package/CHANGELOG.md +187 -1
- package/README.md +6 -8
- package/THIRD-PARTY-NOTICES.md +2 -3
- package/dist/cli.js +728 -385
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +728 -385
- package/dist/cli.mjs.map +1 -1
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -1614,7 +1614,7 @@ var require_proper_lockfile = __commonJS({
|
|
|
1614
1614
|
// src/cli-bootstrap.ts
|
|
1615
1615
|
var readline2 = __toESM(require("readline/promises"));
|
|
1616
1616
|
|
|
1617
|
-
//
|
|
1617
|
+
// node_modules/@vectros-ai/sdk/dist/index.mjs
|
|
1618
1618
|
var __defProp2 = Object.defineProperty;
|
|
1619
1619
|
var __export = (target, all) => {
|
|
1620
1620
|
for (var name in all)
|
|
@@ -5734,7 +5734,7 @@ var AuthClient = class {
|
|
|
5734
5734
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/auth/issuers/{issuerId}");
|
|
5735
5735
|
}
|
|
5736
5736
|
/**
|
|
5737
|
-
* Updates the mutable fields of a registered issuer: `subClaim`, `emailClaim`, `status` (`active`/`suspended` — a suspended issuer's tokens are rejected identically to an unregistered issuer at exchange time), and `selfSignupPolicies`. Fields omitted from the body are left unchanged (partial update). `issuer`, `jwksUri`, `audience`, and `contextId` are trust-anchor / routing-pin fields and are immutable via this route — supplying a value that differs from the current registration is rejected with 400; supplying the current value back is a no-op. Rotating a trust anchor requires deleting and re-registering the issuer, which is itself refused while any user is bound through it. Requires a root API key or the bootstrap's provisioning capability, gated identically to every other operation on this surface. A credential confined to one app context may only update an issuer registered in that context; naming one registered in another context returns 404, identically to a nonexistent issuerId. A root API key may update any issuer.
|
|
5737
|
+
* Updates the mutable fields of a registered issuer: `subClaim`, `emailClaim`, `userinfoUri`, `capturedClaims`, `restrictedToDomain` (#1250 Tier 1a — a new non-blank value must already be a VERIFIED domain for your account; an empty string clears it back to domain-less; see `IssuerUpdateRequest.restrictedToDomain` for the full semantics), `status` (`active`/`suspended` — a suspended issuer's tokens are rejected identically to an unregistered issuer at exchange time), and `selfSignupPolicies`. Fields omitted from the body are left unchanged (partial update). `issuer`, `jwksUri`, `audience`, and `contextId` are trust-anchor / routing-pin fields and are immutable via this route — supplying a value that differs from the current registration is rejected with 400; supplying the current value back is a no-op. Rotating a trust anchor requires deleting and re-registering the issuer, which is itself refused while any user is bound through it. **`subClaim` is identity-determining, not merely cosmetic**: it names which verified JWT claim is read as the federated user's identifier, so changing it on an issuer that already has bound users would silently re-identify (or, under self-signup, orphan) every one of them — changing it is therefore refused once any user has bound through this issuer, the same guard `DELETE` already applies, and is only free before the first real login. Requires a root API key or the bootstrap's provisioning capability, gated identically to every other operation on this surface. A credential confined to one app context may only update an issuer registered in that context; naming one registered in another context returns 404, identically to a nonexistent issuerId. A root API key may update any issuer.
|
|
5738
5738
|
*
|
|
5739
5739
|
* @param {Vectros.IssuerUpdateRequest} request
|
|
5740
5740
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -5801,7 +5801,7 @@ var AuthClient = class {
|
|
|
5801
5801
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "PUT", "/v1/auth/issuers/{issuerId}");
|
|
5802
5802
|
}
|
|
5803
5803
|
/**
|
|
5804
|
-
* Deregisters a trusted third-party IdP issuer. Requires a root API key or the bootstrap's provisioning capability. A credential confined to one app context may only deregister an issuer registered in that context; naming one registered in another context returns 404, identically to a nonexistent issuerId. A root API key may deregister any issuer. Refused if any user account was ever created or matched via this issuer (by a prior self-signup or accepted invite, through `POST /v1/auth/token/exchange`) — that access cannot be silently orphaned.
|
|
5804
|
+
* Deregisters a trusted third-party IdP issuer. Requires a root API key or the bootstrap's provisioning capability. A credential confined to one app context may only deregister an issuer registered in that context; naming one registered in another context returns 404, identically to a nonexistent issuerId. A root API key may deregister any issuer. Refused if any user account was ever created or matched via this issuer (by a prior self-signup or accepted invite, through `POST /v1/auth/token/exchange`) — that access cannot be silently orphaned. This is unconditional on the affected users' `status`: suspending them first does not lift the refusal. **A bound registration cannot be replaced, self-service, within its own app context**: registering a replacement issuer under a different issuerId in the SAME context also fails, because the context's one-active-issuer claim is released only when THIS registration is deleted. Two real options: suspend this issuer (stops new exchanges immediately) and register a replacement under a DIFFERENT app context — note that targets a different context, so this context's existing users/roles/access profiles are not carried over; or contact your platform operator, who can force-release this registration so a replacement may be registered in the SAME context under a new issuerId. **The operator path is not a lighter-weight alternative to the first — it PERMANENTLY RETIRES this issuerId and leaves every bound user unable to ever exchange through it again; it does not preserve continuity for them any better than registering under a different context does.** An issuer that has never been used for an exchange (no bound users yet) can always be deregistered.
|
|
5805
5805
|
*
|
|
5806
5806
|
* @param {Vectros.DeleteIssuerRequest} request
|
|
5807
5807
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -5927,7 +5927,7 @@ var AuthClient = class {
|
|
|
5927
5927
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/auth/issuers");
|
|
5928
5928
|
}
|
|
5929
5929
|
/**
|
|
5930
|
-
* Registers a trusted third-party IdP issuer that BYO-IdP token exchange (`POST /v1/auth/token/exchange`) may accept a `subject_token` from. Requires a root API key or the CLI bootstrap's provisioning capability — never an ordinary partner-grantable scope. A credential authorized only via the provisioning capability may register only against the app context it is bound to; naming a different one returns 403. A root API key is unaffected and may register against any of its contexts. Idempotent by `issuerId` within your tenant; the `(issuer, audience)` pair must not already be registered by a different issuerId/tenant. If `issuerId` collides with a registration owned by a different app context than the one you're confined to, the request fails with 400 rather than returning that context's configuration. An app context may have at most one active issuer — deregister the existing one first if you need to replace it. One issuer MAY serve several contexts today, each via its own registration row with a distinct `audience`.
|
|
5930
|
+
* Registers a trusted third-party IdP issuer that BYO-IdP token exchange (`POST /v1/auth/token/exchange`) may accept a `subject_token` from. Requires a root API key or the CLI bootstrap's provisioning capability — never an ordinary partner-grantable scope. A credential authorized only via the provisioning capability may register only against the app context it is bound to; naming a different one returns 403. A root API key is unaffected and may register against any of its contexts. Idempotent by `issuerId` within your tenant; the `(issuer, audience)` pair must not already be registered by a different issuerId/tenant. If `issuerId` collides with a registration owned by a different app context than the one you're confined to, the request fails with 400 rather than returning that context's configuration. An app context may have at most one active issuer — deregister the existing one first if you need to replace it. One issuer MAY serve several contexts today, each via its own registration row with a distinct `audience`. Optionally name `restrictedToDomain` (#1250 Tier 1a) to scope this registration's (issuer, audience) uniqueness to a specific VERIFIED domain rather than the bare pair — see that field's own description for the full contract, including why it does NOT protect a shared/consumer-IdP registration with no company-domain population.
|
|
5931
5931
|
*
|
|
5932
5932
|
* @param {Vectros.IssuerRequest} request
|
|
5933
5933
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -7425,7 +7425,7 @@ var DocumentsClient = class {
|
|
|
7425
7425
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/documents/{id}/versions");
|
|
7426
7426
|
}
|
|
7427
7427
|
/**
|
|
7428
|
-
* Starts a file-based document by returning a short-lived presigned S3 PUT URL. Upload the file bytes directly to `uploadUrl`; the document is then automatically queued for text extraction and asynchronous indexing. Supplying an `externalId` makes this idempotent — re-initiating an upload with the same `externalId` re-issues a fresh presigned URL
|
|
7428
|
+
* Starts a file-based document by returning a short-lived presigned S3 PUT URL. Upload the file bytes directly to `uploadUrl`; the document is then automatically queued for text extraction and asynchronous indexing. Supplying an `externalId` makes this idempotent — re-initiating an upload with the same `externalId` re-issues a fresh presigned URL for the SAME existing document, targeting a newly-staged object rather than the document's current one — your PUT lands there first, and the document only adopts it once the upload is validated (so a re-upload inherently replaces the file body) rather than creating a duplicate. The response's `created` field (and the HTTP status — 201 when a new document was minted, 200 when an existing one was matched) tells the two apart. With `?upsert=true`, the submitted `payload`/`title` are also applied to the matched document (file-body divergence cannot be diffed at upload-init — the bytes have not arrived yet — so the re-upload itself replaces the body). Creating a NEW document requires the `documents:c` scope. Re-uploading over an EXISTING document overwrites (and re-indexes) its body, so it is an update: it requires the `documents:u` scope (as does `?upsert=true` for the metadata). **Your PUT to `uploadUrl` MUST include the header named in the response's `requiredHeaderName`, set to `requiredHeaderValue` exactly.** It is a signed part of the presigned request — the PUT never reaches this service either way, so neither failure uses this API's JSON error shape. Omitting it, or sending a different value, invalidates the request's signature and S3 rejects it with a 403 `SignatureDoesNotMatch`. Sending it correctly makes the URL single-use: `requiredHeaderName` is always `If-None-Match` and `requiredHeaderValue` is always `*` — a create, a `#559` heal recovery, and a re-upload ALL target a freshly-minted, never-written key, so the same S3 conditional-write precondition applies uniformly (S3's own documented conditional-write behavior): a replay after the object already exists (from your own completed PUT, or anyone else's) fails with `412 Precondition Failed`; a replay after the object was deleted and never re-created finds an absent key again and can succeed — single-use is a property of the object's existence, not a one-time token. **Either precondition can also fail with `409 Conflict` if your PUT genuinely races a concurrent delete on the same object.** Whichever of `412` or `409` you receive, the remedy is the same: call this endpoint again for a fresh URL rather than retrying the same PUT — a fresh call always targets a new key, so it is never subject to whatever raced the previous one.
|
|
7429
7429
|
*
|
|
7430
7430
|
* @param {Vectros.FileUploadRequest} request
|
|
7431
7431
|
* @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -9191,7 +9191,7 @@ var IdentityClient = class {
|
|
|
9191
9191
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/users/exists-by-email");
|
|
9192
9192
|
}
|
|
9193
9193
|
/**
|
|
9194
|
-
* Returns the audit trail of changes to a user, most recent first. Identity history is always recorded
|
|
9194
|
+
* Returns the audit trail of changes to a user, most recent first, including after the user has been deleted. Identity history is always recorded. Post-delete, this is readable by an account-level (root) API key only — a context-confined credential 404s after the delete, the same response it would get for a nonexistent id. Sensitive field values are redacted in every historical version. Returns a page in the `{data, nextCursor}` envelope. Requires the `users:r` scope.
|
|
9195
9195
|
*
|
|
9196
9196
|
* @param {Vectros.GetUserVersionsRequest} request
|
|
9197
9197
|
* @param {IdentityClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -10668,7 +10668,7 @@ var SchemasClient = class {
|
|
|
10668
10668
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "PUT", "/v1/schemas/{id}");
|
|
10669
10669
|
}
|
|
10670
10670
|
/**
|
|
10671
|
-
* Permanently deletes a record schema. The request is refused with 409 if records of this type still exist — delete those records first, since every record must reference a live schema. A lineage base (a schema other schemas declare `basedOn`) also cannot be deleted while any such variant still exists — delete the variant schema(s) first. It is likewise refused while any trigger rule fires off this schema — delete those trigger rules first. Requires the `schemas:d` scope.
|
|
10671
|
+
* Permanently deletes a record schema. The request is refused with 409 if records of this type still exist — delete those records first, since every record must reference a live schema. The same applies to documents bound to this schema (for a schema declaring the `document` surface) — delete those documents first. A lineage base (a schema other schemas declare `basedOn`) also cannot be deleted while any such variant still exists — delete the variant schema(s) first. It is likewise refused while any trigger rule fires off this schema — delete those trigger rules first. Requires the `schemas:d` scope.
|
|
10672
10672
|
*
|
|
10673
10673
|
* @param {Vectros.DeleteSchemaRequest} request
|
|
10674
10674
|
* @param {SchemasClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -10803,7 +10803,7 @@ var ScriptsClient = class {
|
|
|
10803
10803
|
*
|
|
10804
10804
|
* **Budget.** Seconds of useful work: the script has 15 s of wall clock (never more than 16) and a bounded number of statements and host calls; a runaway script is stopped early with `RESOURCE_LIMIT_EXCEEDED`. The transaction is bounded by the number of underlying storage rows it writes — 100 rows after merging — rather than by the number of entities: creating a folder costs 3 rows; creating a plain text document or a record costs 1 row plus one per range-indexed lookup field plus one per `reference` field. So "create a folder and file N documents" holds up to 97 plain documents, 48 with one range field or one reference, 32 with both; if a script exceeds the limit the error says so (`WRITE_BUFFER_CAP_EXCEEDED`, with `total` and `limit`) and nothing is committed. This request takes the write-path limits (burst, credit ceiling, principal quota) once, before the script runs — there is no free-read allowance here, so an account over its credit ceiling is refused even for a read-only script. Reads the script performs are metered as reads, writes as writes, and execution time beyond what those operations include is charged as script execution time (see `GET /v1/usage`).
|
|
10805
10805
|
*
|
|
10806
|
-
* **Reads inside the script.** A `get` sees the script's own staged writes. A list, query or lookup never shows a row the script has deleted, shows a row it re-saved with the updated content, and does not
|
|
10806
|
+
* **Reads inside the script.** A `get` sees the script's own staged writes. A list, query or lookup never shows a row the script has deleted, shows a row it re-saved with the updated content, and does not show a row it created — you hold that row's id from `create()`.
|
|
10807
10807
|
*
|
|
10808
10808
|
* **Retries.** Send an optional `Idempotency-Key` header (1–128 characters of letters, digits, `.`, `_`, `:` or `-`) to make a retry safe: a request repeated under the same key within 24 hours receives the same response as the first attempt without executing again — when that first attempt SUCCEEDED (a duplicate still in flight is refused with `409 IDEMPOTENCY_IN_PROGRESS`; the same key on a different request is refused with `422 IDEMPOTENCY_KEY_REUSED`). A first attempt that FAILED with any other status is not remembered: the same key is free to try again, so a retry after a `400` or a `429` executes. If an execution's outcome cannot be determined — the script did not finish inside the budget while its writes may have committed — the response is `500 EXECUTION_OUTCOME_UNKNOWN` carrying `execution.id`, and a keyed retry receives that same answer: inspect what the script would have written, then use a NEW key for a new attempt. When a key is present the serialised `result` is capped at 16 KB instead of 256 KB. Without a key, a lost response cannot be distinguished from a failed one; re-read before retrying.
|
|
10809
10809
|
*
|
|
@@ -10898,11 +10898,14 @@ var ScriptsClient = class {
|
|
|
10898
10898
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/scripts/execute");
|
|
10899
10899
|
}
|
|
10900
10900
|
/**
|
|
10901
|
-
* Returns a paginated list of your scripts. Pass `name` to list every version of one script name (oldest first); omit it for a flat list across every name in your account (newest first). Results are returned as a `{data, nextCursor}` envelope — pass `nextCursor` as `startFrom` to fetch the next page. Requires the `scripts:r` scope.
|
|
10901
|
+
* Returns a paginated list of your scripts. Pass `name` to list every version of one script name (oldest first); omit it for a flat list across every name in your account (newest first). Results are returned as a `{data, nextCursor}` envelope — pass `nextCursor` as `startFrom` to fetch the next page. Requires the `scripts:r` scope. **Each row's `source` is omitted by default** (`sourceOmitted: true`) — scripts are immutable per version, so a name's version history can otherwise carry every version's complete source in one response. Pass `?includeSource=true` to get it back inline, or fetch one version's full source with a by-id GET. **`?name=<n>&latest=true`** answers "what is the current version of this name" in one bounded call — a single `ScriptResponse` object (not a page), instead of draining every page of the name's version history to find the newest yourself.
|
|
10902
10902
|
*
|
|
10903
10903
|
* @param {Vectros.ListScriptsRequest} request
|
|
10904
10904
|
* @param {ScriptsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
10905
10905
|
*
|
|
10906
|
+
* @throws {@link Vectros.BadRequestError}
|
|
10907
|
+
* @throws {@link Vectros.NotFoundError}
|
|
10908
|
+
*
|
|
10906
10909
|
* @example
|
|
10907
10910
|
* await client.scripts.listScripts({
|
|
10908
10911
|
* name: "notify-assignee",
|
|
@@ -10913,11 +10916,13 @@ var ScriptsClient = class {
|
|
|
10913
10916
|
return HttpResponsePromise.fromPromise(this.__listScripts(request, requestOptions));
|
|
10914
10917
|
}
|
|
10915
10918
|
async __listScripts(request = {}, requestOptions) {
|
|
10916
|
-
const { name, startFrom, limit } = request;
|
|
10919
|
+
const { name, startFrom, limit, includeSource, latest } = request;
|
|
10917
10920
|
const _queryParams = {
|
|
10918
10921
|
name,
|
|
10919
10922
|
startFrom,
|
|
10920
|
-
limit
|
|
10923
|
+
limit,
|
|
10924
|
+
includeSource,
|
|
10925
|
+
latest
|
|
10921
10926
|
};
|
|
10922
10927
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
10923
10928
|
const _headers = mergeHeaders(
|
|
@@ -10943,16 +10948,23 @@ var ScriptsClient = class {
|
|
|
10943
10948
|
return { data: _response.body, rawResponse: _response.rawResponse };
|
|
10944
10949
|
}
|
|
10945
10950
|
if (_response.error.reason === "status-code") {
|
|
10946
|
-
|
|
10947
|
-
|
|
10948
|
-
|
|
10949
|
-
|
|
10950
|
-
|
|
10951
|
+
switch (_response.error.statusCode) {
|
|
10952
|
+
case 400:
|
|
10953
|
+
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
10954
|
+
case 404:
|
|
10955
|
+
throw new NotFoundError(_response.error.body, _response.rawResponse);
|
|
10956
|
+
default:
|
|
10957
|
+
throw new VectrosError({
|
|
10958
|
+
statusCode: _response.error.statusCode,
|
|
10959
|
+
body: _response.error.body,
|
|
10960
|
+
rawResponse: _response.rawResponse
|
|
10961
|
+
});
|
|
10962
|
+
}
|
|
10951
10963
|
}
|
|
10952
10964
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/scripts");
|
|
10953
10965
|
}
|
|
10954
10966
|
/**
|
|
10955
|
-
* Stores a new version of a script object. Every POST creates a genuinely new version — this is NOT idempotent-by-name: pushing `name` again always creates a new row with `scriptVersion` auto-incremented (the first version pushed for a name is 1). `source` is stored as-is: nothing parses or validates it at push time. A version runs when a trigger rule references it (`POST /v1/triggers`) or when `POST /v1/scripts/execute` names it. Requires the `scripts:c` scope.
|
|
10967
|
+
* Stores a new version of a script object. Every POST creates a genuinely new version — this is NOT idempotent-by-name: pushing `name` again always creates a new row with `scriptVersion` auto-incremented (the first version pushed for a name is 1). `source` is stored as-is: nothing parses or validates it at push time. A version runs when a trigger rule references it (`POST /v1/triggers`) or when `POST /v1/scripts/execute` names it. `provisionedBy` MAY BE SET ON ABSENT, NEVER CHANGED, evaluated against the CURRENT LATEST version of this `name` at push time: if the latest version has no marker recorded, this push may set any value; if the latest version already has one, an omitted value inherits it forward and a DIFFERENT value is refused — a live comparison against whichever version is newest at push time, not a value fixed for the name's whole history. Pushing onto an EXISTING name with no `provisionedBy` recorded on its current latest version additionally requires that you were the caller who pushed that latest version — a name carrying a `provisionedBy` marker is exempt from this check (proving you can supply or inherit the matching marker is itself the ownership proof there). Requires the `scripts:c` scope.
|
|
10956
10968
|
*
|
|
10957
10969
|
* @param {Vectros.ScriptRequest} request
|
|
10958
10970
|
* @param {ScriptsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -11139,12 +11151,13 @@ var ScriptsClient = class {
|
|
|
11139
11151
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "PUT", "/v1/scripts/{id}");
|
|
11140
11152
|
}
|
|
11141
11153
|
/**
|
|
11142
|
-
* Permanently deletes one script version by ID. Deleting one version does not affect any other version of the same name. Requires the `scripts:d` scope.
|
|
11154
|
+
* Permanently deletes one script version by ID. Deleting one version does not affect any other version of the same name. Requires the `scripts:d` scope. Refused with 409 if a live trigger rule still references this version — either directly (a pinned `scriptRef`), or via `"latest"` when this IS the newest version of its name. Delete or re-point those rules first.
|
|
11143
11155
|
*
|
|
11144
11156
|
* @param {Vectros.DeleteScriptRequest} request
|
|
11145
11157
|
* @param {ScriptsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
11146
11158
|
*
|
|
11147
11159
|
* @throws {@link Vectros.NotFoundError}
|
|
11160
|
+
* @throws {@link Vectros.ConflictError}
|
|
11148
11161
|
* @throws {@link Vectros.TooManyRequestsError}
|
|
11149
11162
|
*
|
|
11150
11163
|
* @example
|
|
@@ -11184,6 +11197,8 @@ var ScriptsClient = class {
|
|
|
11184
11197
|
switch (_response.error.statusCode) {
|
|
11185
11198
|
case 404:
|
|
11186
11199
|
throw new NotFoundError(_response.error.body, _response.rawResponse);
|
|
11200
|
+
case 409:
|
|
11201
|
+
throw new ConflictError(_response.error.body, _response.rawResponse);
|
|
11187
11202
|
case 429:
|
|
11188
11203
|
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
11189
11204
|
default:
|
|
@@ -11729,6 +11744,25 @@ var logging;
|
|
|
11729
11744
|
var import_zod = require("zod");
|
|
11730
11745
|
var import_zod2 = require("zod");
|
|
11731
11746
|
var CONTEXT_ID_RE = /^[a-z][a-z0-9-]{2,30}$/;
|
|
11747
|
+
var FORGERY_CHARS_RE = /[\x00-\x1f\x7f-\x9f\u00ad\u061c\u200b-\u200f\u2028\u2029\u202a-\u202e\u2060\u2066-\u2069\ufeff\u{e0000}-\u{e007f}]/u;
|
|
11748
|
+
function safeDisplayString(base) {
|
|
11749
|
+
return base.refine((v) => !FORGERY_CHARS_RE.test(v), {
|
|
11750
|
+
message: "must not contain control characters, invisible/zero-width characters, line/paragraph separators, or bidi marks/overrides \u2014 these can forge, hide, or reorder text in a rendered plan preview"
|
|
11751
|
+
});
|
|
11752
|
+
}
|
|
11753
|
+
function safeUrlString(base) {
|
|
11754
|
+
return base.refine(
|
|
11755
|
+
(v) => {
|
|
11756
|
+
if (!/^[\x20-\x7e]*$/.test(v)) return false;
|
|
11757
|
+
const authorityMatch = /^https:\/\/([^/?#]*)/i.exec(v);
|
|
11758
|
+
if (!authorityMatch) return false;
|
|
11759
|
+
return /^[A-Za-z0-9.-]+(:\d+)?$/.test(authorityMatch[1]);
|
|
11760
|
+
},
|
|
11761
|
+
{
|
|
11762
|
+
message: "must be a printable-ASCII https:// URL whose authority is only letters, digits, dot, dash, and an optional port \u2014 no userinfo ('@'), percent-encoding, or non-ASCII host (including a look-alike/homoglyph domain) is accepted; supply an internationalized domain in its punycode (xn--\u2026) form"
|
|
11763
|
+
}
|
|
11764
|
+
);
|
|
11765
|
+
}
|
|
11732
11766
|
var MAX_SCOPE_DIMENSIONS = 2;
|
|
11733
11767
|
var ValidationRulesSchema = import_zod.z.object({
|
|
11734
11768
|
required: import_zod.z.boolean().optional(),
|
|
@@ -11758,7 +11792,7 @@ var RenderHintsSchema = import_zod.z.object({
|
|
|
11758
11792
|
displayField: import_zod.z.boolean().optional()
|
|
11759
11793
|
}).strict();
|
|
11760
11794
|
var BlueprintFieldDefSchema = import_zod.z.object({
|
|
11761
|
-
fieldId: import_zod.z.string().min(1),
|
|
11795
|
+
fieldId: safeDisplayString(import_zod.z.string().min(1)),
|
|
11762
11796
|
fieldType: import_zod.z.string().min(1),
|
|
11763
11797
|
required: import_zod.z.boolean().optional(),
|
|
11764
11798
|
searchable: import_zod.z.boolean().optional(),
|
|
@@ -11849,13 +11883,13 @@ var BlueprintFieldDefSchema = import_zod.z.object({
|
|
|
11849
11883
|
}
|
|
11850
11884
|
});
|
|
11851
11885
|
var BlueprintLookupFieldSchema = import_zod.z.union([
|
|
11852
|
-
import_zod.z.string().min(1),
|
|
11886
|
+
safeDisplayString(import_zod.z.string().min(1)),
|
|
11853
11887
|
import_zod.z.object({
|
|
11854
11888
|
// A lookup field declares either ONE field (fieldName) or 2-3 fields
|
|
11855
11889
|
// matched together (fieldNames) — never both, never neither. `.strict()`
|
|
11856
11890
|
// below only rejects unknown KEYS; it does not enforce this combination,
|
|
11857
11891
|
// so the `.superRefine()` after it carries the actual rule.
|
|
11858
|
-
fieldName: import_zod.z.string().min(1).optional(),
|
|
11892
|
+
fieldName: safeDisplayString(import_zod.z.string().min(1)).optional(),
|
|
11859
11893
|
// Composite (conjunctive) lookup: match on all of these fields at once —
|
|
11860
11894
|
// "every record where `status` is `open` AND `area` is `billing`", exact,
|
|
11861
11895
|
// in a stable order. Order is significant and locked at create: a query
|
|
@@ -11864,7 +11898,7 @@ var BlueprintLookupFieldSchema = import_zod.z.union([
|
|
|
11864
11898
|
// lookup for that. Record-only: the schema's `allowedSurfaces` must be
|
|
11865
11899
|
// exactly `['record']` (checked at the schema level below) — the
|
|
11866
11900
|
// platform's composite index has no document/user/entity reader yet.
|
|
11867
|
-
fieldNames: import_zod.z.array(import_zod.z.string().min(1)).min(2).max(3).optional(),
|
|
11901
|
+
fieldNames: import_zod.z.array(safeDisplayString(import_zod.z.string().min(1))).min(2).max(3).optional(),
|
|
11868
11902
|
// Not available on a composite (`fieldNames`) lookup — checked below.
|
|
11869
11903
|
unique: import_zod.z.boolean().optional(),
|
|
11870
11904
|
// Opt this field into ordered range + prefix lookups (from/to/prefix) on
|
|
@@ -11914,7 +11948,14 @@ var BlueprintCapabilitiesSchema = import_zod.z.object({
|
|
|
11914
11948
|
triggersEnabled: import_zod.z.boolean().optional()
|
|
11915
11949
|
}).strict();
|
|
11916
11950
|
var BlueprintSchemaSchema = import_zod.z.object({
|
|
11917
|
-
|
|
11951
|
+
// safeDisplayString: buildPlan's schema step renders this raw into a single summary
|
|
11952
|
+
// line (`schema '${s.typeName}' (...)`) — a gap found after the rest of this file's
|
|
11953
|
+
// forgery-character pass had already shipped, the same "unconstrained free-text field
|
|
11954
|
+
// reaches a rendered plan line" shape as issuer/allowedActions/etc. `displayName`
|
|
11955
|
+
// here is NOT wrapped: unlike
|
|
11956
|
+
// servicePrincipal's own displayName, this one is never interpolated into a rendered
|
|
11957
|
+
// summary anywhere in loader.ts today (only forwarded to the schema-upsert payload).
|
|
11958
|
+
typeName: safeDisplayString(import_zod.z.string().min(1)),
|
|
11918
11959
|
displayName: import_zod.z.string().min(1),
|
|
11919
11960
|
description: import_zod.z.string().optional(),
|
|
11920
11961
|
// Default search-index mode for instances of this type. HYBRID, SEMANTIC and
|
|
@@ -12051,7 +12092,7 @@ var BlueprintAccessProfileSchema = import_zod.z.object({
|
|
|
12051
12092
|
// `.superRefine` on this schema enforces the XOR, mirroring the platform's
|
|
12052
12093
|
// own access-profile scopes/role-composition XOR check). Exactly one of
|
|
12053
12094
|
// the two authors this profile's grant.
|
|
12054
|
-
allowedActions: import_zod.z.array(import_zod.z.string().min(1)).min(1).optional(),
|
|
12095
|
+
allowedActions: import_zod.z.array(safeDisplayString(import_zod.z.string().min(1))).min(1).optional(),
|
|
12055
12096
|
// Additive role composition: this profile's effective grant is
|
|
12056
12097
|
// each named role's own clauses, concatenated in the order listed, instead of
|
|
12057
12098
|
// an inline clause authored here. Every id must name a role this SAME
|
|
@@ -12080,7 +12121,14 @@ var BlueprintAccessProfileSchema = import_zod.z.object({
|
|
|
12080
12121
|
// NOT see tenant-level/seed records). Every KEY is validated below
|
|
12081
12122
|
// (`lintDataScopeKeys`) — `userId` or a grammar-valid `scope:<ns>`, no
|
|
12082
12123
|
// other shape.
|
|
12083
|
-
|
|
12124
|
+
// safeDisplayString on the value: not currently rendered by VALUE anywhere in buildPlan
|
|
12125
|
+
// (only `Object.keys(...)` of a profile's dataScope is shown today, unlike a role clause's
|
|
12126
|
+
// dataScope, which `renderPlanDataScope` prints values for — the reason that sibling field
|
|
12127
|
+
// WAS wrapped). Wrapped anyway for consistency with every other dataScope-shaped field in
|
|
12128
|
+
// this file, and so a future preview enhancement that starts showing these values (a very
|
|
12129
|
+
// plausible one, given the existing renderPlanDataScope precedent) doesn't silently reopen
|
|
12130
|
+
// this exact class on a field that looks, byte-for-byte, like every other one already closed.
|
|
12131
|
+
dataScope: import_zod.z.record(import_zod.z.array(import_zod.z.union([safeDisplayString(import_zod.z.string().min(1)), import_zod.z.null()]))).optional(),
|
|
12084
12132
|
// Optional identity overrides for the service principal's profile — scope
|
|
12085
12133
|
// values its key STAMPS onto everything it writes (a credential can only
|
|
12086
12134
|
// stamp ownership its identity carries). Keys are the namespaced `scope:<ns>`
|
|
@@ -12156,8 +12204,8 @@ var BlueprintAccessProfileSchema = import_zod.z.object({
|
|
|
12156
12204
|
}
|
|
12157
12205
|
});
|
|
12158
12206
|
var BlueprintRoleClauseSchema = import_zod.z.object({
|
|
12159
|
-
allowedActions: import_zod.z.array(import_zod.z.string().min(1)).min(1),
|
|
12160
|
-
dataScope: import_zod.z.record(import_zod.z.array(import_zod.z.union([import_zod.z.string().min(1), import_zod.z.null()]))).optional(),
|
|
12207
|
+
allowedActions: import_zod.z.array(safeDisplayString(import_zod.z.string().min(1))).min(1),
|
|
12208
|
+
dataScope: import_zod.z.record(import_zod.z.array(import_zod.z.union([safeDisplayString(import_zod.z.string().min(1)), import_zod.z.null()]))).optional(),
|
|
12161
12209
|
dataScopeRef: import_zod.z.string().min(1).optional(),
|
|
12162
12210
|
capabilities: GrantedCapabilitiesSchema,
|
|
12163
12211
|
assignableRoles: AssignableRolesSchema
|
|
@@ -12171,10 +12219,10 @@ var BlueprintRoleClauseSchema = import_zod.z.object({
|
|
|
12171
12219
|
}
|
|
12172
12220
|
});
|
|
12173
12221
|
var BlueprintFragmentsSchema = import_zod.z.record(
|
|
12174
|
-
import_zod.z.record(import_zod.z.array(import_zod.z.union([import_zod.z.string().min(1), import_zod.z.null()])))
|
|
12222
|
+
import_zod.z.record(import_zod.z.array(import_zod.z.union([safeDisplayString(import_zod.z.string().min(1)), import_zod.z.null()])))
|
|
12175
12223
|
);
|
|
12176
|
-
var BlueprintRolesSchema = import_zod.z.record(import_zod.z.array(BlueprintRoleClauseSchema).min(1));
|
|
12177
|
-
var BlueprintRoleAssumableSchema = import_zod.z.record(import_zod.z.array(import_zod.z.string().min(1)).min(1));
|
|
12224
|
+
var BlueprintRolesSchema = import_zod.z.record(safeDisplayString(import_zod.z.string().min(1)), import_zod.z.array(BlueprintRoleClauseSchema).min(1));
|
|
12225
|
+
var BlueprintRoleAssumableSchema = import_zod.z.record(import_zod.z.array(safeDisplayString(import_zod.z.string().min(1))).min(1));
|
|
12178
12226
|
var BlueprintRoleAssumableMapSchema = import_zod.z.record(BlueprintRoleAssumableSchema);
|
|
12179
12227
|
var BlueprintTriggerFiringSourceSchema = import_zod.z.object({
|
|
12180
12228
|
schemaName: import_zod.z.string().min(1),
|
|
@@ -12182,7 +12230,12 @@ var BlueprintTriggerFiringSourceSchema = import_zod.z.object({
|
|
|
12182
12230
|
}).strict();
|
|
12183
12231
|
var SCRIPT_VERSION_INTEGER_RE = /^[1-9][0-9]*$/;
|
|
12184
12232
|
var BlueprintTriggerScriptRefSchema = import_zod.z.object({
|
|
12185
|
-
|
|
12233
|
+
// safeDisplayString: rendered raw into buildPlan's trigger step (`${scriptRef.name}@...`).
|
|
12234
|
+
// When naming a script THIS blueprint ships, `name` is transitively safe already (it must
|
|
12235
|
+
// equal a `scripts` map key, itself grammar-pinned by SCRIPT_NAME_RE) — but a scriptRef
|
|
12236
|
+
// naming a script this blueprint does NOT ship is, by design, "entirely unrestricted" (see
|
|
12237
|
+
// this file's own doc on that case), so the wrap is load-bearing for exactly that path.
|
|
12238
|
+
name: safeDisplayString(import_zod.z.string().min(1)),
|
|
12186
12239
|
version: import_zod.z.string().min(1)
|
|
12187
12240
|
}).strict().superRefine((ref, ctx) => {
|
|
12188
12241
|
if (ref.version !== "latest" && !SCRIPT_VERSION_INTEGER_RE.test(ref.version)) {
|
|
@@ -12202,9 +12255,9 @@ var BlueprintTriggerManifestSchema = import_zod.z.array(
|
|
|
12202
12255
|
var BlueprintTriggerEntrySchema = import_zod.z.object({
|
|
12203
12256
|
firingSource: BlueprintTriggerFiringSourceSchema,
|
|
12204
12257
|
scriptRef: BlueprintTriggerScriptRefSchema,
|
|
12205
|
-
allowedActions: import_zod.z.array(import_zod.z.string().min(1)).min(1).optional(),
|
|
12258
|
+
allowedActions: import_zod.z.array(safeDisplayString(import_zod.z.string().min(1))).min(1).optional(),
|
|
12206
12259
|
roleIds: import_zod.z.array(import_zod.z.string().min(1)).min(1).optional(),
|
|
12207
|
-
dataScope: import_zod.z.record(import_zod.z.array(import_zod.z.union([import_zod.z.string().min(1), import_zod.z.null()]))).optional(),
|
|
12260
|
+
dataScope: import_zod.z.record(import_zod.z.array(import_zod.z.union([safeDisplayString(import_zod.z.string().min(1)), import_zod.z.null()]))).optional(),
|
|
12208
12261
|
capabilities: GrantedCapabilitiesSchema,
|
|
12209
12262
|
manifest: BlueprintTriggerManifestSchema,
|
|
12210
12263
|
/**
|
|
@@ -12228,7 +12281,7 @@ var BlueprintTriggerEntrySchema = import_zod.z.object({
|
|
|
12228
12281
|
* together are capped at 224 KB serialised — a firing whose projected input exceeds that is not
|
|
12229
12282
|
* run, and is recorded as an `INPUT_TOO_LARGE` failure naming the size.
|
|
12230
12283
|
*/
|
|
12231
|
-
fields: import_zod.z.array(import_zod.z.string().min(1))
|
|
12284
|
+
fields: import_zod.z.array(safeDisplayString(import_zod.z.string().min(1)))
|
|
12232
12285
|
}).strict().superRefine((entry, ctx) => {
|
|
12233
12286
|
const hasInline = entry.allowedActions !== void 0;
|
|
12234
12287
|
const hasRoleIds = entry.roleIds !== void 0;
|
|
@@ -12257,7 +12310,7 @@ var BlueprintTriggerEntrySchema = import_zod.z.object({
|
|
|
12257
12310
|
}
|
|
12258
12311
|
}
|
|
12259
12312
|
});
|
|
12260
|
-
var BlueprintTriggersSchema = import_zod.z.record(BlueprintTriggerEntrySchema);
|
|
12313
|
+
var BlueprintTriggersSchema = import_zod.z.record(safeDisplayString(import_zod.z.string().min(1)), BlueprintTriggerEntrySchema);
|
|
12261
12314
|
var SCRIPT_NAME_RE = /^[A-Za-z0-9_-]{1,64}$/;
|
|
12262
12315
|
var SCRIPT_SOURCE_MAX_BYTES = 3e5;
|
|
12263
12316
|
var SCRIPT_INPUT_CONTRACT_MAX_BYTES = 5e4;
|
|
@@ -12303,8 +12356,15 @@ var IdentityKindSchema = import_zod.z.string().min(1).superRefine((val, ctx) =>
|
|
|
12303
12356
|
});
|
|
12304
12357
|
var IdentityDeclSchema = import_zod.z.object({
|
|
12305
12358
|
kind: IdentityKindSchema,
|
|
12306
|
-
|
|
12307
|
-
|
|
12359
|
+
// safeDisplayString: `@vectros-ai/cli`'s `runBootstrapCli` and `blueprint plan` both print
|
|
12360
|
+
// this RAW to stdout ("~ <name> (<kind>) — external id '<externalId>'"), immediately before
|
|
12361
|
+
// the "Apply this plan? [y/N]" confirmation gate itself — the highest-severity gap found in
|
|
12362
|
+
// this pass, since it sits at the confirmation prompt's own disclosure text, not merely
|
|
12363
|
+
// somewhere upstream of it. `displayName` is wrapped too though not currently
|
|
12364
|
+
// rendered in any preview (defense-in-depth, matching this file's practice for its adjacent
|
|
12365
|
+
// sibling fields) — it does reach a platform API payload as an entity's `name`.
|
|
12366
|
+
externalId: safeDisplayString(import_zod.z.string().min(1)),
|
|
12367
|
+
displayName: safeDisplayString(import_zod.z.string().min(1)).optional(),
|
|
12308
12368
|
metadata: import_zod.z.record(import_zod.z.unknown()).optional()
|
|
12309
12369
|
}).strict();
|
|
12310
12370
|
var IDENTITY_NAME_RE = /^[A-Za-z_]\w*$/;
|
|
@@ -12315,8 +12375,8 @@ var IdentitiesDeclSchema = import_zod.z.record(
|
|
|
12315
12375
|
IdentityDeclSchema
|
|
12316
12376
|
);
|
|
12317
12377
|
var BlueprintServicePrincipalSchema = import_zod.z.object({
|
|
12318
|
-
externalId: import_zod.z.string().min(1),
|
|
12319
|
-
displayName: import_zod.z.string().min(1)
|
|
12378
|
+
externalId: safeDisplayString(import_zod.z.string().min(1)),
|
|
12379
|
+
displayName: safeDisplayString(import_zod.z.string().min(1))
|
|
12320
12380
|
}).strict();
|
|
12321
12381
|
var ISSUER_ID_RE = CONTEXT_ID_RE;
|
|
12322
12382
|
var MAX_CAPTURED_CLAIMS = 20;
|
|
@@ -12342,10 +12402,17 @@ var BlueprintIssuerSchema = import_zod.z.object({
|
|
|
12342
12402
|
issuerId: import_zod.z.string().regex(ISSUER_ID_RE, {
|
|
12343
12403
|
message: "issuerId must be 3-31 chars, start with a lowercase letter, then lowercase letters/digits/dashes (e.g. 'auth0-prod')"
|
|
12344
12404
|
}),
|
|
12345
|
-
/**
|
|
12346
|
-
|
|
12347
|
-
|
|
12348
|
-
|
|
12405
|
+
/**
|
|
12406
|
+
* The IdP's `iss` claim value, exactly as it appears in tokens it issues. `safeUrlString`,
|
|
12407
|
+
* not `safeDisplayString`: this is the trust-anchor field the plan preview exists to
|
|
12408
|
+
* disclose, so it gets the stronger positive check (see that function's own doc) rather
|
|
12409
|
+
* than the blocklist every OTHER free-text field uses — a homoglyph host here is exactly
|
|
12410
|
+
* what an operator approving a plan must be able to trust reading. Virtually every real
|
|
12411
|
+
* OIDC issuer is URL-shaped in practice, even though the spec does not strictly require it.
|
|
12412
|
+
*/
|
|
12413
|
+
issuer: safeUrlString(import_zod.z.string().min(1)),
|
|
12414
|
+
/** The IdP's remote JWKS endpoint, used to verify presented tokens' signatures. `safeUrlString` — see `issuer`'s own comment just above. */
|
|
12415
|
+
jwksUri: safeUrlString(import_zod.z.string().min(1)),
|
|
12349
12416
|
/**
|
|
12350
12417
|
* The `aud` claim value a presented subject_token must carry. Must be globally
|
|
12351
12418
|
* unique in combination with `issuer` — use a distinct audience per
|
|
@@ -12373,7 +12440,9 @@ var BlueprintIssuerSchema = import_zod.z.object({
|
|
|
12373
12440
|
* back to calling this endpoint (with the presented token as the bearer credential) and reads
|
|
12374
12441
|
* `emailClaim` from its JSON response instead. Omit to leave the fallback disabled.
|
|
12375
12442
|
*/
|
|
12376
|
-
|
|
12443
|
+
// `safeUrlString` — see `issuer`'s own comment above for why these three trust-anchor
|
|
12444
|
+
// fields get the stronger positive check instead of `safeDisplayString`'s blocklist.
|
|
12445
|
+
userinfoUri: safeUrlString(import_zod.z.string().min(1)).optional(),
|
|
12377
12446
|
/**
|
|
12378
12447
|
* Opt-in self-service signup: a list of {signupType, roleId} pairs. When a first-time exchange
|
|
12379
12448
|
* caller presents no invite token but names a signupType matching one of these (or omits it and
|
|
@@ -12413,9 +12482,18 @@ var BlueprintIssuerSchema = import_zod.z.object({
|
|
|
12413
12482
|
* separate opt-ins, and capturing a claim does not by itself expose it on a profile.
|
|
12414
12483
|
*/
|
|
12415
12484
|
capturedClaims: import_zod.z.array(
|
|
12416
|
-
|
|
12417
|
-
|
|
12418
|
-
|
|
12485
|
+
// `safeDisplayString` (a `.refine`) then the blank-name `.refine` — both ordered AFTER
|
|
12486
|
+
// `.max`, since `.refine` returns a ZodEffects, which has no `.max` (`.refine` itself
|
|
12487
|
+
// chains fine onto a ZodEffects — it's on the ZodType base, not ZodString-specific).
|
|
12488
|
+
// `.min(1)` would be a LENGTH check and would admit a whitespace-only name while the
|
|
12489
|
+
// message claims "non-blank"; the platform rejects on isBlank(). Note the value is
|
|
12490
|
+
// REJECTED, not trimmed — it reaches the wire exactly as authored, matching how the
|
|
12491
|
+
// platform stores it.
|
|
12492
|
+
safeDisplayString(
|
|
12493
|
+
import_zod.z.string().max(MAX_CAPTURED_CLAIM_NAME_LENGTH, {
|
|
12494
|
+
message: `a capturedClaims entry must be at most ${MAX_CAPTURED_CLAIM_NAME_LENGTH} characters`
|
|
12495
|
+
})
|
|
12496
|
+
).refine((name) => name.trim().length > 0, {
|
|
12419
12497
|
message: "a capturedClaims entry must be a non-blank OIDC claim name"
|
|
12420
12498
|
})
|
|
12421
12499
|
).max(MAX_CAPTURED_CLAIMS, {
|
|
@@ -12586,14 +12664,24 @@ function lintNamespaces(value, ctx) {
|
|
|
12586
12664
|
});
|
|
12587
12665
|
}
|
|
12588
12666
|
var SeedCommonShape = {
|
|
12589
|
-
/**
|
|
12590
|
-
|
|
12667
|
+
/**
|
|
12668
|
+
* The schema typeName this seed instantiates (should match a declared schema).
|
|
12669
|
+
* `safeDisplayString`, matching `BlueprintSchemaSchema.typeName`: NOT currently
|
|
12670
|
+
* rendered into any plan-preview line (the seed step only counts records/documents),
|
|
12671
|
+
* but it is the same field concept and costs nothing to close now rather than leave
|
|
12672
|
+
* as a silent gap the next preview enhancement reopens.
|
|
12673
|
+
*/
|
|
12674
|
+
typeName: safeDisplayString(import_zod.z.string().min(1)),
|
|
12591
12675
|
/**
|
|
12592
12676
|
* Stable, caller-supplied id — the loader's idempotency key AND the value other
|
|
12593
12677
|
* seeds resolve a `reference` against (across surfaces: a record seed may
|
|
12594
12678
|
* reference a document seed by its externalId, and vice versa).
|
|
12679
|
+
* `safeDisplayString`: not currently rendered in a plan-preview line (same as this schema's
|
|
12680
|
+
* `typeName` just above until this pass), but the same "costs nothing to close now" standard
|
|
12681
|
+
* applies — a short caller-supplied id, not free-form prose, so no YAML block-scalar
|
|
12682
|
+
* collision risk like `description`'s.
|
|
12595
12683
|
*/
|
|
12596
|
-
externalId: import_zod.z.string().min(1),
|
|
12684
|
+
externalId: safeDisplayString(import_zod.z.string().min(1)),
|
|
12597
12685
|
/**
|
|
12598
12686
|
* Optional scope ownership for the seeded item, as `namespace:value` entries
|
|
12599
12687
|
* (≤2, e.g. `['org:${{ identities.team }}']`; tokens substitute at apply
|
|
@@ -13252,15 +13340,31 @@ var BLUEPRINT_FIELD_PHASES = Object.freeze({
|
|
|
13252
13340
|
});
|
|
13253
13341
|
var BlueprintSchema = import_zod.z.object({
|
|
13254
13342
|
/** Stable blueprint id (the `--blueprint <name>` selector + idempotency key). */
|
|
13255
|
-
name: import_zod.z.string().min(1),
|
|
13256
|
-
version: import_zod.z.string().min(1),
|
|
13343
|
+
name: safeDisplayString(import_zod.z.string().min(1)),
|
|
13344
|
+
version: safeDisplayString(import_zod.z.string().min(1)),
|
|
13345
|
+
// NOT safeDisplayString — deliberately, after that wrap broke a real, multi-line-authored
|
|
13346
|
+
// blueprint's `description` in review. `description` is never interpolated into any
|
|
13347
|
+
// rendered plan-preview line (`buildPlan`'s header prints
|
|
13348
|
+
// `name`/`version`/`contextId`, never `description` — confirmed by grep, not assumed), so
|
|
13349
|
+
// wrapping it protected a sink that does not exist. It also actively broke real content:
|
|
13350
|
+
// this is exactly the field authors reach for a YAML folded (`>`) or literal (`|`) block
|
|
13351
|
+
// scalar to write, and clip chomping (the YAML default) always leaves exactly one trailing
|
|
13352
|
+
// `\n` — which `safeDisplayString` rejects as a C0 control, indistinguishable from a
|
|
13353
|
+
// forgery attempt. See `safeDisplayString`'s own doc for why this is a property of the
|
|
13354
|
+
// wrapper, not a one-off carve-out for this field alone.
|
|
13257
13355
|
description: import_zod.z.string().min(1),
|
|
13258
13356
|
/** The app-context the profile + scoped key bind to (e.g. "mcp"). */
|
|
13259
13357
|
contextId: import_zod.z.string().regex(CONTEXT_ID_RE, {
|
|
13260
13358
|
message: "contextId must be 3-31 chars, start with a lowercase letter, then lowercase letters/digits/dashes (e.g. 'mcp')"
|
|
13261
13359
|
}),
|
|
13262
|
-
/**
|
|
13263
|
-
|
|
13360
|
+
/**
|
|
13361
|
+
* Human-readable app-context name; defaults to `MCP — <name>` (see {@link contextNameOf})
|
|
13362
|
+
* when absent. `safeDisplayString`: `contextNameOf` renders this raw into the FIRST line of
|
|
13363
|
+
* every `buildPlan` summary (the `context '...' (...)` step) — a gap found after the rest
|
|
13364
|
+
* of this file's forgery-character pass had already shipped; same shape as
|
|
13365
|
+
* `name`/`version`/`description` right above it.
|
|
13366
|
+
*/
|
|
13367
|
+
contextName: safeDisplayString(import_zod.z.string().min(1)).optional(),
|
|
13264
13368
|
/**
|
|
13265
13369
|
* The deploying organization's own display name — distinct from {@link contextName}.
|
|
13266
13370
|
* `contextName` is the blueprint author's fixed identity for the app (the same for every
|
|
@@ -13268,8 +13372,13 @@ var BlueprintSchema = import_zod.z.object({
|
|
|
13268
13372
|
* deployer-supplied `${{ inputs.x }}` value rather than hardcoded. Used for branding on
|
|
13269
13373
|
* platform-sent correspondence (e.g. invite emails) alongside `contextName` — not a
|
|
13270
13374
|
* replacement for it. See {@link companyNameOf}.
|
|
13375
|
+
*
|
|
13376
|
+
* `safeDisplayString`: not interpolated into any CLI plan-preview line today (confirmed —
|
|
13377
|
+
* `loader.ts` never reads `companyName`), but it IS forwarded to the platform for the
|
|
13378
|
+
* invite-email correspondence described above, a second class of human-read surface this
|
|
13379
|
+
* value reaches. Wrapped for the same reason as every sibling scalar identity field here.
|
|
13271
13380
|
*/
|
|
13272
|
-
companyName: import_zod.z.string().min(1).optional(),
|
|
13381
|
+
companyName: safeDisplayString(import_zod.z.string().min(1)).optional(),
|
|
13273
13382
|
schemas: import_zod.z.array(BlueprintSchemaSchema).default([]),
|
|
13274
13383
|
accessProfile: BlueprintAccessProfileSchema,
|
|
13275
13384
|
servicePrincipal: BlueprintServicePrincipalSchema,
|
|
@@ -13346,9 +13455,16 @@ var BlueprintSchema = import_zod.z.object({
|
|
|
13346
13455
|
* context's name and needs a broader scope — see `@vectros-ai/cli`'s own notes.)
|
|
13347
13456
|
*/
|
|
13348
13457
|
identityProjectionClaims: import_zod.z.array(
|
|
13349
|
-
|
|
13350
|
-
|
|
13351
|
-
|
|
13458
|
+
// safeDisplayString: rendered raw into buildPlan's context step (`identity projection:
|
|
13459
|
+
// [...]`) — same rendered-plan-preview shape as `capturedClaims` (its structural pair,
|
|
13460
|
+
// and fixed alongside it originally), missed here in the first pass; same ordering rule
|
|
13461
|
+
// applies (safeDisplayString's own `.refine` before the blank-name `.refine` below,
|
|
13462
|
+
// both after `.max` since `.refine` returns a ZodEffects with no `.max`).
|
|
13463
|
+
safeDisplayString(
|
|
13464
|
+
import_zod.z.string().max(MAX_CAPTURED_CLAIM_NAME_LENGTH, {
|
|
13465
|
+
message: `an identityProjectionClaims entry must be at most ${MAX_CAPTURED_CLAIM_NAME_LENGTH} characters`
|
|
13466
|
+
})
|
|
13467
|
+
).refine((name) => name.trim().length > 0, {
|
|
13352
13468
|
message: "an identityProjectionClaims entry must be a non-blank claim name"
|
|
13353
13469
|
})
|
|
13354
13470
|
).max(MAX_CAPTURED_CLAIMS, {
|
|
@@ -15563,6 +15679,9 @@ function validateBaseUrl(rawUrl, opts = {}) {
|
|
|
15563
15679
|
if (host === ALLOWED_HOST_APEX || host.endsWith(ALLOWED_HOST_SUFFIX)) {
|
|
15564
15680
|
return rawUrl;
|
|
15565
15681
|
}
|
|
15682
|
+
if ((opts.additionalAllowedHosts ?? []).some((h) => host === h.toLowerCase())) {
|
|
15683
|
+
return rawUrl;
|
|
15684
|
+
}
|
|
15566
15685
|
throw new InvalidBaseUrlError(
|
|
15567
15686
|
`Refusing base URL host "${url.host}" \u2014 not an official Vectros host (expected vectros.ai or a *.vectros.ai subdomain). Set ${INSECURE_BASE_URL_ENV}=1 to override (e.g. for a trusted local proxy); never point the CLI at an untrusted host while authenticated.`
|
|
15568
15687
|
);
|
|
@@ -15750,6 +15869,9 @@ var PER_ENV = {
|
|
|
15750
15869
|
staging: { domainPrefix: "vectros-developer-staging", region: "us-east-1" },
|
|
15751
15870
|
production: { domainPrefix: "vectros-developer", region: "us-east-1" }
|
|
15752
15871
|
};
|
|
15872
|
+
var COGNITO_ALLOWED_HOSTS = Object.values(PER_ENV).map(
|
|
15873
|
+
(p) => `${p.domainPrefix}.auth.${p.region}.amazoncognito.com`
|
|
15874
|
+
);
|
|
15753
15875
|
var BAKED_CLIENT_IDS = {
|
|
15754
15876
|
staging: "61hjvm25kkjb1eei8t2d3jkcl8",
|
|
15755
15877
|
// validated against deployed staging hosted UI (2026-06-05)
|
|
@@ -15764,13 +15886,30 @@ var MissingCognitoClientIdError = class extends Error {
|
|
|
15764
15886
|
this.name = "MissingCognitoClientIdError";
|
|
15765
15887
|
}
|
|
15766
15888
|
};
|
|
15889
|
+
var REGION_SHAPE = /^[a-z]{2}(?:-[a-z]+)+-\d+$/;
|
|
15890
|
+
var InvalidCognitoRegionError = class extends Error {
|
|
15891
|
+
constructor(region) {
|
|
15892
|
+
super(
|
|
15893
|
+
`Invalid VECTROS_COGNITO_REGION ${JSON.stringify(region)} \u2014 expected an AWS region shape (e.g. "us-east-1").`
|
|
15894
|
+
);
|
|
15895
|
+
this.name = "InvalidCognitoRegionError";
|
|
15896
|
+
}
|
|
15897
|
+
};
|
|
15767
15898
|
function resolveCognitoConfig(env) {
|
|
15768
15899
|
const per = PER_ENV[env];
|
|
15769
|
-
const
|
|
15770
|
-
const
|
|
15900
|
+
const domainOverride = process.env.VECTROS_COGNITO_DOMAIN;
|
|
15901
|
+
const regionOverride = process.env.VECTROS_COGNITO_REGION;
|
|
15902
|
+
if (domainOverride === void 0 && regionOverride !== void 0 && !REGION_SHAPE.test(regionOverride)) {
|
|
15903
|
+
throw new InvalidCognitoRegionError(regionOverride);
|
|
15904
|
+
}
|
|
15905
|
+
const region = regionOverride ?? per.region;
|
|
15906
|
+
const rawDomain = domainOverride ?? `https://${per.domainPrefix}.auth.${region}.amazoncognito.com`;
|
|
15907
|
+
const domain = validateBaseUrl(rawDomain, {
|
|
15908
|
+
additionalAllowedHosts: COGNITO_ALLOWED_HOSTS
|
|
15909
|
+
}).replace(/\/$/, "");
|
|
15771
15910
|
const clientId = process.env.VECTROS_COGNITO_CLIENT_ID ?? BAKED_CLIENT_IDS[env];
|
|
15772
15911
|
if (!clientId) throw new MissingCognitoClientIdError(env);
|
|
15773
|
-
return { domain
|
|
15912
|
+
return { domain, clientId, region };
|
|
15774
15913
|
}
|
|
15775
15914
|
var LOOPBACK_PORTS = [8976, 8977, 8978];
|
|
15776
15915
|
var OAUTH_SCOPES = ["openid", "email", "profile"];
|
|
@@ -16568,7 +16707,7 @@ async function withAuth(opts, deps, body) {
|
|
|
16568
16707
|
log
|
|
16569
16708
|
});
|
|
16570
16709
|
} catch (err) {
|
|
16571
|
-
if (err instanceof MissingCredentialError) {
|
|
16710
|
+
if (err instanceof MissingCredentialError || err instanceof InvalidBaseUrlError || err instanceof InvalidCognitoRegionError) {
|
|
16572
16711
|
process.stderr.write(` \u2716 ${err.message}
|
|
16573
16712
|
`);
|
|
16574
16713
|
return 2;
|
|
@@ -16655,15 +16794,42 @@ var SCRIPT_CONFLICT_RETRY_DELAY_MS = 750;
|
|
|
16655
16794
|
function isVersionConflict(err) {
|
|
16656
16795
|
return typeof err === "object" && err !== null && err.statusCode === 409;
|
|
16657
16796
|
}
|
|
16658
|
-
|
|
16659
|
-
|
|
16660
|
-
|
|
16797
|
+
function isForbidden(err) {
|
|
16798
|
+
return typeof err === "object" && err !== null && err.statusCode === 403;
|
|
16799
|
+
}
|
|
16800
|
+
function withAdoptionContext(err, name) {
|
|
16801
|
+
if (!(err instanceof Error) || !isForbidden(err)) return err;
|
|
16802
|
+
return new Error(
|
|
16803
|
+
`${err.message}
|
|
16804
|
+
|
|
16805
|
+
(This push was adopting '${name}' into this blueprint's management, via --allow-script-adoption \u2014 this CLI's own ownership check cleared it, but the platform refused the push itself. The platform requires EITHER the credential pushing this scoped-token bootstrap to hold 'scripts:c', OR \u2014 if '${name}' already has an unmarked version \u2014 that this same apply is running as the SAME caller identity that originally pushed it. An account OWNER always resolves to the same identity and can adopt any unmarked name; a Dev Portal SUB-USER can only adopt a name they themselves hand-pushed. If neither applies here, ask the original pusher to run this apply, push the account owner's own login first, or push it directly via 'vectros scripts push'.)`
|
|
16661
16806
|
);
|
|
16662
|
-
|
|
16663
|
-
|
|
16664
|
-
|
|
16665
|
-
|
|
16666
|
-
|
|
16807
|
+
}
|
|
16808
|
+
function isScriptNameNotFound(err) {
|
|
16809
|
+
return typeof err === "object" && err !== null && err.statusCode === 404;
|
|
16810
|
+
}
|
|
16811
|
+
function isScriptPageEnvelope(result) {
|
|
16812
|
+
return "data" in result;
|
|
16813
|
+
}
|
|
16814
|
+
async function readLatestScriptVersion(ctxClient, name) {
|
|
16815
|
+
let result;
|
|
16816
|
+
try {
|
|
16817
|
+
result = await ctxClient.scripts.listScripts({ name, latest: true, includeSource: true });
|
|
16818
|
+
} catch (err) {
|
|
16819
|
+
if (isScriptNameNotFound(err)) return void 0;
|
|
16820
|
+
throw err;
|
|
16821
|
+
}
|
|
16822
|
+
if (result == null) {
|
|
16823
|
+
throw new Error(
|
|
16824
|
+
`Script '${name}': a bounded '?latest=true' read returned an empty response body instead of a version object. This is a platform/SDK contract change this code does not understand \u2014 refusing to guess whether the name is unclaimed or a version was silently omitted.`
|
|
16825
|
+
);
|
|
16826
|
+
}
|
|
16827
|
+
if (isScriptPageEnvelope(result)) {
|
|
16828
|
+
throw new Error(
|
|
16829
|
+
`Script '${name}': a bounded '?latest=true' read unexpectedly returned a page envelope (a \`data\` array) instead of a single version. This is a platform/SDK contract change this code does not understand \u2014 refusing to guess which row is actually current.`
|
|
16830
|
+
);
|
|
16831
|
+
}
|
|
16832
|
+
return result;
|
|
16667
16833
|
}
|
|
16668
16834
|
function requireScriptId(row, name) {
|
|
16669
16835
|
if (!row.id) {
|
|
@@ -16674,8 +16840,34 @@ function requireScriptId(row, name) {
|
|
|
16674
16840
|
function scriptMatches(stored, declared) {
|
|
16675
16841
|
return stored.source === declared.source && (stored.declaredInputContract ?? void 0) === declared.declaredInputContract;
|
|
16676
16842
|
}
|
|
16843
|
+
var ScriptOwnershipConflictError = class extends Error {
|
|
16844
|
+
constructor(name, owner, blueprintName) {
|
|
16845
|
+
super(
|
|
16846
|
+
`Script '${name}' is already provisioned by a different blueprint ('${owner}', this blueprint is '${blueprintName}') \u2014 refusing to push over it. No script version is pushed (anything already provisioned earlier in this same apply is unaffected). If you intended to replace that blueprint's script, do it under ITS name; if two blueprints legitimately need distinct code, give this one a different script name.`
|
|
16847
|
+
);
|
|
16848
|
+
this.name = "ScriptOwnershipConflictError";
|
|
16849
|
+
}
|
|
16850
|
+
};
|
|
16851
|
+
var ScriptAdoptionNotAllowedError = class extends Error {
|
|
16852
|
+
constructor(name) {
|
|
16853
|
+
super(
|
|
16854
|
+
`Script '${name}' already exists in this context and carries no blueprint provenance marker \u2014 it is either hand-authored or was pushed before this blueprint managed it. Refusing to push a new version onto it without an explicit opt-in: if a pre-existing trigger rule references '${name}' via 'version: latest', that rule's own operator-authored grant would run this blueprint's code from its very next firing \u2014 the blueprint supplying the code, someone else's rule supplying the authority. No script version is pushed (anything already provisioned earlier in this same apply is unaffected). Re-run with --allow-script-adoption once you've confirmed this blueprint should take over '${name}' going forward.`
|
|
16855
|
+
);
|
|
16856
|
+
this.name = "ScriptAdoptionNotAllowedError";
|
|
16857
|
+
}
|
|
16858
|
+
};
|
|
16859
|
+
function assertScriptPushOwnable(name, latest, blueprintName, allowScriptAdoption) {
|
|
16860
|
+
if (latest === void 0) return { adopting: false };
|
|
16861
|
+
const owner = latest.provisionedBy?.trim() || void 0;
|
|
16862
|
+
if (owner === void 0) {
|
|
16863
|
+
if (!allowScriptAdoption) throw new ScriptAdoptionNotAllowedError(name);
|
|
16864
|
+
return { adopting: true };
|
|
16865
|
+
}
|
|
16866
|
+
if (owner !== blueprintName) throw new ScriptOwnershipConflictError(name, owner, blueprintName);
|
|
16867
|
+
return { adopting: false };
|
|
16868
|
+
}
|
|
16677
16869
|
async function ensureScripts(args) {
|
|
16678
|
-
const { ctxClient, scripts, log } = args;
|
|
16870
|
+
const { ctxClient, scripts, blueprintName, allowScriptAdoption, log } = args;
|
|
16679
16871
|
const actions = [];
|
|
16680
16872
|
for (const [name, entry] of Object.entries(scripts)) {
|
|
16681
16873
|
let latest = await readLatestScriptVersion(ctxClient, name);
|
|
@@ -16686,18 +16878,32 @@ async function ensureScripts(args) {
|
|
|
16686
16878
|
id: requireScriptId(latest, name),
|
|
16687
16879
|
scriptVersion: latest.scriptVersion ?? 0
|
|
16688
16880
|
});
|
|
16689
|
-
|
|
16690
|
-
|
|
16691
|
-
|
|
16692
|
-
|
|
16881
|
+
const foreignOwner = latest.provisionedBy?.trim() || void 0;
|
|
16882
|
+
if (foreignOwner !== void 0 && foreignOwner !== blueprintName) {
|
|
16883
|
+
log.warn(
|
|
16884
|
+
{ component: "loader", scriptName: name, provisionedBy: foreignOwner },
|
|
16885
|
+
`script '${name}' already matches the declared source, but is provisioned by a DIFFERENT blueprint ('${foreignOwner}') \u2014 if this blueprint also declares a 'version: latest' trigger on this name, a future push by that other blueprint will change what that trigger runs`
|
|
16886
|
+
);
|
|
16887
|
+
} else {
|
|
16888
|
+
log.debug(
|
|
16889
|
+
{ component: "loader", scriptName: name, scriptVersion: latest.scriptVersion },
|
|
16890
|
+
"script already matches the declared source \u2014 no version pushed"
|
|
16891
|
+
);
|
|
16892
|
+
}
|
|
16693
16893
|
continue;
|
|
16694
16894
|
}
|
|
16695
|
-
|
|
16895
|
+
let { adopting } = assertScriptPushOwnable(name, latest, blueprintName, allowScriptAdoption);
|
|
16896
|
+
const body = {
|
|
16897
|
+
name,
|
|
16898
|
+
source: entry.source,
|
|
16899
|
+
declaredInputContract: entry.declaredInputContract,
|
|
16900
|
+
provisionedBy: blueprintName
|
|
16901
|
+
};
|
|
16696
16902
|
let pushed;
|
|
16697
16903
|
try {
|
|
16698
16904
|
pushed = await ctxClient.scripts.createScript(body);
|
|
16699
16905
|
} catch (err) {
|
|
16700
|
-
if (!isVersionConflict(err)) throw err;
|
|
16906
|
+
if (!isVersionConflict(err)) throw adopting ? withAdoptionContext(err, name) : err;
|
|
16701
16907
|
log.debug(
|
|
16702
16908
|
{ component: "loader", scriptName: name },
|
|
16703
16909
|
"concurrent push detected (409) \u2014 pausing, then re-reading the current version"
|
|
@@ -16723,10 +16929,11 @@ async function ensureScripts(args) {
|
|
|
16723
16929
|
);
|
|
16724
16930
|
continue;
|
|
16725
16931
|
}
|
|
16932
|
+
({ adopting } = assertScriptPushOwnable(name, latest, blueprintName, allowScriptAdoption));
|
|
16726
16933
|
try {
|
|
16727
16934
|
pushed = await ctxClient.scripts.createScript(body);
|
|
16728
16935
|
} catch (retryErr) {
|
|
16729
|
-
if (!isVersionConflict(retryErr)) throw retryErr;
|
|
16936
|
+
if (!isVersionConflict(retryErr)) throw adopting ? withAdoptionContext(retryErr, name) : retryErr;
|
|
16730
16937
|
throw new Error(
|
|
16731
16938
|
`Script '${name}': still conflicting (409) after a re-read and retry. Another apply is pushing this script name concurrently \u2014 re-run once it has finished.`
|
|
16732
16939
|
);
|
|
@@ -16738,13 +16945,13 @@ async function ensureScripts(args) {
|
|
|
16738
16945
|
const firstVersion = latest === void 0;
|
|
16739
16946
|
actions.push({
|
|
16740
16947
|
name,
|
|
16741
|
-
action: firstVersion ? "created" : "superseded",
|
|
16948
|
+
action: firstVersion ? "created" : adopting ? "adopted" : "superseded",
|
|
16742
16949
|
id: pushed.id,
|
|
16743
16950
|
scriptVersion: pushed.scriptVersion ?? 0
|
|
16744
16951
|
});
|
|
16745
16952
|
log.debug(
|
|
16746
16953
|
{ component: "loader", scriptName: name, scriptVersion: pushed.scriptVersion },
|
|
16747
|
-
firstVersion ? "script pushed \u2014 first version for this name in this context" : "script source changed \u2014 new version pushed over an existing name"
|
|
16954
|
+
firstVersion ? "script pushed \u2014 first version for this name in this context" : adopting ? "script pushed \u2014 adopted a previously-unmarked name into this blueprint" : "script source changed \u2014 new version pushed over an existing name"
|
|
16748
16955
|
);
|
|
16749
16956
|
}
|
|
16750
16957
|
return actions;
|
|
@@ -16915,9 +17122,10 @@ function describeNamespaceMismatch(decl, existing) {
|
|
|
16915
17122
|
return mismatches.length > 0 ? mismatches.join("; ") : null;
|
|
16916
17123
|
}
|
|
16917
17124
|
var LostKeyError = class extends Error {
|
|
16918
|
-
constructor(keyName) {
|
|
17125
|
+
constructor(keyName, rotateAttempted = false, revokedKeyId) {
|
|
17126
|
+
const revokedDisplay = revokedKeyId?.map((id) => `'${id}'`).join(", ");
|
|
16919
17127
|
super(
|
|
16920
|
-
`Scoped key '${keyName}' already exists for this machine but its local copy (~/.vectros) is missing. The raw key is shown only once and cannot be re-read. Re-run with --rotate to revoke + re-mint this machine's key.`
|
|
17128
|
+
`Scoped key '${keyName}' already exists for this machine but its local copy (~/.vectros) is missing. The raw key is shown only once and cannot be re-read. ` + (revokedKeyId && revokedKeyId.length > 0 ? `--rotate just revoked ${revokedKeyId.length === 1 ? "key" : "keys"} ${revokedDisplay}, but this call still sees the scoped key as already existing \u2014 most likely a brief propagation delay right after the revoke, not a failed rotate. Wait a few seconds and re-run, or run \`vectros key list --context <contextId>\` to confirm ${revokedKeyId.length === 1 ? "it is" : "they are"} actually gone before retrying.` : rotateAttempted ? `--rotate ran and found no matching active key to revoke for this context/principal \u2014 either it was already revoked elsewhere or something else holds that name. Run \`vectros key list --context <contextId>\` to find the key actually blocking this, then \`vectros key revoke <id>\` it directly before re-running.` : `Re-run with --rotate to revoke + re-mint this machine's key.`)
|
|
16921
17129
|
);
|
|
16922
17130
|
this.keyName = keyName;
|
|
16923
17131
|
this.name = "LostKeyError";
|
|
@@ -17125,6 +17333,7 @@ async function runLoader(client, input) {
|
|
|
17125
17333
|
provisioned,
|
|
17126
17334
|
readExistingKey,
|
|
17127
17335
|
persistMintedKey,
|
|
17336
|
+
allowScriptAdoption = false,
|
|
17128
17337
|
log
|
|
17129
17338
|
} = input;
|
|
17130
17339
|
assertNoUnresolvedIdentities(blueprint);
|
|
@@ -17275,7 +17484,13 @@ async function runLoader(client, input) {
|
|
|
17275
17484
|
body: profileBody
|
|
17276
17485
|
});
|
|
17277
17486
|
log.debug({ component: "loader", principalId: `usr_${userId}`, accessProfileId: profile.id, profileAction }, "access profile ensured + reconciled to declared scopes");
|
|
17278
|
-
const scriptActions = blueprint.scripts ? await ensureScripts({
|
|
17487
|
+
const scriptActions = blueprint.scripts ? await ensureScripts({
|
|
17488
|
+
ctxClient,
|
|
17489
|
+
scripts: blueprint.scripts,
|
|
17490
|
+
blueprintName: blueprint.name,
|
|
17491
|
+
allowScriptAdoption,
|
|
17492
|
+
log
|
|
17493
|
+
}) : [];
|
|
17279
17494
|
const triggerActions = [];
|
|
17280
17495
|
for (const [triggerName, entry] of Object.entries(blueprint.triggers ?? {})) {
|
|
17281
17496
|
const schemaId = schemaIds[entry.firingSource.schemaName];
|
|
@@ -17386,9 +17601,45 @@ async function runLoader(client, input) {
|
|
|
17386
17601
|
if (existing?.keyId) {
|
|
17387
17602
|
await ctxClient.auth.revokeScopedKey({ keyId: existing.keyId });
|
|
17388
17603
|
log.debug({ component: "loader", keyId: existing.keyId }, "revoked prior key for rotate");
|
|
17389
|
-
revokedKeyId = existing.keyId;
|
|
17604
|
+
revokedKeyId = [existing.keyId];
|
|
17605
|
+
} else {
|
|
17606
|
+
let activeKeys;
|
|
17607
|
+
try {
|
|
17608
|
+
activeKeys = await drainPages(() => ctxClient.auth.listScopedKeys());
|
|
17609
|
+
} catch (err) {
|
|
17610
|
+
throw new Error(
|
|
17611
|
+
`--rotate could not check for a server-side key to revoke, and no local record of one exists on this machine: ${err instanceof Error ? err.message : String(err)}. Run \`vectros key list --context ${blueprint.contextId}\` and \`vectros key revoke <id>\` manually to find and remove the key blocking this, then re-run without --rotate.`,
|
|
17612
|
+
{ cause: err }
|
|
17613
|
+
);
|
|
17614
|
+
}
|
|
17615
|
+
const matches = activeKeys.filter(
|
|
17616
|
+
(k) => k.contextId === blueprint.contextId && k.userId === userId && k.keyName === keyName && k.status === "active" && k.keyId
|
|
17617
|
+
);
|
|
17618
|
+
if (matches.length > 1) {
|
|
17619
|
+
log.warn(
|
|
17620
|
+
{ component: "loader", keyName, contextId: blueprint.contextId, count: matches.length },
|
|
17621
|
+
"rotate: more than one active key matched this context/user/name tuple server-side \u2014 revoking all of them"
|
|
17622
|
+
);
|
|
17623
|
+
}
|
|
17624
|
+
if (matches.length > 0) {
|
|
17625
|
+
const revokedIds = [];
|
|
17626
|
+
for (const match of matches) {
|
|
17627
|
+
await ctxClient.auth.revokeScopedKey({ keyId: match.keyId });
|
|
17628
|
+
revokedIds.push(match.keyId);
|
|
17629
|
+
}
|
|
17630
|
+
log.debug(
|
|
17631
|
+
{ component: "loader", keyIds: revokedIds },
|
|
17632
|
+
"revoked prior key(s) for rotate \u2014 resolved server-side, no local record existed"
|
|
17633
|
+
);
|
|
17634
|
+
revokedKeyId = revokedIds;
|
|
17635
|
+
} else {
|
|
17636
|
+
log.warn(
|
|
17637
|
+
{ component: "loader", keyName, contextId: blueprint.contextId },
|
|
17638
|
+
"rotate: no local record and no matching active key found server-side \u2014 nothing to revoke"
|
|
17639
|
+
);
|
|
17640
|
+
}
|
|
17390
17641
|
}
|
|
17391
|
-
keyAction = "rotated";
|
|
17642
|
+
if (revokedKeyId) keyAction = "rotated";
|
|
17392
17643
|
}
|
|
17393
17644
|
const keyResp = await ctxClient.auth.createScopedKey({ keyName, tenantId, contextId: blueprint.contextId, userId });
|
|
17394
17645
|
let rawKey;
|
|
@@ -17403,7 +17654,7 @@ async function runLoader(client, input) {
|
|
|
17403
17654
|
} else {
|
|
17404
17655
|
const existing = await readExistingKey(blueprint.contextId);
|
|
17405
17656
|
if (!existing) {
|
|
17406
|
-
throw new LostKeyError(keyName);
|
|
17657
|
+
throw new LostKeyError(keyName, rotate, revokedKeyId);
|
|
17407
17658
|
}
|
|
17408
17659
|
rawKey = existing.rawKey;
|
|
17409
17660
|
keyId = existing.keyId;
|
|
@@ -17516,6 +17767,10 @@ async function runLoader(client, input) {
|
|
|
17516
17767
|
seedIndexFailures
|
|
17517
17768
|
};
|
|
17518
17769
|
}
|
|
17770
|
+
function sanitizeForPlanDisplay(value, maxLen = 200) {
|
|
17771
|
+
const stripped = value.replace(/[\x00-\x1f\x7f]/g, "");
|
|
17772
|
+
return stripped.length > maxLen ? `${stripped.slice(0, maxLen)}\u2026` : stripped;
|
|
17773
|
+
}
|
|
17519
17774
|
function renderPlanDataScope(ds) {
|
|
17520
17775
|
const entries = Object.entries(ds ?? {});
|
|
17521
17776
|
if (entries.length === 0) return "(none \u2014 unconstrained)";
|
|
@@ -17542,11 +17797,14 @@ function buildPlan(blueprint, opts = {}) {
|
|
|
17542
17797
|
});
|
|
17543
17798
|
}
|
|
17544
17799
|
for (const iss of blueprint.issuers ?? []) {
|
|
17545
|
-
const
|
|
17546
|
-
const
|
|
17800
|
+
const issuerUrl = sanitizeForPlanDisplay(iss.issuer);
|
|
17801
|
+
const jwksNote = `, jwksUri: ${sanitizeForPlanDisplay(iss.jwksUri)}`;
|
|
17802
|
+
const userinfoNote = iss.userinfoUri ? `, userinfoUri: ${sanitizeForPlanDisplay(iss.userinfoUri)}` : "";
|
|
17803
|
+
const selfSignup = iss.selfSignupPolicies?.length ? `, self-signup: [${iss.selfSignupPolicies.map((p) => `${p.signupType}\u2192${p.roleId}`).join(", ")}]` : "";
|
|
17804
|
+
const captured = iss.capturedClaims?.length ? `, captured claims: [${iss.capturedClaims.map((c) => sanitizeForPlanDisplay(c)).join(", ")}]` : "";
|
|
17547
17805
|
steps.push({
|
|
17548
17806
|
kind: "issuer",
|
|
17549
|
-
summary: `issuer '${iss.issuerId}' (${
|
|
17807
|
+
summary: `issuer '${iss.issuerId}' (${issuerUrl} \u2192 context '${iss.contextId}'${jwksNote}${userinfoNote}${selfSignup}${captured}) [${BLUEPRINT_FIELD_PHASES.issuers}-token phase]`
|
|
17550
17808
|
});
|
|
17551
17809
|
}
|
|
17552
17810
|
for (const s of blueprint.schemas) {
|
|
@@ -17667,9 +17925,11 @@ function renderPlan(blueprint, steps) {
|
|
|
17667
17925
|
` immutable version and every earlier one persists, which is also how a 'latest' rule`,
|
|
17668
17926
|
` picks the change up. And scripts are NOT reconciled by absence: a script this blueprint`,
|
|
17669
17927
|
` previously shipped and no longer declares is LEFT IN PLACE, including every stored`,
|
|
17670
|
-
` version.
|
|
17671
|
-
`
|
|
17672
|
-
`
|
|
17928
|
+
` version. Each version does carry this blueprint's own provenance marker, and a push`,
|
|
17929
|
+
` ONTO an already-marked-or-existing name is gated on it (see 'script' steps above) \u2014 but`,
|
|
17930
|
+
` that gate has no bearing on DELETION: there is no second, principal-shaped factor to`,
|
|
17931
|
+
` pair the marker with the way trigger rows have one, so a wrong delete stays unrecoverable`,
|
|
17932
|
+
` and would break any trigger still referencing it. Remove one deliberately with`,
|
|
17673
17933
|
` 'vectros scripts delete', after 'vectros scripts list' shows you what is there.`
|
|
17674
17934
|
);
|
|
17675
17935
|
if (steps.some((s) => s.kind === "issuer")) {
|
|
@@ -17796,19 +18056,37 @@ var import_proper_lockfile = __toESM(require_proper_lockfile());
|
|
|
17796
18056
|
// src/os-keychain.ts
|
|
17797
18057
|
var KEYCHAIN_SERVICE = "vectros-cli";
|
|
17798
18058
|
var PROBE_ACCOUNT = "__vectros_probe__";
|
|
17799
|
-
async function loadKeychainBinding() {
|
|
18059
|
+
async function loadKeychainBinding(tryLoad = () => import("@napi-rs/keyring")) {
|
|
17800
18060
|
try {
|
|
17801
|
-
const mod = await
|
|
18061
|
+
const mod = await tryLoad();
|
|
17802
18062
|
if (typeof mod?.Entry === "function") return { binding: mod };
|
|
17803
|
-
return {
|
|
18063
|
+
return {
|
|
18064
|
+
reason: "the @napi-rs/keyring package loaded but exposes no Entry \u2014 expected on no platform",
|
|
18065
|
+
installAccident: true
|
|
18066
|
+
};
|
|
17804
18067
|
} catch (err) {
|
|
17805
|
-
return { reason: loadFailureReason(err) };
|
|
18068
|
+
return { reason: loadFailureReason(err), installAccident: isInstallAccident(err) };
|
|
17806
18069
|
}
|
|
17807
18070
|
}
|
|
17808
|
-
|
|
18071
|
+
var PREBUILT_PLATFORMS = {
|
|
18072
|
+
darwin: ["arm64", "x64"],
|
|
18073
|
+
freebsd: ["x64"],
|
|
18074
|
+
linux: ["arm", "arm64", "riscv64", "x64"],
|
|
18075
|
+
win32: ["arm64", "ia32", "x64"]
|
|
18076
|
+
};
|
|
18077
|
+
function prebuiltShouldExist(platform = process.platform, arch = process.arch) {
|
|
18078
|
+
return PREBUILT_PLATFORMS[platform]?.includes(arch) ?? false;
|
|
18079
|
+
}
|
|
18080
|
+
function isInstallAccident(_err, platform = process.platform, arch = process.arch) {
|
|
18081
|
+
return prebuiltShouldExist(platform, arch);
|
|
18082
|
+
}
|
|
18083
|
+
function loadFailureReason(err, platform = process.platform, arch = process.arch) {
|
|
18084
|
+
if (!prebuiltShouldExist(platform, arch)) {
|
|
18085
|
+
return "the optional @napi-rs/keyring package is not installed for this platform";
|
|
18086
|
+
}
|
|
17809
18087
|
const code = err.code;
|
|
17810
18088
|
if (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") {
|
|
17811
|
-
return
|
|
18089
|
+
return `the optional @napi-rs/keyring package is missing even though a prebuilt exists for this platform (${platform}/${arch}) \u2014 most likely skipped during install`;
|
|
17812
18090
|
}
|
|
17813
18091
|
return `the optional @napi-rs/keyring package is installed but would not load (${describeErr(err)})`;
|
|
17814
18092
|
}
|
|
@@ -17932,8 +18210,8 @@ var OsKeychainSecretStore = class {
|
|
|
17932
18210
|
* deleteCredential() -> false
|
|
17933
18211
|
* => deleteSecret() -> false. The gate holds; `remove` refuses.
|
|
17934
18212
|
*
|
|
17935
|
-
* Reproduce
|
|
17936
|
-
*
|
|
18213
|
+
* Reproduce by locking the credential store's collection while the CLI still holds a
|
|
18214
|
+
* cached handle to it. CI cannot pin this half — it unlocks the collection so the rest of
|
|
17937
18215
|
* the suite can use it — so the evidence lives here rather than in a test.
|
|
17938
18216
|
*
|
|
17939
18217
|
* The WINDOWS half of the same contract was assumed for seven review rounds and
|
|
@@ -18375,7 +18653,11 @@ var KEYRING_BACKENDS = ["auto", "file"];
|
|
|
18375
18653
|
async function resolveSecretStore(deps = {}) {
|
|
18376
18654
|
const platform = deps.platform ?? process.platform;
|
|
18377
18655
|
const env = deps.env ?? process.env;
|
|
18378
|
-
const file = (reason2) => ({
|
|
18656
|
+
const file = (reason2, installAccident2) => ({
|
|
18657
|
+
kind: "crypto-file",
|
|
18658
|
+
reason: reason2,
|
|
18659
|
+
...installAccident2 ? { installAccident: installAccident2 } : {}
|
|
18660
|
+
});
|
|
18379
18661
|
const requested = env[KEYRING_BACKEND_ENV]?.trim().toLowerCase();
|
|
18380
18662
|
if (requested !== void 0 && requested !== "") {
|
|
18381
18663
|
if (!KEYRING_BACKENDS.includes(requested)) {
|
|
@@ -18392,9 +18674,9 @@ async function resolveSecretStore(deps = {}) {
|
|
|
18392
18674
|
return file("the macOS Keychain can prompt on unattended reads, which would break MCP startup");
|
|
18393
18675
|
}
|
|
18394
18676
|
const loadBinding0 = deps.loadBinding ?? loadKeychainBinding;
|
|
18395
|
-
const { binding, reason } = await loadBinding0();
|
|
18677
|
+
const { binding, reason, installAccident } = await loadBinding0();
|
|
18396
18678
|
if (!binding) {
|
|
18397
|
-
return file(reason ?? "the optional @napi-rs/keyring package is not usable on this platform");
|
|
18679
|
+
return file(reason ?? "the optional @napi-rs/keyring package is not usable on this platform", installAccident);
|
|
18398
18680
|
}
|
|
18399
18681
|
const probe = deps.probe ?? probeKeychain;
|
|
18400
18682
|
if (!probe(binding)) {
|
|
@@ -18423,6 +18705,9 @@ var Keyring = class _Keyring {
|
|
|
18423
18705
|
stores;
|
|
18424
18706
|
/** Where NEW secrets go, and why — surfaced by `keyring doctor`. */
|
|
18425
18707
|
backend;
|
|
18708
|
+
warn;
|
|
18709
|
+
/** Fires at most once per instance — see {@link storeSecretSomewhere}. */
|
|
18710
|
+
installAccidentWarned = false;
|
|
18426
18711
|
/**
|
|
18427
18712
|
* Construct against EXPLICIT stores. The file store defaults to a
|
|
18428
18713
|
* {@link CryptoFileSecretStore} over `dir` — no native binding, nothing outside
|
|
@@ -18437,6 +18722,8 @@ var Keyring = class _Keyring {
|
|
|
18437
18722
|
"os-keychain": deps.keychainStore
|
|
18438
18723
|
};
|
|
18439
18724
|
this.backend = deps.selection ?? { kind: "crypto-file" };
|
|
18725
|
+
this.warn = deps.warn ?? ((msg) => process.stderr.write(`${msg}
|
|
18726
|
+
`));
|
|
18440
18727
|
}
|
|
18441
18728
|
/**
|
|
18442
18729
|
* The keyring a command should use: the stores resolved for THIS machine per
|
|
@@ -18447,7 +18734,7 @@ var Keyring = class _Keyring {
|
|
|
18447
18734
|
static async open(deps = {}) {
|
|
18448
18735
|
if (deps.fileStore ?? deps.keychainStore ?? deps.selection) return new _Keyring(deps);
|
|
18449
18736
|
const { keychain, ...selection } = await resolveSecretStore(deps);
|
|
18450
|
-
return new _Keyring({ dir: deps.dir, keychainStore: keychain, selection });
|
|
18737
|
+
return new _Keyring({ dir: deps.dir, keychainStore: keychain, selection, warn: deps.warn });
|
|
18451
18738
|
}
|
|
18452
18739
|
/**
|
|
18453
18740
|
* The store holding this entry's secret, or undefined when the stamp does not say
|
|
@@ -18564,6 +18851,28 @@ The key itself WAS created server-side and is live \u2014 its id is ${entry.keyI
|
|
|
18564
18851
|
}
|
|
18565
18852
|
return { landedIn, stranded: swept ? void 0 : prior, vetoedBy };
|
|
18566
18853
|
}
|
|
18854
|
+
/**
|
|
18855
|
+
* Warn, at most once per instance, when this machine is about to write a secret to
|
|
18856
|
+
* the file store because of an INSTALL ACCIDENT — not because of an ordinary,
|
|
18857
|
+
* nothing-to-fix absence (unsupported platform, `VECTROS_KEYRING_BACKEND=file`,
|
|
18858
|
+
* macOS's own design choice, or "loaded fine but no OS credential store answers").
|
|
18859
|
+
*
|
|
18860
|
+
* Before this, the CLI degraded to the file store with ZERO signal at write
|
|
18861
|
+
* time, indistinguishable from every OTHER reason a machine ends up there — a
|
|
18862
|
+
* real `@napi-rs/keyring` install failure looked identical to a platform that
|
|
18863
|
+
* was never going to have one, unless you separately ran `keyring doctor`.
|
|
18864
|
+
* This is the signal that was missing, at the moment it matters (a real
|
|
18865
|
+
* secret is about to land somewhere weaker than intended), naming the one
|
|
18866
|
+
* remedy that actually applies to THIS case and no other.
|
|
18867
|
+
*/
|
|
18868
|
+
warnOnceIfInstallAccident() {
|
|
18869
|
+
if (this.installAccidentWarned) return;
|
|
18870
|
+
if (this.backend.kind !== "crypto-file" || !this.backend.installAccident) return;
|
|
18871
|
+
this.installAccidentWarned = true;
|
|
18872
|
+
this.warn(
|
|
18873
|
+
`WARNING: storing this credential in the encrypted file store, not your OS credential store \u2014 ${this.backend.reason ?? "the optional @napi-rs/keyring package failed to load"}. This may be an install accident rather than an unsupported platform: try reinstalling (npm install -g @vectros-ai/cli, or add @napi-rs/keyring as a direct dependency alongside it) to restore OS-credential-store protection. Run \`vectros keyring doctor\` for the full picture.`
|
|
18874
|
+
);
|
|
18875
|
+
}
|
|
18567
18876
|
/**
|
|
18568
18877
|
* Persist a secret, preferring this machine's chosen store but NEVER losing the
|
|
18569
18878
|
* secret if that store refuses. Returns where it actually landed.
|
|
@@ -18576,8 +18885,15 @@ The key itself WAS created server-side and is live \u2014 its id is ${entry.keyI
|
|
|
18576
18885
|
* credential and leave it orphaned server-side with no local record of its
|
|
18577
18886
|
* `keyId`. Degrading to the encrypted file is strictly better than that, and the
|
|
18578
18887
|
* entry's stamp keeps the result honest rather than silently wrong.
|
|
18888
|
+
*
|
|
18889
|
+
* `warnIfDegraded` defaults to true and exists ONLY for {@link probeSecretStore}
|
|
18890
|
+
* to suppress with `false`: the once-per-instance install-accident warning is a
|
|
18891
|
+
* scarce resource (fires at most once per `Keyring`), and the probe's marker
|
|
18892
|
+
* value is never the credential the warning describes. Spending it on the probe
|
|
18893
|
+
* left the REAL write — the very next call, same instance — silent.
|
|
18579
18894
|
*/
|
|
18580
|
-
async storeSecretSomewhere(ref, secret) {
|
|
18895
|
+
async storeSecretSomewhere(ref, secret, { warnIfDegraded = true } = {}) {
|
|
18896
|
+
if (warnIfDegraded) this.warnOnceIfInstallAccident();
|
|
18581
18897
|
const preferred = this.preferred;
|
|
18582
18898
|
if (preferred) {
|
|
18583
18899
|
try {
|
|
@@ -18629,7 +18945,9 @@ The key itself WAS created server-side and is live \u2014 its id is ${entry.keyI
|
|
|
18629
18945
|
async probeSecretStore() {
|
|
18630
18946
|
const fileStore = this.stores["crypto-file"];
|
|
18631
18947
|
const dirWasPresent = fileStore instanceof CryptoFileSecretStore ? await fileStore.secretsDirPresent() : true;
|
|
18632
|
-
const landedIn = await this.storeSecretSomewhere(PROBE_SECRET_REF, PROBE_SECRET_VALUE
|
|
18948
|
+
const landedIn = await this.storeSecretSomewhere(PROBE_SECRET_REF, PROBE_SECRET_VALUE, {
|
|
18949
|
+
warnIfDegraded: false
|
|
18950
|
+
});
|
|
18633
18951
|
await this.stores[landedIn]?.deleteSecret(PROBE_SECRET_REF).catch(() => {
|
|
18634
18952
|
});
|
|
18635
18953
|
if (!dirWasPresent && fileStore instanceof CryptoFileSecretStore) {
|
|
@@ -18930,7 +19248,12 @@ The key itself WAS created server-side and is live \u2014 its id is ${entry.keyI
|
|
|
18930
19248
|
async readSecret(entry) {
|
|
18931
19249
|
const from = storeKindOf(entry);
|
|
18932
19250
|
if (from === void 0) return void 0;
|
|
18933
|
-
const
|
|
19251
|
+
const store = this.stores[from];
|
|
19252
|
+
if (!store) {
|
|
19253
|
+
this.warnOnceIfInstallAccident();
|
|
19254
|
+
return void 0;
|
|
19255
|
+
}
|
|
19256
|
+
const secret = await store.getSecret(entry.secretRef);
|
|
18934
19257
|
if (secret === void 0) return void 0;
|
|
18935
19258
|
if (from !== this.backend.kind) await this.migrateSecret(entry, from, secret);
|
|
18936
19259
|
return secret;
|
|
@@ -19651,7 +19974,7 @@ function nowIso() {
|
|
|
19651
19974
|
}
|
|
19652
19975
|
function warnReplacingAlias(existing, alias, revokedKeyId) {
|
|
19653
19976
|
if (!existing) return;
|
|
19654
|
-
const stillLive = existing.keyId !== void 0 && existing.keyId
|
|
19977
|
+
const stillLive = existing.keyId !== void 0 && !(revokedKeyId ?? []).includes(existing.keyId);
|
|
19655
19978
|
process.stdout.write(
|
|
19656
19979
|
`
|
|
19657
19980
|
\u26A0 Replacing keyring entry '${alias}' (was ${existing.principalId || "<unknown principal>"} in '${existing.context}' @ ${existing.env}, key ${existing.keyId}). Pass --alias to keep both.
|
|
@@ -19662,13 +19985,14 @@ function warnReplacingAlias(existing, alias, revokedKeyId) {
|
|
|
19662
19985
|
}
|
|
19663
19986
|
async function warnOtherAliasesNaming(keyring, revokedKeyId, exceptAlias) {
|
|
19664
19987
|
const affected = (await keyring.list()).filter(
|
|
19665
|
-
(e) => e.alias !== exceptAlias && e.keyId
|
|
19988
|
+
(e) => e.alias !== exceptAlias && e.keyId !== void 0 && revokedKeyId.includes(e.keyId)
|
|
19666
19989
|
);
|
|
19667
19990
|
if (affected.length === 0) return;
|
|
19668
19991
|
const names = affected.map((e) => `'${e.alias}'`).join(", ");
|
|
19992
|
+
const revokedDisplay = revokedKeyId.length === 1 ? `Key ${revokedKeyId[0]}` : `Keys ${revokedKeyId.join(", ")}`;
|
|
19669
19993
|
process.stderr.write(
|
|
19670
19994
|
`
|
|
19671
|
-
\u26A0
|
|
19995
|
+
\u26A0 ${revokedDisplay} ${revokedKeyId.length === 1 ? "was" : "were"} just revoked, but ${affected.length === 1 ? "entry" : "entries"} ${names} still
|
|
19672
19996
|
${affected.length === 1 ? "names" : "name"} it. That secret is now dead \u2014 it keeps reading fine locally and
|
|
19673
19997
|
will simply stop authenticating. Re-point ${affected.length === 1 ? "it" : "them"} with
|
|
19674
19998
|
\`vectros key rotate --to-keyring --alias <name>\`, or drop with \`vectros keyring remove <name>\`.
|
|
@@ -19698,7 +20022,7 @@ async function storeMintedKey(keyring, binding, secret, opts = {}) {
|
|
|
19698
20022
|
was kept too` : `the ${noun} did not
|
|
19699
20023
|
confirm the delete`;
|
|
19700
20024
|
const tidyUp = vetoNoun !== void 0 ? `Clear the copies by hand from your ${vetoNoun} and your ${noun}` : `Clear it by hand from your ${noun}`;
|
|
19701
|
-
if (opts.revokedKeyId
|
|
20025
|
+
if (opts.revokedKeyId?.includes(stranded.keyId)) {
|
|
19702
20026
|
process.stderr.write(
|
|
19703
20027
|
`
|
|
19704
20028
|
\u26A0 The previous key for '${entry.alias}' (${stranded.keyId}) was revoked and replaced, but
|
|
@@ -19769,6 +20093,7 @@ function parseBootstrapArgs(argv) {
|
|
|
19769
20093
|
blankSlate: false,
|
|
19770
20094
|
noSeed: false,
|
|
19771
20095
|
allowTenantWideNamespaces: false,
|
|
20096
|
+
allowScriptAdoption: false,
|
|
19772
20097
|
client: "desktop",
|
|
19773
20098
|
tenant: "live",
|
|
19774
20099
|
help: false
|
|
@@ -19833,6 +20158,9 @@ function parseBootstrapArgs(argv) {
|
|
|
19833
20158
|
case "--allow-tenant-wide-namespaces":
|
|
19834
20159
|
args.allowTenantWideNamespaces = true;
|
|
19835
20160
|
break;
|
|
20161
|
+
case "--allow-script-adoption":
|
|
20162
|
+
args.allowScriptAdoption = true;
|
|
20163
|
+
break;
|
|
19836
20164
|
case "--tenant": {
|
|
19837
20165
|
const v = argv[++i];
|
|
19838
20166
|
if (v !== "test" && v !== "live") {
|
|
@@ -19861,7 +20189,7 @@ function parseBootstrapArgs(argv) {
|
|
|
19861
20189
|
return args;
|
|
19862
20190
|
}
|
|
19863
20191
|
function shouldOfferSeedOptOut(args, blueprint, isTty) {
|
|
19864
|
-
return !args.noSeed && (blueprint.seed?.length ?? 0) > 0 && !args.yes &&
|
|
20192
|
+
return !args.noSeed && (blueprint.seed?.length ?? 0) > 0 && !args.yes && isTty;
|
|
19865
20193
|
}
|
|
19866
20194
|
function seedDeclined(answer) {
|
|
19867
20195
|
const a = answer.trim().toLowerCase();
|
|
@@ -19927,12 +20255,16 @@ function scriptTally(items) {
|
|
|
19927
20255
|
if (items.length === 0) return null;
|
|
19928
20256
|
const created = items.filter((i) => i.action === "created");
|
|
19929
20257
|
const superseded = items.filter((i) => i.action === "superseded");
|
|
19930
|
-
const
|
|
20258
|
+
const adopted = items.filter((i) => i.action === "adopted");
|
|
20259
|
+
const unchanged = items.length - created.length - superseded.length - adopted.length;
|
|
19931
20260
|
const seg = [];
|
|
19932
20261
|
if (created.length) seg.push(`${created.length} first version${created.length === 1 ? "" : "s"} pushed`);
|
|
19933
20262
|
if (superseded.length) {
|
|
19934
20263
|
seg.push(`${superseded.map((i) => `'${i.name}'`).join(", ")} superseded (a new version now current)`);
|
|
19935
20264
|
}
|
|
20265
|
+
if (adopted.length) {
|
|
20266
|
+
seg.push(`${adopted.map((i) => `'${i.name}'`).join(", ")} adopted (previously unmarked, now managed by this blueprint)`);
|
|
20267
|
+
}
|
|
19936
20268
|
if (unchanged) seg.push(`${unchanged} already current \u2014 nothing written`);
|
|
19937
20269
|
return `${items.length} script${items.length === 1 ? "" : "s"} (${seg.join("; ")})`;
|
|
19938
20270
|
}
|
|
@@ -20004,6 +20336,14 @@ Options:
|
|
|
20004
20336
|
credential can actually complete the registration either way \u2014
|
|
20005
20337
|
this flag doesn't change who's allowed, only whether this run
|
|
20006
20338
|
tries. Never needed for a blueprint with no tenantWide namespace.
|
|
20339
|
+
--allow-script-adoption
|
|
20340
|
+
Required to push a script version onto a name that already exists in
|
|
20341
|
+
this context but carries no blueprint provenance marker (hand-authored,
|
|
20342
|
+
or pushed before this marker existed) \u2014 without it, bootstrap refuses
|
|
20343
|
+
before minting anything for that name, naming it. Never needed for a
|
|
20344
|
+
brand-new script name, or for re-applying a blueprint that already owns
|
|
20345
|
+
the name it's pushing to. Does NOT help if the name is already owned by
|
|
20346
|
+
a DIFFERENT blueprint \u2014 that is refused unconditionally, no flag past it.
|
|
20007
20347
|
--tenant test|live Which tenant the minted key scopes to (default: live).
|
|
20008
20348
|
'test' provisions into your isolated test tenant \u2014 a
|
|
20009
20349
|
dry-run before pointing real data at the same blueprint.
|
|
@@ -20026,7 +20366,8 @@ Options:
|
|
|
20026
20366
|
--yes, -y Skip confirmations (agent / CI path).
|
|
20027
20367
|
--no-merge Still mints/reconciles \u2014 this only changes where the result goes:
|
|
20028
20368
|
print the config snippet instead of merging it into your client's
|
|
20029
|
-
config file. Not a dry run; see 'blueprint plan' for that.
|
|
20369
|
+
config file. Not a dry run; see 'blueprint plan' for that. Does NOT
|
|
20370
|
+
skip the 'Apply this plan?' confirmation \u2014 pass --yes for that.
|
|
20030
20371
|
--rotate Revoke + re-mint THIS machine's key.
|
|
20031
20372
|
--confirm-existing-principal
|
|
20032
20373
|
Required (with --yes, or with no TTY) to mint a NEW key bound to a
|
|
@@ -20226,7 +20567,7 @@ ${renderPlan(blueprint, planSteps)}
|
|
|
20226
20567
|
`);
|
|
20227
20568
|
}
|
|
20228
20569
|
}
|
|
20229
|
-
if (!args.yes &&
|
|
20570
|
+
if (!args.yes && isTty) {
|
|
20230
20571
|
const ans = (await promptCtx.prompt("\n Apply this plan? [y/N]: ")).trim().toLowerCase();
|
|
20231
20572
|
if (ans !== "y" && ans !== "yes") {
|
|
20232
20573
|
process.stdout.write(" Aborted \u2014 nothing minted.\n");
|
|
@@ -20360,6 +20701,7 @@ ${renderPlan(blueprint, planSteps)}
|
|
|
20360
20701
|
machineId,
|
|
20361
20702
|
rotate: args.rotate,
|
|
20362
20703
|
skipSeed: noSeed,
|
|
20704
|
+
allowScriptAdoption: args.allowScriptAdoption,
|
|
20363
20705
|
readExistingKey,
|
|
20364
20706
|
persistMintedKey,
|
|
20365
20707
|
provisioned,
|
|
@@ -21573,7 +21915,7 @@ ${list}
|
|
|
21573
21915
|
return storeAndReport(dest, key, auth, {
|
|
21574
21916
|
...opts,
|
|
21575
21917
|
principalId: `usr_${userId}`,
|
|
21576
|
-
revokedKeyId: existing?.keyId
|
|
21918
|
+
revokedKeyId: existing?.keyId ? [existing.keyId] : void 0
|
|
21577
21919
|
});
|
|
21578
21920
|
emitMintedKey(key, opts.format);
|
|
21579
21921
|
return 0;
|
|
@@ -23245,6 +23587,7 @@ async function runScriptsList(opts, deps = {}) {
|
|
|
23245
23587
|
const rows = await drainPages(
|
|
23246
23588
|
(startFrom) => client.scripts.listScripts({
|
|
23247
23589
|
limit: PAGE_LIMIT,
|
|
23590
|
+
includeSource: true,
|
|
23248
23591
|
...opts.name ? { name: opts.name } : {},
|
|
23249
23592
|
...startFrom ? { startFrom } : {}
|
|
23250
23593
|
})
|
|
@@ -23305,10 +23648,9 @@ async function runScriptsDelete(opts, deps = {}) {
|
|
|
23305
23648
|
`
|
|
23306
23649
|
\u26A0 Deleting script version '${opts.id}' is permanent \u2014 the stored source is the only copy.
|
|
23307
23650
|
Other versions of the same name are unaffected.
|
|
23308
|
-
|
|
23309
|
-
|
|
23310
|
-
|
|
23311
|
-
against the API or the portal before removing a version a rule may still use.
|
|
23651
|
+
The platform refuses this delete if a trigger rule still references this version \u2014
|
|
23652
|
+
directly, or via 'latest' if this is the name's current newest version \u2014 and names the
|
|
23653
|
+
offending rule instead of deleting. If no rule references it, the delete proceeds.
|
|
23312
23654
|
And if this is the name's LATEST version, its identity is REUSED: the next push of that
|
|
23313
23655
|
name recomputes the same version number and the same id, so anything holding a pinned
|
|
23314
23656
|
'${opts.id}' or a name@version reference will silently resolve to different source.
|
|
@@ -23477,7 +23819,7 @@ async function runLogin(opts, deps = {}) {
|
|
|
23477
23819
|
try {
|
|
23478
23820
|
config = resolveCognitoConfig(opts.env);
|
|
23479
23821
|
} catch (err) {
|
|
23480
|
-
if (err instanceof MissingCognitoClientIdError) {
|
|
23822
|
+
if (err instanceof MissingCognitoClientIdError || err instanceof InvalidBaseUrlError || err instanceof InvalidCognitoRegionError) {
|
|
23481
23823
|
process.stderr.write(` \u2716 ${err.message}
|
|
23482
23824
|
`);
|
|
23483
23825
|
return 2;
|
|
@@ -24070,6 +24412,11 @@ async function runBlueprintHarness(client, input, deps) {
|
|
|
24070
24412
|
// recording that it happened and nothing able to restore it. Opt out rather than grow the
|
|
24071
24413
|
// snapshot a leg teardown could not honour anyway.
|
|
24072
24414
|
pruneTriggers: false
|
|
24415
|
+
// `allowScriptAdoption` is left at its default `false` — the SAME created-only contract above
|
|
24416
|
+
// applies here: this harness may run against a shared tenant, and adopting a pre-existing,
|
|
24417
|
+
// unmarked script name would start this run's teardown treating (and, worse, later deleting)
|
|
24418
|
+
// versions on a name it does not own. A test blueprint that needs `scripts:` should ship a
|
|
24419
|
+
// fresh, collision-free name, exactly as the harness already expects for schemas/roles/issuers.
|
|
24073
24420
|
});
|
|
24074
24421
|
log.debug({ component: "harness", contextId: result.contextId, keyName: result.keyName }, "applied");
|
|
24075
24422
|
assertions = await runAssertions(
|
|
@@ -24283,7 +24630,7 @@ async function issueOrRotateKey(client, tenantId, context, userId, keyName) {
|
|
|
24283
24630
|
"The key already exists and its secret could not be re-minted (the prior key may not have cleared yet). Retry shortly."
|
|
24284
24631
|
);
|
|
24285
24632
|
}
|
|
24286
|
-
return { key: rotated, revokedKeyId: existing?.keyId };
|
|
24633
|
+
return { key: rotated, revokedKeyId: existing?.keyId ? [existing.keyId] : void 0 };
|
|
24287
24634
|
}
|
|
24288
24635
|
async function runJoin(opts, deps = {}) {
|
|
24289
24636
|
const hasRole = Boolean(opts.role?.length);
|
|
@@ -24430,9 +24777,9 @@ function emitJoined(opts) {
|
|
|
24430
24777
|
|
|
24431
24778
|
// src/build-info.ts
|
|
24432
24779
|
var BUILD_INFO = {
|
|
24433
|
-
cli: true ? "0.
|
|
24434
|
-
sdk: true ? "0.
|
|
24435
|
-
blueprints: true ? "0.
|
|
24780
|
+
cli: true ? "0.22.0" : "dev",
|
|
24781
|
+
sdk: true ? "0.44.0" : "dev",
|
|
24782
|
+
blueprints: true ? "0.19.0" : "dev"
|
|
24436
24783
|
};
|
|
24437
24784
|
function formatBuildInfo() {
|
|
24438
24785
|
return `vectros ${BUILD_INFO.cli} (sdk ${BUILD_INFO.sdk}, blueprints ${BUILD_INFO.blueprints})`;
|
|
@@ -24459,11 +24806,13 @@ function addCommonAuthOptions(cmd) {
|
|
|
24459
24806
|
"emit machine-readable JSON instead of the human-readable view"
|
|
24460
24807
|
);
|
|
24461
24808
|
}
|
|
24809
|
+
var TENANT_FLAG_INVALID = /* @__PURE__ */ Symbol("parseTenantFlag:invalid");
|
|
24462
24810
|
function parseTenantFlag(raw) {
|
|
24463
24811
|
if (raw !== void 0 && raw !== "test" && raw !== "live") {
|
|
24464
24812
|
process.stderr.write(` \u2716 --tenant must be 'test' or 'live' (got '${raw}')
|
|
24465
24813
|
`);
|
|
24466
|
-
process.
|
|
24814
|
+
process.exitCode = 2;
|
|
24815
|
+
return TENANT_FLAG_INVALID;
|
|
24467
24816
|
}
|
|
24468
24817
|
return raw;
|
|
24469
24818
|
}
|
|
@@ -24484,14 +24833,14 @@ function buildProgram(deps = {}) {
|
|
|
24484
24833
|
);
|
|
24485
24834
|
const login = program.command("login").description("Sign in via your browser (Cognito PKCE) and store a session.").addOption(new import_commander.Option("--env <env>", "target environment").choices(["staging", "production"]).default("production").hideHelp()).option("--no-browser", "print the sign-in URL instead of opening a browser (headless/CI/WSL)");
|
|
24486
24835
|
login.action(async (flags) => {
|
|
24487
|
-
process.
|
|
24836
|
+
process.exitCode = await runLogin({ env: flags.env, json: false, noBrowser: flags.browser === false });
|
|
24488
24837
|
});
|
|
24489
24838
|
program.command("logout").description("Clear the stored login session.").action(async () => {
|
|
24490
|
-
process.
|
|
24839
|
+
process.exitCode = await runLogout({ env: "production", json: false });
|
|
24491
24840
|
});
|
|
24492
24841
|
const whoami = program.command("whoami").description("Show the authenticated session (tenant, env, principal).");
|
|
24493
24842
|
addCommonAuthOptions(whoami).action(async (flags) => {
|
|
24494
|
-
process.
|
|
24843
|
+
process.exitCode = await runWhoami(toCommonOptions(flags), deps);
|
|
24495
24844
|
});
|
|
24496
24845
|
const blueprintTest = program.command("blueprint-test").description("Apply a blueprint to a live env, assert it provisioned a working credential, then tear it down.").argument("<file>", "blueprint file path (./my.yaml) or a bundled blueprint name").option("--keep", "leave the provisioned graph in place (skip teardown) for inspection").option("--tenant <tenant>", "target tenant for the run: 'test' or 'live' (default live)");
|
|
24497
24846
|
addCommonAuthOptions(blueprintTest).action(
|
|
@@ -24499,14 +24848,13 @@ function buildProgram(deps = {}) {
|
|
|
24499
24848
|
if (flags.tenant !== void 0 && flags.tenant !== "test" && flags.tenant !== "live") {
|
|
24500
24849
|
process.stderr.write(` \u2716 --tenant must be 'test' or 'live' (got '${flags.tenant}')
|
|
24501
24850
|
`);
|
|
24502
|
-
process.
|
|
24503
|
-
|
|
24504
|
-
|
|
24505
|
-
|
|
24506
|
-
|
|
24507
|
-
|
|
24508
|
-
|
|
24509
|
-
)
|
|
24851
|
+
process.exitCode = 2;
|
|
24852
|
+
return;
|
|
24853
|
+
}
|
|
24854
|
+
process.exitCode = await runBlueprintTest(
|
|
24855
|
+
file,
|
|
24856
|
+
{ ...toCommonOptions(flags), keep: Boolean(flags.keep), tenant: flags.tenant },
|
|
24857
|
+
deps
|
|
24510
24858
|
);
|
|
24511
24859
|
}
|
|
24512
24860
|
);
|
|
@@ -24527,9 +24875,7 @@ function registerAssumeCommand(program, deps) {
|
|
|
24527
24875
|
const assume = program.command("assume").description("Re-mint your own verb token with one or more identity.<namespace> values switched (needs a role's assumable grant).").requiredOption("--context <c>", "app context to operate in").option("--scope <ns:value>", "namespace value to assume, repeatable (e.g. org:orgB)", collect, []);
|
|
24528
24876
|
addCommonAuthOptions(assume).action(
|
|
24529
24877
|
async (flags) => {
|
|
24530
|
-
process.
|
|
24531
|
-
await runAssume({ ...toCommonOptions(flags), context: flags.context, scope: flags.scope ?? [] }, deps)
|
|
24532
|
-
);
|
|
24878
|
+
process.exitCode = await runAssume({ ...toCommonOptions(flags), context: flags.context, scope: flags.scope ?? [] }, deps);
|
|
24533
24879
|
}
|
|
24534
24880
|
);
|
|
24535
24881
|
}
|
|
@@ -24543,23 +24889,22 @@ function registerJoinCommand(program, deps) {
|
|
|
24543
24889
|
if (flags.tenant !== void 0 && flags.tenant !== "test" && flags.tenant !== "live") {
|
|
24544
24890
|
process.stderr.write(` \u2716 --tenant must be 'test' or 'live' (got '${flags.tenant}')
|
|
24545
24891
|
`);
|
|
24546
|
-
process.
|
|
24892
|
+
process.exitCode = 2;
|
|
24893
|
+
return;
|
|
24547
24894
|
}
|
|
24548
|
-
process.
|
|
24549
|
-
|
|
24550
|
-
|
|
24551
|
-
|
|
24552
|
-
|
|
24553
|
-
|
|
24554
|
-
|
|
24555
|
-
|
|
24556
|
-
|
|
24557
|
-
|
|
24558
|
-
|
|
24559
|
-
|
|
24560
|
-
|
|
24561
|
-
deps
|
|
24562
|
-
)
|
|
24895
|
+
process.exitCode = await runJoin(
|
|
24896
|
+
{
|
|
24897
|
+
...toCommonOptions(flags),
|
|
24898
|
+
contextArg,
|
|
24899
|
+
role: flags.role,
|
|
24900
|
+
actions: flags.actions,
|
|
24901
|
+
as: flags.as,
|
|
24902
|
+
scope: flags.scope,
|
|
24903
|
+
alias: flags.alias,
|
|
24904
|
+
tenant: flags.tenant,
|
|
24905
|
+
activate: flags.activate
|
|
24906
|
+
},
|
|
24907
|
+
deps
|
|
24563
24908
|
);
|
|
24564
24909
|
}
|
|
24565
24910
|
);
|
|
@@ -24568,29 +24913,27 @@ function registerKeyringCommands(program) {
|
|
|
24568
24913
|
const KEYRING_SHOW_FORMATS = ["human", "env", "raw"];
|
|
24569
24914
|
const keyring = program.command("keyring").description("Inspect and manage the local multi-principal keyring.");
|
|
24570
24915
|
keyring.command("list").description("List keyring entries and which one is active.").option("--json", "emit machine-readable JSON").action(async (flags) => {
|
|
24571
|
-
process.
|
|
24916
|
+
process.exitCode = await runKeyringList({ json: Boolean(flags.json) });
|
|
24572
24917
|
});
|
|
24573
24918
|
keyring.command("use").description("Make an entry the active identity.").argument("<alias>", "the keyring entry to activate").option("--json", "emit machine-readable JSON").action(async (alias, flags) => {
|
|
24574
|
-
process.
|
|
24919
|
+
process.exitCode = await runKeyringUse(alias, { json: Boolean(flags.json) });
|
|
24575
24920
|
});
|
|
24576
24921
|
keyring.command("show").description("Reveal the active (or a named) entry\u2019s key. `--format raw` prints just the secret \u2014 the form scripts and the MCP server resolve a key through.").option("--alias <name>", "show this entry instead of the active one").addOption(new import_commander.Option("--format <fmt>", "how to emit the key").choices(KEYRING_SHOW_FORMATS).default("human")).action(async (flags) => {
|
|
24577
|
-
process.
|
|
24922
|
+
process.exitCode = await runKeyringShow({ alias: flags.alias, format: flags.format });
|
|
24578
24923
|
});
|
|
24579
24924
|
keyring.command("doctor").description("Health-check the keyring: which entry is active, whether each secret is readable, which entries hold live keys (the ones that act on real data), and which store is holding your secrets.").option("--json", "emit machine-readable JSON").action(async (flags) => {
|
|
24580
|
-
process.
|
|
24925
|
+
process.exitCode = await runKeyringDoctor({ json: Boolean(flags.json) });
|
|
24581
24926
|
});
|
|
24582
24927
|
keyring.command("prune").description("Drop orphaned entries that have no stored secret. Entries whose secret will not decrypt, or is held in the OS credential store (which cannot report absence), are reported and never removed.").option("--dry-run", "report what would be removed without removing anything").option("--force", "skip the interactive confirmation (required when stdin is not a TTY)").option("--json", "emit machine-readable JSON").action(async (flags) => {
|
|
24583
|
-
process.
|
|
24584
|
-
await runKeyringPrune({ dryRun: Boolean(flags.dryRun), force: Boolean(flags.force), json: Boolean(flags.json) })
|
|
24585
|
-
);
|
|
24928
|
+
process.exitCode = await runKeyringPrune({ dryRun: Boolean(flags.dryRun), force: Boolean(flags.force), json: Boolean(flags.json) });
|
|
24586
24929
|
});
|
|
24587
24930
|
keyring.command("remove").description(
|
|
24588
24931
|
"Drop an entry and its stored secret. Refuses if the secret cannot be confirmed deleted \u2014 removing the entry would strand a live key by discarding the only record of its id."
|
|
24589
24932
|
).argument("<alias>", "the keyring entry to remove").option("--json", "emit machine-readable JSON").action(async (alias, flags) => {
|
|
24590
|
-
process.
|
|
24933
|
+
process.exitCode = await runKeyringRemove(alias, { json: Boolean(flags.json) });
|
|
24591
24934
|
});
|
|
24592
24935
|
program.command("switch").description("Shortcut for `keyring use` \u2014 flip the active identity.").argument("<alias>", "the keyring entry to activate").option("--json", "emit machine-readable JSON").action(async (alias, flags) => {
|
|
24593
|
-
process.
|
|
24936
|
+
process.exitCode = await runKeyringUse(alias, { json: Boolean(flags.json) });
|
|
24594
24937
|
});
|
|
24595
24938
|
}
|
|
24596
24939
|
function registerContextCommands(program, deps) {
|
|
@@ -24601,18 +24944,17 @@ function registerContextCommands(program, deps) {
|
|
|
24601
24944
|
if (flags.tenant !== void 0 && flags.tenant !== "test" && flags.tenant !== "live") {
|
|
24602
24945
|
process.stderr.write(` \u2716 --tenant must be 'test' or 'live' (got '${flags.tenant}')
|
|
24603
24946
|
`);
|
|
24604
|
-
process.
|
|
24947
|
+
process.exitCode = 2;
|
|
24948
|
+
return;
|
|
24605
24949
|
}
|
|
24606
|
-
process.
|
|
24607
|
-
|
|
24608
|
-
|
|
24609
|
-
|
|
24610
|
-
|
|
24611
|
-
|
|
24612
|
-
|
|
24613
|
-
|
|
24614
|
-
deps
|
|
24615
|
-
)
|
|
24950
|
+
process.exitCode = await runContextCreate(
|
|
24951
|
+
{
|
|
24952
|
+
...toCommonOptions(flags),
|
|
24953
|
+
contextId,
|
|
24954
|
+
name: flags.name,
|
|
24955
|
+
tenant: flags.tenant
|
|
24956
|
+
},
|
|
24957
|
+
deps
|
|
24616
24958
|
);
|
|
24617
24959
|
}
|
|
24618
24960
|
);
|
|
@@ -24621,13 +24963,12 @@ function registerContextCommands(program, deps) {
|
|
|
24621
24963
|
if (flags.tenant !== void 0 && flags.tenant !== "test" && flags.tenant !== "live") {
|
|
24622
24964
|
process.stderr.write(` \u2716 --tenant must be 'test' or 'live' (got '${flags.tenant}')
|
|
24623
24965
|
`);
|
|
24624
|
-
process.
|
|
24966
|
+
process.exitCode = 2;
|
|
24967
|
+
return;
|
|
24625
24968
|
}
|
|
24626
|
-
process.
|
|
24627
|
-
|
|
24628
|
-
|
|
24629
|
-
deps
|
|
24630
|
-
)
|
|
24969
|
+
process.exitCode = await runContextList(
|
|
24970
|
+
{ ...toCommonOptions(flags), mine: Boolean(flags.mine), tenant: flags.tenant },
|
|
24971
|
+
deps
|
|
24631
24972
|
);
|
|
24632
24973
|
});
|
|
24633
24974
|
const get = context.command("get").description("Show one app context.").argument("<contextId>").option("--tenant <tenant>", "tenant the context lives in: 'test' or 'live' (default live)");
|
|
@@ -24635,13 +24976,12 @@ function registerContextCommands(program, deps) {
|
|
|
24635
24976
|
if (flags.tenant !== void 0 && flags.tenant !== "test" && flags.tenant !== "live") {
|
|
24636
24977
|
process.stderr.write(` \u2716 --tenant must be 'test' or 'live' (got '${flags.tenant}')
|
|
24637
24978
|
`);
|
|
24638
|
-
process.
|
|
24979
|
+
process.exitCode = 2;
|
|
24980
|
+
return;
|
|
24639
24981
|
}
|
|
24640
|
-
process.
|
|
24641
|
-
|
|
24642
|
-
|
|
24643
|
-
deps
|
|
24644
|
-
)
|
|
24982
|
+
process.exitCode = await runContextGet(
|
|
24983
|
+
{ ...toCommonOptions(flags), contextId, tenant: flags.tenant },
|
|
24984
|
+
deps
|
|
24645
24985
|
);
|
|
24646
24986
|
});
|
|
24647
24987
|
const destroy = context.command("destroy").description(
|
|
@@ -24652,18 +24992,17 @@ function registerContextCommands(program, deps) {
|
|
|
24652
24992
|
if (flags.tenant !== void 0 && flags.tenant !== "test" && flags.tenant !== "live") {
|
|
24653
24993
|
process.stderr.write(` \u2716 --tenant must be 'test' or 'live' (got '${flags.tenant}')
|
|
24654
24994
|
`);
|
|
24655
|
-
process.
|
|
24995
|
+
process.exitCode = 2;
|
|
24996
|
+
return;
|
|
24656
24997
|
}
|
|
24657
|
-
process.
|
|
24658
|
-
|
|
24659
|
-
|
|
24660
|
-
|
|
24661
|
-
|
|
24662
|
-
|
|
24663
|
-
|
|
24664
|
-
|
|
24665
|
-
deps
|
|
24666
|
-
)
|
|
24998
|
+
process.exitCode = await runContextDestroy(
|
|
24999
|
+
{
|
|
25000
|
+
...toCommonOptions(flags),
|
|
25001
|
+
contextId,
|
|
25002
|
+
force: Boolean(flags.force),
|
|
25003
|
+
tenant: flags.tenant
|
|
25004
|
+
},
|
|
25005
|
+
deps
|
|
24667
25006
|
);
|
|
24668
25007
|
}
|
|
24669
25008
|
);
|
|
@@ -24674,18 +25013,16 @@ function registerIssuersCommands(program, deps) {
|
|
|
24674
25013
|
addCommonAuthOptions(list).action(
|
|
24675
25014
|
async (flags) => {
|
|
24676
25015
|
const tenant = parseTenantFlag(flags.tenant);
|
|
24677
|
-
|
|
24678
|
-
|
|
24679
|
-
);
|
|
25016
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
25017
|
+
process.exitCode = await runIssuersList({ ...toCommonOptions(flags), context: flags.context, tenant }, deps);
|
|
24680
25018
|
}
|
|
24681
25019
|
);
|
|
24682
25020
|
const get = issuers.command("get").description("Show one registered issuer.").argument("<issuerId>").requiredOption("--context <c>", "app context the issuer is registered under").option("--tenant <tenant>", "tenant to read: 'test' or 'live' (default live)");
|
|
24683
25021
|
addCommonAuthOptions(get).action(
|
|
24684
25022
|
async (issuerId, flags) => {
|
|
24685
25023
|
const tenant = parseTenantFlag(flags.tenant);
|
|
24686
|
-
|
|
24687
|
-
|
|
24688
|
-
);
|
|
25024
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
25025
|
+
process.exitCode = await runIssuersGet({ ...toCommonOptions(flags), context: flags.context, issuerId, tenant }, deps);
|
|
24689
25026
|
}
|
|
24690
25027
|
);
|
|
24691
25028
|
const register = issuers.command("register").description("Register (idempotent by issuerId) a trusted third-party IdP issuer.").requiredOption("--context <c>", "app context an exchanged token targets").requiredOption("--issuer-id <id>", "short slug for this issuer (3-31 chars; lowercase letter + lowercase/digit/dash)").requiredOption("--issuer <url>", "the IdP's iss claim value").requiredOption("--jwks-uri <url>", "the IdP's remote JWKS endpoint").requiredOption("--audience <aud>", "the required aud claim value (globally unique with --issuer)").option("--sub-claim <claim>", "claim carrying the subject identifier (default 'sub')").option("--email-claim <claim>", "claim carrying the subject's email (default 'email')").option("--userinfo-uri <url>", "OIDC userinfo endpoint, used as an email-resolution fallback").option(
|
|
@@ -24701,24 +25038,23 @@ function registerIssuersCommands(program, deps) {
|
|
|
24701
25038
|
addCommonAuthOptions(register).action(
|
|
24702
25039
|
async (flags) => {
|
|
24703
25040
|
const tenant = parseTenantFlag(flags.tenant);
|
|
24704
|
-
|
|
24705
|
-
|
|
24706
|
-
|
|
24707
|
-
|
|
24708
|
-
|
|
24709
|
-
|
|
24710
|
-
|
|
24711
|
-
|
|
24712
|
-
|
|
24713
|
-
|
|
24714
|
-
|
|
24715
|
-
|
|
24716
|
-
|
|
24717
|
-
|
|
24718
|
-
|
|
24719
|
-
|
|
24720
|
-
|
|
24721
|
-
)
|
|
25041
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
25042
|
+
process.exitCode = await runIssuersRegister(
|
|
25043
|
+
{
|
|
25044
|
+
...toCommonOptions(flags),
|
|
25045
|
+
context: flags.context,
|
|
25046
|
+
issuerId: flags.issuerId,
|
|
25047
|
+
issuer: flags.issuer,
|
|
25048
|
+
jwksUri: flags.jwksUri,
|
|
25049
|
+
audience: flags.audience,
|
|
25050
|
+
subClaim: flags.subClaim,
|
|
25051
|
+
emailClaim: flags.emailClaim,
|
|
25052
|
+
userinfoUri: flags.userinfoUri,
|
|
25053
|
+
selfSignupPolicy: flags.selfSignupPolicy,
|
|
25054
|
+
capturedClaim: flags.capturedClaim,
|
|
25055
|
+
tenant
|
|
25056
|
+
},
|
|
25057
|
+
deps
|
|
24722
25058
|
);
|
|
24723
25059
|
}
|
|
24724
25060
|
);
|
|
@@ -24735,24 +25071,23 @@ function registerIssuersCommands(program, deps) {
|
|
|
24735
25071
|
addCommonAuthOptions(update).action(
|
|
24736
25072
|
async (issuerId, flags) => {
|
|
24737
25073
|
const tenant = parseTenantFlag(flags.tenant);
|
|
24738
|
-
|
|
24739
|
-
|
|
24740
|
-
|
|
24741
|
-
|
|
24742
|
-
|
|
24743
|
-
|
|
24744
|
-
|
|
24745
|
-
|
|
24746
|
-
|
|
24747
|
-
|
|
24748
|
-
|
|
24749
|
-
|
|
24750
|
-
|
|
24751
|
-
|
|
24752
|
-
|
|
24753
|
-
|
|
24754
|
-
|
|
24755
|
-
)
|
|
25074
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
25075
|
+
process.exitCode = await runIssuersUpdate(
|
|
25076
|
+
{
|
|
25077
|
+
...toCommonOptions(flags),
|
|
25078
|
+
context: flags.context,
|
|
25079
|
+
issuerId,
|
|
25080
|
+
subClaim: flags.subClaim,
|
|
25081
|
+
emailClaim: flags.emailClaim,
|
|
25082
|
+
userinfoUri: flags.userinfoUri,
|
|
25083
|
+
status: flags.status,
|
|
25084
|
+
selfSignupPolicy: flags.selfSignupPolicy,
|
|
25085
|
+
clearSelfSignupPolicies: Boolean(flags.clearSelfSignupPolicies),
|
|
25086
|
+
capturedClaim: flags.capturedClaim,
|
|
25087
|
+
clearCapturedClaims: Boolean(flags.clearCapturedClaims),
|
|
25088
|
+
tenant
|
|
25089
|
+
},
|
|
25090
|
+
deps
|
|
24756
25091
|
);
|
|
24757
25092
|
}
|
|
24758
25093
|
);
|
|
@@ -24760,11 +25095,10 @@ function registerIssuersCommands(program, deps) {
|
|
|
24760
25095
|
addCommonAuthOptions(del).action(
|
|
24761
25096
|
async (issuerId, flags) => {
|
|
24762
25097
|
const tenant = parseTenantFlag(flags.tenant);
|
|
24763
|
-
|
|
24764
|
-
|
|
24765
|
-
|
|
24766
|
-
|
|
24767
|
-
)
|
|
25098
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
25099
|
+
process.exitCode = await runIssuersDelete(
|
|
25100
|
+
{ ...toCommonOptions(flags), context: flags.context, issuerId, force: Boolean(flags.force), tenant },
|
|
25101
|
+
deps
|
|
24768
25102
|
);
|
|
24769
25103
|
}
|
|
24770
25104
|
);
|
|
@@ -24780,33 +25114,30 @@ function registerNamespaceCommands(program, deps) {
|
|
|
24780
25114
|
` \u2716 --specificity-rank must be an integer between 0 and 1000000 (got '${flags.specificityRank}').
|
|
24781
25115
|
`
|
|
24782
25116
|
);
|
|
24783
|
-
process.
|
|
25117
|
+
process.exitCode = 2;
|
|
25118
|
+
return;
|
|
24784
25119
|
}
|
|
24785
|
-
process.
|
|
24786
|
-
|
|
24787
|
-
|
|
24788
|
-
|
|
24789
|
-
|
|
24790
|
-
|
|
24791
|
-
|
|
24792
|
-
|
|
24793
|
-
|
|
24794
|
-
|
|
24795
|
-
deps
|
|
24796
|
-
)
|
|
25120
|
+
process.exitCode = await runNamespaceRegister(
|
|
25121
|
+
{
|
|
25122
|
+
...toCommonOptions(flags),
|
|
25123
|
+
namespace: name,
|
|
25124
|
+
context: flags.context,
|
|
25125
|
+
specificityRank,
|
|
25126
|
+
entityBacked: Boolean(flags.entityBacked),
|
|
25127
|
+
defaultSchemaId: flags.defaultSchemaId
|
|
25128
|
+
},
|
|
25129
|
+
deps
|
|
24797
25130
|
);
|
|
24798
25131
|
}
|
|
24799
25132
|
);
|
|
24800
25133
|
const list = namespace.command("list").description("List scope-namespace registrations \u2014 tenant-wide, or one context\u2019s own.").option("--context <c>", "list one app context\u2019s own registrations instead of the tenant-wide set");
|
|
24801
25134
|
addCommonAuthOptions(list).action(async (flags) => {
|
|
24802
|
-
process.
|
|
25135
|
+
process.exitCode = await runNamespaceList({ ...toCommonOptions(flags), context: flags.context }, deps);
|
|
24803
25136
|
});
|
|
24804
25137
|
const get = namespace.command("get").description("Show one namespace registration \u2014 tenant-wide, or one context\u2019s own.").argument("<name>").option("--context <c>", "resolve for one app context\u2019s own registration instead of the tenant-wide one");
|
|
24805
25138
|
addCommonAuthOptions(get).action(
|
|
24806
25139
|
async (name, flags) => {
|
|
24807
|
-
process.
|
|
24808
|
-
await runNamespaceGet({ ...toCommonOptions(flags), namespace: name, context: flags.context }, deps)
|
|
24809
|
-
);
|
|
25140
|
+
process.exitCode = await runNamespaceGet({ ...toCommonOptions(flags), namespace: name, context: flags.context }, deps);
|
|
24810
25141
|
}
|
|
24811
25142
|
);
|
|
24812
25143
|
}
|
|
@@ -24815,59 +25146,59 @@ function registerScriptsCommands(program, deps) {
|
|
|
24815
25146
|
const push = scripts.command("push").description("Store a new version of a script name (always creates a version unless --if-changed).").argument("<name>", "script name: 1-64 chars of letters, digits, '_' and '-'").requiredOption("--context <c>", "app context to push into").requiredOption("--source-file <path>", "file holding the script source (max 300,000 UTF-8 bytes)").option("--input-contract <text>", "free-text description of the expected input shape (descriptive only)").option("--input-contract-file <path>", "read the input contract from a file instead").option("--if-changed", "push nothing when the current version already carries this exact content").option("--tenant <tenant>", "target tenant: 'test' or 'live' (default live)");
|
|
24816
25147
|
addCommonAuthOptions(push).action(
|
|
24817
25148
|
async (name, flags) => {
|
|
24818
|
-
|
|
24819
|
-
|
|
24820
|
-
|
|
24821
|
-
|
|
24822
|
-
|
|
24823
|
-
|
|
24824
|
-
|
|
24825
|
-
|
|
24826
|
-
|
|
24827
|
-
|
|
24828
|
-
|
|
24829
|
-
|
|
24830
|
-
|
|
24831
|
-
|
|
25149
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
25150
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
25151
|
+
process.exitCode = await runScriptsPush(
|
|
25152
|
+
{
|
|
25153
|
+
...toCommonOptions(flags),
|
|
25154
|
+
name,
|
|
25155
|
+
context: flags.context,
|
|
25156
|
+
sourceFile: flags.sourceFile,
|
|
25157
|
+
inputContract: flags.inputContract,
|
|
25158
|
+
inputContractFile: flags.inputContractFile,
|
|
25159
|
+
ifChanged: Boolean(flags.ifChanged),
|
|
25160
|
+
tenant
|
|
25161
|
+
},
|
|
25162
|
+
deps
|
|
24832
25163
|
);
|
|
24833
25164
|
}
|
|
24834
25165
|
);
|
|
24835
25166
|
const list = scripts.command("list").description("List script versions \u2014 every version of one --name (oldest first), or all scripts (newest first).").requiredOption("--context <c>", "app context to read").option("--name <name>", "list every version of this script name instead of a flat cross-name list").option("--tenant <tenant>", "target tenant: 'test' or 'live' (default live)");
|
|
24836
25167
|
addCommonAuthOptions(list).action(
|
|
24837
25168
|
async (flags) => {
|
|
24838
|
-
|
|
24839
|
-
|
|
24840
|
-
|
|
24841
|
-
|
|
24842
|
-
|
|
25169
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
25170
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
25171
|
+
process.exitCode = await runScriptsList(
|
|
25172
|
+
{ ...toCommonOptions(flags), context: flags.context, name: flags.name, tenant },
|
|
25173
|
+
deps
|
|
24843
25174
|
);
|
|
24844
25175
|
}
|
|
24845
25176
|
);
|
|
24846
25177
|
const get = scripts.command("get").description("Show one script version, including its full source.").argument("<id>", "the script VERSION id (each version has its own)").requiredOption("--context <c>", "app context to read").option("--tenant <tenant>", "target tenant: 'test' or 'live' (default live)");
|
|
24847
25178
|
addCommonAuthOptions(get).action(
|
|
24848
25179
|
async (id, flags) => {
|
|
24849
|
-
|
|
24850
|
-
|
|
24851
|
-
|
|
24852
|
-
|
|
24853
|
-
|
|
25180
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
25181
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
25182
|
+
process.exitCode = await runScriptsGet(
|
|
25183
|
+
{ ...toCommonOptions(flags), id, context: flags.context, tenant },
|
|
25184
|
+
deps
|
|
24854
25185
|
);
|
|
24855
25186
|
}
|
|
24856
25187
|
);
|
|
24857
25188
|
const del = scripts.command("delete").description("Permanently delete ONE script version (other versions of the name are unaffected).").argument("<id>", "the script VERSION id").requiredOption("--context <c>", "app context to delete from").option("--tenant <tenant>", "target tenant: 'test' or 'live' (default live)").option("--force", "skip the interactive confirmation (required in a non-interactive shell)");
|
|
24858
25189
|
addCommonAuthOptions(del).action(
|
|
24859
25190
|
async (id, flags) => {
|
|
24860
|
-
|
|
24861
|
-
|
|
24862
|
-
|
|
24863
|
-
|
|
24864
|
-
|
|
24865
|
-
|
|
24866
|
-
|
|
24867
|
-
|
|
24868
|
-
|
|
24869
|
-
|
|
24870
|
-
|
|
25191
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
25192
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
25193
|
+
process.exitCode = await runScriptsDelete(
|
|
25194
|
+
{
|
|
25195
|
+
...toCommonOptions(flags),
|
|
25196
|
+
id,
|
|
25197
|
+
context: flags.context,
|
|
25198
|
+
tenant,
|
|
25199
|
+
force: Boolean(flags.force)
|
|
25200
|
+
},
|
|
25201
|
+
deps
|
|
24871
25202
|
);
|
|
24872
25203
|
}
|
|
24873
25204
|
);
|
|
@@ -24887,9 +25218,11 @@ function registerRoleCommands(program, deps) {
|
|
|
24887
25218
|
).option("--description <text>", "free-text description").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
24888
25219
|
addCommonAuthOptions(create).action(
|
|
24889
25220
|
async (flags) => {
|
|
25221
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
25222
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
24890
25223
|
process.exitCode = await runRoleCreate({
|
|
24891
25224
|
...toCommonOptions(flags),
|
|
24892
|
-
tenant
|
|
25225
|
+
tenant,
|
|
24893
25226
|
context: flags.context,
|
|
24894
25227
|
roleId: flags.roleId,
|
|
24895
25228
|
name: flags.name,
|
|
@@ -24903,16 +25236,20 @@ function registerRoleCommands(program, deps) {
|
|
|
24903
25236
|
);
|
|
24904
25237
|
const list = role.command("list").description("List the roles defined in a context.").requiredOption("--context <c>", "app context").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
24905
25238
|
addCommonAuthOptions(list).action(async (flags) => {
|
|
25239
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
25240
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
24906
25241
|
process.exitCode = await runRoleList(
|
|
24907
|
-
{ ...toCommonOptions(flags), tenant
|
|
25242
|
+
{ ...toCommonOptions(flags), tenant, context: flags.context },
|
|
24908
25243
|
deps
|
|
24909
25244
|
);
|
|
24910
25245
|
});
|
|
24911
25246
|
const get = role.command("get").description("Show one role by id.").requiredOption("--context <c>", "app context").requiredOption("--role-id <id>", "role id").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
24912
25247
|
addCommonAuthOptions(get).action(
|
|
24913
25248
|
async (flags) => {
|
|
25249
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
25250
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
24914
25251
|
process.exitCode = await runRoleGet(
|
|
24915
|
-
{ ...toCommonOptions(flags), tenant
|
|
25252
|
+
{ ...toCommonOptions(flags), tenant, context: flags.context, roleId: flags.roleId },
|
|
24916
25253
|
deps
|
|
24917
25254
|
);
|
|
24918
25255
|
}
|
|
@@ -24920,8 +25257,10 @@ function registerRoleCommands(program, deps) {
|
|
|
24920
25257
|
const del = role.command("delete").description("Delete a role (referencing profiles must be reassigned).").requiredOption("--context <c>", "app context").requiredOption("--role-id <id>", "role id").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
24921
25258
|
addAuthOptions(del).action(
|
|
24922
25259
|
async (flags) => {
|
|
25260
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
25261
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
24923
25262
|
process.exitCode = await runRoleDelete(
|
|
24924
|
-
{ ...toCommonOptions(flags), tenant
|
|
25263
|
+
{ ...toCommonOptions(flags), tenant, context: flags.context, roleId: flags.roleId },
|
|
24925
25264
|
deps
|
|
24926
25265
|
);
|
|
24927
25266
|
}
|
|
@@ -24932,9 +25271,11 @@ function registerAccessCommands(program, deps) {
|
|
|
24932
25271
|
const grant = access2.command("grant").description("Bind a principal to a context via a role or inline actions.").requiredOption("--principal <p>", "principal: 'usr_<userId>', 'key_<keyId>', or 'me' (your own user)").requiredOption("--context <c>", "app context").option("--role <roleId>", "reference a Role (repeatable \u2014 2+ compose additively; XOR --actions)", collect, []).option("--actions <csv>", "inline single-clause actions, comma-separated (XOR --role)").option("--scope <ns:value>", "identity override: a scope the principal acts under (repeatable, e.g. org:<id>)", collect, []).option("--identity-overrides <json>", "general identity overrides as a JSON object (merged over --scope)").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
24933
25272
|
addCommonAuthOptions(grant).action(
|
|
24934
25273
|
async (flags) => {
|
|
25274
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
25275
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
24935
25276
|
process.exitCode = await runAccessGrant({
|
|
24936
25277
|
...toCommonOptions(flags),
|
|
24937
|
-
tenant
|
|
25278
|
+
tenant,
|
|
24938
25279
|
principal: flags.principal,
|
|
24939
25280
|
context: flags.context,
|
|
24940
25281
|
role: flags.role,
|
|
@@ -24947,8 +25288,10 @@ function registerAccessCommands(program, deps) {
|
|
|
24947
25288
|
const revoke = access2.command("revoke").description("Remove a principal\u2019s binding in a context.").requiredOption("--principal <p>", "principal: 'usr_<userId>', 'key_<keyId>', or 'me' (your own user)").requiredOption("--context <c>", "app context").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
24948
25289
|
addAuthOptions(revoke).action(
|
|
24949
25290
|
async (flags) => {
|
|
25291
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
25292
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
24950
25293
|
process.exitCode = await runAccessRevoke(
|
|
24951
|
-
{ ...toCommonOptions(flags), tenant
|
|
25294
|
+
{ ...toCommonOptions(flags), tenant, principal: flags.principal, context: flags.context },
|
|
24952
25295
|
deps
|
|
24953
25296
|
);
|
|
24954
25297
|
}
|
|
@@ -24956,8 +25299,10 @@ function registerAccessCommands(program, deps) {
|
|
|
24956
25299
|
const list = access2.command("list").description("List a context\u2019s members (--context) or a principal\u2019s contexts (--principal).").option("--context <c>", "list members of this context").option("--principal <p>", "list contexts this principal is bound to ('me' = your own user)").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
24957
25300
|
addCommonAuthOptions(list).action(
|
|
24958
25301
|
async (flags) => {
|
|
25302
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
25303
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
24959
25304
|
process.exitCode = await runAccessList(
|
|
24960
|
-
{ ...toCommonOptions(flags), tenant
|
|
25305
|
+
{ ...toCommonOptions(flags), tenant, context: flags.context, principal: flags.principal },
|
|
24961
25306
|
deps
|
|
24962
25307
|
);
|
|
24963
25308
|
}
|
|
@@ -24965,8 +25310,10 @@ function registerAccessCommands(program, deps) {
|
|
|
24965
25310
|
const get = access2.command("get").description("Show one principal\u2019s binding in a context.").requiredOption("--principal <p>", "principal: 'usr_<userId>', 'key_<keyId>', or 'me' (your own user)").requiredOption("--context <c>", "app context").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
24966
25311
|
addCommonAuthOptions(get).action(
|
|
24967
25312
|
async (flags) => {
|
|
25313
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
25314
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
24968
25315
|
process.exitCode = await runAccessGet(
|
|
24969
|
-
{ ...toCommonOptions(flags), tenant
|
|
25316
|
+
{ ...toCommonOptions(flags), tenant, principal: flags.principal, context: flags.context },
|
|
24970
25317
|
deps
|
|
24971
25318
|
);
|
|
24972
25319
|
}
|
|
@@ -24974,8 +25321,10 @@ function registerAccessCommands(program, deps) {
|
|
|
24974
25321
|
const explain = access2.command("explain").description("Show what a principal\u2019s binding grants: its clauses + a concrete access matrix.").requiredOption("--principal <p>", "principal: 'usr_<userId>', 'key_<keyId>', or 'me' (your own user)").requiredOption("--context <c>", "app context").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
24975
25322
|
addCommonAuthOptions(explain).action(
|
|
24976
25323
|
async (flags) => {
|
|
25324
|
+
const tenant = parseTenantFlag(flags.tenant);
|
|
25325
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
24977
25326
|
process.exitCode = await runAccessExplain(
|
|
24978
|
-
{ ...toCommonOptions(flags), tenant
|
|
25327
|
+
{ ...toCommonOptions(flags), tenant, principal: flags.principal, context: flags.context },
|
|
24979
25328
|
deps
|
|
24980
25329
|
);
|
|
24981
25330
|
}
|
|
@@ -24992,31 +25341,27 @@ function registerIdentityCommands(program, deps) {
|
|
|
24992
25341
|
const create = identity.command("create").description("Create (idempotent by externalId) a principal.").addOption(dimensionOption()).requiredOption("--external-id <id>", "your stable id for the principal (idempotency key)").option("--name <name>", "display name (entity namespaces only, not --type user)").option("--email <email>", "email (user only)").option("--service", "user only: mint a SERVICE principal (default HUMAN)").option("--scope <ns:value>", "entity only: a parent ownership edge (repeatable, \u22642, e.g. org:<id>)", collect, []).option("--metadata <json>", "JSON object of metadata");
|
|
24993
25342
|
addCommonAuthOptions(create).action(
|
|
24994
25343
|
async (flags) => {
|
|
24995
|
-
process.
|
|
24996
|
-
|
|
24997
|
-
|
|
24998
|
-
|
|
24999
|
-
|
|
25000
|
-
|
|
25001
|
-
|
|
25002
|
-
|
|
25003
|
-
|
|
25004
|
-
|
|
25005
|
-
}, deps)
|
|
25006
|
-
);
|
|
25344
|
+
process.exitCode = await runIdentityCreate({
|
|
25345
|
+
...toCommonOptions(flags),
|
|
25346
|
+
type: flags.type,
|
|
25347
|
+
externalId: flags.externalId,
|
|
25348
|
+
name: flags.name,
|
|
25349
|
+
email: flags.email,
|
|
25350
|
+
service: flags.service,
|
|
25351
|
+
scope: flags.scope,
|
|
25352
|
+
metadata: flags.metadata
|
|
25353
|
+
}, deps);
|
|
25007
25354
|
}
|
|
25008
25355
|
);
|
|
25009
25356
|
const list = identity.command("list").description("List principals of a dimension (optionally filtered).").addOption(dimensionOption()).option("--external-id <id>", "filter to a stable id").option("--limit <n>", "page size (1\u2013100)", (v) => Number.parseInt(v, 10));
|
|
25010
25357
|
addCommonAuthOptions(list).action(
|
|
25011
25358
|
async (flags) => {
|
|
25012
|
-
process.
|
|
25013
|
-
|
|
25014
|
-
|
|
25015
|
-
|
|
25016
|
-
|
|
25017
|
-
|
|
25018
|
-
}, deps)
|
|
25019
|
-
);
|
|
25359
|
+
process.exitCode = await runIdentityList({
|
|
25360
|
+
...toCommonOptions(flags),
|
|
25361
|
+
type: flags.type,
|
|
25362
|
+
externalId: flags.externalId,
|
|
25363
|
+
limit: flags.limit
|
|
25364
|
+
}, deps);
|
|
25020
25365
|
}
|
|
25021
25366
|
);
|
|
25022
25367
|
const listAll = identity.command("list-all").description(
|
|
@@ -25025,38 +25370,35 @@ function registerIdentityCommands(program, deps) {
|
|
|
25025
25370
|
addCommonAuthOptions(listAll).action(
|
|
25026
25371
|
async (flags) => {
|
|
25027
25372
|
const tenant = parseTenantFlag(flags.tenant);
|
|
25028
|
-
|
|
25029
|
-
|
|
25030
|
-
|
|
25031
|
-
|
|
25032
|
-
)
|
|
25373
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
25374
|
+
process.exitCode = await runIdentityListAll(
|
|
25375
|
+
{ ...toCommonOptions(flags), externalId: flags.externalId, limit: flags.limit, tenant },
|
|
25376
|
+
deps
|
|
25033
25377
|
);
|
|
25034
25378
|
}
|
|
25035
25379
|
);
|
|
25036
25380
|
const get = identity.command("get").description("Show one principal by its Vectros id.").addOption(dimensionOption()).requiredOption("--id <vectrosId>", "the Vectros-assigned id (from create/list)");
|
|
25037
25381
|
addCommonAuthOptions(get).action(async (flags) => {
|
|
25038
|
-
process.
|
|
25382
|
+
process.exitCode = await runIdentityGet({ ...toCommonOptions(flags), type: flags.type, id: flags.id }, deps);
|
|
25039
25383
|
});
|
|
25040
25384
|
const del = identity.command("delete").description("Delete a principal by its Vectros id.").addOption(dimensionOption()).requiredOption("--id <vectrosId>", "the Vectros-assigned id");
|
|
25041
25385
|
addAuthOptions(del).action(async (flags) => {
|
|
25042
|
-
process.
|
|
25386
|
+
process.exitCode = await runIdentityDelete({ ...toCommonOptions(flags), type: flags.type, id: flags.id }, deps);
|
|
25043
25387
|
});
|
|
25044
25388
|
const invite = identity.command("invite").description("Onboard a person into a context by email (POST /v1/users/invite) \u2014 no root key required.").requiredOption("--context <contextId>", "the app context the invitee will operate in").requiredOption("--role <roleId>", "an existing Role in that context; the invitee inherits its scopes").requiredOption("--email <email>", "the invitee's email address").option("--first-name <name>", "personalizes the invitation email greeting").option("--accept-url <url>", "your accept-invite landing page (required unless --no-send-email)").option("--no-send-email", "skip platform delivery; the response carries the raw inviteToken/acceptLink instead");
|
|
25045
25389
|
addCommonAuthOptions(invite).action(
|
|
25046
25390
|
async (flags) => {
|
|
25047
|
-
process.
|
|
25048
|
-
|
|
25049
|
-
|
|
25050
|
-
|
|
25051
|
-
|
|
25052
|
-
|
|
25053
|
-
|
|
25054
|
-
|
|
25055
|
-
|
|
25056
|
-
|
|
25057
|
-
|
|
25058
|
-
deps
|
|
25059
|
-
)
|
|
25391
|
+
process.exitCode = await runIdentityInvite(
|
|
25392
|
+
{
|
|
25393
|
+
...toCommonOptions(flags),
|
|
25394
|
+
context: flags.context,
|
|
25395
|
+
role: flags.role,
|
|
25396
|
+
email: flags.email,
|
|
25397
|
+
firstName: flags.firstName,
|
|
25398
|
+
acceptUrl: flags.acceptUrl,
|
|
25399
|
+
noSendEmail: flags.sendEmail === false
|
|
25400
|
+
},
|
|
25401
|
+
deps
|
|
25060
25402
|
);
|
|
25061
25403
|
}
|
|
25062
25404
|
);
|
|
@@ -25082,17 +25424,16 @@ function registerKeyCommands(program, deps) {
|
|
|
25082
25424
|
);
|
|
25083
25425
|
addAuthOptions(issue).action(async (flags) => {
|
|
25084
25426
|
const tenant = parseTenantFlag(flags.tenant);
|
|
25085
|
-
|
|
25086
|
-
|
|
25087
|
-
|
|
25088
|
-
|
|
25089
|
-
|
|
25090
|
-
|
|
25091
|
-
|
|
25092
|
-
|
|
25093
|
-
|
|
25094
|
-
|
|
25095
|
-
);
|
|
25427
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
25428
|
+
process.exitCode = await runKeyIssue({
|
|
25429
|
+
...toCommonOptions(flags),
|
|
25430
|
+
principal: flags.principal,
|
|
25431
|
+
context: flags.context,
|
|
25432
|
+
name: flags.name,
|
|
25433
|
+
label: flags.label,
|
|
25434
|
+
tenant,
|
|
25435
|
+
...toKeyDestination(flags)
|
|
25436
|
+
}, deps);
|
|
25096
25437
|
});
|
|
25097
25438
|
const list = key.command("list").description("List active scoped keys in one context (default, or --context <id>); --all-contexts for every context.").option("--principal <p>", "only keys for this principal (usr_<id> or <id>)").option("--context <c>", "only keys in this context (default: the default context)").option(
|
|
25098
25439
|
"--all-contexts",
|
|
@@ -25102,20 +25443,20 @@ function registerKeyCommands(program, deps) {
|
|
|
25102
25443
|
async (flags) => {
|
|
25103
25444
|
if (flags.allContexts && flags.tenant !== void 0) {
|
|
25104
25445
|
process.stderr.write(" \u2716 --tenant is not combinable with --all-contexts (which already spans both tenants).\n");
|
|
25105
|
-
process.
|
|
25446
|
+
process.exitCode = 2;
|
|
25447
|
+
return;
|
|
25106
25448
|
}
|
|
25107
25449
|
const tenant = parseTenantFlag(flags.tenant);
|
|
25108
|
-
|
|
25109
|
-
|
|
25110
|
-
|
|
25111
|
-
|
|
25112
|
-
|
|
25113
|
-
|
|
25114
|
-
|
|
25115
|
-
|
|
25116
|
-
|
|
25117
|
-
|
|
25118
|
-
)
|
|
25450
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
25451
|
+
process.exitCode = await runKeyList(
|
|
25452
|
+
{
|
|
25453
|
+
...toCommonOptions(flags),
|
|
25454
|
+
principal: flags.principal,
|
|
25455
|
+
context: flags.context,
|
|
25456
|
+
allContexts: Boolean(flags.allContexts),
|
|
25457
|
+
tenant
|
|
25458
|
+
},
|
|
25459
|
+
deps
|
|
25119
25460
|
);
|
|
25120
25461
|
}
|
|
25121
25462
|
);
|
|
@@ -25123,14 +25464,16 @@ function registerKeyCommands(program, deps) {
|
|
|
25123
25464
|
addCommonAuthOptions(get).action(
|
|
25124
25465
|
async (keyId, flags) => {
|
|
25125
25466
|
const tenant = parseTenantFlag(flags.tenant);
|
|
25126
|
-
|
|
25467
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
25468
|
+
process.exitCode = await runKeyGet(keyId, { ...toCommonOptions(flags), context: flags.context, tenant }, deps);
|
|
25127
25469
|
}
|
|
25128
25470
|
);
|
|
25129
25471
|
const revoke = key.command("revoke").description("Revoke a key (soft-delete; ~5 min to take effect).").argument("<keyId>").option("--context <c>", "context the key is bound to (default: the default context)").option("--tenant <tenant>", "tenant the key lives in: 'test' or 'live' (default live)");
|
|
25130
25472
|
addAuthOptions(revoke).action(
|
|
25131
25473
|
async (keyId, flags) => {
|
|
25132
25474
|
const tenant = parseTenantFlag(flags.tenant);
|
|
25133
|
-
|
|
25475
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
25476
|
+
process.exitCode = await runKeyRevoke(keyId, { ...toCommonOptions(flags), context: flags.context, tenant }, deps);
|
|
25134
25477
|
}
|
|
25135
25478
|
);
|
|
25136
25479
|
const rotate = addKeyDestinationOptions(
|
|
@@ -25141,17 +25484,16 @@ function registerKeyCommands(program, deps) {
|
|
|
25141
25484
|
);
|
|
25142
25485
|
addAuthOptions(rotate).action(async (flags) => {
|
|
25143
25486
|
const tenant = parseTenantFlag(flags.tenant);
|
|
25144
|
-
|
|
25145
|
-
|
|
25146
|
-
|
|
25147
|
-
|
|
25148
|
-
|
|
25149
|
-
|
|
25150
|
-
|
|
25151
|
-
|
|
25152
|
-
|
|
25153
|
-
|
|
25154
|
-
);
|
|
25487
|
+
if (tenant === TENANT_FLAG_INVALID) return;
|
|
25488
|
+
process.exitCode = await runKeyRotate({
|
|
25489
|
+
...toCommonOptions(flags),
|
|
25490
|
+
principal: flags.principal,
|
|
25491
|
+
context: flags.context,
|
|
25492
|
+
name: flags.name,
|
|
25493
|
+
forceNew: flags.forceNew,
|
|
25494
|
+
tenant,
|
|
25495
|
+
...toKeyDestination(flags)
|
|
25496
|
+
}, deps);
|
|
25155
25497
|
});
|
|
25156
25498
|
}
|
|
25157
25499
|
|
|
@@ -25164,7 +25506,8 @@ async function main() {
|
|
|
25164
25506
|
const sub = process.argv[2];
|
|
25165
25507
|
const passthrough = sub ? PASSTHROUGH_COMMANDS[sub] : void 0;
|
|
25166
25508
|
if (passthrough) {
|
|
25167
|
-
process.
|
|
25509
|
+
process.exitCode = await passthrough(process.argv.slice(3));
|
|
25510
|
+
return;
|
|
25168
25511
|
}
|
|
25169
25512
|
await buildProgram().parseAsync(process.argv);
|
|
25170
25513
|
}
|
|
@@ -25175,6 +25518,6 @@ main().catch((err) => {
|
|
|
25175
25518
|
process.stderr.write(`${err.stack}
|
|
25176
25519
|
`);
|
|
25177
25520
|
}
|
|
25178
|
-
process.
|
|
25521
|
+
process.exitCode = 2;
|
|
25179
25522
|
});
|
|
25180
25523
|
//# sourceMappingURL=cli.js.map
|