// ═══════════════════════════════════════════════════════════════════════════
// BLADE-AGENT-HSM Emulator — Core Module v2.5 (adversarial high-assurance)
// ═══════════════════════════════════════════════════════════════════════════
// v2.0 → v2.1 → v2.5 progression: Round 1 (90 tests) + Round 2 (12 tests) +
// Round 3 (37 adversarial-hardening fixes). Audit-document traceability tags inline:
// C-01..C-08, H-01..H-11, M-01..M-16. Runs in Node 19+ and browsers.
// ═══════════════════════════════════════════════════════════════════════════

const ENC = new TextEncoder();
const DEC = new TextDecoder();

// ── Utilities ─────────────────────────────────────────────────────────────
export const hex = (buf) => Array.from(new Uint8Array(buf))
  .map(b => b.toString(16).padStart(2,'0')).join('');
export const hexShort = (h, n=12) => h && h.length > n ? h.slice(0, n) + '…' : (h || '');
export const sha256 = async (data) => {
  const b = typeof data === 'string' ? ENC.encode(data) : data;
  return await crypto.subtle.digest('SHA-256', b);
};
export const concatBytes = (...arrs) => {
  const total = arrs.reduce((a, b) => a + b.byteLength, 0);
  const out = new Uint8Array(total);
  let off = 0;
  for (const a of arrs) { out.set(new Uint8Array(a), off); off += a.byteLength; }
  return out.buffer;
};

// ── Seeded deterministic PRNG (replaces Math.random in simulation-critical
//    paths so EW bit-flips, SEU injection, Byzantine flags, and power telemetry
//    are reproducible for replay/audit). LCG (Numerical Recipes constants). ──
export class SeededPRNG {
  constructor(seed = 0xB1ADE25) {
    this.initialSeed = seed >>> 0;
    this.state = this.initialSeed;
  }
  next() {
    this.state = (1664525 * this.state + 1013904223) >>> 0;
    return this.state / 0x100000000;
  }
  nextInt(maxExclusive) { return Math.floor(this.next() * maxExclusive); }
  reset(seed = this.initialSeed) { this.initialSeed = seed >>> 0; this.state = this.initialSeed; }
  snapshot() { return { initialSeed: this.initialSeed, state: this.state }; }
}

// ── Constants ─────────────────────────────────────────────────────────────
export const TIER_ORDER = ['T3', 'T2', 'T1', 'T0'];
export const PCR_NAMES = [
  'Tier State', 'Ledger Chain', 'Tool Policy', 'Spawn Quorum', 'Tamper Cause',
];
export const ABI_OPCODES = {
  AUDIT_SIGN:        0x10,
  PCR_EXTEND:        0x11,
  PCR_QUOTE:         0x12,
  TOOL_AUTH:         0x13,
  SPAWN_QUORUM_SIGN: 0x14,
  RECONCILE_LEDGER:  0x15,  // M-11
};
export const ABI_V2_MAGIC   = 0xA5;  // C-08 binary ABI v2
export const ABI_V2_VERSION = 0x02;

// Mission modes (M-07)
export const MISSION_MODES = Object.freeze({
  NOMINAL: 'NOMINAL', DEGRADED: 'DEGRADED',
  CONTAINED: 'CONTAINED', SEALED: 'SEALED',
});

// CF4: 2D policy matrix [tier][tool] = allow|deny|step-up
export const DEFAULT_POLICY = {
  T3: { search:'allow', summarize:'allow', fetch_url:'allow',
        write_file:'step-up', run_code:'step-up',
        fetch_credentials:'deny', send_email:'step-up' },
  T2: { search:'allow', summarize:'allow', fetch_url:'allow',
        write_file:'deny', run_code:'deny',
        fetch_credentials:'deny', send_email:'deny' },
  T1: { search:'allow', summarize:'allow', fetch_url:'deny',
        write_file:'deny', run_code:'deny',
        fetch_credentials:'deny', send_email:'deny' },
  T0: { search:'deny', summarize:'deny', fetch_url:'deny',
        write_file:'deny', run_code:'deny',
        fetch_credentials:'deny', send_email:'deny' },
};

// Silicon timing + bus latency (M-01, H-05)
// SE051 datasheet: ECDSA P-256 ~60ms, P-384 ~90ms
// SLB 9670 SPI: PCR extend ~5ms
// I²C @ 400kHz: ~0.025 ms/byte
export const SILICON_TIMING_MS = {
  ECDSA_P256_SIGN: 60, ECDSA_P384_SIGN: 90,
  PCR_EXTEND: 5, HKDF_DERIVE: 15,
  KEYGEN_P256: 1200, KEYGEN_P384: 2000,
  TAMPER_CASCADE: 18,
  ML_DSA_KEYGEN: 800, ML_DSA_SIGN: 50,   // M-14 hybrid sig
  I2C_TRANSFER: 0,                        // computed per-byte
  SPI_TRANSFER: 0,                        // computed per-byte
  SHA256_HASH: 0,                         // computed per-byte
};
export const I2C_MS_PER_BYTE = 0.025;     // 400 kHz Fast-mode
export const SPI_MS_PER_BYTE = 0.005;     // 4 MHz typical
export const SHA256_MS_PER_KB = 0.05;

// H-04: Hold-up capacitor energy model
export const CAPACITOR_MODEL = Object.freeze({
  capacitanceFarads: 47e-6,                   // 47µF
  vNominal: 3.3, vMin: 2.7,
  // E = ½ × C × (V_init² − V_drop²)
  energyJoules: 0.5 * 47e-6 * (3.3*3.3 - 2.7*2.7),  // ≈ 8.46e-5 J
  // SE051 active sign current: 30mA @ 3.3V = 99mW
  signPowerWatts: 0.099,
  // Energy for 60ms ECDSA P-256: P × t = 0.099 × 0.060 = 0.00594 J
  signEnergyRequired: 0.099 * 0.060,
});

// H-09 Measured-boot authorized firmware manifest
export const AUTHORIZED_FIRMWARE_HASHES = new Set([
  // Hash of canonical firmware manifest string (computed on first run)
]);

// ═══════════════════════════════════════════════════════════════════════════
// C-01: AsyncMutex — true FIFO Promise queue (replaces polling loop)
// ═══════════════════════════════════════════════════════════════════════════
export class AsyncMutex {
  constructor() {
    this._queue = [];
    this._locked = false;
  }
  async lock() {
    return new Promise(resolve => {
      if (!this._locked) {
        this._locked = true;
        resolve();
      } else {
        this._queue.push(resolve);
      }
    });
  }
  unlock() {
    if (this._queue.length > 0) {
      const next = this._queue.shift();
      next();
    } else {
      this._locked = false;
    }
  }
  get queueLength() { return this._queue.length; }
}

// ═══════════════════════════════════════════════════════════════════════════
// C-08: HostObjectStore — large objects referenced by handle
// ═══════════════════════════════════════════════════════════════════════════
export class HostObjectStore {
  constructor() { this.objects = new Map(); }
  async put(type, obj) {
    const canonical = JSON.stringify(obj, Object.keys(obj).sort());
    const digest = hex(await sha256(canonical));
    const handle = digest.slice(0, 16);
    this.objects.set(handle, {
      type, obj, digest,
      createdAt: new Date().toISOString(),
    });
    return handle;
  }
  get(handle, expectedType = null) {
    const record = this.objects.get(handle);
    if (!record) throw new HSMError('BAD_HANDLE', `No host object for handle ${handle}`);
    if (expectedType && record.type !== expectedType) {
      throw new HSMError('BAD_HANDLE_TYPE',
        `Expected ${expectedType}, got ${record.type}`);
    }
    return record.obj;
  }
  size() { return this.objects.size; }
}

// ═══════════════════════════════════════════════════════════════════════════
// CRC-16 for ABI frame integrity (M-13 EW bit-flip detection)
// ═══════════════════════════════════════════════════════════════════════════
export function crc16(buffer) {
  let crc = 0xFFFF;
  for (let i = 0; i < buffer.length; i++) {
    crc ^= buffer[i];
    for (let j = 0; j < 8; j++) {
      if (crc & 1) crc = (crc >>> 1) ^ 0xA001;
      else crc >>>= 1;
    }
  }
  return crc & 0xFFFF;
}


// ═══════════════════════════════════════════════════════════════════════════
// HSMError — structured error class
// ═══════════════════════════════════════════════════════════════════════════
export class HSMError extends Error {
  constructor(code, message, fields = {}) {
    super(message);
    this.code = code;
    this.fields = fields;
    this.name = 'HSMError';
  }
}

