@stigmer/server 3.23.0 → 3.23.1-dev.20260923075205

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 (267) hide show
  1. package/dist/authorization/authorizer.d.ts +4 -8
  2. package/dist/authorization/authorizer.d.ts.map +1 -1
  3. package/dist/authorization/authorizer.js +5 -9
  4. package/dist/authorization/authorizer.js.map +1 -1
  5. package/dist/authorization/derived-tuples.d.ts.map +1 -1
  6. package/dist/authorization/derived-tuples.js +10 -25
  7. package/dist/authorization/derived-tuples.js.map +1 -1
  8. package/dist/authorization/evaluator.d.ts +7 -8
  9. package/dist/authorization/evaluator.d.ts.map +1 -1
  10. package/dist/authorization/evaluator.js +4 -20
  11. package/dist/authorization/evaluator.js.map +1 -1
  12. package/dist/authorization/list-read-scope.d.ts.map +1 -1
  13. package/dist/authorization/list-read-scope.js +5 -9
  14. package/dist/authorization/list-read-scope.js.map +1 -1
  15. package/dist/authorization/model/agent.d.ts.map +1 -1
  16. package/dist/authorization/model/agent.js +4 -5
  17. package/dist/authorization/model/agent.js.map +1 -1
  18. package/dist/authorization/model/agent_instance.d.ts.map +1 -1
  19. package/dist/authorization/model/agent_instance.js +2 -2
  20. package/dist/authorization/model/agent_instance.js.map +1 -1
  21. package/dist/authorization/model/mcp_server.d.ts.map +1 -1
  22. package/dist/authorization/model/mcp_server.js +2 -2
  23. package/dist/authorization/model/mcp_server.js.map +1 -1
  24. package/dist/authorization/model/plugin.d.ts.map +1 -1
  25. package/dist/authorization/model/plugin.js +3 -4
  26. package/dist/authorization/model/plugin.js.map +1 -1
  27. package/dist/authorization/model/rewrite.d.ts +8 -10
  28. package/dist/authorization/model/rewrite.d.ts.map +1 -1
  29. package/dist/authorization/model/rewrite.js +0 -3
  30. package/dist/authorization/model/rewrite.js.map +1 -1
  31. package/dist/authorization/model/skill.d.ts.map +1 -1
  32. package/dist/authorization/model/skill.js +2 -2
  33. package/dist/authorization/model/skill.js.map +1 -1
  34. package/dist/authorization/model/workflow.d.ts.map +1 -1
  35. package/dist/authorization/model/workflow.js +2 -2
  36. package/dist/authorization/model/workflow.js.map +1 -1
  37. package/dist/authorization/model/workflow_instance.d.ts.map +1 -1
  38. package/dist/authorization/model/workflow_instance.js +2 -2
  39. package/dist/authorization/model/workflow_instance.js.map +1 -1
  40. package/dist/authorization/organization-directory.d.ts.map +1 -1
  41. package/dist/authorization/organization-directory.js +2 -3
  42. package/dist/authorization/organization-directory.js.map +1 -1
  43. package/dist/authorization/store-test-kit.d.ts +0 -6
  44. package/dist/authorization/store-test-kit.d.ts.map +1 -1
  45. package/dist/authorization/store-test-kit.js +16 -62
  46. package/dist/authorization/store-test-kit.js.map +1 -1
  47. package/dist/authorization/tuples.d.ts +13 -32
  48. package/dist/authorization/tuples.d.ts.map +1 -1
  49. package/dist/authorization/tuples.js +3 -11
  50. package/dist/authorization/tuples.js.map +1 -1
  51. package/dist/domain/agent/controller.d.ts.map +1 -1
  52. package/dist/domain/agent/controller.js +4 -4
  53. package/dist/domain/agent/controller.js.map +1 -1
  54. package/dist/domain/agentchannel/controller.d.ts.map +1 -1
  55. package/dist/domain/agentchannel/controller.js +3 -1
  56. package/dist/domain/agentchannel/controller.js.map +1 -1
  57. package/dist/domain/agentexecution/controller.d.ts.map +1 -1
  58. package/dist/domain/agentexecution/controller.js +3 -1
  59. package/dist/domain/agentexecution/controller.js.map +1 -1
  60. package/dist/domain/agentinstance/controller.d.ts.map +1 -1
  61. package/dist/domain/agentinstance/controller.js +3 -4
  62. package/dist/domain/agentinstance/controller.js.map +1 -1
  63. package/dist/domain/agentshare/constants.d.ts +6 -11
  64. package/dist/domain/agentshare/constants.d.ts.map +1 -1
  65. package/dist/domain/agentshare/constants.js +8 -16
  66. package/dist/domain/agentshare/constants.js.map +1 -1
  67. package/dist/domain/agentshare/controller.d.ts.map +1 -1
  68. package/dist/domain/agentshare/controller.js +3 -1
  69. package/dist/domain/agentshare/controller.js.map +1 -1
  70. package/dist/domain/agentshare/steps.d.ts +18 -25
  71. package/dist/domain/agentshare/steps.d.ts.map +1 -1
  72. package/dist/domain/agentshare/steps.js +41 -142
  73. package/dist/domain/agentshare/steps.js.map +1 -1
  74. package/dist/domain/environment/controller.d.ts.map +1 -1
  75. package/dist/domain/environment/controller.js +2 -3
  76. package/dist/domain/environment/controller.js.map +1 -1
  77. package/dist/domain/executioncontext/controller.d.ts.map +1 -1
  78. package/dist/domain/executioncontext/controller.js +2 -1
  79. package/dist/domain/executioncontext/controller.js.map +1 -1
  80. package/dist/domain/mcpserver/controller.d.ts.map +1 -1
  81. package/dist/domain/mcpserver/controller.js +3 -4
  82. package/dist/domain/mcpserver/controller.js.map +1 -1
  83. package/dist/domain/plugin/controller.d.ts.map +1 -1
  84. package/dist/domain/plugin/controller.js +1 -3
  85. package/dist/domain/plugin/controller.js.map +1 -1
  86. package/dist/domain/plugin/push.d.ts +7 -5
  87. package/dist/domain/plugin/push.d.ts.map +1 -1
  88. package/dist/domain/plugin/push.js +8 -14
  89. package/dist/domain/plugin/push.js.map +1 -1
  90. package/dist/domain/schedule/controller.d.ts.map +1 -1
  91. package/dist/domain/schedule/controller.js +3 -1
  92. package/dist/domain/schedule/controller.js.map +1 -1
  93. package/dist/domain/session/controller.d.ts.map +1 -1
  94. package/dist/domain/session/controller.js +3 -2
  95. package/dist/domain/session/controller.js.map +1 -1
  96. package/dist/domain/skill/controller.d.ts.map +1 -1
  97. package/dist/domain/skill/controller.js +0 -2
  98. package/dist/domain/skill/controller.js.map +1 -1
  99. package/dist/domain/workflow/agent-call-references.d.ts +54 -0
  100. package/dist/domain/workflow/agent-call-references.d.ts.map +1 -0
  101. package/dist/domain/workflow/agent-call-references.js +97 -0
  102. package/dist/domain/workflow/agent-call-references.js.map +1 -0
  103. package/dist/domain/workflow/controller.d.ts.map +1 -1
  104. package/dist/domain/workflow/controller.js +14 -6
  105. package/dist/domain/workflow/controller.js.map +1 -1
  106. package/dist/domain/workflow/registry/data/task-kind-registry.json +1 -1
  107. package/dist/domain/workflow/validation/task-config-constraints.d.ts +10 -1
  108. package/dist/domain/workflow/validation/task-config-constraints.d.ts.map +1 -1
  109. package/dist/domain/workflow/validation/task-config-constraints.js +3 -2
  110. package/dist/domain/workflow/validation/task-config-constraints.js.map +1 -1
  111. package/dist/domain/workflowexecution/controller.d.ts.map +1 -1
  112. package/dist/domain/workflowexecution/controller.js +4 -2
  113. package/dist/domain/workflowexecution/controller.js.map +1 -1
  114. package/dist/domain/workflowinstance/controller.d.ts.map +1 -1
  115. package/dist/domain/workflowinstance/controller.js +3 -4
  116. package/dist/domain/workflowinstance/controller.js.map +1 -1
  117. package/dist/extensions/authorization-queries.d.ts +6 -5
  118. package/dist/extensions/authorization-queries.d.ts.map +1 -1
  119. package/dist/extensions/list-read-scope.d.ts +1 -1
  120. package/dist/extensions/resource-authorization.d.ts +3 -2
  121. package/dist/extensions/resource-authorization.d.ts.map +1 -1
  122. package/dist/index.d.ts +1 -1
  123. package/dist/index.d.ts.map +1 -1
  124. package/dist/index.js.map +1 -1
  125. package/dist/pipeline/apiresource-meta.d.ts +12 -3
  126. package/dist/pipeline/apiresource-meta.d.ts.map +1 -1
  127. package/dist/pipeline/apiresource-meta.js +21 -10
  128. package/dist/pipeline/apiresource-meta.js.map +1 -1
  129. package/dist/pipeline/steps/authorization-tuples.d.ts +9 -3
  130. package/dist/pipeline/steps/authorization-tuples.d.ts.map +1 -1
  131. package/dist/pipeline/steps/authorization-tuples.js +9 -11
  132. package/dist/pipeline/steps/authorization-tuples.js.map +1 -1
  133. package/dist/pipeline/steps/references.d.ts +178 -20
  134. package/dist/pipeline/steps/references.d.ts.map +1 -1
  135. package/dist/pipeline/steps/references.js +344 -43
  136. package/dist/pipeline/steps/references.js.map +1 -1
  137. package/dist/query/search/controller.js +0 -1
  138. package/dist/query/search/controller.js.map +1 -1
  139. package/dist/query/search/criteria.d.ts +1 -5
  140. package/dist/query/search/criteria.d.ts.map +1 -1
  141. package/dist/query/search/criteria.js +3 -13
  142. package/dist/query/search/criteria.js.map +1 -1
  143. package/dist/query/search/handler.d.ts +5 -4
  144. package/dist/query/search/handler.d.ts.map +1 -1
  145. package/dist/query/search/handler.js +7 -6
  146. package/dist/query/search/handler.js.map +1 -1
  147. package/dist/query/search/query-store.d.ts.map +1 -1
  148. package/dist/query/search/query-store.js +0 -2
  149. package/dist/query/search/query-store.js.map +1 -1
  150. package/dist/store/interface.d.ts +3 -6
  151. package/dist/store/interface.d.ts.map +1 -1
  152. package/dist/store/interface.js.map +1 -1
  153. package/dist/store/postgres/migrations.d.ts +3 -1
  154. package/dist/store/postgres/migrations.d.ts.map +1 -1
  155. package/dist/store/postgres/migrations.js +35 -1
  156. package/dist/store/postgres/migrations.js.map +1 -1
  157. package/dist/store/postgres/store.d.ts.map +1 -1
  158. package/dist/store/postgres/store.js +4 -9
  159. package/dist/store/postgres/store.js.map +1 -1
  160. package/dist/store/public-visibility-retired.d.ts +67 -0
  161. package/dist/store/public-visibility-retired.d.ts.map +1 -0
  162. package/dist/store/public-visibility-retired.js +55 -0
  163. package/dist/store/public-visibility-retired.js.map +1 -0
  164. package/dist/store/sqlite/migrations.d.ts +3 -1
  165. package/dist/store/sqlite/migrations.d.ts.map +1 -1
  166. package/dist/store/sqlite/migrations.js +36 -1
  167. package/dist/store/sqlite/migrations.js.map +1 -1
  168. package/dist/store/sqlite/store.d.ts.map +1 -1
  169. package/dist/store/sqlite/store.js +4 -8
  170. package/dist/store/sqlite/store.js.map +1 -1
  171. package/package.json +6 -6
  172. package/src/authorization/README.md +24 -26
  173. package/src/authorization/__tests__/authorizer.test.ts +10 -9
  174. package/src/authorization/__tests__/derived-tuples.test.ts +4 -11
  175. package/src/authorization/__tests__/evaluator.test.ts +14 -73
  176. package/src/authorization/__tests__/facts.test.ts +2 -2
  177. package/src/authorization/__tests__/fixtures/fga/agent-instance-creation.fga.yaml +19 -27
  178. package/src/authorization/__tests__/fixtures/fga/blueprint-private-visibility.fga.yaml +6 -98
  179. package/src/authorization/__tests__/fixtures/fga/default-instance-inheritance.fga.yaml +4 -61
  180. package/src/authorization/__tests__/fixtures/fga/mcp-server-authoring.fga.yaml +1 -15
  181. package/src/authorization/__tests__/fixtures/fga/org-admin-owner-inheritance.fga.yaml +1 -21
  182. package/src/authorization/__tests__/fixtures/fga/platform-visibility.fga.yaml +1 -25
  183. package/src/authorization/__tests__/fixtures/fga/workflow-execution-sharing.fga.yaml +1 -3
  184. package/src/authorization/__tests__/list-read-scope.test.ts +18 -17
  185. package/src/authorization/__tests__/store-tests.test.ts +2 -6
  186. package/src/authorization/__tests__/tuples.test.ts +12 -11
  187. package/src/authorization/authorizer.ts +4 -9
  188. package/src/authorization/derived-tuples.ts +14 -31
  189. package/src/authorization/evaluator.ts +8 -44
  190. package/src/authorization/list-read-scope.ts +5 -11
  191. package/src/authorization/model/agent.ts +2 -5
  192. package/src/authorization/model/agent_instance.ts +0 -2
  193. package/src/authorization/model/mcp_server.ts +0 -2
  194. package/src/authorization/model/plugin.ts +1 -4
  195. package/src/authorization/model/rewrite.ts +8 -13
  196. package/src/authorization/model/skill.ts +0 -2
  197. package/src/authorization/model/workflow.ts +0 -2
  198. package/src/authorization/model/workflow_instance.ts +0 -2
  199. package/src/authorization/organization-directory.ts +1 -3
  200. package/src/authorization/store-test-kit.ts +16 -75
  201. package/src/authorization/tuples.ts +16 -39
  202. package/src/domain/agent/__tests__/agent.test.ts +11 -4
  203. package/src/domain/agent/controller.ts +7 -7
  204. package/src/domain/agentchannel/__tests__/agentchannel.test.ts +223 -48
  205. package/src/domain/agentchannel/controller.ts +6 -1
  206. package/src/domain/agentexecution/controller.ts +6 -1
  207. package/src/domain/agentinstance/controller.ts +6 -12
  208. package/src/domain/agentshare/__tests__/agentshare.test.ts +196 -172
  209. package/src/domain/agentshare/__tests__/create-authorization.test.ts +16 -31
  210. package/src/domain/agentshare/constants.ts +14 -26
  211. package/src/domain/agentshare/controller.ts +6 -1
  212. package/src/domain/agentshare/steps.ts +41 -194
  213. package/src/domain/environment/controller.ts +5 -8
  214. package/src/domain/executioncontext/controller.ts +5 -1
  215. package/src/domain/mcpserver/__tests__/mcpserver.test.ts +115 -28
  216. package/src/domain/mcpserver/__tests__/oauth-handshake.test.ts +53 -18
  217. package/src/domain/mcpserver/controller.ts +6 -12
  218. package/src/domain/plugin/controller.ts +1 -8
  219. package/src/domain/plugin/push.ts +8 -16
  220. package/src/domain/schedule/controller.ts +6 -1
  221. package/src/domain/session/controller.ts +12 -4
  222. package/src/domain/skill/controller.ts +0 -7
  223. package/src/domain/workflow/__tests__/agent-call-references.test.ts +222 -0
  224. package/src/domain/workflow/agent-call-references.ts +157 -0
  225. package/src/domain/workflow/controller.ts +28 -10
  226. package/src/domain/workflow/registry/data/task-kind-registry.json +1 -1
  227. package/src/domain/workflow/validation/task-config-constraints.ts +7 -3
  228. package/src/domain/workflowexecution/controller.ts +7 -2
  229. package/src/domain/workflowinstance/controller.ts +6 -12
  230. package/src/extensions/__tests__/built-in-authorization-composed.test.ts +32 -19
  231. package/src/extensions/__tests__/extension-composition.test.ts +4 -4
  232. package/src/extensions/__tests__/list-read-scope-composed.test.ts +9 -6
  233. package/src/extensions/authorization-queries.ts +6 -5
  234. package/src/extensions/list-read-scope.ts +1 -1
  235. package/src/extensions/resource-authorization.ts +3 -5
  236. package/src/index.ts +0 -1
  237. package/src/pipeline/__tests__/supports-visibility.test.ts +112 -0
  238. package/src/pipeline/apiresource-meta.ts +21 -10
  239. package/src/pipeline/steps/__tests__/authorization-tuples.test.ts +18 -31
  240. package/src/pipeline/steps/__tests__/references.test.ts +703 -0
  241. package/src/pipeline/steps/authorization-tuples.ts +9 -11
  242. package/src/pipeline/steps/references.ts +578 -70
  243. package/src/query/search/__tests__/criteria.test.ts +0 -4
  244. package/src/query/search/__tests__/query-store.test.ts +11 -40
  245. package/src/query/search/controller.ts +0 -1
  246. package/src/query/search/criteria.ts +0 -14
  247. package/src/query/search/handler.ts +6 -7
  248. package/src/query/search/query-store.ts +0 -2
  249. package/src/store/__tests__/public-visibility-retired.test.ts +127 -0
  250. package/src/store/__tests__/store-contract.ts +6 -51
  251. package/src/store/interface.ts +3 -6
  252. package/src/store/postgres/__tests__/migrations.test.ts +187 -6
  253. package/src/store/postgres/__tests__/store-contract.test.ts +0 -2
  254. package/src/store/postgres/migrations.ts +48 -1
  255. package/src/store/postgres/store.ts +4 -11
  256. package/src/store/public-visibility-retired.ts +119 -0
  257. package/src/store/sqlite/__tests__/migrations.test.ts +191 -5
  258. package/src/store/sqlite/migrations.ts +45 -1
  259. package/src/store/sqlite/store.ts +4 -10
  260. package/dist/pipeline/steps/visibility-gates.d.ts +0 -27
  261. package/dist/pipeline/steps/visibility-gates.d.ts.map +0 -1
  262. package/dist/pipeline/steps/visibility-gates.js +0 -139
  263. package/dist/pipeline/steps/visibility-gates.js.map +0 -1
  264. package/src/authorization/__tests__/fixtures/fga/cross-org-share-creation.fga.yaml +0 -98
  265. package/src/authorization/__tests__/fixtures/fga/public-visibility-setter.fga.yaml +0 -52
  266. package/src/pipeline/steps/__tests__/visibility-gates.test.ts +0 -187
  267. package/src/pipeline/steps/visibility-gates.ts +0 -174
