polish(gov+attestation): test de empate en finalize + ancla SSOT de attestation

Polish pass MEDIUM/LOW post-audit (2 items limpios + triage de los invasivos):

governance — cobertura del desempate (consenso-relevante, sin cobertura previa):
- scenario_8b_finalize_tie_rejected_canonical: yes==nay ⇒ `yes > nay` es false ⇒
  Rejected. Fija que se exige MAYORÍA ESTRICTA (el empate favorece el status quo).
  Entre scenario_7 (yes-majority→Approved) y scenario_8 (nay-majority→Rejected)
  faltaba justo el borde del empate.

origin-chain-types — SSOT de attestation ahora que el crate aterrizó:
- `arkhe-noesis-attestation-types` ya existe (crate standalone no_std + feature scale
  en el monorepo). El vendor-copy SE QUEDA como type-mirror deliberado: el chain es
  submódulo y depender del crate del monorepo invierte la dep-direction
  (submódulo→monorepo = ILEGAL). La SSOT se preserva por los golden vectors del
  oráculo real (decode_golden_vector_1/3/4), no por Cargo.
- attestation_verify: §SSOT parity anchor doc — documenta que esos vectores pinnean
  el decoder al field-order de `SigningPayload` y cómo regenerar ante deriva.
- attestation.rs: comentario del mirror actualizado (el crate aterrizó; diferencia
  conocida schema_version [u8;8] vs String es irrelevante — el mirror no serializa
  el wire; el decoder secuencial sí es byte-fiel al postcard, probado por vectores).

Triage documentado (items considerados y DIFERIDOS con rationale, no olvidados):
- register_agent_did unbounded Vec: bound del arg a ConstU32<10> movería el rechazo
  a decode-time, volvería AgentChainOverflow inalcanzable + rompería el test de
  overflow + exigiría spec bump — invasivo para un DoS ya mitigado por length-fee +
  límite de bloque. Diferido.
- SS58 prefix (42): NO es bug — placeholder testnet correcto, gated en un DC de
  ss58-registry pre-mainnet. Sin cambio.
- no-author path (emission): requiere un FindAuthor mock que devuelva None (segundo
  runtime o toggle) — ripple > valor. Diferido.

Verificado: origin-chain-types 77 tests verde (incl. golden parity) + tie test verde.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
davidherranandClaude Opus 4.8 committed 2026-07-07 22:57:44 -05:00
1 parent c7fc6a88fc
commit 266ee4084d
3 files changed
+60 -2

No files matched your search

@@ -222,6 +222,37 @@ fn scenario_8_finalize_rejected_nay_majority_canonical() {
});
}
// ─── Scenario 8b: Finalize con EMPATE (yes == nay) → Rejected ────────
// Polish audit: documenta la semántica del desempate. `finalize_proposal` usa
// `yes_weight > nay_weight` (mayoría ESTRICTA); un empate NO alcanza el umbral
// y la propuesta se RECHAZA. Esto es intencional (una propuesta necesita más
// síes que noes para pasar; el empate favorece el status quo). El gate estaba
// sin cobertura — este test lo fija como comportamiento consensuado.
#[test]
fn scenario_8b_finalize_tie_rejected_canonical() {
new_test_ext().execute_with(|| {
let id = submit_default(alice_origin());
// EMPATE exacto: Aye 500 == Nay 500.
assert_ok!(Governance::cast_vote(bob_origin(), id, VoteStance::Aye, 500));
assert_ok!(Governance::cast_vote(charlie_origin(), id, VoteStance::Nay, 500));
run_to_block(200);
assert_ok!(Governance::finalize_proposal(alice_origin(), id));
let proposal = Proposals::<Test>::get(id).unwrap();
assert_eq!(proposal.yes_weight, 500);
assert_eq!(proposal.nay_weight, 500);
// yes(500) > nay(500) es FALSE ⇒ Rejected (mayoría estricta requerida).
assert!(
matches!(proposal.status, ProposalStatus::Rejected),
"empate debe rechazar: se exige mayoría estricta"
);
});
}
// ─── Scenario 9: Vote closed period rejected canonical ──────────────
#[test]