Skip to content

Il freno dei check sparava sempre la valvola: era un timer, non un freno - #88

Merged
zorahrel merged 11 commits into
mainfrom
topics/pavimento-check-dentro-il-verdetto-swap
Sep 17, 2026
Merged

zorahrel merged 11 commits into
mainfrom
topics/pavimento-check-dentro-il-verdetto-swap

Conversation

@zorahrel

Copy link
Copy Markdown
Contributor

Change approvata: openspec/changes/coda-che-riparte-da-sola (KANBAN-15 modificato).

Il numero

Misurato sulla prima consegna passata dalle righe CI, leggendo i ms di checks_json: typecheck 12,7 s, lint 55,3 s, deadcode 4,4 s, static-rails 7,8 s. Ottanta secondi di esecuzione dentro un giro durato trentadue minuti, con [memsig] che scriveva swap=calm per tutta la durata e la lettura fra 4,8 e 5,9 GB contro una riga a 6. Il giro e' uscito dalla valvola dei trenta minuti, non dalla condizione.

Cosa cambia, e cosa NON cambia

Il pavimento resta, a swap calmo come a swap sostenuto. Cambia la valvola: tre minuti quando lo swap e' calmo, i trenta di oggi quando e' sostenuto. Sotto thrash aspettare serve davvero, la macchina sta restituendo memoria; a swap calmo la lettura migliora solo se una persona libera qualcosa a mano, e in un tratto continuo di 16,2 ore non ci e' mai arrivata.

E i due orologi sono separati: il tempo speso ad aspettare lo SWAP non conta contro la valvola del PAVIMENTO. Senza questa separazione un episodio di thrash regalava l'esenzione per tutto il resto del giro — sostenuto per dieci minuti, poi calmo con la memoria immutata, e il comando partiva all'istante.

La riga del fail-open adesso dice la condizione vera e il tempo vero: «no room after 30 min» su un Mac che ha scambiato per mezz'ora era l'unica traccia che sopravvive, ed era falsa in entrambe le meta'.

La prima stesura era sbagliata, e la correzione e' il punto

Avevo scritto «a swap calmo il pavimento non trattiene, a swap sostenuto vale pieno». Una verifica avversaria l'ha smentita eseguendola: sotto swap sostenuto il freno usciva gia' prima di guardare il pavimento, quindi l'unico momento in cui il pavimento aveva forza era proprio lo swap calmo. Quella regola non lo indeboliva, lo cancellava — provato su 160 stati, floorGB a 0 contro floorGB a 1000 identici in tutti. E serve ancora a qualcuno: e' montato una volta per tutto il server, non per board, e dancerooms-intq6i ha come unico check locale una suite unit, cioe' l'albero da 4-11 GB che il freno esiste per fermare.

Adesso il pavimento e' di nuovo osservabile: 126 stati su 704 differiscono fra floorGB 0 e 1000, tutti a swap calmo.

Verifica

Barra intera: 18 rail su 18 a zero, typecheck 0, lint 0. Venti mutazioni oltre a quelle dichiarate; le sopravvissute sono mutanti equivalenti, verificati uno per uno. Una guardia che il giro prima non era coperta — l'ordine spacing prima di room, che adesso decide il budget e non piu' solo l'etichetta del log — ora muore sotto la sua mutazione: senza di lei un comando parte attraverso la spaziatura dei 120 s, che e' la mandria del 15/09.

Il freno davanti a un comando di check confrontava il minimo su 2 minuti con un
pavimento di 6 GB che su questa macchina non si raggiunge mai: held2m >= 6 GB
zero volte su 1455 letture in 25,7 ore. Cosi' non frenava, scadeva.

Misurato sulla card c4f53a85, il primo giro che passa dalle righe CI (16/09
23:41:29Z - 17/09 00:26Z): 1800 s di attesa, cioe' l'intero budget del giro,
contro circa 195 s di esecuzione dei quattro comandi locali - 9,2 volte tanto.
typecheck rilasciato dopo 372 s, check:deadcode e static-rails solo dalla
valvola dei 30 minuti, ed e' per questo che la stessa riga "no room after 30
min" nomina due comandi di fila: spentMs e' per GIRO, il secondo non ha
aspettato. Sui 46 campioni [memsig] di quel giro held2m era sotto 6 GB in 43, e
41 di quei 43 leggevano swap=calm.

