@fuzdev/fuz_app 0.135.0 → 0.137.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 (104) hide show
  1. package/dist/actions/CLAUDE.md +2 -2
  2. package/dist/actions/action_spec.d.ts +0 -48
  3. package/dist/actions/action_spec.d.ts.map +1 -1
  4. package/dist/actions/transports_ws_backend.d.ts +1 -1
  5. package/dist/actions/transports_ws_backend.js +1 -1
  6. package/dist/auth/CLAUDE.md +8 -2
  7. package/dist/auth/account_action_specs.d.ts +7 -0
  8. package/dist/auth/account_action_specs.d.ts.map +1 -1
  9. package/dist/auth/account_action_specs.js +12 -3
  10. package/dist/auth/account_schema.d.ts +1 -6
  11. package/dist/auth/account_schema.d.ts.map +1 -1
  12. package/dist/auth/account_schema.js +1 -0
  13. package/dist/auth/actor_search_action_specs.d.ts +1 -1
  14. package/dist/auth/actor_search_action_specs.d.ts.map +1 -1
  15. package/dist/auth/actor_search_action_specs.js +3 -6
  16. package/dist/auth/audit_emitter.d.ts +4 -0
  17. package/dist/auth/audit_emitter.d.ts.map +1 -1
  18. package/dist/auth/audit_emitter.js +18 -2
  19. package/dist/auth/cell_action_specs.d.ts +11 -1
  20. package/dist/auth/cell_action_specs.d.ts.map +1 -1
  21. package/dist/auth/cell_action_specs.js +18 -5
  22. package/dist/auth/cell_item_action_specs.d.ts.map +1 -1
  23. package/dist/auth/cell_item_action_specs.js +2 -4
  24. package/dist/auth/password.d.ts +2 -2
  25. package/dist/auth/password.d.ts.map +1 -1
  26. package/dist/auth/password.js +10 -12
  27. package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
  28. package/dist/auth/role_grant_offer_action_specs.js +9 -9
  29. package/dist/auth/role_grant_offer_notifications.d.ts.map +1 -1
  30. package/dist/auth/role_grant_offer_notifications.js +2 -1
  31. package/dist/auth/role_grant_offer_schema.d.ts +1 -1
  32. package/dist/auth/role_grant_offer_schema.d.ts.map +1 -1
  33. package/dist/auth/role_grant_offer_schema.js +4 -7
  34. package/dist/auth/role_schema.d.ts.map +1 -1
  35. package/dist/auth/role_schema.js +5 -4
  36. package/dist/auth/scope_kind_schema.d.ts +10 -0
  37. package/dist/auth/scope_kind_schema.d.ts.map +1 -1
  38. package/dist/auth/scope_kind_schema.js +15 -3
  39. package/dist/cli/args.d.ts +21 -15
  40. package/dist/cli/args.d.ts.map +1 -1
  41. package/dist/cli/args.js +40 -16
  42. package/dist/cli/daemon.d.ts +0 -5
  43. package/dist/cli/daemon.d.ts.map +1 -1
  44. package/dist/cli/help.d.ts +45 -6
  45. package/dist/cli/help.d.ts.map +1 -1
  46. package/dist/cli/help.js +81 -4
  47. package/dist/env/load.d.ts +1 -1
  48. package/dist/env/load.js +1 -1
  49. package/dist/http/CLAUDE.md +1 -1
  50. package/dist/http/auth_shape.d.ts +0 -27
  51. package/dist/http/auth_shape.d.ts.map +1 -1
  52. package/dist/http/body_limit.d.ts +27 -14
  53. package/dist/http/body_limit.d.ts.map +1 -1
  54. package/dist/http/body_limit.js +29 -16
  55. package/dist/http/db_routes.d.ts.map +1 -1
  56. package/dist/http/db_routes.js +5 -3
  57. package/dist/primitive_schemas.d.ts +38 -4
  58. package/dist/primitive_schemas.d.ts.map +1 -1
  59. package/dist/primitive_schemas.js +77 -13
  60. package/dist/server/x_accel.d.ts +0 -1
  61. package/dist/server/x_accel.d.ts.map +1 -1
  62. package/dist/testing/CLAUDE.md +36 -19
  63. package/dist/testing/audit_drift_guard.d.ts +18 -5
  64. package/dist/testing/audit_drift_guard.d.ts.map +1 -1
  65. package/dist/testing/audit_drift_guard.js +24 -8
  66. package/dist/testing/cross_backend/action_manifest.d.ts +0 -14
  67. package/dist/testing/cross_backend/action_manifest.d.ts.map +1 -1
  68. package/dist/testing/cross_backend/body_size.d.ts +18 -5
  69. package/dist/testing/cross_backend/body_size.d.ts.map +1 -1
  70. package/dist/testing/cross_backend/body_size.js +233 -34
  71. package/dist/testing/cross_backend/body_size_smuggling.d.ts +0 -12
  72. package/dist/testing/cross_backend/body_size_smuggling.d.ts.map +1 -1
  73. package/dist/testing/cross_backend/body_size_smuggling.js +92 -147
  74. package/dist/testing/cross_backend/capabilities.d.ts +3 -24
  75. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  76. package/dist/testing/cross_backend/capabilities.js +1 -4
  77. package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
  78. package/dist/testing/cross_backend/cell_crud.js +31 -2
  79. package/dist/testing/cross_backend/conformance_case.d.ts +3 -3
  80. package/dist/testing/cross_backend/credential_header_robustness.d.ts.map +1 -1
  81. package/dist/testing/cross_backend/credential_header_robustness.js +18 -54
  82. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
  83. package/dist/testing/cross_backend/default_backend_configs.js +2 -8
  84. package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
  85. package/dist/testing/cross_backend/testing_backdoor.js +2 -0
  86. package/dist/testing/cross_backend/testing_reset_actions.d.ts +5 -9
  87. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  88. package/dist/testing/cross_backend/testing_reset_actions.js +5 -0
  89. package/dist/testing/cross_backend/token_lifetime.d.ts.map +1 -1
  90. package/dist/testing/cross_backend/token_lifetime.js +34 -1
  91. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
  92. package/dist/testing/cross_backend/ts_spine_backend_config.js +7 -14
  93. package/dist/testing/cross_backend/ws_outbound_overflow.d.ts.map +1 -1
  94. package/dist/testing/cross_backend/ws_outbound_overflow.js +65 -0
  95. package/dist/testing/schema_introspect.d.ts +24 -4
  96. package/dist/testing/schema_introspect.d.ts.map +1 -1
  97. package/dist/testing/schema_introspect.js +27 -4
  98. package/dist/testing/transports/raw_http.d.ts +78 -0
  99. package/dist/testing/transports/raw_http.d.ts.map +1 -0
  100. package/dist/testing/transports/raw_http.js +141 -0
  101. package/package.json +7 -5
  102. package/dist/env/resolve.d.ts +0 -163
  103. package/dist/env/resolve.d.ts.map +0 -1
  104. package/dist/env/resolve.js +0 -267
