@fuzdev/fuz_app 0.106.1 → 0.107.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/actions/CLAUDE.md +42 -23
  2. package/dist/actions/action_bridge.d.ts +34 -3
  3. package/dist/actions/action_bridge.d.ts.map +1 -1
  4. package/dist/actions/action_bridge.js +32 -1
  5. package/dist/actions/action_rpc.d.ts +5 -4
  6. package/dist/actions/action_rpc.d.ts.map +1 -1
  7. package/dist/actions/action_rpc.js +4 -3
  8. package/dist/actions/action_spec.d.ts +8 -2
  9. package/dist/actions/action_spec.d.ts.map +1 -1
  10. package/dist/actions/action_spec.js +5 -2
  11. package/dist/actions/compile_action_registry.d.ts +26 -14
  12. package/dist/actions/compile_action_registry.d.ts.map +1 -1
  13. package/dist/actions/compile_action_registry.js +29 -14
  14. package/dist/actions/perform_action.d.ts +26 -16
  15. package/dist/actions/perform_action.d.ts.map +1 -1
  16. package/dist/actions/perform_action.js +69 -44
  17. package/dist/actions/register_action_ws.d.ts +4 -4
  18. package/dist/actions/register_action_ws.js +5 -5
  19. package/dist/actions/register_ws_endpoint.d.ts +13 -5
  20. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  21. package/dist/actions/register_ws_endpoint.js +15 -6
  22. package/dist/auth/CLAUDE.md +16 -9
  23. package/dist/auth/audit_log_route_schema.d.ts.map +1 -1
  24. package/dist/auth/audit_log_route_schema.js +11 -1
  25. package/dist/auth/audit_log_routes.d.ts.map +1 -1
  26. package/dist/auth/audit_log_routes.js +4 -9
  27. package/dist/auth/auth_guard_resolver.d.ts +48 -21
  28. package/dist/auth/auth_guard_resolver.d.ts.map +1 -1
  29. package/dist/auth/auth_guard_resolver.js +60 -26
  30. package/dist/auth/bearer_auth.d.ts +1 -1
  31. package/dist/auth/bearer_auth.js +1 -1
  32. package/dist/auth/middleware.js +1 -1
  33. package/dist/auth/request_context.d.ts +54 -24
  34. package/dist/auth/request_context.d.ts.map +1 -1
  35. package/dist/auth/request_context.js +117 -39
  36. package/dist/auth/token_scope.d.ts +109 -21
  37. package/dist/auth/token_scope.d.ts.map +1 -1
  38. package/dist/auth/token_scope.js +106 -21
  39. package/dist/http/CLAUDE.md +23 -11
  40. package/dist/http/auth_shape.d.ts +58 -3
  41. package/dist/http/auth_shape.d.ts.map +1 -1
  42. package/dist/http/auth_shape.js +63 -5
  43. package/dist/http/error_schemas.d.ts +24 -1
  44. package/dist/http/error_schemas.d.ts.map +1 -1
  45. package/dist/http/error_schemas.js +36 -14
  46. package/dist/http/route_spec.d.ts +29 -26
  47. package/dist/http/route_spec.d.ts.map +1 -1
  48. package/dist/http/route_spec.js +12 -16
  49. package/dist/http/surface_query.d.ts +2 -2
  50. package/dist/http/surface_query.js +2 -2
  51. package/dist/server/serve_fact_route.d.ts.map +1 -1
  52. package/dist/server/serve_fact_route.js +12 -10
  53. package/dist/testing/CLAUDE.md +39 -3
  54. package/dist/testing/cross_backend/conformance_case.js +1 -1
  55. package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
  56. package/dist/testing/cross_backend/testing_backdoor.js +6 -5
  57. package/dist/testing/cross_backend/token_scope_surface.d.ts +9 -0
  58. package/dist/testing/cross_backend/token_scope_surface.d.ts.map +1 -0
  59. package/dist/testing/cross_backend/token_scope_surface.js +170 -0
  60. package/dist/testing/integration.d.ts.map +1 -1
  61. package/dist/testing/integration.js +3 -2
  62. package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
  63. package/dist/testing/rpc_attack_surface.js +4 -2
  64. package/package.json +1 -1
@@ -189,6 +189,25 @@ export declare const CredentialTypeRequiredError: z.ZodObject<{
189
189
  required_credential_types: z.ZodReadonly<z.ZodArray<z.ZodString>>;
190
190
  }, z.core.$loose>;
