b47a71c66b
Опечатанный ревью-план (GO наставника+судьи, judge_mode=live-block) + одно согласие владельца `FLOOR-ESCAPE: commit:<plan-hash>` → агент делает git add/commit/push без терминала владельца. Гейт ПРИСУТСТВИЯ (router-gate git-approval) отходит; гейты КАЧЕСТВА (criterion-gate/verify-gate) НЕ тронуты — код-коммит всё равно требует по-критерийный GREEN и свежую расписку. Согласия деплоя (ops-runbook:) и коммита (commit:) — раздельные кнопки. - escape-grant: обобщён plan-scoped загрузчик (loadPlanScopedGrants/ planScopedGrantOpen, окно = существование плана); D1 ops-runbook стал тонкой обёрткой; добавлены commit: COMMIT_GRANT_PREFIX/loadCommitGrants/commitGrantOpen. - commit-grant (новый мост план↔router-gate): commitGrantOpenForSession — открыт ли commit:<hash> на валидный sealed live-block план сессии. - shell-content-rules classifyGitCommand: conditional-git пускается при ctx.commitGrantOpen; GIT_HARD (force-push/--no-verify/-c) блокирует ПЕРВЫМ (качество/безопасность не ослаблены). - enforce-router-gate: main кладёт ctx.commitGrantOpen (gated через мост). План: docs/superpowers/plans/2026-06-18-agent-commit-channel-plan.md Спека: docs/superpowers/specs/2026-06-18-agent-commit-channel-design.md §3.1-3.2. ОТЛОЖЕНО (требует решения владельца, в хвосте плана): - §3.3 docs/ops без criterion/verify: .md уже пропускается; расширение на не-.md ops-артефакты конфликтует с CLAUDE.md §13 v2.40 — нужен явный список. - §3.4 десинк push-последним-шагом: рискованная правка снятия печати стены. +22 теста, свод 4319 passed / 2 skipped. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
37 lines
2.1 KiB
JavaScript
37 lines
2.1 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* commit-grant (D2, спека 2026-06-18-agent-commit-channel-design §3.1) — МОСТ план↔router-gate
|
|
* для «канала коммита под ревью». Вычисляет: открыт ли commit:<plan-hash> грант на ВАЛИДНЫЙ
|
|
* опечатанный ревью-план сессии (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;
|
|
}
|