@@ -99,7 +99,8 @@ mismatch.
99
99
 
100
100
  ### `audit_drift_guard.ts` — audit-emission validation
101
101
 
102
- - `install_audit_drift_guard()` — `beforeEach` resets + `afterEach` zero-checks `audit_metadata_validation_failures` + `audit_unknown_event_type_failures` counters from `auth/audit_log_queries.ts`. Call once at the top of any `describe_db` block firing audit emits — production validation is fail-open, so without this any regression shipping a typo'd `event_type` or undeclared metadata field is silent. Pair with `await_pending_effects: true` (the `create_test_app` default) so fire-and-forget audit writes complete by response time.
102
+ - `install_audit_drift_guard()` — `beforeEach` resets + `afterEach` zero-checks the `audit_metadata_validation_failures` + `audit_unknown_event_type_failures` counters from `auth/audit_log_queries.ts` and the emitter's write-failure counter (`get_audit_write_failures` in `auth/audit_emitter.ts`). Call once at the top of any `describe_db` block firing audit emits — production validation and the emitter's write are fail-open, so without this any regression shipping a typo'd `event_type` or undeclared metadata field, or losing a row the database refused, is silent. A test that fails a write on purpose resets the counter (`reset_audit_write_failures`) once it has asserted it.
103
+ - `assert_no_audit_drift()` — the zero-check itself, for a test that manages the counters. Pair with `await_pending_effects: true` (the `create_test_app` default) so fire-and-forget audit writes complete by response time.
103
104
  - `create_recording_audit_emitter(calls_ref?)` — no-op `AuditEmitter` pushing every `emit` and `emit_pool` call into `calls`. Pass `calls_ref` to write into a caller-owned array; omit to let the helper allocate. Returns `{emitter, calls}` — destructure `emitter` as the `audit` dep and read `calls` to assert. `add_listener` records (never fires) and returns a remover with the production semantics, so `listener_count` tracks what a server registered and released. Replaces per-file capturing emitters previously duplicated across `password_change.test.ts`, `audit_log.test.ts`, etc.