@@ -4,7 +4,10 @@
4
4
  * idempotent, a mid-chain database resumes (v1 → v2 picks up the sweep
5
5
  * index), and concurrent first boots serialize on the advisory lock
6
6
  * instead of racing the chain — the multi-instance failure class sqlite's
7
- * single-file lock never had.
7
+ * single-file lock never had. v5, the chain's first row-decoding step,
8
+ * moves every row of the seven kinds that held the retired public level to
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.
8
11
  *
9
12
  * Gated on TEST_DATABASE_URL (see support.ts): visible skips without a
10
13
  * database, always exercised in CI via the ci.stigmer-server service
@@ -14,7 +17,14 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest";
14
17
 
15
18
  import pg from "pg";
16
19
 
17
- import { CURRENT_SCHEMA_VERSION } from "../migrations.js";
20
+ import { create, fromJson, toBinary } from "@bufbuild/protobuf";
21
+ import type { DescMessage } from "@bufbuild/protobuf";
22
+
23
+ import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
24
+ import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
25
+
26
+ import { PUBLIC_ROW_KINDS_AT_RETIREMENT } from "../../public-visibility-retired.js";
27
+ import { CURRENT_SCHEMA_VERSION, SCHEMA_VERSION_5 } from "../migrations.js";
18
28
  import { PostgresStore } from "../store.js";
