@fuzdev/fuz_app 0.117.1 → 0.119.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 (179) hide show
  1. package/dist/actions/CLAUDE.md +301 -73
  2. package/dist/actions/action_rpc.d.ts.map +1 -1
  3. package/dist/actions/action_rpc.js +16 -1
  4. package/dist/actions/connection_closer.d.ts +136 -51
  5. package/dist/actions/connection_closer.d.ts.map +1 -1
  6. package/dist/actions/connection_closer.js +133 -34
  7. package/dist/actions/register_action_ws.d.ts +176 -24
  8. package/dist/actions/register_action_ws.d.ts.map +1 -1
  9. package/dist/actions/register_action_ws.js +581 -241
  10. package/dist/actions/register_ws_endpoint.d.ts +1 -0
  11. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  12. package/dist/actions/register_ws_endpoint.js +1 -0
  13. package/dist/actions/socket.svelte.d.ts +43 -9
  14. package/dist/actions/socket.svelte.d.ts.map +1 -1
  15. package/dist/actions/socket.svelte.js +59 -8
  16. package/dist/actions/transports.d.ts +55 -1
  17. package/dist/actions/transports.d.ts.map +1 -1
  18. package/dist/actions/transports.js +55 -1
  19. package/dist/actions/transports_ws_auth_guard.d.ts +53 -67
  20. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  21. package/dist/actions/transports_ws_auth_guard.js +87 -111
  22. package/dist/actions/transports_ws_backend.d.ts +212 -30
  23. package/dist/actions/transports_ws_backend.d.ts.map +1 -1
  24. package/dist/actions/transports_ws_backend.js +327 -70
  25. package/dist/actions/ws_endpoint_spec.d.ts +26 -13
  26. package/dist/actions/ws_endpoint_spec.d.ts.map +1 -1
  27. package/dist/auth/CLAUDE.md +125 -31
  28. package/dist/auth/account_actions.d.ts +12 -18
  29. package/dist/auth/account_actions.d.ts.map +1 -1
  30. package/dist/auth/account_actions.js +47 -33
  31. package/dist/auth/account_routes.d.ts +5 -11
  32. package/dist/auth/account_routes.d.ts.map +1 -1
  33. package/dist/auth/account_routes.js +40 -36
  34. package/dist/auth/account_schema.d.ts +27 -0
  35. package/dist/auth/account_schema.d.ts.map +1 -1
  36. package/dist/auth/account_schema.js +22 -0
  37. package/dist/auth/admin_actions.d.ts +8 -17
  38. package/dist/auth/admin_actions.d.ts.map +1 -1
  39. package/dist/auth/admin_actions.js +33 -21
  40. package/dist/auth/api_token_queries.d.ts +23 -2
  41. package/dist/auth/api_token_queries.d.ts.map +1 -1
  42. package/dist/auth/api_token_queries.js +39 -3
  43. package/dist/auth/audit_emitter.d.ts +72 -44
  44. package/dist/auth/audit_emitter.d.ts.map +1 -1
  45. package/dist/auth/audit_emitter.js +60 -16
  46. package/dist/auth/audit_log_route_schema.js +7 -7
  47. package/dist/auth/audit_log_routes.d.ts +31 -6
  48. package/dist/auth/audit_log_routes.d.ts.map +1 -1
  49. package/dist/auth/audit_log_routes.js +85 -11
  50. package/dist/auth/audit_log_schema.d.ts +48 -0
  51. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  52. package/dist/auth/audit_log_schema.js +58 -0
  53. package/dist/auth/bootstrap_account.d.ts +3 -2
  54. package/dist/auth/bootstrap_account.d.ts.map +1 -1
  55. package/dist/auth/bootstrap_account.js +3 -3
  56. package/dist/auth/cleanup.d.ts +219 -30
  57. package/dist/auth/cleanup.d.ts.map +1 -1
  58. package/dist/auth/cleanup.js +260 -45
  59. package/dist/auth/deps.d.ts +29 -1
  60. package/dist/auth/deps.d.ts.map +1 -1
  61. package/dist/auth/migrations.d.ts.map +1 -1
  62. package/dist/auth/migrations.js +27 -0
  63. package/dist/auth/resolved_auth.d.ts +105 -0
  64. package/dist/auth/resolved_auth.d.ts.map +1 -0
  65. package/dist/auth/resolved_auth.js +124 -0
  66. package/dist/auth/role_grant_offer_ddl.d.ts +4 -2
  67. package/dist/auth/role_grant_offer_ddl.d.ts.map +1 -1
  68. package/dist/auth/role_grant_offer_ddl.js +10 -3
  69. package/dist/auth/role_grant_offer_queries.d.ts +16 -7
  70. package/dist/auth/role_grant_offer_queries.d.ts.map +1 -1
  71. package/dist/auth/role_grant_offer_queries.js +33 -15
  72. package/dist/auth/role_grant_offer_schema.d.ts +9 -0
  73. package/dist/auth/role_grant_offer_schema.d.ts.map +1 -1
  74. package/dist/auth/session_middleware.d.ts +8 -1
  75. package/dist/auth/session_middleware.d.ts.map +1 -1
  76. package/dist/auth/session_middleware.js +9 -3
  77. package/dist/auth/session_queries.d.ts +31 -6
  78. package/dist/auth/session_queries.d.ts.map +1 -1
  79. package/dist/auth/session_queries.js +31 -6
  80. package/dist/auth/standard_rpc_actions.d.ts +9 -9
  81. package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
  82. package/dist/auth/standard_rpc_actions.js +3 -3
  83. package/dist/db/CLAUDE.md +7 -1
  84. package/dist/db/migrate.d.ts +8 -13
  85. package/dist/db/migrate.d.ts.map +1 -1
  86. package/dist/db/migrate.js +9 -38
  87. package/dist/db/status.d.ts +58 -7
  88. package/dist/db/status.d.ts.map +1 -1
  89. package/dist/db/status.js +205 -97
  90. package/dist/http/CLAUDE.md +7 -3
  91. package/dist/http/db_routes.d.ts +6 -2
  92. package/dist/http/db_routes.d.ts.map +1 -1
  93. package/dist/http/db_routes.js +2 -2
  94. package/dist/periodic.d.ts +80 -0
  95. package/dist/periodic.d.ts.map +1 -0
  96. package/dist/periodic.js +122 -0
  97. package/dist/realtime/sse_auth_guard.d.ts +79 -49
  98. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  99. package/dist/realtime/sse_auth_guard.js +103 -73
  100. package/dist/realtime/subscriber_registry.d.ts +119 -8
  101. package/dist/realtime/subscriber_registry.d.ts.map +1 -1
  102. package/dist/realtime/subscriber_registry.js +184 -25
  103. package/dist/runtime/deno.d.ts.map +1 -1
  104. package/dist/runtime/deno.js +9 -6
  105. package/dist/runtime/deps.d.ts +3 -2
  106. package/dist/runtime/deps.d.ts.map +1 -1
  107. package/dist/runtime/secure_file.d.ts +23 -9
  108. package/dist/runtime/secure_file.d.ts.map +1 -1
  109. package/dist/runtime/secure_file.js +29 -10
  110. package/dist/server/app_backend.d.ts +13 -2
  111. package/dist/server/app_backend.d.ts.map +1 -1
  112. package/dist/server/app_backend.js +5 -2
  113. package/dist/server/app_server.d.ts +55 -15
  114. package/dist/server/app_server.d.ts.map +1 -1
  115. package/dist/server/app_server.js +41 -13
  116. package/dist/testing/CLAUDE.md +140 -48
  117. package/dist/testing/app_server.d.ts +2 -2
  118. package/dist/testing/app_server.d.ts.map +1 -1
  119. package/dist/testing/app_server.js +11 -2
  120. package/dist/testing/audit_drift_guard.d.ts +3 -52
  121. package/dist/testing/audit_drift_guard.d.ts.map +1 -1
  122. package/dist/testing/audit_drift_guard.js +2 -46
  123. package/dist/testing/connection_closer_helpers.d.ts +9 -22
  124. package/dist/testing/connection_closer_helpers.d.ts.map +1 -1
  125. package/dist/testing/connection_closer_helpers.js +11 -17
  126. package/dist/testing/cross_backend/capabilities.d.ts +67 -13
  127. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  128. package/dist/testing/cross_backend/capabilities.js +27 -12
  129. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
  130. package/dist/testing/cross_backend/default_backend_configs.js +21 -0
  131. package/dist/testing/cross_backend/expected_schema.json +1 -0
  132. package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts.map +1 -1
  133. package/dist/testing/cross_backend/role_grant_offer_notification_ws.js +13 -4
  134. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
  135. package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +4 -1
  136. package/dist/testing/cross_backend/session_cap.d.ts +13 -0
  137. package/dist/testing/cross_backend/session_cap.d.ts.map +1 -1
  138. package/dist/testing/cross_backend/session_cap.js +55 -19
  139. package/dist/testing/cross_backend/sse_round_trip.d.ts +10 -2
  140. package/dist/testing/cross_backend/sse_round_trip.d.ts.map +1 -1
  141. package/dist/testing/cross_backend/sse_round_trip.js +93 -26
  142. package/dist/testing/cross_backend/testing_server_bun.js +4 -4
  143. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
  144. package/dist/testing/cross_backend/ts_spine_backend_config.js +10 -2
  145. package/dist/testing/cross_backend/ws_connection_cap.d.ts +35 -0
  146. package/dist/testing/cross_backend/ws_connection_cap.d.ts.map +1 -0
  147. package/dist/testing/cross_backend/ws_connection_cap.js +131 -0
  148. package/dist/testing/cross_backend/ws_round_trip.d.ts +2 -1
  149. package/dist/testing/cross_backend/ws_round_trip.d.ts.map +1 -1
  150. package/dist/testing/cross_backend/ws_round_trip.js +156 -19
  151. package/dist/testing/sse_round_trip.d.ts +6 -4
  152. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  153. package/dist/testing/stubs.d.ts +1 -1
  154. package/dist/testing/stubs.d.ts.map +1 -1
  155. package/dist/testing/stubs.js +6 -4
  156. package/dist/testing/transports/sse_transport.d.ts +1 -1
  157. package/dist/testing/transports/ws_client.d.ts +7 -0
  158. package/dist/testing/transports/ws_client.d.ts.map +1 -1
  159. package/dist/testing/transports/ws_raw_client.d.ts +51 -0
  160. package/dist/testing/transports/ws_raw_client.d.ts.map +1 -0
  161. package/dist/testing/transports/ws_raw_client.js +156 -0
  162. package/dist/testing/transports/ws_transport.d.ts +28 -0
  163. package/dist/testing/transports/ws_transport.d.ts.map +1 -1
  164. package/dist/testing/transports/ws_transport.js +45 -1
  165. package/dist/testing/ws_round_trip.d.ts +8 -3
  166. package/dist/testing/ws_round_trip.d.ts.map +1 -1
  167. package/dist/testing/ws_round_trip.js +19 -4
  168. package/dist/ui/AdminAccounts.svelte +4 -3
  169. package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -1
  170. package/dist/ui/AdminOverview.svelte +3 -1
  171. package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
  172. package/dist/ui/CLAUDE.md +22 -6
  173. package/dist/ui/admin_accounts_state.svelte.d.ts +15 -1
  174. package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
  175. package/dist/ui/admin_accounts_state.svelte.js +16 -0
  176. package/dist/ui/auth_state.svelte.d.ts +25 -1
  177. package/dist/ui/auth_state.svelte.d.ts.map +1 -1
  178. package/dist/ui/auth_state.svelte.js +28 -1
  179. package/package.json +2 -2
