b111ca5ec1
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
105 lines
5.9 KiB
JavaScript
105 lines
5.9 KiB
JavaScript
import { describe, it, expect } from 'vitest';
|
|
import { sealableArtifact, sealablePlan, judgedHashOf, sealArtifact, sealPlan, ownerSealAction, decideSeal } 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);
|
|
});
|
|
});
|
|
|
|
describe('owner-seal (SP3)', () => {
|
|
it('ownerSealAction → каноническая метка owner-seal:<hash>', () => {
|
|
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);
|
|
});
|
|
});
|