@fuzdev/fuz_app 0.140.0 → 0.142.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 (311) hide show
  1. package/dist/actions/CLAUDE.md +72 -22
  2. package/dist/actions/action_rpc.d.ts +5 -1
  3. package/dist/actions/action_rpc.d.ts.map +1 -1
  4. package/dist/actions/action_rpc.js +9 -1
  5. package/dist/actions/connection_closer.d.ts +44 -4
  6. package/dist/actions/connection_closer.d.ts.map +1 -1
  7. package/dist/actions/connection_closer.js +33 -3
  8. package/dist/actions/perform_action.d.ts +10 -0
  9. package/dist/actions/perform_action.d.ts.map +1 -1
  10. package/dist/actions/perform_action.js +23 -9
  11. package/dist/actions/register_action_ws.d.ts +42 -6
  12. package/dist/actions/register_action_ws.d.ts.map +1 -1
  13. package/dist/actions/register_action_ws.js +87 -19
  14. package/dist/actions/register_ws_endpoint.d.ts +15 -6
  15. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  16. package/dist/actions/register_ws_endpoint.js +10 -4
  17. package/dist/actions/socket.svelte.d.ts +19 -4
  18. package/dist/actions/socket.svelte.d.ts.map +1 -1
  19. package/dist/actions/socket.svelte.js +31 -4
  20. package/dist/actions/transports.d.ts +30 -1
  21. package/dist/actions/transports.d.ts.map +1 -1
  22. package/dist/actions/transports.js +30 -1
  23. package/dist/actions/transports_ws_auth_guard.d.ts +13 -6
  24. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  25. package/dist/actions/transports_ws_auth_guard.js +24 -10
  26. package/dist/actions/transports_ws_backend.d.ts +51 -5
  27. package/dist/actions/transports_ws_backend.d.ts.map +1 -1
  28. package/dist/actions/transports_ws_backend.js +62 -9
  29. package/dist/actions/ws_endpoint_spec.d.ts +11 -10
  30. package/dist/actions/ws_endpoint_spec.d.ts.map +1 -1
  31. package/dist/auth/CLAUDE.md +44 -22
  32. package/dist/auth/account_actions.d.ts.map +1 -1
  33. package/dist/auth/account_actions.js +12 -1
  34. package/dist/auth/account_route_schema.d.ts +1 -0
  35. package/dist/auth/account_route_schema.d.ts.map +1 -1
  36. package/dist/auth/account_route_schema.js +12 -0
  37. package/dist/auth/account_routes.d.ts +7 -2
  38. package/dist/auth/account_routes.d.ts.map +1 -1
  39. package/dist/auth/account_routes.js +240 -187
  40. package/dist/auth/admin_action_specs.d.ts +6 -1
  41. package/dist/auth/admin_action_specs.d.ts.map +1 -1
  42. package/dist/auth/admin_action_specs.js +6 -1
  43. package/dist/auth/admin_actions.d.ts.map +1 -1
  44. package/dist/auth/admin_actions.js +14 -2
  45. package/dist/auth/api_token.d.ts +20 -0
  46. package/dist/auth/api_token.d.ts.map +1 -1
  47. package/dist/auth/api_token.js +21 -1
  48. package/dist/auth/api_token_queries.d.ts +22 -4
  49. package/dist/auth/api_token_queries.d.ts.map +1 -1
  50. package/dist/auth/api_token_queries.js +11 -6
  51. package/dist/auth/audit_log_routes.d.ts +10 -0
  52. package/dist/auth/audit_log_routes.d.ts.map +1 -1
  53. package/dist/auth/audit_log_routes.js +20 -0
  54. package/dist/auth/audit_log_schema.d.ts +30 -5
  55. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  56. package/dist/auth/audit_log_schema.js +30 -0
  57. package/dist/auth/bearer_auth.d.ts +4 -2
  58. package/dist/auth/bearer_auth.d.ts.map +1 -1
  59. package/dist/auth/bearer_auth.js +19 -8
  60. package/dist/auth/bootstrap_route_schema.d.ts +1 -0
  61. package/dist/auth/bootstrap_route_schema.d.ts.map +1 -1
  62. package/dist/auth/bootstrap_route_schema.js +1 -0
  63. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  64. package/dist/auth/bootstrap_routes.js +85 -76
  65. package/dist/auth/cell_action_specs.d.ts +7 -3
  66. package/dist/auth/cell_action_specs.d.ts.map +1 -1
  67. package/dist/auth/cell_action_specs.js +9 -5
  68. package/dist/auth/cell_actions.d.ts +3 -3
  69. package/dist/auth/cell_actions.d.ts.map +1 -1
  70. package/dist/auth/cell_actions.js +67 -38
  71. package/dist/auth/cell_authorize.d.ts +69 -8
  72. package/dist/auth/cell_authorize.d.ts.map +1 -1
  73. package/dist/auth/cell_authorize.js +89 -9
  74. package/dist/auth/cell_field_action_specs.d.ts +5 -4
  75. package/dist/auth/cell_field_action_specs.d.ts.map +1 -1
  76. package/dist/auth/cell_field_action_specs.js +7 -7
  77. package/dist/auth/cell_field_actions.d.ts +7 -5
  78. package/dist/auth/cell_field_actions.d.ts.map +1 -1
  79. package/dist/auth/cell_field_actions.js +24 -22
  80. package/dist/auth/cell_grant_action_specs.d.ts +6 -0
  81. package/dist/auth/cell_grant_action_specs.d.ts.map +1 -1
  82. package/dist/auth/cell_grant_action_specs.js +6 -0
  83. package/dist/auth/cell_grant_actions.d.ts +3 -1
  84. package/dist/auth/cell_grant_actions.d.ts.map +1 -1
  85. package/dist/auth/cell_grant_actions.js +16 -2
  86. package/dist/auth/cell_item_action_specs.d.ts +5 -4
  87. package/dist/auth/cell_item_action_specs.d.ts.map +1 -1
  88. package/dist/auth/cell_item_action_specs.js +7 -7
  89. package/dist/auth/cell_item_actions.d.ts +6 -5
  90. package/dist/auth/cell_item_actions.d.ts.map +1 -1
  91. package/dist/auth/cell_item_actions.js +23 -22
  92. package/dist/auth/cell_relation_list_limit.d.ts +20 -0
  93. package/dist/auth/cell_relation_list_limit.d.ts.map +1 -0
  94. package/dist/auth/cell_relation_list_limit.js +27 -0
  95. package/dist/auth/cell_relation_visibility.d.ts +4 -3
  96. package/dist/auth/cell_relation_visibility.d.ts.map +1 -1
  97. package/dist/auth/cell_relation_visibility.js +6 -5
  98. package/dist/auth/cell_steward.d.ts +45 -0
  99. package/dist/auth/cell_steward.d.ts.map +1 -0
  100. package/dist/auth/cell_steward.js +53 -0
  101. package/dist/auth/cleanup.d.ts +9 -9
  102. package/dist/auth/cleanup.js +9 -9
  103. package/dist/auth/credential_deadline.d.ts +95 -0
  104. package/dist/auth/credential_deadline.d.ts.map +1 -0
  105. package/dist/auth/credential_deadline.js +139 -0
  106. package/dist/auth/daemon_token_middleware.d.ts.map +1 -1
  107. package/dist/auth/daemon_token_middleware.js +4 -1
  108. package/dist/auth/middleware.d.ts +90 -12
  109. package/dist/auth/middleware.d.ts.map +1 -1
  110. package/dist/auth/middleware.js +115 -16
  111. package/dist/auth/request_context.d.ts +8 -2
  112. package/dist/auth/request_context.d.ts.map +1 -1
  113. package/dist/auth/request_context.js +20 -4
  114. package/dist/auth/resolved_auth.d.ts +13 -2
  115. package/dist/auth/resolved_auth.d.ts.map +1 -1
  116. package/dist/auth/resolved_auth.js +5 -4
  117. package/dist/auth/role_grant_offer_action_specs.d.ts +5 -0
  118. package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
  119. package/dist/auth/role_grant_offer_action_specs.js +6 -1
  120. package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
  121. package/dist/auth/role_grant_offer_actions.js +23 -16
  122. package/dist/auth/role_grant_offer_queries.d.ts +8 -6
  123. package/dist/auth/role_grant_offer_queries.d.ts.map +1 -1
  124. package/dist/auth/role_grant_offer_queries.js +16 -8
  125. package/dist/auth/session_cookie.d.ts +4 -0
  126. package/dist/auth/session_cookie.d.ts.map +1 -1
  127. package/dist/auth/session_cookie.js +3 -2
  128. package/dist/auth/session_middleware.d.ts +1 -1
  129. package/dist/auth/session_middleware.d.ts.map +1 -1
  130. package/dist/auth/session_middleware.js +4 -1
  131. package/dist/auth/session_queries.d.ts +19 -2
  132. package/dist/auth/session_queries.d.ts.map +1 -1
  133. package/dist/auth/session_queries.js +8 -2
  134. package/dist/auth/signup_route_schema.d.ts.map +1 -1
  135. package/dist/auth/signup_route_schema.js +1 -0
  136. package/dist/auth/signup_routes.d.ts +4 -3
  137. package/dist/auth/signup_routes.d.ts.map +1 -1
  138. package/dist/auth/signup_routes.js +157 -134
  139. package/dist/auth/token_scope.d.ts +7 -1
  140. package/dist/auth/token_scope.d.ts.map +1 -1
  141. package/dist/auth/token_scope.js +8 -2
  142. package/dist/db/CLAUDE.md +32 -19
  143. package/dist/db/cell_ddl.d.ts +1 -2
  144. package/dist/db/cell_ddl.d.ts.map +1 -1
  145. package/dist/db/cell_ddl.js +1 -2
  146. package/dist/db/cell_field_queries.d.ts +20 -18
  147. package/dist/db/cell_field_queries.d.ts.map +1 -1
  148. package/dist/db/cell_field_queries.js +28 -22
  149. package/dist/db/cell_item_queries.d.ts +20 -14
  150. package/dist/db/cell_item_queries.d.ts.map +1 -1
  151. package/dist/db/cell_item_queries.js +28 -18
  152. package/dist/db/cell_moderation.d.ts +67 -0
  153. package/dist/db/cell_moderation.d.ts.map +1 -0
  154. package/dist/db/cell_moderation.js +66 -0
  155. package/dist/db/cell_queries.d.ts +44 -14
  156. package/dist/db/cell_queries.d.ts.map +1 -1
  157. package/dist/db/cell_queries.js +68 -45
  158. package/dist/db/cell_view_sql.d.ts +102 -0
  159. package/dist/db/cell_view_sql.d.ts.map +1 -0
  160. package/dist/db/cell_view_sql.js +112 -0
  161. package/dist/db/db_pg.d.ts +1 -1
  162. package/dist/db/db_pg.js +1 -1
  163. package/dist/hono_context.d.ts +23 -0
  164. package/dist/hono_context.d.ts.map +1 -1
  165. package/dist/hono_context.js +19 -0
  166. package/dist/http/CLAUDE.md +41 -15
  167. package/dist/http/auth_shape.d.ts +5 -2
  168. package/dist/http/auth_shape.d.ts.map +1 -1
  169. package/dist/http/auth_shape.js +5 -2
  170. package/dist/http/ip_canonical.d.ts +30 -3
  171. package/dist/http/ip_canonical.d.ts.map +1 -1
  172. package/dist/http/ip_canonical.js +50 -3
  173. package/dist/http/middleware_spec.d.ts +16 -0
  174. package/dist/http/middleware_spec.d.ts.map +1 -1
  175. package/dist/http/origin.d.ts +113 -18
  176. package/dist/http/origin.d.ts.map +1 -1
  177. package/dist/http/origin.js +247 -127
  178. package/dist/http/route_spec.d.ts +21 -0
  179. package/dist/http/route_spec.d.ts.map +1 -1
  180. package/dist/http/schema_helpers.d.ts +22 -0
  181. package/dist/http/schema_helpers.d.ts.map +1 -1
  182. package/dist/http/schema_helpers.js +24 -0
  183. package/dist/http/surface.d.ts +6 -1
  184. package/dist/http/surface.d.ts.map +1 -1
  185. package/dist/http/surface.js +12 -5
  186. package/dist/primitive_schemas.d.ts +10 -1
  187. package/dist/primitive_schemas.d.ts.map +1 -1
  188. package/dist/primitive_schemas.js +11 -2
  189. package/dist/rate_limiter.d.ts +179 -12
  190. package/dist/rate_limiter.d.ts.map +1 -1
  191. package/dist/rate_limiter.js +352 -42
  192. package/dist/realtime/sse_auth_guard.d.ts +22 -6
  193. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  194. package/dist/realtime/sse_auth_guard.js +31 -12
  195. package/dist/realtime/subscriber_registry.d.ts +10 -1
  196. package/dist/realtime/subscriber_registry.d.ts.map +1 -1
  197. package/dist/realtime/subscriber_registry.js +10 -1
  198. package/dist/server/app_server.d.ts +20 -7
  199. package/dist/server/app_server.d.ts.map +1 -1
  200. package/dist/server/app_server.js +8 -3
  201. package/dist/server/env.d.ts +2 -1
  202. package/dist/server/env.d.ts.map +1 -1
  203. package/dist/server/env.js +7 -17
  204. package/dist/server/serve_fact_route.d.ts +8 -4
  205. package/dist/server/serve_fact_route.d.ts.map +1 -1
  206. package/dist/server/serve_fact_route.js +16 -6
  207. package/dist/testing/CLAUDE.md +164 -25
  208. package/dist/testing/adversarial_headers.d.ts.map +1 -1
  209. package/dist/testing/adversarial_headers.js +13 -6
  210. package/dist/testing/connection_closer_helpers.d.ts +6 -1
  211. package/dist/testing/connection_closer_helpers.d.ts.map +1 -1
  212. package/dist/testing/connection_closer_helpers.js +4 -0
  213. package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -1
  214. package/dist/testing/cross_backend/account_lifecycle.js +37 -2
  215. package/dist/testing/cross_backend/body_size.d.ts.map +1 -1
  216. package/dist/testing/cross_backend/body_size.js +14 -65
  217. package/dist/testing/cross_backend/capabilities.d.ts +26 -2
  218. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  219. package/dist/testing/cross_backend/capabilities.js +9 -3
  220. package/dist/testing/cross_backend/cell_gated_create.d.ts +38 -0
  221. package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
  222. package/dist/testing/cross_backend/cell_gated_create.js +401 -5
  223. package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -1
  224. package/dist/testing/cross_backend/cell_relations.js +145 -2
  225. package/dist/testing/cross_backend/conformance_case.d.ts +11 -3
  226. package/dist/testing/cross_backend/conformance_case.d.ts.map +1 -1
  227. package/dist/testing/cross_backend/conformance_case.js +6 -0
  228. package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
  229. package/dist/testing/cross_backend/conformance_table.js +20 -19
  230. package/dist/testing/cross_backend/credential_deadline.d.ts +25 -0
  231. package/dist/testing/cross_backend/credential_deadline.d.ts.map +1 -0
  232. package/dist/testing/cross_backend/credential_deadline.js +185 -0
  233. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
  234. package/dist/testing/cross_backend/default_backend_configs.js +13 -2
  235. package/dist/testing/cross_backend/expected_schema.json +0 -1
  236. package/dist/testing/cross_backend/full_spine_mount.d.ts +3 -0
  237. package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
  238. package/dist/testing/cross_backend/full_spine_mount.js +7 -1
  239. package/dist/testing/cross_backend/login_security.d.ts +16 -0
  240. package/dist/testing/cross_backend/login_security.d.ts.map +1 -1
  241. package/dist/testing/cross_backend/login_security.js +154 -0
  242. package/dist/testing/cross_backend/narrowed_api_token.d.ts +23 -0
  243. package/dist/testing/cross_backend/narrowed_api_token.d.ts.map +1 -0
  244. package/dist/testing/cross_backend/narrowed_api_token.js +40 -0
  245. package/dist/testing/cross_backend/origin.d.ts.map +1 -1
  246. package/dist/testing/cross_backend/origin.js +6 -26
  247. package/dist/testing/cross_backend/origin_conformance.d.ts +152 -0
  248. package/dist/testing/cross_backend/origin_conformance.d.ts.map +1 -0
  249. package/dist/testing/cross_backend/origin_conformance.js +136 -0
  250. package/dist/testing/cross_backend/origin_conformance.json +1679 -0
  251. package/dist/testing/cross_backend/origin_scope.d.ts +16 -0
  252. package/dist/testing/cross_backend/origin_scope.d.ts.map +1 -0
  253. package/dist/testing/cross_backend/origin_scope.js +140 -0
  254. package/dist/testing/cross_backend/query_shape.d.ts.map +1 -1
  255. package/dist/testing/cross_backend/query_shape.js +24 -2
  256. package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts +35 -0
  257. package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts.map +1 -0
  258. package/dist/testing/cross_backend/role_grant_cross_helpers.js +64 -0
  259. package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts +8 -0
  260. package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts.map +1 -1
  261. package/dist/testing/cross_backend/role_grant_offer_notification_ws.js +44 -33
  262. package/dist/testing/cross_backend/role_grant_participation.d.ts.map +1 -1
  263. package/dist/testing/cross_backend/role_grant_participation.js +64 -0
  264. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
  265. package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +6 -4
  266. package/dist/testing/cross_backend/spine_surface_constants.d.ts +20 -1
  267. package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
  268. package/dist/testing/cross_backend/spine_surface_constants.js +23 -1
  269. package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
  270. package/dist/testing/cross_backend/testing_backdoor.js +20 -7
  271. package/dist/testing/cross_backend/testing_reset_actions.d.ts +180 -5
  272. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  273. package/dist/testing/cross_backend/testing_reset_actions.js +154 -5
  274. package/dist/testing/cross_backend/token_scope_surface.d.ts.map +1 -1
  275. package/dist/testing/cross_backend/token_scope_surface.js +25 -21
  276. package/dist/testing/cross_backend/transport_composition.d.ts +60 -0
  277. package/dist/testing/cross_backend/transport_composition.d.ts.map +1 -0
  278. package/dist/testing/cross_backend/transport_composition.js +364 -0
  279. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
  280. package/dist/testing/cross_backend/ts_spine_backend_config.js +21 -6
  281. package/dist/testing/cross_backend/ws_action_rate_limit.d.ts.map +1 -1
  282. package/dist/testing/cross_backend/ws_action_rate_limit.js +21 -1
  283. package/dist/testing/cross_backend/ws_role_gated.d.ts +38 -0
  284. package/dist/testing/cross_backend/ws_role_gated.d.ts.map +1 -0
  285. package/dist/testing/cross_backend/ws_role_gated.js +142 -0
  286. package/dist/testing/entities.d.ts +23 -0
  287. package/dist/testing/entities.d.ts.map +1 -1
  288. package/dist/testing/entities.js +37 -0
  289. package/dist/testing/integration.d.ts.map +1 -1
  290. package/dist/testing/integration.js +3 -1
  291. package/dist/testing/middleware.d.ts +10 -0
  292. package/dist/testing/middleware.d.ts.map +1 -1
  293. package/dist/testing/middleware.js +31 -7
  294. package/dist/testing/schema_introspect.js +1 -1
  295. package/dist/testing/schema_parity.d.ts +4 -3
  296. package/dist/testing/schema_parity.d.ts.map +1 -1
  297. package/dist/testing/schema_parity.js +4 -3
  298. package/dist/testing/schema_ready_fixture.d.ts +2 -4
  299. package/dist/testing/schema_ready_fixture.d.ts.map +1 -1
  300. package/dist/testing/schema_ready_fixture.js +2 -4
  301. package/dist/testing/stubs.d.ts +16 -3
  302. package/dist/testing/stubs.d.ts.map +1 -1
  303. package/dist/testing/stubs.js +20 -7
  304. package/dist/testing/surface_invariants.js +2 -2
  305. package/dist/ui/auth_state.svelte.d.ts +10 -0
  306. package/dist/ui/auth_state.svelte.d.ts.map +1 -1
  307. package/dist/ui/auth_state.svelte.js +22 -4
  308. package/package.json +1 -1
  309. package/dist/db/cell_history_ddl.d.ts +0 -38
  310. package/dist/db/cell_history_ddl.d.ts.map +0 -1
  311. package/dist/db/cell_history_ddl.js +0 -61
