@fuzdev/fuz_app 0.58.0 → 0.60.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 (107) hide show
  1. package/dist/actions/CLAUDE.md +13 -8
  2. package/dist/actions/action_codegen.d.ts +1 -1
  3. package/dist/actions/action_codegen.js +2 -2
  4. package/dist/actions/action_event_helpers.d.ts +3 -3
  5. package/dist/actions/action_event_helpers.js +8 -8
  6. package/dist/actions/action_event_types.d.ts +3 -3
  7. package/dist/actions/action_event_types.js +3 -3
  8. package/dist/actions/transports_ws_auth_guard.d.ts +2 -2
  9. package/dist/actions/transports_ws_auth_guard.js +3 -3
  10. package/dist/auth/CLAUDE.md +215 -45
  11. package/dist/auth/account_action_specs.d.ts +9 -0
  12. package/dist/auth/account_action_specs.d.ts.map +1 -1
  13. package/dist/auth/account_action_specs.js +9 -0
  14. package/dist/auth/actor_lookup_action_specs.d.ts +127 -0
  15. package/dist/auth/actor_lookup_action_specs.d.ts.map +1 -0
  16. package/dist/auth/actor_lookup_action_specs.js +93 -0
  17. package/dist/auth/actor_lookup_actions.d.ts +19 -0
  18. package/dist/auth/actor_lookup_actions.d.ts.map +1 -0
  19. package/dist/auth/actor_lookup_actions.js +32 -0
  20. package/dist/auth/actor_lookup_queries.d.ts +44 -0
  21. package/dist/auth/actor_lookup_queries.d.ts.map +1 -0
  22. package/dist/auth/actor_lookup_queries.js +42 -0
  23. package/dist/auth/actor_search_action_specs.d.ts +166 -0
  24. package/dist/auth/actor_search_action_specs.d.ts.map +1 -0
  25. package/dist/auth/actor_search_action_specs.js +139 -0
  26. package/dist/auth/actor_search_actions.d.ts +31 -0
  27. package/dist/auth/actor_search_actions.d.ts.map +1 -0
  28. package/dist/auth/actor_search_actions.js +61 -0
  29. package/dist/auth/actor_search_queries.d.ts +75 -0
  30. package/dist/auth/actor_search_queries.d.ts.map +1 -0
  31. package/dist/auth/actor_search_queries.js +91 -0
  32. package/dist/auth/admin_action_specs.d.ts +35 -0
  33. package/dist/auth/admin_action_specs.d.ts.map +1 -1
  34. package/dist/auth/admin_action_specs.js +35 -0
  35. package/dist/auth/admin_actions.js +2 -2
  36. package/dist/auth/all_action_spec_registries.d.ts +55 -0
  37. package/dist/auth/all_action_spec_registries.d.ts.map +1 -0
  38. package/dist/auth/all_action_spec_registries.js +59 -0
  39. package/dist/auth/audit_emitter.d.ts +1 -1
  40. package/dist/auth/audit_emitter.js +2 -2
  41. package/dist/auth/audit_log_queries.d.ts +1 -1
  42. package/dist/auth/audit_log_queries.js +3 -3
  43. package/dist/auth/audit_log_routes.d.ts +1 -1
  44. package/dist/auth/audit_log_routes.js +1 -1
  45. package/dist/auth/audit_log_schema.d.ts +5 -5
  46. package/dist/auth/audit_log_schema.js +7 -7
  47. package/dist/auth/auth_ddl.d.ts +7 -0
  48. package/dist/auth/auth_ddl.d.ts.map +1 -1
  49. package/dist/auth/auth_ddl.js +8 -0
  50. package/dist/auth/credential_type_schema.d.ts +1 -1
  51. package/dist/auth/credential_type_schema.js +3 -3
  52. package/dist/auth/grant_path_schema.d.ts +1 -1
  53. package/dist/auth/grant_path_schema.js +3 -3
  54. package/dist/auth/migrations.d.ts +4 -4
  55. package/dist/auth/migrations.d.ts.map +1 -1
  56. package/dist/auth/migrations.js +7 -6
  57. package/dist/auth/role_grant_offer_action_specs.d.ts +17 -0
  58. package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
  59. package/dist/auth/role_grant_offer_action_specs.js +17 -0
  60. package/dist/auth/role_grant_offer_actions.js +2 -2
  61. package/dist/auth/role_grant_offer_notifications.d.ts +2 -2
  62. package/dist/auth/role_grant_offer_notifications.js +2 -2
  63. package/dist/auth/role_grant_queries.d.ts +21 -0
  64. package/dist/auth/role_grant_queries.d.ts.map +1 -1
  65. package/dist/auth/role_grant_queries.js +31 -0
  66. package/dist/auth/role_schema.d.ts +2 -2
  67. package/dist/auth/role_schema.js +3 -3
  68. package/dist/auth/self_service_role_action_specs.d.ts +8 -0
  69. package/dist/auth/self_service_role_action_specs.d.ts.map +1 -1
  70. package/dist/auth/self_service_role_action_specs.js +8 -0
  71. package/dist/auth/self_service_role_actions.d.ts +1 -1
  72. package/dist/auth/self_service_role_actions.js +2 -2
  73. package/dist/auth/session_cookie.d.ts +1 -1
  74. package/dist/auth/session_cookie.js +1 -1
  75. package/dist/auth/session_middleware.d.ts +1 -1
  76. package/dist/auth/session_middleware.js +5 -5
  77. package/dist/rate_limiter.d.ts +5 -5
  78. package/dist/rate_limiter.js +6 -6
  79. package/dist/realtime/sse_auth_guard.d.ts +3 -3
  80. package/dist/realtime/sse_auth_guard.js +4 -4
  81. package/dist/server/app_backend.d.ts +3 -3
  82. package/dist/server/app_backend.js +4 -4
  83. package/dist/server/app_server.d.ts +1 -1
  84. package/dist/server/app_server.js +10 -10
  85. package/dist/testing/CLAUDE.md +22 -12
  86. package/dist/testing/admin_integration.js +4 -4
  87. package/dist/testing/app_server.d.ts +1 -1
  88. package/dist/testing/app_server.js +2 -2
  89. package/dist/testing/attack_surface.d.ts +4 -4
  90. package/dist/testing/attack_surface.js +6 -6
  91. package/dist/testing/audit_completeness.js +4 -4
  92. package/dist/testing/data_exposure.d.ts +2 -2
  93. package/dist/testing/data_exposure.js +7 -7
  94. package/dist/testing/db.d.ts +8 -8
  95. package/dist/testing/db.js +11 -11
  96. package/dist/testing/integration.js +4 -4
  97. package/dist/testing/integration_helpers.d.ts +6 -6
  98. package/dist/testing/integration_helpers.js +7 -7
  99. package/dist/testing/rate_limiting.js +4 -4
  100. package/dist/testing/round_trip.js +2 -2
  101. package/dist/testing/rpc_round_trip.js +2 -2
  102. package/dist/testing/schema_generators.d.ts.map +1 -1
  103. package/dist/testing/schema_generators.js +23 -2
  104. package/dist/testing/sse_round_trip.js +2 -2
  105. package/dist/testing/surface_invariants.d.ts +4 -4
  106. package/dist/testing/surface_invariants.js +5 -5
  107. package/package.json +1 -1
