betterwithage commited on
Commit
17aa715
·
verified ·
1 Parent(s): d501727

chore(sync): mirror backend .py + Dockerfile to Space (hf-sync-backend)

Browse files

Automated backend sync from szl-holdings/a11oy main via hf-sync-backend.
Updated (differed from the Space): routers/__init__.py, routers/frontier_reads.py, routers/series_a_control_plane.py
Deleted (gone from the repo + Dockerfile COPY set): (none)

Keeps the Space-built backend (serve.py + the Dockerfile-COPY'd .py
modules) identical to GitHub main so the Space never rebuilds from a
stale backend, new endpoints don't 404 there, and orphaned modules
removed from the repo don't linger in the Space tree.

routers/__init__.py CHANGED
@@ -18,9 +18,11 @@ SAME lexical position the routes used to occupy — i.e. BEFORE the SPA
18
  * a missing/broken group can NEVER take down the SPA (guarded), and
19
  * the register() functions are IMPORTED + CALLED (register-invocation-guard clean).
20
 
21
- This is REFACTOR-ONLY. No endpoint behavior changes. Parity is proven by
22
- tools_serve_split/capture_routes.py (before/after route-table fingerprint) plus the
23
- Wave-J frontier contract suite (tests/test_frontier_endpoints.py) and TestClient.
 
 
24
 
25
  The package top-level name is `routers` (not szl_*/a11oy_*), so it is intentionally
26
  OUTSIDE the guarded-import-liveness first-party scan — and the files exist anyway.
@@ -28,4 +30,9 @@ OUTSIDE the guarded-import-liveness first-party scan — and the files exist any
28
  Signed-off-by: Stephen P. Lutar Jr. <stephenlutar2@gmail.com>
29
  """
30
 
31
- __all__ = ["lambda_bounty", "research_3d", "frontier_reads"]
 
 
 
 
 
 
18
  * a missing/broken group can NEVER take down the SPA (guarded), and
19
  * the register() functions are IMPORTED + CALLED (register-invocation-guard clean).
20
 
21
+ The additive `series_a_control_plane` module is not a refactor-only route group. It
22
+ is the single governed integration seam for current estate truth, Counterfactual
23
+ Action Passports, signed receipts, and bounded one-attempt effectors. It is exported
24
+ here so invocation and package-integrity checks can prove the production module is
25
+ intentional rather than an orphaned source file.
26
 
27
  The package top-level name is `routers` (not szl_*/a11oy_*), so it is intentionally
28
  OUTSIDE the guarded-import-liveness first-party scan — and the files exist anyway.
 
30
  Signed-off-by: Stephen P. Lutar Jr. <stephenlutar2@gmail.com>
31
  """
32
 
33
+ __all__ = [
34
+ "lambda_bounty",
35
+ "research_3d",
36
+ "frontier_reads",
37
+ "series_a_control_plane",
38
+ ]
routers/frontier_reads.py CHANGED
@@ -14,7 +14,9 @@ Shared serve.py module-scope state referenced (unchanged, via `import serve`):
14
  this group, so it moves here with the routes. Registered BEFORE the /api/a11oy/
15
  {path:path} Node proxy + SPA catch-all, identical to the pre-refactor inline block.
16
 
17
- REFACTOR-ONLY: paths, methods, and payloads are byte-identical to before.
 
 
18
 
19
  Signed-off-by: Stephen P. Lutar Jr. <stephenlutar2@gmail.com>
20
  """
@@ -42,8 +44,7 @@ _A11OY_VERTICALS = [
42
 
43
 
44
  def register(app) -> dict:
45
- """Attach the frontier read route group to `app`, identically to the prior
46
- inline serve.py block. Called BEFORE the Node proxy + SPA catch-all."""
47
  import serve # shared module-scope state lives at serve module scope
48
 
49
  @app.get("/api/a11oy/v1/forecast-baseline")
@@ -60,7 +61,6 @@ def register(app) -> dict:
60
  "verticals": _A11OY_VERTICALS,
61
  "honesty": "Live = shipping pack; stub = scaffolded, roadmap."})
62
 
63
- # ---- Business Observability (5 domains) on REAL in-image data (no fabricated KPIs) ----
64
  @app.get("/api/a11oy/v1/observability/business")
65
  @app.get("/v1/observability/business")
66
  async def a11oy_business_observability_v2() -> JSONResponse:
@@ -89,8 +89,27 @@ def register(app) -> dict:
89
  "lambda_status": "Conjecture 1 (advisory)",
90
  })
91
 
92
- return {"ok": True, "ns": "a11oy", "group": "frontier-reads", "routes": [
93
- "/api/a11oy/v1/forecast-baseline", "/v1/forecast-baseline",
94
- "/api/a11oy/v1/vertical-packs", "/v1/vertical-packs",
95
- "/api/a11oy/v1/observability/business", "/v1/observability/business",
96
- ]}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  this group, so it moves here with the routes. Registered BEFORE the /api/a11oy/
15
  {path:path} Node proxy + SPA catch-all, identical to the pre-refactor inline block.
16
 
17
+ The additive Series-A controller is registered at this same pre-catch-all seam. It
18
+ keeps GET/HEAD read-only, uses explicit POSTs for refresh/evaluate/execute, and
19
+ fails one surface closed without taking down the existing frontier reads.
20
 
21
  Signed-off-by: Stephen P. Lutar Jr. <stephenlutar2@gmail.com>
22
  """
 
44
 
45
 
46
  def register(app) -> dict:
47
+ """Attach frontier reads and the additive Series-A control plane."""
 
48
  import serve # shared module-scope state lives at serve module scope
49
 
50
  @app.get("/api/a11oy/v1/forecast-baseline")
 
61
  "verticals": _A11OY_VERTICALS,
62
  "honesty": "Live = shipping pack; stub = scaffolded, roadmap."})
63
 
 
64
  @app.get("/api/a11oy/v1/observability/business")
65
  @app.get("/v1/observability/business")
66
  async def a11oy_business_observability_v2() -> JSONResponse:
 
89
  "lambda_status": "Conjecture 1 (advisory)",
90
  })
91
 
