@reddoorla/maintenance 0.97.0 → 0.98.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 (130) hide show
  1. package/dist/{announce-5ZD2SBGD.js → announce-2FH2KBQQ.js} +4 -4
  2. package/dist/{bump-deps-775VLDYB.js → bump-deps-54K4ECP3.js} +2 -2
  3. package/dist/{chunk-YXWBM26P.js → chunk-3YALLSBF.js} +2 -2
  4. package/dist/{chunk-LAJ3FF54.js → chunk-5UI4FERC.js} +16 -5
  5. package/dist/chunk-5UI4FERC.js.map +1 -0
  6. package/dist/{chunk-VMGHQUTV.js → chunk-B2BRFWJY.js} +2 -2
  7. package/dist/{chunk-IOTOOTII.js → chunk-CP6MVZ5X.js} +16 -1
  8. package/dist/{chunk-IOTOOTII.js.map → chunk-CP6MVZ5X.js.map} +1 -1
  9. package/dist/{chunk-L3S3QZXL.js → chunk-DXZRWW2F.js} +64 -9
  10. package/dist/chunk-DXZRWW2F.js.map +1 -0
  11. package/dist/chunk-DZZ5KRTB.js +23 -0
  12. package/dist/chunk-DZZ5KRTB.js.map +1 -0
  13. package/dist/{chunk-U2ZI6G7H.js → chunk-GRF2RYEO.js} +2 -2
  14. package/dist/{chunk-QFSL2L2B.js → chunk-L5F3WYIY.js} +81 -31
  15. package/dist/chunk-L5F3WYIY.js.map +1 -0
  16. package/dist/{chunk-MMSMZC7S.js → chunk-MNRWHSFS.js} +2 -2
  17. package/dist/{chunk-PARKXDXI.js → chunk-ORTOMEYR.js} +2 -2
  18. package/dist/{chunk-FP6HOW6J.js → chunk-S2PLX4XK.js} +2 -2
  19. package/dist/{chunk-A5QKNOXW.js → chunk-TTT6HYPW.js} +47 -10
  20. package/dist/chunk-TTT6HYPW.js.map +1 -0
  21. package/dist/{chunk-BIYQN7FH.js → chunk-VCPTOZ56.js} +31 -1
  22. package/dist/{chunk-BIYQN7FH.js.map → chunk-VCPTOZ56.js.map} +1 -1
  23. package/dist/{chunk-LDHRIQUW.js → chunk-ZEE5WQYU.js} +2 -2
  24. package/dist/cli/bin.js +27 -27
  25. package/dist/cli/commands/audit.js +10 -10
  26. package/dist/client-3XGSZMLU.js +11 -0
  27. package/dist/{convert-to-pnpm-KLWA53N5.js → convert-to-pnpm-U2CRNKNY.js} +2 -2
  28. package/dist/{db-II5WAK4D.js → db-7XYTBIPH.js} +20 -13
  29. package/dist/db-7XYTBIPH.js.map +1 -0
  30. package/dist/{deadletter-G4M3HSVM.js → deadletter-A7RQVNG3.js} +8 -4
  31. package/dist/{digest-CWKK2DMR.js → digest-AMXRGWZA.js} +20 -20
  32. package/dist/{digest-collectors-CYTN2EUX.js → digest-collectors-CX2NCGS7.js} +7 -7
  33. package/dist/{ensure-site-2QZHFVQ3.js → ensure-site-F2WFCWWA.js} +2 -2
  34. package/dist/{forms-notify-target-X6UEOI2M.js → forms-notify-target-DK5R326N.js} +3 -3
  35. package/dist/{github-signals-QIRTHHET.js → github-signals-RGRPYTUB.js} +6 -6
  36. package/dist/{header-image-H7ZJAV26.js → header-image-43ZIEJKP.js} +3 -3
  37. package/dist/{health-endpoint-BJ6BKRHF.js → health-endpoint-K5LXDPGE.js} +2 -2
  38. package/dist/{health-mirror-KU3G7V2Y.js → health-mirror-WZUHWCCV.js} +3 -3
  39. package/dist/index.js +10 -11
  40. package/dist/index.js.map +1 -1
  41. package/dist/{init-GOWDA5QS.js → init-OFITGMOP.js} +5 -5
  42. package/dist/{launch-7N3X2MXB.js → launch-AJY3TGTQ.js} +7 -7
  43. package/dist/{lighthouse-52WSPZ5D.js → lighthouse-RFMFREEX.js} +2 -2
  44. package/dist/{match-harness-GBCDSVPS.js → match-harness-CFELJA57.js} +2 -2
  45. package/dist/migrate-EZL55AWA.js +7 -0
  46. package/dist/{onboard-JQQIMQW7.js → onboard-F7RTLMDZ.js} +2 -2
  47. package/dist/{orchestrate-L2A5BZFY.js → orchestrate-SAIFXZIW.js} +5 -5
  48. package/dist/{pipeline-UUPXOS2J.js → pipeline-TCSNVDQZ.js} +3 -3
  49. package/dist/{preflight-O7Q6FWHU.js → preflight-6XGVVO2U.js} +7 -7
  50. package/dist/{prismic-ci-NAPWOXXD.js → prismic-ci-XMX7A5E4.js} +2 -2
  51. package/dist/{prismic-models-LL6ORTQ2.js → prismic-models-7GKFAAFA.js} +4 -4
  52. package/dist/prospect/types.d.ts +44 -2
  53. package/dist/prospect/types.js +1 -1
  54. package/dist/{prospect-audit-FMPGQIRQ.js → prospect-audit-KWP5P5U2.js} +37 -5
  55. package/dist/prospect-audit-KWP5P5U2.js.map +1 -0
  56. package/dist/{render-MHUUHPLN.js → render-NWUAX6RN.js} +3 -3
  57. package/dist/{renovate-dispatch-2IHAZDVA.js → renovate-dispatch-ZZQXT7DA.js} +2 -2
  58. package/dist/replay-HB44TAII.js +77 -0
  59. package/dist/replay-HB44TAII.js.map +1 -0
  60. package/dist/{report-IAHXN5CW.js → report-MVPWS7WP.js} +13 -13
  61. package/dist/{report-mirror-4QM57LMS.js → report-mirror-O6IDFDWU.js} +3 -3
  62. package/dist/{roster-GDNSIRAK.js → roster-EJ3ILR52.js} +2 -2
  63. package/dist/{screenouts-7P5GOZMV.js → screenouts-7NQUWHOJ.js} +14 -1
  64. package/dist/screenouts-7NQUWHOJ.js.map +1 -0
  65. package/dist/{self-updating-ZUWPGYUP.js → self-updating-KX5FMGEO.js} +2 -2
  66. package/dist/{selftest-G4W3LIBQ.js → selftest-KABY2CYJ.js} +5 -5
  67. package/dist/{site-mirror-WR7ACXOV.js → site-mirror-TKTDWYP3.js} +3 -3
  68. package/dist/{smoke-suite-WAUPI6EJ.js → smoke-suite-QQMV7TNA.js} +2 -2
  69. package/dist/{submissions-JDZP35JM.js → submissions-4I66W6QK.js} +11 -1
  70. package/dist/{submissions-JDZP35JM.js.map → submissions-4I66W6QK.js.map} +1 -1
  71. package/dist/{submissions-2SHCFOKD.js → submissions-K5UXGZUN.js} +4 -4
  72. package/dist/{svelte-codemods-VOLOKKFI.js → svelte-codemods-YFN4TEGV.js} +2 -2
  73. package/dist/{sync-configs-OXSWKVZ2.js → sync-configs-HBXS3D7C.js} +2 -2
  74. package/dist/{upgrade-EVAL3JTO.js → upgrade-XOJMDIT4.js} +2 -2
  75. package/package.json +1 -1
  76. package/dist/chunk-A5QKNOXW.js.map +0 -1
  77. package/dist/chunk-L3S3QZXL.js.map +0 -1
  78. package/dist/chunk-LAJ3FF54.js.map +0 -1
  79. package/dist/chunk-QFSL2L2B.js.map +0 -1
  80. package/dist/client-DGTO65WB.js +0 -11
  81. package/dist/db-II5WAK4D.js.map +0 -1
  82. package/dist/migrate-XZ7U5DFC.js +0 -7
  83. package/dist/prospect-audit-FMPGQIRQ.js.map +0 -1
  84. package/dist/replay-4FVFOMHH.js +0 -54
  85. package/dist/replay-4FVFOMHH.js.map +0 -1
  86. package/dist/screenouts-7P5GOZMV.js.map +0 -1
  87. /package/dist/{announce-5ZD2SBGD.js.map → announce-2FH2KBQQ.js.map} +0 -0
  88. /package/dist/{bump-deps-775VLDYB.js.map → bump-deps-54K4ECP3.js.map} +0 -0
  89. /package/dist/{chunk-YXWBM26P.js.map → chunk-3YALLSBF.js.map} +0 -0
  90. /package/dist/{chunk-VMGHQUTV.js.map → chunk-B2BRFWJY.js.map} +0 -0
  91. /package/dist/{chunk-U2ZI6G7H.js.map → chunk-GRF2RYEO.js.map} +0 -0
  92. /package/dist/{chunk-MMSMZC7S.js.map → chunk-MNRWHSFS.js.map} +0 -0
  93. /package/dist/{chunk-PARKXDXI.js.map → chunk-ORTOMEYR.js.map} +0 -0
  94. /package/dist/{chunk-FP6HOW6J.js.map → chunk-S2PLX4XK.js.map} +0 -0
  95. /package/dist/{chunk-LDHRIQUW.js.map → chunk-ZEE5WQYU.js.map} +0 -0
  96. /package/dist/{client-DGTO65WB.js.map → client-3XGSZMLU.js.map} +0 -0
  97. /package/dist/{convert-to-pnpm-KLWA53N5.js.map → convert-to-pnpm-U2CRNKNY.js.map} +0 -0
  98. /package/dist/{deadletter-G4M3HSVM.js.map → deadletter-A7RQVNG3.js.map} +0 -0
  99. /package/dist/{digest-CWKK2DMR.js.map → digest-AMXRGWZA.js.map} +0 -0
  100. /package/dist/{digest-collectors-CYTN2EUX.js.map → digest-collectors-CX2NCGS7.js.map} +0 -0
  101. /package/dist/{ensure-site-2QZHFVQ3.js.map → ensure-site-F2WFCWWA.js.map} +0 -0
  102. /package/dist/{forms-notify-target-X6UEOI2M.js.map → forms-notify-target-DK5R326N.js.map} +0 -0
  103. /package/dist/{github-signals-QIRTHHET.js.map → github-signals-RGRPYTUB.js.map} +0 -0
  104. /package/dist/{header-image-H7ZJAV26.js.map → header-image-43ZIEJKP.js.map} +0 -0
  105. /package/dist/{health-endpoint-BJ6BKRHF.js.map → health-endpoint-K5LXDPGE.js.map} +0 -0
  106. /package/dist/{health-mirror-KU3G7V2Y.js.map → health-mirror-WZUHWCCV.js.map} +0 -0
  107. /package/dist/{init-GOWDA5QS.js.map → init-OFITGMOP.js.map} +0 -0
  108. /package/dist/{launch-7N3X2MXB.js.map → launch-AJY3TGTQ.js.map} +0 -0
  109. /package/dist/{lighthouse-52WSPZ5D.js.map → lighthouse-RFMFREEX.js.map} +0 -0
  110. /package/dist/{match-harness-GBCDSVPS.js.map → match-harness-CFELJA57.js.map} +0 -0
  111. /package/dist/{migrate-XZ7U5DFC.js.map → migrate-EZL55AWA.js.map} +0 -0
  112. /package/dist/{onboard-JQQIMQW7.js.map → onboard-F7RTLMDZ.js.map} +0 -0
  113. /package/dist/{orchestrate-L2A5BZFY.js.map → orchestrate-SAIFXZIW.js.map} +0 -0
  114. /package/dist/{pipeline-UUPXOS2J.js.map → pipeline-TCSNVDQZ.js.map} +0 -0
  115. /package/dist/{preflight-O7Q6FWHU.js.map → preflight-6XGVVO2U.js.map} +0 -0
  116. /package/dist/{prismic-ci-NAPWOXXD.js.map → prismic-ci-XMX7A5E4.js.map} +0 -0
  117. /package/dist/{prismic-models-LL6ORTQ2.js.map → prismic-models-7GKFAAFA.js.map} +0 -0
  118. /package/dist/{render-MHUUHPLN.js.map → render-NWUAX6RN.js.map} +0 -0
  119. /package/dist/{renovate-dispatch-2IHAZDVA.js.map → renovate-dispatch-ZZQXT7DA.js.map} +0 -0
  120. /package/dist/{report-IAHXN5CW.js.map → report-MVPWS7WP.js.map} +0 -0
  121. /package/dist/{report-mirror-4QM57LMS.js.map → report-mirror-O6IDFDWU.js.map} +0 -0
  122. /package/dist/{roster-GDNSIRAK.js.map → roster-EJ3ILR52.js.map} +0 -0
  123. /package/dist/{self-updating-ZUWPGYUP.js.map → self-updating-KX5FMGEO.js.map} +0 -0
  124. /package/dist/{selftest-G4W3LIBQ.js.map → selftest-KABY2CYJ.js.map} +0 -0
  125. /package/dist/{site-mirror-WR7ACXOV.js.map → site-mirror-TKTDWYP3.js.map} +0 -0
  126. /package/dist/{smoke-suite-WAUPI6EJ.js.map → smoke-suite-QQMV7TNA.js.map} +0 -0
  127. /package/dist/{submissions-2SHCFOKD.js.map → submissions-K5UXGZUN.js.map} +0 -0
  128. /package/dist/{svelte-codemods-VOLOKKFI.js.map → svelte-codemods-YFN4TEGV.js.map} +0 -0
  129. /package/dist/{sync-configs-OXSWKVZ2.js.map → sync-configs-HBXS3D7C.js.map} +0 -0
  130. /package/dist/{upgrade-EVAL3JTO.js.map → upgrade-XOJMDIT4.js.map} +0 -0
