@stigmer/server 3.22.1 → 3.23.1-dev.20260923075205
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 +4 -8
- package/dist/authorization/authorizer.d.ts.map +1 -1
- package/dist/authorization/authorizer.js +5 -9
- package/dist/authorization/authorizer.js.map +1 -1
- package/dist/authorization/derived-tuples.d.ts.map +1 -1
- package/dist/authorization/derived-tuples.js +10 -25
- package/dist/authorization/derived-tuples.js.map +1 -1
- package/dist/authorization/evaluator.d.ts +7 -8
- package/dist/authorization/evaluator.d.ts.map +1 -1
- package/dist/authorization/evaluator.js +4 -20
- package/dist/authorization/evaluator.js.map +1 -1
- package/dist/authorization/list-read-scope.d.ts.map +1 -1
- package/dist/authorization/list-read-scope.js +5 -9
- package/dist/authorization/list-read-scope.js.map +1 -1
- package/dist/authorization/model/agent.d.ts.map +1 -1
- package/dist/authorization/model/agent.js +4 -5
- package/dist/authorization/model/agent.js.map +1 -1
- package/dist/authorization/model/agent_instance.d.ts.map +1 -1
- package/dist/authorization/model/agent_instance.js +2 -2
- package/dist/authorization/model/agent_instance.js.map +1 -1
- package/dist/authorization/model/mcp_server.d.ts.map +1 -1
- package/dist/authorization/model/mcp_server.js +2 -2
- package/dist/authorization/model/mcp_server.js.map +1 -1
- package/dist/authorization/model/plugin.d.ts.map +1 -1
- package/dist/authorization/model/plugin.js +3 -4
- package/dist/authorization/model/plugin.js.map +1 -1
- package/dist/authorization/model/rewrite.d.ts +8 -10
- package/dist/authorization/model/rewrite.d.ts.map +1 -1
- package/dist/authorization/model/rewrite.js +0 -3
- package/dist/authorization/model/rewrite.js.map +1 -1
- package/dist/authorization/model/skill.d.ts.map +1 -1
- package/dist/authorization/model/skill.js +2 -2
- package/dist/authorization/model/skill.js.map +1 -1
- package/dist/authorization/model/workflow.d.ts.map +1 -1
- package/dist/authorization/model/workflow.js +2 -2
- package/dist/authorization/model/workflow.js.map +1 -1
- package/dist/authorization/model/workflow_instance.d.ts.map +1 -1
- package/dist/authorization/model/workflow_instance.js +2 -2
- package/dist/authorization/model/workflow_instance.js.map +1 -1
- package/dist/authorization/organization-directory.d.ts.map +1 -1
- package/dist/authorization/organization-directory.js +2 -3
- package/dist/authorization/organization-directory.js.map +1 -1
- package/dist/authorization/store-test-kit.d.ts +0 -6
- package/dist/authorization/store-test-kit.d.ts.map +1 -1
- package/dist/authorization/store-test-kit.js +16 -62
- package/dist/authorization/store-test-kit.js.map +1 -1
- package/dist/authorization/tuples.d.ts +13 -32
- package/dist/authorization/tuples.d.ts.map +1 -1
- package/dist/authorization/tuples.js +3 -11
- package/dist/authorization/tuples.js.map +1 -1
- package/dist/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 +3 -1
- 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 +3 -1
- 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 +3 -4
- package/dist/domain/agentinstance/controller.js.map +1 -1
- package/dist/domain/agentshare/constants.d.ts +6 -11
- package/dist/domain/agentshare/constants.d.ts.map +1 -1
- package/dist/domain/agentshare/constants.js +8 -16
- package/dist/domain/agentshare/constants.js.map +1 -1
- package/dist/domain/agentshare/controller.d.ts.map +1 -1
- package/dist/domain/agentshare/controller.js +3 -1
- package/dist/domain/agentshare/controller.js.map +1 -1
- package/dist/domain/agentshare/steps.d.ts +18 -25
- package/dist/domain/agentshare/steps.d.ts.map +1 -1
- package/dist/domain/agentshare/steps.js +41 -142
- 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 +2 -3
- package/dist/domain/environment/controller.js.map +1 -1
- package/dist/domain/executioncontext/controller.d.ts.map +1 -1
- package/dist/domain/executioncontext/controller.js +2 -1
- 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 +3 -4
- 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 +9 -11
- package/dist/domain/plugin/controller.js.map +1 -1
- package/dist/domain/plugin/push.d.ts +7 -5
- package/dist/domain/plugin/push.d.ts.map +1 -1
- package/dist/domain/plugin/push.js +8 -14
- package/dist/domain/plugin/push.js.map +1 -1
- package/dist/domain/schedule/controller.d.ts.map +1 -1
- package/dist/domain/schedule/controller.js +3 -1
- 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 +3 -2
- 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 +33 -24
- 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/agent-call-references.d.ts +54 -0
- package/dist/domain/workflow/agent-call-references.d.ts.map +1 -0
- package/dist/domain/workflow/agent-call-references.js +97 -0
- package/dist/domain/workflow/agent-call-references.js.map +1 -0
- package/dist/domain/workflow/controller.d.ts.map +1 -1
- package/dist/domain/workflow/controller.js +14 -6
- package/dist/domain/workflow/controller.js.map +1 -1
- package/dist/domain/workflow/registry/data/task-kind-registry.json +1 -1
- package/dist/domain/workflow/validation/task-config-constraints.d.ts +10 -1
- package/dist/domain/workflow/validation/task-config-constraints.d.ts.map +1 -1
- package/dist/domain/workflow/validation/task-config-constraints.js +3 -2
- package/dist/domain/workflow/validation/task-config-constraints.js.map +1 -1
- package/dist/domain/workflowexecution/controller.d.ts.map +1 -1
- package/dist/domain/workflowexecution/controller.js +4 -2
- package/dist/domain/workflowexecution/controller.js.map +1 -1
- package/dist/domain/workflowinstance/controller.d.ts.map +1 -1
- package/dist/domain/workflowinstance/controller.js +3 -4
- package/dist/domain/workflowinstance/controller.js.map +1 -1
- package/dist/extensions/authorization-queries.d.ts +6 -5
- package/dist/extensions/authorization-queries.d.ts.map +1 -1
- package/dist/extensions/list-read-scope.d.ts +1 -1
- package/dist/extensions/resource-authorization.d.ts +3 -2
- package/dist/extensions/resource-authorization.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/pipeline/apiresource-meta.d.ts +12 -3
- package/dist/pipeline/apiresource-meta.d.ts.map +1 -1
- package/dist/pipeline/apiresource-meta.js +21 -10
- package/dist/pipeline/apiresource-meta.js.map +1 -1
- package/dist/pipeline/steps/authorization-tuples.d.ts +9 -3
- package/dist/pipeline/steps/authorization-tuples.d.ts.map +1 -1
- package/dist/pipeline/steps/authorization-tuples.js +9 -11
- package/dist/pipeline/steps/authorization-tuples.js.map +1 -1
- package/dist/pipeline/steps/references.d.ts +178 -20
- package/dist/pipeline/steps/references.d.ts.map +1 -1
- package/dist/pipeline/steps/references.js +344 -43
- package/dist/pipeline/steps/references.js.map +1 -1
- package/dist/pipeline/steps/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/query/search/controller.js +0 -1
- package/dist/query/search/controller.js.map +1 -1
- package/dist/query/search/criteria.d.ts +1 -5
- package/dist/query/search/criteria.d.ts.map +1 -1
- package/dist/query/search/criteria.js +3 -13
- package/dist/query/search/criteria.js.map +1 -1
- package/dist/query/search/handler.d.ts +5 -4
- package/dist/query/search/handler.d.ts.map +1 -1
- package/dist/query/search/handler.js +7 -6
- package/dist/query/search/handler.js.map +1 -1
- package/dist/query/search/query-store.d.ts.map +1 -1
- package/dist/query/search/query-store.js +0 -2
- package/dist/query/search/query-store.js.map +1 -1
- package/dist/store/interface.d.ts +3 -6
- package/dist/store/interface.d.ts.map +1 -1
- package/dist/store/interface.js.map +1 -1
- package/dist/store/postgres/migrations.d.ts +3 -1
- package/dist/store/postgres/migrations.d.ts.map +1 -1
- package/dist/store/postgres/migrations.js +35 -1
- package/dist/store/postgres/migrations.js.map +1 -1
- package/dist/store/postgres/store.d.ts.map +1 -1
- package/dist/store/postgres/store.js +4 -9
- package/dist/store/postgres/store.js.map +1 -1
- package/dist/store/public-visibility-retired.d.ts +67 -0
- package/dist/store/public-visibility-retired.d.ts.map +1 -0
- package/dist/store/public-visibility-retired.js +55 -0
- package/dist/store/public-visibility-retired.js.map +1 -0
- package/dist/store/sqlite/migrations.d.ts +3 -1
- package/dist/store/sqlite/migrations.d.ts.map +1 -1
- package/dist/store/sqlite/migrations.js +36 -1
- package/dist/store/sqlite/migrations.js.map +1 -1
- package/dist/store/sqlite/store.d.ts.map +1 -1
- package/dist/store/sqlite/store.js +4 -8
- package/dist/store/sqlite/store.js.map +1 -1
- package/package.json +6 -6
- package/src/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 +24 -26
- package/src/authorization/__tests__/authorizer.test.ts +10 -9
- package/src/authorization/__tests__/derived-tuples.test.ts +4 -11
- package/src/authorization/__tests__/evaluator.test.ts +14 -73
- package/src/authorization/__tests__/facts.test.ts +2 -2
- package/src/authorization/__tests__/fixtures/fga/agent-instance-creation.fga.yaml +19 -27
- package/src/authorization/__tests__/fixtures/fga/blueprint-private-visibility.fga.yaml +6 -98
- package/src/authorization/__tests__/fixtures/fga/default-instance-inheritance.fga.yaml +4 -61
- package/src/authorization/__tests__/fixtures/fga/mcp-server-authoring.fga.yaml +1 -15
- package/src/authorization/__tests__/fixtures/fga/org-admin-owner-inheritance.fga.yaml +1 -21
- package/src/authorization/__tests__/fixtures/fga/platform-visibility.fga.yaml +1 -25
- package/src/authorization/__tests__/fixtures/fga/workflow-execution-sharing.fga.yaml +1 -3
- package/src/authorization/__tests__/list-read-scope.test.ts +18 -17
- package/src/authorization/__tests__/store-tests.test.ts +2 -6
- package/src/authorization/__tests__/tuples.test.ts +12 -11
- package/src/authorization/authorizer.ts +4 -9
- package/src/authorization/derived-tuples.ts +14 -31
- package/src/authorization/evaluator.ts +8 -44
- package/src/authorization/list-read-scope.ts +5 -11
- package/src/authorization/model/agent.ts +2 -5
- package/src/authorization/model/agent_instance.ts +0 -2
- package/src/authorization/model/mcp_server.ts +0 -2
- package/src/authorization/model/plugin.ts +1 -4
- package/src/authorization/model/rewrite.ts +8 -13
- package/src/authorization/model/skill.ts +0 -2
- package/src/authorization/model/workflow.ts +0 -2
- package/src/authorization/model/workflow_instance.ts +0 -2
- package/src/authorization/organization-directory.ts +1 -3
- package/src/authorization/store-test-kit.ts +16 -75
- package/src/authorization/tuples.ts +16 -39
- package/src/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 +11 -4
- package/src/domain/agent/controller.ts +7 -7
- package/src/domain/agentchannel/__tests__/agentchannel.test.ts +223 -48
- package/src/domain/agentchannel/controller.ts +6 -1
- package/src/domain/agentexecution/controller.ts +6 -1
- package/src/domain/agentinstance/controller.ts +6 -12
- package/src/domain/agentshare/__tests__/agentshare.test.ts +196 -172
- package/src/domain/agentshare/__tests__/create-authorization.test.ts +16 -31
- package/src/domain/agentshare/constants.ts +14 -26
- package/src/domain/agentshare/controller.ts +6 -1
- package/src/domain/agentshare/steps.ts +41 -194
- package/src/domain/environment/controller.ts +5 -8
- package/src/domain/executioncontext/controller.ts +5 -1
- package/src/domain/mcpserver/__tests__/mcpserver.test.ts +115 -28
- package/src/domain/mcpserver/__tests__/oauth-handshake.test.ts +53 -18
- package/src/domain/mcpserver/controller.ts +6 -12
- package/src/domain/plugin/controller.ts +16 -25
- package/src/domain/plugin/push.ts +8 -16
- package/src/domain/schedule/controller.ts +6 -1
- package/src/domain/session/controller.ts +12 -4
- 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 +44 -42
- 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/__tests__/agent-call-references.test.ts +222 -0
- package/src/domain/workflow/agent-call-references.ts +157 -0
- package/src/domain/workflow/controller.ts +28 -10
- package/src/domain/workflow/registry/data/task-kind-registry.json +1 -1
- package/src/domain/workflow/validation/task-config-constraints.ts +7 -3
- package/src/domain/workflowexecution/controller.ts +7 -2
- package/src/domain/workflowinstance/controller.ts +6 -12
- package/src/extensions/__tests__/built-in-authorization-composed.test.ts +32 -19
- package/src/extensions/__tests__/extension-composition.test.ts +4 -4
- package/src/extensions/__tests__/list-read-scope-composed.test.ts +9 -6
- package/src/extensions/authorization-queries.ts +6 -5
- package/src/extensions/list-read-scope.ts +1 -1
- package/src/extensions/resource-authorization.ts +3 -5
- package/src/index.ts +0 -1
- package/src/pipeline/__tests__/supports-visibility.test.ts +112 -0
- package/src/pipeline/apiresource-meta.ts +21 -10
- package/src/pipeline/steps/__tests__/authorization-tuples.test.ts +18 -31
- package/src/pipeline/steps/__tests__/references.test.ts +703 -0
- package/src/pipeline/steps/authorization-tuples.ts +9 -11
- package/src/pipeline/steps/references.ts +578 -70
- package/src/pipeline/steps/resolve-artifact-source.ts +20 -13
- package/src/query/search/__tests__/criteria.test.ts +0 -4
- package/src/query/search/__tests__/query-store.test.ts +11 -40
- package/src/query/search/controller.ts +0 -1
- package/src/query/search/criteria.ts +0 -14
- package/src/query/search/handler.ts +6 -7
- package/src/query/search/query-store.ts +0 -2
- package/src/store/__tests__/public-visibility-retired.test.ts +127 -0
- package/src/store/__tests__/store-contract.ts +6 -51
- package/src/store/interface.ts +3 -6
- package/src/store/postgres/__tests__/migrations.test.ts +187 -6
- package/src/store/postgres/__tests__/store-contract.test.ts +0 -2
- package/src/store/postgres/migrations.ts +48 -1
- package/src/store/postgres/store.ts +4 -11
- package/src/store/public-visibility-retired.ts +119 -0
- package/src/store/sqlite/__tests__/migrations.test.ts +191 -5
- package/src/store/sqlite/migrations.ts +45 -1
- package/src/store/sqlite/store.ts +4 -10
- package/src/transport/registry/__tests__/lanes.test.ts +1 -1
- package/dist/pipeline/steps/visibility-gates.d.ts +0 -27
- package/dist/pipeline/steps/visibility-gates.d.ts.map +0 -1
- package/dist/pipeline/steps/visibility-gates.js +0 -139
- package/dist/pipeline/steps/visibility-gates.js.map +0 -1
- package/src/authorization/__tests__/fixtures/fga/cross-org-share-creation.fga.yaml +0 -98
- package/src/authorization/__tests__/fixtures/fga/public-visibility-setter.fga.yaml +0 -52
- package/src/pipeline/steps/__tests__/visibility-gates.test.ts +0 -187
- package/src/pipeline/steps/visibility-gates.ts +0 -174
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Archive staging — how an artifact above the gRPC message cap travels,
|
|
3
|
+
* on every blob driver, through one code path (#675, stigmer#1219).
|
|
4
|
+
*
|
|
5
|
+
* The domain owns the two identities and the mapping between them: the
|
|
6
|
+
* wire reference a client is handed (`sau_<hex>`, the capability the
|
|
7
|
+
* push presents) and the staging key the bytes land under in the blob
|
|
8
|
+
* store (`skills/staging/<hex>.zip`, the surface a sweep targets). A
|
|
9
|
+
* driver is asked only to sign or stage a PUT for a key it is given, to
|
|
10
|
+
* read the key back, and to delete it — so the local driver rides the
|
|
11
|
+
* transfer lane's slot registry and a bucket driver signs a PUT straight
|
|
12
|
+
* to the bucket, and nothing above this module can tell which.
|
|
13
|
+
*
|
|
14
|
+
* Consume reads then deletes: the delete is what keeps a reference
|
|
15
|
+
* single-use on a bucket, whose signed URL is otherwise repeatable within
|
|
16
|
+
* its TTL. The delete is best effort and loud — the bytes are already in
|
|
17
|
+
* hand, a replay of the same reference is a content-addressed no-op, and
|
|
18
|
+
* the bucket lifecycle rule on the staging prefix sweeps what the delete
|
|
19
|
+
* missed. On the local driver the same delete retires the staged file the
|
|
20
|
+
* slot registry would otherwise sweep at expiry.
|
|
21
|
+
*
|
|
22
|
+
* A reference the driver cannot read — malformed, never uploaded,
|
|
23
|
+
* expired, already consumed — is one condition on every driver
|
|
24
|
+
* ("unknown or expired"): a bucket cannot distinguish them, so neither
|
|
25
|
+
* does the wire.
|
|
26
|
+
*
|
|
27
|
+
* Proven by __tests__/staging.test.ts over a fake driver and over the
|
|
28
|
+
* real local driver with its slot registry; the conformance suite's
|
|
29
|
+
* transfer-lane block pins the wire behaviour on both editions.
|
|
30
|
+
*/
|
|
31
|
+
import { randomBytes } from "node:crypto";
|
|
32
|
+
|
|
33
|
+
import type { ArtifactStorage } from "../../../artifactstorage/artifact-storage.js";
|
|
34
|
+
import { ArtifactStorageNotFoundError } from "../../../artifactstorage/artifact-storage.js";
|
|
35
|
+
import type { Logger } from "../../../boot/logger.js";
|
|
36
|
+
import {
|
|
37
|
+
DEFAULT_SLOT_TTL_MS,
|
|
38
|
+
DOWNLOAD_URL_TTL_MS,
|
|
39
|
+
MAX_ZIP_SIZE,
|
|
40
|
+
REF_BYTE_LEN,
|
|
41
|
+
REF_PREFIX,
|
|
42
|
+
STAGING_KEY_PREFIX,
|
|
43
|
+
} from "../constants.js";
|
|
44
|
+
import { SlotUnknownError } from "./slots.js";
|
|
45
|
+
|
|
46
|
+
/** The hex the reference and the staging key share; 128 bits, lowercase. */
|
|
47
|
+
const HEX = new RegExp(`^[0-9a-f]{${REF_BYTE_LEN * 2}}$`);
|
|
48
|
+
const STAGED_ARCHIVE_SUFFIX = ".zip";
|
|
49
|
+
|
|
50
|
+
/** The shared hex of a well-formed reference or key, else undefined. */
|
|
51
|
+
function hexBetween(
|
|
52
|
+
value: string,
|
|
53
|
+
prefix: string,
|
|
54
|
+
suffix: string,
|
|
55
|
+
): string | undefined {
|
|
56
|
+
if (!value.startsWith(prefix) || !value.endsWith(suffix)) {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
const hex = value.slice(prefix.length, value.length - suffix.length);
|
|
60
|
+
return HEX.test(hex) ? hex : undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** A fresh upload reference: the prefix and 128 random bits, hex. */
|
|
64
|
+
export function newUploadRef(): string {
|
|
65
|
+
return REF_PREFIX + randomBytes(REF_BYTE_LEN).toString("hex");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The staging key a reference's bytes land under, or undefined when the
|
|
70
|
+
* reference is not one this server could have minted. Refs arrive from
|
|
71
|
+
* clients (the push request, the lane's URL path), so the shape is
|
|
72
|
+
* checked before it becomes a storage key.
|
|
73
|
+
*/
|
|
74
|
+
export function stagingKeyOf(ref: string): string | undefined {
|
|
75
|
+
const hex = hexBetween(ref, REF_PREFIX, "");
|
|
76
|
+
return hex === undefined
|
|
77
|
+
? undefined
|
|
78
|
+
: `${STAGING_KEY_PREFIX}${hex}${STAGED_ARCHIVE_SUFFIX}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The reference for a staging key this module minted. Keys reach here from
|
|
83
|
+
* the domain's own mint, never from a client, so a foreign shape is a
|
|
84
|
+
* programming error and throws.
|
|
85
|
+
*/
|
|
86
|
+
export function uploadRefOf(stagingKey: string): string {
|
|
87
|
+
const hex = hexBetween(stagingKey, STAGING_KEY_PREFIX, STAGED_ARCHIVE_SUFFIX);
|
|
88
|
+
if (hex === undefined) {
|
|
89
|
+
throw new Error(`${stagingKey} is not a staging key this domain minted`);
|
|
90
|
+
}
|
|
91
|
+
return REF_PREFIX + hex;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** What createArtifactUploadUrl hands the client. */
|
|
95
|
+
export interface StagedUpload {
|
|
96
|
+
/** The single-use reference the push presents. */
|
|
97
|
+
readonly ref: string;
|
|
98
|
+
/** Accepts one HTTP PUT of exactly the declared byte count. */
|
|
99
|
+
readonly url: string;
|
|
100
|
+
/** How long the reference and the URL stay valid. */
|
|
101
|
+
readonly ttlMs: number;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** What getArtifactDownloadUrl hands the client. */
|
|
105
|
+
export interface DownloadCapability {
|
|
106
|
+
readonly url: string;
|
|
107
|
+
/** The floor of the URL's validity (DOWNLOAD_URL_TTL_MS). */
|
|
108
|
+
readonly ttlMs: number;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** The staging port both archive kinds (skills, plugins) push through. */
|
|
112
|
+
export interface ArchiveStaging {
|
|
113
|
+
/** Mints a reference and the URL its bytes are PUT to. */
|
|
114
|
+
mint(declaredSizeBytes: number): Promise<StagedUpload>;
|
|
115
|
+
/**
|
|
116
|
+
* Reads the staged bytes for a reference and retires it. Throws
|
|
117
|
+
* SlotUnknownError when the driver holds nothing under the reference;
|
|
118
|
+
* any other failure is the driver's and propagates as an infrastructure
|
|
119
|
+
* fault.
|
|
120
|
+
*/
|
|
121
|
+
consume(ref: string): Promise<Uint8Array>;
|
|
122
|
+
/** A time-limited download URL for a stored archive key. */
|
|
123
|
+
downloadUrl(storageKey: string): Promise<DownloadCapability>;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function newArchiveStaging(
|
|
127
|
+
driver: ArtifactStorage,
|
|
128
|
+
logger: Logger,
|
|
129
|
+
): ArchiveStaging {
|
|
130
|
+
return {
|
|
131
|
+
async mint(declaredSizeBytes) {
|
|
132
|
+
// The controllers refuse over-limit declarations with the limit in
|
|
133
|
+
// the message before reaching here; this is the module's own
|
|
134
|
+
// invariant, so no driver is ever asked to stage more than the
|
|
135
|
+
// archive ceiling.
|
|
136
|
+
if (declaredSizeBytes <= 0 || declaredSizeBytes > MAX_ZIP_SIZE) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
`declared size ${declaredSizeBytes} outside (0, ${MAX_ZIP_SIZE}]`,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
const ref = newUploadRef();
|
|
142
|
+
const key = stagingKeyOf(ref);
|
|
143
|
+
if (key === undefined) {
|
|
144
|
+
throw new Error(`minted reference ${ref} has no staging key`);
|
|
145
|
+
}
|
|
146
|
+
const presigned = await driver.presignPut(
|
|
147
|
+
key,
|
|
148
|
+
declaredSizeBytes,
|
|
149
|
+
DEFAULT_SLOT_TTL_MS,
|
|
150
|
+
);
|
|
151
|
+
return { ref, url: presigned.url, ttlMs: presigned.ttlMs };
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
async consume(ref) {
|
|
155
|
+
const key = stagingKeyOf(ref);
|
|
156
|
+
if (key === undefined) {
|
|
157
|
+
throw new SlotUnknownError();
|
|
158
|
+
}
|
|
159
|
+
let data: Uint8Array;
|
|
160
|
+
try {
|
|
161
|
+
// The allocation is bounded by construction: a bucket accepts only
|
|
162
|
+
// the signed Content-Length, the slot registry only the reserved
|
|
163
|
+
// size, and both were checked against MAX_ZIP_SIZE at mint.
|
|
164
|
+
data = await driver.download(key);
|
|
165
|
+
} catch (error) {
|
|
166
|
+
if (error instanceof ArtifactStorageNotFoundError) {
|
|
167
|
+
throw new SlotUnknownError();
|
|
168
|
+
}
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
await driver.delete(key);
|
|
173
|
+
} catch (error) {
|
|
174
|
+
logger.error("failed to delete consumed staged artifact", {
|
|
175
|
+
stagingKey: key,
|
|
176
|
+
error: error instanceof Error ? error.message : String(error),
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return data;
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
async downloadUrl(storageKey) {
|
|
183
|
+
const url = await driver.getSignedUrl(
|
|
184
|
+
storageKey,
|
|
185
|
+
DOWNLOAD_URL_TTL_MS,
|
|
186
|
+
"",
|
|
187
|
+
);
|
|
188
|
+
return { url, ttlMs: DOWNLOAD_URL_TTL_MS };
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pins the workflow domain's collector for the reference rule
|
|
3
|
+
* (../agent-call-references.ts): the strict `slug` / `org/slug` parser and
|
|
4
|
+
* its refusal copy, the walk over top-level, nested and compensate tasks
|
|
5
|
+
* in declaration order with the bare form resolved to the workflow's
|
|
6
|
+
* organization, a config that does not decode skipped rather than
|
|
7
|
+
* double-reported, and the step over a real store answering the rule's
|
|
8
|
+
* verdicts — a missing agent with the rule's copy, another organization's
|
|
9
|
+
* org-visible agent with the one cross-organization sentence, a
|
|
10
|
+
* platform-visible one admitted.
|
|
11
|
+
*/
|
|
12
|
+
import { create } from "@bufbuild/protobuf";
|
|
13
|
+
import { Code, ConnectError } from "@connectrpc/connect";
|
|
14
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
15
|
+
|
|
16
|
+
import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
|
|
17
|
+
import { WorkflowSchema } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/api_pb";
|
|
18
|
+
import { WorkflowTaskKind } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/enum_pb";
|
|
19
|
+
import type { WorkflowSpec } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/spec_pb";
|
|
20
|
+
import { WorkflowSpecSchema } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/spec_pb";
|
|
21
|
+
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
22
|
+
import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
|
|
23
|
+
|
|
24
|
+
import { RequestContext } from "../../../pipeline/request-context.js";
|
|
25
|
+
import { testCallerIdentity } from "../../../pipeline/__tests__/support.js";
|
|
26
|
+
import {
|
|
27
|
+
missingReferencesMessage,
|
|
28
|
+
notAvailableReferenceMessage,
|
|
29
|
+
referenceTargetKind,
|
|
30
|
+
} from "../../../pipeline/steps/references.js";
|
|
31
|
+
import type { SqliteStore } from "../../../store/sqlite/store.js";
|
|
32
|
+
import { tempStore } from "../../../store/sqlite/__tests__/support.js";
|
|
33
|
+
import {
|
|
34
|
+
collectAgentCallReferences,
|
|
35
|
+
malformedAgentReferenceMessage,
|
|
36
|
+
newValidateAgentCallReferencesStep,
|
|
37
|
+
parseAgentReference,
|
|
38
|
+
} from "../agent-call-references.js";
|
|
39
|
+
|
|
40
|
+
const V = ApiResourceVisibility;
|
|
41
|
+
const AGENT = referenceTargetKind(ApiResourceKind.agent)!;
|
|
42
|
+
|
|
43
|
+
function agentCall(name: string, agent: string) {
|
|
44
|
+
return {
|
|
45
|
+
name,
|
|
46
|
+
kind: WorkflowTaskKind.agent_call,
|
|
47
|
+
taskConfig: { agent, message: "do the thing" },
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function specOf(tasks: ReadonlyArray<Record<string, unknown>>): WorkflowSpec {
|
|
52
|
+
return create(WorkflowSpecSchema, { tasks } as never);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
describe("parseAgentReference", () => {
|
|
56
|
+
it("accepts `slug` and `org/slug` and nothing else", () => {
|
|
57
|
+
expect(parseAgentReference("reviewer")).toEqual({
|
|
58
|
+
org: "",
|
|
59
|
+
slug: "reviewer",
|
|
60
|
+
});
|
|
61
|
+
expect(parseAgentReference("acme/reviewer")).toEqual({
|
|
62
|
+
org: "acme",
|
|
63
|
+
slug: "reviewer",
|
|
64
|
+
});
|
|
65
|
+
for (const malformed of ["", "/reviewer", "acme/", "a/b/c", "//"]) {
|
|
66
|
+
expect(parseAgentReference(malformed), malformed).toBeUndefined();
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe("collectAgentCallReferences", () => {
|
|
72
|
+
it("collects top-level, nested and compensate agent_call tasks in declaration order, the bare form resolved to the workflow's organization", () => {
|
|
73
|
+
const spec = specOf([
|
|
74
|
+
agentCall("first", "reviewer"),
|
|
75
|
+
{
|
|
76
|
+
name: "loop",
|
|
77
|
+
kind: WorkflowTaskKind.for_each,
|
|
78
|
+
taskConfig: {
|
|
79
|
+
each: "item",
|
|
80
|
+
in: "${ .items }",
|
|
81
|
+
// A nested task inside the Struct is JSON: proto field names.
|
|
82
|
+
do: [
|
|
83
|
+
{
|
|
84
|
+
name: "inner",
|
|
85
|
+
kind: "agent_call",
|
|
86
|
+
task_config: { agent: "globex/summarizer", message: "sum" },
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
compensate: [agentCall("undo", "cleaner")],
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: "pause",
|
|
94
|
+
kind: WorkflowTaskKind.wait,
|
|
95
|
+
taskConfig: { duration: {} },
|
|
96
|
+
},
|
|
97
|
+
]);
|
|
98
|
+
expect(collectAgentCallReferences(spec, "acme")).toEqual([
|
|
99
|
+
{ kind: ApiResourceKind.agent, org: "acme", slug: "reviewer" },
|
|
100
|
+
{ kind: ApiResourceKind.agent, org: "globex", slug: "summarizer" },
|
|
101
|
+
{ kind: ApiResourceKind.agent, org: "acme", slug: "cleaner" },
|
|
102
|
+
]);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("refuses a malformed agent string naming the task", () => {
|
|
106
|
+
const spec = specOf([agentCall("broken", "a/b/c")]);
|
|
107
|
+
expect(() => collectAgentCallReferences(spec, "acme")).toThrow(
|
|
108
|
+
malformedAgentReferenceMessage("broken", "a/b/c"),
|
|
109
|
+
);
|
|
110
|
+
let error: unknown;
|
|
111
|
+
try {
|
|
112
|
+
collectAgentCallReferences(spec, "acme");
|
|
113
|
+
} catch (e) {
|
|
114
|
+
error = e;
|
|
115
|
+
}
|
|
116
|
+
expect((error as ConnectError).code).toBe(Code.InvalidArgument);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("skips a config that does not decode — the spec validation step has already refused it", () => {
|
|
120
|
+
const spec = specOf([
|
|
121
|
+
{
|
|
122
|
+
name: "odd",
|
|
123
|
+
kind: WorkflowTaskKind.agent_call,
|
|
124
|
+
taskConfig: { agent: "reviewer", not_a_field: true },
|
|
125
|
+
},
|
|
126
|
+
]);
|
|
127
|
+
expect(collectAgentCallReferences(spec, "acme")).toEqual([]);
|
|
128
|
+
expect(collectAgentCallReferences(undefined, "acme")).toEqual([]);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe("ValidateAgentCallReferences over a store", () => {
|
|
133
|
+
let store: SqliteStore;
|
|
134
|
+
let cleanup: () => Promise<void>;
|
|
135
|
+
|
|
136
|
+
beforeEach(() => {
|
|
137
|
+
const temp = tempStore();
|
|
138
|
+
store = temp.store;
|
|
139
|
+
cleanup = temp.cleanup;
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
afterEach(async () => {
|
|
143
|
+
await cleanup();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
async function seedAgent(
|
|
147
|
+
id: string,
|
|
148
|
+
org: string,
|
|
149
|
+
slug: string,
|
|
150
|
+
visibility: ApiResourceVisibility,
|
|
151
|
+
): Promise<void> {
|
|
152
|
+
await store.saveResource(
|
|
153
|
+
ApiResourceKind.agent,
|
|
154
|
+
id,
|
|
155
|
+
AgentSchema,
|
|
156
|
+
create(AgentSchema, {
|
|
157
|
+
apiVersion: "agentic.stigmer.ai/v1",
|
|
158
|
+
kind: "Agent",
|
|
159
|
+
metadata: { id, name: slug, slug, org, visibility },
|
|
160
|
+
spec: { instructions: "a conformant instruction body" },
|
|
161
|
+
}),
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function workflowCalling(...agents: string[]) {
|
|
166
|
+
return create(WorkflowSchema, {
|
|
167
|
+
apiVersion: "agentic.stigmer.ai/v1",
|
|
168
|
+
kind: "Workflow",
|
|
169
|
+
metadata: { name: "Pipeline", org: "acme", visibility: V.visibility_org },
|
|
170
|
+
spec: specOf(agents.map((agent, i) => agentCall(`call_${i}`, agent))),
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function run(workflow: ReturnType<typeof workflowCalling>) {
|
|
175
|
+
const ctx = new RequestContext(
|
|
176
|
+
WorkflowSchema,
|
|
177
|
+
workflow,
|
|
178
|
+
testCallerIdentity(),
|
|
179
|
+
ApiResourceKind.workflow,
|
|
180
|
+
);
|
|
181
|
+
try {
|
|
182
|
+
await newValidateAgentCallReferencesStep(store).execute(ctx);
|
|
183
|
+
return undefined;
|
|
184
|
+
} catch (error) {
|
|
185
|
+
return error;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
it("a missing agent is refused with the rule's copy; an existing one passes", async () => {
|
|
190
|
+
const error = await run(workflowCalling("reviewer"));
|
|
191
|
+
expect(error).toBeInstanceOf(ConnectError);
|
|
192
|
+
expect((error as ConnectError).code).toBe(Code.FailedPrecondition);
|
|
193
|
+
expect((error as ConnectError).rawMessage).toBe(
|
|
194
|
+
missingReferencesMessage(AGENT, [{ slug: "reviewer", org: "acme" }]),
|
|
195
|
+
);
|
|
196
|
+
await seedAgent("agt_1", "acme", "reviewer", V.visibility_org);
|
|
197
|
+
expect(await run(workflowCalling("reviewer"))).toBeUndefined();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("another organization's agent is admitted only at platform visibility, with the one sentence otherwise", async () => {
|
|
201
|
+
await seedAgent("agt_shared", "globex", "shared", V.visibility_platform);
|
|
202
|
+
await seedAgent("agt_internal", "globex", "internal", V.visibility_org);
|
|
203
|
+
expect(await run(workflowCalling("globex/shared"))).toBeUndefined();
|
|
204
|
+
const error = await run(workflowCalling("globex/internal"));
|
|
205
|
+
expect((error as ConnectError).rawMessage).toBe(
|
|
206
|
+
notAvailableReferenceMessage(AGENT, {
|
|
207
|
+
kind: ApiResourceKind.agent,
|
|
208
|
+
org: "globex",
|
|
209
|
+
slug: "internal",
|
|
210
|
+
}),
|
|
211
|
+
);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("the floor holds for the agent a workflow calls: an org-visible workflow may not call a private agent", async () => {
|
|
215
|
+
await seedAgent("agt_mine", "acme", "mine", V.visibility_private);
|
|
216
|
+
const error = await run(workflowCalling("mine"));
|
|
217
|
+
expect((error as ConnectError).code).toBe(Code.FailedPrecondition);
|
|
218
|
+
expect((error as ConnectError).rawMessage).toContain(
|
|
219
|
+
"referenced agent 'acme/mine' is visibility_private while this resource is visibility_org",
|
|
220
|
+
);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ValidateAgentCallReferences — the second collector for the reference rule
|
|
3
|
+
* (pipeline/steps/references.ts): a workflow's `agent_call` tasks name their
|
|
4
|
+
* agent as a STRING, `"slug"` or `"org/slug"`, inside a `google.protobuf.Struct`
|
|
5
|
+
* config the generic spec walker cannot see, so the rule would never meet
|
|
6
|
+
* them. This step parses each task's config through the converter's own
|
|
7
|
+
* strict decoder (`unmarshalTaskConfig`, the one reader of a task config),
|
|
8
|
+
* walks nested control-flow tasks exactly as the constraint validator does,
|
|
9
|
+
* turns every `agent` string into a reference with the workflow as parent,
|
|
10
|
+
* and asks the rule's one function — same clauses, same copy. It lives in
|
|
11
|
+
* the workflow domain and not inside `InProcessValidator` because the
|
|
12
|
+
* validator is pure over the spec by design and a rule that reads the
|
|
13
|
+
* target's row cannot be.
|
|
14
|
+
*
|
|
15
|
+
* The parser here is STRICT: exactly `slug` or `org/slug`, both segments
|
|
16
|
+
* non-empty, anything else refused at write naming the task. Two lenient
|
|
17
|
+
* readers of the same string exist downstream and disagree with each other
|
|
18
|
+
* (the runner's `parseAgentReference` in activities/call-agent.ts splits on
|
|
19
|
+
* the first `/` and drops a third segment; the CLI's `parseReference` in
|
|
20
|
+
* client-apps/cli/src/resources/reference.ts refuses a leading slash);
|
|
21
|
+
* refusing every malformed shape here means neither ever meets one. The
|
|
22
|
+
* three cannot share code — the packages share only the protos — so the
|
|
23
|
+
* execution conformance suite is where their agreement is proven.
|
|
24
|
+
*
|
|
25
|
+
* Runs after NormalizeReferences and ValidateReferences on the workflow
|
|
26
|
+
* create and update chains, so the org the walk sees is the workflow's own
|
|
27
|
+
* for a bare slug. Proven by __tests__/agent-call-references.test.ts and
|
|
28
|
+
* the workflow conformance suite's reference arm.
|
|
29
|
+
*/
|
|
30
|
+
import type { WorkflowSchema } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/api_pb";
|
|
31
|
+
import type { AgentCallTaskConfig } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/tasks/agent_call_pb";
|
|
32
|
+
import type {
|
|
33
|
+
WorkflowSpec,
|
|
34
|
+
WorkflowTask,
|
|
35
|
+
} from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/spec_pb";
|
|
36
|
+
import { WorkflowTaskKind } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/enum_pb";
|
|
37
|
+
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
38
|
+
|
|
39
|
+
import type { Store } from "../../store/interface.js";
|
|
40
|
+
import { internalError, invalidArgumentError } from "../../pipeline/errors.js";
|
|
41
|
+
import type { PipelineStep } from "../../pipeline/pipeline.js";
|
|
42
|
+
import type { RequestContext } from "../../pipeline/request-context.js";
|
|
43
|
+
import type {
|
|
44
|
+
ReferenceParent,
|
|
45
|
+
SpecReference,
|
|
46
|
+
} from "../../pipeline/steps/references.js";
|
|
47
|
+
import { checkReferences } from "../../pipeline/steps/references.js";
|
|
48
|
+
import { metadataOf } from "../../pipeline/steps/shapes.js";
|
|
49
|
+
import { unmarshalTaskConfig } from "./converter/unmarshal.js";
|
|
50
|
+
import { nestedTasks } from "./validation/task-config-constraints.js";
|
|
51
|
+
|
|
52
|
+
/** The one sentence for an `agent` string that is neither `slug` nor `org/slug`. */
|
|
53
|
+
export function malformedAgentReferenceMessage(
|
|
54
|
+
taskName: string,
|
|
55
|
+
agent: string,
|
|
56
|
+
): string {
|
|
57
|
+
return `task '${taskName}': agent '${agent}' is not a reference; write 'slug' for an agent of this organization or 'org/slug' for another organization's.`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* `slug` or `org/slug` → the reference, with `org` empty for the bare form
|
|
62
|
+
* (the caller fills the parent's). Returns undefined for any other shape.
|
|
63
|
+
*/
|
|
64
|
+
export function parseAgentReference(
|
|
65
|
+
agent: string,
|
|
66
|
+
): { readonly org: string; readonly slug: string } | undefined {
|
|
67
|
+
const parts = agent.split("/");
|
|
68
|
+
if (parts.length === 1) {
|
|
69
|
+
return parts[0] === "" ? undefined : { org: "", slug: parts[0]! };
|
|
70
|
+
}
|
|
71
|
+
if (parts.length === 2 && parts[0] !== "" && parts[1] !== "") {
|
|
72
|
+
return { org: parts[0]!, slug: parts[1]! };
|
|
73
|
+
}
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Every `agent_call` task's agent as a reference, nested tasks included in
|
|
79
|
+
* declaration order, the bare form resolved to `parentOrg`. Throws
|
|
80
|
+
* INVALID_ARGUMENT on the first malformed string. A config that does not
|
|
81
|
+
* decode is skipped: the spec validation step has already refused the
|
|
82
|
+
* workflow for it, and this collector never double-reports.
|
|
83
|
+
*/
|
|
84
|
+
export function collectAgentCallReferences(
|
|
85
|
+
spec: WorkflowSpec | undefined,
|
|
86
|
+
parentOrg: string,
|
|
87
|
+
): SpecReference[] {
|
|
88
|
+
const refs: SpecReference[] = [];
|
|
89
|
+
if (spec === undefined) {
|
|
90
|
+
return refs;
|
|
91
|
+
}
|
|
92
|
+
const visit = (task: WorkflowTask): void => {
|
|
93
|
+
if (task.taskConfig !== undefined) {
|
|
94
|
+
let config;
|
|
95
|
+
try {
|
|
96
|
+
config = unmarshalTaskConfig(task.kind, task.taskConfig);
|
|
97
|
+
} catch {
|
|
98
|
+
config = undefined;
|
|
99
|
+
}
|
|
100
|
+
if (config !== undefined) {
|
|
101
|
+
if (task.kind === WorkflowTaskKind.agent_call) {
|
|
102
|
+
const agent = (config as AgentCallTaskConfig).agent;
|
|
103
|
+
const parsed = parseAgentReference(agent);
|
|
104
|
+
if (parsed === undefined) {
|
|
105
|
+
throw invalidArgumentError(
|
|
106
|
+
malformedAgentReferenceMessage(task.name, agent),
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
refs.push({
|
|
110
|
+
kind: ApiResourceKind.agent,
|
|
111
|
+
org: parsed.org === "" ? parentOrg : parsed.org,
|
|
112
|
+
slug: parsed.slug,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
for (const nested of nestedTasks(config)) {
|
|
116
|
+
visit(nested);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
for (const compensate of task.compensate) {
|
|
121
|
+
visit(compensate);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
for (const task of spec.tasks) {
|
|
125
|
+
visit(task);
|
|
126
|
+
}
|
|
127
|
+
return refs;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function newValidateAgentCallReferencesStep(
|
|
131
|
+
store: Store,
|
|
132
|
+
): PipelineStep<typeof WorkflowSchema> {
|
|
133
|
+
return {
|
|
134
|
+
name: "ValidateAgentCallReferences",
|
|
135
|
+
async execute(ctx: RequestContext<typeof WorkflowSchema>): Promise<void> {
|
|
136
|
+
const metadata = metadataOf(ctx.newState);
|
|
137
|
+
if (metadata === undefined) {
|
|
138
|
+
throw internalError(
|
|
139
|
+
new Error("resource metadata is nil"),
|
|
140
|
+
"validate agent_call references",
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
const refs = collectAgentCallReferences(ctx.newState.spec, metadata.org);
|
|
144
|
+
if (refs.length === 0) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const parent: ReferenceParent = {
|
|
148
|
+
org: metadata.org,
|
|
149
|
+
visibility: metadata.visibility,
|
|
150
|
+
};
|
|
151
|
+
const refusal = await checkReferences(store, parent, refs);
|
|
152
|
+
if (refusal !== undefined) {
|
|
153
|
+
throw refusal;
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
@@ -78,10 +78,6 @@ import {
|
|
|
78
78
|
} from "../../pipeline/steps/authorize-resolved-target.js";
|
|
79
79
|
import { versionHistoryTarget } from "../../pipeline/steps/version-history.js";
|
|
80
80
|
import { newGuardReservedLabelsStep } from "../../pipeline/steps/guard-reserved-labels.js";
|
|
81
|
-
import {
|
|
82
|
-
newAuthorizeVisibilityTransitionStep,
|
|
83
|
-
newGuardPublicVisibilityStep,
|
|
84
|
-
} from "../../pipeline/steps/visibility-gates.js";
|
|
85
81
|
import { newBuildUpdateStateStep } from "../../pipeline/steps/build-update-state.js";
|
|
86
82
|
import {
|
|
87
83
|
setAuditFieldsForUpdate,
|
|
@@ -112,7 +108,16 @@ import {
|
|
|
112
108
|
TARGET_RESOURCE_KEY,
|
|
113
109
|
newLoadTargetStep,
|
|
114
110
|
} from "../../pipeline/steps/load-target.js";
|
|
115
|
-
import {
|
|
111
|
+
import {
|
|
112
|
+
collectSpecReferences,
|
|
113
|
+
newGuardReferenceFloorOnEscalationStep,
|
|
114
|
+
newNormalizeReferencesStep,
|
|
115
|
+
newValidateReferencesStep,
|
|
116
|
+
} from "../../pipeline/steps/references.js";
|
|
117
|
+
import {
|
|
118
|
+
collectAgentCallReferences,
|
|
119
|
+
newValidateAgentCallReferencesStep,
|
|
120
|
+
} from "./agent-call-references.js";
|
|
116
121
|
import {
|
|
117
122
|
newCleanupIamPoliciesStep,
|
|
118
123
|
newCreateAuthorizationTuplesStep,
|
|
@@ -238,9 +243,10 @@ async function createWorkflow(
|
|
|
238
243
|
.addStep(newResolveSlugStep())
|
|
239
244
|
.addStep(newCheckDuplicateStep(deps.store))
|
|
240
245
|
.addStep(newBuildNewStateStep())
|
|
241
|
-
.addStep(newGuardPublicVisibilityStep(deps.authorizer))
|
|
242
246
|
.addStep(newGuardReservedLabelsStep(deps.authorizer))
|
|
243
247
|
.addStep(newNormalizeReferencesStep())
|
|
248
|
+
.addStep(newValidateReferencesStep(deps.store))
|
|
249
|
+
.addStep(newValidateAgentCallReferencesStep(deps.store))
|
|
244
250
|
.addStep(newPopulateServerlessValidationStep(deps.logger))
|
|
245
251
|
.addStep(newComputeVersionHashStep(deps.logger))
|
|
246
252
|
.addStep(newPopulateVersionHashStep(true))
|
|
@@ -305,6 +311,8 @@ async function update(
|
|
|
305
311
|
.addStep(newBuildUpdateStateStep())
|
|
306
312
|
.addStep(newGuardReservedLabelsStep(deps.authorizer))
|
|
307
313
|
.addStep(newNormalizeReferencesStep())
|
|
314
|
+
.addStep(newValidateReferencesStep(deps.store))
|
|
315
|
+
.addStep(newValidateAgentCallReferencesStep(deps.store))
|
|
308
316
|
.addStep(newPopulateServerlessValidationStepForUpdate(deps.logger))
|
|
309
317
|
.addStep(newComputeVersionHashStep(deps.logger))
|
|
310
318
|
.addStep(newCheckVersionChangedStep(deps.logger))
|
|
@@ -411,8 +419,8 @@ async function deleteWorkflow(
|
|
|
411
419
|
// updateVisibility — update_visibility.go: a targeted metadata update (only
|
|
412
420
|
// metadata.visibility changes; spec/status untouched). The level check runs
|
|
413
421
|
// AFTER load, preserving the cross-edition error precedence: unknown id +
|
|
414
|
-
// bad level = NOT_FOUND on both editions.
|
|
415
|
-
//
|
|
422
|
+
// bad level = NOT_FOUND on both editions. The workflow conformance suite's
|
|
423
|
+
// visibility block is the wire pin.
|
|
416
424
|
// ---------------------------------------------------------------------------
|
|
417
425
|
|
|
418
426
|
const UPDATE_VISIBILITY_WORKFLOW_KEY = "updateVisibilityWorkflow";
|
|
@@ -452,10 +460,20 @@ async function updateVisibility(
|
|
|
452
460
|
newRecordVisibilityBeforeUpdateStep(UPDATE_VISIBILITY_WORKFLOW_KEY),
|
|
453
461
|
)
|
|
454
462
|
.addStep(newValidateVisibilityUpdateStep())
|
|
463
|
+
// The reference floor's second door: a workflow may not be raised above
|
|
464
|
+
// the agents its agent_call tasks run.
|
|
455
465
|
.addStep(
|
|
456
|
-
|
|
466
|
+
newGuardReferenceFloorOnEscalationStep(
|
|
467
|
+
deps.store,
|
|
457
468
|
UPDATE_VISIBILITY_WORKFLOW_KEY,
|
|
458
|
-
|
|
469
|
+
[
|
|
470
|
+
(row) => collectSpecReferences(WorkflowSchema, row),
|
|
471
|
+
(row) =>
|
|
472
|
+
collectAgentCallReferences(
|
|
473
|
+
(row as Workflow).spec,
|
|
474
|
+
(row as Workflow).metadata?.org ?? "",
|
|
475
|
+
),
|
|
476
|
+
],
|
|
459
477
|
),
|
|
460
478
|
)
|
|
461
479
|
.addStep(newSetWorkflowVisibilityStep())
|
|
@@ -1525,7 +1525,7 @@
|
|
|
1525
1525
|
"type": "string"
|
|
1526
1526
|
},
|
|
1527
1527
|
"org": {
|
|
1528
|
-
"description": "Organization that owns the referenced resource. When non-empty: must be a valid org slug (lowercase alphanumeric with hyphens, starts with a letter, 1-63 characters). Example: \"stigmer\", \"acme-corp\". When empty: the reference is relative — the server resolves it to the parent resource's organization at write time. All stored and returned references always have org populated (absolute form). Use empty org for same-org references (the common case).
|
|
1528
|
+
"description": "Organization that owns the referenced resource. When non-empty: must be a valid org slug (lowercase alphanumeric with hyphens, starts with a letter, 1-63 characters). Example: \"stigmer\", \"acme-corp\". When empty: the reference is relative — the server resolves it to the parent resource's organization at write time. All stored and returned references always have org populated (absolute form). Use empty org for same-org references (the common case). An explicit other org is accepted only when that organization is a platform that shares the resource with yours (visibility_platform).",
|
|
1529
1529
|
"maxLength": 63,
|
|
1530
1530
|
"pattern": "^$|^[a-z][a-z0-9-]*[a-z0-9]$",
|
|
1531
1531
|
"type": "string"
|