@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
@@ -22,7 +22,9 @@
22
22
  */
23
23
  import { fact_hash_extract_refs } from '@fuzdev/fuz_util/fact_hash.ts';
24
24
  import { assert_row } from './assert_row.js';
25
- import { iso8601_timestamp_expr, qualify_columns } from './sql_columns.js';
25
+ import { columns_sql, iso8601_timestamp_expr, qualify_columns } from './sql_columns.js';
26
+ import { cell_moderation_admits_audience_sql, cell_moderation_is_stewardable_sql } from './cell_moderation.js';
27
+ import { can_view_cell_sql, caller_role_grants_cte, cell_grant_admits_caller_sql } from './cell_view_sql.js';
26
28
  /**
27
29
  * The full `cell` column set, named explicitly so a row read fails loud on
28
30
  * schema drift — a `SELECT *` silently omits a dropped column and the
@@ -69,6 +71,18 @@ const cell_expr = iso8601_timestamp_expr(CELL_COLUMNS, ['created_at', 'updated_a
69
71
  * actually produces one.
70
72
  */
71
73
  export const cell_row_projection = (alias) => `${qualify_columns(CELL_COLUMNS, alias, cell_expr(alias))}, (SELECT COUNT(*)::int FROM cell_grant WHERE cell_id = ${alias}.id) AS grant_count`;
74
+ /**
75
+ * The columns a view check reads (`can_view_cell`) — the `CELL_COLUMNS`
76
+ * subset `query_cell_view_load_many` projects, typed against `CELL_COLUMNS`
77
+ * so the base const's drift guard covers it. Twin of `fuz_cell`'s
78
+ * `CELL_VIEW_COLUMNS`.
79
+ */
80
+ export const CELL_VIEW_COLUMNS = [
81
+ 'id',
82
+ 'visibility',
83
+ 'moderation',
84
+ 'created_by'
85
+ ];
72
86
  /**
73
87
  * Insert a cell row, deriving `refs` from `data`.
74
88
  *
@@ -133,11 +147,10 @@ export const query_cell_get_by_path = async (deps, path) => {
133
147
  return row ?? null;
134
148
  };
135
149
  /**
136
- * Bulk-load active cell rows by id, **no visibility filter applied**. Used
137
- * by the strict relation-read filter (`auth/cell_relation_visibility.ts`'s
138
- * `filter_visible_target_ids`), which runs `can_view_cell` per row in
139
- * memory rather than in SQL. Soft-deleted rows are excluded so relations
140
- * to tombstones never surface.
150
+ * Bulk-load active cell rows by id, **no visibility filter applied** — the
151
+ * full rows, for a caller that copies or returns them (the deep-clone walk).
152
+ * A caller that needs only the view check reads `query_cell_view_load_many`.
153
+ * Soft-deleted rows are excluded so relations to tombstones never surface.
141
154
  *
142
155
  * @param deps - query deps
143
156
  * @param ids - cell ids to load (duplicates are harmless)
@@ -150,6 +163,24 @@ export const query_cell_load_many = async (deps, ids) => {
150
163
  FROM cell
151
164
  WHERE id = ANY($1::uuid[]) AND deleted_at IS NULL`, [ids]);
152
165
  };
166
+ /**
167
+ * Bulk-load the active cells among `ids` for a view check — the
168
+ * `CELL_VIEW_COLUMNS` alone, never the payload. Backs the strict
169
+ * relation-read filter (`auth/cell_relation_visibility.ts`'s
170
+ * `filter_visible_target_ids`), which runs `can_view_cell` per row in memory
171
+ * rather than in SQL. Soft-deleted rows and ids with no row are absent.
172
+ *
173
+ * @param deps - query deps
174
+ * @param ids - cell ids to load (duplicates are harmless)
175
+ * @returns active rows in arbitrary order (caller indexes by `id`)
176
+ */
177
+ export const query_cell_view_load_many = async (deps, ids) => {
178
+ if (ids.length === 0)
179
+ return [];
180
+ return deps.db.query(`SELECT ${columns_sql(CELL_VIEW_COLUMNS)}
181
+ FROM cell
182
+ WHERE id = ANY($1::uuid[]) AND deleted_at IS NULL`, [ids]);
183
+ };
153
184
  /**
154
185
  * Update a cell. Fields left `undefined` in the patch keep their existing
155
186
  * value; explicit `null` writes `NULL`. `refs` is re-derived from `data`
@@ -274,15 +305,24 @@ export const query_cell_list_by_creator = async (deps, actor_id, options) => dep
274
305
  * dimension — keep it simple) plus an optional viewer-aware visibility
275
306
  * predicate.
276
307
  *
277
- * The visibility predicate mirrors `can_view_cell` in SQL form:
308
+ * The visibility predicate mirrors `can_view_cell || can_steward_cell` in SQL
309
+ * form:
278
310
  *
279
311
  * ```
280
312
  * (viewer_is_admin
281
- * OR cell.visibility = 'public'
282
313
  * OR (viewer_actor_id IS NOT NULL AND created_by = viewer_actor_id)
283
- * OR (viewer_actor_id IS NOT NULL AND <grant admits caller>))
314
+ * OR (<moderation admits an audience> AND (
315
+ * cell.visibility = 'public'
316
+ * OR (viewer_actor_id IS NOT NULL AND <grant admits caller>)))
317
+ * OR (viewer_actor_id IS NOT NULL AND <pending or rejected contribution>
318
+ * AND <viewer owns its live governing root>))
284
319
  * ```
285
320
  *
321
+ * The moderation rules are `cell_moderation_admits_audience_sql` and
322
+ * `cell_moderation_is_stewardable_sql` (`db/cell_moderation.ts`). The last arm
323
+ * is the steward arm (`auth/cell_steward.ts`) — a root owner reads the
324
+ * moderation queue under their root; a global admin is already admitted.
325
+ *
286
326
  * The grants branch closes parity with `can_view_cell`: a SQL `EXISTS`
287
327
  * over `cell_grant`, parameterized by the caller's `actor_id` and the
288
328
  * parallel `(role[], scope_id[])` projection of `auth.role_grants`. The
@@ -304,7 +344,9 @@ export const query_cell_list_by_creator = async (deps, actor_id, options) => dep
304
344
  * owner-excluding, but the explicit `created_by IS DISTINCT FROM caller`
305
345
  * guards against any future deviation. The shared_with branch does NOT
306
346
  * bypass for admin: an admin asking "what's shared with me" wants their
307
- * own grant footprint, not every cell.
347
+ * own grant footprint, not every cell. A grant admits there only while the
348
+ * cell's moderation admits an audience, as in `can_view_cell`, and the steward
349
+ * arm doesn't apply — a grant footprint is not a moderation queue.
308
350
  *
309
351
  * Soft-deleted rows are excluded by default; opt-in via `include_deleted`.
310
352
  *
@@ -369,36 +411,17 @@ export const query_cell_list = async (deps, params) => {
369
411
  ]);
370
412
  };
371
413
  /**
372
- * The `cell_grant` admits-caller predicate, factored once and reused by
373
- * both SQL shapes (general visibility branch + shared-with-me semi-join).
374
- *
375
- * Resolves true when the grant row at `g_alias` admits the caller via
376
- * either an actor-shaped principal (`g.actor_id = $11`) or a
377
- * role-shaped principal whose `(role, scope_id)` matches a row in the
378
- * `caller_role_grants` CTE. NULL `g.scope_id` matches any scope, mirroring
379
- * `grant_admits` in `auth/cell_authorize.ts`.
414
+ * The caller's `(role, scope_id)` role_grant pairs, materialized once per
415
+ * query so the role-grant predicate doesn't re-scan `unnest()` per outer row.
416
+ * Shared by both SQL shapes.
380
417
  */
