loki-mode 9.50.2 → 9.50.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/SKILL.md CHANGED
@@ -3,7 +3,7 @@ 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.50.2
6
+ # Loki Mode v9.50.4
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
@@ -470,4 +470,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
470
470
 
471
471
  ---
472
472
 
473
- **v9.50.2 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
473
+ **v9.50.4 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 9.50.2
1
+ 9.50.4
@@ -1536,11 +1536,13 @@ def _build_proof(args, loki_dir, target_dir, repo_root):
1536
1536
  "honesty": honesty,
1537
1537
  }
1538
1538
 
1539
- # Top-level mirror for the intervention axis. trust_trajectory.py:145 already
1540
- # reads proof["interventions"] and documents that no writer exists yet; this
1541
- # is that writer. Mirrored (not moved) for the same back-compat reason the
1542
- # other flat keys are mirrored. Only ever set when actually measured, so the
1543
- # axis stays honestly "unavailable" rather than showing a fabricated zero.
1539
+ # Top-level mirror for the intervention axis. _interventions_value in
1540
+ # trust_trajectory.py reads proof["interventions"]; the counter itself is
1541
+ # written by handle_pause in autonomy/run.sh, which increments
1542
+ # .loki/state/interventions.json once per blocking pause. Mirrored (not
1543
+ # moved) for the same back-compat reason the other flat keys are mirrored.
1544
+ # Only ever set when actually measured, so the axis stays honestly
1545
+ # "unavailable" rather than showing a fabricated zero.
1544
1546
  if isinstance(journey, dict) and isinstance(journey.get("interventions"), int):
1545
1547
  proof["interventions"] = journey["interventions"]
1546
1548
 
package/autonomy/run.sh CHANGED
@@ -4332,6 +4332,63 @@ else:
4332
4332
  ' 2>/dev/null || true
4333
4333
  }
4334
4334
 
