@fuzdev/fuz_app 0.148.0 → 0.150.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.
- package/dist/actions/CLAUDE.md +11 -9
- package/dist/actions/action_bridge.d.ts +7 -0
- package/dist/actions/action_bridge.d.ts.map +1 -1
- package/dist/actions/action_bridge.js +10 -0
- package/dist/actions/action_event.d.ts.map +1 -1
- package/dist/actions/action_event.js +14 -1
- package/dist/actions/action_event_types.d.ts +1 -1
- package/dist/actions/action_event_types.d.ts.map +1 -1
- package/dist/actions/perform_action.d.ts +17 -1
- package/dist/actions/perform_action.d.ts.map +1 -1
- package/dist/actions/perform_action.js +30 -7
- package/dist/actions/register_action_ws.d.ts +8 -2
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +24 -4
- package/dist/actions/transports_ws_auth_guard.d.ts +6 -4
- package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
- package/dist/actions/transports_ws_auth_guard.js +9 -6
- package/dist/actions/ws_endpoint_spec.d.ts +1 -1
- package/dist/auth/CLAUDE.md +72 -33
- package/dist/auth/account_action_specs.d.ts +12 -0
- package/dist/auth/account_action_specs.d.ts.map +1 -1
- package/dist/auth/account_action_specs.js +16 -2
- package/dist/auth/account_actions.d.ts.map +1 -1
- package/dist/auth/account_actions.js +7 -4
- package/dist/auth/actor_lookup_queries.d.ts +3 -2
- package/dist/auth/actor_lookup_queries.d.ts.map +1 -1
- package/dist/auth/actor_lookup_queries.js +6 -3
- package/dist/auth/actor_search_queries.d.ts +5 -2
- package/dist/auth/actor_search_queries.d.ts.map +1 -1
- package/dist/auth/actor_search_queries.js +11 -4
- package/dist/auth/admin_action_specs.d.ts +4 -4
- package/dist/auth/admin_action_specs.d.ts.map +1 -1
- package/dist/auth/admin_action_specs.js +33 -12
- package/dist/auth/admin_actions.d.ts.map +1 -1
- package/dist/auth/admin_actions.js +35 -6
- package/dist/auth/all_cell_actions.d.ts +11 -2
- package/dist/auth/all_cell_actions.d.ts.map +1 -1
- package/dist/auth/all_cell_actions.js +2 -1
- package/dist/auth/audit_emitter.d.ts +23 -3
- package/dist/auth/audit_emitter.d.ts.map +1 -1
- package/dist/auth/audit_emitter.js +14 -4
- package/dist/auth/audit_log_schema.d.ts +1 -0
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +9 -5
- package/dist/auth/bootstrap_account.d.ts +25 -1
- package/dist/auth/bootstrap_account.d.ts.map +1 -1
- package/dist/auth/bootstrap_account.js +37 -4
- package/dist/auth/bootstrap_routes.d.ts +3 -2
- package/dist/auth/bootstrap_routes.d.ts.map +1 -1
- package/dist/auth/bootstrap_routes.js +5 -4
- package/dist/auth/cell_action_specs.d.ts +215 -15
- package/dist/auth/cell_action_specs.d.ts.map +1 -1
- package/dist/auth/cell_action_specs.js +103 -16
- package/dist/auth/cell_actions.d.ts +40 -18
- package/dist/auth/cell_actions.d.ts.map +1 -1
- package/dist/auth/cell_actions.js +126 -19
- package/dist/auth/cell_audit_events.d.ts +1 -1
- package/dist/auth/cell_audit_events.d.ts.map +1 -1
- package/dist/auth/cell_audit_events.js +3 -2
- package/dist/auth/cell_audit_metadata.d.ts +11 -0
- package/dist/auth/cell_audit_metadata.d.ts.map +1 -1
- package/dist/auth/cell_audit_metadata.js +10 -0
- package/dist/auth/cell_grant_action_specs.d.ts +7 -0
- package/dist/auth/cell_grant_action_specs.d.ts.map +1 -1
- package/dist/auth/cell_grant_action_specs.js +7 -0
- package/dist/auth/cell_grant_actions.d.ts +36 -4
- package/dist/auth/cell_grant_actions.d.ts.map +1 -1
- package/dist/auth/cell_grant_actions.js +20 -3
- package/dist/auth/credential_type_schema.d.ts +3 -2
- package/dist/auth/credential_type_schema.d.ts.map +1 -1
- package/dist/auth/credential_type_schema.js +3 -2
- package/dist/auth/role_grant_offer_action_specs.d.ts +18 -2
- package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_action_specs.js +30 -6
- package/dist/auth/role_grant_offer_actions.d.ts +9 -4
- package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_actions.js +58 -7
- package/dist/auth/role_grant_offer_notifications.d.ts +3 -2
- package/dist/auth/role_grant_offer_notifications.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_notifications.js +3 -2
- package/dist/auth/role_grant_offer_queries.d.ts +20 -0
- package/dist/auth/role_grant_offer_queries.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_queries.js +35 -0
- package/dist/auth/role_grant_queries.d.ts +27 -9
- package/dist/auth/role_grant_queries.d.ts.map +1 -1
- package/dist/auth/role_grant_queries.js +40 -13
- package/dist/auth/role_schema.d.ts +14 -8
- package/dist/auth/role_schema.d.ts.map +1 -1
- package/dist/auth/role_schema.js +6 -3
- package/dist/auth/self_service_role_actions.d.ts.map +1 -1
- package/dist/auth/self_service_role_actions.js +20 -0
- package/dist/db/CLAUDE.md +9 -2
- package/dist/db/cell_ddl.d.ts +9 -1
- package/dist/db/cell_ddl.d.ts.map +1 -1
- package/dist/db/cell_ddl.js +11 -1
- package/dist/db/cell_queries.d.ts +53 -7
- package/dist/db/cell_queries.d.ts.map +1 -1
- package/dist/db/cell_queries.js +61 -7
- package/dist/http/CLAUDE.md +14 -2
- package/dist/http/body_limit.d.ts +3 -1
- package/dist/http/body_limit.d.ts.map +1 -1
- package/dist/http/body_limit.js +6 -1
- package/dist/http/error_schemas.d.ts +8 -0
- package/dist/http/error_schemas.d.ts.map +1 -1
- package/dist/http/error_schemas.js +12 -0
- package/dist/http/jsonrpc.d.ts +9 -0
- package/dist/http/jsonrpc.d.ts.map +1 -1
- package/dist/http/jsonrpc.js +17 -0
- package/dist/http/jsonrpc_errors.d.ts +40 -15
- package/dist/http/jsonrpc_errors.d.ts.map +1 -1
- package/dist/http/jsonrpc_errors.js +99 -35
- package/dist/http/proxy.d.ts +4 -2
- package/dist/http/proxy.d.ts.map +1 -1
- package/dist/http/proxy.js +7 -2
- package/dist/http/surface.d.ts +3 -1
- package/dist/http/surface.d.ts.map +1 -1
- package/dist/http/surface.js +7 -4
- package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
- package/dist/realtime/sse_auth_guard.js +3 -2
- package/dist/schema_meta.d.ts +92 -1
- package/dist/schema_meta.d.ts.map +1 -1
- package/dist/schema_meta.js +117 -0
- package/dist/server/app_server.d.ts +13 -5
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +32 -8
- package/dist/testing/CLAUDE.md +196 -82
- package/dist/testing/adversarial_input_cases.d.ts +67 -7
- package/dist/testing/adversarial_input_cases.d.ts.map +1 -1
- package/dist/testing/adversarial_input_cases.js +210 -165
- package/dist/testing/app_server.d.ts +3 -1
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/audit_drift_guard.d.ts +3 -2
- package/dist/testing/audit_drift_guard.d.ts.map +1 -1
- package/dist/testing/audit_drift_guard.js +5 -3
- package/dist/testing/cross_backend/actor_lookup.d.ts.map +1 -1
- package/dist/testing/cross_backend/actor_lookup.js +27 -0
- package/dist/testing/cross_backend/audit_cross_helpers.d.ts +6 -0
- package/dist/testing/cross_backend/audit_cross_helpers.d.ts.map +1 -1
- package/dist/testing/cross_backend/audit_cross_helpers.js +11 -0
- package/dist/testing/cross_backend/capabilities.d.ts +29 -5
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/capabilities.js +10 -6
- package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_crud.js +117 -2
- package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_gated_create.js +109 -13
- package/dist/testing/cross_backend/cell_moderate.d.ts +7 -1
- package/dist/testing/cross_backend/cell_moderate.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_moderate.js +68 -4
- package/dist/testing/cross_backend/cell_ref_admission.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_ref_admission.js +18 -9
- package/dist/testing/cross_backend/conformance_table.d.ts +5 -0
- package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
- package/dist/testing/cross_backend/conformance_table.js +12 -10
- package/dist/testing/cross_backend/cross_rpc.d.ts +3 -1
- package/dist/testing/cross_backend/cross_rpc.d.ts.map +1 -1
- package/dist/testing/cross_backend/cross_rpc.js +2 -2
- package/dist/testing/cross_backend/default_backend_configs.d.ts +9 -7
- package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_backend_configs.js +3 -3
- package/dist/testing/cross_backend/differential.d.ts +7 -0
- package/dist/testing/cross_backend/differential.d.ts.map +1 -1
- package/dist/testing/cross_backend/differential.js +17 -0
- package/dist/testing/cross_backend/expected_schema.json +1 -0
- package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
- package/dist/testing/cross_backend/full_spine_mount.js +5 -4
- package/dist/testing/cross_backend/role_grant_offer_lifecycle.d.ts.map +1 -1
- package/dist/testing/cross_backend/role_grant_offer_lifecycle.js +74 -2
- package/dist/testing/cross_backend/rpc_adversarial.d.ts +9 -0
- package/dist/testing/cross_backend/rpc_adversarial.d.ts.map +1 -1
- package/dist/testing/cross_backend/rpc_adversarial.js +16 -6
- package/dist/testing/cross_backend/rust_spine_consumer_surface.d.ts +1 -1
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +6 -3
- package/dist/testing/cross_backend/setup.d.ts +2 -2
- package/dist/testing/cross_backend/spine_suites.d.ts +85 -5
- package/dist/testing/cross_backend/spine_suites.d.ts.map +1 -1
- package/dist/testing/cross_backend/spine_suites.js +291 -20
- package/dist/testing/cross_backend/spine_surface_constants.d.ts +15 -0
- package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
- package/dist/testing/cross_backend/spine_surface_constants.js +24 -0
- package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts +18 -19
- package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts.map +1 -1
- package/dist/testing/cross_backend/test_cell_gated_create_authorize.js +66 -42
- package/dist/testing/cross_backend/testing_backdoor.d.ts +27 -4
- package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_backdoor.js +136 -41
- package/dist/testing/cross_backend/testing_reset_actions.d.ts +30 -15
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.js +52 -35
- package/dist/testing/cross_backend/transport_composition.js +3 -3
- package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/ts_spine_backend_config.js +6 -3
- package/dist/testing/rpc_attack_surface.d.ts +10 -0
- package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
- package/dist/testing/rpc_attack_surface.js +15 -3
- package/dist/testing/rpc_envelope_cases.d.ts +5 -2
- package/dist/testing/rpc_envelope_cases.d.ts.map +1 -1
- package/dist/testing/rpc_envelope_cases.js +4 -1
- package/dist/testing/rpc_round_trip.js +3 -3
- package/dist/testing/schema_generators.d.ts +99 -2
- package/dist/testing/schema_generators.d.ts.map +1 -1
- package/dist/testing/schema_generators.js +244 -76
- package/dist/testing/stubs.d.ts +4 -3
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +6 -4
- package/dist/zod.d.ts +9 -0
- package/dist/zod.d.ts.map +1 -1
- package/dist/zod.js +20 -0
- package/package.json +1 -1
package/dist/testing/CLAUDE.md
CHANGED
|
@@ -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.
|
|
@@ -345,7 +345,11 @@ field a loose output schema passes on one spine, shows up as a difference.
|
|
|
345
345
|
one alone, registration throws. Per case, each backend's own `setup_test`
|
|
346
346
|
(separate databases and keepers), the principal resolved and the request
|
|
347
347
|
sent through `send_conformance_case` (`conformance_table.ts`), both answers
|
|
348
|
-
normalized and diffed.
|
|
348
|
+
normalized and diffed. Every HTTP request carries
|
|
349
|
+
`DIFFERENTIAL_REQUEST_HEADERS` (`accept-encoding: identity`), so a runtime's
|
|
350
|
+
own compression (`Deno.serve` gzips for a request accepting it, as Node's
|
|
351
|
+
`fetch` sends by default) never reads as a `content-encoding` / `vary`
|
|
352
|
+
difference. The per-case `expect` is not asserted —
|
|
349
353
|
each backend's conformance leg holds it. `KnownDivergence` is the ledger
|
|
350
354
|
row: `{case_name | method, tracking_id, reason, what_differs}`, the names a
|
|
351
355
|
string or a list, `what_differs` the diff paths covered (a path covers
|
|
@@ -474,11 +478,15 @@ Support functions:
|
|
|
474
478
|
|
|
475
479
|
Walks Zod schemas to generate valid values for adversarial/round-trip tests.
|
|
476
480
|
|
|
477
|
-
- `
|
|
478
|
-
- `
|
|
481
|
+
- `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.
|
|
482
|
+
- `detect_format(field_schema)` — `detect_json_schema_format` over `to_field_json_schema`: the `format`, `'pattern'` for a regex, or `null`.
|
|
483
|
+
- `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.
|
|
484
|
+
- `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
485
|
- `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
486
|
- `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
|
-
- `
|
|
487
|
+
- `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.
|
|
488
|
+
- `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.
|
|
489
|
+
- `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
490
|
|
|
483
491
|
### `integration_helpers.ts` — route lookup + body checks
|
|
484
492
|
|
|
@@ -534,9 +542,12 @@ All cases expect 400 with one of `ERROR_INVALID_REQUEST_BODY` /
|
|
|
534
542
|
The generators live in the hono-free `adversarial_input_cases.ts`, so the
|
|
535
543
|
cross-process suites reuse them:
|
|
536
544
|
|
|
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,
|
|
538
|
-
- `
|
|
539
|
-
- `
|
|
545
|
+
- `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).
|
|
546
|
+
- `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.
|
|
547
|
+
- `generate_params_test_cases(params_schema)` → `ParamsTestCase[]` — format violations only (unconstrained string params accept anything), each one the field refuses.
|
|
548
|
+
- `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.
|
|
549
|
+
|
|
550
|
+
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
551
|
|
|
541
552
|
GET-with-input routes hit the RPC `?params=` query convention; invalid-JSON
|
|
542
553
|
arrays there collapse to `ERROR_INVALID_REQUEST_BODY` (schema failure)
|
|
@@ -626,8 +637,8 @@ envelope — and a 401 to a non-public call (`assert_rpc_authenticated`): the
|
|
|
626
637
|
call carried a credential its auth admits, so a 401 means it stopped working.
|
|
627
638
|
A public method may still answer 401 (a wrong-password login), and so may a
|
|
628
639
|
handler naming its own reason (below). A ≥500 carrying a declared code
|
|
629
|
-
(`service_unavailable` 503, `timeout` 504, a consumer code
|
|
630
|
-
`
|
|
640
|
+
(`service_unavailable` 503, `timeout` 504, a consumer code declared with
|
|
641
|
+
`register_jsonrpc_error_code`) is a server condition and passes like a
|
|
631
642
|
4xx. The failure names the method, status, code and message; a non-JSON body
|
|
632
643
|
is reported as text. Options: `{setup_test, surface_source, session_options,
|
|
633
644
|
rpc_endpoints, skip_methods?, input_overrides?, success_fixtures?}`.
|
|
@@ -927,9 +938,10 @@ loses coverage.
|
|
|
927
938
|
|
|
928
939
|
`rpc_success_fixtures`, `rpc_skip_methods` and `rpc_input_overrides`
|
|
929
940
|
forward to the RPC round-trip's `success_fixtures`, `skip_methods` and
|
|
930
|
-
`input_overrides` (same options on `describe_standard_cross_process_tests
|
|
931
|
-
|
|
932
|
-
|
|
941
|
+
`input_overrides` (same options on `describe_standard_cross_process_tests`;
|
|
942
|
+
`describe_spine_cross_suites` also forwards `rpc_input_overrides` to
|
|
943
|
+
`rpc_adversarial`) — `rpc_skip_methods` is the consumer escape hatch for a
|
|
944
|
+
method the generic round-trip can't drive in its test wiring.
|
|
933
945
|
|
|
934
946
|
`StandardTestOptions` requires `create_route_specs` (for rate_limiting) and
|
|
935
947
|
`rpc_endpoints` (for admin/audit_completeness/rpc_round_trip); the admin
|
|
@@ -1003,12 +1015,12 @@ Registry lookups:
|
|
|
1003
1015
|
- correct auth passes (not 401/403)
|
|
1004
1016
|
- GET unauthenticated for `side_effects: false` reads
|
|
1005
1017
|
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)
|
|
1018
|
+
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
1019
|
|
|
1008
1020
|
Skips silently when `surface.rpc_endpoints` is empty. Uses stub deps — no
|
|
1009
1021
|
DB needed.
|
|
1010
1022
|
|
|
1011
|
-
Options: `{build: () => AppSurfaceSpec, roles: Array<string
|
|
1023
|
+
Options: `{build: () => AppSurfaceSpec, roles: Array<string>, input_overrides?}`.
|
|
1012
1024
|
|
|
1013
1025
|
**Opt-in bundles need their own per-bundle suite file.** Action bundles not
|
|
1014
1026
|
folded into `create_standard_rpc_actions` (today `self_service_role_actions`,
|
|
@@ -1128,7 +1140,10 @@ file; the dual-spawn and limiter-enabled suites name their own files.
|
|
|
1128
1140
|
`fact_serving_admin_hash`, `ready`,
|
|
1129
1141
|
`peer_request`, `ws_account_actions`, `ws_role_gated`), and the family
|
|
1130
1142
|
defaults (`ts_default_capabilities` / `rust_default_capabilities`, typed
|
|
1131
|
-
`FamilyDefaultCapabilities`) omit them
|
|
1143
|
+
`FamilyDefaultCapabilities`) omit them and the `testing_backdoors` list (the
|
|
1144
|
+
`_testing_*` backdoors the backend mounts, from `TESTING_BACKDOOR_METHODS` —
|
|
1145
|
+
§Testing-backdoor declaration below; `[]` in-process, every one on fuz_app's
|
|
1146
|
+
spine presets), with `capabilities` required on
|
|
1132
1147
|
`make_default_{ts,rust}_backend_config` — so a config that leaves one out
|
|
1133
1148
|
fails typecheck naming it, rather than silently skipping its suites. The
|
|
1134
1149
|
family defaults carry the declared surface's mounts (`account_lifecycle`,
|
|
@@ -1162,7 +1177,8 @@ file; the dual-spawn and limiter-enabled suites name their own files.
|
|
|
1162
1177
|
`fact_holder` row, so a non-admin can reference what it seeds, and whose
|
|
1163
1178
|
optional `external_url` writes a bare external row naming that URL, so the
|
|
1164
1179
|
suite can pin that a URL naming another hash's file is refused) — it also
|
|
1165
|
-
gates `describe_cell_ref_admission_cross_tests
|
|
1180
|
+
gates `describe_cell_ref_admission_cross_tests` (its grant, item, and clone
|
|
1181
|
+
cases with `cell_relations`). `fact_serving_admin_hash`
|
|
1166
1182
|
is the admin-only bare-hash route (`GET /api/facts/:hash`), its own flag
|
|
1167
1183
|
because a backend can serve the cell-gated route alone: it gates the
|
|
1168
1184
|
fact-serving suite's bare-hash cases (with `fact_serving`) and the bare-hash
|
|
@@ -1224,11 +1240,15 @@ the omitted suites.
|
|
|
1224
1240
|
One call that runs every shipped single-backend cross suite against one
|
|
1225
1241
|
spawned backend: `describe_spine_cross_suites(handle, {surface_source,
|
|
1226
1242
|
session_options, rpc_endpoints, live_rpc_endpoints?, roles?, setup?, exclude?,
|
|
1227
|
-
overrides?, rpc_success_fixtures?, rpc_skip_methods?, rpc_input_overrides?,
|
|
1243
|
+
exclude_cases?, overrides?, rpc_success_fixtures?, rpc_skip_methods?, rpc_input_overrides?,
|
|
1228
1244
|
rpc_max_body_bytes?, ws_max_message_bytes?})`.
|
|
1229
1245
|
`live_rpc_endpoints` is the backend's whole live mount; given, `rpc_adversarial`
|
|
1230
|
-
also walks the methods it keeps off the declared surface.
|
|
1231
|
-
|
|
1246
|
+
also walks the methods it keeps off the declared surface. `rpc_input_overrides`
|
|
1247
|
+
reaches the standard bundle's RPC round-trip and `rpc_adversarial`'s
|
|
1248
|
+
`input_overrides`, so one valid body per method serves both; a key naming a
|
|
1249
|
+
method on neither the declared surface nor `live_rpc_endpoints` throws at
|
|
1250
|
+
collection. Without it a consumer writes a file per suite, and a suite added
|
|
1251
|
+
here never reaches it.
|
|
1232
1252
|
|
|
1233
1253
|
- **The registry.** `SUITE_NAMES` / `SuiteName` — the closed set of suites the
|
|
1234
1254
|
call runs, each named by its module (`ws_round_trip` is
|
|
@@ -1270,13 +1290,51 @@ consumer writes a file per suite, and a suite added here never reaches it.
|
|
|
1270
1290
|
takes it as `rpc_max_body_bytes`), the second `ws_round_trip`'s
|
|
1271
1291
|
`max_message_bytes` — one value per limit for every suite probing it.
|
|
1272
1292
|
- **`exclude`** — `{[suite]: reason}`; the reason is required and shows in the
|
|
1273
|
-
run as a skipped test. An empty reason, an unknown suite name,
|
|
1274
|
-
both excluded and overridden
|
|
1293
|
+
run as a skipped test. An empty reason, an unknown suite name, a suite
|
|
1294
|
+
both excluded and overridden, `testing_backdoor` (it checks the backdoor
|
|
1295
|
+
declaration the gating reads), or a suite already skipping itself for an
|
|
1296
|
+
undeclared backdoor throws before anything registers.
|
|
1297
|
+
- **Backdoors.** `capabilities.testing_backdoors` must declare
|
|
1298
|
+
`SPINE_SUITES_REQUIRED_BACKDOORS` (`_testing_reset`, every setup's per-test
|
|
1299
|
+
reset, and `_testing_drain_effects`, the audit-read barrier), and
|
|
1300
|
+
`_testing_put_fact` when `fact_serving` is declared; the call throws
|
|
1301
|
+
otherwise. A suite every case of which drives another backdoor
|
|
1302
|
+
(`SuiteEntry.backdoors`: `credential_deadline` →
|
|
1303
|
+
`_testing_shorten_credential_expiry`, `origin_conformance` →
|
|
1304
|
+
`_testing_origin_check`, `ws_outbound_overflow` →
|
|
1305
|
+
`_testing_flood_notifications`) skips itself on a backend not declaring it,
|
|
1306
|
+
as a skipped test beside the excludes whose reason names the backdoor; an
|
|
1307
|
+
exclude, override or `exclude_cases` entry for it throws as dead.
|
|
1308
|
+
`spine_suites.test.ts` holds the backdoors each suite module names — quoted
|
|
1309
|
+
`_testing_*` names, `testing_*_action_spec` references, and the
|
|
1310
|
+
`cross_put_fact` / `cross_drain_effects` seeders — to its entry's
|
|
1311
|
+
`backdoors`, the required pair, or `_testing_put_fact` under
|
|
1312
|
+
`capabilities.fact_serving`; a helper module the suite calls isn't read.
|
|
1313
|
+
- **`exclude_cases`** — `SuiteCaseExclusions`, `{[suite]: {[name path]:
|
|
1314
|
+
reason}}`: single tests of a suite that runs, each by its name path (the
|
|
1315
|
+
describe titles and the test name joined with `' > '` — what the reporter
|
|
1316
|
+
prints after the file and any `describe` wrapping the call; a full path, not
|
|
1317
|
+
a leaf name, so a title repeated under two describes names one test). One
|
|
1318
|
+
`beforeEach` on the calling suite (`register_spine_case_exclusions`) skips
|
|
1319
|
+
them at run time with `ctx.skip(reason)`, so the suite files stay as they
|
|
1320
|
+
are and each shows as skipped with its reason; it finds a test's suite
|
|
1321
|
+
from the top-level task ranges each suite registered (read through
|
|
1322
|
+
`TestRunner.getCurrentSuite()` at collection), so an entry matches only
|
|
1323
|
+
below its own suite. A reasonless entry, an empty suite map, or an entry for
|
|
1324
|
+
an unknown, excluded or self-skipping suite, or for `testing_backdoor`,
|
|
1325
|
+
throws at collection; an entry
|
|
1326
|
+
naming no test its suite collected fails the closing test
|
|
1327
|
+
(`check_spine_case_exclusions`, which reads the collected paths, so a
|
|
1328
|
+
filtered run still checks them) — the dead-name check, as the known-divergence
|
|
1329
|
+
ledger has.
|
|
1275
1330
|
- **`overrides`** — `SuiteOverrides`, per suite a `Partial` of its own options
|
|
1276
1331
|
(`SuiteOptionsMap`), shallow-merged over the derived ones: a role list, an
|
|
1277
1332
|
`xfail`, a narrower capability set, `ws: undefined` to drop optional socket
|
|
1278
|
-
cases (`session_cap` and `testing_backdoor`
|
|
1279
|
-
their only socket switch)
|
|
1333
|
+
cases (`session_cap` and `testing_backdoor` gate no case on `capabilities.ws`,
|
|
1334
|
+
so `ws` is their only socket switch), `extra_methods` to fire a consumer's own
|
|
1335
|
+
daemon-token seams through `testing_backdoor` (whose
|
|
1336
|
+
`capabilities.testing_backdoors` an override can't change — the call throws
|
|
1337
|
+
unless the merge keeps the backend's list). An entry can instead be a function of the derived
|
|
1280
1338
|
options returning the patch (`SuiteOverride`), for a change built on a
|
|
1281
1339
|
derived value — an `xfail` added to the derived multi-actor setup — so the
|
|
1282
1340
|
seeding isn't restated. After the merge, every option the suite's type
|
|
@@ -1431,18 +1489,19 @@ capabilities}` protocol every Tier 1 suite uses, so **one case array runs
|
|
|
1431
1489
|
always-available principals to fixture accessors; `role_holder` /
|
|
1432
1490
|
`wrong_role` read a seeded `extra_accounts` username named via
|
|
1433
1491
|
`options.principals`. `send_conformance_case(c, {fixture, surface_source, principals?,
|
|
1434
|
-
resolved_rpc_endpoints})` (exported) is the send half — principal resolved,
|
|
1492
|
+
resolved_rpc_endpoints, request_headers?})` (exported) is the send half — principal resolved,
|
|
1435
1493
|
request sent, the raw `{target, status, headers, body}` back unasserted; the
|
|
1436
1494
|
table asserts on it, and the cross-backend differential compares two.
|
|
1495
|
+
`request_headers` go on the HTTP request beneath the principal's (the
|
|
1496
|
+
differential's identity encoding); the WS leg sends none.
|
|
1437
1497
|
**WS leg:** `ws: WsEndpointTarget` (`{base_url, ws_path, origin?}`) on
|
|
1438
1498
|
either sends each case as one frame on an admitted socket the principal
|
|
1439
1499
|
opens — its headers on the upgrade, no `Origin` where the HTTP leg
|
|
1440
|
-
suppresses it, its own actor as `?acting=`.
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
upgrade-level difference; the front-door table's multi-actor row does. The frame is the envelope the HTTP
|
|
1500
|
+
suppresses it, its own actor as `?acting=`. Both spines resolve an actor at
|
|
1501
|
+
the upgrade, refusing a multi-actor account's upgrade without `?acting=`
|
|
1502
|
+
(400 `actor_required`), and per-frame dispatch resolves each frame's
|
|
1503
|
+
`acting` on both. The leg always sends `?acting=`, so it does not cover that
|
|
1504
|
+
upgrade refusal; the front-door table's multi-actor row does. The frame is the envelope the HTTP
|
|
1446
1505
|
body is (`perform_action_result_to_envelope` on TS), so the response reads
|
|
1447
1506
|
`{status, headers: {}, body: frame}` with `status` the code's
|
|
1448
1507
|
`jsonrpc_error_code_to_http_status` (200 for a result) and every row holds
|
|
@@ -1827,12 +1886,14 @@ never sees them). Both parse every success response against the verb's Zod
|
|
|
1827
1886
|
**output** schema, so a TS↔Rust envelope drift fails the suite — not just a
|
|
1828
1887
|
payload-field drift. Call-site primitives (`cross_rpc_call` / `error_reason` /
|
|
1829
1888
|
`expect_output`) live in `cross_backend/cross_rpc.ts`, shared by every
|
|
1830
|
-
stateful cross suite (
|
|
1889
|
+
stateful cross suite (`RpcResult` carries the HTTP `status` beside the
|
|
1890
|
+
envelope; a reply that is no JSON object throws naming the method,
|
|
1831
1891
|
the HTTP status and the body's head), and every
|
|
1832
1892
|
suite that reads audit rows goes through `cross_backend/audit_cross_helpers.ts`
|
|
1833
1893
|
— `list_audit_events(fixture, params, rpc_path?)` drains
|
|
1834
|
-
(`cross_drain_effects`, the `_testing_drain_effects` barrier
|
|
1835
|
-
parses, so no reader skips the
|
|
1894
|
+
(`cross_drain_effects`, the `_testing_drain_effects` barrier, refusing an answer
|
|
1895
|
+
without `tracked: true`) before it lists and parses, so no reader skips the
|
|
1896
|
+
barrier and every read checks the barrier is live, and parses every row's metadata
|
|
1836
1897
|
(`assert_audit_event_metadata`, closed to undeclared keys when the fixture's
|
|
1837
1898
|
setup asked), so every reader holds the rows it reads to their event's
|
|
1838
1899
|
schema. A suite pinning a row's values uses `assert_audit_metadata_fields(fixture,
|
|
@@ -1851,9 +1912,9 @@ an audit row, an admin account listing, and an offer's `created_at` /
|
|
|
1851
1912
|
imperative cross suite aliases: `CrossSuiteOptions` (`setup_test` alone),
|
|
1852
1913
|
`CapabilityGatedCrossSuiteOptions` (+ `capabilities` — `ready`),
|
|
1853
1914
|
`RpcPathCrossSuiteOptions` (+ `rpc_path?` — origin, body-size (which adds `base_url`), actor
|
|
1854
|
-
lookup, app settings
|
|
1915
|
+
lookup, app settings), and the intersection
|
|
1855
1916
|
`RpcPathCapabilityGatedCrossSuiteOptions` (every `cell_*` suite, query-shape,
|
|
1856
|
-
token-scope surface, fact-serving, account lifecycle). A suite takes the
|
|
1917
|
+
token-scope surface, fact-serving, account lifecycle, testing backdoor). A suite takes the
|
|
1857
1918
|
capability-carrying variant only when it actually gates cases on a flag, so
|
|
1858
1919
|
the option type states whether the bundle is read.
|
|
1859
1920
|
|
|
@@ -1868,7 +1929,15 @@ the option type states whether the bundle is read.
|
|
|
1868
1929
|
`cell_list` pages honoring `limit` + `offset`, a page holding visible rows
|
|
1869
1930
|
only and filling to `limit` past a newer run of rows the caller can't see —
|
|
1870
1931
|
two owners' private cells, read by a non-owner and by anon — and an admin's
|
|
1871
|
-
pages tiling every row
|
|
1932
|
+
pages tiling every row; a `data` that is no `CellData` — a non-object, or a
|
|
1933
|
+
non-string `label` / `summary` — refused 400 `invalid_params` with the
|
|
1934
|
+
generic message and no reason (no `data` beyond a TS spine's DEV-only
|
|
1935
|
+
`issues`, an array of Zod issues) on `cell_create`, on `cell_update` of a
|
|
1936
|
+
missing and of a hidden cell, and, with `cell_relations`, on `cell_clone`'s
|
|
1937
|
+
`with_data_patch` — ahead of the 404 mask the same hidden-cell call answers
|
|
1938
|
+
when well-typed, with the keeper's cell count unchanged; those cases run as
|
|
1939
|
+
the keeper and plain accounts only, so a backend that refuses a non-admin
|
|
1940
|
+
root cell can keep them through `exclude_cases` while excluding the rest).
|
|
1872
1941
|
- **`describe_cell_relations_cross_tests`** (gates on
|
|
1873
1942
|
`capabilities.cell_relations`) — the verbs beyond CRUD: grant lifecycle
|
|
1874
1943
|
(actor-shaped editor grant enables edit, manage-tier `cell_grant_list`,
|
|
@@ -1893,15 +1962,23 @@ the option type states whether the bundle is read.
|
|
|
1893
1962
|
admin-only (a non-admin → **403** `cell_create_forbidden`, and the same for a
|
|
1894
1963
|
non-admin `cell_clone` of a viewable space — clone runs the authorizer for
|
|
1895
1964
|
every cell it writes); a **contribution**
|
|
1896
|
-
under a space is gated by the root's `
|
|
1897
|
-
moderation_required}
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1965
|
+
under a space is gated by the root's `policy[kind] = {min_role,
|
|
1966
|
+
moderation_required}` (the root's manage-tier `policy` column), with
|
|
1967
|
+
`moderation` set per the verdict (`pending`+private vs
|
|
1968
|
+
`approved`+author-visibility); a **hidden** parent **404**-masks vs a
|
|
1969
|
+
**visible** one **403**s; plain parentless creates stay open. The root policy
|
|
1970
|
+
itself: `cell_policy_set` writes it (audited old and new), answers an
|
|
1971
|
+
editor-grant holder 403 `cell_policy_manage_only` and a non-viewer the 404,
|
|
1972
|
+
refuses a contribution `cell_policy_root_only`, and runs each policy through
|
|
1973
|
+
the authorizer's `validate_policy` (`cell_policy_invalid`); an editor
|
|
1974
|
+
rewriting the root's `data.policy` leaves moderation as the column says. The moderate
|
|
1975
|
+
suite then proves the `pending → approved | rejected` transition and the
|
|
1976
|
+
`approved → rejected` takedown: an admin (root manager) approves → public,
|
|
1977
|
+
the **author** self-approve → 403 (anti-self-approval), a non-viewer → 404, reject → stays private, and a cell
|
|
1903
1978
|
with no governing root → 400 `cell_not_a_contribution` for any viewer, the
|
|
1904
|
-
404 mask first for a non-viewer
|
|
1979
|
+
404 mask first for a non-viewer; approval needs a pending post and rejection
|
|
1980
|
+
a pending or approved one (the takedown), else 400 `cell_not_pending` — so a
|
|
1981
|
+
root owner shown a private born-approved contribution can't publish it. It also
|
|
1905
1982
|
pins the moderation read gate (a pending or rejected post reaches no
|
|
1906
1983
|
non-steward reader past its author and admins on `cell_get`, `cell_list`,
|
|
1907
1984
|
`cell_item_list`, and fact serving, even flipped public or granted while
|
|
@@ -2087,22 +2164,25 @@ are `src/test/auth/cell_crud_parity.db.test.ts`
|
|
|
2087
2164
|
`create_testing_actions(deps, options)` factory returning the
|
|
2088
2165
|
`_testing_reset` RPC action. Test binaries register it on their RPC
|
|
2089
2166
|
endpoint; `default_cross_process_setup` fires it unconditionally per
|
|
2090
|
-
test. Handler
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2167
|
+
test. Handler first fires the consumer-supplied `reset_state(db)`
|
|
2168
|
+
callback for domain-state reset — before the auth wipe, because domain
|
|
2169
|
+
rows depend on auth rows (a domain table with a restricting FK to `actor`
|
|
2170
|
+
must empty before the actor delete) — then DELETEs every auth-namespace
|
|
2171
|
+
row (no keeper-preserve filter), resets `app_settings` to production
|
|
2172
|
+
defaults, flips `bootstrap_lock.bootstrapped = true`, inline-seeds a fresh
|
|
2173
|
+
keeper via `create_test_account_with_credentials` (same primitive
|
|
2174
|
+
in-process uses, keeping write semantics in parity), queues the refresh
|
|
2097
2175
|
of `DaemonTokenState.keeper_account_id` to the new row as a post-commit
|
|
2098
|
-
effect (so a reset that rolls back — a
|
|
2099
|
-
commit — leaves the cache on the keeper
|
|
2100
|
-
next reset can still authenticate), then
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2176
|
+
effect as soon as the keeper exists (so a reset that rolls back — a
|
|
2177
|
+
failing extra seed, a failed commit — leaves the cache on the keeper
|
|
2178
|
+
still in the database, and the next reset can still authenticate), then
|
|
2179
|
+
seeds any `extra_accounts` at the same bootstrap-equivalent step (the
|
|
2180
|
+
only path for roles like `ROLE_KEEPER` whose `grant_paths` is
|
|
2181
|
+
bootstrap-only). `reset_state` is passed the **transactional** `Db` the
|
|
2182
|
+
auth wipe then runs on, so DB-domain consumers (e.g. fuz_forge truncating
|
|
2183
|
+
its cell / fact / file tables) reset on the same connection rather than a
|
|
2184
|
+
separately-pooled one that would deadlock against this open transaction
|
|
2185
|
+
under the in-memory drivers. Auth
|
|
2106
2186
|
gates on `credential_types: ['daemon_token']` — effectively keeper-only
|
|
2107
2187
|
without forcing the `actor: 'required'` ⟺ `acting?: ActingActor`
|
|
2108
2188
|
biconditional. No free-form runtime grant action exists — see the
|
|
@@ -2117,6 +2197,10 @@ are `src/test/auth/cell_crud_parity.db.test.ts`
|
|
|
2117
2197
|
emitter must be built with `create_audit_emitter({track_inflight: true})`
|
|
2118
2198
|
(the spine binary's `audit_factory` does — pinned by
|
|
2119
2199
|
`cross_backend/testing_spine_server.audit.test.ts`), or the drain resolves at once.
|
|
2200
|
+
The action answers `{ok: true, tracked}`, `tracked` read from
|
|
2201
|
+
`audit.is_tracking_inflight()`, and `cross_drain_effects` refuses anything but
|
|
2202
|
+
`tracked: true` — so a binary built over an untracked emitter fails its first
|
|
2203
|
+
audit read rather than racing it. The Rust twin answers the same shape.
|
|
2120
2204
|
The barrier is needed whatever the transport: HTTP on a binary running
|
|
2121
2205
|
`await_pending_effects: true` settles a mutation's writes before
|
|
2122
2206
|
responding, but WS replies before its effects flush, so a WS mutation's
|
|
@@ -2207,10 +2291,11 @@ cases are `src/test/auth/cell_ref_admission{,.fact_less}.db.test.ts`.
|
|
|
2207
2291
|
### Malformed-envelope and invalid-params parity — `cross_backend/rpc_adversarial.ts`
|
|
2208
2292
|
|
|
2209
2293
|
`describe_rpc_adversarial_cross_tests({setup_test, surface_source, rpc_path?,
|
|
2210
|
-
live_rpc_endpoints?, skip_methods?})` — the
|
|
2211
|
-
`describe_rpc_attack_surface_tests`, sent over
|
|
2212
|
-
spawned backend. The in-process groups run on stub
|
|
2213
|
-
parses envelopes and params by hand and the
|
|
2294
|
+
live_rpc_endpoints?, skip_methods?, input_overrides?, xfail_params?})` — the
|
|
2295
|
+
envelope and params groups of `describe_rpc_attack_surface_tests`, sent over
|
|
2296
|
+
`fixture.transport` to a spawned backend. The in-process groups run on stub
|
|
2297
|
+
apps, while the Rust spine parses envelopes and params by hand and the
|
|
2298
|
+
action-manifest parity gate
|
|
2214
2299
|
compares no param schemas, so this is what holds the spines to one answer.
|
|
2215
2300
|
|
|
2216
2301
|
- **envelopes** — every `create_rpc_envelope_cases` case, one test each, on a
|
|
@@ -2218,8 +2303,12 @@ compares no param schemas, so this is what holds the spines to one answer.
|
|
|
2218
2303
|
guard test that the endpoint has both a read and an authenticated mutation to
|
|
2219
2304
|
carry them.
|
|
2220
2305
|
- **params** — one test per declared method, sending every
|
|
2221
|
-
`generate_rpc_params_test_cases` case
|
|
2222
|
-
|
|
2306
|
+
`generate_rpc_params_test_cases` case (seeded from the method's
|
|
2307
|
+
`input_overrides` entry when it has one — the cases go as a single-actor
|
|
2308
|
+
principal, so an override omits `acting`) and failing with the list of
|
|
2309
|
+
cases not answered 400 `invalid_params` with the broken field's declared
|
|
2310
|
+
`invalid_reason` as `data.reason` (`expected_invalid_reason`; none for an
|
|
2311
|
+
undeclared field or a whole-body case). `xfail_params`
|
|
2223
2312
|
(`RpcParamsXfail` — `{method, labels, tracking_id, reason}`) moves the named
|
|
2224
2313
|
cases of a method into one `xfail_until` test each, for a known divergence on
|
|
2225
2314
|
one backend; the method's other cases keep their test, and an entry naming no
|
|
@@ -2313,10 +2402,9 @@ one is still asserted. The table
|
|
|
2313
2402
|
is the exported `transport_composition_cases` data. `multi_actor:
|
|
2314
2403
|
{setup_test, xfail?}` adds `transport_composition_multi_actor_cases`, run on
|
|
2315
2404
|
a fixture whose keeper has `extra_actors`: the keeper's session upgrading with
|
|
2316
|
-
no `?acting=` → 400 `actor_required` (flat body), the
|
|
2317
|
-
resolution.
|
|
2318
|
-
|
|
2319
|
-
the Rust stub only. Given `base_url`, every
|
|
2405
|
+
no `?acting=` → 400 `actor_required` (flat body), the upgrade's actor
|
|
2406
|
+
resolution on both spines. `xfail` is for a backend known to admit the
|
|
2407
|
+
socket; fuz_app passes none. Given `base_url`, every
|
|
2320
2408
|
probe is written to the socket (`raw_http_exchange`) and an over-cap request
|
|
2321
2409
|
sends its head alone; with `base_url: null` the probes go through the
|
|
2322
2410
|
fixture's transport and the WS rows skip. Not in the standard bundle. fuz_app's
|
|
@@ -2496,12 +2584,33 @@ payload reproduces it byte for byte and it authenticates, and a leading-zero
|
|
|
2496
2584
|
`0N` authenticates (the `u64` bound is on the value, not the digit count).
|
|
2497
2585
|
Ungated; cross-process only.
|
|
2498
2586
|
|
|
2499
|
-
### Testing-backdoor credential gate — `cross_backend/testing_backdoor.ts`
|
|
2500
|
-
|
|
2501
|
-
`describe_testing_backdoor_cross_tests({setup_test, rpc_path?,
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2587
|
+
### Testing-backdoor declaration + credential gate — `cross_backend/testing_backdoor.ts`
|
|
2588
|
+
|
|
2589
|
+
`describe_testing_backdoor_cross_tests({setup_test, capabilities, rpc_path?,
|
|
2590
|
+
ws?, extra_methods?})` — the backend's backdoor declaration, then the
|
|
2591
|
+
negative-credential parity suite for the `_testing_*` backdoor actions.
|
|
2592
|
+
|
|
2593
|
+
**Declaration.** `capabilities.testing_backdoors` lists the backdoors the
|
|
2594
|
+
backend mounts, from `TESTING_BACKDOOR_METHODS` (`spine_surface_constants.ts`,
|
|
2595
|
+
the one list; `spine_surface_constants.testing_backdoors.test.ts` holds it to
|
|
2596
|
+
the `_testing_*` actions fuz_app's spine binary mounts). Each known backdoor
|
|
2597
|
+
the list leaves out is sent anonymously and must answer `method_not_found`
|
|
2598
|
+
(404): both spines look the method up before they authenticate (the TS
|
|
2599
|
+
dispatcher's lookup step, the Rust `perform_action`'s), so an unmounted method
|
|
2600
|
+
answers that whatever the credential, and a mounted one a 401. Each declared
|
|
2601
|
+
one gets the rows below, whose anonymous row fails as "declared but not
|
|
2602
|
+
mounted" on a `method_not_found`. So the declaration can't lie in either
|
|
2603
|
+
direction, and the suites gated on it skip only what the backend lacks. A
|
|
2604
|
+
declared name outside the known list, or a duplicate, throws at collection.
|
|
2605
|
+
The suite runs on every backend, one declaring only the required backdoors
|
|
2606
|
+
included — `describe_spine_cross_suites` refuses to exclude it, skip any of
|
|
2607
|
+
its cases, or hand it other `capabilities` through `overrides`.
|
|
2608
|
+
|
|
2609
|
+
**Credential gate.** For each declared backdoor (the privileged writes, the
|
|
2610
|
+
audit barrier, the connection flood, and the introspection reads), and each
|
|
2611
|
+
`extra_methods` entry (`TestingBackdoorProbe` `{method, params?}` — a
|
|
2612
|
+
consumer's own daemon-token seam, which must start with `_testing_` and not be
|
|
2613
|
+
a known backdoor), it fires three principals over real HTTP: **anonymous** →
|
|
2505
2614
|
401 `unauthenticated`, **session** → 403 `forbidden` `credential_type_required`,
|
|
2506
2615
|
**bearer** → 403 `forbidden` `credential_type_required` — and, when `ws`
|
|
2507
2616
|
(`{base_url, ws_path, origin?}`) is passed, sends each on a socket opened with
|
|
@@ -2512,18 +2621,23 @@ on the real dispatcher (the spec-derived `describe_rpc_attack_surface_tests`
|
|
|
2512
2621
|
never enumerates them because they're off the declared surface). Each method is
|
|
2513
2622
|
sent with **valid** params so each case varies only the credential (validation
|
|
2514
2623
|
runs after the gates — order is 401 → authz → 403 → 429 → 400); the handler never
|
|
2515
|
-
runs.
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2624
|
+
runs. The known backdoors' params are a `Record<TestingBackdoorMethod, …>`, so a
|
|
2625
|
+
backdoor added to the list fails typecheck until it has them. Cited property:
|
|
2626
|
+
`docs/security.md` §Test Backdoor Actions. The credential rows run
|
|
2627
|
+
cross-process only (the `_testing_*` actions are mounted on the spawned binary,
|
|
2628
|
+
not the in-process app — like the ws/sse suites); in-process, an app mounting
|
|
2629
|
+
none declares none and gets the declaration rows alone, one per known backdoor
|
|
2630
|
+
— fuz_app's in-process leg is `cross_backend/testing_backdoor_parity.db.test.ts`,
|
|
2631
|
+
while its cross legs declare every backdoor, so they run no declaration row.
|
|
2632
|
+
The complement is the
|
|
2520
2633
|
in-process spec-level gate test (`src/test/testing/testing_actions_auth.test.ts`,
|
|
2521
2634
|
asserting each spec declares `credential_types: ['daemon_token']`) + the
|
|
2522
2635
|
`assert_no_testing_methods` surface invariant. fuz_app's conformance table
|
|
2523
2636
|
also covers the same gates, through the auth rows it generates over the live
|
|
2524
2637
|
mount (`live_rpc_endpoints`), which the differential compares across spines;
|
|
2525
2638
|
this suite stays for consumers whose backends mount the `_testing_*` actions
|
|
2526
|
-
without a conformance table over their live mount
|
|
2639
|
+
without a conformance table over their live mount, and for the declaration
|
|
2640
|
+
check.
|
|
2527
2641
|
|
|
2528
2642
|
### Identity-primitive parity — `cross_backend/identity_parity.ts`
|
|
2529
2643
|
|
|
@@ -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
|
-
* @
|
|
44
|
-
*
|
|
45
|
-
*
|
|
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
|
-
*
|
|
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;
|
|
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"}
|