@fuzdev/fuz_app 0.111.0 → 0.113.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 (212) hide show
  1. package/dist/actions/CLAUDE.md +15 -11
  2. package/dist/actions/action_rpc.d.ts +1 -1
  3. package/dist/actions/action_rpc.d.ts.map +1 -1
  4. package/dist/actions/action_rpc.js +4 -6
  5. package/dist/actions/peer_ping.d.ts +8 -0
  6. package/dist/actions/peer_ping.d.ts.map +1 -1
  7. package/dist/actions/peer_ping.js +8 -0
  8. package/dist/actions/peer_request.d.ts +1 -1
  9. package/dist/actions/peer_request.js +1 -1
  10. package/dist/actions/perform_action.d.ts +16 -9
  11. package/dist/actions/perform_action.d.ts.map +1 -1
  12. package/dist/actions/perform_action.js +30 -21
  13. package/dist/actions/register_action_ws.d.ts +1 -1
  14. package/dist/actions/register_action_ws.d.ts.map +1 -1
  15. package/dist/actions/register_action_ws.js +88 -21
  16. package/dist/auth/CLAUDE.md +43 -8
  17. package/dist/auth/account_actions.d.ts.map +1 -1
  18. package/dist/auth/account_actions.js +10 -1
  19. package/dist/auth/account_queries.d.ts.map +1 -1
  20. package/dist/auth/account_queries.js +36 -16
  21. package/dist/auth/account_schema.d.ts +20 -0
  22. package/dist/auth/account_schema.d.ts.map +1 -1
  23. package/dist/auth/account_schema.js +22 -1
  24. package/dist/auth/actor_search_actions.d.ts +3 -3
  25. package/dist/auth/actor_search_actions.d.ts.map +1 -1
  26. package/dist/auth/actor_search_actions.js +10 -6
  27. package/dist/auth/api_token_queries.d.ts.map +1 -1
  28. package/dist/auth/api_token_queries.js +11 -5
  29. package/dist/auth/app_settings_queries.d.ts.map +1 -1
  30. package/dist/auth/app_settings_queries.js +6 -4
  31. package/dist/auth/audit_log_queries.d.ts +6 -0
  32. package/dist/auth/audit_log_queries.d.ts.map +1 -1
  33. package/dist/auth/audit_log_queries.js +11 -5
  34. package/dist/auth/audit_log_route_schema.d.ts.map +1 -1
  35. package/dist/auth/audit_log_route_schema.js +12 -1
  36. package/dist/auth/cell_actions.d.ts.map +1 -1
  37. package/dist/auth/cell_actions.js +3 -8
  38. package/dist/auth/cell_audit_actions.d.ts.map +1 -1
  39. package/dist/auth/cell_audit_actions.js +1 -1
  40. package/dist/auth/cell_field_actions.js +1 -1
  41. package/dist/auth/cell_grant_actions.js +1 -1
  42. package/dist/auth/cell_item_actions.js +1 -1
  43. package/dist/auth/invite_queries.d.ts.map +1 -1
  44. package/dist/auth/invite_queries.js +10 -8
  45. package/dist/auth/role_grant_offer_action_specs.d.ts +2 -1
  46. package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
  47. package/dist/auth/role_grant_offer_action_specs.js +2 -1
  48. package/dist/auth/role_grant_offer_ddl.d.ts +11 -0
  49. package/dist/auth/role_grant_offer_ddl.d.ts.map +1 -1
  50. package/dist/auth/role_grant_offer_ddl.js +20 -2
  51. package/dist/auth/role_grant_offer_queries.d.ts.map +1 -1
  52. package/dist/auth/role_grant_offer_queries.js +29 -15
  53. package/dist/auth/role_grant_queries.d.ts.map +1 -1
  54. package/dist/auth/role_grant_queries.js +21 -7
  55. package/dist/auth/session_queries.d.ts.map +1 -1
  56. package/dist/auth/session_queries.js +9 -4
  57. package/dist/db/CLAUDE.md +21 -2
  58. package/dist/db/cell_audit_queries.js +2 -2
  59. package/dist/db/cell_field_queries.d.ts +1 -1
  60. package/dist/db/cell_field_queries.d.ts.map +1 -1
  61. package/dist/db/cell_field_queries.js +8 -6
  62. package/dist/db/cell_grant_queries.d.ts +1 -1
  63. package/dist/db/cell_grant_queries.d.ts.map +1 -1
  64. package/dist/db/cell_grant_queries.js +12 -10
  65. package/dist/db/cell_item_queries.d.ts +1 -1
  66. package/dist/db/cell_item_queries.d.ts.map +1 -1
  67. package/dist/db/cell_item_queries.js +9 -7
  68. package/dist/db/cell_queries.d.ts +3 -3
  69. package/dist/db/cell_queries.d.ts.map +1 -1
  70. package/dist/db/cell_queries.js +6 -4
  71. package/dist/db/fact_disk_storage.js +1 -1
  72. package/dist/db/fact_queries.d.ts.map +1 -1
  73. package/dist/db/fact_queries.js +9 -3
  74. package/dist/db/sql_columns.d.ts +80 -3
  75. package/dist/db/sql_columns.d.ts.map +1 -1
  76. package/dist/db/sql_columns.js +91 -4
  77. package/dist/http/CLAUDE.md +6 -4
  78. package/dist/http/auth_shape.d.ts +19 -6
  79. package/dist/http/auth_shape.d.ts.map +1 -1
  80. package/dist/http/auth_shape.js +19 -6
  81. package/dist/http/jsonrpc_helpers.d.ts +13 -0
  82. package/dist/http/jsonrpc_helpers.d.ts.map +1 -1
  83. package/dist/http/jsonrpc_helpers.js +13 -0
  84. package/dist/http/surface_query.d.ts +29 -4
  85. package/dist/http/surface_query.d.ts.map +1 -1
  86. package/dist/http/surface_query.js +30 -5
  87. package/dist/realtime/sse_auth_guard.d.ts +9 -3
  88. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  89. package/dist/realtime/sse_auth_guard.js +9 -3
  90. package/dist/server/serve_fact_route.d.ts.map +1 -1
  91. package/dist/server/serve_fact_route.js +7 -2
  92. package/dist/testing/CLAUDE.md +54 -28
  93. package/dist/testing/admin_integration.d.ts +0 -3
  94. package/dist/testing/admin_integration.d.ts.map +1 -1
  95. package/dist/testing/admin_integration.js +0 -1
  96. package/dist/testing/adversarial_404.d.ts.map +1 -1
  97. package/dist/testing/adversarial_404.js +4 -1
  98. package/dist/testing/adversarial_input.d.ts.map +1 -1
  99. package/dist/testing/adversarial_input.js +5 -4
  100. package/dist/testing/app_server.d.ts +4 -3
  101. package/dist/testing/app_server.d.ts.map +1 -1
  102. package/dist/testing/app_server.js +11 -5
  103. package/dist/testing/attack_surface.d.ts +21 -2
  104. package/dist/testing/attack_surface.d.ts.map +1 -1
  105. package/dist/testing/attack_surface.js +78 -27
  106. package/dist/testing/audit_completeness.d.ts +0 -3
  107. package/dist/testing/audit_completeness.d.ts.map +1 -1
  108. package/dist/testing/audit_completeness.js +0 -1
  109. package/dist/testing/auth_apps.d.ts +34 -2
  110. package/dist/testing/auth_apps.d.ts.map +1 -1
  111. package/dist/testing/auth_apps.js +43 -7
  112. package/dist/testing/cross_backend/account_lifecycle.d.ts +5 -4
  113. package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -1
  114. package/dist/testing/cross_backend/account_lifecycle.js +7 -0
  115. package/dist/testing/cross_backend/action_manifest.d.ts +19 -3
  116. package/dist/testing/cross_backend/action_manifest.d.ts.map +1 -1
  117. package/dist/testing/cross_backend/action_manifest.js +10 -4
  118. package/dist/testing/cross_backend/action_manifest_parity.d.ts +9 -3
  119. package/dist/testing/cross_backend/action_manifest_parity.d.ts.map +1 -1
  120. package/dist/testing/cross_backend/action_manifest_parity.js +6 -0
  121. package/dist/testing/cross_backend/actor_lookup.d.ts +3 -2
  122. package/dist/testing/cross_backend/actor_lookup.d.ts.map +1 -1
  123. package/dist/testing/cross_backend/body_size.d.ts +4 -2
  124. package/dist/testing/cross_backend/body_size.d.ts.map +1 -1
  125. package/dist/testing/cross_backend/body_size.js +1 -1
  126. package/dist/testing/cross_backend/cell_crud.d.ts +2 -2
  127. package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
  128. package/dist/testing/cross_backend/cell_crud.js +10 -0
  129. package/dist/testing/cross_backend/cell_gated_create.d.ts +3 -3
  130. package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
  131. package/dist/testing/cross_backend/cell_grant_role.d.ts +2 -2
  132. package/dist/testing/cross_backend/cell_grant_role.d.ts.map +1 -1
  133. package/dist/testing/cross_backend/cell_relations.d.ts +2 -2
  134. package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -1
  135. package/dist/testing/cross_backend/fact_serving.d.ts +2 -2
  136. package/dist/testing/cross_backend/fact_serving.d.ts.map +1 -1
  137. package/dist/testing/cross_backend/make_cross_backend_project.d.ts +17 -2
  138. package/dist/testing/cross_backend/make_cross_backend_project.d.ts.map +1 -1
  139. package/dist/testing/cross_backend/make_cross_backend_project.js +23 -3
  140. package/dist/testing/cross_backend/origin.d.ts +4 -2
  141. package/dist/testing/cross_backend/origin.d.ts.map +1 -1
  142. package/dist/testing/cross_backend/origin.js +1 -1
  143. package/dist/testing/cross_backend/peer_ping_ws.d.ts.map +1 -1
  144. package/dist/testing/cross_backend/peer_ping_ws.js +11 -7
  145. package/dist/testing/cross_backend/query_shape.d.ts +6 -5
  146. package/dist/testing/cross_backend/query_shape.d.ts.map +1 -1
  147. package/dist/testing/cross_backend/ready.d.ts +2 -2
  148. package/dist/testing/cross_backend/ready.d.ts.map +1 -1
  149. package/dist/testing/cross_backend/role_grant_offer_enumeration.d.ts.map +1 -1
  150. package/dist/testing/cross_backend/role_grant_offer_enumeration.js +7 -1
  151. package/dist/testing/cross_backend/setup.d.ts +26 -6
  152. package/dist/testing/cross_backend/setup.d.ts.map +1 -1
  153. package/dist/testing/cross_backend/standard.d.ts +5 -1
  154. package/dist/testing/cross_backend/standard.d.ts.map +1 -1
  155. package/dist/testing/cross_backend/standard.js +1 -5
  156. package/dist/testing/cross_backend/testing_backdoor.js +1 -1
  157. package/dist/testing/cross_backend/testing_reset_actions.d.ts +5 -0
  158. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  159. package/dist/testing/cross_backend/testing_reset_actions.js +3 -2
  160. package/dist/testing/cross_backend/token_lifetime.d.ts.map +1 -1
  161. package/dist/testing/cross_backend/token_lifetime.js +6 -0
  162. package/dist/testing/cross_backend/token_scope_surface.d.ts +2 -2
  163. package/dist/testing/cross_backend/token_scope_surface.d.ts.map +1 -1
  164. package/dist/testing/cross_backend/token_scope_surface.js +36 -7
  165. package/dist/testing/cross_backend/wire_shapes.d.ts +22 -0
  166. package/dist/testing/cross_backend/wire_shapes.d.ts.map +1 -0
  167. package/dist/testing/cross_backend/wire_shapes.js +41 -0
  168. package/dist/testing/data_exposure.d.ts +0 -3
  169. package/dist/testing/data_exposure.d.ts.map +1 -1
  170. package/dist/testing/data_exposure.js +0 -1
  171. package/dist/testing/db.d.ts +44 -7
  172. package/dist/testing/db.d.ts.map +1 -1
  173. package/dist/testing/db.js +62 -27
  174. package/dist/testing/integration.d.ts.map +1 -1
  175. package/dist/testing/integration.js +15 -2
  176. package/dist/testing/round_trip.d.ts +0 -3
  177. package/dist/testing/round_trip.d.ts.map +1 -1
  178. package/dist/testing/round_trip.js +0 -4
  179. package/dist/testing/rpc_attack_surface.js +2 -2
  180. package/dist/testing/rpc_round_trip.d.ts +0 -3
  181. package/dist/testing/rpc_round_trip.d.ts.map +1 -1
  182. package/dist/testing/rpc_round_trip.js +0 -1
  183. package/dist/testing/schema_generators.d.ts +15 -0
  184. package/dist/testing/schema_generators.d.ts.map +1 -1
  185. package/dist/testing/schema_generators.js +45 -11
  186. package/dist/testing/standard.d.ts +5 -1
  187. package/dist/testing/standard.d.ts.map +1 -1
  188. package/dist/testing/standard.js +2 -7
  189. package/dist/testing/surface_invariants.d.ts +7 -1
  190. package/dist/testing/surface_invariants.d.ts.map +1 -1
  191. package/dist/testing/surface_invariants.js +11 -5
  192. package/dist/timestamp.d.ts +58 -0
  193. package/dist/timestamp.d.ts.map +1 -0
  194. package/dist/timestamp.js +57 -0
  195. package/dist/ui/AdminAccounts.svelte +6 -4
  196. package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -1
  197. package/dist/ui/AdminInvites.svelte +3 -3
  198. package/dist/ui/AdminInvites.svelte.d.ts.map +1 -1
  199. package/dist/ui/AdminOverview.svelte +10 -10
  200. package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
  201. package/dist/ui/AdminRoleGrantHistory.svelte +7 -7
  202. package/dist/ui/AdminRoleGrantHistory.svelte.d.ts.map +1 -1
  203. package/dist/ui/AdminSettings.svelte +1 -1
  204. package/dist/ui/RoleGrantOfferHistory.svelte +3 -1
  205. package/dist/ui/RoleGrantOfferHistory.svelte.d.ts.map +1 -1
  206. package/dist/ui/RoleGrantOfferInbox.svelte +4 -1
  207. package/dist/ui/RoleGrantOfferInbox.svelte.d.ts.map +1 -1
  208. package/dist/ui/SurfaceExplorer.svelte +33 -17
  209. package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
  210. package/dist/ui/role_grant_offers_state.svelte.d.ts.map +1 -1
  211. package/dist/ui/role_grant_offers_state.svelte.js +6 -2
  212. package/package.json +10 -10
