@fuzdev/fuz_app 0.141.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 (147) hide show
  1. package/dist/actions/CLAUDE.md +10 -5
  2. package/dist/actions/connection_closer.d.ts +19 -2
  3. package/dist/actions/connection_closer.d.ts.map +1 -1
  4. package/dist/actions/connection_closer.js +25 -2
  5. package/dist/actions/perform_action.d.ts +10 -0
  6. package/dist/actions/perform_action.d.ts.map +1 -1
  7. package/dist/actions/perform_action.js +23 -9
  8. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  9. package/dist/actions/register_ws_endpoint.js +5 -2
  10. package/dist/auth/CLAUDE.md +33 -13
  11. package/dist/auth/account_actions.d.ts.map +1 -1
  12. package/dist/auth/account_actions.js +12 -1
  13. package/dist/auth/account_route_schema.d.ts.map +1 -1
  14. package/dist/auth/account_route_schema.js +8 -0
  15. package/dist/auth/account_routes.d.ts +7 -2
  16. package/dist/auth/account_routes.d.ts.map +1 -1
  17. package/dist/auth/account_routes.js +240 -187
  18. package/dist/auth/admin_action_specs.d.ts +6 -1
  19. package/dist/auth/admin_action_specs.d.ts.map +1 -1
  20. package/dist/auth/admin_action_specs.js +6 -1
  21. package/dist/auth/admin_actions.d.ts.map +1 -1
  22. package/dist/auth/admin_actions.js +14 -2
  23. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  24. package/dist/auth/bootstrap_routes.js +85 -76
  25. package/dist/auth/cell_action_specs.d.ts +7 -3
  26. package/dist/auth/cell_action_specs.d.ts.map +1 -1
  27. package/dist/auth/cell_action_specs.js +9 -5
  28. package/dist/auth/cell_actions.d.ts +3 -3
  29. package/dist/auth/cell_actions.d.ts.map +1 -1
  30. package/dist/auth/cell_actions.js +67 -38
  31. package/dist/auth/cell_authorize.d.ts +69 -8
  32. package/dist/auth/cell_authorize.d.ts.map +1 -1
  33. package/dist/auth/cell_authorize.js +89 -9
  34. package/dist/auth/cell_field_action_specs.d.ts +5 -4
  35. package/dist/auth/cell_field_action_specs.d.ts.map +1 -1
  36. package/dist/auth/cell_field_action_specs.js +7 -7
  37. package/dist/auth/cell_field_actions.d.ts +7 -5
  38. package/dist/auth/cell_field_actions.d.ts.map +1 -1
  39. package/dist/auth/cell_field_actions.js +24 -22
  40. package/dist/auth/cell_grant_action_specs.d.ts +6 -0
  41. package/dist/auth/cell_grant_action_specs.d.ts.map +1 -1
  42. package/dist/auth/cell_grant_action_specs.js +6 -0
  43. package/dist/auth/cell_grant_actions.d.ts +3 -1
  44. package/dist/auth/cell_grant_actions.d.ts.map +1 -1
  45. package/dist/auth/cell_grant_actions.js +16 -2
  46. package/dist/auth/cell_item_action_specs.d.ts +5 -4
  47. package/dist/auth/cell_item_action_specs.d.ts.map +1 -1
  48. package/dist/auth/cell_item_action_specs.js +7 -7
  49. package/dist/auth/cell_item_actions.d.ts +6 -5
  50. package/dist/auth/cell_item_actions.d.ts.map +1 -1
  51. package/dist/auth/cell_item_actions.js +23 -22
  52. package/dist/auth/cell_relation_list_limit.d.ts +20 -0
  53. package/dist/auth/cell_relation_list_limit.d.ts.map +1 -0
  54. package/dist/auth/cell_relation_list_limit.js +27 -0
  55. package/dist/auth/cell_relation_visibility.d.ts +4 -3
  56. package/dist/auth/cell_relation_visibility.d.ts.map +1 -1
  57. package/dist/auth/cell_relation_visibility.js +6 -5
  58. package/dist/auth/cell_steward.d.ts +45 -0
  59. package/dist/auth/cell_steward.d.ts.map +1 -0
  60. package/dist/auth/cell_steward.js +53 -0
  61. package/dist/auth/request_context.d.ts +4 -0
  62. package/dist/auth/request_context.d.ts.map +1 -1
  63. package/dist/auth/request_context.js +6 -1
  64. package/dist/auth/role_grant_offer_action_specs.d.ts +5 -0
  65. package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
  66. package/dist/auth/role_grant_offer_action_specs.js +6 -1
  67. package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
  68. package/dist/auth/role_grant_offer_actions.js +23 -16
  69. package/dist/auth/role_grant_offer_queries.d.ts +8 -6
  70. package/dist/auth/role_grant_offer_queries.d.ts.map +1 -1
  71. package/dist/auth/role_grant_offer_queries.js +16 -8
  72. package/dist/auth/signup_routes.d.ts +4 -3
  73. package/dist/auth/signup_routes.d.ts.map +1 -1
  74. package/dist/auth/signup_routes.js +157 -134
  75. package/dist/auth/token_scope.d.ts +7 -1
  76. package/dist/auth/token_scope.d.ts.map +1 -1
  77. package/dist/auth/token_scope.js +8 -2
  78. package/dist/db/CLAUDE.md +32 -19
  79. package/dist/db/cell_ddl.d.ts +1 -2
  80. package/dist/db/cell_ddl.d.ts.map +1 -1
  81. package/dist/db/cell_ddl.js +1 -2
  82. package/dist/db/cell_field_queries.d.ts +20 -18
  83. package/dist/db/cell_field_queries.d.ts.map +1 -1
  84. package/dist/db/cell_field_queries.js +28 -22
  85. package/dist/db/cell_item_queries.d.ts +20 -14
  86. package/dist/db/cell_item_queries.d.ts.map +1 -1
  87. package/dist/db/cell_item_queries.js +28 -18
  88. package/dist/db/cell_moderation.d.ts +67 -0
  89. package/dist/db/cell_moderation.d.ts.map +1 -0
  90. package/dist/db/cell_moderation.js +66 -0
  91. package/dist/db/cell_queries.d.ts +44 -14
  92. package/dist/db/cell_queries.d.ts.map +1 -1
  93. package/dist/db/cell_queries.js +68 -45
  94. package/dist/db/cell_view_sql.d.ts +102 -0
  95. package/dist/db/cell_view_sql.d.ts.map +1 -0
  96. package/dist/db/cell_view_sql.js +112 -0
  97. package/dist/db/db_pg.d.ts +1 -1
  98. package/dist/db/db_pg.js +1 -1
  99. package/dist/http/auth_shape.d.ts +5 -2
  100. package/dist/http/auth_shape.d.ts.map +1 -1
  101. package/dist/http/auth_shape.js +5 -2
  102. package/dist/http/ip_canonical.d.ts +30 -3
  103. package/dist/http/ip_canonical.d.ts.map +1 -1
  104. package/dist/http/ip_canonical.js +50 -3
  105. package/dist/primitive_schemas.d.ts +10 -1
  106. package/dist/primitive_schemas.d.ts.map +1 -1
  107. package/dist/primitive_schemas.js +11 -2
  108. package/dist/rate_limiter.d.ts +179 -12
  109. package/dist/rate_limiter.d.ts.map +1 -1
  110. package/dist/rate_limiter.js +352 -42
  111. package/dist/server/serve_fact_route.d.ts +8 -4
  112. package/dist/server/serve_fact_route.d.ts.map +1 -1
  113. package/dist/server/serve_fact_route.js +15 -6
  114. package/dist/testing/CLAUDE.md +10 -3
  115. package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -1
  116. package/dist/testing/cross_backend/account_lifecycle.js +37 -2
  117. package/dist/testing/cross_backend/cell_gated_create.d.ts +38 -0
  118. package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
  119. package/dist/testing/cross_backend/cell_gated_create.js +401 -5
  120. package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -1
  121. package/dist/testing/cross_backend/cell_relations.js +145 -2
  122. package/dist/testing/cross_backend/expected_schema.json +0 -1
  123. package/dist/testing/cross_backend/login_security.d.ts +16 -0
  124. package/dist/testing/cross_backend/login_security.d.ts.map +1 -1
  125. package/dist/testing/cross_backend/login_security.js +154 -0
  126. package/dist/testing/cross_backend/query_shape.d.ts.map +1 -1
  127. package/dist/testing/cross_backend/query_shape.js +24 -2
  128. package/dist/testing/cross_backend/role_grant_participation.d.ts.map +1 -1
  129. package/dist/testing/cross_backend/role_grant_participation.js +64 -0
  130. package/dist/testing/cross_backend/spine_surface_constants.d.ts +1 -1
  131. package/dist/testing/cross_backend/spine_surface_constants.js +1 -1
  132. package/dist/testing/cross_backend/ws_action_rate_limit.d.ts.map +1 -1
  133. package/dist/testing/cross_backend/ws_action_rate_limit.js +21 -1
  134. package/dist/testing/schema_introspect.js +1 -1
  135. package/dist/testing/schema_parity.d.ts +4 -3
  136. package/dist/testing/schema_parity.d.ts.map +1 -1
  137. package/dist/testing/schema_parity.js +4 -3
  138. package/dist/testing/schema_ready_fixture.d.ts +2 -4
  139. package/dist/testing/schema_ready_fixture.d.ts.map +1 -1
  140. package/dist/testing/schema_ready_fixture.js +2 -4
  141. package/dist/ui/auth_state.svelte.d.ts +10 -0
  142. package/dist/ui/auth_state.svelte.d.ts.map +1 -1
  143. package/dist/ui/auth_state.svelte.js +22 -4
  144. package/package.json +1 -1
  145. package/dist/db/cell_history_ddl.d.ts +0 -38
  146. package/dist/db/cell_history_ddl.d.ts.map +0 -1
  147. package/dist/db/cell_history_ddl.js +0 -61