@@ -5,10 +5,10 @@ import {
5
5
  } from "./chunk-DYTYPKFN.js";
6
6
  import {
7
7
  resolveSites
8
- } from "./chunk-FP6HOW6J.js";
8
+ } from "./chunk-S2PLX4XK.js";
9
9
  import {
10
10
  init
11
- } from "./chunk-U2ZI6G7H.js";
11
+ } from "./chunk-GRF2RYEO.js";
12
12
  import "./chunk-EN6CTBUN.js";
13
13
  import "./chunk-6PGXKN6F.js";
14
14
  import "./chunk-AYDQQBNG.js";
@@ -21,12 +21,12 @@ import "./chunk-3RLDX3Q2.js";
21
21
  import "./chunk-SLFG7Y76.js";
22
22
  import "./chunk-LUF52EIY.js";
23
23
  import "./chunk-OTGB6YQO.js";
24
- import "./chunk-L3S3QZXL.js";
24
+ import "./chunk-DXZRWW2F.js";
25
25
  import "./chunk-LKGVSM2O.js";
26
26
  import "./chunk-MVKNU4QD.js";
27
27
  import "./chunk-43SW3FGW.js";
28
28
  import "./chunk-Z3JXLULD.js";
29
- import "./chunk-BIYQN7FH.js";
29
+ import "./chunk-VCPTOZ56.js";
30
30
  import "./chunk-UDRHAQT7.js";
31
31
  import "./chunk-PTDMVGKL.js";
32
32
  import "./chunk-LBYOLBW7.js";
@@ -111,4 +111,4 @@ async function runInitCommand(site, opts) {
111
111
  export {
112
112
  runInitCommand
113
113
  };
114
- //# sourceMappingURL=init-GOWDA5QS.js.map
114
+ //# sourceMappingURL=init-OFITGMOP.js.map
@@ -17,7 +17,7 @@ import "./chunk-WQLNUEZL.js";
17
17
  import "./chunk-5L3FPQIJ.js";
18
18
  import {
19
19
  resolveSites
20
- } from "./chunk-FP6HOW6J.js";
20
+ } from "./chunk-S2PLX4XK.js";
21
21
  import {
22
22
  createReportDraft,
23
23
  findReportForPeriod,
@@ -49,12 +49,12 @@ import "./chunk-RQJFGK3A.js";
49
49
  import "./chunk-SLFG7Y76.js";
50
50
  import {
51
51
  runAudits
52
- } from "./chunk-L3S3QZXL.js";
52
+ } from "./chunk-DXZRWW2F.js";
53
53
  import "./chunk-LKGVSM2O.js";
54
54
  import "./chunk-MVKNU4QD.js";
55
55
  import "./chunk-43SW3FGW.js";
56
56
  import "./chunk-Z3JXLULD.js";
57
- import "./chunk-BIYQN7FH.js";
57
+ import "./chunk-VCPTOZ56.js";
58
58
  import "./chunk-UDRHAQT7.js";
59
59
  import "./chunk-PTDMVGKL.js";
60
60
  import "./chunk-LBYOLBW7.js";
