Files
plm-lottery/docs/processo-di-modifica.md
davideandClaude Sonnet 5.5 db24287ea5 Make the draw a commit-reveal at a fixed, confirmed, quorum-agreed height with a public proof (B-73, RND-14 … RND-20, RND-33 … RND-37, DAT-61 … DAT-66, API-25, API-45, AUD-24 … AUD-27, AUD-37, ELC-15, SEC-56, ADR-24, ADR-25)
The seed was the hash of the first block seen after the close, so a miner holding tickets
could withhold blocks until one made them win, the active Electrum server could pick which
real block was used by not announcing another (a failed corroboration even moved the draw to
the next block), and the participant order was the server-only `broadcast_at` with no
tie-break, so nobody could recompute the result.

- Open: every round gets a 32-byte `server_seed`, stored only Fernet-encrypted with the
  master-xprv key (`server_seed_enc`), with its published `server_seed_hash`;
  `draw_confirmations` is copied from round_config. The plaintext column is written only by
  the reveal.
- Entering `drawing` (one transaction, `draw_committed` audit row): participants ordered by
  bet txid, each `draw_position`, `participants_hash` (sha256 of `txid:vout:address\n`
  lines; new `bet_vout`), `draw_height = tip_at_close + 1`. A tip of 0 keeps the round
  `closing` (`draw_baseline_tip_unknown`, once per round). All persisted, so a resumed draw
  reads rather than recomputes (the `_tick` branch itself is B-81).
- `_complete_draw` / `_wait_for_draw_block` replace `_wait_for_next_block`: wait for
  `draw_confirmations` blocks from `draw_height`, then `ElectrumListener.agreed_block_hash`
  asks every server, the active one included, for the header at that exact height (valid
  work, >= 2 other answers, strict majority of all); no agreement means the same height is
  asked again and `draw_header_corroboration_failed` is written once per distinct reported
  hash. `draw_stalled` now counts from the last tip advance, or from when the quorum started
  to fail. Seed = sha256(server_seed || block_hash || participants_hash), index = seed mod n.
- The winner, the seed and the reveal (`server_seed`, `winner_drawn` payload) are written in
  one transaction; a round closing with no participants reveals too. A round opened before
  this change (no commitment) is given a secret as it enters `drawing`.
- `GET /rounds/{id}/proof` (public, 404 `round_not_found`, translated in all 7 languages) and
  the new fields on `GET /rounds/current`; `draw_confirmations` in `/admin/config` and the
  /admin form; `server_seed_hash`/`draw_height` in `/admin/rounds`, never the secret.
- Alembic d3e8b15a7c42 (plain add_column / create_index, expression indexes untouched).
- Docs: the B-73 `*Stato:*`/`*Oggi:*` lines are gone, guides and CLAUDE.md describe the new
  draw. Ambiguities resolved in api.md: the proof's participants also carry `amount_sats`
  (RND-37 lists it); `seed` is decimal in the proof and hex in the audit row, as specified.

Tests changed because the spec changed: ElectrumListener.corroborate_header (asked only the
other servers about the block the active one announced) is replaced by agreed_block_hash and
its quorum tests ported; draw_winner takes the secret and the list fingerprint; the four
_wait_for_next_block scheduler tests are replaced by _wait_for_draw_block ones (fixed height,
confirmations, same-height retry, stall clock). New: test_draw_proof.py recomputes the winner
independently from the public proof, checks the plaintext secret is nowhere in the database,
the API or the log before the reveal, and that a second pass over a `drawing` round yields
the same winner. Mutation-checked: ordering by broadcast_at, an early reveal and asking the
tip instead of the draw height each fail tests.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 16:29:09 +02:00

154 lines
8.1 KiB
Markdown

