@stigmer/server 3.27.2 → 3.29.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 (227) hide show
  1. package/README.md +2 -0
  2. package/dist/authorization/authorizer.d.ts +3 -3
  3. package/dist/authorization/authorizer.js +3 -3
  4. package/dist/authorization/derived-tuples.js +6 -1
  5. package/dist/authorization/derived-tuples.js.map +1 -1
  6. package/dist/authorization/evaluator.d.ts +9 -10
  7. package/dist/authorization/evaluator.d.ts.map +1 -1
  8. package/dist/authorization/evaluator.js.map +1 -1
  9. package/dist/authorization/list-read-scope.d.ts.map +1 -1
  10. package/dist/authorization/list-read-scope.js +29 -18
  11. package/dist/authorization/list-read-scope.js.map +1 -1
  12. package/dist/authorization/model/bindings.d.ts +41 -0
  13. package/dist/authorization/model/bindings.d.ts.map +1 -0
  14. package/dist/authorization/model/bindings.js +89 -0
  15. package/dist/authorization/model/bindings.js.map +1 -0
  16. package/dist/authorization/model/data/authorization-model.json +3141 -0
  17. package/dist/authorization/model/default-of.d.ts +2 -2
  18. package/dist/authorization/model/execution-viewer.d.ts +3 -0
  19. package/dist/authorization/model/execution-viewer.d.ts.map +1 -0
  20. package/dist/authorization/model/execution-viewer.js +50 -0
  21. package/dist/authorization/model/execution-viewer.js.map +1 -0
  22. package/dist/authorization/model/index.d.ts +33 -21
  23. package/dist/authorization/model/index.d.ts.map +1 -1
  24. package/dist/authorization/model/index.js +74 -52
  25. package/dist/authorization/model/index.js.map +1 -1
  26. package/dist/authorization/model/openfga-json.d.ts +18 -0
  27. package/dist/authorization/model/openfga-json.d.ts.map +1 -0
  28. package/dist/authorization/model/openfga-json.js +266 -0
  29. package/dist/authorization/model/openfga-json.js.map +1 -0
  30. package/dist/authorization/model/rewrite.d.ts +39 -67
  31. package/dist/authorization/model/rewrite.d.ts.map +1 -1
  32. package/dist/authorization/model/rewrite.js +1 -117
  33. package/dist/authorization/model/rewrite.js.map +1 -1
  34. package/dist/authorization/strict-json.d.ts +20 -0
  35. package/dist/authorization/strict-json.d.ts.map +1 -0
  36. package/dist/authorization/strict-json.js +45 -0
  37. package/dist/authorization/strict-json.js.map +1 -0
  38. package/dist/boot/compose.d.ts.map +1 -1
  39. package/dist/boot/compose.js +9 -7
  40. package/dist/boot/compose.js.map +1 -1
  41. package/dist/domain/workflowexecution/list-index.d.ts +1 -1
  42. package/dist/extensions/list-read-scope.d.ts +20 -3
  43. package/dist/extensions/list-read-scope.d.ts.map +1 -1
  44. package/dist/extensions/list-read-scope.js +20 -1
  45. package/dist/extensions/list-read-scope.js.map +1 -1
  46. package/dist/index.d.ts +7 -8
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +15 -5
  49. package/dist/index.js.map +1 -1
  50. package/package.json +7 -6
  51. package/src/authorization/README.md +41 -34
  52. package/src/authorization/__tests__/authorizer.test.ts +5 -14
  53. package/src/authorization/__tests__/derived-tuples.test.ts +29 -27
  54. package/src/authorization/__tests__/evaluator.test.ts +53 -160
  55. package/src/authorization/__tests__/facts.test.ts +5 -11
  56. package/src/authorization/__tests__/list-read-scope.measure.test.ts +12 -20
  57. package/src/authorization/__tests__/list-read-scope.test.ts +15 -14
  58. package/src/authorization/__tests__/organization-directory.test.ts +2 -2
  59. package/src/authorization/__tests__/store-test-kit.ts +246 -0
  60. package/src/authorization/__tests__/store-tests.test.ts +92 -223
  61. package/src/authorization/__tests__/support.ts +30 -2
  62. package/src/authorization/__tests__/throwaway-model.ts +75 -0
  63. package/src/authorization/__tests__/wire-permissions.test.ts +141 -107
  64. package/src/authorization/authorizer.ts +3 -3
  65. package/src/authorization/derived-tuples.ts +7 -2
  66. package/src/authorization/evaluator.ts +8 -10
  67. package/src/authorization/list-read-scope.ts +37 -22
  68. package/src/authorization/model/__tests__/openfga-json.test.ts +353 -0
  69. package/src/authorization/model/__tests__/registry.test.ts +345 -277
  70. package/src/authorization/model/bindings.ts +131 -0
  71. package/src/authorization/model/data/authorization-model.json +3141 -0
  72. package/src/authorization/model/default-of.ts +2 -2
  73. package/src/authorization/model/execution-viewer.ts +56 -0
  74. package/src/authorization/model/index.ts +81 -72
  75. package/src/authorization/model/openfga-json.ts +310 -0
  76. package/src/authorization/model/rewrite.ts +39 -188
  77. package/src/authorization/strict-json.ts +52 -0
  78. package/src/boot/compose.ts +9 -7
  79. package/src/extensions/__tests__/built-in-authorization-composed.test.ts +3 -6
  80. package/src/extensions/list-read-scope.ts +32 -4
  81. package/src/index.ts +15 -44
  82. package/dist/authorization/model/agent.d.ts +0 -2
  83. package/dist/authorization/model/agent.d.ts.map +0 -1
  84. package/dist/authorization/model/agent.js +0 -43
  85. package/dist/authorization/model/agent.js.map +0 -1
  86. package/dist/authorization/model/agent_channel.d.ts +0 -2
  87. package/dist/authorization/model/agent_channel.d.ts.map +0 -1
  88. package/dist/authorization/model/agent_channel.js +0 -39
  89. package/dist/authorization/model/agent_channel.js.map +0 -1
  90. package/dist/authorization/model/agent_execution.d.ts +0 -2
  91. package/dist/authorization/model/agent_execution.d.ts.map +0 -1
  92. package/dist/authorization/model/agent_execution.js +0 -25
  93. package/dist/authorization/model/agent_execution.js.map +0 -1
  94. package/dist/authorization/model/agent_instance.d.ts +0 -2
  95. package/dist/authorization/model/agent_instance.d.ts.map +0 -1
  96. package/dist/authorization/model/agent_instance.js +0 -48
  97. package/dist/authorization/model/agent_instance.js.map +0 -1
  98. package/dist/authorization/model/agent_share.d.ts +0 -2
  99. package/dist/authorization/model/agent_share.d.ts.map +0 -1
  100. package/dist/authorization/model/agent_share.js +0 -29
  101. package/dist/authorization/model/agent_share.js.map +0 -1
  102. package/dist/authorization/model/api_key.d.ts +0 -2
  103. package/dist/authorization/model/api_key.d.ts.map +0 -1
  104. package/dist/authorization/model/api_key.js +0 -27
  105. package/dist/authorization/model/api_key.js.map +0 -1
  106. package/dist/authorization/model/artifact.d.ts +0 -2
  107. package/dist/authorization/model/artifact.d.ts.map +0 -1
  108. package/dist/authorization/model/artifact.js +0 -27
  109. package/dist/authorization/model/artifact.js.map +0 -1
  110. package/dist/authorization/model/channel_app.d.ts +0 -2
  111. package/dist/authorization/model/channel_app.d.ts.map +0 -1
  112. package/dist/authorization/model/channel_app.js +0 -28
  113. package/dist/authorization/model/channel_app.js.map +0 -1
  114. package/dist/authorization/model/environment.d.ts +0 -2
  115. package/dist/authorization/model/environment.d.ts.map +0 -1
  116. package/dist/authorization/model/environment.js +0 -33
  117. package/dist/authorization/model/environment.js.map +0 -1
  118. package/dist/authorization/model/execution_context.d.ts +0 -2
  119. package/dist/authorization/model/execution_context.d.ts.map +0 -1
  120. package/dist/authorization/model/execution_context.js +0 -19
  121. package/dist/authorization/model/execution_context.js.map +0 -1
  122. package/dist/authorization/model/iam_policy.d.ts +0 -2
  123. package/dist/authorization/model/iam_policy.d.ts.map +0 -1
  124. package/dist/authorization/model/iam_policy.js +0 -31
  125. package/dist/authorization/model/iam_policy.js.map +0 -1
  126. package/dist/authorization/model/identity_account.d.ts +0 -2
  127. package/dist/authorization/model/identity_account.d.ts.map +0 -1
  128. package/dist/authorization/model/identity_account.js +0 -25
  129. package/dist/authorization/model/identity_account.js.map +0 -1
  130. package/dist/authorization/model/mcp_server.d.ts +0 -2
  131. package/dist/authorization/model/mcp_server.d.ts.map +0 -1
  132. package/dist/authorization/model/mcp_server.js +0 -38
  133. package/dist/authorization/model/mcp_server.js.map +0 -1
  134. package/dist/authorization/model/memory.d.ts +0 -2
  135. package/dist/authorization/model/memory.d.ts.map +0 -1
  136. package/dist/authorization/model/memory.js +0 -26
  137. package/dist/authorization/model/memory.js.map +0 -1
  138. package/dist/authorization/model/oauth_app.d.ts +0 -2
  139. package/dist/authorization/model/oauth_app.d.ts.map +0 -1
  140. package/dist/authorization/model/oauth_app.js +0 -31
  141. package/dist/authorization/model/oauth_app.js.map +0 -1
  142. package/dist/authorization/model/organization.d.ts +0 -2
  143. package/dist/authorization/model/organization.d.ts.map +0 -1
  144. package/dist/authorization/model/organization.js +0 -58
  145. package/dist/authorization/model/organization.js.map +0 -1
  146. package/dist/authorization/model/platform_client.d.ts +0 -2
  147. package/dist/authorization/model/platform_client.d.ts.map +0 -1
  148. package/dist/authorization/model/platform_client.js +0 -31
  149. package/dist/authorization/model/platform_client.js.map +0 -1
  150. package/dist/authorization/model/plugin.d.ts +0 -2
  151. package/dist/authorization/model/plugin.d.ts.map +0 -1
  152. package/dist/authorization/model/plugin.js +0 -45
  153. package/dist/authorization/model/plugin.js.map +0 -1
  154. package/dist/authorization/model/schedule.d.ts +0 -2
  155. package/dist/authorization/model/schedule.d.ts.map +0 -1
  156. package/dist/authorization/model/schedule.js +0 -34
  157. package/dist/authorization/model/schedule.js.map +0 -1
  158. package/dist/authorization/model/session.d.ts +0 -2
  159. package/dist/authorization/model/session.d.ts.map +0 -1
  160. package/dist/authorization/model/session.js +0 -38
  161. package/dist/authorization/model/session.js.map +0 -1
  162. package/dist/authorization/model/skill.d.ts +0 -2
  163. package/dist/authorization/model/skill.d.ts.map +0 -1
  164. package/dist/authorization/model/skill.js +0 -39
  165. package/dist/authorization/model/skill.js.map +0 -1
  166. package/dist/authorization/model/workflow.d.ts +0 -2
  167. package/dist/authorization/model/workflow.d.ts.map +0 -1
  168. package/dist/authorization/model/workflow.js +0 -39
  169. package/dist/authorization/model/workflow.js.map +0 -1
  170. package/dist/authorization/model/workflow_execution.d.ts +0 -2
  171. package/dist/authorization/model/workflow_execution.d.ts.map +0 -1
  172. package/dist/authorization/model/workflow_execution.js +0 -33
  173. package/dist/authorization/model/workflow_execution.js.map +0 -1
  174. package/dist/authorization/model/workflow_instance.d.ts +0 -2
  175. package/dist/authorization/model/workflow_instance.d.ts.map +0 -1
  176. package/dist/authorization/model/workflow_instance.js +0 -96
  177. package/dist/authorization/model/workflow_instance.js.map +0 -1
  178. package/dist/authorization/store-test-kit.d.ts +0 -62
  179. package/dist/authorization/store-test-kit.d.ts.map +0 -1
  180. package/dist/authorization/store-test-kit.js +0 -265
  181. package/dist/authorization/store-test-kit.js.map +0 -1
  182. package/src/authorization/__tests__/enterprise-model.ts +0 -59
  183. package/src/authorization/__tests__/fixtures/fga/agent-channel-owner.fga.yaml +0 -112
  184. package/src/authorization/__tests__/fixtures/fga/agent-instance-creation.fga.yaml +0 -177
  185. package/src/authorization/__tests__/fixtures/fga/agent-share-owner.fga.yaml +0 -109
  186. package/src/authorization/__tests__/fixtures/fga/artifact-org-and-owner.fga.yaml +0 -128
  187. package/src/authorization/__tests__/fixtures/fga/blueprint-private-visibility.fga.yaml +0 -318
  188. package/src/authorization/__tests__/fixtures/fga/channel-app-owner.fga.yaml +0 -125
  189. package/src/authorization/__tests__/fixtures/fga/channel-participation.fga.yaml +0 -201
  190. package/src/authorization/__tests__/fixtures/fga/channel-session-visibility.fga.yaml +0 -187
  191. package/src/authorization/__tests__/fixtures/fga/default-instance-inheritance.fga.yaml +0 -214
  192. package/src/authorization/__tests__/fixtures/fga/mcp-server-authoring.fga.yaml +0 -129
  193. package/src/authorization/__tests__/fixtures/fga/memory-subject-only.fga.yaml +0 -72
  194. package/src/authorization/__tests__/fixtures/fga/org-admin-owner-inheritance.fga.yaml +0 -308
  195. package/src/authorization/__tests__/fixtures/fga/org-shared-environment.fga.yaml +0 -144
  196. package/src/authorization/__tests__/fixtures/fga/platform-visibility.fga.yaml +0 -199
  197. package/src/authorization/__tests__/fixtures/fga/provider-standing-viewer.fga.yaml +0 -40
  198. package/src/authorization/__tests__/fixtures/fga/reserved-label-writer.fga.yaml +0 -41
  199. package/src/authorization/__tests__/fixtures/fga/schedule-owner.fga.yaml +0 -113
  200. package/src/authorization/__tests__/fixtures/fga/schedule-session-visibility.fga.yaml +0 -214
  201. package/src/authorization/__tests__/fixtures/fga/session-personal-resource.fga.yaml +0 -117
  202. package/src/authorization/__tests__/fixtures/fga/workflow-execution-sharing.fga.yaml +0 -190
  203. package/src/authorization/model/agent.ts +0 -64
  204. package/src/authorization/model/agent_channel.ts +0 -57
  205. package/src/authorization/model/agent_execution.ts +0 -33
  206. package/src/authorization/model/agent_instance.ts +0 -66
  207. package/src/authorization/model/agent_share.ts +0 -40
  208. package/src/authorization/model/api_key.ts +0 -28
  209. package/src/authorization/model/artifact.ts +0 -38
  210. package/src/authorization/model/channel_app.ts +0 -39
  211. package/src/authorization/model/environment.ts +0 -48
  212. package/src/authorization/model/execution_context.ts +0 -20
  213. package/src/authorization/model/iam_policy.ts +0 -42
  214. package/src/authorization/model/identity_account.ts +0 -26
  215. package/src/authorization/model/mcp_server.ts +0 -59
  216. package/src/authorization/model/memory.ts +0 -27
  217. package/src/authorization/model/oauth_app.ts +0 -43
  218. package/src/authorization/model/organization.ts +0 -59
  219. package/src/authorization/model/platform_client.ts +0 -43
  220. package/src/authorization/model/plugin.ts +0 -66
  221. package/src/authorization/model/schedule.ts +0 -49
  222. package/src/authorization/model/session.ts +0 -50
  223. package/src/authorization/model/skill.ts +0 -60
  224. package/src/authorization/model/workflow.ts +0 -60
  225. package/src/authorization/model/workflow_execution.ts +0 -46
  226. package/src/authorization/model/workflow_instance.ts +0 -125
  227. package/src/authorization/store-test-kit.ts +0 -395
