From 9acb078844e4a172ee33f7da1e14ffd0e42f7312 Mon Sep 17 00:00:00 2001 From: mrwulf Date: Mon, 6 Jul 2026 19:44:06 +0200 Subject: [PATCH] Serve the paper at /paper; witness mirror must be independently operated - web.py: /paper (and /paper/ltl.pdf) serve the committed PDF, loaded once at startup from the repo checkout; listed in the 404 endpoint index; covered by the web roundtrip test - docs page: 'The paper' card linking the PDF - DEPLOY.md: the second witness mirror belongs on a host the operator does NOT control (a self-hosted mirror adds no equivocation defense) Co-Authored-By: Claude Fable 5 --- DEPLOY.md | 12 +++++++----- provider/src/pacta_provider/web.py | 18 ++++++++++++++++-- provider/src/pacta_provider/webdocs.py | 6 ++++++ tests/test_web_and_witness.py | 3 +++ 4 files changed, 32 insertions(+), 7 deletions(-) diff --git a/DEPLOY.md b/DEPLOY.md index f79a186..cbc0f0d 100644 --- a/DEPLOY.md +++ b/DEPLOY.md @@ -168,13 +168,15 @@ server { Check: `https://ltl.zkdefi.org/docs` renders the customer documentation; `/v1/sth` returns the dogfood-signed head. -## 4. Second mirror (any Forgejo/Gitea/GitLab you operate) +## 4. Second mirror (an independently-operated host — not yours) Create a periodic pull-mirror of -`https://github.com/saymrwulf/lean-transparency-log` on a second, -independently-operated git host. The published repo is the witness -channel; two independent mirrors mean split-view lies must fool two -infrastructures at once — exactly the point. +`https://github.com/saymrwulf/lean-transparency-log` on a second git +host **operated by someone else** (e.g. Codeberg). The published repo is +the witness channel; two independent mirrors mean split-view lies must +fool two infrastructures at once — exactly the point. A mirror on +infrastructure the log operator also controls adds convenience, not +witness value: the operator could equivocate consistently on both. ## 4b. Key hygiene (non-negotiable) diff --git a/provider/src/pacta_provider/web.py b/provider/src/pacta_provider/web.py index 5765707..fd9c388 100644 --- a/provider/src/pacta_provider/web.py +++ b/provider/src/pacta_provider/web.py @@ -12,6 +12,7 @@ from __future__ import annotations import json from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from pathlib import Path from typing import Any from urllib.parse import parse_qs, urlparse @@ -20,7 +21,7 @@ from .transparency_log import TransparencyLog API_VERSION = "v1" -def make_handler(log: TransparencyLog, base_path: str, docs_html: str): +def make_handler(log: TransparencyLog, base_path: str, docs_html: str, paper_pdf: bytes | None = None): base = "/" + base_path.strip("/") if base_path.strip("/") else "" class Handler(BaseHTTPRequestHandler): @@ -43,6 +44,16 @@ def make_handler(log: TransparencyLog, base_path: str, docs_html: str): if route in ("/", "/docs"): self._send_html(docs_html) + elif route in ("/paper", "/paper/ltl.pdf"): + if paper_pdf is None: + self._send(404, {"error": "paper not available on this deployment"}) + return + self.send_response(200) + self.send_header("Content-Type", "application/pdf") + self.send_header("Content-Disposition", 'inline; filename="ltl.pdf"') + self.send_header("Content-Length", str(len(paper_pdf))) + self.end_headers() + self.wfile.write(paper_pdf) elif route == "/healthz": self._send(200, {"ok": True, "tree_size": len(log.entries())}) elif route == f"/{API_VERSION}/metadata": @@ -112,6 +123,7 @@ def make_handler(log: TransparencyLog, base_path: str, docs_html: str): "error": "unknown endpoint", "endpoints": [ f"{base}/docs", + f"{base}/paper", f"{base}/healthz", f"{base}/{API_VERSION}/metadata", f"{base}/{API_VERSION}/sth", @@ -161,5 +173,7 @@ def serve( from .webdocs import render_docs docs_html = render_docs(log, base_path) - handler = make_handler(log, base_path, docs_html) + paper_path = Path(__file__).resolve().parents[3] / "paper" / "ltl.pdf" + paper_pdf = paper_path.read_bytes() if paper_path.is_file() else None + handler = make_handler(log, base_path, docs_html, paper_pdf) return ThreadingHTTPServer((host, port), handler) diff --git a/provider/src/pacta_provider/webdocs.py b/provider/src/pacta_provider/webdocs.py index e1d772b..580bbd1 100644 --- a/provider/src/pacta_provider/webdocs.py +++ b/provider/src/pacta_provider/webdocs.py @@ -218,6 +218,12 @@ the content hash) and build it yourself — compiler and build are declared trus until the reproducible-builds program (R5) lands. Every attestation carries its full residual-risk list. Honesty about the boundary is the product. +

The paper

+
LTL: the Lean Transparency Log +(PDF, 4 pages) — the design in full: the trust model (observations, never verdicts), +the self-certifying signature, the deployment with its retained failure leaves, and an +exact account of what a verified receipt does and does not establish.
+

Log heads are signed offline; this service is read-only and holds no key material. Provider tooling, agent tooling, and the full course (12 Jupyter lectures) live in the pacta repository.

diff --git a/tests/test_web_and_witness.py b/tests/test_web_and_witness.py index b223cbe..96d74ab 100644 --- a/tests/test_web_and_witness.py +++ b/tests/test_web_and_witness.py @@ -56,6 +56,9 @@ def test_web_endpoints_and_online_proof_roundtrip(tmp_path): assert consistency["from_tree_size"] == 2 and consistency["proof"] history = get("/v1/sth-history")["sth_history"] assert len(history) == 3 # one head per append + with urllib.request.urlopen(base + "/paper", timeout=10) as r: + assert r.headers["Content-Type"] == "application/pdf" + assert r.read(5) == b"%PDF-" finally: server.shutdown()