From 96a6204b73bd73ddcef3e0af1d69c16a49ea53d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Tue, 16 Jun 2026 19:10:24 +0300 Subject: [PATCH] feat: round-memory owner-seal decideSeal carve-out via-labels SP3-b1 Co-Authored-By: Claude Opus 4.8 --- tools/seal-orchestration.mjs | 21 +++++++++++++++------ tools/seal-orchestration.test.mjs | 16 ++++++++++++---- 2 files changed, 27 insertions(+), 10 deletions(-) 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' }); }); });