@fuzdev/fuz_app 0.103.0 → 0.104.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 (31) hide show
  1. package/dist/auth/CLAUDE.md +29 -11
  2. package/dist/auth/cell_action_specs.d.ts +226 -1
  3. package/dist/auth/cell_action_specs.d.ts.map +1 -1
  4. package/dist/auth/cell_action_specs.js +79 -1
  5. package/dist/auth/cell_actions.d.ts +32 -9
  6. package/dist/auth/cell_actions.d.ts.map +1 -1
  7. package/dist/auth/cell_actions.js +126 -12
  8. package/dist/auth/cell_audit_events.d.ts +1 -1
  9. package/dist/auth/cell_audit_events.d.ts.map +1 -1
  10. package/dist/auth/cell_audit_events.js +3 -2
  11. package/dist/auth/cell_audit_metadata.d.ts +15 -0
  12. package/dist/auth/cell_audit_metadata.d.ts.map +1 -1
  13. package/dist/auth/cell_audit_metadata.js +11 -0
  14. package/dist/db/CLAUDE.md +16 -6
  15. package/dist/db/cell_ddl.d.ts +14 -1
  16. package/dist/db/cell_ddl.d.ts.map +1 -1
  17. package/dist/db/cell_ddl.js +20 -0
  18. package/dist/db/cell_queries.d.ts +45 -0
  19. package/dist/db/cell_queries.d.ts.map +1 -1
  20. package/dist/db/cell_queries.js +40 -3
  21. package/dist/testing/CLAUDE.md +25 -16
  22. package/dist/testing/cross_backend/cell_gated_create.d.ts +24 -0
  23. package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
  24. package/dist/testing/cross_backend/cell_gated_create.js +177 -35
  25. package/dist/testing/cross_backend/expected_schema.json +3 -0
  26. package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
  27. package/dist/testing/cross_backend/full_spine_mount.js +7 -4
  28. package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts +12 -7
  29. package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts.map +1 -1
  30. package/dist/testing/cross_backend/test_cell_gated_create_authorize.js +54 -15
  31. package/package.json +1 -1
@@ -47,26 +47,49 @@ import { type CellRow } from '../db/cell_queries.js';
47
47
  import type { CellData } from './cell_data_schema.js';
48
48
  /**
49
49
  * Input to a `CellCreateAuthorize` callback — the TS twin of the Rust
50
- * `CellCreateAuthorizeInput`.
50
+ * `CellCreateAuthorizeInput`. **Parent-aware**: it carries the directory
51
+ * context (`parent_id` / the handler-resolved `root_id`) so the authorizer can
52
+ * resolve the governing root's policy.
51
53
  */
52
54
  export interface CellCreateAuthorizeInput {
53
55
  /** The cell's `kind` (the top-level `cell.kind` value); `null` for a typeless cell. */
54
56
  kind: string | null;
55
57
  /** The cell `data` (kind-free — a `kind` key is rejected upstream). For richer M3-era policies (e.g. content pre-screen). */
56
58
  data: CellData;
59
+ /** The immediate container the create targets. `null` = a root creation; otherwise a contribution under that parent. */
60
+ parent_id: Uuid | null;
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
+ 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. */
64
+ root_data: CellData | null;
57
65
  /** Target scope — designed-in for M2 space-scoping; **always `null` in v1** (cells carry no scope column). */
58
66
  scope_id: Uuid | null;
59
67
  }
60
68
  /**
61
- * Opt-in creation authorizer — the TS twin of the Rust `CellCreateAuthorize`
62
- * trait. Answers "may *this actor* create *this kind* of cell?". Runs in
63
- * `cell_create` after `validate_data`, before the insert; returning `false`
64
- * surfaces as the existing `cell_not_found` 404 (IDOR mask), so a denied
65
- * caller can't distinguish a gated kind from a missing resource. Omitted =
66
- * today's open create (all consumers untouched). Async-capable (DB / policy
67
- * calls) — the create handler is already async with `auth` in hand.
69
+ * An authorizer's decision for a `cell_create` — the TS twin of the Rust
70
+ * `Verdict`. Folds the moderation outcome into the authority decision (one
71
+ * policy resolution, not two): `{allow: false}` denies (the handler surfaces a
72
+ * 403 `forbidden` for a viewable parent / a root creation), `{allow: true,
73
+ * moderation_required}` admits — `true` → born `pending` + private, `false` →
74
+ * born `approved` at the author's visibility.
68
75
  */
