@reddoorla/maintenance 0.82.0 → 0.84.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 (129) hide show
  1. package/dist/airtable-5ZPU3MT4.js +9 -0
  2. package/dist/{announce-7QHQGJPV.js → announce-4H64L2Z6.js} +4 -4
  3. package/dist/{blux-EATHOHKK.js → blux-ZGDSHI3U.js} +5 -5
  4. package/dist/{bump-deps-ZREAMK7M.js → bump-deps-2OL6PGTM.js} +5 -5
  5. package/dist/{chunk-OIDDJLS7.js → chunk-2WN4SNBQ.js} +2 -2
  6. package/dist/{chunk-HVOOCK6L.js → chunk-4DHQCN5H.js} +2 -1
  7. package/dist/{chunk-5E4L7WKE.js → chunk-5P2HVNLZ.js} +3 -3
  8. package/dist/{chunk-ZXRRKKMR.js → chunk-6V7565O2.js} +8 -8
  9. package/dist/{chunk-YRM6EED7.js → chunk-CLWNAMUX.js} +28 -2
  10. package/dist/chunk-CLWNAMUX.js.map +1 -0
  11. package/dist/{chunk-VXXF4Y25.js → chunk-FSRJAF2Q.js} +60 -3
  12. package/dist/chunk-FSRJAF2Q.js.map +1 -0
  13. package/dist/{chunk-B5ZUQZFL.js → chunk-ICXV25N7.js} +2 -2
  14. package/dist/{chunk-UH5YPYS4.js → chunk-JVWJJ36K.js} +5 -4
  15. package/dist/chunk-JVWJJ36K.js.map +1 -0
  16. package/dist/{chunk-4PW6ISX7.js → chunk-KV7DIRRO.js} +3 -3
  17. package/dist/{chunk-ODC4PXZL.js → chunk-MQUOUVQP.js} +2 -2
  18. package/dist/{chunk-LMCO7CYQ.js → chunk-OQFOMKBP.js} +2 -2
  19. package/dist/{chunk-FFI7P74K.js → chunk-PBCGXXG6.js} +2 -2
  20. package/dist/{chunk-T4FQ6TZT.js → chunk-QRDB4NLB.js} +6 -6
  21. package/dist/{chunk-K5JZ5KLO.js → chunk-R4AB6GCT.js} +3 -3
  22. package/dist/chunk-R53VL3RI.js +32 -0
  23. package/dist/chunk-R53VL3RI.js.map +1 -0
  24. package/dist/{chunk-FX7RAMGC.js → chunk-R6FPFMOS.js} +3 -3
  25. package/dist/{chunk-MGJ72BA4.js → chunk-RJB3QUSP.js} +2 -2
  26. package/dist/{chunk-ATJ6ZA2V.js → chunk-SDUJIANJ.js} +2 -2
  27. package/dist/{chunk-7ADQL5Y3.js → chunk-THEFTZ7F.js} +2 -2
  28. package/dist/{chunk-NT4WIP7I.js → chunk-UYCAF77E.js} +2 -2
  29. package/dist/chunk-VNB5XBQ4.js +699 -0
  30. package/dist/chunk-VNB5XBQ4.js.map +1 -0
  31. package/dist/{chunk-YAMRVXOD.js → chunk-VRWUL4TR.js} +2 -2
  32. package/dist/{chunk-2BHSGLWR.js → chunk-XDCRVYFL.js} +3 -3
  33. package/dist/chunk-XDCRVYFL.js.map +1 -0
  34. package/dist/{chunk-4NAOR44V.js → chunk-ZMIPG6TR.js} +2 -2
  35. package/dist/cli/bin.js +51 -23
  36. package/dist/cli/bin.js.map +1 -1
  37. package/dist/cli/commands/audit.d.ts +1 -1
  38. package/dist/cli/commands/audit.js +10 -10
  39. package/dist/{convert-to-pnpm-UT2R6EOW.js → convert-to-pnpm-KCXOYOHW.js} +5 -5
  40. package/dist/{digest-CLFUWW62.js → digest-WGEPAWMP.js} +12 -7
  41. package/dist/digest-WGEPAWMP.js.map +1 -0
  42. package/dist/{ensure-site-LLPOXLWP.js → ensure-site-HYAIVZY2.js} +2 -2
  43. package/dist/{forms-notify-target-PN6P3WPW.js → forms-notify-target-HSL4ZMT5.js} +2 -2
  44. package/dist/{github-signals-HTH7474O.js → github-signals-I7XACMCE.js} +6 -6
  45. package/dist/{header-image-WMHZFU7B.js → header-image-ADYPTGVY.js} +2 -2
  46. package/dist/{health-endpoint-4E53ZMLE.js → health-endpoint-JNCUILHO.js} +6 -6
  47. package/dist/index.d.ts +65 -3
  48. package/dist/index.js +32 -31
  49. package/dist/index.js.map +1 -1
  50. package/dist/{init-3ZJ7Z5NG.js → init-FLGLJCMJ.js} +13 -13
  51. package/dist/{launch-FRCDOB56.js → launch-AVKH2JVF.js} +10 -10
  52. package/dist/{lighthouse-airtable-I46QCEIS.js → lighthouse-airtable-CHANWVO6.js} +3 -3
  53. package/dist/{onboard-45NPDMMW.js → onboard-HF4FTJDE.js} +5 -5
  54. package/dist/{orchestrate-34JYNIXT.js → orchestrate-5FMNYJEA.js} +3 -3
  55. package/dist/{preflight-WVD32COX.js → preflight-HTWIX3MN.js} +4 -4
  56. package/dist/prismic-ci-4PDATHU6.js +447 -0
  57. package/dist/prismic-ci-4PDATHU6.js.map +1 -0
  58. package/dist/prismic-models-HXCAM4ZL.js +1020 -0
  59. package/dist/prismic-models-HXCAM4ZL.js.map +1 -0
  60. package/dist/{protection-audit-2UR45SOC.js → protection-audit-64PLLBLI.js} +3 -3
  61. package/dist/recipes/sync-configs.d.ts +1 -1
  62. package/dist/{renovate-dispatch-LOOCNZTW.js → renovate-dispatch-HBZG2UEU.js} +4 -4
  63. package/dist/{report-CLF2753O.js → report-LV7NY5W3.js} +20 -13
  64. package/dist/report-LV7NY5W3.js.map +1 -0
  65. package/dist/{self-updating-5TJ2NB26.js → self-updating-4Q65OVXY.js} +6 -6
  66. package/dist/{selftest-BTCZLCTL.js → selftest-K5XZKVTE.js} +7 -7
  67. package/dist/{smoke-suite-GHKXMKIR.js → smoke-suite-J3LL3JZJ.js} +6 -6
  68. package/dist/{submissions-KUXLKCSQ.js → submissions-GMBUP4LV.js} +2 -2
  69. package/dist/{svelte-codemods-YTCTLK7G.js → svelte-codemods-JTMX4BSX.js} +4 -4
  70. package/dist/{sync-configs-KSSQZU5L.js → sync-configs-5DXSL64Q.js} +4 -4
  71. package/dist/{types-DnEL3NRn.d.ts → types-OIQZ5hWN.d.ts} +1 -1
  72. package/dist/{upgrade-UDAPY3SN.js → upgrade-HCR25P25.js} +5 -5
  73. package/dist/{websites-JXG3WWAU.js → websites-VITQ3WXN.js} +6 -2
  74. package/dist/{write-audits-to-airtable-7D3LV4KP.js → write-audits-to-airtable-3SOXLKJK.js} +4 -4
  75. package/package.json +1 -1
  76. package/dist/airtable-ZDHLKK5K.js +0 -9
  77. package/dist/chunk-2BHSGLWR.js.map +0 -1
  78. package/dist/chunk-KRDLHAKK.js +0 -17
  79. package/dist/chunk-KRDLHAKK.js.map +0 -1
  80. package/dist/chunk-UH5YPYS4.js.map +0 -1
  81. package/dist/chunk-VXXF4Y25.js.map +0 -1
  82. package/dist/chunk-YRM6EED7.js.map +0 -1
  83. package/dist/digest-CLFUWW62.js.map +0 -1
  84. package/dist/report-CLF2753O.js.map +0 -1
  85. /package/dist/{airtable-ZDHLKK5K.js.map → airtable-5ZPU3MT4.js.map} +0 -0
  86. /package/dist/{announce-7QHQGJPV.js.map → announce-4H64L2Z6.js.map} +0 -0
  87. /package/dist/{blux-EATHOHKK.js.map → blux-ZGDSHI3U.js.map} +0 -0
  88. /package/dist/{bump-deps-ZREAMK7M.js.map → bump-deps-2OL6PGTM.js.map} +0 -0
  89. /package/dist/{chunk-OIDDJLS7.js.map → chunk-2WN4SNBQ.js.map} +0 -0
  90. /package/dist/{chunk-HVOOCK6L.js.map → chunk-4DHQCN5H.js.map} +0 -0
  91. /package/dist/{chunk-5E4L7WKE.js.map → chunk-5P2HVNLZ.js.map} +0 -0
  92. /package/dist/{chunk-ZXRRKKMR.js.map → chunk-6V7565O2.js.map} +0 -0
  93. /package/dist/{chunk-B5ZUQZFL.js.map → chunk-ICXV25N7.js.map} +0 -0
  94. /package/dist/{chunk-4PW6ISX7.js.map → chunk-KV7DIRRO.js.map} +0 -0
  95. /package/dist/{chunk-ODC4PXZL.js.map → chunk-MQUOUVQP.js.map} +0 -0
  96. /package/dist/{chunk-LMCO7CYQ.js.map → chunk-OQFOMKBP.js.map} +0 -0
  97. /package/dist/{chunk-FFI7P74K.js.map → chunk-PBCGXXG6.js.map} +0 -0
  98. /package/dist/{chunk-T4FQ6TZT.js.map → chunk-QRDB4NLB.js.map} +0 -0
  99. /package/dist/{chunk-K5JZ5KLO.js.map → chunk-R4AB6GCT.js.map} +0 -0
  100. /package/dist/{chunk-FX7RAMGC.js.map → chunk-R6FPFMOS.js.map} +0 -0
  101. /package/dist/{chunk-MGJ72BA4.js.map → chunk-RJB3QUSP.js.map} +0 -0
  102. /package/dist/{chunk-ATJ6ZA2V.js.map → chunk-SDUJIANJ.js.map} +0 -0
  103. /package/dist/{chunk-7ADQL5Y3.js.map → chunk-THEFTZ7F.js.map} +0 -0
  104. /package/dist/{chunk-NT4WIP7I.js.map → chunk-UYCAF77E.js.map} +0 -0
  105. /package/dist/{chunk-YAMRVXOD.js.map → chunk-VRWUL4TR.js.map} +0 -0
  106. /package/dist/{chunk-4NAOR44V.js.map → chunk-ZMIPG6TR.js.map} +0 -0
  107. /package/dist/{convert-to-pnpm-UT2R6EOW.js.map → convert-to-pnpm-KCXOYOHW.js.map} +0 -0
  108. /package/dist/{ensure-site-LLPOXLWP.js.map → ensure-site-HYAIVZY2.js.map} +0 -0
  109. /package/dist/{forms-notify-target-PN6P3WPW.js.map → forms-notify-target-HSL4ZMT5.js.map} +0 -0
  110. /package/dist/{github-signals-HTH7474O.js.map → github-signals-I7XACMCE.js.map} +0 -0
  111. /package/dist/{header-image-WMHZFU7B.js.map → header-image-ADYPTGVY.js.map} +0 -0
  112. /package/dist/{health-endpoint-4E53ZMLE.js.map → health-endpoint-JNCUILHO.js.map} +0 -0
  113. /package/dist/{init-3ZJ7Z5NG.js.map → init-FLGLJCMJ.js.map} +0 -0
  114. /package/dist/{launch-FRCDOB56.js.map → launch-AVKH2JVF.js.map} +0 -0
  115. /package/dist/{lighthouse-airtable-I46QCEIS.js.map → lighthouse-airtable-CHANWVO6.js.map} +0 -0
  116. /package/dist/{onboard-45NPDMMW.js.map → onboard-HF4FTJDE.js.map} +0 -0
  117. /package/dist/{orchestrate-34JYNIXT.js.map → orchestrate-5FMNYJEA.js.map} +0 -0
  118. /package/dist/{preflight-WVD32COX.js.map → preflight-HTWIX3MN.js.map} +0 -0
  119. /package/dist/{protection-audit-2UR45SOC.js.map → protection-audit-64PLLBLI.js.map} +0 -0
  120. /package/dist/{renovate-dispatch-LOOCNZTW.js.map → renovate-dispatch-HBZG2UEU.js.map} +0 -0
  121. /package/dist/{self-updating-5TJ2NB26.js.map → self-updating-4Q65OVXY.js.map} +0 -0
  122. /package/dist/{selftest-BTCZLCTL.js.map → selftest-K5XZKVTE.js.map} +0 -0
  123. /package/dist/{smoke-suite-GHKXMKIR.js.map → smoke-suite-J3LL3JZJ.js.map} +0 -0
  124. /package/dist/{submissions-KUXLKCSQ.js.map → submissions-GMBUP4LV.js.map} +0 -0
  125. /package/dist/{svelte-codemods-YTCTLK7G.js.map → svelte-codemods-JTMX4BSX.js.map} +0 -0
  126. /package/dist/{sync-configs-KSSQZU5L.js.map → sync-configs-5DXSL64Q.js.map} +0 -0
  127. /package/dist/{upgrade-UDAPY3SN.js.map → upgrade-HCR25P25.js.map} +0 -0
  128. /package/dist/{websites-JXG3WWAU.js.map → websites-VITQ3WXN.js.map} +0 -0
  129. /package/dist/{write-audits-to-airtable-7D3LV4KP.js.map → write-audits-to-airtable-3SOXLKJK.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/reports/digest.ts"],"sourcesContent":["// src/reports/digest.ts\nimport { openBase, readAirtableConfig, type AirtableBase } from \"./airtable/client.js\";\nimport { listAllReports, isPendingApproval } from \"./airtable/reports.js\";\nimport type { ReportRow } from \"./airtable/reports.js\";\nimport { listWebsites, siteSlug, type WebsiteRow } from \"./airtable/websites.js\";\nimport { defaultResendClient, type ResendClient } from \"./send/resend.js\";\nimport { isIdempotencyConflict } from \"./send/idempotency.js\";\nimport {\n collectVulnAlerts,\n collectDeliveryFailures,\n collectPreflightBlocked,\n collectLighthouseAlerts,\n collectRenovateAlerts,\n collectCiAlerts,\n collectAnalyticsFailures,\n collectTurnstileGuardrailAlerts,\n collectNotifyBounceAlerts,\n collectPrismicDriftAlerts,\n NOTIFY_BOUNCE_WINDOW_DAYS,\n} from \"../alerts/digest-collectors.js\";\nimport { diffAttention, readDigestState, writeDigestState } from \"../alerts/digest-state.js\";\nimport { escapeHtml as esc } from \"../util/html.js\";\nimport type {\n AttentionItem,\n AttentionSeverity,\n AttentionStatus,\n ReadyItem,\n DigestSections,\n SubmissionsDigestSection,\n} from \"../alerts/attention.js\";\n// Type-only: erased at compile time, so the kysely/libsql devDependency never loads\n// in a consuming fleet site (the runtime read is the dynamic import in\n// fetchSubmissionsDigestCounts, same rule as fetchNotifyBounceCounts).\nimport type { SiteSubmissionCounts } from \"../db/submissions.js\";\n\n// The attention/digest contract lives in `../alerts/attention.ts` (a dependency-free\n// types module) so the `alerts/*` collectors can depend on it without importing back\n// from this renderer/IO module — see attention.ts for the cycle it breaks. Re-exported\n// here so existing `from \"./digest.js\"` type importers keep resolving.\nexport type {\n AttentionItem,\n AttentionSeverity,\n AttentionStatus,\n ReadyItem,\n DigestSections,\n SubmissionsDigestSection,\n} from \"../alerts/attention.js\";\n\nconst GREY = \"#757575\";\nconst RED = \"#C00\";\n\n/** Shared anchor style — Gmail does not inherit font-family into <a> tags. */\nconst ANCHOR_STYLE = `color:${RED};font-family:helvetica,sans-serif`;\n\nfunction readySection(items: ReadyItem[]): string {\n const heading = `<h2 style=\"color:${RED};font-family:helvetica,sans-serif;font-size:20px;font-weight:700;margin:32px 0 8px\">Ready for your yes</h2>`;\n if (items.length === 0) {\n return `${heading}<p style=\"color:${GREY};font-family:helvetica,sans-serif;font-size:16px;margin:0\">Nothing waiting on you.</p>`;\n }\n const rows = items\n .map((it) => {\n const safeUrl = it.dashboardUrl.startsWith(\"https://\") ? it.dashboardUrl : undefined;\n const link = safeUrl\n ? `<a href=\"${esc(safeUrl)}\" style=\"${ANCHOR_STYLE}\">review &amp; approve</a>`\n : `review &amp; approve`;\n return `\n <tr>\n <td style=\"color:${GREY};font-family:helvetica,sans-serif;font-size:16px;line-height:24px;padding-bottom:8px\">\n <strong style=\"color:#222\">${esc(it.siteName)}</strong> — ${esc(it.reportType)} (${esc(it.period)})\n — ${link}\n </td>\n </tr>`;\n })\n .join(\"\");\n return `${heading}<table role=\"presentation\" style=\"border-collapse:collapse;margin:0\">${rows}</table>`;\n}\n\nconst SEVERITY_ORDER: Record<AttentionSeverity, number> = { critical: 0, warning: 1 };\n\n/** Render the per-item status badge (\"NEW\"/\"WORSE\"); standing items get nothing. */\nfunction attentionBadge(status?: AttentionStatus): string {\n if (status === \"new\")\n return `<strong style=\"color:${RED};font-family:helvetica,sans-serif\">NEW</strong> `;\n if (status === \"worse\")\n return `<strong style=\"color:${RED};font-family:helvetica,sans-serif\">WORSE</strong> `;\n return \"\";\n}\n\nfunction attentionSection(items: AttentionItem[]): string {\n const heading = `<h2 style=\"color:${RED};font-family:helvetica,sans-serif;font-size:20px;font-weight:700;margin:32px 0 8px\">Needs attention</h2>`;\n if (items.length === 0) {\n return `${heading}<p style=\"color:${GREY};font-family:helvetica,sans-serif;font-size:16px;margin:0\">All clear — nothing needs attention.</p>`;\n }\n\n // Group by siteName, preserving first-seen site order; sort within a site by\n // severity (critical first).\n const bySite = new Map<string, AttentionItem[]>();\n for (const it of items) {\n const bucket = bySite.get(it.siteName);\n if (bucket) bucket.push(it);\n else bySite.set(it.siteName, [it]);\n }\n\n const groups = [...bySite.entries()]\n .map(([siteName, siteItems]) => {\n const sorted = [...siteItems].sort(\n (a, b) => SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b.severity],\n );\n const rows = sorted\n .map((it) => {\n const safeUrl = it.url?.startsWith(\"https://\") ? it.url : undefined;\n const titleHtml = safeUrl\n ? `<a href=\"${esc(safeUrl)}\" style=\"${ANCHOR_STYLE}\">${esc(it.title)}</a>`\n : esc(it.title);\n return `\n <tr>\n <td style=\"color:${GREY};font-family:helvetica,sans-serif;font-size:16px;line-height:24px;padding-bottom:8px\">${attentionBadge(it.status)}${titleHtml}</td>\n </tr>`;\n })\n .join(\"\");\n return `\n <tr>\n <td style=\"color:#222;font-family:helvetica,sans-serif;font-size:16px;font-weight:700;padding:8px 0 4px\">${esc(siteName)}</td>\n </tr>\n ${rows}`;\n })\n .join(\"\");\n\n return `${heading}<table role=\"presentation\" style=\"border-collapse:collapse;margin:0\">${groups}</table>`;\n}\n\n/** The \"Submissions (24h)\" telemetry block: fleet totals + a per-site glance line.\n * Context, not an alarm — absent/null section or all-zero totals render nothing\n * (no-noise), and the section never triggers a send on its own (see runDigest). */\nfunction submissionsSection(s: SubmissionsDigestSection | null | undefined): string {\n if (!s) return \"\";\n if (s.leads === 0 && s.signups === 0 && s.spamAuto === 0) return \"\";\n const heading = `<h2 style=\"color:${RED};font-family:helvetica,sans-serif;font-size:20px;font-weight:700;margin:32px 0 8px\">Submissions (24h)</h2>`;\n // All three terms always render together — omitting a zero term next to a nonzero\n // one reads as \"not measured\" rather than \"none\"; simplest honest form.\n const totals = [\n `${s.leads} new lead${s.leads === 1 ? \"\" : \"s\"}`,\n `${s.signups} newsletter/RSVP signup${s.signups === 1 ? \"\" : \"s\"}`,\n `${s.spamAuto} auto-filtered spam`,\n ].join(\" · \");\n const totalLine = `<p style=\"color:${GREY};font-family:helvetica,sans-serif;font-size:16px;margin:0\">${totals}</p>`;\n if (s.bySite.length === 0) return `${heading}${totalLine}`;\n const rows = s.bySite\n .map((site) => {\n const parts: string[] = [];\n if (site.leads > 0) parts.push(`${site.leads} lead${site.leads === 1 ? \"\" : \"s\"}`);\n if (site.signups > 0) parts.push(`${site.signups} signup${site.signups === 1 ? \"\" : \"s\"}`);\n if (site.spamAuto > 0) parts.push(`${site.spamAuto} auto-filtered`);\n return `\n <tr>\n <td style=\"color:${GREY};font-family:helvetica,sans-serif;font-size:16px;line-height:24px;padding-bottom:8px\">\n <strong style=\"color:#222\">${esc(site.siteName)}</strong> — ${parts.join(\" · \")}\n </td>\n </tr>`;\n })\n .join(\"\");\n return `${heading}${totalLine}<table role=\"presentation\" style=\"border-collapse:collapse;margin:8px 0 0\">${rows}</table>`;\n}\n\nconst FROM_ADDRESS = \"Reddoor Reports <reports@reddoorla.com>\";\n/** Single-operator fleet — fallback when OPERATOR_EMAIL is unset. */\nconst DIGEST_OPERATOR_FALLBACK = \"info@reddoorla.com\";\n\n/** UTC \"YYYY-MM-DD\" — the Resend idempotency key suffix, so a same-day cron re-fire dedupes. */\nfunction digestDateKey(d: Date): string {\n return d.toISOString().slice(0, 10);\n}\n\n/**\n * The gate for \"Ready for your yes\": Draft ready ∧ ¬Approved to send ∧ Sent at BLANK.\n *\n * Implemented as `listAllReports(base).filter(...)` (the authorized deviation from the plan's\n * draft, which pre-dated Slice 1's merged fix): `listAllReports` already calls `mapRow`, which\n * handles `Period` correctly, so no local `rawToReportRow` duplicate is needed. The JS filter\n * does real work — the test fake does NOT evaluate `filterByFormula` — so correctness is\n * test-provable here.\n *\n * Exported: the fleet homepage (Task 3.5b) reuses it for the pending-approval count.\n */\nexport async function listPendingApproval(base: AirtableBase): Promise<ReportRow[]> {\n return (await listAllReports(base)).filter(isPendingApproval);\n}\n\n// ── collectAttention (IO wrapper, sibling to runDigest) ──────────────────────\n\nexport type CollectAttentionDeps = {\n base: AirtableBase;\n /** Same baseUrl value runDigest threads; used for the /s/<slug> links. */\n baseUrl: string;\n /** Pre-fetched Websites rows. When supplied (runDigest already read them),\n * collectAttention reuses them instead of issuing a second `listWebsites`. */\n websites?: WebsiteRow[];\n /** Pre-fetched Reports rows. When supplied (runDigest already read them),\n * collectAttention reuses them instead of issuing a second `listAllReports`. */\n reports?: ReportRow[];\n /** Clock for the GitHub-signals staleness gate (collectCiAlerts /\n * collectRenovateAlerts skip a >3-day-stale sweep). Defaults to wall-clock;\n * runDigest threads its run-start `today` so a quiet repo's frozen CI/Renovate\n * signal stops badging once its sweep goes stale. */\n now?: Date;\n /** Pre-fetched per-site bounced-lead-notification counts (tests, or a caller with\n * an open db). When omitted, collectAttention reads them from libSQL itself —\n * defensively, so a missing TURSO env or a Turso blip drops just this signal. */\n notifyBounces?: ReadonlyMap<string, number>;\n};\n\n/** Per-site bounced-notification counts for the notify-bounce collector, read from\n * libSQL over the collector's own window. The db modules are imported DYNAMICALLY:\n * src/db/submissions.ts pulls kysely at top level, and kysely/libsql live in\n * devDependencies consuming fleet sites don't install — a static import here would\n * crash their CLI at require time (same rule as openDb's own lazy loads). Any\n * failure (no TURSO env, Turso down) logs and yields an empty map: the digest\n * must never blank over a missing optional signal. */\nasync function fetchNotifyBounceCounts(now: Date): Promise<ReadonlyMap<string, number>> {\n try {\n const [{ openDb, readDbConfig }, { countNotifyBouncedBySite }, { screenOutsSince }] =\n await Promise.all([\n import(\"../db/client.js\"),\n import(\"../db/submissions.js\"),\n import(\"../db/screenouts.js\"),\n ]);\n const db = await openDb(readDbConfig());\n return await countNotifyBouncedBySite(db, screenOutsSince(now, NOTIFY_BOUNCE_WINDOW_DAYS));\n } catch (e) {\n console.warn(`⚠ notify-bounce counts unavailable (libSQL): ${(e as Error).message}`);\n return new Map();\n }\n}\n\n/** The \"Submissions (24h)\" telemetry window — a precise 24h ISO-timestamp compare\n * (unlike screenOutsSince's date-only strings). */\nconst SUBMISSIONS_DIGEST_WINDOW_MS = 24 * 60 * 60 * 1000;\n\n/** Per-site submission counts for the digest telemetry section, read from libSQL.\n * Dynamic imports + fail-soft for the same reason as fetchNotifyBounceCounts:\n * kysely/libsql are devDependencies consuming fleet sites don't install, and a\n * Turso blip must drop just this section, never the digest. Returns null (not an\n * empty map) on failure so the renderer can omit the section rather than claim a\n * quiet fleet. */\nasync function fetchSubmissionsDigestCounts(\n now: Date,\n): Promise<ReadonlyMap<string, SiteSubmissionCounts> | null> {\n try {\n const [{ openDb, readDbConfig }, { countSubmissionsSinceBySite }] = await Promise.all([\n import(\"../db/client.js\"),\n import(\"../db/submissions.js\"),\n ]);\n const db = await openDb(readDbConfig());\n return await countSubmissionsSinceBySite(\n db,\n new Date(now.getTime() - SUBMISSIONS_DIGEST_WINDOW_MS).toISOString(),\n );\n } catch (e) {\n console.warn(`⚠ submissions telemetry unavailable (libSQL): ${(e as Error).message}`);\n return null;\n }\n}\n\n/** Assemble the \"Submissions (24h)\" section from the per-site counts. PURE (exported\n * for direct tests). Totals sum ALL map entries — an orphan site id still counts in\n * the fleet totals. bySite lists only ids that resolve in `sitesById` AND carry any\n * nonzero count, sorted by total volume desc then siteName A-Z. null counts (libSQL\n * unavailable) → null (section omitted). */\nexport function buildSubmissionsDigestSection(\n counts: ReadonlyMap<string, SiteSubmissionCounts> | null,\n sitesById: ReadonlyMap<string, WebsiteRow>,\n): SubmissionsDigestSection | null {\n if (counts === null) return null;\n let leads = 0;\n let signups = 0;\n let spamAuto = 0;\n const bySite: SubmissionsDigestSection[\"bySite\"] = [];\n for (const [siteId, c] of counts) {\n leads += c.leads;\n signups += c.signups;\n spamAuto += c.spamAuto;\n const site = sitesById.get(siteId);\n if (site && c.leads + c.signups + c.spamAuto > 0) {\n bySite.push({ siteName: site.name, ...c });\n }\n }\n bySite.sort(\n (a, b) =>\n b.leads + b.signups + b.spamAuto - (a.leads + a.signups + a.spamAuto) ||\n a.siteName.localeCompare(b.siteName),\n );\n return { leads, signups, spamAuto, bySite };\n}\n\n/** Run a single collector under a try/catch: a thrown collector logs and yields []\n * so one broken signal never blanks the whole \"Needs attention\" section. */\nfunction runCollector(label: string, fn: () => AttentionItem[]): AttentionItem[] {\n try {\n return fn();\n } catch (e) {\n console.warn(`⚠ attention collector \"${label}\" failed: ${(e as Error).message}`);\n return [];\n }\n}\n\n/**\n * Fetch the free signals once (listAllReports + listWebsites) — or reuse the\n * `reports`/`websites` arrays runDigest already read, so a single run reads each\n * table once — build the sitesById map the delivery collector needs, and run each\n * pure collector isolated. Returns the union of items; diffing/badging happens in\n * runDigest.\n *\n * The Renovate + CI signals come from the SAME persisted collectors the operator\n * cockpit (`buildCockpitModel`) runs — `collectRenovateAlerts` (key\n * `renovate:<siteId>`) and `collectCiAlerts` (key `ci:<siteId>`), reading the\n * nightly-persisted `renovateFailingCis`/`defaultBranchCi` fields. This is the\n * key-space unification: because the digest writes the shared Digest State\n * snapshot with these same keys, the cockpit's NEW/WORSE diff finds them and the\n * two surfaces agree (the prior live per-PR `renovate:<repo>#<n>` sweep never\n * matched the cockpit's keys, so its cards badged NEW forever).\n */\nexport async function collectAttention(deps: CollectAttentionDeps): Promise<AttentionItem[]> {\n const reports = deps.reports ?? (await listAllReports(deps.base));\n const websites = deps.websites ?? (await listWebsites(deps.base));\n const now = deps.now ?? new Date();\n const notifyBounces = deps.notifyBounces ?? (await fetchNotifyBounceCounts(now));\n const sitesById = new Map<string, WebsiteRow>(websites.map((w) => [w.id, w]));\n return [\n ...runCollector(\"vuln\", () => collectVulnAlerts(websites, deps.baseUrl)),\n ...runCollector(\"delivery\", () => collectDeliveryFailures(reports, sitesById, deps.baseUrl)),\n ...runCollector(\"preflight\", () => collectPreflightBlocked(reports, sitesById, deps.baseUrl)),\n ...runCollector(\"lighthouse\", () => collectLighthouseAlerts(websites, deps.baseUrl)),\n ...runCollector(\"renovate\", () => collectRenovateAlerts(websites, deps.baseUrl, now)),\n ...runCollector(\"ci\", () => collectCiAlerts(websites, deps.baseUrl, now)),\n ...runCollector(\"analytics\", () => collectAnalyticsFailures(websites, deps.baseUrl, now)),\n ...runCollector(\"turnstile\", () =>\n collectTurnstileGuardrailAlerts(websites, deps.baseUrl, now),\n ),\n ...runCollector(\"notify-bounce\", () =>\n collectNotifyBounceAlerts(websites, notifyBounces, deps.baseUrl),\n ),\n // Same collector the cockpit runs, over the same persisted `Prismic Models`\n // columns and the same keys — so the snapshot this digest writes is the one\n // the cockpit diffs NEW/WORSE against, and the two surfaces agree.\n ...runCollector(\"prismic-drift\", () => collectPrismicDriftAlerts(websites, deps.baseUrl, now)),\n ];\n}\n\nexport type DigestRunOptions = {\n resend?: ResendClient;\n /** Dashboard origin for the /s/<slug> links, e.g. \"https://reddoor-maintenance.netlify.app\". */\n baseUrl: string;\n /**\n * Inject a pre-opened Airtable base (tests, server handlers).\n * When omitted, `openBase(readAirtableConfig())` is called from the environment.\n */\n base?: AirtableBase;\n /** Per-site submission counts for the \"Submissions (24h)\" section. undefined =\n * fetch from libSQL; null = simulate unavailable (section omitted); a Map =\n * injected (tests). */\n submissionCounts?: ReadonlyMap<string, SiteSubmissionCounts> | null;\n /**\n * The run clock. Production omits it and gets `new Date()`.\n *\n * Injectable because several collectors this run feeds are AGE-SENSITIVE —\n * `collectPrismicDriftAlerts` alone changes an item's key and its wording once\n * a verdict crosses `PRISMIC_DRIFT_STALE_DAYS` — so a digest test that cannot\n * fix the clock is asserting on the day it happens to run. Two did, and both\n * went red four days after they were written, reporting the staleness wording\n * as if the drift wiring had broken. `collectAttention` already takes a `now`\n * for exactly this reason; this threads the same lever one layer out.\n */\n now?: Date;\n};\n\nexport async function runDigest(\n options: DigestRunOptions,\n): Promise<{ output: string; code: number }> {\n // Capture clock BEFORE any await so the idempotency key can't roll past midnight mid-run.\n const today = options.now ?? new Date();\n try {\n const base = options.base ?? openBase(readAirtableConfig());\n // Read each table ONCE for the whole run, then thread the arrays into\n // collectAttention so it doesn't re-fetch (was: listWebsites ×2, listAllReports\n // ×2). Pending is derived in-line with listPendingApproval's exact predicate.\n const reports = await listAllReports(base);\n const websites = await listWebsites(base);\n const sites = new Map(websites.map((w) => [w.id, w]));\n\n const pending = reports.filter(isPendingApproval);\n\n const readyForYourYes: ReadyItem[] = [];\n const baseUrl = options.baseUrl.replace(/\\/$/, \"\");\n for (const r of pending) {\n const site = sites.get(r.siteId);\n if (!site) continue; // orphan report → skip rather than render a broken link\n // An empty Name slugs to \"\" → `/s/` is a dead link (getWebsiteBySlug can't\n // match it). Fall back to the fleet homepage so the operator still lands\n // somewhere usable instead of a 404.\n const slug = siteSlug(site.name);\n readyForYourYes.push({\n siteName: site.name,\n reportType: r.reportType,\n period: r.period ?? \"—\",\n dashboardUrl: slug ? `${baseUrl}/s/${slug}` : baseUrl,\n });\n }\n\n // M5: collect the free signals (isolated), diff against yesterday's snapshot.\n // Renovate + CI come from the persisted collectors (the same ones the cockpit\n // runs), so the snapshot this digest writes carries the `renovate:<siteId>` /\n // `ci:<siteId>` keys the cockpit diffs against — no live GitHub sweep here.\n const collected = await collectAttention({\n base,\n baseUrl: options.baseUrl,\n websites,\n reports,\n now: today,\n });\n const prior = await readDigestState(base);\n const { tagged, next } = diffAttention(collected, prior, digestDateKey(today));\n // The operator only HEARS about a vuln once Renovate's auto-fix is exhausted\n // (tried and failed a couple of nightly cycles) — before that the fleet is\n // still self-patching and the cockpit's amber Watch band is the only surface.\n // Filter the EMAIL list only: `next` (written below) must keep every vuln key\n // so the cockpit's diff against this same snapshot stays consistent, and so\n // the exhausted-flip diffs as WORSE instead of arriving pre-badged-away.\n const needsAttention = tagged.filter(\n (it) => it.kind !== \"vuln\" || it.autoFixExhausted === true,\n );\n\n // No-noise default: skip entirely when there's nothing to report.\n if (readyForYourYes.length === 0 && needsAttention.length === 0) {\n // Persist `next` even on a skip: a key that resolved on a quiet day clears so a\n // later recurrence diffs as NEW (spec §10), and — since the skip can now fire\n // with muted pre-exhaustion vulns collected — their keys keep snapshotting so\n // the eventual exhausted-flip diffs as WORSE. Wrapped: a write failure can't\n // fail the skip.\n try {\n await writeDigestState(base, next);\n } catch (e) {\n console.warn(`⚠ digest state write failed: ${(e as Error).message}`);\n }\n return { output: \"Digest skipped (nothing ready, nothing needs attention).\", code: 0 };\n }\n\n // Submissions telemetry rides only on a digest that is ALREADY sending — the\n // skip predicate above is deliberately unchanged (leads already fire their own\n // ingest-time notification; this is context, not an alarm), and fetching AFTER\n // the skip check keeps quiet days from touching libSQL at all.\n const counts =\n options.submissionCounts !== undefined\n ? options.submissionCounts\n : await fetchSubmissionsDigestCounts(today);\n const submissions = buildSubmissionsDigestSection(counts, sites);\n\n const html = renderDigestHtml({ readyForYourYes, needsAttention, submissions });\n const client = options.resend ?? defaultResendClient();\n const to = [process.env.OPERATOR_EMAIL?.trim() || DIGEST_OPERATOR_FALLBACK];\n const n = readyForYourYes.length;\n const reportWord = n === 1 ? \"report\" : \"reports\";\n let result: Awaited<ReturnType<typeof client.send>>;\n try {\n result = await client.send({\n from: FROM_ADDRESS,\n to,\n subject: `Your fleet — ${digestDateKey(today)}: ${n} ${reportWord} ready for your yes`,\n html,\n idempotencyKey: `digest-${digestDateKey(today)}`,\n });\n } catch (err) {\n // A same-UTC-day re-run whose content changed re-sends with the same\n // `digest-<date>` idempotency key but a DIFFERENT body. Resend rejects that\n // with a 409 (`invalid_idempotent_request`) — \"This idempotency key has been\n // used ... but the request body was modified ...\". The operator already got\n // today's digest on the first send, so re-sending a changed version same-day\n // would just be a duplicate: treat it as an \"already sent today\" no-op.\n //\n // ResendClient (send/resend.ts) wraps the API error as a plain Error and only\n // preserves the *message* string (no name/statusCode), so the message\n // substring is the only reliable discriminator — match defensively on it.\n // Any OTHER send error re-throws to the outer catch → {code:1}, so a genuine\n // Resend/network failure still fails loudly.\n if (isIdempotencyConflict(err)) {\n // Do NOT write the snapshot: the first send already persisted it; writing\n // this run's `next` would diff against the first run's snapshot and mis-badge.\n return {\n output:\n \"Digest already sent today (content changed since the first send) — skipped to avoid a duplicate.\",\n code: 0,\n };\n }\n throw err;\n }\n // Persist the next snapshot AFTER a successful send. A write failure is caught +\n // logged: the digest already went out, tomorrow re-news at worst. (The send-FAILURE\n // path never reaches here — the outer catch returns code 1 with no write, preserving\n // the NEW badge for the retry.)\n try {\n await writeDigestState(base, next);\n } catch (e) {\n console.warn(`⚠ digest state write failed: ${(e as Error).message}`);\n }\n return { output: `Digest sent to ${to.join(\", \")} (${result.messageId})`, code: 0 };\n } catch (err) {\n // Re-throw config errors (exitCode=2: missing env vars, bad config) so runOrExit\n // surfaces them with the correct process exit code rather than collapsing to 1.\n if (typeof (err as { exitCode?: unknown }).exitCode === \"number\") {\n throw err;\n }\n const message = err instanceof Error ? err.message : String(err);\n return { output: `digest failed: ${message}`, code: 1 };\n }\n}\n\n/** Pure render of the unified daily operator digest. No IO — the caller (runDigest)\n * collects the rows and decides whether to send. */\nexport function renderDigestHtml(sections: DigestSections): string {\n return `<!doctype html>\n<html>\n <head><meta charset=\"utf-8\"></head>\n <body style=\"margin:0;padding:0;background:#ffffff\">\n <table width=\"100%\" style=\"border-collapse:collapse\">\n <tr>\n <td align=\"center\" style=\"padding:24px\">\n <table width=\"600\" style=\"border-collapse:collapse\">\n <tr>\n <td>\n <h1 style=\"color:${RED};font-family:helvetica,sans-serif;font-size:24px;font-weight:700;margin:0 0 8px\">Your fleet today</h1>\n ${readySection(sections.readyForYourYes)}\n ${attentionSection(sections.needsAttention)}\n ${submissionsSection(sections.submissions)}\n </td>\n </tr>\n </table>\n </td>\n </tr>\n </table>\n </body>\n</html>`;\n}\n"],"mappings":"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","names":[]}
@@ -2,7 +2,7 @@ import {
2
2
  WEBSITES_TABLE,
3
3
  listWebsites,
4
4
  siteSlug
5
- } from "./chunk-YRM6EED7.js";
5
+ } from "./chunk-CLWNAMUX.js";
6
6
  import {
7
7
  openBase,
8
8
  readAirtableConfig
@@ -84,4 +84,4 @@ async function runEnsureSiteCommand(slug, opts) {
84
84
  export {
85
85
  runEnsureSiteCommand
86
86
  };
87
- //# sourceMappingURL=ensure-site-LLPOXLWP.js.map
87
+ //# sourceMappingURL=ensure-site-HYAIVZY2.js.map
@@ -5,7 +5,7 @@ import {
5
5
  listWebsites,
6
6
  siteSlug,
7
7
  updateSiteField
8
- } from "./chunk-YRM6EED7.js";
8
+ } from "./chunk-CLWNAMUX.js";
9
9
  import {
10
10
  openBase,
11
11
  readAirtableConfig
@@ -255,4 +255,4 @@ export {
255
255
  formatNotifyTarget,
256
256
  runFormsNotifyTargetCommand
257
257
  };
258
- //# sourceMappingURL=forms-notify-target-PN6P3WPW.js.map
258
+ //# sourceMappingURL=forms-notify-target-HSL4ZMT5.js.map
@@ -3,26 +3,26 @@ import {
3
3
  } from "./chunk-PZQ3WED2.js";
4
4
  import {
5
5
  formatFleetWriteSummary
6
- } from "./chunk-2BHSGLWR.js";
7
- import "./chunk-LMCO7CYQ.js";
6
+ } from "./chunk-XDCRVYFL.js";
7
+ import "./chunk-OQFOMKBP.js";
8
8
  import {
9
9
  detectSignalEvents,
10
10
  fleetSweptEvent
11
11
  } from "./chunk-5L3FPQIJ.js";
12
12
  import {
13
13
  makeGitHub
14
- } from "./chunk-YAMRVXOD.js";
14
+ } from "./chunk-VRWUL4TR.js";
15
15
  import {
16
16
  recordFleetEventsBestEffort
17
17
  } from "./chunk-QG6OSL4M.js";