92
+ try:
93
+ from routers import series_a_control_plane as _series_a_control_plane
94
+
95
+ series_a = _series_a_control_plane.register(app, ns="a11oy")
96
+ except Exception as exc: # one additive surface must never take down A11oy
97
+ series_a = {
98
+ "ok": False,
99
+ "state": "UNAVAILABLE",
100
+ "reason": type(exc).__name__,
101
+ "effectors": [],
102
+ }
103
+
104
+ return {
105
+ "ok": True,
106
+ "ns": "a11oy",
107
+ "group": "frontier-reads",
108
+ "series_a": series_a,
109
+ "routes": [
110
+ "/api/a11oy/v1/forecast-baseline", "/v1/forecast-baseline",
111
+ "/api/a11oy/v1/vertical-packs", "/v1/vertical-packs",
112
+ "/api/a11oy/v1/observability/business", "/v1/observability/business",
113
+ "/series-a", "/api/a11oy/v1/series-a/status",
114
+ ],
115
+ }
routers/series_a_control_plane.py ADDED
@@ -0,0 +1,991 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # © 2026 Lutar, Stephen P. — SZL Holdings
3
+ """Series-A Live Control Plane for A11oy.
4
+
5
+ One additive controller combines three previously separate payload families:
6
+
7
+ * a signed, current estate truth plane;
8
+ * a bounded Counterfactual Action Passport; and
9
+ * a zero-bandaid, one-attempt local action executor.
10
+
11
+ It uses real GitHub, Hugging Face, HTTP, SQLite, and ECDSA-P256 boundaries.
12
+ GET/HEAD requests never sign or mutate state. Refresh/evaluate/execute operations
13
+ are explicit POSTs, append hash-linked receipts, and fail closed.
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import asyncio
18
+ import base64
19
+ import hashlib
20
+ import json
21
+ import os
22
+ import sqlite3
23
+ import threading
24
+ import time
25
+ import uuid
26
+ from dataclasses import dataclass
27
+ from datetime import datetime, timedelta, timezone
28
+ from pathlib import Path
29
+ from typing import Any, AsyncIterator, Callable, Mapping
30
+ from urllib.parse import urlsplit
31
+
32
+ import httpx
33
+ from fastapi import FastAPI, HTTPException, Request
34
+ from fastapi.responses import HTMLResponse, JSONResponse, Response, StreamingResponse
35
+
36
+ SCHEMA_MANIFEST = "szl.estate-manifest/v2"
37
+ SCHEMA_PASSPORT = "szl.counterfactual-action-passport/v3"
38
+ SCHEMA_RECEIPT = "szl.series-a-receipt/v1"
39
+ SCHEMA_STATUS = "szl.series-a-status/v1"
40
+ SCHEMA_TRUST = "szl.agent-trust-factor/v1"
41
+ PAYLOAD_TYPE = "application/vnd.szl.series-a-receipt.v1+json"
42
+ ORG = "szl-holdings"
43
+ HF_ORG = "SZLHOLDINGS"
44
+ CANONICAL_SPACE = f"{HF_ORG}/a11oy"
45
+ FORBIDDEN_CLONES = tuple(f"{HF_ORG}/a11oy-clone-{index}" for index in range(1, 5))
46
+ TTL_SECONDS = 300
47
+ MAX_BODY = 64 * 1024
48
+ MAX_RESPONSE_BYTES = 2 * 1024 * 1024
49
+ MAX_PAGES = 20
50
+ ALLOWED_ACTIONS = {"estate.refresh", "probe.public_surface"}
51
+ ALLOWED_PROBE_HOSTS = {
52
+ "a-11-oy.com",
53
+ "a11oy.net",
54
+ "szlholdings-a11oy.hf.space",
55
+ "szlholdings-killinchu.hf.space",
56
+ }
57
+
58
+
59
+ def _now() -> str:
60
+ return datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")
61
+
62
+
63
+ def _future(seconds: int) -> str:
64
+ return (datetime.now(timezone.utc) + timedelta(seconds=seconds)).isoformat(
65
+ timespec="milliseconds"
66
+ ).replace("+00:00", "Z")
67
+
68
+
69
+ def _canonical(value: Any) -> bytes:
70
+ """Narrow deterministic JSON for signed control-plane records."""
71
+
72
+ def walk(item: Any, path: str = "$") -> None:
73
+ if isinstance(item, float):
74
+ raise ValueError(f"{path}: floats are forbidden in signed records")
75
+ if isinstance(item, dict):
76
+ for key, child in item.items():
77
+ if not isinstance(key, str):
78
+ raise ValueError(f"{path}: keys must be strings")
79
+ lowered = key.lower()
80
+ if any(token in lowered for token in ("password", "secret_value", "private_key", "authorization")):
81
+ raise ValueError(f"{path}.{key}: secret-shaped field is forbidden")
82
+ walk(child, f"{path}.{key}")
83
+ return
84
+ if isinstance(item, list):
85
+ for index, child in enumerate(item):
86
+ walk(child, f"{path}[{index}]")
87
+ return
88
+ if item is None or isinstance(item, (str, int, bool)):
89
+ return
90
+ raise ValueError(f"{path}: unsupported type {type(item).__name__}")
91
+
92
+ walk(value)
93
+ return json.dumps(
94
+ value,
95
+ ensure_ascii=False,
96
+ sort_keys=True,
97
+ separators=(",", ":"),
98
+ allow_nan=False,
99
+ ).encode("utf-8")
100
+
101
+
102
+ def _sha(value: Any) -> str:
103
+ payload = value if isinstance(value, (bytes, bytearray)) else _canonical(value)
104
+ return hashlib.sha256(payload).hexdigest()
105
+
106
+
107
+ def _pae(payload_type: str, payload: bytes) -> bytes:
108
+ ptype = payload_type.encode("utf-8")
109
+ return b"DSSEv1 " + str(len(ptype)).encode() + b" " + ptype + b" " + str(len(payload)).encode() + b" " + payload
110
+
111
+
112
+ def _safe_error(exc: Exception) -> dict[str, str]:
113
+ return {"error_class": type(exc).__name__, "error": str(exc)[:240]}
114
+
115
+
116
+ def _git_revision() -> str:
117
+ for key in ("SZL_GIT_SHA", "A11OY_GIT_SHA", "GITHUB_SHA"):
118
+ value = (os.environ.get(key) or "").strip().lower()
119
+ if len(value) == 40 and all(ch in "0123456789abcdef" for ch in value):
120
+ return value
121
+ return "UNKNOWN"
122
+
123
+
124
+ class ReceiptSigner:
125
+ def __init__(self) -> None:
126
+ self.private_key = None
127
+ self.public_pem = ""
128
+ self.source = "unavailable"
129
+ self.error = ""
130
+ try:
131
+ from a11oy_signing_key import load_signing_key
132
+
133
+ private_key, public_pem, source, error = load_signing_key()
134
+ self.private_key = private_key
135
+ self.public_pem = public_pem or ""
136
+ self.source = source or "unavailable"
137
+ self.error = error or ""
138
+ except Exception as exc:
139
+ self.error = f"{type(exc).__name__}: {str(exc)[:180]}"
140
+
141
+ @property
142
+ def keyid(self) -> str | None:
143
+ return _sha(self.public_pem.encode("utf-8")) if self.public_pem else None
144
+
145
+ def sign(self, payload: Mapping[str, Any]) -> dict[str, Any]:
146
+ body = _canonical(dict(payload))
147
+ envelope: dict[str, Any] = {
148
+ "payloadType": PAYLOAD_TYPE,
149
+ "payload": base64.b64encode(body).decode("ascii"),
150
+ "signatures": [],
151
+ "pae_sha256": hashlib.sha256(_pae(PAYLOAD_TYPE, body)).hexdigest(),
152
+ "key_source": self.source,
153
+ }
154
+ if self.private_key is None:
155
+ envelope["signature_status"] = "UNSIGNED_UNAVAILABLE"
156
+ envelope["signature_error"] = self.error or "signing key unavailable"
157
+ return envelope
158
+ try:
159
+ from cryptography.hazmat.primitives import hashes
160
+ from cryptography.hazmat.primitives.asymmetric import ec
161
+
162
+ signature = self.private_key.sign(
163
+ _pae(PAYLOAD_TYPE, body), ec.ECDSA(hashes.SHA256())
164
+ )
165
+ envelope["signatures"] = [
166
+ {
167
+ "keyid": self.keyid,
168
+ "sig": base64.b64encode(signature).decode("ascii"),
169
+ }
170
+ ]
171
+ envelope["signature_status"] = "SIGNED"
172
+ return envelope
173
+ except Exception as exc:
174
+ envelope["signature_status"] = "UNSIGNED_ERROR"
175
+ envelope["signature_error"] = f"{type(exc).__name__}: {str(exc)[:180]}"
176
+ return envelope
177
+
178
+
179
+ class Store:
180
+ def __init__(self, requested_path: str | None = None) -> None:
181
+ self.path = self._resolve_path(requested_path)
182
+ self.lock = threading.RLock()
183
+ self._init()
184
+
185
+ @staticmethod
186
+ def _resolve_path(requested: str | None) -> str:
187
+ candidates = [
188
+ requested or os.environ.get("A11OY_SERIES_A_DB") or "/data/series-a/control-plane.sqlite3",
189
+ "/tmp/a11oy_series_a_control_plane.sqlite3",
190
+ ]
191
+ for candidate in candidates:
192
+ try:
193
+ path = Path(candidate)
194
+ path.parent.mkdir(parents=True, exist_ok=True)
195
+ with path.parent.joinpath(".write-probe").open("w", encoding="utf-8") as probe:
196
+ probe.write("ok")
197
+ path.parent.joinpath(".write-probe").unlink(missing_ok=True)
198
+ return str(path)
199
+ except Exception:
200
+ continue
201
+ raise RuntimeError("no writable SQLite location")
202
+
203
+ def connect(self) -> sqlite3.Connection:
204
+ connection = sqlite3.connect(self.path, timeout=30)
205
+ connection.row_factory = sqlite3.Row
206
+ connection.execute("PRAGMA foreign_keys=ON")
207
+ connection.execute("PRAGMA journal_mode=WAL")
208
+ connection.execute("PRAGMA synchronous=FULL")
209
+ return connection
210
+
211
+ def _init(self) -> None:
212
+ with self.lock, self.connect() as db:
213
+ db.executescript(
214
+ """
215
+ CREATE TABLE IF NOT EXISTS snapshots(
216
+ digest TEXT PRIMARY KEY,
217
+ payload TEXT NOT NULL,
218
+ envelope TEXT NOT NULL,
219
+ observed_at TEXT NOT NULL,
220
+ valid_until TEXT NOT NULL
221
+ );
222
+ CREATE TABLE IF NOT EXISTS passports(
223
+ digest TEXT PRIMARY KEY,
224
+ payload TEXT NOT NULL,
225
+ decision TEXT NOT NULL,
226
+ attempts INTEGER NOT NULL DEFAULT 0 CHECK(attempts BETWEEN 0 AND 1),
227
+ created_at TEXT NOT NULL
228
+ );
229
+ CREATE TABLE IF NOT EXISTS receipts(
230
+ sequence INTEGER PRIMARY KEY AUTOINCREMENT,
231
+ receipt_id TEXT NOT NULL UNIQUE,
232
+ kind TEXT NOT NULL,
233
+ payload TEXT NOT NULL,
234
+ envelope TEXT NOT NULL,
235
+ previous_hash TEXT NOT NULL,
236
+ receipt_hash TEXT NOT NULL UNIQUE,
237
+ created_at TEXT NOT NULL
238
+ );
239
+ CREATE TABLE IF NOT EXISTS events(
240
+ sequence INTEGER PRIMARY KEY AUTOINCREMENT,
241
+ event_id TEXT NOT NULL UNIQUE,
242
+ kind TEXT NOT NULL,
243
+ payload TEXT NOT NULL,
244
+ created_at TEXT NOT NULL
245
+ );
246
+ """
247
+ )
248
+
249
+ def append_event(self, kind: str, payload: Mapping[str, Any]) -> None:
250
+ with self.lock, self.connect() as db:
251
+ db.execute(
252
+ "INSERT INTO events(event_id,kind,payload,created_at) VALUES(?,?,?,?)",
253
+ (f"evt_{uuid.uuid4().hex}", kind, json.dumps(dict(payload), sort_keys=True), _now()),
254
+ )
255
+
256
+ def events_since(self, sequence: int, limit: int = 100) -> list[dict[str, Any]]:
257
+ with self.lock, self.connect() as db:
258
+ rows = db.execute(
259
+ "SELECT sequence,event_id,kind,payload,created_at FROM events WHERE sequence>? ORDER BY sequence LIMIT ?",
260
+ (max(0, sequence), max(1, min(limit, 500))),
261
+ ).fetchall()
262
+ return [
263
+ {
264
+ "sequence": row["sequence"],
265
+ "event_id": row["event_id"],
266
+ "kind": row["kind"],
267
+ "payload": json.loads(row["payload"]),
268
+ "created_at": row["created_at"],
269
+ }
270
+ for row in rows
271
+ ]
272
+
273
+ def append_receipt(
274
+ self, kind: str, payload: Mapping[str, Any], signer: ReceiptSigner
275
+ ) -> dict[str, Any]:
276
+ with self.lock, self.connect() as db:
277
+ row = db.execute(
278
+ "SELECT receipt_hash FROM receipts ORDER BY sequence DESC LIMIT 1"
279
+ ).fetchone()
280
+ previous = row["receipt_hash"] if row else "0" * 64
281
+ receipt = {
282
+ "schema": SCHEMA_RECEIPT,
283
+ "receipt_id": f"rcpt_{uuid.uuid4().hex}",
284
+ "kind": kind,
285
+ "created_at": _now(),
286
+ "source_revision": _git_revision(),
287
+ "previous_receipt_hash": previous,
288
+ "payload": dict(payload),
289
+ }
290
+ envelope = signer.sign(receipt)
291
+ receipt_hash = _sha(envelope)
292
+ db.execute(
293
+ """INSERT INTO receipts(receipt_id,kind,payload,envelope,previous_hash,receipt_hash,created_at)
294
+ VALUES(?,?,?,?,?,?,?)""",
295
+ (
296
+ receipt["receipt_id"],
297
+ kind,
298
+ json.dumps(receipt, sort_keys=True),
299
+ json.dumps(envelope, sort_keys=True),
300
+ previous,
301
+ receipt_hash,
302
+ receipt["created_at"],
303
+ ),
304
+ )
305
+ self.append_event(kind, {"receipt_hash": receipt_hash, "receipt_id": receipt["receipt_id"]})
306
+ return {"receipt": receipt, "envelope": envelope, "receipt_hash": receipt_hash}
307
+
308
+ def list_receipts(self, limit: int = 50) -> list[dict[str, Any]]:
309
+ with self.lock, self.connect() as db:
310
+ rows = db.execute(
311
+ "SELECT sequence,kind,payload,envelope,receipt_hash,created_at FROM receipts ORDER BY sequence DESC LIMIT ?",
312
+ (max(1, min(limit, 200)),),
313
+ ).fetchall()
314
+ return [
315
+ {
316
+ "sequence": row["sequence"],
317
+ "kind": row["kind"],
318
+ "receipt": json.loads(row["payload"]),
319
+ "envelope": json.loads(row["envelope"]),
320
+ "receipt_hash": row["receipt_hash"],
321
+ "created_at": row["created_at"],
322
+ }
323
+ for row in rows
324
+ ]
325
+
326
+ def save_snapshot(self, manifest: Mapping[str, Any], envelope: Mapping[str, Any]) -> str:
327
+ digest = _sha(manifest)
328
+ with self.lock, self.connect() as db:
329
+ db.execute(
330
+ "INSERT OR REPLACE INTO snapshots(digest,payload,envelope,observed_at,valid_until) VALUES(?,?,?,?,?)",
331
+ (
332
+ digest,
333
+ json.dumps(dict(manifest), sort_keys=True),
334
+ json.dumps(dict(envelope), sort_keys=True),
335
+ manifest["observed_at"],
336
+ manifest["valid_until"],
337
+ ),
338
+ )
339
+ return digest
340
+
341
+ def latest_snapshot(self) -> dict[str, Any] | None:
342
+ with self.lock, self.connect() as db:
343
+ row = db.execute(
344
+ "SELECT digest,payload,envelope,observed_at,valid_until FROM snapshots ORDER BY observed_at DESC LIMIT 1"
345
+ ).fetchone()
346
+ if row is None:
347
+ return None
348
+ return {
349
+ "digest": row["digest"],
350
+ "manifest": json.loads(row["payload"]),
351
+ "envelope": json.loads(row["envelope"]),
352
+ "observed_at": row["observed_at"],
353
+ "valid_until": row["valid_until"],
354
+ }
355
+
356
+ def save_passport(self, passport: Mapping[str, Any]) -> str:
357
+ digest = _sha(passport)
358
+ with self.lock, self.connect() as db:
359
+ db.execute(
360
+ "INSERT INTO passports(digest,payload,decision,attempts,created_at) VALUES(?,?,?,?,?)",
361
+ (digest, json.dumps(dict(passport), sort_keys=True), passport["decision"], 0, passport["created_at"]),
362
+ )
363
+ return digest
364
+
365
+ def load_passport(self, digest: str) -> dict[str, Any] | None:
366
+ with self.lock, self.connect() as db:
367
+ row = db.execute(
368
+ "SELECT payload,decision,attempts FROM passports WHERE digest=?", (digest,)
369
+ ).fetchone()
370
+ if row is None:
371
+ return None
372
+ value = json.loads(row["payload"])
373
+ value["attempts"] = row["attempts"]
374
+ return value
375
+
376
+ def consume_attempt(self, digest: str) -> None:
377
+ with self.lock, self.connect() as db:
378
+ result = db.execute(
379
+ "UPDATE passports SET attempts=1 WHERE digest=? AND attempts=0", (digest,)
380
+ )
381
+ if result.rowcount != 1:
382
+ raise RuntimeError("passport attempt is absent or already consumed")
383
+
384
+
385
+ @dataclass
386
+ class Observation:
387
+ state: str
388
+ value: Any = None
389
+ detail: Mapping[str, Any] | None = None
390
+
391
+ def as_dict(self) -> dict[str, Any]:
392
+ value = {"state": self.state}
393
+ if self.value is not None:
394
+ value["value"] = self.value
395
+ if self.detail:
396
+ value["detail"] = dict(self.detail)
397
+ return value
398
+
399
+
400
+ class Collector:
401
+ def __init__(self) -> None:
402
+ self.github_token = (os.environ.get("GITHUB_TOKEN") or "").strip()
403
+ self.hf_token = (os.environ.get("HF_TOKEN") or "").strip()
404
+
405
+ async def _json(
406
+ self,
407
+ client: httpx.AsyncClient,
408
+ url: str,
409
+ *,
410
+ params: Mapping[str, Any] | None = None,
411
+ allowed_host: str,
412
+ ) -> tuple[Any, httpx.Response]:
413
+ parsed = urlsplit(url)
414
+ if parsed.scheme != "https" or parsed.hostname != allowed_host or parsed.username or parsed.password:
415
+ raise RuntimeError("outbound URL left the fixed HTTPS origin")
416
+ response = await client.get(url, params=params)
417
+ if response.status_code != 200:
418
+ raise RuntimeError(f"HTTP {response.status_code}")
419
+ if len(response.content) > MAX_RESPONSE_BYTES:
420
+ raise RuntimeError("response exceeded byte limit")
421
+ final = urlsplit(str(response.url))
422
+ if final.scheme != "https" or final.hostname != allowed_host:
423
+ raise RuntimeError("redirect left the fixed HTTPS origin")
424
+ return response.json(), response
425
+
426
+ async def github(self) -> Observation:
427
+ headers = {"accept": "application/vnd.github+json", "user-agent": "szl-series-a/1"}
428
+ if self.github_token:
429
+ headers["authorization"] = f"Bearer {self.github_token}"
430
+ try:
431
+ repos: list[dict[str, Any]] = []
432
+ async with httpx.AsyncClient(headers=headers, timeout=30, follow_redirects=False) as client:
433
+ complete = False
434
+ for page in range(1, MAX_PAGES + 1):
435
+ values, _ = await self._json(
436
+ client,
437
+ f"https://api.github.com/orgs/{ORG}/repos",
438
+ params={"type": "all", "per_page": 100, "page": page},
439
+ allowed_host="api.github.com",
440
+ )
441
+ if not isinstance(values, list):
442
+ raise RuntimeError("repository listing was not an array")
443
+ repos.extend(item for item in values if isinstance(item, dict))
444
+ if len(values) < 100:
445
+ complete = True
446
+ break
447
+ if not complete:
448
+ raise RuntimeError("repository pagination exceeded bounded window")
449
+ pr_data, _ = await self._json(
450
+ client,
451
+ "https://api.github.com/search/issues",
452
+ params={"q": f"org:{ORG} is:pr is:open", "per_page": 1},
453
+ allowed_host="api.github.com",
454
+ )
455
+ rows = [
456
+ {
457
+ "name": str(item.get("name") or ""),
458
+ "archived": bool(item.get("archived")),
459
+ "visibility": str(item.get("visibility") or "unknown"),
460
+ "default_branch": str(item.get("default_branch") or ""),
461
+ "updated_at": str(item.get("updated_at") or ""),
462
+ }
463
+ for item in repos
464
+ ]
465
+ return Observation(
466
+ "OBSERVED",
467
+ {
468
+ "repository_count": len(rows),
469
+ "open_pull_request_count": int((pr_data or {}).get("total_count", 0)),
470
+ "pagination_complete": True,
471
+ "repositories": rows,
472
+ },
473
+ {"authenticated": bool(self.github_token)},
474
+ )
475
+ except Exception as exc:
476
+ return Observation("UNAVAILABLE", detail=_safe_error(exc))
477
+
478
+ def _hf_list(self, method_name: str, kwargs: Mapping[str, Any]) -> list[Any]:
479
+ from huggingface_hub import HfApi
480
+
481
+ api = HfApi(token=self.hf_token or None)
482
+ method = getattr(api, method_name, None)
483
+ if method is None:
484
+ raise AttributeError(f"HfApi.{method_name} unavailable")
485
+ return list(method(**dict(kwargs)))
486
+
487
+ async def _hf_kernels(self) -> list[dict[str, Any]]:
488
+ headers = {"accept": "application/json", "user-agent": "szl-series-a/1"}
489
+ if self.hf_token:
490
+ headers["authorization"] = f"Bearer {self.hf_token}"
491
+ output: list[dict[str, Any]] = []
492
+ url: str | None = "https://huggingface.co/api/kernels"
493
+ params: Mapping[str, Any] | None = {"author": HF_ORG, "limit": 1000, "full": "true"}
494
+ async with httpx.AsyncClient(headers=headers, timeout=30, follow_redirects=False) as client:
495
+ for _ in range(MAX_PAGES):
496
+ if not url:
497
+ return output
498
+ values, response = await self._json(
499
+ client, url, params=params, allowed_host="huggingface.co"
500
+ )
501
+ if not isinstance(values, list):
502
+ raise RuntimeError("kernel listing was not an array")
503
+ output.extend(item for item in values if isinstance(item, dict))
504
+ link = response.links.get("next") or {}
505
+ url = link.get("url") if isinstance(link, dict) else None
506
+ params = None
507
+ if not url:
508
+ return output
509
+ raise RuntimeError("kernel pagination exceeded bounded window")
510
+
511
+ async def huggingface(self) -> Observation:
512
+ categories: dict[str, Any] = {}
513
+ errors: dict[str, Any] = {}
514
+ methods = {
515
+ "models": ("list_models", {"author": HF_ORG}),
516
+ "datasets": ("list_datasets", {"author": HF_ORG}),
517
+ "spaces": ("list_spaces", {"author": HF_ORG}),
518
+ "collections": ("list_collections", {"owner": HF_ORG}),
519
+ "buckets": ("list_buckets", {"namespace": HF_ORG}),
520
+ }
521
+ for name, (method, kwargs) in methods.items():
522
+ try:
523
+ items = await asyncio.to_thread(self._hf_list, method, kwargs)
524
+ rows = []
525
+ for item in items:
526
+ item_id = None
527
+ for field in ("id", "repo_id", "name", "slug"):
528
+ candidate = item.get(field) if isinstance(item, dict) else getattr(item, field, None)
529
+ if isinstance(candidate, str) and candidate:
530
+ item_id = candidate
531
+ break
532
+ rows.append({"id": item_id})
533
+ categories[name] = {"state": "OBSERVED", "count": len(rows), "items": rows}
534
+ except Exception as exc:
535
+ categories[name] = {"state": "UNAVAILABLE"}
536
+ errors[name] = _safe_error(exc)
537
+ try:
538
+ kernels = await self._hf_kernels()
539
+ categories["kernels"] = {
540
+ "state": "OBSERVED",
541
+ "count": len(kernels),
542
+ "items": [{"id": str(item.get("id") or item.get("repo_id") or "")} for item in kernels],
543
+ }
544
+ except Exception as exc:
545
+ categories["kernels"] = {"state": "UNAVAILABLE"}
546
+ errors["kernels"] = _safe_error(exc)
547
+
548
+ space_ids = {
549
+ row.get("id")
550
+ for row in categories.get("spaces", {}).get("items", [])
551
+ if isinstance(row, dict)
552
+ }
553
+ clones_present = sorted(value for value in FORBIDDEN_CLONES if value in space_ids)
554
+ canonical_present = CANONICAL_SPACE in space_ids
555
+ state = "OBSERVED" if categories.get("spaces", {}).get("state") == "OBSERVED" else "PARTIAL"
556
+ return Observation(
557
+ state,
558
+ {
559
+ "categories": categories,
560
+ "canonical_space": CANONICAL_SPACE,
561
+ "canonical_present": canonical_present,
562
+ "forbidden_clones_present": clones_present,
563
+ "singleton_ok": canonical_present and not clones_present,
564
+ },
565
+ {"authenticated": bool(self.hf_token), "errors": errors},
566
+ )
567
+
568
+ async def collect(self) -> dict[str, Any]:
569
+ github, hf = await asyncio.gather(self.github(), self.huggingface())
570
+ critical_failures: list[str] = []
571
+ if github.state != "OBSERVED":
572
+ critical_failures.append("github_inventory_unavailable")
573
+ if hf.state not in {"OBSERVED", "PARTIAL"}:
574
+ critical_failures.append("huggingface_inventory_unavailable")
575
+ hf_value = hf.value if isinstance(hf.value, dict) else {}
576
+ if hf_value and not hf_value.get("singleton_ok"):
577
+ critical_failures.append("canonical_a11oy_singleton_failed")
578
+ categories = hf_value.get("categories", {}) if isinstance(hf_value, dict) else {}
579
+ counts = {
580
+ name: value.get("count") if isinstance(value, dict) and value.get("state") == "OBSERVED" else None
581
+ for name, value in categories.items()
582
+ }
583
+ manifest = {
584
+ "schema": SCHEMA_MANIFEST,
585
+ "observed_at": _now(),
586
+ "valid_until": _future(TTL_SECONDS),
587
+ "source_revision": _git_revision(),
588
+ "organization": ORG,
589
+ "huggingface_organization": HF_ORG,
590
+ "status": "BLOCKED" if critical_failures else "OBSERVED",
591
+ "critical_failures": critical_failures,
592
+ "github": github.as_dict(),
593
+ "huggingface": hf.as_dict(),
594
+ "counts": {
595
+ "github_repositories": (
596
+ github.value.get("repository_count")
597
+ if isinstance(github.value, dict) and github.state == "OBSERVED"
598
+ else None
599
+ ),
600
+ "github_open_pull_requests": (
601
+ github.value.get("open_pull_request_count")
602
+ if isinstance(github.value, dict) and github.state == "OBSERVED"
603
+ else None
604
+ ),
605
+ **counts,
606
+ },
607
+ "claim": "CURRENT_OBSERVATION_NOT_ETERNAL_TRUTH",
608
+ "counterfactual_label": "MODELED",
609
+ "private_reasoning_collected": False,
610
+ }
611
+ manifest["manifest_digest"] = _sha(manifest)
612
+ return manifest
613
+
614
+
615
+ class Service:
616
+ def __init__(self, db_path: str | None = None) -> None:
617
+ self.store = Store(db_path)
618
+ self.signer = ReceiptSigner()
619
+ self.collector = Collector()
620
+ self.refresh_lock = asyncio.Lock()
621
+ self.started = False
622
+ self.background_task: asyncio.Task[Any] | None = None
623
+
624
+ async def start(self) -> None:
625
+ if self.started:
626
+ return
627
+ self.started = True
628
+ if (os.environ.get("A11OY_SERIES_A_STARTUP_REFRESH") or "1").strip() == "0":
629
+ self.store.append_event("estate.refresh.skipped", {"reason": "explicit test/runtime configuration"})
630
+ return
631
+
632
+ async def run() -> None:
633
+ try:
634
+ await self.refresh("startup")
635
+ except Exception as exc:
636
+ self.store.append_event("estate.refresh.failed", _safe_error(exc))
637
+
638
+ self.background_task = asyncio.create_task(run(), name="a11oy-series-a-startup-refresh")
639
+
640
+ async def refresh(self, actor: str) -> dict[str, Any]:
641
+ if self.refresh_lock.locked():
642
+ raise HTTPException(status_code=409, detail="estate refresh already running")
643
+ async with self.refresh_lock:
644
+ manifest = await self.collector.collect()
645
+ envelope = self.signer.sign(manifest)
646
+ digest = self.store.save_snapshot(manifest, envelope)
647
+ receipt = self.store.append_receipt(
648
+ "estate.refresh",
649
+ {
650
+ "actor": actor,
651
+ "manifest_digest": digest,
652
+ "status": manifest["status"],
653
+ "counts": manifest["counts"],
654
+ },
655
+ self.signer,
656
+ )
657
+ return {"manifest": manifest, "envelope": envelope, "refresh_receipt": receipt}
658
+
659
+ def latest_status(self) -> dict[str, Any]:
660
+ latest = self.store.latest_snapshot()
661
+ if latest is None:
662
+ return {
663
+ "schema": SCHEMA_STATUS,
664
+ "state": "PENDING",
665
+ "terminal": True,
666
+ "source_revision": _git_revision(),
667
+ "signing_key_source": self.signer.source,
668
+ "database": self.store.path,
669
+ "detail": "no completed refresh is persisted yet",
670
+ }
671
+ valid_until = datetime.fromisoformat(latest["valid_until"].replace("Z", "+00:00"))
672
+ stale = datetime.now(timezone.utc) >= valid_until
673
+ manifest = latest["manifest"]
674
+ return {
675
+ "schema": SCHEMA_STATUS,
676
+ "state": "STALE" if stale else manifest["status"],
677
+ "terminal": True,
678
+ "source_revision": _git_revision(),
679
+ "manifest_digest": latest["digest"],
680
+ "observed_at": latest["observed_at"],
681
+ "valid_until": latest["valid_until"],
682
+ "counts": manifest.get("counts", {}),
683
+ "critical_failures": manifest.get("critical_failures", []),
684
+ "signature_status": latest["envelope"].get("signature_status"),
685
+ "signing_key_source": self.signer.source,
686
+ "database": self.store.path,
687
+ }
688
+
689
+ def evaluate_passport(self, body: Mapping[str, Any]) -> dict[str, Any]:
690
+ action = body.get("action")
691
+ if not isinstance(action, dict):
692
+ raise HTTPException(status_code=422, detail="action must be an object")
693
+ action_type = str(action.get("type") or "")
694
+ target = str(action.get("target") or "")
695
+ impact = str(action.get("impact") or "MODERATE").upper()
696
+ irreversible = bool(action.get("irreversible", False))
697
+ if action_type not in ALLOWED_ACTIONS:
698
+ decision = "BLOCK"
699
+ reasons = ["ACTION_TYPE_NOT_ALLOWLISTED"]
700
+ elif not target:
701
+ decision = "BLOCK"
702
+ reasons = ["TARGET_REQUIRED"]
703
+ elif impact in {"HIGH", "CRITICAL"} or irreversible:
704
+ decision = "REQUIRE_APPROVAL"
705
+ reasons = ["INDEPENDENT_APPROVAL_REQUIRED"]
706
+ else:
707
+ decision = "ALLOW"
708
+ reasons = ["BOUNDED_REVERSIBLE_ACTION"]
709
+
710
+ evidence = body.get("evidence")
711
+ if not isinstance(evidence, list) or not evidence:
712
+ decision = "BLOCK"
713
+ reasons = sorted(set(reasons + ["EVIDENCE_REQUIRED"]))
714
+ else:
715
+ for item in evidence:
716
+ if not isinstance(item, dict) or item.get("label") in {"UNKNOWN", "UNAVAILABLE"}:
717
+ decision = "BLOCK"
718
+ reasons = sorted(set(reasons + ["NON_ACTIONABLE_EVIDENCE"]))
719
+ break
720
+
721
+ no_action = {
722
+ "scenario_id": "no-action",
723
+ "kind": "NO_ACTION",
724
+ "label": "MODELED",
725
+ "outcome": str(body.get("expected_if_withheld") or "current state persists"),
726
+ }
727
+ proposed = {
728
+ "scenario_id": "proposed-action",
729
+ "kind": "PROPOSED_ACTION",
730
+ "label": "MODELED",
731
+ "outcome": str(body.get("expected_if_acted") or "bounded action completes or fails closed"),
732
+ }
733
+ passport = {
734
+ "schema": SCHEMA_PASSPORT,
735
+ "passport_id": f"cap_{uuid.uuid4().hex}",
736
+ "created_at": _now(),
737
+ "source_revision": _git_revision(),
738
+ "subject": {
739
+ "principal_id": str(body.get("principal_id") or "anonymous-proposer"),
740
+ "workload_id": str(body.get("workload_id") or "a11oy-series-a"),
741
+ },
742
+ "action": action,
743
+ "action_digest": _sha(action),
744
+ "evidence": evidence if isinstance(evidence, list) else [],
745
+ "counterfactuals": [no_action, proposed],
746
+ "decision": decision,
747
+ "reason_codes": reasons,
748
+ "max_attempts": 1,
749
+ "private_reasoning_collected": False,
750
+ }
751
+ digest = self.store.save_passport(passport)
752
+ receipt = self.store.append_receipt(
753
+ "passport.evaluate",
754
+ {"passport_digest": digest, "decision": decision, "reason_codes": reasons},
755
+ self.signer,
756
+ )
757
+ return {"passport": passport, "passport_digest": digest, "decision_receipt": receipt}
758
+
759
+ async def execute(self, body: Mapping[str, Any]) -> dict[str, Any]:
760
+ digest = str(body.get("passport_digest") or "")
761
+ passport = self.store.load_passport(digest)
762
+ if passport is None:
763
+ raise HTTPException(status_code=404, detail="passport not found")
764
+ if passport["attempts"] != 0:
765
+ raise HTTPException(status_code=409, detail="passport attempt already consumed")
766
+ if passport["decision"] != "ALLOW":
767
+ raise HTTPException(status_code=403, detail=f"passport decision is {passport['decision']}")
768
+ self.store.consume_attempt(digest)
769
+ action = passport["action"]
770
+ started = _now()
771
+ try:
772
+ if action["type"] == "estate.refresh":
773
+ result = await self.refresh(passport["passport_id"])
774
+ outcome = {
775
+ "status": "SUCCEEDED",
776
+ "manifest_digest": result["manifest"]["manifest_digest"],
777
+ "estate_status": result["manifest"]["status"],
778
+ }
779
+ elif action["type"] == "probe.public_surface":
780
+ outcome = await self._probe(str(action["target"]))
781
+ else:
782
+ raise RuntimeError("action left allowlist after authorization")
783
+ except Exception as exc:
784
+ outcome = {"status": "FAILED", **_safe_error(exc)}
785
+ outcome.update(
786
+ {
787
+ "started_at": started,
788
+ "completed_at": _now(),
789
+ "attempt": 1,
790
+ "max_attempts": 1,
791
+ "passport_digest": digest,
792
+ }
793
+ )
794
+ receipt = self.store.append_receipt("passport.outcome", outcome, self.signer)
795
+ return {"outcome": outcome, "outcome_receipt": receipt}
796
+
797
+ async def _probe(self, target: str) -> dict[str, Any]:
798
+ parsed = urlsplit(target)
799
+ if parsed.scheme != "https" or parsed.hostname not in ALLOWED_PROBE_HOSTS or parsed.username or parsed.password:
800
+ raise RuntimeError("probe target is not in the fixed HTTPS allowlist")
801
+ start = time.monotonic()
802
+ async with httpx.AsyncClient(timeout=15, follow_redirects=False) as client:
803
+ response = await client.get(target, headers={"accept": "application/json,text/html;q=0.9"})
804
+ final = urlsplit(str(response.url))
805
+ if final.hostname not in ALLOWED_PROBE_HOSTS:
806
+ raise RuntimeError("probe redirect left the allowlist")
807
+ return {
808
+ "status": "SUCCEEDED" if 200 <= response.status_code < 400 else "FAILED",
809
+ "target": target,
810
+ "http_status": response.status_code,
811
+ "latency_ms": int((time.monotonic() - start) * 1000),
812
+ "bytes": len(response.content),
813
+ "content_type": response.headers.get("content-type", ""),
814
+ }
815
+
816
+ def trust_factor(self) -> dict[str, Any]:
817
+ receipts = self.store.list_receipts(200)
818
+ decisions = [
819
+ item["receipt"]["payload"].get("decision")
820
+ for item in receipts
821
+ if item["kind"] == "passport.evaluate"
822
+ ]
823
+ counts = {name: decisions.count(name) for name in ("ALLOW", "BLOCK", "REQUIRE_APPROVAL")}
824
+ total = sum(counts.values())
825
+ penalty = counts["BLOCK"] * 10 + counts["REQUIRE_APPROVAL"] * 3
826
+ score = 100 if total == 0 else max(0, 100 - (penalty * 100 // max(1, total * 10)))
827
+ return {
828
+ "schema": SCHEMA_TRUST,
829
+ "state": "OBSERVED",
830
+ "total_evaluations": total,
831
+ "counts": counts,
832
+ "score_0_to_100": score,
833
+ "basis": "local signed passport decision receipts",
834
+ "not_a_security_certification": True,
835
+ }
836
+
837
+
838
+ async def _bounded_json(request: Request) -> dict[str, Any]:
839
+ content_type = request.headers.get("content-type", "").split(";", 1)[0].lower()
840
+ if content_type != "application/json":
841
+ raise HTTPException(status_code=415, detail="content-type must be application/json")
842
+ declared = request.headers.get("content-length")
843
+ if declared:
844
+ try:
845
+ if int(declared) > MAX_BODY:
846
+ raise HTTPException(status_code=413, detail="request exceeds 64 KiB")
847
+ except ValueError as exc:
848
+ raise HTTPException(status_code=400, detail="invalid content-length") from exc
849
+ body = await request.body()
850
+ if len(body) > MAX_BODY:
851
+ raise HTTPException(status_code=413, detail="request exceeds 64 KiB")
852
+ try:
853
+ value = json.loads(body.decode("utf-8"))
854
+ except Exception as exc:
855
+ raise HTTPException(status_code=400, detail="request must be UTF-8 JSON") from exc
856
+ if not isinstance(value, dict):
857
+ raise HTTPException(status_code=422, detail="request must be one JSON object")
858
+ return value
859
+
860
+
861
+ def _asset(name: str) -> str:
862
+ path = Path(__file__).resolve().parent / "series_a_web" / name
863
+ if not path.is_file():
864
+ raise HTTPException(status_code=404, detail=f"asset missing: {name}")
865
+ return path.read_text(encoding="utf-8")
866
+
867
+
868
+ def register(app: FastAPI, ns: str = "a11oy", *, db_path: str | None = None) -> dict[str, Any]:
869
+ if any(getattr(route, "path", None) == f"/api/{ns}/v1/series-a/status" for route in app.router.routes):
870
+ return {"ok": True, "state": "ALREADY_REGISTERED", "routes": []}
871
+
872
+ service = Service(db_path)
873
+ prefix = f"/api/{ns}/v1/series-a"
874
+
875
+ async def page(request: Request) -> Response:
876
+ if request.method == "HEAD":
877
+ return Response(status_code=200, media_type="text/html")
878
+ return HTMLResponse(_asset("index.html"), headers={"cache-control": "no-store"})
879
+
880
+ async def js(request: Request) -> Response:
881
+ if request.method == "HEAD":
882
+ return Response(status_code=200, media_type="application/javascript")
883
+ return Response(_asset("app.js"), media_type="application/javascript", headers={"cache-control": "public,max-age=300"})
884
+
885
+ async def css(request: Request) -> Response:
886
+ if request.method == "HEAD":
887
+ return Response(status_code=200, media_type="text/css")
888
+ return Response(_asset("styles.css"), media_type="text/css", headers={"cache-control": "public,max-age=300"})
889
+
890
+ async def status(request: Request) -> Response:
891
+ payload = service.latest_status()
892
+ if request.method == "HEAD":
893
+ return Response(status_code=200, media_type="application/json")
894
+ return JSONResponse(payload, headers={"cache-control": "no-store"})
895
+
896
+ async def manifest(request: Request) -> Response:
897
+ latest = service.store.latest_snapshot()
898
+ if latest is None:
899
+ payload = {"schema": SCHEMA_MANIFEST, "status": "PENDING", "terminal": True}
900
+ else:
901
+ payload = latest
902
+ if request.method == "HEAD":
903
+ return Response(status_code=200, media_type="application/json")
904
+ return JSONResponse(payload, headers={"cache-control": "no-store"})
905
+
906
+ async def refresh(request: Request) -> Response:
907
+ body = await _bounded_json(request)
908
+ actor = str(body.get("actor") or "operator")[:120]
909
+ return JSONResponse(await service.refresh(actor))
910
+
911
+ async def evaluate(request: Request) -> Response:
912
+ return JSONResponse(service.evaluate_passport(await _bounded_json(request)))
913
+
914
+ async def execute(request: Request) -> Response:
915
+ return JSONResponse(await service.execute(await _bounded_json(request)))
916
+
917
+ async def receipts(request: Request) -> Response:
918
+ if request.method == "HEAD":
919
+ return Response(status_code=200, media_type="application/json")
920
+ return JSONResponse({"schema": "szl.series-a-receipts/v1", "items": service.store.list_receipts(50)})
921
+
922
+ async def trust(request: Request) -> Response:
923
+ if request.method == "HEAD":
924
+ return Response(status_code=200, media_type="application/json")
925
+ return JSONResponse(service.trust_factor())
926
+
927
+ async def public_key(request: Request) -> Response:
928
+ if request.method == "HEAD":
929
+ return Response(status_code=200, media_type="text/plain")
930
+ if not service.signer.public_pem:
931
+ return JSONResponse({"state": "UNAVAILABLE", "reason": service.signer.error}, status_code=503)
932
+ return Response(service.signer.public_pem, media_type="text/plain", headers={"cache-control": "public,max-age=300"})
933
+
934
+ async def events(request: Request) -> StreamingResponse:
935
+ last = int(request.query_params.get("after", "0") or 0)
936
+
937
+ async def generate() -> AsyncIterator[bytes]:
938
+ cursor = max(0, last)
939
+ for _ in range(120):
940
+ values = service.store.events_since(cursor)
941
+ for event in values:
942
+ cursor = event["sequence"]
943
+ yield f"id: {cursor}\nevent: {event['kind']}\ndata: {json.dumps(event, separators=(',', ':'))}\n\n".encode()
944
+ if await request.is_disconnected():
945
+ break
946
+ yield b": heartbeat\n\n"
947
+ await asyncio.sleep(1)
948
+
949
+ return StreamingResponse(generate(), media_type="text/event-stream", headers={"cache-control": "no-store"})
950
+
951
+ routes: list[tuple[str, Callable[..., Any], list[str]]] = [
952
+ ("/series-a", page, ["GET", "HEAD"]),
953
+ ("/series-a/app.js", js, ["GET", "HEAD"]),
954
+ ("/series-a/styles.css", css, ["GET", "HEAD"]),
955
+ (f"{prefix}/status", status, ["GET", "HEAD"]),
956
+ (f"{prefix}/manifest", manifest, ["GET", "HEAD"]),
957
+ (f"{prefix}/refresh", refresh, ["POST"]),
958
+ (f"{prefix}/passports/evaluate", evaluate, ["POST"]),
959
+ (f"{prefix}/passports/execute", execute, ["POST"]),
960
+ (f"{prefix}/receipts", receipts, ["GET", "HEAD"]),
961
+ (f"{prefix}/trust", trust, ["GET", "HEAD"]),
962
+ (f"{prefix}/public-key", public_key, ["GET", "HEAD"]),
963
+ (f"{prefix}/events", events, ["GET"]),
964
+ ]
965
+ added: list[str] = []
966
+ for path, endpoint, methods in routes:
967
+ app.add_api_route(path, endpoint, methods=methods, include_in_schema=False)
968
+ added.append(path)
969
+
970
+ route_set = set(added)
971
+ selected = [route for route in app.router.routes if getattr(route, "path", None) in route_set]
972
+ selected_ids = {id(route) for route in selected}
973
+ app.router.routes[:] = selected + [route for route in app.router.routes if id(route) not in selected_ids]
974
+
975
+ app.state.szl_series_a_service = service
976
+ add_handler = getattr(app, "add_event_handler", None)
977
+ if callable(add_handler):
978
+ add_handler("startup", service.start)
979
+
980
+ return {
981
+ "ok": True,
982
+ "state": "REGISTERED",
983
+ "namespace": ns,
984
+ "routes": sorted(added),
985
+ "database": service.store.path,
986
+ "signing_key_source": service.signer.source,
987
+ "sign_on_read": False,
988
+ "effectors": sorted(ALLOWED_ACTIONS),
989
+ "max_attempts": 1,
990
+ "private_reasoning_collected": False,
991
+ }