@fuzdev/fuz_app 0.149.0 → 0.150.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 (141) hide show
  1. package/dist/actions/CLAUDE.md +9 -7
  2. package/dist/actions/action_bridge.d.ts +7 -0
  3. package/dist/actions/action_bridge.d.ts.map +1 -1
  4. package/dist/actions/action_bridge.js +10 -0
  5. package/dist/actions/action_event_types.d.ts +1 -1
  6. package/dist/actions/action_event_types.d.ts.map +1 -1
  7. package/dist/actions/perform_action.d.ts.map +1 -1
  8. package/dist/actions/perform_action.js +8 -5
  9. package/dist/actions/register_action_ws.d.ts +8 -2
  10. package/dist/actions/register_action_ws.d.ts.map +1 -1
  11. package/dist/actions/register_action_ws.js +24 -4
  12. package/dist/actions/transports_ws_auth_guard.d.ts +6 -4
  13. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  14. package/dist/actions/transports_ws_auth_guard.js +9 -6
  15. package/dist/actions/ws_endpoint_spec.d.ts +1 -1
  16. package/dist/auth/CLAUDE.md +69 -31
  17. package/dist/auth/actor_lookup_queries.d.ts +3 -2
  18. package/dist/auth/actor_lookup_queries.d.ts.map +1 -1
  19. package/dist/auth/actor_lookup_queries.js +6 -3
  20. package/dist/auth/actor_search_queries.d.ts +5 -2
  21. package/dist/auth/actor_search_queries.d.ts.map +1 -1
  22. package/dist/auth/actor_search_queries.js +11 -4
  23. package/dist/auth/admin_action_specs.d.ts +4 -4
  24. package/dist/auth/admin_action_specs.js +4 -4
  25. package/dist/auth/admin_actions.d.ts.map +1 -1
  26. package/dist/auth/admin_actions.js +35 -6
  27. package/dist/auth/all_cell_actions.d.ts +11 -2
  28. package/dist/auth/all_cell_actions.d.ts.map +1 -1
  29. package/dist/auth/all_cell_actions.js +2 -1
  30. package/dist/auth/audit_emitter.d.ts +9 -3
  31. package/dist/auth/audit_emitter.d.ts.map +1 -1
  32. package/dist/auth/audit_emitter.js +9 -3
  33. package/dist/auth/audit_log_schema.d.ts +1 -0
  34. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  35. package/dist/auth/audit_log_schema.js +9 -5
  36. package/dist/auth/bootstrap_account.d.ts +25 -1
  37. package/dist/auth/bootstrap_account.d.ts.map +1 -1
  38. package/dist/auth/bootstrap_account.js +37 -4
  39. package/dist/auth/bootstrap_routes.d.ts +3 -2
  40. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  41. package/dist/auth/bootstrap_routes.js +5 -4
  42. package/dist/auth/cell_action_specs.d.ts +215 -15
  43. package/dist/auth/cell_action_specs.d.ts.map +1 -1
  44. package/dist/auth/cell_action_specs.js +103 -16
  45. package/dist/auth/cell_actions.d.ts +40 -18
  46. package/dist/auth/cell_actions.d.ts.map +1 -1
  47. package/dist/auth/cell_actions.js +126 -19
  48. package/dist/auth/cell_audit_events.d.ts +1 -1
  49. package/dist/auth/cell_audit_events.d.ts.map +1 -1
  50. package/dist/auth/cell_audit_events.js +3 -2
  51. package/dist/auth/cell_audit_metadata.d.ts +11 -0
  52. package/dist/auth/cell_audit_metadata.d.ts.map +1 -1
  53. package/dist/auth/cell_audit_metadata.js +10 -0
  54. package/dist/auth/cell_grant_action_specs.d.ts +7 -0
  55. package/dist/auth/cell_grant_action_specs.d.ts.map +1 -1
  56. package/dist/auth/cell_grant_action_specs.js +7 -0
  57. package/dist/auth/cell_grant_actions.d.ts +36 -4
  58. package/dist/auth/cell_grant_actions.d.ts.map +1 -1
  59. package/dist/auth/cell_grant_actions.js +20 -3
  60. package/dist/auth/credential_type_schema.d.ts +3 -2
  61. package/dist/auth/credential_type_schema.d.ts.map +1 -1
  62. package/dist/auth/credential_type_schema.js +3 -2
  63. package/dist/auth/role_grant_offer_action_specs.d.ts +18 -2
  64. package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
  65. package/dist/auth/role_grant_offer_action_specs.js +25 -3
  66. package/dist/auth/role_grant_offer_actions.d.ts +9 -4
  67. package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
  68. package/dist/auth/role_grant_offer_actions.js +58 -7
  69. package/dist/auth/role_grant_offer_notifications.d.ts +3 -2
  70. package/dist/auth/role_grant_offer_notifications.d.ts.map +1 -1
  71. package/dist/auth/role_grant_offer_notifications.js +3 -2
  72. package/dist/auth/role_grant_offer_queries.d.ts +20 -0
  73. package/dist/auth/role_grant_offer_queries.d.ts.map +1 -1
  74. package/dist/auth/role_grant_offer_queries.js +35 -0
  75. package/dist/auth/role_grant_queries.d.ts +27 -9
  76. package/dist/auth/role_grant_queries.d.ts.map +1 -1
  77. package/dist/auth/role_grant_queries.js +40 -13
  78. package/dist/auth/role_schema.d.ts +14 -8
  79. package/dist/auth/role_schema.d.ts.map +1 -1
  80. package/dist/auth/role_schema.js +6 -3
  81. package/dist/auth/self_service_role_actions.d.ts.map +1 -1
  82. package/dist/auth/self_service_role_actions.js +20 -0
  83. package/dist/db/CLAUDE.md +9 -2
  84. package/dist/db/cell_ddl.d.ts +9 -1
  85. package/dist/db/cell_ddl.d.ts.map +1 -1
  86. package/dist/db/cell_ddl.js +11 -1
  87. package/dist/db/cell_queries.d.ts +53 -7
  88. package/dist/db/cell_queries.d.ts.map +1 -1
  89. package/dist/db/cell_queries.js +61 -7
  90. package/dist/http/body_limit.d.ts +3 -1
  91. package/dist/http/body_limit.d.ts.map +1 -1
  92. package/dist/http/body_limit.js +6 -1
  93. package/dist/http/jsonrpc.d.ts +9 -0
  94. package/dist/http/jsonrpc.d.ts.map +1 -1
  95. package/dist/http/jsonrpc.js +17 -0
  96. package/dist/http/jsonrpc_errors.d.ts.map +1 -1
  97. package/dist/http/jsonrpc_errors.js +29 -20
  98. package/dist/http/proxy.d.ts +4 -2
  99. package/dist/http/proxy.d.ts.map +1 -1
  100. package/dist/http/proxy.js +7 -2
  101. package/dist/http/surface.d.ts +3 -1
  102. package/dist/http/surface.d.ts.map +1 -1
  103. package/dist/http/surface.js +7 -4
  104. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  105. package/dist/realtime/sse_auth_guard.js +3 -2
  106. package/dist/schema_meta.d.ts +32 -1
  107. package/dist/schema_meta.d.ts.map +1 -1
  108. package/dist/schema_meta.js +58 -5
  109. package/dist/server/app_server.d.ts +13 -5
  110. package/dist/server/app_server.d.ts.map +1 -1
  111. package/dist/server/app_server.js +32 -8
  112. package/dist/testing/CLAUDE.md +34 -21
  113. package/dist/testing/app_server.d.ts +3 -1
  114. package/dist/testing/app_server.d.ts.map +1 -1
  115. package/dist/testing/cross_backend/actor_lookup.d.ts.map +1 -1
  116. package/dist/testing/cross_backend/actor_lookup.js +27 -0
  117. package/dist/testing/cross_backend/capabilities.d.ts +2 -1
  118. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  119. package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
  120. package/dist/testing/cross_backend/cell_gated_create.js +109 -13
  121. package/dist/testing/cross_backend/cell_moderate.d.ts +7 -1
  122. package/dist/testing/cross_backend/cell_moderate.d.ts.map +1 -1
  123. package/dist/testing/cross_backend/cell_moderate.js +68 -4
  124. package/dist/testing/cross_backend/cell_ref_admission.d.ts.map +1 -1
  125. package/dist/testing/cross_backend/cell_ref_admission.js +18 -9
  126. package/dist/testing/cross_backend/conformance_table.d.ts +5 -0
  127. package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
  128. package/dist/testing/cross_backend/conformance_table.js +12 -10
  129. package/dist/testing/cross_backend/differential.d.ts +7 -0
  130. package/dist/testing/cross_backend/differential.d.ts.map +1 -1
  131. package/dist/testing/cross_backend/differential.js +17 -0
  132. package/dist/testing/cross_backend/expected_schema.json +1 -0
  133. package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
  134. package/dist/testing/cross_backend/full_spine_mount.js +5 -4
  135. package/dist/testing/cross_backend/role_grant_offer_lifecycle.d.ts.map +1 -1
  136. package/dist/testing/cross_backend/role_grant_offer_lifecycle.js +74 -2
  137. package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts +18 -19
  138. package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts.map +1 -1
  139. package/dist/testing/cross_backend/test_cell_gated_create_authorize.js +66 -42
  140. package/dist/testing/cross_backend/transport_composition.js +3 -3
  141. package/package.json +1 -1
