loki-mode 7.103.0 → 7.104.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/SKILL.md 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 v7.103.0
6
+ # Loki Mode v7.104.0
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
@@ -408,4 +408,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
408
408
 
409
409
  ---
410
410
 
411
- **v7.103.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
411
+ **v7.104.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 7.103.0
1
+ 7.104.0
package/autonomy/loki CHANGED
@@ -15555,24 +15555,26 @@ _fable_architect = (
15555
15555
  # cross-route tests in test-plan-command.sh. If you change resolution here, change
15556
15556
  # it in claude.sh AND dashboard/server.py, and re-run those tests. The 'or' chains
15557
15557
  # mirror bash ':-' empty-string-fallthrough; allow_haiku uses an exact 'true'
15558
- # match to mirror bash [ \"\$x\" = \"true\" ]. PROVIDER_MODEL_DEVELOPMENT is opus
15559
- # by default; PROVIDER_MODEL_FAST is sonnet by default (haiku when ALLOW_HAIKU).
15558
+ # match to mirror bash [ \"\$x\" = \"true\" ]. v7.104.0: PROVIDER_MODEL_DEVELOPMENT
15559
+ # and PROVIDER_MODEL_PLANNING default to SONNET (was opus); PROVIDER_MODEL_FAST is
15560
+ # sonnet by default (haiku when ALLOW_HAIKU).
15560
15561
  _allow_haiku = (os.environ.get('LOKI_ALLOW_HAIKU', 'false') or 'false') == 'true'
15561
15562
  def _provider_model_fast():
15562
15563
  return (os.environ.get('LOKI_CLAUDE_MODEL_FAST')
15563
15564
  or os.environ.get('LOKI_MODEL_FAST')
15564
15565
  or ('haiku' if _allow_haiku else 'sonnet'))
15565
15566
  def _provider_model_development():
15567
+ # v7.104.0: CLAUDE_DEFAULT_DEVELOPMENT=sonnet (was opus). ALLOW_HAIKU no longer
15568
+ # changes dev (already sonnet); it only lowers fast to haiku.
15566
15569
  return (os.environ.get('LOKI_CLAUDE_MODEL_DEVELOPMENT')
15567
15570
  or os.environ.get('LOKI_MODEL_DEVELOPMENT')
15568
- or ('sonnet' if _allow_haiku else 'opus'))
15571
+ or 'sonnet')
15569
15572
  def _provider_model_planning():
15570
- # claude.sh:65 -> LOKI_CLAUDE_MODEL_PLANNING > LOKI_MODEL_PLANNING > opus.
15571
- # CLAUDE_DEFAULT_PLANNING is always opus (LOKI_ALLOW_HAIKU only lowers the
15572
- # development and fast defaults, not planning).
15573
+ # claude.sh:55 -> LOKI_CLAUDE_MODEL_PLANNING > LOKI_MODEL_PLANNING > sonnet.
15574
+ # v7.104.0: CLAUDE_DEFAULT_PLANNING=sonnet (was opus).
15573
15575
  return (os.environ.get('LOKI_CLAUDE_MODEL_PLANNING')
15574
15576
  or os.environ.get('LOKI_MODEL_PLANNING')
15575
- or 'opus')
15577
+ or 'sonnet')
15576
15578
  _max_tier = (os.environ.get('LOKI_MAX_TIER', '') or '').strip().lower()
15577
15579
  def _loki_clamp_alias(alias):
15578
15580
  if not _max_tier:
@@ -15593,13 +15595,12 @@ def _loki_clamp_alias(alias):
15593
15595
  # planning, sonnet->development, haiku->fast, fable->fable) and resolves that
15594
15596
  # tier through resolve_model_for_tier (claude.sh:353), which reads
15595
15597
  # PROVIDER_MODEL_PLANNING/DEVELOPMENT/FAST and then applies
