@stigmer/server 3.22.0 → 3.22.1-dev.20260922095755

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 (265) 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/blueprint-private-visibility.fga.yaml +6 -98
  178. package/src/authorization/__tests__/fixtures/fga/default-instance-inheritance.fga.yaml +4 -61
  179. package/src/authorization/__tests__/fixtures/fga/org-admin-owner-inheritance.fga.yaml +1 -21
  180. package/src/authorization/__tests__/fixtures/fga/platform-visibility.fga.yaml +1 -25
  181. package/src/authorization/__tests__/fixtures/fga/workflow-execution-sharing.fga.yaml +1 -3
  182. package/src/authorization/__tests__/list-read-scope.test.ts +18 -17
  183. package/src/authorization/__tests__/store-tests.test.ts +1 -6
  184. package/src/authorization/__tests__/tuples.test.ts +12 -11
  185. package/src/authorization/authorizer.ts +4 -9
  186. package/src/authorization/derived-tuples.ts +14 -31
  187. package/src/authorization/evaluator.ts +8 -44
  188. package/src/authorization/list-read-scope.ts +5 -11
  189. package/src/authorization/model/agent.ts +2 -5
  190. package/src/authorization/model/agent_instance.ts +0 -2
  191. package/src/authorization/model/mcp_server.ts +0 -2
  192. package/src/authorization/model/plugin.ts +1 -4
  193. package/src/authorization/model/rewrite.ts +8 -13
  194. package/src/authorization/model/skill.ts +0 -2
  195. package/src/authorization/model/workflow.ts +0 -2
  196. package/src/authorization/model/workflow_instance.ts +0 -2
  197. package/src/authorization/organization-directory.ts +1 -3
  198. package/src/authorization/store-test-kit.ts +16 -75
  199. package/src/authorization/tuples.ts +16 -39
  200. package/src/domain/agent/__tests__/agent.test.ts +11 -4
  201. package/src/domain/agent/controller.ts +7 -7
  202. package/src/domain/agentchannel/__tests__/agentchannel.test.ts +223 -48
  203. package/src/domain/agentchannel/controller.ts +6 -1
  204. package/src/domain/agentexecution/controller.ts +6 -1
  205. package/src/domain/agentinstance/controller.ts +6 -12
  206. package/src/domain/agentshare/__tests__/agentshare.test.ts +196 -172
  207. package/src/domain/agentshare/__tests__/create-authorization.test.ts +16 -31
  208. package/src/domain/agentshare/constants.ts +14 -26
  209. package/src/domain/agentshare/controller.ts +6 -1
  210. package/src/domain/agentshare/steps.ts +41 -194
  211. package/src/domain/environment/controller.ts +5 -8
  212. package/src/domain/executioncontext/controller.ts +5 -1
  213. package/src/domain/mcpserver/__tests__/mcpserver.test.ts +115 -28
  214. package/src/domain/mcpserver/__tests__/oauth-handshake.test.ts +53 -18
  215. package/src/domain/mcpserver/controller.ts +6 -12
  216. package/src/domain/plugin/controller.ts +1 -8
  217. package/src/domain/plugin/push.ts +8 -16
  218. package/src/domain/schedule/controller.ts +6 -1
  219. package/src/domain/session/controller.ts +12 -4
  220. package/src/domain/skill/controller.ts +0 -7
  221. package/src/domain/workflow/__tests__/agent-call-references.test.ts +222 -0
  222. package/src/domain/workflow/agent-call-references.ts +157 -0
  223. package/src/domain/workflow/controller.ts +28 -10
  224. package/src/domain/workflow/registry/data/task-kind-registry.json +1 -1
  225. package/src/domain/workflow/validation/task-config-constraints.ts +7 -3
  226. package/src/domain/workflowexecution/controller.ts +7 -2
  227. package/src/domain/workflowinstance/controller.ts +6 -12
  228. package/src/extensions/__tests__/built-in-authorization-composed.test.ts +32 -19
  229. package/src/extensions/__tests__/extension-composition.test.ts +4 -4
  230. package/src/extensions/__tests__/list-read-scope-composed.test.ts +9 -6
  231. package/src/extensions/authorization-queries.ts +6 -5
  232. package/src/extensions/list-read-scope.ts +1 -1
  233. package/src/extensions/resource-authorization.ts +3 -5
  234. package/src/index.ts +0 -1
  235. package/src/pipeline/__tests__/supports-visibility.test.ts +112 -0
  236. package/src/pipeline/apiresource-meta.ts +21 -10
  237. package/src/pipeline/steps/__tests__/authorization-tuples.test.ts +18 -31
  238. package/src/pipeline/steps/__tests__/references.test.ts +703 -0
  239. package/src/pipeline/steps/authorization-tuples.ts +9 -11
  240. package/src/pipeline/steps/references.ts +578 -70
  241. package/src/query/search/__tests__/criteria.test.ts +0 -4
  242. package/src/query/search/__tests__/query-store.test.ts +11 -40
  243. package/src/query/search/controller.ts +0 -1
  244. package/src/query/search/criteria.ts +0 -14
  245. package/src/query/search/handler.ts +6 -7
  246. package/src/query/search/query-store.ts +0 -2
  247. package/src/store/__tests__/public-visibility-retired.test.ts +127 -0
  248. package/src/store/__tests__/store-contract.ts +6 -51
  249. package/src/store/interface.ts +3 -6
  250. package/src/store/postgres/__tests__/migrations.test.ts +187 -6
  251. package/src/store/postgres/__tests__/store-contract.test.ts +0 -2
  252. package/src/store/postgres/migrations.ts +48 -1
  253. package/src/store/postgres/store.ts +4 -11
  254. package/src/store/public-visibility-retired.ts +119 -0
  255. package/src/store/sqlite/__tests__/migrations.test.ts +191 -5
  256. package/src/store/sqlite/migrations.ts +45 -1
  257. package/src/store/sqlite/store.ts +4 -10
  258. package/dist/pipeline/steps/visibility-gates.d.ts +0 -27
  259. package/dist/pipeline/steps/visibility-gates.d.ts.map +0 -1
  260. package/dist/pipeline/steps/visibility-gates.js +0 -139
  261. package/dist/pipeline/steps/visibility-gates.js.map +0 -1
  262. package/src/authorization/__tests__/fixtures/fga/cross-org-share-creation.fga.yaml +0 -98
  263. package/src/authorization/__tests__/fixtures/fga/public-visibility-setter.fga.yaml +0 -52
  264. package/src/pipeline/steps/__tests__/visibility-gates.test.ts +0 -187
  265. package/src/pipeline/steps/visibility-gates.ts +0 -174
