loki-mode 9.19.1 → 9.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/lib/deadline.py +21 -1
- package/autonomy/lib/outcome_contract.py +128 -0
- package/autonomy/lib/proof-generator.py +45 -0
- package/autonomy/lib/proof-verify.py +53 -0
- package/autonomy/loki +437 -10
- package/autonomy/receipt_jwt.py +308 -0
- package/autonomy/run.sh +37 -2
- package/autonomy/trigger-server.py +186 -1
- package/bin/loki +1 -0
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +65 -0
- package/docs/COMPETITIVE-SCORECARD.md +31 -0
- package/docs/OUTCOME-CANARY.md +61 -0
- package/docs/OUTCOME-ROUTER.md +7 -0
- package/docs/PROOF-PASSPORT.md +29 -0
- package/docs/SIGNED-RECEIPTS.md +111 -0
- package/docs/metrics.md +32 -0
- package/events/emit.sh +31 -0
- package/loki-ts/dist/loki.js +2 -2
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/tools/outcome-canary.py +194 -0
- package/tools/outcome-router.py +80 -0
- package/tools/proof-passport.py +175 -0
- package/web-app/dist/assets/{AdminPage-CcCJ0Sjt.js → AdminPage-DNGNuAge.js} +1 -1
- package/web-app/dist/assets/{Avatar-DK8kmayw.js → Avatar-CqAveJaT.js} +1 -1
- package/web-app/dist/assets/{Badge-4uAWnemi.js → Badge-BtMejA0Z.js} +1 -1
- package/web-app/dist/assets/{Button-BBMk33tk.js → Button-CikxmqSs.js} +1 -1
- package/web-app/dist/assets/{ComparePage-bt9rwvST.js → ComparePage-BB8DJBWR.js} +1 -1
- package/web-app/dist/assets/EvidenceReceiptPanel-6oquKEjd.js +1 -0
- package/web-app/dist/assets/{GitHubIssuesPanel-WDbH47UM.js → GitHubIssuesPanel-5XLYWzWH.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-C2CiYtTx.js → GitHubPRsPanel-DUfmFYeu.js} +1 -1
- package/web-app/dist/assets/{HomePage-BQk-MUjn.js → HomePage-DZ4MmOdH.js} +5 -5
- package/web-app/dist/assets/{LoginPage-DMOZVGGL.js → LoginPage-VMzI6ROD.js} +1 -1
- package/web-app/dist/assets/{MagicPage-Bzp2Nt1z.js → MagicPage-CFbX5gWg.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-C39JVdsw.js → MetricsPage-DbL6xmRQ.js} +2 -2
- package/web-app/dist/assets/{NotFoundPage-6vT_U9UL.js → NotFoundPage-BUrjXPAR.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-BfFcZp-E.js → ProjectPage-iP6dJihg.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage-CPMBf8Wt.js → ProjectsPage-BibN9bHl.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-BnNN6ETl.js → SettingsPage-C0WJp1hW.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-WDrMf-cx.js → ShowcasePage-DUv0Njp8.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-DX4jb2e8.js → SystemSettingsPage-BIaCB0Vn.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-BCfqcXzu.js → TeamsPage-BE0szv9r.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-CZvmimDj.js → TemplatesPage-BzkA9kat.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-BlRqFwWV.js → TerminalOutput-DBrdT_Fp.js} +1 -1
- package/web-app/dist/assets/{activity-CacZsUyr.js → activity-AbfFz38r.js} +1 -1
- package/web-app/dist/assets/{bell-DK2qtHnk.js → bell-DF6Z6y5U.js} +1 -1
- package/web-app/dist/assets/{bot-CkcUtHad.js → bot-DQ4np_3n.js} +1 -1
- package/web-app/dist/assets/{check-CbCPjX3M.js → check-RWuguCwE.js} +1 -1
- package/web-app/dist/assets/{chevron-left-5NUKWw3i.js → chevron-left-VpbQPOkC.js} +1 -1
- package/web-app/dist/assets/{circle-alert-S7uFoxC2.js → circle-alert-DwT82lJx.js} +1 -1
- package/web-app/dist/assets/{clock-CaQRrIrs.js → clock-BBlUa93G.js} +1 -1
- package/web-app/dist/assets/{cloud-DBAX6c0r.js → cloud-Dgbz3VCA.js} +1 -1
- package/web-app/dist/assets/{code-xml-De5-EXv3.js → code-xml-r6qqION5.js} +1 -1
- package/web-app/dist/assets/{copy-CUkT6k1v.js → copy-DMHj52B8.js} +1 -1
- package/web-app/dist/assets/{database-BAWf1Gwt.js → database-uJqHSHR1.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-Ji8zk86R.js → dollar-sign-BZagljek.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-ChtXoBwS.js → file-code-corner-DMopmhu0.js} +1 -1
- package/web-app/dist/assets/{file-plus-bFa37P76.js → file-plus-DU_hT_3w.js} +1 -1
- package/web-app/dist/assets/{folder-open-DhXpXscO.js → folder-open-BP5TtQAW.js} +1 -1
- package/web-app/dist/assets/{git-commit-horizontal-DVPeDQ3j.js → git-commit-horizontal-zFk_IobS.js} +1 -1
- package/web-app/dist/assets/{globe-BPZgPeeu.js → globe-mthCRtfp.js} +1 -1
- package/web-app/dist/assets/{hammer-jLCaujYH.js → hammer-BWaLBvSA.js} +1 -1
- package/web-app/dist/assets/{index-B-0iHBPO.js → index-BXA3UT6P.js} +3 -3
- package/web-app/dist/assets/{index-BfZSDej1.css → index-DgpUuqwR.css} +1 -1
- package/web-app/dist/assets/{layers-B1vsrsFW.js → layers-Q1LXV5fy.js} +1 -1
- package/web-app/dist/assets/{lightbulb-C-uLoq9Y.js → lightbulb-vRRNJ3dB.js} +1 -1
- package/web-app/dist/assets/{loader-circle-JTfD-ZuM.js → loader-circle-KgjeHEcV.js} +1 -1
- package/web-app/dist/assets/{lock-G9rxD4gZ.js → lock-BerKqno7.js} +1 -1
- package/web-app/dist/assets/{mail-BJ0PTN_V.js → mail-XAxTbprX.js} +1 -1
- package/web-app/dist/assets/{package-CXClfLOO.js → package-DLPKWAvW.js} +1 -1
- package/web-app/dist/assets/{plus-EoL5OCB7.js → plus-NMxxwHMn.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-BjREUnVq.js → refresh-cw-Dt42kYtM.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-DahWX07H.js → rotate-ccw-BCR4Tp9u.js} +1 -1
- package/web-app/dist/assets/{save-Dek3gCn1.js → save-sR4MJd42.js} +1 -1
- package/web-app/dist/assets/{server-D6V1BAia.js → server-SXkchokx.js} +1 -1
- package/web-app/dist/assets/{shield-alert-BtTK5Sxb.js → shield-alert-Bvgp6_Dz.js} +1 -1
- package/web-app/dist/assets/{trash-2-BT5o_g0r.js → trash-2-D0IGudH7.js} +1 -1
- package/web-app/dist/assets/{trending-down-D4Jk7KF3.js → trending-down-BbFFBq-v.js} +1 -1
- package/web-app/dist/assets/{trending-up-EQFTzhEo.js → trending-up-DDrJKL5i.js} +1 -1
- package/web-app/dist/assets/{upload-JfI5lCSE.js → upload-C54rjN8K.js} +1 -1
- package/web-app/dist/assets/{usePolling-BnhPUuGd.js → usePolling-D7fw4YSM.js} +1 -1
- package/web-app/dist/assets/{user-DSUiUYtj.js → user-Cg7fbzdh.js} +1 -1
- package/web-app/dist/index.html +2 -2
- package/web-app/server.py +78 -0
package/autonomy/loki
CHANGED
|
@@ -750,7 +750,11 @@ loki_remote_fetch_receipt() {
|
|
|
750
750
|
printf '%s' "$resp" > "$tmp" && mv "$tmp" "$dir/proof.json"
|
|
751
751
|
echo " Evidence Receipt: $dir/proof.json"
|
|
752
752
|
|
|
753
|
-
|
|
753
|
+
# The URL is passed so the receipt's attestation can be checked against that
|
|
754
|
+
# server's published JWKS. Without it the attestation is simply not checked
|
|
755
|
+
# and the verdict falls back to the gpg path, which is the pre-existing
|
|
756
|
+
# behaviour rather than a downgrade.
|
|
757
|
+
loki_remote_verify_receipt "$dir/proof.json" "$url"
|
|
754
758
|
}
|
|
755
759
|
|
|
756
760
|
# Verify a fetched receipt locally and print exactly what was and was not
|
|
@@ -801,6 +805,121 @@ PY
|
|
|
801
805
|
esac
|
|
802
806
|
}
|
|
803
807
|
|
|
808
|
+
# Check a receipt's JWKS attestation against the server's PUBLISHED key set.
|
|
809
|
+
#
|
|
810
|
+
# This is the half gpg cannot do over a network. gpg proves provenance only if
|
|
811
|
+
# you already hold the publisher's key, which a submitter who ran
|
|
812
|
+
# `loki start --remote` never does -- so the honest gpg outcome there is
|
|
813
|
+
# NOT CHECKED, and it stays that way until someone performs an out-of-band key
|
|
814
|
+
# import. Fetching /.well-known/jwks.json over the same TLS the API already
|
|
815
|
+
# uses removes that step.
|
|
816
|
+
#
|
|
817
|
+
# Prints "ok", "bad", or "" (could not check). Fails toward "" so an
|
|
818
|
+
# unreachable JWKS never manufactures a verdict in either direction -- an
|
|
819
|
+
# absent measurement is not evidence, and inventing a PASS here would be worse
|
|
820
|
+
# than inventing a FAIL.
|
|
821
|
+
loki_remote_attestation_status() {
|
|
822
|
+
local pj="$1" url="$2"
|
|
823
|
+
[ -n "$url" ] || { printf ''; return 0; }
|
|
824
|
+
command -v python3 >/dev/null 2>&1 || { printf ''; return 0; }
|
|
825
|
+
local tok
|
|
826
|
+
tok="$(jq -r '.verification.attestation // empty' "$pj" 2>/dev/null)"
|
|
827
|
+
[ -n "$tok" ] || { printf ''; return 0; }
|
|
828
|
+
python3 - "$pj" "$url" "${_LOKI_SCRIPT_DIR}" <<'PY' 2>/dev/null || printf ''
|
|
829
|
+
import json, sys, hashlib, urllib.request
|
|
830
|
+
sys.path.insert(0, sys.argv[3])
|
|
831
|
+
try:
|
|
832
|
+
from receipt_jwt import verify_attestation
|
|
833
|
+
except ImportError:
|
|
834
|
+
sys.exit(1) # -> "" : cannot check, never a verdict
|
|
835
|
+
proof = json.load(open(sys.argv[1]))
|
|
836
|
+
tok = (proof.get("verification") or {}).get("attestation") or ""
|
|
837
|
+
if not tok:
|
|
838
|
+
sys.exit(1)
|
|
839
|
+
try:
|
|
840
|
+
with urllib.request.urlopen(
|
|
841
|
+
sys.argv[2].rstrip("/") + "/.well-known/jwks.json", timeout=10) as r:
|
|
842
|
+
jwks = json.loads(r.read())
|
|
843
|
+
except Exception:
|
|
844
|
+
sys.exit(1) # unreachable JWKS is UNCHECKED, not a failure
|
|
845
|
+
ok, claims = verify_attestation(tok, jwks)
|
|
846
|
+
if not ok:
|
|
847
|
+
print("bad"); sys.exit(0)
|
|
848
|
+
# The signature verifying is not enough on its own: it must attest to THIS
|
|
849
|
+
# receipt. Recompute the digest the same way the receipt records it (with
|
|
850
|
+
# `verification` stripped) and require the signed claim to match, or a valid
|
|
851
|
+
# token lifted from a different run would vouch for these bytes.
|
|
852
|
+
proof.pop("verification", None)
|
|
853
|
+
canon = json.dumps(proof, sort_keys=True, separators=(",", ":")).encode()
|
|
854
|
+
print("ok" if claims.get("receipt_sha256") == hashlib.sha256(canon).hexdigest()
|
|
855
|
+
else "bad")
|
|
856
|
+
PY
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
# Check a receipt's attestation against a key set given as a URL OR a file.
|
|
860
|
+
#
|
|
861
|
+
# The file form is what makes this usable by a third party with no network and
|
|
862
|
+
# no Loki install: hand them proof.json and jwks.json and they can check both
|
|
863
|
+
# integrity and provenance themselves. That is the whole claim -- "a receipt you
|
|
864
|
+
# can check yourself" is false if checking requires our infrastructure.
|
|
865
|
+
#
|
|
866
|
+
# Prints "ok", "bad", "none" (no attestation present), or "" (could not check).
|
|
867
|
+
# "none" and "" are deliberately distinct: one is a fact about the receipt, the
|
|
868
|
+
# other is an absent measurement, and collapsing them would report an unchecked
|
|
869
|
+
# receipt as an unattested one.
|
|
870
|
+
# NOTE: this shares its verification logic with loki_remote_attestation_status
|
|
871
|
+
# above. The two differ only in their key-set source (that one is URL-only, from
|
|
872
|
+
# a live fetch during a --remote run; this one also accepts a FILE for the
|
|
873
|
+
# offline third-party case). If you change the digest-recompute rule, CHANGE
|
|
874
|
+
# BOTH -- they are covered by different tests
|
|
875
|
+
# (test-remote-attestation-verdict.sh and test-proof-verify-jwks.sh), so a
|
|
876
|
+
# one-sided edit passes one suite and silently weakens the other. Mutation
|
|
877
|
+
# testing found exactly this: a mutation applied to the first copy left every
|
|
878
|
+
# assertion of the second suite green.
|
|
879
|
+
loki_proof_attestation_check() {
|
|
880
|
+
local pj="$1" src="$2"
|
|
881
|
+
[ -n "$src" ] || { printf ''; return 0; }
|
|
882
|
+
command -v python3 >/dev/null 2>&1 || { printf ''; return 0; }
|
|
883
|
+
python3 - "$pj" "$src" "${_LOKI_SCRIPT_DIR}" <<'PY' 2>/dev/null || printf ''
|
|
884
|
+
import json, sys, os, hashlib, urllib.request
|
|
885
|
+
sys.path.insert(0, sys.argv[3])
|
|
886
|
+
try:
|
|
887
|
+
from receipt_jwt import verify_attestation
|
|
888
|
+
except ImportError:
|
|
889
|
+
sys.exit(1) # "" : cannot check
|
|
890
|
+
try:
|
|
891
|
+
proof = json.load(open(sys.argv[1]))
|
|
892
|
+
except Exception:
|
|
893
|
+
sys.exit(1)
|
|
894
|
+
tok = (proof.get("verification") or {}).get("attestation") or ""
|
|
895
|
+
if not tok:
|
|
896
|
+
print("none"); sys.exit(0) # a FACT about the receipt, not an error
|
|
897
|
+
src = sys.argv[2]
|
|
898
|
+
try:
|
|
899
|
+
if os.path.isfile(src):
|
|
900
|
+
jwks = json.load(open(src))
|
|
901
|
+
else:
|
|
902
|
+
if not src.startswith(("http://", "https://")):
|
|
903
|
+
sys.exit(1)
|
|
904
|
+
url = src if src.rstrip("/").endswith("jwks.json") else \
|
|
905
|
+
src.rstrip("/") + "/.well-known/jwks.json"
|
|
906
|
+
with urllib.request.urlopen(url, timeout=10) as r:
|
|
907
|
+
jwks = json.loads(r.read())
|
|
908
|
+
except Exception:
|
|
909
|
+
sys.exit(1) # "" : unreachable/unreadable, no verdict
|
|
910
|
+
ok, claims = verify_attestation(tok, jwks)
|
|
911
|
+
if not ok:
|
|
912
|
+
print("bad"); sys.exit(0)
|
|
913
|
+
# A verifying signature is not enough: it must attest to THESE bytes. Recompute
|
|
914
|
+
# the digest the way the receipt records it, so a token lifted from another run
|
|
915
|
+
# cannot vouch for this one.
|
|
916
|
+
proof.pop("verification", None)
|
|
917
|
+
canon = json.dumps(proof, sort_keys=True, separators=(",", ":")).encode()
|
|
918
|
+
print("ok" if claims.get("receipt_sha256") == hashlib.sha256(canon).hexdigest()
|
|
919
|
+
else "bad")
|
|
920
|
+
PY
|
|
921
|
+
}
|
|
922
|
+
|
|
804
923
|
loki_remote_verify_receipt() {
|
|
805
924
|
local pj="$1"
|
|
806
925
|
local verifier="${_LOKI_SCRIPT_DIR}/lib/proof-verify.py"
|
|
@@ -828,6 +947,10 @@ loki_remote_verify_receipt() {
|
|
|
828
947
|
# a gate that cries wolf every time trains users to ignore the verdict.
|
|
829
948
|
# Only hash_ok and gpg_ok are load-bearing here; drift is not our verdict.
|
|
830
949
|
local out hash_ok gpg_ok
|
|
950
|
+
# Second argument is the remote URL when this receipt came from a cluster;
|
|
951
|
+
# empty for a local receipt, which simply means no attestation is checked.
|
|
952
|
+
local _attest_url="${2:-}" _attest=""
|
|
953
|
+
_attest="$(loki_remote_attestation_status "$pj" "$_attest_url")"
|
|
831
954
|
out="$(python3 "$verifier" "$pj" "${TARGET_DIR:-.}" 2>/dev/null)"
|
|
832
955
|
hash_ok="$(printf '%s' "$out" | jq -r '.hash_ok // false' 2>/dev/null)"
|
|
833
956
|
gpg_ok="$(printf '%s' "$out" | jq -r '.gpg_ok | tostring' 2>/dev/null)"
|
|
@@ -849,6 +972,18 @@ loki_remote_verify_receipt() {
|
|
|
849
972
|
echo " the build ran elsewhere. Not tampering, and not part of the verdict."
|
|
850
973
|
fi
|
|
851
974
|
|
|
975
|
+
# An attestation that does not verify is an ACCUSATION, and it is checked
|
|
976
|
+
# before the gpg branches because it is the stronger statement: the server's
|
|
977
|
+
# own published key says this token does not cover these bytes. Either the
|
|
978
|
+
# receipt was altered after the server signed it, or the token was lifted
|
|
979
|
+
# from a different run. Both mean: do not trust this build.
|
|
980
|
+
if [ "$_attest" = "bad" ]; then
|
|
981
|
+
echo -e " ${RED}TAMPERED: the receipt's attestation does not verify against the server's published keys.${NC}" >&2
|
|
982
|
+
echo " Either these bytes were altered after signing, or the attestation" >&2
|
|
983
|
+
echo " belongs to a different run. Do not trust this build." >&2
|
|
984
|
+
return 1
|
|
985
|
+
fi
|
|
986
|
+
|
|
852
987
|
# gpg_ok=false covers TWO facts that must never be conflated: the signature
|
|
853
988
|
# is cryptographically bad over this content (the receipt was ALTERED), and
|
|
854
989
|
# gpg could not evaluate it at all (we do not hold the public key). Only the
|
|
@@ -866,6 +1001,17 @@ loki_remote_verify_receipt() {
|
|
|
866
1001
|
_why="$(loki_remote_gpg_status "$pj")"
|
|
867
1002
|
fi
|
|
868
1003
|
if [ "$_why" = "nopubkey" ]; then
|
|
1004
|
+
# JWKS exists precisely for this case: the gpg key is absent, but a
|
|
1005
|
+
# remote-issued receipt can still prove provenance against the
|
|
1006
|
+
# server's published key set, with no out-of-band import.
|
|
1007
|
+
if [ "$_attest" = "ok" ]; then
|
|
1008
|
+
echo -e " ${GREEN}VERIFIED${NC}: integrity hash matches and the receipt's attestation is valid."
|
|
1009
|
+
echo " Proven: these bytes are unaltered, and the build server signed them"
|
|
1010
|
+
echo " with a key published at ${_attest_url}/.well-known/jwks.json."
|
|
1011
|
+
echo " The gpg key is not in your keyring; the attestation covers provenance."
|
|
1012
|
+
echo " NOT proven: that the code is correct. A receipt records which checks ran."
|
|
1013
|
+
return 0
|
|
1014
|
+
fi
|
|
869
1015
|
echo -e " ${YELLOW}NOT CHECKED${NC}: the receipt is signed, but the signing key is not in your keyring." >&2
|
|
870
1016
|
echo " Nothing is wrong with the contents: the integrity hash matches. The" >&2
|
|
871
1017
|
echo " signature could not be evaluated, so PROVENANCE is unproven here." >&2
|
|
@@ -897,6 +1043,20 @@ loki_remote_verify_receipt() {
|
|
|
897
1043
|
return 0
|
|
898
1044
|
fi
|
|
899
1045
|
|
|
1046
|
+
# No gpg signature, but a verified attestation. This is the normal shape for
|
|
1047
|
+
# a cluster build: the receiver signs, the build never holds a gpg key, and
|
|
1048
|
+
# provenance is still proven -- so calling it UNSIGNED would understate what
|
|
1049
|
+
# was actually checked, the mirror of the error we refuse in the other
|
|
1050
|
+
# direction.
|
|
1051
|
+
if [ "$_attest" = "ok" ]; then
|
|
1052
|
+
echo -e " ${GREEN}VERIFIED${NC}: integrity hash matches and the receipt's attestation is valid."
|
|
1053
|
+
echo " Proven: these bytes are unaltered, and they were signed by the build"
|
|
1054
|
+
echo " server whose key is published at ${_attest_url}/.well-known/jwks.json."
|
|
1055
|
+
echo " Verify that server is one you trust."
|
|
1056
|
+
echo " NOT proven: that the code is correct. A receipt records which checks ran."
|
|
1057
|
+
return 0
|
|
1058
|
+
fi
|
|
1059
|
+
|
|
900
1060
|
echo -e " ${YELLOW}UNSIGNED${NC}: integrity hash matches. This is NOT proof of provenance."
|
|
901
1061
|
echo " Proven: the receipt is internally consistent with its own hash."
|
|
902
1062
|
echo " NOT proven: who produced it. An unsigned receipt is forgeable -- whoever"
|
|
@@ -1501,7 +1661,7 @@ show_landing() {
|
|
|
1501
1661
|
}
|
|
1502
1662
|
|
|
1503
1663
|
# Detect argument type for unified `loki start` (v6.84.0)
|
|
1504
|
-
# Returns one of: prd | issue | empty | unknown
|
|
1664
|
+
# Returns one of: prd | issue | repo | empty | unknown
|
|
1505
1665
|
# Logic:
|
|
1506
1666
|
# - empty arg -> "empty"
|
|
1507
1667
|
# - file path ending in .md/.json/.txt AND exists on disk -> "prd"
|
|
@@ -1565,7 +1725,10 @@ detect_arg_type() {
|
|
|
1565
1725
|
# BUT only if a file with that literal name does NOT exist on disk
|
|
1566
1726
|
# (ambiguity resolution: files-on-disk win over issue-number heuristic).
|
|
1567
1727
|
if [[ "$arg" =~ ^#?[0-9]+$ ]]; then
|
|
1568
|
-
if [ -
|
|
1728
|
+
if [ -d "$arg" ]; then
|
|
1729
|
+
# Directory on disk wins -- same rule as the -d branch below.
|
|
1730
|
+
echo "repo"
|
|
1731
|
+
elif [ -e "$arg" ]; then
|
|
1569
1732
|
# File on disk wins -- treat as PRD
|
|
1570
1733
|
echo "prd"
|
|
1571
1734
|
else
|
|
@@ -1574,6 +1737,20 @@ detect_arg_type() {
|
|
|
1574
1737
|
return 0
|
|
1575
1738
|
fi
|
|
1576
1739
|
|
|
1740
|
+
# An existing DIRECTORY is a repo to improve, not a spec to build from.
|
|
1741
|
+
# Before this, a directory fell into the "prd" branch below and was handed
|
|
1742
|
+
# to run.sh as a PRD path; it has no .md-family extension, so the
|
|
1743
|
+
# not-found guard skipped it and the run silently proceeded as if no spec
|
|
1744
|
+
# had been given. `loki start <dir>` now means "work on that repo".
|
|
1745
|
+
# ponytail: a directory named `spec.md` still returns "prd" -- the
|
|
1746
|
+
# extension branch at the top of this function wins. Deliberate: an
|
|
1747
|
+
# explicit spec extension is the stronger signal. Move this block above
|
|
1748
|
+
# that branch if directories-with-spec-names ever matter.
|
|
1749
|
+
if [ -d "$arg" ]; then
|
|
1750
|
+
echo "repo"
|
|
1751
|
+
return 0
|
|
1752
|
+
fi
|
|
1753
|
+
|
|
1577
1754
|
# An existing file on disk is unambiguously a PRD, even if its name has
|
|
1578
1755
|
# spaces -- a real path the user pointed at wins over the brief heuristic.
|
|
1579
1756
|
if [ -e "$arg" ]; then
|
|
@@ -1844,6 +2021,8 @@ cmd_start() {
|
|
|
1844
2021
|
local mirofish_bg=false
|
|
1845
2022
|
local mirofish_disabled=false
|
|
1846
2023
|
local no_plan=false # v6.81.1: --no-plan opts out of auto-plan display
|
|
2024
|
+
local repo_depth="full" # repository targets default to full verification
|
|
2025
|
+
local repo_depth_explicit="" # "" | "fast" | "full" -- what the operator asked for
|
|
1847
2026
|
local remote_url="${LOKI_REMOTE_URL:-}" # --remote: submit to a deployed cluster
|
|
1848
2027
|
|
|
1849
2028
|
# v6.84.0: unified entry point -- explicit mode overrides + issue-mode args
|
|
@@ -1862,9 +2041,10 @@ cmd_start() {
|
|
|
1862
2041
|
--help|-h)
|
|
1863
2042
|
echo -e "${BOLD}loki start${NC} - Unified entry point for Loki Mode (v6.84.0)"
|
|
1864
2043
|
echo ""
|
|
1865
|
-
echo "Usage: loki start [PRD|ISSUE-REF] [options]"
|
|
2044
|
+
echo "Usage: loki start [REPO|PRD|ISSUE-REF] [options]"
|
|
1866
2045
|
echo ""
|
|
1867
2046
|
echo "Modes (auto-detected from input):"
|
|
2047
|
+
echo " REPO mode - existing directory -> one bounded verified improvement"
|
|
1868
2048
|
echo " PRD mode - path ending in .md/.json/.txt/.yaml -> build from PRD"
|
|
1869
2049
|
echo " ISSUE mode - GitHub/GitLab/Jira/Azure DevOps URL, owner/repo#N,"
|
|
1870
2050
|
echo " PROJ-123, #123, or bare number -> generate PRD from issue"
|
|
@@ -1873,6 +2053,7 @@ cmd_start() {
|
|
|
1873
2053
|
echo " no input - analyze current directory, auto-generate PRD"
|
|
1874
2054
|
echo ""
|
|
1875
2055
|
echo "Arguments:"
|
|
2056
|
+
echo " REPO Existing project directory"
|
|
1876
2057
|
echo " PRD Path to PRD file (.md/.json/.txt/.yaml)"
|
|
1877
2058
|
echo " ISSUE-REF Issue URL, number, or tracker key (see above)"
|
|
1878
2059
|
echo ""
|
|
@@ -1913,6 +2094,10 @@ cmd_start() {
|
|
|
1913
2094
|
echo " --mirofish-bg Run MiroFish pipeline in background"
|
|
1914
2095
|
echo " --no-mirofish Disable MiroFish even if env var is set"
|
|
1915
2096
|
echo " --no-plan Skip auto-shown PRD analysis at startup"
|
|
2097
|
+
echo " --fast REPO mode: capped lightweight pass, gates reduced"
|
|
2098
|
+
echo " (no council verdict; alias --repo-fast)"
|
|
2099
|
+
echo " --full REPO mode: explicit full verification, the default"
|
|
2100
|
+
echo " (wins over --fast; alias --repo-full)"
|
|
1916
2101
|
echo " --brief \"TEXT\" Zero-config fast first run from a one-line brief"
|
|
1917
2102
|
echo " --yes, -y Skip confirmation prompts (auto-confirm)"
|
|
1918
2103
|
echo " --quiet Suppress [INFO]/[STEP]; warnings and errors still print"
|
|
@@ -1947,6 +2132,7 @@ cmd_start() {
|
|
|
1947
2132
|
echo ""
|
|
1948
2133
|
echo "Examples:"
|
|
1949
2134
|
echo " loki start # Interactive, analyze current dir"
|
|
2135
|
+
echo " loki start ./my-project # Improve an existing project"
|
|
1950
2136
|
echo " loki start \"build a todo app\" # BRIEF mode (zero-config fast run)"
|
|
1951
2137
|
echo " loki start --brief \"snake\" # BRIEF mode (single-word escape hatch)"
|
|
1952
2138
|
echo " loki start ./prd.md # PRD mode"
|
|
@@ -2265,6 +2451,22 @@ cmd_start() {
|
|
|
2265
2451
|
no_plan=true
|
|
2266
2452
|
shift
|
|
2267
2453
|
;;
|
|
2454
|
+
--fast|--repo-fast)
|
|
2455
|
+
# Fail closed on a conflict, same doctrine as --isolation below:
|
|
2456
|
+
# never silently downgrade to weaker verification than asked for.
|
|
2457
|
+
# --full wins regardless of order, so `--fast --full` and
|
|
2458
|
+
# `--full --fast` both keep the gates ON.
|
|
2459
|
+
if [ "$repo_depth_explicit" != "full" ]; then
|
|
2460
|
+
repo_depth="fast"
|
|
2461
|
+
repo_depth_explicit="fast"
|
|
2462
|
+
fi
|
|
2463
|
+
shift
|
|
2464
|
+
;;
|
|
2465
|
+
--full|--repo-full)
|
|
2466
|
+
repo_depth="full"
|
|
2467
|
+
repo_depth_explicit="full"
|
|
2468
|
+
shift
|
|
2469
|
+
;;
|
|
2268
2470
|
--brief)
|
|
2269
2471
|
# R7: explicit one-line brief (escape hatch for single-word
|
|
2270
2472
|
# briefs that detect_arg_type would otherwise treat as a PRD
|
|
@@ -2557,6 +2759,22 @@ cmd_start() {
|
|
|
2557
2759
|
detected_type="empty"
|
|
2558
2760
|
fi
|
|
2559
2761
|
|
|
2762
|
+
# Resolve the directory while the positional path is still relative to the
|
|
2763
|
+
# caller, then enter it before creating any .loki state. Quoted `cd --`
|
|
2764
|
+
# keeps spaces and shell metacharacters inert.
|
|
2765
|
+
if [ "$detected_type" = "repo" ]; then
|
|
2766
|
+
local repo_target
|
|
2767
|
+
repo_target=$(cd -- "$positional_arg" 2>/dev/null && pwd -P) || {
|
|
2768
|
+
echo -e "${RED}Error: cannot enter repository directory: $positional_arg${NC}" >&2
|
|
2769
|
+
return 1
|
|
2770
|
+
}
|
|
2771
|
+
cd -- "$repo_target" || return 1
|
|
2772
|
+
if [ "$LOKI_DIR" = ".loki" ]; then
|
|
2773
|
+
LOKI_DIR="$repo_target/.loki"
|
|
2774
|
+
fi
|
|
2775
|
+
export LOKI_REPO_TARGET="$repo_target"
|
|
2776
|
+
fi
|
|
2777
|
+
|
|
2560
2778
|
# ISSUE mode: delegate to cmd_run's code path. To avoid duplicating ~450
|
|
2561
2779
|
# lines of issue-fetching + PR-creation logic, we re-dispatch through
|
|
2562
2780
|
# cmd_run with reconstructed args. cmd_run then calls cmd_start again
|
|
@@ -2638,12 +2856,66 @@ cmd_start() {
|
|
|
2638
2856
|
echo ""
|
|
2639
2857
|
fi
|
|
2640
2858
|
|
|
2859
|
+
# REPO mode: `loki start <directory>`. We already cd'd into the target
|
|
2860
|
+
# above, so this is the no-arg codebase path with one difference -- a
|
|
2861
|
+
# synthesized brief that bounds the work to ONE high-value improvement
|
|
2862
|
+
# instead of "build out the whole project".
|
|
2863
|
+
#
|
|
2864
|
+
# The DEFAULT and `--full` deliberately do NOT call
|
|
2865
|
+
# set_ttfv_lightweight_profile: that helper turns the council and the
|
|
2866
|
+
# code-review phase OFF, and the whole point of this entry point is to hand
|
|
2867
|
+
# back VERIFIED evidence. Scope is bounded by the brief text, not by
|
|
2868
|
+
# disabling the gates that check the work. Those runs keep full depth and
|
|
2869
|
+
# report LOKI_TTFV=repo -- the same honest full-depth signal the no-arg path
|
|
2870
|
+
# uses.
|
|
2871
|
+
#
|
|
2872
|
+
# `--fast` is the opt-in exception: it applies the SAME lightweight profile
|
|
2873
|
+
# the brief path uses (capped iterations, council off, simple tier, heavy
|
|
2874
|
+
# phases off) and reports LOKI_TTFV=repo-fast, a distinct signal so the
|
|
2875
|
+
# end-of-run wording in run.sh cannot claim council verdicts that never ran.
|
|
2876
|
+
if [ "$detected_type" = "repo" ]; then
|
|
2877
|
+
local version
|
|
2878
|
+
version=$(get_version)
|
|
2879
|
+
# Same cap the brief sub-path reads, so "fast" means one thing.
|
|
2880
|
+
local _repo_max_iter="${LOKI_MAX_ITERATIONS:-3}"
|
|
2881
|
+
mkdir -p "$LOKI_DIR" 2>/dev/null || true
|
|
2882
|
+
find "$LOKI_DIR" -maxdepth 1 -name 'repo-prd-*.md' -mtime +1 -delete 2>/dev/null || true
|
|
2883
|
+
local repo_prd="$LOKI_DIR/repo-prd-$$.md"
|
|
2884
|
+
synthesize_repo_prd "$repo_prd" "$repo_depth"
|
|
2885
|
+
prd_file="$repo_prd"
|
|
2886
|
+
|
|
2887
|
+
if [ "$repo_depth" = "fast" ]; then
|
|
2888
|
+
set_ttfv_lightweight_profile "$_repo_max_iter"
|
|
2889
|
+
export LOKI_TTFV=repo-fast
|
|
2890
|
+
no_plan=true
|
|
2891
|
+
fi
|
|
2892
|
+
|
|
2893
|
+
echo -e "${BOLD}Loki Mode v$version - Repo improvement${NC}"
|
|
2894
|
+
echo ""
|
|
2895
|
+
echo -e "${CYAN}Repo:${NC} $LOKI_REPO_TARGET"
|
|
2896
|
+
if [ "$repo_depth" = "fast" ]; then
|
|
2897
|
+
echo -e "${DIM}Loki will inspect this project, pick one high-value improvement,${NC}"
|
|
2898
|
+
echo -e "${DIM}implement it, and test it.${NC}"
|
|
2899
|
+
echo -e "${DIM}Fast pass: $_repo_max_iter iterations max, simple tier, council and${NC}"
|
|
2900
|
+
echo -e "${DIM}heavy phases OFF. Reduced gates: you get measured run evidence${NC}"
|
|
2901
|
+
echo -e "${DIM}(diffs, cost, time), NOT a council-verified verdict.${NC}"
|
|
2902
|
+
echo -e "${DIM}Run full verification with: loki start \"$LOKI_REPO_TARGET\" --full${NC}"
|
|
2903
|
+
else
|
|
2904
|
+
echo -e "${DIM}Loki will inspect this project, pick one high-value improvement,${NC}"
|
|
2905
|
+
echo -e "${DIM}implement it, test it, and produce verified evidence.${NC}"
|
|
2906
|
+
echo -e "${DIM}Full depth: quality gates and completion council stay ON.${NC}"
|
|
2907
|
+
fi
|
|
2908
|
+
echo -e "${DIM}Scope it yourself instead with: loki start \"<what you want>\".${NC}"
|
|
2909
|
+
echo ""
|
|
2910
|
+
fi
|
|
2911
|
+
|
|
2641
2912
|
# R7: existing-repo no-arg path also gets the TTFV "what next" framing at the
|
|
2642
2913
|
# end of the run. Execution here is UNCHANGED (full-depth no-PRD codebase
|
|
2643
2914
|
# analysis + generated-PRD-reuse); we only add accurate end-of-run framing.
|
|
2644
2915
|
# The signal value ("repo") drives the full-depth wording in run.sh so the
|
|
2645
2916
|
# message does not falsely claim a lightweight pass.
|
|
2646
|
-
if [ "$detected_type" = "empty" ]
|
|
2917
|
+
if { [ "$detected_type" = "empty" ] || [ "$detected_type" = "repo" ]; } \
|
|
2918
|
+
&& [ -z "${LOKI_TTFV:-}" ]; then
|
|
2647
2919
|
export LOKI_TTFV=repo
|
|
2648
2920
|
fi
|
|
2649
2921
|
|
|
@@ -6307,6 +6579,34 @@ else
|
|
|
6307
6579
|
DASHBOARD_DEFAULT_HOST="127.0.0.1"
|
|
6308
6580
|
fi
|
|
6309
6581
|
|
|
6582
|
+
# EXPOSED BIND REQUIRES AUTH (issue #188).
|
|
6583
|
+
#
|
|
6584
|
+
# dashboard/auth.py:788 returns True -- allow -- when neither LOKI_ENTERPRISE_AUTH
|
|
6585
|
+
# nor OIDC is configured. That makes all 166 Depends(require_scope) endpoints
|
|
6586
|
+
# open. On loopback that is defensible: the trust boundary is the machine.
|
|
6587
|
+
# Bound to 0.0.0.0 it is not, and NEITHER the Dockerfile NOR the Helm chart set
|
|
6588
|
+
# any auth env, so an operator following the shipped chart gets an
|
|
6589
|
+
# unauthenticated dashboard on every interface without choosing it.
|
|
6590
|
+
#
|
|
6591
|
+
# Refuses to start rather than auto-minting a token. An auto-token an operator
|
|
6592
|
+
# never sees is worse than a refusal: they believe they are protected, and the
|
|
6593
|
+
# credential is sitting in a container they did not know to guard. A refusal is
|
|
6594
|
+
# loud, happens at deploy time rather than after exposure, and names the fix.
|
|
6595
|
+
#
|
|
6596
|
+
# LOKI_DASHBOARD_ALLOW_INSECURE_BIND=1 is the deliberate override, for a cluster
|
|
6597
|
+
# whose network policy already bounds ingress. Named so it appears in a diff and
|
|
6598
|
+
# a reviewer can ask why it is set.
|
|
6599
|
+
_loki_dashboard_bind_is_safe() {
|
|
6600
|
+
local _host="${1:-}"
|
|
6601
|
+
case "$_host" in
|
|
6602
|
+
127.0.0.1|localhost|::1|"") return 0 ;;
|
|
6603
|
+
esac
|
|
6604
|
+
[ "${LOKI_DASHBOARD_ALLOW_INSECURE_BIND:-}" = "1" ] && return 0
|
|
6605
|
+
[ "${LOKI_ENTERPRISE_AUTH:-}" = "true" ] || [ "${LOKI_ENTERPRISE_AUTH:-}" = "1" ] && return 0
|
|
6606
|
+
[ -n "${LOKI_OIDC_ISSUER:-}" ] && [ -n "${LOKI_OIDC_CLIENT_ID:-}" ] && return 0
|
|
6607
|
+
return 1
|
|
6608
|
+
}
|
|
6609
|
+
|
|
6310
6610
|
cmd_dashboard() {
|
|
6311
6611
|
local subcommand="${1:-}"
|
|
6312
6612
|
|
|
@@ -6461,6 +6761,23 @@ cmd_dashboard_start() {
|
|
|
6461
6761
|
esac
|
|
6462
6762
|
done
|
|
6463
6763
|
|
|
6764
|
+
# Refuse an exposed bind with auth disabled (issue #188). Checked AFTER
|
|
6765
|
+
# argument parsing so an explicit --host is covered, not just the default.
|
|
6766
|
+
if ! _loki_dashboard_bind_is_safe "$host"; then
|
|
6767
|
+
echo -e "${RED}Refusing to start: binding $host with authentication disabled.${NC}" >&2
|
|
6768
|
+
echo "" >&2
|
|
6769
|
+
echo " Every dashboard endpoint would be reachable on all interfaces with no" >&2
|
|
6770
|
+
echo " credential. dashboard/auth.py allows all requests when neither" >&2
|
|
6771
|
+
echo " LOKI_ENTERPRISE_AUTH nor OIDC is configured." >&2
|
|
6772
|
+
echo "" >&2
|
|
6773
|
+
echo " Pick one:" >&2
|
|
6774
|
+
echo " LOKI_ENTERPRISE_AUTH=true enable token auth (loki dashboard token create)" >&2
|
|
6775
|
+
echo " LOKI_OIDC_ISSUER + _CLIENT_ID enable OIDC" >&2
|
|
6776
|
+
echo " --host 127.0.0.1 bind loopback only" >&2
|
|
6777
|
+
echo " LOKI_DASHBOARD_ALLOW_INSECURE_BIND=1 accept the exposure deliberately" >&2
|
|
6778
|
+
exit 2
|
|
6779
|
+
fi
|
|
6780
|
+
|
|
6464
6781
|
# Validate port is numeric
|
|
6465
6782
|
if [[ ! "$port" =~ ^[0-9]+$ ]]; then
|
|
6466
6783
|
echo -e "${RED}Invalid port: $port${NC}"
|
|
@@ -14795,6 +15112,51 @@ $brief_text
|
|
|
14795
15112
|
BRIEFEOF
|
|
14796
15113
|
}
|
|
14797
15114
|
|
|
15115
|
+
# Build a bounded input for `loki start <repo-directory>`. This intentionally
|
|
15116
|
+
# contains no caller-controlled path: repository identity comes from cwd, while
|
|
15117
|
+
# the task remains stable, reviewable, and safe to render into a prompt.
|
|
15118
|
+
#
|
|
15119
|
+
# The trailing Mode line is the only variable part, and it must match what the
|
|
15120
|
+
# run actually does: a fast pass runs with the council off, so a PRD telling the
|
|
15121
|
+
# agent it is under "full verification" would be a false claim in the prompt
|
|
15122
|
+
# itself. The body stays a quoted heredoc (no expansion, nothing injectable);
|
|
15123
|
+
# the Mode line is appended from a fixed string chosen by a validated depth.
|
|
15124
|
+
# Usage: synthesize_repo_prd <output_file> [full|fast]
|
|
15125
|
+
synthesize_repo_prd() {
|
|
15126
|
+
local out_file="$1"
|
|
15127
|
+
local depth="${2:-full}"
|
|
15128
|
+
mkdir -p "$(dirname "$out_file")" 2>/dev/null || true
|
|
15129
|
+
cat > "$out_file" <<'REPOEOF'
|
|
15130
|
+
# Existing Repository Improvement
|
|
15131
|
+
|
|
15132
|
+
## Outcome
|
|
15133
|
+
Inspect this repository's instructions, product signals, and current tests.
|
|
15134
|
+
Select one small, high-value user-facing improvement and implement it completely.
|
|
15135
|
+
|
|
15136
|
+
## Success Criteria
|
|
15137
|
+
- The improvement has a clear user outcome and a bounded diff.
|
|
15138
|
+
- Focused tests or checks prove the changed behavior.
|
|
15139
|
+
- Existing unrelated work is preserved.
|
|
15140
|
+
- Loki's normal outcome evidence identifies what changed and what passed.
|
|
15141
|
+
|
|
15142
|
+
## Constraints
|
|
15143
|
+
- Make one coherent improvement, not a broad rewrite.
|
|
15144
|
+
- Reuse the repository's established architecture and dependencies.
|
|
15145
|
+
- Keep the change easy to review and roll back.
|
|
15146
|
+
- Do not claim deployment, adoption, or customer value without evidence.
|
|
15147
|
+
REPOEOF
|
|
15148
|
+
if [ "$depth" = "fast" ]; then
|
|
15149
|
+
printf '%s\n' \
|
|
15150
|
+
"- This is a fast, capped pass. Keep the change small enough to finish and prove." \
|
|
15151
|
+
"" \
|
|
15152
|
+
"**Mode:** Existing repository improvement (fast pass, reduced gates)." \
|
|
15153
|
+
"The completion council and heavy review phases are OFF on this run." \
|
|
15154
|
+
"Do not describe the result as council-verified or fully verified." >> "$out_file"
|
|
15155
|
+
else
|
|
15156
|
+
printf '\n**Mode:** Existing repository improvement (full verification)\n' >> "$out_file"
|
|
15157
|
+
fi
|
|
15158
|
+
}
|
|
15159
|
+
|
|
14798
15160
|
# Quick mode - lightweight single-task execution
|
|
14799
15161
|
cmd_quick() {
|
|
14800
15162
|
# v7.6.3 B-11 fix: --help previously fell through to provider invocation
|
|
@@ -34878,6 +35240,13 @@ cmd_proof() {
|
|
|
34878
35240
|
echo " show <id> Pretty-print .loki/proofs/<id>/proof.json"
|
|
34879
35241
|
echo " verify <id> Re-check a receipt against the repo (tamper + drift);"
|
|
34880
35242
|
echo " exit 0 clean, 1 tamper/drift. Verify it yourself."
|
|
35243
|
+
echo ""
|
|
35244
|
+
echo "Options for 'verify':"
|
|
35245
|
+
echo " --human Render the verdict and every failure reason as prose"
|
|
35246
|
+
echo " --jwks <url|file> Also check the receipt's attestation against a published"
|
|
35247
|
+
echo " key set. Accepts a server URL or a local jwks.json, so a"
|
|
35248
|
+
echo " third party can check WHO produced a receipt with no API"
|
|
35249
|
+
echo " token, no gpg key import, and no network."
|
|
34881
35250
|
echo " md <id> Paste-able Markdown for a PR comment or Slack
|
|
34882
35251
|
open <id> Open .loki/proofs/<id>/index.html in a browser"
|
|
34883
35252
|
echo " share <id> Publish the proof page as a GitHub Gist (opt-in)"
|
|
@@ -35093,14 +35462,31 @@ PYEOF
|
|
|
35093
35462
|
# stdout verbatim, are unaffected. The flag is STRIPPED from the
|
|
35094
35463
|
# positional args rather than assumed at a fixed index, so it works
|
|
35095
35464
|
# before or after the id.
|
|
35096
|
-
|
|
35097
|
-
|
|
35465
|
+
# --jwks <url-or-file> additionally checks the receipt's
|
|
35466
|
+
# attestation against a published key set. This is the third-party
|
|
35467
|
+
# path: an auditor who was handed a proof.json can check WHO
|
|
35468
|
+
# produced it without running a build, without our API token, and
|
|
35469
|
+
# without importing a gpg key. A FILE is accepted as well as a URL
|
|
35470
|
+
# so an air-gapped reviewer, handed the receipt and the key set on
|
|
35471
|
+
# disk, can still verify -- requiring the network would exclude
|
|
35472
|
+
# exactly the reviewer who most needs this.
|
|
35473
|
+
local _pv_human=0 _pv_jwks="" _pv_args=()
|
|
35474
|
+
local _pv_a _pv_want_jwks=0
|
|
35098
35475
|
for _pv_a in "$@"; do
|
|
35476
|
+
if [ "$_pv_want_jwks" -eq 1 ]; then
|
|
35477
|
+
_pv_jwks="$_pv_a"; _pv_want_jwks=0; continue
|
|
35478
|
+
fi
|
|
35099
35479
|
case "$_pv_a" in
|
|
35100
35480
|
--human) _pv_human=1 ;;
|
|
35481
|
+
--jwks) _pv_want_jwks=1 ;;
|
|
35482
|
+
--jwks=*) _pv_jwks="${_pv_a#--jwks=}" ;;
|
|
35101
35483
|
*) _pv_args+=("$_pv_a") ;;
|
|
35102
35484
|
esac
|
|
35103
35485
|
done
|
|
35486
|
+
if [ "$_pv_want_jwks" -eq 1 ]; then
|
|
35487
|
+
echo -e "${RED}--jwks needs a URL or file path.${NC}" >&2
|
|
35488
|
+
exit 2
|
|
35489
|
+
fi
|
|
35104
35490
|
set -- "${_pv_args[@]+"${_pv_args[@]}"}"
|
|
35105
35491
|
|
|
35106
35492
|
local id="${1:-}"
|
|
@@ -35121,12 +35507,53 @@ PYEOF
|
|
|
35121
35507
|
echo -e "${RED}Verifier not found (autonomy/lib/proof-verify.py).${NC}"
|
|
35122
35508
|
exit 2
|
|
35123
35509
|
fi
|
|
35510
|
+
# `|| _pv_rc=$?` is load-bearing: this script runs under `set -e`
|
|
35511
|
+
# (loki:22), so a bare call to a verifier that exits 1 on drift --
|
|
35512
|
+
# its NORMAL result -- aborts the whole command before the exit
|
|
35513
|
+
# status can be read. Without the guard the attestation block below
|
|
35514
|
+
# is unreachable on exactly the receipts it exists to check.
|
|
35515
|
+
local _pv_rc=0
|
|
35124
35516
|
if [ "$_pv_human" -eq 1 ]; then
|
|
35125
|
-
python3 "$verifier" --human "$pj" "${TARGET_DIR:-.}"
|
|
35517
|
+
python3 "$verifier" --human "$pj" "${TARGET_DIR:-.}" || _pv_rc=$?
|
|
35126
35518
|
else
|
|
35127
|
-
python3 "$verifier" "$pj" "${TARGET_DIR:-.}"
|
|
35519
|
+
python3 "$verifier" "$pj" "${TARGET_DIR:-.}" || _pv_rc=$?
|
|
35520
|
+
fi
|
|
35521
|
+
|
|
35522
|
+
# The attestation check runs AFTER, and can only make the verdict
|
|
35523
|
+
# worse, never better. A valid attestation does not excuse a failed
|
|
35524
|
+
# integrity hash: the signature proves who produced the bytes, not
|
|
35525
|
+
# that the bytes are internally consistent. Folding the two together
|
|
35526
|
+
# would let a signed-but-tampered receipt exit 0.
|
|
35527
|
+
if [ -n "$_pv_jwks" ]; then
|
|
35528
|
+
local _pv_att
|
|
35529
|
+
_pv_att="$(loki_proof_attestation_check "$pj" "$_pv_jwks")"
|
|
35530
|
+
case "$_pv_att" in
|
|
35531
|
+
ok)
|
|
35532
|
+
echo "attestation: VERIFIED against ${_pv_jwks}" >&2
|
|
35533
|
+
echo " Proven: these bytes were signed by the holder of a key in that set." >&2
|
|
35534
|
+
echo " NOT proven: that the code is correct. A receipt records which checks ran." >&2
|
|
35535
|
+
;;
|
|
35536
|
+
bad)
|
|
35537
|
+
echo -e "${RED}attestation: FAILED -- the token does not cover these bytes.${NC}" >&2
|
|
35538
|
+
echo " Either the receipt was altered after signing, or the attestation" >&2
|
|
35539
|
+
echo " belongs to a different run. Do not trust this receipt." >&2
|
|
35540
|
+
_pv_rc=1
|
|
35541
|
+
;;
|
|
35542
|
+
none)
|
|
35543
|
+
echo -e "${YELLOW}attestation: ABSENT -- this receipt carries no attestation.${NC}" >&2
|
|
35544
|
+
echo " Nothing was checked against ${_pv_jwks}. The receipt may still be" >&2
|
|
35545
|
+
echo " gpg-signed; this says only that it has no JWKS attestation." >&2
|
|
35546
|
+
;;
|
|
35547
|
+
*)
|
|
35548
|
+
# Unreachable key set, unreadable file, missing python
|
|
35549
|
+
# deps. NOT a verdict: an absent measurement must never
|
|
35550
|
+
# read as either a pass or an accusation.
|
|
35551
|
+
echo -e "${YELLOW}attestation: NOT CHECKED -- could not read a key set from ${_pv_jwks}.${NC}" >&2
|
|
35552
|
+
echo " This is not a failure of the receipt. Nothing was proven either way." >&2
|
|
35553
|
+
;;
|
|
35554
|
+
esac
|
|
35128
35555
|
fi
|
|
35129
|
-
exit
|
|
35556
|
+
exit "$_pv_rc"
|
|
35130
35557
|
;;
|
|
35131
35558
|
open)
|
|
35132
35559
|
local id="${1:-}"
|