@@ -12,7 +12,8 @@ import { bootstrap_account } from './bootstrap_account.js';
12
12
  import { bootstrap_route_shape } from './bootstrap_route_schema.js';
13
13
  import { get_route_input } from '../http/route_spec.js';
14
14
  import { get_client_ip } from '../http/client_ip.js';
15
- import { rate_limit_exceeded_response } from '../rate_limiter.js';
15
+ import { rate_limit_ip_key } from '../http/ip_canonical.js';
16
+ import { rate_limit_exceeded_response, reserve_optional } from '../rate_limiter.js';
16
17
  import { ERROR_ALREADY_BOOTSTRAPPED, ERROR_TOKEN_FILE_MISSING } from '../http/error_schemas.js';
17
18
  /**
18
19
  * Check bootstrap availability at startup.
@@ -76,88 +77,96 @@ export const create_bootstrap_route_specs = (deps, options) => {
76
77
  if (!bootstrap_status.available || token_path === null) {
77
78
  return c.json({ error: ERROR_ALREADY_BOOTSTRAPPED }, 403);
78
79
  }
79
- // Per-IP rate limit check (before any token/DB work)
80
- const ip = bootstrap_ip_rate_limiter ? get_client_ip(c) : null;
81
- if (bootstrap_ip_rate_limiter && ip) {
82
- const check = bootstrap_ip_rate_limiter.check(ip);
83
- if (!check.allowed) {
84
- return rate_limit_exceeded_response(c, check.retry_after);
85
- }
80
+ // Per-IP rate limit (before any token/DB work). Reserved, not
81
+ // checked: the attempt counts against the cap until it settles, so
82
+ // a concurrent burst of token guesses can't all pass before any of
83
+ // it records. The `finally` releases it unless a failure recorded it.
84
+ // keyed by the address's rate-limit key (an IPv6 client's /64)
85
+ const ip_key = bootstrap_ip_rate_limiter ? rate_limit_ip_key(get_client_ip(c)) : null;
86
+ const ip_reserved = await reserve_optional(bootstrap_ip_rate_limiter, ip_key, {
87
+ signal: c.req.raw.signal
88
+ });
89
+ if (!ip_reserved.ok) {
90
+ return rate_limit_exceeded_response(c, ip_reserved.retry_after);
86
91
  }
87
- const input = get_route_input(c);
88
- // `transaction: false` makes `route.db` the pool. `bootstrap_account`
89
- // manages its own transaction internally.
90
- const result = await bootstrap_account({
91
- db: route.db,
92
- token_path,
93
- read_secure_file: deps.read_secure_file,
94
- delete_file: deps.delete_file,
95
- password: deps.password,
96
- log: deps.log
97
- }, input.token, input);
98
- if (!result.ok) {
99
- // An unreadable token file closes the window. Bootstrap cannot
100
- // succeed without one, and `check_bootstrap_status` already reads
101
- // an unreadable file as unavailable at startup — so leaving the
102
- // flag set means the two disagree, and every later request takes
103
- // this leg and writes another audit row. The limiter would bound
104
- // that channel; closing it ends it, and stops `/status`
105
- // advertising a window that can't be walked through. Twin of the
106
- // Rust spine's `bootstrap_handler`, which does the same.
107
- if (result.error === ERROR_TOKEN_FILE_MISSING)
108
- bootstrap_status.available = false;
109
- if (bootstrap_ip_rate_limiter && ip)
110
- bootstrap_ip_rate_limiter.record(ip);
92
+ try {
93
+ const input = get_route_input(c);
94
+ // `transaction: false` makes `route.db` the pool. `bootstrap_account`
95
+ // manages its own transaction internally.
96
+ const result = await bootstrap_account({
97
+ db: route.db,
98
+ token_path,
99
+ read_secure_file: deps.read_secure_file,
100
+ delete_file: deps.delete_file,
101
+ password: deps.password,
102
+ log: deps.log
103
+ }, input.token, input);
104
+ if (!result.ok) {
105
+ // An unreadable token file closes the window. Bootstrap cannot
106
+ // succeed without one, and `check_bootstrap_status` already reads
107
+ // an unreadable file as unavailable at startup — so leaving the
108
+ // flag set means the two disagree, and every later request takes
109
+ // this leg and writes another audit row. The limiter would bound
110
+ // that channel; closing it ends it, and stops `/status`
111
+ // advertising a window that can't be walked through. Twin of the
112
+ // Rust spine's `bootstrap_handler`, which does the same.
113
+ if (result.error === ERROR_TOKEN_FILE_MISSING)
114
+ bootstrap_status.available = false;
115
+ ip_reserved.reservation.record();
116
+ deps.audit.emit(route, {
117
+ event_type: 'bootstrap',
118
+ outcome: 'failure',
119
+ ip: get_client_ip(c),
120
+ metadata: { error: result.error }
121
+ });
122
+ return c.json({ error: result.error }, result.status);
123
+ }
124
+ // Successful bootstrap — update state immediately. Nothing is
125
+ // reset: bootstrap has no account-grain bucket to forgive (it
126
+ // predates any account), and the IP bucket is never refunded on
127
+ // success (see `RateLimiter.reset`). So this is the one auth
128
+ // surface where a success clears nothing at all. Bootstrap is
129
+ // one-shot, so at most `max_attempts - 1` residual entries
130
+ // survive — and they sit on this route's own limiter, not
131
+ // login's, so a fumbled token can't spend the operator's login
132
+ // budget on a deployment where their account is the only one.
133
+ bootstrap_status.available = false;
134
+ await create_session_and_set_cookie({
135
+ keyring,
136
+ deps: { db: route.db },
137
+ c,
138
+ account_id: result.account.id,
139
+ session_options
140
+ });
141
+ if (on_bootstrap) {
142
+ try {
143
+ await on_bootstrap(result, c);
144
+ }
145
+ catch (err) {
146
+ deps.log.error(`on_bootstrap callback failed: ${to_error_message(err)}`);
147
+ }
148
+ }
111
149
  deps.audit.emit(route, {
112
150
  event_type: 'bootstrap',
113
- outcome: 'failure',
114
- ip: get_client_ip(c),
115
- metadata: { error: result.error }
151
+ actor_id: result.actor.id,
152
+ account_id: result.account.id,
153
+ ip: get_client_ip(c)
116
154
  });
117
- return c.json({ error: result.error }, result.status);
118
- }
119
- // Successful bootstrap — update state immediately. Nothing is
120
- // reset: bootstrap has no account-grain bucket to forgive (it
121
- // predates any account), and the IP bucket is never refunded on
122
- // success (see `RateLimiter.reset`). So this is the one auth
123
- // surface where a success clears nothing at all. Bootstrap is
124
- // one-shot, so at most `max_attempts - 1` residual entries
125
- // survive — and they sit on this route's own limiter, not
126
- // login's, so a fumbled token can't spend the operator's login
127
- // budget on a deployment where their account is the only one.
128
- bootstrap_status.available = false;
129
- await create_session_and_set_cookie({
130
- keyring,
131
- deps: { db: route.db },
132
- c,
133
- account_id: result.account.id,
134
- session_options
135
- });
136
- if (on_bootstrap) {
137
- try {
138
- await on_bootstrap(result, c);
139
- }
140
- catch (err) {
141
- deps.log.error(`on_bootstrap callback failed: ${to_error_message(err)}`);
155
+ // CRITICAL: If token file deletion failed, throw to force operator attention.
156
+ // All success work (session, on_bootstrap, audit) has completed above.
157
+ // The error response alerts the operator to delete the token file manually.
158
+ if (!result.token_file_deleted) {
159
+ throw new Error(`Bootstrap succeeded but token file was not deleted at ${token_path}. Delete it manually and log in.`);
142
160
  }
161
+ return c.json({
162
+ ok: true,
163
+ account: { id: result.account.id, username: result.account.username },
164
+ actor: { id: result.actor.id }
165
+ });
143
166
  }
144
- deps.audit.emit(route, {
145
- event_type: 'bootstrap',
146
- actor_id: result.actor.id,
147
- account_id: result.account.id,
148
- ip: get_client_ip(c)
149
- });
150
- // CRITICAL: If token file deletion failed, throw to force operator attention.
151
- // All success work (session, on_bootstrap, audit) has completed above.
152
- // The error response alerts the operator to delete the token file manually.
153
- if (!result.token_file_deleted) {
154
- throw new Error(`Bootstrap succeeded but token file was not deleted at ${token_path}. Delete it manually and log in.`);
167
+ finally {
168
+ ip_reserved.reservation.release();
155
169
  }
156
- return c.json({
157
- ok: true,
158
- account: { id: result.account.id, username: result.account.username },
159
- actor: { id: result.actor.id }
160
- });
161
170
  }
162
171
  }
163
172
  ];
@@ -252,7 +252,9 @@ export type CellGetInput = z.infer<typeof CellGetInput>;
252
252
  * Output for `cell_get`. Bundles relation arrays (`fields` + `items`)
253
253
  * server-side via JOINs so the common "show this cell with its
254
254
  * children" flow needs one round-trip. Targets are filtered to those the
255
- * caller may view (strict target-visibility). Per-relation `LIMIT
255
+ * caller may view (strict target-visibility); `*_truncated` reports the
256
+ * relation's size before that filter, so it can be `true` while fewer than
257
+ * `CELL_RELATIONS_BUNDLE_LIMIT` edges are returned. Per-relation `LIMIT
256
258
  * CELL_RELATIONS_BUNDLE_LIMIT`; clients paginate via
257
259
  * `cell_item_list({parent_id, position_after})` /
258
260
  * `cell_field_list({source_id})` when truncated.
@@ -501,8 +503,10 @@ export type CellModerationDecision = z.infer<typeof CellModerationDecision>;
501
503
  * Input for `cell_moderate` — the `pending → approved | rejected` transition,
502
504
  * gated on moderation authority over the **governing root** (admin / root
503
505
  * owner in v1), not the contribution (which the author manages, and could
504
- * otherwise self-approve). 404 when the target isn't viewable; 403 when it is
505
- * but the caller isn't a root manager (the author lands here).
506
+ * otherwise self-approve). 404 when the caller neither views the target nor
507
+ * stewards it (a root manager reads a pending or rejected contribution through
508
+ * the steward arm); 403 when the caller reaches it but isn't a root manager
509
+ * (the author lands here).
506
510
  */