15596
- # loki_apply_max_tier_clamp(model, REAL_tier). This DIFFERS from the override-
15597
- # path clamp _loki_clamp_alias above: a 'sonnet' SESSION pin dispatches OPUS
15598
- # (development tier -> PROVIDER_MODEL_DEVELOPMENT=opus on stock config), whereas
15599
- # a 'sonnet' OVERRIDE file dispatches sonnet (alias fed straight to --model).
15600
- # Quoting the alias name on the default session path was the task-568 gap (it
15601
- # understated cost ~1.7x: Sonnet quote vs Opus dispatch). The estimator must
15602
- # quote the model the runner actually dispatches.
15598
+ # loki_apply_max_tier_clamp(model, REAL_tier). v7.104.0: with the sonnet default,
15599
+ # a 'sonnet' SESSION pin now dispatches sonnet (development tier ->
15600
+ # PROVIDER_MODEL_DEVELOPMENT=sonnet), matching a 'sonnet' OVERRIDE file. The
15601
+ # estimator must quote the model the runner actually dispatches; on stock config
15602
+ # the session-pin path and override path now agree on sonnet for dev/planning
15603
+ # tiers (they diverge only when the tier defaults are overridden by env).
15603
15604
  #
15604
15605
  # SYNC: byte-faithful with run.sh's session-pin case + claude.sh
15605
15606
  # resolve_model_for_tier + loki_apply_max_tier_clamp. The same resolver lives in
@@ -15607,11 +15608,24 @@ def _loki_clamp_alias(alias):
15607
15608
  # session-pin parity matrix and the no-cap stock cross-route cases in
15608
15609
  # tests/test-model-override.sh.
15609
15610
  def _resolve_session_pin(alias):
15611
+ # v7.104.0 opus-pin fix: mirror run.sh. Post the Sonnet-5 default flip, NO
15612
+ # tier resolves to opus, so an opus SESSION pin must dispatch opus directly
15613
+ # (not route through planning->sonnet). Clamped by LOKI_MAX_TIER so the
15614
+ # ceiling still binds. sonnet/haiku pins stay on the tier route (preserving
15615
+ # the ALLOW_HAIKU gate for haiku).
15616
+ if alias == 'opus':
15617
+ if not _max_tier:
15618
+ return 'opus'
15619
+ if _max_tier == 'haiku':
15620
+ return _provider_model_fast()
15621
+ # sonnet cap: clamp opus down to the development tier model (sonnet stock).
15622
+ if _max_tier == 'sonnet':
15623
+ return _provider_model_development()
15624
+ return 'opus' # opus cap (or any higher): opus stands.
15610
15625
  # alias -> abstract tier (run.sh:12331). Unknown -> development (the runner's
15611
15626
  # '*' arm passes CURRENT_TIER through to resolve_model_for_tier, whose '*'
15612
15627
  # default is PROVIDER_MODEL_DEVELOPMENT, i.e. the development tier).
15613
15628
  _pin_tier = {
15614
- 'opus': 'planning',
15615
15629
  'sonnet': 'development',
15616
15630
  'haiku': 'fast',
15617
15631
  'fable': 'fable',
package/autonomy/run.sh CHANGED
@@ -2441,10 +2441,13 @@ get_provider_tier_param() {
2441
2441
  # resolve_model_for_tier and the estimator/dashboard.
2442
2442
  echo "opus"
2443
2443
  else
2444
- echo "opus"
2444
+ # v7.104.0: default planning to sonnet (was opus) to match
2445
+ # the sourced claude.sh default. This bare fallback only
2446
+ # fires when claude.sh is unsourced.
2447
+ echo "sonnet"
2445
2448
  fi
2446
2449
  ;;
2447
- development) echo "${PROVIDER_MODEL_DEVELOPMENT:-opus}" ;;
2450
+ development) echo "${PROVIDER_MODEL_DEVELOPMENT:-sonnet}" ;;
2448
2451
  fast) echo "${PROVIDER_MODEL_FAST:-sonnet}" ;;
2449
2452
  # fable unavailable, collapse to opus. Without this arm an
2450
2453
  # unsourced-claude.sh environment (this static fallback) would
@@ -4846,7 +4849,7 @@ _write_pricing_json() {
4846
4849
  "opus": {"input": 5.00, "output": 25.00, "label": "Opus (latest)", "provider": "claude"},
4847
4850
  "sonnet": {"input": 3.00, "output": 15.00, "label": "Sonnet (latest)", "provider": "claude"},
4848
4851
  "haiku": {"input": 1.00, "output": 5.00, "label": "Haiku (latest)", "provider": "claude"},
4849
- "gpt-5.3-codex": {"input": 1.50, "output": 12.00, "label": "GPT-5.3 Codex", "provider": "codex"}
4852
+ "gpt-5.3-codex": {"input": 1.75, "output": 14.00, "label": "GPT-5.3 Codex", "provider": "codex"}
4850
4853
  }
