Approval Gateway¶
Datei:
cli/approve_settlement.ts(122 Zeilen) Zweck: Sicherheitsvalidierung vor dem On-Chain-Commit Aufgerufen von:cli/settle.ts(Schritt 4)
Übersicht¶
Das Approval Gateway führt 7 Validierungs-Gates + 1 Warning durch, bevor ein Settlement für den Blockchain-Commit freigegeben wird. Jedes Gate muss bestehen - bei einem Fehler wird der gesamte Prozess abgebrochen.
dry_run Settlement
│
▼
┌─── G1: Existenz ──────────┐
├─── G2: Eindeutigkeit ─────┤
├─── G3: Winner-Summe ──────┤
├─── G4: Affiliate-Summe ───┤
├─── G5: Pool-Summe ────────┤
├─── G6: Merkle-Root ───────┤
├─── G7: Affiliate-Root ────┤
├─── G8: JackpotHitArr ─────┤
└─── W1: Charity-Sanity ────┘ (Warning only)
│
▼
status = 'approved'
Gate-Details¶
G1 + G2: Existenz und Eindeutigkeit¶
// approve_settlement.ts:30-45
// Exakt 1 dry_run Settlement muss existieren
const runs = await pg.query(
`SELECT * FROM settlement_runs
WHERE day_id = $1 AND status = 'dry_run'`,
[dayId]
);
if (runs.rows.length === 0) throw new Error("G1: Kein dry_run Settlement");
if (runs.rows.length > 1) throw new Error("G2: Mehrere dry_run Settlements");
Zweck: Verhindert Commits ohne Berechnung oder bei inkonsistentem Zustand.
G3: Winner-Summe¶
// approve_settlement.ts:48-59
const winnerSum = await pg.query(
`SELECT COALESCE(SUM(amount), 0) AS total
FROM settlement_winner_payouts
WHERE settlement_run_id = $1`,
[runId]
);
if (winnerSum !== run.total_payout) {
throw new Error(`G3: Winner-Summe ${winnerSum} != totalPayout ${run.total_payout}`);
}
Invariante: SUM(winner_payouts.amount) === settlement_runs.total_payout
G4: Affiliate-Summe¶
// approve_settlement.ts:62-73
const affiliateSum = await pg.query(
`SELECT COALESCE(SUM(amount), 0) AS total
FROM settlement_affiliate_payouts
WHERE settlement_run_id = $1`,
[runId]
);
if (affiliateSum !== run.affiliate_total) {
throw new Error(`G4: Affiliate-Summe ${affiliateSum} != affiliateTotal ${run.affiliate_total}`);
}
Invariante: SUM(affiliate_payouts.amount) === settlement_runs.affiliate_total
G5: Pool-Summe¶
// approve_settlement.ts:76-80
const poolSum = poolPayoutArr.reduce((a, b) => a + b, 0n)
+ poolJackpotPayoutArr.reduce((a, b) => a + b, 0n);
if (poolSum !== run.total_payout) {
throw new Error(`G5: Pool-Summe ${poolSum} != totalPayout ${run.total_payout}`);
}
Invariante: SUM(poolPayout) + SUM(poolJackpotPayout) === totalPayout
G6: Merkle-Root¶
// approve_settlement.ts:83-85
if (run.merkle_root === ZERO_BYTES32) {
throw new Error("G6: merkleRoot ist 0x0");
}
Invariante: Winners-Merkle-Root darf niemals Null sein (Dummy-Leaf bei 0 Gewinnern).
G7: Affiliate-Root-Konsistenz¶
// approve_settlement.ts:87-92
if (run.affiliate_total === 0n && run.affiliate_root !== ZERO_BYTES32) {
throw new Error("G7: affiliateTotal=0 aber affiliateRoot != 0x0");
}
if (run.affiliate_total > 0n && run.affiliate_root === ZERO_BYTES32) {
throw new Error("G7: affiliateTotal>0 aber affiliateRoot = 0x0");
}
G8: JackpotHitArr-Format¶
// approve_settlement.ts:95-97
if (!Array.isArray(run.jackpot_hit_arr) || run.jackpot_hit_arr.length !== 5) {
throw new Error("G8: jackpotHitArr muss Array[5] sein");
}
W1: Charity-Sanity (Warning)¶
// approve_settlement.ts:100-102
if (run.charity_total > run.affiliate_total * 2n) {
console.warn("W1: Charity-Total ungewöhnlich hoch");
// Kein Abbruch - nur Warning
}
Aktionen nach Genehmigung¶
// approve_settlement.ts:105-112
await pg.query(
`UPDATE settlement_runs
SET status = 'approved',
approved_at = NOW()
WHERE id = $1`,
[runId]
);
Status-Transition: dry_run → approved
Gate-Übersicht¶
| Gate | Prüfung | Schweregrad |
|---|---|---|
| G1 | Existiert ein dry_run Settlement? | Fatal |
| G2 | Genau 1 dry_run (nicht mehr)? | Fatal |
| G3 | Winner-Auszahlungssumme = totalPayout? | Fatal |
| G4 | Affiliate-Auszahlungssumme = affiliateTotal? | Fatal |
| G5 | Pool-Auszahlungssumme = totalPayout? | Fatal |
| G6 | Merkle-Root != 0x0? | Fatal |
| G7 | Affiliate-Root-Konsistenz? | Fatal |
| G8 | jackpotHitArr ist Array[5]? | Fatal |
| W1 | Charity plausibel? | Warning |
Fehlermeldungen¶
Alle Gates werfen sprechende Fehlermeldungen mit konkreten Werten:
Dies ermöglicht schnelle Diagnose bei Berechnungsfehlern.