@fuzdev/fuz_app 0.64.0 → 0.66.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 (223) hide show
  1. package/dist/actions/CLAUDE.md +510 -946
  2. package/dist/actions/action_codegen.d.ts +1 -1
  3. package/dist/actions/action_codegen.js +1 -1
  4. package/dist/actions/action_event_data.d.ts +1 -1
  5. package/dist/actions/broadcast_api.d.ts +1 -1
  6. package/dist/actions/broadcast_api.js +1 -1
  7. package/dist/actions/cancel.d.ts +2 -2
  8. package/dist/actions/cancel.js +3 -3
  9. package/dist/actions/connection_closer.d.ts +1 -4
  10. package/dist/actions/connection_closer.d.ts.map +1 -1
  11. package/dist/actions/connection_closer.js +1 -4
  12. package/dist/actions/register_action_ws.d.ts +2 -2
  13. package/dist/actions/register_ws_endpoint.d.ts +1 -1
  14. package/dist/actions/transports_ws_auth_guard.d.ts +1 -2
  15. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  16. package/dist/actions/transports_ws_auth_guard.js +1 -2
  17. package/dist/auth/CLAUDE.md +570 -1871
  18. package/dist/auth/account_schema.d.ts +1 -1
  19. package/dist/auth/account_schema.d.ts.map +1 -1
  20. package/dist/auth/api_token_queries.js +1 -1
  21. package/dist/auth/audit_log_ddl.d.ts +1 -1
  22. package/dist/auth/audit_log_ddl.d.ts.map +1 -1
  23. package/dist/auth/audit_log_ddl.js +1 -1
  24. package/dist/auth/audit_log_schema.js +2 -2
  25. package/dist/auth/bootstrap_account.d.ts.map +1 -1
  26. package/dist/auth/bootstrap_account.js +1 -5
  27. package/dist/auth/bootstrap_routes.d.ts +7 -1
  28. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  29. package/dist/auth/bootstrap_routes.js +15 -11
  30. package/dist/auth/daemon_token_middleware.d.ts +15 -5
  31. package/dist/auth/daemon_token_middleware.d.ts.map +1 -1
  32. package/dist/auth/daemon_token_middleware.js +24 -15
  33. package/dist/auth/invite_queries.d.ts +17 -7
  34. package/dist/auth/invite_queries.d.ts.map +1 -1
  35. package/dist/auth/invite_queries.js +19 -8
  36. package/dist/auth/keyring.d.ts +6 -6
  37. package/dist/auth/keyring.js +8 -8
  38. package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
  39. package/dist/auth/role_grant_offer_actions.js +4 -2
  40. package/dist/auth/signup_routes.d.ts +47 -1
  41. package/dist/auth/signup_routes.d.ts.map +1 -1
  42. package/dist/auth/signup_routes.js +103 -52
  43. package/dist/db/create_db.d.ts.map +1 -1
  44. package/dist/db/create_db.js +13 -0
  45. package/dist/dev/setup.d.ts +2 -2
  46. package/dist/dev/setup.js +3 -3
  47. package/dist/env/resolve.d.ts +44 -7
  48. package/dist/env/resolve.d.ts.map +1 -1
  49. package/dist/env/resolve.js +94 -27
  50. package/dist/http/CLAUDE.md +243 -522
  51. package/dist/http/error_schemas.d.ts +0 -4
  52. package/dist/http/error_schemas.d.ts.map +1 -1
  53. package/dist/http/error_schemas.js +0 -4
  54. package/dist/http/ip_canonical.d.ts +5 -4
  55. package/dist/http/ip_canonical.d.ts.map +1 -1
  56. package/dist/http/ip_canonical.js +8 -4
  57. package/dist/http/jsonrpc.d.ts +23 -7
  58. package/dist/http/jsonrpc.d.ts.map +1 -1
  59. package/dist/http/jsonrpc.js +19 -3
  60. package/dist/http/origin.d.ts +1 -1
  61. package/dist/http/origin.js +1 -1
  62. package/dist/http/surface.d.ts +9 -2
  63. package/dist/http/surface.d.ts.map +1 -1
  64. package/dist/runtime/mock.d.ts +1 -1
  65. package/dist/runtime/mock.js +2 -2
  66. package/dist/server/app_server.d.ts +41 -10
  67. package/dist/server/app_server.d.ts.map +1 -1
  68. package/dist/server/app_server.js +10 -4
  69. package/dist/server/env.d.ts +7 -7
  70. package/dist/server/env.d.ts.map +1 -1
  71. package/dist/server/env.js +14 -14
  72. package/dist/server/static.d.ts +4 -4
  73. package/dist/server/static.js +7 -7
  74. package/dist/testing/CLAUDE.md +740 -418
  75. package/dist/testing/admin_integration.d.ts +18 -23
  76. package/dist/testing/admin_integration.d.ts.map +1 -1
  77. package/dist/testing/admin_integration.js +230 -216
  78. package/dist/testing/app_server.d.ts +141 -39
  79. package/dist/testing/app_server.d.ts.map +1 -1
  80. package/dist/testing/app_server.js +157 -44
  81. package/dist/testing/audit_completeness.d.ts +25 -22
  82. package/dist/testing/audit_completeness.d.ts.map +1 -1
  83. package/dist/testing/audit_completeness.js +198 -159
  84. package/dist/testing/bootstrap_success.d.ts +28 -0
  85. package/dist/testing/bootstrap_success.d.ts.map +1 -0
  86. package/dist/testing/bootstrap_success.js +144 -0
  87. package/dist/testing/cross_backend/backend_config.d.ts +113 -0
  88. package/dist/testing/cross_backend/backend_config.d.ts.map +1 -0
  89. package/dist/testing/cross_backend/backend_config.js +1 -0
  90. package/dist/testing/cross_backend/bench/bench_report.d.ts +46 -0
  91. package/dist/testing/cross_backend/bench/bench_report.d.ts.map +1 -0
  92. package/dist/testing/cross_backend/bench/bench_report.js +83 -0
  93. package/dist/testing/cross_backend/bench/run_cross_impl_bench.d.ts +44 -0
  94. package/dist/testing/cross_backend/bench/run_cross_impl_bench.d.ts.map +1 -0
  95. package/dist/testing/cross_backend/bench/run_cross_impl_bench.js +38 -0
  96. package/dist/testing/cross_backend/bench/scenario.d.ts +57 -0
  97. package/dist/testing/cross_backend/bench/scenario.d.ts.map +1 -0
  98. package/dist/testing/cross_backend/bench/scenario.js +28 -0
  99. package/dist/testing/cross_backend/bootstrap_backend.d.ts +41 -0
  100. package/dist/testing/cross_backend/bootstrap_backend.d.ts.map +1 -0
  101. package/dist/testing/cross_backend/bootstrap_backend.js +34 -0
  102. package/dist/testing/cross_backend/build_test_backend_paths.d.ts +24 -0
  103. package/dist/testing/cross_backend/build_test_backend_paths.d.ts.map +1 -0
  104. package/dist/testing/cross_backend/build_test_backend_paths.js +33 -0
  105. package/dist/testing/cross_backend/capabilities.d.ts +65 -0
  106. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -0
  107. package/dist/testing/cross_backend/capabilities.js +47 -0
  108. package/dist/testing/cross_backend/default_backend_configs.d.ts +122 -0
  109. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -0
  110. package/dist/testing/cross_backend/default_backend_configs.js +111 -0
  111. package/dist/testing/cross_backend/default_secrets.d.ts +40 -0
  112. package/dist/testing/cross_backend/default_secrets.d.ts.map +1 -0
  113. package/dist/testing/cross_backend/default_secrets.js +39 -0
  114. package/dist/testing/cross_backend/default_spine_surface.d.ts +64 -0
  115. package/dist/testing/cross_backend/default_spine_surface.d.ts.map +1 -0
  116. package/dist/testing/cross_backend/default_spine_surface.js +121 -0
  117. package/dist/testing/cross_backend/setup.d.ts +451 -0
  118. package/dist/testing/cross_backend/setup.d.ts.map +1 -0
  119. package/dist/testing/cross_backend/setup.js +581 -0
  120. package/dist/testing/cross_backend/spawn_backend.d.ts +58 -0
  121. package/dist/testing/cross_backend/spawn_backend.d.ts.map +1 -0
  122. package/dist/testing/cross_backend/spawn_backend.js +229 -0
  123. package/dist/testing/cross_backend/spine_stub_backend_config.d.ts +66 -0
  124. package/dist/testing/cross_backend/spine_stub_backend_config.d.ts.map +1 -0
  125. package/dist/testing/cross_backend/spine_stub_backend_config.js +49 -0
  126. package/dist/testing/cross_backend/sse_round_trip.d.ts +37 -0
  127. package/dist/testing/cross_backend/sse_round_trip.d.ts.map +1 -0
  128. package/dist/testing/cross_backend/sse_round_trip.js +137 -0
  129. package/dist/testing/cross_backend/standard.d.ts +96 -0
  130. package/dist/testing/cross_backend/standard.d.ts.map +1 -0
  131. package/dist/testing/cross_backend/standard.js +49 -0
  132. package/dist/testing/cross_backend/testing_reset_actions.d.ts +171 -0
  133. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -0
  134. package/dist/testing/cross_backend/testing_reset_actions.js +213 -0
  135. package/dist/testing/cross_backend/testing_server_bun.d.ts +5 -0
  136. package/dist/testing/cross_backend/testing_server_bun.d.ts.map +1 -0
  137. package/dist/testing/cross_backend/testing_server_bun.js +59 -0
  138. package/dist/testing/cross_backend/testing_server_core.d.ts +140 -0
  139. package/dist/testing/cross_backend/testing_server_core.d.ts.map +1 -0
  140. package/dist/testing/cross_backend/testing_server_core.js +68 -0
  141. package/dist/testing/cross_backend/testing_server_deno.d.ts +5 -0
  142. package/dist/testing/cross_backend/testing_server_deno.d.ts.map +1 -0
  143. package/dist/testing/cross_backend/testing_server_deno.js +37 -0
  144. package/dist/testing/cross_backend/testing_server_node.d.ts +5 -0
  145. package/dist/testing/cross_backend/testing_server_node.d.ts.map +1 -0
  146. package/dist/testing/cross_backend/testing_server_node.js +50 -0
  147. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts +72 -0
  148. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -0
  149. package/dist/testing/cross_backend/ts_spine_backend_config.js +112 -0
  150. package/dist/testing/cross_backend/ws_round_trip.d.ts +35 -0
  151. package/dist/testing/cross_backend/ws_round_trip.d.ts.map +1 -0
  152. package/dist/testing/cross_backend/ws_round_trip.js +113 -0
  153. package/dist/testing/data_exposure.d.ts +11 -14
  154. package/dist/testing/data_exposure.d.ts.map +1 -1
  155. package/dist/testing/data_exposure.js +123 -146
  156. package/dist/testing/db_entities.d.ts +22 -1
  157. package/dist/testing/db_entities.d.ts.map +1 -1
  158. package/dist/testing/db_entities.js +24 -1
  159. package/dist/testing/integration.d.ts +56 -21
  160. package/dist/testing/integration.d.ts.map +1 -1
  161. package/dist/testing/integration.js +294 -319
  162. package/dist/testing/integration_helpers.d.ts +16 -6
  163. package/dist/testing/integration_helpers.d.ts.map +1 -1
  164. package/dist/testing/integration_helpers.js +7 -7
  165. package/dist/testing/mock_fs.d.ts.map +1 -1
  166. package/dist/testing/mock_fs.js +0 -2
  167. package/dist/testing/rate_limiting.d.ts.map +1 -1
  168. package/dist/testing/rate_limiting.js +9 -0
  169. package/dist/testing/role_grant_helpers.d.ts +31 -0
  170. package/dist/testing/role_grant_helpers.d.ts.map +1 -0
  171. package/dist/testing/role_grant_helpers.js +46 -0
  172. package/dist/testing/round_trip.d.ts +20 -16
  173. package/dist/testing/round_trip.d.ts.map +1 -1
  174. package/dist/testing/round_trip.js +61 -86
  175. package/dist/testing/rpc_helpers.d.ts +10 -4
  176. package/dist/testing/rpc_helpers.d.ts.map +1 -1
  177. package/dist/testing/rpc_helpers.js +1 -1
  178. package/dist/testing/rpc_round_trip.d.ts +24 -21
  179. package/dist/testing/rpc_round_trip.d.ts.map +1 -1
  180. package/dist/testing/rpc_round_trip.js +87 -104
  181. package/dist/testing/schema_introspect.d.ts +106 -0
  182. package/dist/testing/schema_introspect.d.ts.map +1 -0
  183. package/dist/testing/schema_introspect.js +123 -0
  184. package/dist/testing/schema_parity.d.ts +144 -0
  185. package/dist/testing/schema_parity.d.ts.map +1 -0
  186. package/dist/testing/schema_parity.js +233 -0
  187. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  188. package/dist/testing/sse_round_trip.js +1 -68
  189. package/dist/testing/standard.d.ts +56 -25
  190. package/dist/testing/standard.d.ts.map +1 -1
  191. package/dist/testing/standard.js +62 -5
  192. package/dist/testing/stubs.d.ts +21 -6
  193. package/dist/testing/stubs.d.ts.map +1 -1
  194. package/dist/testing/stubs.js +33 -23
  195. package/dist/testing/testing_rate_limiter.d.ts +59 -0
  196. package/dist/testing/testing_rate_limiter.d.ts.map +1 -0
  197. package/dist/testing/testing_rate_limiter.js +74 -0
  198. package/dist/testing/transports/bootstrap.d.ts +52 -0
  199. package/dist/testing/transports/bootstrap.d.ts.map +1 -0
  200. package/dist/testing/transports/bootstrap.js +70 -0
  201. package/dist/testing/transports/fetch_transport.d.ts +81 -0
  202. package/dist/testing/transports/fetch_transport.d.ts.map +1 -0
  203. package/dist/testing/transports/fetch_transport.js +74 -0
  204. package/dist/testing/transports/sse_frame_reader.d.ts +41 -0
  205. package/dist/testing/transports/sse_frame_reader.d.ts.map +1 -0
  206. package/dist/testing/transports/sse_frame_reader.js +84 -0
  207. package/dist/testing/transports/sse_transport.d.ts +54 -0
  208. package/dist/testing/transports/sse_transport.d.ts.map +1 -0
  209. package/dist/testing/transports/sse_transport.js +51 -0
  210. package/dist/testing/transports/ws_client.d.ts +108 -0
  211. package/dist/testing/transports/ws_client.d.ts.map +1 -0
  212. package/dist/testing/transports/ws_client.js +56 -0
  213. package/dist/testing/transports/ws_transport.d.ts +43 -0
  214. package/dist/testing/transports/ws_transport.d.ts.map +1 -0
  215. package/dist/testing/transports/ws_transport.js +169 -0
  216. package/dist/testing/ws_round_trip.d.ts +21 -103
  217. package/dist/testing/ws_round_trip.d.ts.map +1 -1
  218. package/dist/testing/ws_round_trip.js +42 -40
  219. package/dist/ui/CLAUDE.md +5 -3
  220. package/dist/ui/MenuLink.svelte +16 -16
  221. package/dist/ui/MenuLink.svelte.d.ts +13 -4
  222. package/dist/ui/MenuLink.svelte.d.ts.map +1 -1
  223. package/package.json +10 -4
