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. +
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()