@fuzdev/fuz_app 0.148.0 → 0.149.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 (88) hide show
  1. package/dist/actions/CLAUDE.md +2 -2
  2. package/dist/actions/action_event.d.ts.map +1 -1
  3. package/dist/actions/action_event.js +14 -1
  4. package/dist/actions/perform_action.d.ts +17 -1
  5. package/dist/actions/perform_action.d.ts.map +1 -1
  6. package/dist/actions/perform_action.js +22 -2
  7. package/dist/auth/CLAUDE.md +3 -2
  8. package/dist/auth/account_action_specs.d.ts +12 -0
  9. package/dist/auth/account_action_specs.d.ts.map +1 -1
  10. package/dist/auth/account_action_specs.js +16 -2
  11. package/dist/auth/account_actions.d.ts.map +1 -1
  12. package/dist/auth/account_actions.js +7 -4
  13. package/dist/auth/admin_action_specs.d.ts.map +1 -1
  14. package/dist/auth/admin_action_specs.js +29 -8
  15. package/dist/auth/audit_emitter.d.ts +14 -0
  16. package/dist/auth/audit_emitter.d.ts.map +1 -1
  17. package/dist/auth/audit_emitter.js +5 -1
  18. package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
  19. package/dist/auth/role_grant_offer_action_specs.js +5 -3
  20. package/dist/http/CLAUDE.md +14 -2
  21. package/dist/http/error_schemas.d.ts +8 -0
  22. package/dist/http/error_schemas.d.ts.map +1 -1
  23. package/dist/http/error_schemas.js +12 -0
  24. package/dist/http/jsonrpc_errors.d.ts +40 -15
  25. package/dist/http/jsonrpc_errors.d.ts.map +1 -1
  26. package/dist/http/jsonrpc_errors.js +90 -35
  27. package/dist/schema_meta.d.ts +60 -0
  28. package/dist/schema_meta.d.ts.map +1 -1
  29. package/dist/schema_meta.js +64 -0
  30. package/dist/testing/CLAUDE.md +162 -61
  31. package/dist/testing/adversarial_input_cases.d.ts +67 -7
  32. package/dist/testing/adversarial_input_cases.d.ts.map +1 -1
  33. package/dist/testing/adversarial_input_cases.js +210 -165
  34. package/dist/testing/audit_drift_guard.d.ts +3 -2
  35. package/dist/testing/audit_drift_guard.d.ts.map +1 -1
  36. package/dist/testing/audit_drift_guard.js +5 -3
  37. package/dist/testing/cross_backend/audit_cross_helpers.d.ts +6 -0
  38. package/dist/testing/cross_backend/audit_cross_helpers.d.ts.map +1 -1
  39. package/dist/testing/cross_backend/audit_cross_helpers.js +11 -0
  40. package/dist/testing/cross_backend/capabilities.d.ts +27 -4
  41. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  42. package/dist/testing/cross_backend/capabilities.js +10 -6
  43. package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
  44. package/dist/testing/cross_backend/cell_crud.js +117 -2
  45. package/dist/testing/cross_backend/cross_rpc.d.ts +3 -1
  46. package/dist/testing/cross_backend/cross_rpc.d.ts.map +1 -1
  47. package/dist/testing/cross_backend/cross_rpc.js +2 -2
  48. package/dist/testing/cross_backend/default_backend_configs.d.ts +9 -7
  49. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
  50. package/dist/testing/cross_backend/default_backend_configs.js +3 -3
  51. package/dist/testing/cross_backend/rpc_adversarial.d.ts +9 -0
  52. package/dist/testing/cross_backend/rpc_adversarial.d.ts.map +1 -1
  53. package/dist/testing/cross_backend/rpc_adversarial.js +16 -6
  54. package/dist/testing/cross_backend/rust_spine_consumer_surface.d.ts +1 -1
  55. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
  56. package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +6 -3
  57. package/dist/testing/cross_backend/setup.d.ts +2 -2
  58. package/dist/testing/cross_backend/spine_suites.d.ts +85 -5
  59. package/dist/testing/cross_backend/spine_suites.d.ts.map +1 -1
  60. package/dist/testing/cross_backend/spine_suites.js +291 -20
  61. package/dist/testing/cross_backend/spine_surface_constants.d.ts +15 -0
  62. package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
  63. package/dist/testing/cross_backend/spine_surface_constants.js +24 -0
  64. package/dist/testing/cross_backend/testing_backdoor.d.ts +27 -4
  65. package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
  66. package/dist/testing/cross_backend/testing_backdoor.js +136 -41
  67. package/dist/testing/cross_backend/testing_reset_actions.d.ts +30 -15
  68. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  69. package/dist/testing/cross_backend/testing_reset_actions.js +52 -35
  70. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
  71. package/dist/testing/cross_backend/ts_spine_backend_config.js +6 -3
  72. package/dist/testing/rpc_attack_surface.d.ts +10 -0
  73. package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
  74. package/dist/testing/rpc_attack_surface.js +15 -3
  75. package/dist/testing/rpc_envelope_cases.d.ts +5 -2
  76. package/dist/testing/rpc_envelope_cases.d.ts.map +1 -1
  77. package/dist/testing/rpc_envelope_cases.js +4 -1
  78. package/dist/testing/rpc_round_trip.js +3 -3
  79. package/dist/testing/schema_generators.d.ts +99 -2
  80. package/dist/testing/schema_generators.d.ts.map +1 -1
  81. package/dist/testing/schema_generators.js +244 -76
  82. package/dist/testing/stubs.d.ts +4 -3
  83. package/dist/testing/stubs.d.ts.map +1 -1
  84. package/dist/testing/stubs.js +6 -4
  85. package/dist/zod.d.ts +9 -0
  86. package/dist/zod.d.ts.map +1 -1
  87. package/dist/zod.js +20 -0
  88. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  import '../assert_dev_env.js';