4335
+ # _loki_receipt_facts <loki_dir>
4336
+ #
4337
+ # Echo three tab-separated facts about THIS run's Evidence Receipt, or nothing
4338
+ # at all: "<run_id>\t<proof_dir>\t<headline>".
4339
+ #
4340
+ # WHY THIS EXISTS (#209). The receipt is generated automatically and opt-OUT
4341
+ # (LOKI_PROOF defaults to 1), but the only place its location ever reached a
4342
+ # user was the TTFV first-run block, which fires solely on a zero-config first
4343
+ # run. A user who does not already know `loki proof` exists could therefore
4344
+ # finish a successful run without ever learning a checkable receipt was written.
4345
+ #
4346
+ # HONESTY RULES, deliberately fail-silent:
4347
+ # - Resolved from the PERSISTED pointer (.loki/state/last-proof-id.txt),
4348
+ # never newest-by-mtime, so it names the run the generator actually wrote.
4349
+ # - Emits NOTHING unless the pointer, the proof dir, and proof.json all exist.
4350
+ # the summary builder (build_completion_summary) is itself invoked mid-pause
4351
+ # before the teardown receipt on the success path, where no receipt exists
4352
+ # yet -- printing a path there would be a promise, not a fact.
4353
+ # - The headline is honesty.headline, which the generator computes
4354
+ # DETERMINISTICALLY from recorded facts (VERIFIED / VERIFIED WITH GAPS /
4355
+ # NOT VERIFIED). It is never the council verdict, which the generator
4356
+ # itself labels "AI judgment, not deterministic proof". A missing or
4357
+ # unreadable headline yields an empty third field, and the callers then
4358
+ # print the path without a verdict rather than inventing one.
4359
+ # Best-effort: never fails a run.
4360
+ _loki_receipt_facts() {
4361
+ local loki_dir="${1:-}"
4362
+ [ -n "$loki_dir" ] || return 0
4363
+ local id_file="$loki_dir/state/last-proof-id.txt"
4364
+ [ -s "$id_file" ] || return 0
4365
+ local rid=""
4366
+ rid="$(cat "$id_file" 2>/dev/null || true)"
4367
+ # Confine the run id to the alphabet the generator actually mints. The
4368
+ # headline is sanitized below; the id was not, so an interior newline or a
4369
+ # tab in the pointer split the tab-separated record and handed the consumers
4370
+ # a field boundary that is not there. Reject rather than repair: a pointer
4371
+ # outside this alphabet is corrupt, and a repaired id would name a directory
4372
+ # that does not exist.
4373
+ case "$rid" in
4374
+ ''|*[!A-Za-z0-9._-]*) return 0 ;;
4375
+ esac
4376
+ [ -n "$rid" ] || return 0
4377
+ local pj="$loki_dir/proofs/$rid/proof.json"
4378
+ [ -f "$pj" ] || return 0
4379
+ local headline=""
4380
+ headline="$(python3 -c "
4381
+ import json, sys
4382
+ try:
4383
+ d = json.load(open(sys.argv[1]))
4384
+ h = (d.get('honesty') or {}).get('headline') or ''
4385
+ print(str(h).replace('\t', ' ').replace('\n', ' ').strip())
4386
+ except Exception:
4387
+ print('')" "$pj" 2>/dev/null || true)"
4388
+ printf '%s\t%s\t%s\n' "$rid" "$loki_dir/proofs/$rid" "$headline"
4389
+ return 0
4390
+ }
4391
+
4335
4392
  build_completion_summary() {
4336
4393
  local outcome="${1:-complete}"
4337
4394
  local loki_dir="${TARGET_DIR:-.}/.loki"
@@ -4366,6 +4423,20 @@ build_completion_summary() {
4366
4423
  # checkable receipt. Name it.
4367
4424
  council_force_approved) outcome_label="Completed (force-approved)"
4368
4425
  notify_title="Run complete (force-approved)" ;;
4426
+ # The three gate-stuck terminals. Spelled out one literal arm each
4427
+ # rather than as a single wildcard pattern:
4428
+ # tests/test-completion-outcome-labels.sh derives outcomes from the call
4429
+ # sites and matches `^ *<outcome>\)`, so a wildcard arm renders correctly
4430
+ # at runtime while reading as "no label arm" to the guard. Literal arms
4431
+ # keep that guard strictly literal, which is what makes a NEWLY-added
4432
+ # outcome fail there instead of shipping as a raw enum. Same reason the
4433
+ # block above names each outcome.
4434
+ gate_stuck_static_analysis) outcome_label="Stopped (static analysis gate would not clear)"
4435
+ notify_title="Run stopped (gate not clearing)" ;;
4436
+ gate_stuck_mock_integrity) outcome_label="Stopped (mock integrity gate would not clear)"
4437
+ notify_title="Run stopped (gate not clearing)" ;;
4438
+ gate_stuck_mutation_integrity) outcome_label="Stopped (mutation integrity gate would not clear)"
4439
+ notify_title="Run stopped (gate not clearing)" ;;
4369
4440
  *) outcome_label="$outcome"; notify_title="Run finished" ;;
4370
4441
  esac
4371
4442
 
@@ -4867,6 +4938,49 @@ except Exception:
4867
4938
  echo "Resolve those in the spec, then re-run."
4868
4939
  ;;
4869
4940
  esac
