@fuzdev/fuz_app 0.68.0 → 0.70.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 (105) hide show
  1. package/dist/actions/perform_action.d.ts.map +1 -1
  2. package/dist/actions/perform_action.js +10 -3
  3. package/dist/auth/admin_action_specs.d.ts +2 -3
  4. package/dist/auth/admin_action_specs.d.ts.map +1 -1
  5. package/dist/auth/admin_action_specs.js +2 -3
  6. package/dist/auth/admin_actions.d.ts +4 -14
  7. package/dist/auth/admin_actions.d.ts.map +1 -1
  8. package/dist/auth/admin_actions.js +28 -36
  9. package/dist/auth/signup_routes.d.ts +0 -3
  10. package/dist/auth/signup_routes.d.ts.map +1 -1
  11. package/dist/auth/signup_routes.js +9 -3
  12. package/dist/auth/standard_rpc_actions.d.ts +5 -5
  13. package/dist/auth/standard_rpc_actions.js +4 -4
  14. package/dist/server/app_server.d.ts +1 -7
  15. package/dist/server/app_server.d.ts.map +1 -1
  16. package/dist/server/app_server.js +1 -5
  17. package/dist/testing/CLAUDE.md +98 -10
  18. package/dist/testing/app_server.d.ts +34 -0
  19. package/dist/testing/app_server.d.ts.map +1 -1
  20. package/dist/testing/app_server.js +31 -6
  21. package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -1
  22. package/dist/testing/cross_backend/account_lifecycle.js +69 -1
  23. package/dist/testing/cross_backend/actor_lookup.d.ts +10 -0
  24. package/dist/testing/cross_backend/actor_lookup.d.ts.map +1 -0
  25. package/dist/testing/cross_backend/actor_lookup.js +83 -0
  26. package/dist/testing/cross_backend/actor_search.d.ts +6 -0
  27. package/dist/testing/cross_backend/actor_search.d.ts.map +1 -0
  28. package/dist/testing/cross_backend/actor_search.js +92 -0
  29. package/dist/testing/cross_backend/app_settings.d.ts +6 -0
  30. package/dist/testing/cross_backend/app_settings.d.ts.map +1 -0
  31. package/dist/testing/cross_backend/app_settings.js +95 -0
  32. package/dist/testing/cross_backend/backend_config.d.ts +1 -1
  33. package/dist/testing/cross_backend/capabilities.d.ts +0 -9
  34. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  35. package/dist/testing/cross_backend/capabilities.js +0 -1
  36. package/dist/testing/cross_backend/cell_grant_role.d.ts +8 -0
  37. package/dist/testing/cross_backend/cell_grant_role.d.ts.map +1 -0
  38. package/dist/testing/cross_backend/cell_grant_role.js +102 -0
  39. package/dist/testing/cross_backend/conformance_case.d.ts +144 -0
  40. package/dist/testing/cross_backend/conformance_case.d.ts.map +1 -0
  41. package/dist/testing/cross_backend/conformance_case.js +132 -0
  42. package/dist/testing/cross_backend/conformance_table.d.ts +46 -0
  43. package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -0
  44. package/dist/testing/cross_backend/conformance_table.js +199 -0
  45. package/dist/testing/cross_backend/create_cross_backend_global_setup.d.ts +57 -0
  46. package/dist/testing/cross_backend/create_cross_backend_global_setup.d.ts.map +1 -0
  47. package/dist/testing/cross_backend/create_cross_backend_global_setup.js +31 -0
  48. package/dist/testing/cross_backend/default_backend_configs.d.ts +13 -0
  49. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
  50. package/dist/testing/cross_backend/default_backend_configs.js +4 -6
  51. package/dist/testing/cross_backend/default_spine_surface.d.ts +17 -9
  52. package/dist/testing/cross_backend/default_spine_surface.d.ts.map +1 -1
  53. package/dist/testing/cross_backend/default_spine_surface.js +20 -12
  54. package/dist/testing/cross_backend/make_cross_backend_project.d.ts +72 -0
  55. package/dist/testing/cross_backend/make_cross_backend_project.d.ts.map +1 -0
  56. package/dist/testing/cross_backend/make_cross_backend_project.js +51 -0
  57. package/dist/testing/cross_backend/origin.d.ts +10 -0
  58. package/dist/testing/cross_backend/origin.d.ts.map +1 -0
  59. package/dist/testing/cross_backend/origin.js +73 -0
  60. package/dist/testing/cross_backend/setup.d.ts +22 -40
  61. package/dist/testing/cross_backend/setup.d.ts.map +1 -1
  62. package/dist/testing/cross_backend/setup.js +34 -5
  63. package/dist/testing/cross_backend/standard.d.ts +8 -0
  64. package/dist/testing/cross_backend/standard.d.ts.map +1 -1
  65. package/dist/testing/cross_backend/standard.js +1 -0
  66. package/dist/testing/cross_backend/testing_reset_actions.d.ts +102 -10
  67. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  68. package/dist/testing/cross_backend/testing_reset_actions.js +96 -5
  69. package/dist/testing/cross_backend/xfail.d.ts +15 -0
  70. package/dist/testing/cross_backend/xfail.d.ts.map +1 -0
  71. package/dist/testing/cross_backend/xfail.js +37 -0
  72. package/dist/testing/integration.d.ts +2 -3
  73. package/dist/testing/integration.d.ts.map +1 -1
  74. package/dist/testing/integration.js +40 -88
  75. package/dist/testing/rate_limiting.d.ts +1 -1
  76. package/dist/testing/rpc_helpers.d.ts +3 -3
  77. package/dist/testing/sse_round_trip.d.ts +1 -1
  78. package/dist/testing/stubs.d.ts.map +1 -1
  79. package/dist/testing/stubs.js +0 -1
  80. package/dist/ui/AdminAccounts.svelte +74 -83
  81. package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -1
  82. package/dist/ui/AdminSessions.svelte +21 -23
  83. package/dist/ui/AdminSessions.svelte.d.ts.map +1 -1
  84. package/dist/ui/CLAUDE.md +17 -26
  85. package/dist/ui/OpenSignupToggle.svelte +2 -5
  86. package/dist/ui/OpenSignupToggle.svelte.d.ts.map +1 -1
  87. package/dist/ui/account_sessions_state.svelte.d.ts +9 -10
  88. package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -1
  89. package/dist/ui/account_sessions_state.svelte.js +7 -17
  90. package/dist/ui/admin_accounts_state.svelte.d.ts +12 -19
  91. package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
  92. package/dist/ui/admin_accounts_state.svelte.js +10 -24
  93. package/dist/ui/admin_invites_state.svelte.d.ts +8 -11
  94. package/dist/ui/admin_invites_state.svelte.d.ts.map +1 -1
  95. package/dist/ui/admin_invites_state.svelte.js +7 -16
  96. package/dist/ui/admin_sessions_state.svelte.d.ts +6 -10
  97. package/dist/ui/admin_sessions_state.svelte.d.ts.map +1 -1
  98. package/dist/ui/admin_sessions_state.svelte.js +4 -14
  99. package/dist/ui/app_settings_state.svelte.d.ts +8 -12
  100. package/dist/ui/app_settings_state.svelte.d.ts.map +1 -1
  101. package/dist/ui/app_settings_state.svelte.js +6 -16
  102. package/dist/ui/audit_log_state.svelte.d.ts +9 -8
  103. package/dist/ui/audit_log_state.svelte.d.ts.map +1 -1
  104. package/dist/ui/audit_log_state.svelte.js +8 -20
  105. package/package.json +1 -1