381
- const grant_admits_caller_predicate = (g_alias) => `(
382
- ($11::uuid IS NOT NULL AND ${g_alias}.actor_id = $11)
383
- OR (
384
- ${g_alias}.role IS NOT NULL
385
- AND EXISTS (
386
- SELECT 1 FROM caller_role_grants p
387
- WHERE p.role = ${g_alias}.role
388
- AND (${g_alias}.scope_id IS NULL OR p.scope_id IS NOT DISTINCT FROM ${g_alias}.scope_id)
389
- )
390
- )
391
- )`;
418
+ const CALLER_ROLE_GRANTS_CTE = `WITH ${caller_role_grants_cte('$12', '$13')}`;
392
419
  /**
393
- * Materialize the caller's `(role, scope_id)` role_grant pairs once per
394
- * query so the role-grant predicate doesn't re-scan `unnest()` per
395
- * outer row. PG 12+ inlines non-recursive single-use CTEs, so this is
396
- * planner-equivalent to a subquery — the form is for clarity.
420
+ * General `cell_list` SQL: cell-driven scan, full visibility predicate — the
421
+ * `can_view_cell` arms (`can_view_cell_sql`), then the steward arm
422
+ * (`can_steward_cell`: the caller owns the live governing root of a pending or
423
+ * rejected contribution; a global admin is already in through `$6`).
397
424
  */
