@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.js
CHANGED
|
@@ -521,8 +521,8 @@ var require_graceful_fs = __commonJS({
|
|
|
521
521
|
fs5.createReadStream = createReadStream;
|
|
522
522
|
fs5.createWriteStream = createWriteStream;
|
|
523
523
|
var fs$readFile = fs5.readFile;
|
|
524
|
-
fs5.readFile =
|
|
525
|
-
function
|
|
524
|
+
fs5.readFile = readFile4;
|
|
525
|
+
function readFile4(path4, options, cb) {
|
|
526
526
|
if (typeof options === "function")
|
|
527
527
|
cb = options, options = null;
|
|
528
528
|
return go$readFile(path4, options, cb);
|
|
@@ -1614,7 +1614,7 @@ var require_proper_lockfile = __commonJS({
|
|
|
1614
1614
|
// src/cli-bootstrap.ts
|
|
1615
1615
|
var readline2 = __toESM(require("readline/promises"));
|
|
1616
1616
|
|
|
1617
|
-
// node_modules/@vectros-ai/sdk/dist/index.mjs
|
|
1617
|
+
// ../../node_modules/@vectros-ai/sdk/dist/index.mjs
|
|
1618
1618
|
var __defProp2 = Object.defineProperty;
|
|
1619
1619
|
var __export = (target, all) => {
|
|
1620
1620
|
for (var name in all)
|
|
@@ -1648,6 +1648,7 @@ __export(api_exports, {
|
|
|
1648
1648
|
FieldDef: () => FieldDef,
|
|
1649
1649
|
FileUploadRequest: () => FileUploadRequest,
|
|
1650
1650
|
ForbiddenError: () => ForbiddenError,
|
|
1651
|
+
GatewayTimeoutError: () => GatewayTimeoutError,
|
|
1651
1652
|
IdentityLookupRequest: () => IdentityLookupRequest,
|
|
1652
1653
|
IndexFailure: () => IndexFailure,
|
|
1653
1654
|
InternalServerError: () => InternalServerError,
|
|
@@ -1672,9 +1673,12 @@ __export(api_exports, {
|
|
|
1672
1673
|
SearchRequest: () => SearchRequest,
|
|
1673
1674
|
SearchResult: () => SearchResult,
|
|
1674
1675
|
SearchResultsEvent: () => SearchResultsEvent,
|
|
1676
|
+
ServiceUnavailableError: () => ServiceUnavailableError,
|
|
1675
1677
|
TooManyRequestsError: () => TooManyRequestsError,
|
|
1678
|
+
TriggerFailureResponse: () => TriggerFailureResponse,
|
|
1676
1679
|
TruncationWarningEvent: () => TruncationWarningEvent,
|
|
1677
1680
|
UnauthorizedError: () => UnauthorizedError,
|
|
1681
|
+
UnprocessableEntityError: () => UnprocessableEntityError,
|
|
1678
1682
|
UserExistsResponse: () => UserExistsResponse,
|
|
1679
1683
|
UserRequest: () => UserRequest,
|
|
1680
1684
|
UserResponse: () => UserResponse,
|
|
@@ -1686,7 +1690,9 @@ __export(api_exports, {
|
|
|
1686
1690
|
inference: () => inference_exports,
|
|
1687
1691
|
records: () => records_exports,
|
|
1688
1692
|
schemas: () => schemas_exports,
|
|
1689
|
-
|
|
1693
|
+
scripts: () => scripts_exports,
|
|
1694
|
+
search: () => search_exports,
|
|
1695
|
+
triggers: () => triggers_exports
|
|
1690
1696
|
});
|
|
1691
1697
|
var toJson = (value, replacer, space) => {
|
|
1692
1698
|
return JSON.stringify(value, replacer, space);
|
|
@@ -1806,6 +1812,21 @@ var ForbiddenError = class extends VectrosError {
|
|
|
1806
1812
|
this.name = this.constructor.name;
|
|
1807
1813
|
}
|
|
1808
1814
|
};
|
|
1815
|
+
var GatewayTimeoutError = class extends VectrosError {
|
|
1816
|
+
constructor(body, rawResponse) {
|
|
1817
|
+
super({
|
|
1818
|
+
message: "GatewayTimeoutError",
|
|
1819
|
+
statusCode: 504,
|
|
1820
|
+
body,
|
|
1821
|
+
rawResponse
|
|
1822
|
+
});
|
|
1823
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
1824
|
+
if (Error.captureStackTrace) {
|
|
1825
|
+
Error.captureStackTrace(this, this.constructor);
|
|
1826
|
+
}
|
|
1827
|
+
this.name = this.constructor.name;
|
|
1828
|
+
}
|
|
1829
|
+
};
|
|
1809
1830
|
var InternalServerError = class extends VectrosError {
|
|
1810
1831
|
constructor(body, rawResponse) {
|
|
1811
1832
|
super({
|
|
@@ -1866,6 +1887,21 @@ var PaymentRequiredError = class extends VectrosError {
|
|
|
1866
1887
|
this.name = this.constructor.name;
|
|
1867
1888
|
}
|
|
1868
1889
|
};
|
|
1890
|
+
var ServiceUnavailableError = class extends VectrosError {
|
|
1891
|
+
constructor(body, rawResponse) {
|
|
1892
|
+
super({
|
|
1893
|
+
message: "ServiceUnavailableError",
|
|
1894
|
+
statusCode: 503,
|
|
1895
|
+
body,
|
|
1896
|
+
rawResponse
|
|
1897
|
+
});
|
|
1898
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
1899
|
+
if (Error.captureStackTrace) {
|
|
1900
|
+
Error.captureStackTrace(this, this.constructor);
|
|
1901
|
+
}
|
|
1902
|
+
this.name = this.constructor.name;
|
|
1903
|
+
}
|
|
1904
|
+
};
|
|
1869
1905
|
var TooManyRequestsError = class extends VectrosError {
|
|
1870
1906
|
constructor(body, rawResponse) {
|
|
1871
1907
|
super({
|
|
@@ -1896,6 +1932,21 @@ var UnauthorizedError = class extends VectrosError {
|
|
|
1896
1932
|
this.name = this.constructor.name;
|
|
1897
1933
|
}
|
|
1898
1934
|
};
|
|
1935
|
+
var UnprocessableEntityError = class extends VectrosError {
|
|
1936
|
+
constructor(body, rawResponse) {
|
|
1937
|
+
super({
|
|
1938
|
+
message: "UnprocessableEntityError",
|
|
1939
|
+
statusCode: 422,
|
|
1940
|
+
body,
|
|
1941
|
+
rawResponse
|
|
1942
|
+
});
|
|
1943
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
1944
|
+
if (Error.captureStackTrace) {
|
|
1945
|
+
Error.captureStackTrace(this, this.constructor);
|
|
1946
|
+
}
|
|
1947
|
+
this.name = this.constructor.name;
|
|
1948
|
+
}
|
|
1949
|
+
};
|
|
1899
1950
|
var auth_exports = {};
|
|
1900
1951
|
var ErasureRequest;
|
|
1901
1952
|
((ErasureRequest2) => {
|
|
@@ -1985,6 +2036,7 @@ var LookupRecordsRequestOrder = {
|
|
|
1985
2036
|
Desc: "desc"
|
|
1986
2037
|
};
|
|
1987
2038
|
var schemas_exports = {};
|
|
2039
|
+
var scripts_exports = {};
|
|
1988
2040
|
var SearchRequest;
|
|
1989
2041
|
((SearchRequest2) => {
|
|
1990
2042
|
SearchRequest2.Mode = {
|
|
@@ -2010,6 +2062,7 @@ var search_exports = {};
|
|
|
2010
2062
|
__export(search_exports, {
|
|
2011
2063
|
SearchRequest: () => SearchRequest
|
|
2012
2064
|
});
|
|
2065
|
+
var triggers_exports = {};
|
|
2013
2066
|
var AccessProfileRequest;
|
|
2014
2067
|
((AccessProfileRequest2) => {
|
|
2015
2068
|
AccessProfileRequest2.Status = {
|
|
@@ -2039,7 +2092,9 @@ var BatchWriteResult;
|
|
|
2039
2092
|
Created: "created",
|
|
2040
2093
|
Updated: "updated",
|
|
2041
2094
|
Conflict: "conflict",
|
|
2042
|
-
Invalid: "invalid"
|
|
2095
|
+
Invalid: "invalid",
|
|
2096
|
+
Forbidden: "forbidden",
|
|
2097
|
+
NotCommitted: "not_committed"
|
|
2043
2098
|
};
|
|
2044
2099
|
})(BatchWriteResult || (BatchWriteResult = {}));
|
|
2045
2100
|
var ChatMessage;
|
|
@@ -2343,13 +2398,23 @@ var SearchResultsEvent;
|
|
|
2343
2398
|
SearchResults: "search_results"
|
|
2344
2399
|
};
|
|
2345
2400
|
})(SearchResultsEvent || (SearchResultsEvent = {}));
|
|
2401
|
+
var TriggerFailureResponse;
|
|
2402
|
+
((TriggerFailureResponse2) => {
|
|
2403
|
+
TriggerFailureResponse2.Event = {
|
|
2404
|
+
Create: "CREATE",
|
|
2405
|
+
Update: "UPDATE",
|
|
2406
|
+
Delete: "DELETE"
|
|
2407
|
+
};
|
|
2408
|
+
})(TriggerFailureResponse || (TriggerFailureResponse = {}));
|
|
2346
2409
|
var TruncationWarningEvent;
|
|
2347
2410
|
((TruncationWarningEvent2) => {
|
|
2348
2411
|
TruncationWarningEvent2.Event = {
|
|
2349
2412
|
TruncationWarning: "truncation_warning"
|
|
2350
2413
|
};
|
|
2351
2414
|
TruncationWarningEvent2.Reason = {
|
|
2352
|
-
ContextWindowBudget: "context_window_budget"
|
|
2415
|
+
ContextWindowBudget: "context_window_budget",
|
|
2416
|
+
NoGroundableContent: "no_groundable_content",
|
|
2417
|
+
ContextWindowBudgetAndNoContent: "context_window_budget_and_no_content"
|
|
2353
2418
|
};
|
|
2354
2419
|
})(TruncationWarningEvent || (TruncationWarningEvent = {}));
|
|
2355
2420
|
var UserExistsResponse;
|
|
@@ -2364,8 +2429,7 @@ var UserRequest;
|
|
|
2364
2429
|
((UserRequest2) => {
|
|
2365
2430
|
UserRequest2.Status = {
|
|
2366
2431
|
Active: "ACTIVE",
|
|
2367
|
-
Suspended: "SUSPENDED"
|
|
2368
|
-
Pending: "PENDING"
|
|
2432
|
+
Suspended: "SUSPENDED"
|
|
2369
2433
|
};
|
|
2370
2434
|
UserRequest2.Type = {
|
|
2371
2435
|
Human: "HUMAN",
|
|
@@ -3833,6 +3897,16 @@ function mergeHeaders(...headersArray) {
|
|
|
3833
3897
|
}
|
|
3834
3898
|
return result;
|
|
3835
3899
|
}
|
|
3900
|
+
function mergeOnlyDefinedHeaders(...headersArray) {
|
|
3901
|
+
const result = {};
|
|
3902
|
+
for (const [key, value] of headersArray.filter((headers) => headers != null).flatMap((headers) => Object.entries(headers))) {
|
|
3903
|
+
const insensitiveKey = key.toLowerCase();
|
|
3904
|
+
if (value != null) {
|
|
3905
|
+
result[insensitiveKey] = value;
|
|
3906
|
+
}
|
|
3907
|
+
}
|
|
3908
|
+
return result;
|
|
3909
|
+
}
|
|
3836
3910
|
function normalizeClientOptions(options) {
|
|
3837
3911
|
const headers = mergeHeaders(
|
|
3838
3912
|
{
|
|
@@ -4103,7 +4177,7 @@ var AuthClient = class {
|
|
|
4103
4177
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/admin/keys/scoped");
|
|
4104
4178
|
}
|
|
4105
4179
|
/**
|
|
4106
|
-
* 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.
|
|
4180
|
+
* 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.
|
|
4107
4181
|
*
|
|
4108
4182
|
* @param {Vectros.CreateScopedKeyRequest} request
|
|
4109
4183
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -4111,6 +4185,7 @@ var AuthClient = class {
|
|
|
4111
4185
|
* @throws {@link Vectros.BadRequestError}
|
|
4112
4186
|
* @throws {@link Vectros.ForbiddenError}
|
|
4113
4187
|
* @throws {@link Vectros.NotFoundError}
|
|
4188
|
+
* @throws {@link Vectros.ConflictError}
|
|
4114
4189
|
* @throws {@link Vectros.TooManyRequestsError}
|
|
4115
4190
|
*
|
|
4116
4191
|
* @example
|
|
@@ -4159,6 +4234,8 @@ var AuthClient = class {
|
|
|
4159
4234
|
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
4160
4235
|
case 404:
|
|
4161
4236
|
throw new NotFoundError(_response.error.body, _response.rawResponse);
|
|
4237
|
+
case 409:
|
|
4238
|
+
throw new ConflictError(_response.error.body, _response.rawResponse);
|
|
4162
4239
|
case 429:
|
|
4163
4240
|
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
4164
4241
|
default:
|
|
@@ -4570,7 +4647,7 @@ var AuthClient = class {
|
|
|
4570
4647
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/app-contexts");
|
|
4571
4648
|
}
|
|
4572
4649
|
/**
|
|
4573
|
-
* 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.
|
|
4650
|
+
* 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.
|
|
4574
4651
|
*
|
|
4575
4652
|
* @param {Vectros.CreateAppContextRequest} request
|
|
4576
4653
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -5054,7 +5131,7 @@ var AuthClient = class {
|
|
|
5054
5131
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/app-contexts/{contextId}");
|
|
5055
5132
|
}
|
|
5056
5133
|
/**
|
|
5057
|
-
* Updates the name and/or
|
|
5134
|
+
* 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.
|
|
5058
5135
|
*
|
|
5059
5136
|
* @param {Vectros.UpdateAppContextRequest} request
|
|
5060
5137
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -5536,7 +5613,7 @@ var AuthClient = class {
|
|
|
5536
5613
|
);
|
|
5537
5614
|
}
|
|
5538
5615
|
/**
|
|
5539
|
-
* Returns full usage detail for the requested calendar month, broken down by category (search, documents, and
|
|
5616
|
+
* 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`.
|
|
5540
5617
|
*
|
|
5541
5618
|
* @param {Vectros.GetUsageRequest} request
|
|
5542
5619
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -7670,7 +7747,7 @@ var FoldersClient = class {
|
|
|
7670
7747
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "PUT", "/v1/folders/{id}");
|
|
7671
7748
|
}
|
|
7672
7749
|
/**
|
|
7673
|
-
* Permanently deletes a folder. The folder must be empty
|
|
7750
|
+
* 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.
|
|
7674
7751
|
*
|
|
7675
7752
|
* @param {Vectros.DeleteFolderRequest} request
|
|
7676
7753
|
* @param {FoldersClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -8079,11 +8156,12 @@ var IdentityClient = class {
|
|
|
8079
8156
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/entities/{namespace}/{id}");
|
|
8080
8157
|
}
|
|
8081
8158
|
/**
|
|
8082
|
-
* 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.
|
|
8159
|
+
* 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.
|
|
8083
8160
|
*
|
|
8084
8161
|
* @param {Vectros.UpdateEntityRequest} request
|
|
8085
8162
|
* @param {IdentityClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
8086
8163
|
*
|
|
8164
|
+
* @throws {@link Vectros.BadRequestError}
|
|
8087
8165
|
* @throws {@link Vectros.NotFoundError}
|
|
8088
8166
|
* @throws {@link Vectros.TooManyRequestsError}
|
|
8089
8167
|
*
|
|
@@ -8133,6 +8211,8 @@ var IdentityClient = class {
|
|
|
8133
8211
|
}
|
|
8134
8212
|
if (_response.error.reason === "status-code") {
|
|
8135
8213
|
switch (_response.error.statusCode) {
|
|
8214
|
+
case 400:
|
|
8215
|
+
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
8136
8216
|
case 404:
|
|
8137
8217
|
throw new NotFoundError(_response.error.body, _response.rawResponse);
|
|
8138
8218
|
case 429:
|
|
@@ -9357,7 +9437,7 @@ var InferenceClient = class {
|
|
|
9357
9437
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/documents/{id}/ask");
|
|
9358
9438
|
}
|
|
9359
9439
|
/**
|
|
9360
|
-
* 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
|
|
9440
|
+
* 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.
|
|
9361
9441
|
*/
|
|
9362
9442
|
ragInference(request, requestOptions) {
|
|
9363
9443
|
return HttpResponsePromise.fromPromise(this.__ragInference(request, requestOptions));
|
|
@@ -9552,14 +9632,18 @@ var RecordsClient = class {
|
|
|
9552
9632
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/records/lookup/batch");
|
|
9553
9633
|
}
|
|
9554
9634
|
/**
|
|
9555
|
-
*
|
|
9635
|
+
* 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.
|
|
9636
|
+
*
|
|
9637
|
+
* `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.
|
|
9638
|
+
*
|
|
9639
|
+
* 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.
|
|
9556
9640
|
*
|
|
9557
9641
|
* @param {Vectros.BatchWriteRequest} request
|
|
9558
9642
|
* @param {RecordsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
9559
9643
|
*
|
|
9644
|
+
* @throws {@link Vectros.BadRequestError}
|
|
9560
9645
|
* @throws {@link Vectros.ForbiddenError}
|
|
9561
9646
|
* @throws {@link Vectros.TooManyRequestsError}
|
|
9562
|
-
* @throws {@link Vectros.NotImplementedError}
|
|
9563
9647
|
*
|
|
9564
9648
|
* @example
|
|
9565
9649
|
* await client.records.batchWriteRecords()
|
|
@@ -9568,6 +9652,11 @@ var RecordsClient = class {
|
|
|
9568
9652
|
return HttpResponsePromise.fromPromise(this.__batchWriteRecords(request, requestOptions));
|
|
9569
9653
|
}
|
|
9570
9654
|
async __batchWriteRecords(request = {}, requestOptions) {
|
|
9655
|
+
const { upsert, allowClear, ..._body } = request;
|
|
9656
|
+
const _queryParams = {
|
|
9657
|
+
upsert,
|
|
9658
|
+
allowClear
|
|
9659
|
+
};
|
|
9571
9660
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
9572
9661
|
const _headers = mergeHeaders(
|
|
9573
9662
|
_authRequest.headers,
|
|
@@ -9582,9 +9671,9 @@ var RecordsClient = class {
|
|
|
9582
9671
|
method: "POST",
|
|
9583
9672
|
headers: _headers,
|
|
9584
9673
|
contentType: "application/json",
|
|
9585
|
-
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
9674
|
+
queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
|
|
9586
9675
|
requestType: "json",
|
|
9587
|
-
body:
|
|
9676
|
+
body: _body,
|
|
9588
9677
|
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
9589
9678
|
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
9590
9679
|
abortSignal: requestOptions?.abortSignal,
|
|
@@ -9596,12 +9685,12 @@ var RecordsClient = class {
|
|
|
9596
9685
|
}
|
|
9597
9686
|
if (_response.error.reason === "status-code") {
|
|
9598
9687
|
switch (_response.error.statusCode) {
|
|
9688
|
+
case 400:
|
|
9689
|
+
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
9599
9690
|
case 403:
|
|
9600
9691
|
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
9601
9692
|
case 429:
|
|
9602
9693
|
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
9603
|
-
case 501:
|
|
9604
|
-
throw new NotImplementedError(_response.error.body, _response.rawResponse);
|
|
9605
9694
|
default:
|
|
9606
9695
|
throw new VectrosError({
|
|
9607
9696
|
statusCode: _response.error.statusCode,
|
|
@@ -10504,7 +10593,7 @@ var SchemasClient = class {
|
|
|
10504
10593
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/schemas/{id}");
|
|
10505
10594
|
}
|
|
10506
10595
|
/**
|
|
10507
|
-
* 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.
|
|
10596
|
+
* 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.
|
|
10508
10597
|
*
|
|
10509
10598
|
* @param {Vectros.UpdateSchemaRequest} request
|
|
10510
10599
|
* @param {SchemasClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -10512,6 +10601,7 @@ var SchemasClient = class {
|
|
|
10512
10601
|
* @throws {@link Vectros.BadRequestError}
|
|
10513
10602
|
* @throws {@link Vectros.ForbiddenError}
|
|
10514
10603
|
* @throws {@link Vectros.NotFoundError}
|
|
10604
|
+
* @throws {@link Vectros.ConflictError}
|
|
10515
10605
|
* @throws {@link Vectros.TooManyRequestsError}
|
|
10516
10606
|
*
|
|
10517
10607
|
* @example
|
|
@@ -10563,6 +10653,8 @@ var SchemasClient = class {
|
|
|
10563
10653
|
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
10564
10654
|
case 404:
|
|
10565
10655
|
throw new NotFoundError(_response.error.body, _response.rawResponse);
|
|
10656
|
+
case 409:
|
|
10657
|
+
throw new ConflictError(_response.error.body, _response.rawResponse);
|
|
10566
10658
|
case 429:
|
|
10567
10659
|
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
10568
10660
|
default:
|
|
@@ -10576,7 +10668,7 @@ var SchemasClient = class {
|
|
|
10576
10668
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "PUT", "/v1/schemas/{id}");
|
|
10577
10669
|
}
|
|
10578
10670
|
/**
|
|
10579
|
-
* 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.
|
|
10671
|
+
* Permanently deletes a record schema. The request is refused with 409 if records of this type still exist — delete those records first, since every record must reference a live schema. A lineage base (a schema other schemas declare `basedOn`) also cannot be deleted while any such variant still exists — delete the variant schema(s) first. It is likewise refused while any trigger rule fires off this schema — delete those trigger rules first. Requires the `schemas:d` scope.
|
|
10580
10672
|
*
|
|
10581
10673
|
* @param {Vectros.DeleteSchemaRequest} request
|
|
10582
10674
|
* @param {SchemasClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -10698,46 +10790,70 @@ var SchemasClient = class {
|
|
|
10698
10790
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/schemas/{id}/versions");
|
|
10699
10791
|
}
|
|
10700
10792
|
};
|
|
10701
|
-
var
|
|
10793
|
+
var ScriptsClient = class {
|
|
10702
10794
|
constructor(options) {
|
|
10703
10795
|
this._options = normalizeClientOptionsWithAuth(options);
|
|
10704
10796
|
}
|
|
10705
10797
|
/**
|
|
10706
|
-
* Runs a
|
|
10798
|
+
* 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.
|
|
10707
10799
|
*
|
|
10708
|
-
*
|
|
10709
|
-
*
|
|
10800
|
+
* **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.
|
|
10801
|
+
*
|
|
10802
|
+
* **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.
|
|
10803
|
+
*
|
|
10804
|
+
* **Budget.** Seconds of useful work: the script has 15 s of wall clock (never more than 16) and a bounded number of statements and host calls; a runaway script is stopped early with `RESOURCE_LIMIT_EXCEEDED`. The transaction is bounded by the number of underlying storage rows it writes — 100 rows after merging — rather than by the number of entities: creating a folder costs 3 rows; creating a plain text document or a record costs 1 row plus one per range-indexed lookup field plus one per `reference` field. So "create a folder and file N documents" holds up to 97 plain documents, 48 with one range field or one reference, 32 with both; if a script exceeds the limit the error says so (`WRITE_BUFFER_CAP_EXCEEDED`, with `total` and `limit`) and nothing is committed. This request takes the write-path limits (burst, credit ceiling, principal quota) once, before the script runs — there is no free-read allowance here, so an account over its credit ceiling is refused even for a read-only script. Reads the script performs are metered as reads, writes as writes, and execution time beyond what those operations include is charged as script execution time (see `GET /v1/usage`).
|
|
10805
|
+
*
|
|
10806
|
+
* **Reads inside the script.** A `get` sees the script's own staged writes. A list, query or lookup never shows a row the script has deleted, shows a row it re-saved with the updated content, and does not yet show a row it created — you hold that row's id from `create()`.
|
|
10807
|
+
*
|
|
10808
|
+
* **Retries.** Send an optional `Idempotency-Key` header (1–128 characters of letters, digits, `.`, `_`, `:` or `-`) to make a retry safe: a request repeated under the same key within 24 hours receives the same response as the first attempt without executing again — when that first attempt SUCCEEDED (a duplicate still in flight is refused with `409 IDEMPOTENCY_IN_PROGRESS`; the same key on a different request is refused with `422 IDEMPOTENCY_KEY_REUSED`). A first attempt that FAILED with any other status is not remembered: the same key is free to try again, so a retry after a `400` or a `429` executes. If an execution's outcome cannot be determined — the script did not finish inside the budget while its writes may have committed — the response is `500 EXECUTION_OUTCOME_UNKNOWN` carrying `execution.id`, and a keyed retry receives that same answer: inspect what the script would have written, then use a NEW key for a new attempt. When a key is present the serialised `result` is capped at 16 KB instead of 256 KB. Without a key, a lost response cannot be distinguished from a failed one; re-read before retrying.
|
|
10809
|
+
*
|
|
10810
|
+
* @param {Vectros.ScriptExecuteRequest} request
|
|
10811
|
+
* @param {ScriptsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
10710
10812
|
*
|
|
10711
10813
|
* @throws {@link Vectros.BadRequestError}
|
|
10814
|
+
* @throws {@link Vectros.PaymentRequiredError}
|
|
10712
10815
|
* @throws {@link Vectros.ForbiddenError}
|
|
10816
|
+
* @throws {@link Vectros.NotFoundError}
|
|
10817
|
+
* @throws {@link Vectros.ConflictError}
|
|
10818
|
+
* @throws {@link Vectros.ContentTooLargeError}
|
|
10819
|
+
* @throws {@link Vectros.UnprocessableEntityError}
|
|
10713
10820
|
* @throws {@link Vectros.TooManyRequestsError}
|
|
10821
|
+
* @throws {@link Vectros.InternalServerError}
|
|
10822
|
+
* @throws {@link Vectros.ServiceUnavailableError}
|
|
10823
|
+
* @throws {@link Vectros.GatewayTimeoutError}
|
|
10714
10824
|
*
|
|
10715
10825
|
* @example
|
|
10716
|
-
* await client.
|
|
10717
|
-
*
|
|
10826
|
+
* await client.scripts.executeScript({
|
|
10827
|
+
* "Idempotency-Key": "order-8412-attempt",
|
|
10828
|
+
* scriptRef: {
|
|
10829
|
+
* name: "notify-assignee",
|
|
10830
|
+
* version: "3"
|
|
10831
|
+
* }
|
|
10718
10832
|
* })
|
|
10719
10833
|
*/
|
|
10720
|
-
|
|
10721
|
-
return HttpResponsePromise.fromPromise(this.
|
|
10834
|
+
executeScript(request, requestOptions) {
|
|
10835
|
+
return HttpResponsePromise.fromPromise(this.__executeScript(request, requestOptions));
|
|
10722
10836
|
}
|
|
10723
|
-
async
|
|
10837
|
+
async __executeScript(request, requestOptions) {
|
|
10838
|
+
const { "Idempotency-Key": idempotencyKey, ..._body } = request;
|
|
10724
10839
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
10725
10840
|
const _headers = mergeHeaders(
|
|
10726
10841
|
_authRequest.headers,
|
|
10727
10842
|
this._options?.headers,
|
|
10843
|
+
mergeOnlyDefinedHeaders({ "Idempotency-Key": idempotencyKey }),
|
|
10728
10844
|
requestOptions?.headers
|
|
10729
10845
|
);
|
|
10730
10846
|
const _response = await fetcher({
|
|
10731
10847
|
url: url_exports.join(
|
|
10732
10848
|
await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
|
|
10733
|
-
"v1/
|
|
10849
|
+
"v1/scripts/execute"
|
|
10734
10850
|
),
|
|
10735
10851
|
method: "POST",
|
|
10736
10852
|
headers: _headers,
|
|
10737
10853
|
contentType: "application/json",
|
|
10738
10854
|
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
10739
10855
|
requestType: "json",
|
|
10740
|
-
body:
|
|
10856
|
+
body: _body,
|
|
10741
10857
|
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
10742
10858
|
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
10743
10859
|
abortSignal: requestOptions?.abortSignal,
|
|
@@ -10751,10 +10867,26 @@ var SearchClient = class {
|
|
|
10751
10867
|
switch (_response.error.statusCode) {
|
|
10752
10868
|
case 400:
|
|
10753
10869
|
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
10870
|
+
case 402:
|
|
10871
|
+
throw new PaymentRequiredError(_response.error.body, _response.rawResponse);
|
|
10754
10872
|
case 403:
|
|
10755
10873
|
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
10874
|
+
case 404:
|
|
10875
|
+
throw new NotFoundError(_response.error.body, _response.rawResponse);
|
|
10876
|
+
case 409:
|
|
10877
|
+
throw new ConflictError(_response.error.body, _response.rawResponse);
|
|
10878
|
+
case 413:
|
|
10879
|
+
throw new ContentTooLargeError(_response.error.body, _response.rawResponse);
|
|
10880
|
+
case 422:
|
|
10881
|
+
throw new UnprocessableEntityError(_response.error.body, _response.rawResponse);
|
|
10756
10882
|
case 429:
|
|
10757
10883
|
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
10884
|
+
case 500:
|
|
10885
|
+
throw new InternalServerError(_response.error.body, _response.rawResponse);
|
|
10886
|
+
case 503:
|
|
10887
|
+
throw new ServiceUnavailableError(_response.error.body, _response.rawResponse);
|
|
10888
|
+
case 504:
|
|
10889
|
+
throw new GatewayTimeoutError(_response.error.body, _response.rawResponse);
|
|
10758
10890
|
default:
|
|
10759
10891
|
throw new VectrosError({
|
|
10760
10892
|
statusCode: _response.error.statusCode,
|
|
@@ -10763,71 +10895,834 @@ var SearchClient = class {
|
|
|
10763
10895
|
});
|
|
10764
10896
|
}
|
|
10765
10897
|
}
|
|
10766
|
-
return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/
|
|
10767
|
-
}
|
|
10768
|
-
};
|
|
10769
|
-
var VectrosClient = class {
|
|
10770
|
-
constructor(options) {
|
|
10771
|
-
this._options = normalizeClientOptionsWithAuth(options);
|
|
10772
|
-
}
|
|
10773
|
-
get auth() {
|
|
10774
|
-
return this._auth ?? (this._auth = new AuthClient(this._options));
|
|
10775
|
-
}
|
|
10776
|
-
get documents() {
|
|
10777
|
-
return this._documents ?? (this._documents = new DocumentsClient(this._options));
|
|
10778
|
-
}
|
|
10779
|
-
get identity() {
|
|
10780
|
-
return this._identity ?? (this._identity = new IdentityClient(this._options));
|
|
10781
|
-
}
|
|
10782
|
-
get compliance() {
|
|
10783
|
-
return this._compliance ?? (this._compliance = new ComplianceClient(this._options));
|
|
10784
|
-
}
|
|
10785
|
-
get folders() {
|
|
10786
|
-
return this._folders ?? (this._folders = new FoldersClient(this._options));
|
|
10787
|
-
}
|
|
10788
|
-
get inference() {
|
|
10789
|
-
return this._inference ?? (this._inference = new InferenceClient(this._options));
|
|
10898
|
+
return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/scripts/execute");
|
|
10790
10899
|
}
|
|
10791
|
-
|
|
10792
|
-
|
|
10793
|
-
|
|
10794
|
-
|
|
10795
|
-
|
|
10900
|
+
/**
|
|
10901
|
+
* Returns a paginated list of your scripts. Pass `name` to list every version of one script name (oldest first); omit it for a flat list across every name in your account (newest first). Results are returned as a `{data, nextCursor}` envelope — pass `nextCursor` as `startFrom` to fetch the next page. Requires the `scripts:r` scope.
|
|
10902
|
+
*
|
|
10903
|
+
* @param {Vectros.ListScriptsRequest} request
|
|
10904
|
+
* @param {ScriptsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
10905
|
+
*
|
|
10906
|
+
* @example
|
|
10907
|
+
* await client.scripts.listScripts({
|
|
10908
|
+
* name: "notify-assignee",
|
|
10909
|
+
* startFrom: "b3BhcXVlLWN1cnNvci1mcm9tLXRoZS1wcmV2aW91cy1wYWdl"
|
|
10910
|
+
* })
|
|
10911
|
+
*/
|
|
10912
|
+
listScripts(request = {}, requestOptions) {
|
|
10913
|
+
return HttpResponsePromise.fromPromise(this.__listScripts(request, requestOptions));
|
|
10796
10914
|
}
|
|
10797
|
-
|
|
10798
|
-
|
|
10915
|
+
async __listScripts(request = {}, requestOptions) {
|
|
10916
|
+
const { name, startFrom, limit } = request;
|
|
10917
|
+
const _queryParams = {
|
|
10918
|
+
name,
|
|
10919
|
+
startFrom,
|
|
10920
|
+
limit
|
|
10921
|
+
};
|
|
10922
|
+
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
10923
|
+
const _headers = mergeHeaders(
|
|
10924
|
+
_authRequest.headers,
|
|
10925
|
+
this._options?.headers,
|
|
10926
|
+
requestOptions?.headers
|
|
10927
|
+
);
|
|
10928
|
+
const _response = await fetcher({
|
|
10929
|
+
url: url_exports.join(
|
|
10930
|
+
await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
|
|
10931
|
+
"v1/scripts"
|
|
10932
|
+
),
|
|
10933
|
+
method: "GET",
|
|
10934
|
+
headers: _headers,
|
|
10935
|
+
queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
|
|
10936
|
+
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
10937
|
+
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
10938
|
+
abortSignal: requestOptions?.abortSignal,
|
|
10939
|
+
fetchFn: this._options?.fetch,
|
|
10940
|
+
logging: this._options.logging
|
|
10941
|
+
});
|
|
10942
|
+
if (_response.ok) {
|
|
10943
|
+
return { data: _response.body, rawResponse: _response.rawResponse };
|
|
10944
|
+
}
|
|
10945
|
+
if (_response.error.reason === "status-code") {
|
|
10946
|
+
throw new VectrosError({
|
|
10947
|
+
statusCode: _response.error.statusCode,
|
|
10948
|
+
body: _response.error.body,
|
|
10949
|
+
rawResponse: _response.rawResponse
|
|
10950
|
+
});
|
|
10951
|
+
}
|
|
10952
|
+
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/scripts");
|
|
10799
10953
|
}
|
|
10800
10954
|
/**
|
|
10801
|
-
*
|
|
10802
|
-
* This is useful for making requests to endpoints not yet supported in the SDK.
|
|
10803
|
-
* The input can be a URL string, URL object, or Request object. Relative paths are resolved against the configured base URL.
|
|
10955
|
+
* Stores a new version of a script object. Every POST creates a genuinely new version — this is NOT idempotent-by-name: pushing `name` again always creates a new row with `scriptVersion` auto-incremented (the first version pushed for a name is 1). `source` is stored as-is: nothing parses or validates it at push time. A version runs when a trigger rule references it (`POST /v1/triggers`) or when `POST /v1/scripts/execute` names it. Requires the `scripts:c` scope.
|
|
10804
10956
|
*
|
|
10805
|
-
* @param {
|
|
10806
|
-
* @param {
|
|
10807
|
-
*
|
|
10808
|
-
* @
|
|
10957
|
+
* @param {Vectros.ScriptRequest} request
|
|
10958
|
+
* @param {ScriptsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
10959
|
+
*
|
|
10960
|
+
* @throws {@link Vectros.BadRequestError}
|
|
10961
|
+
* @throws {@link Vectros.ForbiddenError}
|
|
10962
|
+
* @throws {@link Vectros.ConflictError}
|
|
10963
|
+
* @throws {@link Vectros.TooManyRequestsError}
|
|
10964
|
+
*
|
|
10965
|
+
* @example
|
|
10966
|
+
* await client.scripts.createScript({
|
|
10967
|
+
* name: "notify-assignee",
|
|
10968
|
+
* source: "export default function(input) { return { ok: true }; }"
|
|
10969
|
+
* })
|
|
10809
10970
|
*/
|
|
10810
|
-
|
|
10811
|
-
return
|
|
10812
|
-
|
|
10813
|
-
|
|
10814
|
-
|
|
10815
|
-
|
|
10816
|
-
|
|
10817
|
-
|
|
10818
|
-
|
|
10819
|
-
fetch: this._options.fetch,
|
|
10820
|
-
logging: this._options.logging,
|
|
10821
|
-
getAuthHeaders: async () => (await this._options.authProvider.getAuthRequest()).headers
|
|
10822
|
-
},
|
|
10823
|
-
requestOptions
|
|
10971
|
+
createScript(request, requestOptions) {
|
|
10972
|
+
return HttpResponsePromise.fromPromise(this.__createScript(request, requestOptions));
|
|
10973
|
+
}
|
|
10974
|
+
async __createScript(request, requestOptions) {
|
|
10975
|
+
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
10976
|
+
const _headers = mergeHeaders(
|
|
10977
|
+
_authRequest.headers,
|
|
10978
|
+
this._options?.headers,
|
|
10979
|
+
requestOptions?.headers
|
|
10824
10980
|
);
|
|
10981
|
+
const _response = await fetcher({
|
|
10982
|
+
url: url_exports.join(
|
|
10983
|
+
await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
|
|
10984
|
+
"v1/scripts"
|
|
10985
|
+
),
|
|
10986
|
+
method: "POST",
|
|
10987
|
+
headers: _headers,
|
|
10988
|
+
contentType: "application/json",
|
|
10989
|
+
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
10990
|
+
requestType: "json",
|
|
10991
|
+
body: request,
|
|
10992
|
+
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
10993
|
+
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
10994
|
+
abortSignal: requestOptions?.abortSignal,
|
|
10995
|
+
fetchFn: this._options?.fetch,
|
|
10996
|
+
logging: this._options.logging
|
|
10997
|
+
});
|
|
10998
|
+
if (_response.ok) {
|
|
10999
|
+
return { data: _response.body, rawResponse: _response.rawResponse };
|
|
11000
|
+
}
|
|
11001
|
+
if (_response.error.reason === "status-code") {
|
|
11002
|
+
switch (_response.error.statusCode) {
|
|
11003
|
+
case 400:
|
|
11004
|
+
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
11005
|
+
case 403:
|
|
11006
|
+
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
11007
|
+
case 409:
|
|
11008
|
+
throw new ConflictError(_response.error.body, _response.rawResponse);
|
|
11009
|
+
case 429:
|
|
11010
|
+
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
11011
|
+
default:
|
|
11012
|
+
throw new VectrosError({
|
|
11013
|
+
statusCode: _response.error.statusCode,
|
|
11014
|
+
body: _response.error.body,
|
|
11015
|
+
rawResponse: _response.rawResponse
|
|
11016
|
+
});
|
|
11017
|
+
}
|
|
11018
|
+
}
|
|
11019
|
+
return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/scripts");
|
|
10825
11020
|
}
|
|
10826
|
-
|
|
10827
|
-
|
|
10828
|
-
|
|
10829
|
-
|
|
10830
|
-
|
|
11021
|
+
/**
|
|
11022
|
+
* Retrieves a single script version by its ID. Requires the `scripts:r` scope.
|
|
11023
|
+
*
|
|
11024
|
+
* @param {Vectros.GetScriptRequest} request
|
|
11025
|
+
* @param {ScriptsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
11026
|
+
*
|
|
11027
|
+
* @throws {@link Vectros.NotFoundError}
|
|
11028
|
+
*
|
|
11029
|
+
* @example
|
|
11030
|
+
* await client.scripts.getScript({
|
|
11031
|
+
* id: "id"
|
|
11032
|
+
* })
|
|
11033
|
+
*/
|
|
11034
|
+
getScript(request, requestOptions) {
|
|
11035
|
+
return HttpResponsePromise.fromPromise(this.__getScript(request, requestOptions));
|
|
11036
|
+
}
|
|
11037
|
+
async __getScript(request, requestOptions) {
|
|
11038
|
+
const { id } = request;
|
|
11039
|
+
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
11040
|
+
const _headers = mergeHeaders(
|
|
11041
|
+
_authRequest.headers,
|
|
11042
|
+
this._options?.headers,
|
|
11043
|
+
requestOptions?.headers
|
|
11044
|
+
);
|
|
11045
|
+
const _response = await fetcher({
|
|
11046
|
+
url: url_exports.join(
|
|
11047
|
+
await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
|
|
11048
|
+
`v1/scripts/${url_exports.encodePathParam(id)}`
|
|
11049
|
+
),
|
|
11050
|
+
method: "GET",
|
|
11051
|
+
headers: _headers,
|
|
11052
|
+
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
11053
|
+
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
11054
|
+
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
11055
|
+
abortSignal: requestOptions?.abortSignal,
|
|
11056
|
+
fetchFn: this._options?.fetch,
|
|
11057
|
+
logging: this._options.logging
|
|
11058
|
+
});
|
|
11059
|
+
if (_response.ok) {
|
|
11060
|
+
return { data: _response.body, rawResponse: _response.rawResponse };
|
|
11061
|
+
}
|
|
11062
|
+
if (_response.error.reason === "status-code") {
|
|
11063
|
+
switch (_response.error.statusCode) {
|
|
11064
|
+
case 404:
|
|
11065
|
+
throw new NotFoundError(_response.error.body, _response.rawResponse);
|
|
11066
|
+
default:
|
|
11067
|
+
throw new VectrosError({
|
|
11068
|
+
statusCode: _response.error.statusCode,
|
|
11069
|
+
body: _response.error.body,
|
|
11070
|
+
rawResponse: _response.rawResponse
|
|
11071
|
+
});
|
|
11072
|
+
}
|
|
11073
|
+
}
|
|
11074
|
+
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/scripts/{id}");
|
|
11075
|
+
}
|
|
11076
|
+
/**
|
|
11077
|
+
* 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.
|
|
11078
|
+
*
|
|
11079
|
+
* @param {Vectros.UpdateScriptRequest} request
|
|
11080
|
+
* @param {ScriptsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
11081
|
+
*
|
|
11082
|
+
* @throws {@link Vectros.BadRequestError}
|
|
11083
|
+
* @throws {@link Vectros.TooManyRequestsError}
|
|
11084
|
+
*
|
|
11085
|
+
* @example
|
|
11086
|
+
* await client.scripts.updateScript({
|
|
11087
|
+
* id: "id",
|
|
11088
|
+
* body: {
|
|
11089
|
+
* name: "notify-assignee",
|
|
11090
|
+
* source: "export default function(input) { return { ok: true }; }"
|
|
11091
|
+
* }
|
|
11092
|
+
* })
|
|
11093
|
+
*/
|
|
11094
|
+
updateScript(request, requestOptions) {
|
|
11095
|
+
return HttpResponsePromise.fromPromise(this.__updateScript(request, requestOptions));
|
|
11096
|
+
}
|
|
11097
|
+
async __updateScript(request, requestOptions) {
|
|
11098
|
+
const { id, body: _body } = request;
|
|
11099
|
+
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
11100
|
+
const _headers = mergeHeaders(
|
|
11101
|
+
_authRequest.headers,
|
|
11102
|
+
this._options?.headers,
|
|
11103
|
+
requestOptions?.headers
|
|
11104
|
+
);
|
|
11105
|
+
const _response = await fetcher({
|
|
11106
|
+
url: url_exports.join(
|
|
11107
|
+
await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
|
|
11108
|
+
`v1/scripts/${url_exports.encodePathParam(id)}`
|
|
11109
|
+
),
|
|
11110
|
+
method: "PUT",
|
|
11111
|
+
headers: _headers,
|
|
11112
|
+
contentType: "application/json",
|
|
11113
|
+
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
11114
|
+
requestType: "json",
|
|
11115
|
+
body: _body,
|
|
11116
|
+
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
11117
|
+
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
11118
|
+
abortSignal: requestOptions?.abortSignal,
|
|
11119
|
+
fetchFn: this._options?.fetch,
|
|
11120
|
+
logging: this._options.logging
|
|
11121
|
+
});
|
|
11122
|
+
if (_response.ok) {
|
|
11123
|
+
return { data: void 0, rawResponse: _response.rawResponse };
|
|
11124
|
+
}
|
|
11125
|
+
if (_response.error.reason === "status-code") {
|
|
11126
|
+
switch (_response.error.statusCode) {
|
|
11127
|
+
case 400:
|
|
11128
|
+
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
11129
|
+
case 429:
|
|
11130
|
+
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
11131
|
+
default:
|
|
11132
|
+
throw new VectrosError({
|
|
11133
|
+
statusCode: _response.error.statusCode,
|
|
11134
|
+
body: _response.error.body,
|
|
11135
|
+
rawResponse: _response.rawResponse
|
|
11136
|
+
});
|
|
11137
|
+
}
|
|
11138
|
+
}
|
|
11139
|
+
return handleNonStatusCodeError(_response.error, _response.rawResponse, "PUT", "/v1/scripts/{id}");
|
|
11140
|
+
}
|
|
11141
|
+
/**
|
|
11142
|
+
* Permanently deletes one script version by ID. Deleting one version does not affect any other version of the same name. Requires the `scripts:d` scope.
|
|
11143
|
+
*
|
|
11144
|
+
* @param {Vectros.DeleteScriptRequest} request
|
|
11145
|
+
* @param {ScriptsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
11146
|
+
*
|
|
11147
|
+
* @throws {@link Vectros.NotFoundError}
|
|
11148
|
+
* @throws {@link Vectros.TooManyRequestsError}
|
|
11149
|
+
*
|
|
11150
|
+
* @example
|
|
11151
|
+
* await client.scripts.deleteScript({
|
|
11152
|
+
* id: "id"
|
|
11153
|
+
* })
|
|
11154
|
+
*/
|
|
11155
|
+
deleteScript(request, requestOptions) {
|
|
11156
|
+
return HttpResponsePromise.fromPromise(this.__deleteScript(request, requestOptions));
|
|
11157
|
+
}
|
|
11158
|
+
async __deleteScript(request, requestOptions) {
|
|
11159
|
+
const { id } = request;
|
|
11160
|
+
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
11161
|
+
const _headers = mergeHeaders(
|
|
11162
|
+
_authRequest.headers,
|
|
11163
|
+
this._options?.headers,
|
|
11164
|
+
requestOptions?.headers
|
|
11165
|
+
);
|
|
11166
|
+
const _response = await fetcher({
|
|
11167
|
+
url: url_exports.join(
|
|
11168
|
+
await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
|
|
11169
|
+
`v1/scripts/${url_exports.encodePathParam(id)}`
|
|
11170
|
+
),
|
|
11171
|
+
method: "DELETE",
|
|
11172
|
+
headers: _headers,
|
|
11173
|
+
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
11174
|
+
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
11175
|
+
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
11176
|
+
abortSignal: requestOptions?.abortSignal,
|
|
11177
|
+
fetchFn: this._options?.fetch,
|
|
11178
|
+
logging: this._options.logging
|
|
11179
|
+
});
|
|
11180
|
+
if (_response.ok) {
|
|
11181
|
+
return { data: void 0, rawResponse: _response.rawResponse };
|
|
11182
|
+
}
|
|
11183
|
+
if (_response.error.reason === "status-code") {
|
|
11184
|
+
switch (_response.error.statusCode) {
|
|
11185
|
+
case 404:
|
|
11186
|
+
throw new NotFoundError(_response.error.body, _response.rawResponse);
|
|
11187
|
+
case 429:
|
|
11188
|
+
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
11189
|
+
default:
|
|
11190
|
+
throw new VectrosError({
|
|
11191
|
+
statusCode: _response.error.statusCode,
|
|
11192
|
+
body: _response.error.body,
|
|
11193
|
+
rawResponse: _response.rawResponse
|
|
11194
|
+
});
|
|
11195
|
+
}
|
|
11196
|
+
}
|
|
11197
|
+
return handleNonStatusCodeError(_response.error, _response.rawResponse, "DELETE", "/v1/scripts/{id}");
|
|
11198
|
+
}
|
|
11199
|
+
};
|
|
11200
|
+
var SearchClient = class {
|
|
11201
|
+
constructor(options) {
|
|
11202
|
+
this._options = normalizeClientOptionsWithAuth(options);
|
|
11203
|
+
}
|
|
11204
|
+
/**
|
|
11205
|
+
* 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.
|
|
11206
|
+
*
|
|
11207
|
+
* @param {Vectros.SearchRequest} request
|
|
11208
|
+
* @param {SearchClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
11209
|
+
*
|
|
11210
|
+
* @throws {@link Vectros.BadRequestError}
|
|
11211
|
+
* @throws {@link Vectros.ForbiddenError}
|
|
11212
|
+
* @throws {@link Vectros.TooManyRequestsError}
|
|
11213
|
+
*
|
|
11214
|
+
* @example
|
|
11215
|
+
* await client.search.content({
|
|
11216
|
+
* query: "patient intake form diabetes"
|
|
11217
|
+
* })
|
|
11218
|
+
*/
|
|
11219
|
+
content(request, requestOptions) {
|
|
11220
|
+
return HttpResponsePromise.fromPromise(this.__content(request, requestOptions));
|
|
11221
|
+
}
|
|
11222
|
+
async __content(request, requestOptions) {
|
|
11223
|
+
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
11224
|
+
const _headers = mergeHeaders(
|
|
11225
|
+
_authRequest.headers,
|
|
11226
|
+
this._options?.headers,
|
|
11227
|
+
requestOptions?.headers
|
|
11228
|
+
);
|
|
11229
|
+
const _response = await fetcher({
|
|
11230
|
+
url: url_exports.join(
|
|
11231
|
+
await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
|
|
11232
|
+
"v1/search"
|
|
11233
|
+
),
|
|
11234
|
+
method: "POST",
|
|
11235
|
+
headers: _headers,
|
|
11236
|
+
contentType: "application/json",
|
|
11237
|
+
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
11238
|
+
requestType: "json",
|
|
11239
|
+
body: request,
|
|
11240
|
+
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
11241
|
+
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
11242
|
+
abortSignal: requestOptions?.abortSignal,
|
|
11243
|
+
fetchFn: this._options?.fetch,
|
|
11244
|
+
logging: this._options.logging
|
|
11245
|
+
});
|
|
11246
|
+
if (_response.ok) {
|
|
11247
|
+
return { data: _response.body, rawResponse: _response.rawResponse };
|
|
11248
|
+
}
|
|
11249
|
+
if (_response.error.reason === "status-code") {
|
|
11250
|
+
switch (_response.error.statusCode) {
|
|
11251
|
+
case 400:
|
|
11252
|
+
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
11253
|
+
case 403:
|
|
11254
|
+
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
11255
|
+
case 429:
|
|
11256
|
+
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
11257
|
+
default:
|
|
11258
|
+
throw new VectrosError({
|
|
11259
|
+
statusCode: _response.error.statusCode,
|
|
11260
|
+
body: _response.error.body,
|
|
11261
|
+
rawResponse: _response.rawResponse
|
|
11262
|
+
});
|
|
11263
|
+
}
|
|
11264
|
+
}
|
|
11265
|
+
return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/search");
|
|
11266
|
+
}
|
|
11267
|
+
};
|
|
11268
|
+
var TriggersClient = class {
|
|
11269
|
+
constructor(options) {
|
|
11270
|
+
this._options = normalizeClientOptionsWithAuth(options);
|
|
11271
|
+
}
|
|
11272
|
+
/**
|
|
11273
|
+
* 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.
|
|
11274
|
+
*
|
|
11275
|
+
* 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.
|
|
11276
|
+
*
|
|
11277
|
+
* `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.
|
|
11278
|
+
*
|
|
11279
|
+
* Scoped to your account and app context, derived from your token — never from input. Requires the `triggers:r` scope.
|
|
11280
|
+
*
|
|
11281
|
+
* @param {Vectros.ListTriggerFailuresRequest} request
|
|
11282
|
+
* @param {TriggersClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
11283
|
+
*
|
|
11284
|
+
* @example
|
|
11285
|
+
* await client.triggers.listTriggerFailures({
|
|
11286
|
+
* ruleId: "3b1c9f2e7a5d8410f6c2e9a1b7d4038f5e6a91c2d7f0b3a4586c9e2f1d0a7b3c",
|
|
11287
|
+
* category: "SCRIPT_ERROR",
|
|
11288
|
+
* startFrom: "b3BhcXVlLWN1cnNvci1mcm9tLXRoZS1wcmV2aW91cy1wYWdl"
|
|
11289
|
+
* })
|
|
11290
|
+
*/
|
|
11291
|
+
listTriggerFailures(request = {}, requestOptions) {
|
|
11292
|
+
return HttpResponsePromise.fromPromise(this.__listTriggerFailures(request, requestOptions));
|
|
11293
|
+
}
|
|
11294
|
+
async __listTriggerFailures(request = {}, requestOptions) {
|
|
11295
|
+
const { ruleId, category, retryable, from: from_, to, startFrom, limit } = request;
|
|
11296
|
+
const _queryParams = {
|
|
11297
|
+
ruleId,
|
|
11298
|
+
category,
|
|
11299
|
+
retryable,
|
|
11300
|
+
from: from_,
|
|
11301
|
+
to,
|
|
11302
|
+
startFrom,
|
|
11303
|
+
limit
|
|
11304
|
+
};
|
|
11305
|
+
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
11306
|
+
const _headers = mergeHeaders(
|
|
11307
|
+
_authRequest.headers,
|
|
11308
|
+
this._options?.headers,
|
|
11309
|
+
requestOptions?.headers
|
|
11310
|
+
);
|
|
11311
|
+
const _response = await fetcher({
|
|
11312
|
+
url: url_exports.join(
|
|
11313
|
+
await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
|
|
11314
|
+
"v1/trigger-failures"
|
|
11315
|
+
),
|
|
11316
|
+
method: "GET",
|
|
11317
|
+
headers: _headers,
|
|
11318
|
+
queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
|
|
11319
|
+
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
11320
|
+
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
11321
|
+
abortSignal: requestOptions?.abortSignal,
|
|
11322
|
+
fetchFn: this._options?.fetch,
|
|
11323
|
+
logging: this._options.logging
|
|
11324
|
+
});
|
|
11325
|
+
if (_response.ok) {
|
|
11326
|
+
return { data: _response.body, rawResponse: _response.rawResponse };
|
|
11327
|
+
}
|
|
11328
|
+
if (_response.error.reason === "status-code") {
|
|
11329
|
+
throw new VectrosError({
|
|
11330
|
+
statusCode: _response.error.statusCode,
|
|
11331
|
+
body: _response.error.body,
|
|
11332
|
+
rawResponse: _response.rawResponse
|
|
11333
|
+
});
|
|
11334
|
+
}
|
|
11335
|
+
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/trigger-failures");
|
|
11336
|
+
}
|
|
11337
|
+
/**
|
|
11338
|
+
* 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.
|
|
11339
|
+
*
|
|
11340
|
+
* @param {Vectros.ListTriggersRequest} request
|
|
11341
|
+
* @param {TriggersClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
11342
|
+
*
|
|
11343
|
+
* @example
|
|
11344
|
+
* await client.triggers.listTriggers({
|
|
11345
|
+
* startFrom: "b3BhcXVlLWN1cnNvci1mcm9tLXRoZS1wcmV2aW91cy1wYWdl"
|
|
11346
|
+
* })
|
|
11347
|
+
*/
|
|
11348
|
+
listTriggers(request = {}, requestOptions) {
|
|
11349
|
+
return HttpResponsePromise.fromPromise(this.__listTriggers(request, requestOptions));
|
|
11350
|
+
}
|
|
11351
|
+
async __listTriggers(request = {}, requestOptions) {
|
|
11352
|
+
const { startFrom, limit } = request;
|
|
11353
|
+
const _queryParams = {
|
|
11354
|
+
startFrom,
|
|
11355
|
+
limit
|
|
11356
|
+
};
|
|
11357
|
+
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
11358
|
+
const _headers = mergeHeaders(
|
|
11359
|
+
_authRequest.headers,
|
|
11360
|
+
this._options?.headers,
|
|
11361
|
+
requestOptions?.headers
|
|
11362
|
+
);
|
|
11363
|
+
const _response = await fetcher({
|
|
11364
|
+
url: url_exports.join(
|
|
11365
|
+
await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
|
|
11366
|
+
"v1/triggers"
|
|
11367
|
+
),
|
|
11368
|
+
method: "GET",
|
|
11369
|
+
headers: _headers,
|
|
11370
|
+
queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
|
|
11371
|
+
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
11372
|
+
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
11373
|
+
abortSignal: requestOptions?.abortSignal,
|
|
11374
|
+
fetchFn: this._options?.fetch,
|
|
11375
|
+
logging: this._options.logging
|
|
11376
|
+
});
|
|
11377
|
+
if (_response.ok) {
|
|
11378
|
+
return { data: _response.body, rawResponse: _response.rawResponse };
|
|
11379
|
+
}
|
|
11380
|
+
if (_response.error.reason === "status-code") {
|
|
11381
|
+
throw new VectrosError({
|
|
11382
|
+
statusCode: _response.error.statusCode,
|
|
11383
|
+
body: _response.error.body,
|
|
11384
|
+
rawResponse: _response.rawResponse
|
|
11385
|
+
});
|
|
11386
|
+
}
|
|
11387
|
+
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/triggers");
|
|
11388
|
+
}
|
|
11389
|
+
/**
|
|
11390
|
+
* 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).
|
|
11391
|
+
*
|
|
11392
|
+
* @param {Vectros.CreateTriggerRequest} request
|
|
11393
|
+
* @param {TriggersClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
11394
|
+
*
|
|
11395
|
+
* @throws {@link Vectros.BadRequestError}
|
|
11396
|
+
* @throws {@link Vectros.ForbiddenError}
|
|
11397
|
+
* @throws {@link Vectros.TooManyRequestsError}
|
|
11398
|
+
*
|
|
11399
|
+
* @example
|
|
11400
|
+
* await client.triggers.createTrigger({
|
|
11401
|
+
* body: {
|
|
11402
|
+
* name: "on-intake-create",
|
|
11403
|
+
* firingSource: {
|
|
11404
|
+
* schemaId: "sch_9f8e7d6c",
|
|
11405
|
+
* event: "CREATE"
|
|
11406
|
+
* },
|
|
11407
|
+
* scriptRef: {
|
|
11408
|
+
* name: "notify-assignee",
|
|
11409
|
+
* version: "3"
|
|
11410
|
+
* }
|
|
11411
|
+
* }
|
|
11412
|
+
* })
|
|
11413
|
+
*/
|
|
11414
|
+
createTrigger(request, requestOptions) {
|
|
11415
|
+
return HttpResponsePromise.fromPromise(this.__createTrigger(request, requestOptions));
|
|
11416
|
+
}
|
|
11417
|
+
async __createTrigger(request, requestOptions) {
|
|
11418
|
+
const { upsert, body: _body } = request;
|
|
11419
|
+
const _queryParams = {
|
|
11420
|
+
upsert
|
|
11421
|
+
};
|
|
11422
|
+
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
11423
|
+
const _headers = mergeHeaders(
|
|
11424
|
+
_authRequest.headers,
|
|
11425
|
+
this._options?.headers,
|
|
11426
|
+
requestOptions?.headers
|
|
11427
|
+
);
|
|
11428
|
+
const _response = await fetcher({
|
|
11429
|
+
url: url_exports.join(
|
|
11430
|
+
await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
|
|
11431
|
+
"v1/triggers"
|
|
11432
|
+
),
|
|
11433
|
+
method: "POST",
|
|
11434
|
+
headers: _headers,
|
|
11435
|
+
contentType: "application/json",
|
|
11436
|
+
queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
|
|
11437
|
+
requestType: "json",
|
|
11438
|
+
body: _body,
|
|
11439
|
+
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
11440
|
+
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
11441
|
+
abortSignal: requestOptions?.abortSignal,
|
|
11442
|
+
fetchFn: this._options?.fetch,
|
|
11443
|
+
logging: this._options.logging
|
|
11444
|
+
});
|
|
11445
|
+
if (_response.ok) {
|
|
11446
|
+
return { data: _response.body, rawResponse: _response.rawResponse };
|
|
11447
|
+
}
|
|
11448
|
+
if (_response.error.reason === "status-code") {
|
|
11449
|
+
switch (_response.error.statusCode) {
|
|
11450
|
+
case 400:
|
|
11451
|
+
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
11452
|
+
case 403:
|
|
11453
|
+
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
11454
|
+
case 429:
|
|
11455
|
+
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
11456
|
+
default:
|
|
11457
|
+
throw new VectrosError({
|
|
11458
|
+
statusCode: _response.error.statusCode,
|
|
11459
|
+
body: _response.error.body,
|
|
11460
|
+
rawResponse: _response.rawResponse
|
|
11461
|
+
});
|
|
11462
|
+
}
|
|
11463
|
+
}
|
|
11464
|
+
return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/triggers");
|
|
11465
|
+
}
|
|
11466
|
+
/**
|
|
11467
|
+
* Retrieves a single trigger rule by its ID. Requires the `triggers:r` scope.
|
|
11468
|
+
*
|
|
11469
|
+
* @param {Vectros.GetTriggerRequest} request
|
|
11470
|
+
* @param {TriggersClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
11471
|
+
*
|
|
11472
|
+
* @throws {@link Vectros.NotFoundError}
|
|
11473
|
+
*
|
|
11474
|
+
* @example
|
|
11475
|
+
* await client.triggers.getTrigger({
|
|
11476
|
+
* id: "id"
|
|
11477
|
+
* })
|
|
11478
|
+
*/
|
|
11479
|
+
getTrigger(request, requestOptions) {
|
|
11480
|
+
return HttpResponsePromise.fromPromise(this.__getTrigger(request, requestOptions));
|
|
11481
|
+
}
|
|
11482
|
+
async __getTrigger(request, requestOptions) {
|
|
11483
|
+
const { id } = request;
|
|
11484
|
+
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
11485
|
+
const _headers = mergeHeaders(
|
|
11486
|
+
_authRequest.headers,
|
|
11487
|
+
this._options?.headers,
|
|
11488
|
+
requestOptions?.headers
|
|
11489
|
+
);
|
|
11490
|
+
const _response = await fetcher({
|
|
11491
|
+
url: url_exports.join(
|
|
11492
|
+
await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
|
|
11493
|
+
`v1/triggers/${url_exports.encodePathParam(id)}`
|
|
11494
|
+
),
|
|
11495
|
+
method: "GET",
|
|
11496
|
+
headers: _headers,
|
|
11497
|
+
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
11498
|
+
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
11499
|
+
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
11500
|
+
abortSignal: requestOptions?.abortSignal,
|
|
11501
|
+
fetchFn: this._options?.fetch,
|
|
11502
|
+
logging: this._options.logging
|
|
11503
|
+
});
|
|
11504
|
+
if (_response.ok) {
|
|
11505
|
+
return { data: _response.body, rawResponse: _response.rawResponse };
|
|
11506
|
+
}
|
|
11507
|
+
if (_response.error.reason === "status-code") {
|
|
11508
|
+
switch (_response.error.statusCode) {
|
|
11509
|
+
case 404:
|
|
11510
|
+
throw new NotFoundError(_response.error.body, _response.rawResponse);
|
|
11511
|
+
default:
|
|
11512
|
+
throw new VectrosError({
|
|
11513
|
+
statusCode: _response.error.statusCode,
|
|
11514
|
+
body: _response.error.body,
|
|
11515
|
+
rawResponse: _response.rawResponse
|
|
11516
|
+
});
|
|
11517
|
+
}
|
|
11518
|
+
}
|
|
11519
|
+
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/triggers/{id}");
|
|
11520
|
+
}
|
|
11521
|
+
/**
|
|
11522
|
+
* 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.
|
|
11523
|
+
*
|
|
11524
|
+
* @param {Vectros.UpdateTriggerRequest} request
|
|
11525
|
+
* @param {TriggersClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
11526
|
+
*
|
|
11527
|
+
* @throws {@link Vectros.BadRequestError}
|
|
11528
|
+
* @throws {@link Vectros.ForbiddenError}
|
|
11529
|
+
* @throws {@link Vectros.NotFoundError}
|
|
11530
|
+
* @throws {@link Vectros.TooManyRequestsError}
|
|
11531
|
+
*
|
|
11532
|
+
* @example
|
|
11533
|
+
* await client.triggers.updateTrigger({
|
|
11534
|
+
* id: "id",
|
|
11535
|
+
* body: {
|
|
11536
|
+
* name: "on-intake-create",
|
|
11537
|
+
* firingSource: {
|
|
11538
|
+
* schemaId: "sch_9f8e7d6c",
|
|
11539
|
+
* event: "CREATE"
|
|
11540
|
+
* },
|
|
11541
|
+
* scriptRef: {
|
|
11542
|
+
* name: "notify-assignee",
|
|
11543
|
+
* version: "3"
|
|
11544
|
+
* }
|
|
11545
|
+
* }
|
|
11546
|
+
* })
|
|
11547
|
+
*/
|
|
11548
|
+
updateTrigger(request, requestOptions) {
|
|
11549
|
+
return HttpResponsePromise.fromPromise(this.__updateTrigger(request, requestOptions));
|
|
11550
|
+
}
|
|
11551
|
+
async __updateTrigger(request, requestOptions) {
|
|
11552
|
+
const { id, body: _body } = request;
|
|
11553
|
+
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
11554
|
+
const _headers = mergeHeaders(
|
|
11555
|
+
_authRequest.headers,
|
|
11556
|
+
this._options?.headers,
|
|
11557
|
+
requestOptions?.headers
|
|
11558
|
+
);
|
|
11559
|
+
const _response = await fetcher({
|
|
11560
|
+
url: url_exports.join(
|
|
11561
|
+
await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
|
|
11562
|
+
`v1/triggers/${url_exports.encodePathParam(id)}`
|
|
11563
|
+
),
|
|
11564
|
+
method: "PUT",
|
|
11565
|
+
headers: _headers,
|
|
11566
|
+
contentType: "application/json",
|
|
11567
|
+
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
11568
|
+
requestType: "json",
|
|
11569
|
+
body: _body,
|
|
11570
|
+
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
11571
|
+
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
11572
|
+
abortSignal: requestOptions?.abortSignal,
|
|
11573
|
+
fetchFn: this._options?.fetch,
|
|
11574
|
+
logging: this._options.logging
|
|
11575
|
+
});
|
|
11576
|
+
if (_response.ok) {
|
|
11577
|
+
return { data: _response.body, rawResponse: _response.rawResponse };
|
|
11578
|
+
}
|
|
11579
|
+
if (_response.error.reason === "status-code") {
|
|
11580
|
+
switch (_response.error.statusCode) {
|
|
11581
|
+
case 400:
|
|
11582
|
+
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
11583
|
+
case 403:
|
|
11584
|
+
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
11585
|
+
case 404:
|
|
11586
|
+
throw new NotFoundError(_response.error.body, _response.rawResponse);
|
|
11587
|
+
case 429:
|
|
11588
|
+
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
11589
|
+
default:
|
|
11590
|
+
throw new VectrosError({
|
|
11591
|
+
statusCode: _response.error.statusCode,
|
|
11592
|
+
body: _response.error.body,
|
|
11593
|
+
rawResponse: _response.rawResponse
|
|
11594
|
+
});
|
|
11595
|
+
}
|
|
11596
|
+
}
|
|
11597
|
+
return handleNonStatusCodeError(_response.error, _response.rawResponse, "PUT", "/v1/triggers/{id}");
|
|
11598
|
+
}
|
|
11599
|
+
/**
|
|
11600
|
+
* Permanently deletes one trigger rule by ID. Requires the `triggers:d` scope.
|
|
11601
|
+
*
|
|
11602
|
+
* @param {Vectros.DeleteTriggerRequest} request
|
|
11603
|
+
* @param {TriggersClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
11604
|
+
*
|
|
11605
|
+
* @throws {@link Vectros.NotFoundError}
|
|
11606
|
+
* @throws {@link Vectros.TooManyRequestsError}
|
|
11607
|
+
*
|
|
11608
|
+
* @example
|
|
11609
|
+
* await client.triggers.deleteTrigger({
|
|
11610
|
+
* id: "id"
|
|
11611
|
+
* })
|
|
11612
|
+
*/
|
|
11613
|
+
deleteTrigger(request, requestOptions) {
|
|
11614
|
+
return HttpResponsePromise.fromPromise(this.__deleteTrigger(request, requestOptions));
|
|
11615
|
+
}
|
|
11616
|
+
async __deleteTrigger(request, requestOptions) {
|
|
11617
|
+
const { id } = request;
|
|
11618
|
+
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
11619
|
+
const _headers = mergeHeaders(
|
|
11620
|
+
_authRequest.headers,
|
|
11621
|
+
this._options?.headers,
|
|
11622
|
+
requestOptions?.headers
|
|
11623
|
+
);
|
|
11624
|
+
const _response = await fetcher({
|
|
11625
|
+
url: url_exports.join(
|
|
11626
|
+
await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
|
|
11627
|
+
`v1/triggers/${url_exports.encodePathParam(id)}`
|
|
11628
|
+
),
|
|
11629
|
+
method: "DELETE",
|
|
11630
|
+
headers: _headers,
|
|
11631
|
+
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
11632
|
+
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
11633
|
+
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
11634
|
+
abortSignal: requestOptions?.abortSignal,
|
|
11635
|
+
fetchFn: this._options?.fetch,
|
|
11636
|
+
logging: this._options.logging
|
|
11637
|
+
});
|
|
11638
|
+
if (_response.ok) {
|
|
11639
|
+
return { data: void 0, rawResponse: _response.rawResponse };
|
|
11640
|
+
}
|
|
11641
|
+
if (_response.error.reason === "status-code") {
|
|
11642
|
+
switch (_response.error.statusCode) {
|
|
11643
|
+
case 404:
|
|
11644
|
+
throw new NotFoundError(_response.error.body, _response.rawResponse);
|
|
11645
|
+
case 429:
|
|
11646
|
+
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
11647
|
+
default:
|
|
11648
|
+
throw new VectrosError({
|
|
11649
|
+
statusCode: _response.error.statusCode,
|
|
11650
|
+
body: _response.error.body,
|
|
11651
|
+
rawResponse: _response.rawResponse
|
|
11652
|
+
});
|
|
11653
|
+
}
|
|
11654
|
+
}
|
|
11655
|
+
return handleNonStatusCodeError(_response.error, _response.rawResponse, "DELETE", "/v1/triggers/{id}");
|
|
11656
|
+
}
|
|
11657
|
+
};
|
|
11658
|
+
var VectrosClient = class {
|
|
11659
|
+
constructor(options) {
|
|
11660
|
+
this._options = normalizeClientOptionsWithAuth(options);
|
|
11661
|
+
}
|
|
11662
|
+
get auth() {
|
|
11663
|
+
return this._auth ?? (this._auth = new AuthClient(this._options));
|
|
11664
|
+
}
|
|
11665
|
+
get documents() {
|
|
11666
|
+
return this._documents ?? (this._documents = new DocumentsClient(this._options));
|
|
11667
|
+
}
|
|
11668
|
+
get identity() {
|
|
11669
|
+
return this._identity ?? (this._identity = new IdentityClient(this._options));
|
|
11670
|
+
}
|
|
11671
|
+
get compliance() {
|
|
11672
|
+
return this._compliance ?? (this._compliance = new ComplianceClient(this._options));
|
|
11673
|
+
}
|
|
11674
|
+
get folders() {
|
|
11675
|
+
return this._folders ?? (this._folders = new FoldersClient(this._options));
|
|
11676
|
+
}
|
|
11677
|
+
get inference() {
|
|
11678
|
+
return this._inference ?? (this._inference = new InferenceClient(this._options));
|
|
11679
|
+
}
|
|
11680
|
+
get records() {
|
|
11681
|
+
return this._records ?? (this._records = new RecordsClient(this._options));
|
|
11682
|
+
}
|
|
11683
|
+
get schemas() {
|
|
11684
|
+
return this._schemas ?? (this._schemas = new SchemasClient(this._options));
|
|
11685
|
+
}
|
|
11686
|
+
get scripts() {
|
|
11687
|
+
return this._scripts ?? (this._scripts = new ScriptsClient(this._options));
|
|
11688
|
+
}
|
|
11689
|
+
get search() {
|
|
11690
|
+
return this._search ?? (this._search = new SearchClient(this._options));
|
|
11691
|
+
}
|
|
11692
|
+
get triggers() {
|
|
11693
|
+
return this._triggers ?? (this._triggers = new TriggersClient(this._options));
|
|
11694
|
+
}
|
|
11695
|
+
/**
|
|
11696
|
+
* Make a passthrough request using the SDK's configured auth, retry, logging, etc.
|
|
11697
|
+
* This is useful for making requests to endpoints not yet supported in the SDK.
|
|
11698
|
+
* The input can be a URL string, URL object, or Request object. Relative paths are resolved against the configured base URL.
|
|
11699
|
+
*
|
|
11700
|
+
* @param {Request | string | URL} input - The URL, path, or Request object.
|
|
11701
|
+
* @param {RequestInit} init - Standard fetch RequestInit options.
|
|
11702
|
+
* @param {core.PassthroughRequest.RequestOptions} requestOptions - Per-request overrides (timeout, retries, headers, abort signal).
|
|
11703
|
+
* @returns {Promise<Response>} A standard Response object.
|
|
11704
|
+
*/
|
|
11705
|
+
async fetch(input, init, requestOptions) {
|
|
11706
|
+
return makePassthroughRequest(
|
|
11707
|
+
input,
|
|
11708
|
+
init,
|
|
11709
|
+
{
|
|
11710
|
+
baseUrl: this._options.baseUrl ?? this._options.environment,
|
|
11711
|
+
headers: this._options.headers,
|
|
11712
|
+
timeoutInSeconds: this._options.timeoutInSeconds,
|
|
11713
|
+
maxRetries: this._options.maxRetries,
|
|
11714
|
+
fetch: this._options.fetch,
|
|
11715
|
+
logging: this._options.logging,
|
|
11716
|
+
getAuthHeaders: async () => (await this._options.authProvider.getAuthRequest()).headers
|
|
11717
|
+
},
|
|
11718
|
+
requestOptions
|
|
11719
|
+
);
|
|
11720
|
+
}
|
|
11721
|
+
};
|
|
11722
|
+
var logging;
|
|
11723
|
+
((logging2) => {
|
|
11724
|
+
logging2.LogLevel = LogLevel;
|
|
11725
|
+
logging2.ConsoleLogger = ConsoleLogger;
|
|
10831
11726
|
})(logging || (logging = {}));
|
|
10832
11727
|
|
|
10833
11728
|
// ../blueprints/dist/index.mjs
|
|
@@ -10879,6 +11774,20 @@ var BlueprintFieldDefSchema = import_zod.z.object({
|
|
|
10879
11774
|
// the `s` reveal scope for this record type (SchemaRequest.FieldDef.sensitive).
|
|
10880
11775
|
// Format passthrough — the loader forwards it to createSchema. Default false.
|
|
10881
11776
|
sensitive: import_zod.z.boolean().optional(),
|
|
11777
|
+
// Keep this field on the RECORD ROW when the payload is stored out of line (a large record, or a
|
|
11778
|
+
// LARGE_PAYLOAD storage profile), so it appears in list/lookup projections without
|
|
11779
|
+
// `includePayload`.
|
|
11780
|
+
//
|
|
11781
|
+
// Load-bearing for triggers: a trigger rule's `fields` may name a field only if it is `inline`,
|
|
11782
|
+
// `filterable`, or a lookup field — and never `sensitive`. So this is the flag that makes an
|
|
11783
|
+
// otherwise-ordinary field projectable into a triggered script's `input.record`. Mutually
|
|
11784
|
+
// exclusive with `sensitive` (the platform refuses the combination). The platform also refuses,
|
|
11785
|
+
// with a 409, removing whatever keeps a field projectable while a live trigger rule still
|
|
11786
|
+
// projects it — dropping `inline` OR `filterable` OR the lookup that carried it, not just this
|
|
11787
|
+
// flag.
|
|
11788
|
+
//
|
|
11789
|
+
// Format passthrough — the loader forwards it to createSchema. Default false.
|
|
11790
|
+
inline: import_zod.z.boolean().optional(),
|
|
10882
11791
|
// Reference-field surface — a typed foreign-key link to another record. The
|
|
10883
11792
|
// platform (SchemaRequest.FieldDef) requires BOTH targetTypeName AND
|
|
10884
11793
|
// targetSurface on a reference field; the blueprint format uses the SAME names
|
|
@@ -10901,6 +11810,13 @@ var BlueprintFieldDefSchema = import_zod.z.object({
|
|
|
10901
11810
|
targetSurface: import_zod.z.string().min(1).optional(),
|
|
10902
11811
|
cardinality: import_zod.z.enum(["one", "many"]).optional()
|
|
10903
11812
|
}).strict().superRefine((field, ctx) => {
|
|
11813
|
+
if (field.inline === true && field.sensitive === true) {
|
|
11814
|
+
ctx.addIssue({
|
|
11815
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
11816
|
+
path: ["inline"],
|
|
11817
|
+
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"
|
|
11818
|
+
});
|
|
11819
|
+
}
|
|
10904
11820
|
const isReference = field.fieldType === "reference";
|
|
10905
11821
|
const hasRefKeys = field.targetTypeName !== void 0 || field.targetField !== void 0 || field.targetSurface !== void 0 || field.cardinality !== void 0;
|
|
10906
11822
|
if (isReference && field.targetTypeName === void 0) {
|
|
@@ -10994,7 +11910,8 @@ var BlueprintLookupFieldSchema = import_zod.z.union([
|
|
|
10994
11910
|
})
|
|
10995
11911
|
]);
|
|
10996
11912
|
var BlueprintCapabilitiesSchema = import_zod.z.object({
|
|
10997
|
-
auditHistory: import_zod.z.boolean().optional()
|
|
11913
|
+
auditHistory: import_zod.z.boolean().optional(),
|
|
11914
|
+
triggersEnabled: import_zod.z.boolean().optional()
|
|
10998
11915
|
}).strict();
|
|
10999
11916
|
var BlueprintSchemaSchema = import_zod.z.object({
|
|
11000
11917
|
typeName: import_zod.z.string().min(1),
|
|
@@ -11102,6 +12019,30 @@ var GrantedCapabilitiesSchema = import_zod.z.array(import_zod.z.string().min(1))
|
|
|
11102
12019
|
seen.add(name);
|
|
11103
12020
|
});
|
|
11104
12021
|
});
|
|
12022
|
+
var MAX_ASSIGNABLE_ROLES = 20;
|
|
12023
|
+
var ROLE_ID_RE = /^[a-z][a-z0-9-]{2,30}$/;
|
|
12024
|
+
var AssignableRolesSchema = import_zod.z.array(
|
|
12025
|
+
import_zod.z.string().regex(ROLE_ID_RE, {
|
|
12026
|
+
message: "an assignableRoles entry must be a roleId: 3-31 chars, a lowercase letter first, then lowercase letters/digits/dashes (e.g. 'case-handler')"
|
|
12027
|
+
})
|
|
12028
|
+
).min(1, {
|
|
12029
|
+
message: "assignableRoles must either be omitted (no roleId restriction) or name at least one roleId \u2014 an empty list states no rule"
|
|
12030
|
+
}).max(MAX_ASSIGNABLE_ROLES, {
|
|
12031
|
+
message: `assignableRoles accepts at most ${MAX_ASSIGNABLE_ROLES} entries`
|
|
12032
|
+
}).optional().superRefine((roleIds, ctx) => {
|
|
12033
|
+
if (!roleIds) return;
|
|
12034
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12035
|
+
roleIds.forEach((rid, i) => {
|
|
12036
|
+
if (seen.has(rid)) {
|
|
12037
|
+
ctx.addIssue({
|
|
12038
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
12039
|
+
path: [i],
|
|
12040
|
+
message: `assignableRoles names '${rid}' more than once \u2014 list it once`
|
|
12041
|
+
});
|
|
12042
|
+
}
|
|
12043
|
+
seen.add(rid);
|
|
12044
|
+
});
|
|
12045
|
+
});
|
|
11105
12046
|
var BlueprintAccessProfileSchema = import_zod.z.object({
|
|
11106
12047
|
// Validated structurally here; the SCOPE GATE (in @vectros-ai/cli) is
|
|
11107
12048
|
// what enforces the data-plane-only security boundary.
|
|
@@ -11171,7 +12112,14 @@ var BlueprintAccessProfileSchema = import_zod.z.object({
|
|
|
11171
12112
|
// blast-radius question from granting the same capability to a role that
|
|
11172
12113
|
// a person is bound to. The gate should weigh the two separately rather
|
|
11173
12114
|
// than reuse one policy for both.
|
|
11174
|
-
capabilities: GrantedCapabilitiesSchema
|
|
12115
|
+
capabilities: GrantedCapabilitiesSchema,
|
|
12116
|
+
// Optional roleId allow-list on this profile's own inline clause — see
|
|
12117
|
+
// {@link AssignableRolesSchema}. Allowed here for the same symmetry reason `capabilities` is:
|
|
12118
|
+
// the platform models an access profile's scopes as a list of the SAME ScopeClause a role clause
|
|
12119
|
+
// uses, and the loader already builds this profile as one such clause. Only meaningful when the
|
|
12120
|
+
// clause grants role-composing authority in the first place (`profiles:c`-shaped); on a
|
|
12121
|
+
// clause that grants none it is inert rather than an error, exactly as on the platform.
|
|
12122
|
+
assignableRoles: AssignableRolesSchema
|
|
11175
12123
|
}).strict().superRefine((ap, ctx) => {
|
|
11176
12124
|
const hasInline = ap.allowedActions !== void 0;
|
|
11177
12125
|
const hasRoleIds = ap.roleIds !== void 0;
|
|
@@ -11198,13 +12146,21 @@ var BlueprintAccessProfileSchema = import_zod.z.object({
|
|
|
11198
12146
|
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"
|
|
11199
12147
|
});
|
|
11200
12148
|
}
|
|
12149
|
+
if (ap.assignableRoles !== void 0) {
|
|
12150
|
+
ctx.addIssue({
|
|
12151
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
12152
|
+
path: ["assignableRoles"],
|
|
12153
|
+
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"
|
|
12154
|
+
});
|
|
12155
|
+
}
|
|
11201
12156
|
}
|
|
11202
12157
|
});
|
|
11203
12158
|
var BlueprintRoleClauseSchema = import_zod.z.object({
|
|
11204
12159
|
allowedActions: import_zod.z.array(import_zod.z.string().min(1)).min(1),
|
|
11205
12160
|
dataScope: import_zod.z.record(import_zod.z.array(import_zod.z.union([import_zod.z.string().min(1), import_zod.z.null()]))).optional(),
|
|
11206
12161
|
dataScopeRef: import_zod.z.string().min(1).optional(),
|
|
11207
|
-
capabilities: GrantedCapabilitiesSchema
|
|
12162
|
+
capabilities: GrantedCapabilitiesSchema,
|
|
12163
|
+
assignableRoles: AssignableRolesSchema
|
|
11208
12164
|
}).strict().superRefine((c, ctx) => {
|
|
11209
12165
|
if (c.dataScope !== void 0 && c.dataScopeRef !== void 0) {
|
|
11210
12166
|
ctx.addIssue({
|
|
@@ -11220,6 +12176,115 @@ var BlueprintFragmentsSchema = import_zod.z.record(
|
|
|
11220
12176
|
var BlueprintRolesSchema = import_zod.z.record(import_zod.z.array(BlueprintRoleClauseSchema).min(1));
|
|
11221
12177
|
var BlueprintRoleAssumableSchema = import_zod.z.record(import_zod.z.array(import_zod.z.string().min(1)).min(1));
|
|
11222
12178
|
var BlueprintRoleAssumableMapSchema = import_zod.z.record(BlueprintRoleAssumableSchema);
|
|
12179
|
+
var BlueprintTriggerFiringSourceSchema = import_zod.z.object({
|
|
12180
|
+
schemaName: import_zod.z.string().min(1),
|
|
12181
|
+
event: import_zod.z.enum(["CREATE", "UPDATE", "DELETE"])
|
|
12182
|
+
}).strict();
|
|
12183
|
+
var SCRIPT_VERSION_INTEGER_RE = /^[1-9][0-9]*$/;
|
|
12184
|
+
var BlueprintTriggerScriptRefSchema = import_zod.z.object({
|
|
12185
|
+
name: import_zod.z.string().min(1),
|
|
12186
|
+
version: import_zod.z.string().min(1)
|
|
12187
|
+
}).strict().superRefine((ref, ctx) => {
|
|
12188
|
+
if (ref.version !== "latest" && !SCRIPT_VERSION_INTEGER_RE.test(ref.version)) {
|
|
12189
|
+
ctx.addIssue({
|
|
12190
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
12191
|
+
path: ["version"],
|
|
12192
|
+
message: `scriptRef.version '${ref.version}' is invalid \u2014 must be a positive decimal integer (as a string, e.g. '3') or the literal 'latest'`
|
|
12193
|
+
});
|
|
12194
|
+
}
|
|
12195
|
+
});
|
|
12196
|
+
var MANIFEST_VERB_RE = /^[a-z]+(?:\.[a-z]+)+$/;
|
|
12197
|
+
var BlueprintTriggerManifestSchema = import_zod.z.array(
|
|
12198
|
+
import_zod.z.string().min(1).regex(MANIFEST_VERB_RE, {
|
|
12199
|
+
message: "a manifest entry must be '<noun>.<verb>' (e.g. 'records.update')"
|
|
12200
|
+
})
|
|
12201
|
+
).optional();
|
|
12202
|
+
var BlueprintTriggerEntrySchema = import_zod.z.object({
|
|
12203
|
+
firingSource: BlueprintTriggerFiringSourceSchema,
|
|
12204
|
+
scriptRef: BlueprintTriggerScriptRefSchema,
|
|
12205
|
+
allowedActions: import_zod.z.array(import_zod.z.string().min(1)).min(1).optional(),
|
|
12206
|
+
roleIds: import_zod.z.array(import_zod.z.string().min(1)).min(1).optional(),
|
|
12207
|
+
dataScope: import_zod.z.record(import_zod.z.array(import_zod.z.union([import_zod.z.string().min(1), import_zod.z.null()]))).optional(),
|
|
12208
|
+
capabilities: GrantedCapabilitiesSchema,
|
|
12209
|
+
manifest: BlueprintTriggerManifestSchema,
|
|
12210
|
+
/**
|
|
12211
|
+
* REQUIRED. The record fields this rule projects into its script's `input.record` — and, on an
|
|
12212
|
+
* UPDATE firing, `input.previous` (the same fields as they were before the write). On a DELETE
|
|
12213
|
+
* firing `input.record` carries these fields from the deleted row, which is the only way a
|
|
12214
|
+
* script can see anything of it.
|
|
12215
|
+
*
|
|
12216
|
+
* **An empty list is the "I project nothing" declaration**, not an omission: the script still
|
|
12217
|
+
* receives `input.recordId`. It is required rather than defaulted precisely so that projecting
|
|
12218
|
+
* nothing is something an author STATES, not something they get by forgetting the field.
|
|
12219
|
+
*
|
|
12220
|
+
* Every entry must name a field the schema keeps INLINE — declared `inline: true`, or
|
|
12221
|
+
* `filterable`, or used as a lookup field (a leg of one, or its `sortBy` target) — and never one
|
|
12222
|
+
* marked `sensitive`.
|
|
12223
|
+
* `lintTriggerFields` checks that against the schema this blueprint declares; the platform
|
|
12224
|
+
* checks it again, and refuses a schema update that would un-inline a field a live rule still
|
|
12225
|
+
* projects.
|
|
12226
|
+
*
|
|
12227
|
+
* A field the record does not carry arrives as `undefined`, never null. `record` and `previous`
|
|
12228
|
+
* together are capped at 224 KB serialised — a firing whose projected input exceeds that is not
|
|
12229
|
+
* run, and is recorded as an `INPUT_TOO_LARGE` failure naming the size.
|
|
12230
|
+
*/
|
|
12231
|
+
fields: import_zod.z.array(import_zod.z.string().min(1))
|
|
12232
|
+
}).strict().superRefine((entry, ctx) => {
|
|
12233
|
+
const hasInline = entry.allowedActions !== void 0;
|
|
12234
|
+
const hasRoleIds = entry.roleIds !== void 0;
|
|
12235
|
+
if (hasInline === hasRoleIds) {
|
|
12236
|
+
ctx.addIssue({
|
|
12237
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
12238
|
+
path: ["roleIds"],
|
|
12239
|
+
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)"
|
|
12240
|
+
});
|
|
12241
|
+
return;
|
|
12242
|
+
}
|
|
12243
|
+
if (hasRoleIds) {
|
|
12244
|
+
if (entry.dataScope !== void 0) {
|
|
12245
|
+
ctx.addIssue({
|
|
12246
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
12247
|
+
path: ["dataScope"],
|
|
12248
|
+
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"
|
|
12249
|
+
});
|
|
12250
|
+
}
|
|
12251
|
+
if (entry.capabilities !== void 0) {
|
|
12252
|
+
ctx.addIssue({
|
|
12253
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
12254
|
+
path: ["capabilities"],
|
|
12255
|
+
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"
|
|
12256
|
+
});
|
|
12257
|
+
}
|
|
12258
|
+
}
|
|
12259
|
+
});
|
|
12260
|
+
var BlueprintTriggersSchema = import_zod.z.record(BlueprintTriggerEntrySchema);
|
|
12261
|
+
var SCRIPT_NAME_RE = /^[A-Za-z0-9_-]{1,64}$/;
|
|
12262
|
+
var SCRIPT_SOURCE_MAX_BYTES = 3e5;
|
|
12263
|
+
var SCRIPT_INPUT_CONTRACT_MAX_BYTES = 5e4;
|
|
12264
|
+
var utf8Bytes = (s) => new TextEncoder().encode(s).length;
|
|
12265
|
+
var BlueprintScriptEntrySchema = import_zod.z.object({
|
|
12266
|
+
/**
|
|
12267
|
+
* The script's source text, shipped verbatim.
|
|
12268
|
+
*
|
|
12269
|
+
* NOTHING PARSES THIS — not this package, and not the platform, which stores it as-is. A syntax
|
|
12270
|
+
* error is therefore invisible until the version first RUNS, where it surfaces as a
|
|
12271
|
+
* `SCRIPT_ERROR` from the trigger rule that dispatched it. Validation here is limited to what
|
|
12272
|
+
* can be checked without a JS parser: non-blank, and within the platform's byte cap.
|
|
12273
|
+
*/
|
|
12274
|
+
source: import_zod.z.string().refine((s) => s.trim().length > 0, { message: "'source' must not be blank" }).refine((s) => utf8Bytes(s) <= SCRIPT_SOURCE_MAX_BYTES, {
|
|
12275
|
+
message: `'source' exceeds the platform's ${SCRIPT_SOURCE_MAX_BYTES.toLocaleString("en-US")}-byte cap (measured in UTF-8 bytes, not characters)`
|
|
12276
|
+
}),
|
|
12277
|
+
/**
|
|
12278
|
+
* Optional free-text description of the input shape this script expects.
|
|
12279
|
+
*
|
|
12280
|
+
* DESCRIPTIVE ONLY — neither this package nor the platform validates a firing's input against
|
|
12281
|
+
* it. It documents the contract for a human reading the blueprint; it does not enforce one.
|
|
12282
|
+
*/
|
|
12283
|
+
declaredInputContract: import_zod.z.string().min(1).refine((s) => utf8Bytes(s) <= SCRIPT_INPUT_CONTRACT_MAX_BYTES, {
|
|
12284
|
+
message: `'declaredInputContract' exceeds the platform's ${SCRIPT_INPUT_CONTRACT_MAX_BYTES.toLocaleString("en-US")}-byte cap (measured in UTF-8 bytes, not characters)`
|
|
12285
|
+
}).optional()
|
|
12286
|
+
}).strict();
|
|
12287
|
+
var BlueprintScriptsSchema = import_zod.z.record(BlueprintScriptEntrySchema);
|
|
11223
12288
|
var IDENTITY_NAMESPACE_RE = /^[a-z][a-z0-9_-]{1,31}$/;
|
|
11224
12289
|
var FORBIDDEN_IDENTITY_NAMESPACES = /* @__PURE__ */ new Set(["record", "document", "entity", "user"]);
|
|
11225
12290
|
var IdentityKindSchema = import_zod.z.string().min(1).superRefine((val, ctx) => {
|
|
@@ -11254,6 +12319,24 @@ var BlueprintServicePrincipalSchema = import_zod.z.object({
|
|
|
11254
12319
|
displayName: import_zod.z.string().min(1)
|
|
11255
12320
|
}).strict();
|
|
11256
12321
|
var ISSUER_ID_RE = CONTEXT_ID_RE;
|
|
12322
|
+
var MAX_CAPTURED_CLAIMS = 20;
|
|
12323
|
+
var MAX_CAPTURED_CLAIM_NAME_LENGTH = 64;
|
|
12324
|
+
function assertNoDuplicateClaimNames(field) {
|
|
12325
|
+
return (claims, ctx) => {
|
|
12326
|
+
if (!claims) return;
|
|
12327
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12328
|
+
claims.forEach((name, i) => {
|
|
12329
|
+
if (seen.has(name)) {
|
|
12330
|
+
ctx.addIssue({
|
|
12331
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
12332
|
+
path: [i],
|
|
12333
|
+
message: `${field} repeats '${name}' \u2014 each claim name may appear only once`
|
|
12334
|
+
});
|
|
12335
|
+
}
|
|
12336
|
+
seen.add(name);
|
|
12337
|
+
});
|
|
12338
|
+
};
|
|
12339
|
+
}
|
|
11257
12340
|
var BlueprintIssuerSchema = import_zod.z.object({
|
|
11258
12341
|
/** Short slug identifying this issuer within your account. Immutable once registered. */
|
|
11259
12342
|
issuerId: import_zod.z.string().regex(ISSUER_ID_RE, {
|
|
@@ -11308,7 +12391,36 @@ var BlueprintIssuerSchema = import_zod.z.object({
|
|
|
11308
12391
|
message: "roleId must be 3-31 chars, start with a lowercase letter, then lowercase letters/digits/dashes (e.g. 'practitioner-member')"
|
|
11309
12392
|
})
|
|
11310
12393
|
}).strict()
|
|
11311
|
-
).optional()
|
|
12394
|
+
).optional(),
|
|
12395
|
+
/**
|
|
12396
|
+
* Opt-in ADDITIONAL identity-claim capture: OIDC claim names — beyond `emailClaim`, which keeps
|
|
12397
|
+
* its own dedicated field — read from this issuer's tokens on every successful token exchange
|
|
12398
|
+
* and stored as your tenant's golden IdP-asserted identity copy for the signed-in user.
|
|
12399
|
+
*
|
|
12400
|
+
* Deliberately NOT a fixed set: name whatever claims your IdP actually asserts (standard ones
|
|
12401
|
+
* like `name`/`phone_number`, or your provider's own custom claims). Each name is read from the
|
|
12402
|
+
* verified token first, falling back to `userinfoUri` (when configured) only for names still
|
|
12403
|
+
* missing after that — so a claim your IdP puts only on the userinfo response still resolves,
|
|
12404
|
+
* provided you configured that endpoint.
|
|
12405
|
+
*
|
|
12406
|
+
* Omit entirely to capture nothing beyond email (the default, and the behaviour of every
|
|
12407
|
+
* registration authored before this field existed). Capped at 20 entries, each a non-blank name
|
|
12408
|
+
* of at most 64 characters; a duplicate is REJECTED rather than silently deduplicated, because a
|
|
12409
|
+
* repeated name is a sign the declaration does not mean what its author thinks.
|
|
12410
|
+
*
|
|
12411
|
+
* Pairs with the top-level `identityProjectionClaims`, which decides which of these captured
|
|
12412
|
+
* names are then projected onto access profiles in this context — capture and projection are
|
|
12413
|
+
* separate opt-ins, and capturing a claim does not by itself expose it on a profile.
|
|
12414
|
+
*/
|
|
12415
|
+
capturedClaims: import_zod.z.array(
|
|
12416
|
+
import_zod.z.string().max(MAX_CAPTURED_CLAIM_NAME_LENGTH, {
|
|
12417
|
+
message: `a capturedClaims entry must be at most ${MAX_CAPTURED_CLAIM_NAME_LENGTH} characters`
|
|
12418
|
+
}).refine((name) => name.trim().length > 0, {
|
|
12419
|
+
message: "a capturedClaims entry must be a non-blank OIDC claim name"
|
|
12420
|
+
})
|
|
12421
|
+
).max(MAX_CAPTURED_CLAIMS, {
|
|
12422
|
+
message: `capturedClaims accepts at most ${MAX_CAPTURED_CLAIMS} entries`
|
|
12423
|
+
}).optional().superRefine(assertNoDuplicateClaimNames("capturedClaims"))
|
|
11312
12424
|
}).strict();
|
|
11313
12425
|
var BlueprintNamespaceSchema = import_zod.z.object({
|
|
11314
12426
|
/** The namespace to register — same grammar as a `scope:<namespace>` key. */
|
|
@@ -11513,21 +12625,35 @@ var BlueprintSeedRecordSchema = import_zod.z.discriminatedUnion("surface", [
|
|
|
11513
12625
|
RecordSeedSchema,
|
|
11514
12626
|
DocumentSeedSchema
|
|
11515
12627
|
]);
|
|
11516
|
-
var SELF_TOKEN_RE = /\$\{\{\s*(?:self|under|member)\.[^}]*?\s*\}\}/;
|
|
12628
|
+
var SELF_TOKEN_RE = /\$\{\{\s*(?:self|under|member|input)\.[^}]*?\s*\}\}/;
|
|
12629
|
+
var IDENTITY_DERIVED_TOKEN_RE = /\$\{\{\s*(?:self|under|member)\.[^}]*?\s*\}\}/;
|
|
12630
|
+
var INPUT_TOKEN_RE = /\$\{\{\s*input\.[^}]*?\s*\}\}/;
|
|
11517
12631
|
function lintSelfTokenPlacement(value, ctx) {
|
|
11518
|
-
const walk = (node, path4,
|
|
12632
|
+
const walk = (node, path4, zone) => {
|
|
11519
12633
|
if (typeof node === "string") {
|
|
11520
|
-
if (
|
|
12634
|
+
if (zone === "none" && SELF_TOKEN_RE.test(node)) {
|
|
12635
|
+
ctx.addIssue({
|
|
12636
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
12637
|
+
path: path4,
|
|
12638
|
+
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)"
|
|
12639
|
+
});
|
|
12640
|
+
} else if (zone === "trigger" && IDENTITY_DERIVED_TOKEN_RE.test(node)) {
|
|
11521
12641
|
ctx.addIssue({
|
|
11522
12642
|
code: import_zod.z.ZodIssueCode.custom,
|
|
11523
12643
|
path: path4,
|
|
11524
|
-
message: "'${{ self.* }}' / '${{ member.* }}'
|
|
12644
|
+
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."
|
|
12645
|
+
});
|
|
12646
|
+
} else if (zone === "self-family" && INPUT_TOKEN_RE.test(node)) {
|
|
12647
|
+
ctx.addIssue({
|
|
12648
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
12649
|
+
path: path4,
|
|
12650
|
+
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."
|
|
11525
12651
|
});
|
|
11526
12652
|
}
|
|
11527
12653
|
return;
|
|
11528
12654
|
}
|
|
11529
12655
|
if (Array.isArray(node)) {
|
|
11530
|
-
node.forEach((v, i) => walk(v, [...path4, i],
|
|
12656
|
+
node.forEach((v, i) => walk(v, [...path4, i], zone));
|
|
11531
12657
|
return;
|
|
11532
12658
|
}
|
|
11533
12659
|
if (node && typeof node === "object") {
|
|
@@ -11535,12 +12661,13 @@ function lintSelfTokenPlacement(value, ctx) {
|
|
|
11535
12661
|
const inRoleDataScopeClause = path4.length === 3 && path4[0] === "roles" && k === "dataScope";
|
|
11536
12662
|
const inRoleAssumable = path4.length === 0 && k === "roleAssumable";
|
|
11537
12663
|
const inFragments = path4.length === 0 && k === "fragments";
|
|
11538
|
-
const
|
|
11539
|
-
|
|
12664
|
+
const inTriggerDataScope = path4.length === 2 && path4[0] === "triggers" && k === "dataScope";
|
|
12665
|
+
const nextZone = zone === "trigger" || inTriggerDataScope ? "trigger" : zone === "self-family" || inRoleDataScopeClause || inRoleAssumable || inFragments ? "self-family" : "none";
|
|
12666
|
+
walk(v, [...path4, k], nextZone);
|
|
11540
12667
|
}
|
|
11541
12668
|
}
|
|
11542
12669
|
};
|
|
11543
|
-
walk(value, [],
|
|
12670
|
+
walk(value, [], "none");
|
|
11544
12671
|
}
|
|
11545
12672
|
var IDENTITY_REF_RE = /\$\{\{\s*identities\.([A-Za-z_]\w*)\s*\}\}/g;
|
|
11546
12673
|
var IDENTITY_REF_LOOSE_RE = /\$\{\{\s*identities\.([^}]*?)\s*\}\}/g;
|
|
@@ -11694,6 +12821,44 @@ function lintDataScopeKey(key, path4, ctx) {
|
|
|
11694
12821
|
});
|
|
11695
12822
|
}
|
|
11696
12823
|
}
|
|
12824
|
+
var RECORD_RESERVED_FILTERABLE_FIELD_IDS = /* @__PURE__ */ new Set([
|
|
12825
|
+
"tenantId",
|
|
12826
|
+
"owner_id",
|
|
12827
|
+
"folderId",
|
|
12828
|
+
"rootFolderId",
|
|
12829
|
+
"recordType"
|
|
12830
|
+
]);
|
|
12831
|
+
var DOCUMENT_RESERVED_FILTERABLE_FIELD_IDS = /* @__PURE__ */ new Set([
|
|
12832
|
+
"tenantId",
|
|
12833
|
+
"model_type",
|
|
12834
|
+
"owner_id",
|
|
12835
|
+
"title",
|
|
12836
|
+
"folderId",
|
|
12837
|
+
"rootFolderId",
|
|
12838
|
+
"recordType"
|
|
12839
|
+
]);
|
|
12840
|
+
function lintReservedFilterableFieldIds(value, ctx) {
|
|
12841
|
+
const bp = value;
|
|
12842
|
+
bp.schemas?.forEach((schema, si) => {
|
|
12843
|
+
const surfaces = schema.allowedSurfaces ?? ["record"];
|
|
12844
|
+
schema.fields?.forEach((field, fi) => {
|
|
12845
|
+
if (!field?.filterable || !field.fieldId) return;
|
|
12846
|
+
const collidesWith = /* @__PURE__ */ new Set();
|
|
12847
|
+
if (surfaces.includes("record") && RECORD_RESERVED_FILTERABLE_FIELD_IDS.has(field.fieldId)) {
|
|
12848
|
+
for (const k of RECORD_RESERVED_FILTERABLE_FIELD_IDS) collidesWith.add(k);
|
|
12849
|
+
}
|
|
12850
|
+
if (surfaces.includes("document") && DOCUMENT_RESERVED_FILTERABLE_FIELD_IDS.has(field.fieldId)) {
|
|
12851
|
+
for (const k of DOCUMENT_RESERVED_FILTERABLE_FIELD_IDS) collidesWith.add(k);
|
|
12852
|
+
}
|
|
12853
|
+
if (collidesWith.size === 0) return;
|
|
12854
|
+
ctx.addIssue({
|
|
12855
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
12856
|
+
path: ["schemas", si, "fields", fi, "filterable"],
|
|
12857
|
+
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.`
|
|
12858
|
+
});
|
|
12859
|
+
});
|
|
12860
|
+
});
|
|
12861
|
+
}
|
|
11697
12862
|
function lintDataScopeKeys(value, ctx) {
|
|
11698
12863
|
const bp = value;
|
|
11699
12864
|
for (const key of Object.keys(bp.accessProfile?.dataScope ?? {})) {
|
|
@@ -11711,6 +12876,11 @@ function lintDataScopeKeys(value, ctx) {
|
|
|
11711
12876
|
lintDataScopeKey(key, ["fragments", name, key], ctx);
|
|
11712
12877
|
}
|
|
11713
12878
|
}
|
|
12879
|
+
for (const [name, trigger] of Object.entries(bp.triggers ?? {})) {
|
|
12880
|
+
for (const key of Object.keys(trigger.dataScope ?? {})) {
|
|
12881
|
+
lintDataScopeKey(key, ["triggers", name, "dataScope", key], ctx);
|
|
12882
|
+
}
|
|
12883
|
+
}
|
|
11714
12884
|
}
|
|
11715
12885
|
function lintExpectedScopeDims(value, ctx) {
|
|
11716
12886
|
const bp = value;
|
|
@@ -11806,6 +12976,150 @@ function lintAccessProfileRoleIds(value, ctx) {
|
|
|
11806
12976
|
seen.add(roleId);
|
|
11807
12977
|
});
|
|
11808
12978
|
}
|
|
12979
|
+
function lintTriggerFields(value, ctx) {
|
|
12980
|
+
const bp = value;
|
|
12981
|
+
for (const [triggerName, entry] of Object.entries(bp.triggers ?? {})) {
|
|
12982
|
+
const declared = entry?.fields;
|
|
12983
|
+
if (!declared?.length) continue;
|
|
12984
|
+
const schema = bp.schemas?.find((sc) => sc.typeName === entry.firingSource?.schemaName);
|
|
12985
|
+
if (!schema) continue;
|
|
12986
|
+
const lookupNames = /* @__PURE__ */ new Set();
|
|
12987
|
+
for (const lf of schema.lookupFields ?? []) {
|
|
12988
|
+
if (typeof lf === "string") {
|
|
12989
|
+
lookupNames.add(lf);
|
|
12990
|
+
continue;
|
|
12991
|
+
}
|
|
12992
|
+
if (lf?.fieldName) lookupNames.add(lf.fieldName);
|
|
12993
|
+
for (const n of lf?.fieldNames ?? []) lookupNames.add(n);
|
|
12994
|
+
if (lf?.sortBy && lf.sortBy !== "createdAt" && lf.sortBy !== "lastUpdated") {
|
|
12995
|
+
lookupNames.add(lf.sortBy);
|
|
12996
|
+
}
|
|
12997
|
+
}
|
|
12998
|
+
const seenFields = /* @__PURE__ */ new Set();
|
|
12999
|
+
declared.forEach((fieldId, i) => {
|
|
13000
|
+
const def = schema.fields?.find((f) => f.fieldId === fieldId);
|
|
13001
|
+
const path4 = ["triggers", triggerName, "fields", i];
|
|
13002
|
+
if (seenFields.has(fieldId)) {
|
|
13003
|
+
ctx.addIssue({
|
|
13004
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
13005
|
+
path: path4,
|
|
13006
|
+
message: `triggers['${triggerName}'].fields names '${fieldId}' more than once \u2014 list it once`
|
|
13007
|
+
});
|
|
13008
|
+
return;
|
|
13009
|
+
}
|
|
13010
|
+
seenFields.add(fieldId);
|
|
13011
|
+
if (def?.sensitive === true) {
|
|
13012
|
+
ctx.addIssue({
|
|
13013
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
13014
|
+
path: path4,
|
|
13015
|
+
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`
|
|
13016
|
+
});
|
|
13017
|
+
return;
|
|
13018
|
+
}
|
|
13019
|
+
if (lookupNames.has(fieldId)) return;
|
|
13020
|
+
if (!def) {
|
|
13021
|
+
ctx.addIssue({
|
|
13022
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
13023
|
+
path: path4,
|
|
13024
|
+
message: `triggers['${triggerName}'].fields names '${fieldId}', which schema '${schema.typeName}' neither declares nor uses as a lookup`
|
|
13025
|
+
});
|
|
13026
|
+
return;
|
|
13027
|
+
}
|
|
13028
|
+
if (def.inline !== true && def.filterable !== true) {
|
|
13029
|
+
ctx.addIssue({
|
|
13030
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
13031
|
+
path: path4,
|
|
13032
|
+
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'`
|
|
13033
|
+
});
|
|
13034
|
+
}
|
|
13035
|
+
});
|
|
13036
|
+
}
|
|
13037
|
+
}
|
|
13038
|
+
function lintScripts(value, ctx) {
|
|
13039
|
+
const bp = value;
|
|
13040
|
+
const scripts = bp.scripts;
|
|
13041
|
+
if (!scripts) return;
|
|
13042
|
+
for (const name of Object.keys(scripts)) {
|
|
13043
|
+
if (!SCRIPT_NAME_RE.test(name)) {
|
|
13044
|
+
ctx.addIssue({
|
|
13045
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
13046
|
+
path: ["scripts", name],
|
|
13047
|
+
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.`
|
|
13048
|
+
});
|
|
13049
|
+
}
|
|
13050
|
+
}
|
|
13051
|
+
for (const [triggerName, trigger] of Object.entries(bp.triggers ?? {})) {
|
|
13052
|
+
const ref = trigger?.scriptRef;
|
|
13053
|
+
if (!ref?.name) continue;
|
|
13054
|
+
if (!Object.prototype.hasOwnProperty.call(scripts, ref.name)) continue;
|
|
13055
|
+
if (ref.version !== "latest") {
|
|
13056
|
+
ctx.addIssue({
|
|
13057
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
13058
|
+
path: ["triggers", triggerName, "scriptRef", "version"],
|
|
13059
|
+
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.`
|
|
13060
|
+
});
|
|
13061
|
+
}
|
|
13062
|
+
}
|
|
13063
|
+
}
|
|
13064
|
+
function lintTriggers(value, ctx) {
|
|
13065
|
+
const bp = value;
|
|
13066
|
+
const triggers = bp.triggers;
|
|
13067
|
+
if (!triggers) return;
|
|
13068
|
+
const declaredSchemas = new Set((bp.schemas ?? []).map((s) => s.typeName).filter(Boolean));
|
|
13069
|
+
const triggerEnabledSchemas = new Set(
|
|
13070
|
+
(bp.schemas ?? []).filter((s) => s.capabilities?.triggersEnabled === true).map((s) => s.typeName).filter(Boolean)
|
|
13071
|
+
);
|
|
13072
|
+
const declaredRoles = new Set(Object.keys(bp.roles ?? {}));
|
|
13073
|
+
for (const [name, trigger] of Object.entries(triggers)) {
|
|
13074
|
+
const schemaName = trigger.firingSource?.schemaName;
|
|
13075
|
+
if (schemaName && !declaredSchemas.has(schemaName)) {
|
|
13076
|
+
ctx.addIssue({
|
|
13077
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
13078
|
+
path: ["triggers", name, "firingSource", "schemaName"],
|
|
13079
|
+
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)")
|
|
13080
|
+
});
|
|
13081
|
+
} else if (schemaName && !triggerEnabledSchemas.has(schemaName)) {
|
|
13082
|
+
ctx.addIssue({
|
|
13083
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
13084
|
+
path: ["triggers", name, "firingSource", "schemaName"],
|
|
13085
|
+
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.`
|
|
13086
|
+
});
|
|
13087
|
+
}
|
|
13088
|
+
const roleIds = trigger.roleIds;
|
|
13089
|
+
if (!roleIds) continue;
|
|
13090
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13091
|
+
roleIds.forEach((roleId, i) => {
|
|
13092
|
+
const path4 = ["triggers", name, "roleIds", i];
|
|
13093
|
+
if (!declaredRoles.has(roleId)) {
|
|
13094
|
+
ctx.addIssue({
|
|
13095
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
13096
|
+
path: path4,
|
|
13097
|
+
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)")
|
|
13098
|
+
});
|
|
13099
|
+
}
|
|
13100
|
+
if (seen.has(roleId)) {
|
|
13101
|
+
ctx.addIssue({
|
|
13102
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
13103
|
+
path: path4,
|
|
13104
|
+
message: `triggers['${name}'].roleIds names '${roleId}' more than once \u2014 remove the duplicate`
|
|
13105
|
+
});
|
|
13106
|
+
}
|
|
13107
|
+
seen.add(roleId);
|
|
13108
|
+
for (const clause of bp.roles?.[roleId] ?? []) {
|
|
13109
|
+
for (const [dim, raw] of Object.entries(clause?.dataScope ?? {})) {
|
|
13110
|
+
for (const v of Array.isArray(raw) ? raw : [raw]) {
|
|
13111
|
+
if (typeof v !== "string" || !IDENTITY_DERIVED_TOKEN_RE.test(v)) continue;
|
|
13112
|
+
ctx.addIssue({
|
|
13113
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
13114
|
+
path: path4,
|
|
13115
|
+
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.`
|
|
13116
|
+
});
|
|
13117
|
+
}
|
|
13118
|
+
}
|
|
13119
|
+
}
|
|
13120
|
+
});
|
|
13121
|
+
}
|
|
13122
|
+
}
|
|
11809
13123
|
function lintRoleAssumable(value, ctx) {
|
|
11810
13124
|
const bp = value;
|
|
11811
13125
|
const declaredRoles = new Set(Object.keys(bp.roles ?? {}));
|
|
@@ -11920,7 +13234,21 @@ var BLUEPRINT_FIELD_PHASES = Object.freeze({
|
|
|
11920
13234
|
schemas: "in-context",
|
|
11921
13235
|
accessProfile: "in-context",
|
|
11922
13236
|
roles: "in-context",
|
|
11923
|
-
seed: "in-context"
|
|
13237
|
+
seed: "in-context",
|
|
13238
|
+
// Pushed on the same in-context token as the schemas/roles above, and — load-bearing — BEFORE
|
|
13239
|
+
// `triggers`, because a trigger's `scriptRef` is validated AT DECLARE TIME: a rule naming a script
|
|
13240
|
+
// with no versions in the context is refused with a 400. This map does not order the loader (a
|
|
13241
|
+
// plain object's key order is not a contract); the loader orders itself, and a test pins it.
|
|
13242
|
+
scripts: "in-context",
|
|
13243
|
+
// A trigger's principal + grant are provisioned exactly like the roles: block is (N service-
|
|
13244
|
+
// principal/AccessProfile pairs instead of one), so it applies in the identical in-context phase,
|
|
13245
|
+
// under the same re-minted context-confined token.
|
|
13246
|
+
triggers: "in-context",
|
|
13247
|
+
// Setting it requires the platform provisioning capability, which only the bootstrap credential
|
|
13248
|
+
// carries — the same reasoning as issuers/namespaces above. It is applied to the app context
|
|
13249
|
+
// itself, which the bootstrap phase already creates, so there is nothing for the in-context pass
|
|
13250
|
+
// to do with it.
|
|
13251
|
+
identityProjectionClaims: "bootstrap"
|
|
11924
13252
|
});
|
|
11925
13253
|
var BlueprintSchema = import_zod.z.object({
|
|
11926
13254
|
/** Stable blueprint id (the `--blueprint <name>` selector + idempotency key). */
|
|
@@ -11983,13 +13311,77 @@ var BlueprintSchema = import_zod.z.object({
|
|
|
11983
13311
|
* `allowedSurfaces: ['entity']` schema, both need the registration to
|
|
11984
13312
|
* already exist). `org`/`client` need no entry — they exist already.
|
|
11985
13313
|
*/
|
|
11986
|
-
namespaces: import_zod.z.array(BlueprintNamespaceSchema).optional()
|
|
13314
|
+
namespaces: import_zod.z.array(BlueprintNamespaceSchema).optional(),
|
|
13315
|
+
/**
|
|
13316
|
+
* Declares which of the golden IdP-asserted claim names captured by this tenant's issuers (see
|
|
13317
|
+
* `issuers[].capturedClaims`) are projected, read-only, onto access profiles in THIS context.
|
|
13318
|
+
* Applied to the app context in the BOOTSTRAP-token phase, alongside the context's own create.
|
|
13319
|
+
*
|
|
13320
|
+
* Capture and projection are deliberately separate opt-ins: capturing a claim stores it against
|
|
13321
|
+
* the user, projecting it exposes it on a profile in this context. Declaring a name here that no
|
|
13322
|
+
* issuer captures is not an error — it simply never fills.
|
|
13323
|
+
*
|
|
13324
|
+
* <p><b>Fills once, then freezes.</b> A profile's projection is written the first time a sign-in
|
|
13325
|
+
* for that principal can supply a value — usually at profile creation, but for an INVITED member
|
|
13326
|
+
* not until they actually accept and sign in, since there is nothing to project before that.
|
|
13327
|
+
* Once filled it never updates again, even if this declaration or the underlying identity data
|
|
13328
|
+
* changes later. So editing this list affects only profiles that have not been filled yet; it is
|
|
13329
|
+
* not a backfill, and re-applying a blueprint does not re-project existing profiles.
|
|
13330
|
+
*
|
|
13331
|
+
* <p><b>Authority.</b> Setting this needs the platform provisioning capability — the same
|
|
13332
|
+
* authority the bootstrap credential already uses to register issuers and declare namespaces.
|
|
13333
|
+
* An ordinary `app-contexts:u` credential may rename its own context but may not opt it into
|
|
13334
|
+
* projecting IdP-golden identity data, so this field is only settable on the bootstrap phase's
|
|
13335
|
+
* own token, never on the per-context wildcard the in-context load runs under.
|
|
13336
|
+
*
|
|
13337
|
+
* <p>Omit to leave the context's existing declaration unchanged; declare an empty list to
|
|
13338
|
+
* disable future projection. Capped at 20 entries, each a non-blank name of at most 64
|
|
13339
|
+
* characters, duplicates rejected — the same bounds `capturedClaims` carries.
|
|
13340
|
+
*
|
|
13341
|
+
* <p><b>Applies only when this blueprint CREATES the app context.</b> The condition is the
|
|
13342
|
+
* context being created, not the apply being the first one: creating the context separately
|
|
13343
|
+
* beforehand means even the first apply does not set it. Re-applying against a context that
|
|
13344
|
+
* already exists does not change it, in either direction. Edit the declaration on an existing context through the platform API instead. (A
|
|
13345
|
+
* tool applying this blueprint could in principle reconcile it, but doing so also overwrites the
|
|
13346
|
+
* context's name and needs a broader scope — see `@vectros-ai/cli`'s own notes.)
|
|
13347
|
+
*/
|
|
13348
|
+
identityProjectionClaims: import_zod.z.array(
|
|
13349
|
+
import_zod.z.string().max(MAX_CAPTURED_CLAIM_NAME_LENGTH, {
|
|
13350
|
+
message: `an identityProjectionClaims entry must be at most ${MAX_CAPTURED_CLAIM_NAME_LENGTH} characters`
|
|
13351
|
+
}).refine((name) => name.trim().length > 0, {
|
|
13352
|
+
message: "an identityProjectionClaims entry must be a non-blank claim name"
|
|
13353
|
+
})
|
|
13354
|
+
).max(MAX_CAPTURED_CLAIMS, {
|
|
13355
|
+
message: `identityProjectionClaims accepts at most ${MAX_CAPTURED_CLAIMS} entries`
|
|
13356
|
+
}).optional().superRefine(assertNoDuplicateClaimNames("identityProjectionClaims")),
|
|
13357
|
+
/**
|
|
13358
|
+
* Optional scripts this blueprint SHIPS: scriptName -> `{ source, declaredInputContract? }`.
|
|
13359
|
+
*
|
|
13360
|
+
* Distinct from a trigger's `scriptRef`, which only NAMES a script and requires it to already
|
|
13361
|
+
* exist. Pushed BEFORE the `triggers` block, because a `scriptRef` is validated at declare time.
|
|
13362
|
+
*
|
|
13363
|
+
* Each entry is the CURRENT SOURCE for that name, not a version — the platform assigns versions
|
|
13364
|
+
* itself and they are immutable. Re-applying an unchanged blueprint pushes nothing; editing
|
|
13365
|
+
* `source` pushes a new version, which every `version: 'latest'` rule then picks up.
|
|
13366
|
+
*/
|
|
13367
|
+
scripts: BlueprintScriptsSchema.optional(),
|
|
13368
|
+
/**
|
|
13369
|
+
* Optional async trigger rules: triggerName -> declaration.
|
|
13370
|
+
*
|
|
13371
|
+
* These FIRE. A record write on a schema that opts in via `capabilities.triggersEnabled`
|
|
13372
|
+
* dispatches the rule's script, which runs asynchronously in a sandbox under the grant declared
|
|
13373
|
+
* here — so a trigger's `allowedActions`/`dataScope`/`capabilities` (or the roles its `roleIds`
|
|
13374
|
+
* composes) are live authority, not a declaration awaiting a future release. Author the grant as
|
|
13375
|
+
* narrowly as you would any other credential's.
|
|
13376
|
+
*/
|
|
13377
|
+
triggers: BlueprintTriggersSchema.optional()
|
|
11987
13378
|
}).strict().superRefine((bp, ctx) => {
|
|
11988
13379
|
lintSelfTokenPlacement(bp, ctx);
|
|
11989
13380
|
lintIdentityRefs(bp, ctx);
|
|
11990
13381
|
lintIdentityOverrides(bp, ctx);
|
|
11991
13382
|
lintNamespacedScopeArrays(bp, ctx);
|
|
11992
13383
|
lintDataScopeKeys(bp, ctx);
|
|
13384
|
+
lintReservedFilterableFieldIds(bp, ctx);
|
|
11993
13385
|
lintExpectedScopeDims(bp, ctx);
|
|
11994
13386
|
lintDataScopeRefs(bp, ctx);
|
|
11995
13387
|
lintRoleAssumable(bp, ctx);
|
|
@@ -11998,6 +13390,9 @@ var BlueprintSchema = import_zod.z.object({
|
|
|
11998
13390
|
lintIdentitySurfaces(bp, ctx);
|
|
11999
13391
|
lintIssuerContext(bp, ctx);
|
|
12000
13392
|
lintNamespaces(bp, ctx);
|
|
13393
|
+
lintTriggers(bp, ctx);
|
|
13394
|
+
lintTriggerFields(bp, ctx);
|
|
13395
|
+
lintScripts(bp, ctx);
|
|
12001
13396
|
});
|
|
12002
13397
|
function formatIssuePath(path4) {
|
|
12003
13398
|
let out = "";
|
|
@@ -14082,6 +15477,13 @@ function renderActionableError(info) {
|
|
|
14082
15477
|
return ` \u2716 ${msg}
|
|
14083
15478
|
Your credential lacks the scope for this operation (owner-gated, or a
|
|
14084
15479
|
narrower context/action than required).
|
|
15480
|
+
`;
|
|
15481
|
+
}
|
|
15482
|
+
if (info.statusCode === 409 && /no scoped key can be minted|is not active|is suspended/i.test(msg)) {
|
|
15483
|
+
return ` \u2716 ${msg}
|
|
15484
|
+
This is NOT a scope problem: no credential, including a root key, can mint against an
|
|
15485
|
+
inactive profile or a suspended user, so re-issuing a broader credential will not help.
|
|
15486
|
+
Follow the reactivation step named above, then retry.
|
|
14085
15487
|
`;
|
|
14086
15488
|
}
|
|
14087
15489
|
if (info.statusCode === 404) {
|
|
@@ -14248,7 +15650,10 @@ async function mintBridgeToken(opts) {
|
|
|
14248
15650
|
let url = `${base}${path4}?ttl=${encodeURIComponent(String(ttlSeconds))}&tenant=${encodeURIComponent(tenant)}`;
|
|
14249
15651
|
if (contextId) url += `&context=${encodeURIComponent(contextId)}`;
|
|
14250
15652
|
return fetchStToken(url, cognitoToken, fetchImpl, path4, (status) => {
|
|
14251
|
-
if (status !== 404
|
|
15653
|
+
if (status !== 404) return "";
|
|
15654
|
+
if (useScopedToken || contextId) {
|
|
15655
|
+
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)";
|
|
15656
|
+
}
|
|
14252
15657
|
return ` (the dedicated ${CLI_BOOTSTRAP_TOKEN_PATH} endpoint may not be deployed yet \u2014 retry with --use-scoped-token for the fallback path)`;
|
|
14253
15658
|
});
|
|
14254
15659
|
}
|
|
@@ -14263,7 +15668,10 @@ async function mintContextToken(opts) {
|
|
|
14263
15668
|
} = opts;
|
|
14264
15669
|
const base = apiBaseUrl.replace(/\/$/, "");
|
|
14265
15670
|
const url = `${base}${SCOPED_TOKEN_PATH}?ttl=${encodeURIComponent(String(ttlSeconds))}&tenant=${encodeURIComponent(tenant)}&context=${encodeURIComponent(contextId)}`;
|
|
14266
|
-
return fetchStToken(url, cognitoToken, fetchImpl, `${SCOPED_TOKEN_PATH}?context=${contextId}
|
|
15671
|
+
return fetchStToken(url, cognitoToken, fetchImpl, `${SCOPED_TOKEN_PATH}?context=${contextId}`, (status) => {
|
|
15672
|
+
if (status !== 404) return "";
|
|
15673
|
+
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)";
|
|
15674
|
+
});
|
|
14267
15675
|
}
|
|
14268
15676
|
|
|
14269
15677
|
// src/identity.ts
|
|
@@ -14650,7 +16058,21 @@ var KNOWN_CONTROL_PLANE_RESOURCES = [
|
|
|
14650
16058
|
// unconditionally, never tenant-wide, so the reach is bounded to what the
|
|
14651
16059
|
// blueprint already provisions, never the pack's say-so alone. See
|
|
14652
16060
|
// `@vectros-ai/blueprints`' `namespaces[]` doc for the full design.
|
|
14653
|
-
"namespaces"
|
|
16061
|
+
"namespaces",
|
|
16062
|
+
// The 0.43.0 script/trigger surfaces. Listed for MESSAGE QUALITY only — both were already
|
|
16063
|
+
// rejected by the data-plane allowlist above, but as "unrecognized resource", whose text invites
|
|
16064
|
+
// the reader to widen the allowlist ("if 'scripts' is a new data-plane resource…"). They are not:
|
|
16065
|
+
// the platform deliberately put them out of reach of an ordinary confined partner role (a bare,
|
|
16066
|
+
// non-empty `data_scope` clause — the routine blueprint-issued shape — does not reach scripts at
|
|
16067
|
+
// all), because script authoring is a design-time, admin-shaped surface. So the accurate message
|
|
16068
|
+
// is the control-plane one.
|
|
16069
|
+
//
|
|
16070
|
+
// NOTE on the `x` action letter: `scripts:x` (execute) is a real platform verb, and VALID_OPS
|
|
16071
|
+
// deliberately does NOT gain it. Op letters are only checked AFTER a resource is admitted, and
|
|
16072
|
+
// no admitted resource has an `x` verb — adding it would instead make `records:x` pass the op
|
|
16073
|
+
// check and be admitted as data-plane.
|
|
16074
|
+
"scripts",
|
|
16075
|
+
"triggers"
|
|
14654
16076
|
];
|
|
14655
16077
|
var PARTIALLY_ADMITTED_OPS = /* @__PURE__ */ new Map([
|
|
14656
16078
|
["profiles", /* @__PURE__ */ new Set(["r", "u", "d"])]
|
|
@@ -14658,6 +16080,13 @@ var PARTIALLY_ADMITTED_OPS = /* @__PURE__ */ new Map([
|
|
|
14658
16080
|
var SELF_QUALIFIED_OPS = /* @__PURE__ */ new Map([
|
|
14659
16081
|
["profiles", /* @__PURE__ */ new Set(["c"])]
|
|
14660
16082
|
]);
|
|
16083
|
+
var NAME_QUALIFIED_OPS = /* @__PURE__ */ new Map([
|
|
16084
|
+
["scripts", /* @__PURE__ */ new Set(["x"])]
|
|
16085
|
+
]);
|
|
16086
|
+
var RESOURCE_EXTRA_OPS = /* @__PURE__ */ new Map([
|
|
16087
|
+
["scripts", /* @__PURE__ */ new Set(["x"])]
|
|
16088
|
+
]);
|
|
16089
|
+
var PLATFORM_SCRIPT_NAME = /^[A-Za-z0-9_-]{1,64}$/;
|
|
14661
16090
|
var UNQUALIFIABLE_WITH_ANY_TAIL = /* @__PURE__ */ new Map([
|
|
14662
16091
|
["profiles", /* @__PURE__ */ new Set(["r"])]
|
|
14663
16092
|
]);
|
|
@@ -14668,10 +16097,13 @@ var VALID_OPS = /* @__PURE__ */ new Set(["c", "r", "u", "d"]);
|
|
|
14668
16097
|
var DATA_PLANE_SET = new Set(DATA_PLANE_ALLOWLIST);
|
|
14669
16098
|
var CONTROL_PLANE_SET = new Set(KNOWN_CONTROL_PLANE_RESOURCES);
|
|
14670
16099
|
var ALLOWLIST_HINT = DATA_PLANE_ALLOWLIST.join("/");
|
|
16100
|
+
function formatRejection(r) {
|
|
16101
|
+
return r.source ? `${r.action} (${r.source}): ${r.reason}` : `${r.action}: ${r.reason}`;
|
|
16102
|
+
}
|
|
14671
16103
|
var ScopeGateError = class extends Error {
|
|
14672
16104
|
rejected;
|
|
14673
16105
|
constructor(rejected) {
|
|
14674
|
-
const summary = rejected.map((r) => ` \u2022 ${r
|
|
16106
|
+
const summary = rejected.map((r) => ` \u2022 ${formatRejection(r)}`).join("\n");
|
|
14675
16107
|
super(
|
|
14676
16108
|
`Scope gate rejected ${rejected.length} requested scope(s); the bootstrap loader provisions data-plane access only (${ALLOWLIST_HINT}) and mints nothing on reject:
|
|
14677
16109
|
${summary}`
|
|
@@ -14686,7 +16118,7 @@ var CAPABILITIES_HINT = GRANTED_CAPABILITIES_ALLOWLIST.join("/");
|
|
|
14686
16118
|
var CapabilityGateError = class extends Error {
|
|
14687
16119
|
rejected;
|
|
14688
16120
|
constructor(rejected) {
|
|
14689
|
-
const summary = rejected.map((r) => ` \u2022 ${r
|
|
16121
|
+
const summary = rejected.map((r) => ` \u2022 ${formatRejection(r)}`).join("\n");
|
|
14690
16122
|
super(
|
|
14691
16123
|
`Capability gate rejected ${rejected.length} requested capability(ies); the bootstrap loader grants (${CAPABILITIES_HINT}) only and mints nothing on reject:
|
|
14692
16124
|
${summary}`
|
|
@@ -14727,9 +16159,10 @@ function assertCapabilityGate(capabilities) {
|
|
|
14727
16159
|
throw new CapabilityGateError(result.rejected);
|
|
14728
16160
|
}
|
|
14729
16161
|
}
|
|
14730
|
-
function firstInvalidOp(ops) {
|
|
16162
|
+
function firstInvalidOp(ops, resource) {
|
|
16163
|
+
const extra = resource === void 0 ? void 0 : RESOURCE_EXTRA_OPS.get(resource);
|
|
14731
16164
|
for (const ch of ops) {
|
|
14732
|
-
if (!VALID_OPS.has(ch)) return ch;
|
|
16165
|
+
if (!VALID_OPS.has(ch) && !extra?.has(ch)) return ch;
|
|
14733
16166
|
}
|
|
14734
16167
|
return void 0;
|
|
14735
16168
|
}
|
|
@@ -14756,19 +16189,24 @@ function evaluateScopeToken(action, capabilities) {
|
|
|
14756
16189
|
if (!DATA_PLANE_SET.has(resource)) {
|
|
14757
16190
|
const admittedOps = PARTIALLY_ADMITTED_OPS.get(resource);
|
|
14758
16191
|
const selfOnlyOps = SELF_QUALIFIED_OPS.get(resource);
|
|
14759
|
-
|
|
14760
|
-
|
|
16192
|
+
const nameOnlyOps = NAME_QUALIFIED_OPS.get(resource);
|
|
16193
|
+
if (admittedOps || selfOnlyOps || nameOnlyOps) {
|
|
16194
|
+
const admittedHint = [
|
|
16195
|
+
.../* @__PURE__ */ new Set([...admittedOps ?? [], ...selfOnlyOps ?? [], ...nameOnlyOps ?? []])
|
|
16196
|
+
].sort().join("/");
|
|
14761
16197
|
if (ops === "*") {
|
|
14762
16198
|
return {
|
|
14763
16199
|
action,
|
|
14764
16200
|
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`
|
|
14765
16201
|
};
|
|
14766
16202
|
}
|
|
14767
|
-
const invalidOp2 = firstInvalidOp(ops);
|
|
16203
|
+
const invalidOp2 = firstInvalidOp(ops, resource);
|
|
14768
16204
|
if (invalidOp2) {
|
|
16205
|
+
const extraOps = [...RESOURCE_EXTRA_OPS.get(resource) ?? []].sort();
|
|
16206
|
+
const validHint = ["c", "r", "u", "d", ...extraOps].join(", ");
|
|
14769
16207
|
return {
|
|
14770
16208
|
action,
|
|
14771
|
-
reason: `invalid operation character '${invalidOp2}' (valid operations are
|
|
16209
|
+
reason: `invalid operation character '${invalidOp2}' (valid operations are ${validHint}, or '*')`
|
|
14772
16210
|
};
|
|
14773
16211
|
}
|
|
14774
16212
|
const qualifier = parts.length > 2 ? parts.slice(2).join(":") : void 0;
|
|
@@ -14778,10 +16216,29 @@ function evaluateScopeToken(action, capabilities) {
|
|
|
14778
16216
|
if (qualifier !== void 0 && unqualifiableOps?.has(ch)) {
|
|
14779
16217
|
return {
|
|
14780
16218
|
action,
|
|
14781
|
-
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).`
|
|
16219
|
+
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).`
|
|
16220
|
+
};
|
|
16221
|
+
}
|
|
16222
|
+
if (admittedOps?.has(ch)) continue;
|
|
16223
|
+
if (nameOnlyOps?.has(ch)) {
|
|
16224
|
+
if (qualifier !== void 0 && qualifier.includes("*")) {
|
|
16225
|
+
return {
|
|
16226
|
+
action,
|
|
16227
|
+
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>'`
|
|
16228
|
+
};
|
|
16229
|
+
}
|
|
16230
|
+
if (qualifier !== void 0 && PLATFORM_SCRIPT_NAME.test(qualifier)) continue;
|
|
16231
|
+
if (qualifier !== void 0 && qualifier.length > 0) {
|
|
16232
|
+
return {
|
|
16233
|
+
action,
|
|
16234
|
+
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`
|
|
16235
|
+
};
|
|
16236
|
+
}
|
|
16237
|
+
return {
|
|
16238
|
+
action,
|
|
16239
|
+
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.`
|
|
14782
16240
|
};
|
|
14783
16241
|
}
|
|
14784
|
-
if (admittedOps?.has(ch)) continue;
|
|
14785
16242
|
if (selfOnlyOps?.has(ch)) {
|
|
14786
16243
|
if (isSelfQualified) continue;
|
|
14787
16244
|
const unlockCapability = CAPABILITY_UNLOCKED_OPS.get(resource)?.get(ch);
|
|
@@ -14852,6 +16309,50 @@ function assertBlueprintGates(blueprint) {
|
|
|
14852
16309
|
assertCapabilityGate(clause.capabilities);
|
|
14853
16310
|
}
|
|
14854
16311
|
}
|
|
16312
|
+
for (const [triggerName, entry] of Object.entries(blueprint.triggers ?? {})) {
|
|
16313
|
+
if (entry.allowedActions === void 0) continue;
|
|
16314
|
+
try {
|
|
16315
|
+
assertScopeGate(entry.allowedActions, entry.capabilities);
|
|
16316
|
+
assertCapabilityGate(entry.capabilities);
|
|
16317
|
+
} catch (err) {
|
|
16318
|
+
throw withTriggerContext(err, triggerName);
|
|
16319
|
+
}
|
|
16320
|
+
}
|
|
16321
|
+
assertScriptGrantsNameADeclaredScript(blueprint);
|
|
16322
|
+
}
|
|
16323
|
+
function assertScriptGrantsNameADeclaredScript(blueprint) {
|
|
16324
|
+
const declared = Object.keys(blueprint.scripts ?? {});
|
|
16325
|
+
if (declared.length === 0) return;
|
|
16326
|
+
const rejected = [];
|
|
16327
|
+
const check = (actions, source) => {
|
|
16328
|
+
for (const action of actions ?? []) {
|
|
16329
|
+
const parts = action.trim().split(":");
|
|
16330
|
+
if (parts[0] !== "scripts" || parts[1] !== "x") continue;
|
|
16331
|
+
const name = parts.length > 2 ? parts.slice(2).join(":") : void 0;
|
|
16332
|
+
if (name === void 0 || name.length === 0 || name.includes("*")) continue;
|
|
16333
|
+
if (!declared.includes(name)) {
|
|
16334
|
+
rejected.push({
|
|
16335
|
+
action,
|
|
16336
|
+
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`,
|
|
16337
|
+
...source ? { source } : {}
|
|
16338
|
+
});
|
|
16339
|
+
}
|
|
16340
|
+
}
|
|
16341
|
+
};
|
|
16342
|
+
check(blueprint.accessProfile.allowedActions);
|
|
16343
|
+
for (const [roleId, clauses] of Object.entries(blueprint.roles ?? {})) {
|
|
16344
|
+
for (const clause of clauses) check(clause.allowedActions, `role '${roleId}'`);
|
|
16345
|
+
}
|
|
16346
|
+
for (const [triggerName, entry] of Object.entries(blueprint.triggers ?? {})) {
|
|
16347
|
+
check(entry.allowedActions, `trigger '${triggerName}'`);
|
|
16348
|
+
}
|
|
16349
|
+
if (rejected.length > 0) throw new ScopeGateError(rejected);
|
|
16350
|
+
}
|
|
16351
|
+
function withTriggerContext(err, triggerName) {
|
|
16352
|
+
if (err instanceof ScopeGateError || err instanceof CapabilityGateError) {
|
|
16353
|
+
for (const r of err.rejected) r.source ??= `trigger '${triggerName}'`;
|
|
16354
|
+
}
|
|
16355
|
+
return err;
|
|
14855
16356
|
}
|
|
14856
16357
|
function effectiveAccessProfileActions(blueprint) {
|
|
14857
16358
|
if (blueprint.accessProfile.allowedActions !== void 0) {
|
|
@@ -14963,10 +16464,391 @@ async function resolveApplyTimeIdentities(blueprint, provisioned, bootstrapClien
|
|
|
14963
16464
|
const resolved = await resolveBlueprintIdentities(blueprint, resolver);
|
|
14964
16465
|
return parseBlueprint(resolved);
|
|
14965
16466
|
}
|
|
14966
|
-
|
|
14967
|
-
// src/
|
|
14968
|
-
|
|
14969
|
-
|
|
16467
|
+
|
|
16468
|
+
// src/scope-grammar.ts
|
|
16469
|
+
var FORBIDDEN_SCOPE_NAMESPACES2 = /* @__PURE__ */ new Set([
|
|
16470
|
+
"user",
|
|
16471
|
+
"record",
|
|
16472
|
+
"document",
|
|
16473
|
+
"entity",
|
|
16474
|
+
"self",
|
|
16475
|
+
"tenant",
|
|
16476
|
+
"context",
|
|
16477
|
+
"scope",
|
|
16478
|
+
"versions",
|
|
16479
|
+
"lookup"
|
|
16480
|
+
]);
|
|
16481
|
+
var SCOPE_NAMESPACE_RE2 = /^[a-z][a-z0-9_-]{1,31}$/;
|
|
16482
|
+
var SCOPE_NAMESPACE_MIN_LENGTH = 2;
|
|
16483
|
+
var SCOPE_NAMESPACE_MAX_LENGTH = 32;
|
|
16484
|
+
var SCOPE_VALUE_RE2 = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
|
|
16485
|
+
var SCOPE_VALUE_MIN_LENGTH = 1;
|
|
16486
|
+
var SCOPE_VALUE_MAX_LENGTH = 128;
|
|
16487
|
+
function valueGrammarError(value) {
|
|
16488
|
+
if (!SCOPE_VALUE_RE2.test(value)) {
|
|
16489
|
+
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.`;
|
|
16490
|
+
}
|
|
16491
|
+
return null;
|
|
16492
|
+
}
|
|
16493
|
+
function namespaceGrammarError(ns) {
|
|
16494
|
+
if (FORBIDDEN_SCOPE_NAMESPACES2.has(ns)) {
|
|
16495
|
+
return `'${ns}' is a reserved namespace and cannot be used as an ownership scope`;
|
|
16496
|
+
}
|
|
16497
|
+
if (!SCOPE_NAMESPACE_RE2.test(ns)) {
|
|
16498
|
+
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 '-'`;
|
|
16499
|
+
}
|
|
16500
|
+
return null;
|
|
16501
|
+
}
|
|
16502
|
+
function assertScopeEntries(entries, flag) {
|
|
16503
|
+
if (!entries?.length) return;
|
|
16504
|
+
for (const raw of entries) {
|
|
16505
|
+
const sep = raw.indexOf(":");
|
|
16506
|
+
if (sep <= 0 || sep === raw.length - 1) {
|
|
16507
|
+
throw new Error(`${flag} must be '<namespace>:<value>' (e.g. 'org:6ba7...'); got '${raw}'.`);
|
|
16508
|
+
}
|
|
16509
|
+
const nsError = namespaceGrammarError(raw.slice(0, sep));
|
|
16510
|
+
if (nsError) throw new Error(`${flag} namespace ${nsError}`);
|
|
16511
|
+
const valError = valueGrammarError(raw.slice(sep + 1));
|
|
16512
|
+
if (valError) throw new Error(`${flag} value ${valError}`);
|
|
16513
|
+
}
|
|
16514
|
+
}
|
|
16515
|
+
function assertIdentityOverrideValues(overrides, flag) {
|
|
16516
|
+
if (!overrides) return;
|
|
16517
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
16518
|
+
if (!key.startsWith("scope:")) continue;
|
|
16519
|
+
const error = valueGrammarError(typeof value === "string" ? value : String(value));
|
|
16520
|
+
if (error) throw new Error(`${flag} '${key}' value ${error}`);
|
|
16521
|
+
}
|
|
16522
|
+
}
|
|
16523
|
+
function placeholderScopeNamespace(value) {
|
|
16524
|
+
const self2 = /^\$\{\{\s*(?:under\.)?self\.scope\.([^\s}]+)\s*\}\}$/.exec(value);
|
|
16525
|
+
if (self2) return self2[1];
|
|
16526
|
+
const member = MEMBER_SCOPE_PLACEHOLDER_RE2.exec(value);
|
|
16527
|
+
return member ? member[1] : null;
|
|
16528
|
+
}
|
|
16529
|
+
var SELF_PLACEHOLDER_RE = /^\$\{\{\s*self\.(?:userId|partnerUserId|scope\.[a-z][a-z0-9_-]{1,31})\s*\}\}$/;
|
|
16530
|
+
var UNDER_SELF_PLACEHOLDER_RE2 = /^\$\{\{\s*under\.self\.(?:userId|partnerUserId|scope\.[a-z][a-z0-9_-]{1,31})\s*\}\}$/;
|
|
16531
|
+
var MEMBER_SCOPE_PLACEHOLDER_RE2 = /^\$\{\{\s*member\.scope\.([a-z][a-z0-9_-]{1,31})(?::[a-z][a-z0-9_-]{1,31})?\s*\}\}$/;
|
|
16532
|
+
var ROLE_ID_RE2 = /^[a-z][a-z0-9-]{2,30}$/;
|
|
16533
|
+
var MAX_ASSIGNABLE_ROLES2 = 20;
|
|
16534
|
+
function parseAssignableRoles(raw, flag = "--assignable-role") {
|
|
16535
|
+
if (!raw || raw.length === 0) return void 0;
|
|
16536
|
+
if (raw.length > MAX_ASSIGNABLE_ROLES2) {
|
|
16537
|
+
throw new Error(`${flag} accepts at most ${MAX_ASSIGNABLE_ROLES2} entries; got ${raw.length}.`);
|
|
16538
|
+
}
|
|
16539
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16540
|
+
for (const roleId of raw) {
|
|
16541
|
+
if (!ROLE_ID_RE2.test(roleId)) {
|
|
16542
|
+
throw new Error(
|
|
16543
|
+
`${flag} '${roleId}' is not a valid roleId \u2014 3-31 characters, starting with a lowercase letter and continuing with lowercase letters, digits or '-'.`
|
|
16544
|
+
);
|
|
16545
|
+
}
|
|
16546
|
+
if (seen.has(roleId)) {
|
|
16547
|
+
throw new Error(`${flag} repeats '${roleId}' \u2014 list it once.`);
|
|
16548
|
+
}
|
|
16549
|
+
seen.add(roleId);
|
|
16550
|
+
}
|
|
16551
|
+
return [...raw];
|
|
16552
|
+
}
|
|
16553
|
+
|
|
16554
|
+
// src/verb-support.ts
|
|
16555
|
+
async function withAuth(opts, deps, body) {
|
|
16556
|
+
const log = deps.log ?? createLogger2();
|
|
16557
|
+
const resolveAuth = deps.resolveAuth ?? resolveAuthContext;
|
|
16558
|
+
let auth;
|
|
16559
|
+
try {
|
|
16560
|
+
auth = await resolveAuth({
|
|
16561
|
+
env: opts.env,
|
|
16562
|
+
baseUrl: opts.baseUrl,
|
|
16563
|
+
token: opts.token,
|
|
16564
|
+
context: opts.context,
|
|
16565
|
+
provisioning: opts.provisioning,
|
|
16566
|
+
tenant: opts.tenant,
|
|
16567
|
+
useScopedToken: opts.useScopedToken,
|
|
16568
|
+
log
|
|
16569
|
+
});
|
|
16570
|
+
} catch (err) {
|
|
16571
|
+
if (err instanceof MissingCredentialError) {
|
|
16572
|
+
process.stderr.write(` \u2716 ${err.message}
|
|
16573
|
+
`);
|
|
16574
|
+
return 2;
|
|
16575
|
+
}
|
|
16576
|
+
process.stderr.write(` \u2716 ${formatError(err)}
|
|
16577
|
+
`);
|
|
16578
|
+
return 1;
|
|
16579
|
+
}
|
|
16580
|
+
return body(auth, log);
|
|
16581
|
+
}
|
|
16582
|
+
function requireTenantId(auth) {
|
|
16583
|
+
if (!auth.tenantId) {
|
|
16584
|
+
throw new Error(
|
|
16585
|
+
"Could not resolve tenantId from /v1/ping \u2014 the extended ping (tenant_id) must be deployed."
|
|
16586
|
+
);
|
|
16587
|
+
}
|
|
16588
|
+
return auth.tenantId;
|
|
16589
|
+
}
|
|
16590
|
+
function normalizePrincipalUserId(principal) {
|
|
16591
|
+
return principal.startsWith("usr_") ? principal.slice("usr_".length) : principal;
|
|
16592
|
+
}
|
|
16593
|
+
function splitCsv(csv) {
|
|
16594
|
+
return csv.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
16595
|
+
}
|
|
16596
|
+
function parseScopeOverrides(scopes) {
|
|
16597
|
+
if (!scopes || scopes.length === 0) return void 0;
|
|
16598
|
+
const out = {};
|
|
16599
|
+
for (const raw of scopes) {
|
|
16600
|
+
const sep = raw.indexOf(":");
|
|
16601
|
+
if (sep <= 0 || sep === raw.length - 1) {
|
|
16602
|
+
throw new Error(`--scope must be '<namespace>:<value>' (e.g. 'org:6ba7...'); got '${raw}'.`);
|
|
16603
|
+
}
|
|
16604
|
+
const ns = raw.slice(0, sep);
|
|
16605
|
+
const value = raw.slice(sep + 1);
|
|
16606
|
+
const valueError = valueGrammarError(value);
|
|
16607
|
+
if (valueError) throw new Error(`--scope value ${valueError}`);
|
|
16608
|
+
const key = `scope:${ns}`;
|
|
16609
|
+
if (Object.prototype.hasOwnProperty.call(out, key)) {
|
|
16610
|
+
throw new Error(`--scope sets namespace '${ns}' more than once \u2014 a scope override takes at most one value per namespace.`);
|
|
16611
|
+
}
|
|
16612
|
+
out[key] = value;
|
|
16613
|
+
}
|
|
16614
|
+
if (Object.keys(out).length > 2) {
|
|
16615
|
+
throw new Error("--scope allows at most two distinct namespaces.");
|
|
16616
|
+
}
|
|
16617
|
+
return out;
|
|
16618
|
+
}
|
|
16619
|
+
function parseJsonObjectFlag(flag, raw) {
|
|
16620
|
+
let parsed;
|
|
16621
|
+
try {
|
|
16622
|
+
parsed = JSON.parse(raw);
|
|
16623
|
+
} catch {
|
|
16624
|
+
throw new Error(`${flag} is not valid JSON: ${raw}`);
|
|
16625
|
+
}
|
|
16626
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
16627
|
+
throw new Error(`${flag} must be a JSON object (e.g. '{"userId":["<id>"]}').`);
|
|
16628
|
+
}
|
|
16629
|
+
return parsed;
|
|
16630
|
+
}
|
|
16631
|
+
|
|
16632
|
+
// src/paginate.ts
|
|
16633
|
+
var PAGE_LIMIT = 100;
|
|
16634
|
+
async function drainPages(fetch2) {
|
|
16635
|
+
const out = [];
|
|
16636
|
+
let startFrom;
|
|
16637
|
+
for (; ; ) {
|
|
16638
|
+
const page = await fetch2(startFrom);
|
|
16639
|
+
if (page.data) out.push(...page.data);
|
|
16640
|
+
const next = page.nextCursor;
|
|
16641
|
+
if (!next) break;
|
|
16642
|
+
startFrom = next;
|
|
16643
|
+
}
|
|
16644
|
+
return out;
|
|
16645
|
+
}
|
|
16646
|
+
|
|
16647
|
+
// src/loader.ts
|
|
16648
|
+
function actionOf(created) {
|
|
16649
|
+
return created ? "created" : "reconciled";
|
|
16650
|
+
}
|
|
16651
|
+
function triggerPrincipalExternalId(blueprintName, triggerName) {
|
|
16652
|
+
return `${blueprintName}-trigger-${triggerName}`;
|
|
16653
|
+
}
|
|
16654
|
+
var SCRIPT_CONFLICT_RETRY_DELAY_MS = 750;
|
|
16655
|
+
function isVersionConflict(err) {
|
|
16656
|
+
return typeof err === "object" && err !== null && err.statusCode === 409;
|
|
16657
|
+
}
|
|
16658
|
+
async function readLatestScriptVersion(ctxClient, name) {
|
|
16659
|
+
const versions = await drainPages(
|
|
16660
|
+
(startFrom) => ctxClient.scripts.listScripts({ name, startFrom, limit: PAGE_LIMIT })
|
|
16661
|
+
);
|
|
16662
|
+
return versions.reduce((best, row) => {
|
|
16663
|
+
if (row.scriptVersion === void 0) return best;
|
|
16664
|
+
if (best === void 0 || best.scriptVersion === void 0) return row;
|
|
16665
|
+
return row.scriptVersion > best.scriptVersion ? row : best;
|
|
16666
|
+
}, void 0);
|
|
16667
|
+
}
|
|
16668
|
+
function requireScriptId(row, name) {
|
|
16669
|
+
if (!row.id) {
|
|
16670
|
+
throw new Error(`Script '${name}' listing returned a version with no id.`);
|
|
16671
|
+
}
|
|
16672
|
+
return row.id;
|
|
16673
|
+
}
|
|
16674
|
+
function scriptMatches(stored, declared) {
|
|
16675
|
+
return stored.source === declared.source && (stored.declaredInputContract ?? void 0) === declared.declaredInputContract;
|
|
16676
|
+
}
|
|
16677
|
+
async function ensureScripts(args) {
|
|
16678
|
+
const { ctxClient, scripts, log } = args;
|
|
16679
|
+
const actions = [];
|
|
16680
|
+
for (const [name, entry] of Object.entries(scripts)) {
|
|
16681
|
+
let latest = await readLatestScriptVersion(ctxClient, name);
|
|
16682
|
+
if (latest !== void 0 && scriptMatches(latest, entry)) {
|
|
16683
|
+
actions.push({
|
|
16684
|
+
name,
|
|
16685
|
+
action: "unchanged",
|
|
16686
|
+
id: requireScriptId(latest, name),
|
|
16687
|
+
scriptVersion: latest.scriptVersion ?? 0
|
|
16688
|
+
});
|
|
16689
|
+
log.debug(
|
|
16690
|
+
{ component: "loader", scriptName: name, scriptVersion: latest.scriptVersion },
|
|
16691
|
+
"script already matches the declared source \u2014 no version pushed"
|
|
16692
|
+
);
|
|
16693
|
+
continue;
|
|
16694
|
+
}
|
|
16695
|
+
const body = { name, source: entry.source, declaredInputContract: entry.declaredInputContract };
|
|
16696
|
+
let pushed;
|
|
16697
|
+
try {
|
|
16698
|
+
pushed = await ctxClient.scripts.createScript(body);
|
|
16699
|
+
} catch (err) {
|
|
16700
|
+
if (!isVersionConflict(err)) throw err;
|
|
16701
|
+
log.debug(
|
|
16702
|
+
{ component: "loader", scriptName: name },
|
|
16703
|
+
"concurrent push detected (409) \u2014 pausing, then re-reading the current version"
|
|
16704
|
+
);
|
|
16705
|
+
await new Promise((resolve) => setTimeout(resolve, SCRIPT_CONFLICT_RETRY_DELAY_MS));
|
|
16706
|
+
try {
|
|
16707
|
+
latest = await readLatestScriptVersion(ctxClient, name);
|
|
16708
|
+
} catch (readErr) {
|
|
16709
|
+
throw new Error(
|
|
16710
|
+
`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.`
|
|
16711
|
+
);
|
|
16712
|
+
}
|
|
16713
|
+
if (latest !== void 0 && scriptMatches(latest, entry)) {
|
|
16714
|
+
actions.push({
|
|
16715
|
+
name,
|
|
16716
|
+
action: "unchanged",
|
|
16717
|
+
id: requireScriptId(latest, name),
|
|
16718
|
+
scriptVersion: latest.scriptVersion ?? 0
|
|
16719
|
+
});
|
|
16720
|
+
log.debug(
|
|
16721
|
+
{ component: "loader", scriptName: name, scriptVersion: latest.scriptVersion },
|
|
16722
|
+
"the concurrent push stored the same source \u2014 converged, nothing to do"
|
|
16723
|
+
);
|
|
16724
|
+
continue;
|
|
16725
|
+
}
|
|
16726
|
+
try {
|
|
16727
|
+
pushed = await ctxClient.scripts.createScript(body);
|
|
16728
|
+
} catch (retryErr) {
|
|
16729
|
+
if (!isVersionConflict(retryErr)) throw retryErr;
|
|
16730
|
+
throw new Error(
|
|
16731
|
+
`Script '${name}': still conflicting (409) after a re-read and retry. Another apply is pushing this script name concurrently \u2014 re-run once it has finished.`
|
|
16732
|
+
);
|
|
16733
|
+
}
|
|
16734
|
+
}
|
|
16735
|
+
if (!pushed.id) {
|
|
16736
|
+
throw new Error(`Script '${name}' push returned no id.`);
|
|
16737
|
+
}
|
|
16738
|
+
const firstVersion = latest === void 0;
|
|
16739
|
+
actions.push({
|
|
16740
|
+
name,
|
|
16741
|
+
action: firstVersion ? "created" : "superseded",
|
|
16742
|
+
id: pushed.id,
|
|
16743
|
+
scriptVersion: pushed.scriptVersion ?? 0
|
|
16744
|
+
});
|
|
16745
|
+
log.debug(
|
|
16746
|
+
{ component: "loader", scriptName: name, scriptVersion: pushed.scriptVersion },
|
|
16747
|
+
firstVersion ? "script pushed \u2014 first version for this name in this context" : "script source changed \u2014 new version pushed over an existing name"
|
|
16748
|
+
);
|
|
16749
|
+
}
|
|
16750
|
+
return actions;
|
|
16751
|
+
}
|
|
16752
|
+
async function pruneUndeclaredTriggers(args) {
|
|
16753
|
+
const { ctxClient, blueprintName, contextId, declaredNames, log } = args;
|
|
16754
|
+
const pruned = [];
|
|
16755
|
+
const skipped = [];
|
|
16756
|
+
let live;
|
|
16757
|
+
try {
|
|
16758
|
+
live = await drainPages(
|
|
16759
|
+
(startFrom) => ctxClient.triggers.listTriggers({ startFrom, limit: PAGE_LIMIT })
|
|
16760
|
+
);
|
|
16761
|
+
} catch (err) {
|
|
16762
|
+
log.warn(
|
|
16763
|
+
{ component: "loader", err: extractServerError(err) },
|
|
16764
|
+
"could not list trigger rules \u2014 skipping the trigger reconcile entirely; a trigger removed from this blueprint may still be live"
|
|
16765
|
+
);
|
|
16766
|
+
return { pruned, skipped: [{ reason: "the context's trigger rules could not be listed" }] };
|
|
16767
|
+
}
|
|
16768
|
+
for (const row of live) {
|
|
16769
|
+
if (!row.name || !row.id || !row.principalId) {
|
|
16770
|
+
if (!declaredNames.has(row.name ?? "")) {
|
|
16771
|
+
skipped.push({
|
|
16772
|
+
name: row.name,
|
|
16773
|
+
reason: "the trigger rule carries no id/name/principal, so it cannot be safely de-provisioned"
|
|
16774
|
+
});
|
|
16775
|
+
}
|
|
16776
|
+
continue;
|
|
16777
|
+
}
|
|
16778
|
+
if (declaredNames.has(row.name)) continue;
|
|
16779
|
+
if (!row.provisionedBy) {
|
|
16780
|
+
skipped.push({
|
|
16781
|
+
name: row.name,
|
|
16782
|
+
reason: "it carries no blueprint provenance marker, so it cannot be attributed to this blueprint"
|
|
16783
|
+
});
|
|
16784
|
+
continue;
|
|
16785
|
+
}
|
|
16786
|
+
if (row.provisionedBy !== blueprintName) {
|
|
16787
|
+
log.debug(
|
|
16788
|
+
{ component: "loader", triggerName: row.name, provisionedBy: row.provisionedBy },
|
|
16789
|
+
"undeclared trigger left untouched \u2014 provisioned by a different blueprint"
|
|
16790
|
+
);
|
|
16791
|
+
continue;
|
|
16792
|
+
}
|
|
16793
|
+
let principalExternalId;
|
|
16794
|
+
try {
|
|
16795
|
+
principalExternalId = (await ctxClient.identity.getUser({ id: normalizePrincipalUserId(row.principalId) })).externalId;
|
|
16796
|
+
} catch (err) {
|
|
16797
|
+
log.warn(
|
|
16798
|
+
{ component: "loader", triggerName: row.name, principalId: row.principalId, err: extractServerError(err) },
|
|
16799
|
+
"could not resolve a trigger's principal \u2014 leaving the trigger in place"
|
|
16800
|
+
);
|
|
16801
|
+
skipped.push({
|
|
16802
|
+
name: row.name,
|
|
16803
|
+
reason: "its service principal could not be read, so ownership could not be fully established"
|
|
16804
|
+
});
|
|
16805
|
+
continue;
|
|
16806
|
+
}
|
|
16807
|
+
if (principalExternalId !== triggerPrincipalExternalId(blueprintName, row.name)) {
|
|
16808
|
+
log.warn(
|
|
16809
|
+
{ component: "loader", triggerName: row.name, principalId: row.principalId },
|
|
16810
|
+
"undeclared trigger left untouched \u2014 it carries this blueprint's provenance marker but names a principal this blueprint did not mint for it"
|
|
16811
|
+
);
|
|
16812
|
+
skipped.push({
|
|
16813
|
+
name: row.name,
|
|
16814
|
+
reason: "it names a service principal this blueprint did not mint for a trigger of that name, so its provenance is inconsistent"
|
|
16815
|
+
});
|
|
16816
|
+
continue;
|
|
16817
|
+
}
|
|
16818
|
+
let profileDeleted = true;
|
|
16819
|
+
try {
|
|
16820
|
+
await ctxClient.auth.deleteAccessProfile({ contextId, principalId: row.principalId });
|
|
16821
|
+
} catch (err) {
|
|
16822
|
+
if (!isNotFound(err)) {
|
|
16823
|
+
log.warn(
|
|
16824
|
+
{ component: "loader", triggerName: row.name, principalId: row.principalId, err: extractServerError(err) },
|
|
16825
|
+
"could not delete a removed trigger's access profile \u2014 leaving the trigger rule in place so the next apply retries"
|
|
16826
|
+
);
|
|
16827
|
+
skipped.push({ name: row.name, reason: "its access profile could not be deleted, so its grant may still be live" });
|
|
16828
|
+
continue;
|
|
16829
|
+
}
|
|
16830
|
+
profileDeleted = false;
|
|
16831
|
+
}
|
|
16832
|
+
try {
|
|
16833
|
+
await ctxClient.triggers.deleteTrigger({ id: row.id });
|
|
16834
|
+
} catch (err) {
|
|
16835
|
+
log.warn(
|
|
16836
|
+
{ component: "loader", triggerName: row.name, err: extractServerError(err) },
|
|
16837
|
+
"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"
|
|
16838
|
+
);
|
|
16839
|
+
skipped.push({ name: row.name, reason: "its grant was removed but the rule row itself could not be deleted" });
|
|
16840
|
+
continue;
|
|
16841
|
+
}
|
|
16842
|
+
pruned.push({ name: row.name, principalId: row.principalId, profileDeleted });
|
|
16843
|
+
log.debug(
|
|
16844
|
+
{ component: "loader", triggerName: row.name, principalId: row.principalId, profileDeleted },
|
|
16845
|
+
"undeclared trigger de-provisioned (no longer declared by this blueprint)"
|
|
16846
|
+
);
|
|
16847
|
+
}
|
|
16848
|
+
return { pruned, skipped };
|
|
16849
|
+
}
|
|
16850
|
+
function isNotFound(err) {
|
|
16851
|
+
return typeof err === "object" && err !== null && err.statusCode === 404;
|
|
14970
16852
|
}
|
|
14971
16853
|
async function registerNamespaceIdempotently(client, blueprintContextId, decl) {
|
|
14972
16854
|
const contextIdForRequest = decl.tenantWide ? void 0 : blueprintContextId;
|
|
@@ -15070,6 +16952,10 @@ function toSdkFields(blueprint, schemaIndex) {
|
|
|
15070
16952
|
required: f.required,
|
|
15071
16953
|
searchable: f.searchable,
|
|
15072
16954
|
filterable: f.filterable,
|
|
16955
|
+
// Forwarded explicitly, like every sibling here: this mapping is a named-property list, not a
|
|
16956
|
+
// spread, so an unforwarded field is dropped SILENTLY rather than failing — which is what
|
|
16957
|
+
// happened to `validation` before it was added below.
|
|
16958
|
+
inline: f.inline,
|
|
15073
16959
|
description: f.description,
|
|
15074
16960
|
enumValues: mapEnumValues(f.enumValues),
|
|
15075
16961
|
// Format passthrough — the loader previously DROPPED this (the
|
|
@@ -15125,13 +17011,16 @@ function toCapabilities(capabilities) {
|
|
|
15125
17011
|
if (!capabilities) return void 0;
|
|
15126
17012
|
const out = {};
|
|
15127
17013
|
if (capabilities.auditHistory !== void 0) out.auditHistory = capabilities.auditHistory;
|
|
17014
|
+
if (capabilities.triggersEnabled !== void 0)
|
|
17015
|
+
out.triggersEnabled = capabilities.triggersEnabled;
|
|
15128
17016
|
return Object.keys(out).length > 0 ? out : void 0;
|
|
15129
17017
|
}
|
|
15130
17018
|
function toRoleScopeClauses(clauses) {
|
|
15131
17019
|
return clauses.map((c) => ({
|
|
15132
17020
|
allowed_actions: c.allowedActions,
|
|
15133
17021
|
data_scope: c.dataScope,
|
|
15134
|
-
granted_capabilities: c.capabilities
|
|
17022
|
+
granted_capabilities: c.capabilities,
|
|
17023
|
+
assignable_roles: c.assignableRoles
|
|
15135
17024
|
}));
|
|
15136
17025
|
}
|
|
15137
17026
|
var UnresolvedIdentitiesError = class extends Error {
|
|
@@ -15168,7 +17057,13 @@ async function provisionContext(client, input) {
|
|
|
15168
17057
|
const { blueprint, allowTenantWideNamespaces = false, readExistingKey, confirmExistingPrincipal, log } = input;
|
|
15169
17058
|
assertBlueprintGates(blueprint);
|
|
15170
17059
|
assertTenantWideNamespacesAllowed(blueprint, allowTenantWideNamespaces);
|
|
15171
|
-
const ctxResp = await client.auth.createAppContext({
|
|
17060
|
+
const ctxResp = await client.auth.createAppContext({
|
|
17061
|
+
body: {
|
|
17062
|
+
contextId: blueprint.contextId,
|
|
17063
|
+
name: contextNameOf(blueprint),
|
|
17064
|
+
...blueprint.identityProjectionClaims === void 0 ? {} : { identityProjectionClaims: blueprint.identityProjectionClaims }
|
|
17065
|
+
}
|
|
17066
|
+
});
|
|
15172
17067
|
const contextAction = actionOf(ctxResp.created);
|
|
15173
17068
|
log.debug({ component: "loader", contextId: blueprint.contextId, contextAction }, "app-context ensured");
|
|
15174
17069
|
const user = await client.identity.createUser({
|
|
@@ -15255,7 +17150,8 @@ async function runLoader(client, input) {
|
|
|
15255
17150
|
subClaim: issuer.subClaim,
|
|
15256
17151
|
emailClaim: issuer.emailClaim,
|
|
15257
17152
|
userinfoUri: issuer.userinfoUri,
|
|
15258
|
-
selfSignupPolicies
|
|
17153
|
+
selfSignupPolicies,
|
|
17154
|
+
capturedClaims: issuer.capturedClaims
|
|
15259
17155
|
});
|
|
15260
17156
|
const action = actionOf(issuerResp.created);
|
|
15261
17157
|
issuerActions.push({ issuerId: issuer.issuerId, action });
|
|
@@ -15271,7 +17167,8 @@ async function runLoader(client, input) {
|
|
|
15271
17167
|
subClaim: issuer.subClaim,
|
|
15272
17168
|
emailClaim: issuer.emailClaim,
|
|
15273
17169
|
userinfoUri: issuer.userinfoUri,
|
|
15274
|
-
selfSignupPolicies
|
|
17170
|
+
selfSignupPolicies,
|
|
17171
|
+
capturedClaims: issuer.capturedClaims
|
|
15275
17172
|
});
|
|
15276
17173
|
log.debug({ component: "loader", issuerId: issuer.issuerId }, "issuer already registered \u2014 reconciled safe fields to the declared values");
|
|
15277
17174
|
} else {
|
|
@@ -15352,7 +17249,10 @@ async function runLoader(client, input) {
|
|
|
15352
17249
|
data_scope: blueprint.accessProfile.dataScope,
|
|
15353
17250
|
// Already gate-validated (assertBlueprintGates, above this function's
|
|
15354
17251
|
// call site) before this body is ever built.
|
|
15355
|
-
granted_capabilities: blueprint.accessProfile.capabilities
|
|
17252
|
+
granted_capabilities: blueprint.accessProfile.capabilities,
|
|
17253
|
+
// Absent means "no roleId restriction" server-side; an empty list is refused. Undeclared
|
|
17254
|
+
// must therefore stay `undefined` rather than becoming `[]`.
|
|
17255
|
+
assignable_roles: blueprint.accessProfile.assignableRoles
|
|
15356
17256
|
}
|
|
15357
17257
|
],
|
|
15358
17258
|
// Optional identity overrides — scope values the service key STAMPS onto
|
|
@@ -15375,6 +17275,109 @@ async function runLoader(client, input) {
|
|
|
15375
17275
|
body: profileBody
|
|
15376
17276
|
});
|
|
15377
17277
|
log.debug({ component: "loader", principalId: `usr_${userId}`, accessProfileId: profile.id, profileAction }, "access profile ensured + reconciled to declared scopes");
|
|
17278
|
+
const scriptActions = blueprint.scripts ? await ensureScripts({ ctxClient, scripts: blueprint.scripts, log }) : [];
|
|
17279
|
+
const triggerActions = [];
|
|
17280
|
+
for (const [triggerName, entry] of Object.entries(blueprint.triggers ?? {})) {
|
|
17281
|
+
const schemaId = schemaIds[entry.firingSource.schemaName];
|
|
17282
|
+
if (!schemaId) {
|
|
17283
|
+
throw new Error(
|
|
17284
|
+
`Trigger '${triggerName}' names schema '${entry.firingSource.schemaName}', which has no resolved schemaId \u2014 the schema failed to provision.`
|
|
17285
|
+
);
|
|
17286
|
+
}
|
|
17287
|
+
const triggerUser = await client.identity.createUser({
|
|
17288
|
+
body: {
|
|
17289
|
+
externalId: triggerPrincipalExternalId(blueprint.name, triggerName),
|
|
17290
|
+
type: "SERVICE",
|
|
17291
|
+
payload: { displayName: `Trigger: ${triggerName}` }
|
|
17292
|
+
}
|
|
17293
|
+
});
|
|
17294
|
+
const triggerUserId = triggerUser.id;
|
|
17295
|
+
if (!triggerUserId) {
|
|
17296
|
+
throw new Error(`Trigger '${triggerName}' service-principal create returned no user id.`);
|
|
17297
|
+
}
|
|
17298
|
+
const triggerPlaceholderProfileBody = {
|
|
17299
|
+
principalId: `usr_${triggerUserId}`,
|
|
17300
|
+
scopes: [{ allowed_actions: ["triggers:r"] }]
|
|
17301
|
+
};
|
|
17302
|
+
const triggerProfile = await ctxClient.auth.createAccessProfile({
|
|
17303
|
+
contextId: blueprint.contextId,
|
|
17304
|
+
body: triggerPlaceholderProfileBody
|
|
17305
|
+
});
|
|
17306
|
+
log.debug(
|
|
17307
|
+
{ component: "loader", triggerName, principalId: `usr_${triggerUserId}`, accessProfileId: triggerProfile.id },
|
|
17308
|
+
"trigger principal + placeholder access profile ensured (no declared grant attached yet)"
|
|
17309
|
+
);
|
|
17310
|
+
const triggerResp = await ctxClient.triggers.createTrigger({
|
|
17311
|
+
upsert: true,
|
|
17312
|
+
body: {
|
|
17313
|
+
name: triggerName,
|
|
17314
|
+
firingSource: { schemaId, event: entry.firingSource.event },
|
|
17315
|
+
scriptRef: entry.scriptRef,
|
|
17316
|
+
roleIds: entry.roleIds,
|
|
17317
|
+
scopes: entry.roleIds ? void 0 : [
|
|
17318
|
+
{
|
|
17319
|
+
allowed_actions: entry.allowedActions,
|
|
17320
|
+
data_scope: entry.dataScope,
|
|
17321
|
+
granted_capabilities: entry.capabilities
|
|
17322
|
+
}
|
|
17323
|
+
],
|
|
17324
|
+
manifest: entry.manifest,
|
|
17325
|
+
// REQUIRED on create. `[]` is a real declaration ("project nothing beyond input.recordId"),
|
|
17326
|
+
// not an omission, so it is forwarded as-is rather than collapsed to undefined the way an
|
|
17327
|
+
// absent optional would be — the blueprint schema makes it required precisely so an author
|
|
17328
|
+
// STATES it.
|
|
17329
|
+
fields: entry.fields,
|
|
17330
|
+
principalId: `usr_${triggerUserId}`,
|
|
17331
|
+
// The PROVENANCE MARKER the reconcile-by-absence pass gates its deletes on. Stamped on every
|
|
17332
|
+
// apply (not only the first) so a trigger provisioned before this field existed acquires one
|
|
17333
|
+
// the next time its blueprint is applied — the platform accepts a set-when-absent and refuses
|
|
17334
|
+
// a CHANGE, so re-stating it is idempotent and can never re-point someone else's trigger.
|
|
17335
|
+
provisionedBy: blueprint.name
|
|
17336
|
+
}
|
|
17337
|
+
});
|
|
17338
|
+
triggerActions.push({ name: triggerName, action: actionOf(triggerResp.created) });
|
|
17339
|
+
log.debug({ component: "loader", triggerName, action: actionOf(triggerResp.created) }, "trigger rule ensured (upsert)");
|
|
17340
|
+
const triggerProfileBody = entry.roleIds ? { principalId: `usr_${triggerUserId}`, roleIds: entry.roleIds } : {
|
|
17341
|
+
principalId: `usr_${triggerUserId}`,
|
|
17342
|
+
scopes: [
|
|
17343
|
+
{
|
|
17344
|
+
// Non-null: the `else` branch of the roleIds XOR above — BlueprintTriggerEntrySchema
|
|
17345
|
+
// guarantees allowedActions is set whenever roleIds is not.
|
|
17346
|
+
allowed_actions: entry.allowedActions,
|
|
17347
|
+
data_scope: entry.dataScope,
|
|
17348
|
+
granted_capabilities: entry.capabilities
|
|
17349
|
+
}
|
|
17350
|
+
]
|
|
17351
|
+
};
|
|
17352
|
+
await ctxClient.auth.updateAccessProfile({
|
|
17353
|
+
contextId: blueprint.contextId,
|
|
17354
|
+
principalId: `usr_${triggerUserId}`,
|
|
17355
|
+
body: triggerProfileBody
|
|
17356
|
+
});
|
|
17357
|
+
log.debug(
|
|
17358
|
+
{ component: "loader", triggerName, principalId: `usr_${triggerUserId}` },
|
|
17359
|
+
"trigger access profile widened to its declared grant"
|
|
17360
|
+
);
|
|
17361
|
+
}
|
|
17362
|
+
let triggerPrunes = [];
|
|
17363
|
+
let triggerPruneSkipped = [];
|
|
17364
|
+
if (input.pruneTriggers === false) {
|
|
17365
|
+
log.debug({ component: "loader" }, "trigger reconcile-by-absence skipped \u2014 disabled by the caller");
|
|
17366
|
+
} else if (!input.remintForContext) {
|
|
17367
|
+
log.warn(
|
|
17368
|
+
{ component: "loader", contextId: blueprint.contextId },
|
|
17369
|
+
"trigger reconcile-by-absence skipped \u2014 no per-context client is wired, so a removed trigger may still be live"
|
|
17370
|
+
);
|
|
17371
|
+
triggerPruneSkipped = [{ reason: "no per-context client was wired, so triggers could not be reconciled in the blueprint's own context" }];
|
|
17372
|
+
} else {
|
|
17373
|
+
({ pruned: triggerPrunes, skipped: triggerPruneSkipped } = await pruneUndeclaredTriggers({
|
|
17374
|
+
ctxClient,
|
|
17375
|
+
blueprintName: blueprint.name,
|
|
17376
|
+
contextId: blueprint.contextId,
|
|
17377
|
+
declaredNames: new Set(Object.keys(blueprint.triggers ?? {})),
|
|
17378
|
+
log
|
|
17379
|
+
}));
|
|
17380
|
+
}
|
|
15378
17381
|
const keyName = keyNameFor(blueprint.name, machineId);
|
|
15379
17382
|
let keyAction = "created";
|
|
15380
17383
|
let revokedKeyId;
|
|
@@ -15496,6 +17499,10 @@ async function runLoader(client, input) {
|
|
|
15496
17499
|
profileAction,
|
|
15497
17500
|
schemaActions,
|
|
15498
17501
|
roleActions,
|
|
17502
|
+
scriptActions,
|
|
17503
|
+
triggerActions,
|
|
17504
|
+
triggerPrunes,
|
|
17505
|
+
triggerPruneSkipped,
|
|
15499
17506
|
issuerActions,
|
|
15500
17507
|
namespaceActions,
|
|
15501
17508
|
rawKey,
|
|
@@ -15516,7 +17523,11 @@ function renderPlanDataScope(ds) {
|
|
|
15516
17523
|
}
|
|
15517
17524
|
function buildPlan(blueprint, opts = {}) {
|
|
15518
17525
|
const steps = [];
|
|
15519
|
-
|
|
17526
|
+
const projection = blueprint.identityProjectionClaims?.length ? `, identity projection: [${blueprint.identityProjectionClaims.join(", ")}]` : blueprint.identityProjectionClaims ? ", identity projection: disabled (empty list)" : "";
|
|
17527
|
+
steps.push({
|
|
17528
|
+
kind: "context",
|
|
17529
|
+
summary: `context '${blueprint.contextId}' (${contextNameOf(blueprint)}${projection})`
|
|
17530
|
+
});
|
|
15520
17531
|
steps.push({
|
|
15521
17532
|
kind: "principal",
|
|
15522
17533
|
summary: `principal '${blueprint.servicePrincipal.externalId}' (SERVICE)`
|
|
@@ -15532,9 +17543,10 @@ function buildPlan(blueprint, opts = {}) {
|
|
|
15532
17543
|
}
|
|
15533
17544
|
for (const iss of blueprint.issuers ?? []) {
|
|
15534
17545
|
const selfSignup = iss.selfSignupPolicies?.length ? `, self-signup: ${iss.selfSignupPolicies.length} polic${iss.selfSignupPolicies.length === 1 ? "y" : "ies"}` : "";
|
|
17546
|
+
const captured = iss.capturedClaims?.length ? `, captured claims: [${iss.capturedClaims.join(", ")}]` : "";
|
|
15535
17547
|
steps.push({
|
|
15536
17548
|
kind: "issuer",
|
|
15537
|
-
summary: `issuer '${iss.issuerId}' (${iss.issuer} \u2192 context '${iss.contextId}'${selfSignup}) [${BLUEPRINT_FIELD_PHASES.issuers}-token phase]`
|
|
17549
|
+
summary: `issuer '${iss.issuerId}' (${iss.issuer} \u2192 context '${iss.contextId}'${selfSignup}${captured}) [${BLUEPRINT_FIELD_PHASES.issuers}-token phase]`
|
|
15538
17550
|
});
|
|
15539
17551
|
}
|
|
15540
17552
|
for (const s of blueprint.schemas) {
|
|
@@ -15556,7 +17568,8 @@ function buildPlan(blueprint, opts = {}) {
|
|
|
15556
17568
|
const roleCapabilityNote = capSet.length ? `, capabilities [${capSet.join(", ")}]` : "";
|
|
15557
17569
|
const clauseLines = clauses.map((c, i) => {
|
|
15558
17570
|
const capNote = c.capabilities?.length ? `, capabilities [${c.capabilities.join(", ")}]` : "";
|
|
15559
|
-
|
|
17571
|
+
const assignableNote = c.assignableRoles?.length ? `, assignableRoles [${c.assignableRoles.join(", ")}]` : "";
|
|
17572
|
+
return ` clause ${i + 1}: actions [${c.allowedActions.join(", ")}], dataScope: ${renderPlanDataScope(c.dataScope)}${capNote}${assignableNote}`;
|
|
15560
17573
|
});
|
|
15561
17574
|
const assumable = blueprint.roleAssumable?.[roleId];
|
|
15562
17575
|
const assumableLine = assumable ? `
|
|
@@ -15586,6 +17599,43 @@ function buildPlan(blueprint, opts = {}) {
|
|
|
15586
17599
|
summary: `profile scopes [${actions.join(", ")}]${planeNote}${scopeNote}${capabilityNote}`
|
|
15587
17600
|
});
|
|
15588
17601
|
}
|
|
17602
|
+
for (const [scriptName, entry] of Object.entries(blueprint.scripts ?? {})) {
|
|
17603
|
+
const bytes = new TextEncoder().encode(entry.source).length;
|
|
17604
|
+
const contractNote = entry.declaredInputContract ? " + input contract" : "";
|
|
17605
|
+
steps.push({
|
|
17606
|
+
kind: "script",
|
|
17607
|
+
summary: `script '${scriptName}' (ensure latest matches ${bytes} bytes of source${contractNote}) [${BLUEPRINT_FIELD_PHASES.scripts}-token phase]`
|
|
17608
|
+
});
|
|
17609
|
+
}
|
|
17610
|
+
for (const [triggerName, entry] of Object.entries(blueprint.triggers ?? {})) {
|
|
17611
|
+
steps.push({
|
|
17612
|
+
kind: "principal",
|
|
17613
|
+
summary: `trigger '${triggerName}' principal '${blueprint.name}-trigger-${triggerName}' (SERVICE)`
|
|
17614
|
+
});
|
|
17615
|
+
steps.push({
|
|
17616
|
+
kind: "profile",
|
|
17617
|
+
summary: `trigger '${triggerName}' profile: minimal placeholder grant (minted before the trigger row exists)`
|
|
17618
|
+
});
|
|
17619
|
+
const manifestNote = entry.manifest?.length ? ` manifest: [${entry.manifest.join(", ")}]` : " manifest: (none \u2014 full grant applies)";
|
|
17620
|
+
const fieldsNote = entry.fields?.length ? ` projects: [${entry.fields.join(", ")}]` : " projects: (nothing beyond the record id)";
|
|
17621
|
+
steps.push({
|
|
17622
|
+
kind: "trigger",
|
|
17623
|
+
summary: `trigger '${triggerName}': ${entry.firingSource.schemaName}.${entry.firingSource.event} \u2192 ${entry.scriptRef.name}@${entry.scriptRef.version}${fieldsNote}${manifestNote} [${BLUEPRINT_FIELD_PHASES.triggers}-token phase]`
|
|
17624
|
+
});
|
|
17625
|
+
if (entry.roleIds) {
|
|
17626
|
+
steps.push({
|
|
17627
|
+
kind: "profile",
|
|
17628
|
+
summary: `trigger '${triggerName}' profile widened via role(s) [${entry.roleIds.join(", ")}] (composed)`
|
|
17629
|
+
});
|
|
17630
|
+
} else {
|
|
17631
|
+
const scopeNote = entry.dataScope && Object.keys(entry.dataScope).length ? ` dataScope: ${Object.keys(entry.dataScope).join(", ")}` : "";
|
|
17632
|
+
const capabilityNote = entry.capabilities?.length ? ` capabilities: [${entry.capabilities.join(", ")}]` : "";
|
|
17633
|
+
steps.push({
|
|
17634
|
+
kind: "profile",
|
|
17635
|
+
summary: `trigger '${triggerName}' profile widened to declared scopes [${(entry.allowedActions ?? []).join(", ")}]${scopeNote}${capabilityNote}`
|
|
17636
|
+
});
|
|
17637
|
+
}
|
|
17638
|
+
}
|
|
15589
17639
|
steps.push({ kind: "key", summary: `key ${blueprint.name}-<machine> (mint 1 scoped ssk_*)` });
|
|
15590
17640
|
const seeds = blueprint.seed ?? [];
|
|
15591
17641
|
if (seeds.length > 0 && !opts.noSeed) {
|
|
@@ -15601,9 +17651,27 @@ function buildPlan(blueprint, opts = {}) {
|
|
|
15601
17651
|
function renderPlan(blueprint, steps) {
|
|
15602
17652
|
const header = ` Plan for blueprint '${blueprint.name}' (v${blueprint.version}) \u2192 context '${blueprint.contextId}':`;
|
|
15603
17653
|
const legendLines = [
|
|
15604
|
-
` Apply is a
|
|
15605
|
-
`
|
|
17654
|
+
` Apply is a reconcile \u2014 each item is created if absent, else updated in place to`,
|
|
17655
|
+
` match. Nothing is recreated, and nothing is dropped except as noted below.`
|
|
15606
17656
|
];
|
|
17657
|
+
legendLines.push(
|
|
17658
|
+
` Exception: triggers are also reconciled by ABSENCE \u2014 a trigger this blueprint`,
|
|
17659
|
+
` previously provisioned and no longer declares is DELETED (the rule and its access`,
|
|
17660
|
+
` profile; its service principal is kept). This applies even when this plan shows no`,
|
|
17661
|
+
` trigger steps: removing the last one is exactly what leaves none to show. Triggers`,
|
|
17662
|
+
` created outside this blueprint are never touched. Each deletion is named in the`,
|
|
17663
|
+
` apply output.`
|
|
17664
|
+
);
|
|
17665
|
+
legendLines.push(
|
|
17666
|
+
` Counter-exception: a script is never updated in place \u2014 a changed source ADDS a new`,
|
|
17667
|
+
` immutable version and every earlier one persists, which is also how a 'latest' rule`,
|
|
17668
|
+
` picks the change up. And scripts are NOT reconciled by absence: a script this blueprint`,
|
|
17669
|
+
` previously shipped and no longer declares is LEFT IN PLACE, including every stored`,
|
|
17670
|
+
` version. A script row carries no provisioning marker, so an apply cannot tell one it`,
|
|
17671
|
+
` created from one you pushed by hand \u2014 and a wrong delete is unrecoverable and would`,
|
|
17672
|
+
` break any trigger still referencing it. Remove one deliberately with`,
|
|
17673
|
+
` 'vectros scripts delete', after 'vectros scripts list' shows you what is there.`
|
|
17674
|
+
);
|
|
15607
17675
|
if (steps.some((s) => s.kind === "issuer")) {
|
|
15608
17676
|
legendLines.push(
|
|
15609
17677
|
` Exception: an issuer's trust-anchor fields (issuer, jwksUri, audience,`,
|
|
@@ -17855,6 +19923,19 @@ function namespaceTally(items) {
|
|
|
17855
19923
|
if (matched) seg.push(`${matched} already registered \u2014 confirmed matching`);
|
|
17856
19924
|
return `${items.length} namespace${items.length === 1 ? "" : "s"} (${seg.join(", ")})`;
|
|
17857
19925
|
}
|
|
19926
|
+
function scriptTally(items) {
|
|
19927
|
+
if (items.length === 0) return null;
|
|
19928
|
+
const created = items.filter((i) => i.action === "created");
|
|
19929
|
+
const superseded = items.filter((i) => i.action === "superseded");
|
|
19930
|
+
const unchanged = items.length - created.length - superseded.length;
|
|
19931
|
+
const seg = [];
|
|
19932
|
+
if (created.length) seg.push(`${created.length} first version${created.length === 1 ? "" : "s"} pushed`);
|
|
19933
|
+
if (superseded.length) {
|
|
19934
|
+
seg.push(`${superseded.map((i) => `'${i.name}'`).join(", ")} superseded (a new version now current)`);
|
|
19935
|
+
}
|
|
19936
|
+
if (unchanged) seg.push(`${unchanged} already current \u2014 nothing written`);
|
|
19937
|
+
return `${items.length} script${items.length === 1 ? "" : "s"} (${seg.join("; ")})`;
|
|
19938
|
+
}
|
|
17858
19939
|
function applyOutcomeSummary(result) {
|
|
17859
19940
|
const parts = [
|
|
17860
19941
|
reconcileTally(result.schemaActions, "schema"),
|
|
@@ -17863,13 +19944,34 @@ function applyOutcomeSummary(result) {
|
|
|
17863
19944
|
// 'reconciled' mean the same "updated to match" as every other kind here.
|
|
17864
19945
|
reconcileTally(result.issuerActions, "issuer"),
|
|
17865
19946
|
namespaceTally(result.namespaceActions),
|
|
19947
|
+
// Scripts — reported BEFORE triggers, mirroring the order they are provisioned in, and with
|
|
19948
|
+
// their own wording because "reconciled" would be a lie: a script version is immutable.
|
|
19949
|
+
scriptTally(result.scriptActions),
|
|
19950
|
+
// Trigger rules — 'reconciled' means the same "upsert applied the declared shape" as
|
|
19951
|
+
// schemas/roles (createTrigger's own upsert:true reconcile), so the shared reconcileTally
|
|
19952
|
+
// wording applies unchanged; buildPlan's dry-run preview already discloses each trigger's own
|
|
19953
|
+
// steps individually, this is the apply-time tally counterpart.
|
|
19954
|
+
reconcileTally(result.triggerActions, "trigger"),
|
|
17866
19955
|
`profile ${result.profileAction}`
|
|
17867
19956
|
].filter((p) => p !== null);
|
|
17868
19957
|
const detail = parts.length ? ` \u2014 ${parts.join("; ")}` : "";
|
|
17869
19958
|
const principalNote = result.userAction === "reconciled" ? ` \u26A0 service principal already existed in this tenant before this run (not newly created)
|
|
17870
19959
|
` : "";
|
|
19960
|
+
const pruneNotes = result.triggerPrunes.map(
|
|
19961
|
+
(p) => ` \u26A0 trigger '${p.name}' removed from the blueprint \u2014 de-provisioned (rule + access profile deleted)
|
|
19962
|
+
` + // Not a warning: 404 on the profile means it was already gone, so the end state is exactly
|
|
19963
|
+
// what a clean prune produces. Said out loud only so the absent second delete isn't read as
|
|
19964
|
+
// a silent skip. Every OTHER profile-delete failure lands in triggerPruneSkipped instead.
|
|
19965
|
+
(p.profileDeleted ? "" : ` (its access profile was already absent \u2014 nothing left to remove)
|
|
19966
|
+
`)
|
|
19967
|
+
).join("");
|
|
19968
|
+
const pruneSkippedNotes = result.triggerPruneSkipped.map(
|
|
19969
|
+
(s) => s.name ? ` \u26A0 trigger '${s.name}' is no longer declared but could NOT be removed \u2014 ${s.reason}. It is still live.
|
|
19970
|
+
` : ` \u26A0 triggers could not be reconciled this run \u2014 ${s.reason}. A trigger removed from this blueprint may still be live.
|
|
19971
|
+
`
|
|
19972
|
+
).join("");
|
|
17871
19973
|
return ` \u2714 context '${result.contextId}' ${result.contextAction}${detail}
|
|
17872
|
-
${principalNote}`;
|
|
19974
|
+
${principalNote}${pruneNotes}${pruneSkippedNotes}`;
|
|
17873
19975
|
}
|
|
17874
19976
|
var BOOTSTRAP_HELP = `vectros bootstrap \u2014 provision a least-privilege MCP credential.
|
|
17875
19977
|
|
|
@@ -18068,7 +20170,7 @@ ${BOOTSTRAP_HELP}
|
|
|
18068
20170
|
process.stderr.write(`
|
|
18069
20171
|
\u2716 Scope gate refused blueprint '${blueprint.name}':
|
|
18070
20172
|
`);
|
|
18071
|
-
for (const r of err.rejected) process.stderr.write(` \u2022 ${r
|
|
20173
|
+
for (const r of err.rejected) process.stderr.write(` \u2022 ${formatRejection(r)}
|
|
18072
20174
|
`);
|
|
18073
20175
|
process.stderr.write(" Minting nothing.\n");
|
|
18074
20176
|
return 2;
|
|
@@ -18077,7 +20179,7 @@ ${BOOTSTRAP_HELP}
|
|
|
18077
20179
|
process.stderr.write(`
|
|
18078
20180
|
\u2716 Capability gate refused blueprint '${blueprint.name}':
|
|
18079
20181
|
`);
|
|
18080
|
-
for (const r of err.rejected) process.stderr.write(` \u2022 ${r
|
|
20182
|
+
for (const r of err.rejected) process.stderr.write(` \u2022 ${formatRejection(r)}
|
|
18081
20183
|
`);
|
|
18082
20184
|
process.stderr.write(" Minting nothing.\n");
|
|
18083
20185
|
return 2;
|
|
@@ -18391,71 +20493,6 @@ ${formatCliError(err, { action: "Bootstrap failed" })}`);
|
|
|
18391
20493
|
// src/cli-blueprint.ts
|
|
18392
20494
|
var import_promises3 = require("fs/promises");
|
|
18393
20495
|
|
|
18394
|
-
// src/scope-grammar.ts
|
|
18395
|
-
var FORBIDDEN_SCOPE_NAMESPACES2 = /* @__PURE__ */ new Set([
|
|
18396
|
-
"user",
|
|
18397
|
-
"record",
|
|
18398
|
-
"document",
|
|
18399
|
-
"entity",
|
|
18400
|
-
"self",
|
|
18401
|
-
"tenant",
|
|
18402
|
-
"context",
|
|
18403
|
-
"scope",
|
|
18404
|
-
"versions",
|
|
18405
|
-
"lookup"
|
|
18406
|
-
]);
|
|
18407
|
-
var SCOPE_NAMESPACE_RE2 = /^[a-z][a-z0-9_-]{1,31}$/;
|
|
18408
|
-
var SCOPE_NAMESPACE_MIN_LENGTH = 2;
|
|
18409
|
-
var SCOPE_NAMESPACE_MAX_LENGTH = 32;
|
|
18410
|
-
var SCOPE_VALUE_RE2 = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
|
|
18411
|
-
var SCOPE_VALUE_MIN_LENGTH = 1;
|
|
18412
|
-
var SCOPE_VALUE_MAX_LENGTH = 128;
|
|
18413
|
-
function valueGrammarError(value) {
|
|
18414
|
-
if (!SCOPE_VALUE_RE2.test(value)) {
|
|
18415
|
-
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.`;
|
|
18416
|
-
}
|
|
18417
|
-
return null;
|
|
18418
|
-
}
|
|
18419
|
-
function namespaceGrammarError(ns) {
|
|
18420
|
-
if (FORBIDDEN_SCOPE_NAMESPACES2.has(ns)) {
|
|
18421
|
-
return `'${ns}' is a reserved namespace and cannot be used as an ownership scope`;
|
|
18422
|
-
}
|
|
18423
|
-
if (!SCOPE_NAMESPACE_RE2.test(ns)) {
|
|
18424
|
-
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 '-'`;
|
|
18425
|
-
}
|
|
18426
|
-
return null;
|
|
18427
|
-
}
|
|
18428
|
-
function assertScopeEntries(entries, flag) {
|
|
18429
|
-
if (!entries?.length) return;
|
|
18430
|
-
for (const raw of entries) {
|
|
18431
|
-
const sep = raw.indexOf(":");
|
|
18432
|
-
if (sep <= 0 || sep === raw.length - 1) {
|
|
18433
|
-
throw new Error(`${flag} must be '<namespace>:<value>' (e.g. 'org:6ba7...'); got '${raw}'.`);
|
|
18434
|
-
}
|
|
18435
|
-
const nsError = namespaceGrammarError(raw.slice(0, sep));
|
|
18436
|
-
if (nsError) throw new Error(`${flag} namespace ${nsError}`);
|
|
18437
|
-
const valError = valueGrammarError(raw.slice(sep + 1));
|
|
18438
|
-
if (valError) throw new Error(`${flag} value ${valError}`);
|
|
18439
|
-
}
|
|
18440
|
-
}
|
|
18441
|
-
function assertIdentityOverrideValues(overrides, flag) {
|
|
18442
|
-
if (!overrides) return;
|
|
18443
|
-
for (const [key, value] of Object.entries(overrides)) {
|
|
18444
|
-
if (!key.startsWith("scope:")) continue;
|
|
18445
|
-
const error = valueGrammarError(typeof value === "string" ? value : String(value));
|
|
18446
|
-
if (error) throw new Error(`${flag} '${key}' value ${error}`);
|
|
18447
|
-
}
|
|
18448
|
-
}
|
|
18449
|
-
function placeholderScopeNamespace(value) {
|
|
18450
|
-
const self2 = /^\$\{\{\s*(?:under\.)?self\.scope\.([^\s}]+)\s*\}\}$/.exec(value);
|
|
18451
|
-
if (self2) return self2[1];
|
|
18452
|
-
const member = MEMBER_SCOPE_PLACEHOLDER_RE2.exec(value);
|
|
18453
|
-
return member ? member[1] : null;
|
|
18454
|
-
}
|
|
18455
|
-
var SELF_PLACEHOLDER_RE = /^\$\{\{\s*self\.(?:userId|partnerUserId|scope\.[a-z][a-z0-9_-]{1,31})\s*\}\}$/;
|
|
18456
|
-
var UNDER_SELF_PLACEHOLDER_RE2 = /^\$\{\{\s*under\.self\.(?:userId|partnerUserId|scope\.[a-z][a-z0-9_-]{1,31})\s*\}\}$/;
|
|
18457
|
-
var MEMBER_SCOPE_PLACEHOLDER_RE2 = /^\$\{\{\s*member\.scope\.([a-z][a-z0-9_-]{1,31})(?::[a-z][a-z0-9_-]{1,31})?\s*\}\}$/;
|
|
18458
|
-
|
|
18459
20496
|
// src/lint.ts
|
|
18460
20497
|
var ENUM_COMPATIBLE_TYPES = /* @__PURE__ */ new Set(["enum", "string", "string[]"]);
|
|
18461
20498
|
var BUILT_IN_SORT_BY = /* @__PURE__ */ new Set(["createdAt", "lastUpdated"]);
|
|
@@ -18604,7 +20641,7 @@ function lintBlueprint(bp) {
|
|
|
18604
20641
|
);
|
|
18605
20642
|
}
|
|
18606
20643
|
for (const roleId of Object.keys(bp.roles ?? {})) {
|
|
18607
|
-
if (!
|
|
20644
|
+
if (!ROLE_ID_RE3.test(roleId)) {
|
|
18608
20645
|
findings.push({
|
|
18609
20646
|
severity: "error",
|
|
18610
20647
|
rule: "role-id-format",
|
|
@@ -18651,7 +20688,7 @@ function lintBlueprint(bp) {
|
|
|
18651
20688
|
}
|
|
18652
20689
|
return findings;
|
|
18653
20690
|
}
|
|
18654
|
-
var
|
|
20691
|
+
var ROLE_ID_RE3 = /^[a-z][a-z0-9-]{2,30}$/;
|
|
18655
20692
|
var PLACEHOLDER_SHAPED_RE = /^\s*\$\{\{.*\}\}\s*$/s;
|
|
18656
20693
|
var RECOGNIZED_PLACEHOLDER_RES = [
|
|
18657
20694
|
/^\$\{\{\s*any\s*\}\}$/,
|
|
@@ -18917,14 +20954,14 @@ async function resolveAndValidate(arg, verb, supplied) {
|
|
|
18917
20954
|
if (err instanceof ScopeGateError) {
|
|
18918
20955
|
process.stderr.write(` \u2716 Scope gate rejected '${blueprint.name}':
|
|
18919
20956
|
`);
|
|
18920
|
-
for (const r of err.rejected) process.stderr.write(` \u2022 ${r
|
|
20957
|
+
for (const r of err.rejected) process.stderr.write(` \u2022 ${formatRejection(r)}
|
|
18921
20958
|
`);
|
|
18922
20959
|
return null;
|
|
18923
20960
|
}
|
|
18924
20961
|
if (err instanceof CapabilityGateError) {
|
|
18925
20962
|
process.stderr.write(` \u2716 Capability gate rejected '${blueprint.name}':
|
|
18926
20963
|
`);
|
|
18927
|
-
for (const r of err.rejected) process.stderr.write(` \u2022 ${r
|
|
20964
|
+
for (const r of err.rejected) process.stderr.write(` \u2022 ${formatRejection(r)}
|
|
18928
20965
|
`);
|
|
18929
20966
|
return null;
|
|
18930
20967
|
}
|
|
@@ -19068,137 +21105,44 @@ async function runBlueprintCli(argv, deps = {}) {
|
|
|
19068
21105
|
args = parseBlueprintArgs(argv);
|
|
19069
21106
|
} catch (err) {
|
|
19070
21107
|
process.stderr.write(`${err instanceof Error ? err.message : String(err)}
|
|
19071
|
-
|
|
19072
|
-
${BLUEPRINT_HELP}
|
|
19073
|
-
`);
|
|
19074
|
-
return 1;
|
|
19075
|
-
}
|
|
19076
|
-
if (args.help) {
|
|
19077
|
-
process.stdout.write(`${BLUEPRINT_HELP}
|
|
19078
|
-
`);
|
|
19079
|
-
return 0;
|
|
19080
|
-
}
|
|
19081
|
-
if (!args.sub) {
|
|
19082
|
-
process.stdout.write(`${BLUEPRINT_HELP}
|
|
19083
|
-
`);
|
|
19084
|
-
return 1;
|
|
19085
|
-
}
|
|
19086
|
-
switch (args.sub) {
|
|
19087
|
-
case "init":
|
|
19088
|
-
return runInit(args);
|
|
19089
|
-
case "validate":
|
|
19090
|
-
return runValidate(args);
|
|
19091
|
-
case "plan":
|
|
19092
|
-
return runPlan(args);
|
|
19093
|
-
case "list":
|
|
19094
|
-
return runList();
|
|
19095
|
-
default:
|
|
19096
|
-
process.stderr.write(
|
|
19097
|
-
`Unknown blueprint subcommand: ${args.sub}. Bundled: ${BLUEPRINT_NAMES.join(", ")}
|
|
19098
|
-
|
|
19099
|
-
${BLUEPRINT_HELP}
|
|
19100
|
-
`
|
|
19101
|
-
);
|
|
19102
|
-
return 1;
|
|
19103
|
-
}
|
|
19104
|
-
}
|
|
19105
|
-
|
|
19106
|
-
// src/program.ts
|
|
19107
|
-
var import_commander = require("commander");
|
|
19108
|
-
|
|
19109
|
-
// src/verb-support.ts
|
|
19110
|
-
async function withAuth(opts, deps, body) {
|
|
19111
|
-
const log = deps.log ?? createLogger2();
|
|
19112
|
-
const resolveAuth = deps.resolveAuth ?? resolveAuthContext;
|
|
19113
|
-
let auth;
|
|
19114
|
-
try {
|
|
19115
|
-
auth = await resolveAuth({
|
|
19116
|
-
env: opts.env,
|
|
19117
|
-
baseUrl: opts.baseUrl,
|
|
19118
|
-
token: opts.token,
|
|
19119
|
-
context: opts.context,
|
|
19120
|
-
provisioning: opts.provisioning,
|
|
19121
|
-
tenant: opts.tenant,
|
|
19122
|
-
useScopedToken: opts.useScopedToken,
|
|
19123
|
-
log
|
|
19124
|
-
});
|
|
19125
|
-
} catch (err) {
|
|
19126
|
-
if (err instanceof MissingCredentialError) {
|
|
19127
|
-
process.stderr.write(` \u2716 ${err.message}
|
|
19128
|
-
`);
|
|
19129
|
-
return 2;
|
|
19130
|
-
}
|
|
19131
|
-
process.stderr.write(` \u2716 ${formatError(err)}
|
|
19132
|
-
`);
|
|
19133
|
-
return 1;
|
|
19134
|
-
}
|
|
19135
|
-
return body(auth, log);
|
|
19136
|
-
}
|
|
19137
|
-
function requireTenantId(auth) {
|
|
19138
|
-
if (!auth.tenantId) {
|
|
19139
|
-
throw new Error(
|
|
19140
|
-
"Could not resolve tenantId from /v1/ping \u2014 the extended ping (tenant_id) must be deployed."
|
|
19141
|
-
);
|
|
19142
|
-
}
|
|
19143
|
-
return auth.tenantId;
|
|
19144
|
-
}
|
|
19145
|
-
function normalizePrincipalUserId(principal) {
|
|
19146
|
-
return principal.startsWith("usr_") ? principal.slice("usr_".length) : principal;
|
|
19147
|
-
}
|
|
19148
|
-
function splitCsv(csv) {
|
|
19149
|
-
return csv.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
19150
|
-
}
|
|
19151
|
-
function parseScopeOverrides(scopes) {
|
|
19152
|
-
if (!scopes || scopes.length === 0) return void 0;
|
|
19153
|
-
const out = {};
|
|
19154
|
-
for (const raw of scopes) {
|
|
19155
|
-
const sep = raw.indexOf(":");
|
|
19156
|
-
if (sep <= 0 || sep === raw.length - 1) {
|
|
19157
|
-
throw new Error(`--scope must be '<namespace>:<value>' (e.g. 'org:6ba7...'); got '${raw}'.`);
|
|
19158
|
-
}
|
|
19159
|
-
const ns = raw.slice(0, sep);
|
|
19160
|
-
const value = raw.slice(sep + 1);
|
|
19161
|
-
const valueError = valueGrammarError(value);
|
|
19162
|
-
if (valueError) throw new Error(`--scope value ${valueError}`);
|
|
19163
|
-
const key = `scope:${ns}`;
|
|
19164
|
-
if (Object.prototype.hasOwnProperty.call(out, key)) {
|
|
19165
|
-
throw new Error(`--scope sets namespace '${ns}' more than once \u2014 a scope override takes at most one value per namespace.`);
|
|
19166
|
-
}
|
|
19167
|
-
out[key] = value;
|
|
19168
|
-
}
|
|
19169
|
-
if (Object.keys(out).length > 2) {
|
|
19170
|
-
throw new Error("--scope allows at most two distinct namespaces.");
|
|
19171
|
-
}
|
|
19172
|
-
return out;
|
|
19173
|
-
}
|
|
19174
|
-
function parseJsonObjectFlag(flag, raw) {
|
|
19175
|
-
let parsed;
|
|
19176
|
-
try {
|
|
19177
|
-
parsed = JSON.parse(raw);
|
|
19178
|
-
} catch {
|
|
19179
|
-
throw new Error(`${flag} is not valid JSON: ${raw}`);
|
|
21108
|
+
|
|
21109
|
+
${BLUEPRINT_HELP}
|
|
21110
|
+
`);
|
|
21111
|
+
return 1;
|
|
19180
21112
|
}
|
|
19181
|
-
if (
|
|
19182
|
-
|
|
21113
|
+
if (args.help) {
|
|
21114
|
+
process.stdout.write(`${BLUEPRINT_HELP}
|
|
21115
|
+
`);
|
|
21116
|
+
return 0;
|
|
19183
21117
|
}
|
|
19184
|
-
|
|
19185
|
-
}
|
|
21118
|
+
if (!args.sub) {
|
|
21119
|
+
process.stdout.write(`${BLUEPRINT_HELP}
|
|
21120
|
+
`);
|
|
21121
|
+
return 1;
|
|
21122
|
+
}
|
|
21123
|
+
switch (args.sub) {
|
|
21124
|
+
case "init":
|
|
21125
|
+
return runInit(args);
|
|
21126
|
+
case "validate":
|
|
21127
|
+
return runValidate(args);
|
|
21128
|
+
case "plan":
|
|
21129
|
+
return runPlan(args);
|
|
21130
|
+
case "list":
|
|
21131
|
+
return runList();
|
|
21132
|
+
default:
|
|
21133
|
+
process.stderr.write(
|
|
21134
|
+
`Unknown blueprint subcommand: ${args.sub}. Bundled: ${BLUEPRINT_NAMES.join(", ")}
|
|
19186
21135
|
|
|
19187
|
-
|
|
19188
|
-
|
|
19189
|
-
|
|
19190
|
-
|
|
19191
|
-
let startFrom;
|
|
19192
|
-
for (; ; ) {
|
|
19193
|
-
const page = await fetch2(startFrom);
|
|
19194
|
-
if (page.data) out.push(...page.data);
|
|
19195
|
-
const next = page.nextCursor;
|
|
19196
|
-
if (!next) break;
|
|
19197
|
-
startFrom = next;
|
|
21136
|
+
${BLUEPRINT_HELP}
|
|
21137
|
+
`
|
|
21138
|
+
);
|
|
21139
|
+
return 1;
|
|
19198
21140
|
}
|
|
19199
|
-
return out;
|
|
19200
21141
|
}
|
|
19201
21142
|
|
|
21143
|
+
// src/program.ts
|
|
21144
|
+
var import_commander = require("commander");
|
|
21145
|
+
|
|
19202
21146
|
// src/self-principal.ts
|
|
19203
21147
|
var PRINCIPAL_PREFIX_USER = "usr_";
|
|
19204
21148
|
function emailFromJwt(token) {
|
|
@@ -20216,7 +22160,14 @@ function normalizeClauses(raw) {
|
|
|
20216
22160
|
});
|
|
20217
22161
|
if (!isRecognizedOwnershipKey(k)) unrecognizedDims.push(k);
|
|
20218
22162
|
}
|
|
20219
|
-
|
|
22163
|
+
const rawAssignable = obj.assignable_roles ?? obj.assignableRoles;
|
|
22164
|
+
const assignableRoles = Array.isArray(rawAssignable) && rawAssignable.length > 0 ? rawAssignable.map(String) : void 0;
|
|
22165
|
+
out.push({
|
|
22166
|
+
actions: Array.isArray(actions) ? actions.map(String) : [],
|
|
22167
|
+
dataScope,
|
|
22168
|
+
unrecognizedDims,
|
|
22169
|
+
...assignableRoles ? { assignableRoles } : {}
|
|
22170
|
+
});
|
|
20220
22171
|
}
|
|
20221
22172
|
return out;
|
|
20222
22173
|
}
|
|
@@ -20416,7 +22367,10 @@ async function runAccessExplain(opts, deps = {}) {
|
|
|
20416
22367
|
dataScope: renderDataScope(c.dataScope),
|
|
20417
22368
|
// Present only on a dead clause — an unrecognized dim the platform
|
|
20418
22369
|
// denies (a retired key or a bad scope:<ns>); the matrix omits it.
|
|
20419
|
-
...c.unrecognizedDims.length > 0 ? { grantsNothing: true, unrecognizedDims: c.unrecognizedDims } : {}
|
|
22370
|
+
...c.unrecognizedDims.length > 0 ? { grantsNothing: true, unrecognizedDims: c.unrecognizedDims } : {},
|
|
22371
|
+
// Reported only when the clause actually carries the restriction. Absent means
|
|
22372
|
+
// default-open (any role composable), which is a different fact from an empty list.
|
|
22373
|
+
...c.assignableRoles?.length ? { assignableRoles: c.assignableRoles } : {}
|
|
20420
22374
|
})),
|
|
20421
22375
|
matrix: rows,
|
|
20422
22376
|
// Present only when a clause uses `${{ under.self.* }}`: those rows are UNDECIDED
|
|
@@ -20442,6 +22396,12 @@ async function runAccessExplain(opts, deps = {}) {
|
|
|
20442
22396
|
if (c.unrecognizedDims.length > 0) {
|
|
20443
22397
|
process.stdout.write(
|
|
20444
22398
|
` \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>)
|
|
22399
|
+
`
|
|
22400
|
+
);
|
|
22401
|
+
}
|
|
22402
|
+
if (c.assignableRoles?.length) {
|
|
22403
|
+
process.stdout.write(
|
|
22404
|
+
` may compose only these roles into a delegated profile: ${c.assignableRoles.join(", ")}
|
|
20445
22405
|
`
|
|
20446
22406
|
);
|
|
20447
22407
|
}
|
|
@@ -20507,6 +22467,14 @@ async function runRoleCreate(opts, deps = {}) {
|
|
|
20507
22467
|
return 2;
|
|
20508
22468
|
}
|
|
20509
22469
|
}
|
|
22470
|
+
let assignableRoles;
|
|
22471
|
+
try {
|
|
22472
|
+
assignableRoles = parseAssignableRoles(opts.assignableRole);
|
|
22473
|
+
} catch (err) {
|
|
22474
|
+
process.stderr.write(` \u2716 ${formatError(err)}
|
|
22475
|
+
`);
|
|
22476
|
+
return 2;
|
|
22477
|
+
}
|
|
20510
22478
|
return withAuth(opts, deps, async (auth) => {
|
|
20511
22479
|
const client = auth.client;
|
|
20512
22480
|
try {
|
|
@@ -20519,7 +22487,15 @@ async function runRoleCreate(opts, deps = {}) {
|
|
|
20519
22487
|
// data_scope is included only when supplied — the clause wire shape
|
|
20520
22488
|
// stays byte-identical for unscoped roles.
|
|
20521
22489
|
scopes: [
|
|
20522
|
-
|
|
22490
|
+
{
|
|
22491
|
+
allowed_actions: actions,
|
|
22492
|
+
// Each included only when supplied — the clause wire shape stays byte-identical for an
|
|
22493
|
+
// unscoped, unrestricted role. `assignable_roles` in particular must be OMITTED rather
|
|
22494
|
+
// than sent empty: the platform reads absent as "no roleId restriction" and rejects
|
|
22495
|
+
// `[]` outright, so the two are not interchangeable.
|
|
22496
|
+
...dataScope ? { data_scope: dataScope } : {},
|
|
22497
|
+
...assignableRoles ? { assignable_roles: assignableRoles } : {}
|
|
22498
|
+
}
|
|
20523
22499
|
],
|
|
20524
22500
|
// Included only when supplied — omitting it (rather than sending an
|
|
20525
22501
|
// empty object) keeps "no assume grant" and "explicitly empty grant"
|
|
@@ -20778,6 +22754,36 @@ function parseSelfSignupPolicies(raw) {
|
|
|
20778
22754
|
return { signup_type: entry.slice(0, sep), role_id: entry.slice(sep + 1) };
|
|
20779
22755
|
});
|
|
20780
22756
|
}
|
|
22757
|
+
var MAX_CAPTURED_CLAIMS2 = 20;
|
|
22758
|
+
var MAX_CAPTURED_CLAIM_NAME_LENGTH2 = 64;
|
|
22759
|
+
function parseCapturedClaims(raw) {
|
|
22760
|
+
if (!raw || raw.length === 0) return void 0;
|
|
22761
|
+
if (raw.length > MAX_CAPTURED_CLAIMS2) {
|
|
22762
|
+
throw new Error(
|
|
22763
|
+
`--captured-claim accepts at most ${MAX_CAPTURED_CLAIMS2} entries; got ${raw.length}.`
|
|
22764
|
+
);
|
|
22765
|
+
}
|
|
22766
|
+
const seen = /* @__PURE__ */ new Set();
|
|
22767
|
+
for (const name of raw) {
|
|
22768
|
+
if (name.trim().length === 0) {
|
|
22769
|
+
throw new Error("--captured-claim must be a non-blank OIDC claim name (e.g. 'phone_number').");
|
|
22770
|
+
}
|
|
22771
|
+
if (name.length > MAX_CAPTURED_CLAIM_NAME_LENGTH2) {
|
|
22772
|
+
throw new Error(
|
|
22773
|
+
`--captured-claim '${name}' is ${name.length} characters, above the ${MAX_CAPTURED_CLAIM_NAME_LENGTH2}-character limit.`
|
|
22774
|
+
);
|
|
22775
|
+
}
|
|
22776
|
+
if (seen.has(name)) {
|
|
22777
|
+
throw new Error(`--captured-claim repeats '${name}' \u2014 each claim name may be given only once.`);
|
|
22778
|
+
}
|
|
22779
|
+
seen.add(name);
|
|
22780
|
+
}
|
|
22781
|
+
return [...raw];
|
|
22782
|
+
}
|
|
22783
|
+
function renderCapturedClaims(claims) {
|
|
22784
|
+
if (!claims || claims.length === 0) return "\u2014";
|
|
22785
|
+
return claims.join(", ");
|
|
22786
|
+
}
|
|
20781
22787
|
function renderSelfSignup(policies) {
|
|
20782
22788
|
if (!policies || policies.length === 0) return "\u2014";
|
|
20783
22789
|
return policies.map((p) => `${p.signup_type} \u2192 ${p.role_id}`).join(", ");
|
|
@@ -20794,7 +22800,8 @@ function renderIssuer(i) {
|
|
|
20794
22800
|
["userinfoUri", i.userinfoUri],
|
|
20795
22801
|
["status", i.status],
|
|
20796
22802
|
["createdAt", i.createdAt],
|
|
20797
|
-
["selfSignup", renderSelfSignup(i.selfSignupPolicies)]
|
|
22803
|
+
["selfSignup", renderSelfSignup(i.selfSignupPolicies)],
|
|
22804
|
+
["capturedClaims", renderCapturedClaims(i.capturedClaims)]
|
|
20798
22805
|
]);
|
|
20799
22806
|
}
|
|
20800
22807
|
async function runIssuersList(opts, deps = {}) {
|
|
@@ -20815,7 +22822,8 @@ async function runIssuersList(opts, deps = {}) {
|
|
|
20815
22822
|
{ header: "issuer", get: (i) => i.issuer },
|
|
20816
22823
|
{ header: "audience", get: (i) => i.audience },
|
|
20817
22824
|
{ header: "status", get: (i) => i.status },
|
|
20818
|
-
{ header: "self-signup", get: (i) => renderSelfSignup(i.selfSignupPolicies) }
|
|
22825
|
+
{ header: "self-signup", get: (i) => renderSelfSignup(i.selfSignupPolicies) },
|
|
22826
|
+
{ header: "captured claims", get: (i) => renderCapturedClaims(i.capturedClaims) }
|
|
20819
22827
|
]) + "\n"
|
|
20820
22828
|
);
|
|
20821
22829
|
return 0;
|
|
@@ -20847,8 +22855,10 @@ ${renderIssuer(issuer)}
|
|
|
20847
22855
|
}
|
|
20848
22856
|
async function runIssuersRegister(opts, deps = {}) {
|
|
20849
22857
|
let selfSignupPolicies;
|
|
22858
|
+
let capturedClaims;
|
|
20850
22859
|
try {
|
|
20851
22860
|
selfSignupPolicies = parseSelfSignupPolicies(opts.selfSignupPolicy);
|
|
22861
|
+
capturedClaims = parseCapturedClaims(opts.capturedClaim);
|
|
20852
22862
|
} catch (err) {
|
|
20853
22863
|
process.stderr.write(` \u2716 ${formatError(err)}
|
|
20854
22864
|
`);
|
|
@@ -20866,7 +22876,8 @@ async function runIssuersRegister(opts, deps = {}) {
|
|
|
20866
22876
|
subClaim: opts.subClaim,
|
|
20867
22877
|
emailClaim: opts.emailClaim,
|
|
20868
22878
|
userinfoUri: opts.userinfoUri,
|
|
20869
|
-
selfSignupPolicies
|
|
22879
|
+
selfSignupPolicies,
|
|
22880
|
+
capturedClaims
|
|
20870
22881
|
});
|
|
20871
22882
|
if (opts.json) {
|
|
20872
22883
|
process.stdout.write(`${formatJson(issuer)}
|
|
@@ -20887,8 +22898,10 @@ ${renderIssuer(issuer)}
|
|
|
20887
22898
|
}
|
|
20888
22899
|
async function runIssuersUpdate(opts, deps = {}) {
|
|
20889
22900
|
let selfSignupPolicies;
|
|
22901
|
+
let capturedClaims;
|
|
20890
22902
|
try {
|
|
20891
22903
|
selfSignupPolicies = parseSelfSignupPolicies(opts.selfSignupPolicy);
|
|
22904
|
+
capturedClaims = parseCapturedClaims(opts.capturedClaim);
|
|
20892
22905
|
} catch (err) {
|
|
20893
22906
|
process.stderr.write(` \u2716 ${formatError(err)}
|
|
20894
22907
|
`);
|
|
@@ -20900,6 +22913,12 @@ async function runIssuersUpdate(opts, deps = {}) {
|
|
|
20900
22913
|
);
|
|
20901
22914
|
return 2;
|
|
20902
22915
|
}
|
|
22916
|
+
if (opts.clearCapturedClaims && capturedClaims && capturedClaims.length > 0) {
|
|
22917
|
+
process.stderr.write(
|
|
22918
|
+
" \u2716 --clear-captured-claims and --captured-claim are mutually exclusive.\n"
|
|
22919
|
+
);
|
|
22920
|
+
return 2;
|
|
22921
|
+
}
|
|
20903
22922
|
return withAuth({ ...opts, provisioning: true, context: opts.context }, deps, async (auth) => {
|
|
20904
22923
|
const client = auth.client;
|
|
20905
22924
|
try {
|
|
@@ -20909,7 +22928,8 @@ async function runIssuersUpdate(opts, deps = {}) {
|
|
|
20909
22928
|
emailClaim: opts.emailClaim,
|
|
20910
22929
|
userinfoUri: opts.userinfoUri,
|
|
20911
22930
|
status: opts.status,
|
|
20912
|
-
selfSignupPolicies: opts.clearSelfSignupPolicies ? [] : selfSignupPolicies
|
|
22931
|
+
selfSignupPolicies: opts.clearSelfSignupPolicies ? [] : selfSignupPolicies,
|
|
22932
|
+
capturedClaims: opts.clearCapturedClaims ? [] : capturedClaims
|
|
20913
22933
|
});
|
|
20914
22934
|
if (opts.json) {
|
|
20915
22935
|
process.stdout.write(`${formatJson(issuer)}
|
|
@@ -20968,41 +22988,264 @@ async function runIssuersDelete(opts, deps = {}) {
|
|
|
20968
22988
|
try {
|
|
20969
22989
|
await client.auth.deleteIssuer({ issuerId });
|
|
20970
22990
|
if (opts.json) {
|
|
20971
|
-
process.stdout.write(`${formatJson({ issuerId, deleted: true })}
|
|
22991
|
+
process.stdout.write(`${formatJson({ issuerId, deleted: true })}
|
|
22992
|
+
`);
|
|
22993
|
+
return 0;
|
|
22994
|
+
}
|
|
22995
|
+
process.stdout.write(`
|
|
22996
|
+
\u2714 issuer '${issuerId}' deregistered from '${context}'
|
|
22997
|
+
`);
|
|
22998
|
+
return 0;
|
|
22999
|
+
} catch (err) {
|
|
23000
|
+
process.stderr.write(formatCliError(err, { action: "issuers delete failed" }));
|
|
23001
|
+
return 1;
|
|
23002
|
+
}
|
|
23003
|
+
});
|
|
23004
|
+
}
|
|
23005
|
+
|
|
23006
|
+
// src/commands/namespace.ts
|
|
23007
|
+
function renderNamespace(n) {
|
|
23008
|
+
return formatKeyValues([
|
|
23009
|
+
["namespace", n.namespace],
|
|
23010
|
+
["contextId", n.contextId ?? "(tenant-wide)"],
|
|
23011
|
+
["entityBacked", n.entityBacked === void 0 ? void 0 : String(n.entityBacked)],
|
|
23012
|
+
["reserved", n.reserved === void 0 ? void 0 : String(n.reserved)],
|
|
23013
|
+
["specificityRank", n.specificityRank === void 0 ? void 0 : String(n.specificityRank)],
|
|
23014
|
+
["defaultSchemaId", n.defaultSchemaId],
|
|
23015
|
+
["createdAt", n.createdAt]
|
|
23016
|
+
]);
|
|
23017
|
+
}
|
|
23018
|
+
async function runNamespaceList(opts, deps = {}) {
|
|
23019
|
+
return withAuth({ ...opts, context: opts.context }, deps, async (auth) => {
|
|
23020
|
+
const client = auth.client;
|
|
23021
|
+
try {
|
|
23022
|
+
const rows = await drainPages(
|
|
23023
|
+
(startFrom) => client.identity.listNamespaces({
|
|
23024
|
+
limit: PAGE_LIMIT,
|
|
23025
|
+
...opts.context ? { contextId: opts.context } : {},
|
|
23026
|
+
...startFrom ? { startFrom } : {}
|
|
23027
|
+
})
|
|
23028
|
+
);
|
|
23029
|
+
if (opts.json) {
|
|
23030
|
+
process.stdout.write(`${formatJson(rows)}
|
|
23031
|
+
`);
|
|
23032
|
+
return 0;
|
|
23033
|
+
}
|
|
23034
|
+
process.stdout.write(
|
|
23035
|
+
"\n" + formatTable(rows, [
|
|
23036
|
+
{ header: "namespace", get: (n) => n.namespace },
|
|
23037
|
+
{ header: "contextId", get: (n) => n.contextId ?? "(tenant-wide)" },
|
|
23038
|
+
{
|
|
23039
|
+
header: "entityBacked",
|
|
23040
|
+
get: (n) => n.entityBacked === void 0 ? void 0 : String(n.entityBacked)
|
|
23041
|
+
},
|
|
23042
|
+
{
|
|
23043
|
+
header: "specificityRank",
|
|
23044
|
+
get: (n) => n.specificityRank === void 0 ? void 0 : String(n.specificityRank)
|
|
23045
|
+
}
|
|
23046
|
+
]) + "\n"
|
|
23047
|
+
);
|
|
23048
|
+
return 0;
|
|
23049
|
+
} catch (err) {
|
|
23050
|
+
process.stderr.write(formatCliError(err, { action: "namespace list failed" }));
|
|
23051
|
+
return 1;
|
|
23052
|
+
}
|
|
23053
|
+
});
|
|
23054
|
+
}
|
|
23055
|
+
async function runNamespaceGet(opts, deps = {}) {
|
|
23056
|
+
return withAuth({ ...opts, context: opts.context }, deps, async (auth) => {
|
|
23057
|
+
const client = auth.client;
|
|
23058
|
+
try {
|
|
23059
|
+
const ns = await client.identity.getNamespace({
|
|
23060
|
+
namespace: opts.namespace,
|
|
23061
|
+
...opts.context ? { contextId: opts.context } : {}
|
|
23062
|
+
});
|
|
23063
|
+
if (opts.json) {
|
|
23064
|
+
process.stdout.write(`${formatJson(ns)}
|
|
23065
|
+
`);
|
|
23066
|
+
return 0;
|
|
23067
|
+
}
|
|
23068
|
+
process.stdout.write(`
|
|
23069
|
+
${renderNamespace(ns)}
|
|
23070
|
+
`);
|
|
23071
|
+
return 0;
|
|
23072
|
+
} catch (err) {
|
|
23073
|
+
process.stderr.write(formatCliError(err, { action: "namespace get failed" }));
|
|
23074
|
+
return 1;
|
|
23075
|
+
}
|
|
23076
|
+
});
|
|
23077
|
+
}
|
|
23078
|
+
async function runNamespaceRegister(opts, deps = {}) {
|
|
23079
|
+
return withAuth({ ...opts, provisioning: true, context: opts.context }, deps, async (auth) => {
|
|
23080
|
+
const client = auth.client;
|
|
23081
|
+
try {
|
|
23082
|
+
const ns = await client.identity.registerNamespace({
|
|
23083
|
+
contextId: opts.context,
|
|
23084
|
+
body: {
|
|
23085
|
+
namespace: opts.namespace,
|
|
23086
|
+
specificityRank: opts.specificityRank,
|
|
23087
|
+
entityBacked: opts.entityBacked,
|
|
23088
|
+
defaultSchemaId: opts.defaultSchemaId
|
|
23089
|
+
}
|
|
23090
|
+
});
|
|
23091
|
+
if (opts.json) {
|
|
23092
|
+
process.stdout.write(`${formatJson(ns)}
|
|
23093
|
+
`);
|
|
23094
|
+
return 0;
|
|
23095
|
+
}
|
|
23096
|
+
process.stdout.write(`
|
|
23097
|
+
\u2714 namespace '${opts.namespace}' registered
|
|
23098
|
+
${renderNamespace(ns)}
|
|
23099
|
+
`);
|
|
23100
|
+
return 0;
|
|
23101
|
+
} catch (err) {
|
|
23102
|
+
process.stderr.write(formatCliError(err, { action: "namespace register failed" }));
|
|
23103
|
+
return 1;
|
|
23104
|
+
}
|
|
23105
|
+
});
|
|
23106
|
+
}
|
|
23107
|
+
|
|
23108
|
+
// src/commands/scripts.ts
|
|
23109
|
+
var import_promises4 = require("fs/promises");
|
|
23110
|
+
var readline5 = __toESM(require("readline/promises"));
|
|
23111
|
+
var SOURCE_MAX_BYTES = 3e5;
|
|
23112
|
+
var INPUT_CONTRACT_MAX_BYTES = 5e4;
|
|
23113
|
+
var utf8Bytes2 = (s) => new TextEncoder().encode(s).length;
|
|
23114
|
+
var SCRIPT_NAME_RE2 = /^[A-Za-z0-9_-]{1,64}$/;
|
|
23115
|
+
function renderScript(s, opts = {}) {
|
|
23116
|
+
return formatKeyValues([
|
|
23117
|
+
["id", s.id],
|
|
23118
|
+
["name", s.name],
|
|
23119
|
+
["scriptVersion", s.scriptVersion === void 0 ? void 0 : String(s.scriptVersion)],
|
|
23120
|
+
["declaredInputContract", s.declaredInputContract],
|
|
23121
|
+
["createdAt", s.createdAt],
|
|
23122
|
+
// Full source last, and only where it was asked for — it can be 300 KB.
|
|
23123
|
+
...opts.source && s.source !== void 0 ? [["source", s.source]] : []
|
|
23124
|
+
]);
|
|
23125
|
+
}
|
|
23126
|
+
async function runScriptsPush(opts, deps = {}) {
|
|
23127
|
+
if (!SCRIPT_NAME_RE2.test(opts.name)) {
|
|
23128
|
+
process.stderr.write(
|
|
23129
|
+
` \u2716 '${opts.name}' is not a valid script name \u2014 use 1-64 characters of letters, digits, underscores ('_') and hyphens ('-').
|
|
23130
|
+
`
|
|
23131
|
+
);
|
|
23132
|
+
return 2;
|
|
23133
|
+
}
|
|
23134
|
+
if (opts.inputContract !== void 0 && opts.inputContractFile !== void 0) {
|
|
23135
|
+
process.stderr.write(" \u2716 --input-contract and --input-contract-file are mutually exclusive.\n");
|
|
23136
|
+
return 2;
|
|
23137
|
+
}
|
|
23138
|
+
let source;
|
|
23139
|
+
try {
|
|
23140
|
+
source = await (0, import_promises4.readFile)(opts.sourceFile, "utf8");
|
|
23141
|
+
} catch (err) {
|
|
23142
|
+
process.stderr.write(` \u2716 could not read --source-file '${opts.sourceFile}': ${errText(err)}
|
|
23143
|
+
`);
|
|
23144
|
+
return 2;
|
|
23145
|
+
}
|
|
23146
|
+
if (source.trim().length === 0) {
|
|
23147
|
+
process.stderr.write(` \u2716 --source-file '${opts.sourceFile}' is empty \u2014 'source' must not be blank.
|
|
23148
|
+
`);
|
|
23149
|
+
return 2;
|
|
23150
|
+
}
|
|
23151
|
+
if (opts.inputContract !== void 0 && opts.inputContract.trim().length === 0) {
|
|
23152
|
+
process.stderr.write(
|
|
23153
|
+
` \u2716 --input-contract is blank. Omit the flag to store no input contract at all \u2014 an empty one is not a contract.
|
|
23154
|
+
`
|
|
23155
|
+
);
|
|
23156
|
+
return 2;
|
|
23157
|
+
}
|
|
23158
|
+
let declaredInputContract = opts.inputContract;
|
|
23159
|
+
if (opts.inputContractFile !== void 0) {
|
|
23160
|
+
try {
|
|
23161
|
+
declaredInputContract = await (0, import_promises4.readFile)(opts.inputContractFile, "utf8");
|
|
23162
|
+
} catch (err) {
|
|
23163
|
+
process.stderr.write(
|
|
23164
|
+
` \u2716 could not read --input-contract-file '${opts.inputContractFile}': ${errText(err)}
|
|
23165
|
+
`
|
|
23166
|
+
);
|
|
23167
|
+
return 2;
|
|
23168
|
+
}
|
|
23169
|
+
if (declaredInputContract.trim().length === 0) {
|
|
23170
|
+
process.stderr.write(
|
|
23171
|
+
` \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.
|
|
23172
|
+
`
|
|
23173
|
+
);
|
|
23174
|
+
return 2;
|
|
23175
|
+
}
|
|
23176
|
+
}
|
|
23177
|
+
const sourceBytes = utf8Bytes2(source);
|
|
23178
|
+
if (sourceBytes > SOURCE_MAX_BYTES) {
|
|
23179
|
+
process.stderr.write(
|
|
23180
|
+
` \u2716 source is ${sourceBytes.toLocaleString("en-US")} UTF-8 bytes, over the ${SOURCE_MAX_BYTES.toLocaleString("en-US")}-byte limit.
|
|
23181
|
+
`
|
|
23182
|
+
);
|
|
23183
|
+
return 2;
|
|
23184
|
+
}
|
|
23185
|
+
if (declaredInputContract !== void 0 && utf8Bytes2(declaredInputContract) > INPUT_CONTRACT_MAX_BYTES) {
|
|
23186
|
+
process.stderr.write(
|
|
23187
|
+
` \u2716 input contract is ${utf8Bytes2(declaredInputContract).toLocaleString("en-US")} UTF-8 bytes, over the ${INPUT_CONTRACT_MAX_BYTES.toLocaleString("en-US")}-byte limit.
|
|
23188
|
+
`
|
|
23189
|
+
);
|
|
23190
|
+
return 2;
|
|
23191
|
+
}
|
|
23192
|
+
return withAuth(opts, deps, async (auth) => {
|
|
23193
|
+
const client = auth.client;
|
|
23194
|
+
try {
|
|
23195
|
+
if (opts.ifChanged) {
|
|
23196
|
+
const latest = await readLatestScriptVersion(
|
|
23197
|
+
client,
|
|
23198
|
+
opts.name
|
|
23199
|
+
);
|
|
23200
|
+
if (latest !== void 0 && scriptMatches(latest, { source, declaredInputContract })) {
|
|
23201
|
+
if (opts.json) {
|
|
23202
|
+
process.stdout.write(`${formatJson({ ...latest, pushed: false })}
|
|
23203
|
+
`);
|
|
23204
|
+
return 0;
|
|
23205
|
+
}
|
|
23206
|
+
process.stdout.write(
|
|
23207
|
+
`
|
|
23208
|
+
= script '${opts.name}' already matches at version ${latest.scriptVersion} \u2014 nothing pushed.
|
|
23209
|
+
`
|
|
23210
|
+
);
|
|
23211
|
+
return 0;
|
|
23212
|
+
}
|
|
23213
|
+
}
|
|
23214
|
+
const pushed = await client.scripts.createScript({
|
|
23215
|
+
name: opts.name,
|
|
23216
|
+
source,
|
|
23217
|
+
declaredInputContract
|
|
23218
|
+
});
|
|
23219
|
+
if (opts.json) {
|
|
23220
|
+
process.stdout.write(`${formatJson({ ...pushed, pushed: true })}
|
|
20972
23221
|
`);
|
|
20973
23222
|
return 0;
|
|
20974
23223
|
}
|
|
20975
|
-
process.stdout.write(
|
|
20976
|
-
|
|
20977
|
-
|
|
23224
|
+
process.stdout.write(
|
|
23225
|
+
`
|
|
23226
|
+
\u2714 pushed script '${opts.name}' version ${pushed.scriptVersion} (${sourceBytes.toLocaleString("en-US")} bytes) to '${opts.context}'
|
|
23227
|
+
${renderScript(pushed)}
|
|
23228
|
+
A trigger rule reaches this with scriptRef { name: '${opts.name}', version: 'latest' }.
|
|
23229
|
+
Pinning version '${pushed.scriptVersion}' instead keeps that rule on exactly this one \u2014
|
|
23230
|
+
but only for a script pushed this way. A blueprint that SHIPS a script requires 'latest'
|
|
23231
|
+
for it, since the blueprint cannot know which number its source will land on.
|
|
23232
|
+
`
|
|
23233
|
+
);
|
|
20978
23234
|
return 0;
|
|
20979
23235
|
} catch (err) {
|
|
20980
|
-
process.stderr.write(formatCliError(err, { action: "
|
|
23236
|
+
process.stderr.write(formatCliError(err, { action: "scripts push failed" }));
|
|
20981
23237
|
return 1;
|
|
20982
23238
|
}
|
|
20983
23239
|
});
|
|
20984
23240
|
}
|
|
20985
|
-
|
|
20986
|
-
|
|
20987
|
-
function renderNamespace(n) {
|
|
20988
|
-
return formatKeyValues([
|
|
20989
|
-
["namespace", n.namespace],
|
|
20990
|
-
["contextId", n.contextId ?? "(tenant-wide)"],
|
|
20991
|
-
["entityBacked", n.entityBacked === void 0 ? void 0 : String(n.entityBacked)],
|
|
20992
|
-
["reserved", n.reserved === void 0 ? void 0 : String(n.reserved)],
|
|
20993
|
-
["specificityRank", n.specificityRank === void 0 ? void 0 : String(n.specificityRank)],
|
|
20994
|
-
["defaultSchemaId", n.defaultSchemaId],
|
|
20995
|
-
["createdAt", n.createdAt]
|
|
20996
|
-
]);
|
|
20997
|
-
}
|
|
20998
|
-
async function runNamespaceList(opts, deps = {}) {
|
|
20999
|
-
return withAuth({ ...opts, context: opts.context }, deps, async (auth) => {
|
|
23241
|
+
async function runScriptsList(opts, deps = {}) {
|
|
23242
|
+
return withAuth(opts, deps, async (auth) => {
|
|
21000
23243
|
const client = auth.client;
|
|
21001
23244
|
try {
|
|
21002
23245
|
const rows = await drainPages(
|
|
21003
|
-
(startFrom) => client.
|
|
23246
|
+
(startFrom) => client.scripts.listScripts({
|
|
21004
23247
|
limit: PAGE_LIMIT,
|
|
21005
|
-
...opts.
|
|
23248
|
+
...opts.name ? { name: opts.name } : {},
|
|
21006
23249
|
...startFrom ? { startFrom } : {}
|
|
21007
23250
|
})
|
|
21008
23251
|
);
|
|
@@ -21011,79 +23254,113 @@ async function runNamespaceList(opts, deps = {}) {
|
|
|
21011
23254
|
`);
|
|
21012
23255
|
return 0;
|
|
21013
23256
|
}
|
|
23257
|
+
if (rows.length === 0) {
|
|
23258
|
+
process.stdout.write(
|
|
23259
|
+
opts.name ? `
|
|
23260
|
+
No versions of script '${opts.name}' in '${opts.context}'.
|
|
23261
|
+
` : `
|
|
23262
|
+
No scripts in '${opts.context}'.
|
|
23263
|
+
`
|
|
23264
|
+
);
|
|
23265
|
+
return 0;
|
|
23266
|
+
}
|
|
21014
23267
|
process.stdout.write(
|
|
21015
23268
|
"\n" + formatTable(rows, [
|
|
21016
|
-
{ header: "
|
|
21017
|
-
{ header: "
|
|
21018
|
-
{
|
|
21019
|
-
|
|
21020
|
-
|
|
21021
|
-
},
|
|
21022
|
-
{
|
|
21023
|
-
header: "specificityRank",
|
|
21024
|
-
get: (n) => n.specificityRank === void 0 ? void 0 : String(n.specificityRank)
|
|
21025
|
-
}
|
|
23269
|
+
{ header: "name", get: (s) => s.name },
|
|
23270
|
+
{ header: "version", get: (s) => s.scriptVersion === void 0 ? void 0 : String(s.scriptVersion) },
|
|
23271
|
+
{ header: "id", get: (s) => s.id },
|
|
23272
|
+
{ header: "bytes", get: (s) => s.source === void 0 ? void 0 : String(utf8Bytes2(s.source)) },
|
|
23273
|
+
{ header: "createdAt", get: (s) => s.createdAt }
|
|
21026
23274
|
]) + "\n"
|
|
21027
23275
|
);
|
|
21028
23276
|
return 0;
|
|
21029
23277
|
} catch (err) {
|
|
21030
|
-
process.stderr.write(formatCliError(err, { action: "
|
|
23278
|
+
process.stderr.write(formatCliError(err, { action: "scripts list failed" }));
|
|
21031
23279
|
return 1;
|
|
21032
23280
|
}
|
|
21033
23281
|
});
|
|
21034
23282
|
}
|
|
21035
|
-
async function
|
|
21036
|
-
return withAuth(
|
|
23283
|
+
async function runScriptsGet(opts, deps = {}) {
|
|
23284
|
+
return withAuth(opts, deps, async (auth) => {
|
|
21037
23285
|
const client = auth.client;
|
|
21038
23286
|
try {
|
|
21039
|
-
const
|
|
21040
|
-
namespace: opts.namespace,
|
|
21041
|
-
...opts.context ? { contextId: opts.context } : {}
|
|
21042
|
-
});
|
|
23287
|
+
const script = await client.scripts.getScript({ id: opts.id });
|
|
21043
23288
|
if (opts.json) {
|
|
21044
|
-
process.stdout.write(`${formatJson(
|
|
23289
|
+
process.stdout.write(`${formatJson(script)}
|
|
21045
23290
|
`);
|
|
21046
23291
|
return 0;
|
|
21047
23292
|
}
|
|
21048
23293
|
process.stdout.write(`
|
|
21049
|
-
${
|
|
23294
|
+
${renderScript(script, { source: true })}
|
|
21050
23295
|
`);
|
|
21051
23296
|
return 0;
|
|
21052
23297
|
} catch (err) {
|
|
21053
|
-
process.stderr.write(formatCliError(err, { action: "
|
|
23298
|
+
process.stderr.write(formatCliError(err, { action: "scripts get failed" }));
|
|
21054
23299
|
return 1;
|
|
21055
23300
|
}
|
|
21056
23301
|
});
|
|
21057
23302
|
}
|
|
21058
|
-
async function
|
|
21059
|
-
|
|
23303
|
+
async function runScriptsDelete(opts, deps = {}) {
|
|
23304
|
+
process.stderr.write(
|
|
23305
|
+
`
|
|
23306
|
+
\u26A0 Deleting script version '${opts.id}' is permanent \u2014 the stored source is the only copy.
|
|
23307
|
+
Other versions of the same name are unaffected.
|
|
23308
|
+
Nothing checks whether a trigger rule still references this version. A rule pinned to it
|
|
23309
|
+
will fail on every firing; a rule on 'latest' will silently fall back to the previous
|
|
23310
|
+
version and keep running older code. This CLI cannot list trigger rules, so confirm
|
|
23311
|
+
against the API or the portal before removing a version a rule may still use.
|
|
23312
|
+
And if this is the name's LATEST version, its identity is REUSED: the next push of that
|
|
23313
|
+
name recomputes the same version number and the same id, so anything holding a pinned
|
|
23314
|
+
'${opts.id}' or a name@version reference will silently resolve to different source.
|
|
23315
|
+
|
|
23316
|
+
`
|
|
23317
|
+
);
|
|
23318
|
+
if (!opts.force) {
|
|
23319
|
+
const isTty = deps.isTty ?? Boolean(process.stdin.isTTY);
|
|
23320
|
+
if (!isTty) {
|
|
23321
|
+
process.stderr.write(
|
|
23322
|
+
` \u2716 scripts delete is irreversible and needs an interactive confirmation.
|
|
23323
|
+
Re-run with --force to delete '${opts.id}' without a prompt.
|
|
23324
|
+
`
|
|
23325
|
+
);
|
|
23326
|
+
return 2;
|
|
23327
|
+
}
|
|
23328
|
+
const promptCtx = deps.createPrompt ? deps.createPrompt() : (() => {
|
|
23329
|
+
const rl = readline5.createInterface({ input: process.stdin, output: process.stdout });
|
|
23330
|
+
return { prompt: (q) => rl.question(q), close: () => rl.close() };
|
|
23331
|
+
})();
|
|
23332
|
+
try {
|
|
23333
|
+
const typed = await promptCtx.prompt(` Type the version id ('${opts.id}') to confirm: `);
|
|
23334
|
+
if (typed.trim() !== opts.id) {
|
|
23335
|
+
process.stderr.write(` \u2716 Confirmation did not match \u2014 nothing deleted.
|
|
23336
|
+
`);
|
|
23337
|
+
return 1;
|
|
23338
|
+
}
|
|
23339
|
+
} finally {
|
|
23340
|
+
promptCtx.close();
|
|
23341
|
+
}
|
|
23342
|
+
}
|
|
23343
|
+
return withAuth(opts, deps, async (auth) => {
|
|
21060
23344
|
const client = auth.client;
|
|
21061
23345
|
try {
|
|
21062
|
-
|
|
21063
|
-
contextId: opts.context,
|
|
21064
|
-
body: {
|
|
21065
|
-
namespace: opts.namespace,
|
|
21066
|
-
specificityRank: opts.specificityRank,
|
|
21067
|
-
entityBacked: opts.entityBacked,
|
|
21068
|
-
defaultSchemaId: opts.defaultSchemaId
|
|
21069
|
-
}
|
|
21070
|
-
});
|
|
23346
|
+
await client.scripts.deleteScript({ id: opts.id });
|
|
21071
23347
|
if (opts.json) {
|
|
21072
|
-
process.stdout.write(`${formatJson(
|
|
23348
|
+
process.stdout.write(`${formatJson({ id: opts.id, deleted: true })}
|
|
21073
23349
|
`);
|
|
21074
23350
|
return 0;
|
|
21075
23351
|
}
|
|
21076
|
-
process.stdout.write(`
|
|
21077
|
-
\u2714 namespace '${opts.namespace}' registered
|
|
21078
|
-
${renderNamespace(ns)}
|
|
23352
|
+
process.stdout.write(` \u2714 Deleted script version '${opts.id}' from '${opts.context}'.
|
|
21079
23353
|
`);
|
|
21080
23354
|
return 0;
|
|
21081
23355
|
} catch (err) {
|
|
21082
|
-
process.stderr.write(formatCliError(err, { action: "
|
|
23356
|
+
process.stderr.write(formatCliError(err, { action: "scripts delete failed" }));
|
|
21083
23357
|
return 1;
|
|
21084
23358
|
}
|
|
21085
23359
|
});
|
|
21086
23360
|
}
|
|
23361
|
+
function errText(err) {
|
|
23362
|
+
return err instanceof Error ? err.message : String(err);
|
|
23363
|
+
}
|
|
21087
23364
|
|
|
21088
23365
|
// src/commands/assume.ts
|
|
21089
23366
|
function parseAssumeScopes(raw) {
|
|
@@ -21106,11 +23383,20 @@ function parseAssumeScopes(raw) {
|
|
|
21106
23383
|
return out;
|
|
21107
23384
|
}
|
|
21108
23385
|
function renderAssumeToken(t) {
|
|
21109
|
-
|
|
23386
|
+
const identity = t.resolvedScope?.identity;
|
|
23387
|
+
const allowed = t.resolvedScope?.allowedActions;
|
|
23388
|
+
const rows = [
|
|
21110
23389
|
["access_token", t.access_token],
|
|
21111
23390
|
["token_type", t.token_type],
|
|
21112
23391
|
["expires_in", t.expires_in === void 0 ? void 0 : `${t.expires_in}s`]
|
|
21113
|
-
]
|
|
23392
|
+
];
|
|
23393
|
+
if (identity && Object.keys(identity).length > 0) {
|
|
23394
|
+
rows.push(["identity", Object.entries(identity).map(([k, v]) => `${k}=${v}`).join(", ")]);
|
|
23395
|
+
}
|
|
23396
|
+
if (allowed?.length) {
|
|
23397
|
+
rows.push(["allowedActions", allowed.join(", ")]);
|
|
23398
|
+
}
|
|
23399
|
+
return formatKeyValues(rows);
|
|
21114
23400
|
}
|
|
21115
23401
|
async function runAssume(opts, deps = {}) {
|
|
21116
23402
|
if (!opts.scope || opts.scope.length === 0) {
|
|
@@ -21295,12 +23581,14 @@ var import_node_crypto4 = require("crypto");
|
|
|
21295
23581
|
|
|
21296
23582
|
// src/harness.ts
|
|
21297
23583
|
var errMsg = (e) => e instanceof Error ? e.message : String(e);
|
|
21298
|
-
var
|
|
23584
|
+
var isNotFound2 = (e) => typeof e === "object" && e !== null && e.statusCode === 404;
|
|
21299
23585
|
var SNAPSHOT_PAGE_SIZE = 100;
|
|
21300
23586
|
async function captureSnapshot(client, blueprint) {
|
|
21301
23587
|
const existingSchemaTypes = /* @__PURE__ */ new Set();
|
|
21302
23588
|
const existingProfilePrincipals = /* @__PURE__ */ new Set();
|
|
21303
23589
|
const existingRoleIds = /* @__PURE__ */ new Set();
|
|
23590
|
+
const existingTriggerNames = /* @__PURE__ */ new Set();
|
|
23591
|
+
let triggerSnapshotFailed = false;
|
|
21304
23592
|
let failed = false;
|
|
21305
23593
|
try {
|
|
21306
23594
|
let cursor;
|
|
@@ -21312,7 +23600,7 @@ async function captureSnapshot(client, blueprint) {
|
|
|
21312
23600
|
cursor = next;
|
|
21313
23601
|
}
|
|
21314
23602
|
} catch (e) {
|
|
21315
|
-
if (!
|
|
23603
|
+
if (!isNotFound2(e)) failed = true;
|
|
21316
23604
|
}
|
|
21317
23605
|
try {
|
|
21318
23606
|
let startFrom;
|
|
@@ -21328,7 +23616,7 @@ async function captureSnapshot(client, blueprint) {
|
|
|
21328
23616
|
startFrom = next;
|
|
21329
23617
|
}
|
|
21330
23618
|
} catch (e) {
|
|
21331
|
-
if (!
|
|
23619
|
+
if (!isNotFound2(e)) failed = true;
|
|
21332
23620
|
}
|
|
21333
23621
|
try {
|
|
21334
23622
|
let startFrom;
|
|
@@ -21344,9 +23632,23 @@ async function captureSnapshot(client, blueprint) {
|
|
|
21344
23632
|
startFrom = next;
|
|
21345
23633
|
}
|
|
21346
23634
|
} catch (e) {
|
|
21347
|
-
if (!
|
|
23635
|
+
if (!isNotFound2(e)) failed = true;
|
|
23636
|
+
}
|
|
23637
|
+
if (Object.keys(blueprint.triggers ?? {}).length > 0) {
|
|
23638
|
+
try {
|
|
23639
|
+
let startFrom;
|
|
23640
|
+
for (; ; ) {
|
|
23641
|
+
const page = await client.triggers.listTriggers({ limit: SNAPSHOT_PAGE_SIZE, startFrom });
|
|
23642
|
+
for (const t of page.data ?? []) if (t.name) existingTriggerNames.add(t.name);
|
|
23643
|
+
const next = page.nextCursor;
|
|
23644
|
+
if (!next) break;
|
|
23645
|
+
startFrom = next;
|
|
23646
|
+
}
|
|
23647
|
+
} catch (e) {
|
|
23648
|
+
if (!isNotFound2(e)) triggerSnapshotFailed = true;
|
|
23649
|
+
}
|
|
21348
23650
|
}
|
|
21349
|
-
return { existingSchemaTypes, existingProfilePrincipals, existingRoleIds, failed };
|
|
23651
|
+
return { existingSchemaTypes, existingProfilePrincipals, existingRoleIds, existingTriggerNames, triggerSnapshotFailed, failed };
|
|
21350
23652
|
}
|
|
21351
23653
|
async function captureIssuerSnapshot(bootstrapClient) {
|
|
21352
23654
|
const existingIssuerIds = /* @__PURE__ */ new Set();
|
|
@@ -21542,6 +23844,97 @@ async function runTeardown(client, bootstrapClient, blueprint, machineId, snapsh
|
|
|
21542
23844
|
steps.push({ step: `delete role ${roleId}`, ok: false, error: errMsg(e) });
|
|
21543
23845
|
}
|
|
21544
23846
|
}
|
|
23847
|
+
const declaredTriggerNames = Object.keys(blueprint.triggers ?? {});
|
|
23848
|
+
let allDeclaredTriggersDeleted = true;
|
|
23849
|
+
if (declaredTriggerNames.length > 0) {
|
|
23850
|
+
let live = [];
|
|
23851
|
+
let listFailed = false;
|
|
23852
|
+
try {
|
|
23853
|
+
let startFrom;
|
|
23854
|
+
for (; ; ) {
|
|
23855
|
+
const page = await client.triggers.listTriggers({ limit: SNAPSHOT_PAGE_SIZE, startFrom });
|
|
23856
|
+
live = live.concat(page.data ?? []);
|
|
23857
|
+
const next = page.nextCursor;
|
|
23858
|
+
if (!next) break;
|
|
23859
|
+
startFrom = next;
|
|
23860
|
+
}
|
|
23861
|
+
} catch (e) {
|
|
23862
|
+
listFailed = true;
|
|
23863
|
+
steps.push({ step: "delete triggers", ok: false, error: `could not list triggers: ${errMsg(e)}` });
|
|
23864
|
+
allDeclaredTriggersDeleted = false;
|
|
23865
|
+
}
|
|
23866
|
+
if (!listFailed) {
|
|
23867
|
+
for (const name of declaredTriggerNames) {
|
|
23868
|
+
if (snapshot.failed || snapshot.triggerSnapshotFailed) {
|
|
23869
|
+
steps.push({ step: `delete trigger ${name}`, ok: true, skipped: true, error: "skipped \u2014 pre-apply snapshot failed; cannot prove ownership" });
|
|
23870
|
+
allDeclaredTriggersDeleted = false;
|
|
23871
|
+
continue;
|
|
23872
|
+
}
|
|
23873
|
+
if (snapshot.existingTriggerNames.has(name)) {
|
|
23874
|
+
steps.push({ step: `delete trigger ${name}`, ok: true, skipped: true, error: "skipped \u2014 pre-existed this run" });
|
|
23875
|
+
allDeclaredTriggersDeleted = false;
|
|
23876
|
+
continue;
|
|
23877
|
+
}
|
|
23878
|
+
const row = live.find((t) => t.name === name);
|
|
23879
|
+
if (!row?.id) {
|
|
23880
|
+
steps.push({ step: `delete trigger ${name}`, ok: true, skipped: true, error: "skipped \u2014 not found (apply may not have reached it)" });
|
|
23881
|
+
continue;
|
|
23882
|
+
}
|
|
23883
|
+
if (row.provisionedBy !== blueprint.name) {
|
|
23884
|
+
steps.push({
|
|
23885
|
+
step: `delete trigger ${name}`,
|
|
23886
|
+
ok: true,
|
|
23887
|
+
skipped: true,
|
|
23888
|
+
error: `skipped \u2014 provisionedBy is ${row.provisionedBy ? `'${row.provisionedBy}'` : "unset"}, not this blueprint`
|
|
23889
|
+
});
|
|
23890
|
+
allDeclaredTriggersDeleted = false;
|
|
23891
|
+
continue;
|
|
23892
|
+
}
|
|
23893
|
+
try {
|
|
23894
|
+
await client.triggers.deleteTrigger({ id: row.id });
|
|
23895
|
+
steps.push({ step: `delete trigger ${name}`, ok: true });
|
|
23896
|
+
} catch (e) {
|
|
23897
|
+
steps.push({ step: `delete trigger ${name}`, ok: false, error: errMsg(e) });
|
|
23898
|
+
allDeclaredTriggersDeleted = false;
|
|
23899
|
+
continue;
|
|
23900
|
+
}
|
|
23901
|
+
if (row.principalId) {
|
|
23902
|
+
try {
|
|
23903
|
+
await client.auth.deleteAccessProfile({ contextId: blueprint.contextId, principalId: row.principalId });
|
|
23904
|
+
steps.push({ step: `delete trigger ${name} profile`, ok: true });
|
|
23905
|
+
} catch (e) {
|
|
23906
|
+
steps.push({ step: `delete trigger ${name} profile`, ok: false, error: errMsg(e) });
|
|
23907
|
+
}
|
|
23908
|
+
}
|
|
23909
|
+
}
|
|
23910
|
+
}
|
|
23911
|
+
}
|
|
23912
|
+
for (const script of result.scriptActions) {
|
|
23913
|
+
if (!allDeclaredTriggersDeleted) {
|
|
23914
|
+
steps.push({
|
|
23915
|
+
step: `delete script ${script.name} v${script.scriptVersion}`,
|
|
23916
|
+
ok: true,
|
|
23917
|
+
skipped: true,
|
|
23918
|
+
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"
|
|
23919
|
+
});
|
|
23920
|
+
continue;
|
|
23921
|
+
}
|
|
23922
|
+
if (script.action === "unchanged") {
|
|
23923
|
+
steps.push({
|
|
23924
|
+
step: `delete script ${script.name} v${script.scriptVersion}`,
|
|
23925
|
+
ok: true,
|
|
23926
|
+
skipped: true,
|
|
23927
|
+
error: "skipped \u2014 this run pushed no version (the stored source already matched)"
|
|
23928
|
+
});
|
|
23929
|
+
continue;
|
|
23930
|
+
}
|
|
23931
|
+
try {
|
|
23932
|
+
await client.scripts.deleteScript({ id: script.id });
|
|
23933
|
+
steps.push({ step: `delete script ${script.name} v${script.scriptVersion}`, ok: true });
|
|
23934
|
+
} catch (e) {
|
|
23935
|
+
steps.push({ step: `delete script ${script.name} v${script.scriptVersion}`, ok: false, error: errMsg(e) });
|
|
23936
|
+
}
|
|
23937
|
+
}
|
|
21545
23938
|
const toDelete = [];
|
|
21546
23939
|
for (const [typeName, schemaId] of Object.entries(result.schemaIds)) {
|
|
21547
23940
|
if (snapshot.failed) {
|
|
@@ -21628,7 +24021,9 @@ async function runBlueprintHarness(client, input, deps) {
|
|
|
21628
24021
|
existingSchemaTypes: /* @__PURE__ */ new Set(),
|
|
21629
24022
|
existingProfilePrincipals: /* @__PURE__ */ new Set(),
|
|
21630
24023
|
existingRoleIds: /* @__PURE__ */ new Set(),
|
|
21631
|
-
|
|
24024
|
+
existingTriggerNames: /* @__PURE__ */ new Set(),
|
|
24025
|
+
triggerSnapshotFailed: !isNotFound2(e),
|
|
24026
|
+
failed: !isNotFound2(e)
|
|
21632
24027
|
};
|
|
21633
24028
|
}
|
|
21634
24029
|
} else {
|
|
@@ -21641,7 +24036,7 @@ async function runBlueprintHarness(client, input, deps) {
|
|
|
21641
24036
|
try {
|
|
21642
24037
|
issuerSnapshot = await captureIssuerSnapshot(await remintBootstrapForContext(blueprint.contextId));
|
|
21643
24038
|
} catch (e) {
|
|
21644
|
-
issuerSnapshot = { existingIssuerIds: /* @__PURE__ */ new Set(), failed: !
|
|
24039
|
+
issuerSnapshot = { existingIssuerIds: /* @__PURE__ */ new Set(), failed: !isNotFound2(e) };
|
|
21645
24040
|
}
|
|
21646
24041
|
} else {
|
|
21647
24042
|
issuerSnapshot = await captureIssuerSnapshot(client);
|
|
@@ -21664,7 +24059,17 @@ async function runBlueprintHarness(client, input, deps) {
|
|
|
21664
24059
|
log,
|
|
21665
24060
|
provisioned,
|
|
21666
24061
|
remintForContext,
|
|
21667
|
-
remintBootstrapForContext
|
|
24062
|
+
remintBootstrapForContext,
|
|
24063
|
+
// CREATED-ONLY, and this is the seam that keeps it true for triggers. The loader's step 5d
|
|
24064
|
+
// reconciles triggers by ABSENCE — it deletes rules this blueprint provisioned and no longer
|
|
24065
|
+
// declares. That is right for a real bootstrap run, which owns its context; it is wrong here.
|
|
24066
|
+
// This harness may run against a shared tenant, and its whole safety contract is that a run
|
|
24067
|
+
// touches only what it CREATED: `captureSnapshot` protects pre-existing schemas, profiles,
|
|
24068
|
+
// roles and issuers, and `runTeardown` skips them. There is no trigger leg in either, so a
|
|
24069
|
+
// prune would delete a pre-existing trigger — and its access profile — with nothing
|
|
24070
|
+
// recording that it happened and nothing able to restore it. Opt out rather than grow the
|
|
24071
|
+
// snapshot a leg teardown could not honour anyway.
|
|
24072
|
+
pruneTriggers: false
|
|
21668
24073
|
});
|
|
21669
24074
|
log.debug({ component: "harness", contextId: result.contextId, keyName: result.keyName }, "applied");
|
|
21670
24075
|
assertions = await runAssertions(
|
|
@@ -21766,14 +24171,14 @@ async function runBlueprintTest(fileOrName, opts, deps = {}) {
|
|
|
21766
24171
|
if (err instanceof ScopeGateError) {
|
|
21767
24172
|
process.stderr.write(` \u2716 Scope gate refused blueprint:
|
|
21768
24173
|
`);
|
|
21769
|
-
for (const r of err.rejected) process.stderr.write(` \u2022 ${r
|
|
24174
|
+
for (const r of err.rejected) process.stderr.write(` \u2022 ${formatRejection(r)}
|
|
21770
24175
|
`);
|
|
21771
24176
|
return 2;
|
|
21772
24177
|
}
|
|
21773
24178
|
if (err instanceof CapabilityGateError) {
|
|
21774
24179
|
process.stderr.write(` \u2716 Capability gate refused blueprint:
|
|
21775
24180
|
`);
|
|
21776
|
-
for (const r of err.rejected) process.stderr.write(` \u2022 ${r
|
|
24181
|
+
for (const r of err.rejected) process.stderr.write(` \u2022 ${formatRejection(r)}
|
|
21777
24182
|
`);
|
|
21778
24183
|
return 2;
|
|
21779
24184
|
}
|
|
@@ -22025,9 +24430,9 @@ function emitJoined(opts) {
|
|
|
22025
24430
|
|
|
22026
24431
|
// src/build-info.ts
|
|
22027
24432
|
var BUILD_INFO = {
|
|
22028
|
-
cli: true ? "0.
|
|
22029
|
-
sdk: true ? "0.
|
|
22030
|
-
blueprints: true ? "0.
|
|
24433
|
+
cli: true ? "0.21.0" : "dev",
|
|
24434
|
+
sdk: true ? "0.43.0" : "dev",
|
|
24435
|
+
blueprints: true ? "0.18.0" : "dev"
|
|
22031
24436
|
};
|
|
22032
24437
|
function formatBuildInfo() {
|
|
22033
24438
|
return `vectros ${BUILD_INFO.cli} (sdk ${BUILD_INFO.sdk}, blueprints ${BUILD_INFO.blueprints})`;
|
|
@@ -22038,6 +24443,9 @@ var KEY_FORMATS = ["human", "raw", "env", "json"];
|
|
|
22038
24443
|
function collect(value, previous) {
|
|
22039
24444
|
return [...previous, value];
|
|
22040
24445
|
}
|
|
24446
|
+
function collectOptional(value, previous) {
|
|
24447
|
+
return [...previous ?? [], value];
|
|
24448
|
+
}
|
|
22041
24449
|
function addAuthOptions(cmd) {
|
|
22042
24450
|
return cmd.addOption(
|
|
22043
24451
|
new import_commander.Option("--env <env>", "target environment").choices(["staging", "production"]).default("production").hideHelp()
|
|
@@ -22109,6 +24517,7 @@ function buildProgram(deps = {}) {
|
|
|
22109
24517
|
registerContextCommands(program, deps);
|
|
22110
24518
|
registerIssuersCommands(program, deps);
|
|
22111
24519
|
registerNamespaceCommands(program, deps);
|
|
24520
|
+
registerScriptsCommands(program, deps);
|
|
22112
24521
|
registerAssumeCommand(program, deps);
|
|
22113
24522
|
registerJoinCommand(program, deps);
|
|
22114
24523
|
registerKeyringCommands(program);
|
|
@@ -22284,6 +24693,10 @@ function registerIssuersCommands(program, deps) {
|
|
|
22284
24693
|
"opt-in self-service signup entry, repeatable (e.g. default:member) \u2014 no entry may target an elevated role",
|
|
22285
24694
|
collect,
|
|
22286
24695
|
[]
|
|
24696
|
+
).option(
|
|
24697
|
+
"--captured-claim <name>",
|
|
24698
|
+
"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",
|
|
24699
|
+
collectOptional
|
|
22287
24700
|
).option("--tenant <tenant>", "tenant the context lives in: 'test' or 'live' (default live)");
|
|
22288
24701
|
addCommonAuthOptions(register).action(
|
|
22289
24702
|
async (flags) => {
|
|
@@ -22301,6 +24714,7 @@ function registerIssuersCommands(program, deps) {
|
|
|
22301
24714
|
emailClaim: flags.emailClaim,
|
|
22302
24715
|
userinfoUri: flags.userinfoUri,
|
|
22303
24716
|
selfSignupPolicy: flags.selfSignupPolicy,
|
|
24717
|
+
capturedClaim: flags.capturedClaim,
|
|
22304
24718
|
tenant
|
|
22305
24719
|
},
|
|
22306
24720
|
deps
|
|
@@ -22313,7 +24727,11 @@ function registerIssuersCommands(program, deps) {
|
|
|
22313
24727
|
"replace self-signup policies, repeatable (e.g. default:member) \u2014 omit entirely to leave unchanged",
|
|
22314
24728
|
collect,
|
|
22315
24729
|
[]
|
|
22316
|
-
).option("--clear-self-signup-policies", "disable self-signup entirely (sends an empty list; XOR --self-signup-policy)").option(
|
|
24730
|
+
).option("--clear-self-signup-policies", "disable self-signup entirely (sends an empty list; XOR --self-signup-policy)").option(
|
|
24731
|
+
"--captured-claim <name>",
|
|
24732
|
+
"replace the additional OIDC claims captured beyond the email claim, repeatable \u2014 omit entirely to leave unchanged",
|
|
24733
|
+
collectOptional
|
|
24734
|
+
).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)");
|
|
22317
24735
|
addCommonAuthOptions(update).action(
|
|
22318
24736
|
async (issuerId, flags) => {
|
|
22319
24737
|
const tenant = parseTenantFlag(flags.tenant);
|
|
@@ -22329,6 +24747,8 @@ function registerIssuersCommands(program, deps) {
|
|
|
22329
24747
|
status: flags.status,
|
|
22330
24748
|
selfSignupPolicy: flags.selfSignupPolicy,
|
|
22331
24749
|
clearSelfSignupPolicies: Boolean(flags.clearSelfSignupPolicies),
|
|
24750
|
+
capturedClaim: flags.capturedClaim,
|
|
24751
|
+
clearCapturedClaims: Boolean(flags.clearCapturedClaims),
|
|
22332
24752
|
tenant
|
|
22333
24753
|
},
|
|
22334
24754
|
deps
|
|
@@ -22390,6 +24810,68 @@ function registerNamespaceCommands(program, deps) {
|
|
|
22390
24810
|
}
|
|
22391
24811
|
);
|
|
22392
24812
|
}
|
|
24813
|
+
function registerScriptsCommands(program, deps) {
|
|
24814
|
+
const scripts = program.command("scripts").description("Manage script objects (the versioned JS source a trigger rule runs).");
|
|
24815
|
+
const push = scripts.command("push").description("Store a new version of a script name (always creates a version unless --if-changed).").argument("<name>", "script name: 1-64 chars of letters, digits, '_' and '-'").requiredOption("--context <c>", "app context to push into").requiredOption("--source-file <path>", "file holding the script source (max 300,000 UTF-8 bytes)").option("--input-contract <text>", "free-text description of the expected input shape (descriptive only)").option("--input-contract-file <path>", "read the input contract from a file instead").option("--if-changed", "push nothing when the current version already carries this exact content").option("--tenant <tenant>", "target tenant: 'test' or 'live' (default live)");
|
|
24816
|
+
addCommonAuthOptions(push).action(
|
|
24817
|
+
async (name, flags) => {
|
|
24818
|
+
process.exit(
|
|
24819
|
+
await runScriptsPush(
|
|
24820
|
+
{
|
|
24821
|
+
...toCommonOptions(flags),
|
|
24822
|
+
name,
|
|
24823
|
+
context: flags.context,
|
|
24824
|
+
sourceFile: flags.sourceFile,
|
|
24825
|
+
inputContract: flags.inputContract,
|
|
24826
|
+
inputContractFile: flags.inputContractFile,
|
|
24827
|
+
ifChanged: Boolean(flags.ifChanged),
|
|
24828
|
+
tenant: parseTenantFlag(flags.tenant)
|
|
24829
|
+
},
|
|
24830
|
+
deps
|
|
24831
|
+
)
|
|
24832
|
+
);
|
|
24833
|
+
}
|
|
24834
|
+
);
|
|
24835
|
+
const list = scripts.command("list").description("List script versions \u2014 every version of one --name (oldest first), or all scripts (newest first).").requiredOption("--context <c>", "app context to read").option("--name <name>", "list every version of this script name instead of a flat cross-name list").option("--tenant <tenant>", "target tenant: 'test' or 'live' (default live)");
|
|
24836
|
+
addCommonAuthOptions(list).action(
|
|
24837
|
+
async (flags) => {
|
|
24838
|
+
process.exit(
|
|
24839
|
+
await runScriptsList(
|
|
24840
|
+
{ ...toCommonOptions(flags), context: flags.context, name: flags.name, tenant: parseTenantFlag(flags.tenant) },
|
|
24841
|
+
deps
|
|
24842
|
+
)
|
|
24843
|
+
);
|
|
24844
|
+
}
|
|
24845
|
+
);
|
|
24846
|
+
const get = scripts.command("get").description("Show one script version, including its full source.").argument("<id>", "the script VERSION id (each version has its own)").requiredOption("--context <c>", "app context to read").option("--tenant <tenant>", "target tenant: 'test' or 'live' (default live)");
|
|
24847
|
+
addCommonAuthOptions(get).action(
|
|
24848
|
+
async (id, flags) => {
|
|
24849
|
+
process.exit(
|
|
24850
|
+
await runScriptsGet(
|
|
24851
|
+
{ ...toCommonOptions(flags), id, context: flags.context, tenant: parseTenantFlag(flags.tenant) },
|
|
24852
|
+
deps
|
|
24853
|
+
)
|
|
24854
|
+
);
|
|
24855
|
+
}
|
|
24856
|
+
);
|
|
24857
|
+
const del = scripts.command("delete").description("Permanently delete ONE script version (other versions of the name are unaffected).").argument("<id>", "the script VERSION id").requiredOption("--context <c>", "app context to delete from").option("--tenant <tenant>", "target tenant: 'test' or 'live' (default live)").option("--force", "skip the interactive confirmation (required in a non-interactive shell)");
|
|
24858
|
+
addCommonAuthOptions(del).action(
|
|
24859
|
+
async (id, flags) => {
|
|
24860
|
+
process.exit(
|
|
24861
|
+
await runScriptsDelete(
|
|
24862
|
+
{
|
|
24863
|
+
...toCommonOptions(flags),
|
|
24864
|
+
id,
|
|
24865
|
+
context: flags.context,
|
|
24866
|
+
tenant: parseTenantFlag(flags.tenant),
|
|
24867
|
+
force: Boolean(flags.force)
|
|
24868
|
+
},
|
|
24869
|
+
deps
|
|
24870
|
+
)
|
|
24871
|
+
);
|
|
24872
|
+
}
|
|
24873
|
+
);
|
|
24874
|
+
}
|
|
22393
24875
|
function registerRoleCommands(program, deps) {
|
|
22394
24876
|
const role = program.command("role").description("Manage context-scoped roles (reusable scope rules; bind via `access grant --role`).");
|
|
22395
24877
|
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(
|
|
@@ -22398,72 +24880,106 @@ function registerRoleCommands(program, deps) {
|
|
|
22398
24880
|
).option(
|
|
22399
24881
|
"--assumable <json>",
|
|
22400
24882
|
`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)`
|
|
22401
|
-
).option(
|
|
24883
|
+
).option(
|
|
24884
|
+
"--assignable-role <roleId>",
|
|
24885
|
+
'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"',
|
|
24886
|
+
collectOptional
|
|
24887
|
+
).option("--description <text>", "free-text description").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
22402
24888
|
addCommonAuthOptions(create).action(
|
|
22403
24889
|
async (flags) => {
|
|
22404
|
-
process.
|
|
22405
|
-
|
|
22406
|
-
|
|
22407
|
-
|
|
22408
|
-
|
|
22409
|
-
|
|
22410
|
-
|
|
22411
|
-
|
|
22412
|
-
|
|
22413
|
-
|
|
22414
|
-
|
|
22415
|
-
);
|
|
24890
|
+
process.exitCode = await runRoleCreate({
|
|
24891
|
+
...toCommonOptions(flags),
|
|
24892
|
+
tenant: parseTenantFlag(flags.tenant),
|
|
24893
|
+
context: flags.context,
|
|
24894
|
+
roleId: flags.roleId,
|
|
24895
|
+
name: flags.name,
|
|
24896
|
+
actions: flags.actions,
|
|
24897
|
+
dataScope: flags.dataScope,
|
|
24898
|
+
assumable: flags.assumable,
|
|
24899
|
+
assignableRole: flags.assignableRole,
|
|
24900
|
+
description: flags.description
|
|
24901
|
+
}, deps);
|
|
22416
24902
|
}
|
|
22417
24903
|
);
|
|
22418
|
-
const list = role.command("list").description("List the roles defined in a context.").requiredOption("--context <c>", "app context");
|
|
24904
|
+
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)");
|
|
22419
24905
|
addCommonAuthOptions(list).action(async (flags) => {
|
|
22420
|
-
process.
|
|
22421
|
-
|
|
22422
|
-
|
|
22423
|
-
|
|
22424
|
-
process.exit(await runRoleGet({ ...toCommonOptions(flags), context: flags.context, roleId: flags.roleId }, deps));
|
|
22425
|
-
});
|
|
22426
|
-
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");
|
|
22427
|
-
addAuthOptions(del).action(async (flags) => {
|
|
22428
|
-
process.exit(await runRoleDelete({ ...toCommonOptions(flags), context: flags.context, roleId: flags.roleId }, deps));
|
|
24906
|
+
process.exitCode = await runRoleList(
|
|
24907
|
+
{ ...toCommonOptions(flags), tenant: parseTenantFlag(flags.tenant), context: flags.context },
|
|
24908
|
+
deps
|
|
24909
|
+
);
|
|
22429
24910
|
});
|
|
24911
|
+
const get = role.command("get").description("Show one role by id.").requiredOption("--context <c>", "app context").requiredOption("--role-id <id>", "role id").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
24912
|
+
addCommonAuthOptions(get).action(
|
|
24913
|
+
async (flags) => {
|
|
24914
|
+
process.exitCode = await runRoleGet(
|
|
24915
|
+
{ ...toCommonOptions(flags), tenant: parseTenantFlag(flags.tenant), context: flags.context, roleId: flags.roleId },
|
|
24916
|
+
deps
|
|
24917
|
+
);
|
|
24918
|
+
}
|
|
24919
|
+
);
|
|
24920
|
+
const del = role.command("delete").description("Delete a role (referencing profiles must be reassigned).").requiredOption("--context <c>", "app context").requiredOption("--role-id <id>", "role id").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
24921
|
+
addAuthOptions(del).action(
|
|
24922
|
+
async (flags) => {
|
|
24923
|
+
process.exitCode = await runRoleDelete(
|
|
24924
|
+
{ ...toCommonOptions(flags), tenant: parseTenantFlag(flags.tenant), context: flags.context, roleId: flags.roleId },
|
|
24925
|
+
deps
|
|
24926
|
+
);
|
|
24927
|
+
}
|
|
24928
|
+
);
|
|
22430
24929
|
}
|
|
22431
24930
|
function registerAccessCommands(program, deps) {
|
|
22432
24931
|
const access2 = program.command("access").description("Manage access bindings: principal \u2194 context \u2194 role/scopes.");
|
|
22433
|
-
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)");
|
|
24932
|
+
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)");
|
|
22434
24933
|
addCommonAuthOptions(grant).action(
|
|
22435
24934
|
async (flags) => {
|
|
22436
|
-
process.
|
|
22437
|
-
|
|
22438
|
-
|
|
22439
|
-
|
|
22440
|
-
|
|
22441
|
-
|
|
22442
|
-
|
|
22443
|
-
|
|
22444
|
-
|
|
22445
|
-
|
|
24935
|
+
process.exitCode = await runAccessGrant({
|
|
24936
|
+
...toCommonOptions(flags),
|
|
24937
|
+
tenant: parseTenantFlag(flags.tenant),
|
|
24938
|
+
principal: flags.principal,
|
|
24939
|
+
context: flags.context,
|
|
24940
|
+
role: flags.role,
|
|
24941
|
+
actions: flags.actions,
|
|
24942
|
+
scope: flags.scope,
|
|
24943
|
+
identityOverrides: flags.identityOverrides
|
|
24944
|
+
}, deps);
|
|
24945
|
+
}
|
|
24946
|
+
);
|
|
24947
|
+
const revoke = access2.command("revoke").description("Remove a principal\u2019s binding in a context.").requiredOption("--principal <p>", "principal: 'usr_<userId>', 'key_<keyId>', or 'me' (your own user)").requiredOption("--context <c>", "app context").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
24948
|
+
addAuthOptions(revoke).action(
|
|
24949
|
+
async (flags) => {
|
|
24950
|
+
process.exitCode = await runAccessRevoke(
|
|
24951
|
+
{ ...toCommonOptions(flags), tenant: parseTenantFlag(flags.tenant), principal: flags.principal, context: flags.context },
|
|
24952
|
+
deps
|
|
24953
|
+
);
|
|
24954
|
+
}
|
|
24955
|
+
);
|
|
24956
|
+
const list = access2.command("list").description("List a context\u2019s members (--context) or a principal\u2019s contexts (--principal).").option("--context <c>", "list members of this context").option("--principal <p>", "list contexts this principal is bound to ('me' = your own user)").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
24957
|
+
addCommonAuthOptions(list).action(
|
|
24958
|
+
async (flags) => {
|
|
24959
|
+
process.exitCode = await runAccessList(
|
|
24960
|
+
{ ...toCommonOptions(flags), tenant: parseTenantFlag(flags.tenant), context: flags.context, principal: flags.principal },
|
|
24961
|
+
deps
|
|
24962
|
+
);
|
|
24963
|
+
}
|
|
24964
|
+
);
|
|
24965
|
+
const get = access2.command("get").description("Show one principal\u2019s binding in a context.").requiredOption("--principal <p>", "principal: 'usr_<userId>', 'key_<keyId>', or 'me' (your own user)").requiredOption("--context <c>", "app context").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
24966
|
+
addCommonAuthOptions(get).action(
|
|
24967
|
+
async (flags) => {
|
|
24968
|
+
process.exitCode = await runAccessGet(
|
|
24969
|
+
{ ...toCommonOptions(flags), tenant: parseTenantFlag(flags.tenant), principal: flags.principal, context: flags.context },
|
|
24970
|
+
deps
|
|
24971
|
+
);
|
|
24972
|
+
}
|
|
24973
|
+
);
|
|
24974
|
+
const explain = access2.command("explain").description("Show what a principal\u2019s binding grants: its clauses + a concrete access matrix.").requiredOption("--principal <p>", "principal: 'usr_<userId>', 'key_<keyId>', or 'me' (your own user)").requiredOption("--context <c>", "app context").option("--tenant <tenant>", "tenant to target: 'test' or 'live' (default live)");
|
|
24975
|
+
addCommonAuthOptions(explain).action(
|
|
24976
|
+
async (flags) => {
|
|
24977
|
+
process.exitCode = await runAccessExplain(
|
|
24978
|
+
{ ...toCommonOptions(flags), tenant: parseTenantFlag(flags.tenant), principal: flags.principal, context: flags.context },
|
|
24979
|
+
deps
|
|
22446
24980
|
);
|
|
22447
24981
|
}
|
|
22448
24982
|
);
|
|
22449
|
-
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");
|
|
22450
|
-
addAuthOptions(revoke).action(async (flags) => {
|
|
22451
|
-
process.exit(await runAccessRevoke({ ...toCommonOptions(flags), principal: flags.principal, context: flags.context }, deps));
|
|
22452
|
-
});
|
|
22453
|
-
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)");
|
|
22454
|
-
addCommonAuthOptions(list).action(async (flags) => {
|
|
22455
|
-
process.exit(await runAccessList({ ...toCommonOptions(flags), context: flags.context, principal: flags.principal }, deps));
|
|
22456
|
-
});
|
|
22457
|
-
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");
|
|
22458
|
-
addCommonAuthOptions(get).action(async (flags) => {
|
|
22459
|
-
process.exit(await runAccessGet({ ...toCommonOptions(flags), principal: flags.principal, context: flags.context }, deps));
|
|
22460
|
-
});
|
|
22461
|
-
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");
|
|
22462
|
-
addCommonAuthOptions(explain).action(async (flags) => {
|
|
22463
|
-
process.exit(
|
|
22464
|
-
await runAccessExplain({ ...toCommonOptions(flags), principal: flags.principal, context: flags.context }, deps)
|
|
22465
|
-
);
|
|
22466
|
-
});
|
|
22467
24983
|
}
|
|
22468
24984
|
function dimensionOption() {
|
|
22469
24985
|
return new import_commander.Option(
|