@fuzdev/fuz_app 0.110.1 → 0.112.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 (210) hide show
  1. package/dist/actions/CLAUDE.md +15 -11
  2. package/dist/actions/action_rpc.d.ts +3 -3
  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 +59 -10
  17. package/dist/auth/account_action_specs.d.ts +0 -2
  18. package/dist/auth/account_action_specs.d.ts.map +1 -1
  19. package/dist/auth/account_actions.d.ts.map +1 -1
  20. package/dist/auth/account_actions.js +10 -1
  21. package/dist/auth/account_queries.d.ts +22 -0
  22. package/dist/auth/account_queries.d.ts.map +1 -1
  23. package/dist/auth/account_queries.js +70 -16
  24. package/dist/auth/account_schema.d.ts +27 -9
  25. package/dist/auth/account_schema.d.ts.map +1 -1
  26. package/dist/auth/account_schema.js +23 -3
  27. package/dist/auth/actor_search_actions.d.ts +3 -3
  28. package/dist/auth/actor_search_actions.d.ts.map +1 -1
  29. package/dist/auth/actor_search_actions.js +10 -6
  30. package/dist/auth/admin_action_specs.d.ts +0 -2
  31. package/dist/auth/admin_action_specs.d.ts.map +1 -1
  32. package/dist/auth/api_token_queries.d.ts +9 -4
  33. package/dist/auth/api_token_queries.d.ts.map +1 -1
  34. package/dist/auth/api_token_queries.js +36 -8
  35. package/dist/auth/app_settings_queries.d.ts +8 -0
  36. package/dist/auth/app_settings_queries.d.ts.map +1 -1
  37. package/dist/auth/app_settings_queries.js +16 -3
  38. package/dist/auth/audit_log_queries.d.ts +21 -3
  39. package/dist/auth/audit_log_queries.d.ts.map +1 -1
  40. package/dist/auth/audit_log_queries.js +38 -7
  41. package/dist/auth/audit_log_schema.d.ts +0 -1
  42. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  43. package/dist/auth/auth_ddl.d.ts +6 -0
  44. package/dist/auth/auth_ddl.d.ts.map +1 -1
  45. package/dist/auth/auth_ddl.js +9 -0
  46. package/dist/auth/cell_actions.d.ts.map +1 -1
  47. package/dist/auth/cell_actions.js +3 -8
  48. package/dist/auth/cell_audit_actions.d.ts.map +1 -1
  49. package/dist/auth/cell_audit_actions.js +1 -1
  50. package/dist/auth/cell_field_actions.js +1 -1
  51. package/dist/auth/cell_grant_actions.js +1 -1
  52. package/dist/auth/cell_item_actions.js +1 -1
  53. package/dist/auth/invite_queries.d.ts +10 -0
  54. package/dist/auth/invite_queries.d.ts.map +1 -1
  55. package/dist/auth/invite_queries.js +28 -7
  56. package/dist/auth/migrations.d.ts.map +1 -1
  57. package/dist/auth/migrations.js +10 -0
  58. package/dist/auth/role_grant_offer_ddl.d.ts +34 -2
  59. package/dist/auth/role_grant_offer_ddl.d.ts.map +1 -1
  60. package/dist/auth/role_grant_offer_ddl.js +61 -2
  61. package/dist/auth/role_grant_offer_queries.d.ts.map +1 -1
  62. package/dist/auth/role_grant_offer_queries.js +48 -53
  63. package/dist/auth/role_grant_queries.d.ts +18 -0
  64. package/dist/auth/role_grant_queries.d.ts.map +1 -1
  65. package/dist/auth/role_grant_queries.js +58 -18
  66. package/dist/auth/session_queries.d.ts +12 -3
  67. package/dist/auth/session_queries.d.ts.map +1 -1
  68. package/dist/auth/session_queries.js +26 -6
  69. package/dist/db/CLAUDE.md +34 -0
  70. package/dist/db/cell_audit_queries.d.ts.map +1 -1
  71. package/dist/db/cell_audit_queries.js +3 -1
  72. package/dist/db/cell_field_queries.d.ts +12 -3
  73. package/dist/db/cell_field_queries.d.ts.map +1 -1
  74. package/dist/db/cell_field_queries.js +25 -7
  75. package/dist/db/cell_grant_queries.d.ts +10 -2
  76. package/dist/db/cell_grant_queries.d.ts.map +1 -1
  77. package/dist/db/cell_grant_queries.js +29 -11
  78. package/dist/db/cell_item_queries.d.ts +13 -2
  79. package/dist/db/cell_item_queries.d.ts.map +1 -1
  80. package/dist/db/cell_item_queries.js +27 -7
  81. package/dist/db/cell_queries.d.ts +32 -3
  82. package/dist/db/cell_queries.d.ts.map +1 -1
  83. package/dist/db/cell_queries.js +57 -22
  84. package/dist/db/fact_queries.d.ts +7 -0
  85. package/dist/db/fact_queries.d.ts.map +1 -1
  86. package/dist/db/fact_queries.js +27 -4
  87. package/dist/db/schema_ready.d.ts +6 -1
  88. package/dist/db/schema_ready.d.ts.map +1 -1
  89. package/dist/db/schema_ready.js +6 -2
  90. package/dist/db/sql_columns.d.ts +144 -0
  91. package/dist/db/sql_columns.d.ts.map +1 -0
  92. package/dist/db/sql_columns.js +164 -0
  93. package/dist/db/sql_identifier.d.ts +6 -5
  94. package/dist/db/sql_identifier.d.ts.map +1 -1
  95. package/dist/db/sql_identifier.js +6 -5
  96. package/dist/http/CLAUDE.md +4 -2
  97. package/dist/http/jsonrpc_helpers.d.ts +13 -0
  98. package/dist/http/jsonrpc_helpers.d.ts.map +1 -1
  99. package/dist/http/jsonrpc_helpers.js +13 -0
  100. package/dist/http/route_spec.d.ts +2 -2
  101. package/dist/server/app_server.d.ts.map +1 -1
  102. package/dist/server/app_server.js +1 -2
  103. package/dist/server/serve_fact_route.d.ts.map +1 -1
  104. package/dist/server/serve_fact_route.js +7 -2
  105. package/dist/testing/CLAUDE.md +55 -28
  106. package/dist/testing/admin_integration.d.ts +0 -3
  107. package/dist/testing/admin_integration.d.ts.map +1 -1
  108. package/dist/testing/admin_integration.js +0 -1
  109. package/dist/testing/adversarial_404.d.ts.map +1 -1
  110. package/dist/testing/adversarial_404.js +4 -1
  111. package/dist/testing/adversarial_input.d.ts.map +1 -1
  112. package/dist/testing/adversarial_input.js +5 -4
  113. package/dist/testing/app_server.d.ts +4 -3
  114. package/dist/testing/app_server.d.ts.map +1 -1
  115. package/dist/testing/app_server.js +11 -5
  116. package/dist/testing/attack_surface.d.ts +16 -0
  117. package/dist/testing/attack_surface.d.ts.map +1 -1
  118. package/dist/testing/attack_surface.js +36 -13
  119. package/dist/testing/audit_completeness.d.ts +0 -3
  120. package/dist/testing/audit_completeness.d.ts.map +1 -1
  121. package/dist/testing/audit_completeness.js +0 -1
  122. package/dist/testing/auth_apps.d.ts +9 -2
  123. package/dist/testing/auth_apps.d.ts.map +1 -1
  124. package/dist/testing/auth_apps.js +9 -2
  125. package/dist/testing/cross_backend/account_lifecycle.d.ts +5 -4
  126. package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -1
  127. package/dist/testing/cross_backend/account_lifecycle.js +7 -0
  128. package/dist/testing/cross_backend/action_manifest.d.ts +19 -3
  129. package/dist/testing/cross_backend/action_manifest.d.ts.map +1 -1
  130. package/dist/testing/cross_backend/action_manifest.js +10 -4
  131. package/dist/testing/cross_backend/action_manifest_parity.d.ts +9 -3
  132. package/dist/testing/cross_backend/action_manifest_parity.d.ts.map +1 -1
  133. package/dist/testing/cross_backend/action_manifest_parity.js +6 -0
  134. package/dist/testing/cross_backend/actor_lookup.d.ts +3 -2
  135. package/dist/testing/cross_backend/actor_lookup.d.ts.map +1 -1
  136. package/dist/testing/cross_backend/body_size.d.ts +4 -2
  137. package/dist/testing/cross_backend/body_size.d.ts.map +1 -1
  138. package/dist/testing/cross_backend/body_size.js +1 -1
  139. package/dist/testing/cross_backend/cell_crud.d.ts +2 -2
  140. package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
  141. package/dist/testing/cross_backend/cell_crud.js +10 -0
  142. package/dist/testing/cross_backend/cell_gated_create.d.ts +3 -3
  143. package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
  144. package/dist/testing/cross_backend/cell_grant_role.d.ts +2 -2
  145. package/dist/testing/cross_backend/cell_grant_role.d.ts.map +1 -1
  146. package/dist/testing/cross_backend/cell_relations.d.ts +2 -2
  147. package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -1
  148. package/dist/testing/cross_backend/expected_schema.json +1 -1
  149. package/dist/testing/cross_backend/fact_serving.d.ts +2 -2
  150. package/dist/testing/cross_backend/fact_serving.d.ts.map +1 -1
  151. package/dist/testing/cross_backend/make_cross_backend_project.d.ts +17 -2
  152. package/dist/testing/cross_backend/make_cross_backend_project.d.ts.map +1 -1
  153. package/dist/testing/cross_backend/make_cross_backend_project.js +23 -3
  154. package/dist/testing/cross_backend/origin.d.ts +4 -2
  155. package/dist/testing/cross_backend/origin.d.ts.map +1 -1
  156. package/dist/testing/cross_backend/origin.js +1 -1
  157. package/dist/testing/cross_backend/peer_ping_ws.d.ts.map +1 -1
  158. package/dist/testing/cross_backend/peer_ping_ws.js +11 -7
  159. package/dist/testing/cross_backend/query_shape.d.ts +6 -5
  160. package/dist/testing/cross_backend/query_shape.d.ts.map +1 -1
  161. package/dist/testing/cross_backend/ready.d.ts +2 -2
  162. package/dist/testing/cross_backend/ready.d.ts.map +1 -1
  163. package/dist/testing/cross_backend/role_grant_offer_enumeration.d.ts.map +1 -1
  164. package/dist/testing/cross_backend/role_grant_offer_enumeration.js +7 -1
  165. package/dist/testing/cross_backend/setup.d.ts +26 -6
  166. package/dist/testing/cross_backend/setup.d.ts.map +1 -1
  167. package/dist/testing/cross_backend/standard.d.ts +5 -1
  168. package/dist/testing/cross_backend/standard.d.ts.map +1 -1
  169. package/dist/testing/cross_backend/standard.js +1 -5
  170. package/dist/testing/cross_backend/testing_backdoor.js +1 -1
  171. package/dist/testing/cross_backend/testing_reset_actions.d.ts +5 -0
  172. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  173. package/dist/testing/cross_backend/token_lifetime.d.ts.map +1 -1
  174. package/dist/testing/cross_backend/token_lifetime.js +6 -0
  175. package/dist/testing/cross_backend/token_scope_surface.d.ts +2 -2
  176. package/dist/testing/cross_backend/token_scope_surface.d.ts.map +1 -1
  177. package/dist/testing/cross_backend/wire_shapes.d.ts +22 -0
  178. package/dist/testing/cross_backend/wire_shapes.d.ts.map +1 -0
  179. package/dist/testing/cross_backend/wire_shapes.js +41 -0
  180. package/dist/testing/data_exposure.d.ts +0 -3
  181. package/dist/testing/data_exposure.d.ts.map +1 -1
  182. package/dist/testing/data_exposure.js +0 -1
  183. package/dist/testing/db.d.ts +62 -7
  184. package/dist/testing/db.d.ts.map +1 -1
  185. package/dist/testing/db.js +86 -28
  186. package/dist/testing/integration.d.ts.map +1 -1
  187. package/dist/testing/integration.js +15 -2
  188. package/dist/testing/round_trip.d.ts +0 -3
  189. package/dist/testing/round_trip.d.ts.map +1 -1
  190. package/dist/testing/round_trip.js +0 -4
  191. package/dist/testing/rpc_attack_surface.js +2 -2
  192. package/dist/testing/rpc_round_trip.d.ts +0 -3
  193. package/dist/testing/rpc_round_trip.d.ts.map +1 -1
  194. package/dist/testing/rpc_round_trip.js +0 -1
  195. package/dist/testing/schema_generators.d.ts +15 -0
  196. package/dist/testing/schema_generators.d.ts.map +1 -1
  197. package/dist/testing/schema_generators.js +45 -11
  198. package/dist/testing/standard.d.ts +5 -1
  199. package/dist/testing/standard.d.ts.map +1 -1
  200. package/dist/testing/standard.js +2 -7
  201. package/dist/timestamp.d.ts +58 -0
  202. package/dist/timestamp.d.ts.map +1 -0
  203. package/dist/timestamp.js +57 -0
  204. package/dist/ui/AdminOverview.svelte +5 -10
  205. package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
  206. package/dist/ui/RoleGrantOfferHistory.svelte +3 -1
  207. package/dist/ui/RoleGrantOfferHistory.svelte.d.ts.map +1 -1
  208. package/dist/ui/role_grant_offers_state.svelte.d.ts.map +1 -1
  209. package/dist/ui/role_grant_offers_state.svelte.js +6 -2
  210. package/package.json +1 -1
