69 lines
3.0 KiB
Python

"""Bounded ephemeral write capabilities; validated numeric receipts go to stdout.
These are unverified browser diagnostics, never release-gate attestations.
No public read API, database, cookies, media, device identities or credentials.
"""
from collections import deque
import hashlib
import json
import secrets
import threading
import time
from validation import validate_report
class Refused(Exception):
def __init__(self, status):
self.status = status
class ReceiptStore:
def __init__(self, emit, clock=time.monotonic, wall=time.time):
self.emit, self.clock, self.wall = emit, clock, wall
self.runs, self.starts = {}, deque()
self.lock = threading.Lock()
def start(self):
with self.lock:
now = self.clock()
self.runs = {key: value for key, value in self.runs.items() if value["expires"] > now}
while self.starts and now-self.starts[0] >= 60:
self.starts.popleft()
if len(self.starts) >= 12 or len(self.runs) >= 64:
raise Refused(429)
run_id, token = secrets.token_hex(16), secrets.token_urlsafe(32)
self.runs[run_id] = {"token": hashlib.sha256(token.encode()).digest(), "expires": now+600,
"sequence": 0, "digest": None, "terminal": False, "last_progress": None}
self.starts.append(now)
self.log({"run_id": run_id, "sequence": 0, "state": "starting", "metrics": None})
return {"run_id": run_id, "write_token": token}
def report(self, body, token):
data = validate_report(body)
if type(token) is not str or len(token) != 43:
raise Refused(401)
with self.lock:
now = self.clock()
run = self.runs.get(data["run_id"])
if not run or run["expires"] <= now or not secrets.compare_digest(
run["token"], hashlib.sha256(token.encode()).digest()):
raise Refused(401)
digest = hashlib.sha256(json.dumps(data, sort_keys=True, allow_nan=False).encode()).digest()
if data["sequence"] == run["sequence"] and run["digest"] == digest:
return # Idempotent network retry, no duplicate log entry.
if run["terminal"] or data["sequence"] <= run["sequence"]:
raise Refused(409)
if data["state"] == "running" and run["last_progress"] is not None and now-run["last_progress"] < 8:
raise Refused(429)
self.log(data)
run.update(sequence=data["sequence"], digest=digest, terminal=data["state"] != "running")
if data["state"] == "running":
run["last_progress"] = now
else:
run["expires"] = now+60 # Retain briefly for idempotent final retry.
def log(self, data):
self.emit({"schema": "av-test.receipt.v1", "kind": "unverified-browser-diagnostic",
"received_unix_ms": int(self.wall()*1000), "release_eligible": False, **data})