@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
@@ -32,7 +32,7 @@ time (never runtime), where a throwing guard would break `vite build`.
32
32
  - `stub_handler()` — fresh `Response('stub')`.
33
33
  - `stub_mw` — pass-through middleware (`async (_c, next) => next()`).
34
34
  - `create_stub_app_deps()` — factory: fresh `AppDeps` with no-op keyring/password/`delete_file`, a `read_secure_file` that throws ENOENT (the no-token-file state), a `create_noop_stub` DB, silent `Logger`, no-op `audit`, an empty `connection_closer`.
35
- - `create_test_audit_emitter()` — no-op `AuditEmitter`; `emit` / `emit_role_grant_target` no-op, `emit_pool` resolves immediately, `notify` no-op, `add_listener` throws (use `create_recording_audit_emitter` for a listener-accepting emitter), `listener_count` returns 0, `drain_inflight` resolves immediately.
35
+ - `create_test_audit_emitter()` — no-op `AuditEmitter`; `emit` / `emit_role_grant_target` no-op, `emit_pool` resolves immediately, `notify` no-op, `add_listener` throws (use `create_recording_audit_emitter` for a listener-accepting emitter), `listener_count` returns 0, `drain_inflight` resolves immediately, `is_tracking_inflight` returns `false`.
36
36
  - `create_stub_audit_sse()` — no-op `AuditLogSse` for surface-test wiring without booting real SSE. `on_audit_event` no-op (nothing is broadcast); `required_role` is `admin`; `registry` is a fresh `SubscriberRegistry` (live `.count` / `.close_*` for registry-state tests, isolated per call). For real SSE plumbing build via `create_audit_log_sse` against `create_test_app`.
37
37
  - `create_stub_api_middleware({include_daemon_token?, origin_carve_outs?})` — stub `MiddlewareSpec[]` matching `create_auth_middleware_specs`'s output (origin/session/request_context/bearer_auth, optional daemon_token) for surface generation without booting real auth — the same names, paths, order, and declared `errors` (`origin` declares a 403; `bearer_auth` and `daemon_token` declare none, since both soft-fail through `next()`), pinned against the real factory by `auth/middleware.mounted.test.ts`. The surface merges middleware errors into every route under the path, so a stub declaring more would widen the test surface with statuses the live server never returns. Each spec gets its own pass-through handler (not the shared `stub_mw`), so `assert_middleware_stack_mounted` tells the layers apart. See `auth/CLAUDE.md` §Middleware for the real stack.
38
38
  - `create_stub_app_backend(options?)` — stub `AppBackend` with no database: `create_stub_app_deps()` (or `options.deps`), no migration results, a `close` that does nothing (or `options.close`). For assembling a server in tests that never reach the database.
@@ -474,11 +474,15 @@ Support functions:
474
474
 
475
475
  Walks Zod schemas to generate valid values for adversarial/round-trip tests.
476
476
 
477
- - `detect_format(field_schema)` — reads `format` / `pattern` from the JSON Schema representation.
478
- - `generate_valid_value(field, field_schema)` — base-type switch producing a valid sample (UUIDs → nil UUID, strings → `'xxxxxxxxxx'`, numbers → `1`, objects → recurse, enums → first entry, etc.). For branded-string refinements, walks a fallback chain synthesized from the `pattern` string the JSON Schema representation exposes: fixed-length hex (`^[0-9a-f]{N}$` — blake3 / sha256 / md5 digests; `0`.repeat(N)), prefix-lengthed slug (`^<prefix>_[A-Za-z0-9_-]{N}$` — `ApiTokenId`-style ids; `<prefix>_` + `x`.repeat(N)), absolute path prefix, URL prefix. First candidate that `safeParse` accepts is used.
477
+ - `to_field_json_schema(field_schema)` — the field's JSON Schema rendered with `io: 'input'`, so a `.transform()` or pipe field (`Username`) yields its input-side bounds and pattern instead of throwing; a `.nullable()` / `.nullish()` field's one non-null `anyOf` branch is returned; `null` for a type JSON Schema can't represent. A pipe yields its input stage only, and a `.refine()` never appears (a code-point length bound generates nothing). Every JSON Schema read in the generators goes through it.
478
+ - `detect_format(field_schema)` — `detect_json_schema_format` over `to_field_json_schema`: the `format`, `'pattern'` for a regex, or `null`.
479
+ - `generate_valid_value(field, field_schema)` — base-type switch producing a valid sample (UUIDs → nil UUID, strings → `'xxxxxxxxxx'`, numbers → `1`, objects → `generate_valid_object` against the field schema, so a nested refinement is satisfied too, enums → first entry, etc.). For branded-string refinements, walks a fallback chain synthesized from the `pattern` string the JSON Schema representation exposes: fixed-length hex (`^[0-9a-f]{N}$` — blake3 / sha256 / md5 digests; `0`.repeat(N)), prefix-lengthed slug (`^<prefix>_[A-Za-z0-9_-]{N}$` — `ApiTokenId`-style ids; `<prefix>_` + `x`.repeat(N)), absolute path prefix, URL prefix, two `/`-joined segments (an `owner/name` slug). First candidate that `safeParse` accepts is used.
480
+ - `generate_valid_string_param(field, field_schema)` — a string a URL-sourced field (route param, query value) accepts: the nil UUID for a UUID field, else the first of `test_value`, the generated valid value as a string, `1`, `0`, `true`, `false`, the field's JSON Schema `default` or lower bound as a string (a preprocessed integer whose minimum is above one) and a synthesized string the field accepts; `null` when none does. The params and query case generators build their base from it.
479
481
  - `resolve_valid_path(path, params_schema?)` — swaps `:param` for a value the params schema accepts. Prefers `test_<name>` wherever the field schema admits it (the param name keeps failure messages readable); otherwise a UUID param takes the nil UUID and anything else is synthesized from the schema, percent-encoded. A synthesized value containing `/` is refused (it would route elsewhere) and falls back to `test_<name>`.