@@ -13,12 +13,19 @@
13
13
  *
14
14
  * Three tiers:
15
15
  *
16
- * - `can_view_cell` — admin / public / owner / any `viewer`+ grant.
17
- * - `can_edit_cell` — admin / owner / `editor` grant. NULL `created_by`
16
+ * - `can_view_cell` — admin / owner / public / any `viewer`+ grant, the
17
+ * public and grant arms only while the cell's `moderation` admits an
18
+ * audience (`db/cell_moderation.ts`: unmoderated or `'approved'`).
19
+ * - `can_edit_cell` — admin / owner / `editor` grant, the grant arm only
20
+ * while the cell's `moderation` admits an audience. NULL `created_by`
18
21
  * is admin-only (system-origin defense-in-depth).
19
22
  * - `can_manage_cell` — admin / owner only. Not delegable, not a grant
20
23
  * level. Gates the manage tier: `visibility` writes and all grant
21
24
  * management (`cell_grant_create` / `_list` / `_revoke`).
25
+ * - `can_steward_cell` — the governing root's manager reading a pending or
26
+ * rejected contribution under it. A separate read arm beside
27
+ * `can_view_cell`, composed only at the surfaces a moderator works from
28
+ * (`auth/cell_steward.ts` names them).
22
29
  *
23
30
  * Grant-admit branches: per-`actor_id` grants admit the matching
