@fuzdev/fuz_app 0.102.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 (49) hide show
  1. package/dist/auth/CLAUDE.md +37 -10
  2. package/dist/auth/cell_action_specs.d.ts +261 -35
  3. package/dist/auth/cell_action_specs.d.ts.map +1 -1
  4. package/dist/auth/cell_action_specs.js +102 -9
  5. package/dist/auth/cell_actions.d.ts +51 -0
  6. package/dist/auth/cell_actions.d.ts.map +1 -1
  7. package/dist/auth/cell_actions.js +168 -22
  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/auth/cell_data_schema.d.ts +14 -16
  15. package/dist/auth/cell_data_schema.d.ts.map +1 -1
  16. package/dist/auth/cell_data_schema.js +14 -16
  17. package/dist/db/CLAUDE.md +19 -6
  18. package/dist/db/cell_ddl.d.ts +24 -2
  19. package/dist/db/cell_ddl.d.ts.map +1 -1
  20. package/dist/db/cell_ddl.js +33 -1
  21. package/dist/db/cell_queries.d.ts +59 -8
  22. package/dist/db/cell_queries.d.ts.map +1 -1
  23. package/dist/db/cell_queries.js +50 -12
  24. package/dist/testing/CLAUDE.md +37 -7
  25. package/dist/testing/cross_backend/capabilities.d.ts +14 -0
  26. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  27. package/dist/testing/cross_backend/capabilities.js +3 -0
  28. package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
  29. package/dist/testing/cross_backend/cell_crud.js +55 -13
  30. package/dist/testing/cross_backend/cell_gated_create.d.ts +28 -0
  31. package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -0
  32. package/dist/testing/cross_backend/cell_gated_create.js +213 -0
  33. package/dist/testing/cross_backend/cell_grant_role.js +4 -4
  34. package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -1
  35. package/dist/testing/cross_backend/cell_relations.js +30 -24
  36. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
  37. package/dist/testing/cross_backend/default_backend_configs.js +7 -0
  38. package/dist/testing/cross_backend/expected_schema.json +4 -0
  39. package/dist/testing/cross_backend/fact_serving.js +2 -2
  40. package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
  41. package/dist/testing/cross_backend/full_spine_mount.js +9 -1
  42. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
  43. package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +6 -3
  44. package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts +22 -0
  45. package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts.map +1 -0
  46. package/dist/testing/cross_backend/test_cell_gated_create_authorize.js +75 -0
  47. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
  48. package/dist/testing/cross_backend/ts_spine_backend_config.js +5 -2
  49. package/package.json +1 -1
@@ -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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"}
@@ -64,12 +64,42 @@ export const ERROR_CELL_VISIBILITY_MANAGE_ONLY = 'cell_visibility_manage_only';
64
64
  /** Error reason — input shape for `cell_get` lacked both `id` and `path`. */
65
65
  export const ERROR_CELL_GET_REQUIRES_ID_OR_PATH = 'cell_get_requires_id_or_path';
66
66
  /**
67
- * Error reason — `cell_clone` `with_data_patch` would change `data.kind`.
68
- * Per-kind shape validation can pass coincidentally (e.g., one kind's
69
- * schema accepts most of another's fields), so we reject the cross-kind
70
- * patch explicitly to prevent incoherent clones.
67
+ * Error reason — a `kind` key was supplied inside `data`. `kind` is a
68
+ * top-level column (`cell.kind`), not content metadata; accepting it inside
69
+ * `data` would create two sources of truth. Rejected fail-loud at the
70
+ * create / update / clone-patch boundary so the column stays canonical.
71
71
  */
