Files
portal/tools/seal-orchestration.mjs
T
Дмитрий 09598dd5bd feat(seal): sealed-plan production pipeline (M7 Фаза 8 code-precondition)
Производство двух печатей (артефакт-решение + план-шаги), чтобы стене М2 было
что матчить — код-предусловие флипа. Inline TDD, спека/план одобрены владельцем.

- C1 artifact-from-spec.mjs: спека markdown -> {sections, source_sha} по якорям {#id} (P2-2).
- C2 plan-steps-parse.mjs: план -> [{op,object,ref}], fail-CLOSE, reject op:Task (VA-4),
  канон object = repo-relative POSIX (SE-5; pathNormalize только на матче в стене, не на парсе).
- C3/C4 plan-lock.mjs: judge_mode в ПОДПИСАННОЙ базе freezePlan (VA-2) + атомарный persist
  temp->rename для обоих save (SE-4/VA-3, артефакт ДО плана).
- C6 seal-orchestration.mjs: sealableArtifact/sealablePlan + judgedHashOf (SD-1) +
  sealArtifact/sealPlan на РЕАЛЬНОМ GO (SE-3 wired===true), штамп artifact_id из текущего
  артефакта (SD-3), judge_mode впрыснут в печать ПОСЛЕ хеш-сверки sealOnApproval (фикс TOCTOU).
- C5 enforce-judge-gate.mjs: SPEC_PATH_RE + sealOnWiredGo (печать на wired GO, инъекция в main,
  юнит-тесты hermetic) + judged_hash в вердикте runJudgeGate. extractGate2Product не тронут
  (Гейт-2 = планы; Гейт-1 spec-judging — отдельный заход перед флипом).
- Интеграция seal-to-wall: печать -> decideMode стены М2 (allow / non-match block / closed-door).

Тесты: full tools-only регрессия 3427 passed | 2 skipped, 0 регрессий (+29 новых кейсов).
Печать в рантайме НЕ производится до флипа (стена/судья не зарегистрированы) — сборка
готовит код-предусловие. Спека docs/superpowers/specs/2026-06-09-sealed-plan-production-design.md.
2026-06-09 17:50:25 +03:00

45 lines
3.4 KiB
JavaScript

#!/usr/bin/env node
/**
* seal-orchestration (C3-C6, §5) — собрать sealable-объект, сверить judged_hash (SD-1),
* поставить печать ТОЛЬКО на реальном GO (SE-3), со штампом artifact_id (SD-3) и judge_mode.
* Печать-движок инъектируется (в проде freezeArtifact/freezePlan Машины 2).
*
* NB (фикс при сборке, systematic-debugging): judge_mode НЕ входит в судимое представление —
* judged_hash считается над ЧИСТЫМ sealable-объектом (sealableArtifact/sealablePlan). Поэтому
* для артефакта judge_mode добавляется в ПЕЧАТЬ через обёртку freezeImpl ПОСЛЕ хеш-сверки
* sealOnApproval (иначе её внутренний contentHash({...artifact,judge_mode}) разошёлся бы с
* judged_hash чистого объекта → TOCTOU-false → артефакт никогда не печатался бы).
*/
import { buildArtifact } from './artifact-from-spec.mjs';
import { parsePlanSteps } from './plan-steps-parse.mjs';
import { contentHash, sealOnApproval } from './judge-seal-channel.mjs';
import { freezeArtifact, freezePlan } from './plan-lock.mjs';
export function sealableArtifact(md) { return buildArtifact(md); } // {sections, source_sha}
export function sealablePlan(md) { return { steps: parsePlanSteps(md) }; } // {steps:[{op,object,ref}]}
export function judgedHashOf(obj) { return contentHash(obj); }
function isRealGo(v) { return !!(v && v.wired === true && v.decision === 'GO'); }
/** Печать артефакта на реальном GO (carve-out деньги/тупик — через sealOnApproval). */
export function sealArtifact({ md, verdict, key, judgeMode, nowMs, freezeImpl = freezeArtifact }) {
if (!isRealGo(verdict)) return { sealed: false, reason: 'нет реального GO (SE-3)' };
const artifact = sealableArtifact(md);
if (verdict.judged_hash !== judgedHashOf(artifact)) return { sealed: false, reason: 'judged_hash mismatch (SD-1/TOCTOU)' };
// judge_mode впрыскивается в печать ПОСЛЕ хеш-сверки sealOnApproval (над чистым artifact).
const freezeWithMode = ({ artifact: a, key: k, nowMs: n }) =>
freezeImpl({ artifact: { ...a, judge_mode: judgeMode }, key: k, nowMs: n });
return sealOnApproval({ artifact, verdict, key, nowMs, freezeImpl: freezeWithMode });
}
/** Печать плана: штамп artifact_id текущего артефакта (SD-3); нет артефакта → fail-CLOSE (VA-1). */
export function sealPlan({ md, currentArtifact, verdict, key, judgeMode, nowMs, freezeImpl = freezePlan }) {
if (!isRealGo(verdict)) return { sealed: false, reason: 'нет реального GO (SE-3)' };
if (!currentArtifact || !currentArtifact.artifact_id) return { sealed: false, reason: 'нет текущего артефакта (VA-1/SD-3)' };
let planObj;
try { planObj = sealablePlan(md); } catch (e) { return { sealed: false, reason: `парс плана fail-CLOSE: ${e.message}` }; }
if (verdict.judged_hash !== judgedHashOf(planObj)) return { sealed: false, reason: 'judged_hash mismatch (SD-1/TOCTOU)' };
const seal = freezeImpl({ steps: planObj.steps, artifactId: currentArtifact.artifact_id, judgeMode, key, nowMs });
return { sealed: true, seal };
}