betterwithage commited on
Commit
9f95429
·
verified ·
1 Parent(s): 6b69c89

Add Evidence & Research tab + curated/live evidence layer (#185)

Browse files

Task #185: curated + live (arXiv/GitHub) research evidence layer wired into the console as an Evidence & Research tab; honest live/cached/unreachable labels, no fabrication.

Files changed (4) hide show
  1. Dockerfile +1 -0
  2. pages/console.html +45 -0
  3. serve.py +9 -0
  4. szl_evidence_research.py +475 -0
Dockerfile CHANGED
@@ -427,6 +427,7 @@ COPY szl_budget_router.py ./szl_budget_router.py
427
  # matrix-health pre-flight reports the module missing. serve.py + szl_governance_gateway
428
  # import it try/except-guarded. LOCKED-proven stays EXACTLY 5; Lambda=Conjecture 1.
429
  COPY szl_wave910_proofs.py ./szl_wave910_proofs.py
 
430
 
431
  CMD ["python", "serve.py"]
432
 
 
427
  # matrix-health pre-flight reports the module missing. serve.py + szl_governance_gateway
428
  # import it try/except-guarded. LOCKED-proven stays EXACTLY 5; Lambda=Conjecture 1.
429
  COPY szl_wave910_proofs.py ./szl_wave910_proofs.py
430
+ COPY szl_evidence_research.py ./szl_evidence_research.py
431
 
432
  CMD ["python", "serve.py"]
433
 
pages/console.html CHANGED
@@ -291,6 +291,7 @@ details.raw[open] summary{color:var(--muted);}
291
  <div class="nav-item" data-view="codetab" onclick="go('codetab')"><span class="ico">⌨</span>a11oy Code</div>
292
  <div class="nav-item" data-view="mcp" onclick="go('mcp')"><span class="ico">⚙</span>Agent Tools</div>
293
  <div class="nav-item" data-view="honest" onclick="go('honest')"><span class="ico">⊘</span>What We Claim</div>
 
294
  <div class="nav-group">Leader-Grade (NEW)</div>
295
  <div class="nav-item" data-view="lineage" onclick="go('lineage')"><span class="ico">⚛</span>Knowledge Lineage</div>
296
  <div class="nav-item" data-view="mission" onclick="go('mission')"><span class="ico">⬢</span>Mission Health</div>
@@ -466,6 +467,7 @@ const HONEST='<div class="honesty"><b>How to read this.</b> Every panel reads a
466
  const FLOOR=0.9;
467
 
468
  const VIEWS={
 
469
  command:{title:'Command Center',badge:'LIVE',sub:'One pane of glass over the whole governed-AI platform \u2014 live service health, the connected system map, trust posture, and a verifiable audit trail.',
470
  render:async(c)=>{c.innerHTML=`<div class="kpis">
471
  <div class="kpi"><div class="k">Services up</div><div class="v live" id="k-fleet">probing\u2026</div><div class="d">live health probe</div></div>
@@ -1595,6 +1597,49 @@ window.toggleSide=toggleSide;
1595
  can override VIEWS[tab].render. const bindings do NOT auto-attach to window, so we do it
1596
  explicitly here. This is the single source of truth the batch module mutates in place. */
1597
  window.VIEWS=VIEWS; window.go=go;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1598
  const start=(location.hash||'#command').slice(1);
1599
  /* defer initial render until all inline scripts (helpers + frontier loaders) have executed,
1600
  so deep-links to tabs whose loaders live in a later script block resolve correctly */
 
291
  <div class="nav-item" data-view="codetab" onclick="go('codetab')"><span class="ico">⌨</span>a11oy Code</div>
292
  <div class="nav-item" data-view="mcp" onclick="go('mcp')"><span class="ico">⚙</span>Agent Tools</div>
293
  <div class="nav-item" data-view="honest" onclick="go('honest')"><span class="ico">⊘</span>What We Claim</div>
294
+ <div class="nav-item" data-view="evidence" onclick="go('evidence')"><span class="ico">⧉</span>Evidence &amp; Research</div> <!-- evidence-tab-patch-185 -->
295
  <div class="nav-group">Leader-Grade (NEW)</div>
296
  <div class="nav-item" data-view="lineage" onclick="go('lineage')"><span class="ico">⚛</span>Knowledge Lineage</div>
297
  <div class="nav-item" data-view="mission" onclick="go('mission')"><span class="ico">⬢</span>Mission Health</div>
 
467
  const FLOOR=0.9;
468
 
469
  const VIEWS={
470
+ evidence:{title:'Evidence & Research',badge:'CURATED CITATIONS · LIVE arXiv + GitHub',sub:'Every headline claim below is grounded in real, resolvable sources — official standards, public datasets and GitHub repositories. Paper lists and repo stats are fetched <b>live</b> from the arXiv + GitHub APIs and labelled live/cached; if a feed is down the panel degrades to the curated citations, never to invented figures.',render:async(c)=>{window.evidence_render(c);}}, // evidence-tab-patch-185
471
  command:{title:'Command Center',badge:'LIVE',sub:'One pane of glass over the whole governed-AI platform \u2014 live service health, the connected system map, trust posture, and a verifiable audit trail.',
472
  render:async(c)=>{c.innerHTML=`<div class="kpis">
473
  <div class="kpi"><div class="k">Services up</div><div class="v live" id="k-fleet">probing\u2026</div><div class="d">live health probe</div></div>
 
1597
  can override VIEWS[tab].render. const bindings do NOT auto-attach to window, so we do it
1598
  explicitly here. This is the single source of truth the batch module mutates in place. */
1599
  window.VIEWS=VIEWS; window.go=go;
1600
+ /* evidence-tab-patch-185 — curated + live research/evidence layer */
1601
+ window.__ev_ns="a11oy";
1602
+ window.evidence_live=async function(id){
1603
+ var box=document.getElementById('ev-live-'+id); if(!box) return;
1604
+ box.innerHTML='<div class="dim">fetching live arXiv + GitHub…</div>';
1605
+ try{
1606
+ var r=await fetch('/api/'+window.__ev_ns+'/v1/evidence/research/'+id+'/live');
1607
+ var d=await r.json(); var h=''; var ax=d.arxiv||{};
1608
+ h+='<div class="dim" style="margin:.3rem 0">arXiv ['+esc(ax.mode||'?')+(ax.fetched_at?(' · '+esc(ax.fetched_at)):'')+']</div>';
1609
+ (ax.papers||[]).forEach(function(p){
1610
+ h+='<div class="row"><a href="'+esc(p.url||'#')+'" target="_blank" rel="noopener">'+esc(p.title||'(untitled)')+'</a> <span class="dim">'+esc(p.published||'')+'</span></div>';
1611
+ });
1612
+ if(!((ax.papers||[]).length)) h+='<div class="dim">no live papers ('+esc(ax.mode||'')+') — curated sources above remain valid</div>';
1613
+ h+='<div class="dim" style="margin:.5rem 0 .3rem">GitHub</div>';
1614
+ (d.github||[]).forEach(function(g){
1615
+ var meta=(g.stars!=null?('★ '+g.stars):'')+(g.license?(' · '+esc(g.license)):'')+(g.pushed_at?(' · pushed '+esc(g.pushed_at)):'')+' ['+esc(g.mode||'')+']';
1616
+ h+='<div class="row"><a href="'+esc(g.url||'#')+'" target="_blank" rel="noopener">'+esc(g.repo||'')+'</a> <span class="dim">'+meta+'</span></div>';
1617
+ });
1618
+ box.innerHTML=h;
1619
+ }catch(e){ box.innerHTML='<div class="dim">live evidence unavailable: '+esc(e.message||e)+' — curated sources above remain valid</div>'; }
1620
+ };
1621
+ window.evidence_render=async function(c){
1622
+ c.innerHTML='<div class="card"><div class="dim">loading curated evidence…</div></div>';
1623
+ try{
1624
+ var r=await fetch('/api/'+window.__ev_ns+'/v1/evidence/research');
1625
+ var d=await r.json(); var h='';
1626
+ if(d.honest) h+='<div class="honesty">'+esc(d.honest)+'</div>';
1627
+ (d.claims||[]).forEach(function(cl){
1628
+ h+='<div class="card"><div><b>'+esc(cl.claim||'')+'</b>'+(cl.maturity?(' <span class="badge">'+esc(cl.maturity)+'</span>'):'')+(cl.tab?(' <span class="dim">→ '+esc(cl.tab)+' tab</span>'):'')+'</div>';
1629
+ h+='<div class="dim" style="margin:.45rem 0 .25rem">Cited sources</div>';
1630
+ (cl.sources||[]).forEach(function(s){
1631
+ h+='<div class="row"><span class="badge">'+esc(s.kind||'src')+'</span> <a href="'+esc(s.url||'#')+'" target="_blank" rel="noopener">'+esc(s.title||'')+'</a>'+(s.note?(' <span class="dim">— '+esc(s.note)+'</span>'):'')+'</div>';
1632
+ });
1633
+ h+='<div style="margin-top:.55rem"><button class="btn ev-live-btn" data-ev="'+esc(cl.id)+'">⟳ Load live arXiv + GitHub</button></div>';
1634
+ h+='<div id="ev-live-'+esc(cl.id)+'" style="margin-top:.5rem"></div></div>';
1635
+ });
1636
+ c.innerHTML=h||'<div class="card"><div class="dim">no claims registered.</div></div>';
1637
+ Array.prototype.forEach.call(c.querySelectorAll('.ev-live-btn'),function(b){
1638
+ b.addEventListener('click',function(){ window.evidence_live(b.getAttribute('data-ev')); });
1639
+ });
1640
+ }catch(e){ c.innerHTML='<div class="card"><div class="dim">evidence layer unavailable: '+esc(e.message||e)+'</div></div>'; }
1641
+ };
1642
+ /* end evidence-tab-patch-185 */
1643
  const start=(location.hash||'#command').slice(1);
1644
  /* defer initial render until all inline scripts (helpers + frontier loaders) have executed,
1645
  so deep-links to tabs whose loaders live in a later script block resolve correctly */
serve.py CHANGED
@@ -74,6 +74,15 @@ except ImportError:
74
 
75
  app = FastAPI(title="a11oy — Brand Orchestration Layer", version="2.0.0")
76
 
 
 
 
 
 
 
 
 
 
77
  # ── BE hardening (Greene) — szl_be_hardening ──
78
  # Backend hardening: pydantic validation, 60/min/IP rate limit, real OpenAPI at
79
  # /api/a11oy/openapi.json, /healthz + /readyz (Khipu chain check), JSON logs
 
74
 
75
  app = FastAPI(title="a11oy — Brand Orchestration Layer", version="2.0.0")
76
 
77
+ # ── Evidence & Research layer (evidence-research-185) — curated + live arXiv/GitHub citations.
78
+ # Additive, try/except-guarded, registered EARLY (before the SPA catch-all). Pure stdlib.
79
+ try:
80
+ import szl_evidence_research as _szl_evidence_research
81
+ _szl_evidence_research.register(app, ns="a11oy")
82
+ print("[a11oy] Evidence & Research registered: /api/a11oy/v1/evidence/research", file=__import__("sys").stderr)
83
+ except Exception as _szl_ev_e: # pragma: no cover
84
+ print(f"[a11oy] Evidence & Research NOT registered: {_szl_ev_e!r}", file=__import__("sys").stderr)
85
+
86
  # ── BE hardening (Greene) — szl_be_hardening ──
87
  # Backend hardening: pydantic validation, 60/min/IP rate limit, real OpenAPI at
88
  # /api/a11oy/openapi.json, /healthz + /readyz (Khipu chain check), JSON logs
szl_evidence_research.py ADDED
@@ -0,0 +1,475 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ szl_evidence_research.py — Evidence & Research layer (a11oy + killinchu)
3
+ =======================================================================
4
+
5
+ Grounds the organ demos in REAL, citeable, REFRESHABLE research instead of
6
+ static prose. For every headline claim a console tab makes, this module ships:
7
+
8
+ * a CURATED bundle of real, resolvable sources — official standards /
9
+ project homes, public datasets, and GitHub repositories (each with a URL);
10
+ * a LIVE overlay pulled at request time from the arXiv API (papers) and the
11
+ GitHub REST API (repo stars / last-push / license) so the panel is
12
+ refreshable, not frozen prose;
13
+ * honest mode labels (live | cached | curated) on every block — a down feed
14
+ degrades to the curated bundle, NEVER to fabricated figures.
15
+
16
+ Doctrine: claims need machine-readable evidence. Nothing here invents a number.
17
+ The only numbers are (a) live counts/stars from the upstream APIs, honestly
18
+ labelled with their fetch time, or (b) absent. There are no synthetic figures
19
+ in this layer; where the wider console shows illustrative values they are
20
+ labelled there. This layer is pure citation + live freshness.
21
+
22
+ Pattern mirrors a11oy_live_feeds.py / killinchu_live_feeds.py:
23
+ from szl_evidence_research import register as register_evidence_research
24
+ register_evidence_research(app, ns="a11oy")
25
+
26
+ Endpoints (per namespace ns):
27
+ GET /api/{ns}/v1/evidence/research
28
+ -> index: curated claims + their static sources (fast, always works)
29
+ GET /api/{ns}/v1/evidence/research/{claim_id}/live
30
+ -> refreshable: live arXiv search + live GitHub repo stats for a claim
31
+ GET /api/{ns}/v1/evidence/research/refresh
32
+ -> light freshness sweep across every claim (cached, honest)
33
+ """
34
+ from __future__ import annotations
35
+
36
+ import json
37
+ import time
38
+ import urllib.parse
39
+ import urllib.request
40
+ import xml.etree.ElementTree as ET
41
+ from typing import Any, Dict, List, Optional
42
+
43
+ _UA = "szl-evidence-research/1.0 (+https://a11oy.net; research-evidence layer)"
44
+ _ARXIV = "https://export.arxiv.org/api/query"
45
+ _GH_REPO = "https://api.github.com/repos/"
46
+
47
+ # ---------------------------------------------------------------------------
48
+ # Curated claim -> evidence map. Every URL below is a real, resolvable source:
49
+ # an official standard, a public dataset, a canonical project home, or a real
50
+ # GitHub repository. arxiv_query drives the LIVE paper overlay.
51
+ # ---------------------------------------------------------------------------
52
+
53
+ def _src(kind: str, title: str, url: str, note: str = "") -> Dict[str, str]:
54
+ return {"kind": kind, "title": title, "url": url, "note": note}
55
+
56
+
57
+ CLAIMS: Dict[str, List[Dict[str, Any]]] = {
58
+ # =====================================================================
59
+ "a11oy": [
60
+ {
61
+ "id": "signed-receipts",
62
+ "tab": "chain",
63
+ "claim": "Every governed decision emits a cryptographically signed, "
64
+ "tamper-evident receipt (DSSE envelope), verifiable offline.",
65
+ "maturity": "implemented",
66
+ "sources": [
67
+ _src("standard", "in-toto Attestation Framework",
68
+ "https://github.com/in-toto/attestation",
69
+ "Signed software-supply-chain statements; our receipt shape."),
70
+ _src("standard", "DSSE — Dead Simple Signing Envelope (spec)",
71
+ "https://github.com/secure-systems-lab/dsse",
72
+ "Envelope + PAE pre-auth encoding our receipts use."),
73
+ _src("project", "Sigstore — keyless signing & transparency",
74
+ "https://www.sigstore.dev/",
75
+ "Public-good signing model we mirror."),
76
+ _src("dataset", "Rekor public transparency log",
77
+ "https://rekor.sigstore.dev/",
78
+ "Append-only signature transparency (our live Rekor feed)."),
79
+ ],
80
+ "github": ["in-toto/attestation", "secure-systems-lab/dsse",
81
+ "sigstore/cosign", "sigstore/rekor"],
82
+ "arxiv_query": "in-toto software supply chain provenance signing",
83
+ },
84
+ {
85
+ "id": "slsa-provenance",
86
+ "tab": "deploy",
87
+ "claim": "Container images carry verified SLSA build provenance "
88
+ "(L2 on the organs that claim it; remainder on the roadmap).",
89
+ "maturity": "implemented-partial",
90
+ "sources": [
91
+ _src("standard", "SLSA — Supply-chain Levels for Software Artifacts",
92
+ "https://slsa.dev/spec/v1.0/levels",
93
+ "L1-L4 provenance ladder; we claim L2 honestly."),
94
+ _src("project", "SLSA GitHub generator",
95
+ "https://github.com/slsa-framework/slsa-github-generator",
96
+ "Reference provenance generator."),
97
+ _src("standard", "NIST SSDF (SP 800-218)",
98
+ "https://csrc.nist.gov/pubs/sp/800/218/final",
99
+ "Secure software development framework."),
100
+ ],
101
+ "github": ["slsa-framework/slsa-github-generator", "slsa-framework/slsa"],
102
+ "arxiv_query": "software supply chain security provenance attestation",
103
+ },
104
+ {
105
+ "id": "policy-gates",
106
+ "tab": "gates",
107
+ "claim": "Autonomy is constrained by machine-checkable policy gates; "
108
+ "severity is an input, the gate verdict is the decision.",
109
+ "maturity": "implemented",
110
+ "sources": [
111
+ _src("project", "Open Policy Agent (OPA) / Rego",
112
+ "https://www.openpolicyagent.org/",
113
+ "Policy-as-code engine; our gate model is in this family."),
114
+ _src("standard", "NIST AI Risk Management Framework (AI RMF 1.0)",
115
+ "https://www.nist.gov/itl/ai-risk-management-framework",
116
+ "Govern/Map/Measure/Manage functions we map gates onto."),
117
+ _src("standard", "MITRE ATT&CK",
118
+ "https://attack.mitre.org/",
119
+ "Adversary technique corpus our gates reference."),
120
+ ],
121
+ "github": ["open-policy-agent/opa", "mitre-attack/attack-stix-data"],
122
+ "arxiv_query": "policy as code safe autonomous agent guardrails",
123
+ },
124
+ {
125
+ "id": "lambda-conjecture",
126
+ "tab": "lambda",
127
+ "claim": "The trust score Lambda is a formally specified research "
128
+ "conjecture, machine-checked in Lean (advisory, not an oracle).",
129
+ "maturity": "research",
130
+ "sources": [
131
+ _src("project", "Lean 4 theorem prover",
132
+ "https://github.com/leanprover/lean4",
133
+ "Proof assistant our Lambda formalisation targets."),
134
+ _src("project", "mathlib4 — Lean mathematical library",
135
+ "https://github.com/leanprover-community/mathlib4",
136
+ "Background theory used by the formalisation."),
137
+ ],
138
+ "github": ["leanprover/lean4", "leanprover-community/mathlib4"],
139
+ "arxiv_query": "formal verification trust score multi attribute decision Lean",
140
+ },
141
+ {
142
+ "id": "vuln-grounding",
143
+ "tab": "cve",
144
+ "claim": "Vulnerability prioritisation reads only public signals "
145
+ "(CVSS + CISA-KEV + OSV + EPSS) and labels that it has no "
146
+ "environment telemetry.",
147
+ "maturity": "implemented",
148
+ "sources": [
149
+ _src("dataset", "CISA Known Exploited Vulnerabilities (KEV)",
150
+ "https://www.cisa.gov/known-exploited-vulnerabilities-catalog",
151
+ "Exploited-in-the-wild ground truth (our live KEV feed)."),
152
+ _src("dataset", "OSV — Open Source Vulnerabilities",
153
+ "https://osv.dev/",
154
+ "Distributed vuln database (our live OSV feed)."),
155
+ _src("dataset", "NVD — National Vulnerability Database",
156
+ "https://nvd.nist.gov/",
157
+ "CVSS base scores (the CVE Watch tab reads this live)."),
158
+ _src("dataset", "EPSS — Exploit Prediction Scoring System",
159
+ "https://www.first.org/epss/",
160
+ "Data-driven exploit-likelihood, FIRST.org."),
161
+ ],
162
+ "github": ["cisagov/kev-data", "google/osv.dev"],
163
+ "arxiv_query": "exploit prediction scoring system vulnerability prioritization EPSS",
164
+ },
165
+ {
166
+ "id": "ai-oversight",
167
+ "tab": "oversight",
168
+ "claim": "Agentic autonomy is kept under human-auditable oversight; "
169
+ "no AGI claims are made.",
170
+ "maturity": "research",
171
+ "sources": [
172
+ _src("standard", "NIST AI RMF — Generative AI Profile (600-1)",
173
+ "https://csrc.nist.gov/pubs/ai/600/1/final",
174
+ "Risk profile for generative / agentic systems."),
175
+ _src("standard", "ISO/IEC 42001 — AI management systems",
176
+ "https://www.iso.org/standard/81230.html",
177
+ "Auditable AI governance management system."),
178
+ ],
179
+ "github": ["openai/evals"],
180
+ "arxiv_query": "AI oversight agent governance constitutional AI alignment",
181
+ },
182
+ ],
183
+ # =====================================================================
184
+ "killinchu": [
185
+ {
186
+ "id": "bft-consensus",
187
+ "tab": "w910gg",
188
+ "claim": "Command authority requires Byzantine-fault-tolerant agreement "
189
+ "(survives a minority of faulty / compromised nodes).",
190
+ "maturity": "research",
191
+ "sources": [
192
+ _src("project", "PBFT — Practical Byzantine Fault Tolerance (Castro & Liskov)",
193
+ "https://pmg.csail.mit.edu/papers/osdi99.pdf",
194
+ "Foundational 3f+1 BFT result."),
195
+ _src("project", "Tendermint / CometBFT consensus",
196
+ "https://github.com/cometbft/cometbft",
197
+ "Production BFT state machine replication."),
198
+ ],
199
+ "github": ["cometbft/cometbft", "hyperledger/fabric"],
200
+ "arxiv_query": "byzantine fault tolerant consensus multi agent command",
201
+ },
202
+ {
203
+ "id": "sensor-fusion",
204
+ "tab": "fusion",
205
+ "claim": "Multi-sensor tracks are fused with covariance-aware estimation "
206
+ "(Kalman / covariance intersection) producing valid uncertainty.",
207
+ "maturity": "implemented",
208
+ "sources": [
209
+ _src("project", "FilterPy — Kalman & Bayesian filters",
210
+ "https://github.com/rlabbe/filterpy",
211
+ "Reference Kalman/UKF implementations."),
212
+ _src("project", "Covariance Intersection (Julier & Uhlmann)",
213
+ "https://en.wikipedia.org/wiki/Covariance_intersection",
214
+ "Fuse without known cross-covariance; conservative PSD result."),
215
+ ],
216
+ "github": ["rlabbe/filterpy"],
217
+ "arxiv_query": "covariance intersection sensor fusion track estimation",
218
+ },
219
+ {
220
+ "id": "air-picture",
221
+ "tab": "livepic",
222
+ "claim": "The live air picture is built from public ADS-B feeds, "
223
+ "honestly labelled live vs cached.",
224
+ "maturity": "implemented",
225
+ "sources": [
226
+ _src("dataset", "adsb.lol — community ADS-B (no auth)",
227
+ "https://adsb.lol/",
228
+ "Military + civil aircraft feed (our live air feed)."),
229
+ _src("dataset", "The OpenSky Network",
230
+ "https://opensky-network.org/",
231
+ "Research-grade ADS-B / Mode-S data."),
232
+ _src("project", "dump1090 — ADS-B decoder",
233
+ "https://github.com/flightaware/dump1090",
234
+ "Reference SDR ADS-B decoder."),
235
+ ],
236
+ "github": ["flightaware/dump1090", "wiedehopf/readsb"],
237
+ "arxiv_query": "ADS-B aircraft trajectory anomaly detection surveillance",
238
+ },
239
+ {
240
+ "id": "maritime-picture",
241
+ "tab": "darkhunt",
242
+ "claim": "Maritime / dark-vessel hunting reads public AIS; gaps are "
243
+ "surfaced as candidate dark vessels, not asserted as targets.",
244
+ "maturity": "implemented",
245
+ "sources": [
246
+ _src("dataset", "Digitraffic — open AIS (Fintraffic)",
247
+ "https://www.digitraffic.fi/en/marine-traffic/",
248
+ "Open maritime AIS stream (our live AIS feed)."),
249
+ _src("dataset", "AISStream.io — open AIS websocket",
250
+ "https://aisstream.io/",
251
+ "Public AIS message stream."),
252
+ ],
253
+ "github": ["aisstream/example-code"],
254
+ "arxiv_query": "AIS dark vessel detection maritime anomaly trajectory",
255
+ },
256
+ {
257
+ "id": "pqc-signing",
258
+ "tab": "w910audit",
259
+ "claim": "Receipts can be signed with post-quantum signatures "
260
+ "(ML-DSA / Dilithium) per NIST FIPS 204.",
261
+ "maturity": "implemented",
262
+ "sources": [
263
+ _src("standard", "NIST FIPS 204 — ML-DSA (module-lattice signature)",
264
+ "https://csrc.nist.gov/pubs/fips/204/final",
265
+ "Standardised post-quantum signature."),
266
+ _src("project", "pq-crystals/dilithium (reference)",
267
+ "https://github.com/pq-crystals/dilithium",
268
+ "Reference ML-DSA / Dilithium implementation."),
269
+ _src("project", "Open Quantum Safe — liboqs",
270
+ "https://github.com/open-quantum-safe/liboqs",
271
+ "PQC algorithm library."),
272
+ ],
273
+ "github": ["pq-crystals/dilithium", "open-quantum-safe/liboqs"],
274
+ "arxiv_query": "ML-DSA Dilithium lattice post-quantum digital signature",
275
+ },
276
+ {
277
+ "id": "stl-monitoring",
278
+ "tab": "w910stl",
279
+ "claim": "Runtime behaviour is checked against Signal Temporal Logic "
280
+ "rules, returning a signed robustness margin rho.",
281
+ "maturity": "research",
282
+ "sources": [
283
+ _src("project", "Robustness of STL (Donze & Maler, FORMATS 2010)",
284
+ "https://link.springer.com/chapter/10.1007/978-3-642-15297-9_9",
285
+ "Quantitative robustness semantics (the rho margin)."),
286
+ _src("project", "RTAMT — runtime STL monitoring",
287
+ "https://github.com/nickovic/rtamt",
288
+ "Open STL monitor library."),
289
+ ],
290
+ "github": ["nickovic/rtamt"],
291
+ "arxiv_query": "signal temporal logic robustness runtime monitoring",
292
+ },
293
+ {
294
+ "id": "counter-uas",
295
+ "tab": "engage",
296
+ "claim": "Counter-UAS engagement reasoning is grounded in published "
297
+ "drone-detection research; it is advisory, not a targeting product.",
298
+ "maturity": "research",
299
+ "sources": [
300
+ _src("standard", "FAA UAS Remote ID rule",
301
+ "https://www.faa.gov/uas/getting_started/remote_id",
302
+ "Regulatory basis for drone identification."),
303
+ _src("project", "DJI / open drone telemetry references",
304
+ "https://github.com/opendroneid/opendroneid-core-c",
305
+ "Open Drone ID core library."),
306
+ ],
307
+ "github": ["opendroneid/opendroneid-core-c"],
308
+ "arxiv_query": "counter UAS drone detection deep learning survey",
309
+ },
310
+ ],
311
+ }
312
+
313
+ # ---------------------------------------------------------------------------
314
+ # Live fetch helpers (server-side, cached, honest). No fabrication: on failure
315
+ # we return mode="cached"/"unreachable" and lean on the curated bundle.
316
+ # ---------------------------------------------------------------------------
317
+
318
+ _CACHE: Dict[str, Dict[str, Any]] = {}
319
+ _TTL = 1800 # 30 min
320
+
321
+
322
+ def _get(url: str, timeout: int = 12, headers: Optional[Dict[str, str]] = None) -> bytes:
323
+ req = urllib.request.Request(url, headers={"User-Agent": _UA, **(headers or {})})
324
+ with urllib.request.urlopen(req, timeout=timeout) as r: # nosec - public read-only APIs
325
+ return r.read()
326
+
327
+
328
+ def _arxiv(query: str, limit: int = 5) -> Dict[str, Any]:
329
+ key = "arxiv:" + query
330
+ now = time.time()
331
+ hit = _CACHE.get(key)
332
+ if hit and now - hit["_t"] < _TTL:
333
+ return {**hit["v"], "mode": "cached", "fetched_at": hit["at"]}
334
+ params = urllib.parse.urlencode({
335
+ "search_query": "all:" + query,
336
+ "start": 0,
337
+ "max_results": limit,
338
+ "sortBy": "relevance",
339
+ })
340
+ try:
341
+ raw = _get(_ARXIV + "?" + params, timeout=14)
342
+ ns = {"a": "http://www.w3.org/2005/Atom"}
343
+ root = ET.fromstring(raw)
344
+ papers = []
345
+ for e in root.findall("a:entry", ns):
346
+ link = ""
347
+ for l in e.findall("a:link", ns):
348
+ if l.get("rel") == "alternate" or l.get("type") == "text/html":
349
+ link = l.get("href") or link
350
+ title = (e.findtext("a:title", default="", namespaces=ns) or "").strip()
351
+ summ = (e.findtext("a:summary", default="", namespaces=ns) or "").strip()
352
+ pub = (e.findtext("a:published", default="", namespaces=ns) or "")[:10]
353
+ authors = [a.findtext("a:name", default="", namespaces=ns)
354
+ for a in e.findall("a:author", ns)][:4]
355
+ papers.append({
356
+ "title": title, "url": link or (e.findtext("a:id", default="", namespaces=ns) or ""),
357
+ "published": pub, "authors": [a for a in authors if a],
358
+ "summary": summ[:280],
359
+ })
360
+ val = {"source": "arXiv API", "source_url": "https://arxiv.org",
361
+ "query": query, "count": len(papers), "papers": papers}
362
+ at = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
363
+ _CACHE[key] = {"v": val, "_t": now, "at": at}
364
+ return {**val, "mode": "live", "fetched_at": at}
365
+ except Exception as ex: # noqa: BLE001
366
+ if hit:
367
+ return {**hit["v"], "mode": "cached", "fetched_at": hit["at"],
368
+ "note": "live arXiv unreachable; cached result"}
369
+ return {"source": "arXiv API", "source_url": "https://arxiv.org",
370
+ "query": query, "count": 0, "papers": [],
371
+ "mode": "unreachable", "error": str(ex)[:120],
372
+ "note": "live arXiv unreachable; see curated sources below"}
373
+
374
+
375
+ def _github(repo: str) -> Dict[str, Any]:
376
+ key = "gh:" + repo
377
+ now = time.time()
378
+ hit = _CACHE.get(key)
379
+ if hit and now - hit["_t"] < _TTL:
380
+ return {**hit["v"], "mode": "cached", "fetched_at": hit["at"]}
381
+ try:
382
+ raw = _get(_GH_REPO + repo, timeout=10,
383
+ headers={"Accept": "application/vnd.github+json"})
384
+ d = json.loads(raw)
385
+ val = {
386
+ "repo": repo, "url": d.get("html_url") or ("https://github.com/" + repo),
387
+ "stars": d.get("stargazers_count"),
388
+ "pushed_at": (d.get("pushed_at") or "")[:10],
389
+ "license": ((d.get("license") or {}) or {}).get("spdx_id"),
390
+ "description": (d.get("description") or "")[:160],
391
+ "archived": bool(d.get("archived")),
392
+ }
393
+ at = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
394
+ _CACHE[key] = {"v": val, "_t": now, "at": at}
395
+ return {**val, "mode": "live", "fetched_at": at}
396
+ except Exception as ex: # noqa: BLE001
397
+ if hit:
398
+ return {**hit["v"], "mode": "cached", "fetched_at": hit["at"]}
399
+ return {"repo": repo, "url": "https://github.com/" + repo,
400
+ "stars": None, "mode": "unreachable", "error": str(ex)[:120]}
401
+
402
+
403
+ _HONEST = (
404
+ "Evidence layer: every source below is a real, resolvable citation — an "
405
+ "official standard, a public dataset, or a GitHub repository. Paper lists "
406
+ "and repo stats are fetched live from the arXiv and GitHub APIs and labelled "
407
+ "live/cached/unreachable; a down feed degrades to the curated citations, "
408
+ "never to fabricated figures. No synthetic numbers are introduced here."
409
+ )
410
+
411
+
412
+ def _claims_for(ns: str) -> List[Dict[str, Any]]:
413
+ return CLAIMS.get(ns, CLAIMS.get("a11oy", []))
414
+
415
+
416
+ def register(app, ns: str = "a11oy") -> None:
417
+ """Attach the evidence-research endpoints for namespace ns to a FastAPI app."""
418
+ try:
419
+ from fastapi.responses import JSONResponse
420
+ except Exception: # pragma: no cover
421
+ return
422
+
423
+ base = "/api/%s/v1/evidence/research" % ns
424
+
425
+ @app.get(base)
426
+ async def _evidence_index(): # noqa: ANN202
427
+ claims = _claims_for(ns)
428
+ out = []
429
+ for c in claims:
430
+ out.append({
431
+ "id": c["id"], "tab": c.get("tab"), "claim": c["claim"],
432
+ "maturity": c.get("maturity"),
433
+ "sources": c["sources"],
434
+ "github_repos": c.get("github", []),
435
+ "arxiv_query": c.get("arxiv_query"),
436
+ "live_endpoint": "%s/%s/live" % (base, c["id"]),
437
+ })
438
+ return JSONResponse({
439
+ "layer": "%s evidence & research" % ns,
440
+ "honest": _HONEST,
441
+ "count": len(out),
442
+ "claims": out,
443
+ })
444
+
445
+ @app.get(base + "/{claim_id}/live")
446
+ async def _evidence_live(claim_id: str): # noqa: ANN202
447
+ claim = next((c for c in _claims_for(ns) if c["id"] == claim_id), None)
448
+ if not claim:
449
+ return JSONResponse({"error": "unknown claim", "claim_id": claim_id}, status_code=404)
450
+ papers = _arxiv(claim.get("arxiv_query", claim["claim"]))
451
+ repos = [_github(r) for r in claim.get("github", [])]
452
+ return JSONResponse({
453
+ "id": claim["id"], "claim": claim["claim"], "tab": claim.get("tab"),
454
+ "honest": _HONEST,
455
+ "sources": claim["sources"],
456
+ "arxiv": papers,
457
+ "github": repos,
458
+ })
459
+
460
+ @app.get(base + "/refresh")
461
+ async def _evidence_refresh(): # noqa: ANN202
462
+ claims = _claims_for(ns)
463
+ rows = []
464
+ for c in claims:
465
+ p = _arxiv(c.get("arxiv_query", c["claim"]), limit=2)
466
+ rows.append({
467
+ "id": c["id"], "claim": c["claim"],
468
+ "arxiv_count": p.get("count", 0), "arxiv_mode": p.get("mode"),
469
+ "n_sources": len(c["sources"]), "n_repos": len(c.get("github", [])),
470
+ })
471
+ return JSONResponse({
472
+ "layer": "%s evidence freshness sweep" % ns,
473
+ "honest": _HONEST, "claims": rows,
474
+ "fetched_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
475
+ })