From 623fbc2faac0d362d8fb1e812667e88ad3760e6d Mon Sep 17 00:00:00 2001 From: vitaliytv Date: Sun, 9 Aug 2026 17:32:51 +0300 Subject: [PATCH] =?UTF-8?q?docs(mandates):=20=D0=BA=D0=BE=D0=BD=D1=82?= =?UTF-8?q?=D1=80=D0=B0=D0=BA=D1=82=20M6=20=E2=80=94=20=D0=BA=D0=BE=D1=80?= =?UTF-8?q?=D0=B5=D0=BD=D0=B5=D0=B2=D0=B8=D0=B9=20=D0=BC=D0=B0=D0=BD=D0=B4?= =?UTF-8?q?=D0=B0=D1=82=20=D1=80=D1=96=D0=B2=D0=BD=D0=BE=20=D0=BE=D0=B4?= =?UTF-8?q?=D0=B8=D0=BD,=20=D0=BF=D0=BE=D0=B4=D0=B2=D1=96=D0=B9=D0=BD?= =?UTF-8?q?=D0=B8=D0=B9=20=D0=BF=D1=96=D0=B4=D0=BF=D0=B8=D1=81=20=D0=B7?= =?UTF-8?q?=D0=BC=D1=96=D0=BD=D0=B8=20escalates=5Fto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- .changes/260809-1731.md | 5 +++++ .cspell.json | 1 + docs/architecture/mandates.md | 6 +++--- 3 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 .changes/260809-1731.md diff --git a/.changes/260809-1731.md b/.changes/260809-1731.md new file mode 100644 index 0000000..22b28e2 --- /dev/null +++ b/.changes/260809-1731.md @@ -0,0 +1,5 @@ +--- +bump: patch +section: Fixed +--- +docs(mandates): контракт M6 — кореневий мандат рівно один, подвійний підпис зміни escalates_to diff --git a/.cspell.json b/.cspell.json index b6030ca..5243ce7 100644 --- a/.cspell.json +++ b/.cspell.json @@ -187,6 +187,7 @@ "дедуплікується", "декомпозовано", "декомпозує", + "делегатор", "делегатора", "делегатором", "деплой", diff --git a/docs/architecture/mandates.md b/docs/architecture/mandates.md index cdfb7ec..e45d4ef 100644 --- a/docs/architecture/mandates.md +++ b/docs/architecture/mandates.md @@ -239,9 +239,9 @@ ephemeral_mandate: | `mandates[].thresholds.risk` | enum `low` \| `medium` \| `high` | ні | відсутнє = без обмеження за рівнем ризику | той самий режим, що `budget_eur` | | `mandates[].thresholds.irreversible` | bool | ні | `false` — за замовчуванням мандат **не** покриває незворотні рішення (консервативний дефолт, узгоджений з інваріантом reversible нижче) | розширення до `true` — підписаний акт делегування; звуження до `false` — сам власник | | `mandates[].thresholds.audacity` | enum `low` \| `medium` \| `high`; **лише для `kind: model`** — для `kind: person` поле неприпустиме (validate-помилка, якщо присутнє) | ні (для `kind: model`) | `low` | розширення — **лише людина**, завжди («остання константа», [vision.md](../vision.md) «Дельта») | -| `mandates[].escalates_to` | handle власника-адресата ескалації, або `null` лише для рівно одного кореневого мандата | так | — | зміна маршруту ескалації — підписаний акт **нового** `escalates_to`-власника (він приймає на себе роль ескалаційної точки; це своя згода, не чужий підпис) | +| `mandates[].escalates_to` | handle власника-адресата ескалації, або `null` лише для рівно одного кореневого мандата | так | — | зміна маршруту ескалації — **подвійний підпис**: (а) новий `escalates_to`-адресат приймає роль (згода прийняти на себе точку ескалації); (б) делегатор рівня вище — поточний (замінюваний) `escalates_to`-власник — підтверджує саму зміну маршруту, бо інакше власник міг би односторонньо перевести нагляд на «зручнішого» наглядача, залишивши того, хто організаційно відповідає за нагляд, без відома (директорська модель, [vision.md](../vision.md) «Дельта») | -Валідатор (частина `validate_mandate_change` нижче) додатково відхиляє: більше нуля мандатів з `escalates_to: null` або дерево, де від будь-якого `owner` неможливо дійти до кореня скінченним ланцюгом `escalates_to` (цикл або висячий handle). +Валідатор (частина `validate_mandate_change` нижче) додатково відхиляє: кількість мандатів з `escalates_to: null` ≠ 1 (нуль — немає кореня, більше одного — неоднозначний корінь), а також дерево, де від будь-якого `owner` неможливо дійти до кореня скінченним ланцюгом `escalates_to` (цикл або висячий handle). ### Формат квіз-файлів @@ -303,7 +303,7 @@ Crate живе в `mt-rust` (той самий патерн, що `crates/mt-nap - **`parse_mandates(path) → MandatesFile`** — парсить `.mt/mandates.yaml`, повертає `{ generation, mandates[] }` або помилку валідації (порожній scope, більше/менше одного кореневого мандата, цикл ескалації, `audacity` на `kind: person`). - **`effective_owner(node_ref, decision_type, facets) → { owner, kind, escalation_chain[] }`** — lookup за `refs`×`decision_types`×`thresholds` (крок 1 маршрутизатора вище); повертає обчисленого власника і повний ланцюг `escalates_to` до кореня для автопідйому по SLA. -- **`validate_mandate_change(diff, signature) → Verdict`** — `Verdict = Valid | Invalid(reason)`. Перевіряє: `generation` файлу зріс рівно на 1; будь-яке розширення `scope`/`thresholds` підписане делегатором рівня вище (окрім звуження — самопідпис); розширення `kind: model` мандата (включно з підняттям `audacity`) підписане **лише** людським ключем — модельний підпис на такому дифі відхиляється безумовно («остання константа»). +- **`validate_mandate_change(diff, signature) → Verdict`** — `Verdict = Valid | Invalid(reason)`. Перевіряє: `generation` файлу зріс рівно на 1; будь-яке розширення `scope`/`thresholds` підписане делегатором рівня вище (окрім звуження — самопідпис); зміна `escalates_to` вимагає **обох** підписів — нового адресата й поточного (замінюваного) делегатора; розширення `kind: model` мандата (включно з підняттям `audacity`) підписане **лише** людським ключем — модельний підпис на такому дифі відхиляється безумовно («остання константа»); кількість кореневих мандатів (`escalates_to: null`) — рівно 1. - **`validate_approval(response, quiz) → Verdict`** — перевіряє наявність і завершеність `quiz_ref` для `decision-request`-гейту, відповідність `quiz.decision_ref` вузлу з `response`, `quiz.depth` — мапінгу з `leverage_facets`, і сам Ed25519-підпис проти pubkey-кешу (той самий шлях, що інші два гейти — [access.md](access.md)). ## Місце в roadmap