2
+ import type { TestingBackdoorMethod } from './spine_surface_constants.js';
2
3
  /**
3
4
  * Optional behaviors a backend may support. Each flag's TSDoc names the
4
5
  * tests that gate on it; add a new flag here before referencing it from
@@ -61,7 +62,8 @@ export interface BackendCapabilities {
61
62
  /**
62
63
  * The cell-gated fact-serving route (`GET /api/cells/:cell_id/facts/:hash`)
63
64
  * is live-mounted on the backend, its DB carries the `fuz_facts` migration
64
- * namespace, and it registers the `_testing_put_fact` seeder. Gates
65
+ * namespace, and it mounts the `_testing_put_fact` seeder — declared in
66
+ * `testing_backdoors`, which `describe_spine_cross_suites` requires. Gates
65
67
  * `describe_fact_serving_cross_tests`' per-reference read model (cell-scoped
66
68
  * admit via a viewable cell, cross-owner-dedup-no-leak, 404-mask),
67
69
  * `describe_cell_ref_admission_cross_tests`, and the fact cases of the
@@ -191,6 +193,25 @@ export interface BackendCapabilities {
191
193
  * Deno, Bun, and the Rust spine, which send the response as built.
192
194
  */
193
195
  readonly ws_refusal_body: boolean;
196
+ /**
197
+ * The `_testing_*` backdoors the backend mounts on its RPC endpoint, from
198
+ * `TESTING_BACKDOOR_METHODS` (a consumer's own seams stay off it). Not a
199
+ * flag, but declared like a `MountFlag`: no family default, so every config
200
+ * states it. `describe_testing_backdoor_cross_tests` checks the declaration
201
+ * against the backend — a declared method must be mounted, an undeclared one
202
+ * must answer `method_not_found` — and runs its credential-gate rows over
203
+ * the declared ones; `describe_spine_cross_suites` requires `_testing_reset`
204
+ * and `_testing_drain_effects` (the per-test reset and the audit barrier),
205
+ * skips a suite whose backdoor is undeclared, naming it — `credential_deadline`
206
+ * (`_testing_shorten_credential_expiry`), `origin_conformance`
207
+ * (`_testing_origin_check`), `ws_outbound_overflow`
208
+ * (`_testing_flood_notifications`) — and requires `_testing_put_fact` of a
209
+ * backend declaring `fact_serving`.
210
+ *
211
+ * fuz_app's spine presets declare every one; the in-process transport none
212
+ * (the backdoor suites are cross-process only).
213
+ */
214
+ readonly testing_backdoors: ReadonlyArray<TestingBackdoorMethod>;
194
215
  }
195
216
  /**
196
217
  * The capabilities that describe a backend's own mounts, never a runtime
@@ -203,8 +224,9 @@ export interface BackendCapabilities {
203
224
  */
204
225
  export type MountFlag = keyof Pick<BackendCapabilities, 'ws' | 'sse' | 'cell_crud' | 'cell_relations' | 'cell_gated_create' | 'fact_serving' | 'fact_serving_admin_hash' | 'ready' | 'peer_request' | 'ws_account_actions' | 'ws_role_gated'>;
205
226
  /**
206
- * A family's default capabilities: every flag but the `MountFlag`s, which
207
- * each backend config states itself. Spread one and add every mount flag:
227
+ * A family's default capabilities: every flag but the `MountFlag`s and the
228
+ * `testing_backdoors` list, which each backend config states itself. Spread
229
+ * one and add every mount flag and the backdoor list:
208
230
  *
209
231
  * ```ts
210
232
  * capabilities: {
@@ -213,10 +235,11 @@ export type MountFlag = keyof Pick<BackendCapabilities, 'ws' | 'sse' | 'cell_cru
213
235
  * cell_crud: false, cell_relations: false, cell_gated_create: false,
214
236
  * fact_serving: false, fact_serving_admin_hash: false,
215
237
  * ws_account_actions: false, ws_role_gated: false,
238
+ * testing_backdoors: ['_testing_reset', '_testing_drain_effects'],
216
239
  * }
217
240
  * ```
218
241
  */
