@stigmer/server 3.15.0 → 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/boot/compose.d.ts.map +1 -1
- package/dist/boot/compose.js +23 -2
- package/dist/boot/compose.js.map +1 -1
- package/dist/boot/config.d.ts +14 -0
- package/dist/boot/config.d.ts.map +1 -1
- package/dist/boot/config.js +1 -0
- package/dist/boot/config.js.map +1 -1
- 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/dist/transport/console/handler.d.ts +22 -5
- package/dist/transport/console/handler.d.ts.map +1 -1
- package/dist/transport/console/handler.js +53 -24
- package/dist/transport/console/handler.js.map +1 -1
- package/package.json +4 -4
- package/src/boot/__tests__/auth-enabled-composition.test.ts +49 -3
- package/src/boot/__tests__/config.test.ts +41 -3
- package/src/boot/compose.ts +30 -2
- package/src/boot/config.ts +15 -0
- 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
- package/src/transport/console/__tests__/handler.test.ts +93 -23
- package/src/transport/console/__tests__/resolver.test.ts +6 -0
- package/src/transport/console/handler.ts +84 -30
|
@@ -78,28 +78,21 @@ export function newValidateListRequestStep(): PipelineStep<ListDesc> {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
/**
|
|
81
|
-
* QueryAllExecutions — list.go: full scan into the context.
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
81
|
+
* QueryAllExecutions — list.go: full scan into the context. The read
|
|
82
|
+
* scope no longer runs here: since stigmer-cloud 20260913.04 T02 the scope
|
|
83
|
+
* is the LAST per-row predicate of the chain (RestrictByReadScope below,
|
|
84
|
+
* after ApplyPhaseFilter), so a composed driver is offered the request's
|
|
85
|
+
* org and phase, not the kind (extensions/list-read-scope.ts, the
|
|
86
|
+
* header's last contract line).
|
|
87
87
|
*/
|
|
88
88
|
export function newQueryAllExecutionsStep(
|
|
89
89
|
store: Store,
|
|
90
90
|
logger: Logger,
|
|
91
|
-
listReadScope: ListReadScope | undefined,
|
|
92
91
|
): PipelineStep<ListDesc> {
|
|
93
92
|
return {
|
|
94
93
|
name: "QueryAllExecutions",
|
|
95
94
|
async execute(ctx) {
|
|
96
|
-
const executions = await
|
|
97
|
-
listReadScope,
|
|
98
|
-
ctx.callerIdentity,
|
|
99
|
-
ApiResourceKind.agent_execution,
|
|
100
|
-
await loadAllAgentExecutions(store, logger),
|
|
101
|
-
ctx.input.org,
|
|
102
|
-
);
|
|
95
|
+
const executions = await loadAllAgentExecutions(store, logger);
|
|
103
96
|
logger.debug("Successfully queried executions", {
|
|
104
97
|
count: executions.length,
|
|
105
98
|
});
|
|
@@ -108,6 +101,37 @@ export function newQueryAllExecutionsStep(
|
|
|
108
101
|
};
|
|
109
102
|
}
|
|
110
103
|
|
|
104
|
+
/**
|
|
105
|
+
* RestrictByReadScope — the list chain's last per-row predicate (census
|
|
106
|
+
* lane 4). With a composed ListReadScope (20260830.01) the working list
|
|
107
|
+
* narrows to the caller's authorized executions ∩ the request's org (the
|
|
108
|
+
* Java AgentExecutionListHandler baseline: org set = one-org view, blank
|
|
109
|
+
* = permission-bounded across orgs; the guest cookie rule rides the
|
|
110
|
+
* driver); no scope = the working list unchanged, org a no-op —
|
|
111
|
+
* byte-identical. Placed after ApplyPhaseFilter so the driver is asked
|
|
112
|
+
* about the rows the request keeps, never about the whole kind.
|
|
113
|
+
*/
|
|
114
|
+
export function newRestrictByReadScopeStep(
|
|
115
|
+
listReadScope: ListReadScope | undefined,
|
|
116
|
+
): PipelineStep<ListDesc> {
|
|
117
|
+
return {
|
|
118
|
+
name: "RestrictByReadScope",
|
|
119
|
+
async execute(ctx) {
|
|
120
|
+
const executions = requireExecutions(ctx.get(EXECUTION_LIST_KEY));
|
|
121
|
+
ctx.set(
|
|
122
|
+
EXECUTION_LIST_KEY,
|
|
123
|
+
await restrictListByReadScope(
|
|
124
|
+
listReadScope,
|
|
125
|
+
ctx.callerIdentity,
|
|
126
|
+
ApiResourceKind.agent_execution,
|
|
127
|
+
executions,
|
|
128
|
+
ctx.input.org,
|
|
129
|
+
),
|
|
130
|
+
);
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
111
135
|
/**
|
|
112
136
|
* ApplyPhaseFilter — list.go: status.phase equality filter, skipped when
|
|
113
137
|
* the request carries no phase.
|
|
@@ -153,7 +177,8 @@ export function newValidateListBySessionRequestStep(): PipelineStep<ListBySessio
|
|
|
153
177
|
|
|
154
178
|
/**
|
|
155
179
|
* QueryExecutionsBySession — list_by_session.go: full scan +
|
|
156
|
-
* spec.session_id equality filter
|
|
180
|
+
* spec.session_id equality filter, then the read scope over the session's
|
|
181
|
+
* executions only (the scope is the last per-row predicate).
|
|
157
182
|
*/
|
|
158
183
|
export function newQueryExecutionsBySessionStep(
|
|
159
184
|
store: Store,
|
|
@@ -163,19 +188,19 @@ export function newQueryExecutionsBySessionStep(
|
|
|
163
188
|
return {
|
|
164
189
|
name: "QueryExecutionsBySession",
|
|
165
190
|
async execute(ctx) {
|
|
166
|
-
// Census lane 5:
|
|
191
|
+
// Census lane 5: session filter ∩ authorized ids; the Java handler
|
|
167
192
|
// never consults org on this lane (bounded by the session id).
|
|
168
|
-
const
|
|
193
|
+
const ofSession = (await loadAllAgentExecutions(store, logger)).filter(
|
|
194
|
+
(execution) =>
|
|
195
|
+
(execution.spec?.sessionId ?? "") === ctx.input.sessionId,
|
|
196
|
+
);
|
|
197
|
+
const executions = await restrictListByReadScope(
|
|
169
198
|
listReadScope,
|
|
170
199
|
ctx.callerIdentity,
|
|
171
200
|
ApiResourceKind.agent_execution,
|
|
172
|
-
|
|
201
|
+
ofSession,
|
|
173
202
|
"",
|
|
174
203
|
);
|
|
175
|
-
const executions = all.filter(
|
|
176
|
-
(execution) =>
|
|
177
|
-
(execution.spec?.sessionId ?? "") === ctx.input.sessionId,
|
|
178
|
-
);
|
|
179
204
|
logger.debug("Successfully queried executions by session", {
|
|
180
205
|
sessionId: ctx.input.sessionId,
|
|
181
206
|
count: executions.length,
|
|
@@ -264,25 +264,21 @@ export function newListByOrgAndLabelsStep(
|
|
|
264
264
|
continue;
|
|
265
265
|
}
|
|
266
266
|
}
|
|
267
|
-
|
|
267
|
+
// Census lane 14: the org and label filters (contract in both
|
|
268
|
+
// editions) first, the scope last over the org's rows (the scope is
|
|
269
|
+
// the last per-row predicate, stigmer-cloud 20260913.04 T02).
|
|
270
|
+
const instances = await restrictListByReadScope(
|
|
268
271
|
listReadScope,
|
|
269
272
|
ctx.callerIdentity,
|
|
270
273
|
ApiResourceKind.agent_instance,
|
|
271
|
-
decoded
|
|
274
|
+
decoded.filter(
|
|
275
|
+
(instance) =>
|
|
276
|
+
(instance.metadata?.org ?? "") === org &&
|
|
277
|
+
matchesAllLabels(instance.metadata?.labels ?? {}, filterLabels),
|
|
278
|
+
),
|
|
272
279
|
"",
|
|
273
280
|
);
|
|
274
281
|
|
|
275
|
-
const instances: AgentInstance[] = [];
|
|
276
|
-
for (const instance of visible) {
|
|
277
|
-
if ((instance.metadata?.org ?? "") !== org) {
|
|
278
|
-
continue;
|
|
279
|
-
}
|
|
280
|
-
if (!matchesAllLabels(instance.metadata?.labels ?? {}, filterLabels)) {
|
|
281
|
-
continue;
|
|
282
|
-
}
|
|
283
|
-
instances.push(instance);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
282
|
instances.sort((a, b) =>
|
|
287
283
|
compareCreatedAtDesc(
|
|
288
284
|
a.status?.audit?.specAudit?.createdAt,
|
|
@@ -346,25 +342,20 @@ export function newLoadByAgentStep(
|
|
|
346
342
|
continue;
|
|
347
343
|
}
|
|
348
344
|
}
|
|
349
|
-
|
|
345
|
+
// Census lane 15: the agent's instances (and the optional org) first,
|
|
346
|
+
// the scope last.
|
|
347
|
+
const instances = await restrictListByReadScope(
|
|
350
348
|
listReadScope,
|
|
351
349
|
ctx.callerIdentity,
|
|
352
350
|
ApiResourceKind.agent_instance,
|
|
353
|
-
decoded
|
|
351
|
+
decoded.filter(
|
|
352
|
+
(instance) =>
|
|
353
|
+
(instance.spec?.agentId ?? "") === agentId &&
|
|
354
|
+
(req.org === "" || (instance.metadata?.org ?? "") === req.org),
|
|
355
|
+
),
|
|
354
356
|
"",
|
|
355
357
|
);
|
|
356
358
|
|
|
357
|
-
const instances: AgentInstance[] = [];
|
|
358
|
-
for (const instance of visible) {
|
|
359
|
-
if ((instance.spec?.agentId ?? "") !== agentId) {
|
|
360
|
-
continue;
|
|
361
|
-
}
|
|
362
|
-
if (req.org !== "" && (instance.metadata?.org ?? "") !== req.org) {
|
|
363
|
-
continue;
|
|
364
|
-
}
|
|
365
|
-
instances.push(instance);
|
|
366
|
-
}
|
|
367
|
-
|
|
368
359
|
ctx.set(
|
|
369
360
|
INSTANCE_LIST_KEY,
|
|
370
361
|
create(AgentInstanceListSchema, {
|
|
@@ -596,28 +596,25 @@ function newLoadSharesByAgentStep(
|
|
|
596
596
|
continue;
|
|
597
597
|
}
|
|
598
598
|
}
|
|
599
|
-
// 20260830.01 census lane 17: the
|
|
600
|
-
//
|
|
601
|
-
|
|
599
|
+
// 20260830.01 census lane 17: the org/agent filters are contract
|
|
600
|
+
// parity in both editions and run FIRST; the scope narrows the
|
|
601
|
+
// agent's shares last (the scope is the last per-row predicate,
|
|
602
|
+
// stigmer-cloud 20260913.04 T02).
|
|
603
|
+
const shares = await restrictListByReadScope(
|
|
602
604
|
listReadScope,
|
|
603
605
|
ctx.callerIdentity,
|
|
604
606
|
ApiResourceKind.agent_share,
|
|
605
|
-
decoded
|
|
607
|
+
decoded.filter((share) => {
|
|
608
|
+
const ref = share.spec?.agentRef;
|
|
609
|
+
return (
|
|
610
|
+
(ref?.org ?? "") === agentOrg &&
|
|
611
|
+
(ref?.slug ?? "") === agentSlug &&
|
|
612
|
+
(req.org === "" || (share.metadata?.org ?? "") === req.org)
|
|
613
|
+
);
|
|
614
|
+
}),
|
|
606
615
|
"",
|
|
607
616
|
);
|
|
608
617
|
|
|
609
|
-
const shares: AgentShare[] = [];
|
|
610
|
-
for (const share of visible) {
|
|
611
|
-
const ref = share.spec?.agentRef;
|
|
612
|
-
if ((ref?.org ?? "") !== agentOrg || (ref?.slug ?? "") !== agentSlug) {
|
|
613
|
-
continue;
|
|
614
|
-
}
|
|
615
|
-
if (req.org !== "" && (share.metadata?.org ?? "") !== req.org) {
|
|
616
|
-
continue;
|
|
617
|
-
}
|
|
618
|
-
shares.push(share);
|
|
619
|
-
}
|
|
620
|
-
|
|
621
618
|
ctx.set(
|
|
622
619
|
SHARE_LIST_KEY,
|
|
623
620
|
create(AgentShareListSchema, {
|
|
@@ -700,27 +697,22 @@ function newListByOrgAndLabelsStep(
|
|
|
700
697
|
continue;
|
|
701
698
|
}
|
|
702
699
|
}
|
|
703
|
-
// 20260830.01 census lane 16: the
|
|
704
|
-
//
|
|
705
|
-
|
|
700
|
+
// 20260830.01 census lane 16: the org and label filters serve the
|
|
701
|
+
// Java handler's arms in both editions and run FIRST; the scope
|
|
702
|
+
// narrows the org's rows last (the scope is the last per-row
|
|
703
|
+
// predicate, stigmer-cloud 20260913.04 T02).
|
|
704
|
+
const shares = await restrictListByReadScope(
|
|
706
705
|
listReadScope,
|
|
707
706
|
ctx.callerIdentity,
|
|
708
707
|
ApiResourceKind.agent_share,
|
|
709
|
-
decoded
|
|
708
|
+
decoded.filter(
|
|
709
|
+
(share) =>
|
|
710
|
+
(share.metadata?.org ?? "") === org &&
|
|
711
|
+
matchesAllLabels(share.metadata?.labels ?? {}, filterLabels),
|
|
712
|
+
),
|
|
710
713
|
"",
|
|
711
714
|
);
|
|
712
715
|
|
|
713
|
-
const shares: AgentShare[] = [];
|
|
714
|
-
for (const share of visible) {
|
|
715
|
-
if ((share.metadata?.org ?? "") !== org) {
|
|
716
|
-
continue;
|
|
717
|
-
}
|
|
718
|
-
if (!matchesAllLabels(share.metadata?.labels ?? {}, filterLabels)) {
|
|
719
|
-
continue;
|
|
720
|
-
}
|
|
721
|
-
shares.push(share);
|
|
722
|
-
}
|
|
723
|
-
|
|
724
716
|
shares.sort((a, b) =>
|
|
725
717
|
compareCreatedAtDesc(
|
|
726
718
|
a.status?.audit?.specAudit?.createdAt,
|
|
@@ -797,24 +797,24 @@ function newListByOrgAndLabelsStep(
|
|
|
797
797
|
continue; // skip malformed rows, as Go does
|
|
798
798
|
}
|
|
799
799
|
}
|
|
800
|
-
|
|
800
|
+
// The request's org and labels first (this lane's contract in both
|
|
801
|
+
// editions), the read scope last (census lane 10; the scope is the
|
|
802
|
+
// last per-row predicate): a composed driver is asked about the
|
|
803
|
+
// org's environments, never every tenant's.
|
|
804
|
+
const requested = decoded.filter(
|
|
805
|
+
(env) =>
|
|
806
|
+
(env.metadata?.org ?? "") === org &&
|
|
807
|
+
matchesAllLabels(env.metadata?.labels ?? {}, filterLabels),
|
|
808
|
+
);
|
|
809
|
+
const environments = await restrictListByReadScope(
|
|
801
810
|
listReadScope,
|
|
802
811
|
ctx.callerIdentity,
|
|
803
812
|
ApiResourceKind.environment,
|
|
804
|
-
|
|
813
|
+
requested,
|
|
805
814
|
"",
|
|
806
815
|
);
|
|
807
|
-
|
|
808
|
-
const environments: Environment[] = [];
|
|
809
|
-
for (const env of visible) {
|
|
810
|
-
if ((env.metadata?.org ?? "") !== org) {
|
|
811
|
-
continue;
|
|
812
|
-
}
|
|
813
|
-
if (!matchesAllLabels(env.metadata?.labels ?? {}, filterLabels)) {
|
|
814
|
-
continue;
|
|
815
|
-
}
|
|
816
|
+
for (const env of environments) {
|
|
816
817
|
redactEnvironmentSecrets(env);
|
|
817
|
-
environments.push(env);
|
|
818
818
|
}
|
|
819
819
|
|
|
820
820
|
environments.sort((a, b) =>
|
|
@@ -539,24 +539,18 @@ export function newListMemoriesByOrgStep(
|
|
|
539
539
|
decoded.push(memory);
|
|
540
540
|
}
|
|
541
541
|
}
|
|
542
|
-
// 20260830.01 census lane 11: the
|
|
543
|
-
//
|
|
544
|
-
|
|
542
|
+
// 20260830.01 census lane 11: the org equality serves the Java
|
|
543
|
+
// handler's org arm in both editions and runs FIRST; the scope
|
|
544
|
+
// narrows the org's rows last (the scope is the last per-row
|
|
545
|
+
// predicate, stigmer-cloud 20260913.04 T02).
|
|
546
|
+
const memories = await restrictListByReadScope(
|
|
545
547
|
listReadScope,
|
|
546
548
|
ctx.callerIdentity,
|
|
547
549
|
ApiResourceKind.memory,
|
|
548
|
-
decoded,
|
|
550
|
+
decoded.filter((memory) => (memory.metadata?.org ?? "") === org),
|
|
549
551
|
"",
|
|
550
552
|
);
|
|
551
553
|
|
|
552
|
-
const memories: Memory[] = [];
|
|
553
|
-
for (const memory of visible) {
|
|
554
|
-
if ((memory.metadata?.org ?? "") !== org) {
|
|
555
|
-
continue;
|
|
556
|
-
}
|
|
557
|
-
memories.push(memory);
|
|
558
|
-
}
|
|
559
|
-
|
|
560
554
|
memories.sort((a, b) =>
|
|
561
555
|
compareCreatedAtDesc(
|
|
562
556
|
a.status?.audit?.specAudit?.createdAt,
|
|
@@ -575,35 +575,32 @@ function newLoadSchedulesByAgentStep(
|
|
|
575
575
|
continue;
|
|
576
576
|
}
|
|
577
577
|
}
|
|
578
|
-
// 20260830.01 census lane 13: the
|
|
579
|
-
//
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
ApiResourceKind.schedule,
|
|
584
|
-
decoded,
|
|
585
|
-
"",
|
|
586
|
-
);
|
|
587
|
-
|
|
588
|
-
const schedules: Schedule[] = [];
|
|
589
|
-
for (const schedule of visible) {
|
|
578
|
+
// 20260830.01 census lane 13: the org/agent filters are contract
|
|
579
|
+
// parity in both editions and run FIRST; the scope narrows the
|
|
580
|
+
// agent's schedules last (the scope is the last per-row predicate,
|
|
581
|
+
// stigmer-cloud 20260913.04 T02).
|
|
582
|
+
const ofAgent = decoded.filter((schedule) => {
|
|
590
583
|
const ref =
|
|
591
584
|
schedule.spec?.target.case === "agent"
|
|
592
585
|
? schedule.spec.target.value.agentRef
|
|
593
586
|
: undefined;
|
|
594
587
|
if ((ref?.org ?? "") !== agentOrg || (ref?.slug ?? "") !== agentSlug) {
|
|
595
|
-
|
|
588
|
+
return false;
|
|
596
589
|
}
|
|
597
590
|
// Org scope: a multi-org caller asking for one org's schedules
|
|
598
591
|
// must not see another org's schedules of the same agent.
|
|
599
592
|
// (Schedules are same-org by invariant, so today this only
|
|
600
593
|
// excludes rows when the requested org differs from the agent's —
|
|
601
594
|
// kept anyway for contract parity with the sibling RPCs.)
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
595
|
+
return req.org === "" || (schedule.metadata?.org ?? "") === req.org;
|
|
596
|
+
});
|
|
597
|
+
const schedules = await restrictListByReadScope(
|
|
598
|
+
listReadScope,
|
|
599
|
+
ctx.callerIdentity,
|
|
600
|
+
ApiResourceKind.schedule,
|
|
601
|
+
ofAgent,
|
|
602
|
+
"",
|
|
603
|
+
);
|
|
607
604
|
|
|
608
605
|
ctx.set(
|
|
609
606
|
SCHEDULE_LIST_KEY,
|
|
@@ -677,27 +674,22 @@ function newListByOrgAndLabelsStep(
|
|
|
677
674
|
continue;
|
|
678
675
|
}
|
|
679
676
|
}
|
|
680
|
-
// 20260830.01 census lane 12: the
|
|
681
|
-
//
|
|
682
|
-
|
|
677
|
+
// 20260830.01 census lane 12: the org and label filters serve the
|
|
678
|
+
// Java handler's arms in both editions and run FIRST; the scope
|
|
679
|
+
// narrows the org's rows last (the scope is the last per-row
|
|
680
|
+
// predicate, stigmer-cloud 20260913.04 T02).
|
|
681
|
+
const schedules = await restrictListByReadScope(
|
|
683
682
|
listReadScope,
|
|
684
683
|
ctx.callerIdentity,
|
|
685
684
|
ApiResourceKind.schedule,
|
|
686
|
-
decoded
|
|
685
|
+
decoded.filter(
|
|
686
|
+
(schedule) =>
|
|
687
|
+
(schedule.metadata?.org ?? "") === req.org &&
|
|
688
|
+
matchesAllLabels(schedule.metadata?.labels ?? {}, req.labels),
|
|
689
|
+
),
|
|
687
690
|
"",
|
|
688
691
|
);
|
|
689
692
|
|
|
690
|
-
const schedules: Schedule[] = [];
|
|
691
|
-
for (const schedule of visible) {
|
|
692
|
-
if ((schedule.metadata?.org ?? "") !== req.org) {
|
|
693
|
-
continue;
|
|
694
|
-
}
|
|
695
|
-
if (!matchesAllLabels(schedule.metadata?.labels ?? {}, req.labels)) {
|
|
696
|
-
continue;
|
|
697
|
-
}
|
|
698
|
-
schedules.push(schedule);
|
|
699
|
-
}
|
|
700
|
-
|
|
701
693
|
schedules.sort((a, b) =>
|
|
702
694
|
compareCreatedAtDesc(
|
|
703
695
|
a.status?.audit?.specAudit?.createdAt,
|
|
@@ -725,16 +725,23 @@ export function newFilterByAgentInstanceStep(
|
|
|
725
725
|
throw invalidArgumentError("agent_instance_id is required");
|
|
726
726
|
}
|
|
727
727
|
|
|
728
|
-
|
|
728
|
+
// The instance's sessions first, the scope last (census lane 2): a
|
|
729
|
+
// composed driver is asked about one instance's rows, not the kind.
|
|
730
|
+
const sessions = await loadAllSessions(
|
|
731
|
+
store,
|
|
732
|
+
logger,
|
|
733
|
+
ctx.apiResourceKind,
|
|
734
|
+
);
|
|
735
|
+
const filtered = await restrictListByReadScope(
|
|
729
736
|
listReadScope,
|
|
730
737
|
ctx.callerIdentity,
|
|
731
738
|
ApiResourceKind.session,
|
|
732
|
-
|
|
739
|
+
sessions.filter(
|
|
740
|
+
(session) =>
|
|
741
|
+
(session.spec?.agentInstanceId ?? "") === agentInstanceId,
|
|
742
|
+
),
|
|
733
743
|
"",
|
|
734
744
|
);
|
|
735
|
-
const filtered = sessions.filter(
|
|
736
|
-
(session) => (session.spec?.agentInstanceId ?? "") === agentInstanceId,
|
|
737
|
-
);
|
|
738
745
|
|
|
739
746
|
logger.info("Filtered sessions by agent instance", {
|
|
740
747
|
agentInstanceId,
|
|
@@ -816,17 +823,23 @@ export function newFilterByChannelStep(
|
|
|
816
823
|
throw invalidArgumentError("channel_id is required");
|
|
817
824
|
}
|
|
818
825
|
|
|
819
|
-
|
|
826
|
+
// The channel's sessions first, the scope last (census lane 3).
|
|
827
|
+
const sessions = await loadAllSessions(
|
|
828
|
+
store,
|
|
829
|
+
logger,
|
|
830
|
+
ctx.apiResourceKind,
|
|
831
|
+
);
|
|
832
|
+
const filtered = await restrictListByReadScope(
|
|
820
833
|
listReadScope,
|
|
821
834
|
ctx.callerIdentity,
|
|
822
835
|
ApiResourceKind.session,
|
|
823
|
-
|
|
836
|
+
sessions.filter(
|
|
837
|
+
(session) =>
|
|
838
|
+
(session.metadata?.labels ?? {})[CHANNEL_ID_LABEL_KEY] ===
|
|
839
|
+
channelId,
|
|
840
|
+
),
|
|
824
841
|
"",
|
|
825
842
|
);
|
|
826
|
-
const filtered = sessions.filter(
|
|
827
|
-
(session) =>
|
|
828
|
-
(session.metadata?.labels ?? {})[CHANNEL_ID_LABEL_KEY] === channelId,
|
|
829
|
-
);
|
|
830
843
|
|
|
831
844
|
logger.info("Filtered sessions by channel", {
|
|
832
845
|
channelId,
|
|
@@ -230,7 +230,8 @@ export function registerWorkflowExecutionServices(
|
|
|
230
230
|
router.service(WorkflowExecutionQueryController, {
|
|
231
231
|
get: (id, ctx) => get(deps, id, ctx),
|
|
232
232
|
list: (req, ctx) => list(deps, req, callerIdentityOf(ctx)),
|
|
233
|
-
listByWorkflow: (req, ctx) =>
|
|
233
|
+
listByWorkflow: (req, ctx) =>
|
|
234
|
+
listByWorkflow(deps, req, callerIdentityOf(ctx)),
|
|
234
235
|
subscribe: (req, ctx) => subscribeExecution(deps, req, ctx),
|
|
235
236
|
getEventLog: (req, ctx) => getEventLog(deps, req, callerIdentityOf(ctx)),
|
|
236
237
|
subscribeEvents: (req, ctx) => subscribeEvents(deps, req, ctx),
|
|
@@ -482,33 +483,37 @@ async function get(
|
|
|
482
483
|
* filter.phases is absent), structured filter criteria (T13), and the
|
|
483
484
|
* sort default started_at descending. No pagination (total_pages
|
|
484
485
|
* placeholder 1). With a composed ListReadScope (20260830.01, census
|
|
485
|
-
* lane 6) the
|
|
486
|
+
* lane 6) the list narrows to the caller's authorized executions and the
|
|
486
487
|
* request `org` narrows when non-blank (the Java
|
|
487
488
|
* WorkflowExecutionListHandler baseline: blank = permission-bounded
|
|
488
489
|
* across orgs); no scope = the full scan with `org` a deliberate no-op
|
|
489
|
-
* on this single-tenant edition — byte-identical.
|
|
490
|
+
* on this single-tenant edition — byte-identical. The scope runs AFTER
|
|
491
|
+
* the phase and criteria filters (stigmer-cloud 20260913.04 T02: the
|
|
492
|
+
* scope is the last per-row predicate), so a composed driver is asked
|
|
493
|
+
* about the rows the request keeps, not the kind.
|
|
490
494
|
*/
|
|
491
495
|
async function list(
|
|
492
496
|
deps: WorkflowExecutionControllerDeps,
|
|
493
497
|
req: ListWorkflowExecutionsRequest,
|
|
494
498
|
identity: CallerIdentity,
|
|
495
499
|
): Promise<WorkflowExecutionList> {
|
|
496
|
-
let executions = await
|
|
497
|
-
deps.
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
await loadAllWorkflowExecutions(
|
|
501
|
-
deps.store,
|
|
502
|
-
deps.logger,
|
|
503
|
-
"failed to list workflow executions",
|
|
504
|
-
),
|
|
505
|
-
req.org,
|
|
500
|
+
let executions = await loadAllWorkflowExecutions(
|
|
501
|
+
deps.store,
|
|
502
|
+
deps.logger,
|
|
503
|
+
"failed to list workflow executions",
|
|
506
504
|
);
|
|
507
505
|
|
|
508
506
|
if (req.filter === undefined || req.filter.phases.length === 0) {
|
|
509
507
|
executions = applyLegacyPhaseFilter(executions, req.phase);
|
|
510
508
|
}
|
|
511
509
|
executions = applyFilterCriteria(executions, req.filter);
|
|
510
|
+
executions = await restrictListByReadScope(
|
|
511
|
+
deps.listReadScope,
|
|
512
|
+
identity,
|
|
513
|
+
ApiResourceKind.workflow_execution,
|
|
514
|
+
executions,
|
|
515
|
+
req.org,
|
|
516
|
+
);
|
|
512
517
|
|
|
513
518
|
let sortField = req.sortField;
|
|
514
519
|
let ascending = req.sortAscending;
|
|
@@ -538,26 +543,27 @@ async function listByWorkflow(
|
|
|
538
543
|
throw invalidArgumentError("workflow_id is required");
|
|
539
544
|
}
|
|
540
545
|
|
|
541
|
-
// Census lane 7:
|
|
542
|
-
//
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
deps.store,
|
|
549
|
-
deps.logger,
|
|
550
|
-
"failed to list workflow executions",
|
|
551
|
-
),
|
|
552
|
-
"",
|
|
546
|
+
// Census lane 7: workflow filter ∩ criteria ∩ authorized ids, the scope
|
|
547
|
+
// last (bounded by the workflow id; the Java handler never consults org
|
|
548
|
+
// on this lane).
|
|
549
|
+
const all = await loadAllWorkflowExecutions(
|
|
550
|
+
deps.store,
|
|
551
|
+
deps.logger,
|
|
552
|
+
"failed to list workflow executions",
|
|
553
553
|
);
|
|
554
554
|
let executions = all.filter(
|
|
555
555
|
(execution) =>
|
|
556
556
|
execution.spec?.workflowInstanceId === req.workflowId ||
|
|
557
557
|
execution.spec?.workflowId === req.workflowId,
|
|
558
558
|
);
|
|
559
|
-
|
|
560
559
|
executions = applyFilterCriteria(executions, req.filter);
|
|
560
|
+
executions = await restrictListByReadScope(
|
|
561
|
+
deps.listReadScope,
|
|
562
|
+
identity,
|
|
563
|
+
ApiResourceKind.workflow_execution,
|
|
564
|
+
executions,
|
|
565
|
+
"",
|
|
566
|
+
);
|
|
561
567
|
|
|
562
568
|
let sortField = req.sortField;
|
|
563
569
|
let ascending = req.sortAscending;
|
|
@@ -8,7 +8,11 @@ import { create } from "@bufbuild/protobuf";
|
|
|
8
8
|
import type { Timestamp } from "@bufbuild/protobuf/wkt";
|
|
9
9
|
import { timestampFromMs } from "@bufbuild/protobuf/wkt";
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import type { WorkflowExecution } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/api_pb";
|
|
12
|
+
import {
|
|
13
|
+
ExecutionPhase,
|
|
14
|
+
WorkflowTaskStatus,
|
|
15
|
+
} from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/enum_pb";
|
|
12
16
|
import {
|
|
13
17
|
PendingApprovalSchema,
|
|
14
18
|
PendingApprovalsListSchema,
|
|
@@ -46,19 +50,25 @@ export async function listPendingApprovals(
|
|
|
46
50
|
req: ListPendingApprovalsRequest,
|
|
47
51
|
identity: CallerIdentity,
|
|
48
52
|
): Promise<PendingApprovalsList> {
|
|
49
|
-
// Census lane 8 (20260830.01): the scope narrows the
|
|
50
|
-
//
|
|
53
|
+
// Census lane 8 (20260830.01): the scope narrows the EXECUTIONS — before
|
|
54
|
+
// the approvals projection and its pagination, the Java
|
|
51
55
|
// WorkflowExecutionListPendingApprovalsHandler order — and the request
|
|
52
56
|
// org narrows when non-blank (blank = permission-bounded across orgs).
|
|
57
|
+
// It runs over the executions that CAN carry a pending approval (in
|
|
58
|
+
// progress, a task waiting) rather than the org's whole history: the
|
|
59
|
+
// predicate is per-row, so the scope is last (stigmer-cloud 20260913.04
|
|
60
|
+
// T02) and a composed driver is asked about a handful of rows.
|
|
53
61
|
const executions = await restrictListByReadScope(
|
|
54
62
|
deps.listReadScope,
|
|
55
63
|
identity,
|
|
56
64
|
ApiResourceKind.workflow_execution,
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
65
|
+
(
|
|
66
|
+
await loadAllWorkflowExecutions(
|
|
67
|
+
deps.store,
|
|
68
|
+
deps.logger,
|
|
69
|
+
"failed to list workflow executions for pending approvals",
|
|
70
|
+
)
|
|
71
|
+
).filter(mayHavePendingApproval),
|
|
62
72
|
req.org,
|
|
63
73
|
);
|
|
64
74
|
|
|
@@ -72,12 +82,6 @@ export async function listPendingApprovals(
|
|
|
72
82
|
|
|
73
83
|
let approvals: PendingApproval[] = [];
|
|
74
84
|
for (const execution of executions) {
|
|
75
|
-
const phase =
|
|
76
|
-
execution.status?.phase ?? ExecutionPhase.EXECUTION_PHASE_UNSPECIFIED;
|
|
77
|
-
if (phase !== ExecutionPhase.EXECUTION_IN_PROGRESS) {
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
85
|
for (const task of execution.status?.tasks ?? []) {
|
|
82
86
|
if (task.status !== WorkflowTaskStatus.WORKFLOW_TASK_WAITING_APPROVAL) {
|
|
83
87
|
continue;
|
|
@@ -90,8 +94,7 @@ export async function listPendingApprovals(
|
|
|
90
94
|
// would break submitWorkflowTaskApproval, whose runner-side
|
|
91
95
|
// signal is keyed by the plain task name.
|
|
92
96
|
taskName: task.taskName,
|
|
93
|
-
requester:
|
|
94
|
-
execution.status?.audit?.specAudit?.createdBy?.id ?? "",
|
|
97
|
+
requester: execution.status?.audit?.specAudit?.createdBy?.id ?? "",
|
|
95
98
|
requestedAt: parseTimestampString(task.startedAt),
|
|
96
99
|
uiHint: task.uiHint,
|
|
97
100
|
}),
|
|
@@ -118,3 +121,21 @@ function parseTimestampString(value: string): Timestamp | undefined {
|
|
|
118
121
|
}
|
|
119
122
|
return timestampFromMs(ms);
|
|
120
123
|
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* The per-row predicate of the projection above: only an execution in
|
|
127
|
+
* progress with a task waiting for approval contributes an entry, so only
|
|
128
|
+
* those are worth a scope check. Same rows kept as the loop's own `continue`
|
|
129
|
+
* arms, decided once before the scope.
|
|
130
|
+
*/
|
|
131
|
+
function mayHavePendingApproval(execution: WorkflowExecution): boolean {
|
|
132
|
+
const phase =
|
|
133
|
+
execution.status?.phase ?? ExecutionPhase.EXECUTION_PHASE_UNSPECIFIED;
|
|
134
|
+
return (
|
|
135
|
+
phase === ExecutionPhase.EXECUTION_IN_PROGRESS &&
|
|
136
|
+
(execution.status?.tasks ?? []).some(
|
|
137
|
+
(task) =>
|
|
138
|
+
task.status === WorkflowTaskStatus.WORKFLOW_TASK_WAITING_APPROVAL,
|
|
139
|
+
)
|
|
140
|
+
);
|
|
141
|
+
}
|