@fuzdev/fuz_app 0.112.0 → 0.113.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/CLAUDE.md +6 -1
- package/dist/auth/audit_log_route_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_route_schema.js +12 -1
- package/dist/auth/role_grant_offer_action_specs.d.ts +2 -1
- package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_action_specs.js +2 -1
- package/dist/db/fact_disk_storage.js +1 -1
- package/dist/http/CLAUDE.md +3 -3
- package/dist/http/auth_shape.d.ts +19 -6
- package/dist/http/auth_shape.d.ts.map +1 -1
- package/dist/http/auth_shape.js +19 -6
- package/dist/http/surface_query.d.ts +29 -4
- package/dist/http/surface_query.d.ts.map +1 -1
- package/dist/http/surface_query.js +30 -5
- package/dist/realtime/sse_auth_guard.d.ts +9 -3
- package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
- package/dist/realtime/sse_auth_guard.js +9 -3
- package/dist/testing/CLAUDE.md +1 -1
- package/dist/testing/attack_surface.d.ts +5 -2
- package/dist/testing/attack_surface.d.ts.map +1 -1
- package/dist/testing/attack_surface.js +45 -17
- package/dist/testing/auth_apps.d.ts +25 -0
- package/dist/testing/auth_apps.d.ts.map +1 -1
- package/dist/testing/auth_apps.js +34 -5
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.js +3 -2
- package/dist/testing/cross_backend/token_scope_surface.d.ts.map +1 -1
- package/dist/testing/cross_backend/token_scope_surface.js +36 -7
- package/dist/testing/surface_invariants.d.ts +7 -1
- package/dist/testing/surface_invariants.d.ts.map +1 -1
- package/dist/testing/surface_invariants.js +11 -5
- package/dist/ui/AdminAccounts.svelte +6 -4
- package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -1
- package/dist/ui/AdminInvites.svelte +3 -3
- package/dist/ui/AdminInvites.svelte.d.ts.map +1 -1
- package/dist/ui/AdminOverview.svelte +10 -10
- package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
- package/dist/ui/AdminRoleGrantHistory.svelte +7 -7
- package/dist/ui/AdminRoleGrantHistory.svelte.d.ts.map +1 -1
- package/dist/ui/AdminSettings.svelte +1 -1
- package/dist/ui/RoleGrantOfferInbox.svelte +4 -1
- package/dist/ui/RoleGrantOfferInbox.svelte.d.ts.map +1 -1
- package/dist/ui/SurfaceExplorer.svelte +33 -17
- package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
- package/package.json +10 -10
|
@@ -11,9 +11,11 @@ import { Hono } from 'hono';
|
|
|
11
11
|
import { Logger } from '@fuzdev/fuz_util/log.ts';
|
|
12
12
|
import { apply_route_specs } from '../http/route_spec.js';
|
|
13
13
|
import { is_public_auth } from '../http/auth_shape.js';
|
|
14
|
+
import { CREDENTIAL_TYPE_DAEMON_TOKEN, CREDENTIAL_TYPE_SESSION } from '../auth/credential_type_schema.js';
|
|
15
|
+
import { ROLE_KEEPER } from '../auth/role_schema.js';
|
|
14
16
|
import { fuz_auth_guard_resolver } from '../auth/auth_guard_resolver.js';
|
|
15
17
|
import { REQUEST_CONTEXT_KEY, create_fuz_authorization_handler } from '../auth/request_context.js';
|
|
16
|
-
import { ACCOUNT_ID_KEY, CREDENTIAL_TYPE_KEY, TEST_CONTEXT_PRESET_KEY } from '../hono_context.js';
|
|
18
|
+
import { ACCOUNT_ID_KEY, CREDENTIAL_TYPES, CREDENTIAL_TYPE_KEY, TEST_CONTEXT_PRESET_KEY } from '../hono_context.js';
|
|
17
19
|
import { create_stub_db } from './stubs.js';
|
|
18
20
|
import { create_test_account, create_test_actor, create_test_role_grant } from './entities.js';
|
|
19
21
|
/**
|
|
@@ -40,7 +42,7 @@ export const create_test_app_from_specs = (route_specs, auth_ctx, credential_typ
|
|
|
40
42
|
if (auth_ctx) {
|
|
41
43
|
c.set(ACCOUNT_ID_KEY, auth_ctx.account.id);
|
|
42
44
|
c.set(REQUEST_CONTEXT_KEY, auth_ctx);
|
|
43
|
-
c.set(CREDENTIAL_TYPE_KEY, credential_type ??
|
|
45
|
+
c.set(CREDENTIAL_TYPE_KEY, credential_type ?? CREDENTIAL_TYPE_SESSION);
|
|
44
46
|
c.set(TEST_CONTEXT_PRESET_KEY, true);
|
|
45
47
|
}
|
|
46
48
|
await next();
|
|
@@ -59,25 +61,52 @@ export const create_auth_test_apps = (route_specs, roles) => {
|
|
|
59
61
|
for (const role of roles) {
|
|
60
62
|
by_role.set(role, create_test_app_from_specs(route_specs, create_test_request_context(role)));
|
|
61
63
|
}
|
|
64
|
+
const by_credential_type = new Map(CREDENTIAL_TYPES.map((credential_type) => [
|
|
65
|
+
credential_type,
|
|
66
|
+
create_test_app_from_specs(route_specs, create_test_request_context(ROLE_KEEPER), credential_type)
|
|
67
|
+
]));
|
|
62
68
|
return {
|
|
63
69
|
public: create_test_app_from_specs(route_specs),
|
|
64
70
|
authed: create_test_app_from_specs(route_specs, create_test_request_context()),
|
|
65
|
-
keeper:
|
|
71
|
+
keeper: by_credential_type.get(CREDENTIAL_TYPE_DAEMON_TOKEN),
|
|
72
|
+
by_credential_type,
|
|
66
73
|
by_role
|
|
67
74
|
};
|
|
68
75
|
};
|
|
69
76
|
/**
|
|
70
77
|
* Select the Hono test app with correct auth for a route.
|
|
71
78
|
*
|
|
79
|
+
* The credential channel is decided first, because the channel gate is
|
|
80
|
+
* pre-authorization: an app whose credential the route refuses never reaches
|
|
81
|
+
* the role gate, so a role-correct app on the wrong channel answers
|
|
82
|
+
* `credential_type_required`, and a "correct auth passes guard" case reads it
|
|
83
|
+
* as a failure of the role gate that never ran.
|
|
84
|
+
*
|
|
85
|
+
* A gate admitting `'session'` falls through to the `by_role` /
|
|
86
|
+
* `authed` apps, which carry that channel. Any other gate leaves the
|
|
87
|
+
* `by_credential_type` app as the only candidate — those hold the keeper
|
|
88
|
+
* role, which is why a route pairing a non-session channel with some *other*
|
|
89
|
+
* role has no app and says so.
|
|
90
|
+
*
|
|
72
91
|
* @throws Error if `auth.roles` names a role not present in `apps.by_role` —
|
|
73
92
|
* surfaces a missing entry in the `roles` array passed to
|
|
74
93
|
* `create_auth_test_apps`.
|
|
94
|
+
* @throws Error if the route pairs a non-session channel with a role the
|
|
95
|
+
* channel apps don't hold. Silently handing back a session app would turn
|
|
96
|
+
* every case on the route into a misattributed 403.
|
|
75
97
|
*/
|
|
76
98
|
export const select_auth_app = (apps, auth) => {
|
|
77
99
|
if (is_public_auth(auth))
|
|
78
100
|
return apps.public;
|
|
79
|
-
|
|
80
|
-
|
|
101
|
+
const credential_types = auth.credential_types;
|
|
102
|
+
if (credential_types?.length && !credential_types.includes(CREDENTIAL_TYPE_SESSION)) {
|
|
103
|
+
const channel = CREDENTIAL_TYPES.find((t) => credential_types.includes(t));
|
|
104
|
+
const app = channel === undefined ? undefined : apps.by_credential_type.get(channel);
|
|
105
|
+
const roles = auth.roles;
|
|
106
|
+
if (app && (!roles?.length || roles.includes(ROLE_KEEPER)))
|
|
107
|
+
return app;
|
|
108
|
+
throw new Error(`No test app for credential types '${credential_types.join("', '")}'${roles?.length ? ` with roles '${roles.join("', '")}'` : ''} — the channel apps hold the '${ROLE_KEEPER}' role, and only the session channel has per-role apps`);
|
|
109
|
+
}
|
|
81
110
|
if (auth.roles?.length) {
|
|
82
111
|
// Multi-role disjunction: any of the named roles admits the caller.
|
|
83
112
|
// Tests pick the first role's app; consumers wanting per-role coverage
|
|
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoEG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAKzE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AAqCzC;;;;;;;;;;;;;;;;;;;;;;;;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,SACmB,CAAC;AAE3E;;;;;;;;;;;;;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;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,qCAAqC;;;;;;;;;;;;;;;;;;;;CAYJ,CAAC;AAE/C;;;;GAIG;AACH,eAAO,MAAM,uCAAuC,QAAO,SAGzD,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,mCAAmC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoEG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAKzE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AAqCzC;;;;;;;;;;;;;;;;;;;;;;;;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,SACmB,CAAC;AAE3E;;;;;;;;;;;;;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;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,qCAAqC;;;;;;;;;;;;;;;;;;;;CAYJ,CAAC;AAE/C;;;;GAIG;AACH,eAAO,MAAM,uCAAuC,QAAO,SAGzD,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,mCAAmC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAaF,CAAC;AAyB/C,eAAO,MAAM,qCAAqC,GACjD,SAAS,aAAa,CAAC,SAAS,CAAC,KAC/B,SAMF,CAAC;AAEF,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,CAsIjB,CAAC;AAEF,0FAA0F;AAC1F,eAAO,MAAM,0BAA0B,UAAmC,CAAC"}
|
|
@@ -368,8 +368,9 @@ export const testing_action_manifest_action_spec = {
|
|
|
368
368
|
input: z.strictObject({}),
|
|
369
369
|
output: ActionManifest,
|
|
370
370
|
async: true,
|
|
371
|
-
description: 'Test-binary only — dump the live RPC registry as a normalized
|
|
372
|
-
'manifest for cross-impl method-set +
|
|
371
|
+
description: 'Test-binary only — dump the live RPC registry as a normalized ' +
|
|
372
|
+
'{method, auth, side_effects, rate_limit} manifest for cross-impl method-set + ' +
|
|
373
|
+
'auth-shape parity diffing.'
|
|
373
374
|
};
|
|
374
375
|
/**
|
|
375
376
|
* Build the `_testing_action_manifest` action over a backend's full live RPC
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token_scope_surface.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/token_scope_surface.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"token_scope_surface.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/token_scope_surface.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA8E9B,OAAO,KAAK,EAAE,uCAAuC,EAAE,MAAM,YAAY,CAAC;AAkB1E,wDAAwD;AACxD,MAAM,WAAW,iCAAkC,SAAQ,uCAAuC;IACjG,6DAA6D;IAC7D,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC3B;AAwFD,eAAO,MAAM,wCAAwC,GACpD,SAAS,iCAAiC,KACxC,IAwHF,CAAC"}
|
|
@@ -26,6 +26,15 @@ import '../assert_dev_env.js';
|
|
|
26
26
|
* `required_scope` capability string, per surface. A spine that renamed a
|
|
27
27
|
* surface, dropped the `surface:` prefix, or emitted the JSON-RPC envelope
|
|
28
28
|
* from a REST route fails here.
|
|
29
|
+
* - **The audit stream's channel denial** — that surface is gated to
|
|
30
|
+
* `credential_types: ['session']` on both spines, so a bearer never reaches
|
|
31
|
+
* its scope gate and the observable is `credential_type_required` instead.
|
|
32
|
+
* The case is kept here rather than moved: it is still the bearer-vs-surface
|
|
33
|
+
* probe, and what it now pins is the coarser-gate-first ordering both spines
|
|
34
|
+
* run (a spine that checked scope first would answer `token_scope_required`).
|
|
35
|
+
* `surface:audit_stream`'s own capability string is consequently unreachable
|
|
36
|
+
* on the default mount and has no cross-impl pin — each spine's census holds
|
|
37
|
+
* its declaration, the way the WS upgrade's is held.
|
|
29
38
|
* - **Scope outranks role** — a narrowed token whose account *also* lacks the
|
|
30
39
|
* gating role must hear `token_scope_required`, not
|
|
31
40
|
* `insufficient_permissions`. This is the observable the TS↔Rust ordering
|
|
@@ -37,16 +46,17 @@ import '../assert_dev_env.js';
|
|
|
37
46
|
* Two non-vacuity controls hold the negatives honest: the narrowed token must
|
|
38
47
|
* still reach the RPC method it *does* name (otherwise a spine that rejected
|
|
39
48
|
* the credential outright would pass every denial), and a **full** bearer must
|
|
40
|
-
* reach
|
|
41
|
-
*
|
|
49
|
+
* reach `fact_bare` (otherwise a route broken for all bearers would pass too).
|
|
50
|
+
* The second has no audit-stream twin by construction — refusing every bearer
|
|
51
|
+
* is exactly what that route's channel gate is for.
|
|
42
52
|
*
|
|
43
53
|
* ## Why the WS upgrade isn't here
|
|
44
54
|
*
|
|
45
55
|
* Rule 3's third probeable surface is the WebSocket upgrade, and it is
|
|
46
56
|
* deliberately omitted: `create_ws_transport` threads cookies, not bearer
|
|
47
57
|
* headers, and a refused upgrade surfaces as a thrown connection error whose
|
|
48
|
-
* body — the thing this suite exists to compare — isn't readable. The
|
|
49
|
-
* surfaces are where
|
|
58
|
+
* body — the thing this suite exists to compare — isn't readable. The HTTP
|
|
59
|
+
* surfaces are where a denial body is observable, which is what the parity
|
|
50
60
|
* gate needs. The WS gate keeps its per-spine coverage (fuz_app's surface
|
|
51
61
|
* census pins the call site; the Rust census pins its own).
|
|
52
62
|
*
|
|
@@ -57,7 +67,7 @@ import '../assert_dev_env.js';
|
|
|
57
67
|
*/
|
|
58
68
|
import { describe, assert } from 'vitest';
|
|
59
69
|
import { account_token_create_action_spec } from '../../auth/account_action_specs.js';
|
|
60
|
-
import { ERROR_TOKEN_SCOPE_REQUIRED } from '../../http/error_schemas.js';
|
|
70
|
+
import { ERROR_CREDENTIAL_TYPE_REQUIRED, ERROR_TOKEN_SCOPE_REQUIRED } from '../../http/error_schemas.js';
|
|
61
71
|
import { test_if } from './capabilities.js';
|
|
62
72
|
import { cross_rpc_call } from './cell_cross_helpers.js';
|
|
63
73
|
import { SPINE_RPC_PATH } from './spine_surface_constants.js';
|
|
@@ -107,6 +117,25 @@ const assert_surface_denied = async (fresh_transport, path, token, surface) => {
|
|
|
107
117
|
const body = await res.json();
|
|
108
118
|
assert.deepStrictEqual(body, surface_denial_body(surface), `${path} must return the canonical flat surface denial`);
|
|
109
119
|
};
|
|
120
|
+
/**
|
|
121
|
+
* Assert a bearer GET is refused by the *channel* gate rather than the scope
|
|
122
|
+
* gate — the shape a surface takes once it declares
|
|
123
|
+
* `credential_types: ['session']`.
|
|
124
|
+
*
|
|
125
|
+
* Distinct from `assert_surface_denied` on purpose: a channel-gated surface
|
|
126
|
+
* refuses **every** bearer, narrow or full, so the denial cannot name a scope.
|
|
127
|
+
* A narrowed token is still the right probe — it proves the channel gate runs
|
|
128
|
+
* *first*, since a scope-gated route would have answered
|
|
129
|
+
* `token_scope_required` instead.
|
|
130
|
+
*/
|
|
131
|
+
const assert_channel_denied = async (fresh_transport, path, token, credential_types) => {
|
|
132
|
+
const res = await fresh_transport({ origin: null })(path, {
|
|
133
|
+
headers: { authorization: `Bearer ${token}` }
|
|
134
|
+
});
|
|
135
|
+
assert.strictEqual(res.status, 403, `${path} must refuse a bearer with 403`);
|
|
136
|
+
const body = await res.json();
|
|
137
|
+
assert.deepStrictEqual(body, { error: ERROR_CREDENTIAL_TYPE_REQUIRED, required_credential_types: credential_types }, `${path} must return the canonical flat credential-type denial`);
|
|
138
|
+
};
|
|
110
139
|
export const describe_token_scope_surface_cross_tests = (options) => {
|
|
111
140
|
const { setup_test, capabilities } = options;
|
|
112
141
|
const rpc_path = options.rpc_path ?? SPINE_RPC_PATH;
|
|
@@ -123,10 +152,10 @@ export const describe_token_scope_surface_cross_tests = (options) => {
|
|
|
123
152
|
const res = await cross_rpc_call(fixture.fresh_transport({ origin: null }), rpc_path, ADMITTED_METHOD, undefined, { authorization: `Bearer ${token}` });
|
|
124
153
|
assert.ok(res.ok, `admitted method must succeed: ${JSON.stringify(res.error)}`);
|
|
125
154
|
});
|
|
126
|
-
test_if(capabilities.sse, '
|
|
155
|
+
test_if(capabilities.sse, 'the audit stream refuses a bearer at the channel gate (credential_types)', async () => {
|
|
127
156
|
const fixture = await setup_test();
|
|
128
157
|
const token = await mint_narrowed_token(fixture.transport, rpc_path, fixture.create_session_headers());
|
|
129
|
-
await
|
|
158
|
+
await assert_channel_denied(fixture.fresh_transport, sse_path, token, ['session']);
|
|
130
159
|
});
|
|
131
160
|
test_if(capabilities.fact_serving, 'a narrowed token is refused the bare-hash fact read (surface:fact_bare)', async () => {
|
|
132
161
|
const fixture = await setup_test();
|
|
@@ -5,7 +5,13 @@ import type { AppSurface } from '../http/surface.js';
|
|
|
5
5
|
*/
|
|
6
6
|
export declare const assert_protected_routes_declare_401: (surface: AppSurface) => void;
|
|
7
7
|
/**
|
|
8
|
-
* Every
|
|
8
|
+
* Every route with an authority gate that can answer 403 declares one — a
|
|
9
|
+
* role gate, or a credential-channel gate of any kind (keeper included).
|
|
10
|
+
*
|
|
11
|
+
* The credential half is not just the keeper routes: the session-only gates
|
|
12
|
+
* point the same axis the other way and refuse with the same status, so a
|
|
13
|
+
* route carrying one and no 403 schema would have its denial body go
|
|
14
|
+
* unvalidated by the DEV-only error-schema check.
|
|
9
15
|
*/
|
|
10
16
|
export declare const assert_role_routes_declare_403: (surface: AppSurface) => void;
|
|
11
17
|
/**
|
|
@@ -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,EAAE,UAAU,EAAwB,MAAM,oBAAoB,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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"}
|
|
@@ -18,7 +18,7 @@ import './assert_dev_env.js';
|
|
|
18
18
|
*/
|
|
19
19
|
import { assert } from 'vitest';
|
|
20
20
|
import { middleware_applies } from '../http/schema_helpers.js';
|
|
21
|
-
import { filter_protected_routes, filter_role_routes, filter_keeper_routes, filter_routes_with_input, filter_routes_with_params, filter_routes_with_query, filter_public_routes, format_route_key } from '../http/surface_query.js';
|
|
21
|
+
import { filter_protected_routes, filter_role_routes, filter_keeper_routes, filter_credential_gated_routes, filter_routes_with_input, filter_routes_with_params, filter_routes_with_query, filter_public_routes, format_route_key } from '../http/surface_query.js';
|
|
22
22
|
import { PROTOCOL_ACTION_METHODS } from '../actions/action_codegen.js';
|
|
23
23
|
// --- Structural invariants ---
|
|
24
24
|
/**
|
|
@@ -31,13 +31,19 @@ export const assert_protected_routes_declare_401 = (surface) => {
|
|
|
31
31
|
}
|
|
32
32
|
};
|
|
33
33
|
/**
|
|
34
|
-
* Every
|
|
34
|
+
* Every route with an authority gate that can answer 403 declares one — a
|
|
35
|
+
* role gate, or a credential-channel gate of any kind (keeper included).
|
|
36
|
+
*
|
|
37
|
+
* The credential half is not just the keeper routes: the session-only gates
|
|
38
|
+
* point the same axis the other way and refuse with the same status, so a
|
|
39
|
+
* route carrying one and no 403 schema would have its denial body go
|
|
40
|
+
* unvalidated by the DEV-only error-schema check.
|
|
35
41
|
*/
|
|
36
42
|
export const assert_role_routes_declare_403 = (surface) => {
|
|
37
43
|
const role_routes = filter_role_routes(surface);
|
|
38
|
-
const
|
|
39
|
-
for (const route of [...role_routes, ...
|
|
40
|
-
assert.ok(route.error_schemas && '403' in route.error_schemas, `${format_route_key(route)}
|
|
44
|
+
const credential_gated_routes = filter_credential_gated_routes(surface);
|
|
45
|
+
for (const route of [...role_routes, ...credential_gated_routes]) {
|
|
46
|
+
assert.ok(route.error_schemas && '403' in route.error_schemas, `${format_route_key(route)} has a role or credential gate but missing 403 error schema`);
|
|
41
47
|
}
|
|
42
48
|
};
|
|
43
49
|
/**
|
|
@@ -44,9 +44,9 @@
|
|
|
44
44
|
<h1>accounts</h1>
|
|
45
45
|
{#if admin_accounts.account_count > 0}
|
|
46
46
|
<p>
|
|
47
|
-
<span class="chip color_a"
|
|
48
|
-
|
|
49
|
-
>
|
|
47
|
+
<span class="chip color_a">
|
|
48
|
+
{admin_accounts.account_count} account{admin_accounts.account_count === 1 ? '' : 's'}
|
|
49
|
+
</span>
|
|
50
50
|
</p>
|
|
51
51
|
{/if}
|
|
52
52
|
|
|
@@ -158,7 +158,9 @@
|
|
|
158
158
|
{#each admin_accounts.grantable_roles as role (role)}
|
|
159
159
|
{@const key = grant_key(row.account.id, role)}
|
|
160
160
|
{@const grant_error = admin_accounts.grant.error(key)}
|
|
161
|
-
{#if !row.role_grants.some((p) => p.role === role) &&
|
|
161
|
+
{#if !row.role_grants.some((p) => p.role === role) &&
|
|
162
|
+
!row.pending_offers.some((o) => o.role === role)
|
|
163
|
+
}
|
|
162
164
|
<ConfirmButton
|
|
163
165
|
onconfirm={() => admin_accounts.submit_grant(row.account.id, role)}
|
|
164
166
|
title="offer {role}"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminAccounts.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/AdminAccounts.svelte"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AdminAccounts.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/AdminAccounts.svelte"],"names":[],"mappings":"AAqMA,QAAA,MAAM,aAAa,2DAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|
|
@@ -59,9 +59,9 @@
|
|
|
59
59
|
</section>
|
|
60
60
|
{#if admin_invites.invite_count > 0}
|
|
61
61
|
<p>
|
|
62
|
-
<span class="chip color_a"
|
|
63
|
-
|
|
64
|
-
>
|
|
62
|
+
<span class="chip color_a">
|
|
63
|
+
{admin_invites.unclaimed_count} unclaimed / {admin_invites.invite_count} total
|
|
64
|
+
</span>
|
|
65
65
|
</p>
|
|
66
66
|
{/if}
|
|
67
67
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminInvites.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/AdminInvites.svelte"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AdminInvites.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/AdminInvites.svelte"],"names":[],"mappings":"AAuJA,QAAA,MAAM,YAAY,2DAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
|
|
@@ -146,9 +146,9 @@
|
|
|
146
146
|
<div class="baseline-row gap_xs font_size_sm mt_sm">
|
|
147
147
|
<span class="text_50">newest session:</span>
|
|
148
148
|
<strong>{most_recent.username}</strong>
|
|
149
|
-
<span class="text_50" title={format_datetime_local(most_recent.created_at)}
|
|
150
|
-
|
|
151
|
-
>
|
|
149
|
+
<span class="text_50" title={format_datetime_local(most_recent.created_at)}>
|
|
150
|
+
{format_relative_time(most_recent.created_at)}
|
|
151
|
+
</span>
|
|
152
152
|
</div>
|
|
153
153
|
{/if}
|
|
154
154
|
{/if}
|
|
@@ -214,9 +214,9 @@
|
|
|
214
214
|
<ul class="compact-list">
|
|
215
215
|
{#each recent_events as event (event.id)}
|
|
216
216
|
<li>
|
|
217
|
-
<span class="text_50 font_size_sm" title={format_datetime_local(event.created_at)}
|
|
218
|
-
|
|
219
|
-
>
|
|
217
|
+
<span class="text_50 font_size_sm" title={format_datetime_local(event.created_at)}>
|
|
218
|
+
{format_relative_time(event.created_at)}
|
|
219
|
+
</span>
|
|
220
220
|
<code class="font_size_sm">{event.event_type}</code>
|
|
221
221
|
{#if event.outcome === 'failure'}
|
|
222
222
|
<span class="chip font_size_sm color_c">fail</span>
|
|
@@ -251,9 +251,9 @@
|
|
|
251
251
|
<ul class="compact-list">
|
|
252
252
|
{#each role_grant_changes.slice(0, 4) as event (event.id)}
|
|
253
253
|
<li class="font_size_sm">
|
|
254
|
-
<span class="text_50" title={format_datetime_local(event.created_at)}
|
|
255
|
-
|
|
256
|
-
>
|
|
254
|
+
<span class="text_50" title={format_datetime_local(event.created_at)}>
|
|
255
|
+
{format_relative_time(event.created_at)}
|
|
256
|
+
</span>
|
|
257
257
|
<code>{event.event_type === 'role_grant_create' ? 'grant' : 'revoke'}</code>
|
|
258
258
|
{#if event.metadata?.role}
|
|
259
259
|
<span class="chip font_size_sm">{event.metadata.role}</span>
|
|
@@ -309,7 +309,7 @@
|
|
|
309
309
|
label="log out"
|
|
310
310
|
>
|
|
311
311
|
{#snippet popover_button_content()}
|
|
312
|
-
<span class="p_md">
|
|
312
|
+
<span class="p_md">log out</span>
|
|
313
313
|
{/snippet}
|
|
314
314
|
</ConfirmButton>
|
|
315
315
|
</div>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminOverview.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/AdminOverview.svelte"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AdminOverview.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/AdminOverview.svelte"],"names":[],"mappings":"AA+UA,QAAA,MAAM,aAAa,2DAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|
|
@@ -74,14 +74,14 @@
|
|
|
74
74
|
{:else if column.key === 'username'}
|
|
75
75
|
<!-- Prefer actor-grain id in the truncated fallback; account is
|
|
76
76
|
the second fallback for events with no actor binding. -->
|
|
77
|
-
<span class="text_50"
|
|
78
|
-
|
|
79
|
-
>
|
|
77
|
+
<span class="text_50">
|
|
78
|
+
{row.username ?? truncate_uuid(row.actor_id ?? row.account_id ?? '?')}
|
|
79
|
+
</span>
|
|
80
80
|
{:else if column.key === 'target_username'}
|
|
81
|
-
<span class="text_50"
|
|
82
|
-
|
|
83
|
-
truncate_uuid(row.target_actor_id ?? row.target_account_id ?? '?')}
|
|
84
|
-
>
|
|
81
|
+
<span class="text_50">
|
|
82
|
+
{row.target_username ??
|
|
83
|
+
truncate_uuid(row.target_actor_id ?? row.target_account_id ?? '?')}
|
|
84
|
+
</span>
|
|
85
85
|
{:else if column.key === 'created_at'}
|
|
86
86
|
<span title={format_datetime_local(row.created_at)}>
|
|
87
87
|
{format_relative_time(row.created_at)}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminRoleGrantHistory.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/AdminRoleGrantHistory.svelte"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AdminRoleGrantHistory.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/AdminRoleGrantHistory.svelte"],"names":[],"mappings":"AAuGA,QAAA,MAAM,qBAAqB,2DAAwC,CAAC;AACpE,KAAK,qBAAqB,GAAG,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAC;AACtE,eAAe,qBAAqB,CAAC"}
|
|
@@ -48,7 +48,10 @@
|
|
|
48
48
|
<section class="role-grant-offer-inbox">
|
|
49
49
|
<h2>pending offers</h2>
|
|
50
50
|
|
|
51
|
-
{#if role_grant_offers.list.error ||
|
|
51
|
+
{#if role_grant_offers.list.error ||
|
|
52
|
+
role_grant_offers.accept.error ||
|
|
53
|
+
role_grant_offers.decline.error
|
|
54
|
+
}
|
|
52
55
|
<p class="color_c_50">
|
|
53
56
|
{role_grant_offers.list.error ??
|
|
54
57
|
role_grant_offers.accept.error ??
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RoleGrantOfferInbox.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/RoleGrantOfferInbox.svelte"],"names":[],"mappings":"AAmBA,OAAO,EAA6C,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE/F,KAAK,gBAAgB,GAAI;IACxB,uEAAuE;IACvE,YAAY,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,MAAM,CAAC;IACjD;;;;OAIG;IACH,YAAY,CAAC,EAAE,WAAW,CAAC;IAC3B,+DAA+D;IAC/D,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CACvC,CAAC;
|
|
1
|
+
{"version":3,"file":"RoleGrantOfferInbox.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/RoleGrantOfferInbox.svelte"],"names":[],"mappings":"AAmBA,OAAO,EAA6C,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE/F,KAAK,gBAAgB,GAAI;IACxB,uEAAuE;IACvE,YAAY,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,MAAM,CAAC;IACjD;;;;OAIG;IACH,YAAY,CAAC,EAAE,WAAW,CAAC;IAC3B,+DAA+D;IAC/D,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CACvC,CAAC;AAiGH,QAAA,MAAM,mBAAmB,sDAAwC,CAAC;AAClE,KAAK,mBAAmB,GAAG,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAClE,eAAe,mBAAmB,CAAC"}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import type { AppSurface, AppSurfaceRoute, AppSurfaceDiagnostic } from '../http/surface.js';
|
|
14
14
|
import { surface_auth_summary, format_route_key } from '../http/surface_query.js';
|
|
15
15
|
import {
|
|
16
|
+
is_credential_gated_auth,
|
|
16
17
|
is_keeper_auth,
|
|
17
18
|
is_plain_authenticated_auth,
|
|
18
19
|
is_public_auth,
|
|
@@ -22,7 +23,7 @@
|
|
|
22
23
|
|
|
23
24
|
const { surface }: { surface: AppSurface } = $props();
|
|
24
25
|
|
|
25
|
-
const auth_types = ['all', 'none', 'authenticated', 'role', 'keeper'] as const;
|
|
26
|
+
const auth_types = ['all', 'none', 'authenticated', 'role', 'credential', 'keeper'] as const;
|
|
26
27
|
|
|
27
28
|
let auth_filter: (typeof auth_types)[number] = $state.raw('all');
|
|
28
29
|
let expanded_route: string | null = $state.raw(null);
|
|
@@ -49,6 +50,8 @@
|
|
|
49
50
|
return is_plain_authenticated_auth(auth);
|
|
50
51
|
case 'role':
|
|
51
52
|
return is_role_auth(auth);
|
|
53
|
+
case 'credential':
|
|
54
|
+
return is_credential_gated_auth(auth) && !is_keeper_auth(auth);
|
|
52
55
|
case 'keeper':
|
|
53
56
|
return is_keeper_auth(auth);
|
|
54
57
|
}
|
|
@@ -90,32 +93,44 @@
|
|
|
90
93
|
if (is_public_auth(auth)) return 'chip color_b';
|
|
91
94
|
if (is_keeper_auth(auth)) return 'chip color_c';
|
|
92
95
|
if (is_role_auth(auth)) return 'chip color_d';
|
|
96
|
+
if (is_credential_gated_auth(auth)) return 'chip color_e';
|
|
93
97
|
if (is_plain_authenticated_auth(auth)) return 'chip color_a';
|
|
94
98
|
return 'chip';
|
|
95
99
|
};
|
|
96
100
|
|
|
97
101
|
const role_count = $derived(Array.from(summary.role.values()).reduce((sum, n) => sum + n, 0));
|
|
102
|
+
const credential_count = $derived(
|
|
103
|
+
Array.from(summary.credential.values()).reduce((sum, n) => sum + n, 0)
|
|
104
|
+
);
|
|
98
105
|
</script>
|
|
99
106
|
|
|
100
107
|
<section>
|
|
101
108
|
<div class="row" style:gap="var(--space_md)" style:flex-wrap="wrap" style:align-items="center">
|
|
102
109
|
<span class="chip">{surface.routes.length} routes</span>
|
|
103
110
|
{#if summary.none > 0}<span class="chip color_b">{summary.none} public</span>{/if}
|
|
104
|
-
{#if summary.authenticated > 0}
|
|
105
|
-
|
|
106
|
-
|
|
111
|
+
{#if summary.authenticated > 0}
|
|
112
|
+
<span class="chip color_a">{summary.authenticated} authenticated</span>
|
|
113
|
+
{/if}
|
|
107
114
|
{#if role_count > 0}<span class="chip color_d">{role_count} role</span>{/if}
|
|
115
|
+
{#if credential_count > 0}
|
|
116
|
+
<span class="chip color_e">{credential_count} credential-gated</span>
|
|
117
|
+
{/if}
|
|
108
118
|
{#if summary.keeper > 0}<span class="chip color_c">{summary.keeper} keeper</span>{/if}
|
|
109
119
|
<span class="chip">{surface.middleware.length} middleware</span>
|
|
110
120
|
{#if rpc_method_count > 0}<span class="chip">{rpc_method_count} rpc methods</span>{/if}
|
|
111
121
|
{#if ws_method_count > 0}<span class="chip">{ws_method_count} ws methods</span>{/if}
|
|
112
122
|
{#if surface.env.length}<span class="chip">{surface.env.length} env</span>{/if}
|
|
113
123
|
{#if surface.events.length}<span class="chip">{surface.events.length} events</span>{/if}
|
|
114
|
-
{#if surface.diagnostics.length}
|
|
124
|
+
{#if surface.diagnostics.length}
|
|
125
|
+
{@const warnings = surface.diagnostics.filter(
|
|
115
126
|
(d: AppSurfaceDiagnostic) => d.level === 'warning'
|
|
116
|
-
)}
|
|
117
|
-
|
|
118
|
-
>
|
|
127
|
+
)}
|
|
128
|
+
{#if warnings.length}
|
|
129
|
+
<span class="chip color_e">
|
|
130
|
+
{warnings.length} warning{warnings.length === 1 ? '' : 's'}
|
|
131
|
+
</span>
|
|
132
|
+
{/if}
|
|
133
|
+
{/if}
|
|
119
134
|
</div>
|
|
120
135
|
|
|
121
136
|
<h3>routes</h3>
|
|
@@ -221,9 +236,9 @@
|
|
|
221
236
|
<tr>
|
|
222
237
|
<td><code>{mw.name}</code></td>
|
|
223
238
|
<td><code>{mw.path}</code></td>
|
|
224
|
-
<td class="text_50"
|
|
225
|
-
|
|
226
|
-
>
|
|
239
|
+
<td class="text_50">
|
|
240
|
+
{mw.error_schemas ? Object.keys(mw.error_schemas).join(', ') : '-'}
|
|
241
|
+
</td>
|
|
227
242
|
</tr>
|
|
228
243
|
{/each}
|
|
229
244
|
</tbody>
|
|
@@ -280,11 +295,11 @@
|
|
|
280
295
|
<td>
|
|
281
296
|
<!-- TODO fix the `as any` cast -->
|
|
282
297
|
{#if event.params_schema}
|
|
283
|
-
<code
|
|
284
|
-
|
|
298
|
+
<code>
|
|
299
|
+
{Object.keys(
|
|
285
300
|
(event.params_schema as any).properties ?? event.params_schema
|
|
286
|
-
).join(', ')}
|
|
287
|
-
>
|
|
301
|
+
).join(', ')}
|
|
302
|
+
</code>
|
|
288
303
|
{:else}
|
|
289
304
|
<span class="text_50">none</span>
|
|
290
305
|
{/if}
|
|
@@ -457,8 +472,9 @@
|
|
|
457
472
|
<tbody>
|
|
458
473
|
{#each surface.diagnostics as d, i (i)}
|
|
459
474
|
<tr>
|
|
460
|
-
<td
|
|
461
|
-
|
|
475
|
+
<td>
|
|
476
|
+
<span class={d.level === 'warning' ? 'chip color_e' : 'chip'}>{d.level}</span>
|
|
477
|
+
</td>
|
|
462
478
|
<td><code>{d.category}</code></td>
|
|
463
479
|
<td>{d.message}</td>
|
|
464
480
|
<td class="text_50">{d.source ?? '-'}</td>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SurfaceExplorer.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/SurfaceExplorer.svelte"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,UAAU,EAAyC,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"SurfaceExplorer.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/SurfaceExplorer.svelte"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,UAAU,EAAyC,MAAM,oBAAoB,CAAC;AAW3F,KAAK,gBAAgB,GAAI;IAAE,OAAO,EAAE,UAAU,CAAA;CAAE,CAAC;AAwdlD,QAAA,MAAM,eAAe,sDAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fuzdev/fuz_app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.113.0",
|
|
4
4
|
"description": "fullstack app library",
|
|
5
5
|
"glyph": "🗝",
|
|
6
6
|
"logo": "logo.svg",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"@electric-sql/pglite": ">=0.4",
|
|
35
|
-
"@fuzdev/
|
|
35
|
+
"@fuzdev/blake3-wasm": ">=0.2.0",
|
|
36
36
|
"@fuzdev/fuz_util": ">=0.68.0",
|
|
37
37
|
"@hono/node-server": ">=1",
|
|
38
38
|
"@hono/node-ws": ">=1",
|
|
@@ -73,12 +73,12 @@
|
|
|
73
73
|
},
|
|
74
74
|
"devDependencies": {
|
|
75
75
|
"@electric-sql/pglite": "^0.4.5",
|
|
76
|
-
"@fuzdev/
|
|
77
|
-
"@fuzdev/fuz_code": "^0.
|
|
78
|
-
"@fuzdev/fuz_css": "^0.63.
|
|
79
|
-
"@fuzdev/fuz_ui": "^0.
|
|
80
|
-
"@fuzdev/fuz_util": "^0.
|
|
81
|
-
"@fuzdev/gro": "0.
|
|
76
|
+
"@fuzdev/blake3-wasm": "^0.2.0",
|
|
77
|
+
"@fuzdev/fuz_code": "^0.48.0",
|
|
78
|
+
"@fuzdev/fuz_css": "^0.63.3",
|
|
79
|
+
"@fuzdev/fuz_ui": "^0.208.0",
|
|
80
|
+
"@fuzdev/fuz_util": "^0.69.0",
|
|
81
|
+
"@fuzdev/gro": "^0.212.0",
|
|
82
82
|
"@fuzdev/mdz": "^0.3.0",
|
|
83
83
|
"@hono/node-server": "^1.19.14",
|
|
84
84
|
"@hono/node-ws": "^1.3.1",
|
|
@@ -104,13 +104,13 @@
|
|
|
104
104
|
"prettier-plugin-svelte": "^4.1.1",
|
|
105
105
|
"svelte": "^5.56.4",
|
|
106
106
|
"svelte-check": "^4.7.1",
|
|
107
|
-
"svelte-docinfo": "^0.
|
|
107
|
+
"svelte-docinfo": "^0.7.0",
|
|
108
108
|
"svelte2tsx": "^0.7.52",
|
|
109
109
|
"tslib": "^2.8.1",
|
|
110
110
|
"typescript": "^5.9.3",
|
|
111
111
|
"typescript-eslint": "^8.57.0",
|
|
112
112
|
"vite": "^7.3.1",
|
|
113
|
-
"vitest": "^
|
|
113
|
+
"vitest": "^5.0.0",
|
|
114
114
|
"ws": "^8.20.1",
|
|
115
115
|
"zimmerframe": "^1.1.4",
|
|
116
116
|
"zod": "^4.3.6"
|