@vectros-ai/cli 0.6.1 → 0.6.4
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 +50 -0
- package/dist/cli.js +1157 -303
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +1157 -303
- 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,149 +9671,831 @@ var codingAgentMemory = {
|
|
|
9546
9671
|
]
|
|
9547
9672
|
};
|
|
9548
9673
|
var coding_agent_memory_default = codingAgentMemory;
|
|
9549
|
-
var
|
|
9550
|
-
name: "
|
|
9674
|
+
var agenticSdlc = {
|
|
9675
|
+
name: "agentic-sdlc",
|
|
9551
9676
|
version: "1.0.0",
|
|
9552
|
-
description: "A
|
|
9553
|
-
contextId: "
|
|
9554
|
-
contextName: "
|
|
9677
|
+
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.",
|
|
9678
|
+
contextId: "agentic-sdlc",
|
|
9679
|
+
contextName: "Agentic SDLC Knowledge Base",
|
|
9555
9680
|
schemas: [
|
|
9681
|
+
// ============================ DOCUMENTS ============================
|
|
9682
|
+
// Content-dominant: the markdown body is the artifact; fields are metadata.
|
|
9683
|
+
// Each binds the `document` surface and is ingested via `document_ingest`.
|
|
9556
9684
|
{
|
|
9557
|
-
|
|
9558
|
-
|
|
9685
|
+
// An architecture/product DECISION (an ADR). The body is the prose —
|
|
9686
|
+
// Context / Decision / Consequences — and is what `rag_ask` answers over;
|
|
9687
|
+
// the fields below are filter/sort metadata + the supersede chain. The
|
|
9688
|
+
// anchor of the graph: most other types link back to a decision.
|
|
9689
|
+
typeName: "decision",
|
|
9690
|
+
displayName: "Decision (ADR)",
|
|
9559
9691
|
indexMode: "HYBRID",
|
|
9560
|
-
|
|
9692
|
+
allowedSurfaces: ["document"],
|
|
9693
|
+
// A document carries an INTRINSIC title (the ingest title) + body; the schema
|
|
9694
|
+
// declares only the metadata BEYOND those. (No typed `title` field — that
|
|
9695
|
+
// would duplicate the document's own title.)
|
|
9561
9696
|
fields: [
|
|
9562
9697
|
{
|
|
9563
|
-
fieldId: "
|
|
9698
|
+
fieldId: "summary",
|
|
9564
9699
|
fieldType: "string",
|
|
9565
|
-
required: true,
|
|
9566
9700
|
searchable: true,
|
|
9567
|
-
|
|
9568
|
-
renderHints: { label: "
|
|
9701
|
+
description: "A one-paragraph abstract of the decision. The full reasoning is the document body.",
|
|
9702
|
+
renderHints: { label: "Summary", widget: "textarea", order: 2 }
|
|
9569
9703
|
},
|
|
9570
9704
|
{
|
|
9571
|
-
fieldId: "
|
|
9572
|
-
fieldType: "
|
|
9573
|
-
|
|
9574
|
-
|
|
9575
|
-
|
|
9576
|
-
renderHints: { label: "Body", widget: "textarea", order: 2, section: "Note" }
|
|
9705
|
+
fieldId: "status",
|
|
9706
|
+
fieldType: "enum",
|
|
9707
|
+
filterable: true,
|
|
9708
|
+
enumValues: ["proposed", "accepted", "superseded", "deprecated"],
|
|
9709
|
+
renderHints: { label: "Status", widget: "select", order: 3 }
|
|
9577
9710
|
},
|
|
9578
9711
|
{
|
|
9579
|
-
fieldId: "
|
|
9580
|
-
fieldType: "
|
|
9712
|
+
fieldId: "area",
|
|
9713
|
+
fieldType: "string",
|
|
9581
9714
|
filterable: true,
|
|
9582
|
-
description: '
|
|
9583
|
-
renderHints: { label: "
|
|
9715
|
+
description: 'Subsystem the decision applies to (e.g. "search", "auth", "billing").',
|
|
9716
|
+
renderHints: { label: "Area", widget: "text", order: 4 }
|
|
9584
9717
|
},
|
|
9585
9718
|
{
|
|
9586
|
-
|
|
9587
|
-
|
|
9588
|
-
// `other` keeps it from being a straitjacket for a personal brain-dump.
|
|
9589
|
-
fieldId: "source",
|
|
9590
|
-
fieldType: "enum",
|
|
9719
|
+
fieldId: "tags",
|
|
9720
|
+
fieldType: "array",
|
|
9591
9721
|
filterable: true,
|
|
9592
|
-
|
|
9593
|
-
|
|
9594
|
-
renderHints: { label: "Source", widget: "select", order: 4, section: "Organize" }
|
|
9722
|
+
description: 'Freeform labels (e.g. "security", "schema"). Search-side filter.',
|
|
9723
|
+
renderHints: { label: "Tags", order: 5 }
|
|
9595
9724
|
},
|
|
9596
9725
|
{
|
|
9597
|
-
|
|
9598
|
-
|
|
9599
|
-
|
|
9600
|
-
|
|
9601
|
-
|
|
9602
|
-
|
|
9726
|
+
// Self-reference (document → document): the decision this one supersedes.
|
|
9727
|
+
fieldId: "supersedes",
|
|
9728
|
+
fieldType: "reference",
|
|
9729
|
+
targetTypeName: "decision",
|
|
9730
|
+
targetSurface: "document",
|
|
9731
|
+
targetField: "externalId",
|
|
9732
|
+
cardinality: "one",
|
|
9733
|
+
description: "The decision this one supersedes (by externalId).",
|
|
9734
|
+
renderHints: { label: "Supersedes", order: 6 }
|
|
9603
9735
|
},
|
|
9604
9736
|
{
|
|
9605
|
-
fieldId: "
|
|
9737
|
+
fieldId: "date",
|
|
9606
9738
|
fieldType: "date",
|
|
9607
|
-
description:
|
|
9608
|
-
renderHints: { label: "
|
|
9739
|
+
description: "ISO-8601 decision date. Range-queryable / sortable.",
|
|
9740
|
+
renderHints: { label: "Date", widget: "date", order: 7 }
|
|
9609
9741
|
}
|
|
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
9742
|
],
|
|
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: [
|
|
9743
|
+
lookupFields: ["status", "area", "supersedes", { fieldName: "date", rangeEnabled: true }]
|
|
9744
|
+
},
|
|
9665
9745
|
{
|
|
9666
|
-
|
|
9667
|
-
|
|
9746
|
+
// A DESIGN doc or spec — the exploration that drives a decision. Distinct
|
|
9747
|
+
// from `decision` (a different browse genre + it links to the decision it
|
|
9748
|
+
// informs). Body = the design narrative.
|
|
9749
|
+
typeName: "design",
|
|
9750
|
+
displayName: "Design",
|
|
9668
9751
|
indexMode: "HYBRID",
|
|
9669
|
-
|
|
9670
|
-
//
|
|
9752
|
+
allowedSurfaces: ["document"],
|
|
9753
|
+
// Intrinsic title + body; schema = metadata beyond those (no typed `title`).
|
|
9671
9754
|
fields: [
|
|
9672
|
-
// --- Non-sensitive, searchable/filterable working fields ---
|
|
9673
9755
|
{
|
|
9674
|
-
fieldId: "
|
|
9675
|
-
fieldType: "string",
|
|
9676
|
-
required: true,
|
|
9677
|
-
description: "Caller-stable intake id; the dedup/lookup key.",
|
|
9678
|
-
validation: { minLength: 1, maxLength: 64 },
|
|
9679
|
-
renderHints: { label: "Case ID", widget: "text", order: 1, section: "Intake", displayField: true }
|
|
9680
|
-
},
|
|
9681
|
-
{
|
|
9682
|
-
fieldId: "presentingConcern",
|
|
9756
|
+
fieldId: "summary",
|
|
9683
9757
|
fieldType: "string",
|
|
9684
9758
|
searchable: true,
|
|
9685
|
-
description: "
|
|
9686
|
-
|
|
9687
|
-
renderHints: { label: "Presenting concern", widget: "textarea", order: 2, section: "Intake" }
|
|
9759
|
+
description: "A one-paragraph abstract. The full design is the document body.",
|
|
9760
|
+
renderHints: { label: "Summary", widget: "textarea", order: 2 }
|
|
9688
9761
|
},
|
|
9689
9762
|
{
|
|
9690
|
-
fieldId: "
|
|
9691
|
-
fieldType: "
|
|
9763
|
+
fieldId: "status",
|
|
9764
|
+
fieldType: "enum",
|
|
9765
|
+
filterable: true,
|
|
9766
|
+
enumValues: ["draft", "active", "implemented", "superseded"],
|
|
9767
|
+
renderHints: { label: "Status", widget: "select", order: 3 }
|
|
9768
|
+
},
|
|
9769
|
+
{
|
|
9770
|
+
fieldId: "area",
|
|
9771
|
+
fieldType: "string",
|
|
9772
|
+
filterable: true,
|
|
9773
|
+
renderHints: { label: "Area", widget: "text", order: 4 }
|
|
9774
|
+
},
|
|
9775
|
+
{
|
|
9776
|
+
fieldId: "tags",
|
|
9777
|
+
fieldType: "array",
|
|
9778
|
+
filterable: true,
|
|
9779
|
+
renderHints: { label: "Tags", order: 5 }
|
|
9780
|
+
},
|
|
9781
|
+
{
|
|
9782
|
+
// Cross-document edge: the decision this design informs/produces.
|
|
9783
|
+
fieldId: "relatedDecision",
|
|
9784
|
+
fieldType: "reference",
|
|
9785
|
+
targetTypeName: "decision",
|
|
9786
|
+
targetSurface: "document",
|
|
9787
|
+
targetField: "externalId",
|
|
9788
|
+
cardinality: "one",
|
|
9789
|
+
description: "The decision this design informs (by externalId).",
|
|
9790
|
+
renderHints: { label: "Related decision", order: 6 }
|
|
9791
|
+
},
|
|
9792
|
+
{
|
|
9793
|
+
// Self-reference: a design supersedes an earlier design/spec.
|
|
9794
|
+
fieldId: "supersedes",
|
|
9795
|
+
fieldType: "reference",
|
|
9796
|
+
targetTypeName: "design",
|
|
9797
|
+
targetSurface: "document",
|
|
9798
|
+
targetField: "externalId",
|
|
9799
|
+
cardinality: "one",
|
|
9800
|
+
description: "The design this one supersedes (by externalId).",
|
|
9801
|
+
renderHints: { label: "Supersedes", order: 7 }
|
|
9802
|
+
},
|
|
9803
|
+
{
|
|
9804
|
+
fieldId: "updatedOn",
|
|
9805
|
+
fieldType: "date",
|
|
9806
|
+
description: "ISO-8601 \u2014 when last revised. Range-queryable.",
|
|
9807
|
+
renderHints: { label: "Updated on", widget: "date", order: 8 }
|
|
9808
|
+
}
|
|
9809
|
+
],
|
|
9810
|
+
lookupFields: ["status", "area", "relatedDecision", "supersedes", { fieldName: "updatedOn", rangeEnabled: true }]
|
|
9811
|
+
},
|
|
9812
|
+
{
|
|
9813
|
+
// A REFERENCE doc — maintained "how it works / how to": guides, onboarding,
|
|
9814
|
+
// API docs, process docs. Body = the prose. Distinct shape: a `category`
|
|
9815
|
+
// (DRY sub-labels — onboarding/api/process are same shape) and `lastReviewed`
|
|
9816
|
+
// (freshness is the whole game for reference material).
|
|
9817
|
+
typeName: "reference",
|
|
9818
|
+
displayName: "Reference",
|
|
9819
|
+
indexMode: "HYBRID",
|
|
9820
|
+
allowedSurfaces: ["document"],
|
|
9821
|
+
// Intrinsic title + body; schema = metadata beyond those (no typed `title`).
|
|
9822
|
+
fields: [
|
|
9823
|
+
{
|
|
9824
|
+
fieldId: "summary",
|
|
9825
|
+
fieldType: "string",
|
|
9826
|
+
searchable: true,
|
|
9827
|
+
description: "A one-paragraph abstract. The full reference is the document body.",
|
|
9828
|
+
renderHints: { label: "Summary", widget: "textarea", order: 2 }
|
|
9829
|
+
},
|
|
9830
|
+
{
|
|
9831
|
+
// The sub-kind, as a filterable field (not a separate schema — these are
|
|
9832
|
+
// the same shape, differing only by label, so a field is the DRY choice).
|
|
9833
|
+
fieldId: "category",
|
|
9834
|
+
fieldType: "enum",
|
|
9835
|
+
filterable: true,
|
|
9836
|
+
enumValues: ["guide", "onboarding", "api", "process", "other"],
|
|
9837
|
+
renderHints: { label: "Category", widget: "select", order: 3 }
|
|
9838
|
+
},
|
|
9839
|
+
{
|
|
9840
|
+
fieldId: "area",
|
|
9841
|
+
fieldType: "string",
|
|
9842
|
+
filterable: true,
|
|
9843
|
+
renderHints: { label: "Area", widget: "text", order: 4 }
|
|
9844
|
+
},
|
|
9845
|
+
{
|
|
9846
|
+
fieldId: "tags",
|
|
9847
|
+
fieldType: "array",
|
|
9848
|
+
filterable: true,
|
|
9849
|
+
renderHints: { label: "Tags", order: 5 }
|
|
9850
|
+
},
|
|
9851
|
+
{
|
|
9852
|
+
fieldId: "status",
|
|
9853
|
+
fieldType: "enum",
|
|
9854
|
+
filterable: true,
|
|
9855
|
+
enumValues: ["active", "superseded"],
|
|
9856
|
+
renderHints: { label: "Status", widget: "select", order: 6 }
|
|
9857
|
+
},
|
|
9858
|
+
{
|
|
9859
|
+
// Optional cross-document edge: the decision behind this reference.
|
|
9860
|
+
fieldId: "relatedDecision",
|
|
9861
|
+
fieldType: "reference",
|
|
9862
|
+
targetTypeName: "decision",
|
|
9863
|
+
targetSurface: "document",
|
|
9864
|
+
targetField: "externalId",
|
|
9865
|
+
cardinality: "one",
|
|
9866
|
+
description: "The decision behind this reference, if any (by externalId).",
|
|
9867
|
+
renderHints: { label: "Related decision", order: 7 }
|
|
9868
|
+
},
|
|
9869
|
+
{
|
|
9870
|
+
// Freshness — "references not reviewed since X". The reference-doc signal.
|
|
9871
|
+
fieldId: "lastReviewed",
|
|
9872
|
+
fieldType: "date",
|
|
9873
|
+
description: "ISO-8601 \u2014 when this reference was last verified current. Range-queryable.",
|
|
9874
|
+
renderHints: { label: "Last reviewed", widget: "date", order: 8 }
|
|
9875
|
+
}
|
|
9876
|
+
],
|
|
9877
|
+
lookupFields: ["category", "area", "status", "relatedDecision", { fieldName: "lastReviewed", rangeEnabled: true }]
|
|
9878
|
+
},
|
|
9879
|
+
{
|
|
9880
|
+
// A RUNBOOK — a step-by-step operational procedure (deploy, release, recover).
|
|
9881
|
+
// Body = the procedure. Distinct browse genre, and often BORN FROM a
|
|
9882
|
+
// post-mortem (its resolution, codified) — a cross-document edge.
|
|
9883
|
+
typeName: "runbook",
|
|
9884
|
+
displayName: "Runbook",
|
|
9885
|
+
indexMode: "HYBRID",
|
|
9886
|
+
allowedSurfaces: ["document"],
|
|
9887
|
+
// Intrinsic title + body; schema = metadata beyond those (no typed `title`).
|
|
9888
|
+
fields: [
|
|
9889
|
+
{
|
|
9890
|
+
fieldId: "summary",
|
|
9891
|
+
fieldType: "string",
|
|
9892
|
+
searchable: true,
|
|
9893
|
+
description: "When to use this runbook \u2014 the trigger. The steps are the document body.",
|
|
9894
|
+
renderHints: { label: "Summary", widget: "textarea", order: 2 }
|
|
9895
|
+
},
|
|
9896
|
+
{
|
|
9897
|
+
fieldId: "area",
|
|
9898
|
+
fieldType: "string",
|
|
9899
|
+
filterable: true,
|
|
9900
|
+
renderHints: { label: "Area", widget: "text", order: 3 }
|
|
9901
|
+
},
|
|
9902
|
+
{
|
|
9903
|
+
fieldId: "tags",
|
|
9904
|
+
fieldType: "array",
|
|
9905
|
+
filterable: true,
|
|
9906
|
+
renderHints: { label: "Tags", order: 4 }
|
|
9907
|
+
},
|
|
9908
|
+
{
|
|
9909
|
+
fieldId: "status",
|
|
9910
|
+
fieldType: "enum",
|
|
9911
|
+
filterable: true,
|
|
9912
|
+
enumValues: ["active", "retired"],
|
|
9913
|
+
renderHints: { label: "Status", widget: "select", order: 5 }
|
|
9914
|
+
},
|
|
9915
|
+
{
|
|
9916
|
+
// Cross-document edge: the post-mortem whose resolution this runbook codifies.
|
|
9917
|
+
fieldId: "bornFrom",
|
|
9918
|
+
fieldType: "reference",
|
|
9919
|
+
targetTypeName: "postmortem",
|
|
9920
|
+
targetSurface: "document",
|
|
9921
|
+
targetField: "externalId",
|
|
9922
|
+
cardinality: "one",
|
|
9923
|
+
description: "The post-mortem this runbook was codified from (by externalId).",
|
|
9924
|
+
renderHints: { label: "Born from (post-mortem)", order: 6 }
|
|
9925
|
+
},
|
|
9926
|
+
{
|
|
9927
|
+
fieldId: "relatedDecision",
|
|
9928
|
+
fieldType: "reference",
|
|
9929
|
+
targetTypeName: "decision",
|
|
9930
|
+
targetSurface: "document",
|
|
9931
|
+
targetField: "externalId",
|
|
9932
|
+
cardinality: "one",
|
|
9933
|
+
description: "A decision this runbook implements, if any (by externalId).",
|
|
9934
|
+
renderHints: { label: "Related decision", order: 7 }
|
|
9935
|
+
},
|
|
9936
|
+
{
|
|
9937
|
+
fieldId: "updatedOn",
|
|
9938
|
+
fieldType: "date",
|
|
9939
|
+
description: "ISO-8601 \u2014 when last revised. Range-queryable.",
|
|
9940
|
+
renderHints: { label: "Updated on", widget: "date", order: 8 }
|
|
9941
|
+
}
|
|
9942
|
+
],
|
|
9943
|
+
lookupFields: ["area", "status", "bornFrom", "relatedDecision", { fieldName: "updatedOn", rangeEnabled: true }]
|
|
9944
|
+
},
|
|
9945
|
+
{
|
|
9946
|
+
// A POST-MORTEM — what broke and the durable lesson. Body = the writeup
|
|
9947
|
+
// (impact / root cause / resolution / lesson). Distinct shape: `severity` +
|
|
9948
|
+
// `occurredOn` (the incident timeline). "Have we hit this before?" lives here.
|
|
9949
|
+
typeName: "postmortem",
|
|
9950
|
+
displayName: "Post-mortem",
|
|
9951
|
+
indexMode: "HYBRID",
|
|
9952
|
+
allowedSurfaces: ["document"],
|
|
9953
|
+
// Intrinsic title + body; schema = metadata beyond those (no typed `title`).
|
|
9954
|
+
fields: [
|
|
9955
|
+
{
|
|
9956
|
+
fieldId: "summary",
|
|
9957
|
+
fieldType: "string",
|
|
9958
|
+
searchable: true,
|
|
9959
|
+
description: "What happened, in brief. The full analysis (impact/root cause/resolution/lesson) is the body.",
|
|
9960
|
+
renderHints: { label: "Summary", widget: "textarea", order: 2 }
|
|
9961
|
+
},
|
|
9962
|
+
{
|
|
9963
|
+
fieldId: "severity",
|
|
9964
|
+
fieldType: "enum",
|
|
9965
|
+
filterable: true,
|
|
9966
|
+
enumValues: ["low", "medium", "high", "critical"],
|
|
9967
|
+
renderHints: { label: "Severity", widget: "select", order: 3 }
|
|
9968
|
+
},
|
|
9969
|
+
{
|
|
9970
|
+
fieldId: "status",
|
|
9971
|
+
fieldType: "enum",
|
|
9972
|
+
filterable: true,
|
|
9973
|
+
enumValues: ["open", "mitigated", "resolved"],
|
|
9974
|
+
renderHints: { label: "Status", widget: "select", order: 4 }
|
|
9975
|
+
},
|
|
9976
|
+
{
|
|
9977
|
+
fieldId: "area",
|
|
9978
|
+
fieldType: "string",
|
|
9979
|
+
filterable: true,
|
|
9980
|
+
renderHints: { label: "Area", widget: "text", order: 5 }
|
|
9981
|
+
},
|
|
9982
|
+
{
|
|
9983
|
+
fieldId: "tags",
|
|
9984
|
+
fieldType: "array",
|
|
9985
|
+
filterable: true,
|
|
9986
|
+
renderHints: { label: "Tags", order: 6 }
|
|
9987
|
+
},
|
|
9988
|
+
{
|
|
9989
|
+
fieldId: "relatedDecision",
|
|
9990
|
+
fieldType: "reference",
|
|
9991
|
+
targetTypeName: "decision",
|
|
9992
|
+
targetSurface: "document",
|
|
9993
|
+
targetField: "externalId",
|
|
9994
|
+
cardinality: "one",
|
|
9995
|
+
description: "A decision implicated in or addressed by this incident (by externalId).",
|
|
9996
|
+
renderHints: { label: "Related decision", order: 7 }
|
|
9997
|
+
},
|
|
9998
|
+
{
|
|
9999
|
+
fieldId: "occurredOn",
|
|
10000
|
+
fieldType: "date",
|
|
10001
|
+
description: 'ISO-8601 \u2014 when it happened. Range-queryable \u2014 "incidents this month".',
|
|
10002
|
+
renderHints: { label: "Occurred on", widget: "date", order: 8 }
|
|
10003
|
+
}
|
|
10004
|
+
],
|
|
10005
|
+
lookupFields: ["severity", "status", "area", "relatedDecision", { fieldName: "occurredOn", rangeEnabled: true }]
|
|
10006
|
+
},
|
|
10007
|
+
// ============================= RECORDS =============================
|
|
10008
|
+
// Structure-dominant: the typed fields are the artifact. Short, exact-queryable.
|
|
10009
|
+
{
|
|
10010
|
+
// A governance CONTROL — a policy/standard/control the codebase must satisfy,
|
|
10011
|
+
// WITH its evidence. The compliance instrument: it records what enforces it
|
|
10012
|
+
// (`evidence`), the runbook that VERIFIES it (cross-surface → document), and
|
|
10013
|
+
// the decision that mandates it. "Which critical controls are active, and how
|
|
10014
|
+
// is each proven?"
|
|
10015
|
+
typeName: "control",
|
|
10016
|
+
displayName: "Control",
|
|
10017
|
+
indexMode: "HYBRID",
|
|
10018
|
+
fields: [
|
|
10019
|
+
{
|
|
10020
|
+
fieldId: "title",
|
|
10021
|
+
fieldType: "string",
|
|
10022
|
+
required: true,
|
|
10023
|
+
searchable: true,
|
|
10024
|
+
validation: { minLength: 1, maxLength: 200 },
|
|
10025
|
+
renderHints: { label: "Title", widget: "text", order: 1, displayField: true }
|
|
10026
|
+
},
|
|
10027
|
+
{
|
|
10028
|
+
fieldId: "statement",
|
|
10029
|
+
fieldType: "string",
|
|
10030
|
+
searchable: true,
|
|
10031
|
+
description: "The requirement \u2014 what must always hold.",
|
|
10032
|
+
renderHints: { label: "Statement", widget: "textarea", order: 2 }
|
|
10033
|
+
},
|
|
10034
|
+
{
|
|
10035
|
+
fieldId: "rationale",
|
|
10036
|
+
fieldType: "string",
|
|
10037
|
+
searchable: true,
|
|
10038
|
+
description: "Why the control exists \u2014 the risk it prevents.",
|
|
10039
|
+
renderHints: { label: "Rationale", widget: "textarea", order: 3 }
|
|
10040
|
+
},
|
|
10041
|
+
{
|
|
10042
|
+
// The policy → implementation spectrum in one filterable field.
|
|
10043
|
+
fieldId: "kind",
|
|
10044
|
+
fieldType: "enum",
|
|
10045
|
+
filterable: true,
|
|
10046
|
+
enumValues: ["policy", "standard", "control"],
|
|
10047
|
+
renderHints: { label: "Kind", widget: "select", order: 4 }
|
|
10048
|
+
},
|
|
10049
|
+
{
|
|
10050
|
+
// Ordinal, but EQUALITY (range/prefix order is lexical, so low<critical
|
|
10051
|
+
// would sort alphabetically). Enumerate "all critical controls" by equality.
|
|
10052
|
+
fieldId: "criticality",
|
|
10053
|
+
fieldType: "enum",
|
|
10054
|
+
filterable: true,
|
|
10055
|
+
enumValues: ["low", "medium", "high", "critical"],
|
|
10056
|
+
renderHints: { label: "Criticality", widget: "select", order: 5 }
|
|
10057
|
+
},
|
|
10058
|
+
{
|
|
10059
|
+
fieldId: "evidence",
|
|
10060
|
+
fieldType: "string",
|
|
10061
|
+
searchable: true,
|
|
10062
|
+
description: "What enforces or proves the control inline (e.g. an architecture test or gate).",
|
|
10063
|
+
renderHints: { label: "Evidence", widget: "textarea", order: 6 }
|
|
10064
|
+
},
|
|
10065
|
+
{
|
|
10066
|
+
fieldId: "status",
|
|
10067
|
+
fieldType: "enum",
|
|
10068
|
+
filterable: true,
|
|
10069
|
+
enumValues: ["draft", "active", "retired"],
|
|
10070
|
+
renderHints: { label: "Status", widget: "select", order: 7 }
|
|
10071
|
+
},
|
|
10072
|
+
{
|
|
10073
|
+
fieldId: "area",
|
|
10074
|
+
fieldType: "string",
|
|
10075
|
+
filterable: true,
|
|
10076
|
+
renderHints: { label: "Area", widget: "text", order: 8 }
|
|
10077
|
+
},
|
|
10078
|
+
{
|
|
10079
|
+
fieldId: "tags",
|
|
10080
|
+
fieldType: "array",
|
|
10081
|
+
filterable: true,
|
|
10082
|
+
renderHints: { label: "Tags", order: 9 }
|
|
10083
|
+
},
|
|
10084
|
+
{
|
|
10085
|
+
// Cross-surface edge (record → document): the runbook that proves the control.
|
|
10086
|
+
fieldId: "verifiedBy",
|
|
10087
|
+
fieldType: "reference",
|
|
10088
|
+
targetTypeName: "runbook",
|
|
10089
|
+
targetSurface: "document",
|
|
10090
|
+
targetField: "externalId",
|
|
10091
|
+
cardinality: "one",
|
|
10092
|
+
description: "The runbook that verifies this control (by externalId).",
|
|
10093
|
+
renderHints: { label: "Verified by (runbook)", order: 10 }
|
|
10094
|
+
},
|
|
10095
|
+
{
|
|
10096
|
+
// Cross-surface edge (record → document): the decision that mandates it.
|
|
10097
|
+
fieldId: "relatedDecision",
|
|
10098
|
+
fieldType: "reference",
|
|
10099
|
+
targetTypeName: "decision",
|
|
10100
|
+
targetSurface: "document",
|
|
10101
|
+
targetField: "externalId",
|
|
10102
|
+
cardinality: "one",
|
|
10103
|
+
description: "The decision that mandates this control (by externalId).",
|
|
10104
|
+
renderHints: { label: "Related decision", order: 11 }
|
|
10105
|
+
},
|
|
10106
|
+
{
|
|
10107
|
+
fieldId: "reviewedOn",
|
|
10108
|
+
fieldType: "date",
|
|
10109
|
+
description: "ISO-8601 last-reviewed date. Range-queryable.",
|
|
10110
|
+
renderHints: { label: "Reviewed on", widget: "date", order: 12 }
|
|
10111
|
+
}
|
|
10112
|
+
],
|
|
10113
|
+
lookupFields: [
|
|
10114
|
+
"kind",
|
|
10115
|
+
"criticality",
|
|
10116
|
+
"status",
|
|
10117
|
+
"area",
|
|
10118
|
+
"verifiedBy",
|
|
10119
|
+
"relatedDecision",
|
|
10120
|
+
{ fieldName: "reviewedOn", rangeEnabled: true }
|
|
10121
|
+
]
|
|
10122
|
+
},
|
|
10123
|
+
{
|
|
10124
|
+
// A CONVENTION — a must-follow operating rule the team teaches its agents. The
|
|
10125
|
+
// durable, shareable operating-memory. Distinct fields capture how we actually
|
|
10126
|
+
// write these: the `rule` (imperative), the `why` (rationale), and `howToApply`
|
|
10127
|
+
// (the concrete application) are SEPARATE fields, not one blob — so an agent can
|
|
10128
|
+
// recall the rule, the reasoning, and the how independently.
|
|
10129
|
+
typeName: "convention",
|
|
10130
|
+
displayName: "Convention",
|
|
10131
|
+
indexMode: "HYBRID",
|
|
10132
|
+
fields: [
|
|
10133
|
+
{
|
|
10134
|
+
fieldId: "title",
|
|
10135
|
+
fieldType: "string",
|
|
10136
|
+
required: true,
|
|
10137
|
+
searchable: true,
|
|
10138
|
+
validation: { minLength: 1, maxLength: 200 },
|
|
10139
|
+
renderHints: { label: "Title", widget: "text", order: 1, displayField: true }
|
|
10140
|
+
},
|
|
10141
|
+
{
|
|
10142
|
+
fieldId: "rule",
|
|
10143
|
+
fieldType: "string",
|
|
10144
|
+
searchable: true,
|
|
10145
|
+
description: "The convention itself, stated as an imperative.",
|
|
10146
|
+
renderHints: { label: "Rule", widget: "textarea", order: 2 }
|
|
10147
|
+
},
|
|
10148
|
+
{
|
|
10149
|
+
fieldId: "why",
|
|
10150
|
+
fieldType: "string",
|
|
10151
|
+
searchable: true,
|
|
10152
|
+
description: "Why it matters \u2014 the trade-off / the antipattern it prevents.",
|
|
10153
|
+
renderHints: { label: "Why", widget: "textarea", order: 3 }
|
|
10154
|
+
},
|
|
10155
|
+
{
|
|
10156
|
+
fieldId: "howToApply",
|
|
10157
|
+
fieldType: "string",
|
|
10158
|
+
searchable: true,
|
|
10159
|
+
description: "How to comply in practice \u2014 the concrete application steps.",
|
|
10160
|
+
renderHints: { label: "How to apply", widget: "textarea", order: 4 }
|
|
10161
|
+
},
|
|
10162
|
+
{
|
|
10163
|
+
fieldId: "area",
|
|
10164
|
+
fieldType: "string",
|
|
10165
|
+
filterable: true,
|
|
10166
|
+
renderHints: { label: "Area", widget: "text", order: 5 }
|
|
10167
|
+
},
|
|
10168
|
+
{
|
|
10169
|
+
fieldId: "status",
|
|
10170
|
+
fieldType: "enum",
|
|
10171
|
+
filterable: true,
|
|
10172
|
+
enumValues: ["active", "retired"],
|
|
10173
|
+
renderHints: { label: "Status", widget: "select", order: 6 }
|
|
10174
|
+
},
|
|
10175
|
+
{
|
|
10176
|
+
fieldId: "tags",
|
|
10177
|
+
fieldType: "array",
|
|
10178
|
+
filterable: true,
|
|
10179
|
+
renderHints: { label: "Tags", order: 7 }
|
|
10180
|
+
},
|
|
10181
|
+
{
|
|
10182
|
+
// Cross-surface edge (record → document): the decision that established it.
|
|
10183
|
+
fieldId: "establishedBy",
|
|
10184
|
+
fieldType: "reference",
|
|
10185
|
+
targetTypeName: "decision",
|
|
10186
|
+
targetSurface: "document",
|
|
10187
|
+
targetField: "externalId",
|
|
10188
|
+
cardinality: "one",
|
|
10189
|
+
description: "The decision that established this convention (by externalId).",
|
|
10190
|
+
renderHints: { label: "Established by (decision)", order: 8 }
|
|
10191
|
+
},
|
|
10192
|
+
{
|
|
10193
|
+
fieldId: "updatedOn",
|
|
10194
|
+
fieldType: "date",
|
|
10195
|
+
description: "ISO-8601 \u2014 when last revised. Range-queryable.",
|
|
10196
|
+
renderHints: { label: "Updated on", widget: "date", order: 9 }
|
|
10197
|
+
}
|
|
10198
|
+
],
|
|
10199
|
+
lookupFields: ["area", "status", "establishedBy", { fieldName: "updatedOn", rangeEnabled: true }]
|
|
10200
|
+
},
|
|
10201
|
+
{
|
|
10202
|
+
// A GOTCHA / sharp edge: a symptom, its cause, and the fix. A tight typed
|
|
10203
|
+
// triple — found by meaning (semantic search on the symptom) + area/status.
|
|
10204
|
+
// The most standalone type; no typed edge.
|
|
10205
|
+
typeName: "gotcha",
|
|
10206
|
+
displayName: "Gotcha",
|
|
10207
|
+
indexMode: "HYBRID",
|
|
10208
|
+
fields: [
|
|
10209
|
+
{
|
|
10210
|
+
fieldId: "symptom",
|
|
10211
|
+
fieldType: "string",
|
|
10212
|
+
required: true,
|
|
10213
|
+
searchable: true,
|
|
10214
|
+
validation: { minLength: 1, maxLength: 500 },
|
|
10215
|
+
renderHints: { label: "Symptom", widget: "textarea", order: 1, displayField: true }
|
|
10216
|
+
},
|
|
10217
|
+
{
|
|
10218
|
+
fieldId: "cause",
|
|
10219
|
+
fieldType: "string",
|
|
10220
|
+
searchable: true,
|
|
10221
|
+
renderHints: { label: "Cause", widget: "textarea", order: 2 }
|
|
10222
|
+
},
|
|
10223
|
+
{
|
|
10224
|
+
fieldId: "fix",
|
|
10225
|
+
fieldType: "string",
|
|
10226
|
+
searchable: true,
|
|
10227
|
+
renderHints: { label: "Fix", widget: "textarea", order: 3 }
|
|
10228
|
+
},
|
|
10229
|
+
{
|
|
10230
|
+
fieldId: "area",
|
|
10231
|
+
fieldType: "string",
|
|
10232
|
+
filterable: true,
|
|
10233
|
+
renderHints: { label: "Area", widget: "text", order: 4 }
|
|
10234
|
+
},
|
|
10235
|
+
{
|
|
10236
|
+
fieldId: "status",
|
|
10237
|
+
fieldType: "enum",
|
|
10238
|
+
filterable: true,
|
|
10239
|
+
enumValues: ["active", "resolved"],
|
|
10240
|
+
renderHints: { label: "Status", widget: "select", order: 5 }
|
|
10241
|
+
},
|
|
10242
|
+
{
|
|
10243
|
+
fieldId: "tags",
|
|
10244
|
+
fieldType: "array",
|
|
10245
|
+
filterable: true,
|
|
10246
|
+
renderHints: { label: "Tags", order: 6 }
|
|
10247
|
+
},
|
|
10248
|
+
{
|
|
10249
|
+
fieldId: "discoveredOn",
|
|
10250
|
+
fieldType: "date",
|
|
10251
|
+
description: "ISO-8601 \u2014 when first hit. Range-queryable.",
|
|
10252
|
+
renderHints: { label: "Discovered on", widget: "date", order: 7 }
|
|
10253
|
+
}
|
|
10254
|
+
],
|
|
10255
|
+
lookupFields: ["area", "status", { fieldName: "discoveredOn", rangeEnabled: true }]
|
|
10256
|
+
},
|
|
10257
|
+
{
|
|
10258
|
+
// A glossary TERM — a definition keyed by the term itself. Structure-dominant:
|
|
10259
|
+
// `term` is a UNIQUE exact-lookup key (the showcase of a uniqueness constraint),
|
|
10260
|
+
// `definition` is the RAG body, `aliases` catch alternate names. Links to the
|
|
10261
|
+
// decision that defines/establishes the concept where there is one.
|
|
10262
|
+
typeName: "term",
|
|
10263
|
+
displayName: "Glossary term",
|
|
10264
|
+
indexMode: "HYBRID",
|
|
10265
|
+
fields: [
|
|
10266
|
+
{
|
|
10267
|
+
fieldId: "term",
|
|
10268
|
+
fieldType: "string",
|
|
10269
|
+
required: true,
|
|
10270
|
+
searchable: true,
|
|
10271
|
+
validation: { minLength: 1, maxLength: 200 },
|
|
10272
|
+
renderHints: { label: "Term", widget: "text", order: 1, displayField: true }
|
|
10273
|
+
},
|
|
10274
|
+
{
|
|
10275
|
+
fieldId: "definition",
|
|
10276
|
+
fieldType: "string",
|
|
10277
|
+
searchable: true,
|
|
10278
|
+
description: "What the term means \u2014 the RAG-able body.",
|
|
10279
|
+
renderHints: { label: "Definition", widget: "textarea", order: 2 }
|
|
10280
|
+
},
|
|
10281
|
+
{
|
|
10282
|
+
fieldId: "aliases",
|
|
10283
|
+
fieldType: "array",
|
|
10284
|
+
filterable: true,
|
|
10285
|
+
description: "Alternate names / abbreviations for the same concept.",
|
|
10286
|
+
renderHints: { label: "Aliases", order: 3 }
|
|
10287
|
+
},
|
|
10288
|
+
{
|
|
10289
|
+
fieldId: "area",
|
|
10290
|
+
fieldType: "string",
|
|
10291
|
+
filterable: true,
|
|
10292
|
+
renderHints: { label: "Area", widget: "text", order: 4 }
|
|
10293
|
+
},
|
|
10294
|
+
{
|
|
10295
|
+
fieldId: "tags",
|
|
10296
|
+
fieldType: "array",
|
|
10297
|
+
filterable: true,
|
|
10298
|
+
renderHints: { label: "Tags", order: 5 }
|
|
10299
|
+
},
|
|
10300
|
+
{
|
|
10301
|
+
fieldId: "relatedDecision",
|
|
10302
|
+
fieldType: "reference",
|
|
10303
|
+
targetTypeName: "decision",
|
|
10304
|
+
targetSurface: "document",
|
|
10305
|
+
targetField: "externalId",
|
|
10306
|
+
cardinality: "one",
|
|
10307
|
+
description: "A decision that defines or establishes this term (by externalId).",
|
|
10308
|
+
renderHints: { label: "Related decision", order: 6 }
|
|
10309
|
+
},
|
|
10310
|
+
{
|
|
10311
|
+
fieldId: "updatedOn",
|
|
10312
|
+
fieldType: "date",
|
|
10313
|
+
description: "ISO-8601 \u2014 when last revised. Range-queryable.",
|
|
10314
|
+
renderHints: { label: "Updated on", widget: "date", order: 7 }
|
|
10315
|
+
}
|
|
10316
|
+
],
|
|
10317
|
+
// `term` is a UNIQUE equality lookup — exact "define X" + a one-per-term
|
|
10318
|
+
// guarantee. `area`/`relatedDecision` enumerate; `updatedOn` is the range row.
|
|
10319
|
+
lookupFields: [
|
|
10320
|
+
{ fieldName: "term", unique: true },
|
|
10321
|
+
"area",
|
|
10322
|
+
"relatedDecision",
|
|
10323
|
+
{ fieldName: "updatedOn", rangeEnabled: true }
|
|
10324
|
+
]
|
|
10325
|
+
}
|
|
10326
|
+
],
|
|
10327
|
+
// Least-privilege, data-plane only. r/c/u records + search + schema discovery +
|
|
10328
|
+
// inference:r (grounded recall over the document bodies) + document/folder r/c
|
|
10329
|
+
// (the content artifacts are documents). NO :d — knowledge is superseded/retired
|
|
10330
|
+
// via a status flip, so the trail of how the team's thinking evolved stays intact.
|
|
10331
|
+
accessProfile: {
|
|
10332
|
+
allowedActions: [
|
|
10333
|
+
"records:r",
|
|
10334
|
+
"records:c",
|
|
10335
|
+
"records:u",
|
|
10336
|
+
"search:r",
|
|
10337
|
+
"schemas:r",
|
|
10338
|
+
"inference:r",
|
|
10339
|
+
"documents:r",
|
|
10340
|
+
"documents:c",
|
|
10341
|
+
"folders:r",
|
|
10342
|
+
"folders:c"
|
|
10343
|
+
]
|
|
10344
|
+
},
|
|
10345
|
+
servicePrincipal: {
|
|
10346
|
+
externalId: "agentic-sdlc",
|
|
10347
|
+
displayName: "Agentic SDLC Knowledge Base"
|
|
10348
|
+
}
|
|
10349
|
+
// No bundled seed in this version: the content artifacts live on the document
|
|
10350
|
+
// surface and the cross-surface graph is populated by the ingest agent
|
|
10351
|
+
// (document_ingest / record_create), not the bootstrap seed step. Production
|
|
10352
|
+
// contexts provision with `vectros bootstrap --no-seed` regardless.
|
|
10353
|
+
};
|
|
10354
|
+
var agentic_sdlc_default = agenticSdlc;
|
|
10355
|
+
var secondBrain = {
|
|
10356
|
+
name: "second-brain",
|
|
10357
|
+
version: "1.0.0",
|
|
10358
|
+
description: "A personal knowledge base \u2014 capture notes, ideas, and links, then ask them anything.",
|
|
10359
|
+
contextId: "second-brain",
|
|
10360
|
+
contextName: "Second Brain",
|
|
10361
|
+
schemas: [
|
|
10362
|
+
{
|
|
10363
|
+
typeName: "note",
|
|
10364
|
+
displayName: "Note",
|
|
10365
|
+
indexMode: "HYBRID",
|
|
10366
|
+
// keyword on title + semantic on body — recall by meaning
|
|
10367
|
+
fields: [
|
|
10368
|
+
{
|
|
10369
|
+
fieldId: "title",
|
|
10370
|
+
fieldType: "string",
|
|
10371
|
+
required: true,
|
|
10372
|
+
searchable: true,
|
|
10373
|
+
validation: { minLength: 1, maxLength: 200 },
|
|
10374
|
+
renderHints: { label: "Title", widget: "text", order: 1, section: "Note", displayField: true }
|
|
10375
|
+
},
|
|
10376
|
+
{
|
|
10377
|
+
fieldId: "body",
|
|
10378
|
+
fieldType: "string",
|
|
10379
|
+
searchable: true,
|
|
10380
|
+
description: "The note itself \u2014 the RAG-able body you ask questions against.",
|
|
10381
|
+
validation: { maxLength: 2e4 },
|
|
10382
|
+
renderHints: { label: "Body", widget: "textarea", order: 2, section: "Note" }
|
|
10383
|
+
},
|
|
10384
|
+
{
|
|
10385
|
+
fieldId: "tags",
|
|
10386
|
+
fieldType: "array",
|
|
10387
|
+
filterable: true,
|
|
10388
|
+
description: 'Freeform string labels for filtering (e.g. "idea", "reading", "work").',
|
|
10389
|
+
renderHints: { label: "Tags", order: 3, section: "Organize" }
|
|
10390
|
+
},
|
|
10391
|
+
{
|
|
10392
|
+
// An enum (not freeform) so enumeration-by-source is reliable — a
|
|
10393
|
+
// `record_query` lookup on `source` only works if values are consistent.
|
|
10394
|
+
// `other` keeps it from being a straitjacket for a personal brain-dump.
|
|
10395
|
+
fieldId: "source",
|
|
10396
|
+
fieldType: "enum",
|
|
10397
|
+
filterable: true,
|
|
10398
|
+
enumValues: ["thought", "web", "meeting", "book", "article", "other"],
|
|
10399
|
+
description: "Where it came from.",
|
|
10400
|
+
renderHints: { label: "Source", widget: "select", order: 4, section: "Organize" }
|
|
10401
|
+
},
|
|
10402
|
+
{
|
|
10403
|
+
fieldId: "status",
|
|
10404
|
+
fieldType: "enum",
|
|
10405
|
+
filterable: true,
|
|
10406
|
+
enumValues: ["active", "archived"],
|
|
10407
|
+
description: "Archive instead of delete, so nothing is ever lost.",
|
|
10408
|
+
renderHints: { label: "Status", widget: "select", order: 5, section: "Organize" }
|
|
10409
|
+
},
|
|
10410
|
+
{
|
|
10411
|
+
fieldId: "capturedAt",
|
|
10412
|
+
fieldType: "date",
|
|
10413
|
+
description: 'ISO-8601 capture date. Range-queryable \u2014 "notes from last week".',
|
|
10414
|
+
renderHints: { label: "Captured", widget: "date", order: 6, section: "Organize" }
|
|
10415
|
+
}
|
|
10416
|
+
// externalId is the record's FIRST-CLASS identifier (the dedup/upsert key + the
|
|
10417
|
+
// value a `reference` resolves against) — not a payload field; the loader sends
|
|
10418
|
+
// it top-level on the RecordRequest.
|
|
10419
|
+
],
|
|
10420
|
+
// Lookup shapes are MIGRATION-LOCKED once the schema is live (a field cannot
|
|
10421
|
+
// flip equality↔range later), so each is chosen on purpose:
|
|
10422
|
+
// • `source` — EQUALITY, sorted by `lastUpdated`: enumerate notes from one
|
|
10423
|
+
// source, most-recently-touched first. `lastUpdated` is always present, so
|
|
10424
|
+
// the sort never drops a note (sorting an equality lookup by an OPTIONAL
|
|
10425
|
+
// field would silently exclude rows that lack it).
|
|
10426
|
+
// • `status` — EQUALITY: "show archived notes".
|
|
10427
|
+
// • `capturedAt` — RANGE: ordered `from`/`to`/`prefix` ("captured in 2026-06").
|
|
10428
|
+
// `externalId` has a built-in first-class finder — look it up directly, never
|
|
10429
|
+
// redeclare it here. Equality lookups use the 7 fast index slots (2 used here);
|
|
10430
|
+
// range lookups use a relationship row, billed at the range rate.
|
|
10431
|
+
lookupFields: [
|
|
10432
|
+
{ fieldName: "source", sortBy: "lastUpdated" },
|
|
10433
|
+
"status",
|
|
10434
|
+
{ fieldName: "capturedAt", rangeEnabled: true }
|
|
10435
|
+
]
|
|
10436
|
+
}
|
|
10437
|
+
],
|
|
10438
|
+
// Least-privilege profile — r/c/u + search + schema discovery + inference:r
|
|
10439
|
+
// (so the agent can `rag_ask` a grounded, cited question over your notes). No
|
|
10440
|
+
// records:d: notes are archived (status → 'archived'), never deleted.
|
|
10441
|
+
accessProfile: {
|
|
10442
|
+
allowedActions: ["records:r", "records:c", "records:u", "search:r", "schemas:r", "inference:r"]
|
|
10443
|
+
},
|
|
10444
|
+
servicePrincipal: {
|
|
10445
|
+
externalId: "second-brain",
|
|
10446
|
+
displayName: "Second Brain"
|
|
10447
|
+
},
|
|
10448
|
+
seed: [
|
|
10449
|
+
{
|
|
10450
|
+
surface: "record",
|
|
10451
|
+
typeName: "note",
|
|
10452
|
+
externalId: "seed-welcome",
|
|
10453
|
+
fields: {
|
|
10454
|
+
title: "Welcome to your Second Brain",
|
|
10455
|
+
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.',
|
|
10456
|
+
tags: ["getting-started"],
|
|
10457
|
+
source: "thought",
|
|
10458
|
+
status: "active",
|
|
10459
|
+
capturedAt: "2026-06-14"
|
|
10460
|
+
}
|
|
10461
|
+
}
|
|
10462
|
+
]
|
|
10463
|
+
};
|
|
10464
|
+
var second_brain_default = secondBrain;
|
|
10465
|
+
var clinicalIntake = {
|
|
10466
|
+
name: "clinical-intake",
|
|
10467
|
+
version: "1.0.0",
|
|
10468
|
+
description: "Behavioral-health intake with PHI fields \u2014 demonstrates redact-at-write, audit history, blind-index lookup, and search exclusion. Synthetic data only.",
|
|
10469
|
+
contextId: "clinical-intake",
|
|
10470
|
+
contextName: "Clinical Intake",
|
|
10471
|
+
schemas: [
|
|
10472
|
+
{
|
|
10473
|
+
typeName: "intake",
|
|
10474
|
+
displayName: "Intake",
|
|
10475
|
+
indexMode: "HYBRID",
|
|
10476
|
+
capabilities: { auditHistory: true },
|
|
10477
|
+
// self-documenting compliance posture
|
|
10478
|
+
fields: [
|
|
10479
|
+
// --- Non-sensitive, searchable/filterable working fields ---
|
|
10480
|
+
{
|
|
10481
|
+
fieldId: "caseId",
|
|
10482
|
+
fieldType: "string",
|
|
10483
|
+
required: true,
|
|
10484
|
+
description: "Caller-stable intake id; the dedup/lookup key.",
|
|
10485
|
+
validation: { minLength: 1, maxLength: 64 },
|
|
10486
|
+
renderHints: { label: "Case ID", widget: "text", order: 1, section: "Intake", displayField: true }
|
|
10487
|
+
},
|
|
10488
|
+
{
|
|
10489
|
+
fieldId: "presentingConcern",
|
|
10490
|
+
fieldType: "string",
|
|
10491
|
+
searchable: true,
|
|
10492
|
+
description: "Non-PHI summary of the presenting concern \u2014 safe to index + search.",
|
|
10493
|
+
validation: { maxLength: 2e3 },
|
|
10494
|
+
renderHints: { label: "Presenting concern", widget: "textarea", order: 2, section: "Intake" }
|
|
10495
|
+
},
|
|
10496
|
+
{
|
|
10497
|
+
fieldId: "program",
|
|
10498
|
+
fieldType: "string",
|
|
9692
10499
|
filterable: true,
|
|
9693
10500
|
description: "Program / service line the intake is routed to.",
|
|
9694
10501
|
renderHints: { label: "Program", widget: "text", order: 3, section: "Intake" }
|
|
@@ -9772,6 +10579,7 @@ var clinicalIntake = {
|
|
|
9772
10579
|
},
|
|
9773
10580
|
seed: [
|
|
9774
10581
|
{
|
|
10582
|
+
surface: "record",
|
|
9775
10583
|
typeName: "intake",
|
|
9776
10584
|
externalId: "seed-synthetic-intake",
|
|
9777
10585
|
fields: {
|
|
@@ -9793,6 +10601,7 @@ var clinical_intake_default = clinicalIntake;
|
|
|
9793
10601
|
var BUNDLED_BLUEPRINTS = [
|
|
9794
10602
|
task_management_default,
|
|
9795
10603
|
coding_agent_memory_default,
|
|
10604
|
+
agentic_sdlc_default,
|
|
9796
10605
|
second_brain_default,
|
|
9797
10606
|
clinical_intake_default
|
|
9798
10607
|
];
|
|
@@ -10230,7 +11039,8 @@ async function resolveCognitoToken(env, explicit) {
|
|
|
10230
11039
|
async function resolveAuthContext(opts) {
|
|
10231
11040
|
const baseUrl = envToBaseUrl(opts.env, opts.baseUrl ?? process.env.VECTROS_API_BASE_URL);
|
|
10232
11041
|
const cognitoToken = await resolveCognitoToken(opts.env, opts.token);
|
|
10233
|
-
const
|
|
11042
|
+
const tenant = opts.tenant ?? "live";
|
|
11043
|
+
const bridge = opts.provisioning ? await mintBridgeToken({ cognitoToken, apiBaseUrl: baseUrl, tenant }) : await mintContextToken({ cognitoToken, apiBaseUrl: baseUrl, contextId: opts.context ?? "default", tenant });
|
|
10234
11044
|
const client = new VectrosClient({ token: bridge.token, environment: baseUrl });
|
|
10235
11045
|
const identity = await resolveIdentity({ log: opts.log, apiKey: bridge.token, environment: baseUrl });
|
|
10236
11046
|
return {
|
|
@@ -10240,7 +11050,8 @@ async function resolveAuthContext(opts) {
|
|
|
10240
11050
|
baseUrl,
|
|
10241
11051
|
env: opts.env,
|
|
10242
11052
|
identity,
|
|
10243
|
-
tenantId: typeof identity.tenantId === "string" ? identity.tenantId : void 0
|
|
11053
|
+
tenantId: typeof identity.tenantId === "string" ? identity.tenantId : void 0,
|
|
11054
|
+
tenant
|
|
10244
11055
|
};
|
|
10245
11056
|
}
|
|
10246
11057
|
|
|
@@ -10564,39 +11375,6 @@ function assertNoUnresolvedIdentities(blueprint) {
|
|
|
10564
11375
|
);
|
|
10565
11376
|
}
|
|
10566
11377
|
}
|
|
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
11378
|
async function runLoader(client, input) {
|
|
10601
11379
|
const { blueprint, tenantId, machineId, rotate = false, skipSeed = false, vectrosDir, log } = input;
|
|
10602
11380
|
assertScopeGate(blueprint.accessProfile.allowedActions);
|
|
@@ -10604,14 +11382,16 @@ async function runLoader(client, input) {
|
|
|
10604
11382
|
for (const clause of clauses) assertScopeGate(clause.allowedActions);
|
|
10605
11383
|
}
|
|
10606
11384
|
assertNoUnresolvedIdentities(blueprint);
|
|
10607
|
-
await client.auth.createAppContext({ contextId: blueprint.contextId, name: contextNameOf(blueprint) });
|
|
11385
|
+
await client.auth.createAppContext({ body: { contextId: blueprint.contextId, name: contextNameOf(blueprint) } });
|
|
10608
11386
|
log.debug({ component: "loader", contextId: blueprint.contextId }, "app-context ensured");
|
|
10609
11387
|
const user = await client.identity.createUser({
|
|
10610
|
-
|
|
10611
|
-
|
|
10612
|
-
|
|
10613
|
-
|
|
10614
|
-
|
|
11388
|
+
body: {
|
|
11389
|
+
externalId: blueprint.servicePrincipal.externalId,
|
|
11390
|
+
type: "SERVICE",
|
|
11391
|
+
// SDK 0.18+ (identity-access rework): UserRequest's free-form attribute bag
|
|
11392
|
+
// was renamed `metadata` → `payload`. Same semantics (arbitrary key/value).
|
|
11393
|
+
payload: { displayName: blueprint.servicePrincipal.displayName }
|
|
11394
|
+
}
|
|
10615
11395
|
});
|
|
10616
11396
|
const userId = user.id;
|
|
10617
11397
|
if (!userId) {
|
|
@@ -10646,23 +11426,9 @@ async function runLoader(client, input) {
|
|
|
10646
11426
|
orgId: s.orgId,
|
|
10647
11427
|
clientId: s.clientId
|
|
10648
11428
|
};
|
|
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
|
-
}
|
|
11429
|
+
const resp = await ctxClient.schemas.createSchema({ upsert: true, body: schemaBody });
|
|
10664
11430
|
if (resp.id) schemaIds[s.typeName] = resp.id;
|
|
10665
|
-
log.debug({ component: "loader", typeName: s.typeName, schemaId: resp.id }, "schema ensured");
|
|
11431
|
+
log.debug({ component: "loader", typeName: s.typeName, schemaId: resp.id }, "schema ensured (upsert)");
|
|
10666
11432
|
}
|
|
10667
11433
|
const profileBody = {
|
|
10668
11434
|
principalId: `usr_${userId}`,
|
|
@@ -10738,25 +11504,40 @@ async function runLoader(client, input) {
|
|
|
10738
11504
|
log.debug({ component: "loader", keyName, keyAction }, "scoped key resolved");
|
|
10739
11505
|
let seededCount = 0;
|
|
10740
11506
|
const seededRecordIds = [];
|
|
11507
|
+
const seededDocumentIds = [];
|
|
10741
11508
|
for (const rec of skipSeed ? [] : blueprint.seed ?? []) {
|
|
10742
11509
|
const schemaId = schemaIds[rec.typeName];
|
|
10743
11510
|
if (!schemaId) {
|
|
10744
11511
|
log.warn({ component: "loader", typeName: rec.typeName }, "seed skipped \u2014 no schemaId for typeName");
|
|
10745
11512
|
continue;
|
|
10746
11513
|
}
|
|
10747
|
-
|
|
10748
|
-
|
|
10749
|
-
|
|
10750
|
-
|
|
10751
|
-
|
|
10752
|
-
|
|
10753
|
-
|
|
10754
|
-
|
|
10755
|
-
|
|
10756
|
-
|
|
10757
|
-
|
|
10758
|
-
|
|
10759
|
-
|
|
11514
|
+
if (rec.surface === "document") {
|
|
11515
|
+
const created = await ctxClient.documents.ingestDocument({
|
|
11516
|
+
body: {
|
|
11517
|
+
title: rec.title,
|
|
11518
|
+
text: rec.text,
|
|
11519
|
+
schemaId,
|
|
11520
|
+
externalId: rec.externalId,
|
|
11521
|
+
// Optional structured payload (the document's `fields`); omitted when absent.
|
|
11522
|
+
payload: rec.fields,
|
|
11523
|
+
// Retain the raw text so a seeded document is retrievable via
|
|
11524
|
+
// GET /v1/documents/{id}/text (mirrors the MCP document_ingest default) —
|
|
11525
|
+
// a seed's text IS its content, worth keeping for the getting-started graph.
|
|
11526
|
+
storeText: true
|
|
11527
|
+
}
|
|
11528
|
+
});
|
|
11529
|
+
if (created.id) seededDocumentIds.push(created.id);
|
|
11530
|
+
} else {
|
|
11531
|
+
const created = await ctxClient.records.createRecord({
|
|
11532
|
+
body: {
|
|
11533
|
+
typeName: rec.typeName,
|
|
11534
|
+
schemaId,
|
|
11535
|
+
externalId: rec.externalId,
|
|
11536
|
+
payload: rec.fields
|
|
11537
|
+
}
|
|
11538
|
+
});
|
|
11539
|
+
if (created.id) seededRecordIds.push(created.id);
|
|
11540
|
+
}
|
|
10760
11541
|
seededCount++;
|
|
10761
11542
|
}
|
|
10762
11543
|
return {
|
|
@@ -10769,7 +11550,8 @@ async function runLoader(client, input) {
|
|
|
10769
11550
|
schemaIds,
|
|
10770
11551
|
roleIds,
|
|
10771
11552
|
seededCount,
|
|
10772
|
-
seededRecordIds
|
|
11553
|
+
seededRecordIds,
|
|
11554
|
+
seededDocumentIds
|
|
10773
11555
|
};
|
|
10774
11556
|
}
|
|
10775
11557
|
function buildPlan(blueprint, opts = {}) {
|
|
@@ -10804,9 +11586,14 @@ function buildPlan(blueprint, opts = {}) {
|
|
|
10804
11586
|
});
|
|
10805
11587
|
}
|
|
10806
11588
|
steps.push({ kind: "key", summary: `key ${blueprint.name}-<machine> (mint 1 scoped ssk_*)` });
|
|
10807
|
-
const
|
|
10808
|
-
if (
|
|
10809
|
-
|
|
11589
|
+
const seeds = blueprint.seed ?? [];
|
|
11590
|
+
if (seeds.length > 0 && !opts.noSeed) {
|
|
11591
|
+
const docs = seeds.filter((s) => s.surface === "document").length;
|
|
11592
|
+
const recs = seeds.length - docs;
|
|
11593
|
+
const parts = [];
|
|
11594
|
+
if (recs > 0) parts.push(`${recs} record${recs === 1 ? "" : "s"}`);
|
|
11595
|
+
if (docs > 0) parts.push(`${docs} document${docs === 1 ? "" : "s"}`);
|
|
11596
|
+
steps.push({ kind: "seed", summary: `seed ${parts.join(" + ")}` });
|
|
10810
11597
|
}
|
|
10811
11598
|
return steps;
|
|
10812
11599
|
}
|
|
@@ -10932,6 +11719,7 @@ function parseBootstrapArgs(argv) {
|
|
|
10932
11719
|
blankSlate: false,
|
|
10933
11720
|
noSeed: false,
|
|
10934
11721
|
client: "desktop",
|
|
11722
|
+
tenant: "live",
|
|
10935
11723
|
help: false
|
|
10936
11724
|
};
|
|
10937
11725
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -10982,6 +11770,17 @@ function parseBootstrapArgs(argv) {
|
|
|
10982
11770
|
case "--no-seed":
|
|
10983
11771
|
args.noSeed = true;
|
|
10984
11772
|
break;
|
|
11773
|
+
case "--tenant": {
|
|
11774
|
+
const v = argv[++i];
|
|
11775
|
+
if (v !== "test" && v !== "live") {
|
|
11776
|
+
throw new Error(`--tenant must be 'test' or 'live' (got '${v ?? ""}')`);
|
|
11777
|
+
}
|
|
11778
|
+
args.tenant = v;
|
|
11779
|
+
break;
|
|
11780
|
+
}
|
|
11781
|
+
case "--test":
|
|
11782
|
+
args.tenant = "test";
|
|
11783
|
+
break;
|
|
10985
11784
|
case "--help":
|
|
10986
11785
|
case "-h":
|
|
10987
11786
|
args.help = true;
|
|
@@ -11010,7 +11809,8 @@ var BOOTSTRAP_HELP = `vectros bootstrap \u2014 provision a least-privilege MCP c
|
|
|
11010
11809
|
|
|
11011
11810
|
Usage:
|
|
11012
11811
|
vectros bootstrap [--blueprint <name> | --blank] [--no-seed] [--env staging|production]
|
|
11013
|
-
[--
|
|
11812
|
+
[--tenant test|live | --test] [--client desktop|code]
|
|
11813
|
+
[--yes] [--print] [--rotate]
|
|
11014
11814
|
[--token <t> | env VECTROS_BOOTSTRAP_TOKEN]
|
|
11015
11815
|
[--base-url <url>] [--use-scoped-token]
|
|
11016
11816
|
|
|
@@ -11024,6 +11824,12 @@ Options:
|
|
|
11024
11824
|
(a blueprint's seeds are illustrative, not real data). Unlike
|
|
11025
11825
|
--blank, the schemas are still created.
|
|
11026
11826
|
--env staging|production Target environment (default: production).
|
|
11827
|
+
--tenant test|live Which tenant the minted key scopes to (default: live).
|
|
11828
|
+
'test' provisions into your isolated test tenant \u2014 a
|
|
11829
|
+
dry-run before pointing real data at the same blueprint.
|
|
11830
|
+
The tenant rides the key (ssk_test_* vs ssk_live_*), so
|
|
11831
|
+
the MCP/client config is otherwise identical.
|
|
11832
|
+
--test Shorthand for --tenant test.
|
|
11027
11833
|
--base-url <url> Override the API base URL (beats --env + VECTROS_API_BASE_URL).
|
|
11028
11834
|
--client <desktop|code> MCP host to configure (default: desktop). 'desktop' merges
|
|
11029
11835
|
claude_desktop_config.json (also works for Cursor/Cline);
|
|
@@ -11092,6 +11898,21 @@ async function resolveBridgeCognitoToken(args, prompt, isTty, resolveCognito = r
|
|
|
11092
11898
|
if (!pasted) throw new Error("No token entered.");
|
|
11093
11899
|
return pasted;
|
|
11094
11900
|
}
|
|
11901
|
+
function buildRemintForContext(opts) {
|
|
11902
|
+
const mintContext = opts.mintContext ?? mintContextToken;
|
|
11903
|
+
return async (contextId) => {
|
|
11904
|
+
const ctxToken = await mintContext({
|
|
11905
|
+
cognitoToken: opts.cognitoToken,
|
|
11906
|
+
apiBaseUrl: opts.apiBaseUrl,
|
|
11907
|
+
contextId,
|
|
11908
|
+
tenant: opts.tenant
|
|
11909
|
+
});
|
|
11910
|
+
return new VectrosClient({
|
|
11911
|
+
token: ctxToken.token,
|
|
11912
|
+
environment: opts.apiBaseUrl
|
|
11913
|
+
});
|
|
11914
|
+
};
|
|
11915
|
+
}
|
|
11095
11916
|
async function runBootstrapCli(argv, deps = {}) {
|
|
11096
11917
|
const log = deps.log ?? createLogger2();
|
|
11097
11918
|
let args;
|
|
@@ -11173,12 +11994,17 @@ ${renderPlan(blueprint, planSteps)}
|
|
|
11173
11994
|
isTty,
|
|
11174
11995
|
deps.resolveCognitoToken
|
|
11175
11996
|
);
|
|
11176
|
-
const
|
|
11997
|
+
const mintBridge = deps.mintBridgeToken ?? mintBridgeToken;
|
|
11998
|
+
const mintContext = deps.mintContextToken ?? mintContextToken;
|
|
11999
|
+
const bridge = await mintBridge({
|
|
11177
12000
|
cognitoToken,
|
|
11178
12001
|
apiBaseUrl: baseUrl,
|
|
11179
|
-
useScopedToken: args.useScopedToken
|
|
12002
|
+
useScopedToken: args.useScopedToken,
|
|
12003
|
+
// Tenant rides the key (ssk_test_* vs ssk_live_*), not the base URL.
|
|
12004
|
+
tenant: args.tenant
|
|
11180
12005
|
});
|
|
11181
|
-
process.stdout.write(
|
|
12006
|
+
process.stdout.write(` \u2714 Bootstrap token minted (5-min TTL, ${args.tenant} tenant)
|
|
12007
|
+
`);
|
|
11182
12008
|
const client = new VectrosClient({ token: bridge.token, environment: baseUrl });
|
|
11183
12009
|
const identity = await resolveIdentity({ log, apiKey: bridge.token, environment: baseUrl });
|
|
11184
12010
|
const tenantId = identity.tenantId;
|
|
@@ -11194,17 +12020,16 @@ ${renderPlan(blueprint, planSteps)}
|
|
|
11194
12020
|
machineId,
|
|
11195
12021
|
rotate: args.rotate,
|
|
11196
12022
|
skipSeed: noSeed,
|
|
11197
|
-
remintForContext:
|
|
11198
|
-
|
|
11199
|
-
|
|
11200
|
-
|
|
11201
|
-
|
|
11202
|
-
|
|
11203
|
-
},
|
|
12023
|
+
remintForContext: buildRemintForContext({
|
|
12024
|
+
cognitoToken,
|
|
12025
|
+
apiBaseUrl: baseUrl,
|
|
12026
|
+
tenant: args.tenant,
|
|
12027
|
+
mintContext
|
|
12028
|
+
}),
|
|
11204
12029
|
log
|
|
11205
12030
|
});
|
|
11206
12031
|
process.stdout.write(
|
|
11207
|
-
` \u2714 AccessProfile + key (${result.keyAction}) \u2192 ${result.keyName}
|
|
12032
|
+
` \u2714 AccessProfile + ${args.tenant} key (${result.keyAction}) \u2192 ${result.keyName}
|
|
11208
12033
|
` + seedSummaryLine({
|
|
11209
12034
|
noSeed,
|
|
11210
12035
|
declinedInteractively: seedDeclinedInteractively,
|
|
@@ -11420,7 +12245,8 @@ servicePrincipal:
|
|
|
11420
12245
|
displayName: ${name}
|
|
11421
12246
|
|
|
11422
12247
|
seed:
|
|
11423
|
-
-
|
|
12248
|
+
- surface: record # 'record' (createRecord) or 'document' (text-ingest: needs title + text)
|
|
12249
|
+
typeName: thing
|
|
11424
12250
|
externalId: seed-welcome # stable \u2192 idempotent; re-apply converges
|
|
11425
12251
|
fields: { title: "Hello from ${name}" }
|
|
11426
12252
|
`;
|
|
@@ -11768,6 +12594,7 @@ async function withAuth(opts, deps, body) {
|
|
|
11768
12594
|
token: opts.token,
|
|
11769
12595
|
context: opts.context,
|
|
11770
12596
|
provisioning: opts.provisioning,
|
|
12597
|
+
tenant: opts.tenant,
|
|
11771
12598
|
useScopedToken: opts.useScopedToken,
|
|
11772
12599
|
log
|
|
11773
12600
|
});
|
|
@@ -12044,19 +12871,23 @@ async function createIdentity(client, opts, payload) {
|
|
|
12044
12871
|
switch (opts.type) {
|
|
12045
12872
|
case "user":
|
|
12046
12873
|
return client.identity.createUser({
|
|
12047
|
-
|
|
12048
|
-
|
|
12049
|
-
|
|
12050
|
-
|
|
12874
|
+
body: {
|
|
12875
|
+
externalId,
|
|
12876
|
+
email: opts.email,
|
|
12877
|
+
type: opts.service ? "SERVICE" : "HUMAN",
|
|
12878
|
+
payload
|
|
12879
|
+
}
|
|
12051
12880
|
});
|
|
12052
12881
|
case "org":
|
|
12053
|
-
return client.identity.createOrg({ externalId, name: opts.name, payload });
|
|
12882
|
+
return client.identity.createOrg({ body: { externalId, name: opts.name, payload } });
|
|
12054
12883
|
case "client":
|
|
12055
12884
|
return client.identity.createClient({
|
|
12056
|
-
|
|
12057
|
-
|
|
12058
|
-
|
|
12059
|
-
|
|
12885
|
+
body: {
|
|
12886
|
+
externalId,
|
|
12887
|
+
name: opts.name,
|
|
12888
|
+
orgId: opts.org,
|
|
12889
|
+
payload
|
|
12890
|
+
}
|
|
12060
12891
|
});
|
|
12061
12892
|
}
|
|
12062
12893
|
}
|
|
@@ -12497,8 +13328,10 @@ async function runContextCreate(opts, deps = {}) {
|
|
|
12497
13328
|
const client = auth.client;
|
|
12498
13329
|
try {
|
|
12499
13330
|
const ctx = await client.auth.createAppContext({
|
|
12500
|
-
|
|
12501
|
-
|
|
13331
|
+
body: {
|
|
13332
|
+
contextId: opts.contextId,
|
|
13333
|
+
name: opts.name ?? opts.contextId
|
|
13334
|
+
}
|
|
12502
13335
|
});
|
|
12503
13336
|
if (opts.json) {
|
|
12504
13337
|
process.stdout.write(`${formatJson(ctx)}
|
|
@@ -12809,23 +13642,22 @@ async function runAssertions(client, deps, blueprint, tenantId, result) {
|
|
|
12809
13642
|
}
|
|
12810
13643
|
}
|
|
12811
13644
|
for (const rec of blueprint.seed ?? []) {
|
|
13645
|
+
const isDoc = rec.surface === "document";
|
|
13646
|
+
const op = isDoc ? "lookupDocuments" : "lookupRecords";
|
|
13647
|
+
const noun = isDoc ? "document" : "record";
|
|
12812
13648
|
try {
|
|
12813
|
-
const page = await client.records.lookupRecords({
|
|
12814
|
-
type: rec.typeName,
|
|
12815
|
-
field: "externalId",
|
|
12816
|
-
value: rec.externalId
|
|
12817
|
-
});
|
|
13649
|
+
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
13650
|
const ok = (page.data ?? []).length >= 1;
|
|
12819
13651
|
assertions.push({
|
|
12820
13652
|
name: `seed-externalId:${rec.externalId}`,
|
|
12821
13653
|
ok,
|
|
12822
|
-
detail: ok ? `'${rec.typeName}' seed resolves by externalId '${rec.externalId}'` : `'${rec.typeName}' seed externalId '${rec.externalId}' resolved 0
|
|
13654
|
+
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
13655
|
});
|
|
12824
13656
|
} catch (e) {
|
|
12825
13657
|
assertions.push({
|
|
12826
13658
|
name: `seed-externalId:${rec.externalId}`,
|
|
12827
13659
|
ok: false,
|
|
12828
|
-
detail:
|
|
13660
|
+
detail: `${op}(externalId='${rec.externalId}') failed: ${errMsg(e)}`
|
|
12829
13661
|
});
|
|
12830
13662
|
}
|
|
12831
13663
|
}
|
|
@@ -12883,6 +13715,14 @@ async function runTeardown(client, blueprint, machineId, snapshot, result) {
|
|
|
12883
13715
|
steps.push({ step: `delete record ${id}`, ok: false, error: errMsg(e) });
|
|
12884
13716
|
}
|
|
12885
13717
|
}
|
|
13718
|
+
for (const id of result.seededDocumentIds) {
|
|
13719
|
+
try {
|
|
13720
|
+
await client.documents.deleteDocument({ id });
|
|
13721
|
+
steps.push({ step: `delete document ${id}`, ok: true });
|
|
13722
|
+
} catch (e) {
|
|
13723
|
+
steps.push({ step: `delete document ${id}`, ok: false, error: errMsg(e) });
|
|
13724
|
+
}
|
|
13725
|
+
}
|
|
12886
13726
|
const principalId = `usr_${result.servicePrincipalUserId}`;
|
|
12887
13727
|
if (snapshot.failed) {
|
|
12888
13728
|
steps.push({ step: `delete access profile ${principalId}`, ok: true, skipped: true, error: "skipped \u2014 pre-apply snapshot failed; cannot prove ownership" });
|
|
@@ -13017,7 +13857,7 @@ function renderReport(blueprint, env, report) {
|
|
|
13017
13857
|
if (report.applied) {
|
|
13018
13858
|
const a = report.applied;
|
|
13019
13859
|
lines.push(
|
|
13020
|
-
` \u2714 applied \u2014 key ${a.keyName} (${a.keyAction}), ${Object.keys(a.schemaIds).length} schema(s)` + (a.seededCount ? `, ${a.seededCount} seed
|
|
13860
|
+
` \u2714 applied \u2014 key ${a.keyName} (${a.keyAction}), ${Object.keys(a.schemaIds).length} schema(s)` + (a.seededCount ? `, ${a.seededCount} seed(s)` : "")
|
|
13021
13861
|
);
|
|
13022
13862
|
} else {
|
|
13023
13863
|
lines.push(` \u2716 apply failed \u2014 ${report.error ?? "unknown error"}`);
|
|
@@ -13080,7 +13920,10 @@ async function runBlueprintTest(fileOrName, opts, deps = {}) {
|
|
|
13080
13920
|
const ctxToken = await mintContextToken({
|
|
13081
13921
|
cognitoToken: auth.cognitoToken,
|
|
13082
13922
|
apiBaseUrl: auth.baseUrl,
|
|
13083
|
-
contextId
|
|
13923
|
+
contextId,
|
|
13924
|
+
// Same tenant as the bootstrap token (auth.tenant) — the in-context
|
|
13925
|
+
// load must land in the tenant the run targets.
|
|
13926
|
+
tenant: auth.tenant
|
|
13084
13927
|
});
|
|
13085
13928
|
return new VectrosClient({
|
|
13086
13929
|
token: ctxToken.token,
|
|
@@ -13118,9 +13961,9 @@ async function runBlueprintTest(fileOrName, opts, deps = {}) {
|
|
|
13118
13961
|
|
|
13119
13962
|
// src/build-info.ts
|
|
13120
13963
|
var BUILD_INFO = {
|
|
13121
|
-
cli: true ? "0.6.
|
|
13122
|
-
sdk: true ? "0.
|
|
13123
|
-
blueprints: true ? "0.
|
|
13964
|
+
cli: true ? "0.6.4" : "dev",
|
|
13965
|
+
sdk: true ? "0.31.0" : "dev",
|
|
13966
|
+
blueprints: true ? "0.6.2" : "dev"
|
|
13124
13967
|
};
|
|
13125
13968
|
function formatBuildInfo() {
|
|
13126
13969
|
return `vectros ${BUILD_INFO.cli} (sdk ${BUILD_INFO.sdk}, blueprints ${BUILD_INFO.blueprints})`;
|
|
@@ -13168,10 +14011,21 @@ function buildProgram(deps = {}) {
|
|
|
13168
14011
|
addCommonAuthOptions(whoami).action(async (flags) => {
|
|
13169
14012
|
process.exit(await runWhoami(toCommonOptions(flags), deps));
|
|
13170
14013
|
});
|
|
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");
|
|
14014
|
+
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
14015
|
addCommonAuthOptions(blueprintTest).action(
|
|
13173
14016
|
async (file, flags) => {
|
|
13174
|
-
|
|
14017
|
+
if (flags.tenant !== void 0 && flags.tenant !== "test" && flags.tenant !== "live") {
|
|
14018
|
+
process.stderr.write(` \u2716 --tenant must be 'test' or 'live' (got '${flags.tenant}')
|
|
14019
|
+
`);
|
|
14020
|
+
process.exit(2);
|
|
14021
|
+
}
|
|
14022
|
+
process.exit(
|
|
14023
|
+
await runBlueprintTest(
|
|
14024
|
+
file,
|
|
14025
|
+
{ ...toCommonOptions(flags), keep: Boolean(flags.keep), tenant: flags.tenant },
|
|
14026
|
+
deps
|
|
14027
|
+
)
|
|
14028
|
+
);
|
|
13175
14029
|
}
|
|
13176
14030
|
);
|
|
13177
14031
|
registerKeyCommands(program, deps);
|