@@ -132,13 +132,19 @@ export declare const query_session_list_for_account: (deps: QueryDeps, account_i
132
132
  * Expired-but-unreaped rows count toward the cap (the predicate is `account_id`
133
133
  * alone). Matches the Rust twin `query_session_enforce_limit`.
134
134
  *
135
+ * Deleting a session row does not end the connections it opened: a WebSocket
136
+ * keeps the authority it resolved at upgrade and an SSE stream never rechecks.
137
+ * The returned ids are the token hashes those connections are registered
138
+ * under, so the caller closes them once the transaction commits
139
+ * (`queue_connection_close`) — `POST /login` does.
140
+ *
135
141
  * @param deps - query dependencies (must be transaction-scoped)
136
142
  * @param account_id - the account to enforce the limit for
137
143
  * @param max_sessions - maximum number of sessions to keep
138
- * @returns the number of sessions evicted
144
+ * @returns the evicted sessions' ids (their token hashes), empty when nothing was evicted
139
145
  * @mutates `auth_session` table - deletes the oldest rows past the cap
140
146
  */
141
- export declare const query_session_enforce_limit: (deps: QueryDeps, account_id: string, max_sessions: number) => Promise<number>;
147
+ export declare const query_session_enforce_limit: (deps: QueryDeps, account_id: string, max_sessions: number) => Promise<Array<string>>;
142
148
  /**
143
149
  * List all active sessions across all accounts with usernames.
144
150
  *
@@ -153,10 +159,29 @@ export declare const query_session_list_all_active: (deps: QueryDeps, limit?: nu
153
159
  username: string;
154
160
  }>>;
155
161
  /**
156
- * Delete expired sessions.
157
- *
158
- * @returns the number of sessions cleaned up
162
+ * Delete every expired session, returning the ids (token hashes) of the rows
163
+ * it deleted.
164
+ *
165
+ * The ids are the keys `ConnectionCloser.close_sockets_for_session` closes by.
166
+ * A row past `expires_at` already fails `query_session_get_valid`, but
167
+ * deleting it does not end the connections it opened: a WebSocket keeps the
168
+ * authority it resolved at upgrade and an SSE stream never rechecks. The row
169
+ * was also the only per-session handle on those connections —
170
+ * `account_session_revoke` closes only for a row it deleted — so the caller
171
+ * closes each returned session's connections once the delete has committed.
172
+ * `cleanup_expired_sessions` (`auth/cleanup.ts`) is that caller; reach for it
173
+ * rather than this query.
174
+ *
175
+ * One statement, so the returned ids are exactly the rows this call removed:
176
+ * a concurrent pass gets a disjoint set. Run it in a transaction, as the
177
+ * caller above does, so the ids are in hand before the delete commits — on a
178
+ * pool-level `Db` a connection lost on the response leaves the rows deleted
179
+ * and their ids unread. Served by `idx_auth_session_expires`. Twin of the
180
+ * Rust `query_session_cleanup_expired`.
181
+ *
182
+ * @param deps - query dependencies (transaction-scoped, see above)
183
+ * @returns the deleted sessions' ids (their token hashes), empty when nothing had expired
159
184
  * @mutates `auth_session` table - deletes every row past `expires_at`
160
185
  */
161
- export declare const query_session_cleanup_expired: (deps: QueryDeps) => Promise<number>;
186
+ export declare const query_session_cleanup_expired: (deps: QueryDeps) => Promise<Array<string>>;
162
187
  //# sourceMappingURL=session_queries.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"session_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/session_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAElE;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,QAA2B,CAAC;AAEjE;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,KAAG,SAElD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,QAAO,MAEzC,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,YAAY,MAAM,EAClB,YAAY,IAAI,KACd,OAAO,CAAC,IAAI,CAMd,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,2DAKoB,CAAC;AAQtD;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,WAAW,GAAG,SAAS,CAKjC,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,qCAAqC,GACjD,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,IAAI,CAEd,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,YAAY,MAAM,KAChB,OAAO,CAAC,OAAO,CAMjB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,oCAAoC,GAChD,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,MAAM,CAMhB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,cAAU,KACR,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAK5B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,eAAO,MAAM,2BAA2B,GACvC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,cAAc,MAAM,KAClB,OAAO,CAAC,MAAM,CAYhB,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,cAAW,KACT,OAAO,CAAC,KAAK,CAAC,WAAW,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CASnD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,6BAA6B,GAAU,MAAM,SAAS,KAAG,OAAO,CAAC,MAAM,CAKnF,CAAC"}
1
+ {"version":3,"file":"session_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/session_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAElE;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,QAA2B,CAAC;AAEjE;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,KAAG,SAElD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,QAAO,MAEzC,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,GAChC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,YAAY,MAAM,EAClB,YAAY,IAAI,KACd,OAAO,CAAC,IAAI,CAMd,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,2DAKoB,CAAC;AAQtD;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GACnC,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,WAAW,GAAG,SAAS,CAKjC,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,qCAAqC,GACjD,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,IAAI,CAEd,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,YAAY,MAAM,KAChB,OAAO,CAAC,OAAO,CAMjB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,oCAAoC,GAChD,MAAM,SAAS,EACf,YAAY,MAAM,KAChB,OAAO,CAAC,MAAM,CAMhB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,cAAU,KACR,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAK5B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,eAAO,MAAM,2BAA2B,GACvC,MAAM,SAAS,EACf,YAAY,MAAM,EAClB,cAAc,MAAM,KAClB,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAYvB,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,cAAW,KACT,OAAO,CAAC,KAAK,CAAC,WAAW,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CASnD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,6BAA6B,GAAU,MAAM,SAAS,KAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAK1F,CAAC"}
@@ -165,10 +165,16 @@ export const query_session_list_for_account = async (deps, account_id, limit = 5
165
165
  * Expired-but-unreaped rows count toward the cap (the predicate is `account_id`
166
166
  * alone). Matches the Rust twin `query_session_enforce_limit`.
167
167
  *
168
+ * Deleting a session row does not end the connections it opened: a WebSocket
169
+ * keeps the authority it resolved at upgrade and an SSE stream never rechecks.
170
+ * The returned ids are the token hashes those connections are registered
171
+ * under, so the caller closes them once the transaction commits
172
+ * (`queue_connection_close`) — `POST /login` does.
173
+ *
168
174
  * @param deps - query dependencies (must be transaction-scoped)
169
175
  * @param account_id - the account to enforce the limit for
170
176
  * @param max_sessions - maximum number of sessions to keep
171
- * @returns the number of sessions evicted
177
+ * @returns the evicted sessions' ids (their token hashes), empty when nothing was evicted
172
178
  * @mutates `auth_session` table - deletes the oldest rows past the cap
173
179
  */
174
180
  export const query_session_enforce_limit = async (deps, account_id, max_sessions) => {
@@ -179,7 +185,7 @@ export const query_session_enforce_limit = async (deps, account_id, max_sessions
179
185
  ORDER BY created_at DESC, id DESC
180
186
  OFFSET $2
181
187
  ) RETURNING id`, [account_id, max_sessions]);
182
- return rows.length;
188
+ return rows.map((row) => row.id);
183
189
  };
184
190
  /**
185
191
  * List all active sessions across all accounts with usernames.
@@ -199,12 +205,31 @@ export const query_session_list_all_active = async (deps, limit = 200) => {
199
205
  ORDER BY s.created_at DESC, s.id DESC LIMIT $1`, [limit]);
200
206
  };
201
207
  /**
202
- * Delete expired sessions.
203
- *
204
- * @returns the number of sessions cleaned up
208
+ * Delete every expired session, returning the ids (token hashes) of the rows
209
+ * it deleted.
210
+ *
211
+ * The ids are the keys `ConnectionCloser.close_sockets_for_session` closes by.
212
+ * A row past `expires_at` already fails `query_session_get_valid`, but
213
+ * deleting it does not end the connections it opened: a WebSocket keeps the
214
+ * authority it resolved at upgrade and an SSE stream never rechecks. The row
215
+ * was also the only per-session handle on those connections —
216
+ * `account_session_revoke` closes only for a row it deleted — so the caller
217
+ * closes each returned session's connections once the delete has committed.
218
+ * `cleanup_expired_sessions` (`auth/cleanup.ts`) is that caller; reach for it
219
+ * rather than this query.
220
+ *
221
+ * One statement, so the returned ids are exactly the rows this call removed:
222
+ * a concurrent pass gets a disjoint set. Run it in a transaction, as the
223
+ * caller above does, so the ids are in hand before the delete commits — on a
224
+ * pool-level `Db` a connection lost on the response leaves the rows deleted
225
+ * and their ids unread. Served by `idx_auth_session_expires`. Twin of the
226
+ * Rust `query_session_cleanup_expired`.
227
+ *
228
+ * @param deps - query dependencies (transaction-scoped, see above)
229
+ * @returns the deleted sessions' ids (their token hashes), empty when nothing had expired
205
230
  * @mutates `auth_session` table - deletes every row past `expires_at`
206
231
  */
207
232
  export const query_session_cleanup_expired = async (deps) => {
208
233
  const rows = await deps.db.query(`DELETE FROM auth_session WHERE expires_at <= NOW() RETURNING id`);
209
- return rows.length;
234
+ return rows.map((row) => row.id);
210
235
  };
@@ -10,9 +10,9 @@
10
10
  * Option routing: shared `roles` flows to both admin and role-grant-offer;
11
11
  * `default_ttl_ms` and `authorize` go to role-grant-offer only; `max_tokens`
12
12
  * goes to account only;
13
- * shared `connection_closer` flows to admin + account (role-grant-offer ignores);
14
13
  * `notification_sender` reaches role-grant-offer transparently (admin + account
15
- * ignore it).
14
+ * ignore it). `deps.connection_closer` is read by admin + account, whose
15
+ * revocation handlers close live connections through it.
16
16
  *
