loki-mode 9.16.0 → 9.17.2
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/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/issue-providers.sh +0 -21
- package/autonomy/lib/verdict.py +4 -25
- package/autonomy/loki +4 -190
- package/autonomy/run.sh +64 -167
- package/autonomy/verify.sh +100 -12
- package/completions/_loki +0 -2
- package/completions/loki.bash +1 -1
- package/dashboard/__init__.py +1 -1
- package/dashboard/static/index.html +168 -2
- package/docs/VERIFICATION-COST.md +20 -0
- package/loki-ts/dist/loki.js +215 -215
- package/mcp/__init__.py +1 -1
- package/mcp/_sdk_loader.py +0 -25
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
|
@@ -1323,6 +1323,14 @@
|
|
|
1323
1323
|
<h3 style="font-family: 'Fraunces', Georgia, serif; font-size: 1.15rem; font-weight: 400; color: var(--loki-text-primary); margin-bottom: 12px;">Logs</h3>
|
|
1324
1324
|
<loki-log-stream id="log-stream" auto-scroll max-lines="500"></loki-log-stream>
|
|
1325
1325
|
</div>
|
|
1326
|
+
<!-- LEARNINGS. /api/learnings held real records with a rootCause,
|
|
1327
|
+
a fix and a preventInFuture, and had no UI consumer -- the
|
|
1328
|
+
system was learning from its runs and never showing anyone
|
|
1329
|
+
what it learned. Hidden until data arrives. -->
|
|
1330
|
+
<div id="learnings-panel" style="display:none;">
|
|
1331
|
+
<h3 style="font-family: var(--loki-font-family, 'Inter', system-ui, -apple-system, sans-serif); font-size: 1.15rem; font-weight: 400; color: var(--loki-text-primary); margin-bottom: 12px;">What this build learned</h3>
|
|
1332
|
+
<div id="learnings-list"></div>
|
|
1333
|
+
</div>
|
|
1326
1334
|
<div>
|
|
1327
1335
|
<h3 style="font-family: 'Fraunces', Georgia, serif; font-size: 1.15rem; font-weight: 400; color: var(--loki-text-primary); margin-bottom: 12px;">Memory</h3>
|
|
1328
1336
|
<loki-memory-browser id="memory-browser" tab="summary"></loki-memory-browser>
|
|
@@ -1675,6 +1683,13 @@
|
|
|
1675
1683
|
<div class="section-page-header">
|
|
1676
1684
|
<h2 class="section-page-title">Cost</h2>
|
|
1677
1685
|
</div>
|
|
1686
|
+
<!-- SPEND CAP. /api/budget reports budget_limit, and it ships NULL:
|
|
1687
|
+
there is no automatic spend stop. Nothing in the UI said so, and
|
|
1688
|
+
the only place that fact surfaced was a bill. Shown ALWAYS, not
|
|
1689
|
+
just when a cap exists, because "no cap" is the state a user most
|
|
1690
|
+
needs to know about. -->
|
|
1691
|
+
<div id="budget-banner" style="display:none;margin-bottom:12px;padding:10px 12px;
|
|
1692
|
+
border:1px solid var(--loki-border);border-radius:6px;font-size:12px;"></div>
|
|
1678
1693
|
<loki-cost-dashboard id="cost-dashboard"></loki-cost-dashboard>
|
|
1679
1694
|
</div>
|
|
1680
1695
|
|
|
@@ -1685,8 +1700,18 @@
|
|
|
1685
1700
|
<div class="section-page-header">
|
|
1686
1701
|
<h2 class="section-page-title">Trust Trajectory</h2>
|
|
1687
1702
|
</div>
|
|
1703
|
+
<!-- EVIDENCE RECEIPTS. /api/proofs served 9 receipts with verdict,
|
|
1704
|
+
file count and an HTML view, and NOTHING in the dashboard read it
|
|
1705
|
+
-- only /api/proofs/summary (the header badge) was consumed. The
|
|
1706
|
+
receipt is the thing we ask users to check; it was unreachable
|
|
1707
|
+
from the UI. -->
|
|
1708
|
+
<div id="receipts-panel" style="margin-bottom:16px;display:none;">
|
|
1709
|
+
<h3 style="font-size:13px;font-weight:600;margin:0 0 8px;">Evidence receipts</h3>
|
|
1710
|
+
<div id="receipts-list"></div>
|
|
1711
|
+
<div id="receipts-note" style="font-size:11px;color:var(--loki-text-muted);margin-top:8px;"></div>
|
|
1712
|
+
</div>
|
|
1688
1713
|
<iframe id="trust-frame" title="Trust trajectory" src="about:blank"
|
|
1689
|
-
style="width:100%;height:calc(100vh -
|
|
1714
|
+
style="width:100%;height:calc(100vh - 260px);border:0;border-radius:8px;background:var(--loki-bg-primary);"></iframe>
|
|
1690
1715
|
</div>
|
|
1691
1716
|
|
|
1692
1717
|
<!-- Checkpoints -->
|
|
@@ -11317,7 +11342,7 @@ var LokiDashboard=(()=>{var Be=Object.defineProperty;var zt=Object.getOwnPropert
|
|
|
11317
11342
|
</div>
|
|
11318
11343
|
`}
|
|
11319
11344
|
</div>
|
|
11320
|
-
`,this.shadowRoot.querySelectorAll("[data-tab]").forEach(l=>{l.addEventListener("click",c=>this._handleTabClick(c))});let r=this.shadowRoot.getElementById("tool-time-range");r&&r.addEventListener("change",l=>this._handleTimeRangeChange(l));let o=this.shadowRoot.getElementById("analytics-retry-btn");o&&o.addEventListener("click",()=>this._loadData());let n=this.shadowRoot.getElementById("analytics-overview-btn");n&&n.addEventListener("click",()=>this._navigateToOverview())}_navigateToOverview(){this.dispatchEvent(new CustomEvent("loki-navigate",{detail:{view:"overview"},bubbles:!0,composed:!0}))}};customElements.get("loki-analytics")||customElements.define("loki-analytics",ve);var kt={pass:{color:"var(--loki-green, #22c55e)",bg:"var(--loki-green-muted, rgba(34, 197, 94, 0.15))",label:"PASS"},fail:{color:"var(--loki-red, #ef4444)",bg:"var(--loki-red-muted, rgba(239, 68, 68, 0.15))",label:"FAIL"},pending:{color:"var(--loki-yellow, #eab308)",bg:"var(--loki-yellow-muted, rgba(234, 179, 8, 0.15))",label:"PENDING"}};function Qt(d){if(!d)return"Never";try{return new Date(d).toLocaleString([],{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"})}catch{return"Unknown"}}function Xt(d){if(!d||d.length===0)return{pass:0,fail:0,pending:0,total:0};let e={pass:0,fail:0,pending:0,total:d.length};for(let t of d){let i=(t.status||"pending").toLowerCase();i==="pass"?e.pass++:i==="fail"?e.fail++:e.pending++}return e}var fe=class extends v{static get observedAttributes(){return["api-url","theme"]}constructor(){super(),this._loading=!1,this._error=null,this._api=null,this._gates=[],this._evidence={blocked:!1},this._pollInterval=null,this._lastDataHash=null,this._scanning=!1}connectedCallback(){super.connectedCallback(),this._setupApi(),this._loadData(),this._startPolling()}disconnectedCallback(){super.disconnectedCallback(),this._stopPolling()}attributeChangedCallback(e,t,i){t!==i&&(e==="api-url"&&this._api&&(this._api=h({baseUrl:i}),this._loadData()),e==="theme"&&this._applyTheme())}_setupApi(){let e=this.getAttribute("api-url")||window.location.origin;this._api=h({baseUrl:e})}_startPolling(){this._poll=b({loadFn:()=>this._loadData(),intervalMs:3e4,element:this,immediate:!1})}_stopPolling(){this._poll&&(this._poll.stop(),this._poll=null)}async _loadData(){let e=this._api;try{this._loading=!0;let t=await e._get("/api/council/gate");if(e!==this._api)return;let i=t?.gates||t||[],a=t?.evidence||{blocked:!1},s=JSON.stringify({gates:i,evidence:a});if(
|
|
11345
|
+
`,this.shadowRoot.querySelectorAll("[data-tab]").forEach(l=>{l.addEventListener("click",c=>this._handleTabClick(c))});let r=this.shadowRoot.getElementById("tool-time-range");r&&r.addEventListener("change",l=>this._handleTimeRangeChange(l));let o=this.shadowRoot.getElementById("analytics-retry-btn");o&&o.addEventListener("click",()=>this._loadData());let n=this.shadowRoot.getElementById("analytics-overview-btn");n&&n.addEventListener("click",()=>this._navigateToOverview())}_navigateToOverview(){this.dispatchEvent(new CustomEvent("loki-navigate",{detail:{view:"overview"},bubbles:!0,composed:!0}))}};customElements.get("loki-analytics")||customElements.define("loki-analytics",ve);var kt={pass:{color:"var(--loki-green, #22c55e)",bg:"var(--loki-green-muted, rgba(34, 197, 94, 0.15))",label:"PASS"},fail:{color:"var(--loki-red, #ef4444)",bg:"var(--loki-red-muted, rgba(239, 68, 68, 0.15))",label:"FAIL"},pending:{color:"var(--loki-yellow, #eab308)",bg:"var(--loki-yellow-muted, rgba(234, 179, 8, 0.15))",label:"PENDING"}};function Qt(d){if(!d)return"Never";try{return new Date(d).toLocaleString([],{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"})}catch{return"Unknown"}}function Xt(d){if(!d||d.length===0)return{pass:0,fail:0,pending:0,total:0};let e={pass:0,fail:0,pending:0,total:d.length};for(let t of d){let i=(t.status||"pending").toLowerCase();i==="pass"?e.pass++:i==="fail"?e.fail++:e.pending++}return e}var fe=class extends v{static get observedAttributes(){return["api-url","theme"]}constructor(){super(),this._loading=!1,this._error=null,this._api=null,this._gates=[],this._evidence={blocked:!1},this._policy=null,this._pollInterval=null,this._lastDataHash=null,this._scanning=!1}connectedCallback(){super.connectedCallback(),this._setupApi(),this._loadData(),this._startPolling()}disconnectedCallback(){super.disconnectedCallback(),this._stopPolling()}attributeChangedCallback(e,t,i){t!==i&&(e==="api-url"&&this._api&&(this._api=h({baseUrl:i}),this._loadData()),e==="theme"&&this._applyTheme())}_setupApi(){let e=this.getAttribute("api-url")||window.location.origin;this._api=h({baseUrl:e})}_startPolling(){this._poll=b({loadFn:()=>this._loadData(),intervalMs:3e4,element:this,immediate:!1})}_stopPolling(){this._poll&&(this._poll.stop(),this._poll=null)}_policyLine(e){if(!Array.isArray(this._policy)||!e)return"";let i={"Static Analysis":"static_analysis","Test Suite":"test_coverage","Blind Code Review":"code_review","Anti-Sycophancy":"anti_sycophancy","Mock Integrity":"mock_integrity","Test Mutation":"mutation_integrity","Documentation Coverage":"doc_coverage","Magic Modules Debate":"magic_debate"}[e];if(!i)return"";let a=this._policy.find(l=>l&&l.gate===i);if(!a||!a.mode)return"";let s=a.mode==="blocking",r=a.audit_hits===null||a.audit_hits===void 0?"not measured":`${a.audit_hits} hit${a.audit_hits===1?"":"s"}`,o=s?"BLOCKS":"advisory",n=!s&&a.promote_with?` -- promote with ${this._escapeHtml(a.promote_with)}`:"";return`<div class="gate-meta gate-policy">${o} · ${r}${n}</div>`}async _loadData(){let e=this._api;try{this._loading=!0;let t=await e._get("/api/council/gate");if(e!==this._api)return;let i=t?.gates||t||[],a=t?.evidence||{blocked:!1},s=null;try{let o=await e._get("/api/gate-policy");if(e!==this._api)return;s=Array.isArray(o?.gates)?o.gates:null}catch{s=null}let r=JSON.stringify({gates:i,evidence:a,policy:s});if(r===this._lastDataHash)return;this._lastDataHash=r,this._gates=Array.isArray(i)?i:[],this._evidence=a,this._policy=s,this._error=null}catch(t){if(e!==this._api)return;this._error||(this._error=`Failed to load quality gates: ${t.message}`)}finally{this._loading=!1}this.render()}async _triggerScan(){if(this._scanning)return;let e=this._api;this._scanning=!0,this.render();try{if(await e._post("/api/quality-scan",{},{timeout:3e5}),e!==this._api)return;this._lastDataHash=null,await this._loadData()}catch(t){if(e!==this._api)return;this._error=`Quality scan failed: ${t.message}`}finally{this._scanning=!1,this.render()}}_escapeHtml(e){return e?String(e).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,"""):""}_getStyles(){return`
|
|
11321
11346
|
:host {
|
|
11322
11347
|
display: block;
|
|
11323
11348
|
}
|
|
@@ -11593,6 +11618,7 @@ var LokiDashboard=(()=>{var Be=Object.defineProperty;var zt=Object.getOwnPropert
|
|
|
11593
11618
|
</div>
|
|
11594
11619
|
${p.description?`<div class="gate-description">${this._escapeHtml(p.description)}</div>`:""}
|
|
11595
11620
|
<div class="gate-meta">Last checked: ${Qt(p.last_checked||p.lastChecked)}</div>
|
|
11621
|
+
${this._policyLine(p.name)}
|
|
11596
11622
|
</div>
|
|
11597
11623
|
`}).join("")}</div>`;let r="",o=this._evidence||{};if(o.blocked){let c={empty_diff:"No changes were shipped (empty diff vs run start).",tests_red:"Tests ran and were red.",empty_diff_and_tests_red:"No changes shipped and tests were red.",no_evidence_of_completion:"No evidence of completion."},p=o.error?this._escapeHtml(o.error):c[o.reason]||this._escapeHtml(o.reason||"Completion blocked."),u=Array.isArray(o.failures)?o.failures:[],m=u.length?`<ul class="evidence-failures">${u.map(g=>`<li>${this._escapeHtml(g)}</li>`).join("")}</ul>`:"";r=`
|
|
11598
11624
|
<div class="evidence-banner">
|
|
@@ -16021,6 +16047,143 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
16021
16047
|
badge.classList.add('show');
|
|
16022
16048
|
}
|
|
16023
16049
|
|
|
16050
|
+
// SPEND CAP STATE. budget_limit is NULL by default -- LOKI_BUDGET_LIMIT
|
|
16051
|
+
// ships unset, so a long run has no automatic stop. That is a defensible
|
|
16052
|
+
// default (a run killed at a threshold the user never chose is worse), but
|
|
16053
|
+
// it must not be INVISIBLE: today the only place it surfaced was a bill.
|
|
16054
|
+
//
|
|
16055
|
+
// Renders the no-cap state as prominently as a cap, and names the variable
|
|
16056
|
+
// that sets one. Never invents a number: an unknown current cost reads
|
|
16057
|
+
// "not measured", not $0.00.
|
|
16058
|
+
window.loadBudget = function () {
|
|
16059
|
+
var el = document.getElementById('budget-banner');
|
|
16060
|
+
if (!el) return;
|
|
16061
|
+
fetch('/api/budget', { headers: { 'Accept': 'application/json' } })
|
|
16062
|
+
.then(function (r) { return r.ok ? r.json() : null; })
|
|
16063
|
+
.then(function (d) {
|
|
16064
|
+
if (!d) return;
|
|
16065
|
+
var lim = d.budget_limit;
|
|
16066
|
+
var cur = d.current_cost;
|
|
16067
|
+
var curTxt = (cur === null || cur === undefined) ? 'not measured'
|
|
16068
|
+
: ('$' + Number(cur).toFixed(2));
|
|
16069
|
+
if (lim === null || lim === undefined) {
|
|
16070
|
+
el.style.borderColor = 'var(--loki-warning)';
|
|
16071
|
+
el.innerHTML = '<strong>No spend cap set.</strong> This run will not stop on cost. '
|
|
16072
|
+
+ 'Spent so far: ' + curTxt + '. '
|
|
16073
|
+
+ 'Set one with <code>LOKI_BUDGET_LIMIT=<usd></code>.';
|
|
16074
|
+
} else if (d.exceeded) {
|
|
16075
|
+
el.style.borderColor = 'var(--loki-error)';
|
|
16076
|
+
el.innerHTML = '<strong>Budget exceeded.</strong> Cap $' + Number(lim).toFixed(2)
|
|
16077
|
+
+ ', spent ' + curTxt + '.';
|
|
16078
|
+
} else {
|
|
16079
|
+
var rem = (d.remaining === null || d.remaining === undefined)
|
|
16080
|
+
? 'not measured' : ('$' + Number(d.remaining).toFixed(2));
|
|
16081
|
+
el.style.borderColor = 'var(--loki-border)';
|
|
16082
|
+
el.innerHTML = 'Spend cap $' + Number(lim).toFixed(2)
|
|
16083
|
+
+ '. Spent ' + curTxt + ', remaining ' + rem + '.';
|
|
16084
|
+
}
|
|
16085
|
+
el.style.display = 'block';
|
|
16086
|
+
})
|
|
16087
|
+
.catch(function () { /* leave hidden */ });
|
|
16088
|
+
};
|
|
16089
|
+
|
|
16090
|
+
// LEARNINGS. What the build learned from its own gate failures.
|
|
16091
|
+
//
|
|
16092
|
+
// /api/learnings held real records -- rootCause, fix, preventInFuture --
|
|
16093
|
+
// with no UI consumer. The system was learning from its runs and showing
|
|
16094
|
+
// nobody, which makes the memory unfalsifiable: a user cannot correct a
|
|
16095
|
+
// learning they cannot see. Devin's "misleading knowledge" surface is the
|
|
16096
|
+
// same idea and the reason it is worth showing.
|
|
16097
|
+
//
|
|
16098
|
+
// Renders the record's OWN words. Nothing is summarised or re-derived: a
|
|
16099
|
+
// paraphrased root cause is a second claim about a claim.
|
|
16100
|
+
window.loadLearnings = function () {
|
|
16101
|
+
var panel = document.getElementById('learnings-panel');
|
|
16102
|
+
var list = document.getElementById('learnings-list');
|
|
16103
|
+
if (!panel || !list) return;
|
|
16104
|
+
fetch('/api/learnings', { headers: { 'Accept': 'application/json' } })
|
|
16105
|
+
.then(function (r) { return r.ok ? r.json() : null; })
|
|
16106
|
+
.then(function (d) {
|
|
16107
|
+
if (!d) return;
|
|
16108
|
+
var rows = Array.isArray(d) ? d : (d.learnings || []);
|
|
16109
|
+
if (!rows.length) return; // nothing learned yet: say nothing
|
|
16110
|
+
rows = rows.slice().reverse().slice(0, 8);
|
|
16111
|
+
var html = '';
|
|
16112
|
+
for (var i = 0; i < rows.length; i++) {
|
|
16113
|
+
var x = rows[i] || {};
|
|
16114
|
+
var when = x.timestamp ? String(x.timestamp).slice(0, 16).replace('T', ' ') : '-';
|
|
16115
|
+
var iter = (x.iteration === null || x.iteration === undefined) ? '-' : ('iter ' + x.iteration);
|
|
16116
|
+
html += '<div style="padding:8px;border-bottom:1px solid var(--loki-border);font-size:12px;">'
|
|
16117
|
+
+ '<div style="display:flex;gap:10px;color:var(--loki-text-muted);margin-bottom:4px;">'
|
|
16118
|
+
+ '<span>' + when + '</span><span>' + iter + '</span>'
|
|
16119
|
+
+ '<span>' + String(x.trigger || 'unknown trigger') + '</span></div>'
|
|
16120
|
+
+ '<div style="margin-bottom:3px;"><strong>cause:</strong> '
|
|
16121
|
+
+ String(x.rootCause || 'not recorded') + '</div>'
|
|
16122
|
+
+ (x.fix ? '<div style="margin-bottom:3px;"><strong>fix:</strong> ' + String(x.fix) + '</div>' : '')
|
|
16123
|
+
+ (x.preventInFuture ? '<div style="color:var(--loki-text-muted);"><strong>prevent:</strong> '
|
|
16124
|
+
+ String(x.preventInFuture) + '</div>' : '')
|
|
16125
|
+
+ '</div>';
|
|
16126
|
+
}
|
|
16127
|
+
list.innerHTML = html;
|
|
16128
|
+
panel.style.display = 'block';
|
|
16129
|
+
})
|
|
16130
|
+
.catch(function () { /* leave hidden */ });
|
|
16131
|
+
};
|
|
16132
|
+
|
|
16133
|
+
// RECEIPT LIST. Fetched on demand when the Trust section opens, not on a
|
|
16134
|
+
// timer: it is a history view, and polling it would spend a request every
|
|
16135
|
+
// 30s on data that only changes when a run finishes.
|
|
16136
|
+
//
|
|
16137
|
+
// Every field rendered comes straight from the receipt. Nothing is derived,
|
|
16138
|
+
// and an absent value renders as "-" rather than a zero or a guess -- a
|
|
16139
|
+
// fabricated 0 cost or a blank verdict read as "clean" is the exact false
|
|
16140
|
+
// green the receipt exists to prevent.
|
|
16141
|
+
window.loadReceipts = function () {
|
|
16142
|
+
var panel = document.getElementById('receipts-panel');
|
|
16143
|
+
var list = document.getElementById('receipts-list');
|
|
16144
|
+
var note = document.getElementById('receipts-note');
|
|
16145
|
+
if (!panel || !list) return;
|
|
16146
|
+
fetch('/api/proofs', { headers: { 'Accept': 'application/json' } })
|
|
16147
|
+
.then(function (r) { return r.ok ? r.json() : null; })
|
|
16148
|
+
.then(function (d) {
|
|
16149
|
+
if (!d) return; // endpoint absent: leave hidden
|
|
16150
|
+
var rows = Array.isArray(d) ? d : (d.proofs || d.receipts || []);
|
|
16151
|
+
if (!rows.length) return; // no receipts yet: say nothing
|
|
16152
|
+
rows = rows.slice().reverse().slice(0, 10);
|
|
16153
|
+
var html = '';
|
|
16154
|
+
for (var i = 0; i < rows.length; i++) {
|
|
16155
|
+
var x = rows[i] || {};
|
|
16156
|
+
var verdict = x.headline || x.final_verdict || 'UNKNOWN';
|
|
16157
|
+
var verified = /^VERIFIED/i.test(verdict);
|
|
16158
|
+
var col = verified ? 'var(--loki-success)' : 'var(--loki-text-muted)';
|
|
16159
|
+
var files = (x.files_changed === null || x.files_changed === undefined)
|
|
16160
|
+
? '-' : String(x.files_changed);
|
|
16161
|
+
var cost = (x.cost_usd === null || x.cost_usd === undefined)
|
|
16162
|
+
? '-' : ('$' + Number(x.cost_usd).toFixed(2));
|
|
16163
|
+
var when = x.generated_at ? String(x.generated_at).slice(0, 16).replace('T', ' ') : '-';
|
|
16164
|
+
var link = x.has_html
|
|
16165
|
+
? ('<a href="/api/proofs/' + encodeURIComponent(x.run_id || '') + '/html"'
|
|
16166
|
+
+ ' target="_blank" rel="noopener" style="color:var(--loki-accent);">open</a>')
|
|
16167
|
+
: '<span style="color:var(--loki-text-muted);">no html</span>';
|
|
16168
|
+
html += '<div style="display:flex;gap:12px;align-items:center;padding:6px 8px;'
|
|
16169
|
+
+ 'border-bottom:1px solid var(--loki-border);font-size:12px;">'
|
|
16170
|
+
+ '<span style="color:' + col + ';font-weight:600;min-width:130px;">'
|
|
16171
|
+
+ String(verdict).slice(0, 22) + '</span>'
|
|
16172
|
+
+ '<span style="color:var(--loki-text-muted);min-width:120px;">' + when + '</span>'
|
|
16173
|
+
+ '<span style="min-width:90px;">' + files + ' files</span>'
|
|
16174
|
+
+ '<span style="min-width:70px;">' + cost + '</span>'
|
|
16175
|
+
+ link + '</div>';
|
|
16176
|
+
}
|
|
16177
|
+
list.innerHTML = html;
|
|
16178
|
+
if (note) {
|
|
16179
|
+
note.textContent = 'Newest first. "open" renders the receipt itself. '
|
|
16180
|
+
+ 'Re-check any of them: loki proof verify <run-id>';
|
|
16181
|
+
}
|
|
16182
|
+
panel.style.display = 'block';
|
|
16183
|
+
})
|
|
16184
|
+
.catch(function () { /* leave hidden: no receipt surface is better than a wrong one */ });
|
|
16185
|
+
};
|
|
16186
|
+
|
|
16024
16187
|
function poll() {
|
|
16025
16188
|
fetch('/api/proofs/summary', { headers: { 'Accept': 'application/json' } })
|
|
16026
16189
|
.then(function (r) { return r.ok ? r.json() : null; })
|
|
@@ -16065,7 +16228,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
16065
16228
|
// document and cannot see the SPA's manual data-loki-theme toggle, so we
|
|
16066
16229
|
// pass the resolved theme as a query param (?theme=dark|light); the
|
|
16067
16230
|
// standalone page reads it and matches. v7.18.0.
|
|
16231
|
+
if (sectionId === 'insights') { loadLearnings(); }
|
|
16232
|
+
if (sectionId === 'cost') { loadBudget(); }
|
|
16068
16233
|
if (sectionId === 'trust') {
|
|
16234
|
+
loadReceipts();
|
|
16069
16235
|
var tframe = document.getElementById('trust-frame');
|
|
16070
16236
|
if (tframe && (!tframe.src || tframe.src === 'about:blank' ||
|
|
16071
16237
|
tframe.getAttribute('src') === 'about:blank')) {
|
|
@@ -25,6 +25,8 @@ different answer on your hardware.
|
|
|
25
25
|
| FULL local gate | 23 to 26 minutes | `LOCAL_CI_TIER=full bash scripts/local-ci.sh`, 166 checks, five runs on an M-series Mac |
|
|
26
26
|
| FAST local gate | about 1 minute | `bash scripts/local-ci.sh`, the documented pre-push tier |
|
|
27
27
|
| Shell suite, sharded | 352 seconds | 323 suites, `LOCAL_CI_SHARDS=4`; 1440 seconds serial, so 4.1x |
|
|
28
|
+
| `loki verify` | 77 seconds | `loki verify HEAD~1` on this repository, 5 changed files; dominated by the project's own test suite, not by our gates |
|
|
29
|
+
| of which, shipped-vs-dev CVE split | 418 ms | one extra `npm audit --omit=dev`; 0.5% of the run, and the reason the audit finding can say whether a CVE reaches users |
|
|
28
30
|
| `loki outcomes` | under 1 second per receipt | `git blame` and one `git log` per changed file |
|
|
29
31
|
| `loki intent status` | milliseconds | hash comparison against `.loki/spec/spec.lock` |
|
|
30
32
|
| Agent readiness | milliseconds | filesystem checks only, no network |
|
|
@@ -50,6 +52,24 @@ non-forgeability requires the signed path
|
|
|
50
52
|
(`LOKI_PROOF_GPG_KEY`, see [SIGNED-RECEIPTS.md](SIGNED-RECEIPTS.md)). We removed
|
|
51
53
|
our own "non-forgeable" claim in v7.111.0 after finding it false on that path.
|
|
52
54
|
|
|
55
|
+
**The tests gate depends on correctly identifying YOUR test runner, and it has
|
|
56
|
+
been wrong before.** Until 2026-08-07 the runner was chosen by grepping
|
|
57
|
+
`package.json` for `"jest"` / `"vitest"` / `"mocha"`, which matches a
|
|
58
|
+
**devDependency**. This repository is the case that exposed it: jest is a
|
|
59
|
+
devDependency with no jest config while `scripts.test` runs `bash -n` plus
|
|
60
|
+
`node --test`, so verify ran jest, jest globbed 895 files that are not jest
|
|
61
|
+
tests, and `loki verify` returned BLOCKED on a clean tree -- permanently, for a
|
|
62
|
+
defect that did not exist. It now reads `scripts.test` with a JSON parser and
|
|
63
|
+
runs what the project declares.
|
|
64
|
+
|
|
65
|
+
We record this rather than quietly fixing it because a false BLOCK is the more
|
|
66
|
+
damaging direction of that error: a gate that cries wolf on every run trains
|
|
67
|
+
you to ignore the verdict, which costs more than the gate ever earned. If the
|
|
68
|
+
tests gate reports a runner you do not use, that is a bug in our detection, not
|
|
69
|
+
a finding about your code -- the runner is named in
|
|
70
|
+
`.loki/verify/evidence.json` under `deterministic_gates[].runner` so you can
|
|
71
|
+
check which one it picked.
|
|
72
|
+
|
|
53
73
|
**Only four of the eight quality gates are agent-independent.** Static analysis,
|
|
54
74
|
mock-integrity, test-mutation and documentation coverage do not ask a model
|
|
55
75
|
anything. The other four involve model judgment and are labelled ASSESSMENTS
|