KeyHunter parser v10 + 18 scanners + tests (synthetic fixtures only, no keys/db/tokens)
This commit is contained in:
142
apply_ds_audit.py
Normal file
142
apply_ds_audit.py
Normal file
@@ -0,0 +1,142 @@
|
||||
"""apply_ds_audit.py — write multi-region probe verdicts back into keys.db.
|
||||
|
||||
audit_ds_multiregion.py proved the stored statuses are wrong in both directions:
|
||||
8 rows marked DEAD authenticate fine (region-locked keys probed only on Beijing
|
||||
before), and 31 rows marked WORKING now reject everywhere. This applies the
|
||||
verdicts, using the closed status vocabulary the rest of the farm consumes
|
||||
(NEW/WORKING/DEAD/LIMITED/FREE/ERR) and putting the fine class in `plan`.
|
||||
|
||||
Never marks DEAD from a transport/endpoint verdict: net and endpoint_error leave
|
||||
the row untouched for the next pass.
|
||||
|
||||
Run: python apply_ds_audit.py [--audit PATH] [--dry]
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import sqlite3
|
||||
import sys
|
||||
import time
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
DB = os.path.join(HERE, "data", "keys.db")
|
||||
|
||||
# probe verdict -> (status, plan prefix); None status means "do not touch"
|
||||
VERDICT = {
|
||||
"WORKING": ("WORKING", "auth-ok"),
|
||||
"arrearage": ("LIMITED", "arrearage"),
|
||||
"free_tier": ("LIMITED", "free-tier-exhausted"),
|
||||
"reject": ("DEAD", "rejected-all-regions"),
|
||||
"throttled": (None, "throttled"), # caller/IP side: retry later
|
||||
"net": (None, "net"),
|
||||
"endpoint_error": (None, "endpoint"),
|
||||
"error": (None, "probe-error"),
|
||||
}
|
||||
|
||||
|
||||
def latest_audit(explicit: str | None) -> str:
|
||||
if explicit:
|
||||
return explicit
|
||||
cands = sorted(glob.glob(os.path.join(os.path.dirname(HERE),
|
||||
"ds_multiregion_audit_*.json")))
|
||||
cands += sorted(glob.glob(os.path.join(HERE, "ds_multiregion_audit_*.json")))
|
||||
if not cands:
|
||||
sys.exit("no ds_multiregion_audit_*.json found — run audit_ds_multiregion.py first")
|
||||
return cands[-1]
|
||||
|
||||
|
||||
# a positive probe proves the real provider; the regex guess in `prov` cannot
|
||||
# separate DASHSCOPE from DEEPSEEK (identical sk-<32hex> shape)
|
||||
PROOF = (("dashscope", "DASHSCOPE"), ("deepseek", "DEEPSEEK"), ("zai", "ZAI"),
|
||||
("zhipu", "ZAI"), ("bigmodel", "ZAI"), ("moonshot", "MOONSHOT"),
|
||||
("kimi", "KIMI"), ("siliconflow", "SILICONFLOW"))
|
||||
|
||||
|
||||
def proved_provider(info):
|
||||
head = str(info or "").strip().lower()
|
||||
for needle, prov in PROOF:
|
||||
if head.startswith(needle):
|
||||
return prov
|
||||
return None
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--audit", default=None)
|
||||
ap.add_argument("--dry", action="store_true")
|
||||
args = ap.parse_args()
|
||||
|
||||
path = latest_audit(args.audit)
|
||||
data = json.load(open(path, encoding="utf-8"))
|
||||
rows = data.get("results") or []
|
||||
print(f"audit: {path} ({len(rows)} verdicts, generated {data.get('generated')})")
|
||||
|
||||
conn = sqlite3.connect(DB, timeout=60)
|
||||
conn.execute("PRAGMA journal_mode=WAL")
|
||||
conn.execute("PRAGMA busy_timeout=30000")
|
||||
before = dict(conn.execute("SELECT status, COUNT(*) FROM keys GROUP BY status"))
|
||||
changes: dict[str, int] = {}
|
||||
skipped = 0
|
||||
now = time.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
for r in rows:
|
||||
h = r.get("hash")
|
||||
verdict = r.get("probe_status")
|
||||
mapping = VERDICT.get(verdict)
|
||||
if not h or mapping is None:
|
||||
skipped += 1
|
||||
continue
|
||||
status, prefix = mapping
|
||||
if status == "WORKING" and r.get("usable") is False:
|
||||
# authenticated but cannot run inference (arrears / free tier spent):
|
||||
# LIMITED keeps it out of rotators without calling the key dead
|
||||
status = "LIMITED"
|
||||
prefix = str(r.get("usable_info") or "unusable")[:40]
|
||||
if status is None:
|
||||
skipped += 1 # transient: leave for the next pass
|
||||
continue
|
||||
cur = conn.execute("SELECT status FROM keys WHERE hash=?", (h,)).fetchone()
|
||||
if not cur:
|
||||
skipped += 1
|
||||
continue
|
||||
if cur[0] == status:
|
||||
continue
|
||||
plan = f"{prefix}: {str(r.get('info') or '')[:150]}"
|
||||
proved = proved_provider(r.get("info")) if status == "WORKING" else None
|
||||
if args.dry:
|
||||
changes[f"{cur[0]}->{status}"] = changes.get(f"{cur[0]}->{status}", 0) + 1
|
||||
continue
|
||||
if proved:
|
||||
conn.execute("UPDATE keys SET status=?, prov=?, plan=?, remaining=? "
|
||||
"WHERE hash=?", (status, proved, plan,
|
||||
"OK" if status == "WORKING" else None, h))
|
||||
else:
|
||||
conn.execute("UPDATE keys SET status=?, plan=?, remaining=? WHERE hash=?",
|
||||
(status, plan, "OK" if status == "WORKING" else None, h))
|
||||
changes[f"{cur[0]}->{status}"] = changes.get(f"{cur[0]}->{status}", 0) + 1
|
||||
|
||||
if not args.dry and sum(changes.values()):
|
||||
# snapshot BEFORE committing — this copy is the rollback point
|
||||
bak = DB + ".bak-apply-" + time.strftime("%Y%m%d_%H%M%S")
|
||||
shutil.copy2(DB, bak)
|
||||
print("backup:", bak)
|
||||
conn.commit()
|
||||
elif not args.dry:
|
||||
conn.commit()
|
||||
|
||||
after = dict(conn.execute("SELECT status, COUNT(*) FROM keys GROUP BY status"))
|
||||
conn.close()
|
||||
print("skipped (transient/absent):", skipped)
|
||||
print("transitions:", json.dumps(changes, ensure_ascii=False) or "{}")
|
||||
print("status counts before:", json.dumps(before, ensure_ascii=False))
|
||||
print("status counts after :", json.dumps(after, ensure_ascii=False))
|
||||
print("DRY RUN — nothing written" if args.dry else "APPLIED")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user