@fuzdev/fuz_app 0.64.0 → 0.66.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 (223) hide show
  1. package/dist/actions/CLAUDE.md +510 -946
  2. package/dist/actions/action_codegen.d.ts +1 -1
  3. package/dist/actions/action_codegen.js +1 -1
  4. package/dist/actions/action_event_data.d.ts +1 -1
  5. package/dist/actions/broadcast_api.d.ts +1 -1
  6. package/dist/actions/broadcast_api.js +1 -1
  7. package/dist/actions/cancel.d.ts +2 -2
  8. package/dist/actions/cancel.js +3 -3
  9. package/dist/actions/connection_closer.d.ts +1 -4
  10. package/dist/actions/connection_closer.d.ts.map +1 -1
  11. package/dist/actions/connection_closer.js +1 -4
  12. package/dist/actions/register_action_ws.d.ts +2 -2
  13. package/dist/actions/register_ws_endpoint.d.ts +1 -1
  14. package/dist/actions/transports_ws_auth_guard.d.ts +1 -2
  15. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  16. package/dist/actions/transports_ws_auth_guard.js +1 -2
  17. package/dist/auth/CLAUDE.md +570 -1871
  18. package/dist/auth/account_schema.d.ts +1 -1
  19. package/dist/auth/account_schema.d.ts.map +1 -1
  20. package/dist/auth/api_token_queries.js +1 -1
  21. package/dist/auth/audit_log_ddl.d.ts +1 -1
  22. package/dist/auth/audit_log_ddl.d.ts.map +1 -1
  23. package/dist/auth/audit_log_ddl.js +1 -1
  24. package/dist/auth/audit_log_schema.js +2 -2
  25. package/dist/auth/bootstrap_account.d.ts.map +1 -1
  26. package/dist/auth/bootstrap_account.js +1 -5
  27. package/dist/auth/bootstrap_routes.d.ts +7 -1
  28. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  29. package/dist/auth/bootstrap_routes.js +15 -11
  30. package/dist/auth/daemon_token_middleware.d.ts +15 -5
  31. package/dist/auth/daemon_token_middleware.d.ts.map +1 -1
  32. package/dist/auth/daemon_token_middleware.js +24 -15
  33. package/dist/auth/invite_queries.d.ts +17 -7
  34. package/dist/auth/invite_queries.d.ts.map +1 -1
  35. package/dist/auth/invite_queries.js +19 -8
  36. package/dist/auth/keyring.d.ts +6 -6
  37. package/dist/auth/keyring.js +8 -8
  38. package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
  39. package/dist/auth/role_grant_offer_actions.js +4 -2
  40. package/dist/auth/signup_routes.d.ts +47 -1
  41. package/dist/auth/signup_routes.d.ts.map +1 -1
  42. package/dist/auth/signup_routes.js +103 -52
  43. package/dist/db/create_db.d.ts.map +1 -1
  44. package/dist/db/create_db.js +13 -0
  45. package/dist/dev/setup.d.ts +2 -2
  46. package/dist/dev/setup.js +3 -3
  47. package/dist/env/resolve.d.ts +44 -7
  48. package/dist/env/resolve.d.ts.map +1 -1
  49. package/dist/env/resolve.js +94 -27
  50. package/dist/http/CLAUDE.md +243 -522
  51. package/dist/http/error_schemas.d.ts +0 -4
  52. package/dist/http/error_schemas.d.ts.map +1 -1
  53. package/dist/http/error_schemas.js +0 -4
  54. package/dist/http/ip_canonical.d.ts +5 -4
  55. package/dist/http/ip_canonical.d.ts.map +1 -1
  56. package/dist/http/ip_canonical.js +8 -4
  57. package/dist/http/jsonrpc.d.ts +23 -7
  58. package/dist/http/jsonrpc.d.ts.map +1 -1
  59. package/dist/http/jsonrpc.js +19 -3
  60. package/dist/http/origin.d.ts +1 -1
  61. package/dist/http/origin.js +1 -1
  62. package/dist/http/surface.d.ts +9 -2
  63. package/dist/http/surface.d.ts.map +1 -1
  64. package/dist/runtime/mock.d.ts +1 -1
  65. package/dist/runtime/mock.js +2 -2
  66. package/dist/server/app_server.d.ts +41 -10
  67. package/dist/server/app_server.d.ts.map +1 -1
  68. package/dist/server/app_server.js +10 -4
  69. package/dist/server/env.d.ts +7 -7
  70. package/dist/server/env.d.ts.map +1 -1
  71. package/dist/server/env.js +14 -14
  72. package/dist/server/static.d.ts +4 -4
  73. package/dist/server/static.js +7 -7
  74. package/dist/testing/CLAUDE.md +740 -418
  75. package/dist/testing/admin_integration.d.ts +18 -23
  76. package/dist/testing/admin_integration.d.ts.map +1 -1
  77. package/dist/testing/admin_integration.js +230 -216
  78. package/dist/testing/app_server.d.ts +141 -39
  79. package/dist/testing/app_server.d.ts.map +1 -1
  80. package/dist/testing/app_server.js +157 -44
  81. package/dist/testing/audit_completeness.d.ts +25 -22
  82. package/dist/testing/audit_completeness.d.ts.map +1 -1
  83. package/dist/testing/audit_completeness.js +198 -159
  84. package/dist/testing/bootstrap_success.d.ts +28 -0
  85. package/dist/testing/bootstrap_success.d.ts.map +1 -0
  86. package/dist/testing/bootstrap_success.js +144 -0
  87. package/dist/testing/cross_backend/backend_config.d.ts +113 -0
  88. package/dist/testing/cross_backend/backend_config.d.ts.map +1 -0
  89. package/dist/testing/cross_backend/backend_config.js +1 -0
  90. package/dist/testing/cross_backend/bench/bench_report.d.ts +46 -0
  91. package/dist/testing/cross_backend/bench/bench_report.d.ts.map +1 -0
  92. package/dist/testing/cross_backend/bench/bench_report.js +83 -0
  93. package/dist/testing/cross_backend/bench/run_cross_impl_bench.d.ts +44 -0
  94. package/dist/testing/cross_backend/bench/run_cross_impl_bench.d.ts.map +1 -0
  95. package/dist/testing/cross_backend/bench/run_cross_impl_bench.js +38 -0
  96. package/dist/testing/cross_backend/bench/scenario.d.ts +57 -0
  97. package/dist/testing/cross_backend/bench/scenario.d.ts.map +1 -0
  98. package/dist/testing/cross_backend/bench/scenario.js +28 -0
  99. package/dist/testing/cross_backend/bootstrap_backend.d.ts +41 -0
  100. package/dist/testing/cross_backend/bootstrap_backend.d.ts.map +1 -0
  101. package/dist/testing/cross_backend/bootstrap_backend.js +34 -0
  102. package/dist/testing/cross_backend/build_test_backend_paths.d.ts +24 -0
  103. package/dist/testing/cross_backend/build_test_backend_paths.d.ts.map +1 -0
  104. package/dist/testing/cross_backend/build_test_backend_paths.js +33 -0
  105. package/dist/testing/cross_backend/capabilities.d.ts +65 -0
  106. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -0
  107. package/dist/testing/cross_backend/capabilities.js +47 -0
  108. package/dist/testing/cross_backend/default_backend_configs.d.ts +122 -0
  109. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -0
  110. package/dist/testing/cross_backend/default_backend_configs.js +111 -0
  111. package/dist/testing/cross_backend/default_secrets.d.ts +40 -0
  112. package/dist/testing/cross_backend/default_secrets.d.ts.map +1 -0
  113. package/dist/testing/cross_backend/default_secrets.js +39 -0
  114. package/dist/testing/cross_backend/default_spine_surface.d.ts +64 -0
  115. package/dist/testing/cross_backend/default_spine_surface.d.ts.map +1 -0
  116. package/dist/testing/cross_backend/default_spine_surface.js +121 -0
  117. package/dist/testing/cross_backend/setup.d.ts +451 -0
  118. package/dist/testing/cross_backend/setup.d.ts.map +1 -0
  119. package/dist/testing/cross_backend/setup.js +581 -0
  120. package/dist/testing/cross_backend/spawn_backend.d.ts +58 -0
  121. package/dist/testing/cross_backend/spawn_backend.d.ts.map +1 -0
  122. package/dist/testing/cross_backend/spawn_backend.js +229 -0
  123. package/dist/testing/cross_backend/spine_stub_backend_config.d.ts +66 -0
  124. package/dist/testing/cross_backend/spine_stub_backend_config.d.ts.map +1 -0
  125. package/dist/testing/cross_backend/spine_stub_backend_config.js +49 -0
  126. package/dist/testing/cross_backend/sse_round_trip.d.ts +37 -0
  127. package/dist/testing/cross_backend/sse_round_trip.d.ts.map +1 -0
  128. package/dist/testing/cross_backend/sse_round_trip.js +137 -0
  129. package/dist/testing/cross_backend/standard.d.ts +96 -0
  130. package/dist/testing/cross_backend/standard.d.ts.map +1 -0
  131. package/dist/testing/cross_backend/standard.js +49 -0
  132. package/dist/testing/cross_backend/testing_reset_actions.d.ts +171 -0
  133. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -0
  134. package/dist/testing/cross_backend/testing_reset_actions.js +213 -0
  135. package/dist/testing/cross_backend/testing_server_bun.d.ts +5 -0
  136. package/dist/testing/cross_backend/testing_server_bun.d.ts.map +1 -0
  137. package/dist/testing/cross_backend/testing_server_bun.js +59 -0
  138. package/dist/testing/cross_backend/testing_server_core.d.ts +140 -0
  139. package/dist/testing/cross_backend/testing_server_core.d.ts.map +1 -0
  140. package/dist/testing/cross_backend/testing_server_core.js +68 -0
  141. package/dist/testing/cross_backend/testing_server_deno.d.ts +5 -0
  142. package/dist/testing/cross_backend/testing_server_deno.d.ts.map +1 -0
  143. package/dist/testing/cross_backend/testing_server_deno.js +37 -0
  144. package/dist/testing/cross_backend/testing_server_node.d.ts +5 -0
  145. package/dist/testing/cross_backend/testing_server_node.d.ts.map +1 -0
  146. package/dist/testing/cross_backend/testing_server_node.js +50 -0
  147. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts +72 -0
  148. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -0
  149. package/dist/testing/cross_backend/ts_spine_backend_config.js +112 -0
  150. package/dist/testing/cross_backend/ws_round_trip.d.ts +35 -0
  151. package/dist/testing/cross_backend/ws_round_trip.d.ts.map +1 -0
  152. package/dist/testing/cross_backend/ws_round_trip.js +113 -0
  153. package/dist/testing/data_exposure.d.ts +11 -14
  154. package/dist/testing/data_exposure.d.ts.map +1 -1
  155. package/dist/testing/data_exposure.js +123 -146
  156. package/dist/testing/db_entities.d.ts +22 -1
  157. package/dist/testing/db_entities.d.ts.map +1 -1
  158. package/dist/testing/db_entities.js +24 -1
  159. package/dist/testing/integration.d.ts +56 -21
  160. package/dist/testing/integration.d.ts.map +1 -1
  161. package/dist/testing/integration.js +294 -319
  162. package/dist/testing/integration_helpers.d.ts +16 -6
  163. package/dist/testing/integration_helpers.d.ts.map +1 -1
  164. package/dist/testing/integration_helpers.js +7 -7
  165. package/dist/testing/mock_fs.d.ts.map +1 -1
  166. package/dist/testing/mock_fs.js +0 -2
  167. package/dist/testing/rate_limiting.d.ts.map +1 -1
  168. package/dist/testing/rate_limiting.js +9 -0
  169. package/dist/testing/role_grant_helpers.d.ts +31 -0
  170. package/dist/testing/role_grant_helpers.d.ts.map +1 -0
  171. package/dist/testing/role_grant_helpers.js +46 -0
  172. package/dist/testing/round_trip.d.ts +20 -16
  173. package/dist/testing/round_trip.d.ts.map +1 -1
  174. package/dist/testing/round_trip.js +61 -86
  175. package/dist/testing/rpc_helpers.d.ts +10 -4
  176. package/dist/testing/rpc_helpers.d.ts.map +1 -1
  177. package/dist/testing/rpc_helpers.js +1 -1
  178. package/dist/testing/rpc_round_trip.d.ts +24 -21
  179. package/dist/testing/rpc_round_trip.d.ts.map +1 -1
  180. package/dist/testing/rpc_round_trip.js +87 -104
  181. package/dist/testing/schema_introspect.d.ts +106 -0
  182. package/dist/testing/schema_introspect.d.ts.map +1 -0
  183. package/dist/testing/schema_introspect.js +123 -0
  184. package/dist/testing/schema_parity.d.ts +144 -0
  185. package/dist/testing/schema_parity.d.ts.map +1 -0
  186. package/dist/testing/schema_parity.js +233 -0
  187. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  188. package/dist/testing/sse_round_trip.js +1 -68
  189. package/dist/testing/standard.d.ts +56 -25
  190. package/dist/testing/standard.d.ts.map +1 -1
  191. package/dist/testing/standard.js +62 -5
  192. package/dist/testing/stubs.d.ts +21 -6
  193. package/dist/testing/stubs.d.ts.map +1 -1
  194. package/dist/testing/stubs.js +33 -23
  195. package/dist/testing/testing_rate_limiter.d.ts +59 -0
  196. package/dist/testing/testing_rate_limiter.d.ts.map +1 -0
  197. package/dist/testing/testing_rate_limiter.js +74 -0
  198. package/dist/testing/transports/bootstrap.d.ts +52 -0
  199. package/dist/testing/transports/bootstrap.d.ts.map +1 -0
  200. package/dist/testing/transports/bootstrap.js +70 -0
  201. package/dist/testing/transports/fetch_transport.d.ts +81 -0
  202. package/dist/testing/transports/fetch_transport.d.ts.map +1 -0
  203. package/dist/testing/transports/fetch_transport.js +74 -0
  204. package/dist/testing/transports/sse_frame_reader.d.ts +41 -0
  205. package/dist/testing/transports/sse_frame_reader.d.ts.map +1 -0
  206. package/dist/testing/transports/sse_frame_reader.js +84 -0
  207. package/dist/testing/transports/sse_transport.d.ts +54 -0
  208. package/dist/testing/transports/sse_transport.d.ts.map +1 -0
  209. package/dist/testing/transports/sse_transport.js +51 -0
  210. package/dist/testing/transports/ws_client.d.ts +108 -0
  211. package/dist/testing/transports/ws_client.d.ts.map +1 -0
  212. package/dist/testing/transports/ws_client.js +56 -0
  213. package/dist/testing/transports/ws_transport.d.ts +43 -0
  214. package/dist/testing/transports/ws_transport.d.ts.map +1 -0
  215. package/dist/testing/transports/ws_transport.js +169 -0
  216. package/dist/testing/ws_round_trip.d.ts +21 -103
  217. package/dist/testing/ws_round_trip.d.ts.map +1 -1
  218. package/dist/testing/ws_round_trip.js +42 -40
  219. package/dist/ui/CLAUDE.md +5 -3
  220. package/dist/ui/MenuLink.svelte +16 -16
  221. package/dist/ui/MenuLink.svelte.d.ts +13 -4
  222. package/dist/ui/MenuLink.svelte.d.ts.map +1 -1
  223. package/package.json +10 -4
