@fuzdev/fuz_app 0.74.0 → 0.76.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 (37) hide show
  1. package/dist/auth/CLAUDE.md +4 -0
  2. package/dist/auth/account_routes.d.ts.map +1 -1
  3. package/dist/auth/account_routes.js +19 -14
  4. package/dist/auth/bearer_auth.d.ts +5 -1
  5. package/dist/auth/bearer_auth.d.ts.map +1 -1
  6. package/dist/auth/bearer_auth.js +13 -1
  7. package/dist/db/CLAUDE.md +4 -3
  8. package/dist/db/cell_queries.d.ts +0 -23
  9. package/dist/db/cell_queries.d.ts.map +1 -1
  10. package/dist/db/cell_queries.js +0 -30
  11. package/dist/http/route_spec.d.ts +15 -0
  12. package/dist/http/route_spec.d.ts.map +1 -1
  13. package/dist/http/surface.d.ts +6 -0
  14. package/dist/http/surface.d.ts.map +1 -1
  15. package/dist/http/surface.js +1 -0
  16. package/dist/server/serve_fact_route.d.ts +84 -33
  17. package/dist/server/serve_fact_route.d.ts.map +1 -1
  18. package/dist/server/serve_fact_route.js +242 -141
  19. package/dist/testing/CLAUDE.md +5 -1
  20. package/dist/testing/cross_backend/setup.d.ts +33 -0
  21. package/dist/testing/cross_backend/setup.d.ts.map +1 -1
  22. package/dist/testing/cross_backend/setup.js +19 -1
  23. package/dist/testing/cross_backend/standard.d.ts +19 -1
  24. package/dist/testing/cross_backend/standard.d.ts.map +1 -1
  25. package/dist/testing/cross_backend/standard.js +2 -0
  26. package/dist/testing/cross_backend/testing_reset_actions.d.ts +14 -0
  27. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  28. package/dist/testing/cross_backend/testing_reset_actions.js +24 -1
  29. package/dist/testing/integration.d.ts.map +1 -1
  30. package/dist/testing/integration.js +78 -0
  31. package/dist/testing/round_trip.d.ts +19 -1
  32. package/dist/testing/round_trip.d.ts.map +1 -1
  33. package/dist/testing/round_trip.js +75 -3
  34. package/dist/testing/rpc_round_trip.d.ts +23 -1
  35. package/dist/testing/rpc_round_trip.d.ts.map +1 -1
  36. package/dist/testing/rpc_round_trip.js +26 -1
  37. package/package.json +7 -7
@@ -43,7 +43,7 @@ import type { RoleSchemaResult } from '../../auth/role_schema.js';
43
43
  import type { AppSurfaceSpec } from '../../http/surface.js';
44
44
  import type { RpcEndpointsSuiteOption } from '../rpc_helpers.js';
45
45
  import type { BackendCapabilities } from './capabilities.js';
46
- import type { SetupTest } from './setup.js';
46
+ import type { SetupTest, TestFixture } from './setup.js';
47
47
  /**
48
48
  * Configuration for `describe_standard_cross_process_tests`.
49
49
  *
@@ -94,6 +94,24 @@ export interface StandardCrossProcessTestOptions {
94
94
  * / `info/refs`) which stream git protocol bytes.
95
95
  */
96
96
  round_trip_skip_routes?: Array<string>;
97
+ /**
98
+ * Forwarded to `describe_rpc_round_trip_tests` as `success_fixtures`
99
+ * (method name → async params factory). Drives a populated **success**
100
+ * body for referential RPC reads (`*_get`, `*_log`) the nil-id round-trip
101
+ * can only ever error on, and validates it against the method's `output`
102
+ * schema on each backend — the success-shape parity check. See
103
+ * `RpcRoundTripTestOptions.success_fixtures`.
104
+ */
105
+ rpc_success_fixtures?: Map<string, (fixture: TestFixture) => Promise<Record<string, unknown>>>;
106
+ /**
107
+ * Forwarded to `describe_round_trip_validation` as `success_fixtures`
108
+ * (`'METHOD /path'` → async `{url?, body?}` factory) for referential REST
109
+ * routes. See `RoundTripTestOptions.success_fixtures`.
110
+ */
111
+ rest_success_fixtures?: Map<string, (fixture: TestFixture) => Promise<{
112
+ url?: string;
113
+ body?: Record<string, unknown>;
114
+ }>>;
97
115
  }