@@ -602,9 +602,9 @@ async function runLaunchCommand(site, opts) {
602
602
  if (!target) {
603
603
  return { output: `No site resolved for "${site}".`, code: 1 };
604
604
  }
605
- const { makeReportMirror } = await import("./report-mirror-4QM57LMS.js");
606
- const { makeSiteMirror } = await import("./site-mirror-WR7ACXOV.js");
607
- const { readFleetRoster } = await import("./roster-GDNSIRAK.js");
605
+ const { makeReportMirror } = await import("./report-mirror-O6IDFDWU.js");
606
+ const { makeSiteMirror } = await import("./site-mirror-TKTDWYP3.js");
607
+ const { readFleetRoster } = await import("./roster-EJ3ILR52.js");
608
608
  const result = await launch(target, {
609
609
  roster: () => readFleetRoster(),
610
610
  reportMirror: await makeReportMirror(),
@@ -616,4 +616,4 @@ export {
616
616
  formatStep,
617
617
  runLaunchCommand
618
618
  };
619
- //# sourceMappingURL=launch-7N3X2MXB.js.map
619
+ //# sourceMappingURL=launch-AJY3TGTQ.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  lighthouseAudit
3
- } from "./chunk-BIYQN7FH.js";
3
+ } from "./chunk-VCPTOZ56.js";
4
4
  import "./chunk-PTDMVGKL.js";
5
5
  import "./chunk-LBYOLBW7.js";
6
6
  import "./chunk-XXTZBPUY.js";
@@ -8,4 +8,4 @@ import "./chunk-NMAWXBJA.js";
8
8
  export {
9
9
  lighthouseAudit
10
10
  };
11
- //# sourceMappingURL=lighthouse-52WSPZ5D.js.map
11
+ //# sourceMappingURL=lighthouse-RFMFREEX.js.map
@@ -20,7 +20,7 @@ import {
20
20
  } from "./chunk-DYTYPKFN.js";
21
21
  import {
22
22
  resolveSites
23
- } from "./chunk-FP6HOW6J.js";
23
+ } from "./chunk-S2PLX4XK.js";
24
24
  import {
25
25
  PRETTIER_FLAG_NOTE,
26
26
  formatWithPrettier,
@@ -414,4 +414,4 @@ async function runMatchHarnessCommand(site, opts) {
414
414
  export {
415
415
  runMatchHarnessCommand
416
416
  };
417
- //# sourceMappingURL=match-harness-GBCDSVPS.js.map
417
+ //# sourceMappingURL=match-harness-CFELJA57.js.map
@@ -0,0 +1,7 @@
1
+ import {
2
+ runMigrations
3
+ } from "./chunk-CP6MVZ5X.js";
4
+ export {
5
+ runMigrations
6
+ };
7
+ //# sourceMappingURL=migrate-EZL55AWA.js.map
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-DYTYPKFN.js";
9
9
  import {
10
10
  resolveSites
11
- } from "./chunk-FP6HOW6J.js";
11
+ } from "./chunk-S2PLX4XK.js";
12
12
  import {
13
13
  onboard
14
14
  } from "./chunk-6PGXKN6F.js";
@@ -70,4 +70,4 @@ async function runOnboardCommand(site, opts) {
70
70
  export {
71
71
  runOnboardCommand
72
72
  };
73
- //# sourceMappingURL=onboard-JQQIMQW7.js.map
73
+ //# sourceMappingURL=onboard-F7RTLMDZ.js.map
@@ -4,7 +4,7 @@ import {
4
4
  parseAddresses,
5
5
  sendApprovedReports,
6
6
  withGlobalCc
7
- } from "./chunk-LDHRIQUW.js";
7
+ } from "./chunk-ZEE5WQYU.js";
8
8
  import "./chunk-TEMQPSCM.js";
9
9
  import "./chunk-ASLDCWBF.js";
10
10
  import "./chunk-VAD43MOR.js";
@@ -19,9 +19,9 @@ import "./chunk-TC4GANMX.js";
19
19
  import "./chunk-ICSRWW5Q.js";
20
20
  import "./chunk-RQJFGK3A.js";
21
21
  import "./chunk-WEKL2HYJ.js";
22
- import "./chunk-YXWBM26P.js";
23
- import "./chunk-MMSMZC7S.js";
24
- import "./chunk-IOTOOTII.js";
22
+ import "./chunk-3YALLSBF.js";
23
+ import "./chunk-MNRWHSFS.js";
24
+ import "./chunk-CP6MVZ5X.js";
25
25
  import "./chunk-RARREDQE.js";
26
26
  import "./chunk-6VAL7XJT.js";
27
27
  export {
@@ -31,4 +31,4 @@ export {
31
31
  sendApprovedReports,
32
32
  withGlobalCc
33
33
  };
34
- //# sourceMappingURL=orchestrate-L2A5BZFY.js.map
34
+ //# sourceMappingURL=orchestrate-SAIFXZIW.js.map
@@ -11,8 +11,8 @@ import {
11
11
  envEngines,
12
12
  mergeFixes,
13
13
  runProspectAudit
14
- } from "./chunk-QFSL2L2B.js";
15
- import "./chunk-LAJ3FF54.js";
14
+ } from "./chunk-L5F3WYIY.js";
15
+ import "./chunk-5UI4FERC.js";
16
16
  import "./chunk-T7ZUUOZ2.js";
17
17
  import "./chunk-RARREDQE.js";
18
18
  export {
@@ -29,4 +29,4 @@ export {
29
29
  mergeFixes,
30
30
  runProspectAudit
31
31
  };
32
- //# sourceMappingURL=pipeline-UUPXOS2J.js.map
32
+ //# sourceMappingURL=pipeline-TCSNVDQZ.js.map
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  preflight
3
- } from "./chunk-VMGHQUTV.js";
3
+ } from "./chunk-B2BRFWJY.js";
4
4
  import "./chunk-PZR4EBPY.js";
5
- import "./chunk-LDHRIQUW.js";
5
+ import "./chunk-ZEE5WQYU.js";
6
6
  import "./chunk-TEMQPSCM.js";
7
7
  import "./chunk-ASLDCWBF.js";
8
8
  import "./chunk-VAD43MOR.js";
@@ -17,9 +17,9 @@ import "./chunk-TC4GANMX.js";
17
17
  import "./chunk-ICSRWW5Q.js";
18
18
  import "./chunk-RQJFGK3A.js";
19
19
  import "./chunk-WEKL2HYJ.js";
20
- import "./chunk-YXWBM26P.js";
21
- import "./chunk-MMSMZC7S.js";
22
- import "./chunk-IOTOOTII.js";
20
+ import "./chunk-3YALLSBF.js";
21
+ import "./chunk-MNRWHSFS.js";
22
+ import "./chunk-CP6MVZ5X.js";
23
23
  import "./chunk-RARREDQE.js";
24
24
  import "./chunk-6VAL7XJT.js";
25
25
 