// ═══════════════════════════════════════════════════════════════════════════
// HSM — Core class (v2.5)
// ═══════════════════════════════════════════════════════════════════════════
export class HSM extends EventTarget {
  constructor(opts = {}) {
    super();
    // MP6: identity cert history persists across re-provisioning
    this.identityCertHistory = [];
    // C-02, C-03: ring buffer caps — configuration set once in constructor
    this.MAX_LATENCIES    = opts.maxLatencies    || 5000;
    this.MAX_HISTORY      = opts.maxHistory      || 1000;
    this.MAX_EVENT_LOG    = opts.maxEventLog     || 5000;
    this.MAX_POWER_TRACE  = opts.maxPowerTrace   || 200;
    this._reset();
    this.opts = {
      siliconMode: opts.siliconMode || false,
      policy: opts.policy || DEFAULT_POLICY,
      quorumThreshold: opts.quorumThreshold || { n: 4, m: 5 },
      ztaTokenLifespanMs: opts.ztaTokenLifespanMs || 5000,
      h9TimeoutMs: opts.h9TimeoutMs || 15000,
      ewBitErrorRate: opts.ewBitErrorRate || 0.05,
      eccScrubIntervalMs: opts.eccScrubIntervalMs || 500,
      radiationSEURate: opts.radiationSEURate || 0.05,
      byzantineDetectionRate: opts.byzantineDetectionRate || 0.0,
      seed: (opts.seed !== undefined ? (opts.seed >>> 0) : 0xB1ADE25),
      ...opts,
      deterministicECDSA: false,  // H-02: FORCED — Web Crypto cannot do RFC 6979
    };
    // Deterministic PRNG for simulation-critical stochastic paths (replay)
    this.prng = new SeededPRNG(this.opts.seed);
    // Fault injection knobs (H-06, H-07 enforced; others extant)
    this._faultInject = {
      i2cBusStuck: false, tpmCrcFail: false,
      voltageBrownout: false, rngFail: false,
    };
    // Hazard state (M-10, M-13, H-08)
    this.hazardState = {
      sustainedEW: false,
      ewBitErrorRate: this.opts.ewBitErrorRate,
      h9Tripped: false,
      lastValidHostPingMs: 0,
      radiationActive: false,
      seuDetectedCount: 0,
      eccCorrectionsCount: 0,
      lastEccScrubMs: 0,
    };
    // Audit signature verification key cache (M-08)
    this._exportedSigningPubJwk = null;
  }

  // Backward-compatible alias: UI/evidence code references hardwareState.
  get hardwareState() { return this.hazardState; }

  // Public wrapper so scenarios don't call the private tick directly.
  async simulateHardwarePhysics() { return await this._hardwarePhysicsTick(); }

  _reset() {
    this.provisioned    = false;
    this.tampered       = false;
    this.tier           = 'T3';
    this.deviceId       = null;
    this.signingKey     = null;
    this.signingPubKey  = null;
    this.deviceKey      = null;
    this.deviceKeyPub   = null;
    this.toolMaster     = null;
    this.pqcDeviceKey   = null;             // M-14 hybrid PQC
    this.pqcDeviceKeyPub = null;
    this.pcr = PCR_NAMES.map(name => ({
      name,
      value: new Uint8Array(32).buffer,
      extensions: 0,
      history: [],
    }));
    this.ledgerSeq      = 0;
    this.opCount        = 0;
    this.deniedCount    = 0;
    this.latencies      = [];
    this.allLatencies   = [];               // C-02: capped at MAX_LATENCIES below
    this.provisionTime  = null;
    this.monotonicCounter = 0;
    this.eventLog       = [];
    // M-01: deterministic monotonic simulation clock
    this.simClockMs     = 0;
    this.bootMeasured   = false;            // H-09
    // C-01: AsyncMutex replaces polling
    this._mutex         = new AsyncMutex();
    // Power telemetry ring buffer (M-15)
    this.powerTrace     = [];
    // identityCertHistory intentionally NOT reset (MP6)
    // NOTE: MAX_LATENCIES, MAX_HISTORY, MAX_EVENT_LOG, MAX_POWER_TRACE
    // are configuration, set in constructor; not reset here so tests can override.
  }

  // M-01: deterministic monotonic clock tick
  _advanceClock(ms) {
    this.simClockMs += ms;
  }

  // M-01: deterministic timestamp (replaces Date.now()-derived strings in critical paths)
  _timestamp() {
    return new Date(this.simClockMs).toISOString();
  }

  // H-05, M-01, M-15: silicon timing + bus latency + power trace
  async _simulateLatency(opType, payloadBytes = 0) {
    let msDelay = 0;
    if (opType in SILICON_TIMING_MS) {
      msDelay = SILICON_TIMING_MS[opType] || 0;
    } else {
      this._emit('warning', {
        code: 'UNKNOWN_OP_TYPE',
        msg: `Unknown opType '${opType}' — no delay applied`, opType,
      });
    }
    // H-05: byte-length transit latency
    if (payloadBytes > 0) {
      if (opType.startsWith('I2C')) msDelay += payloadBytes * I2C_MS_PER_BYTE;
      else if (opType.startsWith('SPI')) msDelay += payloadBytes * SPI_MS_PER_BYTE;
      else if (opType.startsWith('SHA256')) msDelay += (payloadBytes / 1024) * SHA256_MS_PER_KB;
    }
    // Always advance the deterministic clock (logic timing)
    this._advanceClock(msDelay);

    // M-15: emit power trace sample
    let peakDrawMA = 15.0;  // idle baseline
    if (opType.includes('ECDSA') || opType.includes('ML_DSA')) {
      peakDrawMA = 45.0 + (this.prng.next() * 0.2);  // constant-time, low variance
    } else if (opType.includes('I2C')) {
      peakDrawMA = 22.0 + (this.prng.next() * 2.5);
    } else if (opType.includes('SPI')) {
      peakDrawMA = 25.0 + (this.prng.next() * 1.5);
    }
    this.powerTrace.push({
      simClockMs: this.simClockMs,
      opType, drawMA: peakDrawMA, durationMs: msDelay,
    });
    if (this.powerTrace.length > this.MAX_POWER_TRACE) {
      this.powerTrace.splice(0, this.powerTrace.length - this.MAX_POWER_TRACE);
    }
    this._emit('telemetry_power_trace', {
      simClockMs: this.simClockMs, opType,
      drawMA: peakDrawMA, durationMs: msDelay,
    });
    // H-08 SEU + ECC scrub tick
    if (this.opts.siliconMode || this.hazardState.radiationActive) {
      await this._hardwarePhysicsTick();
    }
    // Only actually sleep when silicon mode is on (UI animation)
    if (this.opts.siliconMode && msDelay > 0) {
      await new Promise(r => setTimeout(r, msDelay));
    }
  }

  // H-08: SEU injection + ECC memory scrubber
  async _hardwarePhysicsTick() {
    // 1) SEU injection (only when radiation env active)
    if (this.hazardState.radiationActive
        && this.prng.next() < this.opts.radiationSEURate * 0.1) {
      const bitFlipMask = 1 << this.prng.nextInt(32);
      this.monotonicCounter = (this.monotonicCounter ^ bitFlipMask) >>> 0;
      this.hazardState.seuDetectedCount++;
      this._emit('hardware_anomaly', {
        msg: 'SEU bit-flip injected into SRAM (monotonic counter).',
        simClockMs: this.simClockMs,
      });
    }
    // 2) ECC scrub: every eccScrubIntervalMs in sim time
    if (this.simClockMs - this.hazardState.lastEccScrubMs > this.opts.eccScrubIntervalMs) {
      this.hazardState.lastEccScrubMs = this.simClockMs;
      // Detect monotonic counter desync from ledger
      if (this.monotonicCounter < this.ledgerSeq
          || this.monotonicCounter > this.ledgerSeq + 100) {
        this.hazardState.eccCorrectionsCount++;
        this.monotonicCounter = this.ledgerSeq;  // auto-correct
        this._emit('ecc_scrub_correction', {
          msg: 'ECC corrected SRAM bit-flip; monotonic counter restored from ledger.',
          simClockMs: this.simClockMs,
          corrections: this.hazardState.eccCorrectionsCount,
        });
      }
    }
  }

  // HP7: structured event emission (capped ring buffer, C-02)
  _emit(type, detail) {
    const ev = {
      type,
      timestamp: this._timestamp(),
      simClockMs: this.simClockMs,
      correlationId: detail.correlationId || this._currentTraceId,
      ...detail,
    };
    this.eventLog.push(ev);
    if (this.eventLog.length > this.MAX_EVENT_LOG) {
      this.eventLog.splice(0, this.eventLog.length - this.MAX_EVENT_LOG);
    }
    this.dispatchEvent(new CustomEvent(type, { detail: ev }));
    this.dispatchEvent(new CustomEvent('event', { detail: ev }));
  }

  // C-01: clean AsyncMutex wrap (no polling)
  async _withMutex(fn) {
    await this._mutex.lock();
    try { return await fn(); }
    finally { this._mutex.unlock(); }
  }