98
116
  /**
99
117
  * Run the cross-process standard test bundle — integration, admin (when
@@ -1 +1 @@
1
- {"version":3,"file":"standard.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/standard.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,8BAA8B,CAAC;AACjE,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAM1D,OAAO,KAAK,EAAC,uBAAuB,EAAC,MAAM,mBAAmB,CAAC;AAC/D,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,mBAAmB,CAAC;AAC3D,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,YAAY,CAAC;AAE1C;;;;;;GAMG;AACH,MAAM,WAAW,+BAA+B;IAC/C,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,aAAa,EAAE,uBAAuB,CAAC;IACvC;;;OAGG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;;OAMG;IACH,sBAAsB,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACvC;AAED;;;;GAIG;AACH,eAAO,MAAM,qCAAqC,GACjD,SAAS,+BAA+B,KACtC,IAsCF,CAAC"}
1
+ {"version":3,"file":"standard.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/standard.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,8BAA8B,CAAC;AACjE,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAM1D,OAAO,KAAK,EAAC,uBAAuB,EAAC,MAAM,mBAAmB,CAAC;AAC/D,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,mBAAmB,CAAC;AAC3D,OAAO,KAAK,EAAC,SAAS,EAAE,WAAW,EAAC,MAAM,YAAY,CAAC;AAEvD;;;;;;GAMG;AACH,MAAM,WAAW,+BAA+B;IAC/C,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,aAAa,EAAE,uBAAuB,CAAC;IACvC;;;OAGG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;;OAMG;IACH,sBAAsB,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACvC;;;;;;;OAOG;IACH,oBAAoB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAC/F;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,GAAG,CAC1B,MAAM,EACN,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAC,CAAC,CACjF,CAAC;CACF;AAED;;;;GAIG;AACH,eAAO,MAAM,qCAAqC,GACjD,SAAS,+BAA+B,KACtC,IAwCF,CAAC"}
@@ -23,6 +23,7 @@ export const describe_standard_cross_process_tests = (options) => {
23
23
  surface_source: options.surface_source,
24
24
  capabilities: options.capabilities,
25
25
  skip_routes: options.round_trip_skip_routes,
26
+ success_fixtures: options.rest_success_fixtures,
26
27
  });
27
28
  describe_rpc_round_trip_tests({
28
29
  setup_test: options.setup_test,
@@ -30,6 +31,7 @@ export const describe_standard_cross_process_tests = (options) => {
30
31
  capabilities: options.capabilities,
31
32
  session_options: options.session_options,
32
33
  rpc_endpoints: options.rpc_endpoints,
34
+ success_fixtures: options.rpc_success_fixtures,
33
35
  });
34
36
  describe_data_exposure_tests({
35
37
  setup_test: options.setup_test,
@@ -106,6 +106,16 @@ export declare const testing_reset_action_spec: {
106
106
  password_value: z.ZodOptional<z.ZodString>;
107
107
  roles: z.ZodArray<z.ZodString>;
108
108
  }, z.core.$strict>>>;
109
+ /**
110
+ * Additional actor names to seed on the **keeper** account, beyond
111
+ * its single bootstrap actor. Drives the multi-actor `acting`
112
+ * selector branches (omitted `acting` + >1 actor ⇒ `actor_required`
113
+ * with the `available[]` list) that are otherwise unreachable
114
+ * cross-process — account creation only ever mints one actor, and no
115
+ * production wire path adds a second. Bootstrap-cradle seeding, same
116
+ * rationale as `extra_accounts`.
117
+ */
118
+ extra_actors: z.ZodOptional<z.ZodArray<z.ZodString>>;
109
119
  }, z.core.$strict>;