4851
4854
  }
4852
4855
  PRICING_EOF
@@ -10149,11 +10152,14 @@ _dispatch_reviewer() {
10149
10152
  case "${PROVIDER_NAME:-claude}" in
10150
10153
  claude)
10151
10154
  # SECURITY-REVIEW MODEL GUARD (evidence-based routing, item 4b):
10152
- # Reviewers deliberately do NOT pass --model, so they run on
10153
- # the account default model and are NEVER routed to Fable by a
10154
- # mid-flight model override or LOKI_FABLE_ARCHITECT (those only
10155
- # rewrite the iteration's tier_param, not this dispatch). This
10156
- # must stay true. The official model-config docs CONTRADICT
10155
+ # By DEFAULT reviewers pass NO --model, so they run on the account
10156
+ # default model and are NEVER routed to Fable by a mid-flight model
10157
+ # override or LOKI_FABLE_ARCHITECT (those only rewrite the
10158
+ # iteration's tier_param, not this dispatch). The ONLY way to pin the
10159
+ # reviewer model is the explicit opt-in LOKI_ADVISOR_MODEL below,
10160
+ # which honors haiku|sonnet|opus and REFUSES fable for the reason
10161
+ # stated here. This must stay true. The official model-config docs
10162
+ # CONTRADICT
10157
10163
  # routing security review to Fable: Fable's safety classifiers
10158
10164
  # refuse cybersecurity content, and in non-interactive (-p)
10159
10165
  # mode a flagged request ends the turn with stop_reason
@@ -10180,6 +10186,25 @@ _dispatch_reviewer() {
10180
10186
  # real net is commit-before-agent-wave. Opt out
10181
10187
  # LOKI_REVIEW_TOOL_GUARD=0. See loki_review_guard_denylist.
10182
10188
  local _rv_argv=("--dangerously-skip-permissions")
10189
+ # OPT-IN advisor pin (v7.104.0, LOKI_ADVISOR_MODEL): reviewers still
10190
+ # pass NO --model by default (unchanged behavior: they run on the
10191
+ # account default). When the operator/user sets LOKI_ADVISOR_MODEL to
10192
+ # an allowlisted alias, pin the trust-gate reviewer to it for a
10193
+ # stronger/consistent judge - the founder's "advisor will be opus if
10194
+ # needed or user opted". FABLE IS REFUSED here: the model-config docs
10195
+ # say Fable's safety classifiers refuse cybersecurity content and end
10196
+ # a -p turn with stop_reason "refusal", which would return no VERDICT
10197
+ # and break the unanimous-council gate. So only haiku|sonnet|opus are
10198
+ # honored; fable (or any other value) is ignored with no --model.
10199
+ if [ -n "${LOKI_ADVISOR_MODEL:-}" ]; then
10200
+ local _adv _adv_norm
10201
+ _adv="${LOKI_ADVISOR_MODEL}"
10202
+ _adv_norm="$(printf '%s' "$_adv" | tr '[:upper:]' '[:lower:]' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
10203
+ case "$_adv_norm" in
10204
+ haiku|sonnet|opus) _rv_argv+=("--model" "$_adv_norm") ;;
10205
+ *) : ;; # fable and invalid values: no --model (account default)
10206
+ esac
10207
+ fi
10183
10208
  if type loki_subcall_bare_enabled >/dev/null 2>&1 && loki_subcall_bare_enabled; then
10184
10209
  _rv_argv+=("--bare")
10185
10210
  fi
@@ -12152,7 +12177,7 @@ pricing = {
12152
12177
  'opus': {'input': 5.00, 'output': 25.00},
12153
12178
  'sonnet': {'input': 3.00, 'output': 15.00},
12154
12179
  'haiku': {'input': 1.00, 'output': 5.00},
12155
- 'gpt-5.3-codex': {'input': 1.50, 'output': 12.00},
12180
+ 'gpt-5.3-codex': {'input': 1.75, 'output': 14.00},
12156
12181
  }
12157
12182
  for f in glob.glob('${efficiency_dir}/*.json'):
12158
12183
  try:
@@ -15951,6 +15976,9 @@ except Exception as exc:
15951
15976
  # Tier selection (S0.1):
