failproofai 1.0.0-beta.14 → 1.0.0-beta.16

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 (190) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +5 -5
  3. package/.next/standalone/.next/prerender-manifest.json +3 -3
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +2 -2
  6. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page.js +2 -1
  8. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  9. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  12. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  13. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  14. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
  15. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  16. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +2 -2
  18. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  19. package/.next/standalone/.next/server/app/_not-found/page.js +2 -1
  20. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  21. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  22. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  23. package/.next/standalone/.next/server/app/_not-found.rsc +14 -14
  24. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
  25. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  26. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +9 -9
  27. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  28. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  29. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  30. package/.next/standalone/.next/server/app/api/audit/invite/route.js +1 -1
  31. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +1 -1
  36. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/api/auth/logout/route.js +1 -1
  38. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/api/auth/reminder/route.js +1 -1
  40. package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/api/auth/status/route.js +1 -1
  42. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  43. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/audit/page/build-manifest.json +2 -2
  45. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  46. package/.next/standalone/.next/server/app/audit/page.js +2 -1
  47. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  49. package/.next/standalone/.next/server/app/index.html +1 -1
  50. package/.next/standalone/.next/server/app/index.rsc +14 -14
  51. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  52. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +14 -14
  53. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  54. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +9 -9
  55. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  56. package/.next/standalone/.next/server/app/page/build-manifest.json +2 -2
  57. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  58. package/.next/standalone/.next/server/app/page.js +2 -1
  59. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  60. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  61. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +2 -2
  62. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +10 -10
  63. package/.next/standalone/.next/server/app/policies/page.js +2 -1
  64. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  65. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  66. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +2 -2
  67. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  68. package/.next/standalone/.next/server/app/project/[name]/page.js +2 -1
  69. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  70. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  71. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +2 -2
  72. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  73. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  74. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +2 -1
  75. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  76. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  77. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +2 -2
  78. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  79. package/.next/standalone/.next/server/app/projects/page.js +2 -1
  80. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  81. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  82. package/.next/standalone/.next/server/app/settings/page/build-manifest.json +2 -2
  83. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +4 -4
  84. package/.next/standalone/.next/server/app/settings/page.js +2 -1
  85. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  86. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  87. package/.next/standalone/.next/server/chunks/[root-of-the-server]__06hexd0._.js +3 -0
  88. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_0xu5z._.js +1 -1
  89. package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +1 -1
  90. package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +1 -1
  91. package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +3 -0
  92. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  93. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0hxmox-._.js +3 -0
  94. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__12y_xo0._.js → [root-of-the-server]__0no2gzh._.js} +2 -2
  95. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0spkm68._.js +2 -2
  96. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17itmx-._.js +1 -1
  97. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1rv-sc9._.js +1 -1
  98. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1u-wx42._.js +4 -0
  99. package/.next/standalone/.next/server/chunks/ssr/{_202d777._.js → _09tcu2b._.js} +1 -1
  100. package/.next/standalone/.next/server/chunks/ssr/_0rjgfln._.js +1 -1
  101. package/.next/standalone/.next/server/chunks/ssr/_0vo2qyt._.js +1 -1
  102. package/.next/standalone/.next/server/chunks/ssr/_0xej-0e._.js +1 -1
  103. package/.next/standalone/.next/server/chunks/ssr/_10399ge._.js +1 -1
  104. package/.next/standalone/.next/server/chunks/ssr/_149k9oy._.js +1 -1
  105. package/.next/standalone/.next/server/chunks/ssr/_1_hwp06._.js +1 -1
  106. package/.next/standalone/.next/server/chunks/ssr/_1mnhi_r._.js +1 -1
  107. package/.next/standalone/.next/server/chunks/ssr/_1ror7k9._.js +1 -1
  108. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +2 -2
  109. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  110. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
  111. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +4 -0
  112. package/.next/standalone/.next/server/middleware-build-manifest.js +5 -5
  113. package/.next/standalone/.next/server/pages/404.html +1 -1
  114. package/.next/standalone/.next/server/pages/500.html +1 -1
  115. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  116. package/.next/standalone/.next/server/server-reference-manifest.json +15 -15
  117. package/.next/standalone/.next/static/chunks/{0mot4rr-baiiq.js → 0fx3tgb_n0vz5.js} +1 -1
  118. package/.next/standalone/.next/static/chunks/{1_0nr2fuuslbb.js → 16yzosmvpinbh.js} +1 -1
  119. package/.next/standalone/.next/static/chunks/{0ynhunx0nt0n-.js → 1_as8528fwqxf.js} +1 -1
  120. package/.next/standalone/.next/static/chunks/{1tyg866lrq4kh.js → 1nsy22eg5q-pi.js} +1 -1
  121. package/.next/standalone/.next/static/chunks/{2zilodg05qgcf.js → 2f2h7g8sm0bhr.js} +1 -1
  122. package/.next/standalone/.next/static/chunks/{0pkz6r-cgdr3-.js → 2n6wkcj5q_0yd.js} +2 -2
  123. package/.next/standalone/.next/static/chunks/{30f--ye1ly8m6.js → 2okcei5v0-xrx.js} +1 -1
  124. package/.next/standalone/.next/static/chunks/{31fmsp1soxi07.js → 2rl5gq4xl8oa2.js} +1 -1
  125. package/.next/standalone/.next/static/chunks/{1wn2p7yzbcifa.js → 3vsiw3ayz3c7_.js} +1 -1
  126. package/.next/standalone/.next/static/chunks/{2hiai8kvhjcov.js → 3xp43uu8jj8t4.js} +1 -1
  127. package/.next/standalone/.next/static/chunks/43kqvfa-2jq0g.js +1 -0
  128. package/.next/standalone/.next/static/chunks/{turbopack-3diub4ctvn1n0.js → turbopack-0ud32mdjjcn6y.js} +1 -1
  129. package/.next/standalone/SECURITY.md +8 -2
  130. package/.next/standalone/app/actions/get-hooks-config.ts +0 -1
  131. package/.next/standalone/app/audit/_components/audit-dashboard.tsx +1 -3
  132. package/.next/standalone/app/audit/_components/audit-progress-strip.tsx +1 -1
  133. package/.next/standalone/app/audit/_components/come-back-better-section.tsx +1 -1
  134. package/.next/standalone/app/audit/_components/empty-state.tsx +0 -1
  135. package/.next/standalone/app/audit/_components/how-to-improve-section.tsx +1 -1
  136. package/.next/standalone/app/audit/_components/quirks-section.tsx +0 -1
  137. package/.next/standalone/app/audit/_components/report-footer.tsx +0 -2
  138. package/.next/standalone/app/audit/_components/run-progress.tsx +1 -1
  139. package/.next/standalone/app/audit/_components/strengths-section.tsx +0 -1
  140. package/.next/standalone/app/components/pause-notices.tsx +1 -1
  141. package/.next/standalone/app/components/raw-log-viewer.tsx +1 -1
  142. package/.next/standalone/app/components/toast.tsx +1 -1
  143. package/.next/standalone/app/policies/hooks-client.tsx +3 -2
  144. package/.next/standalone/app/settings/settings-client.tsx +1 -2
  145. package/.next/standalone/lib/auth/auth-store.ts +0 -1
  146. package/.next/standalone/package.json +10 -12
  147. package/.next/standalone/server.js +1 -1
  148. package/bin/failproofai.mjs +253 -33
  149. package/bin/failproofaid-shim.mjs +4 -4
  150. package/dist/cli.mjs +6337 -3869
  151. package/dist/worker.mjs +26 -21
  152. package/lib/auth/auth-store.ts +0 -1
  153. package/package.json +10 -12
  154. package/scripts/dev-hook.mjs +46 -5
  155. package/scripts/translate-docs/cache.ts +1 -1
  156. package/scripts/translate-docs/cli.ts +2 -2
  157. package/scripts/translate-docs/mintlify-nav.ts +1 -1
  158. package/src/audit/cache.ts +0 -1
  159. package/src/audit/dashboard-cache.ts +0 -2
  160. package/src/audit/report.ts +0 -1
  161. package/src/hooks/builtin-policies.ts +1 -1
  162. package/src/hooks/cloud-enrollment-cli.ts +0 -2
  163. package/src/hooks/cloud-enrollment.ts +1 -2
  164. package/src/hooks/cloud-managed-policies.ts +0 -1
  165. package/src/hooks/collector-config.ts +4 -20
  166. package/src/hooks/configure-wizard.ts +0 -4
  167. package/src/hooks/custom-hooks-loader.ts +0 -1
  168. package/src/hooks/daemon-client.ts +4 -28
  169. package/src/hooks/daemon-download.ts +0 -1
  170. package/src/hooks/daemon-service.ts +108 -16
  171. package/src/hooks/fp-config.ts +226 -12
  172. package/src/hooks/fp-home.ts +270 -56
  173. package/src/hooks/fp-reset.ts +482 -73
  174. package/src/hooks/hook-logger.ts +0 -1
  175. package/src/hooks/integrations.ts +0 -1
  176. package/src/hooks/loader-utils.ts +1 -1
  177. package/src/hooks/manager.ts +1 -1
  178. package/src/hooks/migrations.ts +381 -0
  179. package/src/hooks/onboarding-lock.ts +1 -2
  180. package/src/hooks/session-pause.ts +0 -1
  181. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1uyc-z4._.js +0 -3
  182. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0vlyx3j._.js +0 -3
  183. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1mmefsr._.js +0 -5
  184. package/.next/standalone/.next/static/chunks/2yy1767yfda67.js +0 -1
  185. package/.next/standalone/ino.ujh3/.failproofai/VERSION +0 -4
  186. package/.next/standalone/ino.ujh3/.failproofai/state/last-version +0 -1
  187. package/.next/standalone/ino.ujh3/.failproofai/state/telemetry-id +0 -1
  188. /package/.next/standalone/.next/static/{ZbPDZe0dNBNdNmo31aq_e → kSTfP_Tzl760gYDnYr-JC}/_buildManifest.js +0 -0
  189. /package/.next/standalone/.next/static/{ZbPDZe0dNBNdNmo31aq_e → kSTfP_Tzl760gYDnYr-JC}/_clientMiddlewareManifest.js +0 -0
  190. /package/.next/standalone/.next/static/{ZbPDZe0dNBNdNmo31aq_e → kSTfP_Tzl760gYDnYr-JC}/_ssgManifest.js +0 -0