@@ -219,15 +219,16 @@ test-only by construction.
219
219
  - `assert_schema_snapshots_equal(a, b, labels?)` — throws on drift with a fully-formatted diff message.
220
220
  - `SchemaDiff` — tagged-union per drift kind: `schema_version_only_in`, `schema_version_sequence_differs`, `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`.
221
221
 
222
- **Cross-impl gate pattern** — consumers running two backends against a
223
- shared schema (zzz's `--backend=both`, fuz_app's cross-backend suite)
224
- bootstrap each impl against an isolated DB, snapshot, then compare:
222
+ **Cross-impl gate pattern** — a dual-impl consumer running two backends
223
+ (a TS Hono server and a Rust spine server) against a shared schema, plus
224
+ fuz_app's own cross-backend suite, bootstrap each impl against an isolated
225
+ DB, snapshot, then compare:
225
226
 
226
227
  ```ts
227
- await drop_recreate_db('zzz_test');
228
+ await drop_recreate_db('app_test');
228
229
  await spawn_backend(deno_config);
229
230
  const snapshot_deno = await query_schema_snapshot(db);
230
- await drop_recreate_db('zzz_test');
231
+ await drop_recreate_db('app_test');
231
232
  await spawn_backend(rust_config);
232
233
  const snapshot_rust = await query_schema_snapshot(db);
233
234
  assert_schema_snapshots_equal(snapshot_deno, snapshot_rust, {a: 'deno', b: 'rust'});
@@ -789,8 +790,8 @@ points:
789
790
  The standard test suites take a unified
790
791
  `{setup_test, surface_source, capabilities}` shape so the same suite bodies
791
792
  run against an in-process Hono harness today and against a spawned backend
792
- over real HTTP — either the Rust spine (`zzz_server`, `fuz_forge_server`, or
793
- the non-domain `testing_spine_stub`) or a **TS** spine binary built on the
793
+ over real HTTP — either the Rust spine (`zzz_server`, another consumer's
794
+ spine server, or the non-domain `testing_spine_stub`) or a **TS** spine binary built on the
794
795
  test-server core below (fuz_app's own domain-free `testing_spine_server`, run
795
796
  on Node + Deno + Bun). In-process is the fast feedback path; cross-process is the
796
797
  source of truth for wire-shape conformance.
@@ -846,7 +847,7 @@ source of truth for wire-shape conformance.
846
847
 
847
848
  - `testing/cross_backend/capabilities.ts` — `BackendCapabilities` vocabulary
848
849
  (`bearer_auth` / `trusted_proxy` / `login_rate_limit` / `ws` / `sse` /
849
- `cell_crud` / `cell_relations` / `account_lifecycle` / `in_process_only`),
850
+ `cell_crud` / `cell_relations` / `account_lifecycle`),
850
851
  `test_if(cond, name, fn)`
851
852
  for capability-gated cases, and `in_process_capabilities` preset. `cell_crud`
852
853
  gates the CRUD parity suite, `cell_relations` the relation / ACL / audit
@@ -885,6 +886,50 @@ consumer needs partial opt-out, add the knob then.
885
886
  `bootstrap`, `rate_limiting_app_options`, `bootstrap_token`) — those drive
886
887
  the omitted suites.
887
888
 
889
+ ### `cross_backend/conformance_table.ts` + `conformance_case.ts` + `xfail.ts` — declarative behavioral/security cases
890
+
891
+ The opinionated behavioral/security layer on top of the spec-derived
892
+ auto-enumeration (`describe_rpc_round_trip_tests` /
893
+ `describe_rpc_attack_surface_tests`). Where those assert wire-shape,
894
+ conformance cases assert _expected behavior_ — the security negatives
895
+ (must be refused / must not leak / found-vs-not-found same shape) a
896
+ wire-shape check passes green on even when behavior is wrong.
897
+
898
+ - `conformance_case.ts` — `ConformanceCase` Zod schema:
899
+ `{name, request: {method, params?, as, verb?}, expect: {status,
900
+ error_reason?, fields?}, note?, xfail?}`. A case is **data** — `method`
901
+ resolves its `input`/`output` from the live registry (RPC) or `RouteSpec`
902
+ (the 6 REST auth routes), so the case never carries a schema. `as` is the
903
+ closed `ConformancePrincipal` enum (`keeper` / `daemon` / `token` /
904
+ `anonymous` / `fresh_non_admin` / `role_holder` / `wrong_role` /
905
+ `expired_session`) — fixture accessors, never inline credential minting.
906
+ `expired_session` is the keeper behind an expired server-side session
907
+ (`fixture.mint_expired_session()`: a backdated `auth_session` row behind a
908
+ still-valid signed cookie, so the DB-row expiry gate is what refuses it).
909
+ `error_reason` is the imported
910
+ `ERROR_*` constant (asserted against the RPC `error.data.reason` or the
911
+ REST flat-body `error`; the bare `unauthenticated()` 401 carries no
912
+ reason, so `status` pins that denial class).
913
+ - `conformance_table.ts` — `describe_conformance_table_tests({cases,
914
+ setup_test, surface_source, capabilities, rpc_endpoints, session_options,
915
+ principals?, suite_name?})`. Same `{setup_test, surface_source,
916
+ capabilities}` protocol every Tier 1 suite uses, so **one case array runs
917
+ both transports** — in-process (`gro test`) and cross-process (the gate,
918
+ each backend's real auth resolution). `resolve_principal` maps the five
919
+ always-available principals to fixture accessors; `role_holder` /
920
+ `wrong_role` read a seeded `extra_accounts` username named via
921
+ `options.principals`.
922
+ - `xfail.ts` — `xfail_until(tracking_id, reason, name, fn)`, a thin
923
+ `test.fails` wrapper for deferred-by-design rows (visible + self-cleaning:
924
+ turns red when the gap closes, forcing marker removal). In-scope gaps fail
925
+ loud as a normal `test`, not via this marker. Sibling to `test_if` in
926
+ `capabilities.ts`.
927
+
928
+ Wire from a `.db.test.ts` (in-process) and a `.cross.test.ts`
929
+ (cross-process) with the same case array — fuz_app's own runner-proof is
930
+ `../../test/cross_backend/conformance.{db,cross}.test.ts` sharing
931
+ `conformance_proof_cases.ts`.
932
+
888
933
  ### `cross_backend/ws_round_trip.ts` — `describe_cross_process_ws_tests`
889
934
 
890
935
  Real-upgrade WebSocket coverage of a spawned backend — the cross-process
@@ -933,9 +978,13 @@ _own_ sessions are revoked (`account_session_revoke_all`) so the audit guard
933
978
  drops the live stream (asserted via `SseTransport.wait_for_close`). The
934
979
  data-frame + close cases gate on `rpc_path` (they drive the standard
935
980
  account/admin actions); all cases gate on `capabilities.sse`. Cross-process
936
- only — wire from a `*.cross.test.ts`. fuz_app's own wiring is
981
+ only — wire from a `*.cross.test.ts`. fuz*app's own wiring is
937
982
  `src/test/cross_backend/sse.cross.test.ts`; only the TS spines advertise
938
983
  `sse` (they wire `audit_log_sse`), so the Rust `spine_stub` cases `.skip`.
984
+ That file also registers one `xfail_until` (only when `sse: false`) asserting
985
+ the stream \_can't* open on a spine without SSE — a self-cleaning tripwire for
986
+ the spine that should grow it, distinct from the consumer-legit capability
987
+ skip the shared suite emits.
939
988
 
940
989
  ### `cross_backend/cell_crud.ts` + `cell_relations.ts` — cell parity suites
941
990
 
@@ -1046,13 +1095,52 @@ in-process legs (plain `gro test`) are `src/test/auth/cell_crud_parity.db.test.t
1046
1095
  same bootstrap-equivalent step (the only path for roles like
1047
1096
  `ROLE_KEEPER` whose `grant_paths` is bootstrap-only), refreshes