  // ── PCR extend (CF7 structured cause + R2-D5 length validation +
  //                H-06/H-07 fault flag enforcement) ─────────────────
  async _extendPcr(idx, measurement, causeRecord = null) {
    if (idx < 0 || idx >= this.pcr.length) {
      throw new HSMError('PCR_INDEX_OUT_OF_RANGE', `PCR index ${idx}`,
        { idx, max: this.pcr.length - 1 });
    }
    // H-03: strict instance check (no plain Arrays)
    const validInstance = (measurement instanceof Uint8Array)
      || (measurement instanceof ArrayBuffer)
      || (measurement && measurement.constructor && measurement.constructor.name === 'Uint8Array');
    if (!validInstance) {
      throw new HSMError('BAD_MEASUREMENT_TYPE',
        'Measurement must be Uint8Array or ArrayBuffer',
        { gotType: typeof measurement });
    }
    const measByteLen = measurement.byteLength !== undefined
      ? measurement.byteLength
      : (measurement.length !== undefined ? measurement.length : -1);
    if (measByteLen !== 32) {
      throw new HSMError('BAD_MEASUREMENT',
        `Measurement must be exactly 32 bytes; got ${measByteLen}`,
        { expected: 32, actual: measByteLen });
    }
    // H-06: I²C bus stuck
    if (this._faultInject.i2cBusStuck) {
      this._emit('error', { code: 'I2C_BUS_STUCK',
        msg: 'I2C bus stuck low during PCR extend', pcrIndex: idx });
      throw new HSMError('I2C_BUS_STUCK',
        'Cannot extend PCR: I2C bus stuck low', { pcrIndex: idx });
    }
    // H-07: TPM SPI CRC failure
    if (this._faultInject.tpmCrcFail) {
      this._emit('error', { code: 'TPM_CRC_FAIL',
        msg: 'TPM SPI CRC verification failed', pcrIndex: idx });
      throw new HSMError('TPM_CRC_FAIL',
        'Cannot extend PCR: TPM CRC failure', { pcrIndex: idx });
    }
    // H-05: SPI transit latency for PCR extend
    await this._simulateLatency('SPI_TRANSFER', 32);

    const current = this.pcr[idx].value;
    const next = await sha256(concatBytes(current, measurement));
    this.pcr[idx].value = next;
    this.pcr[idx].extensions++;
    this.pcr[idx].history.push({
      timestamp: this._timestamp(),
      simClockMs: this.simClockMs,
      cause: causeRecord || { type: 'generic' },
      measurementHex: hex(measurement),
      newValueHex: hex(next),
    });
    // CF7: cap PCR history per-PCR
    if (this.pcr[idx].history.length > this.MAX_HISTORY) {
      this.pcr[idx].history.splice(0, this.pcr[idx].history.length - this.MAX_HISTORY);
    }
    this._emit('pcr_extend', {
      pcrIndex: idx, pcrName: this.pcr[idx].name,
      measurementHex: hex(measurement),    // R2-D1: include for verifier
      newValue: hex(next), extensions: this.pcr[idx].extensions,
      cause: causeRecord,
    });
    await this._simulateLatency('PCR_EXTEND');
    return next;
  }

  async _causeMeasurement(record) {
    const fields = [record.type, ...(record.data || [])].map(String);
    const encoded = fields.map(f => `${f.length}:${f}`).join('|');
    return await sha256(encoded);
  }

  // ═══════════════════════════════════════════════════════════════════════
  // PROVISIONING + Measured Boot (H-09)
  // ═══════════════════════════════════════════════════════════════════════
  async provision(opts = {}) {
    if (this.provisioned && !opts.force) {
      throw new HSMError('ALREADY_PROVISIONED', 'Device already provisioned');
    }
    return this._withMutex(async () => {
      this._reset();
      this.opts = { ...this.opts, ...opts };

      if (this._faultInject.rngFail) {
        this._emit('error', { code: 'RNG_SELF_TEST_FAIL',
          msg: 'SE051 on-chip RNG self-test failure during keygen' });
        throw new HSMError('RNG_FAIL', 'RNG self-test failed');
      }

      this.deviceId = crypto.getRandomValues(new Uint8Array(16)).buffer;
      this._emit('provision_start', { deviceId: hex(this.deviceId) });

      // ECDSA P-256 audit signing key
      await this._simulateLatency('KEYGEN_P256');
      const kp1 = await crypto.subtle.generateKey(
        { name: 'ECDSA', namedCurve: 'P-256' }, false, ['sign']);
      this.signingKey = kp1.privateKey;
      this.signingPubKey = kp1.publicKey;

      // ECDSA P-384 attestation key
      await this._simulateLatency('KEYGEN_P384');
      const kp2 = await crypto.subtle.generateKey(
        { name: 'ECDSA', namedCurve: 'P-384' }, false, ['sign']);
      this.deviceKey = kp2.privateKey;
      this.deviceKeyPub = kp2.publicKey;

      // M-14: ML-DSA (Dilithium2) simulated keys
      await this._simulateLatency('ML_DSA_KEYGEN');
      this.pqcDeviceKey = crypto.getRandomValues(new Uint8Array(2560));
      this.pqcDeviceKeyPub = crypto.getRandomValues(new Uint8Array(1312));

      this.toolMaster = crypto.getRandomValues(new Uint8Array(32));

      const initCause = { type: 'provision_init',
        data: ['T3', hex(this.deviceId)] };
      await this._extendPcr(0, new Uint8Array(await this._causeMeasurement(initCause)), initCause);

      const signingPubRaw = await crypto.subtle.exportKey('raw', kp1.publicKey);
      const devicePubRaw = await crypto.subtle.exportKey('raw', kp2.publicKey);
      // M-08: export signing public key as SPKI for trace verification
      // (avoids JWK key_ops mismatch when re-importing for verify)
      this._exportedSigningPubSpki = await crypto.subtle.exportKey('spki', kp1.publicKey);
      // F-42: cache device P-384 public key SPKI for trace-anchor verification
      this._exportedDevicePubSpki = await crypto.subtle.exportKey('spki', kp2.publicKey);
      const idCert = {
        deviceId: hex(this.deviceId),
        signingPubKey: hex(signingPubRaw),
        attestationPubKey: hex(devicePubRaw),
        attestationPubSpki: this._exportedDevicePubSpki ? hex(this._exportedDevicePubSpki) : null,  // F-42 pinning
        pqcAttestationPubKey: hex(this.pqcDeviceKeyPub),  // M-14
        signingPubSpki: hex(this._exportedSigningPubSpki),  // M-08
        provisionedAt: this._timestamp(),
        firmwareVersion: 'BLADE-AGENT-HSM-v2.5-CNSA',
      };
      this.identityCertHistory.push(idCert);
      // C-03: cap identity history
      if (this.identityCertHistory.length > this.MAX_HISTORY) {
        this.identityCertHistory.splice(0, this.identityCertHistory.length - this.MAX_HISTORY);
      }

      this.provisioned = true;
      this.tampered = false;
      this.tier = 'T3';
      this.provisionTime = new Date(this.simClockMs);

      this._emit('provision_complete', {
        deviceId: hex(this.deviceId),
        identityCertificate: idCert,
        pcr0: hex(this.pcr[0].value),
      });
      return idCert;
    });
  }

  // H-09: TCG DICE Measured Boot
  async bootSequence(firmwareManifest) {
    if (!this.provisioned) {
      throw new HSMError('UNPROVISIONED',
        'Must provision before bootSequence');
    }
    this._emit('boot_init', {
      msg: 'Power-on reset. Initiating RTM Measured Boot.',
    });
    await this._simulateLatency('SHA256_HASH', firmwareManifest.length);
    const firmwareHashBuf = await sha256(firmwareManifest);
    const firmwareHash = new Uint8Array(firmwareHashBuf);
    const firmwareHashHex = hex(firmwareHashBuf);

    // Extend PCR0 with firmware measurement
    const bootCause = { type: 'measured_boot',
      data: [firmwareHashHex.slice(0, 16)] };
    await this._extendPcr(0, firmwareHash, bootCause);
    this.bootMeasured = true;

    this._emit('boot_measured', {
      pcr0: hex(this.pcr[0].value),
      firmwareHashHex,
      msg: `Firmware integrity measured: ${firmwareHashHex.slice(0, 12)}...`,
    });
    return { pcr0: hex(this.pcr[0].value), firmwareHashHex };
  }

  // MP1: centralized tier transitions
  async _transitionTier(targetTier, cause) {
    const order = TIER_ORDER;
    const curIdx = order.indexOf(this.tier);
    const tgtIdx = order.indexOf(targetTier);
    if (tgtIdx === -1) {
      throw new HSMError('INVALID_TIER', `Unknown tier: ${targetTier}`);
    }
    if (tgtIdx < curIdx) {
      this._emit('tier_upgrade_rejected', {
        from: this.tier, to: targetTier,
        reason: 'upgrades require re-provisioning',
      });
      throw new HSMError('UPGRADE_REJECTED',
        `Tier upgrade ${this.tier}→${targetTier} rejected`);
    }
    if (tgtIdx === curIdx) {
      this._emit('tier_noop', { tier: this.tier });
      return false;
    }
    const from = this.tier;
    this.tier = targetTier;
    const causeRecord = { type: 'tier_transition',
      data: [from, targetTier, cause.reason || 'unspecified'] };
    await this._extendPcr(0, new Uint8Array(await this._causeMeasurement(causeRecord)), causeRecord);
    this._emit('tier_transition', {
      from, to: targetTier,
      cause: cause.reason || 'unspecified',
      pcr0: hex(this.pcr[0].value),
    });
    return true;
  }


