@stigmer/server 3.20.0 → 3.22.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/authorization/evaluator.d.ts +7 -1
- package/dist/authorization/evaluator.d.ts.map +1 -1
- 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 +19 -11
- package/dist/authorization/list-read-scope.js.map +1 -1
- package/dist/authorization/model/organization.d.ts.map +1 -1
- package/dist/authorization/model/organization.js +2 -0
- package/dist/authorization/model/organization.js.map +1 -1
- package/dist/boot/compose.d.ts.map +1 -1
- package/dist/boot/compose.js +0 -1
- package/dist/boot/compose.js.map +1 -1
- package/dist/boot/inprocess.d.ts +0 -2
- package/dist/boot/inprocess.d.ts.map +1 -1
- package/dist/boot/inprocess.js +0 -6
- package/dist/boot/inprocess.js.map +1 -1
- package/dist/domain/agent/controller.d.ts +2 -2
- package/dist/domain/agent/controller.d.ts.map +1 -1
- package/dist/domain/agent/controller.js +3 -17
- package/dist/domain/agent/controller.js.map +1 -1
- package/dist/domain/agent/steps.d.ts +0 -1
- package/dist/domain/agent/steps.d.ts.map +1 -1
- package/dist/domain/agent/steps.js +1 -40
- package/dist/domain/agent/steps.js.map +1 -1
- package/dist/domain/agentchannel/controller.d.ts.map +1 -1
- package/dist/domain/agentchannel/controller.js +6 -1
- package/dist/domain/agentchannel/controller.js.map +1 -1
- package/dist/domain/agentchannel/steps.d.ts +19 -0
- package/dist/domain/agentchannel/steps.d.ts.map +1 -1
- package/dist/domain/agentchannel/steps.js +32 -0
- package/dist/domain/agentchannel/steps.js.map +1 -1
- package/dist/domain/agentexecution/controller.d.ts.map +1 -1
- package/dist/domain/agentexecution/controller.js +7 -9
- package/dist/domain/agentexecution/controller.js.map +1 -1
- package/dist/domain/agentexecution/create-execution-context-step.d.ts +0 -2
- package/dist/domain/agentexecution/create-execution-context-step.d.ts.map +1 -1
- package/dist/domain/agentexecution/create-execution-context-step.js +179 -60
- package/dist/domain/agentexecution/create-execution-context-step.js.map +1 -1
- package/dist/domain/agentexecution/create-steps.d.ts +16 -31
- package/dist/domain/agentexecution/create-steps.d.ts.map +1 -1
- package/dist/domain/agentexecution/create-steps.js +36 -102
- package/dist/domain/agentexecution/create-steps.js.map +1 -1
- package/dist/domain/agentexecution/run-target.d.ts +7 -3
- package/dist/domain/agentexecution/run-target.d.ts.map +1 -1
- package/dist/domain/agentexecution/run-target.js.map +1 -1
- package/dist/domain/agentinstance/controller.d.ts +5 -5
- package/dist/domain/agentinstance/controller.d.ts.map +1 -1
- package/dist/domain/agentinstance/controller.js +6 -1
- package/dist/domain/agentinstance/controller.js.map +1 -1
- package/dist/domain/agentinstance/steps.d.ts +43 -8
- package/dist/domain/agentinstance/steps.d.ts.map +1 -1
- package/dist/domain/agentinstance/steps.js +71 -8
- package/dist/domain/agentinstance/steps.js.map +1 -1
- package/dist/domain/agentshare/controller.d.ts.map +1 -1
- package/dist/domain/agentshare/controller.js +7 -1
- package/dist/domain/agentshare/controller.js.map +1 -1
- package/dist/domain/agentshare/steps.d.ts +58 -0
- package/dist/domain/agentshare/steps.d.ts.map +1 -1
- package/dist/domain/agentshare/steps.js +109 -4
- package/dist/domain/agentshare/steps.js.map +1 -1
- package/dist/domain/artifact/controller.js +21 -25
- package/dist/domain/artifact/controller.js.map +1 -1
- package/dist/domain/channelapp/controller.d.ts.map +1 -1
- package/dist/domain/channelapp/controller.js +2 -0
- package/dist/domain/channelapp/controller.js.map +1 -1
- package/dist/domain/environment/controller.d.ts.map +1 -1
- package/dist/domain/environment/controller.js +2 -0
- package/dist/domain/environment/controller.js.map +1 -1
- package/dist/domain/environment/personal.d.ts +42 -0
- package/dist/domain/environment/personal.d.ts.map +1 -0
- package/dist/domain/environment/personal.js +96 -0
- package/dist/domain/environment/personal.js.map +1 -0
- package/dist/domain/executioncontext/controller.d.ts.map +1 -1
- package/dist/domain/executioncontext/controller.js +2 -0
- package/dist/domain/executioncontext/controller.js.map +1 -1
- package/dist/domain/executioncontext/steps.d.ts.map +1 -1
- package/dist/domain/executioncontext/steps.js +2 -1
- package/dist/domain/executioncontext/steps.js.map +1 -1
- package/dist/domain/mcpserver/connect.d.ts +0 -2
- package/dist/domain/mcpserver/connect.d.ts.map +1 -1
- package/dist/domain/mcpserver/connect.js +24 -79
- package/dist/domain/mcpserver/connect.js.map +1 -1
- package/dist/domain/mcpserver/controller.d.ts.map +1 -1
- package/dist/domain/mcpserver/controller.js +2 -0
- package/dist/domain/mcpserver/controller.js.map +1 -1
- package/dist/domain/mcpserver/start-connect.d.ts.map +1 -1
- package/dist/domain/mcpserver/start-connect.js +8 -7
- package/dist/domain/mcpserver/start-connect.js.map +1 -1
- package/dist/domain/memory/controller.d.ts.map +1 -1
- package/dist/domain/memory/controller.js +6 -1
- package/dist/domain/memory/controller.js.map +1 -1
- package/dist/domain/memory/steps.d.ts +23 -0
- package/dist/domain/memory/steps.d.ts.map +1 -1
- package/dist/domain/memory/steps.js +35 -0
- package/dist/domain/memory/steps.js.map +1 -1
- package/dist/domain/oauthapp/controller.d.ts.map +1 -1
- package/dist/domain/oauthapp/controller.js +2 -0
- package/dist/domain/oauthapp/controller.js.map +1 -1
- package/dist/domain/plugin/controller.d.ts.map +1 -1
- package/dist/domain/plugin/controller.js +5 -24
- package/dist/domain/plugin/controller.js.map +1 -1
- package/dist/domain/plugin/overlay/sanitize.js +2 -2
- package/dist/domain/schedule/controller.d.ts.map +1 -1
- package/dist/domain/schedule/controller.js +7 -1
- package/dist/domain/schedule/controller.js.map +1 -1
- package/dist/domain/schedule/steps.d.ts +19 -0
- package/dist/domain/schedule/steps.d.ts.map +1 -1
- package/dist/domain/schedule/steps.js +32 -0
- package/dist/domain/schedule/steps.js.map +1 -1
- package/dist/domain/session/controller.d.ts +0 -7
- package/dist/domain/session/controller.d.ts.map +1 -1
- package/dist/domain/session/controller.js +11 -16
- package/dist/domain/session/controller.js.map +1 -1
- package/dist/domain/session/run-target.d.ts +9 -8
- package/dist/domain/session/run-target.d.ts.map +1 -1
- package/dist/domain/session/run-target.js.map +1 -1
- package/dist/domain/session/steps.d.ts +8 -36
- package/dist/domain/session/steps.d.ts.map +1 -1
- package/dist/domain/session/steps.js +27 -172
- package/dist/domain/session/steps.js.map +1 -1
- package/dist/domain/skill/controller.d.ts.map +1 -1
- package/dist/domain/skill/controller.js +8 -17
- package/dist/domain/skill/controller.js.map +1 -1
- package/dist/domain/workflow/controller.d.ts.map +1 -1
- package/dist/domain/workflow/controller.js +30 -107
- package/dist/domain/workflow/controller.js.map +1 -1
- package/dist/domain/workflow/version-resolution.d.ts +5 -0
- package/dist/domain/workflow/version-resolution.d.ts.map +1 -0
- package/dist/domain/workflow/version-resolution.js +113 -0
- package/dist/domain/workflow/version-resolution.js.map +1 -0
- package/dist/domain/workflowexecution/create-steps.d.ts +4 -3
- package/dist/domain/workflowexecution/create-steps.d.ts.map +1 -1
- package/dist/domain/workflowexecution/create-steps.js +4 -3
- package/dist/domain/workflowexecution/create-steps.js.map +1 -1
- package/dist/domain/workflowinstance/controller.d.ts.map +1 -1
- package/dist/domain/workflowinstance/controller.js +6 -1
- package/dist/domain/workflowinstance/controller.js.map +1 -1
- package/dist/domain/workflowinstance/steps.d.ts +20 -0
- package/dist/domain/workflowinstance/steps.d.ts.map +1 -1
- package/dist/domain/workflowinstance/steps.js +39 -0
- package/dist/domain/workflowinstance/steps.js.map +1 -1
- package/dist/extensions/identity.d.ts +19 -0
- package/dist/extensions/identity.d.ts.map +1 -1
- package/dist/extensions/identity.js +22 -3
- package/dist/extensions/identity.js.map +1 -1
- package/dist/extensions/list-read-scope.d.ts +41 -7
- package/dist/extensions/list-read-scope.d.ts.map +1 -1
- package/dist/extensions/list-read-scope.js +13 -2
- package/dist/extensions/list-read-scope.js.map +1 -1
- package/dist/pipeline/apiresource-labels.d.ts +2 -2
- package/dist/pipeline/apiresource-labels.js +2 -2
- package/dist/pipeline/errors.d.ts +2 -2
- package/dist/pipeline/errors.js +2 -2
- package/dist/pipeline/steps/authorize-resolved-target.d.ts +88 -0
- package/dist/pipeline/steps/authorize-resolved-target.d.ts.map +1 -0
- package/dist/pipeline/steps/authorize-resolved-target.js +36 -0
- package/dist/pipeline/steps/authorize-resolved-target.js.map +1 -0
- package/dist/pipeline/steps/authorize.d.ts +27 -0
- package/dist/pipeline/steps/authorize.d.ts.map +1 -1
- package/dist/pipeline/steps/authorize.js +49 -10
- package/dist/pipeline/steps/authorize.js.map +1 -1
- package/dist/pipeline/steps/guard-memory-capture.d.ts.map +1 -1
- package/dist/pipeline/steps/guard-memory-capture.js +2 -1
- package/dist/pipeline/steps/guard-memory-capture.js.map +1 -1
- package/dist/pipeline/steps/guard-reserved-labels.d.ts.map +1 -1
- package/dist/pipeline/steps/guard-reserved-labels.js +12 -12
- package/dist/pipeline/steps/guard-reserved-labels.js.map +1 -1
- package/dist/pipeline/steps/helpers.d.ts +9 -5
- package/dist/pipeline/steps/helpers.d.ts.map +1 -1
- package/dist/pipeline/steps/helpers.js +11 -15
- package/dist/pipeline/steps/helpers.js.map +1 -1
- package/dist/pipeline/steps/version-history.d.ts +11 -0
- package/dist/pipeline/steps/version-history.d.ts.map +1 -1
- package/dist/pipeline/steps/version-history.js +26 -0
- package/dist/pipeline/steps/version-history.js.map +1 -1
- package/package.json +6 -6
- package/src/authorization/__tests__/list-read-scope.test.ts +23 -12
- package/src/authorization/__tests__/skip-lane-inventory.test.ts +160 -0
- package/src/authorization/__tests__/wire-permissions.test.ts +5 -5
- package/src/authorization/evaluator.ts +7 -0
- package/src/authorization/list-read-scope.ts +22 -11
- package/src/authorization/model/__tests__/registry.test.ts +2 -0
- package/src/authorization/model/organization.ts +2 -0
- package/src/boot/compose.ts +0 -1
- package/src/boot/inprocess.ts +0 -9
- package/src/domain/agent/__tests__/agent.test.ts +0 -68
- package/src/domain/agent/controller.ts +11 -36
- package/src/domain/agent/steps.ts +1 -60
- package/src/domain/agentchannel/__tests__/create-authorization.test.ts +58 -0
- package/src/domain/agentchannel/controller.ts +19 -0
- package/src/domain/agentchannel/steps.ts +40 -0
- package/src/domain/agentexecution/__tests__/create-execution-context-step.test.ts +216 -0
- package/src/domain/agentexecution/__tests__/create-steps.test.ts +70 -145
- package/src/domain/agentexecution/controller.ts +6 -10
- package/src/domain/agentexecution/create-execution-context-step.ts +271 -83
- package/src/domain/agentexecution/create-steps.ts +38 -138
- package/src/domain/agentexecution/run-target.ts +7 -3
- package/src/domain/agentinstance/__tests__/create-authorization.composed.test.ts +243 -0
- package/src/domain/agentinstance/__tests__/create-authorization.test.ts +144 -0
- package/src/domain/agentinstance/controller.ts +24 -5
- package/src/domain/agentinstance/steps.ts +82 -8
- package/src/domain/agentshare/__tests__/create-authorization.test.ts +86 -0
- package/src/domain/agentshare/__tests__/member-audience.test.ts +113 -0
- package/src/domain/agentshare/controller.ts +21 -0
- package/src/domain/agentshare/steps.ts +181 -15
- package/src/domain/artifact/controller.ts +18 -22
- package/src/domain/channelapp/controller.ts +10 -0
- package/src/domain/environment/__tests__/personal.test.ts +143 -0
- package/src/domain/environment/controller.ts +10 -0
- package/src/domain/environment/personal.ts +141 -0
- package/src/domain/executioncontext/__tests__/executioncontext.test.ts +1 -0
- package/src/domain/executioncontext/controller.ts +10 -0
- package/src/domain/executioncontext/steps.ts +2 -1
- package/src/domain/mcpserver/connect.ts +32 -92
- package/src/domain/mcpserver/controller.ts +13 -1
- package/src/domain/mcpserver/start-connect.ts +17 -11
- package/src/domain/memory/__tests__/create-authorization.test.ts +59 -0
- package/src/domain/memory/controller.ts +11 -0
- package/src/domain/memory/steps.ts +41 -0
- package/src/domain/oauthapp/controller.ts +10 -0
- package/src/domain/plugin/controller.ts +22 -36
- package/src/domain/plugin/overlay/sanitize.ts +2 -2
- package/src/domain/schedule/__tests__/create-authorization.test.ts +58 -0
- package/src/domain/schedule/controller.ts +20 -0
- package/src/domain/schedule/steps.ts +45 -2
- package/src/domain/session/__tests__/run-gate.test.ts +19 -52
- package/src/domain/session/__tests__/session.test.ts +27 -203
- package/src/domain/session/controller.ts +10 -30
- package/src/domain/session/run-target.ts +9 -8
- package/src/domain/session/steps.ts +33 -267
- package/src/domain/skill/controller.ts +24 -23
- package/src/domain/workflow/controller.ts +58 -145
- package/src/domain/workflow/version-resolution.ts +166 -0
- package/src/domain/workflowexecution/create-steps.ts +4 -3
- package/src/domain/workflowinstance/__tests__/create-authorization.test.ts +109 -0
- package/src/domain/workflowinstance/controller.ts +19 -0
- package/src/domain/workflowinstance/steps.ts +49 -1
- package/src/extensions/__tests__/composed-support.ts +38 -4
- package/src/extensions/__tests__/identity.test.ts +44 -1
- package/src/extensions/__tests__/list-read-scope-composed.test.ts +87 -1
- package/src/extensions/__tests__/list-read-scope.test.ts +63 -0
- package/src/extensions/identity.ts +23 -5
- package/src/extensions/list-read-scope.ts +47 -7
- package/src/pipeline/apiresource-labels.ts +2 -2
- package/src/pipeline/errors.ts +2 -2
- package/src/pipeline/steps/__tests__/authorize-resolved-target.test.ts +285 -0
- package/src/pipeline/steps/authorize-resolved-target.ts +129 -0
- package/src/pipeline/steps/authorize.ts +70 -13
- package/src/pipeline/steps/guard-memory-capture.ts +2 -1
- package/src/pipeline/steps/guard-reserved-labels.ts +12 -14
- package/src/pipeline/steps/helpers.ts +11 -15
- package/src/pipeline/steps/version-history.ts +34 -0
- package/dist/domain/agent/defaultagent.d.ts +0 -37
- package/dist/domain/agent/defaultagent.d.ts.map +0 -1
- package/dist/domain/agent/defaultagent.js +0 -104
- package/dist/domain/agent/defaultagent.js.map +0 -1
- package/src/domain/agent/defaultagent.ts +0 -135
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
import path from "node:path";
|
|
25
25
|
|
|
26
|
-
import type { DescService } from "@bufbuild/protobuf";
|
|
26
|
+
import type { DescMethod, DescService } from "@bufbuild/protobuf";
|
|
27
27
|
import type {
|
|
28
28
|
ConnectRouter,
|
|
29
29
|
Interceptor,
|
|
@@ -94,11 +94,30 @@ export function bearer(token: string): Interceptor {
|
|
|
94
94
|
export function servedServices(
|
|
95
95
|
routes: (router: ConnectRouter) => void,
|
|
96
96
|
): DescService[] {
|
|
97
|
-
|
|
97
|
+
return servedRegistrations(routes).map((r) => r.service);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** One `router.service(desc, implementation)` registration, as replayed. */
|
|
101
|
+
export interface ServedRegistration {
|
|
102
|
+
readonly service: DescService;
|
|
103
|
+
readonly implementation: Readonly<Record<string, unknown>>;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The registrations themselves, with each service's implementation map,
|
|
108
|
+
* for a reader that must know which of a service's methods are actually
|
|
109
|
+
* served: a partially implemented service (a method registered only when
|
|
110
|
+
* a composed capability carries it) lists the method on its descriptor
|
|
111
|
+
* and omits it from the map, and ConnectRPC answers UNIMPLEMENTED for it.
|
|
112
|
+
*/
|
|
113
|
+
export function servedRegistrations(
|
|
114
|
+
routes: (router: ConnectRouter) => void,
|
|
115
|
+
): ServedRegistration[] {
|
|
116
|
+
const served: ServedRegistration[] = [];
|
|
98
117
|
const recorder = {
|
|
99
118
|
handlers: [],
|
|
100
|
-
service(desc: DescService) {
|
|
101
|
-
served.push(desc);
|
|
119
|
+
service(desc: DescService, implementation: Record<string, unknown>) {
|
|
120
|
+
served.push({ service: desc, implementation });
|
|
102
121
|
return recorder;
|
|
103
122
|
},
|
|
104
123
|
rpc() {
|
|
@@ -109,6 +128,21 @@ export function servedServices(
|
|
|
109
128
|
return served;
|
|
110
129
|
}
|
|
111
130
|
|
|
131
|
+
/** The methods a composed server actually serves: on a registered service AND in its implementation map. */
|
|
132
|
+
export function servedMethods(
|
|
133
|
+
routes: (router: ConnectRouter) => void,
|
|
134
|
+
): DescMethod[] {
|
|
135
|
+
const methods: DescMethod[] = [];
|
|
136
|
+
for (const { service, implementation } of servedRegistrations(routes)) {
|
|
137
|
+
for (const method of service.methods) {
|
|
138
|
+
if (method.localName in implementation) {
|
|
139
|
+
methods.push(method);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return methods;
|
|
144
|
+
}
|
|
145
|
+
|
|
112
146
|
/** A gRPC transport to the composed server on `port`, presenting `token` when given. */
|
|
113
147
|
export function transportFor(port: number, token?: string): Transport {
|
|
114
148
|
return createGrpcTransport({
|
|
@@ -6,10 +6,20 @@
|
|
|
6
6
|
* `create` RPC's admission rule (2a A7) and the three IamPolicy system
|
|
7
7
|
* RPCs' (Q-OR-7). A wire `user`, `runner` or a composition's own class
|
|
8
8
|
* (guest) is never one.
|
|
9
|
+
*
|
|
10
|
+
* Also pins `isServerComposedRequest`, the narrower predicate the label
|
|
11
|
+
* guard, the memory-capture gate, the execution-context create check and
|
|
12
|
+
* the default-instance arm of the instance creates share: `internal` or an
|
|
13
|
+
* in-process origin, and never a wire `machine` account — the one row
|
|
14
|
+
* where the two predicates part, pinned so a step trusting server-composed
|
|
15
|
+
* state can never be widened to the wire by picking the wrong one.
|
|
9
16
|
*/
|
|
10
17
|
import { describe, expect, it } from "vitest";
|
|
11
18
|
|
|
12
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
isPlatformPipelineCaller,
|
|
21
|
+
isServerComposedRequest,
|
|
22
|
+
} from "../identity.js";
|
|
13
23
|
import type { CallerIdentity } from "../identity.js";
|
|
14
24
|
|
|
15
25
|
function caller(fields: Partial<CallerIdentity>): CallerIdentity {
|
|
@@ -60,3 +70,36 @@ describe("isPlatformPipelineCaller", () => {
|
|
|
60
70
|
);
|
|
61
71
|
});
|
|
62
72
|
});
|
|
73
|
+
|
|
74
|
+
describe("isServerComposedRequest", () => {
|
|
75
|
+
it("is true for the internal class and for any identity that entered in-process", () => {
|
|
76
|
+
expect(isServerComposedRequest(caller({ callerClass: "internal" }))).toBe(
|
|
77
|
+
true,
|
|
78
|
+
);
|
|
79
|
+
expect(
|
|
80
|
+
isServerComposedRequest(
|
|
81
|
+
caller({ callerClass: "user", origin: "in-process" }),
|
|
82
|
+
),
|
|
83
|
+
).toBe(true);
|
|
84
|
+
expect(
|
|
85
|
+
isServerComposedRequest(
|
|
86
|
+
caller({ callerClass: "channel", origin: "in-process" }),
|
|
87
|
+
),
|
|
88
|
+
).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("is false for every wire caller, the machine class included — the row where it parts from isPlatformPipelineCaller", () => {
|
|
92
|
+
const wireMachine = caller({ callerClass: "machine" });
|
|
93
|
+
expect(isServerComposedRequest(wireMachine)).toBe(false);
|
|
94
|
+
expect(isPlatformPipelineCaller(wireMachine)).toBe(true);
|
|
95
|
+
expect(isServerComposedRequest(caller({ callerClass: "user" }))).toBe(
|
|
96
|
+
false,
|
|
97
|
+
);
|
|
98
|
+
expect(
|
|
99
|
+
isServerComposedRequest(caller({ callerClass: "user", origin: "wire" })),
|
|
100
|
+
).toBe(false);
|
|
101
|
+
expect(isServerComposedRequest(caller({ callerClass: "runner" }))).toBe(
|
|
102
|
+
false,
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
@@ -12,7 +12,14 @@
|
|
|
12
12
|
* - search (enumeration verb feeding the engine allowlist, and the
|
|
13
13
|
* crossOrgPublic bypass where FGA is never consulted — lane 21),
|
|
14
14
|
* - the OUTAGE arm: a throwing scope answers the sanitized INTERNAL,
|
|
15
|
-
* never an empty (or full!) list
|
|
15
|
+
* never an empty (or full!) list;
|
|
16
|
+
* - the SERVER'S OWN reads over `inProcessTransport` (stigmer#1207):
|
|
17
|
+
* the bare in-process call is stamped `internal` and the helper
|
|
18
|
+
* answers it before the scope — agentexecution.listBySession (the
|
|
19
|
+
* lane every server-internal reader of a session's turns rides) and
|
|
20
|
+
* workflowexecution.list with the org arm still applied; a caller
|
|
21
|
+
* PROPAGATED through the in-process header keeps its class and is
|
|
22
|
+
* narrowed exactly like the wire.
|
|
16
23
|
*
|
|
17
24
|
* Per-lane logic beyond the wiring is pinned in the helper matrix
|
|
18
25
|
* (list-read-scope.test.ts), the summaries suite, and the store-contract
|
|
@@ -31,6 +38,8 @@ import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
|
|
31
38
|
|
|
32
39
|
import { ActivityQueryController } from "@stigmer/protos/ai/stigmer/activity/v1/query_pb";
|
|
33
40
|
import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
|
|
41
|
+
import { AgentExecutionSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
42
|
+
import { AgentExecutionQueryController } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/query_pb";
|
|
34
43
|
import { SessionSchema } from "@stigmer/protos/ai/stigmer/agentic/session/v1/api_pb";
|
|
35
44
|
import { SessionQueryController } from "@stigmer/protos/ai/stigmer/agentic/session/v1/query_pb";
|
|
36
45
|
import { WorkflowExecutionSchema } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/api_pb";
|
|
@@ -44,6 +53,11 @@ import { loadConfig } from "../../boot/config.js";
|
|
|
44
53
|
import { composeServer } from "../../boot/compose.js";
|
|
45
54
|
import type { ComposedServer } from "../../boot/compose.js";
|
|
46
55
|
import { createLogger } from "../../boot/logger.js";
|
|
56
|
+
import { testCallerIdentity } from "../../pipeline/__tests__/support.js";
|
|
57
|
+
import {
|
|
58
|
+
IN_PROCESS_CALLER_HEADER,
|
|
59
|
+
encodeInProcessCaller,
|
|
60
|
+
} from "../../pipeline/interceptors/auth.js";
|
|
47
61
|
import type { ListReadScope } from "../list-read-scope.js";
|
|
48
62
|
import type { ServerExtension } from "../registry.js";
|
|
49
63
|
|
|
@@ -133,6 +147,27 @@ describe("list read scope (composed server, fake scope)", () => {
|
|
|
133
147
|
}),
|
|
134
148
|
);
|
|
135
149
|
}
|
|
150
|
+
// Two turns of one session — the rows a server-internal reader of a
|
|
151
|
+
// conversation's executions asks listBySession for.
|
|
152
|
+
for (const id of ["aex_turn_1", "aex_turn_2"]) {
|
|
153
|
+
await server.store.saveResource(
|
|
154
|
+
ApiResourceKind.agent_execution,
|
|
155
|
+
id,
|
|
156
|
+
AgentExecutionSchema,
|
|
157
|
+
create(AgentExecutionSchema, {
|
|
158
|
+
apiVersion: "agentic.stigmer.ai/v1",
|
|
159
|
+
kind: "AgentExecution",
|
|
160
|
+
metadata: { id, name: id, org: "acme" },
|
|
161
|
+
spec: { sessionId: "ses_mine" },
|
|
162
|
+
status: {
|
|
163
|
+
audit: {
|
|
164
|
+
specAudit: { createdAt: { seconds: 1_700_000_000n } },
|
|
165
|
+
statusAudit: { updatedAt: { seconds: 1_700_000_000n } },
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
}),
|
|
169
|
+
);
|
|
170
|
+
}
|
|
136
171
|
for (const [id, org] of [
|
|
137
172
|
["wfe_mine", "acme"],
|
|
138
173
|
["wfe_other_org", "rival"],
|
|
@@ -273,4 +308,55 @@ describe("list read scope (composed server, fake scope)", () => {
|
|
|
273
308
|
const list = await query.list({});
|
|
274
309
|
expect(list.entries).toEqual([]);
|
|
275
310
|
});
|
|
311
|
+
|
|
312
|
+
describe("the server's own reads over the in-process transport (stigmer#1207)", () => {
|
|
313
|
+
it("agentexecution.listBySession, bare: every turn of the session, the scope never asked", async () => {
|
|
314
|
+
// The scope would hide everything if it were asked.
|
|
315
|
+
allowed = new Set();
|
|
316
|
+
const query = createClient(
|
|
317
|
+
AgentExecutionQueryController,
|
|
318
|
+
server.inProcessTransport,
|
|
319
|
+
);
|
|
320
|
+
const turns = await query.listBySession({ sessionId: "ses_mine" });
|
|
321
|
+
expect(turns.entries.map((e) => e.metadata?.id).sort()).toEqual([
|
|
322
|
+
"aex_turn_1",
|
|
323
|
+
"aex_turn_2",
|
|
324
|
+
]);
|
|
325
|
+
expect(seenKinds).toEqual([]);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it("a caller propagated through the in-process header keeps its class and is narrowed like the wire", async () => {
|
|
329
|
+
allowed = new Set(["aex_turn_2"]);
|
|
330
|
+
const query = createClient(
|
|
331
|
+
AgentExecutionQueryController,
|
|
332
|
+
server.inProcessTransport,
|
|
333
|
+
);
|
|
334
|
+
const turns = await query.listBySession(
|
|
335
|
+
{ sessionId: "ses_mine" },
|
|
336
|
+
{
|
|
337
|
+
headers: {
|
|
338
|
+
[IN_PROCESS_CALLER_HEADER]: encodeInProcessCaller(
|
|
339
|
+
testCallerIdentity({ identityId: "alice" }),
|
|
340
|
+
),
|
|
341
|
+
},
|
|
342
|
+
},
|
|
343
|
+
);
|
|
344
|
+
expect(turns.entries.map((e) => e.metadata?.id)).toEqual(["aex_turn_2"]);
|
|
345
|
+
expect(seenKinds).toEqual([ApiResourceKind.agent_execution]);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it("workflowexecution.list, bare: the request's org predicate still applies, the scope never asked", async () => {
|
|
349
|
+
allowed = new Set();
|
|
350
|
+
const query = createClient(
|
|
351
|
+
WorkflowExecutionQueryController,
|
|
352
|
+
server.inProcessTransport,
|
|
353
|
+
);
|
|
354
|
+
const acme = await query.list({ org: "acme" });
|
|
355
|
+
expect(acme.entries.map((e) => e.metadata?.id).sort()).toEqual([
|
|
356
|
+
"wfe_foreign",
|
|
357
|
+
"wfe_mine",
|
|
358
|
+
]);
|
|
359
|
+
expect(seenKinds).toEqual([]);
|
|
360
|
+
});
|
|
361
|
+
});
|
|
276
362
|
});
|
|
@@ -12,6 +12,11 @@
|
|
|
12
12
|
* 20260913.04 T02 the scope is the last per-row predicate, so it is
|
|
13
13
|
* offered the org's rows and never the kind's — the same result set,
|
|
14
14
|
* a fraction of the candidates);
|
|
15
|
+
* - the `internal` class — the server acting as itself — gets the
|
|
16
|
+
* org-narrowed rows and the scope is never asked (the Authorize
|
|
17
|
+
* step's trust-domain rule on a list answer, stigmer#1207); the rule
|
|
18
|
+
* is class-only, so a caller PROPAGATED through the in-process
|
|
19
|
+
* transport (its own class, origin in-process) is still narrowed;
|
|
15
20
|
* - the candidates carry {id, org, labels} — the driver's guest
|
|
16
21
|
* cookie rule keys on labels — and the row's AUTHORIZATION FACTS
|
|
17
22
|
* (creator stamp, visibility level, every parent link), read by the
|
|
@@ -160,6 +165,64 @@ describe("restrictListByReadScope", () => {
|
|
|
160
165
|
]);
|
|
161
166
|
});
|
|
162
167
|
|
|
168
|
+
describe("the internal class — the server acting as itself (stigmer#1207)", () => {
|
|
169
|
+
/** A scope that must never be reached: any call is the failure under test. */
|
|
170
|
+
const neverAsked: ListReadScope = {
|
|
171
|
+
authorizedResourceIds: () =>
|
|
172
|
+
Promise.reject(new Error("the scope was asked for the internal class")),
|
|
173
|
+
restrictListEntries: () =>
|
|
174
|
+
Promise.reject(new Error("the scope was asked for the internal class")),
|
|
175
|
+
};
|
|
176
|
+
const internal = testCallerIdentity({
|
|
177
|
+
identityId: "system",
|
|
178
|
+
callerClass: "internal",
|
|
179
|
+
origin: "in-process",
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("gets the org-narrowed rows in the lane's order; the scope is never asked", async () => {
|
|
183
|
+
const withOrg = await restrictListByReadScope(
|
|
184
|
+
neverAsked,
|
|
185
|
+
internal,
|
|
186
|
+
ApiResourceKind.session,
|
|
187
|
+
rows,
|
|
188
|
+
"acme",
|
|
189
|
+
);
|
|
190
|
+
// The request's org predicate still applies — it is the request's,
|
|
191
|
+
// not the caller's; only the caller predicate is skipped.
|
|
192
|
+
expect(withOrg.map((r) => r.metadata.id)).toEqual(["ses_a", "ses_b"]);
|
|
193
|
+
const blankOrg = await restrictListByReadScope(
|
|
194
|
+
neverAsked,
|
|
195
|
+
internal,
|
|
196
|
+
ApiResourceKind.session,
|
|
197
|
+
rows,
|
|
198
|
+
"",
|
|
199
|
+
);
|
|
200
|
+
expect(blankOrg.map((r) => r.metadata.id)).toEqual([
|
|
201
|
+
"ses_a",
|
|
202
|
+
"ses_b",
|
|
203
|
+
"ses_c",
|
|
204
|
+
]);
|
|
205
|
+
// A fresh array either way, as every arm of the helper returns.
|
|
206
|
+
expect(blankOrg).not.toBe(rows);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("the rule is class-only: a caller propagated through the in-process transport keeps its class and is still narrowed", async () => {
|
|
210
|
+
const propagated = testCallerIdentity({
|
|
211
|
+
identityId: "alice",
|
|
212
|
+
callerClass: "user",
|
|
213
|
+
origin: "in-process",
|
|
214
|
+
});
|
|
215
|
+
const kept = await restrictListByReadScope(
|
|
216
|
+
scopeKeeping(["ses_c"]),
|
|
217
|
+
propagated,
|
|
218
|
+
ApiResourceKind.session,
|
|
219
|
+
rows,
|
|
220
|
+
"",
|
|
221
|
+
);
|
|
222
|
+
expect(kept.map((r) => r.metadata.id)).toEqual(["ses_c"]);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
163
226
|
it("an empty kept set is a real answer — the empty list", async () => {
|
|
164
227
|
const kept = await restrictListByReadScope(
|
|
165
228
|
scopeKeeping([]),
|
|
@@ -112,9 +112,27 @@ export interface IdentityVerifier {
|
|
|
112
112
|
* rule is load-bearing in open source.
|
|
113
113
|
*/
|
|
114
114
|
export function isPlatformPipelineCaller(caller: CallerIdentity): boolean {
|
|
115
|
-
return (
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
115
|
+
return caller.callerClass === "machine" || isServerComposedRequest(caller);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Whether the request `caller` arrived on was composed by the server's own
|
|
120
|
+
* code rather than presented on the wire: the `internal` class (the server
|
|
121
|
+
* acting as itself) or ANY identity that entered through the in-process
|
|
122
|
+
* transport (a propagated person the server is building a request for,
|
|
123
|
+
* such as the default-instance self-heal that creates as the run's human
|
|
124
|
+
* for attribution). The serving chassis strips the propagation header, so
|
|
125
|
+
* the wire cannot claim this.
|
|
126
|
+
*
|
|
127
|
+
* This is the trust arm the label guard, the memory-capture gate and the
|
|
128
|
+
* execution-context create check share: what a server-composed request
|
|
129
|
+
* carries was decided by the service code that built it, and the
|
|
130
|
+
* entry-point request already passed its own gate. It is NARROWER than
|
|
131
|
+
* `isPlatformPipelineCaller`: a wire `machine` account is one of the
|
|
132
|
+
* platform's pipelines but its request is still the wire's, so a step
|
|
133
|
+
* that trusts server-composed state (a reserved label, a default
|
|
134
|
+
* instance's provenance) must key on this predicate and never on that one.
|
|
135
|
+
*/
|
|
136
|
+
export function isServerComposedRequest(caller: CallerIdentity): boolean {
|
|
137
|
+
return caller.callerClass === "internal" || caller.origin === "in-process";
|
|
120
138
|
}
|
|
@@ -48,11 +48,27 @@
|
|
|
48
48
|
* authorization-backend outage surfaces as the pipeline's sanitized
|
|
49
49
|
* INTERNAL, exactly the Java baseline (DD-007: unavailable is never
|
|
50
50
|
* softened).
|
|
51
|
-
* -
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
51
|
+
* - THE `internal` CLASS IS ANSWERED HERE, NEVER OFFERED TO
|
|
52
|
+
* `restrictListEntries`. The server acting as itself over the
|
|
53
|
+
* in-process transport (boot/inprocess.ts mints the class; nothing
|
|
54
|
+
* else can) is the server's own trust domain, on list lanes exactly
|
|
55
|
+
* as on the Authorize step, which returns for the class before any
|
|
56
|
+
* Authorizer is asked (pipeline/steps/authorize.ts). The helper
|
|
57
|
+
* below returns the org-narrowed rows for that class before the
|
|
58
|
+
* scope is consulted. The rule is class-only, like the step's: a
|
|
59
|
+
* caller PROPAGATED through the in-process header keeps its own
|
|
60
|
+
* class and stays scoped. Why here and not in a driver: a driver
|
|
61
|
+
* answering "every offered id" for the class must be written once
|
|
62
|
+
* per edition and is a silent short list the day one edition
|
|
63
|
+
* forgets (stigmer#1207: a composed driver scoped the class and
|
|
64
|
+
* every server-internal list read came back empty — the
|
|
65
|
+
* personal-environment reads at execution-context creation and MCP
|
|
66
|
+
* connect in this repository, and a composition's own in-process
|
|
67
|
+
* readers of a session's executions).
|
|
68
|
+
* The enumeration verb is a wire caller's verb: no in-process edge
|
|
69
|
+
* reaches `authorizedResourceIds`, and only a scanning driver can
|
|
70
|
+
* say "all", so that verb's internal arm stays the driver's and is
|
|
71
|
+
* stated in its doc below.
|
|
56
72
|
* - THE SCOPE IS THE LAST PER-ROW PREDICATE IN A LANE (stigmer-cloud
|
|
57
73
|
* 20260913.04 T02, Q-LB-11). Every request predicate a lane applies
|
|
58
74
|
* — the request's org, a phase, labels, a parent id, filter criteria
|
|
@@ -157,6 +173,13 @@ export interface ListReadScope {
|
|
|
157
173
|
* answer — consumers map it to their lane's empty shape without
|
|
158
174
|
* touching the store. A scope may throw on a kind it was never ruled
|
|
159
175
|
* to serve (a consumer bug by contract).
|
|
176
|
+
*
|
|
177
|
+
* The `internal` class CAN reach this verb (no helper stands between a
|
|
178
|
+
* consumer and it), and the driver owns that arm: a scanning driver
|
|
179
|
+
* answers every id of the kind, an engine-backed one answers what its
|
|
180
|
+
* engine holds for the class. No in-process edge reaches an
|
|
181
|
+
* enumeration lane today, so the two answers have never met a caller;
|
|
182
|
+
* a consumer that adds one reads this line first.
|
|
160
183
|
*/
|
|
161
184
|
authorizedResourceIds(
|
|
162
185
|
caller: CallerIdentity,
|
|
@@ -168,6 +191,12 @@ export interface ListReadScope {
|
|
|
168
191
|
* baseline: the authorized-ids intersection plus the guest
|
|
169
192
|
* cookie-label rule where the lane carries it). The result must be a
|
|
170
193
|
* subset of the offered ids — a scope only narrows.
|
|
194
|
+
*
|
|
195
|
+
* `caller` is never the `internal` class: `restrictListByReadScope`,
|
|
196
|
+
* the one consumption idiom, answers that class before any driver is
|
|
197
|
+
* asked (the header's contract line). A driver that is offered it has
|
|
198
|
+
* been reached around the idiom and should refuse loudly rather than
|
|
199
|
+
* evaluate the server as a person (the built-in driver does).
|
|
171
200
|
*/
|
|
172
201
|
restrictListEntries(
|
|
173
202
|
caller: CallerIdentity,
|
|
@@ -209,8 +238,13 @@ export interface ScopedListResource {
|
|
|
209
238
|
* permission-bounded across orgs, verified per lane in the entry's
|
|
210
239
|
* census — lanes Java does not org-narrow pass ""), then to the kept
|
|
211
240
|
* ids. The same set either way (both are per-row predicates); the
|
|
212
|
-
* order is the header's
|
|
213
|
-
*
|
|
241
|
+
* order is the header's contract line: the scope sees the org's
|
|
242
|
+
* rows, not the kind's;
|
|
243
|
+
* - scope composed, caller `internal` → the org-narrowed rows, the
|
|
244
|
+
* scope never asked and no candidate built (the header's contract
|
|
245
|
+
* line). The org predicate is the REQUEST's, not the caller's, so
|
|
246
|
+
* the server's own read of a cross-org lane still honours the org
|
|
247
|
+
* it asked for; only the caller predicate is skipped;
|
|
214
248
|
* - every candidate carries the row's authorization facts, and the
|
|
215
249
|
* candidates of a kind whose authorization is its parent's name that
|
|
216
250
|
* parent again as `authorizationParent` — one of the facts' parent
|
|
@@ -231,6 +265,12 @@ export async function restrictListByReadScope<T extends ScopedListResource>(
|
|
|
231
265
|
requestOrg === ""
|
|
232
266
|
? resources
|
|
233
267
|
: resources.filter((resource) => resource.metadata?.org === requestOrg);
|
|
268
|
+
// The server acting as itself: the trust-domain rule of the Authorize
|
|
269
|
+
// step, applied to a list answer — the caller predicate is skipped, the
|
|
270
|
+
// request's org predicate above is not (the header's contract line).
|
|
271
|
+
if (caller.callerClass === "internal") {
|
|
272
|
+
return [...offered];
|
|
273
|
+
}
|
|
234
274
|
const parentOf = authorizationParentReader(kind);
|
|
235
275
|
const entries: ListEntryMeta[] = offered.map((resource) => {
|
|
236
276
|
const entry: ListEntryMeta = {
|
|
@@ -23,8 +23,8 @@ import type { ApiResourceMetadata } from "@stigmer/protos/ai/stigmer/commons/api
|
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* The platform-reserved label key namespace. Keys under this prefix carry
|
|
26
|
-
* platform semantics (
|
|
27
|
-
*
|
|
26
|
+
* platform semantics (personal-environment uniqueness, default-instance
|
|
27
|
+
* marking, plugin membership and lineage) and are written by the server —
|
|
28
28
|
* never introduced by ordinary client requests on the cloud edition.
|
|
29
29
|
*/
|
|
30
30
|
export const RESERVED_LABEL_PREFIX = "stigmer.ai/";
|
package/src/pipeline/errors.ts
CHANGED
|
@@ -100,8 +100,8 @@ export function grpcCodeName(code: Code): string {
|
|
|
100
100
|
* errors.As branch keeps the INNER code but rewrites the message to the
|
|
101
101
|
* full wrapped text — and the inner client error's Error() renders as
|
|
102
102
|
* `rpc error: code = <CodeName> desc = <message>`. Byte-parity with Go on
|
|
103
|
-
* the
|
|
104
|
-
*
|
|
103
|
+
* the arms that hit this (agent create's CreateDefaultInstance, agent
|
|
104
|
+
* execution create's default-instance and session bootstrap). The transport-formatting
|
|
105
105
|
* leak is filed as stigmer/stigmer#852 for a both-editions post-cutover
|
|
106
106
|
* fix; until then this shim IS the wire contract.
|
|
107
107
|
*
|