{% extends "base.html" %} {% block title %}Conversation {{ conversation_id[:12] }} - MoralStack{% endblock %} {% block content %} {#- Colour a final_action badge by the strip's legend (green=NORMAL_COMPLETE, amber=SAFE_COMPLETE, red=REFUSE). A pipeline-failure turn persists a coerced NORMAL_COMPLETE placeholder, so it stays neutral (never green) — the adjacent "not a governed outcome" text carries the meaning. -#} {% macro action_badge(action, is_failure=False) -%} {%- if not action -%} — {%- elif is_failure -%} {{ action }} {%- else -%} {{ action }} {%- endif -%} {%- endmacro %} {% if timeline.turn_index_collision %}

⚠ turn_index repeats within this conversation{% if timeline.turn_index_collision_multi_run %} — at least one repeat spans separate runs (independent runs sharing this conversation id, not one continuous run){% else %} — the repeat is within a single run (turn_index did not advance across turns){% endif %}. See the #pos/size annotations below for exact grouping.

{% endif %}
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() %} {% set failure_count = timeline.pipeline_failure_action_counts.get(action) if timeline.pipeline_failure_action_counts else none %} {{ action }}: {{ count }} {% if failure_count %} — includes {{ failure_count }} pipeline failure{{ 's' if failure_count != 1 else '' }}, not a governed outcome {% endif %} {% endfor %} {% else %}—{% endif %}
Max risk score {% if ov_max_risk is not none %}{{ "%.3f" | format(ov_max_risk) }}{% else %}—{% endif %} {% if timeline.max_risk_is_fail_closed %} fail-closed default (pipeline failure), not an assessed score{% if timeline.max_assessed_risk is not none %} — max assessed: {{ "%.3f" | format(timeline.max_assessed_risk) }}{% endif %} {% endif %}
Last posture {% if ov.get('last_posture') %}{{ ov.get('last_posture') }}{% else %}—{% endif %} {% if timeline.last_posture_is_from_pipeline_failure %} — from a turn that ended in a pipeline failure, not a governed outcome {% 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 }}
{% if timeline.pipeline_failure_count %}

⚠ {{ timeline.pipeline_failure_count }} turn{{ 's' if timeline.pipeline_failure_count != 1 else '' }} ended in a pipeline failure — its final_action is not a governed outcome.

