@stigmer/server 3.39.0 → 3.40.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 +10 -2
- package/dist/artifactstorage/artifact-storage.d.ts.map +1 -1
- package/dist/artifactstorage/artifact-storage.js +6 -3
- package/dist/artifactstorage/artifact-storage.js.map +1 -1
- package/dist/boot/artifact-lane.d.ts +55 -0
- package/dist/boot/artifact-lane.d.ts.map +1 -0
- package/dist/boot/artifact-lane.js +74 -0
- package/dist/boot/artifact-lane.js.map +1 -0
- package/dist/boot/compose.d.ts.map +1 -1
- package/dist/boot/compose.js +42 -19
- package/dist/boot/compose.js.map +1 -1
- package/dist/boot/config.d.ts +11 -6
- package/dist/boot/config.d.ts.map +1 -1
- package/dist/boot/config.js +10 -5
- package/dist/boot/config.js.map +1 -1
- package/dist/domain/agent/controller.d.ts +5 -2
- package/dist/domain/agent/controller.d.ts.map +1 -1
- package/dist/domain/agent/controller.js.map +1 -1
- package/dist/domain/agentexecution/controller.d.ts +18 -4
- package/dist/domain/agentexecution/controller.d.ts.map +1 -1
- package/dist/domain/agentexecution/controller.js +2 -2
- package/dist/domain/agentexecution/controller.js.map +1 -1
- package/dist/domain/agentexecution/create-steps.d.ts +29 -15
- package/dist/domain/agentexecution/create-steps.d.ts.map +1 -1
- package/dist/domain/agentexecution/create-steps.js +118 -51
- package/dist/domain/agentexecution/create-steps.js.map +1 -1
- package/dist/domain/agentinstance/controller.d.ts +8 -2
- package/dist/domain/agentinstance/controller.d.ts.map +1 -1
- package/dist/domain/agentinstance/controller.js.map +1 -1
- package/dist/domain/agentinstance/steps.d.ts +3 -1
- package/dist/domain/agentinstance/steps.d.ts.map +1 -1
- package/dist/domain/agentinstance/steps.js +12 -6
- package/dist/domain/agentinstance/steps.js.map +1 -1
- package/dist/domain/agentshare/controller.d.ts +9 -4
- package/dist/domain/agentshare/controller.d.ts.map +1 -1
- package/dist/domain/agentshare/controller.js +3 -3
- package/dist/domain/agentshare/controller.js.map +1 -1
- package/dist/domain/artifact/file-server.d.ts +2 -1
- package/dist/domain/artifact/file-server.d.ts.map +1 -1
- package/dist/domain/artifact/file-server.js +6 -3
- package/dist/domain/artifact/file-server.js.map +1 -1
- package/dist/domain/channelapp/controller.d.ts +8 -3
- package/dist/domain/channelapp/controller.d.ts.map +1 -1
- package/dist/domain/channelapp/controller.js.map +1 -1
- package/dist/domain/environment/controller.d.ts +8 -5
- package/dist/domain/environment/controller.d.ts.map +1 -1
- package/dist/domain/environment/controller.js +3 -3
- package/dist/domain/environment/controller.js.map +1 -1
- package/dist/domain/executioncontext/controller.d.ts +7 -4
- package/dist/domain/executioncontext/controller.d.ts.map +1 -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 +6 -2
- package/dist/domain/mcpserver/controller.js.map +1 -1
- package/dist/domain/memory/constants.d.ts +9 -0
- package/dist/domain/memory/constants.d.ts.map +1 -1
- package/dist/domain/memory/constants.js +9 -0
- package/dist/domain/memory/constants.js.map +1 -1
- package/dist/domain/memory/controller.d.ts +18 -6
- package/dist/domain/memory/controller.d.ts.map +1 -1
- package/dist/domain/memory/controller.js +18 -21
- package/dist/domain/memory/controller.js.map +1 -1
- package/dist/domain/memory/steps.d.ts +38 -10
- package/dist/domain/memory/steps.d.ts.map +1 -1
- package/dist/domain/memory/steps.js +87 -17
- package/dist/domain/memory/steps.js.map +1 -1
- package/dist/domain/organization/controller.d.ts +7 -4
- package/dist/domain/organization/controller.d.ts.map +1 -1
- package/dist/domain/organization/controller.js.map +1 -1
- package/dist/domain/plugin/controller.d.ts.map +1 -1
- package/dist/domain/plugin/controller.js +10 -5
- package/dist/domain/plugin/controller.js.map +1 -1
- package/dist/domain/session/controller.d.ts +7 -3
- package/dist/domain/session/controller.d.ts.map +1 -1
- package/dist/domain/session/controller.js.map +1 -1
- package/dist/domain/skill/controller.d.ts.map +1 -1
- package/dist/domain/skill/controller.js +15 -5
- package/dist/domain/skill/controller.js.map +1 -1
- package/dist/domain/workflow/controller.d.ts +6 -2
- package/dist/domain/workflow/controller.d.ts.map +1 -1
- package/dist/domain/workflow/controller.js +6 -3
- package/dist/domain/workflow/controller.js.map +1 -1
- package/dist/domain/workflowexecution/controller.d.ts +8 -4
- package/dist/domain/workflowexecution/controller.d.ts.map +1 -1
- package/dist/domain/workflowexecution/controller.js.map +1 -1
- package/dist/domain/workflowexecution/update-status.d.ts.map +1 -1
- package/dist/domain/workflowexecution/update-status.js +3 -2
- package/dist/domain/workflowexecution/update-status.js.map +1 -1
- package/dist/domain/workflowinstance/controller.d.ts +2 -2
- package/dist/domain/workflowinstance/controller.d.ts.map +1 -1
- package/dist/domain/workflowinstance/controller.js +10 -5
- package/dist/domain/workflowinstance/controller.js.map +1 -1
- package/dist/domain/workflowinstance/steps.d.ts +5 -3
- package/dist/domain/workflowinstance/steps.d.ts.map +1 -1
- package/dist/domain/workflowinstance/steps.js +14 -9
- package/dist/domain/workflowinstance/steps.js.map +1 -1
- package/dist/extensions/identity.d.ts +28 -14
- package/dist/extensions/identity.d.ts.map +1 -1
- package/dist/extensions/identity.js +38 -0
- package/dist/extensions/identity.js.map +1 -1
- package/dist/pipeline/steps/guard-memory-capture.d.ts.map +1 -1
- package/dist/pipeline/steps/guard-memory-capture.js +1 -13
- package/dist/pipeline/steps/guard-memory-capture.js.map +1 -1
- package/package.json +6 -6
- package/src/artifactstorage/__tests__/artifact-storage.test.ts +15 -1
- package/src/artifactstorage/artifact-storage.ts +12 -4
- package/src/boot/__tests__/artifact-lane.test.ts +71 -0
- package/src/boot/__tests__/compose.test.ts +73 -3
- package/src/boot/__tests__/config.test.ts +19 -0
- package/src/boot/artifact-lane.ts +96 -0
- package/src/boot/compose.ts +57 -21
- package/src/boot/config.ts +25 -15
- package/src/domain/agent/controller.ts +5 -2
- package/src/domain/agentexecution/__tests__/create-steps.test.ts +230 -2
- package/src/domain/agentexecution/controller.ts +32 -6
- package/src/domain/agentexecution/create-steps.ts +162 -62
- package/src/domain/agentinstance/__tests__/store-faults.test.ts +69 -18
- package/src/domain/agentinstance/controller.ts +8 -2
- package/src/domain/agentinstance/steps.ts +12 -6
- package/src/domain/agentshare/controller.ts +12 -7
- package/src/domain/artifact/file-server.ts +8 -4
- package/src/domain/channelapp/controller.ts +8 -3
- package/src/domain/environment/controller.ts +11 -8
- package/src/domain/executioncontext/controller.ts +7 -4
- package/src/domain/mcpserver/controller.ts +6 -2
- package/src/domain/memory/__tests__/capture-credential.test.ts +6 -6
- package/src/domain/memory/__tests__/person-consent-composed.test.ts +187 -0
- package/src/domain/memory/__tests__/person-consent.test.ts +274 -0
- package/src/domain/memory/constants.ts +11 -0
- package/src/domain/memory/controller.ts +36 -27
- package/src/domain/memory/steps.ts +112 -17
- package/src/domain/organization/controller.ts +7 -4
- package/src/domain/plugin/__tests__/plugin.test.ts +8 -0
- package/src/domain/plugin/__tests__/store-faults.test.ts +104 -0
- package/src/domain/plugin/controller.ts +10 -5
- package/src/domain/session/controller.ts +7 -3
- package/src/domain/skill/__tests__/skill.test.ts +4 -0
- package/src/domain/skill/__tests__/store-faults.test.ts +103 -0
- package/src/domain/skill/controller.ts +15 -5
- package/src/domain/workflow/__tests__/store-faults.test.ts +107 -0
- package/src/domain/workflow/controller.ts +12 -5
- package/src/domain/workflowexecution/controller.ts +8 -4
- package/src/domain/workflowexecution/update-status.ts +3 -2
- package/src/domain/workflowinstance/__tests__/store-faults.test.ts +172 -0
- package/src/domain/workflowinstance/controller.ts +12 -7
- package/src/domain/workflowinstance/steps.ts +14 -10
- package/src/extensions/__tests__/identity.test.ts +82 -0
- package/src/extensions/identity.ts +47 -0
- package/src/pipeline/__tests__/blind-not-found.test.ts +4 -20
- package/src/pipeline/steps/guard-memory-capture.ts +4 -19
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* the family test ../workflow/__tests__/workflow.test.ts (the mutual edge
|
|
9
9
|
* makes the two domains one testable unit).
|
|
10
10
|
*/
|
|
11
|
-
import { ConnectError } from "@connectrpc/connect";
|
|
11
|
+
import { Code, ConnectError } from "@connectrpc/connect";
|
|
12
12
|
import { WorkflowSchema } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/api_pb";
|
|
13
13
|
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
14
14
|
import { IamPermission } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
|
|
@@ -22,9 +22,11 @@ import { ResourceNotFoundError } from "../../store/interface.js";
|
|
|
22
22
|
export const PARENT_WORKFLOW_KEY = "parent_workflow";
|
|
23
23
|
/**
|
|
24
24
|
* LoadParentWorkflow — create.go: loads the workflow the instance
|
|
25
|
-
* materializes and stashes it for the same-org rule.
|
|
26
|
-
*
|
|
27
|
-
* (capital-W copy is the
|
|
25
|
+
* materializes and stashes it for the same-org rule. The workflow is read
|
|
26
|
+
* through the in-process query client, so a missing one arrives as a Connect
|
|
27
|
+
* NotFound and answers NotFound("Workflow", id) (the capital-W copy is the
|
|
28
|
+
* wire contract); any other failure is a fault answered as Internal, never a
|
|
29
|
+
* missing parent.
|
|
28
30
|
*/
|
|
29
31
|
export function newLoadParentWorkflowStep(loaderProvider, logger) {
|
|
30
32
|
return {
|
|
@@ -37,11 +39,14 @@ export function newLoadParentWorkflowStep(loaderProvider, logger) {
|
|
|
37
39
|
parent = await loaderProvider().get(workflowId);
|
|
38
40
|
}
|
|
39
41
|
catch (error) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
if (error instanceof ConnectError && error.code === Code.NotFound) {
|
|
43
|
+
logger.warn("Parent workflow not found", {
|
|
44
|
+
error: error.rawMessage,
|
|
45
|
+
workflowId,
|
|
46
|
+
});
|
|
47
|
+
throw notFoundError("Workflow", workflowId);
|
|
48
|
+
}
|
|
49
|
+
throw internalError(error, "failed to load parent workflow");
|
|
45
50
|
}
|
|
46
51
|
ctx.set(PARENT_WORKFLOW_KEY, parent);
|
|
47
52
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"steps.js","sourceRoot":"","sources":["../../../src/domain/workflowinstance/steps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"steps.js","sourceRoot":"","sources":["../../../src/domain/workflowinstance/steps.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,17 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Identity extension-point types — the verifier-chain contract of the
|
|
3
|
-
* convergence blueprint (20260826.02 blueprint/03 §4, DD-007), carried by
|
|
4
|
-
* the extension registry from O1 (20260826.09) and consumed by the
|
|
5
|
-
* verifier-chain chassis (O2, 20260827.01 — pipeline/interceptors/auth.ts).
|
|
6
|
-
*
|
|
7
|
-
* The shapes are transcribed from the ratified design, not invented here:
|
|
8
|
-
* a verifier either CLAIMS a token (verifying it fully, throwing on
|
|
9
|
-
* signature/expiry/audience failure) or PASSES (returns null → next
|
|
10
|
-
* verifier) — the TS rendering of the Java ProviderManager chain. The
|
|
11
|
-
* product is one typed value, CallerIdentity, threaded explicitly through
|
|
12
|
-
* HandlerContext.values (never ambient state — the every-dependency-
|
|
13
|
-
* explicit doctrine).
|
|
14
|
-
*/
|
|
15
1
|
/**
|
|
16
2
|
* The caller-class discriminant. OSS knows user / machine / runner plus
|
|
17
3
|
* the in-process `internal` class (O2 ruling Q4: the TS rendering of the
|
|
@@ -137,4 +123,32 @@ export declare function isPlatformPipelineCaller(caller: CallerIdentity): boolea
|
|
|
137
123
|
* instance's provenance) must key on this predicate and never on that one.
|
|
138
124
|
*/
|
|
139
125
|
export declare function isServerComposedRequest(caller: CallerIdentity): boolean;
|
|
126
|
+
/**
|
|
127
|
+
* Whether `caller` is a FIRST-PARTY HUMAN OPERATOR: a person at the
|
|
128
|
+
* console, the CLI or an SDK, speaking for themselves on the wire — the
|
|
129
|
+
* TS rendering of the Java RequestCallerIdentity.isFirstPartyHumanOperator
|
|
130
|
+
* (not machine, not impersonated, no token_type, not a PlatformClient user
|
|
131
|
+
* token). Stated once for every consumer that must tell the person from a
|
|
132
|
+
* lane acting near them: memory recall and the person's declared
|
|
133
|
+
* preferences on execution create (domain/agentexecution/create-steps.ts).
|
|
134
|
+
*
|
|
135
|
+
* An ALLOW-list, so a lane this module has never heard of is excluded by
|
|
136
|
+
* construction: the class must be `user` (every human verifier stamps it —
|
|
137
|
+
* OIDC, API key, the cloud's direct and federated logins; runners, the
|
|
138
|
+
* cloud's token lanes and the platform's own pipelines each carry their
|
|
139
|
+
* own class), the request must come from the wire (the open-source
|
|
140
|
+
* schedule fire acts as its creator with class `user` but rides the
|
|
141
|
+
* in-process transport, which is exactly what `isServerComposedRequest`
|
|
142
|
+
* reads), and the bearer must not be a PlatformClient user token (a
|
|
143
|
+
* person, but spoken for by a third-party client — DD-002 D4 as amended).
|
|
144
|
+
*/
|
|
145
|
+
export declare function isFirstPartyHumanOperator(caller: CallerIdentity): boolean;
|
|
146
|
+
/**
|
|
147
|
+
* Whether the bearer is a platform token naming a PlatformClient. Anything
|
|
148
|
+
* that is not a platform token (no token, an API key, another issuer's
|
|
149
|
+
* JWT) is not one, whatever claims it carries (stigmer#1312). Read from
|
|
150
|
+
* the raw token, never from `platformClientId`, which is audit provenance
|
|
151
|
+
* set by one verifier and not a guard vocabulary (see its field doc).
|
|
152
|
+
*/
|
|
153
|
+
export declare function carriesPlatformClientClaim(rawToken: string): boolean;
|
|
140
154
|
//# sourceMappingURL=identity.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../src/extensions/identity.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../src/extensions/identity.ts"],"names":[],"mappings":"AAoBA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,WAAW,GACnB,MAAM,GACN,SAAS,GACT,QAAQ,GACR,UAAU,GACV,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAElB;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,YAAY,CAAC;AAE/C;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,uEAAuE;IACvE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC,wEAAwE;IACxE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,mEAAmE;IACnE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC;CAC9B;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,uEAAuE;IACvE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;CACvD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAExE;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAEvE;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAMzE;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAMpE"}
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
* HandlerContext.values (never ambient state — the every-dependency-
|
|
13
13
|
* explicit doctrine).
|
|
14
14
|
*/
|
|
15
|
+
import { USER_TOKEN_CLAIMS } from "../domain/platformclient/constants.js";
|
|
16
|
+
import { decodeVerifiedPlatformTokenPayload, stringClaim, } from "../platformtoken/envelope.js";
|
|
15
17
|
/**
|
|
16
18
|
* Whether `caller` is one of the platform's own pipelines — the admission
|
|
17
19
|
* rule of the RPCs a person must never reach directly, stated ONCE
|
|
@@ -49,4 +51,40 @@ export function isPlatformPipelineCaller(caller) {
|
|
|
49
51
|
export function isServerComposedRequest(caller) {
|
|
50
52
|
return caller.callerClass === "internal" || caller.origin === "in-process";
|
|
51
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Whether `caller` is a FIRST-PARTY HUMAN OPERATOR: a person at the
|
|
56
|
+
* console, the CLI or an SDK, speaking for themselves on the wire — the
|
|
57
|
+
* TS rendering of the Java RequestCallerIdentity.isFirstPartyHumanOperator
|
|
58
|
+
* (not machine, not impersonated, no token_type, not a PlatformClient user
|
|
59
|
+
* token). Stated once for every consumer that must tell the person from a
|
|
60
|
+
* lane acting near them: memory recall and the person's declared
|
|
61
|
+
* preferences on execution create (domain/agentexecution/create-steps.ts).
|
|
62
|
+
*
|
|
63
|
+
* An ALLOW-list, so a lane this module has never heard of is excluded by
|
|
64
|
+
* construction: the class must be `user` (every human verifier stamps it —
|
|
65
|
+
* OIDC, API key, the cloud's direct and federated logins; runners, the
|
|
66
|
+
* cloud's token lanes and the platform's own pipelines each carry their
|
|
67
|
+
* own class), the request must come from the wire (the open-source
|
|
68
|
+
* schedule fire acts as its creator with class `user` but rides the
|
|
69
|
+
* in-process transport, which is exactly what `isServerComposedRequest`
|
|
70
|
+
* reads), and the bearer must not be a PlatformClient user token (a
|
|
71
|
+
* person, but spoken for by a third-party client — DD-002 D4 as amended).
|
|
72
|
+
*/
|
|
73
|
+
export function isFirstPartyHumanOperator(caller) {
|
|
74
|
+
return (caller.callerClass === "user" &&
|
|
75
|
+
!isServerComposedRequest(caller) &&
|
|
76
|
+
!carriesPlatformClientClaim(caller.rawToken));
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Whether the bearer is a platform token naming a PlatformClient. Anything
|
|
80
|
+
* that is not a platform token (no token, an API key, another issuer's
|
|
81
|
+
* JWT) is not one, whatever claims it carries (stigmer#1312). Read from
|
|
82
|
+
* the raw token, never from `platformClientId`, which is audit provenance
|
|
83
|
+
* set by one verifier and not a guard vocabulary (see its field doc).
|
|
84
|
+
*/
|
|
85
|
+
export function carriesPlatformClientClaim(rawToken) {
|
|
86
|
+
const payload = decodeVerifiedPlatformTokenPayload(rawToken);
|
|
87
|
+
return (payload !== undefined &&
|
|
88
|
+
stringClaim(payload, USER_TOKEN_CLAIMS.platformClientId) !== undefined);
|
|
89
|
+
}
|
|
52
90
|
//# sourceMappingURL=identity.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../../src/extensions/identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;
|
|
1
|
+
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../../src/extensions/identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAC;AAC1E,OAAO,EACL,kCAAkC,EAClC,WAAW,GACZ,MAAM,8BAA8B,CAAC;AAuGtC;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,wBAAwB,CAAC,MAAsB;IAC7D,OAAO,MAAM,CAAC,WAAW,KAAK,SAAS,IAAI,uBAAuB,CAAC,MAAM,CAAC,CAAC;AAC7E,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,uBAAuB,CAAC,MAAsB;IAC5D,OAAO,MAAM,CAAC,WAAW,KAAK,UAAU,IAAI,MAAM,CAAC,MAAM,KAAK,YAAY,CAAC;AAC7E,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,yBAAyB,CAAC,MAAsB;IAC9D,OAAO,CACL,MAAM,CAAC,WAAW,KAAK,MAAM;QAC7B,CAAC,uBAAuB,CAAC,MAAM,CAAC;QAChC,CAAC,0BAA0B,CAAC,MAAM,CAAC,QAAQ,CAAC,CAC7C,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,0BAA0B,CAAC,QAAgB;IACzD,MAAM,OAAO,GAAG,kCAAkC,CAAC,QAAQ,CAAC,CAAC;IAC7D,OAAO,CACL,OAAO,KAAK,SAAS;QACrB,WAAW,CAAC,OAAO,EAAE,iBAAiB,CAAC,gBAAgB,CAAC,KAAK,SAAS,CACvE,CAAC;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guard-memory-capture.d.ts","sourceRoot":"","sources":["../../../src/pipeline/steps/guard-memory-capture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"guard-memory-capture.d.ts","sourceRoot":"","sources":["../../../src/pipeline/steps/guard-memory-capture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAOtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,gDAAgD,CAAC;AAG/F,wEAAwE;AACxE,eAAO,MAAM,6BAA6B,iEACsB,CAAC;AAEjE;;;;;GAKG;AACH,eAAO,MAAM,6BAA6B,4BAA4B,CAAC;AAEvE,wBAAgB,yBAAyB,CAAC,IAAI,SAAS,WAAW,EAChE,QAAQ,CAAC,EAAE,wBAAwB,GAClC,YAAY,CAAC,IAAI,CAAC,CAsDpB;AAED,4EAA4E;AAC5E,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,wBAAwB,EAAE,MAAM,CAAC;IAC1C,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,SAAS,WAAW,EAChE,GAAG,EAAE,cAAc,CAAC,IAAI,CAAC,GACxB,uBAAuB,GAAG,SAAS,CAWrC"}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { Code, ConnectError } from "@connectrpc/connect";
|
|
2
|
-
import {
|
|
3
|
-
import { isServerComposedRequest } from "../../extensions/identity.js";
|
|
4
|
-
import { decodeVerifiedPlatformTokenPayload, stringClaim, } from "../../platformtoken/envelope.js";
|
|
2
|
+
import { carriesPlatformClientClaim, isServerComposedRequest, } from "../../extensions/identity.js";
|
|
5
3
|
import { metadataOf } from "./shapes.js";
|
|
6
4
|
/** The Java MemoryPolicy.MEMORY_CAPTURE_CALLER_MESSAGE, byte-pinned. */
|
|
7
5
|
export const MEMORY_CAPTURE_CALLER_MESSAGE = "memory can only be captured for a first-party human operator";
|
|
@@ -71,14 +69,4 @@ export function memoryCaptureCredentialOf(ctx) {
|
|
|
71
69
|
}
|
|
72
70
|
return undefined;
|
|
73
71
|
}
|
|
74
|
-
/**
|
|
75
|
-
* Whether the bearer is a platform token naming a PlatformClient. Anything
|
|
76
|
-
* that is not a platform token (no token, an API key, another issuer's
|
|
77
|
-
* JWT) is not one, whatever claims it carries.
|
|
78
|
-
*/
|
|
79
|
-
function carriesPlatformClientClaim(rawToken) {
|
|
80
|
-
const payload = decodeVerifiedPlatformTokenPayload(rawToken);
|
|
81
|
-
return (payload !== undefined &&
|
|
82
|
-
stringClaim(payload, USER_TOKEN_CLAIMS.platformClientId) !== undefined);
|
|
83
|
-
}
|
|
84
72
|
//# sourceMappingURL=guard-memory-capture.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guard-memory-capture.js","sourceRoot":"","sources":["../../../src/pipeline/steps/guard-memory-capture.ts"],"names":[],"mappings":"AA0CA,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,
|
|
1
|
+
{"version":3,"file":"guard-memory-capture.js","sourceRoot":"","sources":["../../../src/pipeline/steps/guard-memory-capture.ts"],"names":[],"mappings":"AA0CA,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,EACL,0BAA0B,EAC1B,uBAAuB,GACxB,MAAM,8BAA8B,CAAC;AAItC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,wEAAwE;AACxE,MAAM,CAAC,MAAM,6BAA6B,GACxC,8DAA8D,CAAC;AAEjE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAG,yBAAyB,CAAC;AAEvE,MAAM,UAAU,yBAAyB,CACvC,QAAmC;IAEnC,OAAO;QACL,IAAI,EAAE,oBAAoB;QAC1B,KAAK,CAAC,OAAO,CAAC,GAAyB;YACrC,MAAM,MAAM,GAAG,GAAG,CAAC,cAAc,CAAC;YAClC,IAAI,uBAAuB,CAAC,MAAM,CAAC,EAAE,CAAC;gBACpC,6DAA6D;gBAC7D,+DAA+D;gBAC/D,OAAO;YACT,CAAC;YACD,MAAM,MAAM,GAAG,QAAQ,EAAE,sBAAsB,CAAC;YAChD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,gEAAgE;gBAChE,8DAA8D;gBAC9D,8DAA8D;gBAC9D,gEAAgE;gBAChE,2DAA2D;gBAC3D,4DAA4D;gBAC5D,uDAAuD;gBACvD,MAAM,GAAG,GAAG,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,GAAG,IAAI,EAAE,CAAC;gBAChD,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC;gBAC1D,QAAQ,QAAQ,CAAC,OAAO,EAAE,CAAC;oBACzB,KAAK,OAAO;wBACV,GAAG,CAAC,GAAG,CAAC,6BAA6B,EAAE;4BACrC,wBAAwB,EAAE,QAAQ,CAAC,wBAAwB;4BAC3D,eAAe,EAAE,QAAQ,CAAC,eAAe;yBAC1C,CAAC,CAAC;wBACH,OAAO;oBACT,KAAK,QAAQ;wBACX,MAAM,IAAI,YAAY,CACpB,6BAA6B,EAC7B,IAAI,CAAC,gBAAgB,CACtB,CAAC;oBACJ,KAAK,YAAY;wBACf,MAAM;oBACR,OAAO,CAAC,CAAC,CAAC;wBACR,MAAM,UAAU,GAAU,QAAQ,CAAC;wBACnC,MAAM,IAAI,KAAK,CACb,mCAAmC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,EAAE,CAChE,CAAC;oBACJ,CAAC;gBACH,CAAC;YACH,CAAC;YACD,IACE,MAAM,CAAC,WAAW,KAAK,SAAS;gBAChC,0BAA0B,CAAC,MAAM,CAAC,QAAQ,CAAC,EAC3C,CAAC;gBACD,MAAM,IAAI,YAAY,CACpB,6BAA6B,EAC7B,IAAI,CAAC,gBAAgB,CACtB,CAAC;YACJ,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAQD;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CACvC,GAAyB;IAEzB,MAAM,OAAO,GAAG,GAAG,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;IACvD,IACE,OAAO,OAAO,KAAK,QAAQ;QAC3B,OAAO,KAAK,IAAI;QAChB,0BAA0B,IAAI,OAAO;QACrC,iBAAiB,IAAI,OAAO,EAC5B,CAAC;QACD,OAAO,OAAkC,CAAC;IAC5C,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stigmer/server",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.40.0",
|
|
4
4
|
"description": "The Stigmer OSS control plane as a composable library: composeServer, the extension registry and the driver seams a composition builds on. The self-contained deployable of the same server is @stigmer/server-slim.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -63,11 +63,11 @@
|
|
|
63
63
|
"@connectrpc/connect-node": "^2.0.0",
|
|
64
64
|
"@kubernetes/client-node": "2.0.0",
|
|
65
65
|
"@opentelemetry/api": "^1.9.0",
|
|
66
|
-
"@stigmer/outbound": "3.
|
|
67
|
-
"@stigmer/plugin-package": "3.
|
|
68
|
-
"@stigmer/protos": "3.
|
|
69
|
-
"@stigmer/temporal-codecs": "3.
|
|
70
|
-
"@stigmer/zip-structure": "3.
|
|
66
|
+
"@stigmer/outbound": "3.40.0",
|
|
67
|
+
"@stigmer/plugin-package": "3.40.0",
|
|
68
|
+
"@stigmer/protos": "3.40.0",
|
|
69
|
+
"@stigmer/temporal-codecs": "3.40.0",
|
|
70
|
+
"@stigmer/zip-structure": "3.40.0",
|
|
71
71
|
"@temporalio/activity": "1.16.2",
|
|
72
72
|
"@temporalio/client": "1.16.2",
|
|
73
73
|
"@temporalio/common": "1.16.2",
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Ports storage/local_storage_test.go + disposition_test.go case-for-case:
|
|
3
3
|
* the containment guard (every escaping key refused on every operation,
|
|
4
4
|
* contained dot-segment keys allowed), the #285 no-implicit-segment
|
|
5
|
-
* layout contract, the local signed-URL shape,
|
|
5
|
+
* layout contract, the local signed-URL shape (a fixed serve URL, or one
|
|
6
|
+
* resolved per mint once the file server binds — stigmer#1089), and the
|
|
6
7
|
* Content-Disposition builder. Adds the factory's r2/unknown refusals
|
|
7
8
|
* (Go's are boot asserted; here they're the disclosed deferral), and the
|
|
8
9
|
* O5 widened surface: size, the typed not-found, presigned-PUT over the
|
|
@@ -154,6 +155,19 @@ describe("LocalArtifactStorage", () => {
|
|
|
154
155
|
expect(exotic).toBe(`http://localhost:7235/${key}?download=a~b%2Ac.md`);
|
|
155
156
|
});
|
|
156
157
|
|
|
158
|
+
it("signed URL: a resolved serve URL is read at every mint, not at construction", async () => {
|
|
159
|
+
let origin = "";
|
|
160
|
+
const resolved = new LocalArtifactStorage(base, () => origin);
|
|
161
|
+
const key = "artifacts/aex_1/plan.md";
|
|
162
|
+
await expect(resolved.getSignedUrl(key, 3600_000, "")).rejects.toThrow(
|
|
163
|
+
"local serve URL not configured",
|
|
164
|
+
);
|
|
165
|
+
origin = "http://localhost:51234";
|
|
166
|
+
expect(await resolved.getSignedUrl(key, 3600_000, "")).toBe(
|
|
167
|
+
`http://localhost:51234/${key}`,
|
|
168
|
+
);
|
|
169
|
+
});
|
|
170
|
+
|
|
157
171
|
it("delete removes the artifact and prunes empty parents, never the root", async () => {
|
|
158
172
|
const key = "attachments/01DEL/file.txt";
|
|
159
173
|
await storage.upload(key, Buffer.from("x"), "text/plain");
|
|
@@ -157,7 +157,12 @@ export interface ArtifactStorageConfig {
|
|
|
157
157
|
/** "local" or "r2" ("" defaults to local — Go NewArtifactStorage). */
|
|
158
158
|
readonly type: string;
|
|
159
159
|
readonly localBasePath: string;
|
|
160
|
-
|
|
160
|
+
/**
|
|
161
|
+
* The download URL base: a fixed string, or a resolver read per mint when
|
|
162
|
+
* the base is only known once the artifact file server binds
|
|
163
|
+
* (boot/artifact-lane.ts). "" = not configured.
|
|
164
|
+
*/
|
|
165
|
+
readonly localServeUrl: string | (() => string);
|
|
161
166
|
/** Cloudflare R2 (S3-compatible) settings — required when type is "r2". */
|
|
162
167
|
readonly r2Bucket: string;
|
|
163
168
|
readonly r2Endpoint: string;
|
|
@@ -214,7 +219,8 @@ export function newArtifactStorage(
|
|
|
214
219
|
export class LocalArtifactStorage implements ArtifactStorage {
|
|
215
220
|
constructor(
|
|
216
221
|
private readonly basePath: string,
|
|
217
|
-
|
|
222
|
+
/** Fixed, or resolved per mint (ArtifactStorageConfig.localServeUrl). */
|
|
223
|
+
private readonly serveUrl: string | (() => string),
|
|
218
224
|
/**
|
|
219
225
|
* The staged-upload mechanism backing presignPut — optional because
|
|
220
226
|
* only instances whose root the lane stages into can serve it (the
|
|
@@ -287,10 +293,12 @@ export class LocalArtifactStorage implements ArtifactStorage {
|
|
|
287
293
|
_expiresInMs: number,
|
|
288
294
|
downloadFilename: string,
|
|
289
295
|
): Promise<string> {
|
|
290
|
-
|
|
296
|
+
const serveUrl =
|
|
297
|
+
typeof this.serveUrl === "function" ? this.serveUrl() : this.serveUrl;
|
|
298
|
+
if (serveUrl === "") {
|
|
291
299
|
throw new Error("local serve URL not configured");
|
|
292
300
|
}
|
|
293
|
-
let url = `${
|
|
301
|
+
let url = `${serveUrl}/${key}`;
|
|
294
302
|
if (downloadFilename !== "") {
|
|
295
303
|
url += `?${LOCAL_DOWNLOAD_QUERY_PARAM}=${goQueryEscape(downloadFilename)}`;
|
|
296
304
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pins the artifact lane's placement rules: a configured port wins (an
|
|
3
|
+
* explicit 0 included), an ephemeral unified port makes the lane ephemeral
|
|
4
|
+
* rather than deriving port 1 (stigmer#1089), and otherwise the lane sits at
|
|
5
|
+
* the unified port + 1; a configured serve URL passes through, and the
|
|
6
|
+
* derived one follows the port the lane bound and refuses before the bind.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, expect, it } from "vitest";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
resolveArtifactLanePort,
|
|
12
|
+
resolveArtifactServeUrl,
|
|
13
|
+
} from "../artifact-lane.js";
|
|
14
|
+
|
|
15
|
+
describe("resolveArtifactLanePort", () => {
|
|
16
|
+
it("keeps a configured port whatever the unified port is", () => {
|
|
17
|
+
expect(
|
|
18
|
+
resolveArtifactLanePort({ configured: 9000, unifiedPort: 7234 }),
|
|
19
|
+
).toBe(9000);
|
|
20
|
+
expect(resolveArtifactLanePort({ configured: 9000, unifiedPort: 0 })).toBe(
|
|
21
|
+
9000,
|
|
22
|
+
);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("keeps a configured 0 as an explicit ephemeral bind", () => {
|
|
26
|
+
expect(resolveArtifactLanePort({ configured: 0, unifiedPort: 7234 })).toBe(
|
|
27
|
+
0,
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("binds ephemeral when the unified port is not known until listen, never port 1", () => {
|
|
32
|
+
expect(
|
|
33
|
+
resolveArtifactLanePort({ configured: undefined, unifiedPort: 0 }),
|
|
34
|
+
).toBe(0);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("takes the unified port + 1 when nothing is configured", () => {
|
|
38
|
+
expect(
|
|
39
|
+
resolveArtifactLanePort({ configured: undefined, unifiedPort: 7234 }),
|
|
40
|
+
).toBe(7235);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe("resolveArtifactServeUrl", () => {
|
|
45
|
+
it("passes a configured serve URL through as given", () => {
|
|
46
|
+
expect(
|
|
47
|
+
resolveArtifactServeUrl(
|
|
48
|
+
"https://artifacts.stigmer.test",
|
|
49
|
+
() => undefined,
|
|
50
|
+
),
|
|
51
|
+
).toBe("https://artifacts.stigmer.test");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("derives the lane's localhost origin from the port it bound", () => {
|
|
55
|
+
let bound: number | undefined;
|
|
56
|
+
const serveUrl = resolveArtifactServeUrl("", () => bound);
|
|
57
|
+
if (typeof serveUrl === "string")
|
|
58
|
+
throw new Error("expected a resolver for an unset serve URL");
|
|
59
|
+
bound = 51234;
|
|
60
|
+
expect(serveUrl()).toBe("http://localhost:51234");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("refuses to mint a URL before the lane bound", () => {
|
|
64
|
+
const serveUrl = resolveArtifactServeUrl("", () => undefined);
|
|
65
|
+
if (typeof serveUrl === "string")
|
|
66
|
+
throw new Error("expected a resolver for an unset serve URL");
|
|
67
|
+
expect(() => serveUrl()).toThrow(
|
|
68
|
+
"before the artifact file server bound its port",
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -7,7 +7,12 @@
|
|
|
7
7
|
* - start() flips SERVING BEFORE the port binds, so the first probe that
|
|
8
8
|
* reaches the port already sees a serving server;
|
|
9
9
|
* - shutdown flips NOT_SERVING FIRST, then drains — and the port stops
|
|
10
|
-
* answering
|
|
10
|
+
* answering;
|
|
11
|
+
* - the artifact download lane binds before SERVING, so a lane that
|
|
12
|
+
* cannot bind fails start() with the address and the setting named and
|
|
13
|
+
* the server never reports itself serving (stigmer#1089), and a server
|
|
14
|
+
* on an ephemeral port gets an ephemeral lane whose minted download
|
|
15
|
+
* URLs reach it.
|
|
11
16
|
*/
|
|
12
17
|
import { createClient } from "@connectrpc/connect";
|
|
13
18
|
import { createGrpcTransport } from "@connectrpc/connect-node";
|
|
@@ -15,7 +20,13 @@ import {
|
|
|
15
20
|
Health,
|
|
16
21
|
HealthCheckResponse_ServingStatus as ServingStatus,
|
|
17
22
|
} from "@stigmer/protos/grpc/health/v1/health_pb";
|
|
18
|
-
import {
|
|
23
|
+
import { ArtifactCommandController } from "@stigmer/protos/ai/stigmer/agentic/artifact/v1/command_pb";
|
|
24
|
+
import { ArtifactQueryController } from "@stigmer/protos/ai/stigmer/agentic/artifact/v1/query_pb";
|
|
25
|
+
import {
|
|
26
|
+
createServer as netCreateServer,
|
|
27
|
+
connect as netConnect,
|
|
28
|
+
} from "node:net";
|
|
29
|
+
import type { AddressInfo } from "node:net";
|
|
19
30
|
import { describe, expect, it } from "vitest";
|
|
20
31
|
|
|
21
32
|
import { mkdtempSync } from "node:fs";
|
|
@@ -32,11 +43,12 @@ const silentLogger = createLogger({
|
|
|
32
43
|
write: () => {},
|
|
33
44
|
});
|
|
34
45
|
|
|
35
|
-
function compose() {
|
|
46
|
+
function compose(env: NodeJS.ProcessEnv = {}) {
|
|
36
47
|
// Each composed server gets a throwaway database — the storage stage
|
|
37
48
|
// opens DB_PATH for real (never the developer's ~/.stigmer).
|
|
38
49
|
const testDir = mkdtempSync(path.join(tmpdir(), "compose-test-"));
|
|
39
50
|
const config = loadConfig({
|
|
51
|
+
...env,
|
|
40
52
|
STIGMER_MODEL_REGISTRY_REFRESH: "off",
|
|
41
53
|
// No engine behind composed tests: 127.0.0.1:1 is deterministically
|
|
42
54
|
// closed, so boots fail the non-fatal connect fast and can never touch
|
|
@@ -95,3 +107,61 @@ describe("composition-root boot ordering", () => {
|
|
|
95
107
|
expect(refused, "the drained port must refuse new connections").toBe(true);
|
|
96
108
|
});
|
|
97
109
|
});
|
|
110
|
+
|
|
111
|
+
describe("the artifact download lane", () => {
|
|
112
|
+
it("fails start() when the lane cannot bind, naming the address and the setting, and never serves", async () => {
|
|
113
|
+
const holder = netCreateServer();
|
|
114
|
+
await new Promise<void>((resolve) =>
|
|
115
|
+
holder.listen(0, "127.0.0.1", resolve),
|
|
116
|
+
);
|
|
117
|
+
const held = (holder.address() as AddressInfo).port;
|
|
118
|
+
try {
|
|
119
|
+
const server = await compose({ ARTIFACT_HTTP_PORT: String(held) });
|
|
120
|
+
try {
|
|
121
|
+
await expect(server.start()).rejects.toThrow(
|
|
122
|
+
`the artifact file server could not bind '127.0.0.1:${held}' (EADDRINUSE): ARTIFACT_HTTP_PORT=${held} cannot be bound; set it to a free port`,
|
|
123
|
+
);
|
|
124
|
+
// SERVING is set before the unified port binds, so a server that is
|
|
125
|
+
// still NOT_SERVING never opened it.
|
|
126
|
+
expect(server.healthState.status("")).toBe(ServingStatus.NOT_SERVING);
|
|
127
|
+
} finally {
|
|
128
|
+
await server.shutdown();
|
|
129
|
+
}
|
|
130
|
+
} finally {
|
|
131
|
+
await new Promise<void>((resolve) => holder.close(() => resolve()));
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("binds an ephemeral lane beside an ephemeral unified port, and a minted download URL reaches it", async () => {
|
|
136
|
+
const server = await compose();
|
|
137
|
+
const port = await server.start();
|
|
138
|
+
try {
|
|
139
|
+
const transport = createGrpcTransport({
|
|
140
|
+
baseUrl: `http://127.0.0.1:${port}`,
|
|
141
|
+
});
|
|
142
|
+
const command = createClient(ArtifactCommandController, transport);
|
|
143
|
+
const query = createClient(ArtifactQueryController, transport);
|
|
144
|
+
const content = new TextEncoder().encode("ephemeral lane body\n");
|
|
145
|
+
const created = await command.create({
|
|
146
|
+
spec: {
|
|
147
|
+
displayName: "ephemeral-lane.txt",
|
|
148
|
+
contentType: "text/plain",
|
|
149
|
+
source: { agentExecutionId: "aexec_01ephemerallane" },
|
|
150
|
+
},
|
|
151
|
+
content,
|
|
152
|
+
});
|
|
153
|
+
const download = await query.getDownloadUrl({
|
|
154
|
+
value: created.metadata!.id,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// An OS-assigned port, never the privileged port 1 that +1 derived
|
|
158
|
+
// from 0; the bytes coming back prove it is THIS server's lane.
|
|
159
|
+
expect(Number(new URL(download.url).port)).toBeGreaterThan(1024);
|
|
160
|
+
const served = await fetch(download.url);
|
|
161
|
+
expect(served.status).toBe(200);
|
|
162
|
+
expect(new Uint8Array(await served.arrayBuffer())).toEqual(content);
|
|
163
|
+
} finally {
|
|
164
|
+
await server.shutdown();
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
});
|
|
@@ -71,6 +71,25 @@ describe("loadConfig", () => {
|
|
|
71
71
|
);
|
|
72
72
|
});
|
|
73
73
|
|
|
74
|
+
// stigmer#1089: the artifact lane's port and serve URL follow the unified
|
|
75
|
+
// port the composition binds (boot/artifact-lane.ts), so the loader keeps
|
|
76
|
+
// only what the operator set and derives nothing.
|
|
77
|
+
it("leaves an unset or malformed ARTIFACT_HTTP_PORT unset, for the lane to derive", () => {
|
|
78
|
+
expect(loadConfig({}).artifactHttpPort).toBeUndefined();
|
|
79
|
+
expect(loadConfig({ GRPC_PORT: "0" }).artifactHttpPort).toBeUndefined();
|
|
80
|
+
expect(loadConfig({ ARTIFACT_HTTP_PORT: "7300x" }).artifactHttpPort).toBeUndefined();
|
|
81
|
+
expect(loadConfig({ ARTIFACT_HTTP_PORT: "7300" }).artifactHttpPort).toBe(7300);
|
|
82
|
+
expect(loadConfig({ ARTIFACT_HTTP_PORT: "0" }).artifactHttpPort).toBe(0);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("leaves an unset ARTIFACT_LOCAL_SERVE_URL empty, for the bound lane to answer", () => {
|
|
86
|
+
expect(loadConfig({}).artifactLocalServeUrl).toBe("");
|
|
87
|
+
expect(
|
|
88
|
+
loadConfig({ ARTIFACT_LOCAL_SERVE_URL: "https://artifacts.stigmer.test" })
|
|
89
|
+
.artifactLocalServeUrl,
|
|
90
|
+
).toBe("https://artifacts.stigmer.test");
|
|
91
|
+
});
|
|
92
|
+
|
|
74
93
|
// 20260913.02 (sp.console-login, Q-CL-1): the console's public PKCE client
|
|
75
94
|
// id is lenient on purpose — a CLI-only self-host that set the issuer
|
|
76
95
|
// before this knob existed must keep booting on upgrade, and the served
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the artifact file server listens, and the URL local artifact
|
|
3
|
+
* storage mints downloads under, on a server that was not told.
|
|
4
|
+
*
|
|
5
|
+
* The artifact download lane is a second loopback listener beside the
|
|
6
|
+
* unified port (domain/artifact/file-server.ts), bound only when artifact
|
|
7
|
+
* storage is local. `ARTIFACT_HTTP_PORT` and `ARTIFACT_LOCAL_SERVE_URL`
|
|
8
|
+
* place it explicitly; unset, both follow the unified port, and this module
|
|
9
|
+
* is the one place that says how.
|
|
10
|
+
*
|
|
11
|
+
* The port rules, in order: a configured port always wins (0 included — an
|
|
12
|
+
* explicit ephemeral bind); a unified port of 0 (`GRPC_PORT=0`, and the
|
|
13
|
+
* `portOverride: 0` test seam) is not known until listen, so the lane binds
|
|
14
|
+
* ephemeral too; otherwise the lane takes the unified port + 1, the default
|
|
15
|
+
* every install has always had. Deriving +1 from an ephemeral 0 gave port 1,
|
|
16
|
+
* a privileged port, and a server that reported healthy with a dead download
|
|
17
|
+
* lane (stigmer#1089). The caller passes the port the unified listener will
|
|
18
|
+
* bind (`portOverride ?? grpcPort`), the input the OAuth redirect derivation
|
|
19
|
+
* reads (boot/oauth-redirect-uri.ts).
|
|
20
|
+
*
|
|
21
|
+
* The serve URL: a configured value always wins; unset, it is the lane's own
|
|
22
|
+
* origin on localhost, read from the port the lane actually bound. The
|
|
23
|
+
* composition binds the lane before the unified port opens, so no request can
|
|
24
|
+
* mint a URL before that port exists; a read before the bind is a
|
|
25
|
+
* construction-order fault and throws rather than minting a URL for a port
|
|
26
|
+
* nothing listens on.
|
|
27
|
+
*
|
|
28
|
+
* A lane that cannot bind fails the boot, beside the storage probe it
|
|
29
|
+
* belongs with; artifactLaneBindError is the copy the operator reads, naming
|
|
30
|
+
* the address, the cause and the setting that moves the lane.
|
|
31
|
+
*
|
|
32
|
+
* Proven by __tests__/artifact-lane.test.ts and the lane cases of
|
|
33
|
+
* __tests__/compose.test.ts.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
export interface ArtifactLanePortInputs {
|
|
37
|
+
/** `ARTIFACT_HTTP_PORT` as loaded; undefined when unset. */
|
|
38
|
+
readonly configured: number | undefined;
|
|
39
|
+
/** The port the unified listener binds; 0 means "not known until listen". */
|
|
40
|
+
readonly unifiedPort: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The port the artifact file server binds; 0 asks the OS for an ephemeral one. */
|
|
44
|
+
export function resolveArtifactLanePort(
|
|
45
|
+
inputs: ArtifactLanePortInputs,
|
|
46
|
+
): number {
|
|
47
|
+
if (inputs.configured !== undefined) return inputs.configured;
|
|
48
|
+
if (inputs.unifiedPort === 0) return 0;
|
|
49
|
+
return inputs.unifiedPort + 1;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The serve URL local artifact storage mints download URLs under: the
|
|
54
|
+
* configured value as given, or a resolver over the lane's bound port.
|
|
55
|
+
*/
|
|
56
|
+
export function resolveArtifactServeUrl(
|
|
57
|
+
configured: string,
|
|
58
|
+
boundPort: () => number | undefined,
|
|
59
|
+
): string | (() => string) {
|
|
60
|
+
if (configured !== "") return configured;
|
|
61
|
+
return () => {
|
|
62
|
+
const port = boundPort();
|
|
63
|
+
if (port === undefined) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
"artifact download URL requested before the artifact file server bound its port",
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
return `http://localhost:${port}`;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The boot failure for a lane that could not bind: the address, the socket
|
|
74
|
+
* error's code, and the setting that moves the lane — named as the value
|
|
75
|
+
* that failed when the operator set it, as the default's escape hatch when
|
|
76
|
+
* the port was derived.
|
|
77
|
+
*/
|
|
78
|
+
export function artifactLaneBindError(
|
|
79
|
+
cause: unknown,
|
|
80
|
+
host: string,
|
|
81
|
+
port: number,
|
|
82
|
+
configured: boolean,
|
|
83
|
+
): Error {
|
|
84
|
+
const code =
|
|
85
|
+
cause instanceof Error &&
|
|
86
|
+
typeof (cause as NodeJS.ErrnoException).code === "string"
|
|
87
|
+
? ` (${(cause as NodeJS.ErrnoException).code})`
|
|
88
|
+
: "";
|
|
89
|
+
const remedy = configured
|
|
90
|
+
? `ARTIFACT_HTTP_PORT=${port} cannot be bound; set it to a free port`
|
|
91
|
+
: "set ARTIFACT_HTTP_PORT to a free port (unset, the lane takes the unified port + 1)";
|
|
92
|
+
return new Error(
|
|
93
|
+
`the artifact file server could not bind '${host}:${port}'${code}: ${remedy}`,
|
|
94
|
+
{ cause },
|
|
95
|
+
);
|
|
96
|
+
}
|