398
- const CALLER_ROLE_GRANTS_CTE = `WITH caller_role_grants AS (
399
- SELECT role, scope_id FROM unnest($12::text[], $13::uuid[]) AS p(role, scope_id)
400
- )`;
401
- /** General `cell_list` SQL: cell-driven scan, full visibility predicate. */
402
425
  const build_general_sql = (order_column, order_direction) => `${CALLER_ROLE_GRANTS_CTE}
403
426
  SELECT ${cell_row_projection('c')} FROM cell c
404
427
  WHERE ($1::bool OR c.deleted_at IS NULL)
@@ -411,13 +434,12 @@ const build_general_sql = (order_column, order_direction) => `${CALLER_ROLE_GRAN
411
434
  AND ($15::uuid IS NULL OR c.root_id = $15)
412
435
  AND ($16::text IS NULL OR c.moderation = $16::text)
413
436
  AND (
414
- $6::bool
415
- OR c.visibility = 'public'
416
- OR ($7::uuid IS NOT NULL AND c.created_by = $7)
417
- OR ($7::uuid IS NOT NULL AND EXISTS (
418
- SELECT 1 FROM cell_grant g
419
- WHERE g.cell_id = c.id AND ${grant_admits_caller_predicate('g')}
420
- ))
437
+ ${can_view_cell_sql('c', { is_admin: '$6', actor_id: '$7', grant_actor_id: '$11' })}
438
+ OR ($7::uuid IS NOT NULL AND c.root_id IS NOT NULL AND ${cell_moderation_is_stewardable_sql('c')}
439
+ AND EXISTS (
440
+ SELECT 1 FROM cell r
441
+ WHERE r.id = c.root_id AND r.deleted_at IS NULL AND r.created_by = $7
442
+ ))
421
443
  )
422
444
  ORDER BY c.${order_column} ${order_direction} NULLS LAST
423
445
  LIMIT $8 OFFSET $9`;
@@ -450,9 +472,10 @@ const build_shared_with_sql = (order_column, order_direction) => `${CALLER_ROLE_
450
472
  AND ($16::text IS NULL OR c.moderation = $16::text)
451
473
  AND $7::uuid IS NOT NULL
452
474
  AND c.created_by IS DISTINCT FROM $7
475
+ AND ${cell_moderation_admits_audience_sql('c')}
453
476
  AND c.id IN (
454
477
  SELECT g.cell_id FROM cell_grant g
455
- WHERE ${grant_admits_caller_predicate('g')}
478
+ WHERE ${cell_grant_admits_caller_sql('g', '$11')}
456
479
  )
457
480
  -- $6 (viewer_is_admin) intentionally not consulted: an admin
458
481
  -- asking "what's shared with me" wants their grant footprint, not
@@ -0,0 +1,102 @@
1
+ /**
2
+ * `can_view_cell` in SQL — the view predicate a query applies to a cell row
3
+ * before `LIMIT`, so a page holds only rows the caller may view.
4
+ *
5
+ * `can_view_cell_sql` is the one SQL form of the predicate: `query_cell_list`
6
+ * embeds it (adding the steward arm beside it), and the relation lists apply
7
+ * it to the cell at the other end of each edge. It binds the caller through a
8
+ * `CellViewer` (`auth/cell_authorize.ts`'s `cell_viewer_from_auth` builds one
9
+ * from a request context), and the role-shaped grant arm reads the
10
+ * `caller_role_grants` CTE `caller_role_grants_cte` defines. Twin of the Rust
11
+ * `fuz_cell::cell_view_sql`.
12
+ *
13
+ * @module
14
+ */
15
+ import type { Uuid } from '../primitive_schemas.js';
16
+ /**
17
+ * The caller a view predicate binds — what `can_view_cell` reads off the
18
+ * request context.
19
+ *
20
+ * `role_grant_roles` and `role_grant_scope_ids` are parallel, one entry per
21
+ * active role grant (`null` = a global grant): `unnest` pads the shorter with
22
+ * NULLs, and a NULL scope reads as a global grant, so `cell_viewer_params`
23
+ * refuses a mismatch.
24
+ */
25
+ export interface CellViewer {
26
+ /** The acting actor; `null` for an anonymous or account-grain caller. */
27
+ readonly actor_id: Uuid | null;
28
+ /** Whether the caller holds the global admin role — the arm that admits every cell. */
29
+ readonly is_admin: boolean;
30
+ /** The roles of the caller's active role grants, parallel to `role_grant_scope_ids`. */
31
+ readonly role_grant_roles: ReadonlyArray<string>;
32
+ /** The scopes of the caller's active role grants, parallel to `role_grant_roles`. */
33
+ readonly role_grant_scope_ids: ReadonlyArray<Uuid | null>;
34
+ }
35
+ /** An anonymous caller — admitted by the public arm alone. */
36
+ export declare const CELL_VIEWER_ANONYMOUS: CellViewer;
37
+ /**
38
+ * A server-side read that filters nothing — the admin arm admits every cell.
39
+ * For a caller that applies its own view check afterwards (the `cell_get`
40
+ * bundle) or acts for the server itself.
41
+ */
42
+ export declare const CELL_VIEWER_UNRESTRICTED: CellViewer;
43
+ /** Where a query binds the `CellViewer` facts `can_view_cell_sql` reads — each a `$N` placeholder. */
44
+ export interface CellViewerSqlParams {
45
+ /** The global-admin flag (`bool`). */
46
+ readonly is_admin: string;
47
+ /** The acting actor (`uuid`, NULL when anonymous) — the owner arm and the grant arm's guard. */
48
+ readonly actor_id: string;
49
+ /**
50
+ * The actor an actor-shaped grant matches, when a query binds it apart
51
+ * from `actor_id` (`query_cell_list` does). Defaults to `actor_id`.
52
+ */
53
+ readonly grant_actor_id?: string;
54
+ }
55
+ /**
56
+ * The viewer's bind values, in the order `[is_admin, actor_id, roles,
57
+ * scope_ids]`.
58
+ *
59
+ * @param viewer - the caller
60
+ * @returns the four bind values
61
+ * @throws {Error} when the role-grant arrays aren't parallel
62
+ */
63
+ export declare const cell_viewer_params: (viewer: CellViewer) => [boolean, Uuid | null, ReadonlyArray<string>, ReadonlyArray<Uuid | null>];
64
+ /**
65
+ * The `caller_role_grants` CTE body (no `WITH`): the caller's
66
+ * `(role, scope_id)` pairs, unnested once per query from the parallel arrays
67
+ * at placeholders `roles` and `scope_ids`. PG 12+ inlines a non-recursive
68
+ * single-use CTE, so this is planner-equivalent to a subquery — the form is
69
+ * for clarity.
70
+ *
71
+ * @param roles - the placeholder of the roles array (`text[]`)
72
+ * @param scope_ids - the placeholder of the scope-id array (`uuid[]`)
73
+ * @returns the CTE body
74
+ */
75
+ export declare const caller_role_grants_cte: (roles: string, scope_ids: string) => string;
76
+ /**
77
+ * The `cell_grant` admits-caller predicate over grant alias `g_alias`: an
78
+ * actor-shaped grant matches the actor at placeholder `actor_id`; a
79
+ * role-shaped grant matches a `(role, scope_id)` pair in the
80
+ * `caller_role_grants` CTE (a NULL grant `scope_id` matches any scope).
81
+ * Mirrors `grant_admits` in `auth/cell_authorize.ts`.
82
+ *
83
+ * @param g_alias - the `cell_grant` alias in the enclosing query
84
+ * @param actor_id - the placeholder of the caller's actor (`uuid`)
85
+ * @returns the SQL predicate
86
+ */
87
+ export declare const cell_grant_admits_caller_sql: (g_alias: string, actor_id: string) => string;
88
+ /**
89
+ * `can_view_cell` over the cell alias `c_alias`: admin, the owner, or — while
90
+ * the cell's `moderation` admits an audience — `public` or a `cell_grant`
91
+ * admitting the caller.
92
+ *
93
+ * Reads the caller at `params`; the grant arm needs the
94
+ * `caller_role_grants_cte` in scope. Liveness (`deleted_at IS NULL`) is the
95
+ * query's own clause, as `can_view_cell` reads a loaded live row.
96
+ *
97
+ * @param c_alias - the `cell` alias in the enclosing query
98
+ * @param params - where the query binds the caller
99
+ * @returns the SQL predicate (parenthesized)
100
+ */
101
+ export declare const can_view_cell_sql: (c_alias: string, params: CellViewerSqlParams) => string;
102
+ //# sourceMappingURL=cell_view_sql.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cell_view_sql.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/cell_view_sql.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAGpD;;;;;;;;GAQG;AACH,MAAM,WAAW,UAAU;IAC1B,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;IAC/B,uFAAuF;IACvF,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,wFAAwF;IACxF,QAAQ,CAAC,gBAAgB,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACjD,qFAAqF;IACrF,QAAQ,CAAC,oBAAoB,EAAE,aAAa,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;CAC1D;AAED,8DAA8D;AAC9D,eAAO,MAAM,qBAAqB,EAAE,UAKnC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,EAAE,UAKtC,CAAC;AAEF,sGAAsG;AACtG,MAAM,WAAW,mBAAmB;IACnC,sCAAsC;IACtC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,gGAAgG;IAChG,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CACjC;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,GAC9B,QAAQ,UAAU,KAChB,CAAC,OAAO,EAAE,IAAI,GAAG,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC,IAAI,GAAG,IAAI,CAAC,CAQ1E,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,sBAAsB,GAAI,OAAO,MAAM,EAAE,WAAW,MAAM,KAAG,MAGtE,CAAC;AAEL;;;;;;;;;;GAUG;AACH,eAAO,MAAM,4BAA4B,GAAI,SAAS,MAAM,EAAE,UAAU,MAAM,KAAG,MAU3E,CAAC;AAEP;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,iBAAiB,GAAI,SAAS,MAAM,EAAE,QAAQ,mBAAmB,KAAG,MAchF,CAAC"}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * `can_view_cell` in SQL — the view predicate a query applies to a cell row
3
+ * before `LIMIT`, so a page holds only rows the caller may view.
4
+ *
5
+ * `can_view_cell_sql` is the one SQL form of the predicate: `query_cell_list`
6
+ * embeds it (adding the steward arm beside it), and the relation lists apply
7
+ * it to the cell at the other end of each edge. It binds the caller through a
8
+ * `CellViewer` (`auth/cell_authorize.ts`'s `cell_viewer_from_auth` builds one
9
+ * from a request context), and the role-shaped grant arm reads the
10
+ * `caller_role_grants` CTE `caller_role_grants_cte` defines. Twin of the Rust
11
+ * `fuz_cell::cell_view_sql`.
12
+ *
13
+ * @module
14
+ */
15
+ import { cell_moderation_admits_audience_sql } from './cell_moderation.js';
16
+ /** An anonymous caller — admitted by the public arm alone. */
17
+ export const CELL_VIEWER_ANONYMOUS = {
18
+ actor_id: null,
19
+ is_admin: false,
20
+ role_grant_roles: [],
21
+ role_grant_scope_ids: []
22
+ };
23
+ /**
24
+ * A server-side read that filters nothing — the admin arm admits every cell.
25
+ * For a caller that applies its own view check afterwards (the `cell_get`
26
+ * bundle) or acts for the server itself.
27
+ */
28
+ export const CELL_VIEWER_UNRESTRICTED = {
29
+ actor_id: null,
30
+ is_admin: true,
31
+ role_grant_roles: [],
32
+ role_grant_scope_ids: []
33
+ };
34
+ /**
35
+ * The viewer's bind values, in the order `[is_admin, actor_id, roles,
36
+ * scope_ids]`.
37
+ *
38
+ * @param viewer - the caller
39
+ * @returns the four bind values
40
+ * @throws {Error} when the role-grant arrays aren't parallel
41
+ */
42
+ export const cell_viewer_params = (viewer) => {
43
+ if (viewer.role_grant_roles.length !== viewer.role_grant_scope_ids.length) {
44
+ throw new Error(`CellViewer: role_grant_roles (len=${viewer.role_grant_roles.length}) and ` +
45
+ `role_grant_scope_ids (len=${viewer.role_grant_scope_ids.length}) must be parallel arrays`);
46
+ }
47
+ return [viewer.is_admin, viewer.actor_id, viewer.role_grant_roles, viewer.role_grant_scope_ids];
48
+ };
49
+ /**
50
+ * The `caller_role_grants` CTE body (no `WITH`): the caller's
51
+ * `(role, scope_id)` pairs, unnested once per query from the parallel arrays
52
+ * at placeholders `roles` and `scope_ids`. PG 12+ inlines a non-recursive
53
+ * single-use CTE, so this is planner-equivalent to a subquery — the form is
54
+ * for clarity.
55
+ *
56
+ * @param roles - the placeholder of the roles array (`text[]`)
57
+ * @param scope_ids - the placeholder of the scope-id array (`uuid[]`)
58
+ * @returns the CTE body
59
+ */
60
+ export const caller_role_grants_cte = (roles, scope_ids) => `caller_role_grants AS (
61
+ SELECT role, scope_id FROM unnest(${roles}::text[], ${scope_ids}::uuid[]) AS p(role, scope_id)
62
+ )`;
63
+ /**
64
+ * The `cell_grant` admits-caller predicate over grant alias `g_alias`: an
65
+ * actor-shaped grant matches the actor at placeholder `actor_id`; a
66
+ * role-shaped grant matches a `(role, scope_id)` pair in the
67
+ * `caller_role_grants` CTE (a NULL grant `scope_id` matches any scope).
68
+ * Mirrors `grant_admits` in `auth/cell_authorize.ts`.
69
+ *
70
+ * @param g_alias - the `cell_grant` alias in the enclosing query
71
+ * @param actor_id - the placeholder of the caller's actor (`uuid`)
72
+ * @returns the SQL predicate
73
+ */
74
+ export const cell_grant_admits_caller_sql = (g_alias, actor_id) => `(
75
+ (${actor_id}::uuid IS NOT NULL AND ${g_alias}.actor_id = ${actor_id})
76
+ OR (
77
+ ${g_alias}.role IS NOT NULL
78
+ AND EXISTS (
79
+ SELECT 1 FROM caller_role_grants p
80
+ WHERE p.role = ${g_alias}.role
81
+ AND (${g_alias}.scope_id IS NULL OR p.scope_id IS NOT DISTINCT FROM ${g_alias}.scope_id)
82
+ )
83
+ )
84
+ )`;
85
+ /**
86
+ * `can_view_cell` over the cell alias `c_alias`: admin, the owner, or — while
87
+ * the cell's `moderation` admits an audience — `public` or a `cell_grant`
88
+ * admitting the caller.
89
+ *
90
+ * Reads the caller at `params`; the grant arm needs the
91
+ * `caller_role_grants_cte` in scope. Liveness (`deleted_at IS NULL`) is the
92
+ * query's own clause, as `can_view_cell` reads a loaded live row.
93
+ *
94
+ * @param c_alias - the `cell` alias in the enclosing query
95
+ * @param params - where the query binds the caller
96
+ * @returns the SQL predicate (parenthesized)
97
+ */
98
+ export const can_view_cell_sql = (c_alias, params) => {
99
+ const { is_admin, actor_id } = params;
100
+ const grant_actor_id = params.grant_actor_id ?? actor_id;
101
+ return `(
102
+ ${is_admin}::bool
103
+ OR (${actor_id}::uuid IS NOT NULL AND ${c_alias}.created_by = ${actor_id})
104
+ OR (${cell_moderation_admits_audience_sql(c_alias)} AND (
105
+ ${c_alias}.visibility = 'public'
106
+ OR (${actor_id}::uuid IS NOT NULL AND EXISTS (
107
+ SELECT 1 FROM cell_grant g
108
+ WHERE g.cell_id = ${c_alias}.id AND ${cell_grant_admits_caller_sql('g', grant_actor_id)}
109
+ ))
110
+ ))
111
+ )`;
112
+ };
@@ -18,7 +18,7 @@ import { type DbDriverResult } from './db.js';
18
18
  * call this, don't need `pg` installed) and coerces int8 (`BIGINT`, OID 20)
19
19
  * to a JS number. pg defaults to returning
20
20
  * int8 as a string to avoid 2^53 precision loss; our int8 columns today
21
- * (`audit_log.seq`, `cell_history.id`, `fact.size`) stay well under that
21
+ * (`audit_log.seq`, `fact.size`) stay well under that
22
22
  * bound, and reading as a number keeps the wire shape uniform with PGlite —
23
23
  * which returns int8 as a number — so `AuditLogEvent.seq` and friends
24
24
  * validate identically across both drivers.
package/dist/db/db_pg.js CHANGED
@@ -17,7 +17,7 @@ import { Db, no_nested_transaction } from './db.js';
17
17
  * call this, don't need `pg` installed) and coerces int8 (`BIGINT`, OID 20)
18
18
  * to a JS number. pg defaults to returning
19
19
  * int8 as a string to avoid 2^53 precision loss; our int8 columns today
20
- * (`audit_log.seq`, `cell_history.id`, `fact.size`) stay well under that
20
+ * (`audit_log.seq`, `fact.size`) stay well under that
21
21
  * bound, and reading as a number keeps the wire shape uniform with PGlite —
22
22
  * which returns int8 as a number — so `AuditLogEvent.seq` and friends
23
23
  * validate identically across both drivers.
@@ -57,6 +57,25 @@ export declare const TOKEN_SCOPE_KEY = "token_scope";
57
57
  * read it directly.
58
58
  */
59
59
  export declare const ACCOUNT_ID_KEY = "auth_account_id";
60
+ /**
61
+ * Hono context variable name for the credential's deadline — when it stops
62
+ * authenticating, as a monotonic `performance.now()` timestamp
63
+ * (`auth/credential_deadline.ts`), or `null` for one with no expiry (a daemon
64
+ * token, an eternal API token) and for an unauthenticated request.
65
+ *
66
+ * Set by every auth middleware that resolves a credential: session (the
67
+ * earlier of the signed cookie's expiry and the row's), bearer (the row's),
68
+ * daemon token (`null`). A long-lived connection opened on the credential
69
+ * closes at it (`ResolvedAuth.credential_deadline`).
70
+ */
71
+ export declare const CREDENTIAL_DEADLINE_KEY = "credential_deadline";
72
+ /**
73
+ * Hono context variable name for the session cookie's signed expiry, in
74
+ * seconds since the Unix epoch — set by the session middleware on a cookie
75
+ * that verified and has not expired, `null` otherwise. The request-context
76
+ * middleware folds it into `CREDENTIAL_DEADLINE_KEY`.
77
+ */
78
+ export declare const SESSION_COOKIE_EXPIRES_AT_KEY = "auth_session_cookie_expires_at";
60
79
  /**
61
80
  * Hono context variable name for the test-harness pre-baked context flag.
62
81
  *
@@ -112,6 +131,10 @@ declare module 'hono' {
112
131
  * up the token.
113
132
  */
114
133
  auth_api_token_id: string | null;
134
+ /** The credential's deadline (`CREDENTIAL_DEADLINE_KEY`). */
135
+ credential_deadline: number | null;
136
+ /** The session cookie's signed expiry, epoch seconds (`SESSION_COOKIE_EXPIRES_AT_KEY`). */
137
+ auth_session_cookie_expires_at: number | null;
115
138
  /**
116
139
  * Eager fire-and-forget pool writes for this request — audit emits,
117
140
  * session-touch UPDATE, api-token usage tracking. Producers push the
@@ -1 +1 @@
1
- {"version":3,"file":"hono_context.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/hono_context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAOhE;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,mDAInB,CAAC;AAEX,yDAAyD;AACzD,eAAO,MAAM,cAAc;;;;EAA2B,CAAC;AACvD,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,0DAA0D;AAC1D,eAAO,MAAM,mBAAmB,oBAAoB,CAAC;AAErD,qEAAqE;AACrE,eAAO,MAAM,qBAAqB,sBAAsB,CAAC;AAEzD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe,gBAAgB,CAAC;AAE7C;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,oBAAoB,CAAC;AAEhD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,uBAAuB,wBAAwB,CAAC;AAE7D,OAAO,QAAQ,MAAM,CAAC;IACrB,UAAU,kBAAkB;QAC3B,+DAA+D;QAC/D,SAAS,EAAE,MAAM,CAAC;QAClB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;QAC/B,eAAe,EAAE,cAAc,GAAG,IAAI,CAAC;QACvC,eAAe,EAAE,OAAO,CAAC;QACzB,gBAAgB,EAAE,OAAO,CAAC;QAC1B,eAAe,EAAE,OAAO,CAAC;QACzB,2FAA2F;QAC3F,eAAe,EAAE,cAAc,GAAG,IAAI,CAAC;QACvC;;;;;;WAMG;QACH,WAAW,EAAE,UAAU,GAAG,SAAS,CAAC;QACpC;;;;;WAKG;QACH,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;QAC/B;;;;;WAKG;QACH,uBAAuB,EAAE,MAAM,GAAG,IAAI,CAAC;QACvC;;;;;;WAMG;QACH,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;QACjC;;;;;;;WAOG;QACH,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACtC;;;;;;;;;;;;WAYG;QACH,mBAAmB,EAAE,KAAK,CAAC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACvD;;;;WAIG;QACH,mBAAmB,EAAE,OAAO,CAAC;KAC7B;CACD"}
1
+ {"version":3,"file":"hono_context.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/hono_context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAOhE;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,mDAInB,CAAC;AAEX,yDAAyD;AACzD,eAAO,MAAM,cAAc;;;;EAA2B,CAAC;AACvD,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,0DAA0D;AAC1D,eAAO,MAAM,mBAAmB,oBAAoB,CAAC;AAErD,qEAAqE;AACrE,eAAO,MAAM,qBAAqB,sBAAsB,CAAC;AAEzD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe,gBAAgB,CAAC;AAE7C;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,oBAAoB,CAAC;AAEhD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,uBAAuB,wBAAwB,CAAC;AAE7D;;;;;GAKG;AACH,eAAO,MAAM,6BAA6B,mCAAmC,CAAC;AAE9E;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,uBAAuB,wBAAwB,CAAC;AAE7D,OAAO,QAAQ,MAAM,CAAC;IACrB,UAAU,kBAAkB;QAC3B,+DAA+D;QAC/D,SAAS,EAAE,MAAM,CAAC;QAClB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;QAC/B,eAAe,EAAE,cAAc,GAAG,IAAI,CAAC;QACvC,eAAe,EAAE,OAAO,CAAC;QACzB,gBAAgB,EAAE,OAAO,CAAC;QAC1B,eAAe,EAAE,OAAO,CAAC;QACzB,2FAA2F;QAC3F,eAAe,EAAE,cAAc,GAAG,IAAI,CAAC;QACvC;;;;;;WAMG;QACH,WAAW,EAAE,UAAU,GAAG,SAAS,CAAC;QACpC;;;;;WAKG;QACH,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;QAC/B;;;;;WAKG;QACH,uBAAuB,EAAE,MAAM,GAAG,IAAI,CAAC;QACvC;;;;;;WAMG;QACH,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;QACjC,6DAA6D;QAC7D,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;QACnC,2FAA2F;QAC3F,8BAA8B,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9C;;;;;;;WAOG;QACH,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACtC;;;;;;;;;;;;WAYG;QACH,mBAAmB,EAAE,KAAK,CAAC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACvD;;;;WAIG;QACH,mBAAmB,EAAE,OAAO,CAAC;KAC7B;CACD"}
@@ -55,6 +55,25 @@ export const TOKEN_SCOPE_KEY = 'token_scope';
55
55
  * read it directly.
56
56
  */
57
57
  export const ACCOUNT_ID_KEY = 'auth_account_id';
58
+ /**
59
+ * Hono context variable name for the credential's deadline — when it stops
60
+ * authenticating, as a monotonic `performance.now()` timestamp
61
+ * (`auth/credential_deadline.ts`), or `null` for one with no expiry (a daemon
62
+ * token, an eternal API token) and for an unauthenticated request.
63
+ *
64
+ * Set by every auth middleware that resolves a credential: session (the
65
+ * earlier of the signed cookie's expiry and the row's), bearer (the row's),
66
+ * daemon token (`null`). A long-lived connection opened on the credential
67
+ * closes at it (`ResolvedAuth.credential_deadline`).
68
+ */
69
+ export const CREDENTIAL_DEADLINE_KEY = 'credential_deadline';
70
+ /**
71
+ * Hono context variable name for the session cookie's signed expiry, in
72
+ * seconds since the Unix epoch — set by the session middleware on a cookie
73
+ * that verified and has not expired, `null` otherwise. The request-context
74
+ * middleware folds it into `CREDENTIAL_DEADLINE_KEY`.
75
+ */
76
+ export const SESSION_COOKIE_EXPIRES_AT_KEY = 'auth_session_cookie_expires_at';
58
77
  /**
59
78
  * Hono context variable name for the test-harness pre-baked context flag.
60
79
  *
@@ -25,7 +25,7 @@ effects, see ../../../docs/architecture.md.
25
25
  - `http/proxy.ts` — trusted-proxy middleware, CIDR parsing, rightmost-first XFF resolution. Pulls `hono/utils/ipaddr` for CIDR matching.
26
26
  - `http/client_ip.ts` — `get_client_ip(c)` — reads the resolved IP off the context (set by `proxy.ts`'s middleware). Split out of `proxy.ts` so it's hono-free: dispatch + route modules that only _read_ the IP don't pull `hono/utils/ipaddr`, keeping cross-process test surfaces free of the optional `hono` peer.
27
27
  - `http/ip_canonical.ts` — RFC 5952 IPv6 canonicalization + IPv4-mapped collapse; `IP_LITERAL_CHARS` regex.
28
- - `http/origin.ts` — origin allowlist middleware with wildcard patterns (Origin-only).
28
+ - `http/origin.ts` — origin allowlist: the strict pattern grammar shared with the Rust spine, typed refusals, and the Origin-only middleware.
29
29
  - `http/jsonrpc.ts` — JSON-RPC 2.0 envelope schemas (MCP superset), `JsonrpcErrorCode`, `_meta`.
30
30
  - `http/jsonrpc_errors.ts` — `ThrownJsonrpcError`, `jsonrpc_errors` throwers, HTTP-status mappings.
31
31
  - `http/jsonrpc_helpers.ts` — message builders, type guards, input/result normalizers.
@@ -51,6 +51,8 @@ consumers.
51
51
  - `query?: z.ZodObject` — strict-object schema for URL query string
52
52
  - `input: z.ZodType` — request body schema; `z.null()` for no-body (GET/DELETE)
53
53
  - `output: z.ZodType` — success response schema
54
+ - `mints_credential?: boolean` — the route issues a session cookie while declaring no auth (login, signup, bootstrap); assembly refuses it outside `AUTH_MIDDLEWARE_PATH`, as it does any route whose `auth.account` is not `'none'` (`assert_credential_routes_in_auth_scope` in `auth/middleware.ts`)
55
+ - `reads_credential?: boolean` — the handler reads the resolved account while the route declares no auth gate, answering an anonymous caller itself (account status, the cell-scoped fact read, `create_rpc_endpoint`'s pair, whose dispatcher gates per action); held inside `AUTH_MIDDLEWARE_PATH` by the same assertion
54
56
  - `rate_limit?: RateLimitKey` — metadata only (`'ip' | 'account' | 'both'`); auto-derives 429
55
57
  - `errors?: RouteErrorSchemas` — handler-specific error schemas keyed by HTTP status
56
58
  - `transaction?: boolean` — declarative transaction wrapping (see below)
@@ -237,6 +239,7 @@ Key helpers:
237
239
  - `is_strict_object_schema(schema)` — detects `z.strictObject()` by checking `schema.def.catchall instanceof z.ZodNever`
238
240
  - `schema_to_surface(schema)` — Zod → JSON Schema, with `$schema` and `default` keys stripped recursively (defaults may be non-deterministic; `$schema` is snapshot noise)
239
241
  - `middleware_applies(mw_path, route_path)` — Hono pattern matching: `'*'`, exact, `'/api/*'` prefix (handles `prefix.slice(0, -1)` so `/api/*` also matches the bare `/api`)
242
+ - `middleware_spec_applies(spec, route_path)` — the layer runs on the route: its `path` applies and none of its `carve_outs` does; what surface generation merges middleware errors by
240
243
  - `merge_error_schemas(spec, middleware_errors?)` — the three-layer merge described above
241
244
 
242
245
  ## Surface Generation
@@ -327,9 +330,10 @@ operator configuration in practice.
327
330
  Origin allowlisting for locally-running services — **not** the CSRF layer.
328
331
  CSRF is handled by `SameSite: strict` on session cookies (`auth/session_middleware.ts`).
329
332
 
330
- - `parse_allowed_origins(env_value)` — comma-separated patterns → `Array<RegExp>`
331
- - `should_allow_origin(origin, patterns)` — case-insensitive match
332
- - `verify_request_source(allowed_patterns)` — Hono handler: `Origin` present → must match allowlist or 403 `ERROR_FORBIDDEN_ORIGIN`; no `Origin` → allow through (curl, CLI, token auth is primary control)
333
+ - `parse_allowed_origins(raw)` — comma-separated entries → `Result<{origins: Array<RegExp>}, {errors: Array<AllowedOriginsError>}>`; every refused entry is reported (`{kind, index, entry}`, 1-based), a blank value is `{kind: 'no_origins'}`
334
+ - `OriginPatternErrorKind` / `AllowedOriginsErrorKind` — the refusal kinds, wire names shared with the Rust spine; `ALLOWED_ORIGINS_ERROR_MESSAGES` the reason per kind, `format_allowed_origins_error` one refusal as a boot-error line (entry quoted and escaped)
335
+ - `should_allow_origin(origin, patterns)` — case-insensitive match; a value that isn't visible ASCII matches nothing
336
+ - `verify_request_source(allowed_patterns, carve_outs?)` — Hono handler: `Origin` present → must match allowlist or 403 `ERROR_FORBIDDEN_ORIGIN`; no `Origin` → allow through (curl, CLI, token auth is primary control). A repeated `Origin` reaches it joined (`a, b`) and is refused. A request whose path a `carve_outs` entry (`MiddlewareCarveOut`, `{path, reason}`) matches passes unchecked
333
337
  - `is_browser_context(c)` — true when `Origin` or `Referer` is present (`!== undefined`, so empty-string counts). The bearer + daemon-token middleware discard their credential when true; twin of the Rust spine's `bearer_auth::is_browser_context`. Distinct from `verify_request_source` (allowlist gate) — this only detects browser context
334
338
 
335
339
  **Origin-only by design.** Fetch spec mandates `Origin` on every unsafe
@@ -337,17 +341,39 @@ method, so a real browser request on any state-changing surface always
337
341
  carries it. Non-browser clients don't ship auto-attached session cookies,
338
342
  so CSRF isn't the relevant threat there — auth (bearer / daemon token) is
339
343
  the actual control. A `Referer` fallback would only widen the
340
- accepted-shape envelope without closing a real CSRF hole. Mirrors
341
- `zzz_server::auth::is_request_origin_allowed`.
342
-
343
- Pattern syntax: exact `https://api.fuz.dev`; wildcard subdomain
344
- `https://*.fuz.dev` (matches `api.fuz.dev`, NOT `fuz.dev`); multiple
345
- wildcards `https://*.*.corp.fuz.dev`; port wildcard `http://localhost:*`
346
- (optional port); IPv6 `http://[::1]:3000`, `https://[2001:db8::1]` (no
347
- wildcards inside brackets). Patterns normalize through `URL` constructor.
348
- IPv6 zone identifiers (`%eth0`) not supported. Throws on paths, partial
349
- wildcards (`*fuz.dev`), wildcards inside IPv6 brackets, or missing
350
- protocol.
344
+ accepted-shape envelope without closing a real CSRF hole. The Rust
345
+ spine's `origin_layer` is the twin.
346
+
347
+ **Every path by default.** `create_app_server` mounts the check at `'*'`
348
+ (`ORIGIN_MIDDLEWARE_PATH`), the static fallback included, so a foreign page is
349
+ refused wherever it points — the Rust spine's `fuz_http::compose_stack` checks
350
+ every route group and its fallback the same way. Probes and CLI clients send no
351
+ `Origin` and pass. A path leaves the check only by a named carve-out with its
352
+ reason (`AppServerOptions.origin_carve_outs`), for a route that refuses browser
353
+ callers by other means; one overlapping the auth middleware's scope
354
+ (`AUTH_MIDDLEWARE_PATH`, where every route that reads or mints a credential
355
+ sits) is refused at assembly. A carve-out skips every path it matches, routed
356
+ or not, so an unrouted path under it reaches the static fallback unchecked;
357
+ the Rust stack checks unrouted paths in its fallback group whatever a
358
+ carve-out names.
359
+
360
+ **Grammar** (shared with the Rust spine's `AllowedOrigins::parse`, pinned by
361
+ `testing/cross_backend/origin_conformance.json`): entries are trimmed of ASCII
362
+ whitespace and written as a browser writes an `Origin` — exact
363
+ `https://api.fuz.dev`; wildcard label `https://*.fuz.dev` (one label in any
364
+ position — matches `api.fuz.dev`, NOT `fuz.dev` or `a.b.fuz.dev`); several
365
+ `https://*.*.corp.fuz.dev`; port wildcard `http://localhost:*` (any port or
366
+ none); IPv6 `http://[::1]:3000` (exact, no wildcard inside the brackets).
367
+ **Nothing is normalized** — the matcher is built from the entry as written, so
368
+ an entry a browser would never send is refused rather than rewritten: non-ASCII
369
+ (punycode an IDN host), a scheme other than http/https (`null`, `file://`,
370
+ `chrome-extension://`), a path/query/fragment/trailing slash, userinfo, a
371
+ character no hostname holds (`%`, `\`, `^`, `|`, `<`, `>`, `[`, `]`), a partial
372
+ wildcard, an IPv4 address not written as four canonical decimal numbers, an IPv6
373
+ address not in the browser's form (the URL parser serves as the serializer and
374
+ any difference refuses), a port outside `1..=65535` or with leading zeros, the
375
+ scheme's default port, and an empty entry. The checks run in a fixed order so
376
+ both spines name the same kind for the same entry.
351
377
 
352
378
  ## JSON-RPC (`http/jsonrpc.ts`, `http/jsonrpc_errors.ts`, `http/jsonrpc_helpers.ts`)
353
379
 
@@ -68,9 +68,12 @@ export type ActingActor = z.infer<typeof ActingActor>;
68
68
  * instead would put an input-shape complaint ahead of the authority gates,
69
69
  * which is the ordering this pipeline exists to avoid.
70
70
  *
71
- * Both callers pass through here so that rule has one home: the action
71
+ * The dispatchers pass through here so that rule has one home: the action
72
72
  * dispatcher's `read_acting` and the REST authorization handler's
73
- * `read_route_acting`. Twin of the Rust dispatcher's `read_acting`.
73
+ * `read_route_acting`. Twin of the Rust dispatcher's `read_acting`. The status
74
+ * route and the WS upgrade read the raw `?acting=` through it too; neither has
75
+ * an input schema behind it, so there a malformed selector is final (the
76
+ * status route answers account-only).
74
77
  *
75
78
  * @param value - the raw `acting` field, from params / query / body
76
79
  * @returns the actor id, or `undefined` when absent or malformed
@@ -1 +1 @@
1
- {"version":3,"file":"auth_shape.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/auth_shape.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,WAAW,6DAGtB,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,OAAO,KAAG,MAAM,GAAG,SAGtD,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,aAAa;;;;EAA2C,CAAC;AACtE,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE1D;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;kBAyEnB,CAAC;AACJ,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC;AAUlD;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAAI,MAAM,SAAS,KAAG,OACA,CAAC;AAElD;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,GAAI,MAAM,SAAS,KAAG,OAAgC,CAAC;AAE/E;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAI,MAAM,SAAS,KAAG,OAAkC,CAAC;AAEnF,wEAAwE;AACxE,eAAO,MAAM,YAAY,GAAI,MAAM,SAAS,KAAG,OAA+B,CAAC;AAE/E,8FAA8F;AAC9F,eAAO,MAAM,wBAAwB,GAAI,MAAM,SAAS,KAAG,OAC3B,CAAC;AAEjC;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,cAAc,GAAI,MAAM,SAAS,KAAG,OACQ,CAAC;AAE1D;;;;;;;;;;GAUG;AACH,eAAO,MAAM,2BAA2B,GAAI,MAAM,SAAS,KAAG,OACwB,CAAC;AAYvF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,4BAA4B,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,OAIhE,CAAC;AAEF;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC3B,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,sCAAsC,GAClD,MAAM,SAAS,EACf,OAAO,WAAW,EAClB,SAAS,MAAM,KACb,IAmBF,CAAC"}
1
+ {"version":3,"file":"auth_shape.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/auth_shape.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,WAAW,6DAGtB,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,OAAO,KAAG,MAAM,GAAG,SAGtD,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,aAAa;;;;EAA2C,CAAC;AACtE,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE1D;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;kBAyEnB,CAAC;AACJ,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC;AAUlD;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAAI,MAAM,SAAS,KAAG,OACA,CAAC;AAElD;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,GAAI,MAAM,SAAS,KAAG,OAAgC,CAAC;AAE/E;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAI,MAAM,SAAS,KAAG,OAAkC,CAAC;AAEnF,wEAAwE;AACxE,eAAO,MAAM,YAAY,GAAI,MAAM,SAAS,KAAG,OAA+B,CAAC;AAE/E,8FAA8F;AAC9F,eAAO,MAAM,wBAAwB,GAAI,MAAM,SAAS,KAAG,OAC3B,CAAC;AAEjC;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,cAAc,GAAI,MAAM,SAAS,KAAG,OACQ,CAAC;AAE1D;;;;;;;;;;GAUG;AACH,eAAO,MAAM,2BAA2B,GAAI,MAAM,SAAS,KAAG,OACwB,CAAC;AAYvF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,4BAA4B,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,OAIhE,CAAC;AAEF;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC3B,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,sCAAsC,GAClD,MAAM,SAAS,EACf,OAAO,WAAW,EAClB,SAAS,MAAM,KACb,IAmBF,CAAC"}
@@ -71,9 +71,12 @@ export const ActingActor = Uuid.optional().meta({
71
71
  * instead would put an input-shape complaint ahead of the authority gates,
72
72
  * which is the ordering this pipeline exists to avoid.
73
73
  *
74
- * Both callers pass through here so that rule has one home: the action
74
+ * The dispatchers pass through here so that rule has one home: the action
75
75
  * dispatcher's `read_acting` and the REST authorization handler's
76
- * `read_route_acting`. Twin of the Rust dispatcher's `read_acting`.
76
+ * `read_route_acting`. Twin of the Rust dispatcher's `read_acting`. The status
77
+ * route and the WS upgrade read the raw `?acting=` through it too; neither has
78
+ * an input schema behind it, so there a malformed selector is final (the
79
+ * status route answers account-only).
77
80
  *
78
81
  * @param value - the raw `acting` field, from params / query / body
79
82
  * @returns the actor id, or `undefined` when absent or malformed