@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
|
@@ -13,10 +13,12 @@
|
|
|
13
13
|
* optional deps; every absent-surface answer is a deliberate arm, not an
|
|
14
14
|
* accident.
|
|
15
15
|
*
|
|
16
|
-
* Versus Stigmer Cloud, OSS excludes the
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
16
|
+
* Versus Stigmer Cloud, OSS excludes the TransformResponse/SendResponse
|
|
17
|
+
* steps (no response transformation). The transfer lane's capability
|
|
18
|
+
* URLs come from the blob driver through the staging port
|
|
19
|
+
* (transfer/staging.ts): the local driver's point at this server's own
|
|
20
|
+
* HTTP lane, a bucket driver's straight at the bucket — same
|
|
21
|
+
* capability-URL trust model, same client semantics, one code path.
|
|
20
22
|
*
|
|
21
23
|
* Proven by skill.conformance.test.ts (CONFORMANCE_TARGET=local) and
|
|
22
24
|
* the co-located __tests__/ suites.
|
|
@@ -75,7 +77,6 @@ import {
|
|
|
75
77
|
newAuthorizeResolvedTargetStep,
|
|
76
78
|
} from "../../pipeline/steps/authorize-resolved-target.js";
|
|
77
79
|
import { versionHistoryTarget } from "../../pipeline/steps/version-history.js";
|
|
78
|
-
import { newAuthorizeVisibilityTransitionStep } from "../../pipeline/steps/visibility-gates.js";
|
|
79
80
|
import {
|
|
80
81
|
newCleanupIamPoliciesStep,
|
|
81
82
|
newRecordVisibilityBeforeUpdateStep,
|
|
@@ -119,8 +120,11 @@ import {
|
|
|
119
120
|
import { skillSearchExtractor } from "./search-extractor.js";
|
|
120
121
|
import { ArtifactNotFoundError } from "./storage/artifact-storage.js";
|
|
121
122
|
import type { SkillArtifactStorage } from "./storage/artifact-storage.js";
|
|
122
|
-
import {
|
|
123
|
-
|
|
123
|
+
import type {
|
|
124
|
+
ArchiveStaging,
|
|
125
|
+
DownloadCapability,
|
|
126
|
+
StagedUpload,
|
|
127
|
+
} from "./transfer/staging.js";
|
|
124
128
|
import {
|
|
125
129
|
LIST_VERSIONS_RESPONSE_KEY,
|
|
126
130
|
LIST_VERSIONS_SKILL_ID_KEY,
|
|
@@ -136,13 +140,6 @@ import {
|
|
|
136
140
|
*/
|
|
137
141
|
const EXECUTION_ARTIFACT_DOWNLOAD_TIMEOUT_MS = 60_000;
|
|
138
142
|
|
|
139
|
-
/** The transfer lane's controller-side pair (Go SetTransferLane). */
|
|
140
|
-
export interface SkillTransferLaneDeps {
|
|
141
|
-
readonly slots: UploadSlots;
|
|
142
|
-
/** Externally-reachable base minted into upload and download URLs. */
|
|
143
|
-
readonly baseUrl: string;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
143
|
export interface SkillControllerDeps {
|
|
147
144
|
readonly store: Store;
|
|
148
145
|
readonly logger: Logger;
|
|
@@ -158,11 +155,12 @@ export interface SkillControllerDeps {
|
|
|
158
155
|
*/
|
|
159
156
|
readonly executionArtifactStorage?: ArtifactStorage;
|
|
160
157
|
/**
|
|
161
|
-
* The
|
|
162
|
-
* createArtifactUploadUrl and
|
|
163
|
-
* FailedPrecondition and push accepts
|
|
158
|
+
* The transfer lane's staging port (Go SetTransferLane) over the skill
|
|
159
|
+
* blob driver. Optional — absent, createArtifactUploadUrl and
|
|
160
|
+
* getArtifactDownloadUrl answer FailedPrecondition and push accepts
|
|
161
|
+
* inline bytes only.
|
|
164
162
|
*/
|
|
165
|
-
readonly
|
|
163
|
+
readonly staging?: ArchiveStaging;
|
|
166
164
|
}
|
|
167
165
|
|
|
168
166
|
/** Registers both skill services on the router (routes stage). */
|
|
@@ -222,7 +220,7 @@ async function push(
|
|
|
222
220
|
await newPipeline<typeof PushSkillRequestSchema>("skill-push", deps.logger)
|
|
223
221
|
.addStep(newAuthorizeStep(method, deps.authorizer))
|
|
224
222
|
.addStep(newValidateProtoStep())
|
|
225
|
-
.addStep(newResolveArtifactSourceStep(deps.
|
|
223
|
+
.addStep(newResolveArtifactSourceStep(deps.staging))
|
|
226
224
|
.addStep(newBuildInitialSkillStep())
|
|
227
225
|
.addStep(newExtractAndHashArtifactStep())
|
|
228
226
|
.addStep(newResolveSlugForPushStep())
|
|
@@ -266,19 +264,20 @@ async function push(
|
|
|
266
264
|
}
|
|
267
265
|
|
|
268
266
|
/**
|
|
269
|
-
* CreateArtifactUploadUrl — mints a short-lived, single-use
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
* surfacing as a
|
|
267
|
+
* CreateArtifactUploadUrl — mints a short-lived, single-use upload URL for
|
|
268
|
+
* an artifact exceeding the gRPC message cap (#675): the blob driver's own
|
|
269
|
+
* PUT surface, through the staging port. The size gate is the fail-loud
|
|
270
|
+
* half of the contract: an over-limit artifact is refused with the actual
|
|
271
|
+
* limit in the message BEFORE any bytes move, instead of surfacing as a
|
|
272
|
+
* transport error mid-upload.
|
|
274
273
|
*/
|
|
275
274
|
async function createArtifactUploadUrl(
|
|
276
275
|
deps: SkillControllerDeps,
|
|
277
276
|
req: CreateSkillArtifactUploadUrlRequest,
|
|
278
277
|
ctx: HandlerContext,
|
|
279
278
|
): Promise<SkillArtifactUploadUrl> {
|
|
280
|
-
const
|
|
281
|
-
if (
|
|
279
|
+
const staging = deps.staging;
|
|
280
|
+
if (staging === undefined) {
|
|
282
281
|
throw failedPreconditionError(TRANSFER_LANE_NOT_CONFIGURED);
|
|
283
282
|
}
|
|
284
283
|
|
|
@@ -307,15 +306,15 @@ async function createArtifactUploadUrl(
|
|
|
307
306
|
);
|
|
308
307
|
}
|
|
309
308
|
|
|
310
|
-
let minted:
|
|
309
|
+
let minted: StagedUpload;
|
|
311
310
|
try {
|
|
312
|
-
minted =
|
|
311
|
+
minted = await staging.mint(Number(req.sizeBytes));
|
|
313
312
|
} catch (error) {
|
|
314
313
|
throw internalError(error, "failed to mint upload reference");
|
|
315
314
|
}
|
|
316
315
|
|
|
317
316
|
return create(SkillArtifactUploadUrlSchema, {
|
|
318
|
-
url:
|
|
317
|
+
url: minted.url,
|
|
319
318
|
artifactUploadRef: minted.ref,
|
|
320
319
|
ttlSeconds: Math.trunc(minted.ttlMs / 1000),
|
|
321
320
|
});
|
|
@@ -450,12 +449,6 @@ async function updateVisibility(
|
|
|
450
449
|
)
|
|
451
450
|
.addStep(newRecordVisibilityBeforeUpdateStep(UPDATE_VISIBILITY_SKILL_KEY))
|
|
452
451
|
.addStep(newValidateVisibilityUpdateStep())
|
|
453
|
-
.addStep(
|
|
454
|
-
newAuthorizeVisibilityTransitionStep(
|
|
455
|
-
UPDATE_VISIBILITY_SKILL_KEY,
|
|
456
|
-
deps.authorizer,
|
|
457
|
-
),
|
|
458
|
-
)
|
|
459
452
|
.addStep(newSetVisibilityStep())
|
|
460
453
|
.addStep(newPersistSkillForVisibilityUpdateStep(deps.store))
|
|
461
454
|
.addStep(
|
|
@@ -738,17 +731,19 @@ async function getArtifact(
|
|
|
738
731
|
|
|
739
732
|
/**
|
|
740
733
|
* GetArtifactDownloadUrl — the transfer-lane twin of GetArtifact (#675):
|
|
741
|
-
* stats (never loads) the artifact
|
|
742
|
-
*
|
|
743
|
-
*
|
|
734
|
+
* stats (never loads) the artifact, then asks the staging port for its
|
|
735
|
+
* capability URL. ttl_seconds is the floor of the URL's validity: a bucket
|
|
736
|
+
* driver signs its GET for exactly that long, while the local lane's URL
|
|
737
|
+
* embeds the same content-hash capability the storage key does and never
|
|
738
|
+
* expires, so the floor holds there trivially.
|
|
744
739
|
*/
|
|
745
740
|
async function getArtifactDownloadUrl(
|
|
746
741
|
deps: SkillControllerDeps,
|
|
747
742
|
req: GetArtifactRequest,
|
|
748
743
|
ctx: HandlerContext,
|
|
749
744
|
): Promise<SkillArtifactDownloadUrl> {
|
|
750
|
-
const
|
|
751
|
-
if (
|
|
745
|
+
const staging = deps.staging;
|
|
746
|
+
if (staging === undefined) {
|
|
752
747
|
throw failedPreconditionError(TRANSFER_LANE_NOT_CONFIGURED);
|
|
753
748
|
}
|
|
754
749
|
|
|
@@ -784,9 +779,16 @@ async function getArtifactDownloadUrl(
|
|
|
784
779
|
throw internalError(error, "failed to stat skill artifact");
|
|
785
780
|
}
|
|
786
781
|
|
|
782
|
+
let capability: DownloadCapability;
|
|
783
|
+
try {
|
|
784
|
+
capability = await staging.downloadUrl(req.artifactStorageKey);
|
|
785
|
+
} catch (error) {
|
|
786
|
+
throw internalError(error, "failed to mint skill artifact download URL");
|
|
787
|
+
}
|
|
788
|
+
|
|
787
789
|
return create(SkillArtifactDownloadUrlSchema, {
|
|
788
|
-
url:
|
|
789
|
-
ttlSeconds:
|
|
790
|
+
url: capability.url,
|
|
791
|
+
ttlSeconds: Math.trunc(capability.ttlMs / 1000),
|
|
790
792
|
sizeBytes: BigInt(size),
|
|
791
793
|
});
|
|
792
794
|
}
|
package/src/domain/skill/push.ts
CHANGED
|
@@ -81,7 +81,7 @@ import { skillSearchExtractor } from "./search-extractor.js";
|
|
|
81
81
|
import { extractSkillMd } from "./storage/zip-gate.js";
|
|
82
82
|
import type { ExtractSkillMdResult } from "./storage/zip-gate.js";
|
|
83
83
|
import type { SkillArtifactStorage } from "./storage/artifact-storage.js";
|
|
84
|
-
import type {
|
|
84
|
+
import type { ArchiveStaging } from "./transfer/staging.js";
|
|
85
85
|
|
|
86
86
|
type PushDesc = typeof PushSkillRequestSchema;
|
|
87
87
|
|
|
@@ -99,9 +99,9 @@ export const SHOULD_CREATE_SKILL_KEY = "shouldCreateSkill";
|
|
|
99
99
|
* pipeline/steps/resolve-artifact-source.ts for the semantics).
|
|
100
100
|
*/
|
|
101
101
|
export function newResolveArtifactSourceStep(
|
|
102
|
-
|
|
102
|
+
staging: ArchiveStaging | undefined,
|
|
103
103
|
): PipelineStep<PushDesc> {
|
|
104
|
-
return newSharedResolveArtifactSourceStep<PushDesc>(
|
|
104
|
+
return newSharedResolveArtifactSourceStep<PushDesc>(staging, {
|
|
105
105
|
source: (req) => ({
|
|
106
106
|
artifact: req.artifact,
|
|
107
107
|
artifactUploadRef: req.artifactUploadRef,
|
|
@@ -1,24 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Skill artifact transfer lane — ports pkg/domain/skill/transfer/handler.go:
|
|
3
3
|
* the HTTP upload/download surface that carries skill artifact bytes
|
|
4
|
-
* OUTSIDE the gRPC control plane (#675)
|
|
5
|
-
* 10MB while the skill layer permits
|
|
6
|
-
* physically cannot carry every valid
|
|
4
|
+
* OUTSIDE the gRPC control plane (#675) when the blob driver is the local
|
|
5
|
+
* one. The gRPC server caps messages at 10MB while the skill layer permits
|
|
6
|
+
* 100MB artifacts, so inline bytes physically cannot carry every valid
|
|
7
|
+
* skill; a bucket driver signs URLs straight to its bucket and this lane
|
|
8
|
+
* carries nothing for it.
|
|
7
9
|
*
|
|
8
10
|
* PUT {prefix}/uploads/{ref} — stage artifact bytes (capability: ref)
|
|
9
11
|
* GET {prefix}/{storage_key} — serve artifact bytes (capability: key)
|
|
10
12
|
*
|
|
11
13
|
* Neither route carries bearer auth by design: the URL is the credential,
|
|
12
|
-
* mirroring
|
|
14
|
+
* mirroring a bucket's pre-signed URLs. Minting an upload URL requires the
|
|
13
15
|
* same gRPC authorization as push; download keys are unguessable content
|
|
14
16
|
* hashes handed out by authorized skill reads.
|
|
15
17
|
*
|
|
16
18
|
* The lane slots into the transport's existing skillTransferLane seam
|
|
17
19
|
* (transport/server.ts lane 3); URL renderers live next to the route
|
|
18
20
|
* dispatch so the URL shape and its handler cannot drift apart (Go's
|
|
19
|
-
* stated invariant)
|
|
20
|
-
*
|
|
21
|
-
*
|
|
21
|
+
* stated invariant): uploadUrl is what the local driver's staged-upload
|
|
22
|
+
* lane mints, and transferServeUrl is the serve URL the composition hands
|
|
23
|
+
* the local skill driver so its signed GETs land on the download route
|
|
24
|
+
* below. Proven by __tests__/skill.test.ts's transfer-lane block (both
|
|
25
|
+
* protocol stacks) and the conformance suite's transfer-lane tests.
|
|
22
26
|
*/
|
|
23
27
|
import type { Logger } from "../../../boot/logger.js";
|
|
24
28
|
import { SKILL_ARTIFACTS_PATH_PREFIX } from "../../../transport/constants.js";
|
|
@@ -32,6 +36,7 @@ import {
|
|
|
32
36
|
SlotUnknownError,
|
|
33
37
|
} from "./slots.js";
|
|
34
38
|
import type { UploadSlots } from "./slots.js";
|
|
39
|
+
import { stagingKeyOf } from "./staging.js";
|
|
35
40
|
|
|
36
41
|
/**
|
|
37
42
|
* Renders the capability URL for a minted reference against the lane's
|
|
@@ -41,9 +46,13 @@ export function uploadUrl(baseUrl: string, ref: string): string {
|
|
|
41
46
|
return `${trimTrailingSlash(baseUrl)}${SKILL_ARTIFACTS_PATH_PREFIX}${UPLOADS_SEGMENT}${ref}`;
|
|
42
47
|
}
|
|
43
48
|
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
/**
|
|
50
|
+
* The lane's download origin: the local skill driver's serve URL, so that
|
|
51
|
+
* `${serveUrl}/${storageKey}` (LocalArtifactStorage.getSignedUrl's shape)
|
|
52
|
+
* is exactly the GET route this handler dispatches.
|
|
53
|
+
*/
|
|
54
|
+
export function transferServeUrl(baseUrl: string): string {
|
|
55
|
+
return `${trimTrailingSlash(baseUrl)}${SKILL_ARTIFACTS_PATH_PREFIX}`;
|
|
47
56
|
}
|
|
48
57
|
|
|
49
58
|
function trimTrailingSlash(url: string): string {
|
|
@@ -108,16 +117,25 @@ async function handleUpload(
|
|
|
108
117
|
return;
|
|
109
118
|
}
|
|
110
119
|
|
|
120
|
+
// Deliberately one shape for "malformed", "never existed" and "expired":
|
|
121
|
+
// distinguishing them would let an unauthorized caller probe which
|
|
122
|
+
// tokens were once valid.
|
|
123
|
+
const unknown = () =>
|
|
124
|
+
writeText(response, 404, "upload reference unknown or expired — request a new upload URL");
|
|
125
|
+
|
|
126
|
+
const key = stagingKeyOf(ref);
|
|
127
|
+
if (key === undefined) {
|
|
128
|
+
unknown();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
111
132
|
try {
|
|
112
|
-
await slots.receive(
|
|
133
|
+
await slots.receive(key, request);
|
|
113
134
|
response.statusCode = 204;
|
|
114
135
|
response.end();
|
|
115
136
|
} catch (error) {
|
|
116
137
|
if (error instanceof SlotUnknownError) {
|
|
117
|
-
|
|
118
|
-
// distinguishing them would let an unauthorized caller probe which
|
|
119
|
-
// tokens were once valid.
|
|
120
|
-
writeText(response, 404, "upload reference unknown or expired — request a new upload URL");
|
|
138
|
+
unknown();
|
|
121
139
|
} else if (error instanceof SlotConsumedError) {
|
|
122
140
|
writeText(response, 409, "upload reference already used — request a new upload URL");
|
|
123
141
|
} else if (error instanceof SizeMismatchError) {
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Skill artifact upload slots —
|
|
3
|
-
*
|
|
4
|
-
* staging
|
|
2
|
+
* Skill artifact upload slots — the local blob driver's staging mechanism,
|
|
3
|
+
* ported from pkg/domain/skill/transfer/slots.go: an in-memory registry of
|
|
4
|
+
* single-use upload capabilities plus the staging files their bytes land
|
|
5
|
+
* in (#675).
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
+
* A slot is keyed by the STAGING KEY the domain names (`skills/staging/
|
|
8
|
+
* <hex>.zip`, transfer/staging.ts), and its file is that key resolved
|
|
9
|
+
* under the blob driver's root — so the driver's own download(key) reads
|
|
10
|
+
* what the lane received and its delete(key) retires the bytes, exactly
|
|
11
|
+
* as a bucket driver's would. The registry therefore knows nothing of
|
|
12
|
+
* consumption: it reserves, receives, and sweeps; the domain reads and
|
|
13
|
+
* deletes through the driver, one path for every backend.
|
|
14
|
+
*
|
|
15
|
+
* In-memory is deliberate: the local driver is single-instance (the same
|
|
7
16
|
* assumption the in-process router transport and SQLite store already
|
|
8
17
|
* make), and a slot is worthless across restarts anyway — its bytes live
|
|
9
18
|
* in the staging directory, which is swept on boot.
|
|
@@ -11,20 +20,16 @@
|
|
|
11
20
|
* Sentinel error classes replace Go's sentinel error values so the HTTP
|
|
12
21
|
* handler maps registry failures onto honest status codes without string
|
|
13
22
|
* matching; their message texts are Go's, verbatim (they ride wire-visible
|
|
14
|
-
* copy:
|
|
15
|
-
* body).
|
|
23
|
+
* copy: the handler's 404, 409 and 400 bodies).
|
|
16
24
|
*
|
|
17
25
|
* Proven by __tests__/slots.test.ts (injected clock for expiry) and the
|
|
18
26
|
* conformance suite's transfer-lane tests.
|
|
19
27
|
*/
|
|
20
|
-
import { randomBytes } from "node:crypto";
|
|
21
28
|
import fs from "node:fs";
|
|
22
29
|
import path from "node:path";
|
|
23
30
|
import { pipeline } from "node:stream/promises";
|
|
24
31
|
import type { Readable } from "node:stream";
|
|
25
32
|
|
|
26
|
-
import { REF_BYTE_LEN, REF_PREFIX } from "../constants.js";
|
|
27
|
-
|
|
28
33
|
/** Go errSlotUnknown — never existed, expired, or swept. */
|
|
29
34
|
export class SlotUnknownError extends Error {
|
|
30
35
|
constructor() {
|
|
@@ -41,18 +46,12 @@ export class SlotConsumedError extends Error {
|
|
|
41
46
|
}
|
|
42
47
|
}
|
|
43
48
|
|
|
44
|
-
/** Go
|
|
45
|
-
export class SlotEmptyError extends Error {
|
|
46
|
-
constructor() {
|
|
47
|
-
super("upload reference has no uploaded bytes");
|
|
48
|
-
this.name = "SlotEmptyError";
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** Go errSizeMismatch — the body disagreed with the minted declaration. */
|
|
49
|
+
/** Go errSizeMismatch — the body disagreed with the reserved declaration. */
|
|
53
50
|
export class SizeMismatchError extends Error {
|
|
54
51
|
constructor(received: number, declared: number) {
|
|
55
|
-
super(
|
|
52
|
+
super(
|
|
53
|
+
`upload size mismatch: received ${received} bytes, declared ${declared}`,
|
|
54
|
+
);
|
|
56
55
|
this.name = "SizeMismatchError";
|
|
57
56
|
}
|
|
58
57
|
}
|
|
@@ -63,65 +62,76 @@ interface Slot {
|
|
|
63
62
|
uploaded: boolean;
|
|
64
63
|
}
|
|
65
64
|
|
|
65
|
+
/**
|
|
66
|
+
* The one file-name shape a staging key may carry past its prefix: the
|
|
67
|
+
* domain's hex reference plus the archive suffix. Keys are server-minted,
|
|
68
|
+
* never client-supplied, so this is an invariant check, not input
|
|
69
|
+
* validation — a violation is a programming error and throws.
|
|
70
|
+
*/
|
|
71
|
+
const STAGED_FILE_NAME = /^[0-9a-f]+\.zip$/;
|
|
72
|
+
|
|
66
73
|
export class UploadSlots {
|
|
67
74
|
private readonly slots = new Map<string, Slot>();
|
|
68
75
|
private readonly stagingDir: string;
|
|
69
|
-
private readonly ttlMs: number;
|
|
70
|
-
private readonly maxSize: number;
|
|
71
76
|
/** Injectable for expiry tests, mirroring Go's `now` field. */
|
|
72
77
|
private readonly now: () => number;
|
|
73
78
|
|
|
74
79
|
/**
|
|
75
|
-
* Creates the registry
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* never reached their
|
|
80
|
+
* Creates the registry over `root` (the local blob driver's root) and
|
|
81
|
+
* prepares the staging directory `root/stagingPrefix`. Any file already
|
|
82
|
+
* present is an orphan from a previous process (the registry that knew
|
|
83
|
+
* about it died with that process), so the directory is emptied — this
|
|
84
|
+
* is also the crash-recovery story for uploads that never reached their
|
|
85
|
+
* push.
|
|
80
86
|
*/
|
|
81
87
|
constructor(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
88
|
+
private readonly root: string,
|
|
89
|
+
private readonly stagingPrefix: string,
|
|
90
|
+
private readonly ttlMs: number,
|
|
91
|
+
private readonly maxSize: number,
|
|
85
92
|
now: () => number = Date.now,
|
|
86
93
|
) {
|
|
87
|
-
|
|
88
|
-
fs.
|
|
89
|
-
this.stagingDir
|
|
90
|
-
this.ttlMs = ttlMs;
|
|
91
|
-
this.maxSize = maxSize;
|
|
94
|
+
this.stagingDir = path.join(root, stagingPrefix);
|
|
95
|
+
fs.rmSync(this.stagingDir, { recursive: true, force: true });
|
|
96
|
+
fs.mkdirSync(this.stagingDir, { recursive: true, mode: 0o700 });
|
|
92
97
|
this.now = now;
|
|
93
98
|
}
|
|
94
99
|
|
|
95
100
|
/**
|
|
96
|
-
* Reserves an upload slot for an artifact of declaredSize bytes
|
|
97
|
-
*
|
|
101
|
+
* Reserves an upload slot for an artifact of declaredSize bytes at the
|
|
102
|
+
* staging key; returns the TTL granted. The caller has already
|
|
98
103
|
* authorized the request and validated declaredSize against the skill
|
|
99
|
-
* size limit;
|
|
104
|
+
* size limit; the bound here is the registry's own invariant.
|
|
100
105
|
*/
|
|
101
|
-
|
|
106
|
+
reserve(key: string, declaredSize: number): { ttlMs: number } {
|
|
102
107
|
if (declaredSize <= 0 || declaredSize > this.maxSize) {
|
|
103
|
-
throw new Error(
|
|
108
|
+
throw new Error(
|
|
109
|
+
`declared size ${declaredSize} outside (0, ${this.maxSize}]`,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
this.stagePath(key);
|
|
113
|
+
if (this.slots.has(key)) {
|
|
114
|
+
throw new Error(`staging key ${key} is already reserved`);
|
|
104
115
|
}
|
|
105
|
-
const ref = REF_PREFIX + randomBytes(REF_BYTE_LEN).toString("hex");
|
|
106
116
|
this.sweep();
|
|
107
|
-
this.slots.set(
|
|
117
|
+
this.slots.set(key, {
|
|
108
118
|
declaredSize,
|
|
109
119
|
expiresAtMs: this.now() + this.ttlMs,
|
|
110
120
|
uploaded: false,
|
|
111
121
|
});
|
|
112
|
-
return {
|
|
122
|
+
return { ttlMs: this.ttlMs };
|
|
113
123
|
}
|
|
114
124
|
|
|
115
125
|
/**
|
|
116
126
|
* Streams an upload's body into the slot's staging file. The body must
|
|
117
|
-
* match the size declared at
|
|
127
|
+
* match the size declared at reservation exactly: a shorter body means a
|
|
118
128
|
* truncated transfer, a longer one means the client lied — both reject
|
|
119
129
|
* rather than staging bytes that would fail (or worse, surprise)
|
|
120
|
-
* validation later. The staged file only becomes
|
|
121
|
-
* resolves.
|
|
130
|
+
* validation later. The staged file only becomes readable through the
|
|
131
|
+
* driver once this resolves.
|
|
122
132
|
*/
|
|
123
|
-
async receive(
|
|
124
|
-
const slot = this.slots.get(
|
|
133
|
+
async receive(key: string, body: Readable): Promise<void> {
|
|
134
|
+
const slot = this.slots.get(key);
|
|
125
135
|
if (slot === undefined || this.now() > slot.expiresAtMs) {
|
|
126
136
|
throw new SlotUnknownError();
|
|
127
137
|
}
|
|
@@ -130,9 +140,16 @@ export class UploadSlots {
|
|
|
130
140
|
}
|
|
131
141
|
const declared = slot.declaredSize;
|
|
132
142
|
|
|
133
|
-
const filePath = this.stagePath(
|
|
143
|
+
const filePath = this.stagePath(key);
|
|
134
144
|
let written = 0;
|
|
135
145
|
try {
|
|
146
|
+
// The driver's delete(key) prunes directories it empties, so the
|
|
147
|
+
// staging directory may be gone between two uploads; recreate it
|
|
148
|
+
// rather than fail the second one.
|
|
149
|
+
await fs.promises.mkdir(path.dirname(filePath), {
|
|
150
|
+
recursive: true,
|
|
151
|
+
mode: 0o700,
|
|
152
|
+
});
|
|
136
153
|
// Consume at most declared+1 bytes: seeing the extra byte proves the
|
|
137
154
|
// body exceeds the declaration without buffering an unbounded stream
|
|
138
155
|
// (Go's io.LimitReader(declared+1) + written != declared check).
|
|
@@ -157,7 +174,7 @@ export class UploadSlots {
|
|
|
157
174
|
|
|
158
175
|
// Re-check after the write: the slot may have expired mid-upload
|
|
159
176
|
// (Go re-checks under the lock for the same reason).
|
|
160
|
-
const current = this.slots.get(
|
|
177
|
+
const current = this.slots.get(key);
|
|
161
178
|
if (current === undefined || this.now() > current.expiresAtMs) {
|
|
162
179
|
await fs.promises.rm(filePath, { force: true });
|
|
163
180
|
throw new SlotUnknownError();
|
|
@@ -166,66 +183,35 @@ export class UploadSlots {
|
|
|
166
183
|
}
|
|
167
184
|
|
|
168
185
|
/**
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
* artifact_upload_ref; whatever happens downstream (validation failure
|
|
172
|
-
* included), the slot is gone and the client must re-mint to retry.
|
|
173
|
-
*/
|
|
174
|
-
async consume(ref: string): Promise<Uint8Array> {
|
|
175
|
-
const slot = this.slots.get(ref);
|
|
176
|
-
if (slot === undefined || this.now() > slot.expiresAtMs) {
|
|
177
|
-
throw new SlotUnknownError();
|
|
178
|
-
}
|
|
179
|
-
if (!slot.uploaded) {
|
|
180
|
-
throw new SlotEmptyError();
|
|
181
|
-
}
|
|
182
|
-
this.slots.delete(ref);
|
|
183
|
-
|
|
184
|
-
const filePath = this.stagePath(ref);
|
|
185
|
-
try {
|
|
186
|
-
const data = await fs.promises.readFile(filePath);
|
|
187
|
-
return data;
|
|
188
|
-
} catch (error) {
|
|
189
|
-
throw new Error(
|
|
190
|
-
`failed to read staged artifact: ${error instanceof Error ? error.message : String(error)}`,
|
|
191
|
-
);
|
|
192
|
-
} finally {
|
|
193
|
-
await fs.promises.rm(filePath, { force: true });
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Drops expired slots and their staged files. Called from mint, which
|
|
199
|
-
* bounds the registry: it can hold at most the slots minted within one
|
|
186
|
+
* Drops expired slots and their staged files. Called from reserve, which
|
|
187
|
+
* bounds the registry: it can hold at most the slots reserved within one
|
|
200
188
|
* TTL window.
|
|
201
189
|
*/
|
|
202
190
|
private sweep(): void {
|
|
203
191
|
const nowMs = this.now();
|
|
204
|
-
for (const [
|
|
192
|
+
for (const [key, slot] of this.slots) {
|
|
205
193
|
if (nowMs > slot.expiresAtMs) {
|
|
206
|
-
this.slots.delete(
|
|
207
|
-
fs.rmSync(this.stagePath(
|
|
194
|
+
this.slots.delete(key);
|
|
195
|
+
fs.rmSync(this.stagePath(key), { force: true });
|
|
208
196
|
}
|
|
209
197
|
}
|
|
210
198
|
}
|
|
211
199
|
|
|
212
200
|
/**
|
|
213
|
-
*
|
|
214
|
-
* driver's
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
* with nothing to catch it.
|
|
218
|
-
*/
|
|
219
|
-
stagedFileName(ref: string): string {
|
|
220
|
-
return `${ref}.zip`;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Maps a reference to its staging file. refs are server-generated hex
|
|
225
|
-
* (never client-supplied paths), so simple joining is safe.
|
|
201
|
+
* Maps a staging key to its file: the key resolved under the driver's
|
|
202
|
+
* root, which is how the driver's download(key) will look for it. The
|
|
203
|
+
* key must carry the staging prefix and a plain hex file name — anything
|
|
204
|
+
* else is a caller bug, refused before it touches the filesystem.
|
|
226
205
|
*/
|
|
227
|
-
private stagePath(
|
|
228
|
-
|
|
206
|
+
private stagePath(key: string): string {
|
|
207
|
+
if (!key.startsWith(this.stagingPrefix)) {
|
|
208
|
+
throw new Error(`staging key ${key} is outside ${this.stagingPrefix}`);
|
|
209
|
+
}
|
|
210
|
+
const fileName = key.slice(this.stagingPrefix.length);
|
|
211
|
+
if (!STAGED_FILE_NAME.test(fileName)) {
|
|
212
|
+
throw new Error(`staging key ${key} does not name a staged archive`);
|
|
213
|
+
}
|
|
214
|
+
return path.join(this.root, key);
|
|
229
215
|
}
|
|
230
216
|
}
|
|
231
217
|
|
|
@@ -238,13 +224,16 @@ function limitBytes(
|
|
|
238
224
|
limit: number,
|
|
239
225
|
onBytes: (count: number) => void,
|
|
240
226
|
): (source: AsyncIterable<Buffer>) => AsyncIterable<Buffer> {
|
|
241
|
-
return async function* (
|
|
227
|
+
return async function* (
|
|
228
|
+
source: AsyncIterable<Buffer>,
|
|
229
|
+
): AsyncIterable<Buffer> {
|
|
242
230
|
let remaining = limit;
|
|
243
231
|
for await (const chunk of source) {
|
|
244
232
|
if (remaining <= 0) {
|
|
245
233
|
return;
|
|
246
234
|
}
|
|
247
|
-
const slice =
|
|
235
|
+
const slice =
|
|
236
|
+
chunk.length <= remaining ? chunk : chunk.subarray(0, remaining);
|
|
248
237
|
remaining -= slice.length;
|
|
249
238
|
onBytes(slice.length);
|
|
250
239
|
yield slice;
|