@@ -1,19 +1,20 @@
1
1
  /**
2
2
  * Pins the evaluator's semantics the store tests cannot express (the
3
- * cloud's `.fga.yaml` documents prove the model's answers; this file
4
- * proves the machinery under them): how a person matches a tuple, that a
5
- * direct tuple outside the line's type restriction is ignored (model drift
6
- * fails closed), how far a resolution may walk, that an intersection
7
- * holds only while every member does (the team bound) and stops at its
8
- * first false member, that `declareKind` refuses a line a `.fga` file
9
- * could not hold, and that a declaration cycle or an undeclared target is
10
- * a thrown fault and never a silent deny.
3
+ * model's `.fga.yaml` suites prove its answers; this file proves the
4
+ * machinery under them): how a person matches a tuple, that a direct
5
+ * tuple outside the line's type restriction is ignored (model drift fails
6
+ * closed), how far a resolution may walk, that an intersection holds only
7
+ * while every member does (the team bound) and stops at its first false
8
+ * member, and that a declaration cycle or an undeclared target is a
9
+ * thrown fault and never a silent deny. What the model file may hold is
10
+ * the reader's to refuse (model/__tests__/openfga-json.test.ts).
11
11
  *
12
12
  * The fixture source is the in-memory one the store-test kit uses, so the
13
13
  * two proofs share one tuple vocabulary. Where a case needs a shape the
14
- * real model does not have (a cycle, a chain past the depth bound), it
15
- * declares a throwaway model with `declareKind`; everything else runs
16
- * over the built-in model so the fixtures are real relations.
14
+ * real model does not have (a cycle, a chain past the depth bound, a type
15
+ * the model omits), it declares a throwaway model (throwaway-model.ts);
16
+ * everything else runs over the built-in model so the fixtures are real
17
+ * relations.
17
18
  */