Ora il pavimento e' letto dentro il ramo sostenuto, dove il giro e' trattenuto
comunque e i GB dicono quanto e' profondo; a verdetto calmo una lettura sotto il
pavimento fa partire il comando. Restano invariati: la valvola per giro, la
spaziatura di 120 s fra giri (la mandria del 15/09), il turno, il non partire su
una lettura sola e il non aspettare mai dove la memoria non si misura.

Conseguenza da dire: su un Mac calmo la finestra di 2 minuti non decide piu' un
rilascio, decide solo la riga di log dentro un episodio di swap. I test che
provavano il pavimento da solo sono stati riportati sul verdetto sostenuto.
La premessa diceva "[memsig] scriveva swap=calm per tutta la durata". Non e'
esatto: 3 campioni su 46 sono sostenuti, e durante l'attesa di check:deadcode la
ragione ha alternato 5 volte fra swap e pavimento. La conclusione regge lo
stesso - basta un poll calmo sotto il pavimento per rilasciare, e i poll calmi
erano 41 dei 43 sotto il pavimento - ma il numero va scritto com'e', non come
faceva comodo.
# Conflicts:
#	openspec/changes/coda-che-riparte-da-sola/tasks.md
La regola che avevo scritto - «a swap calmo il pavimento non trattiene» - non
indeboliva il pavimento, lo cancellava: sotto swap sostenuto `releaseDecision`
usciva gia' prima di leggerlo, quindi calmo era l'unico stato in cui decidesse
qualcosa. Provato su 160 stati, `floorGB` 0 e 1000 davano la stessa decisione in
tutti. E il pavimento serve ancora: e' montato una volta per tutto il server,
non per board, e `dancerooms-intq6i` ha come unico check locale una suite unit -
proprio l'albero da 4-11 GB che il freno esiste per tenere fuori da un Mac
vuoto; su topics-app non lo prenderebbe nemmeno `createSwapBrake`, che vuole
alberi da 1 GB mentre tsc ne tiene 460 MB.

Quindi il pavimento resta com'era e cambia il BUDGET: tre minuti quando a
trattenere e' il pavimento su un Mac calmo, i trenta di sempre sotto swap
sostenuto. Sotto thrash aspettare compra memoria davvero; a swap calmo la
lettura non migliora da sola (`held2m >= 6 GB` zero volte su 1455 letture in
25,7 ore), quindi trenta minuti e tre finiscono uguali tranne che per
ventisette. Tre e non meno perche' `swapVerdict` pretende un campione base
vecchio 60 s e la porta del soffitto un'altra finestra prima: un thrash che
nasce dopo un rilascio resta invisibile per 60-120 s. La valvola corta vale solo
sul pavimento: spaziatura e turno si sciolgono da soli entro 120 s e tagliarli
rifarebbe la mandria del 15/09.

La riga del fail-open ora nomina la condizione letta in quell'istante invece
dell'ultima che quel comando aveva stampato: per un comando che non ha aspettato
nemmeno un poll quella era nessuna, ed e' il motivo per cui il 17/09 il log
diceva «no room after 30 min» due volte su un Mac in swap.
… giro

Le due valvole spendevano un contatore solo. Un giro fermo dieci minuti sotto
swap sostenuto, con la lettura immobile a 5,2 GB sotto un pavimento di 6,
partiva nell'istante in cui il verdetto tornava calmo: i dieci minuti spesi
sullo swap avevano gia' coperto la valvola da tre e il pavimento non l'aveva
trattenuto per un secondo. Il freno premiava chi aveva appena fatto annaspare
la macchina. Adesso ogni budget ha il suo orologio e ogni tratto d'attesa e'
addebitato alla condizione in vigore mentre passava, non a quella su cui
l'attesa finisce: lo stesso caso parte al tredicesimo minuto.

E la riga del fail-open diceva la condizione giusta con la durata sbagliata,
perche' stampava il budget del giro al posto dell'attesa: «dopo 3 minuti» per
tre comandi che avevano atteso tre, zero e zero. Il log degli errori non ha
timestamp, quindi quella riga e' l'unica traccia che resta del giro: ora dice
quanto ha atteso QUEL comando e, accanto, quanto il giro aveva speso di quel
budget.

