@fuzdev/fuz_app 0.141.0 → 0.143.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 (159) 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/CLAUDE.md +1 -1
  100. package/dist/http/auth_shape.d.ts +5 -2
  101. package/dist/http/auth_shape.d.ts.map +1 -1
  102. package/dist/http/auth_shape.js +5 -2
  103. package/dist/http/ip_canonical.d.ts +56 -5
  104. package/dist/http/ip_canonical.d.ts.map +1 -1
  105. package/dist/http/ip_canonical.js +147 -35
  106. package/dist/primitive_schemas.d.ts +10 -1
  107. package/dist/primitive_schemas.d.ts.map +1 -1
  108. package/dist/primitive_schemas.js +11 -2
  109. package/dist/rate_limiter.d.ts +179 -12
  110. package/dist/rate_limiter.d.ts.map +1 -1
  111. package/dist/rate_limiter.js +352 -42
  112. package/dist/server/serve_fact_route.d.ts +8 -4
  113. package/dist/server/serve_fact_route.d.ts.map +1 -1
  114. package/dist/server/serve_fact_route.js +15 -6
  115. package/dist/testing/CLAUDE.md +16 -7
  116. package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -1
  117. package/dist/testing/cross_backend/account_lifecycle.js +37 -2
  118. package/dist/testing/cross_backend/cell_gated_create.d.ts +38 -0
  119. package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
  120. package/dist/testing/cross_backend/cell_gated_create.js +401 -5
  121. package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -1
  122. package/dist/testing/cross_backend/cell_relations.js +145 -2
  123. package/dist/testing/cross_backend/expected_schema.json +0 -1
  124. package/dist/testing/cross_backend/full_spine_mount.js +2 -1
  125. package/dist/testing/cross_backend/login_security.d.ts +16 -0
  126. package/dist/testing/cross_backend/login_security.d.ts.map +1 -1
  127. package/dist/testing/cross_backend/login_security.js +154 -0
  128. package/dist/testing/cross_backend/origin_conformance.d.ts +1 -1
  129. package/dist/testing/cross_backend/origin_conformance.d.ts.map +1 -1
  130. package/dist/testing/cross_backend/origin_conformance.js +1 -1
  131. package/dist/testing/cross_backend/query_shape.d.ts.map +1 -1
  132. package/dist/testing/cross_backend/query_shape.js +24 -2
  133. package/dist/testing/cross_backend/role_grant_participation.d.ts.map +1 -1
  134. package/dist/testing/cross_backend/role_grant_participation.js +64 -0
  135. package/dist/testing/cross_backend/spine_surface_constants.d.ts +13 -21
  136. package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
  137. package/dist/testing/cross_backend/spine_surface_constants.js +13 -21
  138. package/dist/testing/cross_backend/testing_origin_check.d.ts +128 -0
  139. package/dist/testing/cross_backend/testing_origin_check.d.ts.map +1 -0
  140. package/dist/testing/cross_backend/testing_origin_check.js +87 -0
  141. package/dist/testing/cross_backend/testing_reset_actions.d.ts +7 -119
  142. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  143. package/dist/testing/cross_backend/testing_reset_actions.js +8 -92
  144. package/dist/testing/cross_backend/ws_action_rate_limit.d.ts.map +1 -1
  145. package/dist/testing/cross_backend/ws_action_rate_limit.js +21 -1
  146. package/dist/testing/schema_introspect.js +1 -1
  147. package/dist/testing/schema_parity.d.ts +4 -3
  148. package/dist/testing/schema_parity.d.ts.map +1 -1
  149. package/dist/testing/schema_parity.js +4 -3
  150. package/dist/testing/schema_ready_fixture.d.ts +2 -4
  151. package/dist/testing/schema_ready_fixture.d.ts.map +1 -1
  152. package/dist/testing/schema_ready_fixture.js +2 -4
  153. package/dist/ui/auth_state.svelte.d.ts +10 -0
  154. package/dist/ui/auth_state.svelte.d.ts.map +1 -1
  155. package/dist/ui/auth_state.svelte.js +22 -4
  156. package/package.json +1 -1
  157. package/dist/db/cell_history_ddl.d.ts +0 -38
  158. package/dist/db/cell_history_ddl.d.ts.map +0 -1
  159. package/dist/db/cell_history_ddl.js +0 -61
@@ -13,6 +13,7 @@ import { z } from 'zod';
13
13
  import { Uuid, bounded_string } from '../primitive_schemas.js';
14
14
  import { FRACTIONAL_INDEX_LENGTH_MAX, FRACTIONAL_INDEX_REGEX } from '@fuzdev/fuz_util/fractional_index.ts';
15
15
  import { ActingActor } from '../http/auth_shape.js';
16
+ import { CellRelationListLimit } from './cell_relation_list_limit.js';
16
17
  // -- Error reasons ----------------------------------------------------------
17
18
  /** Error reason — `cell_item_list` got neither `parent_id` nor `child_id`. */
18
19
  export const ERROR_CELL_ITEM_LIST_REQUIRES_PARENT_OR_CHILD = 'cell_item_list_requires_parent_or_child';
