File size: 2,662 Bytes
adb91eb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import { useI18n } from '../i18n'
import type { OrchestrateResult } from '../api/ai'

type StepRole = 'advisory' | 'authoritative' | 'user' | 'notExecuted' | 'executed'

interface Step {
  key: string
  labelKey:
    | 'trace.context' | 'trace.risk' | 'trace.policy' | 'trace.auth'
    | 'trace.rail' | 'trace.userAction' | 'trace.paymentCore'
  role: StepRole
  summary: string
}

function roleBadge(role: StepRole): { key: string; tone: string } {
  switch (role) {
    case 'advisory': return { key: 'shadow.advisory', tone: 'tone-info' }
    case 'authoritative': return { key: 'authoritative.badge', tone: 'tone-ok' }
    case 'user': return { key: 'trace.userRequired', tone: 'tone-warn' }
    case 'executed': return { key: 'trace.executed', tone: 'tone-ok' }
    default: return { key: 'trace.notExecuted', tone: 'tone-bad' }
  }
}

/** Safe, read-only sequence view. Never renders capabilities, signatures, keys or envelopes. */
export function AgentTrace({ result }: { result: OrchestrateResult }) {
  const { t } = useI18n()
  const executed = result.agent_state === 'initiated'
  const steps: Step[] = [
    { key: 'context', labelKey: 'trace.context', role: 'advisory',
      summary: result.risk ? '' : t('shadow.noChange') },
    { key: 'risk', labelKey: 'trace.risk', role: 'advisory',
      summary: result.risk ? result.risk.band : 'model_unavailable' },
    { key: 'policy', labelKey: 'trace.policy', role: 'authoritative', summary: result.decision },
    { key: 'auth', labelKey: 'trace.auth', role: 'advisory',
      summary: result.auth_recommendation?.recommended_method ?? '' },
    { key: 'rail', labelKey: 'trace.rail', role: 'advisory',
      summary: (result.rails?.ranked ?? []).join(', ') },
    { key: 'user', labelKey: 'trace.userAction', role: 'user',
      summary: result.decision === 'step_up' ? t('trace.userRequired') : '' },
    { key: 'core', labelKey: 'trace.paymentCore',
      role: executed ? 'executed' : 'notExecuted',
      summary: result.payment?.id ?? t('trace.notExecuted') },
  ]
  return (
    <div className="agent-trace">
      <h3>{t('trace.title')}</h3>
      <ol className="trace-list">
        {steps.map((s) => {
          const b = roleBadge(s.role)
          return (
            <li key={s.key} className={`trace-step role-${s.role}`}>
              <div className="trace-head">
                <span className="trace-label">{t(s.labelKey)}</span>
                <span className={`badge ${b.tone}`}>{t(b.key as never)}</span>
              </div>
              {s.summary && <span className="trace-summary">{s.summary}</span>}
            </li>
          )
        })}
      </ol>
    </div>
  )
}