4941
+
4942
+ # Evidence Receipt (#209). The receipt is written automatically on every
4943
+ # run, success or failure, but its location only ever reached the user
4944
+ # through the zero-config first-run block. State it here, where every
4945
+ # terminal outcome passes, so a user who has never heard of `loki proof`
4946
+ # still learns a checkable receipt exists and how to re-check it.
4947
+ # Prints NOTHING when no receipt has been written yet (mid-pause, or the
4948
+ # first summary on the success path): a path that does not exist is a
4949
+ # promise, not a fact. The verdict line is the generator's deterministic
4950
+ # headline, omitted entirely when unreadable rather than invented.
4951
+ _cs_receipt="$(_loki_receipt_facts "$loki_dir" 2>/dev/null || true)"
4952
+ if [ -n "$_cs_receipt" ]; then
4953
+ _cs_rid="${_cs_receipt%% *}"
4954
+ _cs_rest="${_cs_receipt#* }"
4955
+ _cs_dir="${_cs_rest%% *}"
4956
+ _cs_headline="${_cs_rest#* }"
4957
+ echo ""
4958
+ echo "Evidence Receipt (this run):"
4959
+ if [ -n "$_cs_headline" ]; then
4960
+ echo " Verdict: $_cs_headline"
4961
+ fi
4962
+ # Gated on the PAGE, not the receipt. The helper gates on
4963
+ # proof.json, but the generator writes proof.json and THEN renders
4964
+ # index.html unwrapped (proof-generator.py), so a render failure
4965
+ # leaves the data present and the page absent. Naming a page that
4966
+ # is not there is the same class proof.ts:118 refuses ("worse than
4967
+ # an absent one") and `loki proof open` refuses with "Proof page
4968
+ # not found". Gate only this line: proof verify reads proof.json,
4969
+ # so the id and the re-check below still work without the page.
4970
+ if [ -f "$_cs_dir/index.html" ]; then
4971
+ echo " Receipt: $_cs_dir/index.html"
4972
+ fi
4973
+ echo " Re-check it yourself, do not take our word for it:"
4974
+ # Cwd-independent: the receipt is resolved from the run's target
4975
+ # dir, which need not be where the user is standing. A bare
4976
+ # `loki proof verify <id>` then fails for exactly the user we just
4977
+ # told to check our work.
4978
+ if [ "$(cd "$loki_dir/.." 2>/dev/null && pwd -P)" = "$(pwd -P)" ]; then
4979
+ echo " loki proof verify $_cs_rid"
4980
+ else
4981
+ echo " (cd $(cd "$loki_dir/.." 2>/dev/null && pwd -P) && loki proof verify $_cs_rid)"
4982
+ fi
4983
+ fi
4870
4984
  } > "$loki_dir/COMPLETION.txt" 2>/dev/null || true
4871
4985
 
4872
4986
  # ---- Durable machine-readable file: .loki/state/completion.json -----------
@@ -5144,6 +5258,12 @@ EOF
5144
5258
  force_stopped) _label="Stopped (not verified-complete)" ;;
5145
5259
  failed) _label="Failed" ;;
5146
5260
  intervention) _label="Needs input" ;;
5261
+ # Mirror of build_completion_summary's gate-stuck arms. Both must move
5262
+ # together or the card and COMPLETION.txt disagree on the same run.
5263
+ # Literal, not a glob, for the guard reason recorded there.
5264
+ gate_stuck_static_analysis) _label="Stopped (static analysis gate would not clear)" ;;
5265
+ gate_stuck_mock_integrity) _label="Stopped (mock integrity gate would not clear)" ;;
5266
+ gate_stuck_mutation_integrity) _label="Stopped (mutation integrity gate would not clear)" ;;
5147
5267
  *) _label="$_outcome" ;;
5148
5268
  esac
5149
5269
 
@@ -5199,6 +5319,20 @@ except Exception:
5199
5319
  echo -e "${GREEN}|${NC}"
5200
5320
  echo -e "${GREEN}|${NC} ${DIM}Review the work:${NC}"
5201
5321
  echo -e "${GREEN}|${NC} ${_review}"
5322
+ # NO Evidence Receipt line here, deliberately. The card renders from inside
5323
+ # emit_completion_summary, which is reached only from within run_autonomous
5324
+ # -- thousands of lines BEFORE the teardown that generates this run's
5325
+ # receipt. Announcing here would therefore be silent on a normal success
5326
+ # run (no proof exists yet), and on a SECOND run in the same directory it
5327
+ # would have been actively wrong before #211: .loki/state/last-proof-id.txt
5328
+ # was never cleared at run start, so the card would read the PREVIOUS run's
5329
+ # pointer and print that receipt, with that run's verdict, as though it
5330
+ # described this run. The pointer is now cleared during run init (search
5331
+ # "Same reasoning for the proof pointer"), which closes the cross-run leak,
5332
+ # but this site stays silent anyway: it renders thousands of lines before
5333
+ # THIS run's receipt exists, so it would print nothing on a normal success
5334
+ # run. The announcement lives at the teardown instead, after the final
5335
+ # generate_proof_of_run, where the pointer is guaranteed current.
5202
5336
  echo -e "${GREEN}+================================================================+${NC}"
5203
5337
  echo ""
5204
5338
  return 0
