@stigmer/server 3.25.0 → 3.27.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.
Files changed (254) hide show
  1. package/dist/authorization/evaluator.d.ts +4 -1
  2. package/dist/authorization/evaluator.d.ts.map +1 -1
  3. package/dist/authorization/evaluator.js +8 -0
  4. package/dist/authorization/evaluator.js.map +1 -1
  5. package/dist/authorization/model/agent.d.ts.map +1 -1
  6. package/dist/authorization/model/agent.js +4 -2
  7. package/dist/authorization/model/agent.js.map +1 -1
  8. package/dist/authorization/model/agent_channel.d.ts.map +1 -1
  9. package/dist/authorization/model/agent_channel.js +5 -4
  10. package/dist/authorization/model/agent_channel.js.map +1 -1
  11. package/dist/authorization/model/agent_instance.d.ts.map +1 -1
  12. package/dist/authorization/model/agent_instance.js +3 -2
  13. package/dist/authorization/model/agent_instance.js.map +1 -1
  14. package/dist/authorization/model/mcp_server.d.ts.map +1 -1
  15. package/dist/authorization/model/mcp_server.js +3 -2
  16. package/dist/authorization/model/mcp_server.js.map +1 -1
  17. package/dist/authorization/model/organization.d.ts.map +1 -1
  18. package/dist/authorization/model/organization.js +3 -0
  19. package/dist/authorization/model/organization.js.map +1 -1
  20. package/dist/authorization/model/plugin.d.ts.map +1 -1
  21. package/dist/authorization/model/plugin.js +4 -2
  22. package/dist/authorization/model/plugin.js.map +1 -1
  23. package/dist/authorization/model/rewrite.d.ts +25 -7
  24. package/dist/authorization/model/rewrite.d.ts.map +1 -1
  25. package/dist/authorization/model/rewrite.js +40 -1
  26. package/dist/authorization/model/rewrite.js.map +1 -1
  27. package/dist/authorization/model/schedule.d.ts.map +1 -1
  28. package/dist/authorization/model/schedule.js +8 -3
  29. package/dist/authorization/model/schedule.js.map +1 -1
  30. package/dist/authorization/model/skill.d.ts.map +1 -1
  31. package/dist/authorization/model/skill.js +3 -2
  32. package/dist/authorization/model/skill.js.map +1 -1
  33. package/dist/authorization/model/workflow.d.ts.map +1 -1
  34. package/dist/authorization/model/workflow.js +3 -2
  35. package/dist/authorization/model/workflow.js.map +1 -1
  36. package/dist/authorization/model/workflow_execution.d.ts.map +1 -1
  37. package/dist/authorization/model/workflow_execution.js +4 -3
  38. package/dist/authorization/model/workflow_execution.js.map +1 -1
  39. package/dist/authorization/model/workflow_instance.d.ts.map +1 -1
  40. package/dist/authorization/model/workflow_instance.js +5 -1
  41. package/dist/authorization/model/workflow_instance.js.map +1 -1
  42. package/dist/boot/compose.d.ts.map +1 -1
  43. package/dist/boot/compose.js +13 -8
  44. package/dist/boot/compose.js.map +1 -1
  45. package/dist/boot/list-indexes.d.ts +3 -0
  46. package/dist/boot/list-indexes.d.ts.map +1 -0
  47. package/dist/boot/list-indexes.js +24 -0
  48. package/dist/boot/list-indexes.js.map +1 -0
  49. package/dist/domain/agentexecution/controller.d.ts.map +1 -1
  50. package/dist/domain/agentexecution/controller.js +6 -8
  51. package/dist/domain/agentexecution/controller.js.map +1 -1
  52. package/dist/domain/agentexecution/list-index.d.ts +2 -0
  53. package/dist/domain/agentexecution/list-index.d.ts.map +1 -0
  54. package/dist/domain/agentexecution/list-index.js +23 -0
  55. package/dist/domain/agentexecution/list-index.js.map +1 -0
  56. package/dist/domain/agentexecution/steps.d.ts +17 -33
  57. package/dist/domain/agentexecution/steps.d.ts.map +1 -1
  58. package/dist/domain/agentexecution/steps.js +94 -95
  59. package/dist/domain/agentexecution/steps.js.map +1 -1
  60. package/dist/domain/agentexecution/usage.d.ts.map +1 -1
  61. package/dist/domain/agentexecution/usage.js +37 -14
  62. package/dist/domain/agentexecution/usage.js.map +1 -1
  63. package/dist/domain/artifact/controller.d.ts.map +1 -1
  64. package/dist/domain/artifact/controller.js +27 -15
  65. package/dist/domain/artifact/controller.js.map +1 -1
  66. package/dist/domain/artifact/list-index.d.ts +2 -0
  67. package/dist/domain/artifact/list-index.d.ts.map +1 -0
  68. package/dist/domain/artifact/list-index.js +21 -0
  69. package/dist/domain/artifact/list-index.js.map +1 -0
  70. package/dist/domain/iampolicy/access-lists.d.ts +5 -3
  71. package/dist/domain/iampolicy/access-lists.d.ts.map +1 -1
  72. package/dist/domain/iampolicy/access-lists.js +72 -19
  73. package/dist/domain/iampolicy/access-lists.js.map +1 -1
  74. package/dist/domain/iampolicy/constants.d.ts +32 -6
  75. package/dist/domain/iampolicy/constants.d.ts.map +1 -1
  76. package/dist/domain/iampolicy/constants.js +41 -6
  77. package/dist/domain/iampolicy/constants.js.map +1 -1
  78. package/dist/domain/iampolicy/controller.d.ts +6 -0
  79. package/dist/domain/iampolicy/controller.d.ts.map +1 -1
  80. package/dist/domain/iampolicy/controller.js +16 -7
  81. package/dist/domain/iampolicy/controller.js.map +1 -1
  82. package/dist/domain/iampolicy/resource-store.d.ts.map +1 -1
  83. package/dist/domain/iampolicy/resource-store.js +2 -6
  84. package/dist/domain/iampolicy/resource-store.js.map +1 -1
  85. package/dist/domain/iampolicy/steps.d.ts.map +1 -1
  86. package/dist/domain/iampolicy/steps.js +49 -7
  87. package/dist/domain/iampolicy/steps.js.map +1 -1
  88. package/dist/domain/session/list-index.d.ts +4 -0
  89. package/dist/domain/session/list-index.d.ts.map +1 -0
  90. package/dist/domain/session/list-index.js +26 -0
  91. package/dist/domain/session/list-index.js.map +1 -0
  92. package/dist/domain/session/steps.d.ts +7 -8
  93. package/dist/domain/session/steps.d.ts.map +1 -1
  94. package/dist/domain/session/steps.js +76 -78
  95. package/dist/domain/session/steps.js.map +1 -1
  96. package/dist/domain/workflow/registry/data/task-kind-registry.json +1 -0
  97. package/dist/domain/workflowexecution/controller.d.ts.map +1 -1
  98. package/dist/domain/workflowexecution/controller.js +53 -55
  99. package/dist/domain/workflowexecution/controller.js.map +1 -1
  100. package/dist/domain/workflowexecution/execution-filter.d.ts +4 -0
  101. package/dist/domain/workflowexecution/execution-filter.d.ts.map +1 -1
  102. package/dist/domain/workflowexecution/execution-filter.js +11 -5
  103. package/dist/domain/workflowexecution/execution-filter.js.map +1 -1
  104. package/dist/domain/workflowexecution/get-execution-summary.d.ts.map +1 -1
  105. package/dist/domain/workflowexecution/get-execution-summary.js +23 -17
  106. package/dist/domain/workflowexecution/get-execution-summary.js.map +1 -1
  107. package/dist/domain/workflowexecution/list-index.d.ts +2 -0
  108. package/dist/domain/workflowexecution/list-index.d.ts.map +1 -0
  109. package/dist/domain/workflowexecution/list-index.js +21 -0
  110. package/dist/domain/workflowexecution/list-index.js.map +1 -0
  111. package/dist/domain/workflowexecution/list-pending-approvals.d.ts.map +1 -1
  112. package/dist/domain/workflowexecution/list-pending-approvals.js +69 -32
  113. package/dist/domain/workflowexecution/list-pending-approvals.js.map +1 -1
  114. package/dist/domain/workflowexecution/queries.d.ts +30 -4
  115. package/dist/domain/workflowexecution/queries.d.ts.map +1 -1
  116. package/dist/domain/workflowexecution/queries.js +76 -22
  117. package/dist/domain/workflowexecution/queries.js.map +1 -1
  118. package/dist/extensions/drivers.d.ts +12 -0
  119. package/dist/extensions/drivers.d.ts.map +1 -1
  120. package/dist/extensions/gate-slots.d.ts +10 -1
  121. package/dist/extensions/gate-slots.d.ts.map +1 -1
  122. package/dist/extensions/gate-slots.js +1 -0
  123. package/dist/extensions/gate-slots.js.map +1 -1
  124. package/dist/extensions/list-read-scope.d.ts +9 -12
  125. package/dist/extensions/list-read-scope.d.ts.map +1 -1
  126. package/dist/extensions/list-read-scope.js.map +1 -1
  127. package/dist/extensions/principal-display.d.ts +34 -0
  128. package/dist/extensions/principal-display.d.ts.map +1 -0
  129. package/dist/extensions/principal-display.js +2 -0
  130. package/dist/extensions/principal-display.js.map +1 -0
  131. package/dist/extensions/registry.d.ts +6 -0
  132. package/dist/extensions/registry.d.ts.map +1 -1
  133. package/dist/extensions/registry.js +10 -0
  134. package/dist/extensions/registry.js.map +1 -1
  135. package/dist/index.d.ts +4 -2
  136. package/dist/index.d.ts.map +1 -1
  137. package/dist/index.js +6 -1
  138. package/dist/index.js.map +1 -1
  139. package/dist/pipeline/apiresource-meta.d.ts +10 -0
  140. package/dist/pipeline/apiresource-meta.d.ts.map +1 -1
  141. package/dist/pipeline/apiresource-meta.js +16 -0
  142. package/dist/pipeline/apiresource-meta.js.map +1 -1
  143. package/dist/pipeline/steps/list-page.d.ts +63 -0
  144. package/dist/pipeline/steps/list-page.d.ts.map +1 -0
  145. package/dist/pipeline/steps/list-page.js +187 -0
  146. package/dist/pipeline/steps/list-page.js.map +1 -0
  147. package/dist/query/activity/handler.d.ts +6 -7
  148. package/dist/query/activity/handler.d.ts.map +1 -1
  149. package/dist/query/activity/handler.js +42 -59
  150. package/dist/query/activity/handler.js.map +1 -1
  151. package/dist/store/interface.d.ts +29 -0
  152. package/dist/store/interface.d.ts.map +1 -1
  153. package/dist/store/interface.js.map +1 -1
  154. package/dist/store/list-index.d.ts +123 -0
  155. package/dist/store/list-index.d.ts.map +1 -0
  156. package/dist/store/list-index.js +277 -0
  157. package/dist/store/list-index.js.map +1 -0
  158. package/dist/store/logger.d.ts +3 -0
  159. package/dist/store/logger.d.ts.map +1 -1
  160. package/dist/store/logger.js +5 -1
  161. package/dist/store/logger.js.map +1 -1
  162. package/dist/store/postgres/migrations.d.ts +10 -6
  163. package/dist/store/postgres/migrations.d.ts.map +1 -1
  164. package/dist/store/postgres/migrations.js +64 -6
  165. package/dist/store/postgres/migrations.js.map +1 -1
  166. package/dist/store/postgres/store.d.ts +26 -5
  167. package/dist/store/postgres/store.d.ts.map +1 -1
  168. package/dist/store/postgres/store.js +377 -22
  169. package/dist/store/postgres/store.js.map +1 -1
  170. package/dist/store/sqlite/migrations.d.ts +11 -5
  171. package/dist/store/sqlite/migrations.d.ts.map +1 -1
  172. package/dist/store/sqlite/migrations.js +50 -4
  173. package/dist/store/sqlite/migrations.js.map +1 -1
  174. package/dist/store/sqlite/store.d.ts +18 -3
  175. package/dist/store/sqlite/store.d.ts.map +1 -1
  176. package/dist/store/sqlite/store.js +321 -15
  177. package/dist/store/sqlite/store.js.map +1 -1
  178. package/package.json +6 -6
  179. package/src/authorization/__tests__/drivers.ts +6 -1
  180. package/src/authorization/__tests__/evaluator.test.ts +127 -2
  181. package/src/authorization/evaluator.ts +12 -1
  182. package/src/authorization/model/__tests__/registry.test.ts +64 -2
  183. package/src/authorization/model/agent.ts +4 -1
  184. package/src/authorization/model/agent_channel.ts +11 -3
  185. package/src/authorization/model/agent_instance.ts +3 -1
  186. package/src/authorization/model/mcp_server.ts +3 -1
  187. package/src/authorization/model/organization.ts +3 -0
  188. package/src/authorization/model/plugin.ts +4 -1
  189. package/src/authorization/model/rewrite.ts +65 -8
  190. package/src/authorization/model/schedule.ts +11 -2
  191. package/src/authorization/model/skill.ts +3 -1
  192. package/src/authorization/model/workflow.ts +3 -1
  193. package/src/authorization/model/workflow_execution.ts +4 -2
  194. package/src/authorization/model/workflow_instance.ts +5 -0
  195. package/src/boot/__tests__/list-indexes.test.ts +65 -0
  196. package/src/boot/compose.ts +25 -13
  197. package/src/boot/list-indexes.ts +25 -0
  198. package/src/domain/agentexecution/__tests__/agentexecution.test.ts +85 -3
  199. package/src/domain/agentexecution/controller.ts +8 -10
  200. package/src/domain/agentexecution/list-index.ts +24 -0
  201. package/src/domain/agentexecution/steps.ts +118 -115
  202. package/src/domain/agentexecution/usage.ts +38 -17
  203. package/src/domain/artifact/controller.ts +28 -18
  204. package/src/domain/artifact/list-index.ts +22 -0
  205. package/src/domain/iampolicy/__tests__/access-lists.test.ts +120 -6
  206. package/src/domain/iampolicy/__tests__/constants.test.ts +26 -4
  207. package/src/domain/iampolicy/__tests__/controller.test.ts +55 -1
  208. package/src/domain/iampolicy/__tests__/grant-path.test.ts +3 -3
  209. package/src/domain/iampolicy/__tests__/steps.test.ts +123 -1
  210. package/src/domain/iampolicy/access-lists.ts +98 -23
  211. package/src/domain/iampolicy/constants.ts +50 -6
  212. package/src/domain/iampolicy/controller.ts +30 -7
  213. package/src/domain/iampolicy/resource-store.ts +3 -6
  214. package/src/domain/iampolicy/steps.ts +89 -7
  215. package/src/domain/session/list-index.ts +28 -0
  216. package/src/domain/session/steps.ts +108 -123
  217. package/src/domain/workflow/registry/data/task-kind-registry.json +1 -0
  218. package/src/domain/workflowexecution/__tests__/list-read-scope-summaries.test.ts +155 -99
  219. package/src/domain/workflowexecution/__tests__/workflowexecution.test.ts +193 -31
  220. package/src/domain/workflowexecution/controller.ts +54 -82
  221. package/src/domain/workflowexecution/execution-filter.ts +20 -7
  222. package/src/domain/workflowexecution/get-execution-summary.ts +31 -24
  223. package/src/domain/workflowexecution/list-index.ts +22 -0
  224. package/src/domain/workflowexecution/list-pending-approvals.ts +82 -25
  225. package/src/domain/workflowexecution/queries.ts +134 -23
  226. package/src/extensions/__tests__/iam-policy-points.test.ts +30 -0
  227. package/src/extensions/__tests__/registry.test.ts +3 -0
  228. package/src/extensions/drivers.ts +12 -0
  229. package/src/extensions/gate-slots.ts +10 -0
  230. package/src/extensions/list-read-scope.ts +9 -12
  231. package/src/extensions/principal-display.ts +37 -0
  232. package/src/extensions/registry.ts +19 -0
  233. package/src/index.ts +19 -1
  234. package/src/pipeline/apiresource-meta.ts +21 -0
  235. package/src/pipeline/steps/__tests__/list-page.test.ts +186 -0
  236. package/src/pipeline/steps/list-page.ts +284 -0
  237. package/src/query/activity/handler.ts +57 -78
  238. package/src/store/README.md +51 -32
  239. package/src/store/__tests__/list-index.measure.test.ts +293 -0
  240. package/src/store/__tests__/list-index.test.ts +328 -0
  241. package/src/store/__tests__/store-contract.ts +346 -3
  242. package/src/store/interface.ts +39 -0
  243. package/src/store/list-index.ts +465 -0
  244. package/src/store/logger.ts +8 -1
  245. package/src/store/postgres/__tests__/list-index-repair.test.ts +133 -0
  246. package/src/store/postgres/__tests__/migrations.test.ts +27 -19
  247. package/src/store/postgres/__tests__/store-contract.test.ts +50 -3
  248. package/src/store/postgres/migrations.ts +69 -7
  249. package/src/store/postgres/store.ts +485 -29
  250. package/src/store/sqlite/__tests__/migrations.test.ts +51 -7
  251. package/src/store/sqlite/__tests__/store-contract.test.ts +70 -16
  252. package/src/store/sqlite/__tests__/support.ts +12 -3
  253. package/src/store/sqlite/migrations.ts +57 -6
  254. package/src/store/sqlite/store.ts +462 -21