@@ -1,5 +1,5 @@
1
1
  # Pinned copy of stigmer-cloud backend/services/stigmer-server/fga/tests/platform-visibility.fga.yaml
2
- # at commit b87dd1170 (the OpenFGA store test the cloud runs against its live model).
2
+ # as changed by stigmer-cloud#765 (the OpenFGA store test the cloud runs against its live model).
3
3
  # The body below is byte-identical to the source; the cloud re-runs the LIVE
4
4
  # file against this evaluator at the re-pin, so a stale copy is caught there.
5
5
  # Store tests for platform visibility (visibility_platform).
@@ -102,8 +102,6 @@ tests:
102
102
  check:
103
103
  - user: identity_account:alice
104
104
  object: agent:sara
105
- context:
106
- allow: false
107
105
  assertions:
108
106
  can_view: true
109
107
  can_execute: true
@@ -112,8 +110,6 @@ tests:
112
110
  can_delete: false
113
111
  - user: identity_account:bob
114
112
  object: agent:sara
115
- context:
116
- allow: false
117
113
  assertions:
118
114
  can_view: true
119
115
  can_execute: true
@@ -122,8 +118,6 @@ tests:
122
118
  check:
123
119
  - user: identity_account:mallory
124
120
  object: agent:sara
125
- context:
126
- allow: false
127
121
  assertions:
128
122
  can_view: false
129
123
  can_execute: false
@@ -133,8 +127,6 @@ tests:
133
127
  check:
134
128
  - user: identity_account:priya
135
129
  object: agent:sara
136
- context:
137
- allow: false
138
130
  assertions:
139
131
  can_view: true
140
132
  can_execute: true
@@ -144,8 +136,6 @@ tests:
144
136
  # narrower: platform_user resolves member-from-managed_org.)
145
137
  - user: identity_account:veda
146
138
  object: agent:sara
147
- context:
148
- allow: false
149
139
  assertions:
150
140
  can_view: true
151
141
  can_edit: false
@@ -154,8 +144,6 @@ tests:
154
144
  check:
155
145
  - user: identity_account:alice
156
146
  object: agent:internal-bot
157
- context:
158
- allow: false
159
147
  assertions:
160
148
  can_view: false
161
149
  can_execute: false
@@ -163,8 +151,6 @@ tests:
163
151
  # without an explicit visibility tuple.
164
152
  - user: identity_account:priya
165
153
  object: agent:internal-bot
166
- context:
167
- allow: false
168
154
  assertions:
169
155
  can_view: false
170
156
  can_execute: false
@@ -173,8 +159,6 @@ tests:
173
159
  check:
174
160
  - user: identity_account:alice
175
161
  object: skill:research
176
- context:
177
- allow: false
178
162
  assertions:
179
163
  can_view: true
180
164
  can_use: true
@@ -184,8 +168,6 @@ tests:
184
168
  check:
185
169
  - user: identity_account:alice
186
170
  object: workflow:onboarding
187
- context:
188
- allow: false
189
171
  assertions:
