failproofai 1.0.0 → 1.0.1-beta.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 (238) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +0 -1
  3. package/.next/standalone/.next/build-manifest.json +3 -3
  4. package/.next/standalone/.next/prerender-manifest.json +5 -5
  5. package/.next/standalone/.next/required-server-files.json +1 -1
  6. package/.next/standalone/.next/routes-manifest.json +0 -6
  7. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page.js +4 -3
  9. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  10. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  13. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
  14. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  15. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  16. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page.js +4 -3
  18. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  19. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  20. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  21. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  22. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  23. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
  24. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  25. package/.next/standalone/.next/server/app/api/audit/invite/route.js +2 -2
  26. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/audit/run/route.js +3 -3
  28. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/audit/status/route.js +2 -2
  30. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/auth/login-request/route.js +4 -3
  32. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +2 -2
  34. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/auth/logout/route.js +3 -2
  36. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/api/auth/status/route.js +4 -3
  38. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  41. package/.next/standalone/.next/server/app/audit/page.js +5 -3
  42. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  43. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  44. package/.next/standalone/.next/server/app/index.html +1 -1
  45. package/.next/standalone/.next/server/app/index.rsc +15 -15
  46. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
  47. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  48. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  49. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  50. package/.next/standalone/.next/server/app/page.js +4 -3
  51. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  52. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  53. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +10 -10
  54. package/.next/standalone/.next/server/app/policies/page.js +6 -5
  55. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  56. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  57. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  58. package/.next/standalone/.next/server/app/project/[name]/page.js +5 -4
  59. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  60. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  61. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  62. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  63. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +6 -4
  64. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  65. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  66. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  67. package/.next/standalone/.next/server/app/projects/page.js +5 -4
  68. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  69. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  70. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +4 -4
  71. package/.next/standalone/.next/server/app/settings/page.js +6 -4
  72. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  73. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  74. package/.next/standalone/.next/server/app-paths-manifest.json +0 -1
  75. package/.next/standalone/.next/server/chunks/[root-of-the-server]__05ja_05._.js +3 -0
  76. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0agdw2a._.js +3 -0
  77. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0d1u_na._.js +1 -1
  78. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0nne229._.js +3 -0
  79. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ny9q5d._.js +1 -1
  80. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0oy4kxb._.js +3 -0
  81. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zf034w._.js +3 -0
  82. package/.next/standalone/.next/server/chunks/[root-of-the-server]__17g9wh7._.js +3 -0
  83. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1hstmft._.js +3 -0
  84. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1p8poxd._.js +3 -0
  85. package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +1 -1
  86. package/.next/standalone/.next/server/chunks/_1ek68ln._.js +1 -1
  87. package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +1 -1
  88. package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +1 -1
  89. package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_0jx6cp6._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_0f-x2b0._.js} +1 -1
  90. package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_1f290jp._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_0hwvl7e._.js} +1 -1
  91. package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_1g2ji1t._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_1ctjdzl._.js} +1 -1
  92. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  93. package/.next/standalone/.next/server/chunks/src_hooks_fp-home_ts_09kv0bn._.js +3 -0
  94. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__03eykvd._.js +5 -0
  95. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__07175vb._.js → [root-of-the-server]__07oh8b5._.js} +1 -1
  96. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cxe_2_._.js +1 -1
  97. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0g2rg69._.js +3 -0
  98. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0kgy9da._.js +4 -0
  99. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0m98kzy._.js +4 -0
  100. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0oa1lav._.js +3 -0
  101. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0sjzdjg._.js +3 -0
  102. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0spkm68._.js +2 -2
  103. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0v5_91z._.js +4 -0
  104. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1-a3135._.js +4 -0
  105. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1hvwiw8._.js → [root-of-the-server]__11z_299._.js} +1 -1
  106. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1apc90t._.js → [root-of-the-server]__15c_z97._.js} +1 -1
  107. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1wmno1q._.js → [root-of-the-server]__17d_ffl._.js} +1 -1
  108. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1_dfocn._.js +4 -0
  109. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1e5g_ur._.js +4 -0
  110. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1hmqefd._.js +4 -0
  111. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1jiwfsj._.js +1 -1
  112. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1kzapg5._.js +6 -0
  113. package/.next/standalone/.next/server/chunks/ssr/_04j57fb._.js +3 -0
  114. package/.next/standalone/.next/server/chunks/ssr/_05enntk._.js +3 -0
  115. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +3 -0
  116. package/.next/standalone/.next/server/chunks/ssr/_0hom1s1._.js +3 -0
  117. package/.next/standalone/.next/server/chunks/ssr/_1nq-35m._.js +23 -0
  118. package/.next/standalone/.next/server/chunks/ssr/_1q7j05x._.js +3 -0
  119. package/.next/standalone/.next/server/chunks/ssr/_1ylae7o._.js +3 -0
  120. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +2 -2
  121. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  122. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  123. package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +3 -0
  124. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_1n3w9lb._.js +1 -1
  125. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +1 -1
  126. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +4 -0
  127. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +3 -0
  128. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  129. package/.next/standalone/.next/server/pages/404.html +1 -1
  130. package/.next/standalone/.next/server/pages/500.html +1 -1
  131. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  132. package/.next/standalone/.next/server/server-reference-manifest.json +15 -15
  133. package/.next/standalone/.next/static/chunks/00q6cfzuzpc85.js +1 -0
  134. package/.next/standalone/.next/static/chunks/{0zm3_8jpqitxy.js → 054eg7y_cjyf9.js} +22 -22
  135. package/.next/standalone/.next/static/chunks/{2ouq44jv_0x04.js → 0mjufa_0xxz75.js} +1 -1
  136. package/.next/standalone/.next/static/chunks/0u64yey446k4f.css +1 -0
  137. package/.next/standalone/.next/static/chunks/{0zi8zjav5a4rq.js → 1-oz4co31nvc5.js} +1 -1
  138. package/.next/standalone/.next/static/chunks/13--2yvqek_s1.js +1 -0
  139. package/.next/standalone/.next/static/chunks/136ldjmzxhush.js +1 -0
  140. package/.next/standalone/.next/static/chunks/18fqy3doo2ial.js +6 -0
  141. package/.next/standalone/.next/static/chunks/1eyl7fzkmnkf_.js +1 -0
  142. package/.next/standalone/.next/static/chunks/{31k3yxpy57_fy.js → 1o5sxp_mdaoci.js} +1 -1
  143. package/.next/standalone/.next/static/chunks/29-iqnp9v_axt.css +1 -0
  144. package/.next/standalone/.next/static/chunks/{2676v-lyzgo91.js → 2es4z1ei90vsv.js} +1 -1
  145. package/.next/standalone/.next/static/chunks/2k9f4tyv04809.css +1 -0
  146. package/.next/standalone/.next/static/chunks/3peix2vxzj7eh.js +1 -0
  147. package/.next/standalone/.next/static/chunks/3qfoeqhy5l10x.js +1 -0
  148. package/.next/standalone/.next/static/chunks/{0km-jn9o5xyqs.css → 3wq9x0c-7ld5d.css} +1 -1
  149. package/.next/standalone/app/actions/get-scheduled-audit.ts +65 -6
  150. package/.next/standalone/app/actions/update-scheduled-audit.ts +90 -17
  151. package/.next/standalone/app/api/auth/status/route.ts +7 -17
  152. package/.next/standalone/app/audit/_components/audit-dashboard.tsx +2 -6
  153. package/.next/standalone/app/audit/_components/come-back-better-section.tsx +101 -281
  154. package/.next/standalone/app/audit/_components/empty-state.tsx +2 -2
  155. package/.next/standalone/app/audit/_components/run-progress.tsx +1 -1
  156. package/.next/standalone/app/audit/audit-styles.css +150 -26
  157. package/.next/standalone/app/globals.css +39 -1
  158. package/.next/standalone/app/settings/page.tsx +38 -21
  159. package/.next/standalone/app/settings/settings-client.tsx +583 -392
  160. package/.next/standalone/app/settings/settings.css +354 -0
  161. package/.next/standalone/components/navbar.tsx +33 -4
  162. package/.next/standalone/lib/auth/api-server-client.ts +51 -29
  163. package/.next/standalone/lib/auth/auth-store.ts +44 -59
  164. package/.next/standalone/package.json +12 -10
  165. package/.next/standalone/server.js +1 -1
  166. package/bin/failproofai.mjs +6 -0
  167. package/bin/failproofaid-shim.mjs +0 -0
  168. package/dist/cli.mjs +1889 -426
  169. package/dist/worker.mjs +27 -5
  170. package/lib/auth/api-server-client.ts +51 -29
  171. package/lib/auth/auth-store.ts +44 -59
  172. package/package.json +12 -10
  173. package/scripts/docs-audit.ts +493 -0
  174. package/scripts/translate-docs/cli.ts +16 -2
  175. package/scripts/translate-docs/mdx-translator.ts +6 -1
  176. package/scripts/translate-docs/readme-translator.ts +4 -2
  177. package/src/audit/cli-login.ts +330 -0
  178. package/src/audit/cli.ts +124 -6
  179. package/src/audit/dashboard-cache.ts +44 -8
  180. package/src/audit/harm-report.ts +260 -0
  181. package/src/audit/machine-store.ts +120 -0
  182. package/src/audit/redact-example.ts +312 -0
  183. package/src/audit/report-harm.ts +167 -0
  184. package/src/audit/schedule-cli.ts +352 -0
  185. package/src/hooks/builtin-policies.ts +26 -0
  186. package/src/hooks/daemon-service.ts +76 -0
  187. package/src/hooks/fp-config.ts +96 -5
  188. package/src/hooks/fp-home.ts +118 -8
  189. package/src/hooks/fp-reset.ts +79 -9
  190. package/src/hooks/migrations.ts +241 -4
  191. package/src/hooks/tui.ts +61 -3
  192. package/.next/standalone/.next/server/app/api/auth/reminder/route/app-paths-manifest.json +0 -3
  193. package/.next/standalone/.next/server/app/api/auth/reminder/route/build-manifest.json +0 -12
  194. package/.next/standalone/.next/server/app/api/auth/reminder/route/server-reference-manifest.json +0 -4
  195. package/.next/standalone/.next/server/app/api/auth/reminder/route.js +0 -9
  196. package/.next/standalone/.next/server/app/api/auth/reminder/route.js.map +0 -5
  197. package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +0 -1
  198. package/.next/standalone/.next/server/app/api/auth/reminder/route_client-reference-manifest.js +0 -3
  199. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-xggru._.js +0 -3
  200. package/.next/standalone/.next/server/chunks/[root-of-the-server]__00ds05_._.js +0 -3
  201. package/.next/standalone/.next/server/chunks/[root-of-the-server]__094p1f1._.js +0 -3
  202. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1dxagv_._.js +0 -3
  203. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fgqj1_._.js +0 -3
  204. package/.next/standalone/.next/server/chunks/_084toou._.js +0 -3
  205. package/.next/standalone/.next/server/chunks/_0g4t8c9._.js +0 -3
  206. package/.next/standalone/.next/server/chunks/_0km521a._.js +0 -3
  207. package/.next/standalone/.next/server/chunks/_0t8kefc._.js +0 -3
  208. package/.next/standalone/.next/server/chunks/_16dwkfn._.js +0 -3
  209. package/.next/standalone/.next/server/chunks/_1phm9ui._.js +0 -3
  210. package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_auth_reminder_route_actions_1kjgxf8.js +0 -3
  211. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0dk8r_l._.js +0 -3
  212. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0kviq2m._.js +0 -3
  213. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ogyhu0._.js +0 -3
  214. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1-rfu32._.js +0 -5
  215. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1mmefsr._.js +0 -5
  216. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1t62w37._.js +0 -3
  217. package/.next/standalone/.next/server/chunks/ssr/_032wo7w._.js +0 -3
  218. package/.next/standalone/.next/server/chunks/ssr/_08840em._.js +0 -3
  219. package/.next/standalone/.next/server/chunks/ssr/_0gl2ujp._.js +0 -3
  220. package/.next/standalone/.next/server/chunks/ssr/_0j0ggkc._.js +0 -3
  221. package/.next/standalone/.next/server/chunks/ssr/_0kbv5-q._.js +0 -3
  222. package/.next/standalone/.next/server/chunks/ssr/_0r1jgvp._.js +0 -3
  223. package/.next/standalone/.next/server/chunks/ssr/_0t5xq1b._.js +0 -3
  224. package/.next/standalone/.next/server/chunks/ssr/_0w_be0g._.js +0 -3
  225. package/.next/standalone/.next/server/chunks/ssr/_15gba46._.js +0 -5
  226. package/.next/standalone/.next/server/chunks/ssr/_1qyhb9r._.js +0 -23
  227. package/.next/standalone/.next/server/chunks/ssr/_1vel2ey._.js +0 -3
  228. package/.next/standalone/.next/static/chunks/0wd503tv801gy.js +0 -1
  229. package/.next/standalone/.next/static/chunks/1hfocb0sdl85c.js +0 -1
  230. package/.next/standalone/.next/static/chunks/1v1gqvpjk62jm.js +0 -1
  231. package/.next/standalone/.next/static/chunks/2kopjq_or743g.js +0 -6
  232. package/.next/standalone/.next/static/chunks/2uvhw57mf9lg_.css +0 -2
  233. package/.next/standalone/.next/static/chunks/2ynl0-6aqng_b.js +0 -1
  234. package/.next/standalone/.next/static/chunks/3xvtgz6arztp_.js +0 -1
  235. package/.next/standalone/app/api/auth/reminder/route.ts +0 -213
  236. /package/.next/standalone/.next/static/{Aulh3DRWwRfwh2POCvZR_ → jSKB7CQQSNTX-uSbY2bUs}/_buildManifest.js +0 -0
  237. /package/.next/standalone/.next/static/{Aulh3DRWwRfwh2POCvZR_ → jSKB7CQQSNTX-uSbY2bUs}/_clientMiddlewareManifest.js +0 -0
  238. /package/.next/standalone/.next/static/{Aulh3DRWwRfwh2POCvZR_ → jSKB7CQQSNTX-uSbY2bUs}/_ssgManifest.js +0 -0