72
- export const ERROR_CELL_CLONE_KIND_MISMATCH = 'cell_clone_kind_mismatch';
72
+ export const ERROR_CELL_KIND_IN_DATA = 'cell_kind_in_data';
73
+ /**
74
+ * Error reason — `cell_create` was given an empty-string `kind`. `kind` is a
75
+ * non-empty capability tag or absent (`null` = typeless cell); `""` (a tag
76
+ * that tags nothing) is rejected fail-loud so `kind` stays a clean
77
+ * `null | non-empty-string` (twin of the Rust `ERROR_CELL_KIND_EMPTY`).
78
+ */
79
+ export const ERROR_CELL_KIND_EMPTY = 'cell_kind_empty';
80
+ /**
81
+ * Error reason — the parent-aware `CellCreateAuthorize` denied the create for a
82
+ * **viewable** parent (or a root creation): "you see it, you can't contribute
83
+ * here." A **403** `forbidden`, distinct from the create-path **404** mask used
84
+ * when the parent itself isn't viewable (twin of the Rust
85
+ * `ERROR_CELL_CREATE_FORBIDDEN`).
86
+ */
87
+ export const ERROR_CELL_CREATE_FORBIDDEN = 'cell_create_forbidden';
88
+ /**
89
+ * Error reason — `cell_moderate` caller can view the contribution but lacks
90
+ * moderation authority over its governing root (not admin / not the root's
91
+ * manager). A **403** `forbidden`. The author can view their own pending
92
+ * contribution, so they reach this gate — and are denied here, which is the
93
+ * anti-self-approval guard (twin of the Rust `ERROR_CELL_MODERATE_FORBIDDEN`).
94
+ */
95
+ export const ERROR_CELL_MODERATE_FORBIDDEN = 'cell_moderate_forbidden';
96
+ /**
97
+ * Error reason — `cell_moderate` target has no governing root (`root_id` is
98
+ * null): it's a root or an unparented cell, not a gated contribution, so there
99
+ * is nothing to moderate. A **400** `invalid_params` (twin of the Rust
100
+ * `ERROR_CELL_NOT_A_CONTRIBUTION`).
101
+ */
102
+ export const ERROR_CELL_NOT_A_CONTRIBUTION = 'cell_not_a_contribution';
73
103
  /**
74
104
  * Error reason — null-auth `cell_list` caller passed a `created_by`
75
105
  * filter. The filter is a soft account-id enumeration vector ("does
@@ -136,10 +166,22 @@ export const CellJson = z.strictObject({
136
166
  id: Uuid,
137
167
  path: CellPath.nullable(),
138
168
  data: CellData,
169
+ kind: z.string().nullable().meta({
170
+ 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.',
171
+ }),
139
172
  visibility: CellVisibility.meta({
140
173
  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`.",
141
174
  }),
142
175
  refs: z.array(FactHashSchema).nullable(),
176
+ parent_id: Uuid.nullable().meta({
177
+ description: 'Immediate container in the directory tree (the directory this cell lives in). `null` = a root (parentless). Set once at create, immutable in v1.',
178
+ }),
179
+ root_id: Uuid.nullable().meta({
180
+ description: 'Governing root of the directory subtree, denormalized for flat-subtree queries (`root_id = parent.root_id ?? parent.id`). `null` for a root. Set once at create, immutable in v1.',
181
+ }),
182
+ moderation: z.string().nullable().meta({
183
+ description: "Approval-lifecycle marker for a gated contribution (`'pending'` / `'approved'` / `'rejected'`); `null` = unmoderated. A control field with a non-author writer — peer to `visibility`, never inside `data` (set by the create authorizer's verdict, transitioned only by `cell_moderate`).",
184
+ }),
143
185
  created_by: Uuid.nullable(),
144
186
  updated_by: Uuid.nullable(),
145
187
  created_at: z.string(),
@@ -157,7 +199,10 @@ export const CellJson = z.strictObject({
157
199
  */
