loki-mode 9.8.0 → 9.12.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.
Files changed (79) hide show
  1. package/README.md +19 -14
  2. package/SKILL.md +3 -2
  3. package/VERSION +1 -1
  4. package/autonomy/loki +122 -1
  5. package/autonomy/run.sh +49 -2
  6. package/dashboard/__init__.py +1 -1
  7. package/dashboard/api_evidence.py +411 -0
  8. package/dashboard/api_operator.py +283 -0
  9. package/dashboard/api_phases.py +262 -0
  10. package/dashboard/api_releases.py +242 -0
  11. package/dashboard/api_runs.py +477 -0
  12. package/dashboard/api_tests.py +444 -0
  13. package/dashboard/api_v2.py +47 -1
  14. package/dashboard/server.py +54 -0
  15. package/dashboard/static/index.html +246 -135
  16. package/docs/ARCHITECTURE-OVERVIEW.md +5 -3
  17. package/docs/CAPABILITY-BACKLOG.md +53 -0
  18. package/docs/COMPARISON.md +2 -2
  19. package/docs/COMPETITIVE-ANALYSIS.md +1 -1
  20. package/docs/COMPETITIVE-SCORECARD.md +422 -0
  21. package/docs/DASHBOARD-9.12-EVIDENCE.md +97 -0
  22. package/docs/DASHBOARD-ARCHITECTURE.md +423 -0
  23. package/docs/DEMOS.md +21 -23
  24. package/docs/HANDOFF-2026-08-03.md +439 -0
  25. package/docs/INSTALLATION.md +17 -10
  26. package/docs/OUTCOME-FRONTIER.md +536 -0
  27. package/docs/PROMPT-ABLATION-RESULT.md +97 -0
  28. package/docs/TOOLS.md +800 -0
  29. package/docs/alternative-installations.md +2 -3
  30. package/docs/audit-logging.md +44 -35
  31. package/docs/authentication.md +13 -2
  32. package/docs/authorization.md +87 -81
  33. package/docs/git-workflow.md +6 -3
  34. package/docs/metrics.md +15 -16
  35. package/docs/network-security.md +16 -13
  36. package/docs/openclaw-integration.md +36 -556
  37. package/docs/show-hn-post.md +2 -2
  38. package/docs/siem-integration.md +39 -36
  39. package/loki-ts/dist/loki.js +18 -18
  40. package/mcp/__init__.py +1 -1
  41. package/package.json +2 -2
  42. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  43. package/references/confidence-routing.md +18 -1
  44. package/references/invariant-checks.md +13 -8
  45. package/references/magic-rarv-integration.md +0 -1
  46. package/references/multi-provider.md +27 -5
  47. package/skills/healing.md +4 -2
  48. package/tools/audit-docs.py +488 -0
  49. package/tools/baseline-pin.py +19 -1
  50. package/tools/calibration-audit.py +523 -0
  51. package/tools/ci-gate.py +19 -1
  52. package/tools/cost-forecast.py +344 -0
  53. package/tools/cost-guard.py +19 -1
  54. package/tools/cost-history.py +19 -1
  55. package/tools/cost-per-outcome.py +394 -0
  56. package/tools/estimate-run.py +19 -1
  57. package/tools/evidence-freshness.py +307 -0
  58. package/tools/gate-init.py +19 -1
  59. package/tools/gate-report.py +19 -1
  60. package/tools/gate-simulate.py +570 -0
  61. package/tools/gate-trend.py +354 -0
  62. package/tools/model-advisor.py +52 -1
  63. package/tools/policy-load.py +19 -1
  64. package/tools/prompt-cost.py +363 -0
  65. package/tools/prompt-diff.py +448 -0
  66. package/tools/prompt-lint.py +448 -0
  67. package/tools/receipt-bundle.py +72 -2
  68. package/tools/receipt-diff.py +19 -1
  69. package/tools/receipt-find.py +19 -1
  70. package/tools/receipt-stats.py +380 -0
  71. package/tools/receipt-timeline.py +478 -0
  72. package/tools/receipt-verify-batch.py +291 -0
  73. package/tools/run-replay.py +19 -1
  74. package/tools/signing-status.py +19 -1
  75. package/tools/token-guard.py +19 -1
  76. package/tools/token-tax.py +375 -0
  77. package/tools/tool-index.py +19 -1
  78. package/tools/verification-tax.py +277 -0
  79. package/tools/verify-chain.py +361 -0
