@stigmer/server 3.22.1-dev.20260922095755 → 3.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/authorization/authorizer.d.ts +8 -4
- package/dist/authorization/authorizer.d.ts.map +1 -1
- package/dist/authorization/authorizer.js +9 -5
- package/dist/authorization/authorizer.js.map +1 -1
- package/dist/authorization/derived-tuples.d.ts.map +1 -1
- package/dist/authorization/derived-tuples.js +25 -10
- package/dist/authorization/derived-tuples.js.map +1 -1
- package/dist/authorization/evaluator.d.ts +8 -7
- package/dist/authorization/evaluator.d.ts.map +1 -1
- package/dist/authorization/evaluator.js +20 -4
- 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 +9 -5
- package/dist/authorization/list-read-scope.js.map +1 -1
- package/dist/authorization/model/agent.d.ts.map +1 -1
- package/dist/authorization/model/agent.js +5 -4
- package/dist/authorization/model/agent.js.map +1 -1
- package/dist/authorization/model/agent_instance.d.ts.map +1 -1
- package/dist/authorization/model/agent_instance.js +2 -2
- package/dist/authorization/model/agent_instance.js.map +1 -1
- package/dist/authorization/model/mcp_server.d.ts.map +1 -1
- package/dist/authorization/model/mcp_server.js +2 -2
- package/dist/authorization/model/mcp_server.js.map +1 -1
- package/dist/authorization/model/plugin.d.ts.map +1 -1
- package/dist/authorization/model/plugin.js +4 -3
- package/dist/authorization/model/plugin.js.map +1 -1
- package/dist/authorization/model/rewrite.d.ts +10 -8
- package/dist/authorization/model/rewrite.d.ts.map +1 -1
- package/dist/authorization/model/rewrite.js +3 -0
- package/dist/authorization/model/rewrite.js.map +1 -1
- package/dist/authorization/model/skill.d.ts.map +1 -1
- package/dist/authorization/model/skill.js +2 -2
- package/dist/authorization/model/skill.js.map +1 -1
- package/dist/authorization/model/workflow.d.ts.map +1 -1
- package/dist/authorization/model/workflow.js +2 -2
- package/dist/authorization/model/workflow.js.map +1 -1
- package/dist/authorization/model/workflow_instance.d.ts.map +1 -1
- package/dist/authorization/model/workflow_instance.js +2 -2
- package/dist/authorization/model/workflow_instance.js.map +1 -1
- package/dist/authorization/organization-directory.d.ts.map +1 -1
- package/dist/authorization/organization-directory.js +3 -2
- package/dist/authorization/organization-directory.js.map +1 -1
- package/dist/authorization/store-test-kit.d.ts +6 -0
- package/dist/authorization/store-test-kit.d.ts.map +1 -1
- package/dist/authorization/store-test-kit.js +62 -16
- package/dist/authorization/store-test-kit.js.map +1 -1
- package/dist/authorization/tuples.d.ts +32 -13
- package/dist/authorization/tuples.d.ts.map +1 -1
- package/dist/authorization/tuples.js +11 -3
- package/dist/authorization/tuples.js.map +1 -1
- package/dist/domain/agent/controller.d.ts.map +1 -1
- package/dist/domain/agent/controller.js +4 -4
- package/dist/domain/agent/controller.js.map +1 -1
- package/dist/domain/agentchannel/controller.d.ts.map +1 -1
- package/dist/domain/agentchannel/controller.js +1 -3
- package/dist/domain/agentchannel/controller.js.map +1 -1
- package/dist/domain/agentexecution/controller.d.ts.map +1 -1
- package/dist/domain/agentexecution/controller.js +1 -3
- package/dist/domain/agentexecution/controller.js.map +1 -1
- package/dist/domain/agentinstance/controller.d.ts.map +1 -1
- package/dist/domain/agentinstance/controller.js +4 -3
- package/dist/domain/agentinstance/controller.js.map +1 -1
- package/dist/domain/agentshare/constants.d.ts +11 -6
- package/dist/domain/agentshare/constants.d.ts.map +1 -1
- package/dist/domain/agentshare/constants.js +16 -8
- package/dist/domain/agentshare/constants.js.map +1 -1
- package/dist/domain/agentshare/controller.d.ts.map +1 -1
- package/dist/domain/agentshare/controller.js +1 -3
- package/dist/domain/agentshare/controller.js.map +1 -1
- package/dist/domain/agentshare/steps.d.ts +25 -18
- package/dist/domain/agentshare/steps.d.ts.map +1 -1
- package/dist/domain/agentshare/steps.js +142 -41
- package/dist/domain/agentshare/steps.js.map +1 -1
- package/dist/domain/environment/controller.d.ts.map +1 -1
- package/dist/domain/environment/controller.js +3 -2
- package/dist/domain/environment/controller.js.map +1 -1
- package/dist/domain/executioncontext/controller.d.ts.map +1 -1
- package/dist/domain/executioncontext/controller.js +1 -2
- package/dist/domain/executioncontext/controller.js.map +1 -1
- package/dist/domain/mcpserver/controller.d.ts.map +1 -1
- package/dist/domain/mcpserver/controller.js +4 -3
- package/dist/domain/mcpserver/controller.js.map +1 -1
- package/dist/domain/plugin/controller.d.ts.map +1 -1
- package/dist/domain/plugin/controller.js +3 -1
- package/dist/domain/plugin/controller.js.map +1 -1
- package/dist/domain/plugin/push.d.ts +5 -7
- package/dist/domain/plugin/push.d.ts.map +1 -1
- package/dist/domain/plugin/push.js +14 -8
- package/dist/domain/plugin/push.js.map +1 -1
- package/dist/domain/schedule/controller.d.ts.map +1 -1
- package/dist/domain/schedule/controller.js +1 -3
- package/dist/domain/schedule/controller.js.map +1 -1
- package/dist/domain/session/controller.d.ts.map +1 -1
- package/dist/domain/session/controller.js +2 -3
- package/dist/domain/session/controller.js.map +1 -1
- package/dist/domain/skill/controller.d.ts.map +1 -1
- package/dist/domain/skill/controller.js +2 -0
- package/dist/domain/skill/controller.js.map +1 -1
- package/dist/domain/workflow/controller.d.ts.map +1 -1
- package/dist/domain/workflow/controller.js +6 -14
- package/dist/domain/workflow/controller.js.map +1 -1
- package/dist/domain/workflow/registry/data/task-kind-registry.json +1 -1
- package/dist/domain/workflow/validation/task-config-constraints.d.ts +1 -10
- package/dist/domain/workflow/validation/task-config-constraints.d.ts.map +1 -1
- package/dist/domain/workflow/validation/task-config-constraints.js +2 -3
- package/dist/domain/workflow/validation/task-config-constraints.js.map +1 -1
- package/dist/domain/workflowexecution/controller.d.ts.map +1 -1
- package/dist/domain/workflowexecution/controller.js +2 -4
- package/dist/domain/workflowexecution/controller.js.map +1 -1
- package/dist/domain/workflowinstance/controller.d.ts.map +1 -1
- package/dist/domain/workflowinstance/controller.js +4 -3
- package/dist/domain/workflowinstance/controller.js.map +1 -1
- package/dist/extensions/authorization-queries.d.ts +5 -6
- package/dist/extensions/authorization-queries.d.ts.map +1 -1
- package/dist/extensions/list-read-scope.d.ts +1 -1
- package/dist/extensions/resource-authorization.d.ts +2 -3
- package/dist/extensions/resource-authorization.d.ts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -1
- package/dist/pipeline/apiresource-meta.d.ts +3 -12
- package/dist/pipeline/apiresource-meta.d.ts.map +1 -1
- package/dist/pipeline/apiresource-meta.js +10 -21
- package/dist/pipeline/apiresource-meta.js.map +1 -1
- package/dist/pipeline/steps/authorization-tuples.d.ts +3 -9
- package/dist/pipeline/steps/authorization-tuples.d.ts.map +1 -1
- package/dist/pipeline/steps/authorization-tuples.js +11 -9
- package/dist/pipeline/steps/authorization-tuples.js.map +1 -1
- package/dist/pipeline/steps/references.d.ts +20 -178
- package/dist/pipeline/steps/references.d.ts.map +1 -1
- package/dist/pipeline/steps/references.js +43 -344
- package/dist/pipeline/steps/references.js.map +1 -1
- package/dist/pipeline/steps/visibility-gates.d.ts +27 -0
- package/dist/pipeline/steps/visibility-gates.d.ts.map +1 -0
- package/dist/pipeline/steps/visibility-gates.js +139 -0
- package/dist/pipeline/steps/visibility-gates.js.map +1 -0
- package/dist/query/search/controller.js +1 -0
- package/dist/query/search/controller.js.map +1 -1
- package/dist/query/search/criteria.d.ts +5 -1
- package/dist/query/search/criteria.d.ts.map +1 -1
- package/dist/query/search/criteria.js +13 -3
- package/dist/query/search/criteria.js.map +1 -1
- package/dist/query/search/handler.d.ts +4 -5
- package/dist/query/search/handler.d.ts.map +1 -1
- package/dist/query/search/handler.js +6 -7
- package/dist/query/search/handler.js.map +1 -1
- package/dist/query/search/query-store.d.ts.map +1 -1
- package/dist/query/search/query-store.js +2 -0
- package/dist/query/search/query-store.js.map +1 -1
- package/dist/store/interface.d.ts +6 -3
- package/dist/store/interface.d.ts.map +1 -1
- package/dist/store/interface.js.map +1 -1
- package/dist/store/postgres/migrations.d.ts +1 -3
- package/dist/store/postgres/migrations.d.ts.map +1 -1
- package/dist/store/postgres/migrations.js +1 -35
- package/dist/store/postgres/migrations.js.map +1 -1
- package/dist/store/postgres/store.d.ts.map +1 -1
- package/dist/store/postgres/store.js +9 -4
- package/dist/store/postgres/store.js.map +1 -1
- package/dist/store/sqlite/migrations.d.ts +1 -3
- package/dist/store/sqlite/migrations.d.ts.map +1 -1
- package/dist/store/sqlite/migrations.js +1 -36
- package/dist/store/sqlite/migrations.js.map +1 -1
- package/dist/store/sqlite/store.d.ts.map +1 -1
- package/dist/store/sqlite/store.js +8 -4
- package/dist/store/sqlite/store.js.map +1 -1
- package/package.json +6 -6
- package/src/authorization/README.md +26 -24
- package/src/authorization/__tests__/authorizer.test.ts +9 -10
- package/src/authorization/__tests__/derived-tuples.test.ts +11 -4
- package/src/authorization/__tests__/evaluator.test.ts +73 -14
- package/src/authorization/__tests__/facts.test.ts +2 -2
- package/src/authorization/__tests__/fixtures/fga/agent-instance-creation.fga.yaml +185 -0
- package/src/authorization/__tests__/fixtures/fga/blueprint-private-visibility.fga.yaml +98 -6
- package/src/authorization/__tests__/fixtures/fga/cross-org-share-creation.fga.yaml +98 -0
- package/src/authorization/__tests__/fixtures/fga/default-instance-inheritance.fga.yaml +61 -4
- package/src/authorization/__tests__/fixtures/fga/mcp-server-authoring.fga.yaml +143 -0
- package/src/authorization/__tests__/fixtures/fga/org-admin-owner-inheritance.fga.yaml +21 -1
- package/src/authorization/__tests__/fixtures/fga/platform-visibility.fga.yaml +25 -1
- package/src/authorization/__tests__/fixtures/fga/public-visibility-setter.fga.yaml +52 -0
- package/src/authorization/__tests__/fixtures/fga/workflow-execution-sharing.fga.yaml +3 -1
- package/src/authorization/__tests__/list-read-scope.test.ts +17 -18
- package/src/authorization/__tests__/store-tests.test.ts +8 -1
- package/src/authorization/__tests__/tuples.test.ts +11 -12
- package/src/authorization/authorizer.ts +9 -4
- package/src/authorization/derived-tuples.ts +31 -14
- package/src/authorization/evaluator.ts +44 -8
- package/src/authorization/list-read-scope.ts +11 -5
- package/src/authorization/model/agent.ts +5 -2
- package/src/authorization/model/agent_instance.ts +2 -0
- package/src/authorization/model/mcp_server.ts +2 -0
- package/src/authorization/model/plugin.ts +4 -1
- package/src/authorization/model/rewrite.ts +13 -8
- package/src/authorization/model/skill.ts +2 -0
- package/src/authorization/model/workflow.ts +2 -0
- package/src/authorization/model/workflow_instance.ts +2 -0
- package/src/authorization/organization-directory.ts +3 -1
- package/src/authorization/store-test-kit.ts +75 -16
- package/src/authorization/tuples.ts +39 -16
- package/src/domain/agent/__tests__/agent.test.ts +4 -11
- package/src/domain/agent/controller.ts +7 -7
- package/src/domain/agentchannel/__tests__/agentchannel.test.ts +48 -223
- package/src/domain/agentchannel/controller.ts +1 -6
- package/src/domain/agentexecution/controller.ts +1 -6
- package/src/domain/agentinstance/controller.ts +12 -6
- package/src/domain/agentshare/__tests__/agentshare.test.ts +172 -196
- package/src/domain/agentshare/__tests__/create-authorization.test.ts +31 -16
- package/src/domain/agentshare/constants.ts +26 -14
- package/src/domain/agentshare/controller.ts +1 -6
- package/src/domain/agentshare/steps.ts +194 -41
- package/src/domain/environment/controller.ts +8 -5
- package/src/domain/executioncontext/controller.ts +1 -5
- package/src/domain/mcpserver/__tests__/mcpserver.test.ts +28 -115
- package/src/domain/mcpserver/__tests__/oauth-handshake.test.ts +18 -53
- package/src/domain/mcpserver/controller.ts +12 -6
- package/src/domain/plugin/controller.ts +8 -1
- package/src/domain/plugin/push.ts +16 -8
- package/src/domain/schedule/controller.ts +1 -6
- package/src/domain/session/controller.ts +4 -12
- package/src/domain/skill/controller.ts +7 -0
- package/src/domain/workflow/controller.ts +10 -28
- package/src/domain/workflow/registry/data/task-kind-registry.json +1 -1
- package/src/domain/workflow/validation/task-config-constraints.ts +3 -7
- package/src/domain/workflowexecution/controller.ts +2 -7
- package/src/domain/workflowinstance/controller.ts +12 -6
- package/src/extensions/__tests__/built-in-authorization-composed.test.ts +19 -32
- package/src/extensions/__tests__/extension-composition.test.ts +4 -4
- package/src/extensions/__tests__/list-read-scope-composed.test.ts +6 -9
- package/src/extensions/authorization-queries.ts +5 -6
- package/src/extensions/list-read-scope.ts +1 -1
- package/src/extensions/resource-authorization.ts +5 -3
- package/src/index.ts +14 -0
- package/src/pipeline/apiresource-meta.ts +10 -21
- package/src/pipeline/steps/__tests__/authorization-tuples.test.ts +31 -18
- package/src/pipeline/steps/__tests__/visibility-gates.test.ts +187 -0
- package/src/pipeline/steps/authorization-tuples.ts +11 -9
- package/src/pipeline/steps/references.ts +70 -578
- package/src/pipeline/steps/visibility-gates.ts +174 -0
- package/src/query/search/__tests__/criteria.test.ts +4 -0
- package/src/query/search/__tests__/query-store.test.ts +40 -11
- package/src/query/search/controller.ts +1 -0
- package/src/query/search/criteria.ts +14 -0
- package/src/query/search/handler.ts +7 -6
- package/src/query/search/query-store.ts +2 -0
- package/src/store/__tests__/store-contract.ts +51 -6
- package/src/store/interface.ts +6 -3
- package/src/store/postgres/__tests__/migrations.test.ts +6 -187
- package/src/store/postgres/__tests__/store-contract.test.ts +2 -0
- package/src/store/postgres/migrations.ts +1 -48
- package/src/store/postgres/store.ts +11 -4
- package/src/store/sqlite/__tests__/migrations.test.ts +5 -191
- package/src/store/sqlite/migrations.ts +1 -45
- package/src/store/sqlite/store.ts +10 -4
- package/dist/domain/workflow/agent-call-references.d.ts +0 -54
- package/dist/domain/workflow/agent-call-references.d.ts.map +0 -1
- package/dist/domain/workflow/agent-call-references.js +0 -97
- package/dist/domain/workflow/agent-call-references.js.map +0 -1
- package/dist/store/public-visibility-retired.d.ts +0 -67
- package/dist/store/public-visibility-retired.d.ts.map +0 -1
- package/dist/store/public-visibility-retired.js +0 -55
- package/dist/store/public-visibility-retired.js.map +0 -1
- package/src/domain/workflow/__tests__/agent-call-references.test.ts +0 -222
- package/src/domain/workflow/agent-call-references.ts +0 -157
- package/src/pipeline/__tests__/supports-visibility.test.ts +0 -112
- package/src/pipeline/steps/__tests__/references.test.ts +0 -703
- package/src/store/__tests__/public-visibility-retired.test.ts +0 -127
- package/src/store/public-visibility-retired.ts +0 -119
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Pinned copy of stigmer-cloud backend/services/stigmer-server/fga/tests/org-admin-owner-inheritance.fga.yaml
|
|
2
|
-
# as changed by stigmer-cloud#
|
|
2
|
+
# as changed by stigmer-cloud#757 (the OpenFGA store test the cloud runs against its live model).
|
|
3
3
|
# The body below is byte-identical to the source; the cloud re-runs the LIVE
|
|
4
4
|
# file against this evaluator at the re-pin, so a stale copy is caught there.
|
|
5
5
|
# Store tests for org-admin owner inheritance (T08).
|
|
@@ -131,6 +131,8 @@ tests:
|
|
|
131
131
|
check:
|
|
132
132
|
- user: identity_account:ana
|
|
133
133
|
object: agent:carols-bot
|
|
134
|
+
context:
|
|
135
|
+
allow: false
|
|
134
136
|
assertions:
|
|
135
137
|
can_view: true
|
|
136
138
|
can_edit: true
|
|
@@ -167,6 +169,8 @@ tests:
|
|
|
167
169
|
can_grant_access: true
|
|
168
170
|
- user: identity_account:ana
|
|
169
171
|
object: skill:carols-notes
|
|
172
|
+
context:
|
|
173
|
+
allow: false
|
|
170
174
|
assertions:
|
|
171
175
|
can_view: true
|
|
172
176
|
can_edit: true
|
|
@@ -174,6 +178,8 @@ tests:
|
|
|
174
178
|
can_grant_access: true
|
|
175
179
|
- user: identity_account:ana
|
|
176
180
|
object: workflow:carols-flow
|
|
181
|
+
context:
|
|
182
|
+
allow: false
|
|
177
183
|
assertions:
|
|
178
184
|
can_view: true
|
|
179
185
|
can_edit: true
|
|
@@ -191,6 +197,8 @@ tests:
|
|
|
191
197
|
check:
|
|
192
198
|
- user: identity_account:root
|
|
193
199
|
object: agent:carols-bot
|
|
200
|
+
context:
|
|
201
|
+
allow: false
|
|
194
202
|
assertions:
|
|
195
203
|
can_edit: true
|
|
196
204
|
can_delete: true
|
|
@@ -210,6 +218,8 @@ tests:
|
|
|
210
218
|
check:
|
|
211
219
|
- user: identity_account:dave
|
|
212
220
|
object: agent:carols-bot
|
|
221
|
+
context:
|
|
222
|
+
allow: false
|
|
213
223
|
assertions:
|
|
214
224
|
can_view: false
|
|
215
225
|
can_edit: false
|
|
@@ -240,6 +250,8 @@ tests:
|
|
|
240
250
|
check:
|
|
241
251
|
- user: identity_account:zara
|
|
242
252
|
object: agent:carols-bot
|
|
253
|
+
context:
|
|
254
|
+
allow: false
|
|
243
255
|
assertions:
|
|
244
256
|
can_view: false
|
|
245
257
|
can_edit: false
|
|
@@ -254,6 +266,8 @@ tests:
|
|
|
254
266
|
check:
|
|
255
267
|
- user: identity_account:ana
|
|
256
268
|
object: agent_instance:carols-instance
|
|
269
|
+
context:
|
|
270
|
+
allow: false
|
|
257
271
|
assertions:
|
|
258
272
|
can_view: false
|
|
259
273
|
can_edit: false
|
|
@@ -283,6 +297,8 @@ tests:
|
|
|
283
297
|
check:
|
|
284
298
|
- user: identity_account:carol
|
|
285
299
|
object: agent:carols-bot
|
|
300
|
+
context:
|
|
301
|
+
allow: false
|
|
286
302
|
assertions:
|
|
287
303
|
can_view: true
|
|
288
304
|
can_edit: true
|
|
@@ -293,6 +309,8 @@ tests:
|
|
|
293
309
|
list_objects:
|
|
294
310
|
- user: identity_account:ana
|
|
295
311
|
type: agent
|
|
312
|
+
context:
|
|
313
|
+
allow: false
|
|
296
314
|
assertions:
|
|
297
315
|
can_view:
|
|
298
316
|
- agent:carols-bot
|
|
@@ -300,6 +318,8 @@ tests:
|
|
|
300
318
|
- agent:carols-bot
|
|
301
319
|
- user: identity_account:ana
|
|
302
320
|
type: agent_instance
|
|
321
|
+
context:
|
|
322
|
+
allow: false
|
|
303
323
|
assertions:
|
|
304
324
|
can_view: []
|
|
305
325
|
- user: identity_account:ana
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Pinned copy of stigmer-cloud backend/services/stigmer-server/fga/tests/platform-visibility.fga.yaml
|
|
2
|
-
#
|
|
2
|
+
# at commit b87dd1170 (the OpenFGA store test the cloud runs against its live model).
|
|
3
3
|
# The body below is byte-identical to the source; the cloud re-runs the LIVE
|
|
4
4
|
# file against this evaluator at the re-pin, so a stale copy is caught there.
|
|
5
5
|
# Store tests for platform visibility (visibility_platform).
|
|
@@ -102,6 +102,8 @@ tests:
|
|
|
102
102
|
check:
|
|
103
103
|
- user: identity_account:alice
|
|
104
104
|
object: agent:sara
|
|
105
|
+
context:
|
|
106
|
+
allow: false
|
|
105
107
|
assertions:
|
|
106
108
|
can_view: true
|
|
107
109
|
can_execute: true
|
|
@@ -110,6 +112,8 @@ tests:
|
|
|
110
112
|
can_delete: false
|
|
111
113
|
- user: identity_account:bob
|
|
112
114
|
object: agent:sara
|
|
115
|
+
context:
|
|
116
|
+
allow: false
|
|
113
117
|
assertions:
|
|
114
118
|
can_view: true
|
|
115
119
|
can_execute: true
|
|
@@ -118,6 +122,8 @@ tests:
|
|
|
118
122
|
check:
|
|
119
123
|
- user: identity_account:mallory
|
|
120
124
|
object: agent:sara
|
|
125
|
+
context:
|
|
126
|
+
allow: false
|
|
121
127
|
assertions:
|
|
122
128
|
can_view: false
|
|
123
129
|
can_execute: false
|
|
@@ -127,6 +133,8 @@ tests:
|
|
|
127
133
|
check:
|
|
128
134
|
- user: identity_account:priya
|
|
129
135
|
object: agent:sara
|
|
136
|
+
context:
|
|
137
|
+
allow: false
|
|
130
138
|
assertions:
|
|
131
139
|
can_view: true
|
|
132
140
|
can_execute: true
|
|
@@ -136,6 +144,8 @@ tests:
|
|
|
136
144
|
# narrower: platform_user resolves member-from-managed_org.)
|
|
137
145
|
- user: identity_account:veda
|
|
138
146
|
object: agent:sara
|
|
147
|
+
context:
|
|
148
|
+
allow: false
|
|
139
149
|
assertions:
|
|
140
150
|
can_view: true
|
|
141
151
|
can_edit: false
|
|
@@ -144,6 +154,8 @@ tests:
|
|
|
144
154
|
check:
|
|
145
155
|
- user: identity_account:alice
|
|
146
156
|
object: agent:internal-bot
|
|
157
|
+
context:
|
|
158
|
+
allow: false
|
|
147
159
|
assertions:
|
|
148
160
|
can_view: false
|
|
149
161
|
can_execute: false
|
|
@@ -151,6 +163,8 @@ tests:
|
|
|
151
163
|
# without an explicit visibility tuple.
|
|
152
164
|
- user: identity_account:priya
|
|
153
165
|
object: agent:internal-bot
|
|
166
|
+
context:
|
|
167
|
+
allow: false
|
|
154
168
|
assertions:
|
|
155
169
|
can_view: false
|
|
156
170
|
can_execute: false
|
|
@@ -159,6 +173,8 @@ tests:
|
|
|
159
173
|
check:
|
|
160
174
|
- user: identity_account:alice
|
|
161
175
|
object: skill:research
|
|
176
|
+
context:
|
|
177
|
+
allow: false
|
|
162
178
|
assertions:
|
|
163
179
|
can_view: true
|
|
164
180
|
can_use: true
|
|
@@ -168,6 +184,8 @@ tests:
|
|
|
168
184
|
check:
|
|
169
185
|
- user: identity_account:alice
|
|
170
186
|
object: workflow:onboarding
|
|
187
|
+
context:
|
|
188
|
+
allow: false
|
|
171
189
|
assertions:
|
|
172
190
|
can_view: true
|
|
173
191
|
can_execute: true
|
|
@@ -176,6 +194,8 @@ tests:
|
|
|
176
194
|
check:
|
|
177
195
|
- user: identity_account:alice
|
|
178
196
|
object: mcp_server:github
|
|
197
|
+
context:
|
|
198
|
+
allow: false
|
|
179
199
|
assertions:
|
|
180
200
|
can_view: true
|
|
181
201
|
can_use: true
|
|
@@ -185,6 +205,8 @@ tests:
|
|
|
185
205
|
check:
|
|
186
206
|
- user: identity_account:swarup
|
|
187
207
|
object: agent:sara
|
|
208
|
+
context:
|
|
209
|
+
allow: false
|
|
188
210
|
assertions:
|
|
189
211
|
can_view: true
|
|
190
212
|
can_edit: true
|
|
@@ -194,6 +216,8 @@ tests:
|
|
|
194
216
|
list_objects:
|
|
195
217
|
- user: identity_account:alice
|
|
196
218
|
type: agent
|
|
219
|
+
context:
|
|
220
|
+
allow: false
|
|
197
221
|
assertions:
|
|
198
222
|
can_view:
|
|
199
223
|
- agent:sara
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Pinned copy of stigmer-cloud backend/services/stigmer-server/fga/tests/public-visibility-setter.fga.yaml
|
|
2
|
+
# at commit b87dd1170 (the OpenFGA store test the cloud runs against its live model).
|
|
3
|
+
# The body below is byte-identical to the source; the cloud re-runs the LIVE
|
|
4
|
+
# file against this evaluator at the re-pin, so a stale copy is caught there.
|
|
5
|
+
# Store tests for the public-visibility permission (marketplace curation).
|
|
6
|
+
#
|
|
7
|
+
# can_set_public_visibility gates who may set a resource's visibility to
|
|
8
|
+
# PUBLIC — the only level that crosses every org boundary. It derives
|
|
9
|
+
# from the platform operator role: operators (human curators and the
|
|
10
|
+
# seeding machine account) hold it; ordinary tenants — even org owners,
|
|
11
|
+
# who can otherwise fully manage their resources — never do. Enforced by
|
|
12
|
+
# PublicVisibilityEscalationPolicy at both write doors (updateVisibility
|
|
13
|
+
# escalation and create-with-public).
|
|
14
|
+
#
|
|
15
|
+
# Run with:
|
|
16
|
+
# fga model test --tests public-visibility-setter.fga.yaml
|
|
17
|
+
name: public-visibility-setter
|
|
18
|
+
model_file: ../model/fga.mod
|
|
19
|
+
|
|
20
|
+
tuples:
|
|
21
|
+
# The bootstrap machine account is the platform operator
|
|
22
|
+
# (BootstrapIdentitySeeder re-asserts this tuple on every boot).
|
|
23
|
+
- user: identity_account:machine
|
|
24
|
+
relation: operator
|
|
25
|
+
object: platform:stigmer
|
|
26
|
+
|
|
27
|
+
# A human operator granted the same platform role.
|
|
28
|
+
- user: identity_account:opal
|
|
29
|
+
relation: operator
|
|
30
|
+
object: platform:stigmer
|
|
31
|
+
|
|
32
|
+
# A tenant org owner — owns everything in their org, nothing on the
|
|
33
|
+
# platform.
|
|
34
|
+
- user: identity_account:mallory
|
|
35
|
+
relation: owner
|
|
36
|
+
object: organization:acme
|
|
37
|
+
|
|
38
|
+
tests:
|
|
39
|
+
- name: operators may set public visibility, tenants may not
|
|
40
|
+
check:
|
|
41
|
+
- user: identity_account:machine
|
|
42
|
+
object: platform:stigmer
|
|
43
|
+
assertions:
|
|
44
|
+
can_set_public_visibility: true
|
|
45
|
+
- user: identity_account:opal
|
|
46
|
+
object: platform:stigmer
|
|
47
|
+
assertions:
|
|
48
|
+
can_set_public_visibility: true
|
|
49
|
+
- user: identity_account:mallory
|
|
50
|
+
object: platform:stigmer
|
|
51
|
+
assertions:
|
|
52
|
+
can_set_public_visibility: false
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Pinned copy of stigmer-cloud backend/services/stigmer-server/fga/tests/workflow-execution-sharing.fga.yaml
|
|
2
|
-
#
|
|
2
|
+
# at commit b87dd1170 (the OpenFGA store test the cloud runs against its live model).
|
|
3
3
|
# The body below is byte-identical to the source; the cloud re-runs the LIVE
|
|
4
4
|
# file against this evaluator at the re-pin, so a stale copy is caught there.
|
|
5
5
|
# Store tests for workflow_execution sharing and run observability.
|
|
@@ -173,6 +173,8 @@ tests:
|
|
|
173
173
|
# never alice's run history.
|
|
174
174
|
- user: identity_account:vera
|
|
175
175
|
object: workflow_instance:inst1
|
|
176
|
+
context:
|
|
177
|
+
allow: false
|
|
176
178
|
assertions:
|
|
177
179
|
can_view: true
|
|
178
180
|
- user: identity_account:vera
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pins the built-in list read scope on both store drivers as the twin of
|
|
3
3
|
* the cloud's driver (stigmer-cloud iam/list-read-scope.ts): every
|
|
4
|
-
* candidate a list lane offers is answered by `can_view`
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* candidate a list lane offers is answered by `can_view` under the
|
|
5
|
+
* LISTING context (`allow: false`, the public wildcard suppressed) through
|
|
6
|
+
* the same evaluator the Authorizer uses, over tuples derived from the
|
|
7
|
+
* facts the candidate carries — no candidate row is read twice.
|
|
7
8
|
*
|
|
8
9
|
* The adversarial cells come first, because until slice 5's sibling
|
|
9
10
|
* cells this file and the composed proof are the only proof that a
|
|
10
|
-
* member's list is a member's list: the outsider sees nothing (a
|
|
11
|
-
*
|
|
12
|
-
* the org-visible blueprints
|
|
11
|
+
* member's list is a member's list: the outsider sees nothing (a legacy
|
|
12
|
+
* PUBLIC row included); the viewer rung sees the org-visible blueprints
|
|
13
13
|
* and nothing personal; the founder — an admin — sees the organization's
|
|
14
14
|
* blueprints and NOT its members' sessions, keys, environments or
|
|
15
15
|
* memories (the model's own line, now list-visible); an execution is its session's and
|
|
@@ -280,10 +280,9 @@ describe.each(driverFixtures(SEEDED_KINDS))(
|
|
|
280
280
|
await grant(orgRole(VIEWER, "viewer", ORG));
|
|
281
281
|
|
|
282
282
|
// Blueprints: the founder's private and org-visible agents, a
|
|
283
|
-
// member's private one, one
|
|
284
|
-
//
|
|
285
|
-
//
|
|
286
|
-
// stamped by the 3.14 verifiers' raw subject.
|
|
283
|
+
// member's private one, a legacy PUBLIC one (nobody can set public
|
|
284
|
+
// under the built-in posture; a row from before it can carry it),
|
|
285
|
+
// and one stamped by the 3.14 verifiers' raw subject.
|
|
287
286
|
await save("agent", {
|
|
288
287
|
id: "agt_private",
|
|
289
288
|
org: ORG,
|
|
@@ -428,7 +427,7 @@ describe.each(driverFixtures(SEEDED_KINDS))(
|
|
|
428
427
|
});
|
|
429
428
|
|
|
430
429
|
describe("the adversarial cells", () => {
|
|
431
|
-
it("an OUTSIDER lists nothing of the organization's —
|
|
430
|
+
it("an OUTSIDER lists nothing of the organization's — not even the legacy PUBLIC blueprint, which the listing context suppresses", async () => {
|
|
432
431
|
for (const kind of SEEDED_KINDS.filter(
|
|
433
432
|
(k) =>
|
|
434
433
|
k !== ApiResourceKind.iam_policy &&
|
|
@@ -442,13 +441,13 @@ describe.each(driverFixtures(SEEDED_KINDS))(
|
|
|
442
441
|
}
|
|
443
442
|
});
|
|
444
443
|
|
|
445
|
-
it("a VIEWER lists the org-visible
|
|
444
|
+
it("a VIEWER lists the org-visible and the public blueprints and nothing private; a MEMBER adds their own private one", async () => {
|
|
446
445
|
expect(
|
|
447
446
|
await listAs(resolved(VIEWER), ApiResourceKind.agent),
|
|
448
|
-
).toEqual(["agt_org"]);
|
|
447
|
+
).toEqual(["agt_org", "agt_public"]);
|
|
449
448
|
expect(
|
|
450
449
|
await listAs(resolved(MEMBER), ApiResourceKind.agent),
|
|
451
|
-
).toEqual(["agt_org", "agt_member_private"]);
|
|
450
|
+
).toEqual(["agt_org", "agt_member_private", "agt_public"]);
|
|
452
451
|
});
|
|
453
452
|
|
|
454
453
|
it("the FOUNDER and the ADMIN list every blueprint (`owner: … or admin from organization`), including a member's private one", async () => {
|
|
@@ -543,7 +542,7 @@ describe.each(driverFixtures(SEEDED_KINDS))(
|
|
|
543
542
|
const offered = [
|
|
544
543
|
entryOf(
|
|
545
544
|
ApiResourceKind.agent,
|
|
546
|
-
rows.get(ApiResourceKind.agent)!.get("
|
|
545
|
+
rows.get(ApiResourceKind.agent)!.get("agt_public")!,
|
|
547
546
|
),
|
|
548
547
|
entryOf(
|
|
549
548
|
ApiResourceKind.agent,
|
|
@@ -563,15 +562,15 @@ describe.each(driverFixtures(SEEDED_KINDS))(
|
|
|
563
562
|
ApiResourceKind.agent,
|
|
564
563
|
offered,
|
|
565
564
|
);
|
|
566
|
-
expect([...kept]).toEqual(["
|
|
565
|
+
expect([...kept]).toEqual(["agt_public", "agt_org"]);
|
|
567
566
|
// The lane's order is the lane's: the helper filters its own
|
|
568
567
|
// array by the kept set.
|
|
569
568
|
expect(
|
|
570
569
|
await listAs(resolved(MEMBER), ApiResourceKind.agent, [
|
|
571
|
-
rows.get(ApiResourceKind.agent)!.get("
|
|
570
|
+
rows.get(ApiResourceKind.agent)!.get("agt_public")!,
|
|
572
571
|
rows.get(ApiResourceKind.agent)!.get("agt_org")!,
|
|
573
572
|
]),
|
|
574
|
-
).toEqual(["
|
|
573
|
+
).toEqual(["agt_public", "agt_org"]);
|
|
575
574
|
});
|
|
576
575
|
|
|
577
576
|
it("no candidates: the empty set, with no read of any kind", async () => {
|
|
@@ -50,18 +50,22 @@ const FIXTURES = new URL("./fixtures/fga/", import.meta.url);
|
|
|
50
50
|
*/
|
|
51
51
|
const DOCUMENTS = [
|
|
52
52
|
"agent-channel-owner.fga.yaml",
|
|
53
|
+
"agent-instance-creation.fga.yaml",
|
|
53
54
|
"agent-share-owner.fga.yaml",
|
|
54
55
|
"artifact-org-and-owner.fga.yaml",
|
|
55
56
|
"blueprint-private-visibility.fga.yaml",
|
|
56
57
|
"channel-app-owner.fga.yaml",
|
|
57
58
|
"channel-participation.fga.yaml",
|
|
58
59
|
"channel-session-visibility.fga.yaml",
|
|
60
|
+
"cross-org-share-creation.fga.yaml",
|
|
59
61
|
"default-instance-inheritance.fga.yaml",
|
|
62
|
+
"mcp-server-authoring.fga.yaml",
|
|
60
63
|
"memory-subject-only.fga.yaml",
|
|
61
64
|
"org-admin-owner-inheritance.fga.yaml",
|
|
62
65
|
"org-shared-environment.fga.yaml",
|
|
63
66
|
"platform-visibility.fga.yaml",
|
|
64
67
|
"provider-standing-viewer.fga.yaml",
|
|
68
|
+
"public-visibility-setter.fga.yaml",
|
|
65
69
|
"reserved-label-writer.fga.yaml",
|
|
66
70
|
"schedule-owner.fga.yaml",
|
|
67
71
|
"schedule-session-visibility.fga.yaml",
|
|
@@ -98,7 +102,7 @@ const SKIPPED_PINNED: ReadonlyArray<string> = [
|
|
|
98
102
|
"artifact-org-and-owner :: listing reflects org inheritance plus creator ownership :: identity_account:eve <artifact> :: list-objects",
|
|
99
103
|
"artifact-org-and-owner :: listing reflects org inheritance plus creator ownership :: identity_account:victor <artifact> :: list-objects",
|
|
100
104
|
"blueprint-private-visibility :: org admin's listable agents include private ones (T08) :: identity_account:oscar <agent> :: list-objects",
|
|
101
|
-
"blueprint-private-visibility :: org member's listable agents exclude private ones :: identity_account:dave <agent> :: list-objects",
|
|
105
|
+
"blueprint-private-visibility :: org member's listable agents exclude private ones (wildcard suppressed) :: identity_account:dave <agent> :: list-objects",
|
|
102
106
|
"blueprint-private-visibility :: org viewer's listable agents are exactly the viewer-shaped org-visible ones :: identity_account:vera <agent> :: list-objects",
|
|
103
107
|
"blueprint-private-visibility :: owner's listable agents include everything they authored :: identity_account:carol <agent> :: list-objects",
|
|
104
108
|
"channel-app-owner :: listing channel apps reflects ownership and grants, not membership :: identity_account:alice <channel_app> :: list-objects",
|
|
@@ -128,6 +132,9 @@ const SKIPPED_PINNED: ReadonlyArray<string> = [
|
|
|
128
132
|
// undeclared-kind: platform
|
|
129
133
|
"provider-standing-viewer :: operator may view provider standing, tenants may not :: identity_account:mallory platform:stigmer :: undeclared-kind",
|
|
130
134
|
"provider-standing-viewer :: operator may view provider standing, tenants may not :: identity_account:opal platform:stigmer :: undeclared-kind",
|
|
135
|
+
"public-visibility-setter :: operators may set public visibility, tenants may not :: identity_account:machine platform:stigmer :: undeclared-kind",
|
|
136
|
+
"public-visibility-setter :: operators may set public visibility, tenants may not :: identity_account:mallory platform:stigmer :: undeclared-kind",
|
|
137
|
+
"public-visibility-setter :: operators may set public visibility, tenants may not :: identity_account:opal platform:stigmer :: undeclared-kind",
|
|
131
138
|
"reserved-label-writer :: operator may write reserved labels, tenants may not :: identity_account:machine platform:stigmer :: undeclared-kind",
|
|
132
139
|
"reserved-label-writer :: operator may write reserved labels, tenants may not :: identity_account:mallory platform:stigmer :: undeclared-kind",
|
|
133
140
|
// undeclared-subject-type: identity_provider#platform_user
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from "../tuples.js";
|
|
14
14
|
|
|
15
15
|
describe("the string notation", () => {
|
|
16
|
-
it("parses an object and
|
|
16
|
+
it("parses an object, a userset and the wildcard, and an id may carry a colon (issuer subjects do)", () => {
|
|
17
17
|
expect(parseObjectRef("agent:agt_1")).toEqual({
|
|
18
18
|
type: "agent",
|
|
19
19
|
id: "agt_1",
|
|
@@ -27,6 +27,11 @@ describe("the string notation", () => {
|
|
|
27
27
|
object: { type: "organization", id: "acme" },
|
|
28
28
|
relation: "viewer",
|
|
29
29
|
});
|
|
30
|
+
expect(parseSubject("identity_account:*")).toEqual({
|
|
31
|
+
form: "wildcard",
|
|
32
|
+
type: "identity_account",
|
|
33
|
+
condition: undefined,
|
|
34
|
+
});
|
|
30
35
|
expect(parseSubject("identity_account:ida_x")).toEqual({
|
|
31
36
|
form: "object",
|
|
32
37
|
object: { type: "identity_account", id: "ida_x" },
|
|
@@ -38,24 +43,18 @@ describe("the string notation", () => {
|
|
|
38
43
|
expect(() => parseObjectRef(":id")).toThrow("not an object reference");
|
|
39
44
|
});
|
|
40
45
|
|
|
41
|
-
it("
|
|
42
|
-
expect(() => parseSubject("identity_account:*")).toThrow(
|
|
43
|
-
"wildcard subject 'identity_account:*' is not admitted",
|
|
44
|
-
);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it("formats a tuple in the proto's notation", () => {
|
|
46
|
+
it("formats a tuple in the proto's notation, the condition spelled after `with`", () => {
|
|
48
47
|
expect(
|
|
49
48
|
formatTuple({
|
|
50
49
|
object: parseObjectRef("agent:a"),
|
|
51
50
|
relation: "viewer",
|
|
52
51
|
subject: {
|
|
53
|
-
form: "
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
form: "wildcard",
|
|
53
|
+
type: "identity_account",
|
|
54
|
+
condition: "allow_public",
|
|
56
55
|
},
|
|
57
56
|
}),
|
|
58
|
-
).toBe("agent:a#viewer@
|
|
57
|
+
).toBe("agent:a#viewer@identity_account:* with allow_public");
|
|
59
58
|
expect(formatSubject(parseSubject("organization:acme#member"))).toBe(
|
|
60
59
|
"organization:acme#member",
|
|
61
60
|
);
|
|
@@ -17,7 +17,9 @@
|
|
|
17
17
|
* 2. A kind this edition does not serve is `deny` (`kindServedByEdition`,
|
|
18
18
|
* the predicate 2b's `checkMyPermission` arm 1 uses, so the console
|
|
19
19
|
* and the server agree by construction). `platform` is the one that
|
|
20
|
-
*
|
|
20
|
+
* arrives, through `can_set_public_visibility` — under the built-in
|
|
21
|
+
* posture nobody sets public visibility, the model's own reason for
|
|
22
|
+
* making it a platform operator's act.
|
|
21
23
|
* 3. The caller as a person (person.ts) and one derived tuple source for
|
|
22
24
|
* them; the target loaded ONCE through the source's memoised loader,
|
|
23
25
|
* so the read that decides not-found is the read the derivation
|
|
@@ -29,9 +31,11 @@
|
|
|
29
31
|
* member cannot learn which account ids exist. Present or exempt, the
|
|
30
32
|
* row is then evaluated: the cloud asks OpenFGA whether or not the row
|
|
31
33
|
* exists, and so does this driver.
|
|
32
|
-
* 5. `checkRelation
|
|
33
|
-
*
|
|
34
|
-
*
|
|
34
|
+
* 5. `checkRelation` with the point-check context (`allow: true`: a
|
|
35
|
+
* public resource reads for any authenticated caller, the cloud's
|
|
36
|
+
* posture). True is `allow`; false is `deny` with an EMPTY reason, so
|
|
37
|
+
* the method annotation's byte-pinned `error_msg` wins on the wire,
|
|
38
|
+
* exactly the cloud's genuine-denial path.
|
|
35
39
|
*
|
|
36
40
|
* Faults. Everything past the pre-checks runs under one guard: a store
|
|
37
41
|
* fault, a person that names nobody (`personFor`), an evaluation fault
|
|
@@ -175,6 +179,7 @@ export function newBuiltInAuthorizer(deps: BuiltInAuthorizerDeps): Authorizer {
|
|
|
175
179
|
object,
|
|
176
180
|
relation,
|
|
177
181
|
person,
|
|
182
|
+
{ allow: true },
|
|
178
183
|
);
|
|
179
184
|
if (allowed) {
|
|
180
185
|
return { kind: "allow" };
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* - owner SELF: <row>#owner@identity_account:<row id>
|
|
13
13
|
* - creator: <row>#creator@identity_account:<creator stamp> (kinds flagged requires_creator_tuple)
|
|
14
14
|
* - org-viewer: <row>#viewer@organization:<org>#viewer (visibility_org; cloud#257's shape)
|
|
15
|
+
* - public-viewer: <row>#viewer@identity_account:* with allow_public
|
|
15
16
|
* - platform-viewer: none — it fans out over identity providers, a kind this edition does not serve
|
|
16
17
|
*
|
|
17
18
|
* Two facts are this edition's own and are stated here, nowhere else:
|
|
@@ -90,7 +91,11 @@ import type {
|
|
|
90
91
|
Tuple,
|
|
91
92
|
TupleSource,
|
|
92
93
|
} from "./tuples.js";
|
|
93
|
-
import {
|
|
94
|
+
import {
|
|
95
|
+
ACCOUNT_TYPE,
|
|
96
|
+
ALLOW_PUBLIC_CONDITION,
|
|
97
|
+
formatObjectRef,
|
|
98
|
+
} from "./tuples.js";
|
|
94
99
|
|
|
95
100
|
/**
|
|
96
101
|
* Kinds whose `[identity_account]` owner is recorded as an IamPolicy row
|
|
@@ -179,6 +184,12 @@ function visibilitySubject(
|
|
|
179
184
|
object: { type: "organization", id: org },
|
|
180
185
|
relation: "viewer",
|
|
181
186
|
};
|
|
187
|
+
case "public-viewer":
|
|
188
|
+
return {
|
|
189
|
+
form: "wildcard",
|
|
190
|
+
type: ACCOUNT_TYPE,
|
|
191
|
+
condition: ALLOW_PUBLIC_CONDITION,
|
|
192
|
+
};
|
|
182
193
|
case "platform-viewer":
|
|
183
194
|
return undefined;
|
|
184
195
|
default: {
|
|
@@ -350,18 +361,24 @@ function tupleOfRow(row: IamPolicy): Tuple {
|
|
|
350
361
|
type: principal?.kind ?? "",
|
|
351
362
|
id: principal?.id ?? "",
|
|
352
363
|
};
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
364
|
+
let subject: Subject;
|
|
365
|
+
if (principal !== undefined && principal.relation !== "") {
|
|
366
|
+
subject = {
|
|
367
|
+
form: "userset",
|
|
368
|
+
object: principalObject,
|
|
369
|
+
relation: principal.relation,
|
|
370
|
+
};
|
|
371
|
+
} else if (principalObject.id === "*") {
|
|
372
|
+
// A wildcard row carries no condition of its own; the evaluator
|
|
373
|
+
// honours it only where a line admits an unconditioned wildcard,
|
|
374
|
+
// which no line in the model does.
|
|
375
|
+
subject = {
|
|
376
|
+
form: "wildcard",
|
|
377
|
+
type: principalObject.type,
|
|
378
|
+
condition: undefined,
|
|
379
|
+
};
|
|
380
|
+
} else {
|
|
381
|
+
subject = { form: "object", object: principalObject };
|
|
382
|
+
}
|
|
366
383
|
return { object, relation: spec?.relation ?? "", subject };
|
|
367
384
|
}
|