09598dd5bd
Производство двух печатей (артефакт-решение + план-шаги), чтобы стене М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.
44 lines
2.2 KiB
JavaScript
44 lines
2.2 KiB
JavaScript
import { describe, it, expect } from 'vitest';
|
|
import { sealableArtifact, sealablePlan, judgedHashOf, sealArtifact, sealPlan } from './seal-orchestration.mjs';
|
|
import { contentHash } from './judge-seal-channel.mjs';
|
|
|
|
const specMd = '## Реш {#dec-a}\nтекст';
|
|
const planMd = '```steps-json\n[{"op":"Edit","object":"app/Foo.php","ref":"dec-a"}]\n```';
|
|
const KEY = 'k';
|
|
const goVerdict = (obj) => ({ wired: true, decision: 'GO', judged_hash: judgedHashOf(obj), verdict_id: 'v1' });
|
|
|
|
describe('seal-orchestration', () => {
|
|
it('judgedHashOf == contentHash of same sealable object (SD-1)', () => {
|
|
const a = sealableArtifact(specMd);
|
|
expect(judgedHashOf(a)).toBe(contentHash(a));
|
|
});
|
|
it('sealArtifact on real GO → sealed, verifiable', () => {
|
|
const a = sealableArtifact(specMd);
|
|
const r = sealArtifact({ md: specMd, verdict: goVerdict(a), key: KEY, judgeMode: 'live-block' });
|
|
expect(r.sealed).toBe(true);
|
|
expect(r.seal.sections['dec-a']).toBeTruthy();
|
|
expect(r.seal.judge_mode).toBe('live-block');
|
|
});
|
|
it('sealArtifact NOT sealed on degraded GO (wired:false, SE-3)', () => {
|
|
const a = sealableArtifact(specMd);
|
|
const r = sealArtifact({ md: specMd, verdict: { wired: false, decision: 'GO' }, key: KEY, judgeMode: 'shadow' });
|
|
expect(r.sealed).toBe(false);
|
|
});
|
|
it('sealArtifact NOT sealed on judged_hash mismatch (SD-1/TOCTOU)', () => {
|
|
const r = sealArtifact({ md: specMd, verdict: { wired: true, decision: 'GO', judged_hash: 'WRONG' }, key: KEY, judgeMode: 'live-block' });
|
|
expect(r.sealed).toBe(false);
|
|
});
|
|
it('sealPlan stamps artifact_id from current artifact (SD-3)', () => {
|
|
const planObj = sealablePlan(planMd);
|
|
const r = sealPlan({ md: planMd, currentArtifact: { artifact_id: 'AID' }, verdict: goVerdict(planObj), key: KEY, judgeMode: 'live-block' });
|
|
expect(r.sealed).toBe(true);
|
|
expect(r.seal.artifact_id).toBe('AID');
|
|
expect(r.seal.judge_mode).toBe('live-block');
|
|
});
|
|
it('sealPlan fail-CLOSE without current artifact (VA-1/SD-3)', () => {
|
|
const planObj = sealablePlan(planMd);
|
|
const r = sealPlan({ md: planMd, currentArtifact: null, verdict: goVerdict(planObj), key: KEY, judgeMode: 'live-block' });
|
|
expect(r.sealed).toBe(false);
|
|
});
|
|
});
|