24
31
  * actor; `(role, scope_id?)` grants admit any holder of an active
@@ -29,29 +36,55 @@
29
36
  * The admin bypass on every tier is the **global** `admin` grant
30
37
  * (`has_scoped_role(auth, ROLE_ADMIN, null)`), never a scoped one.
31
38
  *
39
+ * Moderation gates the audience: a cell's effective audience is its
40
+ * visibility or grants **and** its admission, so a pending, rejected, or
41
+ * unknown-marker contribution is readable through `can_view_cell` by its owner
42
+ * and a global admin alone, whatever its `visibility` or grants say. The
43
+ * `editor` grant arm of `can_edit_cell` follows the same admission; the owner,
44
+ * admin, and manage arms don't consult it.
45
+ *
32
46
  * @module
33
47
  */
34
48
  import { type RequestContext } from './request_context.js';
35
- import type { CellRow } from '../db/cell_queries.js';
49
+ import type { CellRow, CellViewRow } from '../db/cell_queries.js';
50
+ import { type CellViewer } from '../db/cell_view_sql.js';
36
51
  import type { CellGrantRow } from '../db/cell_grant_queries.js';
37
52
  /**
38
53
  * View authorization for a cell.
39
54
  *
40
55
  * - Admin: always allowed.
41
- * - `cell.visibility === 'public'`: allowed for everyone, including
42
- * unauthenticated callers (e.g. a public landing cell).
43
56
  * - Owner (`cell.created_by === auth.actor.id`): allowed.
57
+ * - `cell.visibility === 'public'`: allowed for everyone, including
58
+ * unauthenticated callers (e.g. a public landing cell) — while the cell's
59
+ * `moderation` admits an audience (`cell_moderation_admits_audience`).
44
60
  * - Any active grant on the cell admits the caller (actor-shaped:
45
61
  * match on actor_id; role-shaped: match on `(role, scope_id?)`
46
- * against an active role_grant).
62
+ * against an active role_grant) — under the same moderation admission.
47
63
  * - Otherwise: false.
48
64
  *
65
+ * A pending or rejected contribution stays with its owner and admins even
66
+ * when public or granted.
67
+ *
68
+ * Reads only the view facts (`visibility`, `moderation`, `created_by`), so a
69
+ * full `CellRow` and the narrow `CellViewRow` both answer it.
70
+ *
49
71
  * @param auth - request context, or `null` for unauthenticated callers
50
- * @param cell - the cell row
72
+ * @param cell - the cell row, or its view facts
51
73
  * @param grants - the cell's grant list, or `null` to skip the grant branch
52
74
  * @returns whether the caller may view the cell
53
75
  */