@@ -15,27 +15,64 @@ export interface DbFactory {
15
15
  skip_reason?: string;
16
16
  }
17
17
  /**
18
- * Reset a PGlite database to a clean state by dropping and recreating the public schema.
18
+ * Reset a test database to a clean state by dropping and recreating the public schema.
19
19
  *
20
20
  * Removes all tables, sequences, indexes, types, and functions.
21
- * The database instance remains usable after reset.
21
+ * The database instance remains usable after reset. Despite the name it issues
22
+ * plain SQL, so it also resets the other drivers the test factories hand out.
22
23
  *
23
24
  * @mutates db - drops the `public` schema and recreates it; all rows in all
24
25
  * tables are gone after this returns.
25
26
  */
26
27
  export declare const reset_pglite: (db: Db) => Promise<void>;
28
+ /**
29
+ * Builds a `DbFactory` from a schema initializer — `create_pglite_factory`'s own shape.
30
+ */
31
+ export type DbFactoryBuilder = (init_schema: (db: Db) => Promise<void>) => DbFactory;
32
+ /**
33
+ * Options for `create_pglite_factory`.
34
+ */
35
+ export interface CreatePgliteFactoryOptions {
36
+ /**
37
+ * Whether an installed substitute (`set_substitute_db_factory`) may stand in
38
+ * for the PGlite factory at this call site. Defaults to `true`; pass `false`
39
+ * where PGlite itself is the subject under test.
40
+ */
41
+ substitutable?: boolean;
42
+ }
43
+ /**
44
+ * Install a builder that stands in for `create_pglite_factory`, so suites
45
+ * written against PGlite can run against another driver without being edited.
46
+ *
47
+ * Injected rather than selected here: the drivers a substitute wraps live in
48
+ * test-side code this shipped module cannot import. A call site that passes
49
+ * `{substitutable: false}` keeps PGlite regardless. Pass `null` to uninstall.
50
+ *
51
+ * The slot is read when a factory is built, not when it creates a database,
52
+ * so install before the first `create_pglite_factory` call — a vitest
53
+ * `setupFiles` entry, which runs ahead of every test file's imports, is the
54
+ * shape fuz_app's own `db` project uses. A factory built earlier (including
55
+ * the module-level fallback in `testing/app_server.ts`) keeps the driver it
56
+ * was built with, and uninstalling does not revert it.
57
+ *
58
+ * @param create_factory - the builder to install, or `null` to remove the installed one
59
+ */
60
+ export declare const set_substitute_db_factory: (create_factory: DbFactoryBuilder | null) => void;
27
61
  /**
28
62
  * Create a pglite (in-memory) database factory for tests.
29
63
  *
30
64
  * Always enabled — no external dependencies required.
31
- * Shares a single PGlite WASM instance across all factories in the same
32
- * vitest worker thread (one test file). Subsequent `create()` calls reset
33
- * the schema via `DROP SCHEMA public CASCADE` instead of paying the WASM
34
- * cold-start cost again.
65
+ * Shares a single PGlite WASM instance across the PGlite factories this module builds.
66
+ * Subsequent `create()` calls reset the schema via `DROP SCHEMA public CASCADE`
67
+ * instead of paying the WASM cold-start cost again.
68
+ *
69
+ * Returns the installed substitute's factory instead when one is installed and
70
+ * `options.substitutable` is not `false` — see `set_substitute_db_factory`.
35
71
  *
36
72
  * @param init_schema - callback to initialize the database schema
73
+ * @param options - `substitutable: false` pins this call site to PGlite
37
74
  */