19
29
  import {
20
30
  createTestDatabase,
@@ -105,7 +115,7 @@ describe.skipIf(testDatabaseAdminUrl() === undefined)(
105
115
  await client.query(`DROP INDEX idx_wfee_created_at`);
106
116
  await client.query(`DROP INDEX idx_oauth_grant_resource`);
107
117
  await client.query(
108
- `DELETE FROM schema_version WHERE version IN (2, 3, 4)`,
118
+ `DELETE FROM schema_version WHERE version IN (2, 3, 4, 5)`,
109
119
  );
110
120
  await client.query(
111
121
  `INSERT INTO resources (kind, id, data) VALUES ('project', 'prj_seeded', '\\x00'), ('organization', 'acme', '\\x00')`,
@@ -120,9 +130,10 @@ describe.skipIf(testDatabaseAdminUrl() === undefined)(
120
130
  const remaining = await client.query(
121
131
  `SELECT kind, id FROM resources ORDER BY kind`,
122
132
  );
123
- expect(remaining.rows, "v4 removed the Project row and kept the rest").toEqual([
124
- { kind: "organization", id: "acme" },
125
- ]);
133
+ expect(
134
+ remaining.rows,
135
+ "v4 removed the Project row and kept the rest",
136
+ ).toEqual([{ kind: "organization", id: "acme" }]);
126
137
  const projectAudit = await client.query(
127
138
  `SELECT count(*)::int AS n FROM resource_audit WHERE kind = 'project'`,
128
139
  );
@@ -190,5 +201,175 @@ describe.skipIf(testDatabaseAdminUrl() === undefined)(
190
201
  await client.end();
191
202
  }
192
203
  });
204
+
205
+ describe("v5: the retired public level leaves every row", () => {
206
+ /** A row of `schema` carrying only the envelope every kind shares, at `visibility`. */
207
+ function envelopeBytes(
208
+ schema: DescMessage,
209
+ id: string,
210
+ visibility: ApiResourceVisibility,
211
+ ): Buffer {
212
+ return Buffer.from(
213
+ toBinary(
214
+ schema,
215
+ fromJson(schema, {
216
+ metadata: {
217
+ id,
218
+ name: id,
219
+ slug: id,
220
+ org: "acme",
221
+ visibility: ApiResourceVisibility[visibility],
222
+ },
223
+ }),
224
+ ),
225
+ );
226
+ }
227
+
228
+ function agentBytes(
229
+ id: string,
230
+ visibility: ApiResourceVisibility,
231
+ ): Buffer {
232
+ return Buffer.from(
233
+ toBinary(
234
+ AgentSchema,
235
+ create(AgentSchema, {
236
+ apiVersion: "agentic.stigmer.ai/v1",
237
+ kind: "Agent",
238
+ metadata: { id, name: id, slug: id, org: "acme", visibility },
239
+ spec: { instructions: "a conformant instruction body" },
240
+ }),
241
+ ),
242
+ );
243
+ }
244
+
245
+ /** Opens the store once (the chain replays), then rewinds to v4 for the seeding. */
246
+ async function v4Client(): Promise<pg.Client> {
247
+ const store = await PostgresStore.open(db.databaseUrl);
248
+ await store.close();
249
+ const client = new pg.Client({ connectionString: db.databaseUrl });
250
+ await client.connect();
251
+ await client.query(`DELETE FROM schema_version WHERE version = $1`, [
252
+ SCHEMA_VERSION_5,
253
+ ]);
254
+ return client;
255
+ }
256
+
257
+ async function row(
258
+ client: pg.Client,
259
+ kind: string,
260
+ id: string,
261
+ ): Promise<{ data: Buffer; updated_at: Date }> {
262
+ const result = await client.query<{ data: Buffer; updated_at: Date }>(
263
+ `SELECT data, updated_at FROM resources WHERE kind = $1 AND id = $2`,
264
+ [kind, id],
265
+ );
266
+ return result.rows[0]!;
267
+ }
268
+
269
+ it("moves a public row of every kind in the frozen table to org, and leaves an org row and a private row byte-for-byte", async () => {
270
+ const client = await v4Client();
271
+ const seededAt = new Date("2026-09-01T00:00:00Z");
272
+ try {
273
+ for (const entry of PUBLIC_ROW_KINDS_AT_RETIREMENT) {
274
+ await client.query(
275
+ `INSERT INTO resources (kind, id, data, updated_at) VALUES ($1, $2, $3, $4)`,
276
+ [
277
+ entry.kind,
278
+ `${entry.kind}_public`,
279
+ envelopeBytes(
280
+ entry.schema,
281
+ `${entry.kind}_public`,
282
+ ApiResourceVisibility.visibility_public,
283
+ ),
284
+ seededAt,
285
+ ],
286
+ );
287
+ }
288
+ const orgBytes = agentBytes(
289
+ "agt_org",
290
+ ApiResourceVisibility.visibility_org,
291
+ );
292
+ const privateBytes = agentBytes(
293
+ "agt_private",
294
+ ApiResourceVisibility.visibility_private,
295
+ );
296
+ await client.query(
297
+ `INSERT INTO resources (kind, id, data, updated_at) VALUES ('agent', 'agt_org', $1, $3), ('agent', 'agt_private', $2, $3)`,
298
+ [orgBytes, privateBytes, seededAt],
299
+ );
300
+
301
+ const reopened = await PostgresStore.open(db.databaseUrl);
302
+ await reopened.close();
303
+
304
+ const version = await client.query(
305
+ `SELECT COALESCE(MAX(version), 0) AS version FROM schema_version`,
306
+ );
307
+ expect(Number((version.rows[0] as { version: string }).version)).toBe(
308
+ CURRENT_SCHEMA_VERSION,
309
+ );
310
+ for (const entry of PUBLIC_ROW_KINDS_AT_RETIREMENT) {
311
+ const moved = await row(client, entry.kind, `${entry.kind}_public`);
312
+ expect(
313
+ moved.data.equals(
314
+ envelopeBytes(
315
+ entry.schema,
316
+ `${entry.kind}_public`,
317
+ ApiResourceVisibility.visibility_org,
318
+ ),
319
+ ),
320
+ entry.kind,
321
+ ).toBe(true);
322
+ expect(
323
+ moved.updated_at.getTime(),
324
+ `${entry.kind} updated_at bumped`,
325
+ ).toBeGreaterThan(seededAt.getTime());
326
+ }
327
+ const org = await row(client, "agent", "agt_org");
328
+ expect(org.data.equals(orgBytes)).toBe(true);
329
+ expect(org.updated_at.getTime()).toBe(seededAt.getTime());
330
+ const priv = await row(client, "agent", "agt_private");
331
+ expect(priv.data.equals(privateBytes)).toBe(true);
332
+ expect(priv.updated_at.getTime()).toBe(seededAt.getTime());
333
+ } finally {
334
+ await client.end();
335
+ }
336
+ });
337
+
338
+ it("a row of a table kind that does not decode fails the step, names the row, and leaves the database at v4 with every row untouched", async () => {
339
+ const client = await v4Client();
340
+ try {
341
+ const publicBytes = agentBytes(
342
+ "agt_public",
343
+ ApiResourceVisibility.visibility_public,
344
+ );
345
+ await client.query(
346
+ `INSERT INTO resources (kind, id, data) VALUES ('agent', 'agt_public', $1), ('agent', 'agt_broken', '\\xffffff'), ('session', 'ses_opaque', '\\xffff')`,
347
+ [publicBytes],
348
+ );
349
+
350
+ await expect(PostgresStore.open(db.databaseUrl)).rejects.toThrow(
351
+ /migrate to v5: .*agent 'agt_broken' cannot be moved off the retired public level/,
352
+ );
353
+
354
+ const version = await client.query(
355
+ `SELECT COALESCE(MAX(version), 0) AS version FROM schema_version`,
356
+ );
357
+ expect(Number((version.rows[0] as { version: string }).version)).toBe(
358
+ SCHEMA_VERSION_5 - 1,
359
+ );
360
+ expect(
361
+ (await row(client, "agent", "agt_public")).data.equals(publicBytes),
362
+ ).toBe(true);
363
+ // A kind outside the frozen table is never decoded, whatever it holds.
364
+ expect(
365
+ (await row(client, "session", "ses_opaque")).data.equals(
366
+ Buffer.from([0xff, 0xff]),
367
+ ),
368
+ ).toBe(true);
369
+ } finally {
370
+ await client.end();
371
+ }
372
+ });
373
+ });
193
374
  },
194
375
  );