18
18
  import "./chunk-DC5H7KEQ.js";
19
19
  import "./chunk-4SOCPK7A.js";
20
- import "./chunk-HVOOCK6L.js";
20
+ import "./chunk-4DHQCN5H.js";
21
21
  import {
22
22
  listWebsites,
23
23
  siteSlug,
24
24
  updateGitHubSignals
25
- } from "./chunk-YRM6EED7.js";
25
+ } from "./chunk-CLWNAMUX.js";
26
26
  import {
27
27
  openBase,
28
28
  readAirtableConfig
@@ -134,4 +134,4 @@ export {
134
134
  githubSignalsExitCode,
135
135
  runGitHubSignalsCommand
136
136
  };
137
- //# sourceMappingURL=github-signals-HTH7474O.js.map
137
+ //# sourceMappingURL=github-signals-I7XACMCE.js.map
@@ -8,7 +8,7 @@ import {
8
8
  ACTIVE_STATUSES,
9
9
  listWebsites,
10
10
  siteSlug
11
- } from "./chunk-YRM6EED7.js";
11
+ } from "./chunk-CLWNAMUX.js";
12
12
  import {
13
13
  openBase,
14
14
  readAirtableConfig
@@ -92,4 +92,4 @@ export {
92
92
  resolveTargets,
93
93
  runHeaderImageCommand
94
94
  };
95
- //# sourceMappingURL=header-image-WMHZFU7B.js.map
95
+ //# sourceMappingURL=header-image-ADYPTGVY.js.map
@@ -5,17 +5,17 @@ import {
5
5
  appendSkipNotice,
6
6
  fleetWorkdir,
7
7
  prepareFleetSites
8
- } from "./chunk-4NAOR44V.js";
8
+ } from "./chunk-ZMIPG6TR.js";
9
9
  import {
10
10
  resolveSites
11
- } from "./chunk-ODC4PXZL.js";
11
+ } from "./chunk-MQUOUVQP.js";
12
12
  import {
13
13
  healthEndpoint
14
- } from "./chunk-FX7RAMGC.js";
15
- import "./chunk-KRDLHAKK.js";
14
+ } from "./chunk-R6FPFMOS.js";
15
+ import "./chunk-R53VL3RI.js";
16
16
  import "./chunk-ZTPPCHDB.js";
