Files
portal/tools/artifact-from-spec.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

36 lines
1.3 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 { 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();
});
});