@@ -74,7 +74,7 @@ export interface RoleGrant {
74
74
  expires_at: string | null;
75
75
  revoked_at: string | null;
76
76
  revoked_by: Uuid | null;
77
- /** Optional free-form reason attached on revoke (surfaced in the revokee WS notification once it lands). */
77
+ /** Optional free-form reason attached on revoke (rides on the `role_grant_revoke` WS notification to the revokee). */
78
78
  revoked_reason: string | null;
79
79
  granted_by: Uuid | null;
80
80
  /** Offer that produced this role_grant (set by `query_accept_offer`). `null` for direct grants. */
@@ -1 +1 @@
1
- {"version":3,"file":"account_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_schema.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"account_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/account_schema.ts"],"names":[],"mappings":"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"}
@@ -51,7 +51,7 @@ export const query_validate_api_token = async (deps, raw_token, ip, pending_effe
51
51
  ip ?? null,
52
52
  row.id,
53
53
  ])
54
- .then(() => { }) // eslint-disable-line @typescript-eslint/no-empty-function
54
+ .then(() => { })
55
55
  .catch((err) => {
56
56
  deps.log.error('Failed to update last_used_at:', err);
57
57
  });
@@ -19,6 +19,6 @@
19
19
  *
20
20
  * @module
21
21
  */
22
- export declare const AUDIT_LOG_SCHEMA = "\nCREATE TABLE IF NOT EXISTS audit_log (\n id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n seq SERIAL NOT NULL,\n event_type TEXT NOT NULL,\n outcome TEXT NOT NULL DEFAULT 'success',\n actor_id UUID REFERENCES actor(id) ON DELETE SET NULL,\n account_id UUID REFERENCES account(id) ON DELETE SET NULL,\n target_account_id UUID REFERENCES account(id) ON DELETE SET NULL,\n target_actor_id UUID REFERENCES actor(id) ON DELETE SET NULL,\n ip TEXT,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n metadata JSONB\n)";
22
+ export declare const AUDIT_LOG_SCHEMA = "\nCREATE TABLE IF NOT EXISTS audit_log (\n id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n seq BIGSERIAL NOT NULL,\n event_type TEXT NOT NULL,\n outcome TEXT NOT NULL DEFAULT 'success',\n actor_id UUID REFERENCES actor(id) ON DELETE SET NULL,\n account_id UUID REFERENCES account(id) ON DELETE SET NULL,\n target_account_id UUID REFERENCES account(id) ON DELETE SET NULL,\n target_actor_id UUID REFERENCES actor(id) ON DELETE SET NULL,\n ip TEXT,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n metadata JSONB\n)";
23
23
  export declare const AUDIT_LOG_INDEXES: string[];
24
24
  //# sourceMappingURL=audit_log_ddl.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"audit_log_ddl.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_ddl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,eAAO,MAAM,gBAAgB,ihBAa3B,CAAC;AAEH,eAAO,MAAM,iBAAiB,UAM7B,CAAC"}
1
+ {"version":3,"file":"audit_log_ddl.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_ddl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,eAAO,MAAM,gBAAgB,ohBAa3B,CAAC;AAEH,eAAO,MAAM,iBAAiB,UAM7B,CAAC"}
@@ -22,7 +22,7 @@
22
22
  export const AUDIT_LOG_SCHEMA = `
23
23
  CREATE TABLE IF NOT EXISTS audit_log (
24
24
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
25
- seq SERIAL NOT NULL,
25
+ seq BIGSERIAL NOT NULL,
26
26
  event_type TEXT NOT NULL,
27
27
  outcome TEXT NOT NULL DEFAULT 'success',
28
28
  actor_id UUID REFERENCES actor(id) ON DELETE SET NULL,
@@ -90,13 +90,13 @@ export const audit_metadata_schemas = Object.freeze({
90
90
  signup: z.looseObject({
91
91
  username: z.string().meta({ description: 'Username submitted at signup.' }),
92
92
  invite_id: Uuid.optional().meta({
93
- description: 'Invite consumed by this signup. Set on success and on `race_lost` / `signup_conflict` failure rows when an invite was matched at attempt time.',
93
+ description: 'Invite consumed by this signup. Set on success and on `signup_conflict` failure rows when an invite was matched at attempt time.',
94
94
  }),
95
95
  open_signup: z.boolean().optional().meta({
96
96
  description: 'True when the signup occurred via the `open_signup` setting (no invite required). Set on success rows under `open_signup` and on failure rows when the attempt was made under `open_signup`.',
97
97
  }),
98
98
  reason: z.string().optional().meta({
99
- description: 'Failure category: `no_match` (no unclaimed invite matched), `race_lost` (invite was claimed between find and claim), `signup_conflict` (username/email already exists). Set only on `outcome=failure`.',
99
+ description: 'Failure category: `no_match` (no unclaimed invite matched), `signup_conflict` (username/email already exists, raised by the DB unique constraint), `internal_error` (catch-all for non-classified tx failures — Argon2 fault, session create error, DB outage mid-tx). Set only on `outcome=failure`.',
100
100
  }),
101
101
  email: Email.optional().meta({
102
102
  description: 'Email submitted at signup — recorded on failure rows for forensic correlation. Omitted on success rows because the email is already tied to the resulting account.',
@@ -1 +1 @@
1
- {"version":3,"file":"bootstrap_account.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bootstrap_account.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,eAAe,CAAC;AACpD,OAAO,EACN,mBAAmB,EACnB,0BAA0B,EAC1B,wBAAwB,EACxB,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAGnE,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AAEpC,gDAAgD;AAChD,MAAM,WAAW,qBAAqB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,6DAA6D;AAC7D,MAAM,WAAW,uBAAuB;IACvC,EAAE,EAAE,IAAI,CAAC;IACT,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;IACb,WAAW,EAAE;QAAC,MAAM,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAC,CAAC;IACnD,wFAAwF;IACxF,kBAAkB,EAAE,OAAO,CAAC;CAC5B;AAED,gCAAgC;AAChC,MAAM,MAAM,uBAAuB,GAChC;IAAC,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,OAAO,0BAA0B,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAC,GAClE;IAAC,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,OAAO,wBAAwB,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAC,GAChE;IAAC,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,OAAO,mBAAmB,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAC,CAAC;AAE/D,qFAAqF;AACrF,MAAM,MAAM,sBAAsB,GAAG,uBAAuB,GAAG,uBAAuB,CAAC;AAEvF;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACpC,EAAE,EAAE,EAAE,CAAC;IACP,gDAAgD;IAChD,UAAU,EAAE,MAAM,CAAC;IACnB,0CAA0C;IAC1C,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAClD,qBAAqB;IACrB,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,6EAA6E;IAC7E,QAAQ,EAAE,IAAI,CAAC,gBAAgB,EAAE,eAAe,CAAC,CAAC;IAClD,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;CACZ;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,iBAAiB,GAC7B,MAAM,oBAAoB,EAC1B,gBAAgB,MAAM,EACtB,OAAO,qBAAqB,KAC1B,OAAO,CAAC,sBAAsB,CA4EhC,CAAC"}
1
+ {"version":3,"file":"bootstrap_account.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bootstrap_account.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,eAAe,CAAC;AACpD,OAAO,EACN,mBAAmB,EACnB,0BAA0B,EAC1B,wBAAwB,EACxB,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAGnE,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AAEpC,gDAAgD;AAChD,MAAM,WAAW,qBAAqB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,6DAA6D;AAC7D,MAAM,WAAW,uBAAuB;IACvC,EAAE,EAAE,IAAI,CAAC;IACT,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;IACb,WAAW,EAAE;QAAC,MAAM,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAC,CAAC;IACnD,wFAAwF;IACxF,kBAAkB,EAAE,OAAO,CAAC;CAC5B;AAED,gCAAgC;AAChC,MAAM,MAAM,uBAAuB,GAChC;IAAC,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,OAAO,0BAA0B,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAC,GAClE;IAAC,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,OAAO,wBAAwB,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAC,GAChE;IAAC,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,OAAO,mBAAmB,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAC,CAAC;AAE/D,qFAAqF;AACrF,MAAM,MAAM,sBAAsB,GAAG,uBAAuB,GAAG,uBAAuB,CAAC;AAEvF;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACpC,EAAE,EAAE,EAAE,CAAC;IACP,gDAAgD;IAChD,UAAU,EAAE,MAAM,CAAC;IACnB,0CAA0C;IAC1C,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAClD,qBAAqB;IACrB,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,6EAA6E;IAC7E,QAAQ,EAAE,IAAI,CAAC,gBAAgB,EAAE,eAAe,CAAC,CAAC;IAClD,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;CACZ;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,iBAAiB,GAC7B,MAAM,oBAAoB,EAC1B,gBAAgB,MAAM,EACtB,OAAO,qBAAqB,KAC1B,OAAO,CAAC,sBAAsB,CAuEhC,CAAC"}
@@ -9,7 +9,7 @@
9
9
  import { timingSafeEqual } from 'node:crypto';
10
10
  import { ERROR_INVALID_TOKEN, ERROR_ALREADY_BOOTSTRAPPED, ERROR_TOKEN_FILE_MISSING, } from '../http/error_schemas.js';
11
11
  import { ROLE_ADMIN, ROLE_KEEPER } from './role_schema.js';
12
- import { query_create_account_with_actor, query_account_has_any } from './account_queries.js';
12
+ import { query_create_account_with_actor } from './account_queries.js';
13
13
  import { query_create_role_grant } from './role_grant_queries.js';
14
14
  /**
15
15
  * Bootstrap the first account with keeper and admin privileges.
@@ -57,10 +57,6 @@ export const bootstrap_account = async (deps, provided_token, input) => {
57
57
  if (lock_rows.length === 0) {
58
58
  return { ok: false, error: ERROR_ALREADY_BOOTSTRAPPED, status: 403 };
59
59
  }
60
- // Belt-and-suspenders: verify no accounts exist even if lock was available
61
- if (await query_account_has_any({ db: tx })) {
62
- return { ok: false, error: ERROR_ALREADY_BOOTSTRAPPED, status: 403 };
63
- }
64
60
  const tx_deps = { db: tx };
65
61
  const { account, actor } = await query_create_account_with_actor(tx_deps, {
66
62
  username: input.username,
@@ -26,7 +26,13 @@ export type BootstrapInput = z.infer<typeof BootstrapInput>;
26
26
  /** Output for `POST /bootstrap`. Session cookie is the operative side effect. */
27
27
  export declare const BootstrapOutput: z.ZodObject<{
28
28
  ok: z.ZodLiteral<true>;
29
- username: z.ZodString;
29
+ account: z.ZodObject<{
30
+ id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
31
+ username: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
32
+ }, z.core.$strict>;
33
+ actor: z.ZodObject<{
34
+ id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
35
+ }, z.core.$strict>;
30
36
  }, z.core.$strict>;
31
37
  export type BootstrapOutput = z.infer<typeof BootstrapOutput>;
32
38
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"bootstrap_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bootstrap_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AACtB,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,MAAM,CAAC;AAClC,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,qBAAqB,CAAC;AAExD,OAAO,EAAoB,KAAK,uBAAuB,EAAC,MAAM,wBAAwB,CAAC;AAGvF,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AACpC,OAAO,EAAkB,KAAK,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAEtE,OAAO,EAA+B,KAAK,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAClF,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAChD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,oBAAoB,CAAC;AAYnD,gFAAgF;AAChF,eAAO,MAAM,cAAc;;;;kBAIzB,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,iFAAiF;AACjF,eAAO,MAAM,eAAe;;;kBAG1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IACrC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,8EAA8E;IAC9E,gBAAgB,EAAE,eAAe,CAAC;IAClC;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,uBAAuB,EAAE,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E,4EAA4E;IAC5E,eAAe,EAAE,WAAW,GAAG,IAAI,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACxC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACnD,EAAE,EAAE,EAAE,CAAC;IACP,GAAG,EAAE,MAAM,CAAC;CACZ;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,wBAAwB,EAC9B,SAAS;IAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAAC,KAClC,OAAO,CAAC,eAAe,CAwBzB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B,GACxC,MAAM,gBAAgB,EACtB,SAAS,qBAAqB,KAC5B,KAAK,CAAC,SAAS,CAiHjB,CAAC"}
1
+ {"version":3,"file":"bootstrap_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/bootstrap_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AACtB,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,MAAM,CAAC;AAClC,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAGpD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,qBAAqB,CAAC;AAExD,OAAO,EAAoB,KAAK,uBAAuB,EAAC,MAAM,wBAAwB,CAAC;AAGvF,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AACpC,OAAO,EAAkB,KAAK,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAEtE,OAAO,EAA+B,KAAK,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAClF,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAChD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,oBAAoB,CAAC;AAWnD,gFAAgF;AAChF,eAAO,MAAM,cAAc;;;;kBAIzB,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,iFAAiF;AACjF,eAAO,MAAM,eAAe;;;;;;;;;kBAI1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IACrC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,8EAA8E;IAC9E,gBAAgB,EAAE,eAAe,CAAC;IAClC;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,uBAAuB,EAAE,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E,4EAA4E;IAC5E,eAAe,EAAE,WAAW,GAAG,IAAI,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACxC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACnD,EAAE,EAAE,EAAE,CAAC;IACP,GAAG,EAAE,MAAM,CAAC;CACZ;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,wBAAwB,EAC9B,SAAS;IAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAAC,KAClC,OAAO,CAAC,eAAe,CAwBzB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B,GACxC,MAAM,gBAAgB,EACtB,SAAS,qBAAqB,KAC5B,KAAK,CAAC,SAAS,CAkHjB,CAAC"}
@@ -7,6 +7,7 @@
7
7
  * @module
8
8
  */
9
9
  import { z } from 'zod';
10
+ import { Uuid } from '@fuzdev/fuz_util/id.js';
10
11
  import { create_session_and_set_cookie } from './session_middleware.js';
11
12
  import { bootstrap_account } from './bootstrap_account.js';
12
13
  import { Username } from '../primitive_schemas.js';
@@ -14,7 +15,7 @@ import { Password } from './password.js';
14
15
  import { get_route_input } from '../http/route_spec.js';
15
16
  import { get_client_ip } from '../http/proxy.js';
16
17
  import { rate_limit_exceeded_response } from '../rate_limiter.js';
17
- import { ERROR_BOOTSTRAP_NOT_CONFIGURED, ERROR_INVALID_TOKEN, ERROR_ALREADY_BOOTSTRAPPED, ERROR_TOKEN_FILE_MISSING, ERROR_INVALID_JSON_BODY, ERROR_INVALID_REQUEST_BODY, } from '../http/error_schemas.js';
18
+ import { ERROR_INVALID_TOKEN, ERROR_ALREADY_BOOTSTRAPPED, ERROR_TOKEN_FILE_MISSING, ERROR_INVALID_JSON_BODY, ERROR_INVALID_REQUEST_BODY, } from '../http/error_schemas.js';
18
19
  // -- Input/output schemas ---------------------------------------------------
19
20
  /** Input for `POST /bootstrap`. `token` is the one-shot token file contents. */
20
21
  export const BootstrapInput = z.strictObject({
@@ -25,7 +26,8 @@ export const BootstrapInput = z.strictObject({
25
26
  /** Output for `POST /bootstrap`. Session cookie is the operative side effect. */
26
27
  export const BootstrapOutput = z.strictObject({
27
28
  ok: z.literal(true),
28
- username: z.string(),
29
+ account: z.strictObject({ id: Uuid, username: Username }),
30
+ actor: z.strictObject({ id: Uuid }),
29
31
  });
30
32
  /**
31
33
  * Check bootstrap availability at startup.
@@ -85,13 +87,14 @@ export const create_bootstrap_route_specs = (deps, options) => {
85
87
  }),
86
88
  401: z.looseObject({ error: z.literal(ERROR_INVALID_TOKEN) }),
87
89
  403: z.looseObject({ error: z.literal(ERROR_ALREADY_BOOTSTRAPPED) }),
88
- 404: z.looseObject({
89
- error: z.enum([ERROR_TOKEN_FILE_MISSING, ERROR_BOOTSTRAP_NOT_CONFIGURED]),
90
- }),
90
+ 404: z.looseObject({ error: z.literal(ERROR_TOKEN_FILE_MISSING) }),
91
91
  },
92
92
  handler: async (c, route) => {
93
- // Short-circuit if bootstrap already completed
94
- if (!bootstrap_status.available) {
93
+ // Short-circuit if bootstrap already completed or surface-only mounted.
94
+ // In 'surface_only' mode `bootstrap_status.token_path === null` and
95
+ // `available === false`; in 'live' mode after success `available` flips
96
+ // to `false`. Either way the wire shape is 403 ALREADY_BOOTSTRAPPED.
97
+ if (!bootstrap_status.available || token_path === null) {
95
98
  return c.json({ error: ERROR_ALREADY_BOOTSTRAPPED }, 403);
96
99
  }
97
100
  // Per-IP rate limit check (before any token/DB work)
@@ -103,9 +106,6 @@ export const create_bootstrap_route_specs = (deps, options) => {
103
106
  }
104
107
  }
105
108
  const input = get_route_input(c);
106
- if (token_path === null) {
107
- return c.json({ error: ERROR_BOOTSTRAP_NOT_CONFIGURED }, 404);
108
- }
109
109
  // `transaction: false` makes `route.db` the pool. `bootstrap_account`
110
110
  // manages its own transaction internally.
111
111
  const result = await bootstrap_account({
@@ -158,7 +158,11 @@ export const create_bootstrap_route_specs = (deps, options) => {
158
158
  if (!result.token_file_deleted) {
159
159
  throw new Error(`Bootstrap succeeded but token file was not deleted at ${token_path}. Delete it manually and log in.`);
160
160
  }
161
- return c.json({ ok: true, username: result.account.username });
161
+ return c.json({
162
+ ok: true,
163
+ account: { id: result.account.id, username: result.account.username },
164
+ actor: { id: result.actor.id },
165
+ });
162
166
  },
163
167
  },
164
168
  ];
@@ -34,6 +34,11 @@ export declare const get_daemon_token_path: (runtime: Pick<EnvDeps, "env_get">,
34
34
  *
35
35
  * Uses `write_file_atomic` (temp file + rename) and optionally sets mode 0600.
36
36
  *
37
+ * On-disk format is JSON `{"token": "..."}` — the wrapper leaves room for
38
+ * future fields (rotated_at, version) without changing every reader. Both
39
+ * the TS cross-backend harness reader (`spawn_backend.read_daemon_token`)
40
+ * and the Rust daemon-token writer match this shape.
41
+ *
37
42
  * @param runtime - runtime with file write capabilities
38
43
  * @param token_path - path to write the token
39
44
  * @param token - the raw token string
@@ -58,8 +63,14 @@ export declare const write_daemon_token: (runtime: DaemonTokenWriteDeps, token_p
58
63
  export declare const resolve_keeper_account_id: (deps: QueryDeps) => Promise<string | null>;
59
64
  /** Options for daemon token rotation. */
60
65
  export interface DaemonTokenRotationOptions {
61
- /** Application name (for `~/.{name}/run/daemon_token`). */
62
- app_name: string;
66
+ /**
67
+ * Absolute path the token file is written to. Caller computes from
68
+ * its own conventions — e.g. `get_daemon_token_path(runtime, app_name)`
69
+ * for the standard `~/.{name}/run/daemon_token` layout, or a path
70
+ * derived from `PUBLIC_<APP>_DIR` for cross-process test setups that
71
+ * isolate the app dir to a tmpdir.
72
+ */
73
+ token_path: string;
63
74
  /** Rotation interval in ms. Default: `30000` (30s). */
64
75
  rotation_interval_ms?: number;
65
76
  }
@@ -76,13 +87,12 @@ export interface DaemonTokenRotation {
76
87
  * Generates an initial token, writes it to disk, resolves the keeper account,
77
88
  * and sets up periodic rotation. Returns the mutable state object and a stop function.
78
89
  *
79
- * @param runtime - runtime with file, env, and remove capabilities
90
+ * @param runtime - runtime with file and remove capabilities
80
91
  * @param deps - query dependencies for resolving keeper account
81
92
  * @param options - rotation configuration
82
93
  * @param log - the logger instance
83
94
  * @returns rotation state and stop function
84
95
  * @mutates filesystem - writes the token file on each rotation; `stop` removes it
85
- * @throws Error if `$HOME` is not set so the daemon token path cannot be resolved
86
96
  */
87
97
  export declare const start_daemon_token_rotation: (runtime: DaemonTokenWriteDeps & FsRemoveDeps, deps: QueryDeps, options: DaemonTokenRotationOptions, log: Logger) => Promise<DaemonTokenRotation>;
88
98
  /**
@@ -105,5 +115,5 @@ export declare const start_daemon_token_rotation: (runtime: DaemonTokenWriteDeps
105
115
  * @param state - the daemon token runtime state
106
116
  * @mutates Hono context - sets `ACCOUNT_ID_KEY`, `CREDENTIAL_TYPE_KEY`, and `AUTH_API_TOKEN_ID_KEY` on a valid token
107
117
  */
108
- export declare const create_daemon_token_middleware: (state: DaemonTokenState, _deps: QueryDeps) => MiddlewareHandler;
118
+ export declare const create_daemon_token_middleware: (state: DaemonTokenState, deps: QueryDeps) => MiddlewareHandler;
109
119
  //# sourceMappingURL=daemon_token_middleware.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"daemon_token_middleware.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/daemon_token_middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,MAAM,CAAC;AAC5C,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EAAC,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,KAAK,OAAO,EAAC,MAAM,oBAAoB,CAAC;AASrF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAEnD,OAAO,EAKN,KAAK,gBAAgB,EACrB,MAAM,mBAAmB,CAAC;AAE3B,8DAA8D;AAC9D,eAAO,MAAM,4BAA4B,QAAS,CAAC;AAEnD,iDAAiD;AACjD,MAAM,MAAM,oBAAoB,GAAG,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,GAC1D,IAAI,CAAC,WAAW,EAAE,OAAO,GAAG,iBAAiB,GAAG,QAAQ,CAAC,GAAG;IAC3D,6FAA6F;IAC7F,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACtD,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,GACjC,SAAS,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,EACjC,MAAM,MAAM,KACV,MAAM,GAAG,IAGX,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,GAC9B,SAAS,oBAAoB,EAC7B,YAAY,MAAM,EAClB,OAAO,MAAM,KACX,OAAO,CAAC,IAAI,CAKd,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,yBAAyB,GAAU,MAAM,SAAS,KAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAEtF,CAAC;AAEF,yCAAyC;AACzC,MAAM,WAAW,0BAA0B;IAC1C,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,CAAC;IACjB,uDAAuD;IACvD,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,gDAAgD;AAChD,MAAM,WAAW,mBAAmB;IACnC,2EAA2E;IAC3E,KAAK,EAAE,gBAAgB,CAAC;IACxB,kGAAkG;IAClG,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,2BAA2B,GACvC,SAAS,oBAAoB,GAAG,YAAY,EAC5C,MAAM,SAAS,EACf,SAAS,0BAA0B,EACnC,KAAK,MAAM,KACT,OAAO,CAAC,mBAAmB,CAwD7B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,8BAA8B,GAC1C,OAAO,gBAAgB,EACvB,OAAO,SAAS,KACd,iBA+BF,CAAC"}
1
+ {"version":3,"file":"daemon_token_middleware.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/daemon_token_middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,MAAM,CAAC;AAC5C,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EAAC,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,KAAK,OAAO,EAAC,MAAM,oBAAoB,CAAC;AASrF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAEnD,OAAO,EAKN,KAAK,gBAAgB,EACrB,MAAM,mBAAmB,CAAC;AAE3B,8DAA8D;AAC9D,eAAO,MAAM,4BAA4B,QAAS,CAAC;AAEnD,iDAAiD;AACjD,MAAM,MAAM,oBAAoB,GAAG,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,GAC1D,IAAI,CAAC,WAAW,EAAE,OAAO,GAAG,iBAAiB,GAAG,QAAQ,CAAC,GAAG;IAC3D,6FAA6F;IAC7F,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACtD,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,GACjC,SAAS,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,EACjC,MAAM,MAAM,KACV,MAAM,GAAG,IAGX,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,kBAAkB,GAC9B,SAAS,oBAAoB,EAC7B,YAAY,MAAM,EAClB,OAAO,MAAM,KACX,OAAO,CAAC,IAAI,CAKd,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,yBAAyB,GAAU,MAAM,SAAS,KAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAEtF,CAAC;AAEF,yCAAyC;AACzC,MAAM,WAAW,0BAA0B;IAC1C;;;;;;OAMG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,gDAAgD;AAChD,MAAM,WAAW,mBAAmB;IACnC,2EAA2E;IAC3E,KAAK,EAAE,gBAAgB,CAAC;IACxB,kGAAkG;IAClG,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,2BAA2B,GACvC,SAAS,oBAAoB,GAAG,YAAY,EAC5C,MAAM,SAAS,EACf,SAAS,0BAA0B,EACnC,KAAK,MAAM,KACT,OAAO,CAAC,mBAAmB,CAqD7B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,8BAA8B,GAC1C,OAAO,gBAAgB,EACvB,MAAM,SAAS,KACb,iBAsCF,CAAC"}
@@ -35,13 +35,18 @@ export const get_daemon_token_path = (runtime, name) => {
35
35
  *
36
36
  * Uses `write_file_atomic` (temp file + rename) and optionally sets mode 0600.
37
37
  *
38
+ * On-disk format is JSON `{"token": "..."}` — the wrapper leaves room for
39
+ * future fields (rotated_at, version) without changing every reader. Both
40
+ * the TS cross-backend harness reader (`spawn_backend.read_daemon_token`)
41
+ * and the Rust daemon-token writer match this shape.
42
+ *
38
43
  * @param runtime - runtime with file write capabilities
39
44
  * @param token_path - path to write the token
40
45
  * @param token - the raw token string
41
46
  * @mutates filesystem - writes `token_path` atomically and `chmod 0600` when supported
42
47
  */
43
48
  export const write_daemon_token = async (runtime, token_path, token) => {
44
- await write_file_atomic(runtime, token_path, token + '\n');
49
+ await write_file_atomic(runtime, token_path, JSON.stringify({ token }) + '\n');
45
50
  if (runtime.chmod) {
46
51
  await runtime.chmod(token_path, 0o600);
47
52
  }
@@ -70,26 +75,23 @@ export const resolve_keeper_account_id = async (deps) => {
70
75
  * Generates an initial token, writes it to disk, resolves the keeper account,
71
76
  * and sets up periodic rotation. Returns the mutable state object and a stop function.
72
77
  *
73
- * @param runtime - runtime with file, env, and remove capabilities
78
+ * @param runtime - runtime with file and remove capabilities
74
79
  * @param deps - query dependencies for resolving keeper account
75
80
  * @param options - rotation configuration
76
81
  * @param log - the logger instance
77
82
  * @returns rotation state and stop function
78
83
  * @mutates filesystem - writes the token file on each rotation; `stop` removes it
79
- * @throws Error if `$HOME` is not set so the daemon token path cannot be resolved
80
84
  */
81
85
  export const start_daemon_token_rotation = async (runtime, deps, options, log) => {
82
- const { app_name, rotation_interval_ms = DEFAULT_ROTATION_INTERVAL_MS } = options;
83
- const token_path = get_daemon_token_path(runtime, app_name);
84
- if (!token_path) {
85
- throw new Error('$HOME not set — cannot determine daemon token path');
86
- }
87
- // ensure run directory exists
88
- const app_dir = get_app_dir(runtime, app_name);
89
- if (app_dir) {
90
- await runtime.mkdir(`${app_dir}/run`, { recursive: true });
86
+ const { token_path, rotation_interval_ms = DEFAULT_ROTATION_INTERVAL_MS } = options;
87
+ // ensure parent directory exists
88
+ const last_slash = token_path.lastIndexOf('/');
89
+ if (last_slash > 0) {
90
+ await runtime.mkdir(token_path.slice(0, last_slash), { recursive: true });
91
91
  }
92
- // resolve keeper account (may be null pre-bootstrap)
92
+ // resolve keeper account (may be null pre-bootstrap; the middleware
93
+ // lazily refreshes on the first null hit to cover the
94
+ // rotation-starts-before-bootstrap case)
93
95
  const keeper_account_id = await resolve_keeper_account_id(deps);
94
96
  // generate initial token and write to disk
95
97
  const initial_token = generate_daemon_token();
@@ -150,7 +152,7 @@ export const start_daemon_token_rotation = async (runtime, deps, options, log) =
150
152
  * @param state - the daemon token runtime state
151
153
  * @mutates Hono context - sets `ACCOUNT_ID_KEY`, `CREDENTIAL_TYPE_KEY`, and `AUTH_API_TOKEN_ID_KEY` on a valid token
152
154
  */
153
- export const create_daemon_token_middleware = (state, _deps) => {
155
+ export const create_daemon_token_middleware = (state, deps) => {
154
156
  return async (c, next) => {
155
157
  const token_header = c.req.header(DAEMON_TOKEN_HEADER);
156
158
  if (!token_header) {
@@ -166,7 +168,14 @@ export const create_daemon_token_middleware = (state, _deps) => {
166
168
  if (!validate_daemon_token(parse_result.data, state)) {
167
169
  return c.json({ error: ERROR_INVALID_DAEMON_TOKEN }, 401);
168
170
  }
169
- // daemon token valid — resolve keeper account
171
+ // daemon token valid — resolve keeper account. `start_daemon_token_rotation`
172
+ // resolves the keeper once at startup, but rotation often starts before the
173
+ // keeper account exists (e.g. cross-process test harnesses spawn the binary
174
+ // then POST /bootstrap). Lazily refresh from the DB on the first null hit
175
+ // so the post-bootstrap state lands without a separate hook.
176
+ if (!state.keeper_account_id) {
177
+ state.keeper_account_id = await resolve_keeper_account_id(deps);
178
+ }
170
179
  if (!state.keeper_account_id) {
171
180
  return c.json({ error: ERROR_KEEPER_ACCOUNT_NOT_CONFIGURED }, 503);
172
181
  }
@@ -26,27 +26,37 @@ export declare const query_invite_find_unclaimed_by_email: (deps: QueryDeps, ema
26
26
  */
27
27
  export declare const query_invite_find_unclaimed_by_username: (deps: QueryDeps, username: string) => Promise<Invite | undefined>;
28
28
  /**
29
- * Find an unclaimed invite matching email and/or username using three scoping modes:
29
+ * Find an unclaimed invite matching email and/or username, taking a
30
+ * row-level write lock on the matched row.
31
+ *
32
+ * Three scoping modes:
30
33
  *
31
34
  * - **Email-only invite** (email set, username NULL) → matches only if signup provides matching email.
32
35
  * - **Username-only invite** (username set, email NULL) → matches only if signup provides matching username.
33
36
  * - **Both-field invite** (both set) → requires BOTH email and username to match.
34
37
  *
35
- * @param deps - query dependencies
38
+ * Must run inside the same transaction as `query_invite_claim_unscoped`:
39
+ * `FOR UPDATE` makes find + claim atomic, so a concurrent signup that
40
+ * matched the same invite blocks on the lock until this transaction
41
+ * commits/rolls back. After commit, the loser's `find_for_update`
42
+ * returns no row (the winner flipped `claimed_at`) and falls through to
43
+ * `ERROR_NO_MATCHING_INVITE` — no race window between find and claim.
44
+ *
45
+ * @param deps - query dependencies — `deps.db` MUST be a transaction
36
46
  * @param email - email to match (or null if signup provides none)
37
47
  * @param username - username to match
38
- * @returns the matching invite, or `undefined`
48
+ * @returns the matching invite (locked), or `undefined`
39
49
  */
40
- export declare const query_invite_find_unclaimed_match: (deps: QueryDeps, email: string | null, username: string) => Promise<Invite | undefined>;
50
+ export declare const query_invite_find_unclaimed_match_for_update: (deps: QueryDeps, email: string | null, username: string) => Promise<Invite | undefined>;
41
51
  /**
42
52
  * Claim an invite by setting the claimed_by and claimed_at fields.
43
53
  *
44
54
  * The `_unscoped` suffix is the safety signal — the SQL only checks the
45
55
  * row state (`claimed_at IS NULL`), not whether the claiming account's
46
56
  * email or username matches the invite. Callers must scope the lookup
47
- * upstream via `query_invite_find_unclaimed_match`; the production caller
48
- * (`auth/signup_routes.ts`) does this. Skipping the find step lets a
49
- * caller claim any unclaimed invite by id.
57
+ * upstream via one of the `_find_unclaimed_match*` siblings (production
58
+ * uses `_for_update` to make find + claim atomic). Skipping the find
59
+ * step lets a caller claim any unclaimed invite by id.
50
60
  *
51
61
  * Mirrors the `query_session_revoke_by_hash_unscoped` precedent — there
52
62
  * is no scoped sibling because the scoping is provided by a separate
@@ -1 +1 @@
1
- {"version":3,"file":"invite_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/invite_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAEnD,OAAO,KAAK,EAAC,MAAM,EAAE,iBAAiB,EAAE,uBAAuB,EAAC,MAAM,oBAAoB,CAAC;AAE3F;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,GAC/B,MAAM,SAAS,EACf,OAAO,iBAAiB,KACtB,OAAO,CAAC,MAAM,CAQhB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,oCAAoC,GAChD,MAAM,SAAS,EACf,OAAO,MAAM,KACX,OAAO,CAAC,MAAM,GAAG,SAAS,CAK5B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,uCAAuC,GACnD,MAAM,SAAS,EACf,UAAU,MAAM,KACd,OAAO,CAAC,MAAM,GAAG,SAAS,CAK5B,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iCAAiC,GAC7C,MAAM,SAAS,EACf,OAAO,MAAM,GAAG,IAAI,EACpB,UAAU,MAAM,KACd,OAAO,CAAC,MAAM,GAAG,SAAS,CAe5B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,2BAA2B,GACvC,MAAM,SAAS,EACf,WAAW,MAAM,EACjB,YAAY,MAAM,KAChB,OAAO,CAAC,OAAO,CAQjB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,qBAAqB,GAAU,MAAM,SAAS,KAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAElF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,oCAAoC,GAChD,MAAM,SAAS,KACb,OAAO,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAUxC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,IAAI,MAAM,KACR,OAAO,CAAC,OAAO,CAMjB,CAAC"}
1
+ {"version":3,"file":"invite_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/invite_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAEnD,OAAO,KAAK,EAAC,MAAM,EAAE,iBAAiB,EAAE,uBAAuB,EAAC,MAAM,oBAAoB,CAAC;AAE3F;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,GAC/B,MAAM,SAAS,EACf,OAAO,iBAAiB,KACtB,OAAO,CAAC,MAAM,CAQhB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,oCAAoC,GAChD,MAAM,SAAS,EACf,OAAO,MAAM,KACX,OAAO,CAAC,MAAM,GAAG,SAAS,CAK5B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,uCAAuC,GACnD,MAAM,SAAS,EACf,UAAU,MAAM,KACd,OAAO,CAAC,MAAM,GAAG,SAAS,CAK5B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,4CAA4C,GACxD,MAAM,SAAS,EACf,OAAO,MAAM,GAAG,IAAI,EACpB,UAAU,MAAM,KACd,OAAO,CAAC,MAAM,GAAG,SAAS,CAgB5B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,2BAA2B,GACvC,MAAM,SAAS,EACf,WAAW,MAAM,EACjB,YAAY,MAAM,KAChB,OAAO,CAAC,OAAO,CAQjB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,qBAAqB,GAAU,MAAM,SAAS,KAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAElF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,oCAAoC,GAChD,MAAM,SAAS,KACb,OAAO,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAUxC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,6BAA6B,GACzC,MAAM,SAAS,EACf,IAAI,MAAM,KACR,OAAO,CAAC,OAAO,CAMjB,CAAC"}
@@ -34,18 +34,28 @@ export const query_invite_find_unclaimed_by_username = async (deps, username) =>
34
34
  return deps.db.query_one(`SELECT * FROM invite WHERE LOWER(username) = LOWER($1) AND claimed_at IS NULL`, [username]);
35
35
  };
36
36
  /**
37
- * Find an unclaimed invite matching email and/or username using three scoping modes:
37
+ * Find an unclaimed invite matching email and/or username, taking a
38
+ * row-level write lock on the matched row.
39
+ *
40
+ * Three scoping modes:
38
41
  *
39
42
  * - **Email-only invite** (email set, username NULL) → matches only if signup provides matching email.
40
43
  * - **Username-only invite** (username set, email NULL) → matches only if signup provides matching username.
41
44
  * - **Both-field invite** (both set) → requires BOTH email and username to match.
42
45
  *
43
- * @param deps - query dependencies
46
+ * Must run inside the same transaction as `query_invite_claim_unscoped`:
47
+ * `FOR UPDATE` makes find + claim atomic, so a concurrent signup that
48
+ * matched the same invite blocks on the lock until this transaction
49
+ * commits/rolls back. After commit, the loser's `find_for_update`
50
+ * returns no row (the winner flipped `claimed_at`) and falls through to
51
+ * `ERROR_NO_MATCHING_INVITE` — no race window between find and claim.
52
+ *
53
+ * @param deps - query dependencies — `deps.db` MUST be a transaction
44
54
  * @param email - email to match (or null if signup provides none)
45
55
  * @param username - username to match
46
- * @returns the matching invite, or `undefined`
56
+ * @returns the matching invite (locked), or `undefined`
47
57
  */
48
- export const query_invite_find_unclaimed_match = async (deps, email, username) => {
58
+ export const query_invite_find_unclaimed_match_for_update = async (deps, email, username) => {
49
59
  return deps.db.query_one(`SELECT * FROM invite WHERE claimed_at IS NULL AND (
50
60
  (email IS NOT NULL AND username IS NULL
51
61
  AND $1::text IS NOT NULL AND LOWER(email) = LOWER($1::text))
@@ -56,7 +66,8 @@ export const query_invite_find_unclaimed_match = async (deps, email, username) =
56
66
  (email IS NOT NULL AND username IS NOT NULL
57
67
  AND $1::text IS NOT NULL AND LOWER(email) = LOWER($1::text)
58
68
  AND LOWER(username) = LOWER($2))
59
- ) ORDER BY created_at ASC, id ASC LIMIT 1`, [email, username]);
69
+ ) ORDER BY created_at ASC, id ASC LIMIT 1
70
+ FOR UPDATE`, [email, username]);
60
71
  };