@@ -6529,6 +6663,15 @@ init_loki_dir() {
6529
6663
  mkdir -p .loki/metrics/efficiency
6530
6664
  # Clear stale metrics from previous sessions so loki metrics shows current run data (#75)
6531
6665
  rm -f .loki/metrics/efficiency/iteration-*.json 2>/dev/null || true
6666
+ # Same reasoning for the proof pointer (#211). .loki/state/last-proof-id.txt
6667
+ # is written by generate_proof_of_run and was never cleared, so a run that
6668
+ # died before generating a proof left the PREVIOUS run's id behind. Every
6669
+ # reader then resolved a receipt describing different work, and
6670
+ # the summary builder (build_completion_summary) would print it under the
6671
+ # literal heading "Evidence
6672
+ # Receipt (this run):". A receipt naming the wrong run is worse than no
6673
+ # receipt: absence reads as "no data", a stale one reads as evidence.
6674
+ rm -f .loki/state/last-proof-id.txt 2>/dev/null || true
6532
6675
  mkdir -p .loki/rules
6533
6676
  mkdir -p .loki/signals
6534
6677
 
@@ -24098,6 +24241,14 @@ EOF
24098
24241
  "gate=static_analysis" \
24099
24242
  "consecutive=$sa_count" 2>/dev/null || true
24100
24243
  save_state "${retry:-0}" "gate_stuck_static_analysis" 20 2>/dev/null || true
24244
+ # Same rule as the COUNCIL_FORCE_STOPPED terminal below
24245
+ # ("No on_run_complete: a force-stop must never open a
24246
+ # 'done' PR"): a non-verified stop never opens a PR, but
24247
+ # it MUST still write COMPLETION.txt and ping. Without
24248
+ # this, the only terminal in run_autonomous that tells
24249
+ # the user nothing is the one that stopped because a
24250
+ # gate would not clear.
24251
+ emit_completion_summary gate_stuck_static_analysis
24101
24252
  return 20
24102
24253
  fi
24103
24254
  fi
@@ -24224,6 +24375,7 @@ EOF
24224
24375
  "gate=mock_integrity" \
24225
24376
  "consecutive=$mk_count" 2>/dev/null || true
24226
24377
  save_state "${retry:-0}" "gate_stuck_mock_integrity" 20 2>/dev/null || true
24378
+ emit_completion_summary gate_stuck_mock_integrity
24227
24379
  return 20
24228
24380
  fi
24229
24381
  ;;
@@ -24272,6 +24424,7 @@ EOF
24272
24424
  "gate=mutation_integrity" \
24273
24425
  "consecutive=$mt_count" 2>/dev/null || true
24274
24426
  save_state "${retry:-0}" "gate_stuck_mutation_integrity" 20 2>/dev/null || true
24427
+ emit_completion_summary gate_stuck_mutation_integrity
24275
24428
  return 20
24276
24429
  fi
24277
24430
  fi
@@ -25728,18 +25881,45 @@ except Exception:
25728
25881
 
25729
25882
  log_header "Execution Paused"
25730
25883
  echo ""
25731
- log_info "To resume: Remove .loki/PAUSE or press Enter"
25884
+ # The keypress half of this line is only true on an interactive terminal.
25885
+ # Off a TTY (--bg, a container, a CI job) no key can be read, so advertising
25886
+ # it there tells the operator to do something that cannot work.
25887
+ if [ -t 0 ]; then
25888
+ log_info "To resume: Remove .loki/PAUSE or press Enter"
25889
+ else
25890
+ log_info "To resume: Remove .loki/PAUSE (no TTY: keypress resume unavailable)"
25891
+ fi
25732
25892
  log_info "To add instructions: echo 'your instructions' > .loki/HUMAN_INPUT.md"
25733
25893
  log_info "To stop completely: touch .loki/STOP"
25734
25894
  echo ""
25735
25895
 
25736
- # Create resume instructions file
25896
+ # Create resume instructions file.
25897
+ #
25898
+ # The resume line is built OUTSIDE the heredoc because the heredoc is quoted
25899
+ # (<< 'EOF') and must stay that way: its body contains `rm .loki/PAUSE` and
25900
+ # `touch .loki/STOP` in backticks, so unquoting to interpolate a variable
25901
+ # would execute them and delete the PAUSE file this function just wrote.
25902
+ #
25903
+ # This file is the surface a NON-INTERACTIVE operator actually reads (--bg,
25904
+ # a container, a CI job), and it was the last place still promising a
25905
+ # keypress that cannot arrive there -- the same defect the console banner
25906
+ # above already fixed. The no-TTY wording is byte-identical to that banner's
25907
+ # so one grep spans both surfaces and any future divergence is visible.
25908
+ local _resume_line
25909
+ if [ -t 0 ]; then
25910
+ _resume_line='1. **Resume**: Press Enter in terminal or `rm .loki/PAUSE`'
25911
+ else
25912
+ _resume_line='1. **Resume**: `rm .loki/PAUSE` (no TTY: keypress resume unavailable)'
25913
+ fi
25914
+
25737
25915
  cat > "$loki_dir/PAUSED.md" << 'EOF'
25738
25916
  # Loki Mode - Paused
25739
25917
 
25740
25918
  Execution is currently paused. Options:
25741
25919
 
25742
- 1. **Resume**: Press Enter in terminal or `rm .loki/PAUSE`
25920
+ EOF
25921
+ printf '%s\n' "$_resume_line" >> "$loki_dir/PAUSED.md"
25922
+ cat >> "$loki_dir/PAUSED.md" << 'EOF'
25743
25923
  2. **Add Instructions**: `echo "Focus on fixing the login bug" > .loki/HUMAN_INPUT.md`
25744
25924
  3. **Stop**: `touch .loki/STOP`
25745
25925
 
@@ -25805,8 +25985,31 @@ except Exception:
25805
25985
  break
25806
25986
  fi
25807
25987
 
25808
- # Check for any key press (non-blocking)
25809
- if read -t 1 -n 1 2>/dev/null; then
25988
+ # Check for any key press (non-blocking). GATED ON AN INTERACTIVE STDIN
25989
+ # ([ -t 0 ], the established idiom in this file) because off a TTY this
25990
+ # arm is not merely useless, it is wrong in BOTH directions:
25991
+ #
25992
+ # 1. stdin is /dev/null (--bg, a container, a CI job): the read can
25993
+ # never succeed, so the loop spins on `sleep 1` forever with nobody
25994
+ # able to press anything. That is the reported hang.
25995
+ # 2. stdin is a PIPE OR FILE THAT HAS BYTES (stdin inherited from a
25996
+ # parent, a heredoc, `< somefile`): the read SUCCEEDS on the first
25997
+ # stray byte and the next line deletes .loki/PAUSE. A gate
25998
+ # escalation that paused at GATE_PAUSE_LIMIT is then silently
25999
+ # resumed by data nobody typed -- a false resume, which is worse
26000
+ # than the hang because the run continues past a blocking gate.
26001
+ #
26002
+ # Both were reproduced directly: with bytes on stdin `read -t 1 -n 1`
26003
+ # returns 0, with /dev/null it returns non-zero.
26004
+ #
26005
+ # The human escape path is UNCHANGED. The STOP and PAUSE-removal checks
26006
+ # above are file-based, run every second, and are what the dashboard,
26007
+ # the CLI, and `rm .loki/PAUSE` already use -- so a non-interactive
26008
+ # operator keeps every way out they had. Only the keypress, which that
26009
+ # operator never had, is skipped. On a real TTY this is byte-identical.
26010
+ # No timeout is imposed: a bounded wait would invent a new terminal
26011
+ # outcome and could fail a legitimate long human pause.
26012
+ if [ -t 0 ] && read -t 1 -n 1 2>/dev/null; then
25810
26013
  rm -f "$loki_dir/PAUSE"
25811
26014
  PAUSED=false
25812
26015
  break
@@ -26980,6 +27183,56 @@ except Exception:
26980
27183
  generate_proof_of_run "$result" || true
26981
27184
  fi
26982
27185
 
27186
+ # Evidence Receipt (#209): tell the user, on screen, that a checkable
27187
+ # receipt exists and how to re-check it.
27188
+ #
27189
+ # POSITION IS LOAD-BEARING. This sits immediately after the FINAL
27190
+ # generate_proof_of_run, which is the only point where the receipt for THIS
27191
+ # run is guaranteed written and .loki/state/last-proof-id.txt is guaranteed
27192
+ # to point at it. Every earlier surface is either too early (the completion
27193
+ # card renders from inside run_autonomous, long before any proof exists) or
27194
+ # unreachable to a foreground user (COMPLETION.txt self-heals here, but only
27195
+ # a --bg launch is ever told to read it). Before #211 the pointer was never
27196
+ # cleared at run start, so announcing from an earlier site printed the
27197
+ # PREVIOUS run's receipt and verdict on a second run in the same directory.
27198
+ # Run init now clears it (search "Same reasoning for the proof pointer"), so
27199
+ # a stale pointer no longer survives into a new run; the position still
27200
+ # matters because an earlier site is simply too early for THIS run's proof.
27201
+ #
27202
+ # TTY-gated the same way print_ttfv_next_steps is above: machine output and
27203
+ # --bg stay byte-identical, and those readers already get the same facts
27204
+ # from COMPLETION.txt. Fail-silent and best-effort: prints nothing at all
27205
+ # when no receipt was written (LOKI_PROOF=0, or generation failed), and
27206
+ # never fails the run.
27207
+ if [ -t 1 ] && [ "${BACKGROUND_MODE:-false}" != "true" ]; then
27208
+ _rcpt="$(_loki_receipt_facts "${TARGET_DIR:-.}/.loki" 2>/dev/null || true)"
27209
+ if [ -n "${_rcpt:-}" ]; then
27210
+ _rcpt_id="${_rcpt%% *}"
27211
+ _rcpt_rest="${_rcpt#* }"
27212
+ _rcpt_dir="${_rcpt_rest%% *}"
27213
+ _rcpt_headline="${_rcpt_rest#* }"
27214
+ echo ""
27215
+ if [ -n "$_rcpt_headline" ]; then
27216
+ echo "Evidence Receipt for this run: $_rcpt_headline"
27217
+ else
27218
+ echo "Evidence Receipt for this run:"
27219
+ fi
27220
+ # Gated on the PAGE existing -- see the COMPLETION.txt site above.
27221
+ if [ -f "$_rcpt_dir/index.html" ]; then
27222
+ echo " $_rcpt_dir/index.html"
27223
+ fi
27224
+ echo " Re-check it yourself, do not take our word for it:"
27225
+ # Cwd-independent, same reasoning as the COMPLETION.txt site.
27226
+ _rcpt_root="$(cd "${TARGET_DIR:-.}" 2>/dev/null && pwd -P)"
27227
+ if [ "$_rcpt_root" = "$(pwd -P)" ]; then
27228
+ echo " loki proof verify $_rcpt_id"
27229
+ else
27230
+ echo " (cd $_rcpt_root && loki proof verify $_rcpt_id)"
27231
+ fi
27232
+ echo ""
27233
+ fi
27234
+ fi
27235
+
26983
27236
  # Close the teardown window here rather than after cleanup: everything below
26984
27237
  # is process reaping and file removal, while everything above is the work a
26985
27238
  # user waits on (commit, PR, summary, proof). Emitting before cleanup also
@@ -27068,7 +27321,12 @@ except Exception:
27068
27321
  # The operator raises the cap (or narrows the spec) and submits a
27069
27322
  # NEW Job -- the same remedy as max_iterations_reached, which is why
27070
27323
  # it shares that code.
27071
- failed|max_iterations_reached|max_retries_exceeded|budget_exceeded|max_duration_reached|policy_blocked|inconclusive_spec_contradiction|force_stopped)
27324
+ # gate_stuck_* is deterministic for the same reason: the same gate
27325
+ # failed for the same reason N times, so a retry reaches the same
27326
+ # verdict. It already arrived here as 20 via save_state, but only
27327
+ # by falling through `*)`, which logs it as "crash, retryable" and
27328
+ # leaves a k8s podFailurePolicy reading a value nothing asserts.
27329
+ failed|max_iterations_reached|max_retries_exceeded|budget_exceeded|max_duration_reached|policy_blocked|inconclusive_spec_contradiction|force_stopped|gate_stuck_static_analysis|gate_stuck_mock_integrity|gate_stuck_mutation_integrity)
27072
27330
  result=20 ;;
27073
27331
  *)
27074
27332
  # Unknown/running/exited terminal: leave $result as-is (nonzero on a
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "9.50.2"
10
+ __version__ = "9.50.4"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try: