loki-mode 10.5.10 → 10.5.13
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/README.md +30 -962
- package/SKILL.md +3 -3
- package/VERSION +1 -1
- package/autonomy/app-runner.sh +2 -1
- package/autonomy/completion-council.sh +40 -3
- package/autonomy/lib/doctor-fix.py +1 -1
- package/autonomy/lib/proof-generator.py +40 -84
- package/autonomy/lib/proof-template.html +7 -11
- package/autonomy/loki +118 -69
- package/autonomy/receipt_jwt.py +59 -14
- package/autonomy/run.sh +91 -124
- package/autonomy/trigger-server.py +2 -2
- package/autonomy/verify.sh +37 -2
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +2 -2
- package/docs/COMPETITIVE-NEXT-10.md +3 -4
- package/docs/INSTALLATION.md +1 -1
- package/docs/README-FULL.md +977 -0
- package/docs/SIGNED-RECEIPTS.md +40 -82
- package/docs/TOOLS.md +0 -23
- package/docs/VERIFICATION-COST.md +2 -2
- package/docs/dev/release-checklist.md +2 -0
- package/docs/v10/ADOPTION-PLAN.md +375 -0
- package/docs/v10/BOARD.md +65 -13
- package/docs/v10/DECISIONS.md +11 -0
- package/docs/v10/DEPS.md +10 -4
- package/docs/v10/ENGINE.md +4 -4
- package/docs/v10/FIRST-RUN-GATE.md +39 -0
- package/docs/v10/FOUNDER-QUEUE.md +7 -0
- package/docs/v10/GUIDE.md +0 -4
- package/docs/v10/PROGRESS.md +75 -0
- package/loki-ts/dist/loki.js +590 -561
- package/mcp/__init__.py +1 -1
- package/package.json +2 -2
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/tools/gate-status.py +3 -46
- package/tools/receipt-attest.py +1 -1
- package/web-app/dist/assets/{AdminPage-B67oykTq.js → AdminPage-C8zraQ0u.js} +1 -1
- package/web-app/dist/assets/{Avatar-oGATGRH9.js → Avatar-DnjIA4Ap.js} +1 -1
- package/web-app/dist/assets/{Badge-BL6VnQ5l.js → Badge-C487hf0b.js} +1 -1
- package/web-app/dist/assets/{Button-QGZm-VP0.js → Button-5AWAEksl.js} +1 -1
- package/web-app/dist/assets/{CockpitPage-B42XvLAM.js → CockpitPage-B6_1gTFO.js} +1 -1
- package/web-app/dist/assets/{ComparePage-CvXeI65J.js → ComparePage-DDbC8-yi.js} +1 -1
- package/web-app/dist/assets/{ErrorBoundary-C7BGRqtK.js → ErrorBoundary-CWHKwjEp.js} +1 -1
- package/web-app/dist/assets/{EvidenceReceiptPanel-CWtQJFvz.js → EvidenceReceiptPanel-MfJq92Ty.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-CaNG20H6.js → GitHubIssuesPanel-BBdijzdg.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-CipAJzPg.js → GitHubPRsPanel-DjBHyymT.js} +1 -1
- package/web-app/dist/assets/{HomePage-_QeUkY15.js → HomePage-B_6m7ITK.js} +2 -2
- package/web-app/dist/assets/{LoginPage-DWBRdcLN.js → LoginPage-OGgMvfjT.js} +1 -1
- package/web-app/dist/assets/{MagicPage-DWjFPG7D.js → MagicPage-m4MSfxWf.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-C4d6da1F.js → MetricsPage-D8Ea1ALe.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-DeDsW3_m.js → NotFoundPage-ttvXL-7K.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-Dm8ok-xS.js → ProjectPage-CDX8tSPV.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage-CTzhekl2.js → ProjectsPage-BQ73nYWf.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-CiLJNswp.js → SettingsPage-B6k71q2g.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-f_1nS83o.js → ShowcasePage-CNbwtr7D.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-DYrUvPNT.js → SystemSettingsPage-CD2qys43.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-H4RiPWAZ.js → TeamsPage-DAmsRIVD.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-CPuxRvnt.js → TemplatesPage-CcRyyUhL.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-CWrePFkI.js → TerminalOutput-DRquxtQ1.js} +1 -1
- package/web-app/dist/assets/{activity-P02y0O8o.js → activity-DUDUi3Qd.js} +1 -1
- package/web-app/dist/assets/{bell-HQXzg-Hl.js → bell-tDpCOcHx.js} +1 -1
- package/web-app/dist/assets/{bot-Dk5s9GSN.js → bot-87IR4aTF.js} +1 -1
- package/web-app/dist/assets/{check-CwDjf4PE.js → check-C2Y2z-Ge.js} +1 -1
- package/web-app/dist/assets/{chevron-left-C7TwN2mS.js → chevron-left-CsPFT88x.js} +1 -1
- package/web-app/dist/assets/{circle-alert-DM4oNAW-.js → circle-alert-Cp2UJikd.js} +1 -1
- package/web-app/dist/assets/{clock-ds_JQQTA.js → clock-DZ1cwNyf.js} +1 -1
- package/web-app/dist/assets/{cloud-DH1iftlI.js → cloud-q5TrIOV-.js} +1 -1
- package/web-app/dist/assets/{code-xml-DVxWm1OZ.js → code-xml-0TNGOS3c.js} +1 -1
- package/web-app/dist/assets/{database-DncR8cxO.js → database-g73vgDVx.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-BgaDf1Xc.js → dollar-sign-DAKkR_MO.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-qXSN84-m.js → file-code-corner-DTAvdxjl.js} +1 -1
- package/web-app/dist/assets/{file-plus-C_G98fmf.js → file-plus-D5SB-1Uk.js} +1 -1
- package/web-app/dist/assets/{globe-riusJlUK.js → globe-j5YaNmx9.js} +1 -1
- package/web-app/dist/assets/{hammer-8rTeIjNL.js → hammer-C9yoXOC6.js} +1 -1
- package/web-app/dist/assets/{index-Bm8x_X7_.js → index--EayJsJ-.js} +3 -3
- package/web-app/dist/assets/{layers-lthGyhBX.js → layers-B4mxkWdH.js} +1 -1
- package/web-app/dist/assets/{loader-circle-Ct6LWreQ.js → loader-circle-CP-qD1W2.js} +1 -1
- package/web-app/dist/assets/{lock-ui-BaDIH.js → lock-DwHNUSTU.js} +1 -1
- package/web-app/dist/assets/{package-BKKKJduw.js → package-BxLWioQ8.js} +1 -1
- package/web-app/dist/assets/{plus-B4cHwuUk.js → plus-B-43Byzl.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-BJZsXiQT.js → refresh-cw-5s47zAFV.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-DazXGjet.js → rotate-ccw-BewZ8_Rx.js} +1 -1
- package/web-app/dist/assets/{scroll-text-CoXDGbkm.js → scroll-text-DPNpHL-Z.js} +1 -1
- package/web-app/dist/assets/{server-BepN1rgV.js → server-hL8_-vRq.js} +1 -1
- package/web-app/dist/assets/{shield-alert-DLnd3crY.js → shield-alert-DAGc2aLO.js} +1 -1
- package/web-app/dist/assets/{thumbs-up-oUBuPBUu.js → thumbs-up-DRGzcWEH.js} +1 -1
- package/web-app/dist/assets/{trash-2-BY9HtMUs.js → trash-2-Bo_YPIqS.js} +1 -1
- package/web-app/dist/assets/{trending-down-DZMdGaq1.js → trending-down-BQ1cK9F3.js} +1 -1
- package/web-app/dist/assets/{trending-up-Cq5n8lwY.js → trending-up-BpnkTO1T.js} +1 -1
- package/web-app/dist/assets/{upload-sdNXkoXm.js → upload-DDdQNxmW.js} +1 -1
- package/web-app/dist/assets/{usePolling-CFw3EoGh.js → usePolling-z_FXsGz7.js} +1 -1
- package/web-app/dist/assets/{user-bN0b7P81.js → user-obPH42HX.js} +1 -1
- package/web-app/dist/index.html +1 -1
- package/tools/signing-status.py +0 -292
package/SKILL.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: loki-mode
|
|
3
|
-
description:
|
|
3
|
+
description: An autonomous software factory that knows what it is supposed to deliver, and proves it did. Use when the user says Loki Mode or asks to build, fix or verify software autonomously.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Loki Mode v10.5.
|
|
6
|
+
# Loki Mode v10.5.13
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -470,4 +470,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
|
|
|
470
470
|
|
|
471
471
|
---
|
|
472
472
|
|
|
473
|
-
**v10.5.
|
|
473
|
+
**v10.5.13 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
10.5.
|
|
1
|
+
10.5.13
|
package/autonomy/app-runner.sh
CHANGED
|
@@ -1015,7 +1015,8 @@ _install_node_deps() {
|
|
|
1015
1015
|
local dir="$1"
|
|
1016
1016
|
if [ -f "$dir/package.json" ] && [ ! -d "$dir/node_modules" ]; then
|
|
1017
1017
|
log_step "App Runner: installing node dependencies..."
|
|
1018
|
-
|
|
1018
|
+
# shellcheck disable=SC2046 # A-132: no lockfile the repo did not have
|
|
1019
|
+
(cd "$dir" && npm install $([ -f "$dir/package-lock.json" ] || echo --no-package-lock) >> "$_APP_RUNNER_DIR/app.log" 2>&1) || \
|
|
1019
1020
|
log_warn "App Runner: npm install failed, app may not start"
|
|
1020
1021
|
fi
|
|
1021
1022
|
}
|
|
@@ -1865,6 +1865,30 @@ _loki_test_provenance() {
|
|
|
1865
1865
|
# actually ran and was red); every inconclusive case passes through so a
|
|
1866
1866
|
# legitimate completion is never falsely stopped. Default-on; opt out with
|
|
1867
1867
|
# LOKI_EVIDENCE_GATE=0 (byte-identical to prior behavior, no read/write).
|
|
1868
|
+
# A-133: true (rc 0) when the changed-file union (arg 2, newline list, .loki/ already
|
|
1869
|
+
# excluded) is a trivial diff: at most 2 files and 20 changed lines, and none of
|
|
1870
|
+
# them a test, CI, auth/security, route/entrypoint, manifest, Docker or SQL file. Only the
|
|
1871
|
+
# costly persistence, auth and tenant probes are skipped; the free boot read never is.
|
|
1872
|
+
# Opt out: LOKI_EVIDENCE_TRIVIAL_SKIP=0.
|
|
1873
|
+
_council_trivial_diff() {
|
|
1874
|
+
local base="$1" files="$2" n added deleted f total=0
|
|
1875
|
+
[ "${LOKI_EVIDENCE_TRIVIAL_SKIP:-1}" = "0" ] && return 1
|
|
1876
|
+
n=$(printf '%s\n' "$files" | wc -l | tr -d ' ')
|
|
1877
|
+
[ "$n" -le 2 ] || return 1
|
|
1878
|
+
printf '%s\n' "$files" | grep -qiE '(^|/)(tests?|__tests__|specs?|e2e|cypress|playwright)(/|$)|\.(test|spec)\.[^/]+$|(^|/)test_[^/]+$|_test\.[^/]+$|conftest\.py$|^\.github/|^\.gitlab-ci|^\.circleci/|Jenkinsfile|azure-pipelines|^\.buildkite/|^\.travis|auth|login|logout|session|passw|secret|credential|token|jwt|oauth|saml|sso|security|permission|rbac|acl|crypt|\.env|\.pem$|\.key$|middleware|policy|tenant|rls|route|router|controller|endpoint|(^|/)api/|(^|/)pages/|(^|/)handlers?/|(^|/)(server|app|main|index)\.[a-z]+$|package\.json|Dockerfile|docker-compose|\.sql$|migrations?/' && return 1
|
|
1879
|
+
# Tracked changes vs the run base (committed, staged and unstaged), then untracked files.
|
|
1880
|
+
while read -r added deleted f; do
|
|
1881
|
+
[ -n "$f" ] || continue
|
|
1882
|
+
case "$added$deleted" in *[!0-9]*) return 1 ;; esac # binary: not trivial
|
|
1883
|
+
total=$((total + added + deleted))
|
|
1884
|
+
done < <(git diff --numstat "$base" -- . ':!.loki' 2>/dev/null || echo "x x x")
|
|
1885
|
+
while IFS= read -r f; do
|
|
1886
|
+
[ -n "$f" ] || continue
|
|
1887
|
+
total=$((total + $(wc -l < "$f" 2>/dev/null || echo 99)))
|
|
1888
|
+
done < <(git ls-files --others --exclude-standard -- . ':!.loki' 2>/dev/null)
|
|
1889
|
+
[ "$total" -le 20 ]
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1868
1892
|
council_evidence_gate() {
|
|
1869
1893
|
# P2 (S-116): when the caller sets _LOKI_EVIDENCE_REQUIRE_TESTS=1, inconclusive
|
|
1870
1894
|
# test evidence blocks instead of passing through. council_evaluate sets it as
|
|
@@ -1908,6 +1932,7 @@ council_evidence_gate() {
|
|
|
1908
1932
|
# diff_inconclusive stays "false" on the conclusive branch below.
|
|
1909
1933
|
local diff_inconclusive="false"
|
|
1910
1934
|
local diff_inconclusive_reason=""
|
|
1935
|
+
local _trivial_diff="false"
|
|
1911
1936
|
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
1912
1937
|
# No git repo => cannot prove fabrication => inconclusive => pass-through.
|
|
1913
1938
|
diff_inconclusive="true"
|
|
@@ -1954,6 +1979,7 @@ council_evidence_gate() {
|
|
|
1954
1979
|
local union_files
|
|
1955
1980
|
union_files=$(printf '%s\n%s\n%s\n%s\n' "$committed_files" "$unstaged_files" "$staged_files" "$untracked_files" | grep -v '^$' | grep -vE '^\.loki/' | sort -u)
|
|
1956
1981
|
if [ -n "$union_files" ]; then
|
|
1982
|
+
_council_trivial_diff "$base_sha" "$union_files" && _trivial_diff="true"
|
|
1957
1983
|
diff_files=$(printf '%s\n' "$union_files" | wc -l | tr -d ' ')
|
|
1958
1984
|
else
|
|
1959
1985
|
diff_files=0
|
|
@@ -2249,6 +2275,11 @@ INCONCLUSIVE_EOF
|
|
|
2249
2275
|
"inconclusive": $test_inconclusive,
|
|
2250
2276
|
"inconclusive_reason": "$test_inconclusive_reason"
|
|
2251
2277
|
},
|
|
2278
|
+
"boot": {
|
|
2279
|
+
"ok": $([ "${boot_fails:-false}" = "true" ] && echo false || echo true),
|
|
2280
|
+
"inconclusive": ${boot_inconclusive:-false},
|
|
2281
|
+
"reason": "${boot_inconclusive_reason:-}"
|
|
2282
|
+
},
|
|
2252
2283
|
"nomock": {
|
|
2253
2284
|
"ok": $_nomock_ok,
|
|
2254
2285
|
"inconclusive": ${nomock_inconclusive:-false},
|
|
@@ -2458,6 +2489,8 @@ PYEOF
|
|
|
2458
2489
|
|
|
2459
2490
|
if [ "$_proof_persist_disabled" = "true" ]; then
|
|
2460
2491
|
persist_inconclusive="true"; persist_inconclusive_reason="persist_gate_disabled"
|
|
2492
|
+
elif [ "$_trivial_diff" = "true" ]; then
|
|
2493
|
+
persist_inconclusive="true"; persist_inconclusive_reason="trivial_diff"
|
|
2461
2494
|
elif [ "$_proof_serveable" != "true" ]; then
|
|
2462
2495
|
persist_inconclusive="true"; persist_inconclusive_reason="not_serveable"
|
|
2463
2496
|
elif command -v python3 >/dev/null 2>&1; then
|
|
@@ -2522,6 +2555,8 @@ PYEOF
|
|
|
2522
2555
|
|
|
2523
2556
|
if [ "$_proof_auth_disabled" = "true" ]; then
|
|
2524
2557
|
auth_inconclusive="true"; auth_inconclusive_reason="auth_gate_disabled"
|
|
2558
|
+
elif [ "$_trivial_diff" = "true" ]; then
|
|
2559
|
+
auth_inconclusive="true"; auth_inconclusive_reason="trivial_diff"
|
|
2525
2560
|
elif [ "$_proof_serveable" != "true" ]; then
|
|
2526
2561
|
auth_inconclusive="true"; auth_inconclusive_reason="not_serveable"
|
|
2527
2562
|
elif command -v python3 >/dev/null 2>&1; then
|
|
@@ -2601,6 +2636,8 @@ PYEOF
|
|
|
2601
2636
|
# isolation blocks.
|
|
2602
2637
|
if [ "$_proof_authz_disabled" = "true" ]; then
|
|
2603
2638
|
authz_inconclusive="true"; authz_inconclusive_reason="authz_gate_disabled"
|
|
2639
|
+
elif [ "$_trivial_diff" = "true" ]; then
|
|
2640
|
+
authz_inconclusive="true"; authz_inconclusive_reason="trivial_diff"
|
|
2604
2641
|
elif [ "$_proof_serveable" != "true" ]; then
|
|
2605
2642
|
authz_inconclusive="true"; authz_inconclusive_reason="not_serveable"
|
|
2606
2643
|
elif command -v python3 >/dev/null 2>&1; then
|
|
@@ -2740,13 +2777,13 @@ PYEOF
|
|
|
2740
2777
|
if [ "$nomock_inconclusive" = "true" ] && [ "$nomock_inconclusive_reason" != "nomock_gate_disabled" ]; then
|
|
2741
2778
|
log_warn "[Council] Evidence gate: no-mock not confirmed (${nomock_inconclusive_reason}). Pass-through; set LOKI_PROOF_NOMOCK=0 to silence."
|
|
2742
2779
|
fi
|
|
2743
|
-
if [ "$persist_inconclusive" = "true" ] && [ "$persist_inconclusive_reason" != "persist_gate_disabled" ]; then
|
|
2780
|
+
if [ "$persist_inconclusive" = "true" ] && [ "$persist_inconclusive_reason" != "persist_gate_disabled" ] && [ "$_trivial_diff" != "true" ]; then
|
|
2744
2781
|
log_warn "[Council] Evidence gate: persistence not proven (${persist_inconclusive_reason}). Pass-through; set LOKI_PROOF_PERSIST=0 to silence, or expose a create form the driver can exercise (LOKI_PROOF_CREATE_SELECTOR)."
|
|
2745
2782
|
fi
|
|
2746
|
-
if [ "$auth_inconclusive" = "true" ] && [ "$auth_inconclusive_reason" != "auth_gate_disabled" ]; then
|
|
2783
|
+
if [ "$auth_inconclusive" = "true" ] && [ "$auth_inconclusive_reason" != "auth_gate_disabled" ] && [ "$_trivial_diff" != "true" ]; then
|
|
2747
2784
|
log_warn "[Council] Evidence gate: auth enforcement not proven (${auth_inconclusive_reason}). Pass-through; set LOKI_PROOF_AUTH=0 to silence, or set LOKI_PROOF_PROTECTED_PATH so the driver can test a logged-out request."
|
|
2748
2785
|
fi
|
|
2749
|
-
if [ "$authz_inconclusive" = "true" ] && [ "$authz_inconclusive_reason" != "authz_gate_disabled" ]; then
|
|
2786
|
+
if [ "$authz_inconclusive" = "true" ] && [ "$authz_inconclusive_reason" != "authz_gate_disabled" ] && [ "$_trivial_diff" != "true" ]; then
|
|
2750
2787
|
log_warn "[Council] Evidence gate: tenant isolation not proven (${authz_inconclusive_reason}). Pass-through; set LOKI_PROOF_AUTHZ=0 to silence, or configure LOKI_PROOF_AUTHZ_* selectors so the driver can drive two distinct sessions."
|
|
2751
2788
|
fi
|
|
2752
2789
|
_write_evidence_details "pass"
|
|
@@ -58,7 +58,7 @@ INPUT CONTRACT. Blockers are DERIVED, not read: `doctor --json` emits no
|
|
|
58
58
|
blocker list (that string is built only in the text path, autonomy/loki:11367).
|
|
59
59
|
Anything with status "fail" is a blocker, and failable things live in three
|
|
60
60
|
places -- `checks[]`, plus the siblings `ai_provider` and `disk`. The advisory
|
|
61
|
-
siblings (`sentrux`, `
|
|
61
|
+
siblings (`sentrux`, `memory`, `model_catalog`) are never
|
|
62
62
|
"fail" by construction and are not scanned.
|
|
63
63
|
|
|
64
64
|
KNOWN LIMITATION, stated rather than papered over: the text path reports two
|
|
@@ -1056,9 +1056,8 @@ def _git_diffstat(target_dir, include_diffs):
|
|
|
1056
1056
|
into _diff_sha256, the receipt's integrity hash, which is written on EVERY
|
|
1057
1057
|
run. A verifier recomputing it therefore attests to the understated stat,
|
|
1058
1058
|
and the receipt is exactly the artifact users are told to trust. (Detached
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
is the always-on path.)
|
|
1059
|
+
Ed25519 signing attests to the same digest; the integrity hash is the
|
|
1060
|
+
always-on path.)
|
|
1062
1061
|
|
|
1063
1062
|
Order of preference:
|
|
1064
1063
|
1. _LOKI_RUN_START_SHA -- the run's own baseline (run.sh exports it).
|
|
@@ -1303,26 +1302,6 @@ def _canonical(obj):
|
|
|
1303
1302
|
return json.dumps(obj, sort_keys=True, separators=(",", ":"))
|
|
1304
1303
|
|
|
1305
1304
|
|
|
1306
|
-
def _gpg_detached_sign(data, key_id):
|
|
1307
|
-
"""Produce an ASCII-armored gpg detached signature over `data`.
|
|
1308
|
-
|
|
1309
|
-
Returns the armored signature string, or None on any failure (gpg missing,
|
|
1310
|
-
key not found, timeout). Best-effort: signing is an optional add-on and
|
|
1311
|
-
never blocks proof emission. Local-only: invokes the on-PATH gpg, no network.
|
|
1312
|
-
"""
|
|
1313
|
-
try:
|
|
1314
|
-
proc = subprocess.run(
|
|
1315
|
-
["gpg", "--batch", "--yes", "--armor", "--detach-sign",
|
|
1316
|
-
"--local-user", key_id, "--output", "-"],
|
|
1317
|
-
input=data, capture_output=True, timeout=30,
|
|
1318
|
-
)
|
|
1319
|
-
if proc.returncode != 0 or not proc.stdout:
|
|
1320
|
-
return None
|
|
1321
|
-
return proc.stdout.decode("utf-8", errors="replace")
|
|
1322
|
-
except Exception:
|
|
1323
|
-
return None
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
1305
|
def _build_proof(args, loki_dir, target_dir, repo_root):
|
|
1327
1306
|
generated_at = _utc_now_iso()
|
|
1328
1307
|
run_id = args.run_id or os.environ.get("LOKI_SESSION_ID") or _gen_run_id()
|
|
@@ -1965,16 +1944,16 @@ def _render_fallback_html(proof):
|
|
|
1965
1944
|
# Signing state, stated plainly. Mirrors renderProvenance in
|
|
1966
1945
|
# proof-template.html (the primary renderer); this fallback path must not
|
|
1967
1946
|
# be quieter about provenance than the page it stands in for.
|
|
1968
|
-
if ver.get("
|
|
1969
|
-
rows.append("<p>Signature: SIGNED (
|
|
1970
|
-
"
|
|
1971
|
-
"
|
|
1947
|
+
if ver.get("attestation"):
|
|
1948
|
+
rows.append("<p>Signature: SIGNED (Ed25519 attestation over the "
|
|
1949
|
+
"integrity hash). Confirm provenance offline: "
|
|
1950
|
+
"loki proof verify <id></p>")
|
|
1972
1951
|
else:
|
|
1973
1952
|
rows.append("<p>Signature: UNSIGNED. The integrity hash proves the "
|
|
1974
1953
|
"bytes were not edited after hashing; it does NOT prove "
|
|
1975
1954
|
"who produced them, so this receipt trusts its generator. "
|
|
1976
|
-
"To sign future receipts,
|
|
1977
|
-
"
|
|
1955
|
+
"To sign future receipts, install the cryptography "
|
|
1956
|
+
"package (see docs/SIGNED-RECEIPTS.md).</p>")
|
|
1978
1957
|
|
|
1979
1958
|
# Model provenance, mirrored here for the same reason the signature row is:
|
|
1980
1959
|
# this fallback "must not be quieter about provenance than the page it
|
|
@@ -2150,61 +2129,38 @@ def generate(args):
|
|
|
2150
2129
|
"scope": "integrity",
|
|
2151
2130
|
}
|
|
2152
2131
|
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
#
|
|
2165
|
-
#
|
|
2166
|
-
#
|
|
2167
|
-
#
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
att_key = (os.environ.get("LOKI_RECEIPT_SIGNING_KEY_FILE", "").strip()
|
|
2186
|
-
or os.environ.get("LOKI_RECEIPT_SIGNING_KEY", "").strip())
|
|
2187
|
-
if att_key:
|
|
2188
|
-
try:
|
|
2189
|
-
sys.path.insert(0, os.path.dirname(os.path.dirname(
|
|
2190
|
-
os.path.abspath(__file__))))
|
|
2191
|
-
from receipt_jwt import load_signing_key, sign_attestation
|
|
2192
|
-
_priv, _kid = load_signing_key()
|
|
2193
|
-
if _priv is not None:
|
|
2194
|
-
_tok = sign_attestation(
|
|
2195
|
-
_priv, _kid,
|
|
2196
|
-
job_id=str(redacted.get("run_id") or ""),
|
|
2197
|
-
run_id=str(redacted.get("run_id") or ""),
|
|
2198
|
-
receipt_hash=digest,
|
|
2199
|
-
)
|
|
2200
|
-
if _tok:
|
|
2201
|
-
verification["attestation"] = _tok
|
|
2202
|
-
verification["attestation_kid"] = _kid
|
|
2203
|
-
except Exception:
|
|
2204
|
-
# Swallowed deliberately, matching the gpg path: an unattested
|
|
2205
|
-
# receipt is a valid state with an existing verdict. Losing the
|
|
2206
|
-
# receipt entirely because signing failed would be strictly worse.
|
|
2207
|
-
pass
|
|
2132
|
+
if os.environ.get("LOKI_PROOF_GPG_KEY", "").strip():
|
|
2133
|
+
sys.stderr.write(
|
|
2134
|
+
"warn: LOKI_PROOF_GPG_KEY is no longer supported and is ignored. "
|
|
2135
|
+
"Receipts are signed with Ed25519 automatically; to use your own "
|
|
2136
|
+
"key set LOKI_RECEIPT_SIGNING_KEY or LOKI_RECEIPT_SIGNING_KEY_FILE "
|
|
2137
|
+
"(docs/SIGNED-RECEIPTS.md).\n")
|
|
2138
|
+
|
|
2139
|
+
# Ed25519 attestation over the SAME digest, checkable against a published
|
|
2140
|
+
# JWKS (or a jwks.json file, offline) with no key exchange. Binds the
|
|
2141
|
+
# DIGEST, not the body, and is written INSIDE `verification`, which the hash
|
|
2142
|
+
# excludes: anywhere else would change the hashed bytes and make every
|
|
2143
|
+
# honest receipt read as tampered. load_signing_key() auto-generates the
|
|
2144
|
+
# default local key, so a first run signs. Best-effort: a signing failure
|
|
2145
|
+
# must not cost the user their receipt (an unattested receipt has its own
|
|
2146
|
+
# verdict). Missing `cryptography` yields no attestation.
|
|
2147
|
+
try:
|
|
2148
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(
|
|
2149
|
+
os.path.abspath(__file__))))
|
|
2150
|
+
from receipt_jwt import load_signing_key, sign_attestation
|
|
2151
|
+
_priv, _kid = load_signing_key()
|
|
2152
|
+
if _priv is not None:
|
|
2153
|
+
_tok = sign_attestation(
|
|
2154
|
+
_priv, _kid,
|
|
2155
|
+
job_id=str(redacted.get("run_id") or ""),
|
|
2156
|
+
run_id=str(redacted.get("run_id") or ""),
|
|
2157
|
+
receipt_hash=digest,
|
|
2158
|
+
)
|
|
2159
|
+
if _tok:
|
|
2160
|
+
verification["attestation"] = _tok
|
|
2161
|
+
verification["attestation_kid"] = _kid
|
|
2162
|
+
except Exception:
|
|
2163
|
+
pass
|
|
2208
2164
|
|
|
2209
2165
|
redacted["verification"] = verification
|
|
2210
2166
|
|
|
@@ -1255,19 +1255,15 @@
|
|
|
1255
1255
|
if (hash) {
|
|
1256
1256
|
kv += '<div class="k">Integrity hash</div><div class="v">' + esc(algo) + ":" + esc(hash) + "</div>";
|
|
1257
1257
|
}
|
|
1258
|
-
// Signing state, named explicitly
|
|
1259
|
-
//
|
|
1260
|
-
//
|
|
1261
|
-
|
|
1262
|
-
// proof-generator.py and is OFF by default -- deliberately, because a
|
|
1263
|
-
// self-minted key would flip proof-verify.py's generator_trusted to false
|
|
1264
|
-
// while the key came from the very process under audit.
|
|
1265
|
-
var signed = !!g(p, "verification.gpg_signature", "");
|
|
1258
|
+
// Signing state, named explicitly: a reader must be told which state this
|
|
1259
|
+
// receipt is in, not just what the hash cannot prove. Signing is an Ed25519
|
|
1260
|
+
// attestation (verification.attestation), auto-generated on first run.
|
|
1261
|
+
var signed = !!g(p, "verification.attestation", "");
|
|
1266
1262
|
kv += '<div class="k">Signature</div><div class="v">' +
|
|
1267
|
-
(signed ? "SIGNED (
|
|
1263
|
+
(signed ? "SIGNED (Ed25519 attestation)" : "UNSIGNED") + "</div>";
|
|
1268
1264
|
var signNote = signed
|
|
1269
|
-
? '<p class="hash-note">This receipt carries
|
|
1270
|
-
: '<p class="hash-note">This receipt is UNSIGNED, so it trusts the generator that produced it (<code>loki proof verify</code> reports <code>generator_trusted: true</code>).
|
|
1265
|
+
? '<p class="hash-note">This receipt carries an Ed25519 attestation over the integrity hash. <code>loki proof verify</code> checks it offline against the signer public key.</p>'
|
|
1266
|
+
: '<p class="hash-note">This receipt is UNSIGNED, so it trusts the generator that produced it (<code>loki proof verify</code> reports <code>generator_trusted: true</code>). Install the <code>cryptography</code> package so the next run signs automatically (docs/SIGNED-RECEIPTS.md).</p>';
|
|
1271
1267
|
// Model provenance from the append-only decision trail
|
|
1272
1268
|
// (.loki/decisions/decisions.jsonl, written once per dispatch).
|
|
1273
1269
|
//
|