@@ -101,10 +102,11 @@ export const CellItemDeleteOutput = z.strictObject({
101
102
  * authz (child view-check gates the call; per-parent view-check filters
102
103
  * the rows).
103
104
  *
104
- * Forward listing supports cursor pagination via `position_after`
105
- * (return rows with `position > position_after`). The reverse listing
106
- * doesn't paginate (the result set is small in practice — number of
107
- * parents containing a given child).
105
+ * Either direction returns a page of up to `limit` items (default
106
+ * `CELL_RELATION_LIST_LIMIT_DEFAULT`) whose other end the caller can view —
107
+ * the view check runs before the limit. Forward listing pages on via
108
+ * `position_after` (return rows with `position > position_after`); the
109
+ * reverse listing has no cursor, oldest item first.
108
110
  */
109
111
  export const CellItemListInput = z
110
112
  .strictObject({
@@ -117,9 +119,7 @@ export const CellItemListInput = z
117
119
  position_after: CellItemPosition.optional().meta({
118
120
  description: 'Cursor for forward pagination — return rows whose position > this.'
119
121
  }),
120
- limit: z.number().int().positive().max(500).optional().meta({
121
- description: 'Page size cap (max 500). Omit for unbounded — explicit list calls escape the bundled `cell_get` cap.'
122
- }),
122
+ limit: CellRelationListLimit,
123
123
  acting: ActingActor
124
124
  })
125
125
  .refine((v) => Boolean(v.parent_id) !== Boolean(v.child_id), {
@@ -12,11 +12,12 @@
12
12
  * collision-error shape as insert.
13
13
  * - `cell_item_delete` — admin / owner / editor-grant on `parent`.
14
14
  * Idempotent: `deleted: false` when no row matched.
15
- * - `cell_item_list` — bidirectional. Forward (pass `parent_id`) is
16
- * gated on `can_view_cell(parent)` and filters children to those the
17
- * caller may view (strict target-visibility, batched). Reverse (pass
18
- * `child_id`) has 2-layer authz: gate on `can_view_cell(child)`, then
19
- * filter rows by `can_view_cell(parent)`.
15
+ * - `cell_item_list` — bidirectional, a page of up to `limit` items whose
16
+ * other end the caller may view (filtered in SQL, before the limit).
17
+ * Forward (pass `parent_id`) is gated on `can_view_cell(parent)` and
18
+ * pages the items whose child is viewable (strict target-visibility).
19
+ * Reverse (pass `child_id`) has 2-layer authz: gate on
20
+ * `can_view_cell(child)`, then page the items whose parent is viewable.
20
21
  *
21
22
  * IDOR-mask 404s on cell-miss / cell-unviewable, mirroring the existence-
22
23
  * leak guards in `auth/cell_actions.ts`.
@@ -1 +1 @@
1
- {"version":3,"file":"cell_item_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cell_item_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAIN,KAAK,SAAS,EACd,MAAM,0BAA0B,CAAC;AAGlC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAEnD,OAAO,EAeN,KAAK,QAAQ,EACb,MAAM,6BAA6B,CAAC;AAMrC,OAAO,EAMN,KAAK,WAAW,EAChB,MAAM,4BAA4B,CAAC;AAOpC,MAAM,MAAM,kBAAkB,GAAG,iBAAiB,CAAC;AAEnD,eAAO,MAAM,YAAY,GAAI,KAAK,WAAW,KAAG,QAS9C,CAAC;AAOH,iDAAiD;AACjD,eAAO,MAAM,wBAAwB,GAAI,MAAM,kBAAkB,KAAG,KAAK,CAAC,SAAS,CA2KlF,CAAC"}
1
+ {"version":3,"file":"cell_item_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cell_item_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAIN,KAAK,SAAS,EACd,MAAM,0BAA0B,CAAC;AAGlC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAEnD,OAAO,EAeN,KAAK,QAAQ,EACb,MAAM,6BAA6B,CAAC;AAMrC,OAAO,EAMN,KAAK,WAAW,EAChB,MAAM,4BAA4B,CAAC;AAOpC,MAAM,MAAM,kBAAkB,GAAG,iBAAiB,CAAC;AAEnD,eAAO,MAAM,YAAY,GAAI,KAAK,WAAW,KAAG,QAS9C,CAAC;AAOH,iDAAiD;AACjD,eAAO,MAAM,wBAAwB,GAAI,MAAM,kBAAkB,KAAG,KAAK,CAAC,SAAS,CAwKlF,CAAC"}
@@ -12,11 +12,12 @@
12
12
  * collision-error shape as insert.
13
13
  * - `cell_item_delete` — admin / owner / editor-grant on `parent`.
14
14
  * Idempotent: `deleted: false` when no row matched.
15
- * - `cell_item_list` — bidirectional. Forward (pass `parent_id`) is
16
- * gated on `can_view_cell(parent)` and filters children to those the
17
- * caller may view (strict target-visibility, batched). Reverse (pass
18
- * `child_id`) has 2-layer authz: gate on `can_view_cell(child)`, then
19
- * filter rows by `can_view_cell(parent)`.
15
+ * - `cell_item_list` — bidirectional, a page of up to `limit` items whose
16
+ * other end the caller may view (filtered in SQL, before the limit).
17
+ * Forward (pass `parent_id`) is gated on `can_view_cell(parent)` and
18
+ * pages the items whose child is viewable (strict target-visibility).
19
+ * Reverse (pass `child_id`) has 2-layer authz: gate on
20
+ * `can_view_cell(child)`, then page the items whose parent is viewable.
20
21
  *
21
22
  * IDOR-mask 404s on cell-miss / cell-unviewable, mirroring the existence-
22
23
  * leak guards in `auth/cell_actions.ts`.
@@ -31,8 +32,8 @@ import { jsonrpc_errors } from '../http/jsonrpc_errors.js';
31
32
  import { is_pg_unique_violation } from '../db/pg_error.js';
32
33
  import { cell_item_insert_action_spec, cell_item_move_action_spec, cell_item_delete_action_spec, cell_item_list_action_spec, ERROR_CELL_ITEM_POSITION_TAKEN } from './cell_item_action_specs.js';
33
34
  import { ERROR_CELL_NOT_FOUND } from './cell_action_specs.js';
34
- import { can_view_cell, can_edit_cell } from './cell_authorize.js';
35
- import { filter_visible_target_ids } from './cell_relation_visibility.js';
35
+ import { can_view_cell, can_edit_cell, cell_viewer_from_auth } from './cell_authorize.js';
36
+ import { CELL_RELATION_LIST_LIMIT_DEFAULT } from './cell_relation_list_limit.js';
36
37
  import { query_cell_get } from '../db/cell_queries.js';
37
38
  import { query_cell_grant_list_for_cell } from '../db/cell_grant_queries.js';
38
39
  import { query_cell_item_insert, query_cell_item_move, query_cell_item_delete, query_cell_item_list_for_parent, query_cell_item_list_for_child } from '../db/cell_item_queries.js';
@@ -160,8 +161,11 @@ export const create_cell_item_actions = (deps) => {
160
161
  };
161
162
  const list_handler = async (input, ctx) => {
162
163
  const auth = ctx.auth;
163
- // Forward listing: gate on can_view_cell(parent), then filter the
164
- // children to those the caller may view (strict target-visibility).
164
+ // an omitted limit is the default page, either direction
165
+ const limit = input.limit ?? CELL_RELATION_LIST_LIMIT_DEFAULT;
166
+ // Forward listing: gate on can_view_cell(parent), then page the items
167
+ // whose child the caller may view (strict target-visibility, in SQL
168
+ // before the limit).
165
169
  if (input.parent_id !== undefined) {
166
170
  const parent = await query_cell_get(ctx, input.parent_id);
167
171
  if (!parent) {
@@ -171,12 +175,8 @@ export const create_cell_item_actions = (deps) => {
171
175
  if (!can_view_cell(auth, parent, parent_grants)) {
172
176
  throw jsonrpc_errors.not_found('cell', { reason: ERROR_CELL_NOT_FOUND });
173
177
  }
174
- const rows = await query_cell_item_list_for_parent(ctx, parent.id, {
175
- limit: input.limit,
176
- position_after: input.position_after
177
- });
178
- const visible_children = await filter_visible_target_ids(ctx, auth, rows.map((r) => r.child_id));
179
- return { items: rows.filter((r) => visible_children.has(r.child_id)).map(to_item_json) };
178
+ const rows = await query_cell_item_list_for_parent(ctx, cell_viewer_from_auth(auth), parent.id, { limit, position_after: input.position_after });
179
+ return { items: rows.map(to_item_json) };
180
180
  }
181
181
  // Reverse listing: 2-layer authz. First, can_view_cell(child).
182
182
  const child = await query_cell_get(ctx, input.child_id);
@@ -187,13 +187,14 @@ export const create_cell_item_actions = (deps) => {
187
187
  if (!can_view_cell(auth, child, child_grants)) {
188
188
  throw jsonrpc_errors.not_found('cell', { reason: ERROR_CELL_NOT_FOUND });
189
189
  }
190
- // Then filter rows by per-parent can_view_cell. Batched (not N+1):
191
- // one bulk visibility filter over all parent ids, same as the forward
192
- // branch. Bounded by `limit` at the query so a heavily inbound-linked
193
- // child can't force an unbounded fetch on this public endpoint.
194
- const rows = await query_cell_item_list_for_child(ctx, child.id, { limit: input.limit });
195
- const visible_parents = await filter_visible_target_ids(ctx, auth, rows.map((r) => r.parent_id));
196
- return { items: rows.filter((r) => visible_parents.has(r.parent_id)).map(to_item_json) };
190
+ // Then page the items whose parent the caller may view — filtered in
191
+ // SQL before the limit, so hidden parents never crowd out visible ones,
192
+ // and bounded so a heavily inbound-linked child can't force an
193
+ // unbounded read on this public endpoint.
194
+ const rows = await query_cell_item_list_for_child(ctx, cell_viewer_from_auth(auth), child.id, {
195
+ limit
196
+ });
197
+ return { items: rows.map(to_item_json) };
197
198
  };
198
199
  return [
199
200
  rpc_action(cell_item_insert_action_spec, insert_handler),
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The page bound the two relation lists share — `cell_field_list` and
3
+ * `cell_item_list`, forward and reverse alike. A page is up to `limit` edges
4
+ * whose other end the caller can view. Both are public reads, so an omitted
5
+ * `limit` is the default page, never unbounded: anyone can pad a public
6
+ * cell's inbound links, and the reverse lists have no cursor.
7
+ *
8
+ * Twin of the Rust spine's `CELL_RELATION_LIST_LIMIT_MAX` /
9
+ * `CELL_RELATION_LIST_LIMIT_DEFAULT`.
10
+ *
11
+ * @module
12
+ */
13
+ import { z } from 'zod';
14
+ /** Cap on a relation-list page, forward and reverse alike. */
15
+ export declare const CELL_RELATION_LIST_LIMIT_MAX = 500;
16
+ /** The page size a relation list that omits `limit` gets, forward and reverse alike. */
17
+ export declare const CELL_RELATION_LIST_LIMIT_DEFAULT = 50;
18
+ /** The `limit` input of `cell_field_list` and `cell_item_list`. */
19
+ export declare const CellRelationListLimit: z.ZodOptional<z.ZodNumber>;
20
+ //# sourceMappingURL=cell_relation_list_limit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cell_relation_list_limit.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cell_relation_list_limit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,8DAA8D;AAC9D,eAAO,MAAM,4BAA4B,MAAM,CAAC;AAEhD,wFAAwF;AACxF,eAAO,MAAM,gCAAgC,KAAK,CAAC;AAEnD,mEAAmE;AACnE,eAAO,MAAM,qBAAqB,4BAQ/B,CAAC"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The page bound the two relation lists share — `cell_field_list` and
3
+ * `cell_item_list`, forward and reverse alike. A page is up to `limit` edges
4
+ * whose other end the caller can view. Both are public reads, so an omitted
5
+ * `limit` is the default page, never unbounded: anyone can pad a public
6
+ * cell's inbound links, and the reverse lists have no cursor.
7
+ *
8
+ * Twin of the Rust spine's `CELL_RELATION_LIST_LIMIT_MAX` /
9
+ * `CELL_RELATION_LIST_LIMIT_DEFAULT`.
10
+ *
11
+ * @module
12
+ */
13
+ import { z } from 'zod';
14
+ /** Cap on a relation-list page, forward and reverse alike. */
15
+ export const CELL_RELATION_LIST_LIMIT_MAX = 500;
16
+ /** The page size a relation list that omits `limit` gets, forward and reverse alike. */
17
+ export const CELL_RELATION_LIST_LIMIT_DEFAULT = 50;
18
+ /** The `limit` input of `cell_field_list` and `cell_item_list`. */
19
+ export const CellRelationListLimit = z
20
+ .number()
21
+ .int()
22
+ .positive()
23
+ .max(CELL_RELATION_LIST_LIMIT_MAX)
24
+ .optional()
25
+ .meta({
26
+ description: `The most edges a page holds, each one whose other end the caller can view — forward and reverse alike: default ${CELL_RELATION_LIST_LIMIT_DEFAULT}, max ${CELL_RELATION_LIST_LIMIT_MAX}.`
27
+ });
@@ -6,9 +6,10 @@
6
6
  * target the caller cannot view — otherwise an editor of a public parent
7
7
  * could enumerate private children by id, or a public cell could leak the
8
8
  * existence of private linked cells. This helper bulk-loads the target
9
- * cells + their grants and runs `can_view_cell` per target in memory,
10
- * returning the set of viewable target ids. Batched (two queries for the
11
- * whole id-set) to avoid the N+1 a naive per-row check would cause.
9
+ * cells' view facts (`CELL_VIEW_COLUMNS` — never the `data` payload) and
10
+ * their grants, then runs `can_view_cell` per target in memory, returning
11
+ * the set of viewable target ids. Batched (two queries for the whole
12
+ * id-set) to avoid the N+1 a naive per-row check would cause.
12
13
  *
13
14
  * @module
14
15
  */
@@ -1 +1 @@
1
- {"version":3,"file":"cell_relation_visibility.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cell_relation_visibility.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAK3D;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,yBAAyB,GACrC,MAAM,SAAS,EACf,MAAM,cAAc,GAAG,IAAI,EAC3B,YAAY,aAAa,CAAC,IAAI,CAAC,KAC7B,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAwBnB,CAAC"}
1
+ {"version":3,"file":"cell_relation_visibility.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cell_relation_visibility.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAK3D;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,yBAAyB,GACrC,MAAM,SAAS,EACf,MAAM,cAAc,GAAG,IAAI,EAC3B,YAAY,aAAa,CAAC,IAAI,CAAC,KAC7B,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAwBnB,CAAC"}
@@ -6,14 +6,15 @@
6
6
  * target the caller cannot view — otherwise an editor of a public parent
7
7
  * could enumerate private children by id, or a public cell could leak the
8
8
  * existence of private linked cells. This helper bulk-loads the target
9
- * cells + their grants and runs `can_view_cell` per target in memory,
10
- * returning the set of viewable target ids. Batched (two queries for the
11
- * whole id-set) to avoid the N+1 a naive per-row check would cause.
9
+ * cells' view facts (`CELL_VIEW_COLUMNS` — never the `data` payload) and
10
+ * their grants, then runs `can_view_cell` per target in memory, returning
11
+ * the set of viewable target ids. Batched (two queries for the whole
12
+ * id-set) to avoid the N+1 a naive per-row check would cause.
12
13
  *
13
14
  * @module
14
15
  */
15
16
  import { can_view_cell } from './cell_authorize.js';
16
- import { query_cell_load_many } from '../db/cell_queries.js';
17
+ import { query_cell_view_load_many } from '../db/cell_queries.js';
17
18
  import { query_cell_grant_list_for_cells } from '../db/cell_grant_queries.js';
18
19
  /**
19
20
  * Return the subset of `target_ids` the caller may view.
@@ -33,7 +34,7 @@ export const filter_visible_target_ids = async (deps, auth, target_ids) => {
33
34
  if (target_ids.length === 0)
34
35
  return visible;
35
36
  const unique = [...new Set(target_ids)];
36
- const cells = await query_cell_load_many(deps, unique);
37
+ const cells = await query_cell_view_load_many(deps, unique);
37
38
  // Grants only matter for authenticated callers — null auth admits via
38
39
  // the public branch alone, so skip the grant load entirely.
39
40
  const grants_by_cell = new Map();
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The steward read arm, with the governing-root load it needs.
3
+ *
4
+ * `can_steward_cell` (`auth/cell_authorize.ts`) is pure over
5
+ * `(auth, cell, root)`; `cell_steward_admits` is the composition every steward
6
+ * site runs after `can_view_cell` denies: it loads the governing root (active
7
+ * rows only, so a soft-deleted root stewards nothing) and applies the
8
+ * predicate. It short-circuits with no query unless the caller is
9
+ * authenticated and the cell is a pending or rejected contribution.
10
+ *
11
+ * **Where the steward arm applies** — the surfaces a moderator works from:
12
+ * `cell_get`, `cell_moderate` (whose root-authority check it lets a non-admin
13
+ * root owner reach), the cell-scoped fact route, and `cell_list`'s general
14
+ * predicate (the SQL twin in `query_cell_list`).
15
+ *
16
+ * **Where it must not** — every other read keeps `can_view_cell` alone:
17
+ * `cell_clone` (a steward copying a contribution out from under its
18
+ * moderation), the parent check on `cell_create` (a steward contributing under
19
+ * a pending post), the relation endpoint gates, `filter_visible_target_ids`
20
+ * (the `cell_get` bundle), the field / item lists' SQL view filter
21
+ * (`db/cell_view_sql.ts`),
22
+ * `cell_list`'s `shared_with` semi-join (a grant footprint, not a moderation
23
+ * queue), and `cell_grant_revoke`'s `still_admitted` recompute. Widening one of
24
+ * these is a decision, not a sweep. Twin of the Rust `fuz_cell::cell_steward`.
25
+ *
26
+ * @module
27
+ */
28
+ import type { QueryDeps } from '../db/query_deps.js';
29
+ import { type CellRow } from '../db/cell_queries.js';
30
+ import type { RequestContext } from './request_context.js';
31
+ /**
32
+ * Whether `auth` stewards `cell` — loads the governing root and applies
33
+ * `can_steward_cell`.
34
+ *
35
+ * Resolves `false` without a query for an unauthenticated caller, a root, or a
36
+ * cell whose moderation isn't `'pending'` / `'rejected'`; a missing or
37
+ * soft-deleted root is `false` too.
38
+ *
39
+ * @param deps - query deps
40
+ * @param auth - request context, or `null` for unauthenticated callers
41
+ * @param cell - the cell the caller couldn't otherwise view
42
+ * @returns whether the steward arm admits the caller
43
+ */
44
+ export declare const cell_steward_admits: (deps: QueryDeps, auth: RequestContext | null, cell: CellRow) => Promise<boolean>;
45
+ //# sourceMappingURL=cell_steward.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cell_steward.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cell_steward.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAkB,KAAK,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAErE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAG3D;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,mBAAmB,GAC/B,MAAM,SAAS,EACf,MAAM,cAAc,GAAG,IAAI,EAC3B,MAAM,OAAO,KACX,OAAO,CAAC,OAAO,CAMjB,CAAC"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * The steward read arm, with the governing-root load it needs.
3
+ *
4
+ * `can_steward_cell` (`auth/cell_authorize.ts`) is pure over
5
+ * `(auth, cell, root)`; `cell_steward_admits` is the composition every steward
6
+ * site runs after `can_view_cell` denies: it loads the governing root (active
7
+ * rows only, so a soft-deleted root stewards nothing) and applies the
8
+ * predicate. It short-circuits with no query unless the caller is
9
+ * authenticated and the cell is a pending or rejected contribution.
10
+ *
11
+ * **Where the steward arm applies** — the surfaces a moderator works from:
12
+ * `cell_get`, `cell_moderate` (whose root-authority check it lets a non-admin
13
+ * root owner reach), the cell-scoped fact route, and `cell_list`'s general
14
+ * predicate (the SQL twin in `query_cell_list`).
15
+ *
16
+ * **Where it must not** — every other read keeps `can_view_cell` alone:
17
+ * `cell_clone` (a steward copying a contribution out from under its
18
+ * moderation), the parent check on `cell_create` (a steward contributing under
19
+ * a pending post), the relation endpoint gates, `filter_visible_target_ids`
20
+ * (the `cell_get` bundle), the field / item lists' SQL view filter
21
+ * (`db/cell_view_sql.ts`),
22
+ * `cell_list`'s `shared_with` semi-join (a grant footprint, not a moderation
23
+ * queue), and `cell_grant_revoke`'s `still_admitted` recompute. Widening one of
24
+ * these is a decision, not a sweep. Twin of the Rust `fuz_cell::cell_steward`.
25
+ *
26
+ * @module
27
+ */
28
+ import { query_cell_get } from '../db/cell_queries.js';
29
+ import { cell_moderation_is_stewardable } from '../db/cell_moderation.js';
30
+ import { can_steward_cell } from './cell_authorize.js';
31
+ /**
32
+ * Whether `auth` stewards `cell` — loads the governing root and applies
33
+ * `can_steward_cell`.
34
+ *
35
+ * Resolves `false` without a query for an unauthenticated caller, a root, or a
36
+ * cell whose moderation isn't `'pending'` / `'rejected'`; a missing or
37
+ * soft-deleted root is `false` too.
38
+ *
39
+ * @param deps - query deps
40
+ * @param auth - request context, or `null` for unauthenticated callers
41
+ * @param cell - the cell the caller couldn't otherwise view
42
+ * @returns whether the steward arm admits the caller
43
+ */
44
+ export const cell_steward_admits = async (deps, auth, cell) => {
45
+ if (auth === null || cell.root_id === null)
46
+ return false;
47
+ if (!cell_moderation_is_stewardable(cell.moderation))
48
+ return false;
49
+ const root = await query_cell_get(deps, cell.root_id);
50
+ if (!root)
51
+ return false;
52
+ return can_steward_cell(auth, cell, root);
53
+ };
@@ -212,6 +212,10 @@ export type ResolveActingActorResult = {
212
212
  * The available list is intentionally not echoed in this branch (treat
213
213
  * as opaque rejection).
214
214
  *
215
+ * `acting_actor_id` is compared lowercased, the form actor ids are stored and
216
+ * read back in, so a caller passing an id that skipped the `Uuid` schema
217
+ * still matches its actor whatever its case.
218
+ *
215
219
  * @param deps - query dependencies
216
220
  * @param account_id - the authenticated account
217
221
  * @param acting_actor_id - the requested acting actor id, or `undefined`
@@ -1 +1 @@
1
- {"version":3,"file":"request_context.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/request_context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAEvD,OAAO,EACN,KAAK,OAAO,EACZ,KAAK,KAAK,EAEV,KAAK,SAAS,EACd,MAAM,qBAAqB,CAAC;AAQ7B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAmBrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAA6C,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElG,OAAO,EAIN,oBAAoB,EACpB,0BAA0B,EAC1B,0BAA0B,EAC1B,sBAAsB,EACtB,MAAM,0BAA0B,CAAC;AAElC;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,cAAc;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED,0DAA0D;AAC1D,eAAO,MAAM,mBAAmB,oBAAoB,CAAC;AAErD;;;;;;;;GAQG;AACH,eAAO,MAAM,2BAA2B,4BAA4B,CAAC;AAErE;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,GAAI,GAAG,OAAO,KAAG,cAAc,GAAG,IAEjE,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,uBAAuB,GAAI,GAAG,OAAO,KAAG,cAQpD,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,WAAW,mBAAoB,SAAQ,cAAc;IAC1D,KAAK,EAAE,KAAK,CAAC;CACb;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,QAAQ,GACpB,KAAK,cAAc,GAAG,IAAI,EAC1B,MAAM,MAAM,EACZ,MAAK,IAAiB,KACpB,OACoF,CAAC;AAExF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,eAAO,MAAM,eAAe,GAC3B,KAAK,cAAc,GAAG,IAAI,EAC1B,MAAM,MAAM,EACZ,UAAU,MAAM,GAAG,IAAI,EACvB,MAAK,IAAiB,KACpB,OAKF,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,mBAAmB,GAC/B,KAAK,cAAc,GAAG,IAAI,EAC1B,OAAO,aAAa,CAAC,MAAM,CAAC,EAC5B,UAAU,MAAM,GAAG,IAAI,EACvB,MAAK,IAAiB,KACpB,OAMF,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,wBAAwB,GACjC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,WAAW,CAAA;CAAE,GAClC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,gBAAgB,CAAC;IAAC,SAAS,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,GACvF;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,sBAAsB,CAAA;CAAE,CAAC;AAEjD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,iBAAiB,MAAM,GAAG,SAAS,KACjC,OAAO,CAAC,wBAAwB,CAclC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,iCAAiC,GAC7C,MAAM,SAAS,EACf,4BAAuC,KACrC,iBAwCF,CAAC;AAuBF;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,0BAA0B,GAAI,GAAG,OAAO,EAAE,YAAY,MAAM,KAAG,QAAQ,GAAG,IAChC,CAAC;AAExD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,mBAAmB,GAAI,YAAY,MAAM,KAAG,iBAYxD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,YAAY,EAAE,iBAK1B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,aAAa,CAAC,MAAM,CAAC,KAAG,iBAW3D,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,wBAAwB,GACpC,kBAAkB,aAAa,CAAC,MAAM,CAAC,KACrC,iBAiBF,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,mBAAmB,GAC/B,KAAK,cAAc,EACnB,MAAM,SAAS,KACb,OAAO,CAAC,cAAc,CAQxB,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,qBAAqB,GACjC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,UAAU,MAAM,KACd,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAUpC,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,qBAAqB,GACjC,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,cAAc,GAAG,IAAI,CAI/B,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,MAAM,wBAAwB,GACjC;IAAE,KAAK,EAAE,OAAO,oBAAoB,CAAC;IAAC,SAAS,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,GACtF;IAAE,KAAK,EAAE,OAAO,0BAA0B,CAAA;CAAE,GAC5C;IAAE,KAAK,EAAE,OAAO,0BAA0B,CAAA;CAAE,GAC5C;IAAE,KAAK,EAAE,OAAO,sBAAsB,CAAA;CAAE,CAAC;AAE5C;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,mBAAmB,GAC5B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,eAAe,EAAE,cAAc,GAAG,IAAI,CAAA;CAAE,GACpD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,GAAG,GAAG,GAAG,CAAC;IAAC,IAAI,EAAE,wBAAwB,CAAA;CAAE,CAAC;AAEpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,eAAO,MAAM,yBAAyB,GACrC,MAAM,SAAS,EACf,YAAY,MAAM,GAAG,IAAI,EACzB,MAAM,SAAS,EACf,cAAc,MAAM,GAAG,SAAS,KAC9B,OAAO,CAAC,mBAAmB,CAwC7B,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,SAAS,KACb,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAoB5D,CAAC"}
1
+ {"version":3,"file":"request_context.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/request_context.ts"],"names":[],"mappings":"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"}
@@ -181,6 +181,10 @@ export const has_any_scoped_role = (ctx, roles, scope_id, now = new Date()) => {
181
181
  * The available list is intentionally not echoed in this branch (treat
182
182
  * as opaque rejection).
183
183
  *
184
+ * `acting_actor_id` is compared lowercased, the form actor ids are stored and
185
+ * read back in, so a caller passing an id that skipped the `Uuid` schema
186
+ * still matches its actor whatever its case.
187
+ *
184
188
  * @param deps - query dependencies
185
189
  * @param account_id - the authenticated account
186
190
  * @param acting_actor_id - the requested acting actor id, or `undefined`
@@ -198,7 +202,8 @@ export const resolve_acting_actor = async (deps, account_id, acting_actor_id) =>
198
202
  available: actors.map((a) => ({ id: a.id, name: a.name }))
199
203
  };
200
204
  }
201
- const match = actors.find((a) => a.id === acting_actor_id);
205
+ const acting = acting_actor_id.toLowerCase();
206
+ const match = actors.find((a) => a.id === acting);
202
207
  if (!match)
203
208
  return { ok: false, reason: 'actor_not_on_account' };
204
209
  return { ok: true, actor_id: match.id };
@@ -310,6 +310,10 @@ export declare const role_grant_offer_create_action_spec: {
310
310
  rate_limit: "account";
311
311
  };
312
312
  /**
313
+ * `rate_limit: 'account'`, matching `role_grant_offer_create`: every call locks
314
+ * the offer row and reads its grant, and a replay of an accepted offer answers
315
+ * without minting anything, so the account bucket is what bounds a loop of them.
316
+ *
313
317
  * `credential_types: ['session']` — see `docs/security.md` §Credential-channel
314
318
  * gating. The only offer verb that moves the *caller's own* authority:
315
319
  * accepting a pending admin offer over a leaked API token is a privilege pivot
@@ -359,6 +363,7 @@ export declare const role_grant_offer_accept_action_spec: {
359
363
  async: true;
360
364
  description: string;
361
365
  error_reasons: ("role_grant_offer_terminal" | "role_grant_offer_expired" | "role_grant_offer_not_found" | "role_grant_offer_role_not_grantable" | "role_grant_offer_not_authorized" | "role_grant_offer_actor_mismatch" | "role_grant_builtin_scoped")[];
366
+ rate_limit: "account";
362
367
  };
363
368
  export declare const role_grant_offer_decline_action_spec: {
364
369
  method: string;
@@ -1 +1 @@
1
- {"version":3,"file":"role_grant_offer_action_specs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_action_specs.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"role_grant_offer_action_specs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_action_specs.ts"],"names":[],"mappings":"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"}
@@ -240,6 +240,10 @@ export const role_grant_offer_create_action_spec = {
240
240
  rate_limit: 'account'
241
241
  };
242
242
  /**
243
+ * `rate_limit: 'account'`, matching `role_grant_offer_create`: every call locks
244
+ * the offer row and reads its grant, and a replay of an accepted offer answers
245
+ * without minting anything, so the account bucket is what bounds a loop of them.
246
+ *
243
247
  * `credential_types: ['session']` — see `docs/security.md` §Credential-channel
244
248
  * gating. The only offer verb that moves the *caller's own* authority:
245
249
  * accepting a pending admin offer over a leaked API token is a privilege pivot
@@ -268,7 +272,8 @@ export const role_grant_offer_accept_action_spec = {
268
272
  ERROR_ROLE_GRANT_OFFER_ROLE_NOT_GRANTABLE,
269
273
  ERROR_ROLE_GRANT_BUILTIN_SCOPED,
270
274
  ERROR_ROLE_GRANT_OFFER_NOT_AUTHORIZED
271
- ]
275
+ ],
276
+ rate_limit: 'account'
272
277
  };
273
278
  export const role_grant_offer_decline_action_spec = {
274
279
  method: 'role_grant_offer_decline',
@@ -1 +1 @@
1
- {"version":3,"file":"role_grant_offer_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,EAGN,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAKN,KAAK,gBAAgB,EACrB,MAAM,kBAAkB,CAAC;AA4B1B,OAAO,EAGN,KAAK,cAAc,EACnB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAEnD,OAAO,EAON,KAAK,kBAAkB,EACvB,MAAM,qCAAqC,CAAC;AAyC7C;;;;;;;;GAQG;AACH,MAAM,MAAM,6BAA6B,GAAG,CAC3C,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EACvE,IAAI,EAAE;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,EACrB,GAAG,EAAE,aAAa,KACd,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEhC,qDAAqD;AACrD,MAAM,WAAW,2BAA2B;IAC3C;;;;;OAKG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,0FAA0F;IAC1F,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,6BAA6B,CAAC;CAC1C;AA6CD;;;;;;;GAOG;AACH,eAAO,MAAM,+BAA+B,GAC3C,MAAM,iBAAiB,GAAG;IACzB,mBAAmB,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CAChD,EACD,UAAS,2BAAgC,KACvC,KAAK,CAAC,SAAS,CAsnBjB,CAAC"}
1
+ {"version":3,"file":"role_grant_offer_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,EAGN,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAKN,KAAK,gBAAgB,EACrB,MAAM,kBAAkB,CAAC;AA4B1B,OAAO,EAGN,KAAK,cAAc,EACnB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAEnD,OAAO,EAON,KAAK,kBAAkB,EACvB,MAAM,qCAAqC,CAAC;AAyC7C;;;;;;;;GAQG;AACH,MAAM,MAAM,6BAA6B,GAAG,CAC3C,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EACvE,IAAI,EAAE;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,EACrB,GAAG,EAAE,aAAa,KACd,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEhC,qDAAqD;AACrD,MAAM,WAAW,2BAA2B;IAC3C;;;;;OAKG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,0FAA0F;IAC1F,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,6BAA6B,CAAC;CAC1C;AA6CD;;;;;;;GAOG;AACH,eAAO,MAAM,+BAA+B,GAC3C,MAAM,iBAAiB,GAAG;IACzB,mBAAmB,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CAChD,EACD,UAAS,2BAAgC,KACvC,KAAK,CAAC,SAAS,CA8nBjB,CAAC"}
@@ -296,8 +296,9 @@ export const create_role_grant_offer_actions = (deps, options = {}) => {
296
296
  // Look up the grantor's account_id inside the transaction so the
297
297
  // post-commit notification has a valid target. One cheap SELECT by
298
298
  // PK — the alternative (widening `query_accept_offer` again) would
299
- // bleed transport concerns into the query layer.
300
- const grantor_actor = notification_sender
299
+ // bleed transport concerns into the query layer. A replay
300
+ // (`created: false`) notifies nothing, so it skips the lookup.
301
+ const grantor_actor = notification_sender && result.created
301
302
  ? await query_actor_by_id(ctx, result.offer.from_actor_id)
302
303
  : null;
303
304
  const grantor_account_id = grantor_actor?.account_id ?? null;
@@ -312,21 +313,27 @@ export const create_role_grant_offer_actions = (deps, options = {}) => {
312
313
  // grantor sees "accepted" and each superseded grantor sees "supersede"
313
314
  // only once the accept has durably committed — and never if it rolls
314
315
  // back (the dispatch site discards `post_commit_effects` on rollback).
315
- emit_after_commit(ctx, () => {
316
- fan_out_audit_events(result.audit_events, audit);
317
- if (notification_sender && grantor_account_id) {
318
- notification_sender.send_to_account(grantor_account_id, build_role_grant_offer_accepted_notification({ offer: offer_json }));
319
- }
320
- if (notification_sender) {
321
- for (const sib of supersede_payloads) {
322
- notification_sender.send_to_account(sib.from_account_id, build_role_grant_offer_supersede_notification({
323
- offer: sib.offer,
324
- reason: 'sibling_accepted',
325
- cause_id: result.offer.id
326
- }));
316
+ // A replay (or a race loser) wrote nothing — the accept that minted the
317
+ // grant already audited it and notified the grantor — so it queues
318
+ // nothing; repeating the notification would let any replay send them
319
+ // without bound.
320
+ if (result.created) {
321
+ emit_after_commit(ctx, () => {
322
+ fan_out_audit_events(result.audit_events, audit);
323
+ if (notification_sender && grantor_account_id) {
324
+ notification_sender.send_to_account(grantor_account_id, build_role_grant_offer_accepted_notification({ offer: offer_json }));
327
325
  }
328
- }
329
- });
326
+ if (notification_sender) {
327
+ for (const sib of supersede_payloads) {
328
+ notification_sender.send_to_account(sib.from_account_id, build_role_grant_offer_supersede_notification({
329
+ offer: sib.offer,
330
+ reason: 'sibling_accepted',
331
+ cause_id: result.offer.id
332
+ }));
333
+ }
334
+ }
335
+ });
336
+ }
330
337
  return {
331
338
  role_grant_id: result.role_grant.id,
332
339
  offer: offer_json,
@@ -232,7 +232,7 @@ export interface AcceptOfferInput {
232
232
  export interface AcceptOfferResult {
233
233
  role_grant: RoleGrant;
234
234
  offer: RoleGrantOffer;
235
- /** `true` if this call is the one that accepted the offer (new role_grant inserted); `false` on a race returning the already-created role_grant. */
235
+ /** `true` if this call is the one that accepted the offer (new role_grant inserted); `false` on a replay (or a race loser) returning the already-created, still-held role_grant — the caller then audits and notifies nothing. */
236
236
  created: boolean;
237
237
  /**
238
238
  * Sibling offers superseded by this accept — empty on the race-loser path.
@@ -252,15 +252,17 @@ export interface AcceptOfferResult {
252
252
  * inside a transaction — the caller's route spec should declare
253
253
  * `transaction: true` (or wrap explicitly).
254
254
  *
255
- * Idempotent on race: if a second concurrent call observes the offer
256
- * already accepted, returns the existing role_grant rather than creating a
257
- * duplicate or throwing.
255
+ * Idempotent on replay and on race: a call that observes the offer already
256
+ * accepted returns the existing role_grant (`created: false`) rather than
257
+ * creating a duplicate or throwing — while that grant is still held. A grant
258
+ * revoked since the accept, or gone, makes the offer terminal.
258
259
  *
259
260
  * Error map:
260
261
  * - `RoleGrantOfferNotFoundError` — offer does not exist, or belongs to a
261
262
  * different recipient (IDOR guard). The offer row is untouched.
262
263
  * - `RoleGrantOfferAlreadyTerminalError` — offer is declined, retracted, or
263
- * superseded.
264
+ * superseded; or accepted, and its grant revoked, gone, or bound to another
265
+ * actor.
264
266
  * - `RoleGrantOfferExpiredError` — offer is pending but past `expires_at`.
265
267
  * - `RoleGrantOfferActorMismatchError` — actor-targeted offer, different actor.
266
268
  * - `RoleGrantOfferBuiltinRefusedError` — a builtin-role offer that must not
@@ -277,7 +279,7 @@ export interface AcceptOfferResult {
277
279
  * @mutates `role_grant_offer` siblings - stamps `superseded_at` on every other pending offer for the tuple
278
280
  * @mutates `audit_log` table - emits `role_grant_offer_accept` + `role_grant_create` + one `role_grant_offer_supersede` per sibling
279
281
  * @throws RoleGrantOfferNotFoundError if the offer is missing or belongs to another recipient
280
- * @throws RoleGrantOfferAlreadyTerminalError if the offer is declined, retracted, or superseded
282
+ * @throws RoleGrantOfferAlreadyTerminalError if the offer is declined, retracted, or superseded, or accepted with its grant revoked, gone, or bound to another actor
281
283
  * @throws RoleGrantOfferExpiredError if the offer is pending but past `expires_at`
282
284
  * @throws RoleGrantOfferActorMismatchError if the offer is actor-targeted to a different actor
283
285
  * @throws RoleGrantOfferBuiltinRefusedError if a builtin-role offer must not mint (see `BuiltinOfferRefusal`)
@@ -1 +1 @@
1
- {"version":3,"file":"role_grant_offer_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_queries.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"role_grant_offer_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_queries.ts"],"names":[],"mappings":"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"}