{% if domain %}{% endif %}
Finding Diff - Finite State Report
{% include "_design_system.html" %}
{% include "_console_shell.html" %}
{% endif %}
{% if render_mode != 'fragment' %}
{% endif %}
{% import "_console_macros.html" as fs with context %}
{# Defensive defaults #}
{% set summary = summary | default({}) %}
{% if summary is not mapping %}{% set summary = {} %}{% endif %}
{% set port_fixes_left_to_right = port_fixes_left_to_right | default([]) %}
{% set port_fixes_right_to_left = port_fixes_right_to_left | default([]) %}
{% set empty_side = empty_side | default(None) %}
{% set left_label = summary.get('left_label', 'Left') if summary is mapping else 'Left' %}
{% set right_label = summary.get('right_label', 'Right') if summary is mapping else 'Right' %}
{% set inventory_available = summary.get('inventory_available', True) if summary is mapping else True %}
{# Leader direction — injected by the two-pass engine (§5.0) as data.left_leads
/ data.leader_label / data.laggard_label. Defaults keep the standalone path
(no compound) rendering left→right. The PRIMARY worklist is leader→laggard;
the reverse is collapsed. #}
{% set left_leads = left_leads | default(True) %}
{% set leader_label = leader_label | default(left_label) %}
{% set laggard_label = laggard_label | default(right_label) %}
{# Primary = leader→laggard rows; reverse = the other direction. #}
{% set primary_rows = port_fixes_left_to_right if left_leads else port_fixes_right_to_left %}
{% set reverse_rows = port_fixes_right_to_left if left_leads else port_fixes_left_to_right %}
{% set primary_count = (summary.get('fixed_left_open_right_count', 0) if left_leads else summary.get('fixed_right_open_left_count', 0)) if summary is mapping else 0 %}
{% set reverse_count = (summary.get('fixed_right_open_left_count', 0) if left_leads else summary.get('fixed_left_open_right_count', 0)) if summary is mapping else 0 %}
{% if render_mode != 'fragment' %}
{{ fs.topbar(
crumbs=["Finite State", "Finding Diff"],
meta=[
{"label": "Left", "value": left_label},
{"label": "Right", "value": right_label},
{"label": "Generated", "value": generated_at or "—"},
],
controls=[],
) }}
{% endif %}
Finding Comparison
{{ left_label }} vs {{ right_label }}
{% if empty_side %}
{% if empty_side == 'both' %}
Both scopes had no findings data.
{% elif empty_side == 'left' %}
{{ left_label }} scope had no data — nothing to compare.
{% elif empty_side == 'right' %}
{{ right_label }} scope had no data — nothing to compare.
{% endif %}
{% else %}
{# Inventory unavailable note #}
{% if not inventory_available %}
Note: Component inventory was not available for one or both scopes.
Findings absent from the other scope's finding list are excluded from the port-fix
tables (conservative default). Run with component inventory enabled for full
fix-evidence classification.
{% endif %}
{# ---- counts pulled once from summary (defensive) ---- #}
{% set fix_lr = summary.get('fixed_left_open_right_count', 0) if summary is mapping else 0 %}
{% set fix_rl = summary.get('fixed_right_open_left_count', 0) if summary is mapping else 0 %}
{% set both_n = summary.get('needs_action_in_both_count', 0) if summary is mapping else 0 %}
{% set supp_n = summary.get('suppressed_divergence_count', 0) if summary is mapping else 0 %}
{# Section intro: data-derived lede + scope tag #}
{{ fix_lr }} fixed in {{ left_label }}, open in {{ right_label }} ·
{{ fix_rl }} the reverse ·
{{ both_n }} open on both ·
{{ supp_n }} suppressed on one side only.
{# ---- PRIMARY worklist: leader → laggard, grouped by the UPGRADE that
clears each component. Per-row FIX TARGET column replaces the
decorative risk gauge; Project column + owner rollup bind to
row.project_names (the needs-action laggard variants). ---- #}
{% if primary_rows and primary_rows | length > 0 %}
Grouped by the upgrade that clears them — {{ leader_label | short_scope }} → {{ laggard_label | short_scope }}
Fixes resolved in {{ leader_label | short_scope }}, still open in {{ laggard_label | short_scope }}. The action is the component bump at the top of each group; the CVE rows are the drill-down of what it clears.
{% for component, rows in primary_rows | groupby('component_name') %}
{% set rows = rows | list %}
{% set g_crit = rows | selectattr('severity', 'eq', 'CRITICAL') | list | length %}
{% set g_high = rows | selectattr('severity', 'eq', 'HIGH') | list | length %}
{% set g_med = rows | selectattr('severity', 'eq', 'MEDIUM') | list | length %}
{% set g_low = rows | selectattr('severity', 'eq', 'LOW') | list | length %}
{% set ev = namespace(absent=0) %}
{% for r in rows %}{% if 'not present' in (r.fix_evidence or '') %}{% set ev.absent = ev.absent + 1 %}{% endif %}{% endfor %}
{% set ev_absent = ev.absent %}
{% set ev_vex = (rows | length) - ev_absent %}
{# Group fix_target: the single leader version off the rows (one per
component). Target-only header; the exact per-row arrow lives in the
FIX TARGET column. #}
{% set ft = namespace(v=None) %}
{% for r in rows %}{% if ft.v is none and r.fix_target %}{% set ft.v = r.fix_target %}{% endif %}{% endfor %}
{% set group_target = ft.v %}
{# Owner rollup: union of the group's rows' project_names. #}
{% set owner_ns = namespace(items=[]) %}
{% for r in rows %}{% for p in (r.project_names or []) %}{% if p and p not in owner_ns.items %}{% set _ = owner_ns.items.append(p) %}{% endif %}{% endfor %}{% endfor %}
{% set group_owners = owner_ns.items | sort %}
{% set tbl_id = 'fdp_' ~ loop.index %}
{% if group_target %}Upgrade {{ component or '—' }} →{{ group_target }}{% else %}Rebuild {{ component or '—' }}{% endif %}Clears {{ rows | length }} · evidence:
{% if ev_vex > 0 %}{{ ev_vex }} VEX resolved{% endif %}{% if ev_vex > 0 and ev_absent > 0 %} · {% endif %}{% if ev_absent > 0 %}{{ ev_absent }} component change{% endif %}{% if ev_vex == 0 and ev_absent == 0 %}—{% endif %}
{% if group_owners %}
Affects{% for o in group_owners %}{{ o | short_scope }}{% endfor %}
{% endif %}
{% if g_crit > 0 %}{% endif %}
{% if g_high > 0 %}{% endif %}
{% if g_med > 0 %}{% endif %}
{% if g_low > 0 %}{% endif %}
{{ g_crit }}C · {{ g_high }}H · {{ g_med }}M · {{ g_low }}L{{ rows | length }}fixes
{% endif %}
{# ---- No portable candidates at all ---- #}
{% if not primary_rows and not reverse_rows %}
No portable fix candidates found between {{ left_label }} and {{ right_label }}.
{% endif %}
{# ---- "Open on both" + "Suppressed divergence" disclosure ---- #}
{% if both_n > 0 or supp_n > 0 %}
Open on both sides{{ both_n }} findings need action in both scopes — open on both, so nothing to port.{{ both_n }}shared
Both sides carry these findings, so a comparison cannot make them portable. Surfaced to account for the full finding population.
{% if supp_n > 0 %}
Suppressed divergence ({{ supp_n }}): suppressed on one side, open on the other — a triage judgment, not a code fix. Reconciled in Triage Status §04.