480
482
  - `to_route_content_type(route)` — the `Content-Type` a well-formed request to the route sends: `application/json`, or `application/octet-stream` for a `raw_body` route. Every `POST` route refuses anything else with 415 before its other gates, so every probe in the harnesses (auth, params and query fuzz, 404, data exposure, round trip) sends it, body or not.
481
- - `generate_valid_body(input_schema) => Record<string, unknown> | undefined` — builds a body satisfying the input schema. Throws with Zod `issues` if the generated body fails validation — surfaces broken generation logic with a descriptive error rather than a confusing 400 downstream.
483
+ - `generate_valid_object(object_schema, schema) => {value, result}` — required and defaulted fields; when `schema` (refinements included) refuses those, each optional field alone in declaration order, the first that parses; failing that, the optional fields added cumulatively until it parses. Covers a `.refine()` over optional fields (at least one of, exactly one of, both of) at any depth, and, where one optional field suffices, keeps the others (an `acting` declared first) out. Returns the last `safeParse` result and leaves the throw to the caller. `format_zod_issues(error)` renders the issues for those errors.
484
+ - `generate_valid_object_with_field(object_schema, schema, base, field)` — a valid object carrying `field`, built from the valid `base`: `base` itself when it carries the field; else the field joins `base`, with the optional fields `base` omits filled as `generate_valid_object` fills them (a pair rule: `scope_kind` needs `scope_id`); failing that, the field replaces `base`'s optional fields, refilled around it (exactly one of: adding `slug` drops `id`). `null` when no object the generator builds carries the field. The body a single-fault case on a field the base omits changes.
485
+ - `generate_valid_body(input_schema) => Record<string, unknown> | undefined` — builds a body satisfying the input schema (`generate_valid_object`). Throws with Zod `issues` if the generated body fails validation — surfaces broken generation logic with a descriptive error rather than a confusing 400 downstream.
482
486
 
483
487
  ### `integration_helpers.ts` — route lookup + body checks
484
488
 
@@ -534,9 +538,12 @@ All cases expect 400 with one of `ERROR_INVALID_REQUEST_BODY` /
534
538
  The generators live in the hono-free `adversarial_input_cases.ts`, so the
535
539
  cross-process suites reuse them:
536
540
 