17
17
  import "./chunk-FDIFTIXC.js";
18
- import "./chunk-HVOOCK6L.js";
18
+ import "./chunk-4DHQCN5H.js";
19
19
  import "./chunk-XXTZBPUY.js";
20
20
  import "./chunk-XTK5VIQB.js";
21
21
  import "./chunk-U5SZ4FOP.js";
@@ -51,4 +51,4 @@ async function runHealthEndpointCommand(site, opts) {
51
51
  export {
52
52
  runHealthEndpointCommand
53
53
  };
54
- //# sourceMappingURL=health-endpoint-4E53ZMLE.js.map
54
+ //# sourceMappingURL=health-endpoint-JNCUILHO.js.map
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { a as AuditResult, S as Site, A as AuditName, b as RecipeResult, R as RecipeName, I as InventoryProvider } from './types-DnEL3NRn.js';
2
- export { C as ConfigName } from './types-DnEL3NRn.js';
1
+ import { a as AuditResult, S as Site, A as AuditName, b as RecipeResult, R as RecipeName, I as InventoryProvider } from './types-OIQZ5hWN.js';
2
+ export { C as ConfigName } from './types-OIQZ5hWN.js';
3
3
  import * as airtable_lib_airtable_base_js from 'airtable/lib/airtable_base.js';
4
4
  import { F as FormType } from './types-RXY-vY-5.js';
5
5
  export { SyncConfigsOptions, syncConfigs } from './recipes/sync-configs.js';
@@ -609,8 +609,51 @@ type WebsiteRow = {
609
609
  * freshness AND encodes the n/a-vs-never-ran distinction. */
610
610
  formE2eOk: "pass" | "fail" | null;
611
611
  formE2eCheckedAt: string | null;
612
+ /**
613
+ * Nightly Prismic model drift sweep (`prismic-models --fleet --write-airtable`).
614
+ *
615
+ * FOUR-VALUED, unlike every other verdict column in this table, and the extra
616
+ * state is the point:
617
+ *
618
+ * - `pass` — the repo and its Prismic repository were compared and agree.
619
+ * - `fail` — they were compared and DIVERGE (a model to create/update, or
620
+ * one that exists only in Prismic). A finding for a human.
621
+ * - `unknown` — THE CHECK ITSELF FAILED. Unreadable checkout, broken config,
622
+ * missing/expired token, unreadable remote. Nothing at all was
623
+ * established about this site's models.
624
+ * - `null` — no verdict to hold: the sweep has never run for this site, or
625
+ * it ran and found no Prismic config at all (a repo that simply
626
+ * is not a Prismic site). `prismicModelsCheckedAt` separates
627
+ * those two — fresh timestamp, blank verdict = "checked, and
628
+ * there is nothing here to check".
629
+ *
630
+ * `unknown` exists because the alternatives are both wrong in the dangerous
631
+ * direction. Writing `pass` for a site nobody could read is a green tick over
632
+ * an outage; writing NOTHING (the obvious "leave the last known value alone")
633
+ * leaves yesterday's `pass` standing for a site that has been failing every
634
+ * night since — and nothing ages a stale `pass` out, because the digest's
635
+ * freshness gate only ever examines failures. Both make "I could not read this
636
+ * site" indistinguishable from "this site is fine", in the record the cockpit
637
+ * and the digest read.
638
+ *
639
+ * OPERATOR PRECONDITION: `Prismic Models` is a single select whose options are
640
+ * `pass`, `fail`, `unknown`. Until the three columns exist the sweep's writes
641
+ * fail with UNKNOWN_FIELD_NAME and are collected as soft failures — the feature
642
+ * ships dark rather than reddening the nightly.
643
+ */
644
+ prismicModels: PrismicModelsVerdict | null;
645
+ /** When the sweep last ran for this site — written on EVERY outcome, including
646
+ * `unknown` and blank, so the age of the row is the age of the answer. */
647
+ prismicModelsCheckedAt: string | null;
648
+ /** What the sweep found, verbatim: the drift report for a `fail`, the reason
649
+ * nothing could be established for an `unknown`, the skip reason for a blank,
650
+ * and null for a `pass` (which clears yesterday's finding). */
651
+ prismicModelsDrift: string | null;
612
652
  notifyRouting: NotifyRouting | null;
613
653
  };
654
+ /** The `Prismic Models` cell — see {@link WebsiteRow.prismicModels} for why it
655
+ * carries a third state that no other verdict column in this table has. */
656
+ type PrismicModelsVerdict = "pass" | "fail" | "unknown";
614
657
  type Severity = "low" | "moderate" | "high" | "critical";
615
658
  /** One known vulnerability behind the security counts, as persisted/rendered. */
616
659
  type SecurityAdvisory = {
@@ -828,6 +871,18 @@ type DraftOptions = {
828
871
  previewPath?: string;
829
872
  /** If true: render locally only, never touch Airtable. */
830
873
  previewOnly?: boolean;
874
+ /** Whether to run the GA / Search Console enrichment fetches. Defaults to
875
+ * `base !== null`, i.e. the real drafting path enriches and a preview does not.
876
+ *
877
+ * This exists because `base === null` was carrying two unrelated meanings —
878
+ * "never write to Airtable" AND "perform no IO at all" — and only the first is
879
+ * what `previewOnly` actually asks for. Enrichment reads Google; it writes
880
+ * nothing, so there is no reason a preview cannot do it on request. Conflating
881
+ * the two made the preview path structurally incapable of ever producing an
882
+ * ANALYTICS section, which in turn made a CI job built to prove the GA secrets
883
+ * fail 100% of the time and report it as a credential outage (2026-08-12). Set
884
+ * this true to render a preview that exercises the credentials for real. */
885
+ enrich?: boolean;
831
886
  /** UTC "YYYY-MM" recurrence key; falls back to periodEnd's month when omitted. */
832
887
  period?: string;
833
888
  /** Airtable record id of an EXISTING (not-ready) row to COMPLETE in place rather
@@ -1046,7 +1101,14 @@ type AttentionStatus = "new" | "worse" | "standing";
1046
1101
  type AttentionItem = {
1047
1102
  /** Stable identity for diffing: `vuln:<siteId>`, `delivery:<reportId>`. */
1048
1103
  key: string;
1049
- kind: "vuln" | "delivery" | "renovate" | "lighthouse" | "ci" | "analytics" | "preflight" | "turnstile" | "notify-bounce";
1104
+ kind: "vuln" | "delivery" | "renovate" | "lighthouse" | "ci" | "analytics" | "preflight" | "turnstile" | "notify-bounce"
1105
+ /** The nightly Prismic model sweep. THREE item flavors share this kind —
1106
+ * `prismic-drift:` (repo and Prismic diverge), `prismic-unknown:` (the check
1107
+ * ran and could not answer), `prismic-stale:` (nobody has re-established the
1108
+ * last verdict). One `kind` because they are one signal on the cards and the
1109
+ * client filter; three KEYS because they are three different conditions and a
1110
+ * diff on one must never stand in for another. */
1111
+ | "prismic-drift";
1050
1112
  /** Grouping key in the (component-3) render. */
1051
1113
  siteName: string;
1052
1114
  title: string;
package/dist/index.js CHANGED
@@ -1,48 +1,48 @@
1
1
  import "./chunk-2SLED3NB.js";
2
- import "./chunk-VXXF4Y25.js";
2
+ import "./chunk-FSRJAF2Q.js";
3
+ import {
4
+ approveBlockers
5
+ } from "./chunk-5P2HVNLZ.js";
6
+ import {
7
+ DEFAULT_INIT_STEPS,
8
+ a11yFixturesPage,
9
+ init
10
+ } from "./chunk-6V7565O2.js";
11
+ import {
12
+ onboard,
13
+ selfCaretRange,
14
+ selfPackageVersion
15
+ } from "./chunk-UYCAF77E.js";
16
+ import {
17
+ findDueReports
18
+ } from "./chunk-D4FVAIEJ.js";
3
19
  import {
4
20
  draftReportForSite
5
- } from "./chunk-UH5YPYS4.js";
21
+ } from "./chunk-JVWJJ36K.js";
6
22
  import "./chunk-62VF32IC.js";
7
23
  import {
8
24
  domainFromUrl,
9
25
  generateHeaderImage,
10
26
  loadPlate
11
27
  } from "./chunk-37S6L6G4.js";
12
- import {
13
- approveBlockers
14
- } from "./chunk-5E4L7WKE.js";
15
- import {
16
- findDueReports
17
- } from "./chunk-D4FVAIEJ.js";
18
28
  import {
19
29
  upgradeSvelte4to5
20
- } from "./chunk-B5ZUQZFL.js";
21
- import {
22
- DEFAULT_INIT_STEPS,
23
- a11yFixturesPage,
24
- init
25
- } from "./chunk-ZXRRKKMR.js";
30
+ } from "./chunk-ICXV25N7.js";
26
31
  import {
27
32
  convertToPnpm
28
- } from "./chunk-MGJ72BA4.js";
33
+ } from "./chunk-RJB3QUSP.js";
29
34
  import {
30
35
  svelteCodemods
31
36
  } from "./chunk-LS6QPANO.js";
