@vectros-ai/cli 0.6.1 → 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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,149 +9648,831 @@ var codingAgentMemory = {
9523
9648
  ]
9524
9649
  };
9525
9650
  var coding_agent_memory_default = codingAgentMemory;
9526
- var secondBrain = {
9527
- name: "second-brain",
9651
+ var agenticSdlc = {
9652
+ name: "agentic-sdlc",
9528
9653
  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",
9654
+ description: "A whole-SDLC system of record for an AI development team \u2014 decisions, designs, references, runbooks, post-mortems (as documents) plus controls, conventions, gotchas, and a glossary (as records), cross-linked and recalled by meaning.",
9655
+ contextId: "agentic-sdlc",
9656
+ contextName: "Agentic SDLC Knowledge Base",
9532
9657
  schemas: [
9658
+ // ============================ DOCUMENTS ============================
9659
+ // Content-dominant: the markdown body is the artifact; fields are metadata.
9660
+ // Each binds the `document` surface and is ingested via `document_ingest`.
9533
9661
  {
9534
- typeName: "note",
9535
- displayName: "Note",
9662
+ // An architecture/product DECISION (an ADR). The body is the prose —
9663
+ // Context / Decision / Consequences — and is what `rag_ask` answers over;
9664
+ // the fields below are filter/sort metadata + the supersede chain. The
9665
+ // anchor of the graph: most other types link back to a decision.
9666
+ typeName: "decision",
9667
+ displayName: "Decision (ADR)",
9536
9668
  indexMode: "HYBRID",
9537
- // keyword on title + semantic on body — recall by meaning
9669
+ allowedSurfaces: ["document"],
9670
+ // A document carries an INTRINSIC title (the ingest title) + body; the schema
9671
+ // declares only the metadata BEYOND those. (No typed `title` field — that
9672
+ // would duplicate the document's own title.)
9538
9673
  fields: [
9539
9674
  {
9540
- fieldId: "title",
9675
+ fieldId: "summary",
9541
9676
  fieldType: "string",
9542
- required: true,
9543
9677
  searchable: true,
9544
- validation: { minLength: 1, maxLength: 200 },
9545
- renderHints: { label: "Title", widget: "text", order: 1, section: "Note", displayField: true }
9678
+ description: "A one-paragraph abstract of the decision. The full reasoning is the document body.",
9679
+ renderHints: { label: "Summary", widget: "textarea", order: 2 }
9546
9680
  },
9547
9681
  {
9548
- fieldId: "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" }
9682
+ fieldId: "status",
9683
+ fieldType: "enum",
9684
+ filterable: true,
9685
+ enumValues: ["proposed", "accepted", "superseded", "deprecated"],
9686
+ renderHints: { label: "Status", widget: "select", order: 3 }
9554
9687
  },
9555
9688
  {
9556
- fieldId: "tags",
9557
- fieldType: "array",
9689
+ fieldId: "area",
9690
+ fieldType: "string",
9558
9691
  filterable: true,
9559
- description: 'Freeform string labels for filtering (e.g. "idea", "reading", "work").',
9560
- renderHints: { label: "Tags", order: 3, section: "Organize" }
9692
+ description: 'Subsystem the decision applies to (e.g. "search", "auth", "billing").',
9693
+ renderHints: { label: "Area", widget: "text", order: 4 }
9561
9694
  },
9562
9695
  {
9563
- // 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",
9696
+ fieldId: "tags",
9697
+ fieldType: "array",
9568
9698
  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" }
9699
+ description: 'Freeform labels (e.g. "security", "schema"). Search-side filter.',
9700
+ renderHints: { label: "Tags", order: 5 }
9572
9701
  },
9573
9702
  {
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" }
9703
+ // Self-reference (document → document): the decision this one supersedes.
9704
+ fieldId: "supersedes",
9705
+ fieldType: "reference",
9706
+ targetTypeName: "decision",
9707
+ targetSurface: "document",
9708
+ targetField: "externalId",
9709
+ cardinality: "one",
9710
+ description: "The decision this one supersedes (by externalId).",
9711
+ renderHints: { label: "Supersedes", order: 6 }
9580
9712
  },
9581
9713
  {
9582
- fieldId: "capturedAt",
9714
+ fieldId: "date",
9583
9715
  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" }
9716
+ description: "ISO-8601 decision date. Range-queryable / sortable.",
9717
+ renderHints: { label: "Date", widget: "date", order: 7 }
9586
9718
  }
9587
- // externalId is the record's FIRST-CLASS identifier (the dedup/upsert key + the
9588
- // value a `reference` resolves against) — not a payload field; the loader sends
9589
- // it top-level on the RecordRequest.
9590
9719
  ],
9591
- // 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: [
9720
+ lookupFields: ["status", "area", "supersedes", { fieldName: "date", rangeEnabled: true }]
9721
+ },
9642
9722
  {
9643
- typeName: "intake",
9644
- displayName: "Intake",
9723
+ // A DESIGN doc or spec — the exploration that drives a decision. Distinct
9724
+ // from `decision` (a different browse genre + it links to the decision it
9725
+ // informs). Body = the design narrative.
9726
+ typeName: "design",
9727
+ displayName: "Design",
9645
9728
  indexMode: "HYBRID",
9646
- capabilities: { auditHistory: true },
9647
- // self-documenting compliance posture
9729
+ allowedSurfaces: ["document"],
9730
+ // Intrinsic title + body; schema = metadata beyond those (no typed `title`).
9648
9731
  fields: [
9649
- // --- Non-sensitive, searchable/filterable working fields ---
9650
9732
  {
9651
- fieldId: "caseId",
9652
- fieldType: "string",
9653
- required: true,
9654
- description: "Caller-stable intake id; the dedup/lookup key.",
9655
- validation: { minLength: 1, maxLength: 64 },
9656
- renderHints: { label: "Case ID", widget: "text", order: 1, section: "Intake", displayField: true }
9657
- },
9658
- {
9659
- fieldId: "presentingConcern",
9733
+ fieldId: "summary",
9660
9734
  fieldType: "string",
9661
9735
  searchable: true,
9662
- description: "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" }
9736
+ description: "A one-paragraph abstract. The full design is the document body.",
9737
+ renderHints: { label: "Summary", widget: "textarea", order: 2 }
9665
9738
  },
9666
9739
  {
9667
- fieldId: "program",
9668
- fieldType: "string",
9740
+ fieldId: "status",
9741
+ fieldType: "enum",
9742
+ filterable: true,
9743
+ enumValues: ["draft", "active", "implemented", "superseded"],
9744
+ renderHints: { label: "Status", widget: "select", order: 3 }
9745
+ },
9746
+ {
9747
+ fieldId: "area",
9748
+ fieldType: "string",
9749
+ filterable: true,
9750
+ renderHints: { label: "Area", widget: "text", order: 4 }
9751
+ },
9752
+ {
9753
+ fieldId: "tags",
9754
+ fieldType: "array",
9755
+ filterable: true,
9756
+ renderHints: { label: "Tags", order: 5 }
9757
+ },
9758
+ {
9759
+ // Cross-document edge: the decision this design informs/produces.
9760
+ fieldId: "relatedDecision",
9761
+ fieldType: "reference",
9762
+ targetTypeName: "decision",
9763
+ targetSurface: "document",
9764
+ targetField: "externalId",
9765
+ cardinality: "one",
9766
+ description: "The decision this design informs (by externalId).",
9767
+ renderHints: { label: "Related decision", order: 6 }
9768
+ },
9769
+ {
9770
+ // Self-reference: a design supersedes an earlier design/spec.
9771
+ fieldId: "supersedes",
9772
+ fieldType: "reference",
9773
+ targetTypeName: "design",
9774
+ targetSurface: "document",
9775
+ targetField: "externalId",
9776
+ cardinality: "one",
9777
+ description: "The design this one supersedes (by externalId).",
9778
+ renderHints: { label: "Supersedes", order: 7 }
9779
+ },
9780
+ {
9781
+ fieldId: "updatedOn",
9782
+ fieldType: "date",
9783
+ description: "ISO-8601 \u2014 when last revised. Range-queryable.",
9784
+ renderHints: { label: "Updated on", widget: "date", order: 8 }
9785
+ }
9786
+ ],
9787
+ lookupFields: ["status", "area", "relatedDecision", "supersedes", { fieldName: "updatedOn", rangeEnabled: true }]
9788
+ },
9789
+ {
9790
+ // A REFERENCE doc — maintained "how it works / how to": guides, onboarding,
9791
+ // API docs, process docs. Body = the prose. Distinct shape: a `category`
9792
+ // (DRY sub-labels — onboarding/api/process are same shape) and `lastReviewed`
9793
+ // (freshness is the whole game for reference material).
9794
+ typeName: "reference",
9795
+ displayName: "Reference",
9796
+ indexMode: "HYBRID",
9797
+ allowedSurfaces: ["document"],
9798
+ // Intrinsic title + body; schema = metadata beyond those (no typed `title`).
9799
+ fields: [
9800
+ {
9801
+ fieldId: "summary",
9802
+ fieldType: "string",
9803
+ searchable: true,
9804
+ description: "A one-paragraph abstract. The full reference is the document body.",
9805
+ renderHints: { label: "Summary", widget: "textarea", order: 2 }
9806
+ },
9807
+ {
9808
+ // The sub-kind, as a filterable field (not a separate schema — these are
9809
+ // the same shape, differing only by label, so a field is the DRY choice).
9810
+ fieldId: "category",
9811
+ fieldType: "enum",
9812
+ filterable: true,
9813
+ enumValues: ["guide", "onboarding", "api", "process", "other"],
9814
+ renderHints: { label: "Category", widget: "select", order: 3 }
9815
+ },
9816
+ {
9817
+ fieldId: "area",
9818
+ fieldType: "string",
9819
+ filterable: true,
9820
+ renderHints: { label: "Area", widget: "text", order: 4 }
9821
+ },
9822
+ {
9823
+ fieldId: "tags",
9824
+ fieldType: "array",
9825
+ filterable: true,
9826
+ renderHints: { label: "Tags", order: 5 }
9827
+ },
9828
+ {
9829
+ fieldId: "status",
9830
+ fieldType: "enum",
9831
+ filterable: true,
9832
+ enumValues: ["active", "superseded"],
9833
+ renderHints: { label: "Status", widget: "select", order: 6 }
9834
+ },
9835
+ {
9836
+ // Optional cross-document edge: the decision behind this reference.
9837
+ fieldId: "relatedDecision",
9838
+ fieldType: "reference",
9839
+ targetTypeName: "decision",
9840
+ targetSurface: "document",
9841
+ targetField: "externalId",
9842
+ cardinality: "one",
9843
+ description: "The decision behind this reference, if any (by externalId).",
9844
+ renderHints: { label: "Related decision", order: 7 }
9845
+ },
9846
+ {
9847
+ // Freshness — "references not reviewed since X". The reference-doc signal.
9848
+ fieldId: "lastReviewed",
9849
+ fieldType: "date",
9850
+ description: "ISO-8601 \u2014 when this reference was last verified current. Range-queryable.",
9851
+ renderHints: { label: "Last reviewed", widget: "date", order: 8 }
9852
+ }
9853
+ ],
9854
+ lookupFields: ["category", "area", "status", "relatedDecision", { fieldName: "lastReviewed", rangeEnabled: true }]
9855
+ },
9856
+ {
9857
+ // A RUNBOOK — a step-by-step operational procedure (deploy, release, recover).
9858
+ // Body = the procedure. Distinct browse genre, and often BORN FROM a
9859
+ // post-mortem (its resolution, codified) — a cross-document edge.
9860
+ typeName: "runbook",
9861
+ displayName: "Runbook",
9862
+ indexMode: "HYBRID",
9863
+ allowedSurfaces: ["document"],
9864
+ // Intrinsic title + body; schema = metadata beyond those (no typed `title`).
9865
+ fields: [
9866
+ {
9867
+ fieldId: "summary",
9868
+ fieldType: "string",
9869
+ searchable: true,
9870
+ description: "When to use this runbook \u2014 the trigger. The steps are the document body.",
9871
+ renderHints: { label: "Summary", widget: "textarea", order: 2 }
9872
+ },
9873
+ {
9874
+ fieldId: "area",
9875
+ fieldType: "string",
9876
+ filterable: true,
9877
+ renderHints: { label: "Area", widget: "text", order: 3 }
9878
+ },
9879
+ {
9880
+ fieldId: "tags",
9881
+ fieldType: "array",
9882
+ filterable: true,
9883
+ renderHints: { label: "Tags", order: 4 }
9884
+ },
9885
+ {
9886
+ fieldId: "status",
9887
+ fieldType: "enum",
9888
+ filterable: true,
9889
+ enumValues: ["active", "retired"],
9890
+ renderHints: { label: "Status", widget: "select", order: 5 }
9891
+ },
9892
+ {
9893
+ // Cross-document edge: the post-mortem whose resolution this runbook codifies.
9894
+ fieldId: "bornFrom",
9895
+ fieldType: "reference",
9896
+ targetTypeName: "postmortem",
9897
+ targetSurface: "document",
9898
+ targetField: "externalId",
9899
+ cardinality: "one",
9900
+ description: "The post-mortem this runbook was codified from (by externalId).",
9901
+ renderHints: { label: "Born from (post-mortem)", order: 6 }
9902
+ },
9903
+ {
9904
+ fieldId: "relatedDecision",
9905
+ fieldType: "reference",
9906
+ targetTypeName: "decision",
9907
+ targetSurface: "document",
9908
+ targetField: "externalId",
9909
+ cardinality: "one",
9910
+ description: "A decision this runbook implements, if any (by externalId).",
9911
+ renderHints: { label: "Related decision", order: 7 }
9912
+ },
9913
+ {
9914
+ fieldId: "updatedOn",
9915
+ fieldType: "date",
9916
+ description: "ISO-8601 \u2014 when last revised. Range-queryable.",
9917
+ renderHints: { label: "Updated on", widget: "date", order: 8 }
9918
+ }
9919
+ ],
9920
+ lookupFields: ["area", "status", "bornFrom", "relatedDecision", { fieldName: "updatedOn", rangeEnabled: true }]
9921
+ },
9922
+ {
9923
+ // A POST-MORTEM — what broke and the durable lesson. Body = the writeup
9924
+ // (impact / root cause / resolution / lesson). Distinct shape: `severity` +
9925
+ // `occurredOn` (the incident timeline). "Have we hit this before?" lives here.
9926
+ typeName: "postmortem",
9927
+ displayName: "Post-mortem",
9928
+ indexMode: "HYBRID",
9929
+ allowedSurfaces: ["document"],
9930
+ // Intrinsic title + body; schema = metadata beyond those (no typed `title`).
9931
+ fields: [
9932
+ {
9933
+ fieldId: "summary",
9934
+ fieldType: "string",
9935
+ searchable: true,
9936
+ description: "What happened, in brief. The full analysis (impact/root cause/resolution/lesson) is the body.",
9937
+ renderHints: { label: "Summary", widget: "textarea", order: 2 }
9938
+ },
9939
+ {
9940
+ fieldId: "severity",
9941
+ fieldType: "enum",
9942
+ filterable: true,
9943
+ enumValues: ["low", "medium", "high", "critical"],
9944
+ renderHints: { label: "Severity", widget: "select", order: 3 }
9945
+ },
9946
+ {
9947
+ fieldId: "status",
9948
+ fieldType: "enum",
9949
+ filterable: true,
9950
+ enumValues: ["open", "mitigated", "resolved"],
9951
+ renderHints: { label: "Status", widget: "select", order: 4 }
9952
+ },
9953
+ {
9954
+ fieldId: "area",
9955
+ fieldType: "string",
9956
+ filterable: true,
9957
+ renderHints: { label: "Area", widget: "text", order: 5 }
9958
+ },
9959
+ {
9960
+ fieldId: "tags",
9961
+ fieldType: "array",
9962
+ filterable: true,
9963
+ renderHints: { label: "Tags", order: 6 }
9964
+ },
9965
+ {
9966
+ fieldId: "relatedDecision",
9967
+ fieldType: "reference",
9968
+ targetTypeName: "decision",
9969
+ targetSurface: "document",
9970
+ targetField: "externalId",
9971
+ cardinality: "one",
9972
+ description: "A decision implicated in or addressed by this incident (by externalId).",
9973
+ renderHints: { label: "Related decision", order: 7 }
9974
+ },
9975
+ {
9976
+ fieldId: "occurredOn",
9977
+ fieldType: "date",
9978
+ description: 'ISO-8601 \u2014 when it happened. Range-queryable \u2014 "incidents this month".',
9979
+ renderHints: { label: "Occurred on", widget: "date", order: 8 }
9980
+ }
9981
+ ],
9982
+ lookupFields: ["severity", "status", "area", "relatedDecision", { fieldName: "occurredOn", rangeEnabled: true }]
9983
+ },
9984
+ // ============================= RECORDS =============================
9985
+ // Structure-dominant: the typed fields are the artifact. Short, exact-queryable.
9986
+ {
9987
+ // A governance CONTROL — a policy/standard/control the codebase must satisfy,
9988
+ // WITH its evidence. The compliance instrument: it records what enforces it
9989
+ // (`evidence`), the runbook that VERIFIES it (cross-surface → document), and
9990
+ // the decision that mandates it. "Which critical controls are active, and how
9991
+ // is each proven?"
9992
+ typeName: "control",
9993
+ displayName: "Control",
9994
+ indexMode: "HYBRID",
9995
+ fields: [
9996
+ {
9997
+ fieldId: "title",
9998
+ fieldType: "string",
9999
+ required: true,
10000
+ searchable: true,
10001
+ validation: { minLength: 1, maxLength: 200 },
10002
+ renderHints: { label: "Title", widget: "text", order: 1, displayField: true }
10003
+ },
10004
+ {
10005
+ fieldId: "statement",
10006
+ fieldType: "string",
10007
+ searchable: true,
10008
+ description: "The requirement \u2014 what must always hold.",
10009
+ renderHints: { label: "Statement", widget: "textarea", order: 2 }
10010
+ },
10011
+ {
10012
+ fieldId: "rationale",
10013
+ fieldType: "string",
10014
+ searchable: true,
10015
+ description: "Why the control exists \u2014 the risk it prevents.",
10016
+ renderHints: { label: "Rationale", widget: "textarea", order: 3 }
10017
+ },
10018
+ {
10019
+ // The policy → implementation spectrum in one filterable field.
10020
+ fieldId: "kind",
10021
+ fieldType: "enum",
10022
+ filterable: true,
10023
+ enumValues: ["policy", "standard", "control"],
10024
+ renderHints: { label: "Kind", widget: "select", order: 4 }
10025
+ },
10026
+ {
10027
+ // Ordinal, but EQUALITY (range/prefix order is lexical, so low<critical
10028
+ // would sort alphabetically). Enumerate "all critical controls" by equality.
10029
+ fieldId: "criticality",
10030
+ fieldType: "enum",
10031
+ filterable: true,
10032
+ enumValues: ["low", "medium", "high", "critical"],
10033
+ renderHints: { label: "Criticality", widget: "select", order: 5 }
10034
+ },
10035
+ {
10036
+ fieldId: "evidence",
10037
+ fieldType: "string",
10038
+ searchable: true,
10039
+ description: "What enforces or proves the control inline (e.g. an architecture test or gate).",
10040
+ renderHints: { label: "Evidence", widget: "textarea", order: 6 }
10041
+ },
10042
+ {
10043
+ fieldId: "status",
10044
+ fieldType: "enum",
10045
+ filterable: true,
10046
+ enumValues: ["draft", "active", "retired"],
10047
+ renderHints: { label: "Status", widget: "select", order: 7 }
10048
+ },
10049
+ {
10050
+ fieldId: "area",
10051
+ fieldType: "string",
10052
+ filterable: true,
10053
+ renderHints: { label: "Area", widget: "text", order: 8 }
10054
+ },
10055
+ {
10056
+ fieldId: "tags",
10057
+ fieldType: "array",
10058
+ filterable: true,
10059
+ renderHints: { label: "Tags", order: 9 }
10060
+ },
10061
+ {
10062
+ // Cross-surface edge (record → document): the runbook that proves the control.
10063
+ fieldId: "verifiedBy",
10064
+ fieldType: "reference",
10065
+ targetTypeName: "runbook",
10066
+ targetSurface: "document",
10067
+ targetField: "externalId",
10068
+ cardinality: "one",
10069
+ description: "The runbook that verifies this control (by externalId).",
10070
+ renderHints: { label: "Verified by (runbook)", order: 10 }
10071
+ },
10072
+ {
10073
+ // Cross-surface edge (record → document): the decision that mandates it.
10074
+ fieldId: "relatedDecision",
10075
+ fieldType: "reference",
10076
+ targetTypeName: "decision",
10077
+ targetSurface: "document",
10078
+ targetField: "externalId",
10079
+ cardinality: "one",
10080
+ description: "The decision that mandates this control (by externalId).",
10081
+ renderHints: { label: "Related decision", order: 11 }
10082
+ },
10083
+ {
10084
+ fieldId: "reviewedOn",
10085
+ fieldType: "date",
10086
+ description: "ISO-8601 last-reviewed date. Range-queryable.",
10087
+ renderHints: { label: "Reviewed on", widget: "date", order: 12 }
10088
+ }
10089
+ ],
10090
+ lookupFields: [
10091
+ "kind",
10092
+ "criticality",
10093
+ "status",
10094
+ "area",
10095
+ "verifiedBy",
10096
+ "relatedDecision",
10097
+ { fieldName: "reviewedOn", rangeEnabled: true }
10098
+ ]
10099
+ },
10100
+ {
10101
+ // A CONVENTION — a must-follow operating rule the team teaches its agents. The
10102
+ // durable, shareable operating-memory. Distinct fields capture how we actually
10103
+ // write these: the `rule` (imperative), the `why` (rationale), and `howToApply`
10104
+ // (the concrete application) are SEPARATE fields, not one blob — so an agent can
10105
+ // recall the rule, the reasoning, and the how independently.
10106
+ typeName: "convention",
10107
+ displayName: "Convention",
10108
+ indexMode: "HYBRID",
10109
+ fields: [
10110
+ {
10111
+ fieldId: "title",
10112
+ fieldType: "string",
10113
+ required: true,
10114
+ searchable: true,
10115
+ validation: { minLength: 1, maxLength: 200 },
10116
+ renderHints: { label: "Title", widget: "text", order: 1, displayField: true }
10117
+ },
10118
+ {
10119
+ fieldId: "rule",
10120
+ fieldType: "string",
10121
+ searchable: true,
10122
+ description: "The convention itself, stated as an imperative.",
10123
+ renderHints: { label: "Rule", widget: "textarea", order: 2 }
10124
+ },
10125
+ {
10126
+ fieldId: "why",
10127
+ fieldType: "string",
10128
+ searchable: true,
10129
+ description: "Why it matters \u2014 the trade-off / the antipattern it prevents.",
10130
+ renderHints: { label: "Why", widget: "textarea", order: 3 }
10131
+ },
10132
+ {
10133
+ fieldId: "howToApply",
10134
+ fieldType: "string",
10135
+ searchable: true,
10136
+ description: "How to comply in practice \u2014 the concrete application steps.",
10137
+ renderHints: { label: "How to apply", widget: "textarea", order: 4 }
10138
+ },
10139
+ {
10140
+ fieldId: "area",
10141
+ fieldType: "string",
10142
+ filterable: true,
10143
+ renderHints: { label: "Area", widget: "text", order: 5 }
10144
+ },
10145
+ {
10146
+ fieldId: "status",
10147
+ fieldType: "enum",
10148
+ filterable: true,
10149
+ enumValues: ["active", "retired"],
10150
+ renderHints: { label: "Status", widget: "select", order: 6 }
10151
+ },
10152
+ {
10153
+ fieldId: "tags",
10154
+ fieldType: "array",
10155
+ filterable: true,
10156
+ renderHints: { label: "Tags", order: 7 }
10157
+ },
10158
+ {
10159
+ // Cross-surface edge (record → document): the decision that established it.
10160
+ fieldId: "establishedBy",
10161
+ fieldType: "reference",
10162
+ targetTypeName: "decision",
10163
+ targetSurface: "document",
10164
+ targetField: "externalId",
10165
+ cardinality: "one",
10166
+ description: "The decision that established this convention (by externalId).",
10167
+ renderHints: { label: "Established by (decision)", order: 8 }
10168
+ },
10169
+ {
10170
+ fieldId: "updatedOn",
10171
+ fieldType: "date",
10172
+ description: "ISO-8601 \u2014 when last revised. Range-queryable.",
10173
+ renderHints: { label: "Updated on", widget: "date", order: 9 }
10174
+ }
10175
+ ],
10176
+ lookupFields: ["area", "status", "establishedBy", { fieldName: "updatedOn", rangeEnabled: true }]
10177
+ },
10178
+ {
10179
+ // A GOTCHA / sharp edge: a symptom, its cause, and the fix. A tight typed
10180
+ // triple — found by meaning (semantic search on the symptom) + area/status.
10181
+ // The most standalone type; no typed edge.
10182
+ typeName: "gotcha",
10183
+ displayName: "Gotcha",
10184
+ indexMode: "HYBRID",
10185
+ fields: [
10186
+ {
10187
+ fieldId: "symptom",
10188
+ fieldType: "string",
10189
+ required: true,
10190
+ searchable: true,
10191
+ validation: { minLength: 1, maxLength: 500 },
10192
+ renderHints: { label: "Symptom", widget: "textarea", order: 1, displayField: true }
10193
+ },
10194
+ {
10195
+ fieldId: "cause",
10196
+ fieldType: "string",
10197
+ searchable: true,
10198
+ renderHints: { label: "Cause", widget: "textarea", order: 2 }
10199
+ },
10200
+ {
10201
+ fieldId: "fix",
10202
+ fieldType: "string",
10203
+ searchable: true,
10204
+ renderHints: { label: "Fix", widget: "textarea", order: 3 }
10205
+ },
10206
+ {
10207
+ fieldId: "area",
10208
+ fieldType: "string",
10209
+ filterable: true,
10210
+ renderHints: { label: "Area", widget: "text", order: 4 }
10211
+ },
10212
+ {
10213
+ fieldId: "status",
10214
+ fieldType: "enum",
10215
+ filterable: true,
10216
+ enumValues: ["active", "resolved"],
10217
+ renderHints: { label: "Status", widget: "select", order: 5 }
10218
+ },
10219
+ {
10220
+ fieldId: "tags",
10221
+ fieldType: "array",
10222
+ filterable: true,
10223
+ renderHints: { label: "Tags", order: 6 }
10224
+ },
10225
+ {
10226
+ fieldId: "discoveredOn",
10227
+ fieldType: "date",
10228
+ description: "ISO-8601 \u2014 when first hit. Range-queryable.",
10229
+ renderHints: { label: "Discovered on", widget: "date", order: 7 }
10230
+ }
10231
+ ],
10232
+ lookupFields: ["area", "status", { fieldName: "discoveredOn", rangeEnabled: true }]
10233
+ },
10234
+ {
10235
+ // A glossary TERM — a definition keyed by the term itself. Structure-dominant:
10236
+ // `term` is a UNIQUE exact-lookup key (the showcase of a uniqueness constraint),
10237
+ // `definition` is the RAG body, `aliases` catch alternate names. Links to the
10238
+ // decision that defines/establishes the concept where there is one.
10239
+ typeName: "term",
10240
+ displayName: "Glossary term",
10241
+ indexMode: "HYBRID",
10242
+ fields: [
10243
+ {
10244
+ fieldId: "term",
10245
+ fieldType: "string",
10246
+ required: true,
10247
+ searchable: true,
10248
+ validation: { minLength: 1, maxLength: 200 },
10249
+ renderHints: { label: "Term", widget: "text", order: 1, displayField: true }
10250
+ },
10251
+ {
10252
+ fieldId: "definition",
10253
+ fieldType: "string",
10254
+ searchable: true,
10255
+ description: "What the term means \u2014 the RAG-able body.",
10256
+ renderHints: { label: "Definition", widget: "textarea", order: 2 }
10257
+ },
10258
+ {
10259
+ fieldId: "aliases",
10260
+ fieldType: "array",
10261
+ filterable: true,
10262
+ description: "Alternate names / abbreviations for the same concept.",
10263
+ renderHints: { label: "Aliases", order: 3 }
10264
+ },
10265
+ {
10266
+ fieldId: "area",
10267
+ fieldType: "string",
10268
+ filterable: true,
10269
+ renderHints: { label: "Area", widget: "text", order: 4 }
10270
+ },
10271
+ {
10272
+ fieldId: "tags",
10273
+ fieldType: "array",
10274
+ filterable: true,
10275
+ renderHints: { label: "Tags", order: 5 }
10276
+ },
10277
+ {
10278
+ fieldId: "relatedDecision",
10279
+ fieldType: "reference",
10280
+ targetTypeName: "decision",
10281
+ targetSurface: "document",
10282
+ targetField: "externalId",
10283
+ cardinality: "one",
10284
+ description: "A decision that defines or establishes this term (by externalId).",
10285
+ renderHints: { label: "Related decision", order: 6 }
10286
+ },
10287
+ {
10288
+ fieldId: "updatedOn",
10289
+ fieldType: "date",
10290
+ description: "ISO-8601 \u2014 when last revised. Range-queryable.",
10291
+ renderHints: { label: "Updated on", widget: "date", order: 7 }
10292
+ }
10293
+ ],
10294
+ // `term` is a UNIQUE equality lookup — exact "define X" + a one-per-term
10295
+ // guarantee. `area`/`relatedDecision` enumerate; `updatedOn` is the range row.
10296
+ lookupFields: [
10297
+ { fieldName: "term", unique: true },
10298
+ "area",
10299
+ "relatedDecision",
10300
+ { fieldName: "updatedOn", rangeEnabled: true }
10301
+ ]
10302
+ }
10303
+ ],
10304
+ // Least-privilege, data-plane only. r/c/u records + search + schema discovery +
10305
+ // inference:r (grounded recall over the document bodies) + document/folder r/c
10306
+ // (the content artifacts are documents). NO :d — knowledge is superseded/retired
10307
+ // via a status flip, so the trail of how the team's thinking evolved stays intact.
10308
+ accessProfile: {
10309
+ allowedActions: [
10310
+ "records:r",
10311
+ "records:c",
10312
+ "records:u",
10313
+ "search:r",
10314
+ "schemas:r",
10315
+ "inference:r",
10316
+ "documents:r",
10317
+ "documents:c",
10318
+ "folders:r",
10319
+ "folders:c"
10320
+ ]
10321
+ },
10322
+ servicePrincipal: {
10323
+ externalId: "agentic-sdlc",
10324
+ displayName: "Agentic SDLC Knowledge Base"
10325
+ }
10326
+ // No bundled seed in this version: the content artifacts live on the document
10327
+ // surface and the cross-surface graph is populated by the ingest agent
10328
+ // (document_ingest / record_create), not the bootstrap seed step. Production
10329
+ // contexts provision with `vectros bootstrap --no-seed` regardless.
10330
+ };
10331
+ var agentic_sdlc_default = agenticSdlc;
10332
+ var secondBrain = {
10333
+ name: "second-brain",
10334
+ version: "1.0.0",
10335
+ description: "A personal knowledge base \u2014 capture notes, ideas, and links, then ask them anything.",
10336
+ contextId: "second-brain",
10337
+ contextName: "Second Brain",
10338
+ schemas: [
10339
+ {
10340
+ typeName: "note",
10341
+ displayName: "Note",
10342
+ indexMode: "HYBRID",
10343
+ // keyword on title + semantic on body — recall by meaning
10344
+ fields: [
10345
+ {
10346
+ fieldId: "title",
10347
+ fieldType: "string",
10348
+ required: true,
10349
+ searchable: true,
10350
+ validation: { minLength: 1, maxLength: 200 },
10351
+ renderHints: { label: "Title", widget: "text", order: 1, section: "Note", displayField: true }
10352
+ },
10353
+ {
10354
+ fieldId: "body",
10355
+ fieldType: "string",
10356
+ searchable: true,
10357
+ description: "The note itself \u2014 the RAG-able body you ask questions against.",
10358
+ validation: { maxLength: 2e4 },
10359
+ renderHints: { label: "Body", widget: "textarea", order: 2, section: "Note" }
10360
+ },
10361
+ {
10362
+ fieldId: "tags",
10363
+ fieldType: "array",
10364
+ filterable: true,
10365
+ description: 'Freeform string labels for filtering (e.g. "idea", "reading", "work").',
10366
+ renderHints: { label: "Tags", order: 3, section: "Organize" }
10367
+ },
10368
+ {
10369
+ // An enum (not freeform) so enumeration-by-source is reliable — a
10370
+ // `record_query` lookup on `source` only works if values are consistent.
10371
+ // `other` keeps it from being a straitjacket for a personal brain-dump.
10372
+ fieldId: "source",
10373
+ fieldType: "enum",
10374
+ filterable: true,
10375
+ enumValues: ["thought", "web", "meeting", "book", "article", "other"],
10376
+ description: "Where it came from.",
10377
+ renderHints: { label: "Source", widget: "select", order: 4, section: "Organize" }
10378
+ },
10379
+ {
10380
+ fieldId: "status",
10381
+ fieldType: "enum",
10382
+ filterable: true,
10383
+ enumValues: ["active", "archived"],
10384
+ description: "Archive instead of delete, so nothing is ever lost.",
10385
+ renderHints: { label: "Status", widget: "select", order: 5, section: "Organize" }
10386
+ },
10387
+ {
10388
+ fieldId: "capturedAt",
10389
+ fieldType: "date",
10390
+ description: 'ISO-8601 capture date. Range-queryable \u2014 "notes from last week".',
10391
+ renderHints: { label: "Captured", widget: "date", order: 6, section: "Organize" }
10392
+ }
10393
+ // externalId is the record's FIRST-CLASS identifier (the dedup/upsert key + the
10394
+ // value a `reference` resolves against) — not a payload field; the loader sends
10395
+ // it top-level on the RecordRequest.
10396
+ ],
10397
+ // Lookup shapes are MIGRATION-LOCKED once the schema is live (a field cannot
10398
+ // flip equality↔range later), so each is chosen on purpose:
10399
+ // • `source` — EQUALITY, sorted by `lastUpdated`: enumerate notes from one
10400
+ // source, most-recently-touched first. `lastUpdated` is always present, so
10401
+ // the sort never drops a note (sorting an equality lookup by an OPTIONAL
10402
+ // field would silently exclude rows that lack it).
10403
+ // • `status` — EQUALITY: "show archived notes".
10404
+ // • `capturedAt` — RANGE: ordered `from`/`to`/`prefix` ("captured in 2026-06").
10405
+ // `externalId` has a built-in first-class finder — look it up directly, never
10406
+ // redeclare it here. Equality lookups use the 7 fast index slots (2 used here);
10407
+ // range lookups use a relationship row, billed at the range rate.
10408
+ lookupFields: [
10409
+ { fieldName: "source", sortBy: "lastUpdated" },
10410
+ "status",
10411
+ { fieldName: "capturedAt", rangeEnabled: true }
10412
+ ]
10413
+ }
10414
+ ],
10415
+ // Least-privilege profile — r/c/u + search + schema discovery + inference:r
10416
+ // (so the agent can `rag_ask` a grounded, cited question over your notes). No
10417
+ // records:d: notes are archived (status → 'archived'), never deleted.
10418
+ accessProfile: {
10419
+ allowedActions: ["records:r", "records:c", "records:u", "search:r", "schemas:r", "inference:r"]
10420
+ },
10421
+ servicePrincipal: {
10422
+ externalId: "second-brain",
10423
+ displayName: "Second Brain"
10424
+ },
10425
+ seed: [
10426
+ {
10427
+ surface: "record",
10428
+ typeName: "note",
10429
+ externalId: "seed-welcome",
10430
+ fields: {
10431
+ title: "Welcome to your Second Brain",
10432
+ body: 'Capture anything here \u2014 ideas, links, meeting notes \u2014 then ask your agent things like "what did I note about onboarding?" and it recalls by meaning, not just keywords.',
10433
+ tags: ["getting-started"],
10434
+ source: "thought",
10435
+ status: "active",
10436
+ capturedAt: "2026-06-14"
10437
+ }
10438
+ }
10439
+ ]
10440
+ };
10441
+ var second_brain_default = secondBrain;
10442
+ var clinicalIntake = {
10443
+ name: "clinical-intake",
10444
+ version: "1.0.0",
10445
+ description: "Behavioral-health intake with PHI fields \u2014 demonstrates redact-at-write, audit history, blind-index lookup, and search exclusion. Synthetic data only.",
10446
+ contextId: "clinical-intake",
10447
+ contextName: "Clinical Intake",
10448
+ schemas: [
10449
+ {
10450
+ typeName: "intake",
10451
+ displayName: "Intake",
10452
+ indexMode: "HYBRID",
10453
+ capabilities: { auditHistory: true },
10454
+ // self-documenting compliance posture
10455
+ fields: [
10456
+ // --- Non-sensitive, searchable/filterable working fields ---
10457
+ {
10458
+ fieldId: "caseId",
10459
+ fieldType: "string",
10460
+ required: true,
10461
+ description: "Caller-stable intake id; the dedup/lookup key.",
10462
+ validation: { minLength: 1, maxLength: 64 },
10463
+ renderHints: { label: "Case ID", widget: "text", order: 1, section: "Intake", displayField: true }
10464
+ },
10465
+ {
10466
+ fieldId: "presentingConcern",
10467
+ fieldType: "string",
10468
+ searchable: true,
10469
+ description: "Non-PHI summary of the presenting concern \u2014 safe to index + search.",
10470
+ validation: { maxLength: 2e3 },
10471
+ renderHints: { label: "Presenting concern", widget: "textarea", order: 2, section: "Intake" }
10472
+ },
10473
+ {
10474
+ fieldId: "program",
10475
+ fieldType: "string",
9669
10476
  filterable: true,
9670
10477
  description: "Program / service line the intake is routed to.",
9671
10478
  renderHints: { label: "Program", widget: "text", order: 3, section: "Intake" }
@@ -9749,6 +10556,7 @@ var clinicalIntake = {
9749
10556
  },
9750
10557
  seed: [
9751
10558
  {
10559
+ surface: "record",
9752
10560
  typeName: "intake",
9753
10561
  externalId: "seed-synthetic-intake",
9754
10562
  fields: {
@@ -9770,6 +10578,7 @@ var clinical_intake_default = clinicalIntake;
9770
10578
  var BUNDLED_BLUEPRINTS = [
9771
10579
  task_management_default,
9772
10580
  coding_agent_memory_default,
10581
+ agentic_sdlc_default,
9773
10582
  second_brain_default,
9774
10583
  clinical_intake_default
9775
10584
  ];
@@ -10207,7 +11016,8 @@ async function resolveCognitoToken(env, explicit) {
10207
11016
  async function resolveAuthContext(opts) {
10208
11017
  const baseUrl = envToBaseUrl(opts.env, opts.baseUrl ?? process.env.VECTROS_API_BASE_URL);
10209
11018
  const cognitoToken = await resolveCognitoToken(opts.env, opts.token);
10210
- const bridge = opts.provisioning ? await mintBridgeToken({ cognitoToken, apiBaseUrl: baseUrl }) : await mintContextToken({ cognitoToken, apiBaseUrl: baseUrl, contextId: opts.context ?? "default" });
11019
+ const tenant = opts.tenant ?? "live";
11020
+ const bridge = opts.provisioning ? await mintBridgeToken({ cognitoToken, apiBaseUrl: baseUrl, tenant }) : await mintContextToken({ cognitoToken, apiBaseUrl: baseUrl, contextId: opts.context ?? "default", tenant });
10211
11021
  const client = new VectrosClient({ token: bridge.token, environment: baseUrl });
10212
11022
  const identity = await resolveIdentity({ log: opts.log, apiKey: bridge.token, environment: baseUrl });
10213
11023
  return {
@@ -10217,7 +11027,8 @@ async function resolveAuthContext(opts) {
10217
11027
  baseUrl,
10218
11028
  env: opts.env,
10219
11029
  identity,
10220
- tenantId: typeof identity.tenantId === "string" ? identity.tenantId : void 0
11030
+ tenantId: typeof identity.tenantId === "string" ? identity.tenantId : void 0,
11031
+ tenant
10221
11032
  };
10222
11033
  }
10223
11034
 
@@ -10541,39 +11352,6 @@ function assertNoUnresolvedIdentities(blueprint) {
10541
11352
  );
10542
11353
  }
10543
11354
  }
10544
- var SchemaReconcileError = class extends Error {
10545
- constructor(typeName, cause) {
10546
- const detail = cause instanceof Error ? cause.message : String(cause);
10547
- super(
10548
- `Schema '${typeName}' already exists but could not be reconciled to the blueprint: ${detail}
10549
- If this is a migration-locked attribute (a lookup field's rangeEnabled/sortBy/sensitive, or the schema typeName), it cannot change on an existing schema \u2014 use a new fieldName/typeName for the new shape.`
10550
- );
10551
- this.name = "SchemaReconcileError";
10552
- }
10553
- };
10554
- function isAlreadyExistsError(e) {
10555
- if (typeof e !== "object" || e === null) return false;
10556
- const err = e;
10557
- const status = err.statusCode;
10558
- const text = `${err.message ?? ""} ${err.body?.message ?? ""}`.toLowerCase();
10559
- return (status === 409 || status === 400) && text.includes("already exists");
10560
- }
10561
- async function findExistingSchemaId(client, typeName, owner) {
10562
- let startFrom;
10563
- for (; ; ) {
10564
- const page = await client.schemas.listSchemas({
10565
- userId: owner.userId,
10566
- orgId: owner.orgId,
10567
- limit: 100,
10568
- startFrom
10569
- });
10570
- const match = (page.data ?? []).find((s) => s.typeName === typeName);
10571
- if (match?.id) return match.id;
10572
- const next = page.nextCursor;
10573
- if (!next) return void 0;
10574
- startFrom = next;
10575
- }
10576
- }
10577
11355
  async function runLoader(client, input) {
10578
11356
  const { blueprint, tenantId, machineId, rotate = false, skipSeed = false, vectrosDir, log } = input;
10579
11357
  assertScopeGate(blueprint.accessProfile.allowedActions);
@@ -10581,14 +11359,16 @@ async function runLoader(client, input) {
10581
11359
  for (const clause of clauses) assertScopeGate(clause.allowedActions);
10582
11360
  }
10583
11361
  assertNoUnresolvedIdentities(blueprint);
10584
- await client.auth.createAppContext({ contextId: blueprint.contextId, name: contextNameOf(blueprint) });
11362
+ await client.auth.createAppContext({ body: { contextId: blueprint.contextId, name: contextNameOf(blueprint) } });
10585
11363
  log.debug({ component: "loader", contextId: blueprint.contextId }, "app-context ensured");
10586
11364
  const user = await client.identity.createUser({
10587
- 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 }
11365
+ body: {
11366
+ externalId: blueprint.servicePrincipal.externalId,
11367
+ type: "SERVICE",
11368
+ // SDK 0.18+ (identity-access rework): UserRequest's free-form attribute bag
11369
+ // was renamed `metadata` → `payload`. Same semantics (arbitrary key/value).
11370
+ payload: { displayName: blueprint.servicePrincipal.displayName }
11371
+ }
10592
11372
  });
10593
11373
  const userId = user.id;
10594
11374
  if (!userId) {
@@ -10623,23 +11403,9 @@ async function runLoader(client, input) {
10623
11403
  orgId: s.orgId,
10624
11404
  clientId: s.clientId
10625
11405
  };
10626
- 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
- }
11406
+ const resp = await ctxClient.schemas.createSchema({ upsert: true, body: schemaBody });
10641
11407
  if (resp.id) schemaIds[s.typeName] = resp.id;
10642
- log.debug({ component: "loader", typeName: s.typeName, schemaId: resp.id }, "schema ensured");
11408
+ log.debug({ component: "loader", typeName: s.typeName, schemaId: resp.id }, "schema ensured (upsert)");
10643
11409
  }
10644
11410
  const profileBody = {
10645
11411
  principalId: `usr_${userId}`,
@@ -10715,25 +11481,40 @@ async function runLoader(client, input) {
10715
11481
  log.debug({ component: "loader", keyName, keyAction }, "scoped key resolved");
10716
11482
  let seededCount = 0;
10717
11483
  const seededRecordIds = [];
11484
+ const seededDocumentIds = [];
10718
11485
  for (const rec of skipSeed ? [] : blueprint.seed ?? []) {
10719
11486
  const schemaId = schemaIds[rec.typeName];
10720
11487
  if (!schemaId) {
10721
11488
  log.warn({ component: "loader", typeName: rec.typeName }, "seed skipped \u2014 no schemaId for typeName");
10722
11489
  continue;
10723
11490
  }
10724
- 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);
11491
+ if (rec.surface === "document") {
11492
+ const created = await ctxClient.documents.ingestDocument({
11493
+ body: {
11494
+ title: rec.title,
11495
+ text: rec.text,
11496
+ schemaId,
11497
+ externalId: rec.externalId,
11498
+ // Optional structured payload (the document's `fields`); omitted when absent.
11499
+ payload: rec.fields,
11500
+ // Retain the raw text so a seeded document is retrievable via
11501
+ // GET /v1/documents/{id}/text (mirrors the MCP document_ingest default) —
11502
+ // a seed's text IS its content, worth keeping for the getting-started graph.
11503
+ storeText: true
11504
+ }
11505
+ });
11506
+ if (created.id) seededDocumentIds.push(created.id);
11507
+ } else {
11508
+ const created = await ctxClient.records.createRecord({
11509
+ body: {
11510
+ typeName: rec.typeName,
11511
+ schemaId,
11512
+ externalId: rec.externalId,
11513
+ payload: rec.fields
11514
+ }
11515
+ });
11516
+ if (created.id) seededRecordIds.push(created.id);
11517
+ }
10737
11518
  seededCount++;
10738
11519
  }
10739
11520
  return {
@@ -10746,7 +11527,8 @@ async function runLoader(client, input) {
10746
11527
  schemaIds,
10747
11528
  roleIds,
10748
11529
  seededCount,
10749
- seededRecordIds
11530
+ seededRecordIds,
11531
+ seededDocumentIds
10750
11532
  };
10751
11533
  }
10752
11534
  function buildPlan(blueprint, opts = {}) {
@@ -10781,9 +11563,14 @@ function buildPlan(blueprint, opts = {}) {
10781
11563
  });
10782
11564
  }
10783
11565
  steps.push({ kind: "key", summary: `key ${blueprint.name}-<machine> (mint 1 scoped ssk_*)` });
10784
- const seedCount = blueprint.seed?.length ?? 0;
10785
- if (seedCount > 0 && !opts.noSeed) {
10786
- steps.push({ kind: "seed", summary: `seed ${seedCount} record(s)` });
11566
+ const seeds = blueprint.seed ?? [];
11567
+ if (seeds.length > 0 && !opts.noSeed) {
11568
+ const docs = seeds.filter((s) => s.surface === "document").length;
11569
+ const recs = seeds.length - docs;
11570
+ const parts = [];
11571
+ if (recs > 0) parts.push(`${recs} record${recs === 1 ? "" : "s"}`);
11572
+ if (docs > 0) parts.push(`${docs} document${docs === 1 ? "" : "s"}`);
11573
+ steps.push({ kind: "seed", summary: `seed ${parts.join(" + ")}` });
10787
11574
  }
10788
11575
  return steps;
10789
11576
  }
@@ -10909,6 +11696,7 @@ function parseBootstrapArgs(argv) {
10909
11696
  blankSlate: false,
10910
11697
  noSeed: false,
10911
11698
  client: "desktop",
11699
+ tenant: "live",
10912
11700
  help: false
10913
11701
  };
10914
11702
  for (let i = 0; i < argv.length; i++) {
@@ -10959,6 +11747,17 @@ function parseBootstrapArgs(argv) {
10959
11747
  case "--no-seed":
10960
11748
  args.noSeed = true;
10961
11749
  break;
11750
+ case "--tenant": {
11751
+ const v = argv[++i];
11752
+ if (v !== "test" && v !== "live") {
11753
+ throw new Error(`--tenant must be 'test' or 'live' (got '${v ?? ""}')`);
11754
+ }
11755
+ args.tenant = v;
11756
+ break;
11757
+ }
11758
+ case "--test":
11759
+ args.tenant = "test";
11760
+ break;
10962
11761
  case "--help":
10963
11762
  case "-h":
10964
11763
  args.help = true;
@@ -10987,7 +11786,8 @@ var BOOTSTRAP_HELP = `vectros bootstrap \u2014 provision a least-privilege MCP c
10987
11786
 
10988
11787
  Usage:
10989
11788
  vectros bootstrap [--blueprint <name> | --blank] [--no-seed] [--env staging|production]
10990
- [--client desktop|code] [--yes] [--print] [--rotate]
11789
+ [--tenant test|live | --test] [--client desktop|code]
11790
+ [--yes] [--print] [--rotate]
10991
11791
  [--token <t> | env VECTROS_BOOTSTRAP_TOKEN]
10992
11792
  [--base-url <url>] [--use-scoped-token]
10993
11793
 
@@ -11001,6 +11801,12 @@ Options:
11001
11801
  (a blueprint's seeds are illustrative, not real data). Unlike
11002
11802
  --blank, the schemas are still created.
11003
11803
  --env staging|production Target environment (default: production).
11804
+ --tenant test|live Which tenant the minted key scopes to (default: live).
11805
+ 'test' provisions into your isolated test tenant \u2014 a
11806
+ dry-run before pointing real data at the same blueprint.
11807
+ The tenant rides the key (ssk_test_* vs ssk_live_*), so
11808
+ the MCP/client config is otherwise identical.
11809
+ --test Shorthand for --tenant test.
11004
11810
  --base-url <url> Override the API base URL (beats --env + VECTROS_API_BASE_URL).
11005
11811
  --client <desktop|code> MCP host to configure (default: desktop). 'desktop' merges
11006
11812
  claude_desktop_config.json (also works for Cursor/Cline);
@@ -11069,6 +11875,21 @@ async function resolveBridgeCognitoToken(args, prompt, isTty, resolveCognito = r
11069
11875
  if (!pasted) throw new Error("No token entered.");
11070
11876
  return pasted;
11071
11877
  }
11878
+ function buildRemintForContext(opts) {
11879
+ const mintContext = opts.mintContext ?? mintContextToken;
11880
+ return async (contextId) => {
11881
+ const ctxToken = await mintContext({
11882
+ cognitoToken: opts.cognitoToken,
11883
+ apiBaseUrl: opts.apiBaseUrl,
11884
+ contextId,
11885
+ tenant: opts.tenant
11886
+ });
11887
+ return new VectrosClient({
11888
+ token: ctxToken.token,
11889
+ environment: opts.apiBaseUrl
11890
+ });
11891
+ };
11892
+ }
11072
11893
  async function runBootstrapCli(argv, deps = {}) {
11073
11894
  const log = deps.log ?? createLogger2();
11074
11895
  let args;
@@ -11150,12 +11971,17 @@ ${renderPlan(blueprint, planSteps)}
11150
11971
  isTty,
11151
11972
  deps.resolveCognitoToken
11152
11973
  );
11153
- const bridge = await mintBridgeToken({
11974
+ const mintBridge = deps.mintBridgeToken ?? mintBridgeToken;
11975
+ const mintContext = deps.mintContextToken ?? mintContextToken;
11976
+ const bridge = await mintBridge({
11154
11977
  cognitoToken,
11155
11978
  apiBaseUrl: baseUrl,
11156
- useScopedToken: args.useScopedToken
11979
+ useScopedToken: args.useScopedToken,
11980
+ // Tenant rides the key (ssk_test_* vs ssk_live_*), not the base URL.
11981
+ tenant: args.tenant
11157
11982
  });
11158
- process.stdout.write(" \u2714 Bootstrap token minted (5-min TTL)\n");
11983
+ process.stdout.write(` \u2714 Bootstrap token minted (5-min TTL, ${args.tenant} tenant)
11984
+ `);
11159
11985
  const client = new VectrosClient({ token: bridge.token, environment: baseUrl });
11160
11986
  const identity = await resolveIdentity({ log, apiKey: bridge.token, environment: baseUrl });
11161
11987
  const tenantId = identity.tenantId;
@@ -11171,17 +11997,16 @@ ${renderPlan(blueprint, planSteps)}
11171
11997
  machineId,
11172
11998
  rotate: args.rotate,
11173
11999
  skipSeed: noSeed,
11174
- remintForContext: async (contextId) => {
11175
- const ctxToken = await mintContextToken({ cognitoToken, apiBaseUrl: baseUrl, contextId });
11176
- return new VectrosClient({
11177
- token: ctxToken.token,
11178
- environment: baseUrl
11179
- });
11180
- },
12000
+ remintForContext: buildRemintForContext({
12001
+ cognitoToken,
12002
+ apiBaseUrl: baseUrl,
12003
+ tenant: args.tenant,
12004
+ mintContext
12005
+ }),
11181
12006
  log
11182
12007
  });
11183
12008
  process.stdout.write(
11184
- ` \u2714 AccessProfile + key (${result.keyAction}) \u2192 ${result.keyName}
12009
+ ` \u2714 AccessProfile + ${args.tenant} key (${result.keyAction}) \u2192 ${result.keyName}
11185
12010
  ` + seedSummaryLine({
11186
12011
  noSeed,
11187
12012
  declinedInteractively: seedDeclinedInteractively,
@@ -11397,7 +12222,8 @@ servicePrincipal:
11397
12222
  displayName: ${name}
11398
12223
 
11399
12224
  seed:
11400
- - typeName: thing
12225
+ - surface: record # 'record' (createRecord) or 'document' (text-ingest: needs title + text)
12226
+ typeName: thing
11401
12227
  externalId: seed-welcome # stable \u2192 idempotent; re-apply converges
11402
12228
  fields: { title: "Hello from ${name}" }
11403
12229
  `;
@@ -11745,6 +12571,7 @@ async function withAuth(opts, deps, body) {
11745
12571
  token: opts.token,
11746
12572
  context: opts.context,
11747
12573
  provisioning: opts.provisioning,
12574
+ tenant: opts.tenant,
11748
12575
  useScopedToken: opts.useScopedToken,
11749
12576
  log
11750
12577
  });
@@ -12021,19 +12848,23 @@ async function createIdentity(client, opts, payload) {
12021
12848
  switch (opts.type) {
12022
12849
  case "user":
12023
12850
  return client.identity.createUser({
12024
- externalId,
12025
- email: opts.email,
12026
- type: opts.service ? "SERVICE" : "HUMAN",
12027
- payload
12851
+ body: {
12852
+ externalId,
12853
+ email: opts.email,
12854
+ type: opts.service ? "SERVICE" : "HUMAN",
12855
+ payload
12856
+ }
12028
12857
  });
12029
12858
  case "org":
12030
- return client.identity.createOrg({ externalId, name: opts.name, payload });
12859
+ return client.identity.createOrg({ body: { externalId, name: opts.name, payload } });
12031
12860
  case "client":
12032
12861
  return client.identity.createClient({
12033
- externalId,
12034
- name: opts.name,
12035
- orgId: opts.org,
12036
- payload
12862
+ body: {
12863
+ externalId,
12864
+ name: opts.name,
12865
+ orgId: opts.org,
12866
+ payload
12867
+ }
12037
12868
  });
12038
12869
  }
12039
12870
  }
@@ -12474,8 +13305,10 @@ async function runContextCreate(opts, deps = {}) {
12474
13305
  const client = auth.client;
12475
13306
  try {
12476
13307
  const ctx = await client.auth.createAppContext({
12477
- contextId: opts.contextId,
12478
- name: opts.name ?? opts.contextId
13308
+ body: {
13309
+ contextId: opts.contextId,
13310
+ name: opts.name ?? opts.contextId
13311
+ }
12479
13312
  });
12480
13313
  if (opts.json) {
12481
13314
  process.stdout.write(`${formatJson(ctx)}
@@ -12786,23 +13619,22 @@ async function runAssertions(client, deps, blueprint, tenantId, result) {
12786
13619
  }
12787
13620
  }
12788
13621
  for (const rec of blueprint.seed ?? []) {
13622
+ const isDoc = rec.surface === "document";
13623
+ const op = isDoc ? "lookupDocuments" : "lookupRecords";
13624
+ const noun = isDoc ? "document" : "record";
12789
13625
  try {
12790
- const page = await client.records.lookupRecords({
12791
- type: rec.typeName,
12792
- field: "externalId",
12793
- value: rec.externalId
12794
- });
13626
+ const page = isDoc ? await client.documents.lookupDocuments({ type: rec.typeName, field: "externalId", value: rec.externalId }) : await client.records.lookupRecords({ type: rec.typeName, field: "externalId", value: rec.externalId });
12795
13627
  const ok = (page.data ?? []).length >= 1;
12796
13628
  assertions.push({
12797
13629
  name: `seed-externalId:${rec.externalId}`,
12798
13630
  ok,
12799
- detail: ok ? `'${rec.typeName}' seed resolves by externalId '${rec.externalId}'` : `'${rec.typeName}' seed externalId '${rec.externalId}' resolved 0 records \u2014 externalId is not first-class (written only in payload?)`
13631
+ detail: ok ? `'${rec.typeName}' seed resolves by externalId '${rec.externalId}'` : `'${rec.typeName}' seed externalId '${rec.externalId}' resolved 0 ${noun}s \u2014 externalId is not first-class (written only in payload?)`
12800
13632
  });
12801
13633
  } catch (e) {
12802
13634
  assertions.push({
12803
13635
  name: `seed-externalId:${rec.externalId}`,
12804
13636
  ok: false,
12805
- detail: `lookupRecords(externalId='${rec.externalId}') failed: ${errMsg(e)}`
13637
+ detail: `${op}(externalId='${rec.externalId}') failed: ${errMsg(e)}`
12806
13638
  });
12807
13639
  }
12808
13640
  }
@@ -12860,6 +13692,14 @@ async function runTeardown(client, blueprint, machineId, snapshot, result) {
12860
13692
  steps.push({ step: `delete record ${id}`, ok: false, error: errMsg(e) });
12861
13693
  }
12862
13694
  }
13695
+ for (const id of result.seededDocumentIds) {
13696
+ try {
13697
+ await client.documents.deleteDocument({ id });
13698
+ steps.push({ step: `delete document ${id}`, ok: true });
13699
+ } catch (e) {
13700
+ steps.push({ step: `delete document ${id}`, ok: false, error: errMsg(e) });
13701
+ }
13702
+ }
12863
13703
  const principalId = `usr_${result.servicePrincipalUserId}`;
12864
13704
  if (snapshot.failed) {
12865
13705
  steps.push({ step: `delete access profile ${principalId}`, ok: true, skipped: true, error: "skipped \u2014 pre-apply snapshot failed; cannot prove ownership" });
@@ -12994,7 +13834,7 @@ function renderReport(blueprint, env, report) {
12994
13834
  if (report.applied) {
12995
13835
  const a = report.applied;
12996
13836
  lines.push(
12997
- ` \u2714 applied \u2014 key ${a.keyName} (${a.keyAction}), ${Object.keys(a.schemaIds).length} schema(s)` + (a.seededCount ? `, ${a.seededCount} seed record(s)` : "")
13837
+ ` \u2714 applied \u2014 key ${a.keyName} (${a.keyAction}), ${Object.keys(a.schemaIds).length} schema(s)` + (a.seededCount ? `, ${a.seededCount} seed(s)` : "")
12998
13838
  );
12999
13839
  } else {
13000
13840
  lines.push(` \u2716 apply failed \u2014 ${report.error ?? "unknown error"}`);
@@ -13057,7 +13897,10 @@ async function runBlueprintTest(fileOrName, opts, deps = {}) {
13057
13897
  const ctxToken = await mintContextToken({
13058
13898
  cognitoToken: auth.cognitoToken,
13059
13899
  apiBaseUrl: auth.baseUrl,
13060
- contextId
13900
+ contextId,
13901
+ // Same tenant as the bootstrap token (auth.tenant) — the in-context
13902
+ // load must land in the tenant the run targets.
13903
+ tenant: auth.tenant
13061
13904
  });
13062
13905
  return new VectrosClient({
13063
13906
  token: ctxToken.token,
@@ -13095,9 +13938,9 @@ async function runBlueprintTest(fileOrName, opts, deps = {}) {
13095
13938
 
13096
13939
  // src/build-info.ts
13097
13940
  var BUILD_INFO = {
13098
- cli: true ? "0.6.1" : "dev",
13099
- sdk: true ? "0.30.0" : "dev",
13100
- blueprints: true ? "0.5.0" : "dev"
13941
+ cli: true ? "0.6.4" : "dev",
13942
+ sdk: true ? "0.31.0" : "dev",
13943
+ blueprints: true ? "0.6.2" : "dev"
13101
13944
  };
13102
13945
  function formatBuildInfo() {
13103
13946
  return `vectros ${BUILD_INFO.cli} (sdk ${BUILD_INFO.sdk}, blueprints ${BUILD_INFO.blueprints})`;
@@ -13145,10 +13988,21 @@ function buildProgram(deps = {}) {
13145
13988
  addCommonAuthOptions(whoami).action(async (flags) => {
13146
13989
  process.exit(await runWhoami(toCommonOptions(flags), deps));
13147
13990
  });
13148
- const blueprintTest = program.command("blueprint-test").description("Apply a blueprint to a live env, assert it provisioned a working credential, then tear it down.").argument("<file>", "blueprint file path (./my.yaml) or a bundled blueprint name").option("--keep", "leave the provisioned graph in place (skip teardown) for inspection");
13991
+ const blueprintTest = program.command("blueprint-test").description("Apply a blueprint to a live env, assert it provisioned a working credential, then tear it down.").argument("<file>", "blueprint file path (./my.yaml) or a bundled blueprint name").option("--keep", "leave the provisioned graph in place (skip teardown) for inspection").option("--tenant <tenant>", "target tenant for the run: 'test' or 'live' (default live)");
13149
13992
  addCommonAuthOptions(blueprintTest).action(
13150
13993
  async (file, flags) => {
13151
- process.exit(await runBlueprintTest(file, { ...toCommonOptions(flags), keep: Boolean(flags.keep) }, deps));
13994
+ if (flags.tenant !== void 0 && flags.tenant !== "test" && flags.tenant !== "live") {
13995
+ process.stderr.write(` \u2716 --tenant must be 'test' or 'live' (got '${flags.tenant}')
13996
+ `);
13997
+ process.exit(2);
13998
+ }
13999
+ process.exit(
14000
+ await runBlueprintTest(
14001
+ file,
14002
+ { ...toCommonOptions(flags), keep: Boolean(flags.keep), tenant: flags.tenant },
14003
+ deps
14004
+ )
14005
+ );
13152
14006
  }
13153
14007
  );
13154
14008
  registerKeyCommands(program, deps);