#!/usr/bin/env python3 """Pre-flight the deployed stack, the way `make audit` pre-flights the source. `make audit` proves 142 tests against a local PostgreSQL. Nothing proved anything against the deployment, so every hosted failure so far was found by opening the page and looking. Two of the worst leave no trace in the API's logs at all, because in both cases no request ever reaches the API: a UI redirecting every visitor to a login page, and a `connect-src` baked at build time that still names the previous API host. Both look exactly like an outage from the outside. Run it before a demo. It also warms the Space, which sleeps after 48 hours idle, and the Neon compute behind it, which suspends sooner — so the first document of the call is not the slow one. make verify-hosted Standard library only, deliberately: this checks a deployment, and should not itself depend on a working install. ## On cleaning up after itself It does not, and cannot. `audit_log` is append-only, enforced by a database trigger that raises on `UPDATE` and `DELETE` — including the cascade from `documents`. That is the product's central claim, not an oversight, so a hosted check is not given a back door through it. What replaces deletion is idempotency. The document uploaded here is pinned: the same bytes every run, so `UNIQUE(file_hash)` means the first run creates one row and every run afterwards is told `duplicate` and creates nothing. The ledger gains one permanent, clearly-named record and never grows again — and the second run onwards turns the clean-up step into a live proof of the idempotency guarantee, which is a better check than the one it replaces. `--fresh` makes the bytes unique so the whole pipeline genuinely re-runs. That adds a permanent row each time, which is why it is not the default. """ from __future__ import annotations import argparse import json import sys import time import urllib.error import urllib.request from pathlib import Path from typing import Any, Final REPO_ROOT: Final = Path(__file__).resolve().parents[1] UI_DEFAULT: Final = "https://ledgerlens-jet.vercel.app" API_DEFAULT: Final = "https://Abdr007-ledgerlens.hf.space" #: A real degraded scan from the labelled evaluation set — a phone photo of an #: invoice, committed to the repository, so this runs from a fresh clone. SAMPLE: Final = REPO_ROOT / "eval" / "testset" / "EV-CE-002-scan.jpg" SAMPLE_NAME: Final = "hosted-verification.jpg" # A cold Space wakes in ~30 s, and the Neon compute behind it can take another # few seconds to resume its first connection. TIMEOUT_S: Final = 120 #: How long to wait for one document to reach a terminal state. PIPELINE_DEADLINE_S: Final = 90 HTTP_OK: Final = 200 HTTP_ACCEPTED: Final = 202 HTTP_FOUND: Final = 302 HTTP_FORBIDDEN: Final = 403 HTTP_TOO_MANY_REQUESTS: Final = 429 HIGH_SEVERITIES: Final = frozenset({"HIGH", "CRITICAL"}) #: Spec §2: Ingest -> Route -> Extract -> Validate -> Screen -> Ledger. PIPELINE_STAGES: Final = ("ingest", "route", "extract", "validate", "screen", "ledger") class CheckError(Exception): """A check that did not hold. The message is what the presenter needs to read.""" def _lower_keys(headers: Any) -> dict[str, str]: """Header names, lower-cased. ``dict(response.headers)`` keeps the wire casing and loses the case-insensitive lookup the underlying ``Message`` provides. Over HTTP/2 the names arrive lower-cased anyway, so a lower-cased lookup against that dict works — and then silently stops working over HTTP/1.1, where Vercel sends ``Content-Security-Policy``. """ return {str(key).lower(): str(value) for key, value in headers.items()} def _request( url: str, *, method: str = "GET", body: bytes | None = None, headers: dict[str, str] | None = None, ) -> tuple[int, dict[str, str], bytes]: request = urllib.request.Request(url, data=body, method=method) # noqa: S310 for key, value in (headers or {}).items(): request.add_header(key, value) try: with urllib.request.urlopen(request, timeout=TIMEOUT_S) as response: # noqa: S310 return int(response.status), _lower_keys(response.headers), response.read() except urllib.error.HTTPError as exc: return int(exc.code), _lower_keys(exc.headers), exc.read() def _json(url: str, *, origin: str, expect: int = HTTP_OK) -> Any: status, _, raw = _request(url, headers={"Origin": origin}) if status != expect: raise CheckError(f"{url} returned HTTP {status}, expected {expect}") return json.loads(raw) # --------------------------------------------------------------------------- # # Checks # # --------------------------------------------------------------------------- # def check_ui_is_public(ui: str) -> str: """A 302 here means deployment protection is on and visitors hit a login page.""" status, headers, _ = _request(ui) if status == HTTP_FOUND: location = headers.get("location", "") if "vercel.com" in location: raise CheckError( "the UI redirects to Vercel login — deployment protection is enabled. " "Project -> Settings -> Deployment Protection -> disable Vercel Authentication" ) raise CheckError(f"the UI redirects to {location}") if status != HTTP_OK: raise CheckError(f"the UI returned HTTP {status}") return f"HTTP 200 at {ui}" def check_csp_points_at_the_api(ui: str, api: str) -> str: """The CSP is baked at build time; a stale one blocks every call inside the browser.""" _, headers, _ = _request(ui) csp = headers.get("content-security-policy", "") if not csp: raise CheckError("the UI sends no Content-Security-Policy") connect = next((part for part in csp.split(";") if part.strip().startswith("connect-src")), "") host = api.split("://", 1)[-1] if host.lower() not in connect.lower(): raise CheckError( f"CSP connect-src does not name {host} — it reads '{connect.strip()}'. " "NEXT_PUBLIC_API_BASE_URL was not set at BUILD time; redeploy after setting it" ) return f"connect-src names {host}" def check_api_health(api: str, origin: str) -> str: data = _json(f"{api}/health", origin=origin) if data.get("status") != "ok": raise CheckError( f"health is '{data.get('status')}' with database '{data.get('database')}' — " "the API is up but its database is not; check DATABASE_URL on the Space" ) if data.get("database") != "up": raise CheckError(f"database is '{data.get('database')}'") return ( f"v{data.get('version')} · {data.get('environment')} · db up · " f"llm {data.get('llm_mode')} · langfuse {data.get('langfuse')}" ) def check_cors(api: str, ui: str) -> str: """The UI may write; an unknown origin may not — asserted on the server. This deliberately does not test `Access-Control-Allow-Origin`, because on this host that header is not ours. Hugging Face answers the pre-flight at its edge and echoes whatever `Origin` it was sent, so an unknown origin is told it is allowed while `ALLOWED_ORIGINS` says otherwise — the same image refuses `evil.example` when run locally (AUDIT.md §4c). A check reading that header would have to either fail forever against a condition nobody can fix, or be softened until it asserted nothing. So it checks the thing that is actually enforceable: `OriginGuardMiddleware` rejects a cross-origin *write* before any handler runs, which no upstream header rewriting can undo. The pre-flight is still checked from the UI's own origin, because that one failing would break the product. """ status, _, _ = _request( f"{api}/v1/documents", method="OPTIONS", headers={ "Origin": ui, "Access-Control-Request-Method": "POST", "Access-Control-Request-Headers": "content-type", }, ) if status != HTTP_OK: raise CheckError(f"pre-flight from {ui} returned HTTP {status}") body, content_type = _multipart("cross-origin-probe.jpg", b"\xff\xd8\xff\xe0 probe") status, _, raw = _request( f"{api}/v1/documents", method="POST", body=body, headers={"Content-Type": content_type, "Origin": "https://evil.example"}, ) if status != HTTP_FORBIDDEN: raise CheckError( f"a write from https://evil.example returned HTTP {status}, not 403 — " "OriginGuardMiddleware is not in force, and on this host the CORS " "headers will not stop it either" ) try: code = json.loads(raw)["error"]["code"] except (json.JSONDecodeError, KeyError, TypeError): code = "?" if code != "forbidden_origin": raise CheckError(f"cross-origin write refused with '{code}', not 'forbidden_origin'") # Reported, never asserted: it is the platform's behaviour, not the app's. _, headers, _ = _request( f"{api}/v1/documents", method="OPTIONS", headers={"Origin": "https://evil.example", "Access-Control-Request-Method": "POST"}, ) edge = ( "; edge echoes any Origin (platform, see AUDIT §4c)" if headers.get("access-control-allow-origin") else "" ) return f"{ui} may write, evil.example gets 403 forbidden_origin{edge}" def check_ledger_reconciles(api: str, origin: str) -> str: """The KPI cards and the ledger must be counting the same thing. Both read the same tables through different queries. They drifting apart is the quiet failure: a dashboard that shows a number nothing else agrees with. """ stats = _json(f"{api}/v1/stats", origin=origin) listing = _json(f"{api}/v1/documents?limit=200", origin=origin) if listing["total"] != stats["documents_total"]: raise CheckError( f"stats says {stats['documents_total']} documents, the ledger says {listing['total']}" ) counted: dict[str, int] = {} for item in listing["items"]: counted[item["status"]] = counted.get(item["status"], 0) + 1 for label, key in ( ("DONE", "documents_processed"), ("NEEDS_REVIEW", "documents_needs_review"), ("FAILED", "documents_failed"), ): if counted.get(label, 0) != stats[key]: raise CheckError( f"stats.{key}={stats[key]} but the ledger holds " f"{counted.get(label, 0)} {label} documents" ) return ( f"{stats['documents_total']} documents · {stats['documents_processed']} done · " f"{stats['documents_needs_review']} in review · {stats['anomalies_open']} open flags · " f"avg {stats['avg_latency_ms']:.0f} ms · p95 {stats['p95_latency_ms']:.0f} ms" ) def _routing_is_consistent(detail: dict[str, Any]) -> str | None: """The thesis, as an assertion: nothing failing verification sits in the ledger. Returns a description of the violation, or None when the record is consistent. """ extraction = detail.get("extraction") or {} failed = [c["rule"] for c in extraction.get("checks", []) if not c["passed"]] high = [a for a in detail.get("anomalies", []) if a["severity"] in HIGH_SEVERITIES] status = detail["status"] if status == "DONE" and (failed or high): return ( f"{detail['filename']} is DONE but has " f"{len(failed)} failed check(s) {failed} and {len(high)} high-severity flag(s)" ) if status == "NEEDS_REVIEW" and not failed and not high: return f"{detail['filename']} is in review with nothing failing — no reason to be there" return None def check_review_routing(api: str, origin: str) -> str: """Sweep every document in the live ledger against the product's own claim.""" listing = _json(f"{api}/v1/documents?limit=200", origin=origin) violations: list[str] = [] reviewed = 0 for item in listing["items"]: detail = _json(f"{api}/v1/documents/{item['id']}", origin=origin) if detail["status"] not in {"DONE", "NEEDS_REVIEW"}: continue reviewed += 1 if problem := _routing_is_consistent(detail): violations.append(problem) if violations: raise CheckError(f"{len(violations)} document(s) misrouted: {violations[0]}") return f"{reviewed} documents, every one routed by its own verification result" def check_review_queue_is_explainable(api: str, origin: str) -> str: """Every flag carries a severity, a score and a reason a human can act on.""" anomalies = _json(f"{api}/v1/anomalies?limit=100", origin=origin) if not anomalies: raise CheckError("the review queue is empty — nothing to demonstrate") for anomaly in anomalies: if not anomaly.get("reason"): raise CheckError(f"anomaly {anomaly['id']} carries no reason") if not anomaly.get("evidence"): raise CheckError(f"anomaly {anomaly['id']} carries no evidence") if anomaly.get("severity") not in {"LOW", "MEDIUM", "HIGH", "CRITICAL"}: raise CheckError(f"anomaly {anomaly['id']} has severity '{anomaly.get('severity')}'") kinds = sorted({a["anomaly_type"] for a in anomalies}) return f"{len(anomalies)} flag(s), all with severity + evidence + reason · {kinds}" def _multipart(name: str, payload: bytes) -> tuple[bytes, str]: boundary = "----ledgerlensverify0000" body = ( ( f"--{boundary}\r\n" f'Content-Disposition: form-data; name="file"; filename="{name}"\r\n' "Content-Type: image/jpeg\r\n\r\n" ).encode() + payload + f"\r\n--{boundary}--\r\n".encode() ) return body, f"multipart/form-data; boundary={boundary}" def _await_terminal(api: str, origin: str, document_id: str) -> dict[str, Any]: deadline = time.monotonic() + PIPELINE_DEADLINE_S state: dict[str, Any] = {} while time.monotonic() < deadline: state = _json(f"{api}/v1/documents/{document_id}/status", origin=origin) if state.get("is_terminal"): return state time.sleep(1) raise CheckError( f"the document was still '{state.get('status')}' after {PIPELINE_DEADLINE_S}s " f"(progress {state.get('progress')})" ) def check_round_trip(api: str, origin: str, *, fresh: bool) -> str: """Upload a real document and verify what came back out. On the first run this drives the whole pipeline. Afterwards the same bytes are recognised and no work is repeated — which is the guarantee, so it is asserted rather than worked around. See the module docstring. """ if not SAMPLE.exists(): raise CheckError(f"{SAMPLE} is missing from the checkout") payload = SAMPLE.read_bytes() name = SAMPLE_NAME if fresh: # Trailing bytes after the JPEG end-of-image marker change the SHA-256 # without changing what any decoder reads. marker = str(time.time_ns()).encode() payload = payload + b"\xff\xfe" + len(marker).to_bytes(2, "big") + marker name = f"hosted-verification-{time.time_ns()}.jpg" body, content_type = _multipart(name, payload) status, _, raw = _request( f"{api}/v1/documents", method="POST", body=body, headers={"Content-Type": content_type, "Origin": origin}, ) if status != HTTP_ACCEPTED: raise CheckError(f"upload returned HTTP {status}: {raw[:300].decode('utf-8', 'replace')}") upload = json.loads(raw) state = _await_terminal(api, origin, upload["document_id"]) detail = _json(f"{api}/v1/documents/{upload['document_id']}", origin=origin) if problem := _routing_is_consistent(detail): raise CheckError(problem) seen = {entry["stage"] for entry in detail.get("audit", []) if entry.get("stage")} missing = [stage for stage in PIPELINE_STAGES if stage not in seen] if missing: raise CheckError(f"the audit trail is missing stage(s) {missing} — it should span all six") extraction = detail.get("extraction") or {} checks = extraction.get("checks", []) if not checks: raise CheckError("no validation report was stored — nothing verified this document") passed = sum(1 for c in checks if c["passed"]) origin_word = "deduplicated" if upload["duplicate"] else "processed" return ( f"{origin_word} · {detail['status']} · lane {detail.get('lane')} · " f"{passed}/{len(checks)} checks passed · {len(seen)}/6 stages audited · " f"{state.get('latency_ms')} ms" ) def check_idempotency(api: str, origin: str) -> str: """The same bytes a second time must not create a second record.""" before = _json(f"{api}/v1/stats", origin=origin)["documents_total"] body, content_type = _multipart(SAMPLE_NAME, SAMPLE.read_bytes()) status, _, raw = _request( f"{api}/v1/documents", method="POST", body=body, headers={"Content-Type": content_type, "Origin": origin}, ) if status != HTTP_ACCEPTED: raise CheckError(f"re-upload returned HTTP {status}") upload = json.loads(raw) if not upload["duplicate"]: raise CheckError("identical bytes were not recognised as a duplicate") after = _json(f"{api}/v1/stats", origin=origin)["documents_total"] if after != before: raise CheckError(f"the ledger grew from {before} to {after} on a duplicate upload") return f"same SHA-256 -> duplicate, ledger still {after} documents" def check_errors_are_typed(api: str, origin: str) -> str: """A rejected upload returns the error envelope, never a stack trace.""" body, content_type = _multipart("payload.exe", b"MZ\x90\x00 this is not a document") status, _, raw = _request( f"{api}/v1/documents", method="POST", body=body, headers={"Content-Type": content_type, "Origin": origin}, ) text = raw.decode("utf-8", "replace") if "Traceback" in text or 'File "' in text: raise CheckError("a rejected upload leaked a stack trace") if status == HTTP_ACCEPTED: raise CheckError("an executable was accepted as a document") try: code = json.loads(text)["error"]["code"] except (json.JSONDecodeError, KeyError, TypeError) as exc: raise CheckError(f"HTTP {status} was not the error envelope: {text[:160]}") from exc return f"HTTP {status} {code}, no stack trace" def check_rate_limit_binds(api: str, origin: str) -> str: """The ingestion limit must actually stop someone, not merely be configured. Production reported `X-RateLimit-Limit: 10` on every upload and never returned 429, because `TRUSTED_PROXY_COUNT` was one less than the host's real proxy depth: the limiter keyed on the address of whichever edge node answered, so callers were spread across a handful of buckets and none of them filled. A check that read the headers would have called that a pass. Rejected uploads are used deliberately. The limiter decorator runs before the handler, so a 415 consumes budget while writing nothing — the limit can be driven to its ceiling without adding a single row to an append-only ledger. """ boundary = "----ledgerlensratelimit" body = ( ( f"--{boundary}\r\n" 'Content-Disposition: form-data; name="file"; filename="rate-limit-probe.exe"\r\n' "Content-Type: application/octet-stream\r\n\r\n" ).encode() + b"MZ\x90\x00 deliberately not a document" + f"\r\n--{boundary}--\r\n".encode() ) headers = {"Content-Type": f"multipart/form-data; boundary={boundary}", "Origin": origin} limit: str | None = None attempts = 0 # One more than the ceiling this project configures, with headroom for a # budget already partly spent by the round-trip checks above. for attempts in range(1, 26): status, response_headers, _ = _request( f"{api}/v1/documents", method="POST", body=body, headers=headers ) limit = response_headers.get("x-ratelimit-limit", limit) if status == HTTP_TOO_MANY_REQUESTS: return f"429 after {attempts} rejected uploads (limit {limit}), 0 ledger writes" if status == HTTP_ACCEPTED: raise CheckError("an executable was accepted as a document") raise CheckError( f"{attempts} uploads in seconds and none was refused, while the API reported " f"X-RateLimit-Limit: {limit}. The limit is counted but does not bind — " "TRUSTED_PROXY_COUNT is almost certainly lower than the host's real proxy " "depth, so the key is a proxy address rather than the caller. The API logs a " "`proxy_depth_mismatch` warning naming the value to set." ) def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--ui", default=UI_DEFAULT) parser.add_argument("--api", default=API_DEFAULT) parser.add_argument( "--fresh", action="store_true", help="force a full pipeline run with unique bytes (adds a permanent ledger row)", ) args = parser.parse_args(argv) ui, api = args.ui.rstrip("/"), args.api.rstrip("/") checks: list[tuple[str, Any]] = [ ("UI is public", lambda: check_ui_is_public(ui)), ("CSP points at the API", lambda: check_csp_points_at_the_api(ui, api)), ("API health", lambda: check_api_health(api, ui)), ("cross-origin writes refused", lambda: check_cors(api, ui)), ("ledger reconciles", lambda: check_ledger_reconciles(api, ui)), ("nothing invalid committed", lambda: check_review_routing(api, ui)), ("review queue explains itself", lambda: check_review_queue_is_explainable(api, ui)), ("upload · extract · validate", lambda: check_round_trip(api, ui, fresh=args.fresh)), ("re-upload is idempotent", lambda: check_idempotency(api, ui)), ("rejections are typed", lambda: check_errors_are_typed(api, ui)), ("rate limit binds", lambda: check_rate_limit_binds(api, ui)), ] print(f"\n UI {ui}\n API {api}\n") failures: list[str] = [] for name, run in checks: started = time.perf_counter() try: detail = run() print(f" PASS {name:30} {detail} [{time.perf_counter() - started:.1f}s]") except CheckError as exc: failures.append(f"{name}: {exc}") print(f" FAIL {name:30} {exc}") except (urllib.error.URLError, TimeoutError, json.JSONDecodeError, KeyError) as exc: failures.append(f"{name}: {exc}") print(f" FAIL {name:30} unreachable: {exc}") if failures: print(f"\n {len(failures)} of {len(checks)} checks failed — do not present\n") return 1 print(f"\n all {len(checks)} checks passed · the Space is warm · ready to present\n") return 0 if __name__ == "__main__": sys.exit(main())