  // ═══════════════════════════════════════════════════════════════════════
  // ABI: audit_sign (0x10) — CF1 rejection chain + M-08 canonical signing
  // ═══════════════════════════════════════════════════════════════════════
  async audit_sign(payloadObj, pcr1Expect = null, traceId = null) {
    this._currentTraceId = traceId;
    return this._withMutex(async () => {
      if (this._faultInject.voltageBrownout) {
        this._emit('error', { code: 'VOLTAGE_BROWNOUT',
          msg: 'Supply voltage below 2.7V threshold' });
        throw new HSMError('VOLTAGE_BROWNOUT', 'Supply voltage below threshold');
      }
      if (this.tampered) {
        await this._logRejection('audit_sign', 'TAMPERED', { reason: 'device tampered' });
        throw new HSMError('TAMPERED', 'Device tampered — signing disabled');
      }
      if (this.tier === 'T0') {
        await this._logRejection('audit_sign', 'T0_SEAL',
          { reason: 'signing key sealed at T0', tier: 'T0' });
        throw new HSMError('T0_SEAL', 'Tier T0 — signing key sealed');
      }
      if (!this.signingKey) {
        throw new HSMError('UNPROVISIONED', 'No signing key available');
      }

      const t0 = performance.now();
      const canonical = this._canonicalize(payloadObj);
      const currentLedgerHex = hex(this.pcr[1].value);

      // CF1: PCR1 mismatch → extend with rejection, cascade to PCR4, seal T0
      if (pcr1Expect !== null && pcr1Expect !== undefined
          && pcr1Expect !== currentLedgerHex) {
        const rejectionRecord = {
          type: 'audit_rejection',
          data: ['ledger_mismatch', pcr1Expect.slice(0,16),
                 currentLedgerHex.slice(0,16), this._timestamp()],
        };
        await this._extendPcr(1, new Uint8Array(await this._causeMeasurement(rejectionRecord)), rejectionRecord);
        const rewriteRecord = { type: 'rewrite_attempt', data: [pcr1Expect.slice(0,16)] };
        await this._extendPcr(4, new Uint8Array(await this._causeMeasurement(rewriteRecord)), rewriteRecord);
        await this._transitionTier('T0', { reason: 'ledger_rewrite_attempt' });
        await this._logRejection('audit_sign', 'LEDGER_MISMATCH', {
          reason: 'attempted ledger rewrite',
          internalHead: currentLedgerHex.slice(0,16),
          suppliedHead: pcr1Expect.slice(0,16),
        });
        throw new HSMError('LEDGER_MISMATCH',
          'PCR1 mismatch — rewrite attempt detected — sealed to T0');
      }

      await this._simulateLatency('ECDSA_P256_SIGN');
      const sig = await crypto.subtle.sign(
        { name: 'ECDSA', hash: 'SHA-256' },
        this.signingKey, ENC.encode(canonical));

      const entryHash = await sha256(canonical);
      const extendRecord = { type: 'audit_entry',
        data: [String(this.ledgerSeq + 1), hex(entryHash).slice(0,16)] };
      await this._extendPcr(1, new Uint8Array(entryHash), extendRecord);

      this.ledgerSeq++;
      this.opCount++;
      this.monotonicCounter++;
      const dt = performance.now() - t0;
      this.latencies.push(dt);
      this.allLatencies.push(dt);
      // C-02: cap allLatencies ring buffer
      if (this.allLatencies.length > this.MAX_LATENCIES) {
        this.allLatencies.splice(0, this.allLatencies.length - this.MAX_LATENCIES);
      }
      if (this.latencies.length > 50) this.latencies.shift();

      const canonicalSha = hex(await sha256(canonical));
      const result = {
        seq: this.ledgerSeq, payload: payloadObj,
        canonical,                                  // M-08: include canonical for verification
        canonicalSha256: canonicalSha,
        sigHex: hex(sig), pcr1: hex(this.pcr[1].value),
        latencyMs: dt, tier: this.tier,
        monotonic: this.monotonicCounter,
      };
      // M-08: emit canonical for trace-based signature verification
      this._emit('audit_sign_ok', {
        opcode: 0x10, seq: this.ledgerSeq, payload: payloadObj,
        canonical, canonicalSha256: canonicalSha,
        sigHex: hex(sig), pcr1: hex(this.pcr[1].value),
        latencyMs: dt,
      });
      return result;
    });
  }

  _canonicalize(obj) {
    const sortKeys = (o) => {
      if (Array.isArray(o)) return o.map(sortKeys);
      if (o !== null && typeof o === 'object') {
        const sorted = {};
        for (const k of Object.keys(o).sort()) sorted[k] = sortKeys(o[k]);
        return sorted;
      }
      return o;
    };
    return JSON.stringify(sortKeys(obj));
  }

  async _logRejection(method, code, fields) {
    this.deniedCount++;
    this._emit('rejection', { method, code, fields, tier: this.tier });
  }

  // ═══════════════════════════════════════════════════════════════════════
  // ABI: pcr_extend (0x11) — host can extend PCR2 only
  // ═══════════════════════════════════════════════════════════════════════
  async pcr_extend(idx, measurementBuf, hostCause = null) {
    return this._withMutex(async () => {
      if (this.tampered || this.tier === 'T0') {
        await this._logRejection('pcr_extend', 'DENIED', { tier: this.tier });
        throw new HSMError('DENIED', 'Device in T0 / tampered');
      }
      if (idx !== 2) {
        await this._logRejection('pcr_extend', 'PCR_RESTRICTED',
          { requested: idx, allowed: [2] });
        throw new HSMError('PCR_RESTRICTED',
          `Host can only extend PCR 2 — requested PCR ${idx}`);
      }
      const causeRecord = hostCause || { type: 'policy_update', data: ['host_supplied'] };
      const newVal = await this._extendPcr(idx, measurementBuf, causeRecord);
      this._emit('pcr_extend_ok', { opcode: 0x11, idx, newValue: hex(newVal) });
      return { idx, newValue: hex(newVal) };
    });
  }

  // ═══════════════════════════════════════════════════════════════════════
  // ABI: pcr_quote (0x12) — CF5 canonical binary + M-14 hybrid sig + M-16 bounds
  // ═══════════════════════════════════════════════════════════════════════
  async pcr_quote(selectionBitmap, nonce) {
    return this._withMutex(async () => {
      if (this.tampered) {
        await this._logRejection('pcr_quote', 'TAMPERED', {});
        throw new HSMError('TAMPERED', 'Cannot quote — device tampered');
      }
      if (!nonce || nonce.byteLength !== 32) {
        throw new HSMError('BAD_NONCE', 'Nonce must be exactly 32 bytes');
      }
      // M-16: bitmap bounds check
      if (selectionBitmap < 0 || selectionBitmap > 0x1F) {
        throw new HSMError('BAD_BITMAP',
          `Selection bitmap 0x${selectionBitmap.toString(16)} exceeds 5-PCR range 0x1F`,
          { bitmap: selectionBitmap, maxValid: 0x1F });
      }

      const selectedPcrs = [];
      for (let i = 0; i < this.pcr.length; i++) {
        if (selectionBitmap & (1 << i)) selectedPcrs.push(i);
      }
      const headerLen = 1 + 1 + 2 + 32 + 8;
      const pcrLen = selectedPcrs.length * (1 + 32);
      const blob = new Uint8Array(headerLen + pcrLen);
      let off = 0;
      blob[off++] = 0x01;
      blob[off++] = selectionBitmap & 0xff;
      blob[off++] = 0; blob[off++] = 32;
      blob.set(new Uint8Array(nonce), off); off += 32;
      const mc = BigInt(this.monotonicCounter);
      for (let i = 7; i >= 0; i--) {
        blob[off + i] = Number((mc >> BigInt((7-i)*8)) & 0xffn);
      }
      off += 8;
      for (const idx of selectedPcrs) {
        blob[off++] = idx;
        blob.set(new Uint8Array(this.pcr[idx].value), off);
        off += 32;
      }

      await this._simulateLatency('ECDSA_P384_SIGN');
      const classicalSig = await crypto.subtle.sign(
        { name: 'ECDSA', hash: { name: 'SHA-384' } },
        this.deviceKey, blob);

      // M-14: CNSA 2.0 hybrid signature — simulated ML-DSA Dilithium2
      await this._simulateLatency('ML_DSA_SIGN');
      const pqcSig = crypto.getRandomValues(new Uint8Array(2420));

      this.monotonicCounter++;
      const result = {
        quoteBlobHex: hex(blob),
        sigHex: hex(classicalSig),                  // classical (for back-compat)
        hybridSig: {                                // M-14: CNSA 2.0 hybrid
          classicalHex: hex(classicalSig),
          pqcHex: hex(pqcSig),
        },
        selectedPcrs, nonceHex: hex(nonce),
        monotonic: this.monotonicCounter,
        pcrs: selectedPcrs.map(i => ({
          idx: i, name: this.pcr[i].name, value: hex(this.pcr[i].value),
        })),
      };
      this._emit('pcr_quote_ok', { opcode: 0x12, ...result });
      return result;
    });
  }