@@ -5,35 +5,39 @@ import './assert_dev_env.js';
5
5
  * For every RPC method, generates valid params and fires JSON-RPC requests
6
6
  * (POST for all methods, GET for reads), validating that responses are
7
7
  * well-formed JSON-RPC. Successful responses are validated against the
8
- * method's declared output schema. DB-backed via `create_test_app`.
8
+ * method's declared output schema. DB-backed via the suite's `setup_test`
9
+ * fixture-producing callback.
10
+ *
11
+ * Cadence: per-describe `setup_test()` call (see `round_trip.ts` module
12
+ * docstring). RPC round-trip tests fire one JSON-RPC envelope per
13
+ * method-direction and don't mutate state in a way that contaminates the
14
+ * next case.
9
15
  *
10
16
  * @module
11
17
  */
12
- import { describe, test, beforeAll, afterAll } from 'vitest';
18
+ import { describe, test, beforeAll } from 'vitest';
13
19
  import { ROLE_ADMIN } from '../auth/role_schema.js';
14
- import { create_test_app, } from './app_server.js';
15
- import { create_pglite_factory } from './db.js';
16
20
  import { generate_valid_body } from './schema_generators.js';
17
- import { run_migrations } from '../db/migrate.js';
18
- import { auth_migration_ns } from '../auth/migrations.js';
19
21
  import { is_public_auth } from '../http/auth_shape.js';