110
120
  readonly output: z.ZodObject<{
111
121
  account: z.ZodObject<{
@@ -128,6 +138,10 @@ export declare const testing_reset_action_spec: {
128
138
  api_token: z.ZodString;
129
139
  session_cookie: z.ZodString;
130
140
  }, z.core.$strict>>;
141
+ extra_actors: z.ZodArray<z.ZodObject<{
142
+ id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
143
+ name: z.ZodString;
144
+ }, z.core.$strict>>;
131
145
  }, z.core.$strict>;
132
146
  readonly async: true;
133
147
  readonly description: "Test-binary only — wipe auth tables, re-bootstrap a fresh keeper (+ optional extras), fire the domain-state reset.";
@@ -1 +1 @@
1
- {"version":3,"file":"testing_reset_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_reset_actions.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAGtB,OAAO,EAAa,KAAK,SAAS,EAAC,MAAM,6BAA6B,CAAC;AAEvE,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,8BAA8B,CAAC;AACjE,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,4BAA4B,CAAC;AACjE,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,gBAAgB,CAAC;AAkBvC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwBQ,CAAC;AAE/C;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,iCAAiC;;;;;;;;;;;;;;;;CAWA,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;;;;;;CAeC,CAAC;AAE/C;;;;;;GAMG;AACH,eAAO,MAAM,mCAAmC,QAAO,SACiB,CAAC;AAEzE;;;;;;;;;GASG;AACH,eAAO,MAAM,mCAAmC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWF,CAAC;AAE/C;;;;GAIG;AACH,eAAO,MAAM,qCAAqC,QAAO,SAGvD,CAAC;AAEH,4CAA4C;AAC5C,MAAM,WAAW,2BAA2B;IAC3C;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACjD;;;;;OAKG;IACH,QAAQ,CAAC,kBAAkB,EAAE,gBAAgB,CAAC;IAC9C;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACxD;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,OAAO,EACb,SAAS,2BAA2B,KAClC,KAAK,CAAC,SAAS,CA2GjB,CAAC;AAEF,0FAA0F;AAC1F,eAAO,MAAM,0BAA0B,UAAmC,CAAC"}
1
+ {"version":3,"file":"testing_reset_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_reset_actions.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAGtB,OAAO,EAAa,KAAK,SAAS,EAAC,MAAM,6BAA6B,CAAC;AAEvE,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,8BAA8B,CAAC;AACjE,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,4BAA4B,CAAC;AACjE,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,gBAAgB,CAAC;AAmBvC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;QAiBpC;;;;;;;;WAQG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWyC,CAAC;AAE/C;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,iCAAiC;;;;;;;;;;;;;;;;CAWA,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;;;;;;CAeC,CAAC;AAE/C;;;;;;GAMG;AACH,eAAO,MAAM,mCAAmC,QAAO,SACiB,CAAC;AAEzE;;;;;;;;;GASG;AACH,eAAO,MAAM,mCAAmC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWF,CAAC;AAE/C;;;;GAIG;AACH,eAAO,MAAM,qCAAqC,QAAO,SAGvD,CAAC;AAEH,4CAA4C;AAC5C,MAAM,WAAW,2BAA2B;IAC3C;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACjD;;;;;OAKG;IACH,QAAQ,CAAC,kBAAkB,EAAE,gBAAgB,CAAC;IAC9C;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACxD;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,OAAO,EACb,SAAS,2BAA2B,KAClC,KAAK,CAAC,SAAS,CAsHjB,CAAC;AAEF,0FAA0F;AAC1F,eAAO,MAAM,0BAA0B,UAAmC,CAAC"}
@@ -64,6 +64,7 @@ import { rpc_action } from '../../actions/action_rpc.js';
64
64
  import { ROLE_ADMIN, ROLE_KEEPER } from '../../auth/role_schema.js';
65
65
  import { auth_integration_truncate_tables } from '../db.js';
66
66
  import { query_schema_snapshot, SchemaSnapshot } from '../schema_introspect.js';
67
+ import { query_create_actor } from '../../auth/account_queries.js';
67
68
  import { create_test_account_with_credentials, mint_test_session, DEFAULT_TEST_PASSWORD, } from '../app_server.js';
68
69
  /** Output shape for an individual seeded account (keeper or extra). */
69
70
  const SeededAccountShape = z.strictObject({
@@ -112,9 +113,21 @@ export const testing_reset_action_spec = {
112
113
  roles: z.array(z.string()),
113
114
  }))
114
115
  .optional(),
116
+ /**
117
+ * Additional actor names to seed on the **keeper** account, beyond
118
+ * its single bootstrap actor. Drives the multi-actor `acting`
119
+ * selector branches (omitted `acting` + >1 actor ⇒ `actor_required`
120
+ * with the `available[]` list) that are otherwise unreachable
121
+ * cross-process — account creation only ever mints one actor, and no
122
+ * production wire path adds a second. Bootstrap-cradle seeding, same
123
+ * rationale as `extra_accounts`.
124
+ */
125
+ extra_actors: z.array(z.string()).optional(),
115
126
  }),
116
127
  output: SeededAccountShape.extend({
117
128
  extra_accounts: z.array(SeededAccountShape),
129
+ /** The keeper's additional actors (from input `extra_actors`), in order. */
130
+ extra_actors: z.array(z.strictObject({ id: Uuid, name: z.string() })),
118
131
  }),
119
132
  async: true,
120
133
  description: 'Test-binary only — wipe auth tables, re-bootstrap a fresh keeper (+ optional extras), fire the domain-state reset.',
@@ -301,6 +314,16 @@ export const create_testing_actions = (deps, options) => {
301
314
  });
302
315
  extras.push(seeded);
303
316
  }
317
+ // 5b. Seed any additional keeper actors. Same bootstrap-cradle
318
+ // bypass as extra_accounts — no production wire path mints a
319
+ // second actor, so the multi-actor `acting` branches need this
320
+ // direct insert. Order-preserving so the fixture can address
321
+ // them positionally.
322
+ const extra_actors = [];
323
+ for (const name of input.extra_actors ?? []) {
324
+ const seeded_actor = await query_create_actor({ db: ctx.db }, keeper.account.id, name);
325
+ extra_actors.push({ id: seeded_actor.id, name: seeded_actor.name });
326
+ }
304
327
  // 6. Refresh the daemon-token cache so subsequent daemon-token