69
- export type CellCreateAuthorize = (auth: RequestActorContext, input: CellCreateAuthorizeInput) => boolean | Promise<boolean>;
76
+ export type CellCreateVerdict = {
77
+ allow: false;
78
+ } | {
79
+ allow: true;
80
+ moderation_required: boolean;
81
+ };
82
+ /**
83
+ * Opt-in, parent-aware creation authorizer — the TS twin of the Rust
84
+ * `CellCreateAuthorize` trait. Gates both roots (`parent_id = null`) and
85
+ * contributions; answers "may *this actor* create *this kind* here?" and
86
+ * returns a `CellCreateVerdict`. Runs in `cell_create` after `validate_data`
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
90
+ * impl closes over its own `db` (the create handler stays unaware).
91
+ */
92
+ export type CellCreateAuthorize = (auth: RequestActorContext, input: CellCreateAuthorizeInput) => CellCreateVerdict | Promise<CellCreateVerdict>;
70
93
  /**
71
94
  * Dependencies for `create_cell_actions`.
72
95
  *
@@ -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,EAAW,KAAK,mBAAmB,EAAC,MAAM,sBAAsB,CAAC;AAExE,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAEhD,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EA8BN,KAAK,QAAQ,EAEb,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAQN,KAAK,OAAO,EACZ,MAAM,uBAAuB,CAAC;AAkB/B,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,uBAAuB,CAAC;AAEpD;;;GAGG;AACH,MAAM,WAAW,wBAAwB;IACxC,uFAAuF;IACvF,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,6HAA6H;IAC7H,IAAI,EAAE,QAAQ,CAAC;IACf,8GAA8G;IAC9G,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;CACtB;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,mBAAmB,GAAG,CACjC,IAAI,EAAE,mBAAmB,EACzB,KAAK,EAAE,wBAAwB,KAC3B,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEhC;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,OAAO,CAAC,GAAG;IACtE,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,QAAQ,CAAC;IAC7C,gBAAgB,CAAC,EAAE,mBAAmB,CAAC;CACvC,CAAC;AAkCF,eAAO,MAAM,YAAY,GAAI,KAAK,OAAO,KAAG,QAa1C,CAAC;AA2BH,+CAA+C;AAC/C,eAAO,MAAM,mBAAmB,GAAI,MAAM,cAAc,KAAG,KAAK,CAAC,SAAS,CA+fzE,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,EAAW,KAAK,mBAAmB,EAAC,MAAM,sBAAsB,CAAC;AAExE,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAEhD,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EAoCN,KAAK,QAAQ,EAEb,MAAM,wBAAwB,CAAC;AAChC,OAAO,EASN,KAAK,OAAO,EACZ,MAAM,uBAAuB,CAAC;AAsB/B,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,uBAAuB,CAAC;AAEpD;;;;;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;IAAC,KAAK,EAAE,KAAK,CAAA;CAAC,GAAG;IAAC,KAAK,EAAE,IAAI,CAAC;IAAC,mBAAmB,EAAE,OAAO,CAAA;CAAC,CAAC;AAE7F;;;;;;;;;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,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,OAAO,CAAC,GAAG;IACtE,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,QAAQ,CAAC;IAC7C,gBAAgB,CAAC,EAAE,mBAAmB,CAAC;CACvC,CAAC;AAkCF,eAAO,MAAM,YAAY,GAAI,KAAK,OAAO,KAAG,QAgB1C,CAAC;AA2BH,+CAA+C;AAC/C,eAAO,MAAM,mBAAmB,GAAI,MAAM,cAAc,KAAG,KAAK,CAAC,SAAS,CAinBzE,CAAC"}
@@ -44,8 +44,8 @@ import { jsonrpc_errors, dev_only } from '../http/jsonrpc_errors.js';
44
44
  import { is_pg_unique_violation } from '../db/pg_error.js';
45
45
  import { has_role } from './request_context.js';
46
46
  import { ROLE_ADMIN } from './role_schema.js';
47
- import { cell_create_action_spec, cell_get_action_spec, cell_update_action_spec, cell_delete_action_spec, cell_list_action_spec, cell_clone_action_spec, ERROR_CELL_NOT_FOUND, ERROR_CELL_PATH_ADMIN_ONLY, ERROR_CELL_PATH_TAKEN, ERROR_CELL_VISIBILITY_MANAGE_ONLY, ERROR_CELL_GET_REQUIRES_ID_OR_PATH, ERROR_CELL_KIND_IN_DATA, ERROR_CELL_KIND_EMPTY, ERROR_CELL_LIST_CREATED_BY_REQUIRES_AUTH, ERROR_CELL_LIST_SHARED_WITH_REQUIRES_AUTH, CELL_LIST_LIMIT_DEFAULT, CELL_RELATIONS_BUNDLE_LIMIT, } from './cell_action_specs.js';
48
- import { query_cell_create, query_cell_get, query_cell_get_by_path, query_cell_update, query_cell_delete, query_cell_list, query_cell_load_many, } from '../db/cell_queries.js';
47
+ import { cell_create_action_spec, cell_get_action_spec, cell_update_action_spec, cell_delete_action_spec, cell_list_action_spec, cell_clone_action_spec, cell_moderate_action_spec, ERROR_CELL_NOT_FOUND, ERROR_CELL_PATH_ADMIN_ONLY, ERROR_CELL_PATH_TAKEN, ERROR_CELL_VISIBILITY_MANAGE_ONLY, ERROR_CELL_GET_REQUIRES_ID_OR_PATH, ERROR_CELL_KIND_IN_DATA, ERROR_CELL_KIND_EMPTY, ERROR_CELL_CREATE_FORBIDDEN, ERROR_CELL_MODERATE_FORBIDDEN, ERROR_CELL_NOT_A_CONTRIBUTION, ERROR_CELL_LIST_CREATED_BY_REQUIRES_AUTH, ERROR_CELL_LIST_SHARED_WITH_REQUIRES_AUTH, CELL_LIST_LIMIT_DEFAULT, CELL_RELATIONS_BUNDLE_LIMIT, } from './cell_action_specs.js';
48
+ import { query_cell_create, query_cell_get, query_cell_get_by_path, query_cell_update, query_cell_delete, query_cell_set_moderation, query_cell_list, query_cell_load_many, } from '../db/cell_queries.js';
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';
@@ -87,6 +87,9 @@ export const to_cell_json = (row) => ({
87
87
  kind: row.kind,
88
88
  visibility: row.visibility,
89
89
  refs: row.refs,
90
+ parent_id: row.parent_id,
91
+ root_id: row.root_id,
92
+ moderation: row.moderation,
90
93
  created_by: row.created_by,
91
94
  updated_by: row.updated_by,
92
95
  created_at: typeof row.created_at === 'string' ? row.created_at : row.created_at.toISOString(),
@@ -156,15 +159,75 @@ export const create_cell_actions = (deps) => {
156
159
  // known kinds with malformed payloads surface as invalid_params.
157
160
  const validated_data = validate_data_or_throw(input.data);
158
161
  const kind = input.kind ?? null;
159
- // Creation authorizer (opt-in): may this actor create this kind? Runs
160
- // after shape validation, before the insert. Deny → 404 IDOR mask (the
161
- // established cell-layer deny shape — a gated kind is indistinguishable
162
- // from a missing resource). Receives the **raw** `input.data` (not the
163
- // `validate_data`-normalized value) to match the Rust twin, so a
164
- // consumer with a transforming validator can't diverge TS↔Rust.
165
- if (authorize_create &&
166
- !(await authorize_create(auth, { kind, data: input.data, scope_id: null }))) {
167
- throw jsonrpc_errors.not_found('cell', { reason: ERROR_CELL_NOT_FOUND });
162
+ // Directory tree: resolve the governing `root_id` from the parent (and
163
+ // the root's `data` for the authorizer's policy read). Runs whether or
164
+ // not an authorizer is mounted — `root_id` is written regardless, and an
165
+ // unviewable parent 404-masks the attempt (the same IDOR mask the read
166
+ // path uses; never reveals a hidden container). A root creation
167
+ // (`parent_id` null/absent) has `root_id = null`. `root_data` is read
168
+ // **in-tx** (only when an authorizer is mounted), reusing the parent row
169
+ // when the parent *is* the root, else one read by `root_id` — so the
170
+ // authorizer stays pure + the read rides the handler's connection (no
171
+ // separate-handle deadlock on the single-connection PGlite).
172
+ const parent_id = input.parent_id ?? null;
173
+ let root_id = null;
174
+ let root_data = null;
175
+ if (parent_id !== null) {
176
+ const parent = await query_cell_get(ctx, parent_id);
177
+ if (!parent)
178
+ throw jsonrpc_errors.not_found('cell', { reason: ERROR_CELL_NOT_FOUND });
179
+ const parent_grants = await query_cell_grant_list_for_cell(ctx, parent.id);
180
+ if (!can_view_cell(auth, parent, parent_grants)) {
181
+ throw jsonrpc_errors.not_found('cell', { reason: ERROR_CELL_NOT_FOUND });
182
+ }
183
+ root_id = parent.root_id ?? parent.id;
184
+ if (authorize_create) {
185
+ root_data =
186
+ root_id === parent.id
187
+ ? parent.data
188
+ : ((await query_cell_get(ctx, root_id))?.data ?? null);
189
+ }
190
+ }
191
+ // Parent-aware creation authorizer (opt-in). Runs after shape validation
192
+ // and after the parent-resolve (a hidden parent already 404-masked).
193
+ // `{allow: false}` for a *viewable* parent / a root creation is a 403
194
+ // `forbidden` (not the 404 mask); an `{allow: true}` verdict folds the
195
+ // moderation outcome. Receives the **raw** `input.data` (not the
196
+ // `validate_data`-normalized value) to match the Rust twin, so a consumer
197
+ // with a transforming validator can't diverge TS↔Rust. `scope_id` is
198
+ // `null` in v1. Without an authorizer, create is open: `moderation` stays
199
+ // null and the author's visibility holds.
200
+ let moderation = null;
201
+ let visibility = input.visibility;
202
+ if (authorize_create) {
203
+ const verdict = await authorize_create(auth, {
204
+ kind,
205
+ data: input.data,
206
+ parent_id,
207
+ root_id,
208
+ root_data,
209
+ scope_id: null,
210
+ });
211
+ if (!verdict.allow) {
212
+ throw jsonrpc_errors.forbidden('cell creation is not permitted here', {
213
+ reason: ERROR_CELL_CREATE_FORBIDDEN,
214
+ });
215
+ }
216
+ // Moderation is a *contribution* concept — only stamp it for a create
217
+ // with a parent. A root / unparented create stays `moderation = null`
218
+ // at the author's visibility even under a mounted authorizer (there is
219
+ // no container to be moderated under).
220
+ if (parent_id !== null) {
221
+ if (verdict.moderation_required) {
222
+ // Born pending: stays private until `cell_moderate` approves it.
223
+ moderation = 'pending';
224
+ visibility = 'private';
225
+ }
226
+ else {
227
+ // Born approved + live immediately at the author's visibility.
228
+ moderation = 'approved';
229
+ }
230
+ }
168
231
  }
169
232
  let row;
170
233
  try {
@@ -176,8 +239,11 @@ export const create_cell_actions = (deps) => {
176
239
  // schema-validated runtime shape.
177
240
  data: validated_data,
178
241
  kind,
179
- visibility: input.visibility,
242
+ visibility,
180
243
  path: input.path ?? null,
244
+ parent_id,
245
+ root_id,
246
+ moderation,
181
247
  created_by: auth.actor.id,
182
248
  });
183
249
  }
@@ -338,6 +404,51 @@ export const create_cell_actions = (deps) => {
338
404
  emit_cell_audit(ctx, 'cell_delete', existing, deps, auth);
339
405
  return { ok: true, deleted: true };
340
406
  };
407
+ const moderate_handler = async (input, ctx) => {
408
+ const auth = ctx.auth;
409
+ const existing = await query_cell_get(ctx, input.cell_id);
410
+ // 404 covers miss + unviewable so a pending (private) contribution
411
+ // doesn't leak existence to a non-viewer.
412
+ if (!existing)
413
+ throw jsonrpc_errors.not_found('cell', { reason: ERROR_CELL_NOT_FOUND });
414
+ const grants = await query_cell_grant_list_for_cell(ctx, existing.id);
415
+ if (!can_view_cell(auth, existing, grants)) {
416
+ throw jsonrpc_errors.not_found('cell', { reason: ERROR_CELL_NOT_FOUND });
417
+ }
418
+ // Only a contribution (with a governing root) is moderatable.
419
+ if (existing.root_id === null) {
420
+ throw jsonrpc_errors.invalid_params('cell is not a contribution (no governing root to moderate under)', { reason: ERROR_CELL_NOT_A_CONTRIBUTION });
421
+ }
422
+ // Authority is over the governing ROOT (admin / root owner), not the
423
+ // contribution — the author manages the contribution and could otherwise
424
+ // self-approve. A viewable-but-unauthorized caller (incl. the author) → 403.
425
+ const root = await query_cell_get(ctx, existing.root_id);
426
+ if (!root)
427
+ throw jsonrpc_errors.not_found('cell', { reason: ERROR_CELL_NOT_FOUND });
428
+ if (!can_manage_cell(auth, root)) {
429
+ throw jsonrpc_errors.forbidden('cell_moderate requires moderation authority over the governing root', { reason: ERROR_CELL_MODERATE_FORBIDDEN });
430
+ }
431
+ const approved = input.moderation === 'approved';
432
+ const updated = await query_cell_set_moderation(ctx, input.cell_id, input.moderation, {
433
+ set_visibility_public: approved,
434
+ updated_by: auth.actor.id,
435
+ });
436
+ // Raced with a deleter between the authz check and the UPDATE.
437
+ if (!updated)
438
+ throw jsonrpc_errors.not_found('cell', { reason: ERROR_CELL_NOT_FOUND });
439
+ deps.audit.emit(ctx, {
440
+ event_type: 'cell_moderate',
441
+ actor_id: auth.actor.id,
442
+ account_id: auth.account.id,
443
+ ip: ctx.client_ip,
444
+ metadata: {
445
+ cell_id: updated.id,
446
+ root_id: existing.root_id,
447
+ moderation: input.moderation,
448
+ },
449
+ });
450
+ return { cell: to_cell_json(updated) };
451
+ };
341
452
  /**
342
453
  * Insert one cloned cell row owned by the caller.
343
454
  *
@@ -542,6 +653,8 @@ export const create_cell_actions = (deps) => {
542
653
  ref: input.ref,
543
654
  created_by: input.created_by,
544
655
  path_prefix: input.path_prefix,
656
+ root_id: input.root_id,
657
+ moderation: input.moderation,
545
658
  viewer_actor_id: caller_actor_id,
546
659
  viewer_is_admin: auth ? has_role(auth, ROLE_ADMIN) : false,
547
660
  caller_actor_id,
@@ -572,5 +685,6 @@ export const create_cell_actions = (deps) => {
572
685
  rpc_action(cell_delete_action_spec, delete_handler),
573
686
  rpc_action(cell_list_action_spec, list_handler),
574
687
  rpc_action(cell_clone_action_spec, clone_handler),
688
+ rpc_action(cell_moderate_action_spec, moderate_handler),
575
689
  ];
576
690
  };
@@ -20,7 +20,7 @@ import type { z } from 'zod';
20
20
  /**
21
21
  * Cell-layer `event_type → metadata schema` map for `extra_events`.
22
22
  *
23
- * Covers the six generic cell verbs' mutation events plus the grant /
23
+ * Covers the seven generic cell verbs' mutation events plus the grant /
24
24
  * field / item relation events. Read-only verbs (`cell_get`, `cell_list`,
25
25
  * `cell_*_list`, `cell_audit_list`) emit nothing and are absent here.
26
26
  */
