# Spec Deviations Single source of truth для всех известных отклонений реализации от спеки Montana. Введён v1.13.0 роли архитектора кода ([C-10] Mandatory deviation tracker). Каждый `// SPEC DEVIATION DEV-NNN: ...` в коде ссылается на конкретный entry ниже. Pre-commit hook (`scripts/pre-commit.sh`) сверяет количество. Закрытые `DEV-N` оставляются в файле как историческая запись со `Status: закрыто (commit )`. --- ## DEV-001: NodeRegistration с vdf_chain_length=0 **Crate:** `montana-node` **File:line:** `crates/montana-node/src/registration.rs:8-22` (build_node_registration) **Spec section:** «NodeRegistration» / «Adaptive VDF» / «Шаг 1: incremental apply» **Spec quote:** «`if NR.vdf_chain_length >= required: apply; N += 1; else: reject`», `required_vdf_length(pending=0, active=0, τ₂)` → `tau2_windows = 20160` **Что делает код:** `vdf_chain_length=0` (либо user-provided), без проверки `≥ τ₂`, обходит `apply_noderegistrations_batch` через ручной `CandidatePool::insert` **Severity:** блокер mainnet ([I-9] / [C-7] violation, обход canonical apply pipeline) **Closure path:** реализовать candidate VDF phase в `start.rs` — узел тикает VDF до `vdf_chain_length ≥ τ₂_windows`, после чего автоматически формирует NodeRegistration с правильным `vdf_chain_length` и вызывает `apply_noderegistrations_batch` через canonical pipeline **Closure cost:** ~14 дней wall-clock на M-class Mac (физика VDF, не код) + ~4 часа кода **Status:** закрыто (commit `fb204ef` mt-local-node: byte-exact rewrite через canonical apply_proposal) --- ## DEV-002: proof_endpoint = candidate_vdf_init(zeros, zeros, node_id) **Crate:** `montana-node` **File:line:** `crates/montana-node/src/registration.rs:11` **Spec section:** «Шаг 2: Кандидатура» / «[I-8] compliance» **Spec quote:** «`candidate_vdf_init = SHA-256("mt-candidate-vdf-init" || timechain_value(W_start) || cemented_bundle_aggregate(W_start - 2) || node_id)`» **Что делает код:** `candidate_vdf_init(&[0u8; 32], &[0u8; 32], &node_id)` — timechain_value и cba как zeros (placeholder) **Severity:** блокер mainnet ([I-8] violation — нет canonical unpredictable-offline binding) **Closure path:** на момент формирования NodeRegistration использовать **реальные** `timechain.t_r` и `cemented_bundle_aggregate(W_start - 2, &cemented_node_ids_at_W_start_minus_2)` из локального state узла **Closure cost:** ~1 час кода после DEV-001 closure **Status:** закрыто (commit `fb204ef` mt-local-node: byte-exact rewrite через canonical apply_proposal) --- ## DEV-003: Лотерея отсутствует — winner = первый node по lex **Crate:** `montana-node` **File:line:** `crates/montana-node/src/commands/start.rs:104-120`, `commands/advance.rs` аналогично **Spec section:** «Лотерея» / «Победитель τ₁» **Spec quote:** «winner = `argmin(weighted_ticket_node)` среди cemented `VDF_Reveal` узлов-кандидатов; `weighted_ticket_node = ln_q64(endpoint) / lottery_weight`» **Что делает код:** `state.nodes.iter().next()` — первый узел по `node_id` lex order, **без формирования VDF_Reveal, без endpoint, без weighted_ticket** **Severity:** блокер mainnet (consensus-critical логика игнорирована, [I-8] violation) **Closure path:** реализовать per окно: формирование `VDF_Reveal` (`mt_lottery::VdfReveal`) с `endpoint = SHA-256("mt-lottery" || T_r || cba || node_id || W LE)`, подпись `node_sk`; вычисление `weighted_ticket_node` через `mt_lottery::weighted_ticket_node`; для singleton — единственный кандидат — argmin тривиален и corretct **через каноничный API** **Closure cost:** ~6 часов кода **Status:** закрыто (commit `fb204ef` mt-local-node: byte-exact rewrite через canonical apply_proposal) --- ## DEV-004: BundledConfirmation никогда не формируется **Crate:** `montana-node` **File:line:** `crates/montana-node/src/commands/start.rs:113-117` **Spec section:** «Confirmer threshold» / «BundledConfirmation» / «apply_proposal Step 3.5» **Spec quote:** «chain_length инкрементируется при cemented `BundledConfirmation`», quorum = `(67 × X + 99) / 100` от active_chain_length **Что делает код:** `chain_length += 1` напрямую без формирования BC, без подписи `op_hashes/reveal_hashes`, без quorum cementing **Severity:** блокер mainnet (chain_length грубо инкрементируется на основании несуществующего правила) **Closure path:** формирование `mt_lottery::BundledConfirmation` с `op_hashes[]` (от Account Table cemented operations) + `reveal_hashes[]` (от cemented VDF_Reveal предыдущего окна) + подпись `node_sk`; cementing через quorum (для singleton — сам себе 100%, проверка через `mt_lottery::is_cemented`) **Closure cost:** ~8 часов кода **Status:** закрыто (commit `fb204ef` mt-local-node: byte-exact rewrite через canonical apply_proposal) --- ## DEV-005: ProposalHeader не формируется, Step 4 apply_proposal обойдён **Crate:** `montana-node` **File:line:** `crates/montana-node/src/commands/start.rs:102-128` **Spec section:** «Proposal header» / «Canonical acceptance» / «apply_proposal Step 4» **Spec quote:** «winner формирует `ProposalHeader` (1080 байт) с `included_bundles + included_reveals + state_root`, подписывает, archive-ит. Validator пересчитывает state_root и сверяет.» **Что делает код:** напрямую `account.balance += 13_000_000_000` минуя `apply_emission`; ProposalHeader не формируется, `archive_proposal` не вызывается **Severity:** блокер mainnet (full Step 4 apply_proposal обойдён) **Closure path:** реализовать формирование `mt_consensus::ProposalHeader` с правильными полями (`canonical_proposer`, `included_bundles`, `included_reveals`, `state_root`), `validate_acceptance`, эмиссия через `mt_account::apply_proposal`, `mt_store::archive_proposal` **Closure cost:** ~12 часов кода **Status:** закрыто (commit `fb204ef` mt-local-node: byte-exact rewrite через canonical apply_proposal) --- ## DEV-006: state_root не cross-check между proposer и validator **Crate:** `montana-node` **File:line:** N/A (отсутствие кода) **Spec section:** «Верификация» / «Финальность proposal» **Spec quote:** «Финальность proposal — подпись `proposer_node_id` на proposal header. Верификация — независимый пересчёт state_root.» **Что делает код:** state_root recompute не существует. Singleton mode — узел сам себе proposer и validator, но cross-check всё равно необходим для регулярного self-verification (защита от corruption диска / памяти) **Severity:** средний (singleton не имеет 2 узлов для cross-check, но self-verification обязателен) **Closure path:** после формирования `ProposalHeader.state_root`, повторно вычислить `compute_state_root(account_root, node_root, candidate_root)` независимо и сверить byte-exact; mismatch → panic (corruption detected) **Closure cost:** ~1 час кода **Status:** закрыто (commit `fb204ef` mt-local-node: byte-exact rewrite через canonical apply_proposal) --- ## DEV-007: next_d не вызывается на τ₂ boundary **Crate:** `montana-node` **File:line:** `crates/montana-node/src/commands/start.rs:95-160` **Spec section:** «Адаптация D через participation-ratio feedback» **Spec quote:** «D адаптируется на границе τ₂ через каноническое chain observation» **Что делает код:** `timechain.current_d` фиксируется на `D₀=252M`, `next_d` не вызывается **Severity:** блокер mainnet для long-running узла (>14 дней) **Closure path:** хранить `participation_history: Vec` (permille per окно) в timechain state; на каждой τ₂ boundary вычислить median + `next_d(current_d, median, params)`; обновить `timechain.current_d`; для singleton: participation_ratio = всегда 1000 → median=1000 → каждые τ₂ D × 1.03 **Closure cost:** ~3 часа кода **Status:** закрыто (commit `fb204ef` mt-local-node: byte-exact rewrite через canonical apply_proposal) --- ## DEV-008: selection_event с zeros в advance.rs vs реальный T_r в start.rs **Crate:** `montana-node` **File:line:** `crates/montana-node/src/commands/advance.rs:55-72` **Spec section:** «Selection event sort_key» **Spec quote:** «`sort_key(c) = SHA-256("mt-selection" || timechain_value(W) || cemented_bundle_aggregate(W-2) || c.node_id)`» **Что делает код:** `let placeholder = [0u8; 32]` для обоих `t_r` и `cba`. Silent divergence между моими же командами — `start.rs` использует реальный `timechain.t_r`, `advance.rs` использует zeros. Один и тот же state, разные seeds → разные ranking → разные winners для multi-candidate. **Severity:** блокер mainnet (silent divergence между путями исполнения) **Closure path:** удалить `advance.rs` целиком — для byte-exact spec нет «быстрой симуляции», есть только реальное исполнение **Closure cost:** ~10 минут (удаление файла + dispatch update) **Status:** закрыто (commit `fb204ef` mt-local-node: byte-exact rewrite через canonical apply_proposal) --- ## DEV-009: apply_proposal целиком обойдён — все steps реализованы ручным insert/update **Crate:** `montana-node` **File:line:** `crates/montana-node/src/commands/start.rs:95-160`, `advance.rs:45-95` **Spec section:** «State transition → apply_proposal» **Spec quote:** «Steps 1, 2, 3a, 3b, 4 в каноническом порядке» **Что делает код:** напрямую модифицирует `AccountTable`/`NodeTable`/`CandidatePool` вне любого `apply_proposal`. Каждое окно — это ad-hoc набор shortcut'ов, не каноническая state transition. **Severity:** блокер mainnet (silent divergence реализации от спеки на per-окно basis) **Closure path:** заменить ad-hoc на canonical `apply_proposal` pipeline через `mt_account::apply_proposal(&mut account_table, &mut node_table, &mut candidate_pool, &proposal_input, params)`. Singleton mode формирует валидный `ProposalInput` для каждого окна и вызывает canonical pipeline. **Closure cost:** ~16 часов кода (зависит от DEV-001..DEV-006) **Status:** закрыто (commit `fb204ef` mt-local-node: byte-exact rewrite через canonical apply_proposal) --- ## DEV-011: hardware calibration initial D под target window time **Crate:** `montana-node` **File:line:** `crates/montana-node/src/commands/start.rs` функция `calibrate_d_for_target_window` + первый запуск start **Spec section:** «Двигатели → TimeChain VDF — осциллятор», «Калибровка D₀» **Spec quote:** «Mainnet calibration `D₀` нацелена на τ₁ ≈ 60 секунд wall-clock на median commodity hardware (engineering target, не protocol invariant)» **Что делает код:** при первом запуске узла (timechain.bin не существовал) запускает benchmark vdf_step(zeros, 10M) → измеряет hardware SHA-256 rate → calibrate `current_d` так чтобы окно ≈ 60 сек wall-clock на этой машине. **Что в спеке:** spec `D₀ = 252M` — engineering calibration target для median commodity. Per-узел actual wall-clock varies ×20 (Apple Silicon ~53s, idle x86_64 VPS ~68s, loaded ~1145s). Adaptive D feedback на τ₂ boundary автоматически подстраивает D под median network rate. **Severity:** косметический — D в genesis узле = local state, не shared consensus invariant с другими узлами (других нет). При появлении новых узлов сети их D будет calibrated самостоятельно либо synchronized через canonical params.d0. **Closure path:** при finalize multi-node M6+ — узлы будут sync через canonical D из Genesis Decree либо negotiate через network consensus. Hardware calibration остаётся для genesis узла как initial value. **Closure cost:** acknowledged как permanent feature для genesis узла, не требует closure **Status:** acknowledged (genesis-узел local hardware calibration — explicit operator choice, не silent shortcut) **Acknowledged:** автор 2026-04-28 «сделай так чтобы мой узел генерил примерно в 60 секунд окно» --- ## DEV-010: genesis bootstrap mode без Candidate VDF (auto-detected) **Crate:** `montana-node` **File:line:** `crates/montana-node/src/state.rs:32-66` (LocalState::bootstrap), `crates/montana-node/src/node_lifecycle.rs:48-92` (NodeLifecycle::fresh_for + is_bootstrap_node), `crates/montana-node/src/commands/start.rs:74-93` (Bootstrap → CandidateVdf transition) **Spec section:** «Genesis Decree» / «bootstrap_node_pubkey» / «Активация узла» **Spec quote:** «`bootstrap_node_pubkey: [u8; PUBLIC_KEY_SIZE]` в `protocol_params` — первый узел сети активируется через genesis state, не через Candidate VDF + selection event цикл» **Что делает код:** автоматическое определение genesis vs candidate per spec: - `NodeLifecycle::is_bootstrap_node(identity, params)` сравнивает `identity.node_pk` с `params.bootstrap_node_pubkey` byte-by-byte - Если `bootstrap_node_pubkey == [0u8; PUBLIC_KEY_SIZE]` (placeholder pre-Genesis-ceremony) — **любой** узел трактуется как genesis (singleton legacy mode для M5 development phase). Эта ветка перестаёт применяться после Genesis ceremony когда `bootstrap_node_pubkey` финализирован конкретным значением - Если `bootstrap_node_pubkey` finalized + `identity.node_pk` совпадает — genesis path: phase=Active immediately, NodeRecord для self в NodeTable - Если `bootstrap_node_pubkey` finalized + `identity.node_pk` НЕ совпадает — standard candidate path: phase=Bootstrap → CandidateVdf на первом окне → Registered (через apply_noderegistrations_batch когда vdf_chain_length ≥ τ₂) → Active (через apply_selection_event на ближайшем W % selection_interval == 0). Узел НЕ появляется в NodeTable bootstrap state — добавляется только через canonical apply_selection_event. **Severity:** acknowledged feature pre-Genesis-ceremony; production-ready после ceremony (auto-detection через canonical apply_proposal pipeline для не-bootstrap узлов работает byte-exact spec). **Closure path:** Genesis ceremony — установить `params.bootstrap_node_pubkey` в реальное значение. После этого DEV-010 закрывается автоматически: проверка `is_bootstrap_node` определит ровно один genesis узел, остальные пройдут стандартный candidate path. **Closure cost:** 0 после Genesis ceremony (код уже implements auto-detection) **Status:** acknowledged (auto-detection в коде, pre-ceremony placeholder активирует singleton legacy ветку для M5; post-ceremony — production spec compliance) **Acknowledged:** автор 2026-04-28 — «у нас автоматически определяется genesis узел с условиями и остальные?» → fix v1.15.0 [C-13] enforcement: правильный путь немедленно, без вопроса автору --- ## История | Версия роли | Дата | Действие | |---|---|---| | v1.13.0 | 2026-04-28 | Файл создан. Открыты DEV-001..DEV-009 для `montana-node` Этапы 1-5. Решение автора: byte-exact rewrite. | | v1.13.0 | 2026-04-28 | DEV-001..DEV-009 закрыты через canonical apply_proposal pipeline rewrite. | | v1.13.0 | 2026-04-28 | DEV-010 added: genesis bootstrap mode (узел стартует Active без Candidate VDF) — explicit acknowledged deviation, decision автора. |