mirror of
https://github.com/saymrwulf/proof-aware-crypto-tooling-agent.git
synced 2026-09-03 19:53:43 +00:00
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 <noreply@anthropic.com>
This commit is contained in:
parent
47dfb61a7f
commit
9acb078844
4 changed files with 32 additions and 7 deletions
12
DEPLOY.md
12
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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.</div>
|
||||
|
||||
<h2>The paper</h2>
|
||||
<div class="card"><a href="{base}/paper"><strong>LTL: the Lean Transparency Log</strong></a>
|
||||
(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.</div>
|
||||
|
||||
<p class="muted">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 <a href="https://github.com/saymrwulf/proof-aware-crypto-tooling-agent">pacta repository</a>.</p>
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue