betterwithage commited on
Commit
5749d55
·
verified ·
1 Parent(s): b953323

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): a11oy_live_feeds.py, serve.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.

Files changed (2) hide show
  1. a11oy_live_feeds.py +128 -40
  2. serve.py +46 -22
a11oy_live_feeds.py CHANGED
@@ -7,12 +7,13 @@ same-origin proxy, keeping the Space sovereign — 0 runtime CDN from the client
7
 
8
  Every response carries an HONEST label:
9
  {"source": <human source>, "source_url": <upstream URL>,
10
- "mode": "live" | "cached" | "self", # never fabricated
11
  "fetched_at": <iso8601>, "ttl_s": <int>, ...payload}
12
 
13
  - "live" = freshly fetched from upstream this request (or within TTL).
14
  - "cached" = upstream was unreachable; serving the last good in-memory value
15
  or the bundled on-disk snapshot (stage resilience).
 
16
  - "self" = our own internal real data (not third-party) — used by callers
17
  that pass through this layer's helpers; the feed endpoints here
18
  are all third-party live/cached.
@@ -27,22 +28,22 @@ Feeds + TTLs:
27
  fhir (hapi.fhir.org/baseR4 Observation/Immunization) TTL 10m
28
 
29
  No auth required for any of these feeds. NEVER fabricates: a down feed returns
30
- the cached snapshot labelled "cached".
31
  """
32
  import json
33
  import os
34
  import time
35
  import threading
36
- import urllib.request
37
- import urllib.error
38
  from datetime import datetime, timezone
39
  from pathlib import Path
40
 
 
41
  from starlette.routing import Route
42
  from starlette.responses import JSONResponse
43
 
44
  _SNAP_DIR = Path(os.environ.get("A11OY_LIVE_SNAPSHOTS", "/app/live_snapshots"))
45
  _UA = "a11oy-live-proxy/1.0 (+https://szlholdings-a11oy.hf.space)"
 
46
 
47
  # in-memory cache: feed -> {"data":..., "ts":..., "mode":...}
48
  _CACHE = {}
@@ -75,13 +76,59 @@ def _now_iso():
75
  return datetime.now(timezone.utc).isoformat()
76
 
77
 
78
- def _http_get(url, timeout=20, headers=None, data=None, method=None):
 
 
 
 
 
 
 
 
79
  h = {"User-Agent": _UA, "Accept": "application/json"}
80
  if headers:
81
  h.update(headers)
82
- req = urllib.request.Request(url, data=data, headers=h, method=method)
83
- with urllib.request.urlopen(req, timeout=timeout) as r:
84
- return json.loads(r.read())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
 
86
 
87
  def _load_snapshot(feed):
@@ -92,7 +139,43 @@ def _load_snapshot(feed):
92
  return None
93
 
94
 
95
- def _fetch(feed):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  """Return raw upstream JSON for a feed (raises on failure)."""
97
  if feed == "prometheus":
98
  import urllib.parse
@@ -102,17 +185,25 @@ def _fetch(feed):
102
  ("cpu", 'rate(node_cpu_seconds_total{mode="user"}[5m])'),
103
  ("mem", "node_memory_MemAvailable_bytes"),
104
  ("http_req", "rate(prometheus_http_requests_total[5m])")):
105
- out[k] = _http_get(base + urllib.parse.quote(q), timeout=12)
 
 
 
 
106
  return out
107
  if feed == "kev":
108
- return _http_get(_SOURCE["kev"][1], timeout=40)
 
 
109
  if feed == "osv":
110
  out = {}
111
  for pkg, eco in (("tensorflow", "PyPI"), ("torch", "PyPI"),
112
  ("transformers", "PyPI"), ("numpy", "PyPI"), ("requests", "PyPI")):
113
  body = json.dumps({"package": {"name": pkg, "ecosystem": eco}}).encode()
114
- r = _http_get("https://api.osv.dev/v1/query", timeout=20, data=body,
115
- headers={"Content-Type": "application/json"}, method="POST")
 
 
116
  vulns = r.get("vulns", [])
117
  out[pkg] = {"ecosystem": eco, "count": len(vulns),
118
  "vulns": [{"id": v.get("id"), "summary": v.get("summary"),
@@ -120,16 +211,24 @@ def _fetch(feed):
120
  "aliases": (v.get("aliases") or [])[:4]} for v in vulns[:25]]}
121
  return out
122
  if feed == "rekor":
123
- return {"log": _http_get(_SOURCE["rekor"][1], timeout=15)}
 
 
124
  if feed == "celestrak":
125
- return _http_get(_SOURCE["celestrak"][1], timeout=20)
 
 
126
  if feed == "iss":
127
- return _http_get(_SOURCE["iss"][1], timeout=12)
 
 
128
  if feed == "fhir":
129
  out = {}
130
  for rt in ("Immunization", "Observation"):
131
- b = _http_get("https://hapi.fhir.org/baseR4/%s?_count=10" % rt, timeout=25,
132
- headers={"Accept": "application/fhir+json"})
 
 
133
  entries = b.get("entry", [])
134
  out[rt] = {"total": b.get("total"), "count": len(entries),
135
  "entries": [e.get("resource", {}) for e in entries[:10]]}
@@ -137,8 +236,8 @@ def _fetch(feed):
137
  raise ValueError("unknown feed: %s" % feed)
138
 
139
 
140
- def get_feed(feed):
141
- """Cached, snapshot-fallback, honestly-labelled feed accessor."""
142
  ttl = _TTL.get(feed, 60)
143
  src, url = _SOURCE.get(feed, ("unknown", ""))
144
  with _LOCK:
@@ -149,30 +248,18 @@ def get_feed(feed):
149
  "fetched_at": ent["iso"], "ttl_s": ttl, "data": ent["data"]}
150
  # need refresh
151
  try:
152
- data = _fetch(feed)
 
 
 
 
153
  iso = _now_iso()
154
  with _LOCK:
155
  _CACHE[feed] = {"data": data, "ts": now, "mode": "live", "iso": iso}
156
  return {"source": src, "source_url": url, "mode": "live",
157
  "fetched_at": iso, "ttl_s": ttl, "data": data}
158
  except Exception as e:
159
- # serve last good in-memory value if present
160
- if ent:
161
- return {"source": src, "source_url": url, "mode": "cached",
162
- "fetched_at": ent["iso"], "ttl_s": ttl,
163
- "cache_note": "upstream unreachable (%s) — serving last good value" % type(e).__name__,
164
- "data": ent["data"]}
165
- # else bundled on-disk snapshot
166
- snap = _load_snapshot(feed)
167
- if snap is not None:
168
- return {"source": src, "source_url": url, "mode": "cached",
169
- "fetched_at": "bundled-snapshot",
170
- "ttl_s": ttl,
171
- "cache_note": "upstream unreachable (%s) — serving bundled in-image snapshot" % type(e).__name__,
172
- "data": snap}
173
- return {"source": src, "source_url": url, "mode": "cached",
174
- "fetched_at": None, "ttl_s": ttl,
175
- "error": "upstream unreachable and no snapshot: %s" % e, "data": None}
176
 
177
 
178
  def register(app, ns="a11oy"):
@@ -201,8 +288,9 @@ def register(app, ns="a11oy"):
201
  return JSONResponse({
202
  "layer": "a11oy live-data proxy",
203
  "honest": "Every feed is server-side fetched + cached, CORS-safe via OUR same-origin "
204
- "proxy (0 client CDN). Mode is honestly labelled live/cached; a down feed "
205
- "serves the bundled in-image snapshot labelled 'cached', never fabricated.",
 
206
  "count": len(feeds), "feeds": feeds,
207
  })
208
 
 
7
 
8
  Every response carries an HONEST label:
9
  {"source": <human source>, "source_url": <upstream URL>,
10
+ "mode": "live" | "cached" | "unavailable" | "self", # never fabricated
11
  "fetched_at": <iso8601>, "ttl_s": <int>, ...payload}
12
 
13
  - "live" = freshly fetched from upstream this request (or within TTL).
14
  - "cached" = upstream was unreachable; serving the last good in-memory value
15
  or the bundled on-disk snapshot (stage resilience).
16
+ - "unavailable" = upstream was unreachable and no cached or bundled data exists.
17
  - "self" = our own internal real data (not third-party) — used by callers
18
  that pass through this layer's helpers; the feed endpoints here
19
  are all third-party live/cached.
 
28
  fhir (hapi.fhir.org/baseR4 Observation/Immunization) TTL 10m
29
 
30
  No auth required for any of these feeds. NEVER fabricates: a down feed returns
31
+ real cached data labelled "cached", or "unavailable" when no cached data exists.
32
  """
