@vectros-ai/cli 0.6.1 → 0.6.5

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