219
- export type FamilyDefaultCapabilities = Omit<BackendCapabilities, MountFlag>;
242
+ export type FamilyDefaultCapabilities = Omit<BackendCapabilities, MountFlag | 'testing_backdoors'>;
220
243
  /**
221
244
  * Capability declarations for the in-process Hono transport. Nearly every
222
245
  * flag is `true` because in-process testing exercises the full backend
@@ -1 +1 @@
1
- {"version":3,"file":"capabilities.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/capabilities.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAiD9B;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IACnC;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB;;;;;OAKG;IACH,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B;;;;;;;;OAQG;IACH,QAAQ,CAAC,uBAAuB,EAAE,OAAO,CAAC;IAC1C;;;;;;;;OAQG;IACH,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB;;;;;;;;OAQG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC;;;;;;;;;;;;;;;;;OAiBG;IACH,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,uBAAuB,EAAE,OAAO,CAAC;IAC1C;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAC;IACrC;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC;;;;;;;;;OASG;IACH,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;CAClC;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,IAAI,CACjC,mBAAmB,EACjB,IAAI,GACJ,KAAK,GACL,WAAW,GACX,gBAAgB,GAChB,mBAAmB,GACnB,cAAc,GACd,yBAAyB,GACzB,OAAO,GACP,cAAc,GACd,oBAAoB,GACpB,eAAe,CACjB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAAC,mBAAmB,EAAE,SAAS,CAAC,CAAC;AAE7E;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,uBAAuB,EAAE,mBAyBpC,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,OAAO,GAAI,MAAM,OAAO,EAAE,MAAM,MAAM,EAAE,IAAI,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAG,IAMrF,CAAC"}
1
+ {"version":3,"file":"capabilities.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/capabilities.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAkD9B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAE1E;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IACnC;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB;;;;;OAKG;IACH,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B;;;;;;;;OAQG;IACH,QAAQ,CAAC,uBAAuB,EAAE,OAAO,CAAC;IAC1C;;;;;;;;OAQG;IACH,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB;;;;;;;;OAQG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC;;;;;;;;;;;;;;;;;OAiBG;IACH,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,uBAAuB,EAAE,OAAO,CAAC;IAC1C;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAC;IACrC;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC;;;;;;;;;OASG;IACH,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC;;;;;;;;;;;;;;;;;OAiBG;IACH,QAAQ,CAAC,iBAAiB,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;CACjE;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,IAAI,CACjC,mBAAmB,EACjB,IAAI,GACJ,KAAK,GACL,WAAW,GACX,gBAAgB,GAChB,mBAAmB,GACnB,cAAc,GACd,yBAAyB,GACzB,OAAO,GACP,cAAc,GACd,oBAAoB,GACpB,eAAe,CACjB,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAAC,mBAAmB,EAAE,SAAS,GAAG,mBAAmB,CAAC,CAAC;AAEnG;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,uBAAuB,EAAE,mBA4BpC,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,OAAO,GAAI,MAAM,OAAO,EAAE,MAAM,MAAM,EAAE,IAAI,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAG,IAMrF,CAAC"}
@@ -28,9 +28,9 @@ import '../assert_dev_env.js';
28
28
  * `rust_spine_stub_backend_config.ts` (the rust family default plus every mount flag).
29
29
  *
30
30
  * **Mount flags have no default.** A flag naming something a backend mounts
31
- * (a `MountFlag`) is never inherited: the family defaults are
32
- * `FamilyDefaultCapabilities`, which omits them, so every backend config states
33
- * each one. The family defaults carry only what the family guarantees — the
31
+ * (a `MountFlag`) is never inherited, nor is the `testing_backdoors` list: the
32
+ * family defaults are `FamilyDefaultCapabilities`, which omits them, so every
33
+ * backend config states each one. The family defaults carry only what the family guarantees — the
34
34
  * declared spine surface's mounts (`account_lifecycle`, `account_status`) and
35
35
  * the runtime behavior of its reference runtime (`ws_handshake_pipelining`,
36
36
  * `ws_refusal_body`: Node for TS, hyper for Rust), which a backend on another
@@ -38,8 +38,9 @@ import '../assert_dev_env.js';
38
38
  *
39
39
  * **Gating flags only.** `BackendCapabilities` holds only flags a suite
40
40
  * actually gates on (each has a `test_if(capabilities.X, ...)` reader, or —
41
- * `ws_refusal_body` — picks how a case reads a refusal). A wiring fact no suite
42
- * reads is not a capability.
41
+ * `ws_refusal_body` — picks how a case reads a refusal), plus the
42
+ * `testing_backdoors` list the backdoor suite and `describe_spine_cross_suites`
43
+ * read. A wiring fact no suite reads is not a capability.
43
44
  *
44
45
  * @module
45
46
  */
@@ -81,7 +82,10 @@ export const in_process_capabilities = Object.freeze({
81
82
  ws_role_gated: false,
82
83
  // Read only by the front-door table's WS rows, which skip in-process (no
83
84
  // socket to upgrade); Hono's own refusals carry their bodies.
84
- ws_refusal_body: true
85
+ ws_refusal_body: true,
86
+ // The in-process app mounts no `_testing_*` action; the suites reading this
87
+ // are cross-process only.
88
+ testing_backdoors: Object.freeze([])
85
89
  });
86
90
  /**
87
91
  * Conditional `test()` wrapper — registers a vitest case only when
@@ -1 +1 @@
1
- {"version":3,"file":"cell_crud.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_crud.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAiE9B,OAAO,KAAK,EAAE,uCAAuC,EAAE,MAAM,YAAY,CAAC;AAG1E,eAAO,MAAM,8BAA8B,GAC1C,SAAS,uCAAuC,KAC9C,IA6lBF,CAAC"}
1
+ {"version":3,"file":"cell_crud.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_crud.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA0E9B,OAAO,KAAK,EAAE,uCAAuC,EAAE,MAAM,YAAY,CAAC;AAgE1E,eAAO,MAAM,8BAA8B,GAC1C,SAAS,uCAAuC,KAC9C,IA4uBF,CAAC"}
@@ -38,7 +38,13 @@ import '../assert_dev_env.js';
38
38
  * accepted as a list filter that matches nothing;
39
39
  * - a `kind` of exactly `CELL_KIND_LENGTH_MAX` UTF-16 code units is stored
40
40
  * whole, and one unit past it is `invalid_params` on create and as a list
41
- * filter.
41
+ * filter;
42
+ * - a `data` that is no `CellData` — not an object, or a `label` / `summary`
43
+ * that is no string — is 400 `invalid_params` with the generic message and
44
+ * no reason on `cell_create`, `cell_update` and (with
45
+ * `capabilities.cell_relations`) `cell_clone`'s `with_data_patch`, ahead of
46
+ * every cell check (a missing or hidden cell refuses it the same way, never
47
+ * with the 404 mask), and writes no cell.
42
48
  *
43
49
  * The visibility-manage-tier 403 (`cell_visibility_manage_only`) needs a
44
50
  * non-owner editor, which only a `cell_grant` can produce, so it lives in
@@ -48,11 +54,64 @@ import '../assert_dev_env.js';
48
54
  * @module
49
55
  */
50
56
  import { describe, assert } from 'vitest';
51
- import { CELL_KIND_LENGTH_MAX, CellCreateOutput, CellDeleteOutput, CellGetOutput, CellListOutput, CellUpdateOutput } from '../../auth/cell_action_specs.js';
57
+ import { jsonrpc_error_messages } from '../../http/jsonrpc_errors.js';
58
+ import { CELL_KIND_LENGTH_MAX, CELL_LIST_LIMIT_MAX, CellCreateOutput, CellDeleteOutput, CellGetOutput, CellListOutput, CellUpdateOutput } from '../../auth/cell_action_specs.js';
52
59
  import { test_if } from './capabilities.js';
53
60
  import { cross_rpc_call, error_reason, expect_output } from './cross_rpc.js';