38
- export declare const create_pglite_factory: (init_schema: (db: Db) => Promise<void>) => DbFactory;
75
+ export declare const create_pglite_factory: (init_schema: (db: Db) => Promise<void>, options?: CreatePgliteFactoryOptions) => DbFactory;
39
76
  /**
40
77
  * Create a pg (PostgreSQL) database factory for tests.
41
78
  *
@@ -103,4 +140,22 @@ export declare const create_describe_db: (factories: DbFactory | Array<DbFactory
103
140
  * Log factory status to console.
104
141
  */
105
142
  export declare const log_db_factory_status: (factories: Array<DbFactory>) => void;
143
+ /**
144
+ * Assert a `*_COLUMNS` projection const names exactly the live columns of
145
+ * `table` — the drift guard for an exported const (`ACCOUNT_COLUMNS`,
146
+ * `CELL_COLUMNS`, a consumer's own).
147
+ *
148
+ * This is the reverse of the fail-loud read: a named projection makes a
149
+ * *dropped* column fail loud at query time, but a column *added* to the table
150
+ * and not to the projection would silently vanish from every row read. Both
151
+ * directions are covered by the equality here. It guards one const; it can't
152
+ * see a table that has *no* const — for that, keep a table → const registry
153
+ * and assert its key set against `query_public_columns` (fuz_app's own is
154
+ * `src/test/db/column_projections.db.test.ts`).
155
+ *
156
+ * @param db - a bootstrapped test database
157
+ * @param table - the `public`-schema table the const projects
158
+ * @param columns - the projection const's column names
159
+ */
160
+ export declare const assert_columns_match_live: (db: Db, table: string, columns: ReadonlyArray<string>) => Promise<void>;
106
161
  //# sourceMappingURL=db.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"db.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/db.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AA8B7B,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAKtC;;GAEG;AACH,eAAO,MAAM,KAAK,SAA4B,CAAC;AAE/C;;GAEG;AACH,MAAM,WAAW,SAAS;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,OAAO,CAAC,EAAE,CAAC,CAAC;IAC1B,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,IAAI,EAAE,OAAO,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,GAAU,IAAI,EAAE,KAAG,OAAO,CAAC,IAAI,CAGvD,CAAC;AAMF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,qBAAqB,GAAI,aAAa,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,KAAG,SAkB7E,CAAC;AAEH;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,iBAAiB,GAC7B,aAAa,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,EACtC,WAAW,MAAM,KACf,SAgEF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,UAQhC,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,gCAAgC,UAAyC,CAAC;AAEvF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,gBAAgB,GAAU,IAAI,EAAE,KAAG,OAAO,CAAC,IAAI,CAG3D,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,kBAAkB,GAC9B,WAAW,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC,EACvC,iBAAiB,KAAK,CAAC,MAAM,CAAC,KAC5B,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,KAAK,IAAI,CAwBzD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,qBAAqB,GAAI,WAAW,KAAK,CAAC,SAAS,CAAC,KAAG,IAQnE,CAAC"}
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/db.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AA8B7B,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAMtC;;GAEG;AACH,eAAO,MAAM,KAAK,SAA4B,CAAC;AAE/C;;GAEG;AACH,MAAM,WAAW,SAAS;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,OAAO,CAAC,EAAE,CAAC,CAAC;IAC1B,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,IAAI,EAAE,OAAO,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,YAAY,GAAU,IAAI,EAAE,KAAG,OAAO,CAAC,IAAI,CAGvD,CAAC;AAQF;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,WAAW,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,KAAK,SAAS,CAAC;AAErF;;GAEG;AACH,MAAM,WAAW,0BAA0B;IAC1C;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACxB;AAKD;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,yBAAyB,GAAI,gBAAgB,gBAAgB,GAAG,IAAI,KAAG,IAEnF,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,qBAAqB,GACjC,aAAa,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,EACtC,UAAU,0BAA0B,KAClC,SAyBF,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,iBAAiB,GAC7B,aAAa,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,EACtC,WAAW,MAAM,KACf,SAgEF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,UAQhC,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,gCAAgC,UAAyC,CAAC;AAEvF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,gBAAgB,GAAU,IAAI,EAAE,KAAG,OAAO,CAAC,IAAI,CAG3D,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,kBAAkB,GAC9B,WAAW,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC,EACvC,iBAAiB,KAAK,CAAC,MAAM,CAAC,KAC5B,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,KAAK,IAAI,CAwBzD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,qBAAqB,GAAI,WAAW,KAAK,CAAC,SAAS,CAAC,KAAG,IAQnE,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,yBAAyB,GACrC,IAAI,EAAE,EACN,OAAO,MAAM,EACb,SAAS,aAAa,CAAC,MAAM,CAAC,KAC5B,OAAO,CAAC,IAAI,CAId,CAAC"}
@@ -22,20 +22,22 @@ import './assert_dev_env.js';
22
22
  *