20
22
  import { create_rpc_post_init, create_rpc_get_url, assert_jsonrpc_error_response, assert_jsonrpc_success_response, resolve_rpc_endpoints_for_setup, } from './rpc_helpers.js';
21
23
  /**
22
- * Pick auth headers matching an RPC method's auth requirement.
24
+ * Pick auth headers matching an RPC method's auth requirement. Accepts
25
+ * any `KeeperHeaderProvider` — both `TestApp` (in-process) and
26
+ * `TestFixture` (cross-backend) satisfy the shape structurally.
23
27
  */
24
- const pick_rpc_auth_headers = (method, test_app, authed_account, admin_account) => {
28
+ const pick_rpc_auth_headers = (method, keeper, authed_account, admin_account) => {
25
29
  const { auth } = method;
26
30
  if (is_public_auth(auth)) {
27
31
  return { host: 'localhost', origin: 'http://localhost:5173' };
28
32
  }
29
33
  if (auth.credential_types?.includes('daemon_token')) {
30
- return test_app.create_daemon_token_headers();
34
+ return keeper.create_daemon_token_headers();
31
35
  }
32
36
  if (auth.roles?.length) {
33
37
  if (auth.roles.includes(ROLE_ADMIN)) {
34
38
  return admin_account.create_session_headers();
35
39
  }
36
- return test_app.create_session_headers();
40
+ return keeper.create_session_headers();
37
41
  }
38
42
  return authed_account.create_session_headers();
39
43
  };
@@ -54,112 +58,91 @@ const pick_rpc_auth_headers = (method, test_app, authed_account, admin_account)
54
58
  export const describe_rpc_round_trip_tests = (options) => {
55
59
  const skip_set = new Set(options.skip_methods);
56
60
  // Resolve factory-form endpoints once for setup-time iteration (method
57
- // enumeration, surface lookup). Real handlers run per-test via the
58
- // top-level `rpc_endpoints` slot on `CreateTestAppOptions`
59
- // `action.spec.method` / `.input` / `.output` are ctx-independent, so
60
- // the stub-resolved specs match what the live dispatcher serves.
61
+ // enumeration, surface lookup). The live dispatcher runs against
62
+ // whatever the backend was started with — `action.spec.method` /
63
+ // `.input` / `.output` are ctx-independent, so the stub-resolved specs
64
+ // match what the running backend serves.
61
65
  const rpc_endpoints_for_setup = resolve_rpc_endpoints_for_setup(options.rpc_endpoints, options.session_options);
62
- const init_schema = async (db) => {
63
- await run_migrations(db, [auth_migration_ns]);
64
- };
65
- const factories = options.db_factories ?? [create_pglite_factory(init_schema)];
66
- for (const factory of factories) {
67
- const describe_fn = factory.skip ? describe.skip : describe;
68
- describe_fn(`RPC round-trip validation (${factory.name})`, () => {
69
- let test_app;
70
- let authed_account;
71
- let admin_account;
72
- let db;
73
- beforeAll(async () => {
74
- db = await factory.create();
75
- test_app = await create_test_app({
76
- session_options: options.session_options,
77
- create_route_specs: options.create_route_specs,
78
- db,
79
- rpc_endpoints: options.rpc_endpoints,
80
- app_options: options.app_options,
81
- });
82
- authed_account = await test_app.create_account({
83
- username: 'rpc_round_trip_authed',
84
- roles: [],
85
- });
86
- admin_account = await test_app.create_account({
87
- username: 'rpc_round_trip_admin',
88
- roles: [ROLE_ADMIN],
89
- });
66
+ const surface_rpc_endpoints = options.surface_source.surface.rpc_endpoints;
67
+ void options.capabilities;
68
+ describe('RPC round-trip validation', () => {
69
+ let fixture;
70
+ let authed_account;
71
+ let admin_account;
72
+ beforeAll(async () => {
73
+ fixture = await options.setup_test();
74
+ authed_account = await fixture.create_account({
75
+ username: 'rpc_round_trip_authed',
76
+ roles: [],
90
77
  });
91
- afterAll(async () => {
92
- await test_app.cleanup();
93
- await factory.close(db);
78
+ admin_account = await fixture.create_account({
79
+ username: 'rpc_round_trip_admin',
80
+ roles: [ROLE_ADMIN],
94
81
  });
95
- test('all RPC methods produce valid JSON-RPC responses (POST)', async () => {
96
- for (const ep_spec of rpc_endpoints_for_setup) {
97
- const surface_ep = test_app.surface_spec.surface.rpc_endpoints.find((e) => e.path === ep_spec.path);
98
- if (!surface_ep)
82
+ });
83
+ test('all RPC methods produce valid JSON-RPC responses (POST)', async () => {
84
+ for (const ep_spec of rpc_endpoints_for_setup) {
85
+ const surface_ep = surface_rpc_endpoints.find((e) => e.path === ep_spec.path);
86
+ if (!surface_ep)
87
+ continue;
88
+ for (const action of ep_spec.actions) {
89
+ if (skip_set.has(action.spec.method))
90
+ continue;
91
+ const surface_method = surface_ep.methods.find((m) => m.name === action.spec.method);
92
+ if (!surface_method)
99
93
  continue;
100
- for (const action of ep_spec.actions) {
101
- if (skip_set.has(action.spec.method))
102
- continue;
103
- const surface_method = surface_ep.methods.find((m) => m.name === action.spec.method);
104
- if (!surface_method)
105
- continue;
106
- // generate or override params
107
- const override = options.input_overrides?.get(action.spec.method);
108
- const params = override ?? generate_valid_body(action.spec.input) ?? null;
109
- // pick auth
110
- const headers = pick_rpc_auth_headers(surface_method, test_app, authed_account, admin_account);
111
- const init = create_rpc_post_init(action.spec.method, params);
112
- // merge auth headers into init
113
- Object.assign(init.headers, headers);
114
- const res = await test_app.app.request(ep_spec.path, init);
115
- const body = await res.json();
116
- // validate well-formed JSON-RPC; successful responses also checked against output schema
117
- try {
118
- if (res.ok) {
119
- assert_jsonrpc_success_response(body, action.spec.output);
120
- }
121
- else {
122
- assert_jsonrpc_error_response(body);
123
- }
94
+ const override = options.input_overrides?.get(action.spec.method);
95
+ const params = override ?? generate_valid_body(action.spec.input) ?? null;
96
+ const headers = pick_rpc_auth_headers(surface_method, fixture, authed_account, admin_account);
97
+ const init = create_rpc_post_init(action.spec.method, params);
98
+ Object.assign(init.headers, headers);
99
+ const res = await fixture.transport(ep_spec.path, init);
100
+ const body = await res.json();
101
+ try {
102
+ if (res.ok) {
103
+ assert_jsonrpc_success_response(body, action.spec.output);
124
104
  }
125
- catch (e) {
126
- throw new Error(`RPC round-trip POST failed for ${action.spec.method} (status ${res.status}): ${e.message}`);
105
+ else {
106
+ assert_jsonrpc_error_response(body);
127
107
  }
128
108
  }
109
+ catch (e) {
110
+ throw new Error(`RPC round-trip POST failed for ${action.spec.method} (status ${res.status}): ${e.message}`);
111
+ }
129
112
  }
130
- });
131
- test('all read RPC methods produce valid JSON-RPC responses (GET)', async () => {
132
- for (const ep_spec of rpc_endpoints_for_setup) {
133
- const surface_ep = test_app.surface_spec.surface.rpc_endpoints.find((e) => e.path === ep_spec.path);
134
- if (!surface_ep)
113
+ }
114
+ });
115
+ test('all read RPC methods produce valid JSON-RPC responses (GET)', async () => {
116
+ for (const ep_spec of rpc_endpoints_for_setup) {
117
+ const surface_ep = surface_rpc_endpoints.find((e) => e.path === ep_spec.path);
118
+ if (!surface_ep)
119
+ continue;
120
+ const read_actions = ep_spec.actions.filter((a) => !a.spec.side_effects);
121
+ for (const action of read_actions) {
122
+ if (skip_set.has(action.spec.method))
123
+ continue;
124
+ const surface_method = surface_ep.methods.find((m) => m.name === action.spec.method);
125
+ if (!surface_method)
135
126
  continue;
136
- const read_actions = ep_spec.actions.filter((a) => !a.spec.side_effects);
137
- for (const action of read_actions) {
138
- if (skip_set.has(action.spec.method))
139
- continue;
140
- const surface_method = surface_ep.methods.find((m) => m.name === action.spec.method);
141
- if (!surface_method)
142
- continue;
143
- const override = options.input_overrides?.get(action.spec.method);
144
- const params = override ?? generate_valid_body(action.spec.input) ?? undefined;
145
- const headers = pick_rpc_auth_headers(surface_method, test_app, authed_account, admin_account);
146
- const url = create_rpc_get_url(ep_spec.path, action.spec.method, params);
147
- const res = await test_app.app.request(url, { headers });
148
- const body = await res.json();
149
- try {
150
- if (res.ok) {
151
- assert_jsonrpc_success_response(body, action.spec.output);
152
- }
153
- else {
154
- assert_jsonrpc_error_response(body);
155
- }
127
+ const override = options.input_overrides?.get(action.spec.method);
128
+ const params = override ?? generate_valid_body(action.spec.input) ?? undefined;
129
+ const headers = pick_rpc_auth_headers(surface_method, fixture, authed_account, admin_account);
130
+ const url = create_rpc_get_url(ep_spec.path, action.spec.method, params);
131
+ const res = await fixture.transport(url, { headers });
132
+ const body = await res.json();
133
+ try {
134
+ if (res.ok) {
135
+ assert_jsonrpc_success_response(body, action.spec.output);
156
136
  }
157
- catch (e) {
158
- throw new Error(`RPC round-trip GET failed for ${action.spec.method} (status ${res.status}): ${e.message}`);
137
+ else {
138
+ assert_jsonrpc_error_response(body);
159
139
  }
160
140
  }
141
+ catch (e) {
142
+ throw new Error(`RPC round-trip GET failed for ${action.spec.method} (status ${res.status}): ${e.message}`);
143
+ }
161
144
  }
162
- });
145
+ }
163
146
  });
164
- }
147
+ });
165
148
  };
@@ -0,0 +1,106 @@
1
+ import './assert_dev_env.js';
2
+ /**
3
+ * PostgreSQL schema introspection — produces a normalized, JSON-serializable
4
+ * snapshot of a database's structure for cross-impl parity checks.
5
+ *
6
+ * The snapshot covers:
7
+ *
8
+ * - `schema_version` rows (`namespace`, `name`, `sequence`) — captures
9
+ * migration set state across impls
10
+ * - Tables with columns (data type, nullability, default, identity)
11
+ * - Indexes with canonical Postgres-rendered definitions
12
+ * - Constraints (CHECK, FOREIGN KEY, PRIMARY KEY, UNIQUE, EXCLUSION)
13
+ * - Sequences with data type — distinguishes `int4` (SERIAL) from `int8`
14
+ * (BIGSERIAL)
15
+ *
16
+ * Designed for `pg_catalog` introspection — works against both PostgreSQL
17
+ * and PGlite. The snapshot is fully deterministic: every collection sorts by
18
+ * a stable key and excludes time-varying fields like `applied_at`.
19
+ *
20
+ * Paired with `schema_parity.ts` for comparison + assertion helpers.
21
+ *
22
+ * @module
23
+ */
24
+ import type { Db } from '../db/db.js';
25
+ /** Per-column structural metadata. */
26
+ export interface ColumnSnapshot {
27
+ /** SQL standard type name from `information_schema.columns.data_type`. */
28
+ readonly data_type: string;
29
+ /** Postgres-native type name from `information_schema.columns.udt_name`. */
30
+ readonly udt_name: string;
31
+ /** `true` when the column accepts NULL. */
32
+ readonly is_nullable: boolean;
33
+ /** Default-value expression as Postgres reports it, or `null` if none. */
34
+ readonly column_default: string | null;
35
+ /** `true` when the column was declared GENERATED ... AS IDENTITY. */
36
+ readonly is_identity: boolean;
37
+ }
38
+ /** Per-table structural metadata. */
39
+ export interface TableSnapshot {
40
+ /** Column metadata keyed by column name (sorted on serialization). */
41
+ readonly columns: Record<string, ColumnSnapshot>;
42
+ /** Index definitions as Postgres renders them via `pg_indexes.indexdef`. */
43
+ readonly indexes: ReadonlyArray<{
44
+ readonly name: string;
45
+ readonly definition: string;
46
+ }>;
47
+ /** Constraint definitions as Postgres renders them via `pg_get_constraintdef`. */
48
+ readonly constraints: ReadonlyArray<{
49
+ readonly name: string;
50
+ readonly type: string;
51
+ readonly definition: string;
52
+ }>;
53
+ }
54
+ /** Sequence metadata — `data_type` is `bigint` (BIGSERIAL) or `integer` (SERIAL). */
55
+ export interface SequenceSnapshot {
56
+ readonly data_type: string;
57
+ }
58
+ /** One row in the `schema_version` migration tracker. */
59
+ export interface SchemaVersionRow {
60
+ readonly namespace: string;
61
+ readonly name: string;
62
+ readonly sequence: number;
63
+ }
64
+ /**
65
+ * Normalized database schema snapshot for parity comparison.
66
+ *
67
+ * All fields are deterministically ordered on capture so structural equality
68
+ * via `JSON.stringify` or per-key comparison yields stable results.
69
+ */
70
+ export interface SchemaSnapshot {
71
+ /** Migration tracker rows, sorted by `(namespace, sequence)`. */
72
+ readonly schema_version: ReadonlyArray<SchemaVersionRow>;
73
+ /** Tables keyed by name. */
74
+ readonly tables: Record<string, TableSnapshot>;
75
+ /** Sequences keyed by name. */
76
+ readonly sequences: Record<string, SequenceSnapshot>;
77
+ }
78
+ /** Filter options for `query_schema_snapshot`. */
79
+ export interface QuerySchemaSnapshotOptions {
80
+ /**
81
+ * Schema name to introspect — defaults to `'public'`. Single-schema only;
82
+ * cross-schema introspection isn't a current need.
83
+ */
84
+ readonly schema?: string;
85
+ /**
86
+ * Tables to exclude from the snapshot. The `schema_version` table itself
87
+ * is always excluded (its content is captured separately).
88
+ */
89
+ readonly exclude_tables?: ReadonlyArray<string>;
90
+ }
91
+ /**
92
+ * Introspect a live database into a deterministic `SchemaSnapshot`.
93
+ *
94
+ * Reads `information_schema` and `pg_catalog` to capture tables, columns,
95
+ * indexes, constraints, sequences, and `schema_version` migration tracker
96
+ * rows. The `applied_at` timestamp is deliberately excluded — only the set
97
+ * of applied migrations matters for parity.
98
+ *
99
+ * The `schema_version` table itself never appears in the `tables` field;
100
+ * its structure is identical across consumers and would only add noise.
101
+ *
102
+ * @throws Error when the `schema_version` table is missing — callers must
103
+ * ensure migrations have run before introspecting.
104
+ */
105
+ export declare const query_schema_snapshot: (db: Db, options?: QuerySchemaSnapshotOptions) => Promise<SchemaSnapshot>;
106
+ //# sourceMappingURL=schema_introspect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema_introspect.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/schema_introspect.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AAEpC,sCAAsC;AACtC,MAAM,WAAW,cAAc;IAC9B,0EAA0E;IAC1E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,2CAA2C;IAC3C,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,0EAA0E;IAC1E,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,qEAAqE;IACrE,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;CAC9B;AAED,qCAAqC;AACrC,MAAM,WAAW,aAAa;IAC7B,sEAAsE;IACtE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACjD,4EAA4E;IAC5E,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;KAAC,CAAC,CAAC;IACtF,kFAAkF;IAClF,QAAQ,CAAC,WAAW,EAAE,aAAa,CAAC;QACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;KAC5B,CAAC,CAAC;CACH;AAED,qFAAqF;AACrF,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3B;AAED,yDAAyD;AACzD,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC9B,iEAAiE;IACjE,QAAQ,CAAC,cAAc,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAC;IACzD,4BAA4B;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC/C,+BAA+B;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;CACrD;AAED,kDAAkD;AAClD,MAAM,WAAW,0BAA0B;IAC1C;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAChD;AAyDD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,qBAAqB,GACjC,IAAI,EAAE,EACN,UAAS,0BAA+B,KACtC,OAAO,CAAC,cAAc,CA+GxB,CAAC"}
@@ -0,0 +1,123 @@
1
+ import './assert_dev_env.js';
2
+ const sort_keys = (record) => {
3
+ const sorted = {};
4
+ for (const key of Object.keys(record).sort()) {
5
+ sorted[key] = record[key];
6
+ }
7
+ return sorted;
8
+ };
9
+ const contype_to_kind = (contype) => {
10
+ switch (contype) {
11
+ case 'p':
12
+ return 'PRIMARY KEY';
13
+ case 'f':
14
+ return 'FOREIGN KEY';
15
+ case 'u':
16
+ return 'UNIQUE';
17
+ case 'c':
18
+ return 'CHECK';
19
+ case 'x':
20
+ return 'EXCLUSION';
21
+ case 't':
22
+ return 'TRIGGER';
23
+ default:
24
+ return contype;
25
+ }
26
+ };
27
+ /**
28
+ * Introspect a live database into a deterministic `SchemaSnapshot`.
29
+ *
30
+ * Reads `information_schema` and `pg_catalog` to capture tables, columns,
31
+ * indexes, constraints, sequences, and `schema_version` migration tracker
32
+ * rows. The `applied_at` timestamp is deliberately excluded — only the set
33
+ * of applied migrations matters for parity.
34
+ *
35
+ * The `schema_version` table itself never appears in the `tables` field;
36
+ * its structure is identical across consumers and would only add noise.
37
+ *
38
+ * @throws Error when the `schema_version` table is missing — callers must
39
+ * ensure migrations have run before introspecting.
40
+ */
41
+ export const query_schema_snapshot = async (db, options = {}) => {
42
+ const schema = options.schema ?? 'public';
43
+ const exclude_tables = new Set(options.exclude_tables ?? []);
44
+ exclude_tables.add('schema_version');
45
+ // schema_version rows — the migration tracker. Exclude `applied_at`
46
+ // because timestamps differ across bootstraps even when the migration
47
+ // set is identical.
48
+ const schema_version_rows = await db.query(`SELECT namespace, name, sequence
49
+ FROM schema_version
50
+ ORDER BY namespace ASC, sequence ASC`);
51
+ // All tables in the target schema, minus the excludes.
52
+ const table_rows = await db.query(`SELECT table_name
53
+ FROM information_schema.tables
54
+ WHERE table_schema = $1 AND table_type = 'BASE TABLE'
55
+ ORDER BY table_name ASC`, [schema]);
56
+ const table_names = table_rows.map((r) => r.table_name).filter((n) => !exclude_tables.has(n));
57
+ // Columns — batched in one query, grouped client-side. udt_name
58
+ // distinguishes int4 from int8 (SERIAL vs BIGSERIAL).
59
+ const column_rows = await db.query(`SELECT table_name, column_name, data_type, udt_name, is_nullable,
60
+ column_default, is_identity
61
+ FROM information_schema.columns
62
+ WHERE table_schema = $1
63
+ ORDER BY table_name ASC, ordinal_position ASC`, [schema]);
64
+ // Indexes — pg_indexes.indexdef gives the canonical CREATE INDEX statement
65
+ // as Postgres would re-emit it, which normalizes whitespace and case.
66
+ const index_rows = await db.query(`SELECT tablename, indexname, indexdef
67
+ FROM pg_indexes
68
+ WHERE schemaname = $1
69
+ ORDER BY tablename ASC, indexname ASC`, [schema]);
70
+ // Constraints — pg_get_constraintdef produces a canonical text rendering.
71
+ const constraint_rows = await db.query(`SELECT c.conrelid::regclass::text AS table_name,
72
+ c.conname,
73
+ c.contype::text,
74
+ pg_get_constraintdef(c.oid) AS definition
75
+ FROM pg_constraint c
76
+ JOIN pg_namespace n ON n.oid = c.connamespace
77
+ WHERE n.nspname = $1
78
+ AND c.conrelid != 0
79
+ ORDER BY table_name ASC, conname ASC`, [schema]);
80
+ // Sequences — data_type distinguishes bigint (BIGSERIAL) from integer (SERIAL).
81
+ const sequence_rows = await db.query(`SELECT sequence_name, data_type
82
+ FROM information_schema.sequences
83
+ WHERE sequence_schema = $1
84
+ ORDER BY sequence_name ASC`, [schema]);
85
+ const tables = {};
86
+ for (const name of table_names) {
87
+ const columns = {};
88
+ for (const row of column_rows) {
89
+ if (row.table_name !== name)
90
+ continue;
91
+ columns[row.column_name] = {
92
+ data_type: row.data_type,
93
+ udt_name: row.udt_name,
94
+ is_nullable: row.is_nullable === 'YES',
95
+ column_default: row.column_default,
96
+ is_identity: row.is_identity === 'YES',
97
+ };
98
+ }
99
+ const indexes = index_rows
100
+ .filter((r) => r.tablename === name)
101
+ .map((r) => ({ name: r.indexname, definition: r.indexdef }));
102
+ const constraints = constraint_rows
103
+ // `conrelid::regclass::text` returns either bare (`foo`) or schema-
104
+ // qualified (`public.foo`) depending on the connection's search_path,
105
+ // so accept both forms here.
106
+ .filter((r) => r.table_name === name || r.table_name === `${schema}.${name}`)
107
+ .map((r) => ({
108
+ name: r.conname,
109
+ type: contype_to_kind(r.contype),
110
+ definition: r.definition,
111
+ }));
112
+ tables[name] = { columns: sort_keys(columns), indexes, constraints };
113
+ }
114
+ const sequences = {};
115
+ for (const row of sequence_rows) {
116
+ sequences[row.sequence_name] = { data_type: row.data_type };
117
+ }
118
+ return {
119
+ schema_version: schema_version_rows,
120
+ tables: sort_keys(tables),
121
+ sequences: sort_keys(sequences),
122
+ };
123
+ };
@@ -0,0 +1,144 @@
1
+ import './assert_dev_env.js';
2
+ /**
3
+ * Cross-impl schema parity — structural diff + assertion over two
4
+ * `SchemaSnapshot`s captured via `query_schema_snapshot`.
5
+ *
6
+ * Two live impls (TS fuz_app vs Rust spine) are each other's parity
7
+ * reference. After both bootstrap, snapshot each, diff, fail loudly on
8
+ * drift. The diff entries name the specific divergence (column type,
9
+ * missing index, schema_version row absent on one side) so the error
10
+ * message points at the source.
11
+ *
12
+ * Consumer pattern (in zzz's integration runner or fuz_app's own
13
+ * cross-backend tests):
14
+ *
15
+ * ```ts
16
+ * const snapshot_a = await query_schema_snapshot(db_after_deno_bootstrap);
17
+ * const snapshot_b = await query_schema_snapshot(db_after_rust_bootstrap);
18
+ * assert_schema_snapshots_equal(snapshot_a, snapshot_b, {a: 'deno', b: 'rust'});
19
+ * ```
20
+ *
21
+ * Non-coverage — drift the gate does **not** detect:
22
+ *
23
+ * - enum types (`CREATE TYPE ... AS ENUM`)
24
+ * - regular triggers (`pg_trigger`); `CONSTRAINT TRIGGER` is captured via
25
+ * pg_constraint, but standalone `CREATE TRIGGER` is not
26
+ * - views, materialized views, functions, procedures
27
+ * - table storage parameters (fillfactor, tablespace, autovacuum settings)
28
+ * - column physical order — the snapshot keys columns by name, so two
29
+ * impls with the same columns in different declaration order compare
30
+ * equal (functional parity is preserved; `SELECT *` ordering is not)
31
+ * - `COMMENT ON ...`
32
+ * - the `schema_version` table's own structure (only its rows are
33
+ * captured)
34
+ * - permissions / `GRANT`s
35
+ *
36
+ * None of these are used by the current fuz_app auth schema. Extend
37
+ * `query_schema_snapshot` + `SchemaDiff` if a consumer's schema reaches
38
+ * for them; omitting them today keeps the diff surface focused on what
39
+ * fuz_app actually emits.
40
+ *
41
+ * @module
42
+ */
43
+ import type { ColumnSnapshot, SchemaSnapshot, SchemaVersionRow } from './schema_introspect.js';
44
+ /** Structured drift entry. `where` is the named source impl ('a' or 'b'). */
45
+ export type SchemaDiff = {
46
+ readonly kind: 'schema_version_only_in';
47
+ readonly where: 'a' | 'b';
48
+ readonly row: SchemaVersionRow;
49
+ } | {
50
+ readonly kind: 'schema_version_sequence_differs';
51
+ readonly namespace: string;
52
+ readonly name: string;
53
+ readonly a: number;
54
+ readonly b: number;
55
+ } | {
56
+ readonly kind: 'table_only_in';
57
+ readonly where: 'a' | 'b';
58
+ readonly table: string;
59
+ } | {
60
+ readonly kind: 'column_only_in';
61
+ readonly where: 'a' | 'b';
62
+ readonly table: string;
63
+ readonly column: string;
64
+ } | {
65
+ readonly kind: 'column_field_differs';
66
+ readonly table: string;
67
+ readonly column: string;
68
+ readonly field: keyof ColumnSnapshot;
69
+ readonly a: unknown;
70
+ readonly b: unknown;
71
+ } | {
72
+ readonly kind: 'index_only_in';
73
+ readonly where: 'a' | 'b';
74
+ readonly table: string;
75
+ readonly index: string;
76
+ } | {
77
+ readonly kind: 'index_definition_differs';
78
+ readonly table: string;
79
+ readonly index: string;
80
+ readonly a: string;
81
+ readonly b: string;
82
+ } | {
83
+ readonly kind: 'constraint_only_in';
84
+ readonly where: 'a' | 'b';
85
+ readonly table: string;
86
+ readonly constraint: string;
87
+ } | {
88
+ readonly kind: 'constraint_differs';
89
+ readonly table: string;
90
+ readonly constraint: string;
91
+ readonly a: {
92
+ type: string;
93
+ definition: string;
94
+ };
95
+ readonly b: {
96
+ type: string;
97
+ definition: string;
98
+ };
99
+ } | {
100
+ readonly kind: 'sequence_only_in';
101
+ readonly where: 'a' | 'b';
102
+ readonly sequence: string;
103
+ } | {
104
+ readonly kind: 'sequence_data_type_differs';
105
+ readonly sequence: string;
106
+ readonly a: string;
107
+ readonly b: string;
108
+ };
109
+ /**
110
+ * Structural diff between two snapshots — empty array means parity holds.
111
+ *
112
+ * Order of diffs is deterministic: schema_version first, then tables in
113
+ * sorted order (with column/index/constraint sub-diffs grouped per table),
114
+ * then sequences. Consumers can rely on this for stable diff output.
115
+ */
116
+ export declare const diff_schema_snapshots: (a: SchemaSnapshot, b: SchemaSnapshot) => Array<SchemaDiff>;
117
+ /** Labels used in formatted output — defaults to `'a'` and `'b'`. */
118
+ export interface SchemaDiffLabels {
119
+ readonly a?: string;
120
+ readonly b?: string;
121
+ }
122
+ /**
123
+ * Render a diff list as a human-readable multi-line string. Empty diffs
124
+ * produce an empty string.
125
+ */
126
+ export declare const format_schema_diffs: (diffs: ReadonlyArray<SchemaDiff>, labels?: SchemaDiffLabels) => string;
127
+ /**
128
+ * Throw if the two snapshots disagree. The error message names the impls
129
+ * (via `labels`) and lists every diff, so the failure is self-diagnosing.
130
+ *
131
+ * Consumers wire this after bootstrapping each impl against an isolated DB:
132
+ *
133
+ * ```ts
134
+ * await drop_recreate_db('zzz_test');
135
+ * await spawn_backend(deno_config);
136
+ * const snapshot_deno = await query_schema_snapshot(db, {});
137
+ * await drop_recreate_db('zzz_test');
138
+ * await spawn_backend(rust_config);
139
+ * const snapshot_rust = await query_schema_snapshot(db, {});
140
+ * assert_schema_snapshots_equal(snapshot_deno, snapshot_rust, {a: 'deno', b: 'rust'});
141
+ * ```
142
+ */
143
+ export declare const assert_schema_snapshots_equal: (a: SchemaSnapshot, b: SchemaSnapshot, labels?: SchemaDiffLabels) => void;
144
+ //# sourceMappingURL=schema_parity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema_parity.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/schema_parity.ts"],"names":[],"mappings":"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"}