{# P5 recovery / gate / escalation panel (FR-4/FR-5). Rendered inside the detail body so it lives-swaps with the rest; the heavier phase diff is a link to the deliberate /runs/{slug}/diff page (not recomputed per SSE tick). `intel` is the resume_intel verdict; `gate` (when present) is the resolved gate/escalation evidence. Control forms are POSTed by control.js to the sanctioned CLI-verb endpoints; destructive verbs (reject) carry the FR-10.7 confirm. #} {% set controls = intel.available_controls %} {% if intel.state not in ("running", "done") %}

{% if intel.state == "gate" %}Pending gate — your decision {% elif intel.state == "escalation" %}Escalation — reconcile, then resume {% elif intel.state == "interrupted" %}Interrupted — resume available {% elif intel.state.startswith("halted") %}Halted — recovery {% elif intel.state == "failed" %}Failed {% elif intel.state == "rejected" %}Rejected (terminal) {% else %}Recovery{% endif %}

Recommended: {{ intel.recommended_action }}

{{ intel.rationale }}

{# --- fail closed (review F-001): gate evidence could not be assembled (unsafe show: path / unreadable artifact). Surface it and suppress the controls below so no decision is made without the required evidence. #} {% if gate_error %} {% endif %} {# --- escalation evidence (FR-4.6): findings + triage verdicts assembled --- #} {% if gate and gate.kind == "escalation" %} {% if gate.target_artifacts %} {% endif %} {% for f in gate.escalated %} {% endfor %}
IDSevCategoryLocationClaimVerdictActionTargetReasoning
{{ f.finding_id }}{{ f.severity or "—" }}{{ f.category or "—" }} {{ f.location or "—" }}{{ f.claim or "—" }} {{ f.verdict or "—" }}{{ f.action or "—" }} {{ f.target_artifact or "—" }}{{ f.reasoning or "—" }}
{% endif %} {# --- gate show: artifacts (FR-4.3): rendered readably --- #} {% if gate and gate.kind == "gate" %} {% if gate.upstream_conflict %}
⚠ UPSTREAM CONFLICT (builder-signalled, FR-4.5)
{{ gate.upstream_conflict }}
{% endif %} {# --- FR-8.1 gate decision context: convergence + prior responses + escalated triage reasoning, so the decision is makeable from this view alone. #} {% set gc = gate.context %} {% if gc %} {% set conv = gc.convergence %} {% if conv %}
Cycle convergence {% if gc.cycle_step_id %}({{ gc.cycle_step_id }}){% endif %} — {{ conv.rounds if conv.rounds is not none else "?" }} round(s), {{ conv.findings_total if conv.findings_total is not none else "?" }} raised, {{ conv.accepted_total if conv.accepted_total is not none else "?" }} accepted {% if conv.per_round %} {% for r in conv.per_round %} {% endfor %}
RoundRaisedFixedDeclined
{{ r.round }} {{ r.raised if r.raised is not none else "—" }} {{ r.fixed if r.fixed is not none else "—" }} {{ r.declined if r.declined is not none else "—" }}
{% endif %}
{% endif %} {% if gc.prior_responses %}
Prior decisions on this gate ({{ gc.prior_responses | length }}) {% for pr in gc.prior_responses %} {% endfor %}
WhenWhoStateDecision
{{ pr.timestamp }}{{ pr.user }} {{ pr.state }}{{ pr.response_text }}
{% endif %} {% if gc.escalated %}
Escalated / low-confidence findings ({{ gc.escalated | length }}) {% for f in gc.escalated %} {% endfor %}
IDSevVerdictActionConfidenceReasoning
{{ f.finding_id }}{{ f.severity or "—" }} {{ f.verdict or "—" }}{{ f.action or "—" }} {{ f.confidence or "—" }}{{ f.reasoning or "—" }}
{% endif %} {% endif %} {% for a in gate.artifacts %}
{{ a.name }} ({{ a.kind }}{% if a.source %} · {{ a.source }}{% endif %}) {% if a.error %}

could not parse: {{ a.error }}

{% endif %} {# FR-4.3: findings/triage/confirm as readable tables, markdown as markdown. #} {% if a.kind == "findings" and a.parsed is not none %} {% for f in a.parsed.get("findings") or [] %} {% endfor %}
IDSevCategoryLocationClaimEvidenceSuggested fix
{{ f.get("id") or "—" }}{{ f.get("severity") or "—" }} {{ f.get("category") or "—" }}{{ f.get("location") or "—" }} {{ f.get("claim") or "—" }}{{ f.get("evidence") or "—" }} {{ f.get("suggested_fix") or "—" }}
{% if a.parsed.get("summary") %}

{{ a.parsed.get("summary") }}

{% endif %} {% elif a.kind == "triage" and a.parsed is not none %} {% for v in a.parsed.get("verdicts") or [] %} {% endfor %}
FindingVerdictActionConfidenceTargetReasoning
{{ v.get("finding_id") or "—" }}{{ v.get("verdict") or "—" }} {{ v.get("action") or "—" }}{{ v.get("confidence") or "—" }} {{ v.get("target_artifact") or "—" }}{{ v.get("reasoning") or "—" }}
{% elif a.kind == "confirm" and a.parsed is not none %} {% for v in a.parsed.get("verdicts") or [] %} {% endfor %}
FindingVerdictNotes
{{ v.get("finding_id") or "—" }}{{ v.get("verdict") or "—" }} {{ v.get("notes") or "—" }}
{% set newf = a.parsed.get("new_findings") or [] %} {% if newf %}

New findings in this round's diff:

{% for nf in newf %} {% endfor %}
SevClaimLocation
{{ nf.get("severity") or "—" }}{{ nf.get("claim") or "—" }}{{ nf.get("location") or "—" }}
{% endif %} {% if a.parsed.get("summary") %}

{{ a.parsed.get("summary") }}

{% endif %} {% elif a.kind == "json" and a.parsed is not none %}
{{ a.parsed | tojson(indent=2) }}
{% elif a.kind == "markdown" and a.content is not none %}
{{ a.content | markdown }}
{% elif a.content is not none %}
{{ a.content }}
{% elif a.kind == "missing" %}

not written yet

{% endif %}
{% endfor %} {% endif %}

View phase diff → · Judge audit →

{# --- FR-4.7 scoped-analysis hand-off (opt-in). Only when enabled AND parked at a gate/escalation. The console makes NO model call and spawns NOTHING — it fetches the assembled prompt and shows it in a read-only, copy-pasteable textarea (D8). control.js wires the button. #} {% if handoff_enabled and intel.state in ("gate", "escalation") %}

Assembles a read-only, copy-pasteable prompt for a separate analysis session. The console runs nothing (FR-4.7/D8).

{% endif %} {# --- control forms: only the verbs resume_intel permits (FR-5.3), and never when gate evidence failed to assemble (review F-001, fail closed). --- #} {% if not external and not gate_error %}
{% set gate_cycle = gate.context.cycle_step_id if (gate and gate.context) else None %} {% if "approve" in controls %}
{# FR-8.2 consequence #} → continues the run past this gate
{% endif %} {% if "reject" in controls %} {# FR-8.2 consequence: name the cycle a reject re-runs (else terminal). The plain-text form is carried on the form (data-reject-consequence) so the JS confirm prompt states the SAME consequence as the rendered control — never a stale "the run fails" that contradicts the re-drive (F-003). #} {% set reject_consequence = ("re-runs the '" ~ gate_cycle ~ "' cycle with your notes injected as a new round") if gate_cycle else "terminally rejects the gate (no upstream cycle to re-run)" %}
→ {% if gate_cycle %}re-runs the {{ gate_cycle }} cycle with your notes injected{% else %}terminally rejects the gate{% endif %}
{% endif %} {% if "resume" in controls %}
{% endif %}
{% endif %}
{% endif %}