@reddoorla/maintenance 0.69.0 → 0.71.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/airtable-2PJ4NSAV.js +9 -0
  2. package/dist/{announce-72R3LRNT.js → announce-3JE5FKW7.js} +6 -6
  3. package/dist/blux-FKZYMKLF.js +2205 -0
  4. package/dist/blux-FKZYMKLF.js.map +1 -0
  5. package/dist/{bump-deps-QNM3H4GI.js → bump-deps-K3JMLMBB.js} +3 -2
  6. package/dist/bump-deps-K3JMLMBB.js.map +1 -0
  7. package/dist/{chunk-RINXPU3H.js → chunk-2PWQ6MPG.js} +2 -2
  8. package/dist/{chunk-2DCA7FHZ.js → chunk-52TDNN7K.js} +34 -12
  9. package/dist/chunk-52TDNN7K.js.map +1 -0
  10. package/dist/{chunk-XWPZ46IN.js → chunk-5QC5KJHR.js} +2 -2
  11. package/dist/{chunk-O6G4HHMF.js → chunk-6J25IIAI.js} +162 -42
  12. package/dist/chunk-6J25IIAI.js.map +1 -0
  13. package/dist/chunk-7MJBTV2R.js +145 -0
  14. package/dist/chunk-7MJBTV2R.js.map +1 -0
  15. package/dist/{chunk-6JFOAPW4.js → chunk-AYM2EFSK.js} +38 -6
  16. package/dist/chunk-AYM2EFSK.js.map +1 -0
  17. package/dist/{chunk-GKSXZIMX.js → chunk-CMQMNFFJ.js} +77 -3
  18. package/dist/chunk-CMQMNFFJ.js.map +1 -0
  19. package/dist/{chunk-6MT5W6UF.js → chunk-F6OE6MGQ.js} +6 -3
  20. package/dist/chunk-F6OE6MGQ.js.map +1 -0
  21. package/dist/{chunk-OTBA7DON.js → chunk-J2SHWT2O.js} +1 -1
  22. package/dist/chunk-J2SHWT2O.js.map +1 -0
  23. package/dist/{chunk-AESJBEM7.js → chunk-KOAGMTRI.js} +3 -3
  24. package/dist/chunk-KRDLHAKK.js +17 -0
  25. package/dist/chunk-KRDLHAKK.js.map +1 -0
  26. package/dist/{chunk-LYCKN7ZR.js → chunk-LPMQXS2N.js} +8 -4
  27. package/dist/chunk-LPMQXS2N.js.map +1 -0
  28. package/dist/{chunk-6G7BC5SN.js → chunk-M7HOQ4W7.js} +385 -5
  29. package/dist/chunk-M7HOQ4W7.js.map +1 -0
  30. package/dist/{chunk-42KKVLYN.js → chunk-MPRLMGJU.js} +10 -2
  31. package/dist/chunk-MPRLMGJU.js.map +1 -0
  32. package/dist/chunk-PKVZMFEB.js +315 -0
  33. package/dist/chunk-PKVZMFEB.js.map +1 -0
  34. package/dist/{chunk-A5KYRGQA.js → chunk-PSJQE6T6.js} +4 -4
  35. package/dist/chunk-SL24DCGC.js +41 -0
  36. package/dist/chunk-SL24DCGC.js.map +1 -0
  37. package/dist/{chunk-E2GKKUEX.js → chunk-VR246V3G.js} +25 -5
  38. package/dist/chunk-VR246V3G.js.map +1 -0
  39. package/dist/{chunk-45AUGDFR.js → chunk-WN4NEHE7.js} +2 -7
  40. package/dist/chunk-WN4NEHE7.js.map +1 -0
  41. package/dist/{chunk-YQMYQAJX.js → chunk-Y7LZHPDF.js} +2 -2
  42. package/dist/{chunk-C3PDS623.js → chunk-YWRQEDB3.js} +53 -3
  43. package/dist/chunk-YWRQEDB3.js.map +1 -0
  44. package/dist/cli/bin.js +63 -19
  45. package/dist/cli/bin.js.map +1 -1
  46. package/dist/cli/commands/audit.d.ts +1 -1
  47. package/dist/cli/commands/audit.js +11 -9
  48. package/dist/cli/commands/audit.js.map +1 -1
  49. package/dist/configs/playwright-a11y.js +1 -1
  50. package/dist/{convert-to-pnpm-XH7NYRVK.js → convert-to-pnpm-6F4RAFEJ.js} +3 -2
  51. package/dist/convert-to-pnpm-6F4RAFEJ.js.map +1 -0
  52. package/dist/{digest-Q2TAQHXF.js → digest-GZCZYJKC.js} +8 -8
  53. package/dist/{ensure-site-2PF522VL.js → ensure-site-H5VMBPLF.js} +2 -2
  54. package/dist/forms/index.d.ts +2 -0
  55. package/dist/forms/index.js +7 -1
  56. package/dist/forms/index.js.map +1 -1
  57. package/dist/{github-signals-CLQC2IPY.js → github-signals-SQECQNST.js} +5 -5
  58. package/dist/health-endpoint-QXWM6GW2.js +54 -0
  59. package/dist/health-endpoint-QXWM6GW2.js.map +1 -0
  60. package/dist/index.d.ts +132 -17
  61. package/dist/index.js +207 -64
  62. package/dist/index.js.map +1 -1
  63. package/dist/{init-UA6D7GKW.js → init-KQOIPLVT.js} +9 -5
  64. package/dist/init-KQOIPLVT.js.map +1 -0
  65. package/dist/{launch-AENAS2O2.js → launch-RX4VI7F2.js} +9 -9
  66. package/dist/{lighthouse-airtable-UBSXKHAQ.js → lighthouse-airtable-TTXULEGU.js} +3 -3
  67. package/dist/{onboard-X6KJ5Z2V.js → onboard-TFBL62J3.js} +2 -2
  68. package/dist/{orchestrate-GADNFGDH.js → orchestrate-DYA4XAQJ.js} +5 -5
  69. package/dist/{preflight-D5RQGX4V.js → preflight-S3GYME3Q.js} +7 -7
  70. package/dist/probe-DJAMLVTT.js +46 -0
  71. package/dist/probe-DJAMLVTT.js.map +1 -0
  72. package/dist/recipes/sync-configs.d.ts +1 -1
  73. package/dist/{renovate-dispatch-LG4RJGDY.js → renovate-dispatch-MZPAWVBL.js} +2 -2
  74. package/dist/{report-3FJ224H5.js → report-BLZ67QAQ.js} +12 -12
  75. package/dist/run-migration-LJNNZWIV.js +205 -0
  76. package/dist/run-migration-LJNNZWIV.js.map +1 -0
  77. package/dist/{self-updating-JTTFOJ7Q.js → self-updating-GSH427XP.js} +2 -2
  78. package/dist/{selftest-55OJIWEB.js → selftest-KPTL6KC4.js} +8 -8
  79. package/dist/smoke-suite-GP3W57JP.js +54 -0
  80. package/dist/smoke-suite-GP3W57JP.js.map +1 -0
  81. package/dist/{svelte-codemods-KJLNH6FQ.js → svelte-codemods-GYZZX742.js} +3 -2
  82. package/dist/svelte-codemods-GYZZX742.js.map +1 -0
  83. package/dist/{sync-configs-TIBO6DUJ.js → sync-configs-CQNOTJ2B.js} +3 -2
  84. package/dist/sync-configs-CQNOTJ2B.js.map +1 -0
  85. package/dist/{types-Dg4mHvyf.d.ts → types-0jreMCF5.d.ts} +2 -2
  86. package/dist/{upgrade-TPTE26FG.js → upgrade-RTAJZD56.js} +3 -2
  87. package/dist/upgrade-RTAJZD56.js.map +1 -0
  88. package/dist/{websites-6YTT5ZTO.js → websites-SVI4KYSH.js} +8 -2
  89. package/dist/{write-audits-to-airtable-IWOO4FFH.js → write-audits-to-airtable-3XFNWMUE.js} +4 -4
  90. package/package.json +4 -1
  91. package/dist/airtable-WGQQ3KAD.js +0 -9
  92. package/dist/bump-deps-QNM3H4GI.js.map +0 -1
  93. package/dist/chunk-2DCA7FHZ.js.map +0 -1
  94. package/dist/chunk-42KKVLYN.js.map +0 -1
  95. package/dist/chunk-45AUGDFR.js.map +0 -1
  96. package/dist/chunk-6G7BC5SN.js.map +0 -1
  97. package/dist/chunk-6JFOAPW4.js.map +0 -1
  98. package/dist/chunk-6MT5W6UF.js.map +0 -1
  99. package/dist/chunk-C3PDS623.js.map +0 -1
  100. package/dist/chunk-E2GKKUEX.js.map +0 -1
  101. package/dist/chunk-GKSXZIMX.js.map +0 -1
  102. package/dist/chunk-LYCKN7ZR.js.map +0 -1
  103. package/dist/chunk-O6G4HHMF.js.map +0 -1
  104. package/dist/chunk-OTBA7DON.js.map +0 -1
  105. package/dist/convert-to-pnpm-XH7NYRVK.js.map +0 -1
  106. package/dist/init-UA6D7GKW.js.map +0 -1
  107. package/dist/svelte-codemods-KJLNH6FQ.js.map +0 -1
  108. package/dist/sync-configs-TIBO6DUJ.js.map +0 -1
  109. package/dist/upgrade-TPTE26FG.js.map +0 -1
  110. /package/dist/{airtable-WGQQ3KAD.js.map → airtable-2PJ4NSAV.js.map} +0 -0
  111. /package/dist/{announce-72R3LRNT.js.map → announce-3JE5FKW7.js.map} +0 -0
  112. /package/dist/{chunk-RINXPU3H.js.map → chunk-2PWQ6MPG.js.map} +0 -0
  113. /package/dist/{chunk-XWPZ46IN.js.map → chunk-5QC5KJHR.js.map} +0 -0
  114. /package/dist/{chunk-AESJBEM7.js.map → chunk-KOAGMTRI.js.map} +0 -0
  115. /package/dist/{chunk-A5KYRGQA.js.map → chunk-PSJQE6T6.js.map} +0 -0
  116. /package/dist/{chunk-YQMYQAJX.js.map → chunk-Y7LZHPDF.js.map} +0 -0
  117. /package/dist/{digest-Q2TAQHXF.js.map → digest-GZCZYJKC.js.map} +0 -0
  118. /package/dist/{ensure-site-2PF522VL.js.map → ensure-site-H5VMBPLF.js.map} +0 -0
  119. /package/dist/{github-signals-CLQC2IPY.js.map → github-signals-SQECQNST.js.map} +0 -0
  120. /package/dist/{launch-AENAS2O2.js.map → launch-RX4VI7F2.js.map} +0 -0
  121. /package/dist/{lighthouse-airtable-UBSXKHAQ.js.map → lighthouse-airtable-TTXULEGU.js.map} +0 -0
  122. /package/dist/{onboard-X6KJ5Z2V.js.map → onboard-TFBL62J3.js.map} +0 -0
  123. /package/dist/{orchestrate-GADNFGDH.js.map → orchestrate-DYA4XAQJ.js.map} +0 -0
  124. /package/dist/{preflight-D5RQGX4V.js.map → preflight-S3GYME3Q.js.map} +0 -0
  125. /package/dist/{renovate-dispatch-LG4RJGDY.js.map → renovate-dispatch-MZPAWVBL.js.map} +0 -0
  126. /package/dist/{report-3FJ224H5.js.map → report-BLZ67QAQ.js.map} +0 -0
  127. /package/dist/{self-updating-JTTFOJ7Q.js.map → self-updating-GSH427XP.js.map} +0 -0
  128. /package/dist/{selftest-55OJIWEB.js.map → selftest-KPTL6KC4.js.map} +0 -0
  129. /package/dist/{websites-6YTT5ZTO.js.map → websites-SVI4KYSH.js.map} +0 -0
  130. /package/dist/{write-audits-to-airtable-IWOO4FFH.js.map → write-audits-to-airtable-3XFNWMUE.js.map} +0 -0
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-Dg4mHvyf.js';
2
- export { C as ConfigName } from './types-Dg4mHvyf.js';
1
+ import { a as AuditResult, S as Site, A as AuditName, b as RecipeResult, R as RecipeName, I as InventoryProvider } from './types-0jreMCF5.js';
2
+ export { C as ConfigName } from './types-0jreMCF5.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';
@@ -41,7 +41,9 @@ type DiscoverDeps = {
41
41
  fetchText: (url: string) => Promise<string | null>;
42
42
  };
