@stigmer/server 3.15.1 → 3.15.2-dev.20260913223433
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/domain/agentchannel/controller.js +15 -26
- package/dist/domain/agentchannel/controller.js.map +1 -1
- package/dist/domain/agentexecution/controller.d.ts.map +1 -1
- package/dist/domain/agentexecution/controller.js +3 -2
- package/dist/domain/agentexecution/controller.js.map +1 -1
- package/dist/domain/agentexecution/steps.d.ts +20 -8
- package/dist/domain/agentexecution/steps.d.ts.map +1 -1
- package/dist/domain/agentexecution/steps.js +32 -12
- package/dist/domain/agentexecution/steps.js.map +1 -1
- package/dist/domain/agentinstance/steps.d.ts.map +1 -1
- package/dist/domain/agentinstance/steps.js +9 -22
- package/dist/domain/agentinstance/steps.js.map +1 -1
- package/dist/domain/agentshare/controller.js +15 -26
- package/dist/domain/agentshare/controller.js.map +1 -1
- package/dist/domain/environment/controller.js +8 -10
- package/dist/domain/environment/controller.js.map +1 -1
- package/dist/domain/memory/steps.d.ts.map +1 -1
- package/dist/domain/memory/steps.js +5 -10
- package/dist/domain/memory/steps.js.map +1 -1
- package/dist/domain/schedule/controller.js +15 -24
- package/dist/domain/schedule/controller.js.map +1 -1
- package/dist/domain/session/steps.d.ts.map +1 -1
- package/dist/domain/session/steps.js +8 -4
- package/dist/domain/session/steps.js.map +1 -1
- package/dist/domain/workflowexecution/controller.d.ts.map +1 -1
- package/dist/domain/workflowexecution/controller.js +12 -6
- package/dist/domain/workflowexecution/controller.js.map +1 -1
- package/dist/domain/workflowexecution/list-pending-approvals.d.ts.map +1 -1
- package/dist/domain/workflowexecution/list-pending-approvals.js +19 -8
- package/dist/domain/workflowexecution/list-pending-approvals.js.map +1 -1
- package/dist/domain/workflowinstance/controller.js +6 -13
- package/dist/domain/workflowinstance/controller.js.map +1 -1
- package/dist/extensions/list-read-scope.d.ts +31 -4
- package/dist/extensions/list-read-scope.d.ts.map +1 -1
- package/dist/extensions/list-read-scope.js +12 -10
- package/dist/extensions/list-read-scope.js.map +1 -1
- package/package.json +4 -4
- package/src/domain/agentchannel/controller.ts +23 -31
- package/src/domain/agentexecution/controller.ts +11 -5
- package/src/domain/agentexecution/steps.ts +47 -22
- package/src/domain/agentinstance/steps.ts +17 -26
- package/src/domain/agentshare/controller.ts +23 -31
- package/src/domain/environment/controller.ts +12 -12
- package/src/domain/memory/steps.ts +6 -12
- package/src/domain/schedule/controller.ts +25 -33
- package/src/domain/session/steps.ts +24 -11
- package/src/domain/workflowexecution/controller.ts +32 -26
- package/src/domain/workflowexecution/list-pending-approvals.ts +37 -16
- package/src/domain/workflowinstance/controller.ts +11 -18
- package/src/extensions/__tests__/list-read-scope.test.ts +29 -7
- package/src/extensions/list-read-scope.ts +37 -12
|
@@ -748,9 +748,7 @@ async function getByWorkflow(
|
|
|
748
748
|
),
|
|
749
749
|
)
|
|
750
750
|
.addStep(newValidateProtoStep())
|
|
751
|
-
.addStep(
|
|
752
|
-
newLoadByWorkflowStep(deps.store, deps.logger, deps.listReadScope),
|
|
753
|
-
)
|
|
751
|
+
.addStep(newLoadByWorkflowStep(deps.store, deps.logger, deps.listReadScope))
|
|
754
752
|
.build()
|
|
755
753
|
.execute(reqCtx);
|
|
756
754
|
|
|
@@ -794,27 +792,22 @@ function newLoadByWorkflowStep(
|
|
|
794
792
|
continue;
|
|
795
793
|
}
|
|
796
794
|
}
|
|
797
|
-
// 20260830.01 census lane 20: the
|
|
798
|
-
//
|
|
799
|
-
|
|
795
|
+
// 20260830.01 census lane 20: the org/workflow filters are contract
|
|
796
|
+
// parity in both editions and run FIRST; the scope narrows the
|
|
797
|
+
// workflow's instances last (the scope is the last per-row
|
|
798
|
+
// predicate, stigmer-cloud 20260913.04 T02).
|
|
799
|
+
const filtered = await restrictListByReadScope(
|
|
800
800
|
listReadScope,
|
|
801
801
|
ctx.callerIdentity,
|
|
802
802
|
ApiResourceKind.workflow_instance,
|
|
803
|
-
decoded
|
|
803
|
+
decoded.filter(
|
|
804
|
+
(instance) =>
|
|
805
|
+
instance.spec?.workflowId === workflowId &&
|
|
806
|
+
(request.org === "" || instance.metadata?.org === request.org),
|
|
807
|
+
),
|
|
804
808
|
"",
|
|
805
809
|
);
|
|
806
810
|
|
|
807
|
-
const filtered: WorkflowInstance[] = [];
|
|
808
|
-
for (const instance of visible) {
|
|
809
|
-
if (instance.spec?.workflowId !== workflowId) {
|
|
810
|
-
continue;
|
|
811
|
-
}
|
|
812
|
-
if (request.org !== "" && instance.metadata?.org !== request.org) {
|
|
813
|
-
continue;
|
|
814
|
-
}
|
|
815
|
-
filtered.push(instance);
|
|
816
|
-
}
|
|
817
|
-
|
|
818
811
|
logger.info("found workflow instances for workflow", {
|
|
819
812
|
workflowId,
|
|
820
813
|
count: filtered.length,
|
|
@@ -7,8 +7,11 @@
|
|
|
7
7
|
* byte-identity arm the four local conformance rosters also pin);
|
|
8
8
|
* - a composed scope narrows to the kept ids — and can only narrow:
|
|
9
9
|
* ids the scope answers that were never offered add nothing;
|
|
10
|
-
* - the org argument narrows
|
|
11
|
-
* (the Java repos' uniform blank-org posture
|
|
10
|
+
* - the org argument narrows BEFORE the scope, only when non-blank
|
|
11
|
+
* (the Java repos' uniform blank-org posture; since stigmer-cloud
|
|
12
|
+
* 20260913.04 T02 the scope is the last per-row predicate, so it is
|
|
13
|
+
* offered the org's rows and never the kind's — the same result set,
|
|
14
|
+
* a fraction of the candidates);
|
|
12
15
|
* - the candidates carry {id, org, labels} — the driver's guest
|
|
13
16
|
* cookie rule keys on labels;
|
|
14
17
|
* - a scope failure PROPAGATES — never an empty result (the outage
|
|
@@ -76,24 +79,39 @@ describe("restrictListByReadScope", () => {
|
|
|
76
79
|
expect(kept.map((r) => r.metadata.id)).toEqual(["ses_b"]);
|
|
77
80
|
});
|
|
78
81
|
|
|
79
|
-
it("a non-blank org narrows
|
|
80
|
-
const
|
|
82
|
+
it("a non-blank org narrows BEFORE the scope: the scope is offered the org's rows only; blank org offers them all", async () => {
|
|
83
|
+
const offered: ReadonlyArray<ListEntryMeta>[] = [];
|
|
84
|
+
const recording: ListReadScope = {
|
|
85
|
+
authorizedResourceIds: () => Promise.resolve(new Set<string>()),
|
|
86
|
+
restrictListEntries: (_caller, _kind, entries) => {
|
|
87
|
+
offered.push(entries);
|
|
88
|
+
return Promise.resolve(new Set(["ses_a", "ses_c"]));
|
|
89
|
+
},
|
|
90
|
+
};
|
|
81
91
|
const withOrg = await restrictListByReadScope(
|
|
82
|
-
|
|
92
|
+
recording,
|
|
83
93
|
caller,
|
|
84
94
|
ApiResourceKind.session,
|
|
85
95
|
rows,
|
|
86
96
|
"acme",
|
|
87
97
|
);
|
|
88
98
|
expect(withOrg.map((r) => r.metadata.id)).toEqual(["ses_a"]);
|
|
99
|
+
// The rival's row was never a candidate: the engine is not asked
|
|
100
|
+
// about rows the request's own predicate excludes.
|
|
101
|
+
expect(offered[0]!.map((entry) => entry.id)).toEqual(["ses_a", "ses_b"]);
|
|
89
102
|
const blankOrg = await restrictListByReadScope(
|
|
90
|
-
|
|
103
|
+
recording,
|
|
91
104
|
caller,
|
|
92
105
|
ApiResourceKind.session,
|
|
93
106
|
rows,
|
|
94
107
|
"",
|
|
95
108
|
);
|
|
96
109
|
expect(blankOrg.map((r) => r.metadata.id)).toEqual(["ses_a", "ses_c"]);
|
|
110
|
+
expect(offered[1]!.map((entry) => entry.id)).toEqual([
|
|
111
|
+
"ses_a",
|
|
112
|
+
"ses_b",
|
|
113
|
+
"ses_c",
|
|
114
|
+
]);
|
|
97
115
|
});
|
|
98
116
|
|
|
99
117
|
it("an empty kept set is a real answer — the empty list", async () => {
|
|
@@ -127,7 +145,11 @@ describe("restrictListByReadScope", () => {
|
|
|
127
145
|
);
|
|
128
146
|
expect(seenKind).toBe(ApiResourceKind.session);
|
|
129
147
|
expect(seen).toEqual([
|
|
130
|
-
{
|
|
148
|
+
{
|
|
149
|
+
id: "ses_a",
|
|
150
|
+
org: "acme",
|
|
151
|
+
labels: { "stigmer.ai/guest-cookie-id": "ck_1" },
|
|
152
|
+
},
|
|
131
153
|
{ id: "ses_b", org: "acme", labels: {} },
|
|
132
154
|
{ id: "ses_c", org: "rival", labels: {} },
|
|
133
155
|
]);
|
|
@@ -47,6 +47,30 @@
|
|
|
47
47
|
* semantics (the Java baseline propagates the original caller
|
|
48
48
|
* through in-process calls, so a composed driver normally never
|
|
49
49
|
* sees `internal` on these lanes).
|
|
50
|
+
* - THE SCOPE IS THE LAST PER-ROW PREDICATE IN A LANE (stigmer-cloud
|
|
51
|
+
* 20260913.04 T02, Q-LB-11). Every request predicate a lane applies
|
|
52
|
+
* — the request's org, a phase, labels, a parent id, filter criteria
|
|
53
|
+
* — is a pure function of one row and the request; the scope is a
|
|
54
|
+
* pure function of one row and the caller; an intersection of
|
|
55
|
+
* per-row predicates commutes, so the order is free and the cheap
|
|
56
|
+
* ones go first: a lane offers the scope ONLY the rows its own
|
|
57
|
+
* predicates keep. Sort, page and aggregate are not per-row and stay
|
|
58
|
+
* after the scope, where they were. Why it matters: a composed
|
|
59
|
+
* driver's cost is proportional to the candidates offered (the cloud
|
|
60
|
+
* checks each one against its authorization engine), and the whole
|
|
61
|
+
* kind across every tenant is what rolled back on 2026-09-14; the
|
|
62
|
+
* request's org is tens of rows. Two idioms carry the rule and both
|
|
63
|
+
* keep their reason: a lane whose Go/Java contract org-narrows in
|
|
64
|
+
* BOTH editions (environment, memory, schedule, agent-instance,
|
|
65
|
+
* agent-share and agent-channel lists; the getBy* parent filters)
|
|
66
|
+
* applies its own filter ABOVE the helper call and passes `""`; a lane
|
|
67
|
+
* whose contract is cross-org on OSS (`agentExecution.list`,
|
|
68
|
+
* `workflowExecution.list`, `listPendingApprovals`) hands the request
|
|
69
|
+
* org to the helper, which applies it before the scope ONLY when a
|
|
70
|
+
* scope is composed — a scope-less server still returns the input
|
|
71
|
+
* untouched with the org unconsulted (the first line above). Two lanes
|
|
72
|
+
* have no org on the wire (`session.list`, `apiKey.findAll`) and offer
|
|
73
|
+
* the kind until P1 entry 12 pages them.
|
|
50
74
|
*/
|
|
51
75
|
import type { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
52
76
|
|
|
@@ -112,10 +136,13 @@ export interface ScopedListResource {
|
|
|
112
136
|
* `requestOrg` deliberately not consulted — the OSS single-tenant
|
|
113
137
|
* posture treats the org field as a no-op, and new filtering on a
|
|
114
138
|
* scope-less server would be a silent wire change);
|
|
115
|
-
* - scope composed → entries narrowed to
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
139
|
+
* - scope composed → entries narrowed to `requestOrg` when non-blank
|
|
140
|
+
* FIRST (the Java repos' uniform posture: blank org =
|
|
141
|
+
* permission-bounded across orgs, verified per lane in the entry's
|
|
142
|
+
* census — lanes Java does not org-narrow pass ""), then to the kept
|
|
143
|
+
* ids. The same set either way (both are per-row predicates); the
|
|
144
|
+
* order is the header's last contract line: the scope sees the org's
|
|
145
|
+
* rows, not the kind's.
|
|
119
146
|
*/
|
|
120
147
|
export async function restrictListByReadScope<T extends ScopedListResource>(
|
|
121
148
|
scope: ListReadScope | undefined,
|
|
@@ -127,17 +154,15 @@ export async function restrictListByReadScope<T extends ScopedListResource>(
|
|
|
127
154
|
if (scope === undefined) {
|
|
128
155
|
return [...resources];
|
|
129
156
|
}
|
|
130
|
-
const
|
|
157
|
+
const offered =
|
|
158
|
+
requestOrg === ""
|
|
159
|
+
? resources
|
|
160
|
+
: resources.filter((resource) => resource.metadata?.org === requestOrg);
|
|
161
|
+
const entries: ListEntryMeta[] = offered.map((resource) => ({
|
|
131
162
|
id: resource.metadata?.id ?? "",
|
|
132
163
|
org: resource.metadata?.org ?? "",
|
|
133
164
|
labels: resource.metadata?.labels ?? {},
|
|
134
165
|
}));
|
|
135
166
|
const keptIds = await scope.restrictListEntries(caller, kind, entries);
|
|
136
|
-
|
|
137
|
-
keptIds.has(resource.metadata?.id ?? ""),
|
|
138
|
-
);
|
|
139
|
-
if (requestOrg !== "") {
|
|
140
|
-
kept = kept.filter((resource) => resource.metadata?.org === requestOrg);
|
|
141
|
-
}
|
|
142
|
-
return kept;
|
|
167
|
+
return offered.filter((resource) => keptIds.has(resource.metadata?.id ?? ""));
|
|
143
168
|
}
|