@reddoorla/maintenance 0.69.0 → 0.72.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 (133) hide show
  1. package/README.md +15 -14
  2. package/dist/airtable-U2GTDTK5.js +9 -0
  3. package/dist/{announce-72R3LRNT.js → announce-HJCGEVJA.js} +6 -6
  4. package/dist/blux-FKZYMKLF.js +2205 -0
  5. package/dist/blux-FKZYMKLF.js.map +1 -0
  6. package/dist/{bump-deps-QNM3H4GI.js → bump-deps-GZEJAUKI.js} +3 -2
  7. package/dist/bump-deps-GZEJAUKI.js.map +1 -0
  8. package/dist/chunk-7MJBTV2R.js +145 -0
  9. package/dist/chunk-7MJBTV2R.js.map +1 -0
  10. package/dist/{chunk-2DCA7FHZ.js → chunk-7P7YETHG.js} +34 -12
  11. package/dist/chunk-7P7YETHG.js.map +1 -0
  12. package/dist/{chunk-42KKVLYN.js → chunk-APTAITXZ.js} +10 -2
  13. package/dist/chunk-APTAITXZ.js.map +1 -0
  14. package/dist/{chunk-6G7BC5SN.js → chunk-BHE5CUWI.js} +392 -7
  15. package/dist/chunk-BHE5CUWI.js.map +1 -0
  16. package/dist/{chunk-XWPZ46IN.js → chunk-BJCA7O4A.js} +2 -2
  17. package/dist/{chunk-C3PDS623.js → chunk-BWKK2ZXS.js} +67 -8
  18. package/dist/chunk-BWKK2ZXS.js.map +1 -0
  19. package/dist/{chunk-45AUGDFR.js → chunk-D4FVAIEJ.js} +6 -20
  20. package/dist/chunk-D4FVAIEJ.js.map +1 -0
  21. package/dist/{chunk-YQMYQAJX.js → chunk-GI4M2AMO.js} +2 -2
  22. package/dist/{chunk-OTBA7DON.js → chunk-GYNRZCM6.js} +7 -2
  23. package/dist/chunk-GYNRZCM6.js.map +1 -0
  24. package/dist/{chunk-6MT5W6UF.js → chunk-HR5SONJC.js} +6 -3
  25. package/dist/chunk-HR5SONJC.js.map +1 -0
  26. package/dist/{chunk-6JFOAPW4.js → chunk-JCITWZQR.js} +51 -19
  27. package/dist/chunk-JCITWZQR.js.map +1 -0
  28. package/dist/{chunk-GKSXZIMX.js → chunk-JGLAWXUC.js} +77 -3
  29. package/dist/chunk-JGLAWXUC.js.map +1 -0
  30. package/dist/{chunk-E2GKKUEX.js → chunk-KLB2OLPW.js} +26 -6
  31. package/dist/chunk-KLB2OLPW.js.map +1 -0
  32. package/dist/{chunk-RINXPU3H.js → chunk-KLVMJYZZ.js} +2 -2
  33. package/dist/chunk-KRDLHAKK.js +17 -0
  34. package/dist/chunk-KRDLHAKK.js.map +1 -0
  35. package/dist/{chunk-AESJBEM7.js → chunk-KRHPS3R4.js} +3 -3
  36. package/dist/{chunk-LYCKN7ZR.js → chunk-LJPCMXR7.js} +8 -5
  37. package/dist/chunk-LJPCMXR7.js.map +1 -0
  38. package/dist/{chunk-O6G4HHMF.js → chunk-NRISG4XD.js} +308 -104
  39. package/dist/chunk-NRISG4XD.js.map +1 -0
  40. package/dist/chunk-PKVZMFEB.js +315 -0
  41. package/dist/chunk-PKVZMFEB.js.map +1 -0
  42. package/dist/chunk-SL24DCGC.js +41 -0
  43. package/dist/chunk-SL24DCGC.js.map +1 -0
  44. package/dist/{chunk-A5KYRGQA.js → chunk-SQJ53Z7R.js} +4 -4
  45. package/dist/cli/bin.js +63 -19
  46. package/dist/cli/bin.js.map +1 -1
  47. package/dist/cli/commands/audit.d.ts +1 -1
  48. package/dist/cli/commands/audit.js +11 -9
  49. package/dist/cli/commands/audit.js.map +1 -1
  50. package/dist/configs/playwright-a11y.d.ts +1 -1
  51. package/dist/configs/playwright-a11y.js +1 -3
  52. package/dist/{convert-to-pnpm-XH7NYRVK.js → convert-to-pnpm-SVFDLXZS.js} +3 -2
  53. package/dist/convert-to-pnpm-SVFDLXZS.js.map +1 -0
  54. package/dist/{digest-Q2TAQHXF.js → digest-VX7Q4MNF.js} +8 -8
  55. package/dist/{ensure-site-2PF522VL.js → ensure-site-O7G5ZJS7.js} +2 -2
  56. package/dist/forms/index.d.ts +5 -2
  57. package/dist/forms/index.js +22 -9
  58. package/dist/forms/index.js.map +1 -1
  59. package/dist/{github-signals-CLQC2IPY.js → github-signals-QVW2LB7N.js} +5 -5
  60. package/dist/health-endpoint-IYR5VBUT.js +54 -0
  61. package/dist/health-endpoint-IYR5VBUT.js.map +1 -0
  62. package/dist/index.d.ts +136 -20
  63. package/dist/index.js +207 -64
  64. package/dist/index.js.map +1 -1
  65. package/dist/{init-UA6D7GKW.js → init-CU6PVZRG.js} +9 -5
  66. package/dist/init-CU6PVZRG.js.map +1 -0
  67. package/dist/{launch-AENAS2O2.js → launch-JWXCTJRG.js} +9 -9
  68. package/dist/{lighthouse-airtable-UBSXKHAQ.js → lighthouse-airtable-J5TLASJC.js} +3 -3
  69. package/dist/{onboard-X6KJ5Z2V.js → onboard-6UQE5QIO.js} +2 -2
  70. package/dist/{orchestrate-GADNFGDH.js → orchestrate-ZDH24Z6Y.js} +5 -5
  71. package/dist/{preflight-D5RQGX4V.js → preflight-BNTMWTPG.js} +7 -7
  72. package/dist/probe-DJAMLVTT.js +46 -0
  73. package/dist/probe-DJAMLVTT.js.map +1 -0
  74. package/dist/recipes/sync-configs.d.ts +1 -1
  75. package/dist/{renovate-dispatch-LG4RJGDY.js → renovate-dispatch-C3ALLACX.js} +2 -2
  76. package/dist/{report-3FJ224H5.js → report-YLFSGNWR.js} +12 -12
  77. package/dist/run-migration-LJNNZWIV.js +205 -0
  78. package/dist/run-migration-LJNNZWIV.js.map +1 -0
  79. package/dist/{self-updating-JTTFOJ7Q.js → self-updating-64FNRQB5.js} +2 -2
  80. package/dist/{selftest-55OJIWEB.js → selftest-7DPNGL6R.js} +12 -9
  81. package/dist/selftest-7DPNGL6R.js.map +1 -0
  82. package/dist/smoke-suite-TWILWDSF.js +54 -0
  83. package/dist/smoke-suite-TWILWDSF.js.map +1 -0
  84. package/dist/{svelte-codemods-KJLNH6FQ.js → svelte-codemods-RVDTTXFD.js} +3 -2
  85. package/dist/svelte-codemods-RVDTTXFD.js.map +1 -0
  86. package/dist/{sync-configs-TIBO6DUJ.js → sync-configs-BUUD6L5Q.js} +3 -2
  87. package/dist/sync-configs-BUUD6L5Q.js.map +1 -0
  88. package/dist/{types-Dg4mHvyf.d.ts → types-0jreMCF5.d.ts} +2 -2
  89. package/dist/{upgrade-TPTE26FG.js → upgrade-HHKWYJRN.js} +3 -2
  90. package/dist/upgrade-HHKWYJRN.js.map +1 -0
  91. package/dist/{websites-6YTT5ZTO.js → websites-EH2ULELZ.js} +8 -2
  92. package/dist/{write-audits-to-airtable-IWOO4FFH.js → write-audits-to-airtable-LZDYTNX5.js} +4 -4
  93. package/package.json +4 -1
  94. package/dist/airtable-WGQQ3KAD.js +0 -9
  95. package/dist/bump-deps-QNM3H4GI.js.map +0 -1
  96. package/dist/chunk-2DCA7FHZ.js.map +0 -1
  97. package/dist/chunk-42KKVLYN.js.map +0 -1
  98. package/dist/chunk-45AUGDFR.js.map +0 -1
  99. package/dist/chunk-6G7BC5SN.js.map +0 -1
  100. package/dist/chunk-6JFOAPW4.js.map +0 -1
  101. package/dist/chunk-6MT5W6UF.js.map +0 -1
  102. package/dist/chunk-C3PDS623.js.map +0 -1
  103. package/dist/chunk-E2GKKUEX.js.map +0 -1
  104. package/dist/chunk-GKSXZIMX.js.map +0 -1
  105. package/dist/chunk-LYCKN7ZR.js.map +0 -1
  106. package/dist/chunk-O6G4HHMF.js.map +0 -1
  107. package/dist/chunk-OTBA7DON.js.map +0 -1
  108. package/dist/convert-to-pnpm-XH7NYRVK.js.map +0 -1
  109. package/dist/init-UA6D7GKW.js.map +0 -1
  110. package/dist/selftest-55OJIWEB.js.map +0 -1
  111. package/dist/svelte-codemods-KJLNH6FQ.js.map +0 -1
  112. package/dist/sync-configs-TIBO6DUJ.js.map +0 -1
  113. package/dist/upgrade-TPTE26FG.js.map +0 -1
  114. /package/dist/{airtable-WGQQ3KAD.js.map → airtable-U2GTDTK5.js.map} +0 -0
  115. /package/dist/{announce-72R3LRNT.js.map → announce-HJCGEVJA.js.map} +0 -0
  116. /package/dist/{chunk-XWPZ46IN.js.map → chunk-BJCA7O4A.js.map} +0 -0
  117. /package/dist/{chunk-YQMYQAJX.js.map → chunk-GI4M2AMO.js.map} +0 -0
  118. /package/dist/{chunk-RINXPU3H.js.map → chunk-KLVMJYZZ.js.map} +0 -0
  119. /package/dist/{chunk-AESJBEM7.js.map → chunk-KRHPS3R4.js.map} +0 -0
  120. /package/dist/{chunk-A5KYRGQA.js.map → chunk-SQJ53Z7R.js.map} +0 -0
  121. /package/dist/{digest-Q2TAQHXF.js.map → digest-VX7Q4MNF.js.map} +0 -0
  122. /package/dist/{ensure-site-2PF522VL.js.map → ensure-site-O7G5ZJS7.js.map} +0 -0
  123. /package/dist/{github-signals-CLQC2IPY.js.map → github-signals-QVW2LB7N.js.map} +0 -0
  124. /package/dist/{launch-AENAS2O2.js.map → launch-JWXCTJRG.js.map} +0 -0
  125. /package/dist/{lighthouse-airtable-UBSXKHAQ.js.map → lighthouse-airtable-J5TLASJC.js.map} +0 -0
  126. /package/dist/{onboard-X6KJ5Z2V.js.map → onboard-6UQE5QIO.js.map} +0 -0
  127. /package/dist/{orchestrate-GADNFGDH.js.map → orchestrate-ZDH24Z6Y.js.map} +0 -0
  128. /package/dist/{preflight-D5RQGX4V.js.map → preflight-BNTMWTPG.js.map} +0 -0
  129. /package/dist/{renovate-dispatch-LG4RJGDY.js.map → renovate-dispatch-C3ALLACX.js.map} +0 -0
  130. /package/dist/{report-3FJ224H5.js.map → report-YLFSGNWR.js.map} +0 -0
  131. /package/dist/{self-updating-JTTFOJ7Q.js.map → self-updating-64FNRQB5.js.map} +0 -0
  132. /package/dist/{websites-6YTT5ZTO.js.map → websites-EH2ULELZ.js.map} +0 -0
  133. /package/dist/{write-audits-to-airtable-IWOO4FFH.js.map → write-audits-to-airtable-LZDYTNX5.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
 
@@ -334,9 +406,10 @@ type WebsiteRow = {
334
406
  maintenanceFreq: Frequency;
335
407
  testingFreq: Frequency;
336
408
  /** The literal Airtable cell values behind the coerced frequencies. `toFrequency`
337
- * maps any unrecognized value ("Quaterly", "Monthly ") to "None" so nothing bogus
338
- * reaches a client email which also makes typos invisible downstream. Preflight
339
- * validates THESE to make that failure mode loud. Null = blank cell. */
409
+ * trims whitespace ("Monthly " reads as Monthly), then maps any still-unrecognized
410
+ * value ("Quaterly") to "None" with a LOUD console.warn so nothing bogus reaches a
411
+ * client email and the drop is never invisible. Preflight validates THESE to surface
412
+ * the same failure as a structured finding. Null = blank cell. */
340
413
  maintenanceFreqRaw: string | null;
341
414
  testingFreqRaw: string | null;
342
415
  /** Last manually-recorded maintenance day (used as fallback when no Reports row exists). */
@@ -418,6 +491,22 @@ type WebsiteRow = {
418
491
  deployStatus: string | null;
419
492
  lastDeployAt: string | null;
420
493
  deployLogUrl: string | null;
494
+ /** When the `netlify-deploy` audit last RAN (freshness stamp for `deployStatus`). The audit
495
+ * already writes "Deploy checked at"; this read-back is the Plan-2 fix so `deployEvidence`
496
+ * (Plan 4) can gate on check time — NOT on `lastDeployAt`, which is deploy time, not check time. */
497
+ deployCheckedAt: string | null;
498
+ /** Function-health verdict (the `function-health` audit): the deployed `/health` function
499
+ * answered `ok:true` (pass) or `ok:false` (fail). Single-select `pass`/`fail`; null = never ran
500
+ * / unreachable (→ Plan 4 maps to unknown/amber). Kept SEPARATE from `deployStatus` so
501
+ * `isFailedDeployStatus` keeps meaning "the build failed". */
502
+ functionHealth: "pass" | "fail" | null;
503
+ /** CMS reachability (server-side), derived from the same `/health` body's `details.prismic ===
504
+ * "ok"`. Single-select `pass`/`fail`; null = never ran. No per-site Prismic token or identity
505
+ * column is ever built — this rides `/health`. */
506
+ cmsReachable: "pass" | "fail" | null;
507
+ /** When the `function-health` audit last ran — the freshness gate for BOTH `functionHealth` and
508
+ * `cmsReachable`. Null = never ran. */
509
+ functionHealthCheckedAt: string | null;
421
510
  /** Deployed-URL browser probe (the `browser` audit): cross-engine render OK, mobile render OK,
422
511
  * internal-links OK + broken count, and when it last ran (one timestamp gates all three). */
423
512
  crossbrowserOk: boolean | null;
@@ -425,6 +514,13 @@ type WebsiteRow = {
425
514
  linksOk: boolean | null;
426
515
  brokenLinks: number | null;
427
516
  browserCheckedAt: string | null;
517
+ /** Uptime-reachable verdict (browser audit): every sampled route returned 2xx/3xx. Single-select
518
+ * `pass`/`fail`; null = never ran. Point-in-time. Freshness-gated by `browserCheckedAt`. */
519
+ reachableOk: "pass" | "fail" | null;
520
+ /** Titles & meta verdict (browser audit, chromium): every sampled route has a non-empty `<title>`
521
+ * ≤ 70 chars + a non-empty meta description, and no duplicate titles across the sample.
522
+ * Single-select `pass`/`fail`; null = never ran. Freshness-gated by `browserCheckedAt`. */
523
+ titleMetaOk: "pass" | "fail" | null;
428
524
  /** Per-site copy overrides (M6a). Blank → null → the DEFAULT_COPY value. */
429
525
  copyIntro: string | null;
430
526
  copyContact: string | null;
@@ -447,6 +543,16 @@ type WebsiteRow = {
447
543
  defaultBranchCi: string | null;
448
544
  lastCommitAt: string | null;
449
545
  githubSignalsAt: string | null;
546
+ /** Per-site smoke-suite verdict (the `smoke` audit runs `pnpm test:smoke`).
547
+ * Single-select pass/fail; null = never ran. `lastSmokeAt` gates freshness. */
548
+ smokeOk: "pass" | "fail" | null;
549
+ lastSmokeAt: string | null;
550
+ /** Synthetic form end-to-end verdict (the `form-e2e` audit submits the real prod
551
+ * contact form in test-mode). Single-select pass/fail; null = never ran OR (with
552
+ * a fresh `formE2eCheckedAt`) no contact form → n/a. `formE2eCheckedAt` gates
553
+ * freshness AND encodes the n/a-vs-never-ran distinction. */
554
+ formE2eOk: "pass" | "fail" | null;
555
+ formE2eCheckedAt: string | null;
450
556
  notifyRouting: NotifyRouting | null;
451
557
  };
452
558
  type Severity = "low" | "moderate" | "high" | "critical";
@@ -559,7 +665,7 @@ type ReportData = {
559
665
  };
560
666
 
561
667
  /** A single auto-check outcome. `pass` + fresh ⇒ the caller ticks the box. */
562
- type EvidenceResult = "pass" | "fail" | "unknown";
668
+ type EvidenceResult = "pass" | "fail" | "unknown" | "n/a";
563
669
  type EvidenceRecord = {
564
670
  result: EvidenceResult;
565
671
  checkedAt: string | null;
@@ -602,9 +708,17 @@ type ReportRow = {
602
708
  * subset (see src/reports/checklist.ts). */
603
709
  checklist: Record<string, boolean>;
604
710
  /** 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. */
711
+ * record. Null when the report predates auto-tick or carried no auto-checked items. This is the
712
+ * health gate's source of truth: `isHealthGateClear`/`gatingHealth` (src/reports/checklist.ts)
713
+ * read these evidence records, NOT the checklist booleans — and also drive the dashboard badges. */
607
714
  autoEvidence: Record<string, EvidenceRecord> | null;
715
+ /** Logged send-anyway override (Phase 10). When `sendOverride` is true AND `overrideReason` is
716
+ * non-empty, the health gate is bypassed for THIS report; `overrideBy`/`overrideAt` are the
717
+ * audit trail (parallel to approvedBy/approvedAt). Missing cells read false/null. */
718
+ sendOverride: boolean;
719
+ overrideReason: string | null;
720
+ overrideBy: string | null;
721
+ overrideAt: string | null;
608
722
  };
609
723
 
610
724
  type DraftOptions = {
@@ -833,9 +947,9 @@ type AttentionItem = {
833
947
  autoFixExhausted?: boolean;
834
948
  };
835
949
 
836
- type FleetEventType = "pr_automerged" | "vuln_cleared" | "ci_recovered" | "site_launched" | "fleet_swept" | "cert_renewed";
950
+ type FleetEventType = "pr_automerged" | "vuln_cleared" | "ci_recovered" | "site_launched" | "fleet_swept" | "cert_renewed" | "report_sent_with_override";
837
951
 
838
- type Tier = "attention" | "watch" | "healthy";
952
+ type Tier = "attention" | "watch" | "healthy" | "pre-launch";
839
953
  type SiteCard = {
840
954
  site: WebsiteRow;
841
955
  tier: Tier;
@@ -871,6 +985,8 @@ type CockpitSummary = {
871
985
  attention: number;
872
986
  watch: number;
873
987
  healthy: number;
988
+ /** Count of pre-live "launch period" sites — muted, never counted as broken. */
989
+ preLaunch: number;
874
990
  criticalHighVulns: number;
875
991
  lighthouseBelowFloor: number;
876
992
  deliveryFailures: number;