diff --git a/SPEC.md b/SPEC.md index 239f512..2713bb0 100644 --- a/SPEC.md +++ b/SPEC.md @@ -131,6 +131,7 @@ V61: red-team send-path hardening (audit V58/V59/V60, B20–B23): (1) `_send_rej V62: stempel scan UTC-naive (B24): `get_voters.persist` menulis `scanned_at`/`first_seen_at` dgn `datetime.now(timezone.utc).replace(tzinfo=None)` — sama basis waktu dgn SEMUA cutoff umur (dashboard `_freshness_cutoffs`/`_revote_cut`/countdown + `db.eligible_voters` + `distribute` semuanya `datetime.now(timezone.utc)`) dan dgn `last_vote` (diturunkan dari epoch 2000 UTC). Dulu `datetime.now()` (waktu LOKAL container `TZ=Asia/Jakarta`, UTC+7) → `first_seen_at` tersimpan WIB tapi dibandingkan sbg string UTC → semua ambang maturity BARU/REVOTE bergeser +7 jam: pemilih baru tertahan di band BARU ~7 jam lebih lama dari seharusnya (B24). Migrasi data existing: UPDATE `voter_first_seen`/`voters` kurangi 7 jam (WIB→UTC), backup tabel `*_bak_wib` di prod. Oracle test_get_voters V62: stempel ≈ now-UTC-naive (selisih <2 mnt) DAN ≠ now−7h (guard anti-WIB) | V62,V52,V40,V41 V63: jeda antar kirim distribusi: send-loop `run_distribution` tidur `DISTRIBUTE_SEND_DELAY` (int, default 30) detik ANTAR pemilih — setelah pemilih ke-i selesai diproses (sent/hold/failed), sebelum pemilih i+1; N pemilih → N−1 tidur, ⊖ tidur setelah pemilih TERAKHIR; real-run saja (dry-run ⊖ tidur — tak ada kirim). Tujuan: beri node/Hyperion waktu mengejar indeks + meratakan beban broadcast run panjang; jeda seragam terlepas hasil pemilih sebelumnya (pacing deterministik utk oracle). Oracle test_distribute V63: 3 pemilih → sleep dipanggil 2× dgn nilai `DISTRIBUTE_SEND_DELAY` | V63,V21,V22 V64: banner BERITA hermetik utk oracle (B25): route index menghitung jam via helper modul `_now()` (isi = `datetime.now()` lokal, ⊖ inline di route) & mengoper ke `_next_schedule(now)`/`_recurrence_label(now)` — test mock `dashboard._now` ke tanggal TETAP (sebelum/sesudah launch) sehingga asersi baris-berulang ⊖ bergantung jam dinding nyata; tanpa ini, setelah tanggal first-run baked (2026-08-17) LEWAT di kalender, `_is_first_run_next(real_now)` ⊖ pernah True (`start >= now` gagal) → baris berulang tampil → asersi "TAK tampil saat first-run" gagal walau `_next_schedule` sudah di-mock (B25; test lulus hanya kalau kalender < tanggal launch). Oracle test_dashboard V38: mock `_now` sebelum launch → baris hilang; sesudah launch → baris muncul | V64,V38,V44 +V65: semua stempel data (audit trail, pembayaran, klaim) UTC-naive konsisten — `distribute.py` `created_at`, `db.py` `updated_at` (update_payment_status), `db.py` `now` (update_claim_fee) memakai `datetime.now(timezone.utc).replace(tzinfo=None)` — sama basis dengan `get_voters.persist` (V62), `eligible_voters` (V57), semua cutoff umur dashboard; ⊖ `datetime.now()` lokal yang menghasilkan WIB (UTC+7) dan merusak perbandingan string UTC. Oracle: test_distribute + test_claim tetap hijau | V65,V62,V57,V40,V52 V35: kill-switch distribusi `DISTRIBUTE_ENABLED` default false → run nyata (bukan dry-run) no-op: exit 0, ⊥ baca saldo, ⊥ tanda tangan, ⊥ tulis DB, ⊥ notif; `--dry-run` tetap menampilkan rencana (read-only); nilai `true`/`1`/`yes` → normal V36: notifikasi klaim reward (sukses `notify_claim` & gagal `notify_claim_failure`) → kanal INTERNAL saja, ⊥ pernah ke KOMUNITAS (V30); fee gagal ⊥ spam — satu notif per klaim (saat transisi ke `failed`) V37: jadwal distribusi MINGGUAN (⊥ harian): `distribute_loop` menunggu `DISTRIBUTE_WEEKDAY` — SATU hari (`sat`) atau CSV beberapa hari (`wed,sat`), nama `mon`..`sun` atau 0-6 (V44: multi-hari, event terdekat di antara hari-hari terdaftar) — jam `DISTRIBUTE_HOUR` (lokal TZ); plus satu one-off `DISTRIBUTE_FIRST_RUN` (`YYYY-MM-DD`; default baked `2026-08-17`) yang diambil bila lebih dekat dari mingguan; setelah lewat (atau ⊥ diset) → hanya mingguan; batas dihitung ulang tiap iterasi loop (setelah run → jadwal berikutnya); payout di-tunda bila `DISTRIBUTE_ENABLED=false` (V35) — jadwal tetap maju, run jadi no-op; `distribute_loop.main` baca jadwal via `config` (⊥ `os.getenv` sendiri) agar konsisten dgn dashboard V38 diff --git a/db.py b/db.py index e8e94b7..3713650 100644 --- a/db.py +++ b/db.py @@ -206,9 +206,9 @@ def record_payment(run_id, owner, amount, status, created_at, def update_payment_status(payment_id, status, txid=None, error=None, updated_at=None): """Perbarui status satu transfer (V21): pending → sent|failed.""" - from datetime import datetime + from datetime import datetime, timezone if updated_at is None: - updated_at = datetime.now().isoformat(timespec='seconds') + updated_at = datetime.now(timezone.utc).replace(tzinfo=None).isoformat(timespec='seconds') conn = connect() try: cur = conn.cursor() @@ -317,12 +317,12 @@ def record_claim(run_date, claim_txid, reward, fee_amount, fee_status, def update_claim_fee(claim_id, fee_status, fee_txid=None): """Perbarui status fee satu klaim (V33): pending → sent|failed|skipped.""" - from datetime import datetime + from datetime import datetime, timezone conn = connect() try: cur = conn.cursor() if fee_status == 'sent': - now = datetime.now().isoformat(timespec='seconds') + now = datetime.now(timezone.utc).replace(tzinfo=None).isoformat(timespec='seconds') cur.execute(_translate( 'UPDATE claim_runs SET fee_status = %s, fee_txid = %s, ' 'fee_sent_at = %s WHERE claim_id = %s'), diff --git a/distribute.py b/distribute.py index ad0bd59..7b5b59a 100644 --- a/distribute.py +++ b/distribute.py @@ -15,7 +15,7 @@ beri node/Hyperion waktu mengejar indeks + meratakan beban broadcast. import sys import time -from datetime import datetime +from datetime import datetime, timezone from decimal import Decimal, ROUND_FLOOR, InvalidOperation import requests @@ -275,7 +275,7 @@ def run_distribution(dry_run=False): total_share = sum(share for _, share in shares) telegram.notify_start(run_date, len(shares), total_share, balance) - created_at = datetime.now().isoformat(timespec='seconds') + created_at = datetime.now(timezone.utc).replace(tzinfo=None).isoformat(timespec='seconds') total_stake = sum(float(s) for _, s in voters) run_id = db.record_run(run_date, float(balance), len(voters), total_stake, 0.0, 'ok', created_at)