Files
portal/tools/seal-to-wall.integration.test.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

41 lines
2.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, it, expect } from 'vitest';
import { sealArtifact, sealPlan, judgedHashOf, sealableArtifact, sealablePlan } from './seal-orchestration.mjs';
import { decideMode } from './enforce-supreme-gate.mjs';
// Интеграция: печать (seal-orchestration) → стена М2 (enforce-supreme-gate.decideMode).
// Проверяет, что формы, которые ставит печать, стена принимает: совпавшую правку пускает,
// несовпавшую блокирует, шаг с неразрешимым ref блокирует (закрытая дверь C-5).
const KEY = 'k';
const specMd = '## Реш {#dec-a}\nтекст решения';
const planMd = '```steps-json\n[{"op":"Edit","object":"app/Foo.php","ref":"dec-a"}]\n```';
const planMdBadRef = '```steps-json\n[{"op":"Edit","object":"app/Foo.php","ref":"dec-zzz"}]\n```';
const go = (obj) => ({ wired: true, decision: 'GO', judged_hash: judgedHashOf(obj), verdict_id: 'v' });
describe('seal → supreme-gate integration', () => {
const aObj = sealableArtifact(specMd);
const aRes = sealArtifact({ md: specMd, verdict: go(aObj), key: KEY, judgeMode: 'live-block' });
it('артефакт опечатан (печать №1)', () => {
expect(aRes.sealed).toBe(true);
expect(aRes.seal.sections['dec-a']).toBeTruthy();
});
it('совпавшая правка → allow; несовпавшая → block', () => {
const pObj = sealablePlan(planMd);
const pRes = sealPlan({ md: planMd, currentArtifact: aRes.seal, verdict: go(pObj), key: KEY, judgeMode: 'live-block' });
expect(pRes.sealed).toBe(true);
expect(pRes.seal.artifact_id).toBe(aRes.seal.artifact_id); // версии связаны
const base = { frozenPlan: pRes.seal, frozenArtifact: aRes.seal, key: KEY, stepPtr: 0 };
expect(decideMode({ ...base, toolUse: { name: 'Edit', input: { file_path: 'app/Foo.php' } } }).decision).toBe('allow');
expect(decideMode({ ...base, toolUse: { name: 'Edit', input: { file_path: 'app/Other.php' } } }).decision).toBe('block');
});
it('закрытая дверь: ref шага не резолвится в артефакте → block даже на совпавшем действии', () => {
const pObj = sealablePlan(planMdBadRef);
const pRes = sealPlan({ md: planMdBadRef, currentArtifact: aRes.seal, verdict: go(pObj), key: KEY, judgeMode: 'live-block' });
expect(pRes.sealed).toBe(true);
const base = { frozenPlan: pRes.seal, frozenArtifact: aRes.seal, key: KEY, stepPtr: 0 };
expect(decideMode({ ...base, toolUse: { name: 'Edit', input: { file_path: 'app/Foo.php' } } }).decision).toBe('block');
});
});