{% extends "base.html" %} {% block title %}Conversation {{ conversation_id[:12] }} - MoralStack{% endblock %} {% block content %}
Export Markdown {% if timeline.run_id %} Back to Run {% endif %}
{% set ov = timeline.overview or {} %} {% set ov_max_risk = ov.get('max_risk_score') %}
Total turns {{ timeline.requests | length }}
Final actions {% if ov.get('final_actions') %} {% for action, count in ov.get('final_actions').items() %} {{ action }}: {{ count }} {% endfor %} {% else %}—{% endif %}
Max risk score {% if ov_max_risk is not none %}{{ "%.3f" | format(ov_max_risk) }}{% else %}—{% endif %}
Last posture {% if ov.get('last_posture') %}{{ ov.get('last_posture') }}{% else %}—{% endif %}
Ledger hits / misses {% set lh = ov.get('ledger_hits') or 0 %} {% set lm = ov.get('ledger_misses') or 0 %} {{ lh }} / {{ lm }} {% if lh == 0 and lm == 0 %} (ledger not configured){% endif %}
Session store hits / misses {{ ov.get('session_store_hits') or 0 }} / {{ ov.get('session_store_misses') or 0 }}
Any turn cached {% if ov.get('any_turn_cached') %}yes{% else %}no{% endif %}
First / last turn {{ ov.get('first_created_at') | fmtdate }} → {{ ov.get('last_created_at') | fmtdate }}
{# Step 14.6 — Conversation strip: horizontal visual summary of turns #} {% if timeline.requests %}

Conversation strip

Each rectangle is one turn. Height encodes risk score (taller = higher risk). Colour encodes final_action (green = NORMAL_COMPLETE, yellow = SAFE_COMPLETE, red = REFUSE). Orange border = ESCALATED posture. ⚡ = cached from a previous turn. Hover for details, click to open the request.

{% for r in timeline.requests %} {% set meta = r.meta_json__parsed or {} %} {% set state = r.state or {} %} {% set summary = state.get('state_summary_json__parsed') or {} %} {% set risk_score = meta.get('risk_score') %} {% set posture = state.get('posture') or summary.get('last_governance_posture') or meta.get('governance_posture') %} {% set action = meta.get('final_action') or 'unknown' %} {% set was_cached = state.get('was_cached') or meta.get('was_cached') %} {% set cached_from = state.get('cached_from_turn') %} {% set bar_height = (24 + ((risk_score or 0.0) * 56)) | int %} {% set action_class = 'conv-strip-cell-' + (action | lower) %} {% set escalated = (posture == 'ESCALATED') %}
T{{ r.turn_index }} {% if was_cached %} {% endif %} {% if r.run_id and r.request_id %} {% endif %}
{% endfor %}
{% for r in timeline.requests %} {% set state = r.state or {} %} {% set cached_from = state.get('cached_from_turn') %} {% if cached_from is not none and r.turn_index is not none %} {% endif %} {% endfor %}
{% endif %}

Posture timeline

{% if timeline.requests %}
{% for r in timeline.requests %} {% set meta = r.meta_json__parsed or {} %} {% set state = r.state or {} %} {% set summary = state.get('state_summary_json__parsed') or {} %} {% set risk_score = meta.get('risk_score') %} {% set state_cached_from = state.get('cached_from_turn') %} {% endfor %}
Turn Request Action Risk Posture (out) Cached Created Actions
{{ r.turn_index if r.turn_index is not none else '—' }} {% if r.run_id and r.request_id %} {{ r.request_id[:12] }} {% else %}{{ (r.request_id or '')[:12] }}{% endif %} {{ meta.get('final_action') or '—' }} {% if risk_score is not none %} {{ "%.3f" | format(risk_score) }} {% else %}—{% endif %} {% set posture = state.get('posture') or summary.get('last_governance_posture') or meta.get('governance_posture') %} {% if posture %}{{ posture }}{% else %}—{% endif %} {% if state.get('was_cached') or meta.get('was_cached') %} cached {% if state_cached_from is not none %} from turn {{ state_cached_from }} {% endif %} {% else %}—{% endif %} {{ r.created_at | fmtdate }} {% if r.run_id and r.request_id %} View {% endif %}
{% else %}

No turns recorded for this conversation.

{% endif %}

Per-turn detail

{% for r in timeline.requests %} {% set meta = r.meta_json__parsed or {} %} {% set state = r.state or {} %} {% set summary = state.get('state_summary_json__parsed') or {} %} {% set state_in_full = state.get('state_in_json__parsed') or {} %} {% set state_out_full = state.get('state_out_json__parsed') or {} %} {% set proxy = r.proxy_event or {} %} {% set proxy_meta = proxy.get('metadata_json__parsed') or {} %} {% set proxy_headers = proxy.get('headers_json__parsed') or {} %} {% set risk_score = meta.get('risk_score') %} {% set meta_cached_from = meta.get('cached_from_turn') %} {% set state_cached_from = state.get('cached_from_turn') %}

Turn {{ r.turn_index if r.turn_index is not none else '?' }} · {{ (r.request_id or '')[:12] }}

{{ meta.get('final_action') or '—' }} {% if risk_score is not none %} risk {{ "%.3f" | format(risk_score) }} {% endif %} {% set posture = state.get('posture') or summary.get('last_governance_posture') or meta.get('governance_posture') %} {% if posture %}{{ posture }}{% endif %} {% if state.get('was_cached') or meta.get('was_cached') %} cached {% endif %}

Prompt

{{ (r.prompt or '—') }}

Final response

{{ (r.final_response or '—') }}

Governance decision

{% if meta_cached_from is not none or state_cached_from is not none %} {% endif %}
Final action{{ meta.get('final_action') or '—' }}
Risk score{% if risk_score is not none %}{{ "%.4f" | format(risk_score) }}{% else %}—{% endif %}
Path{{ meta.get('path') or '—' }}
Domain overlay{{ meta.get('domain_overlay') or '—' }}
Reason codes{{ (meta.get('reason_codes') or []) | join(', ') or '—' }}
Triggered principles{{ (meta.get('triggered_principles') or []) | join(', ') or '—' }}
Posture (final){{ meta.get('governance_posture') or summary.get('last_governance_posture') or '—' }}
Was cached{% if meta.get('was_cached') or state.get('was_cached') %}yes{% else %}no{% endif %}
Cached from turn{{ meta_cached_from if meta_cached_from is not none else state_cached_from }}
{% if meta.get('decision_reason') %}

Decision rationale

{{ meta.get('decision_reason') }}
{% endif %} {% if meta.get('decision_explanation') %}
Decision explanation (structured)
{{ meta.get('decision_explanation') | tojson(indent=2) }}
{% endif %} {% if meta %}
Raw governance meta (JSON)
{{ meta | tojson(indent=2) }}
{% endif %}

Conversation state

{% if state.get('refresh_reason') %} {% endif %}
Active domain{{ summary.get('active_domain') or '—' }}
Active overlay{{ summary.get('active_overlay') or '—' }}
Hard constraints triggered{{ (summary.get('last_hard_constraints_triggered') or []) | join(', ') or '—' }}
Principle shortlist{{ (summary.get('principle_shortlist') or []) | join(', ') or '—' }}
Refresh required{% if state.get('refresh_required') %}yes{% else %}no{% endif %}
Refresh reason{{ state.get('refresh_reason') }}
{% if state_in_full or state_out_full %}
Full state snapshots (JSON) {% if state_in_full %}
State (in)
{{ state_in_full | tojson(indent=2) }}
{% endif %} {% if state_out_full %}
State (out)
{{ state_out_full | tojson(indent=2) }}
{% endif %}
{% endif %}
{% if r.ledger_events %}

Ledger activity

{% for ev in r.ledger_events %} {% endfor %}
OpOutcomeSimilarityReasonFrom turnPostureFinal action
{{ ev.operation or '—' }} {{ ev.outcome or '—' }} {% if ev.similarity is not none %}{{ "%.3f" | format(ev.similarity) }}{% else %}—{% endif %} {{ ev.reason or '—' }} {{ ev.from_turn if ev.from_turn is not none else '—' }} {{ ev.posture or '—' }} {{ ev.final_action or '—' }}
{% endif %} {% if r.session_events %}

Session store activity

{% for ev in r.session_events %} {% set payload = ev.get('payload_json__parsed') or {} %} {% set state_sum = ev.get('state_summary_json__parsed') or {} %} {% set ttl_age = payload.get('ttl_age_ms') %} {% set evicted_ids = payload.get('evicted_ids') %} {% endfor %}
OpOutcomeDetail
{{ ev.operation or '—' }} {{ ev.outcome or '—' }} {% if state_sum.get('last_governance_posture') %}posture={{ state_sum.get('last_governance_posture') }}{% endif %} {% if ttl_age is not none %} · ttl_age={{ ttl_age }}ms{% endif %} {% if evicted_ids %} · evicted={{ evicted_ids | length }}{% endif %}
{% endif %} {% if proxy %}

Proxy finalization

State provided{% if proxy.state_provided %}yes{% else %}no{% endif %}
State updated{% if proxy.state_updated %}yes{% else %}no{% endif %}
Posture in → out{{ proxy.posture_in or '—' }} → {{ proxy.posture_out or '—' }}
Was cached{% if proxy.was_cached %}yes{% else %}no{% endif %}
Final response length{{ proxy.final_response_length if proxy.final_response_length is not none else '—' }}
{% if proxy_headers %}
X-MoralStack headers
{{ proxy_headers | tojson(indent=2) }}
{% endif %} {% endif %}
{% endfor %} {% endblock %}