33
  import json
34
  import os
35
  import time
36
  import threading
 
 
37
  from datetime import datetime, timezone
38
  from pathlib import Path
39
 
40
+ import httpx
41
  from starlette.routing import Route
42
  from starlette.responses import JSONResponse
43
 
44
  _SNAP_DIR = Path(os.environ.get("A11OY_LIVE_SNAPSHOTS", "/app/live_snapshots"))
45
  _UA = "a11oy-live-proxy/1.0 (+https://szlholdings-a11oy.hf.space)"
46
+ _MAX_RESPONSE_BYTES = 32 * 1024 * 1024
47
 
48
  # in-memory cache: feed -> {"data":..., "ts":..., "mode":...}
49
  _CACHE = {}
 
76
  return datetime.now(timezone.utc).isoformat()
77
 
78
 
79
+ def _http_get(url, timeout=20, headers=None, data=None, method=None, deadline=None):
80
+ """Fetch one bounded JSON response.
81
+
82
+ Socket timeouts alone are insufficient because a peer can trickle bytes and
83
+ reset the per-read timer forever. ``iter_bytes`` exposes each received
84
+ network chunk so the absolute monotonic deadline is checked throughout the
85
+ body read. The response context is closed before a deadline error escapes,
86
+ which lets the calling worker finish instead of being abandoned.
87
+ """
88
  h = {"User-Agent": _UA, "Accept": "application/json"}
89
  if headers:
90
  h.update(headers)
91
+ request_timeout = _remaining_timeout(deadline, timeout)
92
+ request_deadline = (
93
+ deadline
94
+ if deadline is not None
95
+ else time.monotonic() + request_timeout
96
+ )
97
+ request_method = method or ("POST" if data is not None else "GET")
98
+ chunks = []
99
+ received = 0
100
+ try:
101
+ with httpx.stream(
102
+ request_method,
103
+ url,
104
+ content=data,
105
+ headers=h,
106
+ timeout=httpx.Timeout(request_timeout),
107
+ follow_redirects=True,
108
+ ) as response:
109
+ response.raise_for_status()
110
+ content_length = response.headers.get("content-length")
111
+ if content_length is not None:
112
+ try:
113
+ declared_bytes = int(content_length)
114
+ except (TypeError, ValueError):
115
+ declared_bytes = None
116
+ if declared_bytes is not None and declared_bytes > _MAX_RESPONSE_BYTES:
117
+ raise ValueError("live-feed response exceeds size limit")
118
+ for chunk in response.iter_bytes():
119
+ if time.monotonic() >= request_deadline:
120
+ raise TimeoutError("live-feed response deadline exhausted")
121
+ if not chunk:
122
+ continue
123
+ received += len(chunk)
124
+ if received > _MAX_RESPONSE_BYTES:
125
+ raise ValueError("live-feed response exceeds size limit")
126
+ chunks.append(chunk)
127
+ if time.monotonic() >= request_deadline:
128
+ raise TimeoutError("live-feed response deadline exhausted")
129
+ except httpx.TimeoutException as exc:
130
+ raise TimeoutError("live-feed response deadline exhausted") from exc
131
+ return json.loads(b"".join(chunks))
132
 
133
 
134
  def _load_snapshot(feed):
 
139
  return None
140
 
141
 