15952
15977
  # Default: pin a single tier for the whole session via LOKI_SESSION_MODEL.
15953
15978
  # Legacy: LOKI_LEGACY_TIER_SWITCHING=true restores RARV-driven rotation.
15979
+ # Reset the opus-pin flag every iteration BEFORE either branch so a value
15980
+ # from a prior pass can never leak (the legacy branch never sets it).
15981
+ local _loki_session_pin_opus=0
15954
15982
  if [ "${LOKI_LEGACY_TIER_SWITCHING:-false}" = "true" ]; then
15955
15983
  CURRENT_TIER=$(get_rarv_tier "$ITERATION_COUNT")
15956
15984
  else
@@ -15983,6 +16011,16 @@ except Exception as exc:
15983
16011
  planning|development|fast) CURRENT_TIER="$_session_pin" ;;
15984
16012
  *) CURRENT_TIER="$_session_pin" ;;
15985
16013
  esac
16014
+ # v7.104.0 opus-pin fix: post the Sonnet-5 default flip, NO tier
16015
+ # resolves to opus (planning+development+fast all default to sonnet).
16016
+ # So mapping an opus SESSION pin to the planning tier would silently
16017
+ # dispatch SONNET - a model-honesty lie ("user picks opus, gets
16018
+ # sonnet"). A concrete opus pin must dispatch opus. Record it so the
16019
+ # tier_param resolution below sets the model directly to opus (still
16020
+ # clamped by LOKI_MAX_TIER). sonnet/haiku pins stay on the tier route
16021
+ # so the LOKI_ALLOW_HAIKU gate for haiku is preserved. Mirrored in the
16022
+ # estimator (autonomy/loki) and dashboard (server.py) + parity test.
16023
+ [ "$_session_pin" = "opus" ] && _loki_session_pin_opus=1
15986
16024
  fi
15987
16025
  # Architect opt-in (LOKI_FABLE_ARCHITECT=1): route ONLY the first
15988
16026
  # iteration (the architecture/REASON pass) to Fable, then fall back to
@@ -16019,6 +16057,24 @@ except Exception as exc:
16019
16057
  export LOKI_CURRENT_TIER
16020
16058
  local rarv_phase=$(get_rarv_phase_name "$ITERATION_COUNT")
16021
16059
  local tier_param=$(get_provider_tier_param "$CURRENT_TIER")
16060
+ # v7.104.0 opus-pin fix (see the case block above): an opus SESSION pin
16061
+ # must dispatch opus, not the sonnet-defaulted planning tier. Set the
16062
+ # model directly, then apply the SAME LOKI_MAX_TIER clamp the resolver
16063
+ # uses so the operator's cost ceiling still binds (e.g. LOKI_MAX_TIER=sonnet
16064
+ # caps this back down). Only for the claude provider; other providers map
16065
+ # tiers to their own model strings.
16066
+ if [ "${PROVIDER_NAME:-claude}" = "claude" ] && [ "${_loki_session_pin_opus:-0}" = "1" ]; then
16067
+ tier_param="opus"
16068
+ if type loki_apply_max_tier_clamp >/dev/null 2>&1; then
16069
+ # Clamp at the PLANNING level (opus conceptually sits at planning):
16070
+ # loki_apply_max_tier_clamp(model, tier). Passing tier=opus would
16071
+ # make the sonnet-cap arm a no-op (it only downgrades planning/fable),
16072
+ # letting opus ESCAPE a LOKI_MAX_TIER=sonnet ceiling. tier=planning
16073
+ # gives: no-cap->opus, haiku->fast, sonnet->development(sonnet),
16074
+ # opus->opus. The ceiling wins over the pin, as everywhere else.
16075
+ tier_param="$(loki_apply_max_tier_clamp "opus" "planning")"
16076
+ fi
16077
+ fi
16022
16078
  # Mid-flight model override: the dashboard (POST /api/session/model) or a
16023
16079
  # CLI user may rewrite .loki/state/model-override between iterations to
16024
16080
  # change the model a live run uses. Read it here, after tier_param is
@@ -1019,6 +1019,14 @@ OPTIONS:
1019
1019
  Default: critical,high (one notch looser than the
1020
1020
  Loki build loop, which also blocks on medium).
1021
1021
  --no-llm Accepted for forward-compat; LLM is already off in MVP.