61
72
  /**
62
73
  * Claim an invite by setting the claimed_by and claimed_at fields.
@@ -64,9 +75,9 @@ export const query_invite_find_unclaimed_match = async (deps, email, username) =
64
75
  * The `_unscoped` suffix is the safety signal — the SQL only checks the
65
76
  * row state (`claimed_at IS NULL`), not whether the claiming account's
66
77
  * email or username matches the invite. Callers must scope the lookup
67
- * upstream via `query_invite_find_unclaimed_match`; the production caller
68
- * (`auth/signup_routes.ts`) does this. Skipping the find step lets a
69
- * caller claim any unclaimed invite by id.
78
+ * upstream via one of the `_find_unclaimed_match*` siblings (production
79
+ * uses `_for_update` to make find + claim atomic). Skipping the find
80
+ * step lets a caller claim any unclaimed invite by id.
70
81
  *
71
82
  * Mirrors the `query_session_revoke_by_hash_unscoped` precedent — there
72
83
  * is no scoped sibling because the scoping is provided by a separate
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * @example
9
9
  * ```ts
10
- * const keyring = create_keyring(process.env.SECRET_COOKIE_KEYS);
10
+ * const keyring = create_keyring(process.env.SECRET_FUZ_COOKIE_KEYS);
11
11
  * if (!keyring) throw new Error('No keys configured');
12
12
  *
13
13
  * const signed = await keyring.sign('user:123:1700000000');
@@ -47,12 +47,12 @@ export interface Keyring {
47
47
  *
48
48
  * **Security: key rotation is an operational concern.** Old keys remain valid
49
49
  * for verification indefinitely — a leaked old key can forge session cookies
50
- * until it is removed from `SECRET_COOKIE_KEYS`. After rotating to a new
50
+ * until it is removed from `SECRET_FUZ_COOKIE_KEYS`. After rotating to a new
51
51
  * signing key, remove the old key within a grace period (e.g. 24–48 hours,
52
52
  * long enough for active sessions to re-sign with the new key via cookie
53
- * refresh). Treat `SECRET_COOKIE_KEYS` changes as security-critical deploys.
53
+ * refresh). Treat `SECRET_FUZ_COOKIE_KEYS` changes as security-critical deploys.
54
54
  *
55
- * @param env_value - the SECRET_COOKIE_KEYS environment variable
55
+ * @param env_value - the SECRET_FUZ_COOKIE_KEYS environment variable
56
56
  * @returns keyring or null if no keys configured
57
57
  */