@@ -1 +1 @@
1
- {"version":3,"file":"cell_audit_events.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cell_audit_events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAiB3B;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAYjE,CAAC"}
1
+ {"version":3,"file":"cell_audit_events.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cell_audit_events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAqB3B;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAajE,CAAC"}
@@ -16,14 +16,14 @@
16
16
  *
17
17
  * @module
18
18
  */
19
- import { CellAuditMetadata, CellCloneAuditMetadata } from './cell_audit_metadata.js';
19
+ import { CellAuditMetadata, CellCloneAuditMetadata, CellModerateAuditMetadata, } from './cell_audit_metadata.js';
20
20
  import { CellGrantCreateAuditMetadata, CellGrantRevokeAuditMetadata, } from './cell_grant_audit_metadata.js';
21
21
  import { CellFieldSetAuditMetadata, CellFieldDeleteAuditMetadata, } from './cell_field_audit_metadata.js';
22
22
  import { CellItemInsertAuditMetadata, CellItemMoveAuditMetadata, CellItemDeleteAuditMetadata, } from './cell_item_audit_metadata.js';
23
23
  /**
24
24
  * Cell-layer `event_type → metadata schema` map for `extra_events`.
25
25
  *
26
- * Covers the six generic cell verbs' mutation events plus the grant /
26
+ * Covers the seven generic cell verbs' mutation events plus the grant /
27
27
  * field / item relation events. Read-only verbs (`cell_get`, `cell_list`,
28
28
  * `cell_*_list`, `cell_audit_list`) emit nothing and are absent here.
29
29
  */