32
37
  import "./chunk-J7SZQUCW.js";
33
- import "./chunk-FX7RAMGC.js";
34
- import "./chunk-K5JZ5KLO.js";
35
- import "./chunk-KRDLHAKK.js";
36
- import {
37
- onboard,
38
- selfCaretRange,
39
- selfPackageVersion
40
- } from "./chunk-NT4WIP7I.js";
38
+ import "./chunk-R6FPFMOS.js";
39
+ import "./chunk-R4AB6GCT.js";
40
+ import "./chunk-R53VL3RI.js";
41
41
  import {
42
42
  parseAddresses,
43
43
  sendApprovedReports,
44
44
  withGlobalCc
45
- } from "./chunk-FFI7P74K.js";
45
+ } from "./chunk-PBCGXXG6.js";
46
46
  import "./chunk-GECESMVS.js";
47
47
  import {
48
48
  BLURRED_CID,
@@ -62,7 +62,7 @@ import {
62
62
  import "./chunk-WEKL2HYJ.js";
63
63
  import {
64
64
  bumpDeps
65
- } from "./chunk-ATJ6ZA2V.js";
65
+ } from "./chunk-SDUJIANJ.js";
66
66
  import {
67
67
  syncConfigs
68
68
  } from "./chunk-Q276MZ7E.js";
@@ -78,10 +78,10 @@ import {
78
78
  runAudits,
79
79
  runAuditsAcross,
80
80
  securityAudit
81
- } from "./chunk-T4FQ6TZT.js";
82
- import "./chunk-GASBX52O.js";
83
- import "./chunk-YAMRVXOD.js";
81
+ } from "./chunk-QRDB4NLB.js";
84
82
  import "./chunk-LKGVSM2O.js";