@@ -320,6 +320,186 @@ export const codexSessionPathsFile = (home?: string) => resolve(stateDir(home),
320
320
 
321
321
  export const logsDir = (home?: string) => atHome(home, "logs");
322
322
 
323
+ // ── Migration state ──────────────────────────────────────────────────────────
324
+
325
+ /**
326
+ * Where a migration records what it did, and what it saved first.
327
+ *
328
+ * The migration CODE ships in the npm package, never here. A home written by
329
+ * 1.0.0 contains only 1.0.0's migrations, and the steps that get that machine
330
+ * forward are precisely the ones its old install never had — so the installed
331
+ * CLI, always the newest thing on the machine, is the only correct source. What
332
+ * belongs in the home is the STATE: which steps have run, and the files they
333
+ * saved before running. Exactly the split a database makes, where migrations are
334
+ * code in the repo and the applied set is a table.
335
+ */
336
+ export const migrationsDir = (home?: string) => atHome(home, "migrations");
337
+
338
+ /** One line per step that has run: `{from, to, cli, at, durationMs, ok}`. */
339
+ export const migrationLedgerFile = (home?: string) =>
340
+ resolve(migrationsDir(home), "applied.json");
341
+
342
+ /**
343
+ * Copies of the irreplaceable files, taken before the layout-`n` migration ran.
344
+ *
345
+ * NOT a copy of the whole home, and the reason is that `HOME_CLASSES` already
346
+ * made one unnecessary: a migration deletes only `derived` and `refetchable`
347
+ * paths, so there is no longer anything irreplaceable for it to lose by design.
348
+ * What remains worth insuring against is a BUG in a step — and for that, the few
349
+ * kilobytes of `config.json`, `credentials.json`, `policies-config.json`, the
350
+ * marker, and the retired layout's own config are a real undo, where a full-home
351
+ * copy would cost a duplicate of `bin/` and `hook-activity/` and need a
352
+ * two-rename window that also has to dodge a live daemon holding `run/`.
353
+ */
354
+ export const migrationBackupDir = (layout: number, home?: string) =>
355
+ resolve(migrationsDir(home), `backup-layout${layout}`);
356
+
357
+ // ── What each path HOLDS ─────────────────────────────────────────────────────
358
+
359
+ /**
360
+ * What kind of data a path in the home holds, which is what decides whether an
361
+ * upgrade may throw it away.
362
+ *
363
+ * The rule, stated once: **derived and re-fetchable may be dropped; anything a
364
+ * person typed, anything not yet delivered, and anything that identifies the
365
+ * machine is carried.**
366
+ */
367
+ export type DataClass =
368
+ /** A person set this and nothing regenerates it. Never deleted. */
369
+ | "user-typed"
370
+ /** Recorded here and not yet shipped anywhere. Never deleted. */
371
+ | "undelivered"
372
+ /** Stable ids and watermarks. Never deleted — a new one is a new machine. */
373
+ | "identity"
374
+ /** Rebuilt on demand. May be dropped. */
375
+ | "derived"
376
+ /** The server is the source of truth. May be dropped and re-fetched. */
377
+ | "refetchable"
378
+ /** Sockets, locks, binaries: live process state. Never touched by a reset. */
379
+ | "ephemeral";
380
+
381
+ /**
382
+ * Every path in the home, and what it holds.
383
+ *
384
+ * `resettablePaths()` is DERIVED from this rather than hand-listed beside it,
385
+ * and that is the whole point. Before this table one path was declared in four
386
+ * places — as a function above, again in `legacy` once it moved, again in the
387
+ * delete list, and again in `CARRIED_POLICY_CONFIG_KEYS` if it held carried
388
+ * keys — with nothing making the four agree. **Every silent-data-loss bug this
389
+ * module has had is one of those lists falling out of sync with a path that
390
+ * moved:** layout 1's `policies/` (hand-written source, deleted), layout 2's
391
+ * nested policy config (enabled policies, emptied), `cache/hook-activity` (the
392
+ * whole decision log, deleted with its parent), the layout-2 `credentials.toml`
393
+ * (a live token left behind), and `collector.sources.extra_paths` — which was
394
+ * on the delete list by way of `config.json` and would have taken every extra
395
+ * path a user typed at the next layout bump.
396
+ *
397
+ * Two consequences worth naming:
398
+ *
399
+ * - **Forgetting is now safe.** A path absent from this table is not deleted,
400
+ * because the delete list is a filter over what is here rather than a
401
+ * catch-all. The failure mode of an oversight becomes a stale file — loud
402
+ * and recoverable — instead of a deleted cloud token.
403
+ * - **`state/` is not classified as a whole**, because it is mixed: its
404
+ * `spool/` and `telemetry-id` are things we must never drop, and everything
405
+ * else under it is scratch. Listing the parent is exactly how a reset came
406
+ * to delete undelivered events and the machine's own telemetry identity.
407
+ *
408
+ * `home-classification.test.ts` asserts every exported path function in this
409
+ * module is either classified here or covered by a classified parent, so the
410
+ * next path added to the home cannot skip the one question that matters.
411
+ */
412
+ export const HOME_CLASSES: readonly { path: (home?: string) => string; class: DataClass }[] = [
413
+ // ── Never deleted: a person typed it ──
414
+ // The cloud enrolment. Deleting it drops the machine out of cloud-managed
415
+ // policy silently — it keeps enforcing whatever it last had, reports healthy,
416
+ // and never reconciles again. Nothing re-derives a token.
417
+ { path: credentialsFile, class: "user-typed" },
418
+ // Holds `daemon.configured` (the flag that makes the machine fail closed),
419
+ // the collector preferences, `[audit] auto`, the telemetry opt-out, and
420
+ // `collector.sources.*.extra_paths` — the entire output of `harness add-path`.
421
+ { path: configFile, class: "user-typed" },
422
+ // The builtin enable/disable set and per-policy params. `resetHome` used to
423
+ // clear this and carry eight named keys back over; it is simply kept now.
424
+ { path: globalPolicyConfigFile, class: "user-typed" },
425
+ // The user's own convention policies, the `lib/` they import and the data
426
+ // files they read. `at("policies")` was on the delete list once: hand-written
427
+ // source destroyed, while `isConfigured()` still read true so the wizard never
428
+ // re-asked and hooks kept firing against an empty policy set.
429
+ { path: policiesDir, class: "user-typed" },
430
+
431
+ // ── Never deleted: recorded and not yet shipped ──
432
+ // Batches read out of transcripts and queued for upload. The reason losing
433
+ // these is PERMANENT and not merely slow is `cursorsDir` below: the watermark
434
+ // has already advanced past them, so nothing will ever read that range again.
435
+ { path: spoolDir, class: "undelivered" },
436
+ { path: failedDir, class: "undelivered" },
437
+ // The SDK spool (`events/` + `failed/`), same argument.
438
+ { path: customAgentsDir, class: "undelivered" },
439
+ // The decision log the dashboard's activity tab reads. Neither derived nor
440
+ // re-fetchable: it is the record of what this machine did, and nothing
441
+ // regenerates it. Deleting its layout-1 parent threw away every decision a
442
+ // machine had recorded, which is why `migrateHookActivity()` exists.
443
+ { path: hookActivityDir, class: "undelivered" },
444
+ // Requests the CLI wrote for the daemon to drain on its next tick. The file's
445
+ // existence IS the pending state, so deleting one silently cancels something
446
+ // a person asked for. Rust-only paths (`crates/failproofaid/src/paths.rs`),
447
+ // hence spelled here rather than exported — nothing on this side reads them.
448
+ { path: (home) => resolve(stateDir(home), "flush-request.json"), class: "undelivered" },
449
+ { path: (home) => resolve(stateDir(home), "backfill-request.json"), class: "undelivered" },
450
+
451
+ // ── Never deleted: it names the machine ──
452
+ // Per-source watermarks. Kept out of the reset so pages carried by
453
+ // `migrateHookActivity()` are not re-shipped in full — it MOVES them, which
454
+ // preserves the inode a cursor is keyed on. A cursor whose file is gone is
455
+ // inert (`retain_existing` drops it), so keeping them costs nothing.
456
+ { path: cursorsDir, class: "identity" },
457
+ // The anonymous instance id both the CLI and the daemon report under. Tiers 1
458
+ // and 2 of `getInstanceId()` re-derive the same value, so most machines would
459
+ // survive losing it — but a machine that fell through to the daemon's own
460
+ // fallback would come back as a brand-new PostHog person, and no analysis
461
+ // spans that split.
462
+ { path: telemetryIdFile, class: "identity" },
463
+ { path: (home) => resolve(stateDir(home), "daemon-telemetry-id"), class: "identity" },
464
+ // The layout marker itself. Not user data, and `resetHome` rewrites it
465
+ // immediately — but listing it made a no-op reset report "removed 1 item" for
466
+ // a file it had just recreated.
467
+ { path: versionFile, class: "identity" },
468
+ // The record of what this machine has been migrated through, and the copies a
469
+ // step took before running. A migration deleting its own audit trail would
470
+ // leave "what has this home been through?" answerable only by guessing, and
471
+ // deleting the backup is deleting the undo for the step that just ran.
472
+ { path: migrationsDir, class: "identity" },
473
+
474
+ // ── May be dropped: rebuilt on demand ──
475
+ { path: auditDir, class: "derived" },
476
+ { path: collectorHealthFile, class: "derived" },
477
+ { path: auditScheduleFile, class: "derived" },
478
+ { path: codexSessionPathsFile, class: "derived" },
479
+ { path: shimsDir, class: "derived" },
480
+ { path: sessionPauseDir, class: "derived" },
481
+ { path: logsDir, class: "derived" },
482
+ { path: lastVersionFile, class: "derived" },
483
+ { path: launcherMarker, class: "derived" },
484
+ { path: onboardingLockFile, class: "derived" },
485
+ { path: onboardingAttemptFile, class: "derived" },
486
+
487
+ // ── May be dropped: the server has it ──
488
+ // Re-fetched and digest-verified on the next daemon poll. This is the whole
489
+ // reason the delete list is per-entry rather than `rm -rf policies/`: one
490
+ // directory holds both the files a person wrote and the ones the fleet sent,
491
+ // and only the second kind may be thrown away.
492
+ { path: cloudPoliciesDir, class: "refetchable" },
493
+
494
+ // ── Never touched ──
495
+ // A downloaded daemon binary is large, version-pinned and re-verified on use,
496
+ // so deleting it only forces a needless re-download.
497
+ { path: binDir, class: "ephemeral" },
498
+ // Sockets and the singleton flock belong to a process that may be alive right
499
+ // now; removing them breaks it rather than resetting configuration.
500
+ { path: runDir, class: "ephemeral" },
501
+ ];
502
+
323
503
  // ── Layout 1 (legacy) ────────────────────────────────────────────────────────
324
504
 
325
505
  /**
@@ -385,11 +565,58 @@ export const legacy = {
385
565
  * out something a future layout adds and this list has not been taught about
386
566
  * — and so the function is auditable at a glance, which matters for anything
387
567
  * that deletes user data.
568
+ *
569
+ * TWO HALVES, and they are not maintained the same way. The paths of layouts
570
+ * that no longer exist are listed by hand below, because a retired path has no
571
+ * definition left to hang a class off — it exists only in this file. The paths
572
+ * of the CURRENT layout are DERIVED from `HOME_CLASSES`, so a path that moves
573
+ * or is added cannot silently fall off (or onto) the list. See `HOME_CLASSES`
574
+ * for what each class means and which incidents the split is answering.
388
575
  */
389
576
  export function resettablePaths(): string[] {
577
+ return [
578
+ ...retiredLayoutPaths(),
579
+ // The current layout, by what each path HOLDS. `user-typed`, `undelivered`
580
+ // and `identity` are absent from this list by construction rather than by
581
+ // anyone remembering to leave them out.
582
+ ...HOME_CLASSES.filter((e) => e.class === "derived" || e.class === "refetchable").map((e) =>
583
+ e.path(),
584
+ ),
585
+ ];
586
+ }
587
+
588
+ /**
589
+ * The paths of layouts this build no longer writes.
590
+ *
591
+ * Hand-listed, and they have to be: a retired path has no definition to carry a
592
+ * class, and `legacy` is the only place it still exists. Each entry is here
593
+ * because an upgrade would otherwise leave it behind — see the notes inline,
594
+ * every one of which records something that actually happened.
595
+ *
596
+ * Phase-splitting these per migration step is what the migration registry does
597
+ * with them next; until then they are one list because `resetHome` is one step.
598
+ */
599
+ function retiredLayoutPaths(): string[] {
390
600
  return [
391
601
  // Layout 1
392
- legacy.policyConfig(),
602
+ //
603
+ // NOT `legacy.policyConfig()`. It is `at("policies-config.json")` — the
604
+ // EXACT path layout 3 puts the live config back at, which `legacy`'s own
605
+ // note records and `detectLayout()` has a whole ordering rule about. Listing
606
+ // it here while `HOME_CLASSES` calls the same path `user-typed` would have
607
+ // the two halves of this function contradict each other, and the delete
608
+ // would win.
609
+ //
610
+ // Leaving it in place is also strictly better than what it replaced. The old
611
+ // sequence was delete-then-carry-eight-named-keys, so every key outside
612
+ // `CARRIED_POLICY_CONFIG_KEYS` was dropped on a layout-1 upgrade; now the
613
+ // file is simply not touched and keeps everything. `readCarriedPolicyConfig`
614
+ // is narrowed to layout 2's NESTED copy accordingly — that path really is
615
+ // deleted (`legacy.localPoliciesDir()`, below) and really does need carrying.
616
+ //
617
+ // The project-scope `policies-config.local.json` is a different file at a
618
+ // different path (`{cwd}/.failproofai/`, see `hooks-config.ts`); the global
619
+ // one below is a layout-1 artefact nothing reads.
393
620
  legacy.policyConfigLocal(),
394
621
  legacy.cloudCredentials(),
395
622
  legacy.ingestCredentials(),
@@ -426,66 +653,53 @@ export function resettablePaths(): string[] {
426
653
  // a `lib/` of helpers colliding with a stale `policies/lib/` was destroyed
427
654
  // outright, and the policy that did move was left importing a file that no
428
655
  // longer existed.
429
- // The MACHINE-OWNED things, never the directory the user owns.
430
656
  //
431
- // `at("policies")` stood here, and on layout 1 that directory IS the
432
- // documented home for hand-written personal policies — `docs/
433
- // configuration.mdx` names `~/.failproofai/policies/` as the user scope,
434
- // and `manager.ts` read it. Those are source files a person wrote; nothing
435
- // regenerates them, nothing backed them up, and the reset message named
436
- // only "policy config, activity history and audit cache". Worse, the
437
- // machine then reported itself configured — `isConfigured()` is a union
438
- // that still sees the agent CLIs' untouched settings files — so the wizard
439
- // was skipped and hooks kept firing against an empty policy set.
657
+ // Everything that used to follow here — `globalPolicyConfigFile()`,
658
+ // `cloudPoliciesDir()`, `at("state")`, `configFile()`, `credentialsFile()`,
659
+ // `auditDir()`, `customAgentsDir()`, `logsDir()` — was the CURRENT layout,
660
+ // hand-listed. It is derived from `HOME_CLASSES` now, and five of those
661
+ // entries stopped being deleted as a result, each for a reason recorded on
662
+ // its row: the cloud token, the whole of `config.json` (including the
663
+ // `extra_paths` a user typed), the undelivered spool, the SDK spool, and the
664
+ // machine's telemetry identity.
440
665
  //
441
- // `policies/` is deliberately absent from this list for the same reason —
442
- // the user's own convention policies sit directly in it and nothing
443
- // re-derives them. Its `cloud-policies/` CHILD is listed instead, which is
444
- // the whole reason this is a per-entry list and not `rm -rf policies/`:
445
- // one directory now holds both the files a person wrote by hand and the
446
- // ones the fleet sent, and only the second kind can be thrown away. Both
447
- // entries below are re-derived — the policy config by setup,
448
- // `cloud-policies/` by the next daemon poll.
666
+ // `at("state")` is the sharpest of them. It is MIXED — `spool/` and
667
+ // `telemetry-id` sit under it beside a dozen scratch files — so listing the
668
+ // parent is precisely how a reset came to delete events that were recorded,
669
+ // queued, and then unreachable forever, because `cursors/` deliberately
670
+ // survives and the watermark had already moved past them. Its scratch
671
+ // children are classified individually now and the parent is not listed at
672
+ // all, so anything under it this table has not been taught about survives
673
+ // rather than being swept up.
449
674
  //
450
- // The policy config holds ONE thing — the user's enabled-policy selection —
451
- // and clearing it on a layout migration is deliberate and tested. But it is
452
- // also the destination `migratePolicyConfig()` carries layout 1's selection
453
- // into, so the carry is written AFTER this list runs, and `resetHome` skips
454
- // this entry when the reset is not a layout migration at all. See both.
675
+ // Two notes worth keeping from the old list, because they explain absences
676
+ // that still hold:
455
677
  //
456
- // Layout 3 moved it from `policies/local-policies/policies-config.json` to
457
- // the home root, so this is a FILE now rather than the directory that held
458
- // it — `policies/` itself must survive, because in layout 3 it is the
459
- // user's.
460
- globalPolicyConfigFile(),
461
- cloudPoliciesDir(),
462
- // `at("cursors")` is deliberately NOT here, and it is load-bearing for the
463
- // activity migration above rather than a separate opinion. Cursors are keyed
464
- // on `(device, inode)`; `migrateHookActivity()` MOVES pages, which preserves
678
+ // `policies/` is `user-typed`, not absent by oversight. On layout 1 that
679
+ // directory IS the documented home for hand-written personal policies —
680
+ // `docs/configuration.mdx` names `~/.failproofai/policies/` as the user
681
+ // scope, and `manager.ts` read it. It stood on this list once: source files
682
+ // a person wrote, nothing regenerating them, nothing backing them up, and a
683
+ // reset message that named only "policy config, activity history and audit
684
+ // cache". Worse, the machine then reported itself configured —
685
+ // `isConfigured()` is a union that still sees the agent CLIs' untouched
686
+ // settings files — so the wizard was skipped and hooks kept firing against
687
+ // an empty policy set. Its `cloud-policies/` CHILD is `refetchable` and does
688
+ // still go, which is the whole reason this is a per-entry list and not
689
+ // `rm -rf policies/`.
690
+ //
691
+ // `cursors/` is `identity` for a reason load-bearing to the activity
692
+ // migration rather than a separate opinion. Cursors are keyed on
693
+ // `(device, inode)`; `migrateHookActivity()` MOVES pages, which preserves
465
694
  // the inode, so a surviving cursor still points at the file it belongs to
466
695
  // and resumes at the right offset. Delete the cursors and every carried page
467
- // reads as new and re-ships in full — which is the outcome the move exists
468
- // to avoid. A cursor whose file is gone is inert (`retain_existing` drops
469
- // it), so keeping them costs nothing when there is nothing to resume.
470
- at("state"),
471
- // Layout 2.
472
- // NOT versionFile() — it is the layout marker, not user data, and the
473
- // reset rewrites it immediately afterwards. Listing it made a no-op reset
474
- // report "removed 1 item" for a file it had just recreated.
475
- configFile(),
476
- credentialsFile(),
477
- auditDir(),
478
- // NOT hookActivityDir(). It is the DESTINATION `migrateHookActivity()` has
479
- // just moved layout 1's log into, so listing it here deleted the migration
480
- // moments after it happened — the reset runs every path in this list after
481
- // the migrations. It is also the one directory here that is neither derived
482
- // nor re-fetchable: a decision log is the record of what this machine did,
483
- // and nothing regenerates it.
484
- customAgentsDir(),
485
- logsDir(),
486
- // NOT bin/ — a downloaded daemon binary is large, version-pinned and
487
- // re-verified on use, so deleting it only forces a needless re-download.
488
- // NOT run/ — sockets belong to a running daemon; removing them out from
489
- // under it breaks a live process rather than resetting configuration.
696
+ // reads as new and re-ships in full — the outcome the move exists to avoid.
697
+ // A cursor whose file is gone is inert (`retain_existing` drops it), so
698
+ // keeping them costs nothing when there is nothing to resume.
699
+ //
700
+ // And `hook-activity/` is `undelivered`, never listed: it is the DESTINATION
701
+ // `migrateHookActivity()` moves layout 1's log into, so listing it deleted
702
+ // the migration moments after it happened — the reset runs every path in
703
+ // this list after the migrations.
490
704
  ];
491
705
  }