# Processo di modifica: dalla documentazione al codice
L'indice di tutta la documentazione è [README.md](README.md). Questo documento
ne fissa le regole di lavoro.
## Principi: docs-first, SSOT, ADR
- **Docs-first.** Si cambia prima la documentazione, poi il codice; spec,
codice e test stanno nello stesso commit.
- **SSOT.** Ogni informazione ha un solo proprietario e gli altri documenti
rimandano a lei per ID senza ripeterla:
| Informazione | Proprietario |
|---|---|
| Perché una scelta (contesto, alternative, conseguenze) | un ADR in [adr/](adr/README.md) |
| Che cosa deve fare il software (regola verificabile) | il requisito `PREFISSO-NN` del documento di dominio |
| Schema, stati, vincoli | `DAT` in [modello-dati.md](modello-dati.md) |
| Soglie e rischi di sicurezza | `SEC` in [sicurezza.md](sicurezza.md) |
| Eventi di audit | `AUD` in [audit-log.md](audit-log.md) |
| Come lo fa | il codice (riferimento `percorso:funzione`) |
| Limiti accettati | `LIM` in [limiti-noti.md](limiti-noti.md) |
| Difetti trovati e corretti | [storico-bug.md](storico-bug.md) |
- **Un ADR per ogni funzionalità.** Una funzionalità nuova, o un cambio di
scelta in una esistente, parte da un ADR scritto da [adr/TEMPLATE.md](adr/TEMPLATE.md):
prossimo numero libero, stato *Proposta* finché non è approvato, poi
*Accettata*. L'ADR elenca i requisiti che lo attuano; i requisiti citano
l'ADR. Un ADR non si riscrive: se la scelta cambia se ne scrive uno nuovo e
il vecchio passa a *Sostituita da ADR-MM*. Un nuovo ADR va aggiunto
all'indice per area e alla tabella funzionalità → ADR di
[adr/README.md](adr/README.md).
La cartella `docs/` è la **specifica** di PLM Lottery. Descrive cosa il
software fa e perché; il codice è l'implementazione di questa specifica.
Ogni modifica di comportamento parte da qui.
## Convenzione dei requisiti
Ogni regola verificabile ha un **identificativo stabile**, nella forma
`PREFISSO-NN`, scritto in grassetto all'inizio del punto che la descrive,
seguito dai riferimenti all'implementazione e, dove esistono, ai test:
```markdown
- **[RND-11]** Al primo passo dello scheduler successivo alla scadenza il round
passa a `closing`; le scommesse sono però rifiutate già dall'istante della
scadenza ([BET-04]).
*Codice:* `app/rounds/scheduler.py:RoundScheduler._tick` ·
*Test:* `tests/unit/test_scheduler.py`
```
Regole della convenzione:
- **Un identificativo non si riusa mai.** Un requisito eliminato resta nel
documento come `~~[RND-11]~~ Rimosso: <motivo>`, così i riferimenti già
scritti (commenti, commit, test) non puntano a una regola diversa.
- **I numeri non si rinumerano.** Un requisito nuovo prende il numero
successivo al più alto del suo prefisso, anche se finisce in mezzo al testo.
- **I riferimenti al codice** sono `percorso:funzione` o `percorso:Classe.metodo`,
mai numeri di riga (cambiano a ogni modifica).
- Il testo descrittivo che non contiene regole (introduzioni, spiegazioni,
esempi) non ha identificativo.
- **Un solo proprietario per regola.** Ogni regola ha l'identificativo in un
solo documento; gli altri documenti che la citano rimandano a quell'ID
invece di ripeterla con un ID proprio. Il documento di dominio (round,
scommesse, transazioni…) è proprietario del comportamento, `DAT` dello
schema, `SEC` delle soglie di sicurezza.
- **Niente formulazioni vaghe nelle regole.** "Circa", "normalmente", "prima o
poi" non compaiono in un requisito: si scrive la condizione esatta, con il
nome della costante.
### Requisiti non ancora implementati
La specifica può essere più avanti del codice: è il caso normale fra il passo 1
e il passo 3 del ciclo qui sotto. Un requisito che il codice non soddisfa
ancora porta, subito dopo i riferimenti, una riga di stato e una che dice cosa
fa il codice oggi:
```markdown
- **[RND-33]** L'altezza del blocco di estrazione è fissata all'ingresso in
`drawing` come `tip_at_close + 1` e non cambia più.
*Codice:* `app/rounds/scheduler.py:RoundScheduler._close_and_draw` ·
*Test:* —
*Stato:* da implementare (B-nn) · *Oggi:* si usa la punta vista al primo
controllo utile, che può essere un blocco successivo.
```
- *Codice* indica dove andrà l'implementazione (il file o la funzione
esistente più vicina); *Test* è `—` finché il test non esiste.
- Il B-nn rimanda alla voce di [BUGS.md](../BUGS.md) che descrive la
correzione; l'ordine di implementazione è in
[piano-correzioni.md](piano-correzioni.md).
- Quando il codice viene implementato, nello stesso commit si tolgono le righe
*Stato* e *Oggi* e si completano *Codice* e *Test*. `git grep "da
implementare"` elenca in ogni momento tutto ciò che manca.
- Un requisito modificato (non nuovo) mantiene il suo identificativo: il testo
descrive il comportamento atteso, *Oggi* quello attuale.
### Prefissi
| Prefisso | Documento | Ambito |
|---|---|---|
| `ARC` | [architettura.md](architettura.md) | Struttura del codice, processi, frontend, test |
| `DAT` | [modello-dati.md](modello-dati.md) | Tabelle, campi, stati, vincoli |
| `API` | [api.md](api.md) | Endpoint, formato delle risposte, codici di errore |
| `SEC` | [sicurezza.md](sicurezza.md) | Chiavi, autenticazione, limiti, rischi accettati |
| `REG` | [registrazione-e-depositi.md](registrazione-e-depositi.md) | Registrazione e account |
| `DEP` | [registrazione-e-depositi.md](registrazione-e-depositi.md) | Depositi e saldo |
| `BET` | [scommesse-e-prelievi.md](scommesse-e-prelievi.md) | Scommesse |
| `WDR` | [scommesse-e-prelievi.md](scommesse-e-prelievi.md) | Prelievi |
| `RND` | [ciclo-di-vita-del-round.md](ciclo-di-vita-del-round.md) | Round, estrazione, pagamento |
| `TX` | [transazioni-e-riconciliazione.md](transazioni-e-riconciliazione.md) | Ciclo di vita delle transazioni, RBF, riconciliazione |
| `ELC` | [connessione-electrum.md](connessione-electrum.md) | Connessione alla rete |
| `AUD` | [audit-log.md](audit-log.md) | Eventi del registro di audit |
| `OPS` | [operativita.md](operativita.md) | Procedure operative |
| `ADR` | [adr/](adr/README.md) | Decisioni di progetto (un file per decisione) |
| `LIM` | [limiti-noti.md](limiti-noti.md) | Limiti noti accettati |
## Il ciclo di una modifica
0. **Scegliere.** Se la modifica è una funzionalità o una scelta nuova, si
scrive o aggiorna l'ADR (e si approva) prima di toccare i requisiti.
1. **Modificare la specifica.** Si cambia il testo del requisito in `docs/`
(o se ne aggiunge uno nuovo con il prossimo numero libero). Se la modifica
tocca una decisione (`ADR-*`) o un limite (`LIM-*`), si aggiorna anche
quello; se introduce una funzionalità o una scelta nuova, si scrive prima
il relativo ADR (vedi sotto).
2. **Individuare l'impatto.** I riferimenti *Codice* e *Test* del requisito
dicono dove intervenire. Si cercano anche le altre occorrenze
dell'identificativo nel repository (`git grep RND-11`) e i requisiti di
altri documenti che dipendono dallo stesso codice.
3. **Implementare e testare.** Si modifica il codice e si aggiunge o aggiorna
un test che verifica il nuovo comportamento. Il test cita l'identificativo
nella sua docstring.
4. **Allineare i riferimenti.** Se il codice si è spostato (funzione
rinominata, file diviso), si aggiornano i riferimenti *Codice* del
requisito.
5. **Commit unico.** Specifica, codice e test entrano nello stesso commit,
con l'identificativo nel messaggio (ad esempio
`Close the round on its own deadline (RND-11)`).
Una correzione di bug che non cambia la specifica (il codice non faceva ciò
che il documento già diceva) segue la convenzione `B-nn` descritta in
[storico-bug.md](storico-bug.md): numero successivo, commit dedicato, test di
regressione. Se invece il bug rivela che la specifica era sbagliata o
incompleta, si corregge prima la specifica, come per qualunque altra modifica.
## Documenti della specifica
Il punto d'ingresso è [come-funziona.md](come-funziona.md). Le guide
operative ([setup.md](setup.md), [running-the-server.md](running-the-server.md),
[guida-utente.md](guida-utente.md), [guida-admin.md](guida-admin.md)) non
contengono requisiti: descrivono come usare il software, e si aggiornano
quando la specifica cambia qualcosa di visibile.