@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
@@ -7,7 +7,9 @@
7
7
  * single-file lock never had. v5, the chain's first row-decoding step,
8
8
  * moves every row of the seven kinds that held the retired public level to
9
9
  * org and leaves every other row's bytes as they were; a row it cannot
10
- * decode fails the step and leaves the database at v4.
10
+ * decode fails the step and leaves the database at v4. v6 adds the list
11
+ * index's table (its behaviour is the store contract's). A step's starting
12
+ * database is built by running the chain up to the step before it.
11
13
  *
12
14
  * Gated on TEST_DATABASE_URL (see support.ts): visible skips without a
13
15
  * database, always exercised in CI via the ci.stigmer-server service
@@ -24,7 +26,12 @@ import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb"
24
26
  import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
25
27
 
26
28
  import { PUBLIC_ROW_KINDS_AT_RETIREMENT } from "../../public-visibility-retired.js";
27
- import { CURRENT_SCHEMA_VERSION, SCHEMA_VERSION_5 } from "../migrations.js";
29
+ import {
30
+ CURRENT_SCHEMA_VERSION,
31
+ SCHEMA_VERSION_1,
32
+ SCHEMA_VERSION_5,
33
+ runMigrations,
34
+ } from "../migrations.js";
28
35
  import { PostgresStore } from "../store.js";
