Pure, total presenter for Cairnloop.Governance.ToolProposal structs.
Mirrors ReviewTaskPresenter and GapCandidatePresenter exactly:
- Total functions with safe fallbacks — no crashes on unexpected input
- Pattern-match on struct and bare atoms
- Returns strings and atoms only — never markup, never raw Elixir terms
- Never re-reads live config at render time (snapshots are truth for trust fields)
D-22 masking choke point: input_rows/1 is the ONLY place raw input_snapshot
values are converted to display rows; it allowlists known scalar fields and
applies the "Unsupported value" posture to nested/unknown/sensitive values.
D-14: reason_label/1 humanizes all reason shapes (including {:missing_scopes, _}
tuples and atoms) — never passes inspect/1 output to operators.
D-13 / brand §7.5: risk_tier_tone/1 returns an atom (:info/:warning/:danger)
that LiveView maps to color — always paired with text, never state-by-color-alone.
Summary
Functions
Human label for an approval mode atom.
Future-tense operator copy describing the approval gate — D-12 honesty seam.
Present-tense operator copy for an active approval record (D15-16 real copy).
Returns operator copy explaining why a proposal was blocked, or nil/empty for non-blocked.
Human-readable relative timestamp for an event datetime.
Returns a human-readable line for a ToolActionEvent.
Converts an input_snapshot map to a list of {label, value} display rows.
Returns a calm one-sentence explanation from the policy_snapshot.
Humanizes a reason value — never passes raw inspect output to operators (D-14).
Human label for a risk tier atom.
Semantic tone for the risk tier — returns an atom only (brand §7.5 / D-13). LiveView MUST pair this with a text label — never rely on color alone.
Human summary of required scopes from a scope_snapshot map.
Groups proposal status into display buckets.
Human label for the status group.
Human label for a proposal status atom or ToolProposal struct.
Status meaning — one calm sentence explaining what this state means for operators.
Returns a map of de-emphasized trace fields for a proposal. Safe for display; all values are strings. Shows a short idempotency key suffix.
Functions
Human label for an approval mode atom.
Future-tense operator copy describing the approval gate — D-12 honesty seam.
Returns:
Present-tense operator copy for an active approval record (D15-16 real copy).
Takes a %ToolApproval{} struct or a map with a :status key (and optionally :reason).
Returns a calm, present-tense string for operators — or nil for unknown states.
Replaces the future-tense approval_outlook/1 honesty seam when an active approval exists.
Never re-reads live config (brand §5.3/§5.6 reason-forward, no raw terms).
Returns operator copy explaining why a proposal was blocked, or nil/empty for non-blocked.
Human-readable relative timestamp for an event datetime.
Returns a human-readable line for a ToolActionEvent.
Catch-all → "Workflow updated" for any unrecognized event_type (D-24).
Converts an input_snapshot map to a list of {label, value} display rows.
MASKING RULES (D-22):
- Iterates ALL keys (both atom and string-keyed JSONB snapshots are supported)
- Scalar values (string, number, boolean) are humanized and displayed
- Lists of scalars are joined with ", "
- Nested maps, tuples, and other complex values → "Unsupported value" sentinel
- Sensitive field names (containing "password", "token", "secret", "key") → "••••••"
- Never dumps raw nested structures to operators
Returns a calm one-sentence explanation from the policy_snapshot.
Uses dual-key lookup (atom + string) to survive the JSONB string-key round-trip (mirrors ReviewTaskPresenter.metadata_value/2). Raw map is NOT returned — only a humanized sentence. (D-14 / brand §5.6)
Humanizes a reason value — never passes raw inspect output to operators (D-14).
Handles:
- nil → nil
- → "Missing scopes: scope1, scope2"
- known atom → human-readable label from @reason_labels
- unknown atom → humanize via capitalize-and-replace
- string → pass through
- any tuple → generic human fallback (no raw inspect)
Human label for a risk tier atom.
Semantic tone for the risk tier — returns an atom only (brand §7.5 / D-13). LiveView MUST pair this with a text label — never rely on color alone.
:info — :read_only (low risk, informational) :warning — :low_write (moderate risk) :danger — :high_write, :destructive (high risk)
Human summary of required scopes from a scope_snapshot map.
Groups proposal status into display buckets.
- :awaiting — :proposed, :needs_input (action needed but not yet blocked)
- :blocked — :scope_invalid, :policy_denied (terminal blocked state in Phase 14)
- :active — declared for future Phase 15/16 running/approved states (unreachable now)
- :done — declared for future completed/executed states (unreachable now)
Human label for the status group.
Human label for a proposal status atom or ToolProposal struct.
Status meaning — one calm sentence explaining what this state means for operators.
Returns a map of de-emphasized trace fields for a proposal. Safe for display; all values are strings. Shows a short idempotency key suffix.