@@ -32,6 +32,7 @@ export const cell_audit_events = {
32
32
  cell_update: CellAuditMetadata,
33
33
  cell_delete: CellAuditMetadata,
34
34
  cell_clone: CellCloneAuditMetadata,
35
+ cell_moderate: CellModerateAuditMetadata,
35
36
  cell_grant_create: CellGrantCreateAuditMetadata,
36
37
  cell_grant_revoke: CellGrantRevokeAuditMetadata,
37
38
  cell_field_set: CellFieldSetAuditMetadata,
@@ -45,4 +45,19 @@ export declare const CellCloneAuditMetadata: z.ZodObject<{
45
45
  kind: z.ZodOptional<z.ZodString>;
46
46
  }, z.core.$loose>;
47
47
  export type CellCloneAuditMetadata = z.infer<typeof CellCloneAuditMetadata>;
48
+ /**
49
+ * Metadata envelope for `cell_moderate`. `cell_id` is the moderated
50
+ * contribution; `root_id` is its governing root (the container whose
51
+ * moderation authority gated the call); `moderation` is the terminal decision
52
+ * applied (`'approved'` | `'rejected'`).
53
+ */
54
+ export declare const CellModerateAuditMetadata: z.ZodObject<{
55
+ cell_id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
56
+ root_id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
57
+ moderation: z.ZodEnum<{
58
+ rejected: "rejected";
59
+ approved: "approved";
60
+ }>;
61
+ }, z.core.$loose>;
62
+ export type CellModerateAuditMetadata = z.infer<typeof CellModerateAuditMetadata>;
48
63
  //# sourceMappingURL=cell_audit_metadata.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cell_audit_metadata.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cell_audit_metadata.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAGtB;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB;;;;iBAI5B,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAElE;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,sBAAsB;;;;;;iBAMjC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC"}
1
+ {"version":3,"file":"cell_audit_metadata.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cell_audit_metadata.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAGtB;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB;;;;iBAI5B,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAElE;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,sBAAsB;;;;;;iBAMjC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAE5E;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB;;;;;;;iBAIpC,CAAC;AACH,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC"}
@@ -44,3 +44,14 @@ export const CellCloneAuditMetadata = z.looseObject({
44
44
  item_count: z.number().int().nonnegative(),
45
45
  kind: z.string().optional(),
46
46
  });
47
+ /**
48
+ * Metadata envelope for `cell_moderate`. `cell_id` is the moderated
49
+ * contribution; `root_id` is its governing root (the container whose
50
+ * moderation authority gated the call); `moderation` is the terminal decision
51
+ * applied (`'approved'` | `'rejected'`).
52
+ */
53
+ export const CellModerateAuditMetadata = z.looseObject({
54
+ cell_id: Uuid,
55
+ root_id: Uuid,
56
+ moderation: z.enum(['approved', 'rejected']),
57
+ });
package/dist/db/CLAUDE.md CHANGED
@@ -53,19 +53,29 @@ The wire schemas + RPC handlers + authz predicates for this layer live in
53
53
  `('private', 'public')`) — a top-level access-control column, peer to
