import { describe, it, expect } from 'vitest'; import { sealableArtifact, sealablePlan, judgedHashOf, sealArtifact, sealPlan, ownerSealAction, decideSeal, ownerSealActionForContent } from './seal-orchestration.mjs'; import { contentHash } from './judge-seal-channel.mjs'; import { planId } from './plan-lock.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); }); // D1: тип плана прокидывается из markdown в печать. const deployMd = '# План\n**Kind:** deploy\n```steps-json\n[{"op":"Bash","object":"composer install","ref":"dec-a"}]\n```'; it('sealablePlan несёт kind: deploy-план → "deploy", обычный → "normal"', () => { expect(sealablePlan(deployMd).kind).toBe('deploy'); expect(sealablePlan(planMd).kind).toBe('normal'); }); it('sealPlan печатает kind:"deploy" в опечатанный план', () => { const planObj = sealablePlan(deployMd); const r = sealPlan({ md: deployMd, currentArtifact: { artifact_id: 'AID' }, verdict: goVerdict(planObj), key: KEY, judgeMode: 'live-block' }); expect(r.sealed).toBe(true); expect(r.seal.kind).toBe('deploy'); }); }); describe('owner-seal (SP3)', () => { it('ownerSealAction → каноническая метка owner-seal:', () => { expect(ownerSealAction('abc123')).toBe('owner-seal:abc123'); }); it('ownerSealAction нулевой hash → owner-seal:', () => { expect(ownerSealAction(null)).toBe('owner-seal:'); }); it('decideSeal: реальный GO → печать via wired-go', () => { expect(decideSeal({ verdict: { wired: true, decision: 'GO' } })).toEqual({ seal: true, via: 'wired-go' }); }); it('decideSeal: NO-GO + ownerSealOpen → печать via owner-seal-override (перевешивает)', () => { expect(decideSeal({ verdict: { wired: true, decision: 'NO-GO' }, ownerSealOpen: true })).toEqual({ seal: true, via: 'owner-seal-override' }); }); it('decideSeal: NO-GO без owner-seal → нет печати', () => { expect(decideSeal({ verdict: { wired: true, decision: 'NO-GO' }, ownerSealOpen: false })).toEqual({ seal: false, via: null }); }); it('decideSeal: degraded (wired:false) + ownerSealOpen → печать via owner-seal-override', () => { expect(decideSeal({ verdict: { wired: false, decision: 'GO' }, ownerSealOpen: true })).toEqual({ seal: true, via: 'owner-seal-override' }); }); it('decideSeal: GO + ownerSealRequired (деньги/тупик) без owner-seal → ownerRequired, нет печати (carve-out §6)', () => { expect(decideSeal({ verdict: { wired: true, decision: 'GO' }, ownerSealRequired: true })) .toEqual({ seal: false, via: null, ownerRequired: true }); }); it('decideSeal: GO + ownerSealRequired + ownerSealOpen → печать via owner-seal-carveout', () => { expect(decideSeal({ verdict: { wired: true, decision: 'GO' }, ownerSealRequired: true, ownerSealOpen: true })) .toEqual({ seal: true, via: 'owner-seal-carveout' }); }); }); describe('owner-seal в seal-функциях (SP3-b3)', () => { const moneyMd = '## Реш {#dec-a}\nсписание 100 ₽ с баланса'; it('sealArtifact: NO-GO + ownerSealOpen → печать (override), owner_sealed', () => { const r = sealArtifact({ md: specMd, verdict: { wired: true, decision: 'NO-GO' }, key: KEY, judgeMode: 'live-block', ownerSealOpen: true }); expect(r.sealed).toBe(true); expect(r.seal.owner_sealed).toBe(true); }); it('sealArtifact: GO + деньги без owner-seal → ownerRequired (carve-out §6)', () => { const a = sealableArtifact(moneyMd); const r = sealArtifact({ md: moneyMd, verdict: goVerdict(a), key: KEY, judgeMode: 'live-block' }); expect(r.sealed).toBe(false); expect(r.ownerRequired).toBe(true); }); it('sealArtifact: GO + деньги + ownerSealOpen → печать (carveout перевешивает)', () => { const a = sealableArtifact(moneyMd); const r = sealArtifact({ md: moneyMd, verdict: goVerdict(a), key: KEY, judgeMode: 'live-block', ownerSealOpen: true }); expect(r.sealed).toBe(true); expect(r.seal.owner_sealed).toBe(true); }); it('sealPlan: NO-GO + ownerSealOpen + currentArtifact → печать (override)', () => { const r = sealPlan({ md: planMd, currentArtifact: { artifact_id: 'AID' }, verdict: { wired: true, decision: 'NO-GO' }, key: KEY, judgeMode: 'live-block', ownerSealOpen: true }); expect(r.sealed).toBe(true); expect(r.seal.artifact_id).toBe('AID'); }); it('sealPlan: GO + deadlock без owner-seal → ownerRequired (новый carve-out плана, §0)', () => { const planObj = sealablePlan(planMd); const r = sealPlan({ md: planMd, currentArtifact: { artifact_id: 'AID' }, verdict: { ...goVerdict(planObj), deadlock: true }, key: KEY, judgeMode: 'live-block' }); expect(r.sealed).toBe(false); expect(r.ownerRequired).toBe(true); }); }); describe('ownerSealActionForContent (SP3-c)', () => { it('план (есть steps-json) → owner-seal:planId(steps) (как в sealTurnProd)', () => { expect(ownerSealActionForContent(planMd)).toBe(ownerSealAction(planId(sealablePlan(planMd).steps))); }); it('спека (нет steps) → owner-seal:judgedHashOf(sealableArtifact) (как в sealTurnProd)', () => { expect(ownerSealActionForContent(specMd)).toBe(ownerSealAction(judgedHashOf(sealableArtifact(specMd)))); }); }); describe('sealablePlan delivery', () => { it('несёт delivery из тела плана', () => { const md = '## Цель\nx\n**Delivery:** user-result\n```steps-json\n[{"op":"Write","object":"a.mjs","ref":"x"}]\n```'; expect(sealablePlan(md).delivery).toBe('user-result'); }); it('без пометки → internal', () => { const md = '```steps-json\n[{"op":"Write","object":"a.mjs","ref":"x"}]\n```'; expect(sealablePlan(md).delivery).toBe('internal'); }); });