montana/Монтана-Протокол/Код/ROADMAP.md

1264 lines
106 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Roadmap — Montana Reference Implementation
**Spec target:** см. `VERSION.md` (single source of truth)
**Implementation version:** 0.0.0 (pre-release, в разработке)
---
## Scope
**В scope:** protocol core согласно спеке Montana (версия — `VERSION.md`) — криптография, TimeChain, NodeChain, AccountChain, Account Table, consensus (Proof of Time), P2P transport, Fast Sync, node binary.
**Вне scope (отдельный workspace в будущем):** Application Layer по спеке `Montana App v2.4.2.md` — Juno agent, messaging, файловое хранилище, профили, клиентская LLM runtime.
---
## Принципы разработки
1. **Одна функция за раз.** Explain → Write + tests → Test → Commit. Никаких «напишу весь модуль сразу».
2. **Success criteria блок до кода.** Для каждой consensus-critical функции — spec quote + контракт + чек-лист критериев (см. `CLAUDE.md` → Verifiable success criteria).
3. **Автокоммит.** Любое изменение в `Протокол/Код/` автоматически завершается `git commit`. См. `CLAUDE.md` → Git discipline.
4. **Четыре обязательные команды зелёные перед каждым коммитом:**
- `cargo fmt --all -- --check`
- `cargo clippy --all-targets -- -D warnings`
- `cargo test --all`
- `cargo build --all --release`
5. **Byte-for-byte детерминизм.** Custom canonical encoding, explicit little-endian, BTreeMap вместо HashMap в consensus, no floats, no system clock. См. `CLAUDE.md` → Byte-for-byte determinism.
6. **Ссылки на спеку в коде.** Каждое consensus-critical решение помечается комментарием `// spec, раздел "<название>"` **без версии**. Источник истины версии — `VERSION.md`. При spec bump `VERSION.md` обновляется, spec-комментарии в коде не трогаются, если раздел не переименован.
7. **Demo откладываются до готовности протокола.** До M4 (consensus core) отдельные `examples/demo.rs` в пакетах не пишем — юнит-тестов достаточно для sanity check. После M4 создаём `examples/` в корне workspace с end-to-end сценариями (OpenAccount → Transfer → BundledConfirmation → apply_proposal), когда есть реальный flow между пакетами.
---
## Статусы crates
- **TODO** — не начато, ожидает своей очереди
- **In progress** — начата работа, не завершено
- **Written** — код написан, тесты есть, ожидает review
- **Committed** — зафиксировано в git, закрыто
- **Blocked** — заблокирован gap в спеке, внешней зависимостью или другим crate
---
## 15 Crates — статус и зависимости
| # | Crate | Milestone | Зависит от | Раздел спеки | Статус |
|---|-------|-----------|------------|--------------|--------|
| 1 | `mt-codec` | M1 | — | Consensus encoding layer | ✅ Committed (90464a8) |
| 2 | `mt-crypto` | M1 | sha2, pqcrypto-falcon | Криптография + Primitive layer | ✅ Committed (df55372) |
| 3 | `mt-merkle` | M1 | mt-codec, mt-crypto | Sparse Merkle Tree | ✅ Committed (f242956) |
| 4 | `mt-genesis` | M1 | mt-codec, mt-crypto | Genesis Decree + protocol params | ✅ Committed (45c1e84) |
| 5 | `mt-state` | M2 | mt-codec, mt-crypto, mt-merkle | Состояние сети (Account/Node/Candidate Tables + roots) | ✅ Committed (ab99e23) |
| 6 | `mt-timechain` | M2 | mt-codec, mt-crypto, mt-genesis, mt-state | TimeChain VDF + Adaptive D + cemented_bundle_aggregate | ✅ Committed (76ed8da) |
| 7 | `mt-account` | M3 | mt-codec, mt-crypto, mt-state, mt-genesis, mt-timechain | AccountChain operations + apply_proposal | ✅ Committed (Phase A..F closed, 102 tests) |
| 8 | `mt-lottery` | M4 | mt-crypto, mt-state | Node/Account lottery + BundledConfirmation | ✅ Closed (6 phases, 96 тестов) |
| 9 | `mt-consensus` | M4 | mt-state, mt-lottery, mt-timechain | Proposal + Lookback Leadership + fallback | ✅ Closed (5 phases, 60 тестов) |
| 10 | `mt-entry` | M4 | mt-state, mt-timechain | NodeRegistration + selection event + adaptive VDF | ✅ Closed (5 phases, 39 тестов) |
| 11 | `mt-store` | M5 | mt-state, mt-consensus | Filesystem persistence | ✅ Closed (5 phases, 24 теста) |
| 12 | `mt-net` + `mt-net-transport` | M6 | mt-consensus, libp2p | libp2p + IBT + Wire format + Dandelion++ + Mesh + S&F | ✅ Closed (Phase A-G + C.0-C.4, 124 tests, in-process e2e; cross-machine pairing defer M8) |
| 13 | `mt-sync` | M7 | mt-state, mt-net | Fast Sync | ⏳ TODO |
| 14 | `montana-node` | M8 | все | Бинарь + CLI | ✅ Closed (byte-exact rewrite через canonical apply_proposal, commit fb204ef; DEV-001..DEV-009 closed; DEV-010 acknowledged genesis bootstrap) |
| 15 | `mt-conformance` | M9 | все | Test vectors + conformance suite | ✅ READY initial (envelope A1-A3 + IBT B1 + PoW F1/F2); expansion в работе (12 TBD-A markers) |
---
## Milestones
### M0 — Workspace skeleton ✅
- [x] Cargo workspace, toolchain pin, rustfmt/clippy configs
- [x] VERSION.md, README.md, ROADMAP.md
- [x] Git repository в `Протокол/Код/`, parent gitignore настроен
- [x] 4 обязательные команды зелёные
- [i] mt-version stub crate (удалён 2026-04-17 как нарушение SSOT [C-1])
**Результат:** база для всей реализации. Commit `e2457ad`.
### M1 — Foundational layer ✅ ЗАКРЫТ
Четыре параллельных crate: fundamentals криптографии и сериализации.
- [x] `mt-codec` — canonical encoding (u8/u16/u32/u64/u128 LE, fixed bytes, 30 domain separators). 26 тестов. Commit `90464a8`.
- [x] `mt-crypto` — SHA-256 + FN-DSA-512 обёртки (Hash32, PublicKey 897B, SecretKey 1281B, Signature 666B, SuiteId). 18 тестов. Commit `df55372`.
- [x] `mt-merkle` — sparse Merkle tree глубины 256, InclusionProof + verify_proof, empty_internal через OnceLock. 25 тестов. Commit `f242956`.
- [x] `mt-genesis` — ProtocolParams (25 полей, 2017 байт canonical), Genesis State Hash formula, OnceLock singleton. 19 тестов + 1 ignored (bootstrap keypairs TBD). Commit `45c1e84`.
**Итог M1:** 4 пакета (mt-codec, mt-crypto, mt-merkle, mt-genesis), 88 тестов зелёных, ~1680 строк кода. Foundational layer закрыт. Найдена ambiguity в спеке: Genesis State Hash формула без domain separator — единственный такой случай, flagged для spec author review.
### M2 — State & time ✅ ЗАКРЫТ
- [x] `mt-state` — Account Table, Node Table, Candidate Pool записи (1000/1043/1027 B) + три sparse Merkle tree + state_root композиция + derive_account_id/derive_node_id + is_active predicate. 32 теста. Commit `ab99e23`.
- [x] `mt-timechain` — vdf_step/verify (T_r = SHA-256^D), next_d (Adaptive D ±3%), cemented_bundle_aggregate (unpredictable-offline binding, 3 ветви). 23 теста. Commit `76ed8da`.
**Итог M2:** 2 пакета, 55 тестов зелёных, ~1000 строк кода. State types + canonical time + anti-grinding binding готовы. Материализация Genesis state (создание bootstrap AccountTable/NodeTable записей) — отложена в M3, зависит от apply_proposal для консистентности.
### M3 — AccountChain ✅ ЗАКРЫТ
Один пакет `mt-account`, 6 phases закрыты. 102 теста, ~1850 строк кода.
| Phase | Scope | Commit |
|-------|-------|--------|
| A | operation types + canonical encoding + op_hash (identifier via signed_scope после v29.7.0) | `0efd92d` → refactor `1af1fff` |
| B | validation (OpError + 5 функций) | `cf95eaf` |
| C | apply individual operations | `8fcca20` |
| D | emission (reward/bonus/bootstrap_cumulative/supply) | `36503dc` |
| E | apply_proposal partial (steps 2/3.5/3.6/4, stubs 1/3a/3b) | `5ab2a45` |
| F | Genesis state materialization | `30400eb` |
Критерий закрытия выполнен: два аккаунта могут обменяться Transfer, balance updated, state_root детерминирован (проверено тестами `apply_transfer_sum_delta_balance_is_zero`, `apply_proposal_state_root_deterministic`).
### M4 — Consensus core 🔄 Next
Три пакета параллельно — сердце консенсуса. Зависимости между ними:
```
mt-lottery (идентификация winner) ──┐
├─→ mt-consensus (proposal сборка)
mt-entry (node admission) ─────────┘
```
#### `mt-lottery` ⏳ TODO
Раздел спеки: «VDF Reveal и лотерея» (~строка 754+), «Confirmer threshold», «cemented_bundle_aggregate» (уже в mt-timechain).
- **Phase A. BundledConfirmation тип + layout** ✅ (24 теста)
- `struct BundledConfirmation { node_id, endpoint, window_index, op_hashes[], reveal_hashes[], signature }` — counts встроены через `Vec::len()`, encoded как u16 LE префиксы
- CanonicalEncode + encode_signed_scope (SSI R1), bundle_hash = identifier (SSI R2, class "mt-bundle")
- `BundleError` (6 вариантов): UnknownNode, UnsupportedSuite, OpsOutOfOrder, RevealsOutOfOrder, WrongEndpoint, InvalidSignature
- validate: strict ascending op_hashes/reveal_hashes, endpoint == expected_T_r (caller), signature verify by NodeTable[node_id].node_pubkey
- **Phase B. VDF_Reveal тип + layout** ✅ (15 тестов)
- `struct VdfReveal { node_id, window_index, endpoint, signature }` — REVEAL_SIZE = 734 B
- CanonicalEncode + encode_signed_scope (SSI R1), reveal_hash = identifier (SSI R2, class "mt-vdf-reveal")
- `compute_endpoint(t_r, cba_w_minus_2, node_id, window_index)` = SHA-256("mt-lottery" || T_r(W) || cba(W-2) || node_id || window_index LE)
- `RevealError` (5 вариантов): UnknownNode, UnsupportedSuite, WrongWindow, WrongEndpoint, InvalidSignature
- validate_reveal: спек-правила 1, 2, 3, 5 («Валидация VDF_Reveal», строки 1020-1026). Правило 4 (weighted_ticket < target) в Phase C
- **Phase C. Node lottery: weighted_ticket_node** (24 теста)
- `seniority_bonus(chain_length, snapshot) = min(chain_length/69, snapshot)` u64 unsigned
- `lottery_weight = snapshot + seniority_bonus` u64 unsigned, DS-2 floor 1
- `log2_q64(endpoint)` bit-scan leading_zeros + нормализация мантиссы в [2^127, 2^128) + **degree-3 Remez minimax polynomial** `log2(1+y) ≈ B0 + y·(B1 - y·(B2_abs - y·B3))` (halved form, unsigned u64 coefficients; max error 2^-10.62)
- `ln_q64(endpoint) = log2_q64 × LN2_Q64 >> 64`, `LN2_Q64 = 0xB17217F7D1CF79AB`
- `weighted_ticket_node = ln_q64(endpoint) / (lottery_weight as u128)` u128 integer div toward zero
- Monotonicity + determinism + boundary tests + 5 binding test vectors TV1-TV5 from spec
- [I-9] compliance **closed** (binding coefficients B0..B3 + 14 binding test vectors total: 5 ln_q64 + 5 weighted_ticket_node + 4 weighted_ticket_account)
- **Phase D. Account lottery: weighted_ticket_account** (13 тестов)
- `compute_account_endpoint(account_id, op_hash, t_r, cba_w_minus_2)` = SHA-256("mt-account-lottery" || ...)
- `weighted_ticket_account = ln_q64(endpoint) / (account_chain_length_snapshot as u128)` u128 совместим с node ticket для argmin
- `AccountLotteryError` (2 варианта): OperatorExcluded, ZeroSnapshot
- `validate_account_participation(is_node_operator, snapshot)` spec правила 2, 3 из «Валидация участия аккаунта»
- **Phase E. Winner determination** (12 тестов)
- `Candidate { ticket: u128, class: u8, id: [u8;32] }` + `Winner`
- `determine_winner(candidates)` argmin by (ticket asc, class asc, id lex asc) canonical rule
- `sorted_candidates_for_fallback` для fallback cascade
- Tie-breaking ambiguity в спеке (probability ~ 2^-128) закрыт canonical rule в коде
- **Phase F. Quorum calculation** (8 тестов)
- `quorum(active_chain_length) = (67 × X + 99) / 100` u64 unsigned, [I-9] compliant (spec vectors passed)
- `is_cemented(cemented_sum, active)` = `cemented_sum ≥ quorum`
#### `mt-consensus` ⏳ TODO
Раздел спеки: «Закрытие окна (Lookback Leadership Finalization)», «Proposal header», «Canonical acceptance», «fallback cascade».
- **Phase A. Proposal header тип + layout** (22 теста)
- `ProposalHeader` struct, 18 полей, PROPOSAL_HEADER_SIZE = 1080 B (target 16B u128, winner_class {1,2} validated)
- encode_signed_scope (R1) + proposal_hash (R2, class "mt-proposal")
- `HeaderError`: UnknownProposer, UnsupportedSuite, InvalidSignature, WindowNotMonotone, ProtocolVersionDecreased, ProtocolVersionUnsupported, FallbackDepthZero
- validate_header: invariants window monotone, protocol_version monotone + local_max, fallback_depth 1, signature R1
- **Phase B. Lookback Leadership proposer_W = winner_{W-2}** (11 тестов)
- `canonical_proposer(current_window, bootstrap, sorted_candidates_W-2)` первый node в sorted list
- `fallback_proposer(...)` Nth node для fallback cascade
- Genesis bootstrap (W<2) + extended bootstrap (нет nodes в candidates)
- Account winner case proposer = ближайший node (spec строка 1315)
- **Phase C. control_set формула** (10 тестов)
- `ControlObjectRef { op_hash, cemented_window }`
- `compute_control_set(all_cemented, prev_window, W)` filter + sort (window asc, op_hash lex asc)
- `validate_control_set` равенство проверяется byte-exact
- **Phase D. Canonical acceptance validation** (13 тестов)
- `AcceptanceError`: ProposerNotCanonical, InsufficientBundles, IncludedRevealsMismatch, WrongWinner
- `validate_proposer_is_canonical(header, bootstrap, sorted_W-2)` сверка с fallback_proposer(depth)
- `validate_bundles_threshold` делегирует mt_lottery::is_cemented
- `validate_included_reveals` byte-exact equality reveal_hashes
- `validate_winner` argmin через mt_lottery::determine_winner
- state_root verification делегирован в mt-account::apply_proposal
- **Phase E. Finalization flow** (4 теста)
- `FinalizationStatus { Cemented, Rejected }` + `finalization_status(sigs_sum, active)`
- `leader_penalty_excluded_node(header)` NodeId для exclusion из lottery текущего окна
#### `mt-entry` ✅ CLOSED (5 phases, 39 тестов)
Раздел спеки: «Вход и регистрация», «apply_proposal Шаг 1/3a/3b», «Adaptive VDF».
- **Phase A. NodeRegistration** (11 тестов)
- `struct NodeRegistration`, NODE_REGISTRATION_SIZE = 1646 B
- encode_signed_scope + nodereg_hash (R2 "mt-nodereg")
- NodeRegError: UnsupportedSuite, InvalidSignature, NodeIdAlreadyIn{NodeTable,CandidatePool}, OperatorAccount{NotFound,AlreadyNode}, WStartOutOfRange, VdfChainTooShort
- validate_noderegistration (structural checks 1-3)
- **Phase B. candidate_vdf_init + Candidate Pool** (4 теста)
- `candidate_vdf_init(t_r, cba, node_id)` = SHA-256("mt-candidate-vdf-init" || ...)
- `compute_expiry_window` = registration +
- `apply_candidate_expiry(pool, window)` шаг 3a
- **Phase C. Selection event** (9 тестов)
- `selection_slots(active_nodes) = max(1, active/130)` 1% cap
- `selection_sort_key(t_r, cba, node_id)` = SHA-256("mt-selection" || ...)
- `is_selection_window(W)` каждые 336 окон
- `rank_candidates_for_selection` + `apply_selection_event` шаг 3b (chain_length=1 активация)
- **Phase D. Adaptive VDF** (6 тестов)
- `required_vdf_length(pending, active, τ₂)` integer form через permille (per [I-9])
- pressure_permille > 10 (1%) → τ₂ × pressure_permille / 10; иначе τ₂
- **Phase E. apply_proposal orchestration** ✅ (5 тестов)
- `nr_sort_key(t_r, cba, node_pubkey)` per spec строки 1838-1843
- `apply_noderegistrations_batch` — incremental apply с pending growth
- Caller (mt-node / integration test) orchestrates: shag 1 = batch, 3a = expiry, 3b = selection, затем mt-account::apply_proposal для steps 2/3.5/3.6/4
**Критерий закрытия M4:** 2 узла могут запустить локальную сеть in-memory, проходить окна, рассчитывать winner, cementing proposals. Полный state transition apply_proposal замкнут.
### M5 — Persistence ⏳ Next after M4
Раздел спеки: «Fast Sync», «Хранение».
Цель: локальный узел перезапускается с восстановлением state с диска; crash-consistent между apply_proposal commit-ами.
#### `mt-store` ✅ CLOSED (5 phases, 24 теста)
**Реализация:** filesystem + fixed-size records (не RocksDB/sled — pure std::fs,
минимум deps, максимум простоты для Manual Validation Gate).
- **Phase A. FsStore** ✅
- `FsStore::open(path)` — создаёт root + proposals/ subdirectory
- `StoreError`: Io, CorruptedLength, ParseFailed, NotFound
- **Phase B. Table persistence** ✅
- `save/load_account_table` — canonical_encode concat → accounts.bin, fixed-size parse
- `save/load_node_table` — nodes.bin
- `save/load_candidate_pool` — candidates.bin
- Decode функции — inverse CanonicalEncode, byte-exact (root byte-equal round-trip verified)
- **Phase C. Proposal archive** ✅
- `archive_proposal(header)``proposals/{window:020}.bin`
- `get_proposal_by_window(W)` → Option<ProposalHeader>, byte-exact decode
- Test 100 proposals random access passes
- **Phase D. Crash recovery** ✅
- `meta.last_cemented.bin` — u64 LE, last committed window
- `verify_consistency()` — meta указывает на archived proposal?
- Test: meta=100 без archive → NotFound finding
- **Phase E. Pruning** ✅
- `prune_proposals_before(threshold)` — delete files window < threshold
- AccountTable/NodeTable/CandidatePool untouched
- Test prune 9/20 proposals, current state preserved
**Критерий закрытия M5 passes:**
1. `cargo test -p mt-store` = 24/24
2. `full_restart_cycle_state_preserved` integration test open populate
save close reopen load roots byte-equal
3. `verify_consistency_detects_missing_proposal` fault flagging
Hands-on example `examples/m5_persist.rs` будет delivered в Validation Gate.
---
## Локальный shakedown — Manual Validation Gate (между M5 и M6) ⏳
**Смысл:** до написания сетевого слоя (M6 libp2p, Dandelion++, IBT) автор вручную на своей машине прогоняет **каждую шестерёнку протокола** с hands-on example binaries. Архитектор (я) объясняет output, критик (я в другой роли) задаёт adversarial вопросы на каждом шаге. Цель убедиться что протокол работает вживую, не только в unit tests.
### Режим прохождения — **incremental, scenario-by-scenario**
**Зафиксировано автором 2026-04-20.** Gate проходится пошагово, по одному сценарию за сессию:
1. Architect пишет `examples/mN_name.rs` binary **только для текущего сценария**
2. Автор запускает команды на своей машине, копирует output
3. Architect разбирает output построчно, сверяет с expected
4. Критик задаёт adversarial checklist
5. Автор либо подтверждает, либо флагует finding
6. Всё OK статус сценария `✅ passed {дата}` в tracker-е ниже
7. Finding fix повторный прогон retry до passed
**Ценность incremental над batch:** ошибки ловятся в момент написания соответствующего binary; не накапливаются до середины Gate. Каждая шестерёнка exercised отдельно.
**Scenario crate:** все binaries живут в `crates/mt-examples/` отдельный crate с dependencies на все необходимые modules, каждый scenario отдельный `examples/mN_*.rs` (cargo example). Создаётся в первой Gate сессии.
### Audit findings (M1-F audit closure — все 7 closed)
**M1-F audit критика реализации (2026-04-26).** 7 findings surface-нуты, **все 7 закрыты конструкцией**. Status: READY FOR EXTERNAL AUDIT (M1 foundational layer scope). Audit package: [AUDIT.md](AUDIT.md) + [docs/audit-checklist.md](docs/audit-checklist.md).
| Finding | Закрытие | Commit |
|---------|----------|--------|
| F-1: нет `Drop+zeroize` для `SecretKey`/`MlkemSecretKey` | workspace dep `zeroize=1.8.1` + Drop impls | `3333738` |
| F-2: panic в lib коде на FFI ошибку | `sign`/`keypair_from_seed` `Result<_, CryptoError>` | `e1164ad` |
| F-3: KAT-baselines self-derived без NIST FIPS oracle | NIST ACVP differential testing 51/51 byte-exact | `6b7ff30` |
| F-4: `cfg!(target_os)` неверно для cross-compile | `CARGO_CFG_TARGET_OS` env var | `9f2ba93` |
| F-5: `keypair()` слабая энтропия в публичном API | `#[cfg(any(test, feature = "testing"))]` гейт | `e1164ad` |
| F-6: один error code маскирует разные fail paths | 6 новых раздельных error codes (7-12) | `71896f6` |
| F-7: `SecretKey::from_array` без validation | implicit closure через F-2 (invalid SK Err при первом sign) | `e1164ad` |
**F-3 closure detail (NIST ACVP differential testing):**
Sparse clone https://github.com/usnistgov/ACVP-Server (Apache-2.0, public domain test vectors из NIST CAVP). Extracted ML-DSA-65 + ML-KEM-768 KeyGen + SigGen deterministic test cases в `crates/mt-crypto-native/tests/fixtures/nist_acvp/` (~500 KB JSON, 3 files). Integration test `crates/mt-crypto-native/tests/nist_acvp_kat.rs` runs differential testing.
**51/51 NIST KAT byte-exact PASS:**
- ML-DSA-65 KeyGen 25/25 (FIPS 204 Algorithm 1 deterministic seed pubkey/secretkey)
- ML-KEM-768 KeyGen 25/25 (FIPS 203 Algorithm 16 (d, z) ek/dk)
- ML-DSA-65 SigGen deterministic external pure empty-context 1/1 (FIPS 204 Algorithm 2 Montana usage pattern)
**Conformance proof:** OpenSSL 3.5.5 LTS backend через mt-crypto-native FFI производит выходы байт-в-байт идентичные NIST FIPS 204/203 reference. Cross-implementation conformance доказана на public NIST oracle.
**Reproduction:**
```
cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код" && cargo test -p mt-crypto-native --test nist_acvp_kat -- --nocapture
```
**Known deferred (lesser scope, явно документированы в [AUDIT.md](AUDIT.md) §3):**
- ML-DSA-65 SigGen с non-empty context current API не принимает context parameter (Montana usage pattern не использует FIPS context); расширение FFI signature когда понадобится
- ML-DSA-65 SigVer NIST KAT direct косвенно подтверждено через round-trip + NIST sign byte-exact
- ML-KEM-768 Encapsulate/Decapsulate NIST KAT M1-F scope только KeyGen; encapDecap нужен в M6+ application layer
Эти limitations задокументированы с closure path и не препятствуют external audit M1 foundational layer.
### Status tracker
| Scenario | Binary | Status | Дата |
|----------|--------|--------|------|
| 0. User onboarding (24-word mnemonic identity) | `mt-examples/examples/m1_mnemonic.rs` | passed (positive end-to-end: 6/6 PASS seeds, keypair (terminal), recovery-fingerprint, mnemonic, vectors (6 binding), roundtrip; adversarial negative-cases unit tests `mt-mnemonic` отдельно, не покрыто example subcommands) | 2026-04-30 |
| 1. M1 Crypto shakedown | `mt-examples/examples/m1_crypto.rs` | passed (5/5 PASS keypair-deterministic, keypair-random, sign, hash, merkle-empty; finding в Subsection 4 cmd_sign: ожидание `sig1 != sig2` противоречило spec строке 5286 «deterministic ML-DSA-65, RND = 0x00 × 32» assertion инвертирована, title переписан на «Determinism…») | 2026-04-30 |
| 2. M2 TimeChain + State | `mt-examples/examples/m2_timechain_state.rs` | passed (5/5 PASS vdf-forward 1000, next-d-boundaries, cba-branches, state-root-compose, merkle-inclusion; binary написан 2026-04-30 коммитом `7e333e6`, прогнан автором на iMac) | 2026-04-30 |
| 3. M3 Account operations | `mt-examples/examples/m3_account.rs` | TODO | |
| 4. M4 Full consensus cycle | `mt-examples/examples/m4_local_net.rs` | TODO | |
| 5. M5 Persistence + restart | `mt-examples/examples/m5_persist.rs` | TODO | |
**M6 unblock criterion:** все 6 строк status = `✅ passed`.
### Starter instructions — начало следующей сессии
Стандартная последовательность для новой сессии после этой точки:
1. **Архитектор реализации погружается в роль.** Прочитать `Код/CLAUDE.md` (v1.4.0+) построчно. Показать критерии работы. Проверить `VERSION.md` актуальный `Spec target` (на 2026-04-21 = Montana v29.13.0).
2. **Sanity check build state:**
```
cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код" && cargo fmt --all -- --check && cargo clippy --all-targets -- -D warnings && cargo test --all 2>&1 | grep -E "test result.*passed|FAILED" | tail -5
```
Ожидание: fmt/clippy clean, 550+ tests passed, 0 failed.
3. **Выбрать следующий сценарий Validation Gate** (incremental, один за сессию). Первый pending сценарий 0 «User onboarding». Последовательность: 0 1 2 3 4 5.
4. **Прогон сценария:**
- Автор copy-paste команды из соответствующего блока «Сценарий N» ниже.
- Автор запускает на своей машине, копирует output в чат.
- Архитектор разбирает построчно, сверяет с acceptance criteria.
- Критик (роль `Код/CRITIC.md` v1.3.0+) применяет adversarial checklist + min 2 perspectives с отдельными выводами per perspective.
- Всё OK сценарий `✅ passed {дата}` в status tracker.
- Finding architect предлагает fix автор apply `делай` retry.
5. **M6 разблокируется** когда все 6 сценариев passed. До этого manual validation приоритет выше любых новых features.
### Policy
- Binary пишет в stdout структурированные events (hashes, state roots, winners, errors). Автор читает output глазами.
- Формат сценария ниже: binary команды expected output architect gate critic checklist.
- M6 не начинается пока ВСЕ 5 сценариев не passed с подписью архитектора и критика.
### Формат каждого сценария
```
Сценарий N: {название}
Фаза покрытия: {M1/M2/...}
Prerequisite: {предыдущие сценарии}
Binary: examples/mN_name.rs
Команды (one-line shell, абсолютные пути):
cd "..." && cargo run --release --example mN_name -- {args}
Expected output pattern:
{literal prefix / regex}
Architect gate: {acceptance criterion}
Critic checklist: {5-10 adversarial вопросов}
```
### Сценарий 0 — User onboarding (24-word mnemonic → derived identity)
Binary: `examples/m1_mnemonic.rs`
Демонстрирует **production user flow**: как реальный пользователь создаёт identity в Montana от 256-битной entropy через 24-словную мнемонику к master_seed и per-role keypair seeds для трёх ролей (account, node, app-encryption).
Что проверяем:
- **Canonical wordlist** 2048 слов, binding SHA-256 fingerprint `2f5eed53a4727b4bf8880d8f3f199efc90e58503646d9ff8eff3a2ed3b24dbda` матчит
- **M-1 Algorithm** (mnemonic_to_master_seed) PBKDF2-HMAC-SHA-256, salt=`"mt-seed"`, iter=2²⁰ 64-байтовый master_seed
- **24-слов mnemonic encoding** entropy (256 bit) + 8-bit checksum = 264 bit 24 × 11 bit = 24 слова (последнее включает checksum)
- **Per-role HKDF-Expand** master_seed + info (`"mt-account-key"` / `"mt-node-key"` / `"mt-app-encryption-key"`) 48B/48B/64B seeds
- **Byte-exact binding vectors** 6 штук в спеке (3 entropy master_seed + 3 per-role derivation)
Команды (actual subcommands):
```bash
cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код" && cargo run --release --example m1_mnemonic -- vectors
cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код" && cargo run --release --example m1_mnemonic -- mnemonic "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art"
cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код" && cargo run --release --example m1_mnemonic -- keypair 0000000000000000000000000000000000000000000000000000000000000000
cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код" && cargo run --release --example m1_mnemonic -- roundtrip 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код" && cargo run --release --example m1_mnemonic -- all
```
Expected output markers:
- `vectors`: «6 BINDING TEST VECTORS» секция, все с `byte-exact match true`; wordlist fingerprint verified
- `mnemonic "<24 words>"`: displayed 24 слова из canonical wordlist, master_seed 64 bytes displayed, 3 derived seeds (48B/48B/64B) displayed
- `keypair <hex32>`: entropy hex mnemonic 24 words master_seed 3 roles seeds
- `roundtrip`: entropy mnemonic master_seed mnemonic (roundtrip) **same master_seed** idempotency proof
- `all`: запускает все 4 subcommands подряд; finale `[result] ALL M1 MNEMONIC: PASS`
Architect gate:
- 6 binding test vectors byte-exact match true
- 24 слова actual visible в output (не просто count)
- Wordlist fingerprint matches binding value
- Roundtrip idempotency verified
- Три per-role seeds distinct для same master_seed (HKDF domain separation works)
Critic adversarial checklist (применять с min 2 perspectives per CRITIC.md v1.3.0):
**Perspective — Cryptographer:**
- PBKDF2 iter = 2²⁰ actual (не меньший для performance)?
- HKDF info domain separation между three roles gives different seeds?
- Wordlist fingerprint tampering detected (mutate one word bit verify fingerprint fails)?
**Perspective — Pen-tester:**
- Invalid mnemonic (word not in list, wrong checksum byte) rejected с ясной ошибкой?
- Partial mnemonic (23 or 25 words) rejected?
- mnemonic с valid words но invalid checksum (изменить последнее слово) rejected?
- Unicode / whitespace normalization два visually-identical inputs producing same master_seed OR rejected?
**Perspective — Production operator:**
- Error messages actionable (user знает как исправить)?
- Timing для 2²⁰ iterations acceptable (должно быть 0.5-2 секунды)?
- SK bytes **не** утекают в stdout (default behavior redacted, M1_DUMP_SK=1 для opt-in, см. m1_crypto P2 external closure)?
### Сценарий 1 — M1 Crypto shakedown
Binary: `examples/m1_crypto.rs` (backfill пишем первым в Validation Gate)
Что проверяем:
- SHA-256 FIPS 180-4 test vector: `hash("abc")` = `ba7816bf...` byte-exact
- FN-DSA-512 keypair + sign message + verify OK; mutate signature byte verify fail
- Merkle `empty_internal(256)` 257 × 32B precomputed, derivation trace уровней 0-2
- Domain separation для всех R2 class domains (`mt-op`, `mt-nodereg`, `mt-proposal`, `mt-bundle`, ...) разные hashes
Команды (subcommands actual binary; подкоманда `all` прогоняет всё подряд):
```bash
cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код" && cargo run --release --example m1_crypto -- hash
cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код" && cargo run --release --example m1_crypto -- sign "test"
cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код" && cargo run --release --example m1_crypto -- keypair 3
cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код" && cargo run --release --example m1_crypto -- merkle-empty
cd "/Users/kh./Python/Ничто/Монтана/Русский/Протокол/Код" && cargo run --release --example m1_crypto -- all
```
Expected output markers (байт-exact проверяется mt_crypto::hash против FIPS 180-4):
- `hash`: секция «1. FIPS 180-4 §B.1 vector» строка `byte-exact match true`; секция «2. Empty-parts collapse» `equal true`; секция «3. Part concatenation» `equal true`; секция «4. Domain separation» различные 32-байтовые хэши для 10 classes
- `sign`: секция «SIGN + VERIFY» `signature 666 bytes`, `verify(mutated signature) false`, 4 adversarial теста все pass
- `keypair N`: N × (pubkey 897B, secret 1281B), `uniqueness N distinct pubkeys`
- `merkle-empty`: `TREE_DEPTH 256`, derivation trace уровни 0..2, finale `empty_internal(256) = <hex>`
Architect gate: `byte-exact match true` в FIPS vector секции; unique hashes для каждого R2 domain; empty_internal(256) deterministic; sign verify mutated false.
Critic checklist:
- Запустить на Intel Mac + ARM Mac + Linux x86 все hashes совпадают? (должны)
- Re-run 2 раза `sign "test"` σ отличаются между запусками? (должно FN-DSA-512 randomized sampler non-deterministic)
- Оба σ верифицируются? (должны stability через signed_scope, Правило R2)
- `hash(b"mt-op", &[b""])` vs `hash(b"mt-op", &[])` одинаковы? (должны empty-parts collapse proof в output)
- `hash(b"mt-op", &[b"aa", b"bb"])` vs `hash(b"mt-op", &[b"aabb"])` одинаковы? (должны concat proof в output)
- Unique domain separation все R2 classes производят разные hashes для того же input? (должны см. секция 4)
### Сценарий 2 — M2 TimeChain + State shakedown
Binary: `examples/m2_timechain_state.rs`
Что проверяем:
- VDF forward: 1000 steps от genesis T_r_0, сверить T_r_1000 ручным пересчётом `SHA-256^1000`
- `next_d` boundary cases: median_permille 850, 851, 949, 950, 1000 верно увеличивает/оставляет/уменьшает
- `cemented_bundle_aggregate`: window < 2 zeros, empty set `SHA-256("mt-bc-aggregate-empty" || W)`, non-empty sorted node_ids aggregate
- State root composition: build AccountTable с 3 accounts + NodeTable 2 nodes + CandidatePool 1 recompute state_root через `SHA-256("mt-state-root" || ...)` independently
- Merkle inclusion proof: один account extract proof verify against root
Команды:
```bash
cd "..." && cargo run --release --example m2_timechain_state -- vdf-forward 1000
cd "..." && cargo run --release --example m2_timechain_state -- next-d-boundaries
cd "..." && cargo run --release --example m2_timechain_state -- cba-branches
cd "..." && cargo run --release --example m2_timechain_state -- state-root-compose
cd "..." && cargo run --release --example m2_timechain_state -- merkle-inclusion
```
Architect gate: все outputs byte-exact совпадают с hand-computed reference.
Critic checklist:
- VDF backward impossible? (должен preimage resistance SHA-256)
- next_d at median=850 exact decreases or unchanged? (per spec: low decrease)
- cemented_bundle_aggregate с одинаковыми node_ids, разным порядком same output? (должен sorted по node_id)
- state_root с reordering accounts in BTreeMap stable? (должен BTreeMap sorted by key)
- Merkle proof на несуществующий key absence proof returns leaf_value=empty, verify_proof returns true? (должен)
### Сценарий 3 — M3 Account operations
Binary: `examples/m3_account.rs`
Что проверяем:
- Scenario: создать bootstrap accounts A, B (через TransferActivation от sponsor) Transfer AB apply_proposal state
- Observable: balance A decreased, B increased; Σ delta_balance = 0; frontier_hash updated; account_chain_length inc
- Emission: manually apply `reward_moneta(params) = EMISSION_moneta` (const 13 Ɉ) для нескольких окон; supply растёт линейно
- Genesis state materialization: build_genesis_state check bootstrap account + node with chain_length=1
- DS-2 edge: attempt apply_proposal с lottery_weight = 0 (chain_length_snapshot=0) winner_node panics? returns err?
Команды:
```bash
cd "..." && cargo run --release --example m3_account -- transfer-scenario
cd "..." && cargo run --release --example m3_account -- emission-schedule 0 10000 20000 60000
cd "..." && cargo run --release --example m3_account -- genesis-state
cd "..." && cargo run --release --example m3_account -- ds2-edge
```
Architect gate: Σ balance invariant после transfer; emission matches closed-form; genesis_state_root deterministic.
Critic checklist:
- Transfer AB с amount > balance A — отклоняется? (InsufficientBalance)
- Transfer A→A — отклоняется? (SelfTransfer)
- Двойной OpenAccount с тем же pubkey → DuplicateAccount?
- Invalid prev_hash (не совпадает с frontier) → InvalidPrevHash?
- Signature от другого keypair → InvalidSignature?
- apply_proposal с одинаковым winner дважды (replay) → emission двойная? (нет — каждое W разный)
### Сценарий 4 — M4 Full consensus cycle (ключевой)
Binary: `examples/m4_local_net.rs --nodes 2 --windows 10`
Что проверяем: in-memory 2+ synthetic узла проходят 10 окон с полным protocol cycle:
- Каждое окно: узлы синтетически подписывают BCs окна W-1 (из testdata) → proposer_{W} = winner_{W-2} → собирает included_bundles + included_reveals → argmin winner → apply_proposal → cementing via quorum
- Printout per window: winner_id, winner_class (Node/Account), reward_moneta, state_root, active_chain_length, quorum
- После 10 окон: supply ledger (Σ rewards) == EMISSION_moneta × 10 — consistency
Команды:
```bash
cd "..." && cargo run --release --example m4_local_net -- --nodes 2 --windows 10
cd "..." && cargo run --release --example m4_local_net -- --nodes 2 --windows 10 --seed 42
cd "..." && cargo run --release --example m4_local_net -- --nodes 5 --windows 100 --verbose
```
Architect gate:
- Σ reward == EMISSION_moneta × N_windows (supply invariant)
- Все apply_proposal succeeded (no protocol violations)
- state_root по окнам — деterministic для given --seed
- Два запуска с --seed 42 → byte-exact same trace
Critic checklist:
- Если один узел не подписывает BC → остаётся в active set? (да, пока < ₂)
- Если winner = offline узел fallback на second_min? (proposer может timeout)
- Fallback cascade: reject proposal second_min rejected third_min? (работает до available candidates)
- 67% quorum точно что если ровно 67%? (`(67X+99)/100` дает ceiling включительно проходит)
- Dependency rule: две операции от одного sender в одном окне вторая отклоняется?
- argmin tie-breaking: два кандидата с одинаковым weighted_ticket как определяется winner? (сравнение by node_id lex asc после ticket equality)
### Сценарий 5 — M5 Persistence + restart
Binary: `examples/m5_persist.rs`
Что проверяем:
- Run M4 scenario close reopen state_root byte-exact совпадает
- Crash посреди apply_proposal (SIGKILL или std::process::exit) reopen state consistent (либо applied, либо not)
- Prune 50 oldest proposals current state untouched, recent proposals accessible
- Concurrent read while writing (RocksDB snapshot): reader видит consistent view
Команды:
```bash
cd "..." && cargo run --release --example m5_persist -- restart-cycle 20
cd "..." && cargo run --release --example m5_persist -- crash-midcommit
cd "..." && cargo run --release --example m5_persist -- prune 50
```
Architect gate: state_root after restart == state_root before close; crash recovery replays partial commit cleanly; pruned proposals NotFound, current state intact.
Critic checklist:
- Corrupt RocksDB файл (e.g., truncate) open returns error, не panic?
- Два процесса одновременно open одну базу второй fails (RocksDB lock)?
- Disk full во время commit WriteBatch atomic либо all, либо nothing?
- Restart через 1 год simulated time replay всех 13 млн окон работает за разумное время?
---
### Проведение Validation Gate — incremental workflow
На каждую сессию **один сценарий**:
1. Автор открывает новую сессию репликой «начинаем Gate Scenario N» (или «пиши» если уже понятно).
2. Architect пишет `crates/mt-examples/examples/mN_name.rs` (новый binary для текущего сценария).
3. Architect даёт команды одной строкой + expected output pattern.
4. Автор запускает на своей машине, копирует output в чат.
5. Architect читает output построчно, сверяет с expected.
6. Критик задаёт adversarial checklist questions (5-10 вопросов из scenario-блока).
7. Автор: «всё работает как ожидал» либо флагует подозрительное.
8. Всё OK architect обновляет status tracker выше: строка сценария `✅ passed {дата}`, commit в ROADMAP.
9. Finding fix (в binary или в core crate) повторный прогон retry.
**Next session trigger:** автор открывает новую сессию после passed предыдущего сценария.
**Критерий закрытия Validation Gate:** все 5 строк tracker-а = `✅ passed`. Только после этого M6.
### Starter — с чего начать новую сессию
Первая Gate сессия (Scenario 1):
1. Автор: «начинаем Scenario 1 M1 Crypto» (или просто «пиши» после тега ROADMAP контекста)
2. Architect: создаёт `crates/mt-examples/` crate (Cargo.toml + пустой `src/lib.rs` или dummy), добавляет workspace members entry
3. Architect: пишет `crates/mt-examples/examples/m1_crypto.rs` с 4 subcommands (fips-abc, falcon-roundtrip, merkle-empty, domain-separation)
4. Architect: даёт команды автору
5. Manual run + разбор + критик passed next session (Scenario 2)
---
## Manual Validation Matrix M1-M5 (production-grade per-element)
Дополняет «Локальный shakedown» сценарии 0-5 (выше). Сценарии happy-path scenario-by-scenario; матрица **каждый винтик** на уровнях кода / протокола / пользователя / UX. Цель production-grade проверка всех элементов перед external audit + перед запуском M6 сетевого слоя.
**Формат каждого пункта:**
```
[ ] Имя | file:line | spec ref | reproduce | expected | pass criterion
```
**Принципы:**
- M6+ network исключён (не реализован)
- Сценарии 0-5 дают smoke-pass; матрица детализирует каждое test vector / invariant / API surface
- Pass criterion бинарный: byte-exact match / numerical equality / observable behavior без «вроде работает»
- Зависимости: пункт может зависеть от предыдущих (например проверка transfer требует init готовый)
---
### Уровень 1 — Code primitives (M1 Foundational)
#### M1.1 mt-codec — canonical encoding + domain registry
| # | Элемент | file:line | Reproduce | Expected | Pass |
|---|---------|-----------|-----------|----------|------|
| 1.1.1 | `write_u8/u16/u32/u64/u128` LE | crates/mt-codec/src/lib.rs | `cargo test -p mt-codec` | LE byte order, no platform dependence | byte-exact match unit-tests |
| 1.1.2 | `write_bytes(buf, &[u8])` | mt-codec/src/lib.rs | round-trip property test | encode decode = identity | property test passes 1000 cases |
| 1.1.3 | `CanonicalEncode` trait impls для всех consensus types | mt-codec/src/lib.rs + per-crate impl | `grep "impl CanonicalEncode"` workspace-wide | каждый consensus type implements | NodeRegistration, ProposalHeader, BundledConfirmation, VdfReveal, AccountRecord, NodeRecord, CandidateRecord все present |
| 1.1.4 | Domain registry 32 константы prefix-free | mt-codec::domain | `grep "pub const " crates/mt-codec/src/lib.rs \| grep -i domain` | список 32 domains, ни один не префикс другого | prefix-free check: convention `mt-{component}[-{subcomponent}]` соблюдена для всех |
| 1.1.5 | `hash(domain, parts)` self-delimiting | mt-crypto::hash | manual reproduce: `python3 -c "import hashlib; print(hashlib.sha256(b'mt-proposal' + bytes([0]) + payload).hexdigest())"` vs Rust output | byte-exact SHA-256 match | byte-exact match для test inputs |
| 1.1.6 | `sha256_raw(bytes)` без domain | mt-crypto:sha256_raw | FIPS 180-4 §B.1 vector "abc" ba7816bf | byte-exact "abc" standard digest | unit-test passes |
#### M1.2 mt-crypto — PQ crypto + secret types
| # | Элемент | file:line | Reproduce | Expected | Pass |
|---|---------|-----------|-----------|----------|------|
| 1.2.1 | `keypair_from_seed(seed)` ML-DSA-65 deterministic | mt-crypto/src/lib.rs | `cargo test -p mt-crypto-native --test nist_acvp_kat` | NIST FIPS 204 KAT byte-exact | 25/25 KeyGen vectors PASS |
| 1.2.2 | `sign(sk, msg)` deterministic ML-DSA-65 (FIPS 204 Algorithm 2) | mt-crypto::sign | NIST ACVP SigGen 15 cases tgId=3 | byte-exact deterministic signature | 15/15 SigGen vectors PASS |
| 1.2.3 | `verify(pk, msg, sig)` constant-time | mt-crypto::verify | sign+verify roundtrip + tamper test | accept good, reject mutated | unit-test PASS, no panic |
| 1.2.4 | `keypair_from_seed_mlkem` ML-KEM-768 | mt-crypto-native NIST KAT | `cargo test -p mt-crypto-native --test nist_acvp_kat` | FIPS 203 KAT byte-exact | 25/25 KeyGen vectors PASS |
| 1.2.5 | `SecretKey` heap+mlock | mt-crypto/src/lib.rs:130 | `cargo test -p mt-crypto --test security_invariants secret_key_is_heap_allocated` | size_of::<SecretKey>() = ptr (8B), не 4032 | invariant test PASS |
| 1.2.6 | `SecretKey: !Clone` | mt-crypto::SecretKey | trait check test | compile-error на try_clone() | compile-time enforce |
| 1.2.7 | `SecretKey: !PartialEq` (timing-leak protection) | mt-crypto::SecretKey | security_invariants test | no `==` operator | compile-time check PASS |
| 1.2.8 | `Drop+zeroize` для SecretKey/MlkemSecretKey | mt-crypto/src/lib.rs Drop impl | drop scope test | bytes zeroed после drop | unit-test verify post-drop memory |
| 1.2.9 | `mlock`/`munlock` 7 unsafe blocks с SAFETY | mt-crypto/src/lib.rs:164,192,235,276,293,364,383 | grep `// SAFETY:` | каждый unsafe имеет comment | 7/7 SAFETY comments present |
| 1.2.10 | No println on secret bytes | grep | `rg "println" crates/mt-crypto/src` | нет matches на SK fields | grep clean |
#### M1.3 mt-crypto-native — FFI к OpenSSL EVP
| # | Элемент | file:line | Reproduce | Expected | Pass |
|---|---------|-----------|-----------|----------|------|
| 1.3.1 | `mt_keypair_from_seed_mldsa` C wrapper | csrc/mt_crypto.c | NIST KAT differential | OpenSSL EVP_PKEY_KEYGEN с `OSSL_PKEY_PARAM_ML_DSA_SEED` | byte-exact NIST FIPS 204 |
| 1.3.2 | `mt_sign_mldsa` deterministic | csrc/mt_crypto.c | ACVP SigGen с `OSSL_SIGNATURE_PARAM_DETERMINISTIC=1` | FIPS 204 Algorithm 2 deterministic | 15/15 PASS |
| 1.3.3 | `mt_sign_mldsa_ctx` (FIPS context support) | csrc/mt_crypto.c | empty ctx == ctx-equivalence | один test case | 1/1 PASS |
| 1.3.4 | OpenSSL 3.5.5 LTS pinned | Cargo.toml | `cargo tree \| grep openssl-src` | =300.5.5+3.5.5 exact pin | exact version match |
| 1.3.5 | Reproducible builds через build.rs | crates/mt-crypto-native/build.rs | `docker build -f docker/release-build.dockerfile` two passes | byte-identical binaries | sha256 binary 1 == sha256 binary 2 |
#### M1.4 mt-mnemonic — 24-word recovery flow
| # | Элемент | file:line | Reproduce | Expected | Pass |
|---|---------|-----------|-----------|----------|------|
| 1.4.1 | `entropy_to_mnemonic([u8; 32])` | mt-mnemonic/src/mnemonic.rs | KAT vectors test | byte-exact 24 words from BIP-39 wordlist | 5/5 KAT PASS |
| 1.4.2 | `mnemonic_to_master_seed("...")` PBKDF2 iter=2²⁰ | mt-mnemonic/src/pbkdf2.rs | RFC 7914 §11 / RFC 6070 vectors | byte-exact 64B master_seed | RFC vectors PASS |
| 1.4.3 | `mldsa_seed_for_role(seed, domain)` HKDF | mt-mnemonic/src/hkdf.rs | RFC 5869 §A.1 | byte-exact 32B per-role seed | RFC vectors PASS |
| 1.4.4 | `mlkem_seed_for_role(seed, domain)` 64B output | mt-mnemonic/src/hkdf.rs | KAT test | byte-exact 64B (d ‖ z FIPS 203 §6.1) | unit-test PASS |
| 1.4.5 | HMAC-SHA-256 RFC 4231 | mt-mnemonic/src/hmac.rs | RFC 4231 cases 1-7 | byte-exact MAC | 7/7 PASS |
| 1.4.6 | Wordlist binding SHA-256 | mt-mnemonic/src/wordlist.rs | hash binding test | sha256(wordlist file) match expected | hash match |
| 1.4.7 | Whitespace-tolerant parsing (F-12 closure) | mt-mnemonic/src/mnemonic.rs:120 | `init --mnemonic " word1 word2 ..."` | accepts irregular whitespace | regression test PASS |
| 1.4.8 | End-to-end recovery determinism | mt-mnemonic/tests/e2e_recovery.rs | `cargo test -p mt-mnemonic --test e2e_recovery` | mnemonic → identity → mnemonic = identity | 1/1 PASS |
---
### Уровень 2 — State foundation (M2)
#### M2.1 mt-merkle — Sparse Merkle Tree depth 256
| # | Элемент | file:line | Reproduce | Expected | Pass |
|---|---------|-----------|-----------|----------|------|
| 2.1.1 | `leaf_hash(serialized)` domain-separated | mt-merkle/src/lib.rs:13 | `python3 -c "import hashlib; print(hashlib.sha256(b'mt-merkle-leaf' + bytes([0]) + bytes).hexdigest())"` | byte-exact SHA-256 | external oracle match |
| 2.1.2 | `internal_hash(left, right)` order-sensitive | mt-merkle/src/lib.rs | swap left↔right test | hash(L,R) ≠ hash(R,L) | order-sensitivity PASS |
| 2.1.3 | `empty_internal(k)` precomputed cache levels 0-256 | mt-merkle/src/lib.rs OnceLock | manual shasum levels 0,1,2,3 | recursive `H(empty_internal(k-1), empty_internal(k-1))` | 4/4 manual computations match |
| 2.1.4 | `SparseMerkleTree::insert(key, value)` | mt-merkle/src/lib.rs | insertion-order independence test | вставка в разном порядке → одинаковый root | order-independence PASS |
| 2.1.5 | `SparseMerkleTree::root()` BTreeMap canonical | mt-merkle::SparseMerkleTree | determinism_invariants tests | byte-exact root для разного insertion order | 10/10 invariants PASS |
| 2.1.6 | `verify_proof(root, proof)` inclusion | mt-merkle::verify_proof | construct proof, verify | true для valid, false для tampered | 2/2 cases PASS |
| 2.1.7 | `verify_proof` absence (empty leaf) | mt-merkle::verify_proof | proof for non-existent key | true (absence verified) | absence proof PASS |
| 2.1.8 | TREE_DEPTH = 256 | mt-merkle::TREE_DEPTH | const check | == 256 | static assertion |
| 2.1.9 | InclusionProof MAX_LEAF_VALUE_SIZE = 4096 (P10-1) | mt-merkle/src/lib.rs:285 | encode test с oversized leaf | debug_assert panic | bound enforced |
| 2.1.10 | InclusionProof MAX_SIBLINGS = TREE_DEPTH | mt-merkle::MAX_SIBLINGS | const | == 256 | static |
#### M2.2 mt-genesis — ProtocolParams SSOT
| # | Элемент | file:line | Reproduce | Expected | Pass |
|---|---------|-----------|-----------|----------|------|
| 2.2.1 | `genesis_params()` singleton OnceLock | mt-genesis/src/lib.rs | повторные вызовы → same Arc ptr | стабильный pointer | unit-test PASS |
| 2.2.2 | `ProtocolParams` encoded size 4094B | mt-genesis::PARAMS_ENCODED_SIZE | `assert_eq!(p.encode().len(), 4094)` | byte-exact 4094B | static |
| 2.2.3 | `emission_moneta = 13 × 10⁹ nɈ` | mt-genesis::ProtocolParams | const check | == 13_000_000_000 | LE bytes [0x00, 0xc8, 0x4d, 0x21, 3, 0, 0, 0] |
| 2.2.4 | `d0 = 325_000_000` | mt-genesis::ProtocolParams | runtime calibration on M-class Mac | окно ≈ 60s wall-clock | empirical 60-65s |
| 2.2.5 | `tau2_windows = 20160` | mt-genesis::ProtocolParams | const | == 20160 (~14 days) | static |
| 2.2.6 | `selection_interval` = 336 | mt-genesis::ProtocolParams | const | == 336 (1/60 of τ₂) | static |
| 2.2.7 | `bootstrap_node_pubkey` placeholder | mt-genesis::ProtocolParams | byte check | == [0u8; 1952] (pre-ceremony) | static; post-ceremony updated |
| 2.2.8 | `genesis_app_id()` SHA-256 domain-separated | mt-genesis/src/lib.rs | manual recompute | byte-exact hash | unit-test PASS |
| 2.2.9 | `compute_genesis_state_hash()` determinism | mt-genesis | повторные вызовы | byte-exact same hash | determinism_invariants 7/7 |
| 2.2.10 | `is_genesis_bootstrap_finalized(params)` | mt-genesis | predicate test | placeholder zeros → false | static check |
#### M2.3 mt-state — AccountTable / NodeTable / CandidatePool
| # | Элемент | file:line | Reproduce | Expected | Pass |
|---|---------|-----------|-----------|----------|------|
| 2.3.1 | `AccountRecord` 2059B byte-exact | mt-state/src/lib.rs:25 | `assert_eq!(record.encode().len(), ACCOUNT_RECORD_SIZE)` | == 2059 | static |
| 2.3.2 | `NodeRecord` 2098B byte-exact | mt-state/src/lib.rs:59 | encode size | == 2098 | static |
| 2.3.3 | `CandidateRecord` 2082B byte-exact | mt-state/src/lib.rs | encode size | == 2082 | static |
| 2.3.4 | `derive_account_id(suite_id, pk)` SHA-256 domain | mt-state::derive_account_id | manual recompute via Python | byte-exact | external oracle PASS |
| 2.3.5 | `derive_node_id(pk)` distinct from account_id | mt-state::derive_node_id | derive both for same pk | account_id ≠ node_id | cross-distinctness PASS |
| 2.3.6 | `AccountTable::insert/get` BTreeMap | mt-state::AccountTable | order-independence test | root hash same regardless insertion order | invariant PASS |
| 2.3.7 | `NodeTable::insert/contains` | mt-state::NodeTable | duplicate check | second insert returns false / errors | unit-test PASS |
| 2.3.8 | `CandidatePool::insert/remove/iter` | mt-state::CandidatePool | TTL-based pruning | apply_candidate_expiry удаляет expired | scenario test |
| 2.3.9 | `compute_state_root(node, candidate, account)` | mt-state::compute_state_root | manual SHA-256 composition | byte-exact tree-of-three | external oracle PASS |
| 2.3.10 | `is_active(node, current_window)` predicate | mt-state::is_active | boundary at 2×τ₂ inclusive test | true within window, false outside | boundary tests PASS |
#### M2.4 mt-timechain — VDF + adaptive D + cba
| # | Элемент | file:line | Reproduce | Expected | Pass |
|---|---------|-----------|-----------|----------|------|
| 2.4.1 | `vdf_step(prev, d)` SHA-256^d associative | mt-timechain/src/lib.rs:13 | composition test: `vdf_step(vdf_step(x, a), b) == vdf_step(x, a+b)` | associativity holds | invariant PASS |
| 2.4.2 | `vdf_step(prev, 0) == prev` identity | mt-timechain | zero-iteration check | output == input | unit-test PASS |
| 2.4.3 | `vdf_verify(prev, expected, d)` | mt-timechain::vdf_verify | sign correct + tamper | accept correct, reject wrong claim | 2/2 cases PASS |
| 2.4.4 | `next_d(current, median_permille, params)` adaptive | mt-timechain/src/lib.rs:40-89 | dead-zone (median == 1000): no change; high (>1030): +3%; low (<970): -3% | integer permille arithmetic | 3/3 boundary tests PASS |
| 2.4.5 | `next_d` overflow protection | mt-timechain/src/lib.rs:51,65 | extreme D close to u64::MAX | panic с descriptive message | overflow caught (~25_000 epochs) |
| 2.4.6 | `cemented_bundle_aggregate(W, &[])` empty marker | mt-timechain::cba | window=0,1 genesis zeros | byte-exact `[0u8; 32]` | static |
| 2.4.7 | `cemented_bundle_aggregate(W, [node_ids])` canonical sort | mt-timechain | input order independence test | byte-exact для разного порядка | [I-8] invariant PASS |
| 2.4.8 | `cemented_bundle_aggregate` distinct empty vs non-empty | mt-timechain | empty `[]` vs `[node_id]` | разные hashes | distinctness PASS |
| 2.4.9 | TimeChain VDF на genesis узле тикает per окно | montana-node start | `montana-node start --max-windows 2 && status` | T_r изменился, current_window инкрементировано на 2 | observable PASS |
---
### Уровень 3 — apply_proposal (M3 mt-account)
| # | Элемент | file:line | Reproduce | Expected | Pass |
|---|---------|-----------|-----------|----------|------|
| 3.1 | Transfer 0x02 layout (TRANSFER_SIZE) | mt-account/src/lib.rs | encode size check | byte-exact spec | static const |
| 3.2 | `validate_transfer` signature + balance + chain_length | mt-account::validate_transfer | tests/transfer_*.rs | reject bad sig / insufficient balance / non-monotone chain | reject all 3 cases |
| 3.3 | `apply_transfer` atomic: balance debited, credited, frontier_hash bound | mt-account::apply_transfer | unit-test | sender.balance -= amount, receiver.balance += amount, frontier_hash = op_hash | invariant PASS |
| 3.4 | ChangeKey 0x03 layout | mt-account::CHANGE_KEY_SIZE | encode size | byte-exact spec | static const |
| 3.5 | `validate_change_key` signature под старым ключом | mt-account::validate_change_key | tamper test | new sig under old key required | unit-test PASS |
| 3.6 | `apply_change_key` rotates current_pubkey | mt-account::apply_change_key | before/after | account.current_pubkey == new_pk, suite_id updated | unit-test PASS |
| 3.7 | Anchor 0x04 layout | mt-account::ANCHOR_SIZE | encode size | byte-exact spec | static const |
| 3.8 | `validate_anchor` app_id + data_hash binding | mt-account::validate_anchor | tests | data_hash 32B fixed, app_id present | unit-test PASS |
| 3.9 | `apply_anchor` AccountChain entry, не меняет balance | mt-account::apply_anchor | balance check | balance unchanged, chain_length += 1 | unit-test PASS |
| 3.10 | TransferActivation 0x0A sponsor pattern | mt-account::TRANSFER_ACTIVATION_SIZE | encode size | byte-exact spec | static const |
| 3.11 | `apply_transfer_activation` создаёт новый AccountRecord | mt-account::apply_transfer_activation | before/after AccountTable | new account inserted, balance=amount, is_node_operator=false | unit-test PASS |
| 3.12 | `op_hash(operation)` SHA-256("mt-op" \|\| signed_scope) | mt-account/src/lib.rs | manual recompute | byte-exact | external oracle PASS |
| 3.13 | `settle_window(cemented_ops, W)` сортировка по op_hash | mt-account::settle_window | unsorted input | output sorted ascending op_hash | invariant PASS |
| 3.14 | `apply_proposal` Step 2 (emission), 3.5 (chain_length++), 3.6 (checkpoint), 4 (state_root) | mt-account::apply_proposal | per-window in start.rs | balance += emission_moneta, node.chain_length += 1, state_root updated | observable balance growth |
| 3.15 | `apply_emission(winner_account, params)` const 13 Ɉ | mt-account::apply_emission | один tick | account.balance += 13_000_000_000 | observable PASS |
| 3.16 | `supply_moneta(W, params) = emission × (W+1)` closed-form | mt-account::supply_moneta | различные W | linear formula | unit-test PASS |
| 3.17 | `build_genesis_state(params)` bootstrap | mt-account::build_genesis_state | call + encode | byte-exact deterministic state | invariant PASS |
| 3.18 | `genesis_state_root(params)` byte-exact | mt-account::genesis_state_root | manual recompute | == compute_state_root результат | external oracle PASS |
| 3.19 | Conservation: Σ delta_balance == 0 per Transfer/Activation | mt-account::apply_* | property test | sum unchanged | invariant PASS |
| 3.20 | checked_sub on balance underflow panic с protocol invariant breach | mt-account::apply_transfer | underflow case | panic с descriptive message | controlled halt |
---
### Уровень 4 — Consensus mechanics (M4)
#### M4.1 mt-lottery — BundledConfirmation + VdfReveal + lottery
| # | Элемент | file:line | Reproduce | Expected | Pass |
|---|---------|-----------|-----------|----------|------|
| 4.1.1 | `BundledConfirmation::encode_signed_scope` byte-exact | mt-lottery/src/lib.rs:34 | size check | fixed overhead 76B + var hashes | static |
| 4.1.2 | `bundle_hash(bc)` SHA-256("mt-bundle" \|\| scope) | mt-lottery/src/lib.rs:69 | external oracle | byte-exact match | external PASS |
| 4.1.3 | `validate_bundle` rules a-d | mt-lottery/src/lib.rs:102 | per-rule rejection tests | UnknownNode/UnsupportedSuite/WrongEndpoint/OpsOutOfOrder/RevealsOutOfOrder/InvalidSignature | 6/6 reject paths PASS |
| 4.1.4 | `validate_bundle` u16 cap (M4-1 closure) | mt-lottery/src/lib.rs:120 | op_hashes.len() > u16::MAX → TooManyOps | reject before signature verify | functional test PASS |
| 4.1.5 | op_hashes ascending strictly | mt-lottery::is_strictly_ascending | unsorted input → reject | OpsOutOfOrder error | unit-test PASS |
| 4.1.6 | `VdfReveal` layout 32+8+32+SIG bytes | mt-lottery::REVEAL_SIZE | size check | == REVEAL_SIZE const | static |
| 4.1.7 | `reveal_hash(reveal)` SHA-256("mt-vdf-reveal" \|\| scope) | mt-lottery::reveal_hash | external oracle | byte-exact | external PASS |
| 4.1.8 | `validate_reveal` (5 rules) | mt-lottery::validate_reveal | per-rule reject | similar to bundle | unit-test PASS |
| 4.1.9 | `compute_endpoint(T_r, cba(W-2), my_node, W)` lottery formula с [I-8] binding | mt-lottery::compute_endpoint | manual SHA-256 composition | byte-exact, [I-8] satisfied | external oracle PASS |
| 4.1.10 | `log2_q64(x_q64)` Q64.64 polynomial | mt-lottery/src/lib.rs (B0..B3 coefficients) | reference Python implementation | max error 2^-10.62 | precision match |
| 4.1.11 | `ln_q64(x_q64)` derived from log2_q64 | mt-lottery::ln_q64 | reference test | byte-exact match | unit-test PASS |
| 4.1.12 | `weighted_ticket_node(endpoint, w, snapshot)` u128 integer division | mt-lottery::weighted_ticket_node | various w | deterministic ticket | determinism_invariants 34/34 |
| 4.1.13 | `lottery_weight(chain_length, snapshot)` integer | mt-lottery::lottery_weight | boundary chain_length | monotone non-decreasing | unit-test PASS |
| 4.1.14 | `seniority_bonus` integer formula | mt-lottery::seniority_bonus | various inputs | spec-exact | unit-test PASS |
| 4.1.15 | `determine_winner` argmin (ticket asc, class asc, id lex asc) | mt-lottery::determine_winner | tie cases | canonical ordering rule | tie-breaker tests PASS |
| 4.1.16 | `quorum(active_chain_length)` 67% ceiling `(67×X+99)/100` | mt-lottery::quorum | various X | integer ceiling formula | spec-binding PASS |
| 4.1.17 | `is_cemented(c, active)` predicate | mt-lottery::is_cemented | boundary cases | true if c >= quorum(active) | boundary tests PASS |
#### M4.2 mt-consensus — ProposalHeader R1/R2
| # | Элемент | file:line | Reproduce | Expected | Pass |
|---|---------|-----------|-----------|----------|------|
| 4.2.1 | `ProposalHeader` 3722B fixed (17 полей) | mt-consensus/src/lib.rs:33 | encode size | == PROPOSAL_HEADER_SIZE = 3722 | static |
| 4.2.2 | `encode_signed_scope` 413B (без подписи) | mt-consensus/src/lib.rs | encode_signed_scope().len() | == 413 | static computation |
| 4.2.3 | `proposal_hash(header)` SHA-256("mt-proposal" \|\| scope) | mt-consensus::proposal_hash | external oracle | byte-exact | external PASS |
| 4.2.4 | `validate_header` rules a-f | mt-consensus::validate_header | per-rule tests | fallback_depth ≥ 1, window_index = prev+1, protocol_version monotone, proposer registered, suite Mldsa65, sig verify | 6/6 reject paths PASS |
| 4.2.5 | `target` поле u128 LE 16B (P5 closure) | mt-consensus::ProposalHeader | encode field | u128 16 bytes LE | static |
| 4.2.6 | `canonical_proposer(W)` Lookback Leadership | mt-consensus::canonical_proposer | proposer_W = winner_{W-2} | spec-exact | unit-test PASS |
| 4.2.7 | `fallback_proposer(depth)` cascade | mt-consensus::fallback_proposer | depth 1, 2, 3 | byzantine-safe ordering | unit-test PASS |
| 4.2.8 | `compute_control_set(headers)` filter + sort | mt-consensus::compute_control_set | unordered input | sorted by (window asc, op_hash lex asc) | invariant PASS |
| 4.2.9 | `validate_proposer_is_canonical` | mt-consensus | mismatched proposer | reject | unit-test PASS |
| 4.2.10 | `finalization_status(header)` Cemented/Rejected | mt-consensus::finalization_status | various confirmer counts | per is_cemented | unit-test PASS |
#### M4.3 mt-entry — NodeRegistration + Selection
| # | Элемент | file:line | Reproduce | Expected | Pass |
|---|---------|-----------|-----------|----------|------|
| 4.3.1 | `NodeRegistration` 5344B fixed | mt-entry/src/lib.rs | encode size | == NODE_REGISTRATION_SIZE | static |
| 4.3.2 | `nodereg_hash` SHA-256("mt-nodereg" \|\| scope) | mt-entry::nodereg_hash | external oracle | byte-exact | external PASS |
| 4.3.3 | `validate_noderegistration` 3 rules | mt-entry::validate_noderegistration | per-rule reject | suite supported, sig verify, node_id unique, operator account exists+!is_node_operator | 4/4 reject paths PASS |
| 4.3.4 | `candidate_vdf_init([T_r, cba(W-2), node_id])` [I-8] | mt-entry::candidate_vdf_init | external oracle | byte-exact composition | external PASS |
| 4.3.5 | `compute_expiry_window(W)` 3τ₂ | mt-entry::compute_expiry_window | various W | == W + 3 × 20160 | static formula |
| 4.3.6 | `apply_candidate_expiry(pool, W)` removes expired | mt-entry::apply_candidate_expiry | per-window call | pool.len() decreases | observable PASS |
| 4.3.7 | `selection_slots` 1% admission cap (ADMISSION_DIVISOR=130) | mt-entry::selection_slots | active_chain_length 13000 → 100 slots | per-formula | spec-binding PASS |
| 4.3.8 | `selection_sort_key` SHA-256("mt-selection" \|\| ...) | mt-entry::selection_sort_key | external oracle | byte-exact | external PASS |
| 4.3.9 | `rank_candidates_for_selection` canonical sort | mt-entry::rank_candidates_for_selection | unsorted input | sorted by sort_key | invariant PASS |
| 4.3.10 | `apply_selection_event(pool, nodes, accounts, W, params)` | mt-entry::apply_selection_event | candidates ready | inserted into NodeTable, removed from pool, operator account flagged | observable PASS |
| 4.3.11 | `apply_noderegistrations_batch` incremental sort+apply | mt-entry::apply_noderegistrations_batch | per-window | applied count == 1 if vdf_chain_length ≥ τ₂ | observable PASS |
| 4.3.12 | `required_vdf_length(pending, active, τ₂)` integer permille | mt-entry::required_vdf_length | pending=0,active=0 → tau2_windows | spec-binding | unit-test PASS |
---
### Уровень 5 — Persistence (M5 mt-store)
| # | Элемент | file:line | Reproduce | Expected | Pass |
|---|---------|-----------|-----------|----------|------|
| 5.1 | `FsStore::open(data_dir)` создаёт proposals/ | mt-store/src/lib.rs | удалить proposals/, открыть store | proposals/ создан | observable PASS |
| 5.2 | `cleanup_orphan_tmp` (M5-LOW-8 closure) | mt-store/src/lib.rs:67 | placement orphan .tmp file | удалён при open | regression test PASS |
| 5.3 | `save_account_table` + `load_account_table` round-trip | mt-store/src/lib.rs:271-281 | save → load | byte-exact decode | round-trip PASS |
| 5.4 | `save_node_table` + `load_node_table` round-trip | mt-store/src/lib.rs:301-311 | save → load | byte-exact decode | round-trip PASS |
| 5.5 | `save_candidate_pool` + `load_candidate_pool` round-trip | mt-store/src/lib.rs:331-341 | save → load | byte-exact decode | round-trip PASS |
| 5.6 | `archive_proposal(header)` создаёт `proposals/{W:020}.bin` | mt-store::archive_proposal | per-window | файл 3722B на диске | `ls data/proposals/` |
| 5.7 | `get_proposal_by_window(W)` byte-exact decode | mt-store::get_proposal_by_window | archived header | recovered identical | round-trip PASS |
| 5.8 | `save_meta_last_cemented(W)` u64 LE | mt-store::save_meta_last_cemented | per-window | meta_last_cemented.bin = 8B u64 LE | hexdump check |
| 5.9 | `verify_consistency()` crash recovery | mt-store::verify_consistency | удалить proposals/ файл, оставить meta_last_cemented | StoreError::NotFound | error path PASS |
| 5.10 | `prune_proposals_before(window)` | mt-store::prune_proposals_before | call с threshold | удаляет файлы window < threshold | filesystem state |
| 5.11 | Atomic rename pattern (tempfile rename) | mt-store/src/lib.rs:87-114 | crash mid-write simulation | partial files либо clean state | atomicity PASS |
| 5.12 | `decode_X` size validation перед read | mt-store/src/lib.rs | corrupted file (wrong size) | StoreError::CorruptedLength | error path PASS |
---
### Уровень 6 — Integration layer (montana-node CLI)
#### N1. Identity management commands
| # | Команда | Reproduce | Expected | Pass |
|---|---------|-----------|----------|------|
| 6.1 | `montana-node init` (random) | `./montana-node init` | 24 words displayed in 4×6 grid + identity.bin (15627B) | 24-word grid + file size byte-exact |
| 6.2 | `montana-node init --mnemonic "..."` | recovery from words | byte-exact same identity as original | identity match |
| 6.3 | `montana-node init --entropy <hex32>` | deterministic from hex | byte-exact same on re-run | determinism PASS |
| 6.4 | `montana-node init --force` overwrite | second init без --force отклонён, с --force OK | IdentityFileExists error без --force | error path PASS |
| 6.5 | `montana-node inspect` (default) | без --reveal-master-seed | fingerprint only, no master_seed | observable PASS |
| 6.6 | `montana-node inspect --reveal-master-seed` | флаг present | master_seed shown (64 hex) | observable PASS |
| 6.7 | Recovery determinism cross-machine | machine 1 init mnemonic; machine 2 init --mnemonic | identical account_id, node_id | byte-exact match |
#### N2. Node lifecycle commands
| # | Команда | Reproduce | Expected | Pass |
|---|---------|-----------|----------|------|
| 6.8 | `montana-node start --max-windows 2` | finite run | 2 windows processed, balance += 26 Ɉ, exit 0 | observable PASS |
| 6.9 | `montana-node start --d-test-override 1000000` | TEST-ONLY override | warning printed, fast windows | observable PASS |
| 6.10 | `montana-node status` | AccountTable + NodeTable + CandidatePool dump | Σ balance, supply, phase, current_window correct | observable PASS |
| 6.11 | `montana-node time` | window timing info | current_window, ближайший selection (W % 336==0), эпоха τ | static check |
| 6.12 | `montana-node help` / `--help` / `-h` | help text | full usage + flag descriptions | text match |
| 6.13 | Exit codes | invalid args exit 2; ok exit 0; runtime err exit 1 | per-spec | exit code check |
| 6.14 | LocalNodeError variants Display | trigger каждую ошибку | typed error message в stderr | manual cases |
#### N3. Phase transitions auto-detection
| # | Сценарий | file:line | Reproduce | Expected | Pass |
|---|----------|-----------|-----------|----------|------|
| 6.15 | Genesis bootstrap (placeholder zeros) | node_lifecycle.rs:70 is_bootstrap_node | `cargo run -p montana-node start --max-windows 1` (свежий узел) | phase = Active immediately, NodeTable содержит self | observable PASS |
| 6.16 | Candidate path (post-ceremony, mismatch pubkey) | node_lifecycle.rs:78 | finalized bootstrap_node_pubkey != identity.node_pk | phase = Bootstrap CandidateVdf на первом окне | DEV-010 logic PASS (не тестируется до ceremony) |
| 6.17 | DEV-010 status | docs/SPEC_DEVIATIONS.md:172 | grep Status | acknowledged auto-detection | static |
#### N4. State migration
| # | Сценарий | file:line | Reproduce | Expected | Pass |
|---|----------|-----------|-----------|----------|------|
| 6.18 | current_window.bin v0 v1 auto-upgrade | clock.rs:31-58 | вручную создать 8B legacy file start узел check size | next save 16B v1 формат | round-trip PASS |
| 6.19 | identity.bin magic+version validation | identity.rs:226-231 | tamper magic byte load_identity | InvalidMagic error | error path PASS |
| 6.20 | timechain.bin magic+version | timechain_state.rs:47-52 | tamper magic | InvalidMagic | error path PASS |
| 6.21 | node_state.bin magic+version | node_lifecycle.rs:82-87 | tamper magic | InvalidMagic | error path PASS |
---
### Уровень 7 — Operator surface (UX/UI)
#### O1. launchd integration (macOS)
| # | Элемент | Reproduce | Expected | Pass |
|---|---------|-----------|----------|------|
| 7.1 | plist Label = "org.montana.node" | `cat ~/Library/LaunchAgents/org.montana.node.plist \| grep Label` | production reverse-domain (no `dev.*`/`local.*`) | static check |
| 7.2 | RunAtLoad=true | plist | автозапуск при логине | login test |
| 7.3 | KeepAlive Crashed=true | plist | restart on crash | `kill -9 <PID>`, узел restart-ит через ThrottleInterval=10 | restart observable |
| 7.4 | StandardOutPath/StandardErrorPath | plist | logs в `data/logs/` | `tail -F data/logs/montana.log` | observable |
| 7.5 | SIGTERM graceful shutdown | start.rs:31-33 + 545-546 | `launchctl unload -w plist` | узел сохраняет state, exit clean | clean shutdown |
| 7.6 | ThrottleInterval=10 rate limit | plist | crash несколько раз | минимум 10s между restart | observable timing |
#### O2. .command файлы
| # | Файл | Reproduce | Expected | Pass |
|---|------|-----------|----------|------|
| 7.7 | `1. Запуск и логи узла.command` | дабл-клик в Finder | Terminal открывается, status + tail -F | observable open Terminal |
| 7.8 | `2. Остановить узел.command` | дабл-клик | launchctl unload, "ГОТОВО" message | узел остановлен |
| 7.9 | Relative `$DIR` paths (не hardcoded) | grep "$HOME/Applications" в .command | пусто | path-portable PASS |
#### O3. Install scripts
| # | Скрипт | Reproduce | Expected | Pass |
|---|--------|-----------|----------|------|
| 7.10 | `scripts/install-local-mac.sh` | `bash scripts/install-local-mac.sh` (свежая система) | build + install + identity init + launchd setup | full e2e install |
| 7.11 | `scripts/install-mac.sh` | `curl ... \| bash` | git clone + build + install | full e2e install |
| 7.12 | `scripts/install-vps.sh` (Linux) | `bash scripts/install-vps.sh` Ubuntu/Debian | systemd setup + start | systemctl status active |
| 7.13 | `INSTALL_DIR` env var override | `INSTALL_DIR=/custom/path bash scripts/install-local-mac.sh` | install в custom location | observable PASS |
| 7.14 | `dist/macOS/install.command` | дабл-клик | full install + open Finder | GUI flow PASS |
#### O4. State files inventory
| # | Файл | Path | Expected size/format | Pass |
|---|------|------|---------------------|------|
| 7.15 | `identity.bin` | `data/identity.bin` | 15627B fixed, magic="mt-local", version=1 | size + magic byte-exact |
| 7.16 | `accounts.bin` | `data/accounts.bin` | N × 2059B (растёт с TransferActivation) | size кратен 2059 |
| 7.17 | `nodes.bin` | `data/nodes.bin` | N × 2098B | size кратен 2098 |
| 7.18 | `candidates.bin` | `data/candidates.bin` | N × 2082B | size кратен 2082 |
| 7.19 | `meta/current_window.bin` | `data/meta/current_window.bin` | 16B v1 (либо 8B v0 legacy) | size 16 либо 8 |
| 7.20 | `meta/timechain.bin` | `data/meta/timechain.bin` | magic "mttc" + version + T_r + D + last_window | hexdump magic |
| 7.21 | `meta/node_state.bin` | `data/meta/node_state.bin` | magic "mtns" + version + lifecycle fields | hexdump magic |
| 7.22 | `meta_last_cemented.bin` | `data/meta_last_cemented.bin` | 8B u64 LE (либо absent если pre-genesis) | hexdump |
| 7.23 | `proposals/{window:020}.bin` | `data/proposals/00000000000000000001.bin` | каждый файл 3722B | size byte-exact |
| 7.24 | `logs/montana.log` + `montana.err.log` | `data/logs/` | log files (launchd redirect) | files present |
#### O5. Crash recovery
| # | Сценарий | Reproduce | Expected | Pass |
|---|----------|-----------|----------|------|
| 7.25 | Power loss mid-write (atomic rename) | start узел, `pkill -9 montana-node` во время save_progress | partial .tmp файл либо clean state, не corrupted bin | atomicity PASS |
| 7.26 | SIGKILL reload state | `kill -9 <PID>` restart | continues с last cemented window (state preserved) | observable continuity |
| 7.27 | Corrupted file detection | tamper data/accounts.bin (truncate) | StoreError::CorruptedLength на load | error path PASS |
| 7.28 | meta_last_cemented vs proposals/ inconsistency | удалить proposals/<W>.bin, оставить meta_last_cemented | StoreError::NotFound, не silent skip | verify_consistency PASS |
| 7.29 | Re-open после crash valid state | crash → restart | узел продолжает с того окна, balance preserved | observable PASS |
---
### Уровень 8 — Conformance & test vectors
#### C1. NIST KAT (cross-implementation conformance)
| # | Vector | Source | Reproduce | Pass |
|---|--------|--------|-----------|------|
| 8.1 | ML-DSA-65 KeyGen 25 cases | NIST ACVP-Server `ML-DSA-keyGen-FIPS204` | `cargo test -p mt-crypto-native --test nist_acvp_kat` | 25/25 byte-exact |
| 8.2 | ML-DSA-65 SigGen 15 cases (1 empty + 14 ctx) | NIST ACVP-Server `ML-DSA-sigGen-FIPS204` tgId=3 | same test | 15/15 byte-exact |
| 8.3 | ML-KEM-768 KeyGen 25 cases | NIST ACVP-Server `ML-KEM-keyGen-FIPS203` | same test | 25/25 byte-exact |
| 8.4 | mt_sign_mldsa ≡ mt_sign_mldsa_ctx (empty ctx) | own correctness check | same test | 1/1 PASS |
#### C2. RFC test vectors
| # | Vector | Source | Pass |
|---|--------|--------|------|
| 8.5 | SHA-256 "abc" → ba7816bf...15ad | FIPS 180-4 §B.1 | unit-test PASS |
| 8.6 | HMAC-SHA-256 cases 1-7 | RFC 4231 | 7/7 PASS |
| 8.7 | HKDF-Expand vectors | RFC 5869 §A.1 | PASS |
| 8.8 | PBKDF2 vectors | RFC 7914 §11 / RFC 6070 | PASS |
#### C3. Determinism invariants (191 automated total)
| # | Crate | Test count | Reproduce | Pass |
|---|-------|------------|-----------|------|
| 8.9 | mt-merkle | 10 | `cargo test -p mt-merkle --test determinism_invariants` | 10/10 |
| 8.10 | mt-genesis | 7 | `cargo test -p mt-genesis --test determinism_invariants` | 7/7 |
| 8.11 | mt-state | (per file) | `cargo test -p mt-state --test determinism_invariants` | all PASS |
| 8.12 | mt-timechain | 19 | `cargo test -p mt-timechain --test determinism_invariants` | 19/19 |
| 8.13 | mt-account | 29 | `cargo test -p mt-account` | 29/29 |
| 8.14 | mt-lottery | 34 | `cargo test -p mt-lottery` | 34/34 |
| 8.15 | mt-consensus | 27 | `cargo test -p mt-consensus` | 27/27 |
| 8.16 | mt-entry | 24 | `cargo test -p mt-entry` | 24/24 |
| 8.17 | mt-store | 17 | `cargo test -p mt-store` | 17/17 |
#### C4. Security invariants (13 automated)
| # | Invariant | Reproduce | Pass |
|---|-----------|-----------|------|
| 8.18 | SecretKey: !Clone | `cargo test -p mt-crypto --test security_invariants secret_key_is_not_clone` | compile-PASS |
| 8.19 | SecretKey: !PartialEq | same test | compile-PASS |
| 8.20 | SecretKey heap-allocated | same test | size_of == ptr |
| 8.21 | SecretKey impl Drop | same test | trait check |
| 8.22 | MlkemSecretKey same 4 invariants | same test | 4/4 PASS |
| 8.23 | FFI fills SK с non-zero bytes | same test | bytes != [0; N] |
| 8.24 | No println/log on SK in lib code | same test | grep clean |
| 8.25 | PublicKey/Signature: Clone (sanity) | same test | trait check |
---
### Уровень 9 — DEV-001..DEV-011 status verification
| # | DEV-N | Spec ref | Status verify | Code location | Pass |
|---|-------|----------|---------------|---------------|------|
| 9.1 | DEV-001..DEV-009 — закрыты через canonical apply_proposal pipeline rewrite | docs/SPEC_DEVIATIONS.md:1-170 | `grep "Status: закрыто" docs/SPEC_DEVIATIONS.md \| wc -l` | 9 closed entries | static |
| 9.2 | DEV-010 — genesis bootstrap auto-detection | docs/SPEC_DEVIATIONS.md:172 | `is_bootstrap_node` logic в node_lifecycle.rs:70-79 | runtime check via `montana-node status` | observable |
| 9.3 | DEV-011 — hardware calibration initial D | docs/SPEC_DEVIATIONS.md:N | runtime D adjustment per τ₂ | post-Genesis ceremony auto-adjust | static acknowledgment |
| 9.4 | Pre-commit hook DEV count enforcement | scripts/pre-commit.sh Gate 3 | edit DEV in code without doc → reject | enforced commit gate | static |
---
### Прохождение matrix
**Этапы:**
1. **Фаза A — automated baseline** (1-2 часа)
- `cargo test --all` — все 191 determinism + 13 security + NIST KAT + RFC vectors
- Pass criterion: 0 failures
- Result: матрица пунктов 8.x = automated PASS
2. **Фаза B — operator surface manual** (1 рабочий день)
- Свежая установка через `scripts/install-local-mac.sh`
- Прохождение пунктов 6.x (CLI commands), 7.x (operator surface)
- Verify state files размеры/magic
- Verify launchd integration (start/stop/restart on crash)
- Verify install scripts на VPS (Linux) + macOS
3. **Фаза C — protocol per-element manual** (3-5 рабочих дней)
- Каждый пункт 1.x (M1 primitives), 2.x (M2 state), 3.x (apply_proposal), 4.x (consensus)
- External oracle cross-checks (Python SHA-256 для hash compositions)
- Boundary tests (overflow, underflow, edge cases)
- Per-DEV deviation status confirmation
4. **Фаза D — recovery & crash** (0.5 рабочего дня)
- Сценарии 7.25-7.29 — atomic rename, SIGKILL recovery, corrupted file detection
**Pass criterion для всей matrix:** 100% пунктов с PASS либо явный finding с DEV-NNN reference в docs/SPEC_DEVIATIONS.md.
**Output:** заполненная matrix (markdown table со столбцом "Status: PASS/FAIL/N/A") в отдельном документе `docs/manual-validation-results-YYYY-MM-DD.md`. Это deliverable перед external audit / перед запуском M6 network layer.
**Tooling:**
- Automated: `cargo test --all` для пунктов 8.x
- External oracle: Python 3 + hashlib для SHA-256 cross-checks
- Diff: `diff -u expected actual` для byte-exact verification
- Hexdump: `hexdump -C file | head` для file format verification
---
### Связь с существующим Manual Validation Gate
«Локальный shakedown» сценарии 0-5 (выше) дают smoke-pass — happy-path запуск каждого слоя. Matrix M1-M5 — детализация: каждый scenario разворачивается в 20-50 проверок per-element.
Сценарии 0-5 → entry-level demonstration. Matrix → production-grade audit-ready evidence.
---
### M6 — Network ✅ ЗАКРЫТ (in-process e2e); cross-machine pairing defer M8
- [x] `mt-net` (~2700 LOC, no_std) — wire format envelope + 12 structured payloads + IBT online/mesh proofs + Bootstrap PoW + Uniform Framing + peer selection (4-level diversity + LRU) + Dandelion++ stem-fluff + NAT traversal + Mesh Transport + Store-and-Forward. **110 unit + integration tests PASS.**
- [x] `mt-net-transport` (~470 LOC, std + libp2p) — libp2p TCP+TLS 1.3+Noise+Yamux upgrade chain + MontanaCodec для request-response + MontanaBehaviour + IBT classify_proof. **14 tests PASS включая 3 e2e two-node communication.**
Phases закрытия:
- Phase A wire envelope + payloads (commits `9de287b`, `bc694a5`)
- Phase B IBT + Bootstrap PoW (commit `26e76c9`)
- Phase D Uniform Framing (commit `cce189f`)
- Phase E peer selection + diversity + LRU (commit `f34726f`)
- Phase F Dandelion + NAT (commit `7d44687`)
- Phase G Mesh + Store-and-Forward (commit `60466c1`)
- **Critic-fix bundle P-C1..P-C8** (commit `93b9cdc`) — 8 code findings closed (domain SSOT, prefix-free rename, fuzz harnesses, try_new constructors, forward-compat, O(1) verify, unwrap/expect refactor)
- Phase C.0 mt-net-transport skeleton + libp2p [C-5] capability checklist 8/8 PASS (commit `ea6608e`)
- Phase C.1 MontanaCodec (commit `11f3b80`)
- Phase C.2 MontanaBehaviour wrapper (commit `ba00051`)
- Phase C.3 e2e two-node handshake — Manual Validation Gate scenario 6 PASS (commit `9a15f49`)
- Phase C.4 e2e proposal exchange + 512 KiB boundary — scenario 7 PASS (commit `04f8d29`)
**Критерий закрытия M6:** 2 узла на разных machines обмениваются proposals через network — closed in-process (e2e tests), cross-machine pairing defer to M8 montana-node binary distribution.
### M7 — Fast Sync
- [ ] `mt-sync` — snapshot делiver + verify через Merkle root сравнение, catch-up от snapshot до current window, genesis content replication.
**Критерий закрытия M7:** новый узел синхронизируется от 0 до текущего state за минуты, не часы.
### M8 — Node binary
- [ ] `mt-node` — CLI, config, wire всё вместе, logging, metrics, graceful shutdown.
**Критерий закрытия M8:** один бинарь `mt-node` запускает узел с конфигом, участвует в сети, зарабатывает Монтану (Ɉ) в лотерее.
### M9 — Conformance suite ✅ READY (initial); expansion in progress
- [x] `mt-conformance` (~150 LOC) — публичный test vectors набор для cross-implementation byte-exact verification. Initial vectors: envelope A1/A2/A3 + IBT B1 (after P-C2 rename) + Bootstrap PoW F1/F2 target derivation. **2 unit tests PASS.**
- [ ] Expansion: 12 TBD-A markers (consensus objects 0x01..0x22 + app-layer 0x60/0x61/0x64) defer until app-layer payload format finalization.
**Критерий закрытия M9:** вторая реализация (Swift iOS либо Go) проходит все vectors байт-в-байт. iOS Phase 2.1 — port done (`MontanaTests/MTConformanceVectors.swift` mirror), pending Xcode `xcodebuild test` verification.
### M10 — Spec compliance cleanup ✅ ЗАКРЫТ
Полное приведение кода в соответствие спеке после серии breaking changes (winner_class removal, TransferActivation opcode, single-path node lottery, ACCOUNT_RECORD_SIZE refactor). Закрыто 7 commits, все 4 обязательные проверки зелёные, 593+ тестов passed.
### M11 — CloseAccount финализация 🔄 Текущий
- [ ] `CloseAccount` opcode `0x0B` — полная реализация после spec-финализации payload формата: struct + Инварианты CloseAccount, apply_proposal dispatch (вычитание баланса в supply, удаление AccountRecord, освобождение operator-binding если is_node_operator), binding test vectors.
**Критерий закрытия M11:** payload format CloseAccount специфицирован в спеке, реализация byte-exact, все 4 обязательные проверки зелёные.
### M12 — Pure conservation monetary policy compliance ✅ ЗАКРЫТ (superseded by M13)
Pre-mainnet миграция к pure geometric pin 41/40, без bootstrap-надбавки, без сжигания, без opcodes прикладного слоя (никнеймы, сервисные кредиты). **Superseded M13 const emission cleanup** — geometric step-up baseline (41/40) удалён в пользу const `EMISSION_moneta = 13 Ɉ` per окно. Историческая запись остаётся для context.
### M13 — Const emission cleanup ✅ ЗАКРЫТ
Pre-mainnet упрощение монетарной политики: pin 41/40 + carry-recurrence заменён на единственную константу `EMISSION_moneta = 13 × 10⁹ nɈ` per окно. `reward_moneta(W) = EMISSION_moneta` (const, навсегда), `supply_moneta(W) = EMISSION_moneta × (W + 1)` closed-form.
Удалено:
- `MonetaryState` struct + 24B persistent state + `apply_step` + 3 controlled overflow panics
- `monetary_epoch_tick` apply_proposal Step 2.5
- `r_baseline_at_epoch` reconstruction helper
- `save_monetary_state`/`load_monetary_state` mt-store API + `monetary.bin` file
- ProtocolParams поля `monetary_epoch_windows`, `inflation_num`, `inflation_den` (24B encoded, 4118 → 4094)
- `m33_emission` example
- Constitutional declaration spec section (Freigeld/Gesell/Frederick/Keynes/Friedman/Schmitt-Grohé/Bordo/Onken/Wörgl academic apparatus)
- Equilibrium analysis для (41/40)^e + Binding test vectors с эпохами
Замена:
- ProtocolParams: единственное поле `emission_moneta: u128 = 13_000_000_000`
- `reward_moneta(params: &ProtocolParams) -> u128` — однострочник `params.emission_moneta`
- `supply_moneta(W, params)` — closed-form `emission × (W + 1)`
- `compute_state_root(node_root, candidate_root, account_root)` — без MonetaryState arg
- `apply_proposal(account_table, node_table, candidate_pool, input, params)` — без monetary mut arg, без Step 2.5
Constitutional break: новый Genesis State Hash (ProtocolParams layout + state_root composition меняются) — pre-mainnet нормально.
**Критерий закрытия M13:** spec + code grep на pattern set (`MonetaryState|r_baseline|carry_current|inflation_num|inflation_den|monetary_epoch|R_GENESIS|r_genesis_moneta|MONETARY_STATE|geometric step|41/40|2\.5%|Freigeld|Gesell|Frederick`) даёт 0 hits в спеке и в коде.
### M1-E — Миграция подписи FN-DSA-512 → ML-DSA-65 ✅ ЗАКРЫТ
Pre-mainnet breaking wire-format change: переход на NIST FIPS 204 финализированный стандарт. Закрывает [I-1] (PQ-secure) более mainstream путём (NIST level 3, formally finalized) и устраняет dependency на необфициальный pqcrypto-falcon binding wrapper.
**Все 11 шагов Phase E plan executed:**
- E.1: проверка test_vectors.rs (vectors уже обновлены под L=32 для ML-DSA, L=64 для ML-KEM)
- E.2: 5 KAT vectors сгенерированы через keygen_vectors.rs reference implementation
- E.3: спека v31.0.0 обновлена — Derivation Vectors 1+2 hex заменён на реальные значения; новый раздел «Binding KAT vectors для KeyGen → terminal observable output» с 5 KAT (SHA-256 fingerprints для byte-exact cross-impl conformance)
- E.4: `mt-crypto::self_test()` обновлён — KAT 1 byte-exact conformance check вместо placeholder; добавлены `EXPECTED_KAT_1_PK_SHA256` / `EXPECTED_KAT_1_SK_SHA256` константы
- E.5: integration test `e2e_recovery.rs` — 3 теста (terminal_observable_byte_exact, distinct_entropies_distinct_terminals, account_node_keys_differ_same_master)
**Phase F (examples):**
- F.1: m1_mnemonic refactor — cmd_keypair → cmd_seeds, новый cmd_keypair (terminal output), новый cmd_recovery_fingerprint; mt-codec domain registry +1 (mt-recovery-fingerprint)
- F.2: m1_crypto refactor — cmd_keypair → cmd_keypair_random + новый cmd_keypair_deterministic (default `keypair`)
- F.3: verify через release binaries — все subcommands PASS
**Phase G (closure):**
- G.1: VERSION.md updated, M1-E entry added
- G.2: ROADMAP.md M1-E milestone closure
- G.3: 4 обязательные проверки green (fmt/clippy/test/build --release)
- G.4: 3 commits — Phase E `8be11f3`, Phase F `11f7232`, Phase G (этот)
**Manual validation готов к авторскому прогону** (Validation Gate Scenario 0 и 1 ready):
- `m1_mnemonic recovery-fingerprint` — single 64-char hex для two-device validation
- `m1_mnemonic keypair` — terminal observable IDs (account_id, node_id) + 6 byte-exact key parts
- `m1_crypto keypair` — deterministic recovery primitive sanity
- `m1_mnemonic vectors` — 6/6 binding vectors PASS
**Acknowledgement:** `ml-dsa 0.1.0-rc.8` — RustCrypto pure-Rust, **NOT formally audited**; migration target `libcrux-ml-dsa` когда formally verified version stable. Любая смена implementation library обязана сохранять byte-identity с KAT fingerprints спеки (cross-impl conformance gate).
---
## Dependency graph
```
sha2, ml-dsa, ml-kem
┌───────────────────────────────────────┐
│ │
mt-codec mt-crypto
│ │
└─────────────┬─────────────────────────┘
┌────────────────┴────────────────┐
▼ ▼
mt-merkle mt-genesis
│ │
└──────────────┬──────────────────┘
mt-state ◄────── mt-timechain
│ │
└────┬─────────────┘
mt-account
┌───────────────────┼───────────────────┐
▼ ▼ ▼
mt-lottery mt-consensus mt-entry
│ │ │
└───────────────────┼───────────────────┘
mt-store
mt-net
mt-sync
mt-node
mt-conformance
```
Каждая стрелка — `[dependencies]` в `Cargo.toml`. Никаких циклов (Rust их запрещает на уровне компилятора).
---
## История обновлений
| Дата | Изменение | Commit |
|------|-----------|--------|
| 2026-04-21 | ROADMAP actualization: R1 stale v29.8.x refs fix + R2 Сценарий 0 User onboarding (24-word mnemonic flow через m1_mnemonic) добавлен в Validation Gate + R3 history entries 2026-04-21 + R4 Starter instructions block для новой сессии. M6 unblock criterion: 6/6 сценариев passed. | (этот) |
| 2026-04-21 | CRITIC.md v1.2.0 → v1.3.0: 3 новых прохода (Source→Sink Flow, Independent Oracle / Differential Check, Misuse-Resistance API Audit) + reframe mt-examples как operator-facing security surface + hard enforcement Multi-perspective rotation через obligatory per-perspective conclusions block. Catalyst — external critic v1.2.0 поднял 6 blind spots. | `5d41d7c` |
| 2026-04-21 | feat: domain separation structural fix (NUL separator) + external critic findings closure. mt_crypto::hash() теперь SHA-256(domain ‖ 0x00 ‖ parts) — self-delimiting guarantee против 8 prefix-collision pairs в registry. Новая sha256_raw() для FIPS/HMAC/raw. Spec v29.12.0 → v29.13.0. Closes 3 external findings (P1 domain sep, P2 SK leak, P3 label) + 2 mine (P4 empty_internal binding, P5 stale RECOVERY). | `d762cec` |
| 2026-04-21 | CRITIC.md v1.1.0 → v1.2.0: 7 новых проходов (timing/side-channel, concurrency, version compat, deps source audit, resource exhaustion беyond DoS, test quality, deployment) + Multi-perspective rotation + Anti-recency bias check + known blind spots (M-1..M-5 documented). | `bcecc65` |
| 2026-04-21 | CRITIC.md v1.0.0 → v1.1.0: 4 новых прохода (primitive byte-level audit, registry integrity, output/observable surface, bottom-up reading discipline) + поведение при external critic finding. Reactive response на domain separation bug surface-ит ранее. | `74d2142` |
| 2026-04-21 | doc: sync ROADMAP сценария 1 subcommands с actual m1_crypto binary interface (hash/sign/keypair/merkle-empty/all, не fips-abc/falcon-roundtrip/domain-separation) + m1_mnemonic stale spec refs cleanup. | `4126006` |
| 2026-04-21 | feat: [I-9] closure path A — poly3 minimax заменяет linear log2_q64. mt-lottery Phase C prototype linear (2^-3.5 error) → Remez degree-3 minimax (2^-10.62, theoretical optimum). Binding coefficients B0..B3 halved form unsigned u64, unsigned Horner с intermediate invariants proofs. 14 binding test vectors (5 ln_q64 + 5 weighted_ticket_node + 4 weighted_ticket_account) + 5 target_next vectors в спеке. Spec v29.10.1 → v29.12.0. 4 conformance pending formulas closed. | `7b76dc9` |
| 2026-04-21 | feat: validate_header winner_class ∈ {1,2} check — закрывает P2 external critic реализации (panic в apply_emission от malformed byte в подписанном proposal → liveness attack). HeaderError::InvalidWinnerClass + 2 тест-vectors. Spec v29.10.1 added invariant `winner_class ∈ {1,2}` в Proposal header. | `589be89` |
| 2026-04-21 | refactor: [C-1] SSOT — WINNER_CLASS_NODE/ACCOUNT в mt-state (единый источник), pub use re-exports в mt-account/mt-lottery. Закрывает P1 external critic реализации (duplicate constants). | `5df072d` |
| 2026-04-21 | Spec bump v29.9.0 → v29.10.1: mt-mnemonic closes [I-9] conformance (6 binding test vectors в спеке для M-1 Algorithm + per-role HKDF derivation). | `a35af3f` |
| 2026-04-20 | Validation Gate режим зафиксирован: incremental scenario-by-scenario (один сценарий за сессию), status tracker, starter instructions для новой сессии. M6 unblock = 5/5 passed. | (предыдущий) |
| 2026-04-20 | M4 + M5 CLOSED: mt-consensus (60 тестов, 5 phases — header + Lookback + control_set + Canonical acceptance + Finalization) + mt-entry (39 тестов, 5 phases — NodeRegistration + Candidate Pool + Selection event + Adaptive VDF + batch apply) + mt-store (24 теста, 5 phases — FsStore + table persist + proposal archive + crash recovery + pruning). Workspace 506 тестов. Next: Manual Validation Gate (5 сценариев). | (предыдущий) |
| 2026-04-20 | Spec+role fix: target 8B → 16B u128 (header aligned с P5 P[I-9] integer form) + fallback_depth bound [1,255] specified + role 4.13.1 → 4.13.2 Gate 0.5 шаг (d.2) field name coverage. Proposal header [I-9] audit: 17 полей прогнаны — 1 finding (target, fixed), остальные clean. Critic findings P12-P15 закрыты. Ничто: `9113850`. | 9113850 |
| 2026-04-19 | ROADMAP: expanded M5 до phases A-E + новая секция «Локальный shakedown — Manual Validation Gate» (5 сценариев между M5 и M6) + policy hands-on example per milestone | 2f317b8 |
| 2026-04-19 | M4 Phase C: mt-lottery Node lottery — `seniority_bonus`, `lottery_weight`, `log2_q64` (bit-scan + Phase C prototype linear approx), `ln_q64`, `weighted_ticket_node` (24 теста). [I-9] compliant, polynomial deferred (closed 2026-04-21 path A) | 4cb42bc |
| 2026-04-19 | Post-audit fix: critic findings P6-P9 (duplicate lottery formulas без integer form в spec) + P8 (methodology failure — пропуск Шага 8). Ничто commits: `21e1547` (spec duplicate fix), `7be68f5` (role 4.13.0 → 4.13.1 + усилен Gate 0.5 обязательным pre-edit duplicate scan). | de68778 |
| 2026-04-19 | C4 re-audit M2-M3 по [I-9]: `next_d` соответствует spec (code использовал permille, spec Q32.32 → aligned на permille); emission family (bonus/reward/bootstrap_cumulative/supply) полностью [I-9] compliant без изменений. Spec P4 patched in-place в Ничто; code comment добавлен. | 2c14587 |
| 2026-04-19 | Spec v29.7.1 → v29.8.0: [I-9] Bit-exact deterministic arithmetic инвариант + integer forms для P2/P3/P4/P5 (role+spec atomic в Ничто `1f3f3f9`); Phase C unblocked; fix ROADMAP ×/+ ошибка | 94c7002 |
| 2026-04-19 | M4 Phase B: mt-lottery VdfReveal тип + encode/signed_scope/reveal_hash/compute_endpoint/validate_reveal (15 тестов) | 5395581 |
| 2026-04-24 | **M10 ЗАКРЫТ** — spec compliance cleanup v30.7.0 → v30.9.0. 11 phases закрыты (Phase 7 CloseAccount отложен в M11). 7 commits: 0b55d69 (ROADMAP), 7f19876 (AccountRecord +53B + winner_class cascade), 500fb9f (account lottery cleanup), 2ed18ab (TransferActivation + cooldown), 67868d9 (Nickname/Auction tables + ProtocolParams extension), 533c457 (price_at binding vectors), и этот closure commit. 593+ тестов зелёные, 4 проверки passed. | (этот commit) |
| 2026-04-23 | M10 Phase 0: VERSION.md bump v30.7.0 → v30.9.0 + ROADMAP M10 milestone block + 13 phases plan + 21 drift points audit | 0b55d69 |
| 2026-04-19 | M4 Phase A: mt-lottery BundledConfirmation тип + encode/signed_scope/bundle_hash/validate (24 теста) | 444e399 |
| 2026-04-19 | Spec v29.7.0 → v29.7.1: editorial cleanup (S-1 дубль mt-node, S-2 genesis_candidate_root, F-1 Genesis State Hash domain) + code fix mt-genesis | 6058f3b |
| 2026-04-19 | Роль v1.3.0 → v1.4.0: [C-2] Spec Flow Pre-verification глобальный инвариант | 0197044 |
| 2026-04-19 | **M3 ЗАКРЫТ** — mt-account 6 phases, 102 теста, ~1850 строк. ROADMAP: M4 детально, M5 crude. | 30400eb |
| 2026-04-19 | M3 Phase F: Genesis state materialization (12 тестов) | 30400eb |
| 2026-04-19 | M3 Phase E: apply_proposal partial steps 2/3.5/3.6/4 (15 тестов) | 5ab2a45 |
| 2026-04-19 | M3 Phase D: emission reward/bonus/supply (17 тестов) | 36503dc |
| 2026-04-19 | M3 Phase C: apply individual ops (15 тестов) | 8fcca20 |
| 2026-04-18 | M3 Phase B: validation OpError + 5 функций (23 теста) | cf95eaf |
| 2026-04-18 | Spec v29.6.9 → v29.7.0: signature architecture (SSI rules R1..R4), breaking `cemented_bundle_aggregate` → node_ids only | 918cdf2 |
| 2026-04-17 | ROADMAP: M3 phase-детализация + M4 crude phases (правило v1.3.0) | e92f0b4 |
| 2026-04-17 | Роль v1.2.0 → v1.3.0: правило детализации ROADMAP | 0893fd4 |
| 2026-04-17 | M3 Phase A: mt-account types + encoding + op_hash (18 тестов) | 0efd92d |
| 2026-04-17 | **M2 ЗАКРЫТ**: mt-timechain (23 теста), итог M2 — 2 пакета, 55 тестов | 76ed8da |
| 2026-04-17 | M2: mt-state закрыт (32 теста) | ab99e23 |
| 2026-04-17 | Spec v29.6.8 → v29.6.9 (противоречия #2, #3 устранены) | 2c148b5 |
| 2026-04-17 | mt-version crate удалён (нарушение SSOT [C-1]) | 8bd008e |
| 2026-04-17 | [C-1] SSOT — глобальный инвариант кода в роли | 2a9309d |
| 2026-04-17 | Spec v29.6.7 → v29.6.8 (противоречие #1: selection_interval) | ff390f2 |
| 2026-04-17 | **M1 ЗАКРЫТ**: mt-genesis (19+1 тест), итог M1 — 4 пакета, 88 тестов | 45c1e84 |
| 2026-04-17 | M1: mt-merkle закрыт (25 тестов) | f242956 |
| 2026-04-17 | Roadmap: demos отложены до M4+ | 7c9219e |
| 2026-04-17 | Создан ROADMAP.md | 65e34ec |
| 2026-04-17 | M1: mt-crypto закрыт (18 тестов) | df55372 |
| 2026-04-17 | M1: mt-codec закрыт (26 тестов) | 90464a8 |
| 2026-04-17 | Роль: post-commit test block обязателен | 8d42073 |
| 2026-04-17 | Роль: language policy — max Russian | c06c56b |
| 2026-04-17 | Роль: terminal commands one-line | a18acc6 |
| 2026-04-17 | Роль: auto-commit policy | cd2f719 |
| 2026-04-17 | M0: workspace skeleton | e2457ad |