@@ -1,10 +1,11 @@
1
1
  /**
2
2
  * Generic cell RPC action handlers.
3
3
  *
4
- * Six `request_response` actions bound to the specs in
4
+ * The generic `request_response` actions bound to the specs in
5
5
  * `auth/cell_action_specs.ts`:
6
6
  *
7
- * - Mutations: `cell_create`, `cell_update`, `cell_delete`, `cell_clone`.
7
+ * - Mutations: `cell_create`, `cell_update`, `cell_delete`, `cell_clone`,
8
+ * `cell_moderate`, `cell_policy_set`.
8
9
  * - Reads: `cell_get`, `cell_list`.
9
10
  *
10
11
  * Authorization model:
@@ -20,6 +21,11 @@
20
21
  * with per-row `can_edit_cell` enforcement. `path` writes on update
21
22
  * are admin-only; `visibility` writes require the manage tier
22
23
  * (`can_manage_cell`).
24
+ * - `cell_policy_set` is the one writer of a root's contribution `policy` past
25
+ * its create — manage-tier, root-only, and validated by the mounted
26
+ * `authorize_create` (the authorizer that interprets the policy is what
27
+ * accepts one; none mounted refuses every non-null policy). `cell_update`
28
+ * never touches it.
23
29
  * - `cell_list` is `optional` auth at the spec level. The SQL-side
24
30
  * visibility predicate in `query_cell_list` admits null auth to
25
31
  * public-only rows and authed callers to owned + public + grant-admitted
@@ -49,14 +55,14 @@ import { type RpcAction } from '../actions/action_rpc.js';
49
55
  import { type RequestActorContext } from './request_context.js';
50
56
  import type { ActionFactoryDeps } from './deps.js';
51
57
  import type { Uuid } from '../primitive_schemas.js';
52
- import { type CellJson } from './cell_action_specs.js';
58
+ import { type CellPolicy, type CellJson } from './cell_action_specs.js';
53
59
  import { type CellRow } from '../db/cell_queries.js';
54
60
  import type { CellData } from './cell_data_schema.js';
55
61
  /**
56
- * Input to a `CellCreateAuthorize` callback — the TS twin of the Rust
62
+ * Input to `CellCreateAuthorize.authorize` — the TS twin of the Rust
57
63
  * `CellCreateAuthorizeInput`. **Parent-aware**: it carries the directory
58
- * context (`parent_id` / the handler-resolved `root_id`) so the authorizer can
59
- * resolve the governing root's policy.
64
+ * context (`parent_id` / the handler-resolved `root_id`) and the governing
65
+ * root's `policy`, so the authorizer resolves the rule that applies.
60
66
  */
