// scripts/verify-segmentation-retirement.ts
// FU-020 (2026-05-08) — network segmentation reachability matrix retirement verification.
// 11 assertions across function-body, audit-log, route, source-static, and DB layers.
// Schema-vocabulary mismatch retirement (shape 2: substrate-not-built, second canonical
// instance after FU-019; FU-018 was shape 1: substrate-impossible). Strict-(β) standard-
// application (see PLATFORM_BEHAVIOUR_NOTES.md). FU-011 programme close-out (final
// bifurcation 2 conversions + 3 retirements asymmetric). Refusal-criterion #5: real DB
// at every step.
//
// Invocation:  npx tsx scripts/verify-segmentation-retirement.ts
import { db } from "../server/db";
import { segmentationRuns, auditLogs } from "@shared/schema";
import { runSegmentationCheck, listSegmentationRuns } from "../server/lib/wave13-pilot-readiness";
import { desc, eq, sql } from "drizzle-orm";
import { readFileSync } from "fs";
import { join } from "path";

const TRIGGERED_BY = "verify-segmentation-retirement-script";
const ROOT = process.cwd();
let pass = 0, fail = 0;
const out = (label: string, ok: boolean, detail = "") => {
  console.log(`${ok ? "PASS" : "FAIL"}  ${label}${detail ? "  — " + detail : ""}`);
  if (ok) pass++; else { fail++; process.exitCode = 1; }
};