@@ -111,8 +111,6 @@ describe.skipIf(testDatabaseAdminUrl() === undefined)(
111
111
  kinds: ["agent"],
112
112
  terms: ["billing"],
113
113
  orgFilter: "",
114
- crossOrgPublic: false,
115
- excludePublic: false,
116
114
  limit: 20,
117
115
  offset: 0,
118
116
  });
@@ -38,14 +38,21 @@
38
38
  */
39
39
  import type { PoolClient } from "pg";
40
40
 
41
+ import {
42
+ PUBLIC_ROW_KINDS_AT_RETIREMENT,
43
+ movePublicRowToOrg,
44
+ } from "../public-visibility-retired.js";
45
+
41
46
  export const SCHEMA_VERSION_1 = 1;
42
47
  export const SCHEMA_VERSION_2 = 2;
43
48
  export const SCHEMA_VERSION_3 = 3;
44
49
  /** v4: the rows of the removed Project kind deleted. */
45
50
  export const SCHEMA_VERSION_4 = 4;
51
+ /** v5: every row holding the retired public visibility level moved to org. */
52
+ export const SCHEMA_VERSION_5 = 5;
46
53
 
47
54
  /** Target version for new databases. */
48
- export const CURRENT_SCHEMA_VERSION = SCHEMA_VERSION_4;
55
+ export const CURRENT_SCHEMA_VERSION = SCHEMA_VERSION_5;
49
56
 