@@ -21,7 +21,7 @@ sections.
21
21
  | Module | Exports |
22
22
  | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
23
23
  | `keyring.ts` | `Keyring`, `create_keyring`, `validate_keyring`, `create_validated_keyring`, `ValidatedKeyringResult` |
24
- | `session_cookie.ts` | `SessionOptions<T>`, `SessionCookieOptions`, `SESSION_COOKIE_OPTIONS`, `SESSION_AGE_MAX`, `SESSION_REFRESH_THRESHOLD_S`, `ParsedSession`, `ProcessSessionResult`, `parse_session`, `create_session_cookie_value`, `process_session_cookie`, `create_session_config`, `fuz_session_config` |
24
+ | `session_cookie.ts` | `SessionOptions<T>`, `SessionCookieOptions`, `session_cookie_options`, `SESSION_AGE_MAX`, `SESSION_REFRESH_THRESHOLD_S`, `ParsedSession`, `ProcessSessionResult`, `parse_session`, `create_session_cookie_value`, `process_session_cookie`, `create_session_config`, `fuz_session_config` |
25
25
  | `password.ts` | `Password`, `PasswordProvided`, `PasswordHashDeps`, `PASSWORD_LENGTH_MIN` (12, OWASP), `PASSWORD_LENGTH_MAX` (300) |
26
26
  | `password_argon2.ts` | `hash_password`, `verify_password`, `verify_dummy`, `argon2_password_deps` |
27
27
  | `api_token.ts` | `API_TOKEN_PREFIX` (`secret_fuz_token_`), `hash_api_token`, `generate_api_token` |
@@ -179,7 +179,7 @@ those three. Mirrors the open-registry pattern used for `RoleName` /
179
179
  constant is named `_API_TOKEN` (not `_BEARER`) so wire literal and
180
180
  the `api_token` storage table stay in lockstep.
181
181
  - `BUILTIN_CREDENTIAL_TYPES` const tuple, `BuiltinCredentialType` Zod
182
- enum, `BUILTIN_CREDENTIAL_TYPE_META` admin-UI-facing descriptions.
182
+ enum, `builtin_credential_type_meta` admin-UI-facing descriptions.
183
183
  - `create_credential_type_schema(consumer_types?)`
184
184
  → `{CredentialType, credential_types: ReadonlyMap}`. Builtins always
185
185
  present; consumer collisions / regex failures / duplicates throw at
@@ -197,7 +197,7 @@ granted. Four builtins (`admin`, `self_service`, `system`, `bootstrap`).
197
197
  - `GRANT_PATH_ADMIN` / `_SELF_SERVICE` / `_SYSTEM` / `_BOOTSTRAP` —
198
198
  builtin literal constants.
199
199
  - `BUILTIN_GRANT_PATHS` const tuple, `BuiltinGrantPath` Zod enum,
200
- `BUILTIN_GRANT_PATH_META` descriptions.
200
+ `builtin_grant_path_meta` descriptions.
201
201
  - `create_grant_path_schema(consumer_paths?)`
202
202
  → `{GrantPath, grant_paths: ReadonlyMap}`. Same construction-time
203
203
  guards as the credential-type schema. Pass the result into
@@ -224,7 +224,7 @@ against the corresponding open registries at construction time.
224
224
  - `ROLE_KEEPER = 'keeper'` — bootstrap-only via daemon token; `grant_paths: ['bootstrap']`,
225
225
  `required_credential_types: ['daemon_token']`.
226
226
  - `ROLE_ADMIN = 'admin'` — admin-grantable; `grant_paths: ['admin']`.
227
- - `BUILTIN_ROLES`, `BuiltinRole` (Zod enum), `BUILTIN_ROLE_SPECS_BY_NAME`
227
+ - `BUILTIN_ROLES`, `BuiltinRole` (Zod enum), `builtin_role_specs_by_name`
228
228
  (`ReadonlyMap<string, RoleSpec>`) — not overridable by consumers.
229
229
  - `RoleSpec`: `{name, description?, required_credential_types?, applicable_scope_kinds?, grant_paths?}`
230
230
  — every cross-axis field is an open-registry string array. Empty
@@ -302,7 +302,7 @@ Zod enum; `AuditOutcome` is `'success' | 'failure'`.
302
302
 
303
303
  #### Metadata schemas
304
304
 
305
- - `AUDIT_METADATA_SCHEMAS` — per-type `z.looseObject`. Notable shapes:
305
+ - `audit_metadata_schemas` — per-type `z.looseObject`. Notable shapes:
306
306
  - `role_grant_create` — `scope_id`, optional `role_grant_id` (failed grants
307
307
  omit — admin-grant-path denial never produces a row), optional
308
308
  `source_offer_id`, optional `self_service` (set by
@@ -376,7 +376,7 @@ Zod enum; `AuditOutcome` is `'success' | 'failure'`.
376
376
  without validation). Pass the result to `create_app_backend({audit_log_config})`
