@stigmer/server 3.21.0 → 3.22.1-dev.20260922095755
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 +4 -8
- package/dist/authorization/authorizer.d.ts.map +1 -1
- package/dist/authorization/authorizer.js +5 -9
- package/dist/authorization/authorizer.js.map +1 -1
- package/dist/authorization/derived-tuples.d.ts.map +1 -1
- package/dist/authorization/derived-tuples.js +10 -25
- package/dist/authorization/derived-tuples.js.map +1 -1
- package/dist/authorization/evaluator.d.ts +13 -8
- package/dist/authorization/evaluator.d.ts.map +1 -1
- package/dist/authorization/evaluator.js +4 -20
- 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 +24 -20
- 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 +4 -5
- 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/organization.d.ts.map +1 -1
- package/dist/authorization/model/organization.js +2 -0
- package/dist/authorization/model/organization.js.map +1 -1
- package/dist/authorization/model/plugin.d.ts.map +1 -1
- package/dist/authorization/model/plugin.js +3 -4
- package/dist/authorization/model/plugin.js.map +1 -1
- package/dist/authorization/model/rewrite.d.ts +8 -10
- package/dist/authorization/model/rewrite.d.ts.map +1 -1
- package/dist/authorization/model/rewrite.js +0 -3
- 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 +2 -3
- package/dist/authorization/organization-directory.js.map +1 -1
- package/dist/authorization/store-test-kit.d.ts +0 -6
- package/dist/authorization/store-test-kit.d.ts.map +1 -1
- package/dist/authorization/store-test-kit.js +16 -62
- package/dist/authorization/store-test-kit.js.map +1 -1
- package/dist/authorization/tuples.d.ts +13 -32
- package/dist/authorization/tuples.d.ts.map +1 -1
- package/dist/authorization/tuples.js +3 -11
- package/dist/authorization/tuples.js.map +1 -1
- package/dist/domain/agent/controller.d.ts.map +1 -1
- package/dist/domain/agent/controller.js +6 -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 +9 -2
- package/dist/domain/agentchannel/controller.js.map +1 -1
- package/dist/domain/agentchannel/steps.d.ts +19 -0
- package/dist/domain/agentchannel/steps.d.ts.map +1 -1
- package/dist/domain/agentchannel/steps.js +32 -0
- package/dist/domain/agentchannel/steps.js.map +1 -1
- package/dist/domain/agentexecution/controller.d.ts.map +1 -1
- package/dist/domain/agentexecution/controller.js +3 -1
- package/dist/domain/agentexecution/controller.js.map +1 -1
- package/dist/domain/agentinstance/controller.d.ts +5 -5
- package/dist/domain/agentinstance/controller.d.ts.map +1 -1
- package/dist/domain/agentinstance/controller.js +9 -5
- package/dist/domain/agentinstance/controller.js.map +1 -1
- package/dist/domain/agentinstance/steps.d.ts +43 -8
- package/dist/domain/agentinstance/steps.d.ts.map +1 -1
- package/dist/domain/agentinstance/steps.js +71 -8
- package/dist/domain/agentinstance/steps.js.map +1 -1
- package/dist/domain/agentshare/constants.d.ts +6 -11
- package/dist/domain/agentshare/constants.d.ts.map +1 -1
- package/dist/domain/agentshare/constants.js +8 -16
- 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 +10 -2
- package/dist/domain/agentshare/controller.js.map +1 -1
- package/dist/domain/agentshare/steps.d.ts +61 -10
- package/dist/domain/agentshare/steps.d.ts.map +1 -1
- package/dist/domain/agentshare/steps.js +121 -117
- package/dist/domain/agentshare/steps.js.map +1 -1
- package/dist/domain/artifact/controller.js +21 -25
- package/dist/domain/artifact/controller.js.map +1 -1
- package/dist/domain/channelapp/controller.d.ts.map +1 -1
- package/dist/domain/channelapp/controller.js +2 -0
- package/dist/domain/channelapp/controller.js.map +1 -1
- package/dist/domain/environment/controller.d.ts.map +1 -1
- package/dist/domain/environment/controller.js +4 -3
- 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 +4 -1
- package/dist/domain/executioncontext/controller.js.map +1 -1
- package/dist/domain/executioncontext/steps.d.ts.map +1 -1
- package/dist/domain/executioncontext/steps.js +2 -1
- package/dist/domain/executioncontext/steps.js.map +1 -1
- package/dist/domain/mcpserver/connect.d.ts.map +1 -1
- package/dist/domain/mcpserver/connect.js +8 -7
- package/dist/domain/mcpserver/connect.js.map +1 -1
- package/dist/domain/mcpserver/controller.d.ts.map +1 -1
- package/dist/domain/mcpserver/controller.js +5 -4
- package/dist/domain/mcpserver/controller.js.map +1 -1
- package/dist/domain/mcpserver/start-connect.d.ts.map +1 -1
- package/dist/domain/mcpserver/start-connect.js +8 -7
- package/dist/domain/mcpserver/start-connect.js.map +1 -1
- package/dist/domain/memory/controller.d.ts.map +1 -1
- package/dist/domain/memory/controller.js +6 -1
- package/dist/domain/memory/controller.js.map +1 -1
- package/dist/domain/memory/steps.d.ts +23 -0
- package/dist/domain/memory/steps.d.ts.map +1 -1
- package/dist/domain/memory/steps.js +35 -0
- package/dist/domain/memory/steps.js.map +1 -1
- package/dist/domain/oauthapp/controller.d.ts.map +1 -1
- package/dist/domain/oauthapp/controller.js +2 -0
- package/dist/domain/oauthapp/controller.js.map +1 -1
- package/dist/domain/plugin/controller.d.ts.map +1 -1
- package/dist/domain/plugin/controller.js +6 -27
- package/dist/domain/plugin/controller.js.map +1 -1
- package/dist/domain/plugin/push.d.ts +7 -5
- package/dist/domain/plugin/push.d.ts.map +1 -1
- package/dist/domain/plugin/push.js +8 -14
- 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 +10 -2
- package/dist/domain/schedule/controller.js.map +1 -1
- package/dist/domain/schedule/steps.d.ts +19 -0
- package/dist/domain/schedule/steps.d.ts.map +1 -1
- package/dist/domain/schedule/steps.js +32 -0
- package/dist/domain/schedule/steps.js.map +1 -1
- package/dist/domain/session/controller.d.ts.map +1 -1
- package/dist/domain/session/controller.js +3 -2
- package/dist/domain/session/controller.js.map +1 -1
- package/dist/domain/session/steps.d.ts +8 -9
- package/dist/domain/session/steps.d.ts.map +1 -1
- package/dist/domain/session/steps.js +17 -21
- package/dist/domain/session/steps.js.map +1 -1
- package/dist/domain/skill/controller.d.ts.map +1 -1
- package/dist/domain/skill/controller.js +8 -19
- package/dist/domain/skill/controller.js.map +1 -1
- package/dist/domain/workflow/agent-call-references.d.ts +54 -0
- package/dist/domain/workflow/agent-call-references.d.ts.map +1 -0
- package/dist/domain/workflow/agent-call-references.js +97 -0
- package/dist/domain/workflow/agent-call-references.js.map +1 -0
- package/dist/domain/workflow/controller.d.ts.map +1 -1
- package/dist/domain/workflow/controller.js +44 -113
- 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 +10 -1
- package/dist/domain/workflow/validation/task-config-constraints.d.ts.map +1 -1
- package/dist/domain/workflow/validation/task-config-constraints.js +3 -2
- package/dist/domain/workflow/validation/task-config-constraints.js.map +1 -1
- package/dist/domain/workflow/version-resolution.d.ts +5 -0
- package/dist/domain/workflow/version-resolution.d.ts.map +1 -0
- package/dist/domain/workflow/version-resolution.js +113 -0
- package/dist/domain/workflow/version-resolution.js.map +1 -0
- package/dist/domain/workflowexecution/controller.d.ts.map +1 -1
- package/dist/domain/workflowexecution/controller.js +4 -2
- 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 +9 -5
- package/dist/domain/workflowinstance/controller.js.map +1 -1
- package/dist/domain/workflowinstance/steps.d.ts +20 -0
- package/dist/domain/workflowinstance/steps.d.ts.map +1 -1
- package/dist/domain/workflowinstance/steps.js +39 -0
- package/dist/domain/workflowinstance/steps.js.map +1 -1
- package/dist/extensions/authorization-queries.d.ts +6 -5
- package/dist/extensions/authorization-queries.d.ts.map +1 -1
- package/dist/extensions/identity.d.ts +19 -0
- package/dist/extensions/identity.d.ts.map +1 -1
- package/dist/extensions/identity.js +22 -3
- package/dist/extensions/identity.js.map +1 -1
- package/dist/extensions/list-read-scope.d.ts +42 -8
- package/dist/extensions/list-read-scope.d.ts.map +1 -1
- package/dist/extensions/list-read-scope.js +13 -2
- package/dist/extensions/list-read-scope.js.map +1 -1
- package/dist/extensions/resource-authorization.d.ts +3 -2
- package/dist/extensions/resource-authorization.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/pipeline/apiresource-meta.d.ts +12 -3
- package/dist/pipeline/apiresource-meta.d.ts.map +1 -1
- package/dist/pipeline/apiresource-meta.js +21 -10
- package/dist/pipeline/apiresource-meta.js.map +1 -1
- package/dist/pipeline/steps/authorization-tuples.d.ts +9 -3
- package/dist/pipeline/steps/authorization-tuples.d.ts.map +1 -1
- package/dist/pipeline/steps/authorization-tuples.js +9 -11
- package/dist/pipeline/steps/authorization-tuples.js.map +1 -1
- package/dist/pipeline/steps/authorize-resolved-target.d.ts +88 -0
- package/dist/pipeline/steps/authorize-resolved-target.d.ts.map +1 -0
- package/dist/pipeline/steps/authorize-resolved-target.js +36 -0
- package/dist/pipeline/steps/authorize-resolved-target.js.map +1 -0
- package/dist/pipeline/steps/authorize.d.ts +27 -0
- package/dist/pipeline/steps/authorize.d.ts.map +1 -1
- package/dist/pipeline/steps/authorize.js +49 -10
- package/dist/pipeline/steps/authorize.js.map +1 -1
- package/dist/pipeline/steps/guard-memory-capture.d.ts.map +1 -1
- package/dist/pipeline/steps/guard-memory-capture.js +2 -1
- package/dist/pipeline/steps/guard-memory-capture.js.map +1 -1
- package/dist/pipeline/steps/guard-reserved-labels.d.ts.map +1 -1
- package/dist/pipeline/steps/guard-reserved-labels.js +6 -7
- package/dist/pipeline/steps/guard-reserved-labels.js.map +1 -1
- package/dist/pipeline/steps/helpers.d.ts +9 -5
- package/dist/pipeline/steps/helpers.d.ts.map +1 -1
- package/dist/pipeline/steps/helpers.js +11 -15
- package/dist/pipeline/steps/helpers.js.map +1 -1
- package/dist/pipeline/steps/references.d.ts +178 -20
- package/dist/pipeline/steps/references.d.ts.map +1 -1
- package/dist/pipeline/steps/references.js +344 -43
- package/dist/pipeline/steps/references.js.map +1 -1
- package/dist/pipeline/steps/version-history.d.ts +11 -0
- package/dist/pipeline/steps/version-history.d.ts.map +1 -1
- package/dist/pipeline/steps/version-history.js +26 -0
- package/dist/pipeline/steps/version-history.js.map +1 -1
- package/dist/query/search/controller.js +0 -1
- package/dist/query/search/controller.js.map +1 -1
- package/dist/query/search/criteria.d.ts +1 -5
- package/dist/query/search/criteria.d.ts.map +1 -1
- package/dist/query/search/criteria.js +3 -13
- package/dist/query/search/criteria.js.map +1 -1
- package/dist/query/search/handler.d.ts +5 -4
- package/dist/query/search/handler.d.ts.map +1 -1
- package/dist/query/search/handler.js +7 -6
- 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 +0 -2
- package/dist/query/search/query-store.js.map +1 -1
- package/dist/store/interface.d.ts +3 -6
- package/dist/store/interface.d.ts.map +1 -1
- package/dist/store/interface.js.map +1 -1
- package/dist/store/postgres/migrations.d.ts +3 -1
- package/dist/store/postgres/migrations.d.ts.map +1 -1
- package/dist/store/postgres/migrations.js +35 -1
- 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 +4 -9
- package/dist/store/postgres/store.js.map +1 -1
- package/dist/store/public-visibility-retired.d.ts +67 -0
- package/dist/store/public-visibility-retired.d.ts.map +1 -0
- package/dist/store/public-visibility-retired.js +55 -0
- package/dist/store/public-visibility-retired.js.map +1 -0
- package/dist/store/sqlite/migrations.d.ts +3 -1
- package/dist/store/sqlite/migrations.d.ts.map +1 -1
- package/dist/store/sqlite/migrations.js +36 -1
- 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 +4 -8
- package/dist/store/sqlite/store.js.map +1 -1
- package/package.json +6 -6
- package/src/authorization/README.md +24 -26
- package/src/authorization/__tests__/authorizer.test.ts +10 -9
- package/src/authorization/__tests__/derived-tuples.test.ts +4 -11
- package/src/authorization/__tests__/evaluator.test.ts +14 -73
- package/src/authorization/__tests__/facts.test.ts +2 -2
- package/src/authorization/__tests__/fixtures/fga/blueprint-private-visibility.fga.yaml +6 -98
- package/src/authorization/__tests__/fixtures/fga/default-instance-inheritance.fga.yaml +4 -61
- package/src/authorization/__tests__/fixtures/fga/org-admin-owner-inheritance.fga.yaml +1 -21
- package/src/authorization/__tests__/fixtures/fga/platform-visibility.fga.yaml +1 -25
- package/src/authorization/__tests__/fixtures/fga/workflow-execution-sharing.fga.yaml +1 -3
- package/src/authorization/__tests__/list-read-scope.test.ts +41 -29
- package/src/authorization/__tests__/skip-lane-inventory.test.ts +160 -0
- package/src/authorization/__tests__/store-tests.test.ts +1 -6
- package/src/authorization/__tests__/tuples.test.ts +12 -11
- package/src/authorization/__tests__/wire-permissions.test.ts +5 -5
- package/src/authorization/authorizer.ts +4 -9
- package/src/authorization/derived-tuples.ts +14 -31
- package/src/authorization/evaluator.ts +15 -44
- package/src/authorization/list-read-scope.ts +27 -22
- package/src/authorization/model/__tests__/registry.test.ts +2 -0
- package/src/authorization/model/agent.ts +2 -5
- package/src/authorization/model/agent_instance.ts +0 -2
- package/src/authorization/model/mcp_server.ts +0 -2
- package/src/authorization/model/organization.ts +2 -0
- package/src/authorization/model/plugin.ts +1 -4
- package/src/authorization/model/rewrite.ts +8 -13
- package/src/authorization/model/skill.ts +0 -2
- package/src/authorization/model/workflow.ts +0 -2
- package/src/authorization/model/workflow_instance.ts +0 -2
- package/src/authorization/organization-directory.ts +1 -3
- package/src/authorization/store-test-kit.ts +16 -75
- package/src/authorization/tuples.ts +16 -39
- package/src/domain/agent/__tests__/agent.test.ts +11 -4
- package/src/domain/agent/controller.ts +17 -7
- package/src/domain/agentchannel/__tests__/agentchannel.test.ts +223 -48
- package/src/domain/agentchannel/__tests__/create-authorization.test.ts +58 -0
- package/src/domain/agentchannel/controller.ts +25 -1
- package/src/domain/agentchannel/steps.ts +40 -0
- package/src/domain/agentexecution/controller.ts +6 -1
- package/src/domain/agentinstance/__tests__/create-authorization.composed.test.ts +243 -0
- package/src/domain/agentinstance/__tests__/create-authorization.test.ts +144 -0
- package/src/domain/agentinstance/controller.ts +30 -17
- package/src/domain/agentinstance/steps.ts +82 -8
- package/src/domain/agentshare/__tests__/agentshare.test.ts +196 -172
- package/src/domain/agentshare/__tests__/create-authorization.test.ts +71 -0
- package/src/domain/agentshare/__tests__/member-audience.test.ts +113 -0
- package/src/domain/agentshare/constants.ts +14 -26
- package/src/domain/agentshare/controller.ts +27 -1
- package/src/domain/agentshare/steps.ts +168 -155
- package/src/domain/artifact/controller.ts +18 -22
- package/src/domain/channelapp/controller.ts +10 -0
- package/src/domain/environment/controller.ts +15 -8
- package/src/domain/executioncontext/__tests__/executioncontext.test.ts +1 -0
- package/src/domain/executioncontext/controller.ts +15 -1
- package/src/domain/executioncontext/steps.ts +2 -1
- package/src/domain/mcpserver/__tests__/mcpserver.test.ts +115 -28
- package/src/domain/mcpserver/__tests__/oauth-handshake.test.ts +53 -18
- package/src/domain/mcpserver/connect.ts +11 -10
- package/src/domain/mcpserver/controller.ts +19 -13
- package/src/domain/mcpserver/start-connect.ts +17 -11
- package/src/domain/memory/__tests__/create-authorization.test.ts +59 -0
- package/src/domain/memory/controller.ts +11 -0
- package/src/domain/memory/steps.ts +41 -0
- package/src/domain/oauthapp/controller.ts +10 -0
- package/src/domain/plugin/controller.ts +23 -44
- package/src/domain/plugin/push.ts +8 -16
- package/src/domain/schedule/__tests__/create-authorization.test.ts +58 -0
- package/src/domain/schedule/controller.ts +26 -1
- package/src/domain/schedule/steps.ts +45 -2
- package/src/domain/session/controller.ts +12 -4
- package/src/domain/session/steps.ts +24 -32
- package/src/domain/skill/controller.ts +24 -30
- package/src/domain/workflow/__tests__/agent-call-references.test.ts +222 -0
- package/src/domain/workflow/agent-call-references.ts +157 -0
- package/src/domain/workflow/controller.ts +86 -155
- package/src/domain/workflow/registry/data/task-kind-registry.json +1 -1
- package/src/domain/workflow/validation/task-config-constraints.ts +7 -3
- package/src/domain/workflow/version-resolution.ts +166 -0
- package/src/domain/workflowexecution/controller.ts +7 -2
- package/src/domain/workflowinstance/__tests__/create-authorization.test.ts +109 -0
- package/src/domain/workflowinstance/controller.ts +25 -12
- package/src/domain/workflowinstance/steps.ts +49 -1
- package/src/extensions/__tests__/built-in-authorization-composed.test.ts +32 -19
- package/src/extensions/__tests__/composed-support.ts +38 -4
- package/src/extensions/__tests__/extension-composition.test.ts +4 -4
- package/src/extensions/__tests__/identity.test.ts +44 -1
- package/src/extensions/__tests__/list-read-scope-composed.test.ts +96 -7
- package/src/extensions/__tests__/list-read-scope.test.ts +63 -0
- package/src/extensions/authorization-queries.ts +6 -5
- package/src/extensions/identity.ts +23 -5
- package/src/extensions/list-read-scope.ts +48 -8
- package/src/extensions/resource-authorization.ts +3 -5
- package/src/index.ts +0 -1
- package/src/pipeline/__tests__/supports-visibility.test.ts +112 -0
- package/src/pipeline/apiresource-meta.ts +21 -10
- package/src/pipeline/steps/__tests__/authorization-tuples.test.ts +18 -31
- package/src/pipeline/steps/__tests__/authorize-resolved-target.test.ts +285 -0
- package/src/pipeline/steps/__tests__/references.test.ts +703 -0
- package/src/pipeline/steps/authorization-tuples.ts +9 -11
- package/src/pipeline/steps/authorize-resolved-target.ts +129 -0
- package/src/pipeline/steps/authorize.ts +70 -13
- package/src/pipeline/steps/guard-memory-capture.ts +2 -1
- package/src/pipeline/steps/guard-reserved-labels.ts +6 -9
- package/src/pipeline/steps/helpers.ts +11 -15
- package/src/pipeline/steps/references.ts +578 -70
- package/src/pipeline/steps/version-history.ts +34 -0
- package/src/query/search/__tests__/criteria.test.ts +0 -4
- package/src/query/search/__tests__/query-store.test.ts +11 -40
- package/src/query/search/controller.ts +0 -1
- package/src/query/search/criteria.ts +0 -14
- package/src/query/search/handler.ts +6 -7
- package/src/query/search/query-store.ts +0 -2
- package/src/store/__tests__/public-visibility-retired.test.ts +127 -0
- package/src/store/__tests__/store-contract.ts +6 -51
- package/src/store/interface.ts +3 -6
- package/src/store/postgres/__tests__/migrations.test.ts +187 -6
- package/src/store/postgres/__tests__/store-contract.test.ts +0 -2
- package/src/store/postgres/migrations.ts +48 -1
- package/src/store/postgres/store.ts +4 -11
- package/src/store/public-visibility-retired.ts +119 -0
- package/src/store/sqlite/__tests__/migrations.test.ts +191 -5
- package/src/store/sqlite/migrations.ts +45 -1
- package/src/store/sqlite/store.ts +4 -10
- package/dist/pipeline/steps/visibility-gates.d.ts +0 -27
- package/dist/pipeline/steps/visibility-gates.d.ts.map +0 -1
- package/dist/pipeline/steps/visibility-gates.js +0 -139
- package/dist/pipeline/steps/visibility-gates.js.map +0 -1
- package/src/authorization/__tests__/fixtures/fga/cross-org-share-creation.fga.yaml +0 -98
- package/src/authorization/__tests__/fixtures/fga/public-visibility-setter.fga.yaml +0 -52
- package/src/pipeline/steps/__tests__/visibility-gates.test.ts +0 -187
- package/src/pipeline/steps/visibility-gates.ts +0 -174
|
@@ -53,11 +53,11 @@ import type { PipelineStep } from "../../pipeline/pipeline.js";
|
|
|
53
53
|
import { callerIdentityOf } from "../../pipeline/interceptors/auth.js";
|
|
54
54
|
import { RequestContext } from "../../pipeline/request-context.js";
|
|
55
55
|
import { newAuthorizeStep } from "../../pipeline/steps/authorize.js";
|
|
56
|
-
import { newGuardReservedLabelsStep } from "../../pipeline/steps/guard-reserved-labels.js";
|
|
57
56
|
import {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
} from "../../pipeline/steps/
|
|
57
|
+
loadedTargetAsMethod,
|
|
58
|
+
newAuthorizeResolvedTargetStep,
|
|
59
|
+
} from "../../pipeline/steps/authorize-resolved-target.js";
|
|
60
|
+
import { newGuardReservedLabelsStep } from "../../pipeline/steps/guard-reserved-labels.js";
|
|
61
61
|
import {
|
|
62
62
|
newBuildNewStateStep,
|
|
63
63
|
setAuditFieldsForUpdate,
|
|
@@ -87,7 +87,10 @@ import {
|
|
|
87
87
|
TARGET_RESOURCE_KEY,
|
|
88
88
|
newLoadTargetStep,
|
|
89
89
|
} from "../../pipeline/steps/load-target.js";
|
|
90
|
-
import {
|
|
90
|
+
import {
|
|
91
|
+
newNormalizeReferencesStep,
|
|
92
|
+
newValidateReferencesStep,
|
|
93
|
+
} from "../../pipeline/steps/references.js";
|
|
91
94
|
import {
|
|
92
95
|
newCleanupIamPoliciesStep,
|
|
93
96
|
newCreateAuthorizationTuplesStep,
|
|
@@ -108,6 +111,7 @@ import {
|
|
|
108
111
|
newRejectDefaultWorkflowInstanceVisibilityUpdateStep,
|
|
109
112
|
newValidateInstanceUpdateStep,
|
|
110
113
|
newValidateSameOrgBusinessRuleStep,
|
|
114
|
+
resolveWorkflowInstanceCreateTargets,
|
|
111
115
|
} from "./steps.js";
|
|
112
116
|
import type { ParentWorkflowLoaderProvider } from "./steps.js";
|
|
113
117
|
|
|
@@ -182,12 +186,20 @@ async function createInstance(
|
|
|
182
186
|
.addStep(newValidateVisibilityStep())
|
|
183
187
|
.addStep(newResolveSlugStep())
|
|
184
188
|
.addStep(newLoadParentWorkflowStep(deps.parentWorkflowLoader, deps.logger))
|
|
189
|
+
// Before the same-org rule, whose refusal names the parent's organization
|
|
190
|
+
// (steps.ts, resolveWorkflowInstanceCreateTargets).
|
|
191
|
+
.addStep(
|
|
192
|
+
newAuthorizeResolvedTargetStep(
|
|
193
|
+
deps.authorizer,
|
|
194
|
+
resolveWorkflowInstanceCreateTargets,
|
|
195
|
+
),
|
|
196
|
+
)
|
|
185
197
|
.addStep(newValidateSameOrgBusinessRuleStep(deps.logger))
|
|
186
198
|
.addStep(newCheckDuplicateStep(deps.store))
|
|
187
199
|
.addStep(newBuildNewStateStep())
|
|
188
|
-
.addStep(newGuardPublicVisibilityStep(deps.authorizer))
|
|
189
200
|
.addStep(newGuardReservedLabelsStep(deps.authorizer))
|
|
190
201
|
.addStep(newNormalizeReferencesStep())
|
|
202
|
+
.addStep(newValidateReferencesStep(deps.store))
|
|
191
203
|
.addStep(newPersistStep(deps.store))
|
|
192
204
|
.addStep(
|
|
193
205
|
newCreateAuthorizationTuplesStep(
|
|
@@ -236,6 +248,7 @@ async function update(
|
|
|
236
248
|
.addStep(newBuildUpdateStateStep())
|
|
237
249
|
.addStep(newGuardReservedLabelsStep(deps.authorizer))
|
|
238
250
|
.addStep(newNormalizeReferencesStep())
|
|
251
|
+
.addStep(newValidateReferencesStep(deps.store))
|
|
239
252
|
.addStep(newPersistStep(deps.store))
|
|
240
253
|
.addStep(
|
|
241
254
|
newIndexSearchStep(
|
|
@@ -394,12 +407,6 @@ async function updateVisibility(
|
|
|
394
407
|
),
|
|
395
408
|
)
|
|
396
409
|
.addStep(newValidateVisibilityUpdateStep())
|
|
397
|
-
.addStep(
|
|
398
|
-
newAuthorizeVisibilityTransitionStep(
|
|
399
|
-
UPDATE_VISIBILITY_INSTANCE_KEY,
|
|
400
|
-
deps.authorizer,
|
|
401
|
-
),
|
|
402
|
-
)
|
|
403
410
|
.addStep(newSetInstanceVisibilityStep())
|
|
404
411
|
.addStep(
|
|
405
412
|
newPersistInstanceStep(
|
|
@@ -707,6 +714,12 @@ async function getByReference(
|
|
|
707
714
|
)
|
|
708
715
|
.addStep(newValidateProtoStep())
|
|
709
716
|
.addStep(newLoadByReferenceStep(deps.store, WorkflowInstanceSchema))
|
|
717
|
+
.addStep(
|
|
718
|
+
newAuthorizeResolvedTargetStep(
|
|
719
|
+
deps.authorizer,
|
|
720
|
+
loadedTargetAsMethod(WorkflowInstanceQueryController.method.get),
|
|
721
|
+
),
|
|
722
|
+
)
|
|
710
723
|
.build()
|
|
711
724
|
.execute(reqCtx);
|
|
712
725
|
return reqCtx.get(TARGET_RESOURCE_KEY) as WorkflowInstance;
|
|
@@ -16,8 +16,10 @@ import type { Workflow } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/ap
|
|
|
16
16
|
import { WorkflowInstanceSchema } from "@stigmer/protos/ai/stigmer/agentic/workflowinstance/v1/api_pb";
|
|
17
17
|
import type { WorkflowInstance } from "@stigmer/protos/ai/stigmer/agentic/workflowinstance/v1/api_pb";
|
|
18
18
|
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
19
|
+
import { IamPermission } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
|
|
19
20
|
|
|
20
21
|
import type { Logger } from "../../boot/logger.js";
|
|
22
|
+
import { isServerComposedRequest } from "../../extensions/identity.js";
|
|
21
23
|
import { isDefaultInstance } from "../../pipeline/apiresource-labels.js";
|
|
22
24
|
import {
|
|
23
25
|
failedPreconditionError,
|
|
@@ -27,6 +29,7 @@ import {
|
|
|
27
29
|
} from "../../pipeline/errors.js";
|
|
28
30
|
import type { PipelineStep } from "../../pipeline/pipeline.js";
|
|
29
31
|
import type { RequestContext } from "../../pipeline/request-context.js";
|
|
32
|
+
import type { AuthorizationTarget } from "../../pipeline/steps/authorize.js";
|
|
30
33
|
import { EXISTING_RESOURCE_KEY } from "../../pipeline/steps/load-existing.js";
|
|
31
34
|
import { rejectDefaultInstanceVisibilityUpdate } from "../../pipeline/steps/validate-visibility.js";
|
|
32
35
|
import { ResourceNotFoundError } from "../../store/interface.js";
|
|
@@ -72,7 +75,8 @@ export function newLoadParentWorkflowStep(
|
|
|
72
75
|
parent = await loaderProvider().get(workflowId);
|
|
73
76
|
} catch (error) {
|
|
74
77
|
logger.warn("Parent workflow not found", {
|
|
75
|
-
error:
|
|
78
|
+
error:
|
|
79
|
+
error instanceof ConnectError ? error.rawMessage : String(error),
|
|
76
80
|
workflowId,
|
|
77
81
|
});
|
|
78
82
|
throw notFoundError("Workflow", workflowId);
|
|
@@ -83,6 +87,50 @@ export function newLoadParentWorkflowStep(
|
|
|
83
87
|
};
|
|
84
88
|
}
|
|
85
89
|
|
|
90
|
+
/** The deny copy of the parent's bar, the Java WorkflowInstanceCreateHandler's. */
|
|
91
|
+
export const WORKFLOW_INSTANCE_PARENT_DENIED_MESSAGE =
|
|
92
|
+
"You don't have permission to create instances of this workflow";
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The create lane's authorization question, for AuthorizeResolvedTarget
|
|
96
|
+
* after LoadParentWorkflow and BEFORE ValidateSameOrgBusinessRule (whose
|
|
97
|
+
* refusal names the parent's organization, which a refused caller should
|
|
98
|
+
* not learn). The RPC is is_skip_authorization because its target is the
|
|
99
|
+
* PARENT, loaded from spec.workflow_id, not a request field.
|
|
100
|
+
*
|
|
101
|
+
* One question: may the caller run this workflow (can_execute on the
|
|
102
|
+
* parent, the relation the run gate asks; the same-org rule already binds
|
|
103
|
+
* the instance's organization to the workflow's, so there is no separate
|
|
104
|
+
* organization bar). A DEFAULT instance asks nothing, for the reason its
|
|
105
|
+
* agent twin gives (agentinstance/steps.ts): it is the workflow's row,
|
|
106
|
+
* composed by the server for the run's human, keyed on the in-process
|
|
107
|
+
* origin, the reserved label and the parent's organization together.
|
|
108
|
+
*/
|
|
109
|
+
export function resolveWorkflowInstanceCreateTargets(
|
|
110
|
+
ctx: RequestContext<InstanceDesc>,
|
|
111
|
+
): ReadonlyArray<AuthorizationTarget> {
|
|
112
|
+
const parent = ctx.get(PARENT_WORKFLOW_KEY) as Workflow | undefined;
|
|
113
|
+
if (parent === undefined) {
|
|
114
|
+
throw new Error("parent workflow not found in context");
|
|
115
|
+
}
|
|
116
|
+
const metadata = ctx.newState.metadata;
|
|
117
|
+
if (
|
|
118
|
+
isDefaultInstance(metadata) &&
|
|
119
|
+
isServerComposedRequest(ctx.callerIdentity) &&
|
|
120
|
+
(metadata?.org ?? "") === (parent.metadata?.org ?? "")
|
|
121
|
+
) {
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
return [
|
|
125
|
+
{
|
|
126
|
+
permission: IamPermission.can_execute,
|
|
127
|
+
resourceKind: ApiResourceKind.workflow,
|
|
128
|
+
resourceId: parent.metadata?.id ?? "",
|
|
129
|
+
deniedMessage: WORKFLOW_INSTANCE_PARENT_DENIED_MESSAGE,
|
|
130
|
+
},
|
|
131
|
+
];
|
|
132
|
+
}
|
|
133
|
+
|
|
86
134
|
/**
|
|
87
135
|
* ValidateSameOrgBusinessRule — create.go: instances must live in the same
|
|
88
136
|
* organization as the parent workflow; cross-org instances could leak
|
|
@@ -16,8 +16,9 @@
|
|
|
16
16
|
* outsider on an EXISTING resource hears PERMISSION_DENIED, the
|
|
17
17
|
* cloud's answer; `findMyOrganizations` lists what a person may view
|
|
18
18
|
* and `find` refuses enumeration; `checkMyPermission` tells the truth;
|
|
19
|
-
*
|
|
20
|
-
*
|
|
19
|
+
* the retired public level is refused for the founder too, as an
|
|
20
|
+
* invalid level and not a permission question; an unprovisioned
|
|
21
|
+
* subject writes nothing until provisioned. The LIST lanes are a member's
|
|
21
22
|
* lists: an outsider lists nothing of an organization they hold no
|
|
22
23
|
* row in; environments and API keys are their owner's even to the
|
|
23
24
|
* organization's owner; the library (a search) omits a private agent
|
|
@@ -26,8 +27,9 @@
|
|
|
26
27
|
* provisioned.
|
|
27
28
|
* - trusted-local: the permissive default stays (one caller,
|
|
28
29
|
* nothing to separate; the laptop's rows are stamped `"system"`):
|
|
29
|
-
* tokenless reads
|
|
30
|
-
*
|
|
30
|
+
* tokenless reads and organization enumeration keep working, and
|
|
31
|
+
* the retired public level is refused here exactly as it is under
|
|
32
|
+
* every other posture (the level is gone, not gated).
|
|
31
33
|
* - a unit's own Authorizer: nothing built in is installed; the unit's
|
|
32
34
|
* answer stands even against the founder.
|
|
33
35
|
*
|
|
@@ -80,7 +82,6 @@ import { loadConfig } from "../../boot/config.js";
|
|
|
80
82
|
import { composeServer } from "../../boot/compose.js";
|
|
81
83
|
import type { ComposedServer } from "../../boot/compose.js";
|
|
82
84
|
import { metadataOf } from "../../pipeline/steps/shapes.js";
|
|
83
|
-
import { PUBLIC_VISIBILITY_DENY_MESSAGE } from "../../pipeline/steps/visibility-gates.js";
|
|
84
85
|
import type { Store } from "../../store/interface.js";
|
|
85
86
|
import type { Authorizer } from "../authorizer.js";
|
|
86
87
|
import type { ListReadScope } from "../list-read-scope.js";
|
|
@@ -132,6 +133,15 @@ function organizationInput(slug: string) {
|
|
|
132
133
|
};
|
|
133
134
|
}
|
|
134
135
|
|
|
136
|
+
/**
|
|
137
|
+
* The one sentence for a level a kind does not support, as the agent chain
|
|
138
|
+
* answers it (pipeline/steps/validate-visibility.ts builds it from the
|
|
139
|
+
* kind's config); pinned as bytes because clients render it.
|
|
140
|
+
*/
|
|
141
|
+
const PUBLIC_LEVEL_REFUSED_FOR_AGENT =
|
|
142
|
+
"agent resources cannot be set to visibility_public. " +
|
|
143
|
+
"Supported visibility levels: visibility_private, visibility_org, visibility_platform.";
|
|
144
|
+
|
|
135
145
|
function agentInput(
|
|
136
146
|
name: string,
|
|
137
147
|
visibility: ApiResourceVisibility,
|
|
@@ -377,14 +387,14 @@ describe("built-in authorizer (composed server, OIDC with no unit Authorizer: th
|
|
|
377
387
|
);
|
|
378
388
|
});
|
|
379
389
|
|
|
380
|
-
it("
|
|
390
|
+
it("the retired public level is refused for the founder as an invalid level, before any permission question", async () => {
|
|
381
391
|
const failure = await failureOf(
|
|
382
392
|
createClient(AgentCommandController, asFounder()).create(
|
|
383
393
|
agentInput("Public Agent", ApiResourceVisibility.visibility_public),
|
|
384
394
|
),
|
|
385
395
|
);
|
|
386
|
-
expect(failure.code).toBe(Code.
|
|
387
|
-
expect(failure.rawMessage).toBe(
|
|
396
|
+
expect(failure.code).toBe(Code.InvalidArgument);
|
|
397
|
+
expect(failure.rawMessage).toBe(PUBLIC_LEVEL_REFUSED_FOR_AGENT);
|
|
388
398
|
});
|
|
389
399
|
|
|
390
400
|
describe("the list lanes (the built-in ListReadScope)", () => {
|
|
@@ -595,20 +605,18 @@ describe("built-in authorizer (composed server, trusted-local: the permissive de
|
|
|
595
605
|
expect(read.metadata?.id).toBe(created.metadata!.id);
|
|
596
606
|
});
|
|
597
607
|
|
|
598
|
-
it("the laptop keeps
|
|
608
|
+
it("the laptop keeps organization enumeration and refuses the retired public level like every posture — the level is gone, not gated", async () => {
|
|
599
609
|
const anonymous = transportFor(port);
|
|
600
|
-
const
|
|
601
|
-
AgentCommandController,
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
ApiResourceVisibility.visibility_public,
|
|
610
|
+
const failure = await failureOf(
|
|
611
|
+
createClient(AgentCommandController, anonymous).create(
|
|
612
|
+
agentInput(
|
|
613
|
+
"Public Laptop Agent",
|
|
614
|
+
ApiResourceVisibility.visibility_public,
|
|
615
|
+
),
|
|
607
616
|
),
|
|
608
617
|
);
|
|
609
|
-
expect(
|
|
610
|
-
|
|
611
|
-
);
|
|
618
|
+
expect(failure.code).toBe(Code.InvalidArgument);
|
|
619
|
+
expect(failure.rawMessage).toBe(PUBLIC_LEVEL_REFUSED_FOR_AGENT);
|
|
612
620
|
const all = await createClient(OrganizationQueryController, anonymous).find(
|
|
613
621
|
{ org: ORG },
|
|
614
622
|
);
|
|
@@ -617,6 +625,11 @@ describe("built-in authorizer (composed server, trusted-local: the permissive de
|
|
|
617
625
|
|
|
618
626
|
it("the laptop's lists are the full scan — no scope composed, the rows stamped `system` included", async () => {
|
|
619
627
|
const anonymous = transportFor(port);
|
|
628
|
+
// A second agent beside the private one the earlier test created, so
|
|
629
|
+
// the search below proves a scan and not a single-row coincidence.
|
|
630
|
+
await createClient(AgentCommandController, anonymous).create(
|
|
631
|
+
agentInput("Second Laptop Agent", ApiResourceVisibility.visibility_org),
|
|
632
|
+
);
|
|
620
633
|
const environments = createClient(EnvironmentCommandController, anonymous);
|
|
621
634
|
await environments.create(environmentInput("laptop env one"));
|
|
622
635
|
await environments.create(environmentInput("laptop env two"));
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
import path from "node:path";
|
|
25
25
|
|
|
26
|
-
import type { DescService } from "@bufbuild/protobuf";
|
|
26
|
+
import type { DescMethod, DescService } from "@bufbuild/protobuf";
|
|
27
27
|
import type {
|
|
28
28
|
ConnectRouter,
|
|
29
29
|
Interceptor,
|
|
@@ -94,11 +94,30 @@ export function bearer(token: string): Interceptor {
|
|
|
94
94
|
export function servedServices(
|
|
95
95
|
routes: (router: ConnectRouter) => void,
|
|
96
96
|
): DescService[] {
|
|
97
|
-
|
|
97
|
+
return servedRegistrations(routes).map((r) => r.service);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** One `router.service(desc, implementation)` registration, as replayed. */
|
|
101
|
+
export interface ServedRegistration {
|
|
102
|
+
readonly service: DescService;
|
|
103
|
+
readonly implementation: Readonly<Record<string, unknown>>;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The registrations themselves, with each service's implementation map,
|
|
108
|
+
* for a reader that must know which of a service's methods are actually
|
|
109
|
+
* served: a partially implemented service (a method registered only when
|
|
110
|
+
* a composed capability carries it) lists the method on its descriptor
|
|
111
|
+
* and omits it from the map, and ConnectRPC answers UNIMPLEMENTED for it.
|
|
112
|
+
*/
|
|
113
|
+
export function servedRegistrations(
|
|
114
|
+
routes: (router: ConnectRouter) => void,
|
|
115
|
+
): ServedRegistration[] {
|
|
116
|
+
const served: ServedRegistration[] = [];
|
|
98
117
|
const recorder = {
|
|
99
118
|
handlers: [],
|
|
100
|
-
service(desc: DescService) {
|
|
101
|
-
served.push(desc);
|
|
119
|
+
service(desc: DescService, implementation: Record<string, unknown>) {
|
|
120
|
+
served.push({ service: desc, implementation });
|
|
102
121
|
return recorder;
|
|
103
122
|
},
|
|
104
123
|
rpc() {
|
|
@@ -109,6 +128,21 @@ export function servedServices(
|
|
|
109
128
|
return served;
|
|
110
129
|
}
|
|
111
130
|
|
|
131
|
+
/** The methods a composed server actually serves: on a registered service AND in its implementation map. */
|
|
132
|
+
export function servedMethods(
|
|
133
|
+
routes: (router: ConnectRouter) => void,
|
|
134
|
+
): DescMethod[] {
|
|
135
|
+
const methods: DescMethod[] = [];
|
|
136
|
+
for (const { service, implementation } of servedRegistrations(routes)) {
|
|
137
|
+
for (const method of service.methods) {
|
|
138
|
+
if (method.localName in implementation) {
|
|
139
|
+
methods.push(method);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return methods;
|
|
144
|
+
}
|
|
145
|
+
|
|
112
146
|
/** A gRPC transport to the composed server on `port`, presenting `token` when given. */
|
|
113
147
|
export function transportFor(port: number, token?: string): Transport {
|
|
114
148
|
return createGrpcTransport({
|
|
@@ -1074,7 +1074,7 @@ describe("extension composition (C2 tuple lifecycle + organization directory)",
|
|
|
1074
1074
|
});
|
|
1075
1075
|
});
|
|
1076
1076
|
|
|
1077
|
-
it("updateVisibility fires the set-diff transition (org→
|
|
1077
|
+
it("updateVisibility fires the set-diff transition (org→private deletes the org shape)", async () => {
|
|
1078
1078
|
const command = createClient(AgentCommandController, portTransport);
|
|
1079
1079
|
const agent = await command.create(
|
|
1080
1080
|
create(AgentSchema, {
|
|
@@ -1091,12 +1091,12 @@ describe("extension composition (C2 tuple lifecycle + organization directory)",
|
|
|
1091
1091
|
visibilityEvents.length = 0;
|
|
1092
1092
|
await command.updateVisibility({
|
|
1093
1093
|
resourceId: agent.metadata?.id ?? "",
|
|
1094
|
-
visibility: ApiResourceVisibility.
|
|
1094
|
+
visibility: ApiResourceVisibility.visibility_private,
|
|
1095
1095
|
});
|
|
1096
1096
|
expect(visibilityEvents).toHaveLength(1);
|
|
1097
1097
|
const event = visibilityEvents[0]!;
|
|
1098
|
-
expect(event.shapesToCreate).toEqual([
|
|
1099
|
-
expect(event.shapesToDelete).toEqual([]);
|
|
1098
|
+
expect(event.shapesToCreate).toEqual([]);
|
|
1099
|
+
expect(event.shapesToDelete).toEqual(["org-viewer"]);
|
|
1100
1100
|
});
|
|
1101
1101
|
|
|
1102
1102
|
it("delete fires the cleanup event; a cleanup failure never fails the delete", async () => {
|
|
@@ -6,10 +6,20 @@
|
|
|
6
6
|
* `create` RPC's admission rule (2a A7) and the three IamPolicy system
|
|
7
7
|
* RPCs' (Q-OR-7). A wire `user`, `runner` or a composition's own class
|
|
8
8
|
* (guest) is never one.
|
|
9
|
+
*
|
|
10
|
+
* Also pins `isServerComposedRequest`, the narrower predicate the label
|
|
11
|
+
* guard, the memory-capture gate, the execution-context create check and
|
|
12
|
+
* the default-instance arm of the instance creates share: `internal` or an
|
|
13
|
+
* in-process origin, and never a wire `machine` account — the one row
|
|
14
|
+
* where the two predicates part, pinned so a step trusting server-composed
|
|
15
|
+
* state can never be widened to the wire by picking the wrong one.
|
|
9
16
|
*/
|
|
10
17
|
import { describe, expect, it } from "vitest";
|
|
11
18
|
|
|
12
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
isPlatformPipelineCaller,
|
|
21
|
+
isServerComposedRequest,
|
|
22
|
+
} from "../identity.js";
|
|
13
23
|
import type { CallerIdentity } from "../identity.js";
|
|
14
24
|
|
|
15
25
|
function caller(fields: Partial<CallerIdentity>): CallerIdentity {
|
|
@@ -60,3 +70,36 @@ describe("isPlatformPipelineCaller", () => {
|
|
|
60
70
|
);
|
|
61
71
|
});
|
|
62
72
|
});
|
|
73
|
+
|
|
74
|
+
describe("isServerComposedRequest", () => {
|
|
75
|
+
it("is true for the internal class and for any identity that entered in-process", () => {
|
|
76
|
+
expect(isServerComposedRequest(caller({ callerClass: "internal" }))).toBe(
|
|
77
|
+
true,
|
|
78
|
+
);
|
|
79
|
+
expect(
|
|
80
|
+
isServerComposedRequest(
|
|
81
|
+
caller({ callerClass: "user", origin: "in-process" }),
|
|
82
|
+
),
|
|
83
|
+
).toBe(true);
|
|
84
|
+
expect(
|
|
85
|
+
isServerComposedRequest(
|
|
86
|
+
caller({ callerClass: "channel", origin: "in-process" }),
|
|
87
|
+
),
|
|
88
|
+
).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("is false for every wire caller, the machine class included — the row where it parts from isPlatformPipelineCaller", () => {
|
|
92
|
+
const wireMachine = caller({ callerClass: "machine" });
|
|
93
|
+
expect(isServerComposedRequest(wireMachine)).toBe(false);
|
|
94
|
+
expect(isPlatformPipelineCaller(wireMachine)).toBe(true);
|
|
95
|
+
expect(isServerComposedRequest(caller({ callerClass: "user" }))).toBe(
|
|
96
|
+
false,
|
|
97
|
+
);
|
|
98
|
+
expect(
|
|
99
|
+
isServerComposedRequest(caller({ callerClass: "user", origin: "wire" })),
|
|
100
|
+
).toBe(false);
|
|
101
|
+
expect(isServerComposedRequest(caller({ callerClass: "runner" }))).toBe(
|
|
102
|
+
false,
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
@@ -10,9 +10,16 @@
|
|
|
10
10
|
* narrows, blank spans orgs — lane 6),
|
|
11
11
|
* - activity.listRecentActivity (enumeration verb, two kinds — lane 22),
|
|
12
12
|
* - search (enumeration verb feeding the engine allowlist, and the
|
|
13
|
-
*
|
|
13
|
+
* proof that no request shape bypasses it — lane 21),
|
|
14
14
|
* - the OUTAGE arm: a throwing scope answers the sanitized INTERNAL,
|
|
15
|
-
* never an empty (or full!) list
|
|
15
|
+
* never an empty (or full!) list;
|
|
16
|
+
* - the SERVER'S OWN reads over `inProcessTransport` (stigmer#1207):
|
|
17
|
+
* the bare in-process call is stamped `internal` and the helper
|
|
18
|
+
* answers it before the scope — agentexecution.listBySession (the
|
|
19
|
+
* lane every server-internal reader of a session's turns rides) and
|
|
20
|
+
* workflowexecution.list with the org arm still applied; a caller
|
|
21
|
+
* PROPAGATED through the in-process header keeps its class and is
|
|
22
|
+
* narrowed exactly like the wire.
|
|
16
23
|
*
|
|
17
24
|
* Per-lane logic beyond the wiring is pinned in the helper matrix
|
|
18
25
|
* (list-read-scope.test.ts), the summaries suite, and the store-contract
|
|
@@ -31,6 +38,8 @@ import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
|
|
31
38
|
|
|
32
39
|
import { ActivityQueryController } from "@stigmer/protos/ai/stigmer/activity/v1/query_pb";
|
|
33
40
|
import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
|
|
41
|
+
import { AgentExecutionSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
42
|
+
import { AgentExecutionQueryController } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/query_pb";
|
|
34
43
|
import { SessionSchema } from "@stigmer/protos/ai/stigmer/agentic/session/v1/api_pb";
|
|
35
44
|
import { SessionQueryController } from "@stigmer/protos/ai/stigmer/agentic/session/v1/query_pb";
|
|
36
45
|
import { WorkflowExecutionSchema } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/api_pb";
|
|
@@ -44,6 +53,11 @@ import { loadConfig } from "../../boot/config.js";
|
|
|
44
53
|
import { composeServer } from "../../boot/compose.js";
|
|
45
54
|
import type { ComposedServer } from "../../boot/compose.js";
|
|
46
55
|
import { createLogger } from "../../boot/logger.js";
|
|
56
|
+
import { testCallerIdentity } from "../../pipeline/__tests__/support.js";
|
|
57
|
+
import {
|
|
58
|
+
IN_PROCESS_CALLER_HEADER,
|
|
59
|
+
encodeInProcessCaller,
|
|
60
|
+
} from "../../pipeline/interceptors/auth.js";
|
|
47
61
|
import type { ListReadScope } from "../list-read-scope.js";
|
|
48
62
|
import type { ServerExtension } from "../registry.js";
|
|
49
63
|
|
|
@@ -133,6 +147,27 @@ describe("list read scope (composed server, fake scope)", () => {
|
|
|
133
147
|
}),
|
|
134
148
|
);
|
|
135
149
|
}
|
|
150
|
+
// Two turns of one session — the rows a server-internal reader of a
|
|
151
|
+
// conversation's executions asks listBySession for.
|
|
152
|
+
for (const id of ["aex_turn_1", "aex_turn_2"]) {
|
|
153
|
+
await server.store.saveResource(
|
|
154
|
+
ApiResourceKind.agent_execution,
|
|
155
|
+
id,
|
|
156
|
+
AgentExecutionSchema,
|
|
157
|
+
create(AgentExecutionSchema, {
|
|
158
|
+
apiVersion: "agentic.stigmer.ai/v1",
|
|
159
|
+
kind: "AgentExecution",
|
|
160
|
+
metadata: { id, name: id, org: "acme" },
|
|
161
|
+
spec: { sessionId: "ses_mine" },
|
|
162
|
+
status: {
|
|
163
|
+
audit: {
|
|
164
|
+
specAudit: { createdAt: { seconds: 1_700_000_000n } },
|
|
165
|
+
statusAudit: { updatedAt: { seconds: 1_700_000_000n } },
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
}),
|
|
169
|
+
);
|
|
170
|
+
}
|
|
136
171
|
for (const [id, org] of [
|
|
137
172
|
["wfe_mine", "acme"],
|
|
138
173
|
["wfe_other_org", "rival"],
|
|
@@ -242,20 +277,23 @@ describe("list read scope (composed server, fake scope)", () => {
|
|
|
242
277
|
]);
|
|
243
278
|
});
|
|
244
279
|
|
|
245
|
-
it("search narrows through the engine allowlist
|
|
280
|
+
it("search narrows through the engine allowlist on every request shape — nothing bypasses the scope (lane 21)", async () => {
|
|
246
281
|
allowed = new Set(["agt_mine"]);
|
|
247
282
|
const search = createClient(SearchService, transport);
|
|
248
283
|
const scoped = await search.search({ query: "scopedagent" });
|
|
249
284
|
expect(scoped.entries.map((e) => e.id)).toEqual(["agt_mine"]);
|
|
250
285
|
|
|
251
|
-
// The
|
|
286
|
+
// The org-filtered shape asks the scope too: the one request shape
|
|
287
|
+
// that once skipped it (the retired public level's cross-organization
|
|
288
|
+
// discovery) no longer exists on the wire.
|
|
252
289
|
seenKinds.length = 0;
|
|
253
|
-
await search.search({
|
|
290
|
+
const orgScoped = await search.search({
|
|
291
|
+
kinds: [ApiResourceKind.agent],
|
|
254
292
|
query: "scopedagent",
|
|
255
293
|
org: "acme",
|
|
256
|
-
crossOrgPublic: true,
|
|
257
294
|
});
|
|
258
|
-
expect(
|
|
295
|
+
expect(orgScoped.entries.map((e) => e.id)).toEqual(["agt_mine"]);
|
|
296
|
+
expect(seenKinds).toEqual([ApiResourceKind.agent]);
|
|
259
297
|
});
|
|
260
298
|
|
|
261
299
|
it("a scope outage answers the sanitized INTERNAL — never an unscoped or empty success", async () => {
|
|
@@ -273,4 +311,55 @@ describe("list read scope (composed server, fake scope)", () => {
|
|
|
273
311
|
const list = await query.list({});
|
|
274
312
|
expect(list.entries).toEqual([]);
|
|
275
313
|
});
|
|
314
|
+
|
|
315
|
+
describe("the server's own reads over the in-process transport (stigmer#1207)", () => {
|
|
316
|
+
it("agentexecution.listBySession, bare: every turn of the session, the scope never asked", async () => {
|
|
317
|
+
// The scope would hide everything if it were asked.
|
|
318
|
+
allowed = new Set();
|
|
319
|
+
const query = createClient(
|
|
320
|
+
AgentExecutionQueryController,
|
|
321
|
+
server.inProcessTransport,
|
|
322
|
+
);
|
|
323
|
+
const turns = await query.listBySession({ sessionId: "ses_mine" });
|
|
324
|
+
expect(turns.entries.map((e) => e.metadata?.id).sort()).toEqual([
|
|
325
|
+
"aex_turn_1",
|
|
326
|
+
"aex_turn_2",
|
|
327
|
+
]);
|
|
328
|
+
expect(seenKinds).toEqual([]);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it("a caller propagated through the in-process header keeps its class and is narrowed like the wire", async () => {
|
|
332
|
+
allowed = new Set(["aex_turn_2"]);
|
|
333
|
+
const query = createClient(
|
|
334
|
+
AgentExecutionQueryController,
|
|
335
|
+
server.inProcessTransport,
|
|
336
|
+
);
|
|
337
|
+
const turns = await query.listBySession(
|
|
338
|
+
{ sessionId: "ses_mine" },
|
|
339
|
+
{
|
|
340
|
+
headers: {
|
|
341
|
+
[IN_PROCESS_CALLER_HEADER]: encodeInProcessCaller(
|
|
342
|
+
testCallerIdentity({ identityId: "alice" }),
|
|
343
|
+
),
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
);
|
|
347
|
+
expect(turns.entries.map((e) => e.metadata?.id)).toEqual(["aex_turn_2"]);
|
|
348
|
+
expect(seenKinds).toEqual([ApiResourceKind.agent_execution]);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it("workflowexecution.list, bare: the request's org predicate still applies, the scope never asked", async () => {
|
|
352
|
+
allowed = new Set();
|
|
353
|
+
const query = createClient(
|
|
354
|
+
WorkflowExecutionQueryController,
|
|
355
|
+
server.inProcessTransport,
|
|
356
|
+
);
|
|
357
|
+
const acme = await query.list({ org: "acme" });
|
|
358
|
+
expect(acme.entries.map((e) => e.metadata?.id).sort()).toEqual([
|
|
359
|
+
"wfe_foreign",
|
|
360
|
+
"wfe_mine",
|
|
361
|
+
]);
|
|
362
|
+
expect(seenKinds).toEqual([]);
|
|
363
|
+
});
|
|
364
|
+
});
|
|
276
365
|
});
|
|
@@ -12,6 +12,11 @@
|
|
|
12
12
|
* 20260913.04 T02 the scope is the last per-row predicate, so it is
|
|
13
13
|
* offered the org's rows and never the kind's — the same result set,
|
|
14
14
|
* a fraction of the candidates);
|
|
15
|
+
* - the `internal` class — the server acting as itself — gets the
|
|
16
|
+
* org-narrowed rows and the scope is never asked (the Authorize
|
|
17
|
+
* step's trust-domain rule on a list answer, stigmer#1207); the rule
|
|
18
|
+
* is class-only, so a caller PROPAGATED through the in-process
|
|
19
|
+
* transport (its own class, origin in-process) is still narrowed;
|
|
15
20
|
* - the candidates carry {id, org, labels} — the driver's guest
|
|
16
21
|
* cookie rule keys on labels — and the row's AUTHORIZATION FACTS
|
|
17
22
|
* (creator stamp, visibility level, every parent link), read by the
|
|
@@ -160,6 +165,64 @@ describe("restrictListByReadScope", () => {
|
|
|
160
165
|
]);
|
|
161
166
|
});
|
|
162
167
|
|
|
168
|
+
describe("the internal class — the server acting as itself (stigmer#1207)", () => {
|
|
169
|
+
/** A scope that must never be reached: any call is the failure under test. */
|
|
170
|
+
const neverAsked: ListReadScope = {
|
|
171
|
+
authorizedResourceIds: () =>
|
|
172
|
+
Promise.reject(new Error("the scope was asked for the internal class")),
|
|
173
|
+
restrictListEntries: () =>
|
|
174
|
+
Promise.reject(new Error("the scope was asked for the internal class")),
|
|
175
|
+
};
|
|
176
|
+
const internal = testCallerIdentity({
|
|
177
|
+
identityId: "system",
|
|
178
|
+
callerClass: "internal",
|
|
179
|
+
origin: "in-process",
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("gets the org-narrowed rows in the lane's order; the scope is never asked", async () => {
|
|
183
|
+
const withOrg = await restrictListByReadScope(
|
|
184
|
+
neverAsked,
|
|
185
|
+
internal,
|
|
186
|
+
ApiResourceKind.session,
|
|
187
|
+
rows,
|
|
188
|
+
"acme",
|
|
189
|
+
);
|
|
190
|
+
// The request's org predicate still applies — it is the request's,
|
|
191
|
+
// not the caller's; only the caller predicate is skipped.
|
|
192
|
+
expect(withOrg.map((r) => r.metadata.id)).toEqual(["ses_a", "ses_b"]);
|
|
193
|
+
const blankOrg = await restrictListByReadScope(
|
|
194
|
+
neverAsked,
|
|
195
|
+
internal,
|
|
196
|
+
ApiResourceKind.session,
|
|
197
|
+
rows,
|
|
198
|
+
"",
|
|
199
|
+
);
|
|
200
|
+
expect(blankOrg.map((r) => r.metadata.id)).toEqual([
|
|
201
|
+
"ses_a",
|
|
202
|
+
"ses_b",
|
|
203
|
+
"ses_c",
|
|
204
|
+
]);
|
|
205
|
+
// A fresh array either way, as every arm of the helper returns.
|
|
206
|
+
expect(blankOrg).not.toBe(rows);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("the rule is class-only: a caller propagated through the in-process transport keeps its class and is still narrowed", async () => {
|
|
210
|
+
const propagated = testCallerIdentity({
|
|
211
|
+
identityId: "alice",
|
|
212
|
+
callerClass: "user",
|
|
213
|
+
origin: "in-process",
|
|
214
|
+
});
|
|
215
|
+
const kept = await restrictListByReadScope(
|
|
216
|
+
scopeKeeping(["ses_c"]),
|
|
217
|
+
propagated,
|
|
218
|
+
ApiResourceKind.session,
|
|
219
|
+
rows,
|
|
220
|
+
"",
|
|
221
|
+
);
|
|
222
|
+
expect(kept.map((r) => r.metadata.id)).toEqual(["ses_c"]);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
163
226
|
it("an empty kept set is a real answer — the empty list", async () => {
|
|
164
227
|
const kept = await restrictListByReadScope(
|
|
165
228
|
scopeKeeping([]),
|