@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,40 +1,59 @@
1
1
  # store/ — the persistence layer
2
2
 
3
3
  Ports `backend/libs/go/store` (D2 §3, DD-003): `interface.ts` is the
4
- driver-agnostic contract (surface-for-surface with Go's `store.Store`, plus
5
- the consolidated members Go kept behind the `DB()` escape hatch — bootstrap
6
- state, signal dedupe, MCP OAuth). Two drivers implement it:
4
+ driver-agnostic contract (surface-for-surface with Go's `store.Store`, plus the
5
+ consolidated members Go kept behind the `DB()` escape hatch — bootstrap state,
6
+ signal dedupe, MCP OAuth). Two drivers implement it:
7
7
 
8
- - `sqlite/` — the laptop-tier default (`node:sqlite`, zero-config, one
9
- file) with the versioned migration chain (v1–v6 adopted from Go
10
- DDL-faithful, v7 = the OD-3 consolidation).
8
+ - `sqlite/` — the laptop-tier default (`node:sqlite`, zero-config, one file)
9
+ with its versioned migration chain (v1–v6 adopted from Go DDL-faithful, then
10
+ this port's own steps, each named at its constant in `sqlite/migrations.ts`).
11
11
  - `postgres/` — the self-host/team tier (DD-010: `pg` pool, row-level
12
- `FOR UPDATE` atomicity, its own independent migration chain v1 with real
13
- indexes, `tsvector`/`tsquery` search per DD-009).
12
+ `FOR UPDATE` atomicity, its own independent migration chain with real indexes,
13
+ `tsvector`/`tsquery` search per DD-009).
14
14
 
15
- Selection is boot config (`boot/config.ts`): `DATABASE_URL` set → Postgres
16
- (it wins when both are set — `DB_PATH` always has a default value); else
17
- sqlite on `DB_PATH`. Domain code depends on `interface.ts` only — never on
18
- a driver directory. Driver-neutral helpers shared by both drivers live
19
- here (`proto-fields.ts` reflection + scan semantics, `logger.ts`).
15
+ Selection is boot config (`boot/config.ts`): `DATABASE_URL` set → Postgres (it
16
+ wins when both are set — `DB_PATH` always has a default value); else sqlite on
17
+ `DB_PATH`. Domain code depends on `interface.ts` only — never on a driver
18
+ directory. Driver-neutral helpers shared by both drivers live here
19
+ (`proto-fields.ts` reflection + scan semantics, `list-index.ts`, `logger.ts`).
20
+
21
+ ## The list index
22
+
23
+ A list lane reads one organization's or one parent's rows in creation order
24
+ through `Store.queryResources`, never by decoding the whole kind.
25
+ `list-index.ts` is the one derivation: a kind's declaration (beside its lanes,
26
+ `domain/<kind>/list-index.ts`) names its keys as data, the composition root
27
+ opens the store with the one list of declarations (`boot/list-indexes.ts`), and
28
+ every driver keeps a declared row's facts (organization, creation instant, keys)
29
+ in the same statement as the row.
30
+
31
+ Reads are exact whoever wrote the rows. A row whose facts are not proven current
32
+ — written by a binary that does not know the index, as the old pod does while a
33
+ roll overlaps it with the new one, or under another revision of the declaration
34
+ — is evaluated from its bytes and repaired by a compare-and-set on those bytes.
35
+ The migrations that add the index are schema only; the reconciliation each
36
+ driver runs at open derives every unproven row, so the unproven set is empty in
37
+ steady state and reads stay one index range. Every read shape names or forces
38
+ its index, because a freshly written table has no planner statistics.
39
+
40
+ ## Contract and continuity
20
41
 
21
42
  The behavioral contract both drivers must satisfy identically is