377
377
  — it gets captured inside the bound `AppDeps.audit` emitter, and every
378
378
  call to `audit.emit` validates against it (defaults to
379
- `BUILTIN_AUDIT_LOG_CONFIG` when absent). `query_audit_log` still accepts
379
+ `builtin_audit_log_config` when absent). `query_audit_log` still accepts
380
380
  the trailing `config` positional arg for in-transaction emit sites that
381
381
  hold a transaction-scoped DB only. Builtin collisions and
382
382
  `AuditEventTypeName` format failures throw at construction. The DB
@@ -396,7 +396,7 @@ Zod enum; `AuditOutcome` is `'success' | 'failure'`.
396
396
  factory they track the same config, but a hand-rolled `AuditLogConfig`
397
397
  (or a cast escape) can fire both on a single emission. Sample via
398
398
  `get_*` getters; `reset_*` are test-only. `AUDIT_EVENT_TYPES`,
399
- `AUDIT_METADATA_SCHEMAS`, `BUILTIN_AUDIT_LOG_CONFIG`, and the configs
399
+ `audit_metadata_schemas`, `builtin_audit_log_config`, and the configs
400
400
  returned by `create_audit_log_config` are `Object.freeze`'d to convert
401
401
  accidental mutation (bugs, test cross-contamination, cast escapes)
402
402
  into loud TypeErrors — not a security boundary.
@@ -470,7 +470,7 @@ exports: `RoleGrantOfferReceivedParams`, `_RetractedParams`, `_AcceptedParams`,
470
470
  `_DeclinedParams`, `_SupersedeParams`, `RoleGrantRevokeParams`. Notification
471
471
  builders: `build_role_grant_offer_received_notification(params)` etc.
472
472
 
473
- `ROLE_GRANT_OFFER_NOTIFICATION_SPECS: Array<EventSpec>` — pass to
473
+ `role_grant_offer_notification_specs: Array<EventSpec>` — pass to
474
474
  `create_app_server`'s `event_specs` so the attack surface reflects them
475
475
  and DEV-mode `create_validated_broadcaster` catches payload drift.
476
476
 
@@ -521,6 +521,31 @@ account_id = ANY(...))` so `actor.id`s never round-trip back to the
521
521
  visibility). Returns `Array<AdminAccountEntryJson>`, sorted by
522
522
  `created_at`.
523
523
 
524
+ ### `actor_lookup_queries.ts`
525
+
526
+ - `query_actors_by_ids(deps, ids) → Array<ActorLookupRow>` — batched
527
+ `actor` ⨝ `account` INNER JOIN, returns
528
+ `{id, username, display_name}` per resolved actor. Empty input
529
+ fast-paths to `[]`; hard-deleted (or cascade-orphaned) rows silently
530
+ drop. Row shape omits `account_id` — the join is control-plane, not
531
+ wire-visible. Caller bounds `ids.length` (the action spec enforces
532
+ `ACTOR_LOOKUP_IDS_MAX`); SQL does not.
533
+
534
+ ### `actor_search_queries.ts`
535
+
536
+ - `query_actor_search(deps, {query, scope_ids?, limit}) → Array<ActorLookupRow>` —
537
+ case-insensitive LIKE-prefix on `actor.name`, backed by the
538
+ `idx_actor_name_lower` functional index in `auth_ddl.ts`. Returns the
539
+ same `{id, username, display_name}` row shape as `query_actors_by_ids`
540
+ so the labels arc stays uniform. LIKE wildcards (`%`, `_`, `\`) in
541
+ the user-supplied `query` are escaped before substitution so the
542
+ prefix-only contract is enforceable. When `scope_ids` is non-empty,
543
+ the result is filtered to actors holding an **active** role_grant
544
+ (`revoked_at IS NULL AND (expires_at IS NULL OR expires_at > NOW())`)
545
+ on one of the supplied scopes; `DISTINCT` collapses multi-grant
546
+ duplicates. When `scope_ids` is empty, no role_grant join — the handler
547
+ enforces admin for that path.
548
+
524
549
  ### `role_grant_queries.ts`
525
550
 
526
551
  - `query_create_role_grant` — idempotent; `ON CONFLICT` target and fallback
@@ -551,6 +576,12 @@ account_id = ANY(...))` so `actor.id`s never round-trip back to the
551
576
  that isn't the request actor (e.g., post-mutation verification, scripts,
552
577
  audit-time checks). For the request actor, prefer `has_scoped_role` /
553
578
  `has_any_scoped_role` on the in-memory `auth.role_grants` snapshot.
579
+ - `query_account_has_global_role(deps, account_id, role)` — account-grain
580
+ sibling: does any actor on `account_id` hold an active **global**
581
+ (`scope_id IS NULL`) role_grant for `role`? For surfaces with
582
+ `auth: actor: 'none'` that don't load `auth.role_grants` and can't use
583
+ `has_scoped_role`. EXISTS over the `idx_role_grant_actor`-backed
584
+ subquery, stops at the first match.
554
585
  - `query_role_grant_find_account_id_for_role(deps, role)` — joins
555
586
  role_grant → actor → account, returns first match. Used by daemon token
556
587
  middleware to resolve the keeper account.
@@ -701,7 +732,7 @@ run'` if the seed somehow missed (defensive — migrations always seed).
701
732
  ### `audit_log_queries.ts`
702
733
 
703
734
  - `query_audit_log<T>(deps, input, config?)` — `config` defaults to
704
- `BUILTIN_AUDIT_LOG_CONFIG`. Membership check runs against
735
+ `builtin_audit_log_config`. Membership check runs against
705
736
  `config.event_types`; metadata validation runs independently against
706
737
  `config.metadata_schemas[event_type]` when present. Mismatches and
707
738
  unknown types log + bump their counters (see schema section);
@@ -769,8 +800,8 @@ without rebuilding `AppDeps`.
769
800
 
770
801
  ### `migrations.ts`
771
802
 