Tolto `calmMaxWaitMs`, dichiarato seam di test e senza un solo call site.
Corretto il numero che reggeva la valvola: «held2m >= 6 GB mai su 1455 letture»
e' falso sul log di oggi (241 su 1553, il 15,5%). Quello che regge la valvola e'
il tratto continuo sotto il pavimento, 854 campioni, circa quattordici ore.
…tipo

`testo.match(EMAIL) ?? []` e' `RegExpMatchArray | never[]`, quindi il parametro
del filter diventa `string & never` e `a.toLowerCase()` non compila. Il file
arriva da main intatto — nessun ramo lo tocca — ma blocca `bun run typecheck`
per chiunque ci rebasi sopra, e la consegna successiva se lo porta in referto
come proprio rosso.
# Conflicts:
#	openspec/changes/coda-che-riparte-da-sola/specs/kanban/spec.md
Su main le due condizioni pescavano dallo stesso `spentMs`, quindi spostare
`room` prima di `spacing` dentro `holdReason` cambiava solo la parola scritta nel
log. Con un orologio per valvola quell'ordine sceglie quale valvola misura lo
stato, e la mutazione passava: 29 pass / 0 fail, suite intera verde. Il
comportamento pero' cambiava dove fa male: un giro sotto il pavimento mentre gira
il comando di un ALTRO giro passa da `heldBy: "spacing", budgetMs: 1800000` a
`heldBy: "room", budgetMs: 180000`, e con la valvola calma gia' spesa - che e'
esattamente come ci arriva, dopo aver aspettato il pavimento - parte ATTRAVERSO i
120 secondi di spaziatura. Quella e' la mandria del 15/09, quattro comandi di
consegne diverse rilasciati sullo stesso poll, cioe' proprio cio' che la
spaziatura esiste per impedire. Nessun test teneva insieme `held < floorGB` e un
rilascio altrui in corso; adesso due, uno sulla decisione e uno sul waiter con
due giri dove il secondo parte a 300 s invece di 180.

E due conseguenze dei due orologi che non avevo dichiarato, scritte nel requisito
e fissate da un test invece che annullate. Il tetto di un giro non e' piu'
`maxWaitMs` ma la somma delle due valvole, `maxWaitMs + min(3 min, maxWaitMs)`:
33 minuti in produzione, dove il pavimento monta senza un tetto suo. E' il prezzo
di non far pagare una condizione all'altra - uno swap che finisce al
ventinovesimo minuto lascia un pavimento che non ha trattenuto niente - e il caso
peggiore vale tre minuti. Il secondo: il tetto del chiamante puo' solo accorciare
la valvola calma, mai allungarla, e non c'e' un seam per alzarla perche' nessun
chiamante ne ha chiesto uno.

Rimisurate anche le cifre del commento e delle tasks sul log intero (1654 righe
`[memsig]`, 16/09 07:27Z - 17/09 12:33Z): 241 letture su 1589 sopra i 6 GB, cioe'
15,2% e non 15,5% di 1553, e il tratto continuo sotto il pavimento e' 854
campioni ma 16,2 ore, non quattordici: la percentuale era contata sulle righe
invece che sulle letture, le ore erano i campioni convertiti a uno al minuto.

Il file di test passa gli 800 righe di `check:bloat` (867) ed e' registrato in
`scripts/bloat-baseline.json` nello stesso commit che lo ha fatto crescere.
…ano niente

Avevo scritto nel referto che `typecheck:e2e` falliva su `origin/main` e avevo
annotato due espressioni di `tests/unit/no-third-party-emails.test.ts` per
sistemarlo. Falso: con il tsc pinnato dal repo e la cache pulita,
`tsc -p tsconfig.e2e.json --ignoreDeprecations 5.0` esce 0 sul file originale, e
il file e' dentro il program. Le annotazioni erano inerti, quindi tornano via: un
diff che dice di riparare qualcosa che non era rotto fa perdere tempo a chi lo
legge dopo. Verificato riportando il file a main ed eseguendo il rail (exit 0),
poi `bun run typecheck` intero (exit 0).
@zorahrel
zorahrel merged commit 253f929 into main Sep 17, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant