/* HALTSEAL Proof Playground — /verify page controller
 *
 * Runs in the main thread. All heavy verification happens inside a WebWorker.
 * Network calls are deliberately disabled on this page (connect-src 'none').
 */

'use strict';

(function () {

  // ---------- Signed export: local session signer (no embedded private keys) ----------
  // /verify generates an ephemeral Ed25519 key pair locally for signing exports.
  // Private key never leaves the device. Production deployments should use customer-managed keys (KMS/HSM).
  let SESSION_SIGNER = null;

  // ---------- Anchored signer demo (no private keys shipped) ----------
  // This page is offline-only (connect-src 'none'). For an identity-anchored signature demo,
  // we pin a bootstrap public key and embed a signed Trust Anchor Snapshot (DSSE).
  // The snapshot contains a "Verifier signing key" public key used to verify an anchored demo report.
const PINNED_BOOTSTRAP = {"kid":"ta.mvg.verifier.bootstrap.2026-02-13.v25.demo","jwk":{"kty":"OKP","crv":"Ed25519","x":"shYV2j8XgLrmamavx8f0Z0eh1aam7wlvNhrC8cQ_6xA"}};
const EMBEDDED_TRUST_ANCHOR_SNAPSHOT_DSSE = {"payloadType":"application/vnd.mvg.trust_anchors.snapshot+json","payload":"eyJlbnRyaWVzIjpbeyJrZXlfbWF0ZXJpYWwiOnsiY3J2IjoiRWQyNTUxOSIsImt0eSI6Ik9LUCIsIngiOiJTdldhQ21vbUMyS2ZLMlRJaDY0dm56aXUzVGFzODVCVnhfQ2pSMmpLOHl3In0sIm5vdGUiOiJEZW1vIHdpdG5lc3Mga2V5IDEuIiwicHVycG9zZXMiOlsicmVnaXN0cnkud2l0bmVzc19zaWduYXR1cmUiXSwidHJ1c3RfYW5jaG9yX2lkIjoidGEubXZnLnZlcmlmaWVyLndpdG5lc3MuMS4yMDI2LTAyLTEzLnYyNS5kZW1vIn0seyJrZXlfbWF0ZXJpYWwiOnsiY3J2IjoiRWQyNTUxOSIsImt0eSI6Ik9LUCIsIngiOiJPRjRQSDB3enFCb1hOb21fTXZrVzNWUUxPVFlxRTJ1bE9EWVhNQ3lTdGdnIn0sIm5vdGUiOiJEZW1vIHdpdG5lc3Mga2V5IDIuIiwicHVycG9zZXMiOlsicmVnaXN0cnkud2l0bmVzc19zaWduYXR1cmUiXSwidHJ1c3RfYW5jaG9yX2lkIjoidGEubXZnLnZlcmlmaWVyLndpdG5lc3MuMi4yMDI2LTAyLTEzLnYyNS5kZW1vIn0seyJrZXlfbWF0ZXJpYWwiOnsiY3J2IjoiRWQyNTUxOSIsImt0eSI6Ik9LUCIsIngiOiJfVFBxUTFuVUJfUlNnQnpWckY4MzdxRFlqQ2RNdHd3cmttVllGOFBCdmxvIn0sIm5vdGUiOiJEZW1vIHZlcmlmaWVyIHJlcG9ydCBzaWduaW5nIGtleS4iLCJwdXJwb3NlcyI6WyJ2ZXJpZmllci5yZXBvcnRfc2lnbmluZyJdLCJ0cnVzdF9hbmNob3JfaWQiOiJ0YS5tdmcudmVyaWZpZXIuc2lnbmluZy4yMDI2LTAyLTEzLnYxOS5kZW1vIn0seyJrZXlfbWF0ZXJpYWwiOnsiY3J2IjoiRWQyNTUxOSIsImt0eSI6Ik9LUCIsIngiOiJDSFlnbW00WEVpV2Y4bTRqZ0NxUFFBWWJxN3JoWkl3d1VVa1BHT1JIRUxzIn0sIm5vdGUiOiJEZW1vIHZlcmlmaWVyIHJlcG9ydCBzaWduaW5nIGtleS4iLCJwdXJwb3NlcyI6WyJ2ZXJpZmllci5yZXBvcnRfc2lnbmluZyJdLCJ0cnVzdF9hbmNob3JfaWQiOiJ0YS5tdmcudmVyaWZpZXIuc2lnbmluZy4yMDI2LTAyLTEzLnYyMy5kZW1vIn0seyJrZXlfbWF0ZXJpYWwiOnsiY3J2IjoiRWQyNTUxOSIsImt0eSI6Ik9LUCIsIngiOiJMYmNrbUNiWlJXMC1MNzNxanBiTDJNX0JSNUpTTTN6eV9sdElPc0N4RlYwIn0sIm5vdGUiOiJEZW1vIHZlcmlmaWVyIHJlcG9ydCBzaWduaW5nIGtleS4iLCJwdXJwb3NlcyI6WyJ2ZXJpZmllci5yZXBvcnRfc2lnbmluZyJdLCJ0cnVzdF9hbmNob3JfaWQiOiJ0YS5tdmcudmVyaWZpZXIuc2lnbmluZy4yMDI2LTAyLTEzLnYyNC5kZW1vIn0seyJrZXlfbWF0ZXJpYWwiOnsiY3J2IjoiRWQyNTUxOSIsImt0eSI6Ik9LUCIsIngiOiI5ZjlWY2htWVRIOHEzekhNeV9KTGhHV3BDbDRLd0FhVm82M0dEbFpzazRJIn0sIm5vdGUiOiJEZW1vIHZlcmlmaWVyIHJlcG9ydCBzaWduaW5nIGtleS4iLCJwdXJwb3NlcyI6WyJ2ZXJpZmllci5yZXBvcnRfc2lnbmluZyJdLCJ0cnVzdF9hbmNob3JfaWQiOiJ0YS5tdmcudmVyaWZpZXIuc2lnbmluZy4yMDI2LTAyLTEzLnYyNS5kZW1vIn1dLCJpc3N1ZWRfdXRjIjoiMjAyNi0wMi0xM1QyMTo1MTowOFoiLCJub3RlIjoiRW1iZWRkZWQgZGVtbyB0cnVzdCBhbmNob3JzIGZvciB2ZXJpZnlpbmcgc2lnbmVkIHJlcG9ydCBEU1NFIGZpbGVzIChwdWJsaWMtc2FmZSkuIiwicmVnaXN0cnlfaWQiOiJtdmcudmVyaWZpZXJfdHJ1c3RfYW5jaG9ycyIsInJlZ2lzdHJ5X3NuYXBzaG90X2lkIjoibnZyczpzaGEyNTY6YmQxYWJjZDZhYWU2NDA0ZmRlZTExM2Y3YmUxMTczMWZjNDZiMDZiNzIzMzBjZDQzNTE5NjI5Yzg5OWMyYTZkMSIsInNjaGVtYV9pZCI6Im12Zy50cnVzdF9hbmNob3JzX3NuYXBzaG90QDEiLCJ2ZXJzaW9uIjoiMjAyNi4wMi4xMy1kZW1vIiwid2l0bmVzc190aHJlc2hvbGQiOjJ9","signatures":[{"keyid":"ta.mvg.verifier.bootstrap.2026-02-13.v25.demo","sig":"HvzCK_peapGCokYlIh8hnxzEPuOgct9IZ7uhmQBu_t13fGeva2CaS44mNFNffLxqzj0Bn1SXvhyWGTigCGasBQ"},{"keyid":"ta.mvg.verifier.witness.1.2026-02-13.v25.demo","sig":"RQjSRbMX1LaRlgdC2C4XDIHeXO8VtDc5gSwOEBxDUy47-ms3tEyoIdKJHo4tJSpRPYXQst2yYI-jTAyDCA5mCg"},{"keyid":"ta.mvg.verifier.witness.2.2026-02-13.v25.demo","sig":"dHajLgjgRGMWlaeYXwpsBtnKqeeTS-0o-n61pJptnqcAtMn5tnkPqBHAOYqD2VOdP4M5RQa2SBxGvglgRPawAA"}],"ext":{"signature_mode":"trust_anchored","trust_anchor_registry_snapshot_id":"nvrs:sha256:bd1abcd6aae6404fdee113f7be11731fc46b06b72330cd43519629c899c2a6d1","witness_threshold":2}};
const EMBEDDED_ANCHORED_DEMO_REPORT_DSSE = {"payloadType":"application/vnd.mvg.conformance_report+json;schema_id=mvg.conformance_report@1","payload":"eyJjaGVja3MiOnsicGlucyI6eyJvayI6dHJ1ZX0sInNpZ25hdHVyZXMiOnsib2siOnRydWV9LCJzdHJ1Y3R1cmUiOnsib2siOnRydWV9fSwiZXh0Ijp7Im5vdGUiOiJQdWJsaWMtc2FmZSBkZW1vIHJlcG9ydDsgdXNlZCB0byBpbGx1c3RyYXRlIHRydXN0LWFuY2hvcmVkIHZlcmlmaWNhdGlvbi4ifSwiZ2VuZXJhdGVkX3V0YyI6IjIwMjYtMDItMTNUMjE6NTE6MDhaIiwib2JzZXJ2ZWRfcmVhc29uX2NvZGVzIjpbXSwib2siOnRydWUsInBpbnMiOnsiZW52ZWxvcGVfcHJvZmlsZXMiOlsiRFNTRSIsIkNPU0VfU2lnbjEiLCJKV1MiXSwicmVhc29uX2NvZGVfcmVnaXN0cnkiOiJtdmcucmVhc29uX2NvZGVzQDAuNS4wIiwidHJ1c3RfYW5jaG9yX3JlZ2lzdHJ5X3NuYXBzaG90X2lkIjoibnZyczpzaGEyNTY6YmQxYWJjZDZhYWU2NDA0ZmRlZTExM2Y3YmUxMTczMWZjNDZiMDZiNzIzMzBjZDQzNTE5NjI5Yzg5OWMyYTZkMSIsInZlcmlmaWVyX3JlbGVhc2UiOiJ2NDIuNi4wIn0sInJlcG9ydF9pZCI6IkNSLURFTU8tQU5DSE9SRUQiLCJzY2hlbWFfaWQiOiJtdmcuY29uZm9ybWFuY2VfcmVwb3J0QDEiLCJzY2hlbWFfdmVyc2lvbiI6IjEuMC4wIiwidmVyZGljdCI6IlBBU1MifQ","signatures":[{"keyid":"ta.mvg.verifier.signing.2026-02-13.v25.demo","sig":"Yv3XW3fVn2CzJ59YF3Css-ImggtHsLhSQMPVmd97ORK3bBzA9fJEkXnXtmzDjcB22JDP4vzmyexUn4YUUrNQDQ"}],"ext":{"signature_mode":"trust_anchored","trust_anchor_id":"ta.mvg.verifier.signing.2026-02-13.v25.demo","trust_anchor_registry_snapshot_id":"nvrs:sha256:bd1abcd6aae6404fdee113f7be11731fc46b06b72330cd43519629c899c2a6d1","signature_profile":"mvg.dsse-ed25519-jcs@1"}};
const VERIFIER_SIGNING_KID = "ta.mvg.verifier.signing.2026-02-13.v25.demo";


  async function getSessionSigner(){
    if(SESSION_SIGNER) return SESSION_SIGNER;
    if(!crypto?.subtle?.generateKey || !crypto?.subtle?.exportKey) throw new Error('ALG_UNSUPPORTED');
    let kp;
    try { kp = await crypto.subtle.generateKey({name:'Ed25519'}, true, ['sign','verify']); }
    catch(e){ throw new Error('ALG_UNSUPPORTED'); }
    let pubJwk;
    try { pubJwk = await crypto.subtle.exportKey('jwk', kp.publicKey); }
    catch(e){ throw new Error('ALG_UNSUPPORTED'); }
    const xBytes = b64uToBytes(pubJwk.x);
    const kid = 'local.ed25519:sha256:' + (await sha256Hex(xBytes)).slice(0,16) + '…';
    SESSION_SIGNER = { kid, publicJwk:{kty:'OKP', crv:'Ed25519', x:pubJwk.x}, privateKey: kp.privateKey };
    return SESSION_SIGNER;
  }

  // ---------- Hard network lock (defense-in-depth) ----------
  // ---------- Hard network lock (defense-in-depth) ----------
  try {
    const deny = (name) => function () { throw new Error(`Network disabled on /verify (${name})`); };
    if (window.fetch) window.fetch = deny('fetch');
    if (window.XMLHttpRequest) window.XMLHttpRequest = function () { throw new Error('Network disabled on /verify (XHR)'); };
    if (window.WebSocket) window.WebSocket = function () { throw new Error('Network disabled on /verify (WebSocket)'); };
    if (navigator.sendBeacon) navigator.sendBeacon = deny('sendBeacon');
  } catch (_) {}

  const $ = (id) => document.getElementById(id);

  function escapeHtml(s) {
    return String(s || '').replace(/[&<>"']/g, (c) => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
  }


  const dropzone = $('dropzone');
  const packFile = $('packFile');
  const fileName = $('fileName');
  const btnVerifySample = $('btnVerifySample');
  const btnVerifyFile = $('btnVerifyFile');
  const btnSimFail = $('btnSimFail');
  const btnSimHold = $('btnSimHold');
  const btnSelfTest = $('btnSelfTest');
  const selfTestPanel = $('selfTestPanel');
  const selfTestBody = $('selfTestBody');
  const selfTestNote = $('selfTestNote');

  const btnCopyJira = $('btnCopyJira');
  const jiraCopyStatus = $('jiraCopyStatus');

  const auditorToggle = $('auditorToggle');
  const chkEquivalence = $('chkEquivalence');
  const chkWitness = $('chkWitness');
  const chkTransparency = $('chkTransparency');
  const witnessThreshold = $('witnessThreshold');

  const results = $('results');
  const verdictBadge = $('verdictBadge');
  const verdictTitle = $('verdictTitle');
  const verdictSubtitle = $('verdictSubtitle');
  const pinsList = $('pinsList');
  const reasonList = $('reasonList');
  const reportJson = $('reportJson');

  const btnCopySummary = $('btnCopySummary');
  const btnCopyReport = $('btnCopyReport');
  const btnDownloadReport = $('btnDownloadReport');
  const btnDownloadSigned = $('btnDownloadSigned');
	  const btnDownloadSigningRequest = $('btnDownloadSigningRequest');
  const btnDownloadAnchoredDemo = $('btnDownloadAnchoredDemo');
  const btnVerifySigned = $('btnVerifySigned');
  const signedPanel = $('signedPanel');
  const signedStatus = $('signedStatus');
  const signedDetails = $('signedDetails');
  const signedFile = $('signedFile');
  const summaryLine = $('summaryLine');

  // Org signer (anchored) — bring-your-own trust anchors (private key 0)
  const orgBootstrapFile = $('orgBootstrapFile');
  const orgSnapshotFile = $('orgSnapshotFile');
  const orgSignedReportFile = $('orgSignedReportFile');
  const btnVerifyOrgAnchored = $('btnVerifyOrgAnchored');
  const orgVerifyStatus = $('orgVerifyStatus');
  const orgVerifyDetail = $('orgVerifyDetail');

  let lastReport = null;
  let lastSigned = null;

  function b64uToBytes(s) {
    s = String(s || '').replace(/-/g, '+').replace(/_/g, '/');
    const pad = '='.repeat((4 - (s.length % 4)) % 4);
    const bin = atob(s + pad);
    const out = new Uint8Array(bin.length);
    for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
    return out;
  }


  function bytesToB64u(bytes) {
    const b = (bytes instanceof Uint8Array) ? bytes : new Uint8Array(bytes);
    let bin = '';
    for (let i = 0; i < b.length; i++) bin += String.fromCharCode(b[i]);
    return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
  }

  async function sha256Hex(bytes) {
    const b = (bytes instanceof Uint8Array) ? bytes : new Uint8Array(bytes);
    const ab = b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength);
    const dig = await crypto.subtle.digest('SHA-256', ab);
    const u8 = new Uint8Array(dig);
    let out = '';
    for (let i = 0; i < u8.length; i++) out += u8[i].toString(16).padStart(2, '0');
    return out;
  }

  // ---------- DSSE helpers (PAE) ----------
  function dssePAE(payloadType, payloadBytes) {
    const pt = new TextEncoder().encode(String(payloadType||''));
    const pieces = [
      new TextEncoder().encode('DSSEv1 '),
      new TextEncoder().encode(String(pt.length)),
      new TextEncoder().encode(' '),
      pt,
      new TextEncoder().encode(' '),
      new TextEncoder().encode(String(payloadBytes.length)),
      new TextEncoder().encode(' '),
      payloadBytes
    ];
    let total = 0; for (const p of pieces) total += p.length;
    const out = new Uint8Array(total);
    let o = 0; for (const p of pieces) { out.set(p, o); o += p.length; }
    return out;
  }

  async function verifyDsseEd25519(env, pubJwk) {
    if (!env || !env.payloadType || !env.payload || !env.signatures) return { ok:false, error:'DSSE_INVALID' };
    let pubKey;
    try {
      pubKey = await crypto.subtle.importKey('jwk', pubJwk, { name: 'Ed25519' }, false, ['verify']);
    } catch (e) {
      return { ok:false, error:'ALG_UNSUPPORTED' };
    }
    const payloadBytes = b64uToBytes(env.payload);
    const pae = dssePAE(env.payloadType, payloadBytes);
    const sig0 = (env.signatures || [])[0];
    if (!sig0 || !sig0.sig) return { ok:false, error:'SIG_MISSING' };
    const sigBytes = b64uToBytes(sig0.sig);
    const ok = await crypto.subtle.verify({ name:'Ed25519' }, pubKey, sigBytes, pae);
    return { ok, error: ok ? null : 'SIG_INVALID' };
  }


  // JCS-ish canonicalization (matches worker): recursively sort object keys.
  function canonicalizeValue(v) {
    if (v === null) return null;
    if (Array.isArray(v)) return v.map(canonicalizeValue);
    if (typeof v === 'object') {
      const out = {};
      const keys = Object.keys(v).sort();
      for (const k of keys) out[k] = canonicalizeValue(v[k]);
      return out;
    }
    return v;
  }

  function jcsBytes(obj) {
    const canon = canonicalizeValue(obj);
    return new TextEncoder().encode(JSON.stringify(canon));
  }

  async function signConformanceReportDSSE(report) {
    // Tamper-evident sharing: sign the exported report locally with a session Ed25519 key.
    // No embedded private keys. Fail-closed if crypto is unavailable.
	    const payloadType = 'application/vnd.mvg.conformance_report+json;schema_id=mvg.conformance_report@1';
	    const payloadBytes = jcsBytes(report);
	    const payloadB64u = bytesToB64u(payloadBytes);

	    const signer = await getSessionSigner();
	    const pae = dssePAE(payloadType, payloadBytes);
    const sigBuf = await crypto.subtle.sign({ name: 'Ed25519' }, signer.privateKey, pae);
    const sigB64u = bytesToB64u(new Uint8Array(sigBuf));

    return {
	      payloadType,
      payload: payloadB64u,
      signatures: [{ keyid: signer.kid, sig: sigB64u }],
      ext: {
        signature_mode: 'local_session',
        signer_kid: signer.kid,
        signer_public_key_jwk: signer.publicJwk,
        note: 'Signed locally by /verify (session key). Private key never leaves this device.'
      }
    };
  }

	  async function buildAnchoredSigningRequest(report) {
	    // Private-key-0 design: export a DSSE signing request that can be signed by an external org signer
	    // (customer-managed key / org signing service / KMS / HSM). The resulting DSSE can be re-verified here.
	    const payloadType = 'application/vnd.mvg.conformance_report+json;schema_id=mvg.conformance_report@1';
	    const payloadBytes = jcsBytes(report);
	    const payloadB64u = bytesToB64u(payloadBytes);
	    const pae = dssePAE(payloadType, payloadBytes);
	    let paeSha = null;
	    try { paeSha = await sha256Hex(pae); } catch (_) {}

	    let snapshotId = null;
	    try {
	      const ta = await verifyEmbeddedTrustAnchors();
	      if (ta?.ok && ta?.snapshot?.registry_snapshot_id) snapshotId = ta.snapshot.registry_snapshot_id;
	    } catch (_) {}

	    return {
	      schema_id: 'mvg.dsse_signing_request@1',
	      created_utc: new Date().toISOString(),
	      purpose: 'verifier.report_signing',
	      report_id: report?.report_id || null,
	      verdict: report?.verdict || null,
	      trust_anchor_registry_snapshot_id: snapshotId,
	      dsse: {
	        payloadType,
	        payload_b64u: payloadB64u,
	        dsse_pae_sha256: paeSha ? `sha256:${paeSha}` : null
	      },
	      expected_signer_kid: VERIFIER_SIGNING_KID,
	      return_format: 'DSSE envelope JSON: {payloadType,payload,signatures:[{keyid,sig}]}'
	    };
	  }

	  function setButtonBusy(btn, busy) {
    if (!btn) return;

    // Persist the original label once, then restore it.
    if (busy) {
      if (!btn.dataset.label) btn.dataset.label = btn.textContent || '';
      btn.disabled = true;
      btn.dataset.busy = '1';
      btn.textContent = 'Verifying…';
      return;
    }

    btn.disabled = false;
    btn.dataset.busy = '';
    btn.textContent = btn.dataset.label || btn.textContent || '';
    delete btn.dataset.label;
  }


  function makeAuditorConfig() {
    const enabled = !!auditorToggle.checked;
    return {
      enabled,
      equivalentEnvelopes: enabled && !!chkEquivalence.checked,
      witness: { required: enabled && !!chkWitness.checked, threshold: parseInt(witnessThreshold.value || '2', 10) || 2 },
      transparency: {
        required: enabled && !!chkTransparency.checked,
        envelopeTypes: enabled && !!chkEquivalence.checked ? ['dsse', 'cose', 'jws'] : ['dsse']
      }
    };
  }

  function renderList(container, items) {
    container.innerHTML = '';
    for (const it of items) {
      const s = document.createElement('span');
      s.className = 'tag';
      s.textContent = it;
      container.appendChild(s);
    }
  }

  function renderPins(pins) {
    const items = [];
    if (pins?.verifier?.verifier_id) items.push(`verifier: ${pins.verifier.verifier_id}@${pins.verifier.verifier_version || '?'}`);
    if (pins?.policy_bundle?.policy_bundle_id) items.push(`policy_bundle: ${pins.policy_bundle.policy_bundle_id}@${pins.policy_bundle.policy_bundle_version || '?'}`);
    if (pins?.canonicalization_id) items.push(`canon: ${pins.canonicalization_id}`);
    if (pins?.rfc_version) items.push(`rfc: ${pins.rfc_version}`);
    renderList(pinsList, items.length ? items : ['—']);
  }

  function renderReasonCodes(observed) {
    const items = [];
    for (const r of (observed || []).slice(0, 10)) {
      const disp = r.disposition ? `→${r.disposition}` : '';
      items.push(`${r.code}${disp} ×${r.count}`);
    }
    renderList(reasonList, items.length ? items : ['—']);
  }

  function verdictBadgeText(v) {
    if (v === 'PASS') return 'PASS';
    if (v === 'HOLD') return 'HOLD';
    return 'FAIL';
  }

  function verdictSubtitleText(report) {
    const profiles = report?.checks?.signature_profiles_verified || [];
    const profilesTxt = profiles.length ? `profiles: ${profiles.join(', ')}` : 'profiles: dsse';
    const regErrors = (report?.checks?.registry_snapshots?.errors || []).length;
    const replayOk = report?.checks?.replay?.ok ? 'replay: ok' : 'replay: mismatch';
    return `${profilesTxt} • ${replayOk} • registry errors: ${regErrors}`;
  }

  function summaryOneLiner(report) {
    const v = report?.verdict || 'FAIL';
    const ts = report?.generated_utc || new Date().toISOString();
    const verifier = report?.pins?.verifier?.verifier_version || '?';
    const policy = report?.pins?.policy_bundle?.policy_bundle_version || '?';
    return `HALTSEAL Proof Playground: ${v} • verifier=${verifier} • policy=${policy} • ${ts}`;
  }

  async function copyText(text) {
    try {
      await navigator.clipboard.writeText(text);
      return true;
    } catch (_) {
      // fallback
      const ta = document.createElement('textarea');
      ta.value = text;
      document.body.appendChild(ta);
      ta.select();
      try { document.execCommand('copy'); } catch (_) {}
      document.body.removeChild(ta);
      return true;
    }
  }

  function downloadJson(filename, obj) {
    const blob = new Blob([JSON.stringify(obj, null, 2)], { type: 'application/json' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = filename;
    document.body.appendChild(a);
    a.click();
    a.remove();
    setTimeout(() => URL.revokeObjectURL(url), 5000);
  }

  function showReport(report) {
    lastReport = report;

    results.style.display = '';
    const v = report?.verdict || 'FAIL';

    verdictBadge.textContent = verdictBadgeText(v);
    verdictTitle.textContent = (v === 'PASS') ? 'PASS' : (v === 'HOLD') ? 'HOLD' : 'FAIL';
    verdictSubtitle.textContent = verdictSubtitleText(report);

    renderPins(report?.pins);
    renderReasonCodes(report?.observed_reason_codes);

    const one = summaryOneLiner(report);
    summaryLine.textContent = one;

    // show report JSON only on demand (copy/download buttons use lastReport)
    reportJson.textContent = JSON.stringify(report, null, 2);
    reportJson.style.display = 'none';
  }

  function newWorker() {
    // Dedicated worker per run (simplifies state + avoids stale listeners)
    return new Worker('/assets/verify/verify-worker.js');
  }


  function verdictToTagClass(v) {
    if (v === 'PASS') return 'ok';
    if (v === 'HOLD') return 'hold';
    return 'deny';
  }

  function formatMs(ms) {
    if (!isFinite(ms)) return '—';
    if (ms < 1000) return `${Math.round(ms)}ms`;
    return `${(ms/1000).toFixed(2)}s`;
  }

  function runVerifyPromise(zipBytes, auditorOverride) {
    return new Promise((resolve, reject) => {
      const w = newWorker();
      const auditor = auditorOverride || makeAuditorConfig();

      w.onmessage = (ev) => {
        const msg = ev.data || {};
        if (msg.type === 'progress') return;
        if (msg.type === 'result') {
          try { w.terminate(); } catch (_) {}
          resolve(msg.report);
        }
      };

      w.onerror = (e) => {
        try { w.terminate(); } catch (_) {}
        reject(new Error(String(e?.message || e)));
      };

      try {
        w.postMessage({ type: 'verify', zipBytes, auditor }, [zipBytes]);
      } catch (e) {
        try { w.terminate(); } catch (_) {}
        reject(e);
      }
    });
  }

  function setSelfTestVisible(on) {
    if (!selfTestPanel) return;
    selfTestPanel.style.display = on ? '' : 'none';
  }

  function setSelfTestRows(rows) {
    if (!selfTestBody) return;
    selfTestBody.innerHTML = rows.map(r => {
      const verdict = r.verdict ? `<span class="tag ${verdictToTagClass(r.verdict)}">${r.verdict}</span>` : `<span class="muted">…</span>`;
      const time = r.ms != null ? `<span class="muted">${formatMs(r.ms)}</span>` : `<span class="muted">…</span>`;
      return `<tr data-sid="${r.id}" style="cursor:pointer">
        <td>${escapeHtml(r.label)}</td>
        <td>${verdict}</td>
        <td>${time}</td>
      </tr>`;
    }).join('');
  }


  async function runVerify(zipBytes) {
    results.style.display = 'none';

    setButtonBusy(btnVerifySample, true);
    setButtonBusy(btnVerifyFile, true);

    const w = newWorker();
    const auditor = makeAuditorConfig();

    w.onmessage = async (ev) => {
      const msg = ev.data || {};
      if (msg.type === 'progress') {
        // Optional: could render progress bar; keep minimal for now.
        return;
      }
      if (msg.type === 'result') {
        setButtonBusy(btnVerifySample, false);
        setButtonBusy(btnVerifyFile, false);

        showReport(msg.report);
        w.terminate();
      }
    };

    w.onerror = (e) => {
      setButtonBusy(btnVerifySample, false);
      setButtonBusy(btnVerifyFile, false);

      showReport({ verdict: 'FAIL', error: String(e?.message || e), generated_utc: new Date().toISOString() });
      try { w.terminate(); } catch (_) {}
    };

    w.postMessage({ type: 'verify', zipBytes, auditor }, [zipBytes]);
  }

  
  
  async function verifyEmbeddedTrustAnchors(){
    // Verify embedded Trust Anchor Snapshot (DSSE) and return key map.
    const env = EMBEDDED_TRUST_ANCHOR_SNAPSHOT_DSSE;
    if(!env || !env.payloadType || !env.payload || !env.signatures) return { ok:false, error:'TRUST_SNAPSHOT_MISSING' };

    // 1) Bootstrap signature (pinned)
    const sigBoot = (env.signatures||[]).find(s=>s && s.keyid===PINNED_BOOTSTRAP.kid);
    if(!sigBoot) return { ok:false, error:'TRUST_BOOTSTRAP_SIG_MISSING' };
    const rBoot = await verifyDsseEd25519({ payloadType: env.payloadType, payload: env.payload, signatures:[sigBoot] }, PINNED_BOOTSTRAP.jwk);
    if(!rBoot.ok) return { ok:false, error:(rBoot.error||'TRUST_BOOTSTRAP_SIG_INVALID') };

    // 2) Snapshot_id check
    const payloadBytes = b64uToBytes(env.payload);
    let ta;
    try { ta = JSON.parse(new TextDecoder().decode(payloadBytes)); } catch(e){ return { ok:false, error:'TRUST_SNAPSHOT_PAYLOAD_INVALID' }; }
    try {
      const taNoId = Object.assign({}, ta); delete taNoId.registry_snapshot_id;
      const digest = await sha256Hex(jcsBytes(taNoId));
      const expectId = `nvrs:sha256:${digest}`;
      if(ta.registry_snapshot_id !== expectId) return { ok:false, error:'TRUST_SNAPSHOT_ID_MISMATCH', snapshot_id: ta.registry_snapshot_id, expect_id: expectId };
    } catch(e){ return { ok:false, error:'TRUST_SNAPSHOT_ID_CHECK_FAILED' }; }

    // 3) Build key map
    const keys = {};
    const witnessKeys = {};
    for(const e of (ta.entries||[])){
      if(e?.key_material?.kty==='OKP' && e?.key_material?.crv==='Ed25519' && e?.key_material?.x){
        keys[e.trust_anchor_id] = {kty:'OKP', crv:'Ed25519', x:e.key_material.x};
        if((e.purposes||[]).includes('registry.witness_signature')) witnessKeys[e.trust_anchor_id] = keys[e.trust_anchor_id];
      }
    }

    // 4) Witness threshold (>=2)
    let okCount = 0;
    for(const sig of (env.signatures||[])){
      if(!sig || !sig.keyid || !witnessKeys[sig.keyid]) continue;
      const r = await verifyDsseEd25519({ payloadType: env.payloadType, payload: env.payload, signatures:[sig] }, witnessKeys[sig.keyid]);
      if(r.ok) okCount++;
    }
    const thr = ta.witness_threshold || 2;
    if(okCount < thr) return { ok:false, error:'TRUST_WITNESS_THRESHOLD', okCount, threshold: thr, snapshot_id: ta.registry_snapshot_id };

    return { ok:true, snapshot: ta, keys, witness_ok: okCount };
  }

  async function verifySignedReportDSSE(env) {
    // Offline DSSE verification for exported reports.
    //  - Anchored: signature keyid resolves via embedded Trust Anchor Snapshot (DSSE) (identity-anchored)
    //  - Local: public key is embedded in the envelope (session key; integrity-only)
    if (!env || !env.payloadType || !env.payload || !env.signatures || !env.signatures.length) return { ok:false, error:'DSSE_INVALID' };
    const sig0 = env.signatures[0] || {};
    const kid0 = sig0.keyid || '';

    // Anchored path (demo): require the signing key to be present in the verified Trust Anchor Snapshot.
    if (kid0 && (kid0 === VERIFIER_SIGNING_KID || String(kid0).startsWith('ta.mvg.verifier.signing.'))) {
      const ta = await verifyEmbeddedTrustAnchors();
      if(!ta.ok) return { ok:false, error: ta.error || 'TRUST_ANCHOR_INVALID', mode:'anchored' };

      const jwk = ta.keys[kid0];
      if(!jwk) return { ok:false, error:'KEY_UNTRUSTED', mode:'anchored', anchor_kid:kid0, snapshot_id: ta.snapshot?.registry_snapshot_id };
      const sigMatch = (env.signatures||[]).find(s=>s && s.keyid===kid0);
      if(!sigMatch) return { ok:false, error:'SIG_MISSING', mode:'anchored', anchor_kid:kid0, snapshot_id: ta.snapshot?.registry_snapshot_id };

      const r = await verifyDsseEd25519({ payloadType: env.payloadType, payload: env.payload, signatures:[sigMatch] }, jwk);
      return { ok: !!r.ok, error: r.error || (r.ok ? null : 'FAIL'), mode:'anchored', anchor_kid:kid0, snapshot_id: ta.snapshot?.registry_snapshot_id };
    }

    // Fallback: local/session signature (self-signed)
    const jwk = env?.ext?.signer_public_key_jwk || env?.ext?.public_key_jwk;
    if (!jwk || !jwk.x) return { ok:false, error:'PUBKEY_MISSING', mode:'local_session' };

    // Keyid sanity: ensure envelope keyid matches derived kid (best-effort).
    let expectKid = '';
    try {
      const xBytes = b64uToBytes(jwk.x);
      expectKid = 'local.ed25519:sha256:' + (await sha256Hex(xBytes)).slice(0,16) + '…';
    } catch (_) {}

    if (expectKid && kid0 && expectKid !== kid0) return { ok:false, error:'KEYID_MISMATCH', mode:'local_session' };

    const res = await verifyDsseEd25519(env, jwk);
    return { ok: !!res.ok, error: res.error || (res.ok ? null : 'FAIL'), mode:'local_session', anchor_kid:kid0 };
  }

  function signedReportDetail(vr){
    if(!vr) return 'Signed report.';
    if(vr.ok){
      if(vr.mode==='anchored') return `Anchored report verified (key: ${vr.anchor_kid}).`;
      return 'Signed report verified.';
    }
    if(vr.mode==='anchored') return `Anchored report: ${vr.error || 'FAIL'}`;
    return 'Signed report: ' + (vr.error || 'FAIL');
  }

function renderSignedStatus(v, detail) {
    if (!signedPanel) return;
    signedPanel.style.display = '';
    const cls = v === 'PASS' ? 'ok' : (v === 'HOLD' ? 'hold' : 'deny');
    signedStatus.className = 'tag ' + cls;
    signedStatus.textContent = v;
    signedDetails.textContent = detail || '';
  }



  // ---------- Org signer (anchored) verification (BYO trust anchors) ----------
  async function readJsonFromInput(inputEl) {
    const f = inputEl && inputEl.files && inputEl.files[0];
    if (!f) throw new Error('MISSING_FILE');
    const txt = await f.text();
    return JSON.parse(txt);
  }

  function setOrgStatus(verdict, detail) {
    if (!orgVerifyStatus || !orgVerifyDetail) return;
    const cls = verdict === 'PASS' ? 'ok' : (verdict === 'HOLD' ? 'hold' : 'deny');
    orgVerifyStatus.className = 'tag ' + cls;
    orgVerifyStatus.textContent = verdict;
    orgVerifyDetail.textContent = detail || '';
  }

  function normalizeBootstrapKey(obj) {
    // Expected shape: { kid: string, jwk: {kty:'OKP', crv:'Ed25519', x:'...'} }
    if (!obj || typeof obj !== 'object') return null;
    if (obj.kid && obj.jwk && obj.jwk.x) return { kid: String(obj.kid), jwk: obj.jwk };
    // Allow: {kid, kty, crv, x}
    if (obj.kid && obj.x) return { kid: String(obj.kid), jwk: { kty: obj.kty || 'OKP', crv: obj.crv || 'Ed25519', x: obj.x } };
    return null;
  }

  async function verifyTrustAnchorsDsse(env, pinnedBootstrap) {
    // Generic verifier for a DSSE-signed trust-anchor snapshot using a caller-provided bootstrap key.
    if (!env || !env.payloadType || !env.payload || !env.signatures) {
      return { ok: false, error: 'TRUST_SNAPSHOT_MISSING' };
    }
    if (!pinnedBootstrap || !pinnedBootstrap.kid || !pinnedBootstrap.jwk) {
      return { ok: false, error: 'BOOTSTRAP_KEY_MISSING' };
    }

    // 1) Bootstrap signature must be present and valid (fail-closed).
    const sigBoot = (env.signatures || []).find(s => s && s.keyid === pinnedBootstrap.kid);
    if (!sigBoot) return { ok: false, error: 'TRUST_BOOTSTRAP_SIG_MISSING' };

    const rBoot = await verifyDsseEd25519(
      { payloadType: env.payloadType, payload: env.payload, signatures: [sigBoot] },
      pinnedBootstrap.jwk
    );
    if (!rBoot.ok) return { ok: false, error: (rBoot.error || 'TRUST_BOOTSTRAP_SIG_INVALID') };

    // 2) Parse payload and (if present) verify registry_snapshot_id binding.
    const payloadBytes = b64uToBytes(env.payload);
    let ta;
    try {
      ta = JSON.parse(new TextDecoder().decode(payloadBytes));
    } catch (e) {
      return { ok: false, error: 'TRUST_SNAPSHOT_PAYLOAD_INVALID' };
    }

    if (ta && ta.registry_snapshot_id) {
      try {
        const taNoId = Object.assign({}, ta);
        delete taNoId.registry_snapshot_id;
        const digest = await sha256Hex(jcsBytes(taNoId));
        const expectId = `nvrs:sha256:${digest}`;
        if (ta.registry_snapshot_id !== expectId) {
          return { ok: false, error: 'TRUST_SNAPSHOT_ID_MISMATCH', snapshot_id: ta.registry_snapshot_id, expect_id: expectId };
        }
      } catch (e) {
        return { ok: false, error: 'TRUST_SNAPSHOT_ID_CHECK_FAILED' };
      }
    }

    // 3) Build key map from entries.
    const keys = {};
    const witnessKeys = {};
    for (const e of (ta.entries || [])) {
      if (e?.key_material?.kty === 'OKP' && e?.key_material?.crv === 'Ed25519' && e?.key_material?.x) {
        keys[e.trust_anchor_id] = { kty: 'OKP', crv: 'Ed25519', x: e.key_material.x };
        if ((e.purposes || []).includes('registry.witness_signature')) {
          witnessKeys[e.trust_anchor_id] = keys[e.trust_anchor_id];
        }
      }
    }

    // 4) Witness threshold (default 0 for org BYO mode).
    const thr = Number.isFinite(ta.witness_threshold) ? ta.witness_threshold : 0;
    let okCount = 0;
    if (thr > 0) {
      for (const sig of (env.signatures || [])) {
        if (!sig || !sig.keyid || !witnessKeys[sig.keyid]) continue;
        const r = await verifyDsseEd25519(
          { payloadType: env.payloadType, payload: env.payload, signatures: [sig] },
          witnessKeys[sig.keyid]
        );
        if (r.ok) okCount++;
      }
      if (okCount < thr) {
        return { ok: false, error: 'TRUST_WITNESS_THRESHOLD', okCount, threshold: thr, snapshot_id: ta.registry_snapshot_id };
      }
    }

    return { ok: true, snapshot: ta, keys, witness_ok: okCount, threshold: thr };
  }

  async function verifyOrgAnchoredReport(reportEnv, snapshotEnv, bootstrap) {
    if (!reportEnv || !reportEnv.payloadType || !reportEnv.payload || !reportEnv.signatures || !reportEnv.signatures.length) {
      return { ok: false, error: 'DSSE_INVALID' };
    }

    const ta = await verifyTrustAnchorsDsse(snapshotEnv, bootstrap);
    if (!ta.ok) return { ok: false, error: ta.error, mode: 'org_anchored', detail: ta };

    const sig0 = (reportEnv.signatures || [])[0] || {};
    const kid0 = sig0.keyid || '';
    if (!kid0) return { ok: false, error: 'SIG_MISSING', mode: 'org_anchored', snapshot_id: ta.snapshot?.registry_snapshot_id };

    const jwk = ta.keys[kid0];
    if (!jwk) {
      return { ok: false, error: 'KEY_UNTRUSTED', mode: 'org_anchored', anchor_kid: kid0, snapshot_id: ta.snapshot?.registry_snapshot_id };
    }

    const sigMatch = (reportEnv.signatures || []).find(s => s && s.keyid === kid0) || sig0;
    const r = await verifyDsseEd25519({ payloadType: reportEnv.payloadType, payload: reportEnv.payload, signatures: [sigMatch] }, jwk);
    return {
      ok: !!r.ok,
      error: r.error || (r.ok ? null : 'FAIL'),
      mode: 'org_anchored',
      anchor_kid: kid0,
      snapshot_id: ta.snapshot?.registry_snapshot_id,
      witness_ok: ta.witness_ok,
      threshold: ta.threshold
    };
  }


// ---------- UI wiring ----------
  function setSelectedFileName(name) {
    fileName.textContent = name ? `Selected: ${name}` : '';
  }

  packFile.addEventListener('change', async () => {
    const f = packFile.files && packFile.files[0];
    setSelectedFileName(f ? f.name : '');
  });

  // drag/drop
  function prevent(e) { e.preventDefault(); e.stopPropagation(); }
  ['dragenter','dragover','dragleave','drop'].forEach(evName => {
    dropzone.addEventListener(evName, prevent);
  });
  dropzone.addEventListener('dragover', () => dropzone.style.borderColor = 'rgba(124,92,255,.55)');
  dropzone.addEventListener('dragleave', () => dropzone.style.borderColor = '');
  dropzone.addEventListener('drop', async (e) => {
    dropzone.style.borderColor = '';
    const f = e.dataTransfer.files && e.dataTransfer.files[0];
    if (!f) return;
    packFile.files = e.dataTransfer.files;
    setSelectedFileName(f.name);
    const ab = await f.arrayBuffer();
    runVerify(ab);
  });

  btnVerifyFile.addEventListener('click', async () => {
    const f = packFile.files && packFile.files[0];
    if (!f) {
      alert('Choose a .zip Conformance Pack first.');
      return;
    }
    const ab = await f.arrayBuffer();
    runVerify(ab);
  });

  btnVerifySample.addEventListener('click', async () => {
    if (!window.HALTSEAL_SAMPLE_PACK || !window.HALTSEAL_SAMPLE_PACK.bytes_b64u) {
      alert('Sample pack is not embedded on this page.');
      return;
    }
    const packs = window.HALTSEAL_SAMPLE_PACKS || {};
    const p = packs.pass || window.HALTSEAL_SAMPLE_PACK;
    const bytes = b64uToBytes(p.bytes_b64u);
    runVerify(bytes.buffer);
  });


  // --- Audit simulations (offline) ---
  if (btnSimFail) btnSimFail.addEventListener('click', async () => {
    const packs = window.HALTSEAL_SAMPLE_PACKS || {};
    const p = packs.fail_tampered || window.HALTSEAL_SAMPLE_PACK;
    if (!p || !p.bytes_b64u) { alert('Simulation pack not available.'); return; }
    // Keep auditor settings as-is; this should deterministically FAIL via SHA256SUM mismatch.
    const bytes = b64uToBytes(p.bytes_b64u);
    runVerify(bytes.buffer);
  });

  if (btnSimHold) btnSimHold.addEventListener('click', async () => {
    const packs = window.HALTSEAL_SAMPLE_PACKS || {};
    const p = packs.pass || window.HALTSEAL_SAMPLE_PACK;
    if (!p || !p.bytes_b64u) { alert('Sample pack not available.'); return; }
    // Force a HOLD path via witness threshold > available witnesses.
    if (auditorToggle) auditorToggle.checked = true;
    if (chkWitness) chkWitness.checked = true;
    if (witnessThreshold) witnessThreshold.value = '3';
    try { syncAuditorUi(); } catch (_) {}
    const bytes = b64uToBytes(p.bytes_b64u);
    runVerify(bytes.buffer);
  });


  async function runSelfTest() {
    const packs = window.HALTSEAL_SAMPLE_PACKS || {};
    const pass = packs.pass || window.HALTSEAL_SAMPLE_PACK;
    const fail = packs.fail_tampered || window.HALTSEAL_SAMPLE_PACK;
    if (!pass || !pass.bytes_b64u || !fail || !fail.bytes_b64u) {
      alert('Self-test packs not available.');
      return;
    }

    const scenarios = [
      { id: 'pass', label: 'PASS (sample)', pack: pass, auditor: { enabled: false } },
      { id: 'fail', label: 'FAIL (tamper)', pack: fail, auditor: { enabled: false } },
      { id: 'hold', label: 'HOLD (witness)', pack: pass, auditor: { enabled: true, equivalentEnvelopes: false, witness: { required: true, threshold: 3 }, transparency: { required: false } } },
    ];

    const rows = scenarios.map(s => ({ id: s.id, label: s.label, verdict: null, ms: null }));
    setSelfTestVisible(true);
    setSelfTestRows(rows);

    if (btnSelfTest) {
      btnSelfTest.disabled = true;
      btnSelfTest.textContent = 'Running…';
    }

    // Disable primary verify buttons while self-test runs.
    try { setButtonBusy(btnVerifySample, true); } catch (_) {}
    try { setButtonBusy(btnVerifyFile, true); } catch (_) {}

    const reports = {};
    for (let i = 0; i < scenarios.length; i++) {
      const s = scenarios[i];
      // mark running
      rows[i].verdict = null;
      setSelfTestRows(rows);

      const u8 = b64uToBytes(s.pack.bytes_b64u);
      // copy buffer before transferring to worker
      const ab = u8.buffer.slice(u8.byteOffset, u8.byteOffset + u8.byteLength);

      const t0 = performance.now();
      try {
        const report = await runVerifyPromise(ab, s.auditor);
        const dt = performance.now() - t0;
        reports[s.id] = report;
        rows[i].verdict = report?.verdict || 'FAIL';
        rows[i].ms = dt;
        setSelfTestRows(rows);
      } catch (e) {
        const dt = performance.now() - t0;
        reports[s.id] = { verdict: 'FAIL', error: String(e?.message || e), generated_utc: new Date().toISOString() };
        rows[i].verdict = 'FAIL';
        rows[i].ms = dt;
        setSelfTestRows(rows);
      }
    }

    // Restore buttons
    try { setButtonBusy(btnVerifySample, false); } catch (_) {}
    try { setButtonBusy(btnVerifyFile, false); } catch (_) {}

    if (btnSelfTest) {
      btnSelfTest.disabled = false;
      btnSelfTest.textContent = 'Self‑test (3)';
    }

    // Make rows clickable to load details below
    if (selfTestBody) {
      Array.from(selfTestBody.querySelectorAll('tr[data-sid]')).forEach(tr => {
        tr.addEventListener('click', () => {
          const sid = tr.getAttribute('data-sid');
          const r = reports[sid];
          if (r) showReport(r);
        });
      });
    }

    // Load PASS details by default.
    if (reports.pass) showReport(reports.pass);
  }

  if (btnSelfTest) btnSelfTest.addEventListener('click', () => { runSelfTest(); });


  btnCopySummary.addEventListener('click', async () => {
    if (!lastReport) return;
    await copyText(summaryOneLiner(lastReport));
  });

  if (btnCopyJira) btnCopyJira.addEventListener('click', async () => {
    const text = btnCopyJira.getAttribute('data-copy-text') || '';
    if (!text) return;
    await copyText(text);
    try {
      const prev = btnCopyJira.textContent || 'Copy for Jira';
      btnCopyJira.textContent = 'Copied';
      btnCopyJira.disabled = true;
      if (jiraCopyStatus) jiraCopyStatus.style.display = '';
      setTimeout(() => {
        btnCopyJira.textContent = prev;
        btnCopyJira.disabled = false;
        if (jiraCopyStatus) jiraCopyStatus.style.display = 'none';
      }, 1200);
    } catch (_) {}
  });


  btnCopyReport.addEventListener('click', async () => {
    if (!lastReport) return;
    await copyText(JSON.stringify(lastReport, null, 2));
  });

  btnDownloadReport.addEventListener('click', async () => {
    if (!lastReport) return;
    const name = `haltseal_conformance_report_${(lastReport.report_id || 'report').replace(/[^a-zA-Z0-9_-]/g,'')}.json`;
    downloadJson(name, lastReport);
  });

  if (btnDownloadSigningRequest) btnDownloadSigningRequest.addEventListener('click', async () => {
    if (!lastReport) return;
    try {
      const req = await buildAnchoredSigningRequest(lastReport);
      const rid = (lastReport.report_id || 'report').replace(/[^a-zA-Z0-9_-]/g,'');
      downloadJson(`haltseal_signing_request_${rid}.json`, req);
    } catch (e) {
      alert('Unable to build signing request in this browser environment.');
    }
  });

    

  if (btnVerifySigned) btnVerifySigned.addEventListener('click', async () => {
    // Prefer last generated signed report; otherwise allow user to pick one.
    if (lastSigned) {
      const vr = await verifySignedReportDSSE(lastSigned);
      renderSignedStatus(vr.ok ? 'PASS' : (vr.error === 'ALG_UNSUPPORTED' ? 'HOLD' : 'FAIL'), signedReportDetail(vr));
      return;
    }
    if (signedFile) signedFile.click();
  });

  if (signedFile) signedFile.addEventListener('change', async () => {
    const f = signedFile.files && signedFile.files[0];
    if (!f) return;
    try {
      const txt = await f.text();
      const env = JSON.parse(txt);
      lastSigned = env;
      const vr = await verifySignedReportDSSE(env);
      renderSignedStatus(vr.ok ? 'PASS' : (vr.error === 'ALG_UNSUPPORTED' ? 'HOLD' : 'FAIL'), signedReportDetail(vr));
    } catch (e) {
      renderSignedStatus('FAIL', 'Signed report: invalid JSON');
    }
  });
if (btnDownloadSigned) btnDownloadSigned.addEventListener('click', async () => {
    if (!lastReport) return;
    try {
      const signed = await signConformanceReportDSSE(lastReport);
      lastSigned = signed;
      // 2-way loop: re-verify immediately
      try {
        const vr = await verifySignedReportDSSE(signed);
        renderSignedStatus(vr.ok ? 'PASS' : (vr.error === 'ALG_UNSUPPORTED' ? 'HOLD' : 'FAIL'), signedReportDetail(vr));
      } catch (e) {
        renderSignedStatus('HOLD', 'Signed report: ALG_UNSUPPORTED');
      }

      const rid = (lastReport.report_id || 'report').replace(/[^a-zA-Z0-9_-]/g,'');
      const name = `haltseal_signed_conformance_report_${rid}.dsse.json`;
      downloadJson(name, signed);
    } catch (e) {
      alert('Signed export requires Ed25519 WebCrypto support in this browser.');
    }
  });

  if (btnDownloadAnchoredDemo) btnDownloadAnchoredDemo.addEventListener('click', async () => {
    try {
      const env = EMBEDDED_ANCHORED_DEMO_REPORT_DSSE;
      lastSigned = env;
      const vr = await verifySignedReportDSSE(env);
      renderSignedStatus(vr.ok ? 'PASS' : (vr.error === 'ALG_UNSUPPORTED' ? 'HOLD' : 'FAIL'), signedReportDetail(vr));
      downloadJson('mvg_anchored_demo_report_v24.dsse.json', env);
    } catch (e) {
      renderSignedStatus('FAIL', 'Anchored demo: FAIL');
    }
  });





  // --- Org signer (anchored) demo ---
  if (btnVerifyOrgAnchored) btnVerifyOrgAnchored.addEventListener('click', async () => {
    try {
      if (!orgBootstrapFile || !orgSnapshotFile || !orgSignedReportFile) {
        alert('Org signer inputs are not available on this page.');
        return;
      }

      const bootRaw = await readJsonFromInput(orgBootstrapFile);
      const boot = normalizeBootstrapKey(bootRaw);
      if (!boot) {
        setOrgStatus('FAIL', 'bootstrap key: invalid format');
        return;
      }

      const snapshotEnv = await readJsonFromInput(orgSnapshotFile);
      const reportEnv = await readJsonFromInput(orgSignedReportFile);

      const vr = await verifyOrgAnchoredReport(reportEnv, snapshotEnv, boot);
      const verdict = vr.ok ? 'PASS' : (vr.error === 'ALG_UNSUPPORTED' ? 'HOLD' : (vr.error === 'TRUST_WITNESS_THRESHOLD' ? 'HOLD' : 'FAIL'));
      const sid = vr.snapshot_id ? String(vr.snapshot_id).slice(0, 28) + '…' : '—';
      const wit = (vr.threshold && vr.threshold > 0) ? `witness ${vr.witness_ok || 0}/${vr.threshold}` : 'witness off';
      const detail = vr.ok
        ? `PASS • signer=${vr.anchor_kid} • ${wit} • snapshot=${sid}`
        : `${vr.error || 'FAIL'} • signer=${vr.anchor_kid || '—'} • ${wit} • snapshot=${sid}`;

      setOrgStatus(verdict, detail);
    } catch (e) {
      const msg = String(e?.message || e);
      if (msg.includes('MISSING_FILE')) setOrgStatus('FAIL', 'Upload 3 files: bootstrap key + snapshot + signed report');
      else setOrgStatus('FAIL', 'Org verify: invalid JSON');
    }
  });

  // Keep sub-controls disabled if auditor mode off
  function syncAuditorUi() {
    const on = !!auditorToggle.checked;
    chkEquivalence.disabled = !on;
    chkWitness.disabled = !on;
    chkTransparency.disabled = !on;
    witnessThreshold.disabled = !on || !chkWitness.checked;
  }
  auditorToggle.addEventListener('change', syncAuditorUi);
  chkWitness.addEventListener('change', syncAuditorUi);
  syncAuditorUi();

})();
