Files
Drew T e7ebb1cd45 docs+rules(S58): R42 commit-banked-work-immediately, R43 refuse-unsupported-input
R42: gate_main reverted 61 byte-proven overlay banks it could not distinguish from its own
substitution (sweep_parallel gates commit=False by design). Fixed by committing overlay banks
before the main batch, chunking main at 8 to bound bisect cost, and replacing every blind
'git checkout -- src/ config/' with commit-or-refuse in ox_campaign and idiom_serial.

R43: sweep_parallel had an explicit branch admitting main, which cannot be gated incrementally
— wave ab banked 0/105 main cards while its non-main cards banked 94/115 (82%), and the wave
read as a drafting failure. sweep_parallel now refuses main and names gate_main.py.

Also: validate_targets now prefers the card's own addr field (named symbols like SYS_OBJ_F00
were MALFORMED and discarded whole 220-card waves); ox_campaign deals model lanes by
smallest-ratio scheduling (a 73-card wave had put 73 shards on ox and 0 on deepseek);
docs/accelerators.md gains the four vacuous-check defects.
2026-08-23 12:59:59 -06:00

50 lines
2.0 KiB
Python

#!/usr/bin/env python3
"""api_rate.py — read .run/api_rate.jsonl and report MEASURED request rate + 429 attribution.
Exists because a flush=True print into a block-buffered shard log makes "no 429s" and "the buffer
has not filled" indistinguishable (P31 S58). This reads the append-only telemetry instead.
tools/api_rate.py [--window 60] [--path .run/api_rate.jsonl]
"""
import argparse, collections, json, os, sys, time
ap = argparse.ArgumentParser()
ap.add_argument('--window', type=int, default=60, help='trailing seconds for the live rate')
ap.add_argument('--path', default='.run/api_rate.jsonl')
a = ap.parse_args()
if not os.path.exists(a.path):
sys.exit(f'no telemetry yet at {a.path}')
recs = []
for line in open(a.path):
line = line.strip()
if line:
try: recs.append(json.loads(line))
except json.JSONDecodeError: pass
if not recs:
sys.exit('telemetry file is empty')
now = time.time()
posts = [r for r in recs if r.get('ev') == 'POST']
h429 = [r for r in recs if r.get('ev') == '429']
span = max(1e-9, posts[-1]['t'] - posts[0]['t']) if len(posts) > 1 else 0
win = [r for r in posts if now - r['t'] <= a.window]
win429 = [r for r in h429 if now - r['t'] <= a.window]
print(f'requests total : {len(posts)}')
print(f'429s total : {len(h429)} ({100.0*len(h429)/max(1,len(posts)):.1f}% of requests)')
if span:
print(f'mean rate : {60.0*len(posts)/span:.1f} req/min over {span/60:.1f} min')
print(f'last {a.window}s : {len(win)} req = {60.0*len(win)/a.window:.1f} req/min, {len(win429)} 429')
print(f'live workers : {len({r["pid"] for r in win}) if win else 0} pids posting in the window')
if h429:
print('\n429 attribution:')
for k, v in collections.Counter(r.get('src', '?') for r in h429).most_common():
print(f' {v:5d} {k}')
else:
print('\n429 attribution: NONE RECORDED - no rate limit has been hit yet.')
per_min = collections.Counter(int(r['t'] // 60) for r in posts)
if per_min:
print(f'\npeak observed : {max(per_min.values())} req in one clock minute')