loki-mode 9.51.1 → 9.53.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/README.md +3 -2
- package/SKILL.md +3 -3
- package/VERSION +1 -1
- package/autonomy/completion-council.sh +93 -53
- package/autonomy/lib/workspace_diff.py +18 -1
- package/autonomy/loki +212 -35
- package/autonomy/receipt_jwt.py +7 -0
- package/autonomy/run.sh +79 -30
- package/dashboard/__init__.py +1 -1
- package/docs/INSTALLATION.md +2 -2
- package/docs/LOKI-10-BUILD-PROMPT.md +350 -0
- package/docs/SIGNED-RECEIPTS.md +9 -6
- package/docs/V10-RESEARCH.md +81 -0
- package/docs/V10-VISION.md +161 -0
- package/docs/exit-codes.md +41 -0
- package/docs/v10/BACKLOG.md +95 -0
- package/docs/v10/DECISIONS.md +52 -0
- package/docs/v10/FOUNDER-QUEUE.md +13 -0
- package/docs/v10/METRICS.md +47 -0
- package/docs/v10/PROGRESS.md +49 -0
- package/loki-ts/dist/loki.js +411 -404
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/tools/verify-chain.py +4 -1
package/README.md
CHANGED
|
@@ -282,7 +282,8 @@ loki proof show <id> # the facts, the assessments, and the headline
|
|
|
282
282
|
loki proof verify <id> # re-hash the receipt and re-derive the diff
|
|
283
283
|
```
|
|
284
284
|
|
|
285
|
-
`loki proof verify` exits 0 clean, 1 on tamper or drift
|
|
285
|
+
`loki proof verify` exits 0 clean, 1 on tamper or drift, 2 when it could not check
|
|
286
|
+
(full table: [docs/exit-codes.md](docs/exit-codes.md)). Receipts are attached
|
|
286
287
|
to pull requests automatically (`LOKI_PROVEN_PR=0` to opt out), so a reviewer
|
|
287
288
|
sees the evidence next to the code.
|
|
288
289
|
|
|
@@ -368,7 +369,7 @@ Receipts are written to `.loki/proofs/<run_id>/` automatically at run completion
|
|
|
368
369
|
```bash
|
|
369
370
|
loki proof list # every receipt: run id, time, council verdict, cost, files
|
|
370
371
|
loki proof show <id> # the full proof.json (facts, assessments, honesty)
|
|
371
|
-
loki proof verify <id> # re-check the receipt against the repo (exit 0 clean, 1 tamper/drift)
|
|
372
|
+
loki proof verify <id> # re-check the receipt against the repo (exit 0 clean, 1 tamper/drift, 2 could not check)
|
|
372
373
|
```
|
|
373
374
|
|
|
374
375
|
`loki proof verify` does two independent checks and prints the result as JSON:
|
package/SKILL.md
CHANGED
|
@@ -3,13 +3,13 @@ name: loki-mode
|
|
|
3
3
|
description: Autonomous spec-driven build system with a built-in trust layer. It does not call work done until it is verified (RARV-C closure loop, 8 quality gates, completion council, verified-completion evidence gate). Triggers on "Loki Mode". Takes a spec (PRD, GitHub issue, OpenAPI doc, etc.) to deployed product with minimal human intervention. Provider-agnostic. Requires --dangerously-skip-permissions flag.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Loki Mode v9.
|
|
6
|
+
# Loki Mode v9.53.0
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
10
10
|
**Spec in, verified product out.** Spec-driven: a "spec" is whatever describes the work -- a Markdown PRD, a GitHub issue, an OpenAPI doc, a Jira ticket (a PRD is one form of spec). The differentiator is the trust layer: Loki does not call work done until it is verified. The RARV-C closure loop, 8 quality gates, the completion council, and the verified-completion evidence gate must all clear before completion is accepted. The evidence gate blocks on empty-diff, red tests, an unhealthy serveable app (runtime-boot axis, `LOKI_EVIDENCE_BOOT_GATE=0` to opt out), and a leaked credential in the changed files (secret-leak axis, `LOKI_EVIDENCE_SECRET_GATE=0` to opt out) -- v8.0.0.
|
|
11
11
|
|
|
12
|
-
**Evidence Receipt (verify it yourself).** Every run writes a receipt to `.loki/proofs/<run_id>/` (opt out with `LOKI_PROOF=0`) that separates deterministic FACTS (git diff with base/head SHAs and a `diff_sha256`, the test command + exit code, the build command + exit code, each gate verdict) from AI ASSESSMENTS (the council verdict, labeled judgment not proof). The headline is computed only from the facts: VERIFIED (tests ran a real command and exited 0, diff non-empty, nothing skipped), VERIFIED WITH GAPS (each gap listed by name), or NOT VERIFIED (a check ran and failed). Inspect and re-check with `loki proof list|show <id>|verify <id>` (aliased `loki receipt`); `loki proof verify` re-hashes the receipt (tamper) and re-derives the diff from the recorded base SHA against the live repo (drift), exiting 0 clean / 1 tamper-or-drift. This is honesty-of-done, not a claim that the code is bug-free.
|
|
12
|
+
**Evidence Receipt (verify it yourself).** Every run writes a receipt to `.loki/proofs/<run_id>/` (opt out with `LOKI_PROOF=0`) that separates deterministic FACTS (git diff with base/head SHAs and a `diff_sha256`, the test command + exit code, the build command + exit code, each gate verdict) from AI ASSESSMENTS (the council verdict, labeled judgment not proof). The headline is computed only from the facts: VERIFIED (tests ran a real command and exited 0, diff non-empty, nothing skipped), VERIFIED WITH GAPS (each gap listed by name), or NOT VERIFIED (a check ran and failed). Inspect and re-check with `loki proof list|show <id>|verify <id>` (aliased `loki receipt`); `loki proof verify` re-hashes the receipt (tamper) and re-derives the diff from the recorded base SHA against the live repo (drift), exiting 0 clean / 1 tamper-or-drift / 2 could not check (64 usage, 66 unknown id; see `docs/exit-codes.md`). This is honesty-of-done, not a claim that the code is bug-free.
|
|
13
13
|
|
|
14
14
|
**Provider-agnostic (stable since v5.0.0):** runs on Claude/Codex/Cline/Aider with abstract model tiers and degraded mode for non-Claude providers; no vendor lock-in. Gemini deprecated v7.5.18. See `skills/providers.md`. **Current track (v8.0.0):** the Anthropic Agent SDK route (see below), spec-mode expansion for OpenAPI/GraphQL/Postman contracts, the runtime-boot and secret-leak evidence axes, and `loki steer` / `loki why` for mid-run control. Earlier tracks: LSP grounding as a first-class agent tool (v7.7.x) and Phase 1 RARV-C closure (real provider judges, gate-failure flock, synthetic PRD e2e, status `--json`).
|
|
15
15
|
|
|
@@ -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
|
-
**v9.
|
|
473
|
+
**v9.53.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
9.
|
|
1
|
+
9.53.0
|
|
@@ -19,6 +19,12 @@
|
|
|
19
19
|
# - Multi-agent debate: Voting beats unanimous (+13.2%), KS adaptive stopping
|
|
20
20
|
# - NVIDIA ToolOrchestra: Efficiency metrics for agent tool use
|
|
21
21
|
#
|
|
22
|
+
# Inline Python (D7): the council runs with the cwd inside the agent's repo, so
|
|
23
|
+
# every "python3 -c" / "python3 -" here runs -E (a PYTHONPATH empty component
|
|
24
|
+
# would load a committed sitecustomize.py) and drops '' and '.' from sys.path
|
|
25
|
+
# before any other import (a committed json.py would print the verdict).
|
|
26
|
+
# Pinned by tests/moat/p2-honest-verdict.sh P2.council-readers-not-shadowed.
|
|
27
|
+
#
|
|
22
28
|
# Environment Variables:
|
|
23
29
|
# LOKI_COUNCIL_ENABLED - Enable completion council (default: true)
|
|
24
30
|
# LOKI_COUNCIL_SIZE - Number of council members (default: 3)
|
|
@@ -371,7 +377,7 @@ council_track_iteration() {
|
|
|
371
377
|
_COUNCIL_ITERATION="${ITERATION_COUNT:-0}" \
|
|
372
378
|
_COUNCIL_FILES_CHANGED="$files_changed" \
|
|
373
379
|
_COUNCIL_DIFF_HASH="$combined_hash" \
|
|
374
|
-
python3 -c "
|
|
380
|
+
python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
375
381
|
import json, os
|
|
376
382
|
state_file = os.environ['_COUNCIL_STATE_FILE']
|
|
377
383
|
try:
|
|
@@ -453,7 +459,7 @@ _uncertainty_state_path() {
|
|
|
453
459
|
# Read uncertainty.json (or emit a default object if missing/corrupt) to stdout.
|
|
454
460
|
_uncertainty_read_state() {
|
|
455
461
|
local file="$1"
|
|
456
|
-
_UNC_FILE="$file" python3 -c "
|
|
462
|
+
_UNC_FILE="$file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
457
463
|
import json, os
|
|
458
464
|
f = os.environ['_UNC_FILE']
|
|
459
465
|
default = {
|
|
@@ -487,7 +493,7 @@ _uncertainty_write_state() {
|
|
|
487
493
|
dir="$(dirname "$file")"
|
|
488
494
|
mkdir -p "$dir" 2>/dev/null || true
|
|
489
495
|
tmp="${file}.tmp.$$"
|
|
490
|
-
if _UNC_PAYLOAD="$payload" _UNC_TMP="$tmp" python3 -c "
|
|
496
|
+
if _UNC_PAYLOAD="$payload" _UNC_TMP="$tmp" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
491
497
|
import json, os
|
|
492
498
|
payload = os.environ['_UNC_PAYLOAD']
|
|
493
499
|
tmp = os.environ['_UNC_TMP']
|
|
@@ -539,7 +545,7 @@ uncertainty_should_escalate() {
|
|
|
539
545
|
_UNC_SPLIT_ROUNDS="$split_rounds" \
|
|
540
546
|
_UNC_NOCHANGE_MIN="$nochange_min" \
|
|
541
547
|
_UNC_RING_SIZE="$ring_size" \
|
|
542
|
-
python3 -c "
|
|
548
|
+
python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
543
549
|
import json, os
|
|
544
550
|
|
|
545
551
|
prior = json.loads(os.environ['_UNC_PRIOR'])
|
|
@@ -826,7 +832,7 @@ council_vote() {
|
|
|
826
832
|
local budget_exceeded=false
|
|
827
833
|
if [ "$total_issue_count" -gt 0 ] && [ "$COUNCIL_ERROR_BUDGET" != "0.0" ] && [ "$COUNCIL_ERROR_BUDGET" != "0" ]; then
|
|
828
834
|
# Check if non-blocking issue ratio exceeds the error budget
|
|
829
|
-
budget_exceeded=$(_NB="$non_blocking_count" _TOTAL="$total_issue_count" _BUDGET="$COUNCIL_ERROR_BUDGET" python3 -c "
|
|
835
|
+
budget_exceeded=$(_NB="$non_blocking_count" _TOTAL="$total_issue_count" _BUDGET="$COUNCIL_ERROR_BUDGET" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
830
836
|
import os
|
|
831
837
|
nb = int(os.environ['_NB'])
|
|
832
838
|
total = int(os.environ['_TOTAL'])
|
|
@@ -918,7 +924,7 @@ print('true' if ratio > budget else 'false')
|
|
|
918
924
|
_COUNCIL_REJECT="$reject_count" \
|
|
919
925
|
_COUNCIL_ITERATION="${ITERATION_COUNT:-0}" \
|
|
920
926
|
_COUNCIL_THRESHOLD="$effective_threshold" \
|
|
921
|
-
python3 -c "
|
|
927
|
+
python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
922
928
|
import json, os
|
|
923
929
|
from datetime import datetime, timezone
|
|
924
930
|
state_file = os.environ['_COUNCIL_STATE_FILE']
|
|
@@ -1036,7 +1042,7 @@ council_write_transcript() {
|
|
|
1036
1042
|
_MEMBERS_DIR="$COUNCIL_STATE_DIR/votes/iteration-${iteration}" \
|
|
1037
1043
|
_THRESHOLD="$effective_threshold" \
|
|
1038
1044
|
_OUT="$transcript_file" \
|
|
1039
|
-
python3 -c "
|
|
1045
|
+
python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
1040
1046
|
import json, os, pathlib, re
|
|
1041
1047
|
|
|
1042
1048
|
iteration_id = os.environ['_IID']
|
|
@@ -1214,7 +1220,7 @@ EVIDENCE_SECTION
|
|
|
1214
1220
|
local queue_file=".loki/queue/${queue}.json"
|
|
1215
1221
|
if [ -f "$queue_file" ]; then
|
|
1216
1222
|
local count
|
|
1217
|
-
count=$(_QUEUE_FILE="$queue_file" python3 -c "import json, os; print(len(json.load(open(os.environ['_QUEUE_FILE']))))" 2>/dev/null || echo "?")
|
|
1223
|
+
count=$(_QUEUE_FILE="$queue_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json, os; print(len(json.load(open(os.environ['_QUEUE_FILE']))))" 2>/dev/null || echo "?")
|
|
1218
1224
|
echo "- ${queue}: $count tasks" >> "$evidence_file"
|
|
1219
1225
|
fi
|
|
1220
1226
|
done
|
|
@@ -1287,7 +1293,7 @@ EVIDENCE_SECTION
|
|
|
1287
1293
|
elif [ -f ".loki/verification/playwright-results.json" ]; then
|
|
1288
1294
|
echo "" >> "$evidence_file"
|
|
1289
1295
|
echo "## Playwright Smoke Test Results" >> "$evidence_file"
|
|
1290
|
-
_PW_RESULTS=".loki/verification/playwright-results.json" python3 -c "
|
|
1296
|
+
_PW_RESULTS=".loki/verification/playwright-results.json" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
1291
1297
|
import json, os
|
|
1292
1298
|
try:
|
|
1293
1299
|
d = json.load(open(os.environ['_PW_RESULTS']))
|
|
@@ -1375,7 +1381,7 @@ council_checklist_gate() {
|
|
|
1375
1381
|
# council_heldout_gate at the ship gate, and surfacing them in this gate's
|
|
1376
1382
|
# block report would leak their identity back into the build loop.
|
|
1377
1383
|
local gate_result
|
|
1378
|
-
gate_result=$(_RESULTS_FILE="$results_file" _WAIVERS_FILE="$waivers_file" _HELDOUT_FILE="$heldout_file" python3 -c "
|
|
1384
|
+
gate_result=$(_RESULTS_FILE="$results_file" _WAIVERS_FILE="$waivers_file" _HELDOUT_FILE="$heldout_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
1379
1385
|
import json, sys, os
|
|
1380
1386
|
|
|
1381
1387
|
results_file = os.environ['_RESULTS_FILE']
|
|
@@ -1438,13 +1444,13 @@ else:
|
|
|
1438
1444
|
local timestamp
|
|
1439
1445
|
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
1440
1446
|
local failures_json
|
|
1441
|
-
failures_json=$(_FAILURES="$failures" python3 -c "
|
|
1447
|
+
failures_json=$(_FAILURES="$failures" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
1442
1448
|
import json, os
|
|
1443
1449
|
items = os.environ['_FAILURES'].split('|')
|
|
1444
1450
|
print(json.dumps(items))
|
|
1445
1451
|
" 2>/dev/null || echo '[]')
|
|
1446
1452
|
local critical_count
|
|
1447
|
-
critical_count=$(_FAILURES="$failures" python3 -c "
|
|
1453
|
+
critical_count=$(_FAILURES="$failures" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
1448
1454
|
import os
|
|
1449
1455
|
print(len(os.environ['_FAILURES'].split('|')))
|
|
1450
1456
|
" 2>/dev/null || echo '0')
|
|
@@ -1514,7 +1520,7 @@ council_heldout_gate() {
|
|
|
1514
1520
|
# contain ':' or '|'); they are read separately from the held-out JSON block
|
|
1515
1521
|
# below in the BLOCK branch.
|
|
1516
1522
|
local gate_result
|
|
1517
|
-
gate_result=$(_RESULTS_FILE="$results_file" _HELDOUT_FILE="$heldout_file" _WAIVERS_FILE="$waivers_file" python3 -c "
|
|
1523
|
+
gate_result=$(_RESULTS_FILE="$results_file" _HELDOUT_FILE="$heldout_file" _WAIVERS_FILE="$waivers_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
1518
1524
|
import json, sys, os
|
|
1519
1525
|
|
|
1520
1526
|
results_file = os.environ['_RESULTS_FILE']
|
|
@@ -1634,7 +1640,7 @@ print('%s %d %d' % (verdict, passed, failed))
|
|
|
1634
1640
|
if [ "$verdict" = "BLOCK" ]; then
|
|
1635
1641
|
# Read failing held-out titles directly from the data (colon/pipe-safe).
|
|
1636
1642
|
local titles_json titles_display
|
|
1637
|
-
titles_json=$(_RESULTS_FILE="$results_file" _HELDOUT_FILE="$heldout_file" _WAIVERS_FILE="$waivers_file" python3 -c "
|
|
1643
|
+
titles_json=$(_RESULTS_FILE="$results_file" _HELDOUT_FILE="$heldout_file" _WAIVERS_FILE="$waivers_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
1638
1644
|
import json, os
|
|
1639
1645
|
results = json.load(open(os.environ['_RESULTS_FILE']))
|
|
1640
1646
|
heldout_ids = set(json.load(open(os.environ['_HELDOUT_FILE'])).get('held_out', []))
|
|
@@ -1653,7 +1659,7 @@ for cat in results.get('categories', []):
|
|
|
1653
1659
|
titles.append(item.get('title', iid))
|
|
1654
1660
|
print(json.dumps(titles[:5]))
|
|
1655
1661
|
" 2>/dev/null || echo '[]')
|
|
1656
|
-
titles_display=$(_T="$titles_json" python3 -c "
|
|
1662
|
+
titles_display=$(_T="$titles_json" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
1657
1663
|
import json, os
|
|
1658
1664
|
try:
|
|
1659
1665
|
print(', '.join(json.loads(os.environ['_T'])))
|
|
@@ -1927,7 +1933,8 @@ council_evidence_gate() {
|
|
|
1927
1933
|
local test_inconclusive_reason=""
|
|
1928
1934
|
if [ -f "$tr_file" ]; then
|
|
1929
1935
|
local test_status
|
|
1930
|
-
test_status=$(_TR_FILE="$tr_file" python3 <<'PYEOF' 2>/dev/null || echo "INCONCLUSIVE:none:true"
|
|
1936
|
+
test_status=$(_TR_FILE="$tr_file" python3 -E -s - <<'PYEOF' 2>/dev/null || echo "INCONCLUSIVE:none:true"
|
|
1937
|
+
import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
|
|
1931
1938
|
import json, os, sys
|
|
1932
1939
|
tr_file = os.environ['_TR_FILE']
|
|
1933
1940
|
try:
|
|
@@ -1937,7 +1944,9 @@ except (json.JSONDecodeError, IOError, KeyError, ValueError):
|
|
|
1937
1944
|
print('INCONCLUSIVE:none:true')
|
|
1938
1945
|
sys.exit(0)
|
|
1939
1946
|
runner = d.get('runner', 'none')
|
|
1940
|
-
|
|
1947
|
+
# No default of True: a results file with no "pass" key recorded no outcome, and
|
|
1948
|
+
# an unrecorded outcome is not a pass. None falls to INCONCLUSIVE below.
|
|
1949
|
+
passed = d.get('pass')
|
|
1941
1950
|
status = d.get('status', '')
|
|
1942
1951
|
#82 (zero-test-file hardening): a runner that ran but executed ZERO real tests
|
|
1943
1952
|
# (node --test on a *.test.js with no test() calls; jest --passWithNoTests with
|
|
@@ -1952,7 +1961,10 @@ if runner == 'none':
|
|
|
1952
1961
|
elif passed is False:
|
|
1953
1962
|
print('FAIL:%s:false' % runner)
|
|
1954
1963
|
elif status == 'no_tests_run' or passed is not True:
|
|
1955
|
-
|
|
1964
|
+
# Both are INCONCLUSIVE; NO_PASS names the second: no boolean pass was
|
|
1965
|
+
# recorded at all (missing, null, or a non-boolean value), which is not a
|
|
1966
|
+
# zero-test run.
|
|
1967
|
+
print('%s:%s:true' % ('INCONCLUSIVE' if status == 'no_tests_run' else 'NO_PASS', runner))
|
|
1956
1968
|
else:
|
|
1957
1969
|
print('PASS:%s:true' % runner)
|
|
1958
1970
|
PYEOF
|
|
@@ -1983,6 +1995,13 @@ PYEOF
|
|
|
1983
1995
|
test_inconclusive="true"
|
|
1984
1996
|
test_inconclusive_reason="no_tests_executed"
|
|
1985
1997
|
fi
|
|
1998
|
+
# A real runner label but no boolean pass recorded: the same
|
|
1999
|
+
# pass-through INCONCLUSIVE, named for what it is. Not gated by the
|
|
2000
|
+
# opt-out either: an unrecorded outcome is never affirmative.
|
|
2001
|
+
if [ "$_verdict" = "NO_PASS" ]; then
|
|
2002
|
+
test_inconclusive="true"
|
|
2003
|
+
test_inconclusive_reason="no_pass_recorded"
|
|
2004
|
+
fi
|
|
1986
2005
|
else
|
|
1987
2006
|
# Missing test-results.json: no suite was recorded at all. Like the
|
|
1988
2007
|
# runner=="none" case this is not affirmative evidence, so classify it
|
|
@@ -2173,7 +2192,8 @@ DETAILS_EOF
|
|
|
2173
2192
|
boot_inconclusive_reason="no_app_runner"
|
|
2174
2193
|
elif command -v python3 >/dev/null 2>&1; then
|
|
2175
2194
|
local _boot_status
|
|
2176
|
-
_boot_status=$(_HEALTH="$_health_file" _STATE="$_state_file" python3 <<'PYEOF' 2>/dev/null || echo "INCONCLUSIVE:parse_error"
|
|
2195
|
+
_boot_status=$(_HEALTH="$_health_file" _STATE="$_state_file" python3 -E -s - <<'PYEOF' 2>/dev/null || echo "INCONCLUSIVE:parse_error"
|
|
2196
|
+
import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
|
|
2177
2197
|
import json, os
|
|
2178
2198
|
health_file = os.environ.get('_HEALTH', '')
|
|
2179
2199
|
state_file = os.environ.get('_STATE', '')
|
|
@@ -2336,7 +2356,8 @@ PYEOF
|
|
|
2336
2356
|
persist_inconclusive="true"; persist_inconclusive_reason="not_serveable"
|
|
2337
2357
|
elif command -v python3 >/dev/null 2>&1; then
|
|
2338
2358
|
local _p_status
|
|
2339
|
-
_p_status=$(_PF="$_proof_file" _ITER="${ITERATION_COUNT:-0}" python3 <<'PYEOF' 2>/dev/null || echo "MISSING"
|
|
2359
|
+
_p_status=$(_PF="$_proof_file" _ITER="${ITERATION_COUNT:-0}" python3 -E -s - <<'PYEOF' 2>/dev/null || echo "MISSING"
|
|
2360
|
+
import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
|
|
2340
2361
|
import json, os
|
|
2341
2362
|
p = os.environ['_PF']
|
|
2342
2363
|
if not os.path.isfile(p):
|
|
@@ -2399,7 +2420,8 @@ PYEOF
|
|
|
2399
2420
|
auth_inconclusive="true"; auth_inconclusive_reason="not_serveable"
|
|
2400
2421
|
elif command -v python3 >/dev/null 2>&1; then
|
|
2401
2422
|
local _a_status
|
|
2402
|
-
_a_status=$(_PF="$_proof_file" _STRICT="${LOKI_PROOF_AUTH_STRICT:-1}" _ITER="${ITERATION_COUNT:-0}" python3 <<'PYEOF' 2>/dev/null || echo "MISSING"
|
|
2423
|
+
_a_status=$(_PF="$_proof_file" _STRICT="${LOKI_PROOF_AUTH_STRICT:-1}" _ITER="${ITERATION_COUNT:-0}" python3 -E -s - <<'PYEOF' 2>/dev/null || echo "MISSING"
|
|
2424
|
+
import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
|
|
2403
2425
|
import json, os
|
|
2404
2426
|
p = os.environ['_PF']
|
|
2405
2427
|
strict = os.environ.get('_STRICT', '1') != '0'
|
|
@@ -2477,7 +2499,8 @@ PYEOF
|
|
|
2477
2499
|
authz_inconclusive="true"; authz_inconclusive_reason="not_serveable"
|
|
2478
2500
|
elif command -v python3 >/dev/null 2>&1; then
|
|
2479
2501
|
local _authz_status
|
|
2480
|
-
_authz_status=$(_PF="$_proof_file" _ITER="${ITERATION_COUNT:-0}" python3 <<'PYEOF' 2>/dev/null || echo "MISSING"
|
|
2502
|
+
_authz_status=$(_PF="$_proof_file" _ITER="${ITERATION_COUNT:-0}" python3 -E -s - <<'PYEOF' 2>/dev/null || echo "MISSING"
|
|
2503
|
+
import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
|
|
2481
2504
|
import json, os
|
|
2482
2505
|
p = os.environ['_PF']
|
|
2483
2506
|
if not os.path.isfile(p):
|
|
@@ -2734,7 +2757,7 @@ PYEOF
|
|
|
2734
2757
|
local timestamp
|
|
2735
2758
|
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
2736
2759
|
local failures_json diff_ok tests_ok base_for_json
|
|
2737
|
-
failures_json=$(_FAILURES="$failures" python3 -c "
|
|
2760
|
+
failures_json=$(_FAILURES="$failures" python3 -E -s -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
2738
2761
|
import json, os
|
|
2739
2762
|
items = [s for s in os.environ['_FAILURES'].split('|') if s]
|
|
2740
2763
|
print(json.dumps(items[:5]))
|
|
@@ -2751,11 +2774,11 @@ print(json.dumps(items[:5]))
|
|
|
2751
2774
|
if [ "$authz_fails" = "true" ]; then authz_ok="false"; else authz_ok="true"; fi
|
|
2752
2775
|
# Record WHY boot was inconclusive (no_app_runner / not_serveable / etc.) so a
|
|
2753
2776
|
# consumer of the block report can tell a genuine boot pass from a pass-through.
|
|
2754
|
-
boot_reason_json=$(_R="${boot_inconclusive_reason:-}" python3 -c "import json,os; print(json.dumps(os.environ['_R']))" 2>/dev/null || echo '""')
|
|
2755
|
-
nomock_reason_json=$(_R="${nomock_inconclusive_reason:-}" python3 -c "import json,os; print(json.dumps(os.environ['_R']))" 2>/dev/null || echo '""')
|
|
2756
|
-
persist_reason_json=$(_R="${persist_inconclusive_reason:-}" python3 -c "import json,os; print(json.dumps(os.environ['_R']))" 2>/dev/null || echo '""')
|
|
2757
|
-
auth_reason_json=$(_R="${auth_inconclusive_reason:-}" python3 -c "import json,os; print(json.dumps(os.environ['_R']))" 2>/dev/null || echo '""')
|
|
2758
|
-
authz_reason_json=$(_R="${authz_inconclusive_reason:-}" python3 -c "import json,os; print(json.dumps(os.environ['_R']))" 2>/dev/null || echo '""')
|
|
2777
|
+
boot_reason_json=$(_R="${boot_inconclusive_reason:-}" python3 -E -s -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json,os; print(json.dumps(os.environ['_R']))" 2>/dev/null || echo '""')
|
|
2778
|
+
nomock_reason_json=$(_R="${nomock_inconclusive_reason:-}" python3 -E -s -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json,os; print(json.dumps(os.environ['_R']))" 2>/dev/null || echo '""')
|
|
2779
|
+
persist_reason_json=$(_R="${persist_inconclusive_reason:-}" python3 -E -s -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json,os; print(json.dumps(os.environ['_R']))" 2>/dev/null || echo '""')
|
|
2780
|
+
auth_reason_json=$(_R="${auth_inconclusive_reason:-}" python3 -E -s -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json,os; print(json.dumps(os.environ['_R']))" 2>/dev/null || echo '""')
|
|
2781
|
+
authz_reason_json=$(_R="${authz_inconclusive_reason:-}" python3 -E -s -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json,os; print(json.dumps(os.environ['_R']))" 2>/dev/null || echo '""')
|
|
2759
2782
|
base_for_json="${base_sha:-}"
|
|
2760
2783
|
cat > "$ev_tmp" << EVIDENCE_EOF
|
|
2761
2784
|
{
|
|
@@ -3393,7 +3416,7 @@ council_heuristic_review() {
|
|
|
3393
3416
|
local _tr_file=".loki/quality/test-results.json"
|
|
3394
3417
|
local _tests_ok=0
|
|
3395
3418
|
if [ -f "$_tr_file" ] && command -v python3 >/dev/null 2>&1; then
|
|
3396
|
-
_tests_ok=$(_TR="$_tr_file" python3 -c "
|
|
3419
|
+
_tests_ok=$(_TR="$_tr_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
3397
3420
|
import json, os
|
|
3398
3421
|
try:
|
|
3399
3422
|
with open(os.environ['_TR']) as f:
|
|
@@ -3558,13 +3581,15 @@ council_evaluate_member() {
|
|
|
3558
3581
|
# the member stays CONTINUE.
|
|
3559
3582
|
#
|
|
3560
3583
|
# Parse verdict mirrors council_evidence_gate: runner=="none" => PASS,
|
|
3561
|
-
# pass is False => FAIL,
|
|
3584
|
+
# pass is False => FAIL, only a boolean True (and not status no_tests_run)
|
|
3585
|
+
# => PASS; a missing, null or non-boolean pass key recorded no outcome =>
|
|
3586
|
+
# INCONCLUSIVE (not red, not positive). Unparseable/missing file => absent.
|
|
3562
3587
|
local tr_file="$loki_dir/quality/test-results.json"
|
|
3563
|
-
local test_evidence="absent" # absent | pass | fail
|
|
3588
|
+
local test_evidence="absent" # absent | pass | fail | inconclusive
|
|
3564
3589
|
local test_runner_seen="none"
|
|
3565
3590
|
if [ -f "$tr_file" ]; then
|
|
3566
3591
|
local _tr_status
|
|
3567
|
-
_tr_status=$(_TR_FILE="$tr_file" python3 -c "
|
|
3592
|
+
_tr_status=$(_TR_FILE="$tr_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
3568
3593
|
import json, os, sys
|
|
3569
3594
|
try:
|
|
3570
3595
|
with open(os.environ['_TR_FILE']) as f:
|
|
@@ -3573,11 +3598,13 @@ except (json.JSONDecodeError, IOError, KeyError, ValueError):
|
|
|
3573
3598
|
print('absent:none')
|
|
3574
3599
|
sys.exit(0)
|
|
3575
3600
|
runner = d.get('runner', 'none')
|
|
3576
|
-
passed = d.get('pass'
|
|
3601
|
+
passed = d.get('pass')
|
|
3577
3602
|
if runner == 'none':
|
|
3578
3603
|
print('pass:none')
|
|
3579
3604
|
elif passed is False:
|
|
3580
3605
|
print('fail:%s' % runner)
|
|
3606
|
+
elif passed is not True or d.get('status') == 'no_tests_run':
|
|
3607
|
+
print('inconclusive:%s' % runner)
|
|
3581
3608
|
else:
|
|
3582
3609
|
print('pass:%s' % runner)
|
|
3583
3610
|
" 2>/dev/null || echo "absent:none")
|
|
@@ -3619,7 +3646,7 @@ else:
|
|
|
3619
3646
|
local _q _qcount
|
|
3620
3647
|
for _q in pending in-progress blocked; do
|
|
3621
3648
|
[ -f "$loki_dir/queue/${_q}.json" ] || continue
|
|
3622
|
-
_qcount=$(_QUEUE_FILE="$loki_dir/queue/${_q}.json" python3 -c "import json, os
|
|
3649
|
+
_qcount=$(_QUEUE_FILE="$loki_dir/queue/${_q}.json" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json, os
|
|
3623
3650
|
d = json.load(open(os.environ['_QUEUE_FILE']))
|
|
3624
3651
|
print(len(d.get('tasks', d) if isinstance(d, dict) else d))" 2>/dev/null || echo "0")
|
|
3625
3652
|
# Guard against a non-numeric read (malformed file, python absent).
|
|
@@ -3642,6 +3669,8 @@ print(len(d.get('tasks', d) if isinstance(d, dict) else d))" 2>/dev/null || echo
|
|
|
3642
3669
|
# a no-tests / greenfield project leaves test_auditor at CONTINUE.
|
|
3643
3670
|
if [ "$test_evidence" = "absent" ]; then
|
|
3644
3671
|
reasons="${reasons}no structured test results found; "
|
|
3672
|
+
elif [ "$test_evidence" = "inconclusive" ]; then
|
|
3673
|
+
reasons="${reasons}structured test results inconclusive (runner '$test_runner_seen' recorded no boolean pass); "
|
|
3645
3674
|
elif [ "$test_runner_seen" = "none" ]; then
|
|
3646
3675
|
reasons="${reasons}no real test suite ran (runner none); "
|
|
3647
3676
|
elif [ "$test_evidence" = "pass" ]; then
|
|
@@ -3754,7 +3783,7 @@ council_aggregate_votes() {
|
|
|
3754
3783
|
# Serialize the votes array with json.dumps so backslashes/control chars are
|
|
3755
3784
|
# escaped correctly (BUG fix: sed-only escaping produced invalid JSON).
|
|
3756
3785
|
local votes_json
|
|
3757
|
-
votes_json=$(_MEMBERS="$_members" _ROLES="$_roles" _VOTEVALS="$_vote_values" _REASONS="$_reasons" python3 -c "
|
|
3786
|
+
votes_json=$(_MEMBERS="$_members" _ROLES="$_roles" _VOTEVALS="$_vote_values" _REASONS="$_reasons" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
3758
3787
|
import json, os
|
|
3759
3788
|
def lines(name):
|
|
3760
3789
|
v = os.environ.get(name, '')
|
|
@@ -3797,7 +3826,7 @@ print(json.dumps(out))
|
|
|
3797
3826
|
_VERDICT="$verdict" \
|
|
3798
3827
|
_VOTES="$votes_json" \
|
|
3799
3828
|
_ROUND_FILE="$round_file" \
|
|
3800
|
-
python3 -c "
|
|
3829
|
+
python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
3801
3830
|
import json, os
|
|
3802
3831
|
from datetime import datetime, timezone
|
|
3803
3832
|
round_data = {
|
|
@@ -3846,14 +3875,19 @@ council_devils_advocate_review() {
|
|
|
3846
3875
|
# (.loki/quality/test-results.json, written by run.sh:ensure_completion_test
|
|
3847
3876
|
# _evidence; parsed the same way as council_evaluate_member ~2414-2438 and the
|
|
3848
3877
|
# evidence gate). Parse verdict: runner=="none" => PASS (no real suite to
|
|
3849
|
-
# contradict completion), pass is False => FAIL,
|
|
3878
|
+
# contradict completion), pass is False => FAIL, a boolean True => PASS,
|
|
3879
|
+
# anything else (missing/null/non-boolean pass, status no_tests_run) =>
|
|
3880
|
+
# INCONCLUSIVE. Only FAIL is an issue here: inconclusive does not contradict
|
|
3881
|
+
# completion, exactly as council_evidence_gate lets it through, and vetoing
|
|
3882
|
+
# it would stall a zero-test run (#82) that is meant to reach this vote.
|
|
3883
|
+
# The legacy log
|
|
3850
3884
|
# glob is kept ONLY as an ADDITIONAL red signal -- its absence is NOT an issue
|
|
3851
3885
|
# (nothing writes .loki/logs/test-*.log, so an empty glob is the normal case
|
|
3852
3886
|
# and must never veto a unanimous COMPLETE on its own).
|
|
3853
3887
|
local tr_file="$loki_dir/quality/test-results.json"
|
|
3854
3888
|
if [ -f "$tr_file" ]; then
|
|
3855
3889
|
local _tr_status
|
|
3856
|
-
_tr_status=$(_TR_FILE="$tr_file" python3 -c "
|
|
3890
|
+
_tr_status=$(_TR_FILE="$tr_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
3857
3891
|
import json, os, sys
|
|
3858
3892
|
try:
|
|
3859
3893
|
with open(os.environ['_TR_FILE']) as f:
|
|
@@ -3862,11 +3896,13 @@ except (json.JSONDecodeError, IOError, KeyError, ValueError):
|
|
|
3862
3896
|
print('absent')
|
|
3863
3897
|
sys.exit(0)
|
|
3864
3898
|
runner = d.get('runner', 'none')
|
|
3865
|
-
passed = d.get('pass'
|
|
3899
|
+
passed = d.get('pass')
|
|
3866
3900
|
if runner == 'none':
|
|
3867
3901
|
print('pass')
|
|
3868
3902
|
elif passed is False:
|
|
3869
3903
|
print('fail')
|
|
3904
|
+
elif passed is not True or d.get('status') == 'no_tests_run':
|
|
3905
|
+
print('inconclusive')
|
|
3870
3906
|
else:
|
|
3871
3907
|
print('pass')
|
|
3872
3908
|
" 2>/dev/null || echo "absent")
|
|
@@ -3889,7 +3925,7 @@ else:
|
|
|
3889
3925
|
# Skeptical check 2: Are there still failing tasks in the queue?
|
|
3890
3926
|
if [ -f "$loki_dir/queue/failed.json" ]; then
|
|
3891
3927
|
local failed_count
|
|
3892
|
-
failed_count=$(_QUEUE_FILE="$loki_dir/queue/failed.json" python3 -c "import json, os; print(len(json.load(open(os.environ['_QUEUE_FILE']))))" 2>/dev/null || echo "0")
|
|
3928
|
+
failed_count=$(_QUEUE_FILE="$loki_dir/queue/failed.json" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json, os; print(len(json.load(open(os.environ['_QUEUE_FILE']))))" 2>/dev/null || echo "0")
|
|
3893
3929
|
if [ "$failed_count" -gt 0 ]; then
|
|
3894
3930
|
((issues_found++))
|
|
3895
3931
|
issue_details="${issue_details}$failed_count tasks in failed queue; "
|
|
@@ -3935,7 +3971,7 @@ else:
|
|
|
3935
3971
|
_ISSUES="$issues_found" \
|
|
3936
3972
|
_DETAILS="${issue_details:-none}" \
|
|
3937
3973
|
_DA_FILE="$da_file" \
|
|
3938
|
-
python3 -c "
|
|
3974
|
+
python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
3939
3975
|
import json, os
|
|
3940
3976
|
from datetime import datetime, timezone
|
|
3941
3977
|
da_result = {
|
|
@@ -4031,7 +4067,7 @@ council_evaluate() {
|
|
|
4031
4067
|
if loki_council_dispatch_agents "$ITERATION_COUNT" "${COUNCIL_PRD_PATH:-}"; then
|
|
4032
4068
|
local _va_round_file="$COUNCIL_STATE_DIR/votes/round-${ITERATION_COUNT}.json"
|
|
4033
4069
|
if [ -f "$_va_round_file" ]; then
|
|
4034
|
-
aggregate_result=$(_RF="$_va_round_file" python3 -c "import json, os; print(json.load(open(os.environ['_RF'])).get('verdict', 'CONTINUE'))" 2>/dev/null || echo "")
|
|
4070
|
+
aggregate_result=$(_RF="$_va_round_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json, os; print(json.load(open(os.environ['_RF'])).get('verdict', 'CONTINUE'))" 2>/dev/null || echo "")
|
|
4035
4071
|
fi
|
|
4036
4072
|
fi
|
|
4037
4073
|
fi
|
|
@@ -4053,7 +4089,7 @@ council_evaluate() {
|
|
|
4053
4089
|
local complete_count=0
|
|
4054
4090
|
local members_present=0
|
|
4055
4091
|
if [ -f "$round_file" ]; then
|
|
4056
|
-
complete_count=$(_RF="$round_file" python3 -c "import json, os; print(json.load(open(os.environ['_RF'])).get('complete_votes', 0))" 2>/dev/null || echo "0")
|
|
4092
|
+
complete_count=$(_RF="$round_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json, os; print(json.load(open(os.environ['_RF'])).get('complete_votes', 0))" 2>/dev/null || echo "0")
|
|
4057
4093
|
# WAVE13 CRITICAL quorum gate: how many voters actually responded
|
|
4058
4094
|
# (total_members records the ACTUAL returned count -- see
|
|
4059
4095
|
# voter-agents.sh). A degraded/partial dispatch response must never
|
|
@@ -4061,7 +4097,7 @@ council_evaluate() {
|
|
|
4061
4097
|
# somehow read COMPLETE. This is defense-in-depth: the parser
|
|
4062
4098
|
# already forces CONTINUE on undercount, but the completion-detection
|
|
4063
4099
|
# trust core must independently assert full quorum before stopping.
|
|
4064
|
-
members_present=$(_RF="$round_file" python3 -c "import json, os; print(json.load(open(os.environ['_RF'])).get('total_members', 0))" 2>/dev/null || echo "0")
|
|
4100
|
+
members_present=$(_RF="$round_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json, os; print(json.load(open(os.environ['_RF'])).get('total_members', 0))" 2>/dev/null || echo "0")
|
|
4065
4101
|
fi
|
|
4066
4102
|
# Normalize to integers (guard against empty/non-numeric on read failure)
|
|
4067
4103
|
case "$complete_count" in (''|*[!0-9]*) complete_count=0 ;; esac
|
|
@@ -4157,7 +4193,7 @@ council_managed_should_stop() {
|
|
|
4157
4193
|
diff_summary=$(cd "$project_dir" 2>/dev/null && git diff --stat 2>/dev/null | tail -20 | tr '\n' ' ' || echo "")
|
|
4158
4194
|
local test_summary=""
|
|
4159
4195
|
if [ -f "$loki_dir/quality/test-results.json" ]; then
|
|
4160
|
-
test_summary=$(_TRF="$loki_dir/quality/test-results.json" python3 -c "
|
|
4196
|
+
test_summary=$(_TRF="$loki_dir/quality/test-results.json" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
4161
4197
|
import json, os
|
|
4162
4198
|
try:
|
|
4163
4199
|
d = json.load(open(os.environ['_TRF']))
|
|
@@ -4168,7 +4204,7 @@ except Exception:
|
|
|
4168
4204
|
fi
|
|
4169
4205
|
local pending_tasks="[]"
|
|
4170
4206
|
if [ -f "$loki_dir/queue/pending.json" ]; then
|
|
4171
|
-
pending_tasks=$(_QF="$loki_dir/queue/pending.json" python3 -c "
|
|
4207
|
+
pending_tasks=$(_QF="$loki_dir/queue/pending.json" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
4172
4208
|
import json, os
|
|
4173
4209
|
try:
|
|
4174
4210
|
d = json.load(open(os.environ['_QF']))
|
|
@@ -4191,7 +4227,8 @@ except Exception:
|
|
|
4191
4227
|
_CC_VERDICTS_DIR="$verdicts_dir" \
|
|
4192
4228
|
_CC_LOKI_DIR="$loki_dir" \
|
|
4193
4229
|
LOKI_TARGET_DIR="${TARGET_DIR:-$(pwd)}" \
|
|
4194
|
-
python3 - <<'PYEOF' 2>/dev/null || exit_code=$?
|
|
4230
|
+
python3 -E - <<'PYEOF' 2>/dev/null || exit_code=$?
|
|
4231
|
+
import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
|
|
4195
4232
|
import json, os, sys, pathlib
|
|
4196
4233
|
|
|
4197
4234
|
# Path setup: prefer project_dir so providers.managed resolves from main tree.
|
|
@@ -4368,7 +4405,7 @@ _council_convergence_evidence_green() {
|
|
|
4368
4405
|
# file or runner=="none" (no suite) is NOT affirmative evidence -> not green.
|
|
4369
4406
|
[ -f "$tr_file" ] || return 1
|
|
4370
4407
|
local tr_state
|
|
4371
|
-
tr_state=$(_TR_FILE="$tr_file" python3 -c "
|
|
4408
|
+
tr_state=$(_TR_FILE="$tr_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
4372
4409
|
import json, os, sys
|
|
4373
4410
|
try:
|
|
4374
4411
|
with open(os.environ['_TR_FILE']) as f:
|
|
@@ -4376,9 +4413,12 @@ try:
|
|
|
4376
4413
|
except (json.JSONDecodeError, IOError, KeyError, ValueError):
|
|
4377
4414
|
print('no'); sys.exit(0)
|
|
4378
4415
|
runner = d.get('runner', 'none')
|
|
4379
|
-
passed = d.get('pass'
|
|
4380
|
-
# Affirmative green requires a REAL suite (runner != none) that
|
|
4381
|
-
|
|
4416
|
+
passed = d.get('pass')
|
|
4417
|
+
# Affirmative green requires a REAL suite (runner != none) that recorded a
|
|
4418
|
+
# boolean True pass. A missing/null/non-boolean pass key or a zero-test run
|
|
4419
|
+
# (status no_tests_run) is inconclusive, which is not green.
|
|
4420
|
+
print('yes' if (runner != 'none' and passed is True
|
|
4421
|
+
and d.get('status') != 'no_tests_run') else 'no')
|
|
4382
4422
|
" 2>/dev/null || echo "no")
|
|
4383
4423
|
[ "$tr_state" = "yes" ] || return 1
|
|
4384
4424
|
|
|
@@ -4388,7 +4428,7 @@ print('yes' if (runner != 'none' and passed is not False) else 'no')
|
|
|
4388
4428
|
local results_file="${TARGET_DIR:-.}/.loki/checklist/verification-results.json"
|
|
4389
4429
|
if [ -f "$results_file" ]; then
|
|
4390
4430
|
local cl_state
|
|
4391
|
-
cl_state=$(_RESULTS_FILE="$results_file" python3 -c "
|
|
4431
|
+
cl_state=$(_RESULTS_FILE="$results_file" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
4392
4432
|
import json, os, sys
|
|
4393
4433
|
try:
|
|
4394
4434
|
with open(os.environ['_RESULTS_FILE']) as f:
|
|
@@ -4637,7 +4677,7 @@ council_write_report() {
|
|
|
4637
4677
|
REPORT_HEADER
|
|
4638
4678
|
|
|
4639
4679
|
# Append vote history from state
|
|
4640
|
-
_COUNCIL_STATE_FILE="$COUNCIL_STATE_DIR/state.json" python3 -c "
|
|
4680
|
+
_COUNCIL_STATE_FILE="$COUNCIL_STATE_DIR/state.json" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
4641
4681
|
import json, os
|
|
4642
4682
|
try:
|
|
4643
4683
|
with open(os.environ['_COUNCIL_STATE_FILE']) as f:
|
|
@@ -29,6 +29,21 @@ def _excluded(path):
|
|
|
29
29
|
return path == ".loki" or path.startswith(".loki/")
|
|
30
30
|
|
|
31
31
|
|
|
32
|
+
def _preexisting_untracked(repo_dir):
|
|
33
|
+
"""Paths the user already had untracked when the Loki session branch was
|
|
34
|
+
minted (run.sh setup_agent_branch writes .loki/state/preexisting-untracked.z,
|
|
35
|
+
NUL-delimited, relative to the repo top). They are not this run's work, so
|
|
36
|
+
the receipt does not list them; tree_sha256 still binds their bytes. Returns
|
|
37
|
+
(prefix of repo_dir inside the repo, set of paths); empty when absent."""
|
|
38
|
+
try:
|
|
39
|
+
with open(os.path.join(repo_dir, ".loki", "state", "preexisting-untracked.z"), "rb") as fh:
|
|
40
|
+
raw = fh.read()
|
|
41
|
+
except OSError:
|
|
42
|
+
return "", frozenset()
|
|
43
|
+
paths = frozenset(p.decode("utf-8", "surrogateescape") for p in raw.split(b"\0") if p)
|
|
44
|
+
return (_git(repo_dir, ["rev-parse", "--show-prefix"]) or "").strip(), paths
|
|
45
|
+
|
|
46
|
+
|
|
32
47
|
def _parse_numstat(raw):
|
|
33
48
|
files = []
|
|
34
49
|
for record in (raw or "").split("\0"):
|
|
@@ -107,7 +122,9 @@ def collect_workspace_diff(repo_dir, base, include_diffs=False):
|
|
|
107
122
|
diffs.append({"path": path, "patch": chunk})
|
|
108
123
|
|
|
109
124
|
untracked = _git(repo_dir, ["ls-files", "--others", "--exclude-standard", "-z"])
|
|
110
|
-
|
|
125
|
+
prefix, preexisting = _preexisting_untracked(repo_dir)
|
|
126
|
+
for path in sorted(p for p in (untracked or "").split("\0")
|
|
127
|
+
if p and not _excluded(p) and prefix + p not in preexisting):
|
|
111
128
|
stat = _git(
|
|
112
129
|
repo_dir,
|
|
113
130
|
["diff", "--no-index", "--no-renames", "--numstat", "-z", "--", "/dev/null", path],
|