{# P3-T22 — per-step Pipeline Replay Explorer. Reproduces §4·f of the scan-debug redesign. This is the pipeline-level replay step (one row in scan_events with step="replay"); distinct from the per-request replay UI that lives inside debug_step_validation.html. Inputs (assembled by ``routes.debug._replay_step_view``): * ``view.scan_id`` / ``view.scan_id_short`` / ``view.back_to_trace_url`` * ``view.step_index`` / ``view.step_index_padded`` / ``view.total_step_count`` * ``view.duration`` / ``view.status_label`` / ``view.status_pill_class`` * ``view.is_in_progress`` / ``view.is_cancelled_scan`` * ``view.error_class`` / ``view.error_message`` / ``view.owner_email`` * ``view.summary_sub_line`` — e.g. "5 endpoints · 4 matched expected shape · 1 differed" * ``view.endpoints`` — list of ``{ep_id, method, url, status, status_pill_class, matched_label, matched_class, reason, inspect_url}`` * ``view.replay_confidence_label`` — "0.80 · 4/5 endpoints matched" or ``""`` * ``view.replay_confidence_class`` — "v-ok"/"v-warn"/"v-danger"/"v-muted" * ``view.cooldown_label`` — "not triggered" / "applied · ..." (placeholder for now) * ``view.surfaced_label`` — "yes · banner suppressed" / "no · low-confidence banner shown" * ``view.has_endpoints`` — bool; false when scan.replay_per_endpoint is NULL #} {% extends "admin_layout.html" %} {% from 'partials/help_tooltip.html' import tip, tip_styles %} {% block page_title %}admin :: step :: {{ view.scan_id_short }} / {{ view.step_index_padded }}{% endblock %} {% block breadcrumb %} admin/ scans/ {{ view.scan_id_short }}/ step {{ view.step_index_padded }} {% endblock %} {% block head_extra %} {{ tip_styles() }} {% if view.is_in_progress %}{% endif %} {% endblock %} {% block content %} {# ============== Sticky breadcrumb strip ============== #}
← back to trace · step {{ view.step_index_padded }} of {{ view.total_step_count }} {{ tip("Pipeline replay — an automatic best-effort verification fired by the synthesis pass. Distinct from the per-request replay inside the Validation Explorer.", position="bottom") }}
{# ============== Eyebrow + headline ============== #}
02.1 — scan debug · step {{ view.step_index_padded }} · replay

Step {{ view.step_index_padded }} — replay · best-effort verification {% if view.is_cancelled_scan %}[cancelled scan]{% endif %}

{{ view.duration }}· {{ view.status_label }} {% if view.summary_sub_line %} · {{ view.summary_sub_line }} {% endif %} {% if is_admin and view.owner_email %} ·{{ view.owner_email }} {% endif %}
{# ============== In-progress banner (P3-T23) ============== #} {% if view.is_in_progress %}
// in progress step {{ view.step_index_padded }} (replay) is still running — page auto-refreshes every 30s.
{% endif %} {# ============== Error banner (§4·g) ============== #} {% if view.error_class or view.error_message %} {% endif %} {# ============== PER-ENDPOINT REPLAY card ============== #}
per-endpoint replay {{ tip("Each Bucket A endpoint the CLI fired against the live site after synthesis. Status is the HTTP status the replay saw; matched-shape is the comparison against the captured response_summary.") }} {{ view.endpoints | length }} endpoint{{ '' if view.endpoints | length == 1 else 's' }}
{% if not view.has_endpoints %}
scan.replay_per_endpoint is NULL — pipeline replay did not run on this scan.
{% else %}
{% for ep in view.endpoints %} {% endfor %}
# method · url {{ tip("HTTP method + URL the pipeline replay fired against the live site.") }} status {{ tip("HTTP status code returned by the live site for this replay. 2xx is green; 4xx/5xx render in danger.") }} matched shape {{ tip("Whether the live response matched the captured response_summary's top-level key set. 'yes' = identical key structure; 'no' = drift; 'error' = the replay itself failed (network / SSL / timeout).") }} reason {{ tip("Free-text explanation from the replay runner. For matched=no this typically lists the expected vs observed key sets.") }} {{ tip("Link to the per-endpoint inspect view. The dedicated inspect surface is a P5 follow-up; the link uses ?inspect= today.") }}
{{ ep.ep_id }} {{ ep.method }} {{ ep.url }} {{ ep.status if ep.status is not none else '—' }} {{ ep.matched_label }} {{ ep.reason or '—' }} diff →
{% endif %}
{# ============== CONFIDENCE card ============== #}
confidence {{ tip("Roll-up signals derived from the per-endpoint replay outcomes. Drives the low-confidence banner on the rendered report.") }}
replay_confidence {{ tip("Float in [0, 1] — ratio of endpoints whose live response matched the captured shape. NULL means replay did not run.") }}
{% if view.replay_confidence_label %} {{ view.replay_confidence_label }} {% else %} — not recorded {% endif %}
replay_cooldown {{ tip("Per-host cooldown that suppresses re-firing the pipeline replay on the same target within a short window. Persists via replay_history (cooldown 60s).") }}
{{ view.cooldown_label }}
surfaced_on_report {{ tip("Whether this signal is rendered on the public report. 'no' typically means the low-confidence banner was shown instead.") }}
{{ view.surfaced_label }}
{# ============== RE-RUN placeholder card (button intentionally non-functional; P5) ============== #}
re-run {{ tip("Re-fires the pipeline replay against the recommended endpoints — useful when the live site has changed since the original scan. Wiring lands in P5.") }}
subject to replay_history cooldown · cost-capped
{% endblock %}