50
57
  /**
51
58
  * Advisory lock key for the migration chain. Arbitrary but stable 64-bit
@@ -79,6 +86,7 @@ export async function runMigrations(client: PoolClient): Promise<void> {
79
86
  [SCHEMA_VERSION_2, migrateToV2],
80
87
  [SCHEMA_VERSION_3, migrateToV3],
81
88
  [SCHEMA_VERSION_4, migrateToV4],
89
+ [SCHEMA_VERSION_5, migrateToV5],
82
90
  ];
83
91
 
84
92
  for (const [version, migrate] of chain) {
@@ -315,3 +323,42 @@ async function migrateToV4(client: PoolClient): Promise<void> {
315
323
  await client.query(`DELETE FROM resources WHERE kind = 'project'`);
316
324
  await client.query(`DELETE FROM resource_audit WHERE kind = 'project'`);
317
325
  }
326
+
327
+ /**
328
+ * v5: the public visibility level is retired, so every row that still
329
+ * holds it is moved to org visibility — the same step as the sqlite
330
+ * driver's v10 (public-visibility-retired.ts says why a migration, why the
331
+ * kind table is frozen there, and why an undecodable row fails the step).
332
+ * The rows are read FOR UPDATE inside applyInTransaction's transaction,
333
+ * under the chain's advisory lock, so a second instance booting against
334
+ * the same database waits rather than moving a row twice; `updated_at` is
335
+ * bumped the way saveResource bumps it; rows that hold any other level are
336
+ * left byte-for-byte as they are. On the hosted edition every such row was
337
+ * moved through the API before this release deployed, so this step finds
338
+ * none and records its version.
339
+ */
340
+ async function migrateToV5(client: PoolClient): Promise<void> {
341
+ for (const entry of PUBLIC_ROW_KINDS_AT_RETIREMENT) {
342
+ const result = await client.query<{ id: string; data: Buffer }>(
343
+ `SELECT id, data FROM resources WHERE kind = $1 FOR UPDATE`,
344
+ [entry.kind],
345
+ );
346
+ for (const row of result.rows) {
347
+ let moved: Uint8Array | undefined;
348
+ try {
349
+ moved = movePublicRowToOrg(entry, new Uint8Array(row.data));
350
+ } catch (error) {
351
+ throw new Error(
352
+ `${entry.kind} '${row.id}' cannot be moved off the retired public level: ${String(error)}`,
353
+ { cause: error },
354
+ );
355
+ }
356
+ if (moved !== undefined) {
357
+ await client.query(
358
+ `UPDATE resources SET data = $1, updated_at = now() WHERE kind = $2 AND id = $3`,
359
+ [Buffer.from(moved), entry.kind, row.id],
360
+ );
361
+ }
362
+ }
363
+ }
364
+ }
@@ -814,20 +814,13 @@ export class PostgresStore implements Store {
814
814
  // right side").
815
815
  const kindParam = `${push(query.kinds as string[])}::text[]`;
816
816
 
817
- // The scope-filter fragments — same composition as the sqlite driver's
818
- // port of Go's buildScopeFilter: org (strict or public-widened) and
819
- // the independent public subtraction.
817
+ // The scope-filter fragments — same composition as the sqlite driver:
818
+ // the org filter, strict (visibility never widens or narrows the
819
+ // scope), then the authorized-id allowlist.
820
820
  const scopeClauses: string[] = [];
821
821
  if (query.orgFilter !== "") {
822
822
  const orgParam = push(query.orgFilter);
823
- scopeClauses.push(
824
- query.crossOrgPublic
825
- ? `AND (org = ${orgParam} OR visibility = 'visibility_public')`
826
- : `AND org = ${orgParam}`,
827
- );
828
- }
829
- if (query.excludePublic) {
830
- scopeClauses.push(`AND visibility != 'visibility_public'`);
823
+ scopeClauses.push(`AND org = ${orgParam}`);
831
824
  }
832
825
  if (query.authorizedIdsByKind !== undefined) {
833
826
  // The 20260830.01 scoping arm: per-kind resource_id allowlists.
@@ -0,0 +1,119 @@
1
+ /**
2
+ * The one data migration both store drivers run when the public visibility
3
+ * level is retired: every stored row that holds `visibility_public` is moved
4
+ * to `visibility_org`, once, in place. The drivers own the SQL (which rows
5
+ * to read, how to write them back, the transaction); this module owns what
6
+ * is edition- and driver-neutral: WHICH kinds could hold the level and HOW
7
+ * one row's bytes are moved.
8
+ *
9
+ * Why a migration and not a sweep. The migration chain is the one place
10
+ * that runs exactly once per database, before any request is served. A
11
+ * public row nobody can set again would otherwise stay readable by every
12
+ * account on a self-hosted install forever, because the level's grant was
13
+ * derived from the row itself; on the hosted edition the rows are moved
14
+ * through the API before this release deploys (the tuple lifecycle is
15
+ * row-driven, and only that path deletes the grant's stored tuple), so the
16
+ * migration there finds none and is the safety net, never the mechanism.
17
+ *
18
+ * Why the kind table is frozen here and not read from the live kind
19
+ * configuration. A migration is a statement about the store AS IT WAS when
20
+ * the level was retired: the seven kinds whose VisibilityConfig declared
21
+ * `supports_public` at that moment are the only kinds a public row can
22
+ * exist under, and a later release that adds or removes a kind must not
23
+ * change what this step does. The list is spelled out with its schemas, the
24
+ * per-consumer kind-to-schema idiom the tree already uses; nothing here
25
+ * reads `kind_meta`. The `kind` strings are the enum NAMES the drivers'
26
+ * `kind` column holds (proto-fields.ts `apiResourceKindName`).
27
+ *
28
+ * Why decode and re-encode rather than patch bytes. `resources.data` is the
29
+ * marshaled proto message; the level is one enum field on the metadata
30
+ * message, and only a decode knows where it is. protobuf-es keeps unknown
31
+ * fields through `fromBinary` and writes them back in `toBinary`, so a row
32
+ * written by a newer or older release than this one loses nothing it
33
+ * carried; every other field is re-encoded from the same values. A row this
34
+ * step cannot decode is a fault that fails the whole transaction (the
35
+ * driver's `applyInTransaction` rolls back and the boot stops): skipping it
36
+ * would leave the one row the migration exists for readable by everyone,
37
+ * silently.
38
+ *
39
+ * `resource_audit` is deliberately untouched: it is the version history,
40
+ * and a snapshot that said "public" at the time is true history. The search
41
+ * index is left to boot's rebuild, which re-derives it from the rows.
42
+ */
43
+ import type { DescMessage } from "@bufbuild/protobuf";
44
+ import { fromBinary, toBinary } from "@bufbuild/protobuf";
45
+ import { reflect } from "@bufbuild/protobuf/reflect";
46
+
47
+ import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
48
+ import { AgentInstanceSchema } from "@stigmer/protos/ai/stigmer/agentic/agentinstance/v1/api_pb";
49
+ import { McpServerSchema } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
50
+ import { PluginSchema } from "@stigmer/protos/ai/stigmer/agentic/plugin/v1/api_pb";
51
+ import { SkillSchema } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/api_pb";
52
+ import { WorkflowSchema } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/api_pb";
53
+ import { WorkflowInstanceSchema } from "@stigmer/protos/ai/stigmer/agentic/workflowinstance/v1/api_pb";
54
+ import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
55
+
56
+ /** One kind that could hold the public level, with the schema its rows decode through. */
57
+ export interface PublicRowKind {
58
+ /** The enum NAME the drivers' `kind` column holds. */
59
+ readonly kind: string;
60
+ readonly schema: DescMessage;
61
+ }
62
+
63
+ /**
64
+ * The seven kinds whose VisibilityConfig declared `supports_public` when the
65
+ * level was retired — frozen (the module header). Order is the order the
66
+ * drivers walk the kinds; it has no other meaning.
67
+ */
68
+ export const PUBLIC_ROW_KINDS_AT_RETIREMENT: ReadonlyArray<PublicRowKind> = [
69
+ { kind: "agent", schema: AgentSchema },
70
+ { kind: "skill", schema: SkillSchema },
71
+ { kind: "mcp_server", schema: McpServerSchema },
72
+ { kind: "agent_instance", schema: AgentInstanceSchema },
73
+ { kind: "workflow", schema: WorkflowSchema },
74
+ { kind: "workflow_instance", schema: WorkflowInstanceSchema },
75
+ { kind: "plugin", schema: PluginSchema },
76
+ ];
77
+
78
+ /** The level every moved row takes: the widest level that stays inside the owning organization. */
79
+ export const LEVEL_AFTER_RETIREMENT = ApiResourceVisibility.visibility_org;
80
+
81
+ /**
82
+ * The moved bytes of one row when it holds the public level, or undefined
83
+ * when it does not (the driver then leaves the row's bytes exactly as they
84
+ * are; a row with no metadata holds no level and is one of these). Throws
85
+ * when the bytes do not decode as the kind's message: a row this migration
86
+ * cannot read is a row it must not pass over (the module header).
87
+ */
88
+ export function movePublicRowToOrg(
89
+ entry: PublicRowKind,
90
+ data: Uint8Array,
91
+ ): Uint8Array | undefined {
92
+ const message = fromBinary(entry.schema, data);
93
+ const root = reflect(entry.schema, message);
94
+ const metadataField = root.fields.find((field) => field.name === "metadata");
95
+ if (metadataField === undefined || metadataField.fieldKind !== "message") {
96
+ throw new Error(
97
+ `${entry.kind}: the schema declares no metadata message field`,
98
+ );
99
+ }
100
+ if (!root.isSet(metadataField)) {
101
+ return undefined;
102
+ }
103
+ const metadata = root.get(metadataField);
104
+ const visibilityField = metadata.fields.find(
105
+ (field) => field.name === "visibility",
106
+ );
107
+ if (visibilityField === undefined || visibilityField.fieldKind !== "enum") {
108
+ throw new Error(
109
+ `${entry.kind}: the metadata message declares no visibility enum field`,
110
+ );
111
+ }
112
+ if (
113
+ metadata.get(visibilityField) !== ApiResourceVisibility.visibility_public
114
+ ) {
115
+ return undefined;
116
+ }
117
+ metadata.set(visibilityField, LEVEL_AFTER_RETIREMENT);
118
+ return toBinary(entry.schema, message);
119
+ }