1048
1097
  `DaemonTokenState.keeper_account_id` to the new row, then fires the
1049
- consumer-supplied `reset_state` callback for domain-state reset. Auth
1098
+ consumer-supplied `reset_state(db)` callback for domain-state reset
1099
+ passed the **transactional** `Db` the auth wipe ran on, so DB-domain
1100
+ consumers (e.g. fuz_forge truncating its cell / fact / file tables) reset
1101
+ on the same connection rather than a separately-pooled one that would
1102
+ deadlock against this open transaction under PGlite. Auth
1050
1103
  gates on `credential_types: ['daemon_token']` — effectively keeper-only
1051
1104
  without forcing the `actor: 'required'` ⟺ `acting?: ActingActor`
1052
1105
  biconditional. No free-form runtime grant action exists — see the
1053
1106
  `testing_reset_actions.ts` TSDoc for the audit + WS fan-out rationale
1054
1107
  that rejected a `_testing_seed_role_grant` shape.
1055
1108
 
1109
+ Same module also exports `create_testing_drain_effects_action()` — the
1110
+ `_testing_drain_effects` RPC action (daemon-token-gated, like
1111
+ `_testing_reset`). It awaits in-flight fire-and-forget audit writes so a
1112
+ following `audit_log_list` is authoritative — the deterministic barrier a
1113
+ cross-process audit assertion fires before reading (no poll/sleep). On the
1114
+ TS spine it is **satisfied by construction** (the binary runs
1115
+ `await_pending_effects: true`, so each mutation's emits land before its
1116
+ response); the Rust spine does the real await in
1117
+ `AuditEmitter::drain_inflight`. `create_testing_actions` bundles it
1118
+ alongside `_testing_reset`; suites that mount their own endpoint (e.g. the
1119
+ in-process `account_lifecycle_parity.db.test.ts`) add it directly so the
1120
+ shared suite body can call the barrier on every backend uniformly.
1121
+
1122
+ Also bundled: `_testing_mint_session` — mints a backdated-expiry
1123
+ `auth_session` row for an account (via `mint_test_session` in `app_server.ts`)
1124
+ and returns its signed cookie value (future-dated payload). Backs the
1125
+ `expired_session` conformance principal: the backdated DB row + valid cookie
1126
+ payload isolate the authoritative server-side DB-row expiry gate
1127
+ (`query_session_get_valid` — `expires_at > NOW()`), the gate the in-process
1128
+ payload-expiry tests never reached. Daemon-token-gated like its siblings; the
1129
+ Rust mirror is `fuz_testing::create_testing_mint_session_action_spec`.
1130
+
1131
+ ### Origin verification parity — `cross_backend/origin.ts`
1132
+
1133
+ `describe_origin_cross_tests({setup_test, capabilities, rpc_path?})` — the
1134
+ imperative Origin-verification suite: disallowed `Origin` → 403 `forbidden_origin` (refused
1135
+ before dispatch), absent `Origin` → request passes (non-browser direct access).
1136
+ Imperative (not a conformance-table row) because origin rejection is
1137
+ middleware-level flat-REST, not the JSON-RPC envelope the table runner expects,
1138
+ and absent-Origin needs `fresh_transport({origin: null})`. Runs both legs (the
1139
+ in-process `auth/origin_parity.db.test.ts` + the cross-process
1140
+ `origin.cross.test.ts`). The promotion surfaced a twin-impl divergence — the
1141
+ Rust spine returned a plain-text body — now converged to the canonical TS
1142
+ `{error: "forbidden_origin"}` via `fuz_http::forbidden_origin_response()`.
1143
+
1056
1144
  ### Building a TS test-server binary — `testing_server_core.ts` + adapters