43
43
 
44
- /** One route probed across desktop engines + mobile devices, plus the internal links found on it. */
44
+ /** One route probed across desktop engines + mobile devices, plus the internal links found on it,
45
+ * plus the chromium-derived reachability + SEO signals (captured on the page chromium already
46
+ * opened — no extra navigation). */
45
47
  type RouteResult = {
46
48
  url: string;
47
49
  /** Per desktop engine (chromium/firefox/webkit): loaded with no JS error + a visible main landmark. */
@@ -56,6 +58,12 @@ type RouteResult = {
56
58
  }>;
57
59
  /** Same-origin links discovered on the page (absolute URLs), for the Links check. */
58
60
  links: string[];
61
+ /** HTTP status of the chromium navigation (2xx/3xx = reachable). null = the nav failed/threw. */
62
+ status: number | null;
63
+ /** The chromium `<title>` (trimmed by the reducer), or null when not captured. */
64
+ title: string | null;
65
+ /** The chromium `meta[name="description"]` content, or null when absent/not captured. */
66
+ metaDescription: string | null;
59
67
  };
60
68
  type LinkResult = {
61
69
  url: string;
@@ -68,6 +76,29 @@ type BrowserRunner = {
68
76
  close?: () => Promise<void>;
69
77
  };
70
78
 
79
+ /** Outcome of driving one site's contact form. `formPresent:false` ⇒ n/a
80
+ * (persisted). `testModeUndeclared` ⇒ the site's /health does not declare
81
+ * `forms.testMode`, so the probe refused to submit — a plain skip, prior
82
+ * verdict preserved. */
83
+ type FormSubmitOutcome = {
84
+ formPresent: false;
85
+ } | {
86
+ formPresent: true;
87
+ success: boolean;
88
+ detail?: string;
89
+ } | {
90
+ testModeUndeclared: true;
91
+ };
92
+ /** Injected browser IO. The real impl drives Playwright; tests pass a fake. */
93
+ type FormRunner = {
94
+ submit: (opts: {
95
+ baseUrl: string;
96
+ testMode: boolean;
97
+ testSitekey: string;
98
+ }) => Promise<FormSubmitOutcome>;
99
+ close?: () => Promise<void>;
100
+ };
101
+
71
102
  /** Result of probing a site's latest PRODUCTION Netlify deploy. Every field is
72
103
  * nullable: a site with no production deploy yet degrades to all-nulls (a real
73
104
  * "none" verdict). A read FAILURE is NOT represented here — it surfaces as
@@ -109,6 +140,38 @@ type NetlifyDeployDeps = {
109
140
  now: Date;
110
141
  };
111
142
 
143
+ /** The parts of the deployed `/health` body this audit reads. `ok` is the endpoint's self-rollup
144
+ * (`functionRan && prismic !== "error"`); `prismic` is the server-side CMS probe status; `forms`
145
+ * are booleans recorded for the dashboard only. A public endpoint can return anything, so every
146
+ * field is validated/coerced by `parseHealthBody`. */
147
+ type HealthBody = {
148
+ ok: boolean;
149
+ prismic: "ok" | "error" | "skipped" | null;
150
+ forms: {
151
+ ingestUrl: boolean;
152
+ ingestToken: boolean;
153
+ turnstile: boolean;
154
+ } | null;
155
+ };
156
+ /** Outcome of fetching `{deployedUrl}/health`, distinguishing "no usable report" (`present:false`
157
+ * → self-skip, the writer preserves the prior verdict) from "read a body" (`present:true`
158
+ * → a real pass/fail). `present:true` with a real `HealthBody` is a 200 JSON response (`ok`
159
+ * drives pass/fail); the fetch layer also synthesizes an all-`ok:false` body for "deployed but
160
+ * erroring" responses (a non-404 non-2xx, or a 200 that isn't valid JSON) so those fail loudly
161
+ * instead of silently skipping. Mirrors `netlify-deploy`'s `NetlifyDeployFetch` read/no-read split. */
162
+ type HealthFetch = {
163
+ present: false;
164
+ } | {
165
+ present: true;
166
+ body: HealthBody;
167
+ };
168
+ /** Injected IO so the audit is unit-testable without a network. `fetchHealth` returns a
169
+ * `HealthFetch`; `now` is the clock for the checked-at stamp. */
170
+ type FunctionHealthDeps = {
171
+ fetchHealth: (deployedUrl: string) => Promise<HealthFetch>;
172
+ now: Date;
173
+ };
174
+
112
175
  /** One GitHub Dependabot alert, mapped to the fields the security audit needs. */
113
176
  type DependabotAlert = {
114
177
  /** The vulnerable package name (`dependency.package.name`). */
@@ -147,8 +210,12 @@ type AuditContext = {
147
210
  discoverDeps?: DiscoverDeps;
148
211
  /** Playwright runner injection for the browser audit (tests). Defaults to real Playwright. */
149
212
  browserRunner?: BrowserRunner;
213
+ /** Playwright runner injection for the form-e2e audit (tests). Defaults to real Playwright. */
214
+ formRunner?: FormRunner;
150
215
  /** Netlify API injection for the netlify-deploy audit (tests). Defaults to a real API call. */
151
216
  netlifyDeployDeps?: NetlifyDeployDeps;
217
+ /** `/health` fetch injection for the function-health audit (tests). Defaults to a real GET. */
218
+ functionHealthDeps?: FunctionHealthDeps;
152
219
  /** GitHub Dependabot fetch injection for the security audit (tests). Defaults to a real client
153
220
  * from GITHUB_TOKEN; absent token or no site.gitRepo → the pnpm/npm audit fallback. */
154
221
  dependabotDeps?: DependabotDeps;
@@ -250,10 +317,11 @@ type InitOptions = {
250
317
  steps?: InitStep[];
251
318
  };
252
319
  /** convert-to-pnpm → onboard → sync-configs → svelte-codemods →
253
- * a11y-fixtures-page → audit. Order is deliberate — every step depends on
254
- * the prior one's output (pnpm before onboard's installs, onboard's deps
255
- * before sync-configs writes lighthouserc, fixtures page before audit
256
- * actually has a route to hit). */
320
+ * a11y-fixtures-page → health-endpoint → smoke-suite → audit. Order is
321
+ * deliberate — every step depends on the prior one's output (pnpm before
322
+ * onboard's installs, onboard's deps before sync-configs writes lighthouserc,
323
+ * fixtures page before audit actually has a route to hit; smoke-suite's
324
+ * `pnpm install` lands before the closing audit). */
257
325
  declare const DEFAULT_INIT_STEPS: InitStep[];
258
326
  /**
259
327
  * One-shot guided onboarding. Runs the default step sequence against a
@@ -296,11 +364,15 @@ type AirtableInventoryOptions = {
296
364
  /**
297
365
  * Read sites from the Airtable Websites table as an InventoryProvider.
298
366
  * Each row becomes one Site; `path` is computed as `{workdir}/{slug}`.
299
- * Only `maintenance` / `launch period` sites that have a `url` are included
300
- * (the live sites we audit + report on). The production URL is exposed as
301
- * `Site.deployedUrl` so the lighthouse audit can run against it with no
302
- * checkout. `repoUrl` is intentionally NOT set from `url` a clone source
303
- * must come from `gitRepo` (`owner/repo`), never the production URL.
367
+ * Only LIVE `maintenance` sites that have a `url` are included — pre-launch
368
+ * stages ("launch period" / "in development", via isPreLaunch) are excluded so a
369
+ * not-yet-live site is never audited as production (its deploy/domain/uptime/CMS
370
+ * audits would fail against nothing and red its Airtable row) nor swept by other
371
+ * `--fleet airtable` ops; it re-enters the fleet when a Launch report flips its
372
+ * Status to "maintenance". The production URL is exposed as `Site.deployedUrl` so
373
+ * the lighthouse audit can run against it with no checkout. `repoUrl` is
374
+ * intentionally NOT set from `url` — a clone source must come from `gitRepo`
375
+ * (`owner/repo`), never the production URL.
304
376
  */
305
377
  declare function fromAirtableBase(base: AirtableBase, opts?: AirtableInventoryOptions): InventoryProvider;
306
378
 
@@ -418,6 +490,22 @@ type WebsiteRow = {
418
490
  deployStatus: string | null;
419
491
  lastDeployAt: string | null;
420
492
  deployLogUrl: string | null;
493
+ /** When the `netlify-deploy` audit last RAN (freshness stamp for `deployStatus`). The audit
494
+ * already writes "Deploy checked at"; this read-back is the Plan-2 fix so `deployEvidence`
495
+ * (Plan 4) can gate on check time — NOT on `lastDeployAt`, which is deploy time, not check time. */
496
+ deployCheckedAt: string | null;
497
+ /** Function-health verdict (the `function-health` audit): the deployed `/health` function
498
+ * answered `ok:true` (pass) or `ok:false` (fail). Single-select `pass`/`fail`; null = never ran
499
+ * / unreachable (→ Plan 4 maps to unknown/amber). Kept SEPARATE from `deployStatus` so
500
+ * `isFailedDeployStatus` keeps meaning "the build failed". */
501
+ functionHealth: "pass" | "fail" | null;
502
+ /** CMS reachability (server-side), derived from the same `/health` body's `details.prismic ===
503
+ * "ok"`. Single-select `pass`/`fail`; null = never ran. No per-site Prismic token or identity
504
+ * column is ever built — this rides `/health`. */
505
+ cmsReachable: "pass" | "fail" | null;
506
+ /** When the `function-health` audit last ran — the freshness gate for BOTH `functionHealth` and
507
+ * `cmsReachable`. Null = never ran. */
508
+ functionHealthCheckedAt: string | null;
421
509
  /** Deployed-URL browser probe (the `browser` audit): cross-engine render OK, mobile render OK,
422
510
  * internal-links OK + broken count, and when it last ran (one timestamp gates all three). */
423
511
  crossbrowserOk: boolean | null;
@@ -425,6 +513,13 @@ type WebsiteRow = {
425
513
  linksOk: boolean | null;
426
514
  brokenLinks: number | null;
427
515
  browserCheckedAt: string | null;
516
+ /** Uptime-reachable verdict (browser audit): every sampled route returned 2xx/3xx. Single-select
517
+ * `pass`/`fail`; null = never ran. Point-in-time. Freshness-gated by `browserCheckedAt`. */
518
+ reachableOk: "pass" | "fail" | null;
519
+ /** Titles & meta verdict (browser audit, chromium): every sampled route has a non-empty `<title>`
520
+ * ≤ 70 chars + a non-empty meta description, and no duplicate titles across the sample.
521
+ * Single-select `pass`/`fail`; null = never ran. Freshness-gated by `browserCheckedAt`. */
522
+ titleMetaOk: "pass" | "fail" | null;
428
523
  /** Per-site copy overrides (M6a). Blank → null → the DEFAULT_COPY value. */
429
524
  copyIntro: string | null;
430
525
  copyContact: string | null;
@@ -447,6 +542,16 @@ type WebsiteRow = {
447
542
  defaultBranchCi: string | null;
448
543
  lastCommitAt: string | null;
449
544
  githubSignalsAt: string | null;
545
+ /** Per-site smoke-suite verdict (the `smoke` audit runs `pnpm test:smoke`).
546
+ * Single-select pass/fail; null = never ran. `lastSmokeAt` gates freshness. */
547
+ smokeOk: "pass" | "fail" | null;
548
+ lastSmokeAt: string | null;
549
+ /** Synthetic form end-to-end verdict (the `form-e2e` audit submits the real prod
550
+ * contact form in test-mode). Single-select pass/fail; null = never ran OR (with
551
+ * a fresh `formE2eCheckedAt`) no contact form → n/a. `formE2eCheckedAt` gates
552
+ * freshness AND encodes the n/a-vs-never-ran distinction. */
553
+ formE2eOk: "pass" | "fail" | null;
554
+ formE2eCheckedAt: string | null;
450
555
  notifyRouting: NotifyRouting | null;
451
556
  };
452
557
  type Severity = "low" | "moderate" | "high" | "critical";
@@ -559,7 +664,7 @@ type ReportData = {
559
664
  };
560
665
 
561
666
  /** A single auto-check outcome. `pass` + fresh ⇒ the caller ticks the box. */
562
- type EvidenceResult = "pass" | "fail" | "unknown";
667
+ type EvidenceResult = "pass" | "fail" | "unknown" | "n/a";
563
668
  type EvidenceRecord = {
564
669
  result: EvidenceResult;
565
670
  checkedAt: string | null;
@@ -602,9 +707,17 @@ type ReportRow = {
602
707
  * subset (see src/reports/checklist.ts). */
603
708
  checklist: Record<string, boolean>;
604
709
  /** Snapshot of the auto-tick evidence at draft time, keyed by checklist field → evidence
605
- * record. Null when the report predates auto-tick or carried no auto-checked items. Drives the
606
- * dashboard's green/amber badges; the gate still reads the booleans, not this. */
710
+ * record. Null when the report predates auto-tick or carried no auto-checked items. This is the
711
+ * health gate's source of truth: `isHealthGateClear`/`gatingHealth` (src/reports/checklist.ts)
712
+ * read these evidence records, NOT the checklist booleans — and also drive the dashboard badges. */
607
713
  autoEvidence: Record<string, EvidenceRecord> | null;
714
+ /** Logged send-anyway override (Phase 10). When `sendOverride` is true AND `overrideReason` is
715
+ * non-empty, the health gate is bypassed for THIS report; `overrideBy`/`overrideAt` are the
716
+ * audit trail (parallel to approvedBy/approvedAt). Missing cells read false/null. */
717
+ sendOverride: boolean;
718
+ overrideReason: string | null;
719
+ overrideBy: string | null;
720
+ overrideAt: string | null;
608
721
  };
609
722
 
610
723
  type DraftOptions = {
@@ -833,9 +946,9 @@ type AttentionItem = {
833
946
  autoFixExhausted?: boolean;
834
947
  };
835
948
 
836
- type FleetEventType = "pr_automerged" | "vuln_cleared" | "ci_recovered" | "site_launched" | "fleet_swept" | "cert_renewed";
949
+ type FleetEventType = "pr_automerged" | "vuln_cleared" | "ci_recovered" | "site_launched" | "fleet_swept" | "cert_renewed" | "report_sent_with_override";
837
950
 
838
- type Tier = "attention" | "watch" | "healthy";
951
+ type Tier = "attention" | "watch" | "healthy" | "pre-launch";
839
952
  type SiteCard = {
840
953
  site: WebsiteRow;
841
954
  tier: Tier;
@@ -871,6 +984,8 @@ type CockpitSummary = {
871
984
  attention: number;
872
985
  watch: number;
873
986
  healthy: number;
987
+ /** Count of pre-live "launch period" sites — muted, never counted as broken. */
988
+ preLaunch: number;
874
989
  criticalHighVulns: number;
875
990
  lighthouseBelowFloor: number;
876
991
  deliveryFailures: number;