@fuzdev/fuz_app 0.54.0 → 0.55.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 (142) hide show
  1. package/dist/actions/CLAUDE.md +68 -13
  2. package/dist/actions/action_codegen.d.ts +13 -0
  3. package/dist/actions/action_codegen.d.ts.map +1 -1
  4. package/dist/actions/action_codegen.js +15 -1
  5. package/dist/actions/action_rpc.d.ts +60 -7
  6. package/dist/actions/action_rpc.d.ts.map +1 -1
  7. package/dist/actions/action_rpc.js +158 -44
  8. package/dist/actions/register_action_ws.d.ts +4 -4
  9. package/dist/actions/register_action_ws.js +6 -6
  10. package/dist/actions/register_ws_endpoint.d.ts +20 -7
  11. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  12. package/dist/actions/register_ws_endpoint.js +30 -5
  13. package/dist/actions/transports.d.ts.map +1 -1
  14. package/dist/actions/transports.js +0 -4
  15. package/dist/auth/CLAUDE.md +219 -66
  16. package/dist/auth/account_actions.d.ts +6 -6
  17. package/dist/auth/account_actions.d.ts.map +1 -1
  18. package/dist/auth/account_actions.js +8 -11
  19. package/dist/auth/account_queries.d.ts +6 -3
  20. package/dist/auth/account_queries.d.ts.map +1 -1
  21. package/dist/auth/account_queries.js +14 -5
  22. package/dist/auth/account_routes.d.ts +7 -10
  23. package/dist/auth/account_routes.d.ts.map +1 -1
  24. package/dist/auth/account_routes.js +70 -23
  25. package/dist/auth/account_schema.d.ts +19 -0
  26. package/dist/auth/account_schema.d.ts.map +1 -1
  27. package/dist/auth/account_schema.js +20 -0
  28. package/dist/auth/admin_action_specs.d.ts +45 -11
  29. package/dist/auth/admin_action_specs.d.ts.map +1 -1
  30. package/dist/auth/admin_action_specs.js +23 -8
  31. package/dist/auth/admin_actions.d.ts +8 -7
  32. package/dist/auth/admin_actions.d.ts.map +1 -1
  33. package/dist/auth/admin_actions.js +11 -18
  34. package/dist/auth/audit_log_queries.d.ts +53 -14
  35. package/dist/auth/audit_log_queries.d.ts.map +1 -1
  36. package/dist/auth/audit_log_queries.js +45 -2
  37. package/dist/auth/audit_log_schema.d.ts +55 -1
  38. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  39. package/dist/auth/audit_log_schema.js +19 -3
  40. package/dist/auth/bearer_auth.d.ts +9 -7
  41. package/dist/auth/bearer_auth.d.ts.map +1 -1
  42. package/dist/auth/bearer_auth.js +13 -21
  43. package/dist/auth/cleanup.d.ts.map +1 -1
  44. package/dist/auth/cleanup.js +5 -0
  45. package/dist/auth/daemon_token_middleware.d.ts +23 -11
  46. package/dist/auth/daemon_token_middleware.d.ts.map +1 -1
  47. package/dist/auth/daemon_token_middleware.js +26 -20
  48. package/dist/auth/deps.d.ts +14 -0
  49. package/dist/auth/deps.d.ts.map +1 -1
  50. package/dist/auth/middleware.d.ts.map +1 -1
  51. package/dist/auth/middleware.js +4 -2
  52. package/dist/auth/migrations.d.ts +15 -7
  53. package/dist/auth/migrations.d.ts.map +1 -1
  54. package/dist/auth/migrations.js +15 -7
  55. package/dist/auth/permit_offer_action_specs.d.ts +45 -6
  56. package/dist/auth/permit_offer_action_specs.d.ts.map +1 -1
  57. package/dist/auth/permit_offer_action_specs.js +38 -7
  58. package/dist/auth/permit_offer_actions.d.ts +2 -2
  59. package/dist/auth/permit_offer_actions.d.ts.map +1 -1
  60. package/dist/auth/permit_offer_actions.js +98 -90
  61. package/dist/auth/permit_offer_notifications.d.ts +10 -0
  62. package/dist/auth/permit_offer_notifications.d.ts.map +1 -1
  63. package/dist/auth/permit_offer_queries.d.ts +68 -9
  64. package/dist/auth/permit_offer_queries.d.ts.map +1 -1
  65. package/dist/auth/permit_offer_queries.js +147 -35
  66. package/dist/auth/permit_offer_schema.d.ts +23 -1
  67. package/dist/auth/permit_offer_schema.d.ts.map +1 -1
  68. package/dist/auth/permit_offer_schema.js +5 -0
  69. package/dist/auth/permit_queries.d.ts +17 -5
  70. package/dist/auth/permit_queries.d.ts.map +1 -1
  71. package/dist/auth/permit_queries.js +19 -8
  72. package/dist/auth/request_context.d.ts +321 -38
  73. package/dist/auth/request_context.d.ts.map +1 -1
  74. package/dist/auth/request_context.js +393 -66
  75. package/dist/auth/route_guards.d.ts +10 -4
  76. package/dist/auth/route_guards.d.ts.map +1 -1
  77. package/dist/auth/route_guards.js +14 -8
  78. package/dist/auth/self_service_role_action_specs.d.ts +2 -0
  79. package/dist/auth/self_service_role_action_specs.d.ts.map +1 -1
  80. package/dist/auth/self_service_role_action_specs.js +2 -0
  81. package/dist/auth/self_service_role_actions.d.ts +6 -5
  82. package/dist/auth/self_service_role_actions.d.ts.map +1 -1
  83. package/dist/auth/self_service_role_actions.js +18 -8
  84. package/dist/db/migrate.d.ts +11 -7
  85. package/dist/db/migrate.d.ts.map +1 -1
  86. package/dist/db/migrate.js +9 -6
  87. package/dist/dev/setup.d.ts.map +1 -1
  88. package/dist/dev/setup.js +5 -3
  89. package/dist/hono_context.d.ts +77 -0
  90. package/dist/hono_context.d.ts.map +1 -1
  91. package/dist/hono_context.js +50 -0
  92. package/dist/http/CLAUDE.md +80 -17
  93. package/dist/http/error_schemas.d.ts +92 -1
  94. package/dist/http/error_schemas.d.ts.map +1 -1
  95. package/dist/http/error_schemas.js +73 -16
  96. package/dist/http/jsonrpc_errors.d.ts +27 -2
  97. package/dist/http/jsonrpc_errors.d.ts.map +1 -1
  98. package/dist/http/jsonrpc_errors.js +26 -2
  99. package/dist/http/route_spec.d.ts +62 -4
  100. package/dist/http/route_spec.d.ts.map +1 -1
  101. package/dist/http/route_spec.js +117 -21
  102. package/dist/http/schema_helpers.d.ts +13 -1
  103. package/dist/http/schema_helpers.d.ts.map +1 -1
  104. package/dist/http/schema_helpers.js +21 -2
  105. package/dist/http/surface.d.ts +10 -1
  106. package/dist/http/surface.d.ts.map +1 -1
  107. package/dist/http/surface.js +2 -2
  108. package/dist/server/app_server.d.ts.map +1 -1
  109. package/dist/server/app_server.js +11 -1
  110. package/dist/testing/CLAUDE.md +23 -17
  111. package/dist/testing/admin_integration.d.ts.map +1 -1
  112. package/dist/testing/admin_integration.js +15 -13
  113. package/dist/testing/adversarial_headers.js +1 -1
  114. package/dist/testing/app_server.js +2 -2
  115. package/dist/testing/audit_completeness.d.ts.map +1 -1
  116. package/dist/testing/audit_completeness.js +21 -7
  117. package/dist/testing/auth_apps.d.ts.map +1 -1
  118. package/dist/testing/auth_apps.js +6 -3
  119. package/dist/testing/entities.d.ts +2 -1
  120. package/dist/testing/entities.d.ts.map +1 -1
  121. package/dist/testing/entities.js +1 -0
  122. package/dist/testing/integration_helpers.d.ts +4 -2
  123. package/dist/testing/integration_helpers.d.ts.map +1 -1
  124. package/dist/testing/integration_helpers.js +9 -5
  125. package/dist/testing/middleware.d.ts +12 -8
  126. package/dist/testing/middleware.d.ts.map +1 -1
  127. package/dist/testing/middleware.js +67 -25
  128. package/dist/testing/rpc_helpers.d.ts.map +1 -1
  129. package/dist/testing/rpc_helpers.js +3 -1
  130. package/dist/testing/ws_round_trip.d.ts.map +1 -1
  131. package/dist/testing/ws_round_trip.js +5 -1
  132. package/dist/ui/CLAUDE.md +16 -10
  133. package/dist/ui/PermitOfferForm.svelte +14 -0
  134. package/dist/ui/PermitOfferForm.svelte.d.ts +6 -0
  135. package/dist/ui/PermitOfferForm.svelte.d.ts.map +1 -1
  136. package/dist/ui/admin_accounts_state.svelte.d.ts +8 -1
  137. package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
  138. package/dist/ui/admin_accounts_state.svelte.js +14 -3
  139. package/dist/ui/permit_offers_state.svelte.d.ts +9 -1
  140. package/dist/ui/permit_offers_state.svelte.d.ts.map +1 -1
  141. package/dist/ui/permit_offers_state.svelte.js +7 -1
  142. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"self_service_role_action_specs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/self_service_role_action_specs.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,2BAA2B,CAAC;AAGzE,0FAA0F;AAC1F,eAAO,MAAM,oCAAoC,EAAG,gCAAyC,CAAC;AAE9F,yCAAyC;AACzC,eAAO,MAAM,uBAAuB;;;kBAMlC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAE9E;;;;GAIG;AACH,eAAO,MAAM,wBAAwB;;;;kBAInC,CAAC;AACH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAEhF,eAAO,MAAM,iCAAiC;;;;;;;;;;;;;;;;;CAWT,CAAC;AAEtC;;;;GAIG;AACH,eAAO,MAAM,kCAAkC,EAAE,aAAa,CAAC,yBAAyB,CAEvF,CAAC"}
