loki-mode 11.17.1 → 11.17.6
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 +3 -3
- package/VERSION +1 -1
- package/autonomy/grill.sh +24 -2
- package/autonomy/hooks/precompact-flush.sh +51 -0
- package/autonomy/hooks/validate-bash.sh +895 -5
- package/autonomy/lib/chat.sh +8 -3
- package/autonomy/lib/claude-flags.sh +143 -6
- package/autonomy/lib/claude_permissions.py +24 -4
- package/autonomy/lib/decision_record.py +92 -3
- package/autonomy/lib/efficiency_cost.py +102 -0
- package/autonomy/lib/modernize/py_capture.py +62 -3
- package/autonomy/lib/prd-enrich.sh +28 -2
- package/autonomy/lib/voter-agents.sh +61 -6
- package/autonomy/lib/wiki-generator.py +58 -10
- package/autonomy/lib/wiki_llm.py +41 -2
- package/autonomy/loki +391 -28
- package/autonomy/quickstart.sh +1 -1
- package/autonomy/run.sh +201 -10
- package/autonomy/trigger-server.py +70 -2
- package/completions/_loki +3 -0
- package/completions/loki.bash +2 -1
- package/dashboard/__init__.py +1 -1
- package/dashboard/audit.py +204 -17
- package/docs/CLI-REFERENCE.md +4 -1
- package/docs/EVALUATING.md +2 -2
- package/docs/INSTALLATION.md +1 -1
- package/docs/environment-variables.md +1 -0
- package/docs/trackers.md +6 -0
- package/docs/v10/BOARD.md +7 -4
- package/docs/v10/CLI-MODERN.md +8 -5
- package/docs/v10/DECISIONS.md +31 -1
- package/docs/v10/ENGINE.md +1 -0
- package/docs/v10/FAILURE-CLASSES.md +60 -1
- package/docs/v10/FOUNDER-QUEUE.md +1 -0
- package/docs/v10/GUIDE-MODERNIZE.md +9 -4
- package/docs/v10/PLAN-V12.md +896 -0
- package/docs/v10/PROGRESS.md +10 -0
- package/docs/v11/COST-HALF.md +3 -1
- package/docs/visual-evidence.md +2 -0
- package/loki-ts/dist/loki.js +1104 -1057
- package/mcp/__init__.py +1 -1
- package/memory/retrieval.py +15 -3
- package/memory/schemas.py +43 -1
- package/memory/storage.py +11 -0
- package/package.json +2 -2
- package/packages/control-plane/dist/server.js +989 -693
- package/packages/control-plane/src/server/legacy/routes-control.ts +5 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/providers/claude.sh +1 -0
- package/providers/loader.sh +26 -0
- package/providers/model_routes.sh +31 -0
- package/src/policies/claude_permissions.js +20 -2
- package/web-app/dist/assets/{AdminPage-OVzN6eLm.js → AdminPage-CCedYmn3.js} +1 -1
- package/web-app/dist/assets/{Avatar-hCCrxrCZ.js → Avatar-DMrDsuGe.js} +1 -1
- package/web-app/dist/assets/{Badge-CDcpkhbJ.js → Badge-K4Ui2tJz.js} +1 -1
- package/web-app/dist/assets/{Button-8mwWlhEu.js → Button-Bhjts5QZ.js} +1 -1
- package/web-app/dist/assets/{CockpitPage-D8cf3u22.js → CockpitPage-Be7sEVqh.js} +1 -1
- package/web-app/dist/assets/{ComparePage-CIgkbDRY.js → ComparePage-D4ty3jiF.js} +1 -1
- package/web-app/dist/assets/{ErrorBoundary-99kJH9g2.js → ErrorBoundary-Cmt2x_mL.js} +1 -1
- package/web-app/dist/assets/{EvidenceReceiptPanel-C9fj32eS.js → EvidenceReceiptPanel-D2IXLnp9.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-De43YQ_C.js → GitHubIssuesPanel-CyXitr6h.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-7g7ZTnkx.js → GitHubPRsPanel-RRM3H-xd.js} +1 -1
- package/web-app/dist/assets/{HomePage-CcNA24ny.js → HomePage-CMtrrgfs.js} +2 -2
- package/web-app/dist/assets/{LoginPage-BYjlb-IK.js → LoginPage-BrzNhnMJ.js} +1 -1
- package/web-app/dist/assets/{MagicPage-C0Dwt9Na.js → MagicPage-D7VynOKM.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-jDGmdcgg.js → MetricsPage-DJ-gDbfP.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-C_ZO8tLE.js → NotFoundPage-BJh0XVTj.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-B5wnurfQ.js → ProjectPage-CAodVUpE.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage--EyKt5-D.js → ProjectsPage-m3siS0Rf.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-cB0lnfti.js → SettingsPage-CFGgjOvy.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-Dl16FDkK.js → ShowcasePage-Dd9tpSKJ.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-BmAXd4V2.js → SystemSettingsPage-DXd0PtTI.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-BFkWVOZe.js → TeamsPage-C5KaLIGW.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-Bf2kmEUM.js → TemplatesPage-CDbzE0ZZ.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-BItv9Wa6.js → TerminalOutput-CKG79r_-.js} +1 -1
- package/web-app/dist/assets/{activity-Df2Awg6I.js → activity-BaQQRhDp.js} +1 -1
- package/web-app/dist/assets/{bell-D-1ztTsJ.js → bell-OeFqvU4e.js} +1 -1
- package/web-app/dist/assets/{bot-A4qgZsKi.js → bot-CSxaA0KU.js} +1 -1
- package/web-app/dist/assets/{check-BPfFpF0c.js → check-DJml2Xdn.js} +1 -1
- package/web-app/dist/assets/{chevron-left-CfSJvmDi.js → chevron-left-DGnt-DtK.js} +1 -1
- package/web-app/dist/assets/{circle-alert-wx2d1dzb.js → circle-alert-BV5rVL7K.js} +1 -1
- package/web-app/dist/assets/{clock-pQMai6l-.js → clock-CZ7SvULz.js} +1 -1
- package/web-app/dist/assets/{cloud-DWL470r6.js → cloud-lkIzrZps.js} +1 -1
- package/web-app/dist/assets/{code-xml-BaEQpQLZ.js → code-xml-DzA2wL8F.js} +1 -1
- package/web-app/dist/assets/{database-h-N7WkQN.js → database-CLFG5upC.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-B5oYiD27.js → dollar-sign-DgWePGsv.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-fbTtTy0o.js → file-code-corner-DGQX2bmv.js} +1 -1
- package/web-app/dist/assets/{file-plus-PaF6HMo8.js → file-plus-LkaXeYd6.js} +1 -1
- package/web-app/dist/assets/{globe-Ct-GlWID.js → globe-rSsEzPYv.js} +1 -1
- package/web-app/dist/assets/{hammer-C1oZFX51.js → hammer-CPogONt9.js} +1 -1
- package/web-app/dist/assets/{index-C2egrRhz.js → index-DpBLuMuz.js} +3 -3
- package/web-app/dist/assets/{layers-Dt5ZK4v2.js → layers-CwFaqidn.js} +1 -1
- package/web-app/dist/assets/{loader-circle-DrpMziic.js → loader-circle-C43izEm5.js} +1 -1
- package/web-app/dist/assets/{lock-DOez8MmK.js → lock-Bc_Uqxpp.js} +1 -1
- package/web-app/dist/assets/{package-ClGIkerr.js → package-BM25lcR-.js} +1 -1
- package/web-app/dist/assets/{plus-C004i9ua.js → plus-D0xoiY6N.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-Ddfk-7Sy.js → refresh-cw-COI1R5Oo.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-Bu1KgDjS.js → rotate-ccw-BoNZx4xe.js} +1 -1
- package/web-app/dist/assets/{scroll-text-3q1lW5pm.js → scroll-text-Cakp8XHd.js} +1 -1
- package/web-app/dist/assets/{server-pd6x4HW1.js → server-CjfIlPmE.js} +1 -1
- package/web-app/dist/assets/{shield-alert-B9jGJepT.js → shield-alert-BilhziJF.js} +1 -1
- package/web-app/dist/assets/{thumbs-up-Cdcc9nQf.js → thumbs-up-CglMrtrt.js} +1 -1
- package/web-app/dist/assets/{trash-2-foSojXHK.js → trash-2-B6zVvnoG.js} +1 -1
- package/web-app/dist/assets/{trending-down-CTTiqlJ1.js → trending-down-BVhwD9bt.js} +1 -1
- package/web-app/dist/assets/{trending-up-B_MgmRAx.js → trending-up-CVJ1ox6c.js} +1 -1
- package/web-app/dist/assets/{upload-g6BfALzG.js → upload-DmuR09WL.js} +1 -1
- package/web-app/dist/assets/{usePolling-DrHwvzuh.js → usePolling-BTALfO91.js} +1 -1
- package/web-app/dist/assets/{user-COp75m7l.js → user-CRdHMZNH.js} +1 -1
- package/web-app/dist/index.html +1 -1
package/dashboard/audit.py
CHANGED
|
@@ -19,6 +19,7 @@ import json
|
|
|
19
19
|
import logging
|
|
20
20
|
import logging.handlers
|
|
21
21
|
import os
|
|
22
|
+
import re
|
|
22
23
|
import socket
|
|
23
24
|
import sys
|
|
24
25
|
import threading
|
|
@@ -31,7 +32,7 @@ try:
|
|
|
31
32
|
import fcntl
|
|
32
33
|
except ImportError: # pragma: no cover - non-POSIX
|
|
33
34
|
fcntl = None # type: ignore[assignment]
|
|
34
|
-
from datetime import datetime, timezone
|
|
35
|
+
from datetime import datetime, timedelta, timezone
|
|
35
36
|
from pathlib import Path
|
|
36
37
|
from typing import Any, Optional
|
|
37
38
|
|
|
@@ -47,6 +48,12 @@ AUDIT_DIR = Path.home() / ".loki" / "dashboard" / "audit"
|
|
|
47
48
|
MAX_LOG_SIZE_MB = int(os.environ.get("LOKI_AUDIT_MAX_SIZE_MB", "10"))
|
|
48
49
|
MAX_LOG_FILES = int(os.environ.get("LOKI_AUDIT_MAX_FILES", "10"))
|
|
49
50
|
|
|
51
|
+
# Retention: audit files whose UTC date is older than this many days are pruned
|
|
52
|
+
# (on the first write of each UTC day, or via prune_logs()). 0 or negative
|
|
53
|
+
# disables pruning. Pruning writes a checkpoint so the chain still verifies.
|
|
54
|
+
DEFAULT_RETENTION_DAYS = 90
|
|
55
|
+
CHECKPOINT_NAME = "audit-checkpoint.json"
|
|
56
|
+
|
|
50
57
|
# Syslog forwarding (optional, off by default)
|
|
51
58
|
_SYSLOG_HOST = os.environ.get("LOKI_AUDIT_SYSLOG_HOST", "").strip()
|
|
52
59
|
_SYSLOG_PORT = int(os.environ.get("LOKI_AUDIT_SYSLOG_PORT", "514"))
|
|
@@ -151,12 +158,10 @@ def _recover_last_hash() -> str:
|
|
|
151
158
|
except (json.JSONDecodeError, IOError, OSError):
|
|
152
159
|
continue
|
|
153
160
|
|
|
154
|
-
|
|
155
|
-
|
|
161
|
+
# No surviving entries: continue from a retention checkpoint if one exists,
|
|
162
|
+
# so a fully pruned directory does not re-root the chain at genesis.
|
|
163
|
+
return _load_checkpoint(AUDIT_DIR)[0].get("last_pruned_hash", genesis)
|
|
156
164
|
|
|
157
|
-
# Recover chain hash from existing logs on startup
|
|
158
|
-
if INTEGRITY_ENABLED:
|
|
159
|
-
_last_hash = _recover_last_hash()
|
|
160
165
|
|
|
161
166
|
# Actions considered security-relevant (logged at WARNING level in syslog)
|
|
162
167
|
_SECURITY_ACTIONS = frozenset({
|
|
@@ -240,9 +245,184 @@ def _cleanup_old_logs() -> None:
|
|
|
240
245
|
|
|
241
246
|
log_files = sorted(AUDIT_DIR.glob("audit-*.jsonl"), key=lambda p: p.stat().st_mtime)
|
|
242
247
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
248
|
+
excess = len(log_files) - MAX_LOG_FILES
|
|
249
|
+
if excess <= 0:
|
|
250
|
+
return
|
|
251
|
+
# Count-cap removal goes through the checkpointing path so the chain still
|
|
252
|
+
# verifies; only a chain that is already broken falls back to a plain unlink.
|
|
253
|
+
if _prune_files(AUDIT_DIR, log_files[:excess])["status"] == "refused_chain_invalid":
|
|
254
|
+
for oldest in log_files[:excess]:
|
|
255
|
+
oldest.unlink()
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
# ---------------------------------------------------------------------------
|
|
259
|
+
# Retention with a chain checkpoint
|
|
260
|
+
#
|
|
261
|
+
# Deleting the oldest files removes the genesis the verifier starts from. The
|
|
262
|
+
# checkpoint records the hash of the last pruned entry so verification resumes
|
|
263
|
+
# there. It is written BEFORE the files are deleted and lists them under
|
|
264
|
+
# "removing", so a crash between the two steps leaves a directory that still
|
|
265
|
+
# verifies (leftovers are skipped, then cleaned by the next prune).
|
|
266
|
+
# ---------------------------------------------------------------------------
|
|
267
|
+
_GENESIS = "0" * 64
|
|
268
|
+
_HEX64 = re.compile(r"^[0-9a-f]{64}$")
|
|
269
|
+
_FILE_DATE = re.compile(r"^audit-(\d{4}-\d{2}-\d{2})")
|
|
270
|
+
_last_prune_day: str = ""
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _checkpoint_digest(cp: dict) -> str:
|
|
274
|
+
body = {k: v for k, v in cp.items() if k != "checkpoint_hash"}
|
|
275
|
+
return hashlib.sha256(
|
|
276
|
+
json.dumps(body, sort_keys=True, default=str).encode("utf-8")).hexdigest()
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _load_checkpoint(audit_dir) -> "tuple[dict, bool]":
|
|
280
|
+
"""Return (checkpoint, ok). ({}, True) when none exists; ({}, False) when
|
|
281
|
+
the file exists but is unreadable, malformed or fails its own digest."""
|
|
282
|
+
path = Path(audit_dir) / CHECKPOINT_NAME
|
|
283
|
+
if not path.exists():
|
|
284
|
+
return {}, True
|
|
285
|
+
try:
|
|
286
|
+
cp = json.loads(path.read_text())
|
|
287
|
+
if (isinstance(cp, dict)
|
|
288
|
+
and isinstance(cp.get("last_pruned_hash"), str)
|
|
289
|
+
and _HEX64.match(cp["last_pruned_hash"])
|
|
290
|
+
and cp.get("checkpoint_hash") == _checkpoint_digest(cp)):
|
|
291
|
+
return cp, True
|
|
292
|
+
except (OSError, ValueError):
|
|
293
|
+
pass
|
|
294
|
+
return {}, False
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _write_checkpoint(audit_dir, cp: dict) -> None:
|
|
298
|
+
cp = {k: v for k, v in cp.items() if k != "checkpoint_hash"}
|
|
299
|
+
cp["checkpoint_hash"] = _checkpoint_digest(cp)
|
|
300
|
+
path = Path(audit_dir) / CHECKPOINT_NAME
|
|
301
|
+
tmp = path.with_suffix(".json.tmp")
|
|
302
|
+
tmp.write_text(json.dumps(cp, sort_keys=True))
|
|
303
|
+
os.replace(tmp, path)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _chain_files(audit_dir) -> "tuple[list, str, bool]":
|
|
307
|
+
"""Return (files in chain order, start hash, checkpoint_ok). Files named in
|
|
308
|
+
a checkpoint's "removing" list are leftovers of an interrupted prune."""
|
|
309
|
+
audit_dir = Path(audit_dir)
|
|
310
|
+
cp, ok = _load_checkpoint(audit_dir)
|
|
311
|
+
skip = set(cp.get("removing", []))
|
|
312
|
+
files = sorted((p for p in audit_dir.glob("audit-*.jsonl") if p.name not in skip),
|
|
313
|
+
key=lambda p: p.stat().st_mtime)
|
|
314
|
+
return files, cp.get("last_pruned_hash", _GENESIS), ok
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _prune_files(audit_dir, targets) -> dict:
|
|
318
|
+
"""Remove `targets` (a prefix of the chain) and checkpoint their last hash.
|
|
319
|
+
Caller holds _hash_lock. Refuses, deleting nothing, if the prefix does not
|
|
320
|
+
verify: pruning must not launder a broken chain into a clean one."""
|
|
321
|
+
audit_dir = Path(audit_dir)
|
|
322
|
+
cp, ok = _load_checkpoint(audit_dir)
|
|
323
|
+
if not ok:
|
|
324
|
+
return {"status": "refused_checkpoint_invalid", "pruned_files": 0}
|
|
325
|
+
# Finish an interrupted prune first.
|
|
326
|
+
for name in cp.get("removing", []):
|
|
327
|
+
try:
|
|
328
|
+
(audit_dir / name).unlink()
|
|
329
|
+
except FileNotFoundError:
|
|
330
|
+
pass
|
|
331
|
+
if cp.get("removing"):
|
|
332
|
+
cp["removing"] = []
|
|
333
|
+
_write_checkpoint(audit_dir, cp)
|
|
334
|
+
targets = list(targets)
|
|
335
|
+
if not targets:
|
|
336
|
+
return {"status": "nothing_to_prune", "pruned_files": 0}
|
|
337
|
+
prev = cp.get("last_pruned_hash", _GENESIS)
|
|
338
|
+
hashed = prev != _GENESIS
|
|
339
|
+
entries = 0
|
|
340
|
+
for f in targets:
|
|
341
|
+
if not hashed and not _file_has_integrity(str(f)):
|
|
342
|
+
continue # pre-integrity file: carries no hash to preserve
|
|
343
|
+
r = verify_log_integrity(str(f), start_hash=prev)
|
|
344
|
+
if not r.get("valid", False):
|
|
345
|
+
return {"status": "refused_chain_invalid", "pruned_files": 0,
|
|
346
|
+
"first_tampered_file": str(f)}
|
|
347
|
+
prev = r["last_hash"]
|
|
348
|
+
hashed = True
|
|
349
|
+
entries += r["entries_checked"]
|
|
350
|
+
if hashed:
|
|
351
|
+
_write_checkpoint(audit_dir, {
|
|
352
|
+
"version": 1,
|
|
353
|
+
"last_pruned_hash": prev,
|
|
354
|
+
"pruned_entries": int(cp.get("pruned_entries", 0)) + entries,
|
|
355
|
+
"pruned_files": int(cp.get("pruned_files", 0)) + len(targets),
|
|
356
|
+
"pruned_at": datetime.now(timezone.utc).isoformat(),
|
|
357
|
+
"removing": [f.name for f in targets],
|
|
358
|
+
})
|
|
359
|
+
for f in targets:
|
|
360
|
+
try:
|
|
361
|
+
f.unlink()
|
|
362
|
+
except FileNotFoundError:
|
|
363
|
+
pass
|
|
364
|
+
if hashed:
|
|
365
|
+
cp, _ = _load_checkpoint(audit_dir)
|
|
366
|
+
cp["removing"] = []
|
|
367
|
+
_write_checkpoint(audit_dir, cp)
|
|
368
|
+
return {"status": "pruned", "pruned_files": len(targets), "pruned_entries": entries}
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def _retention_days() -> int:
|
|
372
|
+
raw = os.environ.get("LOKI_AUDIT_RETENTION_DAYS", "").strip()
|
|
373
|
+
try:
|
|
374
|
+
return int(raw) if raw else DEFAULT_RETENTION_DAYS
|
|
375
|
+
except ValueError:
|
|
376
|
+
return DEFAULT_RETENTION_DAYS
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def _prune_expired_locked(audit_dir, days: int, now: datetime) -> dict:
|
|
380
|
+
if days <= 0:
|
|
381
|
+
return {"status": "disabled", "pruned_files": 0}
|
|
382
|
+
audit_dir = Path(audit_dir)
|
|
383
|
+
if not audit_dir.exists():
|
|
384
|
+
return {"status": "nothing_to_prune", "pruned_files": 0}
|
|
385
|
+
cutoff = (now - timedelta(days=days)).strftime("%Y-%m-%d")
|
|
386
|
+
files, _start, _ok = _chain_files(audit_dir)
|
|
387
|
+
expired = []
|
|
388
|
+
# Contiguous oldest prefix only, and never the newest file (the live tip).
|
|
389
|
+
for f in files[:-1]:
|
|
390
|
+
m = _FILE_DATE.match(f.name)
|
|
391
|
+
if not m or m.group(1) >= cutoff:
|
|
392
|
+
break
|
|
393
|
+
expired.append(f)
|
|
394
|
+
return _prune_files(audit_dir, expired)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def prune_logs(retention_days: Optional[int] = None, audit_dir=None,
|
|
398
|
+
now: Optional[datetime] = None) -> dict:
|
|
399
|
+
"""Prune audit files older than the retention window, keeping the chain
|
|
400
|
+
verifiable via a checkpoint. `retention_days` defaults to
|
|
401
|
+
LOKI_AUDIT_RETENTION_DAYS (default 90); 0 disables. Returns a dict with
|
|
402
|
+
`status` (pruned, nothing_to_prune, disabled, refused_*) and counts."""
|
|
403
|
+
days = _retention_days() if retention_days is None else int(retention_days)
|
|
404
|
+
with _hash_lock:
|
|
405
|
+
return _prune_expired_locked(
|
|
406
|
+
audit_dir if audit_dir is not None else AUDIT_DIR,
|
|
407
|
+
days, now or datetime.now(timezone.utc))
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def _maybe_prune_daily_locked() -> None:
|
|
411
|
+
"""Prune once per UTC day per process, on the write path. Never raises:
|
|
412
|
+
losing an audit entry is worse than a late prune."""
|
|
413
|
+
global _last_prune_day
|
|
414
|
+
today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
|
415
|
+
if _last_prune_day == today:
|
|
416
|
+
return
|
|
417
|
+
_last_prune_day = today
|
|
418
|
+
try:
|
|
419
|
+
_prune_expired_locked(AUDIT_DIR, _retention_days(), datetime.now(timezone.utc))
|
|
420
|
+
except Exception as exc: # noqa: BLE001
|
|
421
|
+
print(f"[loki-audit] WARNING: retention prune failed: {exc}", file=sys.stderr)
|
|
422
|
+
|
|
423
|
+
# Recover chain hash from existing logs on startup
|
|
424
|
+
if INTEGRITY_ENABLED:
|
|
425
|
+
_last_hash = _recover_last_hash()
|
|
246
426
|
|
|
247
427
|
|
|
248
428
|
def _forward_to_syslog(entry: dict) -> None:
|
|
@@ -333,6 +513,7 @@ def log_event(
|
|
|
333
513
|
global _last_hash
|
|
334
514
|
with _hash_lock:
|
|
335
515
|
log_file = _get_current_log_file()
|
|
516
|
+
_maybe_prune_daily_locked()
|
|
336
517
|
_rotate_logs_if_needed(log_file)
|
|
337
518
|
|
|
338
519
|
if not INTEGRITY_ENABLED:
|
|
@@ -666,8 +847,12 @@ def verify_all_logs_in_dir(audit_dir) -> dict:
|
|
|
666
847
|
# would break chain ordering and false-negative on any user who hit
|
|
667
848
|
# size-based rotation. Sort by mtime instead -- mirrors what
|
|
668
849
|
# `_cleanup_old_logs` already does at line 178.
|
|
669
|
-
files
|
|
670
|
-
|
|
850
|
+
files, prev_hash, cp_ok = _chain_files(audit_dir)
|
|
851
|
+
if not cp_ok:
|
|
852
|
+
return {"valid": False, "files_checked": 0, "files_skipped": 0,
|
|
853
|
+
"entries_checked": 0, "first_tampered_file": str(audit_dir / CHECKPOINT_NAME),
|
|
854
|
+
"first_tampered_line": None, "genesis_file": None,
|
|
855
|
+
"verified": False, "status": "tampered"}
|
|
671
856
|
total_entries = 0
|
|
672
857
|
files_checked = 0
|
|
673
858
|
files_skipped = 0
|
|
@@ -762,8 +947,7 @@ def compute_chain_tip_in_dir(audit_dir) -> dict:
|
|
|
762
947
|
genesis = "0" * 64
|
|
763
948
|
tip_hash = genesis
|
|
764
949
|
if audit_dir.exists():
|
|
765
|
-
files
|
|
766
|
-
prev = genesis
|
|
950
|
+
files, prev, _cp_ok = _chain_files(audit_dir)
|
|
767
951
|
for log_file in files:
|
|
768
952
|
if not _file_has_integrity(str(log_file)):
|
|
769
953
|
continue
|
|
@@ -812,8 +996,7 @@ def compute_prefix_hash_in_dir(audit_dir, n_entries: int) -> dict:
|
|
|
812
996
|
return {"found": True, "prefix_hash": genesis, "entries_available": 0}
|
|
813
997
|
if not audit_dir.exists():
|
|
814
998
|
return {"found": False, "prefix_hash": genesis, "entries_available": 0}
|
|
815
|
-
files
|
|
816
|
-
prev_hash = genesis
|
|
999
|
+
files, prev_hash, _cp_ok = _chain_files(audit_dir)
|
|
817
1000
|
seen = 0
|
|
818
1001
|
started = False
|
|
819
1002
|
for log_file in files:
|
|
@@ -868,12 +1051,16 @@ def _unified_cli() -> int:
|
|
|
868
1051
|
`tip` keeps exiting 0 on an empty chain (src/audit/crosslink.js reads it).
|
|
869
1052
|
"""
|
|
870
1053
|
argv = sys.argv[1:]
|
|
871
|
-
if len(argv) < 2 or argv[0] not in ("tip", "verify", "prefix"):
|
|
1054
|
+
if len(argv) < 2 or argv[0] not in ("tip", "verify", "prefix", "prune"):
|
|
872
1055
|
print(json.dumps(
|
|
873
1056
|
{"error": "usage: audit.py {tip|verify} <audit_dir> "
|
|
874
|
-
"| prefix <audit_dir> <n_entries>"}))
|
|
1057
|
+
"| prefix <audit_dir> <n_entries> | prune <audit_dir> [days]"}))
|
|
875
1058
|
return 2
|
|
876
1059
|
cmd, audit_dir = argv[0], argv[1]
|
|
1060
|
+
if cmd == "prune":
|
|
1061
|
+
out = prune_logs(int(argv[2]) if len(argv) > 2 else None, audit_dir)
|
|
1062
|
+
print(json.dumps(out))
|
|
1063
|
+
return 1 if out.get("status", "").startswith("refused") else 0
|
|
877
1064
|
if cmd == "tip":
|
|
878
1065
|
out = compute_chain_tip_in_dir(audit_dir)
|
|
879
1066
|
print(json.dumps(out))
|
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.17.
|
|
3
|
+
Generated from `loki-ts/src/cli/registry.ts` by `scripts/generate-stale-zero.sh` for v11.17.6. 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 |
|
|
@@ -30,6 +30,8 @@ Hidden and legacy-only commands are omitted; see `docs/v10/CLI-MODERN.md` for th
|
|
|
30
30
|
| `loki rollback show` | Show a checkpoint |
|
|
31
31
|
| `loki rollback to` | Restore a checkpoint |
|
|
32
32
|
| `loki rollback latest` | Restore the latest checkpoint |
|
|
33
|
+
| `loki backup` | Back up dashboard state (audit log, checkpoint) with a sha256 manifest |
|
|
34
|
+
| `loki restore` | Restore dashboard state from a verified backup archive |
|
|
33
35
|
| `loki proof` (alias: `receipt`) | Inspect and share proof-of-run receipts |
|
|
34
36
|
| `loki proof list` | List receipts |
|
|
35
37
|
| `loki proof show` | Show a receipt |
|
|
@@ -112,6 +114,7 @@ Hidden and legacy-only commands are omitted; see `docs/v10/CLI-MODERN.md` for th
|
|
|
112
114
|
| `loki stop` | Stop execution immediately |
|
|
113
115
|
| `loki pause` | Pause after the current session |
|
|
114
116
|
| `loki resume` | Resume a paused or interrupted run |
|
|
117
|
+
| `loki cancel` | Cancel the active Loki 10 run at the next stage boundary |
|
|
115
118
|
| `loki why` | Explain how the last run ended |
|
|
116
119
|
| `loki next` | Run the right next step |
|
|
117
120
|
| `loki logs` | Tail recent log output |
|
package/docs/EVALUATING.md
CHANGED
|
@@ -196,7 +196,7 @@ loki bench attribution --report <result.json> # re-render the markdown
|
|
|
196
196
|
|
|
197
197
|
Source checkout only. Implementation: `benchmarks/bench/attribution.py`, reusing `runner._apply_overlay`, `report._median` and `benchmarks/bench/adapters` (process-tree reaping, the Claude JSON parser, the Loki adapter and its cost collection). Options: `--tasks id,id`, `--repeats N` (default 1), `--model ID` (default `claude-sonnet-5`, or `LOKI_BENCH_MODEL`), `--budget-usd X` (default 5), `--timeout SECONDS` (default 900, capped by each task's `agent_timeout_s`), `--out-dir DIR`, `--tasks-dir DIR`. Without `--execute` it only prints the plan and never spawns `claude` or `loki`.
|
|
198
198
|
|
|
199
|
-
Arms and controls: `
|
|
199
|
+
Arms and controls: `raw-configured` runs `claude -p <prompt> --output-format json --model M --dangerously-skip-permissions --max-budget-usd B`; `loki` runs `loki start <prompt-file> --provider claude --no-dashboard --yes` with `LOKI_SESSION_MODEL=M` and `LOKI_BUDGET_LIMIT=B`. Both get a byte-identical seed in a fresh git repo with one fixed "seed" commit, the same prompt text (the PRD file for Loki is kept outside the workspace), the same wall-clock cap with the process tree reaped on timeout, separate scratch directories, a scoped child environment and `LOKI_NO_BROWSER=1`. The arm that goes first alternates per task and repeat. Every result JSON pins `claude --version`, `loki version`, the Loki commit SHA, Python version, model, budget, timeout, repeats, and per task the sha256 of the seed tree, acceptance tree and prompt. Disclosed difference: Loki runs its own planning, review and verification loop; that overhead is what is measured. Provider comparisons are out of scope so model and harness effects are not mixed.
|
|
200
200
|
|
|
201
201
|
Tasks live under `benchmarks/attribution/tasks/<id>/` (2 bug fixes, 2 features, 1 refactor, 1 integration): `task.json`, `prompt.md` (the only instruction either arm sees), `seed/` (workspace), `acceptance/` (hidden test, copied in only after the run, overwriting same-named files) and `reference/` (known-good overlay, used only by tests). Exit code 0 from `acceptance.cmd` means accepted. The loader rejects a task whose acceptance test also appears in the seed. Neither arm grades itself: no council, RARV-C or LLM judge takes part in scoring.
|
|
202
202
|
|
|
@@ -213,7 +213,7 @@ Not measured: human effort, recovery success, review quality. Output: `--out-dir
|
|
|
213
213
|
|
|
214
214
|
## 9. Factory-vs-Loki task set (reproducible, partly unmeasured)
|
|
215
215
|
|
|
216
|
-
`benchmarks/attribution/tasks/coord-1-api-web/` (multi-repo coordination) and `benchmarks/attribution/tasks/longspec-1-three-units/` (long spec, three units) ship with held-out acceptance kept outside the workspace. `benchmarks/bench/attribution.py` has
|
|
216
|
+
`benchmarks/attribution/tasks/coord-1-api-web/` (multi-repo coordination) and `benchmarks/attribution/tasks/longspec-1-three-units/` (long spec, three units) ship with held-out acceptance kept outside the workspace. `benchmarks/bench/attribution.py` has four arms: `raw-configured`, `raw-bare` (`claude --bare`, needs ANTHROPIC_API_KEY, else UNMEASURED with a reason), `loki` and an optional `droid` arm. `--max-spend-usd N` aborts before the next run once true spend (max of reported cost and the Loki child-session ledger) reaches N. The droid arm probes `droid exec --help`; when `droid exec` is unavailable every droid cell is recorded UNMEASURED with a reason, never zero. `python3 benchmarks/bench/attribution.py --dry-run` lists the tasks and arms and makes no model call. No Factory result is published: Factory publishes no Missions benchmark and no droid run has been measured here. Axis where Loki is ahead on documented features: offline-recheckable signed receipts, now composite receipts for multi-repo change sets; Factory documents none.
|
|
217
217
|
|
|
218
218
|
---
|
|
219
219
|
|
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.17.
|
|
5
|
+
**Version:** v11.17.6
|
|
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
|
|
|
@@ -49,6 +49,7 @@ interact and why hitting one is a failure rather than a success.
|
|
|
49
49
|
| `LOKI_ENGINE` | unset | `legacy` forces the previous loop; the `--engine` flag wins over it. |
|
|
50
50
|
| `LOKI_SUMMARY_JSON` | unset | `1` prints the end-of-run summary as one JSON line on the previous loop. |
|
|
51
51
|
| `LOKI_TIER` | `oss` | **Not a model setting.** The open-core licensing seam. Leave it unset. |
|
|
52
|
+
| `LOKI_E10_STABLE_PREFIX` | on | Engine10 sessions send the `claude_code` preset with `excludeDynamicSections: true`, so the system prompt is byte-identical across stages and runs and hits the prompt cache. `0` restores the previous preset. `LOKI_E10_PREFIX=lean` still wins when set. |
|
|
52
53
|
| `LOKI_REPO_BASELINE` | unset | `0` skips the intake repo profile and baseline test run (see docs/HARNESS-INTELLIGENCE-PLAN.md). |
|
|
53
54
|
| `LOKI_ALLOW_CLAUDE_SIDECALLS` | unset | `1` (exactly) lets PRD enrichment, done recognition, the council voters, the USAGE.md refresh and the quickstart intent check prompt the `claude` CLI under a non-claude provider; `loki doctor --airgap` then reports that as required egress to Anthropic. |
|
|
54
55
|
|
package/docs/trackers.md
CHANGED
|
@@ -36,3 +36,9 @@ Optional status moves happen only on done: `LOKI_JIRA_DONE_TRANSITION` (a Jira t
|
|
|
36
36
|
## Safety
|
|
37
37
|
|
|
38
38
|
Issue text is untrusted input. It is passed to the run as data and never grants tool authority.
|
|
39
|
+
|
|
40
|
+
## Inbound comments and transitions
|
|
41
|
+
|
|
42
|
+
Transitions: LOKI_JIRA_{STARTED,BLOCKED,DONE}_TRANSITION (transition ids) and LOKI_LINEAR_{STARTED,BLOCKED,DONE}_STATE (state ids) move the ticket when the run starts, blocks or finishes.
|
|
43
|
+
|
|
44
|
+
Steer from comments: the trigger server turns a Jira `comment_created` (POST /jira) or Linear `Comment` create (POST /linear) into `loki steer` (source=jira|linear, actor recorded) when the author is listed in LOKI_JIRA_STEER_ACTORS (accountIds) or LOKI_LINEAR_STEER_ACTORS (user ids) and a run this server dispatched for that issue is in flight. Unset lists reject everyone.
|
package/docs/v10/BOARD.md
CHANGED
|
@@ -402,10 +402,12 @@ set is declared to avoid overlap with those and with each other.
|
|
|
402
402
|
| M-23 | Remote units via POST /jobs kind unit, Helm worker.engine v10, local re-verify | autonomy/trigger-server.py (unit job kind only), deploy/helm/autonomi/templates/job-worker.yaml, deploy/helm/autonomi/values.yaml, loki-ts/src/engine10/modernize/remote.ts, loki-ts/tests/engine10/modernize/remote.test.ts, tests/test-trigger-server-unit-jobs.py | HIGH | python3 tests/test-trigger-server-unit-jobs.py && (cd loki-ts && bun test tests/engine10/modernize/remote.test.ts) | released@2026-10-16T02:04Z | Source: MODERNIZE.md (D30). Depends on M-17. |
|
|
403
403
|
| M-24 | Dashboard /modernize view | loki-ts/src/engine10/dashboard/modernize.ts, loki-ts/src/engine10/dashboard/server.ts (one route), loki-ts/tests/engine10/modernize/dashboard.test.ts | LOW | cd loki-ts && bun test tests/engine10/modernize/dashboard.test.ts | released@2026-09-28T16:03Z | Source: MODERNIZE.md (D30). Depends on M-01. Built fe3dd87a (branch slice-M-24-dashboard); TL review died on the 04:48Z access outage. Wave E21 (wf_39ab3f7d-8f3). TL review. TL APPROVE of fe3dd87a; merged into train E22 (9042e964): `bun test tests/engine10/` 547 pass 0 fail, tsc 0, `bash tests/test-v10-pulse.sh` 107 passed, `bash eval/loki10/test-harness.sh` 108 passed. Released in v10.2.1 (npm publish 2026-09-28T16:03Z; every cited commit is an ancestor of the v10.2.1 tag; `git merge-base --is-ancestor`). |
|
|
404
404
|
| M-25 | Final report and verdict (COMPLETE only if all proven), sealed | loki-ts/src/engine10/modernize/report.ts, loki-ts/tests/engine10/modernize/report.test.ts | HIGH | cd loki-ts && bun test tests/engine10/modernize/report.test.ts | released@2026-10-16T02:40Z | Source: MODERNIZE.md (D30). Depends on M-13, M-21. |
|
|
405
|
-
| M-26 | End-to-end: py2 fixture repo modernized with stub provider | loki-ts/tests/engine10/modernize/e2e.test.ts, loki-ts/tests/engine10/modernize/fixtures/e2e_py2/ | MEDIUM | cd loki-ts && bun test tests/engine10/modernize/e2e.test.ts |
|
|
406
|
-
| M-27 | Legacy-Bench pilot harness; confirm public set; calibrate cap | eval/modernize/pilot.py, eval/modernize/test-pilot.sh | HIGH | bash eval/modernize/test-pilot.sh |
|
|
407
|
-
| M-
|
|
405
|
+
| M-26 | End-to-end: py2 fixture repo modernized with stub provider | loki-ts/tests/engine10/modernize/e2e.test.ts, loki-ts/tests/engine10/modernize/fixtures/e2e_py2/ | MEDIUM | cd loki-ts && bun test tests/engine10/modernize/e2e.test.ts | released@2026-10-16T22:45Z | Source: MODERNIZE.md (D30). Depends on M-08, M-22, M-25. Reconciled 2026-10-16: shipped as ea6d2eef8 (ancestor of main c89555bce). |
|
|
406
|
+
| M-27 | Legacy-Bench pilot harness; confirm public set; calibrate cap | eval/modernize/pilot.py, eval/modernize/test-pilot.sh | HIGH | bash eval/modernize/test-pilot.sh | parked@2026-10-16T23:05Z | Source: MODERNIZE.md (D30). Depends on M-26. D34: absorbs the D30 large-tier legacy coverage. D98 park after round 2: round 2 opus BLOCK on fc2a7e90 (F1, budget-skipped units vanish, partial run counts ok). Carried forward as M-27b on top of fc2a7e90. |
|
|
407
|
+
| M-27b | M-27 carry-forward: pilot counts planned units from wave.started/estimate.printed, unrun units are not proven, missing modernize.completed or non-zero exit is not ok, cost per 1K lines labelled lower bound | eval/modernize/pilot.py, eval/modernize/test-pilot.sh | HIGH | bash eval/modernize/test-pilot.sh | building@2026-10-16T23:05Z | Base fc2a7e90 (M-27 round 1 fixes). Source: M-27 round 2 HIGH review F1. Unblocks M-28. Round 1 BLOCK (no producer for modernize.completed, spend undercount, planned units); round 2 fix 7fc7c886 (test-pilot 32/0) in HIGH round 2 review. |
|
|
408
|
+
| M-28 | 200K scale run (Mercurial 4.x, measured first, fallback Calibre) | eval/modernize/scale.py, eval/modernize/SCALE.md | HIGH | python3 eval/modernize/scale.py --target hg --dry-run-first | ready@2026-09-28T02:45Z | Source: MODERNIZE.md (D30). Depends on M-27b (M-27 parked). |
|
|
408
409
|
| M-29 | 1M scale run (Hadoop 2.x Java 8 to 21, measured first) | eval/modernize/scale_1m.py, eval/modernize/SCALE-1M.md | HIGH | python3 eval/modernize/scale_1m.py --dry-run-first | ready@2026-09-28T02:45Z | Source: MODERNIZE.md (D30). Depends on M-28, M-11. |
|
|
410
|
+
| M-25w | Modernize report and reslice event wired in production (FC-M25W): coordinator calls writeReport at run end, reslice.ts emits unit.resliced, and a guard asserts every declared modernize event has a producer | loki-ts/src/util/modernize_coordinator.ts, loki-ts/src/engine10/modernize/reslice.ts, loki-ts/tests/engine10/modernize/e2e.test.ts | HIGH | cd loki-ts && bun test tests/engine10/modernize/ | ready@2026-10-16T22:55Z | Source: M-27 HIGH review B2/B3 (FAILURE-CLASSES FC-M25W). Engine10 core is at 4999 of 5000 lines; the coordinator lives in src/util, keep engine10 edits net zero. |
|
|
409
411
|
| M-30 | User docs for loki modernize, no dashes, commands match USAGE | docs/v10/GUIDE-MODERNIZE.md, tests/test-modernize-docs.sh | LOW | bash tests/test-modernize-docs.sh | released@2026-09-28T21:01Z | Source: MODERNIZE.md (D30). Depends on M-07. Dispatched 18:58Z, sonnet engineer. Tech Lead REJECT r1 (--resume id failed MID_RE), APPROVE r2 5035984d; merged 8d568c66; test-modernize-docs.sh 25/0, shard coverage 19/0, durations drift 6/0. Released in v10.5.0 (merge 8d568c66 is an ancestor of v10.5.0). |
|
|
410
412
|
| E-66 | "Already implemented" outcome: evidence search in code, tests, docs, CHANGELOG; no change needed verdict, receipt, issue comment, no PR | loki-ts/src/engine10 (intake or plan, implement marker path, seal, pr) | HIGH | fixture with the feature ends no change needed, no implement session; fixture without proceeds | released@2026-09-28T16:03Z | Source: D32 (augmentiq #52). Opus REJECT (reproduced): no test that ALREADY_SATISFIED opens no PR; evidence search matched 652 hits for an unrelated task on this repo; confirmation need not cite files. Rework dispatched. Rework 1c7e1b09 (branch worktree-wf_b707f4ab-24f-1): all 3 blockers fixed (no-PR test F5, 0 hits for both review sentences on this repo, citation must name an existing code or test hit; evidence in receipt). Opus CONCERN: the real-repo test uses listRepoFiles capped at 2000 of 4784 files, so removing STOP_WORDS still passes; the existsSync branch of the citation check is untested. Next: small fix (full file list in that test, one existsSync test). Wave E18 small fix (uncapped file list, existsSync test), branch slice-E-66-test-fixes. Opus APPROVE of 0a64ea98 (uncapped list, existsSync test). Merge onto main conflicts in seal.ts, supervisor.ts, types.ts, rule_of_two.test.ts after E-68/E-69; rebase dispatched (wave E19). D33: states its net core line delta; core must stay under 5,000 (92 lines of headroom shared by E-67, E-66, E-61, M-14, merge order E-67, E-66, E-61); offset any excess by deleting dead core code. Rework 135ba06b Opus APPROVE, but merged it takes core to 5,189 (D33 cap 5,000; `bun test tests/engine10/budget.test.ts`). Not merged. Next: offset about 190 core lines in the same slice (dead or duplicated core code), then merge. Wave E21 (wf_39ab3f7d-8f3). r3: D33 offset. Rework r3 605f33b0 Opus APPROVE (core under 5,000 after offset); merged into train E22 (9042e964): `bun test tests/engine10/` 547 pass 0 fail, tsc 0, `bash tests/test-v10-pulse.sh` 107 passed, `bash eval/loki10/test-harness.sh` 108 passed. Released in v10.2.1 (npm publish 2026-09-28T16:03Z; every cited commit is an ancestor of the v10.2.1 tag; `git merge-base --is-ancestor`). |
|
|
411
413
|
| EV-13 | Eval expected_outcome no_change_needed; aiq-52 set to it | eval/loki10/harness.py, eval/loki10/tasks/aiq-52-searchbar, test-harness.sh | HIGH | bash eval/loki10/test-harness.sh | released@2026-09-28T05:00Z | Source: D32. Opus CONCERN: a correct v10 no-change run on aiq-52 is graded as a source change. Rework dispatched. Rework APPROVE (branch worktree-wf_b707f4ab-24f-5: 19224201, e0264724): v10 Wall side effect excluded from the diff check. Not merged yet. Merged c9c15f38 (trainE18); `bash eval/loki10/test-harness.sh` 103 passed 0 failed. Released in v10.1.0 (npm publish 2026-09-28T05:00Z; every cited commit is an ancestor of the v10.1.0 tag; `git merge-base --is-ancestor`). |
|
|
@@ -944,7 +946,8 @@ Source: ~/git/autonomi-dev/research/2026-09-30-adoption/SWARM-PROMPT-ADOPTION.md
|
|
|
944
946
|
| COST-4 | Gate xreview and any reviewer on risk tier (medium/high only) | loki-ts/src/engine10/stages/xreview.ts | LOW | xreview test | released@2026-10-16T01:20Z | Cost audit 2026-10-16: no measured run reaches 0.5x raw yet (best 0.68x lean config). |
|
|
945
947
|
| COST-5 | Merge plan into implement via the plan chain resume (one fewer cold prefix) | loki-ts/src/engine10/stages/implement.ts | MEDIUM | conflict_resume tests | released@2026-10-16T01:20Z | Cost audit 2026-10-16: no measured run reaches 0.5x raw yet (best 0.68x lean config). |
|
|
946
948
|
| COST-6 | Measure cost per accepted task vs raw Claude Code: attribution.py --execute, 8 tasks, n=3, Sonnet 5.5, receipt cost.usd vs total_cost_usd; target ratio 0.5 | benchmarks/bench/attribution.py, docs/v10/METRICS.md | MEDIUM | attribution dry-run | parked@2026-10-16T00:12Z | paid eval, needs founder go on spend. Cost audit 2026-10-16: no measured run reaches 0.5x raw yet (best 0.68x lean config). |
|
|
947
|
-
| FC-09u | FC-09 siblings: pnpm-workspace.yaml overrides/packageExtensions and .yarnrc.yml packageExtensions join RUNNER_CONFIGS | loki-ts/src/features/runner_config.ts, loki-ts/tests/engine10/runner_config.test.ts | HIGH | each probe caps |
|
|
949
|
+
| FC-09u | FC-09 siblings: pnpm-workspace.yaml overrides/packageExtensions and .yarnrc.yml packageExtensions join RUNNER_CONFIGS | loki-ts/src/features/runner_config.ts, loki-ts/tests/engine10/runner_config.test.ts | HIGH | each probe caps | parked@2026-10-16T23:12Z | After FC-09t review. Built as 12a4c80a: runner_config.test.ts 135 pass 0 fail; typecheck clean. D98 park after round 2: opus BLOCK e07885d5, real pnpm 11.1.3 applied a link: override through a double BOM and a `? [foo]` explicit key while the check reported UNCAPPED (Bun.YAML vs js-yaml 4 divergence). Carried as FC-09v. |
|
|
950
|
+
| FC-09v | FC-09u carry-forward: trust parsed YAML key comparison only when both sides are in a strict plain subset (no BOM, explicit ? keys, merge keys, anchors, aliases, tags, directives, doc markers, CR, tabs); any other byte change caps | loki-ts/src/features/runner_config.ts, loki-ts/tests/engine10/runner_config.test.ts | HIGH | both FC-09u reviewer probes print CAPPED for every bypass; plain unrelated-key edit does not cap | building@2026-10-16T23:12Z | Base e07885d5. Source: FC-09u round 2 HIGH review. Round 1 BLOCK cebe5641 (block-scalar parser differential reproduced with pnpm 11.1.3); round 2 per D101 caps on any byte change, building. |
|
|
948
951
|
| CPE24-P2a | Checkpoint rollback restores only an allowlist (session.json, dashboard-state.json, CONTINUITY.md, autonomy-state.json, state, queue) and refuses nested symlinks | packages/control-plane/src/server/routes/checkpoints.ts plus test | MEDIUM | planted audit/ in a checkpoint does not overwrite .loki/audit | released@2026-10-15T23:55Z | rv-trust3 advisory on P2. |
|
|
949
952
|
| CPE24-P4a | Legacy /api/control/* callers require ctx.local (or isLoopbackHost) like /v1 | packages/control-plane/src/server/legacy/routes-control.ts plus test | MEDIUM | non-loopback Host refused on legacy control | released@2026-10-15T23:55Z | rv-trust3 advisory on P4. |
|
|
950
953
|
| CP-NITS1 | CP run page nits from the 10.10.2 smoke: Wall row describes the Wall ("Wrote acceptance checks (n files)" or "skipped: no checks written"); local repo with no remote shows the folder name, not REPO UNMEASURED; receipt text read from receipt.json on disk like the derived diff; diffstat failures cached (F1); leading-dash sha fixture | packages/control-plane/src/server plus ui run page plus tests | MEDIUM | CP suite green; a signed receipt on disk renders its text; a no-remote repo shows its folder name | released@2026-10-15T23:55Z | Peer smoke 21:27Z plus CP follow-up review F1. |
|
package/docs/v10/CLI-MODERN.md
CHANGED
|
@@ -6,12 +6,12 @@ Classes: KEEP-MODERN (v10-native or engine-neutral), UPDATE (live but needs mode
|
|
|
6
6
|
|
|
7
7
|
## Counts
|
|
8
8
|
|
|
9
|
-
- KEEP-MODERN:
|
|
9
|
+
- KEEP-MODERN: 74
|
|
10
10
|
- UPDATE: 22
|
|
11
11
|
- DROP-LEGACY: 31
|
|
12
12
|
- DELETE: 14
|
|
13
|
-
- Total rows:
|
|
14
|
-
- Registry paths (commands and nested subcommands):
|
|
13
|
+
- Total rows: 141
|
|
14
|
+
- Registry paths (commands and nested subcommands): 197
|
|
15
15
|
|
|
16
16
|
## Commands
|
|
17
17
|
|
|
@@ -24,6 +24,8 @@ Classes: KEEP-MODERN (v10-native or engine-neutral), UPDATE (live but needs mode
|
|
|
24
24
|
| provider | bun route, bash fallthrough | KEEP-MODERN | Provider-agnostic core |
|
|
25
25
|
| memory | bun route, bash fallthrough | UPDATE | Memory still carries legacy store paths |
|
|
26
26
|
| rollback | bun route, bash fallthrough | KEEP-MODERN | Checkpoint restore is engine-neutral |
|
|
27
|
+
| backup | autonomy/loki | KEEP-MODERN | Enterprise retention and backup (D99 item 7) |
|
|
28
|
+
| restore | autonomy/loki | KEEP-MODERN | Enterprise retention and backup (D99 item 7) |
|
|
27
29
|
| proof | bun route, bash fallthrough | KEEP-MODERN | The Evidence Receipt moat |
|
|
28
30
|
| receipt | bun route, bash fallthrough | DELETE | Alias of proof; shown only under 'loki help aliases' |
|
|
29
31
|
| wiki | bun route, bash fallthrough | KEEP-MODERN | Bun-native knowledge feature |
|
|
@@ -63,8 +65,9 @@ Classes: KEEP-MODERN (v10-native or engine-neutral), UPDATE (live but needs mode
|
|
|
63
65
|
| mcp | autonomy/loki | KEEP-MODERN | Engine-neutral |
|
|
64
66
|
| acp | autonomy/loki | KEEP-MODERN | Engine-neutral |
|
|
65
67
|
| stop | autonomy/loki | KEEP-MODERN | Session control |
|
|
66
|
-
| pause |
|
|
67
|
-
| resume |
|
|
68
|
+
| pause | bun route, bash fallthrough | KEEP-MODERN | Session control; Bun writes the Loki 10 run request, bash keeps the legacy PAUSE file |
|
|
69
|
+
| resume | bun route, bash fallthrough | KEEP-MODERN | Session control; Bun clears a Loki 10 pause, bash keeps the legacy resume |
|
|
70
|
+
| cancel | bun route, bash fallthrough | KEEP-MODERN | Session control; Loki 10 only |
|
|
68
71
|
| why | autonomy/loki | KEEP-MODERN | Outcome explainer |
|
|
69
72
|
| next | autonomy/loki | KEEP-MODERN | Guided flow |
|
|
70
73
|
| logs | autonomy/loki | KEEP-MODERN | Engine-neutral |
|
package/docs/v10/DECISIONS.md
CHANGED
|
@@ -583,7 +583,7 @@ The $20.00 default per-run cap stays. It is a hard stop on priced sessions only;
|
|
|
583
583
|
6. Cap: 90s small, 180s normal, LOKI_E10_WALL_LIMIT_S may override up to 300s, fixed before the session. A Wall timeout stays not_run, never red or already_satisfied, and Seal discloses it.
|
|
584
584
|
7. Ships behind LOKI_E10_WALL_MANIFEST with a lift row (augmentiq#52 plus 2 brownfield tasks, manifest off versus on). Lower lift drops the change. Slices W1-S1..W1-S4 on BOARD; S1 to S3 touch the engine10 core and wait for core budget room.
|
|
585
585
|
|
|
586
|
-
8. P12-05 holdout, behind LOKI_WALL_HOLDOUT_RATIO (default 0
|
|
586
|
+
8. P12-05 holdout, behind LOKI_WALL_HOLDOUT_RATIO (superseded by D99 item 4: default ratio 0.2, on, active at 5+ Wall checks, a holdout failure blocks the Seal; opt out with LOKI_WALL_HOLDOUT=0): a deterministic subset (sha256 of run id and test id, max(1, round(ratio*N)), none under 5 Wall tests) is kept outside the repo and runDir, copied in only by verify as blocking holdout:N checks, and its private store is removed when the run ends. Shipped state (C-C4, C-C4b): on by default. Preconditions (a), (b), (d) and (e) (pin hashes outside the store, drop targetDir from the manifest, close the tmp symlink race, define resume) are NOT yet done: the manifest still carries targetDir and is self-attesting beyond the per-file sha256 check, and a missing or altered store fails closed. Precondition (c) is done in C-C4b: held-out checks are excluded from failures_grouped, and the fix stage receives only an opaque holdout_failed count, never a held-out name, command or failure text.
|
|
587
587
|
|
|
588
588
|
## D78
|
|
589
589
|
2026-10-03T10:39Z, CoS (D69). GitHub push protection rejected train/94 because commit 04f62ff4e (SEC-SCAN-1) carried planted fake AWS key literals in tests/test-secret-scan.sh. Nothing in the range was on origin, so the 124 unpushed main commits were rewritten locally (filter-branch index-filter on that one file): each literal is split with an adjacent empty single-quoted string, so bash builds the same value and the scanner test still blocks it (6/0). The tree diff from the old head was that file only. This was not a force-push, and no protection bypass was requested. Commit SHAs cited on BOARD and PROGRESS for 10:25Z and earlier today, after a185ce16, are pre-rewrite and resolve only through the reflog. Rule: a test fixture never holds a scanner-matching secret literal, split it at write time.
|
|
@@ -799,3 +799,33 @@ Founder: "you are taking hours long for releases, I wanted 3 releases to 10 rele
|
|
|
799
799
|
1. A guard (a test that fails a build on a shape it detects) blocks a merge only on findings that a realistic change would trigger: an ordinary workflow edit or an ordinary contributor mistake. A realistic finding always blocks.
|
|
800
800
|
2. Contrived adversarial bypasses (exit 256, eval rc=0, writes hidden in arithmetic or through an indirect name, deliberately obfuscated shell) are logged as LOW follow-up rows in FAILURE-CLASSES.md and do not block. These guards protect against our own mistakes; an attacker who controls workflow files is covered by branch protection and review.
|
|
801
801
|
3. HIGH reviews have a 2-round limit. Round 3 escalates to the CTO with the finding list, and the CTO decides. Reason: opus share was about 50% against the 30% budget (D13).
|
|
802
|
+
|
|
803
|
+
## D99 (2026-10-16, founder grant at 19:33Z "make all decisions on my behalf to make loki mode better than anything in the world", CoS records): every open founder and CTO decision is decided
|
|
804
|
+
Standing: from this grant on, the CoS decides founder-queue items without waiting and records each here. Items needing a credential or money beyond the caps below still go to FOUNDER-QUEUE.md. Claude stays the primary provider and `loki start` stays the primary entry point (founder, standing).
|
|
805
|
+
1. C-C1 gateway lane wording: a non-Anthropic model routed behind the claude CLI is labelled "gateway lane (unqualified)". `loki doctor` prints that label and a feature-loss list (prompt caching, extended thinking, server tools and hooks are not guaranteed on that lane). Claude routes stay unlabelled and default. Unblocks P12-27.
|
|
806
|
+
2. C-C2 permission default: the compiled permission policy becomes the default once P12-26g-c merges with a HIGH approve. The legacy skip-permissions path stays reachable only with LOKI_PERMISSION_POLICY=legacy, and run start prints which mode is active. Flip slice is HIGH.
|
|
807
|
+
3. C-C3 cost moat overlap: COST-2 and COST-3 fold into P12-32. One scoring module computes expected accepted-outcome cost (price divided by the measured acceptance rate); no parallel moat change. Unparks COST-2/3 as P12-32 inputs.
|
|
808
|
+
4. C-C4 Wall holdout: ratio 0.2, active when a Wall has 5 or more checks (at least 1 held out). A holdout failure blocks the Seal (NOT_PROVEN with the holdout reason), never a warning. Default on. Reason: moat and Seal accuracy rank above cost and speed.
|
|
809
|
+
5. C-F1 paid evaluation: approved, hard cap 150 USD for this cycle across COST-6 n=3, the B9 live run and the S-112 ablation unpark. Each run sets a per-run cost cap; spend lands in METRICS.md. The 50 USD/day line (queue item 3) still binds.
|
|
810
|
+
6. C-F5 hosted Company OS: out of scope until cost at or under 0.5x raw Claude Code is proven. The CLI and `loki start` stay the product.
|
|
811
|
+
7. C-F6 enterprise commitments: no SCIM, HA or named support owner yet. P12-48 (retention and backup) proceeds.
|
|
812
|
+
8. C-F7 comparative external pilot: deferred until C-F1 runs prove the cost claim; no external spend now.
|
|
813
|
+
9. C-F8 stale version references: the CoS refreshes in-repo docs (PLAN-V12) to the shipped VERSION; the source plan outside the repo is left as an archived input.
|
|
814
|
+
10. M-27 Legacy-Bench pilot: use the public set named in MODERNIZE.md; calibrate the cap from the first 3 pilot runs inside the C-F1 cap.
|
|
815
|
+
11. M-27E defaults: D1 (multi-file units NOT_PROVEN), D2 (a wave with no detected integration tests verifies with a warning; build and original suite still block) and D3 (overflow goes to util/, the modernize/ cap stays 4,000) are adopted as built.
|
|
816
|
+
12. P12-13b journeys: the U1-U8 list is confirmed as the journey set; add the gh pr edit and comment stubs.
|
|
817
|
+
|
|
818
|
+
## D100 (2026-10-16, CTO call under D99, CoS records): features cap raised from 3,000 to 3,300 lines
|
|
819
|
+
- FC-09u round 1 HIGH review reproduced 7 bypasses of a hand-rolled YAML key reader in runner_config.ts. The builder had folded that code onto a single line of about 600 characters to stay under the D66 features cap, and the folded code hid the bypasses.
|
|
820
|
+
- The parser-based round 2 fix, unfolded, takes src/features to 3,012 lines.
|
|
821
|
+
- Rejected: folding again, which hurts review. Also rejected: moving the helper into src/util only to dodge the cap, which games the gate.
|
|
822
|
+
- Rule: never fold code to fit a cap. When a correct, readable change crosses a cap, the cap owner raises it in the same slice and records why.
|
|
823
|
+
- Unchanged: the engine10 core cap (D29, D33), the e10ext cap (D42) and the contrib cap.
|
|
824
|
+
- Reverse: lower the cap back once A-150 or a features split frees lines.
|
|
825
|
+
|
|
826
|
+
## D101 (2026-10-16, CTO call under D99, CoS records): runner config files cap trust on any byte change
|
|
827
|
+
- FC-09v round 1 HIGH review reproduced a bypass with real pnpm 11.1.3: a block scalar indicator on its own line made Bun.YAML swallow `overrides` into a string while js-yaml 4 installed a malicious `link:` override. A fuzz found more parser disagreements (`1_000`, timestamps, block indentation indicators).
|
|
828
|
+
- Rule: never trust a second parser's view of a file another tool will load. For YAML runner config, byte-identical means unchanged; any other byte change caps trust. Changed `*.patch` files and paths under `patches/` cap as well.
|
|
829
|
+
- Cost: an unrelated edit to pnpm-workspace.yaml now caps. Accepted: such edits are rare and moat beats convenience.
|
|
830
|
+
- Rejected: more regexes on a strict subset (round 1 showed the class is open); bundling js-yaml (pnpm can change parsers between versions).
|
|
831
|
+
- Reverse: only by comparing with the exact parser the installed package manager uses, proven by a differential fuzz against it.
|
package/docs/v10/ENGINE.md
CHANGED
|
@@ -151,6 +151,7 @@ Targets drive the ETA. Limits are hard kills of the stage (the whole process gro
|
|
|
151
151
|
- **Wall author:** a separate session whose cwd is a fresh temp dir holding only `task.md` and `repomap.txt`. It never sees the repo, which enforces "never the code" physically. It writes behavioral acceptance tests named `loki_wall_*` in the repo's detected test framework.
|
|
152
152
|
- The engine copies the Wall tests into the repo test dir, hashes each file (sha256), emits `wall.sealed` before Implement, and keeps a sealed copy in `<runDir>/wall/`.
|
|
153
153
|
- The engine then runs the Wall tests on the base tree. If they all pass, the run seals ALREADY_SATISFIED.
|
|
154
|
+
- **Wall holdout (on by default):** when the Wall has 5 or more checks, a deterministic 20% subset (at least 1) is kept outside the repo and runDir, never shown to the implementer, and copied in only at Fast verify as blocking `holdout:N` checks. A held-out failure blocks the Seal (NOT PROVEN naming the holdout check). It reaches the fix stage only as an opaque count (`holdout_failed`), never as a name, command or failure text. Opt out with `LOKI_WALL_HOLDOUT=0` (or `LOKI_WALL_HOLDOUT_RATIO=0`); `LOKI_WALL_HOLDOUT_RATIO=0.4` holds out about 40%, capped at 0.9. An unparseable ratio falls back to 0.2, never to off.
|
|
154
155
|
|
|
155
156
|
**Implement:** exactly ONE provider session through the existing loki-ts invoker (`resolveProvider(provider).invoke({mainLoop: true, tier: "development", cwd: repoDir, ...})`), wrapped by `session.ts`.
|
|
156
157
|
|
|
@@ -149,6 +149,9 @@ L0 review rule (ENGINE-LAWS.md L0): a fix that adds an `if` or a regex about the
|
|
|
149
149
|
- the equivalents for other runners: vitest.config setupFiles, jest setupFiles and moduleNameMapper, pytest conftest.py, and go test flags in Makefiles.
|
|
150
150
|
- Mechanism: one runner-config registry for each runner. Any added or changed file that it names (configs, preloads, setup files, conftest) caps the verdict below VERIFIED, with NOT PROVEN naming the file.
|
|
151
151
|
- Fixture: a stub-provider repro for each runner (bun preload, vitest setupFiles, jest moduleNameMapper, pytest conftest).
|
|
152
|
+
- Status (FC-09r): mechanism in `loki-ts/src/features/runner_config.ts`, read by verify.ts (testConfigChanged, base vs working tree) and seal.ts (runnerConfigNotes, base vs head, adds a NOT PROVEN line so the verdict cannot be VERIFIED). Fixtures: `loki-ts/tests/engine10/runner_config.test.ts` (real commit + seal stages).
|
|
153
|
+
- Covered: bunfig.toml; vitest, vite, jest config and vitest.workspace; .mocharc; pytest.ini, tox.ini, conftest.py; pyproject.toml and setup.cfg pytest sections; .cargo/config.toml; preload, setupFiles, globalSetup and exact-file moduleNameMapper targets (jest rootDir resolved, explicit rootDir fails closed); `__mocks__/`; package.json scripts and resolution redirects; go.mod and go.work replace; Cargo.toml patch and replace; tsconfig paths; a changed config that fails to parse is capped, an unchanged broken one is not.
|
|
154
|
+
- Deferred: transitive imports of setup files, pytest `-p` plugins and `pytest_plugins`, .mocharc `require`/`spec` targets, setupFiles given as identifiers or globs, Makefile go test flags.
|
|
152
155
|
|
|
153
156
|
## FC-10 Live line truncated to ~20 columns on a pty with no size
|
|
154
157
|
- User saw: on a pty with no column size (script -q, CI, tmux before a resize, IDE terminals) the quiet-mode live status line was cut to about 20 characters, for example "[implement] implemen". Raw: the stage events were complete; only the rendering was wrong.
|
|
@@ -812,4 +815,60 @@ L0 review rule (ENGINE-LAWS.md L0): a fix that adds an `if` or a regex about the
|
|
|
812
815
|
- Siblings swept: the group receipt (seal_group.ts), Python proof receipts and control-plane fixtures never carry these fields and are not gated; signed receipts already cover both fields through receipt_sha256 and the attestation. The control-plane verify fixture (packages/control-plane/test/server/verify.test.ts signed()) and the undo_plan, undo_apply and workspace e2e receipt/1 fixtures are gated and now carry seal-shaped fields (empty-log hash with zero stages).
|
|
813
816
|
- Mechanism: verify_cmd.ts checkEventLog, for schema loki.v10.receipt/1 only, returns TAMPERED naming the missing field (events_sha256 not 64-char lowercase hex, time absent or null, time.stages not a plain object) and ends "Re-run the task to produce a fresh receipt."
|
|
814
817
|
- Fixture: loki-ts/tests/engine10/verify_cmd.test.ts P12-01c (field missing, malformed hash, time removed with stages at top level, time null, stages string, stages array, honest zero-stage receipt stays UNSIGNED); hand-built receipt/1 fixtures in seal_group, log_seal and visual_evidence tests now carry both fields.
|
|
815
|
-
- Known limits:
|
|
818
|
+
- Known limits: closed by FC-103 (a schema variant or no schema field is now TAMPERED).
|
|
819
|
+
|
|
820
|
+
## FC-102 A resumed run lost intake's deferred already_satisfied flag (D61-04, P12-07j) (RESUME-ALREADY-SATISFIED)
|
|
821
|
+
- User saw: after `loki start --resume`, a run whose intent was already satisfied committed and verified its own diff and opened a redundant PR instead of short-circuiting to ALREADY_SATISFIED. Raw: the deferred hit was applied in memory only.
|
|
822
|
+
- Cause (measured): the deferred hit mutates intake's data after intake's stage.completed was emitted (machine.ts keeps the same object for that reason), so resumeMachineOpts restored intake from a payload that never had the flag.
|
|
823
|
+
- Law: L4 (the durable log must carry every fact a later stage reads).
|
|
824
|
+
- Siblings swept: every other stage output restored by resumeMachineOpts is final before its stage.completed emit; intake is the only stage mutated afterward. Readers of intake.already_satisfied: machine.ts mustJump, seal.ts verdictOf, discard.ts.
|
|
825
|
+
- Mechanism: the worker emits `already.satisfied` (already_done_async.ts, after hitsUnchangedFromBase and apply succeed). SupervisorLog.ingest attests it as the SUPERVISOR_ONLY event `already.attested` only for a well-formed deferred hit while implement is in flight; a worker line of type already.attested is dropped. resumeMachineOpts restores the flag only from already.attested that precedes implement's stage.completed with no later intent.revised (stale evidence). Absent, malformed or rejected means not satisfied; old logs restore as before.
|
|
826
|
+
- Guarantee: a restored flag jumps past verify. VERIFIED is impossible because verdictOf returns ALREADY_SATISFIED, or PARTIAL without proof, whenever intake.already_satisfied is true.
|
|
827
|
+
- Fixture: loki-ts/tests/engine10/fix_resume.test.ts P12-07j (survives, old log, unattested worker event, malformed, intent.revised, post-implement, ingest attestation, verdictOf); tests/engine10/already_done.test.ts (emit payload and ordering).
|
|
828
|
+
- Known limits: a compromised worker can still fake a well-formed already.satisfied while implement is in flight, the same claim it can already make through the LOKI_ALREADY_DONE marker; the attestation closes forgery outside that window and log edits (tamper.detected). discard.ts restores staged paths on this flag, so that window remains the trust boundary. comment and comment_argv are not restored.
|
|
829
|
+
|
|
830
|
+
## FC-103 An unrecognized or missing receipt schema skipped every schema-gated check and read UNSIGNED, and a verify test turned a failed verification into SKIP (RECEIPT-SCHEMA-UNKNOWN)
|
|
831
|
+
- User saw: a re-hashed unsigned receipt whose schema was renamed or removed verified as UNSIGNED (exit 0 under --allow-unsigned); tests/test-mcp-tasks-verify-readonly.sh printed SKIP and exited 0 when the signed verify returned a non-VERIFIED result with no verdict line.
|
|
832
|
+
- Law: a verifier dispatches on a closed set of schemas; anything outside the set is evidence of editing, never a pass. A test may skip only on a named absent precondition, never on a verification result.
|
|
833
|
+
- Cause (measured): schema-gated checks (FC-101, group section) were opt-in per schema, so a receipt that matched no schema matched no check; the test's catch-all case branch mapped every output without "verdict: " to SKIP.
|
|
834
|
+
- Siblings swept: DSSE envelope and composite.json dispatch before this point and have their own hash checks; group/1 without a group section was closed in P12-01d; the other verify-path tests (test-loki-verify-*, verify_cmd.test.ts) assert on verdicts, not on skips.
|
|
835
|
+
- Mechanism: verify_cmd.ts verifyReceipt returns TAMPERED naming "unrecognized receipt schema" unless schema is loki.v10.receipt/1 or loki.v10.group/1 (P12-01d, extended here by tests); the MCP verify test (classify_non_verified) fails on every non-VERIFIED result and SKIPs only when the output contains the engine message "cannot run on this machine: no working bun" (bin/loki checks PATH and the bundled @oven/bun-*, so a PATH probe is the wrong precondition).
|
|
836
|
+
- Fixture: loki-ts/tests/engine10/verify_cmd.test.ts P12-01d loop (renamed, removed, non-string, empty, null, case-variant); tests/test-mcp-tasks-verify-readonly.sh run with PATH lacking bun, a bundled node_modules/@oven/bun-*/bin/bun and LOKI_TS_ENTRY at a crashing stub (old: SKIP 6/0, new: FAIL) plus the classify_non_verified cases.
|
|
837
|
+
- Known limits: the only SKIP is the engine-reported "no working bun"; a bun that exists but crashes fails. The unsigned cases of the same script always run.
|
|
838
|
+
|
|
839
|
+
## FC-104 A steer claim linked to a normal archive was swept as "applied" although the consumer died before applying it (STEER-APPLIED-UNPROVEN)
|
|
840
|
+
- User saw: an operator steer vanished: never injected, never reported, and Seal passed.
|
|
841
|
+
- Law: "applied" must be provable from a durable record written before the evidence that could contradict it is removed; an inode match proves only that a link happened.
|
|
842
|
+
- Cause (measured): consumeSteer linked the claim to human-input-<stamp>.md, unlinked the claim, then recorded revisions. P12-02f's sweep removed any claim sharing an inode with a normal archive, so a kill between link and record (or unlink and record) lost the steer silently.
|
|
843
|
+
- Siblings swept: ORPHAN and REJECTED archives (already reported when made, still removed quietly); the no-hardlink rename fallback in archiveTo (EPERM, ENOTSUP, EXDEV, EMLINK), which now also runs the record and marker step right after the rename (no claim survives there, so the order is record after rename); a dropped call there left the sink and intent-revisions.jsonl empty and Seal passing.
|
|
844
|
+
- Mechanism: archiveTo runs an onLinked step before the claim unlink: revisions are recorded, then a .applied-<archive> marker is written. sweepOrphans removes a claim linked to a normal archive only when that marker exists; otherwise it archives it ORPHAN and reports "orphaned claim" so Seal refuses.
|
|
845
|
+
- Fixture: loki-ts/tests/e10ext/steer.test.ts (P12-02g kill-before-record, marker-before-unlink order, applied-then-unlink-failed removed quietly).
|
|
846
|
+
- Known limits: markers whose archive is gone are pruned by the sweep; the marker is agent-writable like the revision file; a kill between record and marker reports a recorded steer as unapplied (fails closed).
|
|
847
|
+
- Known limits (no-hardlink filesystems): a kill after the rename fallback and before the revision record leaves no claim to sweep, so that steer is lost unreported; the order is unchanged from P12-02f.
|
|
848
|
+
|
|
849
|
+
## FC-REQ Recovery treated a flaky pass, a protocol violation and a requirements conflict as the same transient failure (P12-11) (RECOVERY-CLASSES)
|
|
850
|
+
- User saw: a test that failed once and passed on rerun was reported as a failure or retried blindly; a conflicting spec was re-prompted until the budget ran out.
|
|
851
|
+
- Law: a class is claimed only from structured evidence, never from agent prose; an unrecognized failure stays transient and is retried (retry_class.ts fail-safe).
|
|
852
|
+
- Cause (measured): decideRecovery knew only permanent, transient, build_failed, tree_corrupt and provider_unavailable; supervisor.ts hardcoded `flaky: []` and nothing remembered failures across runs.
|
|
853
|
+
- Siblings swept: verify.ts already reruns once and marks fail-then-pass "flaky" (the evidence source); retry_class.ts classifyFailure is unchanged; the permanent-failure stop and the repeated-signature breaker still run before any new class.
|
|
854
|
+
- Mechanism: recovery_policy.ts adds RecoverySignals rerun (same non-empty tree on fail and pass), protocolError and conflictingRequirements. Results: flaky -> retry, protocol_error -> revise, conflicting_requirements -> escalate. util/failure_memory.ts recordFingerprints (extends the existing failure-memory owner; flaky.json and failures.jsonl keep no per-run rows) appends fingerprints to .loki/state/fingerprints.jsonl (append-only, newest 200 kept) and supervisor.ts feeds verify's flaky list into it and the summary.
|
|
855
|
+
- Fixture: loki-ts/tests/runner/harness_intelligence.test.ts "P12-11 recovery classes (FC-REQ)" (classifier table, fingerprints across two runs, bound).
|
|
856
|
+
- Known limits: protocolError and conflictingRequirements have no producer wired yet (callers must set them from structured signals); fingerprints are recorded for flaky only and not yet read back by a policy rule; verify's flaky list is not yet fed to decideRecovery as a rerun signal (follow-up); engine10 renderMainOutput still passes flaky: [] (names reach the user via seal notProven); the bounding rewrite is best effort and a concurrent append can lose a row.
|
|
857
|
+
|
|
858
|
+
## FC-P12-32 Router cost objective is built but dark in production (RESOLVED by P12-32b)
|
|
859
|
+
- User-visible vs raw: no route decision changes today; the expected accepted-outcome cost ranking (history.ts routeScore, rankExecutorsByCost, haikuFloorExecutor) has no production caller.
|
|
860
|
+
- Law broken: a control nobody reads is theatre (reader without a live caller).
|
|
861
|
+
- Cause (measured): grep of loki-ts/src finds zero callers of resolveExecutor (decision.ts) and none that sets its historyFloor input, so haikuFloorExecutor is unreachable outside tests.
|
|
862
|
+
- Siblings swept: decision.ts envOverride, model_rank.ts nextState and issues_epic.ts are the only decision.ts importers; none builds a ResolveInput.
|
|
863
|
+
- Mechanism: seal.ts recordRun now also records FAILED as a code-owned loss (cost_preview.ts outcomeFor), so acceptance is measured; the remaining step is a caller that builds ResolveInput with historyFloor = haikuFloorExecutor(...) when the router resolves the stage executor.
|
|
864
|
+
- Fixture: loki-ts/tests/runner/cost_preview.test.ts "P12-32 outcomeFor", router_history.test.ts P12-32 cases.
|
|
865
|
+
- Resolved (P12-32b): implement_route.ts routeStart passes a lazy repoHistoryFloor to unit_model.ts routeRecord, which applies history.ts historyRaisedRoute (through resolveExecutor) to a valid cheapest-tier route; history only raises, and no history, below-floor history or corrupt history gives today's routing. Fixture: router_history.test.ts "P12-32b live cost routing".
|
|
866
|
+
- Known limits: acceptance counts only VERIFIED and FAILED; PARTIAL runs are not recorded; history never selects a cheaper unproven model, by design.
|
|
867
|
+
|
|
868
|
+
## FC-M25W Modernize final report and reslice event are built but never written in production (MODERNIZE-REPORT-DARK)
|
|
869
|
+
- User-visible vs raw: a real `loki modernize` run leaves no `.loki/modernize/<mid>/report.json`, so no sealed COMPLETE or NOT PROVEN verdict reaches the user or the M-27 pilot; units re-sliced is never recorded.
|
|
870
|
+
- Law broken: a control nobody reads is theatre (writer without a live caller; event declared with no producer).
|
|
871
|
+
- Cause (measured 2026-10-16, M-27 HIGH review): `git grep writeReport -- loki-ts/src` finds only its definition at report.ts:106; `git grep unit.resliced -- loki-ts/src` finds only the declaration at types.ts:10 (reslice.ts never appends it).
|
|
872
|
+
- Siblings to sweep: every name in the modernize types.ts event list must have at least one producer in loki-ts/src; cost has no per-unit producer either (only budget.hit spent_usd at scheduler.ts:203).
|
|
873
|
+
- Mechanism: the coordinator (loki-ts/src/util/modernize_coordinator.ts) calls writeReport once at the end of a run; reslice.ts appends unit.resliced; a guard test asserts every declared event name has a producer.
|
|
874
|
+
- Fixture: loki-ts/tests/engine10/modernize/e2e.test.ts asserts report.json exists after the stub run; a new producer-coverage case in the existing modernize test file.
|
|
@@ -26,3 +26,4 @@ this file; each item says what the loop does meanwhile.
|
|
|
26
26
|
| 19 | 2026-10-03 | FC-21b and FC-22 (docs/v10/FC21B-FC22-PLAN.md): (a) after an implement time limit, VERIFIED also requires a Wall or task-named test to pass with n>0; (b) dependency install runs by default, only when a selected check fails with a harness-owned load error, opt-out LOKI_E10_INSTALL=0 or loki.yaml verify.install_deps false; (c) exit code 3 (BUDGET_STOP) is dropped whenever a receipt is sealed, outcome and exit follow receipt.verdict. | Behavior and exit-code changes users see. | Decided by the CoS under the standing mandate: (a) yes, (b) yes as stated, (c) yes with a CHANGELOG Changed entry. Founder may reverse. | open |
|
|
27
27
|
| 20 | 2026-10-04 | CP-ASK (docs/v10/CP-ASK-PLAN.md): (a) with LOKI_CP_ASK on, the home box is Ask only, with a "Start a build" link to Compose; (b) defaults $1.00 per ask and 600s, configurable, Ask spend shown on the Cost page apart from run budgets; (c) CP-level runs, receipts and events across all repos always available, per-repo memory and code tools scoped to one repo chip per ask. | Product shape and spend defaults. | Decided by the CoS under the standing mandate as stated. Founder may reverse. | open |
|
|
28
28
|
| 21 | 2026-10-07 | Enable GitHub private vulnerability reporting in the repo settings (Settings, Code security, Private vulnerability reporting). SECURITY.md already tells reporters to use Security tab, "Report a vulnerability". | Public repo settings; agents never change them. | SECURITY.md ships as written; until enabled the "Report a vulnerability" button is absent. | open |
|
|
29
|
+
| 22 | 2026-10-16 | C-C1, C-C2, C-C3, C-C4, C-F1, C-F5, C-F6, C-F7, C-F8, M-27, M-27E D1-D3, P12-13b | Decided by the CoS under the founder grant of 2026-10-16 (D99). | Builds the unblocked slices. | decided (D99) |
|