#!/usr/bin/env node
/**
 * floor-signer (Машина 5, Блок 2) — подписант по-критерийного GREEN.
 *
 * ЧЕСТНАЯ ЦЕННОСТЬ (Δ4, факт-аудит кода spec §10):
 *   Подписант НЕ делает HMAC-ключ нечитаемым для мотивированного контроллера.
 *   Факт: ключ в OS-keychain читается любым процессом того же пользователя
 *   (receipt-key-config.mjs), а `node <файл>` в whitelist гейта (до сужения в
 *   Пакете 4) → контроллер может написать свой `tools/X.mjs` и прочесть ключ.
 *   Поэтому подписант даёт ДВЕ реальные вещи, не больше:
 *     (а) убирает ключ из логов/env (анти-казуальная утечка);
 *     (б) ШОВ ПОД HSM/YubiKey (v4.2) — туда встанет железо, и подделка станет
 *         реально невозможной (см. spec §7, ~0.5% неустранимо без железа).
 *   Блок 2 НЕ заявляет «защиту ключа от контроллера».
 *
 * occurrence-счётчик — анти-пере-зачёт: один зелёный прогон нельзя пере-зачесть
 * на два критерия. Монотонность проверяется acceptGreen относительно
 * lastOccurrence (чистая функция; состояние держит потребитель). Подделка
 * criterion_id/occurrence/fingerprint после подписи аннулирует sig (HMAC привязан
 * к полному payload в домене M5_GREEN).
 *
 * Чистые функции (ключ — аргумент), как receipt-sign.mjs. «Только подписант
 * держит ключ» — операционное свойство процесса-обёртки (Пакет 4+), не этого
 * модуля.
 */
import { signPayload, verifyReceipt, RECEIPT_DOMAINS } from './receipt-sign.mjs';

/**
 * Подписать по-критерийный GREEN в домене M5_GREEN.
 * @param {{criterion_id:string, code_fingerprint:string, occurrence:number}} payload
 * @param {string} key — ключ подписанта; без ключа → null (fail-closed).
 * @returns {{criterion_id, code_fingerprint, occurrence, sig}|null}
 */
export function signGreen(payload, key) {
  if (!key) return null;
  const body = {
    criterion_id: payload.criterion_id,
    code_fingerprint: payload.code_fingerprint,
    occurrence: payload.occurrence,
  };
  const sig = signPayload(body, key, RECEIPT_DOMAINS.M5_GREEN);
  if (!sig) return null;
  return { ...body, sig };
}

/** Проверить подпись GREEN в домене M5_GREEN. Без sig/ключа → false (fail-closed). */
export function verifyGreen(record, key) {
  return verifyReceipt(record, key, RECEIPT_DOMAINS.M5_GREEN);
}

/**
 * Принять GREEN: подпись валидна И occurrence строго больше последнего принятого.
 * lastOccurrence — состояние потребителя (по умолчанию 0). Защищает от:
 *   - подделки (подмена criterion_id/occurrence ломает sig) → 'bad-signature';
 *   - нецелого/неположительного occurrence → 'bad-occurrence';
 *   - пере-зачёта одного green (occurrence <= lastOccurrence) → 'stale-occurrence'.
 * @returns {{accepted:boolean, reason:string}}
 */
export function acceptGreen(record, key, { lastOccurrence = 0 } = {}) {
  if (!verifyGreen(record, key)) return { accepted: false, reason: 'bad-signature' };
  const occ = record.occurrence;
  if (!Number.isInteger(occ) || occ <= 0) return { accepted: false, reason: 'bad-occurrence' };
  if (occ <= lastOccurrence) return { accepted: false, reason: 'stale-occurrence' };
  return { accepted: true, reason: 'ok' };
}