1
+ {"version":3,"file":"self_service_role_action_specs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/self_service_role_action_specs.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,2BAA2B,CAAC;AAIzE,0FAA0F;AAC1F,eAAO,MAAM,oCAAoC,EAAG,gCAAyC,CAAC;AAE9F,yCAAyC;AACzC,eAAO,MAAM,uBAAuB;;;;kBAOlC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAE9E;;;;GAIG;AACH,eAAO,MAAM,wBAAwB;;;;kBAInC,CAAC;AACH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAEhF,eAAO,MAAM,iCAAiC;;;;;;;;;;;;;;;;;;CAWT,CAAC;AAEtC;;;;GAIG;AACH,eAAO,MAAM,kCAAkC,EAAE,aAAa,CAAC,yBAAyB,CAEvF,CAAC"}
@@ -9,6 +9,7 @@
9
9
  */
10
10
  import { z } from 'zod';
11
11
  import { RoleName } from './role_schema.js';
12
+ import { ActingActor } from './account_schema.js';
12
13
  /** Error reason — caller asked to self-toggle a role outside the configured allowlist. */
13
14
  export const ERROR_ROLE_NOT_SELF_SERVICE_ELIGIBLE = 'role_not_self_service_eligible';
14
15
  /** Input for `self_service_role_set`. */
@@ -17,6 +18,7 @@ export const SelfServiceRoleSetInput = z.strictObject({
17
18
  enabled: z.boolean().meta({
18
19
  description: 'Desired post-call state. `true` grants if not held; `false` revokes if held. Idempotent in both directions.',
19
20
  }),
21
+ acting: ActingActor,
20
22
  });
21
23
  /**
22
24
  * Output for `self_service_role_set`. `enabled` echoes the post-call state
@@ -33,7 +33,7 @@
33
33
  */
34
34
  import { type RpcAction } from '../actions/action_rpc.js';
35
35
  import type { RoleSchemaResult } from './role_schema.js';
36
- import type { RouteFactoryDeps } from './deps.js';
36
+ import type { AuditEmitDeps } from './deps.js';
37
37
  /** Options for `create_self_service_role_actions`. */