54
- export declare const can_view_cell: (auth: RequestContext | null, cell: CellRow, grants: ReadonlyArray<CellGrantRow> | null) => boolean;
76
+ export declare const can_view_cell: (auth: RequestContext | null, cell: Omit<CellViewRow, "id">, grants: ReadonlyArray<CellGrantRow> | null) => boolean;
77
+ /**
78
+ * The `CellViewer` a SQL view filter (`db/cell_view_sql.ts`) binds for
79
+ * `auth` — the facts `can_view_cell` reads, so the two admit the same cells:
80
+ * the acting actor, the global `admin` grant, and the active role grants
81
+ * `grant_admits` matches against.
82
+ *
83
+ * @param auth - request context, or `null` for unauthenticated callers
84
+ * @param now - the instant role-grant expiry is judged at
85
+ * @returns the viewer
86
+ */
87
+ export declare const cell_viewer_from_auth: (auth: RequestContext | null, now?: Date) => CellViewer;
55
88
  /**
56
89
  * Edit authorization for a cell.
57
90
  *
@@ -66,6 +99,12 @@ export declare const can_view_cell: (auth: RequestContext | null, cell: CellRow,
66
99
  * `false` for NULL via JS equality semantics — the explicit branch
67
100
  * survives refactors and reads as a load-bearing security property.
68
101
  *
102
+ * The `editor` grant arm requires the cell's `moderation` to admit an
103
+ * audience (`cell_moderation_admits_audience`), as in `can_view_cell`: a
104
+ * grant made while a contribution was approved stops admitting once it is
105
+ * rejected, for writes as for reads (every write verb echoes or reveals the
106
+ * row it touched).
107
+ *
69
108
  * @param auth - request context, or `null` for unauthenticated callers
70
109
  * @param cell - the cell row
71
110
  * @param grants - the cell's grant list, or `null` to skip the grant branch
@@ -88,4 +127,26 @@ export declare const can_edit_cell: (auth: RequestContext | null, cell: CellRow,
88
127
  * @returns whether the caller is in the manage tier for the cell
89
128
  */
90
129
  export declare const can_manage_cell: (auth: RequestContext | null, cell: CellRow) => boolean;
130
+ /**
131
+ * Steward authorization: whether `auth` manages `root` — the governing root of
132
+ * `cell` — and `cell` is a pending or rejected contribution under it.
133
+ *
134
+ * The moderator's read of a contribution its audience can't see: true when
135
+ * `cell.root_id` is `root.id`, `cell.moderation` is exactly `'pending'` or
136
+ * `'rejected'` (`cell_moderation_is_stewardable`), `root` is not soft-deleted,
137
+ * and `can_manage_cell` admits `auth` on `root` (global admin or the root's
138
+ * owner — no grant tier confers manage). Unauthenticated callers are never
139
+ * stewards. An approved contribution later narrowed to private is not
140
+ * stewarded — it reads like any cell its author narrowed.
141
+ *
142
+ * Not part of `can_view_cell`: the steward arm admits only where a moderator
143
+ * works (`auth/cell_steward.ts` names the sites), so the relation gates, clone,
144
+ * contribution parents, and the grant paths keep the narrower predicate.
145
+ *
146
+ * @param auth - request context, or `null` for unauthenticated callers
147
+ * @param cell - the contribution
148
+ * @param root - the cell's governing root
149
+ * @returns whether the caller stewards the contribution
150
+ */
151
+ export declare const can_steward_cell: (auth: RequestContext | null, cell: CellRow, root: CellRow) => boolean;
91
152
  //# sourceMappingURL=cell_authorize.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cell_authorize.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cell_authorize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EAAmB,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAI5E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AA+DhE;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,aAAa,GACzB,MAAM,cAAc,GAAG,IAAI,EAC3B,MAAM,OAAO,EACb,QAAQ,aAAa,CAAC,YAAY,CAAC,GAAG,IAAI,KACxC,OAMF,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,aAAa,GACzB,MAAM,cAAc,GAAG,IAAI,EAC3B,MAAM,OAAO,EACb,QAAQ,aAAa,CAAC,YAAY,CAAC,GAAG,IAAI,KACxC,OAUF,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,eAAe,GAAI,MAAM,cAAc,GAAG,IAAI,EAAE,MAAM,OAAO,KAAG,OAI5E,CAAC"}
1
+ {"version":3,"file":"cell_authorize.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cell_authorize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAEH,OAAO,EAAmB,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAI5E,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,EAAyB,KAAK,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAChF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAiEhE;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,aAAa,GACzB,MAAM,cAAc,GAAG,IAAI,EAC3B,MAAM,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,EAC7B,QAAQ,aAAa,CAAC,YAAY,CAAC,GAAG,IAAI,KACxC,OAOF,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,GACjC,MAAM,cAAc,GAAG,IAAI,EAC3B,MAAK,IAAiB,KACpB,UASF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,aAAa,GACzB,MAAM,cAAc,GAAG,IAAI,EAC3B,MAAM,OAAO,EACb,QAAQ,aAAa,CAAC,YAAY,CAAC,GAAG,IAAI,KACxC,OAYF,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,eAAe,GAAI,MAAM,cAAc,GAAG,IAAI,EAAE,MAAM,OAAO,KAAG,OAI5E,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,gBAAgB,GAC5B,MAAM,cAAc,GAAG,IAAI,EAC3B,MAAM,OAAO,EACb,MAAM,OAAO,KACX,OAKyB,CAAC"}
@@ -13,12 +13,19 @@
13
13
  *
14
14
  * Three tiers:
15
15
  *
16
- * - `can_view_cell` — admin / public / owner / any `viewer`+ grant.
17
- * - `can_edit_cell` — admin / owner / `editor` grant. NULL `created_by`
16
+ * - `can_view_cell` — admin / owner / public / any `viewer`+ grant, the
17
+ * public and grant arms only while the cell's `moderation` admits an
18
+ * audience (`db/cell_moderation.ts`: unmoderated or `'approved'`).
19
+ * - `can_edit_cell` — admin / owner / `editor` grant, the grant arm only
20
+ * while the cell's `moderation` admits an audience. NULL `created_by`
18
21
  * is admin-only (system-origin defense-in-depth).
19
22
  * - `can_manage_cell` — admin / owner only. Not delegable, not a grant
20
23
  * level. Gates the manage tier: `visibility` writes and all grant
21
24
  * management (`cell_grant_create` / `_list` / `_revoke`).
25
+ * - `can_steward_cell` — the governing root's manager reading a pending or
26
+ * rejected contribution under it. A separate read arm beside
27
+ * `can_view_cell`, composed only at the surfaces a moderator works from
28
+ * (`auth/cell_steward.ts` names them).
22
29
  *
23
30
  * Grant-admit branches: per-`actor_id` grants admit the matching
24
31
  * actor; `(role, scope_id?)` grants admit any holder of an active
@@ -29,12 +36,20 @@
29
36
  * The admin bypass on every tier is the **global** `admin` grant
30
37
  * (`has_scoped_role(auth, ROLE_ADMIN, null)`), never a scoped one.
31
38
  *
39
+ * Moderation gates the audience: a cell's effective audience is its
40
+ * visibility or grants **and** its admission, so a pending, rejected, or
41
+ * unknown-marker contribution is readable through `can_view_cell` by its owner
42
+ * and a global admin alone, whatever its `visibility` or grants say. The
43
+ * `editor` grant arm of `can_edit_cell` follows the same admission; the owner,
44
+ * admin, and manage arms don't consult it.
45
+ *
32
46
  * @module
33
47
  */
34
48
  import { has_scoped_role } from './request_context.js';
35
49
  import { is_role_grant_active } from './account_schema.js';
36
50
  import { ROLE_ADMIN } from './role_schema.js';
37
- const cell_is_public = (cell) => cell.visibility === 'public';
51
+ import { CELL_VIEWER_ANONYMOUS } from '../db/cell_view_sql.js';
52
+ import { cell_moderation_admits_audience, cell_moderation_is_stewardable } from '../db/cell_moderation.js';
38
53
  /**
39
54
  * Whether any grant admits `auth` at the given level.
40
55
  *
@@ -92,30 +107,60 @@ const is_owner = (auth, cell) => auth.actor !== null && cell.created_by !== null
92
107
  * View authorization for a cell.
93
108
  *
94
109
  * - Admin: always allowed.
95
- * - `cell.visibility === 'public'`: allowed for everyone, including
96
- * unauthenticated callers (e.g. a public landing cell).
97
110
  * - Owner (`cell.created_by === auth.actor.id`): allowed.
111
+ * - `cell.visibility === 'public'`: allowed for everyone, including
112
+ * unauthenticated callers (e.g. a public landing cell) — while the cell's
113
+ * `moderation` admits an audience (`cell_moderation_admits_audience`).
98
114
  * - Any active grant on the cell admits the caller (actor-shaped:
99
115
  * match on actor_id; role-shaped: match on `(role, scope_id?)`
100
- * against an active role_grant).
116
+ * against an active role_grant) — under the same moderation admission.
101
117
  * - Otherwise: false.
102
118
  *
119
+ * A pending or rejected contribution stays with its owner and admins even
120
+ * when public or granted.
121
+ *
122
+ * Reads only the view facts (`visibility`, `moderation`, `created_by`), so a
123
+ * full `CellRow` and the narrow `CellViewRow` both answer it.
124
+ *
103
125
  * @param auth - request context, or `null` for unauthenticated callers
104
- * @param cell - the cell row
126
+ * @param cell - the cell row, or its view facts
105
127
  * @param grants - the cell's grant list, or `null` to skip the grant branch
106
128
  * @returns whether the caller may view the cell
107
129
  */