@@ -0,0 +1,444 @@
1
+ """Read-only "test / CI results" view over the filesystem .loki/quality/ dir.
2
+
3
+ WHY THIS EXISTS. The dashboard had zero API surface over test and CI results,
4
+ so it could not answer the one question an operator asks first: did the last
5
+ run's tests pass? These are pure functions taking a loki_dir, so they are
6
+ testable without FastAPI and can be mounted by whoever owns server.py. Shape
7
+ and honesty discipline are copied from dashboard/api_runs.py; this module adds
8
+ no second envelope shape.
9
+
10
+ THE SOURCES, and what each can and cannot tell us. Every one is written by
11
+ autonomy/run.sh, and each is ONE flat record for the whole project:
12
+
13
+ .loki/quality/test-results.json the test axis. TWO writers, one schema.
14
+ run.sh:11563 is the no-runner writer (runner "none", pass the STRING
15
+ "inconclusive", status "not_run"); run.sh:11642 is the real-runner
16
+ writer (pass bool, status verified|failed|no_tests_run). Keys:
17
+ timestamp, runner, pass, summary, command, exit_code, status,
18
+ passed_count, failed_count, verification_gap, and min_coverage which is
19
+ present ONLY on the real-runner writer -- always read with .get().
20
+ .loki/quality/static-analysis.json run.sh:10442. Keys: timestamp,
21
+ files_checked, findings, summary, pass (bool).
22
+ .loki/quality/build-results.json run.sh:9811. Keys: timestamp, command,
23
+ ran, applicable, exit_code, duration_sec, status
24
+ (verified|failed|not_run|not_applicable).
25
+ .loki/quality/coverage.json run.sh:11706. Keys: measured, pct, tool,
26
+ runner, threshold, enforced, blocked, reason, timestamp.
27
+ .loki/quality/.test-results.iter the engine's own freshness marker: the
28
+ iteration number the test results belong to (run.sh:11570). Reported as
29
+ `iteration`; this module does NOT derive a staleness verdict from it.
30
+
31
+ WHAT "PER-SUITE" MEANS HERE, and the one place the writers are ambiguous. No
32
+ writer emits per-suite records -- pytest's individual suites are collapsed into
33
+ a single flat result before it is persisted, and passed_count / failed_count
34
+ are best-effort regex parses of the runner's summary line (null, never 0, when
35
+ unparseable). The only decomposition that exists on disk is the four sibling
36
+ AXES above, so a "suite" row here is one axis. A suite-parsing layer would have
37
+ to invent data no writer produces.
38
+
39
+ THE HONESTY RULE, which is the entire point of this module. A gate that did
40
+ not run is not a gate that passed. Three ways that fact gets destroyed, all
41
+ guarded:
42
+
43
+ 1. `pass` is a UNION: bool true, bool false, or the string "inconclusive".
44
+ `if rec.get("pass")` renders "inconclusive" as passing, because a
45
+ non-empty string is truthy. Compared with `is True` / `is False`; anything
46
+ else is UNKNOWN.
47
+ 2. An ABSENT artifact reads UNKNOWN with a stated reason, never "passed".
48
+ Absence of a signal is not evidence of success.
49
+ 3. `status` carries not_run and no_tests_run, which are gaps, not passes.
50
+ A runner that exited 0 having executed zero tests (run.sh:11632) proves
51
+ nothing and must not read verified.
52
+
53
+ WHY record_is_measured() IS NOT USED HERE. autonomy/lib/efficiency_cost.py
54
+ holds the canonical measured-ness predicate for COST, and api_runs.py imports
55
+ it rather than restating it. It is the wrong rule for this domain: it inspects
56
+ only _MEASURED_FIELDS (cost_usd, input_tokens, output_tokens, cache_read_
57
+ tokens, cache_creation_tokens), which a test-results record does not have, so
58
+ it returns False for a genuinely green measured suite -- verified by calling
59
+ it against one. Routing test rows through it would silently mark every suite
60
+ UNKNOWN. The codebase's real definition of trustworthy test evidence is the
61
+ predicate at autonomy/run.sh:20846 (runner not in {"", "none"} AND pass is True
62
+ AND command a non-empty str AND exit_code an int == 0). It lives inside a shell
63
+ heredoc, so it cannot be imported; _test_evidence_is_trustworthy() below
64
+ mirrors it and names that line as its source.
65
+
66
+ .loki/quality/ is NOT wiped at run start -- the run-start clear at
67
+ autonomy/run.sh:6212 covers metrics/efficiency/iteration-*.json only. These
68
+ files are last-write-wins per iteration, so they describe the most recent
69
+ iteration that ran each gate, which is why this reader is singular: there is no
70
+ history on disk to make it plural.
71
+
72
+ Every returned envelope states `source` (the real paths read) and
73
+ `freshness_s` (age in seconds of the newest file that actually contributed,
74
+ None when nothing did), and carries an explicit `reason` when empty. Each row
75
+ carries its own `source` and `freshness_s` too, matching api_runs.py.
76
+ """
77
+
78
+ from __future__ import annotations
79
+
80
+ import json
81
+ import os
82
+ import time
83
+ from typing import Any, Optional
84
+
85
+ __all__ = ["list_test_results", "UNKNOWN"]
86
+
87
+ # What an unmeasured value reads as. Kept as a name so callers can render it
88
+ # without hardcoding None-means-unknown at each call site.
89
+ UNKNOWN = None
90
+
91
+ _QUALITY = "quality"
92
+ _TEST_RESULTS = (_QUALITY, "test-results.json")
93
+ _STATIC_ANALYSIS = (_QUALITY, "static-analysis.json")
94
+ _BUILD_RESULTS = (_QUALITY, "build-results.json")
95
+ _COVERAGE = (_QUALITY, "coverage.json")
96
+ _ITER_MARKER = (_QUALITY, ".test-results.iter")
97
+
98
+ # Declared for the envelope so a caller can see exactly what was read, in the
99
+ # style already used by api_runs.py (real paths, not labels).
100
+ _SOURCE_PATHS = (
101
+ ".loki/quality/test-results.json",
102
+ ".loki/quality/static-analysis.json",
103
+ ".loki/quality/build-results.json",
104
+ ".loki/quality/coverage.json",
105
+ ".loki/quality/.test-results.iter",
106
+ )
107
+
108
+ # status values the writers emit that are POSITIVELY not a pass. Everything
109
+ # outside this set and outside "verified" is UNKNOWN rather than assumed good.
110
+ _GAP_STATUSES = ("not_run", "no_tests_run", "not_applicable")
111
+
112
+
113
+ # ---------------------------------------------------------------------------
114
+ # tiny io helpers -- every one returns a default rather than raising, because a
115
+ # dashboard read must degrade, never 500 on a missing file.
116
+ # ---------------------------------------------------------------------------
117
+
118
+ def _p(loki_dir: str, *parts: str) -> str:
119
+ return os.path.join(loki_dir, *parts)
120
+
121
+
122
+ def _read_json(path: str, default: Any = None) -> Any:
123
+ try:
124
+ with open(path, "r", encoding="utf-8") as fh:
125
+ return json.load(fh)
126
+ except Exception:
127
+ return default
128
+
129
+
130
+ def _mtime(path: str) -> Optional[float]:
131
+ try:
132
+ return os.path.getmtime(path)
133
+ except OSError:
134
+ return None
135
+
136
+
137
+ def _freshness(mtimes: list, now: Optional[float] = None) -> Optional[int]:
138
+ """Age in seconds of the NEWEST file that contributed. None if none did.
139
+
140
+ None is load-bearing: "no file contributed" is not "contributed zero
141
+ seconds ago". Clamped at 0 so a file written during this call (or a clock
142
+ that stepped) never reports a negative age.
143
+ """
144
+ real = [m for m in mtimes if m is not None]
145
+ if not real:
146
+ return None
147
+ return max(0, int((now if now is not None else time.time()) - max(real)))
148
+
149
+
150
+ def _iteration(loki_dir: str) -> Optional[int]:
151
+ """Iteration the test results belong to, from the engine's own marker.
152
+
153
+ Reported as a fact, never turned into a staleness verdict here. Absent or
154
+ unparseable reads None -- not 0, which is a real iteration number.
155
+ """
156
+ try:
157
+ with open(_p(loki_dir, *_ITER_MARKER), "r", encoding="utf-8") as fh:
158
+ return int(fh.read().strip())
159
+ except (OSError, ValueError):
160
+ return None
161
+
162
+
163
+ # ---------------------------------------------------------------------------
164
+ # the honesty predicates
165
+ # ---------------------------------------------------------------------------
166
+
167
+ def _passed(value: Any) -> Optional[bool]:
168
+ """Map a writer's `pass` field to True / False / UNKNOWN.
169
+
170
+ THE load-bearing function. `pass` is a union across the two writers: bool
171
+ true, bool false, or the string "inconclusive" (run.sh:11563 no-runner,
172
+ run.sh:11642 zero-tests-executed). A truth test would render that string as
173
+ a pass, which is the exact fake-green this module exists to prevent, so the
174
+ comparison is identity against the bools and everything else is UNKNOWN.
175
+ """
176
+ if value is True:
177
+ return True
178
+ if value is False:
179
+ return False
180
+ return UNKNOWN
181
+
182
+
183
+ def _test_evidence_is_trustworthy(rec: Any) -> bool:
184
+ """Mirror of the engine's own "real, passing test evidence" predicate.
185
+
186
+ Source of truth: autonomy/run.sh:20846, inside
187
+ _loki_supervised_completion_gates_pass. It is embedded in a shell heredoc
188
+ and therefore not importable, so it is mirrored here rather than imported;
189
+ if that predicate changes, this must follow. It is NOT the same rule as
190
+ efficiency_cost.record_is_measured(), which answers a cost question and
191
+ returns False for every test record (see the module docstring).
192
+
193
+ True only when a REAL runner ran, reported pass, and left a command and an
194
+ exit code of 0 behind. A record that says "tests passed" with no command
195
+ and no exit code is the "trust me" transcript the receipt replaces.
196
+ """
197
+ if not isinstance(rec, dict):
198
+ return False
199
+ runner = str(rec.get("runner") or "").strip().lower()
200
+ command = rec.get("command")
201
+ exit_code = rec.get("exit_code")
202
+ return (
203
+ runner not in ("", "none")
204
+ and rec.get("pass") is True
205
+ and isinstance(command, str)
206
+ and bool(command.strip())
207
+ and isinstance(exit_code, int)
208
+ and not isinstance(exit_code, bool)
209
+ and exit_code == 0
210
+ )
211
+
212
+
213
+ def _status_of(rec: Any, passed: Optional[bool]) -> Any:
214
+ """The writer's own status string, or a fallback derived from `pass`.
215
+
216
+ Never invents "verified". A record with no status and no usable pass reads
217
+ UNKNOWN, because a gate whose outcome was not recorded did not pass.
218
+ """
219
+ if isinstance(rec, dict):
220
+ status = rec.get("status")
221
+ if isinstance(status, str) and status.strip():
222
+ return status
223
+ if passed is True:
224
+ return "verified"
225
+ if passed is False:
226
+ return "failed"
227
+ return UNKNOWN
228
+
229
+
230
+ def _row(name: str, path_label: str, path: str, rec: Any,
231
+ passed: Optional[bool], now: Optional[float],
232
+ extra: Optional[dict] = None) -> dict:
233
+ """One axis row. Carries its own source and freshness, like api_runs rows.
234
+
235
+ `reason` is non-null whenever the outcome is UNKNOWN, so a caller never has
236
+ to guess whether a blank cell means "not run", "not recorded" or "we could
237
+ not read the file".
238
+ """
239
+ present = isinstance(rec, dict)
240
+ if not present:
241
+ reason: Any = "no %s on disk; outcome was never recorded" % (path_label,)
242
+ elif passed is UNKNOWN:
243
+ reason = "%s recorded no pass/fail outcome (pass=%r)" % (
244
+ path_label, rec.get("pass"),
245
+ )
246
+ else:
247
+ reason = None
248
+
249
+ row = {
250
+ "suite": name,
251
+ "passed": passed,
252
+ "status": _status_of(rec, passed),
253
+ "recorded": present,
254
+ "timestamp": (rec.get("timestamp") or UNKNOWN) if present else UNKNOWN,
255
+ "summary": (rec.get("summary") or UNKNOWN) if present else UNKNOWN,
256
+ "reason": reason,
257
+ "source": path_label,
258
+ "freshness_s": _freshness([_mtime(path)], now=now),
259
+ }
260
+ if extra:
261
+ row.update(extra)
262
+ return row
263
+
264
+
265
+ # ---------------------------------------------------------------------------
266
+ # per-axis readers
267
+ # ---------------------------------------------------------------------------
268
+
269
+ def _tests_row(loki_dir: str, now: Optional[float]) -> tuple:
270
+ """(row, record, path). The test axis, the reason this module exists."""
271
+ path = _p(loki_dir, *_TEST_RESULTS)
272
+ rec = _read_json(path)
273
+ present = isinstance(rec, dict)
274
+ passed = _passed(rec.get("pass")) if present else UNKNOWN
275
+
276
+ # A status the writers use for a POSITIVE gap (not_run / no_tests_run)
277
+ # overrides a stray pass value: a runner that exited 0 having executed zero
278
+ # tests proved nothing, so it must not read as passed.
279
+ if present and str(rec.get("status") or "") in _GAP_STATUSES:
280
+ passed = UNKNOWN
281
+
282
+ extra = {
283
+ "runner": (rec.get("runner") or UNKNOWN) if present else UNKNOWN,
284
+ "command": (rec.get("command") or UNKNOWN) if present else UNKNOWN,
285
+ # exit_code 0 is meaningful, so it is read with a presence check rather
286
+ # than an `or`, which would turn a real 0 into UNKNOWN.
287
+ "exit_code": rec.get("exit_code") if present else UNKNOWN,
288
+ # null, never 0, when the runner summary was unparseable -- the writer
289
+ # already emits null for this and the distinction must survive.
290
+ "passed_count": rec.get("passed_count") if present else UNKNOWN,
291
+ "failed_count": rec.get("failed_count") if present else UNKNOWN,
292
+ "verification_gap": (rec.get("verification_gap") or UNKNOWN) if present else UNKNOWN,
293
+ # Present only on the real-runner writer; .get() never assumes it.
294
+ "min_coverage": rec.get("min_coverage") if present else UNKNOWN,
295
+ "trustworthy": _test_evidence_is_trustworthy(rec),
296
+ }
297
+ row = _row("tests", ".loki/quality/test-results.json", path, rec, passed, now, extra)
298
+ if present and passed is UNKNOWN and row["reason"] is None:
299
+ row["reason"] = "test status %r is not an outcome" % (rec.get("status"),)
300
+ return row, rec, path
301
+
302
+
303
+ def _static_analysis_row(loki_dir: str, now: Optional[float]) -> tuple:
304
+ path = _p(loki_dir, *_STATIC_ANALYSIS)
305
+ rec = _read_json(path)
306
+ present = isinstance(rec, dict)
307
+ passed = _passed(rec.get("pass")) if present else UNKNOWN
308
+ extra = {
309
+ "findings": rec.get("findings") if present else UNKNOWN,
310
+ "files_checked": rec.get("files_checked") if present else UNKNOWN,
311
+ }
312
+ return _row("static_analysis", ".loki/quality/static-analysis.json",
313
+ path, rec, passed, now, extra), rec, path
314
+
315
+
316
+ def _build_row(loki_dir: str, now: Optional[float]) -> tuple:
317
+ """The build axis. It has no `pass` key -- outcome comes from `status`.
318
+
319
+ The writer's three-way classification is deliberate (run.sh:9690): verified
320
+ and failed are outcomes, while not_run is an honest gap and not_applicable
321
+ means the stack positively has no build phase. Neither gap is a pass.
322
+ """
323
+ path = _p(loki_dir, *_BUILD_RESULTS)
324
+ rec = _read_json(path)
325
+ present = isinstance(rec, dict)
326
+ status = str(rec.get("status") or "") if present else ""
327
+ if status == "verified":
328
+ passed: Optional[bool] = True
329
+ elif status == "failed":
330
+ passed = False
331
+ else:
332
+ passed = UNKNOWN
333
+ extra = {
334
+ "command": (rec.get("command") or UNKNOWN) if present else UNKNOWN,
335
+ "exit_code": rec.get("exit_code") if present else UNKNOWN,
336
+ "ran": rec.get("ran") if present else UNKNOWN,
337
+ "applicable": rec.get("applicable") if present else UNKNOWN,
338
+ }
339
+ row = _row("build", ".loki/quality/build-results.json", path, rec, passed, now, extra)
340
+ if present and passed is UNKNOWN:
341
+ row["reason"] = "build status %r is not an outcome" % (rec.get("status"),)
342
+ return row, rec, path
343
+
344
+
345
+ def _coverage(loki_dir: str, now: Optional[float]) -> tuple:
346
+ """Coverage facts. NOT a suite row: coverage has no pass/fail outcome.
347
+
348
+ Measurement is opt-in (run.sh:11734), so the default-off record carries
349
+ measured=false with a reason. pct is None when unmeasured and stays None
350
+ here; an unmeasured coverage is never rendered as 0 percent.
351
+ """
352
+ path = _p(loki_dir, *_COVERAGE)
353
+ rec = _read_json(path)
354
+ if not isinstance(rec, dict):
355
+ return {
356
+ "measured": False,
357
+ "pct": UNKNOWN,
358
+ "tool": UNKNOWN,
359
+ "threshold": UNKNOWN,
360
+ "enforced": UNKNOWN,
361
+ "blocked": UNKNOWN,
362
+ "reason": "no .loki/quality/coverage.json on disk; coverage was never recorded",
363
+ "source": ".loki/quality/coverage.json",
364
+ "freshness_s": None,
365
+ }, rec, path
366
+ measured = rec.get("measured") is True
367
+ return {
368
+ "measured": measured,
369
+ "pct": rec.get("pct") if measured else UNKNOWN,
370
+ "tool": rec.get("tool") or UNKNOWN,
371
+ "threshold": rec.get("threshold"),
372
+ "enforced": rec.get("enforced"),
373
+ "blocked": rec.get("blocked"),
374
+ "reason": None if measured else (rec.get("reason") or "coverage not measured"),
375
+ "source": ".loki/quality/coverage.json",
376
+ "freshness_s": _freshness([_mtime(path)], now=now),
377
+ }, rec, path
378
+
379
+
380
+ # ---------------------------------------------------------------------------
381
+ # public API
382
+ # ---------------------------------------------------------------------------
383
+
384
+ def list_test_results(loki_dir: str, now: Optional[float] = None) -> dict:
385
+ """Latest test / CI results from .loki/quality/, one row per axis.
386
+
387
+ Returns an ENVELOPE, not a bare list, because the contract requires an
388
+ explicit reason when the result is empty and a list cannot carry one:
389
+
390
+ {"items": [...], "latest": {...}, "coverage": {...}, "iteration": int|None,
391
+ "source": [...], "freshness_s": int|None, "reason": None|str}
392
+
393
+ `items` is one row per axis (tests, static_analysis, build) -- see the
394
+ module docstring on why an axis is the finest decomposition any writer
395
+ produces. `latest` is the headline: the test axis, the answer to "did the
396
+ last run's tests pass".
397
+
398
+ THE PROPERTY THAT MATTERS: a suite whose outcome was never recorded reads
399
+ passed=UNKNOWN, never True and never False, and its `reason` says why. A
400
+ gate that did not run is not a gate that passed. This holds for an absent
401
+ file, for pass="inconclusive", and for status not_run / no_tests_run.
402
+
403
+ `reason` is None when at least one axis recorded an outcome. When nothing
404
+ on disk recorded anything it states so; rows are still returned (all
405
+ UNKNOWN) rather than padded with placeholders or dropped.
406
+ """
407
+ envelope: dict = {
408
+ "items": [],
409
+ "latest": None,
410
+ "coverage": None,
411
+ "iteration": None,
412
+ "source": list(_SOURCE_PATHS),
413
+ "freshness_s": None,
414
+ "reason": None,
415
+ }
416
+ if not loki_dir or not os.path.isdir(loki_dir):
417
+ envelope["reason"] = "no .loki directory at %s" % (loki_dir,)
418
+ return envelope
419
+
420
+ tests_row, _tr, tests_path = _tests_row(loki_dir, now)
421
+ sa_row, _sa, sa_path = _static_analysis_row(loki_dir, now)
422
+ build_row, _br, build_path = _build_row(loki_dir, now)
423
+ coverage, _cv, cov_path = _coverage(loki_dir, now)
424
+
425
+ rows = [tests_row, sa_row, build_row]
426
+ envelope["items"] = rows
427
+ envelope["latest"] = tests_row
428
+ envelope["coverage"] = coverage
429
+ envelope["iteration"] = _iteration(loki_dir)
430
+ envelope["freshness_s"] = _freshness(
431
+ [_mtime(tests_path), _mtime(sa_path), _mtime(build_path), _mtime(cov_path)],
432
+ now=now,
433
+ )
434
+
435
+ if not any(r["recorded"] for r in rows):
436
+ envelope["reason"] = (
437
+ "no quality artifacts under .loki/quality/: no gate has recorded a "
438
+ "result, so every suite reads UNKNOWN (not passed)"
439
+ )
440
+ elif all(r["passed"] is UNKNOWN for r in rows):
441
+ envelope["reason"] = (
442
+ "artifacts present but no suite recorded a pass/fail outcome"
443
+ )
444
+ return envelope
@@ -496,9 +496,55 @@ async def list_runs(
496
496
  listing is narrowed to the caller's own projects.
497
497
  """
498
498
  if tenant_ctx.is_global_admin or not tenant_ctx.auth_enabled:
499
- return await runs_mod.list_runs(
499
+ _rows = await runs_mod.list_runs(
500
500
  db, project_id=project_id, status=status, limit=limit, offset=offset,
501
501
  )
502
+ # ONE CANONICAL RUN SURFACE, not two divergent APIs.
503
+ #
504
+ # These routes are real and mounted, but the SQL store behind them has
505
+ # NO RUNTIME WRITER: the only writers are this module and
506
+ # dashboard/runs.py, both API-side, while the actual run lifecycle
507
+ # writes to the filesystem (autonomy/run.sh, runner/council.ts,
508
+ # runner/checkpoint.ts). dashboard/runs.py contains zero filesystem
509
+ # references. So for every real `loki start` this returned [].
510
+ #
511
+ # The fix is a fallback here rather than a second /runs endpoint. A
512
+ # second endpoint would have produced a second surface disagreeing
513
+ # with this one, which is worse than an empty table.
514
+ #
515
+ # SCOPED DELIBERATELY TO THIS BRANCH. The filesystem adapter has no
516
+ # tenancy concept -- its signature is (loki_dir, now) and it carries
517
+ # zero project/tenant fields. Wiring it into the tenant-scoped paths
518
+ # below would let filesystem runs cross a boundary that is currently
519
+ # fail-closed (tenant_id is None -> []), trading a security property
520
+ # for a data fix. This branch is ALREADY unscoped: it is taken only
521
+ # when the caller is a global admin or auth is off entirely, which is
522
+ # exactly the local `loki start` case where the data is missing.
523
+ #
524
+ # Fallback only when SQL yields nothing, so a populated store always
525
+ # wins and enterprise behaviour is unchanged.
526
+ if not _rows and project_id is None:
527
+ try:
528
+ from . import api_runs as _fs_runs
529
+
530
+ _loki = os.environ.get("LOKI_DIR") or os.path.join(os.getcwd(), ".loki")
531
+ _fs = _fs_runs.list_runs(_loki)
532
+ # The adapter returns an envelope carrying source, freshness
533
+ # and an explicit reason when empty. Never fabricate a row:
534
+ # an empty filesystem result stays empty and the reason
535
+ # travels with it.
536
+ if _fs.get("runs"):
537
+ return _fs["runs"]
538
+ except Exception as _fs_exc: # pragma: no cover - never fatal
539
+ # This module defines no module-level logger, and referencing
540
+ # one would raise NameError inside the very handler whose job
541
+ # is to guarantee the fallback can never be fatal. Resolve it
542
+ # locally instead.
543
+ import logging as _logging
544
+
545
+ _logging.getLogger(__name__).warning(
546
+ "filesystem run fallback unavailable: %s", _fs_exc)
547
+ return _rows
502
548
 
503
549
  if project_id is not None:
504
550
  # Targeting a specific project: enforce it belongs to the caller.
@@ -978,12 +978,66 @@ app.add_middleware(
978
978
  allow_headers=["Content-Type", "Authorization", "X-Requested-With"],
979
979
  )
980
980
 
981
+ # RESPONSE COMPRESSION. Measured, not assumed: the served dashboard bundle
982
+ # (dashboard/static/index.html) is 779,725 bytes raw and 150,341 gzipped --
983
+ # an 81% reduction. Until now only CORS and the collab WS auth middleware were
984
+ # registered, so every dashboard load shipped the full 780KB.
985
+ #
986
+ # That single fact is the most plausible cause of "the dashboard feels slow":
987
+ # it is not a rendering problem, it is 630KB of avoidable transfer on first
988
+ # paint, and it costs one middleware to fix.
989
+ #
990
+ # minimum_size=1024 leaves small JSON responses uncompressed, where the CPU
991
+ # round-trip outweighs the saving. GZipMiddleware is stdlib-backed and does
992
+ # not negotiate brotli, so it cannot fail closed on a client that only sends
993
+ # `Accept-Encoding: gzip` -- responses stay correct either way.
994
+ #
995
+ # Streaming endpoints are unaffected in a way that matters: Starlette's
996
+ # GZipMiddleware passes through responses it cannot buffer, so SSE and the
997
+ # WebSocket upgrade path keep their existing behaviour.
998
+ try:
999
+ from starlette.middleware.gzip import GZipMiddleware
1000
+
1001
+ app.add_middleware(GZipMiddleware, minimum_size=1024)
1002
+ except Exception as _gzip_exc: # pragma: no cover - starlette always ships it
1003
+ # Never fatal: a dashboard that starts uncompressed is strictly better
1004
+ # than one that does not start.
1005
+ logger.warning("gzip compression unavailable: %s", _gzip_exc)
1006
+
981
1007
  # Static file serving is configured at the end of the file (after all API routes)
982
1008
 
983
1009
  # Mount V2 API router
984
1010
  from .api_v2 import router as api_v2_router
985
1011
  app.include_router(api_v2_router)
986
1012
 
1013
+ # Mount the operator router: the filesystem evidence readers (run detail, gate
1014
+ # results, receipts, releases) reachable over HTTP. Before this they were
1015
+ # libraries only the test suite imported -- four readers, none of them
1016
+ # reachable by a user.
1017
+ #
1018
+ # THIS MOUNT FAILS CLOSED, and the first version did not. It was wrapped in a
1019
+ # bare `except Exception: logger.warning(...)`, which swallows a typo, a
1020
+ # refactor that breaks an import, or a syntax error just as happily as a
1021
+ # genuinely absent optional dependency. The dashboard then starts perfectly,
1022
+ # reports healthy, and serves 404 on every operator path -- the exact
1023
+ # "monitoring surface that is silently blind" failure this whole module exists
1024
+ # to prevent. It also makes the mount test fail on CI with no stated cause,
1025
+ # which is how it was found.
1026
+ #
1027
+ # A missing OPTIONAL dependency is the only tolerable degradation, so only
1028
+ # ImportError is caught, and even that is logged at error level rather than
1029
+ # warning. Every other exception propagates and takes the dashboard down,
1030
+ # because a dashboard that cannot show run evidence is not a dashboard that
1031
+ # should quietly claim to be up.
1032
+ try:
1033
+ from .api_operator import router as api_operator_router
1034
+ except ImportError as _operator_exc: # pragma: no cover - optional dep absent
1035
+ logger.error(
1036
+ "operator API could not be imported, /api/operator/* will 404: %s",
1037
+ _operator_exc)
1038
+ else:
1039
+ app.include_router(api_operator_router)
1040
+
987
1041
  # Phase Merge-4: Mount Purple Lab FastAPI app under /lab/ so it appears as a
988
1042
  # sidebar entry in Dashboard. Same `app` is also wrapped by `standalone_app`
989
1043
  # in web-app/server.py for `loki web` (port 57375). One source of truth, no