@fuzdev/fuz_app 0.67.1 → 0.69.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 (235) hide show
  1. package/dist/actions/perform_action.d.ts.map +1 -1
  2. package/dist/actions/perform_action.js +10 -3
  3. package/dist/auth/CLAUDE.md +99 -5
  4. package/dist/auth/account_queries.d.ts +87 -4
  5. package/dist/auth/account_queries.d.ts.map +1 -1
  6. package/dist/auth/account_queries.js +107 -17
  7. package/dist/auth/account_schema.d.ts +19 -0
  8. package/dist/auth/account_schema.d.ts.map +1 -1
  9. package/dist/auth/account_schema.js +8 -0
  10. package/dist/auth/admin_action_specs.d.ts +170 -3
  11. package/dist/auth/admin_action_specs.d.ts.map +1 -1
  12. package/dist/auth/admin_action_specs.js +148 -4
  13. package/dist/auth/admin_actions.d.ts +4 -14
  14. package/dist/auth/admin_actions.d.ts.map +1 -1
  15. package/dist/auth/admin_actions.js +246 -40
  16. package/dist/auth/audit_log_ddl.d.ts +10 -1
  17. package/dist/auth/audit_log_ddl.d.ts.map +1 -1
  18. package/dist/auth/audit_log_ddl.js +13 -4
  19. package/dist/auth/audit_log_schema.d.ts +34 -1
  20. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  21. package/dist/auth/audit_log_schema.js +73 -0
  22. package/dist/auth/auth_ddl.d.ts +2 -2
  23. package/dist/auth/auth_ddl.d.ts.map +1 -1
  24. package/dist/auth/auth_ddl.js +10 -2
  25. package/dist/auth/cell_action_specs.d.ts +1295 -0
  26. package/dist/auth/cell_action_specs.d.ts.map +1 -0
  27. package/dist/auth/cell_action_specs.js +397 -0
  28. package/dist/auth/cell_actions.d.ts +63 -0
  29. package/dist/auth/cell_actions.d.ts.map +1 -0
  30. package/dist/auth/cell_actions.js +546 -0
  31. package/dist/auth/cell_audit_action_specs.d.ts +131 -0
  32. package/dist/auth/cell_audit_action_specs.d.ts.map +1 -0
  33. package/dist/auth/cell_audit_action_specs.js +70 -0
  34. package/dist/auth/cell_audit_actions.d.ts +18 -0
  35. package/dist/auth/cell_audit_actions.d.ts.map +1 -0
  36. package/dist/auth/cell_audit_actions.js +59 -0
  37. package/dist/auth/cell_audit_events.d.ts +28 -0
  38. package/dist/auth/cell_audit_events.d.ts.map +1 -0
  39. package/dist/auth/cell_audit_events.js +42 -0
  40. package/dist/auth/cell_audit_metadata.d.ts +48 -0
  41. package/dist/auth/cell_audit_metadata.d.ts.map +1 -0
  42. package/dist/auth/cell_audit_metadata.js +46 -0
  43. package/dist/auth/cell_authorize.d.ts +88 -0
  44. package/dist/auth/cell_authorize.d.ts.map +1 -0
  45. package/dist/auth/cell_authorize.js +172 -0
  46. package/dist/auth/cell_data_schema.d.ts +44 -0
  47. package/dist/auth/cell_data_schema.d.ts.map +1 -0
  48. package/dist/auth/cell_data_schema.js +42 -0
  49. package/dist/auth/cell_field_action_specs.d.ts +244 -0
  50. package/dist/auth/cell_field_action_specs.d.ts.map +1 -0
  51. package/dist/auth/cell_field_action_specs.js +136 -0
  52. package/dist/auth/cell_field_actions.d.ts +34 -0
  53. package/dist/auth/cell_field_actions.d.ts.map +1 -0
  54. package/dist/auth/cell_field_actions.js +153 -0
  55. package/dist/auth/cell_field_audit_metadata.d.ts +30 -0
  56. package/dist/auth/cell_field_audit_metadata.d.ts.map +1 -0
  57. package/dist/auth/cell_field_audit_metadata.js +28 -0
  58. package/dist/auth/cell_grant_action_specs.d.ts +333 -0
  59. package/dist/auth/cell_grant_action_specs.d.ts.map +1 -0
  60. package/dist/auth/cell_grant_action_specs.js +148 -0
  61. package/dist/auth/cell_grant_actions.d.ts +50 -0
  62. package/dist/auth/cell_grant_actions.d.ts.map +1 -0
  63. package/dist/auth/cell_grant_actions.js +208 -0
  64. package/dist/auth/cell_grant_audit_metadata.d.ts +75 -0
  65. package/dist/auth/cell_grant_audit_metadata.d.ts.map +1 -0
  66. package/dist/auth/cell_grant_audit_metadata.js +54 -0
  67. package/dist/auth/cell_item_action_specs.d.ts +331 -0
  68. package/dist/auth/cell_item_action_specs.d.ts.map +1 -0
  69. package/dist/auth/cell_item_action_specs.js +182 -0
  70. package/dist/auth/cell_item_actions.d.ts +37 -0
  71. package/dist/auth/cell_item_actions.d.ts.map +1 -0
  72. package/dist/auth/cell_item_actions.js +204 -0
  73. package/dist/auth/cell_item_audit_metadata.d.ts +35 -0
  74. package/dist/auth/cell_item_audit_metadata.d.ts.map +1 -0
  75. package/dist/auth/cell_item_audit_metadata.js +32 -0
  76. package/dist/auth/cell_relation_visibility.d.ts +32 -0
  77. package/dist/auth/cell_relation_visibility.d.ts.map +1 -0
  78. package/dist/auth/cell_relation_visibility.js +57 -0
  79. package/dist/auth/deps.d.ts +9 -0
  80. package/dist/auth/deps.d.ts.map +1 -1
  81. package/dist/auth/role_grant_queries.d.ts +30 -0
  82. package/dist/auth/role_grant_queries.d.ts.map +1 -1
  83. package/dist/auth/role_grant_queries.js +54 -0
  84. package/dist/auth/signup_routes.d.ts +0 -3
  85. package/dist/auth/signup_routes.d.ts.map +1 -1
  86. package/dist/auth/signup_routes.js +9 -3
  87. package/dist/auth/standard_rpc_actions.d.ts +5 -5
  88. package/dist/auth/standard_rpc_actions.js +4 -4
  89. package/dist/db/CLAUDE.md +118 -0
  90. package/dist/db/cell_audit_queries.d.ts +26 -0
  91. package/dist/db/cell_audit_queries.d.ts.map +1 -0
  92. package/dist/db/cell_audit_queries.js +53 -0
  93. package/dist/db/cell_ddl.d.ts +151 -0
  94. package/dist/db/cell_ddl.d.ts.map +1 -0
  95. package/dist/db/cell_ddl.js +247 -0
  96. package/dist/db/cell_field_queries.d.ts +105 -0
  97. package/dist/db/cell_field_queries.d.ts.map +1 -0
  98. package/dist/db/cell_field_queries.js +113 -0
  99. package/dist/db/cell_grant_queries.d.ts +132 -0
  100. package/dist/db/cell_grant_queries.d.ts.map +1 -0
  101. package/dist/db/cell_grant_queries.js +145 -0
  102. package/dist/db/cell_history_ddl.d.ts +38 -0
  103. package/dist/db/cell_history_ddl.d.ts.map +1 -0
  104. package/dist/db/cell_history_ddl.js +61 -0
  105. package/dist/db/cell_item_queries.d.ts +107 -0
  106. package/dist/db/cell_item_queries.d.ts.map +1 -0
  107. package/dist/db/cell_item_queries.js +119 -0
  108. package/dist/db/cell_queries.d.ts +327 -0
  109. package/dist/db/cell_queries.d.ts.map +1 -0
  110. package/dist/db/cell_queries.js +431 -0
  111. package/dist/db/fact_ddl.d.ts +38 -0
  112. package/dist/db/fact_ddl.d.ts.map +1 -0
  113. package/dist/db/fact_ddl.js +71 -0
  114. package/dist/db/fact_queries.d.ts +140 -0
  115. package/dist/db/fact_queries.d.ts.map +1 -0
  116. package/dist/db/fact_queries.js +161 -0
  117. package/dist/db/fact_store.d.ts +112 -0
  118. package/dist/db/fact_store.d.ts.map +1 -0
  119. package/dist/db/fact_store.js +225 -0
  120. package/dist/server/app_server.d.ts +1 -7
  121. package/dist/server/app_server.d.ts.map +1 -1
  122. package/dist/server/app_server.js +1 -5
  123. package/dist/server/env.d.ts +2 -0
  124. package/dist/server/env.d.ts.map +1 -1
  125. package/dist/server/env.js +6 -0
  126. package/dist/server/fact_write.d.ts +32 -0
  127. package/dist/server/fact_write.d.ts.map +1 -0
  128. package/dist/server/fact_write.js +56 -0
  129. package/dist/server/file_fact_fetcher.d.ts +42 -0
  130. package/dist/server/file_fact_fetcher.d.ts.map +1 -0
  131. package/dist/server/file_fact_fetcher.js +60 -0
  132. package/dist/server/file_fact_url.d.ts +53 -0
  133. package/dist/server/file_fact_url.d.ts.map +1 -0
  134. package/dist/server/file_fact_url.js +52 -0
  135. package/dist/server/serve_fact_route.d.ts +78 -0
  136. package/dist/server/serve_fact_route.d.ts.map +1 -0
  137. package/dist/server/serve_fact_route.js +205 -0
  138. package/dist/testing/CLAUDE.md +142 -6
  139. package/dist/testing/app_server.d.ts +46 -0
  140. package/dist/testing/app_server.d.ts.map +1 -1
  141. package/dist/testing/app_server.js +67 -8
  142. package/dist/testing/audit_completeness.d.ts.map +1 -1
  143. package/dist/testing/audit_completeness.js +67 -1
  144. package/dist/testing/cross_backend/account_lifecycle.d.ts +10 -0
  145. package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -0
  146. package/dist/testing/cross_backend/account_lifecycle.js +144 -0
  147. package/dist/testing/cross_backend/actor_lookup.d.ts +10 -0
  148. package/dist/testing/cross_backend/actor_lookup.d.ts.map +1 -0
  149. package/dist/testing/cross_backend/actor_lookup.js +83 -0
  150. package/dist/testing/cross_backend/actor_search.d.ts +6 -0
  151. package/dist/testing/cross_backend/actor_search.d.ts.map +1 -0
  152. package/dist/testing/cross_backend/actor_search.js +92 -0
  153. package/dist/testing/cross_backend/app_settings.d.ts +6 -0
  154. package/dist/testing/cross_backend/app_settings.d.ts.map +1 -0
  155. package/dist/testing/cross_backend/app_settings.js +95 -0
  156. package/dist/testing/cross_backend/backend_config.d.ts +1 -1
  157. package/dist/testing/cross_backend/capabilities.d.ts +29 -7
  158. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  159. package/dist/testing/cross_backend/capabilities.js +3 -1
  160. package/dist/testing/cross_backend/cell_cross_helpers.d.ts +39 -0
  161. package/dist/testing/cross_backend/cell_cross_helpers.d.ts.map +1 -0
  162. package/dist/testing/cross_backend/cell_cross_helpers.js +45 -0
  163. package/dist/testing/cross_backend/cell_crud.d.ts +4 -0
  164. package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -0
  165. package/dist/testing/cross_backend/cell_crud.js +168 -0
  166. package/dist/testing/cross_backend/cell_grant_role.d.ts +8 -0
  167. package/dist/testing/cross_backend/cell_grant_role.d.ts.map +1 -0
  168. package/dist/testing/cross_backend/cell_grant_role.js +102 -0
  169. package/dist/testing/cross_backend/cell_relations.d.ts +4 -0
  170. package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -0
  171. package/dist/testing/cross_backend/cell_relations.js +229 -0
  172. package/dist/testing/cross_backend/conformance_case.d.ts +144 -0
  173. package/dist/testing/cross_backend/conformance_case.d.ts.map +1 -0
  174. package/dist/testing/cross_backend/conformance_case.js +132 -0
  175. package/dist/testing/cross_backend/conformance_table.d.ts +46 -0
  176. package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -0
  177. package/dist/testing/cross_backend/conformance_table.js +199 -0
  178. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
  179. package/dist/testing/cross_backend/default_backend_configs.js +6 -2
  180. package/dist/testing/cross_backend/default_spine_surface.d.ts +17 -9
  181. package/dist/testing/cross_backend/default_spine_surface.d.ts.map +1 -1
  182. package/dist/testing/cross_backend/default_spine_surface.js +20 -12
  183. package/dist/testing/cross_backend/origin.d.ts +10 -0
  184. package/dist/testing/cross_backend/origin.d.ts.map +1 -0
  185. package/dist/testing/cross_backend/origin.js +73 -0
  186. package/dist/testing/cross_backend/setup.d.ts +22 -40
  187. package/dist/testing/cross_backend/setup.d.ts.map +1 -1
  188. package/dist/testing/cross_backend/setup.js +39 -5
  189. package/dist/testing/cross_backend/testing_reset_actions.d.ts +90 -2
  190. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  191. package/dist/testing/cross_backend/testing_reset_actions.js +91 -3
  192. package/dist/testing/cross_backend/xfail.d.ts +15 -0
  193. package/dist/testing/cross_backend/xfail.d.ts.map +1 -0
  194. package/dist/testing/cross_backend/xfail.js +37 -0
  195. package/dist/testing/entities.d.ts.map +1 -1
  196. package/dist/testing/entities.js +4 -0
  197. package/dist/testing/integration.d.ts +2 -3
  198. package/dist/testing/integration.d.ts.map +1 -1
  199. package/dist/testing/integration.js +20 -85
  200. package/dist/testing/rate_limiting.d.ts +1 -1
  201. package/dist/testing/rpc_helpers.d.ts +3 -3
  202. package/dist/testing/sse_round_trip.d.ts +1 -1
  203. package/dist/testing/stubs.d.ts.map +1 -1
  204. package/dist/testing/stubs.js +0 -1
  205. package/dist/testing/ws_round_trip.d.ts.map +1 -1
  206. package/dist/testing/ws_round_trip.js +4 -0
  207. package/dist/ui/AdminAccounts.svelte +84 -35
  208. package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -1
  209. package/dist/ui/AdminSessions.svelte +21 -23
  210. package/dist/ui/AdminSessions.svelte.d.ts.map +1 -1
  211. package/dist/ui/CLAUDE.md +17 -26
  212. package/dist/ui/OpenSignupToggle.svelte +2 -5
  213. package/dist/ui/OpenSignupToggle.svelte.d.ts.map +1 -1
  214. package/dist/ui/account_sessions_state.svelte.d.ts +9 -10
  215. package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -1
  216. package/dist/ui/account_sessions_state.svelte.js +7 -17
  217. package/dist/ui/admin_accounts_state.svelte.d.ts +41 -20
  218. package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
  219. package/dist/ui/admin_accounts_state.svelte.js +52 -22
  220. package/dist/ui/admin_invites_state.svelte.d.ts +8 -11
  221. package/dist/ui/admin_invites_state.svelte.d.ts.map +1 -1
  222. package/dist/ui/admin_invites_state.svelte.js +7 -16
  223. package/dist/ui/admin_rpc_adapters.d.ts +6 -2
  224. package/dist/ui/admin_rpc_adapters.d.ts.map +1 -1
  225. package/dist/ui/admin_rpc_adapters.js +5 -1
  226. package/dist/ui/admin_sessions_state.svelte.d.ts +6 -10
  227. package/dist/ui/admin_sessions_state.svelte.d.ts.map +1 -1
  228. package/dist/ui/admin_sessions_state.svelte.js +4 -14
  229. package/dist/ui/app_settings_state.svelte.d.ts +8 -12
  230. package/dist/ui/app_settings_state.svelte.d.ts.map +1 -1
  231. package/dist/ui/app_settings_state.svelte.js +6 -16
  232. package/dist/ui/audit_log_state.svelte.d.ts +9 -8
  233. package/dist/ui/audit_log_state.svelte.d.ts.map +1 -1
  234. package/dist/ui/audit_log_state.svelte.js +8 -20
  235. package/package.json +2 -2