@@ -0,0 +1,354 @@
1
+ /* /settings — a console for the service running on this machine.
2
+ *
3
+ * Every colour is an existing token from globals.css. No new hue, no third
4
+ * accent: pink is the control that acts and anything that needs a person, mint
5
+ * is the thing that is alive, everything else is the charcoal stack and the ink
6
+ * ramp. The chunky pixel shadow comes from `.btn-press`, which already exists —
7
+ * this file puts one on the console and nothing else.
8
+ *
9
+ * ## The hairline grid
10
+ *
11
+ * Every rule between cells is a `gap: 1px` over a line-coloured background, not
12
+ * a border on each cell. Borders double where two cells meet and go missing at
13
+ * the edges; a gap over a background cannot do either, so the grid is exactly
14
+ * one pixel everywhere by construction rather than by arithmetic.
15
+ *
16
+ * The tape is the only drawn element on the page. That is the budget.
17
+ */
18
+
19
+ .set-sec {
20
+ max-width: 940px;
21
+ }
22
+
23
+ /* ── the console: stat row over panel row, one hairline grid ──────────────── */
24
+
25
+ .set-console {
26
+ display: flex;
27
+ flex-direction: column;
28
+ /* The gap IS the grid — see the note above. */
29
+ gap: 1px;
30
+ background: var(--line);
31
+ border: 1px solid var(--line);
32
+ /* Print-register depth, matching `.btn-press`. Hard offset, never a blur. */
33
+ box-shadow: 6px 6px 0 0 var(--accent-pink-shadow);
34
+ }
35
+
36
+ .set-cell {
37
+ background: var(--bg-2);
38
+ padding: 20px 22px;
39
+ }
40
+
41
+ .set-stats {
42
+ display: grid;
43
+ grid-template-columns: repeat(4, 1fr);
44
+ gap: 1px;
45
+ background: var(--line);
46
+ }
47
+
48
+ .set-stat {
49
+ background: var(--bg-2);
50
+ padding: 16px 18px 15px;
51
+ display: flex;
52
+ flex-direction: column;
53
+ gap: 7px;
54
+ min-width: 0;
55
+ }
56
+ .set-stat-label {
57
+ font-family: var(--font-mono);
58
+ font-size: 9.5px;
59
+ font-weight: 500;
60
+ letter-spacing: 0.18em;
61
+ text-transform: uppercase;
62
+ color: var(--dim);
63
+ }
64
+ .set-stat-value {
65
+ /* The pixel display face, on the page title and these four numbers only. */
66
+ font-family: var(--font-display);
67
+ font-size: 26px;
68
+ line-height: 1;
69
+ letter-spacing: 0.04em;
70
+ color: var(--ink);
71
+ text-transform: lowercase;
72
+ /* Four cells of digits should line up as a row, not drift per glyph width. */
73
+ font-variant-numeric: tabular-nums;
74
+ overflow-wrap: anywhere;
75
+ }
76
+ .set-stat-value.ok { color: var(--accent-green); }
77
+ .set-stat-value.warn { color: var(--accent-pink); }
78
+ .set-stat-sub {
79
+ font-family: var(--font-mono);
80
+ font-size: 10.5px;
81
+ line-height: 1.5;
82
+ color: var(--ink-2);
83
+ /* Reserve the line even when empty, so cells with and without a sub-line
84
+ * keep the same height and the row's baselines stay level. */
85
+ min-height: 1.5em;
86
+ }
87
+
88
+ /* ── panel row: controls, then what the scan does ─────────────────────────── */
89
+
90
+ .set-cols {
91
+ display: grid;
92
+ /* ~56/44 — the controls carry more, so they get more. */
93
+ grid-template-columns: 1.28fr 1fr;
94
+ gap: 1px;
95
+ background: var(--line);
96
+ }
97
+
98
+ .set-config {
99
+ display: flex;
100
+ flex-direction: column;
101
+ gap: 16px;
102
+ }
103
+
104
+ .set-cell-title {
105
+ font-family: var(--font-mono);
106
+ font-size: 10px;
107
+ font-weight: 600;
108
+ letter-spacing: 0.18em;
109
+ text-transform: uppercase;
110
+ color: var(--ink-2);
111
+ margin: 0;
112
+ }
113
+
114
+ .set-actions {
115
+ margin-top: auto;
116
+ padding-top: 4px;
117
+ }
118
+
119
+ /* ── how it works ─────────────────────────────────────────────────────────── */
120
+
121
+ .set-how {
122
+ display: flex;
123
+ flex-direction: column;
124
+ gap: 16px;
125
+ }
126
+ .set-how-list {
127
+ display: flex;
128
+ flex-direction: column;
129
+ gap: 14px;
130
+ margin: 0;
131
+ }
132
+ .set-how-row {
133
+ display: grid;
134
+ grid-template-columns: 58px 1fr;
135
+ gap: 12px;
136
+ align-items: baseline;
137
+ }
138
+ .set-how-label {
139
+ font-family: var(--font-mono);
140
+ font-size: 9.5px;
141
+ font-weight: 500;
142
+ letter-spacing: 0.16em;
143
+ text-transform: uppercase;
144
+ color: var(--accent-pink);
145
+ }
146
+ .set-how-body {
147
+ font-family: var(--font-mono);
148
+ font-size: 11.5px;
149
+ line-height: 1.65;
150
+ color: var(--ink-2);
151
+ margin: 0;
152
+ }
153
+
154
+ /* ── masthead ─────────────────────────────────────────────────────────────── */
155
+
156
+ .set-mast {
157
+ margin-bottom: 28px;
158
+ }
159
+ .set-title {
160
+ /* The pixel display face, used once on the page and nowhere else. */
161
+ font-family: var(--font-display);
162
+ font-size: 40px;
163
+ line-height: 1;
164
+ letter-spacing: 0.06em;
165
+ color: var(--ink);
166
+ margin: 0 0 10px;
167
+ text-transform: lowercase;
168
+ }
169
+ .set-lede {
170
+ font-family: var(--font-mono);
171
+ font-size: 13px;
172
+ line-height: 1.6;
173
+ color: var(--ink-2);
174
+ margin: 0;
175
+ /* Wide enough to hold the sentence on one line at desktop width — 52ch broke
176
+ * it after "not", which reads as a typo rather than a line. */
177
+ max-width: 64ch;
178
+ }
179
+
180
+ /* ── the panel ────────────────────────────────────────────────────────────── */
181
+
182
+ /* ── rows ─────────────────────────────────────────────────────────────────── */
183
+
184
+ .set-row {
185
+ display: flex;
186
+ align-items: center;
187
+ gap: 12px;
188
+ font-family: var(--font-mono);
189
+ font-size: 13px;
190
+ color: var(--ink);
191
+ }
192
+ .set-row-main {
193
+ align-items: flex-start;
194
+ }
195
+ .set-row-copy {
196
+ display: flex;
197
+ flex-direction: column;
198
+ gap: 3px;
199
+ line-height: 1.5;
200
+ }
201
+ .set-row-interval {
202
+ gap: 10px;
203
+ flex-wrap: wrap;
204
+ }
205
+ .set-row-identity {
206
+ flex-wrap: wrap;
207
+ gap: 8px;
208
+ font-size: 12px;
209
+ }
210
+
211
+ .set-strong { color: var(--ink); }
212
+ .set-dim { color: var(--ink-2); }
213
+ .set-hint { color: var(--dim); font-size: 11px; }
214
+ .set-email { color: var(--accent-green); }
215
+
216
+ .set-num {
217
+ font-family: var(--font-mono);
218
+ font-size: 13px;
219
+ width: 62px;
220
+ padding: 6px 10px;
221
+ background: var(--bg);
222
+ border: 1px solid var(--line-2);
223
+ color: var(--ink);
224
+ border-radius: 0;
225
+ }
226
+ .set-num:focus-visible {
227
+ outline: 2px solid var(--accent-pink);
228
+ outline-offset: 1px;
229
+ }
230
+
231
+ .set-link {
232
+ font-family: var(--font-mono);
233
+ font-size: 12px;
234
+ color: var(--dim);
235
+ background: none;
236
+ border: none;
237
+ padding: 0;
238
+ text-decoration: underline;
239
+ text-underline-offset: 3px;
240
+ }
241
+ .set-link:hover:not(:disabled) { color: var(--ink); }
242
+ .set-link:disabled { opacity: 0.5; cursor: not-allowed; }
243
+ .set-link:focus-visible { outline: 2px solid var(--accent-pink); outline-offset: 2px; }
244
+
245
+ .set-warn {
246
+ font-family: var(--font-mono);
247
+ font-size: 12px;
248
+ line-height: 1.6;
249
+ color: var(--accent-pink);
250
+ margin: 0;
251
+ }
252
+ .set-warn-inline { color: var(--accent-pink); }
253
+
254
+ .set-rule {
255
+ height: 1px;
256
+ background: var(--line);
257
+ margin: 2px 0;
258
+ }
259
+
260
+ /* ── the switch ───────────────────────────────────────────────────────────── */
261
+
262
+ .sw {
263
+ position: relative;
264
+ flex: none;
265
+ width: 38px;
266
+ height: 20px;
267
+ border-radius: 10px;
268
+ border: none;
269
+ background: var(--line-2);
270
+ cursor: pointer;
271
+ padding: 0;
272
+ transition: background-color 140ms cubic-bezier(0.22, 1, 0.36, 1);
273
+ }
274
+ .sw[data-on="true"] { background: var(--accent-pink); }
275
+ .sw:disabled { opacity: 0.5; cursor: not-allowed; }
276
+ .sw:focus-visible { outline: 2px solid var(--accent-pink); outline-offset: 3px; }
277
+ .sw-knob {
278
+ position: absolute;
279
+ top: 3px;
280
+ left: 3px;
281
+ width: 14px;
282
+ height: 14px;
283
+ border-radius: 50%;
284
+ background: var(--bg);
285
+ transition: transform 140ms cubic-bezier(0.22, 1, 0.36, 1);
286
+ }
287
+ .sw[data-on="true"] .sw-knob { transform: translateX(18px); }
288
+
289
+ /* ── the schedule tape — the one drawn element ────────────────────────────── */
290
+
291
+ .tape {
292
+ display: flex;
293
+ flex-direction: column;
294
+ gap: 8px;
295
+ /* Clears the console's 6px pixel shadow rather than sitting on it. */
296
+ margin-top: 26px;
297
+ }
298
+ .tape-rail {
299
+ position: relative;
300
+ height: 3px;
301
+ background: var(--line-2);
302
+ }
303
+ .tape-fill {
304
+ position: absolute;
305
+ inset: 0 auto 0 0;
306
+ background: var(--accent-green);
307
+ opacity: 0.55;
308
+ }
309
+ .tape-now {
310
+ position: absolute;
311
+ top: -4px;
312
+ width: 2px;
313
+ height: 11px;
314
+ background: var(--accent-pink);
315
+ /* Centre the marker on its position rather than hanging it to the right. */
316
+ transform: translateX(-1px);
317
+ }
318
+ .tape-ends {
319
+ display: flex;
320
+ justify-content: space-between;
321
+ gap: 12px;
322
+ font-family: var(--font-mono);
323
+ font-size: 10.5px;
324
+ letter-spacing: 0.04em;
325
+ color: var(--dim);
326
+ }
327
+ .tape-next { color: var(--ink-2); }
328
+
329
+ /* ── narrow ───────────────────────────────────────────────────────────────── */
330
+
331
+ /* The stat row folds 4 → 2 before it folds 2 → 1: four cells at phone width
332
+ * would each be too narrow for a display-face number, and a single column
333
+ * makes four facts into a list you scroll rather than a row you scan. */
334
+ @media (max-width: 860px) {
335
+ .set-stats { grid-template-columns: repeat(2, 1fr); }
336
+ .set-cols { grid-template-columns: 1fr; }
337
+ }
338
+
339
+ @media (max-width: 640px) {
340
+ .set-title { font-size: 32px; }
341
+ .set-console { box-shadow: 4px 4px 0 0 var(--accent-pink-shadow); }
342
+ .set-stat-value { font-size: 22px; }
343
+ .set-cell { padding: 18px; }
344
+ }
345
+
346
+ @media (max-width: 420px) {
347
+ .set-stats { grid-template-columns: 1fr; }
348
+ .set-how-row { grid-template-columns: 1fr; gap: 4px; }
349
+ }
350
+
351
+ @media (prefers-reduced-motion: reduce) {
352
+ .sw,
353
+ .sw-knob { transition: none; }
354
+ }
@@ -1,16 +1,22 @@
1
- /** Top navigation bar — wordmark, primary nav, refresh + reach-developers controls.
1
+ /** Top navigation bar — wordmark, primary nav, refresh + settings + reach-us.
2
2
  *
3
3
  * Restyled to the audit / brutalist-pixel-craft system: the wordmark uses the
4
4
  * same pixel pink mark + Bitcount Prop Single lowercase name as the audit
5
5
  * report, and each nav link is a `.tab` with a sharp pink underline on the
6
- * active route. No lucide icons in the bar itself — the chrome stays text-
7
- * forward to match the rest of the design system.
6
+ * active route.
7
+ *
8
+ * The bar was text-forward with no icons. Settings is the one exception, and
9
+ * deliberately so: the tabs are VIEWS OF DATA (projects, policies, audit) and
10
+ * settings is machine configuration, so putting it in that row would have
11
+ * claimed it was another place to look at results. As an icon among the other
12
+ * chrome controls it reads as what it is.
8
13
  */
