{# T55 scan debug page — terminal-brutalist re-skin. #} {# Header (always visible) + sticky tab bar + 5 panes. All panes are #} {# server-rendered; tiny inline JS toggles visibility on click and #} {# rewrites the URL via ``history.replaceState`` so a deep link to #} {# ``?tab=llm`` survives a reload AND stays shareable. #} {% extends "admin_layout.html" %} {% block page_title %}admin :: scan :: {{ scan_id_short }}{% endblock %} {% block breadcrumb %} admin/ scans/ {{ scan_id_short }} {% endblock %} {% block content %} {# Status pill mapping — keeps colour rules in one spot. #} {% set status_pill_class = { 'ready': 'ok', 'success': 'ok', 'errored': 'danger', 'error': 'danger', 'cancelled': 'muted', 'pending': 'accent', }.get(status, 'muted') %}
02.1 — scan debug

Scan {{ scan_id_short }}

{# ================== Scan header card ================== #}
target
{{ status }} {% if is_admin %}viewing as admin{% endif %} {% if deleted_at_display %} {# Wording preserved verbatim ("(deleted YYYY-MM-DD)") so the existing soft-delete integration tests stay green. #} (deleted {{ deleted_at_display }}) {% endif %}
scan_id{{ scan_id }}
target{{ target_url }}
{% if is_admin and owner_email %}
owner{{ owner_email }}
{% endif %}
intent{{ intent_text or '(no intent recorded)' }}
{# ================== Sticky tab bar ================== #} {% set tabs = [ ('overview', 'overview'), ('capture', 'capture'), ('buckets', 'buckets'), ('llm', 'llm calls'), ('evals', 'evals'), ('audit', 'audit'), ] %} {# ================== Tab pane: Overview ================== #}
synthesis view
{% if not synthesis_view %}
// No synthesis output persisted yet.
{% else %} {% if synthesis_view.report_id %}

full report →

{% endif %}

verdict

{% if synthesis_view.verdict %}
{{ synthesis_view.verdict }}
{% else %}

(empty)

{% endif %}

primary recommendation

{{ synthesis_view.primary_recommendation or '(none)' }}

// view recommendation JSON
{{ synthesis_view.recommendation | tojson(indent=2) }}
// view starter_code JSON
{{ synthesis_view.starter_code | tojson(indent=2) }}
{% endif %}
bucket distribution
bucket A
{{ bucket_counts.A }}
target endpoints
bucket B
{{ bucket_counts.B }}
prerequisites
bucket C
{{ bucket_counts.C }}
ignored
replay confidence
{% set replay = (synthesis_view.recommendation or {}).get('replay_confidence') if synthesis_view else None %} {% if replay is not none %}
{{ (replay * 100) | round(0, 'floor') | int }}%

model-estimated replay survivability

{% else %}
// No replay-confidence signal on this scan.
{% endif %}
{% if is_super_admin %}
[super_admin] force hard-delete

Skips the 30-day grace and wipes the scan + LLM-call rows + S3 objects immediately. Audit log entries for this scan are anonymized so the compliance trail survives. This cannot be undone.

{% endif %}
{# ================== Tab pane: Capture ================== #}
re-run

Filter + synthesis are free; full re-run mints a new scan + costs 1 credit.

capture timeline
{% if not timeline %}
// No segmented actions persisted for this scan.
{% else %} {% for action in timeline %}
{{ loop.index }}. {{ action.label or '(unlabelled click)' }} {% if action.started_at %} · t={{ '%.2f'|format(action.started_at) }}s {% endif %}
{% if action.click_target %}
{{ action.click_target.tag or '?' }} {%- if action.click_target.text %} · "{{ action.click_target.text }}"{% endif %}
{% endif %} {% if action.requests %} {% for req in action.requests %}
{{ req.bucket }} {{ req.method }} {{ req.url }}
{% if req.response_summary %}
// response summary
{{ req.response_summary }}
{% endif %}
{% endfor %} {% else %}
(no requests captured in this action)
{% endif %}
{% endfor %} {% endif %}
{# ================== Tab pane: Buckets ================== #}
bucket explainer
{% if not bucket_explainer %}
// No bucket assignment persisted (filter did not run, or legacy scan).
{% else %} {% set bucket_order = ['A', 'B', 'C', '-'] %} {% for bucket_label in bucket_order %} {% set group = bucket_explainer | selectattr('bucket', 'equalto', bucket_label) | list %} {% if group %}

bucket {{ bucket_label }} ({{ group | length }})

{% for ep in group %}
{{ ep.bucket }} {{ ep.method }} {{ ep.url_template }}

bucket signals

{% if ep.signals %}
{% for k, v in ep.signals.items() %}
{{ k }}
{{ v }}
{% endfor %}
{% else %}

No signals captured.

{% endif %} {% if ep.rationale %}

filter rationale

{{ ep.rationale }}
{% endif %}
{% endfor %} {% endif %} {% endfor %} {% endif %}
{# ================== Tab pane: LLM calls ================== #}
LLM call ledger
{% if not llm_ledger %}
// No LLM calls recorded for this scan.
{% else %}
{% for call in llm_ledger %} {# T32: full-width expand row, hidden until clicked. #} {% endfor %}
when prompt model in / out / cache cost latency status dump
{{ call.created_at }} {{ call.prompt_name }}
v{{ call.prompt_version }}
{{ call.model }} {{ call.input_tokens }} / {{ call.output_tokens }} / {{ call.cache_read_tokens or 0 }}r+{{ call.cache_write_tokens or 0 }}w ${{ '%.6f'|format(call.cost_usd) }} {{ call.latency_ms }}ms {% if call.error_class %} {{ call.error_class }} {% if call.error_message %}
{{ call.error_message }}
{% endif %} {% else %} ok{% if call.retry_count %} (×{{ call.retry_count }}){% endif %} {% endif %}
{% if is_admin %} sandbox {% endif %}
{% with chat_view_id='call-' ~ call.id, dump_url='/debug/scans/' ~ scan_id ~ '/llm-calls/' ~ call.id ~ '/dump' %} {% include 'partials/llm_chat_view.html' %} {% endwith %}
{% endif %}
{# ================== Tab pane: Evals (T48) ================== #}
{% include 'partials/evals_matrix.html' %} {% with mode='fixed-scan', fixed_scan_id=scan_id %} {% include 'partials/eval_new_modal.html' %} {% endwith %}
{# ================== Tab pane: Audit ================== #}
audit log
{% if not audit_entries %}
// No audit entries recorded for this scan.
{% else %}
{% for entry in audit_entries %} {% endfor %}
timestamp actor action metadata
{{ entry.timestamp }} {{ entry.actor_email }} {{ entry.action }} {% if entry.metadata %} {{ entry.metadata | tojson }} {% else %} {% endif %}
{% endif %}
{% endblock %} {% block scripts %} {% endblock %}