22
- `__tests__/store-contract.ts`, invoked by each driver's
23
- `store-contract.test.ts` (sqlite always; Postgres under
24
- `TEST_DATABASE_URL` — visible skips locally, a real service container in
25
- CI). Driver-physical behavior stays in each driver's own tests. One
26
- deliberate semantic difference is recorded in DD-010: sqlite serializes
27
- ALL writes globally as a side effect of its single synchronous connection;
28
- Postgres guarantees per-resource atomicity only — the interface contract
29
- is the narrower one.
30
-
31
- Schema continuity across the Go cutover (any Go-created database adopts
32
- forward) is proven by `sqlite/__tests__/migrations.test.ts` against a real
33
- Go-created fixture. The fixture is FROZEN: the Go server retired
34
- (go-server-retirement, D4 #25), its schema can no longer change, and the
35
- committed v6 dump is the permanent record of what real pre-cutover
36
- databases look like. The generator script
37
- (`scripts/regen-go-db-fixture.sh`) lived until #25 and remains in git
38
- history should the fixture ever need forensic regeneration. The Postgres
39
- chain has no adoption story by construction — no Postgres database
40
- predates its driver.
43
+ `__tests__/store-contract.ts`, invoked by each driver's `store-contract.test.ts`
44
+ (sqlite always; Postgres under `TEST_DATABASE_URL` — visible skips locally, a
45
+ real service container in CI). Driver-physical behavior stays in each driver's
46
+ own tests. One deliberate semantic difference is recorded in DD-010: sqlite
47
+ serializes ALL writes globally as a side effect of its single synchronous
48
+ connection; Postgres guarantees per-resource atomicity only — the interface
49
+ contract is the narrower one.
50
+
51
+ Schema continuity across the Go cutover (any Go-created database adopts forward)
52
+ is proven by `sqlite/__tests__/migrations.test.ts` against a real Go-created
53
+ fixture. The fixture is FROZEN: the Go server retired (go-server-retirement, D4
54
+ #25), its schema can no longer change, and the committed v6 dump is the
55
+ permanent record of what real pre-cutover databases look like. The generator
56
+ script (`scripts/regen-go-db-fixture.sh`) lived until #25 and remains in git
57
+ history should the fixture ever need forensic regeneration. The Postgres chain
58
+ has no adoption story by construction — no Postgres database predates its
59
+ driver.
@@ -0,0 +1,293 @@
1
+ /**
2
+ * What a list read costs at production's shape, measured on both drivers.
3
+ * Gated on `STORE_MEASURE=1` and SKIPPED otherwise (the
4
+ * `AUTHORIZATION_MEASURE` idiom of authorization/__tests__/
5
+ * list-read-scope.measure.test.ts): seeding tens of megabytes takes a
6
+ * while, and the numbers are recorded beside the change that moves them,
7
+ * not asserted — no budget is ruled for open source.
8
+ *
9
+ * The seed is the hosted edition's population as read on 2026-09-23:
10
+ * 1,279 agent executions averaging about 37 KB each (the kind's rows are
11
+ * fat: every message, tool call and todo of a run rides the row), 865
12
+ * sessions of about half a kilobyte, twelve organizations with one third
13
+ * of every kind in the busiest. The payload is uniform where production's
14
+ * is skewed (its largest execution is 4 MB), so these numbers understate
15
+ * the tail, never the median.
16
+ *
17
+ * Each shape is timed as the lane pays for it — the store read, the
18
+ * decode, the lane's own predicate — as the median of five runs after one
19
+ * warm-up. The "today" shapes read the whole kind the way every lane did
20
+ * before the list index; the "indexed" shapes read the same answer through
21
+ * `queryResources`. The status-write shape times the run's hottest write,
22
+ * an `updateResource` that changes no list key, which the index must not
23
+ * slow; it uses only the store API that predates the index, so the same
24
+ * file measures the code before and after.
25
+ */
26
+ import { create, fromBinary } from "@bufbuild/protobuf";
27
+ import { afterAll, describe, expect, it } from "vitest";
28
+
29
+ import type { AgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
30
+ import { AgentExecutionSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
31
+ import { MessageType } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
32
+ import { SessionSchema } from "@stigmer/protos/ai/stigmer/agentic/session/v1/api_pb";
33
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
34
+
35
+ import { LIST_INDEXES } from "../../boot/list-indexes.js";
36
+ import { agentExecutionListIndex } from "../../domain/agentexecution/list-index.js";
37
+ import { sessionListIndex } from "../../domain/session/list-index.js";
38
+ import type { Store } from "../interface.js";
39
+ import { PostgresStore } from "../postgres/store.js";
40
+ import {
41
+ createTestDatabase,
42
+ testDatabaseAdminUrl,
43
+ } from "../postgres/__tests__/support.js";
44
+ import type { TestDatabase } from "../postgres/__tests__/support.js";
45
+ import { tempStore } from "../sqlite/__tests__/support.js";
46
+
47
+ const MEASURE = process.env["STORE_MEASURE"] === "1";
48
+
49
+ const ORGS = 12;
50
+ const EXECUTIONS = 1_279;
51
+ const SESSIONS = 865;
52
+ const MESSAGES_PER_EXECUTION = 40;
53
+ const MESSAGE_CHARS = 900;
54
+ const BUSY_ORG = "org-00";
55
+ const RUNS = 5;
56
+
57
+ function orgOf(index: number): string {
58
+ // One third of every kind in the busiest organization, the rest spread.
59
+ return index % 3 === 0
60
+ ? BUSY_ORG
61
+ : `org-${String((index % (ORGS - 1)) + 1).padStart(2, "0")}`;
62
+ }
63
+
64
+ function sessionIdOf(index: number): string {
65
+ return `ses_${String(index).padStart(26, "0")}`;
66
+ }
67
+
68
+ function createdAt(index: number): { seconds: bigint; nanos: number } {
69
+ return { seconds: BigInt(1_790_000_000 + index), nanos: 0 };
70
+ }
71
+
72
+ async function seed(store: Store): Promise<void> {
73
+ const content = "x".repeat(MESSAGE_CHARS);
74
+ for (let i = 0; i < SESSIONS; i++) {
75
+ const id = sessionIdOf(i);
76
+ await store.saveResource(
77
+ ApiResourceKind.session,
78
+ id,
79
+ SessionSchema,
80
+ create(SessionSchema, {
81
+ metadata: { id, org: orgOf(i), name: `session ${i}` },
82
+ spec: { subject: `subject ${i}` },
83
+ status: {
84
+ audit: { specAudit: { createdAt: createdAt(i) } },
85
+ },
86
+ }),
87
+ );
88
+ }
89
+ for (let i = 0; i < EXECUTIONS; i++) {
90
+ const id = `aex_${String(i).padStart(26, "0")}`;
91
+ const session = i % SESSIONS;
92
+ await store.saveResource(
93
+ ApiResourceKind.agent_execution,
94
+ id,
95
+ AgentExecutionSchema,
96
+ create(AgentExecutionSchema, {
97
+ metadata: { id, org: orgOf(session) },
98
+ spec: { sessionId: sessionIdOf(session), message: "run it" },
99
+ status: {
100
+ messages: Array.from({ length: MESSAGES_PER_EXECUTION }, (_, m) => ({
101
+ type:
102
+ m % 2 === 0 ? MessageType.MESSAGE_HUMAN : MessageType.MESSAGE_AI,
103
+ content,
104
+ timestamp: "2026-09-23T10:00:00Z",
105
+ })),
106
+ audit: { specAudit: { createdAt: createdAt(i) } },
107
+ },
108
+ }),
109
+ );
110
+ }
111
+ }
112
+
113
+ async function median(
114
+ work: () => Promise<number>,
115
+ ): Promise<{ ms: number; rows: number }> {
116
+ await work();
117
+ const samples: number[] = [];
118
+ let rows = 0;
119
+ for (let run = 0; run < RUNS; run++) {
120
+ const started = performance.now();
121
+ rows = await work();
122
+ samples.push(performance.now() - started);
123
+ }
124
+ samples.sort((a, b) => a - b);
125
+ return { ms: Math.round(samples[Math.floor(RUNS / 2)] ?? 0), rows };
126
+ }
127
+
128
+ function decodeExecutions(rows: ReadonlyArray<Uint8Array>): AgentExecution[] {
129
+ return rows.map((row) => fromBinary(AgentExecutionSchema, row));
130
+ }
131
+
132
+ interface Shape {
133
+ readonly name: string;
134
+ run(store: Store): Promise<number>;
135
+ }
136
+
137
+ /** What each lane pays today: the whole kind, decoded, then its predicate. */
138
+ const TODAY: ReadonlyArray<Shape> = [
139
+ {
140
+ name: "agentExecution.list(org) — whole kind, org in memory",
141
+ async run(store) {
142
+ const rows = await store.listResources(ApiResourceKind.agent_execution);
143
+ return decodeExecutions(rows).filter((e) => e.metadata?.org === BUSY_ORG)
144
+ .length;
145
+ },
146
+ },
147
+ {
148
+ name: "agentExecution.listBySession — whole kind, session in memory",
149
+ async run(store) {
150
+ const rows = await store.listResources(ApiResourceKind.agent_execution);
151
+ return decodeExecutions(rows).filter(
152
+ (e) => e.spec?.sessionId === sessionIdOf(0),
153
+ ).length;
154
+ },
155
+ },
156
+ {
157
+ name: "session.list — whole kind",
158
+ async run(store) {
159
+ const rows = await store.listResources(ApiResourceKind.session);
160
+ return rows.map((row) => fromBinary(SessionSchema, row)).length;
161
+ },
162
+ },
163
+ ];
164
+
165
+ /** The same answers read through the list index. */
166
+ const INDEXED: ReadonlyArray<Shape> = [
167
+ {
168
+ name: "agentExecution.list(org) — the organization's rows, every one",
169
+ async run(store) {
170
+ const rows = await store.queryResources(agentExecutionListIndex, {
171
+ org: BUSY_ORG,
172
+ });
173
+ return decodeExecutions(rows.map((row) => row.data)).length;
174
+ },
175
+ },
176
+ {
177
+ name: "agentExecution.list(org) — one page of 50",
178
+ async run(store) {
179
+ const rows = await store.queryResources(agentExecutionListIndex, {
180
+ org: BUSY_ORG,
181
+ limit: 50,
182
+ });
183
+ return decodeExecutions(rows.map((row) => row.data)).length;
184
+ },
185
+ },
186
+ {
187
+ name: "agentExecution.listBySession — the session key",
188
+ async run(store) {
189
+ const rows = await store.queryResources(agentExecutionListIndex, {
190
+ anyKey: [{ name: "session", value: sessionIdOf(0) }],
191
+ });
192
+ return decodeExecutions(rows.map((row) => row.data)).length;
193
+ },
194
+ },
195
+ {
196
+ name: "session.list(org) — one page of 50",
197
+ async run(store) {
198
+ const rows = await store.queryResources(sessionListIndex, {
199
+ org: BUSY_ORG,
200
+ limit: 50,
201
+ });
202
+ return rows.map((row) => fromBinary(SessionSchema, row.data)).length;
203
+ },
204
+ },
205
+ ];
206
+
207
+ const STATUS_WRITES = 200;
208
+
209
+ /** The run's hottest write: a status persist that changes no list key. */
210
+ async function timeStatusWrites(store: Store): Promise<number> {
211
+ const id = `aex_${String(1).padStart(26, "0")}`;
212
+ const started = performance.now();
213
+ for (let i = 0; i < STATUS_WRITES; i++) {
214
+ await store.updateResource(
215
+ ApiResourceKind.agent_execution,
216
+ id,
217
+ AgentExecutionSchema,
218
+ (execution) => {
219
+ execution.status!.error = `write ${i}`;
220
+ },
221
+ );
222
+ }
223
+ return (performance.now() - started) / STATUS_WRITES;
224
+ }
225
+
226
+ let postgresDatabase: TestDatabase | undefined;
227
+
228
+ const DRIVERS: ReadonlyArray<{
229
+ readonly name: string;
230
+ readonly skip: boolean;
231
+ open(): Promise<{ store: Store; close(): Promise<void> }>;
232
+ }> = [
233
+ {
234
+ name: "sqlite",
235
+ skip: false,
236
+ async open() {
237
+ const temp = tempStore();
238
+ return { store: temp.store, close: () => temp.cleanup() };
239
+ },
240
+ },
241
+ {
242
+ name: "postgres",
243
+ skip: testDatabaseAdminUrl() === undefined,
244
+ async open() {
245
+ postgresDatabase = await createTestDatabase();
246
+ const store = await PostgresStore.open(
247
+ postgresDatabase.databaseUrl,
248
+ undefined,
249
+ {
250
+ listIndexes: LIST_INDEXES,
251
+ },
252
+ );
253
+ return { store, close: () => store.close() };
254
+ },
255
+ },
256
+ ];
257
+
258
+ afterAll(async () => {
259
+ await postgresDatabase?.drop();
260
+ });
261
+
262
+ describe.skipIf(!MEASURE)("list reads at production's shape", () => {
263
+ describe.each(DRIVERS)("$name", (driver) => {
264
+ it.skipIf(driver.skip)(
265
+ "times every shape",
266
+ async () => {
267
+ const opened = await driver.open();
268
+ try {
269
+ await seed(opened.store);
270
+ for (const [label, shapes] of [
271
+ ["today", TODAY],
272
+ ["indexed", INDEXED],
273
+ ] as const) {
274
+ for (const shape of shapes) {
275
+ const { ms, rows } = await median(() => shape.run(opened.store));
276
+ console.log(
277
+ `[measure] ${driver.name} ${label} ${shape.name}: ${ms} ms (${rows} rows kept)`,
278
+ );
279
+ expect(rows).toBeGreaterThan(0);
280
+ }
281
+ }
282
+ const perWrite = await timeStatusWrites(opened.store);
283
+ console.log(
284
+ `[measure] ${driver.name} status write (updateResource, no key change): ${perWrite.toFixed(2)} ms per write over ${STATUS_WRITES}`,
285
+ );
286
+ } finally {
287
+ await opened.close();
288
+ }
289
+ },
290
+ 600_000,
291
+ );
292
+ });
293
+ });