@vectros-ai/cli 0.6.1 → 0.6.5
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/CHANGELOG.md +63 -0
- package/dist/cli.js +1268 -342
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +1268 -342
- package/dist/cli.mjs.map +1 -1
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -2861,7 +2861,7 @@ var AuthClient = class {
|
|
|
2861
2861
|
);
|
|
2862
2862
|
}
|
|
2863
2863
|
/**
|
|
2864
|
-
* Creates a new access profile under the given app context. This call is idempotent by `principalId`: if a profile with the same `principalId` already exists, the existing profile is returned (with status 200) instead of creating a duplicate. You must provide exactly one of `scopes` (an inline list of scopes) or `roleId` (a reference to a role); supplying both, or neither, is rejected. `identityOverrides` may set only `orgId` and `clientId`; any other key (including the account identifier or `userId`) is rejected. If you use a scoped credential, the profile's effective scopes may not exceed your own; a root API key (`sk_`) is exempt. Requires the `profiles:c` scope.
|
|
2864
|
+
* Creates a new access profile under the given app context. This call is idempotent by `principalId`: if a profile with the same `principalId` already exists, the existing profile 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 profile was returned) tells the two apart. To overwrite an existing profile's `scopes`/`roleId`, `identityOverrides`, and `status` instead of returning it unchanged, set `?upsert=true` (this also requires the `profiles:u` scope). You must provide exactly one of `scopes` (an inline list of scopes) or `roleId` (a reference to a role); supplying both, or neither, is rejected. `identityOverrides` may set only `orgId` and `clientId`; any other key (including the account identifier or `userId`) is rejected. If you use a scoped credential, the profile's effective scopes may not exceed your own; a root API key (`sk_`) is exempt. Requires the `profiles:c` scope.
|
|
2865
2865
|
*
|
|
2866
2866
|
* @param {Vectros.CreateAccessProfileRequest} request
|
|
2867
2867
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -2883,7 +2883,10 @@ var AuthClient = class {
|
|
|
2883
2883
|
return HttpResponsePromise.fromPromise(this.__createAccessProfile(request, requestOptions));
|
|
2884
2884
|
}
|
|
2885
2885
|
async __createAccessProfile(request, requestOptions) {
|
|
2886
|
-
const { contextId, body: _body } = request;
|
|
2886
|
+
const { contextId, upsert, body: _body } = request;
|
|
2887
|
+
const _queryParams = {
|
|
2888
|
+
upsert
|
|
2889
|
+
};
|
|
2887
2890
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
2888
2891
|
const _headers = mergeHeaders(
|
|
2889
2892
|
_authRequest.headers,
|
|
@@ -2898,7 +2901,7 @@ var AuthClient = class {
|
|
|
2898
2901
|
method: "POST",
|
|
2899
2902
|
headers: _headers,
|
|
2900
2903
|
contentType: "application/json",
|
|
2901
|
-
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
2904
|
+
queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
|
|
2902
2905
|
requestType: "json",
|
|
2903
2906
|
body: _body,
|
|
2904
2907
|
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
@@ -2993,9 +2996,9 @@ var AuthClient = class {
|
|
|
2993
2996
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/app-contexts");
|
|
2994
2997
|
}
|
|
2995
2998
|
/**
|
|
2996
|
-
* 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 reserved `contextId` value `vectros-admin` cannot be created through this endpoint; it is provisioned automatically for your account. Requires the `app-contexts:c` scope.
|
|
2999
|
+
* 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.
|
|
2997
3000
|
*
|
|
2998
|
-
* @param {Vectros.
|
|
3001
|
+
* @param {Vectros.CreateAppContextRequest} request
|
|
2999
3002
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
3000
3003
|
*
|
|
3001
3004
|
* @throws {@link Vectros.BadRequestError}
|
|
@@ -3004,14 +3007,20 @@ var AuthClient = class {
|
|
|
3004
3007
|
*
|
|
3005
3008
|
* @example
|
|
3006
3009
|
* await client.auth.createAppContext({
|
|
3007
|
-
*
|
|
3008
|
-
*
|
|
3010
|
+
* body: {
|
|
3011
|
+
* contextId: "myapp",
|
|
3012
|
+
* name: "My Internal App"
|
|
3013
|
+
* }
|
|
3009
3014
|
* })
|
|
3010
3015
|
*/
|
|
3011
3016
|
createAppContext(request, requestOptions) {
|
|
3012
3017
|
return HttpResponsePromise.fromPromise(this.__createAppContext(request, requestOptions));
|
|
3013
3018
|
}
|
|
3014
3019
|
async __createAppContext(request, requestOptions) {
|
|
3020
|
+
const { upsert, body: _body } = request;
|
|
3021
|
+
const _queryParams = {
|
|
3022
|
+
upsert
|
|
3023
|
+
};
|
|
3015
3024
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
3016
3025
|
const _headers = mergeHeaders(
|
|
3017
3026
|
_authRequest.headers,
|
|
@@ -3026,9 +3035,9 @@ var AuthClient = class {
|
|
|
3026
3035
|
method: "POST",
|
|
3027
3036
|
headers: _headers,
|
|
3028
3037
|
contentType: "application/json",
|
|
3029
|
-
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
3038
|
+
queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
|
|
3030
3039
|
requestType: "json",
|
|
3031
|
-
body:
|
|
3040
|
+
body: _body,
|
|
3032
3041
|
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
3033
3042
|
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
3034
3043
|
abortSignal: requestOptions?.abortSignal,
|
|
@@ -3124,7 +3133,7 @@ var AuthClient = class {
|
|
|
3124
3133
|
);
|
|
3125
3134
|
}
|
|
3126
3135
|
/**
|
|
3127
|
-
* Creates a new role under the given app context. This call is idempotent by `roleId`: if a role with the same `roleId` already exists, the existing role is returned (with status 200) instead of creating a duplicate. If you use a scoped credential, the role's scopes may not exceed your own; a root API key (`sk_`) is exempt. Requires the `profiles:c` scope.
|
|
3136
|
+
* Creates a new role under the given app context. This call is idempotent by `roleId`: if a role with the same `roleId` already exists, the existing role 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 role was returned) tells the two apart. To overwrite an existing role's `name`/`description`/`scopes` instead of returning it unchanged, set `?upsert=true` (this also requires the `profiles:u` scope). If you use a scoped credential, the role's scopes may not exceed your own; a root API key (`sk_`) is exempt. Requires the `profiles:c` scope.
|
|
3128
3137
|
*
|
|
3129
3138
|
* @param {Vectros.CreateRoleRequest} request
|
|
3130
3139
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -3150,7 +3159,10 @@ var AuthClient = class {
|
|
|
3150
3159
|
return HttpResponsePromise.fromPromise(this.__createRole(request, requestOptions));
|
|
3151
3160
|
}
|
|
3152
3161
|
async __createRole(request, requestOptions) {
|
|
3153
|
-
const { contextId, body: _body } = request;
|
|
3162
|
+
const { contextId, upsert, body: _body } = request;
|
|
3163
|
+
const _queryParams = {
|
|
3164
|
+
upsert
|
|
3165
|
+
};
|
|
3154
3166
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
3155
3167
|
const _headers = mergeHeaders(
|
|
3156
3168
|
_authRequest.headers,
|
|
@@ -3165,7 +3177,7 @@ var AuthClient = class {
|
|
|
3165
3177
|
method: "POST",
|
|
3166
3178
|
headers: _headers,
|
|
3167
3179
|
contentType: "application/json",
|
|
3168
|
-
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
3180
|
+
queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
|
|
3169
3181
|
requestType: "json",
|
|
3170
3182
|
body: _body,
|
|
3171
3183
|
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
@@ -4649,23 +4661,30 @@ var DocumentsClient = class {
|
|
|
4649
4661
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/documents");
|
|
4650
4662
|
}
|
|
4651
4663
|
/**
|
|
4652
|
-
* Creates a document from a raw text string and queues it for asynchronous indexing so it becomes searchable. Requires the `documents:c` scope.
|
|
4664
|
+
* Creates a document from a raw text string and queues it for asynchronous indexing so it becomes searchable. Optionally supply an `externalId` to make the create idempotent — if a document with the same `externalId` already exists in your context, that existing document is returned unchanged instead of a duplicate being created. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing document was returned) tells the two apart. To overwrite an existing document's content instead of returning it unchanged, set `?upsert=true` (this also requires the `documents:u` scope). Requires the `documents:c` scope.
|
|
4653
4665
|
*
|
|
4654
|
-
* @param {Vectros.
|
|
4666
|
+
* @param {Vectros.IngestDocumentRequest} request
|
|
4655
4667
|
* @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
4656
4668
|
*
|
|
4657
4669
|
* @throws {@link Vectros.BadRequestError}
|
|
4670
|
+
* @throws {@link Vectros.ForbiddenError}
|
|
4658
4671
|
* @throws {@link Vectros.TooManyRequestsError}
|
|
4659
4672
|
*
|
|
4660
4673
|
* @example
|
|
4661
4674
|
* await client.documents.ingestDocument({
|
|
4662
|
-
*
|
|
4675
|
+
* body: {
|
|
4676
|
+
* title: "Patient Intake Form \u2014 Jane Doe"
|
|
4677
|
+
* }
|
|
4663
4678
|
* })
|
|
4664
4679
|
*/
|
|
4665
4680
|
ingestDocument(request, requestOptions) {
|
|
4666
4681
|
return HttpResponsePromise.fromPromise(this.__ingestDocument(request, requestOptions));
|
|
4667
4682
|
}
|
|
4668
4683
|
async __ingestDocument(request, requestOptions) {
|
|
4684
|
+
const { upsert, body: _body } = request;
|
|
4685
|
+
const _queryParams = {
|
|
4686
|
+
upsert
|
|
4687
|
+
};
|
|
4669
4688
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
4670
4689
|
const _headers = mergeHeaders(
|
|
4671
4690
|
_authRequest.headers,
|
|
@@ -4680,9 +4699,9 @@ var DocumentsClient = class {
|
|
|
4680
4699
|
method: "POST",
|
|
4681
4700
|
headers: _headers,
|
|
4682
4701
|
contentType: "application/json",
|
|
4683
|
-
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
4702
|
+
queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
|
|
4684
4703
|
requestType: "json",
|
|
4685
|
-
body:
|
|
4704
|
+
body: _body,
|
|
4686
4705
|
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
4687
4706
|
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
4688
4707
|
abortSignal: requestOptions?.abortSignal,
|
|
@@ -4696,6 +4715,8 @@ var DocumentsClient = class {
|
|
|
4696
4715
|
switch (_response.error.statusCode) {
|
|
4697
4716
|
case 400:
|
|
4698
4717
|
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
4718
|
+
case 403:
|
|
4719
|
+
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
4699
4720
|
case 429:
|
|
4700
4721
|
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
4701
4722
|
default:
|
|
@@ -4962,7 +4983,7 @@ var DocumentsClient = class {
|
|
|
4962
4983
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "PATCH", "/v1/documents/{id}");
|
|
4963
4984
|
}
|
|
4964
4985
|
/**
|
|
4965
|
-
* Finds documents of a given type by
|
|
4986
|
+
* Finds documents of a given type by field value. Supported fields: `externalId` (the document's first-class external identifier — no schema declaration required) and any field declared as a lookup field on the bound schema. A lookup on a sensitive field is rejected here because the value would appear in the URL query string; use POST /v1/documents/lookup (the request-body variant) for a sensitive field instead. Results are paginated: set `limit` for the page size and feed the returned `nextCursor` back as `startFrom` to fetch the next page. The response is a `{data, nextCursor}` envelope. Requires the `documents:r` scope.
|
|
4966
4987
|
*
|
|
4967
4988
|
* @param {Vectros.LookupDocumentsRequest} request
|
|
4968
4989
|
* @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -5257,12 +5278,13 @@ var DocumentsClient = class {
|
|
|
5257
5278
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/documents/{id}/versions");
|
|
5258
5279
|
}
|
|
5259
5280
|
/**
|
|
5260
|
-
* Starts a file-based document by returning a short-lived presigned S3 PUT URL. Upload the file bytes directly to `uploadUrl`; the document is then automatically queued for text extraction and asynchronous indexing. Requires the `documents:c` scope.
|
|
5281
|
+
* Starts a file-based document by returning a short-lived presigned S3 PUT URL. Upload the file bytes directly to `uploadUrl`; the document is then automatically queued for text extraction and asynchronous indexing. Supplying an `externalId` makes this idempotent — re-initiating an upload with the same `externalId` re-issues a fresh presigned URL to the SAME existing document/object (so a re-upload inherently replaces the file body) rather than creating a duplicate. The response's `created` field (and the HTTP status — 201 when a new document was minted, 200 when an existing one was matched) tells the two apart. With `?upsert=true`, the submitted `payload`/`title` are also applied to the matched document (file-body divergence cannot be diffed at upload-init — the bytes have not arrived yet — so the re-upload itself replaces the body; `?upsert=true` requires the `documents:u` scope). Requires the `documents:c` scope.
|
|
5261
5282
|
*
|
|
5262
5283
|
* @param {Vectros.FileUploadRequest} request
|
|
5263
5284
|
* @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
5264
5285
|
*
|
|
5265
5286
|
* @throws {@link Vectros.BadRequestError}
|
|
5287
|
+
* @throws {@link Vectros.ForbiddenError}
|
|
5266
5288
|
* @throws {@link Vectros.TooManyRequestsError}
|
|
5267
5289
|
*
|
|
5268
5290
|
* @example
|
|
@@ -5275,6 +5297,10 @@ var DocumentsClient = class {
|
|
|
5275
5297
|
return HttpResponsePromise.fromPromise(this.__uploadDocument(request, requestOptions));
|
|
5276
5298
|
}
|
|
5277
5299
|
async __uploadDocument(request, requestOptions) {
|
|
5300
|
+
const { upsert, ..._body } = request;
|
|
5301
|
+
const _queryParams = {
|
|
5302
|
+
upsert
|
|
5303
|
+
};
|
|
5278
5304
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
5279
5305
|
const _headers = mergeHeaders(
|
|
5280
5306
|
_authRequest.headers,
|
|
@@ -5289,9 +5315,9 @@ var DocumentsClient = class {
|
|
|
5289
5315
|
method: "POST",
|
|
5290
5316
|
headers: _headers,
|
|
5291
5317
|
contentType: "application/json",
|
|
5292
|
-
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
5318
|
+
queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
|
|
5293
5319
|
requestType: "json",
|
|
5294
|
-
body:
|
|
5320
|
+
body: _body,
|
|
5295
5321
|
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
5296
5322
|
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
5297
5323
|
abortSignal: requestOptions?.abortSignal,
|
|
@@ -5305,6 +5331,8 @@ var DocumentsClient = class {
|
|
|
5305
5331
|
switch (_response.error.statusCode) {
|
|
5306
5332
|
case 400:
|
|
5307
5333
|
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
5334
|
+
case 403:
|
|
5335
|
+
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
5308
5336
|
case 429:
|
|
5309
5337
|
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
5310
5338
|
default:
|
|
@@ -5383,23 +5411,30 @@ var FoldersClient = class {
|
|
|
5383
5411
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/folders");
|
|
5384
5412
|
}
|
|
5385
5413
|
/**
|
|
5386
|
-
* Creates a folder to organize your documents and records. If `parentFolderId` is omitted, the folder is created under your context's default root folder. Requires the `folders:c` scope.
|
|
5414
|
+
* Creates a folder to organize your documents and records. If `parentFolderId` is omitted, the folder is created under your context's default root folder. Folder creation is idempotent by (slug + parent): if a folder with the same slug already exists under the same parent, that existing folder is returned unchanged instead of a duplicate being created. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing folder was returned) tells the two apart. To overwrite an existing folder's mutable fields instead of returning it unchanged, set `?upsert=true` (this also requires the `folders:u` scope). Requires the `folders:c` scope.
|
|
5387
5415
|
*
|
|
5388
|
-
* @param {Vectros.
|
|
5416
|
+
* @param {Vectros.CreateFolderRequest} request
|
|
5389
5417
|
* @param {FoldersClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
5390
5418
|
*
|
|
5391
5419
|
* @throws {@link Vectros.BadRequestError}
|
|
5420
|
+
* @throws {@link Vectros.ForbiddenError}
|
|
5392
5421
|
* @throws {@link Vectros.TooManyRequestsError}
|
|
5393
5422
|
*
|
|
5394
5423
|
* @example
|
|
5395
5424
|
* await client.folders.createFolder({
|
|
5396
|
-
*
|
|
5425
|
+
* body: {
|
|
5426
|
+
* name: "Patient Records 2024"
|
|
5427
|
+
* }
|
|
5397
5428
|
* })
|
|
5398
5429
|
*/
|
|
5399
5430
|
createFolder(request, requestOptions) {
|
|
5400
5431
|
return HttpResponsePromise.fromPromise(this.__createFolder(request, requestOptions));
|
|
5401
5432
|
}
|
|
5402
5433
|
async __createFolder(request, requestOptions) {
|
|
5434
|
+
const { upsert, body: _body } = request;
|
|
5435
|
+
const _queryParams = {
|
|
5436
|
+
upsert
|
|
5437
|
+
};
|
|
5403
5438
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
5404
5439
|
const _headers = mergeHeaders(
|
|
5405
5440
|
_authRequest.headers,
|
|
@@ -5414,9 +5449,9 @@ var FoldersClient = class {
|
|
|
5414
5449
|
method: "POST",
|
|
5415
5450
|
headers: _headers,
|
|
5416
5451
|
contentType: "application/json",
|
|
5417
|
-
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
5452
|
+
queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
|
|
5418
5453
|
requestType: "json",
|
|
5419
|
-
body:
|
|
5454
|
+
body: _body,
|
|
5420
5455
|
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
5421
5456
|
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
5422
5457
|
abortSignal: requestOptions?.abortSignal,
|
|
@@ -5430,6 +5465,8 @@ var FoldersClient = class {
|
|
|
5430
5465
|
switch (_response.error.statusCode) {
|
|
5431
5466
|
case 400:
|
|
5432
5467
|
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
5468
|
+
case 403:
|
|
5469
|
+
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
5433
5470
|
case 429:
|
|
5434
5471
|
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
5435
5472
|
default:
|
|
@@ -5838,23 +5875,30 @@ var IdentityClient = class {
|
|
|
5838
5875
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/clients");
|
|
5839
5876
|
}
|
|
5840
5877
|
/**
|
|
5841
|
-
* Creates a new client identity in your account. This call is idempotent on `externalId`: if a client with the same `externalId` already exists, the existing record is returned instead of creating a duplicate. Requires the `clients:c` scope.
|
|
5878
|
+
* Creates a new client identity in your account. This call is idempotent on `externalId`: if a client with the same `externalId` already exists, the existing record is returned instead of creating a duplicate. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing client was returned) tells the two apart. To overwrite an existing client's content instead of returning it unchanged, set `?upsert=true` (this also requires the `clients:u` scope). Requires the `clients:c` scope.
|
|
5842
5879
|
*
|
|
5843
|
-
* @param {Vectros.
|
|
5880
|
+
* @param {Vectros.CreateClientRequest} request
|
|
5844
5881
|
* @param {IdentityClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
5845
5882
|
*
|
|
5846
5883
|
* @throws {@link Vectros.BadRequestError}
|
|
5884
|
+
* @throws {@link Vectros.ForbiddenError}
|
|
5847
5885
|
* @throws {@link Vectros.TooManyRequestsError}
|
|
5848
5886
|
*
|
|
5849
5887
|
* @example
|
|
5850
5888
|
* await client.identity.createClient({
|
|
5851
|
-
*
|
|
5889
|
+
* body: {
|
|
5890
|
+
* externalId: "patient_789"
|
|
5891
|
+
* }
|
|
5852
5892
|
* })
|
|
5853
5893
|
*/
|
|
5854
5894
|
createClient(request, requestOptions) {
|
|
5855
5895
|
return HttpResponsePromise.fromPromise(this.__createClient(request, requestOptions));
|
|
5856
5896
|
}
|
|
5857
5897
|
async __createClient(request, requestOptions) {
|
|
5898
|
+
const { upsert, body: _body } = request;
|
|
5899
|
+
const _queryParams = {
|
|
5900
|
+
upsert
|
|
5901
|
+
};
|
|
5858
5902
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
5859
5903
|
const _headers = mergeHeaders(
|
|
5860
5904
|
_authRequest.headers,
|
|
@@ -5869,9 +5913,9 @@ var IdentityClient = class {
|
|
|
5869
5913
|
method: "POST",
|
|
5870
5914
|
headers: _headers,
|
|
5871
5915
|
contentType: "application/json",
|
|
5872
|
-
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
5916
|
+
queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
|
|
5873
5917
|
requestType: "json",
|
|
5874
|
-
body:
|
|
5918
|
+
body: _body,
|
|
5875
5919
|
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
5876
5920
|
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
5877
5921
|
abortSignal: requestOptions?.abortSignal,
|
|
@@ -5885,6 +5929,8 @@ var IdentityClient = class {
|
|
|
5885
5929
|
switch (_response.error.statusCode) {
|
|
5886
5930
|
case 400:
|
|
5887
5931
|
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
5932
|
+
case 403:
|
|
5933
|
+
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
5888
5934
|
case 429:
|
|
5889
5935
|
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
5890
5936
|
default:
|
|
@@ -6273,23 +6319,30 @@ var IdentityClient = class {
|
|
|
6273
6319
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/orgs");
|
|
6274
6320
|
}
|
|
6275
6321
|
/**
|
|
6276
|
-
* Creates a new organization in your account. This call is idempotent on `externalId`: if an organization with the same `externalId` already exists, the existing record is returned instead of creating a duplicate. Requires the `orgs:c` scope.
|
|
6322
|
+
* Creates a new organization in your account. This call is idempotent on `externalId`: if an organization with the same `externalId` already exists, the existing record is returned instead of creating a duplicate. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing organization was returned) tells the two apart. To overwrite an existing organization's content instead of returning it unchanged, set `?upsert=true` (this also requires the `orgs:u` scope). Requires the `orgs:c` scope.
|
|
6277
6323
|
*
|
|
6278
|
-
* @param {Vectros.
|
|
6324
|
+
* @param {Vectros.CreateOrgRequest} request
|
|
6279
6325
|
* @param {IdentityClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
6280
6326
|
*
|
|
6281
6327
|
* @throws {@link Vectros.BadRequestError}
|
|
6328
|
+
* @throws {@link Vectros.ForbiddenError}
|
|
6282
6329
|
* @throws {@link Vectros.TooManyRequestsError}
|
|
6283
6330
|
*
|
|
6284
6331
|
* @example
|
|
6285
6332
|
* await client.identity.createOrg({
|
|
6286
|
-
*
|
|
6333
|
+
* body: {
|
|
6334
|
+
* externalId: "clinic_001"
|
|
6335
|
+
* }
|
|
6287
6336
|
* })
|
|
6288
6337
|
*/
|
|
6289
6338
|
createOrg(request, requestOptions) {
|
|
6290
6339
|
return HttpResponsePromise.fromPromise(this.__createOrg(request, requestOptions));
|
|
6291
6340
|
}
|
|
6292
6341
|
async __createOrg(request, requestOptions) {
|
|
6342
|
+
const { upsert, body: _body } = request;
|
|
6343
|
+
const _queryParams = {
|
|
6344
|
+
upsert
|
|
6345
|
+
};
|
|
6293
6346
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
6294
6347
|
const _headers = mergeHeaders(
|
|
6295
6348
|
_authRequest.headers,
|
|
@@ -6304,9 +6357,9 @@ var IdentityClient = class {
|
|
|
6304
6357
|
method: "POST",
|
|
6305
6358
|
headers: _headers,
|
|
6306
6359
|
contentType: "application/json",
|
|
6307
|
-
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
6360
|
+
queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
|
|
6308
6361
|
requestType: "json",
|
|
6309
|
-
body:
|
|
6362
|
+
body: _body,
|
|
6310
6363
|
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
6311
6364
|
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
6312
6365
|
abortSignal: requestOptions?.abortSignal,
|
|
@@ -6320,6 +6373,8 @@ var IdentityClient = class {
|
|
|
6320
6373
|
switch (_response.error.statusCode) {
|
|
6321
6374
|
case 400:
|
|
6322
6375
|
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
6376
|
+
case 403:
|
|
6377
|
+
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
6323
6378
|
case 429:
|
|
6324
6379
|
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
6325
6380
|
default:
|
|
@@ -6694,23 +6749,30 @@ var IdentityClient = class {
|
|
|
6694
6749
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/users");
|
|
6695
6750
|
}
|
|
6696
6751
|
/**
|
|
6697
|
-
* Creates a user identity in your account. The operation is idempotent on `externalId`: if a user with the same `externalId` already exists, the existing record is returned instead of creating a duplicate. Requires the `users:c` scope.
|
|
6752
|
+
* Creates a user identity in your account. The operation is idempotent on `externalId`: if a user with the same `externalId` already exists, the existing record is returned instead of creating a duplicate. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing user was returned) tells the two apart. To overwrite an existing user's mutable fields (email, status, payload, schema binding) instead of returning it unchanged, set `?upsert=true` (this also requires the `users:u` scope). Requires the `users:c` scope.
|
|
6698
6753
|
*
|
|
6699
|
-
* @param {Vectros.
|
|
6754
|
+
* @param {Vectros.CreateUserRequest} request
|
|
6700
6755
|
* @param {IdentityClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
6701
6756
|
*
|
|
6702
6757
|
* @throws {@link Vectros.BadRequestError}
|
|
6758
|
+
* @throws {@link Vectros.ForbiddenError}
|
|
6703
6759
|
* @throws {@link Vectros.TooManyRequestsError}
|
|
6704
6760
|
*
|
|
6705
6761
|
* @example
|
|
6706
6762
|
* await client.identity.createUser({
|
|
6707
|
-
*
|
|
6763
|
+
* body: {
|
|
6764
|
+
* externalId: "usr_12345"
|
|
6765
|
+
* }
|
|
6708
6766
|
* })
|
|
6709
6767
|
*/
|
|
6710
6768
|
createUser(request, requestOptions) {
|
|
6711
6769
|
return HttpResponsePromise.fromPromise(this.__createUser(request, requestOptions));
|
|
6712
6770
|
}
|
|
6713
6771
|
async __createUser(request, requestOptions) {
|
|
6772
|
+
const { upsert, body: _body } = request;
|
|
6773
|
+
const _queryParams = {
|
|
6774
|
+
upsert
|
|
6775
|
+
};
|
|
6714
6776
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
6715
6777
|
const _headers = mergeHeaders(
|
|
6716
6778
|
_authRequest.headers,
|
|
@@ -6725,9 +6787,9 @@ var IdentityClient = class {
|
|
|
6725
6787
|
method: "POST",
|
|
6726
6788
|
headers: _headers,
|
|
6727
6789
|
contentType: "application/json",
|
|
6728
|
-
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
6790
|
+
queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
|
|
6729
6791
|
requestType: "json",
|
|
6730
|
-
body:
|
|
6792
|
+
body: _body,
|
|
6731
6793
|
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
6732
6794
|
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
6733
6795
|
abortSignal: requestOptions?.abortSignal,
|
|
@@ -6741,6 +6803,8 @@ var IdentityClient = class {
|
|
|
6741
6803
|
switch (_response.error.statusCode) {
|
|
6742
6804
|
case 400:
|
|
6743
6805
|
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
6806
|
+
case 403:
|
|
6807
|
+
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
6744
6808
|
case 429:
|
|
6745
6809
|
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
6746
6810
|
default:
|
|
@@ -7558,9 +7622,9 @@ var RecordsClient = class {
|
|
|
7558
7622
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/records");
|
|
7559
7623
|
}
|
|
7560
7624
|
/**
|
|
7561
|
-
* Creates a new record of a given type. The `payload` is validated against that type's schema before the record is stored. Identify the type by sending `typeName`, `schemaId`, or both (they must agree); if you send only `schemaId`, the type is taken from that schema. Optionally supply an `externalId` to make the create idempotent — if a record with the same `externalId` already exists in your context, that existing record is returned unchanged instead of a duplicate being created. Requires the `records:c:<type>` scope.
|
|
7625
|
+
* Creates a new record of a given type. The `payload` is validated against that type's schema before the record is stored. Identify the type by sending `typeName`, `schemaId`, or both (they must agree); if you send only `schemaId`, the type is taken from that schema. Optionally supply an `externalId` to make the create idempotent — if a record with the same `externalId` already exists in your context, that existing record is returned unchanged instead of a duplicate being created. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing record was returned) tells the two apart. To overwrite an existing record's content instead of returning it unchanged, set `?upsert=true` (this also requires the `records:u:<type>` scope). Requires the `records:c:<type>` scope.
|
|
7562
7626
|
*
|
|
7563
|
-
* @param {Vectros.
|
|
7627
|
+
* @param {Vectros.CreateRecordRequest} request
|
|
7564
7628
|
* @param {RecordsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
7565
7629
|
*
|
|
7566
7630
|
* @throws {@link Vectros.BadRequestError}
|
|
@@ -7569,19 +7633,25 @@ var RecordsClient = class {
|
|
|
7569
7633
|
*
|
|
7570
7634
|
* @example
|
|
7571
7635
|
* await client.records.createRecord({
|
|
7572
|
-
*
|
|
7573
|
-
*
|
|
7574
|
-
*
|
|
7575
|
-
*
|
|
7576
|
-
*
|
|
7577
|
-
*
|
|
7578
|
-
*
|
|
7636
|
+
* body: {
|
|
7637
|
+
* typeName: "intake_form",
|
|
7638
|
+
* schemaId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
|
|
7639
|
+
* payload: {
|
|
7640
|
+
* "first_name": "Jane",
|
|
7641
|
+
* "email": "jane@example.com"
|
|
7642
|
+
* },
|
|
7643
|
+
* folderId: "f47ac10b-58cc-4372-a567-0e02b2c3d479"
|
|
7644
|
+
* }
|
|
7579
7645
|
* })
|
|
7580
7646
|
*/
|
|
7581
7647
|
createRecord(request, requestOptions) {
|
|
7582
7648
|
return HttpResponsePromise.fromPromise(this.__createRecord(request, requestOptions));
|
|
7583
7649
|
}
|
|
7584
7650
|
async __createRecord(request, requestOptions) {
|
|
7651
|
+
const { upsert, body: _body } = request;
|
|
7652
|
+
const _queryParams = {
|
|
7653
|
+
upsert
|
|
7654
|
+
};
|
|
7585
7655
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
7586
7656
|
const _headers = mergeHeaders(
|
|
7587
7657
|
_authRequest.headers,
|
|
@@ -7596,9 +7666,9 @@ var RecordsClient = class {
|
|
|
7596
7666
|
method: "POST",
|
|
7597
7667
|
headers: _headers,
|
|
7598
7668
|
contentType: "application/json",
|
|
7599
|
-
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
7669
|
+
queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
|
|
7600
7670
|
requestType: "json",
|
|
7601
|
-
body:
|
|
7671
|
+
body: _body,
|
|
7602
7672
|
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
7603
7673
|
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
7604
7674
|
abortSignal: requestOptions?.abortSignal,
|
|
@@ -8194,43 +8264,50 @@ var SchemasClient = class {
|
|
|
8194
8264
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/schemas");
|
|
8195
8265
|
}
|
|
8196
8266
|
/**
|
|
8197
|
-
* Defines a new record type with optional field definitions, validation rules, and lookup indexes. Idempotent by `typeName` within the same ownership scope: re-creating an existing `typeName` returns the existing schema rather than failing. Requires the `schemas:w` scope.
|
|
8267
|
+
* Defines a new record type with optional field definitions, validation rules, and lookup indexes. Idempotent by `typeName` within the same ownership scope: re-creating an existing `typeName` returns the existing schema rather than failing. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing schema was returned) tells the two apart. To reconcile an existing schema to the submitted shape instead of returning it unchanged, set `?upsert=true` (this also requires the `schemas:w` scope; only legal schema changes are applied — migration-locked changes are rejected). Requires the `schemas:w` scope.
|
|
8198
8268
|
*
|
|
8199
|
-
* @param {Vectros.
|
|
8269
|
+
* @param {Vectros.CreateSchemaRequest} request
|
|
8200
8270
|
* @param {SchemasClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
8201
8271
|
*
|
|
8202
8272
|
* @throws {@link Vectros.BadRequestError}
|
|
8273
|
+
* @throws {@link Vectros.ForbiddenError}
|
|
8203
8274
|
* @throws {@link Vectros.TooManyRequestsError}
|
|
8204
8275
|
*
|
|
8205
8276
|
* @example
|
|
8206
8277
|
* await client.schemas.createSchema({
|
|
8207
|
-
*
|
|
8208
|
-
*
|
|
8209
|
-
*
|
|
8210
|
-
*
|
|
8211
|
-
*
|
|
8212
|
-
*
|
|
8213
|
-
*
|
|
8214
|
-
*
|
|
8215
|
-
*
|
|
8216
|
-
*
|
|
8217
|
-
*
|
|
8218
|
-
*
|
|
8219
|
-
*
|
|
8220
|
-
*
|
|
8221
|
-
*
|
|
8222
|
-
*
|
|
8223
|
-
*
|
|
8224
|
-
*
|
|
8225
|
-
*
|
|
8226
|
-
*
|
|
8227
|
-
*
|
|
8278
|
+
* body: {
|
|
8279
|
+
* typeName: "intake_form",
|
|
8280
|
+
* displayName: "Client Intake Form",
|
|
8281
|
+
* description: "Captures initial client information",
|
|
8282
|
+
* fields: [{
|
|
8283
|
+
* fieldId: "first_name",
|
|
8284
|
+
* fieldType: "string",
|
|
8285
|
+
* required: true,
|
|
8286
|
+
* searchable: true
|
|
8287
|
+
* }, {
|
|
8288
|
+
* fieldId: "email",
|
|
8289
|
+
* fieldType: "string",
|
|
8290
|
+
* required: true
|
|
8291
|
+
* }],
|
|
8292
|
+
* lookupFields: [{
|
|
8293
|
+
* fieldName: "email",
|
|
8294
|
+
* unique: true
|
|
8295
|
+
* }],
|
|
8296
|
+
* capabilities: {
|
|
8297
|
+
* "auditHistory": true
|
|
8298
|
+
* },
|
|
8299
|
+
* allowedSurfaces: ["record"]
|
|
8300
|
+
* }
|
|
8228
8301
|
* })
|
|
8229
8302
|
*/
|
|
8230
8303
|
createSchema(request, requestOptions) {
|
|
8231
8304
|
return HttpResponsePromise.fromPromise(this.__createSchema(request, requestOptions));
|
|
8232
8305
|
}
|
|
8233
8306
|
async __createSchema(request, requestOptions) {
|
|
8307
|
+
const { upsert, body: _body } = request;
|
|
8308
|
+
const _queryParams = {
|
|
8309
|
+
upsert
|
|
8310
|
+
};
|
|
8234
8311
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
8235
8312
|
const _headers = mergeHeaders(
|
|
8236
8313
|
_authRequest.headers,
|
|
@@ -8245,9 +8322,9 @@ var SchemasClient = class {
|
|
|
8245
8322
|
method: "POST",
|
|
8246
8323
|
headers: _headers,
|
|
8247
8324
|
contentType: "application/json",
|
|
8248
|
-
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
8325
|
+
queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
|
|
8249
8326
|
requestType: "json",
|
|
8250
|
-
body:
|
|
8327
|
+
body: _body,
|
|
8251
8328
|
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
8252
8329
|
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
8253
8330
|
abortSignal: requestOptions?.abortSignal,
|
|
@@ -8261,6 +8338,8 @@ var SchemasClient = class {
|
|
|
8261
8338
|
switch (_response.error.statusCode) {
|
|
8262
8339
|
case 400:
|
|
8263
8340
|
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
8341
|
+
case 403:
|
|
8342
|
+
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
8264
8343
|
case 429:
|
|
8265
8344
|
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
8266
8345
|
default:
|
|
@@ -8811,11 +8890,36 @@ var BlueprintServicePrincipalSchema = import_zod.z.object({
|
|
|
8811
8890
|
externalId: import_zod.z.string().min(1),
|
|
8812
8891
|
displayName: import_zod.z.string().min(1)
|
|
8813
8892
|
}).strict();
|
|
8814
|
-
var
|
|
8893
|
+
var SeedCommonShape = {
|
|
8894
|
+
/** The schema typeName this seed instantiates (should match a declared schema). */
|
|
8815
8895
|
typeName: import_zod.z.string().min(1),
|
|
8816
|
-
|
|
8896
|
+
/**
|
|
8897
|
+
* Stable, caller-supplied id — the loader's idempotency key AND the value other
|
|
8898
|
+
* seeds resolve a `reference` against (across surfaces: a record seed may
|
|
8899
|
+
* reference a document seed by its externalId, and vice versa).
|
|
8900
|
+
*/
|
|
8901
|
+
externalId: import_zod.z.string().min(1)
|
|
8902
|
+
};
|
|
8903
|
+
var RecordSeedSchema = import_zod.z.object({
|
|
8904
|
+
surface: import_zod.z.literal("record"),
|
|
8905
|
+
...SeedCommonShape,
|
|
8906
|
+
/** The record payload, validated against the bound schema. */
|
|
8817
8907
|
fields: import_zod.z.record(import_zod.z.unknown())
|
|
8818
8908
|
}).strict();
|
|
8909
|
+
var DocumentSeedSchema = import_zod.z.object({
|
|
8910
|
+
surface: import_zod.z.literal("document"),
|
|
8911
|
+
...SeedCommonShape,
|
|
8912
|
+
/** Human-readable document title — REQUIRED by the text-ingest path. */
|
|
8913
|
+
title: import_zod.z.string().min(1),
|
|
8914
|
+
/** Raw text content to ingest + index — REQUIRED and non-empty (the platform rejects a blank ingest). */
|
|
8915
|
+
text: import_zod.z.string().min(1),
|
|
8916
|
+
/** Optional structured payload bound to the schema (the document's `fields`). */
|
|
8917
|
+
fields: import_zod.z.record(import_zod.z.unknown()).optional()
|
|
8918
|
+
}).strict();
|
|
8919
|
+
var BlueprintSeedRecordSchema = import_zod.z.discriminatedUnion("surface", [
|
|
8920
|
+
RecordSeedSchema,
|
|
8921
|
+
DocumentSeedSchema
|
|
8922
|
+
]);
|
|
8819
8923
|
var SELF_TOKEN_RE = /\$\{\{\s*self\.[A-Za-z_]\w*\s*\}\}/;
|
|
8820
8924
|
function lintSelfTokenPlacement(value, ctx) {
|
|
8821
8925
|
const walk = (node, path4, inRoleDataScope) => {
|
|
@@ -8871,6 +8975,23 @@ function lintIdentityRefsDeclared(value, ctx) {
|
|
|
8871
8975
|
};
|
|
8872
8976
|
walk(value, []);
|
|
8873
8977
|
}
|
|
8978
|
+
function lintSeedSurfaces(value, ctx) {
|
|
8979
|
+
const bp = value;
|
|
8980
|
+
if (!Array.isArray(bp.seed)) return;
|
|
8981
|
+
const byType = new Map((bp.schemas ?? []).map((s) => [s.typeName, s]));
|
|
8982
|
+
bp.seed.forEach((seed, i) => {
|
|
8983
|
+
const schema = byType.get(seed.typeName);
|
|
8984
|
+
if (!schema) return;
|
|
8985
|
+
const allowed = schema.allowedSurfaces ?? ["record"];
|
|
8986
|
+
if (!allowed.includes(seed.surface ?? "record")) {
|
|
8987
|
+
ctx.addIssue({
|
|
8988
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
8989
|
+
path: ["seed", i, "surface"],
|
|
8990
|
+
message: `seed '${seed.externalId}' uses surface '${seed.surface ?? "record"}', but schema '${seed.typeName}' allows only [${allowed.join(", ")}]`
|
|
8991
|
+
});
|
|
8992
|
+
}
|
|
8993
|
+
});
|
|
8994
|
+
}
|
|
8874
8995
|
var BlueprintSchema = import_zod.z.object({
|
|
8875
8996
|
/** Stable blueprint id (the `--blueprint <name>` selector + idempotency key). */
|
|
8876
8997
|
name: import_zod.z.string().min(1),
|
|
@@ -8893,6 +9014,7 @@ var BlueprintSchema = import_zod.z.object({
|
|
|
8893
9014
|
}).strict().superRefine((bp, ctx) => {
|
|
8894
9015
|
lintSelfTokenPlacement(bp, ctx);
|
|
8895
9016
|
lintIdentityRefsDeclared(bp, ctx);
|
|
9017
|
+
lintSeedSurfaces(bp, ctx);
|
|
8896
9018
|
});
|
|
8897
9019
|
function formatIssuePath(path4) {
|
|
8898
9020
|
let out = "";
|
|
@@ -9290,6 +9412,7 @@ var taskManagement = {
|
|
|
9290
9412
|
},
|
|
9291
9413
|
seed: [
|
|
9292
9414
|
{
|
|
9415
|
+
surface: "record",
|
|
9293
9416
|
typeName: "task",
|
|
9294
9417
|
externalId: "seed-welcome",
|
|
9295
9418
|
fields: {
|
|
@@ -9517,6 +9640,7 @@ var codingAgentMemory = {
|
|
|
9517
9640
|
// Seeded FIRST so the convention below resolves its reference to it — the
|
|
9518
9641
|
// loader creates seeds in array order, and a reference target must exist when
|
|
9519
9642
|
// the referencing record is written.
|
|
9643
|
+
surface: "record",
|
|
9520
9644
|
typeName: "decision",
|
|
9521
9645
|
externalId: "seed-use-vectros-for-memory",
|
|
9522
9646
|
fields: {
|
|
@@ -9532,6 +9656,7 @@ var codingAgentMemory = {
|
|
|
9532
9656
|
// Demonstrates a live typed link at bootstrap: this convention references the
|
|
9533
9657
|
// decision above by its externalId, so "open the decision behind this rule"
|
|
9534
9658
|
// works the moment the blueprint is applied.
|
|
9659
|
+
surface: "record",
|
|
9535
9660
|
typeName: "convention",
|
|
9536
9661
|
externalId: "seed-record-the-why",
|
|
9537
9662
|
fields: {
|
|
@@ -9546,145 +9671,841 @@ var codingAgentMemory = {
|
|
|
9546
9671
|
]
|
|
9547
9672
|
};
|
|
9548
9673
|
var coding_agent_memory_default = codingAgentMemory;
|
|
9549
|
-
var
|
|
9550
|
-
|
|
9674
|
+
var DATA_PLANE_ACTIONS = [
|
|
9675
|
+
"records:r",
|
|
9676
|
+
"records:c",
|
|
9677
|
+
"records:u",
|
|
9678
|
+
"search:r",
|
|
9679
|
+
"schemas:r",
|
|
9680
|
+
"inference:r",
|
|
9681
|
+
"documents:r",
|
|
9682
|
+
"documents:c",
|
|
9683
|
+
"folders:r",
|
|
9684
|
+
"folders:c"
|
|
9685
|
+
];
|
|
9686
|
+
var agenticSdlc = {
|
|
9687
|
+
name: "agentic-sdlc",
|
|
9551
9688
|
version: "1.0.0",
|
|
9552
|
-
description: "A
|
|
9553
|
-
contextId: "
|
|
9554
|
-
contextName: "
|
|
9689
|
+
description: "A whole-SDLC system of record for an AI development team \u2014 decisions, designs, references, runbooks, post-mortems (as documents) plus controls, conventions, gotchas, and a glossary (as records), cross-linked and recalled by meaning.",
|
|
9690
|
+
contextId: "agentic-sdlc",
|
|
9691
|
+
contextName: "Agentic SDLC Knowledge Base",
|
|
9555
9692
|
schemas: [
|
|
9693
|
+
// ============================ DOCUMENTS ============================
|
|
9694
|
+
// Content-dominant: the markdown body is the artifact; fields are metadata.
|
|
9695
|
+
// Each binds the `document` surface and is ingested via `document_ingest`.
|
|
9556
9696
|
{
|
|
9557
|
-
|
|
9558
|
-
|
|
9697
|
+
// An architecture/product DECISION (an ADR). The body is the prose —
|
|
9698
|
+
// Context / Decision / Consequences — and is what `rag_ask` answers over;
|
|
9699
|
+
// the fields below are filter/sort metadata + the supersede chain. The
|
|
9700
|
+
// anchor of the graph: most other types link back to a decision.
|
|
9701
|
+
typeName: "decision",
|
|
9702
|
+
displayName: "Decision (ADR)",
|
|
9559
9703
|
indexMode: "HYBRID",
|
|
9560
|
-
|
|
9704
|
+
allowedSurfaces: ["document"],
|
|
9705
|
+
// A document carries an INTRINSIC title (the ingest title) + body; the schema
|
|
9706
|
+
// declares only the metadata BEYOND those. (No typed `title` field — that
|
|
9707
|
+
// would duplicate the document's own title.)
|
|
9561
9708
|
fields: [
|
|
9562
9709
|
{
|
|
9563
|
-
fieldId: "
|
|
9710
|
+
fieldId: "summary",
|
|
9564
9711
|
fieldType: "string",
|
|
9565
|
-
required: true,
|
|
9566
9712
|
searchable: true,
|
|
9567
|
-
|
|
9568
|
-
renderHints: { label: "
|
|
9713
|
+
description: "A one-paragraph abstract of the decision. The full reasoning is the document body.",
|
|
9714
|
+
renderHints: { label: "Summary", widget: "textarea", order: 2 }
|
|
9569
9715
|
},
|
|
9570
9716
|
{
|
|
9571
|
-
fieldId: "
|
|
9572
|
-
fieldType: "
|
|
9573
|
-
|
|
9574
|
-
|
|
9575
|
-
|
|
9576
|
-
renderHints: { label: "Body", widget: "textarea", order: 2, section: "Note" }
|
|
9717
|
+
fieldId: "status",
|
|
9718
|
+
fieldType: "enum",
|
|
9719
|
+
filterable: true,
|
|
9720
|
+
enumValues: ["proposed", "accepted", "superseded", "deprecated"],
|
|
9721
|
+
renderHints: { label: "Status", widget: "select", order: 3 }
|
|
9577
9722
|
},
|
|
9578
9723
|
{
|
|
9579
|
-
fieldId: "
|
|
9580
|
-
fieldType: "
|
|
9724
|
+
fieldId: "area",
|
|
9725
|
+
fieldType: "string",
|
|
9581
9726
|
filterable: true,
|
|
9582
|
-
description: '
|
|
9583
|
-
renderHints: { label: "
|
|
9727
|
+
description: 'Subsystem the decision applies to (e.g. "search", "auth", "billing").',
|
|
9728
|
+
renderHints: { label: "Area", widget: "text", order: 4 }
|
|
9584
9729
|
},
|
|
9585
9730
|
{
|
|
9586
|
-
|
|
9587
|
-
|
|
9588
|
-
// `other` keeps it from being a straitjacket for a personal brain-dump.
|
|
9589
|
-
fieldId: "source",
|
|
9590
|
-
fieldType: "enum",
|
|
9731
|
+
fieldId: "tags",
|
|
9732
|
+
fieldType: "array",
|
|
9591
9733
|
filterable: true,
|
|
9592
|
-
|
|
9593
|
-
|
|
9594
|
-
renderHints: { label: "Source", widget: "select", order: 4, section: "Organize" }
|
|
9734
|
+
description: 'Freeform labels (e.g. "security", "schema"). Search-side filter.',
|
|
9735
|
+
renderHints: { label: "Tags", order: 5 }
|
|
9595
9736
|
},
|
|
9596
9737
|
{
|
|
9597
|
-
|
|
9598
|
-
|
|
9599
|
-
|
|
9600
|
-
|
|
9601
|
-
|
|
9602
|
-
|
|
9738
|
+
// Self-reference (document → document): the decision this one supersedes.
|
|
9739
|
+
fieldId: "supersedes",
|
|
9740
|
+
fieldType: "reference",
|
|
9741
|
+
targetTypeName: "decision",
|
|
9742
|
+
targetSurface: "document",
|
|
9743
|
+
targetField: "externalId",
|
|
9744
|
+
cardinality: "one",
|
|
9745
|
+
description: "The decision this one supersedes (by externalId).",
|
|
9746
|
+
renderHints: { label: "Supersedes", order: 6 }
|
|
9603
9747
|
},
|
|
9604
9748
|
{
|
|
9605
|
-
fieldId: "
|
|
9749
|
+
fieldId: "date",
|
|
9606
9750
|
fieldType: "date",
|
|
9607
|
-
description:
|
|
9608
|
-
renderHints: { label: "
|
|
9751
|
+
description: "ISO-8601 decision date. Range-queryable / sortable.",
|
|
9752
|
+
renderHints: { label: "Date", widget: "date", order: 7 }
|
|
9609
9753
|
}
|
|
9610
|
-
// externalId is the record's FIRST-CLASS identifier (the dedup/upsert key + the
|
|
9611
|
-
// value a `reference` resolves against) — not a payload field; the loader sends
|
|
9612
|
-
// it top-level on the RecordRequest.
|
|
9613
9754
|
],
|
|
9614
|
-
|
|
9615
|
-
|
|
9616
|
-
// • `source` — EQUALITY, sorted by `lastUpdated`: enumerate notes from one
|
|
9617
|
-
// source, most-recently-touched first. `lastUpdated` is always present, so
|
|
9618
|
-
// the sort never drops a note (sorting an equality lookup by an OPTIONAL
|
|
9619
|
-
// field would silently exclude rows that lack it).
|
|
9620
|
-
// • `status` — EQUALITY: "show archived notes".
|
|
9621
|
-
// • `capturedAt` — RANGE: ordered `from`/`to`/`prefix` ("captured in 2026-06").
|
|
9622
|
-
// `externalId` has a built-in first-class finder — look it up directly, never
|
|
9623
|
-
// redeclare it here. Equality lookups use the 7 fast index slots (2 used here);
|
|
9624
|
-
// range lookups use a relationship row, billed at the range rate.
|
|
9625
|
-
lookupFields: [
|
|
9626
|
-
{ fieldName: "source", sortBy: "lastUpdated" },
|
|
9627
|
-
"status",
|
|
9628
|
-
{ fieldName: "capturedAt", rangeEnabled: true }
|
|
9629
|
-
]
|
|
9630
|
-
}
|
|
9631
|
-
],
|
|
9632
|
-
// Least-privilege profile — r/c/u + search + schema discovery + inference:r
|
|
9633
|
-
// (so the agent can `rag_ask` a grounded, cited question over your notes). No
|
|
9634
|
-
// records:d: notes are archived (status → 'archived'), never deleted.
|
|
9635
|
-
accessProfile: {
|
|
9636
|
-
allowedActions: ["records:r", "records:c", "records:u", "search:r", "schemas:r", "inference:r"]
|
|
9637
|
-
},
|
|
9638
|
-
servicePrincipal: {
|
|
9639
|
-
externalId: "second-brain",
|
|
9640
|
-
displayName: "Second Brain"
|
|
9641
|
-
},
|
|
9642
|
-
seed: [
|
|
9643
|
-
{
|
|
9644
|
-
typeName: "note",
|
|
9645
|
-
externalId: "seed-welcome",
|
|
9646
|
-
fields: {
|
|
9647
|
-
title: "Welcome to your Second Brain",
|
|
9648
|
-
body: 'Capture anything here \u2014 ideas, links, meeting notes \u2014 then ask your agent things like "what did I note about onboarding?" and it recalls by meaning, not just keywords.',
|
|
9649
|
-
tags: ["getting-started"],
|
|
9650
|
-
source: "thought",
|
|
9651
|
-
status: "active",
|
|
9652
|
-
capturedAt: "2026-06-14"
|
|
9653
|
-
}
|
|
9654
|
-
}
|
|
9655
|
-
]
|
|
9656
|
-
};
|
|
9657
|
-
var second_brain_default = secondBrain;
|
|
9658
|
-
var clinicalIntake = {
|
|
9659
|
-
name: "clinical-intake",
|
|
9660
|
-
version: "1.0.0",
|
|
9661
|
-
description: "Behavioral-health intake with PHI fields \u2014 demonstrates redact-at-write, audit history, blind-index lookup, and search exclusion. Synthetic data only.",
|
|
9662
|
-
contextId: "clinical-intake",
|
|
9663
|
-
contextName: "Clinical Intake",
|
|
9664
|
-
schemas: [
|
|
9755
|
+
lookupFields: ["status", "area", "supersedes", { fieldName: "date", rangeEnabled: true }]
|
|
9756
|
+
},
|
|
9665
9757
|
{
|
|
9666
|
-
|
|
9667
|
-
|
|
9758
|
+
// A DESIGN doc or spec — the exploration that drives a decision. Distinct
|
|
9759
|
+
// from `decision` (a different browse genre + it links to the decision it
|
|
9760
|
+
// informs). Body = the design narrative.
|
|
9761
|
+
typeName: "design",
|
|
9762
|
+
displayName: "Design",
|
|
9668
9763
|
indexMode: "HYBRID",
|
|
9669
|
-
|
|
9670
|
-
//
|
|
9764
|
+
allowedSurfaces: ["document"],
|
|
9765
|
+
// Intrinsic title + body; schema = metadata beyond those (no typed `title`).
|
|
9671
9766
|
fields: [
|
|
9672
|
-
// --- Non-sensitive, searchable/filterable working fields ---
|
|
9673
9767
|
{
|
|
9674
|
-
fieldId: "
|
|
9768
|
+
fieldId: "summary",
|
|
9675
9769
|
fieldType: "string",
|
|
9676
|
-
|
|
9677
|
-
description: "
|
|
9678
|
-
|
|
9679
|
-
renderHints: { label: "Case ID", widget: "text", order: 1, section: "Intake", displayField: true }
|
|
9770
|
+
searchable: true,
|
|
9771
|
+
description: "A one-paragraph abstract. The full design is the document body.",
|
|
9772
|
+
renderHints: { label: "Summary", widget: "textarea", order: 2 }
|
|
9680
9773
|
},
|
|
9681
9774
|
{
|
|
9682
|
-
fieldId: "
|
|
9683
|
-
fieldType: "
|
|
9684
|
-
|
|
9685
|
-
|
|
9686
|
-
|
|
9687
|
-
|
|
9775
|
+
fieldId: "status",
|
|
9776
|
+
fieldType: "enum",
|
|
9777
|
+
filterable: true,
|
|
9778
|
+
enumValues: ["draft", "active", "implemented", "superseded"],
|
|
9779
|
+
renderHints: { label: "Status", widget: "select", order: 3 }
|
|
9780
|
+
},
|
|
9781
|
+
{
|
|
9782
|
+
fieldId: "area",
|
|
9783
|
+
fieldType: "string",
|
|
9784
|
+
filterable: true,
|
|
9785
|
+
renderHints: { label: "Area", widget: "text", order: 4 }
|
|
9786
|
+
},
|
|
9787
|
+
{
|
|
9788
|
+
fieldId: "tags",
|
|
9789
|
+
fieldType: "array",
|
|
9790
|
+
filterable: true,
|
|
9791
|
+
renderHints: { label: "Tags", order: 5 }
|
|
9792
|
+
},
|
|
9793
|
+
{
|
|
9794
|
+
// Cross-document edge: the decision this design informs/produces.
|
|
9795
|
+
fieldId: "relatedDecision",
|
|
9796
|
+
fieldType: "reference",
|
|
9797
|
+
targetTypeName: "decision",
|
|
9798
|
+
targetSurface: "document",
|
|
9799
|
+
targetField: "externalId",
|
|
9800
|
+
cardinality: "one",
|
|
9801
|
+
description: "The decision this design informs (by externalId).",
|
|
9802
|
+
renderHints: { label: "Related decision", order: 6 }
|
|
9803
|
+
},
|
|
9804
|
+
{
|
|
9805
|
+
// Self-reference: a design supersedes an earlier design/spec.
|
|
9806
|
+
fieldId: "supersedes",
|
|
9807
|
+
fieldType: "reference",
|
|
9808
|
+
targetTypeName: "design",
|
|
9809
|
+
targetSurface: "document",
|
|
9810
|
+
targetField: "externalId",
|
|
9811
|
+
cardinality: "one",
|
|
9812
|
+
description: "The design this one supersedes (by externalId).",
|
|
9813
|
+
renderHints: { label: "Supersedes", order: 7 }
|
|
9814
|
+
},
|
|
9815
|
+
{
|
|
9816
|
+
fieldId: "updatedOn",
|
|
9817
|
+
fieldType: "date",
|
|
9818
|
+
description: "ISO-8601 \u2014 when last revised. Range-queryable.",
|
|
9819
|
+
renderHints: { label: "Updated on", widget: "date", order: 8 }
|
|
9820
|
+
}
|
|
9821
|
+
],
|
|
9822
|
+
lookupFields: ["status", "area", "relatedDecision", "supersedes", { fieldName: "updatedOn", rangeEnabled: true }]
|
|
9823
|
+
},
|
|
9824
|
+
{
|
|
9825
|
+
// A REFERENCE doc — maintained "how it works / how to": guides, onboarding,
|
|
9826
|
+
// API docs, process docs. Body = the prose. Distinct shape: a `category`
|
|
9827
|
+
// (DRY sub-labels — onboarding/api/process are same shape) and `lastReviewed`
|
|
9828
|
+
// (freshness is the whole game for reference material).
|
|
9829
|
+
typeName: "reference",
|
|
9830
|
+
displayName: "Reference",
|
|
9831
|
+
indexMode: "HYBRID",
|
|
9832
|
+
allowedSurfaces: ["document"],
|
|
9833
|
+
// Intrinsic title + body; schema = metadata beyond those (no typed `title`).
|
|
9834
|
+
fields: [
|
|
9835
|
+
{
|
|
9836
|
+
fieldId: "summary",
|
|
9837
|
+
fieldType: "string",
|
|
9838
|
+
searchable: true,
|
|
9839
|
+
description: "A one-paragraph abstract. The full reference is the document body.",
|
|
9840
|
+
renderHints: { label: "Summary", widget: "textarea", order: 2 }
|
|
9841
|
+
},
|
|
9842
|
+
{
|
|
9843
|
+
// The sub-kind, as a filterable field (not a separate schema — these are
|
|
9844
|
+
// the same shape, differing only by label, so a field is the DRY choice).
|
|
9845
|
+
fieldId: "category",
|
|
9846
|
+
fieldType: "enum",
|
|
9847
|
+
filterable: true,
|
|
9848
|
+
enumValues: ["guide", "onboarding", "api", "process", "other"],
|
|
9849
|
+
renderHints: { label: "Category", widget: "select", order: 3 }
|
|
9850
|
+
},
|
|
9851
|
+
{
|
|
9852
|
+
fieldId: "area",
|
|
9853
|
+
fieldType: "string",
|
|
9854
|
+
filterable: true,
|
|
9855
|
+
renderHints: { label: "Area", widget: "text", order: 4 }
|
|
9856
|
+
},
|
|
9857
|
+
{
|
|
9858
|
+
fieldId: "tags",
|
|
9859
|
+
fieldType: "array",
|
|
9860
|
+
filterable: true,
|
|
9861
|
+
renderHints: { label: "Tags", order: 5 }
|
|
9862
|
+
},
|
|
9863
|
+
{
|
|
9864
|
+
fieldId: "status",
|
|
9865
|
+
fieldType: "enum",
|
|
9866
|
+
filterable: true,
|
|
9867
|
+
enumValues: ["active", "superseded"],
|
|
9868
|
+
renderHints: { label: "Status", widget: "select", order: 6 }
|
|
9869
|
+
},
|
|
9870
|
+
{
|
|
9871
|
+
// Optional cross-document edge: the decision behind this reference.
|
|
9872
|
+
fieldId: "relatedDecision",
|
|
9873
|
+
fieldType: "reference",
|
|
9874
|
+
targetTypeName: "decision",
|
|
9875
|
+
targetSurface: "document",
|
|
9876
|
+
targetField: "externalId",
|
|
9877
|
+
cardinality: "one",
|
|
9878
|
+
description: "The decision behind this reference, if any (by externalId).",
|
|
9879
|
+
renderHints: { label: "Related decision", order: 7 }
|
|
9880
|
+
},
|
|
9881
|
+
{
|
|
9882
|
+
// Freshness — "references not reviewed since X". The reference-doc signal.
|
|
9883
|
+
fieldId: "lastReviewed",
|
|
9884
|
+
fieldType: "date",
|
|
9885
|
+
description: "ISO-8601 \u2014 when this reference was last verified current. Range-queryable.",
|
|
9886
|
+
renderHints: { label: "Last reviewed", widget: "date", order: 8 }
|
|
9887
|
+
}
|
|
9888
|
+
],
|
|
9889
|
+
lookupFields: ["category", "area", "status", "relatedDecision", { fieldName: "lastReviewed", rangeEnabled: true }]
|
|
9890
|
+
},
|
|
9891
|
+
{
|
|
9892
|
+
// A RUNBOOK — a step-by-step operational procedure (deploy, release, recover).
|
|
9893
|
+
// Body = the procedure. Distinct browse genre, and often BORN FROM a
|
|
9894
|
+
// post-mortem (its resolution, codified) — a cross-document edge.
|
|
9895
|
+
typeName: "runbook",
|
|
9896
|
+
displayName: "Runbook",
|
|
9897
|
+
indexMode: "HYBRID",
|
|
9898
|
+
allowedSurfaces: ["document"],
|
|
9899
|
+
// Intrinsic title + body; schema = metadata beyond those (no typed `title`).
|
|
9900
|
+
fields: [
|
|
9901
|
+
{
|
|
9902
|
+
fieldId: "summary",
|
|
9903
|
+
fieldType: "string",
|
|
9904
|
+
searchable: true,
|
|
9905
|
+
description: "When to use this runbook \u2014 the trigger. The steps are the document body.",
|
|
9906
|
+
renderHints: { label: "Summary", widget: "textarea", order: 2 }
|
|
9907
|
+
},
|
|
9908
|
+
{
|
|
9909
|
+
fieldId: "area",
|
|
9910
|
+
fieldType: "string",
|
|
9911
|
+
filterable: true,
|
|
9912
|
+
renderHints: { label: "Area", widget: "text", order: 3 }
|
|
9913
|
+
},
|
|
9914
|
+
{
|
|
9915
|
+
fieldId: "tags",
|
|
9916
|
+
fieldType: "array",
|
|
9917
|
+
filterable: true,
|
|
9918
|
+
renderHints: { label: "Tags", order: 4 }
|
|
9919
|
+
},
|
|
9920
|
+
{
|
|
9921
|
+
fieldId: "status",
|
|
9922
|
+
fieldType: "enum",
|
|
9923
|
+
filterable: true,
|
|
9924
|
+
enumValues: ["active", "retired"],
|
|
9925
|
+
renderHints: { label: "Status", widget: "select", order: 5 }
|
|
9926
|
+
},
|
|
9927
|
+
{
|
|
9928
|
+
// Cross-document edge: the post-mortem whose resolution this runbook codifies.
|
|
9929
|
+
fieldId: "bornFrom",
|
|
9930
|
+
fieldType: "reference",
|
|
9931
|
+
targetTypeName: "postmortem",
|
|
9932
|
+
targetSurface: "document",
|
|
9933
|
+
targetField: "externalId",
|
|
9934
|
+
cardinality: "one",
|
|
9935
|
+
description: "The post-mortem this runbook was codified from (by externalId).",
|
|
9936
|
+
renderHints: { label: "Born from (post-mortem)", order: 6 }
|
|
9937
|
+
},
|
|
9938
|
+
{
|
|
9939
|
+
fieldId: "relatedDecision",
|
|
9940
|
+
fieldType: "reference",
|
|
9941
|
+
targetTypeName: "decision",
|
|
9942
|
+
targetSurface: "document",
|
|
9943
|
+
targetField: "externalId",
|
|
9944
|
+
cardinality: "one",
|
|
9945
|
+
description: "A decision this runbook implements, if any (by externalId).",
|
|
9946
|
+
renderHints: { label: "Related decision", order: 7 }
|
|
9947
|
+
},
|
|
9948
|
+
{
|
|
9949
|
+
fieldId: "updatedOn",
|
|
9950
|
+
fieldType: "date",
|
|
9951
|
+
description: "ISO-8601 \u2014 when last revised. Range-queryable.",
|
|
9952
|
+
renderHints: { label: "Updated on", widget: "date", order: 8 }
|
|
9953
|
+
}
|
|
9954
|
+
],
|
|
9955
|
+
lookupFields: ["area", "status", "bornFrom", "relatedDecision", { fieldName: "updatedOn", rangeEnabled: true }]
|
|
9956
|
+
},
|
|
9957
|
+
{
|
|
9958
|
+
// A POST-MORTEM — what broke and the durable lesson. Body = the writeup
|
|
9959
|
+
// (impact / root cause / resolution / lesson). Distinct shape: `severity` +
|
|
9960
|
+
// `occurredOn` (the incident timeline). "Have we hit this before?" lives here.
|
|
9961
|
+
typeName: "postmortem",
|
|
9962
|
+
displayName: "Post-mortem",
|
|
9963
|
+
indexMode: "HYBRID",
|
|
9964
|
+
allowedSurfaces: ["document"],
|
|
9965
|
+
// Intrinsic title + body; schema = metadata beyond those (no typed `title`).
|
|
9966
|
+
fields: [
|
|
9967
|
+
{
|
|
9968
|
+
fieldId: "summary",
|
|
9969
|
+
fieldType: "string",
|
|
9970
|
+
searchable: true,
|
|
9971
|
+
description: "What happened, in brief. The full analysis (impact/root cause/resolution/lesson) is the body.",
|
|
9972
|
+
renderHints: { label: "Summary", widget: "textarea", order: 2 }
|
|
9973
|
+
},
|
|
9974
|
+
{
|
|
9975
|
+
fieldId: "severity",
|
|
9976
|
+
fieldType: "enum",
|
|
9977
|
+
filterable: true,
|
|
9978
|
+
enumValues: ["low", "medium", "high", "critical"],
|
|
9979
|
+
renderHints: { label: "Severity", widget: "select", order: 3 }
|
|
9980
|
+
},
|
|
9981
|
+
{
|
|
9982
|
+
fieldId: "status",
|
|
9983
|
+
fieldType: "enum",
|
|
9984
|
+
filterable: true,
|
|
9985
|
+
enumValues: ["open", "mitigated", "resolved"],
|
|
9986
|
+
renderHints: { label: "Status", widget: "select", order: 4 }
|
|
9987
|
+
},
|
|
9988
|
+
{
|
|
9989
|
+
fieldId: "area",
|
|
9990
|
+
fieldType: "string",
|
|
9991
|
+
filterable: true,
|
|
9992
|
+
renderHints: { label: "Area", widget: "text", order: 5 }
|
|
9993
|
+
},
|
|
9994
|
+
{
|
|
9995
|
+
fieldId: "tags",
|
|
9996
|
+
fieldType: "array",
|
|
9997
|
+
filterable: true,
|
|
9998
|
+
renderHints: { label: "Tags", order: 6 }
|
|
9999
|
+
},
|
|
10000
|
+
{
|
|
10001
|
+
fieldId: "relatedDecision",
|
|
10002
|
+
fieldType: "reference",
|
|
10003
|
+
targetTypeName: "decision",
|
|
10004
|
+
targetSurface: "document",
|
|
10005
|
+
targetField: "externalId",
|
|
10006
|
+
cardinality: "one",
|
|
10007
|
+
description: "A decision implicated in or addressed by this incident (by externalId).",
|
|
10008
|
+
renderHints: { label: "Related decision", order: 7 }
|
|
10009
|
+
},
|
|
10010
|
+
{
|
|
10011
|
+
fieldId: "occurredOn",
|
|
10012
|
+
fieldType: "date",
|
|
10013
|
+
description: 'ISO-8601 \u2014 when it happened. Range-queryable \u2014 "incidents this month".',
|
|
10014
|
+
renderHints: { label: "Occurred on", widget: "date", order: 8 }
|
|
10015
|
+
}
|
|
10016
|
+
],
|
|
10017
|
+
lookupFields: ["severity", "status", "area", "relatedDecision", { fieldName: "occurredOn", rangeEnabled: true }]
|
|
10018
|
+
},
|
|
10019
|
+
// ============================= RECORDS =============================
|
|
10020
|
+
// Structure-dominant: the typed fields are the artifact. Short, exact-queryable.
|
|
10021
|
+
{
|
|
10022
|
+
// A governance CONTROL — a policy/standard/control the codebase must satisfy,
|
|
10023
|
+
// WITH its evidence. The compliance instrument: it records what enforces it
|
|
10024
|
+
// (`evidence`), the runbook that VERIFIES it (cross-surface → document), and
|
|
10025
|
+
// the decision that mandates it. "Which critical controls are active, and how
|
|
10026
|
+
// is each proven?"
|
|
10027
|
+
typeName: "control",
|
|
10028
|
+
displayName: "Control",
|
|
10029
|
+
indexMode: "HYBRID",
|
|
10030
|
+
fields: [
|
|
10031
|
+
{
|
|
10032
|
+
fieldId: "title",
|
|
10033
|
+
fieldType: "string",
|
|
10034
|
+
required: true,
|
|
10035
|
+
searchable: true,
|
|
10036
|
+
validation: { minLength: 1, maxLength: 200 },
|
|
10037
|
+
renderHints: { label: "Title", widget: "text", order: 1, displayField: true }
|
|
10038
|
+
},
|
|
10039
|
+
{
|
|
10040
|
+
fieldId: "statement",
|
|
10041
|
+
fieldType: "string",
|
|
10042
|
+
searchable: true,
|
|
10043
|
+
description: "The requirement \u2014 what must always hold.",
|
|
10044
|
+
renderHints: { label: "Statement", widget: "textarea", order: 2 }
|
|
10045
|
+
},
|
|
10046
|
+
{
|
|
10047
|
+
fieldId: "rationale",
|
|
10048
|
+
fieldType: "string",
|
|
10049
|
+
searchable: true,
|
|
10050
|
+
description: "Why the control exists \u2014 the risk it prevents.",
|
|
10051
|
+
renderHints: { label: "Rationale", widget: "textarea", order: 3 }
|
|
10052
|
+
},
|
|
10053
|
+
{
|
|
10054
|
+
// The policy → implementation spectrum in one filterable field.
|
|
10055
|
+
fieldId: "kind",
|
|
10056
|
+
fieldType: "enum",
|
|
10057
|
+
filterable: true,
|
|
10058
|
+
enumValues: ["policy", "standard", "control"],
|
|
10059
|
+
renderHints: { label: "Kind", widget: "select", order: 4 }
|
|
10060
|
+
},
|
|
10061
|
+
{
|
|
10062
|
+
// Ordinal, but EQUALITY (range/prefix order is lexical, so low<critical
|
|
10063
|
+
// would sort alphabetically). Enumerate "all critical controls" by equality.
|
|
10064
|
+
fieldId: "criticality",
|
|
10065
|
+
fieldType: "enum",
|
|
10066
|
+
filterable: true,
|
|
10067
|
+
enumValues: ["low", "medium", "high", "critical"],
|
|
10068
|
+
renderHints: { label: "Criticality", widget: "select", order: 5 }
|
|
10069
|
+
},
|
|
10070
|
+
{
|
|
10071
|
+
fieldId: "evidence",
|
|
10072
|
+
fieldType: "string",
|
|
10073
|
+
searchable: true,
|
|
10074
|
+
description: "What enforces or proves the control inline (e.g. an architecture test or gate).",
|
|
10075
|
+
renderHints: { label: "Evidence", widget: "textarea", order: 6 }
|
|
10076
|
+
},
|
|
10077
|
+
{
|
|
10078
|
+
fieldId: "status",
|
|
10079
|
+
fieldType: "enum",
|
|
10080
|
+
filterable: true,
|
|
10081
|
+
enumValues: ["draft", "active", "retired"],
|
|
10082
|
+
renderHints: { label: "Status", widget: "select", order: 7 }
|
|
10083
|
+
},
|
|
10084
|
+
{
|
|
10085
|
+
fieldId: "area",
|
|
10086
|
+
fieldType: "string",
|
|
10087
|
+
filterable: true,
|
|
10088
|
+
renderHints: { label: "Area", widget: "text", order: 8 }
|
|
10089
|
+
},
|
|
10090
|
+
{
|
|
10091
|
+
fieldId: "tags",
|
|
10092
|
+
fieldType: "array",
|
|
10093
|
+
filterable: true,
|
|
10094
|
+
renderHints: { label: "Tags", order: 9 }
|
|
10095
|
+
},
|
|
10096
|
+
{
|
|
10097
|
+
// Cross-surface edge (record → document): the runbook that proves the control.
|
|
10098
|
+
fieldId: "verifiedBy",
|
|
10099
|
+
fieldType: "reference",
|
|
10100
|
+
targetTypeName: "runbook",
|
|
10101
|
+
targetSurface: "document",
|
|
10102
|
+
targetField: "externalId",
|
|
10103
|
+
cardinality: "one",
|
|
10104
|
+
description: "The runbook that verifies this control (by externalId).",
|
|
10105
|
+
renderHints: { label: "Verified by (runbook)", order: 10 }
|
|
10106
|
+
},
|
|
10107
|
+
{
|
|
10108
|
+
// Cross-surface edge (record → document): the decision that mandates it.
|
|
10109
|
+
fieldId: "relatedDecision",
|
|
10110
|
+
fieldType: "reference",
|
|
10111
|
+
targetTypeName: "decision",
|
|
10112
|
+
targetSurface: "document",
|
|
10113
|
+
targetField: "externalId",
|
|
10114
|
+
cardinality: "one",
|
|
10115
|
+
description: "The decision that mandates this control (by externalId).",
|
|
10116
|
+
renderHints: { label: "Related decision", order: 11 }
|
|
10117
|
+
},
|
|
10118
|
+
{
|
|
10119
|
+
fieldId: "reviewedOn",
|
|
10120
|
+
fieldType: "date",
|
|
10121
|
+
description: "ISO-8601 last-reviewed date. Range-queryable.",
|
|
10122
|
+
renderHints: { label: "Reviewed on", widget: "date", order: 12 }
|
|
10123
|
+
}
|
|
10124
|
+
],
|
|
10125
|
+
lookupFields: [
|
|
10126
|
+
"kind",
|
|
10127
|
+
"criticality",
|
|
10128
|
+
"status",
|
|
10129
|
+
"area",
|
|
10130
|
+
"verifiedBy",
|
|
10131
|
+
"relatedDecision",
|
|
10132
|
+
{ fieldName: "reviewedOn", rangeEnabled: true }
|
|
10133
|
+
]
|
|
10134
|
+
},
|
|
10135
|
+
{
|
|
10136
|
+
// A CONVENTION — a must-follow operating rule the team teaches its agents. The
|
|
10137
|
+
// durable, shareable operating-memory. Distinct fields capture how we actually
|
|
10138
|
+
// write these: the `rule` (imperative), the `why` (rationale), and `howToApply`
|
|
10139
|
+
// (the concrete application) are SEPARATE fields, not one blob — so an agent can
|
|
10140
|
+
// recall the rule, the reasoning, and the how independently.
|
|
10141
|
+
typeName: "convention",
|
|
10142
|
+
displayName: "Convention",
|
|
10143
|
+
indexMode: "HYBRID",
|
|
10144
|
+
fields: [
|
|
10145
|
+
{
|
|
10146
|
+
fieldId: "title",
|
|
10147
|
+
fieldType: "string",
|
|
10148
|
+
required: true,
|
|
10149
|
+
searchable: true,
|
|
10150
|
+
validation: { minLength: 1, maxLength: 200 },
|
|
10151
|
+
renderHints: { label: "Title", widget: "text", order: 1, displayField: true }
|
|
10152
|
+
},
|
|
10153
|
+
{
|
|
10154
|
+
fieldId: "rule",
|
|
10155
|
+
fieldType: "string",
|
|
10156
|
+
searchable: true,
|
|
10157
|
+
description: "The convention itself, stated as an imperative.",
|
|
10158
|
+
renderHints: { label: "Rule", widget: "textarea", order: 2 }
|
|
10159
|
+
},
|
|
10160
|
+
{
|
|
10161
|
+
fieldId: "why",
|
|
10162
|
+
fieldType: "string",
|
|
10163
|
+
searchable: true,
|
|
10164
|
+
description: "Why it matters \u2014 the trade-off / the antipattern it prevents.",
|
|
10165
|
+
renderHints: { label: "Why", widget: "textarea", order: 3 }
|
|
10166
|
+
},
|
|
10167
|
+
{
|
|
10168
|
+
fieldId: "howToApply",
|
|
10169
|
+
fieldType: "string",
|
|
10170
|
+
searchable: true,
|
|
10171
|
+
description: "How to comply in practice \u2014 the concrete application steps.",
|
|
10172
|
+
renderHints: { label: "How to apply", widget: "textarea", order: 4 }
|
|
10173
|
+
},
|
|
10174
|
+
{
|
|
10175
|
+
fieldId: "area",
|
|
10176
|
+
fieldType: "string",
|
|
10177
|
+
filterable: true,
|
|
10178
|
+
renderHints: { label: "Area", widget: "text", order: 5 }
|
|
10179
|
+
},
|
|
10180
|
+
{
|
|
10181
|
+
fieldId: "status",
|
|
10182
|
+
fieldType: "enum",
|
|
10183
|
+
filterable: true,
|
|
10184
|
+
enumValues: ["active", "retired"],
|
|
10185
|
+
renderHints: { label: "Status", widget: "select", order: 6 }
|
|
10186
|
+
},
|
|
10187
|
+
{
|
|
10188
|
+
fieldId: "tags",
|
|
10189
|
+
fieldType: "array",
|
|
10190
|
+
filterable: true,
|
|
10191
|
+
renderHints: { label: "Tags", order: 7 }
|
|
10192
|
+
},
|
|
10193
|
+
{
|
|
10194
|
+
// Cross-surface edge (record → document): the decision that established it.
|
|
10195
|
+
fieldId: "establishedBy",
|
|
10196
|
+
fieldType: "reference",
|
|
10197
|
+
targetTypeName: "decision",
|
|
10198
|
+
targetSurface: "document",
|
|
10199
|
+
targetField: "externalId",
|
|
10200
|
+
cardinality: "one",
|
|
10201
|
+
description: "The decision that established this convention (by externalId).",
|
|
10202
|
+
renderHints: { label: "Established by (decision)", order: 8 }
|
|
10203
|
+
},
|
|
10204
|
+
{
|
|
10205
|
+
fieldId: "updatedOn",
|
|
10206
|
+
fieldType: "date",
|
|
10207
|
+
description: "ISO-8601 \u2014 when last revised. Range-queryable.",
|
|
10208
|
+
renderHints: { label: "Updated on", widget: "date", order: 9 }
|
|
10209
|
+
}
|
|
10210
|
+
],
|
|
10211
|
+
lookupFields: ["area", "status", "establishedBy", { fieldName: "updatedOn", rangeEnabled: true }]
|
|
10212
|
+
},
|
|
10213
|
+
{
|
|
10214
|
+
// A GOTCHA / sharp edge: a symptom, its cause, and the fix. A tight typed
|
|
10215
|
+
// triple — found by meaning (semantic search on the symptom) + area/status.
|
|
10216
|
+
// The most standalone type; no typed edge.
|
|
10217
|
+
typeName: "gotcha",
|
|
10218
|
+
displayName: "Gotcha",
|
|
10219
|
+
indexMode: "HYBRID",
|
|
10220
|
+
fields: [
|
|
10221
|
+
{
|
|
10222
|
+
fieldId: "symptom",
|
|
10223
|
+
fieldType: "string",
|
|
10224
|
+
required: true,
|
|
10225
|
+
searchable: true,
|
|
10226
|
+
validation: { minLength: 1, maxLength: 500 },
|
|
10227
|
+
renderHints: { label: "Symptom", widget: "textarea", order: 1, displayField: true }
|
|
10228
|
+
},
|
|
10229
|
+
{
|
|
10230
|
+
fieldId: "cause",
|
|
10231
|
+
fieldType: "string",
|
|
10232
|
+
searchable: true,
|
|
10233
|
+
renderHints: { label: "Cause", widget: "textarea", order: 2 }
|
|
10234
|
+
},
|
|
10235
|
+
{
|
|
10236
|
+
fieldId: "fix",
|
|
10237
|
+
fieldType: "string",
|
|
10238
|
+
searchable: true,
|
|
10239
|
+
renderHints: { label: "Fix", widget: "textarea", order: 3 }
|
|
10240
|
+
},
|
|
10241
|
+
{
|
|
10242
|
+
fieldId: "area",
|
|
10243
|
+
fieldType: "string",
|
|
10244
|
+
filterable: true,
|
|
10245
|
+
renderHints: { label: "Area", widget: "text", order: 4 }
|
|
10246
|
+
},
|
|
10247
|
+
{
|
|
10248
|
+
fieldId: "status",
|
|
10249
|
+
fieldType: "enum",
|
|
10250
|
+
filterable: true,
|
|
10251
|
+
enumValues: ["active", "resolved"],
|
|
10252
|
+
renderHints: { label: "Status", widget: "select", order: 5 }
|
|
10253
|
+
},
|
|
10254
|
+
{
|
|
10255
|
+
fieldId: "tags",
|
|
10256
|
+
fieldType: "array",
|
|
10257
|
+
filterable: true,
|
|
10258
|
+
renderHints: { label: "Tags", order: 6 }
|
|
10259
|
+
},
|
|
10260
|
+
{
|
|
10261
|
+
fieldId: "discoveredOn",
|
|
10262
|
+
fieldType: "date",
|
|
10263
|
+
description: "ISO-8601 \u2014 when first hit. Range-queryable.",
|
|
10264
|
+
renderHints: { label: "Discovered on", widget: "date", order: 7 }
|
|
10265
|
+
}
|
|
10266
|
+
],
|
|
10267
|
+
lookupFields: ["area", "status", { fieldName: "discoveredOn", rangeEnabled: true }]
|
|
10268
|
+
},
|
|
10269
|
+
{
|
|
10270
|
+
// A glossary TERM — a definition keyed by the term itself. Structure-dominant:
|
|
10271
|
+
// `term` is a UNIQUE exact-lookup key (the showcase of a uniqueness constraint),
|
|
10272
|
+
// `definition` is the RAG body, `aliases` catch alternate names. Links to the
|
|
10273
|
+
// decision that defines/establishes the concept where there is one.
|
|
10274
|
+
typeName: "term",
|
|
10275
|
+
displayName: "Glossary term",
|
|
10276
|
+
indexMode: "HYBRID",
|
|
10277
|
+
fields: [
|
|
10278
|
+
{
|
|
10279
|
+
fieldId: "term",
|
|
10280
|
+
fieldType: "string",
|
|
10281
|
+
required: true,
|
|
10282
|
+
searchable: true,
|
|
10283
|
+
validation: { minLength: 1, maxLength: 200 },
|
|
10284
|
+
renderHints: { label: "Term", widget: "text", order: 1, displayField: true }
|
|
10285
|
+
},
|
|
10286
|
+
{
|
|
10287
|
+
fieldId: "definition",
|
|
10288
|
+
fieldType: "string",
|
|
10289
|
+
searchable: true,
|
|
10290
|
+
description: "What the term means \u2014 the RAG-able body.",
|
|
10291
|
+
renderHints: { label: "Definition", widget: "textarea", order: 2 }
|
|
10292
|
+
},
|
|
10293
|
+
{
|
|
10294
|
+
fieldId: "aliases",
|
|
10295
|
+
fieldType: "array",
|
|
10296
|
+
filterable: true,
|
|
10297
|
+
description: "Alternate names / abbreviations for the same concept.",
|
|
10298
|
+
renderHints: { label: "Aliases", order: 3 }
|
|
10299
|
+
},
|
|
10300
|
+
{
|
|
10301
|
+
fieldId: "area",
|
|
10302
|
+
fieldType: "string",
|
|
10303
|
+
filterable: true,
|
|
10304
|
+
renderHints: { label: "Area", widget: "text", order: 4 }
|
|
10305
|
+
},
|
|
10306
|
+
{
|
|
10307
|
+
fieldId: "tags",
|
|
10308
|
+
fieldType: "array",
|
|
10309
|
+
filterable: true,
|
|
10310
|
+
renderHints: { label: "Tags", order: 5 }
|
|
10311
|
+
},
|
|
10312
|
+
{
|
|
10313
|
+
fieldId: "relatedDecision",
|
|
10314
|
+
fieldType: "reference",
|
|
10315
|
+
targetTypeName: "decision",
|
|
10316
|
+
targetSurface: "document",
|
|
10317
|
+
targetField: "externalId",
|
|
10318
|
+
cardinality: "one",
|
|
10319
|
+
description: "A decision that defines or establishes this term (by externalId).",
|
|
10320
|
+
renderHints: { label: "Related decision", order: 6 }
|
|
10321
|
+
},
|
|
10322
|
+
{
|
|
10323
|
+
fieldId: "updatedOn",
|
|
10324
|
+
fieldType: "date",
|
|
10325
|
+
description: "ISO-8601 \u2014 when last revised. Range-queryable.",
|
|
10326
|
+
renderHints: { label: "Updated on", widget: "date", order: 7 }
|
|
10327
|
+
}
|
|
10328
|
+
],
|
|
10329
|
+
// `term` is a UNIQUE equality lookup — exact "define X" + a one-per-term
|
|
10330
|
+
// guarantee. `area`/`relatedDecision` enumerate; `updatedOn` is the range row.
|
|
10331
|
+
lookupFields: [
|
|
10332
|
+
{ fieldName: "term", unique: true },
|
|
10333
|
+
"area",
|
|
10334
|
+
"relatedDecision",
|
|
10335
|
+
{ fieldName: "updatedOn", rangeEnabled: true }
|
|
10336
|
+
]
|
|
10337
|
+
}
|
|
10338
|
+
],
|
|
10339
|
+
// Least-privilege, data-plane only. The scope of the `ssk_*` key the bootstrap
|
|
10340
|
+
// mints for THIS blueprint's service principal (the MCP/API runtime). See
|
|
10341
|
+
// DATA_PLANE_ACTIONS above for the action set + rationale.
|
|
10342
|
+
accessProfile: {
|
|
10343
|
+
allowedActions: DATA_PLANE_ACTIONS
|
|
10344
|
+
},
|
|
10345
|
+
// A reusable `editor` role for the HUMAN owner — DISTINCT from `accessProfile`
|
|
10346
|
+
// (which scopes only the service-principal key). `bootstrap` provisions this
|
|
10347
|
+
// role in the context but binds it to no one; the owner joins themselves so the
|
|
10348
|
+
// data-plane app (app.vectros.ai) shows their KB — its switcher lists only
|
|
10349
|
+
// contexts the signed-in user holds an active access profile in, and bootstrap
|
|
10350
|
+
// grants the human none by default. Bind it after bootstrap with:
|
|
10351
|
+
// vectros access grant --principal usr_<your-user-id> --context agentic-sdlc --role editor
|
|
10352
|
+
// (or the admin app's Access > Contexts > agentic-sdlc > Profiles > Create).
|
|
10353
|
+
// Editor PARITY with the service key (same DATA_PLANE_ACTIONS) so a human
|
|
10354
|
+
// curator can browse AND write/correct the KB; still no :d and no control-plane
|
|
10355
|
+
// action, so the scope gate accepts it exactly like the accessProfile.
|
|
10356
|
+
roles: {
|
|
10357
|
+
editor: [{ allowedActions: DATA_PLANE_ACTIONS }]
|
|
10358
|
+
},
|
|
10359
|
+
servicePrincipal: {
|
|
10360
|
+
externalId: "agentic-sdlc",
|
|
10361
|
+
displayName: "Agentic SDLC Knowledge Base"
|
|
10362
|
+
}
|
|
10363
|
+
// No bundled seed in this version: the content artifacts live on the document
|
|
10364
|
+
// surface and the cross-surface graph is populated by the ingest agent
|
|
10365
|
+
// (document_ingest / record_create), not the bootstrap seed step. Production
|
|
10366
|
+
// contexts provision with `vectros bootstrap --no-seed` regardless.
|
|
10367
|
+
};
|
|
10368
|
+
var agentic_sdlc_default = agenticSdlc;
|
|
10369
|
+
var secondBrain = {
|
|
10370
|
+
name: "second-brain",
|
|
10371
|
+
version: "1.0.0",
|
|
10372
|
+
description: "A personal knowledge base \u2014 capture notes, ideas, and links, then ask them anything.",
|
|
10373
|
+
contextId: "second-brain",
|
|
10374
|
+
contextName: "Second Brain",
|
|
10375
|
+
schemas: [
|
|
10376
|
+
{
|
|
10377
|
+
typeName: "note",
|
|
10378
|
+
displayName: "Note",
|
|
10379
|
+
indexMode: "HYBRID",
|
|
10380
|
+
// keyword on title + semantic on body — recall by meaning
|
|
10381
|
+
fields: [
|
|
10382
|
+
{
|
|
10383
|
+
fieldId: "title",
|
|
10384
|
+
fieldType: "string",
|
|
10385
|
+
required: true,
|
|
10386
|
+
searchable: true,
|
|
10387
|
+
validation: { minLength: 1, maxLength: 200 },
|
|
10388
|
+
renderHints: { label: "Title", widget: "text", order: 1, section: "Note", displayField: true }
|
|
10389
|
+
},
|
|
10390
|
+
{
|
|
10391
|
+
fieldId: "body",
|
|
10392
|
+
fieldType: "string",
|
|
10393
|
+
searchable: true,
|
|
10394
|
+
description: "The note itself \u2014 the RAG-able body you ask questions against.",
|
|
10395
|
+
validation: { maxLength: 2e4 },
|
|
10396
|
+
renderHints: { label: "Body", widget: "textarea", order: 2, section: "Note" }
|
|
10397
|
+
},
|
|
10398
|
+
{
|
|
10399
|
+
fieldId: "tags",
|
|
10400
|
+
fieldType: "array",
|
|
10401
|
+
filterable: true,
|
|
10402
|
+
description: 'Freeform string labels for filtering (e.g. "idea", "reading", "work").',
|
|
10403
|
+
renderHints: { label: "Tags", order: 3, section: "Organize" }
|
|
10404
|
+
},
|
|
10405
|
+
{
|
|
10406
|
+
// An enum (not freeform) so enumeration-by-source is reliable — a
|
|
10407
|
+
// `record_query` lookup on `source` only works if values are consistent.
|
|
10408
|
+
// `other` keeps it from being a straitjacket for a personal brain-dump.
|
|
10409
|
+
fieldId: "source",
|
|
10410
|
+
fieldType: "enum",
|
|
10411
|
+
filterable: true,
|
|
10412
|
+
enumValues: ["thought", "web", "meeting", "book", "article", "other"],
|
|
10413
|
+
description: "Where it came from.",
|
|
10414
|
+
renderHints: { label: "Source", widget: "select", order: 4, section: "Organize" }
|
|
10415
|
+
},
|
|
10416
|
+
{
|
|
10417
|
+
fieldId: "status",
|
|
10418
|
+
fieldType: "enum",
|
|
10419
|
+
filterable: true,
|
|
10420
|
+
enumValues: ["active", "archived"],
|
|
10421
|
+
description: "Archive instead of delete, so nothing is ever lost.",
|
|
10422
|
+
renderHints: { label: "Status", widget: "select", order: 5, section: "Organize" }
|
|
10423
|
+
},
|
|
10424
|
+
{
|
|
10425
|
+
fieldId: "capturedAt",
|
|
10426
|
+
fieldType: "date",
|
|
10427
|
+
description: 'ISO-8601 capture date. Range-queryable \u2014 "notes from last week".',
|
|
10428
|
+
renderHints: { label: "Captured", widget: "date", order: 6, section: "Organize" }
|
|
10429
|
+
}
|
|
10430
|
+
// externalId is the record's FIRST-CLASS identifier (the dedup/upsert key + the
|
|
10431
|
+
// value a `reference` resolves against) — not a payload field; the loader sends
|
|
10432
|
+
// it top-level on the RecordRequest.
|
|
10433
|
+
],
|
|
10434
|
+
// Lookup shapes are MIGRATION-LOCKED once the schema is live (a field cannot
|
|
10435
|
+
// flip equality↔range later), so each is chosen on purpose:
|
|
10436
|
+
// • `source` — EQUALITY, sorted by `lastUpdated`: enumerate notes from one
|
|
10437
|
+
// source, most-recently-touched first. `lastUpdated` is always present, so
|
|
10438
|
+
// the sort never drops a note (sorting an equality lookup by an OPTIONAL
|
|
10439
|
+
// field would silently exclude rows that lack it).
|
|
10440
|
+
// • `status` — EQUALITY: "show archived notes".
|
|
10441
|
+
// • `capturedAt` — RANGE: ordered `from`/`to`/`prefix` ("captured in 2026-06").
|
|
10442
|
+
// `externalId` has a built-in first-class finder — look it up directly, never
|
|
10443
|
+
// redeclare it here. Equality lookups use the 7 fast index slots (2 used here);
|
|
10444
|
+
// range lookups use a relationship row, billed at the range rate.
|
|
10445
|
+
lookupFields: [
|
|
10446
|
+
{ fieldName: "source", sortBy: "lastUpdated" },
|
|
10447
|
+
"status",
|
|
10448
|
+
{ fieldName: "capturedAt", rangeEnabled: true }
|
|
10449
|
+
]
|
|
10450
|
+
}
|
|
10451
|
+
],
|
|
10452
|
+
// Least-privilege profile — r/c/u + search + schema discovery + inference:r
|
|
10453
|
+
// (so the agent can `rag_ask` a grounded, cited question over your notes). No
|
|
10454
|
+
// records:d: notes are archived (status → 'archived'), never deleted.
|
|
10455
|
+
accessProfile: {
|
|
10456
|
+
allowedActions: ["records:r", "records:c", "records:u", "search:r", "schemas:r", "inference:r"]
|
|
10457
|
+
},
|
|
10458
|
+
servicePrincipal: {
|
|
10459
|
+
externalId: "second-brain",
|
|
10460
|
+
displayName: "Second Brain"
|
|
10461
|
+
},
|
|
10462
|
+
seed: [
|
|
10463
|
+
{
|
|
10464
|
+
surface: "record",
|
|
10465
|
+
typeName: "note",
|
|
10466
|
+
externalId: "seed-welcome",
|
|
10467
|
+
fields: {
|
|
10468
|
+
title: "Welcome to your Second Brain",
|
|
10469
|
+
body: 'Capture anything here \u2014 ideas, links, meeting notes \u2014 then ask your agent things like "what did I note about onboarding?" and it recalls by meaning, not just keywords.',
|
|
10470
|
+
tags: ["getting-started"],
|
|
10471
|
+
source: "thought",
|
|
10472
|
+
status: "active",
|
|
10473
|
+
capturedAt: "2026-06-14"
|
|
10474
|
+
}
|
|
10475
|
+
}
|
|
10476
|
+
]
|
|
10477
|
+
};
|
|
10478
|
+
var second_brain_default = secondBrain;
|
|
10479
|
+
var clinicalIntake = {
|
|
10480
|
+
name: "clinical-intake",
|
|
10481
|
+
version: "1.0.0",
|
|
10482
|
+
description: "Behavioral-health intake with PHI fields \u2014 demonstrates redact-at-write, audit history, blind-index lookup, and search exclusion. Synthetic data only.",
|
|
10483
|
+
contextId: "clinical-intake",
|
|
10484
|
+
contextName: "Clinical Intake",
|
|
10485
|
+
schemas: [
|
|
10486
|
+
{
|
|
10487
|
+
typeName: "intake",
|
|
10488
|
+
displayName: "Intake",
|
|
10489
|
+
indexMode: "HYBRID",
|
|
10490
|
+
capabilities: { auditHistory: true },
|
|
10491
|
+
// self-documenting compliance posture
|
|
10492
|
+
fields: [
|
|
10493
|
+
// --- Non-sensitive, searchable/filterable working fields ---
|
|
10494
|
+
{
|
|
10495
|
+
fieldId: "caseId",
|
|
10496
|
+
fieldType: "string",
|
|
10497
|
+
required: true,
|
|
10498
|
+
description: "Caller-stable intake id; the dedup/lookup key.",
|
|
10499
|
+
validation: { minLength: 1, maxLength: 64 },
|
|
10500
|
+
renderHints: { label: "Case ID", widget: "text", order: 1, section: "Intake", displayField: true }
|
|
10501
|
+
},
|
|
10502
|
+
{
|
|
10503
|
+
fieldId: "presentingConcern",
|
|
10504
|
+
fieldType: "string",
|
|
10505
|
+
searchable: true,
|
|
10506
|
+
description: "Non-PHI summary of the presenting concern \u2014 safe to index + search.",
|
|
10507
|
+
validation: { maxLength: 2e3 },
|
|
10508
|
+
renderHints: { label: "Presenting concern", widget: "textarea", order: 2, section: "Intake" }
|
|
9688
10509
|
},
|
|
9689
10510
|
{
|
|
9690
10511
|
fieldId: "program",
|
|
@@ -9772,6 +10593,7 @@ var clinicalIntake = {
|
|
|
9772
10593
|
},
|
|
9773
10594
|
seed: [
|
|
9774
10595
|
{
|
|
10596
|
+
surface: "record",
|
|
9775
10597
|
typeName: "intake",
|
|
9776
10598
|
externalId: "seed-synthetic-intake",
|
|
9777
10599
|
fields: {
|
|
@@ -9793,6 +10615,7 @@ var clinical_intake_default = clinicalIntake;
|
|
|
9793
10615
|
var BUNDLED_BLUEPRINTS = [
|
|
9794
10616
|
task_management_default,
|
|
9795
10617
|
coding_agent_memory_default,
|
|
10618
|
+
agentic_sdlc_default,
|
|
9796
10619
|
second_brain_default,
|
|
9797
10620
|
clinical_intake_default
|
|
9798
10621
|
];
|
|
@@ -10230,7 +11053,8 @@ async function resolveCognitoToken(env, explicit) {
|
|
|
10230
11053
|
async function resolveAuthContext(opts) {
|
|
10231
11054
|
const baseUrl = envToBaseUrl(opts.env, opts.baseUrl ?? process.env.VECTROS_API_BASE_URL);
|
|
10232
11055
|
const cognitoToken = await resolveCognitoToken(opts.env, opts.token);
|
|
10233
|
-
const
|
|
11056
|
+
const tenant = opts.tenant ?? "live";
|
|
11057
|
+
const bridge = opts.provisioning ? await mintBridgeToken({ cognitoToken, apiBaseUrl: baseUrl, tenant }) : await mintContextToken({ cognitoToken, apiBaseUrl: baseUrl, contextId: opts.context ?? "default", tenant });
|
|
10234
11058
|
const client = new VectrosClient({ token: bridge.token, environment: baseUrl });
|
|
10235
11059
|
const identity = await resolveIdentity({ log: opts.log, apiKey: bridge.token, environment: baseUrl });
|
|
10236
11060
|
return {
|
|
@@ -10240,7 +11064,8 @@ async function resolveAuthContext(opts) {
|
|
|
10240
11064
|
baseUrl,
|
|
10241
11065
|
env: opts.env,
|
|
10242
11066
|
identity,
|
|
10243
|
-
tenantId: typeof identity.tenantId === "string" ? identity.tenantId : void 0
|
|
11067
|
+
tenantId: typeof identity.tenantId === "string" ? identity.tenantId : void 0,
|
|
11068
|
+
tenant
|
|
10244
11069
|
};
|
|
10245
11070
|
}
|
|
10246
11071
|
|
|
@@ -10564,39 +11389,6 @@ function assertNoUnresolvedIdentities(blueprint) {
|
|
|
10564
11389
|
);
|
|
10565
11390
|
}
|
|
10566
11391
|
}
|
|
10567
|
-
var SchemaReconcileError = class extends Error {
|
|
10568
|
-
constructor(typeName, cause) {
|
|
10569
|
-
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
10570
|
-
super(
|
|
10571
|
-
`Schema '${typeName}' already exists but could not be reconciled to the blueprint: ${detail}
|
|
10572
|
-
If this is a migration-locked attribute (a lookup field's rangeEnabled/sortBy/sensitive, or the schema typeName), it cannot change on an existing schema \u2014 use a new fieldName/typeName for the new shape.`
|
|
10573
|
-
);
|
|
10574
|
-
this.name = "SchemaReconcileError";
|
|
10575
|
-
}
|
|
10576
|
-
};
|
|
10577
|
-
function isAlreadyExistsError(e) {
|
|
10578
|
-
if (typeof e !== "object" || e === null) return false;
|
|
10579
|
-
const err = e;
|
|
10580
|
-
const status = err.statusCode;
|
|
10581
|
-
const text = `${err.message ?? ""} ${err.body?.message ?? ""}`.toLowerCase();
|
|
10582
|
-
return (status === 409 || status === 400) && text.includes("already exists");
|
|
10583
|
-
}
|
|
10584
|
-
async function findExistingSchemaId(client, typeName, owner) {
|
|
10585
|
-
let startFrom;
|
|
10586
|
-
for (; ; ) {
|
|
10587
|
-
const page = await client.schemas.listSchemas({
|
|
10588
|
-
userId: owner.userId,
|
|
10589
|
-
orgId: owner.orgId,
|
|
10590
|
-
limit: 100,
|
|
10591
|
-
startFrom
|
|
10592
|
-
});
|
|
10593
|
-
const match = (page.data ?? []).find((s) => s.typeName === typeName);
|
|
10594
|
-
if (match?.id) return match.id;
|
|
10595
|
-
const next = page.nextCursor;
|
|
10596
|
-
if (!next) return void 0;
|
|
10597
|
-
startFrom = next;
|
|
10598
|
-
}
|
|
10599
|
-
}
|
|
10600
11392
|
async function runLoader(client, input) {
|
|
10601
11393
|
const { blueprint, tenantId, machineId, rotate = false, skipSeed = false, vectrosDir, log } = input;
|
|
10602
11394
|
assertScopeGate(blueprint.accessProfile.allowedActions);
|
|
@@ -10604,14 +11396,16 @@ async function runLoader(client, input) {
|
|
|
10604
11396
|
for (const clause of clauses) assertScopeGate(clause.allowedActions);
|
|
10605
11397
|
}
|
|
10606
11398
|
assertNoUnresolvedIdentities(blueprint);
|
|
10607
|
-
await client.auth.createAppContext({ contextId: blueprint.contextId, name: contextNameOf(blueprint) });
|
|
11399
|
+
await client.auth.createAppContext({ body: { contextId: blueprint.contextId, name: contextNameOf(blueprint) } });
|
|
10608
11400
|
log.debug({ component: "loader", contextId: blueprint.contextId }, "app-context ensured");
|
|
10609
11401
|
const user = await client.identity.createUser({
|
|
10610
|
-
|
|
10611
|
-
|
|
10612
|
-
|
|
10613
|
-
|
|
10614
|
-
|
|
11402
|
+
body: {
|
|
11403
|
+
externalId: blueprint.servicePrincipal.externalId,
|
|
11404
|
+
type: "SERVICE",
|
|
11405
|
+
// SDK 0.18+ (identity-access rework): UserRequest's free-form attribute bag
|
|
11406
|
+
// was renamed `metadata` → `payload`. Same semantics (arbitrary key/value).
|
|
11407
|
+
payload: { displayName: blueprint.servicePrincipal.displayName }
|
|
11408
|
+
}
|
|
10615
11409
|
});
|
|
10616
11410
|
const userId = user.id;
|
|
10617
11411
|
if (!userId) {
|
|
@@ -10646,23 +11440,9 @@ async function runLoader(client, input) {
|
|
|
10646
11440
|
orgId: s.orgId,
|
|
10647
11441
|
clientId: s.clientId
|
|
10648
11442
|
};
|
|
10649
|
-
|
|
10650
|
-
try {
|
|
10651
|
-
resp = await ctxClient.schemas.createSchema(schemaBody);
|
|
10652
|
-
} catch (e) {
|
|
10653
|
-
if (!isAlreadyExistsError(e)) throw e;
|
|
10654
|
-
if (s.clientId) throw e;
|
|
10655
|
-
const existingId = await findExistingSchemaId(ctxClient, s.typeName, { userId: s.userId, orgId: s.orgId });
|
|
10656
|
-
if (!existingId) throw e;
|
|
10657
|
-
try {
|
|
10658
|
-
resp = await ctxClient.schemas.updateSchema({ id: existingId, body: schemaBody });
|
|
10659
|
-
} catch (ue) {
|
|
10660
|
-
throw new SchemaReconcileError(s.typeName, ue);
|
|
10661
|
-
}
|
|
10662
|
-
log.debug({ component: "loader", typeName: s.typeName, schemaId: existingId }, "schema existed; reconciled to declared shape (update)");
|
|
10663
|
-
}
|
|
11443
|
+
const resp = await ctxClient.schemas.createSchema({ upsert: true, body: schemaBody });
|
|
10664
11444
|
if (resp.id) schemaIds[s.typeName] = resp.id;
|
|
10665
|
-
log.debug({ component: "loader", typeName: s.typeName, schemaId: resp.id }, "schema ensured");
|
|
11445
|
+
log.debug({ component: "loader", typeName: s.typeName, schemaId: resp.id }, "schema ensured (upsert)");
|
|
10666
11446
|
}
|
|
10667
11447
|
const profileBody = {
|
|
10668
11448
|
principalId: `usr_${userId}`,
|
|
@@ -10738,25 +11518,40 @@ async function runLoader(client, input) {
|
|
|
10738
11518
|
log.debug({ component: "loader", keyName, keyAction }, "scoped key resolved");
|
|
10739
11519
|
let seededCount = 0;
|
|
10740
11520
|
const seededRecordIds = [];
|
|
11521
|
+
const seededDocumentIds = [];
|
|
10741
11522
|
for (const rec of skipSeed ? [] : blueprint.seed ?? []) {
|
|
10742
11523
|
const schemaId = schemaIds[rec.typeName];
|
|
10743
11524
|
if (!schemaId) {
|
|
10744
11525
|
log.warn({ component: "loader", typeName: rec.typeName }, "seed skipped \u2014 no schemaId for typeName");
|
|
10745
11526
|
continue;
|
|
10746
11527
|
}
|
|
10747
|
-
|
|
10748
|
-
|
|
10749
|
-
|
|
10750
|
-
|
|
10751
|
-
|
|
10752
|
-
|
|
10753
|
-
|
|
10754
|
-
|
|
10755
|
-
|
|
10756
|
-
|
|
10757
|
-
|
|
10758
|
-
|
|
10759
|
-
|
|
11528
|
+
if (rec.surface === "document") {
|
|
11529
|
+
const created = await ctxClient.documents.ingestDocument({
|
|
11530
|
+
body: {
|
|
11531
|
+
title: rec.title,
|
|
11532
|
+
text: rec.text,
|
|
11533
|
+
schemaId,
|
|
11534
|
+
externalId: rec.externalId,
|
|
11535
|
+
// Optional structured payload (the document's `fields`); omitted when absent.
|
|
11536
|
+
payload: rec.fields,
|
|
11537
|
+
// Retain the raw text so a seeded document is retrievable via
|
|
11538
|
+
// GET /v1/documents/{id}/text (mirrors the MCP document_ingest default) —
|
|
11539
|
+
// a seed's text IS its content, worth keeping for the getting-started graph.
|
|
11540
|
+
storeText: true
|
|
11541
|
+
}
|
|
11542
|
+
});
|
|
11543
|
+
if (created.id) seededDocumentIds.push(created.id);
|
|
11544
|
+
} else {
|
|
11545
|
+
const created = await ctxClient.records.createRecord({
|
|
11546
|
+
body: {
|
|
11547
|
+
typeName: rec.typeName,
|
|
11548
|
+
schemaId,
|
|
11549
|
+
externalId: rec.externalId,
|
|
11550
|
+
payload: rec.fields
|
|
11551
|
+
}
|
|
11552
|
+
});
|
|
11553
|
+
if (created.id) seededRecordIds.push(created.id);
|
|
11554
|
+
}
|
|
10760
11555
|
seededCount++;
|
|
10761
11556
|
}
|
|
10762
11557
|
return {
|
|
@@ -10769,7 +11564,8 @@ async function runLoader(client, input) {
|
|
|
10769
11564
|
schemaIds,
|
|
10770
11565
|
roleIds,
|
|
10771
11566
|
seededCount,
|
|
10772
|
-
seededRecordIds
|
|
11567
|
+
seededRecordIds,
|
|
11568
|
+
seededDocumentIds
|
|
10773
11569
|
};
|
|
10774
11570
|
}
|
|
10775
11571
|
function buildPlan(blueprint, opts = {}) {
|
|
@@ -10804,9 +11600,14 @@ function buildPlan(blueprint, opts = {}) {
|
|
|
10804
11600
|
});
|
|
10805
11601
|
}
|
|
10806
11602
|
steps.push({ kind: "key", summary: `key ${blueprint.name}-<machine> (mint 1 scoped ssk_*)` });
|
|
10807
|
-
const
|
|
10808
|
-
if (
|
|
10809
|
-
|
|
11603
|
+
const seeds = blueprint.seed ?? [];
|
|
11604
|
+
if (seeds.length > 0 && !opts.noSeed) {
|
|
11605
|
+
const docs = seeds.filter((s) => s.surface === "document").length;
|
|
11606
|
+
const recs = seeds.length - docs;
|
|
11607
|
+
const parts = [];
|
|
11608
|
+
if (recs > 0) parts.push(`${recs} record${recs === 1 ? "" : "s"}`);
|
|
11609
|
+
if (docs > 0) parts.push(`${docs} document${docs === 1 ? "" : "s"}`);
|
|
11610
|
+
steps.push({ kind: "seed", summary: `seed ${parts.join(" + ")}` });
|
|
10810
11611
|
}
|
|
10811
11612
|
return steps;
|
|
10812
11613
|
}
|
|
@@ -10932,6 +11733,7 @@ function parseBootstrapArgs(argv) {
|
|
|
10932
11733
|
blankSlate: false,
|
|
10933
11734
|
noSeed: false,
|
|
10934
11735
|
client: "desktop",
|
|
11736
|
+
tenant: "live",
|
|
10935
11737
|
help: false
|
|
10936
11738
|
};
|
|
10937
11739
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -10982,6 +11784,17 @@ function parseBootstrapArgs(argv) {
|
|
|
10982
11784
|
case "--no-seed":
|
|
10983
11785
|
args.noSeed = true;
|
|
10984
11786
|
break;
|
|
11787
|
+
case "--tenant": {
|
|
11788
|
+
const v = argv[++i];
|
|
11789
|
+
if (v !== "test" && v !== "live") {
|
|
11790
|
+
throw new Error(`--tenant must be 'test' or 'live' (got '${v ?? ""}')`);
|
|
11791
|
+
}
|
|
11792
|
+
args.tenant = v;
|
|
11793
|
+
break;
|
|
11794
|
+
}
|
|
11795
|
+
case "--test":
|
|
11796
|
+
args.tenant = "test";
|
|
11797
|
+
break;
|
|
10985
11798
|
case "--help":
|
|
10986
11799
|
case "-h":
|
|
10987
11800
|
args.help = true;
|
|
@@ -11010,7 +11823,8 @@ var BOOTSTRAP_HELP = `vectros bootstrap \u2014 provision a least-privilege MCP c
|
|
|
11010
11823
|
|
|
11011
11824
|
Usage:
|
|
11012
11825
|
vectros bootstrap [--blueprint <name> | --blank] [--no-seed] [--env staging|production]
|
|
11013
|
-
[--
|
|
11826
|
+
[--tenant test|live | --test] [--client desktop|code]
|
|
11827
|
+
[--yes] [--print] [--rotate]
|
|
11014
11828
|
[--token <t> | env VECTROS_BOOTSTRAP_TOKEN]
|
|
11015
11829
|
[--base-url <url>] [--use-scoped-token]
|
|
11016
11830
|
|
|
@@ -11024,6 +11838,12 @@ Options:
|
|
|
11024
11838
|
(a blueprint's seeds are illustrative, not real data). Unlike
|
|
11025
11839
|
--blank, the schemas are still created.
|
|
11026
11840
|
--env staging|production Target environment (default: production).
|
|
11841
|
+
--tenant test|live Which tenant the minted key scopes to (default: live).
|
|
11842
|
+
'test' provisions into your isolated test tenant \u2014 a
|
|
11843
|
+
dry-run before pointing real data at the same blueprint.
|
|
11844
|
+
The tenant rides the key (ssk_test_* vs ssk_live_*), so
|
|
11845
|
+
the MCP/client config is otherwise identical.
|
|
11846
|
+
--test Shorthand for --tenant test.
|
|
11027
11847
|
--base-url <url> Override the API base URL (beats --env + VECTROS_API_BASE_URL).
|
|
11028
11848
|
--client <desktop|code> MCP host to configure (default: desktop). 'desktop' merges
|
|
11029
11849
|
claude_desktop_config.json (also works for Cursor/Cline);
|
|
@@ -11092,6 +11912,21 @@ async function resolveBridgeCognitoToken(args, prompt, isTty, resolveCognito = r
|
|
|
11092
11912
|
if (!pasted) throw new Error("No token entered.");
|
|
11093
11913
|
return pasted;
|
|
11094
11914
|
}
|
|
11915
|
+
function buildRemintForContext(opts) {
|
|
11916
|
+
const mintContext = opts.mintContext ?? mintContextToken;
|
|
11917
|
+
return async (contextId) => {
|
|
11918
|
+
const ctxToken = await mintContext({
|
|
11919
|
+
cognitoToken: opts.cognitoToken,
|
|
11920
|
+
apiBaseUrl: opts.apiBaseUrl,
|
|
11921
|
+
contextId,
|
|
11922
|
+
tenant: opts.tenant
|
|
11923
|
+
});
|
|
11924
|
+
return new VectrosClient({
|
|
11925
|
+
token: ctxToken.token,
|
|
11926
|
+
environment: opts.apiBaseUrl
|
|
11927
|
+
});
|
|
11928
|
+
};
|
|
11929
|
+
}
|
|
11095
11930
|
async function runBootstrapCli(argv, deps = {}) {
|
|
11096
11931
|
const log = deps.log ?? createLogger2();
|
|
11097
11932
|
let args;
|
|
@@ -11173,12 +12008,17 @@ ${renderPlan(blueprint, planSteps)}
|
|
|
11173
12008
|
isTty,
|
|
11174
12009
|
deps.resolveCognitoToken
|
|
11175
12010
|
);
|
|
11176
|
-
const
|
|
12011
|
+
const mintBridge = deps.mintBridgeToken ?? mintBridgeToken;
|
|
12012
|
+
const mintContext = deps.mintContextToken ?? mintContextToken;
|
|
12013
|
+
const bridge = await mintBridge({
|
|
11177
12014
|
cognitoToken,
|
|
11178
12015
|
apiBaseUrl: baseUrl,
|
|
11179
|
-
useScopedToken: args.useScopedToken
|
|
12016
|
+
useScopedToken: args.useScopedToken,
|
|
12017
|
+
// Tenant rides the key (ssk_test_* vs ssk_live_*), not the base URL.
|
|
12018
|
+
tenant: args.tenant
|
|
11180
12019
|
});
|
|
11181
|
-
process.stdout.write(
|
|
12020
|
+
process.stdout.write(` \u2714 Bootstrap token minted (5-min TTL, ${args.tenant} tenant)
|
|
12021
|
+
`);
|
|
11182
12022
|
const client = new VectrosClient({ token: bridge.token, environment: baseUrl });
|
|
11183
12023
|
const identity = await resolveIdentity({ log, apiKey: bridge.token, environment: baseUrl });
|
|
11184
12024
|
const tenantId = identity.tenantId;
|
|
@@ -11194,17 +12034,16 @@ ${renderPlan(blueprint, planSteps)}
|
|
|
11194
12034
|
machineId,
|
|
11195
12035
|
rotate: args.rotate,
|
|
11196
12036
|
skipSeed: noSeed,
|
|
11197
|
-
remintForContext:
|
|
11198
|
-
|
|
11199
|
-
|
|
11200
|
-
|
|
11201
|
-
|
|
11202
|
-
|
|
11203
|
-
},
|
|
12037
|
+
remintForContext: buildRemintForContext({
|
|
12038
|
+
cognitoToken,
|
|
12039
|
+
apiBaseUrl: baseUrl,
|
|
12040
|
+
tenant: args.tenant,
|
|
12041
|
+
mintContext
|
|
12042
|
+
}),
|
|
11204
12043
|
log
|
|
11205
12044
|
});
|
|
11206
12045
|
process.stdout.write(
|
|
11207
|
-
` \u2714 AccessProfile + key (${result.keyAction}) \u2192 ${result.keyName}
|
|
12046
|
+
` \u2714 AccessProfile + ${args.tenant} key (${result.keyAction}) \u2192 ${result.keyName}
|
|
11208
12047
|
` + seedSummaryLine({
|
|
11209
12048
|
noSeed,
|
|
11210
12049
|
declinedInteractively: seedDeclinedInteractively,
|
|
@@ -11420,7 +12259,8 @@ servicePrincipal:
|
|
|
11420
12259
|
displayName: ${name}
|
|
11421
12260
|
|
|
11422
12261
|
seed:
|
|
11423
|
-
-
|
|
12262
|
+
- surface: record # 'record' (createRecord) or 'document' (text-ingest: needs title + text)
|
|
12263
|
+
typeName: thing
|
|
11424
12264
|
externalId: seed-welcome # stable \u2192 idempotent; re-apply converges
|
|
11425
12265
|
fields: { title: "Hello from ${name}" }
|
|
11426
12266
|
`;
|
|
@@ -11768,6 +12608,7 @@ async function withAuth(opts, deps, body) {
|
|
|
11768
12608
|
token: opts.token,
|
|
11769
12609
|
context: opts.context,
|
|
11770
12610
|
provisioning: opts.provisioning,
|
|
12611
|
+
tenant: opts.tenant,
|
|
11771
12612
|
useScopedToken: opts.useScopedToken,
|
|
11772
12613
|
log
|
|
11773
12614
|
});
|
|
@@ -11798,12 +12639,63 @@ function splitCsv(csv) {
|
|
|
11798
12639
|
return csv.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
11799
12640
|
}
|
|
11800
12641
|
|
|
12642
|
+
// src/paginate.ts
|
|
12643
|
+
var PAGE_LIMIT = 100;
|
|
12644
|
+
async function drainPages(fetch2) {
|
|
12645
|
+
const out = [];
|
|
12646
|
+
let startFrom;
|
|
12647
|
+
for (; ; ) {
|
|
12648
|
+
const page = await fetch2(startFrom);
|
|
12649
|
+
if (page.data) out.push(...page.data);
|
|
12650
|
+
const next = page.nextCursor;
|
|
12651
|
+
if (!next) break;
|
|
12652
|
+
startFrom = next;
|
|
12653
|
+
}
|
|
12654
|
+
return out;
|
|
12655
|
+
}
|
|
12656
|
+
|
|
12657
|
+
// src/self-principal.ts
|
|
12658
|
+
var PRINCIPAL_PREFIX_USER = "usr_";
|
|
12659
|
+
function emailFromJwt(token) {
|
|
12660
|
+
const parts = token.split(".");
|
|
12661
|
+
if (parts.length < 2) return void 0;
|
|
12662
|
+
try {
|
|
12663
|
+
const payload = Buffer.from(parts[1], "base64url").toString("utf8");
|
|
12664
|
+
const claims = JSON.parse(payload);
|
|
12665
|
+
return typeof claims.email === "string" && claims.email.trim() ? claims.email.trim() : void 0;
|
|
12666
|
+
} catch {
|
|
12667
|
+
return void 0;
|
|
12668
|
+
}
|
|
12669
|
+
}
|
|
12670
|
+
async function resolveSelfPrincipal(client, cognitoToken) {
|
|
12671
|
+
const email = emailFromJwt(cognitoToken);
|
|
12672
|
+
if (!email) return null;
|
|
12673
|
+
const target = email.toLowerCase();
|
|
12674
|
+
const users = await drainPages(
|
|
12675
|
+
(startFrom) => client.identity.listUsers(
|
|
12676
|
+
startFrom === void 0 ? { limit: PAGE_LIMIT } : { startFrom, limit: PAGE_LIMIT }
|
|
12677
|
+
)
|
|
12678
|
+
);
|
|
12679
|
+
const match = users.find(
|
|
12680
|
+
(u) => u.id && typeof u.email === "string" && u.email.toLowerCase() === target
|
|
12681
|
+
);
|
|
12682
|
+
if (!match?.id) return null;
|
|
12683
|
+
return { principalId: `${PRINCIPAL_PREFIX_USER}${match.id}`, userId: match.id, email };
|
|
12684
|
+
}
|
|
12685
|
+
|
|
11801
12686
|
// src/commands/whoami.ts
|
|
11802
12687
|
async function runWhoami(options, deps = {}) {
|
|
11803
12688
|
return withAuth(options, deps, async (auth) => {
|
|
11804
12689
|
const { identity, baseUrl } = auth;
|
|
12690
|
+
let self2 = null;
|
|
12691
|
+
try {
|
|
12692
|
+
self2 = await resolveSelfPrincipal(auth.client, auth.cognitoToken);
|
|
12693
|
+
} catch {
|
|
12694
|
+
self2 = null;
|
|
12695
|
+
}
|
|
11805
12696
|
if (options.json) {
|
|
11806
|
-
|
|
12697
|
+
const out = self2 ? { ...identity, userId: self2.userId, userPrincipalId: self2.principalId } : identity;
|
|
12698
|
+
process.stdout.write(`${formatJson(out)}
|
|
11807
12699
|
`);
|
|
11808
12700
|
return 0;
|
|
11809
12701
|
}
|
|
@@ -11815,6 +12707,7 @@ async function runWhoami(options, deps = {}) {
|
|
|
11815
12707
|
["principal", identity.principalType],
|
|
11816
12708
|
["principal id", identity.principalKeyId],
|
|
11817
12709
|
["label", identity.principalLabel],
|
|
12710
|
+
["your user id", self2?.principalId],
|
|
11818
12711
|
["allowed actions", identity.allowedActions?.length ? identity.allowedActions.join(", ") : void 0],
|
|
11819
12712
|
["token expires", expiry],
|
|
11820
12713
|
["api", baseUrl]
|
|
@@ -11824,21 +12717,6 @@ async function runWhoami(options, deps = {}) {
|
|
|
11824
12717
|
});
|
|
11825
12718
|
}
|
|
11826
12719
|
|
|
11827
|
-
// src/paginate.ts
|
|
11828
|
-
var PAGE_LIMIT = 100;
|
|
11829
|
-
async function drainPages(fetch2) {
|
|
11830
|
-
const out = [];
|
|
11831
|
-
let startFrom;
|
|
11832
|
-
for (; ; ) {
|
|
11833
|
-
const page = await fetch2(startFrom);
|
|
11834
|
-
if (page.data) out.push(...page.data);
|
|
11835
|
-
const next = page.nextCursor;
|
|
11836
|
-
if (!next) break;
|
|
11837
|
-
startFrom = next;
|
|
11838
|
-
}
|
|
11839
|
-
return out;
|
|
11840
|
-
}
|
|
11841
|
-
|
|
11842
12720
|
// src/commands/key.ts
|
|
11843
12721
|
var DEFAULT_KEY_NAME = "default";
|
|
11844
12722
|
function emitMintedKey(key, format) {
|
|
@@ -12044,19 +12922,23 @@ async function createIdentity(client, opts, payload) {
|
|
|
12044
12922
|
switch (opts.type) {
|
|
12045
12923
|
case "user":
|
|
12046
12924
|
return client.identity.createUser({
|
|
12047
|
-
|
|
12048
|
-
|
|
12049
|
-
|
|
12050
|
-
|
|
12925
|
+
body: {
|
|
12926
|
+
externalId,
|
|
12927
|
+
email: opts.email,
|
|
12928
|
+
type: opts.service ? "SERVICE" : "HUMAN",
|
|
12929
|
+
payload
|
|
12930
|
+
}
|
|
12051
12931
|
});
|
|
12052
12932
|
case "org":
|
|
12053
|
-
return client.identity.createOrg({ externalId, name: opts.name, payload });
|
|
12933
|
+
return client.identity.createOrg({ body: { externalId, name: opts.name, payload } });
|
|
12054
12934
|
case "client":
|
|
12055
12935
|
return client.identity.createClient({
|
|
12056
|
-
|
|
12057
|
-
|
|
12058
|
-
|
|
12059
|
-
|
|
12936
|
+
body: {
|
|
12937
|
+
externalId,
|
|
12938
|
+
name: opts.name,
|
|
12939
|
+
orgId: opts.org,
|
|
12940
|
+
payload
|
|
12941
|
+
}
|
|
12060
12942
|
});
|
|
12061
12943
|
}
|
|
12062
12944
|
}
|
|
@@ -12224,6 +13106,17 @@ function assertPrincipalShape(principal) {
|
|
|
12224
13106
|
throw new Error(`--principal must be 'usr_<userId>' or 'key_<keyId>' (got '${principal}').`);
|
|
12225
13107
|
}
|
|
12226
13108
|
}
|
|
13109
|
+
var SELF_PRINCIPAL_ALIAS = "me";
|
|
13110
|
+
async function resolvePrincipalArg(raw, auth) {
|
|
13111
|
+
if (raw !== SELF_PRINCIPAL_ALIAS) return raw;
|
|
13112
|
+
const self2 = await resolveSelfPrincipal(auth.client, auth.cognitoToken);
|
|
13113
|
+
if (!self2) {
|
|
13114
|
+
throw new Error(
|
|
13115
|
+
"Could not resolve your own user id from your login. Pass --principal usr_<id> explicitly (find the id with `vectros identity list --type user`)."
|
|
13116
|
+
);
|
|
13117
|
+
}
|
|
13118
|
+
return self2.principalId;
|
|
13119
|
+
}
|
|
12227
13120
|
function renderProfile(p) {
|
|
12228
13121
|
return formatKeyValues([
|
|
12229
13122
|
["context", p.contextId],
|
|
@@ -12240,21 +13133,24 @@ async function runAccessGrant(opts, deps = {}) {
|
|
|
12240
13133
|
process.stderr.write(" \u2716 Provide exactly one of --role <roleId> or --actions <csv>.\n");
|
|
12241
13134
|
return 2;
|
|
12242
13135
|
}
|
|
12243
|
-
|
|
12244
|
-
|
|
12245
|
-
|
|
12246
|
-
|
|
13136
|
+
if (opts.principal !== SELF_PRINCIPAL_ALIAS) {
|
|
13137
|
+
try {
|
|
13138
|
+
assertPrincipalShape(opts.principal);
|
|
13139
|
+
} catch (err) {
|
|
13140
|
+
process.stderr.write(` \u2716 ${formatError(err)}
|
|
12247
13141
|
`);
|
|
12248
|
-
|
|
13142
|
+
return 2;
|
|
13143
|
+
}
|
|
12249
13144
|
}
|
|
12250
13145
|
return withAuth(opts, deps, async (auth) => {
|
|
12251
13146
|
const client = auth.client;
|
|
12252
13147
|
try {
|
|
13148
|
+
const principal = await resolvePrincipalArg(opts.principal, auth);
|
|
12253
13149
|
const scopes = hasActions ? [{ allowed_actions: splitCsv(opts.actions) }] : void 0;
|
|
12254
13150
|
const profile = await client.auth.createAccessProfile({
|
|
12255
13151
|
contextId: opts.context,
|
|
12256
13152
|
body: {
|
|
12257
|
-
principalId:
|
|
13153
|
+
principalId: principal,
|
|
12258
13154
|
roleId: opts.role,
|
|
12259
13155
|
scopes
|
|
12260
13156
|
}
|
|
@@ -12266,7 +13162,7 @@ async function runAccessGrant(opts, deps = {}) {
|
|
|
12266
13162
|
}
|
|
12267
13163
|
process.stdout.write(
|
|
12268
13164
|
`
|
|
12269
|
-
\u2714 Granted ${
|
|
13165
|
+
\u2714 Granted ${principal} in '${opts.context}'` + (hasRole ? ` via role '${opts.role}'` : ` with ${splitCsv(opts.actions).length} action(s)`) + `
|
|
12270
13166
|
${renderProfile(profile)}
|
|
12271
13167
|
`
|
|
12272
13168
|
);
|
|
@@ -12279,18 +13175,21 @@ ${renderProfile(profile)}
|
|
|
12279
13175
|
});
|
|
12280
13176
|
}
|
|
12281
13177
|
async function runAccessRevoke(opts, deps = {}) {
|
|
12282
|
-
|
|
12283
|
-
|
|
12284
|
-
|
|
12285
|
-
|
|
13178
|
+
if (opts.principal !== SELF_PRINCIPAL_ALIAS) {
|
|
13179
|
+
try {
|
|
13180
|
+
assertPrincipalShape(opts.principal);
|
|
13181
|
+
} catch (err) {
|
|
13182
|
+
process.stderr.write(` \u2716 ${formatError(err)}
|
|
12286
13183
|
`);
|
|
12287
|
-
|
|
13184
|
+
return 2;
|
|
13185
|
+
}
|
|
12288
13186
|
}
|
|
12289
13187
|
return withAuth(opts, deps, async (auth) => {
|
|
12290
13188
|
const client = auth.client;
|
|
12291
13189
|
try {
|
|
12292
|
-
|
|
12293
|
-
|
|
13190
|
+
const principal = await resolvePrincipalArg(opts.principal, auth);
|
|
13191
|
+
await client.auth.deleteAccessProfile({ contextId: opts.context, principalId: principal });
|
|
13192
|
+
process.stdout.write(` \u2714 Revoked ${principal} from '${opts.context}'
|
|
12294
13193
|
`);
|
|
12295
13194
|
return 0;
|
|
12296
13195
|
} catch (err) {
|
|
@@ -12305,7 +13204,7 @@ async function runAccessList(opts, deps = {}) {
|
|
|
12305
13204
|
process.stderr.write(" \u2716 Provide exactly one of --context <c> or --principal <p>.\n");
|
|
12306
13205
|
return 2;
|
|
12307
13206
|
}
|
|
12308
|
-
if (opts.principal) {
|
|
13207
|
+
if (opts.principal && opts.principal !== SELF_PRINCIPAL_ALIAS) {
|
|
12309
13208
|
try {
|
|
12310
13209
|
assertPrincipalShape(opts.principal);
|
|
12311
13210
|
} catch (err) {
|
|
@@ -12317,10 +13216,11 @@ async function runAccessList(opts, deps = {}) {
|
|
|
12317
13216
|
return withAuth(opts, deps, async (auth) => {
|
|
12318
13217
|
const client = auth.client;
|
|
12319
13218
|
try {
|
|
13219
|
+
const principal = opts.principal ? await resolvePrincipalArg(opts.principal, auth) : void 0;
|
|
12320
13220
|
const rows = opts.context ? await drainPages(
|
|
12321
13221
|
(startFrom) => client.auth.listAccessProfiles({ contextId: opts.context, startFrom, limit: PAGE_LIMIT })
|
|
12322
13222
|
) : await drainPages(
|
|
12323
|
-
(startFrom) => client.auth.listProfilesForPrincipal({ principalId:
|
|
13223
|
+
(startFrom) => client.auth.listProfilesForPrincipal({ principalId: principal, startFrom, limit: PAGE_LIMIT })
|
|
12324
13224
|
);
|
|
12325
13225
|
if (opts.json) {
|
|
12326
13226
|
process.stdout.write(`${formatJson(rows)}
|
|
@@ -12344,17 +13244,20 @@ async function runAccessList(opts, deps = {}) {
|
|
|
12344
13244
|
});
|
|
12345
13245
|
}
|
|
12346
13246
|
async function runAccessGet(opts, deps = {}) {
|
|
12347
|
-
|
|
12348
|
-
|
|
12349
|
-
|
|
12350
|
-
|
|
13247
|
+
if (opts.principal !== SELF_PRINCIPAL_ALIAS) {
|
|
13248
|
+
try {
|
|
13249
|
+
assertPrincipalShape(opts.principal);
|
|
13250
|
+
} catch (err) {
|
|
13251
|
+
process.stderr.write(` \u2716 ${formatError(err)}
|
|
12351
13252
|
`);
|
|
12352
|
-
|
|
13253
|
+
return 2;
|
|
13254
|
+
}
|
|
12353
13255
|
}
|
|
12354
13256
|
return withAuth(opts, deps, async (auth) => {
|
|
12355
13257
|
const client = auth.client;
|
|
12356
13258
|
try {
|
|
12357
|
-
const
|
|
13259
|
+
const principal = await resolvePrincipalArg(opts.principal, auth);
|
|
13260
|
+
const p = await client.auth.getAccessProfile({ contextId: opts.context, principalId: principal });
|
|
12358
13261
|
if (opts.json) {
|
|
12359
13262
|
process.stdout.write(`${formatJson(p)}
|
|
12360
13263
|
`);
|
|
@@ -12497,8 +13400,10 @@ async function runContextCreate(opts, deps = {}) {
|
|
|
12497
13400
|
const client = auth.client;
|
|
12498
13401
|
try {
|
|
12499
13402
|
const ctx = await client.auth.createAppContext({
|
|
12500
|
-
|
|
12501
|
-
|
|
13403
|
+
body: {
|
|
13404
|
+
contextId: opts.contextId,
|
|
13405
|
+
name: opts.name ?? opts.contextId
|
|
13406
|
+
}
|
|
12502
13407
|
});
|
|
12503
13408
|
if (opts.json) {
|
|
12504
13409
|
process.stdout.write(`${formatJson(ctx)}
|
|
@@ -12809,23 +13714,22 @@ async function runAssertions(client, deps, blueprint, tenantId, result) {
|
|
|
12809
13714
|
}
|
|
12810
13715
|
}
|
|
12811
13716
|
for (const rec of blueprint.seed ?? []) {
|
|
13717
|
+
const isDoc = rec.surface === "document";
|
|
13718
|
+
const op = isDoc ? "lookupDocuments" : "lookupRecords";
|
|
13719
|
+
const noun = isDoc ? "document" : "record";
|
|
12812
13720
|
try {
|
|
12813
|
-
const page = await client.records.lookupRecords({
|
|
12814
|
-
type: rec.typeName,
|
|
12815
|
-
field: "externalId",
|
|
12816
|
-
value: rec.externalId
|
|
12817
|
-
});
|
|
13721
|
+
const page = isDoc ? await client.documents.lookupDocuments({ type: rec.typeName, field: "externalId", value: rec.externalId }) : await client.records.lookupRecords({ type: rec.typeName, field: "externalId", value: rec.externalId });
|
|
12818
13722
|
const ok = (page.data ?? []).length >= 1;
|
|
12819
13723
|
assertions.push({
|
|
12820
13724
|
name: `seed-externalId:${rec.externalId}`,
|
|
12821
13725
|
ok,
|
|
12822
|
-
detail: ok ? `'${rec.typeName}' seed resolves by externalId '${rec.externalId}'` : `'${rec.typeName}' seed externalId '${rec.externalId}' resolved 0
|
|
13726
|
+
detail: ok ? `'${rec.typeName}' seed resolves by externalId '${rec.externalId}'` : `'${rec.typeName}' seed externalId '${rec.externalId}' resolved 0 ${noun}s \u2014 externalId is not first-class (written only in payload?)`
|
|
12823
13727
|
});
|
|
12824
13728
|
} catch (e) {
|
|
12825
13729
|
assertions.push({
|
|
12826
13730
|
name: `seed-externalId:${rec.externalId}`,
|
|
12827
13731
|
ok: false,
|
|
12828
|
-
detail:
|
|
13732
|
+
detail: `${op}(externalId='${rec.externalId}') failed: ${errMsg(e)}`
|
|
12829
13733
|
});
|
|
12830
13734
|
}
|
|
12831
13735
|
}
|
|
@@ -12883,6 +13787,14 @@ async function runTeardown(client, blueprint, machineId, snapshot, result) {
|
|
|
12883
13787
|
steps.push({ step: `delete record ${id}`, ok: false, error: errMsg(e) });
|
|
12884
13788
|
}
|
|
12885
13789
|
}
|
|
13790
|
+
for (const id of result.seededDocumentIds) {
|
|
13791
|
+
try {
|
|
13792
|
+
await client.documents.deleteDocument({ id });
|
|
13793
|
+
steps.push({ step: `delete document ${id}`, ok: true });
|
|
13794
|
+
} catch (e) {
|
|
13795
|
+
steps.push({ step: `delete document ${id}`, ok: false, error: errMsg(e) });
|
|
13796
|
+
}
|
|
13797
|
+
}
|
|
12886
13798
|
const principalId = `usr_${result.servicePrincipalUserId}`;
|
|
12887
13799
|
if (snapshot.failed) {
|
|
12888
13800
|
steps.push({ step: `delete access profile ${principalId}`, ok: true, skipped: true, error: "skipped \u2014 pre-apply snapshot failed; cannot prove ownership" });
|
|
@@ -13017,7 +13929,7 @@ function renderReport(blueprint, env, report) {
|
|
|
13017
13929
|
if (report.applied) {
|
|
13018
13930
|
const a = report.applied;
|
|
13019
13931
|
lines.push(
|
|
13020
|
-
` \u2714 applied \u2014 key ${a.keyName} (${a.keyAction}), ${Object.keys(a.schemaIds).length} schema(s)` + (a.seededCount ? `, ${a.seededCount} seed
|
|
13932
|
+
` \u2714 applied \u2014 key ${a.keyName} (${a.keyAction}), ${Object.keys(a.schemaIds).length} schema(s)` + (a.seededCount ? `, ${a.seededCount} seed(s)` : "")
|
|
13021
13933
|
);
|
|
13022
13934
|
} else {
|
|
13023
13935
|
lines.push(` \u2716 apply failed \u2014 ${report.error ?? "unknown error"}`);
|
|
@@ -13080,7 +13992,10 @@ async function runBlueprintTest(fileOrName, opts, deps = {}) {
|
|
|
13080
13992
|
const ctxToken = await mintContextToken({
|
|
13081
13993
|
cognitoToken: auth.cognitoToken,
|
|
13082
13994
|
apiBaseUrl: auth.baseUrl,
|
|
13083
|
-
contextId
|
|
13995
|
+
contextId,
|
|
13996
|
+
// Same tenant as the bootstrap token (auth.tenant) — the in-context
|
|
13997
|
+
// load must land in the tenant the run targets.
|
|
13998
|
+
tenant: auth.tenant
|
|
13084
13999
|
});
|
|
13085
14000
|
return new VectrosClient({
|
|
13086
14001
|
token: ctxToken.token,
|
|
@@ -13118,9 +14033,9 @@ async function runBlueprintTest(fileOrName, opts, deps = {}) {
|
|
|
13118
14033
|
|
|
13119
14034
|
// src/build-info.ts
|
|
13120
14035
|
var BUILD_INFO = {
|
|
13121
|
-
cli: true ? "0.6.
|
|
13122
|
-
sdk: true ? "0.
|
|
13123
|
-
blueprints: true ? "0.
|
|
14036
|
+
cli: true ? "0.6.5" : "dev",
|
|
14037
|
+
sdk: true ? "0.31.0" : "dev",
|
|
14038
|
+
blueprints: true ? "0.6.3" : "dev"
|
|
13124
14039
|
};
|
|
13125
14040
|
function formatBuildInfo() {
|
|
13126
14041
|
return `vectros ${BUILD_INFO.cli} (sdk ${BUILD_INFO.sdk}, blueprints ${BUILD_INFO.blueprints})`;
|
|
@@ -13168,10 +14083,21 @@ function buildProgram(deps = {}) {
|
|
|
13168
14083
|
addCommonAuthOptions(whoami).action(async (flags) => {
|
|
13169
14084
|
process.exit(await runWhoami(toCommonOptions(flags), deps));
|
|
13170
14085
|
});
|
|
13171
|
-
const blueprintTest = program.command("blueprint-test").description("Apply a blueprint to a live env, assert it provisioned a working credential, then tear it down.").argument("<file>", "blueprint file path (./my.yaml) or a bundled blueprint name").option("--keep", "leave the provisioned graph in place (skip teardown) for inspection");
|
|
14086
|
+
const blueprintTest = program.command("blueprint-test").description("Apply a blueprint to a live env, assert it provisioned a working credential, then tear it down.").argument("<file>", "blueprint file path (./my.yaml) or a bundled blueprint name").option("--keep", "leave the provisioned graph in place (skip teardown) for inspection").option("--tenant <tenant>", "target tenant for the run: 'test' or 'live' (default live)");
|
|
13172
14087
|
addCommonAuthOptions(blueprintTest).action(
|
|
13173
14088
|
async (file, flags) => {
|
|
13174
|
-
|
|
14089
|
+
if (flags.tenant !== void 0 && flags.tenant !== "test" && flags.tenant !== "live") {
|
|
14090
|
+
process.stderr.write(` \u2716 --tenant must be 'test' or 'live' (got '${flags.tenant}')
|
|
14091
|
+
`);
|
|
14092
|
+
process.exit(2);
|
|
14093
|
+
}
|
|
14094
|
+
process.exit(
|
|
14095
|
+
await runBlueprintTest(
|
|
14096
|
+
file,
|
|
14097
|
+
{ ...toCommonOptions(flags), keep: Boolean(flags.keep), tenant: flags.tenant },
|
|
14098
|
+
deps
|
|
14099
|
+
)
|
|
14100
|
+
);
|
|
13175
14101
|
}
|
|
13176
14102
|
);
|
|
13177
14103
|
registerKeyCommands(program, deps);
|
|
@@ -13228,7 +14154,7 @@ function registerRoleCommands(program, deps) {
|
|
|
13228
14154
|
}
|
|
13229
14155
|
function registerAccessCommands(program, deps) {
|
|
13230
14156
|
const access2 = program.command("access").description("Manage access bindings: principal \u2194 context \u2194 role/scopes.");
|
|
13231
|
-
const grant = access2.command("grant").description("Bind a principal to a context via a role or inline actions.").requiredOption("--principal <p>", "principal: 'usr_<userId>'
|
|
14157
|
+
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 (XOR --actions)").option("--actions <csv>", "inline single-clause actions, comma-separated (XOR --role)");
|
|
13232
14158
|
addCommonAuthOptions(grant).action(
|
|
13233
14159
|
async (flags) => {
|
|
13234
14160
|
process.exit(
|
|
@@ -13242,15 +14168,15 @@ function registerAccessCommands(program, deps) {
|
|
|
13242
14168
|
);
|
|
13243
14169
|
}
|
|
13244
14170
|
);
|
|
13245
|
-
const revoke = access2.command("revoke").description("Remove a principal\u2019s binding in a context.").requiredOption("--principal <p>", "principal: 'usr_<userId>'
|
|
14171
|
+
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");
|
|
13246
14172
|
addAuthOptions(revoke).action(async (flags) => {
|
|
13247
14173
|
process.exit(await runAccessRevoke({ ...toCommonOptions(flags), principal: flags.principal, context: flags.context }, deps));
|
|
13248
14174
|
});
|
|
13249
|
-
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");
|
|
14175
|
+
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)");
|
|
13250
14176
|
addCommonAuthOptions(list).action(async (flags) => {
|
|
13251
14177
|
process.exit(await runAccessList({ ...toCommonOptions(flags), context: flags.context, principal: flags.principal }, deps));
|
|
13252
14178
|
});
|
|
13253
|
-
const get = access2.command("get").description("Show one principal\u2019s binding in a context.").requiredOption("--principal <p>", "principal: 'usr_<userId>'
|
|
14179
|
+
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");
|
|
13254
14180
|
addCommonAuthOptions(get).action(async (flags) => {
|
|
13255
14181
|
process.exit(await runAccessGet({ ...toCommonOptions(flags), principal: flags.principal, context: flags.context }, deps));
|
|
13256
14182
|
});
|