17
17
  * Paired with `create_admin_rpc_adapters` on the UI side.
18
18
  *
@@ -21,7 +21,7 @@
21
21
  import { type AdminActionOptions } from './admin_actions.js';
22
22
  import { type RoleGrantOfferActionOptions } from './role_grant_offer_actions.js';
23
23
  import { type AccountActionOptions } from './account_actions.js';
24
- import type { ActionFactoryDeps } from './deps.js';
24
+ import type { RevokingActionFactoryDeps } from './deps.js';
25
25
  import type { NotificationSender } from './role_grant_offer_notifications.js';
26
26
  import type { RpcAction } from '../actions/action_rpc.js';
27
27
  /**
@@ -38,12 +38,12 @@ export interface StandardRpcActionsOptions extends AdminActionOptions, RoleGrant
38
38
  /**
39
39
  * Dependencies for `create_standard_rpc_actions`.
40
40
  *
41
- * Stack-standard `ActionFactoryDeps` (`log`, `audit`) plus an optional
42
- * `notification_sender` consumed only by the role-grant-offer sub-factory
43
- * for WS fan-out. Admin and account sub-factories ignore
44
- * `notification_sender`.
41
+ * Stack-standard `RevokingActionFactoryDeps` (`log`, `audit`,
42
+ * `connection_closer`) plus an optional `notification_sender` consumed only by the
43
+ * role-grant-offer sub-factory for WS fan-out. Admin and account
44
+ * sub-factories ignore `notification_sender`.
45
45
  */
