loki-mode 7.84.0 → 7.86.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/README.md +76 -0
- package/SKILL.md +4 -2
- package/VERSION +1 -1
- package/autonomy/lib/proof-generator.py +407 -13
- package/autonomy/lib/proof-template.html +309 -1
- package/autonomy/lib/proof-verify.py +483 -0
- package/autonomy/loki +32 -1
- package/autonomy/run.sh +26 -2
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +56 -0
- package/dashboard/static/index.html +93 -0
- package/docs/INSTALLATION.md +2 -2
- package/loki-ts/dist/loki.js +2 -2
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
|
@@ -257,6 +257,69 @@
|
|
|
257
257
|
}
|
|
258
258
|
.hash-note { color: var(--faint); font-size: 12px; margin-top: 8px; }
|
|
259
259
|
|
|
260
|
+
/* Honesty banner (driven by honesty.headline; v1.1) */
|
|
261
|
+
.honesty {
|
|
262
|
+
display: flex; align-items: flex-start; gap: 14px;
|
|
263
|
+
border: 1px solid var(--border); border-radius: 14px;
|
|
264
|
+
padding: 18px 20px; margin-bottom: 22px;
|
|
265
|
+
}
|
|
266
|
+
.honesty.is-verified { border-color: rgba(52,211,153,0.45); background: rgba(52,211,153,0.07); }
|
|
267
|
+
.honesty.is-gaps { border-color: rgba(251,191,36,0.45); background: rgba(251,191,36,0.07); }
|
|
268
|
+
.honesty.is-failed { border-color: rgba(248,113,113,0.45); background: rgba(248,113,113,0.07); }
|
|
269
|
+
.honesty .h-mark {
|
|
270
|
+
flex: 0 0 auto; width: 34px; height: 34px; border-radius: 9px;
|
|
271
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
272
|
+
font-family: var(--mono); font-size: 18px; font-weight: 700;
|
|
273
|
+
}
|
|
274
|
+
.honesty.is-verified .h-mark { color: var(--green); background: rgba(52,211,153,0.12); }
|
|
275
|
+
.honesty.is-gaps .h-mark { color: var(--amber); background: rgba(251,191,36,0.12); }
|
|
276
|
+
.honesty.is-failed .h-mark { color: var(--red); background: rgba(248,113,113,0.12); }
|
|
277
|
+
.honesty .h-body { flex: 1; min-width: 0; }
|
|
278
|
+
.honesty .h-headline { font-size: 18px; font-weight: 650; letter-spacing: 0.2px; }
|
|
279
|
+
.honesty.is-verified .h-headline { color: var(--green); }
|
|
280
|
+
.honesty.is-gaps .h-headline { color: var(--amber); }
|
|
281
|
+
.honesty.is-failed .h-headline { color: var(--red); }
|
|
282
|
+
.honesty .h-sub { color: var(--muted); font-size: 13px; margin-top: 4px; }
|
|
283
|
+
.honesty .h-gaps { margin: 12px 0 0; padding-left: 18px; }
|
|
284
|
+
.honesty .h-gaps li { margin: 5px 0; color: var(--text); font-size: 13.5px; }
|
|
285
|
+
.honesty .h-gaps .gi { font-family: var(--mono); color: var(--amber); }
|
|
286
|
+
.honesty .h-gaps .gr { color: var(--muted); }
|
|
287
|
+
|
|
288
|
+
/* Facts vs Assessments split (v1.1) */
|
|
289
|
+
.split-label {
|
|
290
|
+
display: inline-flex; align-items: center; gap: 8px;
|
|
291
|
+
font-size: 12px; text-transform: uppercase; letter-spacing: 1.2px;
|
|
292
|
+
color: var(--faint); font-weight: 600;
|
|
293
|
+
}
|
|
294
|
+
.split-label .tag {
|
|
295
|
+
text-transform: none; letter-spacing: 0; font-size: 11px; font-weight: 600;
|
|
296
|
+
border-radius: 6px; padding: 2px 8px; border: 1px solid var(--border);
|
|
297
|
+
}
|
|
298
|
+
.split-label .tag.fact { color: var(--green); border-color: rgba(52,211,153,0.4); background: rgba(52,211,153,0.07); }
|
|
299
|
+
.split-label .tag.opinion { color: var(--amber); border-color: rgba(251,191,36,0.4); background: rgba(251,191,36,0.07); }
|
|
300
|
+
.split-note { color: var(--muted); font-size: 13px; margin: 6px 0 12px; }
|
|
301
|
+
.fact-row {
|
|
302
|
+
display: flex; align-items: baseline; gap: 12px; padding: 11px 0;
|
|
303
|
+
border-top: 1px solid var(--border); flex-wrap: wrap;
|
|
304
|
+
}
|
|
305
|
+
.fact-row:first-child { border-top: none; }
|
|
306
|
+
.fact-row .fk { flex: 0 0 130px; color: var(--faint); font-size: 13px; }
|
|
307
|
+
.fact-row .fv { flex: 1; min-width: 160px; font-family: var(--mono); font-size: 13px; color: var(--text); word-break: break-word; }
|
|
308
|
+
.fact-row .fv .meta { color: var(--muted); font-family: var(--sans); }
|
|
309
|
+
.fstatus {
|
|
310
|
+
font-size: 11px; font-family: var(--mono); text-transform: uppercase;
|
|
311
|
+
border-radius: 6px; padding: 2px 8px; border: 1px solid var(--border);
|
|
312
|
+
letter-spacing: 0.4px;
|
|
313
|
+
}
|
|
314
|
+
.fstatus.ok { color: var(--green); border-color: rgba(52,211,153,0.4); background: rgba(52,211,153,0.07); }
|
|
315
|
+
.fstatus.bad { color: var(--red); border-color: rgba(248,113,113,0.4); background: rgba(248,113,113,0.07); }
|
|
316
|
+
.fstatus.warn { color: var(--amber); border-color: rgba(251,191,36,0.4); background: rgba(251,191,36,0.07); }
|
|
317
|
+
.fstatus.none { color: var(--faint); }
|
|
318
|
+
.opinion-note {
|
|
319
|
+
color: var(--amber); font-size: 12.5px; margin: 0 0 14px;
|
|
320
|
+
display: flex; align-items: center; gap: 7px;
|
|
321
|
+
}
|
|
322
|
+
|
|
260
323
|
/* Copy command */
|
|
261
324
|
.cmd {
|
|
262
325
|
display: flex; align-items: stretch; gap: 0; margin-top: 10px;
|
|
@@ -284,6 +347,11 @@
|
|
|
284
347
|
<span id="runIdTop"></span>
|
|
285
348
|
</div>
|
|
286
349
|
|
|
350
|
+
<!-- HONESTY BANNER (v1.1; driven by honesty.headline). Stays hidden for v1.0
|
|
351
|
+
proofs that carry no honesty block. Never green unless the deterministic
|
|
352
|
+
headline says VERIFIED. -->
|
|
353
|
+
<div class="honesty hide" id="honestyBanner"></div>
|
|
354
|
+
|
|
287
355
|
<!-- HERO (branded proof card; rendered client-side from the redacted dict) -->
|
|
288
356
|
<section class="hero">
|
|
289
357
|
<div class="hero-brand">
|
|
@@ -303,9 +371,28 @@
|
|
|
303
371
|
<div class="hero-actions" id="heroActions"></div>
|
|
304
372
|
</section>
|
|
305
373
|
|
|
374
|
+
<!-- FACTS (v1.1): deterministic, re-verifiable. Hidden for v1.0 proofs. -->
|
|
375
|
+
<section class="section hide" id="secFacts">
|
|
376
|
+
<h2><span class="split-label">Facts <span class="tag fact">deterministic, re-verifiable</span></span></h2>
|
|
377
|
+
<p class="split-note">Every line below is recomputed by a skeptic from the same repo state. These are not opinions.</p>
|
|
378
|
+
<div class="card" id="factsCard"></div>
|
|
379
|
+
</section>
|
|
380
|
+
|
|
381
|
+
<!-- ASSESSMENTS (v1.1): AI judgment, explicitly NOT proof. Hidden for v1.0. -->
|
|
382
|
+
<section class="section hide" id="secAssessments">
|
|
383
|
+
<h2><span class="split-label">Assessments <span class="tag opinion">AI judgment, not proof</span></span></h2>
|
|
384
|
+
<div class="card" id="assessmentsCard"></div>
|
|
385
|
+
</section>
|
|
386
|
+
|
|
387
|
+
<!-- VERIFY THIS YOURSELF (v1.1): the exact re-run command + base sha. -->
|
|
388
|
+
<section class="section hide" id="secVerifySelf">
|
|
389
|
+
<h2>Verify this yourself</h2>
|
|
390
|
+
<div class="card" id="verifySelfCard"></div>
|
|
391
|
+
</section>
|
|
392
|
+
|
|
306
393
|
<!-- Tier 1: deployed URL + diff -->
|
|
307
394
|
<section class="section" id="secTier1">
|
|
308
|
-
<h2>
|
|
395
|
+
<h2>Live and review links</h2>
|
|
309
396
|
<div class="card" id="tier1Card"></div>
|
|
310
397
|
</section>
|
|
311
398
|
|
|
@@ -432,8 +519,20 @@
|
|
|
432
519
|
return /^https?:\/\/(localhost|127\.|0\.0\.0\.0|\[::1\])/i.test(u);
|
|
433
520
|
}
|
|
434
521
|
|
|
522
|
+
// v1.1 detection: a proof carries the evidence model when it has the
|
|
523
|
+
// honesty/facts blocks. v1.0 proofs do not, and the new sections stay hidden
|
|
524
|
+
// so they render byte-for-byte as before.
|
|
525
|
+
function isV11(p) {
|
|
526
|
+
return !!(p && typeof p === "object" &&
|
|
527
|
+
p.honesty && typeof p.honesty === "object");
|
|
528
|
+
}
|
|
529
|
+
|
|
435
530
|
function render(p) {
|
|
531
|
+
renderHonesty(p);
|
|
436
532
|
renderHero(p);
|
|
533
|
+
renderFacts(p);
|
|
534
|
+
renderAssessments(p);
|
|
535
|
+
renderVerifySelf(p);
|
|
437
536
|
renderTier1(p);
|
|
438
537
|
renderCost(p);
|
|
439
538
|
renderStatsAndFiles(p);
|
|
@@ -462,6 +561,215 @@
|
|
|
462
561
|
return { count: ok, total: reviewers.length };
|
|
463
562
|
}
|
|
464
563
|
|
|
564
|
+
// ---- v1.1 evidence model rendering -------------------------------------
|
|
565
|
+
|
|
566
|
+
// Map a deterministic fact status to a chip class. "verified"/"passed"/"ok"
|
|
567
|
+
// are the only green states; never paint not_run/inconclusive/skipped green.
|
|
568
|
+
function factStatusClass(st) {
|
|
569
|
+
st = String(st || "").toLowerCase();
|
|
570
|
+
if (st === "verified" || st === "passed" || st === "pass" || st === "ok") return "ok";
|
|
571
|
+
if (st === "failed" || st === "fail" || st === "error") return "bad";
|
|
572
|
+
if (st === "inconclusive" || st === "partial" || st === "unknown") return "warn";
|
|
573
|
+
if (st === "not_run" || st === "notrun" || st === "skipped" || st === "skip" || st === "") return "none";
|
|
574
|
+
return "warn";
|
|
575
|
+
}
|
|
576
|
+
function factStatusLabel(st) {
|
|
577
|
+
st = String(st || "").toLowerCase();
|
|
578
|
+
if (st === "not_run" || st === "notrun") return "not run";
|
|
579
|
+
return st || "not run";
|
|
580
|
+
}
|
|
581
|
+
function statusChip(st) {
|
|
582
|
+
return '<span class="fstatus ' + factStatusClass(st) + '">' +
|
|
583
|
+
esc(factStatusLabel(st)) + "</span>";
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Pick the honesty banner variant from the deterministic headline. Default to
|
|
587
|
+
// the failed/red variant for any unrecognized string so an unknown headline is
|
|
588
|
+
// never silently treated as green.
|
|
589
|
+
function honestyVariant(headline) {
|
|
590
|
+
var h = String(headline || "").toUpperCase();
|
|
591
|
+
if (h === "VERIFIED") return { cls: "is-verified", mark: "OK",
|
|
592
|
+
sub: "Tests verified, a real diff exists, and nothing was left unverified. Every claim below is deterministic and re-checkable." };
|
|
593
|
+
if (h.indexOf("GAPS") !== -1) return { cls: "is-gaps", mark: "!",
|
|
594
|
+
sub: "Some evidence is verified, but the items listed below were not. A gap is never hidden: read them before trusting this run." };
|
|
595
|
+
return { cls: "is-failed", mark: "X",
|
|
596
|
+
sub: "This run did not produce deterministic verification. Treat the build as unproven until the gaps below are closed." };
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function renderHonesty(p) {
|
|
600
|
+
var el = document.getElementById("honestyBanner");
|
|
601
|
+
if (!el) return;
|
|
602
|
+
// v1.0 proofs have no honesty block: leave the banner hidden so they render
|
|
603
|
+
// exactly as before.
|
|
604
|
+
if (!isV11(p)) return;
|
|
605
|
+
var headline = g(p, "honesty.headline", "NOT VERIFIED");
|
|
606
|
+
var v = honestyVariant(headline);
|
|
607
|
+
var degraded = g(p, "honesty.degraded", []);
|
|
608
|
+
var gaps = "";
|
|
609
|
+
if (Array.isArray(degraded) && degraded.length > 0) {
|
|
610
|
+
var lis = "";
|
|
611
|
+
for (var i = 0; i < degraded.length; i++) {
|
|
612
|
+
var d = degraded[i];
|
|
613
|
+
if (d && typeof d === "object") {
|
|
614
|
+
// Generator shape: {item, status, reason}.
|
|
615
|
+
lis += "<li><span class=\"gi\">" + esc(g(d, "item", "")) + "</span> " +
|
|
616
|
+
"<span class=\"gr\">" + esc(factStatusLabel(g(d, "status", ""))) +
|
|
617
|
+
(g(d, "reason", "") ? " - " + esc(g(d, "reason", "")) : "") +
|
|
618
|
+
"</span></li>";
|
|
619
|
+
} else {
|
|
620
|
+
// Tolerate a plain-string degraded entry (verifier-normalized shape).
|
|
621
|
+
lis += "<li><span class=\"gr\">" + esc(d) + "</span></li>";
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
gaps = '<ul class="h-gaps">' + lis + "</ul>";
|
|
625
|
+
} else {
|
|
626
|
+
gaps = '<p class="h-sub" style="margin-top:10px;">No gaps were flagged for this run.</p>';
|
|
627
|
+
}
|
|
628
|
+
el.className = "honesty " + v.cls;
|
|
629
|
+
el.innerHTML =
|
|
630
|
+
'<span class="h-mark" aria-hidden="true">' + v.mark + "</span>" +
|
|
631
|
+
'<div class="h-body">' +
|
|
632
|
+
'<div class="h-headline">' + esc(headline) + "</div>" +
|
|
633
|
+
'<div class="h-sub">' + v.sub + "</div>" +
|
|
634
|
+
gaps +
|
|
635
|
+
"</div>";
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// Render the deterministic FACTS: git diff + sha, build command/exit, tests
|
|
639
|
+
// command/exit/counts/status, quality gates. Each carries a status chip so a
|
|
640
|
+
// not-run fact is visibly not-run, never blank-implied-green.
|
|
641
|
+
function renderFacts(p) {
|
|
642
|
+
var sec = document.getElementById("secFacts");
|
|
643
|
+
var card = document.getElementById("factsCard");
|
|
644
|
+
if (!sec || !card || !isV11(p)) return; // v1.0: stays hidden
|
|
645
|
+
var rows = "";
|
|
646
|
+
|
|
647
|
+
// git diff + sha (deterministic, re-derivable from base..head).
|
|
648
|
+
var diff = g(p, "facts.git.diff", null) || g(p, "files_changed", {});
|
|
649
|
+
var dc = num(g(diff, "count", 0));
|
|
650
|
+
var di = num(g(diff, "insertions", 0));
|
|
651
|
+
var dd = num(g(diff, "deletions", 0));
|
|
652
|
+
var diffSt = dc > 0 ? "verified" : "not_run";
|
|
653
|
+
rows += factRow("Code diff",
|
|
654
|
+
dc + ' file' + (dc === 1 ? "" : "s") +
|
|
655
|
+
' <span class="add">+' + fmtInt(di) + '</span> <span class="del">-' + fmtInt(dd) + "</span>",
|
|
656
|
+
diffSt);
|
|
657
|
+
var dsha = g(p, "facts.git.diff_sha256", "");
|
|
658
|
+
if (dsha) rows += factRow("Diff sha256", '<span class="meta">' + esc(dsha) + "</span>", null);
|
|
659
|
+
var baseSha = g(p, "facts.git.base_sha", "");
|
|
660
|
+
if (baseSha) rows += factRow("Base commit", esc(baseSha), null);
|
|
661
|
+
|
|
662
|
+
// build command + exit code + status.
|
|
663
|
+
var bSt = g(p, "facts.build.status", "not_run");
|
|
664
|
+
var bCmd = g(p, "facts.build.command", "");
|
|
665
|
+
var bExit = g(p, "facts.build.exit_code", null);
|
|
666
|
+
var bVal = bCmd ? esc(bCmd) : '<span class="meta">no build command recorded</span>';
|
|
667
|
+
if (bExit !== null && bExit !== undefined) bVal += ' <span class="meta">exit ' + esc(bExit) + "</span>";
|
|
668
|
+
rows += factRow("Build", bVal, bSt);
|
|
669
|
+
|
|
670
|
+
// tests command + exit code + counts + status.
|
|
671
|
+
var tSt = g(p, "facts.tests.status", "not_run");
|
|
672
|
+
var tCmd = g(p, "facts.tests.command", "");
|
|
673
|
+
var tExit = g(p, "facts.tests.exit_code", null);
|
|
674
|
+
var tPass = g(p, "facts.tests.passed_count", null);
|
|
675
|
+
var tFail = g(p, "facts.tests.failed_count", null);
|
|
676
|
+
var tVal = tCmd ? esc(tCmd) : '<span class="meta">no test command recorded</span>';
|
|
677
|
+
var tMeta = [];
|
|
678
|
+
if (tExit !== null && tExit !== undefined) tMeta.push("exit " + esc(tExit));
|
|
679
|
+
if (tPass !== null && tPass !== undefined) tMeta.push(esc(tPass) + " passed");
|
|
680
|
+
if (tFail !== null && tFail !== undefined && num(tFail) > 0) tMeta.push(esc(tFail) + " failed");
|
|
681
|
+
if (tMeta.length) tVal += ' <span class="meta">' + tMeta.join(", ") + "</span>";
|
|
682
|
+
rows += factRow("Tests", tVal, tSt);
|
|
683
|
+
|
|
684
|
+
// quality gates (each a deterministic status).
|
|
685
|
+
var gates = g(p, "facts.quality_gates", []);
|
|
686
|
+
if (Array.isArray(gates) && gates.length > 0) {
|
|
687
|
+
var chips = "";
|
|
688
|
+
for (var i = 0; i < gates.length; i++) {
|
|
689
|
+
chips += '<span style="margin-right:8px;display:inline-block;">' +
|
|
690
|
+
esc(g(gates[i], "name", "gate")) + " " + statusChip(g(gates[i], "status", "not_run")) +
|
|
691
|
+
"</span>";
|
|
692
|
+
}
|
|
693
|
+
rows += factRow("Quality gates", chips, null);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
card.innerHTML = rows;
|
|
697
|
+
sec.classList.remove("hide");
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
function factRow(k, vHtml, status) {
|
|
701
|
+
var chip = status ? " " + statusChip(status) : "";
|
|
702
|
+
return '<div class="fact-row"><span class="fk">' + esc(k) + "</span>" +
|
|
703
|
+
'<span class="fv">' + vHtml + chip + "</span></div>";
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// Render the ASSESSMENTS: council verdict + completion claim. These are AI
|
|
707
|
+
// judgment, visually marked as opinion, and never contribute to the headline.
|
|
708
|
+
function renderAssessments(p) {
|
|
709
|
+
var sec = document.getElementById("secAssessments");
|
|
710
|
+
var card = document.getElementById("assessmentsCard");
|
|
711
|
+
if (!sec || !card || !isV11(p)) return;
|
|
712
|
+
var html = '<p class="opinion-note"><span aria-hidden="true">!</span>' +
|
|
713
|
+
"These are model opinions about the work, not deterministic proof. A green " +
|
|
714
|
+
"verdict here can still be wrong; trust the Facts above, not these.</p>";
|
|
715
|
+
|
|
716
|
+
// Council verdict (read from assessments.council, mirrored to top-level).
|
|
717
|
+
var enabled = g(p, "assessments.council.enabled", g(p, "council.enabled", false));
|
|
718
|
+
if (enabled) {
|
|
719
|
+
var verdict = g(p, "assessments.council.final_verdict", g(p, "council.final_verdict", ""));
|
|
720
|
+
var reviewers = g(p, "assessments.council.reviewers", g(p, "council.reviewers", []));
|
|
721
|
+
var ok = 0, tot = 0;
|
|
722
|
+
if (Array.isArray(reviewers)) {
|
|
723
|
+
tot = reviewers.length;
|
|
724
|
+
for (var i = 0; i < reviewers.length; i++) {
|
|
725
|
+
var vv = String(g(reviewers[i], "vote", "")).toUpperCase();
|
|
726
|
+
if (vv === "APPROVE" || vv === "APPROVED") ok++;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
var meta = tot > 0 ? (ok + " of " + tot + " approved") : "";
|
|
730
|
+
html += '<div class="fact-row"><span class="fk">Council verdict</span><span class="fv">' +
|
|
731
|
+
(verdict ? '<span class="verdict-badge ' + verdictClass(verdict) + '">' + esc(verdict) + "</span>" : '<span class="meta">not recorded</span>') +
|
|
732
|
+
(meta ? ' <span class="meta">' + meta + "</span>" : "") + "</span></div>";
|
|
733
|
+
} else {
|
|
734
|
+
html += '<div class="fact-row"><span class="fk">Council verdict</span>' +
|
|
735
|
+
'<span class="fv"><span class="meta">council not enabled for this run</span></span></div>';
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// Completion claim (the model's own claim it finished) + evidence-gate verdict.
|
|
739
|
+
var claimed = g(p, "assessments.completion_claim.claimed", false);
|
|
740
|
+
var egVerdict = g(p, "assessments.completion_claim.evidence_gate_verdict", "");
|
|
741
|
+
html += '<div class="fact-row"><span class="fk">Completion claim</span><span class="fv">' +
|
|
742
|
+
(claimed ? "model claimed the work is complete" : "no completion claim recorded") +
|
|
743
|
+
(egVerdict ? ' <span class="meta">evidence gate: ' + esc(egVerdict) + "</span>" : "") +
|
|
744
|
+
"</span></div>";
|
|
745
|
+
|
|
746
|
+
card.innerHTML = html;
|
|
747
|
+
sec.classList.remove("hide");
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// Render the "Verify this yourself" block: the exact deterministic re-check
|
|
751
|
+
// command + the recorded base sha, so a skeptic can re-run the check.
|
|
752
|
+
function renderVerifySelf(p) {
|
|
753
|
+
var sec = document.getElementById("secVerifySelf");
|
|
754
|
+
var card = document.getElementById("verifySelfCard");
|
|
755
|
+
if (!sec || !card || !isV11(p)) return;
|
|
756
|
+
var rid = g(p, "run_id", "");
|
|
757
|
+
var baseSha = g(p, "facts.git.base_sha", "");
|
|
758
|
+
var html = '<p class="note" style="margin-top:0;">This receipt re-verifies. Run the deterministic check ' +
|
|
759
|
+
"below: it re-hashes the receipt for tamper and re-derives the diff from the " +
|
|
760
|
+
"recorded base commit to prove the facts still match the repo.</p>";
|
|
761
|
+
html += cmdBlock("verify", "loki proof verify " + (rid || "<run_id>"));
|
|
762
|
+
var kv = "";
|
|
763
|
+
if (baseSha) kv += '<div class="k">Base commit</div><div class="v">' + esc(baseSha) + "</div>";
|
|
764
|
+
var headSha = g(p, "facts.git.head_sha", "");
|
|
765
|
+
if (headSha) kv += '<div class="k">Head commit</div><div class="v">' + esc(headSha) + "</div>";
|
|
766
|
+
var dsha = g(p, "facts.git.diff_sha256", "");
|
|
767
|
+
if (dsha) kv += '<div class="k">Diff sha256</div><div class="v">' + esc(dsha) + "</div>";
|
|
768
|
+
if (kv) html += '<div class="kv" style="margin-top:14px;">' + kv + "</div>";
|
|
769
|
+
card.innerHTML = html;
|
|
770
|
+
sec.classList.remove("hide");
|
|
771
|
+
}
|
|
772
|
+
|
|
465
773
|
function renderHero(p) {
|
|
466
774
|
// cost.usd is null when cost was not collected for this run. g() coerces
|
|
467
775
|
// null to its default, so read with a null default and branch BEFORE
|