108
130
  export const can_view_cell = (auth, cell, grants) => {
109
131
  if (auth && has_scoped_role(auth, ROLE_ADMIN, null))
110
132
  return true;
111
- if (cell_is_public(cell))
112
- return true;
113
133
  if (auth && is_owner(auth, cell))
114
134
  return true;
135
+ if (!cell_moderation_admits_audience(cell.moderation))
136
+ return false;
137
+ if (cell.visibility === 'public')
138
+ return true;
115
139
  if (auth && grant_admits(auth, grants, 'viewer'))
116
140
  return true;
117
141
  return false;
118
142
  };
143
+ /**
144
+ * The `CellViewer` a SQL view filter (`db/cell_view_sql.ts`) binds for
145
+ * `auth` — the facts `can_view_cell` reads, so the two admit the same cells:
146
+ * the acting actor, the global `admin` grant, and the active role grants
147
+ * `grant_admits` matches against.
148
+ *
149
+ * @param auth - request context, or `null` for unauthenticated callers
150
+ * @param now - the instant role-grant expiry is judged at
151
+ * @returns the viewer
152
+ */
153
+ export const cell_viewer_from_auth = (auth, now = new Date()) => {
154
+ if (!auth)
155
+ return CELL_VIEWER_ANONYMOUS;
156
+ const active = auth.role_grants.filter((p) => is_role_grant_active(p, now));
157
+ return {
158
+ actor_id: auth.actor?.id ?? null,
159
+ is_admin: has_scoped_role(auth, ROLE_ADMIN, null, now),
160
+ role_grant_roles: active.map((p) => p.role),
161
+ role_grant_scope_ids: active.map((p) => p.scope_id)
162
+ };
163
+ };
119
164
  /**
120
165
  * Edit authorization for a cell.
121
166
  *
@@ -130,6 +175,12 @@ export const can_view_cell = (auth, cell, grants) => {
130
175
  * `false` for NULL via JS equality semantics — the explicit branch
131
176
  * survives refactors and reads as a load-bearing security property.
132
177
  *
178
+ * The `editor` grant arm requires the cell's `moderation` to admit an
179
+ * audience (`cell_moderation_admits_audience`), as in `can_view_cell`: a
180
+ * grant made while a contribution was approved stops admitting once it is
181
+ * rejected, for writes as for reads (every write verb echoes or reveals the
182
+ * row it touched).
183
+ *
133
184
  * @param auth - request context, or `null` for unauthenticated callers
134
185
  * @param cell - the cell row
135
186
  * @param grants - the cell's grant list, or `null` to skip the grant branch
@@ -147,6 +198,9 @@ export const can_edit_cell = (auth, cell, grants) => {
147
198
  // fallback below handles the actor-grain editor-grant path.
148
199
  if (is_owner(auth, cell))
149
200
  return true;
201
+ // the grant arm follows moderation admission, as in `can_view_cell`
202
+ if (!cell_moderation_admits_audience(cell.moderation))
203
+ return false;
150
204
  if (grant_admits(auth, grants, 'editor'))
151
205
  return true;
152
206
  return false;
@@ -173,3 +227,29 @@ export const can_manage_cell = (auth, cell) => {
173
227
  return true;
174
228
  return is_owner(auth, cell);
175
229
  };
230
+ /**
231
+ * Steward authorization: whether `auth` manages `root` — the governing root of
232
+ * `cell` — and `cell` is a pending or rejected contribution under it.
233
+ *
234
+ * The moderator's read of a contribution its audience can't see: true when
235
+ * `cell.root_id` is `root.id`, `cell.moderation` is exactly `'pending'` or
236
+ * `'rejected'` (`cell_moderation_is_stewardable`), `root` is not soft-deleted,
237
+ * and `can_manage_cell` admits `auth` on `root` (global admin or the root's
238
+ * owner — no grant tier confers manage). Unauthenticated callers are never
239
+ * stewards. An approved contribution later narrowed to private is not
240
+ * stewarded — it reads like any cell its author narrowed.
241
+ *
242
+ * Not part of `can_view_cell`: the steward arm admits only where a moderator
243
+ * works (`auth/cell_steward.ts` names the sites), so the relation gates, clone,
244
+ * contribution parents, and the grant paths keep the narrower predicate.
245
+ *
246
+ * @param auth - request context, or `null` for unauthenticated callers
247
+ * @param cell - the contribution
248
+ * @param root - the cell's governing root
249
+ * @returns whether the caller stewards the contribution
250
+ */
251
+ export const can_steward_cell = (auth, cell, root) => auth !== null &&
252
+ cell.root_id === root.id &&
253
+ cell_moderation_is_stewardable(cell.moderation) &&
254
+ root.deleted_at === null &&
255
+ can_manage_cell(auth, root);
@@ -70,10 +70,11 @@ export type CellFieldDeleteOutput = z.infer<typeof CellFieldDeleteOutput>;
70
70
  * both / neither). Reverse listing has 2-layer authz (target view-check
71
71
  * gates the call; per-source view-check filters the rows).
72
72
  *
73
- * Forward listing supports cursor pagination via `name_after` (return
74
- * rows whose `name > name_after` lex). The reverse listing doesn't
75
- * paginate (the result set is small in practice — number of sources
76
- * pointing at a given target).
73
+ * Either direction returns a page of up to `limit` fields (default
74
+ * `CELL_RELATION_LIST_LIMIT_DEFAULT`) whose other end the caller can view —
75
+ * the view check runs before the limit. Forward listing pages on via
76
+ * `name_after` (return rows whose `name > name_after` lex); the reverse
77
+ * listing has no cursor, oldest field first.
77
78
  */
