@stigmer/server 3.15.3-dev.20260914100347 → 3.15.3-dev.20260916211208
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/archive/content-store.d.ts +35 -0
- package/dist/archive/content-store.d.ts.map +1 -0
- package/dist/archive/content-store.js +98 -0
- package/dist/archive/content-store.js.map +1 -0
- package/dist/archive/inflate.d.ts +22 -0
- package/dist/archive/inflate.d.ts.map +1 -0
- package/dist/archive/inflate.js +75 -0
- package/dist/archive/inflate.js.map +1 -0
- package/dist/archive/limits.d.ts +33 -0
- package/dist/archive/limits.d.ts.map +1 -0
- package/dist/archive/limits.js +31 -0
- package/dist/archive/limits.js.map +1 -0
- package/dist/archive/open.d.ts +24 -0
- package/dist/archive/open.d.ts.map +1 -0
- package/dist/archive/open.js +93 -0
- package/dist/archive/open.js.map +1 -0
- package/dist/{domain/skill/storage → archive}/prefilter.d.ts +5 -0
- package/dist/archive/prefilter.d.ts.map +1 -0
- package/dist/archive/prefilter.js.map +1 -0
- package/dist/archive/write.d.ts +8 -0
- package/dist/archive/write.d.ts.map +1 -0
- package/dist/archive/write.js +44 -0
- package/dist/archive/write.js.map +1 -0
- package/dist/authorization/authorizer.d.ts +98 -0
- package/dist/authorization/authorizer.d.ts.map +1 -0
- package/dist/authorization/authorizer.js +156 -0
- package/dist/authorization/authorizer.js.map +1 -0
- package/dist/authorization/derived-tuples.d.ts +42 -0
- package/dist/authorization/derived-tuples.d.ts.map +1 -0
- package/dist/authorization/derived-tuples.js +240 -0
- package/dist/authorization/derived-tuples.js.map +1 -0
- package/dist/authorization/evaluator.d.ts +66 -0
- package/dist/authorization/evaluator.d.ts.map +1 -0
- package/dist/authorization/evaluator.js +153 -0
- package/dist/authorization/evaluator.js.map +1 -0
- package/dist/authorization/facts.d.ts +29 -0
- package/dist/authorization/facts.d.ts.map +1 -0
- package/dist/authorization/facts.js +17 -0
- package/dist/authorization/facts.js.map +1 -0
- package/dist/authorization/list-read-scope.d.ts +18 -0
- package/dist/authorization/list-read-scope.d.ts.map +1 -0
- package/dist/authorization/list-read-scope.js +155 -0
- package/dist/authorization/list-read-scope.js.map +1 -0
- package/dist/authorization/model/agent.d.ts +2 -0
- package/dist/authorization/model/agent.d.ts.map +1 -0
- package/dist/authorization/model/agent.js +42 -0
- package/dist/authorization/model/agent.js.map +1 -0
- package/dist/authorization/model/agent_channel.d.ts +2 -0
- package/dist/authorization/model/agent_channel.d.ts.map +1 -0
- package/dist/authorization/model/agent_channel.js +38 -0
- package/dist/authorization/model/agent_channel.js.map +1 -0
- package/dist/authorization/model/agent_execution.d.ts +2 -0
- package/dist/authorization/model/agent_execution.d.ts.map +1 -0
- package/dist/authorization/model/agent_execution.js +25 -0
- package/dist/authorization/model/agent_execution.js.map +1 -0
- package/dist/authorization/model/agent_instance.d.ts +2 -0
- package/dist/authorization/model/agent_instance.d.ts.map +1 -0
- package/dist/authorization/model/agent_instance.js +47 -0
- package/dist/authorization/model/agent_instance.js.map +1 -0
- package/dist/authorization/model/agent_share.d.ts +2 -0
- package/dist/authorization/model/agent_share.d.ts.map +1 -0
- package/dist/authorization/model/agent_share.js +29 -0
- package/dist/authorization/model/agent_share.js.map +1 -0
- package/dist/authorization/model/api_key.d.ts +2 -0
- package/dist/authorization/model/api_key.d.ts.map +1 -0
- package/dist/authorization/model/api_key.js +27 -0
- package/dist/authorization/model/api_key.js.map +1 -0
- package/dist/authorization/model/artifact.d.ts +2 -0
- package/dist/authorization/model/artifact.d.ts.map +1 -0
- package/dist/authorization/model/artifact.js +27 -0
- package/dist/authorization/model/artifact.js.map +1 -0
- package/dist/authorization/model/channel_app.d.ts +2 -0
- package/dist/authorization/model/channel_app.d.ts.map +1 -0
- package/dist/authorization/model/channel_app.js +28 -0
- package/dist/authorization/model/channel_app.js.map +1 -0
- package/dist/authorization/model/default-of.d.ts +38 -0
- package/dist/authorization/model/default-of.d.ts.map +1 -0
- package/dist/authorization/model/default-of.js +39 -0
- package/dist/authorization/model/default-of.js.map +1 -0
- package/dist/authorization/model/environment.d.ts +2 -0
- package/dist/authorization/model/environment.d.ts.map +1 -0
- package/dist/authorization/model/environment.js +33 -0
- package/dist/authorization/model/environment.js.map +1 -0
- package/dist/authorization/model/execution_context.d.ts +2 -0
- package/dist/authorization/model/execution_context.d.ts.map +1 -0
- package/dist/authorization/model/execution_context.js +19 -0
- package/dist/authorization/model/execution_context.js.map +1 -0
- package/dist/authorization/model/iam_policy.d.ts +2 -0
- package/dist/authorization/model/iam_policy.d.ts.map +1 -0
- package/dist/authorization/model/iam_policy.js +31 -0
- package/dist/authorization/model/iam_policy.js.map +1 -0
- package/dist/authorization/model/identity_account.d.ts +2 -0
- package/dist/authorization/model/identity_account.d.ts.map +1 -0
- package/dist/authorization/model/identity_account.js +25 -0
- package/dist/authorization/model/identity_account.js.map +1 -0
- package/dist/authorization/model/index.d.ts +36 -0
- package/dist/authorization/model/index.d.ts.map +1 -0
- package/dist/authorization/model/index.js +72 -0
- package/dist/authorization/model/index.js.map +1 -0
- package/dist/authorization/model/mcp_server.d.ts +2 -0
- package/dist/authorization/model/mcp_server.d.ts.map +1 -0
- package/dist/authorization/model/mcp_server.js +37 -0
- package/dist/authorization/model/mcp_server.js.map +1 -0
- package/dist/authorization/model/memory.d.ts +2 -0
- package/dist/authorization/model/memory.d.ts.map +1 -0
- package/dist/authorization/model/memory.js +26 -0
- package/dist/authorization/model/memory.js.map +1 -0
- package/dist/authorization/model/oauth_app.d.ts +2 -0
- package/dist/authorization/model/oauth_app.d.ts.map +1 -0
- package/dist/authorization/model/oauth_app.js +30 -0
- package/dist/authorization/model/oauth_app.js.map +1 -0
- package/dist/authorization/model/organization.d.ts +2 -0
- package/dist/authorization/model/organization.d.ts.map +1 -0
- package/dist/authorization/model/organization.js +54 -0
- package/dist/authorization/model/organization.js.map +1 -0
- package/dist/authorization/model/plugin.d.ts +2 -0
- package/dist/authorization/model/plugin.d.ts.map +1 -0
- package/dist/authorization/model/plugin.js +44 -0
- package/dist/authorization/model/plugin.js.map +1 -0
- package/dist/authorization/model/project.d.ts +2 -0
- package/dist/authorization/model/project.d.ts.map +1 -0
- package/dist/authorization/model/project.js +32 -0
- package/dist/authorization/model/project.js.map +1 -0
- package/dist/authorization/model/rewrite.d.ts +127 -0
- package/dist/authorization/model/rewrite.d.ts.map +1 -0
- package/dist/authorization/model/rewrite.js +82 -0
- package/dist/authorization/model/rewrite.js.map +1 -0
- package/dist/authorization/model/schedule.d.ts +2 -0
- package/dist/authorization/model/schedule.d.ts.map +1 -0
- package/dist/authorization/model/schedule.js +29 -0
- package/dist/authorization/model/schedule.js.map +1 -0
- package/dist/authorization/model/session.d.ts +2 -0
- package/dist/authorization/model/session.d.ts.map +1 -0
- package/dist/authorization/model/session.js +38 -0
- package/dist/authorization/model/session.js.map +1 -0
- package/dist/authorization/model/skill.d.ts +2 -0
- package/dist/authorization/model/skill.d.ts.map +1 -0
- package/dist/authorization/model/skill.js +38 -0
- package/dist/authorization/model/skill.js.map +1 -0
- package/dist/authorization/model/workflow.d.ts +2 -0
- package/dist/authorization/model/workflow.d.ts.map +1 -0
- package/dist/authorization/model/workflow.js +38 -0
- package/dist/authorization/model/workflow.js.map +1 -0
- package/dist/authorization/model/workflow_execution.d.ts +2 -0
- package/dist/authorization/model/workflow_execution.d.ts.map +1 -0
- package/dist/authorization/model/workflow_execution.js +32 -0
- package/dist/authorization/model/workflow_execution.js.map +1 -0
- package/dist/authorization/model/workflow_instance.d.ts +2 -0
- package/dist/authorization/model/workflow_instance.d.ts.map +1 -0
- package/dist/authorization/model/workflow_instance.js +92 -0
- package/dist/authorization/model/workflow_instance.js.map +1 -0
- package/dist/authorization/organization-directory.d.ts +13 -0
- package/dist/authorization/organization-directory.d.ts.map +1 -0
- package/dist/authorization/organization-directory.js +83 -0
- package/dist/authorization/organization-directory.js.map +1 -0
- package/dist/authorization/person.d.ts +42 -0
- package/dist/authorization/person.d.ts.map +1 -0
- package/dist/authorization/person.js +19 -0
- package/dist/authorization/person.js.map +1 -0
- package/dist/authorization/posture.d.ts +38 -0
- package/dist/authorization/posture.d.ts.map +1 -0
- package/dist/authorization/posture.js +7 -0
- package/dist/authorization/posture.js.map +1 -0
- package/dist/authorization/schedule-fire-caller.d.ts +12 -0
- package/dist/authorization/schedule-fire-caller.d.ts.map +1 -0
- package/dist/authorization/schedule-fire-caller.js +67 -0
- package/dist/authorization/schedule-fire-caller.js.map +1 -0
- package/dist/authorization/store-test-kit.d.ts +68 -0
- package/dist/authorization/store-test-kit.d.ts.map +1 -0
- package/dist/authorization/store-test-kit.js +311 -0
- package/dist/authorization/store-test-kit.js.map +1 -0
- package/dist/authorization/tuples.d.ts +115 -0
- package/dist/authorization/tuples.d.ts.map +1 -0
- package/dist/authorization/tuples.js +83 -0
- package/dist/authorization/tuples.js.map +1 -0
- package/dist/boot/compose.d.ts.map +1 -1
- package/dist/boot/compose.js +157 -32
- package/dist/boot/compose.js.map +1 -1
- package/dist/boot/inprocess.d.ts +12 -0
- package/dist/boot/inprocess.d.ts.map +1 -1
- package/dist/boot/inprocess.js +62 -20
- package/dist/boot/inprocess.js.map +1 -1
- package/dist/domain/agent/controller.d.ts.map +1 -1
- package/dist/domain/agent/controller.js +9 -0
- package/dist/domain/agent/controller.js.map +1 -1
- package/dist/domain/iampolicy/constants.d.ts +11 -0
- package/dist/domain/iampolicy/constants.d.ts.map +1 -1
- package/dist/domain/iampolicy/constants.js +11 -0
- package/dist/domain/iampolicy/constants.js.map +1 -1
- package/dist/domain/iampolicy/membership.d.ts +72 -1
- package/dist/domain/iampolicy/membership.d.ts.map +1 -1
- package/dist/domain/iampolicy/membership.js +141 -56
- package/dist/domain/iampolicy/membership.js.map +1 -1
- package/dist/domain/identityaccount/actor.d.ts +26 -0
- package/dist/domain/identityaccount/actor.d.ts.map +1 -0
- package/dist/domain/identityaccount/actor.js +17 -0
- package/dist/domain/identityaccount/actor.js.map +1 -0
- package/dist/domain/mcpserver/controller.d.ts.map +1 -1
- package/dist/domain/mcpserver/controller.js +9 -0
- package/dist/domain/mcpserver/controller.js.map +1 -1
- package/dist/domain/plugin/archive.d.ts +37 -0
- package/dist/domain/plugin/archive.d.ts.map +1 -0
- package/dist/domain/plugin/archive.js +50 -0
- package/dist/domain/plugin/archive.js.map +1 -0
- package/dist/domain/plugin/constants.d.ts +38 -0
- package/dist/domain/plugin/constants.d.ts.map +1 -0
- package/dist/domain/plugin/constants.js +46 -0
- package/dist/domain/plugin/constants.js.map +1 -0
- package/dist/domain/plugin/controller.d.ts +25 -0
- package/dist/domain/plugin/controller.d.ts.map +1 -0
- package/dist/domain/plugin/controller.js +493 -0
- package/dist/domain/plugin/controller.js.map +1 -0
- package/dist/domain/plugin/materialize/agent.d.ts +13 -0
- package/dist/domain/plugin/materialize/agent.d.ts.map +1 -0
- package/dist/domain/plugin/materialize/agent.js +128 -0
- package/dist/domain/plugin/materialize/agent.js.map +1 -0
- package/dist/domain/plugin/materialize/default-agent-instructions.d.ts +22 -0
- package/dist/domain/plugin/materialize/default-agent-instructions.d.ts.map +1 -0
- package/dist/domain/plugin/materialize/default-agent-instructions.js +31 -0
- package/dist/domain/plugin/materialize/default-agent-instructions.js.map +1 -0
- package/dist/domain/plugin/materialize/identity.d.ts +28 -0
- package/dist/domain/plugin/materialize/identity.d.ts.map +1 -0
- package/dist/domain/plugin/materialize/identity.js +40 -0
- package/dist/domain/plugin/materialize/identity.js.map +1 -0
- package/dist/domain/plugin/materialize/mcp-servers.d.ts +15 -0
- package/dist/domain/plugin/materialize/mcp-servers.d.ts.map +1 -0
- package/dist/domain/plugin/materialize/mcp-servers.js +131 -0
- package/dist/domain/plugin/materialize/mcp-servers.js.map +1 -0
- package/dist/domain/plugin/materialize/plan.d.ts +24 -0
- package/dist/domain/plugin/materialize/plan.d.ts.map +1 -0
- package/dist/domain/plugin/materialize/plan.js +77 -0
- package/dist/domain/plugin/materialize/plan.js.map +1 -0
- package/dist/domain/plugin/materialize/ports.d.ts +42 -0
- package/dist/domain/plugin/materialize/ports.d.ts.map +1 -0
- package/dist/domain/plugin/materialize/ports.js +2 -0
- package/dist/domain/plugin/materialize/ports.js.map +1 -0
- package/dist/domain/plugin/materialize/skills.d.ts +12 -0
- package/dist/domain/plugin/materialize/skills.d.ts.map +1 -0
- package/dist/domain/plugin/materialize/skills.js +44 -0
- package/dist/domain/plugin/materialize/skills.js.map +1 -0
- package/dist/domain/plugin/materialize/workflows.d.ts +15 -0
- package/dist/domain/plugin/materialize/workflows.d.ts.map +1 -0
- package/dist/domain/plugin/materialize/workflows.js +31 -0
- package/dist/domain/plugin/materialize/workflows.js.map +1 -0
- package/dist/domain/plugin/members.d.ts +42 -0
- package/dist/domain/plugin/members.d.ts.map +1 -0
- package/dist/domain/plugin/members.js +97 -0
- package/dist/domain/plugin/members.js.map +1 -0
- package/dist/domain/plugin/overlay/documents.d.ts +31 -0
- package/dist/domain/plugin/overlay/documents.d.ts.map +1 -0
- package/dist/domain/plugin/overlay/documents.js +38 -0
- package/dist/domain/plugin/overlay/documents.js.map +1 -0
- package/dist/domain/plugin/overlay/parse.d.ts +16 -0
- package/dist/domain/plugin/overlay/parse.d.ts.map +1 -0
- package/dist/domain/plugin/overlay/parse.js +79 -0
- package/dist/domain/plugin/overlay/parse.js.map +1 -0
- package/dist/domain/plugin/overlay/sanitize.d.ts +10 -0
- package/dist/domain/plugin/overlay/sanitize.d.ts.map +1 -0
- package/dist/domain/plugin/overlay/sanitize.js +101 -0
- package/dist/domain/plugin/overlay/sanitize.js.map +1 -0
- package/dist/domain/plugin/push.d.ts +133 -0
- package/dist/domain/plugin/push.d.ts.map +1 -0
- package/dist/domain/plugin/push.js +800 -0
- package/dist/domain/plugin/push.js.map +1 -0
- package/dist/domain/plugin/search-extractor.d.ts +3 -0
- package/dist/domain/plugin/search-extractor.d.ts.map +1 -0
- package/dist/domain/plugin/search-extractor.js +41 -0
- package/dist/domain/plugin/search-extractor.js.map +1 -0
- package/dist/domain/skill/constants.d.ts +6 -11
- package/dist/domain/skill/constants.d.ts.map +1 -1
- package/dist/domain/skill/constants.js +6 -11
- package/dist/domain/skill/constants.js.map +1 -1
- package/dist/domain/skill/controller.d.ts.map +1 -1
- package/dist/domain/skill/controller.js +33 -30
- package/dist/domain/skill/controller.js.map +1 -1
- package/dist/domain/skill/push.d.ts +9 -10
- package/dist/domain/skill/push.d.ts.map +1 -1
- package/dist/domain/skill/push.js +53 -122
- package/dist/domain/skill/push.js.map +1 -1
- package/dist/domain/skill/search-extractor.d.ts.map +1 -1
- package/dist/domain/skill/search-extractor.js.map +1 -1
- package/dist/domain/skill/storage/artifact-storage.d.ts +26 -29
- package/dist/domain/skill/storage/artifact-storage.d.ts.map +1 -1
- package/dist/domain/skill/storage/artifact-storage.js +6 -103
- package/dist/domain/skill/storage/artifact-storage.js.map +1 -1
- package/dist/domain/skill/storage/zip-gate.d.ts +1 -2
- package/dist/domain/skill/storage/zip-gate.d.ts.map +1 -1
- package/dist/domain/skill/storage/zip-gate.js +56 -126
- package/dist/domain/skill/storage/zip-gate.js.map +1 -1
- package/dist/domain/skill/version-resolution.d.ts +5 -5
- package/dist/domain/skill/version-resolution.d.ts.map +1 -1
- package/dist/domain/skill/version-resolution.js +34 -216
- package/dist/domain/skill/version-resolution.js.map +1 -1
- package/dist/domain/workflow/controller.d.ts.map +1 -1
- package/dist/domain/workflow/controller.js +30 -3
- package/dist/domain/workflow/controller.js.map +1 -1
- package/dist/domain/workflow/registry/data/task-kind-registry.json +1 -0
- package/dist/extensions/authorizer.d.ts +10 -5
- package/dist/extensions/authorizer.d.ts.map +1 -1
- package/dist/extensions/drivers.d.ts +4 -1
- package/dist/extensions/drivers.d.ts.map +1 -1
- package/dist/extensions/list-read-scope.d.ts +75 -11
- package/dist/extensions/list-read-scope.d.ts.map +1 -1
- package/dist/extensions/list-read-scope.js +40 -7
- package/dist/extensions/list-read-scope.js.map +1 -1
- package/dist/extensions/organization-directory.d.ts +13 -9
- package/dist/extensions/organization-directory.d.ts.map +1 -1
- package/dist/extensions/organization-directory.js.map +1 -1
- package/dist/extensions/resource-authorization.d.ts +33 -0
- package/dist/extensions/resource-authorization.d.ts.map +1 -1
- package/dist/extensions/schedule-fire-caller.d.ts +45 -18
- package/dist/extensions/schedule-fire-caller.d.ts.map +1 -1
- package/dist/extensions/schedule-fire-caller.js +12 -1
- package/dist/extensions/schedule-fire-caller.js.map +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/pipeline/apiresource-labels.d.ts +23 -3
- package/dist/pipeline/apiresource-labels.d.ts.map +1 -1
- package/dist/pipeline/apiresource-labels.js +20 -0
- package/dist/pipeline/apiresource-labels.js.map +1 -1
- package/dist/pipeline/apiresource-meta.d.ts +21 -0
- package/dist/pipeline/apiresource-meta.d.ts.map +1 -1
- package/dist/pipeline/apiresource-meta.js +28 -0
- package/dist/pipeline/apiresource-meta.js.map +1 -1
- package/dist/pipeline/errors.d.ts.map +1 -1
- package/dist/pipeline/errors.js.map +1 -1
- package/dist/pipeline/steps/authorization-facts.d.ts +12 -0
- package/dist/pipeline/steps/authorization-facts.d.ts.map +1 -0
- package/dist/pipeline/steps/authorization-facts.js +100 -0
- package/dist/pipeline/steps/authorization-facts.js.map +1 -0
- package/dist/pipeline/steps/authorization-tuples.d.ts.map +1 -1
- package/dist/pipeline/steps/authorization-tuples.js +7 -26
- package/dist/pipeline/steps/authorization-tuples.js.map +1 -1
- package/dist/pipeline/steps/delete.d.ts.map +1 -1
- package/dist/pipeline/steps/delete.js +1 -1
- package/dist/pipeline/steps/delete.js.map +1 -1
- package/dist/pipeline/steps/duplicate.d.ts.map +1 -1
- package/dist/pipeline/steps/duplicate.js.map +1 -1
- package/dist/pipeline/steps/guard-plugin-managed.d.ts +12 -0
- package/dist/pipeline/steps/guard-plugin-managed.d.ts.map +1 -0
- package/dist/pipeline/steps/guard-plugin-managed.js +96 -0
- package/dist/pipeline/steps/guard-plugin-managed.js.map +1 -0
- package/dist/pipeline/steps/guard-reserved-labels.d.ts +16 -2
- package/dist/pipeline/steps/guard-reserved-labels.d.ts.map +1 -1
- package/dist/pipeline/steps/guard-reserved-labels.js +11 -7
- package/dist/pipeline/steps/guard-reserved-labels.js.map +1 -1
- package/dist/pipeline/steps/helpers.d.ts.map +1 -1
- package/dist/pipeline/steps/helpers.js.map +1 -1
- package/dist/pipeline/steps/index-search.d.ts.map +1 -1
- package/dist/pipeline/steps/index-search.js.map +1 -1
- package/dist/pipeline/steps/load-existing.d.ts.map +1 -1
- package/dist/pipeline/steps/load-existing.js +1 -1
- package/dist/pipeline/steps/load-existing.js.map +1 -1
- package/dist/pipeline/steps/references.d.ts +16 -0
- package/dist/pipeline/steps/references.d.ts.map +1 -1
- package/dist/pipeline/steps/references.js +18 -0
- package/dist/pipeline/steps/references.js.map +1 -1
- package/dist/pipeline/steps/resolve-artifact-source.d.ts +35 -0
- package/dist/pipeline/steps/resolve-artifact-source.d.ts.map +1 -0
- package/dist/pipeline/steps/resolve-artifact-source.js +27 -0
- package/dist/pipeline/steps/resolve-artifact-source.js.map +1 -0
- package/dist/pipeline/steps/server-stamped-reserved-labels.d.ts.map +1 -1
- package/dist/pipeline/steps/server-stamped-reserved-labels.js.map +1 -1
- package/dist/pipeline/steps/shapes.d.ts +18 -0
- package/dist/pipeline/steps/shapes.d.ts.map +1 -1
- package/dist/pipeline/steps/shapes.js +29 -0
- package/dist/pipeline/steps/shapes.js.map +1 -1
- package/dist/pipeline/steps/slug.d.ts.map +1 -1
- package/dist/pipeline/steps/slug.js.map +1 -1
- package/dist/pipeline/steps/validate-visibility.d.ts +9 -0
- package/dist/pipeline/steps/validate-visibility.d.ts.map +1 -1
- package/dist/pipeline/steps/validate-visibility.js +6 -1
- package/dist/pipeline/steps/validate-visibility.js.map +1 -1
- package/dist/pipeline/steps/validation.d.ts.map +1 -1
- package/dist/pipeline/steps/validation.js.map +1 -1
- package/dist/pipeline/steps/version-archive.d.ts +63 -0
- package/dist/pipeline/steps/version-archive.d.ts.map +1 -0
- package/dist/pipeline/steps/version-archive.js +129 -0
- package/dist/pipeline/steps/version-archive.js.map +1 -0
- package/dist/pipeline/steps/version-history.d.ts +49 -0
- package/dist/pipeline/steps/version-history.d.ts.map +1 -0
- package/dist/pipeline/steps/version-history.js +229 -0
- package/dist/pipeline/steps/version-history.js.map +1 -0
- package/dist/pipeline/steps/visibility-gates.d.ts +8 -0
- package/dist/pipeline/steps/visibility-gates.d.ts.map +1 -1
- package/dist/pipeline/steps/visibility-gates.js +7 -2
- package/dist/pipeline/steps/visibility-gates.js.map +1 -1
- package/dist/query/search/registry.d.ts.map +1 -1
- package/dist/query/search/registry.js +2 -0
- package/dist/query/search/registry.js.map +1 -1
- package/dist/temporal/schedule/run-starter.d.ts +1 -1
- package/dist/temporal/schedule/run-starter.d.ts.map +1 -1
- package/dist/temporal/schedule/run-starter.js +14 -1
- package/dist/temporal/schedule/run-starter.js.map +1 -1
- package/package.json +5 -4
- package/src/archive/__tests__/archive.test.ts +233 -0
- package/src/{domain/skill → archive}/__tests__/prefilter.test.ts +1 -1
- package/src/archive/content-store.ts +132 -0
- package/src/archive/inflate.ts +107 -0
- package/src/archive/limits.ts +42 -0
- package/src/archive/open.ts +131 -0
- package/src/{domain/skill/storage → archive}/prefilter.ts +5 -0
- package/src/archive/write.ts +54 -0
- package/src/authorization/README.md +124 -0
- package/src/authorization/__tests__/authorizer.test.ts +719 -0
- package/src/authorization/__tests__/derived-tuples.test.ts +802 -0
- package/src/authorization/__tests__/drivers.ts +70 -0
- package/src/authorization/__tests__/evaluator.test.ts +359 -0
- package/src/authorization/__tests__/facts.test.ts +155 -0
- package/src/authorization/__tests__/fixtures/fga/agent-channel-owner.fga.yaml +112 -0
- package/src/authorization/__tests__/fixtures/fga/agent-share-owner.fga.yaml +109 -0
- package/src/authorization/__tests__/fixtures/fga/artifact-org-and-owner.fga.yaml +128 -0
- package/src/authorization/__tests__/fixtures/fga/blueprint-private-visibility.fga.yaml +410 -0
- package/src/authorization/__tests__/fixtures/fga/channel-app-owner.fga.yaml +125 -0
- package/src/authorization/__tests__/fixtures/fga/channel-participation.fga.yaml +201 -0
- package/src/authorization/__tests__/fixtures/fga/channel-session-visibility.fga.yaml +187 -0
- 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 +271 -0
- package/src/authorization/__tests__/fixtures/fga/memory-subject-only.fga.yaml +72 -0
- package/src/authorization/__tests__/fixtures/fga/org-admin-owner-inheritance.fga.yaml +328 -0
- package/src/authorization/__tests__/fixtures/fga/org-shared-environment.fga.yaml +144 -0
- package/src/authorization/__tests__/fixtures/fga/platform-visibility.fga.yaml +223 -0
- package/src/authorization/__tests__/fixtures/fga/provider-standing-viewer.fga.yaml +40 -0
- package/src/authorization/__tests__/fixtures/fga/public-visibility-setter.fga.yaml +52 -0
- package/src/authorization/__tests__/fixtures/fga/reserved-label-writer.fga.yaml +41 -0
- package/src/authorization/__tests__/fixtures/fga/schedule-owner.fga.yaml +113 -0
- package/src/authorization/__tests__/fixtures/fga/schedule-session-visibility.fga.yaml +214 -0
- package/src/authorization/__tests__/fixtures/fga/session-personal-resource.fga.yaml +117 -0
- package/src/authorization/__tests__/fixtures/fga/workflow-execution-sharing.fga.yaml +192 -0
- package/src/authorization/__tests__/list-read-scope.measure.test.ts +317 -0
- package/src/authorization/__tests__/list-read-scope.test.ts +796 -0
- package/src/authorization/__tests__/organization-directory.test.ts +236 -0
- package/src/authorization/__tests__/person.test.ts +114 -0
- package/src/authorization/__tests__/posture.test.ts +25 -0
- package/src/authorization/__tests__/schedule-fire-caller.test.ts +134 -0
- package/src/authorization/__tests__/store-tests.test.ts +306 -0
- package/src/authorization/__tests__/support.ts +101 -0
- package/src/authorization/__tests__/tuples.test.ts +62 -0
- package/src/authorization/__tests__/wire-permissions.test.ts +154 -0
- package/src/authorization/authorizer.ts +208 -0
- package/src/authorization/derived-tuples.ts +384 -0
- package/src/authorization/evaluator.ts +283 -0
- package/src/authorization/facts.ts +47 -0
- package/src/authorization/list-read-scope.ts +216 -0
- package/src/authorization/model/__tests__/registry.test.ts +317 -0
- package/src/authorization/model/agent.ts +64 -0
- package/src/authorization/model/agent_channel.ts +49 -0
- package/src/authorization/model/agent_execution.ts +33 -0
- package/src/authorization/model/agent_instance.ts +66 -0
- package/src/authorization/model/agent_share.ts +40 -0
- package/src/authorization/model/api_key.ts +28 -0
- package/src/authorization/model/artifact.ts +38 -0
- package/src/authorization/model/channel_app.ts +39 -0
- package/src/authorization/model/default-of.ts +99 -0
- package/src/authorization/model/environment.ts +48 -0
- package/src/authorization/model/execution_context.ts +20 -0
- package/src/authorization/model/iam_policy.ts +42 -0
- package/src/authorization/model/identity_account.ts +26 -0
- package/src/authorization/model/index.ts +108 -0
- package/src/authorization/model/mcp_server.ts +59 -0
- package/src/authorization/model/memory.ts +27 -0
- package/src/authorization/model/oauth_app.ts +42 -0
- package/src/authorization/model/organization.ts +55 -0
- package/src/authorization/model/plugin.ts +66 -0
- package/src/authorization/model/project.ts +47 -0
- package/src/authorization/model/rewrite.ts +213 -0
- package/src/authorization/model/schedule.ts +40 -0
- package/src/authorization/model/session.ts +50 -0
- package/src/authorization/model/skill.ts +60 -0
- package/src/authorization/model/workflow.ts +60 -0
- package/src/authorization/model/workflow_execution.ts +44 -0
- package/src/authorization/model/workflow_instance.ts +122 -0
- package/src/authorization/organization-directory.ts +114 -0
- package/src/authorization/person.ts +68 -0
- package/src/authorization/posture.ts +49 -0
- package/src/authorization/schedule-fire-caller.ts +91 -0
- package/src/authorization/store-test-kit.ts +454 -0
- package/src/authorization/tuples.ts +190 -0
- package/src/boot/compose.ts +175 -38
- package/src/boot/inprocess.ts +108 -30
- package/src/domain/agent/controller.ts +11 -0
- package/src/domain/iampolicy/__tests__/membership.test.ts +357 -3
- package/src/domain/iampolicy/constants.ts +12 -0
- package/src/domain/iampolicy/membership.ts +221 -66
- package/src/domain/identityaccount/actor.ts +44 -0
- package/src/domain/mcpserver/controller.ts +11 -0
- package/src/domain/plugin/__tests__/materialize.test.ts +303 -0
- package/src/domain/plugin/__tests__/overlay-parse.test.ts +85 -0
- package/src/domain/plugin/__tests__/plugin.test.ts +681 -0
- package/src/domain/plugin/__tests__/sanitize.test.ts +174 -0
- package/src/domain/plugin/archive.ts +89 -0
- package/src/domain/plugin/constants.ts +53 -0
- package/src/domain/plugin/controller.ts +913 -0
- package/src/domain/plugin/materialize/agent.ts +186 -0
- package/src/domain/plugin/materialize/default-agent-instructions.ts +59 -0
- package/src/domain/plugin/materialize/identity.ts +67 -0
- package/src/domain/plugin/materialize/mcp-servers.ts +175 -0
- package/src/domain/plugin/materialize/plan.ts +124 -0
- package/src/domain/plugin/materialize/ports.ts +53 -0
- package/src/domain/plugin/materialize/skills.ts +68 -0
- package/src/domain/plugin/materialize/workflows.ts +51 -0
- package/src/domain/plugin/members.ts +180 -0
- package/src/domain/plugin/overlay/documents.ts +78 -0
- package/src/domain/plugin/overlay/parse.ts +115 -0
- package/src/domain/plugin/overlay/sanitize.ts +149 -0
- package/src/domain/plugin/push.ts +1062 -0
- package/src/domain/plugin/search-extractor.ts +60 -0
- package/src/domain/skill/constants.ts +11 -14
- package/src/domain/skill/controller.ts +44 -36
- package/src/domain/skill/push.ts +61 -156
- package/src/domain/skill/search-extractor.ts +3 -1
- package/src/domain/skill/storage/artifact-storage.ts +23 -124
- package/src/domain/skill/storage/zip-gate.ts +58 -154
- package/src/domain/skill/version-resolution.ts +67 -269
- package/src/domain/workflow/controller.ts +38 -4
- package/src/domain/workflow/registry/data/task-kind-registry.json +1 -0
- package/src/extensions/__tests__/built-in-authorization-composed.test.ts +835 -0
- package/src/extensions/__tests__/composed-support.ts +32 -1
- package/src/extensions/__tests__/extension-composition.test.ts +35 -0
- package/src/extensions/__tests__/list-read-scope.test.ts +245 -3
- package/src/extensions/__tests__/role-reconciliation-composed.test.ts +209 -0
- package/src/extensions/__tests__/tier-truthfulness.test.ts +6 -14
- package/src/extensions/authorizer.ts +10 -5
- package/src/extensions/drivers.ts +4 -1
- package/src/extensions/list-read-scope.ts +116 -16
- package/src/extensions/organization-directory.ts +13 -9
- package/src/extensions/resource-authorization.ts +34 -0
- package/src/extensions/schedule-fire-caller.ts +49 -18
- package/src/index.ts +51 -1
- package/src/pipeline/__tests__/inherited-authorization-parent.test.ts +51 -0
- package/src/pipeline/__tests__/router-transport.test.ts +1 -3
- package/src/pipeline/apiresource-labels.ts +31 -3
- package/src/pipeline/apiresource-meta.ts +37 -0
- package/src/pipeline/errors.ts +5 -5
- package/src/pipeline/steps/__tests__/authorize-annotation-completeness.test.ts +1 -20
- package/src/pipeline/steps/__tests__/guard-memory-capture.test.ts +3 -9
- package/src/pipeline/steps/__tests__/guard-plugin-managed.test.ts +151 -0
- package/src/pipeline/steps/__tests__/guard-reserved-labels.test.ts +83 -8
- package/src/pipeline/steps/authorization-facts.ts +131 -0
- package/src/pipeline/steps/authorization-tuples.ts +7 -28
- package/src/pipeline/steps/delete.ts +11 -3
- package/src/pipeline/steps/duplicate.ts +8 -2
- package/src/pipeline/steps/guard-plugin-managed.ts +133 -0
- package/src/pipeline/steps/guard-reserved-labels.ts +29 -8
- package/src/pipeline/steps/helpers.ts +4 -1
- package/src/pipeline/steps/index-search.ts +28 -15
- package/src/pipeline/steps/load-existing.ts +12 -3
- package/src/pipeline/steps/references.ts +36 -2
- package/src/pipeline/steps/resolve-artifact-source.ts +66 -0
- package/src/pipeline/steps/server-stamped-reserved-labels.ts +2 -1
- package/src/pipeline/steps/shapes.ts +31 -0
- package/src/pipeline/steps/slug.ts +7 -2
- package/src/pipeline/steps/validate-visibility.ts +9 -2
- package/src/pipeline/steps/validation.ts +3 -1
- package/src/pipeline/steps/version-archive.ts +225 -0
- package/src/pipeline/steps/version-history.ts +362 -0
- package/src/pipeline/steps/visibility-gates.ts +12 -9
- package/src/query/README.md +1 -1
- package/src/query/search/__tests__/criteria.test.ts +6 -7
- package/src/query/search/__tests__/query-store.test.ts +9 -2
- package/src/query/search/__tests__/registry.test.ts +6 -4
- package/src/query/search/registry.ts +6 -1
- package/src/temporal/schedule/__tests__/run-starter.test.ts +86 -24
- package/src/temporal/schedule/run-starter.ts +31 -8
- package/dist/domain/skill/storage/prefilter.d.ts.map +0 -1
- package/dist/domain/skill/storage/prefilter.js.map +0 -1
- /package/dist/{domain/skill/storage → archive}/prefilter.js +0 -0
|
@@ -0,0 +1,1062 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin push pipeline — the steps that turn a PushPluginRequest into an
|
|
3
|
+
* installed plugin, in two chains the controller runs back to back over
|
|
4
|
+
* one request context:
|
|
5
|
+
*
|
|
6
|
+
* plan: Authorize → ValidateProto → ResolveArtifactSource →
|
|
7
|
+
* GateAndHashArchive → ReadPluginPackage → BuildInitialPlugin →
|
|
8
|
+
* FindExistingBySlug → GenerateIDIfNeeded → ParseOverlayDocuments →
|
|
9
|
+
* SanitizePluginMetadata → PlanMaterialization →
|
|
10
|
+
* GuardPluginVisibility → ResolveConvergence
|
|
11
|
+
* install: CheckAndStoreArtifact → PopulatePluginFields →
|
|
12
|
+
* ArchiveCurrentPlugin → StorePlugin (INSTALLING) →
|
|
13
|
+
* MaterializeMembers → RemoveDroppedMembers →
|
|
14
|
+
* FinalizePluginStatus → StorePlugin → PluginPushAuthorizationTuples →
|
|
15
|
+
* IndexPluginSearch
|
|
16
|
+
*
|
|
17
|
+
* The split is the apply chain's shape (plan, then branch): when the plan
|
|
18
|
+
* chain finds the stored plugin already converged on this archive (same
|
|
19
|
+
* digest, READY, every member present and stamped with it) the controller
|
|
20
|
+
* returns the head and the install chain never runs, so a re-push writes
|
|
21
|
+
* nothing. Everything that can refuse — the archive, the package, the
|
|
22
|
+
* overlay, a reserved label, a held slug, a missing permission, the level
|
|
23
|
+
* — refuses in the plan chain BEFORE any write; the install chain's own
|
|
24
|
+
* failures are a child's, wrapped with the plugin's name, and leave a head
|
|
25
|
+
* that says FAILED so the next push of the same archive converges.
|
|
26
|
+
*
|
|
27
|
+
* Skill push is the precedent (domain/skill/push.ts): content addressing,
|
|
28
|
+
* repoint-never-duplicate, the audit tag as the single holder, non-
|
|
29
|
+
* transactional materialisation that a retry converges. What is a
|
|
30
|
+
* plugin's: the library reads the package, the plan is checked whole
|
|
31
|
+
* before the first child write, and every child is written through the
|
|
32
|
+
* in-process lane as the installing caller.
|
|
33
|
+
*
|
|
34
|
+
* Proven by __tests__/plugin.test.ts (composed-server round-trips) and the
|
|
35
|
+
* plugin conformance suite.
|
|
36
|
+
*/
|
|
37
|
+
import { create } from "@bufbuild/protobuf";
|
|
38
|
+
|
|
39
|
+
import type { PluginFinding, PluginPackage } from "@stigmer/plugin-package";
|
|
40
|
+
import { readPluginPackage } from "@stigmer/plugin-package";
|
|
41
|
+
import { PluginSchema } from "@stigmer/protos/ai/stigmer/agentic/plugin/v1/api_pb";
|
|
42
|
+
import type { Plugin } from "@stigmer/protos/ai/stigmer/agentic/plugin/v1/api_pb";
|
|
43
|
+
import type { PushPluginRequestSchema } from "@stigmer/protos/ai/stigmer/agentic/plugin/v1/io_pb";
|
|
44
|
+
import {
|
|
45
|
+
PluginAuthorSchema,
|
|
46
|
+
PluginDialect,
|
|
47
|
+
PluginSpecSchema,
|
|
48
|
+
} from "@stigmer/protos/ai/stigmer/agentic/plugin/v1/spec_pb";
|
|
49
|
+
import {
|
|
50
|
+
PluginMaterializationSchema,
|
|
51
|
+
PluginState,
|
|
52
|
+
PluginStatusSchema,
|
|
53
|
+
PluginWarningSchema,
|
|
54
|
+
} from "@stigmer/protos/ai/stigmer/agentic/plugin/v1/status_pb";
|
|
55
|
+
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
56
|
+
import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
|
|
57
|
+
import {
|
|
58
|
+
ApiResourceMetadataSchema,
|
|
59
|
+
ApiResourceMetadataVersionSchema,
|
|
60
|
+
} from "@stigmer/protos/ai/stigmer/commons/apiresource/metadata_pb";
|
|
61
|
+
import { ApiResourceAuditSchema } from "@stigmer/protos/ai/stigmer/commons/apiresource/status_pb";
|
|
62
|
+
import { IamPermission } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
|
|
63
|
+
import { Code, ConnectError } from "@connectrpc/connect";
|
|
64
|
+
|
|
65
|
+
import type { Logger } from "../../boot/logger.js";
|
|
66
|
+
import type { ContentAddressedArchiveStore } from "../../archive/content-store.js";
|
|
67
|
+
import type { Authorizer } from "../../extensions/authorizer.js";
|
|
68
|
+
import type { ResourceAuthorizationLifecycle } from "../../extensions/resource-authorization.js";
|
|
69
|
+
import {
|
|
70
|
+
defaultVisibilityFor,
|
|
71
|
+
getIdPrefix,
|
|
72
|
+
} from "../../pipeline/apiresource-meta.js";
|
|
73
|
+
import {
|
|
74
|
+
alreadyExistsError,
|
|
75
|
+
internalError,
|
|
76
|
+
invalidArgumentError,
|
|
77
|
+
} from "../../pipeline/errors.js";
|
|
78
|
+
import type { PipelineStep } from "../../pipeline/pipeline.js";
|
|
79
|
+
import type { RequestContext } from "../../pipeline/request-context.js";
|
|
80
|
+
import {
|
|
81
|
+
diffVisibilityShapes,
|
|
82
|
+
resolveResourceCreatedEvent,
|
|
83
|
+
} from "../../pipeline/steps/authorization-tuples.js";
|
|
84
|
+
import {
|
|
85
|
+
generateId,
|
|
86
|
+
setAuditFieldsForCreate,
|
|
87
|
+
setAuditFieldsForUpdate,
|
|
88
|
+
} from "../../pipeline/steps/defaults.js";
|
|
89
|
+
import { findResourceBySlug } from "../../pipeline/steps/helpers.js";
|
|
90
|
+
import { ARTIFACT_BYTES_KEY } from "../../pipeline/steps/resolve-artifact-source.js";
|
|
91
|
+
import { generateSlug } from "../../pipeline/steps/slug.js";
|
|
92
|
+
import { rejectUnsupportedVisibility } from "../../pipeline/steps/validate-visibility.js";
|
|
93
|
+
import { newArchiveCurrentVersionStep } from "../../pipeline/steps/version-archive.js";
|
|
94
|
+
import { requireOperatorMaySetPublic } from "../../pipeline/steps/visibility-gates.js";
|
|
95
|
+
import { ResourceNotFoundError } from "../../store/interface.js";
|
|
96
|
+
import type { Store } from "../../store/interface.js";
|
|
97
|
+
import { openPluginArchive } from "./archive.js";
|
|
98
|
+
import { VERSION_TAG_PATTERN } from "./constants.js";
|
|
99
|
+
import type { OpenedPluginArchive } from "./archive.js";
|
|
100
|
+
import type { PluginIdentity } from "./materialize/identity.js";
|
|
101
|
+
import { McpServerOverlayError } from "./materialize/mcp-servers.js";
|
|
102
|
+
import { planMaterialization } from "./materialize/plan.js";
|
|
103
|
+
import type { MaterializationPlan } from "./materialize/plan.js";
|
|
104
|
+
import type { PluginMaterializerProvider } from "./materialize/ports.js";
|
|
105
|
+
import {
|
|
106
|
+
droppedMembers,
|
|
107
|
+
findMembers,
|
|
108
|
+
membersConverge,
|
|
109
|
+
slugHolder,
|
|
110
|
+
} from "./members.js";
|
|
111
|
+
import type { Member } from "./members.js";
|
|
112
|
+
import { parseOverlays } from "./overlay/documents.js";
|
|
113
|
+
import type { ParsedOverlays } from "./overlay/documents.js";
|
|
114
|
+
import { OverlayParseError } from "./overlay/parse.js";
|
|
115
|
+
import { sanitizeOverlays } from "./overlay/sanitize.js";
|
|
116
|
+
import { pluginSearchExtractor } from "./search-extractor.js";
|
|
117
|
+
|
|
118
|
+
type PushDesc = typeof PushPluginRequestSchema;
|
|
119
|
+
|
|
120
|
+
// Context keys for the push operation.
|
|
121
|
+
export const PLUGIN_KEY = "plugin";
|
|
122
|
+
export const PLUGIN_ARCHIVE_KEY = "pluginArchive";
|
|
123
|
+
export const PLUGIN_PACKAGE_KEY = "pluginPackage";
|
|
124
|
+
export const PLUGIN_LIBRARY_WARNINGS_KEY = "pluginLibraryWarnings";
|
|
125
|
+
export const EXISTING_PLUGIN_KEY = "existingPlugin";
|
|
126
|
+
export const SHOULD_CREATE_PLUGIN_KEY = "shouldCreatePlugin";
|
|
127
|
+
export const PLUGIN_OVERLAYS_KEY = "pluginOverlays";
|
|
128
|
+
export const PLUGIN_PLAN_KEY = "pluginPlan";
|
|
129
|
+
export const EXISTING_MEMBERS_KEY = "pluginExistingMembers";
|
|
130
|
+
export const PLUGIN_CONVERGED_KEY = "pluginConverged";
|
|
131
|
+
export const ARTIFACT_STORAGE_KEY_KEY = "artifactStorageKey";
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* GateAndHashArchive — the ZIP gate over the shared archive plumbing:
|
|
135
|
+
* size ceiling, hash, prefilter, structural budgets, and the lazy reader
|
|
136
|
+
* the library reads through. Every gate failure is one InvalidArgument arm.
|
|
137
|
+
*/
|
|
138
|
+
export function newGateAndHashArchiveStep(): PipelineStep<PushDesc> {
|
|
139
|
+
return {
|
|
140
|
+
name: "GateAndHashArchive",
|
|
141
|
+
execute(ctx: RequestContext<PushDesc>): void {
|
|
142
|
+
const bytes = ctx.get(ARTIFACT_BYTES_KEY) as Uint8Array;
|
|
143
|
+
let archive: OpenedPluginArchive;
|
|
144
|
+
try {
|
|
145
|
+
archive = openPluginArchive(bytes);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
throw invalidArgumentError(
|
|
148
|
+
`failed to read plugin archive: ${error instanceof Error ? error.message : String(error)}`,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
ctx.set(PLUGIN_ARCHIVE_KEY, archive);
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* ReadPluginPackage — the library reads the package; a refusal lists every
|
|
158
|
+
* problem in the sentences `stigmer validate -f` prints, so an author who
|
|
159
|
+
* skipped the offline check reads the same words here.
|
|
160
|
+
*/
|
|
161
|
+
export function newReadPluginPackageStep(): PipelineStep<PushDesc> {
|
|
162
|
+
return {
|
|
163
|
+
name: "ReadPluginPackage",
|
|
164
|
+
execute(ctx: RequestContext<PushDesc>): void {
|
|
165
|
+
const archive = ctx.get(PLUGIN_ARCHIVE_KEY) as OpenedPluginArchive;
|
|
166
|
+
let outcome;
|
|
167
|
+
try {
|
|
168
|
+
outcome = readPluginPackage(archive.files);
|
|
169
|
+
} catch (error) {
|
|
170
|
+
// The reader's own throw: an entry that failed to inflate.
|
|
171
|
+
throw invalidArgumentError(
|
|
172
|
+
`failed to read plugin archive: ${error instanceof Error ? error.message : String(error)}`,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
if (!outcome.ok) {
|
|
176
|
+
const count = outcome.errors.length;
|
|
177
|
+
throw invalidArgumentError(
|
|
178
|
+
`plugin cannot be installed, ${count} problem${count === 1 ? "" : "s"} found:\n` +
|
|
179
|
+
outcome.errors
|
|
180
|
+
.map((finding) => ` - ${finding.message}`)
|
|
181
|
+
.join("\n"),
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
ctx.set(PLUGIN_PACKAGE_KEY, outcome.plugin);
|
|
185
|
+
ctx.set(PLUGIN_LIBRARY_WARNINGS_KEY, outcome.warnings);
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* BuildInitialPlugin — the scaffold: org from the request, name and slug
|
|
192
|
+
* from the manifest. The id and every status field come later.
|
|
193
|
+
*/
|
|
194
|
+
export function newBuildInitialPluginStep(): PipelineStep<PushDesc> {
|
|
195
|
+
return {
|
|
196
|
+
name: "BuildInitialPlugin",
|
|
197
|
+
execute(ctx: RequestContext<PushDesc>): void {
|
|
198
|
+
const pkg = ctx.get(PLUGIN_PACKAGE_KEY) as PluginPackage;
|
|
199
|
+
const slug = generateSlug(pkg.name);
|
|
200
|
+
if (slug === "") {
|
|
201
|
+
throw invalidArgumentError(`invalid plugin name: ${pkg.name}`);
|
|
202
|
+
}
|
|
203
|
+
const plugin = create(PluginSchema, {
|
|
204
|
+
apiVersion: "agentic.stigmer.ai/v1",
|
|
205
|
+
kind: "Plugin",
|
|
206
|
+
metadata: create(ApiResourceMetadataSchema, {
|
|
207
|
+
org: ctx.input.org,
|
|
208
|
+
name: pkg.name,
|
|
209
|
+
slug,
|
|
210
|
+
}),
|
|
211
|
+
spec: create(PluginSpecSchema, {}),
|
|
212
|
+
status: create(PluginStatusSchema, {}),
|
|
213
|
+
});
|
|
214
|
+
ctx.set(PLUGIN_KEY, plugin);
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** FindExistingBySlug — push is upsert-by-slug: an existing plugin's id is adopted. */
|
|
220
|
+
export function newFindExistingPluginBySlugStep(
|
|
221
|
+
store: Store,
|
|
222
|
+
): PipelineStep<PushDesc> {
|
|
223
|
+
return {
|
|
224
|
+
name: "FindExistingBySlug",
|
|
225
|
+
async execute(ctx: RequestContext<PushDesc>): Promise<void> {
|
|
226
|
+
const plugin = ctx.get(PLUGIN_KEY) as Plugin;
|
|
227
|
+
let existing: Plugin | undefined;
|
|
228
|
+
try {
|
|
229
|
+
existing = await findResourceBySlug(
|
|
230
|
+
store,
|
|
231
|
+
ctx.apiResourceKind,
|
|
232
|
+
PluginSchema,
|
|
233
|
+
plugin.metadata!.slug,
|
|
234
|
+
plugin.metadata!.org,
|
|
235
|
+
);
|
|
236
|
+
} catch (error) {
|
|
237
|
+
throw internalError(error, "failed to search for existing plugin");
|
|
238
|
+
}
|
|
239
|
+
if (existing !== undefined) {
|
|
240
|
+
plugin.metadata!.id = existing.metadata!.id;
|
|
241
|
+
ctx.set(EXISTING_PLUGIN_KEY, existing);
|
|
242
|
+
ctx.set(SHOULD_CREATE_PLUGIN_KEY, false);
|
|
243
|
+
} else {
|
|
244
|
+
ctx.set(EXISTING_PLUGIN_KEY, undefined);
|
|
245
|
+
ctx.set(SHOULD_CREATE_PLUGIN_KEY, true);
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** GenerateIDIfNeeded — plg_{ulid} for new plugins; upgrades keep theirs. */
|
|
252
|
+
export function newGeneratePluginIdIfNeededStep(): PipelineStep<PushDesc> {
|
|
253
|
+
return {
|
|
254
|
+
name: "GenerateIDIfNeeded",
|
|
255
|
+
execute(ctx: RequestContext<PushDesc>): void {
|
|
256
|
+
const plugin = ctx.get(PLUGIN_KEY) as Plugin;
|
|
257
|
+
if (ctx.get(SHOULD_CREATE_PLUGIN_KEY) as boolean) {
|
|
258
|
+
plugin.metadata!.id = generateId(getIdPrefix(ctx.apiResourceKind));
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** ParseOverlayDocuments — the `ai.stigmer/` documents become protos, strictly. */
|
|
265
|
+
export function newParseOverlayDocumentsStep(): PipelineStep<PushDesc> {
|
|
266
|
+
return {
|
|
267
|
+
name: "ParseOverlayDocuments",
|
|
268
|
+
execute(ctx: RequestContext<PushDesc>): void {
|
|
269
|
+
const pkg = ctx.get(PLUGIN_PACKAGE_KEY) as PluginPackage;
|
|
270
|
+
try {
|
|
271
|
+
ctx.set(PLUGIN_OVERLAYS_KEY, parseOverlays(pkg.overlay, ctx.input.org));
|
|
272
|
+
} catch (error) {
|
|
273
|
+
if (error instanceof OverlayParseError) {
|
|
274
|
+
throw invalidArgumentError(error.message);
|
|
275
|
+
}
|
|
276
|
+
throw error;
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** SanitizePluginMetadata — what an overlay author may not say (overlay/sanitize.ts). */
|
|
283
|
+
export function newSanitizePluginMetadataStep(
|
|
284
|
+
authorizer: Authorizer,
|
|
285
|
+
): PipelineStep<PushDesc> {
|
|
286
|
+
return {
|
|
287
|
+
name: "SanitizePluginMetadata",
|
|
288
|
+
async execute(ctx: RequestContext<PushDesc>): Promise<void> {
|
|
289
|
+
const pkg = ctx.get(PLUGIN_PACKAGE_KEY) as PluginPackage;
|
|
290
|
+
const overlays = ctx.get(PLUGIN_OVERLAYS_KEY) as ParsedOverlays;
|
|
291
|
+
await sanitizeOverlays(
|
|
292
|
+
overlays,
|
|
293
|
+
{ pluginName: pkg.name },
|
|
294
|
+
authorizer,
|
|
295
|
+
ctx.callerIdentity,
|
|
296
|
+
);
|
|
297
|
+
},
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* The create permission each member kind's own chain will evaluate for the
|
|
303
|
+
* caller, checked here first so a missing one is a sentence before any
|
|
304
|
+
* write. MCP server create carries `is_skip_authorization` in its
|
|
305
|
+
* contract (mcpserver/v1/command.proto), so it has no row.
|
|
306
|
+
*/
|
|
307
|
+
const MEMBER_CREATE_PERMISSIONS: ReadonlyMap<ApiResourceKind, IamPermission> =
|
|
308
|
+
new Map([
|
|
309
|
+
[ApiResourceKind.skill, IamPermission.can_create_skill],
|
|
310
|
+
[ApiResourceKind.agent, IamPermission.can_create_agent],
|
|
311
|
+
[ApiResourceKind.workflow, IamPermission.can_create_workflow],
|
|
312
|
+
]);
|
|
313
|
+
|
|
314
|
+
/** The plugin as its members see it, from the head and the archive. */
|
|
315
|
+
export function identityOf(
|
|
316
|
+
plugin: Plugin,
|
|
317
|
+
digest: string,
|
|
318
|
+
visibility: ApiResourceVisibility,
|
|
319
|
+
): PluginIdentity {
|
|
320
|
+
return {
|
|
321
|
+
org: plugin.metadata!.org,
|
|
322
|
+
id: plugin.metadata!.id,
|
|
323
|
+
slug: plugin.metadata!.slug,
|
|
324
|
+
name: plugin.metadata!.name,
|
|
325
|
+
digest,
|
|
326
|
+
visibility,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** The level this push installs at: the request's, or the kind's default. */
|
|
331
|
+
export function requestedVisibility(
|
|
332
|
+
ctx: RequestContext<PushDesc>,
|
|
333
|
+
): ApiResourceVisibility {
|
|
334
|
+
return ctx.input.visibility ===
|
|
335
|
+
ApiResourceVisibility.api_resource_visibility_unspecified
|
|
336
|
+
? defaultVisibilityFor(ctx.apiResourceKind)
|
|
337
|
+
: ctx.input.visibility;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* PlanMaterialization — everything the push will write, checked whole:
|
|
342
|
+
* every child slug against the organization (free, ours, or refused
|
|
343
|
+
* naming the holder), the caller's permission for every member kind, and
|
|
344
|
+
* the plugin's current members for the convergence and drop decisions.
|
|
345
|
+
*/
|
|
346
|
+
export function newPlanMaterializationStep(
|
|
347
|
+
store: Store,
|
|
348
|
+
authorizer: Authorizer,
|
|
349
|
+
): PipelineStep<PushDesc> {
|
|
350
|
+
return {
|
|
351
|
+
name: "PlanMaterialization",
|
|
352
|
+
async execute(ctx: RequestContext<PushDesc>): Promise<void> {
|
|
353
|
+
const plugin = ctx.get(PLUGIN_KEY) as Plugin;
|
|
354
|
+
const pkg = ctx.get(PLUGIN_PACKAGE_KEY) as PluginPackage;
|
|
355
|
+
const archive = ctx.get(PLUGIN_ARCHIVE_KEY) as OpenedPluginArchive;
|
|
356
|
+
const overlays = ctx.get(PLUGIN_OVERLAYS_KEY) as ParsedOverlays;
|
|
357
|
+
const identity = identityOf(
|
|
358
|
+
plugin,
|
|
359
|
+
archive.digest,
|
|
360
|
+
requestedVisibility(ctx),
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
let plan: MaterializationPlan;
|
|
364
|
+
try {
|
|
365
|
+
plan = planMaterialization(
|
|
366
|
+
pkg,
|
|
367
|
+
archive.files,
|
|
368
|
+
overlays,
|
|
369
|
+
identity,
|
|
370
|
+
ctx.input.message,
|
|
371
|
+
);
|
|
372
|
+
} catch (error) {
|
|
373
|
+
if (error instanceof McpServerOverlayError) {
|
|
374
|
+
throw invalidArgumentError(error.message);
|
|
375
|
+
}
|
|
376
|
+
throw error;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
for (const member of plan.members) {
|
|
380
|
+
let holder;
|
|
381
|
+
try {
|
|
382
|
+
holder = await slugHolder(store, member, identity.org);
|
|
383
|
+
} catch (error) {
|
|
384
|
+
throw internalError(error, "failed to check a member slug");
|
|
385
|
+
}
|
|
386
|
+
if (!holder.held || holder.byPlugin === identity.id) {
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
const noun = memberNoun(member.kind);
|
|
390
|
+
if (holder.byPlugin === undefined) {
|
|
391
|
+
throw alreadyExistsError(
|
|
392
|
+
noun,
|
|
393
|
+
`'${member.slug}' exists in org '${identity.org}' and is not managed by a plugin; rename or delete it first`,
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
throw alreadyExistsError(
|
|
397
|
+
noun,
|
|
398
|
+
`'${member.slug}' is held by plugin '${await pluginSlugOf(store, holder.byPlugin)}'`,
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const missing: string[] = [];
|
|
403
|
+
for (const kind of new Set(plan.members.map((member) => member.kind))) {
|
|
404
|
+
const permission = MEMBER_CREATE_PERMISSIONS.get(kind);
|
|
405
|
+
if (permission === undefined) {
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
let decision;
|
|
409
|
+
try {
|
|
410
|
+
decision = await authorizer.authorize(ctx.callerIdentity, {
|
|
411
|
+
permission,
|
|
412
|
+
resourceKind: ApiResourceKind.organization,
|
|
413
|
+
resourceId: identity.org,
|
|
414
|
+
});
|
|
415
|
+
} catch (error) {
|
|
416
|
+
throw internalError(
|
|
417
|
+
error,
|
|
418
|
+
"plugin install authorization could not be completed",
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
switch (decision.kind) {
|
|
422
|
+
case "allow":
|
|
423
|
+
break;
|
|
424
|
+
case "deny":
|
|
425
|
+
case "not-found":
|
|
426
|
+
missing.push(IamPermission[permission] ?? String(permission));
|
|
427
|
+
break;
|
|
428
|
+
case "unavailable":
|
|
429
|
+
throw internalError(
|
|
430
|
+
decision.cause,
|
|
431
|
+
"plugin install authorization could not be completed",
|
|
432
|
+
);
|
|
433
|
+
default: {
|
|
434
|
+
const exhaustive: never = decision;
|
|
435
|
+
throw internalError(
|
|
436
|
+
new Error(`unknown decision ${JSON.stringify(exhaustive)}`),
|
|
437
|
+
"plugin install authorization could not be completed",
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
if (missing.length > 0) {
|
|
443
|
+
throw new ConnectError(
|
|
444
|
+
`installing plugin '${identity.slug}' needs ${missing.join(", ")} in organization '${identity.org}'`,
|
|
445
|
+
Code.PermissionDenied,
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
let members: Member[];
|
|
450
|
+
try {
|
|
451
|
+
members = await findMembers(store, identity.id, identity.org);
|
|
452
|
+
} catch (error) {
|
|
453
|
+
throw internalError(error, "failed to list plugin members");
|
|
454
|
+
}
|
|
455
|
+
ctx.set(PLUGIN_PLAN_KEY, plan);
|
|
456
|
+
ctx.set(EXISTING_MEMBERS_KEY, members);
|
|
457
|
+
},
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* GuardPluginVisibility — the requested level must be one the plugin and
|
|
463
|
+
* every planned member kind support, and PUBLIC needs the operator grant
|
|
464
|
+
* (the one owner of that question, shared with every other create door).
|
|
465
|
+
* Each member's own chain will check again in-process; refusing here keeps
|
|
466
|
+
* the head from being written before the answer is known.
|
|
467
|
+
*/
|
|
468
|
+
export function newGuardPluginVisibilityStep(
|
|
469
|
+
authorizer: Authorizer,
|
|
470
|
+
): PipelineStep<PushDesc> {
|
|
471
|
+
return {
|
|
472
|
+
name: "GuardPluginVisibility",
|
|
473
|
+
async execute(ctx: RequestContext<PushDesc>): Promise<void> {
|
|
474
|
+
const level = requestedVisibility(ctx);
|
|
475
|
+
const plan = ctx.get(PLUGIN_PLAN_KEY) as MaterializationPlan;
|
|
476
|
+
rejectUnsupportedVisibility(ctx.apiResourceKind, level);
|
|
477
|
+
for (const kind of new Set(plan.members.map((member) => member.kind))) {
|
|
478
|
+
rejectUnsupportedVisibility(kind, level);
|
|
479
|
+
}
|
|
480
|
+
if (level !== ApiResourceVisibility.visibility_public) {
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
if (ctx.callerIdentity.callerClass === "internal") {
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
await requireOperatorMaySetPublic(authorizer, ctx.callerIdentity);
|
|
487
|
+
},
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* ResolveConvergence — the no-op decision: the stored plugin already IS
|
|
493
|
+
* this archive when its digest matches, it is READY, and every planned
|
|
494
|
+
* member exists stamped with the digest. Anything less installs.
|
|
495
|
+
*/
|
|
496
|
+
export function newResolveConvergenceStep(): PipelineStep<PushDesc> {
|
|
497
|
+
return {
|
|
498
|
+
name: "ResolveConvergence",
|
|
499
|
+
execute(ctx: RequestContext<PushDesc>): void {
|
|
500
|
+
const existing = ctx.get(EXISTING_PLUGIN_KEY) as Plugin | undefined;
|
|
501
|
+
const archive = ctx.get(PLUGIN_ARCHIVE_KEY) as OpenedPluginArchive;
|
|
502
|
+
const plan = ctx.get(PLUGIN_PLAN_KEY) as MaterializationPlan;
|
|
503
|
+
const members = ctx.get(EXISTING_MEMBERS_KEY) as Member[];
|
|
504
|
+
const converged =
|
|
505
|
+
existing !== undefined &&
|
|
506
|
+
existing.status?.digest === archive.digest &&
|
|
507
|
+
existing.status.state === PluginState.READY &&
|
|
508
|
+
(existing.metadata?.visibility ??
|
|
509
|
+
ApiResourceVisibility.api_resource_visibility_unspecified) ===
|
|
510
|
+
requestedVisibility(ctx) &&
|
|
511
|
+
membersConverge(members, plan.members, archive.digest);
|
|
512
|
+
ctx.set(PLUGIN_CONVERGED_KEY, converged);
|
|
513
|
+
},
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/** CheckAndStoreArtifact — content-addressed dedupe: same digest reuses the stored copy. */
|
|
518
|
+
export function newCheckAndStoreArtifactStep(
|
|
519
|
+
artifactStorage: ContentAddressedArchiveStore,
|
|
520
|
+
): PipelineStep<PushDesc> {
|
|
521
|
+
return {
|
|
522
|
+
name: "CheckAndStoreArtifact",
|
|
523
|
+
async execute(ctx: RequestContext<PushDesc>): Promise<void> {
|
|
524
|
+
const bytes = ctx.get(ARTIFACT_BYTES_KEY) as Uint8Array;
|
|
525
|
+
const archive = ctx.get(PLUGIN_ARCHIVE_KEY) as OpenedPluginArchive;
|
|
526
|
+
let exists: boolean;
|
|
527
|
+
try {
|
|
528
|
+
exists = await artifactStorage.exists(archive.digest);
|
|
529
|
+
} catch (error) {
|
|
530
|
+
throw internalError(error, "failed to check artifact existence");
|
|
531
|
+
}
|
|
532
|
+
let storageKey: string;
|
|
533
|
+
if (exists) {
|
|
534
|
+
storageKey = artifactStorage.getStorageKey(archive.digest);
|
|
535
|
+
} else {
|
|
536
|
+
try {
|
|
537
|
+
storageKey = await artifactStorage.store(archive.digest, bytes);
|
|
538
|
+
} catch (error) {
|
|
539
|
+
throw internalError(error, "failed to store artifact");
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
ctx.set(ARTIFACT_STORAGE_KEY_KEY, storageKey);
|
|
543
|
+
},
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* PopulatePluginFields — the manifest into the spec, the archive identity
|
|
549
|
+
* and INSTALLING into the status, the level, the metadata.version chain,
|
|
550
|
+
* and the audit stamping discipline: creates stamp both slots; updates copy
|
|
551
|
+
* the loaded head's slot pointers and stamp spec_audit only (a push is a
|
|
552
|
+
* definition change; the helper sets a fresh spec_audit, never mutating
|
|
553
|
+
* the copied pointer, #540).
|
|
554
|
+
*/
|
|
555
|
+
export function newPopulatePluginFieldsStep(): PipelineStep<PushDesc> {
|
|
556
|
+
return {
|
|
557
|
+
name: "PopulatePluginFields",
|
|
558
|
+
execute(ctx: RequestContext<PushDesc>): void {
|
|
559
|
+
const plugin = ctx.get(PLUGIN_KEY) as Plugin;
|
|
560
|
+
const pkg = ctx.get(PLUGIN_PACKAGE_KEY) as PluginPackage;
|
|
561
|
+
const archive = ctx.get(PLUGIN_ARCHIVE_KEY) as OpenedPluginArchive;
|
|
562
|
+
const storageKey = ctx.get(ARTIFACT_STORAGE_KEY_KEY) as string;
|
|
563
|
+
const shouldCreate = ctx.get(SHOULD_CREATE_PLUGIN_KEY) as boolean;
|
|
564
|
+
const existing = ctx.get(EXISTING_PLUGIN_KEY) as Plugin | undefined;
|
|
565
|
+
|
|
566
|
+
plugin.spec = create(PluginSpecSchema, {
|
|
567
|
+
name: pkg.name,
|
|
568
|
+
version: pkg.version ?? "",
|
|
569
|
+
description: pkg.description ?? "",
|
|
570
|
+
homepage: pkg.homepage ?? "",
|
|
571
|
+
repository: pkg.repository ?? "",
|
|
572
|
+
license: pkg.license ?? "",
|
|
573
|
+
keywords: [...pkg.keywords],
|
|
574
|
+
dialect: dialectOf(pkg.dialect),
|
|
575
|
+
});
|
|
576
|
+
if (pkg.author !== undefined) {
|
|
577
|
+
plugin.spec.author = create(PluginAuthorSchema, {
|
|
578
|
+
name: pkg.author.name ?? "",
|
|
579
|
+
email: pkg.author.email ?? "",
|
|
580
|
+
url: pkg.author.url ?? "",
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
plugin.metadata!.visibility = requestedVisibility(ctx);
|
|
585
|
+
plugin.metadata!.version = create(ApiResourceMetadataVersionSchema, {
|
|
586
|
+
id: archive.digest,
|
|
587
|
+
message: ctx.input.message,
|
|
588
|
+
previousVersionId: existing?.status?.digest ?? "",
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
const status = plugin.status!;
|
|
592
|
+
status.digest = archive.digest;
|
|
593
|
+
status.artifactStorageKey = storageKey;
|
|
594
|
+
status.state = PluginState.INSTALLING;
|
|
595
|
+
status.error = "";
|
|
596
|
+
|
|
597
|
+
if (shouldCreate) {
|
|
598
|
+
setAuditFieldsForCreate(PluginSchema, plugin, ctx.callerIdentity);
|
|
599
|
+
} else {
|
|
600
|
+
if (existing?.status?.audit !== undefined) {
|
|
601
|
+
status.audit ??= create(ApiResourceAuditSchema, {});
|
|
602
|
+
status.audit.specAudit = existing.status.audit.specAudit;
|
|
603
|
+
status.audit.statusAudit = existing.status.audit.statusAudit;
|
|
604
|
+
}
|
|
605
|
+
setAuditFieldsForUpdate(
|
|
606
|
+
PluginSchema,
|
|
607
|
+
plugin,
|
|
608
|
+
"spec_audit",
|
|
609
|
+
ctx.callerIdentity,
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
},
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* ArchiveCurrentPlugin — the shared versioning step bound to the plugin's
|
|
618
|
+
* digest and tag. The live tag IS the manifest version when it fits the
|
|
619
|
+
* tag pattern (the plan already warned when it does not), so the
|
|
620
|
+
* single-holder assignment and the getByReference ladder read one fact;
|
|
621
|
+
* the version is the manifest's word and is never cleared, so a failed
|
|
622
|
+
* tag assignment degrades to "reachable by digest" without touching the
|
|
623
|
+
* spec.
|
|
624
|
+
*/
|
|
625
|
+
export function newArchiveCurrentPluginStep(
|
|
626
|
+
store: Store,
|
|
627
|
+
logger: Logger,
|
|
628
|
+
): PipelineStep<PushDesc> {
|
|
629
|
+
return newArchiveCurrentVersionStep<typeof PluginSchema, PushDesc>(
|
|
630
|
+
store,
|
|
631
|
+
logger,
|
|
632
|
+
{
|
|
633
|
+
stepName: "ArchiveCurrentPlugin",
|
|
634
|
+
resourceKey: PLUGIN_KEY,
|
|
635
|
+
schema: PluginSchema,
|
|
636
|
+
noun: "plugin",
|
|
637
|
+
headHashOf: (plugin) => plugin.status?.digest ?? "",
|
|
638
|
+
clearHeadHash: (plugin) => {
|
|
639
|
+
plugin.status!.digest = "";
|
|
640
|
+
if (plugin.metadata?.version !== undefined) {
|
|
641
|
+
plugin.metadata.version.id = "";
|
|
642
|
+
}
|
|
643
|
+
},
|
|
644
|
+
liveTagOf: pluginLiveTag,
|
|
645
|
+
clearLiveTag: () => undefined,
|
|
646
|
+
},
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/** The tag a plugin head claims: its manifest version when the pattern admits it. */
|
|
651
|
+
export function pluginLiveTag(plugin: Plugin): string {
|
|
652
|
+
const version = plugin.spec?.version ?? "";
|
|
653
|
+
return VERSION_TAG_PATTERN.test(version) ? version : "";
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/** StorePlugin — persists the head; runs twice, before and after materialisation. */
|
|
657
|
+
export function newStorePluginStep(
|
|
658
|
+
store: Store,
|
|
659
|
+
stepName: string,
|
|
660
|
+
): PipelineStep<PushDesc> {
|
|
661
|
+
return {
|
|
662
|
+
name: stepName,
|
|
663
|
+
async execute(ctx: RequestContext<PushDesc>): Promise<void> {
|
|
664
|
+
const plugin = ctx.get(PLUGIN_KEY) as Plugin;
|
|
665
|
+
try {
|
|
666
|
+
await store.saveResource(
|
|
667
|
+
ctx.apiResourceKind,
|
|
668
|
+
plugin.metadata!.id,
|
|
669
|
+
PluginSchema,
|
|
670
|
+
plugin,
|
|
671
|
+
);
|
|
672
|
+
} catch (error) {
|
|
673
|
+
throw internalError(error, "failed to save plugin");
|
|
674
|
+
}
|
|
675
|
+
},
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* MaterializeMembers — skills, MCP servers, the agent, workflows, each
|
|
681
|
+
* through the in-process lane as the installing caller, in the order
|
|
682
|
+
* references resolve. An existing member whose level differs from the
|
|
683
|
+
* plugin's is moved through its kind's updateVisibility afterwards (apply
|
|
684
|
+
* preserves a stored level by contract). A child failure records FAILED on
|
|
685
|
+
* the head, persists it, and fails the request with the plugin named.
|
|
686
|
+
*/
|
|
687
|
+
export function newMaterializeMembersStep(
|
|
688
|
+
store: Store,
|
|
689
|
+
materializerProvider: PluginMaterializerProvider,
|
|
690
|
+
logger: Logger,
|
|
691
|
+
): PipelineStep<PushDesc> {
|
|
692
|
+
return {
|
|
693
|
+
name: "MaterializeMembers",
|
|
694
|
+
async execute(ctx: RequestContext<PushDesc>): Promise<void> {
|
|
695
|
+
const plugin = ctx.get(PLUGIN_KEY) as Plugin;
|
|
696
|
+
const plan = ctx.get(PLUGIN_PLAN_KEY) as MaterializationPlan;
|
|
697
|
+
const materializer = materializerProvider();
|
|
698
|
+
const caller = ctx.callerIdentity;
|
|
699
|
+
const level = requestedVisibility(ctx);
|
|
700
|
+
|
|
701
|
+
const reconcileLevel = async (
|
|
702
|
+
kind: ApiResourceKind,
|
|
703
|
+
applied: {
|
|
704
|
+
readonly metadata?: {
|
|
705
|
+
readonly id: string;
|
|
706
|
+
readonly visibility: ApiResourceVisibility;
|
|
707
|
+
};
|
|
708
|
+
},
|
|
709
|
+
): Promise<void> => {
|
|
710
|
+
const metadata = applied.metadata;
|
|
711
|
+
if (metadata !== undefined && metadata.visibility !== level) {
|
|
712
|
+
await materializer.updateVisibility(kind, metadata.id, level, caller);
|
|
713
|
+
}
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
const materialize = async <T>(
|
|
717
|
+
kind: ApiResourceKind,
|
|
718
|
+
slug: string,
|
|
719
|
+
write: () => Promise<T>,
|
|
720
|
+
): Promise<T> => {
|
|
721
|
+
try {
|
|
722
|
+
return await write();
|
|
723
|
+
} catch (error) {
|
|
724
|
+
throw await recordFailure(store, ctx, plugin, kind, slug, error);
|
|
725
|
+
}
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
for (const skill of plan.skills) {
|
|
729
|
+
const pushed = await materialize(
|
|
730
|
+
ApiResourceKind.skill,
|
|
731
|
+
skill.slug,
|
|
732
|
+
() => materializer.pushSkill(skill.request, caller),
|
|
733
|
+
);
|
|
734
|
+
await materialize(ApiResourceKind.skill, skill.slug, () =>
|
|
735
|
+
reconcileLevel(ApiResourceKind.skill, pushed),
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
for (const server of plan.mcpServers) {
|
|
739
|
+
const applied = await materialize(
|
|
740
|
+
ApiResourceKind.mcp_server,
|
|
741
|
+
server.slug,
|
|
742
|
+
() => materializer.applyMcpServer(server.resource, caller),
|
|
743
|
+
);
|
|
744
|
+
await materialize(ApiResourceKind.mcp_server, server.slug, () =>
|
|
745
|
+
reconcileLevel(ApiResourceKind.mcp_server, applied),
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
if (plan.agent !== undefined) {
|
|
749
|
+
const agent = plan.agent;
|
|
750
|
+
const applied = await materialize(
|
|
751
|
+
ApiResourceKind.agent,
|
|
752
|
+
agent.slug,
|
|
753
|
+
() => materializer.applyAgent(agent.resource, caller),
|
|
754
|
+
);
|
|
755
|
+
await materialize(ApiResourceKind.agent, agent.slug, () =>
|
|
756
|
+
reconcileLevel(ApiResourceKind.agent, applied),
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
for (const workflow of plan.workflows) {
|
|
760
|
+
const applied = await materialize(
|
|
761
|
+
ApiResourceKind.workflow,
|
|
762
|
+
workflow.slug,
|
|
763
|
+
() => materializer.applyWorkflow(workflow.resource, caller),
|
|
764
|
+
);
|
|
765
|
+
await materialize(ApiResourceKind.workflow, workflow.slug, () =>
|
|
766
|
+
reconcileLevel(ApiResourceKind.workflow, applied),
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
logger.info("Materialized plugin members", {
|
|
771
|
+
pluginId: plugin.metadata!.id,
|
|
772
|
+
skills: plan.skills.length,
|
|
773
|
+
mcpServers: plan.mcpServers.length,
|
|
774
|
+
agents: plan.agent === undefined ? 0 : 1,
|
|
775
|
+
workflows: plan.workflows.length,
|
|
776
|
+
});
|
|
777
|
+
},
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
/**
|
|
782
|
+
* RemoveDroppedMembers — an upgrade whose archive no longer names a member
|
|
783
|
+
* deletes it through the member's own delete chain (agent first, so a
|
|
784
|
+
* workflow or a user's own resource still referencing it is judged by
|
|
785
|
+
* that chain, then the rest in reverse materialisation order).
|
|
786
|
+
*/
|
|
787
|
+
export function newRemoveDroppedMembersStep(
|
|
788
|
+
store: Store,
|
|
789
|
+
materializerProvider: PluginMaterializerProvider,
|
|
790
|
+
logger: Logger,
|
|
791
|
+
): PipelineStep<PushDesc> {
|
|
792
|
+
return {
|
|
793
|
+
name: "RemoveDroppedMembers",
|
|
794
|
+
async execute(ctx: RequestContext<PushDesc>): Promise<void> {
|
|
795
|
+
const plugin = ctx.get(PLUGIN_KEY) as Plugin;
|
|
796
|
+
const plan = ctx.get(PLUGIN_PLAN_KEY) as MaterializationPlan;
|
|
797
|
+
const existing = ctx.get(EXISTING_MEMBERS_KEY) as Member[];
|
|
798
|
+
const dropped = droppedMembers(existing, plan.members);
|
|
799
|
+
if (dropped.length === 0) {
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
const materializer = materializerProvider();
|
|
803
|
+
for (const member of orderForDeletion(dropped)) {
|
|
804
|
+
try {
|
|
805
|
+
await materializer.deleteByKind(
|
|
806
|
+
member.kind,
|
|
807
|
+
member.id,
|
|
808
|
+
ctx.callerIdentity,
|
|
809
|
+
);
|
|
810
|
+
} catch (error) {
|
|
811
|
+
throw await recordFailure(
|
|
812
|
+
store,
|
|
813
|
+
ctx,
|
|
814
|
+
plugin,
|
|
815
|
+
member.kind,
|
|
816
|
+
member.slug,
|
|
817
|
+
error,
|
|
818
|
+
"remove",
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
logger.info("Removed members the new archive dropped", {
|
|
823
|
+
pluginId: plugin.metadata!.id,
|
|
824
|
+
dropped: dropped.map(
|
|
825
|
+
(member) => `${ApiResourceKind[member.kind]}:${member.slug}`,
|
|
826
|
+
),
|
|
827
|
+
});
|
|
828
|
+
},
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
/** Cascade order: the agent first (it references the rest), then reverse materialisation order. */
|
|
833
|
+
export function orderForDeletion(members: readonly Member[]): Member[] {
|
|
834
|
+
const rank = new Map<ApiResourceKind, number>([
|
|
835
|
+
[ApiResourceKind.agent, 0],
|
|
836
|
+
[ApiResourceKind.workflow, 1],
|
|
837
|
+
[ApiResourceKind.mcp_server, 2],
|
|
838
|
+
[ApiResourceKind.skill, 3],
|
|
839
|
+
]);
|
|
840
|
+
return [...members].sort(
|
|
841
|
+
(a, b) => (rank.get(a.kind) ?? 9) - (rank.get(b.kind) ?? 9),
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* FinalizePluginStatus — the install receipt: counts, every warning (the
|
|
847
|
+
* library's and the plan's), READY.
|
|
848
|
+
*/
|
|
849
|
+
export function newFinalizePluginStatusStep(): PipelineStep<PushDesc> {
|
|
850
|
+
return {
|
|
851
|
+
name: "FinalizePluginStatus",
|
|
852
|
+
execute(ctx: RequestContext<PushDesc>): void {
|
|
853
|
+
const plugin = ctx.get(PLUGIN_KEY) as Plugin;
|
|
854
|
+
const plan = ctx.get(PLUGIN_PLAN_KEY) as MaterializationPlan;
|
|
855
|
+
const libraryWarnings = ctx.get(
|
|
856
|
+
PLUGIN_LIBRARY_WARNINGS_KEY,
|
|
857
|
+
) as readonly PluginFinding[];
|
|
858
|
+
const status = plugin.status!;
|
|
859
|
+
status.state = PluginState.READY;
|
|
860
|
+
status.error = "";
|
|
861
|
+
status.materialized = create(PluginMaterializationSchema, {
|
|
862
|
+
skills: plan.skills.length,
|
|
863
|
+
mcpServers: plan.mcpServers.length,
|
|
864
|
+
agents: plan.agent === undefined ? 0 : 1,
|
|
865
|
+
workflows: plan.workflows.length,
|
|
866
|
+
});
|
|
867
|
+
status.warnings = [
|
|
868
|
+
...libraryWarnings.map((finding) =>
|
|
869
|
+
create(PluginWarningSchema, {
|
|
870
|
+
kind: finding.kind,
|
|
871
|
+
message: finding.message,
|
|
872
|
+
path: finding.path ?? "",
|
|
873
|
+
}),
|
|
874
|
+
),
|
|
875
|
+
...plan.warnings,
|
|
876
|
+
];
|
|
877
|
+
},
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* PluginPushAuthorizationTuples — the tuple-lifecycle splice for the push
|
|
883
|
+
* lane, the skill push's shape: a genuine create fires the creation event;
|
|
884
|
+
* a re-push diffs the stored level against the pushed head.
|
|
885
|
+
*/
|
|
886
|
+
export function newPluginPushAuthorizationTuplesStep(
|
|
887
|
+
lifecycle: ResourceAuthorizationLifecycle | undefined,
|
|
888
|
+
logger: Logger,
|
|
889
|
+
): PipelineStep<PushDesc> {
|
|
890
|
+
return {
|
|
891
|
+
name: "PluginPushAuthorizationTuples",
|
|
892
|
+
async execute(ctx: RequestContext<PushDesc>): Promise<void> {
|
|
893
|
+
if (lifecycle === undefined) {
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
const plugin = ctx.get(PLUGIN_KEY) as Plugin;
|
|
897
|
+
if (ctx.get(SHOULD_CREATE_PLUGIN_KEY) as boolean) {
|
|
898
|
+
const event = resolveResourceCreatedEvent(
|
|
899
|
+
ctx.apiResourceKind,
|
|
900
|
+
plugin,
|
|
901
|
+
ctx.callerIdentity,
|
|
902
|
+
logger,
|
|
903
|
+
);
|
|
904
|
+
if (event === undefined) {
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
try {
|
|
908
|
+
await lifecycle.onResourceCreated(event);
|
|
909
|
+
} catch (error) {
|
|
910
|
+
throw internalError(error, "failed to create authorization tuples");
|
|
911
|
+
}
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
const existing = ctx.get(EXISTING_PLUGIN_KEY) as Plugin | undefined;
|
|
915
|
+
const oldLevel =
|
|
916
|
+
existing?.metadata?.visibility ??
|
|
917
|
+
ApiResourceVisibility.api_resource_visibility_unspecified;
|
|
918
|
+
const newLevel =
|
|
919
|
+
plugin.metadata?.visibility ??
|
|
920
|
+
ApiResourceVisibility.api_resource_visibility_unspecified;
|
|
921
|
+
const { shapesToCreate, shapesToDelete } = diffVisibilityShapes(
|
|
922
|
+
ctx.apiResourceKind,
|
|
923
|
+
oldLevel,
|
|
924
|
+
newLevel,
|
|
925
|
+
);
|
|
926
|
+
if (shapesToCreate.length === 0 && shapesToDelete.length === 0) {
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
try {
|
|
930
|
+
await lifecycle.onVisibilityChanged({
|
|
931
|
+
kind: ctx.apiResourceKind,
|
|
932
|
+
resourceId: plugin.metadata?.id ?? "",
|
|
933
|
+
orgId: plugin.metadata?.org ?? "",
|
|
934
|
+
shapesToCreate,
|
|
935
|
+
shapesToDelete,
|
|
936
|
+
});
|
|
937
|
+
} catch (error) {
|
|
938
|
+
throw internalError(error, "failed to update visibility tuples");
|
|
939
|
+
}
|
|
940
|
+
},
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
/** IndexPluginSearch — search-index upsert, best-effort by contract. */
|
|
945
|
+
export function newIndexPluginSearchStep(
|
|
946
|
+
store: Store,
|
|
947
|
+
logger: Logger,
|
|
948
|
+
): PipelineStep<PushDesc> {
|
|
949
|
+
return {
|
|
950
|
+
name: "IndexPluginSearch",
|
|
951
|
+
async execute(ctx: RequestContext<PushDesc>): Promise<void> {
|
|
952
|
+
const plugin = ctx.get(PLUGIN_KEY) as Plugin;
|
|
953
|
+
const entry = pluginSearchExtractor.getSearchIndexEntry(plugin);
|
|
954
|
+
if (entry === undefined) {
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
try {
|
|
958
|
+
await store.upsertSearchIndex(
|
|
959
|
+
ctx.apiResourceKind,
|
|
960
|
+
plugin.metadata!.id,
|
|
961
|
+
entry,
|
|
962
|
+
);
|
|
963
|
+
} catch (error) {
|
|
964
|
+
logger.warn(
|
|
965
|
+
"IndexPluginSearch: failed to update search index (best-effort)",
|
|
966
|
+
{
|
|
967
|
+
id: plugin.metadata?.id ?? "",
|
|
968
|
+
error: error instanceof Error ? error.message : String(error),
|
|
969
|
+
},
|
|
970
|
+
);
|
|
971
|
+
}
|
|
972
|
+
},
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// ─── helpers ─────────────────────────────────────────────────────────────
|
|
977
|
+
|
|
978
|
+
const MEMBER_NOUNS: ReadonlyMap<ApiResourceKind, string> = new Map([
|
|
979
|
+
[ApiResourceKind.skill, "skill"],
|
|
980
|
+
[ApiResourceKind.mcp_server, "MCP server"],
|
|
981
|
+
[ApiResourceKind.agent, "agent"],
|
|
982
|
+
[ApiResourceKind.workflow, "workflow"],
|
|
983
|
+
]);
|
|
984
|
+
|
|
985
|
+
function memberNoun(kind: ApiResourceKind): string {
|
|
986
|
+
return MEMBER_NOUNS.get(kind) ?? ApiResourceKind[kind] ?? String(kind);
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
/** The holding plugin's slug for a refusal; a dangling id names itself. */
|
|
990
|
+
async function pluginSlugOf(store: Store, pluginId: string): Promise<string> {
|
|
991
|
+
try {
|
|
992
|
+
const plugin = await store.getResource(
|
|
993
|
+
ApiResourceKind.plugin,
|
|
994
|
+
pluginId,
|
|
995
|
+
PluginSchema,
|
|
996
|
+
);
|
|
997
|
+
return plugin.metadata?.slug ?? pluginId;
|
|
998
|
+
} catch (error) {
|
|
999
|
+
if (error instanceof ResourceNotFoundError) {
|
|
1000
|
+
return pluginId;
|
|
1001
|
+
}
|
|
1002
|
+
throw internalError(error, "failed to load the holding plugin");
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
/**
|
|
1007
|
+
* A child failed: the head records FAILED with one sentence and is
|
|
1008
|
+
* persisted before the request fails, so `get plugin` tells the truth and
|
|
1009
|
+
* the next push of the same archive re-materialises.
|
|
1010
|
+
*/
|
|
1011
|
+
async function recordFailure(
|
|
1012
|
+
store: Store,
|
|
1013
|
+
ctx: RequestContext<PushDesc>,
|
|
1014
|
+
plugin: Plugin,
|
|
1015
|
+
kind: ApiResourceKind,
|
|
1016
|
+
slug: string,
|
|
1017
|
+
error: unknown,
|
|
1018
|
+
verb: "materialize" | "remove" = "materialize",
|
|
1019
|
+
): Promise<ConnectError> {
|
|
1020
|
+
const cause =
|
|
1021
|
+
error instanceof ConnectError
|
|
1022
|
+
? error.rawMessage
|
|
1023
|
+
: error instanceof Error
|
|
1024
|
+
? error.message
|
|
1025
|
+
: String(error);
|
|
1026
|
+
const sentence = `failed to ${verb} ${memberNoun(kind)} '${slug}' from plugin '${plugin.metadata?.slug ?? ""}': ${cause}`;
|
|
1027
|
+
const status = plugin.status!;
|
|
1028
|
+
status.state = PluginState.FAILED;
|
|
1029
|
+
status.error = sentence;
|
|
1030
|
+
try {
|
|
1031
|
+
await store.saveResource(
|
|
1032
|
+
ctx.apiResourceKind,
|
|
1033
|
+
plugin.metadata!.id,
|
|
1034
|
+
PluginSchema,
|
|
1035
|
+
plugin,
|
|
1036
|
+
);
|
|
1037
|
+
} catch {
|
|
1038
|
+
// The original failure is the one the caller must hear; a second
|
|
1039
|
+
// failure persisting the receipt would only hide it.
|
|
1040
|
+
}
|
|
1041
|
+
if (error instanceof ConnectError) {
|
|
1042
|
+
return new ConnectError(sentence, error.code, undefined, undefined, error);
|
|
1043
|
+
}
|
|
1044
|
+
return internalError(error, sentence);
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
function dialectOf(dialect: PluginPackage["dialect"]): PluginDialect {
|
|
1048
|
+
switch (dialect) {
|
|
1049
|
+
case "agent-plugins":
|
|
1050
|
+
return PluginDialect.AGENT_PLUGINS;
|
|
1051
|
+
case "claude":
|
|
1052
|
+
return PluginDialect.CLAUDE;
|
|
1053
|
+
case "cursor":
|
|
1054
|
+
return PluginDialect.CURSOR;
|
|
1055
|
+
case "codex":
|
|
1056
|
+
return PluginDialect.CODEX;
|
|
1057
|
+
default: {
|
|
1058
|
+
const exhaustive: never = dialect;
|
|
1059
|
+
throw new Error(`unknown dialect ${String(exhaustive)}`);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
}
|