1022
+ --hosted Opt-in. When the embedded Autonomi Verify engine is
1023
+ usable (bun present + bundle present), fold its verdict
1024
+ fields into evidence.json under a "hosted" key. Additive
1025
+ only: the deterministic verdict and exit code are
1026
+ unchanged. If the engine is absent or unusable, behavior
1027
+ is exactly as without --hosted (fail-open, never a silent
1028
+ pass). Without --hosted, output is byte-identical to prior
1029
+ releases.
1022
1030
  -h, --help Show this help.
1023
1031
 
1024
1032
  GATES (deterministic, reproducible by construction):
@@ -1108,6 +1116,160 @@ verify_spec_drift_gate() {
1108
1116
  return 0
1109
1117
  }
1110
1118
 
1119
+ # ---------------------------------------------------------------------------
1120
+ # Hosted-engine enrichment (--hosted opt-in).
1121
+ #
1122
+ # Folds the embedded Autonomi Verify engine's verdict fields into the
1123
+ # already-emitted evidence.json, under a top-level "hosted" key. This is
1124
+ # ADDITIVE only: the deterministic bash verdict (and the process exit code)
1125
+ # stays authoritative. The embedded engine is a dependency-free bundle built
1126
+ # from the private Autonomi Verify repo, shipped under a commercial license at
1127
+ # vendor/autonomi-verify/embed.js, and run via bun -- the SAME bun-gated,
1128
+ # optional, fallback-to-current-behavior pattern run.sh uses for loki-ts.
1129
+ #
1130
+ # Fail-open is total. Every one of these degrades to "leave evidence.json
1131
+ # untouched, change nothing": bun absent, bundle absent, engine nonzero exit,
1132
+ # empty/unparseable engine output, evidence.json unreadable, or python3 absent.
1133
+ # It NEVER promotes or alters the verdict, and NEVER turns an unusable engine
1134
+ # into a silent pass -- the deterministic verdict already stands.
1135
+ # ---------------------------------------------------------------------------
1136
+ verify_hosted_enrich() {
1137
+ local out_dir="$1"
1138
+ local base_ref="${2:-}"
1139
+ local ev_path="$out_dir/evidence.json"
1140
+
1141
+ # Precondition probes -- any miss = silent fail-open (return 0, no change).
1142
+ command -v bun >/dev/null 2>&1 || {
1143
+ _verify_log "hosted: bun not found; keeping deterministic evidence (fail-open)"
1144
+ return 0
1145
+ }
1146
+ local bundle
1147
+ bundle="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/vendor/autonomi-verify/embed.js"
1148
+ [ -f "$bundle" ] || {
1149
+ _verify_log "hosted: embedded engine bundle not found; fail-open"
1150
+ return 0
1151
+ }
1152
+ [ -f "$ev_path" ] || {
1153
+ _verify_log "hosted: evidence.json not present; fail-open"
1154
+ return 0
1155
+ }
1156
+ command -v python3 >/dev/null 2>&1 || {
1157
+ _verify_log "hosted: python3 not found for fold; fail-open"
1158
+ return 0
1159
+ }
1160
+
1161
+ # Run the embedded engine read-only against the working tree. Nonzero exit
1162
+ # (genuine observation failure) or empty stdout = fail-open.
1163
+ #
1164
+ # Hand the engine the ALREADY-RESOLVED merge-base SHA (verify_diff_base()
1165
+ # resolved origin/<ref> -> <ref> -> merge-base for the deterministic path).
1166
+ # The embed's own base resolver is intentionally minimal (bare-ref
1167
+ # rev-parse, no origin/ fallback); feeding it the concrete merge-base SHA
1168
+ # makes its diff = merge_base..HEAD, identical PR semantics to the
1169
+ # deterministic verdict, and avoids a spurious "inconclusive (no_base_sha)"
1170
+ # in remote-only checkouts (CI, detached HEAD) where only origin/<ref>
1171
+ # exists. When the deterministic path could not resolve a base, pass the
1172
+ # raw ref through (best effort) and let the engine report inconclusive,
1173
+ # consistent with the deterministic inconclusive.
1174
+ local engine_base="${VERIFY_MERGE_BASE:-}"
1175
+ [ -z "$engine_base" ] && engine_base="$base_ref"
1176
+ local engine_out engine_rc
1177
+ if [ -n "$engine_base" ]; then
1178
+ engine_out="$(bun "$bundle" --dir . --base "$engine_base" 2>/dev/null)"; engine_rc=$?
1179
+ else
1180
+ engine_out="$(bun "$bundle" --dir . 2>/dev/null)"; engine_rc=$?
1181
+ fi
1182
+ if [ "$engine_rc" -ne 0 ] || [ -z "$engine_out" ]; then
1183
+ _verify_log "hosted: embedded engine unusable (rc=$engine_rc); keeping deterministic verdict (fail-open)"
1184
+ return 0
1185
+ fi
1186
+
1187
+ # Fold the engine payload into evidence.json under "hosted". A parse failure
1188
+ # at any step leaves the file byte-for-byte unchanged.
1189
+ _V_EV="$ev_path" _V_ENGINE="$engine_out" python3 - <<'PYEOF' || {
1190
+ import json, os, sys
1191
+
1192
+ ev_path = os.environ["_V_EV"]
1193
+ raw = os.environ["_V_ENGINE"]
1194
+
1195
+ try:
1196
+ engine = json.loads(raw)
1197
+ except Exception:
1198
+ sys.exit(1) # unparseable engine output -> caller fails open, no change
1199
+ if not isinstance(engine, dict):
1200
+ sys.exit(1)
1201
+
1202
+ try:
1203
+ with open(ev_path) as f:
1204
+ doc = json.load(f)
1205
+ except Exception:
1206
+ sys.exit(1) # evidence.json unreadable/corrupt -> no change
1207
+ if not isinstance(doc, dict):
1208
+ sys.exit(1)
1209
+
1210
+ # Additive only: attach the engine verdict under "hosted". The deterministic
1211
+ # "verdict"/"exit_code" fields are left untouched -- the embedded engine
1212
+ # enriches, it does not override.
1213
+ doc["hosted"] = {
1214
+ "engine": engine.get("engine", "autonomi-verify"),
1215
+ "engine_version": engine.get("engine_version"),
1216
+ "verified": engine.get("verified"),
1217
+ "inconclusive": engine.get("inconclusive"),
1218
+ "inconclusive_reason": engine.get("inconclusive_reason"),
1219
+ "evidence": engine.get("evidence"),
1220
+ "note": "additive enrichment; deterministic verdict above is authoritative",
1221
+ }
1222
+
1223
+ # Write atomically: a fold that fails mid-write must not corrupt the document
1224
+ # that the deterministic path already produced.
1225
+ tmp = ev_path + ".hosted.tmp"
1226
+ with open(tmp, "w") as f:
1227
+ json.dump(doc, f, indent=2)
1228
+ f.write("\n")
1229
+ os.replace(tmp, ev_path)
1230
+ PYEOF
1231
+ _verify_log "hosted: fold failed; deterministic evidence preserved (fail-open)"
1232
+ return 0
1233
+ }
1234
+
1235
+ # Surface a one-line human summary of the hosted enrichment for the banner.
1236
+ # Built from the engine payload via python3 (already a precondition above);
1237
+ # any parse hiccup leaves the summary empty and the banner simply omits the
1238
+ # line -- never a fabricated summary.
1239
+ VERIFY_HOSTED_SUMMARY="$(_V_ENGINE="$engine_out" _V_VERDICT="${VERIFY_VERDICT:-}" python3 - <<'PYEOF' 2>/dev/null || true
1240
+ import json, os
1241
+ try:
1242
+ e = json.loads(os.environ["_V_ENGINE"])
1243
+ ver = e.get("engine_version") or "?"
1244
+ # The authoritative verdict is the deterministic one (VERIFY_VERDICT); the
1245
+ # hosted engine is a PARTIAL signal (Phase 2 observes diff+tests only, not
1246
+ # secrets/lint/deps). It must NEVER claim "verified" when the authoritative
1247
+ # verdict is not VERIFIED -- a tool named Verify printing "-> verified" next
1248
+ # to a BLOCKED result is a fake-green-shaped surface, and this product never
1249
+ # lies about done. So the banner reports the engine's partial finding,
1250
+ # explicitly subordinated to the authoritative verdict, and never the word
1251
+ # "verified" on its own when the verdict disagrees.
1252
+ verdict = (os.environ.get("_V_VERDICT") or "").strip().upper()
1253
+ if e.get("inconclusive"):
1254
+ finding = "inconclusive (%s)" % (e.get("inconclusive_reason") or "no reason")
1255
+ elif e.get("verified"):
1256
+ finding = "no fabrication found (diff+tests)"
1257
+ else:
1258
+ finding = "fabrication evidence found"
1259
+ if verdict and verdict != "VERIFIED":
1260
+ # Authoritative verdict overrides; never imply the build is verified.
1261
+ print("Hosted: Autonomi Verify %s partial check: %s (authoritative verdict: %s; see evidence.json:hosted)" % (ver, finding, verdict))
1262
+ else:
1263
+ print("Hosted: Autonomi Verify %s -> %s (enrichment in evidence.json:hosted)" % (ver, finding))
1264
+ except Exception:
1265
+ pass
1266
+ PYEOF
1267
+ )"
1268
+
1269
+ _verify_log "hosted: folded Autonomi Verify engine fields into $ev_path"
1270
+ return 0
1271
+ }
1272
+
1111
1273
  # ---------------------------------------------------------------------------