@@ -102,8 +102,13 @@ doesn't carry `id`). Module direction: `role_grant_offer_queries.ts` imports
102
102
  the revoke cascades in `role_grant_queries.ts` still _write_ offer rows, which
103
103
  is exactly why the offer const and the shared CTE tail live in the DDL module.
104
104
  Every const is pinned to the live schema by
105
- `src/test/db/column_projections.db.test.ts`. Mirrors the Rust spine, which
106
- names columns at every site.
105
+ `src/test/db/column_projections.db.test.ts`. The Rust `fuz_auth` twin carries
106
+ the same nine consts (names and order), projected through
107
+ `fuz_db::qualify_columns` / `omit_columns`, decoded with compile-time
108
+ name→index (`fuz_db::col!`), and pinned by its own
109
+ `tests/columns.rs` registry; its narrow purpose rows (`AccountRow` is an
110
+ `id, username` pair) keep private per-shape lists checked as subsets of the
111
+ table const.
107
112
 
108
113
  `_unscoped` suffix on `query_session_revoke_by_hash_unscoped` and
109
114
  `query_invite_claim_unscoped` is the safety signal: SQL only checks row state,
@@ -295,7 +300,7 @@ Account-only — never loads actor or role_grants, never populates
295
300
  `REQUEST_CONTEXT_KEY`.
296
301
 
297
302
  **Authorization runs before input validation**, matching the dispatcher's
