@stigmer/server 3.22.1-dev.20260922095755 → 3.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/artifactstorage/artifact-storage.d.ts +29 -31
- package/dist/artifactstorage/artifact-storage.d.ts.map +1 -1
- package/dist/artifactstorage/artifact-storage.js +5 -7
- package/dist/artifactstorage/artifact-storage.js.map +1 -1
- package/dist/artifactstorage/r2-storage.d.ts +12 -7
- package/dist/artifactstorage/r2-storage.d.ts.map +1 -1
- package/dist/artifactstorage/r2-storage.js +6 -14
- package/dist/artifactstorage/r2-storage.js.map +1 -1
- 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/boot/compose.d.ts.map +1 -1
- package/dist/boot/compose.js +49 -24
- package/dist/boot/compose.js.map +1 -1
- package/dist/boot/config.d.ts +4 -3
- package/dist/boot/config.d.ts.map +1 -1
- package/dist/boot/config.js.map +1 -1
- package/dist/boot/skill-transfer-origin.d.ts +52 -0
- package/dist/boot/skill-transfer-origin.d.ts.map +1 -0
- package/dist/boot/skill-transfer-origin.js +54 -0
- package/dist/boot/skill-transfer-origin.js.map +1 -0
- 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 +3 -7
- package/dist/domain/plugin/controller.d.ts.map +1 -1
- package/dist/domain/plugin/controller.js +11 -9
- 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/constants.d.ts +26 -3
- package/dist/domain/skill/constants.d.ts.map +1 -1
- package/dist/domain/skill/constants.js +26 -3
- package/dist/domain/skill/constants.js.map +1 -1
- package/dist/domain/skill/controller.d.ts +6 -11
- package/dist/domain/skill/controller.d.ts.map +1 -1
- package/dist/domain/skill/controller.js +35 -22
- package/dist/domain/skill/controller.js.map +1 -1
- package/dist/domain/skill/push.d.ts +2 -2
- package/dist/domain/skill/push.d.ts.map +1 -1
- package/dist/domain/skill/push.js +2 -2
- package/dist/domain/skill/push.js.map +1 -1
- package/dist/domain/skill/transfer/handler.d.ts +17 -9
- package/dist/domain/skill/transfer/handler.d.ts.map +1 -1
- package/dist/domain/skill/transfer/handler.js +19 -8
- package/dist/domain/skill/transfer/handler.js.map +1 -1
- package/dist/domain/skill/transfer/slots.d.ts +26 -41
- package/dist/domain/skill/transfer/slots.d.ts.map +1 -1
- package/dist/domain/skill/transfer/slots.js +80 -87
- package/dist/domain/skill/transfer/slots.js.map +1 -1
- package/dist/domain/skill/transfer/staging.d.ts +48 -0
- package/dist/domain/skill/transfer/staging.d.ts.map +1 -0
- package/dist/domain/skill/transfer/staging.js +127 -0
- package/dist/domain/skill/transfer/staging.js.map +1 -0
- 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/resolve-artifact-source.d.ts +11 -8
- package/dist/pipeline/steps/resolve-artifact-source.d.ts.map +1 -1
- package/dist/pipeline/steps/resolve-artifact-source.js +8 -4
- package/dist/pipeline/steps/resolve-artifact-source.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/artifactstorage/__tests__/artifact-storage.test.ts +36 -33
- package/src/artifactstorage/__tests__/r2-storage.test.ts +11 -13
- package/src/artifactstorage/artifact-storage.ts +37 -33
- package/src/artifactstorage/r2-storage.ts +6 -15
- 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/boot/__tests__/skill-transfer-origin.test.ts +72 -0
- package/src/boot/compose.ts +62 -25
- package/src/boot/config.ts +4 -3
- package/src/boot/skill-transfer-origin.ts +76 -0
- 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 +23 -18
- 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/__tests__/skill.test.ts +5 -3
- package/src/domain/skill/__tests__/slots.test.ts +113 -60
- package/src/domain/skill/__tests__/staging.test.ts +304 -0
- package/src/domain/skill/constants.ts +28 -3
- package/src/domain/skill/controller.ts +51 -35
- package/src/domain/skill/push.ts +3 -3
- package/src/domain/skill/transfer/handler.ts +33 -15
- package/src/domain/skill/transfer/slots.ts +89 -100
- package/src/domain/skill/transfer/staging.ts +191 -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/resolve-artifact-source.ts +20 -13
- 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/src/transport/registry/__tests__/lanes.test.ts +1 -1
- 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,461 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* NormalizeReferences + ValidateReferences — port
|
|
3
|
+
* steps/normalize_references.go and steps/validate_references.go, sharing
|
|
4
|
+
* one spec walker (as the Go files share theirs).
|
|
5
5
|
*
|
|
6
6
|
* NormalizeReferences fills EMPTY org fields in ApiResourceReference
|
|
7
7
|
* messages inside the spec from the resource's own metadata.org, so stored
|
|
8
|
-
* references are absolute;
|
|
9
|
-
* walked — status is system-generated and already
|
|
10
|
-
* BuildNewState/BuildUpdateState, before Persist.
|
|
8
|
+
* references are absolute; explicit orgs (cross-org refs) are preserved.
|
|
9
|
+
* Only the spec is walked — status is system-generated and already
|
|
10
|
+
* absolute. Runs after BuildNewState/BuildUpdateState, before Persist.
|
|
11
11
|
*
|
|
12
|
-
* ValidateReferences
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* (i) A reference with no organization is refused. After the normalize
|
|
17
|
-
* step the only way an org is still empty is a resource that has
|
|
18
|
-
* none itself; a slug looked up with no org matches any
|
|
19
|
-
* organization's row (helpers.ts), which is the cross-tenant read
|
|
20
|
-
* the reference lane's `requireOrgForReference` exists to forbid.
|
|
21
|
-
* (ii) Same organization as the resource: the target must exist, and,
|
|
22
|
-
* when the target's kind is one the RUN reads as the person
|
|
23
|
-
* (`readByRun` in the table below), its visibility must be at least
|
|
24
|
-
* the resource's on the order private < org < platform — the FLOOR.
|
|
25
|
-
* What a person can run they must also be able to read: an
|
|
26
|
-
* org-visible agent over a private MCP server would run for every
|
|
27
|
-
* member and be readable by one. Environments, OAuth apps and
|
|
28
|
-
* channel apps are resolved by the server on the run's behalf and
|
|
29
|
-
* never read by the person, so their level is not a leak the floor
|
|
30
|
-
* closes, and an org-visible instance may hold a private personal
|
|
31
|
-
* environment as it always has.
|
|
32
|
-
* (iii) Another organization: the target must exist AND be
|
|
33
|
-
* platform-visible, answered with ONE sentence that does not say
|
|
34
|
-
* which failed. The caller has no standing to learn what another
|
|
35
|
-
* organization holds (the anti-probe posture the AgentShare lane set
|
|
36
|
-
* first). The rule reads the target's LEVEL, not tenancy: whether
|
|
37
|
-
* the writer's organization is one the target's identity provider
|
|
38
|
-
* links is the run-time authorizer's question, asked when the run
|
|
39
|
-
* reads the target, and the copy here claims no more than the rule
|
|
40
|
-
* checks. No Environment, OAuth app or channel app is ever
|
|
41
|
-
* platform-visible, so every cross-organization reference to one is
|
|
42
|
-
* refused here.
|
|
43
|
-
*
|
|
44
|
-
* The kind a reference names is the kind its FIELD declares
|
|
45
|
-
* (`reference_kind`, the contract's word for what the field points to),
|
|
46
|
-
* never the `kind` value a client happened to put on the message: the
|
|
47
|
-
* runtime resolves `skill_refs` as skills whatever the message says, and
|
|
48
|
-
* the rule must agree with the runtime. A reference with an empty slug is
|
|
49
|
-
* unset and skipped (the field-level CEL rules own "required").
|
|
50
|
-
*
|
|
51
|
-
* The floor has a second door. Raising a resource's level through
|
|
52
|
-
* updateVisibility could open the same gap a create cannot, so
|
|
53
|
-
* GuardReferenceFloorOnEscalation runs on the two chains whose rows carry
|
|
54
|
-
* run-read references (agent, workflow) after ValidateVisibilityUpdate:
|
|
55
|
-
* when the requested level is above the stored one, every run-read
|
|
56
|
-
* reference the STORED row carries must be at least the requested level,
|
|
57
|
-
* judged by the same function over the same collectors, else the
|
|
58
|
-
* escalation is refused naming the dependencies. Only the floor is asked at
|
|
59
|
-
* that door — a dependency that has since left or stopped being shared was
|
|
60
|
-
* judged when the row was written and is the run's to refuse.
|
|
61
|
-
*
|
|
62
|
-
* REFERENCE_TARGET_KINDS is the one explicit table of kinds a spec may
|
|
63
|
-
* reference, with each kind's schema and its `readByRun` flag: the
|
|
64
|
-
* composition-root idiom (query/search/registry.ts) — an explicit list a
|
|
65
|
-
* test pins against the protos, so a new `reference_kind` in the contract
|
|
66
|
-
* fails the pin until this table says how it is read.
|
|
67
|
-
*
|
|
68
|
-
* Cost: one `listResources` scan per referenced KIND per write (not per
|
|
69
|
-
* reference), decoded once into a (org, slug) index; the same scan every
|
|
70
|
-
* runtime path already pays per reference when it resolves the target at
|
|
71
|
-
* run start. A store read by (kind, org, slug) is the follow-on that
|
|
72
|
-
* removes the scan everywhere, and is not this module's.
|
|
73
|
-
*
|
|
74
|
-
* The refusal copy is exported below. The MCP-server sentence predates the
|
|
75
|
-
* rule and is wire contract (byte-pinned by pipeline/__tests__/steps.test.ts
|
|
76
|
-
* and the agent conformance suite); its siblings for the other kinds take
|
|
77
|
-
* the same shape. `collectSpecReferences` is the walk exported for the
|
|
78
|
-
* readers that ask the reverse question ("who references this?").
|
|
12
|
+
* ValidateReferences verifies spec references point at existing resources
|
|
13
|
+
* — strict FAILED_PRECONDITION for missing MCP servers (an agent whose MCP
|
|
14
|
+
* server is missing cannot execute its declared tools). Runs AFTER
|
|
15
|
+
* NormalizeReferences so orgs are resolved.
|
|
79
16
|
*/
|
|
80
|
-
import type {
|
|
81
|
-
import { fromBinary, getOption, hasOption } from "@bufbuild/protobuf";
|
|
17
|
+
import type { DescMessage } from "@bufbuild/protobuf";
|
|
82
18
|
import { reflect } from "@bufbuild/protobuf/reflect";
|
|
83
19
|
import type { ReflectMessage } from "@bufbuild/protobuf/reflect";
|
|
84
20
|
|
|
85
|
-
import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
|
|
86
|
-
import { ChannelAppSchema } from "@stigmer/protos/ai/stigmer/agentic/channelapp/v1/api_pb";
|
|
87
|
-
import { EnvironmentSchema } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/api_pb";
|
|
88
21
|
import { McpServerSchema } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
|
|
89
|
-
import { SkillSchema } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/api_pb";
|
|
90
22
|
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
91
|
-
import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
|
|
92
|
-
import { reference_kind } from "@stigmer/protos/ai/stigmer/commons/apiresource/field_options_pb";
|
|
93
|
-
import type { UpdateVisibilityInputSchema } from "@stigmer/protos/ai/stigmer/commons/apiresource/io_pb";
|
|
94
|
-
import { OAuthAppSchema } from "@stigmer/protos/ai/stigmer/iam/oauthapp/v1/api_pb";
|
|
95
23
|
|
|
96
24
|
import type { Store } from "../../store/interface.js";
|
|
97
|
-
import {
|
|
98
|
-
failedPreconditionError,
|
|
99
|
-
internalError,
|
|
100
|
-
invalidArgumentError,
|
|
101
|
-
} from "../errors.js";
|
|
25
|
+
import { failedPreconditionError, internalError } from "../errors.js";
|
|
102
26
|
import type { PipelineStep } from "../pipeline.js";
|
|
103
27
|
import type { RequestContext } from "../request-context.js";
|
|
28
|
+
import { findResourceBySlug } from "./helpers.js";
|
|
104
29
|
import { messageFieldByName, metadataOf } from "./shapes.js";
|
|
105
30
|
|
|
106
31
|
const API_RESOURCE_REFERENCE_TYPE =
|
|
107
32
|
"ai.stigmer.commons.apiresource.ApiResourceReference";
|
|
108
33
|
|
|
109
|
-
// ---------------------------------------------------------------------------
|
|
110
|
-
// The table.
|
|
111
|
-
// ---------------------------------------------------------------------------
|
|
112
|
-
|
|
113
|
-
/** One kind a spec may reference, and how the rule reads it (the module header). */
|
|
114
|
-
export interface ReferenceTargetKind {
|
|
115
|
-
readonly kind: ApiResourceKind;
|
|
116
|
-
readonly schema: DescMessage;
|
|
117
|
-
/** Whether the run reads the target AS THE PERSON — the kinds the floor applies to. */
|
|
118
|
-
readonly readByRun: boolean;
|
|
119
|
-
/** The kind in the refusal copy's words, singular with the plural marker: "MCP server(s)". */
|
|
120
|
-
readonly label: string;
|
|
121
|
-
/** The CLI command a refusal points at, or undefined where the CLI has no verb for the kind. */
|
|
122
|
-
readonly listHint: string | undefined;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export const REFERENCE_TARGET_KINDS: ReadonlyArray<ReferenceTargetKind> = [
|
|
126
|
-
{
|
|
127
|
-
kind: ApiResourceKind.skill,
|
|
128
|
-
schema: SkillSchema,
|
|
129
|
-
readByRun: true,
|
|
130
|
-
label: "skill(s)",
|
|
131
|
-
listHint: "stigmer list skills",
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
kind: ApiResourceKind.mcp_server,
|
|
135
|
-
schema: McpServerSchema,
|
|
136
|
-
readByRun: true,
|
|
137
|
-
label: "MCP server(s)",
|
|
138
|
-
listHint: "stigmer get mcp-servers",
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
kind: ApiResourceKind.agent,
|
|
142
|
-
schema: AgentSchema,
|
|
143
|
-
readByRun: true,
|
|
144
|
-
label: "agent(s)",
|
|
145
|
-
listHint: "stigmer list agents",
|
|
146
|
-
},
|
|
147
|
-
{
|
|
148
|
-
kind: ApiResourceKind.environment,
|
|
149
|
-
schema: EnvironmentSchema,
|
|
150
|
-
readByRun: false,
|
|
151
|
-
label: "environment(s)",
|
|
152
|
-
listHint: "stigmer list environments",
|
|
153
|
-
},
|
|
154
|
-
{
|
|
155
|
-
kind: ApiResourceKind.channel_app,
|
|
156
|
-
schema: ChannelAppSchema,
|
|
157
|
-
readByRun: false,
|
|
158
|
-
label: "channel app(s)",
|
|
159
|
-
listHint: "stigmer list channel-app",
|
|
160
|
-
},
|
|
161
|
-
{
|
|
162
|
-
kind: ApiResourceKind.oauth_app,
|
|
163
|
-
schema: OAuthAppSchema,
|
|
164
|
-
readByRun: false,
|
|
165
|
-
label: "OAuth app(s)",
|
|
166
|
-
listHint: undefined,
|
|
167
|
-
},
|
|
168
|
-
];
|
|
169
|
-
|
|
170
|
-
export function referenceTargetKind(
|
|
171
|
-
kind: ApiResourceKind,
|
|
172
|
-
): ReferenceTargetKind | undefined {
|
|
173
|
-
return REFERENCE_TARGET_KINDS.find((entry) => entry.kind === kind);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// ---------------------------------------------------------------------------
|
|
177
|
-
// The rule.
|
|
178
|
-
// ---------------------------------------------------------------------------
|
|
179
|
-
|
|
180
|
-
/** One reference a spec carries, as the walker reads it; `kind` is the field's declared kind. */
|
|
181
|
-
export interface SpecReference {
|
|
182
|
-
readonly kind: ApiResourceKind;
|
|
183
|
-
readonly slug: string;
|
|
184
|
-
readonly org: string;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/** The resource the references belong to, as the rule needs it. */
|
|
188
|
-
export interface ReferenceParent {
|
|
189
|
-
readonly org: string;
|
|
190
|
-
readonly visibility: ApiResourceVisibility;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/** What the rule found for one reference; the collectors turn it into copy. */
|
|
194
|
-
export type ReferenceVerdict =
|
|
195
|
-
| { readonly kind: "ok" }
|
|
196
|
-
/** Clause (i): the reference names no organization. */
|
|
197
|
-
| { readonly kind: "no-org" }
|
|
198
|
-
/** Clause (ii): the same-organization target does not exist. */
|
|
199
|
-
| { readonly kind: "missing" }
|
|
200
|
-
/** Clause (ii): the target exists and is less visible than the resource. */
|
|
201
|
-
| {
|
|
202
|
-
readonly kind: "below-floor";
|
|
203
|
-
readonly targetVisibility: ApiResourceVisibility;
|
|
204
|
-
}
|
|
205
|
-
/** Clause (iii): the other-organization target is missing or not platform-visible — one answer. */
|
|
206
|
-
| { readonly kind: "not-available" };
|
|
207
|
-
|
|
208
|
-
/** The visibility of every referenced row the rule may need, one scan per kind (the module header). */
|
|
209
|
-
export interface ReferenceTargets {
|
|
210
|
-
visibilityOf(ref: SpecReference): ApiResourceVisibility | undefined;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Loads the rows the given references could name: one `listResources` per
|
|
215
|
-
* distinct kind, decoded once, indexed by (org, slug). A row that does not
|
|
216
|
-
* decode is skipped as the slug helpers skip it — it is not a row a
|
|
217
|
-
* reference can reach. A reference whose kind is not in the table is a
|
|
218
|
-
* contract the pin test has not admitted yet and is an internal fault.
|
|
219
|
-
*/
|
|
220
|
-
export async function loadReferenceTargets(
|
|
221
|
-
store: Store,
|
|
222
|
-
refs: ReadonlyArray<SpecReference>,
|
|
223
|
-
): Promise<ReferenceTargets> {
|
|
224
|
-
const byKind = new Map<ApiResourceKind, Map<string, ApiResourceVisibility>>();
|
|
225
|
-
for (const kind of new Set(refs.map((ref) => ref.kind))) {
|
|
226
|
-
const entry = referenceTargetKind(kind);
|
|
227
|
-
if (entry === undefined) {
|
|
228
|
-
throw internalError(
|
|
229
|
-
new Error(
|
|
230
|
-
`kind ${ApiResourceKind[kind] ?? kind} is not a reference target`,
|
|
231
|
-
),
|
|
232
|
-
"reference rule table has no entry for the referenced kind",
|
|
233
|
-
);
|
|
234
|
-
}
|
|
235
|
-
const index = new Map<string, ApiResourceVisibility>();
|
|
236
|
-
for (const data of await store.listResources(kind)) {
|
|
237
|
-
let metadata;
|
|
238
|
-
try {
|
|
239
|
-
metadata = metadataOf(fromBinary(entry.schema, data));
|
|
240
|
-
} catch {
|
|
241
|
-
continue;
|
|
242
|
-
}
|
|
243
|
-
if (metadata === undefined || metadata.slug === "") {
|
|
244
|
-
continue;
|
|
245
|
-
}
|
|
246
|
-
index.set(targetKey(metadata.org, metadata.slug), metadata.visibility);
|
|
247
|
-
}
|
|
248
|
-
byKind.set(kind, index);
|
|
249
|
-
}
|
|
250
|
-
return {
|
|
251
|
-
visibilityOf(ref) {
|
|
252
|
-
return byKind.get(ref.kind)?.get(targetKey(ref.org, ref.slug));
|
|
253
|
-
},
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
function targetKey(org: string, slug: string): string {
|
|
258
|
-
return `${org}/${slug}`;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/** The three clauses of the module header, for one reference. */
|
|
262
|
-
export function checkReference(
|
|
263
|
-
targets: ReferenceTargets,
|
|
264
|
-
parent: ReferenceParent,
|
|
265
|
-
ref: SpecReference,
|
|
266
|
-
): ReferenceVerdict {
|
|
267
|
-
if (ref.org === "") {
|
|
268
|
-
return { kind: "no-org" };
|
|
269
|
-
}
|
|
270
|
-
const target = targets.visibilityOf(ref);
|
|
271
|
-
if (ref.org !== parent.org) {
|
|
272
|
-
return target === ApiResourceVisibility.visibility_platform
|
|
273
|
-
? { kind: "ok" }
|
|
274
|
-
: { kind: "not-available" };
|
|
275
|
-
}
|
|
276
|
-
if (target === undefined) {
|
|
277
|
-
return { kind: "missing" };
|
|
278
|
-
}
|
|
279
|
-
const entry = referenceTargetKind(ref.kind);
|
|
280
|
-
if (
|
|
281
|
-
entry?.readByRun === true &&
|
|
282
|
-
visibilityRank(target) < visibilityRank(parent.visibility)
|
|
283
|
-
) {
|
|
284
|
-
return { kind: "below-floor", targetVisibility: target };
|
|
285
|
-
}
|
|
286
|
-
return { kind: "ok" };
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* The order the floor compares on: private < org < platform, an unset
|
|
291
|
-
* level reading as private (the level a row with no config holds). The
|
|
292
|
-
* retired public level is unreachable here — refused at every door and
|
|
293
|
-
* moved off every stored row by the store migration — so meeting it is a
|
|
294
|
-
* fault, not a rank.
|
|
295
|
-
*/
|
|
296
|
-
function visibilityRank(level: ApiResourceVisibility): number {
|
|
297
|
-
switch (level) {
|
|
298
|
-
case ApiResourceVisibility.api_resource_visibility_unspecified:
|
|
299
|
-
case ApiResourceVisibility.visibility_private:
|
|
300
|
-
return 0;
|
|
301
|
-
case ApiResourceVisibility.visibility_org:
|
|
302
|
-
return 1;
|
|
303
|
-
case ApiResourceVisibility.visibility_platform:
|
|
304
|
-
return 2;
|
|
305
|
-
case ApiResourceVisibility.visibility_public:
|
|
306
|
-
throw new Error("the retired public level has no rank");
|
|
307
|
-
default: {
|
|
308
|
-
const exhaustive: never = level;
|
|
309
|
-
throw new Error(`unknown visibility level: ${String(exhaustive)}`);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// ---------------------------------------------------------------------------
|
|
315
|
-
// The copy.
|
|
316
|
-
// ---------------------------------------------------------------------------
|
|
317
|
-
|
|
318
|
-
/** Clause (i)'s sentence. */
|
|
319
|
-
export function noOrgReferenceMessage(
|
|
320
|
-
entry: ReferenceTargetKind,
|
|
321
|
-
slug: string,
|
|
322
|
-
): string {
|
|
323
|
-
return `referenced ${entry.label} '${slug}' names no organization; a reference is 'org/slug', or 'slug' for a resource of this organization.`;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Clause (ii)'s sentence for the targets not found, one per kind. The MCP
|
|
328
|
-
* server form is the wire contract that predates the rule; the others are
|
|
329
|
-
* its siblings.
|
|
330
|
-
*/
|
|
331
|
-
export function missingReferencesMessage(
|
|
332
|
-
entry: ReferenceTargetKind,
|
|
333
|
-
handles: ReadonlyArray<{ readonly slug: string; readonly org: string }>,
|
|
334
|
-
): string {
|
|
335
|
-
const list = handles.map((h) => `'${h.slug}' (org: ${h.org})`).join(", ");
|
|
336
|
-
const hint =
|
|
337
|
-
entry.listHint === undefined
|
|
338
|
-
? ""
|
|
339
|
-
: ` Use '${entry.listHint}' to list available ${plural(entry)}.`;
|
|
340
|
-
return `referenced ${entry.label} not found: ${list}. Verify the slug and org are correct.${hint}`;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/** Clause (ii)'s floor sentence: the dependency and its level, against the resource's. */
|
|
344
|
-
export function belowFloorMessage(
|
|
345
|
-
entry: ReferenceTargetKind,
|
|
346
|
-
ref: SpecReference,
|
|
347
|
-
targetVisibility: ApiResourceVisibility,
|
|
348
|
-
parentVisibility: ApiResourceVisibility,
|
|
349
|
-
): string {
|
|
350
|
-
return `referenced ${singular(entry)} '${ref.org}/${ref.slug}' is ${levelWord(targetVisibility)} while this resource is ${levelWord(parentVisibility)}; a resource may not be more visible than the ${plural(entry)} it runs with. Widen the referenced resource's visibility or narrow this one.`;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
/** The level as the copy names it: an unset level reads as private, which is how every reader treats it. */
|
|
354
|
-
function levelWord(level: ApiResourceVisibility): string {
|
|
355
|
-
return level === ApiResourceVisibility.api_resource_visibility_unspecified
|
|
356
|
-
? ApiResourceVisibility[ApiResourceVisibility.visibility_private]
|
|
357
|
-
: ApiResourceVisibility[level];
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
/** Clause (iii)'s one sentence — the same whether the target is missing or not platform-visible. */
|
|
361
|
-
export function notAvailableReferenceMessage(
|
|
362
|
-
entry: ReferenceTargetKind,
|
|
363
|
-
ref: SpecReference,
|
|
364
|
-
): string {
|
|
365
|
-
return `referenced ${singular(entry)} '${ref.org}/${ref.slug}' is not available to this organization; another organization's resource can be referenced only when that organization shares it at platform visibility.`;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
/** "MCP server(s)" → "MCP server" and "MCP servers": the label's two readings. */
|
|
369
|
-
function singular(entry: ReferenceTargetKind): string {
|
|
370
|
-
return entry.label.replace("(s)", "");
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
function plural(entry: ReferenceTargetKind): string {
|
|
374
|
-
return entry.label.replace("(s)", "s");
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
/**
|
|
378
|
-
* The refusal for a set of verdicts, or undefined when every one is ok.
|
|
379
|
-
* Missing same-organization targets are grouped per kind into one sentence
|
|
380
|
-
* (the contract's shape); every other refusal is its own sentence; the
|
|
381
|
-
* sentences are joined in the order the references were read. A no-org
|
|
382
|
-
* reference is malformed input (INVALID_ARGUMENT); everything else is a
|
|
383
|
-
* precondition the store does not meet (FAILED_PRECONDITION), the code the
|
|
384
|
-
* MCP-server contract already answers.
|
|
385
|
-
*/
|
|
386
|
-
export function referenceRefusal(
|
|
387
|
-
parent: ReferenceParent,
|
|
388
|
-
verdicts: ReadonlyArray<{
|
|
389
|
-
readonly ref: SpecReference;
|
|
390
|
-
readonly verdict: ReferenceVerdict;
|
|
391
|
-
}>,
|
|
392
|
-
): ReturnType<typeof failedPreconditionError> | undefined {
|
|
393
|
-
// Missing same-organization targets, grouped per kind in the order the
|
|
394
|
-
// first of each kind was read.
|
|
395
|
-
const missingByKind = new Map<ApiResourceKind, SpecReference[]>();
|
|
396
|
-
for (const { ref, verdict } of verdicts) {
|
|
397
|
-
if (verdict.kind === "missing") {
|
|
398
|
-
const held = missingByKind.get(ref.kind);
|
|
399
|
-
if (held === undefined) {
|
|
400
|
-
missingByKind.set(ref.kind, [ref]);
|
|
401
|
-
} else {
|
|
402
|
-
held.push(ref);
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
const sentences: string[] = [];
|
|
407
|
-
const groupedKindsSaid = new Set<ApiResourceKind>();
|
|
408
|
-
let malformed = false;
|
|
409
|
-
for (const { ref, verdict } of verdicts) {
|
|
410
|
-
const entry = referenceTargetKind(ref.kind);
|
|
411
|
-
if (entry === undefined) {
|
|
412
|
-
continue;
|
|
413
|
-
}
|
|
414
|
-
switch (verdict.kind) {
|
|
415
|
-
case "ok":
|
|
416
|
-
break;
|
|
417
|
-
case "no-org":
|
|
418
|
-
malformed = true;
|
|
419
|
-
sentences.push(noOrgReferenceMessage(entry, ref.slug));
|
|
420
|
-
break;
|
|
421
|
-
case "missing":
|
|
422
|
-
if (!groupedKindsSaid.has(ref.kind)) {
|
|
423
|
-
groupedKindsSaid.add(ref.kind);
|
|
424
|
-
sentences.push(
|
|
425
|
-
missingReferencesMessage(entry, missingByKind.get(ref.kind) ?? []),
|
|
426
|
-
);
|
|
427
|
-
}
|
|
428
|
-
break;
|
|
429
|
-
case "below-floor":
|
|
430
|
-
sentences.push(
|
|
431
|
-
belowFloorMessage(
|
|
432
|
-
entry,
|
|
433
|
-
ref,
|
|
434
|
-
verdict.targetVisibility,
|
|
435
|
-
parent.visibility,
|
|
436
|
-
),
|
|
437
|
-
);
|
|
438
|
-
break;
|
|
439
|
-
case "not-available":
|
|
440
|
-
sentences.push(notAvailableReferenceMessage(entry, ref));
|
|
441
|
-
break;
|
|
442
|
-
default: {
|
|
443
|
-
const exhaustive: never = verdict;
|
|
444
|
-
throw new Error(`unknown verdict: ${JSON.stringify(exhaustive)}`);
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
if (sentences.length === 0) {
|
|
449
|
-
return undefined;
|
|
450
|
-
}
|
|
451
|
-
const text = sentences.join(" ");
|
|
452
|
-
return malformed ? invalidArgumentError(text) : failedPreconditionError(text);
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
// ---------------------------------------------------------------------------
|
|
456
|
-
// The steps.
|
|
457
|
-
// ---------------------------------------------------------------------------
|
|
458
|
-
|
|
459
34
|
export function newNormalizeReferencesStep<
|
|
460
35
|
Desc extends DescMessage,
|
|
461
36
|
>(): PipelineStep<Desc> {
|
|
@@ -469,8 +44,8 @@ export function newNormalizeReferencesStep<
|
|
|
469
44
|
"normalize references",
|
|
470
45
|
);
|
|
471
46
|
}
|
|
472
|
-
// No org to resolve from — skip silently;
|
|
473
|
-
//
|
|
47
|
+
// No org to resolve from — skip silently; required-org validation is
|
|
48
|
+
// the validation step's responsibility, not this one's.
|
|
474
49
|
if (metadata.org === "") {
|
|
475
50
|
return;
|
|
476
51
|
}
|
|
@@ -490,131 +65,68 @@ export function newValidateReferencesStep<Desc extends DescMessage>(
|
|
|
490
65
|
return {
|
|
491
66
|
name: "ValidateReferences",
|
|
492
67
|
async execute(ctx: RequestContext<Desc>): Promise<void> {
|
|
493
|
-
const
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
(ref) => ref.slug !== "",
|
|
502
|
-
);
|
|
503
|
-
if (refs.length === 0) {
|
|
504
|
-
return;
|
|
505
|
-
}
|
|
506
|
-
const parent: ReferenceParent = {
|
|
507
|
-
org: metadata.org,
|
|
508
|
-
visibility: metadata.visibility,
|
|
509
|
-
};
|
|
510
|
-
const refusal = await checkReferences(store, parent, refs);
|
|
511
|
-
if (refusal !== undefined) {
|
|
512
|
-
throw refusal;
|
|
513
|
-
}
|
|
514
|
-
},
|
|
515
|
-
};
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
/** The rule over a collected list: load once, check each, render the refusal. */
|
|
519
|
-
export async function checkReferences(
|
|
520
|
-
store: Store,
|
|
521
|
-
parent: ReferenceParent,
|
|
522
|
-
refs: ReadonlyArray<SpecReference>,
|
|
523
|
-
): Promise<ReturnType<typeof referenceRefusal>> {
|
|
524
|
-
const targets = await loadReferenceTargets(store, refs);
|
|
525
|
-
return referenceRefusal(
|
|
526
|
-
parent,
|
|
527
|
-
refs.map((ref) => ({ ref, verdict: checkReference(targets, parent, ref) })),
|
|
528
|
-
);
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
/** The references a stored row carries, for the escalation door; a chain passes one collector per place its row keeps them. */
|
|
532
|
-
export type ReferenceCollector = (row: Message) => ReadonlyArray<SpecReference>;
|
|
68
|
+
const refs: Array<{ kind: number; slug: string; org: string }> = [];
|
|
69
|
+
forEachSpecReference(ctx.schema, ctx.newState, (ref) => {
|
|
70
|
+
refs.push({
|
|
71
|
+
kind: numberField(ref, "kind"),
|
|
72
|
+
slug: stringField(ref, "slug"),
|
|
73
|
+
org: stringField(ref, "org"),
|
|
74
|
+
});
|
|
75
|
+
});
|
|
533
76
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
// Wiring error, not a user error: a guard that silently passes
|
|
553
|
-
// un-guards the boundary it exists to protect.
|
|
554
|
-
throw internalError(
|
|
555
|
-
new Error(
|
|
556
|
-
"GuardReferenceFloorOnEscalation ran without a loaded target — the step must follow the load step",
|
|
557
|
-
),
|
|
558
|
-
"reference floor requires the loaded resource",
|
|
559
|
-
);
|
|
77
|
+
const missingMcpServers: string[] = [];
|
|
78
|
+
for (const ref of refs) {
|
|
79
|
+
if (ref.slug === "") {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
// Per-kind validation, exactly Go's switch: only mcp_server today.
|
|
83
|
+
if (ref.kind === ApiResourceKind.mcp_server) {
|
|
84
|
+
const found = await findResourceBySlug(
|
|
85
|
+
store,
|
|
86
|
+
ApiResourceKind.mcp_server,
|
|
87
|
+
McpServerSchema,
|
|
88
|
+
ref.slug,
|
|
89
|
+
ref.org,
|
|
90
|
+
);
|
|
91
|
+
if (found === undefined) {
|
|
92
|
+
missingMcpServers.push(`'${ref.slug}' (org: ${ref.org})`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
560
95
|
}
|
|
561
|
-
|
|
562
|
-
if (
|
|
563
|
-
throw
|
|
564
|
-
|
|
565
|
-
|
|
96
|
+
|
|
97
|
+
if (missingMcpServers.length > 0) {
|
|
98
|
+
throw failedPreconditionError(
|
|
99
|
+
`referenced MCP server(s) not found: ${missingMcpServers.join(", ")}. ` +
|
|
100
|
+
"Verify the slug and org are correct. " +
|
|
101
|
+
"Use 'stigmer get mcp-servers' to list available MCP servers.",
|
|
566
102
|
);
|
|
567
103
|
}
|
|
568
|
-
const requested = ctx.input.visibility;
|
|
569
|
-
if (visibilityRank(requested) <= visibilityRank(metadata.visibility)) {
|
|
570
|
-
return;
|
|
571
|
-
}
|
|
572
|
-
const refs = collectors
|
|
573
|
-
.flatMap((collect) => collect(row))
|
|
574
|
-
.filter((ref) => ref.slug !== "");
|
|
575
|
-
if (refs.length === 0) {
|
|
576
|
-
return;
|
|
577
|
-
}
|
|
578
|
-
const parent: ReferenceParent = {
|
|
579
|
-
org: metadata.org,
|
|
580
|
-
visibility: requested,
|
|
581
|
-
};
|
|
582
|
-
const targets = await loadReferenceTargets(store, refs);
|
|
583
|
-
const refusal = referenceRefusal(
|
|
584
|
-
parent,
|
|
585
|
-
refs
|
|
586
|
-
.map((ref) => ({
|
|
587
|
-
ref,
|
|
588
|
-
verdict: checkReference(targets, parent, ref),
|
|
589
|
-
}))
|
|
590
|
-
.filter(({ verdict }) => verdict.kind === "below-floor"),
|
|
591
|
-
);
|
|
592
|
-
if (refusal !== undefined) {
|
|
593
|
-
throw refusal;
|
|
594
|
-
}
|
|
595
104
|
},
|
|
596
105
|
};
|
|
597
106
|
}
|
|
598
107
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
108
|
+
/** One reference a spec carries, as the walker reads it. */
|
|
109
|
+
export interface SpecReference {
|
|
110
|
+
readonly kind: ApiResourceKind;
|
|
111
|
+
readonly slug: string;
|
|
112
|
+
readonly org: string;
|
|
113
|
+
}
|
|
602
114
|
|
|
603
115
|
/**
|
|
604
|
-
* Every ApiResourceReference in a resource's spec
|
|
605
|
-
*
|
|
606
|
-
*
|
|
607
|
-
*
|
|
608
|
-
*
|
|
116
|
+
* Every ApiResourceReference in a resource's spec — the same walk
|
|
117
|
+
* ValidateReferences runs, exported for the readers that ask the reverse
|
|
118
|
+
* question ("who references this?"): the plugin delete guard scans an
|
|
119
|
+
* organization's agents and workflows for references to a member it is
|
|
120
|
+
* about to remove.
|
|
609
121
|
*/
|
|
610
122
|
export function collectSpecReferences(
|
|
611
123
|
schema: DescMessage,
|
|
612
124
|
msg: Parameters<typeof reflect>[1],
|
|
613
125
|
): SpecReference[] {
|
|
614
126
|
const refs: SpecReference[] = [];
|
|
615
|
-
forEachSpecReference(schema, msg, (ref
|
|
127
|
+
forEachSpecReference(schema, msg, (ref) => {
|
|
616
128
|
refs.push({
|
|
617
|
-
kind:
|
|
129
|
+
kind: numberField(ref, "kind") as ApiResourceKind,
|
|
618
130
|
slug: stringField(ref, "slug"),
|
|
619
131
|
org: stringField(ref, "org"),
|
|
620
132
|
});
|
|
@@ -622,29 +134,16 @@ export function collectSpecReferences(
|
|
|
622
134
|
return refs;
|
|
623
135
|
}
|
|
624
136
|
|
|
625
|
-
/**
|
|
626
|
-
* The kind a reference names: the field's `reference_kind` option when the
|
|
627
|
-
* contract declares one (every reference field does), else the kind the
|
|
628
|
-
* message carries (the walk's only fallback, for a field the contract has
|
|
629
|
-
* not annotated).
|
|
630
|
-
*/
|
|
631
|
-
function declaredKind(field: DescField, ref: ReflectMessage): ApiResourceKind {
|
|
632
|
-
if (hasOption(field, reference_kind)) {
|
|
633
|
-
return getOption(field, reference_kind);
|
|
634
|
-
}
|
|
635
|
-
return numberField(ref, "kind") as ApiResourceKind;
|
|
636
|
-
}
|
|
637
|
-
|
|
638
137
|
/**
|
|
639
138
|
* Walks the resource's spec and invokes fn on every ApiResourceReference
|
|
640
|
-
* (singular, repeated, and map-valued message fields, recursively)
|
|
641
|
-
* the
|
|
642
|
-
* through.
|
|
139
|
+
* (singular, repeated, and map-valued message fields, recursively) —
|
|
140
|
+
* the shared traversal behind both steps (Go walkAndResolveOrg /
|
|
141
|
+
* walkAndCollectRefs). Mutations through the ReflectMessage write through.
|
|
643
142
|
*/
|
|
644
143
|
function forEachSpecReference(
|
|
645
144
|
schema: DescMessage,
|
|
646
145
|
msg: Parameters<typeof reflect>[1],
|
|
647
|
-
fn: (ref: ReflectMessage
|
|
146
|
+
fn: (ref: ReflectMessage) => void,
|
|
648
147
|
): void {
|
|
649
148
|
const root = reflect(schema, msg);
|
|
650
149
|
const specField = messageFieldByName(root, "spec");
|
|
@@ -654,17 +153,14 @@ function forEachSpecReference(
|
|
|
654
153
|
walk(root.get(specField), fn);
|
|
655
154
|
}
|
|
656
155
|
|
|
657
|
-
function walk(
|
|
658
|
-
msg: ReflectMessage,
|
|
659
|
-
fn: (ref: ReflectMessage, field: DescField) => void,
|
|
660
|
-
): void {
|
|
156
|
+
function walk(msg: ReflectMessage, fn: (ref: ReflectMessage) => void): void {
|
|
661
157
|
for (const field of msg.fields) {
|
|
662
158
|
if (field.fieldKind === "list") {
|
|
663
159
|
if (field.listKind !== "message") {
|
|
664
160
|
continue;
|
|
665
161
|
}
|
|
666
162
|
for (const item of msg.get(field)) {
|
|
667
|
-
visit(item as ReflectMessage,
|
|
163
|
+
visit(item as ReflectMessage, fn);
|
|
668
164
|
}
|
|
669
165
|
} else if (field.fieldKind === "map") {
|
|
670
166
|
if (field.mapKind !== "message") {
|
|
@@ -672,24 +168,20 @@ function walk(
|
|
|
672
168
|
}
|
|
673
169
|
const map = msg.get(field);
|
|
674
170
|
for (const [, value] of map) {
|
|
675
|
-
visit(value as ReflectMessage,
|
|
171
|
+
visit(value as ReflectMessage, fn);
|
|
676
172
|
}
|
|
677
173
|
} else if (field.fieldKind === "message") {
|
|
678
174
|
if (!msg.isSet(field)) {
|
|
679
175
|
continue;
|
|
680
176
|
}
|
|
681
|
-
visit(msg.get(field),
|
|
177
|
+
visit(msg.get(field), fn);
|
|
682
178
|
}
|
|
683
179
|
}
|
|
684
180
|
}
|
|
685
181
|
|
|
686
|
-
function visit(
|
|
687
|
-
sub: ReflectMessage,
|
|
688
|
-
field: DescField,
|
|
689
|
-
fn: (ref: ReflectMessage, field: DescField) => void,
|
|
690
|
-
): void {
|
|
182
|
+
function visit(sub: ReflectMessage, fn: (ref: ReflectMessage) => void): void {
|
|
691
183
|
if (sub.desc.typeName === API_RESOURCE_REFERENCE_TYPE) {
|
|
692
|
-
fn(sub
|
|
184
|
+
fn(sub);
|
|
693
185
|
} else {
|
|
694
186
|
walk(sub, fn);
|
|
695
187
|
}
|