54
54
  `cell_grant`, not a `data` field. `cell.kind text` (nullable) is the
55
55
  write-once capability/identity axis — likewise a top-level column, not a
56
- `data` field (`idx_cell_kind` partial-active). Nullable `created_by` /
57
- `updated_by` FKs to `actor` (NULL = system origin). GIN on `data` / `refs`;
58
- global partial-unique on `path`.
56
+ `data` field (`idx_cell_kind` partial-active). `cell.parent_id` / `root_id`
57
+ (nullable self-FKs to `cell.id`) are the **directory tree** — immediate
58
+ container + governing root (`root_id = parent.root_id ?? parent.id`, null for a
59
+ root), immutable in v1 (`idx_cell_root` partial-active for subtree feeds).
60
+ `cell.moderation text` (nullable — `pending` / `approved` / `rejected`) is the
61
+ approval-lifecycle marker, peer to `visibility` (partial `idx_cell_moderation_pending`
62
+ on `root_id WHERE moderation = 'pending'` for the mod queue), set by the create
63
+ authorizer's verdict + the `cell_moderate` verb, never in `data`. Nullable
64
+ `created_by` / `updated_by` FKs to `actor` (NULL = system origin). GIN on `data`
65
+ / `refs`; global partial-unique on `path`.
59
66
  - **`cell_history_ddl.ts`** — dormant `cell_history` table