142
+ def get_cached_feed(feed, error):
143
+ """Return only real cached evidence, without attempting an upstream read."""
144
+ ttl = _TTL.get(feed, 60)
145
+ src, url = _SOURCE.get(feed, ("unknown", ""))
146
+ with _LOCK:
147
+ ent = _CACHE.get(feed)
148
+ if ent:
149
+ return {"source": src, "source_url": url, "mode": "cached",
150
+ "fetched_at": ent["iso"], "ttl_s": ttl,
151
+ "cache_note": "upstream unreachable (%s) — serving last good value"
152
+ % type(error).__name__,
153
+ "data": ent["data"]}
154
+ snap = _load_snapshot(feed)
155
+ if snap is not None:
156
+ return {"source": src, "source_url": url, "mode": "cached",
157
+ "fetched_at": "bundled-snapshot", "ttl_s": ttl,
158
+ "cache_note": "upstream unreachable (%s) — serving bundled in-image snapshot"
159
+ % type(error).__name__,
160
+ "data": snap}
161
+ return {"source": src, "source_url": url, "mode": "unavailable",
162
+ "fetched_at": None, "ttl_s": ttl,
163
+ "error": "upstream unreachable and no snapshot (%s): %s"
164
+ % (type(error).__name__, error),
165
+ "data": None}
166
+
167
+
168
+ def _remaining_timeout(deadline, default):
169
+ """Return a cooperative per-request socket timeout within ``deadline``."""
170
+ if deadline is None:
171
+ return float(default)
172
+ remaining = deadline - time.monotonic()
173
+ if remaining <= 0:
174
+ raise TimeoutError("live-feed network budget exhausted")
175
+ return min(float(default), remaining)
176
+
177
+
178
+ def _fetch(feed, deadline=None):
179
  """Return raw upstream JSON for a feed (raises on failure)."""
180
  if feed == "prometheus":
181
  import urllib.parse
 
185
  ("cpu", 'rate(node_cpu_seconds_total{mode="user"}[5m])'),
186
  ("mem", "node_memory_MemAvailable_bytes"),
187
  ("http_req", "rate(prometheus_http_requests_total[5m])")):
188
+ out[k] = _http_get(
189
+ base + urllib.parse.quote(q),
190
+ timeout=_remaining_timeout(deadline, 12),
191
+ deadline=deadline,
192
+ )
193
  return out
194
  if feed == "kev":
195
+ return _http_get(
196
+ _SOURCE["kev"][1], timeout=_remaining_timeout(deadline, 40),
197
+ deadline=deadline)
198
  if feed == "osv":
199
  out = {}
200
  for pkg, eco in (("tensorflow", "PyPI"), ("torch", "PyPI"),
201
  ("transformers", "PyPI"), ("numpy", "PyPI"), ("requests", "PyPI")):
202
  body = json.dumps({"package": {"name": pkg, "ecosystem": eco}}).encode()
203
+ r = _http_get("https://api.osv.dev/v1/query",
204
+ timeout=_remaining_timeout(deadline, 20), data=body,
205
+ headers={"Content-Type": "application/json"}, method="POST",
206
+ deadline=deadline)
207
  vulns = r.get("vulns", [])
208
  out[pkg] = {"ecosystem": eco, "count": len(vulns),
209
  "vulns": [{"id": v.get("id"), "summary": v.get("summary"),
 
211
  "aliases": (v.get("aliases") or [])[:4]} for v in vulns[:25]]}
212
  return out
213
  if feed == "rekor":
214
+ return {"log": _http_get(
215
+ _SOURCE["rekor"][1], timeout=_remaining_timeout(deadline, 15),
216
+ deadline=deadline)}
217
  if feed == "celestrak":
218
+ return _http_get(
219
+ _SOURCE["celestrak"][1], timeout=_remaining_timeout(deadline, 20),
220
+ deadline=deadline)
221
  if feed == "iss":
222
+ return _http_get(
223
+ _SOURCE["iss"][1], timeout=_remaining_timeout(deadline, 12),
224
+ deadline=deadline)
225
  if feed == "fhir":