@@ -39,7 +39,7 @@ function lazyFleetDb() {
39
39
  return {
40
40
  get: async () => {
41
41
  if (!db) {
42
- const { openDb, readDbConfig } = await import("./client-DGTO65WB.js");
42
+ const { openDb, readDbConfig } = await import("./client-3XGSZMLU.js");
43
43
  db = await openDb(readDbConfig());
44
44
  }
45
45
  return db;
@@ -91,4 +91,4 @@ async function runPreflightCommand(site, opts) {
91
91
  export {
92
92
  runPreflightCommand
93
93
  };
94
- //# sourceMappingURL=preflight-O7Q6FWHU.js.map
94
+ //# sourceMappingURL=preflight-6XGVVO2U.js.map
@@ -12,7 +12,7 @@ import {
12
12
  } from "./chunk-DYTYPKFN.js";
13
13
  import {
14
14
  resolveSites
15
- } from "./chunk-FP6HOW6J.js";
15
+ } from "./chunk-S2PLX4XK.js";
16
16
  import {
17
17
  RESTORED_NOTE,
18
18
  refusedByGit,
@@ -476,4 +476,4 @@ export {
476
476
  formatPrismicCiResults,
477
477
  runPrismicCiCommand
478
478
  };
479
- //# sourceMappingURL=prismic-ci-NAPWOXXD.js.map
479
+ //# sourceMappingURL=prismic-ci-XMX7A5E4.js.map
@@ -18,7 +18,7 @@ import {
18
18
  } from "./chunk-DYTYPKFN.js";
19
19
  import {
20
20
  resolveSites
21
- } from "./chunk-FP6HOW6J.js";
21
+ } from "./chunk-S2PLX4XK.js";
22
22
  import {
23
23
  PRETTIER_FLAG_NOTE,
24
24
  formatWithPrettier
@@ -169,10 +169,10 @@ var defaultDeps = () => ({
169
169
  openVerdictSink: async () => {
170
170
  const { openBase, readAirtableConfig } = await import("./client-EPVO3FEH.js");
171
171
  const { updatePrismicModels } = await import("./websites-Y6FN2XFD.js");
172
- const { readFleetRoster } = await import("./roster-GDNSIRAK.js");
172
+ const { readFleetRoster } = await import("./roster-EJ3ILR52.js");
173
173
  const base = openBase(readAirtableConfig());
174
174
  const websites = await readFleetRoster();
175
- const { makeSiteMirror } = await import("./site-mirror-WR7ACXOV.js");
175
+ const { makeSiteMirror } = await import("./site-mirror-TKTDWYP3.js");
176
176
  const mirror = await makeSiteMirror();
177
177
  return {
178
178
  websites: websites.map((w) => ({ id: w.id, name: w.name })),
@@ -1025,4 +1025,4 @@ export {
1025
1025
  sweepRowWriteback,
1026
1026
  writeSweepToAirtable
1027
1027
  };
1028
- //# sourceMappingURL=prismic-models-LL6ORTQ2.js.map
1028
+ //# sourceMappingURL=prismic-models-7GKFAAFA.js.map
@@ -38,6 +38,18 @@ type AnswerSpace = {
38
38
  * that declined to cite anything is a fact about the query, not about the
39
39
  * prospect, and folding it into the shares would dilute them with silence. */
40
40
  answersWithCitations: number;
41
+ /**
42
+ * Category queries we ASKED — the attempted count, not the returned one.
43
+ *
44
+ * It used to be `answers.filter(kind === "category").length`, i.e. only the
45
+ * probes that came back, which is the identical defect #631 fixed for
46
+ * `visibilityScore` (see `types.ts` — "a flakier run scored HIGHER"): a run
47
+ * where three of five queries timed out reported that we asked two. So this
48
+ * counts every category probe dispatched, whether it answered, answered
49
+ * citing nothing, or never returned at all. `answersWithCitations` is the
50
+ * subset that produced citations; the gap between them is our missing data,
51
+ * and a consumer must read it as ours rather than as the prospect's.
52
+ */
41
53
  queriesAsked: number;
42
54
  /** Every citation across every category answer, repeats included. */
43
55
  citationsTotal: number;
@@ -289,7 +301,10 @@ type BasicsCheck = {
289
301
  * Images loaded over plain http on an https page. Browsers block or refuse to
290
302
  * upgrade these, so they are broken images for some visitors and a mixed-
291
303
  * content warning for the rest. `measured` is false when the site is not on
292
- * https at all, in which case `insecureEntry` is the finding instead.
304
+ * https at all, in which case `insecureEntry` is the finding instead — and
305
+ * also when the crawl recorded no image sources to look at (an older stored
306
+ * report, replayed), where the honest reading is "not measured" rather than
307
+ * "none found".
293
308
  */
294
309
  mixedContent: {
295
310
  measured: boolean;
@@ -394,13 +409,28 @@ type ConsistencyResult = {
394
409
  /** The newest year found, or null when none was. */
395
410
  newestCopyrightYear: number | null;
396
411
  /** Pages carrying none of the site's shared navigation links. Empty when
397
- * there is no shared navigation to compare against — see `sharedNavLinks`. */
412
+ * there is no shared navigation to compare against — see `sharedNavLinks`.
413
+ * A page in `pagesWithTruncatedAnchors` can never appear here. */
398
414
  pagesOffTemplate: string[];
399
415
  /** How many links appear on EVERY page examined. This is the site's shared
400
416
  * navigation, derived rather than assumed: no `<nav>` element is required,
401
417
  * because plenty of sites do not use one. */
402
418
  sharedNavLinks: number;
403
419
  pagesExamined: number;
420
+ /**
421
+ * Pages whose anchor list hit `MAX_ANCHORS`, so we read only its first 300
422
+ * links (see `anchorsTruncated`).
423
+ *
424
+ * They take no part in the template comparison in EITHER direction, and that
425
+ * is the difference from the journey graph, where a truncated page's links
426
+ * stay in as edges. Shared navigation is derived by MAJORITY VOTE, and a link
427
+ * that fell past the cap is indistinguishable from a link the page does not
428
+ * have — so an incomplete page silently votes "absent" for every one of them
429
+ * and can push a genuine nav link below the threshold. It is not purely
430
+ * positive evidence here the way an edge is there, so it is excluded from the
431
+ * derivation as well as from the finding.
432
+ */
433
+ pagesWithTruncatedAnchors: string[];
404
434
  };
405
435
 
406
436
  /**
@@ -1087,6 +1117,18 @@ type JourneyMap = {
1087
1117
  * page on the site being a dead end.
1088
1118
  */
1089
1119
  anchorsMeasured: boolean;
1120
+ /**
1121
+ * Pages whose anchor list hit `MAX_ANCHORS`, so we read only its first 300
1122
+ * links (see `anchorsTruncated`).
1123
+ *
1124
+ * These pages are still in the graph and their links are still edges — a link
1125
+ * we read is a real link, and the evidence is only ever additive in that
1126
+ * direction. What they can never be is `deadEnds`: that finding is drawn from
1127
+ * ABSENCE, and a list we cut off establishes nothing about what came after
1128
+ * the cut. Reported rather than silently withheld, so "we could not judge
1129
+ * this page" is a visible claim rather than an unexplained gap.
1130
+ */
1131
+ pagesWithTruncatedAnchors: string[];
1090
1132
  };
1091
1133
 
1092
1134
  /** Every pipeline stage resolves to this. A failed stage degrades its report
@@ -2,7 +2,7 @@ import {
2
2
  LAYER_LABELS,
3
3
  LAYER_ORDER,
4
4
  tally
5
- } from "../chunk-LAJ3FF54.js";
5
+ } from "../chunk-5UI4FERC.js";
6
6
  import {
7
7
  GOAL_LABELS,
8
8
  orderRequirements
@@ -1,3 +1,10 @@
1
+ import {
2
+ DAILY_CAP_LOOKBACK,
3
+ PROSPECT_AUDIT_DAILY_CAP,
4
+ countAuditsInDailyWindow,
5
+ dailyCapMessage,
6
+ isOverDailyCap
7
+ } from "./chunk-DZZ5KRTB.js";
1
8
  import {
2
9
  hostnameOf,
3
10
  isHttpUrl,
@@ -96,6 +103,25 @@ function summarize(result, link, file, warnings, recovery, email) {
96
103
  }
97
104
  return lines.join("\n");
98
105
  }
106
+ var NO_DB_CAP_WARNING = `No TURSO_DATABASE_URL, so prior audits cannot be counted \u2014 the 24h runaway brake (cap ${PROSPECT_AUDIT_DAILY_CAP}) is NOT protecting this run.`;
107
+ async function checkDailyCap(canPersist, opts) {
108
+ const now = (opts.now ?? (() => /* @__PURE__ */ new Date()))();
109
+ const listRecent = opts.listRecent ?? (canPersist ? async (limit) => {
110
+ const { openDb, readDbConfig } = await import("./client-3XGSZMLU.js");
111
+ const { listRecentProspectAudits } = await import("./prospect-audits-IZVQY3OQ.js");
112
+ return listRecentProspectAudits(await openDb(readDbConfig()), limit);
113
+ } : null);
114
+ if (listRecent === null) return { kind: "unchecked", warning: NO_DB_CAP_WARNING };
115
+ try {
116
+ const count = countAuditsInDailyWindow(await listRecent(DAILY_CAP_LOOKBACK), now);
117
+ return isOverDailyCap(count) ? { kind: "over", count } : { kind: "under", count };
118
+ } catch (err) {
119
+ return {
120
+ kind: "unchecked",
121
+ warning: `Could not read recent audits, so the 24h runaway brake (cap ${PROSPECT_AUDIT_DAILY_CAP}) is NOT protecting this run: ${errorMessage(err)}`
122
+ };
123
+ }
124
+ }
99
125
  async function runProspectAuditCommand(url, opts) {
100
126
  if (!isHttpUrl(url)) {
101
127
  return fail(`"${url}" is not a URL. Pass the full address, e.g. https://example.com`);
@@ -109,7 +135,14 @@ async function runProspectAuditCommand(url, opts) {
109
135
  "No TURSO_DATABASE_URL, so the report cannot be saved or shared. Re-run with --out <file>, or set the Turso credentials."
110
136
  );
111
137
  }
112
- const { runProspectAudit } = await import("./pipeline-UUPXOS2J.js");
138
+ const warnings = [];
139
+ const cap = await checkDailyCap(canPersist, opts);
140
+ if (cap.kind === "over") return fail(dailyCapMessage(cap.count));
141
+ if (cap.kind === "unchecked") {
142
+ warnings.push(cap.warning);
143
+ console.error(`! ${cap.warning}`);
144
+ }
145
+ const { runProspectAudit } = await import("./pipeline-TCSNVDQZ.js");
113
146
  const onStage = (name, status, detail) => {
114
147
  if (status === "start") console.error(`\u2026 ${name}`);
115
148
  else if (status === "ok") console.error(`\u2713 ${name}`);
@@ -140,9 +173,8 @@ async function runProspectAuditCommand(url, opts) {
140
173
  },
141
174
  { ...opts.deps ?? {}, onStage }
142
175
  );
143
- const { renderProspectReport } = await import("./render-MHUUHPLN.js");
176
+ const { renderProspectReport } = await import("./render-NWUAX6RN.js");
144
177
  const html = renderProspectReport(result);
145
- const warnings = [];
146
178
  let file = null;
147
179
  if (opts.out) {
148
180
  try {
@@ -157,7 +189,7 @@ async function runProspectAuditCommand(url, opts) {
157
189
  let auditId = null;
158
190
  if (canPersist) {
159
191
  try {
160
- const { openDb, readDbConfig } = await import("./client-DGTO65WB.js");
192
+ const { openDb, readDbConfig } = await import("./client-3XGSZMLU.js");
161
193
  const { createProspectAudit } = await import("./prospect-audits-IZVQY3OQ.js");
162
194
  const db = await openDb(readDbConfig());
163
195
  const created = await createProspectAudit(db, {
@@ -222,4 +254,4 @@ export {
222
254
  GOALS,
223
255
  runProspectAuditCommand
224
256
  };
225
- //# sourceMappingURL=prospect-audit-FMPGQIRQ.js.map
257
+ //# sourceMappingURL=prospect-audit-KWP5P5U2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/commands/prospect-audit.ts","../src/prospect/report-url.ts"],"sourcesContent":["import { mkdir, writeFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { hostnameOf, isHttpUrl, isPrivateOrLoopbackHost } from \"../../util/url.js\";\nimport { reportUrl, reportPrintUrl } from \"../../prospect/report-url.js\";\nimport type { ProspectAuditStatus } from \"../../db/prospect-audits.js\";\nimport type { PipelineDeps, StageName } from \"../../prospect/pipeline.js\";\nimport type { ProspectAuditResult } from \"../../prospect/types.js\";\nimport type { SendAuditEmailResult } from \"../../prospect/email.js\";\nimport type { SiteGoal } from \"../../prospect/goals.js\";\nimport {\n PROSPECT_AUDIT_DAILY_CAP,\n DAILY_CAP_LOOKBACK,\n countAuditsInDailyWindow,\n isOverDailyCap,\n dailyCapMessage,\n type DatedAudit,\n} from \"../../prospect/daily-cap.js\";\n\n/** The operator-selectable goals, in the order the dispatch dropdown lists\n * them. `unknown` is deliberately absent: it is a finding the audit can reach\n * on its own, never something an operator would choose. */\nexport const GOALS = [\n \"book\",\n \"enquire\",\n \"call\",\n \"visit\",\n \"buy\",\n \"demo\",\n \"partner\",\n] as const satisfies readonly SiteGoal[];\n\nfunction isSiteGoal(value: string): value is SiteGoal {\n return (GOALS as readonly string[]).includes(value);\n}\n\nexport type ProspectAuditCliOptions = {\n business?: string;\n /**\n * The one action the site should produce from a visitor, chosen by the\n * operator instead of inferred from the site.\n *\n * Worth overriding because the operator knows things the site does not say.\n * A practice that wants online bookings but has never built one reads as\n * \"call\" from its own pages, and grading it as \"call\" would score it well for\n * doing the thing they are trying to stop doing.\n */\n goal?: string;\n /** Comma-separated competitor domains. */\n competitors?: string;\n /** cac sets this false for `--no-probes`. */\n probes?: boolean;\n out?: string;\n json?: boolean;\n /**\n * Email the internal sheet (scores, what wasn't measured and why, top\n * fixes, and the shareable link) to `PROSPECT_AUDIT_RECIPIENTS`, once the\n * report is rendered and the `--out`/Turso delivery attempts finish. A\n * missing/empty recipients var, or a Resend failure, is a warning — never a\n * crash — because by the time this runs the audit is already delivered (or\n * recovered) by the code above it.\n */\n email?: boolean;\n /**\n * #676. Search terms and buyer questions chosen by hand, comma-separated.\n *\n * Blank generates them, exactly as before — this is an override for a client\n * we already know, and for a comparison over time, where the terms have to\n * stay fixed between runs.\n */\n terms?: string;\n questions?: string;\n /** Test seam: injected pipeline deps. Never set from the CLI. */\n deps?: PipelineDeps;\n /**\n * Test seam: newest-first recent audits for the daily cap. Never set from the\n * CLI — the real implementation opens Turso below. Same injection shape\n * `ProspectAuditTriggerDeps.listRecent` uses on the dashboard side, so both\n * paths' cap tests drive the brake the same way and neither needs a database.\n */\n listRecent?: (limit: number) => Promise<DatedAudit[]>;\n /** Test seam: injectable clock for the cap's 24h window. */\n now?: () => Date;\n};\n\ntype RecoveryWrite = { htmlPath: string; jsonPath: string };\n\nfunction fail(message: string): { output: string; code: number } {\n return { output: message, code: 2 };\n}\n\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\nfunction scoreLine(label: string, value: number | null): string {\n return `${label.padEnd(14)} ${value === null ? \"not measured\" : String(value).padStart(3)}`;\n}\n\n/** Item 3: the `status` column exists to answer, without deserializing the\n * whole result_json, whether this was a clean run or a degraded one. crawl\n * is excluded — it's the one fatal stage (see pipeline.ts), so by the time a\n * ProspectAuditResult exists it was always ok. Every other stage's\n * StageResult already collapses \"failed\" and \"deliberately skipped\"\n * (--no-probes, the checks→analyze cascade) into the same `ok: false` — both\n * mean the report has a \"not measured\" section, so both count as partial\n * here too. */\nfunction auditStatus(result: ProspectAuditResult): ProspectAuditStatus {\n const allStagesOk =\n result.checks.ok && result.lighthouse.ok && result.analyze.ok && result.probes.ok;\n return allStagesOk ? \"complete\" : \"partial\";\n}\n\n/** Filesystem-safe, never empty. Collapses anything outside [a-z0-9.-] to a\n * single dash so a hostname becomes a sane filename fragment. */\nfunction slugify(s: string): string {\n const slug = s\n .toLowerCase()\n .replace(/[^a-z0-9.-]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n return slug || \"prospect\";\n}\n\n/**\n * Last-resort write when NEITHER `--out` nor the database persist made it —\n * the one situation a paid, already-run audit must never end in silently. Lands\n * in `reports/`, mirroring header-image.ts's own convention for CLI-written\n * artifacts (mkdir -p, resolved against cwd), rather than a bare file dropped\n * into whatever directory the operator happened to be standing in when they\n * ran the command — often unrelated to this repo at all in fleet-tool usage.\n */\nasync function writeRecoveryFiles(\n result: ProspectAuditResult,\n html: string,\n): Promise<RecoveryWrite> {\n const dir = resolve(\"reports\");\n await mkdir(dir, { recursive: true });\n const stamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const base = `prospect-audit-${slugify(hostnameOf(result.url))}-${stamp}`;\n const htmlPath = resolve(dir, `${base}.html`);\n const jsonPath = resolve(dir, `${base}.json`);\n await writeFile(htmlPath, html, \"utf-8\");\n await writeFile(jsonPath, JSON.stringify(result, null, 2), \"utf-8\");\n return { htmlPath, jsonPath };\n}\n\nfunction summarize(\n result: ProspectAuditResult,\n link: string | null,\n file: string | null,\n warnings: string[],\n recovery: RecoveryWrite | null,\n email: SendAuditEmailResult | null,\n): string {\n const lines = [\n `Prospect audit — ${result.businessName ?? result.url}`,\n \"\",\n scoreLine(\"Findability\", result.scores.findability),\n scoreLine(\"Readability\", result.scores.readability),\n scoreLine(\"Answers\", result.scores.answers),\n // No AI Visibility line: the score was removed from every presentation\n // surface (web, print, email, here) — the measurement lives on as the\n // report's receipts section, not as a number that reads as ours to move.\n ];\n for (const [name, stage] of [\n [\"checks\", result.checks],\n [\"lighthouse\", result.lighthouse],\n [\"analyze\", result.analyze],\n [\"probes\", result.probes],\n ] as const) {\n if (!stage.ok) lines.push(` ! ${name} not measured — ${stage.error}`);\n }\n if (file) lines.push(\"\", `Report written to ${file}`);\n if (link) lines.push(\"\", `Shareable link: ${link}`);\n if (email?.sent) {\n lines.push(\"\", `Emailed to ${email.recipients.join(\", \")} (${email.messageId})`);\n }\n for (const w of warnings) lines.push(\"\", `! ${w}`);\n if (recovery) {\n lines.push(\n \"\",\n \"Neither --out nor the database took this report — wrote a recovery copy instead:\",\n ` ${recovery.htmlPath}`,\n ` ${recovery.jsonPath}`,\n );\n }\n return lines.join(\"\\n\");\n}\n\n/** What the runaway brake concluded before any money was spent. */\ntype DailyCapCheck =\n | { kind: \"under\"; count: number }\n | { kind: \"over\"; count: number }\n /** The brake could not be applied at all — carries the sentence the operator\n * must see, because a guard that quietly abstains is worse than none. */\n | { kind: \"unchecked\"; warning: string };\n\nconst NO_DB_CAP_WARNING =\n \"No TURSO_DATABASE_URL, so prior audits cannot be counted — the 24h runaway brake \" +\n `(cap ${PROSPECT_AUDIT_DAILY_CAP}) is NOT protecting this run.`;\n\n/**\n * MED-15 (2026-09-02): the daily cap was enforced only in the dashboard\n * dispatch path, and every batch to date — including the 29-site corpus — ran\n * through this CLI, which had none. #618 fixed exactly this shape for the\n * private-host guard twenty lines above, and its comment describes the guard it\n * did NOT fix: \"one layer at the far end of the chain — so anyone running the\n * CLI directly, or any future second caller, had none.\"\n *\n * Counted, not re-implemented: the window, the threshold and the wording all\n * come from `src/prospect/daily-cap.ts`, which the dashboard path uses too.\n *\n * **When there is no database, this WARNS and lets the run proceed.** A run with\n * no persistence cannot count prior audits, and refusing would break the\n * documented `--out`-only local path — which is also, by construction, a\n * one-off: an unpersisted run is invisible to every later count, so it is not\n * the automated path the brake exists for. What it must never do is pass in\n * silence, so the sentence goes to stderr AND into the run's warnings, where\n * `--json` consumers see it too. Same answer if the count itself throws: a\n * Turso blip must not block a legitimate audit, but must not look like a\n * passed check either.\n */\nasync function checkDailyCap(\n canPersist: boolean,\n opts: ProspectAuditCliOptions,\n): Promise<DailyCapCheck> {\n const now = (opts.now ?? (() => new Date()))();\n const listRecent =\n opts.listRecent ??\n (canPersist\n ? async (limit: number): Promise<DatedAudit[]> => {\n const { openDb, readDbConfig } = await import(\"../../db/client.js\");\n const { listRecentProspectAudits } = await import(\"../../db/prospect-audits.js\");\n return listRecentProspectAudits(await openDb(readDbConfig()), limit);\n }\n : null);\n if (listRecent === null) return { kind: \"unchecked\", warning: NO_DB_CAP_WARNING };\n\n try {\n const count = countAuditsInDailyWindow(await listRecent(DAILY_CAP_LOOKBACK), now);\n return isOverDailyCap(count) ? { kind: \"over\", count } : { kind: \"under\", count };\n } catch (err) {\n return {\n kind: \"unchecked\",\n warning:\n `Could not read recent audits, so the 24h runaway brake (cap ${PROSPECT_AUDIT_DAILY_CAP}) ` +\n `is NOT protecting this run: ${errorMessage(err)}`,\n };\n }\n}\n\n/**\n * Run one prospect audit end to end. Progress goes to stderr so `--json` stdout\n * stays pipeable. Persistence needs Turso; without it `--out` is mandatory,\n * because an audit nobody can read afterwards is just a bill.\n *\n * By the time `runProspectAudit` resolves, the money is already spent — only\n * the crawl is fatal, every other stage degrades rather than throwing. So the\n * `--out` write and the database persist below are each wrapped in their own\n * try/catch and attempted independently: a bad `--out` path must not cost a\n * working Turso persist (or vice versa), and if BOTH fail — or persist fails\n * with no `--out` given at all — a recovery copy goes to `reports/` rather\n * than the run ending with nothing anywhere. `--json` only changes the SHAPE\n * of stdout; it still runs the same `--out`/persist/recovery logic beneath it.\n */\nexport async function runProspectAuditCommand(\n url: string,\n opts: ProspectAuditCliOptions,\n): Promise<{ output: string; code: number }> {\n if (!isHttpUrl(url)) {\n return fail(`\"${url}\" is not a URL. Pass the full address, e.g. https://example.com`);\n }\n // #612 review: this validated only the SHAPE. The sole private-address guard\n // was in `triggerProspectAudit` — one layer at the far end of the chain — so\n // anyone running the CLI directly, or any future second caller, had none.\n // `crawlSite` now refuses too; this keeps the refusal a clean message instead\n // of a thrown crawl error.\n if (isPrivateOrLoopbackHost(new URL(url).hostname)) {\n return fail(`\"${url}\" is a private or loopback address — refusing to audit it.`);\n }\n const canPersist = Boolean(process.env.TURSO_DATABASE_URL);\n if (!canPersist && !opts.out) {\n return fail(\n \"No TURSO_DATABASE_URL, so the report cannot be saved or shared. Re-run with --out <file>, or set the Turso credentials.\",\n );\n }\n\n // Collected from here on, so the brake's verdict can be recorded BEFORE the\n // pipeline runs — a warning printed only in the summary arrives after the\n // money is spent.\n const warnings: string[] = [];\n\n // MED-15. The last refusal before anything is spent (see checkDailyCap).\n const cap = await checkDailyCap(canPersist, opts);\n if (cap.kind === \"over\") return fail(dailyCapMessage(cap.count));\n if (cap.kind === \"unchecked\") {\n warnings.push(cap.warning);\n console.error(`! ${cap.warning}`);\n }\n\n const { runProspectAudit } = await import(\"../../prospect/pipeline.js\");\n const onStage = (name: StageName, status: \"start\" | \"ok\" | \"fail\", detail?: string): void => {\n if (status === \"start\") console.error(`… ${name}`);\n else if (status === \"ok\") console.error(`✓ ${name}`);\n else console.error(`! ${name} — ${detail ?? \"failed\"}`);\n };\n\n const business = opts.business?.trim();\n const goal = opts.goal?.trim();\n // #676. Comma-separated on a flag (a newline is not typeable in one); the\n // cockpit's textareas split on newlines instead. Both drop blanks, and both\n // treat \"nothing usable\" as \"generate\".\n const splitList = (v: string | undefined): string[] =>\n (v ?? \"\")\n .split(\",\")\n .map((x) => x.trim())\n .filter((x) => x !== \"\");\n const chosenTerms = splitList(opts.terms);\n const chosenQuestions = splitList(opts.questions);\n if (goal !== undefined && goal !== \"\" && !isSiteGoal(goal)) {\n return fail(`--goal must be one of: ${GOALS.join(\", \")}`);\n }\n\n const result = await runProspectAudit(\n url,\n {\n ...(business ? { business } : {}),\n // An empty string is what a blank dispatch field sends; it must read as\n // \"not supplied\" so the model's own inference still runs, rather than\n // overriding it with nothing.\n ...(goal ? { goal } : {}),\n ...(opts.competitors\n ? {\n competitors: opts.competitors\n .split(\",\")\n .map((c) => c.trim())\n .filter(Boolean),\n }\n : {}),\n ...(opts.probes === false ? { probes: false } : {}),\n ...(chosenTerms.length > 0 ? { terms: chosenTerms } : {}),\n ...(chosenQuestions.length > 0 ? { questions: chosenQuestions } : {}),\n },\n { ...(opts.deps ?? {}), onStage },\n );\n\n const { renderProspectReport } = await import(\"../../prospect/render.js\");\n const html = renderProspectReport(result);\n\n let file: string | null = null;\n if (opts.out) {\n try {\n await writeFile(opts.out, html, \"utf-8\");\n file = opts.out;\n } catch (err) {\n warnings.push(`Could not write --out (${opts.out}): ${errorMessage(err)}`);\n }\n }\n\n let link: string | null = null;\n let token: string | null = null;\n let auditId: string | null = null;\n if (canPersist) {\n try {\n const { openDb, readDbConfig } = await import(\"../../db/client.js\");\n const { createProspectAudit } = await import(\"../../db/prospect-audits.js\");\n const db = await openDb(readDbConfig());\n const created = await createProspectAudit(db, {\n url: result.url,\n // Map at the boundary: ProspectAuditResult.businessName is the field\n // name (Item 2 — it's a resolved NAME, not a description); the\n // `prospect_audits.business` column keeps its existing name, since\n // renaming a column needs its own migration for no benefit here.\n business: result.businessName,\n status: auditStatus(result),\n resultJson: JSON.stringify(result),\n // #676: on the ROW, not only in result_json — a re-run reuses them and\n // the /audits listing marks them without reading the blob.\n chosenTerms: chosenTerms.length > 0 ? chosenTerms : null,\n chosenQuestions: chosenQuestions.length > 0 ? chosenQuestions : null,\n });\n auditId = created.id;\n token = created.token;\n link = reportUrl(token);\n } catch (err) {\n warnings.push(`Could not save to the database: ${errorMessage(err)}`);\n }\n }\n\n const delivered = file !== null || link !== null;\n const recovery = delivered ? null : await writeRecoveryFiles(result, html);\n const code = delivered ? 0 : 1;\n\n // The audit is already delivered (or recovered) above — an email failure\n // must never take that away. Same independent try/catch shape as the\n // --out write and the Turso persist: record a warning, keep going, never\n // touch `code`. Dynamically imported for the same reason db/client.js and\n // prospect/render.js are above: it reaches `resend` (a devDependency) and\n // must stay out of bin.js's static import graph (see bin.ts's\n // central-dep-blocker comment).\n // The PDF leave-behind, printed from the website's print route — a document\n // designed for paper, not the interactive report with its evidence folded\n // away behind disclosures.\n //\n // Best-effort by design. Every other stage in this pipeline degrades rather\n // than throwing, and an attachment is not worth losing a delivered report\n // over: if this fails the email still goes, with the link, and a warning says\n // the PDF is missing. It also needs a token — with no persisted report there\n // is no page to print.\n let pdf: Buffer | null = null;\n if (opts.email && token) {\n try {\n const { renderReportPdf } = await import(\"../../prospect/pdf.js\");\n pdf = await renderReportPdf(reportPrintUrl(token));\n } catch (err) {\n warnings.push(`Could not attach the PDF: ${errorMessage(err)}`);\n }\n }\n\n let email: SendAuditEmailResult | null = null;\n if (opts.email) {\n try {\n const { sendAuditEmail, parseProspectAuditRecipients } =\n await import(\"../../prospect/email.js\");\n const recipients = parseProspectAuditRecipients(process.env.PROSPECT_AUDIT_RECIPIENTS);\n email = await sendAuditEmail(result, { link, recipients, auditId, pdf });\n if (!email.sent) {\n warnings.push(`Could not email the audit sheet: ${email.reason}`);\n }\n } catch (err) {\n warnings.push(`Could not email the audit sheet: ${errorMessage(err)}`);\n }\n }\n\n if (opts.json) {\n return {\n output: JSON.stringify(\n { result, out: file, link, token, recovery, warnings, email },\n null,\n 2,\n ),\n code,\n };\n }\n\n return { output: summarize(result, link, file, warnings, recovery, email), code };\n}\n","/** Where the prospect-facing report lives now: a real, branded route on the\n * marketing site rather than generated HTML on the ops app's domain. */\nexport const DEFAULT_REPORT_BASE_URL = \"https://reddoorla.com\";\n\n/**\n * Resolve the public report origin.\n *\n * Deliberately its own variable rather than reusing `DASHBOARD_BASE_URL`. That\n * one addresses operators and points at the ops app; this one addresses a\n * prospect and points at reddoorla.com. They are different audiences on\n * different domains, and sharing a variable would mean one of them silently\n * moving the day the other is repointed.\n */\nexport function resolveReportBaseUrl(raw: string | undefined): string {\n return (raw?.trim() || DEFAULT_REPORT_BASE_URL).replace(/\\/$/, \"\");\n}\n\n/** The link a prospect opens. */\nexport function reportUrl(token: string, raw = process.env.REPORT_BASE_URL): string {\n return `${resolveReportBaseUrl(raw)}/audit/${token}`;\n}\n\n/** The print-designed variant, which the runner renders to a PDF. Same token,\n * same guards — a separate document, not a stylesheet over the page. */\nexport function reportPrintUrl(token: string, raw = process.env.REPORT_BASE_URL): string {\n return `${reportUrl(token, raw)}/print`;\n}\n"],"mappings":"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","names":[]}
@@ -3,8 +3,8 @@ import {
3
3
  PROBES_SKIPPED,
4
4
  domainOf,
5
5
  resolveBusinessName
6
- } from "./chunk-QFSL2L2B.js";
7
- import "./chunk-LAJ3FF54.js";
6
+ } from "./chunk-L5F3WYIY.js";
7
+ import "./chunk-5UI4FERC.js";
8
8
  import {
9
9
  escapeHtml,
10
10
  safeUrl
@@ -328,4 +328,4 @@ function renderProspectReport(result) {
328
328
  export {
329
329
  renderProspectReport
330
330
  };
331
- //# sourceMappingURL=render-MHUUHPLN.js.map
331
+ //# sourceMappingURL=render-NWUAX6RN.js.map
@@ -109,7 +109,7 @@ async function runRenovateDispatchCommand(opts) {
109
109
  }
110
110
  const base = opts.base ?? openBase(readAirtableConfig());
111
111
  const websites = await (opts.roster ?? (async () => {
112
- const { readFleetRoster } = await import("./roster-GDNSIRAK.js");
112
+ const { readFleetRoster } = await import("./roster-EJ3ILR52.js");
113
113
  return readFleetRoster();
114
114
  }))();
115
115
  const targets = selectRenovateTargets(websites);
@@ -146,4 +146,4 @@ async function runRenovateDispatchCommand(opts) {
146
146
  export {
147
147
  runRenovateDispatchCommand
148
148
  };
149
- //# sourceMappingURL=renovate-dispatch-2IHAZDVA.js.map
149
+ //# sourceMappingURL=renovate-dispatch-ZZQXT7DA.js.map
@@ -0,0 +1,77 @@
1
+ import {
2
+ markDeadLetterReplayed,
3
+ readUnreplayedDeadLetters
4
+ } from "./chunk-TTT6HYPW.js";
5
+ import {
6
+ ingestSubmission
7
+ } from "./chunk-Z7FDJ35L.js";
8
+ import "./chunk-NO74DXDP.js";
9
+ import "./chunk-I7NHLVVI.js";
10
+ import "./chunk-2OP4JKIQ.js";
11
+
12
+ // src/forms/replay.ts
13
+ async function replayDeadLetters(db, deps) {
14
+ const queue = await readUnreplayedDeadLetters(db);
15
+ const result = {
16
+ replayed: [],
17
+ stillFailing: [],
18
+ unmarked: [],
19
+ unreadable: [...queue.unreadable]
20
+ };
21
+ for (const row of queue.rows) {
22
+ let outcome;
23
+ try {
24
+ outcome = await replayOne(db, deps, row);
25
+ } catch (err) {
26
+ result.stillFailing.push({ id: row.id, error: `replay threw: ${String(err)}` });
27
+ continue;
28
+ }
29
+ if (outcome.terminal) {
30
+ result.replayed.push({
31
+ id: row.id,
32
+ outcome: outcome.outcome,
33
+ submissionId: outcome.submissionId
34
+ });
35
+ } else if (outcome.unmarked) {
36
+ result.unmarked.push({
37
+ id: row.id,
38
+ outcome: outcome.outcome,
39
+ submissionId: outcome.submissionId,
40
+ error: outcome.error
41
+ });
42
+ } else {
43
+ result.stillFailing.push({ id: row.id, error: outcome.error });
44
+ }
45
+ }
46
+ return result;
47
+ }
48
+ async function markTerminal(db, row, outcome, submissionId, now) {
49
+ try {
50
+ await markDeadLetterReplayed(db, row.id, outcome, submissionId, now);
51
+ } catch (err) {
52
+ return { terminal: false, unmarked: true, outcome, submissionId, error: String(err) };
53
+ }
54
+ return { terminal: true, outcome, submissionId };
55
+ }
56
+ async function replayOne(db, deps, row) {
57
+ const { deadLetter: _stripped, ...safeDeps } = deps;
58
+ void _stripped;
59
+ let res;
60
+ try {
61
+ res = await ingestSubmission(safeDeps, row.siteSlug, row.payload, row.turnstile);
62
+ } catch (err) {
63
+ return { terminal: false, error: String(err) };
64
+ }
65
+ const now = deps.now();
66
+ if (res.status === "accepted") {
67
+ return markTerminal(db, row, "accepted", res.submissionId, now);
68
+ }
69
+ if (res.status === "unknown-site") {
70
+ return { terminal: false, error: `unknown-site: no fleet row for '${row.siteSlug}'` };
71
+ }
72
+ return markTerminal(db, row, `rejected:${res.reason}`, null, now);
73
+ }
74
+ export {
75
+ replayDeadLetters
76
+ };
77
+ //# sourceMappingURL=replay-HB44TAII.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/forms/replay.ts"],"sourcesContent":["import type { Db } from \"../db/client.js\";\nimport {\n readUnreplayedDeadLetters,\n markDeadLetterReplayed,\n type DeadLetterRow,\n type UnreadableDeadLetterRow,\n} from \"../db/deadletter.js\";\nimport { ingestSubmission, type IngestDeps } from \"./ingest.js\";\n\nexport type ReplayResult = {\n /** Terminal outcomes this run: the row will never be picked up again. */\n replayed: Array<{ id: string; outcome: string; submissionId: string | null }>;\n /** Rows whose lookup threw AGAIN — left unreplayed for the next run. */\n stillFailing: Array<{ id: string; error: string }>;\n /** MED-10(b). Rows that DID re-ingest — a real submission exists for them —\n * but whose terminal mark could not be written. Distinct from `stillFailing`\n * on purpose: that bucket means \"nothing was written, retry freely\", which is\n * the one thing nobody may believe about these. Re-running replay before the\n * row is reconciled WILL mint a duplicate submission, so each entry names the\n * submission that already exists. */\n unmarked: Array<{ id: string; outcome: string; submissionId: string | null; error: string }>;\n /** MED-10(c). Rows whose stored JSON could not be decoded — they cannot be\n * replayed at all until a human looks. Reported so they are visible; still\n * queued, so nothing is destroyed by reporting them. */\n unreadable: UnreadableDeadLetterRow[];\n};\n\n/**\n * Run every unreplayed dead-letter back through `ingestSubmission` — the SAME\n * pipeline a live submission takes, so a replayed lead gets real spam\n * classification, notify, and fan-out, not a bespoke re-implementation.\n *\n * Each row replays with the Turnstile verification computed at receipt (tokens\n * expire in 300s; re-verifying is impossible by design, see deadletter.ts).\n *\n * `deps` must NOT wire `deadLetter`: a lookup that throws during replay would\n * otherwise dead-letter the SAME lead a second time while the original row stays\n * unreplayed — every retry would mint a duplicate. Enforced here by stripping the\n * key rather than trusting the caller. A thrown lookup leaves the row untouched\n * for the next run; `accepted` and `rejected` are terminal — re-running cannot\n * improve on an answer the store actually gave. `unknown-site` USED to be\n * terminal too and no longer is: see replayOne below (#645).\n *\n * Oldest-first (listUnreplayedDeadLetters orders by received_at) so the\n * duplicate/velocity spam signals see submissions in arrival order.\n */\nexport async function replayDeadLetters(\n db: Db,\n deps: Omit<IngestDeps, \"deadLetter\">,\n): Promise<ReplayResult> {\n const queue = await readUnreplayedDeadLetters(db);\n const result: ReplayResult = {\n replayed: [],\n stillFailing: [],\n unmarked: [],\n unreadable: [...queue.unreadable],\n };\n\n for (const row of queue.rows) {\n // MED-10(b). One row's failure must never abort the ones behind it. Every\n // outcome replayOne can reach is already a return value; this catch is the\n // backstop for anything it cannot anticipate (a `deps.now()` that throws, a\n // dep wired wrong), so a surprise costs one lead's run, not the queue's.\n let outcome: Awaited<ReturnType<typeof replayOne>>;\n try {\n outcome = await replayOne(db, deps, row);\n } catch (err) {\n result.stillFailing.push({ id: row.id, error: `replay threw: ${String(err)}` });\n continue;\n }\n if (outcome.terminal) {\n result.replayed.push({\n id: row.id,\n outcome: outcome.outcome,\n submissionId: outcome.submissionId,\n });\n } else if (outcome.unmarked) {\n result.unmarked.push({\n id: row.id,\n outcome: outcome.outcome,\n submissionId: outcome.submissionId,\n error: outcome.error,\n });\n } else {\n result.stillFailing.push({ id: row.id, error: outcome.error });\n }\n }\n return result;\n}\n\n/**\n * MED-10(b). Write the terminal mark, and never let its failure escape.\n *\n * ORDERING. Ingest-then-mark is kept, because the alternative (mark first) turns\n * every ingest failure into a lead marked replayed that no submission exists\n * for — silent loss, which is strictly worse than a duplicate the operator can\n * see. What changes is what a failed mark MEANS: it is no longer an exception\n * that unwinds `replayDeadLetters` with the row looking like an ordinary retry.\n * It is its own outcome, naming the submission that already exists, and it\n * cannot touch the rows behind it.\n *\n * RESIDUAL, stated honestly: the mark is a durable write and its failure means\n * the store refused a write. Nothing in-process can persist the fact, so the\n * row IS still queued and the next unattended run will re-ingest it and mint a\n * duplicate. That is why this is not retried quietly and not folded into\n * `stillFailing`: the run's output is the only record that exists, so it has to\n * be loud enough to reach a person before the next run does.\n */\nasync function markTerminal(\n db: Db,\n row: DeadLetterRow,\n outcome: string,\n submissionId: string | null,\n now: Date,\n): Promise<ReplayOutcome> {\n try {\n await markDeadLetterReplayed(db, row.id, outcome, submissionId, now);\n } catch (err) {\n return { terminal: false, unmarked: true, outcome, submissionId, error: String(err) };\n }\n return { terminal: true, outcome, submissionId };\n}\n\ntype ReplayOutcome =\n | { terminal: true; outcome: string; submissionId: string | null }\n | { terminal: false; unmarked?: false; error: string }\n | {\n terminal: false;\n unmarked: true;\n outcome: string;\n submissionId: string | null;\n error: string;\n };\n\nasync function replayOne(\n db: Db,\n deps: Omit<IngestDeps, \"deadLetter\">,\n row: DeadLetterRow,\n): Promise<ReplayOutcome> {\n // Strip deadLetter defensively even though the type forbids it — a structurally\n // wider object passed through `as` must not re-arm the duplicate loop.\n const { deadLetter: _stripped, ...safeDeps } = deps as IngestDeps;\n void _stripped;\n let res;\n try {\n res = await ingestSubmission(safeDeps, row.siteSlug, row.payload, row.turnstile);\n } catch (err) {\n return { terminal: false, error: String(err) };\n }\n const now = deps.now();\n if (res.status === \"accepted\") {\n return markTerminal(db, row, \"accepted\", res.submissionId, now);\n }\n // #645. `unknown-site` is NO LONGER terminal. It used to be, on the reasoning\n // that a slug the store rejects \"can never improve\" — true while nothing could\n // create the missing row. `ensure-site` can now heal one (#645 item 1), and\n // `ingestSubmission` now dead-letters `unknown-site` leads instead of dropping\n // them, so this queue holds real client leads whose site is merely absent.\n //\n // Terminality would then be an ORDERING TRAP: replay-before-heal marks every\n // such lead replayed-and-lost, and that is the order a person reaches for\n // first. Leaving the row queued makes both orders safe.\n //\n // The cost is a slug that is genuinely gone piling up rows and holding this\n // command at exit 1. Deliberate: loud and recoverable beats silent and not.\n if (res.status === \"unknown-site\") {\n return { terminal: false, error: `unknown-site: no fleet row for '${row.siteSlug}'` };\n }\n return markTerminal(db, row, `rejected:${res.reason}`, null, now);\n}\n"],"mappings":";;;;;;;;;;;;AA8CA,eAAsB,kBACpB,IACA,MACuB;AACvB,QAAM,QAAQ,MAAM,0BAA0B,EAAE;AAChD,QAAM,SAAuB;AAAA,IAC3B,UAAU,CAAC;AAAA,IACX,cAAc,CAAC;AAAA,IACf,UAAU,CAAC;AAAA,IACX,YAAY,CAAC,GAAG,MAAM,UAAU;AAAA,EAClC;AAEA,aAAW,OAAO,MAAM,MAAM;AAK5B,QAAI;AACJ,QAAI;AACF,gBAAU,MAAM,UAAU,IAAI,MAAM,GAAG;AAAA,IACzC,SAAS,KAAK;AACZ,aAAO,aAAa,KAAK,EAAE,IAAI,IAAI,IAAI,OAAO,iBAAiB,OAAO,GAAG,CAAC,GAAG,CAAC;AAC9E;AAAA,IACF;AACA,QAAI,QAAQ,UAAU;AACpB,aAAO,SAAS,KAAK;AAAA,QACnB,IAAI,IAAI;AAAA,QACR,SAAS,QAAQ;AAAA,QACjB,cAAc,QAAQ;AAAA,MACxB,CAAC;AAAA,IACH,WAAW,QAAQ,UAAU;AAC3B,aAAO,SAAS,KAAK;AAAA,QACnB,IAAI,IAAI;AAAA,QACR,SAAS,QAAQ;AAAA,QACjB,cAAc,QAAQ;AAAA,QACtB,OAAO,QAAQ;AAAA,MACjB,CAAC;AAAA,IACH,OAAO;AACL,aAAO,aAAa,KAAK,EAAE,IAAI,IAAI,IAAI,OAAO,QAAQ,MAAM,CAAC;AAAA,IAC/D;AAAA,EACF;AACA,SAAO;AACT;AAoBA,eAAe,aACb,IACA,KACA,SACA,cACA,KACwB;AACxB,MAAI;AACF,UAAM,uBAAuB,IAAI,IAAI,IAAI,SAAS,cAAc,GAAG;AAAA,EACrE,SAAS,KAAK;AACZ,WAAO,EAAE,UAAU,OAAO,UAAU,MAAM,SAAS,cAAc,OAAO,OAAO,GAAG,EAAE;AAAA,EACtF;AACA,SAAO,EAAE,UAAU,MAAM,SAAS,aAAa;AACjD;AAaA,eAAe,UACb,IACA,MACA,KACwB;AAGxB,QAAM,EAAE,YAAY,WAAW,GAAG,SAAS,IAAI;AAC/C,OAAK;AACL,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,iBAAiB,UAAU,IAAI,UAAU,IAAI,SAAS,IAAI,SAAS;AAAA,EACjF,SAAS,KAAK;AACZ,WAAO,EAAE,UAAU,OAAO,OAAO,OAAO,GAAG,EAAE;AAAA,EAC/C;AACA,QAAM,MAAM,KAAK,IAAI;AACrB,MAAI,IAAI,WAAW,YAAY;AAC7B,WAAO,aAAa,IAAI,KAAK,YAAY,IAAI,cAAc,GAAG;AAAA,EAChE;AAaA,MAAI,IAAI,WAAW,gBAAgB;AACjC,WAAO,EAAE,UAAU,OAAO,OAAO,mCAAmC,IAAI,QAAQ,IAAI;AAAA,EACtF;AACA,SAAO,aAAa,IAAI,KAAK,YAAY,IAAI,MAAM,IAAI,MAAM,GAAG;AAClE;","names":[]}