  // ═══════════════════════════════════════════════════════════════════════
  // ABI: tool_auth (0x13) — CF3 nonce + CF4 2D policy + M-09 ZTA expiry
  // ═══════════════════════════════════════════════════════════════════════
  async tool_auth(toolId, sessionNonce) {
    return this._withMutex(async () => {
      if (this.tampered || this.tier === 'T0') {
        await this._logRejection('tool_auth', 'TIER_T0',
          { tier: this.tier, tool: toolId });
        throw new HSMError('TIER_T0', 'Tier T0 — no tool authorization');
      }
      if (!sessionNonce || sessionNonce.byteLength !== 32) {
        await this._logRejection('tool_auth', 'BAD_NONCE',
          { tool: toolId, providedLen: sessionNonce ? sessionNonce.byteLength : 0 });
        throw new HSMError('BAD_NONCE', 'session_nonce must be exactly 32 bytes');
      }
      const policy = this.opts.policy[this.tier];
      if (!policy) throw new HSMError('NO_POLICY', `No policy for tier ${this.tier}`);
      const decision = policy[toolId];
      if (decision === undefined) {
        await this._logRejection('tool_auth', 'TOOL_UNKNOWN', { tool: toolId, tier: this.tier });
        throw new HSMError('TOOL_UNKNOWN',
          `Tool '${toolId}' has no policy entry for tier ${this.tier}`);
      }
      if (decision === 'deny') {
        await this._logRejection('tool_auth', 'POLICY_DENY',
          { tool: toolId, tier: this.tier, decision });
        throw new HSMError('POLICY_DENY',
          `Tool '${toolId}' denied at tier ${this.tier}`);
      }
      if (decision === 'step-up') {
        await this._logRejection('tool_auth', 'STEP_UP_REQUIRED',
          { tool: toolId, tier: this.tier });
        throw new HSMError('STEP_UP_REQUIRED',
          `Tool '${toolId}' requires operator step-up at tier ${this.tier}`);
      }

      // M-09: ZTA expiry — bind to monotonic clock
      const expirySimMs = this.simClockMs + this.opts.ztaTokenLifespanMs;

      const ikm = await crypto.subtle.importKey(
        'raw', this.toolMaster, 'HKDF', false, ['deriveBits']);
      const salt = concatBytes(this.pcr[0].value, this.pcr[2].value);
      const infoBuf = concatBytes(
        ENC.encode(toolId), ENC.encode('|'),
        sessionNonce, ENC.encode('|'),
        ENC.encode(this.tier), ENC.encode('|'),
        ENC.encode(String(expirySimMs)),     // M-09: bind expiry into HKDF
      );
      await this._simulateLatency('HKDF_DERIVE');
      const tok = await crypto.subtle.deriveBits(
        { name: 'HKDF', hash: 'SHA-256', salt, info: infoBuf },
        ikm, 256);

      this.opCount++;
      const result = {
        token: hex(tok), boundTier: this.tier,
        toolId, sessionNonceHex: hex(sessionNonce),
        expirySimMs,                                  // M-09
        issuedAtSimMs: this.simClockMs,
      };
      this._emit('tool_auth_ok', { opcode: 0x13, ...result });
      return result;
    });
  }

  // M-09: validate token alive against monotonic clock
  validateTokenAlive(expirySimMs) {
    if (this.simClockMs > expirySimMs) {
      this._emit('zta_token_expired', {
        simClockMs: this.simClockMs, expiry: expirySimMs,
        overrunMs: this.simClockMs - expirySimMs,
      });
      throw new HSMError('TOKEN_EXPIRED',
        'ZTA constraint: token exceeded monotonic lifespan',
        { simClockMs: this.simClockMs, expiry: expirySimMs });
    }
    return true;
  }


  // ═══════════════════════════════════════════════════════════════════════
  // ABI: spawn_quorum_sign (0x14) — CF2 + FM12 + M-12 Byzantine isolation
  // ═══════════════════════════════════════════════════════════════════════
  async spawn_quorum_sign(signerKeys, descriptor) {
    return this._withMutex(async () => {
      if (this.tampered || this.tier === 'T0') {
        await this._logRejection('spawn_quorum_sign', 'TIER_T0', { tier: this.tier });
        throw new HSMError('TIER_T0', 'Cannot sign spawn quorum at T0');
      }
      if (!Array.isArray(signerKeys)) {
        throw new HSMError('BAD_INPUT', 'signerKeys must be an array');
      }
      const { n, m } = this.opts.quorumThreshold;
      if (signerKeys.length > m) {
        await this._logRejection('spawn_quorum_sign', 'TOO_MANY_SIGNERS',
          { provided: signerKeys.length, max: m });
        throw new HSMError('TOO_MANY_SIGNERS',
          `${signerKeys.length} signers exceeds M=${m}`);
      }
      const unique = new Set(signerKeys.map(k => hex(k)));
      if (unique.size !== signerKeys.length) {
        await this._logRejection('spawn_quorum_sign', 'DUPLICATE_SIGNER',
          { provided: signerKeys.length, unique: unique.size });
        throw new HSMError('DUPLICATE_SIGNER',
          `${signerKeys.length - unique.size} duplicate signer keys detected`);
      }
      if (signerKeys.length < n) {
        await this._logRejection('spawn_quorum_sign', 'LOW_QUORUM',
          { provided: signerKeys.length, required: n });
        throw new HSMError('LOW_QUORUM',
          `${signerKeys.length}/${m} signers below ${n}-of-${m} threshold`);
      }

      // M-12: Byzantine fault isolation
      const byzantineActors = this._detectByzantineCollusion(signerKeys);
      if (byzantineActors.length > 0) {
        await this._logRejection('spawn_quorum_sign', 'BYZANTINE_ISOLATION',
          { isolatedKeys: byzantineActors.map(k => hexShort(k, 8)) });
        const faultCause = { type: 'byzantine_fault',
          data: [hex(await sha256(descriptor)).slice(0,16)] };
        await this._extendPcr(4, new Uint8Array(await this._causeMeasurement(faultCause)), faultCause);
        await this._transitionTier('T0', { reason: 'byzantine_quorum_attempt' });
        throw new HSMError('BYZANTINE_FAULT',
          'Quorum contains isolated Byzantine actors. Device sealed.',
          { isolated: byzantineActors.length });
      }

      const descHash = await sha256(descriptor);
      const signerListSorted = Array.from(unique).sort().join('|');
      const commitment = await sha256(signerListSorted + '|' + hex(descHash));

      await this._simulateLatency('ECDSA_P256_SIGN');
      const sig = await crypto.subtle.sign(
        { name: 'ECDSA', hash: 'SHA-256' },
        this.signingKey, new Uint8Array(commitment));

      const causeRecord = { type: 'spawn_quorum',
        data: [String(signerKeys.length), String(m), hex(descHash).slice(0,16)] };
      await this._extendPcr(3, new Uint8Array(commitment), causeRecord);

      const result = {
        sigHex: hex(sig), commitmentHex: hex(commitment),
        pcr3: hex(this.pcr[3].value),
        threshold: `${signerKeys.length}-of-${m}`,
        descriptorHash: hex(descHash),
      };
      this._emit('spawn_quorum_ok', { opcode: 0x14, ...result });
      return result;
    });
  }

  // M-12: Byzantine detection — configurable rate (off by default for tests)
  _detectByzantineCollusion(signerKeys) {
    const isolated = [];
    for (const key of signerKeys) {
      if (this.prng.next() < this.opts.byzantineDetectionRate) {
        isolated.push(key);
      }
    }
    return isolated;
  }

  // ═══════════════════════════════════════════════════════════════════════
  // M-10: H9 EW hazard + CARA fallback
  // ═══════════════════════════════════════════════════════════════════════
  setEWHazard(active, errorRate = 0.05) {
    this.hazardState.sustainedEW = active;
    this.hazardState.ewBitErrorRate = errorRate;
    this._emit('hazard_state_change',
      { ewActive: active, ber: errorRate });
  }

  async checkH9Hazard() {
    const H9_TIMEOUT = this.opts.h9TimeoutMs;
    if (this.hazardState.h9Tripped) return;
    if (this.simClockMs - this.hazardState.lastValidHostPingMs > H9_TIMEOUT) {
      this.hazardState.h9Tripped = true;
      this._emit('hazard_h9_tripped', {
        simClockMs: this.simClockMs,
        msg: 'H9 Hazard: permanent EW/comms loss. Initiating CARA fallback.',
      });
      // CARA: extend PCR4, lock to T1, zeroize toolMaster
      const h9Cause = { type: 'hazard_h9_cara_fallback',
        data: [String(this.simClockMs)] };
      await this._extendPcr(4, new Uint8Array(await this._causeMeasurement(h9Cause)), h9Cause);
      if (this.tier !== 'T0') {
        await this._transitionTier('T1', { reason: 'h9_cara_survival_mode' });
      }
      this.toolMaster = null;
      this._emit('cara_fallback_complete', {
        tier: this.tier, simClockMs: this.simClockMs,
      });
    }
  }

  // Host-ping update (called by ABI dispatcher on valid frames)
  notifyHostPing() {
    this.hazardState.lastValidHostPingMs = this.simClockMs;
  }

