/* global window, React */
// ============================================================
// Agent Policy — how the agent decides, and where TP draws the line.
// Sections (top to bottom):
// 1. Header + KPI strip (4 KPIs)
// 2. Governance score card + autonomy heat map (novel)
// 3. Rules library — filter by family, list of cards w/ autonomy band toggle
// 4. Audit log — compact table of recent agent decisions
// ============================================================
const { useState: usePolState, useMemo: usePolMemo } = React;
const BANDS = [
{ id: 'auto', label: 'Auto', short: 'AUTO', desc: 'Agent acts. No human in loop.' },
{ id: 'notify', label: 'Notify', short: 'NOTIFY', desc: 'Agent acts. Pings TP after.' },
{ id: 'approval', label: 'Approval', short: 'APPR', desc: 'Agent stages. TP confirms before action.' },
{ id: 'human', label: 'Human-only', short: 'HUMAN', desc: 'TP decides. Agent never acts.' },
];
const BAND_BY_ID = Object.fromEntries(BANDS.map(b => [b.id, b]));
function PolicyKpi({ label, value, sub, kind }) {
return (
{label}
{value}
{sub &&
{sub}
}
);
}
// ---------- Governance score card (novel) ----------
function GovernanceScore({ governance }) {
const r = 56; // radius
const c = 2 * Math.PI * r;
const pct = governance.score / 100;
const dash = c * pct;
const tier = governance.score >= 80 ? 'green' : governance.score >= 60 ? 'amber' : 'red';
const tierLabel = tier === 'green' ? 'Healthy' : tier === 'amber' ? 'Watch' : 'Risk';
return (
GOVERNANCE SCORE
How well does policy balance autonomy & control?
{governance.score}
{tierLabel}
{governance.bands.map(b => {
const tier = b.value >= 80 ? 'green' : b.value >= 60 ? 'amber' : 'red';
return (
{b.label}
{b.value}
{b.sub}
);
})}
);
}
// ---------- Autonomy heat map (novel) ----------
function AutonomyHeatmap({ families, rules, onCellClick, selectedCell }) {
// grid[family.id][band.id] = { count, savings, triggers }
const grid = {};
families.forEach(f => {
grid[f.id] = {};
BANDS.forEach(b => { grid[f.id][b.id] = { count: 0, savings: 0, triggers: 0 }; });
});
rules.forEach(r => {
if (!grid[r.family] || !grid[r.family][r.autonomy]) return;
const cell = grid[r.family][r.autonomy];
cell.count += 1;
cell.savings += r.dollarsSaved || 0;
cell.triggers += r.triggers7d || 0;
});
// Find the max cell value for color scaling
const maxTriggers = Math.max(1, ...families.flatMap(f => BANDS.map(b => grid[f.id][b.id].triggers)));
return (
AUTONOMY HEATMAP
Where the agent acts · where it asks
Cell shows rules · 7d triggers. Click to filter.
{BANDS.map(b => (
))}
{families.map(f => (
{BANDS.map(b => {
const cell = grid[f.id][b.id];
const intensity = cell.triggers / maxTriggers;
const empty = cell.count === 0;
const isSelected = selectedCell && selectedCell.family === f.id && selectedCell.band === b.id;
return (
);
})}
))}
);
}
// ---------- Rule card with autonomy band selector ----------
function BandSelector({ value, onChange }) {
return (
{BANDS.map(b => (
))}
);
}
function RuleCard({ rule, family, onToggle, onAutonomy, onView }) {
const band = BAND_BY_ID[rule.autonomy];
return (
{family.icon} {family.label}
·
{rule.id.toUpperCase()}
{!rule.active &&
PAUSED}
onAutonomy(rule.id, b)} />
{rule.name}
WHEN
{rule.when}
THEN
{rule.then}
Triggered 7d
{rule.triggers7d}×
$ saved
${rule.dollarsSaved.toLocaleString()}
Avg latency
{rule.avgLatencyMs === 0 ? '—' :
rule.avgLatencyMs < 1000 ? `${rule.avgLatencyMs}ms` :
`${(rule.avgLatencyMs / 1000).toFixed(1)}s`}
Override rate
20 ? 'rule__stat-val--red' : ''}`}>
{rule.overrideRate}%
onToggle(rule.id)} title={rule.active ? 'Pause rule' : 'Activate rule'}>
);
}
// ---------- Audit log table ----------
function AuditLog({ audit, rules, onShowToast }) {
const ruleById = Object.fromEntries(rules.map(r => [r.id, r]));
const [filter, setFilter] = usePolState('all');
const filters = [
{ id: 'all', label: 'All' },
{ id: 'auto', label: 'Agent acted' },
{ id: 'tp-decided', label: 'TP decided' },
{ id: 'tp-override', label: 'TP overrode' },
{ id: 'waiting', label: 'Awaiting TP' },
];
const filtered = audit.filter(a => {
if (filter === 'all') return true;
if (filter === 'auto') return /^auto/.test(a.outcome);
if (filter === 'tp-decided') return a.outcome === 'TP decided';
if (filter === 'tp-override') return a.outcome === 'TP overrode' || a.outcome === 'TP approved';
if (filter === 'waiting') return /awaiting|notified/.test(a.outcome);
return true;
});
const outcomeKind = (o) => {
if (o.startsWith('auto')) return 'green';
if (o === 'TP overrode') return 'red';
if (o === 'TP approved') return 'blue';
if (o === 'TP decided') return 'amber';
if (o.includes('awaiting')) return 'amber';
if (o === 'notified') return 'blue';
return 'grey';
};
return (
Agent decision audit · last {audit.length}
Every decision the agent made, with outcome and override.
{filters.map(f => (
))}
Time
Trip
Rule
Outcome
Action taken
Latency
Override by
{filtered.map(a => {
const rule = ruleById[a.ruleId];
const kind = outcomeKind(a.outcome);
return (
{a.ts}
{a.tripId}
{rule ? rule.name : a.ruleId}
{a.ruleId.toUpperCase()}
{a.outcome}
{a.action}
{a.latencyMs === 0 ? '—' :
a.latencyMs < 1000 ? `${a.latencyMs}ms` :
`${(a.latencyMs / 1000).toFixed(1)}s`}
{a.overrideBy ? (
<>
{a.overrideBy.split(' ').map(p => p[0]).join('')}
{a.overrideBy}
>
) : —}
);
})}
{filtered.length === 0 && (
No entries match this filter.
)}
);
}
// ---------- New Rule modal ----------
function NewRuleModal({ families, onCancel, onCreate }) {
const [family, setFamily] = usePolState(families[0].id);
const [name, setName] = usePolState('');
const [whenText, setWhen] = usePolState('');
const [thenText, setThen] = usePolState('');
const [autonomy, setAuto] = usePolState('notify');
const [active, setActive] = usePolState(true);
const canCreate = name.trim().length > 2 && whenText.trim().length > 4 && thenText.trim().length > 4;
return (
e.stopPropagation()}>
New policy rule
Add a rule to the library
Rules are evaluated by the agent on every wave. New rules start in draft until reviewed by Carrier Manager.
);
}
// ---------- Main view ----------
function AgentPolicy() {
const P = window.POLICY;
const [rules, setRules] = usePolState(P.rules);
const [familyFilter, setFamilyFilter] = usePolState('all');
const [bandFilter, setBandFilter] = usePolState('all');
const [showPaused, setShowPaused] = usePolState(true);
const [toast, setToast] = usePolState(null);
const [newRuleOpen, setNewRuleOpen] = usePolState(false);
const showToast = (msg) => {
setToast(msg);
setTimeout(() => setToast(null), 2800);
};
const onToggleRule = (id) => {
setRules(prev => prev.map(r => r.id === id ? { ...r, active: !r.active } : r));
const r = rules.find(x => x.id === id);
if (r) showToast(`Rule "${r.name}" · ${r.active ? 'paused' : 'activated'}`);
};
const onChangeAutonomy = (id, band) => {
setRules(prev => prev.map(r => r.id === id ? { ...r, autonomy: band } : r));
const r = rules.find(x => x.id === id);
if (r && r.autonomy !== band) {
showToast(`Rule "${r.name}" · moved to ${BAND_BY_ID[band].label}`);
}
};
const onHeatmapClick = (family, band) => {
setFamilyFilter(family);
setBandFilter(band);
};
const filteredRules = usePolMemo(() => {
return rules.filter(r => {
if (!showPaused && !r.active) return false;
if (familyFilter !== 'all' && r.family !== familyFilter) return false;
if (bandFilter !== 'all' && r.autonomy !== bandFilter) return false;
return true;
});
}, [rules, familyFilter, bandFilter, showPaused]);
const totals = usePolMemo(() => {
const active = rules.filter(r => r.active).length;
const paused = rules.filter(r => !r.active).length;
const byBand = Object.fromEntries(BANDS.map(b => [b.id, 0]));
rules.forEach(r => { if (r.active) byBand[r.autonomy]++; });
const autoPct = Math.round((byBand.auto / active) * 100);
return { active, paused, byBand, autoPct };
}, [rules]);
const onCreateRule = (data) => {
// generate next id
const maxN = Math.max(...rules.map(r => parseInt(r.id.replace('r-', ''), 10)).filter(n => !isNaN(n)));
const id = `r-${String(maxN + 1).padStart(2, '0')}`;
const newRule = {
...data,
id,
triggers7d: 0, dollarsSaved: 0, avgLatencyMs: 0, overrideRate: 0,
};
setRules(prev => [newRule, ...prev]);
setNewRuleOpen(false);
showToast(`✓ Rule "${data.name}" created · ${data.active ? 'active' : 'draft'} · ${BAND_BY_ID[data.autonomy].label}`);
};
const hasFilter = familyFilter !== 'all' || bandFilter !== 'all';
return (
{/* Header */}
AGENT POLICY · TP
How the agent decides — and where you draw the line.
{totals.active} rules active · {totals.paused} paused ·
{totals.byBand.human} always-human ·
policy version v3.7 · published 2d ago
{/* KPI strip */}
{/* Governance + Heatmap row */}
{/* Rules library */}
Policy rules library
{filteredRules.length} of {rules.length} rules ·
click a rule to expand · drag autonomy band to change who decides
{hasFilter && (
)}
{/* Family + Band filter chips */}
FAMILY
{P.families.map(f => (
))}
BAND
{BANDS.map(b => (
))}
{filteredRules.map(rule => {
const family = P.families.find(f => f.id === rule.family);
return (
showToast(`Opening trigger list for ${id.toUpperCase()} (last 7d)`)}
/>
);
})}
{filteredRules.length === 0 && (
No rules match your filters.
)}
{/* Audit log */}
{toast &&
{toast}
}
{newRuleOpen && (
setNewRuleOpen(false)}
onCreate={onCreateRule}
/>
)}
);
}
Object.assign(window, { AgentPolicy });