diff --git a/tools/seal-orchestration.mjs b/tools/seal-orchestration.mjs index 41bd3ad..b03fd1a 100644 --- a/tools/seal-orchestration.mjs +++ b/tools/seal-orchestration.mjs @@ -54,11 +54,20 @@ export function ownerSealAction(hash) { return `owner-seal:${String(hash == null ? '' : hash)}`; } -/** Решение печати с учётом owner-seal. Реальный GO (wired && GO) → печать обычным путём. - * Иначе ownerSealOpen (владелец подписал owner-seal на этот hash) → печать несмотря на - * NO-GO/degraded. Ни того, ни другого → нет печати. */ -export function decideSeal({ verdict, ownerSealOpen = false } = {}) { - if (isRealGo(verdict)) return { seal: true, via: 'wired-go' }; - if (ownerSealOpen) return { seal: true, via: 'owner-seal' }; +/** Решение печати с учётом owner-seal и carve-out §6 (деньги/тупик). + * - Чистый GO без чувствительной темы → печать обычным путём (wired-go). + * - Владелец подписал owner-seal:<хеш тела> → печать. Повод различаем для аудита: + * carveout — судья дал GO, но тема денежная/тупик (§6), владелец подтвердил; + * override — судья NO-GO/не дозвонился, владелец перевесил. + * - GO + чувствительная тема, но владелец ещё не подписал → штатно зовём владельца + * (ownerRequired, carve-out §6) — это ожидаемый путь, не спор. + * - Ни GO, ни owner-seal → нет печати. + * ownerSealRequired = requiresOwnerSeal({artifact,verdict}) — единственный детектор «тема + * чувствительная» (не дублируется здесь). Интеграция owner-seal ↔ requiresOwnerSeal: один + * механизм подписи owner-seal закрывает И carve-out, И override. */ +export function decideSeal({ verdict, ownerSealOpen = false, ownerSealRequired = false } = {}) { + if (isRealGo(verdict) && !ownerSealRequired) return { seal: true, via: 'wired-go' }; + if (ownerSealOpen) return { seal: true, via: isRealGo(verdict) ? 'owner-seal-carveout' : 'owner-seal-override' }; + if (isRealGo(verdict) && ownerSealRequired) return { seal: false, via: null, ownerRequired: true }; return { seal: false, via: null }; } diff --git a/tools/seal-orchestration.test.mjs b/tools/seal-orchestration.test.mjs index 1585260..96cff04 100644 --- a/tools/seal-orchestration.test.mjs +++ b/tools/seal-orchestration.test.mjs @@ -52,13 +52,21 @@ describe('owner-seal (SP3)', () => { 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 (перевешивает)', () => { - expect(decideSeal({ verdict: { wired: true, decision: 'NO-GO' }, ownerSealOpen: true })).toEqual({ seal: true, via: 'owner-seal' }); + 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', () => { - expect(decideSeal({ verdict: { wired: false, decision: 'GO' }, ownerSealOpen: true })).toEqual({ seal: true, via: 'owner-seal' }); + 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' }); }); });