@vectros-ai/cli 0.20.0 → 0.21.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 +133 -3
- package/CHANGELOG.md +198 -0
- package/README.md +84 -17
- package/dist/cli.js +2978 -462
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +2978 -462
- package/dist/cli.mjs.map +1 -1
- package/package.json +3 -3
package/dist/cli.mjs
CHANGED
|
@@ -526,8 +526,8 @@ var require_graceful_fs = __commonJS({
|
|
|
526
526
|
fs5.createReadStream = createReadStream;
|
|
527
527
|
fs5.createWriteStream = createWriteStream;
|
|
528
528
|
var fs$readFile = fs5.readFile;
|
|
529
|
-
fs5.readFile =
|
|
530
|
-
function
|
|
529
|
+
fs5.readFile = readFile4;
|
|
530
|
+
function readFile4(path4, options, cb) {
|
|
531
531
|
if (typeof options === "function")
|
|
532
532
|
cb = options, options = null;
|
|
533
533
|
return go$readFile(path4, options, cb);
|
|
@@ -1619,7 +1619,7 @@ var require_proper_lockfile = __commonJS({
|
|
|
1619
1619
|
// src/cli-bootstrap.ts
|
|
1620
1620
|
import * as readline2 from "readline/promises";
|
|
1621
1621
|
|
|
1622
|
-
// node_modules/@vectros-ai/sdk/dist/index.mjs
|
|
1622
|
+
// ../../node_modules/@vectros-ai/sdk/dist/index.mjs
|
|
1623
1623
|
var __defProp2 = Object.defineProperty;
|
|
1624
1624
|
var __export = (target, all) => {
|
|
1625
1625
|
for (var name in all)
|
|
@@ -1653,6 +1653,7 @@ __export(api_exports, {
|
|
|
1653
1653
|
FieldDef: () => FieldDef,
|
|
1654
1654
|
FileUploadRequest: () => FileUploadRequest,
|
|
1655
1655
|
ForbiddenError: () => ForbiddenError,
|
|
1656
|
+
GatewayTimeoutError: () => GatewayTimeoutError,
|
|
1656
1657
|
IdentityLookupRequest: () => IdentityLookupRequest,
|
|
1657
1658
|
IndexFailure: () => IndexFailure,
|
|
1658
1659
|
InternalServerError: () => InternalServerError,
|
|
@@ -1677,9 +1678,12 @@ __export(api_exports, {
|
|
|
1677
1678
|
SearchRequest: () => SearchRequest,
|
|
1678
1679
|
SearchResult: () => SearchResult,
|
|
1679
1680
|
SearchResultsEvent: () => SearchResultsEvent,
|
|
1681
|
+
ServiceUnavailableError: () => ServiceUnavailableError,
|
|
1680
1682
|
TooManyRequestsError: () => TooManyRequestsError,
|
|
1683
|
+
TriggerFailureResponse: () => TriggerFailureResponse,
|
|
1681
1684
|
TruncationWarningEvent: () => TruncationWarningEvent,
|
|
1682
1685
|
UnauthorizedError: () => UnauthorizedError,
|
|
1686
|
+
UnprocessableEntityError: () => UnprocessableEntityError,
|
|
1683
1687
|
UserExistsResponse: () => UserExistsResponse,
|
|
1684
1688
|
UserRequest: () => UserRequest,
|
|
1685
1689
|
UserResponse: () => UserResponse,
|
|
@@ -1691,7 +1695,9 @@ __export(api_exports, {
|
|
|
1691
1695
|
inference: () => inference_exports,
|
|
1692
1696
|
records: () => records_exports,
|
|
1693
1697
|
schemas: () => schemas_exports,
|
|
1694
|
-
|
|
1698
|
+
scripts: () => scripts_exports,
|
|
1699
|
+
search: () => search_exports,
|
|
1700
|
+
triggers: () => triggers_exports
|
|
1695
1701
|
});
|
|
1696
1702
|
var toJson = (value, replacer, space) => {
|
|
1697
1703
|
return JSON.stringify(value, replacer, space);
|
|
@@ -1811,6 +1817,21 @@ var ForbiddenError = class extends VectrosError {
|
|
|
1811
1817
|
this.name = this.constructor.name;
|
|
1812
1818
|
}
|
|
1813
1819
|
};
|
|
1820
|
+
var GatewayTimeoutError = class extends VectrosError {
|
|
1821
|
+
constructor(body, rawResponse) {
|
|
1822
|
+
super({
|
|
1823
|
+
message: "GatewayTimeoutError",
|
|
1824
|
+
statusCode: 504,
|
|
1825
|
+
body,
|
|
1826
|
+
rawResponse
|
|
1827
|
+
});
|
|
1828
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
1829
|
+
if (Error.captureStackTrace) {
|
|
1830
|
+
Error.captureStackTrace(this, this.constructor);
|
|
1831
|
+
}
|
|
1832
|
+
this.name = this.constructor.name;
|
|
1833
|
+
}
|
|
1834
|
+
};
|
|
1814
1835
|
var InternalServerError = class extends VectrosError {
|
|
1815
1836
|
constructor(body, rawResponse) {
|
|
1816
1837
|
super({
|
|
@@ -1871,6 +1892,21 @@ var PaymentRequiredError = class extends VectrosError {
|
|
|
1871
1892
|
this.name = this.constructor.name;
|
|
1872
1893
|
}
|
|
1873
1894
|
};
|
|
1895
|
+
var ServiceUnavailableError = class extends VectrosError {
|
|
1896
|
+
constructor(body, rawResponse) {
|
|
1897
|
+
super({
|
|
1898
|
+
message: "ServiceUnavailableError",
|
|
1899
|
+
statusCode: 503,
|
|
1900
|
+
body,
|
|
1901
|
+
rawResponse
|
|
1902
|
+
});
|
|
1903
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
1904
|
+
if (Error.captureStackTrace) {
|
|
1905
|
+
Error.captureStackTrace(this, this.constructor);
|
|
1906
|
+
}
|
|
1907
|
+
this.name = this.constructor.name;
|
|
1908
|
+
}
|
|
1909
|
+
};
|
|
1874
1910
|
var TooManyRequestsError = class extends VectrosError {
|
|
1875
1911
|
constructor(body, rawResponse) {
|
|
1876
1912
|
super({
|
|
@@ -1901,6 +1937,21 @@ var UnauthorizedError = class extends VectrosError {
|
|
|
1901
1937
|
this.name = this.constructor.name;
|
|
1902
1938
|
}
|
|
1903
1939
|
};
|
|
1940
|
+
var UnprocessableEntityError = class extends VectrosError {
|
|
1941
|
+
constructor(body, rawResponse) {
|
|
1942
|
+
super({
|
|
1943
|
+
message: "UnprocessableEntityError",
|
|
1944
|
+
statusCode: 422,
|
|
1945
|
+
body,
|
|
1946
|
+
rawResponse
|
|
1947
|
+
});
|
|
1948
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
1949
|
+
if (Error.captureStackTrace) {
|
|
1950
|
+
Error.captureStackTrace(this, this.constructor);
|
|
1951
|
+
}
|
|
1952
|
+
this.name = this.constructor.name;
|
|
1953
|
+
}
|
|
1954
|
+
};
|
|
1904
1955
|
var auth_exports = {};
|
|
1905
1956
|
var ErasureRequest;
|
|
1906
1957
|
((ErasureRequest2) => {
|
|
@@ -1990,6 +2041,7 @@ var LookupRecordsRequestOrder = {
|
|
|
1990
2041
|
Desc: "desc"
|
|
1991
2042
|
};
|
|
1992
2043
|
var schemas_exports = {};
|
|
2044
|
+
var scripts_exports = {};
|
|
1993
2045
|
var SearchRequest;
|
|
1994
2046
|
((SearchRequest2) => {
|
|
1995
2047
|
SearchRequest2.Mode = {
|
|
@@ -2015,6 +2067,7 @@ var search_exports = {};
|
|
|
2015
2067
|
__export(search_exports, {
|
|
2016
2068
|
SearchRequest: () => SearchRequest
|
|
2017
2069
|
});
|
|
2070
|
+
var triggers_exports = {};
|
|
2018
2071
|
var AccessProfileRequest;
|
|
2019
2072
|
((AccessProfileRequest2) => {
|
|
2020
2073
|
AccessProfileRequest2.Status = {
|
|
@@ -2044,7 +2097,9 @@ var BatchWriteResult;
|
|
|
2044
2097
|
Created: "created",
|
|
2045
2098
|
Updated: "updated",
|
|
2046
2099
|
Conflict: "conflict",
|
|
2047
|
-
Invalid: "invalid"
|
|
2100
|
+
Invalid: "invalid",
|
|
2101
|
+
Forbidden: "forbidden",
|
|
2102
|
+
NotCommitted: "not_committed"
|
|
2048
2103
|
};
|
|
2049
2104
|
})(BatchWriteResult || (BatchWriteResult = {}));
|
|
2050
2105
|
var ChatMessage;
|
|
@@ -2348,13 +2403,23 @@ var SearchResultsEvent;
|
|
|
2348
2403
|
SearchResults: "search_results"
|
|
2349
2404
|
};
|
|
2350
2405
|
})(SearchResultsEvent || (SearchResultsEvent = {}));
|
|
2406
|
+
var TriggerFailureResponse;
|
|
2407
|
+
((TriggerFailureResponse2) => {
|
|
2408
|
+
TriggerFailureResponse2.Event = {
|
|
2409
|
+
Create: "CREATE",
|
|
2410
|
+
Update: "UPDATE",
|
|
2411
|
+
Delete: "DELETE"
|
|
2412
|
+
};
|
|
2413
|
+
})(TriggerFailureResponse || (TriggerFailureResponse = {}));
|
|
2351
2414
|
var TruncationWarningEvent;
|
|
2352
2415
|
((TruncationWarningEvent2) => {
|
|
2353
2416
|
TruncationWarningEvent2.Event = {
|
|
2354
2417
|
TruncationWarning: "truncation_warning"
|
|
2355
2418
|
};
|
|
2356
2419
|
TruncationWarningEvent2.Reason = {
|
|
2357
|
-
ContextWindowBudget: "context_window_budget"
|
|
2420
|
+
ContextWindowBudget: "context_window_budget",
|
|
2421
|
+
NoGroundableContent: "no_groundable_content",
|
|
2422
|
+
ContextWindowBudgetAndNoContent: "context_window_budget_and_no_content"
|
|
2358
2423
|
};
|
|
2359
2424
|
})(TruncationWarningEvent || (TruncationWarningEvent = {}));
|
|
2360
2425
|
var UserExistsResponse;
|
|
@@ -2369,8 +2434,7 @@ var UserRequest;
|
|
|
2369
2434
|
((UserRequest2) => {
|
|
2370
2435
|
UserRequest2.Status = {
|
|
2371
2436
|
Active: "ACTIVE",
|
|
2372
|
-
Suspended: "SUSPENDED"
|
|
2373
|
-
Pending: "PENDING"
|
|
2437
|
+
Suspended: "SUSPENDED"
|
|
2374
2438
|
};
|
|
2375
2439
|
UserRequest2.Type = {
|
|
2376
2440
|
Human: "HUMAN",
|
|
@@ -3838,6 +3902,16 @@ function mergeHeaders(...headersArray) {
|
|
|
3838
3902
|
}
|
|
3839
3903
|
return result;
|
|
3840
3904
|
}
|
|
3905
|
+
function mergeOnlyDefinedHeaders(...headersArray) {
|
|
3906
|
+
const result = {};
|
|
3907
|
+
for (const [key, value] of headersArray.filter((headers) => headers != null).flatMap((headers) => Object.entries(headers))) {
|
|
3908
|
+
const insensitiveKey = key.toLowerCase();
|
|
3909
|
+
if (value != null) {
|
|
3910
|
+
result[insensitiveKey] = value;
|
|
3911
|
+
}
|
|
3912
|
+
}
|
|
3913
|
+
return result;
|
|
3914
|
+
}
|
|
3841
3915
|
function normalizeClientOptions(options) {
|
|
3842
3916
|
const headers = mergeHeaders(
|
|
3843
3917
|
{
|
|
@@ -4108,7 +4182,7 @@ var AuthClient = class {
|
|
|
4108
4182
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/admin/keys/scoped");
|
|
4109
4183
|
}
|
|
4110
4184
|
/**
|
|
4111
|
-
* Creates a scoped API key (an `ssk_*` secret) that inherits its permissions from an existing access profile in your account. The call is idempotent on the combination of tenant, context, user, and key name: re-issuing the same request returns the existing key WITHOUT re-disclosing its raw secret. The raw key is returned ONLY in this response — store it securely, as it cannot be retrieved again. Requires the `keys:c` scope. If you use a scoped credential, `keys:c` alone is not sufficient: because the minted key is durably bound to the profile you name, the profile's effective scopes may not exceed your own, and you may only mint against a profile whose `identityOverrides` values your own identity holds. Minting a key bound to your OWN principal needs nothing further; minting one bound to a DIFFERENT principal additionally requires the `delegate-mint` capability (`granted_capabilities`) on your credential — without it the request is refused. A root API key (`sk_`) is exempt from all three bounds.
|
|
4185
|
+
* Creates a scoped API key (an `ssk_*` secret) that inherits its permissions from an existing access profile in your account. The call is idempotent on the combination of tenant, context, user, and key name: re-issuing the same request returns the existing key WITHOUT re-disclosing its raw secret. The raw key is returned ONLY in this response — store it securely, as it cannot be retrieved again. Requires the `keys:c` scope. If you use a scoped credential, `keys:c` alone is not sufficient: because the minted key is durably bound to the profile you name, the profile's effective scopes may not exceed your own, and you may only mint against a profile whose `identityOverrides` values your own identity holds. Minting a key bound to your OWN principal needs nothing further; minting one bound to a DIFFERENT principal additionally requires the `delegate-mint` capability (`granted_capabilities`) on your credential — without it the request is refused. A root API key (`sk_`) is exempt from all three bounds. The profile you name must be `active` AND the user you name must not be `SUSPENDED`: either is refused with `409`, and no root exemption applies to that. Both refusals stop ISSUANCE only. Suspending the profile additionally stops credentials already bound to it, within about five minutes; suspending the user does not stop their existing keys at all. A `PENDING` user is fine.
|
|
4112
4186
|
*
|
|
4113
4187
|
* @param {Vectros.CreateScopedKeyRequest} request
|
|
4114
4188
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -4116,6 +4190,7 @@ var AuthClient = class {
|
|
|
4116
4190
|
* @throws {@link Vectros.BadRequestError}
|
|
4117
4191
|
* @throws {@link Vectros.ForbiddenError}
|
|
4118
4192
|
* @throws {@link Vectros.NotFoundError}
|
|
4193
|
+
* @throws {@link Vectros.ConflictError}
|
|
4119
4194
|
* @throws {@link Vectros.TooManyRequestsError}
|
|
4120
4195
|
*
|
|
4121
4196
|
* @example
|
|
@@ -4164,6 +4239,8 @@ var AuthClient = class {
|
|
|
4164
4239
|
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
4165
4240
|
case 404:
|
|
4166
4241
|
throw new NotFoundError(_response.error.body, _response.rawResponse);
|
|
4242
|
+
case 409:
|
|
4243
|
+
throw new ConflictError(_response.error.body, _response.rawResponse);
|
|
4167
4244
|
case 429:
|
|
4168
4245
|
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
4169
4246
|
default:
|
|
@@ -4575,7 +4652,7 @@ var AuthClient = class {
|
|
|
4575
4652
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/app-contexts");
|
|
4576
4653
|
}
|
|
4577
4654
|
/**
|
|
4578
|
-
* Creates a new app context. This call is idempotent by `contextId`: if an app context with the same `contextId` already exists, the existing app context is returned (with status 200) instead of creating a duplicate. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing context was returned) tells the two apart. To overwrite an existing context's `name`/`description` instead of returning it unchanged, set `?upsert=true` (this also requires the `app-contexts:u` scope). The reserved `contextId` value `vectros-admin` cannot be created through this endpoint; it is provisioned automatically for your account. Requires the `app-contexts:c` scope.
|
|
4655
|
+
* Creates a new app context. This call is idempotent by `contextId`: if an app context with the same `contextId` already exists, the existing app context is returned (with status 200) instead of creating a duplicate. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing context was returned) tells the two apart. To overwrite an existing context's `name`/`description`/`companyName` instead of returning it unchanged, set `?upsert=true` (this also requires the `app-contexts:u` scope). The reserved `contextId` value `vectros-admin` cannot be created through this endpoint; it is provisioned automatically for your account. Requires the `app-contexts:c` scope.
|
|
4579
4656
|
*
|
|
4580
4657
|
* @param {Vectros.CreateAppContextRequest} request
|
|
4581
4658
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -5059,7 +5136,7 @@ var AuthClient = class {
|
|
|
5059
5136
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/app-contexts/{contextId}");
|
|
5060
5137
|
}
|
|
5061
5138
|
/**
|
|
5062
|
-
* Updates the name and/or
|
|
5139
|
+
* Updates the name, description, and/or companyName of an app context. This is a partial update: any field you omit (or send as null) keeps its existing value. The `contextId` is immutable and is taken from the URL path, so any `contextId` in the request body is ignored. Requires the `app-contexts:u` scope.
|
|
5063
5140
|
*
|
|
5064
5141
|
* @param {Vectros.UpdateAppContextRequest} request
|
|
5065
5142
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -5541,7 +5618,7 @@ var AuthClient = class {
|
|
|
5541
5618
|
);
|
|
5542
5619
|
}
|
|
5543
5620
|
/**
|
|
5544
|
-
* Returns full usage detail for the requested calendar month, broken down by category (search, documents, and
|
|
5621
|
+
* Returns full usage detail for the requested calendar month, broken down by category (search, documents, records, and trigger-script execution time) with per-category credit estimates and a split between your live and test environments. Defaults to the current month when `year` and `month` are omitted. Requires the `billing:r` scope on scoped tokens; API keys always have access. **A token confined to a single app context sees only that context's usage**: totals, the environment split, and the `contexts` breakdown narrow to it, and the environment your context is not bound to is omitted (`null`), not zeroed. Only a token with cross-context reach sees your full account-wide totals. Two exceptions to the narrowing, since they have no per-context breakdown to narrow to: `reads.calls.used`/`reads.dataOut.bytes` (metered per account, not per context) read as `0` for a confined token rather than a narrowed figure — the corresponding overage-credit charge fields narrow correctly; and `credits.limit` stays your whole plan's ceiling, so `credits.remaining` may overstate the account's true remaining room. The `execution` section narrows correctly for a confined token, like the other per-context charge fields — but note its credits carry sub-credit remainders forward across your whole account for the period, so compare `execution.creditsMilli` against your account-wide figure rather than recomputing it from one context's `billableMillis`.
|
|
5545
5622
|
*
|
|
5546
5623
|
* @param {Vectros.GetUsageRequest} request
|
|
5547
5624
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -7675,7 +7752,7 @@ var FoldersClient = class {
|
|
|
7675
7752
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "PUT", "/v1/folders/{id}");
|
|
7676
7753
|
}
|
|
7677
7754
|
/**
|
|
7678
|
-
* Permanently deletes a folder. The folder must be empty
|
|
7755
|
+
* Permanently deletes a folder. The folder must be empty — it must contain no documents, no records, and no sub-folders — and must not be protected. Your context's root folder is protected and cannot be deleted. Requires the `folders:d` scope.
|
|
7679
7756
|
*
|
|
7680
7757
|
* @param {Vectros.DeleteFolderRequest} request
|
|
7681
7758
|
* @param {FoldersClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -8084,11 +8161,12 @@ var IdentityClient = class {
|
|
|
8084
8161
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/entities/{namespace}/{id}");
|
|
8085
8162
|
}
|
|
8086
8163
|
/**
|
|
8087
|
-
* Updates the mutable fields of an entity. Omitted fields are preserved (a null value does not clear a field), and the `payload` object is replaced in full when supplied. Providing `scopes` replaces the entity's parent edges. Requires the `entities:u:<namespace>` scope.
|
|
8164
|
+
* Updates the mutable fields of an entity. Omitted fields are preserved (a null value does not clear a field), and the `payload` object is replaced in full when supplied. Providing `scopes` replaces the entity's parent edges. Supplying a different `externalId` re-points the entity's identifier; it must still be unused in this namespace and app context. Requires the `entities:u:<namespace>` scope.
|
|
8088
8165
|
*
|
|
8089
8166
|
* @param {Vectros.UpdateEntityRequest} request
|
|
8090
8167
|
* @param {IdentityClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
8091
8168
|
*
|
|
8169
|
+
* @throws {@link Vectros.BadRequestError}
|
|
8092
8170
|
* @throws {@link Vectros.NotFoundError}
|
|
8093
8171
|
* @throws {@link Vectros.TooManyRequestsError}
|
|
8094
8172
|
*
|
|
@@ -8138,6 +8216,8 @@ var IdentityClient = class {
|
|
|
8138
8216
|
}
|
|
8139
8217
|
if (_response.error.reason === "status-code") {
|
|
8140
8218
|
switch (_response.error.statusCode) {
|
|
8219
|
+
case 400:
|
|
8220
|
+
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
8141
8221
|
case 404:
|
|
8142
8222
|
throw new NotFoundError(_response.error.body, _response.rawResponse);
|
|
8143
8223
|
case 429:
|
|
@@ -9362,7 +9442,7 @@ var InferenceClient = class {
|
|
|
9362
9442
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/documents/{id}/ask");
|
|
9363
9443
|
}
|
|
9364
9444
|
/**
|
|
9365
|
-
* Runs hybrid search over your indexed content, then streams a model answer grounded in the top results. The SSE stream emits a `search_results` event first (carrying the matched results and their metadata), an optional `truncation_warning` if
|
|
9445
|
+
* Runs hybrid search over your indexed content, then streams a model answer grounded in the top results. The SSE stream emits a `search_results` event first (carrying the matched results and their metadata), an optional `truncation_warning` if some retrieved results were dropped before the prompt was built — either because they didn't fit the model's context window or because a result had no groundable text to include — then `content_delta` chunks, and finally a terminal `done` event. Requires the `inference:r` scope.
|
|
9366
9446
|
*/
|
|
9367
9447
|
ragInference(request, requestOptions) {
|
|
9368
9448
|
return HttpResponsePromise.fromPromise(this.__ragInference(request, requestOptions));
|
|
@@ -9557,14 +9637,18 @@ var RecordsClient = class {
|
|
|
9557
9637
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/records/lookup/batch");
|
|
9558
9638
|
}
|
|
9559
9639
|
/**
|
|
9560
|
-
*
|
|
9640
|
+
* Writes up to 50 records (or 50 with `atomicity: all_or_nothing`) in a single request. Each item in `items` has exactly the same shape, and goes through exactly the same validation, authorization and idempotency rules, as the body of a single `POST /v1/records` — including schema validation, `externalId` idempotency, unique-field enforcement, and the `records:c:<type>` scope check, which is applied per item against that item's own record type. A batch is not a way to write a type your credential could not write one at a time.
|
|
9641
|
+
*
|
|
9642
|
+
* `atomicity` selects how the batch commits. `best_effort` (the default) writes each item independently: some can succeed while others fail, and each item's own outcome is reported. `all_or_nothing` commits every item in one transaction — if any item fails, no record is created or updated at all, and every item that was itself fine reports `not_committed` while the ones that failed report why. Because the transaction is bounded by the number of underlying storage rows rather than by the number of records, a large `all_or_nothing` batch can be refused as too large to commit atomically even when it is within the item limit; the error says so, and nothing is written when it happens.
|
|
9643
|
+
*
|
|
9644
|
+
* The response is HTTP 200 whenever the batch was processed at all — including when every item failed — so always inspect `results` rather than relying on the status code. Match each result to the item you sent using its `index`. Requires the `records:c` scope.
|
|
9561
9645
|
*
|
|
9562
9646
|
* @param {Vectros.BatchWriteRequest} request
|
|
9563
9647
|
* @param {RecordsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
9564
9648
|
*
|
|
9649
|
+
* @throws {@link Vectros.BadRequestError}
|
|
9565
9650
|
* @throws {@link Vectros.ForbiddenError}
|
|
9566
9651
|
* @throws {@link Vectros.TooManyRequestsError}
|
|
9567
|
-
* @throws {@link Vectros.NotImplementedError}
|
|
9568
9652
|
*
|
|
9569
9653
|
* @example
|
|
9570
9654
|
* await client.records.batchWriteRecords()
|
|
@@ -9573,6 +9657,11 @@ var RecordsClient = class {
|
|
|
9573
9657
|
return HttpResponsePromise.fromPromise(this.__batchWriteRecords(request, requestOptions));
|
|
9574
9658
|
}
|
|
9575
9659
|
async __batchWriteRecords(request = {}, requestOptions) {
|
|
9660
|
+
const { upsert, allowClear, ..._body } = request;
|
|
9661
|
+
const _queryParams = {
|
|
9662
|
+
upsert,
|
|
9663
|
+
allowClear
|
|
9664
|
+
};
|
|
9576
9665
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
9577
9666
|
const _headers = mergeHeaders(
|
|
9578
9667
|
_authRequest.headers,
|
|
@@ -9587,9 +9676,9 @@ var RecordsClient = class {
|
|
|
9587
9676
|
method: "POST",
|
|
9588
9677
|
headers: _headers,
|
|
9589
9678
|
contentType: "application/json",
|
|
9590
|
-
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
9679
|
+
queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
|
|
9591
9680
|
requestType: "json",
|
|
9592
|
-
body:
|
|
9681
|
+
body: _body,
|
|
9593
9682
|
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
9594
9683
|
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
9595
9684
|
abortSignal: requestOptions?.abortSignal,
|
|
@@ -9601,12 +9690,12 @@ var RecordsClient = class {
|
|
|
9601
9690
|
}
|
|
9602
9691
|
if (_response.error.reason === "status-code") {
|
|
9603
9692
|
switch (_response.error.statusCode) {
|
|
9693
|
+
case 400:
|
|
9694
|
+
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
9604
9695
|
case 403:
|
|
9605
9696
|
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
9606
9697
|
case 429:
|
|
9607
9698
|
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
9608
|
-
case 501:
|
|
9609
|
-
throw new NotImplementedError(_response.error.body, _response.rawResponse);
|
|
9610
9699
|
default:
|
|
9611
9700
|
throw new VectrosError({
|
|
9612
9701
|
statusCode: _response.error.statusCode,
|
|
@@ -10509,7 +10598,7 @@ var SchemasClient = class {
|
|
|
10509
10598
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/schemas/{id}");
|
|
10510
10599
|
}
|
|
10511
10600
|
/**
|
|
10512
|
-
* Updates a record schema. Fields you omit are preserved; `typeName` is immutable and cannot be changed. Collection fields (`fields`, `lookupFields`, `renderHints`, `capabilities`) are replaced in full when supplied. Requires the `schemas:u` scope.
|
|
10601
|
+
* Updates a record schema. Fields you omit are preserved; `typeName` is immutable and cannot be changed. Collection fields (`fields`, `lookupFields`, `renderHints`, `capabilities`) are replaced in full when supplied. Because `capabilities` is replaced in full, omitting `triggersEnabled` clears it — the request is refused with 409 if that would disable triggers while trigger rules still fire off this schema, so delete those rules first. Requires the `schemas:u` scope.
|
|
10513
10602
|
*
|
|
10514
10603
|
* @param {Vectros.UpdateSchemaRequest} request
|
|
10515
10604
|
* @param {SchemasClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -10517,6 +10606,7 @@ var SchemasClient = class {
|
|
|
10517
10606
|
* @throws {@link Vectros.BadRequestError}
|
|
10518
10607
|
* @throws {@link Vectros.ForbiddenError}
|
|
10519
10608
|
* @throws {@link Vectros.NotFoundError}
|
|
10609
|
+
* @throws {@link Vectros.ConflictError}
|
|
10520
10610
|
* @throws {@link Vectros.TooManyRequestsError}
|
|
10521
10611
|
*
|
|
10522
10612
|
* @example
|
|
@@ -10568,6 +10658,8 @@ var SchemasClient = class {
|
|
|
10568
10658
|
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
10569
10659
|
case 404:
|
|
10570
10660
|
throw new NotFoundError(_response.error.body, _response.rawResponse);
|
|
10661
|
+
case 409:
|
|
10662
|
+
throw new ConflictError(_response.error.body, _response.rawResponse);
|
|
10571
10663
|
case 429:
|
|
10572
10664
|
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
10573
10665
|
default:
|
|
@@ -10581,7 +10673,7 @@ var SchemasClient = class {
|
|
|
10581
10673
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "PUT", "/v1/schemas/{id}");
|
|
10582
10674
|
}
|
|
10583
10675
|
/**
|
|
10584
|
-
* 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. Requires the `schemas:d` scope.
|
|
10676
|
+
* 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.
|
|
10585
10677
|
*
|
|
10586
10678
|
* @param {Vectros.DeleteSchemaRequest} request
|
|
10587
10679
|
* @param {SchemasClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -10703,46 +10795,70 @@ var SchemasClient = class {
|
|
|
10703
10795
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/schemas/{id}/versions");
|
|
10704
10796
|
}
|
|
10705
10797
|
};
|
|
10706
|
-
var
|
|
10798
|
+
var ScriptsClient = class {
|
|
10707
10799
|
constructor(options) {
|
|
10708
10800
|
this._options = normalizeClientOptionsWithAuth(options);
|
|
10709
10801
|
}
|
|
10710
10802
|
/**
|
|
10711
|
-
* Runs a
|
|
10803
|
+
* Runs a script you previously pushed to `POST /v1/scripts`, under your own credential, and returns what it returned. Every write the script makes is buffered and committed as ONE transaction after the script finishes: either all of them land or none do, and the response is sent only after they have committed — so everything in `result` is committed state or the script's own computation over it. On any failure the body is the standard error envelope and nothing the script staged is committed: its rows are written only when the transaction commits, and a document body or a large payload is DECIDED at that point and WRITTEN to storage only after the commit, so an aborted execution leaves nothing behind. The one residual sits after a successful commit, outside this response: if storing a body fails then, the row exists and the body does not until the platform's repair lands — so a `200` guarantees the rows, and a document's text should be read before it is relied on.
|
|
10712
10804
|
*
|
|
10713
|
-
*
|
|
10714
|
-
*
|
|
10805
|
+
* **Permission.** Requires the `scripts:x` scope — the permission to *execute* — separate from the `scripts:c` permission to push scripts. Bare `scripts:x` grants every script in the context; `scripts:x:<name>` grants exactly that script name, every version of it. Execution discloses no source, so `scripts:r` is not needed. Inside the script, every read and write is enforced by your credential's data scopes exactly as the equivalent REST call would be — `scripts:x` alone can read and write nothing.
|
|
10806
|
+
*
|
|
10807
|
+
* **Input.** Your script sees `input.event` (the literal `"execute"`), `input.userId` (your credential's user id, or null), and `input.params` (the `input` object you sent, verbatim). That is the same `input` grammar a trigger rule uses, so one script can serve both by branching on `input.event`. `params` is not validated — a script that needs a shape checks for it and throws; the script's `declaredInputContract` is documentation for readers and is not enforced.
|
|
10808
|
+
*
|
|
10809
|
+
* **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`).
|
|
10810
|
+
*
|
|
10811
|
+
* **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 yet show a row it created — you hold that row's id from `create()`.
|
|
10812
|
+
*
|
|
10813
|
+
* **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.
|
|
10814
|
+
*
|
|
10815
|
+
* @param {Vectros.ScriptExecuteRequest} request
|
|
10816
|
+
* @param {ScriptsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
10715
10817
|
*
|
|
10716
10818
|
* @throws {@link Vectros.BadRequestError}
|
|
10819
|
+
* @throws {@link Vectros.PaymentRequiredError}
|
|
10717
10820
|
* @throws {@link Vectros.ForbiddenError}
|
|
10821
|
+
* @throws {@link Vectros.NotFoundError}
|
|
10822
|
+
* @throws {@link Vectros.ConflictError}
|
|
10823
|
+
* @throws {@link Vectros.ContentTooLargeError}
|
|
10824
|
+
* @throws {@link Vectros.UnprocessableEntityError}
|
|
10718
10825
|
* @throws {@link Vectros.TooManyRequestsError}
|
|
10826
|
+
* @throws {@link Vectros.InternalServerError}
|
|
10827
|
+
* @throws {@link Vectros.ServiceUnavailableError}
|
|
10828
|
+
* @throws {@link Vectros.GatewayTimeoutError}
|
|
10719
10829
|
*
|
|
10720
10830
|
* @example
|
|
10721
|
-
* await client.
|
|
10722
|
-
*
|
|
10831
|
+
* await client.scripts.executeScript({
|
|
10832
|
+
* "Idempotency-Key": "order-8412-attempt",
|
|
10833
|
+
* scriptRef: {
|
|
10834
|
+
* name: "notify-assignee",
|
|
10835
|
+
* version: "3"
|
|
10836
|
+
* }
|
|
10723
10837
|
* })
|
|
10724
10838
|
*/
|
|
10725
|
-
|
|
10726
|
-
return HttpResponsePromise.fromPromise(this.
|
|
10839
|
+
executeScript(request, requestOptions) {
|
|
10840
|
+
return HttpResponsePromise.fromPromise(this.__executeScript(request, requestOptions));
|
|
10727
10841
|
}
|
|
10728
|
-
async
|
|
10842
|
+
async __executeScript(request, requestOptions) {
|
|
10843
|
+
const { "Idempotency-Key": idempotencyKey, ..._body } = request;
|
|
10729
10844
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
10730
10845
|
const _headers = mergeHeaders(
|
|
10731
10846
|
_authRequest.headers,
|
|
10732
10847
|
this._options?.headers,
|
|
10848
|
+
mergeOnlyDefinedHeaders({ "Idempotency-Key": idempotencyKey }),
|
|
10733
10849
|
requestOptions?.headers
|
|
10734
10850
|
);
|
|
10735
10851
|
const _response = await fetcher({
|
|
10736
10852
|
url: url_exports.join(
|
|
10737
10853
|
await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
|
|
10738
|
-
"v1/
|
|
10854
|
+
"v1/scripts/execute"
|
|
10739
10855
|
),
|
|
10740
10856
|
method: "POST",
|
|
10741
10857
|
headers: _headers,
|
|
10742
10858
|
contentType: "application/json",
|
|
10743
10859
|
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
10744
10860
|
requestType: "json",
|
|
10745
|
-
body:
|
|
10861
|
+
body: _body,
|
|
10746
10862
|
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
10747
10863
|
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
10748
10864
|
abortSignal: requestOptions?.abortSignal,
|
|
@@ -10756,10 +10872,26 @@ var SearchClient = class {
|
|
|
10756
10872
|
switch (_response.error.statusCode) {
|
|
10757
10873
|
case 400:
|
|
10758
10874
|
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
10875
|
+
case 402:
|
|
10876
|
+
throw new PaymentRequiredError(_response.error.body, _response.rawResponse);
|
|
10759
10877
|
case 403:
|
|
10760
10878
|
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
10879
|
+
case 404:
|
|
10880
|
+
throw new NotFoundError(_response.error.body, _response.rawResponse);
|
|
10881
|
+
case 409:
|
|
10882
|
+
throw new ConflictError(_response.error.body, _response.rawResponse);
|
|
10883
|
+
case 413:
|
|
10884
|
+
throw new ContentTooLargeError(_response.error.body, _response.rawResponse);
|
|
10885
|
+
case 422:
|
|
10886
|
+
throw new UnprocessableEntityError(_response.error.body, _response.rawResponse);
|
|
10761
10887
|
case 429:
|
|
10762
10888
|
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
10889
|
+
case 500:
|
|
10890
|
+
throw new InternalServerError(_response.error.body, _response.rawResponse);
|
|
10891
|
+
case 503:
|
|
10892
|
+
throw new ServiceUnavailableError(_response.error.body, _response.rawResponse);
|
|
10893
|
+
case 504:
|
|
10894
|
+
throw new GatewayTimeoutError(_response.error.body, _response.rawResponse);
|
|
10763
10895
|
default:
|
|
10764
10896
|
throw new VectrosError({
|
|
10765
10897
|
statusCode: _response.error.statusCode,
|
|
@@ -10768,71 +10900,834 @@ var SearchClient = class {
|
|
|
10768
10900
|
});
|
|
10769
10901
|
}
|
|
10770
10902
|
}
|
|
10771
|
-
return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/
|
|
10772
|
-
}
|
|
10773
|
-
};
|
|
10774
|
-
var VectrosClient = class {
|
|
10775
|
-
constructor(options) {
|
|
10776
|
-
this._options = normalizeClientOptionsWithAuth(options);
|
|
10777
|
-
}
|
|
10778
|
-
get auth() {
|
|
10779
|
-
return this._auth ?? (this._auth = new AuthClient(this._options));
|
|
10780
|
-
}
|
|
10781
|
-
get documents() {
|
|
10782
|
-
return this._documents ?? (this._documents = new DocumentsClient(this._options));
|
|
10783
|
-
}
|
|
10784
|
-
get identity() {
|
|
10785
|
-
return this._identity ?? (this._identity = new IdentityClient(this._options));
|
|
10786
|
-
}
|
|
10787
|
-
get compliance() {
|
|
10788
|
-
return this._compliance ?? (this._compliance = new ComplianceClient(this._options));
|
|
10789
|
-
}
|
|
10790
|
-
get folders() {
|
|
10791
|
-
return this._folders ?? (this._folders = new FoldersClient(this._options));
|
|
10792
|
-
}
|
|
10793
|
-
get inference() {
|
|
10794
|
-
return this._inference ?? (this._inference = new InferenceClient(this._options));
|
|
10903
|
+
return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/scripts/execute");
|
|
10795
10904
|
}
|
|
10796
|
-
|
|
10797
|
-
|
|
10798
|
-
|
|
10799
|
-
|
|
10800
|
-
|
|
10905
|
+
/**
|
|
10906
|
+
* 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.
|
|
10907
|
+
*
|
|
10908
|
+
* @param {Vectros.ListScriptsRequest} request
|
|
10909
|
+
* @param {ScriptsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
10910
|
+
*
|
|
10911
|
+
* @example
|
|
10912
|
+
* await client.scripts.listScripts({
|
|
10913
|
+
* name: "notify-assignee",
|
|
10914
|
+
* startFrom: "b3BhcXVlLWN1cnNvci1mcm9tLXRoZS1wcmV2aW91cy1wYWdl"
|
|
10915
|
+
* })
|
|
10916
|
+
*/
|
|
10917
|
+
listScripts(request = {}, requestOptions) {
|
|
10918
|
+
return HttpResponsePromise.fromPromise(this.__listScripts(request, requestOptions));
|
|
10801
10919
|
}
|
|
10802
|
-
|
|
10803
|
-
|
|
10920
|
+
async __listScripts(request = {}, requestOptions) {
|
|
10921
|
+
const { name, startFrom, limit } = request;
|
|
10922
|
+
const _queryParams = {
|
|
10923
|
+
name,
|
|
10924
|
+
startFrom,
|
|
10925
|
+
limit
|
|
10926
|
+
};
|
|
10927
|
+
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
10928
|
+
const _headers = mergeHeaders(
|
|
10929
|
+
_authRequest.headers,
|
|
10930
|
+
this._options?.headers,
|
|
10931
|
+
requestOptions?.headers
|
|
10932
|
+
);
|
|
10933
|
+
const _response = await fetcher({
|
|
10934
|
+
url: url_exports.join(
|
|
10935
|
+
await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
|
|
10936
|
+
"v1/scripts"
|
|
10937
|
+
),
|
|
10938
|
+
method: "GET",
|
|
10939
|
+
headers: _headers,
|
|
10940
|
+
queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
|
|
10941
|
+
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
10942
|
+
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
10943
|
+
abortSignal: requestOptions?.abortSignal,
|
|
10944
|
+
fetchFn: this._options?.fetch,
|
|
10945
|
+
logging: this._options.logging
|
|
10946
|
+
});
|
|
10947
|
+
if (_response.ok) {
|
|
10948
|
+
return { data: _response.body, rawResponse: _response.rawResponse };
|
|
10949
|
+
}
|
|
10950
|
+
if (_response.error.reason === "status-code") {
|
|
10951
|
+
throw new VectrosError({
|
|
10952
|
+
statusCode: _response.error.statusCode,
|
|
10953
|
+
body: _response.error.body,
|
|
10954
|
+
rawResponse: _response.rawResponse
|
|
10955
|
+
});
|
|
10956
|
+
}
|
|
10957
|
+
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/scripts");
|
|
10804
10958
|
}
|
|
10805
10959
|
/**
|
|
10806
|
-
*
|
|
10807
|
-
* This is useful for making requests to endpoints not yet supported in the SDK.
|
|
10808
|
-
* The input can be a URL string, URL object, or Request object. Relative paths are resolved against the configured base URL.
|
|
10960
|
+
* 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.
|
|
10809
10961
|
*
|
|
10810
|
-
* @param {
|
|
10811
|
-
* @param {
|
|
10812
|
-
*
|
|
10813
|
-
* @
|
|
10962
|
+
* @param {Vectros.ScriptRequest} request
|
|
10963
|
+
* @param {ScriptsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
10964
|
+
*
|
|
10965
|
+
* @throws {@link Vectros.BadRequestError}
|
|
10966
|
+
* @throws {@link Vectros.ForbiddenError}
|
|
10967
|
+
* @throws {@link Vectros.ConflictError}
|
|
10968
|
+
* @throws {@link Vectros.TooManyRequestsError}
|
|
10969
|
+
*
|
|
10970
|
+
* @example
|
|
10971
|
+
* await client.scripts.createScript({
|
|
10972
|
+
* name: "notify-assignee",
|
|
10973
|
+
* source: "export default function(input) { return { ok: true }; }"
|
|
10974
|
+
* })
|
|
10814
10975
|
*/
|
|
10815
|
-
|
|
10816
|
-
return
|
|
10817
|
-
|
|
10818
|
-
|
|
10819
|
-
|
|
10820
|
-
|
|
10821
|
-
|
|
10822
|
-
|
|
10823
|
-
|
|
10824
|
-
fetch: this._options.fetch,
|
|
10825
|
-
logging: this._options.logging,
|
|
10826
|
-
getAuthHeaders: async () => (await this._options.authProvider.getAuthRequest()).headers
|
|
10827
|
-
},
|
|
10828
|
-
requestOptions
|
|
10976
|
+
createScript(request, requestOptions) {
|
|
10977
|
+
return HttpResponsePromise.fromPromise(this.__createScript(request, requestOptions));
|
|
10978
|
+
}
|
|
10979
|
+
async __createScript(request, requestOptions) {
|
|
10980
|
+
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
10981
|
+
const _headers = mergeHeaders(
|
|
10982
|
+
_authRequest.headers,
|
|
10983
|
+
this._options?.headers,
|
|
10984
|
+
requestOptions?.headers
|
|
10829
10985
|
);
|
|
10986
|
+
const _response = await fetcher({
|
|
10987
|
+
url: url_exports.join(
|
|
10988
|
+
await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
|
|
10989
|
+
"v1/scripts"
|
|
10990
|
+
),
|
|
10991
|
+
method: "POST",
|
|
10992
|
+
headers: _headers,
|
|
10993
|
+
contentType: "application/json",
|
|
10994
|
+
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
10995
|
+
requestType: "json",
|
|
10996
|
+
body: request,
|
|
10997
|
+
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
10998
|
+
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
10999
|
+
abortSignal: requestOptions?.abortSignal,
|
|
11000
|
+
fetchFn: this._options?.fetch,
|
|
11001
|
+
logging: this._options.logging
|
|
11002
|
+
});
|
|
11003
|
+
if (_response.ok) {
|
|
11004
|
+
return { data: _response.body, rawResponse: _response.rawResponse };
|
|
11005
|
+
}
|
|
11006
|
+
if (_response.error.reason === "status-code") {
|
|
11007
|
+
switch (_response.error.statusCode) {
|
|
11008
|
+
case 400:
|
|
11009
|
+
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
11010
|
+
case 403:
|
|
11011
|
+
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
11012
|
+
case 409:
|
|
11013
|
+
throw new ConflictError(_response.error.body, _response.rawResponse);
|
|
11014
|
+
case 429:
|
|
11015
|
+
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
11016
|
+
default:
|
|
11017
|
+
throw new VectrosError({
|
|
11018
|
+
statusCode: _response.error.statusCode,
|
|
11019
|
+
body: _response.error.body,
|
|
11020
|
+
rawResponse: _response.rawResponse
|
|
11021
|
+
});
|
|
11022
|
+
}
|
|
11023
|
+
}
|
|
11024
|
+
return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/scripts");
|
|
10830
11025
|
}
|
|
10831
|
-
|
|
10832
|
-
|
|
10833
|
-
|
|
10834
|
-
|
|
10835
|
-
|
|
11026
|
+
/**
|
|
11027
|
+
* Retrieves a single script version by its ID. Requires the `scripts:r` scope.
|
|
11028
|
+
*
|
|
11029
|
+
* @param {Vectros.GetScriptRequest} request
|
|
11030
|
+
* @param {ScriptsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
11031
|
+
*
|
|
11032
|
+
* @throws {@link Vectros.NotFoundError}
|
|
11033
|
+
*
|
|
11034
|
+
* @example
|
|
11035
|
+
* await client.scripts.getScript({
|
|
11036
|
+
* id: "id"
|
|
11037
|
+
* })
|
|
11038
|
+
*/
|
|
11039
|
+
getScript(request, requestOptions) {
|
|
11040
|
+
return HttpResponsePromise.fromPromise(this.__getScript(request, requestOptions));
|
|
11041
|
+
}
|
|
11042
|
+
async __getScript(request, requestOptions) {
|
|
11043
|
+
const { id } = request;
|
|
11044
|
+
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
11045
|
+
const _headers = mergeHeaders(
|
|
11046
|
+
_authRequest.headers,
|
|
11047
|
+
this._options?.headers,
|
|
11048
|
+
requestOptions?.headers
|
|
11049
|
+
);
|
|
11050
|
+
const _response = await fetcher({
|
|
11051
|
+
url: url_exports.join(
|
|
11052
|
+
await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
|
|
11053
|
+
`v1/scripts/${url_exports.encodePathParam(id)}`
|
|
11054
|
+
),
|
|
11055
|
+
method: "GET",
|
|
11056
|
+
headers: _headers,
|
|
11057
|
+
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
11058
|
+
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
11059
|
+
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
11060
|
+
abortSignal: requestOptions?.abortSignal,
|
|
11061
|
+
fetchFn: this._options?.fetch,
|
|
11062
|
+
logging: this._options.logging
|
|
11063
|
+
});
|
|
11064
|
+
if (_response.ok) {
|
|
11065
|
+
return { data: _response.body, rawResponse: _response.rawResponse };
|
|
11066
|
+
}
|
|
11067
|
+
if (_response.error.reason === "status-code") {
|
|
11068
|
+
switch (_response.error.statusCode) {
|
|
11069
|
+
case 404:
|
|
11070
|
+
throw new NotFoundError(_response.error.body, _response.rawResponse);
|
|
11071
|
+
default:
|
|
11072
|
+
throw new VectrosError({
|
|
11073
|
+
statusCode: _response.error.statusCode,
|
|
11074
|
+
body: _response.error.body,
|
|
11075
|
+
rawResponse: _response.rawResponse
|
|
11076
|
+
});
|
|
11077
|
+
}
|
|
11078
|
+
}
|
|
11079
|
+
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/scripts/{id}");
|
|
11080
|
+
}
|
|
11081
|
+
/**
|
|
11082
|
+
* Scripts are immutable per version; this endpoint always returns a 400 regardless of scope or credential — push a new version via `POST /v1/scripts` instead.
|
|
11083
|
+
*
|
|
11084
|
+
* @param {Vectros.UpdateScriptRequest} request
|
|
11085
|
+
* @param {ScriptsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
11086
|
+
*
|
|
11087
|
+
* @throws {@link Vectros.BadRequestError}
|
|
11088
|
+
* @throws {@link Vectros.TooManyRequestsError}
|
|
11089
|
+
*
|
|
11090
|
+
* @example
|
|
11091
|
+
* await client.scripts.updateScript({
|
|
11092
|
+
* id: "id",
|
|
11093
|
+
* body: {
|
|
11094
|
+
* name: "notify-assignee",
|
|
11095
|
+
* source: "export default function(input) { return { ok: true }; }"
|
|
11096
|
+
* }
|
|
11097
|
+
* })
|
|
11098
|
+
*/
|
|
11099
|
+
updateScript(request, requestOptions) {
|
|
11100
|
+
return HttpResponsePromise.fromPromise(this.__updateScript(request, requestOptions));
|
|
11101
|
+
}
|
|
11102
|
+
async __updateScript(request, requestOptions) {
|
|
11103
|
+
const { id, body: _body } = request;
|
|
11104
|
+
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
11105
|
+
const _headers = mergeHeaders(
|
|
11106
|
+
_authRequest.headers,
|
|
11107
|
+
this._options?.headers,
|
|
11108
|
+
requestOptions?.headers
|
|
11109
|
+
);
|
|
11110
|
+
const _response = await fetcher({
|
|
11111
|
+
url: url_exports.join(
|
|
11112
|
+
await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
|
|
11113
|
+
`v1/scripts/${url_exports.encodePathParam(id)}`
|
|
11114
|
+
),
|
|
11115
|
+
method: "PUT",
|
|
11116
|
+
headers: _headers,
|
|
11117
|
+
contentType: "application/json",
|
|
11118
|
+
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
11119
|
+
requestType: "json",
|
|
11120
|
+
body: _body,
|
|
11121
|
+
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
11122
|
+
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
11123
|
+
abortSignal: requestOptions?.abortSignal,
|
|
11124
|
+
fetchFn: this._options?.fetch,
|
|
11125
|
+
logging: this._options.logging
|
|
11126
|
+
});
|
|
11127
|
+
if (_response.ok) {
|
|
11128
|
+
return { data: void 0, rawResponse: _response.rawResponse };
|
|
11129
|
+
}
|
|
11130
|
+
if (_response.error.reason === "status-code") {
|
|
11131
|
+
switch (_response.error.statusCode) {
|
|
11132
|
+
case 400:
|
|
11133
|
+
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
11134
|
+
case 429:
|
|
11135
|
+
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
11136
|
+
default:
|
|
11137
|
+
throw new VectrosError({
|
|
11138
|
+
statusCode: _response.error.statusCode,
|
|
11139
|
+
body: _response.error.body,
|
|
11140
|
+
rawResponse: _response.rawResponse
|
|
11141
|
+
});
|
|
11142
|
+
}
|
|
11143
|
+
}
|
|
11144
|
+
return handleNonStatusCodeError(_response.error, _response.rawResponse, "PUT", "/v1/scripts/{id}");
|
|
11145
|
+
}
|
|
11146
|
+
/**
|
|
11147
|
+
* 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.
|
|
11148
|
+
*
|
|
11149
|
+
* @param {Vectros.DeleteScriptRequest} request
|
|
11150
|
+
* @param {ScriptsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
11151
|
+
*
|
|
11152
|
+
* @throws {@link Vectros.NotFoundError}
|
|
11153
|
+
* @throws {@link Vectros.TooManyRequestsError}
|
|
11154
|
+
*
|
|
11155
|
+
* @example
|
|
11156
|
+
* await client.scripts.deleteScript({
|
|
11157
|
+
* id: "id"
|
|
11158
|
+
* })
|
|
11159
|
+
*/
|
|
11160
|
+
deleteScript(request, requestOptions) {
|
|
11161
|
+
return HttpResponsePromise.fromPromise(this.__deleteScript(request, requestOptions));
|
|
11162
|
+
}
|
|
11163
|
+
async __deleteScript(request, requestOptions) {
|
|
11164
|
+
const { id } = request;
|
|
11165
|
+
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
11166
|
+
const _headers = mergeHeaders(
|
|
11167
|
+
_authRequest.headers,
|
|
11168
|
+
this._options?.headers,
|
|
11169
|
+
requestOptions?.headers
|
|
11170
|
+
);
|
|
11171
|
+
const _response = await fetcher({
|
|
11172
|
+
url: url_exports.join(
|
|
11173
|
+
await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
|
|
11174
|
+
`v1/scripts/${url_exports.encodePathParam(id)}`
|
|
11175
|
+
),
|
|
11176
|
+
method: "DELETE",
|
|
11177
|
+
headers: _headers,
|
|
11178
|
+
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
11179
|
+
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
11180
|
+
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
11181
|
+
abortSignal: requestOptions?.abortSignal,
|
|
11182
|
+
fetchFn: this._options?.fetch,
|
|
11183
|
+
logging: this._options.logging
|
|
11184
|
+
});
|
|
11185
|
+
if (_response.ok) {
|
|
11186
|
+
return { data: void 0, rawResponse: _response.rawResponse };
|
|
11187
|
+
}
|
|
11188
|
+
if (_response.error.reason === "status-code") {
|
|
11189
|
+
switch (_response.error.statusCode) {
|
|
11190
|
+
case 404:
|
|
11191
|
+
throw new NotFoundError(_response.error.body, _response.rawResponse);
|
|
11192
|
+
case 429:
|
|
11193
|
+
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
11194
|
+
default:
|
|
11195
|
+
throw new VectrosError({
|
|
11196
|
+
statusCode: _response.error.statusCode,
|
|
11197
|
+
body: _response.error.body,
|
|
11198
|
+
rawResponse: _response.rawResponse
|
|
11199
|
+
});
|
|
11200
|
+
}
|
|
11201
|
+
}
|
|
11202
|
+
return handleNonStatusCodeError(_response.error, _response.rawResponse, "DELETE", "/v1/scripts/{id}");
|
|
11203
|
+
}
|
|
11204
|
+
};
|
|
11205
|
+
var SearchClient = class {
|
|
11206
|
+
constructor(options) {
|
|
11207
|
+
this._options = normalizeClientOptionsWithAuth(options);
|
|
11208
|
+
}
|
|
11209
|
+
/**
|
|
11210
|
+
* Runs a hybrid, semantic, or text search across the content you have indexed. By default it returns both documents and records in a single unified result set; pass `contentTypes` to narrow the results (for example `["documents"]`). Each result carries a `sourceType` discriminator (`PartnerDocument` or `GenericRecord`) so you can branch on the type. Requires the `search:r` scope.
|
|
11211
|
+
*
|
|
11212
|
+
* @param {Vectros.SearchRequest} request
|
|
11213
|
+
* @param {SearchClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
11214
|
+
*
|
|
11215
|
+
* @throws {@link Vectros.BadRequestError}
|
|
11216
|
+
* @throws {@link Vectros.ForbiddenError}
|
|
11217
|
+
* @throws {@link Vectros.TooManyRequestsError}
|
|
11218
|
+
*
|
|
11219
|
+
* @example
|
|
11220
|
+
* await client.search.content({
|
|
11221
|
+
* query: "patient intake form diabetes"
|
|
11222
|
+
* })
|
|
11223
|
+
*/
|
|
11224
|
+
content(request, requestOptions) {
|
|
11225
|
+
return HttpResponsePromise.fromPromise(this.__content(request, requestOptions));
|
|
11226
|
+
}
|
|
11227
|
+
async __content(request, requestOptions) {
|
|
11228
|
+
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
11229
|
+
const _headers = mergeHeaders(
|
|
11230
|
+
_authRequest.headers,
|
|
11231
|
+
this._options?.headers,
|
|
11232
|
+
requestOptions?.headers
|
|
11233
|
+
);
|
|
11234
|
+
const _response = await fetcher({
|
|
11235
|
+
url: url_exports.join(
|
|
11236
|
+
await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
|
|
11237
|
+
"v1/search"
|
|
11238
|
+
),
|
|
11239
|
+
method: "POST",
|
|
11240
|
+
headers: _headers,
|
|
11241
|
+
contentType: "application/json",
|
|
11242
|
+
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
11243
|
+
requestType: "json",
|
|
11244
|
+
body: request,
|
|
11245
|
+
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
11246
|
+
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
11247
|
+
abortSignal: requestOptions?.abortSignal,
|
|
11248
|
+
fetchFn: this._options?.fetch,
|
|
11249
|
+
logging: this._options.logging
|
|
11250
|
+
});
|
|
11251
|
+
if (_response.ok) {
|
|
11252
|
+
return { data: _response.body, rawResponse: _response.rawResponse };
|
|
11253
|
+
}
|
|
11254
|
+
if (_response.error.reason === "status-code") {
|
|
11255
|
+
switch (_response.error.statusCode) {
|
|
11256
|
+
case 400:
|
|
11257
|
+
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
11258
|
+
case 403:
|
|
11259
|
+
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
11260
|
+
case 429:
|
|
11261
|
+
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
11262
|
+
default:
|
|
11263
|
+
throw new VectrosError({
|
|
11264
|
+
statusCode: _response.error.statusCode,
|
|
11265
|
+
body: _response.error.body,
|
|
11266
|
+
rawResponse: _response.rawResponse
|
|
11267
|
+
});
|
|
11268
|
+
}
|
|
11269
|
+
}
|
|
11270
|
+
return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/search");
|
|
11271
|
+
}
|
|
11272
|
+
};
|
|
11273
|
+
var TriggersClient = class {
|
|
11274
|
+
constructor(options) {
|
|
11275
|
+
this._options = normalizeClientOptionsWithAuth(options);
|
|
11276
|
+
}
|
|
11277
|
+
/**
|
|
11278
|
+
* Returns a page of trigger executions that failed — what went wrong, whether the platform will retry it, and which of your records and rules were involved. Use this to find automation that has stopped running.
|
|
11279
|
+
*
|
|
11280
|
+
* A record appears once per FIRING, not once per attempt: if the platform retries a retryable failure, the same record's `attempts` count rises rather than a new record appearing. **If a retry later succeeds, the record is removed** — so this endpoint describes what is currently broken, not a permanent history. Records also expire 30 days after the firing first failed.
|
|
11281
|
+
*
|
|
11282
|
+
* `detail` is safe to surface to your own users: it carries only your own content (a script's own thrown message) or public API vocabulary, never platform internals. For `INTERNAL_ERROR` it is absent and a `correlationId` is returned instead — quote that to support.
|
|
11283
|
+
*
|
|
11284
|
+
* Scoped to your account and app context, derived from your token — never from input. Requires the `triggers:r` scope.
|
|
11285
|
+
*
|
|
11286
|
+
* @param {Vectros.ListTriggerFailuresRequest} request
|
|
11287
|
+
* @param {TriggersClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
11288
|
+
*
|
|
11289
|
+
* @example
|
|
11290
|
+
* await client.triggers.listTriggerFailures({
|
|
11291
|
+
* ruleId: "3b1c9f2e7a5d8410f6c2e9a1b7d4038f5e6a91c2d7f0b3a4586c9e2f1d0a7b3c",
|
|
11292
|
+
* category: "SCRIPT_ERROR",
|
|
11293
|
+
* startFrom: "b3BhcXVlLWN1cnNvci1mcm9tLXRoZS1wcmV2aW91cy1wYWdl"
|
|
11294
|
+
* })
|
|
11295
|
+
*/
|
|
11296
|
+
listTriggerFailures(request = {}, requestOptions) {
|
|
11297
|
+
return HttpResponsePromise.fromPromise(this.__listTriggerFailures(request, requestOptions));
|
|
11298
|
+
}
|
|
11299
|
+
async __listTriggerFailures(request = {}, requestOptions) {
|
|
11300
|
+
const { ruleId, category, retryable, from: from_, to, startFrom, limit } = request;
|
|
11301
|
+
const _queryParams = {
|
|
11302
|
+
ruleId,
|
|
11303
|
+
category,
|
|
11304
|
+
retryable,
|
|
11305
|
+
from: from_,
|
|
11306
|
+
to,
|
|
11307
|
+
startFrom,
|
|
11308
|
+
limit
|
|
11309
|
+
};
|
|
11310
|
+
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
11311
|
+
const _headers = mergeHeaders(
|
|
11312
|
+
_authRequest.headers,
|
|
11313
|
+
this._options?.headers,
|
|
11314
|
+
requestOptions?.headers
|
|
11315
|
+
);
|
|
11316
|
+
const _response = await fetcher({
|
|
11317
|
+
url: url_exports.join(
|
|
11318
|
+
await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
|
|
11319
|
+
"v1/trigger-failures"
|
|
11320
|
+
),
|
|
11321
|
+
method: "GET",
|
|
11322
|
+
headers: _headers,
|
|
11323
|
+
queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
|
|
11324
|
+
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
11325
|
+
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
11326
|
+
abortSignal: requestOptions?.abortSignal,
|
|
11327
|
+
fetchFn: this._options?.fetch,
|
|
11328
|
+
logging: this._options.logging
|
|
11329
|
+
});
|
|
11330
|
+
if (_response.ok) {
|
|
11331
|
+
return { data: _response.body, rawResponse: _response.rawResponse };
|
|
11332
|
+
}
|
|
11333
|
+
if (_response.error.reason === "status-code") {
|
|
11334
|
+
throw new VectrosError({
|
|
11335
|
+
statusCode: _response.error.statusCode,
|
|
11336
|
+
body: _response.error.body,
|
|
11337
|
+
rawResponse: _response.rawResponse
|
|
11338
|
+
});
|
|
11339
|
+
}
|
|
11340
|
+
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/trigger-failures");
|
|
11341
|
+
}
|
|
11342
|
+
/**
|
|
11343
|
+
* Returns a paginated list of your trigger rules, newest first. Results are returned as a `{data, nextCursor}` envelope — pass `nextCursor` as `startFrom` to fetch the next page. Requires the `triggers:r` scope.
|
|
11344
|
+
*
|
|
11345
|
+
* @param {Vectros.ListTriggersRequest} request
|
|
11346
|
+
* @param {TriggersClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
11347
|
+
*
|
|
11348
|
+
* @example
|
|
11349
|
+
* await client.triggers.listTriggers({
|
|
11350
|
+
* startFrom: "b3BhcXVlLWN1cnNvci1mcm9tLXRoZS1wcmV2aW91cy1wYWdl"
|
|
11351
|
+
* })
|
|
11352
|
+
*/
|
|
11353
|
+
listTriggers(request = {}, requestOptions) {
|
|
11354
|
+
return HttpResponsePromise.fromPromise(this.__listTriggers(request, requestOptions));
|
|
11355
|
+
}
|
|
11356
|
+
async __listTriggers(request = {}, requestOptions) {
|
|
11357
|
+
const { startFrom, limit } = request;
|
|
11358
|
+
const _queryParams = {
|
|
11359
|
+
startFrom,
|
|
11360
|
+
limit
|
|
11361
|
+
};
|
|
11362
|
+
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
11363
|
+
const _headers = mergeHeaders(
|
|
11364
|
+
_authRequest.headers,
|
|
11365
|
+
this._options?.headers,
|
|
11366
|
+
requestOptions?.headers
|
|
11367
|
+
);
|
|
11368
|
+
const _response = await fetcher({
|
|
11369
|
+
url: url_exports.join(
|
|
11370
|
+
await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
|
|
11371
|
+
"v1/triggers"
|
|
11372
|
+
),
|
|
11373
|
+
method: "GET",
|
|
11374
|
+
headers: _headers,
|
|
11375
|
+
queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
|
|
11376
|
+
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
11377
|
+
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
11378
|
+
abortSignal: requestOptions?.abortSignal,
|
|
11379
|
+
fetchFn: this._options?.fetch,
|
|
11380
|
+
logging: this._options.logging
|
|
11381
|
+
});
|
|
11382
|
+
if (_response.ok) {
|
|
11383
|
+
return { data: _response.body, rawResponse: _response.rawResponse };
|
|
11384
|
+
}
|
|
11385
|
+
if (_response.error.reason === "status-code") {
|
|
11386
|
+
throw new VectrosError({
|
|
11387
|
+
statusCode: _response.error.statusCode,
|
|
11388
|
+
body: _response.error.body,
|
|
11389
|
+
rawResponse: _response.rawResponse
|
|
11390
|
+
});
|
|
11391
|
+
}
|
|
11392
|
+
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/triggers");
|
|
11393
|
+
}
|
|
11394
|
+
/**
|
|
11395
|
+
* Declares a new trigger rule, or (with `?upsert=true`) reconciles an existing one of the same name to the submitted shape. Once declared, the rule fires: a record write on the named schema dispatches the referenced script, which runs asynchronously under this rule's own grant. Because that grant is executed, a scoped credential may not declare a trigger whose grant exceeds its own — such a request is rejected with a `403`, and a `roleIds` entry that does not resolve in this context with a `400`. Requires the `triggers:c` scope (also `triggers:u` when `?upsert=true` matches an existing row).
|
|
11396
|
+
*
|
|
11397
|
+
* @param {Vectros.CreateTriggerRequest} request
|
|
11398
|
+
* @param {TriggersClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
11399
|
+
*
|
|
11400
|
+
* @throws {@link Vectros.BadRequestError}
|
|
11401
|
+
* @throws {@link Vectros.ForbiddenError}
|
|
11402
|
+
* @throws {@link Vectros.TooManyRequestsError}
|
|
11403
|
+
*
|
|
11404
|
+
* @example
|
|
11405
|
+
* await client.triggers.createTrigger({
|
|
11406
|
+
* body: {
|
|
11407
|
+
* name: "on-intake-create",
|
|
11408
|
+
* firingSource: {
|
|
11409
|
+
* schemaId: "sch_9f8e7d6c",
|
|
11410
|
+
* event: "CREATE"
|
|
11411
|
+
* },
|
|
11412
|
+
* scriptRef: {
|
|
11413
|
+
* name: "notify-assignee",
|
|
11414
|
+
* version: "3"
|
|
11415
|
+
* }
|
|
11416
|
+
* }
|
|
11417
|
+
* })
|
|
11418
|
+
*/
|
|
11419
|
+
createTrigger(request, requestOptions) {
|
|
11420
|
+
return HttpResponsePromise.fromPromise(this.__createTrigger(request, requestOptions));
|
|
11421
|
+
}
|
|
11422
|
+
async __createTrigger(request, requestOptions) {
|
|
11423
|
+
const { upsert, body: _body } = request;
|
|
11424
|
+
const _queryParams = {
|
|
11425
|
+
upsert
|
|
11426
|
+
};
|
|
11427
|
+
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
11428
|
+
const _headers = mergeHeaders(
|
|
11429
|
+
_authRequest.headers,
|
|
11430
|
+
this._options?.headers,
|
|
11431
|
+
requestOptions?.headers
|
|
11432
|
+
);
|
|
11433
|
+
const _response = await fetcher({
|
|
11434
|
+
url: url_exports.join(
|
|
11435
|
+
await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
|
|
11436
|
+
"v1/triggers"
|
|
11437
|
+
),
|
|
11438
|
+
method: "POST",
|
|
11439
|
+
headers: _headers,
|
|
11440
|
+
contentType: "application/json",
|
|
11441
|
+
queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
|
|
11442
|
+
requestType: "json",
|
|
11443
|
+
body: _body,
|
|
11444
|
+
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
11445
|
+
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
11446
|
+
abortSignal: requestOptions?.abortSignal,
|
|
11447
|
+
fetchFn: this._options?.fetch,
|
|
11448
|
+
logging: this._options.logging
|
|
11449
|
+
});
|
|
11450
|
+
if (_response.ok) {
|
|
11451
|
+
return { data: _response.body, rawResponse: _response.rawResponse };
|
|
11452
|
+
}
|
|
11453
|
+
if (_response.error.reason === "status-code") {
|
|
11454
|
+
switch (_response.error.statusCode) {
|
|
11455
|
+
case 400:
|
|
11456
|
+
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
11457
|
+
case 403:
|
|
11458
|
+
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
11459
|
+
case 429:
|
|
11460
|
+
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
11461
|
+
default:
|
|
11462
|
+
throw new VectrosError({
|
|
11463
|
+
statusCode: _response.error.statusCode,
|
|
11464
|
+
body: _response.error.body,
|
|
11465
|
+
rawResponse: _response.rawResponse
|
|
11466
|
+
});
|
|
11467
|
+
}
|
|
11468
|
+
}
|
|
11469
|
+
return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/triggers");
|
|
11470
|
+
}
|
|
11471
|
+
/**
|
|
11472
|
+
* Retrieves a single trigger rule by its ID. Requires the `triggers:r` scope.
|
|
11473
|
+
*
|
|
11474
|
+
* @param {Vectros.GetTriggerRequest} request
|
|
11475
|
+
* @param {TriggersClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
11476
|
+
*
|
|
11477
|
+
* @throws {@link Vectros.NotFoundError}
|
|
11478
|
+
*
|
|
11479
|
+
* @example
|
|
11480
|
+
* await client.triggers.getTrigger({
|
|
11481
|
+
* id: "id"
|
|
11482
|
+
* })
|
|
11483
|
+
*/
|
|
11484
|
+
getTrigger(request, requestOptions) {
|
|
11485
|
+
return HttpResponsePromise.fromPromise(this.__getTrigger(request, requestOptions));
|
|
11486
|
+
}
|
|
11487
|
+
async __getTrigger(request, requestOptions) {
|
|
11488
|
+
const { id } = request;
|
|
11489
|
+
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
11490
|
+
const _headers = mergeHeaders(
|
|
11491
|
+
_authRequest.headers,
|
|
11492
|
+
this._options?.headers,
|
|
11493
|
+
requestOptions?.headers
|
|
11494
|
+
);
|
|
11495
|
+
const _response = await fetcher({
|
|
11496
|
+
url: url_exports.join(
|
|
11497
|
+
await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
|
|
11498
|
+
`v1/triggers/${url_exports.encodePathParam(id)}`
|
|
11499
|
+
),
|
|
11500
|
+
method: "GET",
|
|
11501
|
+
headers: _headers,
|
|
11502
|
+
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
11503
|
+
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
11504
|
+
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
11505
|
+
abortSignal: requestOptions?.abortSignal,
|
|
11506
|
+
fetchFn: this._options?.fetch,
|
|
11507
|
+
logging: this._options.logging
|
|
11508
|
+
});
|
|
11509
|
+
if (_response.ok) {
|
|
11510
|
+
return { data: _response.body, rawResponse: _response.rawResponse };
|
|
11511
|
+
}
|
|
11512
|
+
if (_response.error.reason === "status-code") {
|
|
11513
|
+
switch (_response.error.statusCode) {
|
|
11514
|
+
case 404:
|
|
11515
|
+
throw new NotFoundError(_response.error.body, _response.rawResponse);
|
|
11516
|
+
default:
|
|
11517
|
+
throw new VectrosError({
|
|
11518
|
+
statusCode: _response.error.statusCode,
|
|
11519
|
+
body: _response.error.body,
|
|
11520
|
+
rawResponse: _response.rawResponse
|
|
11521
|
+
});
|
|
11522
|
+
}
|
|
11523
|
+
}
|
|
11524
|
+
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/triggers/{id}");
|
|
11525
|
+
}
|
|
11526
|
+
/**
|
|
11527
|
+
* Updates an existing trigger rule in place. The rule's resulting grant must be within your own granted scope — including a grant you did not change, because a trigger's other fields (its `scriptRef`, its `principalId`) determine what that grant executes. `name`, `principalId` and `provisionedBy` are immutable once set. Requires the `triggers:u` scope.
|
|
11528
|
+
*
|
|
11529
|
+
* @param {Vectros.UpdateTriggerRequest} request
|
|
11530
|
+
* @param {TriggersClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
11531
|
+
*
|
|
11532
|
+
* @throws {@link Vectros.BadRequestError}
|
|
11533
|
+
* @throws {@link Vectros.ForbiddenError}
|
|
11534
|
+
* @throws {@link Vectros.NotFoundError}
|
|
11535
|
+
* @throws {@link Vectros.TooManyRequestsError}
|
|
11536
|
+
*
|
|
11537
|
+
* @example
|
|
11538
|
+
* await client.triggers.updateTrigger({
|
|
11539
|
+
* id: "id",
|
|
11540
|
+
* body: {
|
|
11541
|
+
* name: "on-intake-create",
|
|
11542
|
+
* firingSource: {
|
|
11543
|
+
* schemaId: "sch_9f8e7d6c",
|
|
11544
|
+
* event: "CREATE"
|
|
11545
|
+
* },
|
|
11546
|
+
* scriptRef: {
|
|
11547
|
+
* name: "notify-assignee",
|
|
11548
|
+
* version: "3"
|
|
11549
|
+
* }
|
|
11550
|
+
* }
|
|
11551
|
+
* })
|
|
11552
|
+
*/
|
|
11553
|
+
updateTrigger(request, requestOptions) {
|
|
11554
|
+
return HttpResponsePromise.fromPromise(this.__updateTrigger(request, requestOptions));
|
|
11555
|
+
}
|
|
11556
|
+
async __updateTrigger(request, requestOptions) {
|
|
11557
|
+
const { id, body: _body } = request;
|
|
11558
|
+
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
11559
|
+
const _headers = mergeHeaders(
|
|
11560
|
+
_authRequest.headers,
|
|
11561
|
+
this._options?.headers,
|
|
11562
|
+
requestOptions?.headers
|
|
11563
|
+
);
|
|
11564
|
+
const _response = await fetcher({
|
|
11565
|
+
url: url_exports.join(
|
|
11566
|
+
await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
|
|
11567
|
+
`v1/triggers/${url_exports.encodePathParam(id)}`
|
|
11568
|
+
),
|
|
11569
|
+
method: "PUT",
|
|
11570
|
+
headers: _headers,
|
|
11571
|
+
contentType: "application/json",
|
|
11572
|
+
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
11573
|
+
requestType: "json",
|
|
11574
|
+
body: _body,
|
|
11575
|
+
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
11576
|
+
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
11577
|
+
abortSignal: requestOptions?.abortSignal,
|
|
11578
|
+
fetchFn: this._options?.fetch,
|
|
11579
|
+
logging: this._options.logging
|
|
11580
|
+
});
|
|
11581
|
+
if (_response.ok) {
|
|
11582
|
+
return { data: _response.body, rawResponse: _response.rawResponse };
|
|
11583
|
+
}
|
|
11584
|
+
if (_response.error.reason === "status-code") {
|
|
11585
|
+
switch (_response.error.statusCode) {
|
|
11586
|
+
case 400:
|
|
11587
|
+
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
11588
|
+
case 403:
|
|
11589
|
+
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
11590
|
+
case 404:
|
|
11591
|
+
throw new NotFoundError(_response.error.body, _response.rawResponse);
|
|
11592
|
+
case 429:
|
|
11593
|
+
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
11594
|
+
default:
|
|
11595
|
+
throw new VectrosError({
|
|
11596
|
+
statusCode: _response.error.statusCode,
|
|
11597
|
+
body: _response.error.body,
|
|
11598
|
+
rawResponse: _response.rawResponse
|
|
11599
|
+
});
|
|
11600
|
+
}
|
|
11601
|
+
}
|
|
11602
|
+
return handleNonStatusCodeError(_response.error, _response.rawResponse, "PUT", "/v1/triggers/{id}");
|
|
11603
|
+
}
|
|
11604
|
+
/**
|
|
11605
|
+
* Permanently deletes one trigger rule by ID. Requires the `triggers:d` scope.
|
|
11606
|
+
*
|
|
11607
|
+
* @param {Vectros.DeleteTriggerRequest} request
|
|
11608
|
+
* @param {TriggersClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
11609
|
+
*
|
|
11610
|
+
* @throws {@link Vectros.NotFoundError}
|
|
11611
|
+
* @throws {@link Vectros.TooManyRequestsError}
|
|
11612
|
+
*
|
|
11613
|
+
* @example
|
|
11614
|
+
* await client.triggers.deleteTrigger({
|
|
11615
|
+
* id: "id"
|
|
11616
|
+
* })
|
|
11617
|
+
*/
|
|
11618
|
+
deleteTrigger(request, requestOptions) {
|
|
11619
|
+
return HttpResponsePromise.fromPromise(this.__deleteTrigger(request, requestOptions));
|
|
11620
|
+
}
|
|
11621
|
+
async __deleteTrigger(request, requestOptions) {
|
|
11622
|
+
const { id } = request;
|
|
11623
|
+
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
11624
|
+
const _headers = mergeHeaders(
|
|
11625
|
+
_authRequest.headers,
|
|
11626
|
+
this._options?.headers,
|
|
11627
|
+
requestOptions?.headers
|
|
11628
|
+
);
|
|
11629
|
+
const _response = await fetcher({
|
|
11630
|
+
url: url_exports.join(
|
|
11631
|
+
await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
|
|
11632
|
+
`v1/triggers/${url_exports.encodePathParam(id)}`
|
|
11633
|
+
),
|
|
11634
|
+
method: "DELETE",
|
|
11635
|
+
headers: _headers,
|
|
11636
|
+
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
11637
|
+
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
11638
|
+
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
11639
|
+
abortSignal: requestOptions?.abortSignal,
|
|
11640
|
+
fetchFn: this._options?.fetch,
|
|
11641
|
+
logging: this._options.logging
|
|
11642
|
+
});
|
|
11643
|
+
if (_response.ok) {
|
|
11644
|
+
return { data: void 0, rawResponse: _response.rawResponse };
|
|
11645
|
+
}
|
|
11646
|
+
if (_response.error.reason === "status-code") {
|
|
11647
|
+
switch (_response.error.statusCode) {
|
|
11648
|
+
case 404:
|
|
11649
|
+
throw new NotFoundError(_response.error.body, _response.rawResponse);
|
|
11650
|
+
case 429:
|
|
11651
|
+
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
11652
|
+
default:
|
|
11653
|
+
throw new VectrosError({
|
|
11654
|
+
statusCode: _response.error.statusCode,
|
|
11655
|
+
body: _response.error.body,
|
|
11656
|
+
rawResponse: _response.rawResponse
|
|
11657
|
+
});
|
|
11658
|
+
}
|
|
11659
|
+
}
|
|
11660
|
+
return handleNonStatusCodeError(_response.error, _response.rawResponse, "DELETE", "/v1/triggers/{id}");
|
|
11661
|
+
}
|
|
11662
|
+
};
|
|
11663
|
+
var VectrosClient = class {
|
|
11664
|
+
constructor(options) {
|
|
11665
|
+
this._options = normalizeClientOptionsWithAuth(options);
|
|
11666
|
+
}
|
|
11667
|
+
get auth() {
|
|
11668
|
+
return this._auth ?? (this._auth = new AuthClient(this._options));
|
|
11669
|
+
}
|
|
11670
|
+
get documents() {
|
|
11671
|
+
return this._documents ?? (this._documents = new DocumentsClient(this._options));
|
|
11672
|
+
}
|
|
11673
|
+
get identity() {
|
|
11674
|
+
return this._identity ?? (this._identity = new IdentityClient(this._options));
|
|
11675
|
+
}
|
|
11676
|
+
get compliance() {
|
|
11677
|
+
return this._compliance ?? (this._compliance = new ComplianceClient(this._options));
|
|
11678
|
+
}
|
|
11679
|
+
get folders() {
|
|
11680
|
+
return this._folders ?? (this._folders = new FoldersClient(this._options));
|
|
11681
|
+
}
|
|
11682
|
+
get inference() {
|
|
11683
|
+
return this._inference ?? (this._inference = new InferenceClient(this._options));
|
|
11684
|
+
}
|
|
11685
|
+
get records() {
|
|
11686
|
+
return this._records ?? (this._records = new RecordsClient(this._options));
|
|
11687
|
+
}
|
|
11688
|
+
get schemas() {
|
|
11689
|
+
return this._schemas ?? (this._schemas = new SchemasClient(this._options));
|
|
11690
|
+
}
|
|
11691
|
+
get scripts() {
|
|
11692
|
+
return this._scripts ?? (this._scripts = new ScriptsClient(this._options));
|
|
11693
|
+
}
|
|
11694
|
+
get search() {
|
|
11695
|
+
return this._search ?? (this._search = new SearchClient(this._options));
|
|
11696
|
+
}
|
|
11697
|
+
get triggers() {
|
|
11698
|
+
return this._triggers ?? (this._triggers = new TriggersClient(this._options));
|
|
11699
|
+
}
|
|
11700
|
+
/**
|
|
11701
|
+
* Make a passthrough request using the SDK's configured auth, retry, logging, etc.
|
|
11702
|
+
* This is useful for making requests to endpoints not yet supported in the SDK.
|
|
11703
|
+
* The input can be a URL string, URL object, or Request object. Relative paths are resolved against the configured base URL.
|
|
11704
|
+
*
|
|
11705
|
+
* @param {Request | string | URL} input - The URL, path, or Request object.
|
|
11706
|
+
* @param {RequestInit} init - Standard fetch RequestInit options.
|
|
11707
|
+
* @param {core.PassthroughRequest.RequestOptions} requestOptions - Per-request overrides (timeout, retries, headers, abort signal).
|
|
11708
|
+
* @returns {Promise<Response>} A standard Response object.
|
|
11709
|
+
*/
|
|
11710
|
+
async fetch(input, init, requestOptions) {
|
|
11711
|
+
return makePassthroughRequest(
|
|
11712
|
+
input,
|
|
11713
|
+
init,
|
|
11714
|
+
{
|
|
11715
|
+
baseUrl: this._options.baseUrl ?? this._options.environment,
|
|
11716
|
+
headers: this._options.headers,
|
|
11717
|
+
timeoutInSeconds: this._options.timeoutInSeconds,
|
|
11718
|
+
maxRetries: this._options.maxRetries,
|
|
11719
|
+
fetch: this._options.fetch,
|
|
11720
|
+
logging: this._options.logging,
|
|
11721
|
+
getAuthHeaders: async () => (await this._options.authProvider.getAuthRequest()).headers
|
|
11722
|
+
},
|
|
11723
|
+
requestOptions
|
|
11724
|
+
);
|
|
11725
|
+
}
|
|
11726
|
+
};
|
|
11727
|
+
var logging;
|
|
11728
|
+
((logging2) => {
|
|
11729
|
+
logging2.LogLevel = LogLevel;
|
|
11730
|
+
logging2.ConsoleLogger = ConsoleLogger;
|
|
10836
11731
|
})(logging || (logging = {}));
|
|
10837
11732
|
|
|
10838
11733
|
// ../blueprints/dist/index.mjs
|
|
@@ -10884,6 +11779,20 @@ var BlueprintFieldDefSchema = z.object({
|
|
|
10884
11779
|
// the `s` reveal scope for this record type (SchemaRequest.FieldDef.sensitive).
|
|
10885
11780
|
// Format passthrough — the loader forwards it to createSchema. Default false.
|
|
10886
11781
|
sensitive: z.boolean().optional(),
|
|
11782
|
+
// Keep this field on the RECORD ROW when the payload is stored out of line (a large record, or a
|
|
11783
|
+
// LARGE_PAYLOAD storage profile), so it appears in list/lookup projections without
|
|
11784
|
+
// `includePayload`.
|
|
11785
|
+
//
|
|
11786
|
+
// Load-bearing for triggers: a trigger rule's `fields` may name a field only if it is `inline`,
|
|
11787
|
+
// `filterable`, or a lookup field — and never `sensitive`. So this is the flag that makes an
|
|
11788
|
+
// otherwise-ordinary field projectable into a triggered script's `input.record`. Mutually
|
|
11789
|
+
// exclusive with `sensitive` (the platform refuses the combination). The platform also refuses,
|
|
11790
|
+
// with a 409, removing whatever keeps a field projectable while a live trigger rule still
|
|
11791
|
+
// projects it — dropping `inline` OR `filterable` OR the lookup that carried it, not just this
|
|
11792
|
+
// flag.
|
|
11793
|
+
//
|
|
11794
|
+
// Format passthrough — the loader forwards it to createSchema. Default false.
|
|
11795
|
+
inline: z.boolean().optional(),
|
|
10887
11796
|
// Reference-field surface — a typed foreign-key link to another record. The
|
|
10888
11797
|
// platform (SchemaRequest.FieldDef) requires BOTH targetTypeName AND
|
|
10889
11798
|
// targetSurface on a reference field; the blueprint format uses the SAME names
|
|
@@ -10906,6 +11815,13 @@ var BlueprintFieldDefSchema = z.object({
|
|
|
10906
11815
|
targetSurface: z.string().min(1).optional(),
|
|
10907
11816
|
cardinality: z.enum(["one", "many"]).optional()
|
|
10908
11817
|
}).strict().superRefine((field, ctx) => {
|
|
11818
|
+
if (field.inline === true && field.sensitive === true) {
|
|
11819
|
+
ctx.addIssue({
|
|
11820
|
+
code: z.ZodIssueCode.custom,
|
|
11821
|
+
path: ["inline"],
|
|
11822
|
+
message: "a field cannot be both 'inline' and 'sensitive' \u2014 a sensitive field is blind-indexed and masked in responses, so it can never be projected into a trigger script; drop one of the two"
|
|
11823
|
+
});
|
|
11824
|
+
}
|
|
10909
11825
|
const isReference = field.fieldType === "reference";
|
|
10910
11826
|
const hasRefKeys = field.targetTypeName !== void 0 || field.targetField !== void 0 || field.targetSurface !== void 0 || field.cardinality !== void 0;
|
|
10911
11827
|
if (isReference && field.targetTypeName === void 0) {
|
|
@@ -10999,7 +11915,8 @@ var BlueprintLookupFieldSchema = z.union([
|
|
|
10999
11915
|
})
|
|
11000
11916
|
]);
|
|
11001
11917
|
var BlueprintCapabilitiesSchema = z.object({
|
|
11002
|
-
auditHistory: z.boolean().optional()
|
|
11918
|
+
auditHistory: z.boolean().optional(),
|
|
11919
|
+
triggersEnabled: z.boolean().optional()
|
|
11003
11920
|
}).strict();
|
|
11004
11921
|
var BlueprintSchemaSchema = z.object({
|
|
11005
11922
|
typeName: z.string().min(1),
|
|
@@ -11107,6 +12024,30 @@ var GrantedCapabilitiesSchema = z.array(z.string().min(1)).optional().superRefin
|
|
|
11107
12024
|
seen.add(name);
|
|
11108
12025
|
});
|
|
11109
12026
|
});
|
|
12027
|
+
var MAX_ASSIGNABLE_ROLES = 20;
|
|
12028
|
+
var ROLE_ID_RE = /^[a-z][a-z0-9-]{2,30}$/;
|
|
12029
|
+
var AssignableRolesSchema = z.array(
|
|
12030
|
+
z.string().regex(ROLE_ID_RE, {
|
|
12031
|
+
message: "an assignableRoles entry must be a roleId: 3-31 chars, a lowercase letter first, then lowercase letters/digits/dashes (e.g. 'case-handler')"
|
|
12032
|
+
})
|
|
12033
|
+
).min(1, {
|
|
12034
|
+
message: "assignableRoles must either be omitted (no roleId restriction) or name at least one roleId \u2014 an empty list states no rule"
|
|
12035
|
+
}).max(MAX_ASSIGNABLE_ROLES, {
|
|
12036
|
+
message: `assignableRoles accepts at most ${MAX_ASSIGNABLE_ROLES} entries`
|
|
12037
|
+
}).optional().superRefine((roleIds, ctx) => {
|
|
12038
|
+
if (!roleIds) return;
|
|
12039
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12040
|
+
roleIds.forEach((rid, i) => {
|
|
12041
|
+
if (seen.has(rid)) {
|
|
12042
|
+
ctx.addIssue({
|
|
12043
|
+
code: z.ZodIssueCode.custom,
|
|
12044
|
+
path: [i],
|
|
12045
|
+
message: `assignableRoles names '${rid}' more than once \u2014 list it once`
|
|
12046
|
+
});
|
|
12047
|
+
}
|
|
12048
|
+
seen.add(rid);
|
|
12049
|
+
});
|
|
12050
|
+
});
|
|
11110
12051
|
var BlueprintAccessProfileSchema = z.object({
|
|
11111
12052
|
// Validated structurally here; the SCOPE GATE (in @vectros-ai/cli) is
|
|
11112
12053
|
// what enforces the data-plane-only security boundary.
|
|
@@ -11176,7 +12117,14 @@ var BlueprintAccessProfileSchema = z.object({
|
|
|
11176
12117
|
// blast-radius question from granting the same capability to a role that
|
|
11177
12118
|
// a person is bound to. The gate should weigh the two separately rather
|
|
11178
12119
|
// than reuse one policy for both.
|
|
11179
|
-
capabilities: GrantedCapabilitiesSchema
|
|
12120
|
+
capabilities: GrantedCapabilitiesSchema,
|
|
12121
|
+
// Optional roleId allow-list on this profile's own inline clause — see
|
|
12122
|
+
// {@link AssignableRolesSchema}. Allowed here for the same symmetry reason `capabilities` is:
|
|
12123
|
+
// the platform models an access profile's scopes as a list of the SAME ScopeClause a role clause
|
|
12124
|
+
// uses, and the loader already builds this profile as one such clause. Only meaningful when the
|
|
12125
|
+
// clause grants role-composing authority in the first place (`profiles:c`-shaped); on a
|
|
12126
|
+
// clause that grants none it is inert rather than an error, exactly as on the platform.
|
|
12127
|
+
assignableRoles: AssignableRolesSchema
|
|
11180
12128
|
}).strict().superRefine((ap, ctx) => {
|
|
11181
12129
|
const hasInline = ap.allowedActions !== void 0;
|
|
11182
12130
|
const hasRoleIds = ap.roleIds !== void 0;
|
|
@@ -11203,13 +12151,21 @@ var BlueprintAccessProfileSchema = z.object({
|
|
|
11203
12151
|
message: "accessProfile.capabilities is not valid alongside 'roleIds' \u2014 a role-composed profile has no inline clause of its own; author capabilities on the referenced role's clause instead"
|
|
11204
12152
|
});
|
|
11205
12153
|
}
|
|
12154
|
+
if (ap.assignableRoles !== void 0) {
|
|
12155
|
+
ctx.addIssue({
|
|
12156
|
+
code: z.ZodIssueCode.custom,
|
|
12157
|
+
path: ["assignableRoles"],
|
|
12158
|
+
message: "accessProfile.assignableRoles is not valid alongside 'roleIds' \u2014 a role-composed profile has no inline clause of its own; author assignableRoles on the referenced role's clause instead"
|
|
12159
|
+
});
|
|
12160
|
+
}
|
|
11206
12161
|
}
|
|
11207
12162
|
});
|
|
11208
12163
|
var BlueprintRoleClauseSchema = z.object({
|
|
11209
12164
|
allowedActions: z.array(z.string().min(1)).min(1),
|
|
11210
12165
|
dataScope: z.record(z.array(z.union([z.string().min(1), z.null()]))).optional(),
|
|
11211
12166
|
dataScopeRef: z.string().min(1).optional(),
|
|
11212
|
-
capabilities: GrantedCapabilitiesSchema
|
|
12167
|
+
capabilities: GrantedCapabilitiesSchema,
|
|
12168
|
+
assignableRoles: AssignableRolesSchema
|
|
11213
12169
|
}).strict().superRefine((c, ctx) => {
|
|
11214
12170
|
if (c.dataScope !== void 0 && c.dataScopeRef !== void 0) {
|
|
11215
12171
|
ctx.addIssue({
|
|
@@ -11225,6 +12181,115 @@ var BlueprintFragmentsSchema = z.record(
|
|
|
11225
12181
|
var BlueprintRolesSchema = z.record(z.array(BlueprintRoleClauseSchema).min(1));
|
|
11226
12182
|
var BlueprintRoleAssumableSchema = z.record(z.array(z.string().min(1)).min(1));
|
|
11227
12183
|
var BlueprintRoleAssumableMapSchema = z.record(BlueprintRoleAssumableSchema);
|
|
12184
|
+
var BlueprintTriggerFiringSourceSchema = z.object({
|
|
12185
|
+
schemaName: z.string().min(1),
|
|
12186
|
+
event: z.enum(["CREATE", "UPDATE", "DELETE"])
|
|
12187
|
+
}).strict();
|
|
12188
|
+
var SCRIPT_VERSION_INTEGER_RE = /^[1-9][0-9]*$/;
|
|
12189
|
+
var BlueprintTriggerScriptRefSchema = z.object({
|
|
12190
|
+
name: z.string().min(1),
|
|
12191
|
+
version: z.string().min(1)
|
|
12192
|
+
}).strict().superRefine((ref, ctx) => {
|
|
12193
|
+
if (ref.version !== "latest" && !SCRIPT_VERSION_INTEGER_RE.test(ref.version)) {
|
|
12194
|
+
ctx.addIssue({
|
|
12195
|
+
code: z.ZodIssueCode.custom,
|
|
12196
|
+
path: ["version"],
|
|
12197
|
+
message: `scriptRef.version '${ref.version}' is invalid \u2014 must be a positive decimal integer (as a string, e.g. '3') or the literal 'latest'`
|
|
12198
|
+
});
|
|
12199
|
+
}
|
|
12200
|
+
});
|
|
12201
|
+
var MANIFEST_VERB_RE = /^[a-z]+(?:\.[a-z]+)+$/;
|
|
12202
|
+
var BlueprintTriggerManifestSchema = z.array(
|
|
12203
|
+
z.string().min(1).regex(MANIFEST_VERB_RE, {
|
|
12204
|
+
message: "a manifest entry must be '<noun>.<verb>' (e.g. 'records.update')"
|
|
12205
|
+
})
|
|
12206
|
+
).optional();
|
|
12207
|
+
var BlueprintTriggerEntrySchema = z.object({
|
|
12208
|
+
firingSource: BlueprintTriggerFiringSourceSchema,
|
|
12209
|
+
scriptRef: BlueprintTriggerScriptRefSchema,
|
|
12210
|
+
allowedActions: z.array(z.string().min(1)).min(1).optional(),
|
|
12211
|
+
roleIds: z.array(z.string().min(1)).min(1).optional(),
|
|
12212
|
+
dataScope: z.record(z.array(z.union([z.string().min(1), z.null()]))).optional(),
|
|
12213
|
+
capabilities: GrantedCapabilitiesSchema,
|
|
12214
|
+
manifest: BlueprintTriggerManifestSchema,
|
|
12215
|
+
/**
|
|
12216
|
+
* REQUIRED. The record fields this rule projects into its script's `input.record` — and, on an
|
|
12217
|
+
* UPDATE firing, `input.previous` (the same fields as they were before the write). On a DELETE
|
|
12218
|
+
* firing `input.record` carries these fields from the deleted row, which is the only way a
|
|
12219
|
+
* script can see anything of it.
|
|
12220
|
+
*
|
|
12221
|
+
* **An empty list is the "I project nothing" declaration**, not an omission: the script still
|
|
12222
|
+
* receives `input.recordId`. It is required rather than defaulted precisely so that projecting
|
|
12223
|
+
* nothing is something an author STATES, not something they get by forgetting the field.
|
|
12224
|
+
*
|
|
12225
|
+
* Every entry must name a field the schema keeps INLINE — declared `inline: true`, or
|
|
12226
|
+
* `filterable`, or used as a lookup field (a leg of one, or its `sortBy` target) — and never one
|
|
12227
|
+
* marked `sensitive`.
|
|
12228
|
+
* `lintTriggerFields` checks that against the schema this blueprint declares; the platform
|
|
12229
|
+
* checks it again, and refuses a schema update that would un-inline a field a live rule still
|
|
12230
|
+
* projects.
|
|
12231
|
+
*
|
|
12232
|
+
* A field the record does not carry arrives as `undefined`, never null. `record` and `previous`
|
|
12233
|
+
* together are capped at 224 KB serialised — a firing whose projected input exceeds that is not
|
|
12234
|
+
* run, and is recorded as an `INPUT_TOO_LARGE` failure naming the size.
|
|
12235
|
+
*/
|
|
12236
|
+
fields: z.array(z.string().min(1))
|
|
12237
|
+
}).strict().superRefine((entry, ctx) => {
|
|
12238
|
+
const hasInline = entry.allowedActions !== void 0;
|
|
12239
|
+
const hasRoleIds = entry.roleIds !== void 0;
|
|
12240
|
+
if (hasInline === hasRoleIds) {
|
|
12241
|
+
ctx.addIssue({
|
|
12242
|
+
code: z.ZodIssueCode.custom,
|
|
12243
|
+
path: ["roleIds"],
|
|
12244
|
+
message: hasInline ? "a trigger entry carries both 'allowedActions' and 'roleIds' \u2014 provide exactly one: an inline clause (allowedActions, optionally dataScope/capabilities) OR a role composition (roleIds), never both" : "a trigger entry carries neither 'allowedActions' nor 'roleIds' \u2014 provide exactly one: an inline clause (allowedActions, optionally dataScope/capabilities) OR a role composition (roleIds)"
|
|
12245
|
+
});
|
|
12246
|
+
return;
|
|
12247
|
+
}
|
|
12248
|
+
if (hasRoleIds) {
|
|
12249
|
+
if (entry.dataScope !== void 0) {
|
|
12250
|
+
ctx.addIssue({
|
|
12251
|
+
code: z.ZodIssueCode.custom,
|
|
12252
|
+
path: ["dataScope"],
|
|
12253
|
+
message: "a trigger entry's dataScope is not valid alongside 'roleIds' \u2014 a role-composed entry has no inline clause of its own; author dataScope on the referenced role's clause instead"
|
|
12254
|
+
});
|
|
12255
|
+
}
|
|
12256
|
+
if (entry.capabilities !== void 0) {
|
|
12257
|
+
ctx.addIssue({
|
|
12258
|
+
code: z.ZodIssueCode.custom,
|
|
12259
|
+
path: ["capabilities"],
|
|
12260
|
+
message: "a trigger entry's capabilities is not valid alongside 'roleIds' \u2014 a role-composed entry has no inline clause of its own; author capabilities on the referenced role's clause instead"
|
|
12261
|
+
});
|
|
12262
|
+
}
|
|
12263
|
+
}
|
|
12264
|
+
});
|
|
12265
|
+
var BlueprintTriggersSchema = z.record(BlueprintTriggerEntrySchema);
|
|
12266
|
+
var SCRIPT_NAME_RE = /^[A-Za-z0-9_-]{1,64}$/;
|
|
12267
|
+
var SCRIPT_SOURCE_MAX_BYTES = 3e5;
|
|
12268
|
+
var SCRIPT_INPUT_CONTRACT_MAX_BYTES = 5e4;
|
|
12269
|
+
var utf8Bytes = (s) => new TextEncoder().encode(s).length;
|
|
12270
|
+
var BlueprintScriptEntrySchema = z.object({
|
|
12271
|
+
/**
|
|
12272
|
+
* The script's source text, shipped verbatim.
|
|
12273
|
+
*
|
|
12274
|
+
* NOTHING PARSES THIS — not this package, and not the platform, which stores it as-is. A syntax
|
|
12275
|
+
* error is therefore invisible until the version first RUNS, where it surfaces as a
|
|
12276
|
+
* `SCRIPT_ERROR` from the trigger rule that dispatched it. Validation here is limited to what
|
|
12277
|
+
* can be checked without a JS parser: non-blank, and within the platform's byte cap.
|
|
12278
|
+
*/
|
|
12279
|
+
source: z.string().refine((s) => s.trim().length > 0, { message: "'source' must not be blank" }).refine((s) => utf8Bytes(s) <= SCRIPT_SOURCE_MAX_BYTES, {
|
|
12280
|
+
message: `'source' exceeds the platform's ${SCRIPT_SOURCE_MAX_BYTES.toLocaleString("en-US")}-byte cap (measured in UTF-8 bytes, not characters)`
|
|
12281
|
+
}),
|
|
12282
|
+
/**
|
|
12283
|
+
* Optional free-text description of the input shape this script expects.
|
|
12284
|
+
*
|
|
12285
|
+
* DESCRIPTIVE ONLY — neither this package nor the platform validates a firing's input against
|
|
12286
|
+
* it. It documents the contract for a human reading the blueprint; it does not enforce one.
|
|
12287
|
+
*/
|
|
12288
|
+
declaredInputContract: z.string().min(1).refine((s) => utf8Bytes(s) <= SCRIPT_INPUT_CONTRACT_MAX_BYTES, {
|
|
12289
|
+
message: `'declaredInputContract' exceeds the platform's ${SCRIPT_INPUT_CONTRACT_MAX_BYTES.toLocaleString("en-US")}-byte cap (measured in UTF-8 bytes, not characters)`
|
|
12290
|
+
}).optional()
|
|
12291
|
+
}).strict();
|
|
12292
|
+
var BlueprintScriptsSchema = z.record(BlueprintScriptEntrySchema);
|
|
11228
12293
|
var IDENTITY_NAMESPACE_RE = /^[a-z][a-z0-9_-]{1,31}$/;
|
|
11229
12294
|
var FORBIDDEN_IDENTITY_NAMESPACES = /* @__PURE__ */ new Set(["record", "document", "entity", "user"]);
|
|
11230
12295
|
var IdentityKindSchema = z.string().min(1).superRefine((val, ctx) => {
|
|
@@ -11259,6 +12324,24 @@ var BlueprintServicePrincipalSchema = z.object({
|
|
|
11259
12324
|
displayName: z.string().min(1)
|
|
11260
12325
|
}).strict();
|
|
11261
12326
|
var ISSUER_ID_RE = CONTEXT_ID_RE;
|
|
12327
|
+
var MAX_CAPTURED_CLAIMS = 20;
|
|
12328
|
+
var MAX_CAPTURED_CLAIM_NAME_LENGTH = 64;
|
|
12329
|
+
function assertNoDuplicateClaimNames(field) {
|
|
12330
|
+
return (claims, ctx) => {
|
|
12331
|
+
if (!claims) return;
|
|
12332
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12333
|
+
claims.forEach((name, i) => {
|
|
12334
|
+
if (seen.has(name)) {
|
|
12335
|
+
ctx.addIssue({
|
|
12336
|
+
code: z.ZodIssueCode.custom,
|
|
12337
|
+
path: [i],
|
|
12338
|
+
message: `${field} repeats '${name}' \u2014 each claim name may appear only once`
|
|
12339
|
+
});
|
|
12340
|
+
}
|
|
12341
|
+
seen.add(name);
|
|
12342
|
+
});
|
|
12343
|
+
};
|
|
12344
|
+
}
|
|
11262
12345
|
var BlueprintIssuerSchema = z.object({
|
|
11263
12346
|
/** Short slug identifying this issuer within your account. Immutable once registered. */
|
|
11264
12347
|
issuerId: z.string().regex(ISSUER_ID_RE, {
|
|
@@ -11313,7 +12396,36 @@ var BlueprintIssuerSchema = z.object({
|
|
|
11313
12396
|
message: "roleId must be 3-31 chars, start with a lowercase letter, then lowercase letters/digits/dashes (e.g. 'practitioner-member')"
|
|
11314
12397
|
})
|
|
11315
12398
|
}).strict()
|
|
11316
|
-
).optional()
|
|
12399
|
+
).optional(),
|
|
12400
|
+
/**
|
|
12401
|
+
* Opt-in ADDITIONAL identity-claim capture: OIDC claim names — beyond `emailClaim`, which keeps
|
|
12402
|
+
* its own dedicated field — read from this issuer's tokens on every successful token exchange
|
|
12403
|
+
* and stored as your tenant's golden IdP-asserted identity copy for the signed-in user.
|
|
12404
|
+
*
|
|
12405
|
+
* Deliberately NOT a fixed set: name whatever claims your IdP actually asserts (standard ones
|
|
12406
|
+
* like `name`/`phone_number`, or your provider's own custom claims). Each name is read from the
|
|
12407
|
+
* verified token first, falling back to `userinfoUri` (when configured) only for names still
|
|
12408
|
+
* missing after that — so a claim your IdP puts only on the userinfo response still resolves,
|
|
12409
|
+
* provided you configured that endpoint.
|
|
12410
|
+
*
|
|
12411
|
+
* Omit entirely to capture nothing beyond email (the default, and the behaviour of every
|
|
12412
|
+
* registration authored before this field existed). Capped at 20 entries, each a non-blank name
|
|
12413
|
+
* of at most 64 characters; a duplicate is REJECTED rather than silently deduplicated, because a
|
|
12414
|
+
* repeated name is a sign the declaration does not mean what its author thinks.
|
|
12415
|
+
*
|
|
12416
|
+
* Pairs with the top-level `identityProjectionClaims`, which decides which of these captured
|
|
12417
|
+
* names are then projected onto access profiles in this context — capture and projection are
|
|
12418
|
+
* separate opt-ins, and capturing a claim does not by itself expose it on a profile.
|
|
12419
|
+
*/
|
|
12420
|
+
capturedClaims: z.array(
|
|
12421
|
+
z.string().max(MAX_CAPTURED_CLAIM_NAME_LENGTH, {
|
|
12422
|
+
message: `a capturedClaims entry must be at most ${MAX_CAPTURED_CLAIM_NAME_LENGTH} characters`
|
|
12423
|
+
}).refine((name) => name.trim().length > 0, {
|
|
12424
|
+
message: "a capturedClaims entry must be a non-blank OIDC claim name"
|
|
12425
|
+
})
|
|
12426
|
+
).max(MAX_CAPTURED_CLAIMS, {
|
|
12427
|
+
message: `capturedClaims accepts at most ${MAX_CAPTURED_CLAIMS} entries`
|
|
12428
|
+
}).optional().superRefine(assertNoDuplicateClaimNames("capturedClaims"))
|
|
11317
12429
|
}).strict();
|
|
11318
12430
|
var BlueprintNamespaceSchema = z.object({
|
|
11319
12431
|
/** The namespace to register — same grammar as a `scope:<namespace>` key. */
|
|
@@ -11518,21 +12630,35 @@ var BlueprintSeedRecordSchema = z.discriminatedUnion("surface", [
|
|
|
11518
12630
|
RecordSeedSchema,
|
|
11519
12631
|
DocumentSeedSchema
|
|
11520
12632
|
]);
|
|
11521
|
-
var SELF_TOKEN_RE = /\$\{\{\s*(?:self|under|member)\.[^}]*?\s*\}\}/;
|
|
12633
|
+
var SELF_TOKEN_RE = /\$\{\{\s*(?:self|under|member|input)\.[^}]*?\s*\}\}/;
|
|
12634
|
+
var IDENTITY_DERIVED_TOKEN_RE = /\$\{\{\s*(?:self|under|member)\.[^}]*?\s*\}\}/;
|
|
12635
|
+
var INPUT_TOKEN_RE = /\$\{\{\s*input\.[^}]*?\s*\}\}/;
|
|
11522
12636
|
function lintSelfTokenPlacement(value, ctx) {
|
|
11523
|
-
const walk = (node, path4,
|
|
12637
|
+
const walk = (node, path4, zone) => {
|
|
11524
12638
|
if (typeof node === "string") {
|
|
11525
|
-
if (
|
|
12639
|
+
if (zone === "none" && SELF_TOKEN_RE.test(node)) {
|
|
12640
|
+
ctx.addIssue({
|
|
12641
|
+
code: z.ZodIssueCode.custom,
|
|
12642
|
+
path: path4,
|
|
12643
|
+
message: "'${{ self.* }}' / '${{ member.* }}' / '${{ input.* }}' is a runtime placeholder \u2014 it is only valid inside a roles[].dataScope value, a roleAssumable[] value, a fragments[] value, or a triggers[].dataScope value ('${{ input.* }}' specifically is valid ONLY in triggers[].dataScope)"
|
|
12644
|
+
});
|
|
12645
|
+
} else if (zone === "trigger" && IDENTITY_DERIVED_TOKEN_RE.test(node)) {
|
|
11526
12646
|
ctx.addIssue({
|
|
11527
12647
|
code: z.ZodIssueCode.custom,
|
|
11528
12648
|
path: path4,
|
|
11529
|
-
message: "'${{ self.* }}' / '${{ member.* }}'
|
|
12649
|
+
message: "'${{ self.* }}' / '${{ under.self.* }}' / '${{ member.* }}' resolve against the TRIGGER PRINCIPAL's identity, not yours \u2014 a trigger's grant is resolved at fire time under its own principal. The platform refuses them in a trigger grant for that reason: the reach they describe is not bounded by your own. Use '${{ input.* }}' to confine the grant to the record that fired the rule, or name literal values."
|
|
12650
|
+
});
|
|
12651
|
+
} else if (zone === "self-family" && INPUT_TOKEN_RE.test(node)) {
|
|
12652
|
+
ctx.addIssue({
|
|
12653
|
+
code: z.ZodIssueCode.custom,
|
|
12654
|
+
path: path4,
|
|
12655
|
+
message: "'${{ input.* }}' resolves against the record that fired a trigger \u2014 it is only valid inside a triggers[].dataScope value. It is inert (never resolved, matches nothing) in roles[].dataScope, roleAssumable[], and fragments[] values; use '${{ self.* }}' / '${{ under.self.* }}' / '${{ member.* }}' there instead."
|
|
11530
12656
|
});
|
|
11531
12657
|
}
|
|
11532
12658
|
return;
|
|
11533
12659
|
}
|
|
11534
12660
|
if (Array.isArray(node)) {
|
|
11535
|
-
node.forEach((v, i) => walk(v, [...path4, i],
|
|
12661
|
+
node.forEach((v, i) => walk(v, [...path4, i], zone));
|
|
11536
12662
|
return;
|
|
11537
12663
|
}
|
|
11538
12664
|
if (node && typeof node === "object") {
|
|
@@ -11540,12 +12666,13 @@ function lintSelfTokenPlacement(value, ctx) {
|
|
|
11540
12666
|
const inRoleDataScopeClause = path4.length === 3 && path4[0] === "roles" && k === "dataScope";
|
|
11541
12667
|
const inRoleAssumable = path4.length === 0 && k === "roleAssumable";
|
|
11542
12668
|
const inFragments = path4.length === 0 && k === "fragments";
|
|
11543
|
-
const
|
|
11544
|
-
|
|
12669
|
+
const inTriggerDataScope = path4.length === 2 && path4[0] === "triggers" && k === "dataScope";
|
|
12670
|
+
const nextZone = zone === "trigger" || inTriggerDataScope ? "trigger" : zone === "self-family" || inRoleDataScopeClause || inRoleAssumable || inFragments ? "self-family" : "none";
|
|
12671
|
+
walk(v, [...path4, k], nextZone);
|
|
11545
12672
|
}
|
|
11546
12673
|
}
|
|
11547
12674
|
};
|
|
11548
|
-
walk(value, [],
|
|
12675
|
+
walk(value, [], "none");
|
|
11549
12676
|
}
|
|
11550
12677
|
var IDENTITY_REF_RE = /\$\{\{\s*identities\.([A-Za-z_]\w*)\s*\}\}/g;
|
|
11551
12678
|
var IDENTITY_REF_LOOSE_RE = /\$\{\{\s*identities\.([^}]*?)\s*\}\}/g;
|
|
@@ -11699,6 +12826,44 @@ function lintDataScopeKey(key, path4, ctx) {
|
|
|
11699
12826
|
});
|
|
11700
12827
|
}
|
|
11701
12828
|
}
|
|
12829
|
+
var RECORD_RESERVED_FILTERABLE_FIELD_IDS = /* @__PURE__ */ new Set([
|
|
12830
|
+
"tenantId",
|
|
12831
|
+
"owner_id",
|
|
12832
|
+
"folderId",
|
|
12833
|
+
"rootFolderId",
|
|
12834
|
+
"recordType"
|
|
12835
|
+
]);
|
|
12836
|
+
var DOCUMENT_RESERVED_FILTERABLE_FIELD_IDS = /* @__PURE__ */ new Set([
|
|
12837
|
+
"tenantId",
|
|
12838
|
+
"model_type",
|
|
12839
|
+
"owner_id",
|
|
12840
|
+
"title",
|
|
12841
|
+
"folderId",
|
|
12842
|
+
"rootFolderId",
|
|
12843
|
+
"recordType"
|
|
12844
|
+
]);
|
|
12845
|
+
function lintReservedFilterableFieldIds(value, ctx) {
|
|
12846
|
+
const bp = value;
|
|
12847
|
+
bp.schemas?.forEach((schema, si) => {
|
|
12848
|
+
const surfaces = schema.allowedSurfaces ?? ["record"];
|
|
12849
|
+
schema.fields?.forEach((field, fi) => {
|
|
12850
|
+
if (!field?.filterable || !field.fieldId) return;
|
|
12851
|
+
const collidesWith = /* @__PURE__ */ new Set();
|
|
12852
|
+
if (surfaces.includes("record") && RECORD_RESERVED_FILTERABLE_FIELD_IDS.has(field.fieldId)) {
|
|
12853
|
+
for (const k of RECORD_RESERVED_FILTERABLE_FIELD_IDS) collidesWith.add(k);
|
|
12854
|
+
}
|
|
12855
|
+
if (surfaces.includes("document") && DOCUMENT_RESERVED_FILTERABLE_FIELD_IDS.has(field.fieldId)) {
|
|
12856
|
+
for (const k of DOCUMENT_RESERVED_FILTERABLE_FIELD_IDS) collidesWith.add(k);
|
|
12857
|
+
}
|
|
12858
|
+
if (collidesWith.size === 0) return;
|
|
12859
|
+
ctx.addIssue({
|
|
12860
|
+
code: z.ZodIssueCode.custom,
|
|
12861
|
+
path: ["schemas", si, "fields", fi, "filterable"],
|
|
12862
|
+
message: `schema '${schema.typeName ?? si}': field '${field.fieldId}' cannot be declared filterable \u2014 '${field.fieldId}' is a platform-owned search-index metadata key (${[...collidesWith].sort().join(", ")}) for a surface this schema binds to (${surfaces.join(", ")}), and a filterable payload field of the same name would shadow it in the index. Choose a different fieldId, or leave this field non-filterable.`
|
|
12863
|
+
});
|
|
12864
|
+
});
|
|
12865
|
+
});
|
|
12866
|
+
}
|
|
11702
12867
|
function lintDataScopeKeys(value, ctx) {
|
|
11703
12868
|
const bp = value;
|
|
11704
12869
|
for (const key of Object.keys(bp.accessProfile?.dataScope ?? {})) {
|
|
@@ -11716,6 +12881,11 @@ function lintDataScopeKeys(value, ctx) {
|
|
|
11716
12881
|
lintDataScopeKey(key, ["fragments", name, key], ctx);
|
|
11717
12882
|
}
|
|
11718
12883
|
}
|
|
12884
|
+
for (const [name, trigger] of Object.entries(bp.triggers ?? {})) {
|
|
12885
|
+
for (const key of Object.keys(trigger.dataScope ?? {})) {
|
|
12886
|
+
lintDataScopeKey(key, ["triggers", name, "dataScope", key], ctx);
|
|
12887
|
+
}
|
|
12888
|
+
}
|
|
11719
12889
|
}
|
|
11720
12890
|
function lintExpectedScopeDims(value, ctx) {
|
|
11721
12891
|
const bp = value;
|
|
@@ -11811,6 +12981,150 @@ function lintAccessProfileRoleIds(value, ctx) {
|
|
|
11811
12981
|
seen.add(roleId);
|
|
11812
12982
|
});
|
|
11813
12983
|
}
|
|
12984
|
+
function lintTriggerFields(value, ctx) {
|
|
12985
|
+
const bp = value;
|
|
12986
|
+
for (const [triggerName, entry] of Object.entries(bp.triggers ?? {})) {
|
|
12987
|
+
const declared = entry?.fields;
|
|
12988
|
+
if (!declared?.length) continue;
|
|
12989
|
+
const schema = bp.schemas?.find((sc) => sc.typeName === entry.firingSource?.schemaName);
|
|
12990
|
+
if (!schema) continue;
|
|
12991
|
+
const lookupNames = /* @__PURE__ */ new Set();
|
|
12992
|
+
for (const lf of schema.lookupFields ?? []) {
|
|
12993
|
+
if (typeof lf === "string") {
|
|
12994
|
+
lookupNames.add(lf);
|
|
12995
|
+
continue;
|
|
12996
|
+
}
|
|
12997
|
+
if (lf?.fieldName) lookupNames.add(lf.fieldName);
|
|
12998
|
+
for (const n of lf?.fieldNames ?? []) lookupNames.add(n);
|
|
12999
|
+
if (lf?.sortBy && lf.sortBy !== "createdAt" && lf.sortBy !== "lastUpdated") {
|
|
13000
|
+
lookupNames.add(lf.sortBy);
|
|
13001
|
+
}
|
|
13002
|
+
}
|
|
13003
|
+
const seenFields = /* @__PURE__ */ new Set();
|
|
13004
|
+
declared.forEach((fieldId, i) => {
|
|
13005
|
+
const def = schema.fields?.find((f) => f.fieldId === fieldId);
|
|
13006
|
+
const path4 = ["triggers", triggerName, "fields", i];
|
|
13007
|
+
if (seenFields.has(fieldId)) {
|
|
13008
|
+
ctx.addIssue({
|
|
13009
|
+
code: z.ZodIssueCode.custom,
|
|
13010
|
+
path: path4,
|
|
13011
|
+
message: `triggers['${triggerName}'].fields names '${fieldId}' more than once \u2014 list it once`
|
|
13012
|
+
});
|
|
13013
|
+
return;
|
|
13014
|
+
}
|
|
13015
|
+
seenFields.add(fieldId);
|
|
13016
|
+
if (def?.sensitive === true) {
|
|
13017
|
+
ctx.addIssue({
|
|
13018
|
+
code: z.ZodIssueCode.custom,
|
|
13019
|
+
path: path4,
|
|
13020
|
+
message: `triggers['${triggerName}'].fields names '${fieldId}', which is marked 'sensitive'. A sensitive field is blind-indexed and masked, and can never be projected into a trigger script \u2014 drop it from 'fields', or make the field non-sensitive`
|
|
13021
|
+
});
|
|
13022
|
+
return;
|
|
13023
|
+
}
|
|
13024
|
+
if (lookupNames.has(fieldId)) return;
|
|
13025
|
+
if (!def) {
|
|
13026
|
+
ctx.addIssue({
|
|
13027
|
+
code: z.ZodIssueCode.custom,
|
|
13028
|
+
path: path4,
|
|
13029
|
+
message: `triggers['${triggerName}'].fields names '${fieldId}', which schema '${schema.typeName}' neither declares nor uses as a lookup`
|
|
13030
|
+
});
|
|
13031
|
+
return;
|
|
13032
|
+
}
|
|
13033
|
+
if (def.inline !== true && def.filterable !== true) {
|
|
13034
|
+
ctx.addIssue({
|
|
13035
|
+
code: z.ZodIssueCode.custom,
|
|
13036
|
+
path: path4,
|
|
13037
|
+
message: `triggers['${triggerName}'].fields names '${fieldId}', which schema '${schema.typeName}' does not keep inline. A rule may project only a field that is 'inline: true', 'filterable: true', or used as a lookup field \u2014 mark it 'inline: true' on the schema, or drop it from 'fields'`
|
|
13038
|
+
});
|
|
13039
|
+
}
|
|
13040
|
+
});
|
|
13041
|
+
}
|
|
13042
|
+
}
|
|
13043
|
+
function lintScripts(value, ctx) {
|
|
13044
|
+
const bp = value;
|
|
13045
|
+
const scripts = bp.scripts;
|
|
13046
|
+
if (!scripts) return;
|
|
13047
|
+
for (const name of Object.keys(scripts)) {
|
|
13048
|
+
if (!SCRIPT_NAME_RE.test(name)) {
|
|
13049
|
+
ctx.addIssue({
|
|
13050
|
+
code: z.ZodIssueCode.custom,
|
|
13051
|
+
path: ["scripts", name],
|
|
13052
|
+
message: `scripts['${name}'] is not a valid script name \u2014 use 1-64 characters of letters, digits, underscores ('_') and hyphens ('-'). The map key IS the name the platform stores and a trigger's scriptRef.name resolves against.`
|
|
13053
|
+
});
|
|
13054
|
+
}
|
|
13055
|
+
}
|
|
13056
|
+
for (const [triggerName, trigger] of Object.entries(bp.triggers ?? {})) {
|
|
13057
|
+
const ref = trigger?.scriptRef;
|
|
13058
|
+
if (!ref?.name) continue;
|
|
13059
|
+
if (!Object.prototype.hasOwnProperty.call(scripts, ref.name)) continue;
|
|
13060
|
+
if (ref.version !== "latest") {
|
|
13061
|
+
ctx.addIssue({
|
|
13062
|
+
code: z.ZodIssueCode.custom,
|
|
13063
|
+
path: ["triggers", triggerName, "scriptRef", "version"],
|
|
13064
|
+
message: `triggers['${triggerName}'].scriptRef pins version '${ref.version}' of '${ref.name}', which is a script THIS blueprint ships \u2014 use version: 'latest' instead. Version numbers are assigned by the platform, so a pin cannot be known to name the source declared here; and once that source is edited the pin would keep running the superseded version. Pin a version only for a script this blueprint does not ship.`
|
|
13065
|
+
});
|
|
13066
|
+
}
|
|
13067
|
+
}
|
|
13068
|
+
}
|
|
13069
|
+
function lintTriggers(value, ctx) {
|
|
13070
|
+
const bp = value;
|
|
13071
|
+
const triggers = bp.triggers;
|
|
13072
|
+
if (!triggers) return;
|
|
13073
|
+
const declaredSchemas = new Set((bp.schemas ?? []).map((s) => s.typeName).filter(Boolean));
|
|
13074
|
+
const triggerEnabledSchemas = new Set(
|
|
13075
|
+
(bp.schemas ?? []).filter((s) => s.capabilities?.triggersEnabled === true).map((s) => s.typeName).filter(Boolean)
|
|
13076
|
+
);
|
|
13077
|
+
const declaredRoles = new Set(Object.keys(bp.roles ?? {}));
|
|
13078
|
+
for (const [name, trigger] of Object.entries(triggers)) {
|
|
13079
|
+
const schemaName = trigger.firingSource?.schemaName;
|
|
13080
|
+
if (schemaName && !declaredSchemas.has(schemaName)) {
|
|
13081
|
+
ctx.addIssue({
|
|
13082
|
+
code: z.ZodIssueCode.custom,
|
|
13083
|
+
path: ["triggers", name, "firingSource", "schemaName"],
|
|
13084
|
+
message: `triggers['${name}'].firingSource.schemaName names '${schemaName}', which is not a schema this blueprint declares in schemas \u2014 ` + (declaredSchemas.size ? `declared schemas: ${[...declaredSchemas].join(", ")}` : "declared schemas: (none)")
|
|
13085
|
+
});
|
|
13086
|
+
} else if (schemaName && !triggerEnabledSchemas.has(schemaName)) {
|
|
13087
|
+
ctx.addIssue({
|
|
13088
|
+
code: z.ZodIssueCode.custom,
|
|
13089
|
+
path: ["triggers", name, "firingSource", "schemaName"],
|
|
13090
|
+
message: `triggers['${name}'].firingSource.schemaName names '${schemaName}', which does not enable triggers \u2014 records of that schema would never fire it. Set capabilities.triggersEnabled: true on the '${schemaName}' schema.`
|
|
13091
|
+
});
|
|
13092
|
+
}
|
|
13093
|
+
const roleIds = trigger.roleIds;
|
|
13094
|
+
if (!roleIds) continue;
|
|
13095
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13096
|
+
roleIds.forEach((roleId, i) => {
|
|
13097
|
+
const path4 = ["triggers", name, "roleIds", i];
|
|
13098
|
+
if (!declaredRoles.has(roleId)) {
|
|
13099
|
+
ctx.addIssue({
|
|
13100
|
+
code: z.ZodIssueCode.custom,
|
|
13101
|
+
path: path4,
|
|
13102
|
+
message: `triggers['${name}'].roleIds names '${roleId}', which is not a role this blueprint declares in roles \u2014 ` + (declaredRoles.size ? `declared roles: ${[...declaredRoles].join(", ")}` : "declared roles: (none)")
|
|
13103
|
+
});
|
|
13104
|
+
}
|
|
13105
|
+
if (seen.has(roleId)) {
|
|
13106
|
+
ctx.addIssue({
|
|
13107
|
+
code: z.ZodIssueCode.custom,
|
|
13108
|
+
path: path4,
|
|
13109
|
+
message: `triggers['${name}'].roleIds names '${roleId}' more than once \u2014 remove the duplicate`
|
|
13110
|
+
});
|
|
13111
|
+
}
|
|
13112
|
+
seen.add(roleId);
|
|
13113
|
+
for (const clause of bp.roles?.[roleId] ?? []) {
|
|
13114
|
+
for (const [dim, raw] of Object.entries(clause?.dataScope ?? {})) {
|
|
13115
|
+
for (const v of Array.isArray(raw) ? raw : [raw]) {
|
|
13116
|
+
if (typeof v !== "string" || !IDENTITY_DERIVED_TOKEN_RE.test(v)) continue;
|
|
13117
|
+
ctx.addIssue({
|
|
13118
|
+
code: z.ZodIssueCode.custom,
|
|
13119
|
+
path: path4,
|
|
13120
|
+
message: `triggers['${name}'].roleIds names '${roleId}', whose dataScope['${dim}'] uses '${v}'. '\${{ self.* }}' / '\${{ under.self.* }}' / '\${{ member.* }}' resolve against the TRIGGER PRINCIPAL's identity, not yours, so the platform refuses them in a trigger grant \u2014 inline or composed. The role itself is fine for ordinary use; it just cannot be the grant for a trigger. Give the trigger an inline grant using '\${{ input.* }}' or literal values, or compose a role without an identity-derived dataScope.`
|
|
13121
|
+
});
|
|
13122
|
+
}
|
|
13123
|
+
}
|
|
13124
|
+
}
|
|
13125
|
+
});
|
|
13126
|
+
}
|
|
13127
|
+
}
|
|
11814
13128
|
function lintRoleAssumable(value, ctx) {
|
|
11815
13129
|
const bp = value;
|
|
11816
13130
|
const declaredRoles = new Set(Object.keys(bp.roles ?? {}));
|
|
@@ -11925,7 +13239,21 @@ var BLUEPRINT_FIELD_PHASES = Object.freeze({
|
|
|
11925
13239
|
schemas: "in-context",
|
|
11926
13240
|
accessProfile: "in-context",
|
|
11927
13241
|
roles: "in-context",
|
|
11928
|
-
seed: "in-context"
|
|
13242
|
+
seed: "in-context",
|
|
13243
|
+
// Pushed on the same in-context token as the schemas/roles above, and — load-bearing — BEFORE
|
|
13244
|
+
// `triggers`, because a trigger's `scriptRef` is validated AT DECLARE TIME: a rule naming a script
|
|
13245
|
+
// with no versions in the context is refused with a 400. This map does not order the loader (a
|
|
13246
|
+
// plain object's key order is not a contract); the loader orders itself, and a test pins it.
|
|
13247
|
+
scripts: "in-context",
|
|
13248
|
+
// A trigger's principal + grant are provisioned exactly like the roles: block is (N service-
|
|
13249
|
+
// principal/AccessProfile pairs instead of one), so it applies in the identical in-context phase,
|
|
13250
|
+
// under the same re-minted context-confined token.
|
|
13251
|
+
triggers: "in-context",
|
|
13252
|
+
// Setting it requires the platform provisioning capability, which only the bootstrap credential
|
|
13253
|
+
// carries — the same reasoning as issuers/namespaces above. It is applied to the app context
|
|
13254
|
+
// itself, which the bootstrap phase already creates, so there is nothing for the in-context pass
|
|
13255
|
+
// to do with it.
|
|
13256
|
+
identityProjectionClaims: "bootstrap"
|
|
11929
13257
|
});
|
|
11930
13258
|
var BlueprintSchema = z.object({
|
|
11931
13259
|
/** Stable blueprint id (the `--blueprint <name>` selector + idempotency key). */
|
|
@@ -11988,13 +13316,77 @@ var BlueprintSchema = z.object({
|
|
|
11988
13316
|
* `allowedSurfaces: ['entity']` schema, both need the registration to
|
|
11989
13317
|
* already exist). `org`/`client` need no entry — they exist already.
|
|
11990
13318
|
*/
|
|
11991
|
-
namespaces: z.array(BlueprintNamespaceSchema).optional()
|
|
13319
|
+
namespaces: z.array(BlueprintNamespaceSchema).optional(),
|
|
13320
|
+
/**
|
|
13321
|
+
* Declares which of the golden IdP-asserted claim names captured by this tenant's issuers (see
|
|
13322
|
+
* `issuers[].capturedClaims`) are projected, read-only, onto access profiles in THIS context.
|
|
13323
|
+
* Applied to the app context in the BOOTSTRAP-token phase, alongside the context's own create.
|
|
13324
|
+
*
|
|
13325
|
+
* Capture and projection are deliberately separate opt-ins: capturing a claim stores it against
|
|
13326
|
+
* the user, projecting it exposes it on a profile in this context. Declaring a name here that no
|
|
13327
|
+
* issuer captures is not an error — it simply never fills.
|
|
13328
|
+
*
|
|
13329
|
+
* <p><b>Fills once, then freezes.</b> A profile's projection is written the first time a sign-in
|
|
13330
|
+
* for that principal can supply a value — usually at profile creation, but for an INVITED member
|
|
13331
|
+
* not until they actually accept and sign in, since there is nothing to project before that.
|
|
13332
|
+
* Once filled it never updates again, even if this declaration or the underlying identity data
|
|
13333
|
+
* changes later. So editing this list affects only profiles that have not been filled yet; it is
|
|
13334
|
+
* not a backfill, and re-applying a blueprint does not re-project existing profiles.
|
|
13335
|
+
*
|
|
13336
|
+
* <p><b>Authority.</b> Setting this needs the platform provisioning capability — the same
|
|
13337
|
+
* authority the bootstrap credential already uses to register issuers and declare namespaces.
|
|
13338
|
+
* An ordinary `app-contexts:u` credential may rename its own context but may not opt it into
|
|
13339
|
+
* projecting IdP-golden identity data, so this field is only settable on the bootstrap phase's
|
|
13340
|
+
* own token, never on the per-context wildcard the in-context load runs under.
|
|
13341
|
+
*
|
|
13342
|
+
* <p>Omit to leave the context's existing declaration unchanged; declare an empty list to
|
|
13343
|
+
* disable future projection. Capped at 20 entries, each a non-blank name of at most 64
|
|
13344
|
+
* characters, duplicates rejected — the same bounds `capturedClaims` carries.
|
|
13345
|
+
*
|
|
13346
|
+
* <p><b>Applies only when this blueprint CREATES the app context.</b> The condition is the
|
|
13347
|
+
* context being created, not the apply being the first one: creating the context separately
|
|
13348
|
+
* beforehand means even the first apply does not set it. Re-applying against a context that
|
|
13349
|
+
* already exists does not change it, in either direction. Edit the declaration on an existing context through the platform API instead. (A
|
|
13350
|
+
* tool applying this blueprint could in principle reconcile it, but doing so also overwrites the
|
|
13351
|
+
* context's name and needs a broader scope — see `@vectros-ai/cli`'s own notes.)
|
|
13352
|
+
*/
|
|
13353
|
+
identityProjectionClaims: z.array(
|
|
13354
|
+
z.string().max(MAX_CAPTURED_CLAIM_NAME_LENGTH, {
|
|
13355
|
+
message: `an identityProjectionClaims entry must be at most ${MAX_CAPTURED_CLAIM_NAME_LENGTH} characters`
|
|
13356
|
+
}).refine((name) => name.trim().length > 0, {
|
|
13357
|
+
message: "an identityProjectionClaims entry must be a non-blank claim name"
|
|
13358
|
+
})
|
|
13359
|
+
).max(MAX_CAPTURED_CLAIMS, {
|
|
13360
|
+
message: `identityProjectionClaims accepts at most ${MAX_CAPTURED_CLAIMS} entries`
|
|
13361
|
+
}).optional().superRefine(assertNoDuplicateClaimNames("identityProjectionClaims")),
|
|
13362
|
+
/**
|
|
13363
|
+
* Optional scripts this blueprint SHIPS: scriptName -> `{ source, declaredInputContract? }`.
|
|
13364
|
+
*
|
|
13365
|
+
* Distinct from a trigger's `scriptRef`, which only NAMES a script and requires it to already
|
|
13366
|
+
* exist. Pushed BEFORE the `triggers` block, because a `scriptRef` is validated at declare time.
|
|
13367
|
+
*
|
|
13368
|
+
* Each entry is the CURRENT SOURCE for that name, not a version — the platform assigns versions
|
|
13369
|
+
* itself and they are immutable. Re-applying an unchanged blueprint pushes nothing; editing
|
|
13370
|
+
* `source` pushes a new version, which every `version: 'latest'` rule then picks up.
|
|
13371
|
+
*/
|
|
13372
|
+
scripts: BlueprintScriptsSchema.optional(),
|
|
13373
|
+
/**
|
|
13374
|
+
* Optional async trigger rules: triggerName -> declaration.
|
|
13375
|
+
*
|
|
13376
|
+
* These FIRE. A record write on a schema that opts in via `capabilities.triggersEnabled`
|
|
13377
|
+
* dispatches the rule's script, which runs asynchronously in a sandbox under the grant declared
|
|
13378
|
+
* here — so a trigger's `allowedActions`/`dataScope`/`capabilities` (or the roles its `roleIds`
|
|
13379
|
+
* composes) are live authority, not a declaration awaiting a future release. Author the grant as
|
|
13380
|
+
* narrowly as you would any other credential's.
|
|
13381
|
+
*/
|
|
13382
|
+
triggers: BlueprintTriggersSchema.optional()
|
|
11992
13383
|
}).strict().superRefine((bp, ctx) => {
|
|
11993
13384
|
lintSelfTokenPlacement(bp, ctx);
|
|
11994
13385
|
lintIdentityRefs(bp, ctx);
|
|
11995
13386
|
lintIdentityOverrides(bp, ctx);
|
|
11996
13387
|
lintNamespacedScopeArrays(bp, ctx);
|
|
11997
13388
|
lintDataScopeKeys(bp, ctx);
|
|
13389
|
+
lintReservedFilterableFieldIds(bp, ctx);
|
|
11998
13390
|
lintExpectedScopeDims(bp, ctx);
|
|
11999
13391
|
lintDataScopeRefs(bp, ctx);
|
|
12000
13392
|
lintRoleAssumable(bp, ctx);
|
|
@@ -12003,6 +13395,9 @@ var BlueprintSchema = z.object({
|
|
|
12003
13395
|
lintIdentitySurfaces(bp, ctx);
|
|
12004
13396
|
lintIssuerContext(bp, ctx);
|
|
12005
13397
|
lintNamespaces(bp, ctx);
|
|
13398
|
+
lintTriggers(bp, ctx);
|
|
13399
|
+
lintTriggerFields(bp, ctx);
|
|
13400
|
+
lintScripts(bp, ctx);
|
|
12006
13401
|
});
|
|
12007
13402
|
function formatIssuePath(path4) {
|
|
12008
13403
|
let out = "";
|
|
@@ -14087,6 +15482,13 @@ function renderActionableError(info) {
|
|
|
14087
15482
|
return ` \u2716 ${msg}
|
|
14088
15483
|
Your credential lacks the scope for this operation (owner-gated, or a
|
|
14089
15484
|
narrower context/action than required).
|
|
15485
|
+
`;
|
|
15486
|
+
}
|
|
15487
|
+
if (info.statusCode === 409 && /no scoped key can be minted|is not active|is suspended/i.test(msg)) {
|
|
15488
|
+
return ` \u2716 ${msg}
|
|
15489
|
+
This is NOT a scope problem: no credential, including a root key, can mint against an
|
|
15490
|
+
inactive profile or a suspended user, so re-issuing a broader credential will not help.
|
|
15491
|
+
Follow the reactivation step named above, then retry.
|
|
14090
15492
|
`;
|
|
14091
15493
|
}
|
|
14092
15494
|
if (info.statusCode === 404) {
|
|
@@ -14253,7 +15655,10 @@ async function mintBridgeToken(opts) {
|
|
|
14253
15655
|
let url = `${base}${path4}?ttl=${encodeURIComponent(String(ttlSeconds))}&tenant=${encodeURIComponent(tenant)}`;
|
|
14254
15656
|
if (contextId) url += `&context=${encodeURIComponent(contextId)}`;
|
|
14255
15657
|
return fetchStToken(url, cognitoToken, fetchImpl, path4, (status) => {
|
|
14256
|
-
if (status !== 404
|
|
15658
|
+
if (status !== 404) return "";
|
|
15659
|
+
if (useScopedToken || contextId) {
|
|
15660
|
+
return " (this route answers every refusal with the same 404: the named context may not exist yet, or your own member account may be suspended in it)";
|
|
15661
|
+
}
|
|
14257
15662
|
return ` (the dedicated ${CLI_BOOTSTRAP_TOKEN_PATH} endpoint may not be deployed yet \u2014 retry with --use-scoped-token for the fallback path)`;
|
|
14258
15663
|
});
|
|
14259
15664
|
}
|
|
@@ -14268,7 +15673,10 @@ async function mintContextToken(opts) {
|
|
|
14268
15673
|
} = opts;
|
|
14269
15674
|
const base = apiBaseUrl.replace(/\/$/, "");
|
|
14270
15675
|
const url = `${base}${SCOPED_TOKEN_PATH}?ttl=${encodeURIComponent(String(ttlSeconds))}&tenant=${encodeURIComponent(tenant)}&context=${encodeURIComponent(contextId)}`;
|
|
14271
|
-
return fetchStToken(url, cognitoToken, fetchImpl, `${SCOPED_TOKEN_PATH}?context=${contextId}
|
|
15676
|
+
return fetchStToken(url, cognitoToken, fetchImpl, `${SCOPED_TOKEN_PATH}?context=${contextId}`, (status) => {
|
|
15677
|
+
if (status !== 404) return "";
|
|
15678
|
+
return " (this route answers every refusal with the same 404: the named context may not exist, or your own member account may be suspended in it)";
|
|
15679
|
+
});
|
|
14272
15680
|
}
|
|
14273
15681
|
|
|
14274
15682
|
// src/identity.ts
|
|
@@ -14655,7 +16063,21 @@ var KNOWN_CONTROL_PLANE_RESOURCES = [
|
|
|
14655
16063
|
// unconditionally, never tenant-wide, so the reach is bounded to what the
|
|
14656
16064
|
// blueprint already provisions, never the pack's say-so alone. See
|
|
14657
16065
|
// `@vectros-ai/blueprints`' `namespaces[]` doc for the full design.
|
|
14658
|
-
"namespaces"
|
|
16066
|
+
"namespaces",
|
|
16067
|
+
// The 0.43.0 script/trigger surfaces. Listed for MESSAGE QUALITY only — both were already
|
|
16068
|
+
// rejected by the data-plane allowlist above, but as "unrecognized resource", whose text invites
|
|
16069
|
+
// the reader to widen the allowlist ("if 'scripts' is a new data-plane resource…"). They are not:
|
|
16070
|
+
// the platform deliberately put them out of reach of an ordinary confined partner role (a bare,
|
|
16071
|
+
// non-empty `data_scope` clause — the routine blueprint-issued shape — does not reach scripts at
|
|
16072
|
+
// all), because script authoring is a design-time, admin-shaped surface. So the accurate message
|
|
16073
|
+
// is the control-plane one.
|
|
16074
|
+
//
|
|
16075
|
+
// NOTE on the `x` action letter: `scripts:x` (execute) is a real platform verb, and VALID_OPS
|
|
16076
|
+
// deliberately does NOT gain it. Op letters are only checked AFTER a resource is admitted, and
|
|
16077
|
+
// no admitted resource has an `x` verb — adding it would instead make `records:x` pass the op
|
|
16078
|
+
// check and be admitted as data-plane.
|
|
16079
|
+
"scripts",
|
|
16080
|
+
"triggers"
|
|
14659
16081
|
];
|
|
14660
16082
|
var PARTIALLY_ADMITTED_OPS = /* @__PURE__ */ new Map([
|
|
14661
16083
|
["profiles", /* @__PURE__ */ new Set(["r", "u", "d"])]
|
|
@@ -14663,6 +16085,13 @@ var PARTIALLY_ADMITTED_OPS = /* @__PURE__ */ new Map([
|
|
|
14663
16085
|
var SELF_QUALIFIED_OPS = /* @__PURE__ */ new Map([
|
|
14664
16086
|
["profiles", /* @__PURE__ */ new Set(["c"])]
|
|
14665
16087
|
]);
|
|
16088
|
+
var NAME_QUALIFIED_OPS = /* @__PURE__ */ new Map([
|
|
16089
|
+
["scripts", /* @__PURE__ */ new Set(["x"])]
|
|
16090
|
+
]);
|
|
16091
|
+
var RESOURCE_EXTRA_OPS = /* @__PURE__ */ new Map([
|
|
16092
|
+
["scripts", /* @__PURE__ */ new Set(["x"])]
|
|
16093
|
+
]);
|
|
16094
|
+
var PLATFORM_SCRIPT_NAME = /^[A-Za-z0-9_-]{1,64}$/;
|
|
14666
16095
|
var UNQUALIFIABLE_WITH_ANY_TAIL = /* @__PURE__ */ new Map([
|
|
14667
16096
|
["profiles", /* @__PURE__ */ new Set(["r"])]
|
|
14668
16097
|
]);
|
|
@@ -14673,10 +16102,13 @@ var VALID_OPS = /* @__PURE__ */ new Set(["c", "r", "u", "d"]);
|
|
|
14673
16102
|
var DATA_PLANE_SET = new Set(DATA_PLANE_ALLOWLIST);
|
|
14674
16103
|
var CONTROL_PLANE_SET = new Set(KNOWN_CONTROL_PLANE_RESOURCES);
|
|
14675
16104
|
var ALLOWLIST_HINT = DATA_PLANE_ALLOWLIST.join("/");
|
|
16105
|
+
function formatRejection(r) {
|
|
16106
|
+
return r.source ? `${r.action} (${r.source}): ${r.reason}` : `${r.action}: ${r.reason}`;
|
|
16107
|
+
}
|
|
14676
16108
|
var ScopeGateError = class extends Error {
|
|
14677
16109
|
rejected;
|
|
14678
16110
|
constructor(rejected) {
|
|
14679
|
-
const summary = rejected.map((r) => ` \u2022 ${r
|
|
16111
|
+
const summary = rejected.map((r) => ` \u2022 ${formatRejection(r)}`).join("\n");
|
|
14680
16112
|
super(
|
|
14681
16113
|
`Scope gate rejected ${rejected.length} requested scope(s); the bootstrap loader provisions data-plane access only (${ALLOWLIST_HINT}) and mints nothing on reject:
|
|
14682
16114
|
${summary}`
|
|
@@ -14691,7 +16123,7 @@ var CAPABILITIES_HINT = GRANTED_CAPABILITIES_ALLOWLIST.join("/");
|
|
|
14691
16123
|
var CapabilityGateError = class extends Error {
|
|
14692
16124
|
rejected;
|
|
14693
16125
|
constructor(rejected) {
|
|
14694
|
-
const summary = rejected.map((r) => ` \u2022 ${r
|
|
16126
|
+
const summary = rejected.map((r) => ` \u2022 ${formatRejection(r)}`).join("\n");
|
|
14695
16127
|
super(
|
|
14696
16128
|
`Capability gate rejected ${rejected.length} requested capability(ies); the bootstrap loader grants (${CAPABILITIES_HINT}) only and mints nothing on reject:
|
|
14697
16129
|
${summary}`
|
|
@@ -14732,9 +16164,10 @@ function assertCapabilityGate(capabilities) {
|
|
|
14732
16164
|
throw new CapabilityGateError(result.rejected);
|
|
14733
16165
|
}
|
|
14734
16166
|
}
|
|
14735
|
-
function firstInvalidOp(ops) {
|
|
16167
|
+
function firstInvalidOp(ops, resource) {
|
|
16168
|
+
const extra = resource === void 0 ? void 0 : RESOURCE_EXTRA_OPS.get(resource);
|
|
14736
16169
|
for (const ch of ops) {
|
|
14737
|
-
if (!VALID_OPS.has(ch)) return ch;
|
|
16170
|
+
if (!VALID_OPS.has(ch) && !extra?.has(ch)) return ch;
|
|
14738
16171
|
}
|
|
14739
16172
|
return void 0;
|
|
14740
16173
|
}
|
|
@@ -14761,19 +16194,24 @@ function evaluateScopeToken(action, capabilities) {
|
|
|
14761
16194
|
if (!DATA_PLANE_SET.has(resource)) {
|
|
14762
16195
|
const admittedOps = PARTIALLY_ADMITTED_OPS.get(resource);
|
|
14763
16196
|
const selfOnlyOps = SELF_QUALIFIED_OPS.get(resource);
|
|
14764
|
-
|
|
14765
|
-
|
|
16197
|
+
const nameOnlyOps = NAME_QUALIFIED_OPS.get(resource);
|
|
16198
|
+
if (admittedOps || selfOnlyOps || nameOnlyOps) {
|
|
16199
|
+
const admittedHint = [
|
|
16200
|
+
.../* @__PURE__ */ new Set([...admittedOps ?? [], ...selfOnlyOps ?? [], ...nameOnlyOps ?? []])
|
|
16201
|
+
].sort().join("/");
|
|
14766
16202
|
if (ops === "*") {
|
|
14767
16203
|
return {
|
|
14768
16204
|
action,
|
|
14769
16205
|
reason: `requests '${resource}:*', which includes operation(s) outside the narrow allowlist (${admittedHint}) admitted for this control-plane resource. Request only the specific operations you need`
|
|
14770
16206
|
};
|
|
14771
16207
|
}
|
|
14772
|
-
const invalidOp2 = firstInvalidOp(ops);
|
|
16208
|
+
const invalidOp2 = firstInvalidOp(ops, resource);
|
|
14773
16209
|
if (invalidOp2) {
|
|
16210
|
+
const extraOps = [...RESOURCE_EXTRA_OPS.get(resource) ?? []].sort();
|
|
16211
|
+
const validHint = ["c", "r", "u", "d", ...extraOps].join(", ");
|
|
14774
16212
|
return {
|
|
14775
16213
|
action,
|
|
14776
|
-
reason: `invalid operation character '${invalidOp2}' (valid operations are
|
|
16214
|
+
reason: `invalid operation character '${invalidOp2}' (valid operations are ${validHint}, or '*')`
|
|
14777
16215
|
};
|
|
14778
16216
|
}
|
|
14779
16217
|
const qualifier = parts.length > 2 ? parts.slice(2).join(":") : void 0;
|
|
@@ -14783,10 +16221,29 @@ function evaluateScopeToken(action, capabilities) {
|
|
|
14783
16221
|
if (qualifier !== void 0 && unqualifiableOps?.has(ch)) {
|
|
14784
16222
|
return {
|
|
14785
16223
|
action,
|
|
14786
|
-
reason: `requests '${resource}:${ch}' alongside a qualifier ('${qualifier}'), but the platform does not resolve a qualifier for '${ch}' on '${resource}' at all \u2014 it will reject the WHOLE entry as malformed, not just ignore the qualifier. Split into separate entries (e.g. '${resource}:${ch}' + the other op(s) with their qualifier).`
|
|
16224
|
+
reason: `requests '${resource}:${ch}' alongside a qualifier ('${qualifier}'), but the platform does not resolve a qualifier for '${ch}' on '${resource}' at all \u2014 it will reject the WHOLE entry as malformed, not just ignore the qualifier. Split into separate entries (e.g. '${resource}:${ch}' + the other op(s) with their qualifier).`
|
|
16225
|
+
};
|
|
16226
|
+
}
|
|
16227
|
+
if (admittedOps?.has(ch)) continue;
|
|
16228
|
+
if (nameOnlyOps?.has(ch)) {
|
|
16229
|
+
if (qualifier !== void 0 && qualifier.includes("*")) {
|
|
16230
|
+
return {
|
|
16231
|
+
action,
|
|
16232
|
+
reason: `requests '${resource}:${ch}:${qualifier}'. Execute grants have NO wildcard \u2014 the qualifier is compared to the script name exactly, so a '*' matches no script and the grant is stored and then refuses every execution, with no error that can say why. Name each script you ship, one entry each: '${resource}:${ch}:<script-name>'`
|
|
16233
|
+
};
|
|
16234
|
+
}
|
|
16235
|
+
if (qualifier !== void 0 && PLATFORM_SCRIPT_NAME.test(qualifier)) continue;
|
|
16236
|
+
if (qualifier !== void 0 && qualifier.length > 0) {
|
|
16237
|
+
return {
|
|
16238
|
+
action,
|
|
16239
|
+
reason: `requests '${resource}:${ch}:${qualifier}', but '${qualifier}' cannot be a script name \u2014 names are 1-64 characters of letters, digits, '_' or '-'. A qualifier that cannot name a script is stored and then matches nothing, so it is refused here rather than at an execution that cannot explain itself`
|
|
16240
|
+
};
|
|
16241
|
+
}
|
|
16242
|
+
return {
|
|
16243
|
+
action,
|
|
16244
|
+
reason: `requests a bare '${resource}:${ch}' with no qualifier. Execution gates on the action verb and the script NAME, and consults no data scope \u2014 so a bare grant means 'may run every script in this context', including ones added later that this blueprint's reviewer never saw. Name each script you ship, one entry each \u2014 '${resource}:${ch}:<script-name>' \u2014 there is no wildcard form.`
|
|
14787
16245
|
};
|
|
14788
16246
|
}
|
|
14789
|
-
if (admittedOps?.has(ch)) continue;
|
|
14790
16247
|
if (selfOnlyOps?.has(ch)) {
|
|
14791
16248
|
if (isSelfQualified) continue;
|
|
14792
16249
|
const unlockCapability = CAPABILITY_UNLOCKED_OPS.get(resource)?.get(ch);
|
|
@@ -14857,6 +16314,50 @@ function assertBlueprintGates(blueprint) {
|
|
|
14857
16314
|
assertCapabilityGate(clause.capabilities);
|
|
14858
16315
|
}
|
|
14859
16316
|
}
|
|
16317
|
+
for (const [triggerName, entry] of Object.entries(blueprint.triggers ?? {})) {
|
|
16318
|
+
if (entry.allowedActions === void 0) continue;
|
|
16319
|
+
try {
|
|
16320
|
+
assertScopeGate(entry.allowedActions, entry.capabilities);
|
|
16321
|
+
assertCapabilityGate(entry.capabilities);
|
|
16322
|
+
} catch (err) {
|
|
16323
|
+
throw withTriggerContext(err, triggerName);
|
|
16324
|
+
}
|
|
16325
|
+
}
|
|
16326
|
+
assertScriptGrantsNameADeclaredScript(blueprint);
|
|
16327
|
+
}
|
|
16328
|
+
function assertScriptGrantsNameADeclaredScript(blueprint) {
|
|
16329
|
+
const declared = Object.keys(blueprint.scripts ?? {});
|
|
16330
|
+
if (declared.length === 0) return;
|
|
16331
|
+
const rejected = [];
|
|
16332
|
+
const check = (actions, source) => {
|
|
16333
|
+
for (const action of actions ?? []) {
|
|
16334
|
+
const parts = action.trim().split(":");
|
|
16335
|
+
if (parts[0] !== "scripts" || parts[1] !== "x") continue;
|
|
16336
|
+
const name = parts.length > 2 ? parts.slice(2).join(":") : void 0;
|
|
16337
|
+
if (name === void 0 || name.length === 0 || name.includes("*")) continue;
|
|
16338
|
+
if (!declared.includes(name)) {
|
|
16339
|
+
rejected.push({
|
|
16340
|
+
action,
|
|
16341
|
+
reason: `grants execute on '${name}', which this blueprint does not ship \u2014 it declares ${declared.map((d) => `'${d}'`).join(", ")}. Nothing downstream can catch this: the platform stores the grant, and every execution then fails with a 403 that cannot say the name is wrong. Fix the spelling, or drop the grant if the script is provisioned elsewhere`,
|
|
16342
|
+
...source ? { source } : {}
|
|
16343
|
+
});
|
|
16344
|
+
}
|
|
16345
|
+
}
|
|
16346
|
+
};
|
|
16347
|
+
check(blueprint.accessProfile.allowedActions);
|
|
16348
|
+
for (const [roleId, clauses] of Object.entries(blueprint.roles ?? {})) {
|
|
16349
|
+
for (const clause of clauses) check(clause.allowedActions, `role '${roleId}'`);
|
|
16350
|
+
}
|
|
16351
|
+
for (const [triggerName, entry] of Object.entries(blueprint.triggers ?? {})) {
|
|
16352
|
+
check(entry.allowedActions, `trigger '${triggerName}'`);
|
|
16353
|
+
}
|
|
16354
|
+
if (rejected.length > 0) throw new ScopeGateError(rejected);
|
|
16355
|
+
}
|
|
16356
|
+
function withTriggerContext(err, triggerName) {
|
|
16357
|
+
if (err instanceof ScopeGateError || err instanceof CapabilityGateError) {
|
|
16358
|
+
for (const r of err.rejected) r.source ??= `trigger '${triggerName}'`;
|
|
16359
|
+
}
|
|
16360
|
+
return err;
|
|
14860
16361
|
}
|
|
14861
16362
|
function effectiveAccessProfileActions(blueprint) {
|
|
14862
16363
|
if (blueprint.accessProfile.allowedActions !== void 0) {
|
|
@@ -14968,10 +16469,391 @@ async function resolveApplyTimeIdentities(blueprint, provisioned, bootstrapClien
|
|
|
14968
16469
|
const resolved = await resolveBlueprintIdentities(blueprint, resolver);
|
|
14969
16470
|
return parseBlueprint(resolved);
|
|
14970
16471
|
}
|
|
14971
|
-
|
|
14972
|
-
// src/
|
|
14973
|
-
|
|
14974
|
-
|
|
16472
|
+
|
|
16473
|
+
// src/scope-grammar.ts
|
|
16474
|
+
var FORBIDDEN_SCOPE_NAMESPACES2 = /* @__PURE__ */ new Set([
|
|
16475
|
+
"user",
|
|
16476
|
+
"record",
|
|
16477
|
+
"document",
|
|
16478
|
+
"entity",
|
|
16479
|
+
"self",
|
|
16480
|
+
"tenant",
|
|
16481
|
+
"context",
|
|
16482
|
+
"scope",
|
|
16483
|
+
"versions",
|
|
16484
|
+
"lookup"
|
|
16485
|
+
]);
|
|
16486
|
+
var SCOPE_NAMESPACE_RE2 = /^[a-z][a-z0-9_-]{1,31}$/;
|
|
16487
|
+
var SCOPE_NAMESPACE_MIN_LENGTH = 2;
|
|
16488
|
+
var SCOPE_NAMESPACE_MAX_LENGTH = 32;
|
|
16489
|
+
var SCOPE_VALUE_RE2 = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
|
|
16490
|
+
var SCOPE_VALUE_MIN_LENGTH = 1;
|
|
16491
|
+
var SCOPE_VALUE_MAX_LENGTH = 128;
|
|
16492
|
+
function valueGrammarError(value) {
|
|
16493
|
+
if (!SCOPE_VALUE_RE2.test(value)) {
|
|
16494
|
+
return `'${value}' is not a valid scope value \u2014 ${SCOPE_VALUE_MIN_LENGTH}-${SCOPE_VALUE_MAX_LENGTH} characters, starting with a letter or digit and continuing with letters, digits, '_' or '-'. A value may not contain a colon.`;
|
|
16495
|
+
}
|
|
16496
|
+
return null;
|
|
16497
|
+
}
|
|
16498
|
+
function namespaceGrammarError(ns) {
|
|
16499
|
+
if (FORBIDDEN_SCOPE_NAMESPACES2.has(ns)) {
|
|
16500
|
+
return `'${ns}' is a reserved namespace and cannot be used as an ownership scope`;
|
|
16501
|
+
}
|
|
16502
|
+
if (!SCOPE_NAMESPACE_RE2.test(ns)) {
|
|
16503
|
+
return `'${ns}' is not a valid namespace \u2014 lowercase, ${SCOPE_NAMESPACE_MIN_LENGTH}-${SCOPE_NAMESPACE_MAX_LENGTH} characters, starting with a letter and continuing with letters, digits, '_' or '-'`;
|
|
16504
|
+
}
|
|
16505
|
+
return null;
|
|
16506
|
+
}
|
|
16507
|
+
function assertScopeEntries(entries, flag) {
|
|
16508
|
+
if (!entries?.length) return;
|
|
16509
|
+
for (const raw of entries) {
|
|
16510
|
+
const sep = raw.indexOf(":");
|
|
16511
|
+
if (sep <= 0 || sep === raw.length - 1) {
|
|
16512
|
+
throw new Error(`${flag} must be '<namespace>:<value>' (e.g. 'org:6ba7...'); got '${raw}'.`);
|
|
16513
|
+
}
|
|
16514
|
+
const nsError = namespaceGrammarError(raw.slice(0, sep));
|
|
16515
|
+
if (nsError) throw new Error(`${flag} namespace ${nsError}`);
|
|
16516
|
+
const valError = valueGrammarError(raw.slice(sep + 1));
|
|
16517
|
+
if (valError) throw new Error(`${flag} value ${valError}`);
|
|
16518
|
+
}
|
|
16519
|
+
}
|
|
16520
|
+
function assertIdentityOverrideValues(overrides, flag) {
|
|
16521
|
+
if (!overrides) return;
|
|
16522
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
16523
|
+
if (!key.startsWith("scope:")) continue;
|
|
16524
|
+
const error = valueGrammarError(typeof value === "string" ? value : String(value));
|
|
16525
|
+
if (error) throw new Error(`${flag} '${key}' value ${error}`);
|
|
16526
|
+
}
|
|
16527
|
+
}
|
|
16528
|
+
function placeholderScopeNamespace(value) {
|
|
16529
|
+
const self2 = /^\$\{\{\s*(?:under\.)?self\.scope\.([^\s}]+)\s*\}\}$/.exec(value);
|
|
16530
|
+
if (self2) return self2[1];
|
|
16531
|
+
const member = MEMBER_SCOPE_PLACEHOLDER_RE2.exec(value);
|
|
16532
|
+
return member ? member[1] : null;
|
|
16533
|
+
}
|
|
16534
|
+
var SELF_PLACEHOLDER_RE = /^\$\{\{\s*self\.(?:userId|partnerUserId|scope\.[a-z][a-z0-9_-]{1,31})\s*\}\}$/;
|
|
16535
|
+
var UNDER_SELF_PLACEHOLDER_RE2 = /^\$\{\{\s*under\.self\.(?:userId|partnerUserId|scope\.[a-z][a-z0-9_-]{1,31})\s*\}\}$/;
|
|
16536
|
+
var MEMBER_SCOPE_PLACEHOLDER_RE2 = /^\$\{\{\s*member\.scope\.([a-z][a-z0-9_-]{1,31})(?::[a-z][a-z0-9_-]{1,31})?\s*\}\}$/;
|
|
16537
|
+
var ROLE_ID_RE2 = /^[a-z][a-z0-9-]{2,30}$/;
|
|
16538
|
+
var MAX_ASSIGNABLE_ROLES2 = 20;
|
|
16539
|
+
function parseAssignableRoles(raw, flag = "--assignable-role") {
|
|
16540
|
+
if (!raw || raw.length === 0) return void 0;
|
|
16541
|
+
if (raw.length > MAX_ASSIGNABLE_ROLES2) {
|
|
16542
|
+
throw new Error(`${flag} accepts at most ${MAX_ASSIGNABLE_ROLES2} entries; got ${raw.length}.`);
|
|
16543
|
+
}
|
|
16544
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16545
|
+
for (const roleId of raw) {
|
|
16546
|
+
if (!ROLE_ID_RE2.test(roleId)) {
|
|
16547
|
+
throw new Error(
|
|
16548
|
+
`${flag} '${roleId}' is not a valid roleId \u2014 3-31 characters, starting with a lowercase letter and continuing with lowercase letters, digits or '-'.`
|
|
16549
|
+
);
|
|
16550
|
+
}
|
|
16551
|
+
if (seen.has(roleId)) {
|
|
16552
|
+
throw new Error(`${flag} repeats '${roleId}' \u2014 list it once.`);
|
|
16553
|
+
}
|
|
16554
|
+
seen.add(roleId);
|
|
16555
|
+
}
|
|
16556
|
+
return [...raw];
|
|
16557
|
+
}
|
|
16558
|
+
|
|
16559
|
+
// src/verb-support.ts
|
|
16560
|
+
async function withAuth(opts, deps, body) {
|
|
16561
|
+
const log = deps.log ?? createLogger2();
|
|
16562
|
+
const resolveAuth = deps.resolveAuth ?? resolveAuthContext;
|
|
16563
|
+
let auth;
|
|
16564
|
+
try {
|
|
16565
|
+
auth = await resolveAuth({
|
|
16566
|
+
env: opts.env,
|
|
16567
|
+
baseUrl: opts.baseUrl,
|
|
16568
|
+
token: opts.token,
|
|
16569
|
+
context: opts.context,
|
|
16570
|
+
provisioning: opts.provisioning,
|
|
16571
|
+
tenant: opts.tenant,
|
|
16572
|
+
useScopedToken: opts.useScopedToken,
|
|
16573
|
+
log
|
|
16574
|
+
});
|
|
16575
|
+
} catch (err) {
|
|
16576
|
+
if (err instanceof MissingCredentialError) {
|
|
16577
|
+
process.stderr.write(` \u2716 ${err.message}
|
|
16578
|
+
`);
|
|
16579
|
+
return 2;
|
|
16580
|
+
}
|
|
16581
|
+
process.stderr.write(` \u2716 ${formatError(err)}
|
|
16582
|
+
`);
|
|
16583
|
+
return 1;
|
|
16584
|
+
}
|
|
16585
|
+
return body(auth, log);
|
|
16586
|
+
}
|
|
16587
|
+
function requireTenantId(auth) {
|
|
16588
|
+
if (!auth.tenantId) {
|
|
16589
|
+
throw new Error(
|
|
16590
|
+
"Could not resolve tenantId from /v1/ping \u2014 the extended ping (tenant_id) must be deployed."
|
|
16591
|
+
);
|
|
16592
|
+
}
|
|
16593
|
+
return auth.tenantId;
|
|
16594
|
+
}
|
|
16595
|
+
function normalizePrincipalUserId(principal) {
|
|
16596
|
+
return principal.startsWith("usr_") ? principal.slice("usr_".length) : principal;
|
|
16597
|
+
}
|
|
16598
|
+
function splitCsv(csv) {
|
|
16599
|
+
return csv.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
16600
|
+
}
|
|
16601
|
+
function parseScopeOverrides(scopes) {
|
|
16602
|
+
if (!scopes || scopes.length === 0) return void 0;
|
|
16603
|
+
const out = {};
|
|
16604
|
+
for (const raw of scopes) {
|
|
16605
|
+
const sep = raw.indexOf(":");
|
|
16606
|
+
if (sep <= 0 || sep === raw.length - 1) {
|
|
16607
|
+
throw new Error(`--scope must be '<namespace>:<value>' (e.g. 'org:6ba7...'); got '${raw}'.`);
|
|
16608
|
+
}
|
|
16609
|
+
const ns = raw.slice(0, sep);
|
|
16610
|
+
const value = raw.slice(sep + 1);
|
|
16611
|
+
const valueError = valueGrammarError(value);
|
|
16612
|
+
if (valueError) throw new Error(`--scope value ${valueError}`);
|
|
16613
|
+
const key = `scope:${ns}`;
|
|
16614
|
+
if (Object.prototype.hasOwnProperty.call(out, key)) {
|
|
16615
|
+
throw new Error(`--scope sets namespace '${ns}' more than once \u2014 a scope override takes at most one value per namespace.`);
|
|
16616
|
+
}
|
|
16617
|
+
out[key] = value;
|
|
16618
|
+
}
|
|
16619
|
+
if (Object.keys(out).length > 2) {
|
|
16620
|
+
throw new Error("--scope allows at most two distinct namespaces.");
|
|
16621
|
+
}
|
|
16622
|
+
return out;
|
|
16623
|
+
}
|
|
16624
|
+
function parseJsonObjectFlag(flag, raw) {
|
|
16625
|
+
let parsed;
|
|
16626
|
+
try {
|
|
16627
|
+
parsed = JSON.parse(raw);
|
|
16628
|
+
} catch {
|
|
16629
|
+
throw new Error(`${flag} is not valid JSON: ${raw}`);
|
|
16630
|
+
}
|
|
16631
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
16632
|
+
throw new Error(`${flag} must be a JSON object (e.g. '{"userId":["<id>"]}').`);
|
|
16633
|
+
}
|
|
16634
|
+
return parsed;
|
|
16635
|
+
}
|
|
16636
|
+
|
|
16637
|
+
// src/paginate.ts
|
|
16638
|
+
var PAGE_LIMIT = 100;
|
|
16639
|
+
async function drainPages(fetch2) {
|
|
16640
|
+
const out = [];
|
|
16641
|
+
let startFrom;
|
|
16642
|
+
for (; ; ) {
|
|
16643
|
+
const page = await fetch2(startFrom);
|
|
16644
|
+
if (page.data) out.push(...page.data);
|
|
16645
|
+
const next = page.nextCursor;
|
|
16646
|
+
if (!next) break;
|
|
16647
|
+
startFrom = next;
|
|
16648
|
+
}
|
|
16649
|
+
return out;
|
|
16650
|
+
}
|
|
16651
|
+
|
|
16652
|
+
// src/loader.ts
|
|
16653
|
+
function actionOf(created) {
|
|
16654
|
+
return created ? "created" : "reconciled";
|
|
16655
|
+
}
|
|
16656
|
+
function triggerPrincipalExternalId(blueprintName, triggerName) {
|
|
16657
|
+
return `${blueprintName}-trigger-${triggerName}`;
|
|
16658
|
+
}
|
|
16659
|
+
var SCRIPT_CONFLICT_RETRY_DELAY_MS = 750;
|
|
16660
|
+
function isVersionConflict(err) {
|
|
16661
|
+
return typeof err === "object" && err !== null && err.statusCode === 409;
|
|
16662
|
+
}
|
|
16663
|
+
async function readLatestScriptVersion(ctxClient, name) {
|
|
16664
|
+
const versions = await drainPages(
|
|
16665
|
+
(startFrom) => ctxClient.scripts.listScripts({ name, startFrom, limit: PAGE_LIMIT })
|
|
16666
|
+
);
|
|
16667
|
+
return versions.reduce((best, row) => {
|
|
16668
|
+
if (row.scriptVersion === void 0) return best;
|
|
16669
|
+
if (best === void 0 || best.scriptVersion === void 0) return row;
|
|
16670
|
+
return row.scriptVersion > best.scriptVersion ? row : best;
|
|
16671
|
+
}, void 0);
|
|
16672
|
+
}
|
|
16673
|
+
function requireScriptId(row, name) {
|
|
16674
|
+
if (!row.id) {
|
|
16675
|
+
throw new Error(`Script '${name}' listing returned a version with no id.`);
|
|
16676
|
+
}
|
|
16677
|
+
return row.id;
|
|
16678
|
+
}
|
|
16679
|
+
function scriptMatches(stored, declared) {
|
|
16680
|
+
return stored.source === declared.source && (stored.declaredInputContract ?? void 0) === declared.declaredInputContract;
|
|
16681
|
+
}
|
|
16682
|
+
async function ensureScripts(args) {
|
|
16683
|
+
const { ctxClient, scripts, log } = args;
|
|
16684
|
+
const actions = [];
|
|
16685
|
+
for (const [name, entry] of Object.entries(scripts)) {
|
|
16686
|
+
let latest = await readLatestScriptVersion(ctxClient, name);
|
|
16687
|
+
if (latest !== void 0 && scriptMatches(latest, entry)) {
|
|
16688
|
+
actions.push({
|
|
16689
|
+
name,
|
|
16690
|
+
action: "unchanged",
|
|
16691
|
+
id: requireScriptId(latest, name),
|
|
16692
|
+
scriptVersion: latest.scriptVersion ?? 0
|
|
16693
|
+
});
|
|
16694
|
+
log.debug(
|
|
16695
|
+
{ component: "loader", scriptName: name, scriptVersion: latest.scriptVersion },
|
|
16696
|
+
"script already matches the declared source \u2014 no version pushed"
|
|
16697
|
+
);
|
|
16698
|
+
continue;
|
|
16699
|
+
}
|
|
16700
|
+
const body = { name, source: entry.source, declaredInputContract: entry.declaredInputContract };
|
|
16701
|
+
let pushed;
|
|
16702
|
+
try {
|
|
16703
|
+
pushed = await ctxClient.scripts.createScript(body);
|
|
16704
|
+
} catch (err) {
|
|
16705
|
+
if (!isVersionConflict(err)) throw err;
|
|
16706
|
+
log.debug(
|
|
16707
|
+
{ component: "loader", scriptName: name },
|
|
16708
|
+
"concurrent push detected (409) \u2014 pausing, then re-reading the current version"
|
|
16709
|
+
);
|
|
16710
|
+
await new Promise((resolve) => setTimeout(resolve, SCRIPT_CONFLICT_RETRY_DELAY_MS));
|
|
16711
|
+
try {
|
|
16712
|
+
latest = await readLatestScriptVersion(ctxClient, name);
|
|
16713
|
+
} catch (readErr) {
|
|
16714
|
+
throw new Error(
|
|
16715
|
+
`Script '${name}': a concurrent push was detected (409) and the follow-up read to resolve it also failed (${readErr instanceof Error ? readErr.message : String(readErr)}). Re-run the apply once the other write has settled.`
|
|
16716
|
+
);
|
|
16717
|
+
}
|
|
16718
|
+
if (latest !== void 0 && scriptMatches(latest, entry)) {
|
|
16719
|
+
actions.push({
|
|
16720
|
+
name,
|
|
16721
|
+
action: "unchanged",
|
|
16722
|
+
id: requireScriptId(latest, name),
|
|
16723
|
+
scriptVersion: latest.scriptVersion ?? 0
|
|
16724
|
+
});
|
|
16725
|
+
log.debug(
|
|
16726
|
+
{ component: "loader", scriptName: name, scriptVersion: latest.scriptVersion },
|
|
16727
|
+
"the concurrent push stored the same source \u2014 converged, nothing to do"
|
|
16728
|
+
);
|
|
16729
|
+
continue;
|
|
16730
|
+
}
|
|
16731
|
+
try {
|
|
16732
|
+
pushed = await ctxClient.scripts.createScript(body);
|
|
16733
|
+
} catch (retryErr) {
|
|
16734
|
+
if (!isVersionConflict(retryErr)) throw retryErr;
|
|
16735
|
+
throw new Error(
|
|
16736
|
+
`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.`
|
|
16737
|
+
);
|
|
16738
|
+
}
|
|
16739
|
+
}
|
|
16740
|
+
if (!pushed.id) {
|
|
16741
|
+
throw new Error(`Script '${name}' push returned no id.`);
|
|
16742
|
+
}
|
|
16743
|
+
const firstVersion = latest === void 0;
|
|
16744
|
+
actions.push({
|
|
16745
|
+
name,
|
|
16746
|
+
action: firstVersion ? "created" : "superseded",
|
|
16747
|
+
id: pushed.id,
|
|
16748
|
+
scriptVersion: pushed.scriptVersion ?? 0
|
|
16749
|
+
});
|
|
16750
|
+
log.debug(
|
|
16751
|
+
{ component: "loader", scriptName: name, scriptVersion: pushed.scriptVersion },
|
|
16752
|
+
firstVersion ? "script pushed \u2014 first version for this name in this context" : "script source changed \u2014 new version pushed over an existing name"
|
|
16753
|
+
);
|
|
16754
|
+
}
|
|
16755
|
+
return actions;
|
|
16756
|
+
}
|
|
16757
|
+
async function pruneUndeclaredTriggers(args) {
|
|
16758
|
+
const { ctxClient, blueprintName, contextId, declaredNames, log } = args;
|
|
16759
|
+
const pruned = [];
|
|
16760
|
+
const skipped = [];
|
|
16761
|
+
let live;
|
|
16762
|
+
try {
|
|
16763
|
+
live = await drainPages(
|
|
16764
|
+
(startFrom) => ctxClient.triggers.listTriggers({ startFrom, limit: PAGE_LIMIT })
|
|
16765
|
+
);
|
|
16766
|
+
} catch (err) {
|
|
16767
|
+
log.warn(
|
|
16768
|
+
{ component: "loader", err: extractServerError(err) },
|
|
16769
|
+
"could not list trigger rules \u2014 skipping the trigger reconcile entirely; a trigger removed from this blueprint may still be live"
|
|
16770
|
+
);
|
|
16771
|
+
return { pruned, skipped: [{ reason: "the context's trigger rules could not be listed" }] };
|
|
16772
|
+
}
|
|
16773
|
+
for (const row of live) {
|
|
16774
|
+
if (!row.name || !row.id || !row.principalId) {
|
|
16775
|
+
if (!declaredNames.has(row.name ?? "")) {
|
|
16776
|
+
skipped.push({
|
|
16777
|
+
name: row.name,
|
|
16778
|
+
reason: "the trigger rule carries no id/name/principal, so it cannot be safely de-provisioned"
|
|
16779
|
+
});
|
|
16780
|
+
}
|
|
16781
|
+
continue;
|
|
16782
|
+
}
|
|
16783
|
+
if (declaredNames.has(row.name)) continue;
|
|
16784
|
+
if (!row.provisionedBy) {
|
|
16785
|
+
skipped.push({
|
|
16786
|
+
name: row.name,
|
|
16787
|
+
reason: "it carries no blueprint provenance marker, so it cannot be attributed to this blueprint"
|
|
16788
|
+
});
|
|
16789
|
+
continue;
|
|
16790
|
+
}
|
|
16791
|
+
if (row.provisionedBy !== blueprintName) {
|
|
16792
|
+
log.debug(
|
|
16793
|
+
{ component: "loader", triggerName: row.name, provisionedBy: row.provisionedBy },
|
|
16794
|
+
"undeclared trigger left untouched \u2014 provisioned by a different blueprint"
|
|
16795
|
+
);
|
|
16796
|
+
continue;
|
|
16797
|
+
}
|
|
16798
|
+
let principalExternalId;
|
|
16799
|
+
try {
|
|
16800
|
+
principalExternalId = (await ctxClient.identity.getUser({ id: normalizePrincipalUserId(row.principalId) })).externalId;
|
|
16801
|
+
} catch (err) {
|
|
16802
|
+
log.warn(
|
|
16803
|
+
{ component: "loader", triggerName: row.name, principalId: row.principalId, err: extractServerError(err) },
|
|
16804
|
+
"could not resolve a trigger's principal \u2014 leaving the trigger in place"
|
|
16805
|
+
);
|
|
16806
|
+
skipped.push({
|
|
16807
|
+
name: row.name,
|
|
16808
|
+
reason: "its service principal could not be read, so ownership could not be fully established"
|
|
16809
|
+
});
|
|
16810
|
+
continue;
|
|
16811
|
+
}
|
|
16812
|
+
if (principalExternalId !== triggerPrincipalExternalId(blueprintName, row.name)) {
|
|
16813
|
+
log.warn(
|
|
16814
|
+
{ component: "loader", triggerName: row.name, principalId: row.principalId },
|
|
16815
|
+
"undeclared trigger left untouched \u2014 it carries this blueprint's provenance marker but names a principal this blueprint did not mint for it"
|
|
16816
|
+
);
|
|
16817
|
+
skipped.push({
|
|
16818
|
+
name: row.name,
|
|
16819
|
+
reason: "it names a service principal this blueprint did not mint for a trigger of that name, so its provenance is inconsistent"
|
|
16820
|
+
});
|
|
16821
|
+
continue;
|
|
16822
|
+
}
|
|
16823
|
+
let profileDeleted = true;
|
|
16824
|
+
try {
|
|
16825
|
+
await ctxClient.auth.deleteAccessProfile({ contextId, principalId: row.principalId });
|
|
16826
|
+
} catch (err) {
|
|
16827
|
+
if (!isNotFound(err)) {
|
|
16828
|
+
log.warn(
|
|
16829
|
+
{ component: "loader", triggerName: row.name, principalId: row.principalId, err: extractServerError(err) },
|
|
16830
|
+
"could not delete a removed trigger's access profile \u2014 leaving the trigger rule in place so the next apply retries"
|
|
16831
|
+
);
|
|
16832
|
+
skipped.push({ name: row.name, reason: "its access profile could not be deleted, so its grant may still be live" });
|
|
16833
|
+
continue;
|
|
16834
|
+
}
|
|
16835
|
+
profileDeleted = false;
|
|
16836
|
+
}
|
|
16837
|
+
try {
|
|
16838
|
+
await ctxClient.triggers.deleteTrigger({ id: row.id });
|
|
16839
|
+
} catch (err) {
|
|
16840
|
+
log.warn(
|
|
16841
|
+
{ component: "loader", triggerName: row.name, err: extractServerError(err) },
|
|
16842
|
+
"a removed trigger's access profile was deleted but the rule itself was not \u2014 it can no longer execute, and the next apply will retry the rule"
|
|
16843
|
+
);
|
|
16844
|
+
skipped.push({ name: row.name, reason: "its grant was removed but the rule row itself could not be deleted" });
|
|
16845
|
+
continue;
|
|
16846
|
+
}
|
|
16847
|
+
pruned.push({ name: row.name, principalId: row.principalId, profileDeleted });
|
|
16848
|
+
log.debug(
|
|
16849
|
+
{ component: "loader", triggerName: row.name, principalId: row.principalId, profileDeleted },
|
|
16850
|
+
"undeclared trigger de-provisioned (no longer declared by this blueprint)"
|
|
16851
|
+
);
|
|
16852
|
+
}
|
|
16853
|
+
return { pruned, skipped };
|
|
16854
|
+
}
|
|
16855
|
+
function isNotFound(err) {
|
|
16856
|
+
return typeof err === "object" && err !== null && err.statusCode === 404;
|
|
14975
16857
|
}
|
|
14976
16858
|
async function registerNamespaceIdempotently(client, blueprintContextId, decl) {
|
|
14977
16859
|
const contextIdForRequest = decl.tenantWide ? void 0 : blueprintContextId;
|
|
@@ -15075,6 +16957,10 @@ function toSdkFields(blueprint, schemaIndex) {
|
|
|
15075
16957
|
required: f.required,
|
|
15076
16958
|
searchable: f.searchable,
|
|
15077
16959
|
filterable: f.filterable,
|
|
16960
|
+
// Forwarded explicitly, like every sibling here: this mapping is a named-property list, not a
|
|
16961
|
+
// spread, so an unforwarded field is dropped SILENTLY rather than failing — which is what
|
|
16962
|
+
// happened to `validation` before it was added below.
|
|
16963
|
+
inline: f.inline,
|
|
15078
16964
|
description: f.description,
|
|
15079
16965
|
enumValues: mapEnumValues(f.enumValues),
|
|
15080
16966
|
// Format passthrough — the loader previously DROPPED this (the
|
|
@@ -15130,13 +17016,16 @@ function toCapabilities(capabilities) {
|
|
|
15130
17016
|
if (!capabilities) return void 0;
|
|
15131
17017
|
const out = {};
|
|
15132
17018
|
if (capabilities.auditHistory !== void 0) out.auditHistory = capabilities.auditHistory;
|
|
17019
|
+
if (capabilities.triggersEnabled !== void 0)
|
|
17020
|
+
out.triggersEnabled = capabilities.triggersEnabled;
|
|
15133
17021
|
return Object.keys(out).length > 0 ? out : void 0;
|
|
15134
17022
|
}
|
|
15135
17023
|
function toRoleScopeClauses(clauses) {
|
|
15136
17024
|
return clauses.map((c) => ({
|
|
15137
17025
|
allowed_actions: c.allowedActions,
|
|
15138
17026
|
data_scope: c.dataScope,
|
|
15139
|
-
granted_capabilities: c.capabilities
|
|
17027
|
+
granted_capabilities: c.capabilities,
|
|
17028
|
+
assignable_roles: c.assignableRoles
|
|
15140
17029
|
}));
|
|
15141
17030
|
}
|
|
15142
17031
|
var UnresolvedIdentitiesError = class extends Error {
|
|
@@ -15173,7 +17062,13 @@ async function provisionContext(client, input) {
|
|
|
15173
17062
|
const { blueprint, allowTenantWideNamespaces = false, readExistingKey, confirmExistingPrincipal, log } = input;
|
|
15174
17063
|
assertBlueprintGates(blueprint);
|
|
15175
17064
|
assertTenantWideNamespacesAllowed(blueprint, allowTenantWideNamespaces);
|
|
15176
|
-
const ctxResp = await client.auth.createAppContext({
|
|
17065
|
+
const ctxResp = await client.auth.createAppContext({
|
|
17066
|
+
body: {
|
|
17067
|
+
contextId: blueprint.contextId,
|
|
17068
|
+
name: contextNameOf(blueprint),
|
|
17069
|
+
...blueprint.identityProjectionClaims === void 0 ? {} : { identityProjectionClaims: blueprint.identityProjectionClaims }
|
|
17070
|
+
}
|
|
17071
|
+
});
|
|
15177
17072
|
const contextAction = actionOf(ctxResp.created);
|
|
15178
17073
|
log.debug({ component: "loader", contextId: blueprint.contextId, contextAction }, "app-context ensured");
|
|
15179
17074
|
const user = await client.identity.createUser({
|
|
@@ -15260,7 +17155,8 @@ async function runLoader(client, input) {
|
|
|
15260
17155
|
subClaim: issuer.subClaim,
|
|
15261
17156
|
emailClaim: issuer.emailClaim,
|
|
15262
17157
|
userinfoUri: issuer.userinfoUri,
|
|
15263
|
-
selfSignupPolicies
|
|
17158
|
+
selfSignupPolicies,
|
|
17159
|
+
capturedClaims: issuer.capturedClaims
|
|
15264
17160
|
});
|
|
15265
17161
|
const action = actionOf(issuerResp.created);
|
|
15266
17162
|
issuerActions.push({ issuerId: issuer.issuerId, action });
|
|
@@ -15276,7 +17172,8 @@ async function runLoader(client, input) {
|
|
|
15276
17172
|
subClaim: issuer.subClaim,
|
|
15277
17173
|
emailClaim: issuer.emailClaim,
|
|
15278
17174
|
userinfoUri: issuer.userinfoUri,
|
|
15279
|
-
selfSignupPolicies
|
|
17175
|
+
selfSignupPolicies,
|
|
17176
|
+
capturedClaims: issuer.capturedClaims
|
|
15280
17177
|
});
|
|
15281
17178
|
log.debug({ component: "loader", issuerId: issuer.issuerId }, "issuer already registered \u2014 reconciled safe fields to the declared values");
|
|
15282
17179
|
} else {
|
|
@@ -15357,7 +17254,10 @@ async function runLoader(client, input) {
|
|
|
15357
17254
|
data_scope: blueprint.accessProfile.dataScope,
|
|
15358
17255
|
// Already gate-validated (assertBlueprintGates, above this function's
|
|
15359
17256
|
// call site) before this body is ever built.
|
|
15360
|
-
granted_capabilities: blueprint.accessProfile.capabilities
|
|
17257
|
+
granted_capabilities: blueprint.accessProfile.capabilities,
|
|
17258
|
+
// Absent means "no roleId restriction" server-side; an empty list is refused. Undeclared
|
|
17259
|
+
// must therefore stay `undefined` rather than becoming `[]`.
|
|
17260
|
+
assignable_roles: blueprint.accessProfile.assignableRoles
|
|
15361
17261
|
}
|
|
15362
17262
|
],
|
|
15363
17263
|
// Optional identity overrides — scope values the service key STAMPS onto
|
|
@@ -15380,6 +17280,109 @@ async function runLoader(client, input) {
|
|
|
15380
17280
|
body: profileBody
|
|
15381
17281
|
});
|
|
15382
17282
|
log.debug({ component: "loader", principalId: `usr_${userId}`, accessProfileId: profile.id, profileAction }, "access profile ensured + reconciled to declared scopes");
|
|
17283
|
+
const scriptActions = blueprint.scripts ? await ensureScripts({ ctxClient, scripts: blueprint.scripts, log }) : [];
|
|
17284
|
+
const triggerActions = [];
|
|
17285
|
+
for (const [triggerName, entry] of Object.entries(blueprint.triggers ?? {})) {
|
|
17286
|
+
const schemaId = schemaIds[entry.firingSource.schemaName];
|
|
17287
|
+
if (!schemaId) {
|
|
17288
|
+
throw new Error(
|
|
17289
|
+
`Trigger '${triggerName}' names schema '${entry.firingSource.schemaName}', which has no resolved schemaId \u2014 the schema failed to provision.`
|
|
17290
|
+
);
|
|
17291
|
+
}
|
|
17292
|
+
const triggerUser = await client.identity.createUser({
|
|
17293
|
+
body: {
|
|
17294
|
+
externalId: triggerPrincipalExternalId(blueprint.name, triggerName),
|
|
17295
|
+
type: "SERVICE",
|
|
17296
|
+
payload: { displayName: `Trigger: ${triggerName}` }
|
|
17297
|
+
}
|
|
17298
|
+
});
|
|
17299
|
+
const triggerUserId = triggerUser.id;
|
|
17300
|
+
if (!triggerUserId) {
|
|
17301
|
+
throw new Error(`Trigger '${triggerName}' service-principal create returned no user id.`);
|
|
17302
|
+
}
|
|
17303
|
+
const triggerPlaceholderProfileBody = {
|
|
17304
|
+
principalId: `usr_${triggerUserId}`,
|
|
17305
|
+
scopes: [{ allowed_actions: ["triggers:r"] }]
|
|
17306
|
+
};
|
|
17307
|
+
const triggerProfile = await ctxClient.auth.createAccessProfile({
|
|
17308
|
+
contextId: blueprint.contextId,
|
|
17309
|
+
body: triggerPlaceholderProfileBody
|
|
17310
|
+
});
|
|
17311
|
+
log.debug(
|
|
17312
|
+
{ component: "loader", triggerName, principalId: `usr_${triggerUserId}`, accessProfileId: triggerProfile.id },
|
|
17313
|
+
"trigger principal + placeholder access profile ensured (no declared grant attached yet)"
|
|
17314
|
+
);
|
|
17315
|
+
const triggerResp = await ctxClient.triggers.createTrigger({
|
|
17316
|
+
upsert: true,
|
|
17317
|
+
body: {
|
|
17318
|
+
name: triggerName,
|
|
17319
|
+
firingSource: { schemaId, event: entry.firingSource.event },
|
|
17320
|
+
scriptRef: entry.scriptRef,
|
|
17321
|
+
roleIds: entry.roleIds,
|
|
17322
|
+
scopes: entry.roleIds ? void 0 : [
|
|
17323
|
+
{
|
|
17324
|
+
allowed_actions: entry.allowedActions,
|
|
17325
|
+
data_scope: entry.dataScope,
|
|
17326
|
+
granted_capabilities: entry.capabilities
|
|
17327
|
+
}
|
|
17328
|
+
],
|
|
17329
|
+
manifest: entry.manifest,
|
|
17330
|
+
// REQUIRED on create. `[]` is a real declaration ("project nothing beyond input.recordId"),
|
|
17331
|
+
// not an omission, so it is forwarded as-is rather than collapsed to undefined the way an
|
|
17332
|
+
// absent optional would be — the blueprint schema makes it required precisely so an author
|
|
17333
|
+
// STATES it.
|
|
17334
|
+
fields: entry.fields,
|
|
17335
|
+
principalId: `usr_${triggerUserId}`,
|
|
17336
|
+
// The PROVENANCE MARKER the reconcile-by-absence pass gates its deletes on. Stamped on every
|
|
17337
|
+
// apply (not only the first) so a trigger provisioned before this field existed acquires one
|
|
17338
|
+
// the next time its blueprint is applied — the platform accepts a set-when-absent and refuses
|
|
17339
|
+
// a CHANGE, so re-stating it is idempotent and can never re-point someone else's trigger.
|
|
17340
|
+
provisionedBy: blueprint.name
|
|
17341
|
+
}
|
|
17342
|
+
});
|
|
17343
|
+
triggerActions.push({ name: triggerName, action: actionOf(triggerResp.created) });
|
|
17344
|
+
log.debug({ component: "loader", triggerName, action: actionOf(triggerResp.created) }, "trigger rule ensured (upsert)");
|
|
17345
|
+
const triggerProfileBody = entry.roleIds ? { principalId: `usr_${triggerUserId}`, roleIds: entry.roleIds } : {
|
|
17346
|
+
principalId: `usr_${triggerUserId}`,
|
|
17347
|
+
scopes: [
|
|
17348
|
+
{
|
|
17349
|
+
// Non-null: the `else` branch of the roleIds XOR above — BlueprintTriggerEntrySchema
|
|
17350
|
+
// guarantees allowedActions is set whenever roleIds is not.
|
|
17351
|
+
allowed_actions: entry.allowedActions,
|
|
17352
|
+
data_scope: entry.dataScope,
|
|
17353
|
+
granted_capabilities: entry.capabilities
|
|
17354
|
+
}
|
|
17355
|
+
]
|
|
17356
|
+
};
|
|
17357
|
+
await ctxClient.auth.updateAccessProfile({
|
|
17358
|
+
contextId: blueprint.contextId,
|
|
17359
|
+
principalId: `usr_${triggerUserId}`,
|
|
17360
|
+
body: triggerProfileBody
|
|
17361
|
+
});
|
|
17362
|
+
log.debug(
|
|
17363
|
+
{ component: "loader", triggerName, principalId: `usr_${triggerUserId}` },
|
|
17364
|
+
"trigger access profile widened to its declared grant"
|
|
17365
|
+
);
|
|
17366
|
+
}
|
|
17367
|
+
let triggerPrunes = [];
|
|
17368
|
+
let triggerPruneSkipped = [];
|
|
17369
|
+
if (input.pruneTriggers === false) {
|
|
17370
|
+
log.debug({ component: "loader" }, "trigger reconcile-by-absence skipped \u2014 disabled by the caller");
|
|
17371
|
+
} else if (!input.remintForContext) {
|
|
17372
|
+
log.warn(
|
|
17373
|
+
{ component: "loader", contextId: blueprint.contextId },
|
|
17374
|
+
"trigger reconcile-by-absence skipped \u2014 no per-context client is wired, so a removed trigger may still be live"
|
|
17375
|
+
);
|
|
17376
|
+
triggerPruneSkipped = [{ reason: "no per-context client was wired, so triggers could not be reconciled in the blueprint's own context" }];
|
|
17377
|
+
} else {
|
|
17378
|
+
({ pruned: triggerPrunes, skipped: triggerPruneSkipped } = await pruneUndeclaredTriggers({
|
|
17379
|
+
ctxClient,
|
|
17380
|
+
blueprintName: blueprint.name,
|
|
17381
|
+
contextId: blueprint.contextId,
|
|
17382
|
+
declaredNames: new Set(Object.keys(blueprint.triggers ?? {})),
|
|
17383
|
+
log
|
|
17384
|
+
}));
|
|
17385
|
+
}
|
|
15383
17386
|
const keyName = keyNameFor(blueprint.name, machineId);
|
|
15384
17387
|
let keyAction = "created";
|
|
15385
17388
|
let revokedKeyId;
|
|
@@ -15501,6 +17504,10 @@ async function runLoader(client, input) {
|
|
|
15501
17504
|
profileAction,
|
|
15502
17505
|
schemaActions,
|
|
15503
17506
|
roleActions,
|
|
17507
|
+
scriptActions,
|
|
17508
|
+
triggerActions,
|
|
17509
|
+
triggerPrunes,
|
|
17510
|
+
triggerPruneSkipped,
|
|
15504
17511
|
issuerActions,
|
|
15505
17512
|
namespaceActions,
|
|
15506
17513
|
rawKey,
|
|
@@ -15521,7 +17528,11 @@ function renderPlanDataScope(ds) {
|
|
|
15521
17528
|
}
|
|
15522
17529
|
function buildPlan(blueprint, opts = {}) {
|
|
15523
17530
|
const steps = [];
|
|
15524
|
-
|
|
17531
|
+
const projection = blueprint.identityProjectionClaims?.length ? `, identity projection: [${blueprint.identityProjectionClaims.join(", ")}]` : blueprint.identityProjectionClaims ? ", identity projection: disabled (empty list)" : "";
|
|
17532
|
+
steps.push({
|
|
17533
|
+
kind: "context",
|
|
17534
|
+
summary: `context '${blueprint.contextId}' (${contextNameOf(blueprint)}${projection})`
|
|
17535
|
+
});
|
|
15525
17536
|
steps.push({
|
|
15526
17537
|
kind: "principal",
|
|
15527
17538
|
summary: `principal '${blueprint.servicePrincipal.externalId}' (SERVICE)`
|
|
@@ -15537,9 +17548,10 @@ function buildPlan(blueprint, opts = {}) {
|
|
|
15537
17548
|
}
|
|
15538
17549
|
for (const iss of blueprint.issuers ?? []) {
|
|
15539
17550
|
const selfSignup = iss.selfSignupPolicies?.length ? `, self-signup: ${iss.selfSignupPolicies.length} polic${iss.selfSignupPolicies.length === 1 ? "y" : "ies"}` : "";
|
|
17551
|
+
const captured = iss.capturedClaims?.length ? `, captured claims: [${iss.capturedClaims.join(", ")}]` : "";
|
|
15540
17552
|
steps.push({
|
|
15541
17553
|
kind: "issuer",
|
|
15542
|
-
summary: `issuer '${iss.issuerId}' (${iss.issuer} \u2192 context '${iss.contextId}'${selfSignup}) [${BLUEPRINT_FIELD_PHASES.issuers}-token phase]`
|
|
17554
|
+
summary: `issuer '${iss.issuerId}' (${iss.issuer} \u2192 context '${iss.contextId}'${selfSignup}${captured}) [${BLUEPRINT_FIELD_PHASES.issuers}-token phase]`
|
|
15543
17555
|
});
|
|
15544
17556
|
}
|
|
15545
17557
|
for (const s of blueprint.schemas) {
|
|
@@ -15561,7 +17573,8 @@ function buildPlan(blueprint, opts = {}) {
|
|
|
15561
17573
|
const roleCapabilityNote = capSet.length ? `, capabilities [${capSet.join(", ")}]` : "";
|
|
15562
17574
|
const clauseLines = clauses.map((c, i) => {
|
|
15563
17575
|
const capNote = c.capabilities?.length ? `, capabilities [${c.capabilities.join(", ")}]` : "";
|
|
15564
|
-
|
|
17576
|
+
const assignableNote = c.assignableRoles?.length ? `, assignableRoles [${c.assignableRoles.join(", ")}]` : "";
|
|
17577
|
+
return ` clause ${i + 1}: actions [${c.allowedActions.join(", ")}], dataScope: ${renderPlanDataScope(c.dataScope)}${capNote}${assignableNote}`;
|
|
15565
17578
|
});
|
|
15566
17579
|
const assumable = blueprint.roleAssumable?.[roleId];
|
|
15567
17580
|
const assumableLine = assumable ? `
|
|
@@ -15591,6 +17604,43 @@ function buildPlan(blueprint, opts = {}) {
|
|
|
15591
17604
|
summary: `profile scopes [${actions.join(", ")}]${planeNote}${scopeNote}${capabilityNote}`
|
|
15592
17605
|
});
|
|
15593
17606
|
}
|
|
17607
|
+
for (const [scriptName, entry] of Object.entries(blueprint.scripts ?? {})) {
|
|
17608
|
+
const bytes = new TextEncoder().encode(entry.source).length;
|
|
17609
|
+
const contractNote = entry.declaredInputContract ? " + input contract" : "";
|
|
17610
|
+
steps.push({
|
|
17611
|
+
kind: "script",
|
|
17612
|
+
summary: `script '${scriptName}' (ensure latest matches ${bytes} bytes of source${contractNote}) [${BLUEPRINT_FIELD_PHASES.scripts}-token phase]`
|
|
17613
|
+
});
|
|
17614
|
+
}
|
|
17615
|
+
for (const [triggerName, entry] of Object.entries(blueprint.triggers ?? {})) {
|
|
17616
|
+
steps.push({
|
|
17617
|
+
kind: "principal",
|
|
17618
|
+
summary: `trigger '${triggerName}' principal '${blueprint.name}-trigger-${triggerName}' (SERVICE)`
|
|
17619
|
+
});
|
|
17620
|
+
steps.push({
|
|
17621
|
+
kind: "profile",
|
|
17622
|
+
summary: `trigger '${triggerName}' profile: minimal placeholder grant (minted before the trigger row exists)`
|
|
17623
|
+
});
|
|
17624
|
+
const manifestNote = entry.manifest?.length ? ` manifest: [${entry.manifest.join(", ")}]` : " manifest: (none \u2014 full grant applies)";
|
|
17625
|
+
const fieldsNote = entry.fields?.length ? ` projects: [${entry.fields.join(", ")}]` : " projects: (nothing beyond the record id)";
|
|
17626
|
+
steps.push({
|
|
17627
|
+
kind: "trigger",
|
|
17628
|
+
summary: `trigger '${triggerName}': ${entry.firingSource.schemaName}.${entry.firingSource.event} \u2192 ${entry.scriptRef.name}@${entry.scriptRef.version}${fieldsNote}${manifestNote} [${BLUEPRINT_FIELD_PHASES.triggers}-token phase]`
|
|
17629
|
+
});
|
|
17630
|
+
if (entry.roleIds) {
|
|
17631
|
+
steps.push({
|
|
17632
|
+
kind: "profile",
|
|
17633
|
+
summary: `trigger '${triggerName}' profile widened via role(s) [${entry.roleIds.join(", ")}] (composed)`
|
|
17634
|
+
});
|
|
17635
|
+
} else {
|
|
17636
|
+
const scopeNote = entry.dataScope && Object.keys(entry.dataScope).length ? ` dataScope: ${Object.keys(entry.dataScope).join(", ")}` : "";
|
|
17637
|
+
const capabilityNote = entry.capabilities?.length ? ` capabilities: [${entry.capabilities.join(", ")}]` : "";
|
|
17638
|
+
steps.push({
|
|
17639
|
+
kind: "profile",
|
|
17640
|
+
summary: `trigger '${triggerName}' profile widened to declared scopes [${(entry.allowedActions ?? []).join(", ")}]${scopeNote}${capabilityNote}`
|
|
17641
|
+
});
|
|
17642
|
+
}
|
|
17643
|
+
}
|
|
15594
17644
|
steps.push({ kind: "key", summary: `key ${blueprint.name}-<machine> (mint 1 scoped ssk_*)` });
|
|
15595
17645
|
const seeds = blueprint.seed ?? [];
|
|
15596
17646
|
if (seeds.length > 0 && !opts.noSeed) {
|
|
@@ -15606,9 +17656,27 @@ function buildPlan(blueprint, opts = {}) {
|
|
|
15606
17656
|
function renderPlan(blueprint, steps) {
|
|
15607
17657
|
const header = ` Plan for blueprint '${blueprint.name}' (v${blueprint.version}) \u2192 context '${blueprint.contextId}':`;
|
|
15608
17658
|
const legendLines = [
|
|
15609
|
-
` Apply is a
|
|
15610
|
-
`
|
|
17659
|
+
` Apply is a reconcile \u2014 each item is created if absent, else updated in place to`,
|
|
17660
|
+
` match. Nothing is recreated, and nothing is dropped except as noted below.`
|
|
15611
17661
|
];
|
|
17662
|
+
legendLines.push(
|
|
17663
|
+
` Exception: triggers are also reconciled by ABSENCE \u2014 a trigger this blueprint`,
|
|
17664
|
+
` previously provisioned and no longer declares is DELETED (the rule and its access`,
|
|
17665
|
+
` profile; its service principal is kept). This applies even when this plan shows no`,
|
|
17666
|
+
` trigger steps: removing the last one is exactly what leaves none to show. Triggers`,
|
|
17667
|
+
` created outside this blueprint are never touched. Each deletion is named in the`,
|
|
17668
|
+
` apply output.`
|
|
17669
|
+
);
|
|
17670
|
+
legendLines.push(
|
|
17671
|
+
` Counter-exception: a script is never updated in place \u2014 a changed source ADDS a new`,
|
|
17672
|
+
` immutable version and every earlier one persists, which is also how a 'latest' rule`,
|
|
17673
|
+
` picks the change up. And scripts are NOT reconciled by absence: a script this blueprint`,
|
|
17674
|
+
` previously shipped and no longer declares is LEFT IN PLACE, including every stored`,
|
|
17675
|
+
` version. A script row carries no provisioning marker, so an apply cannot tell one it`,
|
|
17676
|
+
` created from one you pushed by hand \u2014 and a wrong delete is unrecoverable and would`,
|
|
17677
|
+
` break any trigger still referencing it. Remove one deliberately with`,
|
|
17678
|
+
` 'vectros scripts delete', after 'vectros scripts list' shows you what is there.`
|
|
17679
|
+
);
|
|
15612
17680
|
if (steps.some((s) => s.kind === "issuer")) {
|
|
15613
17681
|
legendLines.push(
|
|
15614
17682
|
` Exception: an issuer's trust-anchor fields (issuer, jwksUri, audience,`,
|
|
@@ -17860,6 +19928,19 @@ function namespaceTally(items) {
|
|
|
17860
19928
|
if (matched) seg.push(`${matched} already registered \u2014 confirmed matching`);
|
|
17861
19929
|
return `${items.length} namespace${items.length === 1 ? "" : "s"} (${seg.join(", ")})`;
|
|
17862
19930
|
}
|
|
19931
|
+
function scriptTally(items) {
|
|
19932
|
+
if (items.length === 0) return null;
|
|
19933
|
+
const created = items.filter((i) => i.action === "created");
|
|
19934
|
+
const superseded = items.filter((i) => i.action === "superseded");
|
|
19935
|
+
const unchanged = items.length - created.length - superseded.length;
|
|
19936
|
+
const seg = [];
|
|
19937
|
+
if (created.length) seg.push(`${created.length} first version${created.length === 1 ? "" : "s"} pushed`);
|
|
19938
|
+
if (superseded.length) {
|
|
19939
|
+
seg.push(`${superseded.map((i) => `'${i.name}'`).join(", ")} superseded (a new version now current)`);
|
|
19940
|
+
}
|
|
19941
|
+
if (unchanged) seg.push(`${unchanged} already current \u2014 nothing written`);
|
|
19942
|
+
return `${items.length} script${items.length === 1 ? "" : "s"} (${seg.join("; ")})`;
|
|
19943
|
+
}
|
|
17863
19944
|
function applyOutcomeSummary(result) {
|
|
17864
19945
|
const parts = [
|
|
17865
19946
|
reconcileTally(result.schemaActions, "schema"),
|
|
@@ -17868,13 +19949,34 @@ function applyOutcomeSummary(result) {
|
|
|
17868
19949
|
// 'reconciled' mean the same "updated to match" as every other kind here.
|
|
17869
19950
|
reconcileTally(result.issuerActions, "issuer"),
|
|
17870
19951
|
namespaceTally(result.namespaceActions),
|
|
19952
|
+
// Scripts — reported BEFORE triggers, mirroring the order they are provisioned in, and with
|
|
19953
|
+
// their own wording because "reconciled" would be a lie: a script version is immutable.
|
|
19954
|
+
scriptTally(result.scriptActions),
|
|
19955
|
+
// Trigger rules — 'reconciled' means the same "upsert applied the declared shape" as
|
|
19956
|
+
// schemas/roles (createTrigger's own upsert:true reconcile), so the shared reconcileTally
|
|
19957
|
+
// wording applies unchanged; buildPlan's dry-run preview already discloses each trigger's own
|
|
19958
|
+
// steps individually, this is the apply-time tally counterpart.
|
|
19959
|
+
reconcileTally(result.triggerActions, "trigger"),
|
|
17871
19960
|
`profile ${result.profileAction}`
|
|
17872
19961
|
].filter((p) => p !== null);
|
|
17873
19962
|
const detail = parts.length ? ` \u2014 ${parts.join("; ")}` : "";
|
|
17874
19963
|
const principalNote = result.userAction === "reconciled" ? ` \u26A0 service principal already existed in this tenant before this run (not newly created)
|
|
17875
19964
|
` : "";
|
|
19965
|
+
const pruneNotes = result.triggerPrunes.map(
|
|
19966
|
+
(p) => ` \u26A0 trigger '${p.name}' removed from the blueprint \u2014 de-provisioned (rule + access profile deleted)
|
|
19967
|
+
` + // Not a warning: 404 on the profile means it was already gone, so the end state is exactly
|
|
19968
|
+
// what a clean prune produces. Said out loud only so the absent second delete isn't read as
|
|
19969
|
+
// a silent skip. Every OTHER profile-delete failure lands in triggerPruneSkipped instead.
|
|
19970
|
+
(p.profileDeleted ? "" : ` (its access profile was already absent \u2014 nothing left to remove)
|
|
19971
|
+
`)
|
|
19972
|
+
).join("");
|
|
19973
|
+
const pruneSkippedNotes = result.triggerPruneSkipped.map(
|
|
19974
|
+
(s) => s.name ? ` \u26A0 trigger '${s.name}' is no longer declared but could NOT be removed \u2014 ${s.reason}. It is still live.
|
|
19975
|
+
` : ` \u26A0 triggers could not be reconciled this run \u2014 ${s.reason}. A trigger removed from this blueprint may still be live.
|
|
19976
|
+
`
|
|
19977
|
+
).join("");
|
|
17876
19978
|
return ` \u2714 context '${result.contextId}' ${result.contextAction}${detail}
|
|
17877
|
-
${principalNote}`;
|
|
19979
|
+
${principalNote}${pruneNotes}${pruneSkippedNotes}`;
|
|
17878
19980
|
}
|
|
17879
19981
|
var BOOTSTRAP_HELP = `vectros bootstrap \u2014 provision a least-privilege MCP credential.
|
|
17880
19982
|
|
|
@@ -18073,7 +20175,7 @@ ${BOOTSTRAP_HELP}
|
|
|
18073
20175
|
process.stderr.write(`
|
|
18074
20176
|
\u2716 Scope gate refused blueprint '${blueprint.name}':
|
|
18075
20177
|
`);
|
|
18076
|
-
for (const r of err.rejected) process.stderr.write(` \u2022 ${r
|
|
20178
|
+
for (const r of err.rejected) process.stderr.write(` \u2022 ${formatRejection(r)}
|
|
18077
20179
|
`);
|
|
18078
20180
|
process.stderr.write(" Minting nothing.\n");
|
|
18079
20181
|
return 2;
|
|
@@ -18082,7 +20184,7 @@ ${BOOTSTRAP_HELP}
|
|
|
18082
20184
|
process.stderr.write(`
|
|
18083
20185
|
\u2716 Capability gate refused blueprint '${blueprint.name}':
|
|
18084
20186
|
`);
|
|
18085
|
-
for (const r of err.rejected) process.stderr.write(` \u2022 ${r
|
|
20187
|
+
for (const r of err.rejected) process.stderr.write(` \u2022 ${formatRejection(r)}
|
|
18086
20188
|
`);
|
|
18087
20189
|
process.stderr.write(" Minting nothing.\n");
|
|
18088
20190
|
return 2;
|
|
@@ -18396,71 +20498,6 @@ ${formatCliError(err, { action: "Bootstrap failed" })}`);
|
|
|
18396
20498
|
// src/cli-blueprint.ts
|
|
18397
20499
|
import { writeFile as writeFile2, access } from "fs/promises";
|
|
18398
20500
|
|
|
18399
|
-
// src/scope-grammar.ts
|
|
18400
|
-
var FORBIDDEN_SCOPE_NAMESPACES2 = /* @__PURE__ */ new Set([
|
|
18401
|
-
"user",
|
|
18402
|
-
"record",
|
|
18403
|
-
"document",
|
|
18404
|
-
"entity",
|
|
18405
|
-
"self",
|
|
18406
|
-
"tenant",
|
|
18407
|
-
"context",
|
|
18408
|
-
"scope",
|
|
18409
|
-
"versions",
|
|
18410
|
-
"lookup"
|
|
18411
|
-
]);
|
|
18412
|
-
var SCOPE_NAMESPACE_RE2 = /^[a-z][a-z0-9_-]{1,31}$/;
|
|
18413
|
-
var SCOPE_NAMESPACE_MIN_LENGTH = 2;
|
|
18414
|
-
var SCOPE_NAMESPACE_MAX_LENGTH = 32;
|
|
18415
|
-
var SCOPE_VALUE_RE2 = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
|
|
18416
|
-
var SCOPE_VALUE_MIN_LENGTH = 1;
|
|
18417
|
-
var SCOPE_VALUE_MAX_LENGTH = 128;
|
|
18418
|
-
function valueGrammarError(value) {
|
|
18419
|
-
if (!SCOPE_VALUE_RE2.test(value)) {
|
|
18420
|
-
return `'${value}' is not a valid scope value \u2014 ${SCOPE_VALUE_MIN_LENGTH}-${SCOPE_VALUE_MAX_LENGTH} characters, starting with a letter or digit and continuing with letters, digits, '_' or '-'. A value may not contain a colon.`;
|
|
18421
|
-
}
|
|
18422
|
-
return null;
|
|
18423
|
-
}
|
|
18424
|
-
function namespaceGrammarError(ns) {
|
|
18425
|
-
if (FORBIDDEN_SCOPE_NAMESPACES2.has(ns)) {
|
|
18426
|
-
return `'${ns}' is a reserved namespace and cannot be used as an ownership scope`;
|
|
18427
|
-
}
|
|
18428
|
-
if (!SCOPE_NAMESPACE_RE2.test(ns)) {
|
|
18429
|
-
return `'${ns}' is not a valid namespace \u2014 lowercase, ${SCOPE_NAMESPACE_MIN_LENGTH}-${SCOPE_NAMESPACE_MAX_LENGTH} characters, starting with a letter and continuing with letters, digits, '_' or '-'`;
|
|
18430
|
-
}
|
|
18431
|
-
return null;
|
|
18432
|
-
}
|
|
18433
|
-
function assertScopeEntries(entries, flag) {
|
|
18434
|
-
if (!entries?.length) return;
|
|
18435
|
-
for (const raw of entries) {
|
|
18436
|
-
const sep = raw.indexOf(":");
|
|
18437
|
-
if (sep <= 0 || sep === raw.length - 1) {
|
|
18438
|
-
throw new Error(`${flag} must be '<namespace>:<value>' (e.g. 'org:6ba7...'); got '${raw}'.`);
|
|
18439
|
-
}
|
|
18440
|
-
const nsError = namespaceGrammarError(raw.slice(0, sep));
|
|
18441
|
-
if (nsError) throw new Error(`${flag} namespace ${nsError}`);
|
|
18442
|
-
const valError = valueGrammarError(raw.slice(sep + 1));
|
|
18443
|
-
if (valError) throw new Error(`${flag} value ${valError}`);
|
|
18444
|
-
}
|
|
18445
|
-
}
|
|
18446
|
-
function assertIdentityOverrideValues(overrides, flag) {
|
|
18447
|
-
if (!overrides) return;
|
|
18448
|
-
for (const [key, value] of Object.entries(overrides)) {
|
|
18449
|
-
if (!key.startsWith("scope:")) continue;
|
|
18450
|
-
const error = valueGrammarError(typeof value === "string" ? value : String(value));
|
|
18451
|
-
if (error) throw new Error(`${flag} '${key}' value ${error}`);
|
|
18452
|
-
}
|
|
18453
|
-
}
|
|
18454
|
-
function placeholderScopeNamespace(value) {
|
|
18455
|
-
const self2 = /^\$\{\{\s*(?:under\.)?self\.scope\.([^\s}]+)\s*\}\}$/.exec(value);
|
|
18456
|
-
if (self2) return self2[1];
|
|
18457
|
-
const member = MEMBER_SCOPE_PLACEHOLDER_RE2.exec(value);
|
|
18458
|
-
return member ? member[1] : null;
|
|
18459
|
-
}
|
|
18460
|
-
var SELF_PLACEHOLDER_RE = /^\$\{\{\s*self\.(?:userId|partnerUserId|scope\.[a-z][a-z0-9_-]{1,31})\s*\}\}$/;
|
|
18461
|
-
var UNDER_SELF_PLACEHOLDER_RE2 = /^\$\{\{\s*under\.self\.(?:userId|partnerUserId|scope\.[a-z][a-z0-9_-]{1,31})\s*\}\}$/;
|
|
18462
|
-
var MEMBER_SCOPE_PLACEHOLDER_RE2 = /^\$\{\{\s*member\.scope\.([a-z][a-z0-9_-]{1,31})(?::[a-z][a-z0-9_-]{1,31})?\s*\}\}$/;
|
|
18463
|
-
|
|
18464
20501
|
// src/lint.ts
|
|
18465
20502
|
var ENUM_COMPATIBLE_TYPES = /* @__PURE__ */ new Set(["enum", "string", "string[]"]);
|
|
18466
20503
|
var BUILT_IN_SORT_BY = /* @__PURE__ */ new Set(["createdAt", "lastUpdated"]);
|
|
@@ -18609,7 +20646,7 @@ function lintBlueprint(bp) {
|
|
|
18609
20646
|
);
|
|
18610
20647
|
}
|
|
18611
20648
|
for (const roleId of Object.keys(bp.roles ?? {})) {
|
|
18612
|
-
if (!
|
|
20649
|
+
if (!ROLE_ID_RE3.test(roleId)) {
|
|
18613
20650
|
findings.push({
|
|
18614
20651
|
severity: "error",
|
|
18615
20652
|
rule: "role-id-format",
|
|
@@ -18656,7 +20693,7 @@ function lintBlueprint(bp) {
|
|
|
18656
20693
|
}
|
|
18657
20694
|
return findings;
|
|
18658
20695
|
}
|
|
18659
|
-
var
|
|
20696
|
+
var ROLE_ID_RE3 = /^[a-z][a-z0-9-]{2,30}$/;
|
|
18660
20697
|
var PLACEHOLDER_SHAPED_RE = /^\s*\$\{\{.*\}\}\s*$/s;
|
|
18661
20698
|
var RECOGNIZED_PLACEHOLDER_RES = [
|
|
18662
20699
|
/^\$\{\{\s*any\s*\}\}$/,
|
|
@@ -18922,14 +20959,14 @@ async function resolveAndValidate(arg, verb, supplied) {
|
|
|
18922
20959
|
if (err instanceof ScopeGateError) {
|
|
18923
20960
|
process.stderr.write(` \u2716 Scope gate rejected '${blueprint.name}':
|
|
18924
20961
|
`);
|
|
18925
|
-
for (const r of err.rejected) process.stderr.write(` \u2022 ${r
|
|
20962
|
+
for (const r of err.rejected) process.stderr.write(` \u2022 ${formatRejection(r)}
|
|
18926
20963
|
`);
|
|
18927
20964
|
return null;
|
|
18928
20965
|
}
|
|
18929
20966
|
if (err instanceof CapabilityGateError) {
|
|
18930
20967
|
process.stderr.write(` \u2716 Capability gate rejected '${blueprint.name}':
|
|
18931
20968
|
`);
|
|
18932
|
-
for (const r of err.rejected) process.stderr.write(` \u2022 ${r
|
|
20969
|
+
for (const r of err.rejected) process.stderr.write(` \u2022 ${formatRejection(r)}
|
|
18933
20970
|
`);
|
|
18934
20971
|
return null;
|
|
18935
20972
|
}
|
|
@@ -19073,137 +21110,44 @@ async function runBlueprintCli(argv, deps = {}) {
|
|
|
19073
21110
|
args = parseBlueprintArgs(argv);
|
|
19074
21111
|
} catch (err) {
|
|
19075
21112
|
process.stderr.write(`${err instanceof Error ? err.message : String(err)}
|
|
19076
|
-
|
|
19077
|
-
${BLUEPRINT_HELP}
|
|
19078
|
-
`);
|
|
19079
|
-
return 1;
|
|
19080
|
-
}
|
|
19081
|
-
if (args.help) {
|
|
19082
|
-
process.stdout.write(`${BLUEPRINT_HELP}
|
|
19083
|
-
`);
|
|
19084
|
-
return 0;
|
|
19085
|
-
}
|
|
19086
|
-
if (!args.sub) {
|
|
19087
|
-
process.stdout.write(`${BLUEPRINT_HELP}
|
|
19088
|
-
`);
|
|
19089
|
-
return 1;
|
|
19090
|
-
}
|
|
19091
|
-
switch (args.sub) {
|
|
19092
|
-
case "init":
|
|
19093
|
-
return runInit(args);
|
|
19094
|
-
case "validate":
|
|
19095
|
-
return runValidate(args);
|
|
19096
|
-
case "plan":
|
|
19097
|
-
return runPlan(args);
|
|
19098
|
-
case "list":
|
|
19099
|
-
return runList();
|
|
19100
|
-
default:
|
|
19101
|
-
process.stderr.write(
|
|
19102
|
-
`Unknown blueprint subcommand: ${args.sub}. Bundled: ${BLUEPRINT_NAMES.join(", ")}
|
|
19103
|
-
|
|
19104
|
-
${BLUEPRINT_HELP}
|
|
19105
|
-
`
|
|
19106
|
-
);
|
|
19107
|
-
return 1;
|
|
19108
|
-
}
|
|
19109
|
-
}
|
|
19110
|
-
|
|
19111
|
-
// src/program.ts
|
|
19112
|
-
import { Command, Option } from "commander";
|
|
19113
|
-
|
|
19114
|
-
// src/verb-support.ts
|
|
19115
|
-
async function withAuth(opts, deps, body) {
|
|
19116
|
-
const log = deps.log ?? createLogger2();
|
|
19117
|
-
const resolveAuth = deps.resolveAuth ?? resolveAuthContext;
|
|
19118
|
-
let auth;
|
|
19119
|
-
try {
|
|
19120
|
-
auth = await resolveAuth({
|
|
19121
|
-
env: opts.env,
|
|
19122
|
-
baseUrl: opts.baseUrl,
|
|
19123
|
-
token: opts.token,
|
|
19124
|
-
context: opts.context,
|
|
19125
|
-
provisioning: opts.provisioning,
|
|
19126
|
-
tenant: opts.tenant,
|
|
19127
|
-
useScopedToken: opts.useScopedToken,
|
|
19128
|
-
log
|
|
19129
|
-
});
|
|
19130
|
-
} catch (err) {
|
|
19131
|
-
if (err instanceof MissingCredentialError) {
|
|
19132
|
-
process.stderr.write(` \u2716 ${err.message}
|
|
19133
|
-
`);
|
|
19134
|
-
return 2;
|
|
19135
|
-
}
|
|
19136
|
-
process.stderr.write(` \u2716 ${formatError(err)}
|
|
19137
|
-
`);
|
|
19138
|
-
return 1;
|
|
19139
|
-
}
|
|
19140
|
-
return body(auth, log);
|
|
19141
|
-
}
|
|
19142
|
-
function requireTenantId(auth) {
|
|
19143
|
-
if (!auth.tenantId) {
|
|
19144
|
-
throw new Error(
|
|
19145
|
-
"Could not resolve tenantId from /v1/ping \u2014 the extended ping (tenant_id) must be deployed."
|
|
19146
|
-
);
|
|
19147
|
-
}
|
|
19148
|
-
return auth.tenantId;
|
|
19149
|
-
}
|
|
19150
|
-
function normalizePrincipalUserId(principal) {
|
|
19151
|
-
return principal.startsWith("usr_") ? principal.slice("usr_".length) : principal;
|
|
19152
|
-
}
|
|
19153
|
-
function splitCsv(csv) {
|
|
19154
|
-
return csv.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
19155
|
-
}
|
|
19156
|
-
function parseScopeOverrides(scopes) {
|
|
19157
|
-
if (!scopes || scopes.length === 0) return void 0;
|
|
19158
|
-
const out = {};
|
|
19159
|
-
for (const raw of scopes) {
|
|
19160
|
-
const sep = raw.indexOf(":");
|
|
19161
|
-
if (sep <= 0 || sep === raw.length - 1) {
|
|
19162
|
-
throw new Error(`--scope must be '<namespace>:<value>' (e.g. 'org:6ba7...'); got '${raw}'.`);
|
|
19163
|
-
}
|
|
19164
|
-
const ns = raw.slice(0, sep);
|
|
19165
|
-
const value = raw.slice(sep + 1);
|
|
19166
|
-
const valueError = valueGrammarError(value);
|
|
19167
|
-
if (valueError) throw new Error(`--scope value ${valueError}`);
|
|
19168
|
-
const key = `scope:${ns}`;
|
|
19169
|
-
if (Object.prototype.hasOwnProperty.call(out, key)) {
|
|
19170
|
-
throw new Error(`--scope sets namespace '${ns}' more than once \u2014 a scope override takes at most one value per namespace.`);
|
|
19171
|
-
}
|
|
19172
|
-
out[key] = value;
|
|
19173
|
-
}
|
|
19174
|
-
if (Object.keys(out).length > 2) {
|
|
19175
|
-
throw new Error("--scope allows at most two distinct namespaces.");
|
|
19176
|
-
}
|
|
19177
|
-
return out;
|
|
19178
|
-
}
|
|
19179
|
-
function parseJsonObjectFlag(flag, raw) {
|
|
19180
|
-
let parsed;
|
|
19181
|
-
try {
|
|
19182
|
-
parsed = JSON.parse(raw);
|
|
19183
|
-
} catch {
|
|
19184
|
-
throw new Error(`${flag} is not valid JSON: ${raw}`);
|
|
21113
|
+
|
|
21114
|
+
${BLUEPRINT_HELP}
|
|
21115
|
+
`);
|
|
21116
|
+
return 1;
|
|
19185
21117
|
}
|
|
19186
|
-
if (
|
|
19187
|
-
|
|
21118
|
+
if (args.help) {
|
|
21119
|
+
process.stdout.write(`${BLUEPRINT_HELP}
|
|
21120
|
+
`);
|
|
21121
|
+
return 0;
|
|
19188
21122
|
}
|
|
19189
|
-
|
|
19190
|
-
}
|
|
21123
|
+
if (!args.sub) {
|
|
21124
|
+
process.stdout.write(`${BLUEPRINT_HELP}
|
|
21125
|
+
`);
|
|
21126
|
+
return 1;
|
|
21127
|
+
}
|
|
21128
|
+
switch (args.sub) {
|
|
21129
|
+
case "init":
|
|
21130
|
+
return runInit(args);
|
|
21131
|
+
case "validate":
|
|
21132
|
+
return runValidate(args);
|
|
21133
|
+
case "plan":
|
|
21134
|
+
return runPlan(args);
|
|
21135
|
+
case "list":
|
|
21136
|
+
return runList();
|
|
21137
|
+
default:
|
|
21138
|
+
process.stderr.write(
|
|
21139
|
+
`Unknown blueprint subcommand: ${args.sub}. Bundled: ${BLUEPRINT_NAMES.join(", ")}
|
|
19191
21140
|
|
|
19192
|
-
|
|
19193
|
-
|
|
19194
|
-
|
|
19195
|
-
|
|
19196
|
-
let startFrom;
|
|
19197
|
-
for (; ; ) {
|
|
19198
|
-
const page = await fetch2(startFrom);
|
|
19199
|
-
if (page.data) out.push(...page.data);
|
|
19200
|
-
const next = page.nextCursor;
|
|
19201
|
-
if (!next) break;
|
|
19202
|
-
startFrom = next;
|
|
21141
|
+
${BLUEPRINT_HELP}
|
|
21142
|
+
`
|
|
21143
|
+
);
|
|
21144
|
+
return 1;
|
|
19203
21145
|
}
|
|
19204
|
-
return out;
|
|
19205
21146
|
}
|
|
19206
21147
|
|
|
21148
|
+
// src/program.ts
|
|
21149
|
+
import { Command, Option } from "commander";
|
|
21150
|
+
|
|
19207
21151
|
// src/self-principal.ts
|
|
19208
21152
|
var PRINCIPAL_PREFIX_USER = "usr_";
|
|
19209
21153
|
function emailFromJwt(token) {
|
|
@@ -20221,7 +22165,14 @@ function normalizeClauses(raw) {
|
|
|
20221
22165
|
});
|
|
20222
22166
|
if (!isRecognizedOwnershipKey(k)) unrecognizedDims.push(k);
|
|
20223
22167
|
}
|
|
20224
|
-
|
|
22168
|
+
const rawAssignable = obj.assignable_roles ?? obj.assignableRoles;
|
|
22169
|
+
const assignableRoles = Array.isArray(rawAssignable) && rawAssignable.length > 0 ? rawAssignable.map(String) : void 0;
|
|
22170
|
+
out.push({
|
|
22171
|
+
actions: Array.isArray(actions) ? actions.map(String) : [],
|
|
22172
|
+
dataScope,
|
|
22173
|
+
unrecognizedDims,
|
|
22174
|
+
...assignableRoles ? { assignableRoles } : {}
|
|
22175
|
+
});
|
|
20225
22176
|
}
|
|
20226
22177
|
return out;
|
|
20227
22178
|
}
|
|
@@ -20421,7 +22372,10 @@ async function runAccessExplain(opts, deps = {}) {
|
|
|
20421
22372
|
dataScope: renderDataScope(c.dataScope),
|
|
20422
22373
|
// Present only on a dead clause — an unrecognized dim the platform
|
|
20423
22374
|
// denies (a retired key or a bad scope:<ns>); the matrix omits it.
|
|
20424
|
-
...c.unrecognizedDims.length > 0 ? { grantsNothing: true, unrecognizedDims: c.unrecognizedDims } : {}
|
|
22375
|
+
...c.unrecognizedDims.length > 0 ? { grantsNothing: true, unrecognizedDims: c.unrecognizedDims } : {},
|
|
22376
|
+
// Reported only when the clause actually carries the restriction. Absent means
|
|
22377
|
+
// default-open (any role composable), which is a different fact from an empty list.
|
|
22378
|
+
...c.assignableRoles?.length ? { assignableRoles: c.assignableRoles } : {}
|
|
20425
22379
|
})),
|
|
20426
22380
|
matrix: rows,
|
|
20427
22381
|
// Present only when a clause uses `${{ under.self.* }}`: those rows are UNDECIDED
|
|
@@ -20447,6 +22401,12 @@ async function runAccessExplain(opts, deps = {}) {
|
|
|
20447
22401
|
if (c.unrecognizedDims.length > 0) {
|
|
20448
22402
|
process.stdout.write(
|
|
20449
22403
|
` \u2716 grants nothing \u2014 unrecognized dimension${c.unrecognizedDims.length > 1 ? "s" : ""}: ${c.unrecognizedDims.join(", ")} (the platform denies a clause keyed on any dim that is not userId or a valid scope:<namespace>)
|
|
22404
|
+
`
|
|
22405
|
+
);
|
|
22406
|
+
}
|
|
22407
|
+
if (c.assignableRoles?.length) {
|
|
22408
|
+
process.stdout.write(
|
|
22409
|
+
` may compose only these roles into a delegated profile: ${c.assignableRoles.join(", ")}
|
|
20450
22410
|
`
|
|
20451
22411
|
);
|
|
20452
22412
|
}
|
|
@@ -20512,6 +22472,14 @@ async function runRoleCreate(opts, deps = {}) {
|
|
|
20512
22472
|
return 2;
|
|
20513
22473
|
}
|
|
20514
22474
|
}
|
|
22475
|
+
let assignableRoles;
|
|
22476
|
+
try {
|
|
22477
|
+
assignableRoles = parseAssignableRoles(opts.assignableRole);
|
|
22478
|
+
} catch (err) {
|
|
22479
|
+
process.stderr.write(` \u2716 ${formatError(err)}
|
|
22480
|
+
`);
|
|
22481
|
+
return 2;
|
|
22482
|
+
}
|
|
20515
22483
|
return withAuth(opts, deps, async (auth) => {
|
|
20516
22484
|
const client = auth.client;
|
|
20517
22485
|
try {
|
|
@@ -20524,7 +22492,15 @@ async function runRoleCreate(opts, deps = {}) {
|
|
|
20524
22492
|
// data_scope is included only when supplied — the clause wire shape
|
|
20525
22493
|
// stays byte-identical for unscoped roles.
|
|
20526
22494
|
scopes: [
|
|
20527
|
-
|
|
22495
|
+
{
|
|
22496
|
+
allowed_actions: actions,
|
|
22497
|
+
// Each included only when supplied — the clause wire shape stays byte-identical for an
|
|
22498
|
+
// unscoped, unrestricted role. `assignable_roles` in particular must be OMITTED rather
|
|
22499
|
+
// than sent empty: the platform reads absent as "no roleId restriction" and rejects
|
|
22500
|
+
// `[]` outright, so the two are not interchangeable.
|
|
22501
|
+
...dataScope ? { data_scope: dataScope } : {},
|
|
22502
|
+
...assignableRoles ? { assignable_roles: assignableRoles } : {}
|
|
22503
|
+
}
|
|
20528
22504
|
],
|
|
20529
22505
|
// Included only when supplied — omitting it (rather than sending an
|
|
20530
22506
|
// empty object) keeps "no assume grant" and "explicitly empty grant"
|
|
@@ -20783,6 +22759,36 @@ function parseSelfSignupPolicies(raw) {
|
|
|
20783
22759
|
return { signup_type: entry.slice(0, sep), role_id: entry.slice(sep + 1) };
|
|
20784
22760
|
});
|
|
20785
22761
|
}
|
|
22762
|
+
var MAX_CAPTURED_CLAIMS2 = 20;
|
|
22763
|
+
var MAX_CAPTURED_CLAIM_NAME_LENGTH2 = 64;
|
|
22764
|
+
function parseCapturedClaims(raw) {
|
|
22765
|
+
if (!raw || raw.length === 0) return void 0;
|
|
22766
|
+
if (raw.length > MAX_CAPTURED_CLAIMS2) {
|
|
22767
|
+
throw new Error(
|
|
22768
|
+
`--captured-claim accepts at most ${MAX_CAPTURED_CLAIMS2} entries; got ${raw.length}.`
|
|
22769
|
+
);
|
|
22770
|
+
}
|
|
22771
|
+
const seen = /* @__PURE__ */ new Set();
|
|
22772
|
+
for (const name of raw) {
|
|
22773
|
+
if (name.trim().length === 0) {
|
|
22774
|
+
throw new Error("--captured-claim must be a non-blank OIDC claim name (e.g. 'phone_number').");
|
|
22775
|
+
}
|
|
22776
|
+
if (name.length > MAX_CAPTURED_CLAIM_NAME_LENGTH2) {
|
|
22777
|
+
throw new Error(
|
|
22778
|
+
`--captured-claim '${name}' is ${name.length} characters, above the ${MAX_CAPTURED_CLAIM_NAME_LENGTH2}-character limit.`
|
|
22779
|
+
);
|
|
22780
|
+
}
|
|
22781
|
+
if (seen.has(name)) {
|
|
22782
|
+
throw new Error(`--captured-claim repeats '${name}' \u2014 each claim name may be given only once.`);
|
|
22783
|
+
}
|
|
22784
|
+
seen.add(name);
|
|
22785
|
+
}
|
|
22786
|
+
return [...raw];
|
|
22787
|
+
}
|
|
22788
|
+
function renderCapturedClaims(claims) {
|
|
22789
|
+
if (!claims || claims.length === 0) return "\u2014";
|
|
22790
|
+
return claims.join(", ");
|
|
22791
|
+
}
|
|
20786
22792
|
function renderSelfSignup(policies) {
|
|
20787
22793
|
if (!policies || policies.length === 0) return "\u2014";
|
|
20788
22794
|
return policies.map((p) => `${p.signup_type} \u2192 ${p.role_id}`).join(", ");
|
|
@@ -20799,7 +22805,8 @@ function renderIssuer(i) {
|
|
|
20799
22805
|
["userinfoUri", i.userinfoUri],
|
|
20800
22806
|
["status", i.status],
|
|
20801
22807
|
["createdAt", i.createdAt],
|
|
20802
|
-
["selfSignup", renderSelfSignup(i.selfSignupPolicies)]
|
|
22808
|
+
["selfSignup", renderSelfSignup(i.selfSignupPolicies)],
|
|
22809
|
+
["capturedClaims", renderCapturedClaims(i.capturedClaims)]
|
|
20803
22810
|
]);
|
|
20804
22811
|
}
|
|
20805
22812
|
async function runIssuersList(opts, deps = {}) {
|
|
@@ -20820,7 +22827,8 @@ async function runIssuersList(opts, deps = {}) {
|
|
|
20820
22827
|
{ header: "issuer", get: (i) => i.issuer },
|
|
20821
22828
|
{ header: "audience", get: (i) => i.audience },
|
|
20822
22829
|
{ header: "status", get: (i) => i.status },
|
|
20823
|
-
{ header: "self-signup", get: (i) => renderSelfSignup(i.selfSignupPolicies) }
|
|
22830
|
+
{ header: "self-signup", get: (i) => renderSelfSignup(i.selfSignupPolicies) },
|
|
22831
|
+
{ header: "captured claims", get: (i) => renderCapturedClaims(i.capturedClaims) }
|
|
20824
22832
|
]) + "\n"
|
|
20825
22833
|
);
|
|
20826
22834
|
return 0;
|
|
@@ -20852,8 +22860,10 @@ ${renderIssuer(issuer)}
|
|
|
20852
22860
|
}
|
|
20853
22861
|
async function runIssuersRegister(opts, deps = {}) {
|
|
20854
22862
|
let selfSignupPolicies;
|
|
22863
|
+
let capturedClaims;
|
|
20855
22864
|
try {
|
|
20856
22865
|
selfSignupPolicies = parseSelfSignupPolicies(opts.selfSignupPolicy);
|
|
22866
|
+
capturedClaims = parseCapturedClaims(opts.capturedClaim);
|
|
20857
22867
|
} catch (err) {
|
|
20858
22868
|
process.stderr.write(` \u2716 ${formatError(err)}
|
|
20859
22869
|
`);
|
|
@@ -20871,7 +22881,8 @@ async function runIssuersRegister(opts, deps = {}) {
|
|
|
20871
22881
|
subClaim: opts.subClaim,
|
|
20872
22882
|
emailClaim: opts.emailClaim,
|
|
20873
22883
|
userinfoUri: opts.userinfoUri,
|
|
20874
|
-
selfSignupPolicies
|
|
22884
|
+
selfSignupPolicies,
|
|
22885
|
+
capturedClaims
|
|
20875
22886
|
});
|
|
20876
22887
|
if (opts.json) {
|
|
20877
22888
|
process.stdout.write(`${formatJson(issuer)}
|
|
@@ -20892,8 +22903,10 @@ ${renderIssuer(issuer)}
|
|
|
20892
22903
|
}
|
|
20893
22904
|
async function runIssuersUpdate(opts, deps = {}) {
|
|
20894
22905
|
let selfSignupPolicies;
|
|
22906
|
+
let capturedClaims;
|
|
20895
22907
|
try {
|
|
20896
22908
|
selfSignupPolicies = parseSelfSignupPolicies(opts.selfSignupPolicy);
|
|
22909
|
+
capturedClaims = parseCapturedClaims(opts.capturedClaim);
|
|
20897
22910
|
} catch (err) {
|
|
20898
22911
|
process.stderr.write(` \u2716 ${formatError(err)}
|
|
20899
22912
|
`);
|
|
@@ -20905,6 +22918,12 @@ async function runIssuersUpdate(opts, deps = {}) {
|
|
|
20905
22918
|
);
|
|
20906
22919
|
return 2;
|
|
20907
22920
|
}
|
|
22921
|
+
if (opts.clearCapturedClaims && capturedClaims && capturedClaims.length > 0) {
|
|
22922
|
+
process.stderr.write(
|
|
22923
|
+
" \u2716 --clear-captured-claims and --captured-claim are mutually exclusive.\n"
|
|
22924
|
+
);
|
|
22925
|
+
return 2;
|
|
22926
|
+
}
|
|
20908
22927
|
return withAuth({ ...opts, provisioning: true, context: opts.context }, deps, async (auth) => {
|
|
20909
22928
|
const client = auth.client;
|
|
20910
22929
|
try {
|
|
@@ -20914,7 +22933,8 @@ async function runIssuersUpdate(opts, deps = {}) {
|
|
|
20914
22933
|
emailClaim: opts.emailClaim,
|
|
20915
22934
|
userinfoUri: opts.userinfoUri,
|
|
20916
22935
|
status: opts.status,
|
|
20917
|
-
selfSignupPolicies: opts.clearSelfSignupPolicies ? [] : selfSignupPolicies
|
|
22936
|
+
selfSignupPolicies: opts.clearSelfSignupPolicies ? [] : selfSignupPolicies,
|
|
22937
|
+
capturedClaims: opts.clearCapturedClaims ? [] : capturedClaims
|
|
20918
22938
|
});
|
|
20919
22939
|
if (opts.json) {
|
|
20920
22940
|
process.stdout.write(`${formatJson(issuer)}
|
|
@@ -20973,41 +22993,264 @@ async function runIssuersDelete(opts, deps = {}) {
|
|
|
20973
22993
|
try {
|
|
20974
22994
|
await client.auth.deleteIssuer({ issuerId });
|
|
20975
22995
|
if (opts.json) {
|
|
20976
|
-
process.stdout.write(`${formatJson({ issuerId, deleted: true })}
|
|
22996
|
+
process.stdout.write(`${formatJson({ issuerId, deleted: true })}
|
|
22997
|
+
`);
|
|
22998
|
+
return 0;
|
|
22999
|
+
}
|
|
23000
|
+
process.stdout.write(`
|
|
23001
|
+
\u2714 issuer '${issuerId}' deregistered from '${context}'
|
|
23002
|
+
`);
|
|
23003
|
+
return 0;
|
|
23004
|
+
} catch (err) {
|
|
23005
|
+
process.stderr.write(formatCliError(err, { action: "issuers delete failed" }));
|
|
23006
|
+
return 1;
|
|
23007
|
+
}
|
|
23008
|
+
});
|
|
23009
|
+
}
|
|
23010
|
+
|
|
23011
|
+
// src/commands/namespace.ts
|
|
23012
|
+
function renderNamespace(n) {
|
|
23013
|
+
return formatKeyValues([
|
|
23014
|
+
["namespace", n.namespace],
|
|
23015
|
+
["contextId", n.contextId ?? "(tenant-wide)"],
|
|
23016
|
+
["entityBacked", n.entityBacked === void 0 ? void 0 : String(n.entityBacked)],
|
|
23017
|
+
["reserved", n.reserved === void 0 ? void 0 : String(n.reserved)],
|
|
23018
|
+
["specificityRank", n.specificityRank === void 0 ? void 0 : String(n.specificityRank)],
|
|
23019
|
+
["defaultSchemaId", n.defaultSchemaId],
|
|
23020
|
+
["createdAt", n.createdAt]
|
|
23021
|
+
]);
|
|
23022
|
+
}
|
|
23023
|
+
async function runNamespaceList(opts, deps = {}) {
|
|
23024
|
+
return withAuth({ ...opts, context: opts.context }, deps, async (auth) => {
|
|
23025
|
+
const client = auth.client;
|
|
23026
|
+
try {
|
|
23027
|
+
const rows = await drainPages(
|
|
23028
|
+
(startFrom) => client.identity.listNamespaces({
|
|
23029
|
+
limit: PAGE_LIMIT,
|
|
23030
|
+
...opts.context ? { contextId: opts.context } : {},
|
|
23031
|
+
...startFrom ? { startFrom } : {}
|
|
23032
|
+
})
|
|
23033
|
+
);
|
|
23034
|
+
if (opts.json) {
|
|
23035
|
+
process.stdout.write(`${formatJson(rows)}
|
|
23036
|
+
`);
|
|
23037
|
+
return 0;
|
|
23038
|
+
}
|
|
23039
|
+
process.stdout.write(
|
|
23040
|
+
"\n" + formatTable(rows, [
|
|
23041
|
+
{ header: "namespace", get: (n) => n.namespace },
|
|
23042
|
+
{ header: "contextId", get: (n) => n.contextId ?? "(tenant-wide)" },
|
|
23043
|
+
{
|
|
23044
|
+
header: "entityBacked",
|
|
23045
|
+
get: (n) => n.entityBacked === void 0 ? void 0 : String(n.entityBacked)
|
|
23046
|
+
},
|
|
23047
|
+
{
|
|
23048
|
+
header: "specificityRank",
|
|
23049
|
+
get: (n) => n.specificityRank === void 0 ? void 0 : String(n.specificityRank)
|
|
23050
|
+
}
|
|
23051
|
+
]) + "\n"
|
|
23052
|
+
);
|
|
23053
|
+
return 0;
|
|
23054
|
+
} catch (err) {
|
|
23055
|
+
process.stderr.write(formatCliError(err, { action: "namespace list failed" }));
|
|
23056
|
+
return 1;
|
|
23057
|
+
}
|
|
23058
|
+
});
|
|
23059
|
+
}
|
|
23060
|
+
async function runNamespaceGet(opts, deps = {}) {
|
|
23061
|
+
return withAuth({ ...opts, context: opts.context }, deps, async (auth) => {
|
|
23062
|
+
const client = auth.client;
|
|
23063
|
+
try {
|
|
23064
|
+
const ns = await client.identity.getNamespace({
|
|
23065
|
+
namespace: opts.namespace,
|
|
23066
|
+
...opts.context ? { contextId: opts.context } : {}
|
|
23067
|
+
});
|
|
23068
|
+
if (opts.json) {
|
|
23069
|
+
process.stdout.write(`${formatJson(ns)}
|
|
23070
|
+
`);
|
|
23071
|
+
return 0;
|
|
23072
|
+
}
|
|
23073
|
+
process.stdout.write(`
|
|
23074
|
+
${renderNamespace(ns)}
|
|
23075
|
+
`);
|
|
23076
|
+
return 0;
|
|
23077
|
+
} catch (err) {
|
|
23078
|
+
process.stderr.write(formatCliError(err, { action: "namespace get failed" }));
|
|
23079
|
+
return 1;
|
|
23080
|
+
}
|
|
23081
|
+
});
|
|
23082
|
+
}
|
|
23083
|
+
async function runNamespaceRegister(opts, deps = {}) {
|
|
23084
|
+
return withAuth({ ...opts, provisioning: true, context: opts.context }, deps, async (auth) => {
|
|
23085
|
+
const client = auth.client;
|
|
23086
|
+
try {
|
|
23087
|
+
const ns = await client.identity.registerNamespace({
|
|
23088
|
+
contextId: opts.context,
|
|
23089
|
+
body: {
|
|
23090
|
+
namespace: opts.namespace,
|
|
23091
|
+
specificityRank: opts.specificityRank,
|
|
23092
|
+
entityBacked: opts.entityBacked,
|
|
23093
|
+
defaultSchemaId: opts.defaultSchemaId
|
|
23094
|
+
}
|
|
23095
|
+
});
|
|
23096
|
+
if (opts.json) {
|
|
23097
|
+
process.stdout.write(`${formatJson(ns)}
|
|
23098
|
+
`);
|
|
23099
|
+
return 0;
|
|
23100
|
+
}
|
|
23101
|
+
process.stdout.write(`
|
|
23102
|
+
\u2714 namespace '${opts.namespace}' registered
|
|
23103
|
+
${renderNamespace(ns)}
|
|
23104
|
+
`);
|
|
23105
|
+
return 0;
|
|
23106
|
+
} catch (err) {
|
|
23107
|
+
process.stderr.write(formatCliError(err, { action: "namespace register failed" }));
|
|
23108
|
+
return 1;
|
|
23109
|
+
}
|
|
23110
|
+
});
|
|
23111
|
+
}
|
|
23112
|
+
|
|
23113
|
+
// src/commands/scripts.ts
|
|
23114
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
23115
|
+
import * as readline5 from "readline/promises";
|
|
23116
|
+
var SOURCE_MAX_BYTES = 3e5;
|
|
23117
|
+
var INPUT_CONTRACT_MAX_BYTES = 5e4;
|
|
23118
|
+
var utf8Bytes2 = (s) => new TextEncoder().encode(s).length;
|
|
23119
|
+
var SCRIPT_NAME_RE2 = /^[A-Za-z0-9_-]{1,64}$/;
|
|
23120
|
+
function renderScript(s, opts = {}) {
|
|
23121
|
+
return formatKeyValues([
|
|
23122
|
+
["id", s.id],
|
|
23123
|
+
["name", s.name],
|
|
23124
|
+
["scriptVersion", s.scriptVersion === void 0 ? void 0 : String(s.scriptVersion)],
|
|
23125
|
+
["declaredInputContract", s.declaredInputContract],
|
|
23126
|
+
["createdAt", s.createdAt],
|
|
23127
|
+
// Full source last, and only where it was asked for — it can be 300 KB.
|
|
23128
|
+
...opts.source && s.source !== void 0 ? [["source", s.source]] : []
|
|
23129
|
+
]);
|
|
23130
|
+
}
|
|
23131
|
+
async function runScriptsPush(opts, deps = {}) {
|
|
23132
|
+
if (!SCRIPT_NAME_RE2.test(opts.name)) {
|
|
23133
|
+
process.stderr.write(
|
|
23134
|
+
` \u2716 '${opts.name}' is not a valid script name \u2014 use 1-64 characters of letters, digits, underscores ('_') and hyphens ('-').
|
|
23135
|
+
`
|
|
23136
|
+
);
|
|
23137
|
+
return 2;
|
|
23138
|
+
}
|
|
23139
|
+
if (opts.inputContract !== void 0 && opts.inputContractFile !== void 0) {
|
|
23140
|
+
process.stderr.write(" \u2716 --input-contract and --input-contract-file are mutually exclusive.\n");
|
|
23141
|
+
return 2;
|
|
23142
|
+
}
|
|
23143
|
+
let source;
|
|
23144
|
+
try {
|
|
23145
|
+
source = await readFile3(opts.sourceFile, "utf8");
|
|
23146
|
+
} catch (err) {
|
|
23147
|
+
process.stderr.write(` \u2716 could not read --source-file '${opts.sourceFile}': ${errText(err)}
|
|
23148
|
+
`);
|
|
23149
|
+
return 2;
|
|
23150
|
+
}
|
|
23151
|
+
if (source.trim().length === 0) {
|
|
23152
|
+
process.stderr.write(` \u2716 --source-file '${opts.sourceFile}' is empty \u2014 'source' must not be blank.
|
|
23153
|
+
`);
|
|
23154
|
+
return 2;
|
|
23155
|
+
}
|
|
23156
|
+
if (opts.inputContract !== void 0 && opts.inputContract.trim().length === 0) {
|
|
23157
|
+
process.stderr.write(
|
|
23158
|
+
` \u2716 --input-contract is blank. Omit the flag to store no input contract at all \u2014 an empty one is not a contract.
|
|
23159
|
+
`
|
|
23160
|
+
);
|
|
23161
|
+
return 2;
|
|
23162
|
+
}
|
|
23163
|
+
let declaredInputContract = opts.inputContract;
|
|
23164
|
+
if (opts.inputContractFile !== void 0) {
|
|
23165
|
+
try {
|
|
23166
|
+
declaredInputContract = await readFile3(opts.inputContractFile, "utf8");
|
|
23167
|
+
} catch (err) {
|
|
23168
|
+
process.stderr.write(
|
|
23169
|
+
` \u2716 could not read --input-contract-file '${opts.inputContractFile}': ${errText(err)}
|
|
23170
|
+
`
|
|
23171
|
+
);
|
|
23172
|
+
return 2;
|
|
23173
|
+
}
|
|
23174
|
+
if (declaredInputContract.trim().length === 0) {
|
|
23175
|
+
process.stderr.write(
|
|
23176
|
+
` \u2716 --input-contract-file '${opts.inputContractFile}' is blank. Omit the flag to store no input contract at all \u2014 an empty one is not a contract.
|
|
23177
|
+
`
|
|
23178
|
+
);
|
|
23179
|
+
return 2;
|
|
23180
|
+
}
|
|
23181
|
+
}
|
|
23182
|
+
const sourceBytes = utf8Bytes2(source);
|
|
23183
|
+
if (sourceBytes > SOURCE_MAX_BYTES) {
|
|
23184
|
+
process.stderr.write(
|
|
23185
|
+
` \u2716 source is ${sourceBytes.toLocaleString("en-US")} UTF-8 bytes, over the ${SOURCE_MAX_BYTES.toLocaleString("en-US")}-byte limit.
|
|
23186
|
+
`
|
|
23187
|
+
);
|
|
23188
|
+
return 2;
|
|
23189
|
+
}
|
|
23190
|
+
if (declaredInputContract !== void 0 && utf8Bytes2(declaredInputContract) > INPUT_CONTRACT_MAX_BYTES) {
|
|
23191
|
+
process.stderr.write(
|
|
23192
|
+
` \u2716 input contract is ${utf8Bytes2(declaredInputContract).toLocaleString("en-US")} UTF-8 bytes, over the ${INPUT_CONTRACT_MAX_BYTES.toLocaleString("en-US")}-byte limit.
|
|
23193
|
+
`
|
|
23194
|
+
);
|
|
23195
|
+
return 2;
|
|
23196
|
+
}
|
|
23197
|
+
return withAuth(opts, deps, async (auth) => {
|
|
23198
|
+
const client = auth.client;
|
|
23199
|
+
try {
|
|
23200
|
+
if (opts.ifChanged) {
|
|
23201
|
+
const latest = await readLatestScriptVersion(
|
|
23202
|
+
client,
|
|
23203
|
+
opts.name
|
|
23204
|
+
);
|
|
23205
|
+
if (latest !== void 0 && scriptMatches(latest, { source, declaredInputContract })) {
|
|
23206
|
+
if (opts.json) {
|
|
23207
|
+
process.stdout.write(`${formatJson({ ...latest, pushed: false })}
|
|
23208
|
+
`);
|
|
23209
|
+
return 0;
|
|
23210
|
+
}
|
|
23211
|
+
process.stdout.write(
|
|
23212
|
+
`
|
|
23213
|
+
= script '${opts.name}' already matches at version ${latest.scriptVersion} \u2014 nothing pushed.
|
|
23214
|
+
`
|
|
23215
|
+
);
|
|
23216
|
+
return 0;
|
|
23217
|
+
}
|
|
23218
|
+
}
|
|
23219
|
+
const pushed = await client.scripts.createScript({
|
|
23220
|
+
name: opts.name,
|
|
23221
|
+
source,
|
|
23222
|
+
declaredInputContract
|
|
23223
|
+
});
|
|
23224
|
+
if (opts.json) {
|
|
23225
|
+
process.stdout.write(`${formatJson({ ...pushed, pushed: true })}
|
|
20977
23226
|
`);
|
|
20978
23227
|
return 0;
|
|
20979
23228
|
}
|
|
20980
|
-
process.stdout.write(
|
|
20981
|
-
|
|
20982
|
-
|
|
23229
|
+
process.stdout.write(
|
|
23230
|
+
`
|
|
23231
|
+
\u2714 pushed script '${opts.name}' version ${pushed.scriptVersion} (${sourceBytes.toLocaleString("en-US")} bytes) to '${opts.context}'
|
|
23232
|
+
${renderScript(pushed)}
|
|
23233
|
+
A trigger rule reaches this with scriptRef { name: '${opts.name}', version: 'latest' }.
|
|
23234
|
+
Pinning version '${pushed.scriptVersion}' instead keeps that rule on exactly this one \u2014
|
|
23235
|
+
but only for a script pushed this way. A blueprint that SHIPS a script requires 'latest'
|
|
23236
|
+
for it, since the blueprint cannot know which number its source will land on.
|
|
23237
|
+
`
|
|
23238
|
+
);
|
|
20983
23239
|
return 0;
|
|
20984
23240
|
} catch (err) {
|
|
20985
|
-
process.stderr.write(formatCliError(err, { action: "
|
|
23241
|
+
process.stderr.write(formatCliError(err, { action: "scripts push failed" }));
|
|
20986
23242
|
return 1;
|
|
20987
23243
|
}
|
|
20988
23244
|
});
|
|
20989
23245
|
}
|
|
20990
|
-
|
|
20991
|
-
|
|
20992
|
-
function renderNamespace(n) {
|
|
20993
|
-
return formatKeyValues([
|
|
20994
|
-
["namespace", n.namespace],
|
|
20995
|
-
["contextId", n.contextId ?? "(tenant-wide)"],
|
|
20996
|
-
["entityBacked", n.entityBacked === void 0 ? void 0 : String(n.entityBacked)],
|
|
20997
|
-
["reserved", n.reserved === void 0 ? void 0 : String(n.reserved)],
|
|
20998
|
-
["specificityRank", n.specificityRank === void 0 ? void 0 : String(n.specificityRank)],
|
|
20999
|
-
["defaultSchemaId", n.defaultSchemaId],
|
|
21000
|
-
["createdAt", n.createdAt]
|
|
21001
|
-
]);
|
|
21002
|
-
}
|
|
21003
|
-
async function runNamespaceList(opts, deps = {}) {
|
|
21004
|
-
return withAuth({ ...opts, context: opts.context }, deps, async (auth) => {
|
|
23246
|
+
async function runScriptsList(opts, deps = {}) {
|
|
23247
|
+
return withAuth(opts, deps, async (auth) => {
|
|
21005
23248
|
const client = auth.client;
|
|
21006
23249
|
try {
|
|
21007
23250
|
const rows = await drainPages(
|
|
21008
|
-
(startFrom) => client.
|
|
23251
|
+
(startFrom) => client.scripts.listScripts({
|
|
21009
23252
|
limit: PAGE_LIMIT,
|
|
21010
|
-
...opts.
|
|
23253
|
+
...opts.name ? { name: opts.name } : {},
|
|
21011
23254
|
...startFrom ? { startFrom } : {}
|
|
21012
23255
|
})
|
|
21013
23256
|
);
|
|
@@ -21016,79 +23259,113 @@ async function runNamespaceList(opts, deps = {}) {
|
|
|
21016
23259
|
`);
|
|
21017
23260
|
return 0;
|
|
21018
23261
|
}
|
|
23262
|
+
if (rows.length === 0) {
|
|
23263
|
+
process.stdout.write(
|
|
23264
|
+
opts.name ? `
|
|
23265
|
+
No versions of script '${opts.name}' in '${opts.context}'.
|
|
23266
|
+
` : `
|
|
23267
|
+
No scripts in '${opts.context}'.
|
|
23268
|
+
`
|
|
23269
|
+
);
|
|
23270
|
+
return 0;
|
|
23271
|
+
}
|
|
21019
23272
|
process.stdout.write(
|
|
21020
23273
|
"\n" + formatTable(rows, [
|
|
21021
|
-
{ header: "
|
|
21022
|
-
{ header: "
|
|
21023
|
-
{
|
|
21024
|
-
|
|
21025
|
-
|
|
21026
|
-
},
|
|
21027
|
-
{
|
|
21028
|
-
header: "specificityRank",
|
|
21029
|
-
get: (n) => n.specificityRank === void 0 ? void 0 : String(n.specificityRank)
|
|
21030
|
-
}
|
|
23274
|
+
{ header: "name", get: (s) => s.name },
|
|
23275
|
+
{ header: "version", get: (s) => s.scriptVersion === void 0 ? void 0 : String(s.scriptVersion) },
|
|
23276
|
+
{ header: "id", get: (s) => s.id },
|
|
23277
|
+
{ header: "bytes", get: (s) => s.source === void 0 ? void 0 : String(utf8Bytes2(s.source)) },
|
|
23278
|
+
{ header: "createdAt", get: (s) => s.createdAt }
|
|
21031
23279
|
]) + "\n"
|
|
21032
23280
|
);
|
|
21033
23281
|
return 0;
|
|
21034
23282
|
} catch (err) {
|
|
21035
|
-
process.stderr.write(formatCliError(err, { action: "
|
|
23283
|
+
process.stderr.write(formatCliError(err, { action: "scripts list failed" }));
|
|
21036
23284
|
return 1;
|
|
21037
23285
|
}
|
|
21038
23286
|
});
|
|
21039
23287
|
}
|
|
21040
|
-
async function
|
|
21041
|
-
return withAuth(
|
|
23288
|
+
async function runScriptsGet(opts, deps = {}) {
|
|
23289
|
+
return withAuth(opts, deps, async (auth) => {
|
|
21042
23290
|
const client = auth.client;
|
|
21043
23291
|
try {
|
|
21044
|
-
const
|
|
21045
|
-
namespace: opts.namespace,
|
|
21046
|
-
...opts.context ? { contextId: opts.context } : {}
|
|
21047
|
-
});
|
|
23292
|
+
const script = await client.scripts.getScript({ id: opts.id });
|
|
21048
23293
|
if (opts.json) {
|
|
21049
|
-
process.stdout.write(`${formatJson(
|
|
23294
|
+
process.stdout.write(`${formatJson(script)}
|
|
21050
23295
|
`);
|
|
21051
23296
|
return 0;
|
|
21052
23297
|
}
|
|
21053
23298
|
process.stdout.write(`
|
|
21054
|
-
${
|
|
23299
|
+
${renderScript(script, { source: true })}
|
|
21055
23300
|
`);
|
|
21056
23301
|
return 0;
|
|
21057
23302
|
} catch (err) {
|
|
21058
|
-
process.stderr.write(formatCliError(err, { action: "
|
|
23303
|
+
process.stderr.write(formatCliError(err, { action: "scripts get failed" }));
|
|
21059
23304
|
return 1;
|
|
21060
23305
|
}
|
|
21061
23306
|
});
|
|
21062
23307
|
}
|
|
21063
|
-
async function
|
|
21064
|
-
|
|
23308
|
+
async function runScriptsDelete(opts, deps = {}) {
|
|
23309
|
+
process.stderr.write(
|
|
23310
|
+
`
|
|
23311
|
+
\u26A0 Deleting script version '${opts.id}' is permanent \u2014 the stored source is the only copy.
|
|
23312
|
+
Other versions of the same name are unaffected.
|
|
23313
|
+
Nothing checks whether a trigger rule still references this version. A rule pinned to it
|
|
23314
|
+
will fail on every firing; a rule on 'latest' will silently fall back to the previous
|
|
23315
|
+
version and keep running older code. This CLI cannot list trigger rules, so confirm
|
|
23316
|
+
against the API or the portal before removing a version a rule may still use.
|
|
23317
|
+
And if this is the name's LATEST version, its identity is REUSED: the next push of that
|
|
23318
|
+
name recomputes the same version number and the same id, so anything holding a pinned
|
|
23319
|
+
'${opts.id}' or a name@version reference will silently resolve to different source.
|
|
23320
|
+
|
|
23321
|
+
`
|
|
23322
|
+
);
|
|
23323
|
+
if (!opts.force) {
|
|
23324
|
+
const isTty = deps.isTty ?? Boolean(process.stdin.isTTY);
|
|
23325
|
+
if (!isTty) {
|
|
23326
|
+
process.stderr.write(
|
|
23327
|
+
` \u2716 scripts delete is irreversible and needs an interactive confirmation.
|
|
23328
|
+
Re-run with --force to delete '${opts.id}' without a prompt.
|
|
23329
|
+
`
|
|
23330
|
+
);
|
|
23331
|
+
return 2;
|
|
23332
|
+
}
|
|
23333
|
+
const promptCtx = deps.createPrompt ? deps.createPrompt() : (() => {
|
|
23334
|
+
const rl = readline5.createInterface({ input: process.stdin, output: process.stdout });
|
|
23335
|
+
return { prompt: (q) => rl.question(q), close: () => rl.close() };
|
|
23336
|
+
})();
|
|
23337
|
+
try {
|
|
23338
|
+
const typed = await promptCtx.prompt(` Type the version id ('${opts.id}') to confirm: `);
|
|
23339
|
+
if (typed.trim() !== opts.id) {
|
|
23340
|
+
process.stderr.write(` \u2716 Confirmation did not match \u2014 nothing deleted.
|
|
23341
|
+
`);
|
|
23342
|
+
return 1;
|
|
23343
|
+
}
|
|
23344
|
+
} finally {
|
|
23345
|
+
promptCtx.close();
|
|
23346
|
+
}
|
|
23347
|
+
}
|
|
23348
|
+
return withAuth(opts, deps, async (auth) => {
|
|
21065
23349
|
const client = auth.client;
|
|
21066
23350
|
try {
|
|
21067
|
-
|
|
21068
|
-
contextId: opts.context,
|
|
21069
|
-
body: {
|
|
21070
|
-
namespace: opts.namespace,
|
|
21071
|
-
specificityRank: opts.specificityRank,
|
|
21072
|
-
entityBacked: opts.entityBacked,
|
|
21073
|
-
defaultSchemaId: opts.defaultSchemaId
|
|
21074
|
-
}
|
|
21075
|
-
});
|
|
23351
|
+
await client.scripts.deleteScript({ id: opts.id });
|
|
21076
23352
|
if (opts.json) {
|
|
21077
|
-
process.stdout.write(`${formatJson(
|
|
23353
|
+
process.stdout.write(`${formatJson({ id: opts.id, deleted: true })}
|
|
21078
23354
|
`);
|
|
21079
23355
|
return 0;
|
|
21080
23356
|
}
|
|
21081
|
-
process.stdout.write(`
|
|
21082
|
-
\u2714 namespace '${opts.namespace}' registered
|
|
21083
|
-
${renderNamespace(ns)}
|
|
23357
|
+
process.stdout.write(` \u2714 Deleted script version '${opts.id}' from '${opts.context}'.
|
|
21084
23358
|
`);
|
|
21085
23359
|
return 0;
|
|
21086
23360
|
} catch (err) {
|
|
21087
|
-
process.stderr.write(formatCliError(err, { action: "
|
|
23361
|
+
process.stderr.write(formatCliError(err, { action: "scripts delete failed" }));
|
|
21088
23362
|
return 1;
|
|
21089
23363
|
}
|
|
21090
23364
|
});
|
|
21091
23365
|
}
|
|
23366
|
+
function errText(err) {
|
|
23367
|
+
return err instanceof Error ? err.message : String(err);
|
|
23368
|
+
}
|
|
21092
23369
|
|
|
21093
23370
|
// src/commands/assume.ts
|
|
21094
23371
|
function parseAssumeScopes(raw) {
|
|
@@ -21111,11 +23388,20 @@ function parseAssumeScopes(raw) {
|
|
|
21111
23388
|
return out;
|
|
21112
23389
|
}
|
|
21113
23390
|
function renderAssumeToken(t) {
|
|
21114
|
-
|
|
23391
|
+
const identity = t.resolvedScope?.identity;
|
|
23392
|
+
const allowed = t.resolvedScope?.allowedActions;
|
|
23393
|
+
const rows = [
|
|
21115
23394
|
["access_token", t.access_token],
|
|
21116
23395
|
["token_type", t.token_type],
|
|
21117
23396
|
["expires_in", t.expires_in === void 0 ? void 0 : `${t.expires_in}s`]
|
|
21118
|
-
]
|
|
23397
|
+
];
|
|
23398
|
+
if (identity && Object.keys(identity).length > 0) {
|
|
23399
|
+
rows.push(["identity", Object.entries(identity).map(([k, v]) => `${k}=${v}`).join(", ")]);
|
|
23400
|
+
}
|
|
23401
|
+
if (allowed?.length) {
|
|
23402
|
+
rows.push(["allowedActions", allowed.join(", ")]);
|
|
23403
|
+
}
|
|
23404
|
+
return formatKeyValues(rows);
|
|
21119
23405
|
}
|
|
21120
23406
|
async function runAssume(opts, deps = {}) {
|
|
21121
23407
|
if (!opts.scope || opts.scope.length === 0) {
|
|
@@ -21300,12 +23586,14 @@ import { randomUUID as randomUUID2 } from "crypto";
|
|
|
21300
23586
|
|
|
21301
23587
|
// src/harness.ts
|
|
21302
23588
|
var errMsg = (e) => e instanceof Error ? e.message : String(e);
|
|
21303
|
-
var
|
|
23589
|
+
var isNotFound2 = (e) => typeof e === "object" && e !== null && e.statusCode === 404;
|
|
21304
23590
|
var SNAPSHOT_PAGE_SIZE = 100;
|
|
21305
23591
|
async function captureSnapshot(client, blueprint) {
|
|
21306
23592
|
const existingSchemaTypes = /* @__PURE__ */ new Set();
|
|
21307
23593
|
const existingProfilePrincipals = /* @__PURE__ */ new Set();
|
|
21308
23594
|
const existingRoleIds = /* @__PURE__ */ new Set();
|
|
23595
|
+
const existingTriggerNames = /* @__PURE__ */ new Set();
|
|
23596
|
+
let triggerSnapshotFailed = false;
|
|
21309
23597
|
let failed = false;
|
|
21310
23598
|
try {
|
|
21311
23599
|
let cursor;
|
|
@@ -21317,7 +23605,7 @@ async function captureSnapshot(client, blueprint) {
|
|
|
21317
23605
|
cursor = next;
|
|
21318
23606
|
}
|
|
21319
23607
|
} catch (e) {
|
|
21320
|
-
if (!
|
|
23608
|
+
if (!isNotFound2(e)) failed = true;
|
|
21321
23609
|
}
|
|
21322
23610
|
try {
|
|
21323
23611
|
let startFrom;
|
|
@@ -21333,7 +23621,7 @@ async function captureSnapshot(client, blueprint) {
|
|
|
21333
23621
|
startFrom = next;
|
|
21334
23622
|
}
|
|
21335
23623
|
} catch (e) {
|
|
21336
|
-
if (!
|
|
23624
|
+
if (!isNotFound2(e)) failed = true;
|
|
21337
23625
|
}
|
|
21338
23626
|
try {
|
|
21339
23627
|
let startFrom;
|
|
@@ -21349,9 +23637,23 @@ async function captureSnapshot(client, blueprint) {
|
|
|
21349
23637
|
startFrom = next;
|
|
21350
23638
|
}
|
|
21351
23639
|
} catch (e) {
|
|
21352
|
-
if (!
|
|
23640
|
+
if (!isNotFound2(e)) failed = true;
|
|
23641
|
+
}
|
|
23642
|
+
if (Object.keys(blueprint.triggers ?? {}).length > 0) {
|
|
23643
|
+
try {
|
|
23644
|
+
let startFrom;
|
|
23645
|
+
for (; ; ) {
|
|
23646
|
+
const page = await client.triggers.listTriggers({ limit: SNAPSHOT_PAGE_SIZE, startFrom });
|
|
23647
|
+
for (const t of page.data ?? []) if (t.name) existingTriggerNames.add(t.name);
|
|
23648
|
+
const next = page.nextCursor;
|
|
23649
|
+
if (!next) break;
|
|
23650
|
+
startFrom = next;
|
|
23651
|
+
}
|
|
23652
|
+
} catch (e) {
|
|
23653
|
+
if (!isNotFound2(e)) triggerSnapshotFailed = true;
|
|
23654
|
+
}
|
|
21353
23655
|
}
|
|
21354
|
-
return { existingSchemaTypes, existingProfilePrincipals, existingRoleIds, failed };
|
|
23656
|
+
return { existingSchemaTypes, existingProfilePrincipals, existingRoleIds, existingTriggerNames, triggerSnapshotFailed, failed };
|
|
21355
23657
|
}
|
|
21356
23658
|
async function captureIssuerSnapshot(bootstrapClient) {
|
|
21357
23659
|
const existingIssuerIds = /* @__PURE__ */ new Set();
|
|
@@ -21547,6 +23849,97 @@ async function runTeardown(client, bootstrapClient, blueprint, machineId, snapsh
|
|
|
21547
23849
|
steps.push({ step: `delete role ${roleId}`, ok: false, error: errMsg(e) });
|
|
21548
23850
|
}
|
|
21549
23851
|
}
|
|
23852
|
+
const declaredTriggerNames = Object.keys(blueprint.triggers ?? {});
|
|
23853
|
+
let allDeclaredTriggersDeleted = true;
|
|
23854
|
+
if (declaredTriggerNames.length > 0) {
|
|
23855
|
+
let live = [];
|
|
23856
|
+
let listFailed = false;
|
|
23857
|
+
try {
|
|
23858
|
+
let startFrom;
|
|
23859
|
+
for (; ; ) {
|
|
23860
|
+
const page = await client.triggers.listTriggers({ limit: SNAPSHOT_PAGE_SIZE, startFrom });
|
|
23861
|
+
live = live.concat(page.data ?? []);
|
|
23862
|
+
const next = page.nextCursor;
|
|
23863
|
+
if (!next) break;
|
|
23864
|
+
startFrom = next;
|
|
23865
|
+
}
|
|
23866
|
+
} catch (e) {
|
|
23867
|
+
listFailed = true;
|
|
23868
|
+
steps.push({ step: "delete triggers", ok: false, error: `could not list triggers: ${errMsg(e)}` });
|
|
23869
|
+
allDeclaredTriggersDeleted = false;
|
|
23870
|
+
}
|
|
23871
|
+
if (!listFailed) {
|
|
23872
|
+
for (const name of declaredTriggerNames) {
|
|
23873
|
+
if (snapshot.failed || snapshot.triggerSnapshotFailed) {
|
|
23874
|
+
steps.push({ step: `delete trigger ${name}`, ok: true, skipped: true, error: "skipped \u2014 pre-apply snapshot failed; cannot prove ownership" });
|
|
23875
|
+
allDeclaredTriggersDeleted = false;
|
|
23876
|
+
continue;
|
|
23877
|
+
}
|
|
23878
|
+
if (snapshot.existingTriggerNames.has(name)) {
|
|
23879
|
+
steps.push({ step: `delete trigger ${name}`, ok: true, skipped: true, error: "skipped \u2014 pre-existed this run" });
|
|
23880
|
+
allDeclaredTriggersDeleted = false;
|
|
23881
|
+
continue;
|
|
23882
|
+
}
|
|
23883
|
+
const row = live.find((t) => t.name === name);
|
|
23884
|
+
if (!row?.id) {
|
|
23885
|
+
steps.push({ step: `delete trigger ${name}`, ok: true, skipped: true, error: "skipped \u2014 not found (apply may not have reached it)" });
|
|
23886
|
+
continue;
|
|
23887
|
+
}
|
|
23888
|
+
if (row.provisionedBy !== blueprint.name) {
|
|
23889
|
+
steps.push({
|
|
23890
|
+
step: `delete trigger ${name}`,
|
|
23891
|
+
ok: true,
|
|
23892
|
+
skipped: true,
|
|
23893
|
+
error: `skipped \u2014 provisionedBy is ${row.provisionedBy ? `'${row.provisionedBy}'` : "unset"}, not this blueprint`
|
|
23894
|
+
});
|
|
23895
|
+
allDeclaredTriggersDeleted = false;
|
|
23896
|
+
continue;
|
|
23897
|
+
}
|
|
23898
|
+
try {
|
|
23899
|
+
await client.triggers.deleteTrigger({ id: row.id });
|
|
23900
|
+
steps.push({ step: `delete trigger ${name}`, ok: true });
|
|
23901
|
+
} catch (e) {
|
|
23902
|
+
steps.push({ step: `delete trigger ${name}`, ok: false, error: errMsg(e) });
|
|
23903
|
+
allDeclaredTriggersDeleted = false;
|
|
23904
|
+
continue;
|
|
23905
|
+
}
|
|
23906
|
+
if (row.principalId) {
|
|
23907
|
+
try {
|
|
23908
|
+
await client.auth.deleteAccessProfile({ contextId: blueprint.contextId, principalId: row.principalId });
|
|
23909
|
+
steps.push({ step: `delete trigger ${name} profile`, ok: true });
|
|
23910
|
+
} catch (e) {
|
|
23911
|
+
steps.push({ step: `delete trigger ${name} profile`, ok: false, error: errMsg(e) });
|
|
23912
|
+
}
|
|
23913
|
+
}
|
|
23914
|
+
}
|
|
23915
|
+
}
|
|
23916
|
+
}
|
|
23917
|
+
for (const script of result.scriptActions) {
|
|
23918
|
+
if (!allDeclaredTriggersDeleted) {
|
|
23919
|
+
steps.push({
|
|
23920
|
+
step: `delete script ${script.name} v${script.scriptVersion}`,
|
|
23921
|
+
ok: true,
|
|
23922
|
+
skipped: true,
|
|
23923
|
+
error: "skipped \u2014 a trigger rule this blueprint declares was not removed, so deleting the script it may reference could break a live rule silently"
|
|
23924
|
+
});
|
|
23925
|
+
continue;
|
|
23926
|
+
}
|
|
23927
|
+
if (script.action === "unchanged") {
|
|
23928
|
+
steps.push({
|
|
23929
|
+
step: `delete script ${script.name} v${script.scriptVersion}`,
|
|
23930
|
+
ok: true,
|
|
23931
|
+
skipped: true,
|
|
23932
|
+
error: "skipped \u2014 this run pushed no version (the stored source already matched)"
|
|
23933
|
+
});
|
|
23934
|
+
continue;
|
|
23935
|
+
}
|
|
23936
|
+
try {
|
|
23937
|
+
await client.scripts.deleteScript({ id: script.id });
|
|
23938
|
+
steps.push({ step: `delete script ${script.name} v${script.scriptVersion}`, ok: true });
|
|
23939
|
+
} catch (e) {
|
|
23940
|
+
steps.push({ step: `delete script ${script.name} v${script.scriptVersion}`, ok: false, error: errMsg(e) });
|
|
23941
|
+
}
|
|
23942
|
+
}
|
|
21550
23943
|
const toDelete = [];
|
|
21551
23944
|
for (const [typeName, schemaId] of Object.entries(result.schemaIds)) {
|
|
21552
23945
|
if (snapshot.failed) {
|
|
@@ -21633,7 +24026,9 @@ async function runBlueprintHarness(client, input, deps) {
|
|
|
21633
24026
|
existingSchemaTypes: /* @__PURE__ */ new Set(),
|
|
21634
24027
|
existingProfilePrincipals: /* @__PURE__ */ new Set(),
|
|
21635
24028
|
existingRoleIds: /* @__PURE__ */ new Set(),
|
|
21636
|
-
|
|
24029
|
+
existingTriggerNames: /* @__PURE__ */ new Set(),
|
|
24030
|
+
triggerSnapshotFailed: !isNotFound2(e),
|
|
24031
|
+
failed: !isNotFound2(e)
|
|
21637
24032
|
};
|
|
21638
24033
|
}
|
|
21639
24034
|
} else {
|
|
@@ -21646,7 +24041,7 @@ async function runBlueprintHarness(client, input, deps) {
|
|
|
21646
24041
|
try {
|
|
21647
24042
|
issuerSnapshot = await captureIssuerSnapshot(await remintBootstrapForContext(blueprint.contextId));
|
|
21648
24043
|
} catch (e) {
|
|
21649
|
-
issuerSnapshot = { existingIssuerIds: /* @__PURE__ */ new Set(), failed: !
|
|
24044
|
+
issuerSnapshot = { existingIssuerIds: /* @__PURE__ */ new Set(), failed: !isNotFound2(e) };
|
|
21650
24045
|
}
|
|
21651
24046
|
} else {
|
|
21652
24047
|
issuerSnapshot = await captureIssuerSnapshot(client);
|
|
@@ -21669,7 +24064,17 @@ async function runBlueprintHarness(client, input, deps) {
|
|
|
21669
24064
|
log,
|
|
21670
24065
|
provisioned,
|
|
21671
24066
|
remintForContext,
|
|
21672
|
-
remintBootstrapForContext
|
|
24067
|
+
remintBootstrapForContext,
|
|
24068
|
+
// CREATED-ONLY, and this is the seam that keeps it true for triggers. The loader's step 5d
|
|
24069
|
+
// reconciles triggers by ABSENCE — it deletes rules this blueprint provisioned and no longer
|
|
24070
|
+
// declares. That is right for a real bootstrap run, which owns its context; it is wrong here.
|
|
24071
|
+
// This harness may run against a shared tenant, and its whole safety contract is that a run
|
|
24072
|
+
// touches only what it CREATED: `captureSnapshot` protects pre-existing schemas, profiles,
|
|
24073
|
+
// roles and issuers, and `runTeardown` skips them. There is no trigger leg in either, so a
|
|
24074
|
+
// prune would delete a pre-existing trigger — and its access profile — with nothing
|
|
24075
|
+
// recording that it happened and nothing able to restore it. Opt out rather than grow the
|
|
24076
|
+
// snapshot a leg teardown could not honour anyway.
|
|
24077
|
+
pruneTriggers: false
|
|
21673
24078
|
});
|
|
21674
24079
|
log.debug({ component: "harness", contextId: result.contextId, keyName: result.keyName }, "applied");
|
|
21675
24080
|
assertions = await runAssertions(
|
|
@@ -21771,14 +24176,14 @@ async function runBlueprintTest(fileOrName, opts, deps = {}) {
|
|
|
21771
24176
|
if (err instanceof ScopeGateError) {
|
|
21772
24177
|
process.stderr.write(` \u2716 Scope gate refused blueprint:
|
|
21773
24178
|
`);
|
|
21774
|
-
for (const r of err.rejected) process.stderr.write(` \u2022 ${r
|
|
24179
|
+
for (const r of err.rejected) process.stderr.write(` \u2022 ${formatRejection(r)}
|
|
21775
24180
|
`);
|
|
21776
24181
|
return 2;
|
|
21777
24182
|
}
|
|
21778
24183
|
if (err instanceof CapabilityGateError) {
|
|
21779
24184
|
process.stderr.write(` \u2716 Capability gate refused blueprint:
|
|
21780
24185
|
`);
|
|
21781
|
-
for (const r of err.rejected) process.stderr.write(` \u2022 ${r
|
|
24186
|
+
for (const r of err.rejected) process.stderr.write(` \u2022 ${formatRejection(r)}
|
|
21782
24187
|
`);
|
|
21783
24188
|
return 2;
|
|
21784
24189
|
}
|
|
@@ -22030,9 +24435,9 @@ function emitJoined(opts) {
|
|
|
22030
24435
|
|
|
22031
24436
|
// src/build-info.ts
|
|
22032
24437
|
var BUILD_INFO = {
|
|
22033
|
-
cli: true ? "0.
|
|
22034
|
-
sdk: true ? "0.
|
|
22035
|
-
blueprints: true ? "0.
|
|
24438
|
+
cli: true ? "0.21.0" : "dev",
|
|
24439
|
+
sdk: true ? "0.43.0" : "dev",
|
|
24440
|
+
blueprints: true ? "0.18.0" : "dev"
|
|
22036
24441
|
};
|
|
22037
24442
|
function formatBuildInfo() {
|
|
22038
24443
|
return `vectros ${BUILD_INFO.cli} (sdk ${BUILD_INFO.sdk}, blueprints ${BUILD_INFO.blueprints})`;
|
|
@@ -22043,6 +24448,9 @@ var KEY_FORMATS = ["human", "raw", "env", "json"];
|
|
|
22043
24448
|
function collect(value, previous) {
|
|
22044
24449
|
return [...previous, value];
|
|
22045
24450
|
}
|
|
24451
|
+
function collectOptional(value, previous) {
|
|
24452
|
+
return [...previous ?? [], value];
|
|
24453
|
+
}
|
|
22046
24454
|
function addAuthOptions(cmd) {
|
|
22047
24455
|
return cmd.addOption(
|
|
22048
24456
|
new Option("--env <env>", "target environment").choices(["staging", "production"]).default("production").hideHelp()
|
|
@@ -22114,6 +24522,7 @@ function buildProgram(deps = {}) {
|
|
|
22114
24522
|
registerContextCommands(program, deps);
|
|
22115
24523
|
registerIssuersCommands(program, deps);
|
|
22116
24524
|
registerNamespaceCommands(program, deps);
|
|
24525
|
+
registerScriptsCommands(program, deps);
|
|
22117
24526
|
registerAssumeCommand(program, deps);
|
|
22118
24527
|
registerJoinCommand(program, deps);
|
|
22119
24528
|
registerKeyringCommands(program);
|
|
@@ -22289,6 +24698,10 @@ function registerIssuersCommands(program, deps) {
|
|
|
22289
24698
|
"opt-in self-service signup entry, repeatable (e.g. default:member) \u2014 no entry may target an elevated role",
|
|
22290
24699
|
collect,
|
|
22291
24700
|
[]
|
|
24701
|
+
).option(
|
|
24702
|
+
"--captured-claim <name>",
|
|
24703
|
+
"additional OIDC claim to capture from this issuer\u2019s tokens beyond the email claim, repeatable (e.g. phone_number); omit to capture nothing beyond email",
|
|
24704
|
+
collectOptional
|
|
22292
24705
|
).option("--tenant <tenant>", "tenant the context lives in: 'test' or 'live' (default live)");
|
|
22293
24706
|
addCommonAuthOptions(register).action(
|
|
22294
24707
|
async (flags) => {
|
|
@@ -22306,6 +24719,7 @@ function registerIssuersCommands(program, deps) {
|
|
|
22306
24719
|
emailClaim: flags.emailClaim,
|
|
22307
24720
|
userinfoUri: flags.userinfoUri,
|
|
22308
24721
|
selfSignupPolicy: flags.selfSignupPolicy,
|
|
24722
|
+
capturedClaim: flags.capturedClaim,
|
|
22309
24723
|
tenant
|
|
22310
24724
|
},
|
|
22311
24725
|
deps
|
|
@@ -22318,7 +24732,11 @@ function registerIssuersCommands(program, deps) {
|
|
|
22318
24732
|
"replace self-signup policies, repeatable (e.g. default:member) \u2014 omit entirely to leave unchanged",
|
|
22319
24733
|
collect,
|
|
22320
24734
|
[]
|
|
22321
|
-
).option("--clear-self-signup-policies", "disable self-signup entirely (sends an empty list; XOR --self-signup-policy)").option(
|
|
24735
|
+
).option("--clear-self-signup-policies", "disable self-signup entirely (sends an empty list; XOR --self-signup-policy)").option(
|
|
24736
|
+
"--captured-claim <name>",
|
|
24737
|
+
"replace the additional OIDC claims captured beyond the email claim, repeatable \u2014 omit entirely to leave unchanged",
|
|
24738
|
+
collectOptional
|
|
24739
|
+
).option("--clear-captured-claims", "capture nothing beyond the email claim (sends an empty list; XOR --captured-claim)").option("--tenant <tenant>", "tenant the context lives in: 'test' or 'live' (default live)");
|
|
22322
24740
|
addCommonAuthOptions(update).action(
|
|
22323
24741
|
async (issuerId, flags) => {
|
|
22324
24742
|
const tenant = parseTenantFlag(flags.tenant);
|
|
@@ -22334,6 +24752,8 @@ function registerIssuersCommands(program, deps) {
|
|
|
22334
24752
|
status: flags.status,
|
|
22335
24753
|
selfSignupPolicy: flags.selfSignupPolicy,
|
|
22336
24754
|
clearSelfSignupPolicies: Boolean(flags.clearSelfSignupPolicies),
|
|
24755
|
+
capturedClaim: flags.capturedClaim,
|
|
24756
|
+
clearCapturedClaims: Boolean(flags.clearCapturedClaims),
|
|
22337
24757
|
tenant
|
|
22338
24758
|
},
|
|
22339
24759
|
deps
|
|
@@ -22395,6 +24815,68 @@ function registerNamespaceCommands(program, deps) {
|
|
|
22395
24815
|
}
|
|
22396
24816
|
);
|
|
22397
24817
|
}
|
|
24818
|
+
function registerScriptsCommands(program, deps) {
|
|
24819
|
+
const scripts = program.command("scripts").description("Manage script objects (the versioned JS source a trigger rule runs).");
|
|
24820
|
+
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)");
|
|
24821
|
+
addCommonAuthOptions(push).action(
|
|
24822
|
+
async (name, flags) => {
|
|
24823
|
+
process.exit(
|
|
24824
|
+
await runScriptsPush(
|
|
24825
|
+
{
|
|
24826
|
+
...toCommonOptions(flags),
|
|
24827
|
+
name,
|
|
24828
|
+
context: flags.context,
|
|
24829
|
+
sourceFile: flags.sourceFile,
|
|
24830
|
+
inputContract: flags.inputContract,
|
|
24831
|
+
inputContractFile: flags.inputContractFile,
|
|
24832
|
+
ifChanged: Boolean(flags.ifChanged),
|
|
24833
|
+
tenant: parseTenantFlag(flags.tenant)
|
|
24834
|
+
},
|
|
24835
|
+
deps
|
|
24836
|
+
)
|
|
24837
|
+
);
|
|
24838
|
+
}
|
|
24839
|
+
);
|
|
24840
|
+
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)");
|
|
24841
|
+
addCommonAuthOptions(list).action(
|
|
24842
|
+
async (flags) => {
|
|
24843
|
+
process.exit(
|
|
24844
|
+
await runScriptsList(
|
|
24845
|
+
{ ...toCommonOptions(flags), context: flags.context, name: flags.name, tenant: parseTenantFlag(flags.tenant) },
|
|
24846
|
+
deps
|
|
24847
|
+
)
|
|
24848
|
+
);
|
|
24849
|
+
}
|
|
24850
|
+
);
|
|
24851
|
+
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)");
|
|
24852
|
+
addCommonAuthOptions(get).action(
|
|
24853
|
+
async (id, flags) => {
|
|
24854
|
+
process.exit(
|
|
24855
|
+
await runScriptsGet(
|
|
24856
|
+
{ ...toCommonOptions(flags), id, context: flags.context, tenant: parseTenantFlag(flags.tenant) },
|
|
24857
|
+
deps
|
|
24858
|
+
)
|
|
24859
|
+
);
|
|
24860
|
+
}
|
|
24861
|
+
);
|
|
24862
|
+
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)");
|
|
24863
|
+
addCommonAuthOptions(del).action(
|
|
24864
|
+
async (id, flags) => {
|
|
24865
|
+
process.exit(
|
|
24866
|
+
await runScriptsDelete(
|
|
24867
|
+
{
|
|
24868
|
+
...toCommonOptions(flags),
|
|
24869
|
+
id,
|
|
24870
|
+
context: flags.context,
|
|
24871
|
+
tenant: parseTenantFlag(flags.tenant),
|
|
24872
|
+
force: Boolean(flags.force)
|
|
24873
|
+
},
|
|
24874
|
+
deps
|
|
24875
|
+
)
|
|
24876
|
+
);
|
|
24877
|
+
}
|
|
24878
|
+
);
|
|
24879
|
+
}
|
|
22398
24880
|
function registerRoleCommands(program, deps) {
|
|
22399
24881
|
const role = program.command("role").description("Manage context-scoped roles (reusable scope rules; bind via `access grant --role`).");
|
|
22400
24882
|
const create = role.command("create").description("Create (idempotent by roleId) a single-clause role from --actions.").requiredOption("--context <c>", "app context").requiredOption("--role-id <id>", "role id (3-31 chars; lowercase letter + lowercase/digit/dash)").requiredOption("--name <name>", "human-readable name").requiredOption("--actions <csv>", "allowed actions, comma-separated (e.g. records:cru,search:r)").option(
|
|
@@ -22403,72 +24885,106 @@ function registerRoleCommands(program, deps) {
|
|
|
22403
24885
|
).option(
|
|
22404
24886
|
"--assumable <json>",
|
|
22405
24887
|
`role-level \`vectros assume\` grant \u2014 JSON map of scope:<namespace> \u2192 allowed values (e.g. '{"scope:org":["orgB"]}'); independent of --data-scope, never data_scope's grammar (userId cannot be named)`
|
|
22406
|
-
).option(
|
|
24888
|
+
).option(
|
|
24889
|
+
"--assignable-role <roleId>",
|
|
24890
|
+
'restrict WHICH roles this clause may compose into a delegated profile, repeatable \u2014 omit for no restriction (the default); the platform rejects an empty list, so there is no way to spell "compose nothing"',
|
|
24891
|
+
collectOptional
|
|
24892
|
+
).option("--description <text>", "free-text description").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
22407
24893
|
addCommonAuthOptions(create).action(
|
|
22408
24894
|
async (flags) => {
|
|
22409
|
-
process.
|
|
22410
|
-
|
|
22411
|
-
|
|
22412
|
-
|
|
22413
|
-
|
|
22414
|
-
|
|
22415
|
-
|
|
22416
|
-
|
|
22417
|
-
|
|
22418
|
-
|
|
22419
|
-
|
|
22420
|
-
);
|
|
24895
|
+
process.exitCode = await runRoleCreate({
|
|
24896
|
+
...toCommonOptions(flags),
|
|
24897
|
+
tenant: parseTenantFlag(flags.tenant),
|
|
24898
|
+
context: flags.context,
|
|
24899
|
+
roleId: flags.roleId,
|
|
24900
|
+
name: flags.name,
|
|
24901
|
+
actions: flags.actions,
|
|
24902
|
+
dataScope: flags.dataScope,
|
|
24903
|
+
assumable: flags.assumable,
|
|
24904
|
+
assignableRole: flags.assignableRole,
|
|
24905
|
+
description: flags.description
|
|
24906
|
+
}, deps);
|
|
22421
24907
|
}
|
|
22422
24908
|
);
|
|
22423
|
-
const list = role.command("list").description("List the roles defined in a context.").requiredOption("--context <c>", "app context");
|
|
24909
|
+
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)");
|
|
22424
24910
|
addCommonAuthOptions(list).action(async (flags) => {
|
|
22425
|
-
process.
|
|
22426
|
-
|
|
22427
|
-
|
|
22428
|
-
|
|
22429
|
-
process.exit(await runRoleGet({ ...toCommonOptions(flags), context: flags.context, roleId: flags.roleId }, deps));
|
|
22430
|
-
});
|
|
22431
|
-
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");
|
|
22432
|
-
addAuthOptions(del).action(async (flags) => {
|
|
22433
|
-
process.exit(await runRoleDelete({ ...toCommonOptions(flags), context: flags.context, roleId: flags.roleId }, deps));
|
|
24911
|
+
process.exitCode = await runRoleList(
|
|
24912
|
+
{ ...toCommonOptions(flags), tenant: parseTenantFlag(flags.tenant), context: flags.context },
|
|
24913
|
+
deps
|
|
24914
|
+
);
|
|
22434
24915
|
});
|
|
24916
|
+
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)");
|
|
24917
|
+
addCommonAuthOptions(get).action(
|
|
24918
|
+
async (flags) => {
|
|
24919
|
+
process.exitCode = await runRoleGet(
|
|
24920
|
+
{ ...toCommonOptions(flags), tenant: parseTenantFlag(flags.tenant), context: flags.context, roleId: flags.roleId },
|
|
24921
|
+
deps
|
|
24922
|
+
);
|
|
24923
|
+
}
|
|
24924
|
+
);
|
|
24925
|
+
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)");
|
|
24926
|
+
addAuthOptions(del).action(
|
|
24927
|
+
async (flags) => {
|
|
24928
|
+
process.exitCode = await runRoleDelete(
|
|
24929
|
+
{ ...toCommonOptions(flags), tenant: parseTenantFlag(flags.tenant), context: flags.context, roleId: flags.roleId },
|
|
24930
|
+
deps
|
|
24931
|
+
);
|
|
24932
|
+
}
|
|
24933
|
+
);
|
|
22435
24934
|
}
|
|
22436
24935
|
function registerAccessCommands(program, deps) {
|
|
22437
24936
|
const access2 = program.command("access").description("Manage access bindings: principal \u2194 context \u2194 role/scopes.");
|
|
22438
|
-
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)");
|
|
24937
|
+
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)");
|
|
22439
24938
|
addCommonAuthOptions(grant).action(
|
|
22440
24939
|
async (flags) => {
|
|
22441
|
-
process.
|
|
22442
|
-
|
|
22443
|
-
|
|
22444
|
-
|
|
22445
|
-
|
|
22446
|
-
|
|
22447
|
-
|
|
22448
|
-
|
|
22449
|
-
|
|
22450
|
-
|
|
24940
|
+
process.exitCode = await runAccessGrant({
|
|
24941
|
+
...toCommonOptions(flags),
|
|
24942
|
+
tenant: parseTenantFlag(flags.tenant),
|
|
24943
|
+
principal: flags.principal,
|
|
24944
|
+
context: flags.context,
|
|
24945
|
+
role: flags.role,
|
|
24946
|
+
actions: flags.actions,
|
|
24947
|
+
scope: flags.scope,
|
|
24948
|
+
identityOverrides: flags.identityOverrides
|
|
24949
|
+
}, deps);
|
|
24950
|
+
}
|
|
24951
|
+
);
|
|
24952
|
+
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)");
|
|
24953
|
+
addAuthOptions(revoke).action(
|
|
24954
|
+
async (flags) => {
|
|
24955
|
+
process.exitCode = await runAccessRevoke(
|
|
24956
|
+
{ ...toCommonOptions(flags), tenant: parseTenantFlag(flags.tenant), principal: flags.principal, context: flags.context },
|
|
24957
|
+
deps
|
|
24958
|
+
);
|
|
24959
|
+
}
|
|
24960
|
+
);
|
|
24961
|
+
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)");
|
|
24962
|
+
addCommonAuthOptions(list).action(
|
|
24963
|
+
async (flags) => {
|
|
24964
|
+
process.exitCode = await runAccessList(
|
|
24965
|
+
{ ...toCommonOptions(flags), tenant: parseTenantFlag(flags.tenant), context: flags.context, principal: flags.principal },
|
|
24966
|
+
deps
|
|
24967
|
+
);
|
|
24968
|
+
}
|
|
24969
|
+
);
|
|
24970
|
+
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)");
|
|
24971
|
+
addCommonAuthOptions(get).action(
|
|
24972
|
+
async (flags) => {
|
|
24973
|
+
process.exitCode = await runAccessGet(
|
|
24974
|
+
{ ...toCommonOptions(flags), tenant: parseTenantFlag(flags.tenant), principal: flags.principal, context: flags.context },
|
|
24975
|
+
deps
|
|
24976
|
+
);
|
|
24977
|
+
}
|
|
24978
|
+
);
|
|
24979
|
+
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)");
|
|
24980
|
+
addCommonAuthOptions(explain).action(
|
|
24981
|
+
async (flags) => {
|
|
24982
|
+
process.exitCode = await runAccessExplain(
|
|
24983
|
+
{ ...toCommonOptions(flags), tenant: parseTenantFlag(flags.tenant), principal: flags.principal, context: flags.context },
|
|
24984
|
+
deps
|
|
22451
24985
|
);
|
|
22452
24986
|
}
|
|
22453
24987
|
);
|
|
22454
|
-
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");
|
|
22455
|
-
addAuthOptions(revoke).action(async (flags) => {
|
|
22456
|
-
process.exit(await runAccessRevoke({ ...toCommonOptions(flags), principal: flags.principal, context: flags.context }, deps));
|
|
22457
|
-
});
|
|
22458
|
-
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)");
|
|
22459
|
-
addCommonAuthOptions(list).action(async (flags) => {
|
|
22460
|
-
process.exit(await runAccessList({ ...toCommonOptions(flags), context: flags.context, principal: flags.principal }, deps));
|
|
22461
|
-
});
|
|
22462
|
-
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");
|
|
22463
|
-
addCommonAuthOptions(get).action(async (flags) => {
|
|
22464
|
-
process.exit(await runAccessGet({ ...toCommonOptions(flags), principal: flags.principal, context: flags.context }, deps));
|
|
22465
|
-
});
|
|
22466
|
-
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");
|
|
22467
|
-
addCommonAuthOptions(explain).action(async (flags) => {
|
|
22468
|
-
process.exit(
|
|
22469
|
-
await runAccessExplain({ ...toCommonOptions(flags), principal: flags.principal, context: flags.context }, deps)
|
|
22470
|
-
);
|
|
22471
|
-
});
|
|
22472
24988
|
}
|
|
22473
24989
|
function dimensionOption() {
|
|
22474
24990
|
return new Option(
|