60
67
  (`CELL_HISTORY_MIGRATION_NS`, namespace `fuz_cell_history`), FK → `cell.id`.
61
68
  Ships present-but-unwritten; no snapshot lifecycle yet.
62
69
  - **`cell_queries.ts`** — `query_cell_create / get / get_by_path / update /
63
- delete`, `_list_by_kind / _list_by_creator`, the
70
+ delete / set_moderation`, `_list_by_kind / _list_by_creator`, the
64
71
  generic `query_cell_list` (filter + SQL-side visibility predicate mirroring
65
72
  `can_view_cell`; the `kind` filter narrows by the `cell.kind` column, the
66
- `ref` filter by `cell.refs`), and
73
+ `ref` filter by `cell.refs`, plus `root_id` / `moderation` filters for the
74
+ per-root feed + mod queue), and
67
75
  `query_cell_load_many` (bulk id load, no visibility filter — feeds the
68
- strict relation-read filter). `cell.refs` derived from
76
+ strict relation-read filter). `query_cell_set_moderation` is the dedicated
77
+ `cell_moderate` write (`moderation` is absent from `CellUpdatePatch`, so an
78
+ author can't self-approve via `cell_update`). `cell.refs` derived from
69
79
  `data` via `extract_refs` on create/update. `CellRow.grant_count` is a
70
80
  derived projection (correlated subquery on `idx_cell_grant_cell`).
71
81
  - **`cell_grant_queries.ts`** — resource-side ACL: `query_cell_grant_create`
@@ -69,8 +69,17 @@ export declare const CELL_VISIBILITY_TYPE = "\nDO $$ BEGIN\n\tCREATE TYPE cell_v
69
69
  * `CellCreateAuthorize`) and is **write-once**: set at INSERT and carried on
70
70
  * no update path, so a cell's kind is fixed at birth. Content stays
71
71
  * duck-typed in `data`; `kind` is a capability tag, not a content-type.
72
+ *
73
+ * `parent_id` / `root_id` are the **directory tree** (containment): `parent_id`
74
+ * is the immediate container (nullable self-FK; `NULL` = a root), `root_id` is
75
+ * the governing root denormalized for flat-subtree queries (`root_id =
76
+ * parent.root_id ?? parent.id`, so a root has `NULL`). Both are set once at
77
+ * create and immutable in v1 (carried on no update path). `moderation`
78
+ * (nullable text — `pending` / `approved` / `rejected`; `NULL` = unmoderated)
79
+ * is the approval-lifecycle marker, peer to `visibility` (a control field with
80
+ * a non-author writer — see `auth/cell_actions.ts`), never inside `data`.
72
81
  */
73
- export declare const CELL_SCHEMA = "\nCREATE TABLE IF NOT EXISTS cell (\n\tid UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n\tdata JSONB NOT NULL,\n\tkind TEXT,\n\tvisibility cell_visibility NOT NULL DEFAULT 'private',\n\tpath TEXT,\n\trefs TEXT[],\n\tcreated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n\tupdated_at TIMESTAMPTZ,\n\tdeleted_at TIMESTAMPTZ,\n\tcreated_by UUID REFERENCES actor(id) ON DELETE SET NULL,\n\tupdated_by UUID REFERENCES actor(id) ON DELETE SET NULL\n)";
82
+ export declare const CELL_SCHEMA = "\nCREATE TABLE IF NOT EXISTS cell (\n\tid UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n\tdata JSONB NOT NULL,\n\tkind TEXT,\n\tvisibility cell_visibility NOT NULL DEFAULT 'private',\n\tpath TEXT,\n\trefs TEXT[],\n\tparent_id UUID REFERENCES cell(id) ON DELETE SET NULL,\n\troot_id UUID REFERENCES cell(id) ON DELETE SET NULL,\n\tmoderation TEXT,\n\tcreated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n\tupdated_at TIMESTAMPTZ,\n\tdeleted_at TIMESTAMPTZ,\n\tcreated_by UUID REFERENCES actor(id) ON DELETE SET NULL,\n\tupdated_by UUID REFERENCES actor(id) ON DELETE SET NULL\n)";
74
83
  /**
75
84
  * Cell indexes — all active-only, partial on `deleted_at IS NULL`.
76
85
  *
@@ -83,6 +92,10 @@ export declare const CELL_SCHEMA = "\nCREATE TABLE IF NOT EXISTS cell (\n\tid UU
83
92
  * - `idx_cell_data`: shape-driven queries (`data @> ...`).
84
93
  * - `idx_cell_refs`: cells-by-fact discovery (cross-cell reference graph).
85
94
  * - `idx_cell_created_by`: "cells this actor created" queries.
95
+ * - `idx_cell_root`: flat-subtree feed/scope queries by governing root
96
+ * (`cell.root_id = ?`). Active-only.
97
+ * - `idx_cell_moderation_pending`: the moderation queue — pending
98
+ * contributions per governing root. Partial on `moderation = 'pending'`.
86
99
  *
87
100
  * Parent↔child membership and named relations live in sibling tables;
88
101
  * see `CELL_ITEM_INDEXES` / `CELL_FIELD_INDEXES` below.
@@ -1 +1 @@
1
- {"version":3,"file":"cell_ddl.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/cell_ddl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAGH,OAAO,KAAK,EAAC,SAAS,EAAE,kBAAkB,EAAC,MAAM,cAAc,CAAC;AAEhE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,oBAAoB,oIAI1B,CAAC;AAER;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,WAAW,0bAatB,CAAC;AAEH;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,MAAM,CActC,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,0iBAc5B,CAAC;AAEH;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC,MAAM,CAW5C,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,+RAO5B,CAAC;AAEH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC,MAAM,CAE5C,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,qSAO3B,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,MAAM,CAG3C,CAAC;AAEF,6EAA6E;AAC7E,eAAO,MAAM,gBAAgB,4DAA6D,CAAC;AAE3F,uBAAuB;AACvB,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,SAAS,CAuB5C,CAAC;AAEF,gDAAgD;AAChD,eAAO,MAAM,wBAAwB,aAAa,CAAC;AAEnD,wDAAwD;AACxD,eAAO,MAAM,iBAAiB,EAAE,kBAG/B,CAAC"}
1
+ {"version":3,"file":"cell_ddl.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/cell_ddl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAGH,OAAO,KAAK,EAAC,SAAS,EAAE,kBAAkB,EAAC,MAAM,cAAc,CAAC;AAEhE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,oBAAoB,oIAI1B,CAAC;AAER;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,WAAW,gkBAgBtB,CAAC;AAEH;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,MAAM,CAkBtC,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,0iBAc5B,CAAC;AAEH;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC,MAAM,CAW5C,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,+RAO5B,CAAC;AAEH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC,MAAM,CAE5C,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,qSAO3B,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,MAAM,CAG3C,CAAC;AAEF,6EAA6E;AAC7E,eAAO,MAAM,gBAAgB,4DAA6D,CAAC;AAE3F,uBAAuB;AACvB,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,SAAS,CAuB5C,CAAC;AAEF,gDAAgD;AAChD,eAAO,MAAM,wBAAwB,aAAa,CAAC;AAEnD,wDAAwD;AACxD,eAAO,MAAM,iBAAiB,EAAE,kBAG/B,CAAC"}
@@ -72,6 +72,15 @@ END $$`;
72
72
  * `CellCreateAuthorize`) and is **write-once**: set at INSERT and carried on
