loki-mode 10.5.16 → 10.5.19
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/.claude-plugin/marketplace.json +12 -0
- package/README.md +878 -29
- package/SKILL.md +3 -3
- package/VERSION +1 -1
- package/autonomy/lib/backlog.py +280 -0
- package/autonomy/lib/loki_yaml.py +225 -0
- package/autonomy/lib/proof-generator.py +251 -0
- package/autonomy/lib/proof-verify.py +1 -0
- package/autonomy/loki +117 -12
- package/autonomy/run.sh +27 -2
- package/autonomy/verify.sh +11 -0
- package/completions/_loki +1 -0
- package/completions/loki.bash +2 -2
- package/dashboard/__init__.py +1 -1
- package/dashboard/api_start.py +463 -0
- package/dashboard/server.py +31 -0
- package/dashboard/static/start.html +100 -0
- package/docs/COMPARISON.md +6 -19
- package/docs/COMPETITIVE-ANALYSIS.md +6 -41
- package/docs/CRASH-REPORTING-PLAN.md +1 -1
- package/docs/ENTERPRISE-IDENTITY-ROADMAP.md +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/docs/PRIVACY.md +1 -1
- package/docs/R8-SHAREABLE-TEAM-ASSETS-PLAN.md +1 -1
- package/docs/WANG-PRINCIPLES-PLAN.md +1 -1
- package/docs/architecture/STATE-MACHINES.md +18 -27
- package/docs/architecture/bmad-integration-validation.md +2 -2
- package/docs/audit-logging.md +12 -86
- package/docs/authentication.md +4 -4
- package/docs/authorization.md +15 -47
- package/docs/auto-claude-comparison.md +1 -33
- package/docs/certification/01-core-concepts/lab.md +3 -3
- package/docs/certification/01-core-concepts/lesson.md +11 -22
- package/docs/certification/01-core-concepts/quiz.md +7 -7
- package/docs/certification/02-enterprise-features/lab.md +0 -2
- package/docs/certification/02-enterprise-features/lesson.md +0 -10
- package/docs/certification/04-production-deployment/lab.md +1 -1
- package/docs/certification/05-troubleshooting/lab.md +2 -2
- package/docs/certification/README.md +1 -1
- package/docs/certification/answer-key.md +37 -20
- package/docs/certification/certification-exam.md +7 -7
- package/docs/competitive/emergence-others-analysis.md +3 -3
- package/docs/competitive/replit-lovable-analysis.md +2 -2
- package/docs/cursor-comparison.md +2 -2
- package/docs/dashboard-guide.md +14 -2
- package/docs/dev/architecture-reference.md +2 -2
- package/docs/dev/project-structure.md +1 -1
- package/docs/enterprise/architecture.md +11 -8
- package/docs/enterprise/integration-cookbook.md +23 -14
- package/docs/enterprise/migration.md +5 -9
- package/docs/enterprise/performance.md +3 -6
- package/docs/enterprise/sdk-guide.md +1 -1
- package/docs/enterprise/security.md +8 -17
- package/docs/exit-codes.md +29 -0
- package/docs/git-workflow.md +4 -62
- package/docs/network-security.md +3 -13
- package/docs/plans/ULTRAPLAN-FORGE-BAAS.md +1 -1
- package/docs/prd-purple-lab-platform-v2.md +1 -1
- package/docs/prd-purple-lab-platform.md +1 -3
- package/docs/research-2026-07/GAP-ANALYSIS-BACKLOG.md +1 -1
- package/docs/show-hn-post.md +3 -4
- package/docs/siem-integration.md +8 -10
- package/docs/test-scenarios/edge-cases.md +2 -2
- package/docs/test-scenarios/enterprise-scenarios.md +4 -4
- package/docs/v10/BOARD.md +21 -11
- package/docs/v10/DECISIONS.md +36 -0
- package/docs/v10/FOUNDER-QUEUE.md +1 -0
- package/docs/v10/GUIDE.md +5 -3
- package/docs/v10/METRICS.md +36 -0
- package/docs/v10/PROGRESS.md +76 -0
- package/docs/walkthrough/README.md +1 -7
- package/loki-ts/dist/loki.js +415 -407
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/web-app/dist/assets/{AdminPage-B1zdOJaF.js → AdminPage-BH_7fpoz.js} +1 -1
- package/web-app/dist/assets/{Avatar-DKjAJ_w7.js → Avatar-BzyhrX0s.js} +1 -1
- package/web-app/dist/assets/{Badge-DP-UrPLv.js → Badge-CL05xwgC.js} +1 -1
- package/web-app/dist/assets/{Button-c8qBEA2K.js → Button-BVHrGLGH.js} +1 -1
- package/web-app/dist/assets/{CockpitPage-CFljzGbi.js → CockpitPage-B9BAEqD-.js} +1 -1
- package/web-app/dist/assets/{ComparePage-BNKqdNRu.js → ComparePage-D4grckhY.js} +1 -1
- package/web-app/dist/assets/{ErrorBoundary-DNkpXGt5.js → ErrorBoundary-Bg52u7eg.js} +1 -1
- package/web-app/dist/assets/{EvidenceReceiptPanel-CQd49V4Z.js → EvidenceReceiptPanel-B15kRZbJ.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-B9wji_yi.js → GitHubIssuesPanel-DP_BhJSC.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-BkfCV4ag.js → GitHubPRsPanel-DJBu83u9.js} +1 -1
- package/web-app/dist/assets/{HomePage-BVV3Cowy.js → HomePage-6mw4uvLs.js} +2 -2
- package/web-app/dist/assets/{LoginPage-CWiOenLJ.js → LoginPage-BWWzFHif.js} +1 -1
- package/web-app/dist/assets/{MagicPage-DckrjTVd.js → MagicPage-CSyo7k5t.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-DNBnH68C.js → MetricsPage-Buq03cch.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-Ca8za8mL.js → NotFoundPage-BG8vuV4g.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-C92sm9xi.js → ProjectPage-D7eJn--6.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage-BxsCyJ95.js → ProjectsPage-DXPycvjA.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-BKg-6aHI.js → SettingsPage-Blvav87U.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-Bw_fI5rG.js → ShowcasePage-ZCAKRsNN.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-Bc-2OWUp.js → SystemSettingsPage-ClzjKfI4.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-GsYRD1b9.js → TeamsPage-CKp1-WGX.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-BN7ZlULA.js → TemplatesPage-C0yAp2Yl.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-d7__ysTd.js → TerminalOutput-C3KbV8E7.js} +1 -1
- package/web-app/dist/assets/{activity-ycOMpCGz.js → activity-CG5PtpVI.js} +1 -1
- package/web-app/dist/assets/{bell-B93o4lsL.js → bell-CyLp--NR.js} +1 -1
- package/web-app/dist/assets/{bot-CB0kmriG.js → bot-Dp0zY9t7.js} +1 -1
- package/web-app/dist/assets/{check-BAYNAOEO.js → check-ipranz8L.js} +1 -1
- package/web-app/dist/assets/{chevron-left-Bgu8UgyR.js → chevron-left-DhYs9D3t.js} +1 -1
- package/web-app/dist/assets/{circle-alert-n_FciWUP.js → circle-alert-BLAxBNgz.js} +1 -1
- package/web-app/dist/assets/{clock-CNceeS66.js → clock-DzocnapF.js} +1 -1
- package/web-app/dist/assets/{cloud-CJBEdb7m.js → cloud-B3YsIUwq.js} +1 -1
- package/web-app/dist/assets/{code-xml-BHnKVqrH.js → code-xml-BPcy_Eqm.js} +1 -1
- package/web-app/dist/assets/{database-DSVoL7Pl.js → database-DOuuclZf.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-48dvOe4H.js → dollar-sign-o77cj-xB.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-8yzS5DeZ.js → file-code-corner-BZ2VMnYA.js} +1 -1
- package/web-app/dist/assets/{file-plus-BcuVrrey.js → file-plus-DeHfGncj.js} +1 -1
- package/web-app/dist/assets/{globe-Bb-O58oN.js → globe-DmIW-phf.js} +1 -1
- package/web-app/dist/assets/{hammer-DeMMY6xW.js → hammer-CoW8R8V5.js} +1 -1
- package/web-app/dist/assets/{index-9hue9wt2.js → index-5rCTAnsF.js} +3 -3
- package/web-app/dist/assets/{layers-DmCoMrQC.js → layers-DGEk-2RQ.js} +1 -1
- package/web-app/dist/assets/{loader-circle-CJvjM7JW.js → loader-circle-DXn-F-H5.js} +1 -1
- package/web-app/dist/assets/{lock-Dpje5cWO.js → lock-D5nAjeUu.js} +1 -1
- package/web-app/dist/assets/{package-mA_hCaf8.js → package-D8Drpzi8.js} +1 -1
- package/web-app/dist/assets/{plus-Db0JnAoC.js → plus-C-0T9dAu.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-DbCL1y7h.js → refresh-cw-AyYQQPZG.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-Bt6pp50d.js → rotate-ccw-DYbslLfT.js} +1 -1
- package/web-app/dist/assets/{scroll-text-7j0hXxm7.js → scroll-text-CQFQhF_6.js} +1 -1
- package/web-app/dist/assets/{server-BMJYtpVZ.js → server-DxNytQEJ.js} +1 -1
- package/web-app/dist/assets/{shield-alert-Bu8Pbdy-.js → shield-alert-Xj5wGUSK.js} +1 -1
- package/web-app/dist/assets/{thumbs-up-8erzRJ8k.js → thumbs-up-DW0j9Yxz.js} +1 -1
- package/web-app/dist/assets/{trash-2-DBC_D_Hv.js → trash-2-VOdpiJ1e.js} +1 -1
- package/web-app/dist/assets/{trending-down-DMNq5hSm.js → trending-down-eTizH4_b.js} +1 -1
- package/web-app/dist/assets/{trending-up-9b1IqBqR.js → trending-up-agYVl7bU.js} +1 -1
- package/web-app/dist/assets/{upload-SoA0iiLG.js → upload-DQ3NfXA_.js} +1 -1
- package/web-app/dist/assets/{usePolling-DLP2FX9M.js → usePolling-BKETnkDT.js} +1 -1
- package/web-app/dist/assets/{user-D7Pb9wW3.js → user-Cv6BNmOT.js} +1 -1
- package/web-app/dist/index.html +1 -1
- package/docs/README-FULL.md +0 -977
|
@@ -1115,6 +1115,230 @@ def _empty_tree_sha(repo_dir):
|
|
|
1115
1115
|
return out.stdout.strip() if out.returncode == 0 else ""
|
|
1116
1116
|
|
|
1117
1117
|
|
|
1118
|
+
# A-118 / D47: tests_integrity. Did the run WEAKEN the tests that vouch for it?
|
|
1119
|
+
# CFG_ALWAYS is copied from loki-ts/src/engine10/stages/verify.ts (A-115); keep in sync.
|
|
1120
|
+
# The comparison is base vs the final WORKING TREE plus untracked files (like
|
|
1121
|
+
# collect_workspace_diff), so uncommitted work is covered.
|
|
1122
|
+
_TI_CFG_ALWAYS = re.compile(r"(^|/)(conftest\.py|\.?pytest\.(?:ini|toml)|tox\.ini|jest\.config\.[\w.]+|vitest\.config\.[\w.]+|\.mocharc[\w.]*)$")
|
|
1123
|
+
_TI_CFG_SHARED = re.compile(r"(^|/)(setup\.cfg|pyproject\.toml|package\.json)$")
|
|
1124
|
+
_TI_TEST_FILE = re.compile(
|
|
1125
|
+
r"(^|/)(tests?|__tests__|spec|specs)/|\.(test|spec)\.[\w]+$|(^|/)test_[^/]*\.py$|_test\.(py|go|rs)$|(^|/)conftest\.py$")
|
|
1126
|
+
_TI_SKIP_JS = re.compile(
|
|
1127
|
+
# options object only as the 2nd argument: title (string or identifier), then `, {`
|
|
1128
|
+
r"\b(test|it|describe|suite|context|specify)\s*\(\s*(?:'[^'\n]*'|\"[^\"\n]*\"|`[^`\n]*`|[\w.$]+)\s*,\s*\{[^}]*\b(skip|todo)\s*:\s*(?!(?:false|0|null|undefined)\b)\S"
|
|
1129
|
+
r"|\b(it|test|describe|suite|context|specify)\s*\.\s*(skip|only|todo)\b"
|
|
1130
|
+
r"|\bx(it|test|describe)\s*\(|\bf(it|describe)\s*\("
|
|
1131
|
+
r"|\b(t|ctx)\s*\.\s*(skip|todo)\s*\(")
|
|
1132
|
+
_TI_SKIP_PY = re.compile(
|
|
1133
|
+
r"\b(?:pytest\.)?mark\.(skip|skipif|xfail)\b|\bpytest\.(skip|xfail|importorskip)\s*\("
|
|
1134
|
+
r"|@(?:unittest\.)?(skip|skipIf|skipUnless|expectedFailure)\b|\bSkipTest\b|\bskipTest\s*\(")
|
|
1135
|
+
_TI_SKIP_GO = re.compile(r"\b[tb]\.Skip(f|Now)?\s*\(")
|
|
1136
|
+
_TI_SKIP_RS = re.compile(r"#\[ignore")
|
|
1137
|
+
_TI_SKIP_BY_EXT = (
|
|
1138
|
+
(("js", "jsx", "ts", "tsx", "mjs", "cjs"), _TI_SKIP_JS),
|
|
1139
|
+
(("py",), _TI_SKIP_PY), (("go",), _TI_SKIP_GO), (("rs",), _TI_SKIP_RS))
|
|
1140
|
+
_TI_ASSERT = re.compile(r"\bassert|\bexpect\(|\.should\b|\.to[A-Z]\w*\(|\bt\.(Error|Fatal)")
|
|
1141
|
+
_TI_PKG_KEYS = ("jest", "mocha", "vitest", "ava")
|
|
1142
|
+
|
|
1143
|
+
|
|
1144
|
+
def _ti_skip_re(path):
|
|
1145
|
+
ext = path.rsplit(".", 1)[-1].lower() if "." in path else ""
|
|
1146
|
+
for exts, rx in _TI_SKIP_BY_EXT:
|
|
1147
|
+
if ext in exts:
|
|
1148
|
+
return rx
|
|
1149
|
+
return None
|
|
1150
|
+
|
|
1151
|
+
|
|
1152
|
+
def _ti_run(repo, *args):
|
|
1153
|
+
"""Run a read-only git command; returns text decoded from bytes (so a lone
|
|
1154
|
+
CR is never a line break; callers split on \\n only), or None on failure."""
|
|
1155
|
+
try:
|
|
1156
|
+
r = subprocess.run(["git", "-c", "core.quotePath=false", *args], cwd=repo,
|
|
1157
|
+
capture_output=True, timeout=30)
|
|
1158
|
+
except Exception:
|
|
1159
|
+
return None
|
|
1160
|
+
if r.returncode != 0:
|
|
1161
|
+
return None
|
|
1162
|
+
return r.stdout.decode("utf-8", "replace")
|
|
1163
|
+
|
|
1164
|
+
|
|
1165
|
+
def _ti_pkg_view(text, _path=""):
|
|
1166
|
+
"""Test-relevant slice of a package.json, or None when unparseable."""
|
|
1167
|
+
try:
|
|
1168
|
+
d = json.loads(text) if text.strip() else {}
|
|
1169
|
+
except ValueError:
|
|
1170
|
+
return None
|
|
1171
|
+
if not isinstance(d, dict):
|
|
1172
|
+
return None
|
|
1173
|
+
scripts = d.get("scripts") if isinstance(d.get("scripts"), dict) else {}
|
|
1174
|
+
view = {k: v for k, v in scripts.items() if re.match(r"^(pre|post)?test(:.*)?$", k)}
|
|
1175
|
+
return {"scripts": view, **{k: d[k] for k in _TI_PKG_KEYS if k in d}}
|
|
1176
|
+
|
|
1177
|
+
|
|
1178
|
+
_TI_PYTEST_KEYS = ("addopts", "testpaths", "python_files", "python_functions",
|
|
1179
|
+
"python_classes", "norecursedirs")
|
|
1180
|
+
|
|
1181
|
+
|
|
1182
|
+
def _ti_pytest_view(text, path):
|
|
1183
|
+
"""The test-selection keys of a pytest config, or None when unparseable.
|
|
1184
|
+
Parsed (tomllib / configparser), so a multi-line addopts edit is seen.
|
|
1185
|
+
ponytail: without tomllib (python < 3.11) try tomli, else compare only the
|
|
1186
|
+
whitespace-normalized [tool.pytest.ini_options] section text."""
|
|
1187
|
+
if not text.strip():
|
|
1188
|
+
return {}
|
|
1189
|
+
if path.endswith(".toml"):
|
|
1190
|
+
try:
|
|
1191
|
+
try:
|
|
1192
|
+
import tomllib
|
|
1193
|
+
except ImportError:
|
|
1194
|
+
import tomli as tomllib
|
|
1195
|
+
except ImportError:
|
|
1196
|
+
m = re.search(r"^\[tool\.pytest\.ini_options\][ \t]*(?:#.*)?$(.*?)(?=^\[[^\[\]\n]+\][ \t]*(?:#.*)?$|\Z)",
|
|
1197
|
+
text, re.M | re.S)
|
|
1198
|
+
return {"ini_options": " ".join(m.group(1).split())} if m else {}
|
|
1199
|
+
try:
|
|
1200
|
+
d = tomllib.loads(text)
|
|
1201
|
+
except Exception:
|
|
1202
|
+
return None
|
|
1203
|
+
t = d.get("tool", {}).get("pytest", {})
|
|
1204
|
+
t = t.get("ini_options", t) if isinstance(t, dict) else {}
|
|
1205
|
+
return {k: t[k] for k in _TI_PYTEST_KEYS if k in t}
|
|
1206
|
+
import configparser
|
|
1207
|
+
cp = configparser.ConfigParser(interpolation=None, strict=False)
|
|
1208
|
+
try:
|
|
1209
|
+
cp.read_string(text)
|
|
1210
|
+
except Exception:
|
|
1211
|
+
return None
|
|
1212
|
+
out = {}
|
|
1213
|
+
for sec in ("pytest", "tool:pytest"):
|
|
1214
|
+
if cp.has_section(sec):
|
|
1215
|
+
for k in _TI_PYTEST_KEYS:
|
|
1216
|
+
if cp.has_option(sec, k):
|
|
1217
|
+
out[sec + "." + k] = cp.get(sec, k)
|
|
1218
|
+
return out
|
|
1219
|
+
|
|
1220
|
+
|
|
1221
|
+
_TI_SELECT_PY = re.compile(r"\bcollect_ignore(?:_glob)?\b|\bpytest_collection_modifyitems\b")
|
|
1222
|
+
_TI_PYTEST_NAMED = re.compile(r"(^|/)(conftest\.py|\.?pytest\.ini|tox\.ini)$")
|
|
1223
|
+
|
|
1224
|
+
|
|
1225
|
+
def _ti_base_has_pytest_config(target_dir, base):
|
|
1226
|
+
names = _ti_run(target_dir, "ls-tree", "-r", "--name-only", "-z", base) or ""
|
|
1227
|
+
for n in names.split("\0"):
|
|
1228
|
+
if _TI_PYTEST_NAMED.search(n):
|
|
1229
|
+
return True
|
|
1230
|
+
if n.endswith(("pyproject.toml", "setup.cfg")) and \
|
|
1231
|
+
_ti_pytest_view(_ti_run(target_dir, "show", "%s:./%s" % (base, n)) or "", n):
|
|
1232
|
+
return True
|
|
1233
|
+
return False
|
|
1234
|
+
|
|
1235
|
+
|
|
1236
|
+
def _ti_added_config_selects(path, text):
|
|
1237
|
+
"""True when a newly added pytest config changes test selection (unparseable counts)."""
|
|
1238
|
+
if path.endswith(".py"):
|
|
1239
|
+
return bool(_TI_SELECT_PY.search(text))
|
|
1240
|
+
if path.endswith((".ini", ".toml", ".cfg")):
|
|
1241
|
+
v = _ti_pytest_view(text, path)
|
|
1242
|
+
return v is None or bool(v)
|
|
1243
|
+
return False
|
|
1244
|
+
|
|
1245
|
+
|
|
1246
|
+
def _collect_tests_integrity(target_dir, base):
|
|
1247
|
+
"""Return {"weakened": [...], "assertions_edited": [...], "disclosed": [...]} or None.
|
|
1248
|
+
|
|
1249
|
+
Derived from base vs the final working tree (committed, staged, unstaged and
|
|
1250
|
+
untracked). None when there is no usable diff (no repo, no base)."""
|
|
1251
|
+
if not base:
|
|
1252
|
+
return None
|
|
1253
|
+
names = _ti_run(target_dir, "diff", "--relative", "--name-status", "-M", "-z", base, "--")
|
|
1254
|
+
if names is None:
|
|
1255
|
+
return None
|
|
1256
|
+
weakened, edited, disclosed = [], [], []
|
|
1257
|
+
greenfield = base == _empty_tree_sha(target_dir)
|
|
1258
|
+
toks = names.split("\0")
|
|
1259
|
+
entries, i = [], 0 # (status, old_path, new_path)
|
|
1260
|
+
while i < len(toks) and toks[i]:
|
|
1261
|
+
st = toks[i][:1]
|
|
1262
|
+
if st in "RC" and i + 2 < len(toks):
|
|
1263
|
+
entries.append((st, toks[i + 1], toks[i + 2]))
|
|
1264
|
+
i += 3
|
|
1265
|
+
else:
|
|
1266
|
+
entries.append((st, toks[i + 1] if i + 1 < len(toks) else "", toks[i + 1] if i + 1 < len(toks) else ""))
|
|
1267
|
+
i += 2
|
|
1268
|
+
untracked = _ti_run(target_dir, "ls-files", "--others", "--exclude-standard", "-z") or ""
|
|
1269
|
+
for u in untracked.split("\0"):
|
|
1270
|
+
if u and not u.startswith(".loki/"):
|
|
1271
|
+
entries.append(("A", u, u))
|
|
1272
|
+
for st, old, new in entries:
|
|
1273
|
+
if st in ("D", "R") and _TI_TEST_FILE.search(old):
|
|
1274
|
+
weakened.append("test file %s: %s" % ("deleted" if st == "D" else "renamed", old))
|
|
1275
|
+
if not greenfield:
|
|
1276
|
+
for st, old, new in entries:
|
|
1277
|
+
path = new if st != "D" else old
|
|
1278
|
+
is_always = bool(_TI_CFG_ALWAYS.search(path))
|
|
1279
|
+
if not (is_always or _TI_CFG_SHARED.search(path)):
|
|
1280
|
+
continue
|
|
1281
|
+
if st == "A": # no base version: disclose, unless it re-selects tests over a base pytest config
|
|
1282
|
+
try:
|
|
1283
|
+
with open(os.path.join(target_dir, new), encoding="utf-8", errors="replace") as h:
|
|
1284
|
+
text = h.read()
|
|
1285
|
+
except OSError:
|
|
1286
|
+
text = ""
|
|
1287
|
+
if _ti_added_config_selects(path, text) and _ti_base_has_pytest_config(target_dir, base):
|
|
1288
|
+
weakened.append("test runner config changed: " + path)
|
|
1289
|
+
else:
|
|
1290
|
+
disclosed.append("new test runner config: " + path)
|
|
1291
|
+
elif is_always:
|
|
1292
|
+
weakened.append("test runner config changed: " + path)
|
|
1293
|
+
else:
|
|
1294
|
+
before = _ti_run(target_dir, "show", "%s:./%s" % (base, old)) or ""
|
|
1295
|
+
try:
|
|
1296
|
+
with open(os.path.join(target_dir, new), encoding="utf-8", errors="replace") as h:
|
|
1297
|
+
after = h.read()
|
|
1298
|
+
except OSError:
|
|
1299
|
+
after = ""
|
|
1300
|
+
view = _ti_pkg_view if path.endswith("package.json") else _ti_pytest_view
|
|
1301
|
+
b, a = view(before, path), view(after, path)
|
|
1302
|
+
if b is None or a is None or b != a:
|
|
1303
|
+
weakened.append("test runner config changed: " + path)
|
|
1304
|
+
# Skip markers: added lines of tracked test files, and the content of untracked ones.
|
|
1305
|
+
patch = _ti_run(target_dir, "diff", "--relative", "-U0", "-M", base, "--") or ""
|
|
1306
|
+
cur, rx, removed_assert = None, None, False
|
|
1307
|
+
for line in patch.split("\n") + ["diff --git a/ b/"]:
|
|
1308
|
+
if line.startswith("diff --git "):
|
|
1309
|
+
if cur and removed_assert and cur not in edited:
|
|
1310
|
+
edited.append(cur)
|
|
1311
|
+
cur, rx, removed_assert = None, None, False
|
|
1312
|
+
elif line.startswith("+++ b/"):
|
|
1313
|
+
cur = line[6:] if _TI_TEST_FILE.search(line[6:]) else None
|
|
1314
|
+
rx = _ti_skip_re(cur) if cur else None
|
|
1315
|
+
elif cur and line.startswith("+") and rx and rx.search(line):
|
|
1316
|
+
weakened.append("skip added in %s" % cur)
|
|
1317
|
+
elif cur and line.startswith("-") and not line.startswith("---") \
|
|
1318
|
+
and _TI_ASSERT.search(line):
|
|
1319
|
+
removed_assert = True
|
|
1320
|
+
for u in untracked.split("\0"):
|
|
1321
|
+
if not u or u.startswith(".loki/") or not _TI_TEST_FILE.search(u):
|
|
1322
|
+
continue
|
|
1323
|
+
rx = _ti_skip_re(u)
|
|
1324
|
+
if not rx:
|
|
1325
|
+
continue
|
|
1326
|
+
try:
|
|
1327
|
+
with open(os.path.join(target_dir, u), "rb") as h:
|
|
1328
|
+
text = h.read().decode("utf-8", "replace")
|
|
1329
|
+
except OSError:
|
|
1330
|
+
continue
|
|
1331
|
+
if any(rx.search(ln) for ln in text.split("\n")):
|
|
1332
|
+
weakened.append("skip added in %s" % u)
|
|
1333
|
+
uniq = []
|
|
1334
|
+
for w in weakened:
|
|
1335
|
+
if w not in uniq:
|
|
1336
|
+
uniq.append(w)
|
|
1337
|
+
if not uniq and not edited and not disclosed:
|
|
1338
|
+
return None
|
|
1339
|
+
return {"weakened": uniq, "assertions_edited": edited, "disclosed": disclosed}
|
|
1340
|
+
|
|
1341
|
+
|
|
1118
1342
|
def _collect_iterations(loki_dir):
|
|
1119
1343
|
completed = _read_json(os.path.join(loki_dir, "queue", "completed.json"), default=[])
|
|
1120
1344
|
failed = _read_json(os.path.join(loki_dir, "queue", "failed.json"), default=[])
|
|
@@ -1455,6 +1679,10 @@ def _build_proof(args, loki_dir, target_dir, repo_root):
|
|
|
1455
1679
|
# only, never read by _compute_headline / _compute_degraded.
|
|
1456
1680
|
if journey:
|
|
1457
1681
|
facts["journey"] = journey
|
|
1682
|
+
# A-118: additive, present only when the diff touched test integrity.
|
|
1683
|
+
_ti = _collect_tests_integrity(target_dir, diff_base_sha)
|
|
1684
|
+
if _ti:
|
|
1685
|
+
facts["tests_integrity"] = _ti
|
|
1458
1686
|
ablation = _collect_ablation(loki_dir)
|
|
1459
1687
|
if ablation is not None:
|
|
1460
1688
|
facts["ablation"] = ablation
|
|
@@ -1517,6 +1745,24 @@ def _build_proof(args, loki_dir, target_dir, repo_root):
|
|
|
1517
1745
|
"post_headline": True,
|
|
1518
1746
|
})
|
|
1519
1747
|
|
|
1748
|
+
# D47: edited assertion lines are disclosed but never move the headline or rc.
|
|
1749
|
+
_ae = (facts.get("tests_integrity") or {}).get("assertions_edited")
|
|
1750
|
+
if _ae:
|
|
1751
|
+
degraded.append({
|
|
1752
|
+
"item": "tests_integrity:assertions_edited",
|
|
1753
|
+
"status": "inconclusive",
|
|
1754
|
+
"reason": "assertion lines edited in: " + ", ".join(_ae),
|
|
1755
|
+
"post_headline": True,
|
|
1756
|
+
})
|
|
1757
|
+
_di = (facts.get("tests_integrity") or {}).get("disclosed")
|
|
1758
|
+
if _di:
|
|
1759
|
+
degraded.append({
|
|
1760
|
+
"item": "tests_integrity:config_added",
|
|
1761
|
+
"status": "inconclusive",
|
|
1762
|
+
"reason": "; ".join(_di),
|
|
1763
|
+
"post_headline": True,
|
|
1764
|
+
})
|
|
1765
|
+
|
|
1520
1766
|
honesty = {
|
|
1521
1767
|
"headline": headline,
|
|
1522
1768
|
"degraded": degraded,
|
|
@@ -1653,6 +1899,10 @@ def _compute_degraded(facts):
|
|
|
1653
1899
|
out.append({"item": "security", "status": "findings",
|
|
1654
1900
|
"reason": "%s un-waived HIGH security finding(s)"
|
|
1655
1901
|
% sec.get("high_active")})
|
|
1902
|
+
ti = facts.get("tests_integrity") or {}
|
|
1903
|
+
if ti.get("weakened"):
|
|
1904
|
+
out.append({"item": "tests_integrity", "status": "failed",
|
|
1905
|
+
"reason": "tests weakened: " + "; ".join(ti["weakened"])})
|
|
1656
1906
|
git = facts.get("git") or {}
|
|
1657
1907
|
if not (git.get("diff") or {}).get("count"):
|
|
1658
1908
|
out.append({"item": "git.diff", "status": "not_run",
|
|
@@ -1748,6 +1998,7 @@ def _compute_headline(facts, degraded):
|
|
|
1748
1998
|
if _is_exogenous(g))
|
|
1749
1999
|
or sec_high
|
|
1750
2000
|
or fn_failed
|
|
2001
|
+
or bool((facts.get("tests_integrity") or {}).get("weakened"))
|
|
1751
2002
|
)
|
|
1752
2003
|
if any_failed:
|
|
1753
2004
|
return "NOT VERIFIED"
|
package/autonomy/loki
CHANGED
|
@@ -1627,6 +1627,7 @@ show_help() {
|
|
|
1627
1627
|
echo "Build:"
|
|
1628
1628
|
echo " start [SPEC] Start a build (PRD file, GitHub/GitLab/Jira/Azure issue, or no arg)"
|
|
1629
1629
|
echo " plan <PRD-file> Dry-run analysis: complexity, cost, execution plan"
|
|
1630
|
+
echo " backlog <owner/repo> --all|--label X|--issues N,N Run every matching open issue, N in parallel"
|
|
1630
1631
|
echo " quickstart [idea] Guided first build: setup, idea, template, plan, go"
|
|
1631
1632
|
echo ""
|
|
1632
1633
|
echo "Session:"
|
|
@@ -1765,7 +1766,7 @@ show_help() {
|
|
|
1765
1766
|
# missing from this list, so a new command cannot ship invisible.
|
|
1766
1767
|
echo ""
|
|
1767
1768
|
echo "All commands:"
|
|
1768
|
-
echo " agent analyze api assets audit bench checkpoint (cp) ci cleanup"
|
|
1769
|
+
echo " agent analyze api assets audit backlog bench checkpoint (cp) ci cleanup"
|
|
1769
1770
|
echo " cluster cockpit code compliance completions compound config context (ctx)"
|
|
1770
1771
|
echo " cost council crash dashboard demo deploy docker docs doctor dogfood"
|
|
1771
1772
|
echo " enterprise estimate explain export failover github grill heal help"
|
|
@@ -1849,6 +1850,48 @@ show_help_aliases() {
|
|
|
1849
1850
|
echo "See internal/CLI-CONSOLIDATION-DESIGN.md for the full migration plan."
|
|
1850
1851
|
}
|
|
1851
1852
|
|
|
1853
|
+
# D51: bare `loki`. Reuse a running dashboard, else start one on a free
|
|
1854
|
+
# loopback port, then open /start (or just print the URL when headless).
|
|
1855
|
+
loki_ui_free_port() {
|
|
1856
|
+
python3 - "$1" <<'PY'
|
|
1857
|
+
import socket, sys
|
|
1858
|
+
for p in range(int(sys.argv[1]), int(sys.argv[1]) + 100):
|
|
1859
|
+
s = socket.socket()
|
|
1860
|
+
try:
|
|
1861
|
+
s.bind(("127.0.0.1", p))
|
|
1862
|
+
except OSError:
|
|
1863
|
+
continue
|
|
1864
|
+
finally:
|
|
1865
|
+
s.close()
|
|
1866
|
+
print(p)
|
|
1867
|
+
break
|
|
1868
|
+
PY
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
cmd_ui() {
|
|
1872
|
+
local port="" pid=""
|
|
1873
|
+
if [ -f "$DASHBOARD_PID_FILE" ]; then
|
|
1874
|
+
pid=$(cat "$DASHBOARD_PID_FILE" 2>/dev/null)
|
|
1875
|
+
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
|
1876
|
+
port=$(cat "${DASHBOARD_PID_DIR}/port" 2>/dev/null)
|
|
1877
|
+
fi
|
|
1878
|
+
fi
|
|
1879
|
+
if [ -z "$port" ]; then
|
|
1880
|
+
port=$(loki_ui_free_port "${LOKI_DASHBOARD_PORT:-$DASHBOARD_DEFAULT_PORT}")
|
|
1881
|
+
[ -n "$port" ] || { echo "loki: no free local port found" >&2; return 1; }
|
|
1882
|
+
# The WS-origin allowlist must name the chosen port on both loopback names.
|
|
1883
|
+
LOKI_PROJECT_DIR="$PWD" \
|
|
1884
|
+
LOKI_DASHBOARD_CORS="http://localhost:$port,http://127.0.0.1:$port" \
|
|
1885
|
+
cmd_dashboard_start --host 127.0.0.1 --port "$port" >&2 || return 1
|
|
1886
|
+
fi
|
|
1887
|
+
local url="http://127.0.0.1:${port}/start"
|
|
1888
|
+
if [ "${LOKI_HEADLESS:-0}" = "1" ] || [ "${1:-}" = "--no-open" ]; then
|
|
1889
|
+
echo "$url"
|
|
1890
|
+
else
|
|
1891
|
+
loki_open_url "$url" || echo "$url"
|
|
1892
|
+
fi
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1852
1895
|
# Tight newcomer landing for the bare `loki` invocation (no args).
|
|
1853
1896
|
# v7.28.x UX: the full 70-command table lives in `loki help`; a no-args run
|
|
1854
1897
|
# now shows only a calm ~12-line orientation so the "what do I do first"
|
|
@@ -3396,7 +3439,7 @@ cmd_start() {
|
|
|
3396
3439
|
${LOKI_MIROFISH_ROUNDS:+--max-rounds "$LOKI_MIROFISH_ROUNDS"} \
|
|
3397
3440
|
${LOKI_MIROFISH_TIMEOUT:+--timeout "$LOKI_MIROFISH_TIMEOUT"}
|
|
3398
3441
|
# PID is tracked in .loki/mirofish/pipeline-state.json by the adapter
|
|
3399
|
-
echo -e "${CYAN}MiroFish pipeline started in background.
|
|
3442
|
+
echo -e "${CYAN}MiroFish pipeline started in background. Status: $LOKI_DIR/mirofish/pipeline-state.json${NC}"
|
|
3400
3443
|
else
|
|
3401
3444
|
echo -e "${YELLOW}Warning: PRD validation for MiroFish failed. Continuing without.${NC}"
|
|
3402
3445
|
unset MIROFISH_URL
|
|
@@ -9306,6 +9349,32 @@ _deploy_receipt_verdict() {
|
|
|
9306
9349
|
# evaluated, so no key set is written and the verdict is UNCHECKED, never
|
|
9307
9350
|
# TAMPERED: the hash is fine, we just cannot vouch for who signed it. A
|
|
9308
9351
|
# local key only proves "a holder of this key", not an independent build.
|
|
9352
|
+
# Token SHAPE before any key lookup (mirrors engine10 checkAttestation): a present
|
|
9353
|
+
# attestation that is not a string, not a 3-part JWS, or whose header/payload are not
|
|
9354
|
+
# JSON with alg EdDSA is junk, so TAMPERED, never UNCHECKED, with or without a key.
|
|
9355
|
+
if ! python3 -E - "$pj" >/dev/null 2>&1 <<'PYSHAPE'
|
|
9356
|
+
import sys
|
|
9357
|
+
sys.path[:] = [p for p in sys.path if p not in ("", ".")]
|
|
9358
|
+
import json, base64
|
|
9359
|
+
def dec(x):
|
|
9360
|
+
return json.loads(base64.urlsafe_b64decode(x + "=" * (-len(x) % 4)))
|
|
9361
|
+
v = json.load(open(sys.argv[1])).get("verification") or {}
|
|
9362
|
+
t = v.get("attestation")
|
|
9363
|
+
if t is None or t == "":
|
|
9364
|
+
sys.exit(0)
|
|
9365
|
+
if not isinstance(t, str):
|
|
9366
|
+
sys.exit(1)
|
|
9367
|
+
parts = t.split(".")
|
|
9368
|
+
if len(parts) != 3 or not all(parts):
|
|
9369
|
+
sys.exit(1)
|
|
9370
|
+
try:
|
|
9371
|
+
h = dec(parts[0]); dec(parts[1])
|
|
9372
|
+
except Exception:
|
|
9373
|
+
sys.exit(1)
|
|
9374
|
+
sys.exit(0 if isinstance(h, dict) and h.get("alg") == "EdDSA" else 1)
|
|
9375
|
+
PYSHAPE
|
|
9376
|
+
then printf '%s' "TAMPERED"; return 0; fi
|
|
9377
|
+
|
|
9309
9378
|
local jwks att
|
|
9310
9379
|
jwks="$(mktemp "${TMPDIR:-/tmp}/loki-jwks.XXXXXX")" || { printf '%s' "UNCHECKED"; return 0; }
|
|
9311
9380
|
python3 -E - "${_LOKI_SCRIPT_DIR}" "$pj" >"$jwks" 2>/dev/null <<'PY' || true
|
|
@@ -12593,7 +12662,7 @@ cmd_config() {
|
|
|
12593
12662
|
echo " get KEY Get a configuration value"
|
|
12594
12663
|
echo " example Print an annotated config template (for --config)"
|
|
12595
12664
|
echo " schema Print the key -> env-var mapping table"
|
|
12596
|
-
echo " validate FILE
|
|
12665
|
+
echo " validate [FILE] Validate loki.yaml (no FILE: repo root, then ~/.loki) or a --config file"
|
|
12597
12666
|
echo ""
|
|
12598
12667
|
echo "Settable keys (v6.0.0):"
|
|
12599
12668
|
echo " maxTier Cost ceiling: opus, sonnet, haiku (default: opus)"
|
|
@@ -12797,14 +12866,43 @@ cmd_config_schema() {
|
|
|
12797
12866
|
loki_config_generate_schema
|
|
12798
12867
|
}
|
|
12799
12868
|
|
|
12869
|
+
# D51: headless backlog runner. One v10 issue-mode run per open issue, each in
|
|
12870
|
+
# its own worktree and branch, at most N in parallel (loki.yaml concurrency).
|
|
12871
|
+
cmd_backlog() {
|
|
12872
|
+
case "${1:-}" in
|
|
12873
|
+
""|--help|-h|help)
|
|
12874
|
+
echo -e "${BOLD}loki backlog${NC} - run a v10 issue-mode run for every matching open issue"
|
|
12875
|
+
echo ""
|
|
12876
|
+
echo "Usage: loki backlog owner/repo (--all | --label X | --issues 1,2,3) [--concurrency N] [--dry-run]"
|
|
12877
|
+
echo ""
|
|
12878
|
+
echo " --all Every open issue"
|
|
12879
|
+
echo " --label X Open issues with label X"
|
|
12880
|
+
echo " --issues 1,2,3 Exactly these issue numbers"
|
|
12881
|
+
echo " --concurrency N Max runs in parallel (default: loki.yaml concurrency, else 2)"
|
|
12882
|
+
echo " --dry-run List what would run; launch nothing"
|
|
12883
|
+
echo ""
|
|
12884
|
+
echo "Run from a checkout of owner/repo. Each issue runs in its own worktree on loki/backlog-N."
|
|
12885
|
+
echo "Config: loki.yaml (repo root, then ~/.loki/loki.yaml); see docs/loki.yaml.example."
|
|
12886
|
+
echo "Exit: 0 every issue ended VERIFIED or with a PR; 3 only budget stops; 1 otherwise; 2 usage."
|
|
12887
|
+
return 0
|
|
12888
|
+
;;
|
|
12889
|
+
esac
|
|
12890
|
+
command -v python3 >/dev/null 2>&1 || { echo -e "${RED}loki backlog: python3 is required${NC}" >&2; return 2; }
|
|
12891
|
+
python3 "$_LOKI_SCRIPT_DIR/lib/backlog.py" "$@"
|
|
12892
|
+
}
|
|
12893
|
+
|
|
12800
12894
|
# #691: validate a config file (--config) WITHOUT applying it. Reports
|
|
12801
12895
|
# unresolved ${VAR} refs, raw-secret literals (error), and per-value validation
|
|
12802
12896
|
# failures. Non-zero exit on any failure.
|
|
12803
12897
|
cmd_config_validate() {
|
|
12804
|
-
local file="${1:-}"
|
|
12805
|
-
|
|
12806
|
-
|
|
12807
|
-
|
|
12898
|
+
local file="${1:-}" _loki_yaml
|
|
12899
|
+
# D51: loki.yaml (UI + headless config) has its own schema; no argument
|
|
12900
|
+
# validates the discovered loki.yaml (repo root, then ~/.loki/loki.yaml).
|
|
12901
|
+
case "$(basename -- "$file")" in "" | loki.yaml | loki.yml | loki.yaml.example) _loki_yaml=1 ;; *) _loki_yaml=0 ;; esac
|
|
12902
|
+
if [ "$_loki_yaml" = 1 ]; then
|
|
12903
|
+
command -v python3 >/dev/null 2>&1 || { echo -e "${RED}config validate: python3 is required${NC}" >&2; return 2; }
|
|
12904
|
+
python3 "$_LOKI_SCRIPT_DIR/lib/loki_yaml.py" validate ${file:+"$file"}
|
|
12905
|
+
return $?
|
|
12808
12906
|
fi
|
|
12809
12907
|
if ! declare -f loki_config_validate_file >/dev/null 2>&1; then
|
|
12810
12908
|
echo -e "${RED}config validate: config-map.sh helper not loaded${NC}" >&2
|
|
@@ -21530,11 +21628,15 @@ main() {
|
|
|
21530
21628
|
exit 1
|
|
21531
21629
|
fi
|
|
21532
21630
|
|
|
21533
|
-
if [ $# -eq 0 ]; then
|
|
21534
|
-
#
|
|
21535
|
-
#
|
|
21536
|
-
|
|
21537
|
-
|
|
21631
|
+
if [ $# -eq 0 ] || { [ $# -eq 1 ] && [ "$1" = "--no-open" ]; }; then
|
|
21632
|
+
# D51: bare `loki` starts the local UI. LOKI_LANDING=1 keeps the v7.28
|
|
21633
|
+
# newcomer landing (tests and scripts that parse it set it).
|
|
21634
|
+
if [ "${LOKI_LANDING:-0}" = "1" ]; then
|
|
21635
|
+
show_landing
|
|
21636
|
+
exit 0
|
|
21637
|
+
fi
|
|
21638
|
+
cmd_ui "$@"
|
|
21639
|
+
exit $?
|
|
21538
21640
|
fi
|
|
21539
21641
|
|
|
21540
21642
|
local command="$1"
|
|
@@ -21767,6 +21869,9 @@ main() {
|
|
|
21767
21869
|
assets)
|
|
21768
21870
|
cmd_assets "$@"
|
|
21769
21871
|
;;
|
|
21872
|
+
backlog)
|
|
21873
|
+
cmd_backlog "$@"
|
|
21874
|
+
;;
|
|
21770
21875
|
config)
|
|
21771
21876
|
cmd_config "$@"
|
|
21772
21877
|
;;
|
package/autonomy/run.sh
CHANGED
|
@@ -11252,6 +11252,27 @@ _loki_proof_json_for_pr() {
|
|
|
11252
11252
|
}
|
|
11253
11253
|
|
|
11254
11254
|
# A-134: compact quiet-mode receipt for the outer shell (see top of file) to print.
|
|
11255
|
+
# A-118 / D47: a quick run that weakened its own tests exits 3. Prints the rc to use:
|
|
11256
|
+
# only ever RAISES a 0 (never lowers a non-zero rc); NOT VERIFIED from unproven gates alone keeps 0.
|
|
11257
|
+
_loki_quick_integrity_rc() {
|
|
11258
|
+
local rc="${1:-0}" ld="${TARGET_DIR:-.}/.loki" rid pj
|
|
11259
|
+
[ "$rc" = "0" ] || { echo "$rc"; return 0; }
|
|
11260
|
+
rid="$(cat "$ld/state/last-proof-id.txt" 2>/dev/null || true)"
|
|
11261
|
+
case "$rid" in '' | *[!A-Za-z0-9._-]*) echo 0; return 0 ;; esac
|
|
11262
|
+
pj="$ld/proofs/$rid/proof.json"
|
|
11263
|
+
if [ -f "$pj" ] && python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
11264
|
+
import json
|
|
11265
|
+
d = json.load(open(sys.argv[1]))
|
|
11266
|
+
g = (d.get('facts') or {}).get('git') or {}
|
|
11267
|
+
if sys.argv[2] and g.get('head_sha') != sys.argv[2]:
|
|
11268
|
+
sys.exit(1)
|
|
11269
|
+
sys.exit(0 if any(isinstance(x, dict) and x.get('item') == 'tests_integrity' and x.get('status') == 'failed' for x in ((d.get('honesty') or {}).get('degraded') or [])) else 1)" "$pj" "$(git -C "${TARGET_DIR:-.}" rev-parse HEAD 2>/dev/null || true)" 2>/dev/null; then
|
|
11270
|
+
echo 3
|
|
11271
|
+
else
|
|
11272
|
+
echo 0
|
|
11273
|
+
fi
|
|
11274
|
+
}
|
|
11275
|
+
|
|
11255
11276
|
_loki_quick_receipt_write() {
|
|
11256
11277
|
local ld="${TARGET_DIR:-.}/.loki" rid pj
|
|
11257
11278
|
rid="$(cat "$ld/state/last-proof-id.txt" 2>/dev/null || true)"
|
|
@@ -11266,8 +11287,10 @@ v = d.get('verification') or {}
|
|
|
11266
11287
|
if sys.argv[3] and g.get('head_sha') != sys.argv[3]:
|
|
11267
11288
|
print('Evidence Receipt: unavailable (final proof generation failed; newest proof predates the session commit)')
|
|
11268
11289
|
sys.exit(0)
|
|
11269
|
-
|
|
11270
|
-
|
|
11290
|
+
dg = [x for x in ((d.get('honesty') or {}).get('degraded') or []) if isinstance(x, dict)]
|
|
11291
|
+
tw = [str(x.get('reason') or 'tests weakened') for x in dg if x.get('item') == 'tests_integrity' and x.get('status') == 'failed']
|
|
11292
|
+
deg = [str(x.get('item') or '').split(':')[-1] for x in dg if x.get('item') != 'tests_integrity']
|
|
11293
|
+
notes = tw[:1] + ([] if (v.get('gpg_signature') or v.get('attestation')) else ['unsigned']) + (['%d not proven: %s' % (len(deg), ', '.join(deg))] if deg else [])
|
|
11271
11294
|
print('Evidence Receipt: ' + str((d.get('honesty') or {}).get('headline') or 'unavailable') + (' (' + '; '.join(notes) + ')' if notes else ''))
|
|
11272
11295
|
print('receipt_sha256: ' + str(v.get('hash') or ''))
|
|
11273
11296
|
print('Head sha: ' + str(g.get('head_sha') or '') + ' Diff sha256: ' + str(g.get('diff_sha256') or ''))
|
|
@@ -29402,6 +29425,8 @@ except Exception:
|
|
|
29402
29425
|
if [ "${LOKI_PROOF:-1}" != "0" ]; then
|
|
29403
29426
|
generate_proof_of_run "$result" || true
|
|
29404
29427
|
fi
|
|
29428
|
+
# A-118: a quick run that weakened its tests exits 3 (quiet and verbose alike).
|
|
29429
|
+
! _loki_is_quick_prd "${PRD_PATH:-}" || result="$(_loki_quick_integrity_rc "$result")"
|
|
29405
29430
|
[ -z "${LOKI_QUICK_INNER:-}" ] || _loki_quick_receipt_write
|
|
29406
29431
|
# Trusted post-session step. NOT wrapped in _loki_with_github_tokens as a
|
|
29407
29432
|
# whole (round 5): its push (_loki_trusted_push) and gh calls each re-grant
|
package/autonomy/verify.sh
CHANGED
|
@@ -3074,6 +3074,15 @@ sys.exit(0 if ok else 3)" "$lib" "$pj" 2>/dev/null || rc=$?
|
|
|
3074
3074
|
declare -f _deploy_receipt_verdict >/dev/null 2>&1 || return 0
|
|
3075
3075
|
local att
|
|
3076
3076
|
att="$(_deploy_receipt_verdict "$pj")"
|
|
3077
|
+
# D47: UNSIGNED is never a pass; --allow-unsigned / LOKI_VERIFY_ALLOW_UNSIGNED=1 accepts it, said aloud.
|
|
3078
|
+
if [ "$att" = "UNSIGNED" ]; then
|
|
3079
|
+
if [ "${VERIFY_ALLOW_UNSIGNED:-0}" = "1" ] || [ "${LOKI_VERIFY_ALLOW_UNSIGNED:-}" = "1" ]; then
|
|
3080
|
+
printf 'attestation: UNSIGNED (accepted by --allow-unsigned; integrity not attested)\n'
|
|
3081
|
+
return 0
|
|
3082
|
+
fi
|
|
3083
|
+
printf 'attestation: UNSIGNED, integrity not attested; refusing (pass --allow-unsigned to accept)\n'
|
|
3084
|
+
return 1
|
|
3085
|
+
fi
|
|
3077
3086
|
printf 'attestation: %s\n' "$att"
|
|
3078
3087
|
[ "$att" != "TAMPERED" ]
|
|
3079
3088
|
}
|
|
@@ -3093,6 +3102,7 @@ verify_main() {
|
|
|
3093
3102
|
VERIFY_CHECK_FRESH=0
|
|
3094
3103
|
# Opt-in machine-readable stdout (--json). Default 0 = exactly today.
|
|
3095
3104
|
VERIFY_JSON=0
|
|
3105
|
+
VERIFY_ALLOW_UNSIGNED=0
|
|
3096
3106
|
|
|
3097
3107
|
# Fail-closed defaults. These globals are read at the end of this function
|
|
3098
3108
|
# (the VERDICT banner and the function return code). verify_compute_verdict()
|
|
@@ -3158,6 +3168,7 @@ verify_main() {
|
|
|
3158
3168
|
# verdict above stays authoritative for the exit code. Unset =
|
|
3159
3169
|
# exactly today's behavior. See verify_hosted_enrich().
|
|
3160
3170
|
VERIFY_HOSTED=1; shift ;;
|
|
3171
|
+
--allow-unsigned) VERIFY_ALLOW_UNSIGNED=1; shift ;;
|
|
3161
3172
|
--) shift; break ;;
|
|
3162
3173
|
-*)
|
|
3163
3174
|
_verify_err "unknown option: $1"; verify_help; return $VERIFY_EXIT_ERROR ;;
|
package/completions/_loki
CHANGED
|
@@ -205,6 +205,7 @@ function _loki_commands {
|
|
|
205
205
|
'crash:Crash report management'
|
|
206
206
|
'otel:OpenTelemetry management'
|
|
207
207
|
'wiki:Wiki commands'
|
|
208
|
+
'backlog:Run every matching open issue, N in parallel'
|
|
208
209
|
'explain:Explain Loki concepts'
|
|
209
210
|
'optimize:Optimization commands'
|
|
210
211
|
'review:Code review commands'
|
package/completions/loki.bash
CHANGED
|
@@ -5,8 +5,8 @@ _loki_completion() {
|
|
|
5
5
|
_init_completion || return
|
|
6
6
|
|
|
7
7
|
# Main subcommands (must match autonomy/loki main case statement)
|
|
8
|
-
local main_commands="start quick monitor demo tour welcome init stop pause resume steer status next ship dashboard web serve api sandbox notify import github issue config provider reset memory compound checkpoint council dogfood projects enterprise secrets cockpit secure own handoff doctor watchdog audit metrics syslog onboard share proof receipt outcomes explain plan report cost estimate kpis stats test ci watch telemetry agent context ctx code run export review optimize heal modernize migrate cluster worktree wt trigger failover remote deploy docker mcp magic assets analyze compliance crash open otel preview quickstart rc rollback self-update sentrux setup-skill spec state template trust trust-metrics ultracode update verify voice why wiki bench cleanup logs grill docs cp version completions help"
|
|
9
|
-
local main_commands="start quick monitor demo tour welcome init stop pause resume steer status next ship dashboard web serve api sandbox notify import github issue intent config provider reset memory compound checkpoint council dogfood projects enterprise secrets cockpit secure own handoff doctor watchdog audit metrics syslog onboard share proof receipt explain plan report cost estimate kpis stats test ci watch telemetry agent context ctx code run export review optimize heal modernize migrate cluster worktree wt trigger failover remote deploy docker mcp magic assets analyze compliance crash open otel preview quickstart rc rollback self-update sentrux setup-skill spec state template trust trust-metrics ultracode update verify voice why wiki bench cleanup logs grill docs cp version completions help"
|
|
8
|
+
local main_commands="start backlog quick monitor demo tour welcome init stop pause resume steer status next ship dashboard web serve api sandbox notify import github issue config provider reset memory compound checkpoint council dogfood projects enterprise secrets cockpit secure own handoff doctor watchdog audit metrics syslog onboard share proof receipt outcomes explain plan report cost estimate kpis stats test ci watch telemetry agent context ctx code run export review optimize heal modernize migrate cluster worktree wt trigger failover remote deploy docker mcp magic assets analyze compliance crash open otel preview quickstart rc rollback self-update sentrux setup-skill spec state template trust trust-metrics ultracode update verify voice why wiki bench cleanup logs grill docs cp version completions help"
|
|
9
|
+
local main_commands="start backlog quick monitor demo tour welcome init stop pause resume steer status next ship dashboard web serve api sandbox notify import github issue intent config provider reset memory compound checkpoint council dogfood projects enterprise secrets cockpit secure own handoff doctor watchdog audit metrics syslog onboard share proof receipt explain plan report cost estimate kpis stats test ci watch telemetry agent context ctx code run export review optimize heal modernize migrate cluster worktree wt trigger failover remote deploy docker mcp magic assets analyze compliance crash open otel preview quickstart rc rollback self-update sentrux setup-skill spec state template trust trust-metrics ultracode update verify voice why wiki bench cleanup logs grill docs cp version completions help"
|
|
10
10
|
|
|
11
11
|
# 1. If we are on the first argument (subcommand)
|
|
12
12
|
if [[ $cword -eq 1 ]]; then
|