190
172
  can_view: true
191
173
  can_execute: true
@@ -194,8 +176,6 @@ tests:
194
176
  check:
195
177
  - user: identity_account:alice
196
178
  object: mcp_server:github
197
- context:
198
- allow: false
199
179
  assertions:
200
180
  can_view: true
201
181
  can_use: true
@@ -205,8 +185,6 @@ tests:
205
185
  check:
206
186
  - user: identity_account:swarup
207
187
  object: agent:sara
208
- context:
209
- allow: false
210
188
  assertions:
211
189
  can_view: true
212
190
  can_edit: true
@@ -216,8 +194,6 @@ tests:
216
194
  list_objects:
217
195
  - user: identity_account:alice
218
196
  type: agent
219
- context:
220
- allow: false
221
197
  assertions:
222
198
  can_view:
223
199
  - agent:sara
@@ -1,5 +1,5 @@
1
1
  # Pinned copy of stigmer-cloud backend/services/stigmer-server/fga/tests/workflow-execution-sharing.fga.yaml
2
- # at commit b87dd1170 (the OpenFGA store test the cloud runs against its live model).
2
+ # as changed by stigmer-cloud#765 (the OpenFGA store test the cloud runs against its live model).
3
3
  # The body below is byte-identical to the source; the cloud re-runs the LIVE
4
4
  # file against this evaluator at the re-pin, so a stale copy is caught there.
5
5
  # Store tests for workflow_execution sharing and run observability.
@@ -173,8 +173,6 @@ tests:
173
173
  # never alice's run history.
174
174
  - user: identity_account:vera
175
175
  object: workflow_instance:inst1
176
- context:
177
- allow: false
178
176
  assertions:
179
177
  can_view: true
180
178
  - user: identity_account:vera