46
- export interface StandardRpcActionsDeps extends ActionFactoryDeps {
46
+ export interface StandardRpcActionsDeps extends RevokingActionFactoryDeps {
47
47
  notification_sender?: NotificationSender | null;
48
48
  }
49
49
  /**
@@ -54,7 +54,7 @@ export interface StandardRpcActionsDeps extends ActionFactoryDeps {
54
54
  * and `create_account_actions(deps, {max_tokens})`. The shared `roles`
55
55
  * option flows to admin + role-grant-offer.
56
56
  *
57
- * @param deps - `StandardRpcActionsDeps` (`log`, `audit` from `ActionFactoryDeps`; optional `notification_sender` for WS fan-out)
57
+ * @param deps - `StandardRpcActionsDeps` (`log`, `audit`, `connection_closer` from `RevokingActionFactoryDeps`; optional `notification_sender` for WS fan-out)
58
58
  * @param options - role schema, role-grant-offer config, account config
59
59
  * @returns RPC actions to pass as `rpc_endpoints` or spread into `create_rpc_endpoint`
60
60
  */
@@ -1 +1 @@
1
- {"version":3,"file":"standard_rpc_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/standard_rpc_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAwB,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACnF,OAAO,EAEN,KAAK,2BAA2B,EAChC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAA0B,KAAK,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACzF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AAC9E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAE1D;;;;;;;;GAQG;AACH,MAAM,WAAW,yBAChB,SAAQ,kBAAkB,EAAE,2BAA2B,EAAE,oBAAoB;CAAG;AAEjF;;;;;;;GAOG;AACH,MAAM,WAAW,sBAAuB,SAAQ,iBAAiB;IAChE,mBAAmB,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CAChD;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,2BAA2B,GACvC,MAAM,sBAAsB,EAC5B,UAAS,yBAA8B,KACrC,KAAK,CAAC,SAAS,CAIjB,CAAC"}
1
+ {"version":3,"file":"standard_rpc_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/standard_rpc_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAwB,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACnF,OAAO,EAEN,KAAK,2BAA2B,EAChC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAA0B,KAAK,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACzF,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,WAAW,CAAC;AAC3D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AAC9E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAE1D;;;;;;;;GAQG;AACH,MAAM,WAAW,yBAChB,SAAQ,kBAAkB,EAAE,2BAA2B,EAAE,oBAAoB;CAAG;AAEjF;;;;;;;GAOG;AACH,MAAM,WAAW,sBAAuB,SAAQ,yBAAyB;IACxE,mBAAmB,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CAChD;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,2BAA2B,GACvC,MAAM,sBAAsB,EAC5B,UAAS,yBAA8B,KACrC,KAAK,CAAC,SAAS,CAIjB,CAAC"}
@@ -10,9 +10,9 @@
10
10
  * Option routing: shared `roles` flows to both admin and role-grant-offer;
11
11
  * `default_ttl_ms` and `authorize` go to role-grant-offer only; `max_tokens`
12
12
  * goes to account only;
13
- * shared `connection_closer` flows to admin + account (role-grant-offer ignores);
14
13
  * `notification_sender` reaches role-grant-offer transparently (admin + account
15
- * ignore it).
14
+ * ignore it). `deps.connection_closer` is read by admin + account, whose
15
+ * revocation handlers close live connections through it.
16
16
  *