  // ═══════════════════════════════════════════════════════════════════════
  // M-11: Split-brain ledger reconciliation
  // ═══════════════════════════════════════════════════════════════════════
  async reconcile_ledger(remotePcr1Head, remoteLedgerCount) {
    return this._withMutex(async () => {
      await this._simulateLatency('I2C_TRANSFER', 64);
      const localPcr1 = hex(this.pcr[1].value);
      if (localPcr1 === remotePcr1Head && this.ledgerSeq === remoteLedgerCount) {
        this._emit('ledger_sync_ok', { state: 'synchronized' });
        return { state: 'synchronized', localPcr1, remotePcr1Head };
      }
      if (this.ledgerSeq > remoteLedgerCount) {
        this._emit('ledger_sync_push_required', {
          localSeq: this.ledgerSeq, remoteSeq: remoteLedgerCount,
        });
        return { state: 'push_required',
                 localSeq: this.ledgerSeq, remoteSeq: remoteLedgerCount };
      }
      // Fork detected
      const forkCause = { type: 'ledger_fork_detected',
        data: [remotePcr1Head.slice(0,16), localPcr1.slice(0,16)] };
      await this._extendPcr(4, new Uint8Array(await this._causeMeasurement(forkCause)), forkCause);
      await this._transitionTier('T2', { reason: 'split_brain_reconciliation_failure' });
      throw new HSMError('LEDGER_FORK',
        'Cryptographic split-brain detected. Requires coalition override.',
        { localPcr1, remotePcr1Head, localSeq: this.ledgerSeq, remoteSeq: remoteLedgerCount });
    });
  }

  // ═══════════════════════════════════════════════════════════════════════
  // TAMPER — H-04 capacitor energy model + CF6 farewell before zeroize
  // ═══════════════════════════════════════════════════════════════════════
  async tamper(causeStr) {
    if (this.tampered) return null;
    return this._withMutex(async () => {
      this._emit('tamper_detected', {
        cause: causeStr, simClockMs: this.simClockMs,
      });

      // H-04: Hardware physics — does the hold-up capacitor have enough energy?
      // Configurable via opts.capacitanceFarads (default 47µF per CAPACITOR_MODEL)
      const capFarads = this.opts.capacitanceFarads || CAPACITOR_MODEL.capacitanceFarads;
      const E_available = 0.5 * capFarads *
        (CAPACITOR_MODEL.vNominal**2 - CAPACITOR_MODEL.vMin**2);
      const E_required  = CAPACITOR_MODEL.signEnergyRequired;
      const capacitorSufficient = (E_available >= E_required);

      let farewellSig = null;
      let farewellFailed = false;
      let farewellFailReason = null;

      if (this.signingKey && capacitorSufficient) {
        try {
          const farewellPayload = this._canonicalize({
            event: 'tamper_detected', cause: causeStr,
            tier: this.tier, ledgerSeq: this.ledgerSeq,
            simClockMs: this.simClockMs,
            energyBudgetJoules: E_available,
          });
          await this._simulateLatency('ECDSA_P256_SIGN');
          const fSig = await crypto.subtle.sign(
            { name: 'ECDSA', hash: 'SHA-256' },
            this.signingKey, ENC.encode(farewellPayload));
          farewellSig = hex(fSig);
          const fHash = await sha256(farewellPayload);
          const fCause = { type: 'farewell_entry', data: [causeStr] };
          await this._extendPcr(1, new Uint8Array(fHash), fCause);
          this._emit('farewell_signed', {
            cause: causeStr, farewellSigHex: farewellSig,
            farewellPayload, pcr1: hex(this.pcr[1].value),
            energyConsumed: E_required,
            energyAvailable: E_available,
          });
        } catch (e) {
          farewellFailed = true;
          farewellFailReason = e.message || String(e);
          this._emit('farewell_failed', { reason: farewellFailReason });
        }
      } else if (this.signingKey && !capacitorSufficient) {
        // H-04: PHYSICS BUDGET FAILED — immediate zeroize, no farewell
        farewellFailed = true;
        farewellFailReason =
          `Insufficient hold-up capacitance: E_available=${E_available.toExponential(3)}J < E_required=${E_required.toExponential(3)}J`;
        this._emit('farewell_failed', {
          reason: farewellFailReason,
          energyAvailable: E_available,
          energyRequired: E_required,
        });
      }

      const tamperCause = { type: 'tamper', data: [causeStr] };
      await this._simulateLatency('TAMPER_CASCADE');
      await this._extendPcr(4, new Uint8Array(await this._causeMeasurement(tamperCause)), tamperCause);

      // F-12 FIX: capture the actual prior tier BEFORE sealing to T0,
      // so the forensic chain preserves tier-of-origin (T3/T2/T1).
      const fromTier = this.tier;
      this.tier = 'T0';
      const t0Cause = { type: 'tier_transition',
        data: [fromTier, 'T0', causeStr] };
      await this._extendPcr(0, new Uint8Array(await this._causeMeasurement(t0Cause)), t0Cause);

      this.tampered = true;
      // Zeroize
      this.signingKey = null;
      this.signingPubKey = null;
      this.deviceKey = null;
      this.deviceKeyPub = null;
      this.toolMaster = null;
      this.pqcDeviceKey = null;
      this.pqcDeviceKeyPub = null;

      this._emit('tamper_complete', {
        cause: causeStr,
        pcr4: hex(this.pcr[4].value), pcr0: hex(this.pcr[0].value),
        tier: 'T0', keysZeroized: true,
        capacitorSufficient,
        farewellAttempted: capacitorSufficient && this.signingKey !== undefined,
        farewellSucceeded: !farewellFailed && farewellSig !== null,
        farewellFailReason,
      });
      return { farewellSig, pcr4: hex(this.pcr[4].value),
               capacitorSufficient };
    });
  }

  // ═══════════════════════════════════════════════════════════════════════
  // OBSERVABILITY — exportTrace, verifyTrace, replay invariants, stats
  // ═══════════════════════════════════════════════════════════════════════
  // exportTrace now appends a signed anchor (F-42): a P-384 signature over
  // {eventCount, finalPcrHexes, traceSha256}. Truncating the trace breaks the
  // anchor because eventCount and the final PCR digests no longer match.
  exportTrace() {
    return this.eventLog.map(e => JSON.stringify(e)).join('\n');
  }

  // F-42: produce a tamper-evident anchor signed by the P-384 device key.
  async exportAnchoredTrace() {
    const traceJsonl = this.exportTrace();
    const anchor = await this._buildTraceAnchor(traceJsonl);
    return { traceJsonl, anchor };
  }

  async _buildTraceAnchor(traceJsonl) {
    const eventCount = traceJsonl.split('\n').filter(l => l.trim()).length;
    const finalPcrHexes = this.pcr.map(p => hex(p.value));
    const traceSha256 = hex(await sha256(traceJsonl));
    const anchorBody = JSON.stringify({ eventCount, finalPcrHexes, traceSha256 });
    let anchorSigHex = null;
    if (this.deviceKey) {
      const sig = await crypto.subtle.sign(
        { name: 'ECDSA', hash: 'SHA-384' },
        this.deviceKey, ENC.encode(anchorBody));
      anchorSigHex = hex(sig);
    }
    return {
      eventCount, finalPcrHexes, traceSha256, anchorBody,
      anchorSigHex,
      attestationPubSpki: this._exportedDevicePubSpki ? hex(this._exportedDevicePubSpki) : null,
    };
  }