83
+ import "./chunk-GASBX52O.js";
84
+ import "./chunk-VRWUL4TR.js";
85
85
  import "./chunk-QG6OSL4M.js";
86
86
  import "./chunk-DC5H7KEQ.js";
87
87
  import "./chunk-4SOCPK7A.js";
@@ -89,7 +89,7 @@ import {
89
89
  fromJsonFile,
90
90
  localPath
91
91
  } from "./chunk-FDIFTIXC.js";
92
- import "./chunk-HVOOCK6L.js";
92
+ import "./chunk-4DHQCN5H.js";
93
93
  import "./chunk-A3WDWNN5.js";
94
94
  import {
95
95
  isLeadFormType
@@ -98,12 +98,12 @@ import "./chunk-XXTZBPUY.js";
98
98
  import "./chunk-XTK5VIQB.js";
99
99
  import {
100
100
  fromAirtableBase
101
- } from "./chunk-OIDDJLS7.js";
101
+ } from "./chunk-2WN4SNBQ.js";
102
102
  import "./chunk-U5SZ4FOP.js";
103
103
  import {
104
104
  SEVERITY_RANK,
105
105
  siteSlug
106
- } from "./chunk-YRM6EED7.js";
106
+ } from "./chunk-CLWNAMUX.js";
107
107
  import "./chunk-HN6I2XWI.js";
108
108
  import "./chunk-6VAL7XJT.js";
109
109
  import "./chunk-NMAWXBJA.js";
@@ -121,6 +121,7 @@ var ALL_RECIPE_NAMES = [
121
121
  "health-endpoint",
122
122
  "smoke-suite",
123
123
  "self-updating",
124
+ "prismic-ci",
124
125
  "init"
125
126
  ];
126
127
  function isRecipeName(value) {