537
- - `generate_input_test_cases(input_schema)` → `InputTestCase[]` — whole-body structural (non-object, extra key when `strictObject`), missing required fields, one wrong-type per field, null for required non-nullable, one format violation per constrained field, numeric/array/string boundary cases via JSON Schema introspection.
538
- - `generate_params_test_cases(params_schema)` → `ParamsTestCase[]` — format violations only (unconstrained string params accept anything).
539
- - `generate_query_test_cases(query_schema)` → `QueryTestCase[]` — missing required + format violations.
541
+ - `generate_input_test_cases(input_schema, options?)` → `InputTestCase[]` — whole-body structural (non-object, extra key when `strictObject`), missing required fields, one wrong-type per field, null for required non-nullable, one format violation per constrained field, and boundary cases read from the field's input-side JSON Schema (`to_field_json_schema` — through a transform or pipe, and under a nullable's `anyOf`): the empty string, one under `minLength` (above one; a prefix of the valid value) and one over `maxLength`; below `minimum`, above `maximum`, at an exclusive bound, zero and negative for a positive-only field, a non-integer for an integer field and a value off a `multipleOf` grid (each half a step from the valid value, inside the range); the empty array, one under `minItems` (above one; the valid value's own items) and one over `maxItems`. A per-field case is emitted only when the field refuses its value (a `z.coerce` field given a wrong type, a pattern admitting the violation string, a preprocess that repairs the value: dropped), so no generated case is accidentally valid. Each per-field case is single-fault — valid but for the field it breaks: it changes one field of a valid base (`generate_valid_object`'s, or `options.base` (`InputTestCaseOptions`) for an input whose valid shape the generator can't reach), or, for a field that base omits, of the body `generate_valid_object_with_field` builds around it, so a case can't also break an object-level rule. A field no valid body carries keeps its wrong-type and null cases on the base and gets no format or boundary cases. Either base failing the schema throws, naming `options.label` (the method or route) when given. A per-field case names the field it breaks (`InputTestCase.field`; absent on the whole-body cases).
542
+ - `expected_invalid_reason(input_schema, test_case)` → `string | undefined` — the `data.reason` an RPC refusal of the case owes: the broken field's `invalid_reason` (`schema_meta.ts`), resolved by the same `resolve_invalid_reason` the dispatcher resolves through, or none for an undeclared field or a whole-body case.
543
+ - `generate_params_test_cases(params_schema)` → `ParamsTestCase[]` — format violations only (unconstrained string params accept anything), each one the field refuses.
544
+ - `generate_query_test_cases(query_schema)` → `QueryTestCase[]` — missing required fields the field refuses to go without (a preprocessed field with a default accepts its absence) + format violations the field refuses.
545
+
546
+ Both build their base from `generate_valid_string_param` and check it against the whole schema, throwing when a field has no string it accepts or the record fails, so each case breaks one field of a valid record (a URL-sourced integer such as `db_routes`' `offset` / `limit` gets `'1'`, not a `test_value` it refuses).
540
547
 
541
548
  GET-with-input routes hit the RPC `?params=` query convention; invalid-JSON
542
549
  arrays there collapse to `ERROR_INVALID_REQUEST_BODY` (schema failure)
@@ -626,8 +633,8 @@ envelope — and a 401 to a non-public call (`assert_rpc_authenticated`): the
626
633
  call carried a credential its auth admits, so a 401 means it stopped working.
627
634
  A public method may still answer 401 (a wrong-password login), and so may a
628
635
  handler naming its own reason (below). A ≥500 carrying a declared code
629
- (`service_unavailable` 503, `timeout` 504, a consumer code mapped in
630
- `JSONRPC_ERROR_CODE_TO_HTTP_STATUS`) is a server condition and passes like a
636
+ (`service_unavailable` 503, `timeout` 504, a consumer code declared with
637
+ `register_jsonrpc_error_code`) is a server condition and passes like a
631
638
  4xx. The failure names the method, status, code and message; a non-JSON body
632
639
  is reported as text. Options: `{setup_test, surface_source, session_options,
633
640
  rpc_endpoints, skip_methods?, input_overrides?, success_fixtures?}`.
@@ -927,9 +934,10 @@ loses coverage.
927
934
 
928
935
  `rpc_success_fixtures`, `rpc_skip_methods` and `rpc_input_overrides`
929
936
  forward to the RPC round-trip's `success_fixtures`, `skip_methods` and
930
- `input_overrides` (same options on `describe_standard_cross_process_tests`)
931
- — `rpc_skip_methods` is the consumer escape hatch for a method the generic
932
- round-trip can't drive in its test wiring.
937
+ `input_overrides` (same options on `describe_standard_cross_process_tests`;
938
+ `describe_spine_cross_suites` also forwards `rpc_input_overrides` to
939
+ `rpc_adversarial`) — `rpc_skip_methods` is the consumer escape hatch for a
940
+ method the generic round-trip can't drive in its test wiring.
933
941
 
934
942
  `StandardTestOptions` requires `create_route_specs` (for rate_limiting) and
935
943
  `rpc_endpoints` (for admin/audit_completeness/rpc_round_trip); the admin
@@ -1003,12 +1011,12 @@ Registry lookups:
1003
1011
  - correct auth passes (not 401/403)
1004
1012
  - GET unauthenticated for `side_effects: false` reads
1005
1013
  2. **RPC adversarial envelopes** — the fixed set `create_rpc_envelope_cases(endpoint, {skip_methods?})` builds, exercising the envelope parse, method lookup, and GET read restriction: non-JSON body → `parse_error`, wrong `jsonrpc` version, missing `jsonrpc` / `method` / `id`, batch array, non-object POST `params` → `invalid_request`, unknown method → `method_not_found`, GET missing `method`/`id` → `invalid_request`, GET invalid-JSON params → `invalid_params`, GET non-object params on **every** read (each of `RPC_NON_OBJECT_PARAMS`: a number, an array, a string, a boolean) → `invalid_params`, GET mutation method → `invalid_request`, plus three ordering cases sent with no credential (an anonymous GET mutation and an anonymous batch, both `invalid_request`, never 401; an anonymous GET with non-object params on a read requiring an identity, the 401 — params validation runs after authorization). Each case is sent by the app its method's auth admits (`select_auth_app`) and pins status, code, echoed `id`, and `data.reason` (exact where the dispatcher sets one, absent otherwise) via `assert_rpc_envelope_refusal`. The cases are data (`RpcEnvelopeCase`) in the hono-free `rpc_envelope_cases.ts`, shared with the cross-process `describe_rpc_adversarial_cross_tests`; `to_rpc_envelope_request` builds a case's request, and `rpc_get_side_effects_reason(method)` is the GET read restriction's reason.
1006
- 3. **RPC adversarial params** — `generate_rpc_params_test_cases(input)` (`rpc_envelope_cases.ts`): `generate_input_test_cases` minus its structural cases (a non-object `params` is refused at the envelope, not at params validation). Every case expects 400 `invalid_params`. Shared with the cross-process suite.
1014
+ 3. **RPC adversarial params** — `generate_rpc_params_test_cases(input, options?)` (`rpc_envelope_cases.ts`): `generate_input_test_cases` minus its structural cases (a non-object `params` is refused at the envelope, not at params validation), taking the same `{base?, label?}`. Every case expects 400 `invalid_params` with `data.reason` the broken field's declared `invalid_reason` (`expected_invalid_reason`), absent for an undeclared field. Shared with the cross-process suite. `input_overrides` (method name → valid params) seeds a method's cases from that base; an override its schema refuses throws at collection, naming the method.
1007
1015
 
1008
1016
  Skips silently when `surface.rpc_endpoints` is empty. Uses stub deps — no
1009
1017
  DB needed.
1010
1018
 
1011
- Options: `{build: () => AppSurfaceSpec, roles: Array<string>}`.
1019
+ Options: `{build: () => AppSurfaceSpec, roles: Array<string>, input_overrides?}`.
1012
1020
 
1013
1021
  **Opt-in bundles need their own per-bundle suite file.** Action bundles not
1014
1022
  folded into `create_standard_rpc_actions` (today `self_service_role_actions`,
@@ -1128,7 +1136,10 @@ file; the dual-spawn and limiter-enabled suites name their own files.
1128
1136
  `fact_serving_admin_hash`, `ready`,
1129
1137
  `peer_request`, `ws_account_actions`, `ws_role_gated`), and the family
1130
1138
  defaults (`ts_default_capabilities` / `rust_default_capabilities`, typed
1131
- `FamilyDefaultCapabilities`) omit them, with `capabilities` required on
1139
+ `FamilyDefaultCapabilities`) omit them and the `testing_backdoors` list (the
1140
+ `_testing_*` backdoors the backend mounts, from `TESTING_BACKDOOR_METHODS` —
1141
+ §Testing-backdoor declaration below; `[]` in-process, every one on fuz_app's
1142
+ spine presets), with `capabilities` required on
1132
1143
  `make_default_{ts,rust}_backend_config` — so a config that leaves one out
1133
1144
  fails typecheck naming it, rather than silently skipping its suites. The
1134
1145
  family defaults carry the declared surface's mounts (`account_lifecycle`,
@@ -1224,11 +1235,15 @@ the omitted suites.
1224
1235
  One call that runs every shipped single-backend cross suite against one
1225
1236
  spawned backend: `describe_spine_cross_suites(handle, {surface_source,
1226
1237
  session_options, rpc_endpoints, live_rpc_endpoints?, roles?, setup?, exclude?,
1227
- overrides?, rpc_success_fixtures?, rpc_skip_methods?, rpc_input_overrides?,
1238
+ exclude_cases?, overrides?, rpc_success_fixtures?, rpc_skip_methods?, rpc_input_overrides?,
1228
1239
  rpc_max_body_bytes?, ws_max_message_bytes?})`.
1229
1240
  `live_rpc_endpoints` is the backend's whole live mount; given, `rpc_adversarial`
1230
- also walks the methods it keeps off the declared surface. Without it a
1231
- consumer writes a file per suite, and a suite added here never reaches it.
1241
+ also walks the methods it keeps off the declared surface. `rpc_input_overrides`
1242
+ reaches the standard bundle's RPC round-trip and `rpc_adversarial`'s
1243
+ `input_overrides`, so one valid body per method serves both; a key naming a
1244
+ method on neither the declared surface nor `live_rpc_endpoints` throws at
1245
+ collection. Without it a consumer writes a file per suite, and a suite added
1246
+ here never reaches it.
1232
1247
 
1233
1248
  - **The registry.** `SUITE_NAMES` / `SuiteName` — the closed set of suites the
1234
1249
  call runs, each named by its module (`ws_round_trip` is
@@ -1270,13 +1285,51 @@ consumer writes a file per suite, and a suite added here never reaches it.
1270
1285
  takes it as `rpc_max_body_bytes`), the second `ws_round_trip`'s
1271
1286
  `max_message_bytes` — one value per limit for every suite probing it.
1272
1287
  - **`exclude`** — `{[suite]: reason}`; the reason is required and shows in the
1273
- run as a skipped test. An empty reason, an unknown suite name, or a suite
1274
- both excluded and overridden throws before anything registers.
1288
+ run as a skipped test. An empty reason, an unknown suite name, a suite
1289
+ both excluded and overridden, `testing_backdoor` (it checks the backdoor
1290
+ declaration the gating reads), or a suite already skipping itself for an
1291
+ undeclared backdoor throws before anything registers.
1292
+ - **Backdoors.** `capabilities.testing_backdoors` must declare
1293
+ `SPINE_SUITES_REQUIRED_BACKDOORS` (`_testing_reset`, every setup's per-test
1294
+ reset, and `_testing_drain_effects`, the audit-read barrier), and
1295
+ `_testing_put_fact` when `fact_serving` is declared; the call throws
1296
+ otherwise. A suite every case of which drives another backdoor
1297
+ (`SuiteEntry.backdoors`: `credential_deadline` →
1298
+ `_testing_shorten_credential_expiry`, `origin_conformance` →
1299
+ `_testing_origin_check`, `ws_outbound_overflow` →
1300
+ `_testing_flood_notifications`) skips itself on a backend not declaring it,
1301
+ as a skipped test beside the excludes whose reason names the backdoor; an
1302
+ exclude, override or `exclude_cases` entry for it throws as dead.
1303
+ `spine_suites.test.ts` holds the backdoors each suite module names — quoted
1304
+ `_testing_*` names, `testing_*_action_spec` references, and the
1305
+ `cross_put_fact` / `cross_drain_effects` seeders — to its entry's
1306
+ `backdoors`, the required pair, or `_testing_put_fact` under
1307
+ `capabilities.fact_serving`; a helper module the suite calls isn't read.
1308
+ - **`exclude_cases`** — `SuiteCaseExclusions`, `{[suite]: {[name path]:
1309
+ reason}}`: single tests of a suite that runs, each by its name path (the
1310
+ describe titles and the test name joined with `' > '` — what the reporter
1311
+ prints after the file and any `describe` wrapping the call; a full path, not
1312
+ a leaf name, so a title repeated under two describes names one test). One
1313
+ `beforeEach` on the calling suite (`register_spine_case_exclusions`) skips
1314
+ them at run time with `ctx.skip(reason)`, so the suite files stay as they
1315
+ are and each shows as skipped with its reason; it finds a test's suite
1316
+ from the top-level task ranges each suite registered (read through
1317
+ `TestRunner.getCurrentSuite()` at collection), so an entry matches only
1318
+ below its own suite. A reasonless entry, an empty suite map, or an entry for
1319
+ an unknown, excluded or self-skipping suite, or for `testing_backdoor`,
1320
+ throws at collection; an entry
1321
+ naming no test its suite collected fails the closing test
1322
+ (`check_spine_case_exclusions`, which reads the collected paths, so a
1323
+ filtered run still checks them) — the dead-name check, as the known-divergence
1324
+ ledger has.
1275
1325
  - **`overrides`** — `SuiteOverrides`, per suite a `Partial` of its own options
1276
1326
  (`SuiteOptionsMap`), shallow-merged over the derived ones: a role list, an
1277
1327
  `xfail`, a narrower capability set, `ws: undefined` to drop optional socket
1278
- cases (`session_cap` and `testing_backdoor` take no `capabilities`, so `ws` is
1279
- their only socket switch). An entry can instead be a function of the derived
1328
+ cases (`session_cap` and `testing_backdoor` gate no case on `capabilities.ws`,
1329
+ so `ws` is their only socket switch), `extra_methods` to fire a consumer's own
1330
+ daemon-token seams through `testing_backdoor` (whose
1331
+ `capabilities.testing_backdoors` an override can't change — the call throws
1332
+ unless the merge keeps the backend's list). An entry can instead be a function of the derived
1280
1333
  options returning the patch (`SuiteOverride`), for a change built on a
1281
1334
  derived value — an `xfail` added to the derived multi-actor setup — so the
1282
1335
  seeding isn't restated. After the merge, every option the suite's type
@@ -1827,12 +1880,14 @@ never sees them). Both parse every success response against the verb's Zod
1827
1880
  **output** schema, so a TS↔Rust envelope drift fails the suite — not just a
1828
1881
  payload-field drift. Call-site primitives (`cross_rpc_call` / `error_reason` /
1829
1882
  `expect_output`) live in `cross_backend/cross_rpc.ts`, shared by every
1830
- stateful cross suite (a reply that is no JSON object throws naming the method,
1883
+ stateful cross suite (`RpcResult` carries the HTTP `status` beside the
1884
+ envelope; a reply that is no JSON object throws naming the method,
1831
1885
  the HTTP status and the body's head), and every
1832
1886
  suite that reads audit rows goes through `cross_backend/audit_cross_helpers.ts`
1833
1887
  — `list_audit_events(fixture, params, rpc_path?)` drains
1834
- (`cross_drain_effects`, the `_testing_drain_effects` barrier) before it lists and
1835
- parses, so no reader skips the barrier, and parses every row's metadata
1888
+ (`cross_drain_effects`, the `_testing_drain_effects` barrier, refusing an answer
1889
+ without `tracked: true`) before it lists and parses, so no reader skips the
1890
+ barrier and every read checks the barrier is live, and parses every row's metadata
1836
1891
  (`assert_audit_event_metadata`, closed to undeclared keys when the fixture's
1837
1892
  setup asked), so every reader holds the rows it reads to their event's
1838
1893
  schema. A suite pinning a row's values uses `assert_audit_metadata_fields(fixture,
@@ -1851,9 +1906,9 @@ an audit row, an admin account listing, and an offer's `created_at` /
1851
1906
  imperative cross suite aliases: `CrossSuiteOptions` (`setup_test` alone),
1852
1907
  `CapabilityGatedCrossSuiteOptions` (+ `capabilities` — `ready`),
1853
1908
  `RpcPathCrossSuiteOptions` (+ `rpc_path?` — origin, body-size (which adds `base_url`), actor
1854
- lookup, app settings, testing backdoor), and the intersection
1909
+ lookup, app settings), and the intersection
1855
1910
  `RpcPathCapabilityGatedCrossSuiteOptions` (every `cell_*` suite, query-shape,
1856
- token-scope surface, fact-serving, account lifecycle). A suite takes the
1911
+ token-scope surface, fact-serving, account lifecycle, testing backdoor). A suite takes the
1857
1912
  capability-carrying variant only when it actually gates cases on a flag, so
1858
1913
  the option type states whether the bundle is read.
1859
1914
 
@@ -1868,7 +1923,15 @@ the option type states whether the bundle is read.
1868
1923
  `cell_list` pages honoring `limit` + `offset`, a page holding visible rows
1869
1924
  only and filling to `limit` past a newer run of rows the caller can't see —
1870
1925
  two owners' private cells, read by a non-owner and by anon — and an admin's
1871
- pages tiling every row).
1926
+ pages tiling every row; a `data` that is no `CellData` — a non-object, or a
1927
+ non-string `label` / `summary` — refused 400 `invalid_params` with the
1928
+ generic message and no reason (no `data` beyond a TS spine's DEV-only
1929
+ `issues`, an array of Zod issues) on `cell_create`, on `cell_update` of a
1930
+ missing and of a hidden cell, and, with `cell_relations`, on `cell_clone`'s
1931
+ `with_data_patch` — ahead of the 404 mask the same hidden-cell call answers
1932
+ when well-typed, with the keeper's cell count unchanged; those cases run as
1933
+ the keeper and plain accounts only, so a backend that refuses a non-admin
1934
+ root cell can keep them through `exclude_cases` while excluding the rest).
1872
1935
  - **`describe_cell_relations_cross_tests`** (gates on
1873
1936
  `capabilities.cell_relations`) — the verbs beyond CRUD: grant lifecycle
