@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.mjs CHANGED
@@ -2838,7 +2838,7 @@ var AuthClient = class {
2838
2838
  );
2839
2839
  }
2840
2840
  /**
2841
- * Creates a new access profile under the given app context. This call is idempotent by `principalId`: if a profile with the same `principalId` already exists, the existing profile is returned (with status 200) instead of creating a duplicate. You must provide exactly one of `scopes` (an inline list of scopes) or `roleId` (a reference to a role); supplying both, or neither, is rejected. `identityOverrides` may set only `orgId` and `clientId`; any other key (including the account identifier or `userId`) is rejected. If you use a scoped credential, the profile's effective scopes may not exceed your own; a root API key (`sk_`) is exempt. Requires the `profiles:c` scope.
2841
+ * Creates a new access profile under the given app context. This call is idempotent by `principalId`: if a profile with the same `principalId` already exists, the existing profile is returned (with status 200) instead of creating a duplicate. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing profile was returned) tells the two apart. To overwrite an existing profile's `scopes`/`roleId`, `identityOverrides`, and `status` instead of returning it unchanged, set `?upsert=true` (this also requires the `profiles:u` scope). You must provide exactly one of `scopes` (an inline list of scopes) or `roleId` (a reference to a role); supplying both, or neither, is rejected. `identityOverrides` may set only `orgId` and `clientId`; any other key (including the account identifier or `userId`) is rejected. If you use a scoped credential, the profile's effective scopes may not exceed your own; a root API key (`sk_`) is exempt. Requires the `profiles:c` scope.
2842
2842
  *
2843
2843
  * @param {Vectros.CreateAccessProfileRequest} request
2844
2844
  * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -2860,7 +2860,10 @@ var AuthClient = class {
2860
2860
  return HttpResponsePromise.fromPromise(this.__createAccessProfile(request, requestOptions));
2861
2861
  }
2862
2862
  async __createAccessProfile(request, requestOptions) {
2863
- const { contextId, body: _body } = request;
2863
+ const { contextId, upsert, body: _body } = request;
2864
+ const _queryParams = {
2865
+ upsert
2866
+ };
2864
2867
  const _authRequest = await this._options.authProvider.getAuthRequest();
2865
2868
  const _headers = mergeHeaders(
2866
2869
  _authRequest.headers,
@@ -2875,7 +2878,7 @@ var AuthClient = class {
2875
2878
  method: "POST",
2876
2879
  headers: _headers,
2877
2880
  contentType: "application/json",
2878
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
2881
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
2879
2882
  requestType: "json",
2880
2883
  body: _body,
2881
2884
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
@@ -2970,9 +2973,9 @@ var AuthClient = class {
2970
2973
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/app-contexts");
2971
2974
  }
2972
2975
  /**
2973
- * Creates a new app context. This call is idempotent by `contextId`: if an app context with the same `contextId` already exists, the existing app context is returned (with status 200) instead of creating a duplicate. The reserved `contextId` value `vectros-admin` cannot be created through this endpoint; it is provisioned automatically for your account. Requires the `app-contexts:c` scope.
2976
+ * Creates a new app context. This call is idempotent by `contextId`: if an app context with the same `contextId` already exists, the existing app context is returned (with status 200) instead of creating a duplicate. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing context was returned) tells the two apart. To overwrite an existing context's `name`/`description` instead of returning it unchanged, set `?upsert=true` (this also requires the `app-contexts:u` scope). The reserved `contextId` value `vectros-admin` cannot be created through this endpoint; it is provisioned automatically for your account. Requires the `app-contexts:c` scope.
2974
2977
  *
2975
- * @param {Vectros.AppContextRequest} request
2978
+ * @param {Vectros.CreateAppContextRequest} request
2976
2979
  * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
2977
2980
  *
2978
2981
  * @throws {@link Vectros.BadRequestError}
@@ -2981,14 +2984,20 @@ var AuthClient = class {
2981
2984
  *
2982
2985
  * @example
2983
2986
  * await client.auth.createAppContext({
2984
- * contextId: "myapp",
2985
- * name: "My Internal App"
2987
+ * body: {
2988
+ * contextId: "myapp",
2989
+ * name: "My Internal App"
2990
+ * }
2986
2991
  * })
2987
2992
  */
2988
2993
  createAppContext(request, requestOptions) {
2989
2994
  return HttpResponsePromise.fromPromise(this.__createAppContext(request, requestOptions));
2990
2995
  }