1112
1274
  # Entry point
1113
1275
  # ---------------------------------------------------------------------------
@@ -1115,6 +1277,9 @@ verify_main() {
1115
1277
  local base_ref=""
1116
1278
  local out_dir=".loki/verify"
1117
1279
  local block_on="critical,high"
1280
+ # Opt-in hosted-engine enrichment (--hosted). Default 0 = exactly today.
1281
+ VERIFY_HOSTED=0
1282
+ VERIFY_HOSTED_SUMMARY=""
1118
1283
 
1119
1284
  # Fail-closed defaults. These globals are read at the end of this function
1120
1285
  # (the VERDICT banner and the function return code). verify_compute_verdict()
@@ -1139,6 +1304,13 @@ verify_main() {
1139
1304
  block_on="$(printf '%s' "${2:-}" | tr '[:upper:]' '[:lower:]')"; shift 2 ;;
1140
1305
  --no-llm)
1141
1306
  shift ;;
1307
+ --hosted)
1308
+ # Opt-in: when the embedded Autonomi Verify engine is usable
1309
+ # (bun present + bundle present), enrich evidence.json with the
1310
+ # engine's verdict fields. Additive only; the deterministic
1311
+ # verdict above stays authoritative for the exit code. Unset =
1312
+ # exactly today's behavior. See verify_hosted_enrich().
1313
+ VERIFY_HOSTED=1; shift ;;
1142
1314
  --) shift; break ;;