async function main() {
  console.log("─── FU-020 segmentation retirement verification ───");

  // ── Function-body layer (load-bearing diff) ──
  // 1. runSegmentationCheck throws with FU-020 marker
  let threw = false; let msg = "";
  try {
    await runSegmentationCheck({ triggeredBy: TRIGGERED_BY });
  } catch (e: any) {
    threw = true; msg = String(e?.message || "");
  }
  out("1. runSegmentationCheck throws with FU-020 marker",
      threw && msg.includes("FU-020 RETIREMENT") && msg.includes("FOLLOW-UPS.md FU-020"),
      threw ? `msg.len=${msg.length}` : "did not throw");

  // 2. audit_logs row written on invocation (function-surface, three-channel pattern)
  await new Promise((r) => setTimeout(r, 250));
  const recentAudits = await db.select().from(auditLogs)
    .where(eq(auditLogs.action, "WAVE13_SEGMENTATION_RETIREMENT_INVOKED"))
    .orderBy(desc(auditLogs.timestamp))
    .limit(5);
  const fnAudit = recentAudits.find((r) => r.resource === "runSegmentationCheck");
  out("2. audit_logs row written with WAVE13_SEGMENTATION_RETIREMENT_INVOKED action (function-surface)",
      !!fnAudit,
      fnAudit ? `id=${fnAudit.id} resource=${fnAudit.resource}` : `none of ${recentAudits.length} recent matched resource=runSegmentationCheck`);

  // ── Route layer ──
  // 3. Route 410 with retiredAt + code + retainedReceipts:36 fields. Static check
  // (no auth-bearing test client wired; route handler is a single sync expression with literals).
  const routesSrc = readFileSync(join(ROOT, "server/routes.ts"), "utf8");
  const routeBlock = routesSrc.slice(routesSrc.indexOf("// 10. Network Segmentation"), routesSrc.indexOf("/api/wave13/segmentation/list") + 200);
  const has410 = routeBlock.includes("status(410)");
  const hasRetiredAt = routeBlock.includes('retiredAt: "2026-05-08"');
  const hasCode = routeBlock.includes('"WAVE13_SEGMENTATION_RETIRED"');
  const hasHistorical = routeBlock.includes("retainedReceipts: 36");
  out("3. POST route returns 410 with retiredAt + code + historical fields",
      has410 && hasRetiredAt && hasCode && hasHistorical,
      `410=${has410} retiredAt=${hasRetiredAt} code=${hasCode} historical=${hasHistorical}`);

  // ── DB layer (immutability) ──
  // 4. segmentation_runs row count is 36 (frozen, no new writes)
  const [{ n }] = await db.select({ n: sql<number>`count(*)::int` }).from(segmentationRuns);
  out("4. segmentation_runs row count = 36 (frozen)",
      Number(n) === 36, `count=${n}`);

  // 10. listSegmentationRuns returns same 36 rows (read path preserved)
  const listed = await listSegmentationRuns(100);
  out("10. listSegmentationRuns returns same 36 rows (read path preserved)",
      Array.isArray(listed) && listed.length === 36,
      `listed=${listed.length}`);

  // ── Static-source layer (touch-point coverage) ──
  const w13Src = readFileSync(join(ROOT, "server/lib/wave13-pilot-readiness.ts"), "utf8");
  const extras8Src = readFileSync(join(ROOT, "server/lib/pilot-readiness-extras-8.ts"), "utf8");
  const uiSrc = readFileSync(join(ROOT, "client/src/pages/wave13-pilot-readiness.tsx"), "utf8");
  const finalRoutesSrc = readFileSync(join(ROOT, "server/lib/wave-final-routes.ts"), "utf8");

  // 5. Smoke driver no longer references segmentation as runnable entry
  const smokeHasSegEntry = /\{\s*module:\s*"segmentation"\s*,\s*exec:/.test(w13Src);
  out("5. smoke driver no longer has runnable segmentation entry (12→11 modules)",
      !smokeHasSegEntry, smokeHasSegEntry ? "entry still present" : "removed");

  // 6. Monthly-attestation rollup excludes segmentation
  const summaryHasSeg = /id:\s*"segmentation"/.test(w13Src);
  out("6. wave13Summary monthly-attestation rollup excludes segmentation",
      !summaryHasSeg, summaryHasSeg ? 'id: "segmentation" still present' : "removed");

  // 7. Scoreboard destructure has 21 vars (was 22 after FU-019, now FU-020 removes w13Seg).
  // Strip line and block comments before checking for w13Seg as a code identifier
  // (FU-020 banner mentions "w13Seg removed" so naive grep matches the comment).
  const destructureMatch = extras8Src.match(/const\s+\[\s*(canary[^\]]+)\s*\]\s*=\s*await\s+Promise\.all\(/);
  const varCount = destructureMatch
    ? destructureMatch[1].split(",").map((s) => s.trim()).filter((s) => s.length > 0 && !s.startsWith("//")).length
    : -1;
  const extras8CodeOnly = extras8Src
    .replace(/\/\*[\s\S]*?\*\//g, "")
    .split("\n")
    .map((l) => l.replace(/\/\/.*$/, ""))
    .join("\n");
  const hasW13Seg = /\bw13Seg\b/.test(extras8CodeOnly);
  out("7. scoreboard destructure has 21 vars (w13Seg removed from code, not just comments)",
      varCount === 21 && !hasW13Seg, `varCount=${varCount} w13SegInCode=${hasW13Seg}`);

  // 8. Modules list count is 11 wave13-* entries (was 12 with wave13-segmentation)
  const wave13Modules = (extras8Src.match(/module:\s*"wave13-[a-z-]+"/g) || []);
  const hasSegModule = wave13Modules.some((m) => m.includes("wave13-segmentation"));
  out("8. modules list has 11 wave13-* entries (wave13-segmentation removed)",
      wave13Modules.length === 11 && !hasSegModule,
      `wave13ModuleCount=${wave13Modules.length} hasSegModule=${hasSegModule}`);

  // 9. UI disclosure panel renders the retirement date string + key markers
  const uiHasDate = uiSrc.includes("Retired 2026-05-08");
  const uiHasDisclosure = uiSrc.includes('data-testid="text-segmentation-disclosure"');
  const uiHasHistContainer = uiSrc.includes('data-testid="container-segmentation-historical"');
  const uiHasHideRunBtnProp = /hideRunButton\?:\s*boolean/.test(uiSrc);
  out("9. UI disclosure panel renders date + disclosure + historical container + hideRunButton prop",
      uiHasDate && uiHasDisclosure && uiHasHistContainer && uiHasHideRunBtnProp,
      `date=${uiHasDate} disclosure=${uiHasDisclosure} histContainer=${uiHasHistContainer} hideRunBtnProp=${uiHasHideRunBtnProp}`);

  // 11. No stale "12 modules" / "12 active" text in Wave-13 surfaces (audit consistency).
  // Extended scope (Option A, operator-approved 2026-05-07 carried forward to FU-020):
  // also checks file-banner at wave13-pilot-readiness.ts and runtime log line at
  // wave-final-routes.ts. Permitted exceptions: comments documenting 14→13→12→11
  // chronology (i.e. "Was 14"/"Was 13"/"Was 12"/"(was 14"/"(was 13"/"(was 12"/"14→13"/
  // "13→12"/"12→11" parentheticals).
  const stripChronology = (s: string) =>
    s.split("\n").filter((l) => !/Was\s+1[234]|\(was\s+1[234]|1[234]\s*→\s*1[123]/i.test(l)).join("\n");
  const w13Stripped = stripChronology(w13Src);
  const extras8Stripped = stripChronology(extras8Src);
  const uiStripped = stripChronology(uiSrc);
  const finalRoutesStripped = stripChronology(finalRoutesSrc);
  // Negative lookbehind on [Ww]ave-? prevents matching the "12" inside any "Wave-12"
  // / "wave12" substring (defensive — the platform suffix is Wave-13, so Wave-12 does
  // not naturally occur, but the same protection that handled "13"-in-"Wave-13" for
  // FU-019 is carried forward symmetrically for "12" to keep the verifier idiom
  // mechanically uniform across the retirement chain).
  // (Substring-collision pattern: see PLATFORM_BEHAVIOUR_NOTES.md verifier-substring-
  // collision entry; FU-018 hit it on w13Adv-in-comment, FU-019 on 13-in-Wave-13,
  // FU-020 preemption applied for 12-symmetry.)
  const stale12 = [
    ...Array.from(w13Stripped.matchAll(/(?<![Ww]ave-?)\b12\s+(active\s+)?modules/gi)).map((m) => `w13:${m[0]}`),
    ...Array.from(extras8Stripped.matchAll(/Wave-13[^\n]*\(12\s+(active\s+)?modules\)/gi)).map((m) => `extras8:${m[0]}`),
    ...Array.from(uiStripped.matchAll(/(?<![Ww]ave-?)\b12\s+(active\s+)?modules|Running\s+12\s+modules/gi)).map((m) => `ui:${m[0]}`),
    ...Array.from(finalRoutesStripped.matchAll(/\(12\s+modules,/gi)).map((m) => `final-routes:${m[0]}`),
  ];
  out("11. no stale '12 modules' text in Wave-13 surfaces (incl. Option-A extensions: file-banner + log line)",
      stale12.length === 0,
      stale12.length === 0 ? "all updated to 11" : `still ${stale12.length} stale ref(s): ${stale12.join(" | ")}`);

  console.log(`\n─── ${pass}/${pass + fail} assertions passed ───`);
  if (fail > 0) console.log(`${fail} FAILED — see PASS/FAIL log above`);
  process.exit(fail > 0 ? 1 : 0);
}

main().catch((e) => { console.error("FATAL", e); process.exit(2); });