298
- 401 → authz → 403 → 400 phase order (see `http/CLAUDE.md` §Validation
303
+ 401 → authz → 403 → 400 phase order — the 429 is dispatcher-only (see `http/CLAUDE.md` §Validation
299
304
  pipeline). When the route's input declares `acting?: ActingActor` or its auth
300
305
  requires role_grants, the authorization phase calls `resolve_acting_actor` over
301
306
  the `acting` selector — the validated query value on GETs, read off the raw
@@ -320,6 +325,31 @@ path populates `REQUEST_CONTEXT_KEY`; it sets only `ACCOUNT_ID_KEY`,
320
325
  `TEST_CONTEXT_PRESET_KEY` to bypass DB-backed actor resolution; production
321
326
  code that reads `REQUEST_CONTEXT_KEY` is reading test escape-hatch state.
322
327
 
328
+ ### Wire timestamp shape
329
+
330
+ Every timestamp on the wire is **second-precision UTC** —
331
+ `YYYY-MM-DDTHH:MM:SSZ`, exactly 20 characters, the shape both spines emit.
332
+ Timestamps read from a row get it from the SQL projection: each query module
333
+ builds an `iso8601_timestamp_expr` override over its table const and passes
334
+ it to `columns_sql` / `qualify_columns` (see `db/CLAUDE.md`), covering `account`, `actor`,
335
+ `role_grant`, `role_grant_offer`, `auth_session`, `api_token`, `invite`,
336
+ `app_settings`, `audit_log`, and the cell tables. Stamps minted in TS rather
337
+ than read back — the `expires_at` on `account_token_create`'s output — use
338
+ `to_iso8601_seconds` (`timestamp.ts`), so the mint and the later
339
+ `account_token_list` read agree byte for byte.
340
+
341
+ The `*Json` schemas type these as `z.string()`, which a millisecond stamp
342
+ also satisfies — so the shape is pinned behaviorally by
343
+ `assert_iso8601_seconds` (`testing/cross_backend/wire_shapes.ts`) inside the
344
+ shared suites, on both backends.
345
+
346
+ The alias costs one rule: **an `ORDER BY` in a projected SELECT qualifies
347
+ its column** (`ORDER BY auth_session.created_at`) — bare, Postgres resolves
348
+ it to the formatted output column, which ties rows written inside the same
349
+ second and drops the btree index. Qualified, ordering stays exact and
350
+ index-backed, matching the Rust twin (which aliases nothing, so its bare
351
+ `ORDER BY created_at` already means the column).
352
+
323
353
  ### Open-registry composition
324
354
 
325
355
  Four open string registries — `RoleName`, `ScopeKindName`,
@@ -480,10 +510,15 @@ are excluded.
480
510
  declare `credential_types: ['session']`. `account_session_revoke` is
481
511
  gated alongside `_revoke_all` because a leaked bearer can otherwise
482
512
  compose `account_session_list` + N×revoke to reach the same lockout.
513
+ `role_grant_offer_accept` and `self_service_role_set` carry the gate for
514
+ the privilege-pivot shape — both move the caller's own authority, and the
515
+ resulting grant outlives revoking the token.
483
516
  REST `POST /logout` also declares `credential_types: ['session']`, but
484
517
  for forensic fidelity rather than a threat — a bearer / daemon token
485
518
  holds no session to end, so the gate refuses it instead of returning a
486
- misleading 200 + a phantom `logout` audit row.
519
+ misleading 200 + a phantom `logout` audit row. `GET /audit/stream` carries
520
+ it so the stream stays closable (SSE subscribers are keyed by session
521
+ hash, so `token_revoke` could not close a bearer's stream).
487
522
  Admin token/session revoke specs deliberately stay unrestricted (admin
488
523
  scripting from CLI/bearer is legitimate operator workflow). See
489
524
  ../../../docs/security.md §Credential-channel gating.
@@ -511,15 +546,15 @@ authenticate.
511
546
  - `account_delete_action_spec` — mutation (soft delete); self-or-admin (`{account, actor}`, handler elevates to admin when `account_id` ≠ self); input `{account_id?}`; output `{ok, deleted}`. Tombstones account + actor(s), revokes sessions/tokens, emits `account_delete` + per-actor `actor_delete`. Refuses a keeper-role target (`ERROR_CANNOT_DELETE_KEEPER`) or the sole active admin (`ERROR_CANNOT_DELETE_LAST_ADMIN`) — both fail-loud with a failure-audit row.
512
547
  - `account_purge_action_spec` — mutation (hard purge); keeper-only (`credential_types: ['daemon_token']` + `roles: ['keeper']`) + `confirm: true` + WARN; input `{account_id, confirm?}`; output `{ok, purged}`. Cascading delete; emits `account_purge` + per-actor `actor_purge` (identity snapshot survives in metadata since `audit_log` ids carry no FK). Refuses a keeper-role target (`ERROR_CANNOT_DELETE_KEEPER`) or the sole active admin (`ERROR_CANNOT_DELETE_LAST_ADMIN`).
513
548
  - `account_undelete_action_spec` — mutation (reactivation, inverse of soft delete); admin-only (`roles: ['admin']` — no self path, a tombstoned account can't authenticate); input `{account_id}`; output `{ok, undeleted}`. Clears the `deleted_at` tombstone on the account + its actor(s), emits `account_undelete` + per-actor `actor_undelete`. Does not restore revoked sessions/tokens.
514
- - `admin_session_list_action_spec` — read; input `z.void()`; output `{sessions}`.
549
+ - `admin_session_list_action_spec` — read; input `{acting?}` (`.default({})`); output `{sessions}`.
515
550
  - `admin_session_revoke_all_action_spec` — mutation; input `{account_id}`; output `{ok, count}`.
516
551
  - `admin_token_revoke_all_action_spec` — mutation; input `{account_id}`; output `{ok, count}`.
517
- - `audit_log_list_action_spec` — read; input `{event_type?, account_id?, limit?, offset?, since_seq?}`; output `{events}`.
552
+ - `audit_log_list_action_spec` — read; input `{event_type?, outcome?, account_id?, limit?, offset?, since_seq?}`; output `{events}`.
518
553
  - `audit_log_role_grant_history_action_spec` — read; input `{limit?, offset?}`; output `{events}`.
519
554
  - `invite_create_action_spec` — mutation; input `{email?, username?}`; output `{ok, invite}`.
520
- - `invite_list_action_spec` — read; input `z.void()`; output `{invites}`.
555
+ - `invite_list_action_spec` — read; input `{acting?}` (`.default({})`); output `{invites}`.
521
556
  - `invite_delete_action_spec` — mutation; input `{invite_id}`; output `{ok}`.
522
- - `app_settings_get_action_spec` — read; input `z.void()`; output `{settings}`.
557
+ - `app_settings_get_action_spec` — read; input `{acting?}` (`.default({})`); output `{settings}`.
523
558
  - `app_settings_update_action_spec` — mutation; input `{open_signup}`; output `{ok, settings}`.
524
559
 
525
560
  Constants: `AUDIT_LOG_LIST_LIMIT_MAX = 200`, `ADMIN_ACCOUNT_LIST_DEFAULT_LIMIT = 50`,
@@ -1 +1 @@
1
- {"version":3,"file":"account_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAsC,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC9F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAqBxE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAwBnD,4CAA4C;AAC5C,MAAM,WAAW,oBAAoB;IACpC;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B;;;;;;;;;;OAUG;IACH,iBAAiB,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;CAC5C;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,iBAAiB,EACvB,UAAS,oBAAyB,KAChC,KAAK,CAAC,SAAS,CAgKjB,CAAC"}
1
+ {"version":3,"file":"account_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAsC,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC9F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAqBxE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAyBnD,4CAA4C;AAC5C,MAAM,WAAW,oBAAoB;IACpC;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B;;;;;;;;;;OAUG;IACH,iBAAiB,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;CAC5C;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,iBAAiB,EACvB,UAAS,oBAAyB,KAChC,KAAK,CAAC,SAAS,CAwKjB,CAAC"}
@@ -30,6 +30,7 @@ import { parse_stored_token_scope, token_scope_from_input, token_scope_label } f
30
30
  import { token_lifetime_to_expires_at } from './token_lifetime.js';
31
31
  import { generate_api_token } from './api_token.js';
32
32
  import { DEFAULT_MAX_TOKENS } from './account_route_schema.js';
33
+ import { to_iso8601_seconds } from '../timestamp.js';
33
34
  import { account_verify_action_spec, account_session_list_action_spec, account_session_revoke_action_spec, account_session_revoke_all_action_spec, account_token_create_action_spec, account_token_list_action_spec, account_token_revoke_action_spec } from './account_action_specs.js';
34
35
  /**
35
36
  * Create the self-service account RPC actions.
@@ -120,7 +121,15 @@ export const create_account_actions = (deps, options = {}) => {
120
121
  credential_type: ctx.credential_type ?? undefined
121
122
  }
122
123
  });
123
- return { ok: true, token, id, name: input.name, expires_at: expires_at?.toISOString() ?? null };
124
+ return {
125
+ ok: true,
126
+ token,
127
+ id,
128
+ name: input.name,
129
+ // the canonical wire shape, matching what `token_list` reads back
130
+ // through `api_token`'s ISO-8601 projection
131
+ expires_at: expires_at === null ? null : to_iso8601_seconds(expires_at)
132
+ };
124
133
  };
125
134
  const token_list_handler = async (_input, ctx) => {
126
135
  const rows = await query_api_token_list_for_account(ctx, ctx.auth.account.id);
@@ -1 +1 @@
1
- {"version":3,"file":"account_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_queries.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"account_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAQrD,OAAO,EAEN,KAAK,OAAO,EACZ,KAAK,KAAK,EACV,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,MAAM,qBAAqB,CAAC;AAI7B;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,eAAe,6JAYqB,CAAC;AAElD;;;;GAIG;AACH,eAAO,MAAM,aAAa,6GASqB,CAAC;AAehD;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,SAAS,EACf,OAAO,kBAAkB,KACvB,OAAO,CAAC,OAAO,CAQjB,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,GAC/B,MAAM,SAAS,EACf,IAAI,MAAM,KACR,OAAO,CAAC,OAAO,GAAG,SAAS,CAK7B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,yBAAyB,GACrC,MAAM,SAAS,EACf,UAAU,MAAM,KACd,OAAO,CAAC,OAAO,GAAG,SAAS,CAK7B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,SAAS,EACf,OAAO,MAAM,KACX,OAAO,CAAC,OAAO,GAAG,SAAS,CAK7B,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,kCAAkC,GAC9C,MAAM,SAAS,EACf,OAAO,MAAM,KACX,OAAO,CAAC,OAAO,GAAG,SAAS,CAS7B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,IAAI,MAAM,EACV,eAAe,MAAM,EACrB,YAAY,MAAM,GAAG,IAAI,EACzB,eAAe,MAAM,KACnB,OAAO,CAAC,OAAO,CAQjB,CAAC;AAEF;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,yBAAyB,GACrC,MAAM,SAAS,EACf,IAAI,MAAM,EACV,YAAY,MAAM,GAAG,IAAI,KACvB,OAAO,CAAC,uBAAuB,GAAG,SAAS,CAO7C,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,mBAAmB,GAC/B,MAAM,SAAS,EACf,IAAI,MAAM,KACR,OAAO,CAAC,uBAAuB,GAAG,SAAS,CAK7C,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,SAAS,EACf,IAAI,MAAM,EACV,YAAY,MAAM,GAAG,IAAI,KACvB,OAAO,CAAC,OAAO,CAQjB,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,SAAS,EACf,IAAI,MAAM,KACR,OAAO,CAAC,uBAAuB,GAAG,SAAS,CAO7C,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAAU,MAAM,SAAS,EAAE,IAAI,MAAM,KAAG,OAAO,CAAC,OAAO,CAQvF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,qBAAqB,GAAU,MAAM,SAAS,KAAG,OAAO,CAAC,OAAO,CAK5E,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,GAC9B,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,MAAM,MAAM,KACV,OAAO,CAAC,KAAK,CAMf,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAKtB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAKtB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,iBAAiB,GAC7B,MAAM,SAAS,EACf,IAAI,MAAM,KACR,OAAO,CAAC,KAAK,GAAG,SAAS,CAK3B,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,+BAA+B,GAC3C,MAAM,SAAS,EACf,OAAO,kBAAkB,KACvB,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,KAAK,CAAA;CAAE,CAI5C,CAAC;AAgDF,8CAA8C;AAC9C,MAAM,WAAW,uBAAuB;IACvC;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,wCAAwC;IACxC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CACjC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,wBAAwB,GACpC,MAAM,SAAS,EACf,UAAU,uBAAuB,KAC/B,OAAO,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAoHtC,CAAC"}
@@ -7,7 +7,7 @@
7
7
  * @module
8
8
  */
9
9
  import { assert_row } from '../db/assert_row.js';
10
- import { columns_sql, omit_columns } from '../db/sql_columns.js';
10
+ import { columns_sql, iso8601_timestamp_column, iso8601_timestamp_expr, omit_columns } from '../db/sql_columns.js';
11
11
  import { to_admin_account } from './account_schema.js';
12
12
  import { ADMIN_ACCOUNT_LIST_DEFAULT_LIMIT } from './admin_action_specs.js';
13
13
  import { ROLE_GRANT_COLUMNS } from './role_grant_queries.js';
@@ -54,6 +54,18 @@ export const ACTOR_COLUMNS = [
54
54
  'deleted_at',
55
55
  'deleted_by'
56
56
  ];
57
+ /**
58
+ * The timestamp override both tables this module owns share (`ACTOR_COLUMNS`
59
+ * carries the same triple); every read here is on the bare table. Actor
60
+ * timestamps never reach the wire (the summaries carry `{id, name}` only —
61
+ * see `actor_lookup_action_specs.ts` on timing-oracle avoidance), but `Actor`
62
+ * declares them as strings, so the projection makes the row honest there too.
63
+ */
64
+ const account_actor_expr = iso8601_timestamp_expr(ACCOUNT_COLUMNS, [
65
+ 'created_at',
66
+ 'updated_at',
67
+ 'deleted_at'
68
+ ])('');
57
69
  /**
58
70
  * Create a new account.
59
71
  *
@@ -65,7 +77,7 @@ export const ACTOR_COLUMNS = [
65
77
  export const query_create_account = async (deps, input) => {
66
78
  const row = await deps.db.query_one(`INSERT INTO account (username, password_hash, email)
67
79
  VALUES ($1, $2, $3)
68
- RETURNING ${columns_sql(ACCOUNT_COLUMNS)}`, [input.username, input.password_hash, input.email ?? null]);
80
+ RETURNING ${columns_sql(ACCOUNT_COLUMNS, account_actor_expr)}`, [input.username, input.password_hash, input.email ?? null]);
69
81
  return assert_row(row, 'INSERT INTO account');
70
82
  };
71
83
  /**
@@ -78,19 +90,19 @@ export const query_create_account = async (deps, input) => {
78
90
  * soft-deleted rows too, uses `query_purge_account` directly.
79
91
  */
80
92
  export const query_account_by_id = async (deps, id) => {
81
- return deps.db.query_one(`SELECT ${columns_sql(ACCOUNT_COLUMNS)} FROM account WHERE id = $1 AND deleted_at IS NULL`, [id]);
93
+ return deps.db.query_one(`SELECT ${columns_sql(ACCOUNT_COLUMNS, account_actor_expr)} FROM account WHERE id = $1 AND deleted_at IS NULL`, [id]);
82
94
  };
83
95
  /**
84
96
  * Find an account by username (case-insensitive).
85
97
  */
86
98
  export const query_account_by_username = async (deps, username) => {
87
- return deps.db.query_one(`SELECT ${columns_sql(ACCOUNT_COLUMNS)} FROM account WHERE LOWER(username) = LOWER($1)`, [username]);
99
+ return deps.db.query_one(`SELECT ${columns_sql(ACCOUNT_COLUMNS, account_actor_expr)} FROM account WHERE LOWER(username) = LOWER($1)`, [username]);
88
100
  };
89
101
  /**
90
102
  * Find an account by email (case-insensitive).
91
103
  */
92
104
  export const query_account_by_email = async (deps, email) => {
93
- return deps.db.query_one(`SELECT ${columns_sql(ACCOUNT_COLUMNS)} FROM account WHERE LOWER(email) = LOWER($1)`, [email]);
105
+ return deps.db.query_one(`SELECT ${columns_sql(ACCOUNT_COLUMNS, account_actor_expr)} FROM account WHERE LOWER(email) = LOWER($1)`, [email]);
94
106
  };
95
107
  /**
96
108
  * Find an account by username or email.
@@ -243,7 +255,7 @@ export const query_account_has_any = async (deps) => {
243
255
  * @mutates `actor` table - inserts the new row
244
256
  */
245
257
  export const query_create_actor = async (deps, account_id, name) => {
246
- const row = await deps.db.query_one(`INSERT INTO actor (account_id, name) VALUES ($1, $2) RETURNING ${columns_sql(ACTOR_COLUMNS)}`, [account_id, name]);
258
+ const row = await deps.db.query_one(`INSERT INTO actor (account_id, name) VALUES ($1, $2) RETURNING ${columns_sql(ACTOR_COLUMNS, account_actor_expr)}`, [account_id, name]);
247
259
  return assert_row(row, 'INSERT INTO actor');
248
260
  };
249
261
  /**
@@ -258,7 +270,7 @@ export const query_create_actor = async (deps, account_id, name) => {
258
270
  * `query_actor_by_id` instead.
259
271
  */
260
272
  export const query_actors_by_account = async (deps, account_id) => {
261
- return deps.db.query(`SELECT ${columns_sql(ACTOR_COLUMNS)} FROM actor WHERE account_id = $1 ORDER BY created_at ASC, id ASC`, [account_id]);
273
+ return deps.db.query(`SELECT ${columns_sql(ACTOR_COLUMNS, account_actor_expr)} FROM actor WHERE account_id = $1 ORDER BY actor.created_at ASC, actor.id ASC`, [account_id]);
262
274
  };
263
275
  /**
264
276
  * List **active** (non-tombstoned) actors on an account, ordered by
@@ -272,15 +284,13 @@ export const query_actors_by_account = async (deps, account_id) => {
272
284
  * tombstoned rows stay on the unfiltered `query_actors_by_account`.
273
285
  */
274
286
  export const query_active_actors_by_account = async (deps, account_id) => {
275
- return deps.db.query(`SELECT ${columns_sql(ACTOR_COLUMNS)} FROM actor WHERE account_id = $1 AND deleted_at IS NULL ORDER BY created_at ASC, id ASC`, [account_id]);
287
+ return deps.db.query(`SELECT ${columns_sql(ACTOR_COLUMNS, account_actor_expr)} FROM actor WHERE account_id = $1 AND deleted_at IS NULL ORDER BY actor.created_at ASC, actor.id ASC`, [account_id]);
276
288
  };
277
289
  /**
278
290
  * Find an actor by id.
279
291
  */
280
292
  export const query_actor_by_id = async (deps, id) => {
281
- return deps.db.query_one(`SELECT ${columns_sql(ACTOR_COLUMNS)} FROM actor WHERE id = $1`, [
282
- id
283
- ]);
293
+ return deps.db.query_one(`SELECT ${columns_sql(ACTOR_COLUMNS, account_actor_expr)} FROM actor WHERE id = $1`, [id]);
284
294
  };
285
295
  /**
286
296
  * Create an account and its actor in a single operation.
@@ -302,6 +312,14 @@ export const query_create_account_with_actor = async (deps, input) => {
302
312
  * active-grant summary the admin listing projects (`RoleGrantWithActorId`).
303
313
  */
304
314
  const ADMIN_ROLE_GRANT_COLUMNS = omit_columns(ROLE_GRANT_COLUMNS, 'revoked_at', 'revoked_by', 'revoked_reason', 'source_offer_id');
315
+ /**
316
+ * The admin listing's active-grant summary timestamps — twin of the spine's
317
+ * `role_grant_summary_columns`. The read is on the bare `role_grant` table.
318
+ */
319
+ const admin_role_grant_expr = iso8601_timestamp_expr(ADMIN_ROLE_GRANT_COLUMNS, [
320
+ 'created_at',
321
+ 'expires_at'
322
+ ])('');
305
323
  /**
306
324
  * List accounts with their actors, active role_grants, and pending inbound
307
325
  * role_grant offers for admin display.
@@ -327,21 +345,23 @@ export const query_admin_account_list = async (deps, options) => {
327
345
  // `include_deleted` to surface tombstoned rows for reactivation.
328
346
  const where = options?.include_deleted ? '' : 'WHERE deleted_at IS NULL';
329
347
  const account_query = limit == null
330
- ? deps.db.query(`SELECT ${columns_sql(ACCOUNT_COLUMNS)} FROM account ${where} ORDER BY created_at OFFSET $1`, [offset])
331
- : deps.db.query(`SELECT ${columns_sql(ACCOUNT_COLUMNS)} FROM account ${where} ORDER BY created_at LIMIT $1 OFFSET $2`, [limit, offset]);
348
+ ? deps.db.query(`SELECT ${columns_sql(ACCOUNT_COLUMNS, account_actor_expr)} FROM account ${where} ORDER BY account.created_at OFFSET $1`, [offset])
349
+ : deps.db.query(`SELECT ${columns_sql(ACCOUNT_COLUMNS, account_actor_expr)} FROM account ${where} ORDER BY account.created_at LIMIT $1 OFFSET $2`, [limit, offset]);
332
350
  const accounts = await account_query;
333
351
  if (accounts.length === 0)
334
352
  return [];
335
353
  const account_ids = accounts.map((a) => a.id);
336
354
  const [actors, role_grants, pending_offers] = await Promise.all([
337
- deps.db.query(`SELECT ${columns_sql(ACTOR_COLUMNS)} FROM actor WHERE account_id = ANY($1::uuid[])`, [account_ids]),
338
- deps.db.query(`SELECT ${columns_sql(ADMIN_ROLE_GRANT_COLUMNS)}
355
+ deps.db.query(`SELECT ${columns_sql(ACTOR_COLUMNS, account_actor_expr)} FROM actor WHERE account_id = ANY($1::uuid[])`, [account_ids]),
356
+ deps.db.query(`SELECT ${columns_sql(ADMIN_ROLE_GRANT_COLUMNS, admin_role_grant_expr)}
339
357
  FROM role_grant
340
358
  WHERE actor_id IN (SELECT id FROM actor WHERE account_id = ANY($1::uuid[]))
341
359
  AND revoked_at IS NULL
342
360
  AND (expires_at IS NULL OR expires_at > NOW())`, [account_ids]),
343
361
  deps.db.query(`SELECT po.id, po.to_account_id, po.from_actor_id, po.role, po.scope_kind, po.scope_id,
344
- po.created_at, po.expires_at, a.username AS from_username
362
+ ${iso8601_timestamp_column('po', 'created_at')} AS created_at,
363
+ ${iso8601_timestamp_column('po', 'expires_at')} AS expires_at,
364
+ a.username AS from_username
345
365
  FROM role_grant_offer po
346
366
  JOIN actor act ON act.id = po.from_actor_id
347
367
  JOIN account a ON a.id = act.account_id
@@ -97,6 +97,26 @@ export interface RoleGrant {
97
97
  /** Offer that produced this role_grant (set by `query_accept_offer`). `null` for direct grants. */
98
98
  source_offer_id: Uuid | null;
99
99
  }
100
+ /**
101
+ * Whether a role grant is live, from its wire fields — not revoked, and not
102
+ * past `expires_at`.
103
+ *
104
+ * Server-side this is belt-and-suspenders: every `role_grant` a request
105
+ * context holds came through `query_role_grant_find_active_for_actor`, whose
106
+ * `revoked_at IS NULL AND (expires_at IS NULL OR expires_at > NOW())` is the
107
+ * authoritative gate on both spines (the Rust twin's in-memory
108
+ * `RoleGrantRow` carries no timestamps at all and re-checks nothing).
109
+ *
110
+ * `expires_at` arrives second-truncated, so the expiry check is
111
+ * `is_iso8601_seconds_live` (`timestamp.ts`): it admits through the end of
112
+ * the second the stamp names, so the predicate can never report a grant
113
+ * expired *before* the SQL gate would — a bare `expires_at > now` would deny
114
+ * an admitted grant for up to a second.
115
+ *
116
+ * @param p - the grant's revocation + expiry fields
117
+ * @param now - current time (defaults to `new Date()`, pass for testability and hot-path efficiency)
118
+ * @returns `true` when the grant is neither revoked nor expired
119
+ */
100
120
  export declare const is_role_grant_active: (p: {
101
121
  revoked_at?: string | null;
102
122
  expires_at: string | null;
@@ -1 +1 @@
1
- {"version":3,"file":"account_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_schema.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"account_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_schema.ts"],"names":[],"mappings":"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"}
@@ -20,6 +20,7 @@ import { z } from 'zod';
20
20
  import { Uuid } from '@fuzdev/fuz_util/id.ts';
21
21
  import { Blake3Hash } from '@fuzdev/fuz_util/hash_schemas.ts';
22
22
  import { Username, Email } from '../primitive_schemas.js';
23
+ import { is_iso8601_seconds_live } from '../timestamp.js';
23
24
  /**
24
25
  * Maximum length of the optional free-form `revoked_reason` attached to a
25
26
  * revoked role_grant. Bounds the value at the schema layer so both the admin
@@ -27,7 +28,27 @@ import { Username, Email } from '../primitive_schemas.js';
27
28
  * `role_grant_revoke` WS notification validate against the same ceiling.
28
29
  */
29
30
  export const ROLE_GRANT_REVOKED_REASON_LENGTH_MAX = 500;
30
- export const is_role_grant_active = (p, now = new Date()) => !p.revoked_at && (!p.expires_at || new Date(p.expires_at) > now);
31
+ /**
32
+ * Whether a role grant is live, from its wire fields — not revoked, and not
33
+ * past `expires_at`.
34
+ *
35
+ * Server-side this is belt-and-suspenders: every `role_grant` a request
36
+ * context holds came through `query_role_grant_find_active_for_actor`, whose
37
+ * `revoked_at IS NULL AND (expires_at IS NULL OR expires_at > NOW())` is the
38
+ * authoritative gate on both spines (the Rust twin's in-memory
39
+ * `RoleGrantRow` carries no timestamps at all and re-checks nothing).
40
+ *
41
+ * `expires_at` arrives second-truncated, so the expiry check is
42
+ * `is_iso8601_seconds_live` (`timestamp.ts`): it admits through the end of
43
+ * the second the stamp names, so the predicate can never report a grant
44
+ * expired *before* the SQL gate would — a bare `expires_at > now` would deny
45
+ * an admitted grant for up to a second.
46
+ *
47
+ * @param p - the grant's revocation + expiry fields
48
+ * @param now - current time (defaults to `new Date()`, pass for testability and hot-path efficiency)
49
+ * @returns `true` when the grant is neither revoked nor expired
50
+ */
51
+ export const is_role_grant_active = (p, now = new Date()) => !p.revoked_at && (!p.expires_at || is_iso8601_seconds_live(p.expires_at, now.getTime()));
31
52
  /**
32
53
  * A session's storage key — the blake3 hash of its raw token, branded so a
33
54
  * bare `string` can't stand in for one. `Blake3Hash` (`hash_schemas.ts`) is
@@ -11,9 +11,9 @@
11
11
  * - **Admin gate on empty `scope_ids`** — unbounded global search is
12
12
  * admin-only. Non-admin callers without a `scope_ids` filter are
13
13
  * rejected with `invalid_params` carrying `actor_search_scope_required`.
14
- * The admin check is account-grain (any actor on the caller's account
15
- * holds a global `admin` role_grant) since the `actor: 'none'` posture
16
- * doesn't load `auth.role_grants` for an in-memory check.
14
+ * The admin check is account-grain (any **active** actor on the caller's
15
+ * account holds a global `admin` role_grant) since the `actor: 'none'`
16
+ * posture doesn't load `auth.role_grants` for an in-memory check.
17
17
  * - **Limit clamp** — input is bounded by `ACTOR_SEARCH_LIMIT_MAX` at
18
18
  * the schema; the handler picks the default when omitted.
19
19
  *
@@ -1 +1 @@
1
- {"version":3,"file":"actor_search_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/actor_search_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAGtD,OAAO,EAAsC,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAc9F,sDAAsD;AACtD,MAAM,WAAW,qBAAqB;IACrC,GAAG,EAAE,MAAM,CAAC;CACZ;AAED,eAAO,MAAM,2BAA2B,GAAI,OAAO,qBAAqB,KAAG,KAAK,CAAC,SAAS,CAiCzF,CAAC"}
1
+ {"version":3,"file":"actor_search_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/actor_search_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAGtD,OAAO,EAAsC,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAc9F,sDAAsD;AACtD,MAAM,WAAW,qBAAqB;IACrC,GAAG,EAAE,MAAM,CAAC;CACZ;AAED,eAAO,MAAM,2BAA2B,GAAI,OAAO,qBAAqB,KAAG,KAAK,CAAC,SAAS,CAyCzF,CAAC"}
@@ -11,9 +11,9 @@
11
11
  * - **Admin gate on empty `scope_ids`** — unbounded global search is
12
12
  * admin-only. Non-admin callers without a `scope_ids` filter are
13
13
  * rejected with `invalid_params` carrying `actor_search_scope_required`.
14
- * The admin check is account-grain (any actor on the caller's account
15
- * holds a global `admin` role_grant) since the `actor: 'none'` posture
16
- * doesn't load `auth.role_grants` for an in-memory check.
14
+ * The admin check is account-grain (any **active** actor on the caller's
15
+ * account holds a global `admin` role_grant) since the `actor: 'none'`
16
+ * posture doesn't load `auth.role_grants` for an in-memory check.
17
17
  * - **Limit clamp** — input is bounded by `ACTOR_SEARCH_LIMIT_MAX` at
18
18
  * the schema; the handler picks the default when omitted.
19
19
  *
@@ -26,15 +26,19 @@
26
26
  import { jsonrpc_errors } from '../http/jsonrpc_errors.js';
27
27
  import { rpc_action } from '../actions/action_rpc.js';
28
28
  import { query_actor_search } from './actor_search_queries.js';
29
- import { query_account_has_global_role } from './role_grant_queries.js';
29
+ import { query_account_has_active_global_role } from './role_grant_queries.js';
30
30
  import { ROLE_ADMIN } from './role_schema.js';
31
31
  import { ACTOR_SEARCH_LIMIT_DEFAULT, ERROR_ACTOR_SEARCH_SCOPE_REQUIRED, actor_search_action_spec } from './actor_search_action_specs.js';
32
32
  export const create_actor_search_actions = (_deps) => {
33
33
  const handler = async (input, ctx) => {
34
34
  if (!input.scope_ids || input.scope_ids.length === 0) {
35
35
  // Unbounded global search is admin-only. Account-grain admin
36
- // check — any actor on the caller's account holds the role.
37
- const is_admin = await query_account_has_global_role(ctx, ctx.auth.account.id, ROLE_ADMIN);
36
+ // check — any *active* actor on the caller's account holds the
37
+ // role. The tombstone-aware query is what every admission gate
38
+ // reads: soft-delete leaves `role_grant` rows intact (undelete
39
+ // restores them), so the blind sibling would admit an account
40
+ // whose only admin grant hangs off a tombstoned actor.
41
+ const is_admin = await query_account_has_active_global_role(ctx, ctx.auth.account.id, ROLE_ADMIN);
38
42
  if (!is_admin) {
39
43
  throw jsonrpc_errors.invalid_params('scope_ids required for non-admin callers', {
40
44
  reason: ERROR_ACTOR_SEARCH_SCOPE_REQUIRED
@@ -1 +1 @@
1
- {"version":3,"file":"api_token_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/api_token_queries.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGrD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,EAAE,KAAK,UAAU,EAAmD,MAAM,kBAAkB,CAAC;AAEpG,yEAAyE;AACzE,MAAM,WAAW,iBAAkB,SAAQ,SAAS;IACnD,GAAG,EAAE,MAAM,CAAC;CACZ;AAED;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,0HAUoB,CAAC;AASnD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,SAAS,EACf,IAAI,MAAM,EACV,YAAY,MAAM,EAClB,MAAM,MAAM,EACZ,YAAY,MAAM,EAClB,OAAO,UAAU,EACjB,aAAa,IAAI,GAAG,IAAI,KACtB,OAAO,CAAC,QAAQ,CAelB,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,wBAAwB,GACpC,MAAM,iBAAiB,EACvB,WAAW,MAAM,EACjB,IAAI,MAAM,GAAG,SAAS,EACtB,iBAAiB,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,KAC/C,OAAO,CAAC,QAAQ,GAAG,SAAS,CAgC9B,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,uCAAuC,GACnD,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,MAAM,CAMhB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kCAAkC,GAC9C,MAAM,SAAS,EACf,IAAI,MAAM,EACV,YAAY,MAAM,KAChB,OAAO,CAAC,OAAO,CAMjB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,CAM7C,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,YAAY,MAAM,KAChB,OAAO,CAAC,MAAM,CAYhB,CAAC"}
1
+ {"version":3,"file":"api_token_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/api_token_queries.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGrD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,EAAE,KAAK,UAAU,EAAmD,MAAM,kBAAkB,CAAC;AAEpG,yEAAyE;AACzE,MAAM,WAAW,iBAAkB,SAAQ,SAAS;IACnD,GAAG,EAAE,MAAM,CAAC;CACZ;AAED;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,0HAUoB,CAAC;AAgBnD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,SAAS,EACf,IAAI,MAAM,EACV,YAAY,MAAM,EAClB,MAAM,MAAM,EACZ,YAAY,MAAM,EAClB,OAAO,UAAU,EACjB,aAAa,IAAI,GAAG,IAAI,KACtB,OAAO,CAAC,QAAQ,CAelB,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,wBAAwB,GACpC,MAAM,iBAAiB,EACvB,WAAW,MAAM,EACjB,IAAI,MAAM,GAAG,SAAS,EACtB,iBAAiB,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,KAC/C,OAAO,CAAC,QAAQ,GAAG,SAAS,CAgC9B,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,uCAAuC,GACnD,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,MAAM,CAMhB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kCAAkC,GAC9C,MAAM,SAAS,EACf,IAAI,MAAM,EACV,YAAY,MAAM,KAChB,OAAO,CAAC,OAAO,CAMjB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,CAM7C,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,YAAY,MAAM,KAChB,OAAO,CAAC,MAAM,CAYhB,CAAC"}
@@ -4,7 +4,7 @@
4
4
  * @module
5
5
  */
6
6
  import { assert_row } from '../db/assert_row.js';
7
- import { columns_sql, omit_columns } from '../db/sql_columns.js';
7
+ import { columns_sql, iso8601_timestamp_expr, omit_columns } from '../db/sql_columns.js';
8
8
  import { hash_api_token } from './api_token.js';
9
9
  import { parse_stored_token_scope, serialize_token_scope } from './token_scope.js';
10
10
  /**
@@ -30,6 +30,12 @@ export const API_TOKEN_COLUMNS = [
30
30
  * while the hash stays off it by construction.
31
31
  */
32
32
  const API_TOKEN_CLIENT_COLUMNS = omit_columns(API_TOKEN_COLUMNS, 'token_hash');
33
+ /** The `api_token` timestamp override; every read here is on the bare table. */
34
+ const api_token_expr = iso8601_timestamp_expr(API_TOKEN_COLUMNS, [
35
+ 'expires_at',
36
+ 'last_used_at',
37
+ 'created_at'
38
+ ])('');
33
39
  /**
34
40
  * Store a new API token (the hash, not the raw token).
35
41
  *
@@ -45,7 +51,7 @@ const API_TOKEN_CLIENT_COLUMNS = omit_columns(API_TOKEN_COLUMNS, 'token_hash');
45
51
  export const query_create_api_token = async (deps, id, account_id, name, token_hash, scope, expires_at) => {
46
52
  const row = await deps.db.query_one(`INSERT INTO api_token (id, account_id, name, token_hash, scope, expires_at)
47
53
  VALUES ($1, $2, $3, $4, $5::jsonb, $6)
48
- RETURNING ${columns_sql(API_TOKEN_COLUMNS)}`, [
54
+ RETURNING ${columns_sql(API_TOKEN_COLUMNS, api_token_expr)}`, [
49
55
  id,
50
56
  account_id,
51
57
  name,
@@ -72,7 +78,7 @@ export const query_create_api_token = async (deps, id, account_id, name, token_h
72
78
  */
73
79
  export const query_validate_api_token = async (deps, raw_token, ip, pending_effects) => {
74
80
  const token_hash = hash_api_token(raw_token);
75
- const row = await deps.db.query_one(`SELECT ${columns_sql(API_TOKEN_COLUMNS)} FROM api_token
81
+ const row = await deps.db.query_one(`SELECT ${columns_sql(API_TOKEN_COLUMNS, api_token_expr)} FROM api_token
76
82
  WHERE token_hash = $1
77
83
  AND (expires_at IS NULL OR expires_at > NOW())`, [token_hash]);
78
84
  if (!row)
@@ -130,8 +136,8 @@ export const query_revoke_api_token_for_account = async (deps, id, account_id) =
130
136
  * so `token_hash` never rides the listing.
131
137
  */
132
138
  export const query_api_token_list_for_account = async (deps, account_id) => {
133
- return deps.db.query(`SELECT ${columns_sql(API_TOKEN_CLIENT_COLUMNS)}
134
- FROM api_token WHERE account_id = $1 ORDER BY created_at DESC`, [account_id]);
139
+ return deps.db.query(`SELECT ${columns_sql(API_TOKEN_CLIENT_COLUMNS, api_token_expr)}
140
+ FROM api_token WHERE account_id = $1 ORDER BY api_token.created_at DESC`, [account_id]);
135
141
  };
136
142
  /**
137
143
  * Enforce a per-account token limit by evicting the oldest tokens.
@@ -1 +1 @@
1
- {"version":3,"file":"app_settings_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/app_settings_queries.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,KAAK,EAAE,WAAW,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAEzF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,4DAA6D,CAAC;AAK/F;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB,GAAU,MAAM,SAAS,KAAG,OAAO,CAAC,WAAW,CAQlF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,qCAAqC,GACjD,MAAM,SAAS,KACb,OAAO,CAAC,2BAA2B,CAWrC,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,yBAAyB,GACrC,MAAM,SAAS,EACf,aAAa,OAAO,EACpB,UAAU,MAAM,KACd,OAAO,CAAC,WAAW,CASrB,CAAC"}
1
+ {"version":3,"file":"app_settings_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/app_settings_queries.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAOrD,OAAO,KAAK,EAAE,WAAW,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAEzF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,4DAA6D,CAAC;AAQ/F;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB,GAAU,MAAM,SAAS,KAAG,OAAO,CAAC,WAAW,CAQlF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,qCAAqC,GACjD,MAAM,SAAS,KACb,OAAO,CAAC,2BAA2B,CAWrC,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,yBAAyB,GACrC,MAAM,SAAS,EACf,aAAa,OAAO,EACpB,UAAU,MAAM,KACd,OAAO,CAAC,WAAW,CASrB,CAAC"}
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * @module
7
7
  */
8
- import { columns_sql, omit_columns, qualify_columns } from '../db/sql_columns.js';
8
+ import { columns_sql, iso8601_timestamp_expr, omit_columns, qualify_columns } from '../db/sql_columns.js';
9
9
  /**
10
10
  * The full `app_settings` column set — the singleton `id` (always `1`) plus
11
11
  * the settings the row type carries — drift-guarded like every other
@@ -16,6 +16,8 @@ import { columns_sql, omit_columns, qualify_columns } from '../db/sql_columns.js
16
16
  export const APP_SETTINGS_COLUMNS = ['id', 'open_signup', 'updated_at', 'updated_by'];
17
17
  /** `APP_SETTINGS_COLUMNS` minus the constant singleton `id` — the `AppSettings` row projection. */
18
18
  const APP_SETTINGS_ROW_COLUMNS = omit_columns(APP_SETTINGS_COLUMNS, 'id');
19
+ /** The `app_settings` timestamp override, by row qualifier (`''` bare, `s` for the username JOIN). */
20
+ const app_settings_expr = iso8601_timestamp_expr(APP_SETTINGS_ROW_COLUMNS, ['updated_at']);
19
21
  /**
20
22
  * Load the current app settings.
21
23
  *
@@ -24,7 +26,7 @@ const APP_SETTINGS_ROW_COLUMNS = omit_columns(APP_SETTINGS_COLUMNS, 'id');
24
26
  * @throws Error if the singleton `app_settings` row is missing (migration drift — should not occur in practice)
25
27
  */
26
28
  export const query_app_settings_load = async (deps) => {
27
- const row = await deps.db.query_one(`SELECT ${columns_sql(APP_SETTINGS_ROW_COLUMNS)} FROM app_settings WHERE id = 1`);
29
+ const row = await deps.db.query_one(`SELECT ${columns_sql(APP_SETTINGS_ROW_COLUMNS, app_settings_expr(''))} FROM app_settings WHERE id = 1`);
28
30
  if (!row) {
29
31
  throw new Error('app_settings row not found — migration may not have run');
30
32
  }
@@ -38,7 +40,7 @@ export const query_app_settings_load = async (deps) => {
38
40
  * @throws Error if the singleton `app_settings` row is missing
39
41
  */
40
42
  export const query_app_settings_load_with_username = async (deps) => {
41
- const row = await deps.db.query_one(`SELECT ${qualify_columns(APP_SETTINGS_ROW_COLUMNS, 's')}, act.name AS updated_by_username
43
+ const row = await deps.db.query_one(`SELECT ${qualify_columns(APP_SETTINGS_ROW_COLUMNS, 's', app_settings_expr('s'))}, act.name AS updated_by_username
42
44
  FROM app_settings s
43
45
  LEFT JOIN actor act ON act.id = s.updated_by
44
46
  WHERE s.id = 1`);
@@ -58,7 +60,7 @@ export const query_app_settings_load_with_username = async (deps) => {
58
60
  * @throws Error if the singleton `app_settings` row is missing
59
61
  */
60
62
  export const query_app_settings_update = async (deps, open_signup, actor_id) => {
61
- const row = await deps.db.query_one(`UPDATE app_settings SET open_signup = $1, updated_at = NOW(), updated_by = $2 WHERE id = 1 RETURNING ${columns_sql(APP_SETTINGS_ROW_COLUMNS)}`, [open_signup, actor_id]);
63
+ const row = await deps.db.query_one(`UPDATE app_settings SET open_signup = $1, updated_at = NOW(), updated_by = $2 WHERE id = 1 RETURNING ${columns_sql(APP_SETTINGS_ROW_COLUMNS, app_settings_expr(''))}`, [open_signup, actor_id]);
62
64
  if (!row) {
63
65
  throw new Error('app_settings row not found — migration may not have run');
64
66
  }
@@ -24,6 +24,12 @@ import { type AuditLogConfig, type AuditLogEvent, type AuditLogInput, type Audit
24
24
  * per-cell timeline read in `db/cell_audit_queries.ts`.
25
25
  */
26
26
  export declare const AUDIT_LOG_COLUMNS: readonly ["id", "seq", "event_type", "outcome", "actor_id", "account_id", "target_account_id", "target_actor_id", "ip", "created_at", "metadata"];
27
+ /**
28
+ * The `audit_log` timestamp override, by row qualifier (`''` bare, `al` for
29
+ * the username JOINs). Exported for the per-cell timeline read in
30
+ * `db/cell_audit_queries.ts`, which projects the same table.
31
+ */
32
+ export declare const audit_log_expr: (alias: string) => import("../db/sql_columns.js").ColumnExpr;
27
33
  /** Number of audit metadata validation failures observed since process start. */
28
34
  export declare const get_audit_metadata_validation_failures: () => number;
29
35
  /** Reset the counter — for tests only. */
@@ -1 +1 @@
1
- {"version":3,"file":"audit_log_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGrD,OAAO,EAGN,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,8BAA8B,EACnC,KAAK,yBAAyB,EAC9B,MAAM,uBAAuB,CAAC;AAE/B;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,mJAYyB,CAAC;AAaxD,iFAAiF;AACjF,eAAO,MAAM,sCAAsC,QAAO,MACvB,CAAC;AAEpC,0CAA0C;AAC1C,eAAO,MAAM,wCAAwC,QAAO,IAE3D,CAAC;AAYF,gFAAgF;AAChF,eAAO,MAAM,qCAAqC,QAAO,MACvB,CAAC;AAEnC,0CAA0C;AAC1C,eAAO,MAAM,uCAAuC,QAAO,IAE1D,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,eAAe,GAAU,CAAC,SAAS,MAAM,EACrD,MAAM,SAAS,EACf,OAAO,aAAa,CAAC,CAAC,CAAC,EACvB,SAAQ,cAAyC,KAC/C,OAAO,CAAC,aAAa,CAsCvB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,SAAS,EACf,UAAU,mBAAmB,KAC3B,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,CA0C9B,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,mCAAmC,GAC/C,MAAM,SAAS,EACf,UAAU,mBAAmB,KAC3B,OAAO,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAwD/C,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,uCAAuC,GACnD,MAAM,SAAS,EACf,cAA+B,EAC/B,eAAU,KACR,OAAO,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAmB1C,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,SAAS,EACf,QAAQ,IAAI,KACV,OAAO,CAAC,MAAM,CAMhB,CAAC"}
1
+ {"version":3,"file":"audit_log_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGrD,OAAO,EAGN,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,8BAA8B,EACnC,KAAK,yBAAyB,EAC9B,MAAM,uBAAuB,CAAC;AAE/B;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,mJAYyB,CAAC;AAExD;;;;GAIG;AACH,eAAO,MAAM,cAAc,8DAA4D,CAAC;AAaxF,iFAAiF;AACjF,eAAO,MAAM,sCAAsC,QAAO,MACvB,CAAC;AAEpC,0CAA0C;AAC1C,eAAO,MAAM,wCAAwC,QAAO,IAE3D,CAAC;AAYF,gFAAgF;AAChF,eAAO,MAAM,qCAAqC,QAAO,MACvB,CAAC;AAEnC,0CAA0C;AAC1C,eAAO,MAAM,uCAAuC,QAAO,IAE1D,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,eAAe,GAAU,CAAC,SAAS,MAAM,EACrD,MAAM,SAAS,EACf,OAAO,aAAa,CAAC,CAAC,CAAC,EACvB,SAAQ,cAAyC,KAC/C,OAAO,CAAC,aAAa,CAsCvB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,SAAS,EACf,UAAU,mBAAmB,KAC3B,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,CA0C9B,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,mCAAmC,GAC/C,MAAM,SAAS,EACf,UAAU,mBAAmB,KAC3B,OAAO,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAwD/C,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,uCAAuC,GACnD,MAAM,SAAS,EACf,cAA+B,EAC/B,eAAU,KACR,OAAO,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAmB1C,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,SAAS,EACf,QAAQ,IAAI,KACV,OAAO,CAAC,MAAM,CAMhB,CAAC"}
@@ -12,7 +12,7 @@
12
12
  * @module
13
13
  */
14
14
  import { assert_row } from '../db/assert_row.js';
15
- import { columns_sql, qualify_columns } from '../db/sql_columns.js';
15
+ import { columns_sql, iso8601_timestamp_expr, qualify_columns } from '../db/sql_columns.js';
16
16
  import { AUDIT_LOG_DEFAULT_LIMIT, builtin_audit_log_config } from './audit_log_schema.js';
17
17
  /**
18
18
  * The full `audit_log` column set, named explicitly so a row read fails loud
@@ -37,6 +37,12 @@ export const AUDIT_LOG_COLUMNS = [
37
37
  'created_at',
38
38
  'metadata'
39
39
  ];
40
+ /**
41
+ * The `audit_log` timestamp override, by row qualifier (`''` bare, `al` for
42
+ * the username JOINs). Exported for the per-cell timeline read in
43
+ * `db/cell_audit_queries.ts`, which projects the same table.
44
+ */
45
+ export const audit_log_expr = iso8601_timestamp_expr(AUDIT_LOG_COLUMNS, ['created_at']);
40
46
  /**
41
47
  * Process-wide counter for audit metadata validation failures. `query_audit_log`
42
48
  * increments on `safeParse` mismatch and writes the row anyway (fail-open —
@@ -108,7 +114,7 @@ export const query_audit_log = async (deps, input, config = builtin_audit_log_co
108
114
  }
109
115
  const rows = await deps.db.query(`INSERT INTO audit_log (event_type, outcome, actor_id, account_id, target_account_id, target_actor_id, ip, metadata)
110
116
  VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
111
- RETURNING ${columns_sql(AUDIT_LOG_COLUMNS)}`, [
117
+ RETURNING ${columns_sql(AUDIT_LOG_COLUMNS, audit_log_expr(''))}`, [
112
118
  input.event_type,
113
119
  input.outcome ?? 'success',
114
120
  input.actor_id ?? null,
@@ -156,7 +162,7 @@ export const query_audit_log_list = async (deps, options) => {
156
162
  const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
157
163
  const limit = options?.limit ?? AUDIT_LOG_DEFAULT_LIMIT;
158
164
  const offset = options?.offset ?? 0;
159
- return deps.db.query(`SELECT ${columns_sql(AUDIT_LOG_COLUMNS)} FROM audit_log ${where} ORDER BY seq DESC LIMIT $${param_index++} OFFSET $${param_index}`, [...params, limit, offset]);
165
+ return deps.db.query(`SELECT ${columns_sql(AUDIT_LOG_COLUMNS, audit_log_expr(''))} FROM audit_log ${where} ORDER BY seq DESC LIMIT $${param_index++} OFFSET $${param_index}`, [...params, limit, offset]);
160
166
  };
161
167
  /**
162
168
  * List audit log entries with resolved usernames, newest first.
@@ -200,7 +206,7 @@ export const query_audit_log_list_with_usernames = async (deps, options) => {
200
206
  // has no actor binding (admin password reset, session revoke, etc.).
201
207
  // Under v1 1:1 the two branches resolve to the same username; the
202
208
  // chain is forensic future-proofing for N:1 multi-actor.
203
- return deps.db.query(`SELECT ${qualify_columns(AUDIT_LOG_COLUMNS, 'al')},
209
+ return deps.db.query(`SELECT ${qualify_columns(AUDIT_LOG_COLUMNS, 'al', audit_log_expr('al'))},
204
210
  COALESCE(origin_act_acc.username, origin_acc.username) AS username,
205
211
  COALESCE(target_act_acc.username, target_acc.username) AS target_username
206
212
  FROM audit_log al
@@ -224,7 +230,7 @@ export const query_audit_log_list_role_grant_history = async (deps, limit = AUDI
224
230
  // Same actor-chained JOIN as `query_audit_log_list_with_usernames` —
225
231
  // see the comment there for rationale (forensic future-proofing for
226
232
  // N:1 multi-actor; v1 1:1 picks the same username via either branch).
227
- return deps.db.query(`SELECT ${qualify_columns(AUDIT_LOG_COLUMNS, 'al')},
233
+ return deps.db.query(`SELECT ${qualify_columns(AUDIT_LOG_COLUMNS, 'al', audit_log_expr('al'))},
228
234
  COALESCE(origin_act_acc.username, origin_acc.username) AS username,
229
235
  COALESCE(target_act_acc.username, target_acc.username) AS target_username
230
236
  FROM audit_log al