loki-mode 10.5.11 → 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.
Files changed (92) hide show
  1. package/README.md +30 -962
  2. package/SKILL.md +3 -3
  3. package/VERSION +1 -1
  4. package/autonomy/app-runner.sh +2 -1
  5. package/autonomy/completion-council.sh +40 -3
  6. package/autonomy/lib/doctor-fix.py +1 -1
  7. package/autonomy/lib/proof-generator.py +40 -84
  8. package/autonomy/lib/proof-template.html +7 -11
  9. package/autonomy/loki +118 -69
  10. package/autonomy/receipt_jwt.py +59 -14
  11. package/autonomy/run.sh +91 -124
  12. package/autonomy/trigger-server.py +2 -2
  13. package/autonomy/verify.sh +37 -2
  14. package/dashboard/__init__.py +1 -1
  15. package/dashboard/server.py +2 -2
  16. package/docs/COMPETITIVE-NEXT-10.md +3 -4
  17. package/docs/INSTALLATION.md +1 -1
  18. package/docs/README-FULL.md +977 -0
  19. package/docs/SIGNED-RECEIPTS.md +40 -82
  20. package/docs/TOOLS.md +0 -23
  21. package/docs/VERIFICATION-COST.md +2 -2
  22. package/docs/v10/ADOPTION-PLAN.md +1 -1
  23. package/docs/v10/BOARD.md +42 -22
  24. package/docs/v10/DECISIONS.md +11 -0
  25. package/docs/v10/ENGINE.md +4 -4
  26. package/docs/v10/FOUNDER-QUEUE.md +1 -0
  27. package/docs/v10/GUIDE.md +0 -4
  28. package/docs/v10/PROGRESS.md +43 -0
  29. package/loki-ts/dist/loki.js +587 -563
  30. package/mcp/__init__.py +1 -1
  31. package/package.json +2 -2
  32. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  33. package/tools/gate-status.py +3 -46
  34. package/tools/receipt-attest.py +1 -1
  35. package/web-app/dist/assets/{AdminPage-bi-XcWhD.js → AdminPage-C8zraQ0u.js} +1 -1
  36. package/web-app/dist/assets/{Avatar-CmeIKHo6.js → Avatar-DnjIA4Ap.js} +1 -1
  37. package/web-app/dist/assets/{Badge-Bc3RMd7Y.js → Badge-C487hf0b.js} +1 -1
  38. package/web-app/dist/assets/{Button-Dc7yzWmJ.js → Button-5AWAEksl.js} +1 -1
  39. package/web-app/dist/assets/{CockpitPage-C_aRTgNm.js → CockpitPage-B6_1gTFO.js} +1 -1
  40. package/web-app/dist/assets/{ComparePage-B8eqE3GD.js → ComparePage-DDbC8-yi.js} +1 -1
  41. package/web-app/dist/assets/{ErrorBoundary-BRVSyrME.js → ErrorBoundary-CWHKwjEp.js} +1 -1
  42. package/web-app/dist/assets/{EvidenceReceiptPanel-DBkXGSAF.js → EvidenceReceiptPanel-MfJq92Ty.js} +1 -1
  43. package/web-app/dist/assets/{GitHubIssuesPanel-rRZ4rP-a.js → GitHubIssuesPanel-BBdijzdg.js} +1 -1
  44. package/web-app/dist/assets/{GitHubPRsPanel-NMj2a_-3.js → GitHubPRsPanel-DjBHyymT.js} +1 -1
  45. package/web-app/dist/assets/{HomePage-YGPKiV9F.js → HomePage-B_6m7ITK.js} +2 -2
  46. package/web-app/dist/assets/{LoginPage-CVx7_jtD.js → LoginPage-OGgMvfjT.js} +1 -1
  47. package/web-app/dist/assets/{MagicPage-G9BuqW4T.js → MagicPage-m4MSfxWf.js} +1 -1
  48. package/web-app/dist/assets/{MetricsPage-CrPo66D6.js → MetricsPage-D8Ea1ALe.js} +1 -1
  49. package/web-app/dist/assets/{NotFoundPage-CbjchFXV.js → NotFoundPage-ttvXL-7K.js} +1 -1
  50. package/web-app/dist/assets/{ProjectPage-CmPoA170.js → ProjectPage-CDX8tSPV.js} +3 -3
  51. package/web-app/dist/assets/{ProjectsPage-CfVMc4nw.js → ProjectsPage-BQ73nYWf.js} +1 -1
  52. package/web-app/dist/assets/{SettingsPage-6nZ-vONY.js → SettingsPage-B6k71q2g.js} +1 -1
  53. package/web-app/dist/assets/{ShowcasePage-CxdZjDzY.js → ShowcasePage-CNbwtr7D.js} +1 -1
  54. package/web-app/dist/assets/{SystemSettingsPage-Bu0kwwMe.js → SystemSettingsPage-CD2qys43.js} +1 -1
  55. package/web-app/dist/assets/{TeamsPage-Ck6xWgOM.js → TeamsPage-DAmsRIVD.js} +1 -1
  56. package/web-app/dist/assets/{TemplatesPage-BduvcCsH.js → TemplatesPage-CcRyyUhL.js} +1 -1
  57. package/web-app/dist/assets/{TerminalOutput-BmleKz7Q.js → TerminalOutput-DRquxtQ1.js} +1 -1
  58. package/web-app/dist/assets/{activity-xUOENXj1.js → activity-DUDUi3Qd.js} +1 -1
  59. package/web-app/dist/assets/{bell-Do1DnngS.js → bell-tDpCOcHx.js} +1 -1
  60. package/web-app/dist/assets/{bot-Csus-lhk.js → bot-87IR4aTF.js} +1 -1
  61. package/web-app/dist/assets/{check-CDMuF2fW.js → check-C2Y2z-Ge.js} +1 -1
  62. package/web-app/dist/assets/{chevron-left-1pxhei7a.js → chevron-left-CsPFT88x.js} +1 -1
  63. package/web-app/dist/assets/{circle-alert-HufOSfwc.js → circle-alert-Cp2UJikd.js} +1 -1
  64. package/web-app/dist/assets/{clock-BoCMViPQ.js → clock-DZ1cwNyf.js} +1 -1
  65. package/web-app/dist/assets/{cloud-B7JfKA_r.js → cloud-q5TrIOV-.js} +1 -1
  66. package/web-app/dist/assets/{code-xml-BduQ3MqM.js → code-xml-0TNGOS3c.js} +1 -1
  67. package/web-app/dist/assets/{database-CQ9s31cU.js → database-g73vgDVx.js} +1 -1
  68. package/web-app/dist/assets/{dollar-sign-CVJu5-A0.js → dollar-sign-DAKkR_MO.js} +1 -1
  69. package/web-app/dist/assets/{file-code-corner-BPu1BX6Z.js → file-code-corner-DTAvdxjl.js} +1 -1
  70. package/web-app/dist/assets/{file-plus-CGa7y-QG.js → file-plus-D5SB-1Uk.js} +1 -1
  71. package/web-app/dist/assets/{globe-ClS0c8Cw.js → globe-j5YaNmx9.js} +1 -1
  72. package/web-app/dist/assets/{hammer-CeljzUTd.js → hammer-C9yoXOC6.js} +1 -1
  73. package/web-app/dist/assets/{index-Dn35XhiL.js → index--EayJsJ-.js} +3 -3
  74. package/web-app/dist/assets/{layers-CzGZ-NNL.js → layers-B4mxkWdH.js} +1 -1
  75. package/web-app/dist/assets/{loader-circle-Dy1j6q2e.js → loader-circle-CP-qD1W2.js} +1 -1
  76. package/web-app/dist/assets/{lock-pmDyRsWL.js → lock-DwHNUSTU.js} +1 -1
  77. package/web-app/dist/assets/{package-C1dXDph_.js → package-BxLWioQ8.js} +1 -1
  78. package/web-app/dist/assets/{plus-CKPSHefh.js → plus-B-43Byzl.js} +1 -1
  79. package/web-app/dist/assets/{refresh-cw-CRmwRgDw.js → refresh-cw-5s47zAFV.js} +1 -1
  80. package/web-app/dist/assets/{rotate-ccw-Br04gZR3.js → rotate-ccw-BewZ8_Rx.js} +1 -1
  81. package/web-app/dist/assets/{scroll-text-BHjpthNc.js → scroll-text-DPNpHL-Z.js} +1 -1
  82. package/web-app/dist/assets/{server-Db3IfTpl.js → server-hL8_-vRq.js} +1 -1
  83. package/web-app/dist/assets/{shield-alert-jJXWKLDZ.js → shield-alert-DAGc2aLO.js} +1 -1
  84. package/web-app/dist/assets/{thumbs-up-BksBTkqT.js → thumbs-up-DRGzcWEH.js} +1 -1
  85. package/web-app/dist/assets/{trash-2-CzXuGa-4.js → trash-2-Bo_YPIqS.js} +1 -1
  86. package/web-app/dist/assets/{trending-down-c_JXJq0s.js → trending-down-BQ1cK9F3.js} +1 -1
  87. package/web-app/dist/assets/{trending-up-DtvURYKn.js → trending-up-BpnkTO1T.js} +1 -1
  88. package/web-app/dist/assets/{upload-BSsOPrmR.js → upload-DDdQNxmW.js} +1 -1
  89. package/web-app/dist/assets/{usePolling-CFWdgkmg.js → usePolling-z_FXsGz7.js} +1 -1
  90. package/web-app/dist/assets/{user-CL-HjV4R.js → user-obPH42HX.js} +1 -1
  91. package/web-app/dist/index.html +1 -1
  92. package/tools/signing-status.py +0 -292