158
200
  export const CellCreateInput = z.strictObject({
159
201
  data: CellData.meta({
160
- description: 'Cell data. Base fields (kind / label / summary) typed; extras loose. Per-kind shape is sub-API.',
202
+ description: 'Cell content. Base fields (label / summary) typed; extras loose. A `kind` key here is rejected (`ERROR_CELL_KIND_IN_DATA`) — kind is the top-level field. Per-kind shape is sub-API.',
203
+ }),
204
+ kind: z.string().nullish().meta({
205
+ description: 'Capability / identity tag, written to the top-level `cell.kind` column (not `data`). Write-once — fixed at birth, not updatable. Non-empty when present (an empty string is rejected `ERROR_CELL_KIND_EMPTY`); omit / null for a typeless cell. A creation authorizer (if mounted) gates on this.',
161
206
  }),
162
207
  visibility: CellVisibility.optional().meta({
163
208
  description: "Access-control tag. Top-level column (not in `data`). Default `'private'` when omitted.",
@@ -165,6 +210,9 @@ export const CellCreateInput = z.strictObject({
165
210
  path: CellPath.nullish().meta({
166
211
  description: 'Admin-only named lookup alias. Globally unique on active rows.',
167
212
  }),
213
+ parent_id: Uuid.nullish().meta({
214
+ description: 'Immediate container — the directory this cell is created in. Omit / null = a root (parentless). The handler resolves `root_id` from it and gates the create through the parent-aware authorizer; `root_id` / `moderation` are handler-derived, never wire input.',
215
+ }),
168
216
  acting: ActingActor,
169
217
  });
170
218
  export const CellCreateOutput = z.strictObject({ cell: CellJson });
@@ -245,7 +293,7 @@ export const CellDeleteOutput = z.strictObject({
245
293
  * `shared_with: 'me'` narrows to cells that admit the caller via a
246
294
  * `cell_grant` row (actor-shaped or role-shaped principal) AND that
247
295
  * the caller does not own. Authenticated only; combine with
248
- * `data_kind` / `path_prefix` etc. to scope further. Combining with
296
+ * `kind` / `path_prefix` etc. to scope further. Combining with
249
297
  * `created_by: <my-actor-id>` produces an empty result by definition
250
298
  * (owner is implicit, never appears as a grant principal); we don't
251
299
  * reject the combination at the schema layer because SQL emptiness is
@@ -258,7 +306,9 @@ export const CellListInput = z
258
306
  .max(CELL_LIST_LIMIT_MAX)
259
307
  .optional()
260
308
  .meta({ description: 'Batch-fetch by id. Visibility predicate still applies.' }),
261
- data_kind: z.string().min(1).optional().meta({ description: 'Match `data.kind = ?`.' }),
309
+ kind: z.string().optional().meta({
310
+ description: 'Match `cell.kind = ?`. An empty string matches nothing — no cell carries an empty kind.',
311
+ }),
262
312
  visibility: CellVisibility.optional().meta({
263
313
  description: "Match `cell.visibility = ?`. The SQL-side auth-narrow already filters to public-or-admitted; this is an additional narrowing filter (e.g. `visibility: 'public'` on the discovery feed so authed callers don't see their own private entries mixed in).",
264
314
  }),
@@ -267,6 +317,12 @@ export const CellListInput = z
267
317
  path_prefix: CellPath.optional().meta({
268
318
  description: 'Match cells whose path starts with this.',
269
319
  }),
320
+ root_id: Uuid.optional().meta({
321
+ description: 'Scope to a directory subtree by governing root (`cell.root_id = ?`) — the feed + mod-queue key.',
322
+ }),
323
+ moderation: z.string().optional().meta({
324
+ description: "Filter by moderation lifecycle marker (`'pending'` drives the moderation queue).",
325
+ }),
270
326
  shared_with: z.literal('me').optional().meta({
271
327
  description: 'Narrow to cells admitting the caller via `cell_grant`, excluding cells the caller owns. Self-only — a `Uuid` form would need cross-actor role_grant loading.',
272
328
  }),
@@ -305,6 +361,30 @@ export const CellCloneInput = z.strictObject({
305
361
  acting: ActingActor,
306
362
  });
307
363
  export const CellCloneOutput = z.strictObject({ cell: CellJson });
364
+ // -- cell_moderate ----------------------------------------------------------
365
+ /**
366
+ * The terminal transition a `cell_moderate` call applies to a gated
367
+ * contribution's `moderation` lifecycle. The born state (`'pending'`) is set
368
+ * by the create authorizer, never on the wire; the verb only moves it to a
369
+ * terminal state. `'approved'` publishes (also flips `visibility → 'public'`);
370
+ * `'rejected'` leaves it private.
371
+ */
372
+ export const CellModerationDecision = z.enum(['approved', 'rejected']);
373
+ /**
374
+ * Input for `cell_moderate` — the `pending → approved | rejected` transition,
375
+ * gated on moderation authority over the **governing root** (admin / root
376
+ * owner in v1), not the contribution (which the author manages, and could
377
+ * otherwise self-approve). 404 when the target isn't viewable; 403 when it is
378
+ * but the caller isn't a root manager (the author lands here).
379
+ */
380
+ export const CellModerateInput = z.strictObject({
381
+ cell_id: Uuid.meta({ description: 'Contribution to moderate.' }),
382
+ moderation: CellModerationDecision.meta({
383
+ description: 'Terminal decision: `approved` (publishes, visibility → public) or `rejected`.',
384
+ }),
385
+ acting: ActingActor,
386
+ });
387
+ export const CellModerateOutput = z.strictObject({ cell: CellJson });
308
388
  // -- Action specs -----------------------------------------------------------
309
389
  export const cell_create_action_spec = {
310
390
  method: 'cell_create',
@@ -376,9 +456,21 @@ export const cell_clone_action_spec = {
376
456
  rate_limit: 'account',
377
457
  description: 'Clone a cell (optionally deep). New owner is the caller; `path` is always nulled. Provenance recorded only in the `cell_clone` audit row. Per-account rate-limited — `deep: true` walks `cell_item` rows and fans out, so unbounded clone is a write-amplification vector.',
378
458
  };
459
+ export const cell_moderate_action_spec = {
460
+ method: 'cell_moderate',
461
+ kind: 'request_response',
462
+ initiator: 'frontend',
463
+ auth: { account: 'required', actor: 'required' },
464
+ side_effects: true,
465
+ input: CellModerateInput,
466
+ output: CellModerateOutput,
467
+ async: true,
468
+ rate_limit: 'account',
469
+ 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.',
470
+ };
379
471
  /**
380
472
  * All cell-layer action specs — composed by app registries. Bundles the
381
- * six generic verbs (this module), the three `cell_grant_*` specs, the
473
+ * seven generic verbs (this module), the three `cell_grant_*` specs, the
382
474
  * three `cell_field_*` specs, the four `cell_item_*` specs, and the
383
475
  * `cell_audit_list` spec so codegen + UI clients see a single cell
384
476
  * namespace.
@@ -390,6 +482,7 @@ export const all_cell_action_specs = [
390
482
  cell_delete_action_spec,
391
483
  cell_list_action_spec,
392
484
  cell_clone_action_spec,
485
+ cell_moderate_action_spec,
393
486
  ...all_cell_grant_action_specs,
394
487
  ...all_cell_field_action_specs,
395
488
  ...all_cell_item_action_specs,
@@ -39,10 +39,57 @@
39
39
  * @module
40
40
  */
41
41
  import { type RpcAction } from '../actions/action_rpc.js';
42
+ import { type RequestActorContext } from './request_context.js';
42
43
  import type { RouteFactoryDeps } from './deps.js';
44
+ import type { Uuid } from '@fuzdev/fuz_util/id.ts';
43
45
  import { type CellJson } from './cell_action_specs.js';
44
46
  import { type CellRow } from '../db/cell_queries.js';
45
47
  import type { CellData } from './cell_data_schema.js';
48
+ /**
49
+ * Input to a `CellCreateAuthorize` callback — the TS twin of the Rust
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.
53
+ */
54
+ export interface CellCreateAuthorizeInput {
55
+ /** The cell's `kind` (the top-level `cell.kind` value); `null` for a typeless cell. */
56
+ kind: string | null;
57
+ /** The cell `data` (kind-free — a `kind` key is rejected upstream). For richer M3-era policies (e.g. content pre-screen). */
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;
65
+ /** Target scope — designed-in for M2 space-scoping; **always `null` in v1** (cells carry no scope column). */
66
+ scope_id: Uuid | null;
67
+ }
68
+ /**
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.
75
+ */
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>;
46
93
  /**
47
94
  * Dependencies for `create_cell_actions`.
48
95
  *
@@ -53,9 +100,13 @@ import type { CellData } from './cell_data_schema.js';
53
100
  * `invalid_params` JSON-RPC error so per-kind validation failures
54
101
  * surface to clients with code -32602 (not -32603 / internal). When
55
102
  * omitted, payloads pass through as-is.
103
+ *
104
+ * `authorize_create` is the optional creation-gate hook (see
105
+ * `CellCreateAuthorize`). When omitted, create is open (today's behavior).
56
106
  */
57
107
  export type CellActionDeps = Pick<RouteFactoryDeps, 'log' | 'audit'> & {
58
108
  validate_data?: (data: CellData) => CellData;
109
+ authorize_create?: CellCreateAuthorize;
59
110
  };
60
111
  export declare const to_cell_json: (row: CellRow) => CellJson;
61
112
  /** Create the six generic cell RPC actions. */
@@ -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;AAKlC,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAGhD,OAAO,EA6BN,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;;;;;;;;;;GAUG;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;CAC7C,CAAC;AAgBF,eAAO,MAAM,YAAY,GAAI,KAAK,OAAO,KAAG,QAY1C,CAAC;AA2BH,+CAA+C;AAC/C,eAAO,MAAM,mBAAmB,GAAI,MAAM,cAAc,KAAG,KAAK,CAAC,SAAS,CA6ezE,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_CLONE_KIND_MISMATCH, 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';
@@ -66,12 +66,30 @@ const to_iso_nullable = (value) => {
66
66
  * write is unambiguously a path collision.
67
67
  */
68
68
  const path_taken_error = () => jsonrpc_errors.conflict('cell.path is already taken', { reason: ERROR_CELL_PATH_TAKEN });
69
+ /**
70
+ * Reject a `kind` key supplied inside `data`. `kind` is the top-level
71
+ * `cell.kind` column (write-once capability axis), so a copy inside the
72
+ * loose `data` blob would be a second source of truth that silently
73
+ * diverges on update. Fail-loud `invalid_params` at every wire-`data`
74
+ * boundary (create / update / clone-patch).
75
+ */
76
+ const reject_kind_in_data = (data) => {
77
+ if (Object.hasOwn(data, 'kind')) {
78
+ throw jsonrpc_errors.invalid_params('cell.data must not contain `kind` (it is a top-level field)', {
79
+ reason: ERROR_CELL_KIND_IN_DATA,
80
+ });
81
+ }
82
+ };
69
83
  export const to_cell_json = (row) => ({
70
84
  id: row.id,
71
85
  path: row.path,
72
86
  data: row.data,
87
+ kind: row.kind,
73
88
  visibility: row.visibility,
74
89
  refs: row.refs,
90
+ parent_id: row.parent_id,
91
+ root_id: row.root_id,
92
+ moderation: row.moderation,
75
93
  created_by: row.created_by,
76
94
  updated_by: row.updated_by,
77
95
  created_at: typeof row.created_at === 'string' ? row.created_at : row.created_at.toISOString(),
@@ -92,14 +110,14 @@ const emit_cell_audit = (ctx, event_type, row, deps, auth) => {
92
110
  ip: ctx.client_ip,
93
111
  metadata: {
94
112
  cell_id: row.id,
95
- kind: row.data.kind,
113
+ kind: row.kind ?? undefined,
96
114
  path: row.path,
97
115
  },
98
116
  });
99
117
  };
100
118
  /** Create the six generic cell RPC actions. */
101
119
  export const create_cell_actions = (deps) => {
102
- const { validate_data } = deps;
120
+ const { validate_data, authorize_create } = deps;
103
121
  /**
104
122
  * Run the optional `validate_data` deps callback and convert any thrown
105
123
  * `ZodError` into the standard `invalid_params` JSON-RPC error so per-
@@ -129,9 +147,88 @@ export const create_cell_actions = (deps) => {
129
147
  reason: ERROR_CELL_PATH_ADMIN_ONLY,
130
148
  });
131
149
  }
150
+ // `kind` is the top-level column — reject a stray copy inside `data`,
151
+ // and reject an empty `kind` (a tag tags nothing — `null` is the typeless
152
+ // state), so `kind` stays a clean `null | non-empty-string`. Both run
153
+ // after the path-admin gate so an unauthorized caller sees that first.
154
+ reject_kind_in_data(input.data);
155
+ if (input.kind === '') {
156
+ throw jsonrpc_errors.invalid_params('cell.kind must not be empty (use null for a typeless cell)', { reason: ERROR_CELL_KIND_EMPTY });
157
+ }
132
158
  // Per-kind shape validation (sub-API). Unknown kinds pass through;
133
159
  // known kinds with malformed payloads surface as invalid_params.
134
160
  const validated_data = validate_data_or_throw(input.data);
161
+ const kind = input.kind ?? null;
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
+ }
231
+ }
135
232
  let row;
136
233
  try {
137
234
  row = await query_cell_create(ctx, {
@@ -141,8 +238,12 @@ export const create_cell_actions = (deps) => {
141
238
  // extend `Json`. The DB column is JSONB; the cast trusts the
142
239
  // schema-validated runtime shape.
143
240
  data: validated_data,
144
- visibility: input.visibility,
241
+ kind,
242
+ visibility,
145
243
  path: input.path ?? null,
244
+ parent_id,
245
+ root_id,
246
+ moderation,
146
247
  created_by: auth.actor.id,
147
248
  });
148
249
  }
@@ -254,7 +355,11 @@ export const create_cell_actions = (deps) => {
254
355
  // Per-kind shape validation when `data` is supplied. Patch-only —
255
356
  // we don't validate the existing row's data on update (validation
256
357
  // is for incoming patches). `data` writes fully replace, so the
257
- // patch IS the post-update state.
358
+ // patch IS the post-update state. A stray `kind` inside it is rejected
359
+ // (kind is the write-once top-level column — `cell_update` cannot
360
+ // change it, structurally: it is not even a field on `CellUpdateInput`).
361
+ if (input.data !== undefined)
362
+ reject_kind_in_data(input.data);
258
363
  const validated_data = input.data !== undefined ? validate_data_or_throw(input.data) : undefined;
259
364
  let updated;
260
365
  try {
@@ -299,6 +404,51 @@ export const create_cell_actions = (deps) => {
299
404
  emit_cell_audit(ctx, 'cell_delete', existing, deps, auth);
300
405
  return { ok: true, deleted: true };
301
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
+ };
302
452
  /**
303
453
  * Insert one cloned cell row owned by the caller.
304
454
  *
@@ -312,23 +462,13 @@ export const create_cell_actions = (deps) => {
312
462
  * server-stamped provenance fields.
313
463
  */
314
464
  const clone_one_cell_row = async (ctx, source, auth, options) => {
465
+ // A patch is wire-supplied `data` — reject a stray `kind` (kind is the
466
+ // immutable top-level column; the clone inherits `source.kind`).
467
+ if (options.patch_data !== undefined)
468
+ reject_kind_in_data(options.patch_data);
315
469
  // Both `source.data` and `options.patch_data` are CellData (loose
316
470
  // objects). Patch-last shallow merge composes cleanly.
317
471
  const merged_data = options.patch_data !== undefined ? { ...source.data, ...options.patch_data } : source.data;
318
- // Block cross-kind patches before per-kind shape validation: a
319
- // kind-A → kind-B patch can pass `validate_data` coincidentally
320
- // (loose shapes accept overlapping fields) and produce an
321
- // incoherent result. Reject when the merged kind diverges from the
322
- // source's kind. Both undefined or equal kinds pass through;
323
- // one-sided undefined is permissive (caller patching unrelated
324
- // fields on a typeless source).
325
- if (source.data.kind !== undefined &&
326
- merged_data.kind !== undefined &&
327
- source.data.kind !== merged_data.kind) {
328
- throw jsonrpc_errors.invalid_params('cell_clone cannot change kind via with_data_patch', {
329
- reason: ERROR_CELL_CLONE_KIND_MISMATCH,
330
- });
331
- }
332
472
  // Per-kind shape validation runs on the merged result (sub-API).
333
473
  // Source rows are validated on their original create; the patch
334
474
  // could violate the kind shape (e.g., remove a required field).
@@ -336,6 +476,9 @@ export const create_cell_actions = (deps) => {
336
476
  return query_cell_create(ctx, {
337
477
  // Boundary cast (see `create_handler`).
338
478
  data: validated_data,
479
+ // Clone inherits the source's kind verbatim — kind is fixed at
480
+ // birth and a `with_data_patch` can't reach the column.
481
+ kind: source.kind,
339
482
  visibility: source.visibility,
340
483
  path: null, // admin-only paths cannot auto-clone
341
484
  created_by: auth.actor.id,
@@ -463,7 +606,7 @@ export const create_cell_actions = (deps) => {
463
606
  // — emit directly rather than threading it through `emit_cell_audit`.
464
607
  // `kind` is read from `source.data` (not the cloned row) so the
465
608
  // audit trail attributes the clone to the source's shape.
466
- const source_kind = source.data.kind;
609
+ const source_kind = source.kind ?? undefined;
467
610
  const cloned_child_count = deep ? cloneable_items.length : 0;
468
611
  deps.audit.emit(ctx, {
469
612
  event_type: 'cell_clone',
@@ -505,11 +648,13 @@ export const create_cell_actions = (deps) => {
505
648
  const caller_actor_id = auth?.actor?.id ?? null;
506
649
  const rows = await query_cell_list(ctx, {
507
650
  ids: input.ids,
508
- data_kind: input.data_kind,
651
+ kind: input.kind,
509
652
  visibility: input.visibility,
510
653
  ref: input.ref,
511
654
  created_by: input.created_by,
512
655
  path_prefix: input.path_prefix,
656
+ root_id: input.root_id,
657
+ moderation: input.moderation,
513
658
  viewer_actor_id: caller_actor_id,
514
659
  viewer_is_admin: auth ? has_role(auth, ROLE_ADMIN) : false,
515
660
  caller_actor_id,
@@ -540,5 +685,6 @@ export const create_cell_actions = (deps) => {
540
685
  rpc_action(cell_delete_action_spec, delete_handler),
541
686
  rpc_action(cell_list_action_spec, list_handler),
542
687
  rpc_action(cell_clone_action_spec, clone_handler),
688
+ rpc_action(cell_moderate_action_spec, moderate_handler),
543
689
  ];
544
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