loki-mode 11.54.3 → 11.55.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/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/lib/fast_verify.py +142 -18
- package/dashboard/__init__.py +1 -1
- package/docs/CLI-REFERENCE.md +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/docs/v10/FAILURE-CLASSES.md +13 -4
- package/loki-ts/dist/loki.js +1047 -1043
- package/mcp/__init__.py +1 -1
- package/package.json +2 -2
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/web-app/dist/assets/{AdminPage-Dmm3-KKX.js → AdminPage-BMe5dzyh.js} +1 -1
- package/web-app/dist/assets/{Avatar-7RrgWzKi.js → Avatar-DyXOOsPy.js} +1 -1
- package/web-app/dist/assets/{Badge-BONIA7yv.js → Badge-77uqgz2B.js} +1 -1
- package/web-app/dist/assets/{Button-E8PRMj4w.js → Button-r0OlMu23.js} +1 -1
- package/web-app/dist/assets/{CockpitPage-DktiMnMn.js → CockpitPage-HYcVxTky.js} +1 -1
- package/web-app/dist/assets/{ComparePage-Bv6TXlpZ.js → ComparePage-CTYq0eJh.js} +1 -1
- package/web-app/dist/assets/{ErrorBoundary-D4Q4NyU6.js → ErrorBoundary-O-uINmwk.js} +1 -1
- package/web-app/dist/assets/{EvidenceReceiptPanel-D-4aSIOD.js → EvidenceReceiptPanel-COZsfe8y.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-pyZB0ppB.js → GitHubIssuesPanel-ZYX-PcG9.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-C8JzCo4i.js → GitHubPRsPanel-BqnF3V-C.js} +1 -1
- package/web-app/dist/assets/{HomePage-8_qQ6qAA.js → HomePage-Dc-S3z3k.js} +2 -2
- package/web-app/dist/assets/{LoginPage-BeHT-Edx.js → LoginPage-B18TgU21.js} +1 -1
- package/web-app/dist/assets/{MagicPage-CX4ztJG-.js → MagicPage-BChdKANc.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-vskkOC9J.js → MetricsPage-BeO8E8J4.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-BGP_FwcI.js → NotFoundPage-ZPGBstWe.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-4kWMfATR.js → ProjectPage-Ch1ERk3k.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage-BLHLB1XV.js → ProjectsPage-DS-iCrWH.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-D4LsHn8x.js → SettingsPage-C7Zfi6WQ.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-DfYFn_YN.js → ShowcasePage-D6Jzs3xn.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-CKAavyvT.js → SystemSettingsPage-BUToGQOi.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-CC4AMNbY.js → TeamsPage-Ca-imiWe.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-DdMm9p_0.js → TemplatesPage-Cj4PDYvE.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-utetHzR1.js → TerminalOutput-OlfjIMaP.js} +1 -1
- package/web-app/dist/assets/{activity-D8bj2llh.js → activity-DGco0i8E.js} +1 -1
- package/web-app/dist/assets/{bell-ncgZ86_g.js → bell-CXrA_z4D.js} +1 -1
- package/web-app/dist/assets/{bot-BAiWhj3k.js → bot-C3bPbwTv.js} +1 -1
- package/web-app/dist/assets/{check-B98KRRY6.js → check-BH95oBUW.js} +1 -1
- package/web-app/dist/assets/{chevron-left-BeHgro_s.js → chevron-left-DkDiAgdy.js} +1 -1
- package/web-app/dist/assets/{circle-alert-D11Q4DJ2.js → circle-alert-DVm1iRCr.js} +1 -1
- package/web-app/dist/assets/{clock-DII7tRwl.js → clock-B9nq6lv2.js} +1 -1
- package/web-app/dist/assets/{cloud-BiVq9O0S.js → cloud--spL7-5c.js} +1 -1
- package/web-app/dist/assets/{code-xml-CbtEo2gf.js → code-xml-5dQ-OP-m.js} +1 -1
- package/web-app/dist/assets/{database-vSiFIjq7.js → database-Irge9nDf.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-Crrvv0LV.js → dollar-sign-C-fOwQhD.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-BcR71Niu.js → file-code-corner-DL6DXUq9.js} +1 -1
- package/web-app/dist/assets/{file-plus-rt0PxW5E.js → file-plus-CpD_jH9N.js} +1 -1
- package/web-app/dist/assets/{globe-FGzbkm5z.js → globe-75e1X6Qk.js} +1 -1
- package/web-app/dist/assets/{hammer-BlW5Aict.js → hammer-DVmWc_X3.js} +1 -1
- package/web-app/dist/assets/{index-DQDv_Igi.js → index-9vlQVMHV.js} +3 -3
- package/web-app/dist/assets/{layers-Btfkr8AR.js → layers-C_Ac7df5.js} +1 -1
- package/web-app/dist/assets/{loader-circle-DNksvTjl.js → loader-circle-Dwd40Yli.js} +1 -1
- package/web-app/dist/assets/{lock-CK3M3U8a.js → lock-B1QdrHw-.js} +1 -1
- package/web-app/dist/assets/{package-CAw54KBn.js → package-BIdnnta_.js} +1 -1
- package/web-app/dist/assets/{plus-Dc9lYqth.js → plus-djvMCLRk.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-BBc-MpJ2.js → refresh-cw-CVw1ki2J.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-u9JmT2vq.js → rotate-ccw-BZuw7sZD.js} +1 -1
- package/web-app/dist/assets/{scroll-text-ZcPT96o0.js → scroll-text-BSIjb7vz.js} +1 -1
- package/web-app/dist/assets/{server-mJuRWb2u.js → server-CTzeJRJ1.js} +1 -1
- package/web-app/dist/assets/{shield-alert-B_KS9HMO.js → shield-alert-DAdTZIbR.js} +1 -1
- package/web-app/dist/assets/{thumbs-up-DjZsZxxg.js → thumbs-up-D6dHPYAF.js} +1 -1
- package/web-app/dist/assets/{trash-2-0A5x-xNv.js → trash-2-B1iUttVJ.js} +1 -1
- package/web-app/dist/assets/{trending-down-CE52PXTy.js → trending-down-B43IPcHY.js} +1 -1
- package/web-app/dist/assets/{trending-up-D6TRDISW.js → trending-up-Car5b0e9.js} +1 -1
- package/web-app/dist/assets/{upload-QRZTlnwD.js → upload-xtQ8SdqH.js} +1 -1
- package/web-app/dist/assets/{usePolling-B6KSIqY4.js → usePolling-CjBn839g.js} +1 -1
- package/web-app/dist/assets/{user-CDwLK_9N.js → user-BYZErN7w.js} +1 -1
- package/web-app/dist/index.html +1 -1
package/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: loki-mode
|
|
|
3
3
|
description: Runs an autonomous software factory that knows what it is supposed to deliver and proves it did, with a signed receipt. Use when the user says Loki Mode or asks to build, fix or verify software autonomously.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Loki Mode v11.
|
|
6
|
+
# Loki Mode v11.55.0
|
|
7
7
|
|
|
8
8
|
**Current entry points (Loki 10 engine):** `loki "<task>"`, `loki owner/repo#N` and `loki quick "<task>"` run the Loki 10 engine and end with a signed receipt (`loki verify`). `loki backlog owner/repo --all` runs many issues in parallel. Guide: `docs/v10/GUIDE.md`. The skill instructions below, and `loki start`, describe the previous bash loop. PRD-file starts (`loki start ./prd.md`) and the opencode provider still run on it until FC38-SWEEP ports them; the legacy-engine command and the LOKI_ENGINE switch remain removed. Prefer `loki owner/repo#N` for issues.
|
|
9
9
|
|
|
@@ -476,4 +476,4 @@ Implemented, planned and deprecated items, with the version each landed in: `ski
|
|
|
476
476
|
|
|
477
477
|
---
|
|
478
478
|
|
|
479
|
-
**v11.
|
|
479
|
+
**v11.55.0 | [Autonomi](https://www.autonomi.dev/) flagship product | core skill**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
11.
|
|
1
|
+
11.55.0
|
|
@@ -42,11 +42,13 @@ WHAT THIS DELIBERATELY DOES NOT DO
|
|
|
42
42
|
from __future__ import annotations
|
|
43
43
|
|
|
44
44
|
import hashlib
|
|
45
|
+
import hmac
|
|
45
46
|
import json
|
|
46
47
|
import os
|
|
47
48
|
import re
|
|
48
49
|
import subprocess
|
|
49
50
|
import sys
|
|
51
|
+
import tempfile
|
|
50
52
|
import time
|
|
51
53
|
from dataclasses import dataclass, field, asdict
|
|
52
54
|
from pathlib import Path
|
|
@@ -210,43 +212,165 @@ def discover(root: str, diff_base: str = "") -> list:
|
|
|
210
212
|
|
|
211
213
|
# --- Content-addressed cache -------------------------------------------------
|
|
212
214
|
|
|
215
|
+
def _producer_fingerprint() -> str | None:
|
|
216
|
+
"""Digest of this file's own bytes. Every cached verdict is bound to the
|
|
217
|
+
exact rules that produced it, so any rule edit orphans old entries with no
|
|
218
|
+
hand-bumped version to forget. None when the source cannot be read, in
|
|
219
|
+
which case the cache is off."""
|
|
220
|
+
try:
|
|
221
|
+
with open(os.path.abspath(__file__), "rb") as fh:
|
|
222
|
+
return hashlib.blake2b(fh.read(), digest_size=16).hexdigest()
|
|
223
|
+
except OSError:
|
|
224
|
+
return None
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
PRODUCER = _producer_fingerprint()
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def cache_key(rel: str, raw: bytes) -> str:
|
|
231
|
+
"""A verdict depends on the rules, the path (is_test selects which rules
|
|
232
|
+
run) and the content, so all three are in the key."""
|
|
233
|
+
h = hashlib.blake2b(digest_size=16)
|
|
234
|
+
h.update((PRODUCER or "").encode("ascii"))
|
|
235
|
+
h.update(b"\0")
|
|
236
|
+
h.update(rel.encode("utf-8", "replace"))
|
|
237
|
+
h.update(b"\0")
|
|
238
|
+
h.update(raw)
|
|
239
|
+
return h.hexdigest()
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def cache_dir() -> str:
|
|
243
|
+
"""User-owned cache root, outside every repo the agent works in
|
|
244
|
+
(LOKI_FAST_VERIFY_CACHE_DIR overrides it, for tests and sandboxes)."""
|
|
245
|
+
return os.environ.get("LOKI_FAST_VERIFY_CACHE_DIR") or os.path.join(
|
|
246
|
+
os.path.expanduser("~"), ".loki", "cache", "fast-verify")
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def cache_path(root: str) -> str:
|
|
250
|
+
tag = hashlib.blake2b(os.path.realpath(root).encode("utf-8", "replace"), digest_size=8).hexdigest()
|
|
251
|
+
return os.path.join(cache_dir(), tag + ".json")
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _private_dir(path: str) -> bool:
|
|
255
|
+
"""Create the cache dir and force it to 0700, including a pre-existing one
|
|
256
|
+
created loose. A dir this user cannot chmod is not ours: refuse it."""
|
|
257
|
+
try:
|
|
258
|
+
os.makedirs(path, mode=0o700, exist_ok=True)
|
|
259
|
+
os.chmod(path, 0o700)
|
|
260
|
+
return True
|
|
261
|
+
except OSError:
|
|
262
|
+
return False
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _write_private(path: str, data: bytes, replace: bool) -> None:
|
|
266
|
+
"""Write data to a 0600 temp file in the same dir, then publish it
|
|
267
|
+
atomically: os.replace, or os.link so an existing file is never clobbered
|
|
268
|
+
and no reader sees a partial one."""
|
|
269
|
+
fd, tmp = tempfile.mkstemp(dir=os.path.dirname(path), prefix=".tmp-")
|
|
270
|
+
try:
|
|
271
|
+
os.fchmod(fd, 0o600)
|
|
272
|
+
with os.fdopen(fd, "wb") as fh:
|
|
273
|
+
fh.write(data)
|
|
274
|
+
if replace:
|
|
275
|
+
os.replace(tmp, path)
|
|
276
|
+
else:
|
|
277
|
+
os.link(tmp, path)
|
|
278
|
+
finally:
|
|
279
|
+
try:
|
|
280
|
+
os.unlink(tmp)
|
|
281
|
+
except OSError:
|
|
282
|
+
pass
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _read_key(path: str) -> bytes | None:
|
|
286
|
+
try:
|
|
287
|
+
st = os.stat(path)
|
|
288
|
+
if st.st_mode & 0o077 or st.st_uid != os.getuid():
|
|
289
|
+
return None # a key others could read or write proves nothing
|
|
290
|
+
with open(path, "rb") as fh:
|
|
291
|
+
key = fh.read()
|
|
292
|
+
return key if len(key) >= 32 else None
|
|
293
|
+
except OSError:
|
|
294
|
+
return None
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _cache_secret() -> bytes | None:
|
|
298
|
+
"""Per-user HMAC key kept beside the cache, never inside the repo. None when
|
|
299
|
+
it can be neither read nor created, in which case the cache is off."""
|
|
300
|
+
if PRODUCER is None or not _private_dir(cache_dir()):
|
|
301
|
+
return None
|
|
302
|
+
path = os.path.join(cache_dir(), "key")
|
|
303
|
+
key = _read_key(path)
|
|
304
|
+
if key is not None:
|
|
305
|
+
return key
|
|
306
|
+
try:
|
|
307
|
+
_write_private(path, os.urandom(32), replace=False)
|
|
308
|
+
except FileExistsError:
|
|
309
|
+
pass
|
|
310
|
+
except OSError:
|
|
311
|
+
return None
|
|
312
|
+
return _read_key(path)
|
|
313
|
+
|
|
314
|
+
|
|
213
315
|
class Cache:
|
|
214
316
|
"""Findings keyed by content hash, so unchanged files are never re-read.
|
|
215
317
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
318
|
+
The key covers the producing rules (this file's own digest), the path and
|
|
319
|
+
the content, never mtime, so there is no staleness window and a verdict
|
|
320
|
+
cannot outlive a rule change or follow a file to a path where other rules
|
|
321
|
+
apply. Content addressing does not make an entry trustworthy
|
|
322
|
+
on its own: the key is computable by anyone, so an entry written by the
|
|
323
|
+
agent under review could suppress a finding. Each entry therefore carries
|
|
324
|
+
an HMAC under a per-user secret held outside the repo, and an entry whose
|
|
325
|
+
MAC does not verify is a miss: the file is rescanned. Without a secret
|
|
326
|
+
the cache is disabled.
|
|
220
327
|
"""
|
|
221
328
|
|
|
222
329
|
def __init__(self, path: str):
|
|
223
330
|
self.path = path
|
|
224
331
|
self.data = {}
|
|
225
332
|
self.hits = 0
|
|
333
|
+
self.secret = _cache_secret()
|
|
334
|
+
if self.secret is None:
|
|
335
|
+
return
|
|
226
336
|
try:
|
|
227
337
|
with open(path, encoding="utf-8") as fh:
|
|
228
338
|
blob = json.load(fh)
|
|
229
|
-
if isinstance(blob, dict) and blob.get("
|
|
230
|
-
|
|
339
|
+
if isinstance(blob, dict) and blob.get("producer") == PRODUCER:
|
|
340
|
+
entries = blob.get("entries", {})
|
|
341
|
+
if isinstance(entries, dict):
|
|
342
|
+
self.data = entries
|
|
231
343
|
except (OSError, ValueError):
|
|
232
344
|
self.data = {}
|
|
233
345
|
|
|
346
|
+
def _mac(self, key: str, findings: list) -> str:
|
|
347
|
+
body = json.dumps([PRODUCER, key, findings], sort_keys=True, separators=(",", ":"))
|
|
348
|
+
return hmac.new(self.secret, body.encode("utf-8"), "sha256").hexdigest()
|
|
349
|
+
|
|
234
350
|
def get(self, key: str):
|
|
235
|
-
|
|
236
|
-
if
|
|
237
|
-
|
|
238
|
-
|
|
351
|
+
entry = self.data.get(key)
|
|
352
|
+
if self.secret is None or not isinstance(entry, dict):
|
|
353
|
+
return None
|
|
354
|
+
findings, mac = entry.get("findings"), entry.get("mac")
|
|
355
|
+
if not isinstance(findings, list) or not isinstance(mac, str):
|
|
356
|
+
return None
|
|
357
|
+
if not hmac.compare_digest(mac, self._mac(key, findings)):
|
|
358
|
+
return None
|
|
359
|
+
self.hits += 1
|
|
360
|
+
return findings
|
|
239
361
|
|
|
240
362
|
def put(self, key: str, findings: list) -> None:
|
|
241
|
-
self.
|
|
363
|
+
if self.secret is not None:
|
|
364
|
+
self.data[key] = {"findings": findings, "mac": self._mac(key, findings)}
|
|
242
365
|
|
|
243
366
|
def save(self) -> None:
|
|
367
|
+
if self.secret is None:
|
|
368
|
+
return
|
|
244
369
|
try:
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
os.replace(tmp, self.path)
|
|
370
|
+
if not _private_dir(os.path.dirname(self.path)):
|
|
371
|
+
return
|
|
372
|
+
blob = json.dumps({"producer": PRODUCER, "entries": self.data})
|
|
373
|
+
_write_private(self.path, blob.encode("utf-8"), replace=True)
|
|
250
374
|
except OSError:
|
|
251
375
|
pass # a cache that cannot be written must never break a verdict
|
|
252
376
|
|
|
@@ -256,7 +380,7 @@ class Cache:
|
|
|
256
380
|
def verify(root: str = ".", diff_base: str = "", use_cache: bool = True) -> Result:
|
|
257
381
|
t0 = time.perf_counter()
|
|
258
382
|
root = os.path.abspath(root)
|
|
259
|
-
cache = Cache(
|
|
383
|
+
cache = Cache(cache_path(root)) if use_cache else None
|
|
260
384
|
|
|
261
385
|
findings = []
|
|
262
386
|
scanned = 0
|
|
@@ -270,7 +394,7 @@ def verify(root: str = ".", diff_base: str = "", use_cache: bool = True) -> Resu
|
|
|
270
394
|
continue
|
|
271
395
|
scanned += 1
|
|
272
396
|
|
|
273
|
-
key =
|
|
397
|
+
key = cache_key(rel, raw)
|
|
274
398
|
if cache is not None:
|
|
275
399
|
hit = cache.get(key)
|
|
276
400
|
if hit is not None:
|
package/dashboard/__init__.py
CHANGED
package/docs/CLI-REFERENCE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# CLI reference
|
|
2
2
|
|
|
3
|
-
Generated from `loki-ts/src/cli/registry.ts` by `scripts/generate-stale-zero.sh` for v11.
|
|
3
|
+
Generated from `loki-ts/src/cli/registry.ts` by `scripts/generate-stale-zero.sh` for v11.55.0. Do not edit by hand.
|
|
4
4
|
Hidden and legacy-only commands are omitted; see `docs/v10/CLI-MODERN.md` for the full inventory.
|
|
5
5
|
|
|
6
6
|
| Command | Description |
|
package/docs/INSTALLATION.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Loki Mode, by [Autonomi](https://www.autonomi.dev/), turns a spec or an issue into a verified pull request with a receipt you can check. This page covers installing it, a first run, and the options around it.
|
|
4
4
|
|
|
5
|
-
**Version:** v11.
|
|
5
|
+
**Version:** v11.55.0
|
|
6
6
|
|
|
7
7
|
**Engine note:** `loki start` is the primary command. `loki start`, `loki "<task>"`, `loki owner/repo#N` and `loki quick` all run the Loki 10 engine, whatever the flag order or input form (PRD file, issue ref, task or one-word brief). The previous loop runs only on `--engine legacy`, `LOKI_ENGINE=legacy`, or for one call when a flag Loki 10 cannot honor is present. See the [user guide](v10/GUIDE.md) and the [5-minute quickstart](QUICKSTART.md).
|
|
8
8
|
|
|
@@ -1417,9 +1417,18 @@ L0 review rule (ENGINE-LAWS.md L0): a fix that adds an `if` or a regex about the
|
|
|
1417
1417
|
- Fixture: loki-ts/tests/engine10/seal_group.test.ts "#317: local HEAD ahead of origin/HEAD, every unit starts at the journal-key base". Red before the fix (unit base undefined), green after.
|
|
1418
1418
|
|
|
1419
1419
|
## FC-TRUSTED-CACHE-VERDICT fast_verify trusts an agent-writable content-hash cache as a fresh scan, so a planted entry (an empty list suppressing a high finding, or injected finding text) decides the verdict
|
|
1420
|
-
- User-visible vs raw: user-visible. A planted `.loki/cache/fast-verify.json` entry keyed by a file's content hash is returned as that file's findings without rescanning. `[]` makes a file with a high finding PASS; a crafted message reaches the MCP client as trusted tool output.
|
|
1420
|
+
- User-visible vs raw: user-visible. A planted `.loki/cache/fast-verify.json` entry keyed by a file's content hash is returned as that file's findings without rescanning. `[]` makes a file with a high finding PASS; a crafted message reaches the MCP client as trusted tool output. Closed by #319.
|
|
1421
1421
|
- Law: a verdict is derived from the content being judged, never from a file the judged agent can write (EL exogenous gates).
|
|
1422
1422
|
- Cause (measured): autonomy/lib/fast_verify.py Cache reads entries by blake2b(content) and returns them unvalidated; mcp/server.py listed loki_verify_fast in _TRUSTED_TOOLS so its finding text skipped the untrusted envelope.
|
|
1423
|
-
- Siblings swept
|
|
1424
|
-
- Mechanism:
|
|
1425
|
-
-
|
|
1423
|
+
- Siblings swept (grep for cache files under .loki and ~/.loki in loki-ts/src, autonomy, mcp): engine10/cache.ts repomap/testmap cache lives under ~/.loki/cache/v10, outside the repo, keyed by tree hash, no MAC (same-user write is out of this row's threat model; it selects tests, it does not return findings); run.sh `.loki/cache/install` is in-repo and agent-writable but stores node_modules, not a verdict; router history and update_check feed estimates and notices, not a verdict. No other in-repo cache returns findings. Every MCP tool and resource is covered by the runtime-derived guard in tests/mcp/test_phase1_tools.py.
|
|
1424
|
+
- Mechanism: two layers. (1) loki_verify_fast findings sit under untrusted_content via _UNTRUSTED_FIELDS. (2) fast_verify.Cache lives at ~/.loki/cache/fast-verify/<realpath hash>.json (LOKI_FAST_VERIFY_CACHE_DIR overrides), and each entry carries an HMAC under a per-user 0600 key beside it. The cache key and the MAC body are derived from everything the verdict depends on: a producer fingerprint (blake2b of fast_verify.py's own bytes, computed at import, so any rule edit orphans every old entry and there is no hand-bumped schema constant), the repo-relative path (is_test decides which rules run, so a file moved from a test path to a product path is rescanned), and the content. An entry whose MAC fails is a miss and the file is rescanned; with no readable key, no readable source or an unusable cache dir the cache is off. The key is created through a 0600 temp file and os.link (never clobbered, never partial), the cache dir is forced to 0700 even when it pre-exists loose, a key that is group or other accessible or not owned by the user is refused, and the cache json is written at 0600 through a temp file and os.replace. The old repo-local `.loki/cache/fast-verify.json` is never read. Closes #319.
|
|
1425
|
+
- Trust boundary: closed for repo content and for any write the judged agent makes inside the repo. NOT closed against a same-user process that can read ~/.loki/cache/fast-verify/key: it can forge a valid MAC. A same-user agent that is allowed to read the home directory therefore defeats this cache; the only complete answer for that threat is --no-cache (the fast scan is milliseconds), which the exogenous gates should use.
|
|
1426
|
+
- Sibling not closed (engine10 cache): loki-ts/src/engine10/cache.ts keys repomap and testmap by HEAD^{tree} plus a hand-bumped "repomap2-" prefix, so a builder change does not invalidate stored maps (same class as the hand-bumped CACHE_SCHEMA removed here). It selects tests and does not return findings, so it is not a verdict, but the fix is the same idea: derive the builder version from the builder's bytes at build time instead of a manual prefix. Tracked as a follow-up, not changed here because the TS bundle has no stable source path to hash at runtime.
|
|
1427
|
+
- Fixture: tests/mcp/test_phase1_tools.py test_stored_text_appears_only_inside_untrusted_content (red with loki_verify_fast trusted: leaks ['loki_verify_fast']; green after) and tests/test-fast-verify.sh CACHE BINDING block (planted repo-local and forged-MAC empty-list entries; red when the MAC check is replaced by `if False:`, green with it), plus the CACHE KEY and CACHE FILES blocks there: a test-path file moved to a product path must FAIL (red when the path is dropped from cache_key), a copy of fast_verify.py with disabled rules must not poison the real rules' cache (red when PRODUCER is a fixed string), the key and cache json are 0600 and the dir 0700 (red when the key is written 0666), and no verify cache file appears in a repo (red when cache_path points at root/.loki/cache/fast-verify.json). Tests that run fast_verify set LOKI_FAST_VERIFY_CACHE_DIR to a run tmp or pass --no-cache, never the real ~/.loki.
|
|
1428
|
+
|
|
1429
|
+
## FC-CATALOG-CAPABILITY-DRIFT-UNCHECKED A catalog entry whose capabilities drifted from the API failed only at request time
|
|
1430
|
+
- User-visible: a catalog entry whose thinking types or effort levels drifted from what the model accepts fails only at request time, as an API error mid-run. Raw: the catalog (providers/model_catalog.json) is static and never compared with models.retrieve.
|
|
1431
|
+
- Law broken: a capability the harness relies on is claimed without being checked against its source of truth.
|
|
1432
|
+
- Siblings swept: effortForTier clamp (bash and Bun, parity-checked), claudeEffortLevels, the catalog freshness row (age only, offline).
|
|
1433
|
+
- Mechanism: loki doctor "Model capabilities" row. With ANTHROPIC_API_KEY it calls models.retrieve per claude catalog id and prints each mismatch (effort levels, API-expressible thinking types); without a key or offline it warns and never changes the tally or exit code. The bench runner takes --effort sweep, expanding to the catalog levels for --model.
|
|
1434
|
+
- Fixture: loki-ts/tests/commands/doctor.test.ts "model capability row", tests/test_bench_runner.py sweep tests, tests/test-bash-bun-parity.sh effort clamp cells. Red when the comparison, the sweep catalog read or the clamp model argument is removed.
|