1057
1145
 
1058
1146
  The reusable shape for standing up a **spawnable TS** cross-process test
@@ -89,6 +89,40 @@ export declare const create_test_account_with_credentials: (options: CreateTestA
89
89
  api_token: string;
90
90
  session_cookie: string;
91
91
  }>;
92
+ /** Options for `mint_test_session`. */
93
+ export interface MintTestSessionOptions {
94
+ db: Db;
95
+ keyring: Keyring;
96
+ session_options: SessionOptions<string>;
97
+ /** Account the minted session belongs to. */
98
+ account_id: string;
99
+ /**
100
+ * Session lifetime offset in seconds applied to `NOW()` for the
101
+ * `auth_session.expires_at` row. A negative value backdates the row so
102
+ * the authoritative DB-row expiry gate (`query_session_get_valid` —
103
+ * `WHERE expires_at > NOW()`) rejects it, while the returned cookie's
104
+ * own signed payload stays valid (future). Resolution therefore passes
105
+ * the cookie-payload check in `parse_session` and is refused at the
106
+ * DB-row gate — the gate the in-process payload-expiry tests never
107
+ * reach and the one that structurally needs a server-side mint.
108
+ */
109
+ expires_in_seconds: number;
110
+ }
111
+ /**
112
+ * Mint a real `auth_session` row for an existing account and return a
113
+ * validly-signed session cookie value referencing it. Test-only — the
114
+ * forge behind the cross-backend expiry conformance cases (the
115
+ * `expired_session` principal): pass a negative `expires_in_seconds` to
116
+ * produce an *expired server-side session* whose signed cookie envelope is
117
+ * still well-formed. Both the TS `_testing_mint_session` action and the
118
+ * in-process `fixture.mint_expired_session()` seam call this so the write
119
+ * semantics match across transports.
120
+ *
121
+ * @mutates `options.db` — inserts one `auth_session` row.
122
+ */
123
+ export declare const mint_test_session: (options: MintTestSessionOptions) => Promise<{
124
+ session_cookie: string;
125
+ }>;
92
126
  /**
93
127
  * Bootstrap the test-DB keeper. Direct-query shortcut for the default
94
128
  * `create_test_app` path — bootstrap is not what most tests exercise, so
@@ -1 +1 @@
1
- {"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/app_server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/app_server.ts"],"names":[],"mappings":"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"}
@@ -106,12 +106,16 @@ export const create_test_account_with_credentials = async (options) => {
106
106
  // Create API token (account-scoped — acting actor is per-request)
107
107
  const { token: api_token, id: token_id, token_hash } = generate_api_token();
108
108
  await query_create_api_token(deps, token_id, account.id, 'test-cli', token_hash);
109
- // Create session (account-scoped — acting actor is per-request)
110
- const session_token = generate_session_token();
111
- const session_hash = hash_session_token(session_token);
112
- const expires_at = new Date(Date.now() + AUTH_SESSION_LIFETIME_MS);
113
- await query_create_session(deps, session_hash, account.id, expires_at);
114
- const session_cookie = await create_session_cookie_value(keyring, session_token, session_options);
109
+ // Create session (account-scoped — acting actor is per-request).
110
+ // Shares the mint primitive with `mint_test_session` / the
111
+ // `_testing_mint_session` action; here with the standard 30-day lifetime.
112
+ const { session_cookie } = await mint_test_session({
113
+ db,
114
+ keyring,
115
+ session_options,
116
+ account_id: account.id,
117
+ expires_in_seconds: AUTH_SESSION_LIFETIME_MS / 1000,
118
+ });
115
119
  return {
116
120
  account: { id: account.id, username: account.username },
117
121
  actor: { id: actor.id },
@@ -119,6 +123,27 @@ export const create_test_account_with_credentials = async (options) => {
119
123
  session_cookie,
120
124
  };
121
125
  };
126
+ /**
127
+ * Mint a real `auth_session` row for an existing account and return a
128
+ * validly-signed session cookie value referencing it. Test-only — the
129
+ * forge behind the cross-backend expiry conformance cases (the
130
+ * `expired_session` principal): pass a negative `expires_in_seconds` to
131
+ * produce an *expired server-side session* whose signed cookie envelope is
132
+ * still well-formed. Both the TS `_testing_mint_session` action and the
133
+ * in-process `fixture.mint_expired_session()` seam call this so the write
134
+ * semantics match across transports.
135
+ *
136
+ * @mutates `options.db` — inserts one `auth_session` row.
137
+ */
138
+ export const mint_test_session = async (options) => {
139
+ const { db, keyring, session_options, account_id, expires_in_seconds } = options;
140
+ const session_token = generate_session_token();
141
+ const session_hash = hash_session_token(session_token);
142
+ const expires_at = new Date(Date.now() + expires_in_seconds * 1000);
143
+ await query_create_session({ db }, session_hash, account_id, expires_at);
144
+ const session_cookie = await create_session_cookie_value(keyring, session_token, session_options);
145
+ return { session_cookie };
146
+ };
122
147
  /**
123
148
  * Bootstrap the test-DB keeper. Direct-query shortcut for the default
124
149
  * `create_test_app` path — bootstrap is not what most tests exercise, so
@@ -1 +1 @@
1
- {"version":3,"file":"account_lifecycle.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/account_lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA+B9B,OAAO,EAIN,KAAK,oBAAoB,EACzB,MAAM,yBAAyB,CAAC;AAGjC;;;;GAIG;AACH,MAAM,MAAM,gCAAgC,GAAG,oBAAoB,CAAC;AAEpE,eAAO,MAAM,sCAAsC,GAClD,SAAS,gCAAgC,KACvC,IA4IF,CAAC"}
1
+ {"version":3,"file":"account_lifecycle.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/account_lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAiC9B,OAAO,EAIN,KAAK,oBAAoB,EACzB,MAAM,yBAAyB,CAAC;AAGjC;;;;GAIG;AACH,MAAM,MAAM,gCAAgC,GAAG,oBAAoB,CAAC;AAEpE,eAAO,MAAM,sCAAsC,GAClD,SAAS,gCAAgC,KACvC,IA+TF,CAAC"}
@@ -18,7 +18,8 @@ import '../assert_dev_env.js';
18
18
  * @module
19
19
  */