191
191
  export type CredentialTypeRequiredError = z.infer<typeof CredentialTypeRequiredError>;
192
+ /**
193
+ * Token-scope error — returned by the `require_token_scope` guard (and its
194
+ * direct-call twin `token_scope_surface_denial`) when a **narrowed** api token
195
+ * reaches a capability its scope does not admit. See
196
+ * `docs/security.md` §Token scoping.
197
+ *
198
+ * `required_scope` carries the capability string the route demands, verbatim
199
+ * from its `auth.required_scope` — `surface:audit_stream` for a non-RPC
200
+ * surface, `rpc:<method>` for a bridged action — matching the Rust
201
+ * `token_scope_surface_denied_response` and the dispatcher's own per-method
202
+ * denial. Third in the family with `PermissionError` and
203
+ * `CredentialTypeRequiredError` — each names what the route demanded rather
204
+ * than what the caller holds.
205
+ */
206
+ export declare const TokenScopeRequiredError: z.ZodObject<{
207
+ error: z.ZodLiteral<"token_scope_required">;
208
+ required_scope: z.ZodString;
209
+ }, z.core.$loose>;
210
+ export type TokenScopeRequiredError = z.infer<typeof TokenScopeRequiredError>;
192
211
  /** Rate limit error — returned when a rate limiter rejects the request. */