@@ -1,15 +1,15 @@
1
1
  /**
2
2
  * Pins the built-in list read scope on both store drivers as the twin of
3
3
  * the cloud's driver (stigmer-cloud iam/list-read-scope.ts): every
4
- * candidate a list lane offers is answered by `can_view` under the
5
- * LISTING context (`allow: false`, the public wildcard suppressed) through
6
- * the same evaluator the Authorizer uses, over tuples derived from the
7
- * facts the candidate carries — no candidate row is read twice.
4
+ * candidate a list lane offers is answered by `can_view` through the same
5
+ * evaluator the Authorizer uses, over tuples derived from the facts the
6
+ * candidate carries — no candidate row is read twice.
8
7
  *
9
8
  * The adversarial cells come first, because until slice 5's sibling
10
9
  * cells this file and the composed proof are the only proof that a
11
- * member's list is a member's list: the outsider sees nothing (a legacy
12
- * PUBLIC row included); the viewer rung sees the org-visible blueprints
10
+ * member's list is a member's list: the outsider sees nothing (a row
11
+ * still carrying the retired public level included); the viewer rung sees
12
+ * the org-visible blueprints
13
13
  * and nothing personal; the founder — an admin — sees the organization's
14
14
  * blueprints and NOT its members' sessions, keys, environments or
15
15
  * memories (the model's own line, now list-visible); an execution is its session's and
@@ -280,9 +280,10 @@ describe.each(driverFixtures(SEEDED_KINDS))(
280
280
  await grant(orgRole(VIEWER, "viewer", ORG));
281
281
 
282
282
  // Blueprints: the founder's private and org-visible agents, a
283
- // member's private one, a legacy PUBLIC one (nobody can set public
284
- // under the built-in posture; a row from before it can carry it),
285
- // and one stamped by the 3.14 verifiers' raw subject.
283
+ // member's private one, one still carrying the retired public
284
+ // level (a row the store migration has not met, which derives no
285
+ // viewer grant and so reads as its owner's alone), and one
286
+ // stamped by the 3.14 verifiers' raw subject.
286
287
  await save("agent", {
287
288
  id: "agt_private",
288
289
  org: ORG,
@@ -427,7 +428,7 @@ describe.each(driverFixtures(SEEDED_KINDS))(
427
428
  });
428
429
 
429
430
  describe("the adversarial cells", () => {
430
- it("an OUTSIDER lists nothing of the organization's — not even the legacy PUBLIC blueprint, which the listing context suppresses", async () => {
431
+ it("an OUTSIDER lists nothing of the organization's — a row still carrying the retired public level included", async () => {
431
432
  for (const kind of SEEDED_KINDS.filter(
432
433
  (k) =>
433
434
  k !== ApiResourceKind.iam_policy &&
@@ -441,13 +442,13 @@ describe.each(driverFixtures(SEEDED_KINDS))(
441
442
  }
442
443
  });
443
444
 
444
- it("a VIEWER lists the org-visible and the public blueprints and nothing private; a MEMBER adds their own private one", async () => {
445
+ it("a VIEWER lists the org-visible blueprint and nothing private; a MEMBER adds their own private one; the retired level grants neither anything", async () => {
445
446
  expect(
446
447
  await listAs(resolved(VIEWER), ApiResourceKind.agent),
447
- ).toEqual(["agt_org", "agt_public"]);
448
+ ).toEqual(["agt_org"]);
448
449
  expect(
449
450
  await listAs(resolved(MEMBER), ApiResourceKind.agent),
450
- ).toEqual(["agt_org", "agt_member_private", "agt_public"]);
451
+ ).toEqual(["agt_org", "agt_member_private"]);
451
452
  });
452
453
 
453
454
  it("the FOUNDER and the ADMIN list every blueprint (`owner: … or admin from organization`), including a member's private one", async () => {
@@ -542,7 +543,7 @@ describe.each(driverFixtures(SEEDED_KINDS))(
542
543
  const offered = [
543
544
  entryOf(
544
545
  ApiResourceKind.agent,
545
- rows.get(ApiResourceKind.agent)!.get("agt_public")!,
546
+ rows.get(ApiResourceKind.agent)!.get("agt_member_private")!,
546
547
  ),
547
548
  entryOf(
548
549
  ApiResourceKind.agent,
@@ -562,15 +563,15 @@ describe.each(driverFixtures(SEEDED_KINDS))(
562
563
  ApiResourceKind.agent,
563
564
  offered,
564
565
  );
565
- expect([...kept]).toEqual(["agt_public", "agt_org"]);
566
+ expect([...kept]).toEqual(["agt_member_private", "agt_org"]);
566
567
  // The lane's order is the lane's: the helper filters its own
567
568
  // array by the kept set.
568
569
  expect(
569
570
  await listAs(resolved(MEMBER), ApiResourceKind.agent, [
570
- rows.get(ApiResourceKind.agent)!.get("agt_public")!,
571
+ rows.get(ApiResourceKind.agent)!.get("agt_member_private")!,
571
572
  rows.get(ApiResourceKind.agent)!.get("agt_org")!,
572
573
  ]),
573
- ).toEqual(["agt_public", "agt_org"]);
574
+ ).toEqual(["agt_member_private", "agt_org"]);
574
575
  });
575
576
 
576
577
  it("no candidates: the empty set, with no read of any kind", async () => {
@@ -56,14 +56,12 @@ const DOCUMENTS = [
56
56
  "channel-app-owner.fga.yaml",
57
57
  "channel-participation.fga.yaml",
58
58
  "channel-session-visibility.fga.yaml",
59
- "cross-org-share-creation.fga.yaml",
60
59
  "default-instance-inheritance.fga.yaml",
61
60
  "memory-subject-only.fga.yaml",
62
61
  "org-admin-owner-inheritance.fga.yaml",
63
62
  "org-shared-environment.fga.yaml",
64
63
  "platform-visibility.fga.yaml",
65
64
  "provider-standing-viewer.fga.yaml",
66
- "public-visibility-setter.fga.yaml",
67
65
  "reserved-label-writer.fga.yaml",
68
66
  "schedule-owner.fga.yaml",
69
67
  "schedule-session-visibility.fga.yaml",
@@ -100,7 +98,7 @@ const SKIPPED_PINNED: ReadonlyArray<string> = [
100
98
  "artifact-org-and-owner :: listing reflects org inheritance plus creator ownership :: identity_account:eve <artifact> :: list-objects",
101
99
  "artifact-org-and-owner :: listing reflects org inheritance plus creator ownership :: identity_account:victor <artifact> :: list-objects",
102
100
  "blueprint-private-visibility :: org admin's listable agents include private ones (T08) :: identity_account:oscar <agent> :: list-objects",
103
- "blueprint-private-visibility :: org member's listable agents exclude private ones (wildcard suppressed) :: identity_account:dave <agent> :: list-objects",
101
+ "blueprint-private-visibility :: org member's listable agents exclude private ones :: identity_account:dave <agent> :: list-objects",
104
102
  "blueprint-private-visibility :: org viewer's listable agents are exactly the viewer-shaped org-visible ones :: identity_account:vera <agent> :: list-objects",
105
103
  "blueprint-private-visibility :: owner's listable agents include everything they authored :: identity_account:carol <agent> :: list-objects",
106
104
  "channel-app-owner :: listing channel apps reflects ownership and grants, not membership :: identity_account:alice <channel_app> :: list-objects",
@@ -130,9 +128,6 @@ const SKIPPED_PINNED: ReadonlyArray<string> = [
130
128
  // undeclared-kind: platform
131
129
  "provider-standing-viewer :: operator may view provider standing, tenants may not :: identity_account:mallory platform:stigmer :: undeclared-kind",
132
130
  "provider-standing-viewer :: operator may view provider standing, tenants may not :: identity_account:opal platform:stigmer :: undeclared-kind",
133
- "public-visibility-setter :: operators may set public visibility, tenants may not :: identity_account:machine platform:stigmer :: undeclared-kind",
134
- "public-visibility-setter :: operators may set public visibility, tenants may not :: identity_account:mallory platform:stigmer :: undeclared-kind",
135
- "public-visibility-setter :: operators may set public visibility, tenants may not :: identity_account:opal platform:stigmer :: undeclared-kind",
136
131
  "reserved-label-writer :: operator may write reserved labels, tenants may not :: identity_account:machine platform:stigmer :: undeclared-kind",
137
132
  "reserved-label-writer :: operator may write reserved labels, tenants may not :: identity_account:mallory platform:stigmer :: undeclared-kind",
138
133
  // undeclared-subject-type: identity_provider#platform_user
@@ -13,7 +13,7 @@ import {
13
13
  } from "../tuples.js";
14
14
 
15
15
  describe("the string notation", () => {
16
- it("parses an object, a userset and the wildcard, and an id may carry a colon (issuer subjects do)", () => {
16
+ it("parses an object and a userset, and an id may carry a colon (issuer subjects do)", () => {
17
17
  expect(parseObjectRef("agent:agt_1")).toEqual({
18
18
  type: "agent",
19
19
  id: "agt_1",
@@ -27,11 +27,6 @@ describe("the string notation", () => {
27
27
  object: { type: "organization", id: "acme" },
28
28
  relation: "viewer",
29
29
  });
30
- expect(parseSubject("identity_account:*")).toEqual({
31
- form: "wildcard",
32
- type: "identity_account",
33
- condition: undefined,
34
- });
35
30
  expect(parseSubject("identity_account:ida_x")).toEqual({
36
31
  form: "object",
37
32
  object: { type: "identity_account", id: "ida_x" },
@@ -43,18 +38,24 @@ describe("the string notation", () => {
43
38
  expect(() => parseObjectRef(":id")).toThrow("not an object reference");
44
39
  });
45
40
 
46
- it("formats a tuple in the proto's notation, the condition spelled after `with`", () => {
41
+ it("refuses the wildcard notation — no line of the model admits one", () => {
42
+ expect(() => parseSubject("identity_account:*")).toThrow(
43
+ "wildcard subject 'identity_account:*' is not admitted",
44
+ );
45
+ });
46
+
47
+ it("formats a tuple in the proto's notation", () => {
47
48
  expect(
48
49
  formatTuple({
49
50
  object: parseObjectRef("agent:a"),
50
51
  relation: "viewer",
51
52
  subject: {
52
- form: "wildcard",
53
- type: "identity_account",
54
- condition: "allow_public",
53
+ form: "userset",
54
+ object: { type: "organization", id: "acme" },
55
+ relation: "viewer",
55
56
  },
56
57
  }),
57
- ).toBe("agent:a#viewer@identity_account:* with allow_public");
58
+ ).toBe("agent:a#viewer@organization:acme#viewer");
58
59
  expect(formatSubject(parseSubject("organization:acme#member"))).toBe(
59
60
  "organization:acme#member",
60
61
  );
@@ -17,9 +17,7 @@
17
17
  * 2. A kind this edition does not serve is `deny` (`kindServedByEdition`,
18
18
  * the predicate 2b's `checkMyPermission` arm 1 uses, so the console
19
19
  * and the server agree by construction). `platform` is the one that
20
- * arrives, through `can_set_public_visibility` — under the built-in
21
- * posture nobody sets public visibility, the model's own reason for
22
- * making it a platform operator's act.
20
+ * can arrive, through an operator capability no self-host holds.
23
21
  * 3. The caller as a person (person.ts) and one derived tuple source for
24
22
  * them; the target loaded ONCE through the source's memoised loader,
25
23
  * so the read that decides not-found is the read the derivation
@@ -31,11 +29,9 @@
31
29
  * member cannot learn which account ids exist. Present or exempt, the
32
30
  * row is then evaluated: the cloud asks OpenFGA whether or not the row
33
31
  * exists, and so does this driver.
34
- * 5. `checkRelation` with the point-check context (`allow: true`: a
35
- * public resource reads for any authenticated caller, the cloud's
36
- * posture). True is `allow`; false is `deny` with an EMPTY reason, so
37
- * the method annotation's byte-pinned `error_msg` wins on the wire,
38
- * exactly the cloud's genuine-denial path.
32
+ * 5. `checkRelation`. True is `allow`; false is `deny` with an EMPTY
33
+ * reason, so the method annotation's byte-pinned `error_msg` wins on
34
+ * the wire, exactly the cloud's genuine-denial path.
39
35
  *
40
36
  * Faults. Everything past the pre-checks runs under one guard: a store
41
37
  * fault, a person that names nobody (`personFor`), an evaluation fault
@@ -179,7 +175,6 @@ export function newBuiltInAuthorizer(deps: BuiltInAuthorizerDeps): Authorizer {
179
175
  object,
180
176
  relation,
181
177
  person,
182
- { allow: true },
183
178
  );
184
179
  if (allowed) {
185
180
  return { kind: "allow" };
@@ -12,7 +12,6 @@
12
12
  * - owner SELF: <row>#owner@identity_account:<row id>
13
13
  * - creator: <row>#creator@identity_account:<creator stamp> (kinds flagged requires_creator_tuple)
14
14
  * - org-viewer: <row>#viewer@organization:<org>#viewer (visibility_org; cloud#257's shape)
15
- * - public-viewer: <row>#viewer@identity_account:* with allow_public
16
15
  * - platform-viewer: none — it fans out over identity providers, a kind this edition does not serve
17
16
  *
18
17
  * Two facts are this edition's own and are stated here, nowhere else:
@@ -91,11 +90,7 @@ import type {
91
90
  Tuple,
92
91
  TupleSource,
93
92
  } from "./tuples.js";
94
- import {
95
- ACCOUNT_TYPE,
96
- ALLOW_PUBLIC_CONDITION,
97
- formatObjectRef,
98
- } from "./tuples.js";
93
+ import { ACCOUNT_TYPE, formatObjectRef } from "./tuples.js";
99
94
 
100
95
  /**
101
96
  * Kinds whose `[identity_account]` owner is recorded as an IamPolicy row
@@ -184,12 +179,6 @@ function visibilitySubject(
184
179
  object: { type: "organization", id: org },
185
180
  relation: "viewer",
186
181
  };
187
- case "public-viewer":
188
- return {
189
- form: "wildcard",
190
- type: ACCOUNT_TYPE,
191
- condition: ALLOW_PUBLIC_CONDITION,
192
- };
193
182
  case "platform-viewer":
194
183
  return undefined;
195
184
  default: {
@@ -361,24 +350,18 @@ function tupleOfRow(row: IamPolicy): Tuple {
361
350
  type: principal?.kind ?? "",
362
351
  id: principal?.id ?? "",
363
352
  };
364
- let subject: Subject;
365
- if (principal !== undefined && principal.relation !== "") {
366
- subject = {
367
- form: "userset",
368
- object: principalObject,
369
- relation: principal.relation,
370
- };
371
- } else if (principalObject.id === "*") {
372
- // A wildcard row carries no condition of its own; the evaluator
373
- // honours it only where a line admits an unconditioned wildcard,
374
- // which no line in the model does.
375
- subject = {
376
- form: "wildcard",
377
- type: principalObject.type,
378
- condition: undefined,
379
- };
380
- } else {
381
- subject = { form: "object", object: principalObject };
382
- }
353
+ // A principal id of `*` (a wildcard row a cloud database may still hold
354
+ // from the retired public level) is read as an ordinary object id here:
355
+ // no person carries `*` among their aliases, so the row matches nobody
356
+ // and reads as the inert grant it is, never as a fault that would turn
357
+ // the resource unreadable.
358
+ const subject: Subject =
359
+ principal !== undefined && principal.relation !== ""
360
+ ? {
361
+ form: "userset",
362
+ object: principalObject,
363
+ relation: principal.relation,
364
+ }
365
+ : { form: "object", object: principalObject };
383
366
  return { object, relation: spec?.relation ?? "", subject };
384
367
  }
@@ -1,10 +1,11 @@
1
1
  /**
2
2
  * The evaluator — OpenFGA's check, over the built-in model and a tuple
3
3
  * source, small because the model uses four rewrite forms and nothing
4
- * else (model/rewrite.ts). `checkRelation(object, relation, person, context)`
4
+ * else (model/rewrite.ts). `checkRelation(object, relation, person)`
5
5
  * answers "does this person hold this relation on this object" the way
6
6
  * the cloud's engine answers it over stored tuples, so the two editions
7
- * share one meaning of authorization.
7
+ * share one meaning of authorization. There is no context parameter: the
8
+ * model declares no condition, so a check has no posture to state.
8
9
  *
9
10
  * The walk, in the order a `.fga` line reads:
10
11
  * - `this`: the tuples on (object, relation). A tuple counts only if
@@ -12,9 +13,7 @@
12
13
  * OpenFGA makes at write time, made here at read time, so a derived
13
14
  * tuple the model no longer admits denies instead of allowing. An
14
15
  * object subject matches when it names one of the person's aliases;
15
- * a userset subject is resolved on its own object; the wildcard is
16
- * honoured only under its condition (`allow_public` reads the
17
- * context's `allow`; an unknown condition is a fault).
16
+ * a userset subject is resolved on its own object.
18
17
  * - `computed`: the same object, another relation.
19
18
  * - `from`: every object the tupleset relation links to (again within
20
19
  * the tupleset line's type restriction), resolved for the relation.
@@ -44,19 +43,8 @@
44
43
  */
