@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.mjs
CHANGED
|
@@ -2838,7 +2838,7 @@ var AuthClient = class {
|
|
|
2838
2838
|
);
|
|
2839
2839
|
}
|
|
2840
2840
|
/**
|
|
2841
|
-
* 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.
|
|
2841
|
+
* 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.
|
|
2842
2842
|
*
|
|
2843
2843
|
* @param {Vectros.CreateAccessProfileRequest} request
|
|
2844
2844
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -2860,7 +2860,10 @@ var AuthClient = class {
|
|
|
2860
2860
|
return HttpResponsePromise.fromPromise(this.__createAccessProfile(request, requestOptions));
|
|
2861
2861
|
}
|
|
2862
2862
|
async __createAccessProfile(request, requestOptions) {
|
|
2863
|
-
const { contextId, body: _body } = request;
|
|
2863
|
+
const { contextId, upsert, body: _body } = request;
|
|
2864
|
+
const _queryParams = {
|
|
2865
|
+
upsert
|
|
2866
|
+
};
|
|
2864
2867
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
2865
2868
|
const _headers = mergeHeaders(
|
|
2866
2869
|
_authRequest.headers,
|
|
@@ -2875,7 +2878,7 @@ var AuthClient = class {
|
|
|
2875
2878
|
method: "POST",
|
|
2876
2879
|
headers: _headers,
|
|
2877
2880
|
contentType: "application/json",
|
|
2878
|
-
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
2881
|
+
queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
|
|
2879
2882
|
requestType: "json",
|
|
2880
2883
|
body: _body,
|
|
2881
2884
|
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
@@ -2970,9 +2973,9 @@ var AuthClient = class {
|
|
|
2970
2973
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/app-contexts");
|
|
2971
2974
|
}
|
|
2972
2975
|
/**
|
|
2973
|
-
* 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.
|
|
2976
|
+
* 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.
|
|
2974
2977
|
*
|
|
2975
|
-
* @param {Vectros.
|
|
2978
|
+
* @param {Vectros.CreateAppContextRequest} request
|
|
2976
2979
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
2977
2980
|
*
|
|
2978
2981
|
* @throws {@link Vectros.BadRequestError}
|
|
@@ -2981,14 +2984,20 @@ var AuthClient = class {
|
|
|
2981
2984
|
*
|
|
2982
2985
|
* @example
|
|
2983
2986
|
* await client.auth.createAppContext({
|
|
2984
|
-
*
|
|
2985
|
-
*
|
|
2987
|
+
* body: {
|
|
2988
|
+
* contextId: "myapp",
|
|
2989
|
+
* name: "My Internal App"
|
|
2990
|
+
* }
|
|
2986
2991
|
* })
|
|
2987
2992
|
*/
|
|
2988
2993
|
createAppContext(request, requestOptions) {
|
|
2989
2994
|
return HttpResponsePromise.fromPromise(this.__createAppContext(request, requestOptions));
|
|
2990
2995
|
}
|
|
2991
2996
|
async __createAppContext(request, requestOptions) {
|
|
2997
|
+
const { upsert, body: _body } = request;
|
|
2998
|
+
const _queryParams = {
|
|
2999
|
+
upsert
|
|
3000
|
+
};
|
|
2992
3001
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
2993
3002
|
const _headers = mergeHeaders(
|
|
2994
3003
|
_authRequest.headers,
|
|
@@ -3003,9 +3012,9 @@ var AuthClient = class {
|
|
|
3003
3012
|
method: "POST",
|
|
3004
3013
|
headers: _headers,
|
|
3005
3014
|
contentType: "application/json",
|
|
3006
|
-
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
3015
|
+
queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
|
|
3007
3016
|
requestType: "json",
|
|
3008
|
-
body:
|
|
3017
|
+
body: _body,
|
|
3009
3018
|
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
3010
3019
|
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
3011
3020
|
abortSignal: requestOptions?.abortSignal,
|
|
@@ -3101,7 +3110,7 @@ var AuthClient = class {
|
|
|
3101
3110
|
);
|
|
3102
3111
|
}
|
|
3103
3112
|
/**
|
|
3104
|
-
* 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.
|
|
3113
|
+
* 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.
|
|
3105
3114
|
*
|
|
3106
3115
|
* @param {Vectros.CreateRoleRequest} request
|
|
3107
3116
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -3127,7 +3136,10 @@ var AuthClient = class {
|
|
|
3127
3136
|
return HttpResponsePromise.fromPromise(this.__createRole(request, requestOptions));
|
|
3128
3137
|
}
|
|
3129
3138
|
async __createRole(request, requestOptions) {
|
|
3130
|
-
const { contextId, body: _body } = request;
|
|
3139
|
+
const { contextId, upsert, body: _body } = request;
|
|
3140
|
+
const _queryParams = {
|
|
3141
|
+
upsert
|
|
3142
|
+
};
|
|
3131
3143
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
3132
3144
|
const _headers = mergeHeaders(
|
|
3133
3145
|
_authRequest.headers,
|
|
@@ -3142,7 +3154,7 @@ var AuthClient = class {
|
|
|
3142
3154
|
method: "POST",
|
|
3143
3155
|
headers: _headers,
|
|
3144
3156
|
contentType: "application/json",
|
|
3145
|
-
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
3157
|
+
queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
|
|
3146
3158
|
requestType: "json",
|
|
3147
3159
|
body: _body,
|
|
3148
3160
|
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
@@ -4626,23 +4638,30 @@ var DocumentsClient = class {
|
|
|
4626
4638
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/documents");
|
|
4627
4639
|
}
|
|
4628
4640
|
/**
|
|
4629
|
-
* Creates a document from a raw text string and queues it for asynchronous indexing so it becomes searchable. Requires the `documents:c` scope.
|
|
4641
|
+
* 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.
|
|
4630
4642
|
*
|
|
4631
|
-
* @param {Vectros.
|
|
4643
|
+
* @param {Vectros.IngestDocumentRequest} request
|
|
4632
4644
|
* @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
4633
4645
|
*
|
|
4634
4646
|
* @throws {@link Vectros.BadRequestError}
|
|
4647
|
+
* @throws {@link Vectros.ForbiddenError}
|
|
4635
4648
|
* @throws {@link Vectros.TooManyRequestsError}
|
|
4636
4649
|
*
|
|
4637
4650
|
* @example
|
|
4638
4651
|
* await client.documents.ingestDocument({
|
|
4639
|
-
*
|
|
4652
|
+
* body: {
|
|
4653
|
+
* title: "Patient Intake Form \u2014 Jane Doe"
|
|
4654
|
+
* }
|
|
4640
4655
|
* })
|
|
4641
4656
|
*/
|
|
4642
4657
|
ingestDocument(request, requestOptions) {
|
|
4643
4658
|
return HttpResponsePromise.fromPromise(this.__ingestDocument(request, requestOptions));
|
|
4644
4659
|
}
|
|
4645
4660
|
async __ingestDocument(request, requestOptions) {
|
|
4661
|
+
const { upsert, body: _body } = request;
|
|
4662
|
+
const _queryParams = {
|
|
4663
|
+
upsert
|
|
4664
|
+
};
|
|
4646
4665
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
4647
4666
|
const _headers = mergeHeaders(
|
|
4648
4667
|
_authRequest.headers,
|
|
@@ -4657,9 +4676,9 @@ var DocumentsClient = class {
|
|
|
4657
4676
|
method: "POST",
|
|
4658
4677
|
headers: _headers,
|
|
4659
4678
|
contentType: "application/json",
|
|
4660
|
-
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
4679
|
+
queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
|
|
4661
4680
|
requestType: "json",
|
|
4662
|
-
body:
|
|
4681
|
+
body: _body,
|
|
4663
4682
|
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
4664
4683
|
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
4665
4684
|
abortSignal: requestOptions?.abortSignal,
|
|
@@ -4673,6 +4692,8 @@ var DocumentsClient = class {
|
|
|
4673
4692
|
switch (_response.error.statusCode) {
|
|
4674
4693
|
case 400:
|
|
4675
4694
|
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
4695
|
+
case 403:
|
|
4696
|
+
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
4676
4697
|
case 429:
|
|
4677
4698
|
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
4678
4699
|
default:
|
|
@@ -4939,7 +4960,7 @@ var DocumentsClient = class {
|
|
|
4939
4960
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "PATCH", "/v1/documents/{id}");
|
|
4940
4961
|
}
|
|
4941
4962
|
/**
|
|
4942
|
-
* Finds documents of a given type by
|
|
4963
|
+
* 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.
|
|
4943
4964
|
*
|
|
4944
4965
|
* @param {Vectros.LookupDocumentsRequest} request
|
|
4945
4966
|
* @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -5234,12 +5255,13 @@ var DocumentsClient = class {
|
|
|
5234
5255
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/documents/{id}/versions");
|
|
5235
5256
|
}
|
|
5236
5257
|
/**
|
|
5237
|
-
* 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.
|
|
5258
|
+
* 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.
|
|
5238
5259
|
*
|
|
5239
5260
|
* @param {Vectros.FileUploadRequest} request
|
|
5240
5261
|
* @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
5241
5262
|
*
|
|
5242
5263
|
* @throws {@link Vectros.BadRequestError}
|
|
5264
|
+
* @throws {@link Vectros.ForbiddenError}
|
|
5243
5265
|
* @throws {@link Vectros.TooManyRequestsError}
|
|
5244
5266
|
*
|
|
5245
5267
|
* @example
|
|
@@ -5252,6 +5274,10 @@ var DocumentsClient = class {
|
|
|
5252
5274
|
return HttpResponsePromise.fromPromise(this.__uploadDocument(request, requestOptions));
|
|
5253
5275
|
}
|
|
5254
5276
|
async __uploadDocument(request, requestOptions) {
|
|
5277
|
+
const { upsert, ..._body } = request;
|
|
5278
|
+
const _queryParams = {
|
|
5279
|
+
upsert
|
|
5280
|
+
};
|
|
5255
5281
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
5256
5282
|
const _headers = mergeHeaders(
|
|
5257
5283
|
_authRequest.headers,
|
|
@@ -5266,9 +5292,9 @@ var DocumentsClient = class {
|
|
|
5266
5292
|
method: "POST",
|
|
5267
5293
|
headers: _headers,
|
|
5268
5294
|
contentType: "application/json",
|
|
5269
|
-
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
5295
|
+
queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
|
|
5270
5296
|
requestType: "json",
|
|
5271
|
-
body:
|
|
5297
|
+
body: _body,
|
|
5272
5298
|
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
5273
5299
|
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
5274
5300
|
abortSignal: requestOptions?.abortSignal,
|
|
@@ -5282,6 +5308,8 @@ var DocumentsClient = class {
|
|
|
5282
5308
|
switch (_response.error.statusCode) {
|
|
5283
5309
|
case 400:
|
|
5284
5310
|
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
5311
|
+
case 403:
|
|
5312
|
+
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
5285
5313
|
case 429:
|
|
5286
5314
|
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
5287
5315
|
default:
|
|
@@ -5360,23 +5388,30 @@ var FoldersClient = class {
|
|
|
5360
5388
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/folders");
|
|
5361
5389
|
}
|
|
5362
5390
|
/**
|
|
5363
|
-
* 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.
|
|
5391
|
+
* 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.
|
|
5364
5392
|
*
|
|
5365
|
-
* @param {Vectros.
|
|
5393
|
+
* @param {Vectros.CreateFolderRequest} request
|
|
5366
5394
|
* @param {FoldersClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
5367
5395
|
*
|
|
5368
5396
|
* @throws {@link Vectros.BadRequestError}
|
|
5397
|
+
* @throws {@link Vectros.ForbiddenError}
|
|
5369
5398
|
* @throws {@link Vectros.TooManyRequestsError}
|
|
5370
5399
|
*
|
|
5371
5400
|
* @example
|
|
5372
5401
|
* await client.folders.createFolder({
|
|
5373
|
-
*
|
|
5402
|
+
* body: {
|
|
5403
|
+
* name: "Patient Records 2024"
|
|
5404
|
+
* }
|
|
5374
5405
|
* })
|
|
5375
5406
|
*/
|
|
5376
5407
|
createFolder(request, requestOptions) {
|
|
5377
5408
|
return HttpResponsePromise.fromPromise(this.__createFolder(request, requestOptions));
|
|
5378
5409
|
}
|
|
5379
5410
|
async __createFolder(request, requestOptions) {
|
|
5411
|
+
const { upsert, body: _body } = request;
|
|
5412
|
+
const _queryParams = {
|
|
5413
|
+
upsert
|
|
5414
|
+
};
|
|
5380
5415
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
5381
5416
|
const _headers = mergeHeaders(
|
|
5382
5417
|
_authRequest.headers,
|
|
@@ -5391,9 +5426,9 @@ var FoldersClient = class {
|
|
|
5391
5426
|
method: "POST",
|
|
5392
5427
|
headers: _headers,
|
|
5393
5428
|
contentType: "application/json",
|
|
5394
|
-
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
5429
|
+
queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
|
|
5395
5430
|
requestType: "json",
|
|
5396
|
-
body:
|
|
5431
|
+
body: _body,
|
|
5397
5432
|
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
5398
5433
|
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
5399
5434
|
abortSignal: requestOptions?.abortSignal,
|
|
@@ -5407,6 +5442,8 @@ var FoldersClient = class {
|
|
|
5407
5442
|
switch (_response.error.statusCode) {
|
|
5408
5443
|
case 400:
|
|
5409
5444
|
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
5445
|
+
case 403:
|
|
5446
|
+
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
5410
5447
|
case 429:
|
|
5411
5448
|
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
5412
5449
|
default:
|
|
@@ -5815,23 +5852,30 @@ var IdentityClient = class {
|
|
|
5815
5852
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/clients");
|
|
5816
5853
|
}
|
|
5817
5854
|
/**
|
|
5818
|
-
* 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.
|
|
5855
|
+
* 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.
|
|
5819
5856
|
*
|
|
5820
|
-
* @param {Vectros.
|
|
5857
|
+
* @param {Vectros.CreateClientRequest} request
|
|
5821
5858
|
* @param {IdentityClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
5822
5859
|
*
|
|
5823
5860
|
* @throws {@link Vectros.BadRequestError}
|
|
5861
|
+
* @throws {@link Vectros.ForbiddenError}
|
|
5824
5862
|
* @throws {@link Vectros.TooManyRequestsError}
|
|
5825
5863
|
*
|
|
5826
5864
|
* @example
|
|
5827
5865
|
* await client.identity.createClient({
|
|
5828
|
-
*
|
|
5866
|
+
* body: {
|
|
5867
|
+
* externalId: "patient_789"
|
|
5868
|
+
* }
|
|
5829
5869
|
* })
|
|
5830
5870
|
*/
|
|
5831
5871
|
createClient(request, requestOptions) {
|
|
5832
5872
|
return HttpResponsePromise.fromPromise(this.__createClient(request, requestOptions));
|
|
5833
5873
|
}
|
|
5834
5874
|
async __createClient(request, requestOptions) {
|
|
5875
|
+
const { upsert, body: _body } = request;
|
|
5876
|
+
const _queryParams = {
|
|
5877
|
+
upsert
|
|
5878
|
+
};
|
|
5835
5879
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
5836
5880
|
const _headers = mergeHeaders(
|
|
5837
5881
|
_authRequest.headers,
|
|
@@ -5846,9 +5890,9 @@ var IdentityClient = class {
|
|
|
5846
5890
|
method: "POST",
|
|
5847
5891
|
headers: _headers,
|
|
5848
5892
|
contentType: "application/json",
|
|
5849
|
-
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
5893
|
+
queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
|
|
5850
5894
|
requestType: "json",
|
|
5851
|
-
body:
|
|
5895
|
+
body: _body,
|
|
5852
5896
|
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
5853
5897
|
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
5854
5898
|
abortSignal: requestOptions?.abortSignal,
|
|
@@ -5862,6 +5906,8 @@ var IdentityClient = class {
|
|
|
5862
5906
|
switch (_response.error.statusCode) {
|
|
5863
5907
|
case 400:
|
|
5864
5908
|
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
5909
|
+
case 403:
|
|
5910
|
+
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
5865
5911
|
case 429:
|
|
5866
5912
|
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
5867
5913
|
default:
|
|
@@ -6250,23 +6296,30 @@ var IdentityClient = class {
|
|
|
6250
6296
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/orgs");
|
|
6251
6297
|
}
|
|
6252
6298
|
/**
|
|
6253
|
-
* 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.
|
|
6299
|
+
* 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.
|
|
6254
6300
|
*
|
|
6255
|
-
* @param {Vectros.
|
|
6301
|
+
* @param {Vectros.CreateOrgRequest} request
|
|
6256
6302
|
* @param {IdentityClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
6257
6303
|
*
|
|
6258
6304
|
* @throws {@link Vectros.BadRequestError}
|
|
6305
|
+
* @throws {@link Vectros.ForbiddenError}
|
|
6259
6306
|
* @throws {@link Vectros.TooManyRequestsError}
|
|
6260
6307
|
*
|
|
6261
6308
|
* @example
|
|
6262
6309
|
* await client.identity.createOrg({
|
|
6263
|
-
*
|
|
6310
|
+
* body: {
|
|
6311
|
+
* externalId: "clinic_001"
|
|
6312
|
+
* }
|
|
6264
6313
|
* })
|
|
6265
6314
|
*/
|
|
6266
6315
|
createOrg(request, requestOptions) {
|
|
6267
6316
|
return HttpResponsePromise.fromPromise(this.__createOrg(request, requestOptions));
|
|
6268
6317
|
}
|
|
6269
6318
|
async __createOrg(request, requestOptions) {
|
|
6319
|
+
const { upsert, body: _body } = request;
|
|
6320
|
+
const _queryParams = {
|
|
6321
|
+
upsert
|
|
6322
|
+
};
|
|
6270
6323
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
6271
6324
|
const _headers = mergeHeaders(
|
|
6272
6325
|
_authRequest.headers,
|
|
@@ -6281,9 +6334,9 @@ var IdentityClient = class {
|
|
|
6281
6334
|
method: "POST",
|
|
6282
6335
|
headers: _headers,
|
|
6283
6336
|
contentType: "application/json",
|
|
6284
|
-
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
6337
|
+
queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
|
|
6285
6338
|
requestType: "json",
|
|
6286
|
-
body:
|
|
6339
|
+
body: _body,
|
|
6287
6340
|
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
6288
6341
|
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
6289
6342
|
abortSignal: requestOptions?.abortSignal,
|
|
@@ -6297,6 +6350,8 @@ var IdentityClient = class {
|
|
|
6297
6350
|
switch (_response.error.statusCode) {
|
|
6298
6351
|
case 400:
|
|
6299
6352
|
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
6353
|
+
case 403:
|
|
6354
|
+
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
6300
6355
|
case 429:
|
|
6301
6356
|
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
6302
6357
|
default:
|
|
@@ -6671,23 +6726,30 @@ var IdentityClient = class {
|
|
|
6671
6726
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/users");
|
|
6672
6727
|
}
|
|
6673
6728
|
/**
|
|
6674
|
-
* 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.
|
|
6729
|
+
* 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.
|
|
6675
6730
|
*
|
|
6676
|
-
* @param {Vectros.
|
|
6731
|
+
* @param {Vectros.CreateUserRequest} request
|
|
6677
6732
|
* @param {IdentityClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
6678
6733
|
*
|
|
6679
6734
|
* @throws {@link Vectros.BadRequestError}
|
|
6735
|
+
* @throws {@link Vectros.ForbiddenError}
|
|
6680
6736
|
* @throws {@link Vectros.TooManyRequestsError}
|
|
6681
6737
|
*
|
|
6682
6738
|
* @example
|
|
6683
6739
|
* await client.identity.createUser({
|
|
6684
|
-
*
|
|
6740
|
+
* body: {
|
|
6741
|
+
* externalId: "usr_12345"
|
|
6742
|
+
* }
|
|
6685
6743
|
* })
|
|
6686
6744
|
*/
|
|
6687
6745
|
createUser(request, requestOptions) {
|
|
6688
6746
|
return HttpResponsePromise.fromPromise(this.__createUser(request, requestOptions));
|
|
6689
6747
|
}
|
|
6690
6748
|
async __createUser(request, requestOptions) {
|
|
6749
|
+
const { upsert, body: _body } = request;
|
|
6750
|
+
const _queryParams = {
|
|
6751
|
+
upsert
|
|
6752
|
+
};
|
|
6691
6753
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
6692
6754
|
const _headers = mergeHeaders(
|
|
6693
6755
|
_authRequest.headers,
|
|
@@ -6702,9 +6764,9 @@ var IdentityClient = class {
|
|
|
6702
6764
|
method: "POST",
|
|
6703
6765
|
headers: _headers,
|
|
6704
6766
|
contentType: "application/json",
|
|
6705
|
-
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
6767
|
+
queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
|
|
6706
6768
|
requestType: "json",
|
|
6707
|
-
body:
|
|
6769
|
+
body: _body,
|
|
6708
6770
|
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
6709
6771
|
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
6710
6772
|
abortSignal: requestOptions?.abortSignal,
|
|
@@ -6718,6 +6780,8 @@ var IdentityClient = class {
|
|
|
6718
6780
|
switch (_response.error.statusCode) {
|
|
6719
6781
|
case 400:
|
|
6720
6782
|
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
6783
|
+
case 403:
|
|
6784
|
+
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
6721
6785
|
case 429:
|
|
6722
6786
|
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
6723
6787
|
default:
|
|
@@ -7535,9 +7599,9 @@ var RecordsClient = class {
|
|
|
7535
7599
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/records");
|
|
7536
7600
|
}
|
|
7537
7601
|
/**
|
|
7538
|
-
* 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.
|
|
7602
|
+
* 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.
|
|
7539
7603
|
*
|
|
7540
|
-
* @param {Vectros.
|
|
7604
|
+
* @param {Vectros.CreateRecordRequest} request
|
|
7541
7605
|
* @param {RecordsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
7542
7606
|
*
|
|
7543
7607
|
* @throws {@link Vectros.BadRequestError}
|
|
@@ -7546,19 +7610,25 @@ var RecordsClient = class {
|
|
|
7546
7610
|
*
|
|
7547
7611
|
* @example
|
|
7548
7612
|
* await client.records.createRecord({
|
|
7549
|
-
*
|
|
7550
|
-
*
|
|
7551
|
-
*
|
|
7552
|
-
*
|
|
7553
|
-
*
|
|
7554
|
-
*
|
|
7555
|
-
*
|
|
7613
|
+
* body: {
|
|
7614
|
+
* typeName: "intake_form",
|
|
7615
|
+
* schemaId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
|
|
7616
|
+
* payload: {
|
|
7617
|
+
* "first_name": "Jane",
|
|
7618
|
+
* "email": "jane@example.com"
|
|
7619
|
+
* },
|
|
7620
|
+
* folderId: "f47ac10b-58cc-4372-a567-0e02b2c3d479"
|
|
7621
|
+
* }
|
|
7556
7622
|
* })
|
|
7557
7623
|
*/
|
|
7558
7624
|
createRecord(request, requestOptions) {
|
|
7559
7625
|
return HttpResponsePromise.fromPromise(this.__createRecord(request, requestOptions));
|
|
7560
7626
|
}
|
|
7561
7627
|
async __createRecord(request, requestOptions) {
|
|
7628
|
+
const { upsert, body: _body } = request;
|
|
7629
|
+
const _queryParams = {
|
|
7630
|
+
upsert
|
|
7631
|
+
};
|
|
7562
7632
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
7563
7633
|
const _headers = mergeHeaders(
|
|
7564
7634
|
_authRequest.headers,
|
|
@@ -7573,9 +7643,9 @@ var RecordsClient = class {
|
|
|
7573
7643
|
method: "POST",
|
|
7574
7644
|
headers: _headers,
|
|
7575
7645
|
contentType: "application/json",
|
|
7576
|
-
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
7646
|
+
queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
|
|
7577
7647
|
requestType: "json",
|
|
7578
|
-
body:
|
|
7648
|
+
body: _body,
|
|
7579
7649
|
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
7580
7650
|
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
7581
7651
|
abortSignal: requestOptions?.abortSignal,
|
|
@@ -8171,43 +8241,50 @@ var SchemasClient = class {
|
|
|
8171
8241
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/schemas");
|
|
8172
8242
|
}
|
|
8173
8243
|
/**
|
|
8174
|
-
* 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.
|
|
8244
|
+
* 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.
|
|
8175
8245
|
*
|
|
8176
|
-
* @param {Vectros.
|
|
8246
|
+
* @param {Vectros.CreateSchemaRequest} request
|
|
8177
8247
|
* @param {SchemasClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
8178
8248
|
*
|
|
8179
8249
|
* @throws {@link Vectros.BadRequestError}
|
|
8250
|
+
* @throws {@link Vectros.ForbiddenError}
|
|
8180
8251
|
* @throws {@link Vectros.TooManyRequestsError}
|
|
8181
8252
|
*
|
|
8182
8253
|
* @example
|
|
8183
8254
|
* await client.schemas.createSchema({
|
|
8184
|
-
*
|
|
8185
|
-
*
|
|
8186
|
-
*
|
|
8187
|
-
*
|
|
8188
|
-
*
|
|
8189
|
-
*
|
|
8190
|
-
*
|
|
8191
|
-
*
|
|
8192
|
-
*
|
|
8193
|
-
*
|
|
8194
|
-
*
|
|
8195
|
-
*
|
|
8196
|
-
*
|
|
8197
|
-
*
|
|
8198
|
-
*
|
|
8199
|
-
*
|
|
8200
|
-
*
|
|
8201
|
-
*
|
|
8202
|
-
*
|
|
8203
|
-
*
|
|
8204
|
-
*
|
|
8255
|
+
* body: {
|
|
8256
|
+
* typeName: "intake_form",
|
|
8257
|
+
* displayName: "Client Intake Form",
|
|
8258
|
+
* description: "Captures initial client information",
|
|
8259
|
+
* fields: [{
|
|
8260
|
+
* fieldId: "first_name",
|
|
8261
|
+
* fieldType: "string",
|
|
8262
|
+
* required: true,
|
|
8263
|
+
* searchable: true
|
|
8264
|
+
* }, {
|
|
8265
|
+
* fieldId: "email",
|
|
8266
|
+
* fieldType: "string",
|
|
8267
|
+
* required: true
|
|
8268
|
+
* }],
|
|
8269
|
+
* lookupFields: [{
|
|
8270
|
+
* fieldName: "email",
|
|
8271
|
+
* unique: true
|
|
8272
|
+
* }],
|
|
8273
|
+
* capabilities: {
|
|
8274
|
+
* "auditHistory": true
|
|
8275
|
+
* },
|
|
8276
|
+
* allowedSurfaces: ["record"]
|
|
8277
|
+
* }
|
|
8205
8278
|
* })
|
|
8206
8279
|
*/
|
|
8207
8280
|
createSchema(request, requestOptions) {
|
|
8208
8281
|
return HttpResponsePromise.fromPromise(this.__createSchema(request, requestOptions));
|
|
8209
8282
|
}
|
|
8210
8283
|
async __createSchema(request, requestOptions) {
|
|
8284
|
+
const { upsert, body: _body } = request;
|
|
8285
|
+
const _queryParams = {
|
|
8286
|
+
upsert
|
|
8287
|
+
};
|
|
8211
8288
|
const _authRequest = await this._options.authProvider.getAuthRequest();
|
|
8212
8289
|
const _headers = mergeHeaders(
|
|
8213
8290
|
_authRequest.headers,
|
|
@@ -8222,9 +8299,9 @@ var SchemasClient = class {
|
|
|
8222
8299
|
method: "POST",
|
|
8223
8300
|
headers: _headers,
|
|
8224
8301
|
contentType: "application/json",
|
|
8225
|
-
queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
|
|
8302
|
+
queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
|
|
8226
8303
|
requestType: "json",
|
|
8227
|
-
body:
|
|
8304
|
+
body: _body,
|
|
8228
8305
|
timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
|
|
8229
8306
|
maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
|
|
8230
8307
|
abortSignal: requestOptions?.abortSignal,
|
|
@@ -8238,6 +8315,8 @@ var SchemasClient = class {
|
|
|
8238
8315
|
switch (_response.error.statusCode) {
|
|
8239
8316
|
case 400:
|
|
8240
8317
|
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
8318
|
+
case 403:
|
|
8319
|
+
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
8241
8320
|
case 429:
|
|
8242
8321
|
throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
|
|
8243
8322
|
default:
|
|
@@ -8788,11 +8867,36 @@ var BlueprintServicePrincipalSchema = z.object({
|
|
|
8788
8867
|
externalId: z.string().min(1),
|
|
8789
8868
|
displayName: z.string().min(1)
|
|
8790
8869
|
}).strict();
|
|
8791
|
-
var
|
|
8870
|
+
var SeedCommonShape = {
|
|
8871
|
+
/** The schema typeName this seed instantiates (should match a declared schema). */
|
|
8792
8872
|
typeName: z.string().min(1),
|
|
8793
|
-
|
|
8873
|
+
/**
|
|
8874
|
+
* Stable, caller-supplied id — the loader's idempotency key AND the value other
|
|
8875
|
+
* seeds resolve a `reference` against (across surfaces: a record seed may
|
|
8876
|
+
* reference a document seed by its externalId, and vice versa).
|
|
8877
|
+
*/
|
|
8878
|
+
externalId: z.string().min(1)
|
|
8879
|
+
};
|
|
8880
|
+
var RecordSeedSchema = z.object({
|
|
8881
|
+
surface: z.literal("record"),
|
|
8882
|
+
...SeedCommonShape,
|
|
8883
|
+
/** The record payload, validated against the bound schema. */
|
|
8794
8884
|
fields: z.record(z.unknown())
|
|
8795
8885
|
}).strict();
|
|
8886
|
+
var DocumentSeedSchema = z.object({
|
|
8887
|
+
surface: z.literal("document"),
|
|
8888
|
+
...SeedCommonShape,
|
|
8889
|
+
/** Human-readable document title — REQUIRED by the text-ingest path. */
|
|
8890
|
+
title: z.string().min(1),
|
|
8891
|
+
/** Raw text content to ingest + index — REQUIRED and non-empty (the platform rejects a blank ingest). */
|
|
8892
|
+
text: z.string().min(1),
|
|
8893
|
+
/** Optional structured payload bound to the schema (the document's `fields`). */
|
|
8894
|
+
fields: z.record(z.unknown()).optional()
|
|
8895
|
+
}).strict();
|
|
8896
|
+
var BlueprintSeedRecordSchema = z.discriminatedUnion("surface", [
|
|
8897
|
+
RecordSeedSchema,
|
|
8898
|
+
DocumentSeedSchema
|
|
8899
|
+
]);
|
|
8796
8900
|
var SELF_TOKEN_RE = /\$\{\{\s*self\.[A-Za-z_]\w*\s*\}\}/;
|
|
8797
8901
|
function lintSelfTokenPlacement(value, ctx) {
|
|
8798
8902
|
const walk = (node, path4, inRoleDataScope) => {
|
|
@@ -8848,6 +8952,23 @@ function lintIdentityRefsDeclared(value, ctx) {
|
|
|
8848
8952
|
};
|
|
8849
8953
|
walk(value, []);
|
|
8850
8954
|
}
|
|
8955
|
+
function lintSeedSurfaces(value, ctx) {
|
|
8956
|
+
const bp = value;
|
|
8957
|
+
if (!Array.isArray(bp.seed)) return;
|
|
8958
|
+
const byType = new Map((bp.schemas ?? []).map((s) => [s.typeName, s]));
|
|
8959
|
+
bp.seed.forEach((seed, i) => {
|
|
8960
|
+
const schema = byType.get(seed.typeName);
|
|
8961
|
+
if (!schema) return;
|
|
8962
|
+
const allowed = schema.allowedSurfaces ?? ["record"];
|
|
8963
|
+
if (!allowed.includes(seed.surface ?? "record")) {
|
|
8964
|
+
ctx.addIssue({
|
|
8965
|
+
code: z.ZodIssueCode.custom,
|
|
8966
|
+
path: ["seed", i, "surface"],
|
|
8967
|
+
message: `seed '${seed.externalId}' uses surface '${seed.surface ?? "record"}', but schema '${seed.typeName}' allows only [${allowed.join(", ")}]`
|
|
8968
|
+
});
|
|
8969
|
+
}
|
|
8970
|
+
});
|
|
8971
|
+
}
|
|
8851
8972
|
var BlueprintSchema = z.object({
|
|
8852
8973
|
/** Stable blueprint id (the `--blueprint <name>` selector + idempotency key). */
|
|
8853
8974
|
name: z.string().min(1),
|
|
@@ -8870,6 +8991,7 @@ var BlueprintSchema = z.object({
|
|
|
8870
8991
|
}).strict().superRefine((bp, ctx) => {
|
|
8871
8992
|
lintSelfTokenPlacement(bp, ctx);
|
|
8872
8993
|
lintIdentityRefsDeclared(bp, ctx);
|
|
8994
|
+
lintSeedSurfaces(bp, ctx);
|
|
8873
8995
|
});
|
|
8874
8996
|
function formatIssuePath(path4) {
|
|
8875
8997
|
let out = "";
|
|
@@ -9267,6 +9389,7 @@ var taskManagement = {
|
|
|
9267
9389
|
},
|
|
9268
9390
|
seed: [
|
|
9269
9391
|
{
|
|
9392
|
+
surface: "record",
|
|
9270
9393
|
typeName: "task",
|
|
9271
9394
|
externalId: "seed-welcome",
|
|
9272
9395
|
fields: {
|
|
@@ -9494,6 +9617,7 @@ var codingAgentMemory = {
|
|
|
9494
9617
|
// Seeded FIRST so the convention below resolves its reference to it — the
|
|
9495
9618
|
// loader creates seeds in array order, and a reference target must exist when
|
|
9496
9619
|
// the referencing record is written.
|
|
9620
|
+
surface: "record",
|
|
9497
9621
|
typeName: "decision",
|
|
9498
9622
|
externalId: "seed-use-vectros-for-memory",
|
|
9499
9623
|
fields: {
|
|
@@ -9509,6 +9633,7 @@ var codingAgentMemory = {
|
|
|
9509
9633
|
// Demonstrates a live typed link at bootstrap: this convention references the
|
|
9510
9634
|
// decision above by its externalId, so "open the decision behind this rule"
|
|
9511
9635
|
// works the moment the blueprint is applied.
|
|
9636
|
+
surface: "record",
|
|
9512
9637
|
typeName: "convention",
|
|
9513
9638
|
externalId: "seed-record-the-why",
|
|
9514
9639
|
fields: {
|
|
@@ -9523,149 +9648,831 @@ var codingAgentMemory = {
|
|
|
9523
9648
|
]
|
|
9524
9649
|
};
|
|
9525
9650
|
var coding_agent_memory_default = codingAgentMemory;
|
|
9526
|
-
var
|
|
9527
|
-
name: "
|
|
9651
|
+
var agenticSdlc = {
|
|
9652
|
+
name: "agentic-sdlc",
|
|
9528
9653
|
version: "1.0.0",
|
|
9529
|
-
description: "A
|
|
9530
|
-
contextId: "
|
|
9531
|
-
contextName: "
|
|
9654
|
+
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.",
|
|
9655
|
+
contextId: "agentic-sdlc",
|
|
9656
|
+
contextName: "Agentic SDLC Knowledge Base",
|
|
9532
9657
|
schemas: [
|
|
9658
|
+
// ============================ DOCUMENTS ============================
|
|
9659
|
+
// Content-dominant: the markdown body is the artifact; fields are metadata.
|
|
9660
|
+
// Each binds the `document` surface and is ingested via `document_ingest`.
|
|
9533
9661
|
{
|
|
9534
|
-
|
|
9535
|
-
|
|
9662
|
+
// An architecture/product DECISION (an ADR). The body is the prose —
|
|
9663
|
+
// Context / Decision / Consequences — and is what `rag_ask` answers over;
|
|
9664
|
+
// the fields below are filter/sort metadata + the supersede chain. The
|
|
9665
|
+
// anchor of the graph: most other types link back to a decision.
|
|
9666
|
+
typeName: "decision",
|
|
9667
|
+
displayName: "Decision (ADR)",
|
|
9536
9668
|
indexMode: "HYBRID",
|
|
9537
|
-
|
|
9669
|
+
allowedSurfaces: ["document"],
|
|
9670
|
+
// A document carries an INTRINSIC title (the ingest title) + body; the schema
|
|
9671
|
+
// declares only the metadata BEYOND those. (No typed `title` field — that
|
|
9672
|
+
// would duplicate the document's own title.)
|
|
9538
9673
|
fields: [
|
|
9539
9674
|
{
|
|
9540
|
-
fieldId: "
|
|
9675
|
+
fieldId: "summary",
|
|
9541
9676
|
fieldType: "string",
|
|
9542
|
-
required: true,
|
|
9543
9677
|
searchable: true,
|
|
9544
|
-
|
|
9545
|
-
renderHints: { label: "
|
|
9678
|
+
description: "A one-paragraph abstract of the decision. The full reasoning is the document body.",
|
|
9679
|
+
renderHints: { label: "Summary", widget: "textarea", order: 2 }
|
|
9546
9680
|
},
|
|
9547
9681
|
{
|
|
9548
|
-
fieldId: "
|
|
9549
|
-
fieldType: "
|
|
9550
|
-
|
|
9551
|
-
|
|
9552
|
-
|
|
9553
|
-
renderHints: { label: "Body", widget: "textarea", order: 2, section: "Note" }
|
|
9682
|
+
fieldId: "status",
|
|
9683
|
+
fieldType: "enum",
|
|
9684
|
+
filterable: true,
|
|
9685
|
+
enumValues: ["proposed", "accepted", "superseded", "deprecated"],
|
|
9686
|
+
renderHints: { label: "Status", widget: "select", order: 3 }
|
|
9554
9687
|
},
|
|
9555
9688
|
{
|
|
9556
|
-
fieldId: "
|
|
9557
|
-
fieldType: "
|
|
9689
|
+
fieldId: "area",
|
|
9690
|
+
fieldType: "string",
|
|
9558
9691
|
filterable: true,
|
|
9559
|
-
description: '
|
|
9560
|
-
renderHints: { label: "
|
|
9692
|
+
description: 'Subsystem the decision applies to (e.g. "search", "auth", "billing").',
|
|
9693
|
+
renderHints: { label: "Area", widget: "text", order: 4 }
|
|
9561
9694
|
},
|
|
9562
9695
|
{
|
|
9563
|
-
|
|
9564
|
-
|
|
9565
|
-
// `other` keeps it from being a straitjacket for a personal brain-dump.
|
|
9566
|
-
fieldId: "source",
|
|
9567
|
-
fieldType: "enum",
|
|
9696
|
+
fieldId: "tags",
|
|
9697
|
+
fieldType: "array",
|
|
9568
9698
|
filterable: true,
|
|
9569
|
-
|
|
9570
|
-
|
|
9571
|
-
renderHints: { label: "Source", widget: "select", order: 4, section: "Organize" }
|
|
9699
|
+
description: 'Freeform labels (e.g. "security", "schema"). Search-side filter.',
|
|
9700
|
+
renderHints: { label: "Tags", order: 5 }
|
|
9572
9701
|
},
|
|
9573
9702
|
{
|
|
9574
|
-
|
|
9575
|
-
|
|
9576
|
-
|
|
9577
|
-
|
|
9578
|
-
|
|
9579
|
-
|
|
9703
|
+
// Self-reference (document → document): the decision this one supersedes.
|
|
9704
|
+
fieldId: "supersedes",
|
|
9705
|
+
fieldType: "reference",
|
|
9706
|
+
targetTypeName: "decision",
|
|
9707
|
+
targetSurface: "document",
|
|
9708
|
+
targetField: "externalId",
|
|
9709
|
+
cardinality: "one",
|
|
9710
|
+
description: "The decision this one supersedes (by externalId).",
|
|
9711
|
+
renderHints: { label: "Supersedes", order: 6 }
|
|
9580
9712
|
},
|
|
9581
9713
|
{
|
|
9582
|
-
fieldId: "
|
|
9714
|
+
fieldId: "date",
|
|
9583
9715
|
fieldType: "date",
|
|
9584
|
-
description:
|
|
9585
|
-
renderHints: { label: "
|
|
9716
|
+
description: "ISO-8601 decision date. Range-queryable / sortable.",
|
|
9717
|
+
renderHints: { label: "Date", widget: "date", order: 7 }
|
|
9586
9718
|
}
|
|
9587
|
-
// externalId is the record's FIRST-CLASS identifier (the dedup/upsert key + the
|
|
9588
|
-
// value a `reference` resolves against) — not a payload field; the loader sends
|
|
9589
|
-
// it top-level on the RecordRequest.
|
|
9590
9719
|
],
|
|
9591
|
-
|
|
9592
|
-
|
|
9593
|
-
// • `source` — EQUALITY, sorted by `lastUpdated`: enumerate notes from one
|
|
9594
|
-
// source, most-recently-touched first. `lastUpdated` is always present, so
|
|
9595
|
-
// the sort never drops a note (sorting an equality lookup by an OPTIONAL
|
|
9596
|
-
// field would silently exclude rows that lack it).
|
|
9597
|
-
// • `status` — EQUALITY: "show archived notes".
|
|
9598
|
-
// • `capturedAt` — RANGE: ordered `from`/`to`/`prefix` ("captured in 2026-06").
|
|
9599
|
-
// `externalId` has a built-in first-class finder — look it up directly, never
|
|
9600
|
-
// redeclare it here. Equality lookups use the 7 fast index slots (2 used here);
|
|
9601
|
-
// range lookups use a relationship row, billed at the range rate.
|
|
9602
|
-
lookupFields: [
|
|
9603
|
-
{ fieldName: "source", sortBy: "lastUpdated" },
|
|
9604
|
-
"status",
|
|
9605
|
-
{ fieldName: "capturedAt", rangeEnabled: true }
|
|
9606
|
-
]
|
|
9607
|
-
}
|
|
9608
|
-
],
|
|
9609
|
-
// Least-privilege profile — r/c/u + search + schema discovery + inference:r
|
|
9610
|
-
// (so the agent can `rag_ask` a grounded, cited question over your notes). No
|
|
9611
|
-
// records:d: notes are archived (status → 'archived'), never deleted.
|
|
9612
|
-
accessProfile: {
|
|
9613
|
-
allowedActions: ["records:r", "records:c", "records:u", "search:r", "schemas:r", "inference:r"]
|
|
9614
|
-
},
|
|
9615
|
-
servicePrincipal: {
|
|
9616
|
-
externalId: "second-brain",
|
|
9617
|
-
displayName: "Second Brain"
|
|
9618
|
-
},
|
|
9619
|
-
seed: [
|
|
9620
|
-
{
|
|
9621
|
-
typeName: "note",
|
|
9622
|
-
externalId: "seed-welcome",
|
|
9623
|
-
fields: {
|
|
9624
|
-
title: "Welcome to your Second Brain",
|
|
9625
|
-
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.',
|
|
9626
|
-
tags: ["getting-started"],
|
|
9627
|
-
source: "thought",
|
|
9628
|
-
status: "active",
|
|
9629
|
-
capturedAt: "2026-06-14"
|
|
9630
|
-
}
|
|
9631
|
-
}
|
|
9632
|
-
]
|
|
9633
|
-
};
|
|
9634
|
-
var second_brain_default = secondBrain;
|
|
9635
|
-
var clinicalIntake = {
|
|
9636
|
-
name: "clinical-intake",
|
|
9637
|
-
version: "1.0.0",
|
|
9638
|
-
description: "Behavioral-health intake with PHI fields \u2014 demonstrates redact-at-write, audit history, blind-index lookup, and search exclusion. Synthetic data only.",
|
|
9639
|
-
contextId: "clinical-intake",
|
|
9640
|
-
contextName: "Clinical Intake",
|
|
9641
|
-
schemas: [
|
|
9720
|
+
lookupFields: ["status", "area", "supersedes", { fieldName: "date", rangeEnabled: true }]
|
|
9721
|
+
},
|
|
9642
9722
|
{
|
|
9643
|
-
|
|
9644
|
-
|
|
9723
|
+
// A DESIGN doc or spec — the exploration that drives a decision. Distinct
|
|
9724
|
+
// from `decision` (a different browse genre + it links to the decision it
|
|
9725
|
+
// informs). Body = the design narrative.
|
|
9726
|
+
typeName: "design",
|
|
9727
|
+
displayName: "Design",
|
|
9645
9728
|
indexMode: "HYBRID",
|
|
9646
|
-
|
|
9647
|
-
//
|
|
9729
|
+
allowedSurfaces: ["document"],
|
|
9730
|
+
// Intrinsic title + body; schema = metadata beyond those (no typed `title`).
|
|
9648
9731
|
fields: [
|
|
9649
|
-
// --- Non-sensitive, searchable/filterable working fields ---
|
|
9650
9732
|
{
|
|
9651
|
-
fieldId: "
|
|
9652
|
-
fieldType: "string",
|
|
9653
|
-
required: true,
|
|
9654
|
-
description: "Caller-stable intake id; the dedup/lookup key.",
|
|
9655
|
-
validation: { minLength: 1, maxLength: 64 },
|
|
9656
|
-
renderHints: { label: "Case ID", widget: "text", order: 1, section: "Intake", displayField: true }
|
|
9657
|
-
},
|
|
9658
|
-
{
|
|
9659
|
-
fieldId: "presentingConcern",
|
|
9733
|
+
fieldId: "summary",
|
|
9660
9734
|
fieldType: "string",
|
|
9661
9735
|
searchable: true,
|
|
9662
|
-
description: "
|
|
9663
|
-
|
|
9664
|
-
renderHints: { label: "Presenting concern", widget: "textarea", order: 2, section: "Intake" }
|
|
9736
|
+
description: "A one-paragraph abstract. The full design is the document body.",
|
|
9737
|
+
renderHints: { label: "Summary", widget: "textarea", order: 2 }
|
|
9665
9738
|
},
|
|
9666
9739
|
{
|
|
9667
|
-
fieldId: "
|
|
9668
|
-
fieldType: "
|
|
9740
|
+
fieldId: "status",
|
|
9741
|
+
fieldType: "enum",
|
|
9742
|
+
filterable: true,
|
|
9743
|
+
enumValues: ["draft", "active", "implemented", "superseded"],
|
|
9744
|
+
renderHints: { label: "Status", widget: "select", order: 3 }
|
|
9745
|
+
},
|
|
9746
|
+
{
|
|
9747
|
+
fieldId: "area",
|
|
9748
|
+
fieldType: "string",
|
|
9749
|
+
filterable: true,
|
|
9750
|
+
renderHints: { label: "Area", widget: "text", order: 4 }
|
|
9751
|
+
},
|
|
9752
|
+
{
|
|
9753
|
+
fieldId: "tags",
|
|
9754
|
+
fieldType: "array",
|
|
9755
|
+
filterable: true,
|
|
9756
|
+
renderHints: { label: "Tags", order: 5 }
|
|
9757
|
+
},
|
|
9758
|
+
{
|
|
9759
|
+
// Cross-document edge: the decision this design informs/produces.
|
|
9760
|
+
fieldId: "relatedDecision",
|
|
9761
|
+
fieldType: "reference",
|
|
9762
|
+
targetTypeName: "decision",
|
|
9763
|
+
targetSurface: "document",
|
|
9764
|
+
targetField: "externalId",
|
|
9765
|
+
cardinality: "one",
|
|
9766
|
+
description: "The decision this design informs (by externalId).",
|
|
9767
|
+
renderHints: { label: "Related decision", order: 6 }
|
|
9768
|
+
},
|
|
9769
|
+
{
|
|
9770
|
+
// Self-reference: a design supersedes an earlier design/spec.
|
|
9771
|
+
fieldId: "supersedes",
|
|
9772
|
+
fieldType: "reference",
|
|
9773
|
+
targetTypeName: "design",
|
|
9774
|
+
targetSurface: "document",
|
|
9775
|
+
targetField: "externalId",
|
|
9776
|
+
cardinality: "one",
|
|
9777
|
+
description: "The design this one supersedes (by externalId).",
|
|
9778
|
+
renderHints: { label: "Supersedes", order: 7 }
|
|
9779
|
+
},
|
|
9780
|
+
{
|
|
9781
|
+
fieldId: "updatedOn",
|
|
9782
|
+
fieldType: "date",
|
|
9783
|
+
description: "ISO-8601 \u2014 when last revised. Range-queryable.",
|
|
9784
|
+
renderHints: { label: "Updated on", widget: "date", order: 8 }
|
|
9785
|
+
}
|
|
9786
|
+
],
|
|
9787
|
+
lookupFields: ["status", "area", "relatedDecision", "supersedes", { fieldName: "updatedOn", rangeEnabled: true }]
|
|
9788
|
+
},
|
|
9789
|
+
{
|
|
9790
|
+
// A REFERENCE doc — maintained "how it works / how to": guides, onboarding,
|
|
9791
|
+
// API docs, process docs. Body = the prose. Distinct shape: a `category`
|
|
9792
|
+
// (DRY sub-labels — onboarding/api/process are same shape) and `lastReviewed`
|
|
9793
|
+
// (freshness is the whole game for reference material).
|
|
9794
|
+
typeName: "reference",
|
|
9795
|
+
displayName: "Reference",
|
|
9796
|
+
indexMode: "HYBRID",
|
|
9797
|
+
allowedSurfaces: ["document"],
|
|
9798
|
+
// Intrinsic title + body; schema = metadata beyond those (no typed `title`).
|
|
9799
|
+
fields: [
|
|
9800
|
+
{
|
|
9801
|
+
fieldId: "summary",
|
|
9802
|
+
fieldType: "string",
|
|
9803
|
+
searchable: true,
|
|
9804
|
+
description: "A one-paragraph abstract. The full reference is the document body.",
|
|
9805
|
+
renderHints: { label: "Summary", widget: "textarea", order: 2 }
|
|
9806
|
+
},
|
|
9807
|
+
{
|
|
9808
|
+
// The sub-kind, as a filterable field (not a separate schema — these are
|
|
9809
|
+
// the same shape, differing only by label, so a field is the DRY choice).
|
|
9810
|
+
fieldId: "category",
|
|
9811
|
+
fieldType: "enum",
|
|
9812
|
+
filterable: true,
|
|
9813
|
+
enumValues: ["guide", "onboarding", "api", "process", "other"],
|
|
9814
|
+
renderHints: { label: "Category", widget: "select", order: 3 }
|
|
9815
|
+
},
|
|
9816
|
+
{
|
|
9817
|
+
fieldId: "area",
|
|
9818
|
+
fieldType: "string",
|
|
9819
|
+
filterable: true,
|
|
9820
|
+
renderHints: { label: "Area", widget: "text", order: 4 }
|
|
9821
|
+
},
|
|
9822
|
+
{
|
|
9823
|
+
fieldId: "tags",
|
|
9824
|
+
fieldType: "array",
|
|
9825
|
+
filterable: true,
|
|
9826
|
+
renderHints: { label: "Tags", order: 5 }
|
|
9827
|
+
},
|
|
9828
|
+
{
|
|
9829
|
+
fieldId: "status",
|
|
9830
|
+
fieldType: "enum",
|
|
9831
|
+
filterable: true,
|
|
9832
|
+
enumValues: ["active", "superseded"],
|
|
9833
|
+
renderHints: { label: "Status", widget: "select", order: 6 }
|
|
9834
|
+
},
|
|
9835
|
+
{
|
|
9836
|
+
// Optional cross-document edge: the decision behind this reference.
|
|
9837
|
+
fieldId: "relatedDecision",
|
|
9838
|
+
fieldType: "reference",
|
|
9839
|
+
targetTypeName: "decision",
|
|
9840
|
+
targetSurface: "document",
|
|
9841
|
+
targetField: "externalId",
|
|
9842
|
+
cardinality: "one",
|
|
9843
|
+
description: "The decision behind this reference, if any (by externalId).",
|
|
9844
|
+
renderHints: { label: "Related decision", order: 7 }
|
|
9845
|
+
},
|
|
9846
|
+
{
|
|
9847
|
+
// Freshness — "references not reviewed since X". The reference-doc signal.
|
|
9848
|
+
fieldId: "lastReviewed",
|
|
9849
|
+
fieldType: "date",
|
|
9850
|
+
description: "ISO-8601 \u2014 when this reference was last verified current. Range-queryable.",
|
|
9851
|
+
renderHints: { label: "Last reviewed", widget: "date", order: 8 }
|
|
9852
|
+
}
|
|
9853
|
+
],
|
|
9854
|
+
lookupFields: ["category", "area", "status", "relatedDecision", { fieldName: "lastReviewed", rangeEnabled: true }]
|
|
9855
|
+
},
|
|
9856
|
+
{
|
|
9857
|
+
// A RUNBOOK — a step-by-step operational procedure (deploy, release, recover).
|
|
9858
|
+
// Body = the procedure. Distinct browse genre, and often BORN FROM a
|
|
9859
|
+
// post-mortem (its resolution, codified) — a cross-document edge.
|
|
9860
|
+
typeName: "runbook",
|
|
9861
|
+
displayName: "Runbook",
|
|
9862
|
+
indexMode: "HYBRID",
|
|
9863
|
+
allowedSurfaces: ["document"],
|
|
9864
|
+
// Intrinsic title + body; schema = metadata beyond those (no typed `title`).
|
|
9865
|
+
fields: [
|
|
9866
|
+
{
|
|
9867
|
+
fieldId: "summary",
|
|
9868
|
+
fieldType: "string",
|
|
9869
|
+
searchable: true,
|
|
9870
|
+
description: "When to use this runbook \u2014 the trigger. The steps are the document body.",
|
|
9871
|
+
renderHints: { label: "Summary", widget: "textarea", order: 2 }
|
|
9872
|
+
},
|
|
9873
|
+
{
|
|
9874
|
+
fieldId: "area",
|
|
9875
|
+
fieldType: "string",
|
|
9876
|
+
filterable: true,
|
|
9877
|
+
renderHints: { label: "Area", widget: "text", order: 3 }
|
|
9878
|
+
},
|
|
9879
|
+
{
|
|
9880
|
+
fieldId: "tags",
|
|
9881
|
+
fieldType: "array",
|
|
9882
|
+
filterable: true,
|
|
9883
|
+
renderHints: { label: "Tags", order: 4 }
|
|
9884
|
+
},
|
|
9885
|
+
{
|
|
9886
|
+
fieldId: "status",
|
|
9887
|
+
fieldType: "enum",
|
|
9888
|
+
filterable: true,
|
|
9889
|
+
enumValues: ["active", "retired"],
|
|
9890
|
+
renderHints: { label: "Status", widget: "select", order: 5 }
|
|
9891
|
+
},
|
|
9892
|
+
{
|
|
9893
|
+
// Cross-document edge: the post-mortem whose resolution this runbook codifies.
|
|
9894
|
+
fieldId: "bornFrom",
|
|
9895
|
+
fieldType: "reference",
|
|
9896
|
+
targetTypeName: "postmortem",
|
|
9897
|
+
targetSurface: "document",
|
|
9898
|
+
targetField: "externalId",
|
|
9899
|
+
cardinality: "one",
|
|
9900
|
+
description: "The post-mortem this runbook was codified from (by externalId).",
|
|
9901
|
+
renderHints: { label: "Born from (post-mortem)", order: 6 }
|
|
9902
|
+
},
|
|
9903
|
+
{
|
|
9904
|
+
fieldId: "relatedDecision",
|
|
9905
|
+
fieldType: "reference",
|
|
9906
|
+
targetTypeName: "decision",
|
|
9907
|
+
targetSurface: "document",
|
|
9908
|
+
targetField: "externalId",
|
|
9909
|
+
cardinality: "one",
|
|
9910
|
+
description: "A decision this runbook implements, if any (by externalId).",
|
|
9911
|
+
renderHints: { label: "Related decision", order: 7 }
|
|
9912
|
+
},
|
|
9913
|
+
{
|
|
9914
|
+
fieldId: "updatedOn",
|
|
9915
|
+
fieldType: "date",
|
|
9916
|
+
description: "ISO-8601 \u2014 when last revised. Range-queryable.",
|
|
9917
|
+
renderHints: { label: "Updated on", widget: "date", order: 8 }
|
|
9918
|
+
}
|
|
9919
|
+
],
|
|
9920
|
+
lookupFields: ["area", "status", "bornFrom", "relatedDecision", { fieldName: "updatedOn", rangeEnabled: true }]
|
|
9921
|
+
},
|
|
9922
|
+
{
|
|
9923
|
+
// A POST-MORTEM — what broke and the durable lesson. Body = the writeup
|
|
9924
|
+
// (impact / root cause / resolution / lesson). Distinct shape: `severity` +
|
|
9925
|
+
// `occurredOn` (the incident timeline). "Have we hit this before?" lives here.
|
|
9926
|
+
typeName: "postmortem",
|
|
9927
|
+
displayName: "Post-mortem",
|
|
9928
|
+
indexMode: "HYBRID",
|
|
9929
|
+
allowedSurfaces: ["document"],
|
|
9930
|
+
// Intrinsic title + body; schema = metadata beyond those (no typed `title`).
|
|
9931
|
+
fields: [
|
|
9932
|
+
{
|
|
9933
|
+
fieldId: "summary",
|
|
9934
|
+
fieldType: "string",
|
|
9935
|
+
searchable: true,
|
|
9936
|
+
description: "What happened, in brief. The full analysis (impact/root cause/resolution/lesson) is the body.",
|
|
9937
|
+
renderHints: { label: "Summary", widget: "textarea", order: 2 }
|
|
9938
|
+
},
|
|
9939
|
+
{
|
|
9940
|
+
fieldId: "severity",
|
|
9941
|
+
fieldType: "enum",
|
|
9942
|
+
filterable: true,
|
|
9943
|
+
enumValues: ["low", "medium", "high", "critical"],
|
|
9944
|
+
renderHints: { label: "Severity", widget: "select", order: 3 }
|
|
9945
|
+
},
|
|
9946
|
+
{
|
|
9947
|
+
fieldId: "status",
|
|
9948
|
+
fieldType: "enum",
|
|
9949
|
+
filterable: true,
|
|
9950
|
+
enumValues: ["open", "mitigated", "resolved"],
|
|
9951
|
+
renderHints: { label: "Status", widget: "select", order: 4 }
|
|
9952
|
+
},
|
|
9953
|
+
{
|
|
9954
|
+
fieldId: "area",
|
|
9955
|
+
fieldType: "string",
|
|
9956
|
+
filterable: true,
|
|
9957
|
+
renderHints: { label: "Area", widget: "text", order: 5 }
|
|
9958
|
+
},
|
|
9959
|
+
{
|
|
9960
|
+
fieldId: "tags",
|
|
9961
|
+
fieldType: "array",
|
|
9962
|
+
filterable: true,
|
|
9963
|
+
renderHints: { label: "Tags", order: 6 }
|
|
9964
|
+
},
|
|
9965
|
+
{
|
|
9966
|
+
fieldId: "relatedDecision",
|
|
9967
|
+
fieldType: "reference",
|
|
9968
|
+
targetTypeName: "decision",
|
|
9969
|
+
targetSurface: "document",
|
|
9970
|
+
targetField: "externalId",
|
|
9971
|
+
cardinality: "one",
|
|
9972
|
+
description: "A decision implicated in or addressed by this incident (by externalId).",
|
|
9973
|
+
renderHints: { label: "Related decision", order: 7 }
|
|
9974
|
+
},
|
|
9975
|
+
{
|
|
9976
|
+
fieldId: "occurredOn",
|
|
9977
|
+
fieldType: "date",
|
|
9978
|
+
description: 'ISO-8601 \u2014 when it happened. Range-queryable \u2014 "incidents this month".',
|
|
9979
|
+
renderHints: { label: "Occurred on", widget: "date", order: 8 }
|
|
9980
|
+
}
|
|
9981
|
+
],
|
|
9982
|
+
lookupFields: ["severity", "status", "area", "relatedDecision", { fieldName: "occurredOn", rangeEnabled: true }]
|
|
9983
|
+
},
|
|
9984
|
+
// ============================= RECORDS =============================
|
|
9985
|
+
// Structure-dominant: the typed fields are the artifact. Short, exact-queryable.
|
|
9986
|
+
{
|
|
9987
|
+
// A governance CONTROL — a policy/standard/control the codebase must satisfy,
|
|
9988
|
+
// WITH its evidence. The compliance instrument: it records what enforces it
|
|
9989
|
+
// (`evidence`), the runbook that VERIFIES it (cross-surface → document), and
|
|
9990
|
+
// the decision that mandates it. "Which critical controls are active, and how
|
|
9991
|
+
// is each proven?"
|
|
9992
|
+
typeName: "control",
|
|
9993
|
+
displayName: "Control",
|
|
9994
|
+
indexMode: "HYBRID",
|
|
9995
|
+
fields: [
|
|
9996
|
+
{
|
|
9997
|
+
fieldId: "title",
|
|
9998
|
+
fieldType: "string",
|
|
9999
|
+
required: true,
|
|
10000
|
+
searchable: true,
|
|
10001
|
+
validation: { minLength: 1, maxLength: 200 },
|
|
10002
|
+
renderHints: { label: "Title", widget: "text", order: 1, displayField: true }
|
|
10003
|
+
},
|
|
10004
|
+
{
|
|
10005
|
+
fieldId: "statement",
|
|
10006
|
+
fieldType: "string",
|
|
10007
|
+
searchable: true,
|
|
10008
|
+
description: "The requirement \u2014 what must always hold.",
|
|
10009
|
+
renderHints: { label: "Statement", widget: "textarea", order: 2 }
|
|
10010
|
+
},
|
|
10011
|
+
{
|
|
10012
|
+
fieldId: "rationale",
|
|
10013
|
+
fieldType: "string",
|
|
10014
|
+
searchable: true,
|
|
10015
|
+
description: "Why the control exists \u2014 the risk it prevents.",
|
|
10016
|
+
renderHints: { label: "Rationale", widget: "textarea", order: 3 }
|
|
10017
|
+
},
|
|
10018
|
+
{
|
|
10019
|
+
// The policy → implementation spectrum in one filterable field.
|
|
10020
|
+
fieldId: "kind",
|
|
10021
|
+
fieldType: "enum",
|
|
10022
|
+
filterable: true,
|
|
10023
|
+
enumValues: ["policy", "standard", "control"],
|
|
10024
|
+
renderHints: { label: "Kind", widget: "select", order: 4 }
|
|
10025
|
+
},
|
|
10026
|
+
{
|
|
10027
|
+
// Ordinal, but EQUALITY (range/prefix order is lexical, so low<critical
|
|
10028
|
+
// would sort alphabetically). Enumerate "all critical controls" by equality.
|
|
10029
|
+
fieldId: "criticality",
|
|
10030
|
+
fieldType: "enum",
|
|
10031
|
+
filterable: true,
|
|
10032
|
+
enumValues: ["low", "medium", "high", "critical"],
|
|
10033
|
+
renderHints: { label: "Criticality", widget: "select", order: 5 }
|
|
10034
|
+
},
|
|
10035
|
+
{
|
|
10036
|
+
fieldId: "evidence",
|
|
10037
|
+
fieldType: "string",
|
|
10038
|
+
searchable: true,
|
|
10039
|
+
description: "What enforces or proves the control inline (e.g. an architecture test or gate).",
|
|
10040
|
+
renderHints: { label: "Evidence", widget: "textarea", order: 6 }
|
|
10041
|
+
},
|
|
10042
|
+
{
|
|
10043
|
+
fieldId: "status",
|
|
10044
|
+
fieldType: "enum",
|
|
10045
|
+
filterable: true,
|
|
10046
|
+
enumValues: ["draft", "active", "retired"],
|
|
10047
|
+
renderHints: { label: "Status", widget: "select", order: 7 }
|
|
10048
|
+
},
|
|
10049
|
+
{
|
|
10050
|
+
fieldId: "area",
|
|
10051
|
+
fieldType: "string",
|
|
10052
|
+
filterable: true,
|
|
10053
|
+
renderHints: { label: "Area", widget: "text", order: 8 }
|
|
10054
|
+
},
|
|
10055
|
+
{
|
|
10056
|
+
fieldId: "tags",
|
|
10057
|
+
fieldType: "array",
|
|
10058
|
+
filterable: true,
|
|
10059
|
+
renderHints: { label: "Tags", order: 9 }
|
|
10060
|
+
},
|
|
10061
|
+
{
|
|
10062
|
+
// Cross-surface edge (record → document): the runbook that proves the control.
|
|
10063
|
+
fieldId: "verifiedBy",
|
|
10064
|
+
fieldType: "reference",
|
|
10065
|
+
targetTypeName: "runbook",
|
|
10066
|
+
targetSurface: "document",
|
|
10067
|
+
targetField: "externalId",
|
|
10068
|
+
cardinality: "one",
|
|
10069
|
+
description: "The runbook that verifies this control (by externalId).",
|
|
10070
|
+
renderHints: { label: "Verified by (runbook)", order: 10 }
|
|
10071
|
+
},
|
|
10072
|
+
{
|
|
10073
|
+
// Cross-surface edge (record → document): the decision that mandates it.
|
|
10074
|
+
fieldId: "relatedDecision",
|
|
10075
|
+
fieldType: "reference",
|
|
10076
|
+
targetTypeName: "decision",
|
|
10077
|
+
targetSurface: "document",
|
|
10078
|
+
targetField: "externalId",
|
|
10079
|
+
cardinality: "one",
|
|
10080
|
+
description: "The decision that mandates this control (by externalId).",
|
|
10081
|
+
renderHints: { label: "Related decision", order: 11 }
|
|
10082
|
+
},
|
|
10083
|
+
{
|
|
10084
|
+
fieldId: "reviewedOn",
|
|
10085
|
+
fieldType: "date",
|
|
10086
|
+
description: "ISO-8601 last-reviewed date. Range-queryable.",
|
|
10087
|
+
renderHints: { label: "Reviewed on", widget: "date", order: 12 }
|
|
10088
|
+
}
|
|
10089
|
+
],
|
|
10090
|
+
lookupFields: [
|
|
10091
|
+
"kind",
|
|
10092
|
+
"criticality",
|
|
10093
|
+
"status",
|
|
10094
|
+
"area",
|
|
10095
|
+
"verifiedBy",
|
|
10096
|
+
"relatedDecision",
|
|
10097
|
+
{ fieldName: "reviewedOn", rangeEnabled: true }
|
|
10098
|
+
]
|
|
10099
|
+
},
|
|
10100
|
+
{
|
|
10101
|
+
// A CONVENTION — a must-follow operating rule the team teaches its agents. The
|
|
10102
|
+
// durable, shareable operating-memory. Distinct fields capture how we actually
|
|
10103
|
+
// write these: the `rule` (imperative), the `why` (rationale), and `howToApply`
|
|
10104
|
+
// (the concrete application) are SEPARATE fields, not one blob — so an agent can
|
|
10105
|
+
// recall the rule, the reasoning, and the how independently.
|
|
10106
|
+
typeName: "convention",
|
|
10107
|
+
displayName: "Convention",
|
|
10108
|
+
indexMode: "HYBRID",
|
|
10109
|
+
fields: [
|
|
10110
|
+
{
|
|
10111
|
+
fieldId: "title",
|
|
10112
|
+
fieldType: "string",
|
|
10113
|
+
required: true,
|
|
10114
|
+
searchable: true,
|
|
10115
|
+
validation: { minLength: 1, maxLength: 200 },
|
|
10116
|
+
renderHints: { label: "Title", widget: "text", order: 1, displayField: true }
|
|
10117
|
+
},
|
|
10118
|
+
{
|
|
10119
|
+
fieldId: "rule",
|
|
10120
|
+
fieldType: "string",
|
|
10121
|
+
searchable: true,
|
|
10122
|
+
description: "The convention itself, stated as an imperative.",
|
|
10123
|
+
renderHints: { label: "Rule", widget: "textarea", order: 2 }
|
|
10124
|
+
},
|
|
10125
|
+
{
|
|
10126
|
+
fieldId: "why",
|
|
10127
|
+
fieldType: "string",
|
|
10128
|
+
searchable: true,
|
|
10129
|
+
description: "Why it matters \u2014 the trade-off / the antipattern it prevents.",
|
|
10130
|
+
renderHints: { label: "Why", widget: "textarea", order: 3 }
|
|
10131
|
+
},
|
|
10132
|
+
{
|
|
10133
|
+
fieldId: "howToApply",
|
|
10134
|
+
fieldType: "string",
|
|
10135
|
+
searchable: true,
|
|
10136
|
+
description: "How to comply in practice \u2014 the concrete application steps.",
|
|
10137
|
+
renderHints: { label: "How to apply", widget: "textarea", order: 4 }
|
|
10138
|
+
},
|
|
10139
|
+
{
|
|
10140
|
+
fieldId: "area",
|
|
10141
|
+
fieldType: "string",
|
|
10142
|
+
filterable: true,
|
|
10143
|
+
renderHints: { label: "Area", widget: "text", order: 5 }
|
|
10144
|
+
},
|
|
10145
|
+
{
|
|
10146
|
+
fieldId: "status",
|
|
10147
|
+
fieldType: "enum",
|
|
10148
|
+
filterable: true,
|
|
10149
|
+
enumValues: ["active", "retired"],
|
|
10150
|
+
renderHints: { label: "Status", widget: "select", order: 6 }
|
|
10151
|
+
},
|
|
10152
|
+
{
|
|
10153
|
+
fieldId: "tags",
|
|
10154
|
+
fieldType: "array",
|
|
10155
|
+
filterable: true,
|
|
10156
|
+
renderHints: { label: "Tags", order: 7 }
|
|
10157
|
+
},
|
|
10158
|
+
{
|
|
10159
|
+
// Cross-surface edge (record → document): the decision that established it.
|
|
10160
|
+
fieldId: "establishedBy",
|
|
10161
|
+
fieldType: "reference",
|
|
10162
|
+
targetTypeName: "decision",
|
|
10163
|
+
targetSurface: "document",
|
|
10164
|
+
targetField: "externalId",
|
|
10165
|
+
cardinality: "one",
|
|
10166
|
+
description: "The decision that established this convention (by externalId).",
|
|
10167
|
+
renderHints: { label: "Established by (decision)", order: 8 }
|
|
10168
|
+
},
|
|
10169
|
+
{
|
|
10170
|
+
fieldId: "updatedOn",
|
|
10171
|
+
fieldType: "date",
|
|
10172
|
+
description: "ISO-8601 \u2014 when last revised. Range-queryable.",
|
|
10173
|
+
renderHints: { label: "Updated on", widget: "date", order: 9 }
|
|
10174
|
+
}
|
|
10175
|
+
],
|
|
10176
|
+
lookupFields: ["area", "status", "establishedBy", { fieldName: "updatedOn", rangeEnabled: true }]
|
|
10177
|
+
},
|
|
10178
|
+
{
|
|
10179
|
+
// A GOTCHA / sharp edge: a symptom, its cause, and the fix. A tight typed
|
|
10180
|
+
// triple — found by meaning (semantic search on the symptom) + area/status.
|
|
10181
|
+
// The most standalone type; no typed edge.
|
|
10182
|
+
typeName: "gotcha",
|
|
10183
|
+
displayName: "Gotcha",
|
|
10184
|
+
indexMode: "HYBRID",
|
|
10185
|
+
fields: [
|
|
10186
|
+
{
|
|
10187
|
+
fieldId: "symptom",
|
|
10188
|
+
fieldType: "string",
|
|
10189
|
+
required: true,
|
|
10190
|
+
searchable: true,
|
|
10191
|
+
validation: { minLength: 1, maxLength: 500 },
|
|
10192
|
+
renderHints: { label: "Symptom", widget: "textarea", order: 1, displayField: true }
|
|
10193
|
+
},
|
|
10194
|
+
{
|
|
10195
|
+
fieldId: "cause",
|
|
10196
|
+
fieldType: "string",
|
|
10197
|
+
searchable: true,
|
|
10198
|
+
renderHints: { label: "Cause", widget: "textarea", order: 2 }
|
|
10199
|
+
},
|
|
10200
|
+
{
|
|
10201
|
+
fieldId: "fix",
|
|
10202
|
+
fieldType: "string",
|
|
10203
|
+
searchable: true,
|
|
10204
|
+
renderHints: { label: "Fix", widget: "textarea", order: 3 }
|
|
10205
|
+
},
|
|
10206
|
+
{
|
|
10207
|
+
fieldId: "area",
|
|
10208
|
+
fieldType: "string",
|
|
10209
|
+
filterable: true,
|
|
10210
|
+
renderHints: { label: "Area", widget: "text", order: 4 }
|
|
10211
|
+
},
|
|
10212
|
+
{
|
|
10213
|
+
fieldId: "status",
|
|
10214
|
+
fieldType: "enum",
|
|
10215
|
+
filterable: true,
|
|
10216
|
+
enumValues: ["active", "resolved"],
|
|
10217
|
+
renderHints: { label: "Status", widget: "select", order: 5 }
|
|
10218
|
+
},
|
|
10219
|
+
{
|
|
10220
|
+
fieldId: "tags",
|
|
10221
|
+
fieldType: "array",
|
|
10222
|
+
filterable: true,
|
|
10223
|
+
renderHints: { label: "Tags", order: 6 }
|
|
10224
|
+
},
|
|
10225
|
+
{
|
|
10226
|
+
fieldId: "discoveredOn",
|
|
10227
|
+
fieldType: "date",
|
|
10228
|
+
description: "ISO-8601 \u2014 when first hit. Range-queryable.",
|
|
10229
|
+
renderHints: { label: "Discovered on", widget: "date", order: 7 }
|
|
10230
|
+
}
|
|
10231
|
+
],
|
|
10232
|
+
lookupFields: ["area", "status", { fieldName: "discoveredOn", rangeEnabled: true }]
|
|
10233
|
+
},
|
|
10234
|
+
{
|
|
10235
|
+
// A glossary TERM — a definition keyed by the term itself. Structure-dominant:
|
|
10236
|
+
// `term` is a UNIQUE exact-lookup key (the showcase of a uniqueness constraint),
|
|
10237
|
+
// `definition` is the RAG body, `aliases` catch alternate names. Links to the
|
|
10238
|
+
// decision that defines/establishes the concept where there is one.
|
|
10239
|
+
typeName: "term",
|
|
10240
|
+
displayName: "Glossary term",
|
|
10241
|
+
indexMode: "HYBRID",
|
|
10242
|
+
fields: [
|
|
10243
|
+
{
|
|
10244
|
+
fieldId: "term",
|
|
10245
|
+
fieldType: "string",
|
|
10246
|
+
required: true,
|
|
10247
|
+
searchable: true,
|
|
10248
|
+
validation: { minLength: 1, maxLength: 200 },
|
|
10249
|
+
renderHints: { label: "Term", widget: "text", order: 1, displayField: true }
|
|
10250
|
+
},
|
|
10251
|
+
{
|
|
10252
|
+
fieldId: "definition",
|
|
10253
|
+
fieldType: "string",
|
|
10254
|
+
searchable: true,
|
|
10255
|
+
description: "What the term means \u2014 the RAG-able body.",
|
|
10256
|
+
renderHints: { label: "Definition", widget: "textarea", order: 2 }
|
|
10257
|
+
},
|
|
10258
|
+
{
|
|
10259
|
+
fieldId: "aliases",
|
|
10260
|
+
fieldType: "array",
|
|
10261
|
+
filterable: true,
|
|
10262
|
+
description: "Alternate names / abbreviations for the same concept.",
|
|
10263
|
+
renderHints: { label: "Aliases", order: 3 }
|
|
10264
|
+
},
|
|
10265
|
+
{
|
|
10266
|
+
fieldId: "area",
|
|
10267
|
+
fieldType: "string",
|
|
10268
|
+
filterable: true,
|
|
10269
|
+
renderHints: { label: "Area", widget: "text", order: 4 }
|
|
10270
|
+
},
|
|
10271
|
+
{
|
|
10272
|
+
fieldId: "tags",
|
|
10273
|
+
fieldType: "array",
|
|
10274
|
+
filterable: true,
|
|
10275
|
+
renderHints: { label: "Tags", order: 5 }
|
|
10276
|
+
},
|
|
10277
|
+
{
|
|
10278
|
+
fieldId: "relatedDecision",
|
|
10279
|
+
fieldType: "reference",
|
|
10280
|
+
targetTypeName: "decision",
|
|
10281
|
+
targetSurface: "document",
|
|
10282
|
+
targetField: "externalId",
|
|
10283
|
+
cardinality: "one",
|
|
10284
|
+
description: "A decision that defines or establishes this term (by externalId).",
|
|
10285
|
+
renderHints: { label: "Related decision", order: 6 }
|
|
10286
|
+
},
|
|
10287
|
+
{
|
|
10288
|
+
fieldId: "updatedOn",
|
|
10289
|
+
fieldType: "date",
|
|
10290
|
+
description: "ISO-8601 \u2014 when last revised. Range-queryable.",
|
|
10291
|
+
renderHints: { label: "Updated on", widget: "date", order: 7 }
|
|
10292
|
+
}
|
|
10293
|
+
],
|
|
10294
|
+
// `term` is a UNIQUE equality lookup — exact "define X" + a one-per-term
|
|
10295
|
+
// guarantee. `area`/`relatedDecision` enumerate; `updatedOn` is the range row.
|
|
10296
|
+
lookupFields: [
|
|
10297
|
+
{ fieldName: "term", unique: true },
|
|
10298
|
+
"area",
|
|
10299
|
+
"relatedDecision",
|
|
10300
|
+
{ fieldName: "updatedOn", rangeEnabled: true }
|
|
10301
|
+
]
|
|
10302
|
+
}
|
|
10303
|
+
],
|
|
10304
|
+
// Least-privilege, data-plane only. r/c/u records + search + schema discovery +
|
|
10305
|
+
// inference:r (grounded recall over the document bodies) + document/folder r/c
|
|
10306
|
+
// (the content artifacts are documents). NO :d — knowledge is superseded/retired
|
|
10307
|
+
// via a status flip, so the trail of how the team's thinking evolved stays intact.
|
|
10308
|
+
accessProfile: {
|
|
10309
|
+
allowedActions: [
|
|
10310
|
+
"records:r",
|
|
10311
|
+
"records:c",
|
|
10312
|
+
"records:u",
|
|
10313
|
+
"search:r",
|
|
10314
|
+
"schemas:r",
|
|
10315
|
+
"inference:r",
|
|
10316
|
+
"documents:r",
|
|
10317
|
+
"documents:c",
|
|
10318
|
+
"folders:r",
|
|
10319
|
+
"folders:c"
|
|
10320
|
+
]
|
|
10321
|
+
},
|
|
10322
|
+
servicePrincipal: {
|
|
10323
|
+
externalId: "agentic-sdlc",
|
|
10324
|
+
displayName: "Agentic SDLC Knowledge Base"
|
|
10325
|
+
}
|
|
10326
|
+
// No bundled seed in this version: the content artifacts live on the document
|
|
10327
|
+
// surface and the cross-surface graph is populated by the ingest agent
|
|
10328
|
+
// (document_ingest / record_create), not the bootstrap seed step. Production
|
|
10329
|
+
// contexts provision with `vectros bootstrap --no-seed` regardless.
|
|
10330
|
+
};
|
|
10331
|
+
var agentic_sdlc_default = agenticSdlc;
|
|
10332
|
+
var secondBrain = {
|
|
10333
|
+
name: "second-brain",
|
|
10334
|
+
version: "1.0.0",
|
|
10335
|
+
description: "A personal knowledge base \u2014 capture notes, ideas, and links, then ask them anything.",
|
|
10336
|
+
contextId: "second-brain",
|
|
10337
|
+
contextName: "Second Brain",
|
|
10338
|
+
schemas: [
|
|
10339
|
+
{
|
|
10340
|
+
typeName: "note",
|
|
10341
|
+
displayName: "Note",
|
|
10342
|
+
indexMode: "HYBRID",
|
|
10343
|
+
// keyword on title + semantic on body — recall by meaning
|
|
10344
|
+
fields: [
|
|
10345
|
+
{
|
|
10346
|
+
fieldId: "title",
|
|
10347
|
+
fieldType: "string",
|
|
10348
|
+
required: true,
|
|
10349
|
+
searchable: true,
|
|
10350
|
+
validation: { minLength: 1, maxLength: 200 },
|
|
10351
|
+
renderHints: { label: "Title", widget: "text", order: 1, section: "Note", displayField: true }
|
|
10352
|
+
},
|
|
10353
|
+
{
|
|
10354
|
+
fieldId: "body",
|
|
10355
|
+
fieldType: "string",
|
|
10356
|
+
searchable: true,
|
|
10357
|
+
description: "The note itself \u2014 the RAG-able body you ask questions against.",
|
|
10358
|
+
validation: { maxLength: 2e4 },
|
|
10359
|
+
renderHints: { label: "Body", widget: "textarea", order: 2, section: "Note" }
|
|
10360
|
+
},
|
|
10361
|
+
{
|
|
10362
|
+
fieldId: "tags",
|
|
10363
|
+
fieldType: "array",
|
|
10364
|
+
filterable: true,
|
|
10365
|
+
description: 'Freeform string labels for filtering (e.g. "idea", "reading", "work").',
|
|
10366
|
+
renderHints: { label: "Tags", order: 3, section: "Organize" }
|
|
10367
|
+
},
|
|
10368
|
+
{
|
|
10369
|
+
// An enum (not freeform) so enumeration-by-source is reliable — a
|
|
10370
|
+
// `record_query` lookup on `source` only works if values are consistent.
|
|
10371
|
+
// `other` keeps it from being a straitjacket for a personal brain-dump.
|
|
10372
|
+
fieldId: "source",
|
|
10373
|
+
fieldType: "enum",
|
|
10374
|
+
filterable: true,
|
|
10375
|
+
enumValues: ["thought", "web", "meeting", "book", "article", "other"],
|
|
10376
|
+
description: "Where it came from.",
|
|
10377
|
+
renderHints: { label: "Source", widget: "select", order: 4, section: "Organize" }
|
|
10378
|
+
},
|
|
10379
|
+
{
|
|
10380
|
+
fieldId: "status",
|
|
10381
|
+
fieldType: "enum",
|
|
10382
|
+
filterable: true,
|
|
10383
|
+
enumValues: ["active", "archived"],
|
|
10384
|
+
description: "Archive instead of delete, so nothing is ever lost.",
|
|
10385
|
+
renderHints: { label: "Status", widget: "select", order: 5, section: "Organize" }
|
|
10386
|
+
},
|
|
10387
|
+
{
|
|
10388
|
+
fieldId: "capturedAt",
|
|
10389
|
+
fieldType: "date",
|
|
10390
|
+
description: 'ISO-8601 capture date. Range-queryable \u2014 "notes from last week".',
|
|
10391
|
+
renderHints: { label: "Captured", widget: "date", order: 6, section: "Organize" }
|
|
10392
|
+
}
|
|
10393
|
+
// externalId is the record's FIRST-CLASS identifier (the dedup/upsert key + the
|
|
10394
|
+
// value a `reference` resolves against) — not a payload field; the loader sends
|
|
10395
|
+
// it top-level on the RecordRequest.
|
|
10396
|
+
],
|
|
10397
|
+
// Lookup shapes are MIGRATION-LOCKED once the schema is live (a field cannot
|
|
10398
|
+
// flip equality↔range later), so each is chosen on purpose:
|
|
10399
|
+
// • `source` — EQUALITY, sorted by `lastUpdated`: enumerate notes from one
|
|
10400
|
+
// source, most-recently-touched first. `lastUpdated` is always present, so
|
|
10401
|
+
// the sort never drops a note (sorting an equality lookup by an OPTIONAL
|
|
10402
|
+
// field would silently exclude rows that lack it).
|
|
10403
|
+
// • `status` — EQUALITY: "show archived notes".
|
|
10404
|
+
// • `capturedAt` — RANGE: ordered `from`/`to`/`prefix` ("captured in 2026-06").
|
|
10405
|
+
// `externalId` has a built-in first-class finder — look it up directly, never
|
|
10406
|
+
// redeclare it here. Equality lookups use the 7 fast index slots (2 used here);
|
|
10407
|
+
// range lookups use a relationship row, billed at the range rate.
|
|
10408
|
+
lookupFields: [
|
|
10409
|
+
{ fieldName: "source", sortBy: "lastUpdated" },
|
|
10410
|
+
"status",
|
|
10411
|
+
{ fieldName: "capturedAt", rangeEnabled: true }
|
|
10412
|
+
]
|
|
10413
|
+
}
|
|
10414
|
+
],
|
|
10415
|
+
// Least-privilege profile — r/c/u + search + schema discovery + inference:r
|
|
10416
|
+
// (so the agent can `rag_ask` a grounded, cited question over your notes). No
|
|
10417
|
+
// records:d: notes are archived (status → 'archived'), never deleted.
|
|
10418
|
+
accessProfile: {
|
|
10419
|
+
allowedActions: ["records:r", "records:c", "records:u", "search:r", "schemas:r", "inference:r"]
|
|
10420
|
+
},
|
|
10421
|
+
servicePrincipal: {
|
|
10422
|
+
externalId: "second-brain",
|
|
10423
|
+
displayName: "Second Brain"
|
|
10424
|
+
},
|
|
10425
|
+
seed: [
|
|
10426
|
+
{
|
|
10427
|
+
surface: "record",
|
|
10428
|
+
typeName: "note",
|
|
10429
|
+
externalId: "seed-welcome",
|
|
10430
|
+
fields: {
|
|
10431
|
+
title: "Welcome to your Second Brain",
|
|
10432
|
+
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.',
|
|
10433
|
+
tags: ["getting-started"],
|
|
10434
|
+
source: "thought",
|
|
10435
|
+
status: "active",
|
|
10436
|
+
capturedAt: "2026-06-14"
|
|
10437
|
+
}
|
|
10438
|
+
}
|
|
10439
|
+
]
|
|
10440
|
+
};
|
|
10441
|
+
var second_brain_default = secondBrain;
|
|
10442
|
+
var clinicalIntake = {
|
|
10443
|
+
name: "clinical-intake",
|
|
10444
|
+
version: "1.0.0",
|
|
10445
|
+
description: "Behavioral-health intake with PHI fields \u2014 demonstrates redact-at-write, audit history, blind-index lookup, and search exclusion. Synthetic data only.",
|
|
10446
|
+
contextId: "clinical-intake",
|
|
10447
|
+
contextName: "Clinical Intake",
|
|
10448
|
+
schemas: [
|
|
10449
|
+
{
|
|
10450
|
+
typeName: "intake",
|
|
10451
|
+
displayName: "Intake",
|
|
10452
|
+
indexMode: "HYBRID",
|
|
10453
|
+
capabilities: { auditHistory: true },
|
|
10454
|
+
// self-documenting compliance posture
|
|
10455
|
+
fields: [
|
|
10456
|
+
// --- Non-sensitive, searchable/filterable working fields ---
|
|
10457
|
+
{
|
|
10458
|
+
fieldId: "caseId",
|
|
10459
|
+
fieldType: "string",
|
|
10460
|
+
required: true,
|
|
10461
|
+
description: "Caller-stable intake id; the dedup/lookup key.",
|
|
10462
|
+
validation: { minLength: 1, maxLength: 64 },
|
|
10463
|
+
renderHints: { label: "Case ID", widget: "text", order: 1, section: "Intake", displayField: true }
|
|
10464
|
+
},
|
|
10465
|
+
{
|
|
10466
|
+
fieldId: "presentingConcern",
|
|
10467
|
+
fieldType: "string",
|
|
10468
|
+
searchable: true,
|
|
10469
|
+
description: "Non-PHI summary of the presenting concern \u2014 safe to index + search.",
|
|
10470
|
+
validation: { maxLength: 2e3 },
|
|
10471
|
+
renderHints: { label: "Presenting concern", widget: "textarea", order: 2, section: "Intake" }
|
|
10472
|
+
},
|
|
10473
|
+
{
|
|
10474
|
+
fieldId: "program",
|
|
10475
|
+
fieldType: "string",
|
|
9669
10476
|
filterable: true,
|
|
9670
10477
|
description: "Program / service line the intake is routed to.",
|
|
9671
10478
|
renderHints: { label: "Program", widget: "text", order: 3, section: "Intake" }
|
|
@@ -9749,6 +10556,7 @@ var clinicalIntake = {
|
|
|
9749
10556
|
},
|
|
9750
10557
|
seed: [
|
|
9751
10558
|
{
|
|
10559
|
+
surface: "record",
|
|
9752
10560
|
typeName: "intake",
|
|
9753
10561
|
externalId: "seed-synthetic-intake",
|
|
9754
10562
|
fields: {
|
|
@@ -9770,6 +10578,7 @@ var clinical_intake_default = clinicalIntake;
|
|
|
9770
10578
|
var BUNDLED_BLUEPRINTS = [
|
|
9771
10579
|
task_management_default,
|
|
9772
10580
|
coding_agent_memory_default,
|
|
10581
|
+
agentic_sdlc_default,
|
|
9773
10582
|
second_brain_default,
|
|
9774
10583
|
clinical_intake_default
|
|
9775
10584
|
];
|
|
@@ -10207,7 +11016,8 @@ async function resolveCognitoToken(env, explicit) {
|
|
|
10207
11016
|
async function resolveAuthContext(opts) {
|
|
10208
11017
|
const baseUrl = envToBaseUrl(opts.env, opts.baseUrl ?? process.env.VECTROS_API_BASE_URL);
|
|
10209
11018
|
const cognitoToken = await resolveCognitoToken(opts.env, opts.token);
|
|
10210
|
-
const
|
|
11019
|
+
const tenant = opts.tenant ?? "live";
|
|
11020
|
+
const bridge = opts.provisioning ? await mintBridgeToken({ cognitoToken, apiBaseUrl: baseUrl, tenant }) : await mintContextToken({ cognitoToken, apiBaseUrl: baseUrl, contextId: opts.context ?? "default", tenant });
|
|
10211
11021
|
const client = new VectrosClient({ token: bridge.token, environment: baseUrl });
|
|
10212
11022
|
const identity = await resolveIdentity({ log: opts.log, apiKey: bridge.token, environment: baseUrl });
|
|
10213
11023
|
return {
|
|
@@ -10217,7 +11027,8 @@ async function resolveAuthContext(opts) {
|
|
|
10217
11027
|
baseUrl,
|
|
10218
11028
|
env: opts.env,
|
|
10219
11029
|
identity,
|
|
10220
|
-
tenantId: typeof identity.tenantId === "string" ? identity.tenantId : void 0
|
|
11030
|
+
tenantId: typeof identity.tenantId === "string" ? identity.tenantId : void 0,
|
|
11031
|
+
tenant
|
|
10221
11032
|
};
|
|
10222
11033
|
}
|
|
10223
11034
|
|
|
@@ -10541,39 +11352,6 @@ function assertNoUnresolvedIdentities(blueprint) {
|
|
|
10541
11352
|
);
|
|
10542
11353
|
}
|
|
10543
11354
|
}
|
|
10544
|
-
var SchemaReconcileError = class extends Error {
|
|
10545
|
-
constructor(typeName, cause) {
|
|
10546
|
-
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
10547
|
-
super(
|
|
10548
|
-
`Schema '${typeName}' already exists but could not be reconciled to the blueprint: ${detail}
|
|
10549
|
-
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.`
|
|
10550
|
-
);
|
|
10551
|
-
this.name = "SchemaReconcileError";
|
|
10552
|
-
}
|
|
10553
|
-
};
|
|
10554
|
-
function isAlreadyExistsError(e) {
|
|
10555
|
-
if (typeof e !== "object" || e === null) return false;
|
|
10556
|
-
const err = e;
|
|
10557
|
-
const status = err.statusCode;
|
|
10558
|
-
const text = `${err.message ?? ""} ${err.body?.message ?? ""}`.toLowerCase();
|
|
10559
|
-
return (status === 409 || status === 400) && text.includes("already exists");
|
|
10560
|
-
}
|
|
10561
|
-
async function findExistingSchemaId(client, typeName, owner) {
|
|
10562
|
-
let startFrom;
|
|
10563
|
-
for (; ; ) {
|
|
10564
|
-
const page = await client.schemas.listSchemas({
|
|
10565
|
-
userId: owner.userId,
|
|
10566
|
-
orgId: owner.orgId,
|
|
10567
|
-
limit: 100,
|
|
10568
|
-
startFrom
|
|
10569
|
-
});
|
|
10570
|
-
const match = (page.data ?? []).find((s) => s.typeName === typeName);
|
|
10571
|
-
if (match?.id) return match.id;
|
|
10572
|
-
const next = page.nextCursor;
|
|
10573
|
-
if (!next) return void 0;
|
|
10574
|
-
startFrom = next;
|
|
10575
|
-
}
|
|
10576
|
-
}
|
|
10577
11355
|
async function runLoader(client, input) {
|
|
10578
11356
|
const { blueprint, tenantId, machineId, rotate = false, skipSeed = false, vectrosDir, log } = input;
|
|
10579
11357
|
assertScopeGate(blueprint.accessProfile.allowedActions);
|
|
@@ -10581,14 +11359,16 @@ async function runLoader(client, input) {
|
|
|
10581
11359
|
for (const clause of clauses) assertScopeGate(clause.allowedActions);
|
|
10582
11360
|
}
|
|
10583
11361
|
assertNoUnresolvedIdentities(blueprint);
|
|
10584
|
-
await client.auth.createAppContext({ contextId: blueprint.contextId, name: contextNameOf(blueprint) });
|
|
11362
|
+
await client.auth.createAppContext({ body: { contextId: blueprint.contextId, name: contextNameOf(blueprint) } });
|
|
10585
11363
|
log.debug({ component: "loader", contextId: blueprint.contextId }, "app-context ensured");
|
|
10586
11364
|
const user = await client.identity.createUser({
|
|
10587
|
-
|
|
10588
|
-
|
|
10589
|
-
|
|
10590
|
-
|
|
10591
|
-
|
|
11365
|
+
body: {
|
|
11366
|
+
externalId: blueprint.servicePrincipal.externalId,
|
|
11367
|
+
type: "SERVICE",
|
|
11368
|
+
// SDK 0.18+ (identity-access rework): UserRequest's free-form attribute bag
|
|
11369
|
+
// was renamed `metadata` → `payload`. Same semantics (arbitrary key/value).
|
|
11370
|
+
payload: { displayName: blueprint.servicePrincipal.displayName }
|
|
11371
|
+
}
|
|
10592
11372
|
});
|
|
10593
11373
|
const userId = user.id;
|
|
10594
11374
|
if (!userId) {
|
|
@@ -10623,23 +11403,9 @@ async function runLoader(client, input) {
|
|
|
10623
11403
|
orgId: s.orgId,
|
|
10624
11404
|
clientId: s.clientId
|
|
10625
11405
|
};
|
|
10626
|
-
|
|
10627
|
-
try {
|
|
10628
|
-
resp = await ctxClient.schemas.createSchema(schemaBody);
|
|
10629
|
-
} catch (e) {
|
|
10630
|
-
if (!isAlreadyExistsError(e)) throw e;
|
|
10631
|
-
if (s.clientId) throw e;
|
|
10632
|
-
const existingId = await findExistingSchemaId(ctxClient, s.typeName, { userId: s.userId, orgId: s.orgId });
|
|
10633
|
-
if (!existingId) throw e;
|
|
10634
|
-
try {
|
|
10635
|
-
resp = await ctxClient.schemas.updateSchema({ id: existingId, body: schemaBody });
|
|
10636
|
-
} catch (ue) {
|
|
10637
|
-
throw new SchemaReconcileError(s.typeName, ue);
|
|
10638
|
-
}
|
|
10639
|
-
log.debug({ component: "loader", typeName: s.typeName, schemaId: existingId }, "schema existed; reconciled to declared shape (update)");
|
|
10640
|
-
}
|
|
11406
|
+
const resp = await ctxClient.schemas.createSchema({ upsert: true, body: schemaBody });
|
|
10641
11407
|
if (resp.id) schemaIds[s.typeName] = resp.id;
|
|
10642
|
-
log.debug({ component: "loader", typeName: s.typeName, schemaId: resp.id }, "schema ensured");
|
|
11408
|
+
log.debug({ component: "loader", typeName: s.typeName, schemaId: resp.id }, "schema ensured (upsert)");
|
|
10643
11409
|
}
|
|
10644
11410
|
const profileBody = {
|
|
10645
11411
|
principalId: `usr_${userId}`,
|
|
@@ -10715,25 +11481,40 @@ async function runLoader(client, input) {
|
|
|
10715
11481
|
log.debug({ component: "loader", keyName, keyAction }, "scoped key resolved");
|
|
10716
11482
|
let seededCount = 0;
|
|
10717
11483
|
const seededRecordIds = [];
|
|
11484
|
+
const seededDocumentIds = [];
|
|
10718
11485
|
for (const rec of skipSeed ? [] : blueprint.seed ?? []) {
|
|
10719
11486
|
const schemaId = schemaIds[rec.typeName];
|
|
10720
11487
|
if (!schemaId) {
|
|
10721
11488
|
log.warn({ component: "loader", typeName: rec.typeName }, "seed skipped \u2014 no schemaId for typeName");
|
|
10722
11489
|
continue;
|
|
10723
11490
|
}
|
|
10724
|
-
|
|
10725
|
-
|
|
10726
|
-
|
|
10727
|
-
|
|
10728
|
-
|
|
10729
|
-
|
|
10730
|
-
|
|
10731
|
-
|
|
10732
|
-
|
|
10733
|
-
|
|
10734
|
-
|
|
10735
|
-
|
|
10736
|
-
|
|
11491
|
+
if (rec.surface === "document") {
|
|
11492
|
+
const created = await ctxClient.documents.ingestDocument({
|
|
11493
|
+
body: {
|
|
11494
|
+
title: rec.title,
|
|
11495
|
+
text: rec.text,
|
|
11496
|
+
schemaId,
|
|
11497
|
+
externalId: rec.externalId,
|
|
11498
|
+
// Optional structured payload (the document's `fields`); omitted when absent.
|
|
11499
|
+
payload: rec.fields,
|
|
11500
|
+
// Retain the raw text so a seeded document is retrievable via
|
|
11501
|
+
// GET /v1/documents/{id}/text (mirrors the MCP document_ingest default) —
|
|
11502
|
+
// a seed's text IS its content, worth keeping for the getting-started graph.
|
|
11503
|
+
storeText: true
|
|
11504
|
+
}
|
|
11505
|
+
});
|
|
11506
|
+
if (created.id) seededDocumentIds.push(created.id);
|
|
11507
|
+
} else {
|
|
11508
|
+
const created = await ctxClient.records.createRecord({
|
|
11509
|
+
body: {
|
|
11510
|
+
typeName: rec.typeName,
|
|
11511
|
+
schemaId,
|
|
11512
|
+
externalId: rec.externalId,
|
|
11513
|
+
payload: rec.fields
|
|
11514
|
+
}
|
|
11515
|
+
});
|
|
11516
|
+
if (created.id) seededRecordIds.push(created.id);
|
|
11517
|
+
}
|
|
10737
11518
|
seededCount++;
|
|
10738
11519
|
}
|
|
10739
11520
|
return {
|
|
@@ -10746,7 +11527,8 @@ async function runLoader(client, input) {
|
|
|
10746
11527
|
schemaIds,
|
|
10747
11528
|
roleIds,
|
|
10748
11529
|
seededCount,
|
|
10749
|
-
seededRecordIds
|
|
11530
|
+
seededRecordIds,
|
|
11531
|
+
seededDocumentIds
|
|
10750
11532
|
};
|
|
10751
11533
|
}
|
|
10752
11534
|
function buildPlan(blueprint, opts = {}) {
|
|
@@ -10781,9 +11563,14 @@ function buildPlan(blueprint, opts = {}) {
|
|
|
10781
11563
|
});
|
|
10782
11564
|
}
|
|
10783
11565
|
steps.push({ kind: "key", summary: `key ${blueprint.name}-<machine> (mint 1 scoped ssk_*)` });
|
|
10784
|
-
const
|
|
10785
|
-
if (
|
|
10786
|
-
|
|
11566
|
+
const seeds = blueprint.seed ?? [];
|
|
11567
|
+
if (seeds.length > 0 && !opts.noSeed) {
|
|
11568
|
+
const docs = seeds.filter((s) => s.surface === "document").length;
|
|
11569
|
+
const recs = seeds.length - docs;
|
|
11570
|
+
const parts = [];
|
|
11571
|
+
if (recs > 0) parts.push(`${recs} record${recs === 1 ? "" : "s"}`);
|
|
11572
|
+
if (docs > 0) parts.push(`${docs} document${docs === 1 ? "" : "s"}`);
|
|
11573
|
+
steps.push({ kind: "seed", summary: `seed ${parts.join(" + ")}` });
|
|
10787
11574
|
}
|
|
10788
11575
|
return steps;
|
|
10789
11576
|
}
|
|
@@ -10909,6 +11696,7 @@ function parseBootstrapArgs(argv) {
|
|
|
10909
11696
|
blankSlate: false,
|
|
10910
11697
|
noSeed: false,
|
|
10911
11698
|
client: "desktop",
|
|
11699
|
+
tenant: "live",
|
|
10912
11700
|
help: false
|
|
10913
11701
|
};
|
|
10914
11702
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -10959,6 +11747,17 @@ function parseBootstrapArgs(argv) {
|
|
|
10959
11747
|
case "--no-seed":
|
|
10960
11748
|
args.noSeed = true;
|
|
10961
11749
|
break;
|
|
11750
|
+
case "--tenant": {
|
|
11751
|
+
const v = argv[++i];
|
|
11752
|
+
if (v !== "test" && v !== "live") {
|
|
11753
|
+
throw new Error(`--tenant must be 'test' or 'live' (got '${v ?? ""}')`);
|
|
11754
|
+
}
|
|
11755
|
+
args.tenant = v;
|
|
11756
|
+
break;
|
|
11757
|
+
}
|
|
11758
|
+
case "--test":
|
|
11759
|
+
args.tenant = "test";
|
|
11760
|
+
break;
|
|
10962
11761
|
case "--help":
|
|
10963
11762
|
case "-h":
|
|
10964
11763
|
args.help = true;
|
|
@@ -10987,7 +11786,8 @@ var BOOTSTRAP_HELP = `vectros bootstrap \u2014 provision a least-privilege MCP c
|
|
|
10987
11786
|
|
|
10988
11787
|
Usage:
|
|
10989
11788
|
vectros bootstrap [--blueprint <name> | --blank] [--no-seed] [--env staging|production]
|
|
10990
|
-
[--
|
|
11789
|
+
[--tenant test|live | --test] [--client desktop|code]
|
|
11790
|
+
[--yes] [--print] [--rotate]
|
|
10991
11791
|
[--token <t> | env VECTROS_BOOTSTRAP_TOKEN]
|
|
10992
11792
|
[--base-url <url>] [--use-scoped-token]
|
|
10993
11793
|
|
|
@@ -11001,6 +11801,12 @@ Options:
|
|
|
11001
11801
|
(a blueprint's seeds are illustrative, not real data). Unlike
|
|
11002
11802
|
--blank, the schemas are still created.
|
|
11003
11803
|
--env staging|production Target environment (default: production).
|
|
11804
|
+
--tenant test|live Which tenant the minted key scopes to (default: live).
|
|
11805
|
+
'test' provisions into your isolated test tenant \u2014 a
|
|
11806
|
+
dry-run before pointing real data at the same blueprint.
|
|
11807
|
+
The tenant rides the key (ssk_test_* vs ssk_live_*), so
|
|
11808
|
+
the MCP/client config is otherwise identical.
|
|
11809
|
+
--test Shorthand for --tenant test.
|
|
11004
11810
|
--base-url <url> Override the API base URL (beats --env + VECTROS_API_BASE_URL).
|
|
11005
11811
|
--client <desktop|code> MCP host to configure (default: desktop). 'desktop' merges
|
|
11006
11812
|
claude_desktop_config.json (also works for Cursor/Cline);
|
|
@@ -11069,6 +11875,21 @@ async function resolveBridgeCognitoToken(args, prompt, isTty, resolveCognito = r
|
|
|
11069
11875
|
if (!pasted) throw new Error("No token entered.");
|
|
11070
11876
|
return pasted;
|
|
11071
11877
|
}
|
|
11878
|
+
function buildRemintForContext(opts) {
|
|
11879
|
+
const mintContext = opts.mintContext ?? mintContextToken;
|
|
11880
|
+
return async (contextId) => {
|
|
11881
|
+
const ctxToken = await mintContext({
|
|
11882
|
+
cognitoToken: opts.cognitoToken,
|
|
11883
|
+
apiBaseUrl: opts.apiBaseUrl,
|
|
11884
|
+
contextId,
|
|
11885
|
+
tenant: opts.tenant
|
|
11886
|
+
});
|
|
11887
|
+
return new VectrosClient({
|
|
11888
|
+
token: ctxToken.token,
|
|
11889
|
+
environment: opts.apiBaseUrl
|
|
11890
|
+
});
|
|
11891
|
+
};
|
|
11892
|
+
}
|
|
11072
11893
|
async function runBootstrapCli(argv, deps = {}) {
|
|
11073
11894
|
const log = deps.log ?? createLogger2();
|
|
11074
11895
|
let args;
|
|
@@ -11150,12 +11971,17 @@ ${renderPlan(blueprint, planSteps)}
|
|
|
11150
11971
|
isTty,
|
|
11151
11972
|
deps.resolveCognitoToken
|
|
11152
11973
|
);
|
|
11153
|
-
const
|
|
11974
|
+
const mintBridge = deps.mintBridgeToken ?? mintBridgeToken;
|
|
11975
|
+
const mintContext = deps.mintContextToken ?? mintContextToken;
|
|
11976
|
+
const bridge = await mintBridge({
|
|
11154
11977
|
cognitoToken,
|
|
11155
11978
|
apiBaseUrl: baseUrl,
|
|
11156
|
-
useScopedToken: args.useScopedToken
|
|
11979
|
+
useScopedToken: args.useScopedToken,
|
|
11980
|
+
// Tenant rides the key (ssk_test_* vs ssk_live_*), not the base URL.
|
|
11981
|
+
tenant: args.tenant
|
|
11157
11982
|
});
|
|
11158
|
-
process.stdout.write(
|
|
11983
|
+
process.stdout.write(` \u2714 Bootstrap token minted (5-min TTL, ${args.tenant} tenant)
|
|
11984
|
+
`);
|
|
11159
11985
|
const client = new VectrosClient({ token: bridge.token, environment: baseUrl });
|
|
11160
11986
|
const identity = await resolveIdentity({ log, apiKey: bridge.token, environment: baseUrl });
|
|
11161
11987
|
const tenantId = identity.tenantId;
|
|
@@ -11171,17 +11997,16 @@ ${renderPlan(blueprint, planSteps)}
|
|
|
11171
11997
|
machineId,
|
|
11172
11998
|
rotate: args.rotate,
|
|
11173
11999
|
skipSeed: noSeed,
|
|
11174
|
-
remintForContext:
|
|
11175
|
-
|
|
11176
|
-
|
|
11177
|
-
|
|
11178
|
-
|
|
11179
|
-
|
|
11180
|
-
},
|
|
12000
|
+
remintForContext: buildRemintForContext({
|
|
12001
|
+
cognitoToken,
|
|
12002
|
+
apiBaseUrl: baseUrl,
|
|
12003
|
+
tenant: args.tenant,
|
|
12004
|
+
mintContext
|
|
12005
|
+
}),
|
|
11181
12006
|
log
|
|
11182
12007
|
});
|
|
11183
12008
|
process.stdout.write(
|
|
11184
|
-
` \u2714 AccessProfile + key (${result.keyAction}) \u2192 ${result.keyName}
|
|
12009
|
+
` \u2714 AccessProfile + ${args.tenant} key (${result.keyAction}) \u2192 ${result.keyName}
|
|
11185
12010
|
` + seedSummaryLine({
|
|
11186
12011
|
noSeed,
|
|
11187
12012
|
declinedInteractively: seedDeclinedInteractively,
|
|
@@ -11397,7 +12222,8 @@ servicePrincipal:
|
|
|
11397
12222
|
displayName: ${name}
|
|
11398
12223
|
|
|
11399
12224
|
seed:
|
|
11400
|
-
-
|
|
12225
|
+
- surface: record # 'record' (createRecord) or 'document' (text-ingest: needs title + text)
|
|
12226
|
+
typeName: thing
|
|
11401
12227
|
externalId: seed-welcome # stable \u2192 idempotent; re-apply converges
|
|
11402
12228
|
fields: { title: "Hello from ${name}" }
|
|
11403
12229
|
`;
|
|
@@ -11745,6 +12571,7 @@ async function withAuth(opts, deps, body) {
|
|
|
11745
12571
|
token: opts.token,
|
|
11746
12572
|
context: opts.context,
|
|
11747
12573
|
provisioning: opts.provisioning,
|
|
12574
|
+
tenant: opts.tenant,
|
|
11748
12575
|
useScopedToken: opts.useScopedToken,
|
|
11749
12576
|
log
|
|
11750
12577
|
});
|
|
@@ -12021,19 +12848,23 @@ async function createIdentity(client, opts, payload) {
|
|
|
12021
12848
|
switch (opts.type) {
|
|
12022
12849
|
case "user":
|
|
12023
12850
|
return client.identity.createUser({
|
|
12024
|
-
|
|
12025
|
-
|
|
12026
|
-
|
|
12027
|
-
|
|
12851
|
+
body: {
|
|
12852
|
+
externalId,
|
|
12853
|
+
email: opts.email,
|
|
12854
|
+
type: opts.service ? "SERVICE" : "HUMAN",
|
|
12855
|
+
payload
|
|
12856
|
+
}
|
|
12028
12857
|
});
|
|
12029
12858
|
case "org":
|
|
12030
|
-
return client.identity.createOrg({ externalId, name: opts.name, payload });
|
|
12859
|
+
return client.identity.createOrg({ body: { externalId, name: opts.name, payload } });
|
|
12031
12860
|
case "client":
|
|
12032
12861
|
return client.identity.createClient({
|
|
12033
|
-
|
|
12034
|
-
|
|
12035
|
-
|
|
12036
|
-
|
|
12862
|
+
body: {
|
|
12863
|
+
externalId,
|
|
12864
|
+
name: opts.name,
|
|
12865
|
+
orgId: opts.org,
|
|
12866
|
+
payload
|
|
12867
|
+
}
|
|
12037
12868
|
});
|
|
12038
12869
|
}
|
|
12039
12870
|
}
|
|
@@ -12474,8 +13305,10 @@ async function runContextCreate(opts, deps = {}) {
|
|
|
12474
13305
|
const client = auth.client;
|
|
12475
13306
|
try {
|
|
12476
13307
|
const ctx = await client.auth.createAppContext({
|
|
12477
|
-
|
|
12478
|
-
|
|
13308
|
+
body: {
|
|
13309
|
+
contextId: opts.contextId,
|
|
13310
|
+
name: opts.name ?? opts.contextId
|
|
13311
|
+
}
|
|
12479
13312
|
});
|
|
12480
13313
|
if (opts.json) {
|
|
12481
13314
|
process.stdout.write(`${formatJson(ctx)}
|
|
@@ -12786,23 +13619,22 @@ async function runAssertions(client, deps, blueprint, tenantId, result) {
|
|
|
12786
13619
|
}
|
|
12787
13620
|
}
|
|
12788
13621
|
for (const rec of blueprint.seed ?? []) {
|
|
13622
|
+
const isDoc = rec.surface === "document";
|
|
13623
|
+
const op = isDoc ? "lookupDocuments" : "lookupRecords";
|
|
13624
|
+
const noun = isDoc ? "document" : "record";
|
|
12789
13625
|
try {
|
|
12790
|
-
const page = await client.records.lookupRecords({
|
|
12791
|
-
type: rec.typeName,
|
|
12792
|
-
field: "externalId",
|
|
12793
|
-
value: rec.externalId
|
|
12794
|
-
});
|
|
13626
|
+
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 });
|
|
12795
13627
|
const ok = (page.data ?? []).length >= 1;
|
|
12796
13628
|
assertions.push({
|
|
12797
13629
|
name: `seed-externalId:${rec.externalId}`,
|
|
12798
13630
|
ok,
|
|
12799
|
-
detail: ok ? `'${rec.typeName}' seed resolves by externalId '${rec.externalId}'` : `'${rec.typeName}' seed externalId '${rec.externalId}' resolved 0
|
|
13631
|
+
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?)`
|
|
12800
13632
|
});
|
|
12801
13633
|
} catch (e) {
|
|
12802
13634
|
assertions.push({
|
|
12803
13635
|
name: `seed-externalId:${rec.externalId}`,
|
|
12804
13636
|
ok: false,
|
|
12805
|
-
detail:
|
|
13637
|
+
detail: `${op}(externalId='${rec.externalId}') failed: ${errMsg(e)}`
|
|
12806
13638
|
});
|
|
12807
13639
|
}
|
|
12808
13640
|
}
|
|
@@ -12860,6 +13692,14 @@ async function runTeardown(client, blueprint, machineId, snapshot, result) {
|
|
|
12860
13692
|
steps.push({ step: `delete record ${id}`, ok: false, error: errMsg(e) });
|
|
12861
13693
|
}
|
|
12862
13694
|
}
|
|
13695
|
+
for (const id of result.seededDocumentIds) {
|
|
13696
|
+
try {
|
|
13697
|
+
await client.documents.deleteDocument({ id });
|
|
13698
|
+
steps.push({ step: `delete document ${id}`, ok: true });
|
|
13699
|
+
} catch (e) {
|
|
13700
|
+
steps.push({ step: `delete document ${id}`, ok: false, error: errMsg(e) });
|
|
13701
|
+
}
|
|
13702
|
+
}
|
|
12863
13703
|
const principalId = `usr_${result.servicePrincipalUserId}`;
|
|
12864
13704
|
if (snapshot.failed) {
|
|
12865
13705
|
steps.push({ step: `delete access profile ${principalId}`, ok: true, skipped: true, error: "skipped \u2014 pre-apply snapshot failed; cannot prove ownership" });
|
|
@@ -12994,7 +13834,7 @@ function renderReport(blueprint, env, report) {
|
|
|
12994
13834
|
if (report.applied) {
|
|
12995
13835
|
const a = report.applied;
|
|
12996
13836
|
lines.push(
|
|
12997
|
-
` \u2714 applied \u2014 key ${a.keyName} (${a.keyAction}), ${Object.keys(a.schemaIds).length} schema(s)` + (a.seededCount ? `, ${a.seededCount} seed
|
|
13837
|
+
` \u2714 applied \u2014 key ${a.keyName} (${a.keyAction}), ${Object.keys(a.schemaIds).length} schema(s)` + (a.seededCount ? `, ${a.seededCount} seed(s)` : "")
|
|
12998
13838
|
);
|
|
12999
13839
|
} else {
|
|
13000
13840
|
lines.push(` \u2716 apply failed \u2014 ${report.error ?? "unknown error"}`);
|
|
@@ -13057,7 +13897,10 @@ async function runBlueprintTest(fileOrName, opts, deps = {}) {
|
|
|
13057
13897
|
const ctxToken = await mintContextToken({
|
|
13058
13898
|
cognitoToken: auth.cognitoToken,
|
|
13059
13899
|
apiBaseUrl: auth.baseUrl,
|
|
13060
|
-
contextId
|
|
13900
|
+
contextId,
|
|
13901
|
+
// Same tenant as the bootstrap token (auth.tenant) — the in-context
|
|
13902
|
+
// load must land in the tenant the run targets.
|
|
13903
|
+
tenant: auth.tenant
|
|
13061
13904
|
});
|
|
13062
13905
|
return new VectrosClient({
|
|
13063
13906
|
token: ctxToken.token,
|
|
@@ -13095,9 +13938,9 @@ async function runBlueprintTest(fileOrName, opts, deps = {}) {
|
|
|
13095
13938
|
|
|
13096
13939
|
// src/build-info.ts
|
|
13097
13940
|
var BUILD_INFO = {
|
|
13098
|
-
cli: true ? "0.6.
|
|
13099
|
-
sdk: true ? "0.
|
|
13100
|
-
blueprints: true ? "0.
|
|
13941
|
+
cli: true ? "0.6.4" : "dev",
|
|
13942
|
+
sdk: true ? "0.31.0" : "dev",
|
|
13943
|
+
blueprints: true ? "0.6.2" : "dev"
|
|
13101
13944
|
};
|
|
13102
13945
|
function formatBuildInfo() {
|
|
13103
13946
|
return `vectros ${BUILD_INFO.cli} (sdk ${BUILD_INFO.sdk}, blueprints ${BUILD_INFO.blueprints})`;
|
|
@@ -13145,10 +13988,21 @@ function buildProgram(deps = {}) {
|
|
|
13145
13988
|
addCommonAuthOptions(whoami).action(async (flags) => {
|
|
13146
13989
|
process.exit(await runWhoami(toCommonOptions(flags), deps));
|
|
13147
13990
|
});
|
|
13148
|
-
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");
|
|
13991
|
+
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)");
|
|
13149
13992
|
addCommonAuthOptions(blueprintTest).action(
|
|
13150
13993
|
async (file, flags) => {
|
|
13151
|
-
|
|
13994
|
+
if (flags.tenant !== void 0 && flags.tenant !== "test" && flags.tenant !== "live") {
|
|
13995
|
+
process.stderr.write(` \u2716 --tenant must be 'test' or 'live' (got '${flags.tenant}')
|
|
13996
|
+
`);
|
|
13997
|
+
process.exit(2);
|
|
13998
|
+
}
|
|
13999
|
+
process.exit(
|
|
14000
|
+
await runBlueprintTest(
|
|
14001
|
+
file,
|
|
14002
|
+
{ ...toCommonOptions(flags), keep: Boolean(flags.keep), tenant: flags.tenant },
|
|
14003
|
+
deps
|
|
14004
|
+
)
|
|
14005
|
+
);
|
|
13152
14006
|
}
|
|
13153
14007
|
);
|
|
13154
14008
|
registerKeyCommands(program, deps);
|