File size: 7,008 Bytes
d9ba8d6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
"""routers/lambda_bounty.py — Λ-BOUNTY INTAKE receiver (moved verbatim from serve.py).

Wave-K Dev4 refactor-only extraction. Route group:
    GET  /api/lambda-bounty/healthz
    POST /api/lambda-bounty/submit
    GET  /api/lambda-bounty/receipts

Mirrors szl-holdings/lambda-bounty/webhook/intake.py so the endpoint advertised in
lutar-lean/BOUNTY.md is REAL, not a 404.

HONESTY: a receipt acknowledges INTAKE only. Award eligibility is decided SOLELY by
the verify-proof CI on a PR to szl-holdings/lambda-bounty. This receiver never
declares a winner and never moves money. Λ = Conjecture 1, NOT a theorem. Registered
BEFORE the SPA catch-all /{full_path:path}.

DSSE/HMAC receipts are REAL when LAMBDA_BOUNTY_HMAC_KEY is present; an honest
"dev-key" placeholder hmac is emitted (and flagged) when absent. The ledger is
in-memory (ring buffer) on the Space — honest disclosure; durable receipts land in
the repo via the bounty-webhook GitHub Action. ADDITIVE ONLY.

This module is fully self-contained: it uses only stdlib + FastAPI response types.
The state (ring buffer + keys) lives here now instead of at serve.py module scope.
Behavior is byte-identical to the pre-refactor inline block.

Signed-off-by: Stephen P. Lutar Jr. <stephenlutar2@gmail.com>
"""
from __future__ import annotations

import collections as _pr_col
import hashlib as _lb_hashlib
import hmac as _lb_hmac
import json
import os
import re as _lb_re
import threading as _pr_thr

from fastapi import Request
from fastapi.responses import JSONResponse

# --- module state (was serve.py module-scope) --------------------------------
_LB_SIGN_KEY = os.environ.get("LAMBDA_BOUNTY_HMAC_KEY", "dev-key-not-for-prod")
_LB_HMAC_IS_DEV = _LB_SIGN_KEY == "dev-key-not-for-prod"
_LB_PR_RE = _lb_re.compile(r"^https://github\.com/szl-holdings/lambda-bounty/pull/\d+$")
_LB_ALLOWED_AXIOMS = ("propext", "Quot.sound", "Classical.choice")
_LB_LEDGER: _pr_col.deque = _pr_col.deque(maxlen=500)
_LB_LEDGER_LOCK = _pr_thr.Lock()
_LB_CONJECTURE = {
    "id": "Conjecture 1",
    "formula": "F23",
    "status": "OPEN — NOT a theorem",
    "statement": "Any two 9-axis aggregators satisfying A1 idempotence, A2 monotonicity, "
                 "A3 symmetry, A4 zero-absorption agree on every input.",
    "arbiter": "verify-proof CI on a PR to szl-holdings/lambda-bounty (sole, no-bypass)",
}


