loki-mode 9.52.0 → 9.54.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/autonomy/run.sh CHANGED
@@ -2678,7 +2678,7 @@ _advance_current_phase() {
2678
2678
  [ -f "$orch" ] || return 0
2679
2679
  # Values are passed via argv (not interpolated into the source) so a phase or
2680
2680
  # path containing quotes can never break or inject into the python.
2681
- python3 -c "
2681
+ python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
2682
2682
  import json, sys
2683
2683
  f, phase = sys.argv[1], sys.argv[2]
2684
2684
  try:
@@ -4377,7 +4377,7 @@ _loki_receipt_facts() {
4377
4377
  local pj="$loki_dir/proofs/$rid/proof.json"
4378
4378
  [ -f "$pj" ] || return 0
4379
4379
  local headline=""
4380
- headline="$(python3 -c "
4380
+ headline="$(python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
4381
4381
  import json, sys
4382
4382
  try:
4383
4383
  d = json.load(open(sys.argv[1]))
@@ -4999,7 +4999,7 @@ except Exception:
4999
4999
  _LOKI_CS_ASSUMPTIONS_HIGH="$assumptions_high" \
5000
5000
  _LOKI_CS_OUT_FILE="$loki_dir/state/completion.json" \
5001
5001
  _LOKI_CS_LAST_ERROR="$loki_dir/state/LAST_ERROR.json" \
5002
- python3 -c "
5002
+ python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
5003
5003
  import json, os, tempfile
5004
5004
  out = os.environ['_LOKI_CS_OUT_FILE']
5005
5005
  def i(v):
@@ -5219,7 +5219,7 @@ print_completion_card() {
5219
5219
  # split would collapse adjacent empties). Any failure leaves the card
5220
5220
  # unrendered. Trailing-newline guard: NUL-free, fields are single-line.
5221
5221
  local _fields
5222
- _fields="$(python3 -c "
5222
+ _fields="$(python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
5223
5223
  import json,sys
5224
5224
  try:
5225
5225
  d=json.load(open(sys.argv[1]))
@@ -5541,7 +5541,8 @@ validate_exec_manifest_result() {
5541
5541
  result_file=$(mktemp "${TARGET_DIR}/.loki/.exec-manifest-result.XXXXXX") || return 1
5542
5542
  LOKI_RESULT_FILE="$result_file" LOKI_RESULT_STREAM="$stream_name" \
5543
5543
  LOKI_RESULT_BASE="$base_sha" LOKI_RESULT_BRANCH="$branch" \
5544
- LOKI_RESULT_REPO="$TARGET_DIR" python3 <<'PY'
5544
+ LOKI_RESULT_REPO="$TARGET_DIR" python3 -E - <<'PY'
5545
+ import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
5545
5546
  import json, os, subprocess
5546
5547
  paths = subprocess.check_output(
5547
5548
  ["git", "-C", os.environ["LOKI_RESULT_REPO"], "diff", "--name-only",
@@ -8098,7 +8099,7 @@ generate_proof_of_run() {
8098
8099
  ITERATION_COUNT="${ITERATION_COUNT:-0}" \
8099
8100
  PROVIDER_NAME="$provider" \
8100
8101
  PRD_PATH="${prd_path:-}" \
8101
- python3 "$gen" \
8102
+ python3 -E "$gen" \
8102
8103
  --loki-dir "$loki_dir" \
8103
8104
  --loki-version "$ver" \
8104
8105
  --provider "$provider" \
@@ -8125,7 +8126,7 @@ generate_proof_of_run() {
8125
8126
  ITERATION_COUNT="${ITERATION_COUNT:-0}" \
8126
8127
  PROVIDER_NAME="$provider" \
8127
8128
  PRD_PATH="${prd_path:-}" \
8128
- python3 "$gen" \
8129
+ python3 -E "$gen" \
8129
8130
  --loki-dir "$loki_dir" \
8130
8131
  --loki-version "$ver" \
8131
8132
  --provider "$provider" \
@@ -9063,6 +9064,10 @@ setup_agent_branch() {
9063
9064
  # Controlled by LOKI_BRANCH_PROTECTION env var (default: true). Set it to
9064
9065
  # "false" to opt out fully and work on the current branch (back-compat).
9065
9066
  local branch_protection="${LOKI_BRANCH_PROTECTION:-true}"
9067
+ # Set to 1 only once THIS run has a current snapshot (mint or resume
9068
+ # union); _loki_record_session_created records nothing otherwise, since a
9069
+ # leftover or stale snapshot would make it claim a user's file.
9070
+ _LOKI_SNAPSHOT_THIS_RUN=0
9066
9071
 
9067
9072
  if [ "$branch_protection" != "true" ]; then
9068
9073
  log_info "Branch protection disabled (LOKI_BRANCH_PROTECTION=${branch_protection})"
@@ -9097,11 +9102,15 @@ setup_agent_branch() {
9097
9102
 
9098
9103
  # Already on a loki branch (session-* or delegate-*): idempotent reuse, do
9099
9104
  # not nest a branch off a loki branch (LOCK A5/A7).
9105
+ # Both reuse paths add what is untracked now to the pre-existing list (a
9106
+ # file the user made between sessions is theirs), except the files an
9107
+ # unfinished previous session created (_loki_resume_snapshot).
9100
9108
  case "$cur" in
9101
9109
  loki/*)
9102
9110
  log_info "Already on loki branch ${cur}"
9103
9111
  mkdir -p .loki/state 2>/dev/null || true
9104
9112
  printf '%s\n' "$cur" > .loki/state/agent-branch.txt 2>/dev/null || true
9113
+ _loki_resume_snapshot
9105
9114
  return 0
9106
9115
  ;;
9107
9116
  esac
@@ -9112,8 +9121,14 @@ setup_agent_branch() {
9112
9121
  if [ -s .loki/state/agent-branch.txt ]; then
9113
9122
  recorded="$(cat .loki/state/agent-branch.txt 2>/dev/null || true)"
9114
9123
  if [ -n "$recorded" ] && git rev-parse --verify "$recorded" >/dev/null 2>&1; then
9115
- if git checkout "$recorded" >/dev/null 2>&1; then
9124
+ # --no-overwrite-ignore: git's default checkout silently replaces a
9125
+ # gitignored user file at a path the session branch tracks, and the
9126
+ # file is gone when the user switches back. On conflict the resume
9127
+ # is refused and a fresh session branch (with a fresh snapshot) is
9128
+ # minted below.
9129
+ if git checkout --no-overwrite-ignore "$recorded" >/dev/null 2>&1; then
9116
9130
  log_info "Resuming on recorded agent branch: ${recorded}"
9131
+ _loki_resume_snapshot
9117
9132
  return 0
9118
9133
  fi
9119
9134
  log_warn "Recorded agent branch ${recorded} could not be checked out - creating a new one"
@@ -9132,12 +9147,31 @@ setup_agent_branch() {
9132
9147
 
9133
9148
  log_info "Branch protection enabled - creating agent branch: $branch_name (base: $cur)"
9134
9149
 
9150
+ # Record the user's own untracked and gitignored files so
9151
+ # commit_session_changes and the receipt leave them alone.
9152
+ local snap_ok=1 leftover=""
9153
+ if ! _loki_snapshot_or_fail_closed; then
9154
+ snap_ok=0
9155
+ rm -f .loki/state/preexisting-untracked.z .loki/state/preexisting-untracked.sha.z 2>/dev/null
9156
+ log_warn "Could not record pre-existing untracked files; this session will commit nothing (review and commit manually)"
9157
+ fi
9158
+
9135
9159
  # Create and checkout the feature branch
9136
9160
  if ! git checkout -b "$branch_name" 2>/dev/null; then
9137
9161
  log_error "Failed to create agent branch: $branch_name"
9138
9162
  return 1
9139
9163
  fi
9140
9164
 
9165
+ # A new session branch starts with an empty session-created record. Files
9166
+ # an unfinished earlier session left (reached here after a refused resume)
9167
+ # were just snapshotted as pre-existing: kept on disk, never committed.
9168
+ leftover="$(_loki_nul_names .loki/state/session-created.z)"
9169
+ if [ -n "$leftover" ]; then
9170
+ log_warn "Left uncommitted from an earlier unfinished session, now treated as your files: $leftover"
9171
+ fi
9172
+ rm -f .loki/state/session-created.z 2>/dev/null
9173
+ if [ "$snap_ok" = 1 ]; then _LOKI_SNAPSHOT_THIS_RUN=1; fi
9174
+
9141
9175
  # Store the branch name for later use (PR creation, cleanup)
9142
9176
  printf '%s\n' "$branch_name" > .loki/state/agent-branch.txt 2>/dev/null
9143
9177
 
@@ -9146,6 +9180,180 @@ setup_agent_branch() {
9146
9180
  echo "$branch_name"
9147
9181
  }
9148
9182
 
9183
+ # Snapshot, or fail closed. Any snapshot failure (for example git older than
9184
+ # 2.18, whose status lacks --no-renames / --ignored=matching) leaves a marker
9185
+ # that makes commit_session_changes commit nothing: a missing snapshot must
9186
+ # only ever mean an older session, never "sweep everything".
9187
+ _loki_snapshot_or_fail_closed() {
9188
+ if _loki_snapshot_preexisting "$@"; then
9189
+ rm -f .loki/state/preexisting-untracked.failed 2>/dev/null
9190
+ return 0
9191
+ fi
9192
+ mkdir -p .loki/state 2>/dev/null
9193
+ : > .loki/state/preexisting-untracked.failed 2>/dev/null
9194
+ return 1
9195
+ }
9196
+
9197
+ # _loki_snapshot_preexisting [union]
9198
+ # Write .loki/state/preexisting-untracked.z: every path git does not track,
9199
+ # untracked and gitignored, repo-wide, NUL-delimited, relative to the repo top.
9200
+ # commit_session_changes unstages these and the receipt
9201
+ # (autonomy/lib/workspace_diff.py) does not claim them. --ignored=matching lists
9202
+ # a directory only when an ignore pattern matches it, so node_modules/ is one
9203
+ # "dir/" entry covering its subtree, while logs/ holding only *.log files is
9204
+ # listed file by file (a new logs/app.json is still the agent's). "union" keeps
9205
+ # the paths already recorded. The sibling .sha.z (content hash per file entry)
9206
+ # lets the receipt list a pre-existing file the run changed; it is removed
9207
+ # first, so a failed hash step means no detection, never stale hashes. Returns
9208
+ # non-zero, leaving any earlier list in place, when git cannot list.
9209
+ _loki_snapshot_preexisting() {
9210
+ local snap=".loki/state/preexisting-untracked.z" top="" base="" exclude=""
9211
+ rm -f "${snap%.z}.sha.z" 2>/dev/null
9212
+ top="$(git rev-parse --show-toplevel 2>/dev/null)" || return 1
9213
+ mkdir -p .loki/state 2>/dev/null || return 1
9214
+ # "union" never adds a path listed in session-created.z: those files were
9215
+ # made by a session that did not finish (interrupt, pod loss), so the
9216
+ # session that finishes the work commits them. If the user edits one of
9217
+ # them between sessions it is still the session's file and is committed
9218
+ # with the edit. Recorded entries only block additions; they never remove
9219
+ # a path already in the list.
9220
+ if [ "${1:-}" = union ]; then
9221
+ base="$snap"
9222
+ exclude=".loki/state/session-created.z"
9223
+ fi
9224
+ if ! _loki_untracked_status "$snap.status" \
9225
+ || ! _loki_untracked_merge "$snap.status" "$base" "$exclude" exact "$snap"; then
9226
+ rm -f "$snap.status" "$snap.tmp"
9227
+ return 1
9228
+ fi
9229
+ rm -f "$snap.status"
9230
+ python3 -E "$SCRIPT_DIR/lib/workspace_diff.py" hash-snapshot "$top" "$snap" >/dev/null 2>&1 \
9231
+ || log_warn "Could not hash your pre-existing untracked files; the receipt cannot list the ones this run changes"
9232
+ return 0
9233
+ }
9234
+
9235
+ # _loki_untracked_status <out>: the one enumeration behind both the snapshot
9236
+ # and the session-created record, so the two always agree: raw
9237
+ # `git status --porcelain -z` records for the whole repo, .loki excluded.
9238
+ # Non-zero, writing nothing, when git cannot list.
9239
+ _loki_untracked_status() {
9240
+ local top="" prefix=""
9241
+ top="$(git rev-parse --show-toplevel 2>/dev/null)" || return 1
9242
+ prefix="$(git rev-parse --show-prefix 2>/dev/null)" || return 1
9243
+ git -C "$top" --no-optional-locks status --porcelain -z --no-renames -uall \
9244
+ --ignored=matching --ignore-submodules=all -- ":(exclude,literal)${prefix}.loki" \
9245
+ > "$1" 2>/dev/null && return 0
9246
+ rm -f "$1"
9247
+ return 1
9248
+ }
9249
+
9250
+ # _loki_untracked_merge <status> <base> <exclude> exact|cover <out>
9251
+ # Atomically write <out>: the NUL-delimited paths of <base>, plus every
9252
+ # untracked ("??") or ignored ("!!") path in <status> that <exclude> does not
9253
+ # hold. With "cover", an <exclude> entry ending in "/" also holds every path
9254
+ # below it (workspace_diff._covered). An empty or missing <base> or <exclude>
9255
+ # is an empty list; any other read or write failure is non-zero and leaves
9256
+ # <out> as it was. Sorted bytewise, like `LC_ALL=C sort -z -u`. One python
9257
+ # process: bash 3.2 has no associative arrays, and a bash loop cost 300ms per
9258
+ # 6,000 entries on /bin/bash. -E and no cwd on sys.path (D7): the cwd is the
9259
+ # agent's repo.
9260
+ _loki_untracked_merge() {
9261
+ python3 -E -c 'import sys
9262
+ sys.path[:] = [p for p in sys.path if p not in ("", ".")]
9263
+ import os
9264
+ status, base, exclude, mode, out = sys.argv[1:6]
9265
+ def entries(path, missing_ok=True):
9266
+ try:
9267
+ with open(path, "rb") as fh:
9268
+ return [p for p in fh.read().split(b"\0") if p]
9269
+ except FileNotFoundError:
9270
+ if missing_ok:
9271
+ return []
9272
+ raise
9273
+ held = set(entries(exclude)) if exclude else set()
9274
+ def covered(path):
9275
+ if path in held:
9276
+ return True
9277
+ cut = path.find(b"/") if mode == "cover" else -1
9278
+ while cut != -1:
9279
+ if path[:cut + 1] in held:
9280
+ return True
9281
+ cut = path.find(b"/", cut + 1)
9282
+ return False
9283
+ paths = set(entries(base)) if base else set()
9284
+ for rec in entries(status, missing_ok=False):
9285
+ # The session-created record ("cover" mode) never holds a directory entry:
9286
+ # a whole-directory entry (logs/, out/) would stop the resume union from
9287
+ # adopting a file the user later creates inside it, and a .gitignore
9288
+ # rewrite would then sweep that file. Adopting such a directory as
9289
+ # pre-existing fails safe (the agent files inside stay uncommitted).
9290
+ if mode == "cover" and rec.endswith(b"/"):
9291
+ continue
9292
+ if rec[:3] in (b"?? ", b"!! ") and not covered(rec[3:]):
9293
+ paths.add(rec[3:])
9294
+ with open(out + ".tmp", "wb") as fh:
9295
+ fh.write(b"".join(p + b"\0" for p in sorted(paths)))
9296
+ os.replace(out + ".tmp", out)' "$@" 2>/dev/null
9297
+ }
9298
+
9299
+ # _loki_record_session_created
9300
+ # Union into .loki/state/session-created.z (NUL-delimited, repo-top-relative,
9301
+ # atomic) the paths git does not track now that the pre-existing snapshot does
9302
+ # not cover: the files this session created. The resume union never adds these
9303
+ # to the snapshot, so a session that is interrupted (cleanup) or killed (pod
9304
+ # loss: no trap runs, hence the call after every provider turn) still has its
9305
+ # own files committed by the session that finishes. commit_session_changes
9306
+ # clears the record after a normal commit; a new session branch starts empty.
9307
+ # Records nothing unless this run took a current snapshot (a leftover or stale
9308
+ # one would make it claim a user's file). On a failure the record is left as it
9309
+ # was, never partial, and preexisting-untracked.failed is never touched; the
9310
+ # next successful call recomputes everything, so only a failure on the final
9311
+ # call before a kill loses paths (those are then treated as the user's: kept on
9312
+ # disk, not committed).
9313
+ _loki_record_session_created() {
9314
+ local snap=".loki/state/preexisting-untracked.z" out=".loki/state/session-created.z"
9315
+ [ "${_LOKI_SNAPSHOT_THIS_RUN:-0}" = 1 ] || return 0
9316
+ if [ -f "$snap" ] && _loki_untracked_status "$out.status" \
9317
+ && _loki_untracked_merge "$out.status" "$out" "$snap" cover "$out"; then
9318
+ rm -f "$out.status"
9319
+ return 0
9320
+ fi
9321
+ rm -f "$out.status" "$out.tmp" 2>/dev/null
9322
+ log_warn "Could not record the files this session created; if the run stops before the next record, they are treated as yours and left uncommitted"
9323
+ return 1
9324
+ }
9325
+
9326
+ # _loki_resume_snapshot: the resume paths' union, naming the files carried over
9327
+ # from an unfinished previous session (this session's commit includes them).
9328
+ _loki_resume_snapshot() {
9329
+ local carried=""
9330
+ if ! _loki_snapshot_or_fail_closed union; then
9331
+ log_warn "Could not add your current untracked files to the pre-existing list; this session will commit nothing (review and commit manually)"
9332
+ return 0
9333
+ fi
9334
+ _LOKI_SNAPSHOT_THIS_RUN=1
9335
+ carried="$(_loki_nul_names .loki/state/session-created.z)"
9336
+ if [ -n "$carried" ]; then
9337
+ log_info "Carried over as this session's work, not adopted as your files (created by the unfinished previous session): $carried"
9338
+ fi
9339
+ }
9340
+
9341
+ # _loki_nul_names <file>: the entries of a NUL-delimited repo-top-relative list
9342
+ # that exist now, comma-separated, the first 20 then "(and N more)".
9343
+ _loki_nul_names() {
9344
+ local p top="" n=0 out=""
9345
+ [ -s "$1" ] || return 0
9346
+ top="$(git rev-parse --show-toplevel 2>/dev/null)" || return 0
9347
+ while IFS= read -r -d '' p; do
9348
+ if [ -e "$top/$p" ] || [ -L "$top/$p" ]; then
9349
+ n=$((n + 1))
9350
+ if [ "$n" -le 20 ]; then out="${out}${out:+, }${p}"; fi
9351
+ fi
9352
+ done < "$1"
9353
+ if [ "$n" -gt 20 ]; then out="${out} (and $((n - 20)) more)"; fi
9354
+ printf '%s' "$out"
9355
+ }
9356
+
9149
9357
  # RUN-25 iter 21 (Wave D #2): the two secret matchers now live in one sourceable
9150
9358
  # lib so the commit gate (here) and the completion evidence gate (completion-
9151
9359
  # council.sh) share a single implementation. Sourced with a guard so a re-source
@@ -9210,8 +9418,37 @@ commit_session_changes() {
9210
9418
  ':!*.key' ':!*.pem' ':!*.p12' ':!*.keystore' \
9211
9419
  ':!id_rsa*' ':!*.token' ':!credentials*' 2>/dev/null || true
9212
9420
 
9213
- # Nothing staged = clean no-op, never an error.
9421
+ # Unstage exactly the paths recorded as untracked or gitignored when the
9422
+ # session started (setup_agent_branch; a "dir/" entry covers its subtree):
9423
+ # they are the user's, not this session's work, and committing them here
9424
+ # would delete them from disk on a later checkout of the base (an agent
9425
+ # rewrite of .gitignore can expose ignored files to `git add -A`).
9426
+ # Agent-created files stay staged. Skip an empty snapshot: an
9427
+ # empty --pathspec-from-file resets the WHOLE index. No snapshot (older
9428
+ # session): behave as before. If the unstage fails (git < 2.25), commit
9429
+ # nothing rather than sweep the user's files in.
9430
+ if [ -f "$PWD/.loki/state/preexisting-untracked.failed" ]; then
9431
+ git reset -q >/dev/null 2>&1 || true
9432
+ log_warn "Left uncommitted: could not record your pre-existing untracked files at session start (the snapshot needs git 2.18+, excluding them needs 2.25+). Review and commit manually."
9433
+ return 0
9434
+ fi
9435
+ local preexisting="$PWD/.loki/state/preexisting-untracked.z" top=""
9436
+ if [ -s "$preexisting" ]; then
9437
+ if ! { top="$(git rev-parse --show-toplevel 2>/dev/null)" \
9438
+ && git -C "$top" --literal-pathspecs reset -q \
9439
+ --pathspec-from-file="$preexisting" --pathspec-file-nul >/dev/null 2>&1; }; then
9440
+ git reset -q >/dev/null 2>&1 || true
9441
+ log_warn "Left uncommitted: could not exclude your pre-existing untracked files from the session commit (needs git 2.25+). Review and commit manually."
9442
+ return 0
9443
+ fi
9444
+ fi
9445
+
9446
+ # Nothing staged = clean no-op, never an error. A session that ends here or
9447
+ # commits below ended normally: its session-created record is spent (what
9448
+ # it listed is committed, or was never committable), so the next session
9449
+ # starts clean.
9214
9450
  if git diff --cached --quiet 2>/dev/null; then
9451
+ rm -f .loki/state/session-created.z 2>/dev/null
9215
9452
  return 0
9216
9453
  fi
9217
9454
 
@@ -9253,7 +9490,9 @@ commit_session_changes() {
9253
9490
  return 0
9254
9491
  fi
9255
9492
 
9256
- git commit -m "Loki Mode session changes (${ITERATION_COUNT:-0} iterations, result=${result:-0})" 2>/dev/null || true
9493
+ if git commit -m "Loki Mode session changes (${ITERATION_COUNT:-0} iterations, result=${result:-0})" 2>/dev/null; then
9494
+ rm -f .loki/state/session-created.z 2>/dev/null
9495
+ fi
9257
9496
  audit_agent_action "git_commit" "Committed session changes" "iterations=${ITERATION_COUNT:-0},result=${result:-0}" || true
9258
9497
  return 0
9259
9498
  }
@@ -10227,7 +10466,8 @@ _loki_supervised_build_result_passes() {
10227
10466
  local result_file="$1"
10228
10467
  ! loki_is_supervised_simple_web && return 0
10229
10468
  [ -s "$result_file" ] || return 1
10230
- python3 - "$result_file" <<'PYEOF' 2>/dev/null
10469
+ python3 -E - "$result_file" <<'PYEOF' 2>/dev/null
10470
+ import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
10231
10471
  import json
10232
10472
  import sys
10233
10473
 
@@ -10712,7 +10952,8 @@ SAEMPTYEOF
10712
10952
  # not product-specific copy or design opinions.
10713
10953
  local _ui_contract_out="" _ui_contract_rc=0
10714
10954
  _ui_contract_out=$(
10715
- _LOKI_CHANGED_FILES="$changed_files" python3 - "${TARGET_DIR:-.}" <<'PYEOF' 2>/dev/null
10955
+ _LOKI_CHANGED_FILES="$changed_files" python3 -E - "${TARGET_DIR:-.}" <<'PYEOF' 2>/dev/null
10956
+ import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
10716
10957
  import os
10717
10958
  import re
10718
10959
  import sys
@@ -10778,8 +11019,14 @@ PYEOF
10778
11019
  [ -f "${TARGET_DIR:-.}/$f" ] || continue
10779
11020
  total_checked=$((total_checked + 1))
10780
11021
  local _py_compile_rc=0
11022
+ # Same check as `python3 -m py_compile` (importlib's source_to_code
11023
+ # is compile(bytes, path, "exec", dont_inherit=True)), but it
11024
+ # writes no __pycache__/*.pyc into the user's repo, where the
11025
+ # session commit would pick it up. -E and the sys.path filter
11026
+ # (D7): the repo under check must not supply modules to the gate.
10781
11027
  LOKI_DEADLINE_IDLE_TIMEOUT=0 _loki_with_deadline "$gate_timeout" \
10782
- python3 -m py_compile "${TARGET_DIR:-.}/$f" 2>&1 || _py_compile_rc=$?
11028
+ python3 -E -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]; compile(open(sys.argv[1], "rb").read(), sys.argv[1], "exec", dont_inherit=True)' \
11029
+ "${TARGET_DIR:-.}/$f" 2>&1 || _py_compile_rc=$?
10783
11030
  if [ "$_py_compile_rc" -ne 0 ]; then
10784
11031
  findings=$((findings + 1))
10785
11032
  if [ "$_py_compile_rc" -eq 124 ]; then
@@ -11096,7 +11343,7 @@ SECEMPTY
11096
11343
 
11097
11344
  # Run the scanner. exit 0 = no findings, 1 = findings, 2 = bad input.
11098
11345
  local raw rc=0
11099
- raw=$(python3 "$scanner" "${TARGET_DIR:-.}" --json 2>/dev/null) || rc=$?
11346
+ raw=$(python3 -E "$scanner" "${TARGET_DIR:-.}" --json 2>/dev/null) || rc=$?
11100
11347
  if [ "$rc" -eq 2 ] || [ -z "$raw" ]; then
11101
11348
  cat > "$out_file" << 'SECEMPTY'
11102
11349
  {"rules_version":null,"findings":[],"summary":{"total":0,"by_severity":{}},"skipped":"scanner-error"}
@@ -11110,7 +11357,7 @@ SECEMPTY
11110
11357
  # It prints a final line: ACTIVE_HIGH=<n>\tACTIVE_TOTAL=<n>\tWAIVED=<n>
11111
11358
  # and writes the enriched receipt (findings carry a "waived" bool).
11112
11359
  local verdict
11113
- verdict=$(_SEC_RAW="$raw" _SEC_WAIVERS="$waivers_file" _SEC_OUT="$out_file" python3 -c '
11360
+ verdict=$(_SEC_RAW="$raw" _SEC_WAIVERS="$waivers_file" _SEC_OUT="$out_file" python3 -E -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
11114
11361
  import json, os, sys
11115
11362
  raw = os.environ.get("_SEC_RAW", "")
11116
11363
  waivers_file = os.environ.get("_SEC_WAIVERS", "")
@@ -11610,7 +11857,7 @@ measure_test_coverage() {
11610
11857
  --coverage.reportsDirectory=.loki/quality/vitest-cov >/dev/null 2>&1) || true
11611
11858
  local f="$target_dir/.loki/quality/vitest-cov/coverage-summary.json"
11612
11859
  if [ -f "$f" ]; then
11613
- COVERAGE_PCT=$(_LOKI_COV_F="$f" python3 -c "
11860
+ COVERAGE_PCT=$(_LOKI_COV_F="$f" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
11614
11861
  import json, os, sys
11615
11862
  try:
11616
11863
  d=json.load(open(os.environ['_LOKI_COV_F']))
@@ -11629,7 +11876,7 @@ except Exception:
11629
11876
  --coverageDirectory=.loki/quality/jest-cov --passWithNoTests >/dev/null 2>&1) || true
11630
11877
  local f="$target_dir/.loki/quality/jest-cov/coverage-summary.json"
11631
11878
  if [ -f "$f" ]; then
11632
- COVERAGE_PCT=$(_LOKI_COV_F="$f" python3 -c "
11879
+ COVERAGE_PCT=$(_LOKI_COV_F="$f" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
11633
11880
  import json, os, sys
11634
11881
  try:
11635
11882
  d=json.load(open(os.environ['_LOKI_COV_F']))
@@ -11649,7 +11896,7 @@ except Exception:
11649
11896
  _loki_run_pytest_with_timeout "$target_dir" \
11650
11897
  --cov --cov-report="json:$pyc_json" -q >/dev/null 2>&1 || true
11651
11898
  if [ -f "$pyc_json" ]; then
11652
- COVERAGE_PCT=$(_LOKI_COV_F="$pyc_json" python3 -c "
11899
+ COVERAGE_PCT=$(_LOKI_COV_F="$pyc_json" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
11653
11900
  import json, os, sys
11654
11901
  try:
11655
11902
  d=json.load(open(os.environ['_LOKI_COV_F']))
@@ -11689,7 +11936,7 @@ except Exception:
11689
11936
  local out
11690
11937
  out=$(cd "$target_dir" && timeout "$gate_timeout" cargo llvm-cov --json 2>/dev/null) || true
11691
11938
  if [ -n "$out" ]; then
11692
- COVERAGE_PCT=$(_LOKI_COV_JSON="$out" python3 -c "
11939
+ COVERAGE_PCT=$(_LOKI_COV_JSON="$out" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
11693
11940
  import json, os, sys
11694
11941
  try:
11695
11942
  d=json.loads(os.environ['_LOKI_COV_JSON'])
@@ -12412,7 +12659,7 @@ TREOF
12412
12659
  local cov_below=false
12413
12660
  if [ "$COVERAGE_MEASURED" = "true" ] && [ -n "$COVERAGE_PCT" ]; then
12414
12661
  # Float-safe compare via python3 (pct may be e.g. 87.5).
12415
- if _LOKI_COV_PCT="$COVERAGE_PCT" _LOKI_COV_MIN="$min_coverage" python3 -c "
12662
+ if _LOKI_COV_PCT="$COVERAGE_PCT" _LOKI_COV_MIN="$min_coverage" python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
12416
12663
  import os, sys
12417
12664
  try:
12418
12665
  pct=float(os.environ['_LOKI_COV_PCT']); mn=float(os.environ['_LOKI_COV_MIN'])
@@ -12437,7 +12684,7 @@ sys.exit(0 if pct < mn else 1)
12437
12684
  _LOKI_COV_BLOCKED="$coverage_block" \
12438
12685
  _LOKI_COV_RUNNER="$test_runner" \
12439
12686
  _LOKI_COV_OUT="$quality_dir/coverage.json" \
12440
- python3 -c "
12687
+ python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
12441
12688
  import json, os, tempfile
12442
12689
  out=os.environ['_LOKI_COV_OUT']
12443
12690
  measured = os.environ.get('_LOKI_COV_MEASURED','false') == 'true'
@@ -12498,7 +12745,7 @@ os.replace(tmp, out)
12498
12745
  _LOKI_COV_BLOCKED="false" \
12499
12746
  _LOKI_COV_RUNNER="$test_runner" \
12500
12747
  _LOKI_COV_OUT="$quality_dir/coverage.json" \
12501
- python3 -c "
12748
+ python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
12502
12749
  import json, os, tempfile
12503
12750
  out=os.environ['_LOKI_COV_OUT']
12504
12751
  measured = os.environ.get('_LOKI_COV_MEASURED','false') == 'true'
@@ -12531,9 +12778,19 @@ os.replace(tmp, out)
12531
12778
  fi
12532
12779
 
12533
12780
  if [ "$test_passed" = "true" ]; then
12534
- touch "$quality_dir/unit-tests.pass"
12535
12781
  rm -f "$loki_dir/signals/TESTS_FAILED" 2>/dev/null || true
12536
- log_info "Test suite gate: $test_runner passed"
12782
+ # BACKLOG 55: the #82 zero-test run exited 0 but proved nothing. It is
12783
+ # inconclusive, so it must not leave the pass marker the receipt reads
12784
+ # as "unit_tests passed" (proof-generator.py _collect_quality_gates).
12785
+ # Still non-blocking (return 0 below) and still not a failure (no
12786
+ # TESTS_FAILED), so the council decides it, per the #82 design.
12787
+ if [ "$_tr_zero_tests" = "true" ]; then
12788
+ rm -f "$quality_dir/unit-tests.pass" 2>/dev/null || true
12789
+ log_warn "Test suite gate: $test_runner ran zero tests -- inconclusive (not passed, not failed)"
12790
+ else
12791
+ touch "$quality_dir/unit-tests.pass"
12792
+ log_info "Test suite gate: $test_runner passed"
12793
+ fi
12537
12794
  # Coverage block is distinct from tests-red: tests passed, but enforced
12538
12795
  # coverage is below threshold. Return nonzero to gate WITHOUT writing the
12539
12796
  # TESTS_FAILED signal or removing unit-tests.pass.
@@ -12767,7 +13024,7 @@ run_doc_quality_gate() {
12767
13024
  local manifest="$project_dir/.loki/docs/docs-manifest.json"
12768
13025
  if [ -f "$manifest" ]; then
12769
13026
  local doc_sha
12770
- doc_sha=$(python3 -c "import json; print(json.load(open('$manifest')).get('git_sha', ''))" 2>/dev/null)
13027
+ doc_sha=$(python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json; print(json.load(open('$manifest')).get('git_sha', ''))" 2>/dev/null)
12771
13028
  if [ -n "$doc_sha" ]; then
12772
13029
  local behind
12773
13030
  behind=$(git -C "$project_dir" rev-list --count "$doc_sha..HEAD" 2>/dev/null || echo "0")
@@ -13198,7 +13455,7 @@ enforce_lsp_diagnostics() {
13198
13455
  fi
13199
13456
 
13200
13457
  if [ -f "$lsp_file" ]; then
13201
- verdict=$(_LOKI_LSP_FILE="$lsp_file" python3 -c '
13458
+ verdict=$(_LOKI_LSP_FILE="$lsp_file" python3 -E -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
13202
13459
  import json, os, sys
13203
13460
  try:
13204
13461
  with open(os.environ["_LOKI_LSP_FILE"], encoding="utf-8") as handle:
@@ -13537,7 +13794,8 @@ council_verdicts_to_txt_files() {
13537
13794
  local out_dir_env="$review_dir"
13538
13795
  export LOKI_COUNCIL_OUT_DIR="$out_dir_env"
13539
13796
  export LOKI_COUNCIL_VERDICTS_JSON="$verdicts_json"
13540
- python3 << 'COUNCIL_WRITE'
13797
+ python3 -E - << 'COUNCIL_WRITE'
13798
+ import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
13541
13799
  import json
13542
13800
  import os
13543
13801
  import re
@@ -13626,6 +13884,9 @@ _run_managed_review_council() {
13626
13884
 
13627
13885
  local result_json
13628
13886
  result_json=$(python3 << 'MANAGED_REVIEW' 2>&1
13887
+ # Drops the cwd (the agent's repo) from sys.path. No -E here: the
13888
+ # LOKI_MANAGED_REVIEW_FAKE_MODULE test hook below is found through PYTHONPATH.
13889
+ import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
13629
13890
  import json
13630
13891
  import os
13631
13892
  import sys
@@ -13724,7 +13985,7 @@ MANAGED_REVIEW
13724
13985
  fi
13725
13986
 
13726
13987
  local status
13727
- status=$(printf '%s' "$result_json" | python3 -c "import json,sys; d=json.loads(sys.stdin.read() or '{}'); print(d.get('status',''))" 2>/dev/null || echo "")
13988
+ status=$(printf '%s' "$result_json" | python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json,sys; d=json.loads(sys.stdin.read() or '{}'); print(d.get('status',''))" 2>/dev/null || echo "")
13728
13989
 
13729
13990
  if [ "$status" != "ok" ]; then
13730
13991
  local reason
@@ -16384,7 +16645,8 @@ REVIEW_WAVE_TIMING
16384
16645
  export LOKI_REVIEW_AGG_QSCORE="$quality_score"
16385
16646
  export LOKI_REVIEW_AGG_QMED="$nonblocking_medium"
16386
16647
  export LOKI_REVIEW_AGG_QLOW="$nonblocking_low"
16387
- python3 << 'AGG_SCRIPT'
16648
+ python3 -E - << 'AGG_SCRIPT'
16649
+ import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
16388
16650
  import json, os
16389
16651
  result = {
16390
16652
  "review_id": os.environ["LOKI_REVIEW_AGG_ID"],
@@ -16527,7 +16789,8 @@ AGG_SCRIPT
16527
16789
 
16528
16790
  _LOKI_DA_AGG_FILE="$review_dir/$review_id/aggregate.json" \
16529
16791
  _LOKI_DA_STATUS="$da_status" _LOKI_DA_RC="$da_dispatch_rc" \
16530
- _LOKI_DA_SPECULATIVE="$da_speculative" python3 <<'DA_AGG_PATCH' 2>/dev/null || true
16792
+ _LOKI_DA_SPECULATIVE="$da_speculative" python3 -E - <<'DA_AGG_PATCH' 2>/dev/null || true
16793
+ import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
16531
16794
  import json
16532
16795
  import os
16533
16796
 
@@ -17895,7 +18158,7 @@ is_completed() {
17895
18158
  # Check orchestrator state
17896
18159
  if [ -f ".loki/state/orchestrator.json" ]; then
17897
18160
  if command -v python3 &> /dev/null; then
17898
- local phase=$(python3 -c "import json; print(json.load(open('.loki/state/orchestrator.json')).get('currentPhase', ''))" 2>/dev/null || echo "")
18161
+ local phase=$(python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]; import json; print(json.load(open('.loki/state/orchestrator.json')).get('currentPhase', ''))" 2>/dev/null || echo "")
17899
18162
  # Accept various completion states
17900
18163
  if [ "$phase" = "COMPLETED" ] || [ "$phase" = "complete" ] || [ "$phase" = "finalized" ] || [ "$phase" = "growth-loop" ]; then
17901
18164
  return 0
@@ -19535,7 +19798,11 @@ load_state() {
19535
19798
  if command -v python3 &> /dev/null; then
19536
19799
  # BUG-ST-006: Validate checkpoint integrity before loading state
19537
19800
  local state_valid
19538
- state_valid=$(LOKI_STATE_FILE="$state_file" python3 -c "
19801
+ # -E and the sys.path filter (D7): the cwd is the target repo, and a
19802
+ # committed json.py must not decide the loaded iteration count
19803
+ # (a fake non-zero count would skip the iteration-0 evidence drop).
19804
+ state_valid=$(LOKI_STATE_FILE="$state_file" python3 -E -c "
19805
+ import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
19539
19806
  import json, os, sys
19540
19807
  try:
19541
19808
  with open(os.environ['LOKI_STATE_FILE']) as f:
@@ -19561,15 +19828,20 @@ except (json.JSONDecodeError, KeyError, TypeError, OSError):
19561
19828
  ITERATION_COUNT=0
19562
19829
  # Back up corrupted state file for diagnosis
19563
19830
  mv "$state_file" "${state_file}.corrupt.$(date +%s)" 2>/dev/null || true
19831
+ # Starts at iteration 0: drop the previous session's test
19832
+ # evidence, as the iteration-0 block at the end does.
19833
+ rm -f "${TARGET_DIR:-.}/.loki/quality/.test-results.iter" \
19834
+ "${TARGET_DIR:-.}/.loki/quality/unit-tests.pass" \
19835
+ "${TARGET_DIR:-.}/.loki/quality/test-results.json" 2>/dev/null || true
19564
19836
  return
19565
19837
  fi
19566
19838
 
19567
19839
  # Load retry count, iteration count, and status from previous session
19568
19840
  local prev_status
19569
- prev_status=$(LOKI_STATE_FILE="$state_file" python3 -c "import json, os; print(json.load(open(os.environ['LOKI_STATE_FILE'])).get('status', 'unknown'))" 2>/dev/null || echo "unknown")
19570
- RETRY_COUNT=$(LOKI_STATE_FILE="$state_file" python3 -c "import json, os; print(json.load(open(os.environ['LOKI_STATE_FILE'])).get('retryCount', 0))" 2>/dev/null || echo "0")
19841
+ prev_status=$(LOKI_STATE_FILE="$state_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['LOKI_STATE_FILE'])).get('status', 'unknown'))" 2>/dev/null || echo "unknown")
19842
+ RETRY_COUNT=$(LOKI_STATE_FILE="$state_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['LOKI_STATE_FILE'])).get('retryCount', 0))" 2>/dev/null || echo "0")
19571
19843
  # BUG-RUN-003: Restore ITERATION_COUNT from persisted state
19572
- ITERATION_COUNT=$(LOKI_STATE_FILE="$state_file" python3 -c "import json, os; print(json.load(open(os.environ['LOKI_STATE_FILE'])).get('iterationCount', 0))" 2>/dev/null || echo "0")
19844
+ ITERATION_COUNT=$(LOKI_STATE_FILE="$state_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['LOKI_STATE_FILE'])).get('iterationCount', 0))" 2>/dev/null || echo "0")
19573
19845
 
19574
19846
  # Reset retry count + iteration count if previous session ended in a
19575
19847
  # terminal state. A fresh `loki start` after a terminal run is a NEW
@@ -19630,6 +19902,18 @@ except (json.JSONDecodeError, KeyError, TypeError, OSError):
19630
19902
  else
19631
19903
  RETRY_COUNT=0
19632
19904
  fi
19905
+ # A session that starts at iteration 0 has run nothing yet, so a leftover
19906
+ # freshness marker or unit-tests.pass is a previous session's evidence.
19907
+ # Iterations restart at 0, so keeping them would let an old pass:true read
19908
+ # as this iteration's result.
19909
+ if [ "${ITERATION_COUNT:-0}" = "0" ]; then
19910
+ # Same path the writer (enforce_test_coverage) and the freshness
19911
+ # readers use.
19912
+ local _q="${TARGET_DIR:-.}/.loki/quality"
19913
+ # test-results.json too: with the marker gone the receipt's quality
19914
+ # gates fall back to its status, reporting a previous session's run.
19915
+ rm -f "$_q/.test-results.iter" "$_q/unit-tests.pass" "$_q/test-results.json" 2>/dev/null || true
19916
+ fi
19633
19917
  }
19634
19918
 
19635
19919
  # Load tasks from queue files for prompt injection
@@ -21909,7 +22193,8 @@ _loki_supervised_completion_gates_pass() {
21909
22193
  local test_iteration="$quality_dir/.test-results.iter"
21910
22194
  if [ ! -s "$test_results" ] || [ ! -s "$test_iteration" ] \
21911
22195
  || [ "$(tr -d '[:space:]' < "$test_iteration" 2>/dev/null)" != "${ITERATION_COUNT:-0}" ] \
21912
- || ! python3 - "$test_results" <<'PYEOF' 2>/dev/null
22196
+ || ! python3 -E - "$test_results" <<'PYEOF' 2>/dev/null
22197
+ import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
21913
22198
  import json
21914
22199
  import sys
21915
22200
 
@@ -23859,6 +24144,12 @@ if __name__ == "__main__":
23859
24144
  # v7.5.12: Provider invocation finished (or was killed by trap).
23860
24145
  LOKI_PROVIDER_ACTIVE=0
23861
24146
 
24147
+ # Record the files this session has created so far. A pod loss is a
24148
+ # SIGKILL (no trap runs), so this per-turn record is what lets the
24149
+ # resumed session commit them. One git status; no-op without a
24150
+ # session snapshot.
24151
+ _loki_record_session_created || true
24152
+
23862
24153
  echo ""
23863
24154
  echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
23864
24155
  echo ""
@@ -26159,6 +26450,10 @@ cleanup() {
26159
26450
  fi
26160
26451
  # v7.5.12: Kill any running provider pipeline first, before slow cleanup.
26161
26452
  kill_provider_child 2>/dev/null || true
26453
+ # This exit makes no session commit: record the files this session
26454
+ # created (after the provider is gone, so none are added later), so a
26455
+ # resume commits them instead of adopting them as the user's.
26456
+ _loki_record_session_created || true
26162
26457
  rm -f "$loki_dir/STOP" "$loki_dir/PAUSE" "$loki_dir/PAUSED.md" 2>/dev/null
26163
26458
  # UT2-13: Clear cli-provider marker on session end.
26164
26459
  rm -f "$loki_dir/state/cli-provider" 2>/dev/null || true
@@ -26233,6 +26528,8 @@ except (json.JSONDecodeError, OSError): pass
26233
26528
  log_warn "Loki Mode interrupted -- shutting down (double Ctrl+C)"
26234
26529
  # v7.5.12: Kill provider pipeline immediately so we don't wait on it.
26235
26530
  kill_provider_child 2>/dev/null || true
26531
+ # No session commit on this exit either (see the branch above).
26532
+ _loki_record_session_created || true
26236
26533
  # Write STOP signal so any peer processes (dashboard, etc.) also stop.
26237
26534
  mkdir -p "$loki_dir" 2>/dev/null && touch "$loki_dir/STOP" 2>/dev/null || true
26238
26535
  if type app_runner_cleanup &>/dev/null; then
@@ -27102,7 +27399,7 @@ main() {
27102
27399
  local _terminal_status=""
27103
27400
  local _terminal_state_file
27104
27401
  _terminal_state_file="$(_loki_state_file)"
27105
- _terminal_status=$(LOKI_STATE_FILE="$_terminal_state_file" python3 -c "import json, os; print(json.load(open(os.environ['LOKI_STATE_FILE'])).get('status',''))" 2>/dev/null || true)
27402
+ _terminal_status=$(LOKI_STATE_FILE="$_terminal_state_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['LOKI_STATE_FILE'])).get('status',''))" 2>/dev/null || true)
27106
27403
  case "$_terminal_status" in
27107
27404
  deterministic_gates_passed|council_approved|council_force_approved|completion_promise_fulfilled|reuse_already_satisfied)
27108
27405
  if [ "$result" = "0" ]; then
@@ -27163,7 +27460,7 @@ main() {
27163
27460
  # Refresh its durable files against the final HEAD without notifying twice.
27164
27461
  local _completion_file="${TARGET_DIR:-.}/.loki/state/completion.json"
27165
27462
  local _completion_outcome=""
27166
- _completion_outcome=$(LOKI_COMPLETION_FILE="$_completion_file" python3 -c '
27463
+ _completion_outcome=$(LOKI_COMPLETION_FILE="$_completion_file" python3 -E -c 'import sys; sys.path[:] = [p for p in sys.path if p not in ("", ".")]
27167
27464
  import json, os
27168
27465
  try:
27169
27466
  value = json.load(open(os.environ["LOKI_COMPLETION_FILE"], encoding="utf-8")).get("outcome", "")
@@ -27288,7 +27585,7 @@ except Exception:
27288
27585
  if [ "${LOKI_DURABLE_STATE:-0}" = "1" ]; then
27289
27586
  local _final_status _final_state_file
27290
27587
  _final_state_file="$(_loki_state_file)"
27291
- _final_status=$(LOKI_STATE_FILE="$_final_state_file" python3 -c "import json, os; print(json.load(open(os.environ['LOKI_STATE_FILE'])).get('status','unknown'))" 2>/dev/null || echo "unknown")
27588
+ _final_status=$(LOKI_STATE_FILE="$_final_state_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['LOKI_STATE_FILE'])).get('status','unknown'))" 2>/dev/null || echo "unknown")
27292
27589
  case "$_final_status" in
27293
27590
  council_approved|council_force_approved|deterministic_gates_passed|completion_promise_fulfilled|paused|interrupted|stopped)
27294
27591
  result=0 ;;