17
17
  * Paired with `create_admin_rpc_adapters` on the UI side.
18
18
  *
@@ -29,7 +29,7 @@ import { create_account_actions } from './account_actions.js';
29
29
  * and `create_account_actions(deps, {max_tokens})`. The shared `roles`
30
30
  * option flows to admin + role-grant-offer.
31
31
  *
32
- * @param deps - `StandardRpcActionsDeps` (`log`, `audit` from `ActionFactoryDeps`; optional `notification_sender` for WS fan-out)
32
+ * @param deps - `StandardRpcActionsDeps` (`log`, `audit`, `connection_closer` from `RevokingActionFactoryDeps`; optional `notification_sender` for WS fan-out)
33
33
  * @param options - role schema, role-grant-offer config, account config
34
34
  * @returns RPC actions to pass as `rpc_endpoints` or spread into `create_rpc_endpoint`
35
35
  */
package/dist/db/CLAUDE.md CHANGED
@@ -50,7 +50,13 @@ the code.
50
50
  `fact`'s store rows are the deliberate exception: `PgFactStore` feeds
51
51
  `FactMeta.created_at`, a `Date` in the `@fuzdev/fuz_util` `FactStore`
52
52
  contract, and nothing serializes it.
53
- - `status.ts` — CLI DB status utility.
53
+ - `status.ts` — CLI DB status utility: `query_db_status` (read-only;
54
+ per-namespace applied/pending plus name-divergence, mirroring the
55
+ runner's boot check), `format_db_status` (the report body — the
56
+ `database status` / `url:` header is the calling script's, using
57
+ `display_db_url`, which renders the URL from its parse without the
58
+ password or query). The connected body is byte-identical with the Rust
59
+ `fuz_db` twin's; `src/test/db/status.test.ts` pins the shared fixture.
54
60
  - `schema_ready.ts` — `/ready` deploy-gate core: `query_public_columns`
55
61
  (keeps `schema_version`, unlike `query_schema_snapshot`), pure
56
62
  `check_schema_drift` / `format_schema_drift`, `READY_ERROR`. Column-presence