def _lb_now() -> str:
    import datetime as _dt
    return _dt.datetime.now(_dt.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")


def _lb_validate(payload: dict) -> list:
    errs = []
    for k in ("submitter", "pr_url", "lean_toolchain", "axiom_print", "sorry_free_claim"):
        if k not in payload:
            errs.append(f"missing required field: {k}")
    if "pr_url" in payload and not _LB_PR_RE.match(str(payload.get("pr_url", ""))):
        errs.append("pr_url must be https://github.com/szl-holdings/lambda-bounty/pull/<n>")
    if payload.get("lean_toolchain") not in (None, "leanprover/lean4:v4.13.0"):
        errs.append("lean_toolchain must be leanprover/lean4:v4.13.0")
    if payload.get("sorry_free_claim") is not True:
        errs.append("sorry_free_claim must be true (CI verifies independently)")
    sub = payload.get("submitter")
    if not isinstance(sub, dict) or not sub.get("name"):
        errs.append("submitter.name is required")
    ap = payload.get("axiom_print", "")
    if ap and "sorryAx" in str(ap):
        errs.append("axiom_print contains sorryAx — proof is incomplete")
    return errs


def _lb_prev_hash() -> str:
    if not _LB_LEDGER:
        return "genesis"
    return _LB_LEDGER[-1].get("hash", "genesis")


def _lb_make_receipt(payload: dict, accepted: bool, errors: list) -> dict:
    body = {
        "receipt_type": "lambda_bounty_intake",
        "conjecture": "Conjecture 1 (F23 Λ-aggregator uniqueness)",
        "ts": _lb_now(),
        "submitter": (payload.get("submitter") or {}).get("name", "?"),
        "pr_url": payload.get("pr_url"),
        "accepted_intake": accepted,
        "errors": errors,
        "eligibility_note": "Intake acknowledgement only. Award eligibility = verify-proof CI green on the PR.",
        "prev": _lb_prev_hash(),
    }
    digest = _lb_hashlib.sha256(json.dumps(body, sort_keys=True).encode()).hexdigest()
    sig = _lb_hmac.new(_LB_SIGN_KEY.encode(), digest.encode(), _lb_hashlib.sha256).hexdigest()
    body["hash"] = digest
    body["hmac_sha256"] = sig
    body["hmac_key"] = "dev-key-placeholder (set LAMBDA_BOUNTY_HMAC_KEY for a real signature)" if _LB_HMAC_IS_DEV else "env-provided"
    return body


def register(app) -> dict:
    """Attach the Λ-bounty intake route group to `app`, identically to the prior
    inline serve.py block. Called BEFORE the SPA catch-all."""

    @app.get("/api/lambda-bounty/healthz")
    async def _lb_healthz():
        """Λ-bounty intake liveness + live Conjecture-1 status. Λ = NOT a theorem."""
        return JSONResponse({"status": "ok", "service": "lambda-bounty-intake",
                             "conjecture": _LB_CONJECTURE, "doctrine": "v11",
                             "receipts_buffered": len(_LB_LEDGER)})

    @app.post("/api/lambda-bounty/submit")
    async def _lb_submit(request: Request):
        """Validate a Conjecture-1 submission payload, emit a hash-chained Khipu
        intake receipt. 200 + receipt (accepted) or 422 + errors (rejected); a
        receipt is appended either way. Eligibility is decided ONLY by verify-proof
        CI on the PR — this never declares a winner."""
        try:
            payload = await request.json()
        except Exception:
            return JSONResponse({"error": "invalid JSON"}, status_code=400)
        if not isinstance(payload, dict):
            return JSONResponse({"error": "payload must be a JSON object"}, status_code=400)
        errors = _lb_validate(payload)
        accepted = len(errors) == 0
        receipt = _lb_make_receipt(payload, accepted, errors)
        with _LB_LEDGER_LOCK:
            _LB_LEDGER.append(receipt)
        return JSONResponse(status_code=(200 if accepted else 422), content={
            "accepted_intake": accepted, "errors": errors, "receipt": receipt,
            "next_step": "Open a PR to szl-holdings/lambda-bounty; verify-proof CI is the sole arbiter.",
        })

    @app.get("/api/lambda-bounty/receipts")
    async def _lb_receipts():
        """Append-only intake receipt ledger as NDJSON. In-memory ring buffer
        (maxlen=500); resets on Space rebuild (honest disclosure). Durable receipts
        are committed to the repo by the bounty-webhook GitHub Action."""
        from fastapi.responses import PlainTextResponse as _LBPlain
        with _LB_LEDGER_LOCK:
            lines = "\n".join(json.dumps(r) for r in _LB_LEDGER)
        return _LBPlain(lines, media_type="application/x-ndjson")

    return {"ok": True, "ns": "lambda-bounty", "routes": [
        "/api/lambda-bounty/healthz",
        "/api/lambda-bounty/submit",
        "/api/lambda-bounty/receipts",
    ]}