@stigmer/server 3.27.1 → 3.28.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/README.md +2 -0
- package/dist/authorization/authorizer.d.ts +7 -5
- package/dist/authorization/authorizer.d.ts.map +1 -1
- package/dist/authorization/authorizer.js +7 -5
- package/dist/authorization/authorizer.js.map +1 -1
- package/dist/authorization/derived-tuples.d.ts +3 -1
- package/dist/authorization/derived-tuples.d.ts.map +1 -1
- package/dist/authorization/derived-tuples.js +52 -2
- package/dist/authorization/derived-tuples.js.map +1 -1
- package/dist/authorization/evaluator.d.ts +9 -10
- package/dist/authorization/evaluator.d.ts.map +1 -1
- package/dist/authorization/evaluator.js.map +1 -1
- package/dist/authorization/list-read-scope.d.ts.map +1 -1
- package/dist/authorization/list-read-scope.js +29 -18
- package/dist/authorization/list-read-scope.js.map +1 -1
- package/dist/authorization/model/bindings.d.ts +41 -0
- package/dist/authorization/model/bindings.d.ts.map +1 -0
- package/dist/authorization/model/bindings.js +89 -0
- package/dist/authorization/model/bindings.js.map +1 -0
- package/dist/authorization/model/data/authorization-model.json +3114 -0
- package/dist/authorization/model/default-of.d.ts +2 -2
- package/dist/authorization/model/execution-viewer.d.ts +3 -0
- package/dist/authorization/model/execution-viewer.d.ts.map +1 -0
- package/dist/authorization/model/execution-viewer.js +50 -0
- package/dist/authorization/model/execution-viewer.js.map +1 -0
- package/dist/authorization/model/index.d.ts +33 -21
- package/dist/authorization/model/index.d.ts.map +1 -1
- package/dist/authorization/model/index.js +74 -52
- package/dist/authorization/model/index.js.map +1 -1
- package/dist/authorization/model/openfga-json.d.ts +18 -0
- package/dist/authorization/model/openfga-json.d.ts.map +1 -0
- package/dist/authorization/model/openfga-json.js +266 -0
- package/dist/authorization/model/openfga-json.js.map +1 -0
- package/dist/authorization/model/rewrite.d.ts +39 -67
- package/dist/authorization/model/rewrite.d.ts.map +1 -1
- package/dist/authorization/model/rewrite.js +1 -117
- package/dist/authorization/model/rewrite.js.map +1 -1
- package/dist/authorization/strict-json.d.ts +20 -0
- package/dist/authorization/strict-json.d.ts.map +1 -0
- package/dist/authorization/strict-json.js +45 -0
- package/dist/authorization/strict-json.js.map +1 -0
- package/dist/boot/compose.d.ts.map +1 -1
- package/dist/boot/compose.js +9 -7
- package/dist/boot/compose.js.map +1 -1
- package/dist/boot/list-indexes.d.ts.map +1 -1
- package/dist/boot/list-indexes.js +6 -0
- package/dist/boot/list-indexes.js.map +1 -1
- package/dist/domain/iampolicy/list-index.d.ts +2 -0
- package/dist/domain/iampolicy/list-index.d.ts.map +1 -0
- package/dist/domain/iampolicy/list-index.js +32 -0
- package/dist/domain/iampolicy/list-index.js.map +1 -0
- package/dist/domain/iampolicy/resource-store.d.ts.map +1 -1
- package/dist/domain/iampolicy/resource-store.js +24 -11
- package/dist/domain/iampolicy/resource-store.js.map +1 -1
- package/dist/domain/workflowexecution/list-index.d.ts +1 -1
- package/dist/extensions/list-read-scope.d.ts +20 -3
- package/dist/extensions/list-read-scope.d.ts.map +1 -1
- package/dist/extensions/list-read-scope.js +22 -2
- package/dist/extensions/list-read-scope.js.map +1 -1
- package/dist/index.d.ts +8 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -5
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
- package/src/authorization/README.md +48 -39
- package/src/authorization/__tests__/authorizer.test.ts +5 -14
- package/src/authorization/__tests__/derived-tuples.test.ts +192 -17
- package/src/authorization/__tests__/evaluator.test.ts +53 -160
- package/src/authorization/__tests__/facts.test.ts +5 -11
- package/src/authorization/__tests__/list-read-scope.measure.test.ts +424 -62
- package/src/authorization/__tests__/list-read-scope.test.ts +15 -14
- package/src/authorization/__tests__/organization-directory.test.ts +2 -2
- package/src/authorization/__tests__/store-test-kit.ts +246 -0
- package/src/authorization/__tests__/store-tests.test.ts +91 -223
- package/src/authorization/__tests__/support.ts +30 -2
- package/src/authorization/__tests__/throwaway-model.ts +75 -0
- package/src/authorization/__tests__/wire-permissions.test.ts +141 -107
- package/src/authorization/authorizer.ts +7 -5
- package/src/authorization/derived-tuples.ts +105 -16
- package/src/authorization/evaluator.ts +8 -10
- package/src/authorization/list-read-scope.ts +37 -22
- package/src/authorization/model/__tests__/openfga-json.test.ts +353 -0
- package/src/authorization/model/__tests__/registry.test.ts +102 -276
- package/src/authorization/model/bindings.ts +131 -0
- package/src/authorization/model/data/authorization-model.json +3114 -0
- package/src/authorization/model/default-of.ts +2 -2
- package/src/authorization/model/execution-viewer.ts +56 -0
- package/src/authorization/model/index.ts +81 -72
- package/src/authorization/model/openfga-json.ts +310 -0
- package/src/authorization/model/rewrite.ts +39 -188
- package/src/authorization/strict-json.ts +52 -0
- package/src/boot/__tests__/list-indexes.test.ts +4 -0
- package/src/boot/compose.ts +9 -7
- package/src/boot/list-indexes.ts +6 -0
- package/src/domain/iampolicy/__tests__/resource-store.test.ts +65 -5
- package/src/domain/iampolicy/list-index.ts +33 -0
- package/src/domain/iampolicy/resource-store.ts +24 -11
- package/src/extensions/__tests__/built-in-authorization-composed.test.ts +3 -6
- package/src/extensions/list-read-scope.ts +32 -4
- package/src/index.ts +20 -44
- package/dist/authorization/model/agent.d.ts +0 -2
- package/dist/authorization/model/agent.d.ts.map +0 -1
- package/dist/authorization/model/agent.js +0 -43
- package/dist/authorization/model/agent.js.map +0 -1
- package/dist/authorization/model/agent_channel.d.ts +0 -2
- package/dist/authorization/model/agent_channel.d.ts.map +0 -1
- package/dist/authorization/model/agent_channel.js +0 -39
- package/dist/authorization/model/agent_channel.js.map +0 -1
- package/dist/authorization/model/agent_execution.d.ts +0 -2
- package/dist/authorization/model/agent_execution.d.ts.map +0 -1
- package/dist/authorization/model/agent_execution.js +0 -25
- package/dist/authorization/model/agent_execution.js.map +0 -1
- package/dist/authorization/model/agent_instance.d.ts +0 -2
- package/dist/authorization/model/agent_instance.d.ts.map +0 -1
- package/dist/authorization/model/agent_instance.js +0 -48
- package/dist/authorization/model/agent_instance.js.map +0 -1
- package/dist/authorization/model/agent_share.d.ts +0 -2
- package/dist/authorization/model/agent_share.d.ts.map +0 -1
- package/dist/authorization/model/agent_share.js +0 -29
- package/dist/authorization/model/agent_share.js.map +0 -1
- package/dist/authorization/model/api_key.d.ts +0 -2
- package/dist/authorization/model/api_key.d.ts.map +0 -1
- package/dist/authorization/model/api_key.js +0 -27
- package/dist/authorization/model/api_key.js.map +0 -1
- package/dist/authorization/model/artifact.d.ts +0 -2
- package/dist/authorization/model/artifact.d.ts.map +0 -1
- package/dist/authorization/model/artifact.js +0 -27
- package/dist/authorization/model/artifact.js.map +0 -1
- package/dist/authorization/model/channel_app.d.ts +0 -2
- package/dist/authorization/model/channel_app.d.ts.map +0 -1
- package/dist/authorization/model/channel_app.js +0 -28
- package/dist/authorization/model/channel_app.js.map +0 -1
- package/dist/authorization/model/environment.d.ts +0 -2
- package/dist/authorization/model/environment.d.ts.map +0 -1
- package/dist/authorization/model/environment.js +0 -33
- package/dist/authorization/model/environment.js.map +0 -1
- package/dist/authorization/model/execution_context.d.ts +0 -2
- package/dist/authorization/model/execution_context.d.ts.map +0 -1
- package/dist/authorization/model/execution_context.js +0 -19
- package/dist/authorization/model/execution_context.js.map +0 -1
- package/dist/authorization/model/iam_policy.d.ts +0 -2
- package/dist/authorization/model/iam_policy.d.ts.map +0 -1
- package/dist/authorization/model/iam_policy.js +0 -31
- package/dist/authorization/model/iam_policy.js.map +0 -1
- package/dist/authorization/model/identity_account.d.ts +0 -2
- package/dist/authorization/model/identity_account.d.ts.map +0 -1
- package/dist/authorization/model/identity_account.js +0 -25
- package/dist/authorization/model/identity_account.js.map +0 -1
- package/dist/authorization/model/mcp_server.d.ts +0 -2
- package/dist/authorization/model/mcp_server.d.ts.map +0 -1
- package/dist/authorization/model/mcp_server.js +0 -38
- package/dist/authorization/model/mcp_server.js.map +0 -1
- package/dist/authorization/model/memory.d.ts +0 -2
- package/dist/authorization/model/memory.d.ts.map +0 -1
- package/dist/authorization/model/memory.js +0 -26
- package/dist/authorization/model/memory.js.map +0 -1
- package/dist/authorization/model/oauth_app.d.ts +0 -2
- package/dist/authorization/model/oauth_app.d.ts.map +0 -1
- package/dist/authorization/model/oauth_app.js +0 -31
- package/dist/authorization/model/oauth_app.js.map +0 -1
- package/dist/authorization/model/organization.d.ts +0 -2
- package/dist/authorization/model/organization.d.ts.map +0 -1
- package/dist/authorization/model/organization.js +0 -58
- package/dist/authorization/model/organization.js.map +0 -1
- package/dist/authorization/model/platform_client.d.ts +0 -2
- package/dist/authorization/model/platform_client.d.ts.map +0 -1
- package/dist/authorization/model/platform_client.js +0 -31
- package/dist/authorization/model/platform_client.js.map +0 -1
- package/dist/authorization/model/plugin.d.ts +0 -2
- package/dist/authorization/model/plugin.d.ts.map +0 -1
- package/dist/authorization/model/plugin.js +0 -45
- package/dist/authorization/model/plugin.js.map +0 -1
- package/dist/authorization/model/schedule.d.ts +0 -2
- package/dist/authorization/model/schedule.d.ts.map +0 -1
- package/dist/authorization/model/schedule.js +0 -34
- package/dist/authorization/model/schedule.js.map +0 -1
- package/dist/authorization/model/session.d.ts +0 -2
- package/dist/authorization/model/session.d.ts.map +0 -1
- package/dist/authorization/model/session.js +0 -38
- package/dist/authorization/model/session.js.map +0 -1
- package/dist/authorization/model/skill.d.ts +0 -2
- package/dist/authorization/model/skill.d.ts.map +0 -1
- package/dist/authorization/model/skill.js +0 -39
- package/dist/authorization/model/skill.js.map +0 -1
- package/dist/authorization/model/workflow.d.ts +0 -2
- package/dist/authorization/model/workflow.d.ts.map +0 -1
- package/dist/authorization/model/workflow.js +0 -39
- package/dist/authorization/model/workflow.js.map +0 -1
- package/dist/authorization/model/workflow_execution.d.ts +0 -2
- package/dist/authorization/model/workflow_execution.d.ts.map +0 -1
- package/dist/authorization/model/workflow_execution.js +0 -33
- package/dist/authorization/model/workflow_execution.js.map +0 -1
- package/dist/authorization/model/workflow_instance.d.ts +0 -2
- package/dist/authorization/model/workflow_instance.d.ts.map +0 -1
- package/dist/authorization/model/workflow_instance.js +0 -96
- package/dist/authorization/model/workflow_instance.js.map +0 -1
- package/dist/authorization/store-test-kit.d.ts +0 -62
- package/dist/authorization/store-test-kit.d.ts.map +0 -1
- package/dist/authorization/store-test-kit.js +0 -265
- package/dist/authorization/store-test-kit.js.map +0 -1
- package/src/authorization/__tests__/fixtures/fga/agent-channel-owner.fga.yaml +0 -112
- package/src/authorization/__tests__/fixtures/fga/agent-instance-creation.fga.yaml +0 -177
- package/src/authorization/__tests__/fixtures/fga/agent-share-owner.fga.yaml +0 -109
- package/src/authorization/__tests__/fixtures/fga/artifact-org-and-owner.fga.yaml +0 -128
- package/src/authorization/__tests__/fixtures/fga/blueprint-private-visibility.fga.yaml +0 -318
- package/src/authorization/__tests__/fixtures/fga/channel-app-owner.fga.yaml +0 -125
- package/src/authorization/__tests__/fixtures/fga/channel-participation.fga.yaml +0 -201
- package/src/authorization/__tests__/fixtures/fga/channel-session-visibility.fga.yaml +0 -187
- package/src/authorization/__tests__/fixtures/fga/default-instance-inheritance.fga.yaml +0 -214
- package/src/authorization/__tests__/fixtures/fga/mcp-server-authoring.fga.yaml +0 -129
- package/src/authorization/__tests__/fixtures/fga/memory-subject-only.fga.yaml +0 -72
- package/src/authorization/__tests__/fixtures/fga/org-admin-owner-inheritance.fga.yaml +0 -308
- package/src/authorization/__tests__/fixtures/fga/org-shared-environment.fga.yaml +0 -144
- package/src/authorization/__tests__/fixtures/fga/platform-visibility.fga.yaml +0 -199
- package/src/authorization/__tests__/fixtures/fga/provider-standing-viewer.fga.yaml +0 -40
- package/src/authorization/__tests__/fixtures/fga/reserved-label-writer.fga.yaml +0 -41
- package/src/authorization/__tests__/fixtures/fga/schedule-owner.fga.yaml +0 -113
- package/src/authorization/__tests__/fixtures/fga/schedule-session-visibility.fga.yaml +0 -214
- package/src/authorization/__tests__/fixtures/fga/session-personal-resource.fga.yaml +0 -117
- package/src/authorization/__tests__/fixtures/fga/workflow-execution-sharing.fga.yaml +0 -190
- package/src/authorization/model/agent.ts +0 -64
- package/src/authorization/model/agent_channel.ts +0 -57
- package/src/authorization/model/agent_execution.ts +0 -33
- package/src/authorization/model/agent_instance.ts +0 -66
- package/src/authorization/model/agent_share.ts +0 -40
- package/src/authorization/model/api_key.ts +0 -28
- package/src/authorization/model/artifact.ts +0 -38
- package/src/authorization/model/channel_app.ts +0 -39
- package/src/authorization/model/environment.ts +0 -48
- package/src/authorization/model/execution_context.ts +0 -20
- package/src/authorization/model/iam_policy.ts +0 -42
- package/src/authorization/model/identity_account.ts +0 -26
- package/src/authorization/model/mcp_server.ts +0 -59
- package/src/authorization/model/memory.ts +0 -27
- package/src/authorization/model/oauth_app.ts +0 -43
- package/src/authorization/model/organization.ts +0 -59
- package/src/authorization/model/platform_client.ts +0 -43
- package/src/authorization/model/plugin.ts +0 -66
- package/src/authorization/model/schedule.ts +0 -49
- package/src/authorization/model/session.ts +0 -50
- package/src/authorization/model/skill.ts +0 -60
- package/src/authorization/model/workflow.ts +0 -60
- package/src/authorization/model/workflow_execution.ts +0 -46
- package/src/authorization/model/workflow_instance.ts +0 -125
- package/src/authorization/store-test-kit.ts +0 -395
|
@@ -1,21 +1,55 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
3
|
-
* measurement, not an assumption, decides whether a driver needs
|
|
2
|
+
* The built-in evaluator's cost, measured — the plan's storage-impact line
|
|
3
|
+
* ("a measurement, not an assumption, decides whether a driver needs
|
|
4
4
|
* anything"). Gated on `AUTHORIZATION_MEASURE=1` and SKIPPED otherwise
|
|
5
5
|
* (the `TEST_DATABASE_URL` idiom: an environment variable read once,
|
|
6
6
|
* `describe.skipIf`), because ten thousand rows on Postgres take a while
|
|
7
|
-
* and the
|
|
8
|
-
*
|
|
9
|
-
* different engine's
|
|
7
|
+
* and the numbers are recorded in a project log, not asserted: timing on
|
|
8
|
+
* a shared machine is noise in a unit suite. No budget is ruled for open
|
|
9
|
+
* source, and the cloud's 300 ms is a different engine's; an edition that
|
|
10
|
+
* serves teams over this evaluator holds a page of a paged list to 300 ms
|
|
11
|
+
* and a point check to 50 ms, read from these lines, and records the
|
|
12
|
+
* single 10,000-candidate call (a request that asks for every row, the
|
|
13
|
+
* enumeration lanes) as its worst case.
|
|
10
14
|
*
|
|
11
|
-
*
|
|
12
|
-
* the
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
* Two axes. The CANDIDATE count (1k and 10k rows of the listed kind) and
|
|
16
|
+
* the POLICY TABLE every check and every list batch reads a person's rows
|
|
17
|
+
* from through the IamPolicy port (indexed by principal on the OSS
|
|
18
|
+
* adapter; resource-store.ts).
|
|
19
|
+
* Every shape runs at an Enterprise-sized population: one organization of
|
|
20
|
+
* 1,000 members in 50 teams of 20, teams granted `viewer` on half the
|
|
21
|
+
* candidate agents, one person granted directly on 2% of them, and other
|
|
22
|
+
* members' direct grants filling the table to the candidate count (10,000
|
|
23
|
+
* rows at 10k; the base population alone is past 1k).
|
|
24
|
+
*
|
|
25
|
+
* The shapes, each timed once after the seed, both drivers:
|
|
26
|
+
* - the five open-source shapes: the restrict verb over a pure
|
|
27
|
+
* owner-only kind (session), over a kind with one userset hop
|
|
28
|
+
* (org-visible agents for a member), over the parent hop at two
|
|
29
|
+
* densities (executions in ten sessions, and in a thousand), over the
|
|
30
|
+
* kind whose `derived` rule reads the row (instances of one agent);
|
|
31
|
+
* the enumeration verb over the blueprint kind (the console library's
|
|
32
|
+
* path);
|
|
33
|
+
* - five Enterprise shapes over private agents: a team member's grants
|
|
34
|
+
* (the team hop through the model's organization bound); the same for
|
|
35
|
+
* a person in five teams (one more policy read per team); a person's
|
|
36
|
+
* direct grants; a former member who keeps the team's row and is
|
|
37
|
+
* admitted by none of it (the bound's correctness at volume); and the
|
|
38
|
+
* enumeration verb for the team member;
|
|
39
|
+
* - pages, as the paged lanes run them (pipeline/steps/list-page.ts): a
|
|
40
|
+
* page that fills in one batch (one scope call of 100 candidates) and
|
|
41
|
+
* a caller's worst page (five calls of 100, the examine budget of 500,
|
|
42
|
+
* each call its own source as `admit` makes it), for the team member,
|
|
43
|
+
* the direct grantee and an organization member, at the population of
|
|
44
|
+
* 10,000 policy rows;
|
|
45
|
+
* - point checks: one `can_view` through a team grant and one through a
|
|
46
|
+
* direct grant, each over 100 checks with a fresh source per check
|
|
47
|
+
* (the Authorizer's own shape), the mean reported.
|
|
48
|
+
*
|
|
49
|
+
* Every shape states what its caller keeps, so a measurement over an
|
|
50
|
+
* evaluator that answered wrongly measures nothing. The model is the
|
|
51
|
+
* built-in one, which declares the Enterprise team type beside the
|
|
52
|
+
* open-source ones.
|
|
19
53
|
*/
|
|
20
54
|
import { create } from "@bufbuild/protobuf";
|
|
21
55
|
import { afterAll, afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
@@ -23,8 +57,11 @@ import { afterAll, afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
|
23
57
|
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
24
58
|
import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
|
|
25
59
|
import { IamPolicySchema } from "@stigmer/protos/ai/stigmer/iam/iampolicy/v1/api_pb";
|
|
60
|
+
import type { IamPolicySpec } from "@stigmer/protos/ai/stigmer/iam/iampolicy/v1/spec_pb";
|
|
26
61
|
import { IdentityAccountSchema } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/api_pb";
|
|
27
62
|
import { IdentityAccountProvisioningMode } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/enum_pb";
|
|
63
|
+
import { IamPermission } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
|
|
64
|
+
import { ServerEdition } from "@stigmer/protos/ai/stigmer/platform/v1/server_info_pb";
|
|
28
65
|
|
|
29
66
|
import {
|
|
30
67
|
IAM_POLICY_API_VERSION,
|
|
@@ -33,7 +70,7 @@ import {
|
|
|
33
70
|
} from "../../domain/iampolicy/constants.js";
|
|
34
71
|
import { newResourceIamPolicyStore } from "../../domain/iampolicy/resource-store.js";
|
|
35
72
|
import type { IamPolicyStore } from "../../domain/iampolicy/store.js";
|
|
36
|
-
import { orgRole } from "../../domain/iampolicy/__tests__/support.js";
|
|
73
|
+
import { orgRole, triple } from "../../domain/iampolicy/__tests__/support.js";
|
|
37
74
|
import { accountIdFor } from "../../domain/identityaccount/constants.js";
|
|
38
75
|
import { newResourceIdentityAccountStore } from "../../domain/identityaccount/resource-store.js";
|
|
39
76
|
import type { IdentityAccountStore } from "../../domain/identityaccount/store.js";
|
|
@@ -42,12 +79,13 @@ import type { CallerIdentity } from "../../extensions/identity.js";
|
|
|
42
79
|
import type { ListEntryMeta } from "../../extensions/list-read-scope.js";
|
|
43
80
|
import { kindEnumName } from "../../pipeline/apiresource-meta.js";
|
|
44
81
|
import { rowAuthorizationFactsOf } from "../../pipeline/steps/authorization-facts.js";
|
|
82
|
+
import { newBuiltInAuthorizer } from "../authorizer.js";
|
|
45
83
|
import { newBuiltInListReadScope } from "../list-read-scope.js";
|
|
46
|
-
import { builtInModel } from "../model/index.js";
|
|
47
84
|
import type { KindDeclaration } from "../model/rewrite.js";
|
|
48
85
|
import { driverFixtures, dropPostgresFixture } from "./drivers.js";
|
|
49
86
|
import type { OpenedStore } from "./drivers.js";
|
|
50
|
-
import {
|
|
87
|
+
import { builtInModel } from "../model/index.js";
|
|
88
|
+
import { fixtureRow, storedDeclaration } from "./support.js";
|
|
51
89
|
import type { FixtureRowFacts } from "./support.js";
|
|
52
90
|
|
|
53
91
|
const MEASURE = process.env["AUTHORIZATION_MEASURE"] === "1";
|
|
@@ -56,14 +94,53 @@ const SIZES = [1_000, 10_000];
|
|
|
56
94
|
const FOUNDER = accountIdFor("auth0|founder");
|
|
57
95
|
const MEMBER = accountIdFor("auth0|member");
|
|
58
96
|
const VIEWER = accountIdFor("auth0|viewer");
|
|
97
|
+
/** In team 0. */
|
|
98
|
+
const TEAM_MEMBER = accountIdFor("auth0|team-member");
|
|
99
|
+
/** In teams 0 to 4. */
|
|
100
|
+
const MULTI_TEAM = accountIdFor("auth0|multi-team");
|
|
101
|
+
/** Granted `viewer` directly on 2% of the candidate agents. */
|
|
102
|
+
const DIRECT = accountIdFor("auth0|direct");
|
|
103
|
+
/** Still holds team 0's `member` row; holds no organization role. */
|
|
104
|
+
const FORMER = accountIdFor("auth0|former");
|
|
59
105
|
const ORG = "acme";
|
|
60
106
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
107
|
+
const TEAMS = 50;
|
|
108
|
+
const TEAM_SIZE = 20;
|
|
109
|
+
const MEMBERS = TEAMS * TEAM_SIZE;
|
|
110
|
+
const MULTI_TEAM_COUNT = 5;
|
|
111
|
+
const POINT_CHECKS = 100;
|
|
112
|
+
/** The page loop's largest page and its examine budget (pipeline/steps/list-page.ts). */
|
|
113
|
+
const PAGE_SIZE = 100;
|
|
114
|
+
const PAGE_EXAMINE_BUDGET = 500;
|
|
115
|
+
/** The population the pages run at: a 10,000-row policy table. */
|
|
116
|
+
const PAGE_POPULATION = 10_000;
|
|
117
|
+
/**
|
|
118
|
+
* A case seeds up to twenty thousand rows one write at a time before its
|
|
119
|
+
* timer starts; the suite's 15 s budget is for tests, not for this seed.
|
|
120
|
+
*/
|
|
121
|
+
const MEASURE_TIMEOUT_MS = 600_000;
|
|
122
|
+
|
|
123
|
+
function teamId(i: number): string {
|
|
124
|
+
return `tm_${i}`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function populationMemberId(i: number): string {
|
|
128
|
+
return `ida_pop_${i}`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** The candidate agent of index `i`: private, the founder's. */
|
|
132
|
+
function enterpriseAgentId(i: number): string {
|
|
133
|
+
return `agt_e_${i}`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** The team granted `viewer` on agent `i`, or undefined: every even agent, round-robin over the teams. */
|
|
137
|
+
function teamGrantedOn(i: number): number | undefined {
|
|
138
|
+
return i % 2 === 0 ? i % TEAMS : undefined;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Whether `DIRECT` is granted `viewer` on agent `i`: 2% of the agents. */
|
|
142
|
+
function directGrantOn(i: number): boolean {
|
|
143
|
+
return i % 50 === 1;
|
|
67
144
|
}
|
|
68
145
|
|
|
69
146
|
function resolved(accountId: string): CallerIdentity {
|
|
@@ -97,6 +174,37 @@ function sessionRow(i: number): FixtureRowFacts {
|
|
|
97
174
|
};
|
|
98
175
|
}
|
|
99
176
|
|
|
177
|
+
function enterpriseAgentRow(i: number): FixtureRowFacts {
|
|
178
|
+
return {
|
|
179
|
+
id: enterpriseAgentId(i),
|
|
180
|
+
org: ORG,
|
|
181
|
+
visibility: ApiResourceVisibility.visibility_private,
|
|
182
|
+
createdBy: FOUNDER,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** How many of `n` agents a person in teams `0..teams-1` views through team grants. */
|
|
187
|
+
function keptThroughTeams(n: number, teams: number): number {
|
|
188
|
+
let kept = 0;
|
|
189
|
+
for (let i = 0; i < n; i += 1) {
|
|
190
|
+
const team = teamGrantedOn(i);
|
|
191
|
+
if (team !== undefined && team < teams) {
|
|
192
|
+
kept += 1;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return kept;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function keptDirectly(n: number): number {
|
|
199
|
+
let kept = 0;
|
|
200
|
+
for (let i = 0; i < n; i += 1) {
|
|
201
|
+
if (directGrantOn(i)) {
|
|
202
|
+
kept += 1;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return kept;
|
|
206
|
+
}
|
|
207
|
+
|
|
100
208
|
const SHAPES: ReadonlyArray<Shape> = [
|
|
101
209
|
{
|
|
102
210
|
name: "restrict: sessions, owner-only, the founder's own",
|
|
@@ -191,8 +299,138 @@ const SHAPES: ReadonlyArray<Shape> = [
|
|
|
191
299
|
// other; the cost measured is the rule's read of every candidate row.
|
|
192
300
|
kept: () => 1,
|
|
193
301
|
},
|
|
302
|
+
{
|
|
303
|
+
name: "restrict: private agents shared with the caller's team (the team hop through the organization bound)",
|
|
304
|
+
kind: "agent",
|
|
305
|
+
caller: TEAM_MEMBER,
|
|
306
|
+
verb: "restrict",
|
|
307
|
+
row: enterpriseAgentRow,
|
|
308
|
+
kept: (n) => keptThroughTeams(n, 1),
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
name: "restrict: private agents shared with any of the caller's five teams (one policy read more per team)",
|
|
312
|
+
kind: "agent",
|
|
313
|
+
caller: MULTI_TEAM,
|
|
314
|
+
verb: "restrict",
|
|
315
|
+
row: enterpriseAgentRow,
|
|
316
|
+
kept: (n) => keptThroughTeams(n, MULTI_TEAM_COUNT),
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
name: "restrict: private agents granted to the caller directly (a per-resource grant)",
|
|
320
|
+
kind: "agent",
|
|
321
|
+
caller: DIRECT,
|
|
322
|
+
verb: "restrict",
|
|
323
|
+
row: enterpriseAgentRow,
|
|
324
|
+
kept: keptDirectly,
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
name: "restrict: a former member keeps the team's row and is admitted by none of it",
|
|
328
|
+
kind: "agent",
|
|
329
|
+
caller: FORMER,
|
|
330
|
+
verb: "restrict",
|
|
331
|
+
row: enterpriseAgentRow,
|
|
332
|
+
kept: () => 0,
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
name: "enumerate: private agents for the team member (the library's path)",
|
|
336
|
+
kind: "agent",
|
|
337
|
+
caller: TEAM_MEMBER,
|
|
338
|
+
verb: "enumerate",
|
|
339
|
+
row: enterpriseAgentRow,
|
|
340
|
+
kept: (n) => keptThroughTeams(n, 1),
|
|
341
|
+
},
|
|
194
342
|
];
|
|
195
343
|
|
|
344
|
+
interface PageShape {
|
|
345
|
+
readonly name: string;
|
|
346
|
+
readonly caller: string;
|
|
347
|
+
/** The candidate of index `i`, an agent. */
|
|
348
|
+
row(i: number): FixtureRowFacts;
|
|
349
|
+
/** How many of the first `n` candidates the caller keeps. */
|
|
350
|
+
kept(n: number): number;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const PAGE_SHAPES: ReadonlyArray<PageShape> = [
|
|
354
|
+
{
|
|
355
|
+
name: "the team member's private agents shared with their team",
|
|
356
|
+
caller: TEAM_MEMBER,
|
|
357
|
+
row: enterpriseAgentRow,
|
|
358
|
+
kept: (n) => keptThroughTeams(n, 1),
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
name: "the direct grantee's private agents granted to them",
|
|
362
|
+
caller: DIRECT,
|
|
363
|
+
row: enterpriseAgentRow,
|
|
364
|
+
kept: keptDirectly,
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
name: "an organization member's org-visible agents",
|
|
368
|
+
caller: MEMBER,
|
|
369
|
+
row: (i) => ({
|
|
370
|
+
id: `agt_${i}`,
|
|
371
|
+
org: ORG,
|
|
372
|
+
visibility: ApiResourceVisibility.visibility_org,
|
|
373
|
+
createdBy: FOUNDER,
|
|
374
|
+
}),
|
|
375
|
+
kept: (n) => n,
|
|
376
|
+
},
|
|
377
|
+
];
|
|
378
|
+
|
|
379
|
+
/** The Enterprise population's IamPolicy rows at candidate size `n`. */
|
|
380
|
+
function populationPolicies(n: number): IamPolicySpec[] {
|
|
381
|
+
const specs: IamPolicySpec[] = [
|
|
382
|
+
orgRole(FOUNDER, "owner", ORG),
|
|
383
|
+
orgRole(MEMBER, "member", ORG),
|
|
384
|
+
orgRole(VIEWER, "viewer", ORG),
|
|
385
|
+
orgRole(TEAM_MEMBER, "member", ORG),
|
|
386
|
+
orgRole(MULTI_TEAM, "member", ORG),
|
|
387
|
+
orgRole(DIRECT, "member", ORG),
|
|
388
|
+
];
|
|
389
|
+
const membership = (accountId: string, team: number): IamPolicySpec =>
|
|
390
|
+
triple({ kind: "identity_account", id: accountId }, "member", {
|
|
391
|
+
kind: "team",
|
|
392
|
+
id: teamId(team),
|
|
393
|
+
});
|
|
394
|
+
specs.push(membership(TEAM_MEMBER, 0), membership(FORMER, 0));
|
|
395
|
+
for (let team = 0; team < MULTI_TEAM_COUNT; team += 1) {
|
|
396
|
+
specs.push(membership(MULTI_TEAM, team));
|
|
397
|
+
}
|
|
398
|
+
for (let i = 0; i < MEMBERS; i += 1) {
|
|
399
|
+
specs.push(orgRole(populationMemberId(i), "member", ORG));
|
|
400
|
+
specs.push(membership(populationMemberId(i), i % TEAMS));
|
|
401
|
+
}
|
|
402
|
+
const viewerOn = (
|
|
403
|
+
principal: { kind: string; id: string; relation?: string },
|
|
404
|
+
i: number,
|
|
405
|
+
): IamPolicySpec =>
|
|
406
|
+
triple(principal, "viewer", { kind: "agent", id: enterpriseAgentId(i) });
|
|
407
|
+
const unclaimed: number[] = [];
|
|
408
|
+
for (let i = 0; i < n; i += 1) {
|
|
409
|
+
const team = teamGrantedOn(i);
|
|
410
|
+
if (team !== undefined) {
|
|
411
|
+
specs.push(
|
|
412
|
+
viewerOn({ kind: "team", id: teamId(team), relation: "member" }, i),
|
|
413
|
+
);
|
|
414
|
+
} else if (directGrantOn(i)) {
|
|
415
|
+
specs.push(viewerOn({ kind: "identity_account", id: DIRECT }, i));
|
|
416
|
+
} else {
|
|
417
|
+
unclaimed.push(i);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
// Other members' direct grants fill the table to the candidate count.
|
|
421
|
+
const filler = Math.min(unclaimed.length, Math.max(0, n - specs.length));
|
|
422
|
+
for (let k = 0; k < filler; k += 1) {
|
|
423
|
+
const i = unclaimed[k] ?? 0;
|
|
424
|
+
specs.push(
|
|
425
|
+
viewerOn(
|
|
426
|
+
{ kind: "identity_account", id: populationMemberId(i % MEMBERS) },
|
|
427
|
+
i,
|
|
428
|
+
),
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
return specs;
|
|
432
|
+
}
|
|
433
|
+
|
|
196
434
|
afterAll(dropPostgresFixture);
|
|
197
435
|
|
|
198
436
|
describe
|
|
@@ -202,19 +440,20 @@ describe
|
|
|
202
440
|
ApiResourceKind.iam_policy,
|
|
203
441
|
ApiResourceKind.identity_account,
|
|
204
442
|
ApiResourceKind.organization,
|
|
443
|
+
ApiResourceKind.team,
|
|
205
444
|
ApiResourceKind.agent,
|
|
206
445
|
ApiResourceKind.session,
|
|
207
446
|
ApiResourceKind.agent_execution,
|
|
208
447
|
ApiResourceKind.agent_instance,
|
|
209
448
|
]),
|
|
210
|
-
)("the built-in
|
|
449
|
+
)("the built-in evaluator's cost on $name", (fixture) => {
|
|
211
450
|
describe.skipIf(fixture.skip)("measured", () => {
|
|
212
451
|
let opened: OpenedStore;
|
|
213
452
|
let policies: IamPolicyStore;
|
|
214
453
|
let accounts: IdentityAccountStore;
|
|
215
454
|
|
|
216
455
|
async function save(type: string, facts: FixtureRowFacts): Promise<object> {
|
|
217
|
-
const declaration =
|
|
456
|
+
const declaration = storedDeclaration(type);
|
|
218
457
|
const row = fixtureRow(declaration, facts);
|
|
219
458
|
await opened.store.saveResource(
|
|
220
459
|
declaration.kind,
|
|
@@ -225,6 +464,31 @@ describe
|
|
|
225
464
|
return row;
|
|
226
465
|
}
|
|
227
466
|
|
|
467
|
+
/**
|
|
468
|
+
* Writes the population's rows through the generic Store, where the
|
|
469
|
+
* OSS adapter reads every policy from: the port's `save` would read
|
|
470
|
+
* each id before writing it, doubling a seed of ten thousand rows.
|
|
471
|
+
* Returns the policy table's size.
|
|
472
|
+
*/
|
|
473
|
+
async function seedPopulation(n: number): Promise<number> {
|
|
474
|
+
const specs = populationPolicies(n);
|
|
475
|
+
for (const spec of specs) {
|
|
476
|
+
const id = policyIdFor(spec);
|
|
477
|
+
await opened.store.saveResource(
|
|
478
|
+
ApiResourceKind.iam_policy,
|
|
479
|
+
id,
|
|
480
|
+
IamPolicySchema,
|
|
481
|
+
create(IamPolicySchema, {
|
|
482
|
+
apiVersion: IAM_POLICY_API_VERSION,
|
|
483
|
+
kind: IAM_POLICY_KIND,
|
|
484
|
+
metadata: { id },
|
|
485
|
+
spec,
|
|
486
|
+
}),
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
return specs.length;
|
|
490
|
+
}
|
|
491
|
+
|
|
228
492
|
beforeEach(async () => {
|
|
229
493
|
opened = await fixture.open();
|
|
230
494
|
policies = newResourceIamPolicyStore(opened.store);
|
|
@@ -233,6 +497,10 @@ describe
|
|
|
233
497
|
[FOUNDER, "auth0|founder"],
|
|
234
498
|
[MEMBER, "auth0|member"],
|
|
235
499
|
[VIEWER, "auth0|viewer"],
|
|
500
|
+
[TEAM_MEMBER, "auth0|team-member"],
|
|
501
|
+
[MULTI_TEAM, "auth0|multi-team"],
|
|
502
|
+
[DIRECT, "auth0|direct"],
|
|
503
|
+
[FORMER, "auth0|former"],
|
|
236
504
|
] as const) {
|
|
237
505
|
await accounts.save(
|
|
238
506
|
create(IdentityAccountSchema, {
|
|
@@ -250,17 +518,16 @@ describe
|
|
|
250
518
|
visibility: ApiResourceVisibility.visibility_private,
|
|
251
519
|
createdBy: FOUNDER,
|
|
252
520
|
});
|
|
253
|
-
for (
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
spec,
|
|
521
|
+
for (let team = 0; team < TEAMS; team += 1) {
|
|
522
|
+
await opened.store.saveResource(
|
|
523
|
+
ApiResourceKind.team,
|
|
524
|
+
teamId(team),
|
|
525
|
+
storedDeclaration("team").schema,
|
|
526
|
+
fixtureRow(storedDeclaration("team"), {
|
|
527
|
+
id: teamId(team),
|
|
528
|
+
org: ORG,
|
|
529
|
+
visibility: ApiResourceVisibility.visibility_private,
|
|
530
|
+
createdBy: FOUNDER,
|
|
264
531
|
}),
|
|
265
532
|
);
|
|
266
533
|
}
|
|
@@ -272,14 +539,63 @@ describe
|
|
|
272
539
|
|
|
273
540
|
for (const shape of SHAPES) {
|
|
274
541
|
for (const n of SIZES) {
|
|
275
|
-
it(
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
542
|
+
it(
|
|
543
|
+
`${shape.name} at ${n} rows`,
|
|
544
|
+
async () => {
|
|
545
|
+
const declaration = storedDeclaration(shape.kind);
|
|
546
|
+
const policyRows = await seedPopulation(n);
|
|
547
|
+
for (const parent of shape.parents ?? []) {
|
|
548
|
+
await save(parent.type, parent.facts);
|
|
549
|
+
}
|
|
550
|
+
const entries: ListEntryMeta[] = [];
|
|
551
|
+
for (let i = 0; i < n; i += 1) {
|
|
552
|
+
const row = await save(shape.kind, shape.row(i, n));
|
|
553
|
+
entries.push({
|
|
554
|
+
...rowAuthorizationFactsOf(declaration.kind, row),
|
|
555
|
+
labels: {},
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
const scope = newBuiltInListReadScope({
|
|
559
|
+
store: opened.store,
|
|
560
|
+
policies,
|
|
561
|
+
accounts,
|
|
562
|
+
logger: silentLogger,
|
|
563
|
+
model: builtInModel,
|
|
564
|
+
});
|
|
565
|
+
const started = performance.now();
|
|
566
|
+
const kept =
|
|
567
|
+
shape.verb === "restrict"
|
|
568
|
+
? await scope.restrictListEntries(
|
|
569
|
+
resolved(shape.caller),
|
|
570
|
+
declaration.kind,
|
|
571
|
+
entries,
|
|
572
|
+
)
|
|
573
|
+
: await scope.authorizedResourceIds(
|
|
574
|
+
resolved(shape.caller),
|
|
575
|
+
declaration.kind,
|
|
576
|
+
);
|
|
577
|
+
const elapsedMs = Math.round(performance.now() - started);
|
|
578
|
+
// Every shape states what its caller keeps, so a measurement
|
|
579
|
+
// over a scope that answered wrongly measures nothing.
|
|
580
|
+
expect(kept.size).toBe(shape.kept?.(n) ?? n);
|
|
581
|
+
console.log(
|
|
582
|
+
`[measure] ${fixture.name} ${kindEnumName(declaration.kind)} ${shape.verb} n=${n} policies=${policyRows} elapsed=${elapsedMs}ms — ${shape.name}`,
|
|
583
|
+
);
|
|
584
|
+
},
|
|
585
|
+
MEASURE_TIMEOUT_MS,
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
for (const shape of PAGE_SHAPES) {
|
|
591
|
+
it(
|
|
592
|
+
`pages: ${shape.name}, at a ${PAGE_POPULATION}-row population`,
|
|
593
|
+
async () => {
|
|
594
|
+
const declaration = storedDeclaration("agent");
|
|
595
|
+
const policyRows = await seedPopulation(PAGE_POPULATION);
|
|
280
596
|
const entries: ListEntryMeta[] = [];
|
|
281
|
-
for (let i = 0; i <
|
|
282
|
-
const row = await save(
|
|
597
|
+
for (let i = 0; i < PAGE_EXAMINE_BUDGET; i += 1) {
|
|
598
|
+
const row = await save("agent", shape.row(i));
|
|
283
599
|
entries.push({
|
|
284
600
|
...rowAuthorizationFactsOf(declaration.kind, row),
|
|
285
601
|
labels: {},
|
|
@@ -290,27 +606,73 @@ describe
|
|
|
290
606
|
policies,
|
|
291
607
|
accounts,
|
|
292
608
|
logger: silentLogger,
|
|
609
|
+
model: builtInModel,
|
|
293
610
|
});
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
611
|
+
const caller = resolved(shape.caller);
|
|
612
|
+
// A page's batches run one after another, each through the scope
|
|
613
|
+
// with its own source, exactly as the page loop calls `admit`.
|
|
614
|
+
async function page(batches: number): Promise<number> {
|
|
615
|
+
let kept = 0;
|
|
616
|
+
for (let b = 0; b < batches; b += 1) {
|
|
617
|
+
const batch = entries.slice(b * PAGE_SIZE, (b + 1) * PAGE_SIZE);
|
|
618
|
+
kept += (
|
|
619
|
+
await scope.restrictListEntries(caller, declaration.kind, batch)
|
|
620
|
+
).size;
|
|
621
|
+
}
|
|
622
|
+
return kept;
|
|
623
|
+
}
|
|
624
|
+
for (const batches of [1, PAGE_EXAMINE_BUDGET / PAGE_SIZE]) {
|
|
625
|
+
const started = performance.now();
|
|
626
|
+
const kept = await page(batches);
|
|
627
|
+
const elapsedMs = Math.round(performance.now() - started);
|
|
628
|
+
expect(kept).toBe(shape.kept(batches * PAGE_SIZE));
|
|
629
|
+
console.log(
|
|
630
|
+
`[measure] ${fixture.name} agent page calls=${batches} candidates=${batches * PAGE_SIZE} policies=${policyRows} elapsed=${elapsedMs}ms — ${batches === 1 ? "a page that fills in one batch" : "the worst page"}: ${shape.name}`,
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
},
|
|
634
|
+
MEASURE_TIMEOUT_MS,
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
for (const [caller, agent, grant] of [
|
|
639
|
+
[TEAM_MEMBER, 0, "a team grant"],
|
|
640
|
+
[DIRECT, 1, "a direct grant"],
|
|
641
|
+
] as const) {
|
|
642
|
+
for (const n of SIZES) {
|
|
643
|
+
it(
|
|
644
|
+
`point check: can_view through ${grant} at the ${n}-candidate population`,
|
|
645
|
+
async () => {
|
|
646
|
+
const policyRows = await seedPopulation(n);
|
|
647
|
+
await save("agent", enterpriseAgentRow(agent));
|
|
648
|
+
const authorizer = newBuiltInAuthorizer({
|
|
649
|
+
store: opened.store,
|
|
650
|
+
policies,
|
|
651
|
+
accounts,
|
|
652
|
+
edition: ServerEdition.oss,
|
|
653
|
+
logger: silentLogger,
|
|
654
|
+
model: builtInModel,
|
|
655
|
+
});
|
|
656
|
+
const check = {
|
|
657
|
+
permission: IamPermission.can_view,
|
|
658
|
+
resourceKind: ApiResourceKind.agent,
|
|
659
|
+
resourceId: enterpriseAgentId(agent),
|
|
660
|
+
};
|
|
661
|
+
const started = performance.now();
|
|
662
|
+
for (let k = 0; k < POINT_CHECKS; k += 1) {
|
|
663
|
+
// Each call builds its own source, the Authorizer's shape: no
|
|
664
|
+
// memo carries from one check to the next.
|
|
665
|
+
expect(
|
|
666
|
+
await authorizer.authorize(resolved(caller), check),
|
|
667
|
+
).toEqual({ kind: "allow" });
|
|
668
|
+
}
|
|
669
|
+
const meanMs = (performance.now() - started) / POINT_CHECKS;
|
|
670
|
+
console.log(
|
|
671
|
+
`[measure] ${fixture.name} agent point-check policies=${policyRows} mean=${meanMs.toFixed(2)}ms over ${POINT_CHECKS} — can_view through ${grant}`,
|
|
672
|
+
);
|
|
673
|
+
},
|
|
674
|
+
MEASURE_TIMEOUT_MS,
|
|
675
|
+
);
|
|
314
676
|
}
|
|
315
677
|
}
|
|
316
678
|
});
|
|
@@ -53,7 +53,10 @@ import type {
|
|
|
53
53
|
ListEntryMeta,
|
|
54
54
|
ListReadScope,
|
|
55
55
|
} from "../../extensions/list-read-scope.js";
|
|
56
|
-
import {
|
|
56
|
+
import {
|
|
57
|
+
InternalCallerOfferedError,
|
|
58
|
+
restrictListByReadScope,
|
|
59
|
+
} from "../../extensions/list-read-scope.js";
|
|
57
60
|
import { kindEnumName } from "../../pipeline/apiresource-meta.js";
|
|
58
61
|
import { rowAuthorizationFactsOf } from "../../pipeline/steps/authorization-facts.js";
|
|
59
62
|
import type { Store } from "../../store/interface.js";
|
|
@@ -63,7 +66,7 @@ import { builtInModel } from "../model/index.js";
|
|
|
63
66
|
import type { KindDeclaration } from "../model/rewrite.js";
|
|
64
67
|
import { driverFixtures, dropPostgresFixture } from "./drivers.js";
|
|
65
68
|
import type { OpenedStore } from "./drivers.js";
|
|
66
|
-
import { fixtureRow } from "./support.js";
|
|
69
|
+
import { fixtureRow, storedDeclaration } from "./support.js";
|
|
67
70
|
import type { FixtureRowFacts } from "./support.js";
|
|
68
71
|
|
|
69
72
|
const FOUNDER = accountIdFor("auth0|founder");
|
|
@@ -94,14 +97,6 @@ const SEEDED_KINDS = [
|
|
|
94
97
|
ApiResourceKind.agent_instance,
|
|
95
98
|
];
|
|
96
99
|
|
|
97
|
-
function declared(type: string): KindDeclaration {
|
|
98
|
-
const declaration = builtInModel.byType(type);
|
|
99
|
-
if (declaration === undefined) {
|
|
100
|
-
throw new Error(`${type} is declared`);
|
|
101
|
-
}
|
|
102
|
-
return declaration;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
100
|
function resolved(accountId: string): CallerIdentity {
|
|
106
101
|
return {
|
|
107
102
|
identityId: accountId,
|
|
@@ -161,7 +156,7 @@ describe.each(driverFixtures(SEEDED_KINDS))(
|
|
|
161
156
|
type: string,
|
|
162
157
|
facts: FixtureRowFacts,
|
|
163
158
|
): Promise<void> {
|
|
164
|
-
const declaration =
|
|
159
|
+
const declaration = storedDeclaration(type);
|
|
165
160
|
const row = fixtureRow(declaration, facts);
|
|
166
161
|
await opened.store.saveResource(
|
|
167
162
|
declaration.kind,
|
|
@@ -608,9 +603,15 @@ describe.each(driverFixtures(SEEDED_KINDS))(
|
|
|
608
603
|
() => undefined,
|
|
609
604
|
(error: unknown) => error,
|
|
610
605
|
);
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
606
|
+
// The seam's own refusal, the one a composition's driver throws
|
|
607
|
+
// too; never the evaluator's fault, since nothing was evaluated.
|
|
608
|
+
expect(refusal).toBeInstanceOf(InternalCallerOfferedError);
|
|
609
|
+
expect(refusal).not.toBeInstanceOf(AuthorizationEvaluationError);
|
|
610
|
+
expect((refusal as InternalCallerOfferedError).kind).toBe(
|
|
611
|
+
ApiResourceKind.agent,
|
|
612
|
+
);
|
|
613
|
+
expect((refusal as InternalCallerOfferedError).message).toContain(
|
|
614
|
+
"kind 'agent'",
|
|
614
615
|
);
|
|
615
616
|
// Refused before any read: the server was never evaluated as a person.
|
|
616
617
|
expect(accountReads).toBe(0);
|
|
@@ -38,7 +38,7 @@ import { builtInModel } from "../model/index.js";
|
|
|
38
38
|
import { newBuiltInOrganizationDirectory } from "../organization-directory.js";
|
|
39
39
|
import { driverFixtures, dropPostgresFixture } from "./drivers.js";
|
|
40
40
|
import type { OpenedStore } from "./drivers.js";
|
|
41
|
-
import { fixtureRow } from "./support.js";
|
|
41
|
+
import { fixtureRow, storedDeclaration } from "./support.js";
|
|
42
42
|
|
|
43
43
|
const FOUNDER = accountIdFor("auth0|founder");
|
|
44
44
|
const MEMBER = accountIdFor("auth0|member");
|
|
@@ -81,7 +81,7 @@ describe.each(
|
|
|
81
81
|
let principalReads: number;
|
|
82
82
|
|
|
83
83
|
async function organization(id: string) {
|
|
84
|
-
const declaration =
|
|
84
|
+
const declaration = storedDeclaration("organization");
|
|
85
85
|
await opened.store.saveResource(
|
|
86
86
|
ApiResourceKind.organization,
|
|
87
87
|
id,
|