@stigmer/server 3.22.1-dev.20260922095755 → 3.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/artifactstorage/artifact-storage.d.ts +29 -31
- package/dist/artifactstorage/artifact-storage.d.ts.map +1 -1
- package/dist/artifactstorage/artifact-storage.js +5 -7
- package/dist/artifactstorage/artifact-storage.js.map +1 -1
- package/dist/artifactstorage/r2-storage.d.ts +12 -7
- package/dist/artifactstorage/r2-storage.d.ts.map +1 -1
- package/dist/artifactstorage/r2-storage.js +6 -14
- package/dist/artifactstorage/r2-storage.js.map +1 -1
- package/dist/authorization/authorizer.d.ts +8 -4
- package/dist/authorization/authorizer.d.ts.map +1 -1
- package/dist/authorization/authorizer.js +9 -5
- package/dist/authorization/authorizer.js.map +1 -1
- package/dist/authorization/derived-tuples.d.ts.map +1 -1
- package/dist/authorization/derived-tuples.js +25 -10
- package/dist/authorization/derived-tuples.js.map +1 -1
- package/dist/authorization/evaluator.d.ts +8 -7
- package/dist/authorization/evaluator.d.ts.map +1 -1
- package/dist/authorization/evaluator.js +20 -4
- package/dist/authorization/evaluator.js.map +1 -1
- package/dist/authorization/list-read-scope.d.ts.map +1 -1
- package/dist/authorization/list-read-scope.js +9 -5
- package/dist/authorization/list-read-scope.js.map +1 -1
- package/dist/authorization/model/agent.d.ts.map +1 -1
- package/dist/authorization/model/agent.js +5 -4
- package/dist/authorization/model/agent.js.map +1 -1
- package/dist/authorization/model/agent_instance.d.ts.map +1 -1
- package/dist/authorization/model/agent_instance.js +2 -2
- package/dist/authorization/model/agent_instance.js.map +1 -1
- package/dist/authorization/model/mcp_server.d.ts.map +1 -1
- package/dist/authorization/model/mcp_server.js +2 -2
- package/dist/authorization/model/mcp_server.js.map +1 -1
- package/dist/authorization/model/plugin.d.ts.map +1 -1
- package/dist/authorization/model/plugin.js +4 -3
- package/dist/authorization/model/plugin.js.map +1 -1
- package/dist/authorization/model/rewrite.d.ts +10 -8
- package/dist/authorization/model/rewrite.d.ts.map +1 -1
- package/dist/authorization/model/rewrite.js +3 -0
- package/dist/authorization/model/rewrite.js.map +1 -1
- package/dist/authorization/model/skill.d.ts.map +1 -1
- package/dist/authorization/model/skill.js +2 -2
- package/dist/authorization/model/skill.js.map +1 -1
- package/dist/authorization/model/workflow.d.ts.map +1 -1
- package/dist/authorization/model/workflow.js +2 -2
- package/dist/authorization/model/workflow.js.map +1 -1
- package/dist/authorization/model/workflow_instance.d.ts.map +1 -1
- package/dist/authorization/model/workflow_instance.js +2 -2
- package/dist/authorization/model/workflow_instance.js.map +1 -1
- package/dist/authorization/organization-directory.d.ts.map +1 -1
- package/dist/authorization/organization-directory.js +3 -2
- package/dist/authorization/organization-directory.js.map +1 -1
- package/dist/authorization/store-test-kit.d.ts +6 -0
- package/dist/authorization/store-test-kit.d.ts.map +1 -1
- package/dist/authorization/store-test-kit.js +62 -16
- package/dist/authorization/store-test-kit.js.map +1 -1
- package/dist/authorization/tuples.d.ts +32 -13
- package/dist/authorization/tuples.d.ts.map +1 -1
- package/dist/authorization/tuples.js +11 -3
- package/dist/authorization/tuples.js.map +1 -1
- package/dist/boot/compose.d.ts.map +1 -1
- package/dist/boot/compose.js +49 -24
- package/dist/boot/compose.js.map +1 -1
- package/dist/boot/config.d.ts +4 -3
- package/dist/boot/config.d.ts.map +1 -1
- package/dist/boot/config.js.map +1 -1
- package/dist/boot/skill-transfer-origin.d.ts +52 -0
- package/dist/boot/skill-transfer-origin.d.ts.map +1 -0
- package/dist/boot/skill-transfer-origin.js +54 -0
- package/dist/boot/skill-transfer-origin.js.map +1 -0
- package/dist/domain/agent/controller.d.ts.map +1 -1
- package/dist/domain/agent/controller.js +4 -4
- package/dist/domain/agent/controller.js.map +1 -1
- package/dist/domain/agentchannel/controller.d.ts.map +1 -1
- package/dist/domain/agentchannel/controller.js +1 -3
- package/dist/domain/agentchannel/controller.js.map +1 -1
- package/dist/domain/agentexecution/controller.d.ts.map +1 -1
- package/dist/domain/agentexecution/controller.js +1 -3
- package/dist/domain/agentexecution/controller.js.map +1 -1
- package/dist/domain/agentinstance/controller.d.ts.map +1 -1
- package/dist/domain/agentinstance/controller.js +4 -3
- package/dist/domain/agentinstance/controller.js.map +1 -1
- package/dist/domain/agentshare/constants.d.ts +11 -6
- package/dist/domain/agentshare/constants.d.ts.map +1 -1
- package/dist/domain/agentshare/constants.js +16 -8
- package/dist/domain/agentshare/constants.js.map +1 -1
- package/dist/domain/agentshare/controller.d.ts.map +1 -1
- package/dist/domain/agentshare/controller.js +1 -3
- package/dist/domain/agentshare/controller.js.map +1 -1
- package/dist/domain/agentshare/steps.d.ts +25 -18
- package/dist/domain/agentshare/steps.d.ts.map +1 -1
- package/dist/domain/agentshare/steps.js +142 -41
- package/dist/domain/agentshare/steps.js.map +1 -1
- package/dist/domain/environment/controller.d.ts.map +1 -1
- package/dist/domain/environment/controller.js +3 -2
- package/dist/domain/environment/controller.js.map +1 -1
- package/dist/domain/executioncontext/controller.d.ts.map +1 -1
- package/dist/domain/executioncontext/controller.js +1 -2
- package/dist/domain/executioncontext/controller.js.map +1 -1
- package/dist/domain/mcpserver/controller.d.ts.map +1 -1
- package/dist/domain/mcpserver/controller.js +4 -3
- package/dist/domain/mcpserver/controller.js.map +1 -1
- package/dist/domain/plugin/controller.d.ts +3 -7
- package/dist/domain/plugin/controller.d.ts.map +1 -1
- package/dist/domain/plugin/controller.js +11 -9
- package/dist/domain/plugin/controller.js.map +1 -1
- package/dist/domain/plugin/push.d.ts +5 -7
- package/dist/domain/plugin/push.d.ts.map +1 -1
- package/dist/domain/plugin/push.js +14 -8
- package/dist/domain/plugin/push.js.map +1 -1
- package/dist/domain/schedule/controller.d.ts.map +1 -1
- package/dist/domain/schedule/controller.js +1 -3
- package/dist/domain/schedule/controller.js.map +1 -1
- package/dist/domain/session/controller.d.ts.map +1 -1
- package/dist/domain/session/controller.js +2 -3
- package/dist/domain/session/controller.js.map +1 -1
- package/dist/domain/skill/constants.d.ts +26 -3
- package/dist/domain/skill/constants.d.ts.map +1 -1
- package/dist/domain/skill/constants.js +26 -3
- package/dist/domain/skill/constants.js.map +1 -1
- package/dist/domain/skill/controller.d.ts +6 -11
- package/dist/domain/skill/controller.d.ts.map +1 -1
- package/dist/domain/skill/controller.js +35 -22
- package/dist/domain/skill/controller.js.map +1 -1
- package/dist/domain/skill/push.d.ts +2 -2
- package/dist/domain/skill/push.d.ts.map +1 -1
- package/dist/domain/skill/push.js +2 -2
- package/dist/domain/skill/push.js.map +1 -1
- package/dist/domain/skill/transfer/handler.d.ts +17 -9
- package/dist/domain/skill/transfer/handler.d.ts.map +1 -1
- package/dist/domain/skill/transfer/handler.js +19 -8
- package/dist/domain/skill/transfer/handler.js.map +1 -1
- package/dist/domain/skill/transfer/slots.d.ts +26 -41
- package/dist/domain/skill/transfer/slots.d.ts.map +1 -1
- package/dist/domain/skill/transfer/slots.js +80 -87
- package/dist/domain/skill/transfer/slots.js.map +1 -1
- package/dist/domain/skill/transfer/staging.d.ts +48 -0
- package/dist/domain/skill/transfer/staging.d.ts.map +1 -0
- package/dist/domain/skill/transfer/staging.js +127 -0
- package/dist/domain/skill/transfer/staging.js.map +1 -0
- package/dist/domain/workflow/controller.d.ts.map +1 -1
- package/dist/domain/workflow/controller.js +6 -14
- package/dist/domain/workflow/controller.js.map +1 -1
- package/dist/domain/workflow/registry/data/task-kind-registry.json +1 -1
- package/dist/domain/workflow/validation/task-config-constraints.d.ts +1 -10
- package/dist/domain/workflow/validation/task-config-constraints.d.ts.map +1 -1
- package/dist/domain/workflow/validation/task-config-constraints.js +2 -3
- package/dist/domain/workflow/validation/task-config-constraints.js.map +1 -1
- package/dist/domain/workflowexecution/controller.d.ts.map +1 -1
- package/dist/domain/workflowexecution/controller.js +2 -4
- package/dist/domain/workflowexecution/controller.js.map +1 -1
- package/dist/domain/workflowinstance/controller.d.ts.map +1 -1
- package/dist/domain/workflowinstance/controller.js +4 -3
- package/dist/domain/workflowinstance/controller.js.map +1 -1
- package/dist/extensions/authorization-queries.d.ts +5 -6
- package/dist/extensions/authorization-queries.d.ts.map +1 -1
- package/dist/extensions/list-read-scope.d.ts +1 -1
- package/dist/extensions/resource-authorization.d.ts +2 -3
- package/dist/extensions/resource-authorization.d.ts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -1
- package/dist/pipeline/apiresource-meta.d.ts +3 -12
- package/dist/pipeline/apiresource-meta.d.ts.map +1 -1
- package/dist/pipeline/apiresource-meta.js +10 -21
- package/dist/pipeline/apiresource-meta.js.map +1 -1
- package/dist/pipeline/steps/authorization-tuples.d.ts +3 -9
- package/dist/pipeline/steps/authorization-tuples.d.ts.map +1 -1
- package/dist/pipeline/steps/authorization-tuples.js +11 -9
- package/dist/pipeline/steps/authorization-tuples.js.map +1 -1
- package/dist/pipeline/steps/references.d.ts +20 -178
- package/dist/pipeline/steps/references.d.ts.map +1 -1
- package/dist/pipeline/steps/references.js +43 -344
- package/dist/pipeline/steps/references.js.map +1 -1
- package/dist/pipeline/steps/resolve-artifact-source.d.ts +11 -8
- package/dist/pipeline/steps/resolve-artifact-source.d.ts.map +1 -1
- package/dist/pipeline/steps/resolve-artifact-source.js +8 -4
- package/dist/pipeline/steps/resolve-artifact-source.js.map +1 -1
- package/dist/pipeline/steps/visibility-gates.d.ts +27 -0
- package/dist/pipeline/steps/visibility-gates.d.ts.map +1 -0
- package/dist/pipeline/steps/visibility-gates.js +139 -0
- package/dist/pipeline/steps/visibility-gates.js.map +1 -0
- package/dist/query/search/controller.js +1 -0
- package/dist/query/search/controller.js.map +1 -1
- package/dist/query/search/criteria.d.ts +5 -1
- package/dist/query/search/criteria.d.ts.map +1 -1
- package/dist/query/search/criteria.js +13 -3
- package/dist/query/search/criteria.js.map +1 -1
- package/dist/query/search/handler.d.ts +4 -5
- package/dist/query/search/handler.d.ts.map +1 -1
- package/dist/query/search/handler.js +6 -7
- package/dist/query/search/handler.js.map +1 -1
- package/dist/query/search/query-store.d.ts.map +1 -1
- package/dist/query/search/query-store.js +2 -0
- package/dist/query/search/query-store.js.map +1 -1
- package/dist/store/interface.d.ts +6 -3
- package/dist/store/interface.d.ts.map +1 -1
- package/dist/store/interface.js.map +1 -1
- package/dist/store/postgres/migrations.d.ts +1 -3
- package/dist/store/postgres/migrations.d.ts.map +1 -1
- package/dist/store/postgres/migrations.js +1 -35
- package/dist/store/postgres/migrations.js.map +1 -1
- package/dist/store/postgres/store.d.ts.map +1 -1
- package/dist/store/postgres/store.js +9 -4
- package/dist/store/postgres/store.js.map +1 -1
- package/dist/store/sqlite/migrations.d.ts +1 -3
- package/dist/store/sqlite/migrations.d.ts.map +1 -1
- package/dist/store/sqlite/migrations.js +1 -36
- package/dist/store/sqlite/migrations.js.map +1 -1
- package/dist/store/sqlite/store.d.ts.map +1 -1
- package/dist/store/sqlite/store.js +8 -4
- package/dist/store/sqlite/store.js.map +1 -1
- package/package.json +6 -6
- package/src/artifactstorage/__tests__/artifact-storage.test.ts +36 -33
- package/src/artifactstorage/__tests__/r2-storage.test.ts +11 -13
- package/src/artifactstorage/artifact-storage.ts +37 -33
- package/src/artifactstorage/r2-storage.ts +6 -15
- package/src/authorization/README.md +26 -24
- package/src/authorization/__tests__/authorizer.test.ts +9 -10
- package/src/authorization/__tests__/derived-tuples.test.ts +11 -4
- package/src/authorization/__tests__/evaluator.test.ts +73 -14
- package/src/authorization/__tests__/facts.test.ts +2 -2
- package/src/authorization/__tests__/fixtures/fga/agent-instance-creation.fga.yaml +185 -0
- package/src/authorization/__tests__/fixtures/fga/blueprint-private-visibility.fga.yaml +98 -6
- package/src/authorization/__tests__/fixtures/fga/cross-org-share-creation.fga.yaml +98 -0
- package/src/authorization/__tests__/fixtures/fga/default-instance-inheritance.fga.yaml +61 -4
- package/src/authorization/__tests__/fixtures/fga/mcp-server-authoring.fga.yaml +143 -0
- package/src/authorization/__tests__/fixtures/fga/org-admin-owner-inheritance.fga.yaml +21 -1
- package/src/authorization/__tests__/fixtures/fga/platform-visibility.fga.yaml +25 -1
- package/src/authorization/__tests__/fixtures/fga/public-visibility-setter.fga.yaml +52 -0
- package/src/authorization/__tests__/fixtures/fga/workflow-execution-sharing.fga.yaml +3 -1
- package/src/authorization/__tests__/list-read-scope.test.ts +17 -18
- package/src/authorization/__tests__/store-tests.test.ts +8 -1
- package/src/authorization/__tests__/tuples.test.ts +11 -12
- package/src/authorization/authorizer.ts +9 -4
- package/src/authorization/derived-tuples.ts +31 -14
- package/src/authorization/evaluator.ts +44 -8
- package/src/authorization/list-read-scope.ts +11 -5
- package/src/authorization/model/agent.ts +5 -2
- package/src/authorization/model/agent_instance.ts +2 -0
- package/src/authorization/model/mcp_server.ts +2 -0
- package/src/authorization/model/plugin.ts +4 -1
- package/src/authorization/model/rewrite.ts +13 -8
- package/src/authorization/model/skill.ts +2 -0
- package/src/authorization/model/workflow.ts +2 -0
- package/src/authorization/model/workflow_instance.ts +2 -0
- package/src/authorization/organization-directory.ts +3 -1
- package/src/authorization/store-test-kit.ts +75 -16
- package/src/authorization/tuples.ts +39 -16
- package/src/boot/__tests__/skill-transfer-origin.test.ts +72 -0
- package/src/boot/compose.ts +62 -25
- package/src/boot/config.ts +4 -3
- package/src/boot/skill-transfer-origin.ts +76 -0
- package/src/domain/agent/__tests__/agent.test.ts +4 -11
- package/src/domain/agent/controller.ts +7 -7
- package/src/domain/agentchannel/__tests__/agentchannel.test.ts +48 -223
- package/src/domain/agentchannel/controller.ts +1 -6
- package/src/domain/agentexecution/controller.ts +1 -6
- package/src/domain/agentinstance/controller.ts +12 -6
- package/src/domain/agentshare/__tests__/agentshare.test.ts +172 -196
- package/src/domain/agentshare/__tests__/create-authorization.test.ts +31 -16
- package/src/domain/agentshare/constants.ts +26 -14
- package/src/domain/agentshare/controller.ts +1 -6
- package/src/domain/agentshare/steps.ts +194 -41
- package/src/domain/environment/controller.ts +8 -5
- package/src/domain/executioncontext/controller.ts +1 -5
- package/src/domain/mcpserver/__tests__/mcpserver.test.ts +28 -115
- package/src/domain/mcpserver/__tests__/oauth-handshake.test.ts +18 -53
- package/src/domain/mcpserver/controller.ts +12 -6
- package/src/domain/plugin/controller.ts +23 -18
- package/src/domain/plugin/push.ts +16 -8
- package/src/domain/schedule/controller.ts +1 -6
- package/src/domain/session/controller.ts +4 -12
- package/src/domain/skill/__tests__/skill.test.ts +5 -3
- package/src/domain/skill/__tests__/slots.test.ts +113 -60
- package/src/domain/skill/__tests__/staging.test.ts +304 -0
- package/src/domain/skill/constants.ts +28 -3
- package/src/domain/skill/controller.ts +51 -35
- package/src/domain/skill/push.ts +3 -3
- package/src/domain/skill/transfer/handler.ts +33 -15
- package/src/domain/skill/transfer/slots.ts +89 -100
- package/src/domain/skill/transfer/staging.ts +191 -0
- package/src/domain/workflow/controller.ts +10 -28
- package/src/domain/workflow/registry/data/task-kind-registry.json +1 -1
- package/src/domain/workflow/validation/task-config-constraints.ts +3 -7
- package/src/domain/workflowexecution/controller.ts +2 -7
- package/src/domain/workflowinstance/controller.ts +12 -6
- package/src/extensions/__tests__/built-in-authorization-composed.test.ts +19 -32
- package/src/extensions/__tests__/extension-composition.test.ts +4 -4
- package/src/extensions/__tests__/list-read-scope-composed.test.ts +6 -9
- package/src/extensions/authorization-queries.ts +5 -6
- package/src/extensions/list-read-scope.ts +1 -1
- package/src/extensions/resource-authorization.ts +5 -3
- package/src/index.ts +14 -0
- package/src/pipeline/apiresource-meta.ts +10 -21
- package/src/pipeline/steps/__tests__/authorization-tuples.test.ts +31 -18
- package/src/pipeline/steps/__tests__/visibility-gates.test.ts +187 -0
- package/src/pipeline/steps/authorization-tuples.ts +11 -9
- package/src/pipeline/steps/references.ts +70 -578
- package/src/pipeline/steps/resolve-artifact-source.ts +20 -13
- package/src/pipeline/steps/visibility-gates.ts +174 -0
- package/src/query/search/__tests__/criteria.test.ts +4 -0
- package/src/query/search/__tests__/query-store.test.ts +40 -11
- package/src/query/search/controller.ts +1 -0
- package/src/query/search/criteria.ts +14 -0
- package/src/query/search/handler.ts +7 -6
- package/src/query/search/query-store.ts +2 -0
- package/src/store/__tests__/store-contract.ts +51 -6
- package/src/store/interface.ts +6 -3
- package/src/store/postgres/__tests__/migrations.test.ts +6 -187
- package/src/store/postgres/__tests__/store-contract.test.ts +2 -0
- package/src/store/postgres/migrations.ts +1 -48
- package/src/store/postgres/store.ts +11 -4
- package/src/store/sqlite/__tests__/migrations.test.ts +5 -191
- package/src/store/sqlite/migrations.ts +1 -45
- package/src/store/sqlite/store.ts +10 -4
- package/src/transport/registry/__tests__/lanes.test.ts +1 -1
- package/dist/domain/workflow/agent-call-references.d.ts +0 -54
- package/dist/domain/workflow/agent-call-references.d.ts.map +0 -1
- package/dist/domain/workflow/agent-call-references.js +0 -97
- package/dist/domain/workflow/agent-call-references.js.map +0 -1
- package/dist/store/public-visibility-retired.d.ts +0 -67
- package/dist/store/public-visibility-retired.d.ts.map +0 -1
- package/dist/store/public-visibility-retired.js +0 -55
- package/dist/store/public-visibility-retired.js.map +0 -1
- package/src/domain/workflow/__tests__/agent-call-references.test.ts +0 -222
- package/src/domain/workflow/agent-call-references.ts +0 -157
- package/src/pipeline/__tests__/supports-visibility.test.ts +0 -112
- package/src/pipeline/steps/__tests__/references.test.ts +0 -703
- package/src/store/__tests__/public-visibility-retired.test.ts +0 -127
- package/src/store/public-visibility-retired.ts +0 -119
|
@@ -41,18 +41,26 @@
|
|
|
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
|
-
*
|
|
49
|
-
*
|
|
44
|
+
* Context. A check's `context.allow` is the `allow_public` parameter
|
|
45
|
+
* (tuples.ts). A check without a context runs with `allow: false`, the
|
|
46
|
+
* listing posture: OpenFGA itself would fault on a conditional tuple it
|
|
47
|
+
* reached with the parameter missing, so the cloud's documents never do
|
|
48
|
+
* that — their CI would already fail — and the choice cannot change an
|
|
49
|
+
* answer the oracle gives. The parser is strict on purpose: an unknown
|
|
50
|
+
* key anywhere is a thrown fault, so a change to the format upstream is
|
|
51
|
+
* loud here rather than half-run.
|
|
50
52
|
*/
|
|
51
53
|
import assert from "node:assert/strict";
|
|
52
54
|
|
|
53
55
|
import { checkRelation } from "./evaluator.js";
|
|
54
56
|
import type { Model } from "./model/index.js";
|
|
55
|
-
import type {
|
|
57
|
+
import type {
|
|
58
|
+
CheckContext,
|
|
59
|
+
Person,
|
|
60
|
+
Subject,
|
|
61
|
+
Tuple,
|
|
62
|
+
TupleSource,
|
|
63
|
+
} from "./tuples.js";
|
|
56
64
|
import {
|
|
57
65
|
ACCOUNT_TYPE,
|
|
58
66
|
newInMemoryTupleSource,
|
|
@@ -68,17 +76,20 @@ export interface StoreTestTuple {
|
|
|
68
76
|
readonly user: string;
|
|
69
77
|
readonly relation: string;
|
|
70
78
|
readonly object: string;
|
|
79
|
+
readonly condition?: { readonly name: string };
|
|
71
80
|
}
|
|
72
81
|
|
|
73
82
|
export interface StoreTestCheck {
|
|
74
83
|
readonly user: string;
|
|
75
84
|
readonly object: string;
|
|
85
|
+
readonly context?: CheckContext;
|
|
76
86
|
readonly assertions: Readonly<Record<string, boolean>>;
|
|
77
87
|
}
|
|
78
88
|
|
|
79
89
|
export interface StoreTestListObjects {
|
|
80
90
|
readonly user: string;
|
|
81
91
|
readonly type: string;
|
|
92
|
+
readonly context?: CheckContext;
|
|
82
93
|
readonly assertions: Readonly<Record<string, ReadonlyArray<string>>>;
|
|
83
94
|
}
|
|
84
95
|
|
|
@@ -121,12 +132,28 @@ export function parseStoreTestDocument(
|
|
|
121
132
|
}
|
|
122
133
|
|
|
123
134
|
function parseTuple(value: unknown, where: string): StoreTestTuple {
|
|
124
|
-
const entry = record(value, where, [
|
|
125
|
-
|
|
135
|
+
const entry = record(value, where, [
|
|
136
|
+
"user",
|
|
137
|
+
"relation",
|
|
138
|
+
"object",
|
|
139
|
+
"condition",
|
|
140
|
+
]);
|
|
141
|
+
const tuple: StoreTestTuple = {
|
|
126
142
|
user: text(entry["user"], `${where}.user`),
|
|
127
143
|
relation: text(entry["relation"], `${where}.relation`),
|
|
128
144
|
object: text(entry["object"], `${where}.object`),
|
|
129
145
|
};
|
|
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
|
+
};
|
|
130
157
|
}
|
|
131
158
|
|
|
132
159
|
function parseTest(value: unknown, where: string): StoreTest {
|
|
@@ -146,7 +173,12 @@ function parseTest(value: unknown, where: string): StoreTest {
|
|
|
146
173
|
}
|
|
147
174
|
|
|
148
175
|
function parseCheck(value: unknown, where: string): StoreTestCheck {
|
|
149
|
-
const entry = record(value, where, [
|
|
176
|
+
const entry = record(value, where, [
|
|
177
|
+
"user",
|
|
178
|
+
"object",
|
|
179
|
+
"context",
|
|
180
|
+
"assertions",
|
|
181
|
+
]);
|
|
150
182
|
const assertions = record(entry["assertions"], `${where}.assertions`);
|
|
151
183
|
const parsed: Record<string, boolean> = {};
|
|
152
184
|
for (const [relation, expected] of Object.entries(assertions)) {
|
|
@@ -155,15 +187,17 @@ function parseCheck(value: unknown, where: string): StoreTestCheck {
|
|
|
155
187
|
}
|
|
156
188
|
parsed[relation] = expected;
|
|
157
189
|
}
|
|
190
|
+
const context = parseContext(entry["context"], `${where}.context`);
|
|
158
191
|
return {
|
|
159
192
|
user: text(entry["user"], `${where}.user`),
|
|
160
193
|
object: text(entry["object"], `${where}.object`),
|
|
194
|
+
...(context === undefined ? {} : { context }),
|
|
161
195
|
assertions: parsed,
|
|
162
196
|
};
|
|
163
197
|
}
|
|
164
198
|
|
|
165
199
|
function parseListObjects(value: unknown, where: string): StoreTestListObjects {
|
|
166
|
-
const entry = record(value, where, ["user", "type", "assertions"]);
|
|
200
|
+
const entry = record(value, where, ["user", "type", "context", "assertions"]);
|
|
167
201
|
const assertions = record(entry["assertions"], `${where}.assertions`);
|
|
168
202
|
const parsed: Record<string, ReadonlyArray<string>> = {};
|
|
169
203
|
for (const [relation, expected] of Object.entries(assertions)) {
|
|
@@ -171,13 +205,26 @@ function parseListObjects(value: unknown, where: string): StoreTestListObjects {
|
|
|
171
205
|
(item, index) => text(item, `${where}.assertions.${relation}[${index}]`),
|
|
172
206
|
);
|
|
173
207
|
}
|
|
208
|
+
const context = parseContext(entry["context"], `${where}.context`);
|
|
174
209
|
return {
|
|
175
210
|
user: text(entry["user"], `${where}.user`),
|
|
176
211
|
type: text(entry["type"], `${where}.type`),
|
|
212
|
+
...(context === undefined ? {} : { context }),
|
|
177
213
|
assertions: parsed,
|
|
178
214
|
};
|
|
179
215
|
}
|
|
180
216
|
|
|
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
|
+
|
|
181
228
|
function record(
|
|
182
229
|
value: unknown,
|
|
183
230
|
where: string,
|
|
@@ -250,6 +297,9 @@ export interface StoreTestKit {
|
|
|
250
297
|
readonly skipped: ReadonlyArray<StoreTestSkip>;
|
|
251
298
|
}
|
|
252
299
|
|
|
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
|
+
|
|
253
303
|
/**
|
|
254
304
|
* The document's tests as runnable cases over `model`, plus every
|
|
255
305
|
* assertion group the kit does not run.
|
|
@@ -296,6 +346,7 @@ export function storeTestCases(
|
|
|
296
346
|
for (const item of runnable) {
|
|
297
347
|
const object = parseObjectRef(item.object);
|
|
298
348
|
const person = personOf(item.user);
|
|
349
|
+
const context = item.context ?? CONTEXT_WHEN_ABSENT;
|
|
299
350
|
for (const [relation, expected] of Object.entries(item.assertions)) {
|
|
300
351
|
const recording = recordUndeclaredTypes(source, model);
|
|
301
352
|
const actual = await checkRelation(
|
|
@@ -303,6 +354,7 @@ export function storeTestCases(
|
|
|
303
354
|
object,
|
|
304
355
|
relation,
|
|
305
356
|
person,
|
|
357
|
+
context,
|
|
306
358
|
);
|
|
307
359
|
if (recording.reached.size > 0) {
|
|
308
360
|
skipped.push({
|
|
@@ -319,7 +371,7 @@ export function storeTestCases(
|
|
|
319
371
|
assert.equal(
|
|
320
372
|
actual,
|
|
321
373
|
expected,
|
|
322
|
-
`${document.name} :: ${test.name}: ${item.user} ${relation} ${item.object} expected ${String(expected)}, got ${String(actual)}`,
|
|
374
|
+
`${document.name} :: ${test.name}: ${item.user} ${relation} ${item.object} (allow: ${String(context.allow)}) expected ${String(expected)}, got ${String(actual)}`,
|
|
323
375
|
);
|
|
324
376
|
}
|
|
325
377
|
}
|
|
@@ -337,9 +389,10 @@ interface RecordingTupleSource extends TupleSource {
|
|
|
337
389
|
/**
|
|
338
390
|
* A view of `inner` that records, for one walk, every subject type the
|
|
339
391
|
* evaluator read that `model` does not declare. A person
|
|
340
|
-
* (`identity_account:<id
|
|
341
|
-
* type it resolves, so it is not a "reach" whatever
|
|
342
|
-
* userset or a parent object of an undeclared
|
|
392
|
+
* (`identity_account:<id>`, the wildcard) is a subject the evaluator
|
|
393
|
+
* compares, never a type it resolves, so it is not a "reach" whatever
|
|
394
|
+
* the model declares; a userset or a parent object of an undeclared
|
|
395
|
+
* type is.
|
|
343
396
|
*/
|
|
344
397
|
function recordUndeclaredTypes(
|
|
345
398
|
inner: TupleSource,
|
|
@@ -370,6 +423,8 @@ function resolvedTypeOf(subject: Subject): string | undefined {
|
|
|
370
423
|
: subject.object.type;
|
|
371
424
|
case "userset":
|
|
372
425
|
return subject.object.type;
|
|
426
|
+
case "wildcard":
|
|
427
|
+
return undefined;
|
|
373
428
|
default: {
|
|
374
429
|
const exhaustive: never = subject;
|
|
375
430
|
throw new Error(`unknown subject form: ${JSON.stringify(exhaustive)}`);
|
|
@@ -378,10 +433,14 @@ function resolvedTypeOf(subject: Subject): string | undefined {
|
|
|
378
433
|
}
|
|
379
434
|
|
|
380
435
|
function tupleOf(entry: StoreTestTuple): Tuple {
|
|
436
|
+
const subject = parseSubject(entry.user);
|
|
381
437
|
return {
|
|
382
438
|
object: parseObjectRef(entry.object),
|
|
383
439
|
relation: entry.relation,
|
|
384
|
-
subject:
|
|
440
|
+
subject:
|
|
441
|
+
subject.form === "wildcard" && entry.condition !== undefined
|
|
442
|
+
? { ...subject, condition: entry.condition.name }
|
|
443
|
+
: subject,
|
|
385
444
|
};
|
|
386
445
|
}
|
|
387
446
|
|
|
@@ -1,11 +1,9 @@
|
|
|
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
|
-
*
|
|
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
|
|
4
|
+
* being an object, a userset `type:id#relation`, or the public wildcard
|
|
5
|
+
* `type:*` under a condition), typed once so the evaluator, the
|
|
6
|
+
* derivation and the store-test kit speak one language. Nothing here is
|
|
9
7
|
* stored: open source derives every tuple from a row (derived-tuples.ts)
|
|
10
8
|
* or reads it from an IamPolicy row, and this module only names what a
|
|
11
9
|
* tuple IS.
|
|
@@ -20,18 +18,28 @@
|
|
|
20
18
|
* The type is named here; the construction (`personFor`, `resolvePerson`)
|
|
21
19
|
* is person.ts's, so this module stays vocabulary with no I/O.
|
|
22
20
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* and
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
21
|
+
* `CheckContext` is the FGA condition context. The one condition the
|
|
22
|
+
* model declares is `allow_public(allow: bool)` on the public wildcard,
|
|
23
|
+
* and the cloud's own posture (stigmer-cloud authorizer/fga-authorizer.ts,
|
|
24
|
+
* the Java BuildCheckRequest) is `allow: true` on a point check — a public
|
|
25
|
+
* resource reads for any authenticated caller — and `allow: false` in a
|
|
26
|
+
* listing, which keeps a public wildcard from flooding an organization's
|
|
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.
|
|
29
34
|
*/
|
|
30
35
|
import type { Message } from "@bufbuild/protobuf";
|
|
31
36
|
|
|
32
37
|
/** The FGA type name of the kind every person is: `identity_account:<id>`. */
|
|
33
38
|
export const ACCOUNT_TYPE = "identity_account";
|
|
34
39
|
|
|
40
|
+
/** The one condition the model declares; it guards the public wildcard. */
|
|
41
|
+
export const ALLOW_PUBLIC_CONDITION = "allow_public";
|
|
42
|
+
|
|
35
43
|
/** `type:id` — an object of the model. */
|
|
36
44
|
export interface ObjectRef {
|
|
37
45
|
readonly type: string;
|
|
@@ -47,6 +55,12 @@ export type Subject =
|
|
|
47
55
|
readonly form: "userset";
|
|
48
56
|
readonly object: ObjectRef;
|
|
49
57
|
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;
|
|
50
64
|
};
|
|
51
65
|
|
|
52
66
|
export interface Tuple {
|
|
@@ -83,6 +97,11 @@ export interface Person {
|
|
|
83
97
|
readonly aliases: ReadonlySet<string>;
|
|
84
98
|
}
|
|
85
99
|
|
|
100
|
+
/** The condition parameters of a check (the module header). */
|
|
101
|
+
export interface CheckContext {
|
|
102
|
+
readonly allow: boolean;
|
|
103
|
+
}
|
|
104
|
+
|
|
86
105
|
// ---------------------------------------------------------------------------
|
|
87
106
|
// The string notation.
|
|
88
107
|
// ---------------------------------------------------------------------------
|
|
@@ -101,8 +120,10 @@ export function formatObjectRef(object: ObjectRef): string {
|
|
|
101
120
|
}
|
|
102
121
|
|
|
103
122
|
/**
|
|
104
|
-
* `type:id
|
|
105
|
-
*
|
|
123
|
+
* `type:id`, `type:id#relation` or `type:*` → Subject. A wildcard parsed
|
|
124
|
+
* from text carries no condition; the caller attaches one when its source
|
|
125
|
+
* says so (the store tests carry it on the tuple, the derivation attaches
|
|
126
|
+
* the model's).
|
|
106
127
|
*/
|
|
107
128
|
export function parseSubject(text: string): Subject {
|
|
108
129
|
const hash = text.indexOf("#");
|
|
@@ -115,9 +136,7 @@ export function parseSubject(text: string): Subject {
|
|
|
115
136
|
}
|
|
116
137
|
const object = parseObjectRef(text);
|
|
117
138
|
if (object.id === "*") {
|
|
118
|
-
|
|
119
|
-
`wildcard subject '${text}' is not admitted: the model declares no wildcard line`,
|
|
120
|
-
);
|
|
139
|
+
return { form: "wildcard", type: object.type, condition: undefined };
|
|
121
140
|
}
|
|
122
141
|
return { form: "object", object };
|
|
123
142
|
}
|
|
@@ -128,6 +147,10 @@ export function formatSubject(subject: Subject): string {
|
|
|
128
147
|
return formatObjectRef(subject.object);
|
|
129
148
|
case "userset":
|
|
130
149
|
return `${formatObjectRef(subject.object)}#${subject.relation}`;
|
|
150
|
+
case "wildcard":
|
|
151
|
+
return subject.condition === undefined
|
|
152
|
+
? `${subject.type}:*`
|
|
153
|
+
: `${subject.type}:* with ${subject.condition}`;
|
|
131
154
|
default: {
|
|
132
155
|
const exhaustive: never = subject;
|
|
133
156
|
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
|
+
}
|