1874
1937
  (actor-shaped editor grant enables edit, manage-tier `cell_grant_list`,
@@ -2087,22 +2150,25 @@ are `src/test/auth/cell_crud_parity.db.test.ts`
2087
2150
  `create_testing_actions(deps, options)` factory returning the
2088
2151
  `_testing_reset` RPC action. Test binaries register it on their RPC
2089
2152
  endpoint; `default_cross_process_setup` fires it unconditionally per
2090
- test. Handler DELETEs every auth-namespace row (no keeper-preserve
2091
- filter), resets `app_settings` to production defaults, flips
2092
- `bootstrap_lock.bootstrapped = true`, inline-seeds a fresh keeper via
2093
- `create_test_account_with_credentials` (same primitive in-process uses,
2094
- keeping write semantics in parity), seeds any `extra_accounts` at the
2095
- same bootstrap-equivalent step (the only path for roles like
2096
- `ROLE_KEEPER` whose `grant_paths` is bootstrap-only), queues the refresh
2153
+ test. Handler first fires the consumer-supplied `reset_state(db)`
2154
+ callback for domain-state reset — before the auth wipe, because domain
2155
+ rows depend on auth rows (a domain table with a restricting FK to `actor`
2156
+ must empty before the actor delete) — then DELETEs every auth-namespace
2157
+ row (no keeper-preserve filter), resets `app_settings` to production
2158
+ defaults, flips `bootstrap_lock.bootstrapped = true`, inline-seeds a fresh
2159
+ keeper via `create_test_account_with_credentials` (same primitive
2160
+ in-process uses, keeping write semantics in parity), queues the refresh
2097
2161
  of `DaemonTokenState.keeper_account_id` to the new row as a post-commit
2098
- effect (so a reset that rolls back — a throwing `reset_state`, a failed
2099
- commit — leaves the cache on the keeper still in the database, and the
2100
- next reset can still authenticate), then fires the
2101
- consumer-supplied `reset_state(db)` callback for domain-state reset —
2102
- passed the **transactional** `Db` the auth wipe ran on, so DB-domain
2103
- consumers (e.g. fuz_forge truncating its cell / fact / file tables) reset
2104
- on the same connection rather than a separately-pooled one that would
2105
- deadlock against this open transaction under the in-memory drivers. Auth
2162
+ effect as soon as the keeper exists (so a reset that rolls back — a
2163
+ failing extra seed, a failed commit — leaves the cache on the keeper
2164
+ still in the database, and the next reset can still authenticate), then
2165
+ seeds any `extra_accounts` at the same bootstrap-equivalent step (the
2166
+ only path for roles like `ROLE_KEEPER` whose `grant_paths` is
2167
+ bootstrap-only). `reset_state` is passed the **transactional** `Db` the
2168
+ auth wipe then runs on, so DB-domain consumers (e.g. fuz_forge truncating
2169
+ its cell / fact / file tables) reset on the same connection rather than a
2170
+ separately-pooled one that would deadlock against this open transaction
2171
+ under the in-memory drivers. Auth
2106
2172
  gates on `credential_types: ['daemon_token']` — effectively keeper-only
2107
2173
  without forcing the `actor: 'required'` ⟺ `acting?: ActingActor`
2108
2174
  biconditional. No free-form runtime grant action exists — see the
@@ -2117,6 +2183,10 @@ are `src/test/auth/cell_crud_parity.db.test.ts`
2117
2183
  emitter must be built with `create_audit_emitter({track_inflight: true})`
2118
2184
  (the spine binary's `audit_factory` does — pinned by
2119
2185
  `cross_backend/testing_spine_server.audit.test.ts`), or the drain resolves at once.
2186
+ The action answers `{ok: true, tracked}`, `tracked` read from
2187
+ `audit.is_tracking_inflight()`, and `cross_drain_effects` refuses anything but
2188
+ `tracked: true` — so a binary built over an untracked emitter fails its first
2189
+ audit read rather than racing it. The Rust twin answers the same shape.
2120
2190
  The barrier is needed whatever the transport: HTTP on a binary running
2121
2191
  `await_pending_effects: true` settles a mutation's writes before
2122
2192
  responding, but WS replies before its effects flush, so a WS mutation's
@@ -2207,10 +2277,11 @@ cases are `src/test/auth/cell_ref_admission{,.fact_less}.db.test.ts`.
2207
2277
  ### Malformed-envelope and invalid-params parity — `cross_backend/rpc_adversarial.ts`
2208
2278
 
2209
2279
  `describe_rpc_adversarial_cross_tests({setup_test, surface_source, rpc_path?,
2210
- live_rpc_endpoints?, skip_methods?})` — the envelope and params groups of
2211
- `describe_rpc_attack_surface_tests`, sent over `fixture.transport` to a
2212
- spawned backend. The in-process groups run on stub apps, while the Rust spine
2213
- parses envelopes and params by hand and the action-manifest parity gate
2280
+ live_rpc_endpoints?, skip_methods?, input_overrides?, xfail_params?})` — the
2281
+ envelope and params groups of `describe_rpc_attack_surface_tests`, sent over
2282
+ `fixture.transport` to a spawned backend. The in-process groups run on stub
2283
+ apps, while the Rust spine parses envelopes and params by hand and the
2284
+ action-manifest parity gate
2214
2285
  compares no param schemas, so this is what holds the spines to one answer.
2215
2286
 
2216
2287
  - **envelopes** — every `create_rpc_envelope_cases` case, one test each, on a
@@ -2218,8 +2289,12 @@ compares no param schemas, so this is what holds the spines to one answer.
2218
2289
  guard test that the endpoint has both a read and an authenticated mutation to
2219
2290
  carry them.
2220
2291
  - **params** — one test per declared method, sending every
2221
- `generate_rpc_params_test_cases` case and failing with the list of cases not
2222
- answered 400 `invalid_params` with no `data.reason`. `xfail_params`
2292
+ `generate_rpc_params_test_cases` case (seeded from the method's
2293
+ `input_overrides` entry when it has one — the cases go as a single-actor
2294
+ principal, so an override omits `acting`) and failing with the list of
2295
+ cases not answered 400 `invalid_params` with the broken field's declared
2296
+ `invalid_reason` as `data.reason` (`expected_invalid_reason`; none for an
2297
+ undeclared field or a whole-body case). `xfail_params`
2223
2298
  (`RpcParamsXfail` — `{method, labels, tracking_id, reason}`) moves the named
2224
2299
  cases of a method into one `xfail_until` test each, for a known divergence on
2225
2300
  one backend; the method's other cases keep their test, and an entry naming no
@@ -2496,12 +2571,33 @@ payload reproduces it byte for byte and it authenticates, and a leading-zero
2496
2571
  `0N` authenticates (the `u64` bound is on the value, not the digit count).
2497
2572
  Ungated; cross-process only.
2498
2573
 
2499
- ### Testing-backdoor credential gate — `cross_backend/testing_backdoor.ts`
2500
-
2501
- `describe_testing_backdoor_cross_tests({setup_test, rpc_path?, ws?})` —
2502
- the negative-credential parity suite for the `_testing_*` backdoor actions.
2503
- For each `_testing_*` action (the privileged writes, the audit barrier, the
2504
- connection flood, and the introspection reads) it fires three principals over real HTTP: **anonymous** →
2574
+ ### Testing-backdoor declaration + credential gate — `cross_backend/testing_backdoor.ts`
2575
+
2576
+ `describe_testing_backdoor_cross_tests({setup_test, capabilities, rpc_path?,
2577
+ ws?, extra_methods?})` — the backend's backdoor declaration, then the
2578
+ negative-credential parity suite for the `_testing_*` backdoor actions.
2579
+
2580
+ **Declaration.** `capabilities.testing_backdoors` lists the backdoors the
2581
+ backend mounts, from `TESTING_BACKDOOR_METHODS` (`spine_surface_constants.ts`,
2582
+ the one list; `spine_surface_constants.testing_backdoors.test.ts` holds it to
2583
+ the `_testing_*` actions fuz_app's spine binary mounts). Each known backdoor
2584
+ the list leaves out is sent anonymously and must answer `method_not_found`
2585
+ (404): both spines look the method up before they authenticate (the TS
2586
+ dispatcher's lookup step, the Rust `perform_action`'s), so an unmounted method
2587
+ answers that whatever the credential, and a mounted one a 401. Each declared
2588
+ one gets the rows below, whose anonymous row fails as "declared but not
2589
+ mounted" on a `method_not_found`. So the declaration can't lie in either
2590
+ direction, and the suites gated on it skip only what the backend lacks. A
2591
+ declared name outside the known list, or a duplicate, throws at collection.
2592
+ The suite runs on every backend, one declaring only the required backdoors
2593
+ included — `describe_spine_cross_suites` refuses to exclude it, skip any of
2594
+ its cases, or hand it other `capabilities` through `overrides`.
2595
+
2596
+ **Credential gate.** For each declared backdoor (the privileged writes, the
2597
+ audit barrier, the connection flood, and the introspection reads), and each
2598
+ `extra_methods` entry (`TestingBackdoorProbe` `{method, params?}` — a
2599
+ consumer's own daemon-token seam, which must start with `_testing_` and not be
2600
+ a known backdoor), it fires three principals over real HTTP: **anonymous** →
2505
2601
  401 `unauthenticated`, **session** → 403 `forbidden` `credential_type_required`,
2506
2602
  **bearer** → 403 `forbidden` `credential_type_required` — and, when `ws`
2507
2603
  (`{base_url, ws_path, origin?}`) is passed, sends each on a socket opened with
@@ -2512,18 +2608,23 @@ on the real dispatcher (the spec-derived `describe_rpc_attack_surface_tests`
2512
2608
  never enumerates them because they're off the declared surface). Each method is
2513
2609
  sent with **valid** params so each case varies only the credential (validation
2514
2610
  runs after the gates — order is 401 → authz → 403 → 429 → 400); the handler never
2515
- runs. Cited property: `docs/security.md`
2516
- §Test Backdoor Actions. Cross-process only (the `_testing_*` actions are
2517
- mounted on the spawned binary, not the in-process app — like the ws/sse
2518
- suites); ungated, since every cross backend that uses
2519
- `default_cross_process_setup` mounts them. The complement is the
2611
+ runs. The known backdoors' params are a `Record<TestingBackdoorMethod, …>`, so a
2612
+ backdoor added to the list fails typecheck until it has them. Cited property:
2613
+ `docs/security.md` §Test Backdoor Actions. The credential rows run
2614
+ cross-process only (the `_testing_*` actions are mounted on the spawned binary,
2615
+ not the in-process app — like the ws/sse suites); in-process, an app mounting
2616
+ none declares none and gets the declaration rows alone, one per known backdoor
2617
+ — fuz_app's in-process leg is `cross_backend/testing_backdoor_parity.db.test.ts`,
2618
+ while its cross legs declare every backdoor, so they run no declaration row.
2619
+ The complement is the
2520
2620
  in-process spec-level gate test (`src/test/testing/testing_actions_auth.test.ts`,
2521
2621
  asserting each spec declares `credential_types: ['daemon_token']`) + the
2522
2622
  `assert_no_testing_methods` surface invariant. fuz_app's conformance table
2523
2623
  also covers the same gates, through the auth rows it generates over the live
2524
2624
  mount (`live_rpc_endpoints`), which the differential compares across spines;
2525
2625
  this suite stays for consumers whose backends mount the `_testing_*` actions
2526
- without a conformance table over their live mount.
2626
+ without a conformance table over their live mount, and for the declaration
2627
+ check.
2527
2628
 
2528
2629
  ### Identity-primitive parity — `cross_backend/identity_parity.ts`
2529
2630
 
@@ -18,6 +18,12 @@ import { ERROR_INVALID_REQUEST_BODY, ERROR_INVALID_JSON_BODY } from '../http/err
18
18
  export interface InputTestCase {
19
19
  label: string;
20
20
  body: unknown;
21
+ /**
22
+ * The one top-level field the case breaks; absent on a whole-body case (a
23
+ * non-object body, an extra unknown key). An RPC refusal of the case owes
24
+ * the field's declared `invalid_reason` (`expected_invalid_reason`).
25
+ */
26
+ field?: string;
21
27
  expected_error: typeof ERROR_INVALID_REQUEST_BODY | typeof ERROR_INVALID_JSON_BODY;
22
28
  }
23
29
  /** One adversarial params set for a route's params schema. */
@@ -30,6 +36,19 @@ export interface QueryTestCase {
30
36
  label: string;
31
37
  query: Record<string, string>;
32
38
  }
39
+ /** Options for `generate_input_test_cases`. */
40
+ export interface InputTestCaseOptions {
41
+ /**
42
+ * A valid body to build the cases from, in place of the generated one —
43
+ * for an input whose valid shape the generator can't reach, or one whose
44
+ * generated body the handler can't run. Each case is this body with one
45
+ * field broken, so it must pass the input schema itself: one that doesn't
46
+ * throws.
47
+ */
48
+ base?: Record<string, unknown>;
49
+ /** Names the input in error messages — the RPC method or route it belongs to. */
50
+ label?: string;
51
+ }
33
52
  /**
34
53
  * Generate adversarial test cases for a route's input schema.
35
54
  *
@@ -39,26 +58,67 @@ export interface QueryTestCase {
39
58
  * - One wrong-type value per field
40
59
  * - Null for required non-nullable fields
41
60
  * - One format violation per constrained field
61
+ * - String length (empty, one under `minLength`, one over `maxLength`), numeric
62
+ * (out of range, non-integer, off a `multipleOf` grid) and array-length
63
+ * (empty, one under `minItems`, one over `maxItems`) boundaries
64
+ *
65
+ * Format and boundaries come from the field's input-side JSON Schema
66
+ * (`to_field_json_schema`), so a field carrying a `.transform()` or pipe and a
67
+ * `.nullable()` / `.nullish()` field get them too. A bound only a `.refine()`
68
+ * enforces generates nothing — JSON Schema can't express it.
69
+ *
70
+ * Each per-field case is single-fault: valid except for the field it breaks.
71
+ * It changes one field of a valid base body — `options.base` when given, else
72
+ * one built by `generate_valid_object` — or, for a field that base omits, of
73
+ * the valid body `generate_valid_object_with_field` builds around it, so a case
74
+ * can't also break an object-level rule (adding `scope_kind` without its
75
+ * paired `scope_id`, or `slug` beside an exactly-one-of `id`). A field no valid
76
+ * body carries keeps its wrong-type and null cases on the base and gets no
77
+ * format or boundary cases, which would test no bound. A per-field case is
78
+ * emitted only when the field refuses its value, so no case is accidentally
79
+ * valid.
42
80
  *
43
- * @throws Error if the seed body built by `generate_valid_value` fails
44
- * `input_schema.safeParse` — surfaces broken generation logic with the
45
- * Zod issues path rather than producing nonsense adversarial cases.
81
+ * @param input_schema - the input schema to attack
82
+ * @param options - a base body to seed the cases, and a label for errors
83
+ * @throws Error if the base body fails `input_schema.safeParse` — a seeded
84
+ * base the schema refuses, or a generated one that surfaces broken
85
+ * generation logic — naming the issues rather than producing nonsense
86
+ * adversarial cases.
46
87
  */
47
- export declare const generate_input_test_cases: (input_schema: z.ZodType) => Array<InputTestCase>;
88
+ export declare const generate_input_test_cases: (input_schema: z.ZodType, options?: InputTestCaseOptions) => Array<InputTestCase>;
89
+ /**
90
+ * The `error.data.reason` an RPC refusal of `test_case` owes: the declared
91
+ * `invalid_reason` of the field it breaks, resolved as the dispatcher resolves
92
+ * a failure on that field (`resolve_invalid_reason`), or `undefined` — no
93
+ * reason — for an undeclared field or a whole-body case.
94
+ *
95
+ * @param input_schema - the input schema the case was generated from
96
+ * @param test_case - a case `generate_input_test_cases` produced from it
97
+ * @returns the owed reason, or `undefined` when the refusal carries none
98
+ */
99
+ export declare const expected_invalid_reason: (input_schema: z.ZodType, test_case: InputTestCase) => string | undefined;
48
100
  /**
49
101
  * Generate adversarial test cases for a route's params schema.
50
102
  *
51
103
  * Params are always strings from URL segments. Only generates cases for
52
104
  * format-constrained fields (uuid, pattern) since unconstrained string
53
- * params accept any string value.
105
+ * params accept any string value, and only a violation the field refuses.
106
+ * Each case breaks one field of a valid params record.
107
+ *
108
+ * @throws Error if no valid params record can be generated
54
109
  */
55
110
  export declare const generate_params_test_cases: (params_schema: z.ZodObject) => Array<ParamsTestCase>;
56
111
  /**
57
112
  * Generate adversarial test cases for a route's query schema.
58
113
  *
59
114
  * Query params are always strings from the URL. Generates cases for:
60
- * - Missing required fields
61
- * - Format violations on constrained fields (uuid, pattern)
115
+ * - Missing required fields the field refuses to go without (a preprocessed
116
+ * field with a default accepts its absence)
117
+ * - Format violations on constrained fields (uuid, pattern) the field refuses
118
+ *
119
+ * Each case breaks one field of a valid query record.
120
+ *
121
+ * @throws Error if no valid query record can be generated
62
122
  */
63
123
  export declare const generate_query_test_cases: (query_schema: z.ZodObject) => Array<QueryTestCase>;
64
124
  //# sourceMappingURL=adversarial_input_cases.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"adversarial_input_cases.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/adversarial_input_cases.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,OAAO,EAAE,0BAA0B,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AA8C/F,4EAA4E;AAC5E,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,cAAc,EAAE,OAAO,0BAA0B,GAAG,OAAO,uBAAuB,CAAC;CACnF;AAED,8DAA8D;AAC9D,MAAM,WAAW,cAAc;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/B;AAED,wDAAwD;AACxD,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAID;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,yBAAyB,GAAI,cAAc,CAAC,CAAC,OAAO,KAAG,KAAK,CAAC,aAAa,CAsMtF,CAAC;AAIF;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,GAAI,eAAe,CAAC,CAAC,SAAS,KAAG,KAAK,CAAC,cAAc,CA+B3F,CAAC;AAIF;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,GAAI,cAAc,CAAC,CAAC,SAAS,KAAG,KAAK,CAAC,aAAa,CA4CxF,CAAC"}
1
+ {"version":3,"file":"adversarial_input_cases.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/adversarial_input_cases.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,OAAO,EAAE,0BAA0B,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAmE/F,4EAA4E;AAC5E,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,OAAO,0BAA0B,GAAG,OAAO,uBAAuB,CAAC;CACnF;AAED,8DAA8D;AAC9D,MAAM,WAAW,cAAc;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/B;AAED,wDAAwD;AACxD,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAID,+CAA+C;AAC/C,MAAM,WAAW,oBAAoB;IACpC;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,iFAAiF;IACjF,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,eAAO,MAAM,yBAAyB,GACrC,cAAc,CAAC,CAAC,OAAO,EACvB,UAAU,oBAAoB,KAC5B,KAAK,CAAC,aAAa,CAmOrB,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB,GACnC,cAAc,CAAC,CAAC,OAAO,EACvB,WAAW,aAAa,KACtB,MAAM,GAAG,SAG+C,CAAC;AAkC5D;;;;;;;;;GASG;AACH,eAAO,MAAM,0BAA0B,GAAI,eAAe,CAAC,CAAC,SAAS,KAAG,KAAK,CAAC,cAAc,CAoB3F,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,yBAAyB,GAAI,cAAc,CAAC,CAAC,SAAS,KAAG,KAAK,CAAC,aAAa,CAkCxF,CAAC"}