@@ -1,27 +1,32 @@
1
1
  /**
2
- * Pins the ListReadScope arm of BOTH getExecutionSummary handlers (C2
3
- * Stage 4's ExecutionReadScope port, absorbed into the generalized seam
4
- * by 20260830.01.sp.list-read-scoping):
2
+ * Pins the read of BOTH getExecutionSummary handlers through the list
3
+ * index and the ListReadScope's restrict verb:
5
4
  *
6
- * - a composed scope narrows the aggregation to authorized ids ∩ the
7
- * requested org (the Java GetExecutionSummary baseline);
8
- * - an EMPTY authorized set answers the proto DEFAULT INSTANCE — the
9
- * conformance-pinned multi-tenant zero shape (workflow: success_rate
10
- * 0 and NO cost summary — the exact opposite of the OSS -1 sentinel
11
- * and always-present zero cost) falls out of the scoping;
12
- * - NO scope composed = the full scan, org NOT consulted (the OSS
13
- * single-user semantics, byte-identity guarded here as well as by
14
- * the conformance rosters).
5
+ * - the rows are the requested org's, within the time window, read
6
+ * through the index in every posture (a row with no creation stamp is
7
+ * kept, as the aggregation loop keeps it);
8
+ * - a composed scope is offered exactly those rows and never asked to
9
+ * enumerate (its enumeration verb throws here); the aggregation runs
10
+ * over the rows it keeps;
11
+ * - when it keeps NONE the answer is the proto DEFAULT INSTANCE — the
12
+ * conformance-pinned multi-tenant zero shape (workflow: success_rate 0
13
+ * and NO cost summary, the opposite of the OSS -1 sentinel and
14
+ * always-present zero cost);
15
+ * - NO scope composed = every row of the org and window, and the OSS
16
+ * zero pins hold.
15
17
  */
