@fuzdev/fuz_app 0.144.0 → 0.146.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/actions/CLAUDE.md +7 -6
- package/dist/auth/CLAUDE.md +5 -2
- package/dist/auth/account_action_specs.d.ts.map +1 -1
- package/dist/auth/account_action_specs.js +2 -1
- package/dist/auth/account_route_schema.d.ts.map +1 -1
- package/dist/auth/account_route_schema.js +9 -2
- package/dist/auth/account_routes.d.ts +8 -0
- package/dist/auth/account_routes.d.ts.map +1 -1
- package/dist/auth/account_routes.js +12 -3
- package/dist/auth/request_context.d.ts +23 -0
- package/dist/auth/request_context.d.ts.map +1 -1
- package/dist/auth/request_context.js +32 -1
- package/dist/auth/token_scope.d.ts +22 -14
- package/dist/auth/token_scope.d.ts.map +1 -1
- package/dist/auth/token_scope.js +23 -14
- package/dist/db/CLAUDE.md +7 -3
- package/dist/db/fact_ddl.d.ts +1 -0
- package/dist/db/fact_ddl.d.ts.map +1 -1
- package/dist/db/fact_ddl.js +1 -0
- package/dist/db/fact_holder_queries.d.ts +8 -4
- package/dist/db/fact_holder_queries.d.ts.map +1 -1
- package/dist/db/fact_holder_queries.js +9 -5
- package/dist/db/fact_queries.d.ts +10 -7
- package/dist/db/fact_queries.d.ts.map +1 -1
- package/dist/db/fact_queries.js +31 -10
- package/dist/http/CLAUDE.md +2 -2
- package/dist/http/auth_shape.d.ts.map +1 -1
- package/dist/http/auth_shape.js +11 -6
- package/dist/http/proxy.d.ts +22 -21
- package/dist/http/proxy.d.ts.map +1 -1
- package/dist/http/proxy.js +28 -33
- package/dist/http/route_spec.d.ts +8 -0
- package/dist/http/route_spec.d.ts.map +1 -1
- package/dist/server/env.d.ts.map +1 -1
- package/dist/server/env.js +1 -1
- package/dist/server/fact_security_headers.d.ts +59 -0
- package/dist/server/fact_security_headers.d.ts.map +1 -0
- package/dist/server/fact_security_headers.js +58 -0
- package/dist/server/serve_fact_route.d.ts +21 -10
- package/dist/server/serve_fact_route.d.ts.map +1 -1
- package/dist/server/serve_fact_route.js +41 -50
- package/dist/server/x_accel.d.ts +135 -31
- package/dist/server/x_accel.d.ts.map +1 -1
- package/dist/server/x_accel.js +652 -88
- package/dist/testing/CLAUDE.md +73 -22
- package/dist/testing/cross_backend/capabilities.d.ts +0 -18
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/capabilities.js +1 -4
- package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_backend_configs.js +0 -7
- package/dist/testing/cross_backend/fact_serving.d.ts.map +1 -1
- package/dist/testing/cross_backend/fact_serving.js +85 -12
- package/dist/testing/cross_backend/identity_parity.d.ts +11 -0
- package/dist/testing/cross_backend/identity_parity.d.ts.map +1 -1
- package/dist/testing/cross_backend/identity_parity.js +124 -9
- package/dist/testing/cross_backend/login_security.d.ts.map +1 -1
- package/dist/testing/cross_backend/login_security.js +28 -0
- package/dist/testing/cross_backend/revocation_drivers.d.ts +70 -0
- package/dist/testing/cross_backend/revocation_drivers.d.ts.map +1 -0
- package/dist/testing/cross_backend/revocation_drivers.js +104 -0
- package/dist/testing/cross_backend/sse_round_trip.d.ts +6 -1
- package/dist/testing/cross_backend/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/cross_backend/sse_round_trip.js +52 -16
- package/dist/testing/cross_backend/token_scope_surface.d.ts.map +1 -1
- package/dist/testing/cross_backend/token_scope_surface.js +33 -0
- package/dist/testing/cross_backend/ws_round_trip.d.ts +13 -1
- package/dist/testing/cross_backend/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/cross_backend/ws_round_trip.js +155 -39
- package/package.json +3 -3
package/dist/actions/CLAUDE.md
CHANGED
|
@@ -159,10 +159,10 @@ if a consumer genuinely owns protocol actions in their typed API.
|
|
|
159
159
|
|
|
160
160
|
**Consumer tiers.** Single-source consumers (zzz) drop into the helpers
|
|
161
161
|
and accept the default `* as specs` namespace import. Multi-source
|
|
162
|
-
consumers (
|
|
163
|
-
`
|
|
164
|
-
`
|
|
165
|
-
|
|
162
|
+
consumers (stitching local specs with `all_admin_action_specs` /
|
|
163
|
+
`all_role_grant_offer_action_specs` / `all_account_action_specs` /
|
|
164
|
+
`all_self_service_role_action_specs` from fuz_app) call
|
|
165
|
+
`create_namespace_qualifier` once, then pass the returned
|
|
166
166
|
`qualify_spec` callback to multi-source helpers.
|
|
167
167
|
|
|
168
168
|
**Tier 1** (HTTP-only) emits a smaller surface — typically
|
|
@@ -962,8 +962,9 @@ answer. The close hands the frames still waiting in the connection's outbound
|
|
|
962
962
|
queue to the socket ahead of the close frame while the socket's buffer is below
|
|
963
963
|
`WS_WRITE_HIGH_WATER_BYTES`, so the reply arrives when it was queued behind
|
|
964
964
|
another frame of the same run on a socket that is keeping up, and is discarded
|
|
965
|
-
when the socket is backed up past that mark. (The Rust spine
|
|
966
|
-
the
|
|
965
|
+
when the socket is backed up past that mark. (The Rust spine drains first but marks the reply owed beforehand, and its
|
|
966
|
+
socket loop writes that reply ahead of the close — the same order on the wire,
|
|
967
|
+
held by the cross-backend WS suite.)
|
|
967
968
|
|
|
968
969
|
**Lifecycle hooks.** `on_socket_open({ws, connection_id, identity, notify, signal})`
|
|
969
970
|
fires once the connection is admitted and before the first message dispatches
|
package/dist/auth/CLAUDE.md
CHANGED
|
@@ -220,7 +220,7 @@ Everything else listed under §RPC action surfaces.
|
|
|
220
220
|
### Middleware
|
|
221
221
|
|
|
222
222
|
- `auth/middleware.ts` — `create_auth_middleware_specs(deps, options)` assembles `[origin, session, request_context, bearer_auth]` + optional `daemon_token` (the content-type refusal isn't a middleware: `apply_route_specs` checks every `POST` route, see `http/simple_request.ts`). The origin check mounts on every path (`ORIGIN_MIDDLEWARE_PATH`, `'*'`), less its `origin_carve_outs` — each a `MiddlewareCarveOut` (`{path, reason}`, copied and frozen at assembly by `freeze_origin_carve_outs`, refused by `assert_origin_carve_outs_valid` without a reason or when it overlaps the auth scope — `middleware_paths_overlap` against `AUTH_MIDDLEWARE_PATH`, which holds every credential-reading and credential-minting route — and recorded on the origin spec so the surface leaves its `403` off the carved routes); `AUTH_MIDDLEWARE_PATH` (`'/api/*'`) is where `create_app_server` mounts the rest of the stack; assembly asserts lean on it: `assert_endpoint_in_auth_scope` (an `rpc_endpoints` / `ws_endpoints` path, or the bootstrap route under `bootstrap.route_prefix`, must sit inside it — so no carve-out can reach one), `assert_credential_routes_in_auth_scope(route_specs)` (every registered route that reads a credential — `auth.account` not `'none'`, or marked `reads_credential`, as account status is — or is marked `mints_credential` — login, signup, bootstrap — must sit inside it too, so a consumer prefixing the relative account or signup specs outside `/api/` is refused), and `assert_middleware_stack_mounted(stack_specs, middleware_specs)`, run on a `transform_middleware` result against the pre-transform stack (`[proxy, ...auth]`) — each stack spec, matched by its `handler` closure (not its spoofable `name`), mounted exactly once at its original path (`'*'` for the trusted proxy and the origin check, `AUTH_MIDDLEWARE_PATH` for the rest of auth), in its original order; other specs may sit anywhere, including before the proxy and between the auth layers. The proxy is in the stack because dropping it turns every `get_client_ip` into `'unknown'`, one shared bucket for every IP-keyed limiter. The transform receives shallow copies in a fresh array, so an in-place mutation can't rewrite the originals the check reads. The check pins where those layers sit, not what added layers do — a layer you add runs with full context access. Both run in `create_test_app_surface_spec` too.
|
|
223
|
-
- `auth/request_context.ts` — `RequestContext`, `resolve_acting_actor`, `build_request_context`, predicates (`has_role`, `has_scoped_role`, `has_any_scoped_role` — the dispatcher's role gate and every builtin-role check read the **global** grant via `has_scoped_role(_, role, null)` / `has_any_scoped_role(_, roles, null)`; scope-blind `has_role` is never the gate for a builtin role), guards (`require_auth`, `require_role`, `require_credential_types`, `require_token_scope`), `token_scope_surface_denial` (the direct-call form, for surfaces that aren't route specs), `refresh_role_grants`.
|
|
223
|
+
- `auth/request_context.ts` — `RequestContext`, `resolve_acting_actor`, `build_request_context`, predicates (`has_role`, `has_scoped_role`, `has_any_scoped_role` — the dispatcher's role gate and every builtin-role check read the **global** grant via `has_scoped_role(_, role, null)` / `has_any_scoped_role(_, roles, null)`; scope-blind `has_role` is never the gate for a builtin role), guards (`require_auth`, `require_role`, `require_credential_types`, `require_token_scope`), `token_scope_surface_denial` (the direct-call form, for surfaces that aren't route specs), `get_public_account_id` (the account id a `reads_credential` route may read — `null` for a scope-narrowed token, which such a route answers as anonymous; twin of the Rust `AuthGate::public_request_context` downgrade), `refresh_role_grants`.
|
|
224
224
|
- `auth/session_middleware.ts` — `process_session_cookie` integration, `create_session_and_set_cookie` (shared by login / signup / bootstrap; returns the token hashes of the sessions the cap evicted, which the caller closes after its commit — only login passes a cap).
|
|
225
225
|
- `auth/resolved_auth.ts` — `ResolvedAuth` (the resolved credential as one value: `account_id`, `credential_type`, `token_hash`, `api_token_id`, `credential_deadline`), `get_resolved_auth(c)` (gathers it from the context keys the auth middleware set), and `revalidate_resolved_auth(deps, resolved)`, the no-touch re-read a long-lived connection runs once it is registered: session row / token row (by id) still live and still on the account, account not soft-deleted; a daemon token re-reads its account only. Reuses the middleware's own queries and predicates. Called by the WS upgrade (`actions/register_action_ws.ts`) and the audit SSE route between registering a connection pending and admitting it. Twin of the Rust `revalidate_resolved_auth`.
|
|
226
226
|
- `auth/credential_deadline.ts` — when a resolved credential stops authenticating, which a WebSocket or audit stream opened on it closes at (expiry writes nothing a revocation listener hears): `credential_deadline_after` (the earliest remaining lifetime, anchored to `performance.now()`), `EXPIRES_IN_MS_SQL` (a row's `expires_at - NOW()`, read on the database clock in the statement that checks it, so the session and token validation queries return `expires_in_ms`), and `schedule_credential_deadline` (a chain of timers of at most `CREDENTIAL_DEADLINE_TIMER_CAP_MS`, an hour — a 30-day session is past `setTimeout`'s ceiling — each wake-up also checking a wall-clock copy of the deadline, so a host suspend, which stops the monotonic clock, delays the close by at most one timer after the resume). The middleware sets `CREDENTIAL_DEADLINE_KEY`: session — the earlier of the cookie's signed expiry (`SESSION_COOKIE_EXPIRES_AT_KEY`, set by the session middleware) and the row's; bearer — the row's, none for an eternal token; daemon token — none. Twin of the Rust `ResolvedAuth::credential_deadline`.
|
|
@@ -811,7 +811,10 @@ Options: `roles?: RoleSchemaResult` (drives admin-grant-path lookup),
|
|
|
811
811
|
|
|
812
812
|
`account_verify` is intentionally on both surfaces: the REST `GET /verify`
|
|
813
813
|
shim is a status-only nginx probe; the RPC action returns
|
|
814
|
-
`SessionAccountJson` for programmatic callers.
|
|
814
|
+
`SessionAccountJson` for programmatic callers. Only the RPC action is open to a
|
|
815
|
+
narrowed token that names it: the REST probe is the `surface:account_verify`
|
|
816
|
+
rule-3 surface, refused to every narrowed token, and `GET /status` answers one
|
|
817
|
+
as the anonymous caller.
|
|
815
818
|
|
|
816
819
|
`session_id` validates as `Blake3Hash`; `token_id` as `ApiTokenId`
|
|
817
820
|
(`tok_[A-Za-z0-9_-]{12}`).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"account_action_specs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_action_specs.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AAW3E,6EAA6E;AAC7E,eAAO,MAAM,WAAW,WAAW,CAAC;AACpC,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD,uDAAuD;AACvD,eAAO,MAAM,gBAAgB,WAAW,CAAC;AACzC,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEhE,yCAAyC;AACzC,eAAO,MAAM,iBAAiB;;;;;;;kBAE5B,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAElE,2EAA2E;AAC3E,eAAO,MAAM,kBAAkB;;kBAE7B,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE,iFAAiF;AACjF,eAAO,MAAM,mBAAmB;;;kBAG9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,6DAA6D;AAC7D,eAAO,MAAM,qBAAqB,WAAW,CAAC;AAC9C,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAE1E,+CAA+C;AAC/C,eAAO,MAAM,sBAAsB;;;kBAGjC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAE5E;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAE7C;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"account_action_specs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_action_specs.ts"],"names":[],"mappings":"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"}
|
|
@@ -68,7 +68,8 @@ export const TokenCreateInput = z.strictObject({
|
|
|
68
68
|
scope: TokenScopeInput.meta({
|
|
69
69
|
description: "What the token may do. `{kind:'full'}` for full account authority, or " +
|
|
70
70
|
"`{kind:'methods',methods:[…]}` to narrow it to named RPC methods — a " +
|
|
71
|
-
'narrowed token is RPC-only and reaches no non-RPC surface
|
|
71
|
+
'narrowed token is RPC-only and reaches no non-RPC surface that reads ' +
|
|
72
|
+
'account data or carries authority.'
|
|
72
73
|
}),
|
|
73
74
|
lifetime: TokenLifetimeInput.meta({
|
|
74
75
|
description: "How long the token lives. `{kind:'eternal'}` never expires; " +
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"account_route_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_route_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAQvD,kFAAkF;AAClF,eAAO,MAAM,kBAAkB,WAAW,CAAC;AAC3C,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE,4CAA4C;AAC5C,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;kBAI9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,4EAA4E;AAC5E,eAAO,MAAM,iCAAiC;;;iBAG5C,CAAC;AACH,MAAM,MAAM,iCAAiC,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iCAAiC,CAAC,CAAC;AAElG,oFAAoF;AACpF,eAAO,MAAM,UAAU;;;kBAGrB,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,6EAA6E;AAC7E,eAAO,MAAM,WAAW;;kBAEtB,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD,2EAA2E;AAC3E,eAAO,MAAM,WAAW,WAAW,CAAC;AACpC,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD,wFAAwF;AACxF,eAAO,MAAM,YAAY;;;kBAGvB,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAExD,sHAAsH;AACtH,eAAO,MAAM,mBAAmB;;;kBAG9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,uGAAuG;AACvG,eAAO,MAAM,oBAAoB;;;;kBAI/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAEtC,8CAA8C;AAC9C,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAaD,CAAC;AAEvC,8EAA8E;AAC9E,MAAM,WAAW,wBAAwB;IACxC,8FAA8F;IAC9F,0BAA0B,EAAE,OAAO,CAAC;CACpC;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,2BAA2B,GACvC,SAAS,wBAAwB,KAC/B,CACF,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,EAC1B,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,EAC1B,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,EAC1B,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,
|
|
1
|
+
{"version":3,"file":"account_route_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_route_schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAQvD,kFAAkF;AAClF,eAAO,MAAM,kBAAkB,WAAW,CAAC;AAC3C,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE,4CAA4C;AAC5C,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;kBAI9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,4EAA4E;AAC5E,eAAO,MAAM,iCAAiC;;;iBAG5C,CAAC;AACH,MAAM,MAAM,iCAAiC,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iCAAiC,CAAC,CAAC;AAElG,oFAAoF;AACpF,eAAO,MAAM,UAAU;;;kBAGrB,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,6EAA6E;AAC7E,eAAO,MAAM,WAAW;;kBAEtB,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD,2EAA2E;AAC3E,eAAO,MAAM,WAAW,WAAW,CAAC;AACpC,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD,wFAAwF;AACxF,eAAO,MAAM,YAAY;;;kBAGvB,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAExD,sHAAsH;AACtH,eAAO,MAAM,mBAAmB;;;kBAG9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,uGAAuG;AACvG,eAAO,MAAM,oBAAoB;;;;kBAI/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAEtC,8CAA8C;AAC9C,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAaD,CAAC;AAEvC,8EAA8E;AAC9E,MAAM,WAAW,wBAAwB;IACxC,8FAA8F;IAC9F,0BAA0B,EAAE,OAAO,CAAC;CACpC;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,2BAA2B,GACvC,SAAS,wBAAwB,KAC/B,CACF,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,EAC1B,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,EAC1B,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,EAC1B,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,CAqE1B,CAAC"}
|
|
@@ -109,8 +109,15 @@ export const create_account_route_shapes = (options) => [
|
|
|
109
109
|
{
|
|
110
110
|
method: 'GET',
|
|
111
111
|
path: '/verify',
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
// a non-RPC surface: the probe answers 200 for the account behind a
|
|
113
|
+
// credential, so a narrowed token is refused it (it would otherwise pass
|
|
114
|
+
// an nginx `auth_request` gate as its account)
|
|
115
|
+
auth: {
|
|
116
|
+
account: 'required',
|
|
117
|
+
actor: 'none',
|
|
118
|
+
required_scope: 'surface:account_verify'
|
|
119
|
+
},
|
|
120
|
+
description: 'Session-validity probe for nginx auth_request (empty body; 200, 401, or 403 for a narrowed token)',
|
|
114
121
|
input: z.null(),
|
|
115
122
|
output: z.null()
|
|
116
123
|
},
|
|
@@ -32,6 +32,14 @@ import type { RouteFactoryDeps } from './deps.js';
|
|
|
32
32
|
* - Authenticated: returns `{account}` with 200
|
|
33
33
|
* - Unauthenticated: returns 401 with optional `bootstrap_available` flag
|
|
34
34
|
*
|
|
35
|
+
* A scope-narrowed API token is answered as the unauthenticated caller, byte
|
|
36
|
+
* for byte. A narrowed token reaches no non-RPC surface
|
|
37
|
+
* (`token_scope_admits_non_rpc`), and this one hands out the account's email,
|
|
38
|
+
* its verification state, and the acting actor's role grants; the route
|
|
39
|
+
* already answers anonymous callers, so that answer is the refusal — a 403
|
|
40
|
+
* would tell the holder nothing that dropping the header doesn't. The handler
|
|
41
|
+
* reads the credential through `get_public_account_id`, which applies it.
|
|
42
|
+
*
|
|
35
43
|
* This eliminates the need for a separate `/health` fetch on page load —
|
|
36
44
|
* the frontend gets both session state and bootstrap availability in one request.
|
|
37
45
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"account_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"account_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AA6B1D,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAIxE,OAAO,EAGN,KAAK,WAAW,EAEhB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAOlD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,gCAAgC,GAAI,UAAU,oBAAoB,KAAG,SAmFhF,CAAC;AAEH,iDAAiD;AACjD,MAAM,WAAW,oBAAoB;IACpC,yDAAyD;IACzD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8FAA8F;IAC9F,gBAAgB,CAAC,EAAE;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC;CAC1C;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAE/C;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B,KAAK,CAAC;AAQ/C;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,uBAAuB;IACvC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;CACxC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAoB,SAAQ,uBAAuB;IACnE;;;;;;;OAOG;IACH,qBAAqB,EAAE,WAAW,GAAG,IAAI,CAAC;IAC1C,4FAA4F;IAC5F,0BAA0B,EAAE,WAAW,GAAG,IAAI,CAAC;IAC/C,2FAA2F;IAC3F,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;OAOG;IACH,gBAAgB,CAAC,EAAE;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC;CAC1C;AAID;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,0BAA0B,GACtC,MAAM,gBAAgB,EACtB,SAAS,mBAAmB,KAC1B,KAAK,CAAC,SAAS,CA+VjB,CAAC"}
|
|
@@ -27,8 +27,8 @@ import { account_status_route_shape, create_account_route_shapes, DEFAULT_MAX_SE
|
|
|
27
27
|
import { hash_session_token, query_session_revoke_all_for_account, query_session_revoke_by_hash_unscoped } from './session_queries.js';
|
|
28
28
|
import { query_account_by_username_or_email, query_update_account_password } from './account_queries.js';
|
|
29
29
|
import { query_revoke_all_api_tokens_for_account } from './api_token_queries.js';
|
|
30
|
-
import { build_account_context, build_request_context, get_request_context, require_request_context, resolve_acting_actor } from './request_context.js';
|
|
31
|
-
import {
|
|
30
|
+
import { build_account_context, build_request_context, get_public_account_id, get_request_context, require_request_context, resolve_acting_actor } from './request_context.js';
|
|
31
|
+
import { CREDENTIAL_TYPE_KEY } from '../hono_context.js';
|
|
32
32
|
import { get_route_input } from '../http/route_spec.js';
|
|
33
33
|
import { get_client_ip } from '../http/client_ip.js';
|
|
34
34
|
import { rate_limit_ip_key } from '../http/ip_canonical.js';
|
|
@@ -43,6 +43,14 @@ import { ERROR_AUTHENTICATION_REQUIRED, ERROR_INVALID_CREDENTIALS } from '../htt
|
|
|
43
43
|
* - Authenticated: returns `{account}` with 200
|
|
44
44
|
* - Unauthenticated: returns 401 with optional `bootstrap_available` flag
|
|
45
45
|
*
|
|
46
|
+
* A scope-narrowed API token is answered as the unauthenticated caller, byte
|
|
47
|
+
* for byte. A narrowed token reaches no non-RPC surface
|
|
48
|
+
* (`token_scope_admits_non_rpc`), and this one hands out the account's email,
|
|
49
|
+
* its verification state, and the acting actor's role grants; the route
|
|
50
|
+
* already answers anonymous callers, so that answer is the refusal — a 403
|
|
51
|
+
* would tell the holder nothing that dropping the header doesn't. The handler
|
|
52
|
+
* reads the credential through `get_public_account_id`, which applies it.
|
|
53
|
+
*
|
|
46
54
|
* This eliminates the need for a separate `/health` fetch on page load —
|
|
47
55
|
* the frontend gets both session state and bootstrap availability in one request.
|
|
48
56
|
*
|
|
@@ -53,7 +61,8 @@ export const create_account_status_route_spec = (options) => ({
|
|
|
53
61
|
...account_status_route_shape,
|
|
54
62
|
path: options?.path ?? account_status_route_shape.path,
|
|
55
63
|
handler: async (c, route) => {
|
|
56
|
-
|
|
64
|
+
// a narrowed token is the anonymous caller here, never a refusal
|
|
65
|
+
const account_id = get_public_account_id(c);
|
|
57
66
|
if (!account_id) {
|
|
58
67
|
return c.json({
|
|
59
68
|
error: ERROR_AUTHENTICATION_REQUIRED,
|
|
@@ -268,6 +268,29 @@ export declare const create_request_context_middleware: (deps: QueryDeps, sessio
|
|
|
268
268
|
* @returns the 403 to return, or `null` when the caller may proceed
|
|
269
269
|
*/
|
|
270
270
|
export declare const token_scope_surface_denial: (c: Context, capability: string) => Response | null;
|
|
271
|
+
/**
|
|
272
|
+
* The account id a public route — one that answers anonymous callers and reads
|
|
273
|
+
* the credential (`reads_credential`) — may act on: `null` for the anonymous
|
|
274
|
+
* caller **and for a scope-narrowed API token**. Rule 3 in its
|
|
275
|
+
* anonymous-admitting disposition — the downgrade the Rust
|
|
276
|
+
* `AuthGate::public_request_context` applies.
|
|
277
|
+
*
|
|
278
|
+
* A narrowed token reaches no non-RPC surface that reads account data
|
|
279
|
+
* (`token_scope_admits_non_rpc`). On a route that refuses anonymous callers
|
|
280
|
+
* that is a 403 (`auth.required_scope`); on one that answers them, a 403
|
|
281
|
+
* would enforce nothing — the holder drops the `Authorization` header and
|
|
282
|
+
* reads the anonymous answer anyway — so the narrowed token *is* the
|
|
283
|
+
* anonymous caller there. Read the credential through this on any
|
|
284
|
+
* `reads_credential` route instead of `ACCOUNT_ID_KEY` directly, so the
|
|
285
|
+
* downgrade is one decision rather than an `&&` hand-rolled per handler.
|
|
286
|
+
*
|
|
287
|
+
* The RPC endpoint is the exception: its dispatcher gates each method against
|
|
288
|
+
* the scope, so it reads the full credential.
|
|
289
|
+
*
|
|
290
|
+
* @param c - the request context, after the auth middleware chain
|
|
291
|
+
* @returns the account id, or `null` for an anonymous caller or a narrowed token
|
|
292
|
+
*/
|
|
293
|
+
export declare const get_public_account_id: (c: Context) => string | null;
|
|
271
294
|
/**
|
|
272
295
|
* Create middleware refusing a route to a credential whose scope does not admit
|
|
273
296
|
* `capability`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request_context.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/request_context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAEvD,OAAO,EACN,KAAK,OAAO,EACZ,KAAK,KAAK,EAEV,KAAK,SAAS,EACd,MAAM,qBAAqB,CAAC;AAQ7B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"request_context.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/request_context.ts"],"names":[],"mappings":"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"}
|
|
@@ -40,7 +40,7 @@ import { query_account_by_id, query_actor_by_id, query_active_actors_by_account
|
|
|
40
40
|
import { query_role_grant_find_active_for_actor } from './role_grant_queries.js';
|
|
41
41
|
import { ACCOUNT_ID_KEY, AUTH_API_TOKEN_ID_KEY, CREDENTIAL_DEADLINE_KEY, CREDENTIAL_TYPE_KEY, SESSION_COOKIE_EXPIRES_AT_KEY, TOKEN_SCOPE_KEY, TEST_CONTEXT_PRESET_KEY } from '../hono_context.js';
|
|
42
42
|
import { credential_deadline_after } from './credential_deadline.js';
|
|
43
|
-
import { parse_token_scope_capability, token_scope_admits_capability, token_scope_denied_body, token_scope_full } from './token_scope.js';
|
|
43
|
+
import { parse_token_scope_capability, token_scope_admits_capability, token_scope_admits_non_rpc, token_scope_denied_body, token_scope_full } from './token_scope.js';
|
|
44
44
|
import { is_public_auth, needs_actor, parse_acting } from '../http/auth_shape.js';
|
|
45
45
|
import { is_null_schema } from '../http/schema_helpers.js';
|
|
46
46
|
import { ERROR_AUTHENTICATION_REQUIRED, ERROR_INSUFFICIENT_PERMISSIONS, ERROR_CREDENTIAL_TYPE_REQUIRED, ERROR_ACTOR_REQUIRED, ERROR_ACTOR_NOT_ON_ACCOUNT, ERROR_NO_ACTORS_ON_ACCOUNT, ERROR_ACCOUNT_VANISHED } from '../http/error_schemas.js';
|
|
@@ -308,6 +308,37 @@ const token_scope_denial = (c, capability) => {
|
|
|
308
308
|
* @returns the 403 to return, or `null` when the caller may proceed
|
|
309
309
|
*/
|
|
310
310
|
export const token_scope_surface_denial = (c, capability) => token_scope_denial(c, { kind: 'surface', capability });
|
|
311
|
+
/**
|
|
312
|
+
* The account id a public route — one that answers anonymous callers and reads
|
|
313
|
+
* the credential (`reads_credential`) — may act on: `null` for the anonymous
|
|
314
|
+
* caller **and for a scope-narrowed API token**. Rule 3 in its
|
|
315
|
+
* anonymous-admitting disposition — the downgrade the Rust
|
|
316
|
+
* `AuthGate::public_request_context` applies.
|
|
317
|
+
*
|
|
318
|
+
* A narrowed token reaches no non-RPC surface that reads account data
|
|
319
|
+
* (`token_scope_admits_non_rpc`). On a route that refuses anonymous callers
|
|
320
|
+
* that is a 403 (`auth.required_scope`); on one that answers them, a 403
|
|
321
|
+
* would enforce nothing — the holder drops the `Authorization` header and
|
|
322
|
+
* reads the anonymous answer anyway — so the narrowed token *is* the
|
|
323
|
+
* anonymous caller there. Read the credential through this on any
|
|
324
|
+
* `reads_credential` route instead of `ACCOUNT_ID_KEY` directly, so the
|
|
325
|
+
* downgrade is one decision rather than an `&&` hand-rolled per handler.
|
|
326
|
+
*
|
|
327
|
+
* The RPC endpoint is the exception: its dispatcher gates each method against
|
|
328
|
+
* the scope, so it reads the full credential.
|
|
329
|
+
*
|
|
330
|
+
* @param c - the request context, after the auth middleware chain
|
|
331
|
+
* @returns the account id, or `null` for an anonymous caller or a narrowed token
|
|
332
|
+
*/
|
|
333
|
+
export const get_public_account_id = (c) => {
|
|
334
|
+
const account_id = c.get(ACCOUNT_ID_KEY) ?? null;
|
|
335
|
+
if (!account_id)
|
|
336
|
+
return null;
|
|
337
|
+
const scope = c.get(TOKEN_SCOPE_KEY);
|
|
338
|
+
if (scope && !token_scope_admits_non_rpc(scope))
|
|
339
|
+
return null;
|
|
340
|
+
return account_id;
|
|
341
|
+
};
|
|
311
342
|
/**
|
|
312
343
|
* Create middleware refusing a route to a credential whose scope does not admit
|
|
313
344
|
* `capability`.
|
|
@@ -19,9 +19,12 @@
|
|
|
19
19
|
* 2. **`methods` denies every method it does not list.** An empty list is legal
|
|
20
20
|
* and renders the token inert for RPC — unlike an empty `credential_types`
|
|
21
21
|
* allowlist, which is rejected because it renders an *action* unreachable.
|
|
22
|
-
* 3. **A narrowed token is RPC-only.** `methods` denies every non-RPC
|
|
23
|
-
* surface
|
|
24
|
-
* audit SSE stream,
|
|
22
|
+
* 3. **A narrowed token is RPC-only.** `methods` denies outright every non-RPC
|
|
23
|
+
* spine surface that reads account data or carries authority — the
|
|
24
|
+
* db-admin browser, the bare-hash fact read, the audit SSE stream, the WS
|
|
25
|
+
* upgrade, the surface explorer, and the `auth_request` probe. See
|
|
26
|
+
* `token_scope_admits_non_rpc`, and `docs/security.md` §Token scoping for
|
|
27
|
+
* the reads that answer it as anonymous and the server status read.
|
|
25
28
|
*
|
|
26
29
|
* @module
|
|
27
30
|
*/
|
|
@@ -94,10 +97,10 @@ export declare const token_scope_admits_method: (scope: TokenScope, method: stri
|
|
|
94
97
|
/**
|
|
95
98
|
* Does `scope` admit a non-RPC spine surface?
|
|
96
99
|
*
|
|
97
|
-
* **A narrowed token reaches no non-RPC surface
|
|
98
|
-
* half of token scoping, and what
|
|
99
|
-
* callers and the Rust twin use that
|
|
100
|
-
* §Token scoping.
|
|
100
|
+
* **A narrowed token reaches no non-RPC surface that reads account data or
|
|
101
|
+
* carries authority** — the load-bearing half of token scoping, and what
|
|
102
|
+
* "rule 3" names wherever this module's callers and the Rust twin use that
|
|
103
|
+
* shorthand. See `docs/security.md` §Token scoping.
|
|
101
104
|
*
|
|
102
105
|
* A method-name allowlist alone would have been a false promise: the db-admin
|
|
103
106
|
* browser serves paginated rows of any `public` table (including
|
|
@@ -105,16 +108,21 @@ export declare const token_scope_admits_method: (scope: TokenScope, method: stri
|
|
|
105
108
|
* gated on a global role a bearer credential satisfies. A token whose UI badge
|
|
106
109
|
* read "scoped to `cell_get`" could still delete an account row.
|
|
107
110
|
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
+
* The predicate is all-or-nothing: no per-surface vocabulary decides anything,
|
|
112
|
+
* so it fits in one sentence a user can hold — *a scoped token is RPC-only*.
|
|
113
|
+
* Placement is per site: a route that refuses anonymous callers declares
|
|
114
|
+
* `auth.required_scope`, one that answers them reads the credential through
|
|
115
|
+
* `get_public_account_id` and answers a narrowed token as anonymous, and the
|
|
116
|
+
* surface census pins every site.
|
|
111
117
|
*/
|
|
112
118
|
export declare const token_scope_admits_non_rpc: (scope: TokenScope) => boolean;
|
|
113
119
|
/**
|
|
114
120
|
* The non-RPC surfaces the **spine itself** mounts (rule 3). Twin of the Rust
|
|
115
|
-
* `TokenSurface` enum, name for name, except
|
|
116
|
-
* `GET /api/surface` explorer
|
|
117
|
-
*
|
|
121
|
+
* `TokenSurface` enum, name for name, except the two only the TS spine serves:
|
|
122
|
+
* `app_surface`, the opt-in `GET /api/surface` explorer
|
|
123
|
+
* (`server/surface_route.ts`), and `account_verify`, the
|
|
124
|
+
* `GET /api/account/verify` nginx `auth_request` probe
|
|
125
|
+
* (`auth/account_route_schema.ts`).
|
|
118
126
|
*
|
|
119
127
|
* Not a registry a consumer registers into. `RouteAuth.required_scope` accepts
|
|
120
128
|
* any well-formed `surface:<name>` (see `parse_token_scope_capability`), so a
|
|
@@ -128,7 +136,7 @@ export declare const token_scope_admits_non_rpc: (scope: TokenScope) => boolean;
|
|
|
128
136
|
* refused, in the `required_scope` a denial reports. That is the same field
|
|
129
137
|
* whose `rpc:<method>` arm has always carried arbitrary consumer method names.
|
|
130
138
|
*/
|
|
131
|
-
export declare const TOKEN_SURFACES: readonly ["db_admin", "fact_bare", "audit_stream", "ws_upgrade", "app_surface"];
|
|
139
|
+
export declare const TOKEN_SURFACES: readonly ["db_admin", "fact_bare", "audit_stream", "ws_upgrade", "app_surface", "account_verify"];
|
|
132
140
|
export type TokenSurface = (typeof TOKEN_SURFACES)[number];
|
|
133
141
|
/**
|
|
134
142
|
* Is `value` one of the surfaces the spine mounts?
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token_scope.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/token_scope.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"token_scope.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/token_scope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,wFAAwF;AACxF,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAErC;;;GAGG;AACH,eAAO,MAAM,qBAAqB,QAAW,CAAC;AAE9C;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,UAAU;;;;;;;;;4BAYrB,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe;;;;;4BAG1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,kDAAkD;AAClD,eAAO,MAAM,gBAAgB,QAAO,UAGlC,CAAC;AAEH,uCAAuC;AACvC,eAAO,MAAM,mBAAmB,GAAI,SAAS,KAAK,CAAC,MAAM,CAAC,KAAG,UAI3D,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,GAAI,OAAO,eAAe,KAAG,UAC8B,CAAC;AAE/F;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,GAAI,OAAO,UAAU,EAAE,QAAQ,MAAM,KAAG,OACtB,CAAC;AAEzD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,0BAA0B,GAAI,OAAO,UAAU,KAAG,OAAgC,CAAC;AAEhG;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,cAAc,mGAOjB,CAAC;AACX,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAE3D;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,GAAI,OAAO,MAAM,KAAG,KAAK,IAAI,YACA,CAAC;AAE3D;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,QAAgC,CAAC;AAQtE;;;;;;GAMG;AACH,eAAO,MAAM,6BAA6B,GAAI,QAAQ,MAAM,KAAG,MAC3B,CAAC;AAErC;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,oBAAoB,GAC/B;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/F;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,4BAA4B,GAAI,OAAO,MAAM,KAAG,oBAAoB,GAAG,IAUnF,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,6BAA6B,GACzC,OAAO,UAAU,EACjB,YAAY,oBAAoB,KAC9B,OAGkC,CAAC;AAEtC;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB,GACnC,YAAY,MAAM,KAChB;IAAE,KAAK,EAAE,sBAAsB,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAGxD,CAAC;AAEH,4CAA4C;AAC5C,eAAO,MAAM,iBAAiB,GAAI,OAAO,UAAU,KAAG,MAGrD,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,GAAI,KAAK,MAAM,KAAG,UAAU,GAAG,IAU5D,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAAI,OAAO,UAAU,KAAG,MAQzD,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,GAAI,OAAO,OAAO,KAAG,UAAU,GAAG,IAQtE,CAAC"}
|
package/dist/auth/token_scope.js
CHANGED
|
@@ -19,9 +19,12 @@
|
|
|
19
19
|
* 2. **`methods` denies every method it does not list.** An empty list is legal
|
|
20
20
|
* and renders the token inert for RPC — unlike an empty `credential_types`
|
|
21
21
|
* allowlist, which is rejected because it renders an *action* unreachable.
|
|
22
|
-
* 3. **A narrowed token is RPC-only.** `methods` denies every non-RPC
|
|
23
|
-
* surface
|
|
24
|
-
* audit SSE stream,
|
|
22
|
+
* 3. **A narrowed token is RPC-only.** `methods` denies outright every non-RPC
|
|
23
|
+
* spine surface that reads account data or carries authority — the
|
|
24
|
+
* db-admin browser, the bare-hash fact read, the audit SSE stream, the WS
|
|
25
|
+
* upgrade, the surface explorer, and the `auth_request` probe. See
|
|
26
|
+
* `token_scope_admits_non_rpc`, and `docs/security.md` §Token scoping for
|
|
27
|
+
* the reads that answer it as anonymous and the server status read.
|
|
25
28
|
*
|
|
26
29
|
* @module
|
|
27
30
|
*/
|
|
@@ -100,10 +103,10 @@ export const token_scope_admits_method = (scope, method) => scope.kind === 'full
|
|
|
100
103
|
/**
|
|
101
104
|
* Does `scope` admit a non-RPC spine surface?
|
|
102
105
|
*
|
|
103
|
-
* **A narrowed token reaches no non-RPC surface
|
|
104
|
-
* half of token scoping, and what
|
|
105
|
-
* callers and the Rust twin use that
|
|
106
|
-
* §Token scoping.
|
|
106
|
+
* **A narrowed token reaches no non-RPC surface that reads account data or
|
|
107
|
+
* carries authority** — the load-bearing half of token scoping, and what
|
|
108
|
+
* "rule 3" names wherever this module's callers and the Rust twin use that
|
|
109
|
+
* shorthand. See `docs/security.md` §Token scoping.
|
|
107
110
|
*
|
|
108
111
|
* A method-name allowlist alone would have been a false promise: the db-admin
|
|
109
112
|
* browser serves paginated rows of any `public` table (including
|
|
@@ -111,16 +114,21 @@ export const token_scope_admits_method = (scope, method) => scope.kind === 'full
|
|
|
111
114
|
* gated on a global role a bearer credential satisfies. A token whose UI badge
|
|
112
115
|
* read "scoped to `cell_get`" could still delete an account row.
|
|
113
116
|
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
+
* The predicate is all-or-nothing: no per-surface vocabulary decides anything,
|
|
118
|
+
* so it fits in one sentence a user can hold — *a scoped token is RPC-only*.
|
|
119
|
+
* Placement is per site: a route that refuses anonymous callers declares
|
|
120
|
+
* `auth.required_scope`, one that answers them reads the credential through
|
|
121
|
+
* `get_public_account_id` and answers a narrowed token as anonymous, and the
|
|
122
|
+
* surface census pins every site.
|
|
117
123
|
*/
|
|
118
124
|
export const token_scope_admits_non_rpc = (scope) => scope.kind === 'full';
|
|
119
125
|
/**
|
|
120
126
|
* The non-RPC surfaces the **spine itself** mounts (rule 3). Twin of the Rust
|
|
121
|
-
* `TokenSurface` enum, name for name, except
|
|
122
|
-
* `GET /api/surface` explorer
|
|
123
|
-
*
|
|
127
|
+
* `TokenSurface` enum, name for name, except the two only the TS spine serves:
|
|
128
|
+
* `app_surface`, the opt-in `GET /api/surface` explorer
|
|
129
|
+
* (`server/surface_route.ts`), and `account_verify`, the
|
|
130
|
+
* `GET /api/account/verify` nginx `auth_request` probe
|
|
131
|
+
* (`auth/account_route_schema.ts`).
|
|
124
132
|
*
|
|
125
133
|
* Not a registry a consumer registers into. `RouteAuth.required_scope` accepts
|
|
126
134
|
* any well-formed `surface:<name>` (see `parse_token_scope_capability`), so a
|
|
@@ -139,7 +147,8 @@ export const TOKEN_SURFACES = [
|
|
|
139
147
|
'fact_bare',
|
|
140
148
|
'audit_stream',
|
|
141
149
|
'ws_upgrade',
|
|
142
|
-
'app_surface'
|
|
150
|
+
'app_surface',
|
|
151
|
+
'account_verify'
|
|
143
152
|
];
|
|
144
153
|
/**
|
|
145
154
|
* Is `value` one of the surfaces the spine mounts?
|
package/dist/db/CLAUDE.md
CHANGED
|
@@ -177,9 +177,13 @@ records who holds them. Optional — minimal consumers never migrate it.
|
|
|
177
177
|
DO NOTHING`), `_put_fact_refs`, `_get_fact` / `_get_fact_meta` / `_has_fact`
|
|
178
178
|
/ `_get_fact_refs`, `_delete_fact` (returns `{size, external_url}` for
|
|
179
179
|
external unlink), and the cell-coupled orphan queries `query_orphan_facts_list`
|
|
180
|
-
/ `_select_for_delete` (a fact is orphan when no active `cell.refs` names it
|
|
180
|
+
/ `_select_for_delete` (a fact is orphan when no active `cell.refs` names it;
|
|
181
|
+
the `older_than` cutoff keeps a fact whose `created_at` or any
|
|
182
|
+
`fact_holder.created_at` is within the window, so a deduplicated re-upload
|
|
183
|
+
survives any sweep whose window covers it).
|
|
181
184
|
- **`fact_holder_queries.ts`** — `FACT_HOLDER_COLUMNS`, `query_put_fact_holder`
|
|
182
|
-
(idempotent
|
|
185
|
+
(idempotent upsert — a repeat put refreshes the row's `created_at` to the
|
|
186
|
+
most recent put), and the admission reads `query_fact_holder_table_exists` /
|
|
183
187
|
`query_fact_holder_unheld` (answered from holder rows alone).
|
|
184
188
|
- **`fact_store.ts`** — `PgFactStore`, implementing `HeldFactStore` (reads per
|
|
185
189
|
the `@fuzdev/fuz_util/fact_store.ts` `FactStore` contract; puts take
|
|
@@ -212,7 +216,7 @@ DO NOTHING`), `_put_fact_refs`, `_get_fact` / `_get_fact_meta` / `_has_fact`
|
|
|
212
216
|
- **`fact_store_errors.ts`** — `FactPayloadTooLargeError` / `FactStorageFullError` (+
|
|
213
217
|
`is_enospc_error`) thrown by `put_stream`, for a consumer route's 413 / 507.
|
|
214
218
|
- The HTTP serving plumbing lives under `server/` (`serve_fact_route.ts`,
|
|
215
|
-
`x_accel.ts`).
|
|
219
|
+
`x_accel.ts`, `fact_security_headers.ts`).
|
|
216
220
|
|
|
217
221
|
### Migration namespace order
|
|
218
222
|
|
package/dist/db/fact_ddl.d.ts
CHANGED
|
@@ -48,6 +48,7 @@ export declare const MEMOS_SCHEMA = "\nCREATE TABLE IF NOT EXISTS memo (\n\tfn_i
|
|
|
48
48
|
* Both FKs cascade: deleting an actor erases its possession, and deleting a
|
|
49
49
|
* fact (the orphan sweep) drops its holders. The primary key leads with
|
|
50
50
|
* `actor_id`, serving the admission lookup and the actor-side cascade.
|
|
51
|
+
* `created_at` is the holder's most recent put, not the first.
|
|
51
52
|
*/
|
|
52
53
|
export declare const FACT_HOLDER_SCHEMA = "\nCREATE TABLE IF NOT EXISTS fact_holder (\n\tactor_id UUID NOT NULL REFERENCES actor(id) ON DELETE CASCADE,\n\thash TEXT NOT NULL REFERENCES fact(hash) ON DELETE CASCADE,\n\tcreated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n\tPRIMARY KEY (actor_id, hash)\n)";
|
|
53
54
|
/** Serves the fact-side cascade of `fact_holder`. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fact_ddl.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/fact_ddl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAElE,mDAAmD;AACnD,eAAO,MAAM,YAAY,qSASvB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,6LAK3B,CAAC;AAEH,4DAA4D;AAC5D,eAAO,MAAM,sBAAsB,8EACqC,CAAC;AAEzE,oFAAoF;AACpF,eAAO,MAAM,YAAY,mNAOvB,CAAC;AAEH
|
|
1
|
+
{"version":3,"file":"fact_ddl.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/fact_ddl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAElE,mDAAmD;AACnD,eAAO,MAAM,YAAY,qSASvB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,6LAK3B,CAAC;AAEH,4DAA4D;AAC5D,eAAO,MAAM,sBAAsB,8EACqC,CAAC;AAEzE,oFAAoF;AACpF,eAAO,MAAM,YAAY,mNAOvB,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,qQAM7B,CAAC;AAEH,qDAAqD;AACrD,eAAO,MAAM,sBAAsB,2EACkC,CAAC;AAEtE,6EAA6E;AAC7E,eAAO,MAAM,gBAAgB,sDAAuD,CAAC;AAErF,sFAAsF;AACtF,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,SAAS,CAiB5C,CAAC;AAEF,uDAAuD;AACvD,eAAO,MAAM,wBAAwB,cAAc,CAAC;AAEpD,wDAAwD;AACxD,eAAO,MAAM,iBAAiB,EAAE,kBAG/B,CAAC"}
|
package/dist/db/fact_ddl.js
CHANGED
|
@@ -69,6 +69,7 @@ CREATE TABLE IF NOT EXISTS memo (
|
|
|
69
69
|
* Both FKs cascade: deleting an actor erases its possession, and deleting a
|
|
70
70
|
* fact (the orphan sweep) drops its holders. The primary key leads with
|
|
71
71
|
* `actor_id`, serving the admission lookup and the actor-side cascade.
|
|
72
|
+
* `created_at` is the holder's most recent put, not the first.
|
|
72
73
|
*/
|
|
73
74
|
export const FACT_HOLDER_SCHEMA = `
|
|
74
75
|
CREATE TABLE IF NOT EXISTS fact_holder (
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* ref only for a fact the acting actor holds.
|
|
10
10
|
*
|
|
11
11
|
* Same discipline as `db/fact_queries.ts`: `deps: QueryDeps` first (the caller
|
|
12
|
-
* owns the transaction), no audit side effects, idempotent
|
|
12
|
+
* owns the transaction), no audit side effects, idempotent writes.
|
|
13
13
|
*
|
|
14
14
|
* @module
|
|
15
15
|
*/
|
|
@@ -31,13 +31,17 @@ export interface FactHolderRow {
|
|
|
31
31
|
*/
|
|
32
32
|
export declare const FACT_HOLDER_COLUMNS: readonly ["actor_id", "hash", "created_at"];
|
|
33
33
|
/**
|
|
34
|
-
* Record `actor_id` as a holder of `hash
|
|
35
|
-
*
|
|
34
|
+
* Record `actor_id` as a holder of `hash`, or refresh the row's `created_at`
|
|
35
|
+
* when it already holds it. The `fact` row must already exist (FK).
|
|
36
|
+
*
|
|
37
|
+
* `created_at` is the holder's most recent put, which the orphan sweep reads
|
|
38
|
+
* as the fact's grace window: a re-put keeps an old orphan out of any
|
|
39
|
+
* sweep whose window covers the put.
|
|
36
40
|
*
|
|
37
41
|
* @param deps - query deps
|
|
38
42
|
* @param actor_id - the actor that brought the bytes
|
|
39
43
|
* @param hash - the fact's hash
|
|
40
|
-
* @mutates fact_holder - inserts the `(actor_id, hash)` row
|
|
44
|
+
* @mutates fact_holder - inserts the `(actor_id, hash)` row, or sets an existing row's `created_at` to now
|
|
41
45
|
*/
|
|
42
46
|
export declare const query_put_fact_holder: (deps: QueryDeps, actor_id: Uuid, hash: FactHash) => Promise<void>;
|
|
43
47
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fact_holder_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/fact_holder_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAEjE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAEpD,kCAAkC;AAClC,MAAM,WAAW,aAAa;IAC7B,QAAQ,EAAE,IAAI,CAAC;IACf,IAAI,EAAE,QAAQ,CAAC;IACf,UAAU,EAAE,IAAI,CAAC;CACjB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,6CAIuB,CAAC;AAExD
|
|
1
|
+
{"version":3,"file":"fact_holder_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/fact_holder_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAEjE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAEpD,kCAAkC;AAClC,MAAM,WAAW,aAAa;IAC7B,QAAQ,EAAE,IAAI,CAAC;IACf,IAAI,EAAE,QAAQ,CAAC;IACf,UAAU,EAAE,IAAI,CAAC;CACjB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,6CAIuB,CAAC;AAExD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,qBAAqB,GACjC,MAAM,SAAS,EACf,UAAU,IAAI,EACd,MAAM,QAAQ,KACZ,OAAO,CAAC,IAAI,CAMd,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,8BAA8B,GAAU,MAAM,SAAS,KAAG,OAAO,CAAC,OAAO,CAQrF,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,wBAAwB,GACpC,MAAM,SAAS,EACf,UAAU,IAAI,EACd,QAAQ,aAAa,CAAC,MAAM,CAAC,KAC3B,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAQvB,CAAC"}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* ref only for a fact the acting actor holds.
|
|
10
10
|
*
|
|
11
11
|
* Same discipline as `db/fact_queries.ts`: `deps: QueryDeps` first (the caller
|
|
12
|
-
* owns the transaction), no audit side effects, idempotent
|
|
12
|
+
* owns the transaction), no audit side effects, idempotent writes.
|
|
13
13
|
*
|
|
14
14
|
* @module
|
|
15
15
|
*/
|
|
@@ -26,17 +26,21 @@ export const FACT_HOLDER_COLUMNS = [
|
|
|
26
26
|
'created_at'
|
|
27
27
|
];
|
|
28
28
|
/**
|
|
29
|
-
* Record `actor_id` as a holder of `hash
|
|
30
|
-
*
|
|
29
|
+
* Record `actor_id` as a holder of `hash`, or refresh the row's `created_at`
|
|
30
|
+
* when it already holds it. The `fact` row must already exist (FK).
|
|
31
|
+
*
|
|
32
|
+
* `created_at` is the holder's most recent put, which the orphan sweep reads
|
|
33
|
+
* as the fact's grace window: a re-put keeps an old orphan out of any
|
|
34
|
+
* sweep whose window covers the put.
|
|
31
35
|
*
|
|
32
36
|
* @param deps - query deps
|
|
33
37
|
* @param actor_id - the actor that brought the bytes
|
|
34
38
|
* @param hash - the fact's hash
|
|
35
|
-
* @mutates fact_holder - inserts the `(actor_id, hash)` row
|
|
39
|
+
* @mutates fact_holder - inserts the `(actor_id, hash)` row, or sets an existing row's `created_at` to now
|
|
36
40
|
*/
|
|
37
41
|
export const query_put_fact_holder = async (deps, actor_id, hash) => {
|
|
38
42
|
await deps.db.query(`INSERT INTO fact_holder (actor_id, hash) VALUES ($1, $2)
|
|
39
|
-
ON CONFLICT (actor_id, hash) DO
|
|
43
|
+
ON CONFLICT (actor_id, hash) DO UPDATE SET created_at = NOW()`, [actor_id, hash]);
|
|
40
44
|
};
|
|
41
45
|
/**
|
|
42
46
|
* Whether the `fact_holder` table exists — `false` on a database that mounts
|
|
@@ -123,14 +123,15 @@ export interface OrphanFactsListResult {
|
|
|
123
123
|
* outputs? external fact mirrors?) the predicate moves to a generic
|
|
124
124
|
* `fact_consumers` registry; today the cell layer is the only consumer.
|
|
125
125
|
*
|
|
126
|
-
* The `older_than` filter
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
126
|
+
* The `older_than` filter is the grace cutoff: a fact passes it when both
|
|
127
|
+
* its `created_at` and every `fact_holder` row's are older (a re-put
|
|
128
|
+
* refreshes the holder row). Pass `null` to skip the filter (used by the
|
|
129
|
+
* list-summary preview); the delete handler always passes a non-null
|
|
130
|
+
* cutoff (default 0, meaning "any orphan").
|
|
130
131
|
*
|
|
131
132
|
* @param deps - query deps
|
|
132
|
-
* @param older_than - filter to facts created
|
|
133
|
-
* to skip)
|
|
133
|
+
* @param older_than - filter to facts created, and last put by any holder,
|
|
134
|
+
* before this Date (or null to skip)
|
|
134
135
|
* @param sample_limit - row cap for the returned `sample`
|
|
135
136
|
*/
|
|
136
137
|
export declare const query_orphan_facts_list: (deps: QueryDeps, older_than: Date | null, sample_limit: number) => Promise<OrphanFactsListResult>;
|
|
@@ -139,7 +140,9 @@ export declare const query_orphan_facts_list: (deps: QueryDeps, older_than: Date
|
|
|
139
140
|
* (no row-count limit) — callers iterate to unlink disk files. The
|
|
140
141
|
* `older_than` cutoff is required (non-null) here: bulk delete should
|
|
141
142
|
* always be operator-scoped to a time window. A "delete all" sweep
|
|
142
|
-
* passes a far-future cutoff, not `null`.
|
|
143
|
+
* passes a far-future cutoff, not `null`. It is the same grace cutoff as
|
|
144
|
+
* `query_orphan_facts_list`: a fact put — or re-put, deduplicated — by an
|
|
145
|
+
* actor within the window is kept, whatever its own `created_at`.
|
|
143
146
|
*/
|
|
144
147
|
export declare const query_orphan_facts_select_for_delete: (deps: QueryDeps, older_than: Date) => Promise<Array<{
|
|
145
148
|
hash: FactHash;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fact_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/fact_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAGjE,0CAA0C;AAC1C,MAAM,WAAW,OAAO;IACvB,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IACzB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAK5B,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,UAAU,EAAE,IAAI,CAAC;CACjB;AAED;;;;;GAKG;AACH,eAAO,MAAM,YAAY,kFAOwB,CAAC;AAYlD,qEAAqE;AACrE,MAAM,WAAW,WAAW;IAC3B,IAAI,EAAE,QAAQ,CAAC;IACf,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAE5B,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,UAAU,EAAE,IAAI,CAAC;CACjB;AAED,oFAAoF;AACpF,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IACzB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,GAAU,MAAM,SAAS,EAAE,OAAO,YAAY,KAAG,OAAO,CAAC,OAAO,CAS1F,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAC/B,MAAM,SAAS,EACf,aAAa,QAAQ,EACrB,eAAe,KAAK,CAAC,QAAQ,CAAC,KAC5B,OAAO,CAAC,IAAI,CAQd,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,cAAc,GAAU,MAAM,SAAS,EAAE,MAAM,QAAQ,KAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAO5F,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,mBAAmB,GAC/B,MAAM,SAAS,EACf,MAAM,QAAQ,KACZ,OAAO,CAAC,WAAW,GAAG,IAAI,CAO5B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,cAAc,GAAU,MAAM,SAAS,EAAE,MAAM,QAAQ,KAAG,OAAO,CAAC,OAAO,CAMrF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAC/B,MAAM,SAAS,EACf,aAAa,QAAQ,KACnB,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,CAMzB,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iBAAiB,GAC7B,MAAM,SAAS,EACf,MAAM,QAAQ,KACZ,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,CAQ9D,CAAC;
|
|
1
|
+
{"version":3,"file":"fact_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/fact_queries.ts"],"names":[],"mappings":"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"}
|