@fuzdev/fuz_app 0.148.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.
- package/dist/actions/CLAUDE.md +11 -9
- package/dist/actions/action_bridge.d.ts +7 -0
- package/dist/actions/action_bridge.d.ts.map +1 -1
- package/dist/actions/action_bridge.js +10 -0
- package/dist/actions/action_event.d.ts.map +1 -1
- package/dist/actions/action_event.js +14 -1
- package/dist/actions/action_event_types.d.ts +1 -1
- package/dist/actions/action_event_types.d.ts.map +1 -1
- package/dist/actions/perform_action.d.ts +17 -1
- package/dist/actions/perform_action.d.ts.map +1 -1
- package/dist/actions/perform_action.js +30 -7
- package/dist/actions/register_action_ws.d.ts +8 -2
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +24 -4
- package/dist/actions/transports_ws_auth_guard.d.ts +6 -4
- package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
- package/dist/actions/transports_ws_auth_guard.js +9 -6
- package/dist/actions/ws_endpoint_spec.d.ts +1 -1
- package/dist/auth/CLAUDE.md +72 -33
- package/dist/auth/account_action_specs.d.ts +12 -0
- package/dist/auth/account_action_specs.d.ts.map +1 -1
- package/dist/auth/account_action_specs.js +16 -2
- package/dist/auth/account_actions.d.ts.map +1 -1
- package/dist/auth/account_actions.js +7 -4
- package/dist/auth/actor_lookup_queries.d.ts +3 -2
- package/dist/auth/actor_lookup_queries.d.ts.map +1 -1
- package/dist/auth/actor_lookup_queries.js +6 -3
- package/dist/auth/actor_search_queries.d.ts +5 -2
- package/dist/auth/actor_search_queries.d.ts.map +1 -1
- package/dist/auth/actor_search_queries.js +11 -4
- package/dist/auth/admin_action_specs.d.ts +4 -4
- package/dist/auth/admin_action_specs.d.ts.map +1 -1
- package/dist/auth/admin_action_specs.js +33 -12
- package/dist/auth/admin_actions.d.ts.map +1 -1
- package/dist/auth/admin_actions.js +35 -6
- package/dist/auth/all_cell_actions.d.ts +11 -2
- package/dist/auth/all_cell_actions.d.ts.map +1 -1
- package/dist/auth/all_cell_actions.js +2 -1
- package/dist/auth/audit_emitter.d.ts +23 -3
- package/dist/auth/audit_emitter.d.ts.map +1 -1
- package/dist/auth/audit_emitter.js +14 -4
- package/dist/auth/audit_log_schema.d.ts +1 -0
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +9 -5
- package/dist/auth/bootstrap_account.d.ts +25 -1
- package/dist/auth/bootstrap_account.d.ts.map +1 -1
- package/dist/auth/bootstrap_account.js +37 -4
- package/dist/auth/bootstrap_routes.d.ts +3 -2
- package/dist/auth/bootstrap_routes.d.ts.map +1 -1
- package/dist/auth/bootstrap_routes.js +5 -4
- package/dist/auth/cell_action_specs.d.ts +215 -15
- package/dist/auth/cell_action_specs.d.ts.map +1 -1
- package/dist/auth/cell_action_specs.js +103 -16
- package/dist/auth/cell_actions.d.ts +40 -18
- package/dist/auth/cell_actions.d.ts.map +1 -1
- package/dist/auth/cell_actions.js +126 -19
- package/dist/auth/cell_audit_events.d.ts +1 -1
- package/dist/auth/cell_audit_events.d.ts.map +1 -1
- package/dist/auth/cell_audit_events.js +3 -2
- package/dist/auth/cell_audit_metadata.d.ts +11 -0
- package/dist/auth/cell_audit_metadata.d.ts.map +1 -1
- package/dist/auth/cell_audit_metadata.js +10 -0
- package/dist/auth/cell_grant_action_specs.d.ts +7 -0
- package/dist/auth/cell_grant_action_specs.d.ts.map +1 -1
- package/dist/auth/cell_grant_action_specs.js +7 -0
- package/dist/auth/cell_grant_actions.d.ts +36 -4
- package/dist/auth/cell_grant_actions.d.ts.map +1 -1
- package/dist/auth/cell_grant_actions.js +20 -3
- package/dist/auth/credential_type_schema.d.ts +3 -2
- package/dist/auth/credential_type_schema.d.ts.map +1 -1
- package/dist/auth/credential_type_schema.js +3 -2
- package/dist/auth/role_grant_offer_action_specs.d.ts +18 -2
- package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_action_specs.js +30 -6
- package/dist/auth/role_grant_offer_actions.d.ts +9 -4
- package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_actions.js +58 -7
- package/dist/auth/role_grant_offer_notifications.d.ts +3 -2
- package/dist/auth/role_grant_offer_notifications.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_notifications.js +3 -2
- package/dist/auth/role_grant_offer_queries.d.ts +20 -0
- package/dist/auth/role_grant_offer_queries.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_queries.js +35 -0
- package/dist/auth/role_grant_queries.d.ts +27 -9
- package/dist/auth/role_grant_queries.d.ts.map +1 -1
- package/dist/auth/role_grant_queries.js +40 -13
- package/dist/auth/role_schema.d.ts +14 -8
- package/dist/auth/role_schema.d.ts.map +1 -1
- package/dist/auth/role_schema.js +6 -3
- package/dist/auth/self_service_role_actions.d.ts.map +1 -1
- package/dist/auth/self_service_role_actions.js +20 -0
- package/dist/db/CLAUDE.md +9 -2
- package/dist/db/cell_ddl.d.ts +9 -1
- package/dist/db/cell_ddl.d.ts.map +1 -1
- package/dist/db/cell_ddl.js +11 -1
- package/dist/db/cell_queries.d.ts +53 -7
- package/dist/db/cell_queries.d.ts.map +1 -1
- package/dist/db/cell_queries.js +61 -7
- package/dist/http/CLAUDE.md +14 -2
- package/dist/http/body_limit.d.ts +3 -1
- package/dist/http/body_limit.d.ts.map +1 -1
- package/dist/http/body_limit.js +6 -1
- package/dist/http/error_schemas.d.ts +8 -0
- package/dist/http/error_schemas.d.ts.map +1 -1
- package/dist/http/error_schemas.js +12 -0
- package/dist/http/jsonrpc.d.ts +9 -0
- package/dist/http/jsonrpc.d.ts.map +1 -1
- package/dist/http/jsonrpc.js +17 -0
- package/dist/http/jsonrpc_errors.d.ts +40 -15
- package/dist/http/jsonrpc_errors.d.ts.map +1 -1
- package/dist/http/jsonrpc_errors.js +99 -35
- package/dist/http/proxy.d.ts +4 -2
- package/dist/http/proxy.d.ts.map +1 -1
- package/dist/http/proxy.js +7 -2
- package/dist/http/surface.d.ts +3 -1
- package/dist/http/surface.d.ts.map +1 -1
- package/dist/http/surface.js +7 -4
- package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
- package/dist/realtime/sse_auth_guard.js +3 -2
- package/dist/schema_meta.d.ts +92 -1
- package/dist/schema_meta.d.ts.map +1 -1
- package/dist/schema_meta.js +117 -0
- package/dist/server/app_server.d.ts +13 -5
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +32 -8
- package/dist/testing/CLAUDE.md +196 -82
- package/dist/testing/adversarial_input_cases.d.ts +67 -7
- package/dist/testing/adversarial_input_cases.d.ts.map +1 -1
- package/dist/testing/adversarial_input_cases.js +210 -165
- package/dist/testing/app_server.d.ts +3 -1
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/audit_drift_guard.d.ts +3 -2
- package/dist/testing/audit_drift_guard.d.ts.map +1 -1
- package/dist/testing/audit_drift_guard.js +5 -3
- package/dist/testing/cross_backend/actor_lookup.d.ts.map +1 -1
- package/dist/testing/cross_backend/actor_lookup.js +27 -0
- package/dist/testing/cross_backend/audit_cross_helpers.d.ts +6 -0
- package/dist/testing/cross_backend/audit_cross_helpers.d.ts.map +1 -1
- package/dist/testing/cross_backend/audit_cross_helpers.js +11 -0
- package/dist/testing/cross_backend/capabilities.d.ts +29 -5
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/capabilities.js +10 -6
- package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_crud.js +117 -2
- package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_gated_create.js +109 -13
- package/dist/testing/cross_backend/cell_moderate.d.ts +7 -1
- package/dist/testing/cross_backend/cell_moderate.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_moderate.js +68 -4
- package/dist/testing/cross_backend/cell_ref_admission.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_ref_admission.js +18 -9
- package/dist/testing/cross_backend/conformance_table.d.ts +5 -0
- package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
- package/dist/testing/cross_backend/conformance_table.js +12 -10
- package/dist/testing/cross_backend/cross_rpc.d.ts +3 -1
- package/dist/testing/cross_backend/cross_rpc.d.ts.map +1 -1
- package/dist/testing/cross_backend/cross_rpc.js +2 -2
- package/dist/testing/cross_backend/default_backend_configs.d.ts +9 -7
- package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_backend_configs.js +3 -3
- package/dist/testing/cross_backend/differential.d.ts +7 -0
- package/dist/testing/cross_backend/differential.d.ts.map +1 -1
- package/dist/testing/cross_backend/differential.js +17 -0
- package/dist/testing/cross_backend/expected_schema.json +1 -0
- package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
- package/dist/testing/cross_backend/full_spine_mount.js +5 -4
- package/dist/testing/cross_backend/role_grant_offer_lifecycle.d.ts.map +1 -1
- package/dist/testing/cross_backend/role_grant_offer_lifecycle.js +74 -2
- package/dist/testing/cross_backend/rpc_adversarial.d.ts +9 -0
- package/dist/testing/cross_backend/rpc_adversarial.d.ts.map +1 -1
- package/dist/testing/cross_backend/rpc_adversarial.js +16 -6
- package/dist/testing/cross_backend/rust_spine_consumer_surface.d.ts +1 -1
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +6 -3
- package/dist/testing/cross_backend/setup.d.ts +2 -2
- package/dist/testing/cross_backend/spine_suites.d.ts +85 -5
- package/dist/testing/cross_backend/spine_suites.d.ts.map +1 -1
- package/dist/testing/cross_backend/spine_suites.js +291 -20
- package/dist/testing/cross_backend/spine_surface_constants.d.ts +15 -0
- package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
- package/dist/testing/cross_backend/spine_surface_constants.js +24 -0
- package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts +18 -19
- package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts.map +1 -1
- package/dist/testing/cross_backend/test_cell_gated_create_authorize.js +66 -42
- package/dist/testing/cross_backend/testing_backdoor.d.ts +27 -4
- package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_backdoor.js +136 -41
- package/dist/testing/cross_backend/testing_reset_actions.d.ts +30 -15
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.js +52 -35
- package/dist/testing/cross_backend/transport_composition.js +3 -3
- package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/ts_spine_backend_config.js +6 -3
- package/dist/testing/rpc_attack_surface.d.ts +10 -0
- package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
- package/dist/testing/rpc_attack_surface.js +15 -3
- package/dist/testing/rpc_envelope_cases.d.ts +5 -2
- package/dist/testing/rpc_envelope_cases.d.ts.map +1 -1
- package/dist/testing/rpc_envelope_cases.js +4 -1
- package/dist/testing/rpc_round_trip.js +3 -3
- package/dist/testing/schema_generators.d.ts +99 -2
- package/dist/testing/schema_generators.d.ts.map +1 -1
- package/dist/testing/schema_generators.js +244 -76
- package/dist/testing/stubs.d.ts +4 -3
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +6 -4
- package/dist/zod.d.ts +9 -0
- package/dist/zod.d.ts.map +1 -1
- package/dist/zod.js +20 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role_grant_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAS3E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAEpE;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,0KAaoB,CAAC;AAcpD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,SAAS,EACf,OAAO,oBAAoB,KACzB,OAAO,CAAC,SAAS,CAmCnB,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,SAAS,EACf,IAAI,IAAI,KACN,OAAO,CAAC,SAAS,GAAG,SAAS,CAI9B,CAAC;AAEH
|
|
1
|
+
{"version":3,"file":"role_grant_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_queries.ts"],"names":[],"mappings":"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"}
|
|
@@ -103,11 +103,12 @@ export const query_create_role_grant = async (deps, input) => {
|
|
|
103
103
|
*/
|
|
104
104
|
export const query_role_grant_by_id = async (deps, id) => deps.db.query_one(`SELECT ${columns_sql(ROLE_GRANT_COLUMNS, role_grant_expr)} FROM role_grant WHERE id = $1`, [id]);
|
|
105
105
|
/**
|
|
106
|
-
* Look up the role of an active role_grant (constrained to a
|
|
107
|
-
* actor) plus the actor's `account_id`.
|
|
106
|
+
* Look up the role and scope of an active role_grant (constrained to a
|
|
107
|
+
* specific actor) plus the actor's `account_id`.
|
|
108
108
|
*
|
|
109
109
|
* Used by admin routes to inspect the role_grant's role before acting
|
|
110
|
-
* (e.g., enforcing the admin-grant-path gate
|
|
110
|
+
* (e.g., enforcing the admin-grant-path gate and the global-`admin` guards on
|
|
111
|
+
* revoke). The actor constraint
|
|
111
112
|
* mirrors `query_revoke_role_grant` so IDOR protection is consistent:
|
|
112
113
|
* a caller can only see role_grants belonging to the target actor.
|
|
113
114
|
*
|
|
@@ -124,10 +125,10 @@ export const query_role_grant_by_id = async (deps, id) => deps.db.query_one(`SEL
|
|
|
124
125
|
* @param deps - query dependencies
|
|
125
126
|
* @param role_grant_id - the role_grant id to look up
|
|
126
127
|
* @param actor_id - the actor that must own the role_grant
|
|
127
|
-
* @returns `{role, account_id}` on a match, or `null`
|
|
128
|
+
* @returns `{role, scope_id, account_id}` on a match, or `null`
|
|
128
129
|
*/
|
|
129
130
|
export const query_role_grant_find_active_role_for_actor = async (deps, role_grant_id, actor_id) => {
|
|
130
|
-
const row = await deps.db.query_one(`SELECT role_grant.role, actor.account_id
|
|
131
|
+
const row = await deps.db.query_one(`SELECT role_grant.role, role_grant.scope_id, actor.account_id
|
|
131
132
|
FROM role_grant
|
|
132
133
|
JOIN actor ON actor.id = role_grant.actor_id
|
|
133
134
|
WHERE role_grant.id = $1 AND role_grant.actor_id = $2 AND role_grant.revoked_at IS NULL`, [role_grant_id, actor_id]);
|
|
@@ -264,7 +265,7 @@ export const query_account_has_global_role = async (deps, account_id, role) => {
|
|
|
264
265
|
* Like `query_account_has_global_role`, but only counts the grant when the
|
|
265
266
|
* **account and the granting actor are both active** (`deleted_at IS NULL`).
|
|
266
267
|
* Used by the last-admin branch of the removability guard: a soft-deleted admin
|
|
267
|
-
* can't log in and is excluded from `
|
|
268
|
+
* can't log in and is excluded from `query_count_active_accounts_with_global_role_for_update`,
|
|
268
269
|
* so the guard must use the same active predicate when testing whether the
|
|
269
270
|
* *target* is an admin — otherwise removing an already-tombstoned admin is
|
|
270
271
|
* falsely blocked as `cannot_delete_last_admin`. Soft-delete does **not** revoke
|
|
@@ -298,19 +299,39 @@ export const query_account_has_active_global_role = async (deps, account_id, rol
|
|
|
298
299
|
/**
|
|
299
300
|
* Count **active** accounts (`deleted_at IS NULL`) holding an active global
|
|
300
301
|
* role_grant for `role`, counting only grants on **active actors**
|
|
301
|
-
* (`act.deleted_at IS NULL`). Used by the last-admin
|
|
302
|
-
* `
|
|
302
|
+
* (`act.deleted_at IS NULL`) — and lock what it counted. Used by the last-admin
|
|
303
|
+
* guards on `account_delete` / `account_purge` and on `role_grant_revoke` of an
|
|
304
|
+
* `admin` grant: soft-delete revokes neither a tombstoned account's nor a
|
|
303
305
|
* tombstoned actor's admin grant, so an unfiltered count would include unusable
|
|
304
306
|
* admins — this joins `account` *and* filters the actor, so an account whose
|
|
305
307
|
* only admin grant sits on a tombstoned actor no longer counts (otherwise the
|
|
306
|
-
* guard could fail-open and let the last usable admin be
|
|
308
|
+
* guard could fail-open and let the last usable admin be removed).
|
|
309
|
+
*
|
|
310
|
+
* **Locks** every counted `role_grant`, `actor`, and `account` row
|
|
311
|
+
* (`FOR UPDATE`) until the caller's transaction ends, so two guards racing to
|
|
312
|
+
* remove the last two admins serialize: the second waits for the first to
|
|
313
|
+
* commit, re-reads the rows it changed, and counts one. Run it inside the
|
|
314
|
+
* transaction that does the removal. Twin of the Rust
|
|
315
|
+
* `query_count_active_accounts_with_global_role_for_update`.
|
|
316
|
+
*
|
|
317
|
+
* `exclude_role_grant_id` leaves one grant out of the count, so a revoke asks
|
|
318
|
+
* whether any active admin account would remain without that grant — an
|
|
319
|
+
* account holding the grant on two actors keeps counting through the other.
|
|
320
|
+
* The excluded grant is still locked, so two revokes of each other's grant
|
|
321
|
+
* serialize rather than deadlock.
|
|
307
322
|
*
|
|
308
323
|
* @param deps - query dependencies
|
|
309
324
|
* @param role - the role to count (e.g. `ROLE_ADMIN`)
|
|
325
|
+
* @param exclude_role_grant_id - a grant to leave out of the count (the one being revoked)
|
|
310
326
|
* @returns the number of distinct active accounts with an active global `role` grant
|
|
327
|
+
* @mutates `role_grant` / `actor` / `account` rows - row-locks the counted rows for the transaction
|
|
311
328
|
*/
|
|
312
|
-
export const
|
|
313
|
-
|
|
329
|
+
export const query_count_active_accounts_with_global_role_for_update = async (deps, role, exclude_role_grant_id = null) => {
|
|
330
|
+
// `FOR UPDATE` refuses an aggregate, so the rows come back and are counted
|
|
331
|
+
// here. The excluded grant is dropped in code, not SQL: filtering it in the
|
|
332
|
+
// WHERE would leave it unlocked, and two cross-revokes (each locking only the
|
|
333
|
+
// other's grant, then updating it) would deadlock instead of serializing
|
|
334
|
+
const rows = await deps.db.query(`SELECT a.id AS account_id, rg.id AS role_grant_id
|
|
314
335
|
FROM account a
|
|
315
336
|
JOIN actor act ON act.account_id = a.id
|
|
316
337
|
JOIN role_grant rg ON rg.actor_id = act.id
|
|
@@ -319,8 +340,14 @@ export const query_count_active_accounts_with_global_role = async (deps, role) =
|
|
|
319
340
|
AND rg.role = $1
|
|
320
341
|
AND rg.scope_id IS NULL
|
|
321
342
|
AND rg.revoked_at IS NULL
|
|
322
|
-
AND (rg.expires_at IS NULL OR rg.expires_at > NOW())
|
|
323
|
-
|
|
343
|
+
AND (rg.expires_at IS NULL OR rg.expires_at > NOW())
|
|
344
|
+
FOR UPDATE`, [role]);
|
|
345
|
+
const account_ids = new Set();
|
|
346
|
+
for (const row of rows) {
|
|
347
|
+
if (row.role_grant_id !== exclude_role_grant_id)
|
|
348
|
+
account_ids.add(row.account_id);
|
|
349
|
+
}
|
|
350
|
+
return account_ids.size;
|
|
324
351
|
};
|
|
325
352
|
/**
|
|
326
353
|
* List all role_grants for an actor (including revoked/expired).
|
|
@@ -64,7 +64,7 @@ export declare const is_builtin_role: (name: string) => name is BuiltinRole;
|
|
|
64
64
|
/**
|
|
65
65
|
* Configuration for a role.
|
|
66
66
|
*
|
|
67
|
-
* Each role declares the credential types its holders
|
|
67
|
+
* Each role declares the credential types its holders are meant to use, the
|
|
68
68
|
* scope kinds it applies to, and the grant paths through which it can
|
|
69
69
|
* be granted. Every cross-axis field is an open-registry string array —
|
|
70
70
|
* `required_credential_types` against `create_credential_type_schema`,
|
|
@@ -93,10 +93,13 @@ export interface RoleSpec {
|
|
|
93
93
|
/** Admin-UI-facing copy describing the role's intent. */
|
|
94
94
|
description?: string;
|
|
95
95
|
/**
|
|
96
|
-
* Credential types whose holders are
|
|
97
|
-
*
|
|
98
|
-
* `credential_types` registry passed to `create_role_schema
|
|
99
|
-
*
|
|
96
|
+
* Credential types whose holders are meant to exercise this role.
|
|
97
|
+
* Informative only: each entry is checked at construction time against
|
|
98
|
+
* the `credential_types` registry passed to `create_role_schema`, but no
|
|
99
|
+
* middleware or dispatcher reads it per request — a route or action
|
|
100
|
+
* gates credentials with its own `auth.credential_types` (the keeper
|
|
101
|
+
* verbs declare `['daemon_token']` there). Empty array = any
|
|
102
|
+
* authenticated credential type.
|
|
100
103
|
*/
|
|
101
104
|
required_credential_types?: ReadonlyArray<string>;
|
|
102
105
|
/**
|
|
@@ -150,9 +153,12 @@ export interface RoleSchemaResult {
|
|
|
150
153
|
*
|
|
151
154
|
* Call once at server init. The returned `Role` schema validates role
|
|
152
155
|
* strings at I/O boundaries (grant endpoint, role_grant queries). The
|
|
153
|
-
* `role_specs` map is read by
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
+
* `role_specs` map is read by the admin / self-service factories to derive
|
|
157
|
+
* their default eligibility filters from `RoleSpec.grant_paths`, and by the
|
|
158
|
+
* cell grant verbs to refuse an unregistered role principal.
|
|
159
|
+
* `RoleSpec.required_credential_types` is validated here but enforced
|
|
160
|
+
* nowhere — credential gating lives on each route's and action's
|
|
161
|
+
* `auth.credential_types`.
|
|
156
162
|
*
|
|
157
163
|
* Construction-time guards (all fire on misconfiguration):
|
|
158
164
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAEN,KAAK,0BAA0B,EAC/B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAGN,KAAK,qBAAqB,EAC1B,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAEpE;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAEvC;;;GAGG;AACH,eAAO,MAAM,QAAQ,aAMpB,CAAC;AACF,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAC;AAIhD,sFAAsF;AACtF,eAAO,MAAM,WAAW,WAAW,CAAC;AAEpC,iEAAiE;AACjE,eAAO,MAAM,UAAU,UAAU,CAAC;AAElC,+CAA+C;AAC/C,eAAO,MAAM,aAAa,8BAAqC,CAAC;AAEhE,yCAAyC;AACzC,eAAO,MAAM,WAAW;;;EAAwB,CAAC;AACjD,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,eAAe,GAAI,MAAM,MAAM,KAAG,IAAI,IAAI,WACX,CAAC;AAE7C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,WAAW,QAAQ;IACxB,0GAA0G;IAC1G,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB
|
|
1
|
+
{"version":3,"file":"role_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAEN,KAAK,0BAA0B,EAC/B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAGN,KAAK,qBAAqB,EAC1B,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAEpE;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAEvC;;;GAGG;AACH,eAAO,MAAM,QAAQ,aAMpB,CAAC;AACF,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAC;AAIhD,sFAAsF;AACtF,eAAO,MAAM,WAAW,WAAW,CAAC;AAEpC,iEAAiE;AACjE,eAAO,MAAM,UAAU,UAAU,CAAC;AAElC,+CAA+C;AAC/C,eAAO,MAAM,aAAa,8BAAqC,CAAC;AAEhE,yCAAyC;AACzC,eAAO,MAAM,WAAW;;;EAAwB,CAAC;AACjD,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,eAAe,GAAI,MAAM,MAAM,KAAG,IAAI,IAAI,WACX,CAAC;AAE7C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,WAAW,QAAQ;IACxB,0GAA0G;IAC1G,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;OAQG;IACH,yBAAyB,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAClD;;;;;OAKG;IACH,sBAAsB,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC/C;;;;;;;;;;OAUG;IACH,WAAW,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CACpC;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,0BAA0B,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,CAuBnE,CAAC;AAEH,iGAAiG;AACjG,MAAM,WAAW,uBAAuB;IACvC,uGAAuG;IACvG,gBAAgB,CAAC,EAAE,0BAA0B,CAAC;IAC9C,+FAA+F;IAC/F,WAAW,CAAC,EAAE,qBAAqB,CAAC;IACpC,oFAAoF;IACpF,WAAW,CAAC,EAAE,qBAAqB,CAAC;CACpC;AAED,oFAAoF;AACpF,MAAM,WAAW,gBAAgB;IAChC,0GAA0G;IAC1G,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACxB,yEAAyE;IACzE,UAAU,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;CAC1C;AAqBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AACH,eAAO,MAAM,kBAAkB,GAC9B,gBAAgB,aAAa,CAAC,QAAQ,CAAC,EACvC,UAAS,uBAA4B,KACnC,gBA2CF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,GAC/B,YAAY,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,EACzC,MAAM,MAAM,EACZ,YAAY,MAAM,KAChB,OAGF,CAAC;AAEF,kFAAkF;AAClF,eAAO,MAAM,0BAA0B,GACtC,YAAY,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,EACzC,YAAY,MAAM,KAChB,KAAK,CAAC,MAAM,CAMd,CAAC"}
|
package/dist/auth/role_schema.js
CHANGED
|
@@ -105,9 +105,12 @@ const validate_registry_membership = (role_name, field, values, registry) => {
|
|
|
105
105
|
*
|
|
106
106
|
* Call once at server init. The returned `Role` schema validates role
|
|
107
107
|
* strings at I/O boundaries (grant endpoint, role_grant queries). The
|
|
108
|
-
* `role_specs` map is read by
|
|
109
|
-
*
|
|
110
|
-
*
|
|
108
|
+
* `role_specs` map is read by the admin / self-service factories to derive
|
|
109
|
+
* their default eligibility filters from `RoleSpec.grant_paths`, and by the
|
|
110
|
+
* cell grant verbs to refuse an unregistered role principal.
|
|
111
|
+
* `RoleSpec.required_credential_types` is validated here but enforced
|
|
112
|
+
* nowhere — credential gating lives on each route's and action's
|
|
113
|
+
* `auth.credential_types`.
|
|
111
114
|
*
|
|
112
115
|
* Construction-time guards (all fire on misconfiguration):
|
|
113
116
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"self_service_role_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/self_service_role_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,OAAO,EAAuC,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAE/F,OAAO,EAKN,KAAK,gBAAgB,EACrB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAWnD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oCAAoC,GAAI,OAAO,QAAQ,CAAC,MAAM,CAAC,KAAG,IAW9E,CAAC;AAEF,sDAAsD;AACtD,MAAM,WAAW,6BAA6B;IAC7C;;;;;;;;;;;OAWG;IACH,cAAc,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACvC;;;;OAIG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;CACzB;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,iBAAiB,EACvB,UAAS,6BAAkC,KACzC,KAAK,CAAC,SAAS,
|
|
1
|
+
{"version":3,"file":"self_service_role_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/self_service_role_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,OAAO,EAAuC,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAE/F,OAAO,EAKN,KAAK,gBAAgB,EACrB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAWnD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oCAAoC,GAAI,OAAO,QAAQ,CAAC,MAAM,CAAC,KAAG,IAW9E,CAAC;AAEF,sDAAsD;AACtD,MAAM,WAAW,6BAA6B;IAC7C;;;;;;;;;;;OAWG;IACH,cAAc,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACvC;;;;OAIG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;CACzB;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,iBAAiB,EACvB,UAAS,6BAAkC,KACzC,KAAK,CAAC,SAAS,CAiJjB,CAAC"}
|
|
@@ -176,6 +176,26 @@ export const create_self_service_role_actions = (deps, options = {}) => {
|
|
|
176
176
|
self_service: true
|
|
177
177
|
}
|
|
178
178
|
});
|
|
179
|
+
// Supersede cascade — `query_revoke_role_grant` marks any pending offers
|
|
180
|
+
// for the same `(account, role, scope)` superseded as a side effect, and
|
|
181
|
+
// each gets its forensic row like the admin revoke's (empty in practice:
|
|
182
|
+
// offer creation gates on the role's `grant_paths`, so only a role that is
|
|
183
|
+
// both offerable and self-service-eligible has siblings). The WS supersede
|
|
184
|
+
// notification to each grantor is not sent (twin of the Rust path).
|
|
185
|
+
for (const offer of result.superseded_offers) {
|
|
186
|
+
deps.audit.emit_role_grant_target(ctx, auth, {
|
|
187
|
+
event_type: 'role_grant_offer_supersede',
|
|
188
|
+
target_account_id: offer.to_account_id,
|
|
189
|
+
target_actor_id: offer.to_actor_id,
|
|
190
|
+
metadata: {
|
|
191
|
+
offer_id: offer.id,
|
|
192
|
+
role: offer.role,
|
|
193
|
+
scope_id: offer.scope_id,
|
|
194
|
+
reason: 'role_grant_revoked',
|
|
195
|
+
cause_id: result.id
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
179
199
|
return { ok: true, enabled: false, changed: true };
|
|
180
200
|
};
|
|
181
201
|
return [rpc_action(self_service_role_set_action_spec, handler)];
|
package/dist/db/CLAUDE.md
CHANGED
|
@@ -94,7 +94,10 @@ The wire schemas + RPC handlers + authz predicates for this layer live in
|
|
|
94
94
|
`cell.moderation text` (nullable — `pending` / `approved` / `rejected`) is the
|
|
95
95
|
approval-lifecycle marker, peer to `visibility` (partial `idx_cell_moderation_pending`
|
|
96
96
|
on `root_id WHERE moderation = 'pending'` for the mod queue), set by the create
|
|
97
|
-
authorizer's verdict + the `cell_moderate` verb, never in `data`.
|
|
97
|
+
authorizer's verdict + the `cell_moderate` verb, never in `data`.
|
|
98
|
+
`cell.policy jsonb` (nullable) is a root's contribution policy, opaque to the
|
|
99
|
+
spine — a table CHECK holds it to roots and to JSON objects (`policy @> '{}'`,
|
|
100
|
+
the object test pglet runs too). Nullable
|
|
98
101
|
`created_by` / `updated_by` FKs to `actor` (NULL = system origin). GIN on `data`
|
|
99
102
|
/ `refs`; global partial-unique on `path`.
|
|
100
103
|
- **`cell_queries.ts`** — `query_cell_create / get / get_by_path / update /
|
|
@@ -109,7 +112,11 @@ delete / set_moderation`, `_list_by_kind / _list_by_creator`, the
|
|
|
109
112
|
the in-memory relation filter the `cell_get` bundle and the clone walk
|
|
110
113
|
run). `query_cell_set_moderation` is the dedicated
|
|
111
114
|
`cell_moderate` write (`moderation` is absent from `CellUpdatePatch`, so an
|
|
112
|
-
author can't self-approve via `cell_update`)
|
|
115
|
+
author can't self-approve via `cell_update`); approval moves only a
|
|
116
|
+
`pending` row and rejection a `pending` or `approved` one (the takedown) —
|
|
117
|
+
the `WHERE` carries the from-state. `query_cell_set_policy` is the dedicated
|
|
118
|
+
`cell_policy_set` write (`policy` is absent from `CellUpdatePatch`; its
|
|
119
|
+
`WHERE` matches roots only). `cell.refs` derived from
|
|
113
120
|
`data` via the exported `derive_cell_refs` on create/update (exact
|
|
114
121
|
whole-string hashes, sorted — byte-identical with the Rust `extract_refs`). `CellRow.grant_count` is a
|
|
115
122
|
derived projection (correlated subquery on `idx_cell_grant_cell`).
|
package/dist/db/cell_ddl.d.ts
CHANGED
|
@@ -78,8 +78,16 @@ export declare const CELL_VISIBILITY_TYPE = "\nDO $$ BEGIN\n\tCREATE TYPE cell_v
|
|
|
78
78
|
* (nullable text — `pending` / `approved` / `rejected`; `NULL` = unmoderated)
|
|
79
79
|
* is the approval-lifecycle marker, peer to `visibility` (a control field with
|
|
80
80
|
* a non-author writer — see `auth/cell_actions.ts`), never inside `data`.
|
|
81
|
+
*
|
|
82
|
+
* `policy` (nullable JSONB) is a root's contribution policy — an object opaque
|
|
83
|
+
* to the spine, read by the consumer's create authorizer (`NULL` = its floor
|
|
84
|
+
* governs). The table CHECK holds it to roots (`parent_id IS NULL`) and to JSON
|
|
85
|
+
* objects (`policy @> '{}'` holds for an object and for nothing else — the
|
|
86
|
+
* object test every driver runs, where `jsonb_typeof` is not on pglet). A
|
|
87
|
+
* manage-tier control field: written at a root's create and by
|
|
88
|
+
* `cell_policy_set`, never through `data`, which an editor grant can write.
|
|
81
89
|
*/
|
|
82
|
-
export declare const CELL_SCHEMA = "\nCREATE TABLE IF NOT EXISTS cell (\n\tid UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n\tdata JSONB NOT NULL,\n\tkind TEXT,\n\tvisibility cell_visibility NOT NULL DEFAULT 'private',\n\tpath TEXT,\n\trefs TEXT[],\n\tparent_id UUID REFERENCES cell(id) ON DELETE SET NULL,\n\troot_id UUID REFERENCES cell(id) ON DELETE SET NULL,\n\tmoderation TEXT,\n\tcreated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n\tupdated_at TIMESTAMPTZ,\n\tdeleted_at TIMESTAMPTZ,\n\tcreated_by UUID REFERENCES actor(id) ON DELETE SET NULL,\n\tupdated_by UUID REFERENCES actor(id) ON DELETE SET NULL\n)";
|
|
90
|
+
export declare const CELL_SCHEMA = "\nCREATE TABLE IF NOT EXISTS cell (\n\tid UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n\tdata JSONB NOT NULL,\n\tkind TEXT,\n\tvisibility cell_visibility NOT NULL DEFAULT 'private',\n\tpath TEXT,\n\trefs TEXT[],\n\tparent_id UUID REFERENCES cell(id) ON DELETE SET NULL,\n\troot_id UUID REFERENCES cell(id) ON DELETE SET NULL,\n\tmoderation TEXT,\n\tpolicy JSONB,\n\tcreated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n\tupdated_at TIMESTAMPTZ,\n\tdeleted_at TIMESTAMPTZ,\n\tcreated_by UUID REFERENCES actor(id) ON DELETE SET NULL,\n\tupdated_by UUID REFERENCES actor(id) ON DELETE SET NULL,\n\tCHECK (policy IS NULL OR (parent_id IS NULL AND policy @> '{}'::jsonb))\n)";
|
|
83
91
|
/**
|
|
84
92
|
* Cell indexes — all active-only, partial on `deleted_at IS NULL`.
|
|
85
93
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cell_ddl.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/cell_ddl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAElE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,oBAAoB,oIAI1B,CAAC;AAER
|
|
1
|
+
{"version":3,"file":"cell_ddl.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/cell_ddl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAElE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,oBAAoB,oIAI1B,CAAC;AAER;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,eAAO,MAAM,WAAW,6pBAkBtB,CAAC;AAEH;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,MAAM,CAkBtC,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,0iBAc5B,CAAC;AAEH;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC,MAAM,CAW5C,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,+RAO5B,CAAC;AAEH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC,MAAM,CAE5C,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,qSAO3B,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,MAAM,CAG3C,CAAC;AAEF,6EAA6E;AAC7E,eAAO,MAAM,gBAAgB,4DAA6D,CAAC;AAE3F,uBAAuB;AACvB,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,SAAS,CAuB5C,CAAC;AAEF,gDAAgD;AAChD,eAAO,MAAM,wBAAwB,aAAa,CAAC;AAEnD,wDAAwD;AACxD,eAAO,MAAM,iBAAiB,EAAE,kBAG/B,CAAC"}
|
package/dist/db/cell_ddl.js
CHANGED
|
@@ -81,6 +81,14 @@ END $$`;
|
|
|
81
81
|
* (nullable text — `pending` / `approved` / `rejected`; `NULL` = unmoderated)
|
|
82
82
|
* is the approval-lifecycle marker, peer to `visibility` (a control field with
|
|
83
83
|
* a non-author writer — see `auth/cell_actions.ts`), never inside `data`.
|
|
84
|
+
*
|
|
85
|
+
* `policy` (nullable JSONB) is a root's contribution policy — an object opaque
|
|
86
|
+
* to the spine, read by the consumer's create authorizer (`NULL` = its floor
|
|
87
|
+
* governs). The table CHECK holds it to roots (`parent_id IS NULL`) and to JSON
|
|
88
|
+
* objects (`policy @> '{}'` holds for an object and for nothing else — the
|
|
89
|
+
* object test every driver runs, where `jsonb_typeof` is not on pglet). A
|
|
90
|
+
* manage-tier control field: written at a root's create and by
|
|
91
|
+
* `cell_policy_set`, never through `data`, which an editor grant can write.
|
|
84
92
|
*/
|
|
85
93
|
export const CELL_SCHEMA = `
|
|
86
94
|
CREATE TABLE IF NOT EXISTS cell (
|
|
@@ -93,11 +101,13 @@ CREATE TABLE IF NOT EXISTS cell (
|
|
|
93
101
|
parent_id UUID REFERENCES cell(id) ON DELETE SET NULL,
|
|
94
102
|
root_id UUID REFERENCES cell(id) ON DELETE SET NULL,
|
|
95
103
|
moderation TEXT,
|
|
104
|
+
policy JSONB,
|
|
96
105
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
97
106
|
updated_at TIMESTAMPTZ,
|
|
98
107
|
deleted_at TIMESTAMPTZ,
|
|
99
108
|
created_by UUID REFERENCES actor(id) ON DELETE SET NULL,
|
|
100
|
-
updated_by UUID REFERENCES actor(id) ON DELETE SET NULL
|
|
109
|
+
updated_by UUID REFERENCES actor(id) ON DELETE SET NULL,
|
|
110
|
+
CHECK (policy IS NULL OR (parent_id IS NULL AND policy @> '{}'::jsonb))
|
|
101
111
|
)`;
|
|
102
112
|
/**
|
|
103
113
|
* Cell indexes — all active-only, partial on `deleted_at IS NULL`.
|
|
@@ -25,7 +25,7 @@ import type { Json } from '@fuzdev/fuz_util/json.ts';
|
|
|
25
25
|
import type { Uuid } from '../primitive_schemas.js';
|
|
26
26
|
import { type FactHash } from '@fuzdev/fuz_util/hash_schemas.ts';
|
|
27
27
|
import type { CellData } from '../auth/cell_data_schema.js';
|
|
28
|
-
import type { CellVisibility } from '../auth/cell_action_specs.js';
|
|
28
|
+
import type { CellPolicy, CellVisibility } from '../auth/cell_action_specs.js';
|
|
29
29
|
/**
|
|
30
30
|
* Row shape returned by `cell` SELECTs. `data` is typed as `CellData` —
|
|
31
31
|
* the storage layer trusts the wire validation; the row is what was
|
|
@@ -49,6 +49,7 @@ export interface CellRow {
|
|
|
49
49
|
parent_id: Uuid | null;
|
|
50
50
|
root_id: Uuid | null;
|
|
51
51
|
moderation: string | null;
|
|
52
|
+
policy: CellPolicy | null;
|
|
52
53
|
created_at: string;
|
|
53
54
|
updated_at: string | null;
|
|
54
55
|
deleted_at: string | null;
|
|
@@ -66,7 +67,7 @@ export interface CellRow {
|
|
|
66
67
|
* `grant_count` is not a column — `cell_row_projection` appends it. Keep
|
|
67
68
|
* in sync with `CellRow` and the `cell` DDL in `db/cell_ddl.ts`.
|
|
68
69
|
*/
|
|
69
|
-
export declare const CELL_COLUMNS: readonly ["id", "data", "kind", "visibility", "path", "refs", "parent_id", "root_id", "moderation", "created_at", "updated_at", "deleted_at", "created_by", "updated_by"];
|
|
70
|
+
export declare const CELL_COLUMNS: readonly ["id", "data", "kind", "visibility", "path", "refs", "parent_id", "root_id", "moderation", "policy", "created_at", "updated_at", "deleted_at", "created_by", "updated_by"];
|
|
70
71
|
/**
|
|
71
72
|
* The full `CellRow` projection — `CELL_COLUMNS` qualified by `alias` plus
|
|
72
73
|
* the derived `grant_count` (a correlated subquery against `cell_grant`,
|
|
@@ -101,7 +102,9 @@ export type CellViewRow = Pick<CellRow, (typeof CELL_VIEW_COLUMNS)[number]>;
|
|
|
101
102
|
* `parent_id` / `root_id` / `moderation` are the directory-tree + lifecycle
|
|
102
103
|
* columns the handler derives (parent containment, governing root, the
|
|
103
104
|
* create-authorizer verdict) — likewise write-once / control-gated, never on
|
|
104
|
-
* `CellUpdatePatch`.
|
|
105
|
+
* `CellUpdatePatch`. `policy` is a root's contribution policy (the table CHECK
|
|
106
|
+
* refuses one beside a `parent_id`), changed later only by
|
|
107
|
+
* `query_cell_set_policy`.
|
|
105
108
|
*/
|
|
106
109
|
export interface CellCreateQueryInput {
|
|
107
110
|
data: Json;
|
|
@@ -111,6 +114,7 @@ export interface CellCreateQueryInput {
|
|
|
111
114
|
parent_id?: Uuid | null;
|
|
112
115
|
root_id?: Uuid | null;
|
|
113
116
|
moderation?: string | null;
|
|
117
|
+
policy?: CellPolicy | null;
|
|
114
118
|
created_by?: Uuid | null;
|
|
115
119
|
}
|
|
116
120
|
/**
|
|
@@ -219,9 +223,15 @@ export declare const query_cell_delete: (deps: QueryDeps, id: Uuid, options?: {
|
|
|
219
223
|
}) => Promise<boolean>;
|
|
220
224
|
/**
|
|
221
225
|
* Transition a contribution's `moderation` marker (the `cell_moderate` verb's
|
|
222
|
-
* write).
|
|
223
|
-
*
|
|
224
|
-
*
|
|
226
|
+
* write). The `WHERE` carries the allowed from-states: an approval moves only a
|
|
227
|
+
* `'pending'` row, and a rejection a `'pending'` or `'approved'` one (the
|
|
228
|
+
* moderator's takedown). A rejected or unmoderated cell never moves, and nothing
|
|
229
|
+
* already approved is approved again — without the from-state an approval would
|
|
230
|
+
* force any cell its caller can reach public. On `set_visibility_public` (an
|
|
231
|
+
* approval) it also flips `visibility` to `'public'` so the approved
|
|
232
|
+
* contribution goes live; a rejection leaves visibility untouched — a pending
|
|
233
|
+
* post stays as its author set it, and a taken-down one keeps its visibility,
|
|
234
|
+
* hidden by the marker alone. `updated_at` / `updated_by` are stamped.
|
|
225
235
|
*
|
|
226
236
|
* `moderation` is deliberately **not** writable through `query_cell_update`
|
|
227
237
|
* (it's absent from `CellUpdatePatch`) — gating the transition on a dedicated
|
|
@@ -232,13 +242,49 @@ export declare const query_cell_delete: (deps: QueryDeps, id: Uuid, options?: {
|
|
|
232
242
|
* @param id - cell id
|
|
233
243
|
* @param moderation - the terminal marker to write (`'approved'` | `'rejected'`)
|
|
234
244
|
* @param options - `set_visibility_public` flips visibility on approval; `updated_by` records the moderator
|
|
235
|
-
* @returns the updated row, or `null` when no active row matched (raced delete)
|
|
245
|
+
* @returns the updated row, or `null` when no active row in an allowed from-state matched (a raced delete or decision, or a refused transition)
|
|
236
246
|
* @mutates `cell` - sets `moderation` (+ maybe `visibility`) on one row
|
|
237
247
|
*/
|
|
238
248
|
export declare const query_cell_set_moderation: (deps: QueryDeps, id: Uuid, moderation: string, options: {
|
|
239
249
|
set_visibility_public: boolean;
|
|
240
250
|
updated_by?: Uuid | null;
|
|
241
251
|
}) => Promise<CellRow | null>;
|
|
252
|
+
/** What `query_cell_set_policy` answers: the updated root and the policy it replaced. */
|
|
253
|
+
export interface CellSetPolicyResult {
|
|
254
|
+
cell: CellRow;
|
|
255
|
+
/** The root's `policy` as the `UPDATE` found it, under the row lock it writes with. */
|
|
256
|
+
previous_policy: CellPolicy | null;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Replace a root's contribution `policy` (the `cell_policy_set` verb's write);
|
|
260
|
+
* `null` clears it.
|
|
261
|
+
*
|
|
262
|
+
* The `WHERE` carries `parent_id IS NULL`, so only a root ever takes a policy
|
|
263
|
+
* — the table CHECK's rule restated where the write is. `updated_at` /
|
|
264
|
+
* `updated_by` are stamped.
|
|
265
|
+
*
|
|
266
|
+
* The replaced policy is read by the statement that writes, from a
|
|
267
|
+
* `FOR UPDATE` subquery: a concurrent set waits on that row lock and then
|
|
268
|
+
* reads the committed value, so the audit's `previous_policy` is the value
|
|
269
|
+
* this write replaced, never one a racing set already overwrote.
|
|
270
|
+
*
|
|
271
|
+
* `policy` is deliberately **not** writable through `query_cell_update` (it's
|
|
272
|
+
* absent from `CellUpdatePatch`): `data` writes gate on edit authority, which
|
|
273
|
+
* an editor grant carries, and the policy decides who may contribute under the
|
|
274
|
+
* root, so it changes only through this query behind the handler's
|
|
275
|
+
* manage-tier check.
|
|
276
|
+
*
|
|
277
|
+
* @param deps - query deps
|
|
278
|
+
* @param id - the root's id
|
|
279
|
+
* @param policy - the new policy, or `null` to clear it
|
|
280
|
+
* @param options - `updated_by` records the manager
|
|
281
|
+
* @returns the updated row and the policy it replaced, or `null` when no active root
|
|
282
|
+
* matched (a raced delete)
|
|
283
|
+
* @mutates `cell` - sets `policy` on one root
|
|
284
|
+
*/
|
|
285
|
+
export declare const query_cell_set_policy: (deps: QueryDeps, id: Uuid, policy: CellPolicy | null, options: {
|
|
286
|
+
updated_by?: Uuid | null;
|
|
287
|
+
}) => Promise<CellSetPolicyResult | null>;
|
|
242
288
|
/**
|
|
243
289
|
* List active cells with the given `kind`, newest first. Uses the
|
|
244
290
|
* `idx_cell_kind` index (`cell.kind = ?`).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cell_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/cell_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AACrD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAgB,KAAK,QAAQ,EAAE,MAAM,kCAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"cell_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/cell_queries.ts"],"names":[],"mappings":"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"}
|
package/dist/db/cell_queries.js
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
import { is_fact_hash } from '@fuzdev/fuz_util/hash_schemas.ts';
|
|
24
24
|
import { assert_row } from './assert_row.js';
|
|
25
25
|
import { columns_sql, iso8601_timestamp_expr, qualify_columns } from './sql_columns.js';
|
|
26
|
-
import { cell_moderation_admits_audience_sql, cell_moderation_is_stewardable_sql } from './cell_moderation.js';
|
|
26
|
+
import { CELL_MODERATION_APPROVED, CELL_MODERATION_PENDING, CELL_MODERATION_REJECTED, cell_moderation_admits_audience_sql, cell_moderation_is_stewardable_sql } from './cell_moderation.js';
|
|
27
27
|
import { can_view_cell_sql, caller_role_grants_cte, cell_grant_admits_caller_sql } from './cell_view_sql.js';
|
|
28
28
|
/**
|
|
29
29
|
* The full `cell` column set, named explicitly so a row read fails loud on
|
|
@@ -45,6 +45,7 @@ export const CELL_COLUMNS = [
|
|
|
45
45
|
'parent_id',
|
|
46
46
|
'root_id',
|
|
47
47
|
'moderation',
|
|
48
|
+
'policy',
|
|
48
49
|
'created_at',
|
|
49
50
|
'updated_at',
|
|
50
51
|
'deleted_at',
|
|
@@ -100,8 +101,8 @@ export const CELL_VIEW_COLUMNS = [
|
|
|
100
101
|
export const query_cell_create = async (deps, input) => {
|
|
101
102
|
const refs = derive_cell_refs(input.data);
|
|
102
103
|
const row = await deps.db.query_one(`INSERT INTO cell
|
|
103
|
-
(data, kind, visibility, path, refs, parent_id, root_id, moderation, created_by)
|
|
104
|
-
VALUES ($1::jsonb, $2, COALESCE($3::cell_visibility, 'private'::cell_visibility), $4, $5::text[], $6, $7, $8, $9)
|
|
104
|
+
(data, kind, visibility, path, refs, parent_id, root_id, moderation, policy, created_by)
|
|
105
|
+
VALUES ($1::jsonb, $2, COALESCE($3::cell_visibility, 'private'::cell_visibility), $4, $5::text[], $6, $7, $8, $9::jsonb, $10)
|
|
105
106
|
RETURNING ${cell_row_projection('cell')}`, [
|
|
106
107
|
JSON.stringify(input.data),
|
|
107
108
|
input.kind ?? null,
|
|
@@ -111,6 +112,7 @@ export const query_cell_create = async (deps, input) => {
|
|
|
111
112
|
input.parent_id ?? null,
|
|
112
113
|
input.root_id ?? null,
|
|
113
114
|
input.moderation ?? null,
|
|
115
|
+
input.policy == null ? null : JSON.stringify(input.policy),
|
|
114
116
|
input.created_by ?? null
|
|
115
117
|
]);
|
|
116
118
|
return assert_row(row, 'INSERT INTO cell');
|
|
@@ -242,9 +244,15 @@ export const query_cell_delete = async (deps, id, options) => {
|
|
|
242
244
|
};
|
|
243
245
|
/**
|
|
244
246
|
* Transition a contribution's `moderation` marker (the `cell_moderate` verb's
|
|
245
|
-
* write).
|
|
246
|
-
*
|
|
247
|
-
*
|
|
247
|
+
* write). The `WHERE` carries the allowed from-states: an approval moves only a
|
|
248
|
+
* `'pending'` row, and a rejection a `'pending'` or `'approved'` one (the
|
|
249
|
+
* moderator's takedown). A rejected or unmoderated cell never moves, and nothing
|
|
250
|
+
* already approved is approved again — without the from-state an approval would
|
|
251
|
+
* force any cell its caller can reach public. On `set_visibility_public` (an
|
|
252
|
+
* approval) it also flips `visibility` to `'public'` so the approved
|
|
253
|
+
* contribution goes live; a rejection leaves visibility untouched — a pending
|
|
254
|
+
* post stays as its author set it, and a taken-down one keeps its visibility,
|
|
255
|
+
* hidden by the marker alone. `updated_at` / `updated_by` are stamped.
|
|
248
256
|
*
|
|
249
257
|
* `moderation` is deliberately **not** writable through `query_cell_update`
|
|
250
258
|
* (it's absent from `CellUpdatePatch`) — gating the transition on a dedicated
|
|
@@ -255,7 +263,7 @@ export const query_cell_delete = async (deps, id, options) => {
|
|
|
255
263
|
* @param id - cell id
|
|
256
264
|
* @param moderation - the terminal marker to write (`'approved'` | `'rejected'`)
|
|
257
265
|
* @param options - `set_visibility_public` flips visibility on approval; `updated_by` records the moderator
|
|
258
|
-
* @returns the updated row, or `null` when no active row matched (raced delete)
|
|
266
|
+
* @returns the updated row, or `null` when no active row in an allowed from-state matched (a raced delete or decision, or a refused transition)
|
|
259
267
|
* @mutates `cell` - sets `moderation` (+ maybe `visibility`) on one row
|
|
260
268
|
*/
|
|
261
269
|
export const query_cell_set_moderation = async (deps, id, moderation, options) => {
|
|
@@ -265,9 +273,55 @@ export const query_cell_set_moderation = async (deps, id, moderation, options) =
|
|
|
265
273
|
updated_by = $4,
|
|
266
274
|
updated_at = NOW()
|
|
267
275
|
WHERE id = $1 AND deleted_at IS NULL
|
|
276
|
+
AND (moderation = '${CELL_MODERATION_PENDING}'
|
|
277
|
+
OR ($2::text = '${CELL_MODERATION_REJECTED}' AND moderation = '${CELL_MODERATION_APPROVED}'))
|
|
268
278
|
RETURNING ${cell_row_projection('cell')}`, [id, moderation, options.set_visibility_public, options.updated_by ?? null]);
|
|
269
279
|
return row ?? null;
|
|
270
280
|
};
|
|
281
|
+
/**
|
|
282
|
+
* Replace a root's contribution `policy` (the `cell_policy_set` verb's write);
|
|
283
|
+
* `null` clears it.
|
|
284
|
+
*
|
|
285
|
+
* The `WHERE` carries `parent_id IS NULL`, so only a root ever takes a policy
|
|
286
|
+
* — the table CHECK's rule restated where the write is. `updated_at` /
|
|
287
|
+
* `updated_by` are stamped.
|
|
288
|
+
*
|
|
289
|
+
* The replaced policy is read by the statement that writes, from a
|
|
290
|
+
* `FOR UPDATE` subquery: a concurrent set waits on that row lock and then
|
|
291
|
+
* reads the committed value, so the audit's `previous_policy` is the value
|
|
292
|
+
* this write replaced, never one a racing set already overwrote.
|
|
293
|
+
*
|
|
294
|
+
* `policy` is deliberately **not** writable through `query_cell_update` (it's
|
|
295
|
+
* absent from `CellUpdatePatch`): `data` writes gate on edit authority, which
|
|
296
|
+
* an editor grant carries, and the policy decides who may contribute under the
|
|
297
|
+
* root, so it changes only through this query behind the handler's
|
|
298
|
+
* manage-tier check.
|
|
299
|
+
*
|
|
300
|
+
* @param deps - query deps
|
|
301
|
+
* @param id - the root's id
|
|
302
|
+
* @param policy - the new policy, or `null` to clear it
|
|
303
|
+
* @param options - `updated_by` records the manager
|
|
304
|
+
* @returns the updated row and the policy it replaced, or `null` when no active root
|
|
305
|
+
* matched (a raced delete)
|
|
306
|
+
* @mutates `cell` - sets `policy` on one root
|
|
307
|
+
*/
|
|
308
|
+
export const query_cell_set_policy = async (deps, id, policy, options) => {
|
|
309
|
+
const row = await deps.db.query_one(`UPDATE cell SET
|
|
310
|
+
policy = $2::jsonb,
|
|
311
|
+
updated_by = $3,
|
|
312
|
+
updated_at = NOW()
|
|
313
|
+
FROM (
|
|
314
|
+
SELECT p.id, p.policy AS previous_policy FROM cell p
|
|
315
|
+
WHERE p.id = $1 AND p.deleted_at IS NULL AND p.parent_id IS NULL
|
|
316
|
+
FOR UPDATE
|
|
317
|
+
) prev
|
|
318
|
+
WHERE cell.id = prev.id
|
|
319
|
+
RETURNING ${cell_row_projection('cell')}, prev.previous_policy`, [id, policy === null ? null : JSON.stringify(policy), options.updated_by ?? null]);
|
|
320
|
+
if (!row)
|
|
321
|
+
return null;
|
|
322
|
+
const { previous_policy, ...cell } = row;
|
|
323
|
+
return { cell, previous_policy };
|
|
324
|
+
};
|
|
271
325
|
/**
|
|
272
326
|
* List active cells with the given `kind`, newest first. Uses the
|
|
273
327
|
* `idx_cell_kind` index (`cell.kind = ?`).
|
package/dist/http/CLAUDE.md
CHANGED
|
@@ -396,7 +396,8 @@ per-action schemas so mismatches surface as `invalid_params` rather than
|
|
|
396
396
|
### 15-code error taxonomy
|
|
397
397
|
|
|
398
398
|
Five standard codes + ten general application codes (consumers add their
|
|
399
|
-
own by casting `as JsonrpcErrorCode
|
|
399
|
+
own by casting `as JsonrpcErrorCode`, and declare each one's HTTP status with
|
|
400
|
+
`register_jsonrpc_error_code` — see below):
|
|
400
401
|
|
|
401
402
|
- `parse_error` (-32700, HTTP 400) — JSON parse failure.
|
|
402
403
|
- `invalid_request` (-32600, HTTP 400) — envelope malformed.
|
|
@@ -423,13 +424,24 @@ business rules.
|
|
|
423
424
|
limiting is the default interpretation when translating generic HTTP back
|
|
424
425
|
to a JSON-RPC code.
|
|
425
426
|
|
|
427
|
+
**Consumer codes** — a consumer code answers 500 until
|
|
428
|
+
`register_jsonrpc_error_code(code, http_status)` declares its status, called
|
|
429
|
+
once at module load beside the code (list that module in `sideEffects`). It
|
|
430
|
+
throws when the code is outside -32099..-32000, is any code
|
|
431
|
+
`JSONRPC_ERROR_CODES` defines, or is already registered with another status,
|
|
432
|
+
or when the status is outside 400–599; the same code and status again is a
|
|
433
|
+
no-op. A registered code joins neither the reverse map nor
|
|
434
|
+
`JSONRPC_ERROR_CODE_TO_NAME`. Twin of the Rust spine's `ConsumerErrorCode::new`,
|
|
435
|
+
which holds a declaration to the same rules.
|
|
436
|
+
|
|
426
437
|
### API map
|
|
427
438
|
|
|
428
439
|
- `JSONRPC_ERROR_CODES` — `Record<JsonrpcErrorName, JsonrpcErrorCode>` (frozen)
|
|
429
440
|
- `jsonrpc_error_messages` — named constructors returning `JsonrpcErrorObject`
|
|
430
441
|
- `jsonrpc_errors` — named constructors returning `ThrownJsonrpcError` (derived from `jsonrpc_error_messages` via `create_error_thrower`). Usage: `throw jsonrpc_errors.not_found('user')`, `throw jsonrpc_errors.forbidden()`
|
|
431
442
|
- `ThrownJsonrpcError` — `Error` subclass carrying `code` + optional `data`
|
|
432
|
-
- `
|
|
443
|
+
- `jsonrpc_error_code_to_http_status` / `http_status_to_jsonrpc_error_code` (fall back to 500 / `internal_error`) — the code → status table is module-private; `HTTP_STATUS_TO_JSONRPC_ERROR_CODE` (frozen) is the reverse over the base set
|
|
444
|
+
- `register_jsonrpc_error_code(code, http_status)` — declares a consumer code's status (see **Consumer codes** above)
|
|
433
445
|
- Envelope schemas in `http/jsonrpc.ts`: `JsonrpcRequest`, `JsonrpcNotification`, `JsonrpcResponse`, `JsonrpcErrorResponse`, `JsonrpcResponseOrError`, `JsonrpcMessage`, `JsonrpcMessageFromClientToServer`, `JsonrpcMessageFromServerToClient`. Also `JsonrpcRequestId`, `JsonrpcMethod`, `JsonrpcProgressToken`, `JsonrpcMcpMeta`, `JsonrpcRequestParamsMeta`
|
|
434
446
|
- Builders in `http/jsonrpc_helpers.ts`: `create_jsonrpc_request`, `create_jsonrpc_notification`, `create_jsonrpc_response`, `create_jsonrpc_error_response`, `create_jsonrpc_error_response_from_thrown` (preserves code/message/data on `ThrownJsonrpcError`; plain `Error` → `internal_error` with `{stack}` in DEV)
|
|
435
447
|
- Type guards: `is_jsonrpc_request_id` (rejects NaN/Infinity), `is_jsonrpc_object`, `is_jsonrpc_message`, `is_jsonrpc_request` / `_notification` / `_response` / `_error_response`
|
|
@@ -67,8 +67,10 @@ import type { MiddlewareHandler } from 'hono';
|
|
|
67
67
|
/**
|
|
68
68
|
* Create the body size limit middleware.
|
|
69
69
|
*
|
|
70
|
-
* @param max_size - the largest accepted body, in bytes
|
|
70
|
+
* @param max_size - the largest accepted body, in bytes; a safe integer, `0` or more
|
|
71
71
|
* @returns middleware answering `413` `{error: 'payload_too_large'}` past the cap and `400` `{error: 'invalid_content_length'}` to a `Content-Length` that is not a run of digits
|
|
72
|
+
* @throws Error when `max_size` is not a safe integer `0` or more — a `NaN` cap
|
|
73
|
+
* compares false against every length and would admit any body
|
|
72
74
|
*/
|
|
73
75
|
export declare const create_body_limit_middleware: (max_size: number) => MiddlewareHandler;
|
|
74
76
|
//# sourceMappingURL=body_limit.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"body_limit.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/body_limit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAQ9C
|
|
1
|
+
{"version":3,"file":"body_limit.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/body_limit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAQ9C;;;;;;;GAOG;AACH,eAAO,MAAM,4BAA4B,GAAI,UAAU,MAAM,KAAG,iBA6B/D,CAAC"}
|