@fuzdev/fuz_app 0.84.0 → 0.85.1

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.
@@ -13,9 +13,13 @@ testing-patterns. This file is a reference index for the helpers themselves.
13
13
 
14
14
  ## Production guard — always the first import
15
15
 
16
- Every module here starts with `import './assert_dev_env.js';` — reads `DEV`
17
- from `esm-env` and throws if false, preventing production-bundle inclusion.
18
- Enforced by grep, not a linter; make this the first line in new modules.
16
+ Every runtime-reachable module here starts with `import './assert_dev_env.js';`
17
+ — reads `DEV` from `esm-env` and throws if false, preventing production-bundle
18
+ inclusion. Make this the first line in new modules. Enforced by
19
+ `src/test/testing/assert_dev_env_coverage.test.ts`, which fails if any module
20
+ omits the guard. The sole exemption is `cross_backend/make_cross_backend_project.ts`
21
+ — a vitest-project factory consumed by consumers' `vite.config.ts` at config
22
+ time (never runtime), where a throwing guard would break `vite build`.
19
23
 
20
24
  ## Stubs, factories, mocks
21
25
 
@@ -121,8 +125,7 @@ factories accept any migration namespace set.
121
125
  - `create_pg_factory(init_schema, test_url?)` — PostgreSQL; `skip: true` when `test_url` missing. Drops `schema_version` before `init_schema` so migrations re-evaluate against actual tables (prevents stale tracker rows from skipping migrations when DDL changes between sessions). Pool reused + cleaned up across `create()` calls.
122
126
  - `auth_truncate_tables` — `['invite', 'api_token', 'auth_session', 'role_grant', 'role_grant_offer', 'actor', 'account']` in FK-safe order. Excludes `audit_log` — unit DB tests don't need to truncate it.
123
127
  - `auth_integration_truncate_tables` — `auth_truncate_tables + ['audit_log']` for integration suites that exercise the audit path.
124
- - `auth_drop_tables` — full set from `auth_migrations` in drop order; call `drop_auth_schema(db)` at the top of `init_schema` on persistent pg databases that may hold stale DDL from previous fuz_app versions.
125
- - `drop_auth_schema(db)` — `DROP TABLE IF EXISTS <table> CASCADE` for every entry in `auth_drop_tables` plus `schema_version`. Safe on fresh DBs.
128
+ - `drop_auth_schema(db)` — `DROP SCHEMA public CASCADE; CREATE SCHEMA public`; call at the top of `init_schema` on persistent pg databases that may hold stale DDL from previous fuz_app versions. Drift-proof full-schema reset (despite the name, not auth-scoped) — same mechanism as `reset_pglite`.
126
129
  - `create_describe_db(factories, truncate_tables)` — returns `describe_db(name, fn)` running `fn(get_db)` once per factory inside a `describe` with shared `beforeAll(create)` + `beforeEach(TRUNCATE)` + `afterAll(close)`. Skipped factories use `describe.skip`.
127
130
  - `log_db_factory_status(factories)` — console summary of enabled / skipped factories.
128
131
 