@@ -1,292 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Can this machine produce SIGNED receipts? Prove it, do not assume it.
3
-
4
- WHY THIS EXISTS. An unsigned Evidence Receipt proves INTEGRITY but not ORIGIN.
5
- The hash says the bytes were not tampered with; it says nothing about who
6
- produced them, and anyone can generate a receipt with a valid hash. Origin is
7
- the part a third party is actually buying. GPG signing closes that gap and has
8
- shipped since the proof generator learned LOKI_PROOF_GPG_KEY -- default OFF,
9
- undiscoverable, and silent when broken.
10
-
11
- Silent is the dangerous word. autonomy/lib/proof-generator.py:_gpg_detached_sign
12
- swallows EVERY failure and returns None: missing key, expired key, revoked key,
13
- a passphrase this environment cannot supply. Signing is best-effort by design so
14
- a gpg problem never blocks proof emission. The cost of that design is that a
15
- user who set LOKI_PROOF_GPG_KEY, believes receipts are signed, and has a broken
16
- key gets UNSIGNED receipts forever with no error anywhere. This tool is the only
17
- place that failure becomes visible.
18
-
19
- So the bar here is higher than "gpg is installed and a key id is set". That
20
- sentence is compatible with every one of the failures above. The only honest
21
- evidence that this machine can sign is a real detached-sign of a real payload
22
- followed by a real verify of the resulting signature -- with the SAME argv the
23
- receipt pipeline uses, or the round trip predicts nothing about the pipeline.
24
-
25
- FOUR STATES, NEVER A BOOLEAN. Collapsing these sends a user to debug the wrong
26
- thing, which for a security control is worse than saying nothing:
27
-
28
- ok round trip signed AND verified. Receipts carry origin.
29
- broken configured, and signing FAILED. Receipts silently unsigned.
30
- not_configured gpg works, LOKI_PROOF_GPG_KEY unset. Nothing is wrong.
31
- gpg_absent no gpg on PATH. Signing is impossible until installed.
32
-
33
- KEY MATERIAL NEVER LEAVES. A key ID is an identifier and is safe to print.
34
- Secret key bytes and passphrases are not, and gpg writes diagnostics to stderr
35
- that can quote them. Raw gpg stderr is therefore NEVER printed: it is
36
- classified into a known reason and only the classification is emitted. No
37
- --export path is invoked anywhere in this file. Requirement and implementation
38
- are the same line: print the reason, not the transcript.
39
-
40
- Read-only against the keyring: no key is created, deleted, imported, or
41
- modified. The round trip signs a scratch file in a temp dir that is removed
42
- afterwards, and uses the caller's real GNUPGHOME because the question being
43
- answered is whether the REAL keyring can sign.
44
- """
45
-
46
- import argparse
47
- import json
48
- import os
49
- import shutil
50
- import subprocess
51
- import sys
52
- import tempfile
53
-
54
- # The payload is arbitrary: the round trip proves the KEY works, not that any
55
- # particular bytes are special. Fixed and tiny so a signature is fast.
56
- _SCRATCH_PAYLOAD = b"loki-signing-status round-trip probe\n"
57
-
58
- _TIMEOUT = 30
59
-
60
- # Ordered: the first match wins, so the more specific pattern precedes the
61
- # general one. Left side is matched against LOWERCASED gpg stderr; right side is
62
- # what the user is told. Never the raw stderr -- see the module docstring.
63
- _REASONS = (
64
- ("no secret key", "no secret key for that key id in this keyring"),
65
- ("secret key not available", "no secret key for that key id in this keyring"),
66
- ("no default secret key", "no secret key for that key id in this keyring"),
67
- ("unusable secret key", "the secret key is unusable (expired or revoked)"),
68
- ("key has expired", "the key has expired"),
69
- ("expired", "the key has expired"),
70
- ("has been revoked", "the key has been revoked"),
71
- ("revoked", "the key has been revoked"),
72
- ("bad passphrase", "the passphrase was rejected"),
73
- ("passphrase", "the key needs a passphrase this environment cannot supply"),
74
- ("pinentry", "the key needs a passphrase this environment cannot supply"),
75
- ("inappropriate ioctl", "the key needs a passphrase this environment cannot supply"),
76
- ("no such file", "the key id does not resolve to a key in this keyring"),
77
- ("not found", "the key id does not resolve to a key in this keyring"),
78
- )
79
-
80
-
81
- class _Parser(argparse.ArgumentParser):
82
- """Usage errors exit 64, not argparse's default 2.
83
-
84
- In this repo's convention 2 means "could NOT be checked" -- a real
85
- answer about the subject. A mistyped flag is not that: it is an error
86
- about the INVOCATION, and nothing about the subject was examined. The
87
- two call for opposite responses, since retrying cannot fix a typo.
88
-
89
- argparse exits 2 for every usage error unless this is overridden, so
90
- every tool needs it. tests/test_tool_exit_contract.py asserts it.
91
- """
92
-
93
- def error(self, message):
94
- self.print_usage(sys.stderr)
95
- sys.stderr.write("%s: error: %s\n" % (self.prog, message))
96
- raise SystemExit(64)
97
-
98
-
99
- def _classify(stderr, fallback):
100
- """Map gpg stderr onto a known reason. NEVER returns the stderr itself.
101
-
102
- Anything unrecognized degrades to a generic fallback rather than leaking the
103
- transcript, because the unrecognized case is exactly the one whose contents
104
- cannot be predicted -- and therefore the one most likely to quote key
105
- material or a passphrase prompt.
106
- """
107
- low = (stderr or "").lower()
108
- for needle, reason in _REASONS:
109
- if needle in low:
110
- return reason
111
- return fallback
112
-
113
-
114
- def _round_trip(key_id):
115
- """Sign a scratch payload with key_id and verify the signature.
116
-
117
- Returns (True, None) when the signature verified, else (False, reason).
118
-
119
- The argv MUST mirror autonomy/lib/proof-generator.py:_gpg_detached_sign and
120
- autonomy/lib/proof-verify.py:_verify_gpg. A round trip that signs some other
121
- way can succeed while the receipt pipeline still emits unsigned, which would
122
- make this tool confidently wrong in the one direction that matters.
123
- """
124
- tmp = tempfile.mkdtemp(prefix="loki-signing-status-")
125
- try:
126
- data_path = os.path.join(tmp, "payload.bin")
127
- with open(data_path, "wb") as fh:
128
- fh.write(_SCRATCH_PAYLOAD)
129
-
130
- try:
131
- signed = subprocess.run(
132
- ["gpg", "--batch", "--yes", "--armor", "--detach-sign",
133
- "--local-user", key_id, "--output", "-"],
134
- input=_SCRATCH_PAYLOAD, capture_output=True, timeout=_TIMEOUT,
135
- )
136
- except subprocess.TimeoutExpired:
137
- # --batch should make gpg fail rather than prompt, but a wedged
138
- # agent or a pinentry that ignores batch mode hangs instead. Never
139
- # let this tool hang: a timeout IS a signing failure.
140
- return False, "gpg timed out (a passphrase prompt is the usual cause)"
141
- except OSError as exc:
142
- return False, "gpg could not be executed (%s)" % type(exc).__name__
143
-
144
- if signed.returncode != 0 or not signed.stdout:
145
- stderr = signed.stderr.decode("utf-8", errors="replace")
146
- return False, _classify(stderr, "gpg refused to sign with that key")
147
-
148
- sig_path = os.path.join(tmp, "payload.sig")
149
- with open(sig_path, "wb") as fh:
150
- fh.write(signed.stdout)
151
-
152
- try:
153
- checked = subprocess.run(
154
- ["gpg", "--verify", sig_path, data_path],
155
- capture_output=True, timeout=_TIMEOUT,
156
- )
157
- except subprocess.TimeoutExpired:
158
- return False, "gpg timed out verifying its own signature"
159
- except OSError as exc:
160
- return False, "gpg could not be executed (%s)" % type(exc).__name__
161
-
162
- if checked.returncode != 0:
163
- # gpg produced bytes that gpg itself will not accept. A naive
164
- # "signing worked, we got output" check calls this state OK.
165
- stderr = checked.stderr.decode("utf-8", errors="replace")
166
- return False, _classify(
167
- stderr, "gpg produced a signature it could not verify")
168
-
169
- return True, None
170
- finally:
171
- shutil.rmtree(tmp, ignore_errors=True)
172
-
173
-
174
- def evaluate(env=None):
175
- """Resolve the signing state. Returns a JSON-safe dict.
176
-
177
- Pure with respect to the keyring: reads it, changes nothing.
178
- """
179
- env = os.environ if env is None else env
180
- gpg_path = shutil.which("gpg")
181
- key_id = (env.get("LOKI_PROOF_GPG_KEY") or "").strip()
182
-
183
- result = {
184
- "gpg_installed": gpg_path is not None,
185
- "gpg_path": gpg_path,
186
- "key_id_set": bool(key_id),
187
- # A key ID is an identifier, not key material: safe to echo back so the
188
- # user can see WHICH key was tried.
189
- "key_id": key_id or None,
190
- "round_trip_verified": False,
191
- }
192
-
193
- if gpg_path is None:
194
- result["status"] = "gpg_absent"
195
- result["reason"] = "gpg is not on PATH"
196
- result["next_command"] = "brew install gnupg # or: apt-get install gnupg"
197
- return result
198
-
199
- if not key_id:
200
- result["status"] = "not_configured"
201
- result["reason"] = "LOKI_PROOF_GPG_KEY is not set"
202
- result["next_command"] = (
203
- "gpg --list-secret-keys --keyid-format=long "
204
- "# then: export LOKI_PROOF_GPG_KEY=<key-id>")
205
- return result
206
-
207
- verified, reason = _round_trip(key_id)
208
- result["round_trip_verified"] = verified
209
-
210
- # The single load-bearing line in this file. "ok" is reachable ONLY through
211
- # a completed sign-and-verify round trip; every other path is a failure with
212
- # a reason. Deriving status from gpg_installed and key_id_set instead would
213
- # report ok for an expired key, which is the exact lie this tool exists to
214
- # prevent.
215
- result["status"] = "ok" if verified else "broken"
216
-
217
- if verified:
218
- result["reason"] = None
219
- result["next_command"] = None
220
- else:
221
- result["reason"] = reason
222
- result["next_command"] = (
223
- "gpg --list-secret-keys --keyid-format=long "
224
- "# confirm %s is present and usable" % key_id)
225
- return result
226
-
227
-
228
- # Exit codes are distinct per state so a caller can branch without parsing.
229
- # 0 means, and only means, a round trip completed.
230
- _EXIT = {"ok": 0, "broken": 1, "not_configured": 2, "gpg_absent": 3}
231
-
232
- _HEADLINE = {
233
- "ok": "SIGNED receipts from this machine carry a verifiable origin",
234
- "broken": "BROKEN receipts are being emitted UNSIGNED, silently",
235
- "not_configured": "UNSIGNED receipts prove integrity but NOT origin",
236
- "gpg_absent": "UNSIGNED signing is unavailable on this machine",
237
- }
238
-
239
-
240
- def render(result):
241
- """Human-readable report. Contains no gpg transcript, by construction."""
242
- status = result["status"]
243
- lines = ["Receipt signing: %s" % _HEADLINE[status], ""]
244
-
245
- lines.append(" gpg installed: %s" % (
246
- result["gpg_path"] if result["gpg_installed"] else "NO"))
247
- lines.append(" LOKI_PROOF_GPG_KEY: %s" % (
248
- result["key_id"] if result["key_id_set"] else "not set"))
249
- lines.append(" sign+verify proof: %s" % (
250
- "PASS (round trip completed)" if result["round_trip_verified"]
251
- else "not proven"))
252
-
253
- if result.get("reason"):
254
- lines += ["", " Why: %s" % result["reason"]]
255
-
256
- if status == "broken":
257
- lines += [
258
- "",
259
- " A key is configured but cannot sign. The proof generator treats",
260
- " signing as best-effort and swallows this failure, so receipts",
261
- " keep emitting UNSIGNED with no error. Nothing else reports it.",
262
- ]
263
- elif status == "not_configured":
264
- lines += [
265
- "",
266
- " Nothing is broken. Signing is opt-in and off. Turn it on to prove",
267
- " a receipt came from you and not merely that its bytes are intact.",
268
- ]
269
-
270
- if result.get("next_command"):
271
- lines += ["", " Next: %s" % result["next_command"]]
272
-
273
- return "\n".join(lines)
274
-
275
-
276
- def main(argv=None):
277
- parser = _Parser(
278
- description="Report whether this machine can produce SIGNED receipts.")
279
- parser.add_argument("--json", action="store_true",
280
- help="emit the result as JSON")
281
- args = parser.parse_args(argv)
282
-
283
- result = evaluate()
284
- if args.json:
285
- print(json.dumps(result, indent=2, sort_keys=True))
286
- else:
287
- print(render(result))
288
- return _EXIT[result["status"]]
289
-
290
-
291
- if __name__ == "__main__":
292
- sys.exit(main())