29
36
  import {
30
37
  createTestDatabase,
@@ -32,6 +39,18 @@ import {
32
39
  type TestDatabase,
33
40
  } from "./support.js";
34
41
 
42
+ /** Builds the database a step starts from: the chain run up to `version`. */
43
+ async function migrateTo(databaseUrl: string, version: number): Promise<void> {
44
+ const pool = new pg.Pool({ connectionString: databaseUrl, max: 1 });
45
+ const client = await pool.connect();
46
+ try {
47
+ await runMigrations(client, version);
48
+ } finally {
49
+ client.release();
50
+ await pool.end();
51
+ }
52
+ }
53
+
35
54
  describe.skipIf(testDatabaseAdminUrl() === undefined)(
36
55
  "postgres migrations",
37
56
  () => {
@@ -71,6 +90,7 @@ describe.skipIf(testDatabaseAdminUrl() === undefined)(
71
90
  "oauth_grant",
72
91
  "pending_oauth_state",
73
92
  "resource_audit",
93
+ "resource_list_keys",
74
94
  "resources",
75
95
  "schedule_runs",
76
96
  "schema_version",
@@ -102,21 +122,13 @@ describe.skipIf(testDatabaseAdminUrl() === undefined)(
102
122
  });
103
123
 
104
124
  it("a v1 database resumes the chain on reopen: the v2/v3 indexes arrive and v4 removes the Project rows", async () => {
105
- const store = await PostgresStore.open(db.databaseUrl);
106
- await store.close();
107
-
108
- // Rewind to a v1 database: drop exactly what v2+v4 created and their
109
- // version rows — the state any pre-v2 deployment is actually in —
110
- // and seed one row of the Project kind an earlier release wrote
125
+ // A v1 database — the state any pre-v2 deployment is actually in —
126
+ // seeded with one row of the Project kind an earlier release wrote
111
127
  // (the kind column holds the enum NAME) beside a row v4 must keep.
128
+ await migrateTo(db.databaseUrl, SCHEMA_VERSION_1);
112
129
  const client = new pg.Client({ connectionString: db.databaseUrl });
113
130
  await client.connect();
114
131
  try {
115
- await client.query(`DROP INDEX idx_wfee_created_at`);
116
- await client.query(`DROP INDEX idx_oauth_grant_resource`);
117
- await client.query(
118
- `DELETE FROM schema_version WHERE version IN (2, 3, 4, 5)`,
119
- );
120
132
  await client.query(
121
133
  `INSERT INTO resources (kind, id, data) VALUES ('project', 'prj_seeded', '\\x00'), ('organization', 'acme', '\\x00')`,
122
134
  );
@@ -242,15 +254,11 @@ describe.skipIf(testDatabaseAdminUrl() === undefined)(
242
254
  );
243
255
  }
244
256
 
245
- /** Opens the store once (the chain replays), then rewinds to v4 for the seeding. */
257
+ /** A v4 database, the one v5 starts from, and a client on it for the seeding. */
246
258
  async function v4Client(): Promise<pg.Client> {
247
- const store = await PostgresStore.open(db.databaseUrl);
248
- await store.close();
259
+ await migrateTo(db.databaseUrl, SCHEMA_VERSION_5 - 1);
249
260
  const client = new pg.Client({ connectionString: db.databaseUrl });
250
261
  await client.connect();
251
- await client.query(`DELETE FROM schema_version WHERE version = $1`, [
252
- SCHEMA_VERSION_5,
253
- ]);
254
262
  return client;
255
263
  }
256
264
 
@@ -15,8 +15,13 @@ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/
15
15
 
16
16
  import pg from "pg";
17
17
 
18
+ import type { Store } from "../../interface.js";
19
+ import { apiResourceKindName } from "../../proto-fields.js";
18
20
  import type { StoreContractFixture } from "../../__tests__/store-contract.js";
19
- import { describeStoreContract } from "../../__tests__/store-contract.js";
21
+ import {
22
+ CONTRACT_STORE_OPTIONS,
23
+ describeStoreContract,
24
+ } from "../../__tests__/store-contract.js";
20
25
  import { PostgresStore } from "../store.js";
21
26
  import {
22
27
  createTestDatabase,
@@ -26,6 +31,7 @@ import {
26
31
 
27
32
  const ALL_TABLES = [
28
33
  "resources",
34
+ "resource_list_keys",
29
35
  "resource_audit",
30
36
  "search_index",
31
37
  "bootstrap_state",
@@ -64,7 +70,12 @@ describe.skipIf(testDatabaseAdminUrl() === undefined)(
64
70
  await hooks.query(
65
71
  `TRUNCATE ${ALL_TABLES.join(", ")} RESTART IDENTITY CASCADE`,
66
72
  );
67
- const store = await PostgresStore.open(db.databaseUrl);
73
+ const store = await PostgresStore.open(
74
+ db.databaseUrl,
75
+ undefined,
76
+ CONTRACT_STORE_OPTIONS,
77
+ );
78
+ const others: Store[] = [];
68
79
  return {
69
80
  store,
70
81
  async forceDedupeExpiry(id, expiresAtIso) {
@@ -79,7 +90,43 @@ describe.skipIf(testDatabaseAdminUrl() === undefined)(
79
90
  );
80
91
  return Number((result.rows[0] as { count: string }).count);
81
92
  },
82
- cleanup: () => store.close(),
93
+ async writeAsOlderBinary(kind, id, data) {
94
+ // The statement this driver ran before the list index existed.
95
+ await hooks.query(
96
+ `INSERT INTO resources (kind, id, data, updated_at) VALUES ($1, $2, $3, now())
97
+ ON CONFLICT (kind, id) DO UPDATE SET data = excluded.data, updated_at = now()`,
98
+ [apiResourceKindName(kind), id, Buffer.from(data)],
99
+ );
100
+ },
101
+ async countUnproven(kind, revision) {
102
+ const revisionArm =
103
+ revision === undefined
104
+ ? ""
105
+ : ` OR list_index_revision IS NULL OR list_index_revision <> $2`;
106
+ const result = await hooks.query(
107
+ `SELECT COUNT(*) AS count FROM resources
108
+ WHERE kind = $1 AND (list_indexed_at IS DISTINCT FROM updated_at${revisionArm})`,
109
+ revision === undefined
110
+ ? [apiResourceKindName(kind)]
111
+ : [apiResourceKindName(kind), revision],
112
+ );
113
+ return Number((result.rows[0] as { count: string }).count);
114
+ },
115
+ async openAnother(options) {
116
+ const other = await PostgresStore.open(
117
+ db.databaseUrl,
118
+ undefined,
119
+ options,
120
+ );
121
+ others.push(other);
122
+ return other;
123
+ },
124
+ async cleanup() {
125
+ for (const other of others) {
126
+ await other.close();
127
+ }
128
+ await store.close();
129
+ },
83
130
  };
84
131
  });
85
132
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Versioned schema migrations for the Postgres driver — an INDEPENDENT
3
3
  * chain starting at its own v1 (DD-010 §3). It deliberately does NOT
4
- * mirror sqlite's v1–v7: that chain's value is Go-DDL fidelity for adopted
4
+ * mirror sqlite's chain: that chain's value is Go-DDL fidelity for adopted
5
5
  * laptop databases, a concern Postgres cannot have (no Postgres database
6
6
  * predates this driver). Same runner discipline as sqlite/migrations.ts —
7
7
  * deliberate, versioned, each step in its own transaction, `schema_version`
@@ -50,9 +50,11 @@ export const SCHEMA_VERSION_3 = 3;
50
50
  export const SCHEMA_VERSION_4 = 4;
51
51
  /** v5: every row holding the retired public visibility level moved to org. */
52
52
  export const SCHEMA_VERSION_5 = 5;
53
+ /** v6: the list index's columns, key table and indexes (DDL only). */
54
+ export const SCHEMA_VERSION_6 = 6;
53
55
 
54
56
  /** Target version for new databases. */
55
- export const CURRENT_SCHEMA_VERSION = SCHEMA_VERSION_5;
57
+ export const CURRENT_SCHEMA_VERSION = SCHEMA_VERSION_6;
56
58
 
57
59
  /**
58
60
  * Advisory lock key for the migration chain. Arbitrary but stable 64-bit
@@ -63,11 +65,16 @@ export const CURRENT_SCHEMA_VERSION = SCHEMA_VERSION_5;
63
65
  export const MIGRATION_LOCK_KEY = 0x5354474d5231n;
64
66
 
65
67
  /**
66
- * Applies all pending migrations in order, serialized across instances by
67
- * the advisory lock. Runs on a dedicated client (locks are session-scoped;
68
- * a pool query could release on a different session).
68
+ * Applies every pending migration up to `targetVersion` in order — all of
69
+ * them unless a test builds the database a given step starts from —
70
+ * serialized across instances by the advisory lock. Runs on a dedicated
71
+ * client (locks are session-scoped; a pool query could release on a
72
+ * different session).
69
73
  */
70
- export async function runMigrations(client: PoolClient): Promise<void> {
74
+ export async function runMigrations(
75
+ client: PoolClient,
76
+ targetVersion: number = CURRENT_SCHEMA_VERSION,
77
+ ): Promise<void> {
71
78
  await client.query("SELECT pg_advisory_lock($1)", [MIGRATION_LOCK_KEY]);
72
79
  try {
73
80
  await client.query(`
@@ -87,10 +94,11 @@ export async function runMigrations(client: PoolClient): Promise<void> {
87
94
  [SCHEMA_VERSION_3, migrateToV3],
88
95
  [SCHEMA_VERSION_4, migrateToV4],
89
96
  [SCHEMA_VERSION_5, migrateToV5],
97
+ [SCHEMA_VERSION_6, migrateToV6],
90
98
  ];
91
99
 
92
100
  for (const [version, migrate] of chain) {
93
- if (currentVersion < version) {
101
+ if (currentVersion < version && version <= targetVersion) {
94
102
  await applyInTransaction(client, version, migrate);
95
103
  }
96
104
  }
@@ -362,3 +370,57 @@ async function migrateToV5(client: PoolClient): Promise<void> {
362
370
  }
363
371
  }
364
372
  }
373
+
374
+ /**
375
+ * v6: the list index (../list-index.ts) — schema only. Every existing row
376
+ * arrives with its stamp NULL, which makes it unproven, so the store's
377
+ * reconciliation at open derives its facts with the code that runs and
378
+ * reads stay exact until it has; a migration that decoded rows here would
379
+ * freeze today's declarations into the chain (the v5 step freezes its
380
+ * kind table for the same reason, public-visibility-retired.ts).
381
+ *
382
+ * - `list_org` and `list_created_at` are the order's inputs, byte-collated
383
+ * (`COLLATE "C"`) so the database sorts them the way list-index.ts
384
+ * compares them when it merges unproven rows in; `id` is compared under
385
+ * the same collation in the index expressions and the queries.
386
+ * - `list_index_revision` is the declaration revision the facts were
387
+ * derived under; `list_indexed_at` is the `updated_at` they were derived
388
+ * from. A writer that does not know these columns leaves them behind as
389
+ * it bumps `updated_at`, which is exactly what marks its row unproven.
390
+ * - `resource_list_keys` holds one row per (resource, key) with the
391
+ * creation instant copied in, so a parent's rows are one range of one
392
+ * index. It carries no foreign key: every indexed read joins
393
+ * `resources`, so a key row whose resource is gone matches nothing.
394
+ * - The partial index holds only unproven rows (none in steady state), so
395
+ * finding them is never a scan of the kind.
396
+ */
397
+ async function migrateToV6(client: PoolClient): Promise<void> {
398
+ await client.query(`
399
+ ALTER TABLE resources
400
+ ADD COLUMN list_org TEXT COLLATE "C",
401
+ ADD COLUMN list_created_at TEXT COLLATE "C",
402
+ ADD COLUMN list_index_revision INTEGER,
403
+ ADD COLUMN list_indexed_at timestamptz;
404
+
405
+ CREATE INDEX idx_resources_list_org
406
+ ON resources (kind, list_org, list_created_at, (id COLLATE "C"));
407
+ CREATE INDEX idx_resources_list_created
408
+ ON resources (kind, list_created_at, (id COLLATE "C"));
409
+ CREATE INDEX idx_resources_list_revision
410
+ ON resources (kind, list_index_revision);
411
+ CREATE INDEX idx_resources_list_unproven
412
+ ON resources (kind, id) WHERE list_indexed_at IS DISTINCT FROM updated_at;
413
+
414
+ CREATE TABLE resource_list_keys (
415
+ kind TEXT NOT NULL,
416
+ id TEXT NOT NULL,
417
+ key TEXT NOT NULL,
418
+ value TEXT NOT NULL,
419
+ created_at TEXT COLLATE "C" NOT NULL,
420
+ PRIMARY KEY (kind, id, key)
421
+ );
422
+
423
+ CREATE INDEX idx_resource_list_keys_lookup
424
+ ON resource_list_keys (kind, key, value, created_at, (id COLLATE "C"));
425
+ `);
426
+ }