104
105
  - `RecordingAuditEmitter` — `{emitter: AuditEmitter; calls: Array<AuditLogInput>}`.
105
106
 
@@ -223,7 +224,7 @@ test-only by construction.
223
224
 
224
225
  ### `schema_introspect.ts` — `query_schema_snapshot`
225
226
 
226
- - `query_schema_snapshot(db, options?)` — introspects a live DB into a deterministic `SchemaSnapshot` via `pg_catalog` + `information_schema`. Captures tables, columns (with `udt_name` to distinguish int4/int8), indexes (`indexdef`), constraints (`pg_get_constraintdef`), sequences, and enum types (`pg_enum` labels in declared `enumsortorder`, so a `cell_visibility` label-set/order drift is gated). The `schema_version` migration tracker is always excluded — it's framework bookkeeping, not domain schema; twin impls keep it identical (same namespaces, names and partitioning), which `query_migration_tracker` + `assert_migration_trackers_equal` gate separately. Twinned by `fuz_db::query_schema_snapshot` (Rust); the `_testing_schema_snapshot` RPC's wire validator is the shared `SchemaSnapshot`, so the enum field must serialize on both sides.
227
+ - `query_schema_snapshot(db, options?)` — introspects a live DB into a deterministic `SchemaSnapshot` via `pg_catalog` + `information_schema`. Throws on pglet when the snapshot holds an index, unless `accept_pglet_index_definitions` is passed: pglet's `indexdef` omits index modifiers, so its index definitions read as false differences against PostgreSQL and false agreement between two pglet schemas. Captures tables, columns (with `udt_name` to distinguish int4/int8), indexes (`indexdef`), constraints (`pg_get_constraintdef`), sequences, and enum types (`pg_enum` labels in declared `enumsortorder`, so a `cell_visibility` label-set/order drift is gated). The `schema_version` migration tracker is always excluded — it's framework bookkeeping, not domain schema; twin impls keep it identical (same namespaces, names and partitioning), which `query_migration_tracker` + `assert_migration_trackers_equal` gate separately. Twinned by `fuz_db::query_schema_snapshot` (Rust); the `_testing_schema_snapshot` RPC's wire validator is the shared `SchemaSnapshot`, so the enum field must serialize on both sides.
227
228
  - `SchemaSnapshot` — the Zod schema is canonical (co-located in `schema_introspect.ts`; the cross-impl `_testing_schema_snapshot` RPC action reuses it as its wire validator, and the type is `z.infer`'d from it). Fully JSON-serializable; every collection deterministically sorted on capture so structural equality is stable across runs.
228
229
  - `query_migration_tracker(db)` / `MigrationTracker` — the migration-_identity_ twin of the snapshot: reads the `schema_version` tracker rows (`[{namespace, name, sequence}]`) the snapshot deliberately _excludes_. `MigrationTracker` is the `_testing_migration_tracker` RPC's wire validator; pairs with `assert_migration_trackers_equal` to gate that the two spines record byte-identical migration identity (the swap-freely invariant), catching a migration-name or namespace-partitioning drift the provenance-agnostic schema snapshot can't see.
229
230
 
@@ -927,7 +928,7 @@ source of truth for wire-shape conformance.
927
928
  - `testing/cross_backend/capabilities.ts` — `BackendCapabilities` vocabulary
928
929
  (the **gating** flags `ws` / `sse` / `cell_crud` / `cell_relations` /
929
930
  `cell_gated_create` / `account_lifecycle` / `fact_serving` / `ready` /
930
- `account_status` / `oversized_reject_closes_connection` / `peer_request` /
931
+ `account_status` / `peer_request` /
931
932
  `ws_handshake_pipelining` / `ws_account_actions` / `ws_self_revocation_reply` —
932
933
  each has a `test_if`
933
934
  reader (`ws_self_revocation_reply` forks one case's assertion instead); `peer_request` (server-initiated requests — the ActionPeer
@@ -971,18 +972,17 @@ source of truth for wire-shape conformance.
971
972
  case — `GET /api/account/status` is bundled into `create_account_route_specs`,
972
973
  so every spine serves it (`true`); the gate fails loud if a declaring backend
973
974
  doesn't mount it (replacing the old runtime 404-sniff-skip).
974
- `oversized_reject_closes_connection` picks the arm of the body-size
975
- smuggling probe (`true` Node/Deno/Rust — the connection closes after an
976
- oversized-body 413, at once on Node and once the declared body has arrived
977
- on Deno, so the pipelined request goes unanswered; `false` Bun —
978
- `Bun.serve` keeps the connection and serves the next request, correctly
979
- framed, so the suite requires no desync there instead: exactly the 413 and
980
- the reply to the pipelined request, which a backend that closes fails).
975
+ No flag covers the body-size smuggling probe: every backend closes the
976
+ connection after an oversized-body 413 (at once on Node, Bun and the Rust
977
+ spine, once the declared body has arrived on Deno), so the pipelined request
978
+ goes unanswered. Bun 1.4.3 is the tested floor — an older Bun kept the
979
+ connection and served the next request, correctly framed, and fails the probe.
981
980
  `ws_handshake_pipelining` gates the WS round-trip suite's
982
981
  frames-with-the-handshake case (`true` Node/Deno/Rust — frames written in the
983
- same TCP write as the upgrade request reach the socket; `false` Bun, whose
984
- HTTP parser answers such a request `400`, and `false` in-process, where there
985
- is no socket to write raw bytes to).
982
+ same TCP write as the upgrade request reach the socket; `false` Bun, which
983
+ answers such a request `400` when the upgrade follows an asynchronous check,
984
+ as it does behind auth, and `false` in-process, where there is no socket to
985
+ write raw bytes to).
986
986
  `ws_account_actions` (the WS endpoint mounts the account actions — `true` on
987
987
  fuz_app's own spine presets, `false` in the family defaults) gates the WS
988
988
  round-trip suite's self-revocation case and, with `rpc_path`, its
@@ -1345,7 +1345,7 @@ an audit row, an admin account listing, and an offer's `created_at` /
1345
1345
  `expires_at`. `cross_backend/setup.ts` carries the four bases every
1346
1346
  imperative cross suite aliases: `CrossSuiteOptions` (`setup_test` alone),
1347
1347
  `CapabilityGatedCrossSuiteOptions` (+ `capabilities` — `ready`),
1348
- `RpcPathCrossSuiteOptions` (+ `rpc_path?` — origin, body-size, actor
1348
+ `RpcPathCrossSuiteOptions` (+ `rpc_path?` — origin, body-size (which adds `base_url`), actor
1349
1349
  lookup/search, app settings, testing backdoor), and the intersection
1350
1350
  `RpcPathCapabilityGatedCrossSuiteOptions` (every `cell_*` suite, query-shape,
1351
1351
  token-scope surface, fact-serving, account lifecycle). A suite takes the
@@ -1358,7 +1358,8 @@ the option type states whether the bundle is read.
1358
1358
  edit/read/delete → 404 IDOR mask; admin reaches any; dup active `path` → 409;
1359
1359
  `path` write by non-admin → 403 on create + update; `cell_get` with no
1360
1360
  id/path → `invalid_params`; null-auth `cell_list` `created_by` →
1361
- `invalid_params`).
1361
+ `invalid_params`; a `kind` at `CELL_KIND_LENGTH_MAX` stored whole, one
1362
+ UTF-16 code unit past it → `invalid_params` on create and as a list filter).
1362
1363
  - **`describe_cell_relations_cross_tests`** (gates on
1363
1364
  `capabilities.cell_relations`) — the verbs beyond CRUD: grant lifecycle
1364
1365
  (actor-shaped editor grant enables edit, manage-tier `cell_grant_list`,
@@ -1463,6 +1464,21 @@ are `src/test/auth/cell_crud_parity.db.test.ts`
1463
1464
  write as the upgrade request, so before the connection can have been
1464
1465
  admitted). Small unfragmented text frames only. Helpers `is_raw_ws_close`,
1465
1466
  `raw_ws_close_code`, `RAW_WS_OPCODE_TEXT` / `_CLOSE`.
1467
+ - `testing/transports/raw_http.ts` — `raw_http_exchange(base_url, request_bytes, {read_timeout_ms, until?})`
1468
+ → `RawHttpExchange` (`{received, closed_by_server}`): writes caller-built
1469
+ HTTP/1.1 bytes to a raw `node:net` socket once and reads what the server
1470
+ sends until it closes (`until: 'close'`, the default), until one whole
1471
+ response has arrived (`until: 'response'` — for a server that answers and
1472
+ keeps the connection), or until the read timeout. For what `fetch` can't
1473
+ send: two requests in one write, duplicate or malformed headers, a request
1474
+ head whose body never follows, an unterminated chunked body. Write errors are
1475
+ swallowed, and a write error ends the socket — so a caller that must read a
1476
+ refusal reliably sends nothing the server will leave unread. Helpers
1477
+ `raw_http_parse_response` (the first response, `null` until its head and
1478
+ framed body are whole), `raw_http_count_responses` (unanchored status-line
1479
+ count, so a response concatenated after a body is seen),
1480
+ `raw_http_first_status`. Shared by the body-size, body-size smuggling, and
1481
+ credential-header suites.
1466
1482
  - `testing/transports/sse_frame_reader.ts` — `create_sse_frame_reader(reader, default_timeout_ms?)`,
1467
1483
  the transport-agnostic SSE framing core over a
1468
1484
  `ReadableStreamDefaultReader<Uint8Array>`: `\n\n` framing, per-read timeout,
@@ -1667,8 +1683,9 @@ reads the audit log behind `_testing_drain_effects`). fuz_app's own wiring is
1667
1683
  imperative round-trip on `account_token_create`'s required `lifetime` union:
1668
1684
  a `{kind:'ttl', days}` mint returns and stores a bounded `expires_at`
1669
1685
  (create output + `account_token_list` read-back), `{kind:'eternal'}` stores
1670
- NULL, and an omitted or out-of-range lifetime 400s identically on both
1671
- spines. Exists because the action-manifest parity gate captures no param
1686
+ NULL, an omitted or out-of-range lifetime 400s identically on both
1687
+ spines, and a `name` at `API_TOKEN_NAME_LENGTH_MAX` mints and reads back whole
1688
+ while one UTF-16 code unit past it is `invalid_params`. Exists because the action-manifest parity gate captures no param
1672
1689
  schemas while the Rust spine hand-parses params — a spine that silently
1673
1690
  dropped the field would answer the same mint with a 200 and an eternal
1674
1691
  token; only the stored-state read-back catches it. Ungated (mint + list are
@@ -1705,8 +1722,8 @@ drift-guarded by `src/test/cross_backend/spine_expected_schema.db.test.ts`
1705
1722
 
1706
1723
  `describe_testing_backdoor_cross_tests({setup_test, rpc_path?, ws?})` —
1707
1724
  the negative-credential parity suite for the `_testing_*` backdoor actions.
1708
- For each `_testing_*` action except `_testing_drain_effects` (the privileged
1709
- writes, the connection flood, and the introspection reads) it fires three principals over real HTTP: **anonymous** →
1725
+ For each `_testing_*` action (the privileged writes, the audit barrier, the
1726
+ connection flood, and the introspection reads) it fires three principals over real HTTP: **anonymous** →
1710
1727
  401 `unauthenticated`, **session** → 403 `forbidden` `credential_type_required`,
1711
1728
  **bearer** → 403 `forbidden` `credential_type_required` — and, when `ws`
1712
1729
  (`{base_url, ws_path, origin?}`) is passed, sends each on a socket opened with
@@ -1,26 +1,39 @@
1
1
  import './assert_dev_env.js';
2
- import type { AuditEmitter } from '../auth/audit_emitter.js';
2
+ import { type AuditEmitter } from '../auth/audit_emitter.js';
3
3
  import type { AuditLogInput } from '../auth/audit_log_schema.js';
4
4
  /**
5
5
  * Register per-test `beforeEach` + `afterEach` hooks that catch any audit
6
6
  * emission with a metadata shape that fails its `audit_metadata_schemas`
7
- * entry, or an `event_type` not present in the active `AuditLogConfig`.
7
+ * entry, an `event_type` not present in the active `AuditLogConfig`, or a
8
+ * row the emitter failed to write.
8
9
  *
9
10
  * The production validation in `query_audit_log` is fail-open — it bumps
10
11
  * process-wide counters and proceeds, so a regression that emits an
11
12
  * undeclared metadata field or a typo'd event-type lands a row that
12
- * passes downstream queries but breaks forensics. Tests that exercise
13
- * audit emits should fail loudly when this happens.
13
+ * passes downstream queries but breaks forensics. The emitter's pool write
14
+ * is fail-open too: a refused `INSERT` is logged and counted, the request
15
+ * succeeds, and the row is gone. Tests that exercise audit emits should fail
16
+ * loudly when either happens.
14
17
  *
15
18
  * Call at the top of every `describe` / `describe_db` block that fires
16
19
  * audit writes through `deps.audit.emit`. Resets counters before each
17
- * test and asserts zero on completion.
20
+ * test and asserts zero on completion. A test that makes a write fail on
21
+ * purpose calls `reset_audit_write_failures` (`auth/audit_emitter.ts`) once
22
+ * it has asserted the count.
18
23
  *
19
24
  * Pair with `await_pending_effects: true` (the default for
20
25
  * `create_test_app`) so fire-and-forget audit writes have completed by
21
26
  * the time the after-each check observes counter state.
22
27
  */
23
28
  export declare const install_audit_drift_guard: () => void;
29
+ /**
30
+ * Assert the process-wide audit counters are zero — the check
31
+ * `install_audit_drift_guard` runs after each test. Exported for a test that
32
+ * manages the counters itself.
33
+ *
34
+ * @throws AssertionError naming the first counter that is not zero
35
+ */
36
+ export declare const assert_no_audit_drift: () => void;
24
37
  /**
25
38
  * Pair returned by `create_recording_audit_emitter` — the
26
39
  * `AuditEmitter` to inject as `deps.audit`, plus the shared `calls`
@@ -1 +1 @@
1
- {"version":3,"file":"audit_drift_guard.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/audit_drift_guard.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAU7B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,KAAK,EAAiB,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAEhF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,yBAAyB,QAAO,IAiB5C,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACrC,OAAO,EAAE,YAAY,CAAC;IACtB,KAAK,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,eAAO,MAAM,8BAA8B,GAC1C,YAAY,KAAK,CAAC,aAAa,CAAC,KAC9B,qBAqCF,CAAC"}
1
+ {"version":3,"file":"audit_drift_guard.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/audit_drift_guard.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAU7B,OAAO,EAGN,KAAK,YAAY,EACjB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAiB,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAEhF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,yBAAyB,QAAO,IAO5C,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,QAAO,IAgBxC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACrC,OAAO,EAAE,YAAY,CAAC;IACtB,KAAK,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,eAAO,MAAM,8BAA8B,GAC1C,YAAY,KAAK,CAAC,aAAa,CAAC,KAC9B,qBAqCF,CAAC"}
@@ -1,20 +1,26 @@
1
1
  import './assert_dev_env.js';
2
2
  import { assert, beforeEach, afterEach } from 'vitest';
3
3
  import { get_audit_metadata_validation_failures, get_audit_unknown_event_type_failures, reset_audit_metadata_validation_failures, reset_audit_unknown_event_type_failures } from '../auth/audit_log_queries.js';
4
+ import { get_audit_write_failures, reset_audit_write_failures } from '../auth/audit_emitter.js';
4
5
  /**
5
6
  * Register per-test `beforeEach` + `afterEach` hooks that catch any audit
6
7
  * emission with a metadata shape that fails its `audit_metadata_schemas`
7
- * entry, or an `event_type` not present in the active `AuditLogConfig`.
8
+ * entry, an `event_type` not present in the active `AuditLogConfig`, or a
9
+ * row the emitter failed to write.
8
10
  *
9
11
  * The production validation in `query_audit_log` is fail-open — it bumps
10
12
  * process-wide counters and proceeds, so a regression that emits an
11
13
  * undeclared metadata field or a typo'd event-type lands a row that
12
- * passes downstream queries but breaks forensics. Tests that exercise
13
- * audit emits should fail loudly when this happens.
14
+ * passes downstream queries but breaks forensics. The emitter's pool write
15
+ * is fail-open too: a refused `INSERT` is logged and counted, the request
16
+ * succeeds, and the row is gone. Tests that exercise audit emits should fail
17
+ * loudly when either happens.
14
18
  *
15
19
  * Call at the top of every `describe` / `describe_db` block that fires
16
20
  * audit writes through `deps.audit.emit`. Resets counters before each
17
- * test and asserts zero on completion.
21
+ * test and asserts zero on completion. A test that makes a write fail on
22
+ * purpose calls `reset_audit_write_failures` (`auth/audit_emitter.ts`) once
23
+ * it has asserted the count.
18
24
  *
19
25
  * Pair with `await_pending_effects: true` (the default for
20
26
  * `create_test_app`) so fire-and-forget audit writes have completed by
@@ -24,11 +30,21 @@ export const install_audit_drift_guard = () => {
24
30
  beforeEach(() => {
25
31
  reset_audit_metadata_validation_failures();
26
32
  reset_audit_unknown_event_type_failures();
33
+ reset_audit_write_failures();
27
34
  });
28
- afterEach(() => {
29
- assert.strictEqual(get_audit_metadata_validation_failures(), 0, 'audit metadata failed schema validation — see audit_log_schema.audit_metadata_schemas');
30
- assert.strictEqual(get_audit_unknown_event_type_failures(), 0, 'audit emitted an unknown event_type — see AUDIT_EVENT_TYPES');
31
- });
35
+ afterEach(assert_no_audit_drift);
36
+ };
37
+ /**
38
+ * Assert the process-wide audit counters are zero — the check
39
+ * `install_audit_drift_guard` runs after each test. Exported for a test that
40
+ * manages the counters itself.
41
+ *
42
+ * @throws AssertionError naming the first counter that is not zero
43
+ */
44
+ export const assert_no_audit_drift = () => {
45
+ assert.strictEqual(get_audit_metadata_validation_failures(), 0, 'audit metadata failed schema validation — see audit_log_schema.audit_metadata_schemas');
46
+ assert.strictEqual(get_audit_unknown_event_type_failures(), 0, 'audit emitted an unknown event_type — see AUDIT_EVENT_TYPES');
47
+ assert.strictEqual(get_audit_write_failures(), 0, 'an audit row failed to write and was lost — the emitter logged the database error');
32
48
  };
33
49
  /**
34
50
  * Build a no-op `AuditEmitter` that records every `emit`, `emit_pool`, and
@@ -43,27 +43,20 @@ import type { RequestResponseActionSpec } from '../../actions/action_spec.js';
43
43
  * serializes an explicit `null`, so a missing key is drift, not a default.
44
44
  */
45
45
  export declare const ActionManifestEntry: z.ZodObject<{
46
- /** RPC method name (`spec.method`). */
47
46
  method: z.ZodString;
48
- /** `true` when the action declares `side_effects` (mutation; POST-only on the RPC transport). */
49
47
  side_effects: z.ZodBoolean;
50
- /** The `account` auth axis. */
51
48
  account: z.ZodEnum<{
52
49
  optional: "optional";
53
50
  none: "none";
54
51
  required: "required";
55
52
  }>;
56
- /** The `actor` auth axis. */
57
53
  actor: z.ZodEnum<{
58
54
  optional: "optional";
59
55
  none: "none";
60
56
  required: "required";
61
57
  }>;
62
- /** Required roles (any-of), sorted; `[]` when the action declares no role gate. */
63
58
  roles: z.ZodArray<z.ZodString>;
64
- /** Permitted credential channels, sorted; `[]` when ungated (any credential). */
65
59
  credential_types: z.ZodArray<z.ZodString>;
66
- /** Per-action rate-limit class, or `null` when the action declares none. */
67
60
  rate_limit: z.ZodNullable<z.ZodEnum<{
68
61
  account: "account";
69
62
  ip: "ip";
@@ -74,27 +67,20 @@ export type ActionManifestEntry = z.infer<typeof ActionManifestEntry>;
74
67
  /** The full action manifest — every entry, sorted by `method`. */
75
68
  export declare const ActionManifest: z.ZodObject<{
76
69
  methods: z.ZodArray<z.ZodObject<{
77
- /** RPC method name (`spec.method`). */
78
70
  method: z.ZodString;
79
- /** `true` when the action declares `side_effects` (mutation; POST-only on the RPC transport). */
80
71
  side_effects: z.ZodBoolean;
81
- /** The `account` auth axis. */
82
72
  account: z.ZodEnum<{
83
73
  optional: "optional";
84
74
  none: "none";
85
75
  required: "required";
86
76
  }>;
87
- /** The `actor` auth axis. */
88
77
  actor: z.ZodEnum<{
89
78
  optional: "optional";
90
79
  none: "none";
91
80
  required: "required";
92
81
  }>;
93
- /** Required roles (any-of), sorted; `[]` when the action declares no role gate. */
94
82
  roles: z.ZodArray<z.ZodString>;
95
- /** Permitted credential channels, sorted; `[]` when ungated (any credential). */
96
83
  credential_types: z.ZodArray<z.ZodString>;
97
- /** Per-action rate-limit class, or `null` when the action declares none. */
98
84
  rate_limit: z.ZodNullable<z.ZodEnum<{
99
85
  account: "account";
100
86
  ip: "ip";
@@ -1 +1 @@
1
- {"version":3,"file":"action_manifest.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/action_manifest.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAiB,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,8BAA8B,CAAC;AAE9E;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB;IAC/B,uCAAuC;;IAEvC,iGAAiG;;IAEjG,+BAA+B;;;;;;IAE/B,6BAA6B;;;;;;IAE7B,mFAAmF;;IAEnF,iFAAiF;;IAEjF,4EAA4E;;;;;;kBAE3E,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,kEAAkE;AAClE,eAAO,MAAM,cAAc;;QAlB1B,uCAAuC;;QAEvC,iGAAiG;;QAEjG,+BAA+B;;;;;;QAE/B,6BAA6B;;;;;;QAE7B,mFAAmF;;QAEnF,iFAAiF;;QAEjF,4EAA4E;;;;;;;kBAQ3E,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAM5D;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,GAAI,MAAM;IAC3C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,UAAU,CAAC,EAAE,YAAY,CAAC;CACnC,KAAG,mBAQF,CAAC;AAMH;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,GACjC,OAAO,aAAa,CACnB,IAAI,CAAC,yBAAyB,EAAE,QAAQ,GAAG,MAAM,GAAG,cAAc,GAAG,YAAY,CAAC,CAClF,KACC,cAED,CAAC"}
1
+ {"version":3,"file":"action_manifest.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/action_manifest.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAiB,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,8BAA8B,CAAC;AAE9E;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;kBAe9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,kEAAkE;AAClE,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;kBAEzB,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAM5D;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,GAAI,MAAM;IAC3C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,UAAU,CAAC,EAAE,YAAY,CAAC;CACnC,KAAG,mBAQF,CAAC;AAMH;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,GACjC,OAAO,aAAa,CACnB,IAAI,CAAC,yBAAyB,EAAE,QAAQ,GAAG,MAAM,GAAG,cAAc,GAAG,YAAY,CAAC,CAClF,KACC,cAED,CAAC"}
@@ -2,11 +2,24 @@ import '../assert_dev_env.js';
2
2
  import type { RpcPathCrossSuiteOptions } from './setup.js';
3
3
  /**
4
4
  * Options for the body-size parity suite — the standard RPC-dispatched
5
- * cross-suite shape (`setup_test` / `rpc_path`); aliases the shared
6
- * `RpcPathCrossSuiteOptions` rather than minting a duplicate. The limit is
7
- * on every spine, so no case is capability-gated and the flag bundle is not
8
- * on the shape.
5
+ * cross-suite shape (`setup_test` / `rpc_path`) plus where the backend's
6
+ * socket is. The limit is on every spine, so no case is capability-gated and
7
+ * the flag bundle is not on the shape.
9
8
  */
10
- export type BodySizeCrossTestOptions = RpcPathCrossSuiteOptions;
9
+ export interface BodySizeCrossTestOptions extends RpcPathCrossSuiteOptions {
10
+ /**
11
+ * Base URL the spawned backend is reachable at (e.g. `http://localhost:1178`),
12
+ * or `null` for an in-process run, which has no socket. Given a URL, the
13
+ * over-cap cases write to the socket directly and send only the bytes the
14
+ * refusal needs; with `null` they send whole bodies through the fixture's
15
+ * transport.
16
+ *
17
+ * Pass the URL whenever the fixture's transport reaches a real socket.
18
+ * `null` there uploads each whole over-cap body through `fetch`, where the
19
+ * write races the close that follows the 413 and the case fails some of the
20
+ * time.
21
+ */
22
+ readonly base_url: string | null;
23
+ }
11
24
  export declare const describe_body_size_cross_tests: (options: BodySizeCrossTestOptions) => void;
12
25
  //# sourceMappingURL=body_size.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"body_size.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/body_size.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA0D9B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAG3D;;;;;;GAMG;AACH,MAAM,MAAM,wBAAwB,GAAG,wBAAwB,CAAC;AAmDhE,eAAO,MAAM,8BAA8B,GAAI,SAAS,wBAAwB,KAAG,IA0ClF,CAAC"}
1
+ {"version":3,"file":"body_size.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/body_size.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA8F9B,OAAO,KAAK,EAAE,wBAAwB,EAAe,MAAM,YAAY,CAAC;AAGxE;;;;;GAKG;AACH,MAAM,WAAW,wBAAyB,SAAQ,wBAAwB;IACzE;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AA2HD,eAAO,MAAM,8BAA8B,GAAI,SAAS,wBAAwB,KAAG,IAyLlF,CAAC"}