507
511
  export declare const CellModerateInput: z.ZodObject<{
508
512
  cell_id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
@@ -1 +1 @@
1
- {"version":3,"file":"cell_action_specs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cell_action_specs.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cell_action_specs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cell_action_specs.ts"],"names":[],"mappings":"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"}
@@ -179,7 +179,7 @@ export const CellJson = z.strictObject({
179
179
  description: 'Capability / identity tag — a write-once top-level column (peer to `visibility` / `path`), not a field inside `data`. `null` for a typeless cell. The discriminator a creation authorizer gates on; fixed at birth.'
180
180
  }),
181
181
  visibility: CellVisibility.meta({
182
- description: "Access-control tag. `'public'` admits everyone (including unauthenticated visitors); `'private'` (default) admits admin / owner / `cell_grant`-admitted callers. Top-level column, not inside `data`."
182
+ description: "Access-control tag. `'public'` admits everyone (including unauthenticated visitors); `'private'` (default) admits admin / owner / `cell_grant`-admitted callers. Past admin and owner, either admits only while `moderation` is `null` or `'approved'`. Top-level column, not inside `data`."
183
183
  }),
184
184
  refs: z.array(FactHashSchema).nullable(),
185
185
  parent_id: Uuid.nullable().meta({
@@ -247,7 +247,9 @@ export const CellGetInput = z
247
247
  * Output for `cell_get`. Bundles relation arrays (`fields` + `items`)
248
248
  * server-side via JOINs so the common "show this cell with its
249
249
  * children" flow needs one round-trip. Targets are filtered to those the
250
- * caller may view (strict target-visibility). Per-relation `LIMIT
250
+ * caller may view (strict target-visibility); `*_truncated` reports the
251
+ * relation's size before that filter, so it can be `true` while fewer than
252
+ * `CELL_RELATIONS_BUNDLE_LIMIT` edges are returned. Per-relation `LIMIT
251
253
  * CELL_RELATIONS_BUNDLE_LIMIT`; clients paginate via
252
254
  * `cell_item_list({parent_id, position_after})` /
253
255
  * `cell_field_list({source_id})` when truncated.
@@ -385,8 +387,10 @@ export const CellModerationDecision = z.enum(['approved', 'rejected']);
385
387
  * Input for `cell_moderate` — the `pending → approved | rejected` transition,
386
388
  * gated on moderation authority over the **governing root** (admin / root
387
389
  * owner in v1), not the contribution (which the author manages, and could
388
- * otherwise self-approve). 404 when the target isn't viewable; 403 when it is
389
- * but the caller isn't a root manager (the author lands here).
390
+ * otherwise self-approve). 404 when the caller neither views the target nor
391
+ * stewards it (a root manager reads a pending or rejected contribution through
392
+ * the steward arm); 403 when the caller reaches it but isn't a root manager
393
+ * (the author lands here).
390
394
  */
391
395
  export const CellModerateInput = z.strictObject({
392
396
  cell_id: Uuid.meta({ description: 'Contribution to moderate.' }),
@@ -477,7 +481,7 @@ export const cell_moderate_action_spec = {
477
481
  output: CellModerateOutput,
478
482
  async: true,
479
483
  rate_limit: 'account',
480
- description: 'Moderate a gated contribution (`pending → approved | rejected`). Gated on moderation authority over the governing root (admin / root owner), not the contribution — so the author cannot self-approve. Approval flips `moderation → approved` + `visibility → public`; rejection sets `moderation → rejected`. 404 on an unviewable target, 403 for a viewable target whose caller is not a root manager.'
484
+ description: 'Moderate a gated contribution (`pending → approved | rejected`). Gated on moderation authority over the governing root (admin / root owner), not the contribution — so the author cannot self-approve. Approval flips `moderation → approved` + `visibility → public`; rejection sets `moderation → rejected`. 404 on a target the caller neither views nor stewards (a root manager reads a pending or rejected contribution), 403 for a viewable target whose caller is not a root manager.'
481
485
  };
482
486
  /**
483
487
  * All cell-layer action specs — composed by app registries. Bundles the
@@ -60,7 +60,7 @@ export interface CellCreateAuthorizeInput {
60
60
  parent_id: Uuid | null;
61
61
  /** The governing root of the directory subtree, resolved by the handler from the parent (`parent.root_id ?? parent.id`). `null` for a root creation. */
62
62
  root_id: Uuid | null;
63
- /** The governing root's `data` — the handler reads it in-tx (when an authorizer is mounted) and hands it over, so a directory-aware authorizer resolves `root.data.policy[kind]` **without a DB read of its own** (pure predicate; reading in-tx avoids the single-connection PGlite deadlock a separate handle would hit). `null` for a root creation, or when no authorizer is mounted. */
63
+ /** The governing root's `data` — the handler reads it in-tx and hands it over, so a directory-aware authorizer resolves `root.data.policy[kind]` **without a DB read of its own** (pure predicate; reading in-tx avoids the single-connection PGlite deadlock a separate handle would hit). Non-null whenever `root_id` is — a missing or soft-deleted root answers `cell_not_found` before the authorizer runs — and `null` for a root creation. */
64
64
  root_data: CellData | null;
65
65
  /** Target scope — designed-in for M2 space-scoping; **always `null` in v1** (cells carry no scope column). */
66
66
  scope_id: Uuid | null;
@@ -85,8 +85,8 @@ export type CellCreateVerdict = {
85
85
  * contributions; answers "may *this actor* create *this kind* here?" and
86
86
  * returns a `CellCreateVerdict`. Runs in `cell_create` after `validate_data`
87
87
  * and after the handler resolves `root_id` from the parent (an unviewable
88
- * parent already 404-masks before this runs). Omitted = today's open create
89
- * (all consumers untouched). Async-capable (DB / policy calls) — a DB-backed
88
+ * parent or a missing or soft-deleted root already 404-masks before this runs).
89
+ * Omitted = today's open create (all consumers untouched). Async-capable (DB / policy calls) — a DB-backed
90
90
  * impl closes over its own `db` (the create handler stays unaware).
91
91
  */
92
92
  export type CellCreateAuthorize = (auth: RequestActorContext, input: CellCreateAuthorizeInput) => CellCreateVerdict | Promise<CellCreateVerdict>;
@@ -1 +1 @@
1
- {"version":3,"file":"cell_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cell_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAIH,OAAO,EAIN,KAAK,SAAS,EACd,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAAmB,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEjF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAEnD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EAoCN,KAAK,QAAQ,EAEb,MAAM,wBAAwB,CAAC;AAChC,OAAO,EASN,KAAK,OAAO,EACZ,MAAM,uBAAuB,CAAC;AAyB/B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEtD;;;;;GAKG;AACH,MAAM,WAAW,wBAAwB;IACxC,uFAAuF;IACvF,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,6HAA6H;IAC7H,IAAI,EAAE,QAAQ,CAAC;IACf,wHAAwH;IACxH,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,wJAAwJ;IACxJ,OAAO,EAAE,IAAI,GAAG,IAAI,CAAC;IACrB,6XAA6X;IAC7X,SAAS,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC3B,8GAA8G;IAC9G,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAAE,KAAK,EAAE,KAAK,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,mBAAmB,EAAE,OAAO,CAAA;CAAE,CAAC;AAEjG;;;;;;;;;GASG;AACH,MAAM,MAAM,mBAAmB,GAAG,CACjC,IAAI,EAAE,mBAAmB,EACzB,KAAK,EAAE,wBAAwB,KAC3B,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAEpD;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,cAAc,GAAG,iBAAiB,GAAG;IAChD,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,QAAQ,CAAC;IAC7C,gBAAgB,CAAC,EAAE,mBAAmB,CAAC;CACvC,CAAC;AA6BF,eAAO,MAAM,YAAY,GAAI,KAAK,OAAO,KAAG,QAgB1C,CAAC;AA2BH,+CAA+C;AAC/C,eAAO,MAAM,mBAAmB,GAAI,MAAM,cAAc,KAAG,KAAK,CAAC,SAAS,CAgpBzE,CAAC"}
1
+ {"version":3,"file":"cell_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cell_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAIH,OAAO,EAIN,KAAK,SAAS,EACd,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAEN,KAAK,mBAAmB,EAExB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAEnD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EAoCN,KAAK,QAAQ,EAEb,MAAM,wBAAwB,CAAC;AAChC,OAAO,EASN,KAAK,OAAO,EACZ,MAAM,uBAAuB,CAAC;AAkC/B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEtD;;;;;GAKG;AACH,MAAM,WAAW,wBAAwB;IACxC,uFAAuF;IACvF,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,6HAA6H;IAC7H,IAAI,EAAE,QAAQ,CAAC;IACf,wHAAwH;IACxH,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,wJAAwJ;IACxJ,OAAO,EAAE,IAAI,GAAG,IAAI,CAAC;IACrB,qbAAqb;IACrb,SAAS,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC3B,8GAA8G;IAC9G,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAAE,KAAK,EAAE,KAAK,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,mBAAmB,EAAE,OAAO,CAAA;CAAE,CAAC;AAEjG;;;;;;;;;GASG;AACH,MAAM,MAAM,mBAAmB,GAAG,CACjC,IAAI,EAAE,mBAAmB,EACzB,KAAK,EAAE,wBAAwB,KAC3B,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAEpD;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,cAAc,GAAG,iBAAiB,GAAG;IAChD,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,QAAQ,CAAC;IAC7C,gBAAgB,CAAC,EAAE,mBAAmB,CAAC;CACvC,CAAC;AA6BF,eAAO,MAAM,YAAY,GAAI,KAAK,OAAO,KAAG,QAgB1C,CAAC;AAkDH,+CAA+C;AAC/C,eAAO,MAAM,mBAAmB,GAAI,MAAM,cAAc,KAAG,KAAK,CAAC,SAAS,CA8pBzE,CAAC"}
@@ -49,8 +49,10 @@ import { query_cell_create, query_cell_get, query_cell_get_by_path, query_cell_u
49
49
  import { query_cell_grant_list_for_cell, query_cell_grants_for_caller_in_cells } from '../db/cell_grant_queries.js';
50
50
  import { query_cell_field_list_for_source, query_cell_field_set } from '../db/cell_field_queries.js';
51
51
  import { query_cell_item_insert, query_cell_item_list_for_parent } from '../db/cell_item_queries.js';
52
- import { can_view_cell, can_edit_cell, can_manage_cell } from './cell_authorize.js';
52
+ import { can_view_cell, can_edit_cell, can_manage_cell, cell_viewer_from_auth } from './cell_authorize.js';
53
53
  import { filter_visible_target_ids } from './cell_relation_visibility.js';
54
+ import { CELL_VIEWER_UNRESTRICTED } from '../db/cell_view_sql.js';
55
+ import { cell_steward_admits } from './cell_steward.js';
54
56
  import { to_grant_json } from './cell_grant_actions.js';
55
57
  import { to_field_json } from './cell_field_actions.js';
56
58
  import { to_item_json } from './cell_item_actions.js';
@@ -110,6 +112,25 @@ const emit_cell_audit = (ctx, event_type, row, deps, auth) => {
110
112
  }
111
113
  });
112
114
  };
115
+ /**
116
+ * Gate a loaded cell on `can_view_cell` or, when that denies, the steward arm
117
+ * (`cell_steward_admits` — the governing root's manager reading a pending or
118
+ * rejected contribution), 404-masking a denial.
119
+ *
120
+ * The read gate of the surfaces a moderator works from: `cell_get` and
121
+ * `cell_moderate`'s view step. It is **not** for `cell_clone`'s source, a
122
+ * contribution's parent, the relation endpoint gates, or the grant paths —
123
+ * those keep `can_view_cell` alone (`auth/cell_steward.ts` says why), so a
124
+ * later sweep must not route them here. The steward check runs only after the
125
+ * view check denies, so an admitted read costs no root load.
126
+ */
127
+ const admit_viewable_or_stewarded_cell = async (deps, auth, row, grants) => {
128
+ if (can_view_cell(auth, row, grants))
129
+ return;
130
+ if (await cell_steward_admits(deps, auth, row))
131
+ return;
132
+ throw jsonrpc_errors.not_found('cell', { reason: ERROR_CELL_NOT_FOUND });
133
+ };
113
134
  /** Create the six generic cell RPC actions. */
114
135
  export const create_cell_actions = (deps) => {
115
136
  const { validate_data, authorize_create } = deps;
@@ -161,11 +182,15 @@ export const create_cell_actions = (deps) => {
161
182
  // not an authorizer is mounted — `root_id` is written regardless, and an
162
183
  // unviewable parent 404-masks the attempt (the same IDOR mask the read
163
184
  // path uses; never reveals a hidden container). A root creation
164
- // (`parent_id` null/absent) has `root_id = null`. `root_data` is read
165
- // **in-tx** (only when an authorizer is mounted), reusing the parent row
166
- // when the parent *is* the root, else one read by `root_id` — so the
167
- // authorizer stays pure + the read rides the handler's connection (no
168
- // separate-handle deadlock on the single-connection in-memory drivers).
185
+ // (`parent_id` null/absent) has `root_id = null`. The root is read
186
+ // **in-tx**, reusing the parent row when the parent *is* the root, else
187
+ // one read by `root_id` — so the authorizer stays pure + the read rides
188
+ // the handler's connection (no separate-handle deadlock on the
189
+ // single-connection in-memory drivers). A missing or soft-deleted root
190
+ // answers `cell_not_found` like a missing parent (soft delete doesn't
191
+ // cascade, so a live parent can sit under a tombstoned root): nothing is
192
+ // written pointing at the tombstone, and an authorizer never sees a root
193
+ // id without its data.
169
194
  const parent_id = input.parent_id ?? null;
170
195
  let root_id = null;
171
196
  let root_data = null;
@@ -178,11 +203,14 @@ export const create_cell_actions = (deps) => {
178
203
  throw jsonrpc_errors.not_found('cell', { reason: ERROR_CELL_NOT_FOUND });
179
204
  }
180
205
  root_id = parent.root_id ?? parent.id;
181
- if (authorize_create) {
182
- root_data =
183
- root_id === parent.id
184
- ? parent.data
185
- : ((await query_cell_get(ctx, root_id))?.data ?? null);
206
+ if (root_id === parent.id) {
207
+ root_data = parent.data;
208
+ }
209
+ else {
210
+ const root = await query_cell_get(ctx, root_id);
211
+ if (!root)
212
+ throw jsonrpc_errors.not_found('cell', { reason: ERROR_CELL_NOT_FOUND });
213
+ root_data = root.data;
186
214
  }
187
215
  }
188
216
  // Parent-aware creation authorizer (opt-in). Runs after shape validation
@@ -272,22 +300,23 @@ export const create_cell_actions = (deps) => {
272
300
  // `row.id` and have no inter-dependency. Bundle fetches one over the
273
301
  // cap so we can detect truncation without a separate count query.
274
302
  // Skip the grant fetch for unauthenticated callers — no grant can
275
- // admit a null auth, so the predicate either short-circuits via
276
- // `cell_is_public` or returns false either way.
303
+ // admit a null auth, so the predicate either admits via the public
304
+ // arm or returns false either way.
277
305
  const [grants, fields, items] = await Promise.all([
278
306
  auth ? query_cell_grant_list_for_cell(ctx, row.id) : Promise.resolve(null),
279
- query_cell_field_list_for_source(ctx, row.id, {
307
+ query_cell_field_list_for_source(ctx, CELL_VIEWER_UNRESTRICTED, row.id, {
280
308
  limit: CELL_RELATIONS_BUNDLE_LIMIT + 1
281
309
  }),
282
- query_cell_item_list_for_parent(ctx, row.id, {
310
+ query_cell_item_list_for_parent(ctx, CELL_VIEWER_UNRESTRICTED, row.id, {
283
311
  limit: CELL_RELATIONS_BUNDLE_LIMIT + 1
284
312
  })
285
313
  ]);
286
314
  // 404 covers both "no such cell" and "exists but caller can't view"
287
- // — same response code so private-cell existence doesn't leak.
288
- if (!can_view_cell(auth, row, grants)) {
289
- throw jsonrpc_errors.not_found('cell', { reason: ERROR_CELL_NOT_FOUND });
290
- }
315
+ // — same response code so private-cell existence doesn't leak. The
316
+ // governing root's manager also reads a pending or rejected
317
+ // contribution here (the steward arm); `can_edit` / `can_grant` stay on
318
+ // the base predicates, so a steward reads `false` / `false`.
319
+ await admit_viewable_or_stewarded_cell(ctx, auth, row, grants);
291
320
  const can_edit = can_edit_cell(auth, row, grants);
292
321
  // `can_grant` gates the share UI — managing grants is a manage-tier
293
322
  // affordance (admin / owner), so it tracks `can_manage_cell` rather
@@ -404,14 +433,15 @@ export const create_cell_actions = (deps) => {
404
433
  const moderate_handler = async (input, ctx) => {
405
434
  const auth = ctx.auth;
406
435
  const existing = await query_cell_get(ctx, input.cell_id);
407
- // 404 covers miss + unviewable so a pending (private) contribution
408
- // doesn't leak existence to a non-viewer.
436
+ // 404 covers miss + unviewable so a pending contribution doesn't leak
437
+ // existence to a non-viewer; the governing root's manager stewards it,
438
+ // which is how a non-admin root owner reaches the root check below. An
439
+ // approved contribution its author narrowed to private is not
440
+ // stewarded, so its root owner gets the 404 there.
409
441
  if (!existing)
410
442
  throw jsonrpc_errors.not_found('cell', { reason: ERROR_CELL_NOT_FOUND });
411
443
  const grants = await query_cell_grant_list_for_cell(ctx, existing.id);
412
- if (!can_view_cell(auth, existing, grants)) {
413
- throw jsonrpc_errors.not_found('cell', { reason: ERROR_CELL_NOT_FOUND });
414
- }
444
+ await admit_viewable_or_stewarded_cell(ctx, auth, existing, grants);
415
445
  // Only a contribution (with a governing root) is moderatable.
416
446
  if (existing.root_id === null) {
417
447
  throw jsonrpc_errors.invalid_params('cell is not a contribution (no governing root to moderate under)', { reason: ERROR_CELL_NOT_A_CONTRIBUTION });
@@ -526,9 +556,9 @@ export const create_cell_actions = (deps) => {
526
556
  // don't bloat the transaction. The fetch sees the world as of
527
557
  // pre-transaction; the inserts inside the transaction commit
528
558
  // atomically together.
529
- const source_fields = await query_cell_field_list_for_source(ctx, source.id);
559
+ const source_fields = await query_cell_field_list_for_source(ctx, CELL_VIEWER_UNRESTRICTED, source.id);
530
560
  const source_items = deep
531
- ? await query_cell_item_list_for_parent(ctx, source.id)
561
+ ? await query_cell_item_list_for_parent(ctx, CELL_VIEWER_UNRESTRICTED, source.id)
532
562
  : [];
533
563
  // In deep mode, pre-resolve which children are viewable so we can
534
564
  // skip un-viewable ones silently (strict per-target view filter, D8).
@@ -614,7 +644,7 @@ export const create_cell_actions = (deps) => {
614
644
  // `cell_item_insert` enforce. Non-viewable children are skipped
615
645
  // silently (no count surfaced — see the deep-walk note on the
616
646
  // hidden-child-count leak).
617
- const shallow_items = await query_cell_item_list_for_parent(ctx, source.id);
647
+ const shallow_items = await query_cell_item_list_for_parent(ctx, CELL_VIEWER_UNRESTRICTED, source.id);
618
648
  const shallow_children_visible = await filter_visible_target_ids(ctx, auth, shallow_items.map((i) => i.child_id));
619
649
  for (const item of shallow_items) {
620
650
  if (!shallow_children_visible.has(item.child_id))
@@ -663,13 +693,12 @@ export const create_cell_actions = (deps) => {
663
693
  reason: ERROR_CELL_LIST_SHARED_WITH_REQUIRES_AUTH
664
694
  });
665
695
  }
666
- // Project the active role_grant set into parallel arrays for the
667
- // `cell_grant` role-shaped EXISTS. Middleware (`request_context`)
668
- // has already filtered to active-only role_grants; we trust that and
669
- // pass NULL `scope_id`s through (global-scope role_grants).
670
- const role_grant_roles = auth ? auth.role_grants.map((p) => p.role) : [];
671
- const role_grant_scope_ids = auth ? auth.role_grants.map((p) => p.scope_id) : [];
672
- const caller_actor_id = auth?.actor?.id ?? null;
696
+ // The caller the SQL view predicate binds — the facts `can_view_cell`
697
+ // reads, its active role grants projected into parallel arrays for the
698
+ // `cell_grant` role-shaped EXISTS (NULL `scope_id`s pass through as
699
+ // global-scope role_grants).
700
+ const viewer = cell_viewer_from_auth(auth);
701
+ const caller_actor_id = viewer.actor_id;
673
702
  const rows = await query_cell_list(ctx, {
674
703
  ids: input.ids,
675
704
  kind: input.kind,
@@ -680,10 +709,10 @@ export const create_cell_actions = (deps) => {
680
709
  root_id: input.root_id,
681
710
  moderation: input.moderation,
682
711
  viewer_actor_id: caller_actor_id,
683
- viewer_is_admin: auth ? has_scoped_role(auth, ROLE_ADMIN, null) : false,
712
+ viewer_is_admin: viewer.is_admin,
684
713
  caller_actor_id,
685
- caller_role_grant_roles: role_grant_roles,
686
- caller_role_grant_scope_ids: role_grant_scope_ids,
714
+ caller_role_grant_roles: viewer.role_grant_roles,
715
+ caller_role_grant_scope_ids: viewer.role_grant_scope_ids,
687
716
  shared_with_caller_only: input.shared_with === 'me',
688
717
  order_by: input.order_by,
689
718
  order_direction: input.order_direction,
@@ -694,7 +723,7 @@ export const create_cell_actions = (deps) => {
694
723
  });
695
724
  let cell_grants;
696
725
  if (input.shared_with === 'me' && rows.length > 0 && caller_actor_id !== null) {
697
- const grant_rows = await query_cell_grants_for_caller_in_cells(ctx, rows.map((r) => r.id), caller_actor_id, role_grant_roles, role_grant_scope_ids);
726
+ const grant_rows = await query_cell_grants_for_caller_in_cells(ctx, rows.map((r) => r.id), caller_actor_id, viewer.role_grant_roles, viewer.role_grant_scope_ids);
698
727
  cell_grants = {};
699
728
  for (const g of grant_rows) {
700
729
  (cell_grants[g.cell_id] ??= []).push(to_grant_json(g));