{# Executive — Probes tab compact table (QA-032 / QA-049 / BUG-1 / per-agent grouping 2026-06-03). A 5-column scannable table: AGENT · ASI | VERDICT | TURNS | LAST ACTIVITY | (row opens modal) One ROW PER AGENT (operator feedback 2026-06-03) ----------------------------------------------- Recon alone emits ~17 individual turn records; rendering one row per turn buried the conversation. Each row now represents ONE AGENT's whole probe run — its ordered turns are collapsed into a single conversational entry (:func:`_assemble_probe_groups`). Clicking the row opens the shared modal (``_slideover.html``) with that agent's full turn-by-turn chat plus the final judge reasoning. Row activation -------------- Each row carries: * ``data-action="probe-row-click"`` — the row-click contract the shared slide-over JS (``executive_findings.js``) keys off (QA-049). Click + Enter/Space open the modal. * ``data-source="probe"`` — selects the probe renderer in JS. * ``data-probe-href="/scan//probe?group="`` — the URL the drawer JS ``fetch()``-es when the row is opened. The server renders the agent's whole conversation. Carries NO verbatim prompt / response / reasoning text in the initial page HTML (BUG-1) — that data only enters the DOM on demand. * ``data-probe-group`` (the agent key) + ``data-probe-id`` (the first seed id, for deep links). Verdict vocab — same single source of truth as the Findings tab evidence pills (QA-031): ``fail`` → EXPLOITED (red), ``pass`` → DEFENDED (green), ``inconclusive`` → INCONCLUSIVE (amber), empty/unset → PENDING (muted). The grouped row's pill is the worst-case rollup across the agent's turns (:func:`_rollup_verdict`). All ``g.*`` fields are operator-untrusted (sourced from ``/memory.jsonl`` via :func:`_assemble_probe_groups`) — Jinja autoescape applies to every cell. The verbatim prompt / response / reasoning text never enters the initial HTML render. #} {% for g in probe_groups %} {% set _verdict = g.verdict or 'unknown' %} {% set _rr = g.run_result %} {# SUMMARY — the AI-written one-liner (synthesised from all of this agent's turns + its rolled-up verdict) when available; falls back to the strongest-turn judge-reasoning gloss while a live scan is still running (the AI summary is generated at scan finalization). #} {% set _summary = g.ai_summary or (_rr.summary if _rr and _rr.has_run_result else '') %} {% endfor %} {% if probe_groups | length == 0 %}{% endif %}
{# Inner flex wrapper mirrors the body cell: "AGENT · ASI" left, "RUNS" pushed right so the header label sits directly above the right-aligned run counts. Wrapper (not the th itself) so the th stays a table-cell and the fixed column width is preserved. #} AGENT · ASI RUNS STATUS VERDICT SUMMARY
{{ g.asi_category or '—' }} {{ g.agent or '—' }} {# RUNS — turn count moved next to the ASI badge (was a far-right column). #} {{ g.turn_count }} run{{ '' if g.turn_count == 1 else 's' }} {# Run status — completed / running / skipped. Server-rendered from the per-agent terminal events.jsonl signal; ``live-append.js`` updates it in place as ``agent_start`` / ``agent_done`` / ``agent_skipped`` arrive over SSE. #} {{ g.status_label or 'Running' }} {# Worst-case rollup pill — same single source of truth as the Findings tab evidence pills. Judge v2 (M5 / Stage D) gives each verdict a distinct pill colour via ``exec-verdict-pill--``. #} {%- if _verdict == 'fail' or _verdict == 'exploited' or _verdict == 'info_leak' -%}EXPLOITED {%- elif _verdict == 'vulnerable' -%}VULNERABLE {%- elif _verdict == 'needs_followup' -%}NEEDS FOLLOW-UP {%- elif _verdict == 'simulated_or_unverified' -%}UNVERIFIED {%- elif _verdict == 'pass' or _verdict == 'defended' -%}DEFENDED {%- elif _verdict == 'inconclusive' -%}INCONCLUSIVE {%- elif _verdict == 'recon' -%}RECON {%- else -%}PENDING {%- endif -%} {{ _summary or '—' }} {% if _rr and _rr.has_run_result and _rr.evaluator_attack %} ⚠ evaluator-attack {% endif %}
No probe attempts recorded yet — they appear here as the swarm runs.