  // F-10 + F-42 + PINNING FIX. verifyTrace now:
  //  (1) re-derives PCR chains;
  //  (2) F-42: checks the signed P-384 anchor (count + sha + signature);
  //  (3) PINNING (Residual-2): the anchor's attestation key is NOT trusted just
  //      because it is embedded in the file. The caller pins an out-of-band
  //      identity (options.pinnedAttestationPubSpki, or the verifier's own
  //      identityCertHistory). If a pin is supplied and does not match, the
  //      trace is treated as adversarially forged. If no pin is available, the
  //      trust model is downgraded to integrity/same-session only.
  //  (4) F-10 + Residual-1: audit-entry signatures are verified against the
  //      signing key embedded IN THE TRACE's provision event (authenticated
  //      transitively by the anchor over traceSha256), NOT the live instance
  //      key. This makes foreign-trace verification correct.
  //
  // result.trustModel:
  //   'adversarial-forgery-resistant' — anchor present, identity pinned & matched,
  //        anchor sig valid, count/sha matched, trace-keyed audit sigs valid.
  //   'integrity-and-same-session'    — anchor present but no out-of-band pin;
  //        detects accidental corruption + same-session tampering only.
  //   'pcr-chain-only'                — no anchor supplied.
  async verifyTrace(traceJsonl, anchor = null, options = {}) {
    const lines = traceJsonl.split('\n').filter(l => l.trim());
    let events;
    try {
      events = lines.map(l => JSON.parse(l));
    } catch (e) {
      return { verified: false, reason: 'PARSE_ERROR', detail: e.message };
    }
    const result = {
      verified: true,
      totalEvents: events.length,
      pcrChainsChecked: {},
      signaturesChecked: { total: 0, valid: 0, invalid: 0 },
      anchorChecked: false,
      identityPinned: false,
      trustModel: 'pcr-chain-only',
      mismatches: [],
    };
    // ── (1) PCR chain re-derivation ──
    for (let pcrIdx = 0; pcrIdx < 5; pcrIdx++) {
      const pcrEvents = events.filter(
        e => e.type === 'pcr_extend' && e.pcrIndex === pcrIdx);
      if (pcrEvents.length === 0) continue;
      let derivedHex = '0'.repeat(64);
      let matchedAll = true;
      let firstMismatch = null;
      for (const ev of pcrEvents) {
        if (!ev.measurementHex || !ev.newValue) {
          matchedAll = false;
          firstMismatch = { event: ev, reason: 'MISSING_FIELDS' };
          break;
        }
        const currentBytes = new Uint8Array(
          derivedHex.match(/.{2}/g).map(b => parseInt(b, 16)));
        const measBytes = new Uint8Array(
          ev.measurementHex.match(/.{2}/g).map(b => parseInt(b, 16)));
        const concat = new Uint8Array(currentBytes.length + measBytes.length);
        concat.set(currentBytes, 0);
        concat.set(measBytes, currentBytes.length);
        const newDerived = await crypto.subtle.digest('SHA-256', concat);
        derivedHex = hex(newDerived);
        if (derivedHex !== ev.newValue) {
          matchedAll = false;
          firstMismatch = {
            extensionIdx: ev.extensions,
            expectedHex: ev.newValue,
            derivedHex,
          };
          break;
        }
      }
      result.pcrChainsChecked[`PCR${pcrIdx}`] = {
        extensions: pcrEvents.length,
        finalHex: derivedHex, verified: matchedAll,
      };
      if (!matchedAll) {
        result.verified = false;
        result.mismatches.push({ pcrIndex: pcrIdx, ...firstMismatch });
      }
    }

    // ── (2)+(3) F-42 anchor + identity pinning (establish trust FIRST) ──
    let anchorTrusted = false;
    if (anchor) {
      result.anchorChecked = true;
      result.trustModel = 'integrity-and-same-session';
      const recomputedSha = hex(await sha256(traceJsonl));
      const countMatches = (anchor.eventCount === events.length);
      const shaMatches = (anchor.traceSha256 === recomputedSha);

      // Determine the pinned (out-of-band trusted) attestation identity.
      // Priority: explicit caller pin → verifier's own identity history.
      let pinnedSpki = options.pinnedAttestationPubSpki || null;
      if (!pinnedSpki && Array.isArray(this.identityCertHistory)) {
        const match = this.identityCertHistory.find(
          c => c && c.attestationPubSpki && c.attestationPubSpki === anchor.attestationPubSpki);
        if (match) pinnedSpki = match.attestationPubSpki;
      }
      // Residual-2: if a pin exists, the embedded key MUST equal it.
      const identityMatches = pinnedSpki
        ? (pinnedSpki === anchor.attestationPubSpki)
        : null;  // null = no pin available (cannot resist adversary)
      result.identityPinned = !!pinnedSpki && identityMatches === true;

      let anchorSigOk = false;
      if (anchor.anchorSigHex && anchor.attestationPubSpki) {
        try {
          const spki = new Uint8Array(anchor.attestationPubSpki.match(/.{2}/g).map(b => parseInt(b, 16)));
          const aPub = await crypto.subtle.importKey(
            'spki', spki, { name: 'ECDSA', namedCurve: 'P-384' }, false, ['verify']);
          const sigBytes = new Uint8Array(anchor.anchorSigHex.match(/.{2}/g).map(b => parseInt(b, 16)));
          anchorSigOk = await crypto.subtle.verify(
            { name: 'ECDSA', hash: 'SHA-384' },
            aPub, sigBytes, ENC.encode(anchor.anchorBody));
        } catch (_) { anchorSigOk = false; }
      }
      result.anchor = { countMatches, shaMatches, anchorSigOk, identityMatches };

      // Hard failures: structural integrity always required.
      if (!countMatches || !shaMatches || !anchorSigOk) {
        result.verified = false;
        result.mismatches.push({
          type: 'ANCHOR_INVALID',
          countMatches, shaMatches, anchorSigOk,
          expectedCount: anchor.eventCount, actualCount: events.length,
        });
      }
      // Pinning failure: a supplied pin that does not match means the file's
      // self-asserted identity is untrusted → adversarial forgery.
      if (pinnedSpki && identityMatches === false) {
        result.verified = false;
        result.mismatches.push({
          type: 'ATTESTATION_IDENTITY_MISMATCH',
          note: 'Anchor attestation key does not match pinned device identity (possible forgery).',
        });
      }
      // Trust is only adversary-resistant when identity is pinned AND matched
      // AND the anchor structurally verifies.
      anchorTrusted = result.identityPinned && countMatches && shaMatches && anchorSigOk;
      if (anchorTrusted) result.trustModel = 'adversarial-forgery-resistant';
    }

    // ── (4) F-10 + Residual-1: verify audit sigs off the TRACE's signing key ──
    const signEvents = events.filter(
      e => e.type === 'audit_sign_ok' && e.canonical && e.sigHex);
    if (signEvents.length > 0) {
      // Prefer the signing key embedded in the trace's own provision event.
      let traceSigningSpki = null;
      const prov = events.find(e =>
        e.type === 'provision_complete' &&
        e.identityCertificate && e.identityCertificate.signingPubSpki);
      if (prov) traceSigningSpki = prov.identityCertificate.signingPubSpki;
      // Fallback to live instance key only if the trace carries none.
      const usingTraceKey = !!traceSigningSpki;
      const spkiHex = traceSigningSpki || (this._exportedSigningPubSpki ? hex(this._exportedSigningPubSpki) : null);
      result.signaturesChecked.keySource = usingTraceKey ? 'trace-embedded' : (spkiHex ? 'live-instance' : 'none');

      if (spkiHex) {
        const spkiBytes = new Uint8Array(spkiHex.match(/.{2}/g).map(b => parseInt(b, 16)));
        const pubKey = await crypto.subtle.importKey(
          'spki', spkiBytes, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['verify']);
        for (const ev of signEvents) {
          result.signaturesChecked.total++;
          let ok = false;
          try {
            const sigBytes = new Uint8Array(ev.sigHex.match(/.{2}/g).map(b => parseInt(b, 16)));
            ok = await crypto.subtle.verify(
              { name: 'ECDSA', hash: 'SHA-256' },
              pubKey, sigBytes, ENC.encode(ev.canonical));
          } catch (_) { ok = false; }
          if (ok) {
            result.signaturesChecked.valid++;
          } else {
            result.signaturesChecked.invalid++;
            result.verified = false;
            result.mismatches.push({
              type: 'SIGNATURE_INVALID', seq: ev.seq,
              sigHexPrefix: (ev.sigHex || '').slice(0, 16),
            });
          }
        }
      } else {
        result.signaturesChecked.note = 'NO_PUBKEY_AVAILABLE_FOR_SIGNATURE_CHECK';
      }
    }
    return result;
  }

  // M-06: Replay-invariant verifier
  async verifyReplayInvariants(traceJsonl) {
    const lines = traceJsonl.split('\n').filter(Boolean);
    const events = lines.map(l => JSON.parse(l));
    const failures = [];
    let lastSeq = 0;
    let sawTamper = false;
    let tier = 'T3';
    for (const ev of events) {
      if (ev.type === 'audit_sign_ok') {
        if (typeof ev.seq !== 'number') {
          failures.push({ type: 'BAD_SEQ', event: ev.type });
        }
        if (ev.seq <= lastSeq) {
          failures.push({ type: 'NON_MONOTONIC_SEQ',
            prev: lastSeq, current: ev.seq });
        }
        lastSeq = ev.seq;
        if (sawTamper) failures.push({ type: 'SIGN_AFTER_TAMPER' });
      }
      if (ev.type === 'tier_transition') tier = ev.to || tier;
      if (ev.type === 'tamper_complete') { sawTamper = true; tier = 'T0'; }
      if (tier === 'T0' && ev.type === 'tool_auth_ok') {
        failures.push({ type: 'TOOL_AUTH_AFTER_T0' });
      }
    }
    const pcrCheck = await this.verifyTrace(traceJsonl);
    return {
      verified: pcrCheck.verified && failures.length === 0,
      pcrCheck,
      invariantFailures: failures,
    };
  }

  // M-08: Audit signature verification using exported public key (SPKI)
  async verifyAuditSignature(canonical, sigHex) {
    if (!this._exportedSigningPubSpki) {
      throw new HSMError('NO_PUBKEY', 'No exported signing public key cached');
    }
    const pubKey = await crypto.subtle.importKey(
      'spki', this._exportedSigningPubSpki,
      { name: 'ECDSA', namedCurve: 'P-256' },
      false, ['verify']);
    const sigBytes = new Uint8Array(sigHex.match(/.{2}/g).map(b => parseInt(b, 16)));
    return await crypto.subtle.verify(
      { name: 'ECDSA', hash: 'SHA-256' },
      pubKey, sigBytes, ENC.encode(canonical));
  }

  getStats() {
    if (this.allLatencies.length === 0) return { n: 0 };
    const sorted = [...this.allLatencies].sort((a, b) => a - b);
    const mean = sorted.reduce((a, b) => a + b, 0) / sorted.length;
    const sd = Math.sqrt(sorted.reduce((a, b) => a + (b - mean) ** 2, 0) / sorted.length);
    const pct = (p) => sorted[Math.min(sorted.length - 1,
      Math.floor(sorted.length * p))];
    return {
      n: sorted.length,
      min: sorted[0], max: sorted[sorted.length - 1],
      mean, median: pct(0.5), p95: pct(0.95), p99: pct(0.99),
      sd, throughputOpsPerSec: mean > 0 ? 1000 / mean : 0,
    };
  }

  // M-07: mission mode derivation
  missionMode() {
    if (this.tampered || this.tier === 'T0') return MISSION_MODES.SEALED;
    if (this.tier === 'T1') return MISSION_MODES.CONTAINED;
    if (this.tier === 'T2') return MISSION_MODES.DEGRADED;
    return MISSION_MODES.NOMINAL;
  }