54
61
  import { assert_iso8601_seconds, assert_iso8601_seconds_nullable } from './wire_shapes.js';
55
62
  import { SPINE_RPC_PATH } from './spine_surface_constants.js';
63
+ /**
64
+ * `data` values that are no `CellData` — not an object, or an object whose
65
+ * `label` / `summary` is no string. Twin of the Rust spine's mistyped-data
66
+ * table in its cell authz tests.
67
+ */
68
+ const MISTYPED_CELL_DATA = [
69
+ 'not_an_object',
70
+ 42,
71
+ true,
72
+ [1],
73
+ { label: 123 },
74
+ { label: null },
75
+ { summary: {} },
76
+ { summary: false }
77
+ ];
78
+ /** The message every schema refusal answers, naming no field. */
79
+ const INVALID_PARAMS_MESSAGE = jsonrpc_error_messages.invalid_params().message;
80
+ /**
81
+ * Assert `r` is a schema refusal: 400 `invalid_params` with the generic
82
+ * message and no `data`, save the Zod `issues` a TS spine adds under DEV
83
+ * alone — an array of issue objects, each with a string `code` and
84
+ * `message`, and never a `reason`.
85
+ */
86
+ const assert_schema_refusal = (r, label) => {
87
+ const detail = `${label}: got ${r.status} ${JSON.stringify(r.error ?? r.result)}`;
88
+ assert.strictEqual(r.status, 400, detail);
89
+ const { error } = r;
90
+ assert.ok(error, detail);
91
+ assert.strictEqual(error.code, -32602, detail);
92
+ assert.strictEqual(error.message, INVALID_PARAMS_MESSAGE, detail);
93
+ const { data } = error;
94
+ if (data === undefined)
95
+ return;
96
+ assert.ok(typeof data === 'object' && data !== null && Object.keys(data).join() === 'issues', `${detail} — data beyond the DEV-only issues`);
97
+ const { issues } = data;
98
+ assert.ok(Array.isArray(issues), `${detail} — DEV-only issues that are no array`);
99
+ for (const issue of issues) {
100
+ assert.ok(typeof issue === 'object' &&
101
+ issue !== null &&
102
+ typeof issue.code === 'string' &&
103
+ typeof issue.message === 'string', `${detail} — a DEV-only issue that is no Zod issue`);
104
+ }
105
+ };
106
+ /**
107
+ * Assert `r` is the IDOR mask: 404 with reason `cell_not_found` — the
108
+ * well-typed control beside a schema refusal of the same call.
109
+ */
110
+ const assert_cell_not_found = (r, label) => {
111
+ const detail = `${label}: got ${r.status} ${JSON.stringify(r.error ?? r.result)}`;
112
+ assert.strictEqual(r.status, 404, detail);
113
+ assert.strictEqual(error_reason(r), 'cell_not_found', detail);
114
+ };
56
115
  export const describe_cell_crud_cross_tests = (options) => {
57
116
  const { setup_test, capabilities } = options;
58
117
  const rpc_path = options.rpc_path ?? SPINE_RPC_PATH;
@@ -327,5 +386,61 @@ export const describe_cell_crud_cross_tests = (options) => {
327
386
  }
328
387
  }
329
388
  });
389
+ describe('mistyped data', () => {
390
+ /** Count every cell, read as the keeper (an admin sees every row). */
391
+ const count_cells = async (t, keeper_headers) => {
392
+ const { cells } = expect_output(await cross_rpc_call(t, rpc_path, 'cell_list', { limit: CELL_LIST_LIMIT_MAX }, keeper_headers), CellListOutput);
393
+ assert.ok(cells.length < CELL_LIST_LIMIT_MAX, 'the count needs a page that holds every cell');
394
+ return cells.length;
395
+ };
396
+ test_if(capabilities.cell_crud, 'cell_create and cell_update refuse it as invalid_params before any cell check', async () => {
397
+ const fixture = await setup_test();
398
+ const writer = await fixture.create_account({ username: 'cell_data_writer' });
399
+ const stranger = await fixture.create_account({ username: 'cell_data_stranger' });
400
+ const t = fixture.fresh_transport();
401
+ const keeper_headers = fixture.create_session_headers();
402
+ const writer_headers = writer.create_session_headers();
403
+ // the keeper's private cell, hidden from the stranger; the keeper
404
+ // creates it so a backend that refuses a non-admin root runs the case
405
+ const hidden = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { kind: 'note', data: {} }, keeper_headers), CellCreateOutput).cell.id;
406
+ const missing = crypto.randomUUID();
407
+ const before = await count_cells(t, keeper_headers);
408
+ for (const data of MISTYPED_CELL_DATA) {
409
+ const shown = JSON.stringify(data);
410
+ assert_schema_refusal(await cross_rpc_call(t, rpc_path, 'cell_create', { data }, writer_headers), `cell_create data ${shown}`);
411
+ for (const [cell_id, headers, who] of [
412
+ [missing, writer_headers, 'a missing cell'],
413
+ [hidden, stranger.create_session_headers(), 'a hidden cell']
414
+ ]) {
415
+ assert_schema_refusal(await cross_rpc_call(t, rpc_path, 'cell_update', { cell_id, data }, headers), `cell_update of ${who} data ${shown}`);
416
+ }
417
+ }
418
+ // the control: well-typed, the same call meets the 404 mask, so the
419
+ // 400s above came before the cell check
420
+ assert_cell_not_found(await cross_rpc_call(t, rpc_path, 'cell_update', { cell_id: hidden, data: {} }, stranger.create_session_headers()), 'cell_update of a hidden cell data {}');
421
+ assert.strictEqual(await count_cells(t, keeper_headers), before, 'a refused write wrote a cell');
422
+ });
423
+ test_if(capabilities.cell_crud && capabilities.cell_relations, 'cell_clone refuses it in with_data_patch as invalid_params before any cell check', async () => {
424
+ const fixture = await setup_test();
425
+ const writer = await fixture.create_account({ username: 'cell_patch_writer' });
426
+ const stranger = await fixture.create_account({ username: 'cell_patch_stranger' });
427
+ const t = fixture.fresh_transport();
428
+ const keeper_headers = fixture.create_session_headers();
429
+ const hidden = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { kind: 'note', data: {} }, keeper_headers), CellCreateOutput).cell.id;
430
+ const missing = crypto.randomUUID();
431
+ const before = await count_cells(t, keeper_headers);
432
+ for (const with_data_patch of MISTYPED_CELL_DATA) {
433
+ for (const [source_id, headers, who] of [
434
+ [missing, writer.create_session_headers(), 'a missing cell'],
435
+ [hidden, stranger.create_session_headers(), 'a hidden cell']
436
+ ]) {
437
+ assert_schema_refusal(await cross_rpc_call(t, rpc_path, 'cell_clone', { source_id, with_data_patch }, headers), `cell_clone of ${who} with_data_patch ${JSON.stringify(with_data_patch)}`);
438
+ }
439
+ }
440
+ // the control: well-typed, the same clone meets the 404 mask
441
+ assert_cell_not_found(await cross_rpc_call(t, rpc_path, 'cell_clone', { source_id: hidden, with_data_patch: { label: 'patched' } }, stranger.create_session_headers()), 'cell_clone of a hidden cell with_data_patch {label}');
442
+ assert.strictEqual(await count_cells(t, keeper_headers), before, 'a refused clone wrote a cell');
443
+ });
444
+ });
330
445
  });