18
19
  import { describe, expect, it } from "vitest";
19
20
 
@@ -26,23 +27,21 @@ import {
26
27
  checkRelation,
27
28
  } from "../evaluator.js";
28
29
  import { builtInModel, newModel } from "../model/index.js";
30
+ import type { Person, Tuple, TupleSource } from "../tuples.js";
31
+ import {
32
+ newInMemoryTupleSource,
33
+ parseObjectRef,
34
+ parseSubject,
35
+ } from "../tuples.js";
29
36
  import {
30
37
  computed,
31
- declareKind,
32
38
  direct,
33
39
  from,
34
- intersection,
35
40
  objectOf,
41
+ throwawayDeclaration,
36
42
  union,
37
43
  usersetOf,
38
- } from "../model/rewrite.js";
39
- import type { Rewrite } from "../model/rewrite.js";
40
- import type { Person, Tuple, TupleSource } from "../tuples.js";
41
- import {
42
- newInMemoryTupleSource,
43
- parseObjectRef,
44
- parseSubject,
45
- } from "../tuples.js";
44
+ } from "./throwaway-model.js";
46
45
 
47
46
  /** `object#relation@subject` in the FGA tuple notation, the fixtures' own spelling. */
48
47
  function tuple(text: string): Tuple {
@@ -157,13 +156,13 @@ describe("relations and kinds the model does not declare", () => {
157
156
  ).toBe(false);
158
157
  });