  injectFault(faultType, enabled = true) {
    if (!(faultType in this._faultInject)) {
      throw new HSMError('UNKNOWN_FAULT', `Unknown fault: ${faultType}`);
    }
    this._faultInject[faultType] = enabled;
    this._emit('fault_injection', { faultType, enabled });
  }

  async reset() {
    this._emit('reset_initiated', {});
    this._reset();
    await this.provision();
  }
}


// ═══════════════════════════════════════════════════════════════════════════
// ABI Frame Layer v2 — C-08 + H-01 + M-13 EW frame corruption + CRC-16
// Frame v2: [magic:1][version:1][opcode:1][flags:1][handle:16]
//           [arg0:4][arg1:4][nonce:32][crc16:2][reserved:2]  = 64 bytes
// ═══════════════════════════════════════════════════════════════════════════
export class ABIv2 {
  constructor(hsm, store) {
    this.hsm = hsm;
    this.store = store || new HostObjectStore();
  }

  static frameSize() { return 64; }

  static build({ opcode, handle = '', arg0 = 0, arg1 = 0, nonce = null }) {
    const frame = new Uint8Array(64);
    frame[0] = ABI_V2_MAGIC;
    frame[1] = ABI_V2_VERSION;
    frame[2] = opcode;
    frame[3] = 0;  // flags
    const handleBytes = ENC.encode(handle.slice(0, 16));
    frame.set(handleBytes, 4);
    const dv = new DataView(frame.buffer);
    dv.setUint32(20, arg0 >>> 0, false);
    dv.setUint32(24, arg1 >>> 0, false);
    if (nonce) {
      if (nonce.byteLength !== 32) {
        throw new HSMError('BAD_FRAME_NONCE', 'nonce must be 32 bytes');
      }
      frame.set(nonce, 28);
    }
    // CRC-16 over bytes 0..59
    const crc = crc16(frame.slice(0, 60));
    dv.setUint16(60, crc, false);
    return frame;
  }

  async dispatch(frame) {
    if (!(frame instanceof Uint8Array)) {
      throw new HSMError('BAD_FRAME', 'Frame must be Uint8Array');
    }
    if (frame.length !== 64) {
      throw new HSMError('BAD_FRAME_LENGTH',
        `Expected 64 bytes, got ${frame.length}`);
    }
    // M-13: Sustained EW corruption (in-flight bit flips)
    let processed = frame;
    if (this.hsm.hazardState.sustainedEW) {
      processed = new Uint8Array(frame.length);
      for (let i = 0; i < frame.length; i++) {
        let b = frame[i];
        if (this.hsm.prng.next() < this.hsm.hazardState.ewBitErrorRate) {
          b ^= (1 << this.hsm.prng.nextInt(8));
        }
        processed[i] = b;
      }
    }

    if (processed[0] !== ABI_V2_MAGIC) {
      throw new HSMError('BAD_MAGIC',
        `Expected 0x${ABI_V2_MAGIC.toString(16)}, got 0x${processed[0].toString(16)}`);
    }
    if (processed[1] !== ABI_V2_VERSION) {
      throw new HSMError('BAD_VERSION', `Expected ABI v2, got v${processed[1]}`);
    }

    // M-13: CRC-16 verification (rejects bit-flipped frames)
    const dv = new DataView(processed.buffer, processed.byteOffset);
    const claimedCrc = dv.getUint16(60, false);
    const actualCrc = crc16(processed.slice(0, 60));
    if (claimedCrc !== actualCrc) {
      this.hsm._emit('abi_frame_dropped', {
        reason: 'CRC16 mismatch (likely EW bit flip)',
        claimed: claimedCrc, computed: actualCrc,
      });
      throw new HSMError('FRAME_CORRUPTED',
        'CRC-16 mismatch — frame dropped',
        { claimed: claimedCrc, computed: actualCrc });
    }

    // Notify host-ping watchdog (M-10)
    this.hsm.notifyHostPing();

    const opcode = processed[2];
    const handle = DEC.decode(processed.slice(4, 20)).replace(/\0+$/g, '');
    const arg0 = dv.getUint32(20, false);
    const arg1 = dv.getUint32(24, false);
    const nonce = processed.slice(28, 60);

    // H-01: wrap all opcode handling in strict try/catch
    try {
      switch (opcode) {
        case ABI_OPCODES.AUDIT_SIGN: {
          const obj = this.store.get(handle, 'audit_payload');
          await this.hsm._simulateLatency('I2C_TRANSFER', 64);
          return await this.hsm.audit_sign(obj.payload, obj.pcr1Expect || null,
            obj.traceId || null);
        }
        case ABI_OPCODES.PCR_EXTEND: {
          // arg0 = PCR index, nonce = 32-byte measurement
          await this.hsm._simulateLatency('SPI_TRANSFER', 32);
          return await this.hsm.pcr_extend(arg0 & 0xff, nonce);
        }
        case ABI_OPCODES.PCR_QUOTE: {
          // arg0 = bitmap, nonce = 32-byte challenge
          await this.hsm._simulateLatency('SPI_TRANSFER', 64);
          return await this.hsm.pcr_quote(arg0 & 0xff, nonce);
        }
        case ABI_OPCODES.TOOL_AUTH: {
          const obj = this.store.get(handle, 'tool_auth');
          await this.hsm._simulateLatency('I2C_TRANSFER', 64);
          return await this.hsm.tool_auth(obj.toolId, nonce);
        }
        case ABI_OPCODES.SPAWN_QUORUM_SIGN: {
          const obj = this.store.get(handle, 'spawn_quorum');
          await this.hsm._simulateLatency('I2C_TRANSFER', 64);
          return await this.hsm.spawn_quorum_sign(obj.signerKeys, obj.descriptor);
        }
        case ABI_OPCODES.RECONCILE_LEDGER: {
          const obj = this.store.get(handle, 'ledger_sync');
          await this.hsm._simulateLatency('I2C_TRANSFER', 64);
          return await this.hsm.reconcile_ledger(obj.remotePcr1Head, obj.remoteLedgerCount);
        }
        default:
          throw new HSMError('UNKNOWN_OPCODE',
            `Opcode 0x${opcode.toString(16)}`);
      }
    } catch (err) {
      if (err instanceof SyntaxError) {
        this.hsm._emit('abi_frame_error',
          { reason: 'Malformed payload', detail: err.message });
        throw new HSMError('MALFORMED_PAYLOAD', 'Invalid frame encoding');
      }
      throw err;
    }
  }
}

// ═══════════════════════════════════════════════════════════════════════════
// Legacy ABI v1 — kept for back-compat with original test suite
// ═══════════════════════════════════════════════════════════════════════════
export class ABI {
  constructor(hsm) { this.hsm = hsm; }

  async dispatch(frame) {
    if (!(frame instanceof Uint8Array)) {
      throw new HSMError('BAD_FRAME', 'Frame must be Uint8Array');
    }
    if (frame.length !== 64) {
      throw new HSMError('BAD_FRAME_LENGTH',
        `Frame must be exactly 64 bytes, got ${frame.length}`);
    }
    const opcode = frame[0];
    const len = frame[1];
    if (len > 62) {
      throw new HSMError('BAD_FRAME_LENGTH',
        `Payload length ${len} exceeds 62-byte max`);
    }
    const payload = frame.slice(2, 2 + len);

    // H-01: strict try/catch on JSON.parse
    try {
      switch (opcode) {
        case ABI_OPCODES.AUDIT_SIGN: {
          const obj = JSON.parse(DEC.decode(payload));
          return this.hsm.audit_sign(obj.payload, obj.pcr1Expect);
        }
        case ABI_OPCODES.PCR_EXTEND: {
          const idx = payload[0];
          const measurement = payload.slice(1, 33);
          return this.hsm.pcr_extend(idx, measurement);
        }
        case ABI_OPCODES.PCR_QUOTE: {
          const bitmap = payload[0];
          const nonce = payload.slice(1, 33);
          return this.hsm.pcr_quote(bitmap, nonce);
        }
        case ABI_OPCODES.TOOL_AUTH: {
          const obj = JSON.parse(DEC.decode(payload));
          const nonceBuf = new Uint8Array(
            obj.nonceHex.match(/.{2}/g).map(b => parseInt(b, 16)));
          return this.hsm.tool_auth(obj.toolId, nonceBuf);
        }
        case ABI_OPCODES.SPAWN_QUORUM_SIGN: {
          const obj = JSON.parse(DEC.decode(payload));
          const signerKeys = obj.signers.map(s => new Uint8Array(
            s.match(/.{2}/g).map(b => parseInt(b, 16))));
          return this.hsm.spawn_quorum_sign(signerKeys, obj.descriptor);
        }
        default:
          throw new HSMError('UNKNOWN_OPCODE',
            `Opcode 0x${opcode.toString(16)}`);
      }
    } catch (err) {
      if (err instanceof SyntaxError) {
        this.hsm._emit('abi_frame_error',
          { reason: 'Malformed JSON payload' });
        throw new HSMError('MALFORMED_PAYLOAD', 'Invalid frame encoding');
      }
      throw err;
    }
  }
}