61
67
  export interface CellCreateAuthorizeInput {
62
68
  /** The cell's `kind` (the top-level `cell.kind` value); `null` for a typeless cell. */
@@ -67,8 +73,8 @@ export interface CellCreateAuthorizeInput {
67
73
  parent_id: Uuid | null;
68
74
  /** The governing root of the directory subtree, resolved by the handler from the parent (`parent.root_id ?? parent.id`). `null` for a root creation. */
69
75
  root_id: Uuid | null;
70
- /** The governing root's `data` — the handler reads it in-tx and hands it over, so a directory-aware authorizer resolves `root.data.policy[kind]` **without a DB read of its own** (pure predicate; reading in-tx avoids the single-connection PGlite deadlock a separate handle would hit). Non-null whenever `root_id` is — a missing or soft-deleted root answers `cell_not_found` before the authorizer runs — and `null` for a root creation. */
71
- root_data: CellData | null;
76
+ /** The governing root's contribution `policy` column — the handler reads the root in-tx and hands its policy over, so a directory-aware authorizer resolves `root_policy[kind]` **without a DB read of its own** (pure predicate; reading in-tx avoids the single-connection deadlock a separate handle would hit on the in-memory drivers). `null` for a root creation and for a root carrying no policy (the authorizer's floor governs); a missing or soft-deleted root answers `cell_not_found` before the authorizer runs. Never the root's `data`: an editor grant can rewrite `data`, so a policy read from it would be the editor's to loosen. */
77
+ root_policy: CellPolicy | null;
72
78
  /** Target scope — designed-in for M2 space-scoping; **always `null` in v1** (cells carry no scope column). */
73
79
  scope_id: Uuid | null;
74
80
  }
@@ -88,15 +94,30 @@ export type CellCreateVerdict = {
88
94
  };
89
95
  /**
90
96
  * Opt-in, parent-aware creation authorizer — the TS twin of the Rust
91
- * `CellCreateAuthorize` trait. Gates both roots (`parent_id = null`) and
92
- * contributions; answers "may *this actor* create *this kind* here?" and
93
- * returns a `CellCreateVerdict`. Runs in `cell_create` after `validate_data`
94
- * and after the handler resolves `root_id` from the parent (an unviewable
95
- * parent or a missing or soft-deleted root already 404-masks before this runs).
96
- * Omitted = today's open create (all consumers untouched). Async-capable (DB / policy calls) — a DB-backed
97
- * impl closes over its own `db` (the create handler stays unaware).
97
+ * `CellCreateAuthorize` trait, its two members the trait's two methods.
98
+ * Omitted = open create, and every non-null policy write refused
99
+ * `ERROR_CELL_POLICY_UNSUPPORTED`.
100
+ *
101
+ * - `authorize` gates both roots (`parent_id = null`) and contributions;
102
+ * answers "may *this actor* create *this kind* here?" and returns a
103
+ * `CellCreateVerdict`. Runs in `cell_create` after `validate_data` and after
104
+ * the handler resolves `root_id` and `root_policy` from the parent (an
105
+ * unviewable parent or a missing or soft-deleted root already 404-masks
106
+ * before this runs). Async-capable — a DB-backed impl closes over its own
107
+ * `db` (the create handler stays unaware).
108
+ * - `validate_policy` accepts or refuses a root `policy` before it is written
109
+ * (a root's `cell_create`, `cell_policy_set`) — the shape `authorize` reads.
110
+ * It runs after the manage and root-only gates, so it decides validity, never
111
+ * authority: throw `jsonrpc_errors.invalid_params` with
112
+ * `ERROR_CELL_POLICY_INVALID` (or a reason of the consumer's), or a
113
+ * `ZodError`, which the handler turns into `invalid_params`. **Omitted
114
+ * refuses every policy** (`ERROR_CELL_POLICY_UNSUPPORTED`, the Rust trait's
115
+ * default): an authorizer that reads no policy stores none.
98
116
  */
99
- export type CellCreateAuthorize = (auth: RequestActorContext, input: CellCreateAuthorizeInput) => CellCreateVerdict | Promise<CellCreateVerdict>;
117
+ export interface CellCreateAuthorize {
118
+ authorize: (auth: RequestActorContext, input: CellCreateAuthorizeInput) => CellCreateVerdict | Promise<CellCreateVerdict>;
119
+ validate_policy?: (policy: CellPolicy) => void;
120
+ }
100
121
  /**
101
122
  * Dependencies for `create_cell_actions`.
102
123
  *
@@ -109,13 +130,14 @@ export type CellCreateAuthorize = (auth: RequestActorContext, input: CellCreateA
109
130
  * omitted, payloads pass through as-is.
110
131
  *
111
132
  * `authorize_create` is the optional creation-gate hook (see
112
- * `CellCreateAuthorize`). When omitted, create is open (today's behavior).
133
+ * `CellCreateAuthorize`). When omitted, create is open, and every non-null
134
+ * root policy write is refused.
113
135
  */
114
136
  export type CellActionDeps = ActionFactoryDeps & {
115
137
  validate_data?: (data: CellData) => CellData;
116
138
  authorize_create?: CellCreateAuthorize;
117
139
  };
118
140
  export declare const to_cell_json: (row: CellRow) => CellJson;
119
- /** Create the six generic cell RPC actions. */
141
+ /** Create the generic cell RPC actions. */
120
142
  export declare const create_cell_actions: (deps: CellActionDeps) => Array<RpcAction>;
121
143
  //# sourceMappingURL=cell_actions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cell_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cell_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAIH,OAAO,EAIN,KAAK,SAAS,EACd,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAEN,KAAK,mBAAmB,EAExB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAEnD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EAoCN,KAAK,QAAQ,EAEb,MAAM,wBAAwB,CAAC;AAChC,OAAO,EASN,KAAK,OAAO,EACZ,MAAM,uBAAuB,CAAC;AAmC/B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEtD;;;;;GAKG;AACH,MAAM,WAAW,wBAAwB;IACxC,uFAAuF;IACvF,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,6HAA6H;IAC7H,IAAI,EAAE,QAAQ,CAAC;IACf,wHAAwH;IACxH,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,wJAAwJ;IACxJ,OAAO,EAAE,IAAI,GAAG,IAAI,CAAC;IACrB,qbAAqb;IACrb,SAAS,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC3B,8GAA8G;IAC9G,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAAE,KAAK,EAAE,KAAK,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,mBAAmB,EAAE,OAAO,CAAA;CAAE,CAAC;AAEjG;;;;;;;;;GASG;AACH,MAAM,MAAM,mBAAmB,GAAG,CACjC,IAAI,EAAE,mBAAmB,EACzB,KAAK,EAAE,wBAAwB,KAC3B,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAEpD;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,cAAc,GAAG,iBAAiB,GAAG;IAChD,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,QAAQ,CAAC;IAC7C,gBAAgB,CAAC,EAAE,mBAAmB,CAAC;CACvC,CAAC;AA6BF,eAAO,MAAM,YAAY,GAAI,KAAK,OAAO,KAAG,QAgB1C,CAAC;AAkDH,+CAA+C;AAC/C,eAAO,MAAM,mBAAmB,GAAI,MAAM,cAAc,KAAG,KAAK,CAAC,SAAS,CAmrBzE,CAAC"}
1
+ {"version":3,"file":"cell_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cell_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AAIH,OAAO,EAIN,KAAK,SAAS,EACd,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAEN,KAAK,mBAAmB,EAExB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAEnD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EA6BN,KAAK,UAAU,EAef,KAAK,QAAQ,EAEb,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAUN,KAAK,OAAO,EACZ,MAAM,uBAAuB,CAAC;AAoC/B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEtD;;;;;GAKG;AACH,MAAM,WAAW,wBAAwB;IACxC,uFAAuF;IACvF,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,6HAA6H;IAC7H,IAAI,EAAE,QAAQ,CAAC;IACf,wHAAwH;IACxH,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,wJAAwJ;IACxJ,OAAO,EAAE,IAAI,GAAG,IAAI,CAAC;IACrB,0nBAA0nB;IAC1nB,WAAW,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/B,8GAA8G;IAC9G,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAAE,KAAK,EAAE,KAAK,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,mBAAmB,EAAE,OAAO,CAAA;CAAE,CAAC;AAEjG;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,mBAAmB;IACnC,SAAS,EAAE,CACV,IAAI,EAAE,mBAAmB,EACzB,KAAK,EAAE,wBAAwB,KAC3B,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACpD,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC;CAC/C;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,cAAc,GAAG,iBAAiB,GAAG;IAChD,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,QAAQ,CAAC;IAC7C,gBAAgB,CAAC,EAAE,mBAAmB,CAAC;CACvC,CAAC;AA+CF,eAAO,MAAM,YAAY,GAAI,KAAK,OAAO,KAAG,QAiB1C,CAAC;AAkDH,2CAA2C;AAC3C,eAAO,MAAM,mBAAmB,GAAI,MAAM,cAAc,KAAG,KAAK,CAAC,SAAS,CA4wBzE,CAAC"}
@@ -1,10 +1,11 @@
1
1
  /**
2
2
  * Generic cell RPC action handlers.
3
3
  *
4
- * Six `request_response` actions bound to the specs in
4
+ * The generic `request_response` actions bound to the specs in
5
5
  * `auth/cell_action_specs.ts`:
6
6
  *
7
- * - Mutations: `cell_create`, `cell_update`, `cell_delete`, `cell_clone`.
7
+ * - Mutations: `cell_create`, `cell_update`, `cell_delete`, `cell_clone`,
8
+ * `cell_moderate`, `cell_policy_set`.
8
9
  * - Reads: `cell_get`, `cell_list`.
9
10
  *
10
11
  * Authorization model:
@@ -20,6 +21,11 @@
20
21
  * with per-row `can_edit_cell` enforcement. `path` writes on update
21
22
  * are admin-only; `visibility` writes require the manage tier
22
23
  * (`can_manage_cell`).
24
+ * - `cell_policy_set` is the one writer of a root's contribution `policy` past
25
+ * its create — manage-tier, root-only, and validated by the mounted
26
+ * `authorize_create` (the authorizer that interprets the policy is what
27
+ * accepts one; none mounted refuses every non-null policy). `cell_update`
28
+ * never touches it.
23
29
  * - `cell_list` is `optional` auth at the spec level. The SQL-side
24
30
  * visibility predicate in `query_cell_list` admits null auth to
25
31
  * public-only rows and authed callers to owned + public + grant-admitted
@@ -51,8 +57,8 @@ import { jsonrpc_errors, dev_only } from '../http/jsonrpc_errors.js';
51
57
  import { is_pg_unique_violation } from '../db/pg_error.js';
52
58
  import { has_scoped_role } from './request_context.js';
53
59
  import { ROLE_ADMIN } from './role_schema.js';
54
- 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';
55
- 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';
60
+ 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, cell_policy_set_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_NOT_PENDING, ERROR_CELL_POLICY_MANAGE_ONLY, ERROR_CELL_POLICY_ROOT_ONLY, ERROR_CELL_POLICY_UNSUPPORTED, 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';
61
+ import { query_cell_create, query_cell_get, query_cell_get_by_path, query_cell_update, query_cell_delete, query_cell_set_moderation, query_cell_set_policy, query_cell_list, query_cell_load_many } from '../db/cell_queries.js';
56
62
  import { query_cell_grant_list_for_cell, query_cell_grants_for_caller_in_cells } from '../db/cell_grant_queries.js';
57
63
  import { query_cell_field_list_for_source, query_cell_field_set } from '../db/cell_field_queries.js';
58
64
  import { query_cell_item_insert, query_cell_item_list_for_parent } from '../db/cell_item_queries.js';
@@ -71,6 +77,18 @@ import { to_item_json } from './cell_item_actions.js';
71
77
  * write is unambiguously a path collision.
72
78
  */
73
79
  const path_taken_error = () => jsonrpc_errors.conflict('cell.path is already taken', { reason: ERROR_CELL_PATH_TAKEN });
80
+ /** The refusal of a policy write nothing can interpret. */
81
+ const cell_policy_unsupported_error = () => jsonrpc_errors.invalid_params('cell.policy needs a create authorizer that accepts it', {
82
+ reason: ERROR_CELL_POLICY_UNSUPPORTED
83
+ });
84
+ /** The refusal of a policy write on a cell with a parent. */
85
+ const cell_policy_root_only_error = () => jsonrpc_errors.invalid_params('cell.policy is set only on a root (a cell without a parent)', {
86
+ reason: ERROR_CELL_POLICY_ROOT_ONLY
87
+ });
88
+ /** The `cell_moderate` refusal of a transition the contribution's state doesn't allow. */
89
+ const cell_not_pending_error = () => jsonrpc_errors.invalid_params('cell is not pending moderation', {
90
+ reason: ERROR_CELL_NOT_PENDING
91
+ });
74
92
  /**
75
93
  * Reject a `kind` key supplied inside `data`. `kind` is the top-level
76
94
  * `cell.kind` column (write-once capability axis), so a copy inside the
@@ -95,6 +113,7 @@ export const to_cell_json = (row) => ({
95
113
  parent_id: row.parent_id,
96
114
  root_id: row.root_id,
97
115
  moderation: row.moderation,
116
+ policy: row.policy,
98
117
  created_by: row.created_by,
99
118
  updated_by: row.updated_by,
100
119
  created_at: row.created_at,
@@ -139,7 +158,7 @@ const admit_viewable_or_stewarded_cell = async (deps, auth, row, grants) => {
139
158
  return;
140
159
  throw jsonrpc_errors.not_found('cell', { reason: ERROR_CELL_NOT_FOUND });
141
160
  };
142
- /** Create the six generic cell RPC actions. */
161
+ /** Create the generic cell RPC actions. */
143
162
  export const create_cell_actions = (deps) => {
144
163
  const { validate_data, authorize_create } = deps;
145
164
  /**
@@ -162,6 +181,25 @@ export const create_cell_actions = (deps) => {
162
181
  throw err;
163
182
  }
164
183
  };
184
+ /**
185
+ * Validate a non-null policy write through the mounted authorizer's
186
+ * `validate_policy` — `ERROR_CELL_POLICY_UNSUPPORTED` when there is none
187
+ * (no authorizer, or one without the member). A thrown `ZodError` becomes
188
+ * `invalid_params`, as `validate_data_or_throw` does.
189
+ */
190
+ const validate_policy_write = (policy) => {
191
+ if (authorize_create?.validate_policy === undefined)
192
+ throw cell_policy_unsupported_error();
193
+ try {
194
+ authorize_create.validate_policy(policy);
195
+ }
196
+ catch (err) {
197
+ if (err instanceof z.ZodError) {
198
+ throw jsonrpc_errors.invalid_params('cell.policy validation failed', dev_only({ issues: err.issues }));
199
+ }
200
+ throw err;
201
+ }
202
+ };
165
203
  const create_handler = async (input, ctx) => {
166
204
  const auth = ctx.auth;
167
205
  // Path writes are admin-only. Reject before the insert so the audit
@@ -185,8 +223,18 @@ export const create_cell_actions = (deps) => {
185
223
  // known kinds with malformed payloads surface as invalid_params.
186
224
  const validated_data = validate_data_or_throw(input.data);
187
225
  const kind = input.kind ?? null;
226
+ const parent_id = input.parent_id ?? null;
227
+ const policy = input.policy ?? null;
228
+ // A contribution policy is a root's alone, and the mounted authorizer —
229
+ // what interprets it — must accept it (none mounted refuses it). The
230
+ // authorizer does not gate it: a root's creator manages the root anyway.
231
+ if (policy !== null) {
232
+ if (parent_id !== null)
233
+ throw cell_policy_root_only_error();
234
+ validate_policy_write(policy);
235
+ }
188
236
  // Directory tree: resolve the governing `root_id` from the parent (and
189
- // the root's `data` for the authorizer's policy read). Runs whether or
237
+ // the root's `policy` for the authorizer). Runs whether or
190
238
  // not an authorizer is mounted — `root_id` is written regardless, and an
191
239
  // unviewable parent 404-masks the attempt (the same IDOR mask the read
192
240
  // path uses; never reveals a hidden container). A root creation
@@ -198,10 +246,9 @@ export const create_cell_actions = (deps) => {
198
246
  // answers `cell_not_found` like a missing parent (soft delete doesn't
199
247
  // cascade, so a live parent can sit under a tombstoned root): nothing is
200
248
  // written pointing at the tombstone, and an authorizer never sees a root
201
- // id without its data.
202
- const parent_id = input.parent_id ?? null;
249
+ // id it can't read.
203
250
  let root_id = null;
204
- let root_data = null;
251
+ let root_policy = null;
205
252
  if (parent_id !== null) {
206
253
  const parent = await query_cell_get(ctx, parent_id);
207
254
  if (!parent)
@@ -212,13 +259,13 @@ export const create_cell_actions = (deps) => {
212
259
  }
213
260
  root_id = parent.root_id ?? parent.id;
214
261
  if (root_id === parent.id) {
215
- root_data = parent.data;
262
+ root_policy = parent.policy;
216
263
  }
217
264
  else {
218
265
  const root = await query_cell_get(ctx, root_id);
219
266
  if (!root)
220
267
  throw jsonrpc_errors.not_found('cell', { reason: ERROR_CELL_NOT_FOUND });
221
- root_data = root.data;
268
+ root_policy = root.policy;
222
269
  }
223
270
  }
224
271
  // Parent-aware creation authorizer (opt-in). Runs after shape validation
@@ -233,12 +280,12 @@ export const create_cell_actions = (deps) => {
233
280
  let moderation = null;
234
281
  let visibility = input.visibility;
235
282
  if (authorize_create) {
236
- const verdict = await authorize_create(auth, {
283
+ const verdict = await authorize_create.authorize(auth, {
237
284
  kind,
238
285
  data: input.data,
239
286
  parent_id,
240
287
  root_id,
241
- root_data,
288
+ root_policy,
242
289
  scope_id: null
243
290
  });
244
291
  if (!verdict.allow) {
@@ -282,6 +329,7 @@ export const create_cell_actions = (deps) => {
282
329
  parent_id,
283
330
  root_id,
284
331
  moderation,
332
+ policy,
285
333
  created_by: auth.actor.id
286
334
  });
287
335
  }
@@ -473,14 +521,21 @@ export const create_cell_actions = (deps) => {
473
521
  if (!can_manage_cell(auth, root)) {
474
522
  throw jsonrpc_errors.forbidden('cell_moderate requires moderation authority over the governing root', { reason: ERROR_CELL_MODERATE_FORBIDDEN });
475
523
  }
524
+ // The UPDATE carries the allowed from-states: an approval moves only a
525
+ // pending contribution, a rejection a pending or approved one (the
526
+ // takedown); a rejected or unmoderated one never moves.
476
527
  const approved = input.moderation === 'approved';
477
528
  const updated = await query_cell_set_moderation(ctx, input.cell_id, input.moderation, {
478
529
  set_visibility_public: approved,
479
530
  updated_by: auth.actor.id
480
531
  });
481
- // Raced with a deleter between the authz check and the UPDATE.
482
- if (!updated)
532
+ if (!updated) {
533
+ // No row in an allowed from-state: the transition is refused (or
534
+ // another moderator moved it first) — or a deleter won the race (404).
535
+ if (await query_cell_get(ctx, input.cell_id))
536
+ throw cell_not_pending_error();
483
537
  throw jsonrpc_errors.not_found('cell', { reason: ERROR_CELL_NOT_FOUND });
538
+ }
484
539
  deps.audit.emit(ctx, {
485
540
  event_type: 'cell_moderate',
486
541
  actor_id: auth.actor.id,
@@ -494,6 +549,55 @@ export const create_cell_actions = (deps) => {
494
549
  });
495
550
  return { cell: to_cell_json(updated) };
496
551
  };
552
+ /**
553
+ * `cell_policy_set` — replace (or clear) a root's contribution policy,
554
+ * manage-tier on the root. Order: the 404 mask (a caller who can't view the
555
+ * cell), the manage check (403 `cell_policy_manage_only` — an editor grant
556
+ * edits `data` but never the policy that decides who may contribute),
557
+ * root-only (400 `cell_policy_root_only`), then a non-null policy's
558
+ * validation through the mounted authorizer. Audited with the policy written
559
+ * and the one it replaced.
560
+ */
561
+ const policy_set_handler = async (input, ctx) => {
562
+ const auth = ctx.auth;
563
+ const existing = await query_cell_get(ctx, input.cell_id);
564
+ if (!existing)
565
+ throw jsonrpc_errors.not_found('cell', { reason: ERROR_CELL_NOT_FOUND });
566
+ const grants = await query_cell_grant_list_for_cell(ctx, existing.id);
567
+ if (!can_view_cell(auth, existing, grants)) {
568
+ throw jsonrpc_errors.not_found('cell', { reason: ERROR_CELL_NOT_FOUND });
569
+ }
570
+ if (!can_manage_cell(auth, existing)) {
571
+ throw jsonrpc_errors.forbidden('cell.policy is manage-tier only', {
572
+ reason: ERROR_CELL_POLICY_MANAGE_ONLY
573
+ });
574
+ }
575
+ if (existing.parent_id !== null)
576
+ throw cell_policy_root_only_error();
577
+ if (input.policy !== null)
578
+ validate_policy_write(input.policy);
579
+ const result = await query_cell_set_policy(ctx, input.cell_id, input.policy, {
580
+ updated_by: auth.actor.id
581
+ });
582
+ // Raced with a deleter between the gates and the UPDATE.
583
+ if (!result)
584
+ throw jsonrpc_errors.not_found('cell', { reason: ERROR_CELL_NOT_FOUND });
585
+ const updated = result.cell;
586
+ deps.audit.emit(ctx, {
587
+ event_type: 'cell_policy_set',
588
+ actor_id: auth.actor.id,
589
+ account_id: auth.account.id,
590
+ ip: ctx.client_ip,
591
+ metadata: {
592
+ cell_id: updated.id,
593
+ policy: updated.policy,
594
+ // read under the UPDATE's row lock, not from `existing`, which a set
595
+ // racing past the gates may already have overwritten
596
+ previous_policy: result.previous_policy
597
+ }
598
+ });
599
+ return { cell: to_cell_json(updated) };
600
+ };
497
601
  /**
498
602
  * Insert one cloned cell row owned by the caller.
499
603
  *
@@ -532,16 +636,16 @@ export const create_cell_actions = (deps) => {
532
636
  // could violate the kind shape (e.g., remove a required field).
533
637
  const validated_data = validate_data_or_throw(merged_data);
534
638
  // Creation-capability gate, after the shape check (the `cell_create`
535
- // order). The clone is parentless, so no `root_id` / `root_data`; the
639
+ // order). The clone is parentless, so no `root_id` / `root_policy`; the
536
640
  // authorizer sees the merged (pre-normalization) data, as `cell_create`
537
641
  // hands it the raw input.
538
642
  if (authorize_create) {
539
- const verdict = await authorize_create(auth, {
643
+ const verdict = await authorize_create.authorize(auth, {
540
644
  kind: source.kind,
541
645
  data: merged_data,
542
646
  parent_id: null,
543
647
  root_id: null,
544
- root_data: null,
648
+ root_policy: null,
545
649
  scope_id: null
546
650
  });
547
651
  if (!verdict.allow) {
@@ -564,6 +668,8 @@ export const create_cell_actions = (deps) => {
564
668
  kind: source.kind,
565
669
  visibility: source.visibility,
566
670
  path: null, // admin-only paths cannot auto-clone
671
+ // no `policy`: a clone is born without one, so cloning a root never
672
+ // copies its governance onto a cell the cloner owns
567
673
  created_by: auth.actor.id
568
674
  });
569
675
  };
@@ -767,6 +873,7 @@ export const create_cell_actions = (deps) => {
767
873
  rpc_action(cell_delete_action_spec, delete_handler),
768
874
  rpc_action(cell_list_action_spec, list_handler),
769
875
  rpc_action(cell_clone_action_spec, clone_handler),
770
- rpc_action(cell_moderate_action_spec, moderate_handler)
876
+ rpc_action(cell_moderate_action_spec, moderate_handler),
877
+ rpc_action(cell_policy_set_action_spec, policy_set_handler)
771
878
  ];
772
879
  };
@@ -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 seven generic cell verbs' mutation events plus the grant /
23
+ * Covers the 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,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAqB7B;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAajE,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,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAsB7B;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAcjE,CAAC"}
@@ -16,14 +16,14 @@
16
16
  *
17
17
  * @module
18
18
  */
19
- import { CellAuditMetadata, CellCloneAuditMetadata, CellModerateAuditMetadata } from './cell_audit_metadata.js';
19
+ import { CellAuditMetadata, CellCloneAuditMetadata, CellModerateAuditMetadata, CellPolicySetAuditMetadata } 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 seven generic cell verbs' mutation events plus the grant /
26
+ * Covers the 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
  */
@@ -33,6 +33,7 @@ export const cell_audit_events = {
33
33
  cell_delete: CellAuditMetadata,
34
34
  cell_clone: CellCloneAuditMetadata,
35
35
  cell_moderate: CellModerateAuditMetadata,
36
+ cell_policy_set: CellPolicySetAuditMetadata,
36
37
  cell_grant_create: CellGrantCreateAuditMetadata,
37
38
  cell_grant_revoke: CellGrantRevokeAuditMetadata,
38
39
  cell_field_set: CellFieldSetAuditMetadata,
@@ -60,4 +60,15 @@ export declare const CellModerateAuditMetadata: z.ZodObject<{
60
60
  }>;
61
61
  }, z.core.$loose>;
62
62
  export type CellModerateAuditMetadata = z.infer<typeof CellModerateAuditMetadata>;
63
+ /**
64
+ * Metadata envelope for `cell_policy_set`. `cell_id` is the root; `policy` is
65
+ * the policy written (`null` when cleared) and `previous_policy` the one it
66
+ * replaced, so the audit trail carries every policy a root has had.
67
+ */
68
+ export declare const CellPolicySetAuditMetadata: z.ZodObject<{
69
+ cell_id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
70
+ policy: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
71
+ previous_policy: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
72
+ }, z.core.$loose>;
73
+ export type CellPolicySetAuditMetadata = z.infer<typeof CellPolicySetAuditMetadata>;
63
74
  //# 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,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;;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"}
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,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;;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;AAElF;;;;GAIG;AACH,eAAO,MAAM,0BAA0B;;;;iBAIrC,CAAC;AACH,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC"}
@@ -55,3 +55,13 @@ export const CellModerateAuditMetadata = z.looseObject({
55
55
  root_id: Uuid,
56
56
  moderation: z.enum(['approved', 'rejected'])
57
57
  });
58
+ /**
59
+ * Metadata envelope for `cell_policy_set`. `cell_id` is the root; `policy` is
60
+ * the policy written (`null` when cleared) and `previous_policy` the one it
61
+ * replaced, so the audit trail carries every policy a root has had.
62
+ */
63
+ export const CellPolicySetAuditMetadata = z.looseObject({
64
+ cell_id: Uuid,
65
+ policy: z.record(z.string(), z.unknown()).nullable(),
66
+ previous_policy: z.record(z.string(), z.unknown()).nullable()
67
+ });
@@ -35,6 +35,13 @@ export declare const ERROR_CELL_GRANT_UNKNOWN_ROLE: "cell_grant_unknown_role";
35
35
  * extend an audience the cell doesn't have yet.
36
36
  */
37
37
  export declare const ERROR_CELL_GRANT_UNAPPROVED: "cell_grant_unapproved";
38
+ /**
39
+ * Error reason — the mounted `CellGrantCreateAuthorize` refused a manager's
40
+ * `cell_grant_create`. A **403** `forbidden`, answered only to a caller who
41
+ * manages the cell (everyone else gets the 404 mask before the hook runs).
42
+ * Twin of the Rust `ERROR_CELL_GRANT_NOT_AUTHORIZED`.
43
+ */
44
+ export declare const ERROR_CELL_GRANT_NOT_AUTHORIZED: "cell_grant_not_authorized";
38
45
  /** Grant level — view-only or view-plus-edit. */
39
46
  export declare const CellGrantLevel: z.ZodEnum<{
40
47
  viewer: "viewer";
@@ -1 +1 @@
1
- {"version":3,"file":"cell_grant_action_specs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cell_grant_action_specs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAQxB,yEAAyE;AACzE,eAAO,MAAM,0BAA0B,EAAG,sBAA+B,CAAC;AAE1E;;;;;GAKG;AACH,eAAO,MAAM,mCAAmC,EAAG,+BAAwC,CAAC;AAE5F;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,EAAG,yBAAkC,CAAC;AAEhF;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,EAAG,uBAAgC,CAAC;AAI5E,iDAAiD;AACjD,eAAO,MAAM,cAAc;;;EAA+B,CAAC;AAC3D,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D;;;;GAIG;AACH,eAAO,MAAM,uBAAuB;;;;;;;4BAYlC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAE9E;;;;GAIG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;kBASpB,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC;AAIlD;;;;GAIG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;kBAO/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;kBAAuC,CAAC;AAC1E,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAI1E,eAAO,MAAM,oBAAoB;;;kBAG/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB;;;kBAGhC,CAAC;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAI1E,eAAO,MAAM,kBAAkB;;;kBAG7B,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;kBAE9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAItE,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWL,CAAC;AAEtC,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;CAWL,CAAC;AAEtC,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWH,CAAC;AAEtC,2EAA2E;AAC3E,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAI9B,CAAC"}
1
+ {"version":3,"file":"cell_grant_action_specs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cell_grant_action_specs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAQxB,yEAAyE;AACzE,eAAO,MAAM,0BAA0B,EAAG,sBAA+B,CAAC;AAE1E;;;;;GAKG;AACH,eAAO,MAAM,mCAAmC,EAAG,+BAAwC,CAAC;AAE5F;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,EAAG,yBAAkC,CAAC;AAEhF;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,EAAG,uBAAgC,CAAC;AAE5E;;;;;GAKG;AACH,eAAO,MAAM,+BAA+B,EAAG,2BAAoC,CAAC;AAIpF,iDAAiD;AACjD,eAAO,MAAM,cAAc;;;EAA+B,CAAC;AAC3D,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D;;;;GAIG;AACH,eAAO,MAAM,uBAAuB;;;;;;;4BAYlC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAE9E;;;;GAIG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;kBASpB,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC;AAIlD;;;;GAIG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;kBAO/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;kBAAuC,CAAC;AAC1E,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAI1E,eAAO,MAAM,oBAAoB;;;kBAG/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB;;;kBAGhC,CAAC;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAI1E,eAAO,MAAM,kBAAkB;;;kBAG7B,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;kBAE9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAItE,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWL,CAAC;AAEtC,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;CAWL,CAAC;AAEtC,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWH,CAAC;AAEtC,2EAA2E;AAC3E,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAI9B,CAAC"}
@@ -38,6 +38,13 @@ export const ERROR_CELL_GRANT_UNKNOWN_ROLE = 'cell_grant_unknown_role';
38
38
  * extend an audience the cell doesn't have yet.
39
39
  */
40
40
  export const ERROR_CELL_GRANT_UNAPPROVED = 'cell_grant_unapproved';
41
+ /**
42
+ * Error reason — the mounted `CellGrantCreateAuthorize` refused a manager's
43
+ * `cell_grant_create`. A **403** `forbidden`, answered only to a caller who
44
+ * manages the cell (everyone else gets the 404 mask before the hook runs).
45
+ * Twin of the Rust `ERROR_CELL_GRANT_NOT_AUTHORIZED`.
46
+ */
47
+ export const ERROR_CELL_GRANT_NOT_AUTHORIZED = 'cell_grant_not_authorized';
41
48
  // -- Shared schemas ---------------------------------------------------------
42
49
  /** Grant level — view-only or view-plus-edit. */
43
50
  export const CellGrantLevel = z.enum(['viewer', 'editor']);
@@ -15,7 +15,9 @@
15
15
  * principals against the role schema; rejects owner-as-principal.
16
16
  * Actor-shaped principals carry a pre-resolved `actor_id` (callers pick
17
17
  * via `actor_search`). Idempotent — re-granting the same principal
18
- * updates `level` via UPSERT.
18
+ * updates `level` via UPSERT. A mounted `authorize_grant_create` is asked
19
+ * after the spine's own gates, before the owner check and the write (403
20
+ * `cell_grant_not_authorized` on refusal).
19
21
  * - `cell_grant_revoke` — admin / owner, plus self for actor-shaped grants
20
22
  * ("leave shared cell"). Returns `still_admitted` computed by re-running
21
23
  * `can_view_cell` against the remaining grants.
@@ -34,17 +36,47 @@
34
36
  import { type RpcAction } from '../actions/action_rpc.js';
35
37
  import type { RoleSchemaResult } from './role_schema.js';
36
38
  import type { ActionFactoryDeps } from './deps.js';
37
- import { type GrantJson } from './cell_grant_action_specs.js';
38
- import { type CellGrantRow } from '../db/cell_grant_queries.js';
39
+ import type { RequestActorContext } from './request_context.js';
40
+ import type { Uuid } from '../primitive_schemas.js';
41
+ import { type CellGrantCreateInput, type GrantJson } from './cell_grant_action_specs.js';
42
+ import { type CellGrantRow, type CellGrantPrincipalQueryInput } from '../db/cell_grant_queries.js';
43
+ /**
44
+ * Input to a `CellGrantCreateAuthorize` callback — the twin of the Rust
45
+ * `CellGrantCreateAuthorizeInput`.
46
+ */
47
+ export interface CellGrantCreateAuthorizeInput {
48
+ /** The cell the grant names. */
49
+ cell_id: Uuid;
50
+ /** The cell's owner (`null` = a system cell). */
51
+ cell_created_by: Uuid | null;
52
+ /** The level the grant confers — on an upsert, the new level. */
53
+ level: CellGrantCreateInput['level'];
54
+ /** Who the grant admits, as the wire named them. */
55
+ principal: CellGrantPrincipalQueryInput;
56
+ }
57
+ /**
58
+ * Opt-in gate on `cell_grant_create` — the TS twin of the Rust
59
+ * `CellGrantCreateAuthorize` trait. Asked after the spine's own gates — the
60
+ * manage tier (a non-manager gets the 404 mask and never reaches it), the
61
+ * moderation admission, and the role registry — and before the owner check
62
+ * and the write, for a new grant and a level change alike. `false` refuses
63
+ * with 403 `ERROR_CELL_GRANT_NOT_AUTHORIZED`, writing neither the grant nor an
64
+ * audit row. A consumer narrows sharing with it (say, role-shaped grants only
65
+ * to roles of the cell's own space); it can't widen it, as every caller it
66
+ * sees already manages the cell. Omitted = no gate past the spine's.
67
+ */
68
+ export type CellGrantCreateAuthorize = (auth: RequestActorContext, input: CellGrantCreateAuthorizeInput) => boolean | Promise<boolean>;
39
69
  /**
40
70
  * Dependencies for `create_cell_grant_actions`.
41
71
  *
42
72
  * `roles` is the role schema — read for the role-validity gate on
43
- * `cell_grant_create`. The other slots match `CellActionDeps` so
73
+ * `cell_grant_create`; `authorize_grant_create` is the optional consumer gate
74
+ * (`CellGrantCreateAuthorize`). The other slots match `CellActionDeps` so
44
75
  * audit-log emit goes through the same fire-and-forget plumbing.
45
76
  */
46
77
  export type CellGrantActionDeps = ActionFactoryDeps & {
47
78
  roles: RoleSchemaResult;
79
+ authorize_grant_create?: CellGrantCreateAuthorize;
48
80
  };
49
81
  export declare const to_grant_json: (row: CellGrantRow) => GrantJson;
50
82
  /** Create the three `cell_grant_*` RPC actions. */
@@ -1 +1 @@
1
- {"version":3,"file":"cell_grant_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cell_grant_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EAAuC,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAE/F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAEnD,OAAO,EAcN,KAAK,SAAS,EACd,MAAM,8BAA8B,CAAC;AAKtC,OAAO,EAKN,KAAK,YAAY,EAEjB,MAAM,6BAA6B,CAAC;AAOrC;;;;;;GAMG;AACH,MAAM,MAAM,mBAAmB,GAAG,iBAAiB,GAAG;IACrD,KAAK,EAAE,gBAAgB,CAAC;CACxB,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,KAAK,YAAY,KAAG,SAShD,CAAC;AAgDH,mDAAmD;AACnD,eAAO,MAAM,yBAAyB,GAAI,MAAM,mBAAmB,KAAG,KAAK,CAAC,SAAS,CA+IpF,CAAC"}
1
+ {"version":3,"file":"cell_grant_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/cell_grant_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,EAAuC,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAE/F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EASN,KAAK,oBAAoB,EAMzB,KAAK,SAAS,EACd,MAAM,8BAA8B,CAAC;AAKtC,OAAO,EAKN,KAAK,YAAY,EACjB,KAAK,4BAA4B,EACjC,MAAM,6BAA6B,CAAC;AAOrC;;;GAGG;AACH,MAAM,WAAW,6BAA6B;IAC7C,gCAAgC;IAChC,OAAO,EAAE,IAAI,CAAC;IACd,iDAAiD;IACjD,eAAe,EAAE,IAAI,GAAG,IAAI,CAAC;IAC7B,iEAAiE;IACjE,KAAK,EAAE,oBAAoB,CAAC,OAAO,CAAC,CAAC;IACrC,oDAAoD;IACpD,SAAS,EAAE,4BAA4B,CAAC;CACxC;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,wBAAwB,GAAG,CACtC,IAAI,EAAE,mBAAmB,EACzB,KAAK,EAAE,6BAA6B,KAChC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEhC;;;;;;;GAOG;AACH,MAAM,MAAM,mBAAmB,GAAG,iBAAiB,GAAG;IACrD,KAAK,EAAE,gBAAgB,CAAC;IACxB,sBAAsB,CAAC,EAAE,wBAAwB,CAAC;CAClD,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,KAAK,YAAY,KAAG,SAShD,CAAC;AAgDH,mDAAmD;AACnD,eAAO,MAAM,yBAAyB,GAAI,MAAM,mBAAmB,KAAG,KAAK,CAAC,SAAS,CA8JpF,CAAC"}