@@ -275,6 +278,7 @@ RPC / WS structural invariants (options-free, apply over `surface.rpc_endpoints`
275
278
  * `assert_ws_method_descriptions_present` — every WS method on every endpoint has a non-empty `description`.
276
279
  * `assert_ws_endpoints_include_protocol_actions` — every WS endpoint includes `heartbeat` + `cancel` (the `protocol_actions` spread from `actions/protocol.js`).
277
280
  * `assert_ws_notifications_have_null_auth` — WS method `kind === 'remote_notification' ⟺ auth === null`; guards against drift between spec union and surface emitter.
281
+ * `assert_no_testing_methods` — no `_testing_*` backdoor action (`TESTING_METHOD_PREFIX`) appears as an RPC or WS method on the declared surface. The test-binary actions are live-mounted only; this guards against a future wiring change folding `create_testing_actions(...)` into the surface-generating registry.
278
282
 
279
283
  Per-endpoint duplicate method names and the auth-shape biconditional are
280
284
  already enforced at startup by `compile_action_registry` (see
@@ -1209,6 +1213,29 @@ spine bootstrap (auth + cell + cell_history + fact) and is regenerated +
1209
1213
  drift-guarded by `src/test/cross_backend/spine_expected_schema.db.test.ts`
1210
1214
  (`UPDATE_SCHEMA_READY=1`, then `gro format`).
1211
1215
 
1216
+ ### Testing-backdoor credential gate — `cross_backend/testing_backdoor.ts`
1217
+
1218
+ `describe_testing_backdoor_cross_tests({setup_test, capabilities, rpc_path?})` —
1219
+ the negative-credential parity suite for the `_testing_*` backdoor actions.
1220
+ For each of `_testing_reset` / `_testing_mint_session` / `_testing_put_fact` /
1221
+ `_testing_schema_snapshot` (the three privileged writes plus the schema-dump
1222
+ read) it fires three principals over real HTTP: **anonymous** → 401, **session** →
1223
+ 403 `credential_type_required`, **bearer** → 403 `credential_type_required` —
1224
+ proving the daemon-token gate that fences each backdoor action holds end-to-end
1225
+ on the real dispatcher (the spec-derived `describe_rpc_attack_surface_tests`
1226
+ never enumerates them because they're off the declared surface). Each method is
1227
+ sent with **valid** params so the session/bearer cases clear the 400
1228
+ input-validation phase and reach the 403 credential gate (order is
1229
+ 401 → 400 → 403); the handler never runs. Cited property: `docs/security.md`
1230
+ §Test Backdoor Actions. Cross-process only (the `_testing_*` actions are
1231
+ mounted on the spawned binary, not the in-process app — like the ws/sse
1232
+ suites); ungated, since every cross backend that uses
1233
+ `default_cross_process_setup` mounts them. fuz_app's own wiring is
1234
+ `src/test/cross_backend/testing_backdoor.cross.test.ts`. The complement is the
1235
+ in-process spec-level gate test (`src/test/testing/testing_actions_auth.test.ts`,
1236
+ asserting each spec declares `credential_types: ['daemon_token']`) + the
1237
+ `assert_no_testing_methods` surface invariant.
1238
+
1212
1239
  ### Building a TS test-server binary — `testing_server_core.ts` + adapters
1213
1240
 
1214
1241
  The reusable shape for standing up a **spawnable TS** cross-process test
@@ -0,0 +1,6 @@
1
+ import '../assert_dev_env.js';
2
+ import type { CellCrossTestOptions } from './cell_cross_helpers.js';
3
+ /** Options for the testing-backdoor negative-credential suite. */
4
+ export type TestingBackdoorCrossTestOptions = CellCrossTestOptions;
5
+ export declare const describe_testing_backdoor_cross_tests: (options: TestingBackdoorCrossTestOptions) => void;
6
+ //# sourceMappingURL=testing_backdoor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing_backdoor.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_backdoor.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAyD9B,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,yBAAyB,CAAC;AAgElE,kEAAkE;AAClE,MAAM,MAAM,+BAA+B,GAAG,oBAAoB,CAAC;AAEnE,eAAO,MAAM,qCAAqC,GACjD,SAAS,+BAA+B,KACtC,IAiCF,CAAC"}
@@ -0,0 +1,126 @@
1
+ import '../assert_dev_env.js';
2
+ /**
3
+ * Cross-backend negative-credential suite for the `_testing_*` backdoor
4
+ * actions.
5
+ *
6
+ * `_testing_reset` / `_testing_mint_session` / `_testing_put_fact` /
7
+ * `_testing_schema_snapshot` are privileged test-binary actions the
8
+ * production wire never exposes — three direct DB writes (full auth wipe,
9
+ * forged session row, raw fact insert) plus a full-schema introspection read
10
+ * (the highest info-leak of the set were the gate to break). Their only
11
+ * structural fence is the **daemon-token** credential gate on
12
+ * each spec's `auth` axis. A test binary live-mounts them on its RPC
13
+ * endpoint but keeps them off the declared surface — so the spec-derived
14
+ * `describe_rpc_attack_surface_tests` never enumerates them, and nothing
15
+ * else fires them with a non-daemon credential to prove the gate holds
16
+ * end-to-end. This suite does, against each impl's real auth resolution.
17
+ *
18
+ * For every backdoor method, three principals:
19
+ *
20
+ * - **anonymous** (no credential) → `401` (pre-validation auth refuses an
21
+ * account-less caller before anything else).
22
+ * - **session** (the keeper's browser-context cookie) → `403`
23
+ * `credential_type_required` — a session cookie, even one carrying the
24
+ * keeper role, tops out below the daemon-token channel.
25
+ * - **bearer** (the keeper's api-token, non-browser context) → `403`
26
+ * `credential_type_required` — same ceiling; an api token cannot reach
27
+ * keeper operations.
28
+ *
29
+ * Each method is sent with **valid** params so the session/bearer cases
30
+ * clear the dispatcher's input-validation (400) phase and actually reach the
31
+ * post-authorization credential gate (the order is 401 → 400 → 403); the
32
+ * handler never runs (the gate refuses first), so the writes never execute.
33
+ *
34
+ * Complements the spec-level gate check (which pins that each spec *declares*
35
+ * `credential_types: ['daemon_token']`) and the surface-absence invariant
36
+ * (`assert_no_testing_methods`) — this one pins the runtime 401/403 behavior
37
+ * on both impls. Cited property: `security.md` §Test Backdoor Actions
38
+ * (daemon-token-gated, off-surface, DEV-excluded).
39
+ *
40
+ * Cross-process only — the `_testing_*` actions are mounted on the spawned
41
+ * binary, not the in-process app — like the ws/sse suites. Wire from a
42
+ * `*.cross.test.ts`. Requires the standard `_testing_*` actions mounted (the
43
+ * same precondition `default_cross_process_setup` already imposes for its
44
+ * per-test `_testing_reset`); ungated, since every cross backend mounts them.
45
+ *
46
+ * `$lib`-free by contract (relative specifiers only), like the sibling
47
+ * cross-backend suites.
48
+ *
49
+ * @module
50
+ */
51
+ import { describe, test, assert } from 'vitest';
52
+ import { ERROR_CREDENTIAL_TYPE_REQUIRED } from '../../http/error_schemas.js';
53
+ import { rpc_call } from '../rpc_helpers.js';
54
+ import { SPINE_RPC_PATH } from './default_spine_surface.js';
55
+ /** A well-formed UUID that never names a real row. */
56
+ const NIL_UUID = '00000000-0000-0000-0000-000000000000';
57
+ /**
58
+ * The backdoor methods + a **valid** params payload each (so the
59
+ * session/bearer cases reach the 403 credential gate rather than a 400 on
60
+ * input validation). The handlers never run — the gate refuses first.
61
+ */
62
+ const backdoor_methods = [
63
+ { method: '_testing_reset', params: {} },
64
+ { method: '_testing_mint_session', params: { account_id: NIL_UUID, expires_in_seconds: -60 } },
65
+ { method: '_testing_put_fact', params: { content: 'backdoor-probe' } },
66
+ // The schema-dump read — `exclude_tables` is optional, so `{}` is valid
67
+ // and clears the 400 phase like the writes above.
68
+ { method: '_testing_schema_snapshot', params: {} },
69
+ ];
70
+ const principals = [
71
+ {
72
+ name: 'anonymous',
73
+ status: 401,
74
+ // Fresh jar so the keeper cookie (cross-process) can't leak in.
75
+ resolve: (f) => ({ transport: f.fresh_transport(), headers: {} }),
76
+ },
77
+ {
78
+ name: 'session',
79
+ status: 403,
80
+ reason: ERROR_CREDENTIAL_TYPE_REQUIRED,
81
+ resolve: (f) => ({ transport: f.transport, headers: f.create_session_headers() }),
82
+ },
83
+ {
84
+ name: 'bearer',
85
+ status: 403,
86
+ reason: ERROR_CREDENTIAL_TYPE_REQUIRED,
87
+ // Bearer is discarded in a browser context, so suppress Origin (empty
88
+ // jar + no Origin) — the credential must actually resolve so the refusal
89
+ // lands on the credential-type gate, not on bearer-discard (→ 401).
90
+ resolve: (f) => ({
91
+ transport: f.fresh_transport({ origin: null }),
92
+ headers: f.create_bearer_headers(),
93
+ suppress_default_origin: true,
94
+ }),
95
+ },
96
+ ];
97
+ export const describe_testing_backdoor_cross_tests = (options) => {
98
+ const { setup_test } = options;
99
+ const rpc_path = options.rpc_path ?? SPINE_RPC_PATH;
100
+ describe('testing backdoor credential gate parity', () => {
101
+ for (const { method, params } of backdoor_methods) {
102
+ for (const principal of principals) {
103
+ test(`${method} rejects ${principal.name} → ${principal.status}`, async () => {
104
+ const fixture = await setup_test();
105
+ const { transport, headers, suppress_default_origin } = principal.resolve(fixture);
106
+ const res = await rpc_call({
107
+ app: transport,
108
+ path: rpc_path,
109
+ method,
110
+ params,
111
+ headers,
112
+ ...(suppress_default_origin && { suppress_default_origin: true }),
113
+ });
114
+ const label = `${method} ${principal.name}`;
115
+ assert.ok(!res.ok, `${label}: expected denial (${principal.status}) but the call succeeded`);
116
+ assert.strictEqual(res.status, principal.status, `${label}: status`);
117
+ // `!res.ok` narrows `res` to the error variant for `res.error`.
118
+ if (principal.reason !== undefined && !res.ok) {
119
+ const reason = res.error.data?.reason;
120
+ assert.strictEqual(reason, principal.reason, `${label}: error.data.reason`);
121
+ }
122
+ });
123
+ }
124
+ }
125
+ });
126
+ };
@@ -183,9 +183,18 @@ export declare const testing_drain_effects_action_spec: {
183
183
  * `_testing_mint_session` — mint an expired-by-construction server-side
184
184
  * session for an existing account and return its signed cookie value.
185
185
  *
186
- * The minted `auth_session` row's `expires_at` is backdated (negative
187
- * `expires_in_seconds`) while the returned cookie's own signed payload
188
- * stays valid (future). Cross-process auth resolution therefore passes the
186
+ * `expires_in_seconds` is **constrained negative** (`z.number().int().negative()`)
187
+ * so the action is structurally incapable of minting a *usable* session: it
188
+ * can only produce an already-backdated, already-dead `auth_session` row. The
189
+ * daemon-token gate + loopback binding already fence the backdoor, but the
190
+ * negative constraint is the make-impossible-states floor — even a misuse
191
+ * can't forge a valid session for an arbitrary `account_id`. The Rust mirror
192
+ * (`fuz_testing::create_testing_mint_session_action_spec`) enforces the same
193
+ * floor.
194
+ *
195
+ * The minted `auth_session` row's `expires_at` is backdated while the
196
+ * returned cookie's own signed payload stays valid (future). Cross-process
197
+ * auth resolution therefore passes the
189
198
  * cookie-payload gate (`parse_session`) and is refused by the authoritative
190
199
  * DB-row gate (`query_session_get_valid` — `WHERE expires_at > NOW()`) —
191
200
  * the gate the in-process payload-expiry tests never reach and the one that
@@ -1 +1 @@
1
- {"version":3,"file":"testing_reset_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_reset_actions.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAItB,OAAO,EAAa,KAAK,SAAS,EAAC,MAAM,6BAA6B,CAAC;AAGvE,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,8BAA8B,CAAC;AACjE,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,4BAA4B,CAAC;AACjE,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,gBAAgB,CAAC;AAmBvC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;QAiBpC;;;;;;;;WAQG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWyC,CAAC;AAE/C;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,iCAAiC;;;;;;;;;;;;;;;;CAWA,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;;;;;;CAeC,CAAC;AAE/C;;;;;;GAMG;AACH,eAAO,MAAM,mCAAmC,QAAO,SACiB,CAAC;AAEzE;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;CAeK,CAAC;AAE/C;;;;;;;;;GASG;AACH,eAAO,MAAM,mCAAmC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWF,CAAC;AAE/C;;;;GAIG;AACH,eAAO,MAAM,qCAAqC,QAAO,SAGvD,CAAC;AAEH,4CAA4C;AAC5C,MAAM,WAAW,2BAA2B;IAC3C;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACjD;;;;;OAKG;IACH,QAAQ,CAAC,kBAAkB,EAAE,gBAAgB,CAAC;IAC9C;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACxD;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,OAAO,EACb,SAAS,2BAA2B,KAClC,KAAK,CAAC,SAAS,CAqIjB,CAAC;AAEF,0FAA0F;AAC1F,eAAO,MAAM,0BAA0B,UAAmC,CAAC"}
1
+ {"version":3,"file":"testing_reset_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_reset_actions.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAItB,OAAO,EAAa,KAAK,SAAS,EAAC,MAAM,6BAA6B,CAAC;AAIvE,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,8BAA8B,CAAC;AACjE,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,4BAA4B,CAAC;AACjE,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,gBAAgB,CAAC;AAkCvC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;QAiBpC;;;;;;;;WAQG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWyC,CAAC;AAE/C;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,iCAAiC;;;;;;;;;;;;;;;;CAWA,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;;;;;;CAwBC,CAAC;AAE/C;;;;;;GAMG;AACH,eAAO,MAAM,mCAAmC,QAAO,SACiB,CAAC;AAEzE;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;CAeK,CAAC;AAE/C;;;;;;;;;GASG;AACH,eAAO,MAAM,mCAAmC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWF,CAAC;AAE/C;;;;GAIG;AACH,eAAO,MAAM,qCAAqC,QAAO,SAGvD,CAAC;AAEH,4CAA4C;AAC5C,MAAM,WAAW,2BAA2B;IAC3C;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACjD;;;;;OAKG;IACH,QAAQ,CAAC,kBAAkB,EAAE,gBAAgB,CAAC;IAC9C;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACxD;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,OAAO,EACb,SAAS,2BAA2B,KAClC,KAAK,CAAC,SAAS,CAqIjB,CAAC;AAEF,0FAA0F;AAC1F,eAAO,MAAM,0BAA0B,UAAmC,CAAC"}
@@ -68,6 +68,20 @@ import { auth_integration_truncate_tables } from '../db.js';
68
68
  import { query_schema_snapshot, SchemaSnapshot } from '../schema_introspect.js';
69
69
  import { query_create_actor } from '../../auth/account_queries.js';
70
70
  import { create_test_account_with_credentials, mint_test_session, DEFAULT_TEST_PASSWORD, } from '../app_server.js';
71
+ /**
72
+ * Shared `auth` axis for every `_testing_*` action: keeper-only via the
73
+ * daemon-token credential, no acting actor. This is the entire structural
74
+ * fence on the backdoor surface (these actions run direct DB writes the
75
+ * production wire never exposes), so all five specs reference this one const
76
+ * rather than re-declaring it — a single source of truth the gate test
77
+ * (`testing_actions_auth.test.ts`) pins. Mirrors the Rust `DAEMON_TOKEN_ONLY`
78
+ * / shared `AuthSpec` in `fuz_testing`.
79
+ */
80
+ const TESTING_ACTION_AUTH = {
81
+ account: 'required',
82
+ actor: 'none',
83
+ credential_types: ['daemon_token'],
84
+ };
71
85
  /** Output shape for an individual seeded account (keeper or extra). */
72
86
  const SeededAccountShape = z.strictObject({
73
87
  account: z.strictObject({ id: Uuid, username: z.string() }),
@@ -104,7 +118,7 @@ export const testing_reset_action_spec = {
104
118
  method: '_testing_reset',
105
119
  kind: 'request_response',
106
120
  initiator: 'frontend',
107
- auth: { account: 'required', actor: 'none', credential_types: ['daemon_token'] },
121
+ auth: TESTING_ACTION_AUTH,
108
122
  side_effects: true,
109
123
  input: z.strictObject({
110
124
  extra_keeper_roles: z.array(z.string()).optional(),
@@ -154,7 +168,7 @@ export const testing_drain_effects_action_spec = {
154
168
  method: '_testing_drain_effects',
155
169
  kind: 'request_response',
156
170
  initiator: 'frontend',
157
- auth: { account: 'required', actor: 'none', credential_types: ['daemon_token'] },
171
+ auth: TESTING_ACTION_AUTH,
158
172
  side_effects: false,
159
173
  input: z.void(),
160
174
  output: z.strictObject({ ok: z.boolean() }),
@@ -165,9 +179,18 @@ export const testing_drain_effects_action_spec = {
165
179
  * `_testing_mint_session` — mint an expired-by-construction server-side
166
180
  * session for an existing account and return its signed cookie value.
167
181
  *
168
- * The minted `auth_session` row's `expires_at` is backdated (negative
169
- * `expires_in_seconds`) while the returned cookie's own signed payload
170
- * stays valid (future). Cross-process auth resolution therefore passes the
182
+ * `expires_in_seconds` is **constrained negative** (`z.number().int().negative()`)
183
+ * so the action is structurally incapable of minting a *usable* session: it
184
+ * can only produce an already-backdated, already-dead `auth_session` row. The
185
+ * daemon-token gate + loopback binding already fence the backdoor, but the
186
+ * negative constraint is the make-impossible-states floor — even a misuse
187
+ * can't forge a valid session for an arbitrary `account_id`. The Rust mirror
188
+ * (`fuz_testing::create_testing_mint_session_action_spec`) enforces the same
189
+ * floor.
190
+ *
191
+ * The minted `auth_session` row's `expires_at` is backdated while the
192
+ * returned cookie's own signed payload stays valid (future). Cross-process
193
+ * auth resolution therefore passes the
171
194
  * cookie-payload gate (`parse_session`) and is refused by the authoritative
172
195
  * DB-row gate (`query_session_get_valid` — `WHERE expires_at > NOW()`) —
173
196
  * the gate the in-process payload-expiry tests never reach and the one that
@@ -186,11 +209,19 @@ export const testing_mint_session_action_spec = {
186
209
  method: '_testing_mint_session',
187
210
  kind: 'request_response',
188
211
  initiator: 'frontend',
189
- auth: { account: 'required', actor: 'none', credential_types: ['daemon_token'] },
212
+ auth: TESTING_ACTION_AUTH,
190
213
  side_effects: true,
191
214
  input: z.strictObject({
192
215
  account_id: Uuid,
193
- expires_in_seconds: z.number().int(),
216
+ expires_in_seconds: z
217
+ .number()
218
+ .int()
219
+ .negative()
220
+ .meta({
221
+ description: 'Seconds to offset the session row from NOW(). Constrained negative so this ' +
222
+ 'backdoor can ONLY mint an already-expired (backdated) row — never a usable ' +
223
+ 'session for an arbitrary account. Its sole use is the expired-session gate.',
224
+ }),
194
225
  }),
195
226
  output: z.strictObject({ session_cookie: z.string() }),
196
227
  async: true,
@@ -223,7 +254,7 @@ export const testing_put_fact_action_spec = {
223
254
  method: '_testing_put_fact',
224
255
  kind: 'request_response',
225
256
  initiator: 'frontend',
226
- auth: { account: 'required', actor: 'none', credential_types: ['daemon_token'] },
257
+ auth: TESTING_ACTION_AUTH,
227
258
  side_effects: true,
228
259
  input: z.strictObject({
229
260
  content: z.string(),
@@ -248,7 +279,7 @@ export const testing_schema_snapshot_action_spec = {
248
279
  method: '_testing_schema_snapshot',
249
280
  kind: 'request_response',
250
281
  initiator: 'frontend',
251
- auth: { account: 'required', actor: 'none', credential_types: ['daemon_token'] },
282
+ auth: TESTING_ACTION_AUTH,
252
283
  side_effects: false,
253
284
  input: z.strictObject({ exclude_tables: z.array(z.string()).optional() }),
254
285
  output: SchemaSnapshot,
@@ -127,14 +127,25 @@ export interface StartTestingServerOptions {
127
127
  /** Optional logger; defaults to a `[daemon_name]`-namespaced `Logger`. */
128
128
  log?: LoggerType;
129
129
  }
130
+ /**
131
+ * Loopback bind hosts — the only ones the test binary may serve on. It ships
132
+ * deterministic dev secrets (fixed cookie keys + bootstrap token in
133
+ * `default_secrets.ts`), so binding any network-reachable interface would let
134
+ * anyone who knows those fixed keys forge cookies against it. An allowlist
135
+ * (not an `0.0.0.0`/`::` blocklist) closes the gap a concrete LAN/public
136
+ * interface IP — e.g. `--host 192.168.1.50` — would otherwise slip through.
137
+ * Covers `localhost`, the IPv4 loopback `127.0.0.0/8`, and IPv6 `::1`.
138
+ */
139
+ export declare const is_loopback_host: (host: string) => boolean;
130
140
  /**
131
141
  * Boot a test-mode server using the supplied runtime adapter.
132
142
  *
133
143
  * Mirrors a production `start_server` at the surface level — stale-daemon
134
144
  * check, daemon-info write, bind, graceful drain — but the app is the
135
145
  * caller's no-domain (or domain) {@link StartTestingServerOptions.build_app}
136
- * and the runtime boundary is the {@link TestingServerAdapter}. Refuses to
137
- * bind an open host (the test binary must stay on loopback).
146
+ * and the runtime boundary is the {@link TestingServerAdapter}. Refuses any
147
+ * non-loopback bind host (the test binary must stay on loopback — see
148
+ * `is_loopback_host`).
138
149
  */
139
150
  export declare const start_testing_server: (options: StartTestingServerOptions) => Promise<void>;
140
151
  //# sourceMappingURL=testing_server_core.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"testing_server_core.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_server_core.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAC,OAAO,EAAE,IAAI,EAAC,MAAM,MAAM,CAAC;AACxC,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,SAAS,CAAC;AAC9C,OAAO,EAAS,KAAK,MAAM,IAAI,UAAU,EAAC,MAAM,yBAAyB,CAAC;AAG1E,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,uBAAuB,CAAC;AAEvD;;;;;;;GAOG;AACH,MAAM,WAAW,WAAW;IAC3B,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,iFAAiF;IACjF,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IACjC,iBAAiB,EAAE,gBAAgB,CAAC;IACpC,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;CACjD;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACpC,6EAA6E;IAC7E,aAAa,EAAE,MAAM,CAAC;IACtB,0FAA0F;IAC1F,OAAO,EAAE,WAAW,CAAC;IACrB,6DAA6D;IAC7D,iBAAiB,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,CAAC;IACtD,mFAAmF;IACnF,iBAAiB,EAAE,CAAC,GAAG,EAAE,IAAI,KAAK,iBAAiB,CAAC;IACpD,8EAA8E;IAC9E,KAAK,EAAE,CAAC,OAAO,EAAE;QAAC,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAC,KAAK,WAAW,CAAC;IACxF,+CAA+C;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,yEAAyE;IACzE,yBAAyB,EAAE,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC;IAClE,oEAAoE;IACpE,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,KAAK,CAAC;CAC9B;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,eAAe;IAC/B,kEAAkE;IAClE,GAAG,EAAE,IAAI,CAAC;IACV,qEAAqE;IACrE,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,uEAAuE;IACvE,eAAe,CAAC,EAAE,CAAC,iBAAiB,EAAE,gBAAgB,KAAK,IAAI,CAAC;CAChE;AAED,gDAAgD;AAChD,MAAM,WAAW,yBAAyB;IACzC,+CAA+C;IAC/C,OAAO,EAAE,oBAAoB,CAAC;IAC9B;;;;;;OAMG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,SAAS,EAAE,MAAM,OAAO,CAAC,eAAe,CAAC,CAAC;IAC1C,0EAA0E;IAC1E,GAAG,CAAC,EAAE,UAAU,CAAC;CACjB;AAKD;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,GAAU,SAAS,yBAAyB,KAAG,OAAO,CAAC,IAAI,CA4D3F,CAAC"}
1
+ {"version":3,"file":"testing_server_core.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_server_core.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAC,OAAO,EAAE,IAAI,EAAC,MAAM,MAAM,CAAC;AACxC,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,SAAS,CAAC;AAC9C,OAAO,EAAS,KAAK,MAAM,IAAI,UAAU,EAAC,MAAM,yBAAyB,CAAC;AAG1E,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,uBAAuB,CAAC;AAEvD;;;;;;;GAOG;AACH,MAAM,WAAW,WAAW;IAC3B,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,iFAAiF;IACjF,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IACjC,iBAAiB,EAAE,gBAAgB,CAAC;IACpC,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;CACjD;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACpC,6EAA6E;IAC7E,aAAa,EAAE,MAAM,CAAC;IACtB,0FAA0F;IAC1F,OAAO,EAAE,WAAW,CAAC;IACrB,6DAA6D;IAC7D,iBAAiB,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,CAAC;IACtD,mFAAmF;IACnF,iBAAiB,EAAE,CAAC,GAAG,EAAE,IAAI,KAAK,iBAAiB,CAAC;IACpD,8EAA8E;IAC9E,KAAK,EAAE,CAAC,OAAO,EAAE;QAAC,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAC,KAAK,WAAW,CAAC;IACxF,+CAA+C;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,yEAAyE;IACzE,yBAAyB,EAAE,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC;IAClE,oEAAoE;IACpE,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,KAAK,CAAC;CAC9B;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,eAAe;IAC/B,kEAAkE;IAClE,GAAG,EAAE,IAAI,CAAC;IACV,qEAAqE;IACrE,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,uEAAuE;IACvE,eAAe,CAAC,EAAE,CAAC,iBAAiB,EAAE,gBAAgB,KAAK,IAAI,CAAC;CAChE;AAED,gDAAgD;AAChD,MAAM,WAAW,yBAAyB;IACzC,+CAA+C;IAC/C,OAAO,EAAE,oBAAoB,CAAC;IAC9B;;;;;;OAMG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,SAAS,EAAE,MAAM,OAAO,CAAC,eAAe,CAAC,CAAC;IAC1C,0EAA0E;IAC1E,GAAG,CAAC,EAAE,UAAU,CAAC;CACjB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,GAAI,MAAM,MAAM,KAAG,OAG/C,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,GAAU,SAAS,yBAAyB,KAAG,OAAO,CAAC,IAAI,CA4D3F,CAAC"}
@@ -1,24 +1,36 @@
1
1
  import '../assert_dev_env.js';
2
2
  import { Logger } from '@fuzdev/fuz_util/log.js';
3
3
  import { write_daemon_info, read_daemon_info, is_daemon_running } from '../../cli/daemon.js';
4
- /** Hosts that expose the test binary beyond loopback — refused at startup. */
5
- const OPEN_HOSTS = new Set(['0.0.0.0', '::', '[::]']);
4
+ /**
5
+ * Loopback bind hosts — the only ones the test binary may serve on. It ships
6
+ * deterministic dev secrets (fixed cookie keys + bootstrap token in
7
+ * `default_secrets.ts`), so binding any network-reachable interface would let
8
+ * anyone who knows those fixed keys forge cookies against it. An allowlist
9
+ * (not an `0.0.0.0`/`::` blocklist) closes the gap a concrete LAN/public
10
+ * interface IP — e.g. `--host 192.168.1.50` — would otherwise slip through.
11
+ * Covers `localhost`, the IPv4 loopback `127.0.0.0/8`, and IPv6 `::1`.
12
+ */
13
+ export const is_loopback_host = (host) => {
14
+ const h = host.replace(/^\[(.*)\]$/, '$1'); // unwrap an `[::1]`-style IPv6 literal
15
+ return h === 'localhost' || h === '::1' || /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(h);
16
+ };
6
17
  /**
7
18
  * Boot a test-mode server using the supplied runtime adapter.
8
19
  *
9
20
  * Mirrors a production `start_server` at the surface level — stale-daemon
10
21
  * check, daemon-info write, bind, graceful drain — but the app is the
11
22
  * caller's no-domain (or domain) {@link StartTestingServerOptions.build_app}
12
- * and the runtime boundary is the {@link TestingServerAdapter}. Refuses to
13
- * bind an open host (the test binary must stay on loopback).
23
+ * and the runtime boundary is the {@link TestingServerAdapter}. Refuses any
24
+ * non-loopback bind host (the test binary must stay on loopback — see
25
+ * `is_loopback_host`).
14
26
  */
15
27
  export const start_testing_server = async (options) => {
16
28
  const { adapter, daemon_name, host, port, app_version, build_app } = options;
17
29
  const log = options.log ?? new Logger(`[${daemon_name}]`);
18
30
  const { runtime } = adapter;
19
- if (OPEN_HOSTS.has(host)) {
20
- log.error(`FATAL: binding to '${host}' exposes the test binary to your entire network. ` +
21
- `Use --host localhost (default) or 127.0.0.1 instead.`);
31
+ if (!is_loopback_host(host)) {
32
+ log.error(`FATAL: binding to '${host}' exposes the test binary (which ships deterministic ` +
33
+ `dev secrets) beyond loopback. Use --host localhost (default), 127.0.0.1, or ::1 instead.`);
22
34
  adapter.exit(1);
23
35
  }
24
36
  const stale = await read_daemon_info(runtime, daemon_name);
@@ -71,25 +71,18 @@ export declare const auth_truncate_tables: string[];
71
71
  */
72
72
  export declare const auth_integration_truncate_tables: string[];
73
73
  /**
74
- * All auth tables in drop order (children first for FK safety).
75
- *
76
- * The full set created by `auth_migrations` — use for clean-slate
77
- * test DB initialization. `auth_truncate_tables` is the subset for
78
- * between-test data cleanup (excludes `audit_log`).
79
- *
80
- * When adding tables to `auth_migrations`, add them here too.
81
- */
82
- export declare const auth_drop_tables: readonly ["app_settings", "invite", "audit_log", "api_token", "auth_session", "role_grant", "role_grant_offer", "actor", "account", "bootstrap_lock"];
83
- /**
84
- * Drop all auth tables and schema version tracking for a clean slate.
74
+ * Reset the entire `public` schema for a clean slate before re-migration.
85
75
  *
86
76
  * Recommended at the start of `init_schema` callbacks for `create_pg_factory`.
87
- * Persistent test databases can accumulate stale schema from previous fuz_app
88
- * versions this ensures migrations run against a truly empty database.
89
- * Safe on fresh databases (`IF EXISTS` on all statements). No-op effect for
90
- * PGlite (already fresh), but harmless to call unconditionally.
77
+ * Persistent test databases accumulate stale DDL across fuz_app versions;
78
+ * `DROP SCHEMA public CASCADE; CREATE SCHEMA public` wipes every table, type,
79
+ * and sequence regardless of namespace, so migrations always run against a
80
+ * truly empty database. Drift-proof unlike a hand-maintained drop list it
81
+ * needs no upkeep when the schema gains a table. Despite the historical name,
82
+ * this resets the whole schema, not just auth tables (the only documented use
83
+ * is clean-slate re-migration, which always wanted a full reset).
91
84
  *
92
- * @mutates db - drops every table in `auth_drop_tables` plus `schema_version`.
85
+ * @mutates db - drops and recreates the `public` schema; all tables gone.
93
86
  */
94
87
  export declare const drop_auth_schema: (db: Db) => Promise<void>;
95
88
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"db.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/db.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AA6B7B,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AAKpC;;GAEG;AACH,eAAO,MAAM,KAAK,SAA4B,CAAC;AAE/C;;GAEG;AACH,MAAM,WAAW,SAAS;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,OAAO,CAAC,EAAE,CAAC,CAAC;IAC1B,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,IAAI,EAAE,OAAO,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,GAAU,IAAI,EAAE,KAAG,OAAO,CAAC,IAAI,CAGvD,CAAC;AAMF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,qBAAqB,GAAI,aAAa,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,KAAG,SAkB7E,CAAC;AAEH;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,iBAAiB,GAC7B,aAAa,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,EACtC,WAAW,MAAM,KACf,SA2DF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,UAQhC,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,gCAAgC,UAAyC,CAAC;AAEvF;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,uJAWnB,CAAC;AAEX;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gBAAgB,GAAU,IAAI,EAAE,KAAG,OAAO,CAAC,IAAI,CAK3D,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,kBAAkB,GAC9B,WAAW,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC,EACvC,iBAAiB,KAAK,CAAC,MAAM,CAAC,KAC5B,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,KAAK,IAAI,CAwBzD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,qBAAqB,GAAI,WAAW,KAAK,CAAC,SAAS,CAAC,KAAG,IAMnE,CAAC"}
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/db.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AA6B7B,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AAKpC;;GAEG;AACH,eAAO,MAAM,KAAK,SAA4B,CAAC;AAE/C;;GAEG;AACH,MAAM,WAAW,SAAS;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,OAAO,CAAC,EAAE,CAAC,CAAC;IAC1B,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,IAAI,EAAE,OAAO,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,GAAU,IAAI,EAAE,KAAG,OAAO,CAAC,IAAI,CAGvD,CAAC;AAMF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,qBAAqB,GAAI,aAAa,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,KAAG,SAkB7E,CAAC;AAEH;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,iBAAiB,GAC7B,aAAa,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,EACtC,WAAW,MAAM,KACf,SA2DF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,UAQhC,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,gCAAgC,UAAyC,CAAC;AAEvF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,gBAAgB,GAAU,IAAI,EAAE,KAAG,OAAO,CAAC,IAAI,CAG3D,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,kBAAkB,GAC9B,WAAW,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC,EACvC,iBAAiB,KAAK,CAAC,MAAM,CAAC,KAC5B,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,KAAK,IAAI,CAwBzD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,qBAAqB,GAAI,WAAW,KAAK,CAAC,SAAS,CAAC,KAAG,IAMnE,CAAC"}
@@ -178,42 +178,22 @@ export const auth_truncate_tables = [
178
178
  */
179
179
  export const auth_integration_truncate_tables = [...auth_truncate_tables, 'audit_log'];
180
180
  /**
181
- * All auth tables in drop order (children first for FK safety).
182
- *
183
- * The full set created by `auth_migrations` — use for clean-slate
184
- * test DB initialization. `auth_truncate_tables` is the subset for
185
- * between-test data cleanup (excludes `audit_log`).
186
- *
187
- * When adding tables to `auth_migrations`, add them here too.
188
- */
189
- export const auth_drop_tables = [
190
- 'app_settings',
191
- 'invite',
192
- 'audit_log',
193
- 'api_token',
194
- 'auth_session',
195
- 'role_grant',
196
- 'role_grant_offer',
197
- 'actor',
198
- 'account',
199
- 'bootstrap_lock',
200
- ];
201
- /**
202
- * Drop all auth tables and schema version tracking for a clean slate.
181
+ * Reset the entire `public` schema for a clean slate before re-migration.
203
182
  *
204
183
  * Recommended at the start of `init_schema` callbacks for `create_pg_factory`.
205
- * Persistent test databases can accumulate stale schema from previous fuz_app
206
- * versions this ensures migrations run against a truly empty database.
207
- * Safe on fresh databases (`IF EXISTS` on all statements). No-op effect for
208
- * PGlite (already fresh), but harmless to call unconditionally.
209
- *
210
- * @mutates db - drops every table in `auth_drop_tables` plus `schema_version`.
184
+ * Persistent test databases accumulate stale DDL across fuz_app versions;
185
+ * `DROP SCHEMA public CASCADE; CREATE SCHEMA public` wipes every table, type,
186
+ * and sequence regardless of namespace, so migrations always run against a
187
+ * truly empty database. Drift-proof unlike a hand-maintained drop list it
188
+ * needs no upkeep when the schema gains a table. Despite the historical name,
189
+ * this resets the whole schema, not just auth tables (the only documented use
190
+ * is clean-slate re-migration, which always wanted a full reset).
191
+ *
192
+ * @mutates db - drops and recreates the `public` schema; all tables gone.
211
193
  */
212
194
  export const drop_auth_schema = async (db) => {
213
- for (const table of auth_drop_tables) {
214
- await db.query(`DROP TABLE IF EXISTS ${assert_valid_sql_identifier(table)} CASCADE`);
215
- }
216
- await db.query('DROP TABLE IF EXISTS schema_version CASCADE');
195
+ await db.query('DROP SCHEMA public CASCADE');
196
+ await db.query('CREATE SCHEMA public');
217
197
  };
218
198
  /**
219
199
  * Create a `describe_db` function bound to specific factories and truncate tables.
@@ -1,3 +1,4 @@
1
+ import './assert_dev_env.js';
1
2
  /**
2
3
  * In-memory file system mock for tests that use dependency-injected
3
4
  * `read_file` and `write_file` callbacks. Avoids module-level mocking.
@@ -1 +1 @@
1
- {"version":3,"file":"mock_fs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/mock_fs.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,MAAM;IACtB,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/D,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/E,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;CAC/C;AAED;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAAI,gBAAe,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,KAAG,MAsB3E,CAAC"}
1
+ {"version":3,"file":"mock_fs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/mock_fs.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;GAKG;AAEH,MAAM,WAAW,MAAM;IACtB,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/D,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/E,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;CAC/C;AAED;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAAI,gBAAe,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,KAAG,MAsB3E,CAAC"}
@@ -1,9 +1,4 @@
1
- /**
2
- * In-memory file system mock for tests that use dependency-injected
3
- * `read_file` and `write_file` callbacks. Avoids module-level mocking.
4
- *
5
- * @module
6
- */
1
+ import './assert_dev_env.js';
7
2
  /**
8
3
  * Creates an in-memory file system for tests.
9
4
  *
@@ -142,6 +142,38 @@ export declare const assert_ws_endpoints_include_protocol_actions: (surface: App
142
142
  * surface mocks the shape incorrectly.
143
143
  */
144
144
  export declare const assert_ws_notifications_have_null_auth: (surface: AppSurface) => void;
145
+ /**
146
+ * Reserved method-name prefix for the daemon-token-gated test-backdoor
147
+ * actions (`_testing_reset`, `_testing_mint_session`, `_testing_put_fact`,
148
+ * `_testing_drain_effects`, `_testing_schema_snapshot`). Test binaries
149
+ * live-mount these on their RPC endpoint but they must never appear on a
150
+ * **declared** surface.
151
+ */
152
+ export declare const TESTING_METHOD_PREFIX = "_testing_";
153
+ /**
154
+ * No `_testing_*` backdoor action ever appears as a method on the declared
155
+ * surface (RPC or WS).
156
+ *
157
+ * The test-backdoor actions (`_testing_reset` et al.) are daemon-token-gated
158
+ * privileged actions a consumer's test binary appends to its live RPC
159
+ * endpoint at assembly time — but they are deliberately excluded from
160
+ * surface generation (`spine_rpc_endpoints` and every consumer's
161
+ * `create_*_app_surface_spec` omit them) so the published attack surface,
162
+ * the committed `*_attack_surface.json` snapshot, and codegen never carry
163
+ * a backdoor. This invariant is the structural guard against a future
164
+ * wiring change that folds `create_testing_actions(...)` into the *declared*
165
+ * registry instead of the live-only append — the surface stays the
166
+ * authoritative "what the server exposes" map only if a backdoor can never
167
+ * hide in it.
168
+ *
169
+ * Pairs with the wire-level negative-credential check
170
+ * (`describe_testing_backdoor_cross_tests`, cross-process) and the
171
+ * spec-level gate check: this one pins absence-from-surface, those pin
172
+ * the daemon-token gate and the 401/403 behavior.
173
+ *
174
+ * @throws AssertionError naming the offending endpoint + method.
175
+ */
176
+ export declare const assert_no_testing_methods: (surface: AppSurface) => void;
145
177
  /**
146
178
  * Configuration for security policy invariants.
147
179
  *
@@ -282,7 +314,8 @@ export declare const assert_surface_invariants: (surface: AppSurface) => void;
282
314
  * `actions/CLAUDE.md` §Registry compile) — these assertions cover only
283
315
  * the contract-surface concerns that a runtime registration check
284
316
  * cannot: empty descriptions, missing protocol-action spread on WS
285
- * endpoints, and kind ⇔ auth drift on WS methods.
317
+ * endpoints, kind ⇔ auth drift on WS methods, and a `_testing_*`
318
+ * backdoor action leaking onto the declared surface.
286
319
  *
287
320
  * @throws AssertionError on the first invariant violation; the message
288
321
  * names the offending endpoint, method, and field.
@@ -1 +1 @@
1
- {"version":3,"file":"surface_invariants.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/surface_invariants.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAuB7B,OAAO,KAAK,EAAC,UAAU,EAAuB,MAAM,oBAAoB,CAAC;AAezE;;GAEG;AACH,eAAO,MAAM,mCAAmC,GAAI,SAAS,UAAU,KAAG,IAQzE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,8BAA8B,GAAI,SAAS,UAAU,KAAG,IASpE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,+BAA+B,GAAI,SAAS,UAAU,KAAG,IAQrE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,gCAAgC,GAAI,SAAS,UAAU,KAAG,IAQtE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,+BAA+B,GAAI,SAAS,UAAU,KAAG,IAQrE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,2BAA2B,GAAI,SAAS,UAAU,KAAG,IAIjE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,0BAA0B,GAAI,SAAS,UAAU,KAAG,IAOhE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mCAAmC,GAAI,SAAS,UAAU,KAAG,IAezE,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,uCAAuC,GAAI,SAAS,UAAU,KAAG,IAO7E,CAAC;AAyBF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,oCAAoC,GAAI,SAAS,UAAU,KAAG,IAoC1E,CAAC;AAsEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,sCAAsC,GAAI,SAAS,UAAU,KAAG,IAU5E,CAAC;AAIF,4DAA4D;AAC5D,MAAM,MAAM,sBAAsB,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAAC;AAEpE,iEAAiE;AACjE,MAAM,WAAW,qBAAqB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,sBAAsB,CAAC;IACpC,qDAAqD;IACrD,WAAW,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;CAClC;AAiED;;;;;;;;GAQG;AACH,eAAO,MAAM,4BAA4B,GAAI,SAAS,UAAU,KAAG,KAAK,CAAC,qBAAqB,CAgB7F,CAAC;AAIF;;;;;;GAMG;AACH,eAAO,MAAM,sCAAsC,GAAI,SAAS,UAAU,KAAG,IAS5E,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,qCAAqC,GAAI,SAAS,UAAU,KAAG,IAS3E,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,4CAA4C,GAAI,SAAS,UAAU,KAAG,IAWlF,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,sCAAsC,GAAI,SAAS,UAAU,KAAG,IAa5E,CAAC;AAIF;;;;GAIG;AACH,MAAM,WAAW,4BAA4B;IAC5C;;;;;OAKG;IACH,wBAAwB,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;IAClD;;;OAGG;IACH,yBAAyB,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC1C;;;OAGG;IACH,qBAAqB,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACtC;AASD;;;;;;GAMG;AACH,eAAO,MAAM,oCAAoC,GAChD,SAAS,UAAU,EACnB,qBAAoB,KAAK,CAAC,MAAM,GAAG,MAAM,CAA8B,KACrE,IAcF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,qCAAqC,GACjD,SAAS,UAAU,EACnB,YAAW,KAAK,CAAC,MAAM,CAAM,KAC3B,IAYF,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,+BAA+B,GAAI,SAAS,UAAU,KAAG,IAQrE,CAAC;AAKF;;;;;GAKG;AACH,eAAO,MAAM,iCAAiC,GAC7C,SAAS,UAAU,EACnB,WAAU,KAAK,CAAC,MAAM,CAAiC,KACrD,IASF,CAAC;AAWF,mDAAmD;AACnD,MAAM,WAAW,2BAA2B;IAC3C,6FAA6F;IAC7F,eAAe,CAAC,EAAE,sBAAsB,CAAC;IACzC,mEAAmE;IACnE,eAAe,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAChC,kDAAkD;IAClD,SAAS,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CAC1B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,uCAAuC,EAAE,aAAa,CAAC,MAAM,CAAM,CAAC;AAEjF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,8BAA8B,EAAE,2BAG5C,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,6BAA6B,GACzC,SAAS,UAAU,EACnB,UAAU,2BAA2B,KACnC,IAsBF,CAAC;AAIF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,yBAAyB,GAAI,SAAS,UAAU,KAAG,IAY/D,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,gCAAgC,GAAI,SAAS,UAAU,KAAG,IAKtE,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,8BAA8B,GAC1C,SAAS,UAAU,EACnB,UAAS,4BAAiC,KACxC,IAKF,CAAC"}
1
+ {"version":3,"file":"surface_invariants.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/surface_invariants.ts"],"names":[],"mappings":"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"}
@@ -452,6 +452,52 @@ export const assert_ws_notifications_have_null_auth = (surface) => {
452
452
  }
453
453
  }
454
454
  };
455
+ /**
456
+ * Reserved method-name prefix for the daemon-token-gated test-backdoor
457
+ * actions (`_testing_reset`, `_testing_mint_session`, `_testing_put_fact`,
458
+ * `_testing_drain_effects`, `_testing_schema_snapshot`). Test binaries
459
+ * live-mount these on their RPC endpoint but they must never appear on a
460
+ * **declared** surface.
461
+ */
462
+ export const TESTING_METHOD_PREFIX = '_testing_';
463
+ /**
464
+ * No `_testing_*` backdoor action ever appears as a method on the declared
465
+ * surface (RPC or WS).
466
+ *
467
+ * The test-backdoor actions (`_testing_reset` et al.) are daemon-token-gated
468
+ * privileged actions a consumer's test binary appends to its live RPC
469
+ * endpoint at assembly time — but they are deliberately excluded from
470
+ * surface generation (`spine_rpc_endpoints` and every consumer's
471
+ * `create_*_app_surface_spec` omit them) so the published attack surface,
472
+ * the committed `*_attack_surface.json` snapshot, and codegen never carry
473
+ * a backdoor. This invariant is the structural guard against a future
474
+ * wiring change that folds `create_testing_actions(...)` into the *declared*
475
+ * registry instead of the live-only append — the surface stays the
476
+ * authoritative "what the server exposes" map only if a backdoor can never
477
+ * hide in it.
478
+ *
479
+ * Pairs with the wire-level negative-credential check
480
+ * (`describe_testing_backdoor_cross_tests`, cross-process) and the
481
+ * spec-level gate check: this one pins absence-from-surface, those pin
482
+ * the daemon-token gate and the 401/403 behavior.
483
+ *
484
+ * @throws AssertionError naming the offending endpoint + method.
485
+ */
486
+ export const assert_no_testing_methods = (surface) => {
487
+ for (const ep of surface.rpc_endpoints) {
488
+ for (const method of ep.methods) {
489
+ assert.ok(!method.name.startsWith(TESTING_METHOD_PREFIX), `RPC endpoint '${ep.path}' exposes test-backdoor method '${method.name}' on the ` +
490
+ `declared surface — '${TESTING_METHOD_PREFIX}*' actions must be live-mounted only, ` +
491
+ `never folded into the surface-generating registry`);
492
+ }
493
+ }
494
+ for (const ep of surface.ws_endpoints) {
495
+ for (const method of ep.methods) {
496
+ assert.ok(!method.name.startsWith(TESTING_METHOD_PREFIX), `WS endpoint '${ep.path}' exposes test-backdoor method '${method.name}' on the ` +
497
+ `declared surface — '${TESTING_METHOD_PREFIX}*' actions must be live-mounted only`);
498
+ }
499
+ }
500
+ };
455
501
  /** Default patterns for sensitive REST routes that should be rate-limited. */
456
502
  const DEFAULT_SENSITIVE_PATTERNS = [
457
503
  /\/login$/,
@@ -637,7 +683,8 @@ export const assert_surface_invariants = (surface) => {
637
683
  * `actions/CLAUDE.md` §Registry compile) — these assertions cover only
638
684
  * the contract-surface concerns that a runtime registration check
639
685
  * cannot: empty descriptions, missing protocol-action spread on WS
640
- * endpoints, and kind ⇔ auth drift on WS methods.
686
+ * endpoints, kind ⇔ auth drift on WS methods, and a `_testing_*`
687
+ * backdoor action leaking onto the declared surface.
641
688
  *
642
689
  * @throws AssertionError on the first invariant violation; the message
643
690
  * names the offending endpoint, method, and field.
@@ -647,6 +694,7 @@ export const assert_rpc_ws_surface_invariants = (surface) => {
647
694
  assert_ws_method_descriptions_present(surface);
648
695
  assert_ws_endpoints_include_protocol_actions(surface);
649
696
  assert_ws_notifications_have_null_auth(surface);
697
+ assert_no_testing_methods(surface);
650
698
  };
651
699
  /**
652
700
  * Run security policy invariants. Configurable with sensible defaults.
@@ -1,3 +1,4 @@
1
+ import './assert_dev_env.js';
1
2
  /**
2
3
  * In-process test helpers for WebSocket JSON-RPC round-trips.
3
4
  *
@@ -1 +1 @@
1
- {"version":3,"file":"ws_round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/ws_round_trip.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ws_round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/ws_round_trip.ts"],"names":[],"mappings":"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"}
@@ -1,41 +1,4 @@
1
- /**
2
- * In-process test helpers for WebSocket JSON-RPC round-trips.
3
- *
4
- * Drives `register_action_ws` without an HTTP server. Consumers supply
5
- * specs + handlers; the harness constructs real `WSContext` instances
6
- * backed by test-owned `send`/`close` pairs, fakes the authenticated
7
- * Hono context (`request_context`, credential type, session id, api
8
- * token id), and exposes a `connect()` factory returning a `WsClient`
9
- * per connection.
10
- *
11
- * Three layers are exported:
12
- *
13
- * - **Primitives** (`create_fake_ws`, `create_fake_hono_context`,
14
- * `create_stub_upgrade`, `MinimalActionEnvironment`,
15
- * `dispatch_ws_message`) — used by fuz_app's own dispatcher tests
16
- * and by consumers wiring tight one-off tests.
17
- * - **Harness** (`create_ws_test_harness`, `keeper_identity`) — the
18
- * high-level driver. Give it specs + handlers, get back
19
- * `{transport, connect()}`. `connect()` is async and resolves after
20
- * `on_socket_open` completes, so broadcasts sent immediately after
21
- * `await harness.connect()` reach the client. Returns a `WsClient`
22
- * (shared interface — see `transports/ws_client.ts`); the same
23
- * interface is implemented by `transports/ws_transport.ts` for
24
- * cross-process tests.
25
- * - **Broadcast wiring** — `build_broadcast_api` for wiring a typed
26
- * broadcast API against the harness's transport. Wire-frame types
27
- * + predicates (`is_notification`, `is_response_for`,
28
- * `JsonrpcNotificationFrame`, ...) live in `transports/ws_client.ts`
29
- * so both in-process and cross-process drivers reference one source.
30
- *
31
- * Hono's wire upgrade is skipped — the Node test runtime has no
32
- * `@hono/node-ws` adapter — but the full dispatch path is exercised
33
- * (per-action auth, input validation, `ctx.notify` back to the
34
- * originating socket, broadcast via `BackendWebsocketTransport`, and
35
- * close-on-revoke).
36
- *
37
- * @module
38
- */
1
+ import './assert_dev_env.js';
39
2
  import { WSContext, createWSMessageEvent, } from 'hono/ws';
40
3
  import { Logger } from '@fuzdev/fuz_util/log.js';
41
4
  import { create_uuid } from '@fuzdev/fuz_util/id.js';
@@ -50,14 +50,13 @@
50
50
  import type {SvelteHTMLElements} from 'svelte/elements';
51
51
  import type {ComponentProps, Snippet} from 'svelte';
52
52
  import type {OmitStrict} from '@fuzdev/fuz_util/types.js';
53
- import Glyph from '@fuzdev/fuz_ui/Glyph.svelte';
53
+ import {icon_remove} from '@fuzdev/fuz_ui/icons.js';
54
54
  import PendingAnimation from '@fuzdev/fuz_ui/PendingAnimation.svelte';
55
+ import Svg from '@fuzdev/fuz_ui/Svg.svelte';
55
56
 
56
57
  import PopoverButton from './PopoverButton.svelte';
57
58
  import type {Popover} from './popover.svelte.js';
58
59
 
59
- const GLYPH_REMOVE = '🗙';
60
-
61
60
  const {
62
61
  onconfirm,
63
62
  popover_button_attrs,
@@ -140,7 +139,7 @@
140
139
  {#if popover_button_content}
141
140
  {@render popover_button_content(popover, () => confirm(popover))}
142
141
  {:else}
143
- <Glyph glyph={GLYPH_REMOVE} />
142
+ <Svg data={icon_remove} />
144
143
  {/if}
145
144
  </button>
146
145
  {/if}
@@ -155,7 +154,7 @@
155
154
  {:else if label !== undefined}
156
155
  {label}
157
156
  {:else}
158
- <Glyph glyph={GLYPH_REMOVE} />
157
+ <Svg data={icon_remove} />
159
158
  {/if}
160
159
  </span>
161
160
  {#if pending}
@@ -1 +1 @@
1
- {"version":3,"file":"ConfirmButton.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/ConfirmButton.svelte"],"names":[],"mappings":"AAkDA,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,iBAAiB,CAAC;AACxD,OAAO,KAAK,EAAC,cAAc,EAAE,OAAO,EAAC,MAAM,QAAQ,CAAC;AACpD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,2BAA2B,CAAC;AAI1D,OAAO,aAAa,MAAM,wBAAwB,CAAC;AACnD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,qBAAqB,CAAC;AAEhD,KAAK,gBAAgB,GAAI,UAAU,CAAC,cAAc,CAAC,OAAO,aAAa,CAAC,EAAE,iBAAiB,GAAG,UAAU,CAAC,GACxG,UAAU,CAAC,kBAAkB,CAAC,QAAQ,CAAC,EAAE,UAAU,CAAC,GAAG;IACtD,SAAS,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,oBAAoB,CAAC,EAAE,kBAAkB,CAAC,QAAQ,CAAC,GAAG,SAAS,CAAC;IAChE,eAAe,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACtC,yEAAyE;IACzE,eAAe,CAAC,EAAE,OAAO,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,SAAS,CAAC;IAC/E,qCAAqC;IACrC,sBAAsB,CAAC,EAAE,OAAO,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,SAAS,CAAC;IACtF,yDAAyD;IACzD,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,SAAS,CAAC;IACxE,iFAAiF;IACjF,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC9B,CAAC;AAgGJ,QAAA,MAAM,aAAa,sDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"ConfirmButton.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/ConfirmButton.svelte"],"names":[],"mappings":"AAkDA,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,iBAAiB,CAAC;AACxD,OAAO,KAAK,EAAC,cAAc,EAAE,OAAO,EAAC,MAAM,QAAQ,CAAC;AACpD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,2BAA2B,CAAC;AAK1D,OAAO,aAAa,MAAM,wBAAwB,CAAC;AACnD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,qBAAqB,CAAC;AAEhD,KAAK,gBAAgB,GAAI,UAAU,CAAC,cAAc,CAAC,OAAO,aAAa,CAAC,EAAE,iBAAiB,GAAG,UAAU,CAAC,GACxG,UAAU,CAAC,kBAAkB,CAAC,QAAQ,CAAC,EAAE,UAAU,CAAC,GAAG;IACtD,SAAS,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,oBAAoB,CAAC,EAAE,kBAAkB,CAAC,QAAQ,CAAC,GAAG,SAAS,CAAC;IAChE,eAAe,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACtC,yEAAyE;IACzE,eAAe,CAAC,EAAE,OAAO,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,SAAS,CAAC;IAC/E,qCAAqC;IACrC,sBAAsB,CAAC,EAAE,OAAO,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,SAAS,CAAC;IACtF,yDAAyD;IACzD,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,SAAS,CAAC;IACxE,iFAAiF;IACjF,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC9B,CAAC;AA+FJ,QAAA,MAAM,aAAa,sDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzdev/fuz_app",
3
- "version": "0.84.0",
3
+ "version": "0.85.1",
4
4
  "description": "fullstack app library",
5
5
  "glyph": "🗝",
6
6
  "logo": "logo.svg",
@@ -70,10 +70,11 @@
70
70
  "@electric-sql/pglite": "^0.4.5",
71
71
  "@fuzdev/blake3_wasm": "^0.1.1",
72
72
  "@fuzdev/fuz_code": "^0.46.0",
73
- "@fuzdev/fuz_css": "^0.62.0",
74
- "@fuzdev/fuz_ui": "^0.199.0",
73
+ "@fuzdev/fuz_css": "^0.63.0",
74
+ "@fuzdev/fuz_ui": "^0.205.0",
75
75
  "@fuzdev/fuz_util": "^0.65.1",
76
76
  "@fuzdev/gro": "^0.204.0",
77
+ "@fuzdev/mdz": "^0.1.0",
77
78
  "@hono/node-server": "^1.19.14",
78
79
  "@hono/node-ws": "^1.3.1",
79
80
  "@node-rs/argon2": "^2.0.2",
@@ -98,7 +99,7 @@
98
99
  "prettier-plugin-svelte": "^3.5.1",
99
100
  "svelte": "^5.56.2",
100
101
  "svelte-check": "^4.6.0",
101
- "svelte-docinfo": "^0.4.1",
102
+ "svelte-docinfo": "^0.5.1",
102
103
  "svelte2tsx": "^0.7.52",
103
104
  "tslib": "^2.8.1",
104
105
  "typescript": "^5.9.3",