Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/260809-1731.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
bump: patch
section: Fixed
---
docs(mandates): контракт M6 — кореневий мандат рівно один, подвійний підпис зміни escalates_to
1 change: 1 addition & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@
"дедуплікується",
"декомпозовано",
"декомпозує",
"делегатор",
"делегатора",
"делегатором",
"деплой",
Expand Down
6 changes: 3 additions & 3 deletions docs/architecture/mandates.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

### Формат квіз-файлів

Expand Down Expand Up @@ -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
Expand Down
Loading