78
79
  export declare const CellFieldListInput: z.ZodObject<{
79
80
  source_id: z.ZodOptional<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
@@ -1 +1 @@
1
- {"version":3,"file":"cell_field_action_specs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cell_field_action_specs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAQxB,gFAAgF;AAChF,eAAO,MAAM,+CAA+C,EAC3D,2CAAoD,CAAC;AAItD;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,QAA2B,CAAC;AAC9D,eAAO,MAAM,aAAa,yDAAiE,CAAC;AAC5F,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE1D,2EAA2E;AAC3E,eAAO,MAAM,SAAS;;;;;kBAKpB,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC;AAIlD;;;GAGG;AACH,eAAO,MAAM,iBAAiB;;;;;kBAO5B,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAElE,eAAO,MAAM,kBAAkB;;;;;;;kBAAuC,CAAC;AACvE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAIpE;;;GAGG;AACH,eAAO,MAAM,oBAAoB;;;;kBAI/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE,eAAO,MAAM,qBAAqB;;;kBAGhC,CAAC;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAI1E;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kBAAkB;;;;;;kBAoB5B,CAAC;AACJ,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE,eAAO,MAAM,mBAAmB;;;;;;;kBAE9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAItE,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;CAWF,CAAC;AAEtC,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;CAWL,CAAC;AAEtC,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;CAYH,CAAC;AAEtC,2EAA2E;AAC3E,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAI9B,CAAC"}
1
+ {"version":3,"file":"cell_field_action_specs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cell_field_action_specs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AASxB,gFAAgF;AAChF,eAAO,MAAM,+CAA+C,EAC3D,2CAAoD,CAAC;AAItD;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,QAA2B,CAAC;AAC9D,eAAO,MAAM,aAAa,yDAAiE,CAAC;AAC5F,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE1D,2EAA2E;AAC3E,eAAO,MAAM,SAAS;;;;;kBAKpB,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC;AAIlD;;;GAGG;AACH,eAAO,MAAM,iBAAiB;;;;;kBAO5B,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAElE,eAAO,MAAM,kBAAkB;;;;;;;kBAAuC,CAAC;AACvE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAIpE;;;GAGG;AACH,eAAO,MAAM,oBAAoB;;;;kBAI/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE,eAAO,MAAM,qBAAqB;;;kBAGhC,CAAC;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAI1E;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,kBAAkB;;;;;;kBAiB5B,CAAC;AACJ,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE,eAAO,MAAM,mBAAmB;;;;;;;kBAE9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAItE,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;CAWF,CAAC;AAEtC,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;CAWL,CAAC;AAEtC,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;CAYH,CAAC;AAEtC,2EAA2E;AAC3E,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAI9B,CAAC"}
@@ -13,6 +13,7 @@
13
13
  import { z } from 'zod';
14
14
  import { Uuid } from '../primitive_schemas.js';
15
15
  import { ActingActor } from '../http/auth_shape.js';
16
+ import { CellRelationListLimit } from './cell_relation_list_limit.js';
16
17
  // -- Error reasons ----------------------------------------------------------
17
18
  /** Error reason — `cell_field_list` got neither `source_id` nor `target_id`. */
18
19
  export const ERROR_CELL_FIELD_LIST_REQUIRES_SOURCE_OR_TARGET = 'cell_field_list_requires_source_or_target';
@@ -66,10 +67,11 @@ export const CellFieldDeleteOutput = z.strictObject({
66
67
  * both / neither). Reverse listing has 2-layer authz (target view-check
67
68
  * gates the call; per-source view-check filters the rows).
68
69
  *
69
- * Forward listing supports cursor pagination via `name_after` (return
70
- * rows whose `name > name_after` lex). The reverse listing doesn't
71
- * paginate (the result set is small in practice — number of sources
72
- * pointing at a given target).
70
+ * Either direction returns a page of up to `limit` fields (default
71
+ * `CELL_RELATION_LIST_LIMIT_DEFAULT`) whose other end the caller can view —
72
+ * the view check runs before the limit. Forward listing pages on via
73
+ * `name_after` (return rows whose `name > name_after` lex); the reverse
74
+ * listing has no cursor, oldest field first.
73
75
  */
74
76
  export const CellFieldListInput = z
75
77
  .strictObject({
@@ -82,9 +84,7 @@ export const CellFieldListInput = z
82
84
  name_after: CellFieldName.optional().meta({
83
85
  description: 'Cursor for forward pagination — return rows whose name > this lex. Forward only.'
84
86
  }),
85
- limit: z.number().int().positive().max(500).optional().meta({
86
- description: 'Page size cap (max 500). Forward only. Omit for unbounded — explicit list calls escape the bundled `cell_get` cap.'
87
- }),
87
+ limit: CellRelationListLimit,
88
88
  acting: ActingActor
89
89
  })
90
90
  .refine((v) => Boolean(v.source_id) !== Boolean(v.target_id), {
@@ -9,11 +9,13 @@
9
9
  * couldn't otherwise see). Idempotent UPSERT on `(source_id, name)`.
10
10
  * - `cell_field_delete` — admin / owner / editor-grant on `source`.
11
11
  * Idempotent: `deleted: false` when no row matched.
12
- * - `cell_field_list` — bidirectional. Forward (pass `source_id`) is
13
- * gated on `can_view_cell(source)` and filters targets to those the
14
- * caller may view (strict target-visibility, batched). Reverse (pass
15
- * `target_id`) has 2-layer authz: gate on `can_view_cell(target)`
16
- * first, then filter rows by `can_view_cell(source)`.
12
+ * - `cell_field_list` — bidirectional, a page of up to `limit` fields
13
+ * whose other end the caller may view (filtered in SQL, before the
14
+ * limit). Forward (pass `source_id`) is gated on `can_view_cell(source)`
15
+ * and pages the fields whose target is viewable (strict
16
+ * target-visibility). Reverse (pass `target_id`) has 2-layer authz: gate
17
+ * on `can_view_cell(target)` first, then page the fields whose source is
18
+ * viewable.
17
19
  *
18
20
  * IDOR-mask 404s on cell-miss / cell-unviewable, mirroring the existence-
19
21
  * leak guards in `auth/cell_actions.ts` / `auth/cell_grant_actions.ts`.
@@ -1 +1 @@
1
- {"version":3,"file":"cell_field_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cell_field_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAIN,KAAK,SAAS,EACd,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAEnD,OAAO,EAUN,KAAK,SAAS,EACd,MAAM,8BAA8B,CAAC;AAMtC,OAAO,EAKN,KAAK,YAAY,EACjB,MAAM,6BAA6B,CAAC;AAMrC,MAAM,MAAM,mBAAmB,GAAG,iBAAiB,CAAC;AAEpD,eAAO,MAAM,aAAa,GAAI,KAAK,YAAY,KAAG,SAKhD,CAAC;AAEH,mDAAmD;AACnD,eAAO,MAAM,yBAAyB,GAAI,MAAM,mBAAmB,KAAG,KAAK,CAAC,SAAS,CAmIpF,CAAC"}
1
+ {"version":3,"file":"cell_field_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cell_field_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAIN,KAAK,SAAS,EACd,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAEnD,OAAO,EAUN,KAAK,SAAS,EACd,MAAM,8BAA8B,CAAC;AAMtC,OAAO,EAKN,KAAK,YAAY,EACjB,MAAM,6BAA6B,CAAC;AAMrC,MAAM,MAAM,mBAAmB,GAAG,iBAAiB,CAAC;AAEpD,eAAO,MAAM,aAAa,GAAI,KAAK,YAAY,KAAG,SAKhD,CAAC;AAEH,mDAAmD;AACnD,eAAO,MAAM,yBAAyB,GAAI,MAAM,mBAAmB,KAAG,KAAK,CAAC,SAAS,CAqIpF,CAAC"}
@@ -9,11 +9,13 @@
9
9
  * couldn't otherwise see). Idempotent UPSERT on `(source_id, name)`.
10
10
  * - `cell_field_delete` — admin / owner / editor-grant on `source`.
11
11
  * Idempotent: `deleted: false` when no row matched.
12
- * - `cell_field_list` — bidirectional. Forward (pass `source_id`) is
13
- * gated on `can_view_cell(source)` and filters targets to those the
14
- * caller may view (strict target-visibility, batched). Reverse (pass
15
- * `target_id`) has 2-layer authz: gate on `can_view_cell(target)`
16
- * first, then filter rows by `can_view_cell(source)`.
12
+ * - `cell_field_list` — bidirectional, a page of up to `limit` fields
13
+ * whose other end the caller may view (filtered in SQL, before the
14
+ * limit). Forward (pass `source_id`) is gated on `can_view_cell(source)`
15
+ * and pages the fields whose target is viewable (strict
16
+ * target-visibility). Reverse (pass `target_id`) has 2-layer authz: gate
17
+ * on `can_view_cell(target)` first, then page the fields whose source is
18
+ * viewable.
17
19
  *
18
20
  * IDOR-mask 404s on cell-miss / cell-unviewable, mirroring the existence-
19
21
  * leak guards in `auth/cell_actions.ts` / `auth/cell_grant_actions.ts`.
@@ -27,8 +29,8 @@ import { rpc_action } from '../actions/action_rpc.js';
27
29
  import { jsonrpc_errors } from '../http/jsonrpc_errors.js';
28
30
  import { cell_field_set_action_spec, cell_field_delete_action_spec, cell_field_list_action_spec } from './cell_field_action_specs.js';
29
31
  import { ERROR_CELL_NOT_FOUND } from './cell_action_specs.js';
30
- import { can_view_cell, can_edit_cell } from './cell_authorize.js';
31
- import { filter_visible_target_ids } from './cell_relation_visibility.js';
32
+ import { can_view_cell, can_edit_cell, cell_viewer_from_auth } from './cell_authorize.js';
33
+ import { CELL_RELATION_LIST_LIMIT_DEFAULT } from './cell_relation_list_limit.js';
32
34
  import { query_cell_get } from '../db/cell_queries.js';
33
35
  import { query_cell_grant_list_for_cell } from '../db/cell_grant_queries.js';
34
36
  import { query_cell_field_set, query_cell_field_delete, query_cell_field_list_for_source, query_cell_field_list_for_target } from '../db/cell_field_queries.js';
@@ -108,8 +110,11 @@ export const create_cell_field_actions = (deps) => {
108
110
  };
109
111
  const list_handler = async (input, ctx) => {
110
112
  const auth = ctx.auth;
111
- // Forward listing: gate on can_view_cell(source), then filter the
112
- // targets to those the caller may view (strict target-visibility).
113
+ // an omitted limit is the default page, either direction
114
+ const limit = input.limit ?? CELL_RELATION_LIST_LIMIT_DEFAULT;
115
+ // Forward listing: gate on can_view_cell(source), then page the fields
116
+ // whose target the caller may view (strict target-visibility, in SQL
117
+ // before the limit).
113
118
  if (input.source_id !== undefined) {
114
119
  const source = await query_cell_get(ctx, input.source_id);
115
120
  if (!source) {
@@ -119,12 +124,8 @@ export const create_cell_field_actions = (deps) => {
119
124
  if (!can_view_cell(auth, source, source_grants)) {
120
125
  throw jsonrpc_errors.not_found('cell', { reason: ERROR_CELL_NOT_FOUND });
121
126
  }
122
- const rows = await query_cell_field_list_for_source(ctx, source.id, {
123
- limit: input.limit,
124
- name_after: input.name_after
125
- });
126
- const visible_targets = await filter_visible_target_ids(ctx, auth, rows.map((r) => r.target_id));
127
- return { fields: rows.filter((r) => visible_targets.has(r.target_id)).map(to_field_json) };
127
+ const rows = await query_cell_field_list_for_source(ctx, cell_viewer_from_auth(auth), source.id, { limit, name_after: input.name_after });
128
+ return { fields: rows.map(to_field_json) };
128
129
  }
129
130
  // Reverse listing: 2-layer authz. First, can_view_cell(target).
130
131
  // Without this, the count of returned rows leaks "at least N
@@ -137,13 +138,14 @@ export const create_cell_field_actions = (deps) => {
137
138
  if (!can_view_cell(auth, target, target_grants)) {
138
139
  throw jsonrpc_errors.not_found('cell', { reason: ERROR_CELL_NOT_FOUND });
139
140
  }
140
- // Then filter rows by per-source can_view_cell. Batched (not N+1):
141
- // one bulk visibility filter over all source ids, same as the forward
142
- // branch. Bounded by `limit` at the query so a heavily inbound-linked
143
- // target can't force an unbounded fetch on this public endpoint.
144
- const rows = await query_cell_field_list_for_target(ctx, target.id, { limit: input.limit });
145
- const visible_sources = await filter_visible_target_ids(ctx, auth, rows.map((r) => r.source_id));
146
- return { fields: rows.filter((r) => visible_sources.has(r.source_id)).map(to_field_json) };
141
+ // Then page the fields whose source the caller may view — filtered in
142
+ // SQL before the limit, so hidden sources never crowd out visible ones,
143
+ // and bounded so a heavily inbound-linked target can't force an
144
+ // unbounded read on this public endpoint.
145
+ const rows = await query_cell_field_list_for_target(ctx, cell_viewer_from_auth(auth), target.id, {
146
+ limit
147
+ });
148
+ return { fields: rows.map(to_field_json) };
147
149
  };
148
150
  return [
149
151
  rpc_action(cell_field_set_action_spec, set_handler),
@@ -29,6 +29,12 @@ export declare const ERROR_CELL_GRANT_PRINCIPAL_IS_OWNER: "cell_grant_principal_
29
29
  * no role_grant could match.
30
30
  */
31
31
  export declare const ERROR_CELL_GRANT_UNKNOWN_ROLE: "cell_grant_unknown_role";
32
+ /**
33
+ * Error reason — `cell_grant_create` on a cell whose `moderation` doesn't
34
+ * admit an audience (pending, rejected, or an unknown marker). A grant would
35
+ * extend an audience the cell doesn't have yet.
36
+ */
37
+ export declare const ERROR_CELL_GRANT_UNAPPROVED: "cell_grant_unapproved";
32
38
  /** Grant level — view-only or view-plus-edit. */
33
39
  export declare const CellGrantLevel: z.ZodEnum<{
34
40
  viewer: "viewer";
@@ -1 +1 @@
1
- {"version":3,"file":"cell_grant_action_specs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cell_grant_action_specs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAQxB,yEAAyE;AACzE,eAAO,MAAM,0BAA0B,EAAG,sBAA+B,CAAC;AAE1E;;;;;GAKG;AACH,eAAO,MAAM,mCAAmC,EAAG,+BAAwC,CAAC;AAE5F;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,EAAG,yBAAkC,CAAC;AAIhF,iDAAiD;AACjD,eAAO,MAAM,cAAc;;;EAA+B,CAAC;AAC3D,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D;;;;GAIG;AACH,eAAO,MAAM,uBAAuB;;;;;;;4BAYlC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAE9E;;;;GAIG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;kBASpB,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC;AAIlD;;;;GAIG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;kBAO/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;kBAAuC,CAAC;AAC1E,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAI1E,eAAO,MAAM,oBAAoB;;;kBAG/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB;;;kBAGhC,CAAC;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAI1E,eAAO,MAAM,kBAAkB;;;kBAG7B,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;kBAE9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAItE,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWL,CAAC;AAEtC,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;CAWL,CAAC;AAEtC,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWH,CAAC;AAEtC,2EAA2E;AAC3E,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAI9B,CAAC"}
1
+ {"version":3,"file":"cell_grant_action_specs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cell_grant_action_specs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAQxB,yEAAyE;AACzE,eAAO,MAAM,0BAA0B,EAAG,sBAA+B,CAAC;AAE1E;;;;;GAKG;AACH,eAAO,MAAM,mCAAmC,EAAG,+BAAwC,CAAC;AAE5F;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,EAAG,yBAAkC,CAAC;AAEhF;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,EAAG,uBAAgC,CAAC;AAI5E,iDAAiD;AACjD,eAAO,MAAM,cAAc;;;EAA+B,CAAC;AAC3D,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D;;;;GAIG;AACH,eAAO,MAAM,uBAAuB;;;;;;;4BAYlC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAE9E;;;;GAIG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;kBASpB,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC;AAIlD;;;;GAIG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;kBAO/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;kBAAuC,CAAC;AAC1E,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAI1E,eAAO,MAAM,oBAAoB;;;kBAG/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB;;;kBAGhC,CAAC;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAI1E,eAAO,MAAM,kBAAkB;;;kBAG7B,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;kBAE9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAItE,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWL,CAAC;AAEtC,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;CAWL,CAAC;AAEtC,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWH,CAAC;AAEtC,2EAA2E;AAC3E,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAI9B,CAAC"}
@@ -32,6 +32,12 @@ export const ERROR_CELL_GRANT_PRINCIPAL_IS_OWNER = 'cell_grant_principal_is_owne
32
32
  * no role_grant could match.
33
33
  */
34
34
  export const ERROR_CELL_GRANT_UNKNOWN_ROLE = 'cell_grant_unknown_role';
35
+ /**
36
+ * Error reason — `cell_grant_create` on a cell whose `moderation` doesn't
37
+ * admit an audience (pending, rejected, or an unknown marker). A grant would
38
+ * extend an audience the cell doesn't have yet.
39
+ */
40
+ export const ERROR_CELL_GRANT_UNAPPROVED = 'cell_grant_unapproved';
35
41
  // -- Shared schemas ---------------------------------------------------------
36
42
  /** Grant level — view-only or view-plus-edit. */
37
43
  export const CellGrantLevel = z.enum(['viewer', 'editor']);
@@ -9,7 +9,9 @@
9
9
  * cannot manage its grants — delegating the share list would let an editor
10
10
  * widen access or mint peer editors and escape the manager's authority.
11
11
  *
12
- * - `cell_grant_create` — admin / owner only. Validates role-shaped
12
+ * - `cell_grant_create` — admin / owner only. Refuses a cell whose
13
+ * `moderation` doesn't admit an audience (403 `cell_grant_unapproved`,
14
+ * after the manage gate). Validates role-shaped
13
15
  * principals against the role schema; rejects owner-as-principal.
14
16
  * Actor-shaped principals carry a pre-resolved `actor_id` (callers pick
15
17
  * via `actor_search`). Idempotent — re-granting the same principal
@@ -1 +1 @@
1
- {"version":3,"file":"cell_grant_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cell_grant_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAAuC,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAE/F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAEnD,OAAO,EAaN,KAAK,SAAS,EACd,MAAM,8BAA8B,CAAC;AAItC,OAAO,EAKN,KAAK,YAAY,EAEjB,MAAM,6BAA6B,CAAC;AAOrC;;;;;;GAMG;AACH,MAAM,MAAM,mBAAmB,GAAG,iBAAiB,GAAG;IACrD,KAAK,EAAE,gBAAgB,CAAC;CACxB,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,KAAK,YAAY,KAAG,SAShD,CAAC;AAgDH,mDAAmD;AACnD,eAAO,MAAM,yBAAyB,GAAI,MAAM,mBAAmB,KAAG,KAAK,CAAC,SAAS,CAoIpF,CAAC"}
1
+ {"version":3,"file":"cell_grant_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cell_grant_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EAAuC,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAE/F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAEnD,OAAO,EAcN,KAAK,SAAS,EACd,MAAM,8BAA8B,CAAC;AAKtC,OAAO,EAKN,KAAK,YAAY,EAEjB,MAAM,6BAA6B,CAAC;AAOrC;;;;;;GAMG;AACH,MAAM,MAAM,mBAAmB,GAAG,iBAAiB,GAAG;IACrD,KAAK,EAAE,gBAAgB,CAAC;CACxB,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,KAAK,YAAY,KAAG,SAShD,CAAC;AAgDH,mDAAmD;AACnD,eAAO,MAAM,yBAAyB,GAAI,MAAM,mBAAmB,KAAG,KAAK,CAAC,SAAS,CA+IpF,CAAC"}
@@ -9,7 +9,9 @@
9
9
  * cannot manage its grants — delegating the share list would let an editor
10
10
  * widen access or mint peer editors and escape the manager's authority.
11
11
  *
12
- * - `cell_grant_create` — admin / owner only. Validates role-shaped
12
+ * - `cell_grant_create` — admin / owner only. Refuses a cell whose
13
+ * `moderation` doesn't admit an audience (403 `cell_grant_unapproved`,
14
+ * after the manage gate). Validates role-shaped
13
15
  * principals against the role schema; rejects owner-as-principal.
14
16
  * Actor-shaped principals carry a pre-resolved `actor_id` (callers pick
15
17
  * via `actor_search`). Idempotent — re-granting the same principal
@@ -31,10 +33,11 @@
31
33
  */
32
34
  import { rpc_action } from '../actions/action_rpc.js';
33
35
  import { jsonrpc_errors } from '../http/jsonrpc_errors.js';
34
- import { cell_grant_create_action_spec, cell_grant_revoke_action_spec, cell_grant_list_action_spec, ERROR_CELL_GRANT_NOT_FOUND, ERROR_CELL_GRANT_PRINCIPAL_IS_OWNER, ERROR_CELL_GRANT_UNKNOWN_ROLE } from './cell_grant_action_specs.js';
36
+ import { cell_grant_create_action_spec, cell_grant_revoke_action_spec, cell_grant_list_action_spec, ERROR_CELL_GRANT_NOT_FOUND, ERROR_CELL_GRANT_PRINCIPAL_IS_OWNER, ERROR_CELL_GRANT_UNAPPROVED, ERROR_CELL_GRANT_UNKNOWN_ROLE } from './cell_grant_action_specs.js';
35
37
  import { ERROR_CELL_NOT_FOUND } from './cell_action_specs.js';
36
38
  import { can_view_cell, can_manage_cell } from './cell_authorize.js';
37
39
  import { query_cell_get } from '../db/cell_queries.js';
40
+ import { cell_moderation_admits_audience } from '../db/cell_moderation.js';
38
41
  import { query_cell_grant_create, query_cell_grant_get, query_cell_grant_delete, query_cell_grant_list_for_cell } from '../db/cell_grant_queries.js';
39
42
  export const to_grant_json = (row) => ({
40
43
  id: row.id,
@@ -105,6 +108,17 @@ export const create_cell_grant_actions = (deps) => {
105
108
  if (!can_manage_cell(auth, cell)) {
106
109
  throw jsonrpc_errors.not_found('cell', { reason: ERROR_CELL_NOT_FOUND });
107
110
  }
111
+ // A grant extends a cell's audience, and a contribution whose moderation
112
+ // doesn't admit one (pending, rejected, or an unknown marker) has none past
113
+ // its author and admins — `can_view_cell`'s grant arm wouldn't admit on it —
114
+ // so refuse the grant rather than mint a row that would wake up on
115
+ // approval. After the manage gate, so a non-manager keeps the 404 mask; no
116
+ // admin exemption.
117
+ if (!cell_moderation_admits_audience(cell.moderation)) {
118
+ throw jsonrpc_errors.forbidden('cell_grant_create requires an unmoderated or approved cell', {
119
+ reason: ERROR_CELL_GRANT_UNAPPROVED
120
+ });
121
+ }
108
122
  const principal = to_query_principal(input.principal);
109
123
  // Role validity — only relevant for role-shaped principals; reject
110
124
  // before insert so dead grant rows nothing can match are foreclosed.
@@ -110,10 +110,11 @@ export type CellItemDeleteOutput = z.infer<typeof CellItemDeleteOutput>;
110
110
  * authz (child view-check gates the call; per-parent view-check filters
111
111
  * the rows).
112
112
  *
113
- * Forward listing supports cursor pagination via `position_after`
114
- * (return rows with `position > position_after`). The reverse listing
115
- * doesn't paginate (the result set is small in practice — number of
116
- * parents containing a given child).
113
+ * Either direction returns a page of up to `limit` items (default
114
+ * `CELL_RELATION_LIST_LIMIT_DEFAULT`) whose other end the caller can view —
115
+ * the view check runs before the limit. Forward listing pages on via
116
+ * `position_after` (return rows with `position > position_after`); the
117
+ * reverse listing has no cursor, oldest item first.
117
118
  */
118
119
  export declare const CellItemListInput: z.ZodObject<{
119
120
  parent_id: z.ZodOptional<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
@@ -1 +1 @@
1
- {"version":3,"file":"cell_item_action_specs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cell_item_action_specs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAYxB,8EAA8E;AAC9E,eAAO,MAAM,6CAA6C,EACzD,yCAAkD,CAAC;AAEpD;;;;;;;;;GASG;AACH,eAAO,MAAM,8BAA8B,EAAG,0BAAmC,CAAC;AAIlF;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,4DAEF,CAAC;AAC5B,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEhE;;;;;;;;;GASG;AACH,eAAO,MAAM,QAAQ;;;;;kBAKnB,CAAC;AACH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAC;AAIhD;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB;;;;;kBAO9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,eAAO,MAAM,oBAAoB;;;;;;;kBAAqC,CAAC;AACvE,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAIxE;;;GAGG;AACH,eAAO,MAAM,iBAAiB;;;;;kBAK5B,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAElE,eAAO,MAAM,kBAAkB;;;;;;;kBAAqC,CAAC;AACrE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAIpE,gEAAgE;AAChE,eAAO,MAAM,mBAAmB;;;;kBAI9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,eAAO,MAAM,oBAAoB;;;kBAG/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAIxE;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB;;;;;;kBAmB3B,CAAC;AACJ,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAElE,eAAO,MAAM,kBAAkB;;;;;;;kBAE7B,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAIpE,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;;CAWJ,CAAC;AAEtC,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;CAWF,CAAC;AAEtC,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;;CAWJ,CAAC;AAEtC,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;CAYF,CAAC;AAEtC,0EAA0E;AAC1E,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAK7B,CAAC"}
1
+ {"version":3,"file":"cell_item_action_specs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cell_item_action_specs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAaxB,8EAA8E;AAC9E,eAAO,MAAM,6CAA6C,EACzD,yCAAkD,CAAC;AAEpD;;;;;;;;;GASG;AACH,eAAO,MAAM,8BAA8B,EAAG,0BAAmC,CAAC;AAIlF;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,4DAEF,CAAC;AAC5B,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEhE;;;;;;;;;GASG;AACH,eAAO,MAAM,QAAQ;;;;;kBAKnB,CAAC;AACH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAC;AAIhD;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB;;;;;kBAO9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,eAAO,MAAM,oBAAoB;;;;;;;kBAAqC,CAAC;AACvE,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAIxE;;;GAGG;AACH,eAAO,MAAM,iBAAiB;;;;;kBAK5B,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAElE,eAAO,MAAM,kBAAkB;;;;;;;kBAAqC,CAAC;AACrE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAIpE,gEAAgE;AAChE,eAAO,MAAM,mBAAmB;;;;kBAI9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,eAAO,MAAM,oBAAoB;;;kBAG/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAIxE;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iBAAiB;;;;;;kBAgB3B,CAAC;AACJ,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAElE,eAAO,MAAM,kBAAkB;;;;;;;kBAE7B,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAIpE,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;;CAWJ,CAAC;AAEtC,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;CAWF,CAAC;AAEtC,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;;CAWJ,CAAC;AAEtC,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;CAYF,CAAC;AAEtC,0EAA0E;AAC1E,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAK7B,CAAC"}