226
  out = {}
227
  for rt in ("Immunization", "Observation"):
228
+ b = _http_get("https://hapi.fhir.org/baseR4/%s?_count=10" % rt,
229
+ timeout=_remaining_timeout(deadline, 25),
230
+ headers={"Accept": "application/fhir+json"},
231
+ deadline=deadline)
232
  entries = b.get("entry", [])
233
  out[rt] = {"total": b.get("total"), "count": len(entries),
234
  "entries": [e.get("resource", {}) for e in entries[:10]]}
 
236
  raise ValueError("unknown feed: %s" % feed)
237
 
238
 
239
+ def get_feed(feed, timeout_s=None):
240
+ """Cached, honestly-labelled accessor with an optional cooperative network budget."""
241
  ttl = _TTL.get(feed, 60)
242
  src, url = _SOURCE.get(feed, ("unknown", ""))
243
  with _LOCK:
 
248
  "fetched_at": ent["iso"], "ttl_s": ttl, "data": ent["data"]}
249
  # need refresh
250
  try:
251
+ deadline = (
252
+ time.monotonic() + max(0.001, float(timeout_s))
253
+ if timeout_s is not None else None
254
+ )
255
+ data = _fetch(feed, deadline=deadline)
256
  iso = _now_iso()
257
  with _LOCK:
258
  _CACHE[feed] = {"data": data, "ts": now, "mode": "live", "iso": iso}
259
  return {"source": src, "source_url": url, "mode": "live",
260
  "fetched_at": iso, "ttl_s": ttl, "data": data}
261
  except Exception as e:
262
+ return get_cached_feed(feed, e)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
263
 
264
 
265
  def register(app, ns="a11oy"):
 
288
  return JSONResponse({
289
  "layer": "a11oy live-data proxy",
290
  "honest": "Every feed is server-side fetched + cached, CORS-safe via OUR same-origin "
291
+ "proxy (0 client CDN). Mode is honestly labelled live/cached/unavailable; "
292
+ "a down feed serves real cached data when present and reports unavailable "
293
+ "when absent, never fabricated.",
294
  "count": len(feeds), "feeds": feeds,
295
  })
296
 
serve.py CHANGED
@@ -10708,6 +10708,13 @@ try:
10708
  # real mode (live/cached), age, and round-trip latency. This is the data-
10709
  # provenance heartbeat for the governed-AI mission: a governed system must
10710
  # know whether its evidence feeds are actually live. 0 fabricated status.
 
 
 
 
 
 
 
10711
  @app.get("/api/a11oy/v1/feeds/pulse")
10712
  async def _feeds_pulse():
10713
  import anyio, time as _t
@@ -10715,27 +10722,42 @@ try:
10715
  async def _one(f):
10716
  t0 = _t.time()
10717
  try:
10718
- p = await anyio.to_thread.run_sync(_kl_live.get_feed, f)
10719
- dt = round((_t.time()-t0)*1000)
10720
- d = p.get("data")
10721
- # honest payload-size signal
10722
- try:
10723
- import json as _j; size = len(_j.dumps(d)) if d is not None else 0
10724
- except Exception:
10725
- size = 0
10726
- return {"feed": f, "source": p.get("source"),
10727
- "source_url": p.get("source_url"),
10728
- "mode": p.get("mode"), "fetched_at": p.get("fetched_at"),
10729
- "ttl_s": p.get("ttl_s"), "latency_ms": dt,
10730
- "payload_bytes": size,
10731
- "error": p.get("error")}
10732
  except Exception as e:
10733
- return {"feed": f, "mode": "unavailable",
10734
- "latency_ms": round((_t.time()-t0)*1000),
10735
- "error": "%s: %s" % (type(e).__name__, e)}
10736
- items = []
10737
- for f in feeds:
10738
- items.append(await _one(f))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10739
  live = sum(1 for i in items if i.get("mode")=="live")