305
328
  // requests resolve to the freshly seeded keeper. The
306
329
  // middleware's lazy-refresh path only fires when the cached
@@ -314,7 +337,7 @@ export const create_testing_actions = (deps, options) => {
314
337
  // against this open transaction under PGlite.
315
338
  if (reset_state)
316
339
  await reset_state(ctx.db);
317
- return { ...keeper, extra_accounts: extras };
340
+ return { ...keeper, extra_accounts: extras, extra_actors };
318
341
  }),
319
342
  rpc_action(testing_mint_session_action_spec, async (input, ctx) => {
320
343
  const { session_cookie } = await mint_test_session({
@@ -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,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAM9D,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AAiB1B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAC,KAAK,mBAAmB,EAAC,MAAM,iCAAiC,CAAC;AACzE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAGxD;;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,IA60CF,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,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAM9D,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AAkB1B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAC,KAAK,mBAAmB,EAAC,MAAM,iCAAiC,CAAC;AACzE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAGxD;;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,IAw6CF,CAAC"}
@@ -23,6 +23,7 @@ import { ErrorCoverageCollector, assert_error_coverage, DEFAULT_INTEGRATION_ERRO
23
23
  import { is_public_auth } from '../http/auth_shape.js';
24
24
  import { account_verify_action_spec, account_session_list_action_spec, account_session_revoke_action_spec, account_session_revoke_all_action_spec, account_token_create_action_spec, account_token_list_action_spec, account_token_revoke_action_spec, } from '../auth/account_action_specs.js';
25
25
  import { invite_create_action_spec } from '../auth/admin_action_specs.js';
26
+ import { LoginOutput, AccountStatusOutput } from '../auth/account_routes.js';
26
27
  import {} from './cross_backend/capabilities.js';
27
28
  import { DEFAULT_TEST_PASSWORD } from './app_server.js';
28
29
  /**
@@ -91,6 +92,14 @@ export const describe_standard_integration_tests = (options) => {
91
92
  });
92
93
  assert_error_coverage(error_collector, auth_routes.length > 0 ? auth_routes : route_specs, {
93
94
  min_coverage: options.error_coverage_min ?? DEFAULT_INTEGRATION_ERROR_COVERAGE,
95
+ // Authorization denials (403) on these scoped auth routes — the
96
+ // credential-channel gate on /logout + /password, the invite gate on
97
+ // /signup — are exercised by the conformance + attack-surface suites,
98
+ // not this lifecycle suite. Drop 403 from this collector's denominator
99
+ // (same spirit as the [401, 403, 429] ignore in the attack-surface
100
+ // tightness defaults); otherwise #10 adding /logout's 403 to the spine
101
+ // surface tips the ratio under threshold here though the gate is tested.
102
+ ignore_statuses: [403],
94
103
  });
95
104
  });
96
105
  // --- 1. Login/logout lifecycle ---
@@ -269,6 +278,75 @@ export const describe_standard_integration_tests = (options) => {
269
278
  assert.deepStrictEqual(wrong_pw_keys, no_user_keys, 'Response keys must be identical to prevent account enumeration');
270
279
  assert.strictEqual(wrong_pw_body.error, no_user_body.error, 'Error codes must be identical');
271
280
  });
281
+ // Wire-shape gate: the successful `POST /login` body must strict-parse
282
+ // against `LoginOutput` (`{ok: true}`). `.strictObject` rejects any
283
+ // extra field, so a backend leaking `username` / `account_id` (the
284
+ // Rust spine's old shape) fails here on either impl.
285
+ test('successful login body strict-parses against LoginOutput', async () => {
286
+ const fixture = await options.setup_test();
287
+ const login_route = find_auth_route(route_specs, '/login', 'POST');
288
+ assert.ok(login_route, 'Expected POST /login route — ensure create_route_specs includes account routes');
289
+ const res = await fixture.transport(login_route.path, {
290
+ method: 'POST',
291
+ headers: {
292
+ host: 'localhost',
293
+ origin: 'http://localhost:5173',
294
+ 'content-type': 'application/json',
295
+ },
296
+ body: JSON.stringify({
297
+ username: fixture.account.username,
298
+ password: DEFAULT_TEST_PASSWORD,
299
+ }),
300
+ });
301
+ assert.strictEqual(res.status, 200);
302
+ const body = await res.json();
303
+ // Throws on any extra or missing field — drift on either backend fails.
304
+ LoginOutput.parse(body);
305
+ });
306
+ });
307
+ // --- 1b. Account status body (strict schema) ---
308
+ describe('account status response body', () => {
309
+ // Wire-shape gate: the authenticated `GET /api/account/status` body
310
+ // must strict-parse against `AccountStatusOutput` — the full shape
311
+ // `{account: SessionAccountJson, actor: ActorSummaryJson | null,
312
+ // role_grants: RoleGrantSummaryJson[]}`. `.strictObject` rejects any
313
+ // extra or missing field on `account` / `actor` / each role_grant, so
314
+ // a backend returning the old narrow shape (Rust's `{account:{id,
315
+ // username}, role_grants:[{role}]}`) fails here on either impl. The
316
+ // fixture keeper is single-actor, so `actor` must be non-null and
317
+ // `role_grants` populated (keeper holds keeper + admin globally).
318
+ //
319
+ // `/status` is mounted at `create_app_server` time (it needs the
320
+ // `bootstrap_available` runtime state), not by `create_account_route_specs`
321
+ // and not listed in the declared surface, so we can't gate on `route_specs`.
322
+ // A backend that mounts only the account-route factory (e.g. a minimal
323
+ // in-process route set) doesn't serve it — probe at runtime and skip on
324
+ // 404. The full spine surfaces (in-process + cross-process) serve it, so
325
+ // the gate runs there. `find_auth_route` can't be used: `/status` isn't a
326
+ // `RestAuthRouteSuffix`.
327
+ test('authenticated status body strict-parses against AccountStatusOutput', async (ctx) => {
328
+ const fixture = await options.setup_test();
329
+ const login_route = find_auth_route(route_specs, '/login', 'POST');
330
+ assert.ok(login_route, 'Expected POST /login route — ensure create_route_specs includes account routes');
331
+ // `/status` is the sibling of `/login` under the same account prefix.
332
+ const status_path = login_route.path.replace(/\/login$/, '/status');
333
+ const res = await fixture.transport(status_path, {
334
+ method: 'GET',
335
+ headers: fixture.create_session_headers({ host: 'localhost' }),
336
+ });
337
+ if (res.status === 404) {
338
+ // Backend doesn't mount /status (minimal route set) — nothing to gate.
339
+ ctx.skip();
340
+ return;
341
+ }
342
+ assert.strictEqual(res.status, 200);
343
+ const body = await res.json();
344
+ // Throws on any extra/missing field across account/actor/role_grants.
345
+ const parsed = AccountStatusOutput.parse(body);
346
+ // Single-actor keeper: actor resolved, role_grants populated.
347
+ assert.ok(parsed.actor, 'single-actor keeper must resolve a non-null actor');
348
+ assert.ok(parsed.role_grants.length > 0, 'single-actor keeper must have populated role_grants');
349
+ });
272
350
  });
273
351
  // --- 2. Cookie attributes ---
274
352
  describe('cookie attributes', () => {
@@ -1,6 +1,6 @@
1
1
  import './assert_dev_env.js';
2
2
  import type { BackendCapabilities } from './cross_backend/capabilities.js';
3
- import type { SetupTest } from './cross_backend/setup.js';
3
+ import type { SetupTest, TestFixture } from './cross_backend/setup.js';
4
4
  import type { AppSurfaceSpec } from '../http/surface.js';
5
5
  /** Options for `describe_round_trip_validation`. */
6
6
  export interface RoundTripTestOptions {
@@ -22,6 +22,24 @@ export interface RoundTripTestOptions {
22
22
  skip_routes?: Array<string>;
23
23
  /** Override generated bodies for specific routes (`'METHOD /path'` → body). */
24
24
  input_overrides?: Map<string, Record<string, unknown>>;
25
+ /**
26
+ * Success-case fixtures for routes whose **populated success body** the
27
+ * generic nil-id input can't reach — referential REST routes whose path
28
+ * params / body must point at existing rows. Maps `'METHOD /path'` to an
29
+ * async factory that receives the per-test `fixture` (so it can seed the
30
+ * referenced state) and returns `{url?, body?}`: an explicit resolved `url`
31
+ * (when the factory built it from the ids it just seeded) and/or a request
32
+ * `body`. Omit `url` to fall back to the generated valid path.
33
+ *
34
+ * Distinct from `input_overrides` (body-only, accepts a valid error
35
+ * envelope): a `success_fixtures` entry **asserts a 2xx response** and
36
+ * validates it against the route's `output` schema — the success-shape
37
+ * parity check the nil-id round-trip can't perform.
38
+ */
39
+ success_fixtures?: Map<string, (fixture: TestFixture) => Promise<{
40
+ url?: string;
41
+ body?: Record<string, unknown>;
42
+ }>>;
25
43
  }
26
44
  /**
27
45
  * Run schema-driven round-trip validation tests.
@@ -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;AA0B7B,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,iCAAiC,CAAC;AACzE,OAAO,KAAK,EAAC,SAAS,EAAc,MAAM,0BAA0B,CAAC;AACrE,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,oBAAoB,CAAC;AAEvD,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;CACvD;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,8BAA8B,GAAI,SAAS,oBAAoB,KAAG,IA6D9E,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,EAAC,mBAAmB,EAAC,MAAM,iCAAiC,CAAC;AACzE,OAAO,KAAK,EAAC,SAAS,EAAE,WAAW,EAAC,MAAM,0BAA0B,CAAC;AACrE,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,oBAAoB,CAAC;AAGvD,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;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAC,CAAC,CACjF,CAAC;CACF;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,8BAA8B,GAAI,SAAS,oBAAoB,KAAG,IAyI9E,CAAC"}
@@ -16,8 +16,9 @@ import './assert_dev_env.js';
16
16
  *
17
17
  * @module
18
18
  */
19
- import { describe, test, beforeAll } from 'vitest';
19
+ import { describe, test, beforeAll, assert } from 'vitest';
20
20
  import { ROLE_ADMIN } from '../auth/role_schema.js';
21
+ import { is_public_auth, needs_actor, input_schema_declares_acting } from '../http/auth_shape.js';
21
22
  import { assert_response_matches_spec, pick_auth_headers } from './integration_helpers.js';
22
23
  import { resolve_valid_path, generate_valid_body } from './schema_generators.js';
23
24
  /**
@@ -54,14 +55,51 @@ export const describe_round_trip_validation = (options) => {
54
55
  roles: [ROLE_ADMIN],
55
56
  });
56
57
  });
58
+ // Mirror `pick_auth_headers`' account selection to recover the actor id
59
+ // of whichever account it authed as — so an `actor: 'required'` route
60
+ // that declares `acting?: ActingActor` gets the matching actor supplied
61
+ // explicitly (its sole actor, here), rather than relying on implicit
62
+ // single-actor resolution. Keeps such routes drivable without a
63
+ // consumer skip-list entry. Returns `null` for public routes.
64
+ const pick_acting_actor_id = (spec) => {
65
+ const { auth } = spec;
66
+ if (is_public_auth(auth))
67
+ return null;
68
+ if (auth.credential_types?.includes('daemon_token'))
69
+ return fixture.actor.id;
70
+ if (auth.roles?.length) {
71
+ return auth.roles.includes(ROLE_ADMIN) ? admin_account.actor.id : fixture.actor.id;
72
+ }
73
+ return authed_account.actor.id;
74
+ };
57
75
  test.each(describe_time_specs)('$method $path produces schema-valid response', async (spec) => {
58
76
  const route_key = `${spec.method} ${spec.path}`;
59
77
  if (skip_set.has(route_key))
60
78
  return;
79
+ // Raw-byte / streaming routes (git smart-HTTP, binary upload/download)
80
+ // can't be round-tripped — no meaningful body to synthesize, no JSON
81
+ // shape to assert. Auto-skip by the spec marker rather than making
82
+ // every consumer hand-list them in `skip_routes`.
83
+ if (spec.raw_body)
84
+ return;
61
85
  const url = resolve_valid_path(spec.path, spec.params);
62
86
  const override = options.input_overrides?.get(route_key);
63
- const body = override ?? generate_valid_body(spec.input);
87
+ let body = override ?? generate_valid_body(spec.input);
64
88
  const headers = pick_auth_headers(spec, fixture, authed_account, admin_account);
89
+ // Auto-supply `acting` for actor-required routes that declare it. The
90
+ // `actor !== 'none' ⟺ acting declared` registry invariant means a
91
+ // route either declares `acting` in `query` (REST GET/body-less) or
92
+ // `input` — supply the picked account's actor in the matching channel.
93
+ let request_url = url;
94
+ const acting_id = needs_actor(spec.auth) ? pick_acting_actor_id(spec) : null;
95
+ if (acting_id !== null) {
96
+ if (spec.query && input_schema_declares_acting(spec.query)) {
97
+ request_url = `${url}${url.includes('?') ? '&' : '?'}acting=${acting_id}`;
98
+ }
99
+ else if (input_schema_declares_acting(spec.input) && body) {
100
+ body = { ...body, acting: acting_id };
101
+ }
102
+ }
65
103
  const request_init = {
66
104
  method: spec.method,
67
105
  headers: {
@@ -70,7 +108,7 @@ export const describe_round_trip_validation = (options) => {
70
108
  },
71
109
  ...(body ? { body: JSON.stringify(body) } : {}),
72
110
  };
73
- const res = await fixture.transport(url, request_init);
111
+ const res = await fixture.transport(request_url, request_init);
74
112
  if (res.headers.get('Content-Type')?.includes('text/event-stream')) {
75
113
  await res.body?.cancel();
76
114
  return;
@@ -82,5 +120,39 @@ export const describe_round_trip_validation = (options) => {
82
120
  throw new Error(`Round-trip validation failed for ${route_key} (status ${res.status}): ${e.message}`);
83
121
  }
84
122
  });
123
+ test('declared success fixtures produce schema-valid success bodies', async () => {
124
+ const success_fixtures = options.success_fixtures;
125
+ if (!success_fixtures || success_fixtures.size === 0)
126
+ return;
127
+ for (const [route_key, build] of success_fixtures) {
128
+ const space = route_key.indexOf(' ');
129
+ const method = route_key.slice(0, space);
130
+ const path = route_key.slice(space + 1);
131
+ const spec = describe_time_specs.find((s) => s.method === method && s.path === path);
132
+ assert.ok(spec, `success_fixtures references unknown route '${route_key}'`);
133
+ const seeded = await build(fixture);
134
+ const url = seeded.url ?? resolve_valid_path(spec.path, spec.params);
135
+ const body = seeded.body;
136
+ const headers = pick_auth_headers(spec, fixture, authed_account, admin_account);
137
+ const res = await fixture.transport(url, {
138
+ method: spec.method,
139
+ headers: {
140
+ ...headers,
141
+ ...(body ? { 'content-type': 'application/json' } : {}),
142
+ },
143
+ ...(body ? { body: JSON.stringify(body) } : {}),
144
+ });
145
+ try {
146
+ assert.ok(res.ok, `success fixture expected a 2xx response, got status ${res.status}: ${await res
147
+ .clone()
148
+ .text()
149
+ .catch(() => '<unreadable>')}`);
150
+ await assert_response_matches_spec(describe_time_specs, spec.method, url, res);
151
+ }
152
+ catch (e) {
153
+ throw new Error(`Round-trip success-fixture failed for ${route_key} (status ${res.status}): ${e.message}`);
154
+ }
155
+ }
156
+ });
85
157
  });
86
158
  };
@@ -2,7 +2,7 @@ import './assert_dev_env.js';
2
2
  import type { AppSurfaceSpec } from '../http/surface.js';
3
3
  import { type RpcEndpointsSuiteOption } from './rpc_helpers.js';
4
4
  import type { BackendCapabilities } from './cross_backend/capabilities.js';
5
- import type { SetupTest } from './cross_backend/setup.js';
5
+ import type { SetupTest, TestFixture } from './cross_backend/setup.js';
6
6
  import type { SessionOptions } from '../auth/session_cookie.js';
7
7
  /** Options for `describe_rpc_round_trip_tests`. */
8
8
  export interface RpcRoundTripTestOptions {
@@ -34,6 +34,28 @@ export interface RpcRoundTripTestOptions {
34
34
  skip_methods?: Array<string>;
35
35
  /** Override generated params for specific methods (method name → params). */
36
36
  input_overrides?: Map<string, Record<string, unknown>>;
37
+ /**
38
+ * Success-case fixtures for methods whose **populated success body** the
39
+ * generic nil-id input can't reach — referential reads (`*_get`, `*_log`)
40
+ * whose required ids must point at existing rows. Maps method name to an
41
+ * async factory that receives the per-test `fixture` (so it can seed the
42
+ * referenced state — e.g. create a repo via `fixture.transport` +
43
+ * `fixture.create_session_headers()`) and returns the params that drive a
44
+ * **success** response.
45
+ *
46
+ * Distinct from `input_overrides`, which only swaps the request params; the
47
+ * response may still be a valid *error* envelope (missing-row `not_found`),
48
+ * which the generic loop accepts. A `success_fixtures` entry **asserts the
49
+ * response is `ok`** and validates `result` against the method's `output`
50
+ * schema — so a backend that drops a field, or errors where the other
51
+ * backend succeeds, fails loud. This is the success-shape parity check the
52
+ * nil-id round-trip structurally cannot perform (it only ever sees error
53
+ * envelopes for referential methods).
54
+ *
55
+ * Fired as POST. The factory runs against the shared per-describe fixture,
56
+ * so it must not assume a clean slate between entries (seed unique state).
57
+ */
58
+ success_fixtures?: Map<string, (fixture: TestFixture) => Promise<Record<string, unknown>>>;
37
59
  }
38
60
  /**
39
61
  * Run schema-driven round-trip validation for RPC endpoints.
@@ -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,EAAC,cAAc,EAAsB,MAAM,oBAAoB,CAAC;AAE5E,OAAO,EAMN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,iCAAiC,CAAC;AACzE,OAAO,KAAK,EAAC,SAAS,EAAc,MAAM,0BAA0B,CAAC;AACrE,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAE9D,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;CACvD;AAoDD;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,6BAA6B,GAAI,SAAS,uBAAuB,KAAG,IAoHhF,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,EAAC,cAAc,EAAsB,MAAM,oBAAoB,CAAC;AAE5E,OAAO,EAQN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,iCAAiC,CAAC;AACzE,OAAO,KAAK,EAAC,SAAS,EAAE,WAAW,EAAC,MAAM,0BAA0B,CAAC;AACrE,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAE9D,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,IAwJhF,CAAC"}
@@ -20,7 +20,7 @@ import { ROLE_ADMIN } from '../auth/role_schema.js';
20
20
  import { JSONRPC_METHOD_NOT_FOUND, JsonrpcErrorResponse } from '../http/jsonrpc.js';
21
21
  import { generate_valid_body } from './schema_generators.js';
22
22
  import { is_public_auth } from '../http/auth_shape.js';
23
- 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';
23
+ import { create_rpc_post_init, create_rpc_get_url, assert_jsonrpc_error_response, assert_jsonrpc_success_response, resolve_rpc_endpoints_for_setup, find_rpc_action, find_rpc_method, } from './rpc_helpers.js';
24
24
  /**
25
25
  * Pick auth headers matching an RPC method's auth requirement. Accepts
26
26
  * any `KeeperHeaderProvider` — both `TestApp` (in-process) and
@@ -170,5 +170,30 @@ export const describe_rpc_round_trip_tests = (options) => {
170
170
  }
171
171
  }
172
172
  });
173
+ test('declared success fixtures produce schema-valid success bodies', async () => {
174
+ const success_fixtures = options.success_fixtures;
175
+ if (!success_fixtures || success_fixtures.size === 0)
176
+ return;
177
+ for (const [method, build] of success_fixtures) {
178
+ const located = find_rpc_action(rpc_endpoints_for_setup, method);
179
+ assert.ok(located, `success_fixtures references unknown RPC method '${method}'`);
180
+ const surface = find_rpc_method(surface_rpc_endpoints, method);
181
+ assert.ok(surface, `success_fixtures method '${method}' missing from generated surface`);
182
+ const params = await build(fixture);
183
+ const headers = pick_rpc_auth_headers(surface.method_spec, fixture, authed_account, admin_account);
184
+ const init = create_rpc_post_init(method, params);
185
+ Object.assign(init.headers, headers);
186
+ const res = await fixture.transport(located.path, init);
187
+ const body = await res.json();
188
+ try {
189
+ assert_method_implemented(method, body);
190
+ assert.ok(res.ok, `success fixture expected a success response, got status ${res.status}: ${JSON.stringify(body)}`);
191
+ assert_jsonrpc_success_response(body, located.action.spec.output);
192
+ }
193
+ catch (e) {
194
+ throw new Error(`RPC success-fixture failed for ${method} (status ${res.status}): ${e.message}`);
195
+ }
196
+ }
197
+ });
173
198
  });
174
199
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzdev/fuz_app",
3
- "version": "0.74.0",
3
+ "version": "0.76.0",
4
4
  "description": "fullstack app library",
5
5
  "glyph": "🗝",
6
6
  "logo": "logo.svg",
@@ -63,12 +63,12 @@
63
63
  },
64
64
  "devDependencies": {
65
65
  "@electric-sql/pglite": "^0.4.5",
66
- "@fuzdev/blake3_wasm": "^0.1.0",
66
+ "@fuzdev/blake3_wasm": "^0.1.1",
67
67
  "@fuzdev/fuz_code": "^0.45.1",
68
- "@fuzdev/fuz_css": "^0.60.0",
69
- "@fuzdev/fuz_ui": "^0.195.1",
70
- "@fuzdev/fuz_util": "^0.62.0",
71
- "@fuzdev/gro": "^0.199.1",
68
+ "@fuzdev/fuz_css": "^0.61.1",
69
+ "@fuzdev/fuz_ui": "^0.197.0",
70
+ "@fuzdev/fuz_util": "^0.63.0",
71
+ "@fuzdev/gro": "^0.200.0",
72
72
  "@hono/node-server": "^1.19.14",
73
73
  "@hono/node-ws": "^1.3.1",
74
74
  "@jridgewell/trace-mapping": "^0.3.31",
@@ -94,7 +94,7 @@
94
94
  "prettier-plugin-svelte": "^3.5.1",
95
95
  "svelte": "^5.56.0",
96
96
  "svelte-check": "^4.4.5",
97
- "svelte-docinfo": "^0.1.0",
97
+ "svelte-docinfo": "^0.2.1",
98
98
  "svelte2tsx": "^0.7.52",
99
99
  "tslib": "^2.8.1",
100
100
  "typescript": "^5.9.3",