772
- - `AUTH_MIGRATION_NAMESPACE = 'fuz_auth'`, `AUTH_MIGRATION_NS` (pre-composed), `RESERVED_MIGRATION_NAMESPACES: ReadonlyArray<string>` (membership list `create_app_backend` rejects on; consumer-discoverable instead of probing the runtime throw).
773
- - `AUTH_MIGRATIONS`:
803
+ - `AUTH_MIGRATION_NAMESPACE = 'fuz_auth'`, `auth_migration_ns` (pre-composed), `reserved_migration_namespaces: ReadonlyArray<string>` (membership list `create_app_backend` rejects on; consumer-discoverable instead of probing the runtime throw).
804
+ - `auth_migrations`:
774
805
  - **v0 `full_auth_schema`** — every table + index + seed for the v1
775
806
  identity system (account, actor, role_grant, auth_session, api_token,
776
807
  audit_log, bootstrap_lock, invite, app_settings). All
@@ -1232,26 +1263,30 @@ acting?: ActingActor` biconditional).
1232
1263
 
1233
1264
  | Spec | Side effects | Rate limit | Input | Output |
1234
1265
  | ------------------------------------------ | ------------ | ----------- | --------------------------------------------------------- | ----------------------------- |
1235
- | `admin_account_list_action_spec` | false | | `{limit?, offset?}` | `{accounts, grantable_roles}` |
1236
- | `admin_session_list_action_spec` | false | | `z.void()` | `{sessions}` |
1266
+ | `admin_account_list_action_spec` | false | `'account'` | `{limit?, offset?}` | `{accounts, grantable_roles}` |
1267
+ | `admin_session_list_action_spec` | false | `'account'` | `z.void()` | `{sessions}` |
1237
1268
  | `admin_session_revoke_all_action_spec` | true | `'account'` | `{account_id}` | `{ok, count}` |
1238
1269
  | `admin_token_revoke_all_action_spec` | true | `'account'` | `{account_id}` | `{ok, count}` |
1239
- | `audit_log_list_action_spec` | false | | `{event_type?, account_id?, limit?, offset?, since_seq?}` | `{events}` |
1240
- | `audit_log_role_grant_history_action_spec` | false | | `{limit?, offset?}` | `{events}` |
1270
+ | `audit_log_list_action_spec` | false | `'account'` | `{event_type?, account_id?, limit?, offset?, since_seq?}` | `{events}` |
1271
+ | `audit_log_role_grant_history_action_spec` | false | `'account'` | `{limit?, offset?}` | `{events}` |
1241
1272
  | `invite_create_action_spec` | true | `'account'` | `{email?, username?}` | `{ok, invite}` |
1242
- | `invite_list_action_spec` | false | | `z.void()` | `{invites}` |
1273
+ | `invite_list_action_spec` | false | `'account'` | `z.void()` | `{invites}` |
1243
1274
  | `invite_delete_action_spec` | true | `'account'` | `{invite_id}` | `{ok}` |
1244
1275
  | `app_settings_get_action_spec` | false | | `z.void()` | `{settings}` |
1245
1276
  | `app_settings_update_action_spec` | true | `'account'` | `{open_signup}` | `{ok, settings}` |
1246
1277
 
1247
- Mutating admin specs declare `rate_limit: 'account'` — keyed on the
1248
- admin's `request_context.actor.id`. The dispatcher's per-action hook
1249
- (shared by HTTP RPC + WS) records every invocation regardless of
1250
- outcome so successful probes (e.g. `invite_create`'s account-existence
1251
- oracle on the `LOWER()` lookup in `query_account_by_username/_by_email`)
1252
- consume budget. Default `DEFAULT_ACTION_ACCOUNT_RATE_LIMIT` is 1200/15min
1253
- per actor permissive enough for any human admin workflow, slow enough
1254
- that scripted oracles surface in audit. Tighten downstream via
1278
+ Every admin spec declares `rate_limit: 'account'` — keyed on the
1279
+ admin's `request_context.actor.id`. Mutations cap the
1280
+ `invite_create`-style account-existence oracle (`LOWER()` lookup in
1281
+ `query_account_by_username/_by_email`); reads cap admin-side scraping
1282
+ of paginated cross-account listings (`admin_account_list`,
1283
+ `audit_log_list`, `audit_log_role_grant_history`) and unbounded
1284
+ cross-account reads (`admin_session_list`, `invite_list`). The
1285
+ dispatcher's per-action hook (shared by HTTP RPC + WS) records every
1286
+ invocation regardless of outcome so successful probes consume budget.
1287
+ Default `default_action_account_rate_limit` is 1200/15min per actor —
1288
+ permissive enough for any human admin workflow, slow enough that
1289
+ scripted oracles surface in audit. Tighten downstream via
1255
1290
  `AppServerOptions.action_account_rate_limiter`.
1256
1291
 
1257
1292
  `AUDIT_LOG_LIST_LIMIT_MAX = 200` — page size clamp. `ADMIN_ACCOUNT_LIST_DEFAULT_LIMIT = 50` / `ADMIN_ACCOUNT_LIST_LIMIT_MAX = 200` — same shape on `admin_account_list`.
@@ -1282,7 +1317,7 @@ self-service `account_actions.ts` surface):
1282
1317
 
1283
1318
  Closure state:
1284
1319
 
1285
- - `grantable_roles` is derived once from `options.roles?.role_specs ?? BUILTIN_ROLE_SPECS_BY_NAME`
1320
+ - `grantable_roles` is derived once from `options.roles?.role_specs ?? builtin_role_specs_by_name`
1286
1321
  via `list_roles_with_grant_path(_, GRANT_PATH_ADMIN)` and closed over
1287
1322
  by the `admin_account_list` handler.
1288
1323
  - `options.app_settings` — when provided, captured by the
@@ -1344,15 +1379,25 @@ Every input row below also carries the shared `acting?: ActingActor`
1344
1379
  field that the dispatcher's authorization phase reads off the raw
1345
1380
  params (omitted from the table for brevity).
1346
1381
 
1347
- | Spec | Input | Output |
1348
- | -------------------------------------- | ---------------------------------------------------------- | ---------------------------------------------- |
1349
- | `role_grant_offer_create_action_spec` | `{to_account_id, to_actor_id?, role, scope_id?, message?}` | `{offer}` |
1350
- | `role_grant_offer_accept_action_spec` | `{offer_id}` | `{role_grant_id, offer, superseded_offer_ids}` |
1351
- | `role_grant_offer_decline_action_spec` | `{offer_id, reason?}` | `{ok}` |
1352
- | `role_grant_offer_retract_action_spec` | `{offer_id}` | `{ok}` |
1353
- | `role_grant_offer_list_action_spec` | `{account_id?}` | `{offers}` |
1354
- | `role_grant_offer_history_action_spec` | `{account_id?, limit?, offset?}` | `{offers}` |
1355
- | `role_grant_revoke_action_spec` | `{actor_id, role_grant_id, reason?}` | `{ok, revoked}` |
1382
+ | Spec | Rate limit | Input | Output |
1383
+ | -------------------------------------- | ----------- | ---------------------------------------------------------- | ---------------------------------------------- |
1384
+ | `role_grant_offer_create_action_spec` | `'account'` | `{to_account_id, to_actor_id?, role, scope_id?, message?}` | `{offer}` |
1385
+ | `role_grant_offer_accept_action_spec` | | `{offer_id}` | `{role_grant_id, offer, superseded_offer_ids}` |
1386
+ | `role_grant_offer_decline_action_spec` | | `{offer_id, reason?}` | `{ok}` |
1387
+ | `role_grant_offer_retract_action_spec` | | `{offer_id}` | `{ok}` |
1388
+ | `role_grant_offer_list_action_spec` | | `{account_id?}` | `{offers}` |
1389
+ | `role_grant_offer_history_action_spec` | | `{account_id?, limit?, offset?}` | `{offers}` |
1390
+ | `role_grant_revoke_action_spec` | `'account'` | `{actor_id, role_grant_id, reason?}` | `{ok, revoked}` |
1391
+
1392
+ `role_grant_offer_create` carries the same shape as `invite_create` —
1393
+ hostile authed callers can iterate `to_account_id` to spam offers and
1394
+ probe `ERROR_ACCOUNT_NOT_FOUND` /
1395
+ `ERROR_ROLE_GRANT_OFFER_ACTOR_ACCOUNT_MISMATCH` as account-existence
1396
+ oracles, so the rate cap fires on the same threat model the admin
1397
+ `invite_create` spec addresses upstream. `role_grant_revoke` keeps its
1398
+ cap because it's an admin mutation. The accept / decline / retract /
1399
+ list / history specs are recipient-side or caller-own-data — no
1400
+ enumeration vector, no rate cap.
1356
1401
 
1357
1402
  Error reason constants (exported as `as const` literals):
1358
1403
 
@@ -1416,7 +1461,7 @@ Options:
1416
1461
 
1417
1462
  - `roles?: RoleSchemaResult` — drives the admin-grant-path lookup
1418
1463
  (`role_has_grant_path(_, role, GRANT_PATH_ADMIN)`); defaults to
1419
- `BUILTIN_ROLE_SPECS_BY_NAME`.
1464
+ `builtin_role_specs_by_name`.
1420
1465
  - `default_ttl_ms?: number` — applied to new offers (defaults to
1421
1466
  `ROLE_GRANT_OFFER_DEFAULT_TTL_MS`).
1422
1467
  - `authorize?: RoleGrantOfferCreateAuthorize` — custom policy for
@@ -1481,6 +1526,14 @@ and `create_frontend_rpc_client({specs})` wiring. Self-service role
1481
1526
  specs are not included (opt-in, app-specific `eligible_roles`) —
1482
1527
  spread `all_self_service_role_action_specs` separately when needed.
1483
1528
 
1529
+ ### `all_action_spec_registries.ts` — walker-only registry-of-registries
1530
+
1531
+ `all_fuz_auth_action_spec_registries` — walker/codegen entry for every
1532
+ fuz-auth action-spec bundle (`admin`, `role_grant_offer`, `account`,
1533
+ `self_service_role`, `actor_lookup`). Not a mounting surface; protocol
1534
+ specs are excluded. Iterated by `../../test/auth/action_spec_input_invariants.test.ts`
1535
+ and `../../test/auth/all_action_spec_registries.acting_biconditional.test.ts`.
1536
+
1484
1537
  ### `account_action_specs.ts` + `account_actions.ts` — seven self-service RPC actions
1485
1538
 
1486
1539
  Counterpart to `account_routes.ts`. Cookie-lifecycle flows (`login`,
@@ -1500,15 +1553,23 @@ operations are account-scoped via `query_session_revoke_for_account` /
1500
1553
  or token id returns `revoked: false` rather than revealing whether the id
1501
1554
  exists.
1502
1555
 
1503
- | Spec | Side effects | Input | Output |
1504
- | ---------------------------------------- | ------------ | -------------- | ----------------------- |
1505
- | `account_verify_action_spec` | false | `z.void()` | `SessionAccountJson` |
1506
- | `account_session_list_action_spec` | false | `z.void()` | `{sessions}` |
1507
- | `account_session_revoke_action_spec` | true | `{session_id}` | `{ok, revoked}` |
1508
- | `account_session_revoke_all_action_spec` | true | `z.void()` | `{ok, count}` |
1509
- | `account_token_create_action_spec` | true | `{name?}` | `{ok, token, id, name}` |
1510
- | `account_token_list_action_spec` | false | `z.void()` | `{tokens}` |
1511
- | `account_token_revoke_action_spec` | true | `{token_id}` | `{ok, revoked}` |
1556
+ | Spec | Side effects | Rate limit | Input | Output |
1557
+ | ---------------------------------------- | ------------ | ----------- | -------------- | ----------------------- |
1558
+ | `account_verify_action_spec` | false | | `z.void()` | `SessionAccountJson` |
1559
+ | `account_session_list_action_spec` | false | | `z.void()` | `{sessions}` |
1560
+ | `account_session_revoke_action_spec` | true | | `{session_id}` | `{ok, revoked}` |
1561
+ | `account_session_revoke_all_action_spec` | true | | `z.void()` | `{ok, count}` |
1562
+ | `account_token_create_action_spec` | true | `'account'` | `{name?}` | `{ok, token, id, name}` |
1563
+ | `account_token_list_action_spec` | false | | `z.void()` | `{tokens}` |
1564
+ | `account_token_revoke_action_spec` | true | | `{token_id}` | `{ok, revoked}` |
1565
+
1566
+ `account_token_create` declares `rate_limit: 'account'` to bound the
1567
+ _rate_ of token churn. The outstanding-token count is already capped by
1568
+ `max_tokens` via `query_api_token_enforce_limit`, but the per-account
1569
+ burn rate is not — without this cap a caller could rotate tokens in a
1570
+ tight loop to amplify `token_create` audit churn. The other six specs
1571
+ are IDOR-guarded reads/revokes of caller-own state with no enumeration
1572
+ vector, so rate caps are symmetry-only and skipped.
1512
1573
 
1513
1574
  `session_id` validates as `Blake3Hash`; `token_id` validates as
1514
1575
  `ApiTokenId` (`tok_[A-Za-z0-9_-]{12}`).
@@ -1546,6 +1607,12 @@ distinguish self-toggled role_grants from admin grants/offers. The
1546
1607
  part of the documented surface rather than riding on `z.looseObject`
1547
1608
  permissiveness.
1548
1609
 
1610
+ Declares `rate_limit: 'account'` — every call writes a
1611
+ `role_grant_create` / `role_grant_revoke` audit row regardless of
1612
+ `changed`, so a flapping loop could inflate the log and obscure
1613
+ unrelated activity. The toggle's idempotency doesn't bound the burn
1614
+ rate; the dispatcher's per-action hook does.
1615
+
1549
1616
  Method name is static — `role` lives in the input, not the method
1550
1617
  name. Mirrors the `role_grant_offer_create({role})` precedent. Per-role
1551
1618
  parameterized methods would break the `satisfies RequestResponseActionSpec`
@@ -1555,7 +1622,7 @@ codegen invariant and grow the surface linearly per role.
1555
1622
 
1556
1623
  - `eligible_roles?: ReadonlyArray<string>` — optional override
1557
1624
  allowlist. When omitted, eligibility is derived from
1558
- `roles.role_specs` (or `BUILTIN_ROLE_SPECS_BY_NAME` when `roles` is
1625
+ `roles.role_specs` (or `builtin_role_specs_by_name` when `roles` is
1559
1626
  also omitted) by selecting every role whose `RoleSpec.grant_paths`
1560
1627
  includes `'self_service'` (`GRANT_PATH_SELF_SERVICE`). Roles outside
1561
1628
  the eligible set are rejected with `forbidden` + reason
@@ -1582,6 +1649,109 @@ Deps: `Pick<RouteFactoryDeps, 'log' | 'audit'>`.
1582
1649
  `all_self_service_role_action_specs: ReadonlyArray<RequestResponseActionSpec>` —
1583
1650
  codegen-ready registry of the single unified spec.
1584
1651
 
1652
+ ### `actor_lookup_action_specs.ts` + `actor_lookup_actions.ts` — opt-in batched actor → label resolver
1653
+
1654
+ One static `request_response` action — `actor_lookup({ids}) → {actors:
1655
+ [{id, username, display_name?}]}` — powers the labels arc for surfaces
1656
+ that stamp an actor id (bylines, owner columns, grantor labels, audit
1657
+ "by" cells). One round trip resolves a batch to display strings;
1658
+ `ACTOR_LOOKUP_IDS_MAX = 50` cap per call.
1659
+
1660
+ **Auth + rate-limit posture.** `{account: 'required', actor: 'none'}` +
1661
+ `rate_limit: 'account'`. Account-grain — the caller need only be signed
1662
+ in; resolution skips the actor phase. The auth gate + per-account rate
1663
+ limit + per-call cap bound the batched username-enumeration surface that
1664
+ a `cell_list` ↔ `actor_lookup` pair would otherwise present. Don't loosen
1665
+ to public — a public-surface byline should resolve via SSR-stamped labels
1666
+ or per-cell embedded actor labels, not by widening this gate.
1667
+
1668
+ **Wire shape — info-leak audit.** Deliberately omitted from
1669
+ `ActorLookupEntryJson`:
1670
+
1671
+ - `account_id` — the actor↔account join is a control-plane detail.
1672
+ - `email`, password/credential fields — never queried.
1673
+ - `created_at` / `updated_at` — timing-oracle avoidance.
1674
+ - role / role_grants / session state — separation of concern.
1675
+
1676
+ `display_name` is omitted (not `null`) when `actor.name` is blank, so
1677
+ clients see `undefined` rather than a sentinel string. Unknown ids are
1678
+ silently absent — by construction this is an existence-oracle (caller
1679
+ diffs response ids against request ids), bounded by rate-limit, the
1680
+ 50-id cap, actor-uuid intractability (122-bit random), and the
1681
+ hard-delete-cascade indistinguishability from never-existed (no
1682
+ tombstone oracle). Response order is unspecified — callers index by
1683
+ `id` when needed.
1684
+
1685
+ `create_actor_lookup_actions(deps)` — `deps:
1686
+ Pick<RouteFactoryDeps, 'log'>`. Pure read; no audit, no side effects.
1687
+ Backed by `query_actors_by_ids` (see Queries §
1688
+ [`actor_lookup_queries.ts`](#actor_lookup_queriests)).
1689
+
1690
+ Bundle is **not** included in `create_standard_rpc_actions` — consumers
1691
+ without a byline surface can skip it. Spread
1692
+ `all_actor_lookup_action_specs` alongside the standard bundle when the
1693
+ labels arc is needed.
1694
+
1695
+ ### `actor_search_action_specs.ts` + `actor_search_actions.ts` — opt-in prefix-search picker
1696
+
1697
+ One static `request_response` action — `actor_search({query, scope_ids?, limit?}) → {actors: [{id, username, display_name?}]}` —
1698
+ powers person-target pickers. Sibling to `actor_lookup`: that resolves a
1699
+ batch of known ids to labels; this resolves a partial name to candidate
1700
+ actors. Reuses `ActorLookupEntryJson` from
1701
+ `./actor_lookup_action_specs.ts` so the labels arc on the consumer side
1702
+ stays uniform. Default limit `ACTOR_SEARCH_LIMIT_DEFAULT = 20`, hard cap
1703
+ `ACTOR_SEARCH_LIMIT_MAX = 50`. Query string capped at
1704
+ `ACTOR_SEARCH_QUERY_LENGTH_MAX = 50`.
1705
+
1706
+ **Auth + rate-limit posture.** `{account: 'required', actor: 'none'}` +
1707
+ `rate_limit: 'account'`. Same shape as `actor_lookup`. The handler
1708
+ additionally requires the caller to be admin when `scope_ids` is empty —
1709
+ unbounded global search is the admin-only arm; non-admin callers must
1710
+ always pass at least one scope_id and get filtered to actors with active
1711
+ role_grants on those scopes. The admin check is **account-grain** (any
1712
+ actor on the caller's account holds a global `admin` role_grant) via
1713
+ `query_account_has_global_role` — the `actor: 'none'` posture means
1714
+ `auth.role_grants` is empty and the in-memory `has_scoped_role` helper
1715
+ doesn't apply.
1716
+
1717
+ **Caller-passes-scope_ids design.** `scope_ids` is trusted as a filter,
1718
+ not as an authority claim — the SQL filters to actors with role_grants on
1719
+ those scopes regardless of whether the caller has authority over them.
1720
+ Consumers (e.g. visiones' `CellGrantsEditor.svelte`) pre-filter
1721
+ `scope_ids` against their own authority (the teacher-predicate stays in
1722
+ the consumer layer, not in fuz_app). This does **not** widen the
1723
+ scope-existence oracle: an attacker passing a random scope_id never
1724
+ learns the scope existed if no member happens to match the query, and
1725
+ even on a match the result row only carries the matching actor — never
1726
+ "which scope matched".
1727
+
1728
+ **Wire shape — additional info-leak posture beyond `actor_lookup`'s.**
1729
+
1730
+ - Prefix match (`LOWER(name) LIKE LOWER(query) || '%' ESCAPE '\\'`),
1731
+ **not** full `%query%`. LIKE wildcards in the user-supplied query are
1732
+ escaped at the JS layer so a `%xyz` input can't widen to full LIKE
1733
+ and defeat the per-call cap.
1734
+ - Empty result set on no-match — fail-soft like `cell_list`. No "no
1735
+ actor matches" error that would leak an existence boundary on the
1736
+ search-term axis.
1737
+ - Hard-deleted actors silently drop (same `account_id` cascade as
1738
+ `actor_lookup`).
1739
+
1740
+ Reason constant exported for failed-arm matching: `ERROR_ACTOR_SEARCH_SCOPE_REQUIRED`
1741
+ (`'actor_search_scope_required'`) — fired with `invalid_params` when a
1742
+ non-admin caller omits `scope_ids` or passes `[]`. Surfaced on
1743
+ `spec.error_reasons` so codegen + form-state matching can read it
1744
+ declaratively.
1745
+
1746
+ `create_actor_search_actions(deps)` — `deps:
1747
+ Pick<RouteFactoryDeps, 'log'>`. Pure read; no audit, no side effects.
1748
+ Backed by `query_actor_search` (see Queries §`actor_search_queries.ts`).
1749
+
1750
+ Bundle is **not** included in `create_standard_rpc_actions` — consumers
1751
+ without a person-target picker can skip it. Spread
1752
+ `all_actor_search_action_specs` alongside the standard bundle when the
1753
+ picker is needed.
1754
+
1585
1755
  ## Cleanup
1586
1756
 
1587
1757
  `cleanup.ts` — periodic auth maintenance:
@@ -164,6 +164,14 @@ export declare const account_session_revoke_all_action_spec: {
164
164
  async: true;
165
165
  description: string;
166
166
  };
167
+ /**
168
+ * `rate_limit: 'account'` bounds the burn rate of API-token creates. The
169
+ * outstanding-token count is already capped by `max_tokens` (via
170
+ * `query_api_token_enforce_limit`), but the per-account *rate* of churn
171
+ * is not — without this cap, a caller could rotate tokens in a tight
172
+ * loop to amplify `token_create` audit churn or attempt to provoke
173
+ * downstream rate-limit hot spots.
174
+ */
167
175
  export declare const account_token_create_action_spec: {
168
176
  method: string;
169
177
  kind: "request_response";
@@ -184,6 +192,7 @@ export declare const account_token_create_action_spec: {
184
192
  }, z.core.$strict>;
185
193
  async: true;
186
194
  description: string;
195
+ rate_limit: "account";
187
196
  };
188
197
  export declare const account_token_list_action_spec: {
189
198
  method: string;
@@ -1 +1 @@
1
- {"version":3,"file":"account_action_specs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_action_specs.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"account_action_specs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_action_specs.ts"],"names":[],"mappings":"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"}
@@ -112,6 +112,14 @@ export const account_session_revoke_all_action_spec = {
112
112
  async: true,
113
113
  description: 'Revoke every auth session for the current account.',
114
114
  };
115
+ /**
116
+ * `rate_limit: 'account'` bounds the burn rate of API-token creates. The
117
+ * outstanding-token count is already capped by `max_tokens` (via
118
+ * `query_api_token_enforce_limit`), but the per-account *rate* of churn
119
+ * is not — without this cap, a caller could rotate tokens in a tight
120
+ * loop to amplify `token_create` audit churn or attempt to provoke
121
+ * downstream rate-limit hot spots.
122
+ */
115
123
  export const account_token_create_action_spec = {
116
124
  method: 'account_token_create',
117
125
  kind: 'request_response',
@@ -122,6 +130,7 @@ export const account_token_create_action_spec = {
122
130
  output: TokenCreateOutput,
123
131
  async: true,
124
132
  description: 'Create an API token for the current account. Raw token is returned once.',
133
+ rate_limit: 'account',
125
134
  };
126
135
  export const account_token_list_action_spec = {
127
136
  method: 'account_token_list',
@@ -0,0 +1,127 @@
1
+ /**
2
+ * `actor_lookup` RPC spec — authenticated batched id → username/display_name
3
+ * resolver, keyed by actor id.
4
+ *
5
+ * Powers the labels arc for surfaces that stamp an actor id (bylines,
6
+ * owner columns, grantor labels, audit-log "by" cells). One round trip
7
+ * resolves an array of ids to display strings.
8
+ *
9
+ * ## Auth + rate-limit posture
10
+ *
11
+ * `{account: 'required', actor: 'none'}` + `rate_limit: 'account'`.
12
+ * Account-grain — only that the caller is signed in matters, not which
13
+ * actor is calling, so resolution skips the actor phase. The auth gate
14
+ * + per-account rate limit (default 1200/15min) + the
15
+ * {@link ACTOR_LOOKUP_IDS_MAX | per-call cap} bound the batched
16
+ * username-enumeration surface that the `cell_list` ↔ `actor_lookup`
17
+ * pair would otherwise present.
18
+ *
19
+ * If a public-surface byline ever lands (e.g. an unauthenticated
20
+ * gallery), it should resolve via a separate public-safe mechanism
21
+ * (SSR-stamped labels or a per-cell embedded actor label), **not** by
22
+ * loosening this gate.
23
+ *
24
+ * ## Wire shape — info-leak audit
25
+ *
26
+ * Output: `{actors: [{id, username, display_name?}]}`. Deliberately
27
+ * omitted:
28
+ *
29
+ * - `account_id` — the actor↔account join is a control-plane detail
30
+ * - `email`, password/credential fields — never queried
31
+ * - `created_at` / `updated_at` — timing-oracle avoidance
32
+ * - role / role_grants / session state — separation of concern
33
+ *
34
+ * `display_name` is omitted (not `null`) when `actor.name` is blank, so
35
+ * clients see `undefined` rather than a sentinel string. Unknown ids are
36
+ * silently absent from the response — by construction this is an
37
+ * existence-oracle (the caller can diff response ids against request
38
+ * ids), bounded by:
39
+ *
40
+ * 1. rate-limit (per-account, see above),
41
+ * 2. {@link ACTOR_LOOKUP_IDS_MAX} cap per call,
42
+ * 3. actor-uuid intractability (122-bit random),
43
+ * 4. hard-deleted actors are indistinguishable from never-existed (no
44
+ * tombstone oracle — see `actor_lookup_queries.ts`).
45
+ *
46
+ * Response order is unspecified — callers index by `id` when needed.
47
+ *
48
+ * @module
49
+ */
50
+ import { z } from 'zod';
51
+ /**
52
+ * Hard cap on the number of ids resolvable in one call. Bounds the
53
+ * batched username-enumeration surface.
54
+ */
55
+ export declare const ACTOR_LOOKUP_IDS_MAX = 50;
56
+ /** One resolved actor row. `display_name` omitted when blank. */
57
+ export declare const ActorLookupEntryJson: z.ZodObject<{
58
+ id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
59
+ username: z.ZodString;
60
+ display_name: z.ZodOptional<z.ZodString>;
61
+ }, z.core.$strict>;
62
+ export type ActorLookupEntryJson = z.infer<typeof ActorLookupEntryJson>;
63
+ export declare const ActorLookupInput: z.ZodObject<{
64
+ ids: z.ZodArray<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
65
+ }, z.core.$strict>;
66
+ export type ActorLookupInput = z.infer<typeof ActorLookupInput>;
67
+ export declare const ActorLookupOutput: z.ZodObject<{
68
+ actors: z.ZodArray<z.ZodObject<{
69
+ id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
70
+ username: z.ZodString;
71
+ display_name: z.ZodOptional<z.ZodString>;
72
+ }, z.core.$strict>>;
73
+ }, z.core.$strict>;
74
+ export type ActorLookupOutput = z.infer<typeof ActorLookupOutput>;
75
+ export declare const actor_lookup_action_spec: {
76
+ method: string;
77
+ kind: "request_response";
78
+ initiator: "frontend";
79
+ auth: {
80
+ account: "required";
81
+ actor: "none";
82
+ };
83
+ side_effects: false;
84
+ input: z.ZodObject<{
85
+ ids: z.ZodArray<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
86
+ }, z.core.$strict>;
87
+ output: z.ZodObject<{
88
+ actors: z.ZodArray<z.ZodObject<{
89
+ id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
90
+ username: z.ZodString;
91
+ display_name: z.ZodOptional<z.ZodString>;
92
+ }, z.core.$strict>>;
93
+ }, z.core.$strict>;
94
+ async: true;
95
+ rate_limit: "account";
96
+ description: string;
97
+ };
98
+ /**
99
+ * All actor_lookup action specs — independent opt-in registry. Consumers
100
+ * spread alongside `all_standard_action_specs` if they want the labels
101
+ * arc; not folded into the standard bundle because consumers without a
102
+ * byline surface can skip it.
103
+ */
104
+ export declare const all_actor_lookup_action_specs: readonly [{
105
+ method: string;
106
+ kind: "request_response";
107
+ initiator: "frontend";
108
+ auth: {
109
+ account: "required";
110
+ actor: "none";
111
+ };
112
+ side_effects: false;
113
+ input: z.ZodObject<{
114
+ ids: z.ZodArray<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
115
+ }, z.core.$strict>;
116
+ output: z.ZodObject<{
117
+ actors: z.ZodArray<z.ZodObject<{
118
+ id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
119
+ username: z.ZodString;
120
+ display_name: z.ZodOptional<z.ZodString>;
121
+ }, z.core.$strict>>;
122
+ }, z.core.$strict>;
123
+ async: true;
124
+ rate_limit: "account";
125
+ description: string;
126
+ }];
127
+ //# sourceMappingURL=actor_lookup_action_specs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actor_lookup_action_specs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/actor_lookup_action_specs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAKtB;;;GAGG;AACH,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAEvC,iEAAiE;AACjE,eAAO,MAAM,oBAAoB;;;;kBAI/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE,eAAO,MAAM,gBAAgB;;kBAQ3B,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEhE,eAAO,MAAM,iBAAiB;;;;;;kBAE5B,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAElE,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;CAWA,CAAC;AAEtC;;;;;GAKG;AACH,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;;EAAsC,CAAC"}