10740
  return JSONResponse({
10741
  "probed_at": _kl_live._now_iso(),
@@ -10744,8 +10766,10 @@ try:
10744
  "cached_count": sum(1 for i in items if i.get("mode")=="cached"),
10745
  "down_count": sum(1 for i in items if i.get("mode")=="unavailable"),
10746
  "note": ("Real-time provenance heartbeat: each row is a live server-side "
10747
- "probe of an upstream evidence feed. mode/latency are measured, "
10748
- "never fabricated. A governed-AI system must know its feeds are live."),
 
 
10749
  "items": items,
10750
  })
10751
 
 
10708
  # real mode (live/cached), age, and round-trip latency. This is the data-
10709
  # provenance heartbeat for the governed-AI mission: a governed system must
10710
  # know whether its evidence feeds are actually live. 0 fabricated status.
10711
+ try:
10712
+ _KL_FEED_PULSE_TIMEOUT_S = max(
10713
+ 0.25, min(10.0, float(os.environ.get("A11OY_FEED_PULSE_TIMEOUT_SEC", "4.0")))
10714
+ )
10715
+ except (TypeError, ValueError):
10716
+ _KL_FEED_PULSE_TIMEOUT_S = 4.0
10717
+
10718
  @app.get("/api/a11oy/v1/feeds/pulse")
10719
  async def _feeds_pulse():
10720
  import anyio, time as _t
 
10722
  async def _one(f):
10723
  t0 = _t.time()
10724
  try:
10725
+ # get_feed enforces the absolute deadline inside every streamed
10726
+ # network read. Keep the worker joined: it must exit and return
10727
+ # its honest cache fallback instead of being orphaned.
10728
+ p = await anyio.to_thread.run_sync(
10729
+ _kl_live.get_feed,
10730
+ f,
10731
+ _KL_FEED_PULSE_TIMEOUT_S,
10732
+ )
10733
+ except TimeoutError as e:
10734
+ p = _kl_live.get_cached_feed(f, e)
10735
+ if p.get("mode") == "unavailable":
10736
+ p["error"] = "probe timeout after %.2fs" % _KL_FEED_PULSE_TIMEOUT_S
 
 
10737
  except Exception as e:
10738
+ p = _kl_live.get_cached_feed(f, e)
10739
+ if p.get("mode") == "unavailable":
10740
+ p["error"] = "%s: %s" % (type(e).__name__, e)
10741
+ dt = round((_t.time()-t0)*1000)
10742
+ d = p.get("data")
10743
+ # honest payload-size signal
10744
+ try:
10745
+ import json as _j; size = len(_j.dumps(d)) if d is not None else 0
10746
+ except Exception:
10747
+ size = 0
10748
+ return {"feed": f, "source": p.get("source"),
10749
+ "source_url": p.get("source_url"),
10750
+ "mode": p.get("mode"), "fetched_at": p.get("fetched_at"),
10751
+ "ttl_s": p.get("ttl_s"), "latency_ms": dt,
10752
+ "payload_bytes": size,
10753
+ "error": p.get("error"),
10754
+ "cache_note": p.get("cache_note")}
10755
+ items = [None] * len(feeds)
10756
+ async def _collect(index, feed):
10757
+ items[index] = await _one(feed)
10758
+ async with anyio.create_task_group() as task_group:
10759
+ for index, feed in enumerate(feeds):
10760
+ task_group.start_soon(_collect, index, feed)
10761
  live = sum(1 for i in items if i.get("mode")=="live")
10762
  return JSONResponse({
10763
  "probed_at": _kl_live._now_iso(),
 
10766
  "cached_count": sum(1 for i in items if i.get("mode")=="cached"),
10767
  "down_count": sum(1 for i in items if i.get("mode")=="unavailable"),
10768
  "note": ("Real-time provenance heartbeat: each row is a live server-side "
10769
+ "bounded probe of an upstream evidence feed. mode/latency are measured, "
10770
+ "internally handled timeouts remain visible through cache_note when real "
10771
+ "cached data exists and otherwise count as unavailable. A governed-AI "
10772
+ "system must know its feeds are live."),
10773
  "items": items,
10774
  })
10775