331
446
  };
@@ -3,6 +3,8 @@ import type { z } from 'zod';
3
3
  import type { FetchTransport } from '../transports/fetch_transport.js';
4
4
  /** Minimal JSON-RPC envelope shape the suites read off responses. */
5
5
  export interface RpcResult {
6
+ /** The HTTP status the reply carried. */
7
+ readonly status: number;
6
8
  readonly ok: boolean;
7
9
  readonly result?: unknown;
8
10
  readonly error?: {
@@ -22,7 +24,7 @@ export interface RpcResult {
22
24
  * @param method - the JSON-RPC method
23
25
  * @param params - the request params
24
26
  * @param headers - auth and other headers, merged over the JSON defaults
25
- * @returns the response's `ok`, `result` and `error`
27
+ * @returns the response's HTTP `status`, `ok`, `result` and `error`
26
28
  * @throws Error when the reply is not a JSON object — naming the method, the
27
29
  * HTTP status and the head of the body, so a proxy page or a crash reads
28
30
  * as what it is
@@ -1 +1 @@
1
- {"version":3,"file":"cross_rpc.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cross_rpc.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAgB9B,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAG7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAEvE,qEAAqE;AACrE,MAAM,WAAW,SAAS;IACzB,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CAC9F;AAKD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,cAAc,GAC1B,WAAW,cAAc,EACzB,MAAM,MAAM,EACZ,QAAQ,MAAM,EACd,QAAQ,OAAO,EACf,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAC7B,OAAO,CAAC,SAAS,CAmBnB,CAAC;AAEF,gFAAgF;AAChF,eAAO,MAAM,YAAY,GAAI,GAAG,SAAS,KAAG,OAG/B,CAAC;AAEd;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,CAAC,EAAE,GAAG,SAAS,EAAE,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAG,CAUrE,CAAC"}
1
+ {"version":3,"file":"cross_rpc.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cross_rpc.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAgB9B,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAG7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAEvE,qEAAqE;AACrE,MAAM,WAAW,SAAS;IACzB,yCAAyC;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CAC9F;AAKD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,cAAc,GAC1B,WAAW,cAAc,EACzB,MAAM,MAAM,EACZ,QAAQ,MAAM,EACd,QAAQ,OAAO,EACf,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAC7B,OAAO,CAAC,SAAS,CAmBnB,CAAC;AAEF,gFAAgF;AAChF,eAAO,MAAM,YAAY,GAAI,GAAG,SAAS,KAAG,OAG/B,CAAC;AAEd;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,CAAC,EAAE,GAAG,SAAS,EAAE,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAG,CAUrE,CAAC"}
@@ -26,7 +26,7 @@ const BODY_HEAD_LENGTH = 200;
26
26
  * @param method - the JSON-RPC method
27
27
  * @param params - the request params
28
28
  * @param headers - auth and other headers, merged over the JSON defaults
29
- * @returns the response's `ok`, `result` and `error`
29
+ * @returns the response's HTTP `status`, `ok`, `result` and `error`
30
30
  * @throws Error when the reply is not a JSON object — naming the method, the
31
31
  * HTTP status and the head of the body, so a proxy page or a crash reads
32
32
  * as what it is
@@ -48,7 +48,7 @@ export const cross_rpc_call = async (transport, path, method, params, headers) =
48
48
  JSON.stringify(text.slice(0, BODY_HEAD_LENGTH)));
49
49
  }
50
50
  const envelope = body;
51
- return { ok: res.ok, result: envelope.result, error: envelope.error };
51
+ return { status: res.status, ok: res.ok, result: envelope.result, error: envelope.error };
52
52
  };
53
53
  /** Pull `error.data.reason` off an RPC error envelope (undefined if absent). */
54
54
  export const error_reason = (r) => r.error && typeof r.error.data === 'object' && r.error.data !== null
@@ -34,14 +34,14 @@ export declare const action_rate_limit_env: (options: ActionRateLimitEnvOptions)
34
34
  /**
35
35
  * Capabilities shared by TS-family backends — only what the family
36
36
  * guarantees: the declared spine surface's mounts and the behavior of its
37
- * reference runtime, Node. Carries no `MountFlag`; each backend config states
38
- * those itself (`FamilyDefaultCapabilities`).
37
+ * reference runtime, Node. Carries no `MountFlag` and no `testing_backdoors`;
38
+ * each backend config states those itself (`FamilyDefaultCapabilities`).
39
39
  */
