@shield-agent/kya 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.mcp.json +1 -1
- package/README.md +5 -3
- package/amp/settings.example.json +1 -1
- package/claude/claude_desktop_config.example.json +1 -1
- package/cline/cline_mcp_settings.example.json +1 -1
- package/copilot/mcp-config.example.json +1 -1
- package/dist/receipt/receipt-css.d.ts +16 -0
- package/dist/receipt/receipt-css.d.ts.map +1 -0
- package/dist/receipt/receipt-css.js +508 -0
- package/dist/receipt/receipt-css.js.map +1 -0
- package/dist/receipt/render-receipt.d.ts.map +1 -1
- package/dist/receipt/render-receipt.js +199 -426
- package/dist/receipt/render-receipt.js.map +1 -1
- package/droid/README.md +1 -1
- package/droid/mcp.example.json +1 -1
- package/gemini/settings.example.json +1 -1
- package/grok/README.md +1 -1
- package/kilo/kilo.example.json +1 -1
- package/kimi/mcp.example.json +1 -1
- package/kiro/mcp.example.json +1 -1
- package/manifest.json +1 -1
- package/mastracode/mcp.example.json +1 -1
- package/mcp.json +1 -1
- package/openai/codex.config.example.toml +1 -1
- package/opencode/opencode.example.json +1 -1
- package/package.json +1 -1
- package/qwen/settings.example.json +1 -1
- package/server.json +2 -2
|
@@ -7,6 +7,7 @@ import { productLabel, readTrail, readTrailSince } from "../trail.js";
|
|
|
7
7
|
import { loadCertifyCard, loadIdentity, loadOrrCard, loadSandboxes, loadShowbackCard, loadWiredHosts, } from "./enrich.js";
|
|
8
8
|
import { SHOWBACK_DISCLAIMER, } from "../showback/cost-per-task.js";
|
|
9
9
|
import { computeDashboard } from "./dashboard.js";
|
|
10
|
+
import { receiptCss } from "./receipt-css.js";
|
|
10
11
|
function esc(s) {
|
|
11
12
|
return stripEscapes(s)
|
|
12
13
|
.replace(/&/g, "&")
|
|
@@ -264,8 +265,8 @@ function projectMeta(project) {
|
|
|
264
265
|
function renderFeed(events, nowMs, live) {
|
|
265
266
|
if (events.length === 0) {
|
|
266
267
|
const hint = live
|
|
267
|
-
? "No events yet
|
|
268
|
-
: "No events yet
|
|
268
|
+
? "No events yet — evaluate a tool call with <code>kya wrap</code> and this page will refresh."
|
|
269
|
+
: "No events yet — evaluate a tool call with <code>kya wrap</code>, then regenerate this receipt.";
|
|
269
270
|
return `<p class="empty">${hint}</p>`;
|
|
270
271
|
}
|
|
271
272
|
const sorted = [...events].sort((a, b) => b.ts.localeCompare(a.ts));
|
|
@@ -541,39 +542,111 @@ function orrPanel(orr, nowMs) {
|
|
|
541
542
|
<p class="mute small">scorecards: ${orr.scorecards.pass} pass · ${orr.scorecards.fail} fail · ${orr.scorecards.partial} partial · ${orr.scorecards.notEvaluated} n/e${when}</p>
|
|
542
543
|
</section>`;
|
|
543
544
|
}
|
|
544
|
-
function
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
545
|
+
function usdText(usd) {
|
|
546
|
+
return usd === null ? "USD n/a" : `~$${usd.toFixed(2)}`;
|
|
547
|
+
}
|
|
548
|
+
/** KPI tile: big tabular number over a small caps label. The tone class is
|
|
549
|
+
* only applied above zero — a zero count never reads as a signal. */
|
|
550
|
+
function kpiTile(label, value, cls = "") {
|
|
551
|
+
const v = typeof value === "number" ? value : esc(value);
|
|
552
|
+
const toned = typeof value === "number" && value > 0 && cls ? ` ${cls}` : "";
|
|
553
|
+
return `<div class="kpi"><span class="kpi-num${toned}">${v}</span><span class="kpi-lab">${label}</span></div>`;
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Hero Certify panel: purpose-built rich card for the live Agent Trust
|
|
557
|
+
* Baseline. The section carries the result state (`hero-certify pass|gap|
|
|
558
|
+
* none`) for the accent border; an absent card is a fail-closed neutral
|
|
559
|
+
* state that must never read as a pass.
|
|
560
|
+
*/
|
|
561
|
+
function heroCertifyPanel(certify) {
|
|
562
|
+
if (!certify) {
|
|
563
|
+
return `<section class="panel hero-certify none" aria-label="Certify">
|
|
564
|
+
<h2>Certify — Agent Trust Baseline</h2>
|
|
565
|
+
<div class="orrline">
|
|
566
|
+
<span class="pill">not evaluated</span>
|
|
567
|
+
</div>
|
|
568
|
+
<p class="mute small">No live evaluation available — wrap tool calls and run kya certify to build the baseline.</p>
|
|
569
|
+
</section>`;
|
|
570
|
+
}
|
|
571
|
+
const state = certify.result === "pass" ? "pass" : "gap";
|
|
572
|
+
// topGaps arrives severity-ordered from the loader; cap defensively at 5.
|
|
555
573
|
const gaps = certify.topGaps.length === 0
|
|
556
574
|
? ""
|
|
557
575
|
: `<ul class="rows">
|
|
558
576
|
${certify.topGaps
|
|
559
|
-
.
|
|
577
|
+
.slice(0, 5)
|
|
578
|
+
.map((g) => ` <li><code>${esc(g.id)}</code> <span class="sev ${esc(g.severity)}">${esc(g.severity)}</span></li>`)
|
|
560
579
|
.join("\n")}
|
|
561
580
|
</ul>`;
|
|
562
|
-
// Pill tones reuse the ORR
|
|
563
|
-
return `<section class="panel" aria-label="Certify">
|
|
581
|
+
// Pill tones reuse the ORR palette: pass is green, gap is amber.
|
|
582
|
+
return `<section class="panel hero-certify ${state}" aria-label="Certify">
|
|
564
583
|
<h2>Certify — Agent Trust Baseline</h2>
|
|
565
584
|
<div class="orrline">
|
|
566
|
-
<span class="pill orr-${
|
|
585
|
+
<span class="pill orr-${state === "pass" ? "green" : "amber"}">${esc(certify.result)}</span>
|
|
586
|
+
</div>
|
|
587
|
+
<div class="kpi-row">
|
|
588
|
+
${kpiTile("Pass", certify.pass, "ok")}
|
|
589
|
+
${kpiTile("Gap", certify.gap, "warn")}
|
|
590
|
+
${kpiTile("Insufficient", certify.insufficientEvidence, "mute")}
|
|
591
|
+
${kpiTile("Attested", certify.attested, "ok")}
|
|
567
592
|
</div>
|
|
568
|
-
<div class="stats">${chips}</div>
|
|
569
593
|
${gaps}
|
|
570
594
|
<p class="mute small">live evaluation — window ${certify.windowDays}d, ${certify.trailEvents} trail events · kya certify for the full gap report + signed bundle</p>
|
|
571
595
|
</section>`;
|
|
572
596
|
}
|
|
573
|
-
|
|
574
|
-
|
|
597
|
+
/** Hero KPI cards: compact, non-interactive rollups (filters live in the
|
|
598
|
+
* Activity zone's chip bar). Numbers keep tabular-nums via .mix-num/.kpi-num. */
|
|
599
|
+
function verdictsHeroCard(c, total) {
|
|
600
|
+
// Base 100% = total events, matching dashboard.ts's verdictMix contract:
|
|
601
|
+
// verdict is a free-form string from untrusted JSONL, so an event may land
|
|
602
|
+
// in no bucket (bogus verdict) while still counting in `never` — only the
|
|
603
|
+
// raw event count keeps every bar inside the base the Overview card uses.
|
|
604
|
+
const pct = (n) => (total > 0 ? Math.round((n / total) * 100) : 0);
|
|
605
|
+
const mixRow = (label, n, fill) => `<div class="mix-row"><span class="mix-lab">${label}</span><span class="mix-bar"><span class="mix-fill ${fill}" style="width:${pct(n)}%"></span></span><span class="mix-num">${n}</span></div>`;
|
|
606
|
+
return `<section class="panel" aria-label="Verdicts">
|
|
607
|
+
<h2>Verdicts</h2>
|
|
608
|
+
<div class="mix">
|
|
609
|
+
${mixRow("Allow", c.allow, "ok")}
|
|
610
|
+
${mixRow("Deny", c.deny, "bad")}
|
|
611
|
+
${mixRow("Review", c.require, "warn")}
|
|
612
|
+
${mixRow("Never", c.never, "bad")}
|
|
613
|
+
</div>
|
|
614
|
+
</section>`;
|
|
615
|
+
}
|
|
616
|
+
const SPARK_BARS = 7;
|
|
617
|
+
const SPARK_H = 24;
|
|
618
|
+
/**
|
|
619
|
+
* Inline 7-bar sparkline (no JS): the last 7 activity buckets, left-padded
|
|
620
|
+
* with zeros, heights normalized so the max bucket is full height. Empty
|
|
621
|
+
* input renders a flat baseline — heights are guarded, never NaN/Infinity.
|
|
622
|
+
*/
|
|
623
|
+
function activitySparkline(activity) {
|
|
624
|
+
const counts = activity.buckets.slice(-SPARK_BARS).map((b) => b.count);
|
|
625
|
+
while (counts.length < SPARK_BARS)
|
|
626
|
+
counts.unshift(0);
|
|
627
|
+
const max = Math.max(0, ...counts);
|
|
628
|
+
const rects = counts
|
|
629
|
+
.map((n, i) => {
|
|
630
|
+
const h = max > 0 ? Math.round((n / max) * SPARK_H) : 0;
|
|
631
|
+
return `<rect x="${i * 10}" y="${SPARK_H - h}" width="8" height="${h}" rx="1"></rect>`;
|
|
632
|
+
})
|
|
633
|
+
.join("");
|
|
634
|
+
const unit = activity.granularity === "hour" ? "hours" : "days";
|
|
635
|
+
return `<svg class="spark" viewBox="0 0 68 ${SPARK_H}" role="img" aria-label="Activity, last ${SPARK_BARS} ${unit}">${rects}<line class="base" x1="0" y1="${SPARK_H - 0.5}" x2="68" y2="${SPARK_H - 0.5}"></line></svg>`;
|
|
575
636
|
}
|
|
576
|
-
function
|
|
637
|
+
function activityHeroCard(agg, db, total) {
|
|
638
|
+
return `<section class="panel" aria-label="Activity">
|
|
639
|
+
<h2>Activity</h2>
|
|
640
|
+
<div class="kpi-row">
|
|
641
|
+
${kpiTile("Events", total)}
|
|
642
|
+
</div>
|
|
643
|
+
${activitySparkline(db.activity)}
|
|
644
|
+
<p class="mute small">observe ${agg.modes.observe} · hold ${agg.modes.hold} · offline ${agg.modes.offline} · IDE ${agg.planes.ide} · runtime ${agg.planes.runtime}</p>
|
|
645
|
+
</section>`;
|
|
646
|
+
}
|
|
647
|
+
/** Hero-styled showback: same fields as the old panel (tokens in/out, est.
|
|
648
|
+
* USD, top runs, disclaimer), omitted entirely when no report exists. */
|
|
649
|
+
function showbackHeroCard(report) {
|
|
577
650
|
if (!report)
|
|
578
651
|
return "";
|
|
579
652
|
const topRuns = [...report.perRun]
|
|
@@ -588,7 +661,11 @@ ${topRuns
|
|
|
588
661
|
</ul>`;
|
|
589
662
|
return `<section class="panel" aria-label="Showback">
|
|
590
663
|
<h2>Showback (observe only)</h2>
|
|
591
|
-
<
|
|
664
|
+
<div class="kpi-row">
|
|
665
|
+
${kpiTile("Tokens in", report.totalTokensIn)}
|
|
666
|
+
${kpiTile("Tokens out", report.totalTokensOut)}
|
|
667
|
+
${kpiTile("Est. USD", usdText(report.estimatedUsd))}
|
|
668
|
+
</div>
|
|
592
669
|
${runs}
|
|
593
670
|
<p class="mute small">${esc(SHOWBACK_DISCLAIMER)}</p>
|
|
594
671
|
</section>`;
|
|
@@ -644,6 +721,12 @@ export function renderReceiptHtml(model) {
|
|
|
644
721
|
// must never resolve via Object.prototype. The id lets tests extract just
|
|
645
722
|
// this script. KNOWN is space-delimited so `indexOf(' '+g+' ')` doubles as a
|
|
646
723
|
// prototype-safe whitelist ('constructor' etc. never match).
|
|
724
|
+
// Filter state persists in the ?f= QUERY param, not the hash: the hash is
|
|
725
|
+
// owned by the pure-CSS :target tabs (#overview/#activity/#system), so a
|
|
726
|
+
// hash-based filter would hide the Activity zone on every chip toggle and
|
|
727
|
+
// every tab click would wipe the filter. Legacy #f= hashes are still parsed
|
|
728
|
+
// on load (read-only); the next save writes the ?f= form and drops the
|
|
729
|
+
// legacy fragment.
|
|
647
730
|
const filterScript = `<script id="kya-filters">
|
|
648
731
|
(function(){
|
|
649
732
|
var feed = document.getElementById('feed');
|
|
@@ -654,9 +737,20 @@ export function renderReceiptHtml(model) {
|
|
|
654
737
|
var chips = document.querySelectorAll('[data-fgroup]');
|
|
655
738
|
function parse(){
|
|
656
739
|
state = Object.create(null);
|
|
657
|
-
var
|
|
658
|
-
|
|
659
|
-
|
|
740
|
+
var raw = null;
|
|
741
|
+
var s = location.search;
|
|
742
|
+
if (s.indexOf('?') === 0) {
|
|
743
|
+
var params = s.slice(1).split('&');
|
|
744
|
+
for (var i = 0; i < params.length; i++) {
|
|
745
|
+
if (params[i].indexOf('f=') === 0) { raw = params[i].slice(2); break; }
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
if (raw === null) {
|
|
749
|
+
var legacy = location.hash;
|
|
750
|
+
if (legacy.indexOf('#f=') === 0) raw = legacy.slice(3);
|
|
751
|
+
}
|
|
752
|
+
if (raw === null) return;
|
|
753
|
+
raw.split(',').forEach(function(seg){
|
|
660
754
|
var i = seg.indexOf(':');
|
|
661
755
|
if (i < 1) return;
|
|
662
756
|
var g = seg.slice(0, i);
|
|
@@ -672,8 +766,22 @@ export function renderReceiptHtml(model) {
|
|
|
672
766
|
Object.keys(state).forEach(function(g){
|
|
673
767
|
Object.keys(state[g]).forEach(function(v){ parts.push(g + ':' + encodeURIComponent(v)); });
|
|
674
768
|
});
|
|
675
|
-
|
|
676
|
-
|
|
769
|
+
// Rebuild the query rather than overwrite it: the live page carries its
|
|
770
|
+
// loopback token as ?t= and must not lose it on a chip toggle.
|
|
771
|
+
var kept = [];
|
|
772
|
+
var s = location.search;
|
|
773
|
+
if (s.indexOf('?') === 0) {
|
|
774
|
+
var params = s.slice(1).split('&');
|
|
775
|
+
for (var i = 0; i < params.length; i++) {
|
|
776
|
+
if (params[i] && params[i].indexOf('f=') !== 0) kept.push(params[i]);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
if (parts.length) kept.push('f=' + parts.join(','));
|
|
780
|
+
var q = kept.length ? '?' + kept.join('&') : '';
|
|
781
|
+
// The tab hash rides along untouched; a legacy #f= fragment is not a tab
|
|
782
|
+
// hash and is dropped now that the state lives in the query.
|
|
783
|
+
var h = location.hash.indexOf('#f=') === 0 ? '' : location.hash;
|
|
784
|
+
history.replaceState(null, '', location.pathname + q + h);
|
|
677
785
|
}
|
|
678
786
|
function active(){
|
|
679
787
|
return Object.keys(state).some(function(g){ return Object.keys(state[g]).length > 0; });
|
|
@@ -721,406 +829,71 @@ export function renderReceiptHtml(model) {
|
|
|
721
829
|
})();
|
|
722
830
|
</script>`;
|
|
723
831
|
const livePill = model.live ? `<span class="live" title="Watching trail.jsonl">Live</span>` : "";
|
|
832
|
+
// Zones never render as bare whitespace: with no panels they get a hint.
|
|
833
|
+
const zoneBody = (parts, empty) => parts.some((p) => p.length > 0)
|
|
834
|
+
? parts.filter(Boolean).join("\n ")
|
|
835
|
+
: `<p class="mute small zone-empty">${empty}</p>`;
|
|
836
|
+
const overviewBody = zoneBody([dashboardPanel(dashboard), sessionsPanel(agg, nowMs), reasonsPanel(agg)], "No activity in this window yet — analytics appear once events land.");
|
|
837
|
+
const systemBody = zoneBody([
|
|
838
|
+
wiredHostsPanel(model.wiredHosts),
|
|
839
|
+
sandboxesPanel(model.sandboxes, nowMs),
|
|
840
|
+
orrPanel(model.orr, nowMs),
|
|
841
|
+
toolsHtml,
|
|
842
|
+
], "No system state yet — wire a host, configure a sandbox, or run an ORR.");
|
|
724
843
|
const html = `<!DOCTYPE html>
|
|
725
844
|
<html lang="en">
|
|
726
845
|
<head>
|
|
727
846
|
<meta charset="utf-8"/>
|
|
728
847
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
729
848
|
<title>${esc(model.title)} - ${esc(model.rangeLabel)}</title>
|
|
730
|
-
<style>
|
|
731
|
-
:root {
|
|
732
|
-
color-scheme: dark;
|
|
733
|
-
--bg: #0a0c10;
|
|
734
|
-
--fg: #eef2f7;
|
|
735
|
-
--mute: #8b95a8;
|
|
736
|
-
--line: #1c2330;
|
|
737
|
-
--card: #10141c;
|
|
738
|
-
--ok: #3ecf8e;
|
|
739
|
-
--bad: #ff5d5d;
|
|
740
|
-
--warn: #f0b429;
|
|
741
|
-
--rail: #2a3344;
|
|
742
|
-
--day: #6b7280;
|
|
743
|
-
}
|
|
744
|
-
@media (prefers-color-scheme: light) {
|
|
745
|
-
:root {
|
|
746
|
-
color-scheme: light;
|
|
747
|
-
--bg: #f3f5f8;
|
|
748
|
-
--fg: #0f172a;
|
|
749
|
-
--mute: #64748b;
|
|
750
|
-
--line: #e2e8f0;
|
|
751
|
-
--card: #ffffff;
|
|
752
|
-
--ok: #059669;
|
|
753
|
-
--bad: #dc2626;
|
|
754
|
-
--warn: #d97706;
|
|
755
|
-
--rail: #cbd5e1;
|
|
756
|
-
--day: #64748b;
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
* { box-sizing: border-box; }
|
|
760
|
-
body {
|
|
761
|
-
margin: 0;
|
|
762
|
-
font-family: "IBM Plex Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
763
|
-
background: var(--bg);
|
|
764
|
-
color: var(--fg);
|
|
765
|
-
line-height: 1.4;
|
|
766
|
-
}
|
|
767
|
-
main { max-width: 42rem; margin: 0 auto; padding: 1.35rem 1rem 3rem; }
|
|
768
|
-
header.bar {
|
|
769
|
-
position: sticky; top: 0; z-index: 3;
|
|
770
|
-
background: color-mix(in srgb, var(--bg) 88%, transparent);
|
|
771
|
-
backdrop-filter: blur(10px);
|
|
772
|
-
padding: 0.85rem 0 0.75rem;
|
|
773
|
-
border-bottom: 1px solid var(--line);
|
|
774
|
-
margin-bottom: 1rem;
|
|
775
|
-
}
|
|
776
|
-
.title-row {
|
|
777
|
-
display: flex; align-items: baseline; justify-content: space-between;
|
|
778
|
-
gap: 0.75rem; flex-wrap: wrap;
|
|
779
|
-
}
|
|
780
|
-
h1 {
|
|
781
|
-
margin: 0; font-size: 1.2rem; font-weight: 600;
|
|
782
|
-
letter-spacing: -0.025em;
|
|
783
|
-
}
|
|
784
|
-
.range { color: var(--mute); font-size: 0.85rem; display: inline-flex; align-items: center; gap: 0.5rem; }
|
|
785
|
-
.live {
|
|
786
|
-
display: inline-flex; align-items: center; gap: 0.35rem;
|
|
787
|
-
font-size: 0.7rem; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase;
|
|
788
|
-
color: var(--ok); border: 1px solid color-mix(in srgb, var(--ok) 40%, transparent);
|
|
789
|
-
border-radius: 999px; padding: 0.12rem 0.5rem;
|
|
790
|
-
}
|
|
791
|
-
.live::before {
|
|
792
|
-
content: ""; width: 0.4rem; height: 0.4rem; border-radius: 50%;
|
|
793
|
-
background: var(--ok);
|
|
794
|
-
box-shadow: 0 0 0 3px color-mix(in srgb, var(--ok) 22%, transparent);
|
|
795
|
-
animation: pulse 1.6s ease-in-out infinite;
|
|
796
|
-
}
|
|
797
|
-
.live.off { color: var(--mute); border-color: var(--line); }
|
|
798
|
-
.live.off::before { background: var(--mute); box-shadow: none; animation: none; }
|
|
799
|
-
@keyframes pulse {
|
|
800
|
-
0%, 100% { opacity: 1; }
|
|
801
|
-
50% { opacity: 0.45; }
|
|
802
|
-
}
|
|
803
|
-
@media (prefers-reduced-motion: reduce) {
|
|
804
|
-
.live::before { animation: none; }
|
|
805
|
-
}
|
|
806
|
-
.stats {
|
|
807
|
-
display: flex; flex-wrap: wrap; gap: 0.4rem; margin-top: 0.7rem;
|
|
808
|
-
}
|
|
809
|
-
.stat {
|
|
810
|
-
font-size: 0.75rem; font-variant-numeric: tabular-nums;
|
|
811
|
-
color: var(--mute); background: var(--card);
|
|
812
|
-
border: 1px solid var(--line); border-radius: 999px;
|
|
813
|
-
padding: 0.2rem 0.6rem;
|
|
814
|
-
}
|
|
815
|
-
.stat b { color: var(--fg); font-weight: 650; margin-left: 0.25rem; }
|
|
816
|
-
.stat.bad b { color: var(--bad); }
|
|
817
|
-
.stat.warn b { color: var(--warn); }
|
|
818
|
-
.stat.ok b { color: var(--ok); }
|
|
819
|
-
button.stat {
|
|
820
|
-
font-family: inherit; cursor: pointer;
|
|
821
|
-
appearance: none; -webkit-appearance: none;
|
|
822
|
-
}
|
|
823
|
-
button.stat:hover { border-color: var(--mute); }
|
|
824
|
-
.stat[aria-pressed="true"] {
|
|
825
|
-
color: var(--fg);
|
|
826
|
-
border-color: color-mix(in srgb, var(--ok) 55%, var(--line));
|
|
827
|
-
background: color-mix(in srgb, var(--ok) 12%, var(--card));
|
|
828
|
-
}
|
|
829
|
-
.stat[hidden] { display: none; }
|
|
830
|
-
.clear-filters { color: var(--mute); font-size: 0.72rem; }
|
|
831
|
-
/* Analytics dashboard: 2×2 card grid, collapses to one column on narrow. */
|
|
832
|
-
.db-grid {
|
|
833
|
-
display: grid; grid-template-columns: 1fr 1fr; gap: 0.9rem 1.1rem;
|
|
834
|
-
}
|
|
835
|
-
@media (max-width: 700px) {
|
|
836
|
-
.db-grid { grid-template-columns: 1fr; }
|
|
837
|
-
}
|
|
838
|
-
.db-card { min-width: 0; }
|
|
839
|
-
.db-card h3 {
|
|
840
|
-
margin: 0 0 0.4rem; font-size: 0.68rem; font-weight: 700;
|
|
841
|
-
letter-spacing: 0.06em; text-transform: uppercase; color: var(--mute);
|
|
842
|
-
}
|
|
843
|
-
.db-card h3 .mute { text-transform: none; letter-spacing: 0.02em; font-weight: 600; }
|
|
844
|
-
.db-card .sub {
|
|
845
|
-
margin: 0.35rem 0 0.15rem; font-size: 0.66rem; font-weight: 700;
|
|
846
|
-
letter-spacing: 0.05em; text-transform: uppercase; color: var(--day);
|
|
847
|
-
}
|
|
848
|
-
/* Filter-linked bar row: block variant of .stat's hover/active contract. */
|
|
849
|
-
.db-item {
|
|
850
|
-
display: grid; grid-template-columns: 6.5rem 1fr 2.4rem;
|
|
851
|
-
gap: 0.5rem; align-items: center; width: 100%;
|
|
852
|
-
font-family: inherit; font-size: 0.76rem; font-variant-numeric: tabular-nums;
|
|
853
|
-
color: var(--mute); text-align: left;
|
|
854
|
-
background: transparent; border: 1px solid transparent; border-radius: 8px;
|
|
855
|
-
padding: 0.14rem 0.3rem; cursor: pointer;
|
|
856
|
-
appearance: none; -webkit-appearance: none;
|
|
857
|
-
}
|
|
858
|
-
.db-item:hover { border-color: var(--line); background: color-mix(in srgb, var(--bg) 55%, var(--card)); }
|
|
859
|
-
.db-item[aria-pressed="true"] {
|
|
860
|
-
color: var(--fg);
|
|
861
|
-
border-color: color-mix(in srgb, var(--ok) 55%, var(--line));
|
|
862
|
-
background: color-mix(in srgb, var(--ok) 12%, var(--card));
|
|
863
|
-
}
|
|
864
|
-
.db-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
865
|
-
.db-bar {
|
|
866
|
-
height: 0.55rem; border-radius: 4px; overflow: hidden;
|
|
867
|
-
background: color-mix(in srgb, var(--bg) 55%, var(--card));
|
|
868
|
-
}
|
|
869
|
-
.db-fill { display: block; height: 100%; border-radius: 4px; }
|
|
870
|
-
.db-fill.ok { background: var(--ok); }
|
|
871
|
-
.db-fill.warn { background: var(--warn); }
|
|
872
|
-
.db-fill.bad { background: var(--bad); }
|
|
873
|
-
.db-num { text-align: right; color: var(--fg); }
|
|
874
|
-
/* Vertical mini-bar timeline (not clickable — no time filter exists). */
|
|
875
|
-
.db-tl { display: flex; align-items: stretch; gap: 2px; height: 4.6rem; }
|
|
876
|
-
.db-tl .col {
|
|
877
|
-
flex: 1 1 0; min-width: 0; display: flex; flex-direction: column;
|
|
878
|
-
align-items: center; justify-content: flex-end; gap: 0.15rem;
|
|
879
|
-
}
|
|
880
|
-
.db-vbar {
|
|
881
|
-
width: 100%; min-height: 2px; border-radius: 2px 2px 0 0;
|
|
882
|
-
background: color-mix(in srgb, var(--ok) 75%, var(--mute));
|
|
883
|
-
}
|
|
884
|
-
.db-tl .lab {
|
|
885
|
-
font-size: 0.52rem; color: var(--mute); white-space: nowrap;
|
|
886
|
-
overflow: hidden; max-width: 100%; font-variant-numeric: tabular-nums;
|
|
887
|
-
}
|
|
888
|
-
/* utility: must beat .ev's display:grid (same specificity, later rule would win) */
|
|
889
|
-
.filtered-out { display: none !important; }
|
|
890
|
-
.identity {
|
|
891
|
-
margin-top: 0.45rem; color: var(--mute); font-size: 0.78rem;
|
|
892
|
-
word-break: break-word;
|
|
893
|
-
}
|
|
894
|
-
.modeb {
|
|
895
|
-
display: inline-block; font-size: 0.6rem; font-weight: 700;
|
|
896
|
-
color: var(--mute); border: 1px solid var(--line); border-radius: 4px;
|
|
897
|
-
padding: 0 0.28rem; line-height: 1.1rem; vertical-align: baseline;
|
|
898
|
-
}
|
|
899
|
-
.panel {
|
|
900
|
-
background: var(--card); border: 1px solid var(--line);
|
|
901
|
-
border-radius: 12px; padding: 0.7rem 0.9rem 0.8rem; margin-bottom: 1rem;
|
|
902
|
-
}
|
|
903
|
-
.panel h2 {
|
|
904
|
-
margin: 0 0 0.5rem; font-size: 0.72rem; font-weight: 700;
|
|
905
|
-
letter-spacing: 0.07em; text-transform: uppercase; color: var(--day);
|
|
906
|
-
}
|
|
907
|
-
.panel .sub { margin: 0 0 0.45rem; color: var(--mute); font-size: 0.8rem; }
|
|
908
|
-
.panel .line { margin: 0.25rem 0; font-size: 0.84rem; word-break: break-word; }
|
|
909
|
-
.panel .mute { color: var(--mute); }
|
|
910
|
-
.panel .small { font-size: 0.74rem; }
|
|
911
|
-
.rows {
|
|
912
|
-
list-style: none; margin: 0; padding: 0;
|
|
913
|
-
display: flex; flex-direction: column; gap: 0.3rem;
|
|
914
|
-
font-size: 0.82rem;
|
|
915
|
-
}
|
|
916
|
-
.rows li { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.45rem; min-width: 0; }
|
|
917
|
-
.rows li.mute { color: var(--mute); }
|
|
918
|
-
.rows .sid, .rows code {
|
|
919
|
-
font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
920
|
-
font-size: 0.78rem; word-break: break-all;
|
|
921
|
-
}
|
|
922
|
-
.rows .cnt { color: var(--mute); font-size: 0.76rem; }
|
|
923
|
-
.rows .rel { margin-left: auto; color: var(--mute); font-size: 0.74rem; white-space: nowrap; }
|
|
924
|
-
.rows .hint {
|
|
925
|
-
font-size: 0.72rem; border-bottom: 1px dotted var(--mute); cursor: help;
|
|
926
|
-
}
|
|
927
|
-
.wdot {
|
|
928
|
-
width: 0.5rem; height: 0.5rem; border-radius: 50%; flex: none;
|
|
929
|
-
align-self: center; background: var(--mute);
|
|
930
|
-
}
|
|
931
|
-
.wdot.allow { background: var(--ok); }
|
|
932
|
-
.wdot.hold { background: var(--warn); }
|
|
933
|
-
.wdot.deny, .wdot.never { background: var(--bad); }
|
|
934
|
-
.chiprow { display: flex; flex-wrap: wrap; gap: 0.35rem; }
|
|
935
|
-
.chip {
|
|
936
|
-
font-size: 0.76rem; color: var(--mute);
|
|
937
|
-
background: color-mix(in srgb, var(--bg) 55%, var(--card));
|
|
938
|
-
border: 1px solid var(--line); border-radius: 999px; padding: 0.15rem 0.55rem;
|
|
939
|
-
}
|
|
940
|
-
.chip code {
|
|
941
|
-
font-family: "IBM Plex Mono", ui-monospace, Menlo, monospace;
|
|
942
|
-
color: var(--fg); font-size: 0.74rem;
|
|
943
|
-
}
|
|
944
|
-
.tbl { width: 100%; border-collapse: collapse; font-size: 0.8rem; }
|
|
945
|
-
.tbl th {
|
|
946
|
-
text-align: left; font-size: 0.66rem; font-weight: 700; letter-spacing: 0.06em;
|
|
947
|
-
text-transform: uppercase; color: var(--mute); padding: 0.15rem 0.5rem 0.3rem 0;
|
|
948
|
-
}
|
|
949
|
-
.tbl td { padding: 0.2rem 0.5rem 0.2rem 0; border-top: 1px solid var(--line); }
|
|
950
|
-
.tbl code {
|
|
951
|
-
font-family: "IBM Plex Mono", ui-monospace, Menlo, monospace; font-size: 0.76rem;
|
|
952
|
-
}
|
|
953
|
-
.pill {
|
|
954
|
-
display: inline-block; font-size: 0.7rem; font-weight: 700;
|
|
955
|
-
letter-spacing: 0.04em; text-transform: uppercase;
|
|
956
|
-
border-radius: 999px; padding: 0.1rem 0.55rem;
|
|
957
|
-
border: 1px solid var(--line); color: var(--mute);
|
|
958
|
-
}
|
|
959
|
-
.pill.ok { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 40%, transparent); }
|
|
960
|
-
.pill.orr-green { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 40%, transparent); }
|
|
961
|
-
.pill.orr-amber { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 40%, transparent); }
|
|
962
|
-
.pill.orr-red { color: var(--bad); border-color: color-mix(in srgb, var(--bad) 40%, transparent); }
|
|
963
|
-
.orrline { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.55rem; }
|
|
964
|
-
.orrline .disp { font-size: 0.84rem; font-weight: 600; }
|
|
965
|
-
.alert {
|
|
966
|
-
display: flex; flex-wrap: wrap; gap: 0.45rem 0.75rem; align-items: baseline;
|
|
967
|
-
background: color-mix(in srgb, var(--bad) 12%, var(--card));
|
|
968
|
-
border: 1px solid color-mix(in srgb, var(--bad) 35%, var(--line));
|
|
969
|
-
border-radius: 10px; padding: 0.65rem 0.85rem; margin-bottom: 1rem;
|
|
970
|
-
font-size: 0.85rem;
|
|
971
|
-
}
|
|
972
|
-
.alert strong { color: var(--bad); font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.04em; }
|
|
973
|
-
.alert code {
|
|
974
|
-
font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
975
|
-
font-size: 0.78rem;
|
|
976
|
-
}
|
|
977
|
-
.feed {
|
|
978
|
-
background: var(--card);
|
|
979
|
-
border: 1px solid var(--line);
|
|
980
|
-
border-radius: 12px;
|
|
981
|
-
overflow: hidden;
|
|
982
|
-
}
|
|
983
|
-
.day {
|
|
984
|
-
padding: 0.55rem 0.9rem 0.35rem;
|
|
985
|
-
font-size: 0.68rem; font-weight: 700; letter-spacing: 0.08em;
|
|
986
|
-
text-transform: uppercase; color: var(--day);
|
|
987
|
-
background: color-mix(in srgb, var(--bg) 55%, var(--card));
|
|
988
|
-
border-bottom: 1px solid var(--line);
|
|
989
|
-
}
|
|
990
|
-
.ev {
|
|
991
|
-
display: grid; grid-template-columns: 1.1rem 1fr; gap: 0;
|
|
992
|
-
border-bottom: 1px solid var(--line);
|
|
993
|
-
}
|
|
994
|
-
.ev:last-child { border-bottom: none; }
|
|
995
|
-
.rail {
|
|
996
|
-
position: relative;
|
|
997
|
-
display: flex; justify-content: center;
|
|
998
|
-
padding-top: 1.05rem;
|
|
999
|
-
}
|
|
1000
|
-
.rail::before {
|
|
1001
|
-
content: ""; position: absolute; top: 0; bottom: 0; left: 50%;
|
|
1002
|
-
width: 1px; background: var(--rail); transform: translateX(-50%);
|
|
1003
|
-
}
|
|
1004
|
-
.tick {
|
|
1005
|
-
width: 0.55rem; height: 0.55rem; border-radius: 50%;
|
|
1006
|
-
background: var(--mute); position: relative; z-index: 1;
|
|
1007
|
-
box-shadow: 0 0 0 3px var(--card);
|
|
1008
|
-
}
|
|
1009
|
-
.ev.ok .tick { background: var(--ok); }
|
|
1010
|
-
.ev.bad .tick { background: var(--bad); }
|
|
1011
|
-
.ev.warn .tick { background: var(--warn); }
|
|
1012
|
-
.ev.never { background: color-mix(in srgb, var(--bad) 7%, transparent); }
|
|
1013
|
-
.main { padding: 0.7rem 0.85rem 0.7rem 0.35rem; min-width: 0; }
|
|
1014
|
-
.top {
|
|
1015
|
-
display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.35rem 0.55rem;
|
|
1016
|
-
}
|
|
1017
|
-
@media (max-width: 480px) {
|
|
1018
|
-
.when { flex-basis: 100%; margin-left: 0; margin-top: 0.1rem; }
|
|
1019
|
-
}
|
|
1020
|
-
.verdict {
|
|
1021
|
-
font-size: 0.68rem; font-weight: 750; letter-spacing: 0.05em;
|
|
1022
|
-
min-width: 2.6rem;
|
|
1023
|
-
}
|
|
1024
|
-
.ev.ok .verdict { color: var(--ok); }
|
|
1025
|
-
.ev.bad .verdict { color: var(--bad); }
|
|
1026
|
-
.ev.warn .verdict { color: var(--warn); }
|
|
1027
|
-
.tool {
|
|
1028
|
-
font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
1029
|
-
font-size: 0.84rem; word-break: break-word; flex: 1 1 auto;
|
|
1030
|
-
}
|
|
1031
|
-
.when {
|
|
1032
|
-
margin-left: auto; color: var(--mute); font-size: 0.72rem;
|
|
1033
|
-
font-variant-numeric: tabular-nums; white-space: nowrap;
|
|
1034
|
-
}
|
|
1035
|
-
.when .rel { opacity: 0.85; margin-left: 0.35rem; }
|
|
1036
|
-
.summary {
|
|
1037
|
-
margin-top: 0.2rem;
|
|
1038
|
-
font-size: 0.84rem;
|
|
1039
|
-
color: var(--fg);
|
|
1040
|
-
word-break: break-word;
|
|
1041
|
-
}
|
|
1042
|
-
details.diff {
|
|
1043
|
-
margin-top: 0.35rem;
|
|
1044
|
-
border: 1px solid var(--line);
|
|
1045
|
-
border-radius: 8px;
|
|
1046
|
-
background: color-mix(in srgb, var(--bg) 55%, var(--card));
|
|
1047
|
-
overflow: hidden;
|
|
1048
|
-
}
|
|
1049
|
-
details.diff > summary {
|
|
1050
|
-
cursor: pointer;
|
|
1051
|
-
list-style: none;
|
|
1052
|
-
padding: 0.35rem 0.55rem;
|
|
1053
|
-
font-size: 0.72rem;
|
|
1054
|
-
font-weight: 650;
|
|
1055
|
-
letter-spacing: 0.04em;
|
|
1056
|
-
text-transform: uppercase;
|
|
1057
|
-
color: var(--mute);
|
|
1058
|
-
}
|
|
1059
|
-
details.diff > summary::-webkit-details-marker { display: none; }
|
|
1060
|
-
details.diff pre {
|
|
1061
|
-
margin: 0;
|
|
1062
|
-
padding: 0.45rem 0.55rem 0.6rem;
|
|
1063
|
-
border-top: 1px solid var(--line);
|
|
1064
|
-
font-family: "IBM Plex Mono", ui-monospace, Menlo, monospace;
|
|
1065
|
-
font-size: 0.72rem;
|
|
1066
|
-
line-height: 1.35;
|
|
1067
|
-
white-space: pre-wrap;
|
|
1068
|
-
word-break: break-word;
|
|
1069
|
-
color: var(--fg);
|
|
1070
|
-
}
|
|
1071
|
-
.meta {
|
|
1072
|
-
margin-top: 0.18rem; color: var(--mute); font-size: 0.76rem;
|
|
1073
|
-
display: flex; flex-wrap: wrap; gap: 0.25rem; align-items: baseline;
|
|
1074
|
-
}
|
|
1075
|
-
.dot { opacity: 0.55; }
|
|
1076
|
-
.empty { margin: 0; padding: 1.25rem 1rem; color: var(--mute); font-size: 0.9rem; }
|
|
1077
|
-
.tools {
|
|
1078
|
-
margin-top: 1.1rem; display: flex; flex-wrap: wrap; gap: 0.35rem; align-items: center;
|
|
1079
|
-
}
|
|
1080
|
-
.tools .label {
|
|
1081
|
-
font-size: 0.68rem; font-weight: 700; letter-spacing: 0.06em;
|
|
1082
|
-
text-transform: uppercase; color: var(--mute); margin-right: 0.25rem;
|
|
1083
|
-
}
|
|
1084
|
-
.tools code {
|
|
1085
|
-
font-family: "IBM Plex Mono", ui-monospace, Menlo, monospace;
|
|
1086
|
-
font-size: 0.72rem; color: var(--mute);
|
|
1087
|
-
background: var(--card); border: 1px solid var(--line);
|
|
1088
|
-
border-radius: 6px; padding: 0.18rem 0.4rem;
|
|
1089
|
-
}
|
|
1090
|
-
</style>
|
|
849
|
+
<style>${receiptCss()}</style>
|
|
1091
850
|
</head>
|
|
1092
851
|
<body>
|
|
1093
852
|
<main>
|
|
1094
|
-
<
|
|
1095
|
-
<
|
|
1096
|
-
<
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
<
|
|
1104
|
-
<
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
${chips.products}
|
|
1109
|
-
${chips.projects}
|
|
1110
|
-
</header>
|
|
853
|
+
<div class="topstick">
|
|
854
|
+
<header class="bar">
|
|
855
|
+
<div class="title-row">
|
|
856
|
+
<h1>${esc(model.title)}</h1>
|
|
857
|
+
<div class="range">${esc(model.rangeLabel)}${livePill}</div>
|
|
858
|
+
</div>
|
|
859
|
+
${identityLine(model.identity)}
|
|
860
|
+
</header>
|
|
861
|
+
<nav class="tabs" aria-label="Report sections">
|
|
862
|
+
<a class="tab" id="tab-overview" href="#overview">Overview</a>
|
|
863
|
+
<a class="tab" id="tab-activity" href="#activity">Activity</a>
|
|
864
|
+
<a class="tab" id="tab-system" href="#system">System</a>
|
|
865
|
+
</nav>
|
|
866
|
+
</div>
|
|
1111
867
|
${blockedBanner}
|
|
1112
|
-
|
|
1113
|
-
|
|
868
|
+
<div class="hero">
|
|
869
|
+
${heroCertifyPanel(model.certify)}
|
|
870
|
+
${verdictsHeroCard(c, events.length)}
|
|
871
|
+
${activityHeroCard(agg, dashboard, events.length)}
|
|
872
|
+
${showbackHeroCard(model.showback)}
|
|
873
|
+
</div>
|
|
874
|
+
<section class="zone" id="overview" aria-labelledby="tab-overview">
|
|
875
|
+
${overviewBody}
|
|
876
|
+
</section>
|
|
877
|
+
<section class="zone" id="activity" aria-labelledby="tab-activity">
|
|
878
|
+
<div class="feed-head">
|
|
879
|
+
<div class="stats" aria-label="Counts">
|
|
880
|
+
<button type="button" class="stat ok" data-fgroup="verdict" data-fvalue="ALLOW" aria-pressed="false" title="Filter: Allow">Allow<b>${c.allow}</b></button>
|
|
881
|
+
<button type="button" class="stat bad" data-fgroup="verdict" data-fvalue="DENY" aria-pressed="false" title="Filter: Deny">Deny<b>${c.deny}</b></button>
|
|
882
|
+
<button type="button" class="stat warn" data-fgroup="verdict" data-fvalue="REQUIRE_APPROVE" aria-pressed="false" title="Filter: Review">Review<b>${c.require}</b></button>
|
|
883
|
+
<button type="button" class="stat" data-fgroup="never" data-fvalue="1" aria-pressed="false" title="Filter: Never">Never<b>${c.never}</b></button>
|
|
884
|
+
${chips.modes}${chips.planes}
|
|
885
|
+
<button type="button" class="stat clear-filters" id="clear-filters" hidden>Clear filters ×</button>
|
|
886
|
+
</div>
|
|
887
|
+
${chips.products}
|
|
888
|
+
${chips.projects}
|
|
889
|
+
</div>
|
|
890
|
+
<section class="feed" id="feed" aria-label="Activity feed">
|
|
1114
891
|
${renderFeed(events, nowMs, model.live)}
|
|
892
|
+
</section>
|
|
893
|
+
</section>
|
|
894
|
+
<section class="zone" id="system" aria-labelledby="tab-system">
|
|
895
|
+
${systemBody}
|
|
1115
896
|
</section>
|
|
1116
|
-
${sessionsPanel(agg, nowMs)}
|
|
1117
|
-
${reasonsPanel(agg)}
|
|
1118
|
-
${wiredHostsPanel(model.wiredHosts)}
|
|
1119
|
-
${sandboxesPanel(model.sandboxes, nowMs)}
|
|
1120
|
-
${orrPanel(model.orr, nowMs)}
|
|
1121
|
-
${certifyPanel(model.certify)}
|
|
1122
|
-
${showbackPanel(model.showback)}
|
|
1123
|
-
${toolsHtml}
|
|
1124
897
|
</main>
|
|
1125
898
|
${filterScript}
|
|
1126
899
|
${liveScript}
|
|
@@ -1170,6 +943,17 @@ export function renderReceiptMarkdown(model) {
|
|
|
1170
943
|
if (agg.projects.length >= 2) {
|
|
1171
944
|
lines.push("## Projects", ...agg.projects.map((p) => `- ${mdText(p.label)} × ${p.count}`), "");
|
|
1172
945
|
}
|
|
946
|
+
if (model.certify) {
|
|
947
|
+
const c = model.certify;
|
|
948
|
+
lines.push("## Certify", `result: ${c.result} · counts: ${c.pass} pass · ${c.gap} gap · ${c.insufficientEvidence} insufficient · ${c.attested} attested`);
|
|
949
|
+
if (c.topGaps.length > 0) {
|
|
950
|
+
lines.push("Top gaps:");
|
|
951
|
+
for (const g of c.topGaps) {
|
|
952
|
+
lines.push(`- \`${mdInline(g.id)}\` — ${mdText(g.severity)}`);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
lines.push(`live evaluation — window ${c.windowDays}d, ${c.trailEvents} trail events · kya certify for the full gap report + signed bundle`, "");
|
|
956
|
+
}
|
|
1173
957
|
const db = computeDashboard(model.events);
|
|
1174
958
|
if (db.verdictMix.total > 0) {
|
|
1175
959
|
const pct = (n) => Math.round((n / db.verdictMix.total) * 100);
|
|
@@ -1259,17 +1043,6 @@ export function renderReceiptMarkdown(model) {
|
|
|
1259
1043
|
lines.push(`Most urgent fix: ${mdText(orr.mostUrgentFix)}`);
|
|
1260
1044
|
lines.push(`scorecards: ${orr.scorecards.pass} pass · ${orr.scorecards.fail} fail · ${orr.scorecards.partial} partial · ${orr.scorecards.notEvaluated} n/e${orr.generatedAt ? ` · ${relativeTime(orr.generatedAt, nowMs)}` : ""}`, "");
|
|
1261
1045
|
}
|
|
1262
|
-
if (model.certify) {
|
|
1263
|
-
const c = model.certify;
|
|
1264
|
-
lines.push("## Certify", `result: ${c.result} · counts: ${c.pass} pass · ${c.gap} gap · ${c.insufficientEvidence} insufficient · ${c.attested} attested`);
|
|
1265
|
-
if (c.topGaps.length > 0) {
|
|
1266
|
-
lines.push("Top gaps:");
|
|
1267
|
-
for (const g of c.topGaps) {
|
|
1268
|
-
lines.push(`- \`${mdInline(g.id)}\` — ${mdText(g.severity)}`);
|
|
1269
|
-
}
|
|
1270
|
-
}
|
|
1271
|
-
lines.push(`live evaluation — window ${c.windowDays}d, ${c.trailEvents} trail events · kya certify for the full gap report + signed bundle`, "");
|
|
1272
|
-
}
|
|
1273
1046
|
if (model.showback) {
|
|
1274
1047
|
const sb = model.showback;
|
|
1275
1048
|
lines.push("## Showback (observe only)", `${sb.totalTokensIn} tokens in · ${sb.totalTokensOut} tokens out · ${usdText(sb.estimatedUsd)}`);
|