// ═══════════════════════════════════════════════════════════════════════════
// BLADE-AGENT-HSM v2.0 — Test Harness (DoD-grade verification)
// Each test follows: identify → apply → verify pattern
// ═══════════════════════════════════════════════════════════════════════════
import { HSM, HSMError, ABI, ABI_OPCODES, TIER_ORDER, DEFAULT_POLICY,
         hex, sha256, concatBytes } from './hsm-core.mjs';

let PASS = 0, FAIL = 0;
const FAILURES = [];

function assert(cond, name, detail = '') {
  if (cond) {
    PASS++;
    console.log(`  ✓ PASS  ${name}` + (detail ? `  [${detail}]` : ''));
  } else {
    FAIL++;
    FAILURES.push(`${name} ${detail}`);
    console.log(`  ✗ FAIL  ${name}` + (detail ? `  [${detail}]` : ''));
  }
}

function section(title) {
  console.log('\n' + '═'.repeat(72));
  console.log(' ' + title);
  console.log('═'.repeat(72));
}

async function main() {
  // ═══════════════════════════════════════════════════════════════════════
  // CF1: PCR1 extended with rejection event BEFORE throwing
  // ═══════════════════════════════════════════════════════════════════════
  section('CF1: Rejection events extend PCR1 (forensic audit chain)');
  {
    const hsm = new HSM();
    await hsm.provision();
    // Sign one legitimate entry to advance PCR1 off zero
    await hsm.audit_sign({event:'legit-1'}, hex(hsm.pcr[1].value));
    const pcr1BeforeRejection = hex(hsm.pcr[1].value);
    const extensionsBeforeRejection = hsm.pcr[1].extensions;
    // Send stale PCR1 expectation → should reject AND extend PCR1
    try {
      await hsm.audit_sign({event:'forged'}, '0'.repeat(64));
      assert(false, 'CF1: should have thrown on PCR1 mismatch');
    } catch (e) {
      assert(e.code === 'LEDGER_MISMATCH',
        'CF1: throws LEDGER_MISMATCH', `code=${e.code}`);
      assert(hsm.pcr[1].extensions > extensionsBeforeRejection,
        'CF1: PCR1 extended on rejection (forensic chain preserved)',
        `before=${extensionsBeforeRejection} after=${hsm.pcr[1].extensions}`);
      assert(hex(hsm.pcr[1].value) !== pcr1BeforeRejection,
        'CF1: PCR1 value changed on rejection');
      assert(hsm.pcr[1].history.some(h => h.cause.type === 'audit_rejection'),
        'CF1: PCR1 history contains structured rejection record');
      assert(hsm.tier === 'T0', 'CF1: tier transitioned to T0',
        `tier=${hsm.tier}`);
    }
  }

  // ═══════════════════════════════════════════════════════════════════════
  // CF2: Configurable quorum threshold (not hardcoded 4-of-5)
  // ═══════════════════════════════════════════════════════════════════════
  section('CF2: Configurable quorum threshold');
  {
    // Test 3-of-7 configuration
    const hsm = new HSM({ quorumThreshold: { n: 3, m: 7 } });
    await hsm.provision();
    const sk = (n) => Array.from({length:n}, () =>
      crypto.getRandomValues(new Uint8Array(32)));
    try {
      await hsm.spawn_quorum_sign(sk(2), 'spawn:test');
      assert(false, 'CF2: 2-of-7 should be rejected');
    } catch (e) {
      assert(e.code === 'LOW_QUORUM',
        'CF2: rejects 2-of-7 below configured threshold n=3',
        `code=${e.code}`);
    }
    try {
      const r = await hsm.spawn_quorum_sign(sk(3), 'spawn:test-accept');
      assert(r.threshold === '3-of-7',
        'CF2: accepts 3-of-7 at configured threshold', `got=${r.threshold}`);
    } catch (e) {
      assert(false, `CF2: 3-of-7 should succeed but threw ${e.code}`);
    }
    // Test 2-of-3 configuration
    const hsm2 = new HSM({ quorumThreshold: { n: 2, m: 3 } });
    await hsm2.provision();
    try {
      const r = await hsm2.spawn_quorum_sign(sk(2), 'spawn:test-2-of-3');
      assert(r.threshold === '2-of-3',
        'CF2: accepts 2-of-3 with different configuration',
        `got=${r.threshold}`);
    } catch (e) {
      assert(false, `CF2: 2-of-3 should succeed but threw ${e.code}`);
    }
  }

  // ═══════════════════════════════════════════════════════════════════════
  // CF3: session_nonce mandatory and bound into tool_auth
  // ═══════════════════════════════════════════════════════════════════════
  section('CF3: session_nonce mandatory in tool_auth');
  {
    const hsm = new HSM();
    await hsm.provision();
    // No nonce → should reject
    try {
      await hsm.tool_auth('search', null);
      assert(false, 'CF3: should reject null nonce');
    } catch (e) {
      assert(e.code === 'BAD_NONCE',
        'CF3: rejects null nonce', `code=${e.code}`);
    }
    // Wrong length nonce → should reject
    try {
      await hsm.tool_auth('search', new Uint8Array(16));
      assert(false, 'CF3: should reject 16-byte nonce');
    } catch (e) {
      assert(e.code === 'BAD_NONCE',
        'CF3: rejects wrong-length nonce', `code=${e.code}`);
    }
    // Two calls with different nonces produce different tokens (CF3 binding)
    const n1 = crypto.getRandomValues(new Uint8Array(32));
    const n2 = crypto.getRandomValues(new Uint8Array(32));
    const t1 = await hsm.tool_auth('search', n1);
    const t2 = await hsm.tool_auth('search', n2);
    assert(t1.token !== t2.token,
      'CF3: different nonces produce different tokens');
    // M-09 ZTA update: tokens are bound to monotonic simClockMs (time-window),
    // so same nonce produces a DIFFERENT token at a later clock — this is correct
    // per Zero-Trust Architecture (each session has a finite ephemeral lifespan).
    const t3 = await hsm.tool_auth('search', n1);
    assert(t1.token !== t3.token,
      'CF3 (M-09): same nonce at later monotonic time produces fresh token (ZTA ephemeral binding)');
    // expirySimMs must be populated and in the future
    assert(typeof t3.expirySimMs === 'number' && t3.expirySimMs > 0,
      'CF3 (M-09): token includes expirySimMs field',
      `expirySimMs=${t3.expirySimMs}`);
  }

  // ═══════════════════════════════════════════════════════════════════════
  // CF4: 2D policy matrix [tier][tool] = allow|deny|step-up
  // ═══════════════════════════════════════════════════════════════════════
  section('CF4: 2D policy matrix replaces hardcoded lists');
  {
    const hsm = new HSM();
    await hsm.provision();
    const nonce = crypto.getRandomValues(new Uint8Array(32));
    // At T3, fetch_credentials is "deny"
    try {
      await hsm.tool_auth('fetch_credentials', nonce);
      assert(false, 'CF4: fetch_credentials should be denied at T3');
    } catch (e) {
      assert(e.code === 'POLICY_DENY',
        'CF4: fetch_credentials denied at T3 via policy matrix',
        `code=${e.code}`);
    }
    // At T3, write_file is "step-up"
    try {
      await hsm.tool_auth('write_file', nonce);
      assert(false, 'CF4: write_file should require step-up at T3');
    } catch (e) {
      assert(e.code === 'STEP_UP_REQUIRED',
        'CF4: write_file requires step-up at T3', `code=${e.code}`);
    }
    // At T3, search is "allow"
    const r = await hsm.tool_auth('search', nonce);
    assert(r.boundTier === 'T3',
      'CF4: search allowed at T3, token bound to T3');
    // After tier downgrade to T1, search still allow, summarize allow,
    // but fetch_url now deny
    await hsm._transitionTier('T1', { reason: 'test' });
    try {
      await hsm.tool_auth('fetch_url', nonce);
      assert(false, 'CF4: fetch_url should be denied at T1');
    } catch (e) {
      assert(e.code === 'POLICY_DENY',
        'CF4: fetch_url denied at T1 (was allow at T3)',
        `code=${e.code}`);
    }
    const r2 = await hsm.tool_auth('search', nonce);
    assert(r2.boundTier === 'T1', 'CF4: search still allowed at T1');
  }

  // ═══════════════════════════════════════════════════════════════════════
  // CF5: Canonical binary encoding for pcr_quote
  // ═══════════════════════════════════════════════════════════════════════
  section('CF5: Canonical binary encoding for pcr_quote');
  {
    const hsm = new HSM();
    await hsm.provision();
    const nonce = crypto.getRandomValues(new Uint8Array(32));
    // Quote PCR 0 and 1 → bitmap = 0b00011 = 3
    const q = await hsm.pcr_quote(3, nonce);
    // Decode the blob and verify structure
    const blob = new Uint8Array(q.quoteBlobHex.match(/.{2}/g)
      .map(b => parseInt(b, 16)));
    assert(blob[0] === 0x01, 'CF5: blob version byte = 0x01', `got=0x${blob[0].toString(16)}`);
    assert(blob[1] === 3, 'CF5: blob bitmap byte = 3', `got=${blob[1]}`);
    assert(blob[2] === 0 && blob[3] === 32,
      'CF5: blob nonce length = 32 (big-endian uint16)',
      `got=${blob[2]},${blob[3]}`);
    // Nonce at bytes 4..35
    const noncePart = blob.slice(4, 36);
    assert(hex(noncePart) === hex(nonce),
      'CF5: nonce bytes match supplied nonce');
    // PCR data at bytes 44 onwards (after 8-byte monotonic counter)
    assert(blob[44] === 0, 'CF5: first PCR index = 0', `got=${blob[44]}`);
    assert(blob[77] === 1, 'CF5: second PCR index = 1', `got=${blob[77]}`);
    // Expected total length: 1+1+2+32+8 + 2*(1+32) = 110 bytes
    assert(blob.length === 110,
      'CF5: blob length matches canonical format',
      `expected=110 got=${blob.length}`);
    // Bad nonce length should reject
    try {
      await hsm.pcr_quote(3, new Uint8Array(16));
      assert(false, 'CF5: should reject 16-byte nonce');
    } catch (e) {
      assert(e.code === 'BAD_NONCE',
        'CF5: rejects wrong-length nonce', `code=${e.code}`);
    }
  }

  // ═══════════════════════════════════════════════════════════════════════
  // CF6: Farewell entry signed BEFORE key zeroization
  // ═══════════════════════════════════════════════════════════════════════
  section('CF6: Farewell entry signed before zeroize');
  {
    const hsm = new HSM();
    await hsm.provision();
    // Sign one legitimate entry first
    await hsm.audit_sign({event:'pre-tamper'}, hex(hsm.pcr[1].value));
    const pcr1BeforeTamper = hex(hsm.pcr[1].value);
    const extensionsBeforeTamper = hsm.pcr[1].extensions;
    // H-04 physics: with default 47µF cap (84.6 µJ available) and 60ms ECDSA
    // requiring ~6000 µJ, farewell should physically FAIL.
    // For a successful farewell, we need a larger cap (e.g., 4700 µF).
    // Test BOTH branches:
    let farewellSigned = null, farewellFailed = null;
    hsm.addEventListener('farewell_signed', (e) => { farewellSigned = e.detail; });
    hsm.addEventListener('farewell_failed', (e) => { farewellFailed = e.detail; });
    // Trigger tamper with default (small) capacitor → expect physics-blocked farewell
    const result = await hsm.tamper('mesh-discontinuity-test');
    assert(farewellFailed !== null || farewellSigned !== null,
      'CF6+H-04: tamper emits either farewell_signed OR farewell_failed');
    assert(farewellFailed !== null && farewellFailed.reason && farewellFailed.energyAvailable !== undefined,
      'CF6+H-04: default 47µF cap correctly emits farewell_failed (physics-accurate)',
      `available=${farewellFailed?.energyAvailable?.toExponential(2)} required=${farewellFailed?.energyRequired?.toExponential(2)}`);
    assert(hsm.signingKey === null,
      'CF6: signing key zeroized after tamper');
    assert(hsm.tampered === true && hsm.tier === 'T0',
      'CF6: device sealed at T0 after tamper');
    // Now test the OTHER branch with a larger capacitor that satisfies physics
    const hsm2 = new HSM({ capacitanceFarads: 4700e-6 }); // 4700µF
    await hsm2.provision();
    await hsm2.audit_sign({event:'pre-tamper-2'}, hex(hsm2.pcr[1].value));
    let farewellSigned2 = null;
    hsm2.addEventListener('farewell_signed', (e) => { farewellSigned2 = e.detail; });
    await hsm2.tamper('mesh-discontinuity-test-2');
    assert(farewellSigned2 !== null && farewellSigned2.farewellSigHex,
      'CF6+H-04: 4700µF cap emits farewell_signed (physics budget met)',
      `cap=4700µF`);
    assert(hsm2.pcr[1].history.some(h => h.cause.type === 'farewell_entry'),
      'CF6+H-04: PCR1 history contains farewell_entry when physics permits');
  }

  // ═══════════════════════════════════════════════════════════════════════
  // CF7: Structured cause records (objects, not string concat)
  // ═══════════════════════════════════════════════════════════════════════
  section('CF7: Structured cause records');
  {
    const hsm = new HSM();
    await hsm.provision();
    await hsm.audit_sign({event:'test'}, hex(hsm.pcr[1].value));
    const hist = hsm.pcr[1].history;
    assert(hist.length > 0, 'CF7: PCR history is populated');
    const lastRec = hist[hist.length - 1];
    assert(typeof lastRec.cause === 'object' && lastRec.cause !== null,
      'CF7: cause is an object', `type=${typeof lastRec.cause}`);
    assert('type' in lastRec.cause,
      'CF7: cause has structured "type" field',
      `type=${lastRec.cause.type}`);
    assert(Array.isArray(lastRec.cause.data),
      'CF7: cause has structured "data" array');
  }

  // ═══════════════════════════════════════════════════════════════════════
  // CF8: Silicon-mode timing toggle
  // ═══════════════════════════════════════════════════════════════════════
  section('CF8: Silicon-mode timing simulation');
  {
    // Fast mode (no silicon timing)
    const hsm1 = new HSM({ siliconMode: false });
    await hsm1.provision();
    const t0 = Date.now();
    await hsm1.audit_sign({event:'fast'}, hex(hsm1.pcr[1].value));
    const fastMs = Date.now() - t0;
    // Silicon mode (with timing)
    const hsm2 = new HSM({ siliconMode: true });
    await hsm2.provision();
    const t1 = Date.now();
    await hsm2.audit_sign({event:'slow'}, hex(hsm2.pcr[1].value));
    const slowMs = Date.now() - t1;
    assert(slowMs >= 60, 'CF8: silicon-mode ECDSA P-256 sign ≥ 60ms',
      `actual=${slowMs}ms expected≥60ms`);
    assert(slowMs > fastMs, 'CF8: silicon-mode slower than fast mode',
      `fast=${fastMs}ms silicon=${slowMs}ms`);
  }

  // ═══════════════════════════════════════════════════════════════════════
  // A2: reset() preserves instance identity (no replace pattern)
  // ═══════════════════════════════════════════════════════════════════════
  section('A2: reset() preserves instance identity');
  {
    const hsm = new HSM();
    await hsm.provision();
    const originalRef = hsm;
    await hsm.audit_sign({event:'before-reset'}, hex(hsm.pcr[1].value));
    const ledgerBefore = hsm.ledgerSeq;
    await hsm.reset();
    assert(originalRef === hsm, 'A2: object reference unchanged after reset');
    assert(hsm.ledgerSeq === 0,
      'A2: state reset (ledgerSeq=0)', `got=${hsm.ledgerSeq}`);
    assert(hsm.provisioned === true,
      'A2: re-provisioned automatically after reset');
    assert(hsm.identityCertHistory.length === 2,
      'A2: identity cert history preserved across reset (MP6)',
      `count=${hsm.identityCertHistory.length}`);
  }

  // ═══════════════════════════════════════════════════════════════════════
  // A3/MP1: Centralized tier transitions, upgrade rejection
  // ═══════════════════════════════════════════════════════════════════════
  section('A3/MP1: Centralized tier state machine');
  {
    const hsm = new HSM();
    await hsm.provision();
    // Downgrade allowed
    const r1 = await hsm._transitionTier('T1', { reason: 'test-downgrade' });
    assert(r1 === true && hsm.tier === 'T1', 'A3: downgrade T3→T1 succeeds');
    // Upgrade rejected
    try {
      await hsm._transitionTier('T3', { reason: 'test-upgrade' });
      assert(false, 'A3: upgrade should throw');
    } catch (e) {
      assert(e.code === 'UPGRADE_REJECTED',
        'A3: upgrade attempt rejected', `code=${e.code}`);
    }
    // No-op same-tier
    const r2 = await hsm._transitionTier('T1', { reason: 'noop' });
    assert(r2 === false, 'A3: same-tier transition is no-op');
  }

  // ═══════════════════════════════════════════════════════════════════════
  // A5: ABI frame layer (64-byte fixed frames)
  // ═══════════════════════════════════════════════════════════════════════
  section('A5: ABI frame layer (64-byte dispatcher)');
  {
    const hsm = new HSM();
    await hsm.provision();
    const abi = new ABI(hsm);
    // Bad length frame
    try {
      await abi.dispatch(new Uint8Array(63));
      assert(false, 'A5: 63-byte frame should reject');
    } catch (e) {
      assert(e.code === 'BAD_FRAME_LENGTH',
        'A5: rejects frames not exactly 64 bytes', `code=${e.code}`);
    }
    // Bad type
    try {
      await abi.dispatch(new Array(64).fill(0));
      assert(false, 'A5: non-Uint8Array should reject');
    } catch (e) {
      assert(e.code === 'BAD_FRAME',
        'A5: rejects non-Uint8Array input', `code=${e.code}`);
    }
    // Unknown opcode
    const badFrame = new Uint8Array(64);
    badFrame[0] = 0xFF; // unknown opcode
    badFrame[1] = 0; // length 0
    try {
      await abi.dispatch(badFrame);
      assert(false, 'A5: unknown opcode should reject');
    } catch (e) {
      assert(e.code === 'UNKNOWN_OPCODE',
        'A5: rejects unknown opcode', `code=${e.code}`);
    }
    // Valid audit_sign via ABI — use minimal payload that fits in 62 bytes
    // Real binary ABI would use raw bytes; JSON-in-payload is a simplification
    const payload = JSON.stringify({ payload: { e: 'abi-test' } });
    const payloadBytes = new TextEncoder().encode(payload);
    assert(payloadBytes.length <= 62,
      'A5: test payload fits in ABI frame', `len=${payloadBytes.length}`);
    const validFrame = new Uint8Array(64);
    validFrame[0] = ABI_OPCODES.AUDIT_SIGN;
    validFrame[1] = payloadBytes.length;
    validFrame.set(payloadBytes, 2);
    const result = await abi.dispatch(validFrame);
    assert(result && result.sigHex && result.sigHex.length === 128,
      'A5: valid audit_sign via ABI frame succeeds',
      `sigLen=${result?.sigHex?.length}`);
    // Verify oversize payload rejected at dispatcher level
    const oversize = new Uint8Array(64);
    oversize[0] = ABI_OPCODES.AUDIT_SIGN;
    oversize[1] = 63;  // claims 63 bytes of payload (over 62 max)
    try {
      await abi.dispatch(oversize);
      assert(false, 'A5: should reject payload length > 62');
    } catch (e) {
      assert(e.code === 'BAD_FRAME_LENGTH',
        'A5: rejects oversize payload length', `code=${e.code}`);
    }
  }

  // ═══════════════════════════════════════════════════════════════════════
  // FM01: RNG fault injection
  // ═══════════════════════════════════════════════════════════════════════
  section('FM01: RNG self-test fault injection');
  {
    const hsm = new HSM();
    hsm.injectFault('rngFail', true);
    try {
      await hsm.provision();
      assert(false, 'FM01: provision should fail when RNG fault injected');
    } catch (e) {
      assert(e.code === 'RNG_FAIL',
        'FM01: provision fails on injected RNG fault', `code=${e.code}`);
    }
    // Recovery: disable fault, retry
    hsm.injectFault('rngFail', false);
    const cert = await hsm.provision({force: true});
    assert(cert && cert.deviceId,
      'FM01: recovery succeeds after fault cleared');
  }

  // ═══════════════════════════════════════════════════════════════════════
  // FM04: Voltage brownout fault injection
  // ═══════════════════════════════════════════════════════════════════════
  section('FM04: Voltage brownout detection');
  {
    const hsm = new HSM();
    await hsm.provision();
    hsm.injectFault('voltageBrownout', true);
    try {
      await hsm.audit_sign({event:'test'}, hex(hsm.pcr[1].value));
      assert(false, 'FM04: should reject on brownout');
    } catch (e) {
      assert(e.code === 'VOLTAGE_BROWNOUT',
        'FM04: audit_sign rejects on voltage brownout', `code=${e.code}`);
    }
  }

  // ═══════════════════════════════════════════════════════════════════════
  // FM07: Mutex serialises concurrent ABI calls
  // ═══════════════════════════════════════════════════════════════════════
  section('FM07: Mutex serialises concurrent ABI calls');
  {
    const hsm = new HSM();
    await hsm.provision();
    // Fire 5 concurrent audit_signs
    const promises = [];
    for (let i = 0; i < 5; i++) {
      promises.push(hsm.audit_sign({event:`concurrent-${i}`}, hex(hsm.pcr[1].value)));
    }
    const results = await Promise.allSettled(promises);
    const fulfilled = results.filter(r => r.status === 'fulfilled');
    assert(fulfilled.length >= 1,
      'FM07: at least one concurrent call succeeded',
      `${fulfilled.length}/5 fulfilled`);
    // The sequence numbers should be strictly increasing
    const seqs = fulfilled.map(r => r.value.seq);
    const sorted = [...seqs].sort((a,b)=>a-b);
    assert(JSON.stringify(seqs) === JSON.stringify(sorted),
      'FM07: sequence numbers strictly ordered (no race)',
      `seqs=${JSON.stringify(seqs)}`);
  }

  // ═══════════════════════════════════════════════════════════════════════
  // FM09: Monotonic counter in quote blob
  // ═══════════════════════════════════════════════════════════════════════
  section('FM09: Monotonic counter prevents replay');
  {
    const hsm = new HSM();
    await hsm.provision();
    const n1 = crypto.getRandomValues(new Uint8Array(32));
    const n2 = crypto.getRandomValues(new Uint8Array(32));
    const q1 = await hsm.pcr_quote(1, n1);
    const q2 = await hsm.pcr_quote(1, n2);
    assert(q2.monotonic > q1.monotonic,
      'FM09: monotonic counter strictly increasing',
      `q1=${q1.monotonic} q2=${q2.monotonic}`);
    // Sigs should differ even for same PCR selection (different nonces + monotonic)
    assert(q1.sigHex !== q2.sigHex,
      'FM09: signatures differ due to nonce + monotonic in blob');
  }

  // ═══════════════════════════════════════════════════════════════════════
  // FM12: Duplicate signer detection
  // ═══════════════════════════════════════════════════════════════════════
  section('FM12: Duplicate signer key detection');
  {
    const hsm = new HSM();
    await hsm.provision();
    const k = crypto.getRandomValues(new Uint8Array(32));
    const k2 = crypto.getRandomValues(new Uint8Array(32));
    const k3 = crypto.getRandomValues(new Uint8Array(32));
    // 4 keys with one duplicate
    try {
      await hsm.spawn_quorum_sign([k, k2, k, k3], 'spawn:duplicate-test');
      assert(false, 'FM12: should reject duplicate signers');
    } catch (e) {
      assert(e.code === 'DUPLICATE_SIGNER',
        'FM12: rejects duplicate signer keys', `code=${e.code}`);
    }
    // Distinct 4 keys → succeeds at 4-of-5 default
    const k4 = crypto.getRandomValues(new Uint8Array(32));
    const r = await hsm.spawn_quorum_sign([k, k2, k3, k4], 'spawn:distinct-test');
    assert(r.threshold === '4-of-5',
      'FM12: accepts 4 distinct keys at 4-of-5 threshold',
      `got=${r.threshold}`);
  }

  // ═══════════════════════════════════════════════════════════════════════
  // HP2: Trace export
  // ═══════════════════════════════════════════════════════════════════════
  section('HP2: Trace export to JSONL');
  {
    const hsm = new HSM();
    await hsm.provision();
    await hsm.audit_sign({event:'trace-1'}, hex(hsm.pcr[1].value));
    await hsm.audit_sign({event:'trace-2'}, hex(hsm.pcr[1].value));
    const trace = hsm.exportTrace();
    const lines = trace.split('\n');
    assert(lines.length > 0, 'HP2: trace export non-empty',
      `lines=${lines.length}`);
    // Each line is valid JSON
    let allValid = true;
    for (const line of lines) {
      try { JSON.parse(line); } catch { allValid = false; break; }
    }
    assert(allValid, 'HP2: every trace line is valid JSON');
    // Contains expected event types
    const events = lines.map(l => JSON.parse(l));
    assert(events.some(e => e.type === 'audit_sign_ok'),
      'HP2: trace contains audit_sign_ok events');
    assert(events.some(e => e.type === 'pcr_extend'),
      'HP2: trace contains pcr_extend events');
    assert(events.some(e => e.type === 'provision_complete'),
      'HP2: trace contains provision_complete events');
  }

  // ═══════════════════════════════════════════════════════════════════════
  // HP7: Structured event records (EventTarget bus)
  // ═══════════════════════════════════════════════════════════════════════
  section('HP7: Structured event bus emissions');
  {
    const hsm = new HSM();
    await hsm.provision();
    const captured = [];
    hsm.addEventListener('event', (e) => captured.push(e.detail));
    await hsm.audit_sign({event:'eventbus-test'}, hex(hsm.pcr[1].value));
    assert(captured.length > 0,
      'HP7: event bus captures emissions', `count=${captured.length}`);
    // Each event has structured fields
    const lastEvent = captured[captured.length - 1];
    assert(typeof lastEvent.type === 'string' && lastEvent.timestamp,
      'HP7: events have type and timestamp fields');
  }

  // ═══════════════════════════════════════════════════════════════════════
  // MP4: Benchmark statistics (min/max/mean/median/p95/p99/SD)
  // ═══════════════════════════════════════════════════════════════════════
  section('MP4: Full benchmark statistics');
  {
    const hsm = new HSM();
    await hsm.provision();
    for (let i = 0; i < 20; i++) {
      await hsm.audit_sign({event:`bench-${i}`}, hex(hsm.pcr[1].value));
    }
    const stats = hsm.getStats();
    assert(stats.n === 20, 'MP4: stats includes count', `n=${stats.n}`);
    assert(typeof stats.min === 'number',
      'MP4: min available', `min=${stats.min?.toFixed(2)}`);
    assert(typeof stats.max === 'number',
      'MP4: max available', `max=${stats.max?.toFixed(2)}`);
    assert(typeof stats.mean === 'number',
      'MP4: mean available', `mean=${stats.mean?.toFixed(2)}`);
    assert(typeof stats.median === 'number',
      'MP4: median available', `median=${stats.median?.toFixed(2)}`);
    assert(typeof stats.p95 === 'number',
      'MP4: p95 available', `p95=${stats.p95?.toFixed(2)}`);
    assert(typeof stats.p99 === 'number',
      'MP4: p99 available', `p99=${stats.p99?.toFixed(2)}`);
    assert(typeof stats.sd === 'number',
      'MP4: standard deviation available', `sd=${stats.sd?.toFixed(2)}`);
    assert(stats.min <= stats.median && stats.median <= stats.max,
      'MP4: min ≤ median ≤ max (ordering)');
  }

  // ═══════════════════════════════════════════════════════════════════════
  // MP6: Device identity certificate history
  // ═══════════════════════════════════════════════════════════════════════
  section('MP6: Device-identity certificate history');
  {
    const hsm = new HSM();
    const cert1 = await hsm.provision();
    assert(cert1 && cert1.deviceId && cert1.signingPubKey && cert1.attestationPubKey,
      'MP6: provision emits identity certificate');
    assert(hsm.identityCertHistory.length === 1,
      'MP6: history starts at 1 after provision');
    await hsm.reset();
    assert(hsm.identityCertHistory.length === 2,
      'MP6: reset adds new cert',
      `count=${hsm.identityCertHistory.length}`);
    // Different device IDs across re-provisioning
    assert(hsm.identityCertHistory[0].deviceId !==
           hsm.identityCertHistory[1].deviceId,
      'MP6: each re-provisioning emits new deviceId');
  }

  // ═══════════════════════════════════════════════════════════════════════
  // UC-1: Prompt injection scenario (audit-paper reference use case)
  // ═══════════════════════════════════════════════════════════════════════
  section('UC-1: Prompt-injection tier downgrade scenario');
  {
    const hsm = new HSM();
    await hsm.provision();
    const nonce = crypto.getRandomValues(new Uint8Array(32));
    // Step 1: legitimate doc load
    await hsm.audit_sign({event:'doc-load'}, hex(hsm.pcr[1].value));
    // Step 2: ADARA flags injection → policy update on PCR 2
    await hsm.pcr_extend(2,
      new Uint8Array(await sha256('policy:restricted')),
      { type: 'policy_update', data: ['restricted'] });
    // Step 3: tool_auth for blocked tool should be denied
    let blockedDenied = false;
    try {
      await hsm.tool_auth('fetch_credentials', nonce);
    } catch (e) {
      blockedDenied = (e.code === 'POLICY_DENY');
    }
    assert(blockedDenied, 'UC-1: fetch_credentials denied by policy matrix');
    // Step 4: explicit tier downgrade
    await hsm._transitionTier('T1', { reason: 'adara-flag-injection' });
    assert(hsm.tier === 'T1', 'UC-1: tier downgraded to T1');
    // Step 5: tool_auth for safe tool succeeds at T1
    const r = await hsm.tool_auth('summarize', nonce);
    assert(r.boundTier === 'T1', 'UC-1: summarize allowed at T1, bound to T1');
    // Final: verify no unsafe action reached EFFECTOR
    assert(hsm.deniedCount > 0, 'UC-1: denial counter incremented');
  }

  // ═══════════════════════════════════════════════════════════════════════
  // UC-2: Spawn quorum scenario
  // ═══════════════════════════════════════════════════════════════════════
  section('UC-2: Sub-agent spawn quorum scenario');
  {
    const hsm = new HSM();
    await hsm.provision();
    const sk = (n) => Array.from({length:n}, () =>
      crypto.getRandomValues(new Uint8Array(32)));
    // 3-of-5 attempt — should fail
    let lowQuorumRejected = false;
    try {
      await hsm.spawn_quorum_sign(sk(3), 'spawn:research-task');
    } catch (e) {
      lowQuorumRejected = (e.code === 'LOW_QUORUM');
    }
    assert(lowQuorumRejected, 'UC-2: 3-of-5 rejected (below threshold)');
    // 4-of-5 retry — should succeed
    const r = await hsm.spawn_quorum_sign(sk(4), 'spawn:research-task-retry');
    assert(r.threshold === '4-of-5', 'UC-2: 4-of-5 accepted');
    assert(r.pcr3 && r.pcr3.length === 64,
      'UC-2: PCR3 extended with quorum commitment');
  }

  // ═══════════════════════════════════════════════════════════════════════
  // UC-3: Host-side ledger rewrite scenario
  // ═══════════════════════════════════════════════════════════════════════
  section('UC-3: Host-side ledger rewrite detection');
  {
    const hsm = new HSM();
    await hsm.provision();
    // Two legitimate entries
    await hsm.audit_sign({event:'op-1'}, hex(hsm.pcr[1].value));
    await hsm.audit_sign({event:'op-2'}, hex(hsm.pcr[1].value));
    const pcr1Before = hex(hsm.pcr[1].value);
    // Capture an old stale PCR1 value (from before op-2)
    // The most realistic attack: use stale-but-non-zero value
    let detected = false;
    try {
      await hsm.audit_sign({event:'forged-rewrite'}, '0'.repeat(64));
    } catch (e) {
      detected = (e.code === 'LEDGER_MISMATCH');
    }
    assert(detected, 'UC-3: rewrite attempt detected');
    assert(hsm.tier === 'T0', 'UC-3: device sealed at T0');
    assert(hex(hsm.pcr[4].value) !==
           hex(new Uint8Array(32).buffer),
      'UC-3: PCR4 extended with tamper cause');
    // Post-tamper sign should fail
    let postTamperBlocked = false;
    try {
      await hsm.audit_sign({event:'post-tamper'}, hex(hsm.pcr[1].value));
    } catch (e) {
      postTamperBlocked = (e.code === 'T0_SEAL');
    }
    assert(postTamperBlocked, 'UC-3: post-tamper signing blocked');
  }


  // ═══════════════════════════════════════════════════════════════════════
  // ROUND 2 REGRESSIONS (v2.1 hardening)
  // ═══════════════════════════════════════════════════════════════════════

  section('R2-D1: verifyTrace() really re-derives PCR chains');
  {
    const hsm = new HSM();
    await hsm.provision();
    await hsm.audit_sign({event:'a'}, hex(hsm.pcr[1].value));
    await hsm.audit_sign({event:'b'}, hex(hsm.pcr[1].value));
    const trace = hsm.exportTrace();
    // Verify CLEAN trace returns verified:true
    const cleanResult = await hsm.verifyTrace(trace);
    assert(cleanResult.verified === true,
      'R2-D1: clean trace passes verification');
    assert(cleanResult.pcrChainsChecked && cleanResult.pcrChainsChecked.PCR1,
      'R2-D1: result includes PCR1 chain check');
    // Tamper with a PCR1 newValue and verify detection
    const lines = trace.split('\n');
    const tampered = lines.map(l => {
      const o = JSON.parse(l);
      if (o.type === 'pcr_extend' && o.pcrIndex === 1 && o.extensions > 1) {
        o.newValue = 'a'.repeat(64);  // corrupt
      }
      return JSON.stringify(o);
    }).join('\n');
    const tamperResult = await hsm.verifyTrace(tampered);
    assert(tamperResult.verified === false,
      'R2-D1: tampered trace FAILS verification (was always-true stub)');
    assert(tamperResult.mismatches && tamperResult.mismatches.length > 0,
      'R2-D1: mismatches array populated with specifics');
  }

  section('R2-D2: eventLog capped at MAX_EVENT_LOG (ring buffer)');
  {
    const hsm = new HSM();
    await hsm.provision();
    // Drive 6000+ ops to exceed the 5000 cap
    for (let i = 0; i < 6000; i++) {
      hsm._emit('synthetic', { i });  // direct emit to avoid burning crypto time
    }
    assert(hsm.eventLog.length === 5000,
      'R2-D2: eventLog capped at 5000', `len=${hsm.eventLog.length}`);
    // Oldest entries dropped: first entry should no longer be provision_start/complete
    const firstType = hsm.eventLog[0].type;
    assert(firstType === 'synthetic',
      'R2-D2: oldest entries evicted (FIFO ring buffer)',
      `first=${firstType}`);
  }

  section('R2-D4: _simulateLatency warns on unknown opType');
  {
    const hsm = new HSM({siliconMode: true});
    await hsm.provision();
    let warningCaptured = null;
    hsm.addEventListener('warning', (e) => { warningCaptured = e.detail; });
    await hsm._simulateLatency('TYPO_NONEXISTENT');
    assert(warningCaptured !== null,
      'R2-D4: warning event emitted for unknown opType');
    assert(warningCaptured && warningCaptured.code === 'UNKNOWN_OP_TYPE',
      'R2-D4: warning has UNKNOWN_OP_TYPE code',
      `code=${warningCaptured?.code}`);
  }

  section('R2-D5: pcr_extend validates measurement length');
  {
    const hsm = new HSM();
    await hsm.provision();
    // Try a 5-byte measurement on the internal extend
    try {
      await hsm._extendPcr(2, new Uint8Array(5));
      assert(false, 'R2-D5: 5-byte measurement should be rejected');
    } catch (e) {
      assert(e.code === 'BAD_MEASUREMENT',
        'R2-D5: rejects 5-byte measurement on _extendPcr',
        `code=${e.code}`);
      assert(e.fields && e.fields.expected === 32 && e.fields.actual === 5,
        'R2-D5: error fields populated (expected/actual)');
    }
    // Try a 33-byte measurement
    try {
      await hsm._extendPcr(2, new Uint8Array(33));
      assert(false, 'R2-D5: 33-byte measurement should be rejected');
    } catch (e) {
      assert(e.code === 'BAD_MEASUREMENT',
        'R2-D5: rejects 33-byte measurement (too long)');
    }
    // Try the public pcr_extend (which dispatches via _extendPcr)
    try {
      await hsm.pcr_extend(2, new Uint8Array(16));
      assert(false, 'R2-D5: public pcr_extend should reject 16-byte');
    } catch (e) {
      assert(e.code === 'BAD_MEASUREMENT',
        'R2-D5: public pcr_extend also enforces 32-byte length');
    }
    // 32 bytes still works
    const r = await hsm.pcr_extend(2, new Uint8Array(32));
    assert(r && r.newValue,
      'R2-D5: 32-byte measurement still accepted (no false-positive)');
  }

  section('R2-D7: HSMError fields populated on representative throws');
  {
    const hsm = new HSM();
    await hsm.provision();
    let err = null;
    try {
      await hsm._extendPcr(99, new Uint8Array(32));  // out-of-range PCR
    } catch (e) { err = e; }
    assert(err && err.fields && err.fields.idx === 99,
      'R2-D7: PCR_INDEX_OUT_OF_RANGE error has structured fields',
      `fields=${JSON.stringify(err?.fields)}`);
  }

// ═══════════════════════════════════════════════════════════════════════
  // FINAL REPORT
  // ═══════════════════════════════════════════════════════════════════════
  console.log('\n' + '═'.repeat(72));
  console.log(' FINAL TEST REPORT');
  console.log('═'.repeat(72));
  console.log(`  Total: ${PASS + FAIL}`);
  console.log(`  PASS:  ${PASS}`);
  console.log(`  FAIL:  ${FAIL}`);
  if (FAIL > 0) {
    console.log('\n  FAILURES:');
    FAILURES.forEach(f => console.log('    - ' + f));
  }
  console.log('═'.repeat(72));
  if (FAIL > 0) process.exit(1);
  console.log('  ALL FIXES VERIFIED. DoD-LEVEL VERIFICATION COMPLETE.');
}

main().catch(e => {
  console.error('TEST HARNESS CRASHED:', e);
  process.exit(2);
});

// ─────────────────────────────────────────────────────────────────────────
// THIS BLOCK IS UNREACHABLE — main() exits above.
// Kept for documentation of test-extension pattern.
// ─────────────────────────────────────────────────────────────────────────
