#!/usr/bin/env node
/**
 * freeze-gate (§5.6/6.1 CD-3) — ОТДЕЛЬНОЕ предусловие ПЕРЕД freezePlan/freezeArtifact.
 * plan-lock/seal/М6 НЕ трогаются (CD-3): caller сначала зовёт freezeGate, и ТОЛЬКО при
 * pass:true вызывает freezePlan/freezeArtifact [plan-lock.mjs:62/:194].
 * Проверки (порядок A1): (0) binding — verdict.plan_hash === planHash (нах.F4: stale/чужой
 * содержательный вердикт НЕ пропускает печать нового плана); (а) содержательный
 * mentor-вердикт (VA-8/SE-R6-6); (б) VA-9 (Д-2а, SE-A3-обязательство из context-verity.mjs):
 * пустой артефакт / 0 EXTRACTED — НЕ «проверен» → блок; (в) verity — нет неразрешённой
 * EXTRACTED (✅O2). hasUnresolvedExtractedImpl инъектируется (в проде
 * artifactHasUnresolvedExtracted из A; полярность: true = ЕСТЬ неразрешённая EXTRACTED =
 * ГРЯЗНО = блок) → C автономна. fail-CLOSED (SE-3): провал/сбой → блок → owner-escape;
 * «наставник ОШИБСЯ» (таймаут/краш → RETRY) vs «ЗАБЛОКИРОВАЛ» (legit → escape) — R2-SE-h,
 * различает caller по reason.
 */
import { validateMentorVerdict, isMentorVerdictSubstantive } from './mentor-verdict.mjs';

/**
 * @param {object} args
 * @param {object} args.mentorVerdict — вердикт наставника (runMentorVerdict)
 * @param {boolean} args.mentorWired — реальный заход (SE-R6-6)
 * @param {string|null} args.planHash — planId(замораживаемых steps) [plan-lock.mjs:15-17]
 * @param {Array} args.verifiedContextArtifact — артефакт проверенного контекста (§5.5)
 * @param {Function} args.hasUnresolvedExtractedImpl — (artifact)=>boolean; true = ЕСТЬ
 *   неразрешённая EXTRACTED = ГРЯЗНО = блок (зеркало artifactHasUnresolvedExtracted из A)
 * @returns {{pass: boolean, reason: string}} pass:true только когда ВСЕ условия выполнены.
 */
export function freezeGate({ mentorVerdict, mentorWired = false, planHash = null, verifiedContextArtifact = [], hasUnresolvedExtractedImpl } = {}) {
  // (0) binding нах.F4 — ДО substance: «вердикт есть» ≠ «вердикт ДЛЯ ЭТОГО плана».
  // F-C4 (sharp-edges, ужесточение A1): planHash ОБЯЗАТЕЛЕН — омиссия отключала бы
  // binding-проверку (configuration cliff); печать без binding запрещена (fail-CLOSED).
  if (planHash == null || !String(planHash).trim()) {
    return { pass: false, reason: 'planHash не передан — печать без binding вердикт↔план запрещена (F-C4, fail-CLOSED)' };
  }
  if (!mentorVerdict || mentorVerdict.plan_hash !== planHash) {
    return { pass: false, reason: 'вердикт не привязан к замораживаемому плану (stale/чужой) — нах.F4' };
  }
  // (а) substance VA-8/SE-R6-6.
  if (!isMentorVerdictSubstantive(mentorVerdict, { wired: mentorWired })) {
    const v = validateMentorVerdict(mentorVerdict);
    return { pass: false, reason: !mentorWired
      ? 'mentor-вердикт не из реального захода (wired:false) — не суд (SE-R6-6)'
      : `mentor-вердикт несодержателен: пустые слоты [${v.missingSlots.join(', ')}] (VA-8)` };
  }
  // (б) VA-9 (Д-2а/SE-A3): 0 EXTRACTED = «ничего не проверено» — не пускать.
  const list = Array.isArray(verifiedContextArtifact) ? verifiedContextArtifact : [];
  const extractedCount = list.filter((e) => e && e.kind === 'EXTRACTED').length;
  if (extractedCount === 0) {
    return { pass: false, reason: 'пустой артефакт контекста (0 EXTRACTED) — НЕ «проверен» (VA-9/SE-A3)' };
  }
  // FR-1 (финревью 2026-06-11, SE-A3-обязательство context-verity:93-94 — В ГЕЙТЕ):
  // (б2) VF-1 — INFERRED без derivation_ref не «проверен», печать с ним запрещена;
  // (б3) SE-A1 — неизвестный kind (третья полоса обхода: запись ни резолвится, ни
  // флагуется) печать не проходит. Чистые data-чеки — автономность C сохранена
  // (kinds — спека §5.5, зеркало CITATION_KINDS без импорта A).
  for (const e of list) {
    if (!e || typeof e !== 'object') {
      return { pass: false, reason: 'битая запись артефакта контекста — печать отказана (FR-1, fail-CLOSED)' };
    }
    if (e.kind === 'INFERRED' && !String(e.derivation_ref || '').trim()) {
      return { pass: false, reason: 'INFERRED без derivation_ref в артефакте — печать отказана (VF-1/FR-1)' };
    }
    if (e.kind !== 'EXTRACTED' && e.kind !== 'INFERRED') {
      return { pass: false, reason: `неизвестный kind ${JSON.stringify(e.kind)} в артефакте — печать отказана (SE-A1/FR-1)` };
    }
  }
  // (в) verity ✅O2: fail-CLOSED — сбой/отсутствие impl = считаем грязным.
  let dirty;
  try { dirty = typeof hasUnresolvedExtractedImpl === 'function' ? !!hasUnresolvedExtractedImpl(list) : true; }
  catch { dirty = true; }
  if (dirty) return { pass: false, reason: 'неразрешённая EXTRACTED-цитата в контексте — печать отказана (✅O2)' };
  return { pass: true, reason: 'binding ok + mentor-вердикт содержателен + VA-9 ok + verity чист' };
}