@@ -86,7 +86,7 @@ export interface MigrationResult {
86
86
  * Callers branch on `.kind` rather than matching error messages — message
87
87
  * text is for operators, not control flow.
88
88
  */
89
- export type MigrationErrorKind = 'binary-older-than-db' | 'name-divergence-at-N' | 'old-tracker-shape' | 'migration-failed' | 'baseline-name-not-in-code' | 'baseline-name-out-of-order' | 'baseline-namespace-already-populated';
89
+ export type MigrationErrorKind = 'binary-older-than-db' | 'name-divergence-at-N' | 'migration-failed' | 'baseline-name-not-in-code' | 'baseline-name-out-of-order' | 'baseline-namespace-already-populated';
90
90
  /** Structured context passed alongside a `MigrationError`. */
91
91
  export interface MigrationErrorContext {
92
92
  namespace?: string;
@@ -137,26 +137,22 @@ export declare class MigrationError extends Error {
137
137
  * namespaces are omitted)
138
138
  * @mutates schema_version - inserts one row per applied migration
139
139
  * @throws MigrationError with `kind` of `binary-older-than-db`,
140
- * `name-divergence-at-N`, `old-tracker-shape`, or `migration-failed`
140
+ * `name-divergence-at-N`, or `migration-failed`
141
141
  */
142
142
  export declare const run_migrations: (db: Db, namespaces: Array<MigrationNamespace>) => Promise<Array<MigrationResult>>;
143
143
  /**
144
144
  * Insert tracker rows for the named migrations of a namespace **without
145
145
  * executing them**.
146
146
  *
147
- * Used to promote an existing schema (e.g. produced by a pre-0.42 build,
148
- * preserved through a tracker-shape upgrade) into the new identity tracker.
147
+ * Used to promote an existing schema (e.g. produced by an out-of-band
148
+ * bootstrap, or a hand-applied DDL set) into the identity tracker.
149
149
  * `baseline()` trusts the operator-supplied list — it does not verify that
150
150
  * the schema actually matches what the named migrations would have produced.
151
151
  * Pair with a schema-assertion script post-baseline before re-enabling traffic.
152
152
  *
153
153
  * Contract:
154
- * - Probes for the pre-0.42 tracker shape; throws `old-tracker-shape` if
155
- * found (DDL with `IF NOT EXISTS` would otherwise no-op against the old
156
- * table and the INSERT would fail with a confusing column-not-found).
157
- * - Creates the new-shape `schema_version` table if missing — cutover
158
- * scripts that just dropped the old-shape table can call `baseline()`
159
- * directly with no separate DDL step.
154
+ * - Creates the `schema_version` table if missing — a promotion script can
155
+ * call `baseline()` against a fresh tracker with no separate DDL step.
160
156
  * - Acquires the same per-namespace advisory lock as `run_migrations` (with
161
157
  * the same try/catch fallback for environments lacking `pg_advisory_lock`).
162
158
  * - Refuses if any tracker rows already exist *for this namespace* — lets
@@ -172,9 +168,8 @@ export declare const run_migrations: (db: Db, namespaces: Array<MigrationNamespa
172
168
  * @param names - prefix of `ns.migrations[].name` to record as already-applied
173
169
  * @mutates schema_version - inserts tracker rows for `names` without running
174
170
  * the corresponding migration bodies
175
- * @throws MigrationError with `kind` of `old-tracker-shape`,
176
- * `baseline-name-not-in-code`, `baseline-name-out-of-order`, or
177
- * `baseline-namespace-already-populated`
171
+ * @throws MigrationError with `kind` of `baseline-name-not-in-code`,
172
+ * `baseline-name-out-of-order`, or `baseline-namespace-already-populated`
178
173
  */
179
174
  export declare const baseline: (db: Db, ns: MigrationNamespace, names: ReadonlyArray<string>) => Promise<void>;
180
175
  //# sourceMappingURL=migrate.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"migrate.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/migrate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AAIH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAElC;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;CAC7B;AAED,2DAA2D;AAC3D,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,aAAa,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CAC7B;AAED;;;;;GAKG;AACH,MAAM,MAAM,kBAAkB,GAC3B,sBAAsB,GACtB,sBAAsB,GACtB,mBAAmB,GACnB,kBAAkB,GAClB,2BAA2B,GAC3B,4BAA4B,GAC5B,sCAAsC,CAAC;AAE1C,8DAA8D;AAC9D,MAAM,WAAW,qBAAqB;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACtC,KAAK,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;;;GAIG;AACH,qBAAa,cAAe,SAAQ,KAAK;IACxC,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;gBAEnC,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB;CAQtF;AA6ED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,cAAc,GAC1B,IAAI,EAAE,EACN,YAAY,KAAK,CAAC,kBAAkB,CAAC,KACnC,OAAO,CAAC,KAAK,CAAC,eAAe,CAAC,CAuFhC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,eAAO,MAAM,QAAQ,GACpB,IAAI,EAAE,EACN,IAAI,kBAAkB,EACtB,OAAO,aAAa,CAAC,MAAM,CAAC,KAC1B,OAAO,CAAC,IAAI,CA+Dd,CAAC"}
1
+ {"version":3,"file":"migrate.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/migrate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AAIH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAElC;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;CAC7B;AAED,2DAA2D;AAC3D,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,aAAa,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CAC7B;AAED;;;;;GAKG;AACH,MAAM,MAAM,kBAAkB,GAC3B,sBAAsB,GACtB,sBAAsB,GACtB,kBAAkB,GAClB,2BAA2B,GAC3B,4BAA4B,GAC5B,sCAAsC,CAAC;AAE1C,8DAA8D;AAC9D,MAAM,WAAW,qBAAqB;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACtC,KAAK,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;;;GAIG;AACH,qBAAa,cAAe,SAAQ,KAAK;IACxC,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;gBAEnC,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB;CAQtF;AAsDD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,cAAc,GAC1B,IAAI,EAAE,EACN,YAAY,KAAK,CAAC,kBAAkB,CAAC,KACnC,OAAO,CAAC,KAAK,CAAC,eAAe,CAAC,CAmFhC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,QAAQ,GACpB,IAAI,EAAE,EACN,IAAI,kBAAkB,EACtB,OAAO,aAAa,CAAC,MAAM,CAAC,KAC1B,OAAO,CAAC,IAAI,CA2Dd,CAAC"}
@@ -83,24 +83,6 @@ CREATE TABLE IF NOT EXISTS schema_version (
83
83
  PRIMARY KEY (namespace, name),
84
84
  UNIQUE (namespace, sequence)
85
85
  )`;
86
- /**
87
- * Detect the pre-0.42 `schema_version` shape (`namespace`, `version`,
88
- * `applied_at`). The new-shape DDL uses `IF NOT EXISTS` and would silently
89
- * no-op against the old table, so this probe runs before DDL and before any
90
- * per-namespace lock.
91
- */
92
- const detect_old_tracker = async (db) => {
93
- const row = await db.query_one(`SELECT EXISTS (
94
- SELECT 1 FROM information_schema.columns
95
- WHERE table_schema = 'public'
96
- AND table_name = 'schema_version'
97
- AND column_name = 'version'
98
- ) as exists`);
99
- return row?.exists ?? false;
100
- };
101
- const OLD_TRACKER_HINT = 'Detected fuz_app < 0.42 tracker shape (schema_version.version column exists). ' +
102
- 'Hint: `DROP TABLE schema_version` and re-run, or call `baseline()` first if ' +
103
- 'preserving an existing schema.';
104
86
  /**
105
87
  * Compute a stable int32 advisory lock key from a namespace string.
106
88
  *
@@ -171,12 +153,9 @@ const with_namespace_lock = async (db, namespace, fn) => {
171
153
  * namespaces are omitted)
172
154
  * @mutates schema_version - inserts one row per applied migration
173
155
  * @throws MigrationError with `kind` of `binary-older-than-db`,
174
- * `name-divergence-at-N`, `old-tracker-shape`, or `migration-failed`
156
+ * `name-divergence-at-N`, or `migration-failed`
175
157
  */
176
158
  export const run_migrations = async (db, namespaces) => {
177
- if (await detect_old_tracker(db)) {
178
- throw new MigrationError('old-tracker-shape', OLD_TRACKER_HINT);
179
- }
180
159
  await db.query(SCHEMA_VERSION_DDL);
181
160
  const results = [];
182
161
  for (const { namespace, migrations } of namespaces) {
@@ -206,10 +185,10 @@ export const run_migrations = async (db, namespaces) => {
206
185
  `that follow it, or delete from row ${i} onward to re-apply the tail).`, { namespace, at_index: i });
207
186
  }
208
187
  }
209
- // Step 5: up-to-date case
188
+ // up-to-date case
210
189
  if (applied.length === migrations.length)
211
190
  return;
212
- // Step 6: run pending tail in a single chain-tx
191
+ // Step 5: run pending tail in a single chain-tx
213
192
  let next_sequence = applied.length > 0 ? applied[applied.length - 1].sequence + 1 : 0;
214
193
  const applied_names = [];
215
194
  await db.transaction(async (tx) => {
@@ -240,19 +219,15 @@ export const run_migrations = async (db, namespaces) => {
240
219
  * Insert tracker rows for the named migrations of a namespace **without
241
220
  * executing them**.
242
221
  *
243
- * Used to promote an existing schema (e.g. produced by a pre-0.42 build,
244
- * preserved through a tracker-shape upgrade) into the new identity tracker.
222
+ * Used to promote an existing schema (e.g. produced by an out-of-band
223
+ * bootstrap, or a hand-applied DDL set) into the identity tracker.
245
224
  * `baseline()` trusts the operator-supplied list — it does not verify that
246
225
  * the schema actually matches what the named migrations would have produced.
247
226
  * Pair with a schema-assertion script post-baseline before re-enabling traffic.
248
227
  *
249
228
  * Contract:
250
- * - Probes for the pre-0.42 tracker shape; throws `old-tracker-shape` if
251
- * found (DDL with `IF NOT EXISTS` would otherwise no-op against the old
252
- * table and the INSERT would fail with a confusing column-not-found).
253
- * - Creates the new-shape `schema_version` table if missing — cutover
254
- * scripts that just dropped the old-shape table can call `baseline()`
255
- * directly with no separate DDL step.
229
+ * - Creates the `schema_version` table if missing — a promotion script can
230
+ * call `baseline()` against a fresh tracker with no separate DDL step.
256
231
  * - Acquires the same per-namespace advisory lock as `run_migrations` (with
257
232
  * the same try/catch fallback for environments lacking `pg_advisory_lock`).
258
233
  * - Refuses if any tracker rows already exist *for this namespace* — lets
@@ -268,14 +243,10 @@ export const run_migrations = async (db, namespaces) => {
268
243
  * @param names - prefix of `ns.migrations[].name` to record as already-applied
269
244
  * @mutates schema_version - inserts tracker rows for `names` without running
270
245
  * the corresponding migration bodies
271
- * @throws MigrationError with `kind` of `old-tracker-shape`,
272
- * `baseline-name-not-in-code`, `baseline-name-out-of-order`, or
273
- * `baseline-namespace-already-populated`
246
+ * @throws MigrationError with `kind` of `baseline-name-not-in-code`,
247
+ * `baseline-name-out-of-order`, or `baseline-namespace-already-populated`
274
248
  */
275
249
  export const baseline = async (db, ns, names) => {
276
- if (await detect_old_tracker(db)) {
277
- throw new MigrationError('old-tracker-shape', OLD_TRACKER_HINT, { namespace: ns.namespace });
278
- }
279
250
  await db.query(SCHEMA_VERSION_DDL);
280
251
  const code_names = ns.migrations.map((m) => m.name);
281
252
  // Validate every supplied name exists in code (catches drift between cutover
@@ -82,12 +82,6 @@ export interface DbStatus {
82
82
  tables: Array<TableStatus>;
83
83
  /** Per-namespace migration status. */
84
84
  migrations: Array<MigrationStatus>;
85
- /**
86
- * True if the pre-0.42 `schema_version` shape (with a `version` column)
87
- * was detected. The runner refuses to start in this state — operators
88
- * see this flag as their cue to drop the table or call `baseline()`.
89
- */
90
- old_tracker_shape?: boolean;
91
85
  }
92
86
  /**
93
87
  * Query database status including connectivity, tables, and migration state.
@@ -103,10 +97,67 @@ export interface DbStatus {
103
97
  */
104
98
  export declare const query_db_status: (db: Db, namespaces?: Array<MigrationNamespace>) => Promise<DbStatus>;
105
99
  /**
106
- * Format a `DbStatus` as a human-readable string for CLI output.
100
+ * Format a `DbStatus` as the body of the human-facing operator report.
101
+ *
102
+ * A `tables:` count with one aligned `name N rows` line per table, then a
103
+ * `migrations:` header (always printed, even with no namespaces) and one
104
+ * aligned line per namespace: its state (`up-to-date`, `PENDING <n>`, or
105
+ * `DIVERGED`), `applied/declared`, and the latest applied name (`(none)` when
106
+ * nothing is applied). A diverged namespace adds a `diverged:` line, a pending
107
+ * one a `pending:` line naming the tail. A failed connection renders the single
108
+ * line `connection: FAILED (<error>)`. Every line ends in a newline.
109
+ *
110
+ * The body only — the `database status` / `url:` header belongs to the CLI
111
+ * script, which holds the URL a `DbStatus` never sees. For a connected status
112
+ * the output is byte-identical with the Rust twin's `format_db_status`; both
113
+ * test suites pin the same fixture report.
107
114
  *
108
115
  * @param status - the status to format
109
116
  * @returns multi-line string suitable for console output
110
117
  */
111
118
  export declare const format_db_status: (status: DbStatus) => string;
119
+ /**
120
+ * Render a database URL for printing from the parse node-postgres dials —
121
+ * `new URL`, with pg-connection-string's precedence, where a `user`, `host`, or
122
+ * `port` query parameter overrides the URL's own — so the line names the server
123
+ * dialed and no credential can leak however the input spells it.
124
+ *
125
+ * A `postgres://` or `postgresql://` URL renders as
126
+ * `postgres://[user@]host:port[/dbname]`: never the password, and never the
127
+ * query or fragment (`sslmode` and the like are dropped by design). The scheme
128
+ * always prints as `postgres://`. The port always prints, `5432` when the URL
129
+ * names none. The user and database name print percent-encoded with
130
+ * `encodeURIComponent`, so no control character, escape sequence, or bidi
131
+ * override reaches the terminal and an `@` can't pose as the separator; each
132
+ * prints only when non-empty. An IP-literal host prints canonically (IPv6
133
+ * bracketed); a Unix-socket directory prints with each `/` as `%2F`. A PGlite
134
+ * `file://` or `memory://` URL prints as scheme, host, and path.
135
+ *
136
+ * The whole line is `(not a URL; hidden)` for any other scheme, when `new URL`
137
+ * rejects the input, when it holds a malformed percent-escape (`%ZZ`), when
138
+ * there is no host, when the host is neither an IP literal, a hostname of
139
+ * `A-Z a-z 0-9 . _ -`, nor a socket directory of those characters and `/`, when
140
+ * the user holds `?`, `=`, `&`, `/`, or `#`, or when the port isn't a number up
141
+ * to 65535. The host and user rules match the Rust twin's, where they catch
142
+ * tokio-postgres's split at the first `@`.
143
+ *
144
+ * Twin of the Rust `fuz_db` CLI's `display_db_url`, byte-identical on the URLs
145
+ * both drivers read alike. They part where the drivers do: a `host=` or `port=`
146
+ * query parameter overrides the URL's here (tokio-postgres appends it to a list
147
+ * whose first entry the pool dials); a `dbname=` query parameter, which
148
+ * tokio-postgres honours and node-postgres ignores, and a `#…` fragment, which
149
+ * tokio-postgres keeps as part of the database name; tokio-postgres also
150
+ * accepts `key=value` strings, host lists, and an unencoded `/` or `?` in the
151
+ * password (here, the placeholder); an unencoded `@` in the userinfo splits at
152
+ * the last `@` here, rendering the user and host node-postgres dials, where the
153
+ * Rust twin prints the placeholder; node-postgres decodes the database name
154
+ * with `decodeURI`, so a reserved-character escape such as `%2F` stays literal
155
+ * and prints as `%252F`; an IPv4-mapped IPv6 address serializes differently;
156
+ * and tokio-postgres refuses an unknown query parameter (`sslpassword=`) that
157
+ * node-postgres passes.
158
+ *
159
+ * @param url - the database URL
160
+ * @returns the URL without its password, query, or fragment, or the placeholder
161
+ */
162
+ export declare const display_db_url: (url: string) => string;
112
163
  //# sourceMappingURL=status.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"status.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/status.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD;;;;;;;;GAQG;AACH,MAAM,MAAM,UAAU,GACnB;IACA,mEAAmE;IACnE,IAAI,EAAE,eAAe,CAAC;IACtB,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,OAAO,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAC;CAChB,GACD;IACA,kEAAkE;IAClE,IAAI,EAAE,cAAc,CAAC;IACrB,qCAAqC;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAC;CAChB,CAAC;AAEL;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,uEAAuE;IACvE,aAAa,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7B,2EAA2E;IAC3E,aAAa,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7B;;;OAGG;IACH,UAAU,EAAE,OAAO,CAAC;IACpB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACxB,yCAAyC;IACzC,SAAS,EAAE,OAAO,CAAC;IACnB,0CAA0C;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+BAA+B;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,MAAM,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;IAC3B,sCAAsC;IACtC,UAAU,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;IACnC;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC5B;AA8BD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,eAAe,GAC3B,IAAI,EAAE,EACN,aAAa,KAAK,CAAC,kBAAkB,CAAC,KACpC,OAAO,CAAC,QAAQ,CA2GlB,CAAC;AAeF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAAI,QAAQ,QAAQ,KAAG,MAoDnD,CAAC"}
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/status.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD;;;;;;;;GAQG;AACH,MAAM,MAAM,UAAU,GACnB;IACA,mEAAmE;IACnE,IAAI,EAAE,eAAe,CAAC;IACtB,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,OAAO,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAC;CAChB,GACD;IACA,kEAAkE;IAClE,IAAI,EAAE,cAAc,CAAC;IACrB,qCAAqC;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAC;CAChB,CAAC;AAEL;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,uEAAuE;IACvE,aAAa,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7B,2EAA2E;IAC3E,aAAa,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7B;;;OAGG;IACH,UAAU,EAAE,OAAO,CAAC;IACpB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACxB,yCAAyC;IACzC,SAAS,EAAE,OAAO,CAAC;IACnB,0CAA0C;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+BAA+B;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,MAAM,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;IAC3B,sCAAsC;IACtC,UAAU,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;CACnC;AA6DD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,eAAe,GAC3B,IAAI,EAAE,EACN,aAAa,KAAK,CAAC,kBAAkB,CAAC,KACpC,OAAO,CAAC,QAAQ,CA2DlB,CAAC;AA0BF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,gBAAgB,GAAI,QAAQ,QAAQ,KAAG,MAmCnD,CAAC;AA8BF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,KAAG,MAoC5C,CAAC"}