@@ -0,0 +1,546 @@
1
+ /**
2
+ * Generic cell RPC action handlers.
3
+ *
4
+ * Six `request_response` actions bound to the specs in
5
+ * `./cell_action_specs.ts`:
6
+ *
7
+ * - Mutations: `cell_create`, `cell_update`, `cell_delete`, `cell_clone`.
8
+ * - Reads: `cell_get`, `cell_list`.
9
+ *
10
+ * Authorization model:
11
+ *
12
+ * - `cell_create` is authenticated at the spec level. The handler stamps
13
+ * `created_by` from `auth.actor.id`. `path` writes are admin-only —
14
+ * non-admin callers supplying `path` get `ERROR_CELL_PATH_ADMIN_ONLY`.
15
+ * - `cell_get` is `optional` auth at the spec level. Per-row authorization
16
+ * via `can_view_cell(auth, cell)`. Misses + unauthorized reads both 404,
17
+ * so private-cell existence doesn't leak through the wire. Bundled
18
+ * relations are filtered to viewable targets (strict target-visibility).
19
+ * - `cell_update` / `cell_delete` are authenticated at the spec level
20
+ * with per-row `can_edit_cell` enforcement. `path` writes on update
21
+ * are admin-only; `visibility` writes require the manage tier
22
+ * (`can_manage_cell`).
23
+ * - `cell_list` is `optional` auth at the spec level. The SQL-side
24
+ * visibility predicate in `query_cell_list` admits null auth to
25
+ * public-only rows and authed callers to owned + public + grant-admitted
26
+ * rows; admin sees all. SQL-side because post-filtering in JS would
27
+ * silently truncate pages. The handler rejects the `created_by` filter
28
+ * for null auth (account-id enumeration guard).
29
+ *
30
+ * Mutations emit `cell_create` / `cell_update` / `cell_delete` audit
31
+ * events via `deps.audit.emit(...)`. The `AuditLogConfig` threaded through
32
+ * the consumer's `audit_factory` (see `create_app_backend`) must declare
33
+ * the cell event types (see `./cell_audit_metadata.ts`).
34
+ *
35
+ * App vocabulary (e.g., collection / entry kinds) lives in client-side
36
+ * helpers and per-app `validate_data` deps — this layer is generic-only
37
+ * by construction.
38
+ *
39
+ * @module
40
+ */
41
+ import { z } from 'zod';
42
+ import { rpc_action, } from '../actions/action_rpc.js';
43
+ import { jsonrpc_errors } from '../http/jsonrpc_errors.js';
44
+ import { is_pg_unique_violation } from '../db/pg_error.js';
45
+ import { has_role } from './request_context.js';
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';
49
+ import { query_cell_grant_list_for_cell, query_cell_grants_for_caller_in_cells, } from '../db/cell_grant_queries.js';
50
+ import { query_cell_field_list_for_source, query_cell_field_set } from '../db/cell_field_queries.js';
51
+ import { query_cell_item_insert, query_cell_item_list_for_parent, } from '../db/cell_item_queries.js';
52
+ import { can_view_cell, can_edit_cell, can_manage_cell } from './cell_authorize.js';
53
+ import { filter_visible_target_ids } from './cell_relation_visibility.js';
54
+ import { to_grant_json } from './cell_grant_actions.js';
55
+ import { to_field_json } from './cell_field_actions.js';
56
+ import { to_item_json } from './cell_item_actions.js';
57
+ const to_iso_nullable = (value) => {
58
+ if (value === null)
59
+ return null;
60
+ return typeof value === 'string' ? value : value.toISOString();
61
+ };
62
+ /**
63
+ * Translate the `idx_cell_path_unique` violation into a clean `conflict`
64
+ * (409) reason. `path` is the only unique constraint a cell write can hit
65
+ * (the id is a server-generated UUID), so a `23505` on a path-bearing
66
+ * write is unambiguously a path collision.
67
+ */
68
+ const path_taken_error = () => jsonrpc_errors.conflict('cell.path is already taken', { reason: ERROR_CELL_PATH_TAKEN });
69
+ export const to_cell_json = (row) => ({
70
+ id: row.id,
71
+ path: row.path,
72
+ data: row.data,
73
+ visibility: row.visibility,
74
+ refs: row.refs,
75
+ created_by: row.created_by,
76
+ updated_by: row.updated_by,
77
+ created_at: typeof row.created_at === 'string' ? row.created_at : row.created_at.toISOString(),
78
+ updated_at: to_iso_nullable(row.updated_at),
79
+ deleted_at: to_iso_nullable(row.deleted_at),
80
+ grant_count: row.grant_count,
81
+ });
82
+ /**
83
+ * Emit a cell-mutation audit event with the standard `{cell_id, kind?,
84
+ * path?}` envelope. Relation-graph mutations are tracked independently
85
+ * via per-row `cell_item_*` / `cell_field_*` events.
86
+ */
87
+ const emit_cell_audit = (ctx, event_type, row, deps, auth) => {
88
+ deps.audit.emit(ctx, {
89
+ event_type,
90
+ actor_id: auth.actor.id,
91
+ account_id: auth.account.id,
92
+ ip: ctx.client_ip,
93
+ metadata: {
94
+ cell_id: row.id,
95
+ kind: row.data.kind,
96
+ path: row.path,
97
+ },
98
+ });
99
+ };
100
+ /** Create the six generic cell RPC actions. */
101
+ export const create_cell_actions = (deps) => {
102
+ const { validate_data } = deps;
103
+ /**
104
+ * Run the optional `validate_data` deps callback and convert any thrown
105
+ * `ZodError` into the standard `invalid_params` JSON-RPC error so per-
106
+ * kind validation failures surface to clients with code -32602 (not
107
+ * -32603 / internal). The dispatcher only auto-converts ZodError for
108
+ * wire-level input schemas; sub-API validation runs inside the handler.
109
+ */
110
+ const validate_data_or_throw = (data) => {
111
+ if (validate_data === undefined)
112
+ return data;
113
+ try {
114
+ return validate_data(data);
115
+ }
116
+ catch (err) {
117
+ if (err instanceof z.ZodError) {
118
+ throw jsonrpc_errors.invalid_params('cell.data shape validation failed', {
119
+ issues: err.issues,
120
+ });
121
+ }
122
+ throw err;
123
+ }
124
+ };
125
+ const create_handler = async (input, ctx) => {
126
+ const auth = ctx.auth;
127
+ // Path writes are admin-only. Reject before the insert so the audit
128
+ // + DB are clean.
129
+ if (input.path !== undefined && input.path !== null && !has_role(auth, ROLE_ADMIN)) {
130
+ throw jsonrpc_errors.forbidden('cell.path is admin-only', {
131
+ reason: ERROR_CELL_PATH_ADMIN_ONLY,
132
+ });
133
+ }
134
+ // Per-kind shape validation (sub-API). Unknown kinds pass through;
135
+ // known kinds with malformed payloads surface as invalid_params.
136
+ const validated_data = validate_data_or_throw(input.data);
137
+ let row;
138
+ try {
139
+ row = await query_cell_create(ctx, {
140
+ // Boundary cast: `CellData` is structurally JSON-compatible at
141
+ // runtime (loose object of JSON-safe values), but its inferred
142
+ // type carries `T | undefined` from `.optional()` which doesn't
143
+ // extend `Json`. The DB column is JSONB; the cast trusts the
144
+ // schema-validated runtime shape.
145
+ data: validated_data,
146
+ visibility: input.visibility,
147
+ path: input.path ?? null,
148
+ created_by: auth.actor.id,
149
+ });
150
+ }
151
+ catch (err) {
152
+ if (input.path != null && is_pg_unique_violation(err))
153
+ throw path_taken_error();
154
+ throw err;
155
+ }
156
+ emit_cell_audit(ctx, 'cell_create', row, deps, auth);
157
+ return { cell: to_cell_json(row) };
158
+ };
159
+ const get_handler = async (input, ctx) => {
160
+ // Defense in depth: spec already refines for "id or path". Surface
161
+ // the same error code from the handler so adversarial callers that
162
+ // bypass the wire schema get the same error shape.
163
+ if (input.id === undefined && input.path === undefined) {
164
+ throw jsonrpc_errors.invalid_params('cell_get requires id or path', {
165
+ reason: ERROR_CELL_GET_REQUIRES_ID_OR_PATH,
166
+ });
167
+ }
168
+ const auth = ctx.auth;
169
+ const row = input.id !== undefined
170
+ ? await query_cell_get(ctx, input.id)
171
+ : await query_cell_get_by_path(ctx, input.path);
172
+ if (!row) {
173
+ throw jsonrpc_errors.not_found('cell', { reason: ERROR_CELL_NOT_FOUND });
174
+ }
175
+ // Run the three post-cell fetches in parallel — they share only
176
+ // `row.id` and have no inter-dependency. Bundle fetches one over the
177
+ // cap so we can detect truncation without a separate count query.
178
+ // Skip the grant fetch for unauthenticated callers — no grant can
179
+ // admit a null auth, so the predicate either short-circuits via
180
+ // `cell_is_public` or returns false either way.
181
+ const [grants, fields, items] = await Promise.all([
182
+ auth ? query_cell_grant_list_for_cell(ctx, row.id) : Promise.resolve(null),
183
+ query_cell_field_list_for_source(ctx, row.id, {
184
+ limit: CELL_RELATIONS_BUNDLE_LIMIT + 1,
185
+ }),
186
+ query_cell_item_list_for_parent(ctx, row.id, {
187
+ limit: CELL_RELATIONS_BUNDLE_LIMIT + 1,
188
+ }),
189
+ ]);
190
+ // 404 covers both "no such cell" and "exists but caller can't view"
191
+ // — same response code so private-cell existence doesn't leak.
192
+ if (!can_view_cell(auth, row, grants)) {
193
+ throw jsonrpc_errors.not_found('cell', { reason: ERROR_CELL_NOT_FOUND });
194
+ }
195
+ const can_edit = can_edit_cell(auth, row, grants);
196
+ // `can_grant` gates the share UI — managing grants is a manage-tier
197
+ // affordance (admin / owner), so it tracks `can_manage_cell` rather
198
+ // than the broader `can_edit` (editor-grant holders edit, but the
199
+ // share list is the manager's to curate).
200
+ const can_grant = can_manage_cell(auth, row);
201
+ const fields_truncated = fields.length > CELL_RELATIONS_BUNDLE_LIMIT;
202
+ const fields_bundled = fields_truncated ? fields.slice(0, CELL_RELATIONS_BUNDLE_LIMIT) : fields;
203
+ const items_truncated = items.length > CELL_RELATIONS_BUNDLE_LIMIT;
204
+ const items_bundled = items_truncated ? items.slice(0, CELL_RELATIONS_BUNDLE_LIMIT) : items;
205
+ // Strict target-visibility (D8): drop bundled relations whose target
206
+ // the caller can't view, so a viewer of this cell can't enumerate
207
+ // private linked cells by id. One batched filter over both relation
208
+ // id-sets. `*_truncated` still reflects the raw relation size.
209
+ const visible_targets = await filter_visible_target_ids(ctx, auth, [
210
+ ...fields_bundled.map((f) => f.target_id),
211
+ ...items_bundled.map((i) => i.child_id),
212
+ ]);
213
+ const fields_visible = fields_bundled.filter((f) => visible_targets.has(f.target_id));
214
+ const items_visible = items_bundled.filter((i) => visible_targets.has(i.child_id));
215
+ return {
216
+ cell: to_cell_json(row),
217
+ fields: fields_visible.map(to_field_json),
218
+ fields_truncated,
219
+ items: items_visible.map(to_item_json),
220
+ items_truncated,
221
+ can_edit,
222
+ can_grant,
223
+ };
224
+ };
225
+ const update_handler = async (input, ctx) => {
226
+ const auth = ctx.auth;
227
+ const path_provided = Object.hasOwn(input, 'path');
228
+ // `path` writes are admin-only. Check before fetching so non-admins
229
+ // can't probe for cell existence by varying `path` shape.
230
+ if (path_provided && !has_role(auth, ROLE_ADMIN)) {
231
+ throw jsonrpc_errors.forbidden('cell.path is admin-only', {
232
+ reason: ERROR_CELL_PATH_ADMIN_ONLY,
233
+ });
234
+ }
235
+ const existing = await query_cell_get(ctx, input.cell_id);
236
+ if (!existing) {
237
+ throw jsonrpc_errors.not_found('cell', { reason: ERROR_CELL_NOT_FOUND });
238
+ }
239
+ const grants = await query_cell_grant_list_for_cell(ctx, existing.id);
240
+ if (!can_edit_cell(auth, existing, grants)) {
241
+ // IDOR mask: 404, not 403 — same shape as cell_get.
242
+ throw jsonrpc_errors.not_found('cell', { reason: ERROR_CELL_NOT_FOUND });
243
+ }
244
+ // Visibility writes are manage-tier only (admin / owner). An
245
+ // editor-grant holder may edit `data` but cannot flip the cell's
246
+ // visibility — that would let a delegated editor expose a private
247
+ // cell or hide a public one. Gated on an actual change so a client
248
+ // round-tripping the unchanged value isn't rejected.
249
+ if (input.visibility !== undefined &&
250
+ input.visibility !== existing.visibility &&
251
+ !can_manage_cell(auth, existing)) {
252
+ throw jsonrpc_errors.forbidden('cell.visibility is manage-tier only', {
253
+ reason: ERROR_CELL_VISIBILITY_MANAGE_ONLY,
254
+ });
255
+ }
256
+ // Per-kind shape validation when `data` is supplied. Patch-only —
257
+ // we don't validate the existing row's data on update (validation
258
+ // is for incoming patches). `data` writes fully replace, so the
259
+ // patch IS the post-update state.
260
+ const validated_data = input.data !== undefined ? validate_data_or_throw(input.data) : undefined;
261
+ let updated;
262
+ try {
263
+ updated = await query_cell_update(ctx, input.cell_id, {
264
+ // Boundary cast (see `create_handler`).
265
+ data: validated_data,
266
+ visibility: input.visibility,
267
+ path: path_provided ? (input.path ?? null) : undefined,
268
+ updated_by: auth.actor.id,
269
+ });
270
+ }
271
+ catch (err) {
272
+ if (path_provided && input.path != null && is_pg_unique_violation(err)) {
273
+ throw path_taken_error();
274
+ }
275
+ throw err;
276
+ }
277
+ if (!updated) {
278
+ // Raced with a deleter between the visibility check and the UPDATE.
279
+ throw jsonrpc_errors.not_found('cell', { reason: ERROR_CELL_NOT_FOUND });
280
+ }
281
+ emit_cell_audit(ctx, 'cell_update', updated, deps, auth);
282
+ return { cell: to_cell_json(updated) };
283
+ };
284
+ const delete_handler = async (input, ctx) => {
285
+ const auth = ctx.auth;
286
+ // Fetch first so we can audit `kind` + `path` after the soft-delete
287
+ // flips `deleted_at`.
288
+ const existing = await query_cell_get(ctx, input.cell_id);
289
+ if (!existing) {
290
+ throw jsonrpc_errors.not_found('cell', { reason: ERROR_CELL_NOT_FOUND });
291
+ }
292
+ const grants = await query_cell_grant_list_for_cell(ctx, existing.id);
293
+ if (!can_edit_cell(auth, existing, grants)) {
294
+ throw jsonrpc_errors.not_found('cell', { reason: ERROR_CELL_NOT_FOUND });
295
+ }
296
+ const deleted = await query_cell_delete(ctx, input.cell_id, { deleted_by: auth.actor.id });
297
+ if (!deleted) {
298
+ // Raced with another deleter.
299
+ throw jsonrpc_errors.not_found('cell', { reason: ERROR_CELL_NOT_FOUND });
300
+ }
301
+ emit_cell_audit(ctx, 'cell_delete', existing, deps, auth);
302
+ return { ok: true, deleted: true };
303
+ };
304
+ /**
305
+ * Insert one cloned cell row owned by the caller.
306
+ *
307
+ * `data` is `{...source.data, ...patch}` — patch wins last for
308
+ * predictable merge semantics. `path` is always nulled (admin-only
309
+ * paths can't auto-clone — no admin escalation through clone).
310
+ * Relations (`cell_field`, `cell_item`) are NOT copied here; the
311
+ * caller copies fields shallowly and walks items per the clone
312
+ * semantics in `clone_handler`. Provenance lives only in the
313
+ * `cell_clone` audit row's `source_id`; `data` carries no
314
+ * server-stamped provenance fields.
315
+ */
316
+ const clone_one_cell_row = async (ctx, source, auth, options) => {
317
+ // Both `source.data` and `options.patch_data` are CellData (loose
318
+ // objects). Patch-last shallow merge composes cleanly.
319
+ const merged_data = options.patch_data !== undefined ? { ...source.data, ...options.patch_data } : source.data;
320
+ // Block cross-kind patches before per-kind shape validation: a
321
+ // kind-A → kind-B patch can pass `validate_data` coincidentally
322
+ // (loose shapes accept overlapping fields) and produce an
323
+ // incoherent result. Reject when the merged kind diverges from the
324
+ // source's kind. Both undefined or equal kinds pass through;
325
+ // one-sided undefined is permissive (caller patching unrelated
326
+ // fields on a typeless source).
327
+ if (source.data.kind !== undefined &&
328
+ merged_data.kind !== undefined &&
329
+ source.data.kind !== merged_data.kind) {
330
+ throw jsonrpc_errors.invalid_params('cell_clone cannot change kind via with_data_patch', {
331
+ reason: ERROR_CELL_CLONE_KIND_MISMATCH,
332
+ });
333
+ }
334
+ // Per-kind shape validation runs on the merged result (sub-API).
335
+ // Source rows are validated on their original create; the patch
336
+ // could violate the kind shape (e.g., remove a required field).
337
+ const validated_data = validate_data_or_throw(merged_data);
338
+ return query_cell_create(ctx, {
339
+ // Boundary cast (see `create_handler`).
340
+ data: validated_data,
341
+ visibility: source.visibility,
342
+ path: null, // admin-only paths cannot auto-clone
343
+ created_by: auth.actor.id,
344
+ });
345
+ };
346
+ const clone_handler = async (input, ctx) => {
347
+ const auth = ctx.auth;
348
+ const source = await query_cell_get(ctx, input.source_id);
349
+ if (!source) {
350
+ throw jsonrpc_errors.not_found('cell', { reason: ERROR_CELL_NOT_FOUND });
351
+ }
352
+ const source_grants = await query_cell_grant_list_for_cell(ctx, source.id);
353
+ // 404 covers both miss and unauthorized read — same shape as cell_get
354
+ // so the existence of private cells doesn't leak through clones.
355
+ if (!can_view_cell(auth, source, source_grants)) {
356
+ throw jsonrpc_errors.not_found('cell', { reason: ERROR_CELL_NOT_FOUND });
357
+ }
358
+ const deep = input.deep === true;
359
+ // Pre-fetch source relations OUTSIDE the transaction so authz checks
360
+ // (per-child `can_view_cell`) on `query_cell_get`-fetched grants
361
+ // don't bloat the transaction. The fetch sees the world as of
362
+ // pre-transaction; the inserts inside the transaction commit
363
+ // atomically together.
364
+ const source_fields = await query_cell_field_list_for_source(ctx, source.id);
365
+ const source_items = deep
366
+ ? await query_cell_item_list_for_parent(ctx, source.id)
367
+ : [];
368
+ // In deep mode, pre-resolve which children are viewable so we can
369
+ // skip un-viewable ones silently (strict per-target view filter, D8).
370
+ // Batched (not per-child): one bulk row load + one batched visibility
371
+ // filter over the whole child id-set — same shape the forward reads
372
+ // and `filter_visible_target_ids` use, instead of an N+1 walk. Done
373
+ // outside the transaction for the same reason as the relation fetch.
374
+ const cloneable_items = [];
375
+ const cloneable_children = new Map();
376
+ if (deep) {
377
+ const child_ids = source_items.map((i) => i.child_id);
378
+ const [child_rows, visible_children] = await Promise.all([
379
+ query_cell_load_many(ctx, child_ids),
380
+ filter_visible_target_ids(ctx, auth, child_ids),
381
+ ]);
382
+ const child_by_id = new Map(child_rows.map((r) => [r.id, r]));
383
+ for (const item of source_items) {
384
+ const child = child_by_id.get(item.child_id);
385
+ // Skip missing (soft-deleted/vanished) and non-viewable children
386
+ // silently — no count is surfaced, so the source's hidden-child
387
+ // count never leaks to the cloner (D8).
388
+ if (!child || !visible_children.has(item.child_id))
389
+ continue;
390
+ cloneable_items.push(item);
391
+ cloneable_children.set(item.child_id, child);
392
+ }
393
+ }
394
+ // `cell_clone_action_spec.side_effects = true`, so the RPC
395
+ // dispatcher already wraps the handler in a transaction —
396
+ // `ctx.db` is transaction-scoped. Every write below participates
397
+ // in that single transaction and rolls back together on any
398
+ // failure. No nested `ctx.db.transaction(...)` here.
399
+ const cloned_root = await clone_one_cell_row(ctx, source, auth, {
400
+ patch_data: input.with_data_patch,
401
+ });
402
+ // Copy outgoing fields shallowly: the clone points at the same
403
+ // targets the source did (fields are JSON references, not
404
+ // contents). Cloning `foo` should not deep-clone `foo.author`.
405
+ //
406
+ // Strict target-visibility (D8): only copy field edges whose target
407
+ // the caller may view. `cell_field_set` gates the target on
408
+ // `can_view_cell`, so without this filter clone would be a side door
409
+ // to owning edges that point at private cells the cloner can't see.
410
+ // Non-viewable targets are dropped silently.
411
+ const field_targets_visible = await filter_visible_target_ids(ctx, auth, source_fields.map((f) => f.target_id));
412
+ for (const f of source_fields) {
413
+ if (!field_targets_visible.has(f.target_id))
414
+ continue;
415
+ // Route through the query layer (not raw SQL) for parity with the
416
+ // item-copy path below. The clone target is fresh, so the UPSERT's
417
+ // `ON CONFLICT` is a no-op — behaviorally a plain insert.
418
+ await query_cell_field_set(ctx, {
419
+ source_id: cloned_root.id,
420
+ name: f.name,
421
+ target_id: f.target_id,
422
+ });
423
+ }
424
+ if (deep) {
425
+ // Deep mode: clone each viewable direct child, attach via new
426
+ // cell_item rows reusing the source position. Reusing the
427
+ // position keeps lex order stable and avoids the
428
+ // fractional_index machinery for an internal walk that can't
429
+ // collide (the clone's `(parent_id, position)` slot is fresh —
430
+ // no concurrent writer).
431
+ for (const item of cloneable_items) {
432
+ const child = cloneable_children.get(item.child_id);
433
+ const cloned_child = await clone_one_cell_row(ctx, child, auth, {});
434
+ await query_cell_item_insert(ctx, {
435
+ parent_id: cloned_root.id,
436
+ position: item.position,
437
+ child_id: cloned_child.id,
438
+ });
439
+ }
440
+ }
441
+ else {
442
+ // Shallow: copy `cell_item` rows referencing the source's
443
+ // children, sharing both `child_id` and `position`. Preserves
444
+ // the "shallow copies the clone's outgoing edges, sharing
445
+ // targets with the source" invariant.
446
+ //
447
+ // Strict target-visibility (D8): only copy item edges whose child
448
+ // the caller may view — the same invariant the deep walk and
449
+ // `cell_item_insert` enforce. Non-viewable children are skipped
450
+ // silently (no count surfaced — see the deep-walk note on the
451
+ // hidden-child-count leak).
452
+ const shallow_items = await query_cell_item_list_for_parent(ctx, source.id);
453
+ const shallow_children_visible = await filter_visible_target_ids(ctx, auth, shallow_items.map((i) => i.child_id));
454
+ for (const item of shallow_items) {
455
+ if (!shallow_children_visible.has(item.child_id))
456
+ continue;
457
+ await query_cell_item_insert(ctx, {
458
+ parent_id: cloned_root.id,
459
+ position: item.position,
460
+ child_id: item.child_id,
461
+ });
462
+ }
463
+ }
464
+ // Audit envelope is richer than the standard cell-mutation envelope
465
+ // — emit directly rather than threading it through `emit_cell_audit`.
466
+ // `kind` is read from `source.data` (not the cloned row) so the
467
+ // audit trail attributes the clone to the source's shape.
468
+ const source_kind = source.data.kind;
469
+ const cloned_child_count = deep ? cloneable_items.length : 0;
470
+ deps.audit.emit(ctx, {
471
+ event_type: 'cell_clone',
472
+ actor_id: auth.actor.id,
473
+ account_id: auth.account.id,
474
+ ip: ctx.client_ip,
475
+ metadata: {
476
+ source_id: source.id,
477
+ new_id: cloned_root.id,
478
+ deep,
479
+ item_count: cloned_child_count,
480
+ ...(source_kind !== undefined ? { kind: source_kind } : {}),
481
+ },
482
+ });
483
+ return { cell: to_cell_json(cloned_root) };
484
+ };
485
+ const list_handler = async (input, ctx) => {
486
+ const auth = ctx.auth;
487
+ // Null auth + `created_by` is a soft account-id enumeration probe
488
+ // ("does account X have any public cells?") — require auth to use it.
489
+ if (auth === null && input.created_by !== undefined) {
490
+ throw jsonrpc_errors.invalid_params('cell_list created_by requires authentication', {
491
+ reason: ERROR_CELL_LIST_CREATED_BY_REQUIRES_AUTH,
492
+ });
493
+ }
494
+ // `shared_with: 'me'` resolves to the caller's actor + role_grants;
495
+ // no auth means no caller, no admit path.
496
+ if (auth === null && input.shared_with !== undefined) {
497
+ throw jsonrpc_errors.invalid_params('cell_list shared_with requires authentication', {
498
+ reason: ERROR_CELL_LIST_SHARED_WITH_REQUIRES_AUTH,
499
+ });
500
+ }
501
+ // Project the active role_grant set into parallel arrays for the
502
+ // `cell_grant` role-shaped EXISTS. Middleware (`request_context`)
503
+ // has already filtered to active-only role_grants; we trust that and
504
+ // pass NULL `scope_id`s through (global-scope role_grants).
505
+ const role_grant_roles = auth ? auth.role_grants.map((p) => p.role) : [];
506
+ const role_grant_scope_ids = auth ? auth.role_grants.map((p) => p.scope_id) : [];
507
+ const caller_actor_id = auth?.actor?.id ?? null;
508
+ const rows = await query_cell_list(ctx, {
509
+ ids: input.ids,
510
+ data_kind: input.data_kind,
511
+ visibility: input.visibility,
512
+ ref: input.ref,
513
+ created_by: input.created_by,
514
+ path_prefix: input.path_prefix,
515
+ viewer_actor_id: caller_actor_id,
516
+ viewer_is_admin: auth ? has_role(auth, ROLE_ADMIN) : false,
517
+ caller_actor_id,
518
+ caller_role_grant_roles: role_grant_roles,
519
+ caller_role_grant_scope_ids: role_grant_scope_ids,
520
+ shared_with_caller_only: input.shared_with === 'me',
521
+ order_by: input.order_by,
522
+ order_direction: input.order_direction,
523
+ // Apply the default cap when caller omits `limit`. Without this
524
+ // the SQL `LIMIT NULL` returns every matching row.
525
+ limit: input.limit ?? CELL_LIST_LIMIT_DEFAULT,
526
+ offset: input.offset,
527
+ });
528
+ let cell_grants;
529
+ if (input.shared_with === 'me' && rows.length > 0 && caller_actor_id !== null) {
530
+ const grant_rows = await query_cell_grants_for_caller_in_cells(ctx, rows.map((r) => r.id), caller_actor_id, role_grant_roles, role_grant_scope_ids);
531
+ cell_grants = {};
532
+ for (const g of grant_rows) {
533
+ (cell_grants[g.cell_id] ??= []).push(to_grant_json(g));
534
+ }
535
+ }
536
+ return { cells: rows.map(to_cell_json), cell_grants };
537
+ };
538
+ return [
539
+ rpc_action(cell_create_action_spec, create_handler),
540
+ rpc_action(cell_get_action_spec, get_handler),
541
+ rpc_action(cell_update_action_spec, update_handler),
542
+ rpc_action(cell_delete_action_spec, delete_handler),
543
+ rpc_action(cell_list_action_spec, list_handler),
544
+ rpc_action(cell_clone_action_spec, clone_handler),
545
+ ];
546
+ };
@@ -0,0 +1,131 @@
1
+ /**
2
+ * `cell_audit_list` RPC — per-cell audit timeline.
3
+ *
4
+ * Returns audit-log rows whose metadata names this cell on any of the
5
+ * `(cell_id, source_id, parent_id, child_id, target_id, new_id)` keys
6
+ * used by the cell-domain event types. The handler 404-masks for
7
+ * callers who are not in the cell's manage tier (`can_manage_cell` =
8
+ * admin / owner) — the timeline reveals who-touched-the-cell, so it is
9
+ * gated above `can_view_cell`.
10
+ *
11
+ * Read-only; no audit side effect. Returns the most-recent
12
+ * `CELL_AUDIT_LIST_DEFAULT_LIMIT` events; pagination is intentionally
13
+ * not on the wire yet — the only consumer renders a single page. Add
14
+ * `{before, limit}` input + `{next_before}` output together when a
15
+ * paginating consumer surfaces.
16
+ *
17
+ * @module
18
+ */
19
+ import { z } from 'zod';
20
+ /**
21
+ * Wire shape for a single cell-audit row. Narrower than
22
+ * `AuditLogEventJson` — `account_id` and `target_account_id` are
23
+ * deliberately omitted so this verb does NOT surface the actor↔account
24
+ * join. `target_actor_id` and `metadata` are dropped too: `target_actor_id`
25
+ * is NULL for every cell-domain event (the grant recipient lives inside
26
+ * `metadata.principal` on grant rows, not on the audit-log top-level
27
+ * field); `metadata` is unread by the timeline UI.
28
+ *
29
+ * `ip` is also omitted: it is PII about the actors who touched the cell,
30
+ * and even at the manage tier this per-cell timeline has no need for it
31
+ * (admins reach the full `audit_log` surface, which carries `ip`, through
32
+ * the admin audit verbs). Keeping it off this wire avoids leaking
33
+ * collaborators' IPs to a cell's owner.
34
+ *
35
+ * All omitted fields can be re-added under a richer admin-only
36
+ * event-detail view later — keep the wire surface honest about what
37
+ * consumers use.
38
+ */
39
+ export declare const CellAuditEventJson: z.ZodObject<{
40
+ id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
41
+ seq: z.ZodNumber;
42
+ event_type: z.ZodString;
43
+ outcome: z.ZodEnum<{
44
+ success: "success";
45
+ failure: "failure";
46
+ }>;
47
+ actor_id: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
48
+ created_at: z.ZodString;
49
+ }, z.core.$strict>;
50
+ export type CellAuditEventJson = z.infer<typeof CellAuditEventJson>;
51
+ /** Page size for `cell_audit_list`. Single page at MVP; no cursor wire. */
52
+ export declare const CELL_AUDIT_LIST_DEFAULT_LIMIT = 50;
53
+ export declare const CellAuditListInput: z.ZodObject<{
54
+ cell_id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
55
+ acting: z.ZodOptional<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
56
+ }, z.core.$strict>;
57
+ export type CellAuditListInput = z.infer<typeof CellAuditListInput>;
58
+ export declare const CellAuditListOutput: z.ZodObject<{
59
+ events: z.ZodArray<z.ZodObject<{
60
+ id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
61
+ seq: z.ZodNumber;
62
+ event_type: z.ZodString;
63
+ outcome: z.ZodEnum<{
64
+ success: "success";
65
+ failure: "failure";
66
+ }>;
67
+ actor_id: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
68
+ created_at: z.ZodString;
69
+ }, z.core.$strict>>;
70
+ }, z.core.$strict>;
71
+ export type CellAuditListOutput = z.infer<typeof CellAuditListOutput>;
72
+ export declare const cell_audit_list_action_spec: {
73
+ method: string;
74
+ kind: "request_response";
75
+ initiator: "frontend";
76
+ auth: {
77
+ account: "required";
78
+ actor: "required";
79
+ };
80
+ side_effects: false;
81
+ input: z.ZodObject<{
82
+ cell_id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
83
+ acting: z.ZodOptional<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
84
+ }, z.core.$strict>;
85
+ output: z.ZodObject<{
86
+ events: z.ZodArray<z.ZodObject<{
87
+ id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
88
+ seq: z.ZodNumber;
89
+ event_type: z.ZodString;
90
+ outcome: z.ZodEnum<{
91
+ success: "success";
92
+ failure: "failure";
93
+ }>;
94
+ actor_id: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
95
+ created_at: z.ZodString;
96
+ }, z.core.$strict>>;
97
+ }, z.core.$strict>;
98
+ async: true;
99
+ description: string;
100
+ };
101
+ /** Registry export to compose into `all_cell_action_specs`. */
102
+ export declare const all_cell_audit_action_specs: readonly [{
103
+ method: string;
104
+ kind: "request_response";
105
+ initiator: "frontend";
106
+ auth: {
107
+ account: "required";
108
+ actor: "required";
109
+ };
110
+ side_effects: false;
111
+ input: z.ZodObject<{
112
+ cell_id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
113
+ acting: z.ZodOptional<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
114
+ }, z.core.$strict>;
115
+ output: z.ZodObject<{
116
+ events: z.ZodArray<z.ZodObject<{
117
+ id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
118
+ seq: z.ZodNumber;
119
+ event_type: z.ZodString;
120
+ outcome: z.ZodEnum<{
121
+ success: "success";
122
+ failure: "failure";
123
+ }>;
124
+ actor_id: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
125
+ created_at: z.ZodString;
126
+ }, z.core.$strict>>;
127
+ }, z.core.$strict>;
128
+ async: true;
129
+ description: string;
130
+ }];
131
+ //# sourceMappingURL=cell_audit_action_specs.d.ts.map