38
38
  export interface SelfServiceRoleActionsOptions {
39
39
  /**
@@ -50,12 +50,13 @@ export interface SelfServiceRoleActionsOptions {
50
50
  roles?: RoleSchemaResult;
51
51
  }
52
52
  /**
53
- * Dependencies for `create_self_service_role_actions`. Same shape as the
54
- * peer factories so consumers thread one deps object through all three.
55
- * `audit_log_config` flows from `AppDeps` and is consumed by
53
+ * Dependencies for `create_self_service_role_actions`.
54
+ *
55
+ * Aliases the shared `AuditEmitDeps` so consumers thread one deps object
56
+ * through every action factory. `audit_log_config` is consumed by
56
57
  * `audit_log_fire_and_forget`.
57
58
  */
58
- export type SelfServiceRoleActionDeps = Pick<RouteFactoryDeps, 'log' | 'on_audit_event' | 'audit_log_config'>;
59
+ export type SelfServiceRoleActionDeps = AuditEmitDeps;
59
60
  /**
60
61
  * Build the unified self-service role toggle RPC action.
61
62
  *
@@ -1 +1 @@
1
- {"version":3,"file":"self_service_role_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/self_service_role_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EAAiC,KAAK,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAExF,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AACvD,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAYhD,sDAAsD;AACtD,MAAM,WAAW,6BAA6B;IAC7C;;;;OAIG;IACH,cAAc,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACtC;;;;OAIG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;CACzB;AAED;;;;;GAKG;AACH,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAC3C,gBAAgB,EAChB,KAAK,GAAG,gBAAgB,GAAG,kBAAkB,CAC7C,CAAC;AAOF;;;;;;;GAOG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,yBAAyB,EAC/B,SAAS,6BAA6B,KACpC,KAAK,CAAC,SAAS,CA+GjB,CAAC"}
1
+ {"version":3,"file":"self_service_role_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/self_service_role_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EAA4C,KAAK,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAEnG,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AACvD,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,WAAW,CAAC;AAY7C,sDAAsD;AACtD,MAAM,WAAW,6BAA6B;IAC7C;;;;OAIG;IACH,cAAc,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACtC;;;;OAIG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;CACzB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,yBAAyB,GAAG,aAAa,CAAC;AAEtD;;;;;;;GAOG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,yBAAyB,EAC/B,SAAS,6BAA6B,KACpC,KAAK,CAAC,SAAS,CA8HjB,CAAC"}
@@ -31,18 +31,13 @@
31
31
  *
32
32
  * @module
33
33
  */
34
- import { rpc_action } from '../actions/action_rpc.js';
34
+ import { rpc_actor_action } from '../actions/action_rpc.js';
35
35
  import { jsonrpc_errors } from '../http/jsonrpc_errors.js';
36
36
  import { query_grant_permit, query_revoke_permit } from './permit_queries.js';
37
37
  import { audit_log_fire_and_forget } from './audit_log_queries.js';
38
38
  import { is_permit_active } from './account_schema.js';
39
39
  import { has_scoped_role } from './request_context.js';
40
40
  import { ERROR_ROLE_NOT_SELF_SERVICE_ELIGIBLE, self_service_role_set_action_spec, } from './self_service_role_action_specs.js';
41
- const require_request_auth = (auth) => {
42
- if (!auth)
43
- throw new Error('unreachable: action auth guard did not enforce authentication');
44
- return auth;
45
- };
46
41
  /**
47
42
  * Build the unified self-service role toggle RPC action.
48
43
  *
@@ -69,7 +64,7 @@ export const create_self_service_role_actions = (deps, options) => {
69
64
  }
70
65
  };
71
66
  const handler = async (input, ctx) => {
72
- const auth = require_request_auth(ctx.auth);
67
+ const auth = ctx.auth;
73
68
  reject_if_ineligible(input.role);
74
69
  if (input.enabled) {
75
70
  // Pre-check for idempotent re-grant. `query_grant_permit` is itself
@@ -91,10 +86,19 @@ export const create_self_service_role_actions = (deps, options) => {
91
86
  expires_at: null,
92
87
  granted_by: auth.actor.id,
93
88
  });
89
+ // `permit_grant` is the canonical actor-bound-subject event —
90
+ // populate both target columns even on self-service so the
91
+ // "always populated for permit_grant" rule holds uniformly
92
+ // regardless of who initiated the grant. On self-service the
93
+ // grantor and grantee are the same identity; admin direct-grant
94
+ // (separate code path) populates the same columns with the
95
+ // grantee actor.
94
96
  void audit_log_fire_and_forget(ctx, {
95
97
  event_type: 'permit_grant',
96
98
  actor_id: auth.actor.id,
97
99
  account_id: auth.account.id,
100
+ target_account_id: auth.account.id,
101
+ target_actor_id: auth.actor.id,
98
102
  ip: ctx.client_ip,
99
103
  metadata: {
100
104
  role: permit.role,
@@ -120,10 +124,16 @@ export const create_self_service_role_actions = (deps, options) => {
120
124
  // Raced with another revoker — treat as already revoked.
121
125
  return { ok: true, enabled: false, changed: false };
122
126
  }
127
+ // Same actor-bound rule as the grant branch — `permit_revoke`
128
+ // always populates both target columns even on self-service so
129
+ // forensic queries that filter on `target_actor_id IS NOT NULL`
130
+ // don't silently miss self-toggled permits.
123
131
  void audit_log_fire_and_forget(ctx, {
124
132
  event_type: 'permit_revoke',
125
133
  actor_id: auth.actor.id,
126
134
  account_id: auth.account.id,
135
+ target_account_id: auth.account.id,
136
+ target_actor_id: auth.actor.id,
127
137
  ip: ctx.client_ip,
128
138
  metadata: {
129
139
  role: result.role,
@@ -134,5 +144,5 @@ export const create_self_service_role_actions = (deps, options) => {
134
144
  }, deps);
135
145
  return { ok: true, enabled: false, changed: true };
136
146
  };
137
- return [rpc_action(self_service_role_set_action_spec, handler)];
147
+ return [rpc_actor_action(self_service_role_set_action_spec, handler)];
138
148
  };
@@ -6,12 +6,15 @@
6
6
  * `(namespace, name, sequence, applied_at)` — and the runner verifies the
7
7
  * applied list is a name-prefix of the code's migration array at boot.
8
8
  *
9
- * **Append-only after first publish**: once a fuz_app version containing a
10
- * given migration is published (`npm publish` / `jsr publish`), that
11
- * migration's name and position are frozen. Never edit, rename, or reorder
12
- * after publish append only. Pre-publish, anything goes; the cliff is the
13
- * publish event. Edits to a published migration's body slip past the runner
14
- * (no content hashing) and are caught by schema-snapshot tests in consumers.
9
+ * **Schema is not stabilized yet append-only is NOT the rule.** While
10
+ * fuz_app is pre-stable, migration bodies, names, and positions can change
11
+ * freely between versions; consumers upgrading across a schema change are
12
+ * expected to drop and re-bootstrap their dev/test databases (production
13
+ * deployments are not yet a supported use case). Once the schema is
14
+ * declared stable a hard append-only-after-publish rule will apply and the
15
+ * cliff will be called out in that release's notes; until then, body edits
16
+ * to a published migration slip past the runner (no content hashing) by
17
+ * design — they're the recommended way to evolve the schema.
15
18
  *
16
19
  * **Chain-level transactions**: All pending migrations in a namespace run in
17
20
  * a single transaction. Any failure rolls back every migration in that run —
@@ -53,7 +56,8 @@ export interface Migration {
53
56
  /**
54
57
  * A named group of ordered migrations.
55
58
  *
56
- * Array index = position in the chain. Append-only after publish.
59
+ * Array index = position in the chain. Pre-stable: bodies, names, and
60
+ * positions can change between versions (consumers re-bootstrap on upgrade).
57
61
  */
58
62
  export interface MigrationNamespace {
59
63
  namespace: string;
@@ -1 +1 @@
1
- {"version":3,"file":"migrate.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/migrate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,SAAS,CAAC;AAEhC;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;CAC7B;AAED,2DAA2D;AAC3D,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,aAAa,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CAC7B;AAED;;;;;GAKG;AACH,MAAM,MAAM,kBAAkB,GAC3B,sBAAsB,GACtB,sBAAsB,GACtB,mBAAmB,GACnB,kBAAkB,GAClB,2BAA2B,GAC3B,4BAA4B,GAC5B,sCAAsC,CAAC;AAE1C,8DAA8D;AAC9D,MAAM,WAAW,qBAAqB;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACtC,KAAK,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;;;GAIG;AACH,qBAAa,cAAe,SAAQ,KAAK;IACxC,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;gBAEnC,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB;CAQtF;AA6ED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,cAAc,GAC1B,IAAI,EAAE,EACN,YAAY,KAAK,CAAC,kBAAkB,CAAC,KACnC,OAAO,CAAC,KAAK,CAAC,eAAe,CAAC,CAuFhC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,eAAO,MAAM,QAAQ,GACpB,IAAI,EAAE,EACN,IAAI,kBAAkB,EACtB,OAAO,aAAa,CAAC,MAAM,CAAC,KAC1B,OAAO,CAAC,IAAI,CA+Dd,CAAC"}
1
+ {"version":3,"file":"migrate.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/migrate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,SAAS,CAAC;AAEhC;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;CAC7B;AAED,2DAA2D;AAC3D,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,aAAa,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CAC7B;AAED;;;;;GAKG;AACH,MAAM,MAAM,kBAAkB,GAC3B,sBAAsB,GACtB,sBAAsB,GACtB,mBAAmB,GACnB,kBAAkB,GAClB,2BAA2B,GAC3B,4BAA4B,GAC5B,sCAAsC,CAAC;AAE1C,8DAA8D;AAC9D,MAAM,WAAW,qBAAqB;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACtC,KAAK,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;;;GAIG;AACH,qBAAa,cAAe,SAAQ,KAAK;IACxC,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;gBAEnC,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB;CAQtF;AA6ED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,cAAc,GAC1B,IAAI,EAAE,EACN,YAAY,KAAK,CAAC,kBAAkB,CAAC,KACnC,OAAO,CAAC,KAAK,CAAC,eAAe,CAAC,CAuFhC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,eAAO,MAAM,QAAQ,GACpB,IAAI,EAAE,EACN,IAAI,kBAAkB,EACtB,OAAO,aAAa,CAAC,MAAM,CAAC,KAC1B,OAAO,CAAC,IAAI,CA+Dd,CAAC"}
@@ -6,12 +6,15 @@
6
6
  * `(namespace, name, sequence, applied_at)` — and the runner verifies the
7
7
  * applied list is a name-prefix of the code's migration array at boot.
8
8
  *
9
- * **Append-only after first publish**: once a fuz_app version containing a
10
- * given migration is published (`npm publish` / `jsr publish`), that
11
- * migration's name and position are frozen. Never edit, rename, or reorder
12
- * after publish append only. Pre-publish, anything goes; the cliff is the
13
- * publish event. Edits to a published migration's body slip past the runner
14
- * (no content hashing) and are caught by schema-snapshot tests in consumers.
9
+ * **Schema is not stabilized yet append-only is NOT the rule.** While
10
+ * fuz_app is pre-stable, migration bodies, names, and positions can change
11
+ * freely between versions; consumers upgrading across a schema change are
12
+ * expected to drop and re-bootstrap their dev/test databases (production
13
+ * deployments are not yet a supported use case). Once the schema is
14
+ * declared stable a hard append-only-after-publish rule will apply and the
15
+ * cliff will be called out in that release's notes; until then, body edits
16
+ * to a published migration slip past the runner (no content hashing) by
17
+ * design — they're the recommended way to evolve the schema.
15
18
  *
16
19
  * **Chain-level transactions**: All pending migrations in a namespace run in
17
20
  * a single transaction. Any failure rolls back every migration in that run —
@@ -1 +1 @@
1
- {"version":3,"file":"setup.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/dev/setup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EACX,WAAW,EACX,aAAa,EACb,OAAO,EACP,UAAU,EACV,YAAY,EACZ,WAAW,EACX,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAQnD;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC3B,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1B,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B;AAED,2CAA2C;AAC3C,eAAO,MAAM,oBAAoB,EAAE,WAIlC,CAAC;AAEF,kCAAkC;AAClC,MAAM,WAAW,cAAc;IAC9B,6DAA6D;IAC7D,OAAO,EAAE,OAAO,CAAC;IACjB,kDAAkD;IAClD,OAAO,EAAE,OAAO,CAAC;IACjB,yBAAyB;IACzB,IAAI,EAAE,MAAM,CAAC;CACb;AAED,yCAAyC;AACzC,MAAM,WAAW,gBAAgB;IAChC,kEAAkE;IAClE,OAAO,EAAE,OAAO,CAAC;IACjB,2BAA2B;IAC3B,UAAU,EAAE,MAAM,CAAC;CACnB;AAED,kCAAkC;AAClC,MAAM,WAAW,aAAa;IAC7B,+CAA+C;IAC/C,KAAK,EAAE,OAAO,CAAC;IACf,wEAAwE;IACxE,OAAO,EAAE,OAAO,CAAC;IACjB,0CAA0C;IAC1C,OAAO,EAAE,UAAU,GAAG,QAAQ,GAAG,MAAM,CAAC;CACxC;AAED,oCAAoC;AACpC,MAAM,WAAW,eAAe;IAC/B;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IACrD,qEAAqE;IACrE,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,GAAG,CAAC,EAAE,WAAW,CAAC;CAClB;AAED,2CAA2C;AAC3C,MAAM,WAAW,0BAA0B;IAC1C,6DAA6D;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,GAAG,CAAC,EAAE,WAAW,CAAC;CAClB;AAED,qCAAqC;AACrC,MAAM,WAAW,qBAAqB;IACrC,GAAG,CAAC,EAAE,WAAW,CAAC;CAClB;AAED,oCAAoC;AACpC,MAAM,WAAW,oBAAoB;IACpC,iDAAiD;IACjD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,GAAG,CAAC,EAAE,WAAW,CAAC;CAClB;AAID;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAI,KAAK,MAAM,KAAG,MAAM,GAAG,IAQpD,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,GAAU,MAAM,WAAW,KAAG,OAAO,CAAC,MAAM,CAI3E,CAAC;AAIF;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,GACxB,MAAM,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,gBAAgB,CAAC,EACjD,UAAU,MAAM,EAChB,MAAM,MAAM,KACV,OAAO,CAAC,MAAM,GAAG,SAAS,CAU5B,CAAC;AAIF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,cAAc,GAC1B,MAAM,UAAU,GAAG,WAAW,GAAG,WAAW,EAC5C,UAAU,MAAM,EAChB,cAAc,MAAM,EACpB,UAAU,eAAe,KACvB,OAAO,CAAC,cAAc,CAiDxB,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,qBAAqB,GACjC,MAAM,UAAU,GAAG,WAAW,GAAG,WAAW,GAAG,OAAO,EACtD,UAAU,MAAM,EAChB,UAAU,0BAA0B,KAClC,OAAO,CAAC,gBAAgB,CA0B1B,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,GACjC,MAAM,UAAU,GAAG,WAAW,GAAG,YAAY,GAAG,WAAW,GAAG,OAAO,EACrE,UAAU,MAAM,EAChB,UAAU,0BAA0B,KAClC,OAAO,CAAC,gBAAgB,CAoB1B,CAAC;AAIF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,eAAe,GAC3B,MAAM,WAAW,EACjB,SAAS,MAAM,EACf,UAAU,qBAAqB,KAC7B,OAAO,CAAC,aAAa,CAgBvB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,cAAc,GAC1B,MAAM,WAAW,GAAG,UAAU,GAAG,YAAY,EAC7C,cAAc,MAAM,EACpB,UAAU,oBAAoB,KAC5B,OAAO,CAAC,aAAa,CA8CvB,CAAC;AAIF,mCAAmC;AACnC,MAAM,WAAW,mBAAmB;IACnC,+EAA+E;IAC/E,QAAQ,EAAE,MAAM,CAAC;IACjB,+EAA+E;IAC/E,QAAQ,EAAE,MAAM,CAAC;IACjB,8CAA8C;IAC9C,KAAK,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAC9B;AAED,oCAAoC;AACpC,MAAM,WAAW,oBAAoB;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,OAAO,EAAE,OAAO,CAAC;CACjB;AAED,2CAA2C;AAC3C,MAAM,WAAW,kBAAmB,SAAQ,SAAS;IACpD,oEAAoE;IACpE,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CACrD;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,gBAAgB,GAC5B,MAAM,kBAAkB,EACxB,OAAO,mBAAmB,EAC1B,UAAU;IAAC,GAAG,CAAC,EAAE,WAAW,CAAA;CAAC,KAC3B,OAAO,CAAC,oBAAoB,CAsC9B,CAAC"}
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/dev/setup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EACX,WAAW,EACX,aAAa,EACb,OAAO,EACP,UAAU,EACV,YAAY,EACZ,WAAW,EACX,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAQnD;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC3B,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1B,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B;AAED,2CAA2C;AAC3C,eAAO,MAAM,oBAAoB,EAAE,WAIlC,CAAC;AAEF,kCAAkC;AAClC,MAAM,WAAW,cAAc;IAC9B,6DAA6D;IAC7D,OAAO,EAAE,OAAO,CAAC;IACjB,kDAAkD;IAClD,OAAO,EAAE,OAAO,CAAC;IACjB,yBAAyB;IACzB,IAAI,EAAE,MAAM,CAAC;CACb;AAED,yCAAyC;AACzC,MAAM,WAAW,gBAAgB;IAChC,kEAAkE;IAClE,OAAO,EAAE,OAAO,CAAC;IACjB,2BAA2B;IAC3B,UAAU,EAAE,MAAM,CAAC;CACnB;AAED,kCAAkC;AAClC,MAAM,WAAW,aAAa;IAC7B,+CAA+C;IAC/C,KAAK,EAAE,OAAO,CAAC;IACf,wEAAwE;IACxE,OAAO,EAAE,OAAO,CAAC;IACjB,0CAA0C;IAC1C,OAAO,EAAE,UAAU,GAAG,QAAQ,GAAG,MAAM,CAAC;CACxC;AAED,oCAAoC;AACpC,MAAM,WAAW,eAAe;IAC/B;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IACrD,qEAAqE;IACrE,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,GAAG,CAAC,EAAE,WAAW,CAAC;CAClB;AAED,2CAA2C;AAC3C,MAAM,WAAW,0BAA0B;IAC1C,6DAA6D;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,GAAG,CAAC,EAAE,WAAW,CAAC;CAClB;AAED,qCAAqC;AACrC,MAAM,WAAW,qBAAqB;IACrC,GAAG,CAAC,EAAE,WAAW,CAAC;CAClB;AAED,oCAAoC;AACpC,MAAM,WAAW,oBAAoB;IACpC,iDAAiD;IACjD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,GAAG,CAAC,EAAE,WAAW,CAAC;CAClB;AAID;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAI,KAAK,MAAM,KAAG,MAAM,GAAG,IAQpD,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,GAAU,MAAM,WAAW,KAAG,OAAO,CAAC,MAAM,CAI3E,CAAC;AAIF;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,GACxB,MAAM,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,gBAAgB,CAAC,EACjD,UAAU,MAAM,EAChB,MAAM,MAAM,KACV,OAAO,CAAC,MAAM,GAAG,SAAS,CAU5B,CAAC;AAIF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,cAAc,GAC1B,MAAM,UAAU,GAAG,WAAW,GAAG,WAAW,EAC5C,UAAU,MAAM,EAChB,cAAc,MAAM,EACpB,UAAU,eAAe,KACvB,OAAO,CAAC,cAAc,CAiDxB,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,qBAAqB,GACjC,MAAM,UAAU,GAAG,WAAW,GAAG,WAAW,GAAG,OAAO,EACtD,UAAU,MAAM,EAChB,UAAU,0BAA0B,KAClC,OAAO,CAAC,gBAAgB,CA0B1B,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,GACjC,MAAM,UAAU,GAAG,WAAW,GAAG,YAAY,GAAG,WAAW,GAAG,OAAO,EACrE,UAAU,MAAM,EAChB,UAAU,0BAA0B,KAClC,OAAO,CAAC,gBAAgB,CAoB1B,CAAC;AAIF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,eAAe,GAC3B,MAAM,WAAW,EACjB,SAAS,MAAM,EACf,UAAU,qBAAqB,KAC7B,OAAO,CAAC,aAAa,CAgBvB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,cAAc,GAC1B,MAAM,WAAW,GAAG,UAAU,GAAG,YAAY,EAC7C,cAAc,MAAM,EACpB,UAAU,oBAAoB,KAC5B,OAAO,CAAC,aAAa,CA8CvB,CAAC;AAIF,mCAAmC;AACnC,MAAM,WAAW,mBAAmB;IACnC,+EAA+E;IAC/E,QAAQ,EAAE,MAAM,CAAC;IACjB,+EAA+E;IAC/E,QAAQ,EAAE,MAAM,CAAC;IACjB,8CAA8C;IAC9C,KAAK,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAC9B;AAED,oCAAoC;AACpC,MAAM,WAAW,oBAAoB;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,OAAO,EAAE,OAAO,CAAC;CACjB;AAED,2CAA2C;AAC3C,MAAM,WAAW,kBAAmB,SAAQ,SAAS;IACpD,oEAAoE;IACpE,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CACrD;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,gBAAgB,GAC5B,MAAM,kBAAkB,EACxB,OAAO,mBAAmB,EAC1B,UAAU;IAAC,GAAG,CAAC,EAAE,WAAW,CAAA;CAAC,KAC3B,OAAO,CAAC,oBAAoB,CAwC9B,CAAC"}
package/dist/dev/setup.js CHANGED
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * @module
10
10
  */
11
- import { query_account_by_username, query_actor_by_account, query_create_account_with_actor, } from '../auth/account_queries.js';
11
+ import { query_account_by_username, query_actors_by_account, query_create_account_with_actor, } from '../auth/account_queries.js';
12
12
  import { query_grant_permit } from '../auth/permit_queries.js';
13
13
  /** Default logger using bracket format. */
14
14
  export const default_setup_logger = {
@@ -292,11 +292,13 @@ export const seed_dev_account = async (deps, input, options) => {
292
292
  const query_deps = { db: deps.db };
293
293
  const existing = await query_account_by_username(query_deps, input.username);
294
294
  if (existing) {
295
- const actor = await query_actor_by_account(query_deps, existing.id);
296
- if (!actor) {
295
+ const actors = await query_actors_by_account(query_deps, existing.id);
296
+ if (actors.length === 0) {
297
297
  log.error(`dev account '${input.username}' exists but has no actor`);
298
298
  throw new Error(`dev account '${input.username}' has no actor`);
299
299
  }
300
+ // Dev seed is single-actor by construction; pick the first.
301
+ const actor = actors[0];
300
302
  for (const role of input.roles ?? []) {
301
303
  await query_grant_permit(query_deps, {
302
304
  actor_id: actor.id,
@@ -25,6 +25,63 @@ export type CredentialType = z.infer<typeof CredentialType>;
25
25
  export declare const CREDENTIAL_TYPE_KEY = "credential_type";
26
26
  /** Hono context variable name for the authenticated API token id. */
27
27
  export declare const AUTH_API_TOKEN_ID_KEY = "auth_api_token_id";
28
+ /**
29
+ * Hono context variable name for the authenticated account id.
30
+ *
31
+ * Set by the auth middleware (session, bearer, or daemon token) on a valid
32
+ * credential. `null` for unauthenticated requests. The route-spec wrapper /
33
+ * RPC dispatcher's authorization phase reads this when resolving the acting
34
+ * actor; account-grain auth guards (`require_auth`) and account-grain handlers
35
+ * read it directly.
36
+ */
37
+ export declare const ACCOUNT_ID_KEY = "auth_account_id";
38
+ /**
39
+ * Hono context variable name for the test-harness pre-baked context flag.
40
+ *
41
+ * Test harnesses (`create_test_app_from_specs`, `create_fake_hono_context`,
42
+ * the WS round-trip `connect()` helper, plus per-test middleware that
43
+ * pre-populates `REQUEST_CONTEXT_KEY`) set this to `true` so
44
+ * `apply_authorization_phase` skips its DB-backed actor resolution and
45
+ * trusts the supplied `RequestContext`. Production middleware never sets
46
+ * this key — only test code does. The flag is the explicit escape hatch
47
+ * that replaced the implicit "is `REQUEST_CONTEXT_KEY` already set?" probe,
48
+ * so that future production code consulting `REQUEST_CONTEXT_KEY` cannot
49
+ * silently bypass the live build.
50
+ */
51
+ export declare const TEST_CONTEXT_PRESET_KEY = "test_context_preset";
52
+ /**
53
+ * Cached parsed JSON request body, keyed by `'cached_request_body'`.
54
+ *
55
+ * Written by `read_raw_acting` (in the dispatcher's authorization
56
+ * phase) when it pre-parses the body to extract the `acting` field;
57
+ * read by `create_input_validation` so the input-validation step does
58
+ * not pay for a second `JSON.parse` on the same Hono-cached body text.
59
+ *
60
+ * Decouples our pipeline from Hono's internal `bodyCache` shape: Hono
61
+ * caches the body *text* (so a second `c.req.json()` call doesn't
62
+ * re-read the request stream), but each call still re-runs
63
+ * `JSON.parse(text)`. Storing the parsed value here saves the second
64
+ * parse and keeps fuz_app from depending on undocumented Hono
65
+ * implementation details.
66
+ *
67
+ * Three states:
68
+ *
69
+ * - Key absent — body has not been pre-parsed yet (the route had no
70
+ * `acting` to extract, or the request is GET).
71
+ * - `{ok: true, body: unknown}` — pre-parse succeeded; the parsed
72
+ * value (object, primitive, or array) is in `body`.
73
+ * - `{ok: false}` — pre-parse threw (malformed JSON). The downstream
74
+ * input-validation step short-circuits with `ERROR_INVALID_JSON_BODY`
75
+ * instead of re-parsing.
76
+ */
77
+ export declare const CACHED_REQUEST_BODY_KEY = "cached_request_body";
78
+ /** The shape stored under `CACHED_REQUEST_BODY_KEY`. */
79
+ export type CachedRequestBody = {
80
+ ok: true;
81
+ body: unknown;
82
+ } | {
83
+ ok: false;
84
+ };
28
85
  declare module 'hono' {
29
86
  interface ContextVariableMap {
30
87
  /** Resolved client IP, set by the trusted proxy middleware. */
@@ -36,6 +93,13 @@ declare module 'hono' {
36
93
  validated_query: unknown;
37
94
  /** How the request was authenticated (`'session'`, `'api_token'`, or `'daemon_token'`). */
38
95
  credential_type: CredentialType | null;
96
+ /**
97
+ * Authenticated account id. Set by the session / bearer / daemon-token
98
+ * middleware on a valid credential; `null` for unauthenticated requests.
99
+ * The dispatcher's authorization phase resolves the acting actor against
100
+ * this id; `require_auth` 401s when it is `null`.
101
+ */
102
+ auth_account_id: string | null;
39
103
  /**
40
104
  * blake3 hash of the authenticated session token, or `null` for non-session
41
105
  * credentials. Set by `create_request_context_middleware`. Used to scope
@@ -57,6 +121,19 @@ declare module 'hono' {
57
121
  * all effects are awaited before the response returns.
58
122
  */
59
123
  pending_effects: Array<Promise<void>>;
124
+ /**
125
+ * Set to `true` by test harnesses that pre-populate `request_context`
126
+ * to bypass the dispatcher's DB-backed actor resolution. Read by
127
+ * `apply_authorization_phase`. Production middleware never sets this.
128
+ */
129
+ test_context_preset: boolean;
130
+ /**
131
+ * Pre-parsed JSON request body cache. Written by `read_raw_acting`
132
+ * (the dispatcher's `acting` extractor) and read by
133
+ * `create_input_validation` so the same body is not parsed twice.
134
+ * See `CACHED_REQUEST_BODY_KEY` for state semantics.
135
+ */
136
+ cached_request_body: CachedRequestBody;
60
137
  }
61
138
  }
62
139
  //# sourceMappingURL=hono_context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"hono_context.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/hono_context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAE9D,4DAA4D;AAC5D,eAAO,MAAM,gBAAgB,mDAAoD,CAAC;AAElF,yDAAyD;AACzD,eAAO,MAAM,cAAc;;;;EAA2B,CAAC;AACvD,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,0DAA0D;AAC1D,eAAO,MAAM,mBAAmB,oBAAoB,CAAC;AAErD,qEAAqE;AACrE,eAAO,MAAM,qBAAqB,sBAAsB,CAAC;AAEzD,OAAO,QAAQ,MAAM,CAAC;IACrB,UAAU,kBAAkB;QAC3B,+DAA+D;QAC/D,SAAS,EAAE,MAAM,CAAC;QAClB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;QAC/B,eAAe,EAAE,cAAc,GAAG,IAAI,CAAC;QACvC,eAAe,EAAE,OAAO,CAAC;QACzB,gBAAgB,EAAE,OAAO,CAAC;QAC1B,eAAe,EAAE,OAAO,CAAC;QACzB,2FAA2F;QAC3F,eAAe,EAAE,cAAc,GAAG,IAAI,CAAC;QACvC;;;;;WAKG;QACH,uBAAuB,EAAE,MAAM,GAAG,IAAI,CAAC;QACvC;;;;;;WAMG;QACH,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;QACjC;;;;WAIG;QACH,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;KACtC;CACD"}
1
+ {"version":3,"file":"hono_context.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/hono_context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAE9D,4DAA4D;AAC5D,eAAO,MAAM,gBAAgB,mDAAoD,CAAC;AAElF,yDAAyD;AACzD,eAAO,MAAM,cAAc;;;;EAA2B,CAAC;AACvD,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,0DAA0D;AAC1D,eAAO,MAAM,mBAAmB,oBAAoB,CAAC;AAErD,qEAAqE;AACrE,eAAO,MAAM,qBAAqB,sBAAsB,CAAC;AAEzD;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,oBAAoB,CAAC;AAEhD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,uBAAuB,wBAAwB,CAAC;AAE7D;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,uBAAuB,wBAAwB,CAAC;AAE7D,wDAAwD;AACxD,MAAM,MAAM,iBAAiB,GAAG;IAAC,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAC,GAAG;IAAC,EAAE,EAAE,KAAK,CAAA;CAAC,CAAC;AAExE,OAAO,QAAQ,MAAM,CAAC;IACrB,UAAU,kBAAkB;QAC3B,+DAA+D;QAC/D,SAAS,EAAE,MAAM,CAAC;QAClB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;QAC/B,eAAe,EAAE,cAAc,GAAG,IAAI,CAAC;QACvC,eAAe,EAAE,OAAO,CAAC;QACzB,gBAAgB,EAAE,OAAO,CAAC;QAC1B,eAAe,EAAE,OAAO,CAAC;QACzB,2FAA2F;QAC3F,eAAe,EAAE,cAAc,GAAG,IAAI,CAAC;QACvC;;;;;WAKG;QACH,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;QAC/B;;;;;WAKG;QACH,uBAAuB,EAAE,MAAM,GAAG,IAAI,CAAC;QACvC;;;;;;WAMG;QACH,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;QACjC;;;;WAIG;QACH,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACtC;;;;WAIG;QACH,mBAAmB,EAAE,OAAO,CAAC;QAC7B;;;;;WAKG;QACH,mBAAmB,EAAE,iBAAiB,CAAC;KACvC;CACD"}
@@ -19,3 +19,53 @@ export const CredentialType = z.enum(CREDENTIAL_TYPES);
19
19
  export const CREDENTIAL_TYPE_KEY = 'credential_type';
20
20
  /** Hono context variable name for the authenticated API token id. */
21
21
  export const AUTH_API_TOKEN_ID_KEY = 'auth_api_token_id';
22
+ /**
23
+ * Hono context variable name for the authenticated account id.
24
+ *
25
+ * Set by the auth middleware (session, bearer, or daemon token) on a valid
26
+ * credential. `null` for unauthenticated requests. The route-spec wrapper /
27
+ * RPC dispatcher's authorization phase reads this when resolving the acting
28
+ * actor; account-grain auth guards (`require_auth`) and account-grain handlers
29
+ * read it directly.
30
+ */
31
+ export const ACCOUNT_ID_KEY = 'auth_account_id';
32
+ /**
33
+ * Hono context variable name for the test-harness pre-baked context flag.
34
+ *
35
+ * Test harnesses (`create_test_app_from_specs`, `create_fake_hono_context`,
36
+ * the WS round-trip `connect()` helper, plus per-test middleware that
37
+ * pre-populates `REQUEST_CONTEXT_KEY`) set this to `true` so
38
+ * `apply_authorization_phase` skips its DB-backed actor resolution and
39
+ * trusts the supplied `RequestContext`. Production middleware never sets
40
+ * this key — only test code does. The flag is the explicit escape hatch
41
+ * that replaced the implicit "is `REQUEST_CONTEXT_KEY` already set?" probe,
42
+ * so that future production code consulting `REQUEST_CONTEXT_KEY` cannot
43
+ * silently bypass the live build.
44
+ */
45
+ export const TEST_CONTEXT_PRESET_KEY = 'test_context_preset';
46
+ /**
47
+ * Cached parsed JSON request body, keyed by `'cached_request_body'`.
48
+ *
49
+ * Written by `read_raw_acting` (in the dispatcher's authorization
50
+ * phase) when it pre-parses the body to extract the `acting` field;
51
+ * read by `create_input_validation` so the input-validation step does
52
+ * not pay for a second `JSON.parse` on the same Hono-cached body text.
53
+ *
54
+ * Decouples our pipeline from Hono's internal `bodyCache` shape: Hono
55
+ * caches the body *text* (so a second `c.req.json()` call doesn't
56
+ * re-read the request stream), but each call still re-runs
57
+ * `JSON.parse(text)`. Storing the parsed value here saves the second
58
+ * parse and keeps fuz_app from depending on undocumented Hono
59
+ * implementation details.
60
+ *
61
+ * Three states:
62
+ *
63
+ * - Key absent — body has not been pre-parsed yet (the route had no
64
+ * `acting` to extract, or the request is GET).
65
+ * - `{ok: true, body: unknown}` — pre-parse succeeded; the parsed
66
+ * value (object, primitive, or array) is in `body`.
67
+ * - `{ok: false}` — pre-parse threw (malformed JSON). The downstream
68
+ * input-validation step short-circuits with `ERROR_INVALID_JSON_BODY`
69
+ * instead of re-parsing.
70
+ */
71
+ export const CACHED_REQUEST_BODY_KEY = 'cached_request_body';
@@ -91,21 +91,66 @@ wrapper). See `../auth/signup_routes.ts`.
91
91
 
92
92
  `apply_route_specs` assembles the following middleware chain per spec:
93
93
 
94
- 1. **Auth guards** — `resolve_auth_guards(spec.auth)`, injected via
95
- `AuthGuardResolver` (use `fuz_auth_guard_resolver` from `../auth/route_guards.ts`)
96
- 2. **Params validation** — `spec.params` → `validated_params` context var;
97
- mismatch returns 400 `ERROR_INVALID_ROUTE_PARAMS` with Zod `issues`
98
- 3. **Query validation** — `spec.query` → `validated_query`; mismatch returns
99
- 400 `ERROR_INVALID_QUERY_PARAMS`
100
- 4. **Input validation** JSON body parsed + validated; mismatch returns 400
101
- `ERROR_INVALID_JSON_BODY` (not JSON) or `ERROR_INVALID_REQUEST_BODY`
102
- (schema failure with `issues`). Skipped on GET and `z.null()` inputs
103
- 5. **Handler** — wrapped in transaction when `use_transaction` (see above),
104
- receives `RouteContext`
105
- 6. **DEV-only output + error validation** — wraps the handler (see below)
106
- 7. **Error catch** — catches `ThrownJsonrpcError` maps to HTTP status +
107
- JSON-RPC error body; catches generic `Error` 500 `internal_error`
108
- (message only included in DEV)
94
+ 1. **Params validation** — `spec.params` `validated_params` context
95
+ var; mismatch returns 400 `ERROR_INVALID_ROUTE_PARAMS` with Zod
96
+ `issues`
97
+ 2. **Query validation** `spec.query` `validated_query`; mismatch
98
+ returns 400 `ERROR_INVALID_QUERY_PARAMS`
99
+ 3. **Pre-validation auth guards** — `require_auth` (401
100
+ `ERROR_AUTHENTICATION_REQUIRED`) for any non-public route. Fires
101
+ before any body parsing so unauthenticated callers never see
102
+ route-shape information from input parse failures. The
103
+ `AuthGuardResolver` (e.g. `fuz_auth_guard_resolver` from
104
+ `../auth/route_guards.ts`) returns this set as
105
+ `pre_validation: Array<MiddlewareHandler>`.
106
+ 4. **Authorization phase** — when the route's input schema declares
107
+ `acting?: ActingActor` or `spec.auth.type` is `'role'` / `'keeper'`,
108
+ resolves the acting actor against `c.var.account_id` (set by the
109
+ auth middleware) plus the raw `acting` value extracted from query
110
+ (GET) or pre-parsed JSON body (mutating methods), builds
111
+ `RequestContext` via `build_request_context`, and sets
112
+ `REQUEST_CONTEXT_KEY`. Resolution failures return 400
113
+ `ERROR_ACTOR_REQUIRED` (with `available[]`) or
114
+ `ERROR_ACTOR_NOT_ON_ACCOUNT` (or 500 `ERROR_NO_ACTORS_ON_ACCOUNT`
115
+ when the actor enumeration came back empty, 500 `ERROR_ACCOUNT_VANISHED`
116
+ on torn account/actor reads after a successful resolve) before the
117
+ handler runs. Account-grain
118
+ routes skip this phase; their handlers see no `RequestContext` (or
119
+ one with `actor: null`, depending on the helper). The pre-parsed body
120
+ lands on `c.var.cached_request_body` (see step 6) so the subsequent
121
+ input-validation step reads from there instead of re-parsing.
122
+ 5. **Post-authorization auth guards** — `require_role(role)` /
123
+ `require_keeper` (403 `ERROR_INSUFFICIENT_PERMISSIONS` /
124
+ `ERROR_KEEPER_REQUIRES_DAEMON_TOKEN`). Reads `REQUEST_CONTEXT_KEY`
125
+ populated by step 4. The resolver returns this set as
126
+ `post_authorization: Array<MiddlewareHandler>`.
127
+ 6. **Input validation** — JSON body parsed + validated; mismatch returns
128
+ 400 `ERROR_INVALID_JSON_BODY` (not JSON) or `ERROR_INVALID_REQUEST_BODY`
129
+ (schema failure with `issues`). Skipped on GET and `z.null()` inputs.
130
+ On mutating methods, the parse result is shared with the authorization
131
+ phase's pre-parse via `c.var.cached_request_body`
132
+ (`CACHED_REQUEST_BODY_KEY` from `hono_context.ts`) — the cache is
133
+ fuz_app-owned, not Hono's internal `bodyCache`, so a future Hono
134
+ internals refactor can't break our second-parse-avoidance contract.
135
+ 7. **Handler** — wrapped in transaction when `use_transaction` (see
136
+ above), receives `RouteContext`
137
+ 8. **DEV-only output + error validation** — wraps the handler (see below)
138
+ 9. **Error catch** — catches `ThrownJsonrpcError` → maps to HTTP status +
139
+ the flat REST `ApiError` body (`{error: <reason>, message?, ...rest_data}`);
140
+ catches generic `Error` → 500 `{error: 'internal_error', message?}`
141
+ (message only included in DEV). The reason string comes from
142
+ `err.data.reason` when set (consumer-supplied canonical reason
143
+ override) or from `jsonrpc_error_code_to_name(err.code)` (e.g.
144
+ `-32003 → 'not_found'`). The flat shape matches what middleware
145
+ and direct handlers emit (`c.json({error: ERROR_FOO}, status)`,
146
+ `c.json(failure.body, status)` from the dispatcher's authorization
147
+ phase) — REST callers see one envelope across every emit site, while
148
+ the JSON-RPC dispatcher keeps its own `{jsonrpc, id, error: {code,
149
+ message, data}}` envelope on the RPC mount
150
+
151
+ The auth-before-validation order matches the RPC dispatcher
152
+ (`actions/action_rpc.ts`) so HTTP RPC and REST surface failures with
153
+ the same priority: 401 → 403 → 400 → handler.
109
154
 
110
155
  Duplicate `method path` pairs throw at registration.
111
156
 
@@ -162,21 +207,39 @@ Pair every schema with the `z.infer` type export (`export type ApiError = z.infe
162
207
 
163
208
  ### Three-layer error-schema merge
164
209
 
165
- `merge_error_schemas(spec, middleware_errors?)` (in `schema_helpers.ts`)
210
+ `merge_error_schemas(spec, middleware_errors?, acting_aware?)` (in `schema_helpers.ts`)
166
211
  merges three layers, later overrides earlier at the same status code:
167
212
 
168
- 1. **Derived** — from `derive_error_schemas(auth, has_input, has_params, has_query, rate_limit)`:
213
+ 1. **Derived** — from `derive_error_schemas({auth, has_input?, has_params?, has_query?, rate_limit?, acting_aware?})`:
169
214
  - `has_input || has_params || has_query` → 400 `ValidationError`
170
215
  - `auth.type === 'authenticated'` → 401 `ApiError`
171
216
  - `auth.type === 'role'` → 401 `ApiError` + 403 `PermissionError`
172
217
  - `auth.type === 'keeper'` → 401 `ApiError` + 403 `KeeperError`
173
218
  - `rate_limit` → 429 `RateLimitError`
219
+ - `acting_aware` → widens 400 to a union with `ActorRequiredError` /
220
+ `ActorNotOnAccountError` and adds 500 union of `NoActorsOnAccountError`
221
+ / `AccountVanishedError`. Mirrors what the dispatcher's authorization
222
+ phase actually emits on routes whose input declares `acting?: ActingActor`
223
+ or whose auth requires permits — so DEV-mode error-schema validation in
224
+ `wrap_output_validation` doesn't reject the auth phase's body.
174
225
  2. **Middleware** — from `MiddlewareSpec.errors` that apply to the route's
175
226
  path (via `middleware_applies`)
176
227
  3. **Explicit** — `RouteSpec.errors` — always wins
177
228
 
178
229
  Routes typically only need `errors` for handler-specific codes (404, 409, 422).
179
230
 
231
+ `acting_aware` is computed at the call site (`apply_route_specs` /
232
+ `generate_app_surface`) via the optional `is_acting_aware?: (spec) => boolean`
233
+ callback. Computation lives in the consumer because the canonical
234
+ "input declares `acting?: ActingActor`" check uses reference equality with
235
+ the canonical `ActingActor` Zod schema in `auth/account_schema.ts`, and
236
+ `http/` stays auth-agnostic. fuz_app's `create_app_server` wires
237
+ `(spec) => is_actor_implying_auth(spec.auth) || input_schema_declares_acting(spec.input)`
238
+ — consumers building on raw `apply_route_specs` opt in by passing the
239
+ same predicate (or a narrower one). When the callback is omitted the
240
+ flag defaults to false so frameworks not using fuz_app's auth phase don't
241
+ get fuz_app-specific shapes on their derived surface.
242
+
180
243
  ### `ERROR_*` constants by category
181
244
 
182
245
  - **Validation**: `ERROR_INVALID_REQUEST_BODY`, `ERROR_INVALID_JSON_BODY`,