@stigmer/server 3.27.2 → 3.29.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 +3 -3
- package/dist/authorization/authorizer.js +3 -3
- package/dist/authorization/derived-tuples.js +6 -1
- 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 +3141 -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/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 +20 -1
- package/dist/extensions/list-read-scope.js.map +1 -1
- package/dist/index.d.ts +7 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -5
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
- package/src/authorization/README.md +41 -34
- package/src/authorization/__tests__/authorizer.test.ts +5 -14
- package/src/authorization/__tests__/derived-tuples.test.ts +29 -27
- 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 +12 -20
- 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 +92 -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 +3 -3
- package/src/authorization/derived-tuples.ts +7 -2
- 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 +345 -277
- package/src/authorization/model/bindings.ts +131 -0
- package/src/authorization/model/data/authorization-model.json +3141 -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/compose.ts +9 -7
- 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 +15 -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__/enterprise-model.ts +0 -59
- 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,255 +1,97 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pins the built-in model
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
2
|
+
* Pins the built-in model against its sources and the contract:
|
|
3
|
+
* - its types are the files `fga.mod` lists, in that order, one type per
|
|
4
|
+
* file named by the file, and every one is bound (bindings.ts);
|
|
5
|
+
* - every binding's schema is the message `kind_meta` spells for the
|
|
6
|
+
* kind (`ai.stigmer.<group>.<name>.<version>.<Name>`), `platform`
|
|
7
|
+
* excepted by name as the one rowless type, so the explicit table
|
|
8
|
+
* cannot bind a kind to the wrong message;
|
|
9
|
+
* - every declaration names a `.fga` file that exists under fga/model;
|
|
10
|
+
* - the model declares every open-source-tier kind, and outside the tier
|
|
11
|
+
* exactly the four kinds the wider editions serve, so a kind the
|
|
12
|
+
* contract moves between tiers is a visible diff here;
|
|
13
|
+
* - a kind is reached by enum and by FGA type name alike;
|
|
14
|
+
* - the relations whose direct list admits a team (`team#member`) are
|
|
15
|
+
* exactly the roles the kind's `team_grantable_roles` names, a subset
|
|
16
|
+
* of what a person can be granted and never ownership, so the grant
|
|
17
|
+
* step and the model cannot disagree about where a team may be
|
|
18
|
+
* granted;
|
|
19
|
+
* - the derived rules are exactly the three relations `kind_meta` cannot
|
|
20
|
+
* derive;
|
|
21
|
+
* - a kind whose authorization is its parent's whole
|
|
22
|
+
* (`inheritedAuthorizationParentOf`) holds nothing of its own: its one
|
|
23
|
+
* direct relation is the parent link, every other relation is built
|
|
24
|
+
* through it, and its `can_view` answers exactly what the parent's
|
|
25
|
+
* does, so the list read scope may ask the parent in the child's place
|
|
26
|
+
* (extensions/list-read-scope.ts) without hiding or showing a row the
|
|
27
|
+
* child's own check would not.
|
|
13
28
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* set-equal to the `kind_meta.tier` open-source members (a kind the
|
|
17
|
-
* contract moves into or out of the tier is a visible diff here before
|
|
18
|
-
* it is a silent gap in enforcement).
|
|
29
|
+
* Whether the model defines what the wire asks is the whole contract's
|
|
30
|
+
* question, pinned in __tests__/wire-permissions.test.ts.
|
|
19
31
|
*/
|
|
32
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
33
|
+
import { fileURLToPath } from "node:url";
|
|
34
|
+
|
|
20
35
|
import { describe, expect, it } from "vitest";
|
|
21
36
|
|
|
37
|
+
import { ApiResourceGroup } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_group_pb";
|
|
22
38
|
import {
|
|
23
39
|
ApiResourceKind,
|
|
24
40
|
ApiResourceKindSchema,
|
|
41
|
+
ApiResourceVersion,
|
|
25
42
|
ResourceTier,
|
|
26
43
|
} from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
27
|
-
import {
|
|
28
|
-
IamPermissionSchema,
|
|
29
|
-
IamRole,
|
|
30
|
-
} from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
|
|
44
|
+
import { IamRole } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
|
|
31
45
|
|
|
32
46
|
import {
|
|
33
47
|
getKindMeta,
|
|
34
48
|
grantableRolesFor,
|
|
49
|
+
inheritedAuthorizationParentOf,
|
|
35
50
|
kindEnumName,
|
|
36
51
|
teamGrantableRolesFor,
|
|
37
52
|
} from "../../../pipeline/apiresource-meta.js";
|
|
53
|
+
import {
|
|
54
|
+
computed,
|
|
55
|
+
direct,
|
|
56
|
+
from,
|
|
57
|
+
objectOf,
|
|
58
|
+
throwawayDeclaration,
|
|
59
|
+
union,
|
|
60
|
+
} from "../../__tests__/throwaway-model.js";
|
|
61
|
+
import { KIND_BINDINGS, ROWLESS } from "../bindings.js";
|
|
38
62
|
import { builtInModel, declarationFor } from "../index.js";
|
|
39
|
-
import type { Rewrite, SubjectType } from "../rewrite.js";
|
|
63
|
+
import type { KindDeclaration, Rewrite, SubjectType } from "../rewrite.js";
|
|
40
64
|
|
|
41
|
-
/** The
|
|
42
|
-
const
|
|
43
|
-
ApiResourceKind.identity_account,
|
|
44
|
-
ApiResourceKind.iam_policy,
|
|
45
|
-
ApiResourceKind.api_key,
|
|
46
|
-
ApiResourceKind.oauth_app,
|
|
47
|
-
ApiResourceKind.platform_client,
|
|
48
|
-
ApiResourceKind.organization,
|
|
49
|
-
ApiResourceKind.agent,
|
|
50
|
-
ApiResourceKind.agent_channel,
|
|
51
|
-
ApiResourceKind.agent_share,
|
|
52
|
-
ApiResourceKind.channel_app,
|
|
53
|
-
ApiResourceKind.agent_instance,
|
|
54
|
-
ApiResourceKind.agent_execution,
|
|
55
|
-
ApiResourceKind.artifact,
|
|
56
|
-
ApiResourceKind.environment,
|
|
57
|
-
ApiResourceKind.execution_context,
|
|
58
|
-
ApiResourceKind.mcp_server,
|
|
59
|
-
ApiResourceKind.memory,
|
|
60
|
-
ApiResourceKind.schedule,
|
|
61
|
-
ApiResourceKind.session,
|
|
62
|
-
ApiResourceKind.skill,
|
|
63
|
-
ApiResourceKind.workflow,
|
|
64
|
-
ApiResourceKind.workflow_instance,
|
|
65
|
-
ApiResourceKind.workflow_execution,
|
|
66
|
-
ApiResourceKind.plugin,
|
|
67
|
-
] as const;
|
|
65
|
+
/** The server package root, where fga/model lives. */
|
|
66
|
+
const PACKAGE_ROOT = new URL("../../../../", import.meta.url);
|
|
68
67
|
|
|
69
|
-
/**
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
"
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
]
|
|
94
|
-
|
|
95
|
-
"can_view",
|
|
96
|
-
"can_edit",
|
|
97
|
-
"can_delete",
|
|
98
|
-
"can_grant_access",
|
|
99
|
-
"can_view_access",
|
|
100
|
-
],
|
|
101
|
-
oauth_app: [
|
|
102
|
-
"can_view",
|
|
103
|
-
"can_edit",
|
|
104
|
-
"can_delete",
|
|
105
|
-
"can_grant_access",
|
|
106
|
-
"can_view_access",
|
|
107
|
-
],
|
|
108
|
-
platform_client: [
|
|
109
|
-
"can_view",
|
|
110
|
-
"can_edit",
|
|
111
|
-
"can_delete",
|
|
112
|
-
"can_grant_access",
|
|
113
|
-
"can_view_access",
|
|
114
|
-
],
|
|
115
|
-
organization: [
|
|
116
|
-
"can_view",
|
|
117
|
-
"can_edit",
|
|
118
|
-
"can_delete",
|
|
119
|
-
"can_create_agent",
|
|
120
|
-
"can_create_workflow",
|
|
121
|
-
"can_create_session",
|
|
122
|
-
"can_create_environment",
|
|
123
|
-
"can_create_skill",
|
|
124
|
-
"can_create_plugin",
|
|
125
|
-
"can_create_mcp_server",
|
|
126
|
-
"can_create_idp",
|
|
127
|
-
"can_create_identity_account",
|
|
128
|
-
"can_create_oauth_app",
|
|
129
|
-
"can_create_platform_client",
|
|
130
|
-
"can_create_channel_app",
|
|
131
|
-
"can_create_execution_in",
|
|
132
|
-
"can_create_agent_share",
|
|
133
|
-
"can_create_agent_instance",
|
|
134
|
-
"can_create_team",
|
|
135
|
-
"can_grant_access",
|
|
136
|
-
"can_view_access",
|
|
137
|
-
"can_view_billing",
|
|
138
|
-
"can_manage_billing",
|
|
139
|
-
],
|
|
140
|
-
agent: [
|
|
141
|
-
"can_view",
|
|
142
|
-
"can_edit",
|
|
143
|
-
"can_delete",
|
|
144
|
-
"can_execute",
|
|
145
|
-
"can_create_instance",
|
|
146
|
-
"can_grant_access",
|
|
147
|
-
"can_view_access",
|
|
148
|
-
],
|
|
149
|
-
agent_channel: [
|
|
150
|
-
"can_view",
|
|
151
|
-
"can_edit",
|
|
152
|
-
"can_delete",
|
|
153
|
-
"can_participate",
|
|
154
|
-
"can_grant_access",
|
|
155
|
-
"can_view_access",
|
|
156
|
-
],
|
|
157
|
-
agent_share: [
|
|
158
|
-
"can_view",
|
|
159
|
-
"can_edit",
|
|
160
|
-
"can_delete",
|
|
161
|
-
"can_grant_access",
|
|
162
|
-
"can_view_access",
|
|
163
|
-
],
|
|
164
|
-
channel_app: [
|
|
165
|
-
"can_view",
|
|
166
|
-
"can_edit",
|
|
167
|
-
"can_delete",
|
|
168
|
-
"can_grant_access",
|
|
169
|
-
"can_view_access",
|
|
170
|
-
],
|
|
171
|
-
agent_instance: [
|
|
172
|
-
"can_view",
|
|
173
|
-
"can_execute",
|
|
174
|
-
"can_edit",
|
|
175
|
-
"can_delete",
|
|
176
|
-
"can_grant_access",
|
|
177
|
-
"can_view_access",
|
|
178
|
-
],
|
|
179
|
-
agent_execution: ["can_view", "can_edit"],
|
|
180
|
-
artifact: ["can_view", "can_edit"],
|
|
181
|
-
environment: [
|
|
182
|
-
"can_view",
|
|
183
|
-
"can_edit",
|
|
184
|
-
"can_delete",
|
|
185
|
-
"can_read_secrets",
|
|
186
|
-
"can_grant_access",
|
|
187
|
-
"can_view_access",
|
|
188
|
-
],
|
|
189
|
-
execution_context: ["can_view", "can_edit"],
|
|
190
|
-
mcp_server: [
|
|
191
|
-
"can_view",
|
|
192
|
-
"can_edit",
|
|
193
|
-
"can_delete",
|
|
194
|
-
"can_connect",
|
|
195
|
-
"can_grant_access",
|
|
196
|
-
"can_view_access",
|
|
197
|
-
],
|
|
198
|
-
memory: ["can_view", "can_edit", "can_delete"],
|
|
199
|
-
schedule: [
|
|
200
|
-
"can_view",
|
|
201
|
-
"can_edit",
|
|
202
|
-
"can_delete",
|
|
203
|
-
"can_grant_access",
|
|
204
|
-
"can_view_access",
|
|
205
|
-
],
|
|
206
|
-
session: [
|
|
207
|
-
"can_view",
|
|
208
|
-
"can_edit",
|
|
209
|
-
"can_delete",
|
|
210
|
-
"can_create_execution_in",
|
|
211
|
-
"can_grant_access",
|
|
212
|
-
"can_view_access",
|
|
213
|
-
],
|
|
214
|
-
skill: [
|
|
215
|
-
"can_view",
|
|
216
|
-
"can_edit",
|
|
217
|
-
"can_delete",
|
|
218
|
-
"can_grant_access",
|
|
219
|
-
"can_view_access",
|
|
220
|
-
],
|
|
221
|
-
workflow: [
|
|
222
|
-
"can_view",
|
|
223
|
-
"can_edit",
|
|
224
|
-
"can_delete",
|
|
225
|
-
"can_execute",
|
|
226
|
-
"can_grant_access",
|
|
227
|
-
"can_view_access",
|
|
228
|
-
],
|
|
229
|
-
plugin: [
|
|
230
|
-
"can_view",
|
|
231
|
-
"can_edit",
|
|
232
|
-
"can_delete",
|
|
233
|
-
"can_grant_access",
|
|
234
|
-
"can_view_access",
|
|
235
|
-
],
|
|
236
|
-
workflow_instance: [
|
|
237
|
-
"can_view",
|
|
238
|
-
"can_execute",
|
|
239
|
-
"can_edit",
|
|
240
|
-
"can_delete",
|
|
241
|
-
"can_grant_access",
|
|
242
|
-
"can_view_access",
|
|
243
|
-
],
|
|
244
|
-
workflow_execution: [
|
|
245
|
-
"can_view",
|
|
246
|
-
"can_edit",
|
|
247
|
-
"can_grant_access",
|
|
248
|
-
"can_view_access",
|
|
249
|
-
],
|
|
250
|
-
};
|
|
68
|
+
/** The kinds the model defines outside the open-source tier: the ones the wider editions serve. */
|
|
69
|
+
const WIDER_EDITION_KINDS: ReadonlyArray<ApiResourceKind> = [
|
|
70
|
+
ApiResourceKind.platform,
|
|
71
|
+
ApiResourceKind.identity_provider,
|
|
72
|
+
ApiResourceKind.invitation,
|
|
73
|
+
ApiResourceKind.team,
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
/** The files `fga.mod` lists, in its order. */
|
|
77
|
+
function modFiles(): ReadonlyArray<string> {
|
|
78
|
+
const text = readFileSync(fileURLToPath(new URL("fga/model/fga.mod", PACKAGE_ROOT)), "utf8");
|
|
79
|
+
return [...text.matchAll(/^\s*-\s*(\S+\.fga)\s*$/gm)].map((match) => match[1] ?? "");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The message `kind_meta` spells for a kind. */
|
|
83
|
+
function kindMetaMessageName(kind: ApiResourceKind): string {
|
|
84
|
+
const meta = getKindMeta(kind);
|
|
85
|
+
return [
|
|
86
|
+
"ai",
|
|
87
|
+
"stigmer",
|
|
88
|
+
ApiResourceGroup[meta.group],
|
|
89
|
+
meta.name.toLowerCase(),
|
|
90
|
+
ApiResourceVersion[meta.version],
|
|
91
|
+
meta.name,
|
|
92
|
+
].join(".");
|
|
93
|
+
}
|
|
251
94
|
|
|
252
|
-
/** The subject types a relation's direct lists name, wherever they sit in its rewrite. */
|
|
253
95
|
function directSubjects(rewrite: Rewrite): ReadonlyArray<SubjectType> {
|
|
254
96
|
switch (rewrite.node) {
|
|
255
97
|
case "this":
|
|
@@ -267,15 +109,185 @@ function directSubjects(rewrite: Rewrite): ReadonlyArray<SubjectType> {
|
|
|
267
109
|
}
|
|
268
110
|
}
|
|
269
111
|
|
|
112
|
+
/** The relation every list lane reads through, on the child and on its parent. */
|
|
113
|
+
const LIST_RELATION = "can_view";
|
|
114
|
+
|
|
115
|
+
/** The `this`, `computed` and `from` nodes a rewrite is built from, in the line's order. */
|
|
116
|
+
function leavesOf(rewrite: Rewrite): ReadonlyArray<Rewrite> {
|
|
117
|
+
switch (rewrite.node) {
|
|
118
|
+
case "this":
|
|
119
|
+
case "computed":
|
|
120
|
+
case "from":
|
|
121
|
+
return [rewrite];
|
|
122
|
+
case "union":
|
|
123
|
+
case "intersection":
|
|
124
|
+
return rewrite.members.flatMap(leavesOf);
|
|
125
|
+
default: {
|
|
126
|
+
const exhaustive: never = rewrite;
|
|
127
|
+
throw new Error(`unknown rewrite node: ${JSON.stringify(exhaustive)}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* The relations of `declaration` that `relation` includes whole: itself,
|
|
134
|
+
* and every sibling a union member or a computed node names, transitively.
|
|
135
|
+
* An intersection includes none of its members whole, so it stops here.
|
|
136
|
+
*/
|
|
137
|
+
function includedWhole(declaration: KindDeclaration, relation: string): ReadonlySet<string> {
|
|
138
|
+
const included = new Set<string>();
|
|
139
|
+
const visit = (name: string): void => {
|
|
140
|
+
if (included.has(name)) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
included.add(name);
|
|
144
|
+
const walk = (rewrite: Rewrite | undefined): void => {
|
|
145
|
+
if (rewrite?.node === "computed") {
|
|
146
|
+
visit(rewrite.relation);
|
|
147
|
+
} else if (rewrite?.node === "union") {
|
|
148
|
+
rewrite.members.forEach(walk);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
walk(declaration.relations.get(name));
|
|
152
|
+
};
|
|
153
|
+
visit(relation);
|
|
154
|
+
return included;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* The parent relations `relation` of `child` reaches through `link`,
|
|
159
|
+
* following the child's own computed relations to their leaves.
|
|
160
|
+
*/
|
|
161
|
+
function parentRelationsReached(
|
|
162
|
+
child: KindDeclaration,
|
|
163
|
+
relation: string,
|
|
164
|
+
link: string,
|
|
165
|
+
): ReadonlySet<string> {
|
|
166
|
+
const reached = new Set<string>();
|
|
167
|
+
const seen = new Set<string>();
|
|
168
|
+
const visit = (name: string): void => {
|
|
169
|
+
if (seen.has(name)) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
seen.add(name);
|
|
173
|
+
const rewrite = child.relations.get(name);
|
|
174
|
+
for (const leaf of rewrite === undefined ? [] : leavesOf(rewrite)) {
|
|
175
|
+
if (leaf.node === "computed") {
|
|
176
|
+
visit(leaf.relation);
|
|
177
|
+
} else if (leaf.node === "from" && leaf.tupleset === link) {
|
|
178
|
+
reached.add(leaf.relation);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
visit(relation);
|
|
183
|
+
return reached;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Every way `child` breaks the promise its `kind_meta` makes when it says
|
|
188
|
+
* the kind's authorization is its parent's whole (PARENT scope, INHERITED
|
|
189
|
+
* owner); empty when the model keeps it. The list read scope reads that
|
|
190
|
+
* promise to let a driver ask the parent's `can_view` in the child's
|
|
191
|
+
* place, which gives the child's answer only while:
|
|
192
|
+
* - the child's one direct relation is the link, to the parent type
|
|
193
|
+
* alone, so nothing is granted on the child that the parent does not
|
|
194
|
+
* hold;
|
|
195
|
+
* - every other relation is built through the link or from the child's
|
|
196
|
+
* own relations, never from another object;
|
|
197
|
+
* - the child's `can_view` includes the parent's (`can_view from <link>`
|
|
198
|
+
* as a union member), so asking the parent hides nothing;
|
|
199
|
+
* - every parent relation the child's `can_view` reaches is one the
|
|
200
|
+
* parent's `can_view` includes whole, so asking the parent shows
|
|
201
|
+
* nothing more.
|
|
202
|
+
* The last reads inclusion structurally (union and computed), so it errs
|
|
203
|
+
* toward a violation: a shape it cannot prove is a design review, never a
|
|
204
|
+
* pass.
|
|
205
|
+
*/
|
|
206
|
+
function inheritedWholeViolations(
|
|
207
|
+
child: KindDeclaration,
|
|
208
|
+
parent: KindDeclaration,
|
|
209
|
+
link: string,
|
|
210
|
+
): ReadonlyArray<string> {
|
|
211
|
+
const violations: string[] = [];
|
|
212
|
+
const linkRewrite = child.relations.get(link);
|
|
213
|
+
const [linked, ...more] = linkRewrite?.node === "this" ? linkRewrite.subjects : [];
|
|
214
|
+
if (linked?.form !== "object" || linked.type !== parent.type || more.length > 0) {
|
|
215
|
+
violations.push(`${child.type}#${link} is not the direct link [${parent.type}] alone`);
|
|
216
|
+
}
|
|
217
|
+
for (const [name, rewrite] of child.relations) {
|
|
218
|
+
if (name === link) {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
for (const leaf of leavesOf(rewrite)) {
|
|
222
|
+
if (leaf.node === "this") {
|
|
223
|
+
violations.push(`${child.type}#${name} admits a tuple on the child itself`);
|
|
224
|
+
} else if (leaf.node === "from" && leaf.tupleset !== link) {
|
|
225
|
+
violations.push(
|
|
226
|
+
`${child.type}#${name} reads ${leaf.relation} from ${leaf.tupleset}, not from ${link}`,
|
|
227
|
+
);
|
|
228
|
+
} else if (leaf.node === "computed" && !child.relations.has(leaf.relation)) {
|
|
229
|
+
violations.push(
|
|
230
|
+
`${child.type}#${name} names ${leaf.relation}, which the child does not declare`,
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
const childView = child.relations.get(LIST_RELATION);
|
|
236
|
+
if (childView === undefined || !parent.relations.has(LIST_RELATION)) {
|
|
237
|
+
violations.push(`${child.type} and ${parent.type} must both declare ${LIST_RELATION}`);
|
|
238
|
+
return violations;
|
|
239
|
+
}
|
|
240
|
+
const members = childView.node === "union" ? childView.members : [childView];
|
|
241
|
+
if (
|
|
242
|
+
!members.some(
|
|
243
|
+
(member) =>
|
|
244
|
+
member.node === "from" && member.relation === LIST_RELATION && member.tupleset === link,
|
|
245
|
+
)
|
|
246
|
+
) {
|
|
247
|
+
violations.push(
|
|
248
|
+
`${child.type}#${LIST_RELATION} does not include ${LIST_RELATION} from ${link}`,
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
const parentView = includedWhole(parent, LIST_RELATION);
|
|
252
|
+
for (const relation of parentRelationsReached(child, LIST_RELATION, link)) {
|
|
253
|
+
if (!parentView.has(relation)) {
|
|
254
|
+
violations.push(
|
|
255
|
+
`${child.type}#${LIST_RELATION} reaches ${parent.type}#${relation}, which ${parent.type}#${LIST_RELATION} does not include`,
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return violations;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** A declaration the model must hold, or a loud failure naming it. */
|
|
263
|
+
function declared(declaration: KindDeclaration | undefined, type: string): KindDeclaration {
|
|
264
|
+
if (declaration === undefined) {
|
|
265
|
+
throw new Error(`${type} is not in the built-in model`);
|
|
266
|
+
}
|
|
267
|
+
return declaration;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** The kinds whose `kind_meta` makes their authorization their parent's whole, with that parent. */
|
|
271
|
+
function inheritedWholeKinds(): ReadonlyArray<{
|
|
272
|
+
readonly kind: ApiResourceKind;
|
|
273
|
+
readonly parentType: string;
|
|
274
|
+
readonly link: string;
|
|
275
|
+
}> {
|
|
276
|
+
return ApiResourceKindSchema.values.flatMap((value) => {
|
|
277
|
+
const kind = value.number as ApiResourceKind;
|
|
278
|
+
if (kind === ApiResourceKind.api_resource_kind_unknown) {
|
|
279
|
+
return [];
|
|
280
|
+
}
|
|
281
|
+
const parent = inheritedAuthorizationParentOf(kind);
|
|
282
|
+
return parent === undefined ? [] : [{ kind, parentType: parent.kind, link: parent.relation }];
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
270
286
|
function isTeamMembers(subject: SubjectType): boolean {
|
|
271
|
-
return
|
|
272
|
-
subject.form === "userset" &&
|
|
273
|
-
subject.type === "team" &&
|
|
274
|
-
subject.relation === "member"
|
|
275
|
-
);
|
|
287
|
+
return subject.form === "userset" && subject.type === "team" && subject.relation === "member";
|
|
276
288
|
}
|
|
277
289
|
|
|
278
|
-
/** Every kind whose `kind_meta.tier` is the open-source tier
|
|
290
|
+
/** Every kind whose `kind_meta.tier` is the open-source tier. */
|
|
279
291
|
function openSourceTierKinds(): ReadonlySet<ApiResourceKind> {
|
|
280
292
|
const kinds = new Set<ApiResourceKind>();
|
|
281
293
|
for (const value of ApiResourceKindSchema.values) {
|
|
@@ -290,59 +302,63 @@ function openSourceTierKinds(): ReadonlySet<ApiResourceKind> {
|
|
|
290
302
|
return kinds;
|
|
291
303
|
}
|
|
292
304
|
|
|
293
|
-
describe("the built-in model
|
|
294
|
-
it("
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
305
|
+
describe("the built-in model", () => {
|
|
306
|
+
it("defines one type per file fga.mod lists, named by the file, in fga.mod order, every one bound", () => {
|
|
307
|
+
const files = modFiles();
|
|
308
|
+
expect(files.length).toBeGreaterThan(0);
|
|
309
|
+
expect(builtInModel.declarations.map((d) => d.type)).toEqual(
|
|
310
|
+
files.map((file) => file.replace(/^.*\//, "").replace(/\.fga$/, "")),
|
|
311
|
+
);
|
|
312
|
+
expect(builtInModel.declarations.map((d) => d.source)).toEqual(
|
|
313
|
+
files.map((file) => `fga/model/${file}`),
|
|
314
|
+
);
|
|
315
|
+
expect([...KIND_BINDINGS.keys()].map(kindEnumName)).toEqual(
|
|
316
|
+
builtInModel.declarations.map((d) => d.type),
|
|
317
|
+
);
|
|
299
318
|
});
|
|
300
319
|
|
|
301
|
-
it("
|
|
302
|
-
for (const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
ApiResourceKind.invitation,
|
|
311
|
-
ApiResourceKind.team,
|
|
312
|
-
]) {
|
|
313
|
-
expect(declarationFor(unserved), kindEnumName(unserved)).toBeUndefined();
|
|
320
|
+
it("binds every kind to the message kind_meta spells, and platform alone to ROWLESS", () => {
|
|
321
|
+
for (const declaration of builtInModel.declarations) {
|
|
322
|
+
if (declaration.kind === ApiResourceKind.platform) {
|
|
323
|
+
expect(declaration.schema).toBe(ROWLESS);
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
expect(declaration.schema?.typeName, declaration.type).toBe(
|
|
327
|
+
kindMetaMessageName(declaration.kind),
|
|
328
|
+
);
|
|
314
329
|
}
|
|
315
|
-
expect(builtInModel.byType("")).toBeUndefined();
|
|
316
330
|
});
|
|
317
331
|
|
|
318
|
-
it("names
|
|
332
|
+
it("names a .fga source that exists", () => {
|
|
319
333
|
for (const declaration of builtInModel.declarations) {
|
|
320
|
-
expect(
|
|
321
|
-
new
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
);
|
|
334
|
+
expect(
|
|
335
|
+
existsSync(fileURLToPath(new URL(declaration.source, PACKAGE_ROOT))),
|
|
336
|
+
declaration.source,
|
|
337
|
+
).toBe(true);
|
|
325
338
|
}
|
|
326
339
|
});
|
|
327
340
|
|
|
328
|
-
it("
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
)
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
341
|
+
it("defines every open-source-tier kind, and outside the tier exactly the kinds the wider editions serve", () => {
|
|
342
|
+
const declared = new Set(builtInModel.declarations.map((d) => d.kind));
|
|
343
|
+
const tier = openSourceTierKinds();
|
|
344
|
+
for (const kind of tier) {
|
|
345
|
+
expect(declared.has(kind), kindEnumName(kind)).toBe(true);
|
|
346
|
+
}
|
|
347
|
+
expect(
|
|
348
|
+
[...declared].filter((kind) => !tier.has(kind)).map(kindEnumName).sort(),
|
|
349
|
+
).toEqual(WIDER_EDITION_KINDS.map(kindEnumName).sort());
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it("reaches a declaration by kind and by FGA type name", () => {
|
|
335
353
|
for (const declaration of builtInModel.declarations) {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
);
|
|
339
|
-
expect(declaredWirePermissions, declaration.type).toEqual(
|
|
340
|
-
WIRE_PERMISSIONS_BY_TYPE[declaration.type],
|
|
341
|
-
);
|
|
354
|
+
expect(declarationFor(declaration.kind)).toBe(declaration);
|
|
355
|
+
expect(builtInModel.byType(kindEnumName(declaration.kind))).toBe(declaration);
|
|
342
356
|
}
|
|
357
|
+
expect(declarationFor(ApiResourceKind.api_resource_kind_unknown)).toBeUndefined();
|
|
358
|
+
expect(builtInModel.byType("")).toBeUndefined();
|
|
343
359
|
});
|
|
344
360
|
|
|
345
|
-
it("admits a team exactly on the roles each kind's team_grantable_roles lists — the
|
|
361
|
+
it("admits a team exactly on the roles each kind's team_grantable_roles lists — the model and the contract say one thing", () => {
|
|
346
362
|
for (const declaration of builtInModel.declarations) {
|
|
347
363
|
const admitsTeam = [...declaration.relations]
|
|
348
364
|
.filter(([, rewrite]) => directSubjects(rewrite).some(isTeamMembers))
|
|
@@ -376,4 +392,56 @@ describe("the built-in model's registry", () => {
|
|
|
376
392
|
"workflow_instance#execution_viewer",
|
|
377
393
|
]);
|
|
378
394
|
});
|
|
395
|
+
|
|
396
|
+
it("gives a kind whose authorization is its parent's nothing of its own, so asking the parent answers for it", () => {
|
|
397
|
+
const inherited = inheritedWholeKinds();
|
|
398
|
+
expect(inherited.length, "kinds whose authorization is their parent's").toBeGreaterThan(0);
|
|
399
|
+
for (const { kind, parentType, link } of inherited) {
|
|
400
|
+
const child = declared(declarationFor(kind), kindEnumName(kind));
|
|
401
|
+
const parent = declared(builtInModel.byType(parentType), parentType);
|
|
402
|
+
expect(inheritedWholeViolations(child, parent, link), kindEnumName(kind)).toEqual([]);
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
describe("the parent-inheritance walker refuses what would make asking the parent wrong", () => {
|
|
408
|
+
const execution = declared(declarationFor(ApiResourceKind.agent_execution), "agent_execution");
|
|
409
|
+
const session = declared(builtInModel.byType("session"), "session");
|
|
410
|
+
|
|
411
|
+
/** agent_execution as the model has it, with one line rewritten. */
|
|
412
|
+
function executionWith(relation: string, rewrite: Rewrite): KindDeclaration {
|
|
413
|
+
return throwawayDeclaration({
|
|
414
|
+
kind: ApiResourceKind.agent_execution,
|
|
415
|
+
relations: [...execution.relations].map(([name, current]) =>
|
|
416
|
+
name === relation ? ([name, rewrite] as const) : ([name, current] as const),
|
|
417
|
+
),
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
it.each([
|
|
422
|
+
{
|
|
423
|
+
shape: "a viewer granted on the execution itself",
|
|
424
|
+
child: executionWith(
|
|
425
|
+
"viewer",
|
|
426
|
+
union(direct(objectOf("identity_account")), from("viewer", "session"), computed("owner")),
|
|
427
|
+
),
|
|
428
|
+
violation: "agent_execution#viewer admits a tuple on the child itself",
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
shape: "a can_view that no longer includes the session's",
|
|
432
|
+
child: executionWith("can_view", computed("viewer")),
|
|
433
|
+
violation: "agent_execution#can_view does not include can_view from session",
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
shape: "a can_view reaching a session relation its can_view does not include",
|
|
437
|
+
child: executionWith(
|
|
438
|
+
"can_view",
|
|
439
|
+
union(computed("viewer"), from("can_view", "session"), from("can_delete", "session")),
|
|
440
|
+
),
|
|
441
|
+
violation:
|
|
442
|
+
"agent_execution#can_view reaches session#can_delete, which session#can_view does not include",
|
|
443
|
+
},
|
|
444
|
+
])("$shape", ({ child, violation }) => {
|
|
445
|
+
expect(inheritedWholeViolations(child, session, "session")).toEqual([violation]);
|
|
446
|
+
});
|
|
379
447
|
});
|