20
20
  import { describe, assert } from 'vitest';
21
- import { AccountDeleteOutput, AccountUndeleteOutput, AccountPurgeOutput, AdminAccountListOutput, ERROR_CANNOT_DELETE_KEEPER, } from '../../auth/admin_action_specs.js';
21
+ import { AccountDeleteOutput, AccountUndeleteOutput, AccountPurgeOutput, AdminAccountListOutput, AuditLogListOutput, ERROR_CANNOT_DELETE_KEEPER, } from '../../auth/admin_action_specs.js';
22
+ import { ERROR_ACCOUNT_NOT_FOUND, ERROR_AUTHENTICATION_REQUIRED } from '../../http/error_schemas.js';
22
23
  import { test_if } from './capabilities.js';
23
24
  import { cross_rpc_call, error_reason, expect_output, } from './cell_cross_helpers.js';
24
25
  import { SPINE_RPC_PATH } from './default_spine_surface.js';
@@ -56,6 +57,73 @@ export const describe_account_lifecycle_cross_tests = (options) => {
56
57
  assert.strictEqual(purge.ok, false, 'purge of keeper account must be refused');
57
58
  assert.strictEqual(error_reason(purge), ERROR_CANNOT_DELETE_KEEPER);
58
59
  });
60
+ test_if(capabilities.account_lifecycle, 'fail-closed: a soft-deleted account’s session + bearer credentials no longer authenticate', async () => {
61
+ const fixture = await setup_test();
62
+ const victim = await fixture.create_account({ username: 'lifecycle_failclosed' });
63
+ const admin_headers = fixture.create_session_headers();
64
+ // Sanity: the victim's session authenticates while active, so the
65
+ // post-deletion 401 is a real fail-closed transition, not a
66
+ // never-valid credential passing vacuously.
67
+ const before = await cross_rpc_call(fixture.fresh_transport(), rpc_path, 'account_verify', undefined, victim.create_session_headers());
68
+ assert.ok(before.ok, 'victim session authenticates before deletion');
69
+ const deleted = expect_output(await cross_rpc_call(fixture.fresh_transport(), rpc_path, 'account_delete', { account_id: victim.account.id }, admin_headers), AccountDeleteOutput);
70
+ assert.strictEqual(deleted.deleted, true);
71
+ // The tombstone blocks auth resolution (and the soft-delete
72
+ // revoked sessions/tokens) — the stale session credential must
73
+ // fail closed with a generic 401, not partially authenticate.
74
+ const session_probe = await cross_rpc_call(fixture.fresh_transport(), rpc_path, 'account_verify', undefined, victim.create_session_headers());
75
+ assert.strictEqual(session_probe.ok, false, 'soft-deleted account session must not authenticate');
76
+ assert.strictEqual(error_reason(session_probe), ERROR_AUTHENTICATION_REQUIRED);
77
+ // The victim's bearer token must fail closed too.
78
+ const bearer_probe = await cross_rpc_call(fixture.fresh_transport({ origin: null }), rpc_path, 'account_verify', undefined, victim.create_bearer_headers());
79
+ assert.strictEqual(bearer_probe.ok, false, 'soft-deleted account bearer token must not authenticate');
80
+ assert.strictEqual(error_reason(bearer_probe), ERROR_AUTHENTICATION_REQUIRED);
81
+ });
82
+ test_if(capabilities.account_lifecycle, 'keeper guard emits a fail-loud failure-audit row (drained, cross-impl)', async () => {
83
+ const fixture = await setup_test();
84
+ const t = fixture.fresh_transport();
85
+ // Refused keeper self-delete — the guard fires before any mutation
86
+ // and emits a forensic `outcome: failure` audit row.
87
+ const del = await cross_rpc_call(t, rpc_path, 'account_delete', { account_id: fixture.account.id }, fixture.create_session_headers());
88
+ assert.strictEqual(error_reason(del), ERROR_CANNOT_DELETE_KEEPER);
89
+ // Deterministic barrier before reading: await in-flight
90
+ // fire-and-forget audit writes (the real await on the Rust spine;
91
+ // satisfied-by-construction on the TS spine via await_pending_effects).
92
+ const td = fixture.fresh_transport({ origin: null });
93
+ const drained = await cross_rpc_call(td, rpc_path, '_testing_drain_effects', undefined, fixture.create_daemon_token_headers());
94
+ assert.ok(drained.ok, `_testing_drain_effects failed: ${JSON.stringify(drained.error)}`);
95
+ // The failure row is now authoritative on both spines. `_testing_reset`
96
+ // wiped audit_log at setup, so the refused delete is the only
97
+ // account_delete event.
98
+ const listed = expect_output(await cross_rpc_call(t, rpc_path, 'audit_log_list', { event_type: 'account_delete' }, fixture.create_session_headers()), AuditLogListOutput);
99
+ const failure = listed.events.find((e) => e.outcome === 'failure' &&
100
+ e.metadata?.reason === ERROR_CANNOT_DELETE_KEEPER);
101
+ assert.ok(failure, 'keeper-removal guard must emit an account_delete outcome=failure audit row with reason cannot_delete_keeper');
102
+ });
103
+ test_if(capabilities.account_lifecycle, 'deterministic: double-undelete → second call is not_found', async () => {
104
+ const fixture = await setup_test();
105
+ const victim = await fixture.create_account({ username: 'lifecycle_double' });
106
+ const t = fixture.fresh_transport();
107
+ const admin_headers = fixture.create_session_headers();
108
+ const deleted = expect_output(await cross_rpc_call(t, rpc_path, 'account_delete', { account_id: victim.account.id }, admin_headers), AccountDeleteOutput);
109
+ assert.strictEqual(deleted.deleted, true);
110
+ // First undelete clears the tombstone.
111
+ const undeleted = expect_output(await cross_rpc_call(t, rpc_path, 'account_undelete', { account_id: victim.account.id }, admin_headers), AccountUndeleteOutput);
112
+ assert.strictEqual(undeleted.undeleted, true);
113
+ // Second undelete on the now-active account is a deterministic
114
+ // not_found — the query only matches soft-deleted rows, so the
115
+ // outcome is the same on both spines (no silent idempotent ok).
116
+ const again = await cross_rpc_call(t, rpc_path, 'account_undelete', { account_id: victim.account.id }, admin_headers);
117
+ assert.strictEqual(again.ok, false, 'double-undelete must not silently succeed');
118
+ assert.strictEqual(error_reason(again), ERROR_ACCOUNT_NOT_FOUND);
119
+ });
120
+ // The last-admin guard (`ERROR_CANNOT_DELETE_LAST_ADMIN`) is **not**
121
+ // cross-process-testable against this fixture: the per-test keeper
122
+ // permanently holds `ROLE_ADMIN` (bootstrap seeds `[ROLE_KEEPER,
123
+ // ROLE_ADMIN]` and there is no remove-admin-from-keeper path), so a
124
+ // non-keeper admin is never the *sole* active admin and the guard
125
+ // never fires here. Its logic is covered in-process by
126
+ // `src/test/auth/account_keeper_guard.db.test.ts`.
59
127
  test_if(capabilities.account_lifecycle, 'admin_account_list include_deleted surfaces tombstoned rows with deleted_at set', async () => {
60
128
  const fixture = await setup_test();
61
129
  const victim = await fixture.create_account({ username: 'lifecycle_listed' });
@@ -0,0 +1,10 @@
1
+ import '../assert_dev_env.js';
2
+ import type { CellCrossTestOptions } from './cell_cross_helpers.js';
3
+ /**
4
+ * Options for the actor-lookup parity suite. Shares the shape of the cell /
5
+ * origin suites (`setup_test` / `capabilities` / `rpc_path`); reuses
6
+ * `CellCrossTestOptions` rather than minting a structural duplicate.
7
+ */
8
+ export type ActorLookupCrossTestOptions = CellCrossTestOptions;
9
+ export declare const describe_actor_lookup_cross_tests: (options: ActorLookupCrossTestOptions) => void;
10
+ //# sourceMappingURL=actor_lookup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actor_lookup.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/actor_lookup.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAqC9B,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,yBAAyB,CAAC;AAGlE;;;;GAIG;AACH,MAAM,MAAM,2BAA2B,GAAG,oBAAoB,CAAC;AAS/D,eAAO,MAAM,iCAAiC,GAAI,SAAS,2BAA2B,KAAG,IAkDxF,CAAC"}
@@ -0,0 +1,83 @@
1
+ import '../assert_dev_env.js';
2
+ /**
3
+ * Cross-backend parity suite for `actor_lookup`.
4
+ *
5
+ * `actor_lookup` is an opt-in batched id → label resolver
6
+ * (`{ids} → {actors: [{id, username, display_name?}]}`), not folded into the
7
+ * standard bundle. It's live-mounted on the spine RPC path but kept off the
8
+ * declared surface (`create_spine_surface_spec`) — like cells / ws / sse — so
9
+ * the standard cross suite's generic round-trip never drives it; this
10
+ * dedicated suite is its validator. Three cases over raw transport calls:
11
+ *
12
+ * - **anonymous → 401** — the account-grain auth gate refuses an
13
+ * unauthenticated caller before the handler runs.
14
+ * - **keeper resolves own actor → 200** — the populated round trip: the
15
+ * returned row carries the keeper's `id` + `username`, and **no**
16
+ * `account_id` / `email` / timestamp / role field (the wire shape's
17
+ * deliberate info-leak posture). This is the assertion that exercises the
18
+ * Rust row→JSON mapping against the TS canonical shape.
19
+ * - **empty `ids` → 400** — the `min(1)` input bound is enforced on both
20
+ * spines (TS Zod, Rust `parse_ids`).
21
+ *
22
+ * Runs both legs via the shared `{setup_test}` protocol: the in-process leg
23
+ * (`auth/actor_lookup_parity.db.test.ts`, plain `gro test`) and the
24
+ * cross-process leg (`cross_backend/actor_lookup.cross.test.ts`, the TS spine
25
+ * binaries + Rust `testing_spine_stub` over real HTTP). `actor_lookup` is
26
+ * mounted on every spine, so the suite is ungated.
27
+ *
28
+ * `$lib`-free by contract (relative specifiers only), like the sibling
29
+ * cross-backend suites.
30
+ *
31
+ * @module
32
+ */
33
+ import { describe, test, assert } from 'vitest';
34
+ import { actor_lookup_action_spec } from '../../auth/actor_lookup_action_specs.js';
35
+ import { SPINE_RPC_PATH } from './default_spine_surface.js';
36
+ /** Keys that must never appear on an `actor_lookup` result row. */
37
+ const forbidden_row_keys = ['account_id', 'email', 'created_at', 'updated_at', 'role'];
38
+ /** Build the JSON-RPC envelope body for an `actor_lookup` call. */
39
+ const lookup_envelope = (ids, id) => JSON.stringify({ jsonrpc: '2.0', method: actor_lookup_action_spec.method, params: { ids }, id });
40
+ export const describe_actor_lookup_cross_tests = (options) => {
41
+ const { setup_test } = options;
42
+ const rpc_path = options.rpc_path ?? SPINE_RPC_PATH;
43
+ describe('actor_lookup parity', () => {
44
+ test('anonymous → 401 (account-grain auth gate)', async () => {
45
+ const fixture = await setup_test();
46
+ const res = await fixture.fresh_transport()(rpc_path, {
47
+ method: 'POST',
48
+ headers: { 'content-type': 'application/json' },
49
+ body: lookup_envelope([fixture.actor.id], 'anon-lookup'),
50
+ });
51
+ assert.strictEqual(res.status, 401, 'unauthenticated actor_lookup must be refused');
52
+ });
53
+ test('keeper resolves own actor → 200 with id + username, no control fields', async () => {
54
+ const fixture = await setup_test();
55
+ const res = await fixture.transport(rpc_path, {
56
+ method: 'POST',
57
+ headers: { ...fixture.create_session_headers(), 'content-type': 'application/json' },
58
+ body: lookup_envelope([fixture.actor.id], 'keeper-lookup'),
59
+ });
60
+ assert.strictEqual(res.status, 200, 'authenticated actor_lookup must succeed');
61
+ const body = (await res.json());
62
+ const actors = body.result?.actors;
63
+ assert(Array.isArray(actors), 'response carries an actors array');
64
+ assert.strictEqual(actors.length, 1, 'the keeper actor resolves to exactly one row');
65
+ const row = actors[0];
66
+ assert(row !== undefined, 'the resolved row is present');
67
+ assert.strictEqual(row.id, fixture.actor.id, 'resolved row id matches the requested actor');
68
+ assert.strictEqual(row.username, fixture.account.username, 'resolved row carries the keeper username');
69
+ for (const key of forbidden_row_keys) {
70
+ assert(!(key in row), `actor_lookup row must not leak '${key}'`);
71
+ }
72
+ });
73
+ test('empty ids → 400 (min(1) input bound)', async () => {
74
+ const fixture = await setup_test();
75
+ const res = await fixture.transport(rpc_path, {
76
+ method: 'POST',
77
+ headers: { ...fixture.create_session_headers(), 'content-type': 'application/json' },
78
+ body: lookup_envelope([], 'empty-lookup'),
79
+ });
80
+ assert.strictEqual(res.status, 400, 'empty ids must fail input validation');
81
+ });
82
+ });
83
+ };
@@ -0,0 +1,6 @@
1
+ import '../assert_dev_env.js';
2
+ import type { CellCrossTestOptions } from './cell_cross_helpers.js';
3
+ /** Options for the actor-search parity suite (shares the cell/origin shape). */
4
+ export type ActorSearchCrossTestOptions = CellCrossTestOptions;
5
+ export declare const describe_actor_search_cross_tests: (options: ActorSearchCrossTestOptions) => void;
6
+ //# sourceMappingURL=actor_search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actor_search.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/actor_search.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA2C9B,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,yBAAyB,CAAC;AAGlE,gFAAgF;AAChF,MAAM,MAAM,2BAA2B,GAAG,oBAAoB,CAAC;AAS/D,eAAO,MAAM,iCAAiC,GAAI,SAAS,2BAA2B,KAAG,IAyDxF,CAAC"}
@@ -0,0 +1,92 @@
1
+ import '../assert_dev_env.js';
2
+ /**
3
+ * Cross-backend parity suite for `actor_search`.
4
+ *
5
+ * `actor_search` is an opt-in case-insensitive prefix search over
6
+ * `actor.name` (`{query, scope_ids?, limit?} → {actors: [{id, username,
7
+ * display_name?}]}`), not folded into the standard bundle. Like
8
+ * `actor_lookup` / cells, it's live-mounted on the spine RPC path but kept
9
+ * off the declared surface, so this dedicated suite is its validator. The
10
+ * security property under test is the **empty-`scope_ids` admin gate**:
11
+ *
12
+ * - **anonymous → 401** — the account-grain auth gate refuses an
13
+ * unauthenticated caller before the handler runs.
14
+ * - **non-admin + no `scope_ids` → 400** `actor_search_scope_required` — an
15
+ * unbounded global search is admin-only; a non-admin must scope the query.
16
+ * This is the core security assertion, exercised against each impl's real
17
+ * auth resolution.
18
+ * - **non-admin + `scope_ids` → 200** — passing a scope bypasses the admin
19
+ * requirement (results are filtered to actors holding active role_grants on
20
+ * those scopes); an unheld scope simply yields an empty result, not a
21
+ * rejection — proving the gate keys on `scope_ids` presence, not identity.
22
+ * - **admin + no `scope_ids` → 200** — the admin path reaches the unbounded
23
+ * search.
24
+ *
25
+ * Cites `security.md` §Authorization (the `actor_search` scope gate).
26
+ *
27
+ * Runs both legs via the shared `{setup_test}` protocol: in-process
28
+ * (`auth/actor_search_parity.db.test.ts`) + cross-process
29
+ * (`cross_backend/actor_search.cross.test.ts`, TS spine binaries + Rust
30
+ * `testing_spine_stub`). Mounted on every spine, so the suite is ungated.
31
+ *
32
+ * `$lib`-free by contract (relative specifiers only).
33
+ *
34
+ * @module
35
+ */
36
+ import { describe, test, assert } from 'vitest';
37
+ import { actor_search_action_spec, ERROR_ACTOR_SEARCH_SCOPE_REQUIRED, } from '../../auth/actor_search_action_specs.js';
38
+ import { SPINE_RPC_PATH } from './default_spine_surface.js';
39
+ /** Nil UUID — an unheld scope id (no actor holds a role_grant on it). */
40
+ const NIL_UUID = '00000000-0000-0000-0000-000000000000';
41
+ /** Build the JSON-RPC envelope body for an `actor_search` call. */
42
+ const search_envelope = (params, id) => JSON.stringify({ jsonrpc: '2.0', method: actor_search_action_spec.method, params, id });
43
+ export const describe_actor_search_cross_tests = (options) => {
44
+ const { setup_test } = options;
45
+ const rpc_path = options.rpc_path ?? SPINE_RPC_PATH;
46
+ describe('actor_search parity', () => {
47
+ test('anonymous → 401 (account-grain auth gate)', async () => {
48
+ const fixture = await setup_test();
49
+ const res = await fixture.fresh_transport()(rpc_path, {
50
+ method: 'POST',
51
+ headers: { 'content-type': 'application/json' },
52
+ body: search_envelope({ query: 'a' }, 'anon-search'),
53
+ });
54
+ assert.strictEqual(res.status, 401, 'unauthenticated actor_search must be refused');
55
+ });
56
+ test('non-admin + no scope_ids → 400 actor_search_scope_required', async () => {
57
+ const fixture = await setup_test();
58
+ const account = await fixture.create_account();
59
+ const res = await fixture.fresh_transport()(rpc_path, {
60
+ method: 'POST',
61
+ headers: { ...account.create_session_headers(), 'content-type': 'application/json' },
62
+ body: search_envelope({ query: 'a' }, 'nonadmin-noscope'),
63
+ });
64
+ assert.strictEqual(res.status, 400, 'non-admin unbounded search must be rejected');
65
+ const body = (await res.json());
66
+ assert.strictEqual(body.error?.data?.reason, ERROR_ACTOR_SEARCH_SCOPE_REQUIRED, 'rejection carries the scope-required reason');
67
+ });
68
+ test('non-admin + scope_ids → 200 (scope bypasses admin gate)', async () => {
69
+ const fixture = await setup_test();
70
+ const account = await fixture.create_account();
71
+ const res = await fixture.fresh_transport()(rpc_path, {
72
+ method: 'POST',
73
+ headers: { ...account.create_session_headers(), 'content-type': 'application/json' },
74
+ body: search_envelope({ query: 'a', scope_ids: [NIL_UUID] }, 'nonadmin-scope'),
75
+ });
76
+ assert.strictEqual(res.status, 200, 'non-admin with a scope filter is allowed');
77
+ const body = (await res.json());
78
+ assert(Array.isArray(body.result?.actors), 'response carries an actors array');
79
+ });
80
+ test('admin + no scope_ids → 200 (admin reaches unbounded search)', async () => {
81
+ const fixture = await setup_test();
82
+ const res = await fixture.transport(rpc_path, {
83
+ method: 'POST',
84
+ headers: { ...fixture.create_session_headers(), 'content-type': 'application/json' },
85
+ body: search_envelope({ query: 'a' }, 'admin-noscope'),
86
+ });
87
+ assert.strictEqual(res.status, 200, 'admin unbounded search is allowed');
88
+ const body = (await res.json());
89
+ assert(Array.isArray(body.result?.actors), 'response carries an actors array');
90
+ });
91
+ });
92
+ };
@@ -0,0 +1,6 @@
1
+ import '../assert_dev_env.js';
2
+ import type { CellCrossTestOptions } from './cell_cross_helpers.js';
3
+ /** Options for the app-settings effect suite (shares the cell/origin shape). */
4
+ export type AppSettingsCrossTestOptions = CellCrossTestOptions;
5
+ export declare const describe_app_settings_cross_tests: (options: AppSettingsCrossTestOptions) => void;
6
+ //# sourceMappingURL=app_settings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app_settings.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/app_settings.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAsC9B,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,yBAAyB,CAAC;AAGlE,gFAAgF;AAChF,MAAM,MAAM,2BAA2B,GAAG,oBAAoB,CAAC;AAiB/D,eAAO,MAAM,iCAAiC,GAAI,SAAS,2BAA2B,KAAG,IA8DxF,CAAC"}