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.
36 lines
1.3 KiB
JavaScript
36 lines
1.3 KiB
JavaScript
import { describe, it, expect } from 'vitest';
|
||
import { parseAnchoredSections, buildArtifact } from './artifact-from-spec.mjs';
|
||
|
||
describe('artifact-from-spec', () => {
|
||
const md = [
|
||
'# Spec',
|
||
'## Решение А {#dec-a}',
|
||
'Текст А.',
|
||
'## Решение Б {#dec-b}',
|
||
'Текст Б.',
|
||
].join('\n');
|
||
|
||
for (const [name, fn] of [['parses anchored sections', () => {
|
||
const { sections } = parseAnchoredSections(md);
|
||
expect(Object.keys(sections).sort()).toEqual(['dec-a', 'dec-b']);
|
||
expect(sections['dec-a']).toContain('Текст А.');
|
||
}]]) it(name, fn);
|
||
|
||
it('heading WITHOUT anchor is not a section key', () => {
|
||
const { sections } = parseAnchoredSections('## Без якоря\nx');
|
||
expect(Object.keys(sections)).toEqual([]);
|
||
});
|
||
|
||
it('empty-content section is dropped (fail-CLOSE)', () => {
|
||
const { sections } = parseAnchoredSections('## Пусто {#e}\n\n## Полно {#f}\ny');
|
||
expect(Object.keys(sections)).toEqual(['f']);
|
||
});
|
||
|
||
it('buildArtifact returns {sections, source_sha} deterministically', () => {
|
||
const a1 = buildArtifact(md); const a2 = buildArtifact(md);
|
||
expect(a1.source_sha).toEqual(a2.source_sha);
|
||
expect(typeof a1.source_sha).toBe('string');
|
||
expect(a1.sections['dec-a']).toBeTruthy();
|
||
});
|
||
});
|