40
40
  export declare const ts_default_capabilities: FamilyDefaultCapabilities;
41
41
  /**
42
42
  * Capabilities for the Rust family — only what the family guarantees, as with
43
43
  * `ts_default_capabilities`: the declared spine surface's mounts and the
44
- * behavior of hyper. Carries no `MountFlag`.
44
+ * behavior of hyper. Carries no `MountFlag` and no `testing_backdoors`.
45
45
  */
46
46
  export declare const rust_default_capabilities: FamilyDefaultCapabilities;
47
47
  export interface MakeDefaultTsBackendConfigOptions {
@@ -57,8 +57,9 @@ export interface MakeDefaultTsBackendConfigOptions {
57
57
  readonly extra_env?: Readonly<Record<string, string>>;
58
58
  /**
59
59
  * Every capability, stated: spread `ts_default_capabilities` and add each
60
- * `MountFlag`. Required, so a mount flag left out is a type error rather
61
- * than a suite that silently skips.
60
+ * `MountFlag` and the `testing_backdoors` list. Required, so a mount flag or
61
+ * the backdoor list left out is a type error rather than a suite that
62
+ * silently skips.
62
63
  */
63
64
  readonly capabilities: BackendCapabilities;
64
65
  /** Pre-computed paths; defaults to `build_test_backend_paths(name)`. */
@@ -113,8 +114,9 @@ export interface MakeDefaultRustBackendConfigOptions {
113
114
  readonly extra_env?: Readonly<Record<string, string>>;
114
115
  /**
115
116
  * Every capability, stated: spread `rust_default_capabilities` and add each
116
- * `MountFlag`. Required, so a mount flag left out is a type error rather
117
- * than a suite that silently skips.
117
+ * `MountFlag` and the `testing_backdoors` list. Required, so a mount flag or
118
+ * the backdoor list left out is a type error rather than a suite that
119
+ * silently skips.
118
120
  */
119
121
  readonly capabilities: BackendCapabilities;
120
122
  /** Pre-computed paths; defaults to `build_test_backend_paths(name)`. */
@@ -1 +1 @@
1
- {"version":3,"file":"default_backend_configs.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/default_backend_configs.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"default_backend_configs.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/default_backend_configs.ts"],"names":[],"mappings":"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"}
@@ -81,8 +81,8 @@ export const action_rate_limit_env = (options) => ({
81
81
  /**
82
82
  * Capabilities shared by TS-family backends — only what the family
83
83
  * guarantees: the declared spine surface's mounts and the behavior of its
84
- * reference runtime, Node. Carries no `MountFlag`; each backend config states
85
- * those itself (`FamilyDefaultCapabilities`).
84
+ * reference runtime, Node. Carries no `MountFlag` and no `testing_backdoors`;
85
+ * each backend config states those itself (`FamilyDefaultCapabilities`).
86
86
  */
87
87
  export const ts_default_capabilities = Object.freeze({
88
88
  // The account-lifecycle verbs are on the declared surface
@@ -104,7 +104,7 @@ export const ts_default_capabilities = Object.freeze({
104
104
  /**
105
105
  * Capabilities for the Rust family — only what the family guarantees, as with
106
106
  * `ts_default_capabilities`: the declared spine surface's mounts and the
107
- * behavior of hyper. Carries no `MountFlag`.
107
+ * behavior of hyper. Carries no `MountFlag` and no `testing_backdoors`.
108
108
  */
109
109
  export const rust_default_capabilities = Object.freeze({
110
110
  // The account-lifecycle verbs are in the spine's admin action set, on the
@@ -14,6 +14,15 @@ export interface RpcAdversarialCrossTestOptions extends RpcPathCrossSuiteOptions
14
14
  readonly live_rpc_endpoints?: ReadonlyArray<RpcEndpointSpec>;
15
15
  /** Methods to leave out, beyond the protocol actions always skipped. */
16
16
  readonly skip_methods?: ReadonlyArray<string>;
17
+ /**
18
+ * Valid params per method (method name → params) to build its params cases
19
+ * from, in place of the generated base — for an input whose valid shape
20
+ * the generator can't reach. Each must pass the method's input schema; one
21
+ * that doesn't throws at collection, naming the method. The cases are sent
22
+ * as a single-actor principal, so an override omits `acting`.
23
+ * `describe_spine_cross_suites` passes its `rpc_input_overrides` here.
24
+ */
25
+ readonly input_overrides?: ReadonlyMap<string, Record<string, unknown>>;
17
26
  /**
18
27
  * Generated params cases a backend is known to answer otherwise, each run
19
28
  * as its own `xfail_until` test; the method's other cases keep their test.
@@ -1 +1 @@
1
- {"version":3,"file":"rpc_adversarial.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/rpc_adversarial.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAyE9B,OAAO,EAGN,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,MAAM,uBAAuB,CAAC;AAoB/B,OAAO,KAAK,EAAE,wBAAwB,EAAe,MAAM,YAAY,CAAC;AAKxE,0DAA0D;AAC1D,MAAM,WAAW,8BAA+B,SAAQ,wBAAwB;IAC/E,qGAAqG;IACrG,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC;;;;;OAKG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAC7D,wEAAwE;IACxE,QAAQ,CAAC,YAAY,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9C;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;CACtD;AAED,gFAAgF;AAChF,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,kFAAkF;IAClF,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACvC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB;AAmGD;;;;GAIG;AACH,eAAO,MAAM,oCAAoC,GAChD,SAAS,8BAA8B,KACrC,IA8JF,CAAC"}
1
+ {"version":3,"file":"rpc_adversarial.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/rpc_adversarial.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA6E9B,OAAO,EAGN,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,MAAM,uBAAuB,CAAC;AAoB/B,OAAO,KAAK,EAAE,wBAAwB,EAAe,MAAM,YAAY,CAAC;AAKxE,0DAA0D;AAC1D,MAAM,WAAW,8BAA+B,SAAQ,wBAAwB;IAC/E,qGAAqG;IACrG,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC;;;;;OAKG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAC7D,wEAAwE;IACxE,QAAQ,CAAC,YAAY,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9C;;;;;;;OAOG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACxE;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;CACtD;AAED,gFAAgF;AAChF,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,kFAAkF;IAClF,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACvC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB;AAmGD;;;;GAIG;AACH,eAAO,MAAM,oCAAoC,GAChD,SAAS,8BAA8B,KACrC,IAqKF,CAAC"}
@@ -30,8 +30,10 @@ import '../assert_dev_env.js';
30
30
  * - **Params** — for each declared method (and each live-mounted one, below),
31
31
  * `generate_rpc_params_test_cases`
32
32
  * (the in-process generator minus its structural cases), each sent as the
33
- * method's authorized principal and owing 400 `invalid_params` with no
34
- * `data.reason`. One test per method, reporting every case that answers
33
+ * method's authorized principal and owing 400 `invalid_params` with the
34
+ * `data.reason` the broken field declares (`invalid_reason` in its
35
+ * `.meta()`, read by `expected_invalid_reason`) — none for an undeclared
36
+ * field or a whole-body case. One test per method, reporting every case that answers
35
37
  * otherwise; `xfail_params` runs named cases of a method as their own
36
38
  * `xfail_until` tests, for a known divergence on one backend.
37
39
  *
@@ -42,7 +44,9 @@ import '../assert_dev_env.js';
42
44
  * role a declared method gates on (`default_cross_process_setup` seeds
43
45
  * `keeper` and `admin`; pass `extra_keeper_roles` for others), and it has a
44
46
  * single actor. No generated body carries a valid `acting` — the generator's
45
- * base fills only required and defaulted fields — and a malformed `acting` is
47
+ * base fills required and defaulted fields, and an optional one only where a
48
+ * refinement needs it (each alone first, so an `acting` declared ahead of an
49
+ * at-least-one or exactly-one-of pair stays out) — and a malformed `acting` is
46
50
  * read as omitted ahead of validation on both spines, so a single-actor
47
51
  * keeper resolves its actor and validation is what refuses. A keeper with a
48
52
  * second actor answers `actor_required` first; such a suite lists the
@@ -78,6 +82,7 @@ import { assert_rpc_envelope_refusal, create_rpc_envelope_cases, generate_rpc_pa
78
82
  import { resolve_conformance_principal } from './conformance_table.js';
79
83
  import { SPINE_RPC_PATH } from './spine_surface_constants.js';
80
84
  import { xfail_until } from './xfail.js';
85
+ import { expected_invalid_reason } from '../adversarial_input_cases.js';
81
86
  const PROTOCOL_METHODS = new Set(PROTOCOL_ACTION_METHODS);
82
87
  /** The keeper principal on each builtin credential channel. */
83
88
  const CHANNEL_PRINCIPALS = {
@@ -231,12 +236,14 @@ export const describe_rpc_adversarial_cross_tests = (options) => {
231
236
  const check_case = async (fixture, action, tc) => {
232
237
  const { status, body } = await send_as(fixture, action.spec.auth, 'authorized', rpc_path, create_rpc_post_init(action.spec.method, tc.body));
233
238
  const code = body?.error?.code;
239
+ const expected_reason = expected_invalid_reason(action.spec.input, tc);
234
240
  if (status === 400 &&
235
241
  code === JSONRPC_ERROR_CODES.invalid_params &&
236
- reason_of(body) === undefined) {
242
+ reason_of(body) === expected_reason) {
237
243
  return null;
238
244
  }
239
- return `[${tc.label}] params ${JSON.stringify(tc.body)} → ${status} ${JSON.stringify(body)}`;
245
+ const owed = expected_reason === undefined ? 'no reason' : `reason '${expected_reason}'`;
246
+ return `[${tc.label}] params ${JSON.stringify(tc.body)} → ${status} ${JSON.stringify(body)} (owed 400 invalid_params, ${owed})`;
240
247
  };
241
248
  // the declared methods, then the live-mounted ones the declared surface lacks
242
249
  const declared_actions = endpoint_spec.actions.filter((action) => !action.spec.method.startsWith(TESTING_METHOD_PREFIX) &&
@@ -247,7 +254,10 @@ export const describe_rpc_adversarial_cross_tests = (options) => {
247
254
  continue;
248
255
  if (skip.has(method))
249
256
  continue;
250
- const all_cases = generate_rpc_params_test_cases(input);
257
+ const all_cases = generate_rpc_params_test_cases(input, {
258
+ base: options.input_overrides?.get(method),
259
+ label: method
260
+ });
251
261
  if (all_cases.length === 0)
252
262
  continue;
253
263
  total += all_cases.length;
@@ -10,7 +10,7 @@ import '../assert_dev_env.js';
10
10
  * actions plus the consumer's own. This module builds that surface from the
11
11
  * hono-free route shapes and the consumer's action specs, so a consumer
12
12
  * supplies only what is its own. A server that mounts no signup router (a
13
- * single-keeper daemon such as mageguild's) is not this shape.
13
+ * single-keeper daemon) is not this shape.
14
14
  *
15
15
  * The route shapes come from `create_account_route_shapes` and friends, never
16
16
  * the live route factories, which statically pull `hono/cookie` and
@@ -1 +1 @@
1
- {"version":3,"file":"rust_spine_stub_backend_config.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/rust_spine_stub_backend_config.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAwC9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAazD,uGAAuG;AACvG,eAAO,MAAM,uBAAuB,oCAAoC,CAAC;AAEzE;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,6CAA6C,CAAC;AAE3F;;;;;GAKG;AACH,eAAO,MAAM,wCAAwC,6CAA6C,CAAC;AAiCnG,kGAAkG;AAClG,eAAO,MAAM,4BAA4B,OAAO,CAAC;AAEjD,8EAA8E;AAC9E,eAAO,MAAM,4BAA4B,eAAe,CAAC;AAEzD,0FAA0F;AAC1F,eAAO,MAAM,oCAAoC,sDACG,CAAC;AAErD,MAAM,WAAW,6BAA6B;IAC7C,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;;;OAMG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;OAOG;IACH,QAAQ,CAAC,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAC3C;;;;;;OAMG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC;;;;;;OAMG;IACH,QAAQ,CAAC,wBAAwB,CAAC,EAAE,OAAO,CAAC;IAC5C;;;;;OAKG;IACH,QAAQ,CAAC,8BAA8B,CAAC,EAAE,MAAM,CAAC;CACjD;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,8BAA8B,GAC1C,UAAS,6BAAkC,KACzC,aAmDF,CAAC"}
1
+ {"version":3,"file":"rust_spine_stub_backend_config.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/rust_spine_stub_backend_config.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAwC9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAczD,uGAAuG;AACvG,eAAO,MAAM,uBAAuB,oCAAoC,CAAC;AAEzE;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,6CAA6C,CAAC;AAE3F;;;;;GAKG;AACH,eAAO,MAAM,wCAAwC,6CAA6C,CAAC;AAoCnG,kGAAkG;AAClG,eAAO,MAAM,4BAA4B,OAAO,CAAC;AAEjD,8EAA8E;AAC9E,eAAO,MAAM,4BAA4B,eAAe,CAAC;AAEzD,0FAA0F;AAC1F,eAAO,MAAM,oCAAoC,sDACG,CAAC;AAErD,MAAM,WAAW,6BAA6B;IAC7C,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;;;OAMG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;OAOG;IACH,QAAQ,CAAC,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAC3C;;;;;;OAMG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC;;;;;;OAMG;IACH,QAAQ,CAAC,wBAAwB,CAAC,EAAE,OAAO,CAAC;IAC5C;;;;;OAKG;IACH,QAAQ,CAAC,8BAA8B,CAAC,EAAE,MAAM,CAAC;CACjD;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,8BAA8B,GAC1C,UAAS,6BAAkC,KACzC,aAmDF,CAAC"}
@@ -36,7 +36,7 @@ import '../assert_dev_env.js';
36
36
  */
37
37
  import { fileURLToPath } from 'node:url';
38
38
  import { build_test_backend_paths } from './build_test_backend_paths.js';
39
- import { LOGIN_RATE_LIMIT_ENABLED_ENV, SPINE_EXPECTED_SCHEMA_URL } from './spine_surface_constants.js';
39
+ import { LOGIN_RATE_LIMIT_ENABLED_ENV, SPINE_EXPECTED_SCHEMA_URL, TESTING_BACKDOOR_METHODS } from './spine_surface_constants.js';
40
40
  import { action_rate_limit_env, make_default_rust_backend_config, rust_default_capabilities } from './default_backend_configs.js';
41
41
  /** Env var naming the prebuilt `testing_spine_stub` binary. Required when `binary_path` is omitted. */
42
42
  export const RUST_SPINE_STUB_BIN_ENV = 'FUZ_TESTING_RUST_SPINE_STUB_BIN';
@@ -66,7 +66,9 @@ export const RUST_SPINE_STUB_EXPECTED_SCHEMA_PATH_ENV = 'FUZ_RUST_SPINE_STUB_EXP
66
66
  * serves one action registry on `/api/rpc` and `/api/ws`), `cell_gated_create`
67
67
  * (it mounts the `TestCellGatedCreateAuthorize` policy on its cell layer), and
68
68
  * `ws_role_gated` (it mounts the role-gated WS endpoint at
69
- * `SPINE_ROLE_GATED_WS_PATH`, sharing its connection registry). Named
69
+ * `SPINE_ROLE_GATED_WS_PATH`, sharing its connection registry), and every
70
+ * `TESTING_BACKDOOR_METHODS` entry as `testing_backdoors` (it mounts them all,
71
+ * on one registry for both transports). Named
70
72
  * (not inline) so every spine preset is greppable, mirroring
71
73
  * `ts_spine_capabilities`.
72
74
  */
@@ -82,7 +84,8 @@ const rust_spine_stub_capabilities = Object.freeze({
82
84
  ready: true,
83
85
  ws_account_actions: true,
84
86
  cell_gated_create: true,
85
- ws_role_gated: true
87
+ ws_role_gated: true,
88
+ testing_backdoors: TESTING_BACKDOOR_METHODS
86
89
  });
87
90
  /** Default listening port — slots beside zzz's 1175/1176; matches the binary's `DEFAULT_PORT`. */
88
91
  export const RUST_SPINE_STUB_DEFAULT_PORT = 1177;
@@ -229,7 +229,7 @@ export interface CapabilityGatedCrossSuiteOptions extends CrossSuiteOptions {
229
229
  * `CrossSuiteOptions` plus the RPC endpoint path the suite dispatches
230
230
  * against — the shape every JSON-RPC-driven imperative suite takes (cell
231
231
  * verbs, origin, body-size, actor lookup, account lifecycle, app
232
- * settings, testing backdoor). Suites alias this under a self-documenting
232
+ * settings). Suites alias this under a self-documenting
233
233
  * per-suite name (e.g. `OriginCrossTestOptions = RpcPathCrossSuiteOptions`).
234
234
  */
235
235
  export interface RpcPathCrossSuiteOptions extends CrossSuiteOptions {
@@ -241,7 +241,7 @@ export interface RpcPathCrossSuiteOptions extends CrossSuiteOptions {
241
241
  * of `RpcPathCrossSuiteOptions` and `CapabilityGatedCrossSuiteOptions`, and
242
242
  * the most common option shape among the imperative cross suites (every
243
243
  * `cell_*` suite, `query_shape`, `token_scope_surface`, `fact_serving`,
244
- * `account_lifecycle`).
244
+ * `account_lifecycle`, `testing_backdoor`).
245
245
  */
246
246
  export interface RpcPathCapabilityGatedCrossSuiteOptions extends RpcPathCrossSuiteOptions, CapabilityGatedCrossSuiteOptions {
247
247
  }