16
- import { create, toBinary } from "@bufbuild/protobuf";
17
- import { describe, expect, it } from "vitest";
18
+ import { create } from "@bufbuild/protobuf";
19
+ import { afterEach, describe, expect, it } from "vitest";
18
20
 
19
21
  import { AgentExecutionSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
20
22
  import { ExecutionPhase as AgentExecutionPhase } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
21
23
  import { GetAgentExecutionSummaryRequestSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/io_pb";
22
24
  import { WorkflowExecutionSchema } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/api_pb";
23
25
  import { ExecutionPhase } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/enum_pb";
24
- import { GetExecutionSummaryRequestSchema } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/io_pb";
26
+ import {
27
+ GetExecutionSummaryRequestSchema,
28
+ SummaryTimeWindow,
29
+ } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/io_pb";
25
30
  import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
26
31
 
27
32
  import { createLogger } from "../../../boot/logger.js";
@@ -29,6 +34,8 @@ import type { ListReadScope } from "../../../extensions/list-read-scope.js";
29
34
  import { testCallerIdentity } from "../../../pipeline/__tests__/support.js";
30
35
  import { newPermissiveSingleTeamAuthorizer } from "../../../pipeline/steps/authorize.js";
31
36
  import type { Store } from "../../../store/interface.js";
37
+ import { tempStore } from "../../../store/sqlite/__tests__/support.js";
38
+ import type { TempStore } from "../../../store/sqlite/__tests__/support.js";
32
39
 
33
40
  import { getExecutionSummary as getAgentSummary } from "../../agentexecution/usage.js";
34
41
  import { getExecutionSummary as getWorkflowSummary } from "../get-execution-summary.js";
@@ -40,73 +47,135 @@ const silentLogger = createLogger({
40
47
  });
41
48
 
42
49
  const caller = testCallerIdentity();
50
+ const NOW_SECONDS = BigInt(Math.floor(Date.now() / 1000));
51
+ const DAY = 24n * 60n * 60n;
43
52
 
44
- function scopeOf(ids: ReadonlyArray<string>): ListReadScope {
53
+ interface RecordingScope extends ListReadScope {
54
+ readonly offered: Array<{ kind: ApiResourceKind; ids: string[] }>;
55
+ }
56
+
57
+ /** A scope that keeps `allowed`, records what it was offered, and refuses to enumerate. */
58
+ function restrictingScope(allowed: ReadonlyArray<string>): RecordingScope {
59
+ const offered: Array<{ kind: ApiResourceKind; ids: string[] }> = [];
45
60
  return {
46
- authorizedResourceIds: () => Promise.resolve(new Set(ids)),
47
- restrictListEntries: () => Promise.resolve(new Set<string>()),
61
+ offered,
62
+ authorizedResourceIds: () =>
63
+ Promise.reject(new Error("the summaries never enumerate")),
64
+ restrictListEntries: (_caller, kind, entries) => {
65
+ offered.push({ kind, ids: entries.map((e) => e.id).sort() });
66
+ return Promise.resolve(
67
+ new Set(entries.map((e) => e.id).filter((id) => allowed.includes(id))),
68
+ );
69
+ },
48
70
  };
49
71
  }
50
72
 
51
- /** A store whose listResources answers the given serialized rows. */
52
- function listOnlyStore(rows: Uint8Array[]): Store {
53
- return { listResources: () => Promise.resolve(rows) } as unknown as Store;
54
- }
73
+ let temp: TempStore | undefined;
74
+ afterEach(async () => {
75
+ await temp?.cleanup();
76
+ temp = undefined;
77
+ });
55
78
 
56
- function workflowExecRow(id: string, org: string, phase: ExecutionPhase) {
57
- return toBinary(
58
- WorkflowExecutionSchema,
59
- create(WorkflowExecutionSchema, {
60
- metadata: { id, name: id, slug: id, org },
61
- status: { phase },
62
- }),
63
- );
79
+ function audit(createdDaysAgo: bigint | undefined) {
80
+ return createdDaysAgo === undefined
81
+ ? {}
82
+ : {
83
+ audit: {
84
+ specAudit: {
85
+ createdAt: {
86
+ seconds: NOW_SECONDS - createdDaysAgo * DAY,
87
+ nanos: 0,
88
+ },
89
+ },
90
+ },
91
+ };
64
92
  }
65
93
 
66
- function agentExecRow(id: string, org: string, phase: AgentExecutionPhase) {
67
- return toBinary(
68
- AgentExecutionSchema,
69
- create(AgentExecutionSchema, {
70
- metadata: { id, name: id, org },
71
- status: { phase },
72
- }),
73
- );
94
+ async function workflowStore(): Promise<Store> {
95
+ temp = tempStore();
96
+ const rows: Array<[string, string, ExecutionPhase, bigint | undefined]> = [
97
+ ["wfe_mine_done", "acme", ExecutionPhase.EXECUTION_COMPLETED, 1n],
98
+ ["wfe_mine_run", "acme", ExecutionPhase.EXECUTION_IN_PROGRESS, undefined],
99
+ ["wfe_not_mine", "acme", ExecutionPhase.EXECUTION_FAILED, 1n],
100
+ ["wfe_stale", "acme", ExecutionPhase.EXECUTION_FAILED, 30n],
101
+ ["wfe_other_org", "rival", ExecutionPhase.EXECUTION_FAILED, 1n],
102
+ ];
103
+ for (const [id, org, phase, days] of rows) {
104
+ await temp.store.saveResource(
105
+ ApiResourceKind.workflow_execution,
106
+ id,
107
+ WorkflowExecutionSchema,
108
+ create(WorkflowExecutionSchema, {
109
+ metadata: { id, name: id, slug: id, org },
110
+ status: { phase, ...audit(days) },
111
+ }),
112
+ );
113
+ }
114
+ return temp.store;
74
115
  }
75
116
 
76
- describe("workflow getExecutionSummary read scope", () => {
77
- const rows = [
78
- workflowExecRow("wfe_mine_done", "acme", ExecutionPhase.EXECUTION_COMPLETED),
79
- workflowExecRow("wfe_mine_run", "acme", ExecutionPhase.EXECUTION_IN_PROGRESS),
80
- // Authorized but in ANOTHER org — the org intersection must drop it.
81
- workflowExecRow("wfe_other_org", "rival", ExecutionPhase.EXECUTION_FAILED),
82
- // In the org but NOT authorized — the id set must drop it.
83
- workflowExecRow("wfe_not_mine", "acme", ExecutionPhase.EXECUTION_FAILED),
117
+ async function agentStore(): Promise<Store> {
118
+ temp = tempStore();
119
+ const rows: Array<[string, string]> = [
120
+ ["aexec_mine", "acme"],
121
+ ["aexec_not_mine", "acme"],
122
+ ["aexec_other_org", "rival"],
84
123
  ];
85
-
86
- function deps(scope: ListReadScope | undefined) {
87
- return {
88
- store: listOnlyStore(rows),
89
- logger: silentLogger,
90
- listReadScope: scope,
91
- };
124
+ for (const [id, org] of rows) {
125
+ await temp.store.saveResource(
126
+ ApiResourceKind.agent_execution,
127
+ id,
128
+ AgentExecutionSchema,
129
+ create(AgentExecutionSchema, {
130
+ metadata: { id, name: id, org },
131
+ status: {
132
+ phase: AgentExecutionPhase.EXECUTION_IN_PROGRESS,
133
+ ...audit(1n),
134
+ },
135
+ }),
136
+ );
92
137
  }
138
+ return temp.store;
139
+ }
140
+
141
+ const LAST_7D = { org: "acme", timeWindow: SummaryTimeWindow.LAST_7D };
93
142
 
94
- it("narrows to authorized ids ∩ the requested org", async () => {
143
+ describe("workflow getExecutionSummary read", () => {
144
+ it("offers the scope the org's rows within the window, and aggregates the ones it keeps", async () => {
145
+ const scope = restrictingScope([
146
+ "wfe_mine_done",
147
+ "wfe_mine_run",
148
+ "wfe_other_org",
149
+ ]);
95
150
  const summary = await getWorkflowSummary(
96
- deps(scopeOf(["wfe_mine_done", "wfe_mine_run", "wfe_other_org"])),
97
- create(GetExecutionSummaryRequestSchema, { org: "acme" }),
151
+ {
152
+ store: await workflowStore(),
153
+ logger: silentLogger,
154
+ listReadScope: scope,
155
+ },
156
+ create(GetExecutionSummaryRequestSchema, LAST_7D),
98
157
  caller,
99
158
  );
159
+ expect(scope.offered).toEqual([
160
+ {
161
+ kind: ApiResourceKind.workflow_execution,
162
+ ids: ["wfe_mine_done", "wfe_mine_run", "wfe_not_mine"],
163
+ },
164
+ ]);
100
165
  expect(summary.totalCount).toBe(2);
101
166
  expect(summary.activeCount).toBe(1);
102
167
  // One terminal (completed) — a real 100%, not the -1 sentinel.
103
168
  expect(summary.successRate).toBe(1);
104
169
  });
105
170
 
106
- it("an empty authorized set answers the default instance — the multi-tenant zero shape", async () => {
171
+ it("answers the default instance when the scope keeps none — the multi-tenant zero shape", async () => {
107
172
  const summary = await getWorkflowSummary(
108
- deps(scopeOf([])),
109
- create(GetExecutionSummaryRequestSchema, { org: "acme" }),
173
+ {
174
+ store: await workflowStore(),
175
+ logger: silentLogger,
176
+ listReadScope: restrictingScope([]),
177
+ },
178
+ create(GetExecutionSummaryRequestSchema, LAST_7D),
110
179
  caller,
111
180
  );
112
181
  expect(summary.successRate).toBe(0);
@@ -115,48 +184,52 @@ describe("workflow getExecutionSummary read scope", () => {
115
184
  expect(summary.avgDuration).toBeUndefined();
116
185
  });
117
186
 
118
- it("no scope composed = the full scan; org is NOT consulted (OSS byte-identity)", async () => {
187
+ it("with no scope composed counts every row of the org and window, and keeps the OSS zero pins", async () => {
119
188
  const summary = await getWorkflowSummary(
120
- deps(undefined),
121
- create(GetExecutionSummaryRequestSchema, { org: "acme" }),
189
+ {
190
+ store: await workflowStore(),
191
+ logger: silentLogger,
192
+ listReadScope: undefined,
193
+ },
194
+ create(GetExecutionSummaryRequestSchema, LAST_7D),
122
195
  caller,
123
196
  );
124
- // All four rows counted, the rival org's included; and the OSS zero
125
- // pins hold: the -1 sentinel family and the ALWAYS-present cost
126
- // summary.
127
- expect(summary.totalCount).toBe(4);
197
+ // The org's three rows in the window, the unstamped one included; the
198
+ // stale row and the rival org's row are not read.
199
+ expect(summary.totalCount).toBe(3);
128
200
  expect(summary.totalCost).toBeDefined();
129
201
  });
130
202
  });
131
203
 
132
- describe("agent getExecutionSummary read scope", () => {
133
- const rows = [
134
- agentExecRow("aexec_mine", "acme", AgentExecutionPhase.EXECUTION_IN_PROGRESS),
135
- agentExecRow("aexec_other_org", "rival", AgentExecutionPhase.EXECUTION_IN_PROGRESS),
136
- agentExecRow("aexec_not_mine", "acme", AgentExecutionPhase.EXECUTION_IN_PROGRESS),
137
- ];
138
-
139
- function deps(scope: ListReadScope | undefined) {
204
+ describe("agent getExecutionSummary read", () => {
205
+ function deps(store: Store, scope: ListReadScope | undefined) {
140
206
  return {
141
- store: listOnlyStore(rows),
207
+ store,
142
208
  logger: silentLogger,
143
209
  authorizer: newPermissiveSingleTeamAuthorizer(),
144
210
  listReadScope: scope,
145
211
  };
146
212
  }
147
213
 
148
- it("narrows to authorized ids ∩ the requested org", async () => {
214
+ it("offers the scope the org's rows as agent executions, and aggregates the ones it keeps", async () => {
215
+ const scope = restrictingScope(["aexec_mine", "aexec_other_org"]);
149
216
  const summary = await getAgentSummary(
150
- deps(scopeOf(["aexec_mine", "aexec_other_org"])),
217
+ deps(await agentStore(), scope),
151
218
  create(GetAgentExecutionSummaryRequestSchema, { org: "acme" }),
152
219
  caller,
153
220
  );
221
+ expect(scope.offered).toEqual([
222
+ {
223
+ kind: ApiResourceKind.agent_execution,
224
+ ids: ["aexec_mine", "aexec_not_mine"],
225
+ },
226
+ ]);
154
227
  expect(summary.activeCount).toBe(1);
155
228
  });
156
229
 
157
- it("an empty authorized set answers the default instance", async () => {
230
+ it("answers the default instance when the scope keeps none", async () => {
158
231
  const summary = await getAgentSummary(
159
- deps(scopeOf([])),
232
+ deps(await agentStore(), restrictingScope([])),
160
233
  create(GetAgentExecutionSummaryRequestSchema, { org: "acme" }),
161
234
  caller,
162
235
  );
@@ -165,29 +238,12 @@ describe("agent getExecutionSummary read scope", () => {
165
238
  expect(summary.avgDuration).toBeUndefined();
166
239
  });
167
240
 
168
- it("no scope composed = the full scan across orgs (OSS byte-identity)", async () => {
241
+ it("with no scope composed counts every row of the org", async () => {
169
242
  const summary = await getAgentSummary(
170
- deps(undefined),
171
- create(GetAgentExecutionSummaryRequestSchema, { org: "acme" }),
172
- caller,
173
- );
174
- expect(summary.activeCount).toBe(3);
175
- });
176
-
177
- it("the scope receives the kind it is scoping (agent_execution here)", async () => {
178
- const kinds: ApiResourceKind[] = [];
179
- const recordingScope: ListReadScope = {
180
- authorizedResourceIds: (_caller, kind) => {
181
- kinds.push(kind);
182
- return Promise.resolve(new Set<string>());
183
- },
184
- restrictListEntries: () => Promise.resolve(new Set<string>()),
185
- };
186
- await getAgentSummary(
187
- deps(recordingScope),
243
+ deps(await agentStore(), undefined),
188
244
  create(GetAgentExecutionSummaryRequestSchema, { org: "acme" }),
189
245
  caller,
190
246
  );
191
- expect(kinds).toEqual([ApiResourceKind.agent_execution]);
247
+ expect(summary.activeCount).toBe(2);
192
248
  });
193
249
  });
@@ -9,10 +9,11 @@
9
9
  *
10
10
  * Load-bearing pins the zero-record conformance arm cannot cover:
11
11
  * - list's legacy-phase fallback vs filter.phases precedence, the T13
12
- * structured filter over seeded rows, the started_at-descending sort
13
- * default, and the total_pages placeholder;
12
+ * structured filter over seeded rows, the newest-created default
13
+ * order and its cursor pages (a token refused on another request),
14
+ * and a non-default sort answering its first page with no token;
14
15
  * - listByWorkflow matching EITHER spec.workflow_id or
15
- * spec.workflow_instance_id;
16
+ * spec.workflow_instance_id, and paging the workflow's runs;
16
17
  * - getEventLog cursor pagination over REAL persisted events: has_more
17
18
  * from the +1 fetch, latest_sequence, the 500 cap, the multi-type
18
19
  * in-memory filter, and the malformed-record skip;
@@ -20,7 +21,8 @@
20
21
  * avg duration, failure ranks, cost breakdown, workflow scoping,
21
22
  * time-window cutoff);
22
23
  * - listPendingApprovals projection (task_name not task_id; requester
23
- * from spec audit) and its pre-truncation total_count;
24
+ * from spec audit), its pre-truncation total_count, and its cursor
25
+ * splitting one execution's waiting tasks across pages;
24
26
  * - update's status-clearing standard build and delete's audit-trail
25
27
  * return, over the wire.
26
28
  */
@@ -246,7 +248,10 @@ function eventRecord(
246
248
 
247
249
  describe("get / list / listByWorkflow over the wire", () => {
248
250
  it("get answers NotFound for an unknown id", async () => {
249
- await expectCode(() => query.get({ value: "wfexec_missing" }), Code.NotFound);
251
+ await expectCode(
252
+ () => query.get({ value: "wfexec_missing" }),
253
+ Code.NotFound,
254
+ );
250
255
  });
251
256
 
252
257
  it("get round-trips a seeded execution", async () => {
@@ -269,9 +274,7 @@ describe("get / list / listByWorkflow over the wire", () => {
269
274
  (entry) => entry.status?.phase === ExecutionPhase.EXECUTION_FAILED,
270
275
  ),
271
276
  ).toBe(true);
272
- expect(legacy.entries.map((entry) => entry.metadata?.id)).toContain(
273
- failed,
274
- );
277
+ expect(legacy.entries.map((entry) => entry.metadata?.id)).toContain(failed);
275
278
  expect(legacy.totalPages).toBe(1);
276
279
 
277
280
  // filter.phases supersedes the legacy field: a COMPLETED filter with a
@@ -288,17 +291,115 @@ describe("get / list / listByWorkflow over the wire", () => {
288
291
  ).toBe(true);
289
292
  });
290
293
 
291
- it("list defaults to started_at descending", async () => {
292
- const early = await seed(
293
- seedInput({ startedAt: "2026-05-23T10:00:00Z" }),
294
+ it("list defaults to newest created first, so a queued run leads", async () => {
295
+ const org = "order-org";
296
+ const started = await seed(
297
+ seedInput({
298
+ org,
299
+ startedAt: "2026-05-23T14:00:00Z",
300
+ createdAtMs: Date.parse("2026-05-23T09:00:00Z"),
301
+ }),
302
+ );
303
+ const queued = await seed(
304
+ seedInput({
305
+ org,
306
+ phase: ExecutionPhase.EXECUTION_PENDING,
307
+ createdAtMs: Date.parse("2026-05-23T10:00:00Z"),
308
+ }),
294
309
  );
295
- const late = await seed(seedInput({ startedAt: "2026-05-23T14:00:00Z" }));
296
310
 
297
- const result = await query.list({});
298
- const positions = new Map(
299
- result.entries.map((entry, index) => [entry.metadata?.id, index]),
311
+ const result = await query.list({ org });
312
+ expect(result.entries.map((entry) => entry.metadata?.id)).toEqual([
313
+ queued,
314
+ started,
315
+ ]);
316
+ });
317
+
318
+ it("list pages newest created first with no gap or duplicate, and refuses a token on another request", async () => {
319
+ const org = "paging-org";
320
+ const base = Date.parse("2026-09-01T00:00:00Z");
321
+ const newestFirst: string[] = [];
322
+ for (let i = 0; i < 5; i++) {
323
+ newestFirst.unshift(
324
+ await seed(seedInput({ org, createdAtMs: base + i * 1000 })),
325
+ );
326
+ }
327
+ await seed(seedInput({ org: "other-org", createdAtMs: base + 10_000 }));
328
+
329
+ const walked: string[] = [];
330
+ let pageToken = "";
331
+ let firstToken = "";
332
+ do {
333
+ const page = await query.list({ org, pageSize: 2, pageToken });
334
+ expect(page.entries.length).toBeLessThanOrEqual(2);
335
+ expect(page.totalPages).toBe(page.nextPageToken === "" ? 1 : 0);
336
+ walked.push(...page.entries.map((entry) => entry.metadata!.id));
337
+ firstToken = firstToken === "" ? page.nextPageToken : firstToken;
338
+ pageToken = page.nextPageToken;
339
+ } while (pageToken !== "");
340
+ expect(walked).toEqual(newestFirst);
341
+
342
+ const replayed = await expectCode(
343
+ () =>
344
+ query.list({ org: "other-org", pageSize: 2, pageToken: firstToken }),
345
+ Code.InvalidArgument,
346
+ );
347
+ expect(replayed.rawMessage).toBe(
348
+ "page_token was issued for a different request",
300
349
  );
301
- expect(positions.get(late)).toBeLessThan(positions.get(early)!);
350
+ });
351
+
352
+ it("a sort other than creation sorts the whole set and returns its first page with no token", async () => {
353
+ const org = "sorted-org";
354
+ const hours = ["10", "12", "14"];
355
+ for (const hour of hours) {
356
+ await seed(
357
+ seedInput({
358
+ org,
359
+ startedAt: `2026-05-23T${hour}:00:00Z`,
360
+ createdAtMs: Date.parse(`2026-05-20T${hour}:00:00Z`),
361
+ }),
362
+ );
363
+ }
364
+
365
+ const page = await query.list({
366
+ org,
367
+ pageSize: 2,
368
+ sortField: ExecutionSortField.STARTED_AT,
369
+ sortAscending: true,
370
+ });
371
+ expect(page.entries.map((entry) => entry.status?.startedAt)).toEqual([
372
+ "2026-05-23T10:00:00Z",
373
+ "2026-05-23T12:00:00Z",
374
+ ]);
375
+ expect(page.nextPageToken).toBe("");
376
+ });
377
+
378
+ it("listByWorkflow pages the workflow's runs", async () => {
379
+ const base = Date.parse("2026-09-03T00:00:00Z");
380
+ const newestFirst: string[] = [];
381
+ for (let i = 0; i < 3; i++) {
382
+ newestFirst.unshift(
383
+ await seed(
384
+ seedInput({ workflowId: "wf_paged", createdAtMs: base + i * 1000 }),
385
+ ),
386
+ );
387
+ }
388
+
389
+ const first = await query.listByWorkflow({
390
+ workflowId: "wf_paged",
391
+ pageSize: 2,
392
+ });
393
+ expect(first.nextPageToken).not.toBe("");
394
+ const second = await query.listByWorkflow({
395
+ workflowId: "wf_paged",
396
+ pageSize: 2,
397
+ pageToken: first.nextPageToken,
398
+ });
399
+ expect(second.nextPageToken).toBe("");
400
+ expect(
401
+ [...first.entries, ...second.entries].map((entry) => entry.metadata?.id),
402
+ ).toEqual(newestFirst);
302
403
  });
303
404
 
304
405
  it("listByWorkflow requires workflow_id and matches either spec reference", async () => {
@@ -343,12 +444,17 @@ describe("getEventLog over the wire (CW-7 pagination contract)", () => {
343
444
  const records: WorkflowExecutionEventRecord[] = [];
344
445
  for (let sequence = 1; sequence <= 5; sequence++) {
345
446
  records.push(
346
- eventRecord(id, sequence, WorkflowEventType.task_started, `t${sequence}`),
447
+ eventRecord(
448
+ id,
449
+ sequence,
450
+ WorkflowEventType.task_started,
451
+ `t${sequence}`,
452
+ ),
347
453
  );
348
454
  }
349
- expect(
350
- await server.store.appendWorkflowExecutionEvents(id, records),
351
- ).toBe(5);
455
+ expect(await server.store.appendWorkflowExecutionEvents(id, records)).toBe(
456
+ 5,
457
+ );
352
458
 
353
459
  const page1 = await query.getEventLog({ executionId: id, pageSize: 2 });
354
460
  expect(page1.events).toHaveLength(2);
@@ -360,10 +466,7 @@ describe("getEventLog over the wire (CW-7 pagination contract)", () => {
360
466
  pageSize: 2,
361
467
  afterSequence: page1.latestSequence,
362
468
  });
363
- expect(page2.events.map((event) => event.sequenceNumber)).toEqual([
364
- 3n,
365
- 4n,
366
- ]);
469
+ expect(page2.events.map((event) => event.sequenceNumber)).toEqual([3n, 4n]);
367
470
  expect(page2.hasMore).toBe(true);
368
471
 
369
472
  const page3 = await query.getEventLog({
@@ -444,7 +547,10 @@ describe("getEventLog over the wire (CW-7 pagination contract)", () => {
444
547
  ]);
445
548
  const tailPage = await query.getEventLog({ executionId: tail });
446
549
  expect(tailPage.events.map((event) => event.sequenceNumber)).toEqual([1n]);
447
- expect(tailPage.latestSequence, "malformed rows never advance the cursor").toBe(1n);
550
+ expect(
551
+ tailPage.latestSequence,
552
+ "malformed rows never advance the cursor",
553
+ ).toBe(1n);
448
554
  });
449
555
 
450
556
  it("caps page_size at 500 and defaults to 100", async () => {
@@ -548,7 +654,10 @@ describe("getExecutionSummary over the wire", () => {
548
654
  });
549
655
  expect(windowed.totalCount).toBe(0);
550
656
  expect(windowed.successRate).toBe(-1);
551
- expect(windowed.totalCost, "zero cost summary is ALWAYS present").toBeDefined();
657
+ expect(
658
+ windowed.totalCost,
659
+ "zero cost summary is ALWAYS present",
660
+ ).toBeDefined();
552
661
  expect(windowed.avgDuration, "no completed runs → absent").toBeUndefined();
553
662
 
554
663
  const allTime = await query.getExecutionSummary({
@@ -603,9 +712,9 @@ describe("listPendingApprovals over the wire", () => {
603
712
  expect(entry?.workflowName).toBe("Approval Flow");
604
713
  expect(entry?.requester).toBe("usr_seeder");
605
714
  expect(entry?.uiHint).toBe("approval-form");
606
- expect(
607
- result.entries.some((item) => item.taskName === "paused-gate"),
608
- ).toBe(false);
715
+ expect(result.entries.some((item) => item.taskName === "paused-gate")).toBe(
716
+ false,
717
+ );
609
718
  });
610
719
 
611
720
  it("total_count is the pre-truncation total", async () => {
@@ -621,6 +730,54 @@ describe("listPendingApprovals over the wire", () => {
621
730
  expect(page.entries).toHaveLength(1);
622
731
  expect(page.totalCount).toBeGreaterThanOrEqual(3);
623
732
  });
733
+
734
+ it("pages newest execution first, then each execution's waiting tasks in order, across page boundaries", async () => {
735
+ const org = "approvals-paging-org";
736
+ const waiting = (taskName: string) => ({
737
+ taskName,
738
+ status: WorkflowTaskStatus.WORKFLOW_TASK_WAITING_APPROVAL,
739
+ });
740
+ const older = await seed(
741
+ seedInput({
742
+ org,
743
+ phase: ExecutionPhase.EXECUTION_IN_PROGRESS,
744
+ createdAtMs: Date.parse("2026-09-04T00:00:00Z"),
745
+ tasks: [waiting("older-gate")],
746
+ }),
747
+ );
748
+ const newer = await seed(
749
+ seedInput({
750
+ org,
751
+ phase: ExecutionPhase.EXECUTION_IN_PROGRESS,
752
+ createdAtMs: Date.parse("2026-09-04T00:00:01Z"),
753
+ tasks: [waiting("first-gate"), waiting("second-gate")],
754
+ }),
755
+ );
756
+
757
+ const walked: Array<[string, string]> = [];
758
+ let pageToken = "";
759
+ do {
760
+ const page = await query.listPendingApprovals({
761
+ org,
762
+ pageSize: 1,
763
+ pageToken,
764
+ });
765
+ expect(page.totalCount).toBe(3);
766
+ walked.push(
767
+ ...page.entries.map((entry): [string, string] => [
768
+ entry.executionId,
769
+ entry.taskName,
770
+ ]),
771
+ );
772
+ pageToken = page.nextPageToken;
773
+ } while (pageToken !== "");
774
+
775
+ expect(walked).toEqual([
776
+ [newer, "first-gate"],
777
+ [newer, "second-gate"],
778
+ [older, "older-gate"],
779
+ ]);
780
+ });
624
781
  });
625
782
 
626
783
  describe("updateStatus over the wire", () => {
@@ -717,7 +874,9 @@ describe("subscribe over the wire (streaming smoke through the real transport)",
717
874
  }
718
875
  }, Code.InvalidArgument);
719
876
  await expectCode(async () => {
720
- for await (const _ of query.subscribe({ executionId: "wfexec_missing" })) {
877
+ for await (const _ of query.subscribe({
878
+ executionId: "wfexec_missing",
879
+ })) {
721
880
  break;
722
881
  }
723
882
  }, Code.NotFound);
@@ -1256,7 +1415,10 @@ describe("submitFileDecision over the wire (propagation through the REAL in-proc
1256
1415
  const changeSet = child.status?.fileChangeSets.find(
1257
1416
  (cs) => cs.id === changeSetId,
1258
1417
  );
1259
- expect(changeSet?.decisions, "the child recorded the decision").toHaveLength(1);
1418
+ expect(
1419
+ changeSet?.decisions,
1420
+ "the child recorded the decision",
1421
+ ).toHaveLength(1);
1260
1422
  });
1261
1423
 
1262
1424
  it("the child's actionable rejection PROPAGATES unchanged (the file-decision asymmetry)", async () => {