73
73
  * no update path, so a cell's kind is fixed at birth. Content stays
74
74
  * duck-typed in `data`; `kind` is a capability tag, not a content-type.
75
+ *
76
+ * `parent_id` / `root_id` are the **directory tree** (containment): `parent_id`
77
+ * is the immediate container (nullable self-FK; `NULL` = a root), `root_id` is
78
+ * the governing root denormalized for flat-subtree queries (`root_id =
79
+ * parent.root_id ?? parent.id`, so a root has `NULL`). Both are set once at
80
+ * create and immutable in v1 (carried on no update path). `moderation`
81
+ * (nullable text — `pending` / `approved` / `rejected`; `NULL` = unmoderated)
82
+ * is the approval-lifecycle marker, peer to `visibility` (a control field with
83
+ * a non-author writer — see `auth/cell_actions.ts`), never inside `data`.
75
84
  */
76
85
  export const CELL_SCHEMA = `
77
86
  CREATE TABLE IF NOT EXISTS cell (
@@ -81,6 +90,9 @@ CREATE TABLE IF NOT EXISTS cell (
81
90
  visibility cell_visibility NOT NULL DEFAULT 'private',
82
91
  path TEXT,
83
92
  refs TEXT[],
93
+ parent_id UUID REFERENCES cell(id) ON DELETE SET NULL,
94
+ root_id UUID REFERENCES cell(id) ON DELETE SET NULL,
95
+ moderation TEXT,
84
96
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
85
97
  updated_at TIMESTAMPTZ,
86
98
  deleted_at TIMESTAMPTZ,
@@ -99,6 +111,10 @@ CREATE TABLE IF NOT EXISTS cell (
99
111
  * - `idx_cell_data`: shape-driven queries (`data @> ...`).
100
112
  * - `idx_cell_refs`: cells-by-fact discovery (cross-cell reference graph).
101
113
  * - `idx_cell_created_by`: "cells this actor created" queries.
114
+ * - `idx_cell_root`: flat-subtree feed/scope queries by governing root
115
+ * (`cell.root_id = ?`). Active-only.
116
+ * - `idx_cell_moderation_pending`: the moderation queue — pending
117
+ * contributions per governing root. Partial on `moderation = 'pending'`.
102
118
  *
103
119
  * Parent↔child membership and named relations live in sibling tables;
104
120
  * see `CELL_ITEM_INDEXES` / `CELL_FIELD_INDEXES` below.
@@ -117,6 +133,10 @@ export const CELL_INDEXES = [
117
133
  WHERE refs IS NOT NULL AND deleted_at IS NULL`,