159
158
 
160
- it("an undeclared kind as the TARGET is a thrown fault — the driver refused unserved kinds before, so reaching here is a registry gap", async () => {
159
+ it("an undeclared kind as the TARGET is a thrown fault — the model declares every kind, so reaching here is a registry gap", async () => {
161
160
  await expect(
162
- allowed(source, "identity_provider:idp", "can_view", person("ida_carol")),
161
+ allowed(source, "api_resource_version:v1", "can_view", person("ida_carol")),
163
162
  ).rejects.toMatchObject({ reason: "undeclared-target-kind" });
164
163
  });
165
164
 
166
- it("an undeclared kind reached through a hop yields nothing — `agent.platform_viewer` walks `identity_provider#platform_user`", async () => {
165
+ it("a declared kind reached through a hop with no tuple on it yields nothing — `agent.platform_viewer` walks `identity_provider#platform_user`", async () => {
167
166
  const platform = newInMemoryTupleSource([
168
167
  tuple("agent:p#organization@organization:acme"),
169
168
  tuple("agent:p#platform_viewer@identity_provider:idp#platform_user"),
@@ -172,6 +171,30 @@ describe("relations and kinds the model does not declare", () => {
172
171
  await allowed(platform, "agent:p", "can_view", person("ida_zed")),
173
172
  ).toBe(false);
174
173
  });
174
+
175
+ it("a type the model omits, reached through a hop, is an object with no relations — nothing, never a fault", async () => {
176
+ const model = newModel([
177
+ throwawayDeclaration({
178
+ kind: ApiResourceKind.agent,
179
+ schema: AgentSchema,
180
+ relations: [
181
+ ["viewer", direct(usersetOf("api_resource_version", "member"))],
182
+ ["can_view", computed("viewer")],
183
+ ],
184
+ }),
185
+ ]);
186
+ const hop = newInMemoryTupleSource([
187
+ tuple("agent:p#viewer@api_resource_version:v1#member"),
188
+ ]);
189
+ expect(
190
+ await checkRelation(
191
+ { model, source: hop },
192
+ parseObjectRef("agent:p"),
193
+ "can_view",
194
+ person("ida_zed"),
195
+ ),
196
+ ).toBe(false);
197
+ });
175
198
  });
176
199
 
177
200
  describe("the walk itself", () => {
@@ -203,10 +226,9 @@ describe("the walk itself", () => {
203
226
 
204
227
  it("bounds the resolution at OpenFGA's own limit and reports the overrun as a fault", async () => {
205
228
  // A chain of `next from link` deeper than the bound, on a throwaway model.
206
- const deep = declareKind({
229
+ const deep = throwawayDeclaration({
207
230
  kind: ApiResourceKind.agent,
208
231
  schema: AgentSchema,
209
- source: "test/deep.fga",
210
232
  relations: [
211
233
  ["link", direct(objectOf("agent"))],
212
234
  [
@@ -230,23 +252,8 @@ describe("the walk itself", () => {
230
252
  });
231
253
 
232
254
  it("an intersection holds only while every member does — a team member reaches a team grant only while still an organization viewer", async () => {
233
- // The Enterprise team type, declared here because open source serves
234
- // no team: membership bounded by the organization's viewers.
235
- const team = declareKind({
236
- kind: ApiResourceKind.team,
237
- schema: AgentSchema,
238
- source: "test/team.fga",
239
- relations: [
240
- ["organization", direct(objectOf("organization"))],
241
- [
242
- "member",
243
- intersection(
244
- direct(objectOf("identity_account")),
245
- from("viewer", "organization"),
246
- ),
247
- ],
248
- ],
249
- });
255
+ // The model's Enterprise team type: `member` is a person granted the
256
+ // role AND still one of the organization's viewers.
250
257
  const source = newInMemoryTupleSource([
251
258
  tuple("organization:acme#viewer@identity_account:ida_vic"),
252
259
  tuple("team:sre#organization@organization:acme"),
@@ -255,10 +262,9 @@ describe("the walk itself", () => {
255
262
  tuple("agent:a#organization@organization:acme"),
256
263
  tuple("agent:a#viewer@team:sre#member"),
257
264
  ]);
258
- const model = newModel([...builtInModel.declarations, team]);
259
265
  const check = (who: Person): Promise<boolean> =>
260
266
  checkRelation(
261
- { model, source },
267
+ { model: builtInModel, source },
262
268
  parseObjectRef("agent:a"),
263
269
  "can_view",
264
270
  who,
@@ -270,21 +276,6 @@ describe("the walk itself", () => {
270
276
  });
271
277
 
272
278
  it("an intersection stops at its first false member — the direct arm is read before the hop that bounds it", async () => {
273
- const team = declareKind({
274
- kind: ApiResourceKind.team,
275
- schema: AgentSchema,
276
- source: "test/team.fga",
277
- relations: [
278
- ["organization", direct(objectOf("organization"))],
279
- [
280
- "member",
281
- intersection(
282
- direct(objectOf("identity_account")),
283
- from("viewer", "organization"),
284
- ),
285
- ],
286
- ],
287
- });
288
279
  const asked: string[] = [];
289
280
  const inner = newInMemoryTupleSource([
290
281
  tuple("team:sre#organization@organization:acme"),
@@ -297,7 +288,7 @@ describe("the walk itself", () => {
297
288
  };
298
289
  expect(
299
290
  await checkRelation(
300
- { model: newModel([team]), source: counting },
291
+ { model: builtInModel, source: counting },
301
292
  parseObjectRef("team:sre"),
302
293
  "member",
303
294
  person("ida_nobody"),
@@ -307,10 +298,9 @@ describe("the walk itself", () => {
307
298
  });
308
299
 
309
300
  it("a declaration that re-enters itself on one object is a cycle fault, never a silent false", async () => {
310
- const cyclic = declareKind({
301
+ const cyclic = throwawayDeclaration({
311
302
  kind: ApiResourceKind.agent,
312
303
  schema: AgentSchema,
313
- source: "test/cyclic.fga",
314
304
  relations: [
315
305
  ["a", computed("b")],
316
306
  ["b", computed("a")],
@@ -326,100 +316,3 @@ describe("the walk itself", () => {
326
316
  ).rejects.toBeInstanceOf(AuthorizationEvaluationError);
327
317
  });
328
318
  });
329
-
330
- describe("declareKind validates a transcript at load", () => {
331
- it("refuses a computed or tupleset reference to a relation the kind does not declare", () => {
332
- expect(() =>
333
- declareKind({
334
- kind: ApiResourceKind.agent,
335
- schema: AgentSchema,
336
- source: "test/bad.fga",
337
- relations: [["can_view", computed("viewer")]],
338
- }),
339
- ).toThrow("relation 'viewer'");
340
- expect(() =>
341
- declareKind({
342
- kind: ApiResourceKind.agent,
343
- schema: AgentSchema,
344
- source: "test/bad.fga",
345
- relations: [["owner", from("admin", "organization")]],
346
- }),
347
- ).toThrow("tupleset 'organization'");
348
- });
349
-
350
- it("refuses a relation declared twice", () => {
351
- expect(() =>
352
- declareKind({
353
- kind: ApiResourceKind.agent,
354
- schema: AgentSchema,
355
- source: "test/bad.fga",
356
- relations: [
357
- ["viewer", direct(objectOf("identity_account"))],
358
- ["viewer", direct(usersetOf("organization", "viewer"))],
359
- ],
360
- }),
361
- ).toThrow("declared twice");
362
- });
363
-
364
- it("refuses a line no .fga file could hold without parentheses, and an operator over one member", () => {
365
- const declare = (rewrite: Rewrite) =>
366
- declareKind({
367
- kind: ApiResourceKind.agent,
368
- schema: AgentSchema,
369
- source: "test/bad.fga",
370
- relations: [
371
- ["organization", direct(objectOf("organization"))],
372
- ["viewer", rewrite],
373
- ],
374
- });
375
- expect(() =>
376
- declare(
377
- intersection(
378
- union(direct(objectOf("identity_account")), computed("organization")),
379
- from("viewer", "organization"),
380
- ),
381
- ),
382
- ).toThrow("nests or inside and");
383
- expect(() =>
384
- declare(
385
- union(
386
- intersection(
387
- direct(objectOf("identity_account")),
388
- from("viewer", "organization"),
389
- ),
390
- computed("organization"),
391
- ),
392
- ),
393
- ).toThrow("nests and inside or");
394
- expect(() =>
395
- declare(intersection(direct(objectOf("identity_account")))),
396
- ).toThrow("fewer than two members");
397
- expect(() =>
398
- declare(
399
- intersection(
400
- direct(objectOf("identity_account")),
401
- from("viewer", "organization"),
402
- ),
403
- ),
404
- ).not.toThrow();
405
- });
406
-
407
- it("carries the FGA type name of its kind and its relations in file order", () => {
408
- const declaration = declareKind({
409
- kind: ApiResourceKind.mcp_server,
410
- schema: AgentSchema,
411
- source: "test/order.fga",
412
- relations: [
413
- ["organization", direct(objectOf("organization"))],
414
- ["owner", direct(usersetOf("organization", "admin"))],
415
- ["can_edit", computed("owner")],
416
- ],
417
- });
418
- expect(declaration.type).toBe("mcp_server");
419
- expect([...declaration.relations.keys()]).toEqual([
420
- "organization",
421
- "owner",
422
- "can_edit",
423
- ]);
424
- });
425
- });
@@ -36,7 +36,7 @@ import {
36
36
  } from "../../pipeline/steps/authorization-tuples.js";
37
37
  import { rowFactsOf, rowFactsOfEntry } from "../facts.js";
38
38
  import { builtInModel } from "../model/index.js";
39
- import { fixtureRow } from "./support.js";
39
+ import { fixtureRow, hasStoredRows, storedDeclaration } from "./support.js";
40
40
 
41
41
  const CREATOR: CallerIdentity = {
42
42
  identityId: "ida_carol",
@@ -46,7 +46,7 @@ const CREATOR: CallerIdentity = {
46
46
  };
47
47
 
48
48
  describe("rowFactsOf against the tuple lifecycle's own resolution", () => {
49
- for (const declaration of builtInModel.declarations) {
49
+ for (const declaration of builtInModel.declarations.filter(hasStoredRows)) {
50
50
  for (const visibility of [
51
51
  ApiResourceVisibility.visibility_private,
52
52
  ApiResourceVisibility.visibility_org,
@@ -77,10 +77,7 @@ describe("rowFactsOf against the tuple lifecycle's own resolution", () => {
77
77
  }
78
78
 
79
79
  it("carries a legacy row's missing organization as no scope link, where the create-time resolver throws", () => {
80
- const agent = builtInModel.byType("agent");
81
- if (agent === undefined) {
82
- throw new Error("agent is declared in this slice");
83
- }
80
+ const agent = storedDeclaration("agent");
84
81
  const row = fixtureRow(agent, {
85
82
  id: "agent-legacy",
86
83
  org: "",
@@ -94,10 +91,7 @@ describe("rowFactsOf against the tuple lifecycle's own resolution", () => {
94
91
  });
95
92
 
96
93
  it("reads an absent creator stamp as the empty string", () => {
97
- const agent = builtInModel.byType("agent");
98
- if (agent === undefined) {
99
- throw new Error("agent is declared in this slice");
100
- }
94
+ const agent = storedDeclaration("agent");
101
95
  const bare = create(agent.schema, {
102
96
  metadata: { id: "agent-bare", org: "acme" },
103
97
  });
@@ -129,7 +123,7 @@ describe("a list candidate against the loaded row — the facts' two producers",
129
123
  return offered;
130
124
  }
131
125
 
132
- for (const declaration of builtInModel.declarations) {
126
+ for (const declaration of builtInModel.declarations.filter(hasStoredRows)) {
133
127
  for (const visibility of [
134
128
  ApiResourceVisibility.visibility_private,
135
129
  ApiResourceVisibility.visibility_org,
@@ -48,8 +48,8 @@
48
48
  *
49
49
  * Every shape states what its caller keeps, so a measurement over an
50
50
  * evaluator that answered wrongly measures nothing. The model is the
51
- * built-in one with the Enterprise team type beside it
52
- * (enterprise-model.ts); the open-source shapes answer the same under it.
51
+ * built-in one, which declares the Enterprise team type beside the
52
+ * open-source ones.
53
53
  */
54
54
  import { create } from "@bufbuild/protobuf";
55
55
  import { afterAll, afterEach, beforeEach, describe, expect, it } from "vitest";
@@ -84,8 +84,8 @@ import { newBuiltInListReadScope } from "../list-read-scope.js";
84
84
  import type { KindDeclaration } from "../model/rewrite.js";
85
85
  import { driverFixtures, dropPostgresFixture } from "./drivers.js";
86
86
  import type { OpenedStore } from "./drivers.js";
87
- import { enterpriseModel, teamDeclaration } from "./enterprise-model.js";
88
- import { fixtureRow } from "./support.js";
87
+ import { builtInModel } from "../model/index.js";
88
+ import { fixtureRow, storedDeclaration } from "./support.js";
89
89
  import type { FixtureRowFacts } from "./support.js";
90
90
 
91
91
  const MEASURE = process.env["AUTHORIZATION_MEASURE"] === "1";
@@ -143,14 +143,6 @@ function directGrantOn(i: number): boolean {
143
143
  return i % 50 === 1;
144
144
  }
145
145
 
146
- function declared(type: string): KindDeclaration {
147
- const declaration = enterpriseModel.byType(type);
148
- if (declaration === undefined) {
149
- throw new Error(`${type} is declared`);
150
- }
151
- return declaration;
152
- }
153
-
154
146
  function resolved(accountId: string): CallerIdentity {
155
147
  return {
156
148
  identityId: accountId,
@@ -461,7 +453,7 @@ describe
461
453
  let accounts: IdentityAccountStore;
462
454
 
463
455
  async function save(type: string, facts: FixtureRowFacts): Promise<object> {
464
- const declaration = declared(type);
456
+ const declaration = storedDeclaration(type);
465
457
  const row = fixtureRow(declaration, facts);
466
458
  await opened.store.saveResource(
467
459
  declaration.kind,
@@ -530,8 +522,8 @@ describe
530
522
  await opened.store.saveResource(
531
523
  ApiResourceKind.team,
532
524
  teamId(team),
533
- teamDeclaration.schema,
534
- fixtureRow(teamDeclaration, {
525
+ storedDeclaration("team").schema,
526
+ fixtureRow(storedDeclaration("team"), {
535
527
  id: teamId(team),
536
528
  org: ORG,
537
529
  visibility: ApiResourceVisibility.visibility_private,
@@ -550,7 +542,7 @@ describe
550
542
  it(
551
543
  `${shape.name} at ${n} rows`,
552
544
  async () => {
553
- const declaration = declared(shape.kind);
545
+ const declaration = storedDeclaration(shape.kind);
554
546
  const policyRows = await seedPopulation(n);
555
547
  for (const parent of shape.parents ?? []) {
556
548
  await save(parent.type, parent.facts);
@@ -568,7 +560,7 @@ describe
568
560
  policies,
569
561
  accounts,
570
562
  logger: silentLogger,
571
- model: enterpriseModel,
563
+ model: builtInModel,
572
564
  });
573
565
  const started = performance.now();
574
566
  const kept =
@@ -599,7 +591,7 @@ describe
599
591
  it(
600
592
  `pages: ${shape.name}, at a ${PAGE_POPULATION}-row population`,
601
593
  async () => {
602
- const declaration = declared("agent");
594
+ const declaration = storedDeclaration("agent");
603
595
  const policyRows = await seedPopulation(PAGE_POPULATION);
604
596
  const entries: ListEntryMeta[] = [];
605
597
  for (let i = 0; i < PAGE_EXAMINE_BUDGET; i += 1) {
@@ -614,7 +606,7 @@ describe
614
606
  policies,
615
607
  accounts,
616
608
  logger: silentLogger,
617
- model: enterpriseModel,
609
+ model: builtInModel,
618
610
  });
619
611
  const caller = resolved(shape.caller);
620
612
  // A page's batches run one after another, each through the scope
@@ -659,7 +651,7 @@ describe
659
651
  accounts,
660
652
  edition: ServerEdition.oss,
661
653
  logger: silentLogger,
662
- model: enterpriseModel,
654
+ model: builtInModel,
663
655
  });
664
656
  const check = {
665
657
  permission: IamPermission.can_view,
@@ -53,7 +53,10 @@ import type {
53
53
  ListEntryMeta,
54
54
  ListReadScope,
55
55
  } from "../../extensions/list-read-scope.js";
56
- import { restrictListByReadScope } from "../../extensions/list-read-scope.js";
56
+ import {
57
+ InternalCallerOfferedError,
58
+ restrictListByReadScope,
59
+ } from "../../extensions/list-read-scope.js";
57
60
  import { kindEnumName } from "../../pipeline/apiresource-meta.js";
58
61
  import { rowAuthorizationFactsOf } from "../../pipeline/steps/authorization-facts.js";
59
62
  import type { Store } from "../../store/interface.js";
@@ -63,7 +66,7 @@ import { builtInModel } from "../model/index.js";
63
66
  import type { KindDeclaration } from "../model/rewrite.js";
64
67
  import { driverFixtures, dropPostgresFixture } from "./drivers.js";
65
68
  import type { OpenedStore } from "./drivers.js";
66
- import { fixtureRow } from "./support.js";
69
+ import { fixtureRow, storedDeclaration } from "./support.js";
67
70
  import type { FixtureRowFacts } from "./support.js";
68
71
 
69
72
  const FOUNDER = accountIdFor("auth0|founder");
@@ -94,14 +97,6 @@ const SEEDED_KINDS = [
94
97
  ApiResourceKind.agent_instance,
95
98
  ];
96
99
 
97
- function declared(type: string): KindDeclaration {
98
- const declaration = builtInModel.byType(type);
99
- if (declaration === undefined) {
100
- throw new Error(`${type} is declared`);
101
- }
102
- return declaration;
103
- }
104
-
105
100
  function resolved(accountId: string): CallerIdentity {
106
101
  return {
107
102
  identityId: accountId,
@@ -161,7 +156,7 @@ describe.each(driverFixtures(SEEDED_KINDS))(
161
156
  type: string,
162
157
  facts: FixtureRowFacts,
163
158
  ): Promise<void> {
164
- const declaration = declared(type);
159
+ const declaration = storedDeclaration(type);
165
160
  const row = fixtureRow(declaration, facts);
166
161
  await opened.store.saveResource(
167
162
  declaration.kind,
@@ -608,9 +603,15 @@ describe.each(driverFixtures(SEEDED_KINDS))(
608
603
  () => undefined,
609
604
  (error: unknown) => error,
610
605
  );
611
- expect(refusal).toBeInstanceOf(AuthorizationEvaluationError);
612
- expect((refusal as AuthorizationEvaluationError).reason).toBe(
613
- "internal-caller-offered",
606
+ // The seam's own refusal, the one a composition's driver throws
607
+ // too; never the evaluator's fault, since nothing was evaluated.
608
+ expect(refusal).toBeInstanceOf(InternalCallerOfferedError);
609
+ expect(refusal).not.toBeInstanceOf(AuthorizationEvaluationError);
610
+ expect((refusal as InternalCallerOfferedError).kind).toBe(
611
+ ApiResourceKind.agent,
612
+ );
613
+ expect((refusal as InternalCallerOfferedError).message).toContain(
614
+ "kind 'agent'",
614
615
  );
615
616
  // Refused before any read: the server was never evaluated as a person.
616
617
  expect(accountReads).toBe(0);
@@ -38,7 +38,7 @@ import { builtInModel } from "../model/index.js";
38
38
  import { newBuiltInOrganizationDirectory } from "../organization-directory.js";
39
39
  import { driverFixtures, dropPostgresFixture } from "./drivers.js";
40
40
  import type { OpenedStore } from "./drivers.js";
41
- import { fixtureRow } from "./support.js";
41
+ import { fixtureRow, storedDeclaration } from "./support.js";
42
42
 
43
43
  const FOUNDER = accountIdFor("auth0|founder");
44
44
  const MEMBER = accountIdFor("auth0|member");
@@ -81,7 +81,7 @@ describe.each(
81
81
  let principalReads: number;
82
82
 
83
83
  async function organization(id: string) {
84
- const declaration = builtInModel.byType("organization")!;
84
+ const declaration = storedDeclaration("organization");
85
85
  await opened.store.saveResource(
86
86
  ApiResourceKind.organization,
87
87
  id,