23
23
  * @module
24
24
  */
25
- import { describe, beforeAll, beforeEach, afterAll } from 'vitest';
25
+ import { describe, beforeAll, beforeEach, afterAll, assert } from 'vitest';
26
26
  import { to_error_message } from '@fuzdev/fuz_util/error.ts';
27
27
  import { create_pglite_db } from '../db/db_pglite.js';
28
28
  import { assert_valid_sql_identifier } from '../db/sql_identifier.js';
29
29
  import { create_pg_db, register_pg_type_parsers } from '../db/db_pg.js';
30
+ import { query_public_columns } from '../db/schema_ready.js';
30
31
  /**
31
32
  * CI detection — `CI=true` is set automatically by GitHub Actions, GitLab CI, etc.
32
33
  */
33
34
  export const IS_CI = process.env.CI === 'true';
34
35
  /**
35
- * Reset a PGlite database to a clean state by dropping and recreating the public schema.
36
+ * Reset a test database to a clean state by dropping and recreating the public schema.
36
37
  *
37
38
  * Removes all tables, sequences, indexes, types, and functions.
38
- * The database instance remains usable after reset.
39
+ * The database instance remains usable after reset. Despite the name it issues
40
+ * plain SQL, so it also resets the other drivers the test factories hand out.
39
41
  *
40
42
  * @mutates db - drops the `public` schema and recreates it; all rows in all
41
43
  * tables are gone after this returns.
@@ -44,40 +46,74 @@ export const reset_pglite = async (db) => {
44
46
  await db.query('DROP SCHEMA public CASCADE');
45
47
  await db.query('CREATE SCHEMA public');
46
48
  };
47
- // Module-level PGlite cache — shared across all factories in the same vitest worker (one test file).
48
- // Each vitest file runs in its own worker thread, so no cross-file contamination.
49
+ // Module-level PGlite cache — one instance shared by the PGlite factories this
50
+ // module builds, for as long as the module is loaded. Under the `db` project's
51
+ // `isolate: false` + `fileParallelism: false` that is the whole run; a project
52
+ // that isolates its files gets one per worker instead.
49
53
  let module_db = null;
54
+ // The installed stand-in for `create_pglite_factory`, `null` when none is installed.
55
+ let substitute_db_factory = null;
56
+ /**
57
+ * Install a builder that stands in for `create_pglite_factory`, so suites
58
+ * written against PGlite can run against another driver without being edited.
59
+ *
60
+ * Injected rather than selected here: the drivers a substitute wraps live in
61
+ * test-side code this shipped module cannot import. A call site that passes
62
+ * `{substitutable: false}` keeps PGlite regardless. Pass `null` to uninstall.
63
+ *
64
+ * The slot is read when a factory is built, not when it creates a database,
65
+ * so install before the first `create_pglite_factory` call — a vitest
66
+ * `setupFiles` entry, which runs ahead of every test file's imports, is the
67
+ * shape fuz_app's own `db` project uses. A factory built earlier (including
68
+ * the module-level fallback in `testing/app_server.ts`) keeps the driver it
69
+ * was built with, and uninstalling does not revert it.
70
+ *
71
+ * @param create_factory - the builder to install, or `null` to remove the installed one
72
+ */
73
+ export const set_substitute_db_factory = (create_factory) => {
74
+ substitute_db_factory = create_factory;
75
+ };
50
76
  /**
51
77
  * Create a pglite (in-memory) database factory for tests.
52
78
  *
53
79
  * Always enabled — no external dependencies required.
54
- * Shares a single PGlite WASM instance across all factories in the same
55
- * vitest worker thread (one test file). Subsequent `create()` calls reset
56
- * the schema via `DROP SCHEMA public CASCADE` instead of paying the WASM
57
- * cold-start cost again.
80
+ * Shares a single PGlite WASM instance across the PGlite factories this module builds.
81
+ * Subsequent `create()` calls reset the schema via `DROP SCHEMA public CASCADE`
82
+ * instead of paying the WASM cold-start cost again.
83
+ *
84
+ * Returns the installed substitute's factory instead when one is installed and
85
+ * `options.substitutable` is not `false` — see `set_substitute_db_factory`.
58
86
  *
59
87
  * @param init_schema - callback to initialize the database schema
88
+ * @param options - `substitutable: false` pins this call site to PGlite
60
89
  */
