#!/usr/bin/env node /** * commit-grant (D2, спека 2026-06-18-agent-commit-channel-design §3.1) — МОСТ план↔router-gate * для «канала коммита под ревью». Вычисляет: открыт ли commit: грант на ВАЛИДНЫЙ * опечатанный ревью-план сессии (sealed + judge_mode='live-block'). Если да — router-gate отпускает * гейт ПРИСУТСТВИЯ (git-approval); гейты КАЧЕСТВА (criterion-gate/verify-gate) остаются нетронуты. * * Брат blessed-ops (D1): тот же приём — мост знает оба слоя, зовётся лишь когда владелец дал согласие. */ import { homedir } from 'node:os'; import { verifyFrozenPlan, loadFrozenPlan } from './plan-lock.mjs'; import { commitGrantOpen, loadCommitGrants } from './escape-grant.mjs'; import { resolveReceiptKey } from './receipt-key-config.mjs'; /** * Открыт ли commit-канал для сессии. Грузит commit-гранты ПЕРВЫМ; нет грантов → false БЕЗ загрузки * плана/ключа. Есть грант → план обязан быть опечатан (verify), judge_mode='live-block' (GO к * энфорсменту) и грант — именно на его plan_id. Всё инъектируемо для теста. */ export function commitGrantOpenForSession(sessionId, { loadGrantsImpl = loadCommitGrants, loadPlanImpl = loadFrozenPlan, keyImpl = resolveReceiptKey, verifyImpl = verifyFrozenPlan, runtimeDir = `${homedir()}/.claude/runtime`, } = {}) { const grants = loadGrantsImpl(sessionId); if (!Array.isArray(grants) || grants.length === 0) return false; const plan = loadPlanImpl({ sessionId, runtimeDir }); if (!plan || !plan.plan_id) return false; if (!commitGrantOpen(plan.plan_id, grants)) return false; if (plan.judge_mode !== 'live-block') return false; // одобрение к энфорсменту const key = keyImpl(); if (!verifyImpl(plan, key)) return false; // печать цела return true; }