{% endif %} {% set token_usage_title = 'Token usage by model (conversation)' %} {% include "_token_usage.html" %} {# Conversation-level spine: replaces the horizontal strip. One node per turn (input -> decision -> outcome), a first-turn node for contract/history context, and a terminal node folding the failure-aware aggregates. Overview only — per-turn internals stay on the request page (linked from each node). Order follows seq_pos (turn_index, created_at); parent_request_id is never used for ordering (100% self-referential — see docs/CODEBASE_FACTS.md). #} {% if timeline.requests %}

Conversation spine

One node per turn: what it saw (input), what governance decided (decision), what was delivered (outcome). Risk is the exact decision-trace value plus a proportional bar (not height — a deliberate substitution, see .claude/ui-loop/DECISIONS.md). Order follows creation order (turn_index, created_at); a dashed divider marks turns sharing the same turn_index, where order is not established — never a causal claim.

{% set first = timeline.requests[0] %} {% set first_anchor = first.spine_node.input.anchor if first.spine_node else none %}
Conversation start
{% if first_anchor %}
{% if first_anchor.developer_contract_present %} developer contract present {% endif %} {% if first_anchor.conversation_chip %} {{ first_anchor.conversation_chip.label | safe }} {% endif %}
{% endif %}
{% set ns = namespace(prev_posture=none) %} {% for r in timeline.requests %} {% set node = r.spine_node %}
{% if r.turn_index_collision %} same turn_index — order not established {% elif node and node.outcome.cached_from_turn is not none %} reused decision from turn {{ node.outcome.cached_from_turn }} {% elif node and ns.prev_posture and node.outcome.posture and ns.prev_posture != node.outcome.posture %} {{ ns.prev_posture }} → {{ node.outcome.posture }} {% endif %}
Turn {{ r.turn_index if r.turn_index is not none else '?' }}{% if r.turn_index_collision %} · #{{ r.turn_index_collision_pos }}/{{ r.turn_index_collision_group_size }}{% endif %} {{ action_badge(node.outcome.final_action if node else none, r.get('pipeline_failure')) }} {% if r.get('pipeline_failure') %} not a governed outcome {% endif %}
Input {% if node %} {% if node.input.anchor.developer_contract_present %} developer contract {% endif %} {% if node.input.anchor.conversation_chip %} {{ node.input.anchor.conversation_chip.label | safe }} {% endif %} {% set prompt_preview = node.input.prompt_preview %} {% if prompt_preview %}

“{{ (prompt_preview[:140] + '…') if prompt_preview | length > 140 else prompt_preview }}”

{% endif %} {% endif %}
Decision {% if not r.get('pipeline_failure') and node and node.decision %} {% set d = node.decision %} {% if d.risk_score is not none %}
{{ "%.2f" | format(d.risk_score) }}
{% endif %} {% if d.winning_rule %}

{{ d.winning_rule }}

{% endif %} {% if d.hard_violation_codes %}

{% for code in d.hard_violation_codes %} {{ code }} {% endfor %}

{% endif %} {% set causal = none %} {% if d.final_action == 'NORMAL_COMPLETE' %}{% set causal = d.why_not_normal_complete %} {% elif d.final_action == 'SAFE_COMPLETE' %}{% set causal = d.why_not_safe_complete %} {% elif d.final_action == 'REFUSE' %}{% set causal = d.why_not_refuse %} {% endif %} {% if causal %}

{{ causal }}

{% endif %} {% elif not r.get('pipeline_failure') %} no risk claim (no FINAL decision row for this turn) {% endif %}
Outcome {% if node %} {% set response_preview = node.outcome.response_preview %} {% if response_preview %}

“{{ (response_preview[:140] + '…') if response_preview | length > 140 else response_preview }}”

{% endif %} {% if node.outcome.posture %} {{ node.outcome.posture }} {% endif %} {% if node.outcome.was_cached %} ⚡ {% endif %} {% endif %}
{% if r.run_id and r.request_id %} {% endif %}
{% set ns.prev_posture = node.outcome.posture if node else ns.prev_posture %} {% endfor %}
Conversation end
Last posture {% if ov.get('last_posture') %}{{ ov.get('last_posture') }}{% else %}—{% endif %} {% if timeline.last_posture_is_from_pipeline_failure %} — from a turn that ended in a pipeline failure, not a governed outcome {% endif %}
Final actions {% if ov.get('final_actions') %} {% for action, count in ov.get('final_actions').items() %} {% set failure_count = timeline.pipeline_failure_action_counts.get(action) if timeline.pipeline_failure_action_counts else none %} {{ action }}: {{ count }} {% if failure_count %} — includes {{ failure_count }} pipeline failure{{ 's' if failure_count != 1 else '' }}, not a governed outcome {% endif %} {% endfor %} {% else %}—{% endif %}
Max risk score {% if ov_max_risk is not none %}{{ "%.3f" | format(ov_max_risk) }}{% else %}—{% endif %} {% if timeline.max_risk_is_fail_closed %} fail-closed default (pipeline failure), not an assessed score{% if timeline.max_assessed_risk is not none %} — max assessed: {{ "%.3f" | format(timeline.max_assessed_risk) }}{% endif %} {% endif %}
{% if timeline.pipeline_failure_count %}
Pipeline failures {{ timeline.pipeline_failure_count }} turn{{ 's' if timeline.pipeline_failure_count != 1 else '' }} ended in a pipeline failure — not a governed outcome.
{% endif %}
{% 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.turn_index_collision %}
#{{ r.turn_index_collision_pos }}/{{ r.turn_index_collision_group_size }}{% if r.turn_index_collision_multi_run %} — separate runs share this turn_index{% else %} — same run, turn_index did not advance{% endif %} {% endif %}
{% if r.run_id and r.request_id %} {{ r.request_id[:12] }} {% else %}{{ (r.request_id or '')[:12] }}{% endif %} {{ action_badge(meta.get('final_action'), r.get('pipeline_failure')) }} {% if r.get('pipeline_failure') %}
not a governed outcome {% endif %}
{% if risk_score is not none %} {{ "%.3f" | format(risk_score) }} {% if r.get('pipeline_failure') %}
fail-closed default{% if r.get('last_assessed_risk') is not none %} (last assessed {{ "%.3f" | format(r.get('last_assessed_risk')) }}){% endif %} {% endif %} {% 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] }}

{% if r.turn_index_collision %}

Shares turn_index {{ r.turn_index }} with {{ r.turn_index_collision_group_size - 1 }} other request{{ 's' if r.turn_index_collision_group_size != 2 else '' }} in this conversation ({{ 'separate runs' if r.turn_index_collision_multi_run else 'same run' }}). Position {{ r.turn_index_collision_pos }} of {{ r.turn_index_collision_group_size }} by creation order (row {{ r.seq_pos }} of {{ timeline.requests | length }} overall).

{% endif %}
{{ action_badge(meta.get('final_action'), r.get('pipeline_failure')) }} {% if r.get('pipeline_failure') %} not a governed outcome {% endif %} {% if risk_score is not none %} risk {{ "%.3f" | format(risk_score) }} {% if r.get('pipeline_failure') %} fail-closed default{% if r.get('last_assessed_risk') is not none %} (last assessed {{ "%.3f" | format(r.get('last_assessed_risk')) }}){% endif %} {% endif %} {% 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{{ action_badge(meta.get('final_action'), r.get('pipeline_failure')) }}{% if r.get('pipeline_failure') %} not a governed outcome{% endif %}
Risk score{% if risk_score is not none %}{{ "%.4f" | format(risk_score) }}{% if r.get('pipeline_failure') %} fail-closed default{% if r.get('last_assessed_risk') is not none %} (last assessed {{ "%.4f" | format(r.get('last_assessed_risk')) }}){% endif %}{% endif %}{% 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 %}