61
- export const create_pglite_factory = (init_schema) => ({
62
- name: 'pglite',
63
- skip: false,
64
- async create() {
65
- if (module_db) {
66
- await reset_pglite(module_db);
67
- }
68
- else {
69
- const { PGlite } = await import('@electric-sql/pglite');
70
- const pglite = new PGlite();
71
- module_db = create_pglite_db(pglite).db;
72
- }
73
- await init_schema(module_db);
74
- return module_db;
75
- },
76
- async close() {
77
- // No-op: shared instance lives for the worker thread's lifetime.
78
- // PGlite is cleaned up when the vitest worker exits.
90
+ export const create_pglite_factory = (init_schema, options) => {
91
+ if (substitute_db_factory && options?.substitutable !== false) {
92
+ return substitute_db_factory(init_schema);
79
93
  }
80
- });
94
+ return {
95
+ name: 'pglite',
96
+ skip: false,
97
+ async create() {
98
+ if (module_db) {
99
+ await reset_pglite(module_db);
100
+ }
101
+ else {
102
+ const { PGlite } = await import('@electric-sql/pglite');
103
+ const pglite = new PGlite();
104
+ module_db = create_pglite_db(pglite).db;
105
+ }
106
+ await init_schema(module_db);
107
+ return module_db;
108
+ },
109
+ async close() {
110
+ // No-op: the instance is the module-level cache above, so it lives as long
111
+ // as this module stays loaded — under the `db` project's `isolate: false` +
112
+ // `fileParallelism: false` that is the whole run. Nothing frees the PGlite
113
+ // instance earlier.
114
+ }
115
+ };
116
+ };
81
117
  /**
82
118
  * Create a pg (PostgreSQL) database factory for tests.
83
119
  *
@@ -246,3 +282,25 @@ export const log_db_factory_status = (factories) => {
246
282
  const skipped = factories.filter((f) => f.skip).map((f) => `${f.name} (${f.skip_reason})`);
247
283
  console.log(`[db tests] drivers: ${enabled.join(', ')}${skipped.length ? ` | skipped: ${skipped.join(', ')}` : ''}`);
248
284
  };
285
+ /**
286
+ * Assert a `*_COLUMNS` projection const names exactly the live columns of
287
+ * `table` — the drift guard for an exported const (`ACCOUNT_COLUMNS`,
288
+ * `CELL_COLUMNS`, a consumer's own).
289
+ *
290
+ * This is the reverse of the fail-loud read: a named projection makes a
291
+ * *dropped* column fail loud at query time, but a column *added* to the table
292
+ * and not to the projection would silently vanish from every row read. Both
293
+ * directions are covered by the equality here. It guards one const; it can't
294
+ * see a table that has *no* const — for that, keep a table → const registry
295
+ * and assert its key set against `query_public_columns` (fuz_app's own is
296
+ * `src/test/db/column_projections.db.test.ts`).
297
+ *
298
+ * @param db - a bootstrapped test database
299
+ * @param table - the `public`-schema table the const projects
300
+ * @param columns - the projection const's column names
301
+ */
302
+ export const assert_columns_match_live = async (db, table, columns) => {
303
+ const live = await query_public_columns(db, { table });
304
+ assert.ok(live[table], `table "${table}" is not in the live public schema`);
305
+ assert.deepEqual([...columns].sort(), live[table]);
306
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"integration.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/integration.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAsB7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAMhE,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AAkB1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAW,KAAK,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACpF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAG1D;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC9C;;;;OAIG;IACH,UAAU,EAAE,SAAS,CAAC;IACtB;;;;;;;;;OASG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,kEAAkE;IAClE,YAAY,EAAE,mBAAmB,CAAC;IAClC;;;;OAIG;IACH,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;;;;;;;;;;OAYG;IACH,aAAa,EAAE,uBAAuB,CAAC;IACvC;;;;;;;OAOG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,mCAAmC,GAC/C,SAAS,8BAA8B,KACrC,IA07CF,CAAC"}
1
+ {"version":3,"file":"integration.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/integration.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAsB7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAMhE,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AAkB1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAW,KAAK,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAKpF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAG1D;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC9C;;;;OAIG;IACH,UAAU,EAAE,SAAS,CAAC;IACtB;;;;;;;;;OASG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,kEAAkE;IAClE,YAAY,EAAE,mBAAmB,CAAC;IAClC;;;;OAIG;IACH,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;;;;;;;;;;OAYG;IACH,aAAa,EAAE,uBAAuB,CAAC;IACvC;;;;;;;OAOG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,mCAAmC,GAC/C,SAAS,8BAA8B,KACrC,IAs8CF,CAAC"}
@@ -25,6 +25,7 @@ import { account_verify_action_spec, account_session_list_action_spec, account_s
25
25
  import { invite_create_action_spec } from '../auth/admin_action_specs.js';
26
26
  import { LoginOutput, AccountStatusOutput } from '../auth/account_route_schema.js';
27
27
  import { test_if } from './cross_backend/capabilities.js';
28
+ import { assert_iso8601_seconds, assert_iso8601_seconds_nullable } from './cross_backend/wire_shapes.js';
28
29
  import { DEFAULT_TEST_PASSWORD } from './test_credentials.js';
29
30
  /**
30
31
  * Standard integration test suite for fuz_app auth routes.
@@ -343,6 +344,13 @@ export const describe_standard_integration_tests = (options) => {
343
344
  // Single-actor keeper: actor resolved, role_grants populated.
344
345
  assert.ok(parsed.actor, 'single-actor keeper must resolve a non-null actor');
345
346
  assert.ok(parsed.role_grants.length > 0, 'single-actor keeper must have populated role_grants');
347
+ // Byte-shape gate: `z.string()` accepts a millisecond stamp too,
348
+ // so the schema parse above can't tell the two spines apart.
349
+ assert_iso8601_seconds(parsed.account.created_at, 'account.created_at');
350
+ for (const grant of parsed.role_grants) {
351
+ assert_iso8601_seconds(grant.created_at, 'role_grant.created_at');
352
+ assert_iso8601_seconds_nullable(grant.expires_at, 'role_grant.expires_at');
353
+ }
346
354
  });
347
355
  });
348
356
  // --- 2. Cookie attributes ---
@@ -419,7 +427,12 @@ export const describe_standard_integration_tests = (options) => {
419
427
  });
420
428
  assert.ok(list_res.ok, 'account_session_list should succeed');
421
429
  assert.ok(list_res.result.sessions.length >= 1);
422
- const session_id = list_res.result.sessions[0].id;
430
+ const session = list_res.result.sessions[0];
431
+ // Byte-shape gate on the session timestamps — see the account-status
432
+ // case for why the output schema alone can't catch the divergence.
433
+ assert_iso8601_seconds(session.created_at, 'session.created_at');
434
+ assert_iso8601_seconds(session.expires_at, 'session.expires_at');
435
+ const session_id = session.id;
423
436
  // Revoke that session by ID
424
437
  const revoke_res = await rpc_call_for_spec({
425
438
  app: { request: fixture.transport },
@@ -691,7 +704,7 @@ export const describe_standard_integration_tests = (options) => {
691
704
  // phase doesn't first demand an acting actor → `actor_required`)
692
705
  // and input/param/query-free (params and query still validate ahead
693
706
  // of the auth phase — only body validation moved behind the gates,
694
- // per the 401 → authz → 403 → 400 order). Admin surfaces are
707
+ // per the 401 → authz → 403 → 429 → 400 order). Admin surfaces are
695
708
  // RPC-first now; a consumer may expose no such REST route (fuz_app
696
709
  // itself, zzz) — then this REST-era check is a no-op and RPC denial
697
710
  // is covered by `describe_rpc_attack_surface_tests`.
@@ -1,5 +1,4 @@
1
1
  import './assert_dev_env.js';
2
- import type { BackendCapabilities } from './cross_backend/capabilities.js';
3
2
  import type { SetupTest, TestFixture } from './cross_backend/setup.js';
4
3
  import type { AppSurfaceSpec } from '../http/surface.js';
5
4
  /** Options for `describe_round_trip_validation`. */
@@ -16,8 +15,6 @@ export interface RoundTripTestOptions {
16
15
  * TS by the consumer; same shape for in-process and cross-process tests.
17
16
  */
18
17
  surface_source: AppSurfaceSpec;
19
- /** Backend capability declarations — see `cross_backend/capabilities.ts`. */
20
- capabilities: BackendCapabilities;
21
18
  /** Routes to skip, in `'METHOD /path'` format. */
22
19
  skip_routes?: Array<string>;
23
20
  /** Override generated bodies for specific routes (`'METHOD /path'` → body). */
@@ -1 +1 @@
1
- {"version":3,"file":"round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/round_trip.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AA2B7B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAC3E,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAGzD,oDAAoD;AACpD,MAAM,WAAW,oBAAoB;IACpC;;;;;OAKG;IACH,UAAU,EAAE,SAAS,CAAC;IACtB;;;OAGG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,6EAA6E;IAC7E,YAAY,EAAE,mBAAmB,CAAC;IAClC,kDAAkD;IAClD,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC5B,+EAA+E;IAC/E,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACvD;;;;;;;;;;;;;OAaG;IACH,gBAAgB,CAAC,EAAE,GAAG,CACrB,MAAM,EACN,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CACnF,CAAC;CACF;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,8BAA8B,GAAI,SAAS,oBAAoB,KAAG,IA6I9E,CAAC"}
1
+ {"version":3,"file":"round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/round_trip.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AA2B7B,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAGzD,oDAAoD;AACpD,MAAM,WAAW,oBAAoB;IACpC;;;;;OAKG;IACH,UAAU,EAAE,SAAS,CAAC;IACtB;;;OAGG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,kDAAkD;IAClD,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC5B,+EAA+E;IAC/E,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACvD;;;;;;;;;;;;;OAaG;IACH,gBAAgB,CAAC,EAAE,GAAG,CACrB,MAAM,EACN,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CACnF,CAAC;CACF;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,8BAA8B,GAAI,SAAS,oBAAoB,KAAG,IAyI9E,CAAC"}
@@ -36,10 +36,6 @@ import { resolve_valid_path, generate_valid_body } from './schema_generators.js'
36
36
  export const describe_round_trip_validation = (options) => {
37
37
  const describe_time_specs = options.surface_source.route_specs;
38
38
  const skip_set = new Set(options.skip_routes);
39
- // `capabilities` is currently unused by this suite (no in-process-only
40
- // reads, no transport-gated cases) but stays on the options for
41
- // uniformity with the other Tier 1 suites.
42
- void options.capabilities;
43
39
  describe('round-trip validation', () => {
44
40
  let fixture;
45
41
  let authed_account;
@@ -84,8 +84,8 @@ const describe_rpc_auth = (options) => {
84
84
  if (!app)
85
85
  throw new Error(`No test app for role '${wrong_role}'`);
86
86
  // Send valid params so the probe varies only the role. Dispatcher
87
- // ordering is 401 → authz → 403 → 400, so the role gate trips
88
- // ahead of validation regardless; valid params also keep the
87
+ // ordering is 401 → authz → 403 → 429 → 400, so the role gate
88
+ // trips ahead of validation regardless; valid params also keep the
89
89
  // authorization phase from answering `actor_required` first.
90
90
  const action = find_rpc_action(rpc_endpoint_specs, endpoint.path, method.name);
91
91
  const params = action ? generate_valid_body(action.spec.input) : undefined;
@@ -1,7 +1,6 @@
1
1
  import './assert_dev_env.js';
2
2
  import type { AppSurfaceSpec } from '../http/surface.js';
3
3
  import { type RpcEndpointsSuiteOption } from './rpc_helpers.js';
4
- import type { BackendCapabilities } from './cross_backend/capabilities.js';
5
4
  import type { SetupTest, TestFixture } from './cross_backend/setup.js';
6
5
  import type { SessionOptions } from '../auth/session_cookie.js';
7
6
  /** Options for `describe_rpc_round_trip_tests`. */
@@ -14,8 +13,6 @@ export interface RpcRoundTripTestOptions {
14
13
  * in-process and cross-process tests.
15
14
  */
16
15
  surface_source: AppSurfaceSpec;
17
- /** Backend capability declarations. */
18
- capabilities: BackendCapabilities;
19
16
  /**
20
17
  * Session config — only needed to resolve factory-form `rpc_endpoints`
21
18
  * against a stub `AppServerContext` at setup time (the actions' input
@@ -1 +1 @@
1
- {"version":3,"file":"rpc_round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/rpc_round_trip.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAyB7B,OAAO,KAAK,EAAE,cAAc,EAAuB,MAAM,oBAAoB,CAAC;AAE9E,OAAO,EAQN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAC3E,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAEhE,mDAAmD;AACnD,MAAM,WAAW,uBAAuB;IACvC,kEAAkE;IAClE,UAAU,EAAE,SAAS,CAAC;IACtB;;;;OAIG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,uCAAuC;IACvC,YAAY,EAAE,mBAAmB,CAAC;IAClC;;;;;OAKG;IACH,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;;;OAKG;IACH,aAAa,EAAE,uBAAuB,CAAC;IACvC,qDAAqD;IACrD,YAAY,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7B,6EAA6E;IAC7E,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACvD;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;CAC3F;AAoDD;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,6BAA6B,GAAI,SAAS,uBAAuB,KAAG,IAgKhF,CAAC"}
1
+ {"version":3,"file":"rpc_round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/rpc_round_trip.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAyB7B,OAAO,KAAK,EAAE,cAAc,EAAuB,MAAM,oBAAoB,CAAC;AAE9E,OAAO,EAQN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAEhE,mDAAmD;AACnD,MAAM,WAAW,uBAAuB;IACvC,kEAAkE;IAClE,UAAU,EAAE,SAAS,CAAC;IACtB;;;;OAIG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B;;;;;OAKG;IACH,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;;;OAKG;IACH,aAAa,EAAE,uBAAuB,CAAC;IACvC,qDAAqD;IACrD,YAAY,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7B,6EAA6E;IAC7E,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACvD;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;CAC3F;AAoDD;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,6BAA6B,GAAI,SAAS,uBAAuB,KAAG,IA+JhF,CAAC"}
@@ -88,7 +88,6 @@ export const describe_rpc_round_trip_tests = (options) => {
88
88
  // match what the running backend serves.
89
89
  const rpc_endpoints_for_setup = resolve_rpc_endpoints_for_setup(options.rpc_endpoints, options.session_options);
90
90
  const surface_rpc_endpoints = options.surface_source.surface.rpc_endpoints;
91
- void options.capabilities;
92
91
  describe('RPC round-trip validation', () => {
93
92
  let fixture;
94
93
  let authed_account;
@@ -20,6 +20,21 @@ export declare const generate_valid_value: (field: ZodFieldInfo, field_schema: z
20
20
  /**
21
21
  * Resolve a route path with valid-ish param values so params validation passes.
22
22
  * Used when testing input on routes that also have params.
23
+ *
24
+ * Without a params schema (or for a param the schema doesn't name) each
25
+ * `:name` becomes `test_<name>` — fine for an unconstrained segment. With
26
+ * one, `test_<name>` is still preferred wherever the field schema accepts
27
+ * it, because the param name in the URL is what makes a failure message
28
+ * readable. Only a segment that refuses it falls through: a `uuid` param
29
+ * takes the nil UUID (the fast path — by far the most common shape), and
30
+ * anything else is synthesized by `generate_valid_string`, so a
31
+ * format-constrained segment (a `blake3:…` fact hash, a `tok_…` id)
32
+ * resolves to a value its schema accepts instead of a `test_<name>` that
33
+ * 400s before the route's real gates run.
34
+ *
35
+ * The synthesized value is percent-encoded, and a value containing `/` is
36
+ * refused (it would silently add a path segment and route elsewhere) —
37
+ * both fall back to `test_<name>`.
23
38
  */
24
39
  export declare const resolve_valid_path: (path: string, params_schema?: z.ZodObject) => string;
25
40
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"schema_generators.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/schema_generators.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAKN,KAAK,YAAY,EACjB,MAAM,yBAAyB,CAAC;AAIjC;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,cAAc,CAAC,CAAC,OAAO,KAAG,MAAM,GAAG,IAkBhE,CAAC;AAsGF,qEAAqE;AACrE,eAAO,MAAM,oBAAoB,GAAI,OAAO,YAAY,EAAE,cAAc,CAAC,CAAC,OAAO,KAAG,OAgGnF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAAI,MAAM,MAAM,EAAE,gBAAgB,CAAC,CAAC,SAAS,KAAG,MAa9E,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,GAC/B,cAAc,CAAC,CAAC,OAAO,KACrB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SA+B5B,CAAC"}
1
+ {"version":3,"file":"schema_generators.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/schema_generators.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAKN,KAAK,YAAY,EACjB,MAAM,yBAAyB,CAAC;AAIjC;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,cAAc,CAAC,CAAC,OAAO,KAAG,MAAM,GAAG,IAkBhE,CAAC;AAmHF,qEAAqE;AACrE,eAAO,MAAM,oBAAoB,GAAI,OAAO,YAAY,EAAE,cAAc,CAAC,CAAC,OAAO,KAAG,OAgGnF,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,kBAAkB,GAAI,MAAM,MAAM,EAAE,gBAAgB,CAAC,CAAC,SAAS,KAAG,MAiB9E,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,GAC/B,cAAc,CAAC,CAAC,OAAO,KACrB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SA+B5B,CAAC"}
@@ -41,19 +41,32 @@ export const detect_format = (field_schema) => {
41
41
  return null;
42
42
  };
43
43
  /**
44
- * Extract a candidate value from a JSON Schema `pattern` when the shape is
45
- * a fixed-length hex character class — covers blake3 (64-char lowercase hex),
46
- * sha256, md5, and similar digest refinements. Returns `null` when the
47
- * pattern doesn't match the expected shape.
44
+ * Extract a candidate value from a JSON Schema `pattern` when the shape is a
45
+ * fixed-length hex character class, optionally behind a literal prefix —
46
+ * covers bare digests (blake3 64-char lowercase hex, sha256, md5) and the
47
+ * algorithm-tagged form the fact store addresses bytes by
48
+ * (`^blake3:[0-9a-f]{64}$` → `blake3:` + 64 zeros).
49
+ *
50
+ * Coverage today:
51
+ * - Optional prefix: alphanumeric, starting with a letter, ending in `:`
52
+ * (`blake3:`, `sha256:`). A `_`-joined prefix over a base64url class is
53
+ * the sibling shape — see `generate_prefix_slug_pattern_value`.
54
+ * - Character class must be exactly `[0-9a-f]` or `[0-9a-fA-F]` (Zod passes
55
+ * the regex source through verbatim — no character-class reordering).
56
+ * - Fixed-length quantifier `{N}`.
57
+ *
58
+ * Returns `null` when the pattern doesn't match the expected shape; the
59
+ * caller then falls through to its other candidates.
48
60
  */
49
61
  const generate_hex_pattern_value = (pattern) => {
50
- const match = /^\^\[0-9a-f(?:A-F)?\]\{(\d+)\}\$$/.exec(pattern);
62
+ const match = /^\^(?:([A-Za-z][A-Za-z0-9]*):)?\[0-9a-f(?:A-F)?\]\{(\d+)\}\$$/.exec(pattern);
51
63
  if (!match)
52
64
  return null;
53
- const n = Number(match[1]);
65
+ const n = Number(match[2]);
54
66
  if (!Number.isInteger(n) || n <= 0)
55
67
  return null;
56
- return '0'.repeat(n);
68
+ const prefix = match[1] === undefined ? '' : `${match[1]}:`;
69
+ return prefix + '0'.repeat(n);
57
70
  };
58
71
  /**
59
72
  * Extract a candidate value from a JSON Schema `pattern` when the shape is a
@@ -78,8 +91,8 @@ const generate_hex_pattern_value = (pattern) => {
78
91
  * doesn't match the detection regex. `x`-fill would still satisfy the
79
92
  * refinement if the detection were widened.
80
93
  * - No-prefix fixed-length slugs (e.g. `^[A-Za-z0-9_-]{43}$` — daemon
81
- * token shape) are not matched here; see `generate_hex_pattern_value`
82
- * for the hex variant.
94
+ * token shape) are not matched here; `generate_hex_pattern_value` is the
95
+ * hex sibling, and it covers both the bare and the `prefix:`-tagged form.
83
96
  * Widen the detection regex when a new branded shape surfaces.
84
97
  */
85
98
  const generate_prefix_slug_pattern_value = (pattern) => {
@@ -254,22 +267,43 @@ export const generate_valid_value = (field, field_schema) => {
254
267
  /**
255
268
  * Resolve a route path with valid-ish param values so params validation passes.
256
269
  * Used when testing input on routes that also have params.
270
+ *
271
+ * Without a params schema (or for a param the schema doesn't name) each
272
+ * `:name` becomes `test_<name>` — fine for an unconstrained segment. With
273
+ * one, `test_<name>` is still preferred wherever the field schema accepts
274
+ * it, because the param name in the URL is what makes a failure message
275
+ * readable. Only a segment that refuses it falls through: a `uuid` param
276
+ * takes the nil UUID (the fast path — by far the most common shape), and
277
+ * anything else is synthesized by `generate_valid_string`, so a
278
+ * format-constrained segment (a `blake3:…` fact hash, a `tok_…` id)
279
+ * resolves to a value its schema accepts instead of a `test_<name>` that
280
+ * 400s before the route's real gates run.
281
+ *
282
+ * The synthesized value is percent-encoded, and a value containing `/` is
283
+ * refused (it would silently add a path segment and route elsewhere) —
284
+ * both fall back to `test_<name>`.
257
285
  */
258
286
  export const resolve_valid_path = (path, params_schema) => {
259
287
  if (!params_schema) {
260
288
  return path.replace(/:(\w+)/g, 'test_$1');
261
289
  }
262
290
  return path.replace(/:(\w+)/g, (_match, name) => {
291
+ const fallback = `test_${name}`;
263
292
  const field_schema = params_schema.shape[name];
264
293
  if (!field_schema)
265
- return `test_${name}`;
294
+ return fallback;
295
+ if (field_schema.safeParse(fallback).success)
296
+ return fallback;
266
297
  const base_type = zod_get_base_type(field_schema);
267
298
  if (base_type === 'uuid')
268
299
  return '00000000-0000-0000-0000-000000000000';
269
300
  const format = detect_format(field_schema);
270
301
  if (format === 'uuid')
271
302
  return '00000000-0000-0000-0000-000000000000';
272
- return `test_${name}`;
303
+ const generated = generate_valid_string(field_schema);
304
+ if (generated.includes('/'))
305
+ return fallback;
306
+ return encodeURIComponent(generated);
273
307
  });
274
308
  };
275
309
  /**
@@ -37,7 +37,11 @@ export interface StandardTestOptions {
37
37
  * in-process and cross-process tests.
38
38
  */
39
39
  surface_source: AppSurfaceSpec;
40
- /** Backend capability declarations. */
40
+ /**
41
+ * Backend capability declarations. Forwarded to the one bundled suite
42
+ * that gates on them (`describe_standard_integration_tests`); the other
43
+ * suites in the bundle read no flags and take no capability field.
44
+ */
41
45
  capabilities: BackendCapabilities;
42
46
  /** Session config — needed for cookie_name + factory-form rpc_endpoints resolution. */
43
47
  session_options: SessionOptions<string>;
@@ -1 +1 @@
1
- {"version":3,"file":"standard.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/standard.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AASvD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAC3E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IACnC,2CAA2C;IAC3C,UAAU,EAAE,SAAS,CAAC;IACtB;;;OAGG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,uCAAuC;IACvC,YAAY,EAAE,mBAAmB,CAAC;IAClC,uFAAuF;IACvF,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;;;OAKG;IACH,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE;;;;;OAKG;IACH,aAAa,EAAE,uBAAuB,CAAC;IACvC;;;OAGG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB;;;;OAIG;IACH,SAAS,CAAC,EAAE,sBAAsB,CAAC;IACnC,sEAAsE;IACtE,yBAAyB,CAAC,EAAE,eAAe,CAAC;IAC5C;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,8DAA8D;IAC9D,eAAe,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GAAI,SAAS,mBAAmB,KAAG,IA2DtE,CAAC"}
1
+ {"version":3,"file":"standard.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/standard.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AASvD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAC3E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IACnC,2CAA2C;IAC3C,UAAU,EAAE,SAAS,CAAC;IACtB;;;OAGG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B;;;;OAIG;IACH,YAAY,EAAE,mBAAmB,CAAC;IAClC,uFAAuF;IACvF,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;;;OAKG;IACH,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE;;;;;OAKG;IACH,aAAa,EAAE,uBAAuB,CAAC;IACvC;;;OAGG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB;;;;OAIG;IACH,SAAS,CAAC,EAAE,sBAAsB,CAAC;IACnC,sEAAsE;IACtE,yBAAyB,CAAC,EAAE,eAAe,CAAC;IAC5C;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,8DAA8D;IAC9D,eAAe,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GAAI,SAAS,mBAAmB,KAAG,IAsDtE,CAAC"}
@@ -24,20 +24,17 @@ export const describe_standard_tests = (options) => {
24
24
  });
25
25
  describe_round_trip_validation({
26
26
  setup_test: options.setup_test,
27
- surface_source: options.surface_source,
28
- capabilities: options.capabilities
27
+ surface_source: options.surface_source
29
28
  });
30
29
  describe_rpc_round_trip_tests({
31
30
  setup_test: options.setup_test,
32
31
  surface_source: options.surface_source,
33
- capabilities: options.capabilities,
34
32
  session_options: options.session_options,
35
33
  rpc_endpoints: options.rpc_endpoints
36
34
  });
37
35
  describe_data_exposure_tests({
38
36
  setup_test: options.setup_test,
39
- surface_source: options.surface_source,
40
- capabilities: options.capabilities
37
+ surface_source: options.surface_source
41
38
  });
42
39
  describe_rate_limiting_tests({
43
40
  session_options: options.session_options,
@@ -49,7 +46,6 @@ export const describe_standard_tests = (options) => {
49
46
  describe_standard_admin_integration_tests({
50
47
  setup_test: options.setup_test,
51
48
  surface_source: options.surface_source,
52
- capabilities: options.capabilities,
53
49
  session_options: options.session_options,
54
50
  roles: options.roles,
55
51
  rpc_endpoints: options.rpc_endpoints,
@@ -58,7 +54,6 @@ export const describe_standard_tests = (options) => {
58
54
  describe_audit_completeness_tests({
59
55
  setup_test: options.setup_test,
60
56
  surface_source: options.surface_source,
61
- capabilities: options.capabilities,
62
57
  session_options: options.session_options,
63
58
  rpc_endpoints: options.rpc_endpoints
64
59
  });
@@ -0,0 +1,58 @@
1
+ /**
2
+ * The canonical wire timestamp shape shared by the two spine implementations.
3
+ *
4
+ * Every timestamp that reaches the wire is second-precision UTC —
5
+ * `YYYY-MM-DDTHH:MM:SSZ`, exactly 20 characters. Timestamps read from
6
+ * Postgres get that shape from the SQL projection
7
+ * (`iso8601_timestamp_column` in `db/sql_columns.ts`, twin of
8
+ * `fuz_db::iso8601_timestamp_column`); this module is the same shape for the
9
+ * stamps minted in TS rather than read back from a row, so the two sources
10
+ * agree and the two backends serialize the same instant to the same bytes.
11
+ *
12
+ * @module
13
+ */
14
+ /**
15
+ * The canonical wire timestamp shape as a regex — second-precision UTC,
16
+ * exactly 20 characters.
17
+ *
18
+ * Lives in library code rather than a test helper so the unit suites, the db
19
+ * suites, and the cross-backend suites all pin the same pattern the server
20
+ * renders.
21
+ */
22
+ export declare const ISO8601_SECONDS: RegExp;
23
+ /**
24
+ * The width of one wire timestamp, in milliseconds.
25
+ *
26
+ * A wire stamp is truncated, not rounded, so the instant it names lies
27
+ * anywhere in `[stamp, stamp + ISO8601_SECOND_MS)`. A predicate that must
28
+ * not fire early against a stamp — "has this expiry passed?" — adds this to
29
+ * reach the end of the second the stamp stands for.
30
+ */
31
+ export declare const ISO8601_SECOND_MS = 1000;
32
+ /**
33
+ * Whether the second a wire timestamp names has not yet ended.
34
+ *
35
+ * The one in-memory expiry rule over a truncated stamp: `stamp` stands for
36
+ * `[stamp, stamp + ISO8601_SECOND_MS)`, so this admits until that whole
37
+ * second is over and can never report an expiry the server's raw-column
38
+ * `expires_at > NOW()` gate would still accept. A stamp that does not parse
39
+ * is never live.
40
+ *
41
+ * @param stamp - a second-precision wire timestamp
42
+ * @param now_ms - the current time in epoch milliseconds
43
+ * @returns `true` while `now_ms` lies before the end of `stamp`'s second
44
+ */
45
+ export declare const is_iso8601_seconds_live: (stamp: string, now_ms: number) => boolean;
46
+ /**
47
+ * Render a `Date` as the canonical second-precision UTC wire timestamp
48
+ * (`YYYY-MM-DDTHH:MM:SSZ`).
49
+ *
50
+ * Truncates rather than rounds, mirroring Postgres'
51
+ * `to_char(…, 'YYYY-MM-DD"T"HH24:MI:SS"Z"')` and the Rust spine's
52
+ * `fuz_sys::rfc3339_now`.
53
+ *
54
+ * @param date - the instant to render
55
+ * @returns the 20-character UTC timestamp
56
+ */
57
+ export declare const to_iso8601_seconds: (date: Date) => string;
58
+ //# sourceMappingURL=timestamp.d.ts.map