118
134
  `CREATE INDEX IF NOT EXISTS idx_cell_created_by ON cell(created_by)
119
135
  WHERE deleted_at IS NULL`,
136
+ `CREATE INDEX IF NOT EXISTS idx_cell_root ON cell(root_id)
137
+ WHERE deleted_at IS NULL`,
138
+ `CREATE INDEX IF NOT EXISTS idx_cell_moderation_pending ON cell(root_id)
139
+ WHERE moderation = 'pending'`,
120
140
  ];
121
141
  /**
122
142
  * `cell_grant` table — resource-side ACL for cells. Each row admits a
@@ -46,6 +46,9 @@ export interface CellRow {
46
46
  visibility: CellVisibility;
47
47
  path: string | null;
48
48
  refs: Array<FactHash> | null;
49
+ parent_id: Uuid | null;
50
+ root_id: Uuid | null;
51
+ moderation: string | null;
49
52
  created_at: Date;
50
53
  updated_at: Date | null;
51
54
  deleted_at: Date | null;
@@ -57,12 +60,19 @@ export interface CellRow {
57
60
  * Input for `query_cell_create`. `refs` is derived from `data`. `kind` is
58
61
  * the write-once capability axis (`cell.kind` column) — set here at INSERT
59
62
  * and absent from `CellUpdatePatch`, so it can never change post-create.
63
+ * `parent_id` / `root_id` / `moderation` are the directory-tree + lifecycle
64
+ * columns the handler derives (parent containment, governing root, the
65
+ * create-authorizer verdict) — likewise write-once / control-gated, never on
66
+ * `CellUpdatePatch`.
60
67
  */
61
68
  export interface CellCreateQueryInput {
62
69
  data: Json;
63
70
  kind?: string | null;
64
71
  visibility?: CellVisibility;
65
72
  path?: string | null;
73
+ parent_id?: Uuid | null;
74
+ root_id?: Uuid | null;
75
+ moderation?: string | null;
66
76
  created_by?: Uuid | null;
67
77
  }
68
78
  /**
@@ -158,6 +168,28 @@ export declare const query_cell_update: (deps: QueryDeps, id: Uuid, patch: CellU
158
168
  export declare const query_cell_delete: (deps: QueryDeps, id: Uuid, options?: {
159
169
  deleted_by?: Uuid | null;
160
170
  }) => Promise<boolean>;
171
+ /**
172
+ * Transition a contribution's `moderation` marker (the `cell_moderate` verb's
173
+ * write). On `set_visibility_public` (an approval) it also flips `visibility`
174
+ * to `'public'` so the approved contribution goes live; a rejection leaves
175
+ * visibility untouched (stays private). `updated_at` / `updated_by` are stamped.
176
+ *
177
+ * `moderation` is deliberately **not** writable through `query_cell_update`
178
+ * (it's absent from `CellUpdatePatch`) — gating the transition on a dedicated
179
+ * query (with the manage-tier authority check in the handler) is what stops an
180
+ * author self-approving their own pending cell.
181
+ *
182
+ * @param deps - query deps
183
+ * @param id - cell id
184
+ * @param moderation - the terminal marker to write (`'approved'` | `'rejected'`)
185
+ * @param options - `set_visibility_public` flips visibility on approval; `updated_by` records the moderator
186
+ * @returns the updated row, or `null` when no active row matched (raced delete)
187
+ * @mutates `cell` - sets `moderation` (+ maybe `visibility`) on one row
188
+ */
189
+ export declare const query_cell_set_moderation: (deps: QueryDeps, id: Uuid, moderation: string, options: {
190
+ set_visibility_public: boolean;
191
+ updated_by?: Uuid | null;
192
+ }) => Promise<CellRow | null>;
161
193
  /**
162
194
  * List active cells with the given `kind`, newest first. Uses the
163
195
  * `idx_cell_kind` index (`cell.kind = ?`).
@@ -246,6 +278,19 @@ export interface CellListParams {
246
278
  * literal matching.
247
279
  */
248
280
  path_prefix?: string;
281
+ /** Scope to a directory subtree by governing root (`cell.root_id = ?`, uses `idx_cell_root`). */
282
+ root_id?: Uuid;
283
+ /**
284
+ * Filter by moderation lifecycle marker (`'pending'` drives the mod queue via
285
+ * `idx_cell_moderation_pending`).
286
+ *
287
+ * Note: the visibility predicate still applies, so a `'pending'` (private)
288
+ * queue surfaces only to viewers it admits — admin / owner / grant. A
289
+ * non-admin container manager won't see pending children here until a
290
+ * manager-scoped visibility branch is added, so for now the queue is an admin
291
+ * surface (`cell_moderate` of a known id still works regardless).
292
+ */
293
+ moderation?: string;
249
294
  /**
250
295
  * Batch-fetch by id. The visibility predicate still runs, so callers
251
296
  * passing ids they can't view simply get fewer rows back. Order of
@@ -1 +1 @@
1
- {"version":3,"file":"cell_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/cell_queries.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cell_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/cell_queries.ts"],"names":[],"mappings":"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"}