193
212
  export declare const RateLimitError: z.ZodObject<{
194
213
  error: z.ZodLiteral<"rate_limit_exceeded">;
@@ -275,12 +294,16 @@ export type RateLimitKey = z.infer<typeof RateLimitKey>;
275
294
  * Derivation rules under the new flat-record auth shape:
276
295
  * - **Has input / params / query schema**: 400 (`ValidationError`).
277
296
  * - **`auth.account === 'required'`** or **`auth.actor === 'required'`**: 401
278
- * (`ApiError`) — pre-validation 401 fires when the credential isn't there.
297
+ * (`ApiError`) — pre-authorization 401 fires when the credential isn't there.
279
298
  * `'optional'` does not derive 401.
280
299
  * - **`auth.roles?.length`**: 403 (`PermissionError` carrying `required_roles`).
281
300
  * - **`auth.credential_types?.length`**: 403 (`CredentialTypeRequiredError`
282
301
  * carrying `required_credential_types` — symmetric with `PermissionError`).
283
302
  * Today the only credential gate is keeper; future gates reuse the literal.
303
+ * - **`auth.required_scope`**: 403 (`TokenScopeRequiredError` carrying
304
+ * `required_scope`) — the refusal `require_token_scope` emits.
305
+ * A route declaring more than one of these three gates derives the union
306
+ * of their shapes, since any of them can be the one that answers.
284
307
  * - **`auth.actor !== 'none'`** (`'optional'` or `'required'`): extends 400
285
308
  * with `ActorRequiredError` / `ActorNotOnAccountError` and adds 500 union
286
309
  * of `NoActorsOnAccountError` / `AccountVanishedError`. The dispatcher's
@@ -1 +1 @@
1
- {"version":3,"file":"error_schemas.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/error_schemas.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"error_schemas.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/error_schemas.ts"],"names":[],"mappings":"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"}
@@ -195,6 +195,24 @@ export const CredentialTypeRequiredError = z.looseObject({
195
195
  error: z.literal(ERROR_CREDENTIAL_TYPE_REQUIRED),
196
196
  required_credential_types: z.array(z.string()).readonly()
197
197
  });
198
+ /**
199
+ * Token-scope error — returned by the `require_token_scope` guard (and its
200
+ * direct-call twin `token_scope_surface_denial`) when a **narrowed** api token
201
+ * reaches a capability its scope does not admit. See
202
+ * `docs/security.md` §Token scoping.
203
+ *
204
+ * `required_scope` carries the capability string the route demands, verbatim
205
+ * from its `auth.required_scope` — `surface:audit_stream` for a non-RPC
206
+ * surface, `rpc:<method>` for a bridged action — matching the Rust
207
+ * `token_scope_surface_denied_response` and the dispatcher's own per-method
208
+ * denial. Third in the family with `PermissionError` and
209
+ * `CredentialTypeRequiredError` — each names what the route demanded rather
210
+ * than what the caller holds.
211
+ */
212
+ export const TokenScopeRequiredError = z.looseObject({
213
+ error: z.literal(ERROR_TOKEN_SCOPE_REQUIRED),
214
+ required_scope: z.string()
215
+ });
198
216
  /** Rate limit error — returned when a rate limiter rejects the request. */
199
217
  export const RateLimitError = z.looseObject({
200
218
  error: z.literal(ERROR_RATE_LIMIT_EXCEEDED),
@@ -262,27 +280,31 @@ export const derive_error_schemas = ({ auth, has_input = false, has_params = fal
262
280
  else if (has_validation) {
263
281
  errors[400] = ValidationError;
264
282
  }
265
- // 401 fires when the dispatcher's pre-validation gate rejects an
283
+ // 401 fires when the dispatcher's pre-authorization gate rejects an
266
284
  // unauthenticated caller — `account === 'required'` (no credential) or
267
285
  // `actor === 'required'` (no credential to resolve an actor against,
268
286
  // per registry-time invariant 3 forbidding accountless actors in v1).
269
287
  if (auth.account === 'required' || auth.actor === 'required') {
270
288
  errors[401] = ApiError;
271
289
  }
272
- // 403 fires when `auth.roles` or `auth.credential_types` rejects a
273
- // resolved request context. With both axes set, the 403 body could be
274
- // either shape — emit the union so DEV-mode error-schema validation
275
- // accepts whichever the dispatcher produced.
276
- const has_role_gate = !!auth.roles?.length;
277
- const has_credential_gate = !!auth.credential_types?.length;
278
- if (has_role_gate && has_credential_gate) {
279
- errors[403] = z.union([PermissionError, CredentialTypeRequiredError]);
280
- }
281
- else if (has_role_gate) {
282
- errors[403] = PermissionError;
290
+ // 403 fires from any of three gates: the token-scope refusal
291
+ // (`auth.required_scope`, pre-authorization), the credential-type gate, and
292
+ // the role gate (both post-authorization). A route can declare any
293
+ // combination, so collect the shapes its gates can emit — in the order they
294
+ // fire — and union them when there's more than one, so DEV-mode
295
+ // error-schema validation accepts whichever the pipeline produced.
296
+ const forbidden_shapes = [];
297
+ if (auth.required_scope !== undefined)
298
+ forbidden_shapes.push(TokenScopeRequiredError);
299
+ if (auth.credential_types?.length)
300
+ forbidden_shapes.push(CredentialTypeRequiredError);
301
+ if (auth.roles?.length)
302
+ forbidden_shapes.push(PermissionError);
303
+ if (forbidden_shapes.length === 1) {
304
+ errors[403] = forbidden_shapes[0];
283
305
  }
284
- else if (has_credential_gate) {
285
- errors[403] = CredentialTypeRequiredError;
306
+ else if (forbidden_shapes.length > 1) {
307
+ errors[403] = z.union(forbidden_shapes);
286
308
  }
287
309
  if (rate_limit) {
288
310
  errors[429] = RateLimitError;
@@ -22,14 +22,20 @@ import { type RouteAuth } from './auth_shape.js';
22
22
  /**
23
23
  * Two-phase auth guard set returned by `AuthGuardResolver`.
24
24
  *
25
- * `pre_validation` runs before input validation — 401 checks live here
26
- * so unauthenticated callers never see route-shape information from
27
- * input parsing failures. `post_authorization` runs after the
28
- * authorization phase has populated `RequestContext` — role / keeper
29
- * checks live here because they read `c.var.request_context.role_grants`.
25
+ * `pre_authorization` runs before the authorization phase — the 401 check
26
+ * and the rule-3 token-scope refusal live here, so a caller either one turns
27
+ * away never reaches the actor resolution or the route's body.
28
+ * `post_authorization` runs after the authorization phase has populated
29
+ * `RequestContext` — credential / role checks live here because they read
30
+ * `c.var.request_context.role_grants`.
31
+ *
32
+ * Both phases run ahead of body validation, which is why neither is named
33
+ * for it: route-shape information stays behind every authority gate. The
34
+ * names say which side of the authorization phase a guard sits on, the only
35
+ * axis that still distinguishes them.
30
36
  */
31
37
  export interface AuthGuards {
32
- pre_validation: Array<MiddlewareHandler>;
38
+ pre_authorization: Array<MiddlewareHandler>;
33
39
  post_authorization: Array<MiddlewareHandler>;
34
40
  }
35
41
  /**
@@ -41,12 +47,13 @@ export interface AuthGuards {
41
47
  */
42
48
  export type AuthGuardResolver = (auth: RouteAuth) => AuthGuards;
43
49
  /**
44
- * Per-route authorization phase. Runs after pre-validation auth guards
45
- * AND input validation; resolves the acting actor (when `auth.actor !== 'none'`)
46
- * by reading `c.var.validated_input.acting` and sets the request context on
47
- * the Hono context. Per-route order in `apply_route_specs`: params → query
48
- * → pre-validation auth (401) → input validation (400) → authorization
49
- * phase → post-authorization auth (403) → handler.
50
+ * Per-route authorization phase. Runs after the pre-authorization auth guards
51
+ * and before input validation; resolves the acting actor (when `auth.actor !==
52
+ * 'none'`) from the `acting` selector — `c.var.validated_query.acting` on GETs,
53
+ * read off the raw body on mutations — and sets the request context on the Hono
54
+ * context. Per-route order in `apply_route_specs`: params → query →
55
+ * pre-authorization auth (401 + rule-3 scope) → authorization phase →
56
+ * post-authorization auth (403) → input validation (400) → handler.
50
57
  *
51
58
  * Returns a `Response` to short-circuit (resolution failure → 400 / 500),
52
59
  * or `void` to continue. The http framework stays auth-agnostic — fuz_app
@@ -221,19 +228,15 @@ export declare const apply_middleware_specs: (app: Hono, specs: Array<Middleware
221
228
  * validation, wraps with error catch layer (catches `ThrownJsonrpcError`
222
229
  * and generic errors), and registers the route.
223
230
  *
224
- * Per-route middleware order: params → query → pre-validation auth
225
- * guards (401) → input validation (400) → authorization phase →
226
- * post-authorization auth guards (403) → handler. The 401 check runs
227
- * before any body parsing so unauthenticated callers never see
228
- * route-shape information from parse failures. Input validation runs
229
- * before the authorization phase (validate first, authorize after) so
230
- * the authorization phase reads `c.var.validated_input.acting` as a
231
- * typed Zod field instead of pre-parsing the raw body. Role /
232
- * credential-type denials still surface 403 last; trade-off is that
233
- * authenticated-but-unauthorized callers can distinguish 400
234
- * (validation) from 403 (authorization), a defense-in-depth concession
235
- * deemed acceptable because the route surface is public via
236
- * spec/codegen JSON.
231
+ * Per-route middleware order: params → query → pre-authorization auth
232
+ * guards (401 + rule-3 scope) → authorization phase →
233
+ * post-authorization auth guards (403) → input validation (400) →
234
+ * handler. Body validation runs behind every authority gate, so a caller
235
+ * any of them refuse never sees route-shape information from a parse
236
+ * failure — a 400 there would confirm the route exists and describe how
237
+ * to call it. Params and query still validate first (they address the
238
+ * route rather than carry its payload, and the authorization phase reads
239
+ * `acting` off the query on GETs).
237
240
  *
238
241
  * Each handler receives a `RouteContext` with:
239
242
  * - `db`: transaction-scoped when `RouteSpec.transaction` is true; pool-level otherwise
@@ -248,7 +251,7 @@ export declare const apply_middleware_specs: (app: Hono, specs: Array<Middleware
248
251
  * `input` only — `ActionSpec` has no `query` shape.
249
252
  *
250
253
  * @param resolve_auth_guards - maps `RouteAuth` to middleware — use `fuz_auth_guard_resolver` from `auth/auth_guard_resolver.ts`
251
- * @param authorize - optional authorization phase; runs after input validation
254
+ * @param authorize - optional authorization phase; runs after the pre-authorization guards and before the post-authorization guards
252
255
  * @param db - used for transaction wrapping and `RouteContext`
253
256
  * @mutates `app`
254
257
  * @throws Error if two specs share the same `method` + `path` (each combination must be unique), or if any spec violates the actor-acting biconditional
@@ -1 +1 @@
1
- {"version":3,"file":"route_spec.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/route_spec.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"route_spec.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/route_spec.ts"],"names":[],"mappings":"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"}
@@ -266,19 +266,15 @@ const build_rest_error_body = (err) => {
266
266
  * validation, wraps with error catch layer (catches `ThrownJsonrpcError`
267
267
  * and generic errors), and registers the route.
268
268
  *
269
- * Per-route middleware order: params → query → pre-validation auth
270
- * guards (401) → input validation (400) → authorization phase →
271
- * post-authorization auth guards (403) → handler. The 401 check runs
272
- * before any body parsing so unauthenticated callers never see
273
- * route-shape information from parse failures. Input validation runs
274
- * before the authorization phase (validate first, authorize after) so
275
- * the authorization phase reads `c.var.validated_input.acting` as a
276
- * typed Zod field instead of pre-parsing the raw body. Role /
277
- * credential-type denials still surface 403 last; trade-off is that
278
- * authenticated-but-unauthorized callers can distinguish 400
279
- * (validation) from 403 (authorization), a defense-in-depth concession
280
- * deemed acceptable because the route surface is public via
281
- * spec/codegen JSON.
269
+ * Per-route middleware order: params → query → pre-authorization auth
270
+ * guards (401 + rule-3 scope) → authorization phase →
271
+ * post-authorization auth guards (403) → input validation (400) →
272
+ * handler. Body validation runs behind every authority gate, so a caller
273
+ * any of them refuse never sees route-shape information from a parse
274
+ * failure — a 400 there would confirm the route exists and describe how
275
+ * to call it. Params and query still validate first (they address the
276
+ * route rather than carry its payload, and the authorization phase reads
277
+ * `acting` off the query on GETs).
282
278
  *
283
279
  * Each handler receives a `RouteContext` with:
284
280
  * - `db`: transaction-scoped when `RouteSpec.transaction` is true; pool-level otherwise
@@ -293,7 +289,7 @@ const build_rest_error_body = (err) => {
293
289
  * `input` only — `ActionSpec` has no `query` shape.
294
290
  *
295
291
  * @param resolve_auth_guards - maps `RouteAuth` to middleware — use `fuz_auth_guard_resolver` from `auth/auth_guard_resolver.ts`
296
- * @param authorize - optional authorization phase; runs after input validation
292
+ * @param authorize - optional authorization phase; runs after the pre-authorization guards and before the post-authorization guards
297
293
  * @param db - used for transaction wrapping and `RouteContext`
298
294
  * @mutates `app`
299
295
  * @throws Error if two specs share the same `method` + `path` (each combination must be unique), or if any spec violates the actor-acting biconditional
@@ -307,7 +303,7 @@ export const apply_route_specs = (app, specs, resolve_auth_guards, log, db, auth
307
303
  }
308
304
  registered.add(route_key);
309
305
  assert_route_auth_acting_biconditional(spec.auth, { input: spec.input, query: spec.query }, `Route "${route_key}"`);
310
- const { pre_validation: pre_validation_guards, post_authorization: post_authorization_guards } = resolve_auth_guards(spec.auth);
306
+ const { pre_authorization: pre_authorization_guards, post_authorization: post_authorization_guards } = resolve_auth_guards(spec.auth);
311
307
  const params_validation = create_params_validation(spec.params);
312
308
  const query_validation = create_query_validation(spec.query);
313
309
  const input_validation = create_input_validation(spec.input, spec.method);
@@ -344,7 +340,7 @@ export const apply_route_specs = (app, specs, resolve_auth_guards, log, db, auth
344
340
  handler = wrap_output_validation(handler, spec.output, merged_errors, log);
345
341
  // Step 3: error catch layer
346
342
  handler = wrap_error_catch(handler, log);
347
- app.on(spec.method, [spec.path], ...params_validation, ...query_validation, ...pre_validation_guards, ...input_validation, ...authorization, ...post_authorization_guards, handler);
343
+ app.on(spec.method, [spec.path], ...params_validation, ...query_validation, ...pre_authorization_guards, ...authorization, ...post_authorization_guards, ...input_validation, handler);
348
344
  }
349
345
  };
350
346
  /**
@@ -8,7 +8,7 @@
8
8
  * `filter_keeper_routes`) group the new flat-record `RouteAuth` shape into
9
9
  * the legacy categorical buckets (`'authenticated'`, `'role'`, `'keeper'`)
10
10
  * for adversarial test runners and the surface explorer. The buckets are
11
- * derived views over the four axes (`account`, `actor`, `roles`,
11
+ * derived views over the authority axes (`account`, `actor`, `roles`,
12
12
  * `credential_types`) — see `http/auth_shape.ts` for the canonical shape.
13
13
  *
14
14
  * @module
@@ -65,7 +65,7 @@ export declare const format_route_key: (route: AppSurfaceRoute) => string;
65
65
  /**
66
66
  * Summarize route auth distribution across the surface.
67
67
  *
68
- * Categorical view over the four-axis flat record. Multi-role specs
68
+ * Categorical view over the flat auth record. Multi-role specs
69
69
  * contribute one count per role they admit.
70
70
  *
71
71
  * @returns counts by auth category, with role counts broken out by role name
@@ -8,7 +8,7 @@
8
8
  * `filter_keeper_routes`) group the new flat-record `RouteAuth` shape into
9
9
  * the legacy categorical buckets (`'authenticated'`, `'role'`, `'keeper'`)
10
10
  * for adversarial test runners and the surface explorer. The buckets are
11
- * derived views over the four axes (`account`, `actor`, `roles`,
11
+ * derived views over the authority axes (`account`, `actor`, `roles`,
12
12
  * `credential_types`) — see `http/auth_shape.ts` for the canonical shape.
13
13
  *
14
14
  * @module
@@ -87,7 +87,7 @@ export const format_route_key = (route) => `${route.method} ${route.path}`;
87
87
  /**
88
88
  * Summarize route auth distribution across the surface.
89
89
  *
90
- * Categorical view over the four-axis flat record. Multi-role specs
90
+ * Categorical view over the flat auth record. Multi-role specs
91
91
  * contribute one count per role they admit.
92
92
  *
93
93
  * @returns counts by auth category, with role counts broken out by role name
@@ -1 +1 @@
1
- {"version":3,"file":"serve_fact_route.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/serve_fact_route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwFG;AAMH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAgBtD,OAAO,EAAuC,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAE5F,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAM/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AA6DjD,MAAM,WAAW,+BAA+B;IAC/C;;;;;;OAMG;IACH,IAAI,EAAE,OAAO,CAAC;IACd,kFAAkF;IAClF,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC;CACZ;AAgHD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iCAAiC,GAC7C,SAAS,+BAA+B,KACtC,SAgDF,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,4BAA4B,GACxC,SAAS,+BAA+B,KACtC,SAwCF,CAAC"}
1
+ {"version":3,"file":"serve_fact_route.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/serve_fact_route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwFG;AAMH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAetD,OAAO,EAAuC,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAE5F,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAM/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AA6DjD,MAAM,WAAW,+BAA+B;IAC/C;;;;;;OAMG;IACH,IAAI,EAAE,OAAO,CAAC;IACd,kFAAkF;IAClF,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC;CACZ;AAgHD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iCAAiC,GAC7C,SAAS,+BAA+B,KACtC,SAgDF,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,4BAA4B,GACxC,SAAS,+BAA+B,KACtC,SA0CF,CAAC"}
@@ -93,7 +93,7 @@ import { join } from 'node:path';
93
93
  import { FactHashSchema } from '@fuzdev/fuz_util/hash_schemas.ts';
94
94
  import { Uuid } from '@fuzdev/fuz_util/id.ts';
95
95
  import { z } from 'zod';
96
- import { build_request_context, get_request_context, has_role, token_scope_surface_denial } from '../auth/request_context.js';
96
+ import { build_request_context, get_request_context, has_role } from '../auth/request_context.js';
97
97
  import { ROLE_ADMIN } from '../auth/role_schema.js';
98
98
  import { ActingActor } from '../http/auth_shape.js';
99
99
  import { ACCOUNT_ID_KEY } from '../hono_context.js';
@@ -320,7 +320,17 @@ export const create_serve_fact_route_spec = (options) => {
320
320
  return {
321
321
  method: 'GET',
322
322
  path: '/api/facts/:hash',
323
- auth: { account: 'required', actor: 'required', roles: [ROLE_ADMIN] },
323
+ auth: {
324
+ account: 'required',
325
+ actor: 'required',
326
+ roles: [ROLE_ADMIN],
327
+ // Rule 3 — a narrowed token gets no bare-hash read. This route
328
+ // serves *any* stored fact by hash with no per-reference cell check
329
+ // (that is the per-cell route's job), so it is the broad capability
330
+ // of the pair; gating it keeps a narrowed token to the cell-scoped
331
+ // route its RPC methods can justify.
332
+ required_scope: 'surface:fact_bare'
333
+ },
324
334
  description: 'Serve content-addressed fact bytes by bare hash — admin only. Non-admin reads go through GET /api/cells/:cell_id/facts/:hash (per-reference).',
325
335
  params: bare_hash_params_schema,
326
336
  // `actor: 'required'` (implied by the admin role gate) needs the
@@ -334,14 +344,6 @@ export const create_serve_fact_route_spec = (options) => {
334
344
  errors: params_400_error,
335
345
  handler: async (c, route) => {
336
346
  const { hash } = get_route_params(c);
337
- // Rule 3 — a narrowed token gets no bare-hash read. This route serves
338
- // *any* stored fact by hash with no per-reference cell check (that is
339
- // the per-cell route's job), so it is the broad capability of the
340
- // pair; gating it keeps a narrowed token to the cell-scoped route its
341
- // RPC methods can justify.
342
- const denied = token_scope_surface_denial(c, 'fact_bare');
343
- if (denied)
344
- return denied;
345
347
  // Defense-in-depth: the auth phase already gated this on the admin
346
348
  // role, but re-check the resolved context so a mounting/auth-shape
347
349
  // regression fails closed instead of serving by bare hash.
@@ -1346,6 +1346,42 @@ are `src/test/auth/cell_crud_parity.db.test.ts`
1346
1346
  complete action list plus its own spec. Daemon-token-gated like its siblings;
1347
1347
  the Rust mirror is `fuz_testing::create_testing_action_manifest_action_spec`.
1348
1348
 
1349
+ ### Token-scope surface parity — `cross_backend/token_scope_surface.ts`
1350
+
1351
+ `describe_token_scope_surface_cross_tests({setup_test, capabilities, rpc_path?,
1352
+ sse_path?})` — the `surface:<name>` twin of the conformance slate's
1353
+ `token_scope_cases`, which cover only the `rpc:<method>` arm. Token scoping's
1354
+ RPC-only rule (_a narrowed api token reaches no non-RPC surface_) is the
1355
+ load-bearing half of the design and had **no** cross-impl gate: the schema gate
1356
+ can't see it (no column), the action-manifest gate can't see it (no method), and
1357
+ the spec-derived suites can't see it (REST routes, off the declared RPC surface).
1358
+
1359
+ Mints a narrowed bearer through the production `account_token_create` path and
1360
+ fires it at the two surfaces whose denial **body** is observable over plain
1361
+ HTTP — the audit SSE stream (`capabilities.sse`) and the admin-only bare-hash
1362
+ fact read (`capabilities.fact_serving`) — asserting the exact flat
1363
+ `{error: 'token_scope_required', required_scope: 'surface:<name>'}` both spines
1364
+ contract on (`token_scope_denied_body` ↔
1365
+ `fuz_auth::token_scope_surface_denied_response`).
1366
+
1367
+ The fifth case is the one with no prior pin anywhere: **scope outranks role**. A
1368
+ narrowed token on an account that _also_ lacks the gating role must hear
1369
+ `token_scope_required`, not `insufficient_permissions` — the observable the
1370
+ TS↔Rust ordering divergence produced before both spines converged on running the
1371
+ surface gate ahead of the role gate. Every other scoped-token probe uses the
1372
+ keeper's token, whose account holds the role, so both orderings look identical
1373
+ there; that is why the divergence survived as long as it did. Verified to
1374
+ discriminate: dropping the route's `required_scope` turns it into a 403
1375
+ `insufficient_permissions` + `required_roles`, exactly the pre-convergence shape.
1376
+
1377
+ Two non-vacuity controls: the narrowed token must still reach the RPC method it
1378
+ _does_ name, and a full bearer must get past the scope gate on the same route.
1379
+ The WS upgrade is deliberately omitted — `create_ws_transport` threads cookies,
1380
+ not bearer headers, and a refused upgrade's body isn't readable, so it can't
1381
+ serve a body-parity gate (its per-spine coverage stays in the two surface
1382
+ censuses). Cross-process only; fuz_app's own wiring is
1383
+ `src/test/cross_backend/token_scope_surface.cross.test.ts`.
1384
+
1349
1385
  ### Origin verification parity — `cross_backend/origin.ts`
1350
1386
 
1351
1387
  `describe_origin_cross_tests({setup_test, capabilities, rpc_path?})` — the
@@ -1396,9 +1432,9 @@ read) it fires three principals over real HTTP: **anonymous** → 401, **session
1396
1432
  proving the daemon-token gate that fences each backdoor action holds end-to-end
1397
1433
  on the real dispatcher (the spec-derived `describe_rpc_attack_surface_tests`
1398
1434
  never enumerates them because they're off the declared surface). Each method is
1399
- sent with **valid** params so the session/bearer cases clear the 400
1400
- input-validation phase and reach the 403 credential gate (order is
1401
- 401 → 400 → 403); the handler never runs. Cited property: `docs/security.md`
1435
+ sent with **valid** params so each case varies only the credential (validation
1436
+ runs after the gates — order is 401 → authz → 403 → 400); the handler never
1437
+ runs. Cited property: `docs/security.md`
1402
1438
  §Test Backdoor Actions. Cross-process only (the `_testing_*` actions are
1403
1439
  mounted on the spawned binary, not the in-process app — like the ws/sse
1404
1440
  suites); ungated, since every cross backend that uses
@@ -141,7 +141,7 @@ export const ConformanceCaseExpectation = z.strictObject({
141
141
  description: 'Expected error reason — pass the IMPORTED `ERROR_*` constant from ' +
142
142
  '`http/error_schemas.ts`, never a string literal. Asserted against the RPC ' +
143
143
  '`error.data.reason` (when the denial carries one) or the REST flat-body ' +
144
- '`error` field. The pre-validation 401 carries `data.reason` too; a denial ' +
144
+ '`error` field. The pre-authorization 401 carries `data.reason` too; a denial ' +
145
145
  'that genuinely omits it falls back to the `status` assertion to pin the class.'
146
146
  }),
147
147
  fields: z
@@ -1 +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;AA2D9B,OAAO,KAAK,EAAE,wBAAwB,EAAe,MAAM,YAAY,CAAC;AAqExE,kEAAkE;AAClE,MAAM,MAAM,+BAA+B,GAAG,wBAAwB,CAAC;AAEvE,eAAO,MAAM,qCAAqC,GACjD,SAAS,+BAA+B,KACtC,IAiCF,CAAC"}
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;AA4D9B,OAAO,KAAK,EAAE,wBAAwB,EAAe,MAAM,YAAY,CAAC;AAqExE,kEAAkE;AAClE,MAAM,MAAM,+BAA+B,GAAG,wBAAwB,CAAC;AAEvE,eAAO,MAAM,qCAAqC,GACjD,SAAS,+BAA+B,KACtC,IAiCF,CAAC"}
@@ -19,7 +19,7 @@ import '../assert_dev_env.js';
19
19
  *
20
20
  * For every backdoor method, three principals:
21
21
  *
22
- * - **anonymous** (no credential) → `401` (pre-validation auth refuses an
22
+ * - **anonymous** (no credential) → `401` (pre-authorization auth refuses an
23
23
  * account-less caller before anything else).
24
24
  * - **session** (the keeper's browser-context cookie) → `403`
25
25
  * `credential_type_required` — a session cookie, even one carrying the
@@ -28,10 +28,11 @@ import '../assert_dev_env.js';
28
28
  * `credential_type_required` — same ceiling; an api token cannot reach
29
29
  * keeper operations.
30
30
  *
31
- * Each method is sent with **valid** params so the session/bearer cases
32
- * clear the dispatcher's input-validation (400) phase and actually reach the
33
- * post-authorization credential gate (the order is 401 → 400 → 403); the
34
- * handler never runs (the gate refuses first), so the writes never execute.
31
+ * Each method is sent with **valid** params so each case varies only the
32
+ * credential. (Validation runs after the gates — the order is 401 → authz →
33
+ * 403 → 400 — so the shapes are belt-and-suspenders here rather than load-
34
+ * bearing.) The handler never runs (the gate refuses first), so the writes
35
+ * never execute.
35
36
  *
36
37
  * Complements the spec-level gate check (which pins that each spec *declares*
37
38
  * `credential_types: ['daemon_token']`) and the surface-absence invariant
@@ -0,0 +1,9 @@
1
+ import '../assert_dev_env.js';
2
+ import type { RpcPathCrossSuiteOptions } from './setup.js';
3
+ /** Options for the token-scope surface parity suite. */
4
+ export interface TokenScopeSurfaceCrossTestOptions extends RpcPathCrossSuiteOptions {
5
+ /** Audit-log SSE path. Default `/api/admin/audit/stream`. */
6
+ readonly sse_path?: string;
7
+ }
8
+ export declare const describe_token_scope_surface_cross_tests: (options: TokenScopeSurfaceCrossTestOptions) => void;
9
+ //# sourceMappingURL=token_scope_surface.d.ts.map
@@ -0,0 +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;AAiE9B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAkB3D,wDAAwD;AACxD,MAAM,WAAW,iCAAkC,SAAQ,wBAAwB;IAClF,6DAA6D;IAC7D,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC3B;AA2DD,eAAO,MAAM,wCAAwC,GACpD,SAAS,iCAAiC,KACxC,IAwHF,CAAC"}