#!/usr/bin/env node
/**
 * Deterministic builder for the /sign/v3-launch console dataset.
 *
 * The console must never invent state. Every fact it renders is derived here
 * from checked-in public evidence files in the release repository:
 *
 *   release-evidence/BASE_V3_READ_ONLY_CANDIDATE_MANIFEST_20260822.json
 *   release-evidence/VAULTFIRE_V3_FINAL_CROSS_CHAIN_READINESS_20260822.json
 *   release-evidence/V3_LAUNCH_CURRENT_STATUS_CORRECTIONS_20260823.json
 *   pr8/pilot/base_v3_read_only_public_roles.template.json
 *
 * The output (app/sign/v3-launch/data/v3-launch-console-data.json) is checked
 * in so the page can import it without filesystem or network access, and
 * tests/v3-launch-console.test.mjs re-derives it and fails on any drift.
 *
 * Deterministic by construction: no timestamps, no environment values, no
 * network calls, sorted keys only where explicitly built below.
 *
 * Usage: node tools/build_v3_launch_console_data.mjs [--check]
 */
import { createHash } from 'node:crypto';
import { readFileSync, writeFileSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const siteRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
/** Release repository root that contains release-evidence/ and pr8/. */
const releaseRoot = path.resolve(siteRoot, '..', '..');

const SOURCES = {
  candidate: 'release-evidence/BASE_V3_READ_ONLY_CANDIDATE_MANIFEST_20260822.json',
  readiness: 'release-evidence/VAULTFIRE_V3_FINAL_CROSS_CHAIN_READINESS_20260822.json',
  currentStatus: 'release-evidence/V3_LAUNCH_CURRENT_STATUS_CORRECTIONS_20260823.json',
  roles: 'pr8/pilot/base_v3_read_only_public_roles.template.json',
};

export const OUTPUT_RELATIVE_PATH = 'app/sign/v3-launch/data/v3-launch-console-data.json';

function readSource(relative) {
  const raw = readFileSync(path.join(releaseRoot, relative));
  return {
    relative,
    bytes: raw.byteLength,
    sha256: createHash('sha256').update(raw).digest('hex'),
    json: JSON.parse(raw.toString('utf8')),
  };
}

function readTextSource(relative) {
  const raw = readFileSync(path.join(releaseRoot, relative));
  return {
    path: relative,
    sha256: createHash('sha256').update(raw).digest('hex'),
    text: raw.toString('utf8'),
  };
}

function invariant(condition, message) {
  if (!condition) throw new Error(`Invalid current-status evidence: ${message}`);
}

/**
 * Current corrections are deliberately narrow: they can only supersede a
 * historical record after its exact later source/command inputs are pinned and
 * re-checked. This prevents a display-only JSON edit from silently changing a
 * blocker and leaves unrelated historical blockers untouched.
 */
function validateCurrentStatusEvidence(status, candidate) {
  invariant(status.schema === 'vaultfire.v3-launch-current-status-corrections.v1', 'unsupported schema');
  invariant(Array.isArray(status.corrections), 'corrections must be an array');
  const allowedCorrectionIds = new Set(['V2-ABI-001', 'X402-VALID-AFTER']);
  invariant(
    status.corrections.length === allowedCorrectionIds.size &&
      new Set(status.corrections.map((correction) => correction.id)).size === status.corrections.length &&
      status.corrections.every((correction) => allowedCorrectionIds.has(correction.id)),
    'only the explicitly validated correction IDs may supersede historical findings',
  );

  for (const correction of status.corrections) {
    const current = correction.currentEvidence;
    const assertion = current?.candidateAssertions;
    invariant(assertion?.path === SOURCES.candidate, `${correction.id} must bind the candidate manifest`);
    invariant(assertion.sha256 === candidate.sha256, `${correction.id} candidate manifest hash mismatch`);

    if (correction.id === 'V2-ABI-001') {
      invariant(assertion.expected?.exitCode === candidate.json.engineeringEvidence?.canonicalReleaseGate?.exitCode, 'V2 ABI exit code mismatch');
      invariant(
        assertion.expected?.strictV2AbiCount === candidate.json.engineeringEvidence?.canonicalReleaseGate?.strictV2AbiCount,
        'V2 ABI count mismatch',
      );
      invariant(assertion.expected.exitCode === 0, 'V2 ABI correction requires a passing strict command');
      invariant(assertion.expected.strictV2AbiCount === 36, 'V2 ABI correction requires 36 verified ABIs');
      const validator = readTextSource(current.command?.validatorSource?.path);
      invariant(validator.sha256 === current.command?.validatorSource?.sha256, 'V2 ABI validator source hash mismatch');
      invariant(
        JSON.stringify(current.command?.argv) === JSON.stringify(['python3', 'baseline/check_v2_abi_package.py', '--strict']),
        'V2 ABI command is not exact',
      );
      invariant(current.command?.exitCode === 0, 'V2 ABI command result is not a pass');
    }

    if (correction.id === 'X402-VALID-AFTER') {
      for (const [key, value] of Object.entries(assertion.expected ?? {})) {
        invariant(candidate.json.engineeringEvidence?.apiAndX402?.[key] === value, `x402 candidate assertion mismatch: ${key}`);
      }
      invariant(assertion.expected?.v3ValidAfterBoundAndTested === true, 'x402 validAfter must be bound and tested');
      invariant(assertion.expected?.v2Default === true, 'x402 must retain V2 as the default');
      invariant(assertion.expected?.v3LiveSettlementVerified === false, 'x402 correction cannot claim settlement');
      invariant(
        assertion.expected?.productionDurableIdempotencyVerified === false,
        'x402 correction cannot claim production durability',
      );
      const bindings = current.sourceBindings ?? [];
      invariant(bindings.length === 2, 'x402 correction requires both source and focused-test bindings');
      invariant(
        JSON.stringify(bindings.map((binding) => binding.path)) ===
          JSON.stringify(['pr7/sdk/v3/x402.ts', 'pr7/tests/x402/x402-payment-security.spec.ts']),
        'x402 correction source binding paths are not exact',
      );
      for (const binding of bindings) {
        const source = readTextSource(binding.path);
        invariant(source.sha256 === binding.sha256, `x402 source hash mismatch: ${binding.path}`);
        for (const marker of binding.requiredText ?? []) {
          invariant(source.text.includes(marker), `x402 source marker missing: ${binding.path}: ${marker}`);
        }
      }
    }
  }
}

export function buildData() {
  const candidate = readSource(SOURCES.candidate);
  const readiness = readSource(SOURCES.readiness);
  const currentStatus = readSource(SOURCES.currentStatus);
  const roles = readSource(SOURCES.roles);

  const c = candidate.json;
  const r = readiness.json;
  const cs = currentStatus.json;
  const t = roles.json;
  validateCurrentStatusEvidence(cs, candidate);

  const blockerById = Object.fromEntries(
    (r.unresolved_findings_and_blockers || []).map((b) => [b.id, b]),
  );
  const correctionById = Object.fromEntries((cs.corrections || []).map((correction) => [correction.id, correction]));
  const blockerSummary = (id) => {
    const b = blockerById[id];
    const correction = correctionById[id];
    if (correction) {
      invariant(b, `correction has no historical finding: ${id}`);
      invariant(
        correction.historical?.status === b.status && correction.historical?.summary === b.summary,
        `historical finding drift: ${id}`,
      );
      return {
        id,
        status: correction.displayStatus,
        summary: correction.summary,
        provenance: {
          basis: 'current-status-correction',
          correctionEvidence: SOURCES.currentStatus,
          historical: {
            path: SOURCES.readiness,
            status: b.status,
            summary: b.summary,
          },
          current: correction.currentEvidence,
        },
      };
    }
    return b
      ? {
          id: b.id,
          status: b.status,
          summary: b.summary,
          provenance: {
            basis: 'historical-manifest-not-corrected',
            path: SOURCES.readiness,
          },
        }
      : { id, status: 'unknown', summary: null, provenance: { basis: 'missing-evidence' } };
  };

  const recordedBaseV3Addresses = r.base?.v3_verified_addresses_and_transactions?.v3_deployment_addresses || {};

  return {
    schema: 'vaultfire.v3-launch-console-data.v1',
    provenance: {
      generator: 'tools/build_v3_launch_console_data.mjs',
      derivation: 'Field selection only. No value is edited, rounded, inferred, or invented.',
      sources: [candidate, readiness, currentStatus, roles].map((s) => ({
        path: s.relative,
        sha256: s.sha256,
        bytes: s.bytes,
      })),
      candidateGeneratedAtUtc: c.generatedAtUtc,
      readinessPreparedDate: r.prepared_date,
      releaseStatus: c.releaseStatus,
      branch: c.repository?.branch ?? null,
      validatedCombinedSourceCommit: c.repository?.validatedCombinedSourceCommit ?? null,
      frozenSourceCommit: c.repository?.frozenSourceCommit ?? null,
      disclaimer: r.disclaimer,
      claimsNotMade: r.disposition?.claims_not_made ?? [],
      auditClaim: c.audit?.claim ?? null,
      independentHumanAuditComplete: c.audit?.independentHumanAuditComplete ?? false,
      independentAuditHandoff: c.audit?.handoff ?? null,
      currentStatusCorrections: {
        path: SOURCES.currentStatus,
        preparedDate: cs.preparedDate,
        purpose: cs.purpose,
        claimsNotMade: cs.claimsNotMade ?? [],
        correctedFindingIds: (cs.corrections ?? []).map((correction) => correction.id),
      },
    },
    authority: {
      base: {
        chainId: c.scope?.chainId ?? 8453,
        deployer: {
          address: '0xfA15Ee28939B222B0448261A22156070f0A7813C',
          label: 'ghostkey.base.eth',
          status: 'already-evidenced-public-wallet',
          evidence: [
            'audit-evidence/phase3-v2-compatibility-20260822/PUBLIC_READONLY_EVIDENCE.md',
            'audit-evidence/phase3-v2-compatibility-20260822/public-readonly-evidence/discovery-and-read-responses.json',
          ],
          note:
            'Public payTo/deployer wallet observed in published discovery evidence. Being the authorized connector does not authorize signing, deployment, or broadcast.',
        },
        canonicalConnectionPath: {
          surface: 'Base App in-app browser on mobile (Coinbase/Base Wallet)',
          signingModel: 'local wagmi/viem signing by ghostkey.base.eth in that wallet',
          nonCanonicalButAccepted: ['MetaMask', 'WalletConnect'],
          note:
            'MetaMask and WalletConnect may be used to connect and read state. Neither is the canonical deployment path for Base V3.',
        },
      },
      rejectedForV3Authority: [
        {
          address: '0xA054f831B562e729F8D268291EBde1B2EDcFb84F',
          reason: 'operator-declared compromised wallet',
          disposition: 'rejected for every V3 role, signer slot, guardian, treasury, and deployer use',
        },
        {
          address: '0x5a03C962227Dd478B5CD2B9f09E81105096154ad',
          reason: 'operator-declared audit-only burner',
          disposition: 'rejected for every V3 authority role; audit/observation use only',
        },
      ],
      solanaPublicPayee: {
        value: '6KpXaFmaR8WPWr79qzsMBvwDbDzYFErwNoWxfwqDr8JU',
        classification: 'V2 manifest evidence only',
        disposition: 'not a V3 authority, not a V3 program ID, and not settlement evidence',
        evidence: ['audit-evidence/phase3-v2-compatibility-20260822/PUBLIC_READONLY_EVIDENCE.md'],
      },
    },
    tracks: [
      {
        id: 'base-read-only-bootstrap',
        title: 'Base read-only bootstrap',
        chain: 'Base mainnet (8453)',
        state: 'prepared',
        stateLabel: 'Prepared — not deployed, not signed, not broadcast',
        facts: [
          { label: 'Release status', value: c.releaseStatus },
          { label: 'Mode', value: c.scope?.mode ?? null },
          { label: 'Planned transactions', value: String(c.scope?.transactionCount ?? 0) },
          { label: 'Planned transaction value (wei)', value: String(c.scope?.transactionValueWei ?? '0') },
          { label: 'Profile status', value: c.deployment?.profileStatus ?? null },
          { label: 'Contracts in scope', value: (c.scope?.contracts ?? []).join(', ') },
          { label: 'Internal factory creates', value: (c.scope?.internalFactoryCreates ?? []).join(', ') },
          { label: 'Base fork tests passed', value: String(c.engineeringEvidence?.baseFork?.testsPassed ?? 0) },
          { label: 'Foundry non-fork tests passed', value: String(c.engineeringEvidence?.foundry?.nonForkTestsPassed ?? 0) },
          { label: 'Website tests passed', value: String(c.engineeringEvidence?.canonicalReleaseGate?.websiteTestsPassed ?? 0) },
          { label: 'Pinned public read-only block', value: String(r.base?.pinned_public_read_only_snapshot?.blockNumber ?? '') },
        ],
        flags: {
          deployEnabled: c.scope?.deployEnabled ?? false,
          writesEnabled: c.scope?.writesEnabled ?? false,
          paymentsEnabled: c.scope?.paymentsEnabled ?? false,
          broadcastAuthorized: c.scope?.broadcastAuthorized ?? false,
          unsignedPackageGenerated: c.deployment?.unsignedPackageGenerated ?? false,
          predictedAddressesGenerated: c.deployment?.predictedAddressesGenerated ?? false,
          signed: c.deployment?.signed ?? false,
          broadcast: c.deployment?.broadcast ?? false,
        },
        deployedAddresses: null,
        deploymentTransactions: null,
        missingArtifacts: [
          'Verified immutable unsigned transaction package (deployment.unsignedPackageGenerated = false)',
          'Predicted deployment addresses (deployment.predictedAddressesGenerated = false)',
          'Explicit approval artifact carrying the exact phrase',
          'Seven owner-provided public role addresses (publicRoleAddressesPopulated = false)',
          'Fresh starting nonce disclosure for the deployer',
          'Independent human audit disposition (independentHumanAuditComplete = false)',
        ],
        blockers: [blockerSummary('BOOTSTRAP-BLANK-PROFILE-001')],
        approvalPhrase: c.deployment?.approvalPhraseRequired ?? null,
      },
      {
        id: 'base-capped-partner-pilot',
        title: 'Base capped partner pilot',
        chain: 'Base mainnet (8453)',
        state: 'blocked',
        stateLabel: 'Blocked — locked behind the read-only bootstrap and human gates',
        facts: [
          { label: 'Pilot ready', value: String(c.scope?.pilotReady ?? false) },
          { label: 'Partner package', value: r.partner_proposals?.package_path ?? null },
          { label: 'Partner package status', value: r.partner_proposals?.status ?? null },
          { label: 'Proposal templates', value: String(r.partner_proposals?.proposal_template_count ?? 0) },
          { label: 'Approval evidence', value: r.partner_proposals?.approval_evidence ?? null },
          { label: 'Targets or calldata in templates', value: r.partner_proposals?.targets_or_calldata ?? null },
          { label: 'V2 remains default', value: String(r.partner_proposals?.v2_default ?? true) },
        ],
        flags: { pilotReady: c.scope?.pilotReady ?? false },
        deployedAddresses: null,
        deploymentTransactions: null,
        missingArtifacts: [
          'Completed Base read-only deployment (none exists yet)',
          'Independent human audit with dispositioned findings',
          'Approved roles, caps, timelock, and monitoring plan',
          'Partner consent and approved pilot scope',
          'Live payment evidence',
          'Exact approval phrase artifact',
        ],
        blockers: [
          {
            id: 'PARTNER-PILOT-GATE',
            status: 'gated',
            summary: r.separateLaterGates?.partnerPilot ?? c.separateLaterGates?.partnerPilot ?? null,
          },
        ],
        approvalPhrase: 'APPROVE CAPPED BASE V3 PARTNER PILOT',
      },
      {
        id: 'solana-v3',
        title: 'Solana V3',
        chain: 'Solana mainnet',
        state: 'blocked',
        stateLabel: 'Blocked — source-only, not deployed',
        facts: [
          { label: 'V3 program status', value: r.solana?.release_gates?.v3Program_status ?? null },
          { label: 'Deployment or settlement evidence', value: r.solana?.release_gates?.deployment_or_settlement_evidence ?? null },
          { label: 'x402 USDC settlement', value: r.solana?.release_gates?.x402UsdcSettlement_status ?? null },
          { label: 'Native validation', value: r.solana?.native_validation_status ?? null },
          { label: 'Source readiness tests', value: r.solana?.source_readiness_test_status ?? null },
          { label: 'Source assessment only', value: String(r.solana?.source_assessment_only ?? true) },
          { label: 'Evidence', value: r.solana?.evidence ?? null },
        ],
        flags: { deployed: false, activated: false, signingOffered: false },
        deployedAddresses: null,
        deploymentTransactions: null,
        missingArtifacts: [
          'Pinned Solana/Anchor/Rust toolchain with a passing native build and test run',
          'Verified program IDs (none recorded)',
          'Upgrade-authority custody threshold and pause/rollback runbook',
          'Reviewed transaction package',
          'Deployment or settlement evidence of any kind',
        ],
        blockers: [
          blockerSummary('SOLANA-NATIVE-TOOLCHAIN-001'),
          blockerSummary('SOLANA-MAINNET-READINESS-001'),
          blockerSummary('SOLANA-DETERMINISTIC-SBF'),
          blockerSummary('SOLANA-DEPLOYMENT-CUSTODY'),
        ],
        approvalPhrase: null,
      },
      {
        id: 'x402-v3',
        title: 'x402 V3',
        chain: 'Base / Solana rails (configured, unverified)',
        state: 'blocked',
        stateLabel: 'Opt-in and configured-unverified — V2 remains default',
        facts: [
          { label: 'Default protocol version', value: r.v2_v3_compatibility_inventory?.default_protocol ?? 'v2' },
          { label: 'Registry entries', value: String(r.v2_v3_compatibility_inventory?.registry_entries ?? 0) },
          { label: 'V2 entries', value: String(r.v2_v3_compatibility_inventory?.registry_by_version?.v2 ?? 0) },
          { label: 'V3 entries', value: String(r.v2_v3_compatibility_inventory?.registry_by_version?.v3 ?? 0) },
          { label: 'V3 default state', value: r.v2_v3_compatibility_inventory?.v3_default_state ?? null },
          { label: 'V3 access', value: r.v2_v3_compatibility_inventory?.v3_access ?? null },
          { label: 'V2 to V3 write redirect', value: r.v2_v3_compatibility_inventory?.v2_to_v3_write_redirect ?? null },
          { label: 'Bazaar live status', value: r.x402_and_bazaar?.bazaar_live_status ?? null },
          { label: 'Runtime boundary', value: r.x402_and_bazaar?.runtime_boundary ?? null },
          { label: 'Focused x402 tests passed', value: String(c.engineeringEvidence?.apiAndX402?.focusedX402TestsPassed ?? 0) },
          { label: 'Payment security tests passed', value: String(c.engineeringEvidence?.apiAndX402?.paymentSecurityTestsPassed ?? 0) },
          {
            label: 'Signed validAfter bound and tested',
            value: String(c.engineeringEvidence?.apiAndX402?.v3ValidAfterBoundAndTested ?? false),
          },
          { label: 'V3 live settlement verified', value: String(c.engineeringEvidence?.apiAndX402?.v3LiveSettlementVerified ?? false) },
          { label: 'Production durable idempotency verified', value: String(c.engineeringEvidence?.apiAndX402?.productionDurableIdempotencyVerified ?? false) },
        ],
        flags: {
          v2Default: c.engineeringEvidence?.apiAndX402?.v2Default ?? true,
          v3LiveSettlementVerified: c.engineeringEvidence?.apiAndX402?.v3LiveSettlementVerified ?? false,
          productionDurableIdempotencyVerified:
            c.engineeringEvidence?.apiAndX402?.productionDurableIdempotencyVerified ?? false,
          routeActivationOffered: false,
          paymentOffered: false,
          bazaarMutationOffered: false,
        },
        deployedAddresses: null,
        deploymentTransactions: null,
        missingArtifacts: [
          'Owner-authorized wallet and maximum budget for any bounded validation',
          'Immutable deployment SHA and build artifact checksum for the served middleware',
          'Durable transactional idempotency, outage recovery, and authenticated persistence',
          'Facilitator attestation, receipt verification, and reorg drill evidence',
        ],
        blockers: [
          blockerSummary('X402-OWNER-EVIDENCE-001'),
          blockerSummary('X402-DEPLOYMENT-IDENTITY'),
          blockerSummary('X402-BASE-SETTLEMENT'),
          blockerSummary('X402-SOLANA-SETTLEMENT'),
          blockerSummary('X402-DURABLE-CONCURRENCY'),
        ],
        approvalPhrase: null,
      },
    ],
    roleInputs: {
      templatePath: SOURCES.roles,
      templateSha256: roles.sha256,
      schema: t.schema,
      status: t.status,
      chain: t.chain,
      frozenSourceCommit: t.frozenSourceCommit,
      governance: {
        signerCount: t.governance?.signerCount ?? 5,
        threshold: t.governance?.threshold ?? null,
        minimumTimelockSeconds: t.governance?.minimumTimelockSeconds ?? null,
        signerSlots: (t.governance?.signers ?? []).map((s) => s.slot),
      },
      requiredCounts: c.humanInputsRequired ?? {},
      instructions: t.instructions ?? [],
      allowedCustodyClassifications: t.allowedCustodyClassifications ?? {},
      confirmations: Object.keys(t.confirmations ?? {}),
      authorization: t.authorization ?? {},
      emptyTemplate: t,
    },
    approvals: {
      baseReadOnlyPhrase: c.deployment?.approvalPhraseRequired ?? null,
      partnerPilotPhrase: 'APPROVE CAPPED BASE V3 PARTNER PILOT',
      approvalArtifactPresent: false,
      transactionPackagePresent: false,
      note:
        'No approval artifact and no verified immutable transaction package exist in the checked-in evidence, so every signing, sending, and broadcast control stays absent from this console.',
    },
    verifiedDeployments: {
      baseV3Addresses: recordedBaseV3Addresses,
      baseV3AddressInterpretation: r.base?.v3_verified_addresses_and_transactions?.interpretation ?? null,
      baseV3AddressStatus: r.base?.v3_verified_addresses_and_transactions?.status ?? null,
      transactionHashes: [],
    },
    remainingHumanDecisions: r.remaining_human_decisions ?? [],
  };
}

const serialize = (data) => `${JSON.stringify(data, null, 2)}\n`;

function main() {
  const data = buildData();
  const outPath = path.join(siteRoot, OUTPUT_RELATIVE_PATH);
  const next = serialize(data);
  if (process.argv.includes('--check')) {
    const current = readFileSync(outPath, 'utf8');
    if (current !== next) {
      console.error(`DRIFT: ${OUTPUT_RELATIVE_PATH} does not match its evidence sources.`);
      process.exit(1);
    }
    console.log(`OK: ${OUTPUT_RELATIVE_PATH} matches its evidence sources.`);
    return;
  }
  writeFileSync(outPath, next);
  console.log(`Wrote ${OUTPUT_RELATIVE_PATH}`);
}

if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
  main();
}

export { serialize };