45
44
  import type { Model } from "./model/index.js";
46
45
  import type { Rewrite, SubjectType } from "./model/rewrite.js";
47
- import type {
48
- CheckContext,
49
- ObjectRef,
50
- Person,
51
- Subject,
52
- TupleSource,
53
- } from "./tuples.js";
54
- import {
55
- ACCOUNT_TYPE,
56
- ALLOW_PUBLIC_CONDITION,
57
- formatObjectRef,
58
- pairKey,
59
- } from "./tuples.js";
46
+ import type { ObjectRef, Person, Subject, TupleSource } from "./tuples.js";
47
+ import { ACCOUNT_TYPE, formatObjectRef, pairKey } from "./tuples.js";
60
48
 
61
49
  /**
62
50
  * OpenFGA's default resolution-depth limit (its `resolveNodeLimit`), so
@@ -77,8 +65,7 @@ export type EvaluationFault =
77
65
  | "undeclared-target-kind"
78
66
  | "internal-caller-offered"
79
67
  | "resolution-depth-exceeded"
80
- | "resolution-cycle"
81
- | "unknown-condition";
68
+ | "resolution-cycle";
82
69
 
83
70
  /** A fault in the model or its evaluation — never a denial; the driver maps it to `unavailable`. */
84
71
  export class AuthorizationEvaluationError extends Error {
@@ -101,7 +88,6 @@ export async function checkRelation(
101
88
  object: ObjectRef,
102
89
  relation: string,
103
90
  person: Person,
104
- context: CheckContext,
105
91
  ): Promise<boolean> {
106
92
  if (deps.model.byType(object.type) === undefined) {
107
93
  throw new AuthorizationEvaluationError(
@@ -109,7 +95,7 @@ export async function checkRelation(
109
95
  `no declaration for kind '${object.type}' (target ${formatObjectRef(object)})`,
110
96
  );
111
97
  }
112
- return new Walk(deps, person, context).resolve(object, relation, 0);
98
+ return new Walk(deps, person).resolve(object, relation, 0);
113
99
  }
114
100
 
115
101
  /** One check's state: the memo and the in-progress set, over one source. */
@@ -120,7 +106,6 @@ class Walk {
120
106
  constructor(
121
107
  private readonly deps: EvaluationDeps,
122
108
  private readonly person: Person,
123
- private readonly context: CheckContext,
124
109
  ) {}
125
110
 
126
111
  resolve(
@@ -234,15 +219,6 @@ class Walk {
234
219
  allowed.relation === subject.relation,
235
220
  ) && (await this.resolve(subject.object, subject.relation, depth + 1))
236
221
  );
237
- case "wildcard": {
238
- const allowed = restriction.some(
239
- (entry) =>
240
- entry.form === "wildcard" &&
241
- entry.type === subject.type &&
242
- entry.condition === subject.condition,
243
- );
244
- return allowed && this.conditionHolds(subject.condition);
245
- }
246
222
  default: {
247
223
  const exhaustive: never = subject;
248
224
  throw new Error(`unknown subject form: ${JSON.stringify(exhaustive)}`);
@@ -250,18 +226,6 @@ class Walk {
250
226
  }
251
227
  }
252
228
 
253
- private conditionHolds(condition: string | undefined): boolean {
254
- switch (condition) {
255
- case ALLOW_PUBLIC_CONDITION:
256
- return this.context.allow;
257
- default:
258
- throw new AuthorizationEvaluationError(
259
- "unknown-condition",
260
- `the model declares no condition '${String(condition)}'`,
261
- );
262
- }
263
- }
264
-
265
229
  /** The object types the tupleset line admits — `[organization]`, `[session]` — from its `this` nodes. */
266
230
  private objectTypesOf(
267
231
  object: ObjectRef,
@@ -11,12 +11,10 @@
11
11
  * row a second time. A self-host's lists show a person exactly what a
12
12
  * `get` would let them read.
13
13
  *
14
- * Both verbs ask under the LISTING context, `allow: false` — the public
15
- * wildcard suppressed, the cloud's own posture (its Java
16
- * listAuthorizedResourceIds set it): a legacy PUBLIC row reads for any
17
- * signed-in person on a point check and never expands into an outsider's
18
- * listing; the organization's own people still reach it through the
19
- * org-viewer shape public also emits.
14
+ * Both verbs ask the same question a point check asks, `can_view` on the
15
+ * candidate: the model has no subject whose answer differs between a
16
+ * listing and a get, so a list is exactly the set of rows a get would
17
+ * read.
20
18
  *
21
19
  * - `restrictListEntries`: one derived source for the caller, seeded
22
20
  * with every candidate's facts; one `checkRelation` per unique id —
@@ -84,14 +82,11 @@ import { builtInModel } from "./model/index.js";
84
82
  import type { KindDeclaration } from "./model/rewrite.js";
85
83
  import type { AccountsByCaller } from "./person.js";
86
84
  import { resolvePerson } from "./person.js";
87
- import type { CheckContext, ObjectRef, Person } from "./tuples.js";
85
+ import type { ObjectRef, Person } from "./tuples.js";
88
86
 
89
87
  /** The relation every list lane reads through (the cloud's LIST_RELATION). */
90
88
  const LIST_RELATION = "can_view";
91
89
 
92
- /** The listing context: the public wildcard suppressed (the module header). */
93
- const LISTING: CheckContext = { allow: false };
94
-
95
90
  export interface BuiltInListReadScopeDeps {
96
91
  readonly store: Store;
97
92
  /** The port the grant path writes through — the person's rows are read from the same instance. */
@@ -138,7 +133,6 @@ export function newBuiltInListReadScope(
138
133
  object,
139
134
  LIST_RELATION,
140
135
  person,
141
- LISTING,
142
136
  );
143
137
  return allowed ? facts.id : undefined;
144
138
  }),
@@ -4,9 +4,8 @@
4
4
  * read by whoever the visibility axis lets in. The `organization#member`
5
5
  * and `organization#viewer` usersets on `viewer` are the org-visibility
6
6
  * tuple's two shapes (the cloud writes `#viewer` since cloud#257; `#member`
7
- * is the legacy shape it still honours), the conditional wildcard is the
8
- * public level, and `platform_viewer` is an identity-provider userset no
9
- * open-source tuple ever names.
7
+ * is the legacy shape it still honours), and `platform_viewer` is an
8
+ * identity-provider userset no open-source tuple ever names.
10
9
  */
