@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
package/src/boot/compose.ts
CHANGED
|
@@ -85,6 +85,7 @@ import {
|
|
|
85
85
|
import { ensureOperatorAccount } from "../domain/identityaccount/operator.js";
|
|
86
86
|
import { newDirectAccountProvisioner } from "../domain/identityaccount/provisioning.js";
|
|
87
87
|
import { newResourceIdentityAccountStore } from "../domain/identityaccount/resource-store.js";
|
|
88
|
+
import type { AccountsByCaller } from "../domain/identityaccount/resolve.js";
|
|
88
89
|
import { registerIamPolicyServices } from "../domain/iampolicy/controller.js";
|
|
89
90
|
import { newIamPolicyGrantPath } from "../domain/iampolicy/grant-path.js";
|
|
90
91
|
import { asFetch, guardedFetch, nodeLookup } from "@stigmer/outbound/egress";
|
|
@@ -178,6 +179,11 @@ import { PostgresStore } from "../store/postgres/store.js";
|
|
|
178
179
|
import { SqliteStore } from "../store/sqlite/store.js";
|
|
179
180
|
import type { Store } from "../store/interface.js";
|
|
180
181
|
import { resolveExtensions } from "../extensions/registry.js";
|
|
182
|
+
import {
|
|
183
|
+
artifactLaneBindError,
|
|
184
|
+
resolveArtifactLanePort,
|
|
185
|
+
resolveArtifactServeUrl,
|
|
186
|
+
} from "./artifact-lane.js";
|
|
181
187
|
import { resolveOAuthRedirectUri } from "./oauth-redirect-uri.js";
|
|
182
188
|
import { assessSkillTransferOrigin } from "./skill-transfer-origin.js";
|
|
183
189
|
import type { ServerExtension } from "../extensions/registry.js";
|
|
@@ -570,6 +576,16 @@ export async function composeServer(
|
|
|
570
576
|
logger,
|
|
571
577
|
})
|
|
572
578
|
: undefined);
|
|
579
|
+
// The persons callers stand for (stigmer#1387): under the
|
|
580
|
+
// require-authentication posture every caller is someone the
|
|
581
|
+
// identity-account domain can name, so memory and the person's declared
|
|
582
|
+
// preferences are that person's own; trusted-local composes none — the
|
|
583
|
+
// laptop's one operator, whose memory is the organization's switch alone
|
|
584
|
+
// (the contract's single-operator arm). The same binding both verifiers
|
|
585
|
+
// read, so a composed driver is followed here too.
|
|
586
|
+
const personAccounts: AccountsByCaller | undefined = requireAuthentication
|
|
587
|
+
? identityAccounts
|
|
588
|
+
: undefined;
|
|
573
589
|
logger.info("authorization posture resolved", {
|
|
574
590
|
posture: authorizationPosture,
|
|
575
591
|
authorizer:
|
|
@@ -951,11 +967,24 @@ export async function composeServer(
|
|
|
951
967
|
// arm landed with #13; the health probe runs in start(), matching Go's
|
|
952
968
|
// boot check. Since O5 the factory consults the composition's registered
|
|
953
969
|
// drivers for non-built-in types (§6b).
|
|
970
|
+
//
|
|
971
|
+
// The download lane's placement (boot/artifact-lane.ts): the configured
|
|
972
|
+
// port, or the port beside the one the unified listener binds, ephemeral
|
|
973
|
+
// when that one is. Unset, the serve URL reads the port the lane actually
|
|
974
|
+
// bound in start(), before any request can mint a URL.
|
|
975
|
+
const artifactLanePort = resolveArtifactLanePort({
|
|
976
|
+
configured: config.artifactHttpPort,
|
|
977
|
+
unifiedPort: options.portOverride ?? config.grpcPort,
|
|
978
|
+
});
|
|
979
|
+
let artifactLaneBoundPort: number | undefined;
|
|
954
980
|
const artifactStorage = newArtifactStorage(
|
|
955
981
|
{
|
|
956
982
|
type: config.artifactStorageType,
|
|
957
983
|
localBasePath: config.artifactLocalBasePath,
|
|
958
|
-
localServeUrl:
|
|
984
|
+
localServeUrl: resolveArtifactServeUrl(
|
|
985
|
+
config.artifactLocalServeUrl,
|
|
986
|
+
() => artifactLaneBoundPort,
|
|
987
|
+
),
|
|
959
988
|
r2Bucket: config.r2Bucket,
|
|
960
989
|
r2Endpoint: config.r2Endpoint,
|
|
961
990
|
r2AccessKeyId: config.r2AccessKeyId,
|
|
@@ -964,10 +993,9 @@ export async function composeServer(
|
|
|
964
993
|
},
|
|
965
994
|
extensions.drivers.artifactStorageDrivers,
|
|
966
995
|
);
|
|
967
|
-
// The artifact download lane: a SECOND loopback listener
|
|
968
|
-
//
|
|
969
|
-
//
|
|
970
|
-
// shutdown().
|
|
996
|
+
// The artifact download lane: a SECOND loopback listener, local storage
|
|
997
|
+
// only (Go server.go 849–870) — deliberately not a unified-port lane.
|
|
998
|
+
// Lifecycle rides start()/shutdown().
|
|
971
999
|
const artifactFileServer =
|
|
972
1000
|
config.artifactStorageType === "local"
|
|
973
1001
|
? createArtifactFileServer({
|
|
@@ -1461,6 +1489,7 @@ export async function composeServer(
|
|
|
1461
1489
|
authorizer,
|
|
1462
1490
|
authorizationLifecycle,
|
|
1463
1491
|
listReadScope,
|
|
1492
|
+
personAccounts,
|
|
1464
1493
|
runnerCredentialProvider: runnerCredentials,
|
|
1465
1494
|
});
|
|
1466
1495
|
registerAgentExecutionServices(router, {
|
|
@@ -1469,6 +1498,7 @@ export async function composeServer(
|
|
|
1469
1498
|
authorizer,
|
|
1470
1499
|
authorizationLifecycle,
|
|
1471
1500
|
listReadScope,
|
|
1501
|
+
personAccounts,
|
|
1472
1502
|
runnerCredentialProvider: runnerCredentials,
|
|
1473
1503
|
broker: agentExecutionStreamBroker,
|
|
1474
1504
|
engineState: executionEngineState,
|
|
@@ -1846,6 +1876,28 @@ export async function composeServer(
|
|
|
1846
1876
|
// Artifact storage must be reachable and writable before the server
|
|
1847
1877
|
// answers (Go server.go boots-fatal on the same probe).
|
|
1848
1878
|
await artifactStorage.health();
|
|
1879
|
+
// The download lane binds with the store it serves, and on the same
|
|
1880
|
+
// terms: a lane that cannot bind fails the boot, where the retired Go
|
|
1881
|
+
// server logged the failure and served on with every download dead
|
|
1882
|
+
// (stigmer#1089). Binding here, before SERVING and the unified port,
|
|
1883
|
+
// also means the derived serve URL exists before a request can mint
|
|
1884
|
+
// one.
|
|
1885
|
+
if (artifactFileServer !== undefined) {
|
|
1886
|
+
await warnOnLegacyArtifactLayout(config.artifactLocalBasePath, logger);
|
|
1887
|
+
try {
|
|
1888
|
+
artifactLaneBoundPort = await artifactFileServer.listen(
|
|
1889
|
+
artifactLanePort,
|
|
1890
|
+
config.artifactHttpHost,
|
|
1891
|
+
);
|
|
1892
|
+
} catch (error) {
|
|
1893
|
+
throw artifactLaneBindError(
|
|
1894
|
+
error,
|
|
1895
|
+
config.artifactHttpHost,
|
|
1896
|
+
artifactLanePort,
|
|
1897
|
+
config.artifactHttpPort !== undefined,
|
|
1898
|
+
);
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1849
1901
|
// Rebuild the search index before the port binds (Go
|
|
1850
1902
|
// server.go:617): the index is separate from the resources table,
|
|
1851
1903
|
// and rebuilding here makes every resource — including rows an
|
|
@@ -1884,22 +1936,6 @@ export async function composeServer(
|
|
|
1884
1936
|
options.host,
|
|
1885
1937
|
);
|
|
1886
1938
|
logger.info("stigmer-server listening", { port });
|
|
1887
|
-
// The artifact file server binds AFTER the main server, exactly Go's
|
|
1888
|
-
// boot order; a bind failure is logged but NOT fatal (Go's
|
|
1889
|
-
// ListenAndServe goroutine logs and dies while the server runs on).
|
|
1890
|
-
if (artifactFileServer !== undefined) {
|
|
1891
|
-
await warnOnLegacyArtifactLayout(config.artifactLocalBasePath, logger);
|
|
1892
|
-
try {
|
|
1893
|
-
await artifactFileServer.listen(
|
|
1894
|
-
config.artifactHttpPort,
|
|
1895
|
-
config.artifactHttpHost,
|
|
1896
|
-
);
|
|
1897
|
-
} catch (error) {
|
|
1898
|
-
logger.error("Artifact HTTP file server failed", {
|
|
1899
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1900
|
-
});
|
|
1901
|
-
}
|
|
1902
|
-
}
|
|
1903
1939
|
return port;
|
|
1904
1940
|
},
|
|
1905
1941
|
|
package/src/boot/config.ts
CHANGED
|
@@ -95,16 +95,21 @@ export interface ServerConfig {
|
|
|
95
95
|
/** The artifact root — shared with the runner's LOCAL_ARTIFACT_PATH (#285). */
|
|
96
96
|
readonly artifactLocalBasePath: string;
|
|
97
97
|
/**
|
|
98
|
-
* Base URL for local artifact download URLs
|
|
99
|
-
*
|
|
100
|
-
*
|
|
98
|
+
* Base URL for local artifact download URLs (ARTIFACT_LOCAL_SERVE_URL;
|
|
99
|
+
* #13); no trailing path segment (the storage key carries the full path).
|
|
100
|
+
* "" = unset: the URL follows the port the artifact file server actually
|
|
101
|
+
* bound (boot/artifact-lane.ts), so an ephemeral lane mints URLs that
|
|
102
|
+
* reach it.
|
|
101
103
|
*/
|
|
102
104
|
readonly artifactLocalServeUrl: string;
|
|
103
105
|
/**
|
|
104
|
-
* The artifact file server's own port (ARTIFACT_HTTP_PORT
|
|
105
|
-
*
|
|
106
|
+
* The artifact file server's own port (ARTIFACT_HTTP_PORT). Undefined =
|
|
107
|
+
* unset: the lane follows the unified port, +1, or ephemeral when that
|
|
108
|
+
* port is 0 — the rule lives in boot/artifact-lane.ts, because only the
|
|
109
|
+
* composition knows the port the unified listener binds. Only bound when
|
|
110
|
+
* artifact storage is local, and a lane that cannot bind fails the boot.
|
|
106
111
|
*/
|
|
107
|
-
readonly artifactHttpPort: number;
|
|
112
|
+
readonly artifactHttpPort: number | undefined;
|
|
108
113
|
/**
|
|
109
114
|
* The artifact file server's bind host (ARTIFACT_HTTP_HOST, DD-013;
|
|
110
115
|
* shipped with the Docker image, Phase-2 P4). Defaults to 127.0.0.1 —
|
|
@@ -294,8 +299,9 @@ export const DEFAULT_SANDBOX_RUNNER_IMAGE = "ghcr.io/stigmer/runner:latest";
|
|
|
294
299
|
export function loadConfig(env: NodeJS.ProcessEnv = process.env): ServerConfig {
|
|
295
300
|
const { operatorEmail, operatorName } = loadOperatorIdentity(env);
|
|
296
301
|
const grpcPort = envInt(env, "GRPC_PORT", DEFAULT_GRPC_PORT);
|
|
297
|
-
//
|
|
298
|
-
|
|
302
|
+
// Unset (or malformed, the loader's leniency) leaves the lane to follow
|
|
303
|
+
// the unified port; boot/artifact-lane.ts derives it at composition.
|
|
304
|
+
const artifactHttpPort = envOptionalInt(env, "ARTIFACT_HTTP_PORT");
|
|
299
305
|
const artifactHttpHost = envString(env, "ARTIFACT_HTTP_HOST", "127.0.0.1");
|
|
300
306
|
const artifactStorageType = envString(env, "ARTIFACT_STORAGE_TYPE", "local");
|
|
301
307
|
const r2 = {
|
|
@@ -337,11 +343,7 @@ export function loadConfig(env: NodeJS.ProcessEnv = process.env): ServerConfig {
|
|
|
337
343
|
"ARTIFACT_LOCAL_BASE_PATH",
|
|
338
344
|
defaultArtifactPath(),
|
|
339
345
|
),
|
|
340
|
-
artifactLocalServeUrl: envString(
|
|
341
|
-
env,
|
|
342
|
-
"ARTIFACT_LOCAL_SERVE_URL",
|
|
343
|
-
`http://localhost:${artifactHttpPort}`,
|
|
344
|
-
),
|
|
346
|
+
artifactLocalServeUrl: envString(env, "ARTIFACT_LOCAL_SERVE_URL", ""),
|
|
345
347
|
logLevel: envString(env, "LOG_LEVEL", "info"),
|
|
346
348
|
env: envString(env, "ENV", "local"),
|
|
347
349
|
modelRegistryUpstream: envString(
|
|
@@ -531,14 +533,22 @@ function envString(
|
|
|
531
533
|
}
|
|
532
534
|
|
|
533
535
|
function envInt(env: NodeJS.ProcessEnv, key: string, fallback: number): number {
|
|
536
|
+
return envOptionalInt(env, key) ?? fallback;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/** An integer setting, or undefined when it is unset or malformed. */
|
|
540
|
+
function envOptionalInt(
|
|
541
|
+
env: NodeJS.ProcessEnv,
|
|
542
|
+
key: string,
|
|
543
|
+
): number | undefined {
|
|
534
544
|
const value = env[key];
|
|
535
545
|
if (value === undefined || value === "") {
|
|
536
|
-
return
|
|
546
|
+
return undefined;
|
|
537
547
|
}
|
|
538
548
|
const parsed = Number.parseInt(value, 10);
|
|
539
549
|
// Go's strconv.Atoi rejects trailing garbage ("7234x"); Number.parseInt
|
|
540
550
|
// would accept it, so the round-trip check keeps the two loaders aligned.
|
|
541
551
|
return Number.isSafeInteger(parsed) && String(parsed) === value.trim()
|
|
542
552
|
? parsed
|
|
543
|
-
:
|
|
553
|
+
: undefined;
|
|
544
554
|
}
|
|
@@ -11,8 +11,11 @@
|
|
|
11
11
|
* Proven by agent.conformance.test.ts (CONFORMANCE_TARGET=local),
|
|
12
12
|
* __tests__/agent.test.ts and __tests__/store-faults.test.ts.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* Every chain opens with Authorize; create, delete and updateVisibility run
|
|
15
|
+
* the shared tuple-lifecycle steps against the composed lifecycle;
|
|
16
|
+
* getByReference loads, then authorizes the loaded agent exactly as `get`
|
|
17
|
+
* would (AuthorizeResolvedTarget). The kind has no list RPC. Per-RPC
|
|
18
|
+
* posture: docs/authorization-coverage.md §6.
|
|
16
19
|
*/
|
|
17
20
|
import type { ConnectRouter, HandlerContext } from "@connectrpc/connect";
|
|
18
21
|
|
|
@@ -7,8 +7,18 @@
|
|
|
7
7
|
* direct-step shape as Go). Go's nil-client skip proofs map to throwing
|
|
8
8
|
* providers — reaching the client would fail the test just as a nil
|
|
9
9
|
* dereference would panic Go.
|
|
10
|
+
*
|
|
11
|
+
* The two compose steps are also pinned where callers are persons
|
|
12
|
+
* (stigmer#1387, stigmer#1397): recall is the run's person's own
|
|
13
|
+
* confirmed facts, behind the first-party gate, the org switch and the
|
|
14
|
+
* person's own, and user_context is that person's standing context — the
|
|
15
|
+
* retired Java steps' gates, in their order.
|
|
10
16
|
*/
|
|
11
17
|
import { testCallerIdentity } from "../../../pipeline/__tests__/support.js";
|
|
18
|
+
import { IdentityAccountSchema } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/api_pb";
|
|
19
|
+
import type { CallerIdentity } from "../../../extensions/identity.js";
|
|
20
|
+
import type { AccountsByCaller } from "../../identityaccount/resolve.js";
|
|
21
|
+
import { fakeIdentityAccountStore } from "../../identityaccount/__tests__/support.js";
|
|
12
22
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
13
23
|
import { tmpdir } from "node:os";
|
|
14
24
|
import path from "node:path";
|
|
@@ -94,11 +104,12 @@ function newExecution(sessionId: string, agentId: string): AgentExecution {
|
|
|
94
104
|
|
|
95
105
|
function newContext(
|
|
96
106
|
execution: AgentExecution,
|
|
107
|
+
caller: CallerIdentity = testCallerIdentity(),
|
|
97
108
|
): RequestContext<typeof AgentExecutionSchema> {
|
|
98
109
|
return new RequestContext(
|
|
99
110
|
AgentExecutionSchema,
|
|
100
111
|
execution,
|
|
101
|
-
|
|
112
|
+
caller,
|
|
102
113
|
ApiResourceKind.agent_execution,
|
|
103
114
|
);
|
|
104
115
|
}
|
|
@@ -476,7 +487,7 @@ describe("newComposeDeclaredPreferencesStep", () => {
|
|
|
476
487
|
"server-owned field must be stamped on every path",
|
|
477
488
|
).toBeDefined();
|
|
478
489
|
expect(got?.orgContext).toBe(tt.wantOrgContext);
|
|
479
|
-
// user_context
|
|
490
|
+
// user_context stays empty under the single-operator posture.
|
|
480
491
|
expect(got?.userContext).toBe("");
|
|
481
492
|
});
|
|
482
493
|
}
|
|
@@ -775,6 +786,223 @@ describe("newComposeRecalledMemoriesStep", () => {
|
|
|
775
786
|
}
|
|
776
787
|
});
|
|
777
788
|
|
|
789
|
+
// ---------------------------------------------------------------------------
|
|
790
|
+
// Where callers are persons (stigmer#1387, stigmer#1397).
|
|
791
|
+
// ---------------------------------------------------------------------------
|
|
792
|
+
|
|
793
|
+
describe("the compose steps where callers are persons", () => {
|
|
794
|
+
const CAROL = "ida_carol";
|
|
795
|
+
const DAVE = "ida_dave";
|
|
796
|
+
|
|
797
|
+
/** Carol at the console: a wire user whose identity is her account id. */
|
|
798
|
+
const carol = testCallerIdentity({ identityId: CAROL });
|
|
799
|
+
|
|
800
|
+
function directory(preferences: {
|
|
801
|
+
memoryEnabled?: boolean;
|
|
802
|
+
standingContext?: string;
|
|
803
|
+
}): AccountsByCaller {
|
|
804
|
+
const accounts = fakeIdentityAccountStore();
|
|
805
|
+
accounts.rows.set(
|
|
806
|
+
CAROL,
|
|
807
|
+
create(IdentityAccountSchema, {
|
|
808
|
+
metadata: { id: CAROL, name: "Carol" },
|
|
809
|
+
spec: { idpId: "auth0|carol", preferences },
|
|
810
|
+
}),
|
|
811
|
+
);
|
|
812
|
+
return accounts;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
const faultingDirectory: AccountsByCaller = {
|
|
816
|
+
findById: async () => {
|
|
817
|
+
throw new Error("simulated store fault");
|
|
818
|
+
},
|
|
819
|
+
findDirectByIdpId: async () => {
|
|
820
|
+
throw new Error("simulated store fault");
|
|
821
|
+
},
|
|
822
|
+
};
|
|
823
|
+
|
|
824
|
+
/** A JWT carrying `payload`, unsigned: the gate reads, never verifies. */
|
|
825
|
+
function jwtWith(payload: Record<string, unknown>): string {
|
|
826
|
+
const segment = (value: object) =>
|
|
827
|
+
Buffer.from(JSON.stringify(value)).toString("base64url");
|
|
828
|
+
return `${segment({ alg: "RS256", typ: "JWT" })}.${segment(payload)}.signature`;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
/** Every lane that is not a person speaking for themselves. */
|
|
832
|
+
const notFirstParty: Array<[string, CallerIdentity]> = [
|
|
833
|
+
[
|
|
834
|
+
"a runner acting as Carol",
|
|
835
|
+
testCallerIdentity({ identityId: CAROL, callerClass: "runner" }),
|
|
836
|
+
],
|
|
837
|
+
[
|
|
838
|
+
"a machine account",
|
|
839
|
+
testCallerIdentity({ identityId: CAROL, callerClass: "machine" }),
|
|
840
|
+
],
|
|
841
|
+
[
|
|
842
|
+
"a cloud channel sender",
|
|
843
|
+
testCallerIdentity({ identityId: CAROL, callerClass: "channel" }),
|
|
844
|
+
],
|
|
845
|
+
[
|
|
846
|
+
"the schedule fire acting as Carol (in-process)",
|
|
847
|
+
testCallerIdentity({ identityId: CAROL, origin: "in-process" }),
|
|
848
|
+
],
|
|
849
|
+
[
|
|
850
|
+
"Carol through a PlatformClient user token",
|
|
851
|
+
testCallerIdentity({
|
|
852
|
+
identityId: CAROL,
|
|
853
|
+
issuer: "stigmer",
|
|
854
|
+
rawToken: jwtWith({
|
|
855
|
+
iss: "stigmer",
|
|
856
|
+
sub: CAROL,
|
|
857
|
+
platform_client_id: "pc_1",
|
|
858
|
+
}),
|
|
859
|
+
}),
|
|
860
|
+
],
|
|
861
|
+
];
|
|
862
|
+
|
|
863
|
+
async function seedRecallRows(): Promise<void> {
|
|
864
|
+
await seedMemoryOrg("test-org", true);
|
|
865
|
+
const confirmed = MemoryLifecycleState.lifecycle_state_confirmed;
|
|
866
|
+
await seedMemory({
|
|
867
|
+
id: "mem_carol",
|
|
868
|
+
orgId: "test-org",
|
|
869
|
+
subject: CAROL,
|
|
870
|
+
content: "Carol's",
|
|
871
|
+
state: confirmed,
|
|
872
|
+
createdAt: new Date("2026-09-01T00:00:00Z"),
|
|
873
|
+
});
|
|
874
|
+
await seedMemory({
|
|
875
|
+
id: "mem_dave",
|
|
876
|
+
orgId: "test-org",
|
|
877
|
+
subject: DAVE,
|
|
878
|
+
content: "Dave's",
|
|
879
|
+
state: confirmed,
|
|
880
|
+
createdAt: new Date("2026-09-02T00:00:00Z"),
|
|
881
|
+
});
|
|
882
|
+
await seedMemory({
|
|
883
|
+
id: "mem_sentinel",
|
|
884
|
+
orgId: "test-org",
|
|
885
|
+
subject: "",
|
|
886
|
+
content: "nobody's",
|
|
887
|
+
state: confirmed,
|
|
888
|
+
createdAt: new Date("2026-09-03T00:00:00Z"),
|
|
889
|
+
});
|
|
890
|
+
await seedMemory({
|
|
891
|
+
id: "mem_carol_proposed",
|
|
892
|
+
orgId: "test-org",
|
|
893
|
+
subject: CAROL,
|
|
894
|
+
content: "unconfirmed",
|
|
895
|
+
state: MemoryLifecycleState.lifecycle_state_proposed,
|
|
896
|
+
createdAt: new Date("2026-09-04T00:00:00Z"),
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
async function recall(
|
|
901
|
+
accounts: AccountsByCaller | undefined,
|
|
902
|
+
caller: CallerIdentity,
|
|
903
|
+
) {
|
|
904
|
+
const step = newComposeRecalledMemoriesStep(store, silentLogger, accounts);
|
|
905
|
+
const ctx = newContext(newExecution("ses_1", "agt_1"), caller);
|
|
906
|
+
await step.execute(ctx);
|
|
907
|
+
return ctx.newState.spec?.recalledMemories;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
it("recalls exactly the run's person's confirmed facts", async () => {
|
|
911
|
+
await seedRecallRows();
|
|
912
|
+
const got = await recall(directory({ memoryEnabled: true }), carol);
|
|
913
|
+
expect(got?.enabled).toBe(true);
|
|
914
|
+
expect(got?.facts.map((f) => f.memoryId)).toEqual(["mem_carol"]);
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
it("recalls nothing and offers no remember tool when the person's own switch is off", async () => {
|
|
918
|
+
await seedRecallRows();
|
|
919
|
+
const got = await recall(directory({ memoryEnabled: false }), carol);
|
|
920
|
+
expect(got?.enabled).toBe(false);
|
|
921
|
+
expect(got?.facts).toEqual([]);
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
it("recalls nothing when the organization's switch is off, whatever the person's", async () => {
|
|
925
|
+
await seedMemoryOrg("test-org", false);
|
|
926
|
+
const got = await recall(directory({ memoryEnabled: true }), carol);
|
|
927
|
+
expect(got?.enabled).toBe(false);
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
it("recalls nothing for a caller no account stands for", async () => {
|
|
931
|
+
await seedRecallRows();
|
|
932
|
+
const got = await recall(
|
|
933
|
+
directory({ memoryEnabled: true }),
|
|
934
|
+
testCallerIdentity({ identityId: "auth0|stranger" }),
|
|
935
|
+
);
|
|
936
|
+
expect(got?.enabled).toBe(false);
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
it("degrades to disabled on a directory fault (best-effort)", async () => {
|
|
940
|
+
await seedRecallRows();
|
|
941
|
+
const got = await recall(faultingDirectory, carol);
|
|
942
|
+
expect(got?.enabled).toBe(false);
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
for (const [name, caller] of notFirstParty) {
|
|
946
|
+
it(`recalls nothing for ${name}`, async () => {
|
|
947
|
+
await seedRecallRows();
|
|
948
|
+
const got = await recall(directory({ memoryEnabled: true }), caller);
|
|
949
|
+
expect(got?.enabled).toBe(false);
|
|
950
|
+
expect(got?.facts).toEqual([]);
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
async function declared(
|
|
955
|
+
accounts: AccountsByCaller | undefined,
|
|
956
|
+
caller: CallerIdentity,
|
|
957
|
+
) {
|
|
958
|
+
await seedOrg("test-org", "We deploy to us-east-1.");
|
|
959
|
+
const step = newComposeDeclaredPreferencesStep(
|
|
960
|
+
store,
|
|
961
|
+
silentLogger,
|
|
962
|
+
accounts,
|
|
963
|
+
);
|
|
964
|
+
const execution = newExecution("ses_1", "agt_1");
|
|
965
|
+
execution.spec!.declaredPreferences = create(DeclaredPreferencesSchema, {
|
|
966
|
+
userContext: "injected user context",
|
|
967
|
+
});
|
|
968
|
+
const ctx = newContext(execution, caller);
|
|
969
|
+
await step.execute(ctx);
|
|
970
|
+
return ctx.newState.spec?.declaredPreferences;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
it("composes the run's person's standing context beside the organization's", async () => {
|
|
974
|
+
const got = await declared(
|
|
975
|
+
directory({ standingContext: "Call me Carol." }),
|
|
976
|
+
carol,
|
|
977
|
+
);
|
|
978
|
+
expect(got?.orgContext).toBe("We deploy to us-east-1.");
|
|
979
|
+
expect(got?.userContext).toBe("Call me Carol.");
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
it("keeps the organization's context and composes no person's on a directory fault", async () => {
|
|
983
|
+
const got = await declared(faultingDirectory, carol);
|
|
984
|
+
expect(got?.orgContext).toBe("We deploy to us-east-1.");
|
|
985
|
+
expect(got?.userContext).toBe("");
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
it("composes no person's context under the single-operator posture", async () => {
|
|
989
|
+
const got = await declared(undefined, carol);
|
|
990
|
+
expect(got?.orgContext).toBe("We deploy to us-east-1.");
|
|
991
|
+
expect(got?.userContext).toBe("");
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
for (const [name, caller] of notFirstParty) {
|
|
995
|
+
it(`composes no person's context for ${name}, and keeps the organization's`, async () => {
|
|
996
|
+
const got = await declared(
|
|
997
|
+
directory({ standingContext: "Call me Carol." }),
|
|
998
|
+
caller,
|
|
999
|
+
);
|
|
1000
|
+
expect(got?.orgContext).toBe("We deploy to us-east-1.");
|
|
1001
|
+
expect(got?.userContext).toBe("");
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
});
|
|
1005
|
+
|
|
778
1006
|
// ---------------------------------------------------------------------------
|
|
779
1007
|
// agentCallTaskEnvironmentRefs (create_execution_context_workflow_refs_test.go).
|
|
780
1008
|
// ---------------------------------------------------------------------------
|
|
@@ -8,10 +8,15 @@
|
|
|
8
8
|
* Proven by agentexecution.conformance.test.ts
|
|
9
9
|
* (CONFORMANCE_TARGET=local) and __tests__/.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* Every chain opens with Authorize, and create also authorizes its run
|
|
12
|
+
* target (AuthorizeRunTarget); create and delete run the shared
|
|
13
|
+
* tuple-lifecycle steps against the composed lifecycle. The direct
|
|
14
|
+
* handlers — subscribe and the two artifact reads — evaluate their
|
|
15
|
+
* annotation through authorizeDirect; uploadAttachment is authorized at
|
|
16
|
+
* the create its storage key feeds. list, listBySession and
|
|
17
|
+
* getExecutionSummary narrow through the composed list read scope.
|
|
18
|
+
* Subscribe streams ride the in-memory stream broker (ADR 011); there is
|
|
19
|
+
* no Redis. Per-RPC posture: docs/authorization-coverage.md §16.
|
|
15
20
|
*/
|
|
16
21
|
import type { ConnectRouter, HandlerContext } from "@connectrpc/connect";
|
|
17
22
|
|
|
@@ -31,6 +36,7 @@ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/
|
|
|
31
36
|
import type { Logger } from "../../boot/logger.js";
|
|
32
37
|
import type { Authorizer } from "../../extensions/authorizer.js";
|
|
33
38
|
import type { ListReadScope } from "../../extensions/list-read-scope.js";
|
|
39
|
+
import type { AccountsByCaller } from "../identityaccount/resolve.js";
|
|
34
40
|
import type { ResourceAuthorizationLifecycle } from "../../extensions/resource-authorization.js";
|
|
35
41
|
import type { ResolvedGateSteps } from "../../extensions/gate-slots.js";
|
|
36
42
|
import { stepsForSlot } from "../../extensions/gate-slots.js";
|
|
@@ -151,6 +157,14 @@ export interface AgentExecutionControllerDeps {
|
|
|
151
157
|
readonly authorizationLifecycle: ResourceAuthorizationLifecycle | undefined;
|
|
152
158
|
/** The composed summary read scope — undefined = the OSS full scan (C2 Stage 4). */
|
|
153
159
|
readonly listReadScope: ListReadScope | undefined;
|
|
160
|
+
/**
|
|
161
|
+
* The directory of the persons callers stand for (stigmer#1387) — the
|
|
162
|
+
* composed identity-account port, bound under the require-authentication
|
|
163
|
+
* posture; undefined = the single-operator posture (trusted-local),
|
|
164
|
+
* where memory is the organization's switch alone and the subject the
|
|
165
|
+
* empty-string sentinel.
|
|
166
|
+
*/
|
|
167
|
+
readonly personAccounts: AccountsByCaller | undefined;
|
|
154
168
|
/**
|
|
155
169
|
* The shared broadcast fabric for subscribe streams. ONE instance spans
|
|
156
170
|
* both routers (serving + in-process) — see stream-broker.ts; the
|
|
@@ -360,8 +374,20 @@ async function createExecution(
|
|
|
360
374
|
sessionCreator: deps.sessionCreator,
|
|
361
375
|
}),
|
|
362
376
|
)
|
|
363
|
-
.addStep(
|
|
364
|
-
|
|
377
|
+
.addStep(
|
|
378
|
+
newComposeDeclaredPreferencesStep(
|
|
379
|
+
deps.store,
|
|
380
|
+
deps.logger,
|
|
381
|
+
deps.personAccounts,
|
|
382
|
+
),
|
|
383
|
+
)
|
|
384
|
+
.addStep(
|
|
385
|
+
newComposeRecalledMemoriesStep(
|
|
386
|
+
deps.store,
|
|
387
|
+
deps.logger,
|
|
388
|
+
deps.personAccounts,
|
|
389
|
+
),
|
|
390
|
+
)
|
|
365
391
|
.addStep(newSetInitialPhaseStep())
|
|
366
392
|
.addStep(newCreateExecutionContextStep(deps.executionContextBuilder))
|
|
367
393
|
.addStep(newProcessAttachmentsStep(deps.logger))
|