@fuzdev/fuz_app 0.89.0 → 0.91.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/auth/CLAUDE.md +7 -0
- package/dist/auth/account_action_specs.d.ts +1 -1
- package/dist/auth/account_route_schema.d.ts +2 -2
- package/dist/auth/account_schema.d.ts +3 -3
- package/dist/auth/admin_action_specs.d.ts +8 -8
- package/dist/auth/all_cell_actions.d.ts +35 -0
- package/dist/auth/all_cell_actions.d.ts.map +1 -0
- package/dist/auth/all_cell_actions.js +32 -0
- package/dist/auth/audit_log_schema.d.ts +1 -1
- package/dist/auth/invite_schema.d.ts +2 -2
- package/dist/auth/signup_route_schema.d.ts +2 -2
- package/dist/auth/signup_route_schema.d.ts.map +1 -1
- package/dist/auth/signup_route_schema.js +2 -2
- package/dist/primitive_schemas.d.ts +27 -1
- package/dist/primitive_schemas.d.ts.map +1 -1
- package/dist/primitive_schemas.js +55 -2
- package/dist/server/env.d.ts +1 -1
- package/dist/server/env.d.ts.map +1 -1
- package/dist/server/env.js +15 -3
- package/dist/testing/CLAUDE.md +135 -10
- package/dist/testing/app_server.d.ts +16 -5
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +3 -1
- package/dist/testing/cross_backend/action_manifest.d.ts +113 -0
- package/dist/testing/cross_backend/action_manifest.d.ts.map +1 -0
- package/dist/testing/cross_backend/action_manifest.js +89 -0
- package/dist/testing/cross_backend/action_manifest_parity.d.ts +67 -0
- package/dist/testing/cross_backend/action_manifest_parity.d.ts.map +1 -0
- package/dist/testing/cross_backend/action_manifest_parity.js +88 -0
- package/dist/testing/cross_backend/capabilities.d.ts +47 -30
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/capabilities.js +19 -4
- package/dist/testing/cross_backend/default_backend_configs.d.ts +31 -15
- package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_backend_configs.js +30 -14
- package/dist/testing/cross_backend/default_spine_surface.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_spine_surface.js +6 -0
- package/dist/testing/cross_backend/full_spine_mount.d.ts +81 -0
- package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -0
- package/dist/testing/cross_backend/full_spine_mount.js +49 -0
- package/dist/testing/cross_backend/identity_parity.d.ts +13 -0
- package/dist/testing/cross_backend/identity_parity.d.ts.map +1 -0
- package/dist/testing/cross_backend/identity_parity.js +465 -0
- package/dist/testing/cross_backend/method_coverage.d.ts +71 -0
- package/dist/testing/cross_backend/method_coverage.d.ts.map +1 -0
- package/dist/testing/cross_backend/method_coverage.js +89 -0
- package/dist/testing/cross_backend/setup.d.ts +21 -0
- package/dist/testing/cross_backend/setup.d.ts.map +1 -1
- package/dist/testing/cross_backend/setup.js +33 -1
- package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_backdoor.js +9 -5
- package/dist/testing/cross_backend/testing_reset_actions.d.ts +76 -2
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.js +63 -2
- package/dist/testing/schema_generators.d.ts.map +1 -1
- package/dist/testing/schema_generators.js +7 -0
- package/package.json +2 -2
package/dist/testing/CLAUDE.md
CHANGED
|
@@ -222,7 +222,37 @@ test-only by construction.
|
|
|
222
222
|
- `assert_schema_snapshots_equal(a, b, labels?)` — throws on drift with a fully-formatted diff message.
|
|
223
223
|
- `SchemaDiff` — tagged-union per drift kind: `table_only_in`, `column_only_in`, `column_field_differs`, `index_only_in`, `index_definition_differs`, `constraint_only_in`, `constraint_differs`, `sequence_only_in`, `sequence_data_type_differs`, `enum_only_in`, `enum_labels_differ` (enum labels compared positionally — declared order is significant).
|
|
224
224
|
|
|
225
|
-
fuz_app's own spine gates this cross-process via the `
|
|
225
|
+
fuz_app's own spine gates this cross-process via the `cross_backend_parity` project (`schema_parity.cross.test.ts` + the dual-spawn `global_setup_schema_parity.ts`), diffing the TS spine ↔ `testing_spine_stub` full schema — `npm run test:cross:parity`. The forge has its own deno↔rust parity gate.
|
|
226
|
+
|
|
227
|
+
## Cross-impl RPC-manifest parity
|
|
228
|
+
|
|
229
|
+
The action-surface sibling of schema parity. Where schema parity diffs the live
|
|
230
|
+
_database_ shape, this diffs the live _RPC registry_ shape — proving the TS
|
|
231
|
+
spine and the Rust `testing_spine_stub` mount the same method set with the same
|
|
232
|
+
per-method auth shape.
|
|
233
|
+
|
|
234
|
+
- `cross_backend/action_manifest.ts` — `ActionManifest` / `ActionManifestEntry`
|
|
235
|
+
Zod schema (the `_testing_action_manifest` wire validator) + `build_action_manifest(specs)`,
|
|
236
|
+
which normalizes each spec to `{method, side_effects, account, actor, roles,
|
|
237
|
+
credential_types}` (auth axes pass through; `roles` / `credential_types` flatten
|
|
238
|
+
to sorted arrays). Twin of `schema_introspect.ts`. Excludes the protocol actions
|
|
239
|
+
(`heartbeat` / `cancel`) by construction — the two impls organize them
|
|
240
|
+
differently (TS WS-only; Rust on one shared registry), so the caller passes a
|
|
241
|
+
protocol-free list.
|
|
242
|
+
- `cross_backend/action_manifest_parity.ts` — `diff_action_manifests` /
|
|
243
|
+
`format_action_manifest_diffs` / `assert_action_manifests_equal` (twin of
|
|
244
|
+
`schema_parity.ts`) — asserts exact method-set + per-method auth-shape parity.
|
|
245
|
+
- `cross_backend/setup.ts` `capture_action_manifest(handle)` — dumps a backend's
|
|
246
|
+
manifest over the daemon-token `_testing_action_manifest` channel (twin of
|
|
247
|
+
`capture_schema_snapshot`).
|
|
248
|
+
|
|
249
|
+
It runs under the same dual-spawn `cross_backend_parity` project (one TS-spine +
|
|
250
|
+
Rust-stub spawn serves both gates). It complements the in-repo
|
|
251
|
+
`spine_method_coverage` reconciliation gate (which proves _mounted ⟹ covered_);
|
|
252
|
+
this proves _TS-mount-set ≡ Rust-mount-set_. The gate asserts **exact** parity —
|
|
253
|
+
every mounted method and its full auth shape (side-effects + the four auth axes)
|
|
254
|
+
must match across the two impls, with no allowlisted divergences (see
|
|
255
|
+
`action_manifest_parity.cross.test.ts`).
|
|
226
256
|
|
|
227
257
|
**Cross-impl gate pattern** — a dual-impl consumer running two backends
|
|
228
258
|
(a TS Hono server and a Rust spine server) against a shared schema, plus
|
|
@@ -840,7 +870,12 @@ source of truth for wire-shape conformance.
|
|
|
840
870
|
whose `RoleSpec.grant_paths` don't include `'admin'` reject loudly at
|
|
841
871
|
offer-create time (`ERROR_ROLE_GRANT_OFFER_ROLE_NOT_GRANTABLE`); those
|
|
842
872
|
roles must be seeded via `extra_accounts` at bootstrap-equivalent time
|
|
843
|
-
instead.
|
|
873
|
+
instead. `create_account({email})` threads an email onto the signup body
|
|
874
|
+
(the username-scoped invite matches regardless of email), so the account
|
|
875
|
+
lands with an email set — the hook the username-or-email login parity suite
|
|
876
|
+
uses; the in-process path sets it directly on the inserted row. Must be a
|
|
877
|
+
valid, per-test-unique `Email` (the `LOWER(email)` partial-unique index
|
|
878
|
+
rejects collisions). Caller-supplied `username`s pass through _as-is_ now that the DB
|
|
844
879
|
wipes between tests — hardcoded names like `'user_two'` work and the
|
|
845
880
|
earlier uniquification prefix is gone. Every `TestFixture` also exposes
|
|
846
881
|
`fresh_transport({origin?: string | null})` — cookie-jar-free probe;
|
|
@@ -856,18 +891,26 @@ source of truth for wire-shape conformance.
|
|
|
856
891
|
deliver it.
|
|
857
892
|
|
|
858
893
|
- `testing/cross_backend/capabilities.ts` — `BackendCapabilities` vocabulary
|
|
859
|
-
(
|
|
860
|
-
`
|
|
861
|
-
`
|
|
894
|
+
(the **gating** flags `ws` / `sse` / `cell_crud` / `cell_relations` /
|
|
895
|
+
`account_lifecycle` / `fact_serving` / `ready` / `account_status` /
|
|
896
|
+
`oversized_reject_closes_connection` — each has a `test_if` reader),
|
|
862
897
|
`test_if(cond, name, fn)`
|
|
863
|
-
for capability-gated cases, and `in_process_capabilities` preset.
|
|
898
|
+
for capability-gated cases, and `in_process_capabilities` preset. The
|
|
899
|
+
non-gating wiring facts (`bearer_auth` / `trusted_proxy` / `login_rate_limit`,
|
|
900
|
+
no `test_if` readers) live in the parallel `BackendShapeNotes` record
|
|
901
|
+
(`in_process_shape_notes`; `ts_default_shape_notes` / `rust_default_shape_notes`
|
|
902
|
+
in `default_backend_configs.ts`) — split out so the capability type stops
|
|
903
|
+
claiming gating it doesn't do. `cell_crud`
|
|
864
904
|
gates the CRUD parity suite, `cell_relations` the relation / ACL / audit
|
|
865
905
|
parity suite — both `true` on every backend that live-mounts the full cell
|
|
866
906
|
surface (TS spine binary, in-process app, Rust stub). A backend mounting only
|
|
867
907
|
plain CRUD would declare `cell_crud: true, cell_relations: false`.
|
|
868
908
|
`account_lifecycle` gates `describe_account_lifecycle_cross_tests` (the
|
|
869
|
-
`account_delete` / `account_undelete` / `account_purge` parity suite)
|
|
870
|
-
|
|
909
|
+
`account_delete` / `account_undelete` / `account_purge` parity suite); unlike
|
|
910
|
+
cells those verbs **are** on the declared surface (in `create_admin_actions`,
|
|
911
|
+
auto-enumerated for shape/auth) — the flag gates the behavioral parity the
|
|
912
|
+
generic round-trip can't drive (it can't delete the subject). `true` on every
|
|
913
|
+
spine. `fact_serving`
|
|
871
914
|
gates `describe_fact_serving_cross_tests` (the cell-scoped per-reference +
|
|
872
915
|
admin-only bare-hash fact-serving parity suite); like cells it stays off the
|
|
873
916
|
declared surface and is `true` on every spine that mounts the serve routes +
|
|
@@ -1186,6 +1229,16 @@ in-process legs (plain `gro test`) are `src/test/auth/cell_crud_parity.db.test.t
|
|
|
1186
1229
|
payload-expiry tests never reached. Daemon-token-gated like its siblings; the
|
|
1187
1230
|
Rust mirror is `fuz_testing::create_testing_mint_session_action_spec`.
|
|
1188
1231
|
|
|
1232
|
+
Also in the module — but **not** bundled by `create_testing_actions` —
|
|
1233
|
+
`_testing_action_manifest` (+ `create_testing_action_manifest_action(mounted)`):
|
|
1234
|
+
dumps the live RPC registry as a normalized `ActionManifest` for the
|
|
1235
|
+
cross-impl manifest-parity gate (see §Cross-impl RPC-manifest parity). It must
|
|
1236
|
+
enumerate _every_ mounted method, which `create_testing_actions` (one
|
|
1237
|
+
sub-factory) can't see, so it's appended at the full-mount layer
|
|
1238
|
+
(`full_spine_mount.ts` `build_full_spine_rpc_actions`), closing over the
|
|
1239
|
+
complete action list plus its own spec. Daemon-token-gated like its siblings;
|
|
1240
|
+
the Rust mirror is `fuz_testing::create_testing_action_manifest_action_spec`.
|
|
1241
|
+
|
|
1189
1242
|
### Origin verification parity — `cross_backend/origin.ts`
|
|
1190
1243
|
|
|
1191
1244
|
`describe_origin_cross_tests({setup_test, capabilities, rpc_path?})` — the
|
|
@@ -1247,6 +1300,53 @@ in-process spec-level gate test (`src/test/testing/testing_actions_auth.test.ts`
|
|
|
1247
1300
|
asserting each spec declares `credential_types: ['daemon_token']`) + the
|
|
1248
1301
|
`assert_no_testing_methods` surface invariant.
|
|
1249
1302
|
|
|
1303
|
+
### Identity-primitive parity — `cross_backend/identity_parity.ts`
|
|
1304
|
+
|
|
1305
|
+
`describe_identity_parity_cross_tests({setup_test, login_path?, signup_path?})` —
|
|
1306
|
+
the imperative suite pinning the `primitive_schemas` twins (`Username`,
|
|
1307
|
+
`UsernameProvided`, `Email`) + login/signup input handling end-to-end over real
|
|
1308
|
+
HTTP (flat REST `/login` + `/signup`, so imperative, not a `conformance_table`
|
|
1309
|
+
row). Six facets, each a TS↔Rust reimplementation pair that the spec-derived
|
|
1310
|
+
round-trip / conformance suites never vary:
|
|
1311
|
+
|
|
1312
|
+
- **Login canonicalization** — case-insensitive + whitespace-trim lookup
|
|
1313
|
+
(`UsernameProvided` `.trim().toLowerCase()` + `LOWER(username)`), plus a
|
|
1314
|
+
Turkish-`İ` homograph **no-Unicode-fold-collision** negative.
|
|
1315
|
+
- **Username-or-email login** — an account created with an email logs in via
|
|
1316
|
+
that email, case-insensitively both directions; username login still works;
|
|
1317
|
+
non-existent email → 401. This is the corner that surfaced and closed the Rust
|
|
1318
|
+
spine's username-only divergence (it now mirrors TS's
|
|
1319
|
+
`query_account_by_username_or_email`).
|
|
1320
|
+
- **Login input validation** — malformed login input → 400 `invalid_request_body`
|
|
1321
|
+
on every spine: whitespace-only username (empty after trim), over-long username
|
|
1322
|
+
(> 255), empty password, unknown body key (strict object). TS runs the full
|
|
1323
|
+
`LoginInput` Zod schema; the Rust spine enforces the same shape in
|
|
1324
|
+
`account_login` (closed a cluster where three of these fell through to a
|
|
1325
|
+
lookup-miss 401 on Rust and a whitespace-only identifier 401'd on TS). Asserted
|
|
1326
|
+
by error reason, not just status.
|
|
1327
|
+
- **ASCII-only creation invariant** — non-ASCII username → 400 at signup.
|
|
1328
|
+
- **Length + format regex parity** — both `[USERNAME_LENGTH_MIN,
|
|
1329
|
+
USERNAME_LENGTH_MAX]` = `[3, 39]` boundary edges (just-outside → 400,
|
|
1330
|
+
just-inside → 403 no-matching-invite) and the creation regex shape (leading
|
|
1331
|
+
non-letter / trailing punctuation / embedded disallowed char → 400; mid-string
|
|
1332
|
+
`_`/`-` accepted), catching an off-by-one or position bug in the TS Zod
|
|
1333
|
+
`.min/.max/.regex` vs the Rust byte-scan `is_valid_username_for_creation`.
|
|
1334
|
+
- **Email format (creation)** — the optional signup `email` validated to the
|
|
1335
|
+
loose `local@domain.tld` shape (TS `Email` `^[^\s@]+@[^\s@]+\.[^\s@]+$` + 254-byte
|
|
1336
|
+
bound, Rust `is_valid_email`): malformed (no `@` / no domain dot / empty
|
|
1337
|
+
side / dot at a domain edge / whitespace / over-length) → 400, well-formed →
|
|
1338
|
+
403 no-invite. Accepted siblings include the single-char-TLD `a@b.c` that
|
|
1339
|
+
Zod's `z.email()` rejects — the deliberately-looser shared rule. **Closed a
|
|
1340
|
+
real divergence**: the Rust spine previously length-checked the email only,
|
|
1341
|
+
accepting any non-empty string TS rejected.
|
|
1342
|
+
|
|
1343
|
+
Ungated (login + signup are on every spine). Runs both legs via the shared
|
|
1344
|
+
`{setup_test}` protocol: in-process (`identity_parity.db.test.ts`, plain
|
|
1345
|
+
`gro test`) and cross-process (`identity_parity.cross.test.ts`). `$lib`-free.
|
|
1346
|
+
The accepted-case 403s rely on `create_spine_route_specs` zeroing
|
|
1347
|
+
`signup_fail_floor_ms` (mirroring `login_fail_floor_ms: 0`) so they don't each
|
|
1348
|
+
pay the 250ms denial floor.
|
|
1349
|
+
|
|
1250
1350
|
### Building a TS test-server binary — `testing_server_core.ts` + adapters
|
|
1251
1351
|
|
|
1252
1352
|
The reusable shape for standing up a **spawnable TS** cross-process test
|
|
@@ -1257,10 +1357,35 @@ re-roll the serve / daemon-info / WS-attach / drain boilerplate:
|
|
|
1257
1357
|
- `testing/cross_backend/testing_server_node.ts` — `create_node_testing_adapter()` (`@hono/node-server` + `@hono/node-ws`). Optional peer deps (like `ws`); only test binaries import them.
|
|
1258
1358
|
- `testing/cross_backend/testing_server_deno.ts` — `create_deno_testing_adapter()` (`Deno.serve` + `hono/deno`; `Deno` declared locally so it typechecks under the Node toolchain). Spawn the entry with `--sloppy-imports` (Deno doesn't do `.js`→`.ts`; Gro's loader does, so the Node path needs no flag).
|
|
1259
1359
|
- `testing/cross_backend/testing_server_bun.ts` — `create_bun_testing_adapter()` (`Bun.serve` + `hono/bun`'s module-level `upgradeWebSocket` + `websocket`; `Bun.serve` declared locally so it typechecks under the Node toolchain). **No extra deps** (`hono/bun` ships with `hono`; `Bun.serve` is built in, unlike Node's `@hono/node-server` + `@hono/node-ws`), and Bun resolves `.js`→`.ts` natively (no flag, unlike Deno). Reuses `create_node_runtime` (Bun implements the `node:fs`/`node:process` surface). WS is module-level + stateless (like Deno) — the `websocket` handler is threaded into `serve`, where `Bun.serve` wants it, so no post-serve attach.
|
|
1260
|
-
- `testing/cross_backend/default_spine_surface.ts` — the canonical no-domain spine surface (account/admin/audit/signup + bootstrap): `spine_session_options`, `spine_roles`, `create_spine_route_specs`, `spine_rpc_endpoints`, `create_spine_surface_spec`. `$lib`-free (it's reached by the spawned binary under Gro's loader, which doesn't resolve `$lib`), so keep it on relative imports. Shared by the spine_stub cross test, the TS cross tests, and the binary.
|
|
1360
|
+
- `testing/cross_backend/default_spine_surface.ts` — the canonical no-domain spine surface (account/admin/audit/signup + bootstrap): `spine_session_options`, `spine_roles`, `create_spine_route_specs`, `spine_rpc_endpoints`, `create_spine_surface_spec`. This is the **declared** surface — the `create_standard_rpc_actions` bundle the spec-derived suites auto-enumerate. `$lib`-free (it's reached by the spawned binary under Gro's loader, which doesn't resolve `$lib`), so keep it on relative imports. Shared by the spine_stub cross test, the TS cross tests, and the binary.
|
|
1361
|
+
- `testing/cross_backend/full_spine_mount.ts` — `build_full_spine_rpc_actions(deps, options)` / `full_spine_rpc_endpoints(ctx, options)` — the **full** live RPC mount: the declared bundle **plus** the off-declared-surface families the binary live-mounts (`_testing_*` backdoors, the cell verb set, the opt-in `actor_lookup` / `actor_search` resolvers). Single-sources what was an inline assembly in `testing_spine_server.ts`, so the binary and the `spine_method_coverage` reconciliation test build the same list. Also `$lib`-free.
|
|
1261
1362
|
- `testing/cross_backend/ts_spine_backend_config.ts` — `ts_spine_node_backend_config()` / `ts_spine_deno_backend_config()` / `ts_spine_bun_backend_config()` presets (in-memory PGlite, no external infra), the TS analog of `rust_spine_stub_backend_config()`.
|
|
1262
1363
|
|
|
1263
|
-
fuz_app's own binary wiring (`src/test/cross_backend/testing_spine_server{,_node,_deno,_bun}.ts`) is the worked example: ~one `build_app` over `create_app_backend` + `create_app_server` + `
|
|
1364
|
+
fuz_app's own binary wiring (`src/test/cross_backend/testing_spine_server{,_node,_deno,_bun}.ts`) is the worked example: ~one `build_app` over `create_app_backend` + `create_app_server` + `full_spine_rpc_endpoints` + a WS mount, reusing `default_spine_surface`. The `_node`/`_deno`/`_bun` entries differ only in which adapter they wire — `build_spine_app` is runtime-agnostic.
|
|
1365
|
+
|
|
1366
|
+
### Live-method coverage reconciliation — `method_coverage.ts`
|
|
1367
|
+
|
|
1368
|
+
`assert_rpc_method_coverage({live_methods, declared_methods, manifest})` keeps
|
|
1369
|
+
the **off-declared-surface** methods as drift-proof as the declared ones. The
|
|
1370
|
+
spec-derived suites auto-enumerate the declared surface, but a backend's live
|
|
1371
|
+
RPC endpoint also mounts methods kept off it (stateful cell verbs, opt-in
|
|
1372
|
+
resolvers, `_testing_*` backdoors) that rely on hand-wired imperative suites —
|
|
1373
|
+
nothing structurally guaranteed every live method was actually claimed by one.
|
|
1374
|
+
|
|
1375
|
+
The assertion diffs the live method set against a `MethodCoverageEntry`
|
|
1376
|
+
manifest both directions (a mounted-but-unclaimed method **and** a stale
|
|
1377
|
+
manifest row both fail loud) and checks each entry's `tier`
|
|
1378
|
+
(`declared` / `off_surface` / `backdoor`) is consistent with the declared
|
|
1379
|
+
surface + the `_testing_` prefix. `off_surface` rows must name the covering
|
|
1380
|
+
`suite`; `capability` is typed `keyof BackendCapabilities` so a stale flag is a
|
|
1381
|
+
compile error. The manifest is the forcing function — a method can't reach the
|
|
1382
|
+
live mount without a row naming its coverage.
|
|
1383
|
+
|
|
1384
|
+
Pairs with `surface_invariants.ts` `assert_no_testing_methods` (the _reverse_
|
|
1385
|
+
guard — a backdoor must never leak _onto_ the declared surface). fuz_app's own
|
|
1386
|
+
wiring is the manifest `src/test/cross_backend/spine_method_coverage.ts` + the
|
|
1387
|
+
no-DB reconciliation test `spine_method_coverage.test.ts`, which enumerates
|
|
1388
|
+
`build_full_spine_rpc_actions` with stub deps against `create_spine_surface_spec`.
|
|
1264
1389
|
|
|
1265
1390
|
The in-process `ws_round_trip` harness stays (it drives the dispatcher
|
|
1266
1391
|
against a fake upgrade, no wire), but the real-upgrade coverage now lives in
|
|
@@ -48,6 +48,8 @@ export interface CreateTestAccountWithCredentialsOptions {
|
|
|
48
48
|
username?: string;
|
|
49
49
|
password_value?: string;
|
|
50
50
|
roles?: Array<string>;
|
|
51
|
+
/** Optional email stored on the account row — exercises the username-or-email login lookup. */
|
|
52
|
+
email?: string;
|
|
51
53
|
}
|
|
52
54
|
/** Alias for the keeper-flavored call site. Same shape. */
|
|
53
55
|
export type BootstrapTestKeeperOptions = CreateTestAccountWithCredentialsOptions;
|
|
@@ -269,6 +271,19 @@ export interface TestAccount {
|
|
|
269
271
|
/** Build request headers with this account's Bearer token. */
|
|
270
272
|
create_bearer_headers: (extra?: Record<string, string>) => Record<string, string>;
|
|
271
273
|
}
|
|
274
|
+
/**
|
|
275
|
+
* Args for `TestApp.create_account` — mint an additional account alongside the
|
|
276
|
+
* keeper. Mirrors the cross-process `CreateTestAccountOptions` (the two
|
|
277
|
+
* `create_account` paths share a shape so fixture call sites read identically),
|
|
278
|
+
* kept local to avoid a cross-module cycle with `cross_backend/setup.ts`.
|
|
279
|
+
*/
|
|
280
|
+
export interface CreateTestAppAccountArgs {
|
|
281
|
+
username?: string;
|
|
282
|
+
password_value?: string;
|
|
283
|
+
roles?: Array<string>;
|
|
284
|
+
/** Optional email stored on the account row — exercises the username-or-email login lookup. */
|
|
285
|
+
email?: string;
|
|
286
|
+
}
|
|
272
287
|
/**
|
|
273
288
|
* A fully assembled test app — Hono app + backend + helpers.
|
|
274
289
|
*/
|
|
@@ -285,11 +300,7 @@ export interface TestApp {
|
|
|
285
300
|
/** Build request headers with the daemon token (keeper auth). */
|
|
286
301
|
create_daemon_token_headers: (extra?: Record<string, string>) => Record<string, string>;
|
|
287
302
|
/** Create an additional account with credentials. */
|
|
288
|
-
create_account: (options?:
|
|
289
|
-
username?: string;
|
|
290
|
-
password_value?: string;
|
|
291
|
-
roles?: Array<string>;
|
|
292
|
-
}) => Promise<TestAccount>;
|
|
303
|
+
create_account: (options?: CreateTestAppAccountArgs) => Promise<TestAccount>;
|
|
293
304
|
/** Cleanup resources (delegates to TestAppServer.cleanup). */
|
|
294
305
|
cleanup: () => Promise<void>;
|
|
295
306
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/app_server.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,MAAM,CAAC;AAG/B,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAIjD,OAAO,EAA2B,KAAK,OAAO,EAAC,MAAM,oBAAoB,CAAC;AAE1E,OAAO,KAAK,EAAC,EAAE,EAAE,MAAM,EAAC,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,qBAAqB,CAAC;AAU1D,OAAO,EAA8B,KAAK,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAC3F,OAAO,EAAiB,KAAK,kBAAkB,EAAC,MAAM,kBAAkB,CAAC;AAEzE,OAAO,EAAwB,KAAK,UAAU,EAAE,KAAK,YAAY,EAAC,MAAM,0BAA0B,CAAC;AACnG,OAAO,EAEN,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EACzB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,iCAAiC,CAAC;AACtE,OAAO,KAAK,EAAC,UAAU,EAAE,cAAc,EAAC,MAAM,oBAAoB,CAAC;AACnE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAOrD,OAAO,KAAK,EAAC,uBAAuB,EAAC,MAAM,kBAAkB,CAAC;AAE9D;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,EAAE,gBAIhC,CAAC;AAEF,gFAAgF;AAChF,eAAO,MAAM,kBAAkB,QAAiB,CAAC;AA0CjD;;;;;GAKG;AACH,MAAM,WAAW,uCAAuC;IACvD,EAAE,EAAE,EAAE,CAAC;IACP,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/app_server.ts"],"names":[],"mappings":"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"}
|
|
@@ -78,12 +78,13 @@ const resolve_fallback_factory = (migration_namespaces) => {
|
|
|
78
78
|
* `role_grant` (one per role), `api_token`, and `auth_session`.
|
|
79
79
|
*/
|
|
80
80
|
export const create_test_account_with_credentials = async (options) => {
|
|
81
|
-
const { db, keyring, session_options, password, username = 'keeper', password_value = DEFAULT_TEST_PASSWORD, roles = [], } = options;
|
|
81
|
+
const { db, keyring, session_options, password, username = 'keeper', password_value = DEFAULT_TEST_PASSWORD, roles = [], email, } = options;
|
|
82
82
|
const deps = { db };
|
|
83
83
|
const password_hash = await password.hash_password(password_value);
|
|
84
84
|
const { account, actor } = await query_create_account_with_actor(deps, {
|
|
85
85
|
username,
|
|
86
86
|
password_hash,
|
|
87
|
+
...(email !== undefined && { email }),
|
|
87
88
|
});
|
|
88
89
|
// Grant roles
|
|
89
90
|
for (const role of roles) {
|
|
@@ -315,6 +316,7 @@ export const create_test_app = async (options) => {
|
|
|
315
316
|
username: account_options?.username ?? `test_user_${account_counter}`,
|
|
316
317
|
password_value: account_options?.password_value ?? DEFAULT_TEST_PASSWORD,
|
|
317
318
|
roles: account_options?.roles ?? [],
|
|
319
|
+
...(account_options?.email !== undefined && { email: account_options.email }),
|
|
318
320
|
});
|
|
319
321
|
return {
|
|
320
322
|
...bootstrapped,
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Cross-impl RPC **action-manifest** introspection — a normalized,
|
|
4
|
+
* JSON-serializable dump of a backend's live RPC method set, one entry per
|
|
5
|
+
* method carrying its auth shape + side-effect flag.
|
|
6
|
+
*
|
|
7
|
+
* The sibling of `schema_introspect.ts`'s `SchemaSnapshot`: where that
|
|
8
|
+
* captures the live *database* shape for the schema-parity gate, this
|
|
9
|
+
* captures the live *RPC registry* shape for the action-manifest parity gate.
|
|
10
|
+
* Both are dumped over a daemon-token `_testing_*` introspection action
|
|
11
|
+
* (`_testing_action_manifest` here, `_testing_schema_snapshot` there) and
|
|
12
|
+
* diffed across the TS spine and the Rust `testing_spine_stub` so a
|
|
13
|
+
* method-set or per-method auth-shape divergence fails loud. This complements
|
|
14
|
+
* the in-repo `spine_method_coverage` gate: that proves *mounted ⟹ covered*;
|
|
15
|
+
* this proves *TS-mount-set ≡ Rust-mount-set* (method set + auth shape).
|
|
16
|
+
*
|
|
17
|
+
* **Scope — domain + testing surface, not wire protocol.** The manifest is
|
|
18
|
+
* built from exactly the spec list its caller passes
|
|
19
|
+
* (`build_full_spine_rpc_actions` on the TS spine; the spec list minus
|
|
20
|
+
* `PROTOCOL_ACTION_SPECS` on the Rust stub). The protocol actions
|
|
21
|
+
* (`heartbeat` / `cancel`) are excluded by the caller on purpose: the two
|
|
22
|
+
* impls organize them differently (the TS spine mounts them on the WS
|
|
23
|
+
* endpoint only, never the HTTP-RPC endpoint; the Rust stub compiles one
|
|
24
|
+
* shared registry serving both transports), so including them would be a
|
|
25
|
+
* spurious cross-impl diff. This matches the scope of the in-repo
|
|
26
|
+
* `spine_method_coverage` gate (also over `build_full_spine_rpc_actions`).
|
|
27
|
+
*
|
|
28
|
+
* Paired with `action_manifest_parity.ts` for the diff + assertion helpers.
|
|
29
|
+
*
|
|
30
|
+
* `$lib`-free by contract — reached by the spawned TS binary (via
|
|
31
|
+
* `testing_reset_actions.ts` → `full_spine_mount.ts`), so every import is
|
|
32
|
+
* relative.
|
|
33
|
+
*
|
|
34
|
+
* @module
|
|
35
|
+
*/
|
|
36
|
+
import { z } from 'zod';
|
|
37
|
+
import { type RouteAuth } from '../../http/auth_shape.js';
|
|
38
|
+
import type { RequestResponseActionSpec } from '../../actions/action_spec.js';
|
|
39
|
+
/**
|
|
40
|
+
* One method's normalized RPC metadata — the cross-impl-comparable unit.
|
|
41
|
+
* `roles` / `credential_types` are always present + sorted (an absent gate
|
|
42
|
+
* and an empty list both serialize to `[]`) so the diff never trips on a
|
|
43
|
+
* `undefined`-vs-`[]` or declaration-order difference between impls; the
|
|
44
|
+
* auth axes reuse the canonical `AuthAxisState` enum.
|
|
45
|
+
*/
|
|
46
|
+
export declare const ActionManifestEntry: z.ZodObject<{
|
|
47
|
+
/** RPC method name (`spec.method`). */
|
|
48
|
+
method: z.ZodString;
|
|
49
|
+
/** `true` when the action declares `side_effects` (mutation; POST-only on the RPC transport). */
|
|
50
|
+
side_effects: z.ZodBoolean;
|
|
51
|
+
/** The `account` auth axis. */
|
|
52
|
+
account: z.ZodEnum<{
|
|
53
|
+
optional: "optional";
|
|
54
|
+
none: "none";
|
|
55
|
+
required: "required";
|
|
56
|
+
}>;
|
|
57
|
+
/** The `actor` auth axis. */
|
|
58
|
+
actor: z.ZodEnum<{
|
|
59
|
+
optional: "optional";
|
|
60
|
+
none: "none";
|
|
61
|
+
required: "required";
|
|
62
|
+
}>;
|
|
63
|
+
/** Required roles (any-of), sorted; `[]` when the action declares no role gate. */
|
|
64
|
+
roles: z.ZodArray<z.ZodString>;
|
|
65
|
+
/** Permitted credential channels, sorted; `[]` when ungated (any credential). */
|
|
66
|
+
credential_types: z.ZodArray<z.ZodString>;
|
|
67
|
+
}, z.core.$strict>;
|
|
68
|
+
export type ActionManifestEntry = z.infer<typeof ActionManifestEntry>;
|
|
69
|
+
/** The full action manifest — every entry, sorted by `method`. */
|
|
70
|
+
export declare const ActionManifest: z.ZodObject<{
|
|
71
|
+
methods: z.ZodArray<z.ZodObject<{
|
|
72
|
+
/** RPC method name (`spec.method`). */
|
|
73
|
+
method: z.ZodString;
|
|
74
|
+
/** `true` when the action declares `side_effects` (mutation; POST-only on the RPC transport). */
|
|
75
|
+
side_effects: z.ZodBoolean;
|
|
76
|
+
/** The `account` auth axis. */
|
|
77
|
+
account: z.ZodEnum<{
|
|
78
|
+
optional: "optional";
|
|
79
|
+
none: "none";
|
|
80
|
+
required: "required";
|
|
81
|
+
}>;
|
|
82
|
+
/** The `actor` auth axis. */
|
|
83
|
+
actor: z.ZodEnum<{
|
|
84
|
+
optional: "optional";
|
|
85
|
+
none: "none";
|
|
86
|
+
required: "required";
|
|
87
|
+
}>;
|
|
88
|
+
/** Required roles (any-of), sorted; `[]` when the action declares no role gate. */
|
|
89
|
+
roles: z.ZodArray<z.ZodString>;
|
|
90
|
+
/** Permitted credential channels, sorted; `[]` when ungated (any credential). */
|
|
91
|
+
credential_types: z.ZodArray<z.ZodString>;
|
|
92
|
+
}, z.core.$strict>>;
|
|
93
|
+
}, z.core.$strict>;
|
|
94
|
+
export type ActionManifest = z.infer<typeof ActionManifest>;
|
|
95
|
+
/**
|
|
96
|
+
* Normalize one spec's auth + side-effects into a manifest entry. Pulls the
|
|
97
|
+
* four auth axes off `RouteAuth` (the same shape the Rust `AuthSpec` mirrors)
|
|
98
|
+
* and flattens optional `roles` / `credential_types` to sorted arrays.
|
|
99
|
+
*/
|
|
100
|
+
export declare const action_manifest_entry: (spec: {
|
|
101
|
+
readonly method: string;
|
|
102
|
+
readonly auth: RouteAuth;
|
|
103
|
+
readonly side_effects: boolean;
|
|
104
|
+
}) => ActionManifestEntry;
|
|
105
|
+
/**
|
|
106
|
+
* Build the normalized `ActionManifest` from a list of request-response
|
|
107
|
+
* specs. Entries are sorted by `method` so two impls producing the same set
|
|
108
|
+
* serialize identically regardless of mount order. The caller owns the scope
|
|
109
|
+
* it passes (the TS spine passes its full mount; the Rust stub filters
|
|
110
|
+
* `PROTOCOL_ACTION_SPECS` first) — see the module doc.
|
|
111
|
+
*/
|
|
112
|
+
export declare const build_action_manifest: (specs: ReadonlyArray<Pick<RequestResponseActionSpec, "method" | "auth" | "side_effects">>) => ActionManifest;
|
|
113
|
+
//# sourceMappingURL=action_manifest.d.ts.map
|
|
@@ -0,0 +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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,OAAO,EAAgB,KAAK,SAAS,EAAC,MAAM,0BAA0B,CAAC;AACvE,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,8BAA8B,CAAC;AAE5E;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB;IAC/B,uCAAuC;;IAEvC,iGAAiG;;IAEjG,+BAA+B;;;;;;IAE/B,6BAA6B;;;;;;IAE7B,mFAAmF;;IAEnF,iFAAiF;;kBAEhF,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,kEAAkE;AAClE,eAAO,MAAM,cAAc;;QAhB1B,uCAAuC;;QAEvC,iGAAiG;;QAEjG,+BAA+B;;;;;;QAE/B,6BAA6B;;;;;;QAE7B,mFAAmF;;QAEnF,iFAAiF;;;kBAQhF,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;CAC/B,KAAG,mBAOF,CAAC;AAMH;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,GACjC,OAAO,aAAa,CAAC,IAAI,CAAC,yBAAyB,EAAE,QAAQ,GAAG,MAAM,GAAG,cAAc,CAAC,CAAC,KACvF,cAED,CAAC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Cross-impl RPC **action-manifest** introspection — a normalized,
|
|
4
|
+
* JSON-serializable dump of a backend's live RPC method set, one entry per
|
|
5
|
+
* method carrying its auth shape + side-effect flag.
|
|
6
|
+
*
|
|
7
|
+
* The sibling of `schema_introspect.ts`'s `SchemaSnapshot`: where that
|
|
8
|
+
* captures the live *database* shape for the schema-parity gate, this
|
|
9
|
+
* captures the live *RPC registry* shape for the action-manifest parity gate.
|
|
10
|
+
* Both are dumped over a daemon-token `_testing_*` introspection action
|
|
11
|
+
* (`_testing_action_manifest` here, `_testing_schema_snapshot` there) and
|
|
12
|
+
* diffed across the TS spine and the Rust `testing_spine_stub` so a
|
|
13
|
+
* method-set or per-method auth-shape divergence fails loud. This complements
|
|
14
|
+
* the in-repo `spine_method_coverage` gate: that proves *mounted ⟹ covered*;
|
|
15
|
+
* this proves *TS-mount-set ≡ Rust-mount-set* (method set + auth shape).
|
|
16
|
+
*
|
|
17
|
+
* **Scope — domain + testing surface, not wire protocol.** The manifest is
|
|
18
|
+
* built from exactly the spec list its caller passes
|
|
19
|
+
* (`build_full_spine_rpc_actions` on the TS spine; the spec list minus
|
|
20
|
+
* `PROTOCOL_ACTION_SPECS` on the Rust stub). The protocol actions
|
|
21
|
+
* (`heartbeat` / `cancel`) are excluded by the caller on purpose: the two
|
|
22
|
+
* impls organize them differently (the TS spine mounts them on the WS
|
|
23
|
+
* endpoint only, never the HTTP-RPC endpoint; the Rust stub compiles one
|
|
24
|
+
* shared registry serving both transports), so including them would be a
|
|
25
|
+
* spurious cross-impl diff. This matches the scope of the in-repo
|
|
26
|
+
* `spine_method_coverage` gate (also over `build_full_spine_rpc_actions`).
|
|
27
|
+
*
|
|
28
|
+
* Paired with `action_manifest_parity.ts` for the diff + assertion helpers.
|
|
29
|
+
*
|
|
30
|
+
* `$lib`-free by contract — reached by the spawned TS binary (via
|
|
31
|
+
* `testing_reset_actions.ts` → `full_spine_mount.ts`), so every import is
|
|
32
|
+
* relative.
|
|
33
|
+
*
|
|
34
|
+
* @module
|
|
35
|
+
*/
|
|
36
|
+
import { z } from 'zod';
|
|
37
|
+
import { AuthAxisState } from '../../http/auth_shape.js';
|
|
38
|
+
/**
|
|
39
|
+
* One method's normalized RPC metadata — the cross-impl-comparable unit.
|
|
40
|
+
* `roles` / `credential_types` are always present + sorted (an absent gate
|
|
41
|
+
* and an empty list both serialize to `[]`) so the diff never trips on a
|
|
42
|
+
* `undefined`-vs-`[]` or declaration-order difference between impls; the
|
|
43
|
+
* auth axes reuse the canonical `AuthAxisState` enum.
|
|
44
|
+
*/
|
|
45
|
+
export const ActionManifestEntry = z.strictObject({
|
|
46
|
+
/** RPC method name (`spec.method`). */
|
|
47
|
+
method: z.string(),
|
|
48
|
+
/** `true` when the action declares `side_effects` (mutation; POST-only on the RPC transport). */
|
|
49
|
+
side_effects: z.boolean(),
|
|
50
|
+
/** The `account` auth axis. */
|
|
51
|
+
account: AuthAxisState,
|
|
52
|
+
/** The `actor` auth axis. */
|
|
53
|
+
actor: AuthAxisState,
|
|
54
|
+
/** Required roles (any-of), sorted; `[]` when the action declares no role gate. */
|
|
55
|
+
roles: z.array(z.string()),
|
|
56
|
+
/** Permitted credential channels, sorted; `[]` when ungated (any credential). */
|
|
57
|
+
credential_types: z.array(z.string()),
|
|
58
|
+
});
|
|
59
|
+
/** The full action manifest — every entry, sorted by `method`. */
|
|
60
|
+
export const ActionManifest = z.strictObject({
|
|
61
|
+
methods: z.array(ActionManifestEntry),
|
|
62
|
+
});
|
|
63
|
+
/** Sorted copy of a readonly string list (stable cross-impl ordering). */
|
|
64
|
+
const sorted_strings = (xs) => [...(xs ?? [])].sort();
|
|
65
|
+
/**
|
|
66
|
+
* Normalize one spec's auth + side-effects into a manifest entry. Pulls the
|
|
67
|
+
* four auth axes off `RouteAuth` (the same shape the Rust `AuthSpec` mirrors)
|
|
68
|
+
* and flattens optional `roles` / `credential_types` to sorted arrays.
|
|
69
|
+
*/
|
|
70
|
+
export const action_manifest_entry = (spec) => ({
|
|
71
|
+
method: spec.method,
|
|
72
|
+
side_effects: spec.side_effects,
|
|
73
|
+
account: spec.auth.account,
|
|
74
|
+
actor: spec.auth.actor,
|
|
75
|
+
roles: sorted_strings(spec.auth.roles),
|
|
76
|
+
credential_types: sorted_strings(spec.auth.credential_types),
|
|
77
|
+
});
|
|
78
|
+
/** Deterministic byte-lexicographic method order — matches the Rust `str::cmp` the stub sorts by. */
|
|
79
|
+
const compare_method = (a, b) => a.method < b.method ? -1 : a.method > b.method ? 1 : 0;
|
|
80
|
+
/**
|
|
81
|
+
* Build the normalized `ActionManifest` from a list of request-response
|
|
82
|
+
* specs. Entries are sorted by `method` so two impls producing the same set
|
|
83
|
+
* serialize identically regardless of mount order. The caller owns the scope
|
|
84
|
+
* it passes (the TS spine passes its full mount; the Rust stub filters
|
|
85
|
+
* `PROTOCOL_ACTION_SPECS` first) — see the module doc.
|
|
86
|
+
*/
|
|
87
|
+
export const build_action_manifest = (specs) => ({
|
|
88
|
+
methods: specs.map(action_manifest_entry).sort(compare_method),
|
|
89
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Cross-impl action-manifest parity — structural diff + assertion over two
|
|
4
|
+
* `ActionManifest`s captured via the `_testing_action_manifest` RPC action.
|
|
5
|
+
*
|
|
6
|
+
* The action-surface twin of `schema_parity.ts`: two live impls (the TS
|
|
7
|
+
* fuz_app spine and the Rust `testing_spine_stub`) are each other's parity
|
|
8
|
+
* reference. After both bootstrap, dump each one's live RPC method set, diff,
|
|
9
|
+
* fail loudly on drift. The diff entries name the specific divergence (a
|
|
10
|
+
* method only one impl mounts, a per-method auth-axis or side-effect
|
|
11
|
+
* mismatch) so the error message points at the source.
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* const manifest_a = await capture_action_manifest(ts_handle);
|
|
15
|
+
* const manifest_b = await capture_action_manifest(rust_handle);
|
|
16
|
+
* assert_action_manifests_equal(manifest_a, manifest_b, {a: 'ts', b: 'rust'});
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* Non-coverage: the manifest captures `{method, side_effects, account, actor,
|
|
20
|
+
* roles, credential_types}` — the wire-relevant auth shape. It does **not**
|
|
21
|
+
* capture input/output schemas (the declared-surface wire shape is gated by
|
|
22
|
+
* `rpc_round_trip`) nor the protocol actions `heartbeat` / `cancel` (excluded
|
|
23
|
+
* by construction — see `action_manifest.ts`).
|
|
24
|
+
*
|
|
25
|
+
* @module
|
|
26
|
+
*/
|
|
27
|
+
import type { ActionManifest } from './action_manifest.js';
|
|
28
|
+
/** Structured drift entry. `where` is the named source impl ('a' or 'b'). */
|
|
29
|
+
export type ActionManifestDiff = {
|
|
30
|
+
readonly kind: 'method_only_in';
|
|
31
|
+
readonly where: 'a' | 'b';
|
|
32
|
+
readonly method: string;
|
|
33
|
+
} | {
|
|
34
|
+
readonly kind: 'side_effects_differ';
|
|
35
|
+
readonly method: string;
|
|
36
|
+
readonly a: boolean;
|
|
37
|
+
readonly b: boolean;
|
|
38
|
+
} | {
|
|
39
|
+
readonly kind: 'auth_field_differs';
|
|
40
|
+
readonly method: string;
|
|
41
|
+
readonly field: 'account' | 'actor' | 'roles' | 'credential_types';
|
|
42
|
+
readonly a: unknown;
|
|
43
|
+
readonly b: unknown;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Structural diff between two manifests — empty array means parity holds.
|
|
47
|
+
*
|
|
48
|
+
* Order is deterministic: methods in sorted order, with each method's
|
|
49
|
+
* side-effect + auth-field sub-diffs grouped together.
|
|
50
|
+
*/
|
|
51
|
+
export declare const diff_action_manifests: (a: ActionManifest, b: ActionManifest) => Array<ActionManifestDiff>;
|
|
52
|
+
/** Labels used in formatted output — defaults to `'a'` and `'b'`. */
|
|
53
|
+
export interface ActionManifestDiffLabels {
|
|
54
|
+
readonly a?: string;
|
|
55
|
+
readonly b?: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Render a diff list as a human-readable multi-line string. Empty diffs
|
|
59
|
+
* produce an empty string.
|
|
60
|
+
*/
|
|
61
|
+
export declare const format_action_manifest_diffs: (diffs: ReadonlyArray<ActionManifestDiff>, labels?: ActionManifestDiffLabels) => string;
|
|
62
|
+
/**
|
|
63
|
+
* Throw if the two manifests disagree. The error message names the impls
|
|
64
|
+
* (via `labels`) and lists every diff, so the failure is self-diagnosing.
|
|
65
|
+
*/
|
|
66
|
+
export declare const assert_action_manifests_equal: (a: ActionManifest, b: ActionManifest, labels?: ActionManifestDiffLabels) => void;
|
|
67
|
+
//# sourceMappingURL=action_manifest_parity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action_manifest_parity.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/action_manifest_parity.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAC,cAAc,EAAsB,MAAM,sBAAsB,CAAC;AAW9E,6EAA6E;AAC7E,MAAM,MAAM,kBAAkB,GAC3B;IAAC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,GAAG,GAAG,GAAG,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAC,GACrF;IACA,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,CAAC,EAAE,OAAO,CAAC;CACnB,GACD;IACA,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,kBAAkB,CAAC;IACnE,QAAQ,CAAC,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAML;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GACjC,GAAG,cAAc,EACjB,GAAG,cAAc,KACf,KAAK,CAAC,kBAAkB,CA+B1B,CAAC;AAEF,qEAAqE;AACrE,MAAM,WAAW,wBAAwB;IACxC,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,eAAO,MAAM,4BAA4B,GACxC,OAAO,aAAa,CAAC,kBAAkB,CAAC,EACxC,SAAQ,wBAA6B,KACnC,MA4BF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,6BAA6B,GACzC,GAAG,cAAc,EACjB,GAAG,cAAc,EACjB,SAAQ,wBAA6B,KACnC,IAQF,CAAC"}
|