9
14
  "use client";
10
15
 
11
16
  import React from "react";
12
17
  import Link from "next/link";
13
18
  import { usePathname } from "next/navigation";
19
+ import { Settings } from "lucide-react";
14
20
  import { ReachDevelopers } from "@/components/reach-developers";
15
21
  import { RefreshButton } from "@/app/components/refresh-button";
16
22
  import { usePostHog } from "@/contexts/PostHogContext";
@@ -19,7 +25,6 @@ const NAV_LINKS = [
19
25
  { href: "/projects", label: "projects" },
20
26
  { href: "/policies", label: "policies" },
21
27
  { href: "/audit", label: "audit" },
22
- { href: "/settings", label: "settings" },
23
28
  ];
24
29
 
25
30
  const REMOTE_LOGO_URL =
@@ -131,6 +136,30 @@ export const Navbar: React.FC<{
131
136
  <div className="h-actions">
132
137
  <RefreshButton />
133
138
  <span style={{ width: 1, height: 18, background: "var(--line)", margin: "0 6px" }} />
139
+ {/* Settings sits between the refresh controls and reach-us as an ICON,
140
+ not a nav tab: it is machine configuration rather than a view of the
141
+ data, so it belongs with the other chrome controls rather than
142
+ beside projects / policies / audit.
143
+
144
+ Being chrome rather than a tab is what put it OUTSIDE the filter
145
+ above, so `FAILPROOFAI_DISABLE_PAGES=settings` hid nothing and the
146
+ gear kept pointing at a page the operator had turned off. The tabs
147
+ and the icon are different placements of the same idea, and the
148
+ filter applies to both. */}
149
+ {!disabledPages.includes("settings") && (
150
+ <>
151
+ <Link
152
+ href="/settings"
153
+ className={`h-icon-btn${pathname.startsWith("/settings") ? " is-active" : ""}`}
154
+ aria-label="settings"
155
+ aria-current={pathname.startsWith("/settings") ? "page" : undefined}
156
+ title="settings"
157
+ >
158
+ <Settings className="w-3.5 h-3.5" strokeWidth={1.75} aria-hidden="true" />
159
+ </Link>
160
+ <span style={{ width: 1, height: 18, background: "var(--line)", margin: "0 6px" }} />
161
+ </>
162
+ )}
134
163
  <ReachDevelopers />
135
164
  </div>
136
165
  </header>
@@ -107,7 +107,7 @@ async function parseError(res: Response): Promise<AuthApiError> {
107
107
  return new AuthApiError(res.status, code, message, retryAfterSecs);
108
108
  }
109
109
 
110
- /** Hard cap on every auth/reminder HTTP call. Without this, a wedged DNS
110
+ /** Hard cap on every auth/report HTTP call. Without this, a wedged DNS
111
111
  * resolver or a hung server keeps the CLI / dashboard route stuck forever. */
112
112
  const REQUEST_TIMEOUT_MS = 10_000;
113
113
 
@@ -210,34 +210,6 @@ export async function fetchMe(accessToken: string): Promise<MeResponse> {
210
210
  return getJson<MeResponse>("/v0/auth/me", accessToken);
211
211
  }
212
212
 
213
- export interface ServerReminder {
214
- user_id: string;
215
- email: string;
216
- fire_at: number; // unix seconds
217
- set_at: number; // unix seconds
218
- }
219
-
220
- export async function scheduleReminder(
221
- accessToken: string,
222
- body: { in_days?: number; at?: number },
223
- ): Promise<ServerReminder> {
224
- const res = await postJson<{ reminder: ServerReminder }>(
225
- "/v0/reminders",
226
- body,
227
- { accessToken },
228
- );
229
- return res.reminder;
230
- }
231
-
232
- export async function cancelReminder(accessToken: string): Promise<void> {
233
- const res = await fetchWithTimeout(`${getApiBase()}/v0/reminders`, {
234
- method: "DELETE",
235
- headers: { authorization: `Bearer ${accessToken}` },
236
- });
237
- if (res.status === 204 || res.ok) return;
238
- throw await parseError(res);
239
- }
240
-
241
213
  export interface InviteSendResult {
242
214
  /** Recipients that were dispatched successfully. */
243
215
  sent: string[];
@@ -266,6 +238,56 @@ export async function sendInvites(
266
238
  );
267
239
  }
268
240
 
241
+ export interface AuditReportBody {
242
+ machine_id: string;
243
+ label?: string;
244
+ platform?: string;
245
+ window_from?: string;
246
+ window_to: string;
247
+ harmful: {
248
+ policy: string;
249
+ category: string;
250
+ title: string;
251
+ hits: number;
252
+ first_seen?: string;
253
+ last_seen?: string;
254
+ examples: string[];
255
+ }[];
256
+ }
257
+
258
+ export interface AuditReportResult {
259
+ report_id: string;
260
+ /** Whether this report produced an email. */
261
+ emailed: boolean;
262
+ /** `below_threshold`, `cooldown`, `send_failed`, or null when mail went out. */
263
+ reason: string | null;
264
+ /**
265
+ * Where the next window starts, per the SERVER.
266
+ *
267
+ * Persisted verbatim rather than computed locally. The server anchors it on
268
+ * the last DELIVERED digest, so a report held by the cooldown — or one whose
269
+ * send failed — correctly leaves the watermark where it was, and its findings
270
+ * turn up in the next digest instead of falling into a gap. A machine that
271
+ * lost `machine.json` also resyncs here rather than re-reporting from the
272
+ * beginning of time.
273
+ */
274
+ next_window_from: string;
275
+ }
276
+
277
+ /**
278
+ * Submit one scheduled scan's harmful findings.
279
+ *
280
+ * Called only by the audit child, and only on `--scheduled`. The destination
281
+ * address is never sent: the api-server takes it from the access-token claims,
282
+ * so a report cannot name where its digest goes.
283
+ */
284
+ export async function submitAuditReport(
285
+ accessToken: string,
286
+ body: AuditReportBody,
287
+ ): Promise<AuditReportResult> {
288
+ return postJson<AuditReportResult>("/v0/audit-reports", body, { accessToken });
289
+ }
290
+
269
291
  interface JwtClaims {
270
292
  sub: string;
271
293
  email: string;
@@ -1,16 +1,17 @@
1
1
  /**
2
- * Persistence layer for the FailproofAI auth.json file.
2
+ * Persistence layer for the signed-in session.
3
3
  *
4
- * Tokens live at ~/.failproofai/auth.json with mode 0600. The dashboard's
5
- * Next.js API routes read and write through here, so a session survives across
6
- * dashboard runs.
4
+ * Tokens live at `~/.failproofai/audit/session.json` with mode 0600 (layout 4;
5
+ * `auth.json` at the home root before that). The dashboard's Next.js API routes
6
+ * and the audit child both read and write through here, so a session survives
7
+ * across dashboard runs and is the same one a scheduled report uses.
7
8
  */
8
9
 
9
- import { existsSync, readFileSync, rmSync } from "node:fs";
10
- import { join } from "node:path";
10
+ import { existsSync, readFileSync, readdirSync, rmSync } from "node:fs";
11
+ import { join, resolve } from "node:path";
11
12
 
12
13
  import { writeJsonAtomically } from "../atomic-write";
13
- import { failproofaiHome } from "../../src/hooks/fp-home";
14
+ import { auditDir, auditSessionFile, migrationsDir } from "../../src/hooks/fp-home";
14
15
  import {
15
16
  AuthApiError,
16
17
  decodeJwt,
@@ -27,62 +28,24 @@ export interface StoredAuth {
27
28
  user: { id: string; email: string };
28
29
  }
29
30
 
31
+ /**
32
+ * Where the session file lives.
33
+ *
34
+ * `FAILPROOFAI_AUTH_DIR` overrides it OUTRIGHT — the override names the
35
+ * directory the two files sit in directly, with no `audit/` beneath it, which is
36
+ * the contract it has always had and what every test using it expects. Without
37
+ * the override the paths come from `fp-home.ts`, which as of layout 4 puts them
38
+ * under `audit/` with the rest of what the audit owns.
39
+ */
30
40
  export function getAuthDir(): string {
31
41
  const override = process.env.FAILPROOFAI_AUTH_DIR;
32
42
  if (override) return override;
33
- return failproofaiHome();
43
+ return auditDir();
34
44
  }
35
45
 
36
46
  export function getAuthFilePath(): string {
37
- return join(getAuthDir(), "auth.json");
38
- }
39
-
40
- /** Location of the persisted re-audit reminder (separate from auth.json so
41
- * the reminder survives unrelated session refreshes). */
42
- export function getReminderFilePath(): string {
43
- return join(getAuthDir(), "next-audit.json");
44
- }
45
-
46
- export interface StoredReminder {
47
- /** Unix seconds. */
48
- next_audit_at: number;
49
- /** Email the reminder was set for. Used to invalidate the reminder if the
50
- * active session belongs to a different user. */
51
- user_email: string;
52
- /** Unix seconds. */
53
- set_at: number;
54
- }
55
-
56
- export function readReminder(): StoredReminder | null {
57
- const p = getReminderFilePath();
58
- if (!existsSync(p)) return null;
59
- try {
60
- const raw = readFileSync(p, "utf-8");
61
- const parsed = JSON.parse(raw) as Partial<StoredReminder>;
62
- if (
63
- typeof parsed.next_audit_at !== "number" ||
64
- typeof parsed.user_email !== "string" ||
65
- typeof parsed.set_at !== "number"
66
- ) {
67
- return null;
68
- }
69
- return {
70
- next_audit_at: parsed.next_audit_at,
71
- user_email: parsed.user_email,
72
- set_at: parsed.set_at,
73
- };
74
- } catch {
75
- return null;
76
- }
77
- }
78
-
79
- export function writeReminder(reminder: StoredReminder): void {
80
- writeJsonAtomically(getReminderFilePath(), reminder);
81
- }
82
-
83
- export function deleteReminder(): void {
84
- const p = getReminderFilePath();
85
- if (existsSync(p)) rmSync(p, { force: true });
47
+ const override = process.env.FAILPROOFAI_AUTH_DIR;
48
+ return override ? join(override, "session.json") : auditSessionFile();
86
49
  }
87
50
 
88
51
  export function readAuth(): StoredAuth | null {
@@ -127,6 +90,28 @@ export function writeAuth(auth: StoredAuth): void {
127
90
  export function deleteAuth(): void {
128
91
  const p = getAuthFilePath();
129
92
  if (existsSync(p)) rmSync(p, { force: true });
93
+
94
+ // Also any copy a migration took and could not clean up.
95
+ //
96
+ // The layout-4 step backs `auth.json` up before moving it, and a chain that
97
+ // FAILED deliberately keeps that copy — it is the only insurance against a
98
+ // half-moved credential. But "sign me out" has to mean the token is off this
99
+ // machine, and `migrationsDir` is classed `identity`, so nothing else will
100
+ // ever remove it: without this sweep a dashboard sign-out, a 401 auto-delete
101
+ // and `failproofai reset` all left a live bearer and refresh token on disk,
102
+ // to be carried into every dotfile backup and container image after it.
103
+ try {
104
+ const root = migrationsDir();
105
+ if (!existsSync(root)) return;
106
+ for (const entry of readdirSync(root)) {
107
+ if (!entry.startsWith("backup-layout")) continue;
108
+ const copy = resolve(root, entry, "auth.json");
109
+ if (existsSync(copy)) rmSync(copy, { force: true });
110
+ }
111
+ } catch {
112
+ // Sign-out must succeed even if the sweep cannot. The live token — the one
113
+ // that actually authenticates a request — is already gone above.
114
+ }
130
115
  }
131
116
 
132
117
  /** Convert verify/refresh response into the on-disk shape. */
@@ -161,8 +146,8 @@ const REFRESH_LEEWAY_SECS = 60;
161
146
 
162
147
  /**
163
148
  * In-flight refresh dedup. Without this, two concurrent callers (e.g.
164
- * the dashboard's `/api/auth/status` poll and a `/api/auth/reminder`
165
- * POST in flight) both observe the same expired access token, both call
149
+ * the dashboard's `/api/auth/status` poll and a scheduled audit's report
150
+ * in flight) both observe the same expired access token, both call
166
151
  * `refreshAccessToken(auth.refresh_token)` with the same refresh token,
167
152
  * and the api-server treats the second call as token-replay and revokes
168
153
  * every session for that user — a silent logout. Keying on the refresh