11
10
  import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
12
11
  import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
@@ -17,7 +16,6 @@ import {
17
16
  direct,
18
17
  from,
19
18
  objectOf,
20
- publicWith,
21
19
  union,
22
20
  usersetOf,
23
21
  } from "./rewrite.js";
@@ -44,7 +42,6 @@ export const agentDeclaration = declareKind({
44
42
  union(
45
43
  direct(
46
44
  objectOf("identity_account"),
47
- publicWith("identity_account", "allow_public"),
48
45
  usersetOf("organization", "member"),
49
46
  usersetOf("organization", "viewer"),
50
47
  ),
@@ -19,7 +19,6 @@ import {
19
19
  direct,
20
20
  from,
21
21
  objectOf,
22
- publicWith,
23
22
  union,
24
23
  usersetOf,
25
24
  } from "./rewrite.js";
@@ -38,7 +37,6 @@ export const agentInstanceDeclaration = declareKind({
38
37
  union(
39
38
  direct(
40
39
  objectOf("identity_account"),
41
- publicWith("identity_account", "allow_public"),
42
40
  usersetOf("organization", "member"),
43
41
  usersetOf("organization", "viewer"),
44
42
  ),
@@ -12,7 +12,6 @@ import {
12
12
  direct,
13
13
  from,
14
14
  objectOf,
15
- publicWith,
16
15
  union,
17
16
  usersetOf,
18
17
  } from "./rewrite.js";
@@ -39,7 +38,6 @@ export const mcpServerDeclaration = declareKind({
39
38
  union(
40
39
  direct(
41
40
  objectOf("identity_account"),
42
- publicWith("identity_account", "allow_public"),
43
41
  usersetOf("organization", "member"),
44
42
  usersetOf("organization", "viewer"),
45
43
  ),
@@ -2,8 +2,7 @@
2
2
  * Transcript of fga/model/agentic/plugin.fga — the unit of install, shaped
3
3
  * exactly like a skill: an organization-owned blueprint whose owner is the
4
4
  * installer (and, through the organization, its admins), whose viewers are
5
- * the org's members and viewers, public readers when published, and the
6
- * platform viewer. Its verbs are the blueprint set: `can_view`, `can_edit`
5
+ * the org's members and viewers and the platform viewer. Its verbs are the blueprint set: `can_view`, `can_edit`
7
6
  * (visibility), `can_delete` (uninstall), `can_grant_access`,
8
7
  * `can_view_access`; `can_use` is carried as the model's word for
9
8
  * "install from" though no wire permission names it yet.
@@ -21,7 +20,6 @@ import {
21
20
  direct,
22
21
  from,
23
22
  objectOf,
24
- publicWith,
25
23
  union,
26
24
  usersetOf,
27
25
  } from "./rewrite.js";
@@ -48,7 +46,6 @@ export const pluginDeclaration = declareKind({
48
46
  union(
49
47
  direct(
50
48
  objectOf("identity_account"),
51
- publicWith("identity_account", "allow_public"),
52
49
  usersetOf("organization", "member"),
53
50
  usersetOf("organization", "viewer"),
54
51
  ),