58
58
  export declare const create_keyring: (env_value: string | undefined) => Keyring | null;
@@ -63,7 +63,7 @@ export declare const create_keyring: (env_value: string | undefined) => Keyring
63
63
  * or all-separator input like `'____'`), and for each key shorter than
64
64
  * `MIN_KEY_LENGTH` characters.
65
65
  *
66
- * @param env_value - the SECRET_COOKIE_KEYS environment variable
66
+ * @param env_value - the SECRET_FUZ_COOKIE_KEYS environment variable
67
67
  * @returns array of validation errors (empty if valid)
68
68
  */
69
69
  export declare const validate_keyring: (env_value: string | undefined) => Array<string>;
@@ -84,7 +84,7 @@ export type ValidatedKeyringResult = {
84
84
  * Returns a discriminated union so callers handle exit/logging their own way
85
85
  * (e.g. `Deno.exit(1)` vs `runtime.exit(1)`).
86
86
  *
87
- * @param env_value - the SECRET_COOKIE_KEYS environment variable
87
+ * @param env_value - the SECRET_FUZ_COOKIE_KEYS environment variable
88
88
  * @returns `{ok: true, keyring}` or `{ok: false, errors}`
89
89
  */
90
90
  export declare const create_validated_keyring: (env_value: string | undefined) => ValidatedKeyringResult;
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * @example
9
9
  * ```ts
10
- * const keyring = create_keyring(process.env.SECRET_COOKIE_KEYS);
10
+ * const keyring = create_keyring(process.env.SECRET_FUZ_COOKIE_KEYS);
11
11
  * if (!keyring) throw new Error('No keys configured');
12
12
  *
13
13
  * const signed = await keyring.sign('user:123:1700000000');
@@ -30,12 +30,12 @@ const encoder = new TextEncoder();
30
30
  *
31
31
  * **Security: key rotation is an operational concern.** Old keys remain valid
32
32
  * for verification indefinitely — a leaked old key can forge session cookies
33
- * until it is removed from `SECRET_COOKIE_KEYS`. After rotating to a new
33
+ * until it is removed from `SECRET_FUZ_COOKIE_KEYS`. After rotating to a new
34
34
  * signing key, remove the old key within a grace period (e.g. 24–48 hours,
35
35
  * long enough for active sessions to re-sign with the new key via cookie
36
- * refresh). Treat `SECRET_COOKIE_KEYS` changes as security-critical deploys.
36
+ * refresh). Treat `SECRET_FUZ_COOKIE_KEYS` changes as security-critical deploys.
37
37
  *
38
- * @param env_value - the SECRET_COOKIE_KEYS environment variable
38
+ * @param env_value - the SECRET_FUZ_COOKIE_KEYS environment variable
39
39
  * @returns keyring or null if no keys configured
40
40
  */
41
41
  export const create_keyring = (env_value) => {
@@ -75,13 +75,13 @@ export const create_keyring = (env_value) => {
75
75
  * or all-separator input like `'____'`), and for each key shorter than
76
76
  * `MIN_KEY_LENGTH` characters.
77
77
  *
78
- * @param env_value - the SECRET_COOKIE_KEYS environment variable
78
+ * @param env_value - the SECRET_FUZ_COOKIE_KEYS environment variable
79
79
  * @returns array of validation errors (empty if valid)
80
80
  */
81
81
  export const validate_keyring = (env_value) => {
82
82
  const keys = parse_keys(env_value);
83
83
  if (keys.length === 0) {
84
- return ['SECRET_COOKIE_KEYS is required'];
84
+ return ['SECRET_FUZ_COOKIE_KEYS is required'];
85
85
  }
86
86
  const errors = [];
87
87
  for (const [i, key] of keys.entries()) {
@@ -126,7 +126,7 @@ const verify_with_crypto_key = async (signed_value, key) => {
126
126
  * Returns a discriminated union so callers handle exit/logging their own way
127
127
  * (e.g. `Deno.exit(1)` vs `runtime.exit(1)`).
128
128
  *
129
- * @param env_value - the SECRET_COOKIE_KEYS environment variable
129
+ * @param env_value - the SECRET_FUZ_COOKIE_KEYS environment variable
130
130
  * @returns `{ok: true, keyring}` or `{ok: false, errors}`
131
131
  */
132
132
  export const create_validated_keyring = (env_value) => {
@@ -136,7 +136,7 @@ export const create_validated_keyring = (env_value) => {
136
136
  }
137
137
  const keyring = create_keyring(env_value);
138
138
  if (!keyring) {
139
- return { ok: false, errors: ['SECRET_COOKIE_KEYS is required'] };
139
+ return { ok: false, errors: ['SECRET_FUZ_COOKIE_KEYS is required'] };
140
140
  }
141
141
  return { ok: true, keyring };
142
142
  };
@@ -1 +1 @@
1
- {"version":3,"file":"role_grant_offer_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,OAAO,EAGN,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAIN,KAAK,gBAAgB,EACrB,MAAM,kBAAkB,CAAC;AA0B1B,OAAO,EAA4C,KAAK,cAAc,EAAC,MAAM,sBAAsB,CAAC;AACpG,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAEhD,OAAO,EAON,KAAK,kBAAkB,EACvB,MAAM,qCAAqC,CAAC;AAiC7C;;;;;;;;GAQG;AACH,MAAM,MAAM,6BAA6B,GAAG,CAC3C,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAAC,EACrE,IAAI,EAAE,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,EACnC,GAAG,EAAE,aAAa,KACd,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEhC,qDAAqD;AACrD,MAAM,WAAW,2BAA2B;IAC3C;;;;;OAKG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,0FAA0F;IAC1F,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,6BAA6B,CAAC;CAC1C;AA6BD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,yBAAyB,EAAE,6BAavC,CAAC;AAIF;;;;;;;GAOG;AACH,eAAO,MAAM,+BAA+B,GAC3C,MAAM,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,OAAO,CAAC,GAAG;IAC/C,mBAAmB,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CAChD,EACD,UAAS,2BAAgC,KACvC,KAAK,CAAC,SAAS,CAidjB,CAAC"}
1
+ {"version":3,"file":"role_grant_offer_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/role_grant_offer_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,OAAO,EAGN,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAIN,KAAK,gBAAgB,EACrB,MAAM,kBAAkB,CAAC;AA0B1B,OAAO,EAA4C,KAAK,cAAc,EAAC,MAAM,sBAAsB,CAAC;AACpG,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAEhD,OAAO,EAON,KAAK,kBAAkB,EACvB,MAAM,qCAAqC,CAAC;AAiC7C;;;;;;;;GAQG;AACH,MAAM,MAAM,6BAA6B,GAAG,CAC3C,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAAC,EACrE,IAAI,EAAE,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,EACnC,GAAG,EAAE,aAAa,KACd,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEhC,qDAAqD;AACrD,MAAM,WAAW,2BAA2B;IAC3C;;;;;OAKG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,0FAA0F;IAC1F,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,6BAA6B,CAAC;CAC1C;AA6BD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,yBAAyB,EAAE,6BAavC,CAAC;AAIF;;;;;;;GAOG;AACH,eAAO,MAAM,+BAA+B,GAC3C,MAAM,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,OAAO,CAAC,GAAG;IAC/C,mBAAmB,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CAChD,EACD,UAAS,2BAAgC,KACvC,KAAK,CAAC,SAAS,CAmdjB,CAAC"}
@@ -130,8 +130,10 @@ export const create_role_grant_offer_actions = (deps, options = {}) => {
130
130
  });
131
131
  };
132
132
  // Returns {offer} only — no auto-accept. Recipient must call
133
- // role_grant_offer_accept; admin tests materialize role_grants via
134
- // query_accept_offer (see testing/admin_integration.ts `offer_and_accept`).
133
+ // role_grant_offer_accept; admin tests drive the full consent flow over
134
+ // RPC (see testing/admin_integration.ts `offer_and_accept`), or seed
135
+ // role_grants directly via create_test_role_grant_direct when the
136
+ // test isn't about the consent path.
135
137
  const create_handler = async (input, ctx) => {
136
138
  const auth = ctx.auth;
137
139
  // Role must include the admin grant path — same gate as admin direct-grant.