@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
|
@@ -32,8 +32,7 @@
|
|
|
32
32
|
* annotation, which is why this driver meets that caller at all: a
|
|
33
33
|
* skip lane hands the directory the identity the interceptor stamped,
|
|
34
34
|
* and no Authorize arm ran before it. Every other caller gets the
|
|
35
|
-
* evaluated list.
|
|
36
|
-
* though no organization line admits a wildcard.
|
|
35
|
+
* evaluated list.
|
|
37
36
|
* The controller loads each id it is handed and skips the ones whose row
|
|
38
37
|
* is gone (grants can outlive rows); the source has already loaded the
|
|
39
38
|
* row once for the evaluation — two primary-key reads per organization,
|
|
@@ -102,7 +101,6 @@ export function newBuiltInOrganizationDirectory(
|
|
|
102
101
|
{ type: ORGANIZATION, id },
|
|
103
102
|
"can_view",
|
|
104
103
|
person,
|
|
105
|
-
{ allow: false },
|
|
106
104
|
)
|
|
107
105
|
) {
|
|
108
106
|
visible.push(id);
|
|
@@ -41,26 +41,18 @@
|
|
|
41
41
|
* `skipped` is therefore complete once every case has run; a consumer
|
|
42
42
|
* pins it after the cases, which is the order a test file declares them.
|
|
43
43
|
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* that
|
|
49
|
-
*
|
|
50
|
-
* key anywhere is a thrown fault, so a change to the format upstream is
|
|
51
|
-
* loud here rather than half-run.
|
|
44
|
+
* The parser is strict on purpose: an unknown key anywhere is a thrown
|
|
45
|
+
* fault, so a change to the format upstream is loud here rather than
|
|
46
|
+
* half-run. The model declares no condition, so the format's `condition`
|
|
47
|
+
* on a tuple and `context` on a check are among the keys refused: a
|
|
48
|
+
* document that still carries them is written against a model this
|
|
49
|
+
* evaluator does not answer, and saying so beats running it half-blind.
|
|
52
50
|
*/
|
|
53
51
|
import assert from "node:assert/strict";
|
|
54
52
|
|
|
55
53
|
import { checkRelation } from "./evaluator.js";
|
|
56
54
|
import type { Model } from "./model/index.js";
|
|
57
|
-
import type {
|
|
58
|
-
CheckContext,
|
|
59
|
-
Person,
|
|
60
|
-
Subject,
|
|
61
|
-
Tuple,
|
|
62
|
-
TupleSource,
|
|
63
|
-
} from "./tuples.js";
|
|
55
|
+
import type { Person, Subject, Tuple, TupleSource } from "./tuples.js";
|
|
64
56
|
import {
|
|
65
57
|
ACCOUNT_TYPE,
|
|
66
58
|
newInMemoryTupleSource,
|
|
@@ -76,20 +68,17 @@ export interface StoreTestTuple {
|
|
|
76
68
|
readonly user: string;
|
|
77
69
|
readonly relation: string;
|
|
78
70
|
readonly object: string;
|
|
79
|
-
readonly condition?: { readonly name: string };
|
|
80
71
|
}
|
|
81
72
|
|
|
82
73
|
export interface StoreTestCheck {
|
|
83
74
|
readonly user: string;
|
|
84
75
|
readonly object: string;
|
|
85
|
-
readonly context?: CheckContext;
|
|
86
76
|
readonly assertions: Readonly<Record<string, boolean>>;
|
|
87
77
|
}
|
|
88
78
|
|
|
89
79
|
export interface StoreTestListObjects {
|
|
90
80
|
readonly user: string;
|
|
91
81
|
readonly type: string;
|
|
92
|
-
readonly context?: CheckContext;
|
|
93
82
|
readonly assertions: Readonly<Record<string, ReadonlyArray<string>>>;
|
|
94
83
|
}
|
|
95
84
|
|
|
@@ -132,28 +121,12 @@ export function parseStoreTestDocument(
|
|
|
132
121
|
}
|
|
133
122
|
|
|
134
123
|
function parseTuple(value: unknown, where: string): StoreTestTuple {
|
|
135
|
-
const entry = record(value, where, [
|
|
136
|
-
|
|
137
|
-
"relation",
|
|
138
|
-
"object",
|
|
139
|
-
"condition",
|
|
140
|
-
]);
|
|
141
|
-
const tuple: StoreTestTuple = {
|
|
124
|
+
const entry = record(value, where, ["user", "relation", "object"]);
|
|
125
|
+
return {
|
|
142
126
|
user: text(entry["user"], `${where}.user`),
|
|
143
127
|
relation: text(entry["relation"], `${where}.relation`),
|
|
144
128
|
object: text(entry["object"], `${where}.object`),
|
|
145
129
|
};
|
|
146
|
-
if (entry["condition"] === undefined) {
|
|
147
|
-
return tuple;
|
|
148
|
-
}
|
|
149
|
-
const condition = record(entry["condition"], `${where}.condition`, [
|
|
150
|
-
"name",
|
|
151
|
-
"context",
|
|
152
|
-
]);
|
|
153
|
-
return {
|
|
154
|
-
...tuple,
|
|
155
|
-
condition: { name: text(condition["name"], `${where}.condition.name`) },
|
|
156
|
-
};
|
|
157
130
|
}
|
|
158
131
|
|
|
159
132
|
function parseTest(value: unknown, where: string): StoreTest {
|
|
@@ -173,12 +146,7 @@ function parseTest(value: unknown, where: string): StoreTest {
|
|
|
173
146
|
}
|
|
174
147
|
|
|
175
148
|
function parseCheck(value: unknown, where: string): StoreTestCheck {
|
|
176
|
-
const entry = record(value, where, [
|
|
177
|
-
"user",
|
|
178
|
-
"object",
|
|
179
|
-
"context",
|
|
180
|
-
"assertions",
|
|
181
|
-
]);
|
|
149
|
+
const entry = record(value, where, ["user", "object", "assertions"]);
|
|
182
150
|
const assertions = record(entry["assertions"], `${where}.assertions`);
|
|
183
151
|
const parsed: Record<string, boolean> = {};
|
|
184
152
|
for (const [relation, expected] of Object.entries(assertions)) {
|
|
@@ -187,17 +155,15 @@ function parseCheck(value: unknown, where: string): StoreTestCheck {
|
|
|
187
155
|
}
|
|
188
156
|
parsed[relation] = expected;
|
|
189
157
|
}
|
|
190
|
-
const context = parseContext(entry["context"], `${where}.context`);
|
|
191
158
|
return {
|
|
192
159
|
user: text(entry["user"], `${where}.user`),
|
|
193
160
|
object: text(entry["object"], `${where}.object`),
|
|
194
|
-
...(context === undefined ? {} : { context }),
|
|
195
161
|
assertions: parsed,
|
|
196
162
|
};
|
|
197
163
|
}
|
|
198
164
|
|
|
199
165
|
function parseListObjects(value: unknown, where: string): StoreTestListObjects {
|
|
200
|
-
const entry = record(value, where, ["user", "type", "
|
|
166
|
+
const entry = record(value, where, ["user", "type", "assertions"]);
|
|
201
167
|
const assertions = record(entry["assertions"], `${where}.assertions`);
|
|
202
168
|
const parsed: Record<string, ReadonlyArray<string>> = {};
|
|
203
169
|
for (const [relation, expected] of Object.entries(assertions)) {
|
|
@@ -205,26 +171,13 @@ function parseListObjects(value: unknown, where: string): StoreTestListObjects {
|
|
|
205
171
|
(item, index) => text(item, `${where}.assertions.${relation}[${index}]`),
|
|
206
172
|
);
|
|
207
173
|
}
|
|
208
|
-
const context = parseContext(entry["context"], `${where}.context`);
|
|
209
174
|
return {
|
|
210
175
|
user: text(entry["user"], `${where}.user`),
|
|
211
176
|
type: text(entry["type"], `${where}.type`),
|
|
212
|
-
...(context === undefined ? {} : { context }),
|
|
213
177
|
assertions: parsed,
|
|
214
178
|
};
|
|
215
179
|
}
|
|
216
180
|
|
|
217
|
-
function parseContext(value: unknown, where: string): CheckContext | undefined {
|
|
218
|
-
if (value === undefined) {
|
|
219
|
-
return undefined;
|
|
220
|
-
}
|
|
221
|
-
const entry = record(value, where, ["allow"]);
|
|
222
|
-
if (typeof entry["allow"] !== "boolean") {
|
|
223
|
-
throw new Error(`${where}.allow: expected a boolean`);
|
|
224
|
-
}
|
|
225
|
-
return { allow: entry["allow"] };
|
|
226
|
-
}
|
|
227
|
-
|
|
228
181
|
function record(
|
|
229
182
|
value: unknown,
|
|
230
183
|
where: string,
|
|
@@ -297,9 +250,6 @@ export interface StoreTestKit {
|
|
|
297
250
|
readonly skipped: ReadonlyArray<StoreTestSkip>;
|
|
298
251
|
}
|
|
299
252
|
|
|
300
|
-
/** The listing posture, for a check the document leaves without a context (the module header). */
|
|
301
|
-
const CONTEXT_WHEN_ABSENT: CheckContext = { allow: false };
|
|
302
|
-
|
|
303
253
|
/**
|
|
304
254
|
* The document's tests as runnable cases over `model`, plus every
|
|
305
255
|
* assertion group the kit does not run.
|
|
@@ -346,7 +296,6 @@ export function storeTestCases(
|
|
|
346
296
|
for (const item of runnable) {
|
|
347
297
|
const object = parseObjectRef(item.object);
|
|
348
298
|
const person = personOf(item.user);
|
|
349
|
-
const context = item.context ?? CONTEXT_WHEN_ABSENT;
|
|
350
299
|
for (const [relation, expected] of Object.entries(item.assertions)) {
|
|
351
300
|
const recording = recordUndeclaredTypes(source, model);
|
|
352
301
|
const actual = await checkRelation(
|
|
@@ -354,7 +303,6 @@ export function storeTestCases(
|
|
|
354
303
|
object,
|
|
355
304
|
relation,
|
|
356
305
|
person,
|
|
357
|
-
context,
|
|
358
306
|
);
|
|
359
307
|
if (recording.reached.size > 0) {
|
|
360
308
|
skipped.push({
|
|
@@ -371,7 +319,7 @@ export function storeTestCases(
|
|
|
371
319
|
assert.equal(
|
|
372
320
|
actual,
|
|
373
321
|
expected,
|
|
374
|
-
`${document.name} :: ${test.name}: ${item.user} ${relation} ${item.object}
|
|
322
|
+
`${document.name} :: ${test.name}: ${item.user} ${relation} ${item.object} expected ${String(expected)}, got ${String(actual)}`,
|
|
375
323
|
);
|
|
376
324
|
}
|
|
377
325
|
}
|
|
@@ -389,10 +337,9 @@ interface RecordingTupleSource extends TupleSource {
|
|
|
389
337
|
/**
|
|
390
338
|
* A view of `inner` that records, for one walk, every subject type the
|
|
391
339
|
* evaluator read that `model` does not declare. A person
|
|
392
|
-
* (`identity_account:<id
|
|
393
|
-
*
|
|
394
|
-
*
|
|
395
|
-
* type is.
|
|
340
|
+
* (`identity_account:<id>`) is a subject the evaluator compares, never a
|
|
341
|
+
* type it resolves, so it is not a "reach" whatever the model declares; a
|
|
342
|
+
* userset or a parent object of an undeclared type is.
|
|
396
343
|
*/
|
|
397
344
|
function recordUndeclaredTypes(
|
|
398
345
|
inner: TupleSource,
|
|
@@ -423,8 +370,6 @@ function resolvedTypeOf(subject: Subject): string | undefined {
|
|
|
423
370
|
: subject.object.type;
|
|
424
371
|
case "userset":
|
|
425
372
|
return subject.object.type;
|
|
426
|
-
case "wildcard":
|
|
427
|
-
return undefined;
|
|
428
373
|
default: {
|
|
429
374
|
const exhaustive: never = subject;
|
|
430
375
|
throw new Error(`unknown subject form: ${JSON.stringify(exhaustive)}`);
|
|
@@ -433,14 +378,10 @@ function resolvedTypeOf(subject: Subject): string | undefined {
|
|
|
433
378
|
}
|
|
434
379
|
|
|
435
380
|
function tupleOf(entry: StoreTestTuple): Tuple {
|
|
436
|
-
const subject = parseSubject(entry.user);
|
|
437
381
|
return {
|
|
438
382
|
object: parseObjectRef(entry.object),
|
|
439
383
|
relation: entry.relation,
|
|
440
|
-
subject:
|
|
441
|
-
subject.form === "wildcard" && entry.condition !== undefined
|
|
442
|
-
? { ...subject, condition: entry.condition.name }
|
|
443
|
-
: subject,
|
|
384
|
+
subject: parseSubject(entry.user),
|
|
444
385
|
};
|
|
445
386
|
}
|
|
446
387
|
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The tuple vocabulary of the built-in authorizer — the OpenFGA notions
|
|
3
3
|
* the cloud's model is written in (object, relation, subject; a subject
|
|
4
|
-
* being an object
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* being an object or a userset `type:id#relation`), typed once so the
|
|
5
|
+
* evaluator, the derivation and the store-test kit speak one language. The
|
|
6
|
+
* model declares no wildcard subject and no condition: every reader of a
|
|
7
|
+
* row is an account or the holder of a relation on an object, which is
|
|
8
|
+
* why a check needs no context parameter. Nothing here is
|
|
7
9
|
* stored: open source derives every tuple from a row (derived-tuples.ts)
|
|
8
10
|
* or reads it from an IamPolicy row, and this module only names what a
|
|
9
11
|
* tuple IS.
|
|
@@ -18,28 +20,18 @@
|
|
|
18
20
|
* The type is named here; the construction (`personFor`, `resolvePerson`)
|
|
19
21
|
* is person.ts's, so this module stays vocabulary with no I/O.
|
|
20
22
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* and the
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* catalog. The parameter is required so a caller states which posture it
|
|
28
|
-
* is in; there is no default because either one is wrong for the other.
|
|
29
|
-
*
|
|
30
|
-
* The string forms (`agent:x`, `organization:acme#viewer`,
|
|
31
|
-
* `identity_account:*`) are the notation the cloud's store tests and the
|
|
32
|
-
* IamPolicy proto both use; parse and format are here so the kit and the
|
|
33
|
-
* tests never spell them a second way.
|
|
23
|
+
* The string forms (`agent:x`, `organization:acme#viewer`) are the
|
|
24
|
+
* notation the cloud's store tests and the IamPolicy proto both use; parse
|
|
25
|
+
* and format are here so the kit and the tests never spell them a second
|
|
26
|
+
* way. The wildcard notation `type:*` is refused by the parser: the model
|
|
27
|
+
* has no line that admits one, so a fixture or a policy that spells it is
|
|
28
|
+
* a mistake to surface, never a subject to carry.
|
|
34
29
|
*/
|
|
35
30
|
import type { Message } from "@bufbuild/protobuf";
|
|
36
31
|
|
|
37
32
|
/** The FGA type name of the kind every person is: `identity_account:<id>`. */
|
|
38
33
|
export const ACCOUNT_TYPE = "identity_account";
|
|
39
34
|
|
|
40
|
-
/** The one condition the model declares; it guards the public wildcard. */
|
|
41
|
-
export const ALLOW_PUBLIC_CONDITION = "allow_public";
|
|
42
|
-
|
|
43
35
|
/** `type:id` — an object of the model. */
|
|
44
36
|
export interface ObjectRef {
|
|
45
37
|
readonly type: string;
|
|
@@ -55,12 +47,6 @@ export type Subject =
|
|
|
55
47
|
readonly form: "userset";
|
|
56
48
|
readonly object: ObjectRef;
|
|
57
49
|
readonly relation: string;
|
|
58
|
-
}
|
|
59
|
-
/** `type:*` — every object of the type, honoured only when its condition holds. */
|
|
60
|
-
| {
|
|
61
|
-
readonly form: "wildcard";
|
|
62
|
-
readonly type: string;
|
|
63
|
-
readonly condition: string | undefined;
|
|
64
50
|
};
|
|
65
51
|
|
|
66
52
|
export interface Tuple {
|
|
@@ -97,11 +83,6 @@ export interface Person {
|
|
|
97
83
|
readonly aliases: ReadonlySet<string>;
|
|
98
84
|
}
|
|
99
85
|
|
|
100
|
-
/** The condition parameters of a check (the module header). */
|
|
101
|
-
export interface CheckContext {
|
|
102
|
-
readonly allow: boolean;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
86
|
// ---------------------------------------------------------------------------
|
|
106
87
|
// The string notation.
|
|
107
88
|
// ---------------------------------------------------------------------------
|
|
@@ -120,10 +101,8 @@ export function formatObjectRef(object: ObjectRef): string {
|
|
|
120
101
|
}
|
|
121
102
|
|
|
122
103
|
/**
|
|
123
|
-
* `type:id
|
|
124
|
-
*
|
|
125
|
-
* says so (the store tests carry it on the tuple, the derivation attaches
|
|
126
|
-
* the model's).
|
|
104
|
+
* `type:id` or `type:id#relation` → Subject. `type:*` is refused (the
|
|
105
|
+
* module header): no line of the model admits a wildcard subject.
|
|
127
106
|
*/
|
|
128
107
|
export function parseSubject(text: string): Subject {
|
|
129
108
|
const hash = text.indexOf("#");
|
|
@@ -136,7 +115,9 @@ export function parseSubject(text: string): Subject {
|
|
|
136
115
|
}
|
|
137
116
|
const object = parseObjectRef(text);
|
|
138
117
|
if (object.id === "*") {
|
|
139
|
-
|
|
118
|
+
throw new Error(
|
|
119
|
+
`wildcard subject '${text}' is not admitted: the model declares no wildcard line`,
|
|
120
|
+
);
|
|
140
121
|
}
|
|
141
122
|
return { form: "object", object };
|
|
142
123
|
}
|
|
@@ -147,10 +128,6 @@ export function formatSubject(subject: Subject): string {
|
|
|
147
128
|
return formatObjectRef(subject.object);
|
|
148
129
|
case "userset":
|
|
149
130
|
return `${formatObjectRef(subject.object)}#${subject.relation}`;
|
|
150
|
-
case "wildcard":
|
|
151
|
-
return subject.condition === undefined
|
|
152
|
-
? `${subject.type}:*`
|
|
153
|
-
: `${subject.type}:* with ${subject.condition}`;
|
|
154
131
|
default: {
|
|
155
132
|
const exhaustive: never = subject;
|
|
156
133
|
throw new Error(`unknown subject form: ${JSON.stringify(exhaustive)}`);
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pins the skill transfer origin verdict (boot/skill-transfer-origin.ts):
|
|
3
|
+
* loopback on the relay is fine for a single-operator install and a
|
|
4
|
+
* warning under the require-authentication posture; a public origin is
|
|
5
|
+
* reachable whatever the posture; a bucket driver makes the base URL
|
|
6
|
+
* unused; an unparseable base URL is not mistaken for loopback.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, expect, it } from "vitest";
|
|
9
|
+
|
|
10
|
+
import { assessSkillTransferOrigin } from "../skill-transfer-origin.js";
|
|
11
|
+
|
|
12
|
+
describe("assessSkillTransferOrigin", () => {
|
|
13
|
+
it("the default loopback on a single-operator install is correct", () => {
|
|
14
|
+
expect(
|
|
15
|
+
assessSkillTransferOrigin({
|
|
16
|
+
baseUrl: "http://localhost:7234",
|
|
17
|
+
relaysThroughServer: true,
|
|
18
|
+
requireAuthentication: false,
|
|
19
|
+
}),
|
|
20
|
+
).toEqual({ kind: "loopback-local" });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it.each([
|
|
24
|
+
"http://localhost:7234",
|
|
25
|
+
"http://127.0.0.1:7234",
|
|
26
|
+
"http://[::1]:7234",
|
|
27
|
+
"http://0.0.0.0:7234",
|
|
28
|
+
])(
|
|
29
|
+
"loopback (%s) under the require-authentication posture is the warning, naming the URL",
|
|
30
|
+
(baseUrl) => {
|
|
31
|
+
expect(
|
|
32
|
+
assessSkillTransferOrigin({
|
|
33
|
+
baseUrl,
|
|
34
|
+
relaysThroughServer: true,
|
|
35
|
+
requireAuthentication: true,
|
|
36
|
+
}),
|
|
37
|
+
).toEqual({ kind: "loopback-hosted", baseUrl });
|
|
38
|
+
},
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
it("a public origin is reachable under either posture", () => {
|
|
42
|
+
for (const requireAuthentication of [true, false]) {
|
|
43
|
+
expect(
|
|
44
|
+
assessSkillTransferOrigin({
|
|
45
|
+
baseUrl: "https://api.example.com",
|
|
46
|
+
relaysThroughServer: true,
|
|
47
|
+
requireAuthentication,
|
|
48
|
+
}),
|
|
49
|
+
).toEqual({ kind: "reachable" });
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("a driver that signs its own URLs makes the base URL unused, loopback or not", () => {
|
|
54
|
+
expect(
|
|
55
|
+
assessSkillTransferOrigin({
|
|
56
|
+
baseUrl: "http://localhost:7234",
|
|
57
|
+
relaysThroughServer: false,
|
|
58
|
+
requireAuthentication: true,
|
|
59
|
+
}),
|
|
60
|
+
).toEqual({ kind: "unused" });
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("an unparseable base URL is not loopback; its failure is loud on the first mint instead", () => {
|
|
64
|
+
expect(
|
|
65
|
+
assessSkillTransferOrigin({
|
|
66
|
+
baseUrl: "not a url",
|
|
67
|
+
relaysThroughServer: true,
|
|
68
|
+
requireAuthentication: true,
|
|
69
|
+
}),
|
|
70
|
+
).toEqual({ kind: "reachable" });
|
|
71
|
+
});
|
|
72
|
+
});
|
package/src/boot/compose.ts
CHANGED
|
@@ -119,14 +119,20 @@ import { registerSkillServices } from "../domain/skill/controller.js";
|
|
|
119
119
|
import {
|
|
120
120
|
DEFAULT_SLOT_TTL_MS,
|
|
121
121
|
MAX_ZIP_SIZE,
|
|
122
|
+
STAGING_KEY_PREFIX,
|
|
122
123
|
} from "../domain/skill/constants.js";
|
|
123
124
|
import { newSkillArtifactStorage } from "../domain/skill/storage/artifact-storage.js";
|
|
124
125
|
import { newContentAddressedArchiveStore } from "../archive/content-store.js";
|
|
125
126
|
import {
|
|
126
127
|
newSkillTransferLane,
|
|
128
|
+
transferServeUrl,
|
|
127
129
|
uploadUrl as skillUploadUrl,
|
|
128
130
|
} from "../domain/skill/transfer/handler.js";
|
|
129
131
|
import { UploadSlots } from "../domain/skill/transfer/slots.js";
|
|
132
|
+
import {
|
|
133
|
+
newArchiveStaging,
|
|
134
|
+
uploadRefOf,
|
|
135
|
+
} from "../domain/skill/transfer/staging.js";
|
|
130
136
|
import {
|
|
131
137
|
DEFAULT_WRITE_VERSION,
|
|
132
138
|
ENCRYPTION_KEY_ENV_VAR,
|
|
@@ -166,6 +172,7 @@ import { SqliteStore } from "../store/sqlite/store.js";
|
|
|
166
172
|
import type { Store } from "../store/interface.js";
|
|
167
173
|
import { resolveExtensions } from "../extensions/registry.js";
|
|
168
174
|
import { resolveOAuthRedirectUri } from "./oauth-redirect-uri.js";
|
|
175
|
+
import { assessSkillTransferOrigin } from "./skill-transfer-origin.js";
|
|
169
176
|
import type { ServerExtension } from "../extensions/registry.js";
|
|
170
177
|
import { registerWorkflowServices } from "../domain/workflow/controller.js";
|
|
171
178
|
import {
|
|
@@ -926,7 +933,7 @@ export async function composeServer(
|
|
|
926
933
|
: undefined;
|
|
927
934
|
// The skill artifact store + transfer lane (Go server.go:318-340). The
|
|
928
935
|
// store is content-addressed and never-GC at {storagePath}/skills/;
|
|
929
|
-
// the slots registry wipes {storagePath}/skills
|
|
936
|
+
// the slots registry wipes {storagePath}/skills/staging at boot (orphans
|
|
930
937
|
// from a dead process are useless — the registry died with it). On OSS
|
|
931
938
|
// the lane is ALWAYS configured (Go wires it unconditionally too): the
|
|
932
939
|
// FailedPrecondition lane-absent arms exist for construction-order
|
|
@@ -938,16 +945,25 @@ export async function composeServer(
|
|
|
938
945
|
// deployment changes nothing for skill artifacts until it opts in
|
|
939
946
|
// explicitly, and the Go-written skills/ directory keeps serving in
|
|
940
947
|
// place on the default arm.
|
|
948
|
+
//
|
|
949
|
+
// Capability URLs come from the driver through the staging port
|
|
950
|
+
// (domain/skill/transfer/staging.ts, stigmer#1219): the local driver's
|
|
951
|
+
// presignPut reserves a slot here and renders the lane URL below, a
|
|
952
|
+
// bucket driver's signs a PUT straight to the bucket, and the
|
|
953
|
+
// controllers cannot tell which. The slots are constructed on every
|
|
954
|
+
// arm so the HTTP lane always has a registry to consult; on a bucket
|
|
955
|
+
// arm nothing is ever reserved in it.
|
|
941
956
|
const skillUploadSlots = new UploadSlots(
|
|
942
|
-
|
|
957
|
+
config.storagePath,
|
|
958
|
+
STAGING_KEY_PREFIX,
|
|
943
959
|
DEFAULT_SLOT_TTL_MS,
|
|
944
960
|
MAX_ZIP_SIZE,
|
|
945
961
|
);
|
|
962
|
+
const skillType =
|
|
963
|
+
config.skillArtifactStorageType === ""
|
|
964
|
+
? "local"
|
|
965
|
+
: config.skillArtifactStorageType;
|
|
946
966
|
const skillStorageDriver = ((): ArtifactStorage => {
|
|
947
|
-
const skillType =
|
|
948
|
-
config.skillArtifactStorageType === ""
|
|
949
|
-
? "local"
|
|
950
|
-
: config.skillArtifactStorageType;
|
|
951
967
|
if (skillType === "local") {
|
|
952
968
|
// Go layout invariant: {storagePath}/skills exists from boot with
|
|
953
969
|
// 0755 (the retired LocalFileStorage constructor's mkdir; the
|
|
@@ -958,16 +974,23 @@ export async function composeServer(
|
|
|
958
974
|
});
|
|
959
975
|
// The local presigned-PUT arm rides the skill transfer lane's slot
|
|
960
976
|
// mechanism (§6b Q1 ruling: one upload surface, no new lane). The
|
|
961
|
-
// slots stage inside THIS driver's root
|
|
962
|
-
// reads back
|
|
963
|
-
//
|
|
964
|
-
//
|
|
965
|
-
return new LocalArtifactStorage(
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
977
|
+
// slots stage inside THIS driver's root under the domain's staging
|
|
978
|
+
// key, so download(key) reads back what the lane received; the
|
|
979
|
+
// serve URL is the lane's download route, so getSignedUrl renders
|
|
980
|
+
// the very URL the lane dispatches.
|
|
981
|
+
return new LocalArtifactStorage(
|
|
982
|
+
config.storagePath,
|
|
983
|
+
transferServeUrl(config.skillTransferBaseUrl),
|
|
984
|
+
{
|
|
985
|
+
reserve: (key, declaredSizeBytes) => {
|
|
986
|
+
const { ttlMs } = skillUploadSlots.reserve(key, declaredSizeBytes);
|
|
987
|
+
return {
|
|
988
|
+
url: skillUploadUrl(config.skillTransferBaseUrl, uploadRefOf(key)),
|
|
989
|
+
ttlMs,
|
|
990
|
+
};
|
|
991
|
+
},
|
|
992
|
+
},
|
|
993
|
+
);
|
|
971
994
|
}
|
|
972
995
|
// Non-local skill backends share the artifact store's R2 settings for
|
|
973
996
|
// the built-in arm and the composition's registered drivers beyond it.
|
|
@@ -994,11 +1017,31 @@ export async function composeServer(
|
|
|
994
1017
|
skillStorageDriver,
|
|
995
1018
|
PLUGIN_ARTIFACT_KEY_PREFIX,
|
|
996
1019
|
);
|
|
1020
|
+
// One staging port for every archive kind, over the one skill blob
|
|
1021
|
+
// driver: the controllers mint, consume and sign through it and never
|
|
1022
|
+
// meet the slots or the bucket.
|
|
1023
|
+
const skillArchiveStaging = newArchiveStaging(skillStorageDriver, logger);
|
|
997
1024
|
const skillTransferLane = newSkillTransferLane(
|
|
998
1025
|
skillUploadSlots,
|
|
999
1026
|
skillArtifactStorage,
|
|
1000
1027
|
logger,
|
|
1001
1028
|
);
|
|
1029
|
+
// A hosted server relaying bytes through itself from its loopback
|
|
1030
|
+
// default mints URLs no caller can reach (stigmer#1219); nothing inside
|
|
1031
|
+
// the process can observe that, so the boot says it. Never fatal: the
|
|
1032
|
+
// inline push and every other RPC work, and a self-host behind a tunnel
|
|
1033
|
+
// it forgot to name deserves a pointer, not an outage.
|
|
1034
|
+
const transferOrigin = assessSkillTransferOrigin({
|
|
1035
|
+
baseUrl: config.skillTransferBaseUrl,
|
|
1036
|
+
relaysThroughServer: skillType === "local",
|
|
1037
|
+
requireAuthentication,
|
|
1038
|
+
});
|
|
1039
|
+
if (transferOrigin.kind === "loopback-hosted") {
|
|
1040
|
+
logger.warn(
|
|
1041
|
+
"skill artifact transfer lane mints capability URLs on the server's own loopback while the server requires authentication; pushes above the gRPC message cap and sandbox skill downloads will fail — set SKILL_TRANSFER_BASE_URL to the origin callers reach this server on",
|
|
1042
|
+
{ baseUrl: transferOrigin.baseUrl },
|
|
1043
|
+
);
|
|
1044
|
+
}
|
|
1002
1045
|
// The console lane (lane 4, DD-012): present only when a static export
|
|
1003
1046
|
// is bundled (slim artifacts) or configured (STIGMER_CONSOLE_DIR) —
|
|
1004
1047
|
// "not bundled" is a modeled state, logged once, with the router
|
|
@@ -1463,14 +1506,11 @@ export async function composeServer(
|
|
|
1463
1506
|
// The agentexecution blob store (server.go:362-363) — read side of
|
|
1464
1507
|
// pushFromExecutionArtifact.
|
|
1465
1508
|
executionArtifactStorage: artifactStorage,
|
|
1466
|
-
|
|
1467
|
-
slots: skillUploadSlots,
|
|
1468
|
-
baseUrl: config.skillTransferBaseUrl,
|
|
1469
|
-
},
|
|
1509
|
+
staging: skillArchiveStaging,
|
|
1470
1510
|
});
|
|
1471
1511
|
// Plugins materialise their members through the in-process lane as
|
|
1472
1512
|
// the installing caller; the lazy provider resolves the clients built
|
|
1473
|
-
// from these same routes after boot. The
|
|
1513
|
+
// from these same routes after boot. The staging port is the skill
|
|
1474
1514
|
// lane's: one upload surface for every archive.
|
|
1475
1515
|
registerPluginServices(router, {
|
|
1476
1516
|
store,
|
|
@@ -1479,10 +1519,7 @@ export async function composeServer(
|
|
|
1479
1519
|
authorizationLifecycle,
|
|
1480
1520
|
artifactStorage: pluginArtifactStorage,
|
|
1481
1521
|
materializerProvider: () => requireInProcess().pluginMaterializer,
|
|
1482
|
-
|
|
1483
|
-
slots: skillUploadSlots,
|
|
1484
|
-
baseUrl: config.skillTransferBaseUrl,
|
|
1485
|
-
},
|
|
1522
|
+
staging: skillArchiveStaging,
|
|
1486
1523
|
});
|
|
1487
1524
|
// Artifact CRUD shares the ONE blob store with agentexecution's
|
|
1488
1525
|
// attachment lanes (Go server.go 347–374).
|
package/src/boot/config.ts
CHANGED
|
@@ -121,9 +121,10 @@ export interface ServerConfig {
|
|
|
121
121
|
readonly oauthRedirectUri: string;
|
|
122
122
|
/**
|
|
123
123
|
* Skill artifact storage root (STORAGE_PATH; Go defaultStoragePath
|
|
124
|
-
* ~/.stigmer/storage). Artifacts live at {storagePath}/skills
|
|
125
|
-
*
|
|
126
|
-
*
|
|
124
|
+
* ~/.stigmer/storage). Artifacts live at {storagePath}/skills/ —
|
|
125
|
+
* byte-identical to Go's layout, so a Go-written directory is served in
|
|
126
|
+
* place at cutover — and upload staging at {storagePath}/skills/staging/,
|
|
127
|
+
* the prefix the domain names (domain/skill/constants.ts).
|
|
127
128
|
*/
|
|
128
129
|
readonly storagePath: string;
|
|
129
130
|
/**
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether the skill transfer lane's origin can be reached by the callers
|
|
3
|
+
* it is minted for.
|
|
4
|
+
*
|
|
5
|
+
* On the local blob driver the server itself carries artifact bytes over
|
|
6
|
+
* HTTP, and every upload and download capability URL it mints is rendered
|
|
7
|
+
* from `SKILL_TRANSFER_BASE_URL`, which defaults to the server's own
|
|
8
|
+
* loopback. That default is right for a single-operator install reached
|
|
9
|
+
* on the machine it runs on, and silently wrong for a hosted one: the
|
|
10
|
+
* server boots, serves, and passes every in-process check while handing
|
|
11
|
+
* the CLI, the SDKs and the sandbox runner a host none of them can reach —
|
|
12
|
+
* every push above the gRPC cap fails on the PUT and every sandbox mount
|
|
13
|
+
* of a skill with supporting files loses them (stigmer#1219). No test
|
|
14
|
+
* inside the process can see it, so the process says it at boot.
|
|
15
|
+
*
|
|
16
|
+
* The hosted signal is the require-authentication posture (boot/compose.ts
|
|
17
|
+
* combines its two sources): a server that requires credentials serves
|
|
18
|
+
* callers it does not share a machine with. A bucket driver signs URLs
|
|
19
|
+
* straight to its bucket and never renders the base URL, so the verdict
|
|
20
|
+
* is moot there and says so rather than warning about a knob nothing
|
|
21
|
+
* reads.
|
|
22
|
+
*
|
|
23
|
+
* Proven by __tests__/skill-transfer-origin.test.ts.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/** Hosts that name the process's own machine and nothing beyond it. */
|
|
27
|
+
const LOOPBACK_HOSTS = new Set([
|
|
28
|
+
"localhost",
|
|
29
|
+
"127.0.0.1",
|
|
30
|
+
"[::1]",
|
|
31
|
+
"0.0.0.0",
|
|
32
|
+
"[::]",
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
export type SkillTransferOriginVerdict =
|
|
36
|
+
/** The base URL names a host other than the server's own loopback. */
|
|
37
|
+
| { readonly kind: "reachable" }
|
|
38
|
+
/** Loopback on a single-operator install: the default, and correct. */
|
|
39
|
+
| { readonly kind: "loopback-local" }
|
|
40
|
+
/** Loopback on a server that requires authentication: minted URLs are dead on arrival. */
|
|
41
|
+
| { readonly kind: "loopback-hosted"; readonly baseUrl: string }
|
|
42
|
+
/** The blob driver signs its own URLs; the base URL is never rendered. */
|
|
43
|
+
| { readonly kind: "unused" };
|
|
44
|
+
|
|
45
|
+
export interface SkillTransferOriginInputs {
|
|
46
|
+
/** The configured or defaulted `SKILL_TRANSFER_BASE_URL`. */
|
|
47
|
+
readonly baseUrl: string;
|
|
48
|
+
/** Whether the local driver relays artifact bytes through this server. */
|
|
49
|
+
readonly relaysThroughServer: boolean;
|
|
50
|
+
/** The composed require-authentication posture. */
|
|
51
|
+
readonly requireAuthentication: boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function assessSkillTransferOrigin(
|
|
55
|
+
inputs: SkillTransferOriginInputs,
|
|
56
|
+
): SkillTransferOriginVerdict {
|
|
57
|
+
if (!inputs.relaysThroughServer) {
|
|
58
|
+
return { kind: "unused" };
|
|
59
|
+
}
|
|
60
|
+
if (!namesLoopback(inputs.baseUrl)) {
|
|
61
|
+
return { kind: "reachable" };
|
|
62
|
+
}
|
|
63
|
+
return inputs.requireAuthentication
|
|
64
|
+
? { kind: "loopback-hosted", baseUrl: inputs.baseUrl }
|
|
65
|
+
: { kind: "loopback-local" };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function namesLoopback(baseUrl: string): boolean {
|
|
69
|
+
try {
|
|
70
|
+
return LOOPBACK_HOSTS.has(new URL(baseUrl).hostname);
|
|
71
|
+
} catch {
|
|
72
|
+
// An unparseable base URL renders into every capability URL as-is; it
|
|
73
|
+
// is not loopback, and the failure it causes is loud on the first mint.
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|