stage-22: cockpit reflects the live adapter + all six feeds
The Worker Mesh Classifier badge was hardcoded "psyc-v4" — it kept claiming the old model even after the v5 swap, making it look like nothing had changed. Now derived dynamically from the inference server's reported adapter via inference.adapter_name(); shows the real version (psyc-v5) or "rules" when the server is down, so it can't go stale on the next swap. Also refreshed the cases page intro/help text to name all six feeds (URLhaus, CISA KEV, Feodo, ThreatFox, MalwareBazaar, OTX) instead of the original three. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -77,7 +77,12 @@ def case_journey(request: Request, case_id: str) -> HTMLResponse:
|
|||||||
if isinstance(result, Err):
|
if isinstance(result, Err):
|
||||||
raise HTTPException(status_code=404, detail=result.reason)
|
raise HTTPException(status_code=404, detail=result.reason)
|
||||||
beats = journey_view.build_journey(result.value)
|
beats = journey_view.build_journey(result.value)
|
||||||
return TEMPLATES.TemplateResponse(request, "journey.html", {"case": result.value, "beats": beats})
|
model_label = inference.adapter_name() or "rules"
|
||||||
|
return TEMPLATES.TemplateResponse(
|
||||||
|
request,
|
||||||
|
"journey.html",
|
||||||
|
{"case": result.value, "beats": beats, "model_label": model_label},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/ledger", response_class=HTMLResponse)
|
@app.get("/ledger", response_class=HTMLResponse)
|
||||||
|
|||||||
@@ -34,6 +34,15 @@ def server_adapter(timeout: float = 2.0) -> Optional[str]:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def adapter_name(timeout: float = 2.0) -> Optional[str]:
|
||||||
|
"""Short name of the live adapter, e.g. 'psyc-v5' from '/data/adapters/psyc-v5/final'."""
|
||||||
|
path = server_adapter(timeout=timeout)
|
||||||
|
if not path:
|
||||||
|
return None
|
||||||
|
parts = [p for p in path.split("/") if p and p != "final"]
|
||||||
|
return parts[-1] if parts else None
|
||||||
|
|
||||||
|
|
||||||
def model_severity(case: Case, timeout: float = 15.0) -> Optional[str]:
|
def model_severity(case: Case, timeout: float = 15.0) -> Optional[str]:
|
||||||
"""Ask the live model to classify case severity. None if the server is down."""
|
"""Ask the live model to classify case severity. None if the server is down."""
|
||||||
payload = {
|
payload = {
|
||||||
|
|||||||
@@ -6,12 +6,12 @@
|
|||||||
<h1>Case Queue</h1>
|
<h1>Case Queue</h1>
|
||||||
<span class="count">{{ total }} case{{ '' if total == 1 else 's' }}</span>
|
<span class="count">{{ total }} case{{ '' if total == 1 else 's' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="page-intro">Every threat case psyc is tracking — ingested from URLhaus, CISA KEV, and Feodo Tracker, then classified by severity and TLP. The live queue of what the platform currently knows about; click any case to follow it through the pipeline.</p>
|
<p class="page-intro">Every threat case psyc is tracking — ingested from URLhaus, CISA KEV, Feodo Tracker, ThreatFox, MalwareBazaar, and AlienVault OTX, then classified by severity and TLP. The live queue of what the platform currently knows about; click any case to follow it through the pipeline.</p>
|
||||||
<details class="page-help">
|
<details class="page-help">
|
||||||
<summary>how to use this view</summary>
|
<summary>how to use this view</summary>
|
||||||
<div class="help-body">
|
<div class="help-body">
|
||||||
<p><b>How to use.</b> Scan the severity and TLP badges to triage; click any case ID to open its full record and Worker Mesh journey. Run <code>psyc fetch-all</code> to pull fresh cases from the feeds.</p>
|
<p><b>How to use.</b> Scan the severity and TLP badges to triage; click any case ID to open its full record and Worker Mesh journey. Run <code>psyc fetch-all</code> to pull fresh cases from the feeds.</p>
|
||||||
<p><b>What you're seeing.</b> Each row is one normalized Case object — ingested by Scoutline from URLhaus, CISA KEV, or Feodo Tracker, then rated by Classifyline.</p>
|
<p><b>What you're seeing.</b> Each row is one normalized Case object — ingested by Scoutline from any of six feeds (URLhaus, CISA KEV, Feodo Tracker, ThreatFox, MalwareBazaar, OTX), then rated by Classifyline.</p>
|
||||||
<p><b>Why it matters.</b> A defender needs one place that answers "what is happening right now" before deciding what to act on — this queue is that place.</p>
|
<p><b>Why it matters.</b> A defender needs one place that answers "what is happening right now" before deciding what to act on — this queue is that place.</p>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
|
|||||||
@@ -74,7 +74,7 @@
|
|||||||
{% set rule_sev = case.classification.severity.value if case.classification.severity else '' %}
|
{% set rule_sev = case.classification.severity.value if case.classification.severity else '' %}
|
||||||
{% set agrees = b.model_answer == rule_sev %}
|
{% set agrees = b.model_answer == rule_sev %}
|
||||||
<p class="bot-model {{ 'model-agree' if agrees else 'model-differ' }}">
|
<p class="bot-model {{ 'model-agree' if agrees else 'model-differ' }}">
|
||||||
<span class="model-chip">⬡ psyc-v4 · live model</span>
|
<span class="model-chip">⬡ {{ model_label }} · live model</span>
|
||||||
severity: <strong>{{ b.model_answer | upper }}</strong>
|
severity: <strong>{{ b.model_answer | upper }}</strong>
|
||||||
{% if agrees %}<span class="model-verdict">✓ agrees with the rule</span>
|
{% if agrees %}<span class="model-verdict">✓ agrees with the rule</span>
|
||||||
{% else %}<span class="model-verdict">✗ differs — rule said {{ rule_sev | upper }}</span>{% endif %}
|
{% else %}<span class="model-verdict">✗ differs — rule said {{ rule_sev | upper }}</span>{% endif %}
|
||||||
|
|||||||
Reference in New Issue
Block a user