2991
2996
  async __createAppContext(request, requestOptions) {
2997
+ const { upsert, body: _body } = request;
2998
+ const _queryParams = {
2999
+ upsert
3000
+ };
2992
3001
  const _authRequest = await this._options.authProvider.getAuthRequest();
2993
3002
  const _headers = mergeHeaders(
2994
3003
  _authRequest.headers,
@@ -3003,9 +3012,9 @@ var AuthClient = class {
3003
3012
  method: "POST",
3004
3013
  headers: _headers,
3005
3014
  contentType: "application/json",
3006
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
3015
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
3007
3016
  requestType: "json",
3008
- body: request,
3017
+ body: _body,
3009
3018
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
3010
3019
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
3011
3020
  abortSignal: requestOptions?.abortSignal,
@@ -3101,7 +3110,7 @@ var AuthClient = class {
3101
3110
  );
3102
3111
  }
3103
3112
  /**
3104
- * Creates a new role under the given app context. This call is idempotent by `roleId`: if a role with the same `roleId` already exists, the existing role is returned (with status 200) instead of creating a duplicate. If you use a scoped credential, the role's scopes may not exceed your own; a root API key (`sk_`) is exempt. Requires the `profiles:c` scope.
3113
+ * Creates a new role under the given app context. This call is idempotent by `roleId`: if a role with the same `roleId` already exists, the existing role is returned (with status 200) instead of creating a duplicate. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing role was returned) tells the two apart. To overwrite an existing role's `name`/`description`/`scopes` instead of returning it unchanged, set `?upsert=true` (this also requires the `profiles:u` scope). If you use a scoped credential, the role's scopes may not exceed your own; a root API key (`sk_`) is exempt. Requires the `profiles:c` scope.
3105
3114
  *
3106
3115
  * @param {Vectros.CreateRoleRequest} request
3107
3116
  * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -3127,7 +3136,10 @@ var AuthClient = class {
3127
3136
  return HttpResponsePromise.fromPromise(this.__createRole(request, requestOptions));
3128
3137
  }
3129
3138
  async __createRole(request, requestOptions) {
3130
- const { contextId, body: _body } = request;
3139
+ const { contextId, upsert, body: _body } = request;
3140
+ const _queryParams = {
3141
+ upsert
3142
+ };
3131
3143
  const _authRequest = await this._options.authProvider.getAuthRequest();
3132
3144
  const _headers = mergeHeaders(
3133
3145
  _authRequest.headers,
@@ -3142,7 +3154,7 @@ var AuthClient = class {
3142
3154
  method: "POST",
3143
3155
  headers: _headers,
3144
3156
  contentType: "application/json",
3145
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
3157
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
3146
3158
  requestType: "json",
3147
3159
  body: _body,
3148
3160
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
@@ -4626,23 +4638,30 @@ var DocumentsClient = class {
4626
4638
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/documents");
4627
4639
  }
4628
4640
  /**
4629
- * Creates a document from a raw text string and queues it for asynchronous indexing so it becomes searchable. Requires the `documents:c` scope.
4641
+ * Creates a document from a raw text string and queues it for asynchronous indexing so it becomes searchable. Optionally supply an `externalId` to make the create idempotent — if a document with the same `externalId` already exists in your context, that existing document is returned unchanged instead of a duplicate being created. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing document was returned) tells the two apart. To overwrite an existing document's content instead of returning it unchanged, set `?upsert=true` (this also requires the `documents:u` scope). Requires the `documents:c` scope.
4630
4642
  *
4631
- * @param {Vectros.DocumentRequest} request
4643
+ * @param {Vectros.IngestDocumentRequest} request
4632
4644
  * @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
4633
4645
  *
4634
4646
  * @throws {@link Vectros.BadRequestError}
4647
+ * @throws {@link Vectros.ForbiddenError}
4635
4648
  * @throws {@link Vectros.TooManyRequestsError}
4636
4649
  *
4637
4650
  * @example
4638
4651
  * await client.documents.ingestDocument({
4639
- * title: "Patient Intake Form \u2014 Jane Doe"
4652
+ * body: {
4653
+ * title: "Patient Intake Form \u2014 Jane Doe"
4654
+ * }
4640
4655
  * })
4641
4656
  */
4642
4657
  ingestDocument(request, requestOptions) {
4643
4658
  return HttpResponsePromise.fromPromise(this.__ingestDocument(request, requestOptions));
4644
4659
  }
4645
4660
  async __ingestDocument(request, requestOptions) {
4661
+ const { upsert, body: _body } = request;
4662
+ const _queryParams = {
4663
+ upsert
4664
+ };
4646
4665
  const _authRequest = await this._options.authProvider.getAuthRequest();
4647
4666
  const _headers = mergeHeaders(
4648
4667
  _authRequest.headers,
@@ -4657,9 +4676,9 @@ var DocumentsClient = class {
4657
4676
  method: "POST",
4658
4677
  headers: _headers,
4659
4678
  contentType: "application/json",
4660
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
4679
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
4661
4680
  requestType: "json",
4662
- body: request,
4681
+ body: _body,
4663
4682
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
4664
4683
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
4665
4684
  abortSignal: requestOptions?.abortSignal,
@@ -4673,6 +4692,8 @@ var DocumentsClient = class {
4673
4692
  switch (_response.error.statusCode) {
4674
4693
  case 400:
4675
4694
  throw new BadRequestError(_response.error.body, _response.rawResponse);
4695
+ case 403:
4696
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
4676
4697
  case 429:
4677
4698
  throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
4678
4699
  default:
@@ -4939,7 +4960,7 @@ var DocumentsClient = class {
4939
4960
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "PATCH", "/v1/documents/{id}");
4940
4961
  }
4941
4962
  /**
4942
- * Finds documents of a given type by 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.
4963
+ * Finds documents of a given type by field value. Supported fields: `externalId` (the document's first-class external identifier — no schema declaration required) and any field declared as a lookup field on the bound schema. A lookup on a sensitive field is rejected here because the value would appear in the URL query string; use POST /v1/documents/lookup (the request-body variant) for a sensitive field instead. Results are paginated: set `limit` for the page size and feed the returned `nextCursor` back as `startFrom` to fetch the next page. The response is a `{data, nextCursor}` envelope. Requires the `documents:r` scope.
4943
4964
  *
4944
4965
  * @param {Vectros.LookupDocumentsRequest} request
4945
4966
  * @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -5234,12 +5255,13 @@ var DocumentsClient = class {
5234
5255
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/documents/{id}/versions");
5235
5256
  }
5236
5257
  /**
5237
- * Starts a file-based document by returning a short-lived presigned S3 PUT URL. Upload the file bytes directly to `uploadUrl`; the document is then automatically queued for text extraction and asynchronous indexing. Requires the `documents:c` scope.
5258
+ * Starts a file-based document by returning a short-lived presigned S3 PUT URL. Upload the file bytes directly to `uploadUrl`; the document is then automatically queued for text extraction and asynchronous indexing. Supplying an `externalId` makes this idempotent — re-initiating an upload with the same `externalId` re-issues a fresh presigned URL to the SAME existing document/object (so a re-upload inherently replaces the file body) rather than creating a duplicate. The response's `created` field (and the HTTP status — 201 when a new document was minted, 200 when an existing one was matched) tells the two apart. With `?upsert=true`, the submitted `payload`/`title` are also applied to the matched document (file-body divergence cannot be diffed at upload-init — the bytes have not arrived yet — so the re-upload itself replaces the body; `?upsert=true` requires the `documents:u` scope). Requires the `documents:c` scope.
5238
5259
  *
5239
5260
  * @param {Vectros.FileUploadRequest} request
5240
5261
  * @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
5241
5262
  *
5242
5263
  * @throws {@link Vectros.BadRequestError}
5264
+ * @throws {@link Vectros.ForbiddenError}
5243
5265
  * @throws {@link Vectros.TooManyRequestsError}
5244
5266
  *
5245
5267
  * @example
@@ -5252,6 +5274,10 @@ var DocumentsClient = class {
5252
5274
  return HttpResponsePromise.fromPromise(this.__uploadDocument(request, requestOptions));
5253
5275
  }
5254
5276
  async __uploadDocument(request, requestOptions) {
5277
+ const { upsert, ..._body } = request;
5278
+ const _queryParams = {
5279
+ upsert
5280
+ };
5255
5281
  const _authRequest = await this._options.authProvider.getAuthRequest();
5256
5282
  const _headers = mergeHeaders(
5257
5283
  _authRequest.headers,
@@ -5266,9 +5292,9 @@ var DocumentsClient = class {
5266
5292
  method: "POST",
5267
5293
  headers: _headers,
5268
5294
  contentType: "application/json",
5269
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
5295
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
5270
5296
  requestType: "json",
5271
- body: request,
5297
+ body: _body,
5272
5298
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
5273
5299
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
5274
5300
  abortSignal: requestOptions?.abortSignal,
@@ -5282,6 +5308,8 @@ var DocumentsClient = class {
5282
5308
  switch (_response.error.statusCode) {
5283
5309
  case 400:
5284
5310
  throw new BadRequestError(_response.error.body, _response.rawResponse);
5311
+ case 403:
5312
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
5285
5313
  case 429:
5286
5314
  throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
5287
5315
  default:
@@ -5360,23 +5388,30 @@ var FoldersClient = class {
5360
5388
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/folders");
5361
5389
  }
5362
5390
  /**
5363
- * Creates a folder to organize your documents and records. If `parentFolderId` is omitted, the folder is created under your context's default root folder. Requires the `folders:c` scope.
5391
+ * Creates a folder to organize your documents and records. If `parentFolderId` is omitted, the folder is created under your context's default root folder. Folder creation is idempotent by (slug + parent): if a folder with the same slug already exists under the same parent, that existing folder is returned unchanged instead of a duplicate being created. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing folder was returned) tells the two apart. To overwrite an existing folder's mutable fields instead of returning it unchanged, set `?upsert=true` (this also requires the `folders:u` scope). Requires the `folders:c` scope.
5364
5392
  *
5365
- * @param {Vectros.FolderRequest} request
5393
+ * @param {Vectros.CreateFolderRequest} request
5366
5394
  * @param {FoldersClient.RequestOptions} requestOptions - Request-specific configuration.
5367
5395
  *
5368
5396
  * @throws {@link Vectros.BadRequestError}
5397
+ * @throws {@link Vectros.ForbiddenError}
5369
5398
  * @throws {@link Vectros.TooManyRequestsError}
5370
5399
  *
5371
5400
  * @example
5372
5401
  * await client.folders.createFolder({
5373
- * name: "Patient Records 2024"
5402
+ * body: {
5403
+ * name: "Patient Records 2024"
5404
+ * }
5374
5405
  * })
5375
5406
  */
5376
5407
  createFolder(request, requestOptions) {
5377
5408
  return HttpResponsePromise.fromPromise(this.__createFolder(request, requestOptions));
5378
5409
  }
5379
5410
  async __createFolder(request, requestOptions) {
5411
+ const { upsert, body: _body } = request;
5412
+ const _queryParams = {
5413
+ upsert
5414
+ };
5380
5415
  const _authRequest = await this._options.authProvider.getAuthRequest();
5381
5416
  const _headers = mergeHeaders(
5382
5417
  _authRequest.headers,
@@ -5391,9 +5426,9 @@ var FoldersClient = class {
5391
5426
  method: "POST",
5392
5427
  headers: _headers,
5393
5428
  contentType: "application/json",
5394
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
5429
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
5395
5430
  requestType: "json",
5396
- body: request,
5431
+ body: _body,
5397
5432
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
5398
5433
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
5399
5434
  abortSignal: requestOptions?.abortSignal,
@@ -5407,6 +5442,8 @@ var FoldersClient = class {
5407
5442
  switch (_response.error.statusCode) {
5408
5443
  case 400:
5409
5444
  throw new BadRequestError(_response.error.body, _response.rawResponse);
5445
+ case 403:
5446
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
5410
5447
  case 429:
5411
5448
  throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
5412
5449
  default:
@@ -5815,23 +5852,30 @@ var IdentityClient = class {
5815
5852
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/clients");
5816
5853
  }
5817
5854
  /**
5818
- * Creates a new client identity in your account. This call is idempotent on `externalId`: if a client with the same `externalId` already exists, the existing record is returned instead of creating a duplicate. Requires the `clients:c` scope.
5855
+ * Creates a new client identity in your account. This call is idempotent on `externalId`: if a client with the same `externalId` already exists, the existing record is returned instead of creating a duplicate. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing client was returned) tells the two apart. To overwrite an existing client's content instead of returning it unchanged, set `?upsert=true` (this also requires the `clients:u` scope). Requires the `clients:c` scope.
5819
5856
  *
5820
- * @param {Vectros.ClientRequest} request
5857
+ * @param {Vectros.CreateClientRequest} request
5821
5858
  * @param {IdentityClient.RequestOptions} requestOptions - Request-specific configuration.
5822
5859
  *
5823
5860
  * @throws {@link Vectros.BadRequestError}
5861
+ * @throws {@link Vectros.ForbiddenError}
5824
5862
  * @throws {@link Vectros.TooManyRequestsError}
5825
5863
  *
5826
5864
  * @example
5827
5865
  * await client.identity.createClient({
5828
- * externalId: "patient_789"
5866
+ * body: {
5867
+ * externalId: "patient_789"
5868
+ * }
5829
5869
  * })
5830
5870
  */
5831
5871
  createClient(request, requestOptions) {
5832
5872
  return HttpResponsePromise.fromPromise(this.__createClient(request, requestOptions));
5833
5873
  }
5834
5874
  async __createClient(request, requestOptions) {
5875
+ const { upsert, body: _body } = request;
5876
+ const _queryParams = {
5877
+ upsert
5878
+ };
5835
5879
  const _authRequest = await this._options.authProvider.getAuthRequest();
5836
5880
  const _headers = mergeHeaders(
5837
5881
  _authRequest.headers,
@@ -5846,9 +5890,9 @@ var IdentityClient = class {
5846
5890
  method: "POST",
5847
5891
  headers: _headers,
5848
5892
  contentType: "application/json",
5849
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
5893
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
5850
5894
  requestType: "json",
5851
- body: request,
5895
+ body: _body,
5852
5896
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
5853
5897
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
5854
5898
  abortSignal: requestOptions?.abortSignal,
@@ -5862,6 +5906,8 @@ var IdentityClient = class {
5862
5906
  switch (_response.error.statusCode) {
5863
5907
  case 400:
5864
5908
  throw new BadRequestError(_response.error.body, _response.rawResponse);
5909
+ case 403:
5910
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
5865
5911
  case 429:
5866
5912
  throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
5867
5913
  default:
@@ -6250,23 +6296,30 @@ var IdentityClient = class {
6250
6296
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/orgs");
6251
6297
  }
6252
6298
  /**
6253
- * Creates a new organization in your account. This call is idempotent on `externalId`: if an organization with the same `externalId` already exists, the existing record is returned instead of creating a duplicate. Requires the `orgs:c` scope.
6299
+ * Creates a new organization in your account. This call is idempotent on `externalId`: if an organization with the same `externalId` already exists, the existing record is returned instead of creating a duplicate. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing organization was returned) tells the two apart. To overwrite an existing organization's content instead of returning it unchanged, set `?upsert=true` (this also requires the `orgs:u` scope). Requires the `orgs:c` scope.
6254
6300
  *
6255
- * @param {Vectros.OrgRequest} request
6301
+ * @param {Vectros.CreateOrgRequest} request
6256
6302
  * @param {IdentityClient.RequestOptions} requestOptions - Request-specific configuration.
6257
6303
  *
6258
6304
  * @throws {@link Vectros.BadRequestError}
6305
+ * @throws {@link Vectros.ForbiddenError}
6259
6306
  * @throws {@link Vectros.TooManyRequestsError}
6260
6307
  *
6261
6308
  * @example
6262
6309
  * await client.identity.createOrg({
6263
- * externalId: "clinic_001"
6310
+ * body: {
6311
+ * externalId: "clinic_001"
6312
+ * }
6264
6313
  * })
6265
6314
  */
6266
6315
  createOrg(request, requestOptions) {
6267
6316
  return HttpResponsePromise.fromPromise(this.__createOrg(request, requestOptions));
6268
6317
  }
6269
6318
  async __createOrg(request, requestOptions) {
6319
+ const { upsert, body: _body } = request;
6320
+ const _queryParams = {
6321
+ upsert
6322
+ };
6270
6323
  const _authRequest = await this._options.authProvider.getAuthRequest();
6271
6324
  const _headers = mergeHeaders(
6272
6325
  _authRequest.headers,
@@ -6281,9 +6334,9 @@ var IdentityClient = class {
6281
6334
  method: "POST",
6282
6335
  headers: _headers,
6283
6336
  contentType: "application/json",
6284
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
6337
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
6285
6338
  requestType: "json",
6286
- body: request,
6339
+ body: _body,
6287
6340
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
6288
6341
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
6289
6342
  abortSignal: requestOptions?.abortSignal,
@@ -6297,6 +6350,8 @@ var IdentityClient = class {
6297
6350
  switch (_response.error.statusCode) {
6298
6351
  case 400:
6299
6352
  throw new BadRequestError(_response.error.body, _response.rawResponse);
6353
+ case 403:
6354
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
6300
6355
  case 429:
6301
6356
  throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
6302
6357
  default:
@@ -6671,23 +6726,30 @@ var IdentityClient = class {
6671
6726
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/users");
6672
6727
  }
6673
6728
  /**
6674
- * Creates a user identity in your account. The operation is idempotent on `externalId`: if a user with the same `externalId` already exists, the existing record is returned instead of creating a duplicate. Requires the `users:c` scope.
6729
+ * Creates a user identity in your account. The operation is idempotent on `externalId`: if a user with the same `externalId` already exists, the existing record is returned instead of creating a duplicate. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing user was returned) tells the two apart. To overwrite an existing user's mutable fields (email, status, payload, schema binding) instead of returning it unchanged, set `?upsert=true` (this also requires the `users:u` scope). Requires the `users:c` scope.
6675
6730
  *
6676
- * @param {Vectros.UserRequest} request
6731
+ * @param {Vectros.CreateUserRequest} request
6677
6732
  * @param {IdentityClient.RequestOptions} requestOptions - Request-specific configuration.
6678
6733
  *
6679
6734
  * @throws {@link Vectros.BadRequestError}
6735
+ * @throws {@link Vectros.ForbiddenError}
6680
6736
  * @throws {@link Vectros.TooManyRequestsError}
6681
6737
  *
6682
6738
  * @example
6683
6739
  * await client.identity.createUser({
6684
- * externalId: "usr_12345"
6740
+ * body: {
6741
+ * externalId: "usr_12345"
6742
+ * }
6685
6743
  * })
6686
6744
  */
6687
6745
  createUser(request, requestOptions) {
6688
6746
  return HttpResponsePromise.fromPromise(this.__createUser(request, requestOptions));
6689
6747
  }
6690
6748
  async __createUser(request, requestOptions) {
6749
+ const { upsert, body: _body } = request;
6750
+ const _queryParams = {
6751
+ upsert
6752
+ };
6691
6753
  const _authRequest = await this._options.authProvider.getAuthRequest();
6692
6754
  const _headers = mergeHeaders(
6693
6755
  _authRequest.headers,
@@ -6702,9 +6764,9 @@ var IdentityClient = class {
6702
6764
  method: "POST",
6703
6765
  headers: _headers,
6704
6766
  contentType: "application/json",
6705
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
6767
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
6706
6768
  requestType: "json",
6707
- body: request,
6769
+ body: _body,
6708
6770
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
6709
6771
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
6710
6772
  abortSignal: requestOptions?.abortSignal,
@@ -6718,6 +6780,8 @@ var IdentityClient = class {
6718
6780
  switch (_response.error.statusCode) {
6719
6781
  case 400:
6720
6782
  throw new BadRequestError(_response.error.body, _response.rawResponse);
6783
+ case 403:
6784
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
6721
6785
  case 429:
6722
6786
  throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
6723
6787
  default:
@@ -7535,9 +7599,9 @@ var RecordsClient = class {
7535
7599
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/records");
7536
7600
  }
7537
7601
  /**
7538
- * Creates a new record of a given type. The `payload` is validated against that type's schema before the record is stored. Identify the type by sending `typeName`, `schemaId`, or both (they must agree); if you send only `schemaId`, the type is taken from that schema. Optionally supply an `externalId` to make the create idempotent — if a record with the same `externalId` already exists in your context, that existing record is returned unchanged instead of a duplicate being created. Requires the `records:c:<type>` scope.
7602
+ * Creates a new record of a given type. The `payload` is validated against that type's schema before the record is stored. Identify the type by sending `typeName`, `schemaId`, or both (they must agree); if you send only `schemaId`, the type is taken from that schema. Optionally supply an `externalId` to make the create idempotent — if a record with the same `externalId` already exists in your context, that existing record is returned unchanged instead of a duplicate being created. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing record was returned) tells the two apart. To overwrite an existing record's content instead of returning it unchanged, set `?upsert=true` (this also requires the `records:u:<type>` scope). Requires the `records:c:<type>` scope.
7539
7603
  *
7540
- * @param {Vectros.RecordRequest} request
7604
+ * @param {Vectros.CreateRecordRequest} request
7541
7605
  * @param {RecordsClient.RequestOptions} requestOptions - Request-specific configuration.
7542
7606
  *
7543
7607
  * @throws {@link Vectros.BadRequestError}
@@ -7546,19 +7610,25 @@ var RecordsClient = class {
7546
7610
  *
7547
7611
  * @example
7548
7612
  * await client.records.createRecord({
7549
- * typeName: "intake_form",
7550
- * schemaId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
7551
- * payload: {
7552
- * "first_name": "Jane",
7553
- * "email": "jane@example.com"
7554
- * },
7555
- * folderId: "f47ac10b-58cc-4372-a567-0e02b2c3d479"
7613
+ * body: {
7614
+ * typeName: "intake_form",
7615
+ * schemaId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
7616
+ * payload: {
7617
+ * "first_name": "Jane",
7618
+ * "email": "jane@example.com"
7619
+ * },
7620
+ * folderId: "f47ac10b-58cc-4372-a567-0e02b2c3d479"
7621
+ * }
7556
7622
  * })
7557
7623
  */
7558
7624
  createRecord(request, requestOptions) {
7559
7625
  return HttpResponsePromise.fromPromise(this.__createRecord(request, requestOptions));
7560
7626
  }
7561
7627
  async __createRecord(request, requestOptions) {
7628
+ const { upsert, body: _body } = request;
7629
+ const _queryParams = {
7630
+ upsert
7631
+ };
7562
7632
  const _authRequest = await this._options.authProvider.getAuthRequest();
7563
7633
  const _headers = mergeHeaders(
7564
7634
  _authRequest.headers,
@@ -7573,9 +7643,9 @@ var RecordsClient = class {
7573
7643
  method: "POST",
7574
7644
  headers: _headers,
7575
7645
  contentType: "application/json",
7576
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
7646
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
7577
7647
  requestType: "json",
7578
- body: request,
7648
+ body: _body,
7579
7649
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
7580
7650
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
7581
7651
  abortSignal: requestOptions?.abortSignal,
@@ -8171,43 +8241,50 @@ var SchemasClient = class {
8171
8241
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/schemas");
8172
8242
  }
8173
8243
  /**
8174
- * Defines a new record type with optional field definitions, validation rules, and lookup indexes. Idempotent by `typeName` within the same ownership scope: re-creating an existing `typeName` returns the existing schema rather than failing. Requires the `schemas:w` scope.
8244
+ * Defines a new record type with optional field definitions, validation rules, and lookup indexes. Idempotent by `typeName` within the same ownership scope: re-creating an existing `typeName` returns the existing schema rather than failing. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing schema was returned) tells the two apart. To reconcile an existing schema to the submitted shape instead of returning it unchanged, set `?upsert=true` (this also requires the `schemas:w` scope; only legal schema changes are applied — migration-locked changes are rejected). Requires the `schemas:w` scope.
8175
8245
  *
8176
- * @param {Vectros.SchemaRequest} request
8246
+ * @param {Vectros.CreateSchemaRequest} request
8177
8247
  * @param {SchemasClient.RequestOptions} requestOptions - Request-specific configuration.
8178
8248
  *
8179
8249
  * @throws {@link Vectros.BadRequestError}
8250
+ * @throws {@link Vectros.ForbiddenError}
8180
8251
  * @throws {@link Vectros.TooManyRequestsError}
8181
8252
  *
8182
8253
  * @example
8183
8254
  * await client.schemas.createSchema({
8184
- * typeName: "intake_form",
8185
- * displayName: "Client Intake Form",
8186
- * description: "Captures initial client information",
8187
- * fields: [{
8188
- * fieldId: "first_name",
8189
- * fieldType: "string",
8190
- * required: true,
8191
- * searchable: true
8192
- * }, {
8193
- * fieldId: "email",
8194
- * fieldType: "string",
8195
- * required: true
8196
- * }],
8197
- * lookupFields: [{
8198
- * fieldName: "email",
8199
- * unique: true
8200
- * }],
8201
- * capabilities: {
8202
- * "auditHistory": true
8203
- * },
8204
- * allowedSurfaces: ["record"]
8255
+ * body: {
8256
+ * typeName: "intake_form",
8257
+ * displayName: "Client Intake Form",
8258
+ * description: "Captures initial client information",
8259
+ * fields: [{
8260
+ * fieldId: "first_name",
8261
+ * fieldType: "string",
8262
+ * required: true,
8263
+ * searchable: true
8264
+ * }, {
8265
+ * fieldId: "email",
8266
+ * fieldType: "string",
8267
+ * required: true
8268
+ * }],
8269
+ * lookupFields: [{
8270
+ * fieldName: "email",
8271
+ * unique: true
8272
+ * }],
8273
+ * capabilities: {
8274
+ * "auditHistory": true
8275
+ * },
8276
+ * allowedSurfaces: ["record"]
8277
+ * }
8205
8278
  * })
8206
8279
  */
8207
8280
  createSchema(request, requestOptions) {
8208
8281
  return HttpResponsePromise.fromPromise(this.__createSchema(request, requestOptions));
8209
8282
  }
8210
8283
  async __createSchema(request, requestOptions) {
8284
+ const { upsert, body: _body } = request;
8285
+ const _queryParams = {
8286
+ upsert
8287
+ };
8211
8288
  const _authRequest = await this._options.authProvider.getAuthRequest();
8212
8289
  const _headers = mergeHeaders(
8213
8290
  _authRequest.headers,
@@ -8222,9 +8299,9 @@ var SchemasClient = class {
8222
8299
  method: "POST",
8223
8300
  headers: _headers,
8224
8301
  contentType: "application/json",
8225
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
8302
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
8226
8303
  requestType: "json",
8227
- body: request,
8304
+ body: _body,
8228
8305
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
8229
8306
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
8230
8307
  abortSignal: requestOptions?.abortSignal,
@@ -8238,6 +8315,8 @@ var SchemasClient = class {
8238
8315
  switch (_response.error.statusCode) {
8239
8316
  case 400:
8240
8317
  throw new BadRequestError(_response.error.body, _response.rawResponse);
8318
+ case 403:
8319
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
8241
8320
  case 429:
8242
8321
  throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
8243
8322
  default:
@@ -8788,11 +8867,36 @@ var BlueprintServicePrincipalSchema = z.object({
8788
8867
  externalId: z.string().min(1),
8789
8868
  displayName: z.string().min(1)
8790
8869
  }).strict();
8791
- var BlueprintSeedRecordSchema = z.object({
8870
+ var SeedCommonShape = {
8871
+ /** The schema typeName this seed instantiates (should match a declared schema). */
8792
8872
  typeName: z.string().min(1),
8793
- externalId: z.string().min(1),
8873
+ /**
8874
+ * Stable, caller-supplied id — the loader's idempotency key AND the value other
8875
+ * seeds resolve a `reference` against (across surfaces: a record seed may
8876
+ * reference a document seed by its externalId, and vice versa).
8877
+ */
8878
+ externalId: z.string().min(1)
8879
+ };
8880
+ var RecordSeedSchema = z.object({
8881
+ surface: z.literal("record"),
8882
+ ...SeedCommonShape,
8883
+ /** The record payload, validated against the bound schema. */
8794
8884
  fields: z.record(z.unknown())
8795
8885
  }).strict();
8886
+ var DocumentSeedSchema = z.object({
8887
+ surface: z.literal("document"),
8888
+ ...SeedCommonShape,
8889
+ /** Human-readable document title — REQUIRED by the text-ingest path. */
8890
+ title: z.string().min(1),
8891
+ /** Raw text content to ingest + index — REQUIRED and non-empty (the platform rejects a blank ingest). */
8892
+ text: z.string().min(1),
8893
+ /** Optional structured payload bound to the schema (the document's `fields`). */
8894
+ fields: z.record(z.unknown()).optional()
8895
+ }).strict();
8896
+ var BlueprintSeedRecordSchema = z.discriminatedUnion("surface", [
8897
+ RecordSeedSchema,
8898
+ DocumentSeedSchema
8899
+ ]);
8796
8900
  var SELF_TOKEN_RE = /\$\{\{\s*self\.[A-Za-z_]\w*\s*\}\}/;
8797
8901
  function lintSelfTokenPlacement(value, ctx) {
8798
8902
  const walk = (node, path4, inRoleDataScope) => {
@@ -8848,6 +8952,23 @@ function lintIdentityRefsDeclared(value, ctx) {
8848
8952
  };
8849
8953
  walk(value, []);
8850
8954
  }
8955
+ function lintSeedSurfaces(value, ctx) {
8956
+ const bp = value;
8957
+ if (!Array.isArray(bp.seed)) return;
8958
+ const byType = new Map((bp.schemas ?? []).map((s) => [s.typeName, s]));
8959
+ bp.seed.forEach((seed, i) => {
8960
+ const schema = byType.get(seed.typeName);
8961
+ if (!schema) return;
8962
+ const allowed = schema.allowedSurfaces ?? ["record"];
8963
+ if (!allowed.includes(seed.surface ?? "record")) {
8964
+ ctx.addIssue({
8965
+ code: z.ZodIssueCode.custom,
8966
+ path: ["seed", i, "surface"],
8967
+ message: `seed '${seed.externalId}' uses surface '${seed.surface ?? "record"}', but schema '${seed.typeName}' allows only [${allowed.join(", ")}]`
8968
+ });
8969
+ }
8970
+ });
8971
+ }
8851
8972
  var BlueprintSchema = z.object({
8852
8973
  /** Stable blueprint id (the `--blueprint <name>` selector + idempotency key). */
8853
8974
  name: z.string().min(1),
@@ -8870,6 +8991,7 @@ var BlueprintSchema = z.object({
8870
8991
  }).strict().superRefine((bp, ctx) => {
8871
8992
  lintSelfTokenPlacement(bp, ctx);
8872
8993
  lintIdentityRefsDeclared(bp, ctx);
8994
+ lintSeedSurfaces(bp, ctx);
8873
8995
  });
8874
8996
  function formatIssuePath(path4) {
8875
8997
  let out = "";
@@ -9267,6 +9389,7 @@ var taskManagement = {
9267
9389
  },
9268
9390
  seed: [
9269
9391
  {
9392
+ surface: "record",
9270
9393
  typeName: "task",
9271
9394
  externalId: "seed-welcome",
9272
9395
  fields: {
@@ -9494,6 +9617,7 @@ var codingAgentMemory = {
9494
9617
  // Seeded FIRST so the convention below resolves its reference to it — the
9495
9618
  // loader creates seeds in array order, and a reference target must exist when
9496
9619
  // the referencing record is written.
9620
+ surface: "record",
9497
9621
  typeName: "decision",
9498
9622
  externalId: "seed-use-vectros-for-memory",
9499
9623
  fields: {
@@ -9509,6 +9633,7 @@ var codingAgentMemory = {
9509
9633
  // Demonstrates a live typed link at bootstrap: this convention references the
9510
9634
  // decision above by its externalId, so "open the decision behind this rule"
9511
9635
  // works the moment the blueprint is applied.
9636
+ surface: "record",
9512
9637
  typeName: "convention",
9513
9638
  externalId: "seed-record-the-why",
9514
9639
  fields: {
@@ -9523,145 +9648,841 @@ var codingAgentMemory = {
9523
9648
  ]
9524
9649
  };
9525
9650
  var coding_agent_memory_default = codingAgentMemory;
9526
- var secondBrain = {
9527
- name: "second-brain",
9651
+ var DATA_PLANE_ACTIONS = [
9652
+ "records:r",
9653
+ "records:c",
9654
+ "records:u",
9655
+ "search:r",
9656
+ "schemas:r",
9657
+ "inference:r",
9658
+ "documents:r",
9659
+ "documents:c",
9660
+ "folders:r",
9661
+ "folders:c"
9662
+ ];
9663
+ var agenticSdlc = {
9664
+ name: "agentic-sdlc",
9528
9665
  version: "1.0.0",
9529
- description: "A personal knowledge base \u2014 capture notes, ideas, and links, then ask them anything.",
9530
- contextId: "second-brain",
9531
- contextName: "Second Brain",
9666
+ 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.",
9667
+ contextId: "agentic-sdlc",
9668
+ contextName: "Agentic SDLC Knowledge Base",
9532
9669
  schemas: [
9670
+ // ============================ DOCUMENTS ============================
9671
+ // Content-dominant: the markdown body is the artifact; fields are metadata.
9672
+ // Each binds the `document` surface and is ingested via `document_ingest`.
9533
9673
  {
9534
- typeName: "note",
9535
- displayName: "Note",
9674
+ // An architecture/product DECISION (an ADR). The body is the prose —
9675
+ // Context / Decision / Consequences — and is what `rag_ask` answers over;
9676
+ // the fields below are filter/sort metadata + the supersede chain. The
9677
+ // anchor of the graph: most other types link back to a decision.
9678
+ typeName: "decision",
9679
+ displayName: "Decision (ADR)",
9536
9680
  indexMode: "HYBRID",
9537
- // keyword on title + semantic on body — recall by meaning
9681
+ allowedSurfaces: ["document"],
9682
+ // A document carries an INTRINSIC title (the ingest title) + body; the schema
9683
+ // declares only the metadata BEYOND those. (No typed `title` field — that
9684
+ // would duplicate the document's own title.)
9538
9685
  fields: [
9539
9686
  {
9540
- fieldId: "title",
9687
+ fieldId: "summary",
9541
9688
  fieldType: "string",
9542
- required: true,
9543
9689
  searchable: true,
9544
- validation: { minLength: 1, maxLength: 200 },
9545
- renderHints: { label: "Title", widget: "text", order: 1, section: "Note", displayField: true }
9690
+ description: "A one-paragraph abstract of the decision. The full reasoning is the document body.",
9691
+ renderHints: { label: "Summary", widget: "textarea", order: 2 }
9546
9692
  },
9547
9693
  {
9548
- fieldId: "body",
9549
- fieldType: "string",
9550
- searchable: true,
9551
- description: "The note itself \u2014 the RAG-able body you ask questions against.",
9552
- validation: { maxLength: 2e4 },
9553
- renderHints: { label: "Body", widget: "textarea", order: 2, section: "Note" }
9694
+ fieldId: "status",
9695
+ fieldType: "enum",
9696
+ filterable: true,
9697
+ enumValues: ["proposed", "accepted", "superseded", "deprecated"],
9698
+ renderHints: { label: "Status", widget: "select", order: 3 }
9554
9699
  },
9555
9700
  {
9556
- fieldId: "tags",
9557
- fieldType: "array",
9701
+ fieldId: "area",
9702
+ fieldType: "string",
9558
9703
  filterable: true,
9559
- description: 'Freeform string labels for filtering (e.g. "idea", "reading", "work").',
9560
- renderHints: { label: "Tags", order: 3, section: "Organize" }
9704
+ description: 'Subsystem the decision applies to (e.g. "search", "auth", "billing").',
9705
+ renderHints: { label: "Area", widget: "text", order: 4 }
9561
9706
  },
9562
9707
  {
9563
- // An enum (not freeform) so enumeration-by-source is reliable — a
9564
- // `record_query` lookup on `source` only works if values are consistent.
9565
- // `other` keeps it from being a straitjacket for a personal brain-dump.
9566
- fieldId: "source",
9567
- fieldType: "enum",
9708
+ fieldId: "tags",
9709
+ fieldType: "array",
9568
9710
  filterable: true,
9569
- enumValues: ["thought", "web", "meeting", "book", "article", "other"],
9570
- description: "Where it came from.",
9571
- renderHints: { label: "Source", widget: "select", order: 4, section: "Organize" }
9711
+ description: 'Freeform labels (e.g. "security", "schema"). Search-side filter.',
9712
+ renderHints: { label: "Tags", order: 5 }
9572
9713
  },
9573
9714
  {
9574
- fieldId: "status",
9575
- fieldType: "enum",
9576
- filterable: true,
9577
- enumValues: ["active", "archived"],
9578
- description: "Archive instead of delete, so nothing is ever lost.",
9579
- renderHints: { label: "Status", widget: "select", order: 5, section: "Organize" }
9715
+ // Self-reference (document → document): the decision this one supersedes.
9716
+ fieldId: "supersedes",
9717
+ fieldType: "reference",
9718
+ targetTypeName: "decision",
9719
+ targetSurface: "document",
9720
+ targetField: "externalId",
9721
+ cardinality: "one",
9722
+ description: "The decision this one supersedes (by externalId).",
9723
+ renderHints: { label: "Supersedes", order: 6 }
9580
9724
  },
9581
9725
  {
9582
- fieldId: "capturedAt",
9726
+ fieldId: "date",
9583
9727
  fieldType: "date",
9584
- description: 'ISO-8601 capture date. Range-queryable \u2014 "notes from last week".',
9585
- renderHints: { label: "Captured", widget: "date", order: 6, section: "Organize" }
9728
+ description: "ISO-8601 decision date. Range-queryable / sortable.",
9729
+ renderHints: { label: "Date", widget: "date", order: 7 }
9586
9730
  }
9587
- // externalId is the record's FIRST-CLASS identifier (the dedup/upsert key + the
9588
- // value a `reference` resolves against) — not a payload field; the loader sends
9589
- // it top-level on the RecordRequest.
9590
9731
  ],
9591
- // Lookup shapes are MIGRATION-LOCKED once the schema is live (a field cannot
9592
- // flip equality↔range later), so each is chosen on purpose:
9593
- // • `source` — EQUALITY, sorted by `lastUpdated`: enumerate notes from one
9594
- // source, most-recently-touched first. `lastUpdated` is always present, so
9595
- // the sort never drops a note (sorting an equality lookup by an OPTIONAL
9596
- // field would silently exclude rows that lack it).
9597
- // • `status` — EQUALITY: "show archived notes".
9598
- // • `capturedAt` — RANGE: ordered `from`/`to`/`prefix` ("captured in 2026-06").
9599
- // `externalId` has a built-in first-class finder — look it up directly, never
9600
- // redeclare it here. Equality lookups use the 7 fast index slots (2 used here);
9601
- // range lookups use a relationship row, billed at the range rate.
9602
- lookupFields: [
9603
- { fieldName: "source", sortBy: "lastUpdated" },
9604
- "status",
9605
- { fieldName: "capturedAt", rangeEnabled: true }
9606
- ]
9607
- }
9608
- ],
9609
- // Least-privilege profile — r/c/u + search + schema discovery + inference:r
9610
- // (so the agent can `rag_ask` a grounded, cited question over your notes). No
9611
- // records:d: notes are archived (status → 'archived'), never deleted.
9612
- accessProfile: {
9613
- allowedActions: ["records:r", "records:c", "records:u", "search:r", "schemas:r", "inference:r"]
9614
- },
9615
- servicePrincipal: {
9616
- externalId: "second-brain",
9617
- displayName: "Second Brain"
9618
- },
9619
- seed: [
9620
- {
9621
- typeName: "note",
9622
- externalId: "seed-welcome",
9623
- fields: {
9624
- title: "Welcome to your Second Brain",
9625
- body: 'Capture anything here \u2014 ideas, links, meeting notes \u2014 then ask your agent things like "what did I note about onboarding?" and it recalls by meaning, not just keywords.',
9626
- tags: ["getting-started"],
9627
- source: "thought",
9628
- status: "active",
9629
- capturedAt: "2026-06-14"
9630
- }
9631
- }
9632
- ]
9633
- };
9634
- var second_brain_default = secondBrain;
9635
- var clinicalIntake = {
9636
- name: "clinical-intake",
9637
- version: "1.0.0",
9638
- description: "Behavioral-health intake with PHI fields \u2014 demonstrates redact-at-write, audit history, blind-index lookup, and search exclusion. Synthetic data only.",
9639
- contextId: "clinical-intake",
9640
- contextName: "Clinical Intake",
9641
- schemas: [
9732
+ lookupFields: ["status", "area", "supersedes", { fieldName: "date", rangeEnabled: true }]
9733
+ },
9642
9734
  {
9643
- typeName: "intake",
9644
- displayName: "Intake",
9735
+ // A DESIGN doc or spec — the exploration that drives a decision. Distinct
9736
+ // from `decision` (a different browse genre + it links to the decision it
9737
+ // informs). Body = the design narrative.
9738
+ typeName: "design",
9739
+ displayName: "Design",
9645
9740
  indexMode: "HYBRID",
9646
- capabilities: { auditHistory: true },
9647
- // self-documenting compliance posture
9741
+ allowedSurfaces: ["document"],
9742
+ // Intrinsic title + body; schema = metadata beyond those (no typed `title`).
9648
9743
  fields: [
9649
- // --- Non-sensitive, searchable/filterable working fields ---
9650
9744
  {
9651
- fieldId: "caseId",
9745
+ fieldId: "summary",
9652
9746
  fieldType: "string",
9653
- required: true,
9654
- description: "Caller-stable intake id; the dedup/lookup key.",
9655
- validation: { minLength: 1, maxLength: 64 },
9656
- renderHints: { label: "Case ID", widget: "text", order: 1, section: "Intake", displayField: true }
9747
+ searchable: true,
9748
+ description: "A one-paragraph abstract. The full design is the document body.",
9749
+ renderHints: { label: "Summary", widget: "textarea", order: 2 }
9657
9750
  },
9658
9751
  {
9659
- fieldId: "presentingConcern",
9660
- fieldType: "string",
9661
- searchable: true,
9662
- description: "Non-PHI summary of the presenting concern \u2014 safe to index + search.",
9663
- validation: { maxLength: 2e3 },
9664
- renderHints: { label: "Presenting concern", widget: "textarea", order: 2, section: "Intake" }
9752
+ fieldId: "status",
9753
+ fieldType: "enum",
9754
+ filterable: true,
9755
+ enumValues: ["draft", "active", "implemented", "superseded"],
9756
+ renderHints: { label: "Status", widget: "select", order: 3 }
9757
+ },
9758
+ {
9759
+ fieldId: "area",
9760
+ fieldType: "string",
9761
+ filterable: true,
9762
+ renderHints: { label: "Area", widget: "text", order: 4 }
9763
+ },
9764
+ {
9765
+ fieldId: "tags",
9766
+ fieldType: "array",
9767
+ filterable: true,
9768
+ renderHints: { label: "Tags", order: 5 }
9769
+ },
9770
+ {
9771
+ // Cross-document edge: the decision this design informs/produces.
9772
+ fieldId: "relatedDecision",
9773
+ fieldType: "reference",
9774
+ targetTypeName: "decision",
9775
+ targetSurface: "document",
9776
+ targetField: "externalId",
9777
+ cardinality: "one",
9778
+ description: "The decision this design informs (by externalId).",
9779
+ renderHints: { label: "Related decision", order: 6 }
9780
+ },
9781
+ {
9782
+ // Self-reference: a design supersedes an earlier design/spec.
9783
+ fieldId: "supersedes",
9784
+ fieldType: "reference",
9785
+ targetTypeName: "design",
9786
+ targetSurface: "document",
9787
+ targetField: "externalId",
9788
+ cardinality: "one",
9789
+ description: "The design this one supersedes (by externalId).",
9790
+ renderHints: { label: "Supersedes", order: 7 }
9791
+ },
9792
+ {
9793
+ fieldId: "updatedOn",
9794
+ fieldType: "date",
9795
+ description: "ISO-8601 \u2014 when last revised. Range-queryable.",
9796
+ renderHints: { label: "Updated on", widget: "date", order: 8 }
9797
+ }
9798
+ ],
9799
+ lookupFields: ["status", "area", "relatedDecision", "supersedes", { fieldName: "updatedOn", rangeEnabled: true }]
9800
+ },
9801
+ {
9802
+ // A REFERENCE doc — maintained "how it works / how to": guides, onboarding,
9803
+ // API docs, process docs. Body = the prose. Distinct shape: a `category`
9804
+ // (DRY sub-labels — onboarding/api/process are same shape) and `lastReviewed`
9805
+ // (freshness is the whole game for reference material).
9806
+ typeName: "reference",
9807
+ displayName: "Reference",
9808
+ indexMode: "HYBRID",
9809
+ allowedSurfaces: ["document"],
9810
+ // Intrinsic title + body; schema = metadata beyond those (no typed `title`).
9811
+ fields: [
9812
+ {
9813
+ fieldId: "summary",
9814
+ fieldType: "string",
9815
+ searchable: true,
9816
+ description: "A one-paragraph abstract. The full reference is the document body.",
9817
+ renderHints: { label: "Summary", widget: "textarea", order: 2 }
9818
+ },
9819
+ {
9820
+ // The sub-kind, as a filterable field (not a separate schema — these are
9821
+ // the same shape, differing only by label, so a field is the DRY choice).
9822
+ fieldId: "category",
9823
+ fieldType: "enum",
9824
+ filterable: true,
9825
+ enumValues: ["guide", "onboarding", "api", "process", "other"],
9826
+ renderHints: { label: "Category", widget: "select", order: 3 }
9827
+ },
9828
+ {
9829
+ fieldId: "area",
9830
+ fieldType: "string",
9831
+ filterable: true,
9832
+ renderHints: { label: "Area", widget: "text", order: 4 }
9833
+ },
9834
+ {
9835
+ fieldId: "tags",
9836
+ fieldType: "array",
9837
+ filterable: true,
9838
+ renderHints: { label: "Tags", order: 5 }
9839
+ },
9840
+ {
9841
+ fieldId: "status",
9842
+ fieldType: "enum",
9843
+ filterable: true,
9844
+ enumValues: ["active", "superseded"],
9845
+ renderHints: { label: "Status", widget: "select", order: 6 }
9846
+ },
9847
+ {
9848
+ // Optional cross-document edge: the decision behind this reference.
9849
+ fieldId: "relatedDecision",
9850
+ fieldType: "reference",
9851
+ targetTypeName: "decision",
9852
+ targetSurface: "document",
9853
+ targetField: "externalId",
9854
+ cardinality: "one",
9855
+ description: "The decision behind this reference, if any (by externalId).",
9856
+ renderHints: { label: "Related decision", order: 7 }
9857
+ },
9858
+ {
9859
+ // Freshness — "references not reviewed since X". The reference-doc signal.
9860
+ fieldId: "lastReviewed",
9861
+ fieldType: "date",
9862
+ description: "ISO-8601 \u2014 when this reference was last verified current. Range-queryable.",
9863
+ renderHints: { label: "Last reviewed", widget: "date", order: 8 }
9864
+ }
9865
+ ],
9866
+ lookupFields: ["category", "area", "status", "relatedDecision", { fieldName: "lastReviewed", rangeEnabled: true }]
9867
+ },
9868
+ {
9869
+ // A RUNBOOK — a step-by-step operational procedure (deploy, release, recover).
9870
+ // Body = the procedure. Distinct browse genre, and often BORN FROM a
9871
+ // post-mortem (its resolution, codified) — a cross-document edge.
9872
+ typeName: "runbook",
9873
+ displayName: "Runbook",
9874
+ indexMode: "HYBRID",
9875
+ allowedSurfaces: ["document"],
9876
+ // Intrinsic title + body; schema = metadata beyond those (no typed `title`).
9877
+ fields: [
9878
+ {
9879
+ fieldId: "summary",
9880
+ fieldType: "string",
9881
+ searchable: true,
9882
+ description: "When to use this runbook \u2014 the trigger. The steps are the document body.",
9883
+ renderHints: { label: "Summary", widget: "textarea", order: 2 }
9884
+ },
9885
+ {
9886
+ fieldId: "area",
9887
+ fieldType: "string",
9888
+ filterable: true,
9889
+ renderHints: { label: "Area", widget: "text", order: 3 }
9890
+ },
9891
+ {
9892
+ fieldId: "tags",
9893
+ fieldType: "array",
9894
+ filterable: true,
9895
+ renderHints: { label: "Tags", order: 4 }
9896
+ },
9897
+ {
9898
+ fieldId: "status",
9899
+ fieldType: "enum",
9900
+ filterable: true,
9901
+ enumValues: ["active", "retired"],
9902
+ renderHints: { label: "Status", widget: "select", order: 5 }
9903
+ },
9904
+ {
9905
+ // Cross-document edge: the post-mortem whose resolution this runbook codifies.
9906
+ fieldId: "bornFrom",
9907
+ fieldType: "reference",
9908
+ targetTypeName: "postmortem",
9909
+ targetSurface: "document",
9910
+ targetField: "externalId",
9911
+ cardinality: "one",
9912
+ description: "The post-mortem this runbook was codified from (by externalId).",
9913
+ renderHints: { label: "Born from (post-mortem)", order: 6 }
9914
+ },
9915
+ {
9916
+ fieldId: "relatedDecision",
9917
+ fieldType: "reference",
9918
+ targetTypeName: "decision",
9919
+ targetSurface: "document",
9920
+ targetField: "externalId",
9921
+ cardinality: "one",
9922
+ description: "A decision this runbook implements, if any (by externalId).",
9923
+ renderHints: { label: "Related decision", order: 7 }
9924
+ },
9925
+ {
9926
+ fieldId: "updatedOn",
9927
+ fieldType: "date",
9928
+ description: "ISO-8601 \u2014 when last revised. Range-queryable.",
9929
+ renderHints: { label: "Updated on", widget: "date", order: 8 }
9930
+ }
9931
+ ],
9932
+ lookupFields: ["area", "status", "bornFrom", "relatedDecision", { fieldName: "updatedOn", rangeEnabled: true }]
9933
+ },
9934
+ {
9935
+ // A POST-MORTEM — what broke and the durable lesson. Body = the writeup
9936
+ // (impact / root cause / resolution / lesson). Distinct shape: `severity` +
9937
+ // `occurredOn` (the incident timeline). "Have we hit this before?" lives here.
9938
+ typeName: "postmortem",
9939
+ displayName: "Post-mortem",
9940
+ indexMode: "HYBRID",
9941
+ allowedSurfaces: ["document"],
9942
+ // Intrinsic title + body; schema = metadata beyond those (no typed `title`).
9943
+ fields: [
9944
+ {
9945
+ fieldId: "summary",
9946
+ fieldType: "string",
9947
+ searchable: true,
9948
+ description: "What happened, in brief. The full analysis (impact/root cause/resolution/lesson) is the body.",
9949
+ renderHints: { label: "Summary", widget: "textarea", order: 2 }
9950
+ },
9951
+ {
9952
+ fieldId: "severity",
9953
+ fieldType: "enum",
9954
+ filterable: true,
9955
+ enumValues: ["low", "medium", "high", "critical"],
9956
+ renderHints: { label: "Severity", widget: "select", order: 3 }
9957
+ },
9958
+ {
9959
+ fieldId: "status",
9960
+ fieldType: "enum",
9961
+ filterable: true,
9962
+ enumValues: ["open", "mitigated", "resolved"],
9963
+ renderHints: { label: "Status", widget: "select", order: 4 }
9964
+ },
9965
+ {
9966
+ fieldId: "area",
9967
+ fieldType: "string",
9968
+ filterable: true,
9969
+ renderHints: { label: "Area", widget: "text", order: 5 }
9970
+ },
9971
+ {
9972
+ fieldId: "tags",
9973
+ fieldType: "array",
9974
+ filterable: true,
9975
+ renderHints: { label: "Tags", order: 6 }
9976
+ },
9977
+ {
9978
+ fieldId: "relatedDecision",
9979
+ fieldType: "reference",
9980
+ targetTypeName: "decision",
9981
+ targetSurface: "document",
9982
+ targetField: "externalId",
9983
+ cardinality: "one",
9984
+ description: "A decision implicated in or addressed by this incident (by externalId).",
9985
+ renderHints: { label: "Related decision", order: 7 }
9986
+ },
9987
+ {
9988
+ fieldId: "occurredOn",
9989
+ fieldType: "date",
9990
+ description: 'ISO-8601 \u2014 when it happened. Range-queryable \u2014 "incidents this month".',
9991
+ renderHints: { label: "Occurred on", widget: "date", order: 8 }
9992
+ }
9993
+ ],
9994
+ lookupFields: ["severity", "status", "area", "relatedDecision", { fieldName: "occurredOn", rangeEnabled: true }]
9995
+ },
9996
+ // ============================= RECORDS =============================
9997
+ // Structure-dominant: the typed fields are the artifact. Short, exact-queryable.
9998
+ {
9999
+ // A governance CONTROL — a policy/standard/control the codebase must satisfy,
10000
+ // WITH its evidence. The compliance instrument: it records what enforces it
10001
+ // (`evidence`), the runbook that VERIFIES it (cross-surface → document), and
10002
+ // the decision that mandates it. "Which critical controls are active, and how
10003
+ // is each proven?"
10004
+ typeName: "control",
10005
+ displayName: "Control",
10006
+ indexMode: "HYBRID",
10007
+ fields: [
10008
+ {
10009
+ fieldId: "title",
10010
+ fieldType: "string",
10011
+ required: true,
10012
+ searchable: true,
10013
+ validation: { minLength: 1, maxLength: 200 },
10014
+ renderHints: { label: "Title", widget: "text", order: 1, displayField: true }
10015
+ },
10016
+ {
10017
+ fieldId: "statement",
10018
+ fieldType: "string",
10019
+ searchable: true,
10020
+ description: "The requirement \u2014 what must always hold.",
10021
+ renderHints: { label: "Statement", widget: "textarea", order: 2 }
10022
+ },
10023
+ {
10024
+ fieldId: "rationale",
10025
+ fieldType: "string",
10026
+ searchable: true,
10027
+ description: "Why the control exists \u2014 the risk it prevents.",
10028
+ renderHints: { label: "Rationale", widget: "textarea", order: 3 }
10029
+ },
10030
+ {
10031
+ // The policy → implementation spectrum in one filterable field.
10032
+ fieldId: "kind",
10033
+ fieldType: "enum",
10034
+ filterable: true,
10035
+ enumValues: ["policy", "standard", "control"],
10036
+ renderHints: { label: "Kind", widget: "select", order: 4 }
10037
+ },
10038
+ {
10039
+ // Ordinal, but EQUALITY (range/prefix order is lexical, so low<critical
10040
+ // would sort alphabetically). Enumerate "all critical controls" by equality.
10041
+ fieldId: "criticality",
10042
+ fieldType: "enum",
10043
+ filterable: true,
10044
+ enumValues: ["low", "medium", "high", "critical"],
10045
+ renderHints: { label: "Criticality", widget: "select", order: 5 }
10046
+ },
10047
+ {
10048
+ fieldId: "evidence",
10049
+ fieldType: "string",
10050
+ searchable: true,
10051
+ description: "What enforces or proves the control inline (e.g. an architecture test or gate).",
10052
+ renderHints: { label: "Evidence", widget: "textarea", order: 6 }
10053
+ },
10054
+ {
10055
+ fieldId: "status",
10056
+ fieldType: "enum",
10057
+ filterable: true,
10058
+ enumValues: ["draft", "active", "retired"],
10059
+ renderHints: { label: "Status", widget: "select", order: 7 }
10060
+ },
10061
+ {
10062
+ fieldId: "area",
10063
+ fieldType: "string",
10064
+ filterable: true,
10065
+ renderHints: { label: "Area", widget: "text", order: 8 }
10066
+ },
10067
+ {
10068
+ fieldId: "tags",
10069
+ fieldType: "array",
10070
+ filterable: true,
10071
+ renderHints: { label: "Tags", order: 9 }
10072
+ },
10073
+ {
10074
+ // Cross-surface edge (record → document): the runbook that proves the control.
10075
+ fieldId: "verifiedBy",
10076
+ fieldType: "reference",
10077
+ targetTypeName: "runbook",
10078
+ targetSurface: "document",
10079
+ targetField: "externalId",
10080
+ cardinality: "one",
10081
+ description: "The runbook that verifies this control (by externalId).",
10082
+ renderHints: { label: "Verified by (runbook)", order: 10 }
10083
+ },
10084
+ {
10085
+ // Cross-surface edge (record → document): the decision that mandates it.
10086
+ fieldId: "relatedDecision",
10087
+ fieldType: "reference",
10088
+ targetTypeName: "decision",
10089
+ targetSurface: "document",
10090
+ targetField: "externalId",
10091
+ cardinality: "one",
10092
+ description: "The decision that mandates this control (by externalId).",
10093
+ renderHints: { label: "Related decision", order: 11 }
10094
+ },
10095
+ {
10096
+ fieldId: "reviewedOn",
10097
+ fieldType: "date",
10098
+ description: "ISO-8601 last-reviewed date. Range-queryable.",
10099
+ renderHints: { label: "Reviewed on", widget: "date", order: 12 }
10100
+ }
10101
+ ],
10102
+ lookupFields: [
10103
+ "kind",
10104
+ "criticality",
10105
+ "status",
10106
+ "area",
10107
+ "verifiedBy",
10108
+ "relatedDecision",
10109
+ { fieldName: "reviewedOn", rangeEnabled: true }
10110
+ ]
10111
+ },
10112
+ {
10113
+ // A CONVENTION — a must-follow operating rule the team teaches its agents. The
10114
+ // durable, shareable operating-memory. Distinct fields capture how we actually
10115
+ // write these: the `rule` (imperative), the `why` (rationale), and `howToApply`
10116
+ // (the concrete application) are SEPARATE fields, not one blob — so an agent can
10117
+ // recall the rule, the reasoning, and the how independently.
10118
+ typeName: "convention",
10119
+ displayName: "Convention",
10120
+ indexMode: "HYBRID",
10121
+ fields: [
10122
+ {
10123
+ fieldId: "title",
10124
+ fieldType: "string",
10125
+ required: true,
10126
+ searchable: true,
10127
+ validation: { minLength: 1, maxLength: 200 },
10128
+ renderHints: { label: "Title", widget: "text", order: 1, displayField: true }
10129
+ },
10130
+ {
10131
+ fieldId: "rule",
10132
+ fieldType: "string",
10133
+ searchable: true,
10134
+ description: "The convention itself, stated as an imperative.",
10135
+ renderHints: { label: "Rule", widget: "textarea", order: 2 }
10136
+ },
10137
+ {
10138
+ fieldId: "why",
10139
+ fieldType: "string",
10140
+ searchable: true,
10141
+ description: "Why it matters \u2014 the trade-off / the antipattern it prevents.",
10142
+ renderHints: { label: "Why", widget: "textarea", order: 3 }
10143
+ },
10144
+ {
10145
+ fieldId: "howToApply",
10146
+ fieldType: "string",
10147
+ searchable: true,
10148
+ description: "How to comply in practice \u2014 the concrete application steps.",
10149
+ renderHints: { label: "How to apply", widget: "textarea", order: 4 }
10150
+ },
10151
+ {
10152
+ fieldId: "area",
10153
+ fieldType: "string",
10154
+ filterable: true,
10155
+ renderHints: { label: "Area", widget: "text", order: 5 }
10156
+ },
10157
+ {
10158
+ fieldId: "status",
10159
+ fieldType: "enum",
10160
+ filterable: true,
10161
+ enumValues: ["active", "retired"],
10162
+ renderHints: { label: "Status", widget: "select", order: 6 }
10163
+ },
10164
+ {
10165
+ fieldId: "tags",
10166
+ fieldType: "array",
10167
+ filterable: true,
10168
+ renderHints: { label: "Tags", order: 7 }
10169
+ },
10170
+ {
10171
+ // Cross-surface edge (record → document): the decision that established it.
10172
+ fieldId: "establishedBy",
10173
+ fieldType: "reference",
10174
+ targetTypeName: "decision",
10175
+ targetSurface: "document",
10176
+ targetField: "externalId",
10177
+ cardinality: "one",
10178
+ description: "The decision that established this convention (by externalId).",
10179
+ renderHints: { label: "Established by (decision)", order: 8 }
10180
+ },
10181
+ {
10182
+ fieldId: "updatedOn",
10183
+ fieldType: "date",
10184
+ description: "ISO-8601 \u2014 when last revised. Range-queryable.",
10185
+ renderHints: { label: "Updated on", widget: "date", order: 9 }
10186
+ }
10187
+ ],
10188
+ lookupFields: ["area", "status", "establishedBy", { fieldName: "updatedOn", rangeEnabled: true }]
10189
+ },
10190
+ {
10191
+ // A GOTCHA / sharp edge: a symptom, its cause, and the fix. A tight typed
10192
+ // triple — found by meaning (semantic search on the symptom) + area/status.
10193
+ // The most standalone type; no typed edge.
10194
+ typeName: "gotcha",
10195
+ displayName: "Gotcha",
10196
+ indexMode: "HYBRID",
10197
+ fields: [
10198
+ {
10199
+ fieldId: "symptom",
10200
+ fieldType: "string",
10201
+ required: true,
10202
+ searchable: true,
10203
+ validation: { minLength: 1, maxLength: 500 },
10204
+ renderHints: { label: "Symptom", widget: "textarea", order: 1, displayField: true }
10205
+ },
10206
+ {
10207
+ fieldId: "cause",
10208
+ fieldType: "string",
10209
+ searchable: true,
10210
+ renderHints: { label: "Cause", widget: "textarea", order: 2 }
10211
+ },
10212
+ {
10213
+ fieldId: "fix",
10214
+ fieldType: "string",
10215
+ searchable: true,
10216
+ renderHints: { label: "Fix", widget: "textarea", order: 3 }
10217
+ },
10218
+ {
10219
+ fieldId: "area",
10220
+ fieldType: "string",
10221
+ filterable: true,
10222
+ renderHints: { label: "Area", widget: "text", order: 4 }
10223
+ },
10224
+ {
10225
+ fieldId: "status",
10226
+ fieldType: "enum",
10227
+ filterable: true,
10228
+ enumValues: ["active", "resolved"],
10229
+ renderHints: { label: "Status", widget: "select", order: 5 }
10230
+ },
10231
+ {
10232
+ fieldId: "tags",
10233
+ fieldType: "array",
10234
+ filterable: true,
10235
+ renderHints: { label: "Tags", order: 6 }
10236
+ },
10237
+ {
10238
+ fieldId: "discoveredOn",
10239
+ fieldType: "date",
10240
+ description: "ISO-8601 \u2014 when first hit. Range-queryable.",
10241
+ renderHints: { label: "Discovered on", widget: "date", order: 7 }
10242
+ }
10243
+ ],
10244
+ lookupFields: ["area", "status", { fieldName: "discoveredOn", rangeEnabled: true }]
10245
+ },
10246
+ {
10247
+ // A glossary TERM — a definition keyed by the term itself. Structure-dominant:
10248
+ // `term` is a UNIQUE exact-lookup key (the showcase of a uniqueness constraint),
10249
+ // `definition` is the RAG body, `aliases` catch alternate names. Links to the
10250
+ // decision that defines/establishes the concept where there is one.
10251
+ typeName: "term",
10252
+ displayName: "Glossary term",
10253
+ indexMode: "HYBRID",
10254
+ fields: [
10255
+ {
10256
+ fieldId: "term",
10257
+ fieldType: "string",
10258
+ required: true,
10259
+ searchable: true,
10260
+ validation: { minLength: 1, maxLength: 200 },
10261
+ renderHints: { label: "Term", widget: "text", order: 1, displayField: true }
10262
+ },
10263
+ {
10264
+ fieldId: "definition",
10265
+ fieldType: "string",
10266
+ searchable: true,
10267
+ description: "What the term means \u2014 the RAG-able body.",
10268
+ renderHints: { label: "Definition", widget: "textarea", order: 2 }
10269
+ },
10270
+ {
10271
+ fieldId: "aliases",
10272
+ fieldType: "array",
10273
+ filterable: true,
10274
+ description: "Alternate names / abbreviations for the same concept.",
10275
+ renderHints: { label: "Aliases", order: 3 }
10276
+ },
10277
+ {
10278
+ fieldId: "area",
10279
+ fieldType: "string",
10280
+ filterable: true,
10281
+ renderHints: { label: "Area", widget: "text", order: 4 }
10282
+ },
10283
+ {
10284
+ fieldId: "tags",
10285
+ fieldType: "array",
10286
+ filterable: true,
10287
+ renderHints: { label: "Tags", order: 5 }
10288
+ },
10289
+ {
10290
+ fieldId: "relatedDecision",
10291
+ fieldType: "reference",
10292
+ targetTypeName: "decision",
10293
+ targetSurface: "document",
10294
+ targetField: "externalId",
10295
+ cardinality: "one",
10296
+ description: "A decision that defines or establishes this term (by externalId).",
10297
+ renderHints: { label: "Related decision", order: 6 }
10298
+ },
10299
+ {
10300
+ fieldId: "updatedOn",
10301
+ fieldType: "date",
10302
+ description: "ISO-8601 \u2014 when last revised. Range-queryable.",
10303
+ renderHints: { label: "Updated on", widget: "date", order: 7 }
10304
+ }
10305
+ ],
10306
+ // `term` is a UNIQUE equality lookup — exact "define X" + a one-per-term
10307
+ // guarantee. `area`/`relatedDecision` enumerate; `updatedOn` is the range row.
10308
+ lookupFields: [
10309
+ { fieldName: "term", unique: true },
10310
+ "area",
10311
+ "relatedDecision",
10312
+ { fieldName: "updatedOn", rangeEnabled: true }
10313
+ ]
10314
+ }
10315
+ ],
10316
+ // Least-privilege, data-plane only. The scope of the `ssk_*` key the bootstrap
10317
+ // mints for THIS blueprint's service principal (the MCP/API runtime). See
10318
+ // DATA_PLANE_ACTIONS above for the action set + rationale.
10319
+ accessProfile: {
10320
+ allowedActions: DATA_PLANE_ACTIONS
10321
+ },
10322
+ // A reusable `editor` role for the HUMAN owner — DISTINCT from `accessProfile`
10323
+ // (which scopes only the service-principal key). `bootstrap` provisions this
10324
+ // role in the context but binds it to no one; the owner joins themselves so the
10325
+ // data-plane app (app.vectros.ai) shows their KB — its switcher lists only
10326
+ // contexts the signed-in user holds an active access profile in, and bootstrap
10327
+ // grants the human none by default. Bind it after bootstrap with:
10328
+ // vectros access grant --principal usr_<your-user-id> --context agentic-sdlc --role editor
10329
+ // (or the admin app's Access > Contexts > agentic-sdlc > Profiles > Create).
10330
+ // Editor PARITY with the service key (same DATA_PLANE_ACTIONS) so a human
10331
+ // curator can browse AND write/correct the KB; still no :d and no control-plane
10332
+ // action, so the scope gate accepts it exactly like the accessProfile.
10333
+ roles: {
10334
+ editor: [{ allowedActions: DATA_PLANE_ACTIONS }]
10335
+ },
10336
+ servicePrincipal: {
10337
+ externalId: "agentic-sdlc",
10338
+ displayName: "Agentic SDLC Knowledge Base"
10339
+ }
10340
+ // No bundled seed in this version: the content artifacts live on the document
10341
+ // surface and the cross-surface graph is populated by the ingest agent
10342
+ // (document_ingest / record_create), not the bootstrap seed step. Production
10343
+ // contexts provision with `vectros bootstrap --no-seed` regardless.
10344
+ };
10345
+ var agentic_sdlc_default = agenticSdlc;
10346
+ var secondBrain = {
10347
+ name: "second-brain",
10348
+ version: "1.0.0",
10349
+ description: "A personal knowledge base \u2014 capture notes, ideas, and links, then ask them anything.",
10350
+ contextId: "second-brain",
10351
+ contextName: "Second Brain",
10352
+ schemas: [
10353
+ {
10354
+ typeName: "note",
10355
+ displayName: "Note",
10356
+ indexMode: "HYBRID",
10357
+ // keyword on title + semantic on body — recall by meaning
10358
+ fields: [
10359
+ {
10360
+ fieldId: "title",
10361
+ fieldType: "string",
10362
+ required: true,
10363
+ searchable: true,
10364
+ validation: { minLength: 1, maxLength: 200 },
10365
+ renderHints: { label: "Title", widget: "text", order: 1, section: "Note", displayField: true }
10366
+ },
10367
+ {
10368
+ fieldId: "body",
10369
+ fieldType: "string",
10370
+ searchable: true,
10371
+ description: "The note itself \u2014 the RAG-able body you ask questions against.",
10372
+ validation: { maxLength: 2e4 },
10373
+ renderHints: { label: "Body", widget: "textarea", order: 2, section: "Note" }
10374
+ },
10375
+ {
10376
+ fieldId: "tags",
10377
+ fieldType: "array",
10378
+ filterable: true,
10379
+ description: 'Freeform string labels for filtering (e.g. "idea", "reading", "work").',
10380
+ renderHints: { label: "Tags", order: 3, section: "Organize" }
10381
+ },
10382
+ {
10383
+ // An enum (not freeform) so enumeration-by-source is reliable — a
10384
+ // `record_query` lookup on `source` only works if values are consistent.
10385
+ // `other` keeps it from being a straitjacket for a personal brain-dump.
10386
+ fieldId: "source",
10387
+ fieldType: "enum",
10388
+ filterable: true,
10389
+ enumValues: ["thought", "web", "meeting", "book", "article", "other"],
10390
+ description: "Where it came from.",
10391
+ renderHints: { label: "Source", widget: "select", order: 4, section: "Organize" }
10392
+ },
10393
+ {
10394
+ fieldId: "status",
10395
+ fieldType: "enum",
10396
+ filterable: true,
10397
+ enumValues: ["active", "archived"],
10398
+ description: "Archive instead of delete, so nothing is ever lost.",
10399
+ renderHints: { label: "Status", widget: "select", order: 5, section: "Organize" }
10400
+ },
10401
+ {
10402
+ fieldId: "capturedAt",
10403
+ fieldType: "date",
10404
+ description: 'ISO-8601 capture date. Range-queryable \u2014 "notes from last week".',
10405
+ renderHints: { label: "Captured", widget: "date", order: 6, section: "Organize" }
10406
+ }
10407
+ // externalId is the record's FIRST-CLASS identifier (the dedup/upsert key + the
10408
+ // value a `reference` resolves against) — not a payload field; the loader sends
10409
+ // it top-level on the RecordRequest.
10410
+ ],
10411
+ // Lookup shapes are MIGRATION-LOCKED once the schema is live (a field cannot
10412
+ // flip equality↔range later), so each is chosen on purpose:
10413
+ // • `source` — EQUALITY, sorted by `lastUpdated`: enumerate notes from one
10414
+ // source, most-recently-touched first. `lastUpdated` is always present, so
10415
+ // the sort never drops a note (sorting an equality lookup by an OPTIONAL
10416
+ // field would silently exclude rows that lack it).
10417
+ // • `status` — EQUALITY: "show archived notes".
10418
+ // • `capturedAt` — RANGE: ordered `from`/`to`/`prefix` ("captured in 2026-06").
10419
+ // `externalId` has a built-in first-class finder — look it up directly, never
10420
+ // redeclare it here. Equality lookups use the 7 fast index slots (2 used here);
10421
+ // range lookups use a relationship row, billed at the range rate.
10422
+ lookupFields: [
10423
+ { fieldName: "source", sortBy: "lastUpdated" },
10424
+ "status",
10425
+ { fieldName: "capturedAt", rangeEnabled: true }
10426
+ ]
10427
+ }
10428
+ ],
10429
+ // Least-privilege profile — r/c/u + search + schema discovery + inference:r
10430
+ // (so the agent can `rag_ask` a grounded, cited question over your notes). No
10431
+ // records:d: notes are archived (status → 'archived'), never deleted.
10432
+ accessProfile: {
10433
+ allowedActions: ["records:r", "records:c", "records:u", "search:r", "schemas:r", "inference:r"]
10434
+ },
10435
+ servicePrincipal: {
10436
+ externalId: "second-brain",
10437
+ displayName: "Second Brain"
10438
+ },
10439
+ seed: [
10440
+ {
10441
+ surface: "record",
10442
+ typeName: "note",
10443
+ externalId: "seed-welcome",
10444
+ fields: {
10445
+ title: "Welcome to your Second Brain",
10446
+ 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.',
10447
+ tags: ["getting-started"],
10448
+ source: "thought",
10449
+ status: "active",
10450
+ capturedAt: "2026-06-14"
10451
+ }
10452
+ }
10453
+ ]
10454
+ };
10455
+ var second_brain_default = secondBrain;
10456
+ var clinicalIntake = {
10457
+ name: "clinical-intake",
10458
+ version: "1.0.0",
10459
+ description: "Behavioral-health intake with PHI fields \u2014 demonstrates redact-at-write, audit history, blind-index lookup, and search exclusion. Synthetic data only.",
10460
+ contextId: "clinical-intake",
10461
+ contextName: "Clinical Intake",
10462
+ schemas: [
10463
+ {
10464
+ typeName: "intake",
10465
+ displayName: "Intake",
10466
+ indexMode: "HYBRID",
10467
+ capabilities: { auditHistory: true },
10468
+ // self-documenting compliance posture
10469
+ fields: [
10470
+ // --- Non-sensitive, searchable/filterable working fields ---
10471
+ {
10472
+ fieldId: "caseId",
10473
+ fieldType: "string",
10474
+ required: true,
10475
+ description: "Caller-stable intake id; the dedup/lookup key.",
10476
+ validation: { minLength: 1, maxLength: 64 },
10477
+ renderHints: { label: "Case ID", widget: "text", order: 1, section: "Intake", displayField: true }
10478
+ },
10479
+ {
10480
+ fieldId: "presentingConcern",
10481
+ fieldType: "string",
10482
+ searchable: true,
10483
+ description: "Non-PHI summary of the presenting concern \u2014 safe to index + search.",
10484
+ validation: { maxLength: 2e3 },
10485
+ renderHints: { label: "Presenting concern", widget: "textarea", order: 2, section: "Intake" }
9665
10486
  },
9666
10487
  {
9667
10488
  fieldId: "program",
@@ -9749,6 +10570,7 @@ var clinicalIntake = {
9749
10570
  },
9750
10571
  seed: [
9751
10572
  {
10573
+ surface: "record",
9752
10574
  typeName: "intake",
9753
10575
  externalId: "seed-synthetic-intake",
9754
10576
  fields: {
@@ -9770,6 +10592,7 @@ var clinical_intake_default = clinicalIntake;
9770
10592
  var BUNDLED_BLUEPRINTS = [
9771
10593
  task_management_default,
9772
10594
  coding_agent_memory_default,
10595
+ agentic_sdlc_default,
9773
10596
  second_brain_default,
9774
10597
  clinical_intake_default
9775
10598
  ];
@@ -10207,7 +11030,8 @@ async function resolveCognitoToken(env, explicit) {
10207
11030
  async function resolveAuthContext(opts) {
10208
11031
  const baseUrl = envToBaseUrl(opts.env, opts.baseUrl ?? process.env.VECTROS_API_BASE_URL);
10209
11032
  const cognitoToken = await resolveCognitoToken(opts.env, opts.token);
10210
- const bridge = opts.provisioning ? await mintBridgeToken({ cognitoToken, apiBaseUrl: baseUrl }) : await mintContextToken({ cognitoToken, apiBaseUrl: baseUrl, contextId: opts.context ?? "default" });
11033
+ const tenant = opts.tenant ?? "live";
11034
+ const bridge = opts.provisioning ? await mintBridgeToken({ cognitoToken, apiBaseUrl: baseUrl, tenant }) : await mintContextToken({ cognitoToken, apiBaseUrl: baseUrl, contextId: opts.context ?? "default", tenant });
10211
11035
  const client = new VectrosClient({ token: bridge.token, environment: baseUrl });
10212
11036
  const identity = await resolveIdentity({ log: opts.log, apiKey: bridge.token, environment: baseUrl });
10213
11037
  return {
@@ -10217,7 +11041,8 @@ async function resolveAuthContext(opts) {
10217
11041
  baseUrl,
10218
11042
  env: opts.env,
10219
11043
  identity,
10220
- tenantId: typeof identity.tenantId === "string" ? identity.tenantId : void 0
11044
+ tenantId: typeof identity.tenantId === "string" ? identity.tenantId : void 0,
11045
+ tenant
10221
11046
  };
10222
11047
  }
10223
11048
 
@@ -10541,39 +11366,6 @@ function assertNoUnresolvedIdentities(blueprint) {
10541
11366
  );
10542
11367
  }
10543
11368
  }
10544
- var SchemaReconcileError = class extends Error {
10545
- constructor(typeName, cause) {
10546
- const detail = cause instanceof Error ? cause.message : String(cause);
10547
- super(
10548
- `Schema '${typeName}' already exists but could not be reconciled to the blueprint: ${detail}
10549
- If this is a migration-locked attribute (a lookup field's rangeEnabled/sortBy/sensitive, or the schema typeName), it cannot change on an existing schema \u2014 use a new fieldName/typeName for the new shape.`
10550
- );
10551
- this.name = "SchemaReconcileError";
10552
- }
10553
- };
10554
- function isAlreadyExistsError(e) {
10555
- if (typeof e !== "object" || e === null) return false;
10556
- const err = e;
10557
- const status = err.statusCode;
10558
- const text = `${err.message ?? ""} ${err.body?.message ?? ""}`.toLowerCase();
10559
- return (status === 409 || status === 400) && text.includes("already exists");
10560
- }
10561
- async function findExistingSchemaId(client, typeName, owner) {
10562
- let startFrom;
10563
- for (; ; ) {
10564
- const page = await client.schemas.listSchemas({
10565
- userId: owner.userId,
10566
- orgId: owner.orgId,
10567
- limit: 100,
10568
- startFrom
10569
- });
10570
- const match = (page.data ?? []).find((s) => s.typeName === typeName);
10571
- if (match?.id) return match.id;
10572
- const next = page.nextCursor;
10573
- if (!next) return void 0;
10574
- startFrom = next;
10575
- }
10576
- }
10577
11369
  async function runLoader(client, input) {
10578
11370
  const { blueprint, tenantId, machineId, rotate = false, skipSeed = false, vectrosDir, log } = input;
10579
11371
  assertScopeGate(blueprint.accessProfile.allowedActions);
@@ -10581,14 +11373,16 @@ async function runLoader(client, input) {
10581
11373
  for (const clause of clauses) assertScopeGate(clause.allowedActions);
10582
11374
  }
10583
11375
  assertNoUnresolvedIdentities(blueprint);
10584
- await client.auth.createAppContext({ contextId: blueprint.contextId, name: contextNameOf(blueprint) });
11376
+ await client.auth.createAppContext({ body: { contextId: blueprint.contextId, name: contextNameOf(blueprint) } });
10585
11377
  log.debug({ component: "loader", contextId: blueprint.contextId }, "app-context ensured");
10586
11378
  const user = await client.identity.createUser({
10587
- externalId: blueprint.servicePrincipal.externalId,
10588
- type: "SERVICE",
10589
- // SDK 0.18+ (identity-access rework): UserRequest's free-form attribute bag
10590
- // was renamed `metadata` → `payload`. Same semantics (arbitrary key/value).
10591
- payload: { displayName: blueprint.servicePrincipal.displayName }
11379
+ body: {
11380
+ externalId: blueprint.servicePrincipal.externalId,
11381
+ type: "SERVICE",
11382
+ // SDK 0.18+ (identity-access rework): UserRequest's free-form attribute bag
11383
+ // was renamed `metadata` → `payload`. Same semantics (arbitrary key/value).
11384
+ payload: { displayName: blueprint.servicePrincipal.displayName }
11385
+ }
10592
11386
  });
10593
11387
  const userId = user.id;
10594
11388
  if (!userId) {
@@ -10623,23 +11417,9 @@ async function runLoader(client, input) {
10623
11417
  orgId: s.orgId,
10624
11418
  clientId: s.clientId
10625
11419
  };
10626
- let resp;
10627
- try {
10628
- resp = await ctxClient.schemas.createSchema(schemaBody);
10629
- } catch (e) {
10630
- if (!isAlreadyExistsError(e)) throw e;
10631
- if (s.clientId) throw e;
10632
- const existingId = await findExistingSchemaId(ctxClient, s.typeName, { userId: s.userId, orgId: s.orgId });
10633
- if (!existingId) throw e;
10634
- try {
10635
- resp = await ctxClient.schemas.updateSchema({ id: existingId, body: schemaBody });
10636
- } catch (ue) {
10637
- throw new SchemaReconcileError(s.typeName, ue);
10638
- }
10639
- log.debug({ component: "loader", typeName: s.typeName, schemaId: existingId }, "schema existed; reconciled to declared shape (update)");
10640
- }
11420
+ const resp = await ctxClient.schemas.createSchema({ upsert: true, body: schemaBody });
10641
11421
  if (resp.id) schemaIds[s.typeName] = resp.id;
10642
- log.debug({ component: "loader", typeName: s.typeName, schemaId: resp.id }, "schema ensured");
11422
+ log.debug({ component: "loader", typeName: s.typeName, schemaId: resp.id }, "schema ensured (upsert)");
10643
11423
  }
10644
11424
  const profileBody = {
10645
11425
  principalId: `usr_${userId}`,
@@ -10715,25 +11495,40 @@ async function runLoader(client, input) {
10715
11495
  log.debug({ component: "loader", keyName, keyAction }, "scoped key resolved");
10716
11496
  let seededCount = 0;
10717
11497
  const seededRecordIds = [];
11498
+ const seededDocumentIds = [];
10718
11499
  for (const rec of skipSeed ? [] : blueprint.seed ?? []) {
10719
11500
  const schemaId = schemaIds[rec.typeName];
10720
11501
  if (!schemaId) {
10721
11502
  log.warn({ component: "loader", typeName: rec.typeName }, "seed skipped \u2014 no schemaId for typeName");
10722
11503
  continue;
10723
11504
  }
10724
- const created = await ctxClient.records.createRecord({
10725
- typeName: rec.typeName,
10726
- schemaId,
10727
- // externalId is the FIRST-CLASS RecordRequest field — the platform's idempotency
10728
- // key AND the value other records resolve a `reference` against. Send it
10729
- // TOP-LEVEL (never in payload): payload-only leaves the record's first-class
10730
- // externalId null, so re-apply duplicates seeds (no dedup) and a reference with
10731
- // the default targetField=externalId can never resolve the target. It is not a
10732
- // payload field — the bundled schemas correctly do NOT declare one.
10733
- externalId: rec.externalId,
10734
- payload: rec.fields
10735
- });
10736
- if (created.id) seededRecordIds.push(created.id);
11505
+ if (rec.surface === "document") {
11506
+ const created = await ctxClient.documents.ingestDocument({
11507
+ body: {
11508
+ title: rec.title,
11509
+ text: rec.text,
11510
+ schemaId,
11511
+ externalId: rec.externalId,
11512
+ // Optional structured payload (the document's `fields`); omitted when absent.
11513
+ payload: rec.fields,
11514
+ // Retain the raw text so a seeded document is retrievable via
11515
+ // GET /v1/documents/{id}/text (mirrors the MCP document_ingest default) —
11516
+ // a seed's text IS its content, worth keeping for the getting-started graph.
11517
+ storeText: true
11518
+ }
11519
+ });
11520
+ if (created.id) seededDocumentIds.push(created.id);
11521
+ } else {
11522
+ const created = await ctxClient.records.createRecord({
11523
+ body: {
11524
+ typeName: rec.typeName,
11525
+ schemaId,
11526
+ externalId: rec.externalId,
11527
+ payload: rec.fields
11528
+ }
11529
+ });
11530
+ if (created.id) seededRecordIds.push(created.id);
11531
+ }
10737
11532
  seededCount++;
10738
11533
  }
10739
11534
  return {
@@ -10746,7 +11541,8 @@ async function runLoader(client, input) {
10746
11541
  schemaIds,
10747
11542
  roleIds,
10748
11543
  seededCount,
10749
- seededRecordIds
11544
+ seededRecordIds,
11545
+ seededDocumentIds
10750
11546
  };
10751
11547
  }
10752
11548
  function buildPlan(blueprint, opts = {}) {
@@ -10781,9 +11577,14 @@ function buildPlan(blueprint, opts = {}) {
10781
11577
  });
10782
11578
  }
10783
11579
  steps.push({ kind: "key", summary: `key ${blueprint.name}-<machine> (mint 1 scoped ssk_*)` });
10784
- const seedCount = blueprint.seed?.length ?? 0;
10785
- if (seedCount > 0 && !opts.noSeed) {
10786
- steps.push({ kind: "seed", summary: `seed ${seedCount} record(s)` });
11580
+ const seeds = blueprint.seed ?? [];
11581
+ if (seeds.length > 0 && !opts.noSeed) {
11582
+ const docs = seeds.filter((s) => s.surface === "document").length;
11583
+ const recs = seeds.length - docs;
11584
+ const parts = [];
11585
+ if (recs > 0) parts.push(`${recs} record${recs === 1 ? "" : "s"}`);
11586
+ if (docs > 0) parts.push(`${docs} document${docs === 1 ? "" : "s"}`);
11587
+ steps.push({ kind: "seed", summary: `seed ${parts.join(" + ")}` });
10787
11588
  }
10788
11589
  return steps;
10789
11590
  }
@@ -10909,6 +11710,7 @@ function parseBootstrapArgs(argv) {
10909
11710
  blankSlate: false,
10910
11711
  noSeed: false,
10911
11712
  client: "desktop",
11713
+ tenant: "live",
10912
11714
  help: false
10913
11715
  };
10914
11716
  for (let i = 0; i < argv.length; i++) {
@@ -10959,6 +11761,17 @@ function parseBootstrapArgs(argv) {
10959
11761
  case "--no-seed":
10960
11762
  args.noSeed = true;
10961
11763
  break;
11764
+ case "--tenant": {
11765
+ const v = argv[++i];
11766
+ if (v !== "test" && v !== "live") {
11767
+ throw new Error(`--tenant must be 'test' or 'live' (got '${v ?? ""}')`);
11768
+ }
11769
+ args.tenant = v;
11770
+ break;
11771
+ }
11772
+ case "--test":
11773
+ args.tenant = "test";
11774
+ break;
10962
11775
  case "--help":
10963
11776
  case "-h":
10964
11777
  args.help = true;
@@ -10987,7 +11800,8 @@ var BOOTSTRAP_HELP = `vectros bootstrap \u2014 provision a least-privilege MCP c
10987
11800
 
10988
11801
  Usage:
10989
11802
  vectros bootstrap [--blueprint <name> | --blank] [--no-seed] [--env staging|production]
10990
- [--client desktop|code] [--yes] [--print] [--rotate]
11803
+ [--tenant test|live | --test] [--client desktop|code]
11804
+ [--yes] [--print] [--rotate]
10991
11805
  [--token <t> | env VECTROS_BOOTSTRAP_TOKEN]
10992
11806
  [--base-url <url>] [--use-scoped-token]
10993
11807
 
@@ -11001,6 +11815,12 @@ Options:
11001
11815
  (a blueprint's seeds are illustrative, not real data). Unlike
11002
11816
  --blank, the schemas are still created.
11003
11817
  --env staging|production Target environment (default: production).
11818
+ --tenant test|live Which tenant the minted key scopes to (default: live).
11819
+ 'test' provisions into your isolated test tenant \u2014 a
11820
+ dry-run before pointing real data at the same blueprint.
11821
+ The tenant rides the key (ssk_test_* vs ssk_live_*), so
11822
+ the MCP/client config is otherwise identical.
11823
+ --test Shorthand for --tenant test.
11004
11824
  --base-url <url> Override the API base URL (beats --env + VECTROS_API_BASE_URL).
11005
11825
  --client <desktop|code> MCP host to configure (default: desktop). 'desktop' merges
11006
11826
  claude_desktop_config.json (also works for Cursor/Cline);
@@ -11069,6 +11889,21 @@ async function resolveBridgeCognitoToken(args, prompt, isTty, resolveCognito = r
11069
11889
  if (!pasted) throw new Error("No token entered.");
11070
11890
  return pasted;
11071
11891
  }
11892
+ function buildRemintForContext(opts) {
11893
+ const mintContext = opts.mintContext ?? mintContextToken;
11894
+ return async (contextId) => {
11895
+ const ctxToken = await mintContext({
11896
+ cognitoToken: opts.cognitoToken,
11897
+ apiBaseUrl: opts.apiBaseUrl,
11898
+ contextId,
11899
+ tenant: opts.tenant
11900
+ });
11901
+ return new VectrosClient({
11902
+ token: ctxToken.token,
11903
+ environment: opts.apiBaseUrl
11904
+ });
11905
+ };
11906
+ }
11072
11907
  async function runBootstrapCli(argv, deps = {}) {
11073
11908
  const log = deps.log ?? createLogger2();
11074
11909
  let args;
@@ -11150,12 +11985,17 @@ ${renderPlan(blueprint, planSteps)}
11150
11985
  isTty,
11151
11986
  deps.resolveCognitoToken
11152
11987
  );
11153
- const bridge = await mintBridgeToken({
11988
+ const mintBridge = deps.mintBridgeToken ?? mintBridgeToken;
11989
+ const mintContext = deps.mintContextToken ?? mintContextToken;
11990
+ const bridge = await mintBridge({
11154
11991
  cognitoToken,
11155
11992
  apiBaseUrl: baseUrl,
11156
- useScopedToken: args.useScopedToken
11993
+ useScopedToken: args.useScopedToken,
11994
+ // Tenant rides the key (ssk_test_* vs ssk_live_*), not the base URL.
11995
+ tenant: args.tenant
11157
11996
  });
11158
- process.stdout.write(" \u2714 Bootstrap token minted (5-min TTL)\n");
11997
+ process.stdout.write(` \u2714 Bootstrap token minted (5-min TTL, ${args.tenant} tenant)
11998
+ `);
11159
11999
  const client = new VectrosClient({ token: bridge.token, environment: baseUrl });
11160
12000
  const identity = await resolveIdentity({ log, apiKey: bridge.token, environment: baseUrl });
11161
12001
  const tenantId = identity.tenantId;
@@ -11171,17 +12011,16 @@ ${renderPlan(blueprint, planSteps)}
11171
12011
  machineId,
11172
12012
  rotate: args.rotate,
11173
12013
  skipSeed: noSeed,
11174
- remintForContext: async (contextId) => {
11175
- const ctxToken = await mintContextToken({ cognitoToken, apiBaseUrl: baseUrl, contextId });
11176
- return new VectrosClient({
11177
- token: ctxToken.token,
11178
- environment: baseUrl
11179
- });
11180
- },
12014
+ remintForContext: buildRemintForContext({
12015
+ cognitoToken,
12016
+ apiBaseUrl: baseUrl,
12017
+ tenant: args.tenant,
12018
+ mintContext
12019
+ }),
11181
12020
  log
11182
12021
  });
11183
12022
  process.stdout.write(
11184
- ` \u2714 AccessProfile + key (${result.keyAction}) \u2192 ${result.keyName}
12023
+ ` \u2714 AccessProfile + ${args.tenant} key (${result.keyAction}) \u2192 ${result.keyName}
11185
12024
  ` + seedSummaryLine({
11186
12025
  noSeed,
11187
12026
  declinedInteractively: seedDeclinedInteractively,
@@ -11397,7 +12236,8 @@ servicePrincipal:
11397
12236
  displayName: ${name}
11398
12237
 
11399
12238
  seed:
11400
- - typeName: thing
12239
+ - surface: record # 'record' (createRecord) or 'document' (text-ingest: needs title + text)
12240
+ typeName: thing
11401
12241
  externalId: seed-welcome # stable \u2192 idempotent; re-apply converges
11402
12242
  fields: { title: "Hello from ${name}" }
11403
12243
  `;
@@ -11745,6 +12585,7 @@ async function withAuth(opts, deps, body) {
11745
12585
  token: opts.token,
11746
12586
  context: opts.context,
11747
12587
  provisioning: opts.provisioning,
12588
+ tenant: opts.tenant,
11748
12589
  useScopedToken: opts.useScopedToken,
11749
12590
  log
11750
12591
  });
@@ -11775,12 +12616,63 @@ function splitCsv(csv) {
11775
12616
  return csv.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
11776
12617
  }
11777
12618
 
12619
+ // src/paginate.ts
12620
+ var PAGE_LIMIT = 100;
12621
+ async function drainPages(fetch2) {
12622
+ const out = [];
12623
+ let startFrom;
12624
+ for (; ; ) {
12625
+ const page = await fetch2(startFrom);
12626
+ if (page.data) out.push(...page.data);
12627
+ const next = page.nextCursor;
12628
+ if (!next) break;
12629
+ startFrom = next;
12630
+ }
12631
+ return out;
12632
+ }
12633
+
12634
+ // src/self-principal.ts
12635
+ var PRINCIPAL_PREFIX_USER = "usr_";
12636
+ function emailFromJwt(token) {
12637
+ const parts = token.split(".");
12638
+ if (parts.length < 2) return void 0;
12639
+ try {
12640
+ const payload = Buffer.from(parts[1], "base64url").toString("utf8");
12641
+ const claims = JSON.parse(payload);
12642
+ return typeof claims.email === "string" && claims.email.trim() ? claims.email.trim() : void 0;
12643
+ } catch {
12644
+ return void 0;
12645
+ }
12646
+ }
12647
+ async function resolveSelfPrincipal(client, cognitoToken) {
12648
+ const email = emailFromJwt(cognitoToken);
12649
+ if (!email) return null;
12650
+ const target = email.toLowerCase();
12651
+ const users = await drainPages(
12652
+ (startFrom) => client.identity.listUsers(
12653
+ startFrom === void 0 ? { limit: PAGE_LIMIT } : { startFrom, limit: PAGE_LIMIT }
12654
+ )
12655
+ );
12656
+ const match = users.find(
12657
+ (u) => u.id && typeof u.email === "string" && u.email.toLowerCase() === target
12658
+ );
12659
+ if (!match?.id) return null;
12660
+ return { principalId: `${PRINCIPAL_PREFIX_USER}${match.id}`, userId: match.id, email };
12661
+ }
12662
+
11778
12663
  // src/commands/whoami.ts
11779
12664
  async function runWhoami(options, deps = {}) {
11780
12665
  return withAuth(options, deps, async (auth) => {
11781
12666
  const { identity, baseUrl } = auth;
12667
+ let self2 = null;
12668
+ try {
12669
+ self2 = await resolveSelfPrincipal(auth.client, auth.cognitoToken);
12670
+ } catch {
12671
+ self2 = null;
12672
+ }
11782
12673
  if (options.json) {
11783
- process.stdout.write(`${formatJson(identity)}
12674
+ const out = self2 ? { ...identity, userId: self2.userId, userPrincipalId: self2.principalId } : identity;
12675
+ process.stdout.write(`${formatJson(out)}
11784
12676
  `);
11785
12677
  return 0;
11786
12678
  }
@@ -11792,6 +12684,7 @@ async function runWhoami(options, deps = {}) {
11792
12684
  ["principal", identity.principalType],
11793
12685
  ["principal id", identity.principalKeyId],
11794
12686
  ["label", identity.principalLabel],
12687
+ ["your user id", self2?.principalId],
11795
12688
  ["allowed actions", identity.allowedActions?.length ? identity.allowedActions.join(", ") : void 0],
11796
12689
  ["token expires", expiry],
11797
12690
  ["api", baseUrl]
@@ -11801,21 +12694,6 @@ async function runWhoami(options, deps = {}) {
11801
12694
  });
11802
12695
  }
11803
12696
 
11804
- // src/paginate.ts
11805
- var PAGE_LIMIT = 100;
11806
- async function drainPages(fetch2) {
11807
- const out = [];
11808
- let startFrom;
11809
- for (; ; ) {
11810
- const page = await fetch2(startFrom);
11811
- if (page.data) out.push(...page.data);
11812
- const next = page.nextCursor;
11813
- if (!next) break;
11814
- startFrom = next;
11815
- }
11816
- return out;
11817
- }
11818
-
11819
12697
  // src/commands/key.ts
11820
12698
  var DEFAULT_KEY_NAME = "default";
11821
12699
  function emitMintedKey(key, format) {
@@ -12021,19 +12899,23 @@ async function createIdentity(client, opts, payload) {
12021
12899
  switch (opts.type) {
12022
12900
  case "user":
12023
12901
  return client.identity.createUser({
12024
- externalId,
12025
- email: opts.email,
12026
- type: opts.service ? "SERVICE" : "HUMAN",
12027
- payload
12902
+ body: {
12903
+ externalId,
12904
+ email: opts.email,
12905
+ type: opts.service ? "SERVICE" : "HUMAN",
12906
+ payload
12907
+ }
12028
12908
  });
12029
12909
  case "org":
12030
- return client.identity.createOrg({ externalId, name: opts.name, payload });
12910
+ return client.identity.createOrg({ body: { externalId, name: opts.name, payload } });
12031
12911
  case "client":
12032
12912
  return client.identity.createClient({
12033
- externalId,
12034
- name: opts.name,
12035
- orgId: opts.org,
12036
- payload
12913
+ body: {
12914
+ externalId,
12915
+ name: opts.name,
12916
+ orgId: opts.org,
12917
+ payload
12918
+ }
12037
12919
  });
12038
12920
  }
12039
12921
  }
@@ -12201,6 +13083,17 @@ function assertPrincipalShape(principal) {
12201
13083
  throw new Error(`--principal must be 'usr_<userId>' or 'key_<keyId>' (got '${principal}').`);
12202
13084
  }
12203
13085
  }
13086
+ var SELF_PRINCIPAL_ALIAS = "me";
13087
+ async function resolvePrincipalArg(raw, auth) {
13088
+ if (raw !== SELF_PRINCIPAL_ALIAS) return raw;
13089
+ const self2 = await resolveSelfPrincipal(auth.client, auth.cognitoToken);
13090
+ if (!self2) {
13091
+ throw new Error(
13092
+ "Could not resolve your own user id from your login. Pass --principal usr_<id> explicitly (find the id with `vectros identity list --type user`)."
13093
+ );
13094
+ }
13095
+ return self2.principalId;
13096
+ }
12204
13097
  function renderProfile(p) {
12205
13098
  return formatKeyValues([
12206
13099
  ["context", p.contextId],
@@ -12217,21 +13110,24 @@ async function runAccessGrant(opts, deps = {}) {
12217
13110
  process.stderr.write(" \u2716 Provide exactly one of --role <roleId> or --actions <csv>.\n");
12218
13111
  return 2;
12219
13112
  }
12220
- try {
12221
- assertPrincipalShape(opts.principal);
12222
- } catch (err) {
12223
- process.stderr.write(` \u2716 ${formatError(err)}
13113
+ if (opts.principal !== SELF_PRINCIPAL_ALIAS) {
13114
+ try {
13115
+ assertPrincipalShape(opts.principal);
13116
+ } catch (err) {
13117
+ process.stderr.write(` \u2716 ${formatError(err)}
12224
13118
  `);
12225
- return 2;
13119
+ return 2;
13120
+ }
12226
13121
  }
12227
13122
  return withAuth(opts, deps, async (auth) => {
12228
13123
  const client = auth.client;
12229
13124
  try {
13125
+ const principal = await resolvePrincipalArg(opts.principal, auth);
12230
13126
  const scopes = hasActions ? [{ allowed_actions: splitCsv(opts.actions) }] : void 0;
12231
13127
  const profile = await client.auth.createAccessProfile({
12232
13128
  contextId: opts.context,
12233
13129
  body: {
12234
- principalId: opts.principal,
13130
+ principalId: principal,
12235
13131
  roleId: opts.role,
12236
13132
  scopes
12237
13133
  }
@@ -12243,7 +13139,7 @@ async function runAccessGrant(opts, deps = {}) {
12243
13139
  }
12244
13140
  process.stdout.write(
12245
13141
  `
12246
- \u2714 Granted ${opts.principal} in '${opts.context}'` + (hasRole ? ` via role '${opts.role}'` : ` with ${splitCsv(opts.actions).length} action(s)`) + `
13142
+ \u2714 Granted ${principal} in '${opts.context}'` + (hasRole ? ` via role '${opts.role}'` : ` with ${splitCsv(opts.actions).length} action(s)`) + `
12247
13143
  ${renderProfile(profile)}
12248
13144
  `
12249
13145
  );
@@ -12256,18 +13152,21 @@ ${renderProfile(profile)}
12256
13152
  });
12257
13153
  }
12258
13154
  async function runAccessRevoke(opts, deps = {}) {
12259
- try {
12260
- assertPrincipalShape(opts.principal);
12261
- } catch (err) {
12262
- process.stderr.write(` \u2716 ${formatError(err)}
13155
+ if (opts.principal !== SELF_PRINCIPAL_ALIAS) {
13156
+ try {
13157
+ assertPrincipalShape(opts.principal);
13158
+ } catch (err) {
13159
+ process.stderr.write(` \u2716 ${formatError(err)}
12263
13160
  `);
12264
- return 2;
13161
+ return 2;
13162
+ }
12265
13163
  }
12266
13164
  return withAuth(opts, deps, async (auth) => {
12267
13165
  const client = auth.client;
12268
13166
  try {
12269
- await client.auth.deleteAccessProfile({ contextId: opts.context, principalId: opts.principal });
12270
- process.stdout.write(` \u2714 Revoked ${opts.principal} from '${opts.context}'
13167
+ const principal = await resolvePrincipalArg(opts.principal, auth);
13168
+ await client.auth.deleteAccessProfile({ contextId: opts.context, principalId: principal });
13169
+ process.stdout.write(` \u2714 Revoked ${principal} from '${opts.context}'
12271
13170
  `);
12272
13171
  return 0;
12273
13172
  } catch (err) {
@@ -12282,7 +13181,7 @@ async function runAccessList(opts, deps = {}) {
12282
13181
  process.stderr.write(" \u2716 Provide exactly one of --context <c> or --principal <p>.\n");
12283
13182
  return 2;
12284
13183
  }
12285
- if (opts.principal) {
13184
+ if (opts.principal && opts.principal !== SELF_PRINCIPAL_ALIAS) {
12286
13185
  try {
12287
13186
  assertPrincipalShape(opts.principal);
12288
13187
  } catch (err) {
@@ -12294,10 +13193,11 @@ async function runAccessList(opts, deps = {}) {
12294
13193
  return withAuth(opts, deps, async (auth) => {
12295
13194
  const client = auth.client;
12296
13195
  try {
13196
+ const principal = opts.principal ? await resolvePrincipalArg(opts.principal, auth) : void 0;
12297
13197
  const rows = opts.context ? await drainPages(
12298
13198
  (startFrom) => client.auth.listAccessProfiles({ contextId: opts.context, startFrom, limit: PAGE_LIMIT })
12299
13199
  ) : await drainPages(
12300
- (startFrom) => client.auth.listProfilesForPrincipal({ principalId: opts.principal, startFrom, limit: PAGE_LIMIT })
13200
+ (startFrom) => client.auth.listProfilesForPrincipal({ principalId: principal, startFrom, limit: PAGE_LIMIT })
12301
13201
  );
12302
13202
  if (opts.json) {
12303
13203
  process.stdout.write(`${formatJson(rows)}
@@ -12321,17 +13221,20 @@ async function runAccessList(opts, deps = {}) {
12321
13221
  });
12322
13222
  }
12323
13223
  async function runAccessGet(opts, deps = {}) {
12324
- try {
12325
- assertPrincipalShape(opts.principal);
12326
- } catch (err) {
12327
- process.stderr.write(` \u2716 ${formatError(err)}
13224
+ if (opts.principal !== SELF_PRINCIPAL_ALIAS) {
13225
+ try {
13226
+ assertPrincipalShape(opts.principal);
13227
+ } catch (err) {
13228
+ process.stderr.write(` \u2716 ${formatError(err)}
12328
13229
  `);
12329
- return 2;
13230
+ return 2;
13231
+ }
12330
13232
  }
12331
13233
  return withAuth(opts, deps, async (auth) => {
12332
13234
  const client = auth.client;
12333
13235
  try {
12334
- const p = await client.auth.getAccessProfile({ contextId: opts.context, principalId: opts.principal });
13236
+ const principal = await resolvePrincipalArg(opts.principal, auth);
13237
+ const p = await client.auth.getAccessProfile({ contextId: opts.context, principalId: principal });
12335
13238
  if (opts.json) {
12336
13239
  process.stdout.write(`${formatJson(p)}
12337
13240
  `);
@@ -12474,8 +13377,10 @@ async function runContextCreate(opts, deps = {}) {
12474
13377
  const client = auth.client;
12475
13378
  try {
12476
13379
  const ctx = await client.auth.createAppContext({
12477
- contextId: opts.contextId,
12478
- name: opts.name ?? opts.contextId
13380
+ body: {
13381
+ contextId: opts.contextId,
13382
+ name: opts.name ?? opts.contextId
13383
+ }
12479
13384
  });
12480
13385
  if (opts.json) {
12481
13386
  process.stdout.write(`${formatJson(ctx)}
@@ -12786,23 +13691,22 @@ async function runAssertions(client, deps, blueprint, tenantId, result) {
12786
13691
  }
12787
13692
  }
12788
13693
  for (const rec of blueprint.seed ?? []) {
13694
+ const isDoc = rec.surface === "document";
13695
+ const op = isDoc ? "lookupDocuments" : "lookupRecords";
13696
+ const noun = isDoc ? "document" : "record";
12789
13697
  try {
12790
- const page = await client.records.lookupRecords({
12791
- type: rec.typeName,
12792
- field: "externalId",
12793
- value: rec.externalId
12794
- });
13698
+ const page = isDoc ? await client.documents.lookupDocuments({ type: rec.typeName, field: "externalId", value: rec.externalId }) : await client.records.lookupRecords({ type: rec.typeName, field: "externalId", value: rec.externalId });
12795
13699
  const ok = (page.data ?? []).length >= 1;
12796
13700
  assertions.push({
12797
13701
  name: `seed-externalId:${rec.externalId}`,
12798
13702
  ok,
12799
- 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?)`
13703
+ detail: ok ? `'${rec.typeName}' seed resolves by externalId '${rec.externalId}'` : `'${rec.typeName}' seed externalId '${rec.externalId}' resolved 0 ${noun}s \u2014 externalId is not first-class (written only in payload?)`
12800
13704
  });
12801
13705
  } catch (e) {
12802
13706
  assertions.push({
12803
13707
  name: `seed-externalId:${rec.externalId}`,
12804
13708
  ok: false,
12805
- detail: `lookupRecords(externalId='${rec.externalId}') failed: ${errMsg(e)}`
13709
+ detail: `${op}(externalId='${rec.externalId}') failed: ${errMsg(e)}`
12806
13710
  });
12807
13711
  }
12808
13712
  }
@@ -12860,6 +13764,14 @@ async function runTeardown(client, blueprint, machineId, snapshot, result) {
12860
13764
  steps.push({ step: `delete record ${id}`, ok: false, error: errMsg(e) });
12861
13765
  }
12862
13766
  }
13767
+ for (const id of result.seededDocumentIds) {
13768
+ try {
13769
+ await client.documents.deleteDocument({ id });
13770
+ steps.push({ step: `delete document ${id}`, ok: true });
13771
+ } catch (e) {
13772
+ steps.push({ step: `delete document ${id}`, ok: false, error: errMsg(e) });
13773
+ }
13774
+ }
12863
13775
  const principalId = `usr_${result.servicePrincipalUserId}`;
12864
13776
  if (snapshot.failed) {
12865
13777
  steps.push({ step: `delete access profile ${principalId}`, ok: true, skipped: true, error: "skipped \u2014 pre-apply snapshot failed; cannot prove ownership" });
@@ -12994,7 +13906,7 @@ function renderReport(blueprint, env, report) {
12994
13906
  if (report.applied) {
12995
13907
  const a = report.applied;
12996
13908
  lines.push(
12997
- ` \u2714 applied \u2014 key ${a.keyName} (${a.keyAction}), ${Object.keys(a.schemaIds).length} schema(s)` + (a.seededCount ? `, ${a.seededCount} seed record(s)` : "")
13909
+ ` \u2714 applied \u2014 key ${a.keyName} (${a.keyAction}), ${Object.keys(a.schemaIds).length} schema(s)` + (a.seededCount ? `, ${a.seededCount} seed(s)` : "")
12998
13910
  );
12999
13911
  } else {
13000
13912
  lines.push(` \u2716 apply failed \u2014 ${report.error ?? "unknown error"}`);
@@ -13057,7 +13969,10 @@ async function runBlueprintTest(fileOrName, opts, deps = {}) {
13057
13969
  const ctxToken = await mintContextToken({
13058
13970
  cognitoToken: auth.cognitoToken,
13059
13971
  apiBaseUrl: auth.baseUrl,
13060
- contextId
13972
+ contextId,
13973
+ // Same tenant as the bootstrap token (auth.tenant) — the in-context
13974
+ // load must land in the tenant the run targets.
13975
+ tenant: auth.tenant
13061
13976
  });
13062
13977
  return new VectrosClient({
13063
13978
  token: ctxToken.token,
@@ -13095,9 +14010,9 @@ async function runBlueprintTest(fileOrName, opts, deps = {}) {
13095
14010
 
13096
14011
  // src/build-info.ts
13097
14012
  var BUILD_INFO = {
13098
- cli: true ? "0.6.1" : "dev",
13099
- sdk: true ? "0.30.0" : "dev",
13100
- blueprints: true ? "0.5.0" : "dev"
14013
+ cli: true ? "0.6.5" : "dev",
14014
+ sdk: true ? "0.31.0" : "dev",
14015
+ blueprints: true ? "0.6.3" : "dev"
13101
14016
  };
13102
14017
  function formatBuildInfo() {
13103
14018
  return `vectros ${BUILD_INFO.cli} (sdk ${BUILD_INFO.sdk}, blueprints ${BUILD_INFO.blueprints})`;
@@ -13145,10 +14060,21 @@ function buildProgram(deps = {}) {
13145
14060
  addCommonAuthOptions(whoami).action(async (flags) => {
13146
14061
  process.exit(await runWhoami(toCommonOptions(flags), deps));
13147
14062
  });
13148
- const blueprintTest = program.command("blueprint-test").description("Apply a blueprint to a live env, assert it provisioned a working credential, then tear it down.").argument("<file>", "blueprint file path (./my.yaml) or a bundled blueprint name").option("--keep", "leave the provisioned graph in place (skip teardown) for inspection");
14063
+ const blueprintTest = program.command("blueprint-test").description("Apply a blueprint to a live env, assert it provisioned a working credential, then tear it down.").argument("<file>", "blueprint file path (./my.yaml) or a bundled blueprint name").option("--keep", "leave the provisioned graph in place (skip teardown) for inspection").option("--tenant <tenant>", "target tenant for the run: 'test' or 'live' (default live)");
13149
14064
  addCommonAuthOptions(blueprintTest).action(
13150
14065
  async (file, flags) => {
13151
- process.exit(await runBlueprintTest(file, { ...toCommonOptions(flags), keep: Boolean(flags.keep) }, deps));
14066
+ if (flags.tenant !== void 0 && flags.tenant !== "test" && flags.tenant !== "live") {
14067
+ process.stderr.write(` \u2716 --tenant must be 'test' or 'live' (got '${flags.tenant}')
14068
+ `);
14069
+ process.exit(2);
14070
+ }
14071
+ process.exit(
14072
+ await runBlueprintTest(
14073
+ file,
14074
+ { ...toCommonOptions(flags), keep: Boolean(flags.keep), tenant: flags.tenant },
14075
+ deps
14076
+ )
14077
+ );
13152
14078
  }
13153
14079
  );
13154
14080
  registerKeyCommands(program, deps);
@@ -13205,7 +14131,7 @@ function registerRoleCommands(program, deps) {
13205
14131
  }
13206
14132
  function registerAccessCommands(program, deps) {
13207
14133
  const access2 = program.command("access").description("Manage access bindings: principal \u2194 context \u2194 role/scopes.");
13208
- 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)");
14134
+ 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)");
13209
14135
  addCommonAuthOptions(grant).action(
13210
14136
  async (flags) => {
13211
14137
  process.exit(
@@ -13219,15 +14145,15 @@ function registerAccessCommands(program, deps) {
13219
14145
  );
13220
14146
  }
13221
14147
  );
13222
- 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");
14148
+ 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");
13223
14149
  addAuthOptions(revoke).action(async (flags) => {
13224
14150
  process.exit(await runAccessRevoke({ ...toCommonOptions(flags), principal: flags.principal, context: flags.context }, deps));
13225
14151
  });
13226
- 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");
14152
+ 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)");
13227
14153
  addCommonAuthOptions(list).action(async (flags) => {
13228
14154
  process.exit(await runAccessList({ ...toCommonOptions(flags), context: flags.context, principal: flags.principal }, deps));
13229
14155
  });
13230
- 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");
14156
+ 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");
13231
14157
  addCommonAuthOptions(get).action(async (flags) => {
13232
14158
  process.exit(await runAccessGet({ ...toCommonOptions(flags), principal: flags.principal, context: flags.context }, deps));
13233
14159
  });