loki-mode 7.103.0 → 7.104.1
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 +2 -2
- package/VERSION +1 -1
- package/autonomy/loki +29 -15
- package/autonomy/run.sh +65 -9
- package/autonomy/verify.sh +185 -0
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +137 -24
- package/dashboard/static/index.html +57 -5
- package/docs/INSTALLATION.md +1 -1
- package/docs/SONNET5-DEFAULT-PLAN.md +157 -0
- package/loki-ts/data/model-pricing.json +3 -3
- package/loki-ts/dist/loki.js +105 -104
- package/mcp/__init__.py +1 -1
- package/package.json +2 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/providers/claude.sh +15 -9
- package/providers/model_catalog.json +20 -19
- package/vendor/autonomi-verify/LICENSE +39 -0
- package/vendor/autonomi-verify/README.md +58 -0
- package/vendor/autonomi-verify/embed.js +8 -0
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.
|
|
6
|
+
# Loki Mode v7.104.1
|
|
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.
|
|
411
|
+
**v7.104.1 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
7.
|
|
1
|
+
7.104.1
|
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
|
|
15559
|
-
#
|
|
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
|
|
15571
|
+
or 'sonnet')
|
|
15569
15572
|
def _provider_model_planning():
|
|
15570
|
-
# claude.sh:
|
|
15571
|
-
# CLAUDE_DEFAULT_PLANNING
|
|
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 '
|
|
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).
|
|
15597
|
-
#
|
|
15598
|
-
#
|
|
15599
|
-
#
|
|
15600
|
-
#
|
|
15601
|
-
#
|
|
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
|
-
|
|
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:-
|
|
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.
|
|
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
|
-
#
|
|
10153
|
-
#
|
|
10154
|
-
#
|
|
10155
|
-
#
|
|
10156
|
-
#
|
|
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.
|
|
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
|
package/autonomy/verify.sh
CHANGED
|
@@ -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
|
}
|