1143
1315
  -*)
1144
1316
  _verify_err "unknown option: $1"; verify_help; return $VERIFY_EXIT_ERROR ;;
@@ -1227,9 +1399,22 @@ verify_main() {
1227
1399
  return $VERIFY_EXIT_ERROR
1228
1400
  }
1229
1401
 
1402
+ # Opt-in (--hosted): fold the embedded Autonomi Verify engine's verdict
1403
+ # fields into the just-written evidence.json. Fully additive and fail-open:
1404
+ # any failure (no bun, no bundle, engine error, unparseable output) leaves
1405
+ # evidence.json exactly as emitted above and never changes the verdict or
1406
+ # exit code. Skipped entirely on the default path (VERIFY_HOSTED=0).
1407
+ if [ "${VERIFY_HOSTED:-0}" = "1" ]; then
1408
+ verify_hosted_enrich "$out_dir" "$base_ref" || true
1409
+ fi
1410
+
1230
1411
  printf 'VERDICT: %s\n' "$VERIFY_VERDICT"
1231
1412
  printf 'Evidence: %s/evidence.json\n' "$out_dir"
1232
1413
  printf 'Report: %s/report.md\n' "$out_dir"
1414
+ # --hosted only: surface the enrichment in human output so the extra signal
1415
+ # is visible without parsing JSON. Printed solely when a fold succeeded;
1416
+ # the default path never sets VERIFY_HOSTED_SUMMARY, so it stays byte-identical.
1417
+ [ -n "${VERIFY_HOSTED_SUMMARY:-}" ] && printf '%s\n' "$VERIFY_HOSTED_SUMMARY"
1233
1418
 
1234
1419
  return "$VERIFY_EXIT"
1235
1420
  }
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "7.103.0"
10
+ __version__ = "7.104.0"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try: