@vectros-ai/cli 0.12.0 → 0.13.0
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/AUTHORING.md +55 -7
- package/CHANGELOG.md +86 -0
- package/dist/cli.js +444 -72
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +444 -72
- package/dist/cli.mjs.map +1 -1
- package/package.json +3 -3
package/dist/cli.mjs
CHANGED
|
@@ -4048,7 +4048,7 @@ var AuthClient = class {
|
|
|
4048
4048
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/admin/access-log");
|
|
4049
4049
|
}
|
|
4050
4050
|
/**
|
|
4051
|
-
* Lists
|
|
4051
|
+
* Lists your scoped API keys (`ssk_*`) in your credential's own environment — a live key lists live keys, a test key lists test keys. Revoked keys are excluded. Requires the `keys:r` scope.
|
|
4052
4052
|
*
|
|
4053
4053
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
4054
4054
|
*
|
|
@@ -4431,7 +4431,7 @@ var AuthClient = class {
|
|
|
4431
4431
|
);
|
|
4432
4432
|
}
|
|
4433
4433
|
/**
|
|
4434
|
-
* 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` is keyed by ownership namespace in `scope:<namespace>` form — `scope:org` and `scope:client` for the reserved namespaces, or any namespace you have registered — and may name at most two; 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.
|
|
4434
|
+
* 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, and applies the same `identityOverrides` bounds the update endpoint documents — a scoped credential may not repoint or clear an identity value it does not itself hold). 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` is keyed by ownership namespace in `scope:<namespace>` form — `scope:org` and `scope:client` for the reserved namespaces, or any namespace you have registered — and may name at most two; 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.
|
|
4435
4435
|
*
|
|
4436
4436
|
* @param {Vectros.CreateAccessProfileRequest} request
|
|
4437
4437
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -4849,7 +4849,7 @@ var AuthClient = class {
|
|
|
4849
4849
|
);
|
|
4850
4850
|
}
|
|
4851
4851
|
/**
|
|
4852
|
-
* Updates an access profile. This is a partial update: any field you omit (or send as null) keeps its existing value. A profile must reference either inline `scopes` or a `roleId`, never both — so setting `scopes` clears any `roleId`, and setting `roleId` clears any inline `scopes`. The `contextId` and `principalId` are immutable. Status changes (for example active to suspended) take effect within about five minutes. If you use a scoped credential, the profile's effective scopes may not exceed your own
|
|
4852
|
+
* Updates an access profile. This is a partial update: any field you omit (or send as null) keeps its existing value. A profile must reference either inline `scopes` or a `roleId`, never both — so setting `scopes` clears any `roleId`, and setting `roleId` clears any inline `scopes`. The `contextId` and `principalId` are immutable. Status changes (for example active to suspended) take effect within about five minutes. If you use a scoped credential, the profile's effective scopes may not exceed your own, and its `identityOverrides` are bounded twice: you may only set a value your own identity holds, and you may only change or clear a value the profile already holds if that value is yours as well. Repointing or clearing another principal's established identity therefore returns 403. A root API key (`sk_`) is exempt. Requires the `profiles:u` scope.
|
|
4853
4853
|
*
|
|
4854
4854
|
* @param {Vectros.UpdateAccessProfileRequest} request
|
|
4855
4855
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -4922,7 +4922,7 @@ var AuthClient = class {
|
|
|
4922
4922
|
);
|
|
4923
4923
|
}
|
|
4924
4924
|
/**
|
|
4925
|
-
* Deletes an access profile. Within about five minutes (the access-profile cache lifetime), token minting for this principal in this context will be denied. Requires the `profiles:d` scope.
|
|
4925
|
+
* Deletes an access profile. Within about five minutes (the access-profile cache lifetime), token minting for this principal in this context will be denied. If you use a scoped credential and the profile carries `identityOverrides`, you may only delete it when you hold those values yourself — deleting a profile removes its identity, so the same bound applies as when clearing it. A profile with no `identityOverrides` is unaffected, and a root API key (`sk_`) is exempt. Requires the `profiles:d` scope.
|
|
4926
4926
|
*
|
|
4927
4927
|
* @param {Vectros.DeleteAccessProfileRequest} request
|
|
4928
4928
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -5776,7 +5776,7 @@ var AuthClient = class {
|
|
|
5776
5776
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/auth/token");
|
|
5777
5777
|
}
|
|
5778
5778
|
/**
|
|
5779
|
-
* Invite a new member to one of your app contexts by email. Creates a pending user with a pre-resolved access profile (their permissions on accept) and signs an invitation token. This call is idempotent on the combination of context and email: re-inviting the same email in the same context rotates the token and resends the invitation rather than creating a duplicate. Returns HTTP 201 on a new invite or a successful resend. Returns 409 if that email already belongs to an active or suspended member of the app context, or already has an identity elsewhere in your account (an email can currently belong to only one tenant per account, i.e. your test and live environments cannot share an email). When `sendEmail` is false, the response includes the raw token and a ready-to-use accept link so you can deliver the invitation through your own email provider. Requires the `
|
|
5779
|
+
* Invite a new member to one of your app contexts by email. Creates a pending user with a pre-resolved access profile (their permissions on accept) and signs an invitation token. This call is idempotent on the combination of context and email: re-inviting the same email in the same context rotates the token and resends the invitation rather than creating a duplicate — this requires the `users:r` and `users:u` scopes in addition to `users:c`, because resending rotates a credential on an existing invitation and invalidates any link already sent. Without them the collision returns 409 instead, with no invitation details and no change to the outstanding invitation. Returns HTTP 201 on a new invite or a successful resend. Returns 409 if that email already belongs to an active or suspended member of the app context, or already has an identity elsewhere in your account (an email can currently belong to only one tenant per account, i.e. your test and live environments cannot share an email). When `sendEmail` is false, the response includes the raw token and a ready-to-use accept link so you can deliver the invitation through your own email provider. Requires the `users:c` scope.
|
|
5780
5780
|
*
|
|
5781
5781
|
* @param {Vectros.CreateInviteRequest} request
|
|
5782
5782
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -5847,7 +5847,7 @@ var AuthClient = class {
|
|
|
5847
5847
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/users/invite");
|
|
5848
5848
|
}
|
|
5849
5849
|
/**
|
|
5850
|
-
* Resend an outstanding invitation, identified by its email and app context. Rotates the invitation token and extends its expiry, then (when `sendEmail` is true) re-delivers the email. Rotating the token invalidates any previously issued link for this invitation, so only the newest link works. The invitee's pending permissions are left unchanged.
|
|
5850
|
+
* Resend an outstanding invitation, identified by its email and app context. Rotates the invitation token and extends its expiry, then (when `sendEmail` is true) re-delivers the email. Rotating the token invalidates any previously issued link for this invitation, so only the newest link works. The invitee's pending permissions are left unchanged. Because this rotates a credential on an existing invitation, it requires the `users:c`, `users:r` and `users:u` scopes.
|
|
5851
5851
|
*
|
|
5852
5852
|
* @param {Vectros.CreateInviteRequest} request
|
|
5853
5853
|
* @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -6181,7 +6181,7 @@ var DocumentsClient = class {
|
|
|
6181
6181
|
* userId: "550e8400-e29b-41d4-a716-446655440000",
|
|
6182
6182
|
* scope: "group:eng-team",
|
|
6183
6183
|
* folderId: "f47ac10b-58cc-4372-a567-0e02b2c3d479",
|
|
6184
|
-
* startFrom: "
|
|
6184
|
+
* startFrom: "b3BhcXVlLWN1cnNvci1mcm9tLXRoZS1wcmV2aW91cy1wYWdl"
|
|
6185
6185
|
* })
|
|
6186
6186
|
*/
|
|
6187
6187
|
listDocuments(request = {}, requestOptions) {
|
|
@@ -6491,6 +6491,7 @@ var DocumentsClient = class {
|
|
|
6491
6491
|
* @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
6492
6492
|
*
|
|
6493
6493
|
* @throws {@link Vectros.BadRequestError}
|
|
6494
|
+
* @throws {@link Vectros.ForbiddenError}
|
|
6494
6495
|
* @throws {@link Vectros.NotFoundError}
|
|
6495
6496
|
* @throws {@link Vectros.ConflictError}
|
|
6496
6497
|
* @throws {@link Vectros.TooManyRequestsError}
|
|
@@ -6538,6 +6539,8 @@ var DocumentsClient = class {
|
|
|
6538
6539
|
switch (_response.error.statusCode) {
|
|
6539
6540
|
case 400:
|
|
6540
6541
|
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
6542
|
+
case 403:
|
|
6543
|
+
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
6541
6544
|
case 404:
|
|
6542
6545
|
throw new NotFoundError(_response.error.body, _response.rawResponse);
|
|
6543
6546
|
case 409:
|
|
@@ -6568,7 +6571,7 @@ var DocumentsClient = class {
|
|
|
6568
6571
|
* field: "po_number",
|
|
6569
6572
|
* value: "PO-1001",
|
|
6570
6573
|
* prefix: "PO-2024",
|
|
6571
|
-
* startFrom: "
|
|
6574
|
+
* startFrom: "b3BhcXVlLWN1cnNvci1mcm9tLXRoZS1wcmV2aW91cy1wYWdl",
|
|
6572
6575
|
* userId: "550e8400-e29b-41d4-a716-446655440000",
|
|
6573
6576
|
* scope: "org:6ba7b810-9dad-11d1-80b4-00c04fd430c8"
|
|
6574
6577
|
* })
|
|
@@ -6944,7 +6947,7 @@ var FoldersClient = class {
|
|
|
6944
6947
|
* parentFolderId: "f47ac10b-58cc-4372-a567-0e02b2c3d479",
|
|
6945
6948
|
* userId: "550e8400-e29b-41d4-a716-446655440000",
|
|
6946
6949
|
* scope: "group:eng-team",
|
|
6947
|
-
* startFrom: "
|
|
6950
|
+
* startFrom: "b3BhcXVlLWN1cnNvci1mcm9tLXRoZS1wcmV2aW91cy1wYWdl"
|
|
6948
6951
|
* })
|
|
6949
6952
|
*/
|
|
6950
6953
|
listFolders(request = {}, requestOptions) {
|
|
@@ -7121,6 +7124,7 @@ var FoldersClient = class {
|
|
|
7121
7124
|
* @param {Vectros.UpdateFolderRequest} request
|
|
7122
7125
|
* @param {FoldersClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
7123
7126
|
*
|
|
7127
|
+
* @throws {@link Vectros.ForbiddenError}
|
|
7124
7128
|
* @throws {@link Vectros.NotFoundError}
|
|
7125
7129
|
* @throws {@link Vectros.TooManyRequestsError}
|
|
7126
7130
|
*
|
|
@@ -7165,6 +7169,8 @@ var FoldersClient = class {
|
|
|
7165
7169
|
}
|
|
7166
7170
|
if (_response.error.reason === "status-code") {
|
|
7167
7171
|
switch (_response.error.statusCode) {
|
|
7172
|
+
case 403:
|
|
7173
|
+
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
7168
7174
|
case 404:
|
|
7169
7175
|
throw new NotFoundError(_response.error.body, _response.rawResponse);
|
|
7170
7176
|
case 429:
|
|
@@ -7247,6 +7253,7 @@ var FoldersClient = class {
|
|
|
7247
7253
|
* @param {FoldersClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
7248
7254
|
*
|
|
7249
7255
|
* @throws {@link Vectros.BadRequestError}
|
|
7256
|
+
* @throws {@link Vectros.ForbiddenError}
|
|
7250
7257
|
* @throws {@link Vectros.NotFoundError}
|
|
7251
7258
|
* @throws {@link Vectros.ConflictError}
|
|
7252
7259
|
* @throws {@link Vectros.TooManyRequestsError}
|
|
@@ -7294,6 +7301,8 @@ var FoldersClient = class {
|
|
|
7294
7301
|
switch (_response.error.statusCode) {
|
|
7295
7302
|
case 400:
|
|
7296
7303
|
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
7304
|
+
case 403:
|
|
7305
|
+
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
7297
7306
|
case 404:
|
|
7298
7307
|
throw new NotFoundError(_response.error.body, _response.rawResponse);
|
|
7299
7308
|
case 409:
|
|
@@ -7381,7 +7390,8 @@ var IdentityClient = class {
|
|
|
7381
7390
|
*
|
|
7382
7391
|
* @example
|
|
7383
7392
|
* await client.identity.listEntities({
|
|
7384
|
-
* namespace: "team"
|
|
7393
|
+
* namespace: "team",
|
|
7394
|
+
* startFrom: "b3BhcXVlLWN1cnNvci1mcm9tLXRoZS1wcmV2aW91cy1wYWdl"
|
|
7385
7395
|
* })
|
|
7386
7396
|
*/
|
|
7387
7397
|
listEntities(request, requestOptions) {
|
|
@@ -8145,7 +8155,7 @@ var IdentityClient = class {
|
|
|
8145
8155
|
* @example
|
|
8146
8156
|
* await client.identity.listUsers({
|
|
8147
8157
|
* externalId: "usr_12345",
|
|
8148
|
-
* startFrom: "
|
|
8158
|
+
* startFrom: "b3BhcXVlLWN1cnNvci1mcm9tLXRoZS1wcmV2aW91cy1wYWdl",
|
|
8149
8159
|
* type: "person_v1",
|
|
8150
8160
|
* field: "team",
|
|
8151
8161
|
* value: "engineering",
|
|
@@ -8326,11 +8336,12 @@ var IdentityClient = class {
|
|
|
8326
8336
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/users/{id}");
|
|
8327
8337
|
}
|
|
8328
8338
|
/**
|
|
8329
|
-
* Updates mutable fields on an existing user (such as email, status, payload, or schema binding). The `type` field is immutable after creation. This endpoint also activates an invited user: a PUT that moves a PENDING user to ACTIVE and carries `inviteToken`, `externalSubject`, and `emailVerifiedAttestation=true` completes the invitation. Requires the `users:u` scope.
|
|
8339
|
+
* Updates mutable fields on an existing user (such as email, status, payload, or schema binding). The `type` field is immutable after creation, and `email` cannot be changed while an invitation to that user is still outstanding — revoke the invitation, or invite the new address instead. This endpoint also activates an invited user: a PUT that moves a PENDING user to ACTIVE and carries `inviteToken`, `externalSubject`, and `emailVerifiedAttestation=true` completes the invitation. Requires the `users:u` scope.
|
|
8330
8340
|
*
|
|
8331
8341
|
* @param {Vectros.UpdateUserRequest} request
|
|
8332
8342
|
* @param {IdentityClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
8333
8343
|
*
|
|
8344
|
+
* @throws {@link Vectros.BadRequestError}
|
|
8334
8345
|
* @throws {@link Vectros.NotFoundError}
|
|
8335
8346
|
* @throws {@link Vectros.TooManyRequestsError}
|
|
8336
8347
|
*
|
|
@@ -8375,6 +8386,8 @@ var IdentityClient = class {
|
|
|
8375
8386
|
}
|
|
8376
8387
|
if (_response.error.reason === "status-code") {
|
|
8377
8388
|
switch (_response.error.statusCode) {
|
|
8389
|
+
case 400:
|
|
8390
|
+
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
8378
8391
|
case 404:
|
|
8379
8392
|
throw new NotFoundError(_response.error.body, _response.rawResponse);
|
|
8380
8393
|
case 429:
|
|
@@ -9022,7 +9035,7 @@ var RecordsClient = class {
|
|
|
9022
9035
|
* folderId: "f47ac10b-58cc-4372-a567-0e02b2c3d479",
|
|
9023
9036
|
* userId: "550e8400-e29b-41d4-a716-446655440000",
|
|
9024
9037
|
* scope: "group:eng-team",
|
|
9025
|
-
* startFrom: "
|
|
9038
|
+
* startFrom: "b3BhcXVlLWN1cnNvci1mcm9tLXRoZS1wcmV2aW91cy1wYWdl"
|
|
9026
9039
|
* })
|
|
9027
9040
|
*/
|
|
9028
9041
|
listRecords(request = {}, requestOptions) {
|
|
@@ -9210,6 +9223,7 @@ var RecordsClient = class {
|
|
|
9210
9223
|
* @param {RecordsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
9211
9224
|
*
|
|
9212
9225
|
* @throws {@link Vectros.BadRequestError}
|
|
9226
|
+
* @throws {@link Vectros.ForbiddenError}
|
|
9213
9227
|
* @throws {@link Vectros.NotFoundError}
|
|
9214
9228
|
* @throws {@link Vectros.ConflictError}
|
|
9215
9229
|
* @throws {@link Vectros.TooManyRequestsError}
|
|
@@ -9258,6 +9272,8 @@ var RecordsClient = class {
|
|
|
9258
9272
|
switch (_response.error.statusCode) {
|
|
9259
9273
|
case 400:
|
|
9260
9274
|
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
9275
|
+
case 403:
|
|
9276
|
+
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
9261
9277
|
case 404:
|
|
9262
9278
|
throw new NotFoundError(_response.error.body, _response.rawResponse);
|
|
9263
9279
|
case 409:
|
|
@@ -9339,6 +9355,7 @@ var RecordsClient = class {
|
|
|
9339
9355
|
* @param {RecordsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
9340
9356
|
*
|
|
9341
9357
|
* @throws {@link Vectros.BadRequestError}
|
|
9358
|
+
* @throws {@link Vectros.ForbiddenError}
|
|
9342
9359
|
* @throws {@link Vectros.NotFoundError}
|
|
9343
9360
|
* @throws {@link Vectros.ConflictError}
|
|
9344
9361
|
* @throws {@link Vectros.TooManyRequestsError}
|
|
@@ -9389,6 +9406,8 @@ var RecordsClient = class {
|
|
|
9389
9406
|
switch (_response.error.statusCode) {
|
|
9390
9407
|
case 400:
|
|
9391
9408
|
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
9409
|
+
case 403:
|
|
9410
|
+
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
9392
9411
|
case 404:
|
|
9393
9412
|
throw new NotFoundError(_response.error.body, _response.rawResponse);
|
|
9394
9413
|
case 409:
|
|
@@ -9406,7 +9425,15 @@ var RecordsClient = class {
|
|
|
9406
9425
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "PATCH", "/v1/records/{id}");
|
|
9407
9426
|
}
|
|
9408
9427
|
/**
|
|
9409
|
-
* Finds records by the value of a lookup field declared on the type's schema. Provide exactly one lookup mode: `value` (exact match), `from`+`to` (inclusive range, ascending by value), or `prefix` (string fields only, ascending). Range and prefix lookups are not supported on a sensitive field, because its value is stored as a blind index and has no sortable order. An exact-`value` lookup on a sensitive field is also rejected on this GET endpoint — the value must not appear in the URL — so use the `POST /v1/records/lookup` body variant for sensitive fields.
|
|
9428
|
+
* Finds records by the value of a lookup field declared on the type's schema. Provide exactly one lookup mode: `value` (exact match), `from`+`to` (inclusive range, ascending by value), or `prefix` (string fields only, ascending). Range and prefix lookups are not supported on a sensitive field, because its value is stored as a blind index and has no sortable order. An exact-`value` lookup on a sensitive field is also rejected on this GET endpoint — the value must not appear in the URL — so use the `POST /v1/records/lookup` body variant for sensitive fields.
|
|
9429
|
+
*
|
|
9430
|
+
* A `value` lookup can additionally be narrowed to a window of the lookup field's **sort key** using `sortFrom` and/or `sortTo` (inclusive) — for example, one session's records created since a timestamp. The sort key is whatever the schema declares as that lookup's `sortBy` (`createdAt` by default, `lastUpdated`, or another field), and bounds are given in that field's own units — epoch milliseconds for the two timestamp options. **Records that have no value for the sorted field are never included in a bounded window** — they are ordered ahead of every record that does have one, and a `sortFrom`/`sortTo` window only ever selects from records carrying a value. The sorted field does not have to be `required`.
|
|
9431
|
+
*
|
|
9432
|
+
* Narrowing is available on any lookup field your schema declares for fast equality lookup, and on `externalId` — which is always ordered by creation time, so its bounds are epoch milliseconds whatever the schema says. It is rejected (`400`) for a field declared with `rangeEnabled`, for a field declared beyond the schema's fast-lookup budget, for a lookup whose `sortBy` names a sensitive field (a sensitive value is stored as a blind index and has no order), and for a window whose start is after its end. Ownership fields are not lookup fields on this endpoint at all — see the `field` parameter.
|
|
9433
|
+
*
|
|
9434
|
+
* While paging a narrowed lookup, keep every other parameter identical. The cursor is valid only for the exact query that returned it — changing `order`, `sortFrom`, `sortTo`, or dropping them altogether, is rejected rather than silently resumed at a position that means something different in the new query.
|
|
9435
|
+
*
|
|
9436
|
+
* Results are paginated: set `limit` for the page size and pass the returned `nextCursor` back as `startFrom` for the next page. **Keep paging until `nextCursor` is null** — a page can come back empty or shorter than `limit` while more results remain, so an empty page is not the end of the results. Requires the `records:r:<type>` scope.
|
|
9410
9437
|
*
|
|
9411
9438
|
* @param {Vectros.LookupRecordsRequest} request
|
|
9412
9439
|
* @param {RecordsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -9418,22 +9445,40 @@ var RecordsClient = class {
|
|
|
9418
9445
|
* type: "intake_form",
|
|
9419
9446
|
* field: "email",
|
|
9420
9447
|
* value: "jane@example.com",
|
|
9448
|
+
* values: ["open"],
|
|
9421
9449
|
* prefix: "jane",
|
|
9422
|
-
* startFrom: "
|
|
9450
|
+
* startFrom: "b3BhcXVlLWN1cnNvci1mcm9tLXRoZS1wcmV2aW91cy1wYWdl"
|
|
9423
9451
|
* })
|
|
9424
9452
|
*/
|
|
9425
9453
|
lookupRecords(request, requestOptions) {
|
|
9426
9454
|
return HttpResponsePromise.fromPromise(this.__lookupRecords(request, requestOptions));
|
|
9427
9455
|
}
|
|
9428
9456
|
async __lookupRecords(request, requestOptions) {
|
|
9429
|
-
const {
|
|
9457
|
+
const {
|
|
9458
|
+
type: type_,
|
|
9459
|
+
field,
|
|
9460
|
+
value,
|
|
9461
|
+
values,
|
|
9462
|
+
from: from_,
|
|
9463
|
+
to,
|
|
9464
|
+
prefix,
|
|
9465
|
+
sortFrom,
|
|
9466
|
+
sortTo,
|
|
9467
|
+
startFrom,
|
|
9468
|
+
limit,
|
|
9469
|
+
includePayload,
|
|
9470
|
+
order
|
|
9471
|
+
} = request;
|
|
9430
9472
|
const _queryParams = {
|
|
9431
9473
|
type: type_,
|
|
9432
9474
|
field,
|
|
9433
9475
|
value,
|
|
9476
|
+
values,
|
|
9434
9477
|
from: from_,
|
|
9435
9478
|
to,
|
|
9436
9479
|
prefix,
|
|
9480
|
+
sortFrom,
|
|
9481
|
+
sortTo,
|
|
9437
9482
|
startFrom,
|
|
9438
9483
|
limit,
|
|
9439
9484
|
includePayload,
|
|
@@ -9477,7 +9522,7 @@ var RecordsClient = class {
|
|
|
9477
9522
|
return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/records/lookup");
|
|
9478
9523
|
}
|
|
9479
9524
|
/**
|
|
9480
|
-
* Body-based equivalent of `GET /v1/records/lookup`. Use this when looking up by a sensitive field: the value travels in the request body (and is blind-indexed server-side) instead of in the URL query string, so it never lands in access, CDN, or proxy logs. The GET variant rejects an exact-value lookup on a sensitive field and directs you here. Non-sensitive exact-value, range (`from`+`to`), and prefix lookups also work here. Returns the same `{data, nextCursor}` envelope and uses the same pagination as the GET variant. Requires the `records:r:<type>` scope.
|
|
9525
|
+
* Body-based equivalent of `GET /v1/records/lookup`. Use this when looking up by a sensitive field: the value travels in the request body (and is blind-indexed server-side) instead of in the URL query string, so it never lands in access, CDN, or proxy logs. The GET variant rejects an exact-value lookup on a sensitive field and directs you here. Non-sensitive exact-value, range (`from`+`to`), and prefix lookups also work here, as does narrowing a `value` lookup by the field's sort key with `sortFrom`/`sortTo` — see the GET variant for what the sort key is and when narrowing by it is available. Returns the same `{data, nextCursor}` envelope and uses the same pagination as the GET variant; keep paging until `nextCursor` is null rather than stopping on an empty page. Requires the `records:r:<type>` scope.
|
|
9481
9526
|
*
|
|
9482
9527
|
* @param {Vectros.RecordLookupRequest} request
|
|
9483
9528
|
* @param {RecordsClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
@@ -9672,7 +9717,7 @@ var SchemasClient = class {
|
|
|
9672
9717
|
* scope: "org:6ba7b810-9dad-11d1-80b4-00c04fd430c8",
|
|
9673
9718
|
* surface: "document",
|
|
9674
9719
|
* recordType: "intake_form",
|
|
9675
|
-
* startFrom: "
|
|
9720
|
+
* startFrom: "b3BhcXVlLWN1cnNvci1mcm9tLXRoZS1wcmV2aW91cy1wYWdl"
|
|
9676
9721
|
* })
|
|
9677
9722
|
*/
|
|
9678
9723
|
listSchemas(request = {}, requestOptions) {
|
|
@@ -9876,6 +9921,7 @@ var SchemasClient = class {
|
|
|
9876
9921
|
* @param {SchemasClient.RequestOptions} requestOptions - Request-specific configuration.
|
|
9877
9922
|
*
|
|
9878
9923
|
* @throws {@link Vectros.BadRequestError}
|
|
9924
|
+
* @throws {@link Vectros.ForbiddenError}
|
|
9879
9925
|
* @throws {@link Vectros.NotFoundError}
|
|
9880
9926
|
* @throws {@link Vectros.TooManyRequestsError}
|
|
9881
9927
|
*
|
|
@@ -9924,6 +9970,8 @@ var SchemasClient = class {
|
|
|
9924
9970
|
switch (_response.error.statusCode) {
|
|
9925
9971
|
case 400:
|
|
9926
9972
|
throw new BadRequestError(_response.error.body, _response.rawResponse);
|
|
9973
|
+
case 403:
|
|
9974
|
+
throw new ForbiddenError(_response.error.body, _response.rawResponse);
|
|
9927
9975
|
case 404:
|
|
9928
9976
|
throw new NotFoundError(_response.error.body, _response.rawResponse);
|
|
9929
9977
|
case 429:
|
|
@@ -10190,7 +10238,7 @@ var logging;
|
|
|
10190
10238
|
logging2.ConsoleLogger = ConsoleLogger;
|
|
10191
10239
|
})(logging || (logging = {}));
|
|
10192
10240
|
|
|
10193
|
-
//
|
|
10241
|
+
// node_modules/@vectros-ai/blueprints/dist/index.mjs
|
|
10194
10242
|
import { z } from "zod";
|
|
10195
10243
|
import { z as z2 } from "zod";
|
|
10196
10244
|
var CONTEXT_ID_RE = /^[a-z][a-z0-9-]{2,30}$/;
|
|
@@ -10294,19 +10342,63 @@ var BlueprintFieldDefSchema = z.object({
|
|
|
10294
10342
|
var BlueprintLookupFieldSchema = z.union([
|
|
10295
10343
|
z.string().min(1),
|
|
10296
10344
|
z.object({
|
|
10297
|
-
|
|
10345
|
+
// A lookup field declares either ONE field (fieldName) or 2-3 fields
|
|
10346
|
+
// matched together (fieldNames) — never both, never neither. `.strict()`
|
|
10347
|
+
// below only rejects unknown KEYS; it does not enforce this combination,
|
|
10348
|
+
// so the `.superRefine()` after it carries the actual rule.
|
|
10349
|
+
fieldName: z.string().min(1).optional(),
|
|
10350
|
+
// Composite (conjunctive) lookup: match on all of these fields at once —
|
|
10351
|
+
// "every record where `status` is `open` AND `area` is `billing`", exact,
|
|
10352
|
+
// in a stable order. Order is significant and locked at create: a query
|
|
10353
|
+
// may match a leading run of the list (the first field alone, the first
|
|
10354
|
+
// two together, …) but never a later field alone — declare a separate
|
|
10355
|
+
// lookup for that. Record-only: the schema's `allowedSurfaces` must be
|
|
10356
|
+
// exactly `['record']` (checked at the schema level below) — the
|
|
10357
|
+
// platform's composite index has no document/user/entity reader yet.
|
|
10358
|
+
fieldNames: z.array(z.string().min(1)).min(2).max(3).optional(),
|
|
10359
|
+
// Not available on a composite (`fieldNames`) lookup — checked below.
|
|
10298
10360
|
unique: z.boolean().optional(),
|
|
10299
10361
|
// Opt this field into ordered range + prefix lookups (from/to/prefix) on
|
|
10300
10362
|
// top of exact match. Billed at the range-index rate; not valid on a
|
|
10301
|
-
// sensitive field (a blind index is not orderable)
|
|
10363
|
+
// sensitive field (a blind index is not orderable), and — like `unique`
|
|
10364
|
+
// — not available on a composite lookup: it is an exact-match index over
|
|
10365
|
+
// its fields; declare the range lookup separately. Locked at create.
|
|
10302
10366
|
rangeEnabled: z.boolean().optional(),
|
|
10303
10367
|
// Sort key for the exact-match index: 'createdAt' (default), 'lastUpdated',
|
|
10304
|
-
// or a declared field on this schema. Locked at create.
|
|
10368
|
+
// or a declared field on this schema. Locked at create. Valid on a
|
|
10369
|
+
// composite lookup too — it orders WITHIN a group when a query supplies
|
|
10370
|
+
// fewer values than the lookup declares, not across the whole result.
|
|
10305
10371
|
sortBy: z.string().min(1).optional(),
|
|
10306
10372
|
// Opt a field past the fixed fast-index budget into a higher-cost
|
|
10307
10373
|
// secondary index. No effect on a field that fits within the budget.
|
|
10308
10374
|
allowOverflow: z.boolean().optional()
|
|
10309
|
-
}).strict()
|
|
10375
|
+
}).strict().superRefine((lf, ctx) => {
|
|
10376
|
+
const hasFieldName = lf.fieldName !== void 0;
|
|
10377
|
+
const hasFieldNames = lf.fieldNames !== void 0;
|
|
10378
|
+
if (hasFieldName === hasFieldNames) {
|
|
10379
|
+
ctx.addIssue({
|
|
10380
|
+
code: z.ZodIssueCode.custom,
|
|
10381
|
+
message: "a lookup field declares either 'fieldName' (one field) or 'fieldNames' (2-3 fields matched together), never both and never neither"
|
|
10382
|
+
});
|
|
10383
|
+
return;
|
|
10384
|
+
}
|
|
10385
|
+
if (hasFieldNames) {
|
|
10386
|
+
if (lf.unique !== void 0) {
|
|
10387
|
+
ctx.addIssue({
|
|
10388
|
+
code: z.ZodIssueCode.custom,
|
|
10389
|
+
path: ["unique"],
|
|
10390
|
+
message: "'unique' is not available on a composite ('fieldNames') lookup"
|
|
10391
|
+
});
|
|
10392
|
+
}
|
|
10393
|
+
if (lf.rangeEnabled !== void 0) {
|
|
10394
|
+
ctx.addIssue({
|
|
10395
|
+
code: z.ZodIssueCode.custom,
|
|
10396
|
+
path: ["rangeEnabled"],
|
|
10397
|
+
message: "'rangeEnabled' is not available on a composite ('fieldNames') lookup"
|
|
10398
|
+
});
|
|
10399
|
+
}
|
|
10400
|
+
}
|
|
10401
|
+
})
|
|
10310
10402
|
]);
|
|
10311
10403
|
var BlueprintCapabilitiesSchema = z.object({
|
|
10312
10404
|
auditHistory: z.boolean().optional()
|
|
@@ -10344,7 +10436,21 @@ var BlueprintSchemaSchema = z.object({
|
|
|
10344
10436
|
// hop); immutable once set. Mirrors the `basedOn` field on the platform's
|
|
10345
10437
|
// schema-create request.
|
|
10346
10438
|
basedOn: z.string().min(1).optional()
|
|
10347
|
-
}).strict()
|
|
10439
|
+
}).strict().superRefine((schema, ctx) => {
|
|
10440
|
+
const hasComposite = (schema.lookupFields ?? []).some(
|
|
10441
|
+
(lf) => typeof lf === "object" && lf.fieldNames !== void 0
|
|
10442
|
+
);
|
|
10443
|
+
if (!hasComposite) return;
|
|
10444
|
+
const surfaces = schema.allowedSurfaces;
|
|
10445
|
+
const isRecordOnly = surfaces === void 0 || surfaces.length === 1 && surfaces[0] === "record";
|
|
10446
|
+
if (!isRecordOnly) {
|
|
10447
|
+
ctx.addIssue({
|
|
10448
|
+
code: z.ZodIssueCode.custom,
|
|
10449
|
+
path: ["allowedSurfaces"],
|
|
10450
|
+
message: `schema '${schema.typeName}': a composite ('fieldNames') lookup requires allowedSurfaces to be exactly ['record'] \u2014 the platform's composite index has no document/user/entity reader yet`
|
|
10451
|
+
});
|
|
10452
|
+
}
|
|
10453
|
+
});
|
|
10348
10454
|
var BlueprintAccessProfileSchema = z.object({
|
|
10349
10455
|
// Validated structurally here; the SCOPE GATE (in @vectros-ai/cli) is
|
|
10350
10456
|
// what enforces the data-plane-only security boundary.
|
|
@@ -12283,9 +12389,10 @@ var secondBrain = {
|
|
|
12283
12389
|
// Lookup shapes are MIGRATION-LOCKED once the schema is live (a field cannot
|
|
12284
12390
|
// flip equality↔range later), so each is chosen on purpose:
|
|
12285
12391
|
// • `source` — EQUALITY, sorted by `lastUpdated`: enumerate notes from one
|
|
12286
|
-
// source, most-recently-touched first.
|
|
12287
|
-
//
|
|
12288
|
-
//
|
|
12392
|
+
// source, most-recently-touched first. A platform timestamp is the natural
|
|
12393
|
+
// choice for "most recently touched" and is always present, so every note
|
|
12394
|
+
// has a position. (Sorting by an OPTIONAL payload field is also fine — notes
|
|
12395
|
+
// lacking it are ordered ahead of those that have it, never dropped.)
|
|
12289
12396
|
// • `status` — EQUALITY: "show archived notes".
|
|
12290
12397
|
// • `capturedAt` — RANGE: ordered `from`/`to`/`prefix` ("captured in 2026-06").
|
|
12291
12398
|
// `externalId` has a built-in first-class finder — look it up directly, never
|
|
@@ -13407,7 +13514,16 @@ function toSdkFields(blueprint, schemaIndex) {
|
|
|
13407
13514
|
function toLookupDefs(lookupFields) {
|
|
13408
13515
|
return lookupFields?.map((lf) => {
|
|
13409
13516
|
if (typeof lf === "string") return { fieldName: lf };
|
|
13410
|
-
const
|
|
13517
|
+
const hasFieldName = lf.fieldName !== void 0;
|
|
13518
|
+
const hasFieldNames = lf.fieldNames !== void 0;
|
|
13519
|
+
if (hasFieldName === hasFieldNames) {
|
|
13520
|
+
throw new Error(
|
|
13521
|
+
`Internal error: lookupField resolved to ${hasFieldName ? "both fieldName and fieldNames" : "neither fieldName nor fieldNames"} \u2014 @vectros-ai/blueprints' parseBlueprint should have refused this before the loader ever saw it.`
|
|
13522
|
+
);
|
|
13523
|
+
}
|
|
13524
|
+
const def = {};
|
|
13525
|
+
if (lf.fieldName !== void 0) def.fieldName = lf.fieldName;
|
|
13526
|
+
if (lf.fieldNames !== void 0) def.fieldNames = lf.fieldNames;
|
|
13411
13527
|
if (lf.unique !== void 0) def.unique = lf.unique;
|
|
13412
13528
|
if (lf.rangeEnabled !== void 0) def.rangeEnabled = lf.rangeEnabled;
|
|
13413
13529
|
if (lf.sortBy !== void 0) def.sortBy = lf.sortBy;
|
|
@@ -13670,7 +13786,7 @@ function buildPlan(blueprint, opts = {}) {
|
|
|
13670
13786
|
summary: `principal '${blueprint.servicePrincipal.externalId}' (SERVICE)`
|
|
13671
13787
|
});
|
|
13672
13788
|
for (const s of blueprint.schemas) {
|
|
13673
|
-
const lookups = toLookupDefs(s.lookupFields)?.map((l) => l.fieldName) ?? [];
|
|
13789
|
+
const lookups = toLookupDefs(s.lookupFields)?.map((l) => l.fieldNames ? `(${l.fieldNames.join("+")})` : l.fieldName) ?? [];
|
|
13674
13790
|
const lookupNote = lookups.length ? ` lookup: [${lookups.join(", ")}]` : "";
|
|
13675
13791
|
const surfaceNote = s.allowedSurfaces ? ` surfaces: [${s.allowedSurfaces.join(", ")}]` : "";
|
|
13676
13792
|
const sensitive = s.fields.filter((f) => f.sensitive).map((f) => f.fieldId);
|
|
@@ -14262,10 +14378,121 @@ ${formatCliError(err, { action: "Bootstrap failed" })}`);
|
|
|
14262
14378
|
// src/cli-blueprint.ts
|
|
14263
14379
|
import { writeFile as writeFile2, access } from "fs/promises";
|
|
14264
14380
|
|
|
14381
|
+
// src/scope-grammar.ts
|
|
14382
|
+
var FORBIDDEN_SCOPE_NAMESPACES = /* @__PURE__ */ new Set([
|
|
14383
|
+
"user",
|
|
14384
|
+
"record",
|
|
14385
|
+
"document",
|
|
14386
|
+
"entity",
|
|
14387
|
+
"self",
|
|
14388
|
+
"tenant",
|
|
14389
|
+
"context",
|
|
14390
|
+
"scope",
|
|
14391
|
+
"versions",
|
|
14392
|
+
"lookup"
|
|
14393
|
+
]);
|
|
14394
|
+
var SCOPE_NAMESPACE_RE = /^[a-z][a-z0-9_-]{1,31}$/;
|
|
14395
|
+
var SCOPE_NAMESPACE_MIN_LENGTH = 2;
|
|
14396
|
+
var SCOPE_NAMESPACE_MAX_LENGTH = 32;
|
|
14397
|
+
var SCOPE_VALUE_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
|
|
14398
|
+
var SCOPE_VALUE_MIN_LENGTH = 1;
|
|
14399
|
+
var SCOPE_VALUE_MAX_LENGTH = 128;
|
|
14400
|
+
function valueGrammarError(value) {
|
|
14401
|
+
if (!SCOPE_VALUE_RE.test(value)) {
|
|
14402
|
+
return `'${value}' is not a valid scope value \u2014 ${SCOPE_VALUE_MIN_LENGTH}-${SCOPE_VALUE_MAX_LENGTH} characters, starting with a letter or digit and continuing with letters, digits, '_' or '-'. A value may not contain a colon.`;
|
|
14403
|
+
}
|
|
14404
|
+
return null;
|
|
14405
|
+
}
|
|
14406
|
+
function namespaceGrammarError(ns) {
|
|
14407
|
+
if (FORBIDDEN_SCOPE_NAMESPACES.has(ns)) {
|
|
14408
|
+
return `'${ns}' is a reserved namespace and cannot be used as an ownership scope`;
|
|
14409
|
+
}
|
|
14410
|
+
if (!SCOPE_NAMESPACE_RE.test(ns)) {
|
|
14411
|
+
return `'${ns}' is not a valid namespace \u2014 lowercase, ${SCOPE_NAMESPACE_MIN_LENGTH}-${SCOPE_NAMESPACE_MAX_LENGTH} characters, starting with a letter and continuing with letters, digits, '_' or '-'`;
|
|
14412
|
+
}
|
|
14413
|
+
return null;
|
|
14414
|
+
}
|
|
14415
|
+
function assertScopeEntries(entries, flag) {
|
|
14416
|
+
if (!entries?.length) return;
|
|
14417
|
+
for (const raw of entries) {
|
|
14418
|
+
const sep = raw.indexOf(":");
|
|
14419
|
+
if (sep <= 0 || sep === raw.length - 1) {
|
|
14420
|
+
throw new Error(`${flag} must be '<namespace>:<value>' (e.g. 'org:6ba7...'); got '${raw}'.`);
|
|
14421
|
+
}
|
|
14422
|
+
const nsError = namespaceGrammarError(raw.slice(0, sep));
|
|
14423
|
+
if (nsError) throw new Error(`${flag} namespace ${nsError}`);
|
|
14424
|
+
const valError = valueGrammarError(raw.slice(sep + 1));
|
|
14425
|
+
if (valError) throw new Error(`${flag} value ${valError}`);
|
|
14426
|
+
}
|
|
14427
|
+
}
|
|
14428
|
+
function assertIdentityOverrideValues(overrides, flag) {
|
|
14429
|
+
if (!overrides) return;
|
|
14430
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
14431
|
+
if (!key.startsWith("scope:")) continue;
|
|
14432
|
+
const error = valueGrammarError(typeof value === "string" ? value : String(value));
|
|
14433
|
+
if (error) throw new Error(`${flag} '${key}' value ${error}`);
|
|
14434
|
+
}
|
|
14435
|
+
}
|
|
14436
|
+
function placeholderScopeNamespace(value) {
|
|
14437
|
+
const m = /^\$\{\{\s*(?:under\.)?self\.scope\.([^\s}]+)\s*\}\}$/.exec(value);
|
|
14438
|
+
return m ? m[1] : null;
|
|
14439
|
+
}
|
|
14440
|
+
var SELF_PLACEHOLDER_RE = /^\$\{\{\s*self\.(?:userId|partnerUserId|scope\.[a-z][a-z0-9_-]{1,31})\s*\}\}$/;
|
|
14441
|
+
var UNDER_SELF_PLACEHOLDER_RE = /^\$\{\{\s*under\.self\.(?:userId|partnerUserId|scope\.[a-z][a-z0-9_-]{1,31})\s*\}\}$/;
|
|
14442
|
+
|
|
14265
14443
|
// src/lint.ts
|
|
14266
14444
|
var ENUM_COMPATIBLE_TYPES = /* @__PURE__ */ new Set(["enum", "string", "string[]"]);
|
|
14267
|
-
|
|
14268
|
-
|
|
14445
|
+
var BUILT_IN_SORT_BY = /* @__PURE__ */ new Set(["createdAt", "lastUpdated"]);
|
|
14446
|
+
function lookupFieldNames(lf) {
|
|
14447
|
+
if (typeof lf === "string") return [lf];
|
|
14448
|
+
if (lf.fieldNames !== void 0) return lf.fieldNames;
|
|
14449
|
+
return lf.fieldName !== void 0 ? [lf.fieldName] : [];
|
|
14450
|
+
}
|
|
14451
|
+
function compositeLegFindings(schema, lf, fieldsById, rangeEnabledFieldNames) {
|
|
14452
|
+
const findings = [];
|
|
14453
|
+
const id = `[${lf.fieldNames.join(", ")}]`;
|
|
14454
|
+
const seenLegs = /* @__PURE__ */ new Set();
|
|
14455
|
+
for (const leg of lf.fieldNames) {
|
|
14456
|
+
if (seenLegs.has(leg)) {
|
|
14457
|
+
findings.push({
|
|
14458
|
+
severity: "error",
|
|
14459
|
+
rule: "lookup-composite-repeated-leg",
|
|
14460
|
+
message: `schema '${schema.typeName}': composite lookupField ${id} repeats the leg '${leg}'. Each leg occupies its own position in the sort key, so a repeat doubles the key cost of every row to express a conjunction of a field with itself.`
|
|
14461
|
+
});
|
|
14462
|
+
}
|
|
14463
|
+
seenLegs.add(leg);
|
|
14464
|
+
const field = fieldsById.get(leg);
|
|
14465
|
+
if (!field) continue;
|
|
14466
|
+
if (field.fieldType === "array" || field.fieldType === "object") {
|
|
14467
|
+
findings.push({
|
|
14468
|
+
severity: "error",
|
|
14469
|
+
rule: "lookup-composite-leg-multi-valued",
|
|
14470
|
+
message: `schema '${schema.typeName}': composite lookupField ${id} uses '${leg}' as a leg, but it is declared '${field.fieldType}'. A leg occupies one sort-key position and holds one value \u2014 a multi-valued or structured field would index only one rendering and the conjunction would return a subset while appearing complete. Use a scalar field as a leg.`
|
|
14471
|
+
});
|
|
14472
|
+
}
|
|
14473
|
+
if (field.fieldType === "reference" && field.cardinality === "many") {
|
|
14474
|
+
findings.push({
|
|
14475
|
+
severity: "error",
|
|
14476
|
+
rule: "lookup-composite-leg-multi-valued",
|
|
14477
|
+
message: `schema '${schema.typeName}': composite lookupField ${id} uses '${leg}' as a leg, but it is a reference with cardinality 'many'. One sort-key position holds one value, so a multi-valued leg would index only one target and the conjunction would silently return a subset. Use a cardinality 'one' reference, or a scalar field.`
|
|
14478
|
+
});
|
|
14479
|
+
}
|
|
14480
|
+
if (rangeEnabledFieldNames.has(leg)) {
|
|
14481
|
+
findings.push({
|
|
14482
|
+
severity: "error",
|
|
14483
|
+
rule: "lookup-composite-leg-range-enabled",
|
|
14484
|
+
message: `schema '${schema.typeName}': composite lookupField ${id} uses '${leg}' as a leg, but '${leg}' has its own lookupField declaring rangeEnabled \u2014 that stores it as an ordered row keyed on its own value, not an equality index. A leg is matched exactly. Declare the composite over non-range fields, and keep the range lookup as its own declaration.`
|
|
14485
|
+
});
|
|
14486
|
+
}
|
|
14487
|
+
if (lf.sortBy !== void 0 && leg === lf.sortBy) {
|
|
14488
|
+
findings.push({
|
|
14489
|
+
severity: "error",
|
|
14490
|
+
rule: "lookup-composite-leg-is-sortby",
|
|
14491
|
+
message: `schema '${schema.typeName}': composite lookupField ${id} cannot use '${leg}' as both a leg and its sortBy target. Within a fixed tuple every matching record has the same value for that leg, so the sort payload would be constant and a sortFrom/sortTo window would return either everything or nothing \u2014 at double the sort-key cost. Sort by a different field, 'createdAt', or 'lastUpdated'.`
|
|
14492
|
+
});
|
|
14493
|
+
}
|
|
14494
|
+
}
|
|
14495
|
+
return findings;
|
|
14269
14496
|
}
|
|
14270
14497
|
function lintBlueprint(bp) {
|
|
14271
14498
|
const findings = [];
|
|
@@ -14273,6 +14500,7 @@ function lintBlueprint(bp) {
|
|
|
14273
14500
|
for (const schema of bp.schemas) {
|
|
14274
14501
|
typeNames.add(schema.typeName);
|
|
14275
14502
|
const fieldIds = /* @__PURE__ */ new Set();
|
|
14503
|
+
const fieldsById = /* @__PURE__ */ new Map();
|
|
14276
14504
|
for (const f of schema.fields) {
|
|
14277
14505
|
if (fieldIds.has(f.fieldId)) {
|
|
14278
14506
|
findings.push({
|
|
@@ -14282,6 +14510,7 @@ function lintBlueprint(bp) {
|
|
|
14282
14510
|
});
|
|
14283
14511
|
}
|
|
14284
14512
|
fieldIds.add(f.fieldId);
|
|
14513
|
+
fieldsById.set(f.fieldId, f);
|
|
14285
14514
|
if (f.enumValues && f.enumValues.length > 0 && !ENUM_COMPATIBLE_TYPES.has(f.fieldType)) {
|
|
14286
14515
|
findings.push({
|
|
14287
14516
|
severity: "warning",
|
|
@@ -14290,14 +14519,35 @@ function lintBlueprint(bp) {
|
|
|
14290
14519
|
});
|
|
14291
14520
|
}
|
|
14292
14521
|
}
|
|
14522
|
+
const rangeEnabledFieldNames = /* @__PURE__ */ new Set();
|
|
14293
14523
|
for (const lf of schema.lookupFields ?? []) {
|
|
14294
|
-
|
|
14295
|
-
|
|
14296
|
-
|
|
14297
|
-
|
|
14298
|
-
|
|
14299
|
-
|
|
14300
|
-
|
|
14524
|
+
if (typeof lf !== "string" && lf.fieldName !== void 0 && lf.rangeEnabled) {
|
|
14525
|
+
rangeEnabledFieldNames.add(lf.fieldName);
|
|
14526
|
+
}
|
|
14527
|
+
}
|
|
14528
|
+
for (const lf of schema.lookupFields ?? []) {
|
|
14529
|
+
for (const name of lookupFieldNames(lf)) {
|
|
14530
|
+
if (!fieldIds.has(name)) {
|
|
14531
|
+
findings.push({
|
|
14532
|
+
severity: "error",
|
|
14533
|
+
rule: "lookup-field-unknown",
|
|
14534
|
+
message: `schema '${schema.typeName}': lookupField '${name}' is not a declared field.`
|
|
14535
|
+
});
|
|
14536
|
+
}
|
|
14537
|
+
}
|
|
14538
|
+
if (typeof lf !== "string" && lf.fieldNames !== void 0) {
|
|
14539
|
+
findings.push(
|
|
14540
|
+
...compositeLegFindings(schema, { fieldNames: lf.fieldNames, sortBy: lf.sortBy }, fieldsById, rangeEnabledFieldNames)
|
|
14541
|
+
);
|
|
14542
|
+
}
|
|
14543
|
+
if (typeof lf !== "string" && lf.sortBy !== void 0) {
|
|
14544
|
+
if (!BUILT_IN_SORT_BY.has(lf.sortBy) && !fieldIds.has(lf.sortBy)) {
|
|
14545
|
+
findings.push({
|
|
14546
|
+
severity: "warning",
|
|
14547
|
+
rule: "lookup-sortby-unknown-field",
|
|
14548
|
+
message: `schema '${schema.typeName}': lookupField sortBy '${lf.sortBy}' is not a declared field (nor 'createdAt'/'lastUpdated'). The platform does not reject this \u2014 it silently treats the sort target as an untyped field no record ever populates, which usually isn't what was intended. Likely a typo.`
|
|
14549
|
+
});
|
|
14550
|
+
}
|
|
14301
14551
|
}
|
|
14302
14552
|
}
|
|
14303
14553
|
}
|
|
@@ -14313,6 +14563,10 @@ function lintBlueprint(bp) {
|
|
|
14313
14563
|
const ds = bp.accessProfile.dataScope;
|
|
14314
14564
|
if (ds) {
|
|
14315
14565
|
for (const [key, values] of Object.entries(ds)) {
|
|
14566
|
+
for (const value of values) {
|
|
14567
|
+
const bad = placeholderFinding("accessProfile", key, value);
|
|
14568
|
+
if (bad) findings.push(bad);
|
|
14569
|
+
}
|
|
14316
14570
|
if (values.length > 0 && !values.includes(null)) {
|
|
14317
14571
|
findings.push({
|
|
14318
14572
|
severity: "warning",
|
|
@@ -14336,6 +14590,10 @@ function lintBlueprint(bp) {
|
|
|
14336
14590
|
const cds = clause.dataScope;
|
|
14337
14591
|
if (!cds) return;
|
|
14338
14592
|
for (const [key, values] of Object.entries(cds)) {
|
|
14593
|
+
for (const value of values) {
|
|
14594
|
+
const bad = placeholderFinding(`role '${roleId}' clause ${i}`, key, value);
|
|
14595
|
+
if (bad) findings.push(bad);
|
|
14596
|
+
}
|
|
14339
14597
|
if (values.length > 0 && !values.includes(null)) {
|
|
14340
14598
|
findings.push({
|
|
14341
14599
|
severity: "warning",
|
|
@@ -14349,6 +14607,28 @@ function lintBlueprint(bp) {
|
|
|
14349
14607
|
return findings;
|
|
14350
14608
|
}
|
|
14351
14609
|
var ROLE_ID_RE = /^[a-z][a-z0-9-]{2,30}$/;
|
|
14610
|
+
var PLACEHOLDER_SHAPED_RE = /^\s*\$\{\{.*\}\}\s*$/s;
|
|
14611
|
+
var RECOGNIZED_PLACEHOLDER_RES = [/^\$\{\{\s*any\s*\}\}$/, SELF_PLACEHOLDER_RE, UNDER_SELF_PLACEHOLDER_RE];
|
|
14612
|
+
var BLUEPRINT_TOKEN_RE = /^\s*\$\{\{\s*(identities|inputs|vectros|ns)\./;
|
|
14613
|
+
function placeholderFinding(where, key, value) {
|
|
14614
|
+
if (typeof value !== "string" || !PLACEHOLDER_SHAPED_RE.test(value)) return null;
|
|
14615
|
+
if (BLUEPRINT_TOKEN_RE.test(value)) return null;
|
|
14616
|
+
if (RECOGNIZED_PLACEHOLDER_RES.some((re) => re.test(value))) {
|
|
14617
|
+
const ns = placeholderScopeNamespace(value);
|
|
14618
|
+
const nsError = ns === null ? null : namespaceGrammarError(ns);
|
|
14619
|
+
if (nsError === null) return null;
|
|
14620
|
+
return {
|
|
14621
|
+
severity: "error",
|
|
14622
|
+
rule: "datascope-unknown-placeholder",
|
|
14623
|
+
message: `${where}: dataScope.${key} value '${value}' names a namespace the platform rejects \u2014 ${nsError}. It will be rejected at apply.`
|
|
14624
|
+
};
|
|
14625
|
+
}
|
|
14626
|
+
return {
|
|
14627
|
+
severity: "error",
|
|
14628
|
+
rule: "datascope-unknown-placeholder",
|
|
14629
|
+
message: `${where}: dataScope.${key} value '${value}' is not a placeholder the platform recognizes \u2014 it will be rejected at apply. Valid: \${{ any }}, \${{ self.userId }}, \${{ self.scope.<namespace> }}, \${{ under.self.userId }}, \${{ under.self.scope.<namespace> }}. Spelling is exact and lowercase; use a plain value for a literal owner id.`
|
|
14630
|
+
};
|
|
14631
|
+
}
|
|
14352
14632
|
function lintPasses(findings) {
|
|
14353
14633
|
return !findings.some((f) => f.severity === "error");
|
|
14354
14634
|
}
|
|
@@ -14752,11 +15032,14 @@ function parseScopeOverrides(scopes) {
|
|
|
14752
15032
|
throw new Error(`--scope must be '<namespace>:<value>' (e.g. 'org:6ba7...'); got '${raw}'.`);
|
|
14753
15033
|
}
|
|
14754
15034
|
const ns = raw.slice(0, sep);
|
|
15035
|
+
const value = raw.slice(sep + 1);
|
|
15036
|
+
const valueError = valueGrammarError(value);
|
|
15037
|
+
if (valueError) throw new Error(`--scope value ${valueError}`);
|
|
14755
15038
|
const key = `scope:${ns}`;
|
|
14756
15039
|
if (Object.prototype.hasOwnProperty.call(out, key)) {
|
|
14757
15040
|
throw new Error(`--scope sets namespace '${ns}' more than once \u2014 a scope override takes at most one value per namespace.`);
|
|
14758
15041
|
}
|
|
14759
|
-
out[key] =
|
|
15042
|
+
out[key] = value;
|
|
14760
15043
|
}
|
|
14761
15044
|
if (Object.keys(out).length > 2) {
|
|
14762
15045
|
throw new Error("--scope allows at most two distinct namespaces.");
|
|
@@ -15101,6 +15384,7 @@ async function createIdentity(client, opts, payload) {
|
|
|
15101
15384
|
}
|
|
15102
15385
|
});
|
|
15103
15386
|
}
|
|
15387
|
+
assertScopeEntries(opts.scope, "--scope");
|
|
15104
15388
|
return client.identity.createEntity({
|
|
15105
15389
|
namespace: opts.type,
|
|
15106
15390
|
body: {
|
|
@@ -15306,6 +15590,7 @@ async function runAccessGrant(opts, deps = {}) {
|
|
|
15306
15590
|
...parseScopeOverrides(opts.scope) ?? {},
|
|
15307
15591
|
...opts.identityOverrides ? parseJsonObjectFlag("--identity-overrides", opts.identityOverrides) : {}
|
|
15308
15592
|
};
|
|
15593
|
+
assertIdentityOverrideValues(identityOverrides, "--identity-overrides");
|
|
15309
15594
|
} catch (err) {
|
|
15310
15595
|
process.stderr.write(` \u2716 ${formatError(err)}
|
|
15311
15596
|
`);
|
|
@@ -15458,30 +15743,21 @@ ${renderProfile(p)}
|
|
|
15458
15743
|
|
|
15459
15744
|
// src/commands/access-explain.ts
|
|
15460
15745
|
var SELF = /* @__PURE__ */ Symbol("self");
|
|
15746
|
+
var ANY = /* @__PURE__ */ Symbol("any");
|
|
15747
|
+
var UNDER_SELF = /* @__PURE__ */ Symbol("under.self");
|
|
15461
15748
|
function canonicalDim(key) {
|
|
15462
15749
|
if (key === "userId" || key === "partnerUserId") return "user";
|
|
15463
15750
|
return key;
|
|
15464
15751
|
}
|
|
15465
|
-
var FORBIDDEN_SCOPE_NAMESPACES = /* @__PURE__ */ new Set([
|
|
15466
|
-
"user",
|
|
15467
|
-
"record",
|
|
15468
|
-
"document",
|
|
15469
|
-
"entity",
|
|
15470
|
-
"self",
|
|
15471
|
-
"tenant",
|
|
15472
|
-
"context",
|
|
15473
|
-
"scope",
|
|
15474
|
-
"versions",
|
|
15475
|
-
"lookup"
|
|
15476
|
-
]);
|
|
15477
|
-
var SCOPE_NAMESPACE_RE = /^[a-z][a-z0-9_-]{1,31}$/;
|
|
15478
15752
|
function isRecognizedOwnershipKey(key) {
|
|
15753
|
+
if (key === DIM_WILDCARD) return true;
|
|
15479
15754
|
if (key === "userId" || key === "partnerUserId") return true;
|
|
15480
15755
|
if (!key.startsWith("scope:")) return false;
|
|
15481
15756
|
const ns = key.slice("scope:".length);
|
|
15482
15757
|
return !FORBIDDEN_SCOPE_NAMESPACES.has(ns) && SCOPE_NAMESPACE_RE.test(ns);
|
|
15483
15758
|
}
|
|
15484
|
-
var
|
|
15759
|
+
var DIM_WILDCARD = "*";
|
|
15760
|
+
var ANY_TOKEN_RE = /^\$\{\{\s*any\s*\}\}$/;
|
|
15485
15761
|
function normalizeClauses(raw) {
|
|
15486
15762
|
const out = [];
|
|
15487
15763
|
for (const c of raw) {
|
|
@@ -15497,7 +15773,9 @@ function normalizeClauses(raw) {
|
|
|
15497
15773
|
dataScope[dim] = values.map((val) => {
|
|
15498
15774
|
if (val === null) return null;
|
|
15499
15775
|
const s = String(val);
|
|
15500
|
-
|
|
15776
|
+
if (ANY_TOKEN_RE.test(s)) return ANY;
|
|
15777
|
+
if (UNDER_SELF_PLACEHOLDER_RE.test(s)) return UNDER_SELF;
|
|
15778
|
+
return SELF_PLACEHOLDER_RE.test(s) ? SELF : s;
|
|
15501
15779
|
});
|
|
15502
15780
|
if (!isRecognizedOwnershipKey(k)) unrecognizedDims.push(k);
|
|
15503
15781
|
}
|
|
@@ -15505,6 +15783,38 @@ function normalizeClauses(raw) {
|
|
|
15505
15783
|
}
|
|
15506
15784
|
return out;
|
|
15507
15785
|
}
|
|
15786
|
+
function recordTypesOf(actions) {
|
|
15787
|
+
if (actions.includes("*")) return "all";
|
|
15788
|
+
const types = /* @__PURE__ */ new Set();
|
|
15789
|
+
let sawRecordsAction = false;
|
|
15790
|
+
let sawUntyped = false;
|
|
15791
|
+
for (const action of actions) {
|
|
15792
|
+
const parts = action.split(":");
|
|
15793
|
+
if (parts[0] !== "records") continue;
|
|
15794
|
+
if (!(parts[1] ?? "").includes("r")) continue;
|
|
15795
|
+
if (parts.length >= 3) {
|
|
15796
|
+
const type = parts.slice(2).join(":");
|
|
15797
|
+
if (type === "") continue;
|
|
15798
|
+
if (type === "*") continue;
|
|
15799
|
+
if (type.endsWith(":*")) {
|
|
15800
|
+
const stripped = type.slice(0, -2);
|
|
15801
|
+
if (stripped === "" || stripped.includes(":")) continue;
|
|
15802
|
+
sawRecordsAction = true;
|
|
15803
|
+
types.add(stripped);
|
|
15804
|
+
continue;
|
|
15805
|
+
}
|
|
15806
|
+
if (type.includes(":")) continue;
|
|
15807
|
+
sawRecordsAction = true;
|
|
15808
|
+
types.add(type);
|
|
15809
|
+
} else {
|
|
15810
|
+
sawRecordsAction = true;
|
|
15811
|
+
sawUntyped = true;
|
|
15812
|
+
}
|
|
15813
|
+
}
|
|
15814
|
+
if (!sawRecordsAction) return null;
|
|
15815
|
+
if (sawUntyped) return "all";
|
|
15816
|
+
return [...types].sort();
|
|
15817
|
+
}
|
|
15508
15818
|
function buildArchetypes(clauses) {
|
|
15509
15819
|
const rows = [
|
|
15510
15820
|
{ label: "a record you own alone (private)", owners: { user: SELF } },
|
|
@@ -15515,8 +15825,13 @@ function buildArchetypes(clauses) {
|
|
|
15515
15825
|
if (c.unrecognizedDims.length > 0) continue;
|
|
15516
15826
|
for (const [dim, values] of Object.entries(c.dataScope)) {
|
|
15517
15827
|
if (dim === "user") continue;
|
|
15828
|
+
if (dim === DIM_WILDCARD) continue;
|
|
15518
15829
|
const set = scopeDims.get(dim) ?? /* @__PURE__ */ new Set();
|
|
15519
|
-
|
|
15830
|
+
set.add(SELF);
|
|
15831
|
+
for (const v of values) {
|
|
15832
|
+
if (v === null || v === ANY || v === UNDER_SELF) continue;
|
|
15833
|
+
set.add(v);
|
|
15834
|
+
}
|
|
15520
15835
|
scopeDims.set(dim, set);
|
|
15521
15836
|
}
|
|
15522
15837
|
}
|
|
@@ -15535,19 +15850,42 @@ function buildArchetypes(clauses) {
|
|
|
15535
15850
|
rows.push({ label: "no owner values at all (tenant-shared)", owners: {} });
|
|
15536
15851
|
return rows;
|
|
15537
15852
|
}
|
|
15853
|
+
function listAdmits(allowed, recordValue) {
|
|
15854
|
+
return allowed.some((a) => {
|
|
15855
|
+
if (a === null) return recordValue === void 0;
|
|
15856
|
+
if (a === SELF) return recordValue === SELF;
|
|
15857
|
+
if (a === ANY) return recordValue !== void 0;
|
|
15858
|
+
if (a === UNDER_SELF) return false;
|
|
15859
|
+
return recordValue === a;
|
|
15860
|
+
});
|
|
15861
|
+
}
|
|
15538
15862
|
function clauseMatches(clause, arch) {
|
|
15539
15863
|
if (clause.unrecognizedDims.length > 0) return false;
|
|
15864
|
+
const wildcard = clause.dataScope[DIM_WILDCARD];
|
|
15540
15865
|
for (const [dim, allowed] of Object.entries(clause.dataScope)) {
|
|
15541
|
-
|
|
15542
|
-
|
|
15543
|
-
|
|
15544
|
-
|
|
15545
|
-
|
|
15546
|
-
|
|
15547
|
-
|
|
15866
|
+
if (dim === DIM_WILDCARD) continue;
|
|
15867
|
+
if (!listAdmits(allowed, arch.owners[dim])) return false;
|
|
15868
|
+
}
|
|
15869
|
+
if (wildcard) {
|
|
15870
|
+
let carriesAny = false;
|
|
15871
|
+
for (const [dim, value] of Object.entries(arch.owners)) {
|
|
15872
|
+
carriesAny = true;
|
|
15873
|
+
if (dim in clause.dataScope) continue;
|
|
15874
|
+
if (!listAdmits(wildcard, value)) return false;
|
|
15875
|
+
}
|
|
15876
|
+
if (!carriesAny && !listAdmits(wildcard, void 0)) return false;
|
|
15548
15877
|
}
|
|
15549
15878
|
return true;
|
|
15550
15879
|
}
|
|
15880
|
+
function undecidableDims(clauses) {
|
|
15881
|
+
const dims = /* @__PURE__ */ new Set();
|
|
15882
|
+
for (const c of clauses) {
|
|
15883
|
+
for (const [dim, values] of Object.entries(c.dataScope)) {
|
|
15884
|
+
if (values.includes(UNDER_SELF)) dims.add(dim);
|
|
15885
|
+
}
|
|
15886
|
+
}
|
|
15887
|
+
return [...dims];
|
|
15888
|
+
}
|
|
15551
15889
|
function explainClauses(clauses) {
|
|
15552
15890
|
const archetypes = buildArchetypes(clauses);
|
|
15553
15891
|
return archetypes.map((arch) => {
|
|
@@ -15559,15 +15897,34 @@ function explainClauses(clauses) {
|
|
|
15559
15897
|
for (const a of c.actions) actions.add(a);
|
|
15560
15898
|
}
|
|
15561
15899
|
});
|
|
15562
|
-
|
|
15900
|
+
const recordTypes = recordTypesOf([...actions]);
|
|
15901
|
+
return {
|
|
15902
|
+
archetype: arch.label,
|
|
15903
|
+
clauses: matched,
|
|
15904
|
+
actions: [...actions],
|
|
15905
|
+
...recordTypes !== null ? { recordTypes } : {}
|
|
15906
|
+
};
|
|
15563
15907
|
});
|
|
15564
15908
|
}
|
|
15565
15909
|
function renderDataScope(ds) {
|
|
15566
15910
|
const entries = Object.entries(ds);
|
|
15567
15911
|
if (entries.length === 0) return "(unscoped \u2014 all data in the context)";
|
|
15568
|
-
return entries.map(
|
|
15569
|
-
|
|
15570
|
-
|
|
15912
|
+
return entries.map(([dim, values]) => {
|
|
15913
|
+
const shown = values.map((v) => {
|
|
15914
|
+
if (v === SELF) return "self";
|
|
15915
|
+
if (v === ANY) return "any";
|
|
15916
|
+
if (v === UNDER_SELF) return "under-self";
|
|
15917
|
+
if (v === null) return "null";
|
|
15918
|
+
return v;
|
|
15919
|
+
});
|
|
15920
|
+
const label = dim === DIM_WILDCARD ? "* (every other dimension)" : dim;
|
|
15921
|
+
return `${label}:[${shown.join(", ")}]`;
|
|
15922
|
+
}).join(" AND ");
|
|
15923
|
+
}
|
|
15924
|
+
function renderRecordTypes(recordTypes) {
|
|
15925
|
+
if (recordTypes === void 0) return "\u2014";
|
|
15926
|
+
if (recordTypes === "all") return "all types";
|
|
15927
|
+
return recordTypes.join(", ");
|
|
15571
15928
|
}
|
|
15572
15929
|
var SELF_PRINCIPAL_ALIAS2 = "me";
|
|
15573
15930
|
async function runAccessExplain(opts, deps = {}) {
|
|
@@ -15595,6 +15952,7 @@ async function runAccessExplain(opts, deps = {}) {
|
|
|
15595
15952
|
}
|
|
15596
15953
|
const clauses = normalizeClauses(rawClauses);
|
|
15597
15954
|
const rows = explainClauses(clauses);
|
|
15955
|
+
const undecidable = undecidableDims(clauses);
|
|
15598
15956
|
if (opts.json) {
|
|
15599
15957
|
process.stdout.write(
|
|
15600
15958
|
`${formatJson({
|
|
@@ -15611,7 +15969,10 @@ async function runAccessExplain(opts, deps = {}) {
|
|
|
15611
15969
|
...c.unrecognizedDims.length > 0 ? { grantsNothing: true, unrecognizedDims: c.unrecognizedDims } : {}
|
|
15612
15970
|
})),
|
|
15613
15971
|
matrix: rows,
|
|
15614
|
-
|
|
15972
|
+
// Present only when a clause uses `${{ under.self.* }}`: those rows are UNDECIDED
|
|
15973
|
+
// offline, not denied, and a consumer reading a blank cell as a denial would be wrong.
|
|
15974
|
+
...undecidable.length > 0 ? { undecidableDims: undecidable } : {},
|
|
15975
|
+
note: "This preview models READ reachability, not write authorization: a `c`/`u`/`d` verb shown granted for a row may not actually be able to create/update a record with that exact ownership shape, because the platform requires a clause to explicitly name a dimension to place a value into it, while an unmentioned dimension never narrows reads. matrix[].recordTypes is the `records:` reach of that row's grant, separate from the ownership archetype: \"all\" reaches every type, an array names the type(s), absent means no `records:` action grants this row. Search/RAG visibility is a separate, still-unmodeled narrowing bounded per row by the same clause's typed read grants. Client-side preview; the platform decides at request time."
|
|
15615
15976
|
})}
|
|
15616
15977
|
`
|
|
15617
15978
|
);
|
|
@@ -15639,8 +16000,19 @@ async function runAccessExplain(opts, deps = {}) {
|
|
|
15639
16000
|
"\n" + formatTable(rows, [
|
|
15640
16001
|
{ header: "record owned by\u2026", get: (r) => r.archetype },
|
|
15641
16002
|
{ header: "granted?", get: (r) => r.clauses.length ? `via clause ${r.clauses.join(", ")}` : "NO" },
|
|
15642
|
-
{ header: "actions", get: (r) => r.actions.length ? r.actions.join(", ") : "\u2014" }
|
|
15643
|
-
|
|
16003
|
+
{ header: "actions", get: (r) => r.actions.length ? r.actions.join(", ") : "\u2014" },
|
|
16004
|
+
// The ownership "granted?" cell says nothing about WHICH record types — a clause can be
|
|
16005
|
+
// ownership-unscoped and still type-restricted (a curated shared-KB clause with no owner
|
|
16006
|
+
// dimension, readable only for a few named types, never others). Surface the actual
|
|
16007
|
+
// `records:` reach per row instead of leaving it to a footnote.
|
|
16008
|
+
{ header: "record types", get: (r) => renderRecordTypes(r.recordTypes) }
|
|
16009
|
+
]) + (undecidable.length > 0 ? `
|
|
16010
|
+
|
|
16011
|
+
\u26A0 UNDECIDED offline: ${undecidable.join(", ")}
|
|
16012
|
+
A clause matching on \`\${{ under.self.* }}\` needs the parent graph, which only the server has,
|
|
16013
|
+
so rows granted through it are shown as NO above. Read those cells as "cannot tell here",
|
|
16014
|
+
NOT as a denial \u2014 the matrix under-reports this clause.
|
|
16015
|
+
` : "") + '\n\n \u26A0 READ ONLY: "granted?"/"actions" model whether a row can be READ, not whether a c/u/d\n verb shown there can actually WRITE that row\'s exact shape \u2014 the platform requires a clause\n to name a dimension to place a value into it, while reads are never narrowed by a dimension a\n clause omits. A create/update verb next to a row is advisory: it names which records exist to\n act on, not that a specific write will succeed.\n\n\n NOTE: this matrix models the OWNERSHIP dimensions only \u2014 "granted?" says a row\'s ownership\n shape matches a clause, and "record types" (above) says which `records:` types that grant\n actually reaches; "all" reaches every type, "\u2014" means no `records:` action grants this row at\n all. Search/RAG visibility is a separate, still-unmodeled narrowing: it is bounded per row by\n the SAME clause\'s typed read grants \u2014 a row shown with `search:r` is searchable only for the\n content types that clause can read.\n Preview only \u2014 computed client-side from the documented matching rules; the platform decides at request time.\n'
|
|
15644
16016
|
);
|
|
15645
16017
|
return 0;
|
|
15646
16018
|
} catch (err) {
|
|
@@ -18379,9 +18751,9 @@ function emitJoined(alias, context, principalId, role, keyId, reused) {
|
|
|
18379
18751
|
|
|
18380
18752
|
// src/build-info.ts
|
|
18381
18753
|
var BUILD_INFO = {
|
|
18382
|
-
cli: true ? "0.
|
|
18383
|
-
sdk: true ? "0.
|
|
18384
|
-
blueprints: true ? "0.
|
|
18754
|
+
cli: true ? "0.13.0" : "dev",
|
|
18755
|
+
sdk: true ? "0.38.0" : "dev",
|
|
18756
|
+
blueprints: true ? "0.10.0" : "dev"
|
|
18385
18757
|
};
|
|
18386
18758
|
function formatBuildInfo() {
|
|
18387
18759
|
return `vectros ${BUILD_INFO.cli} (sdk ${BUILD_INFO.sdk}, blueprints ${BUILD_INFO.blueprints})`;
|