loki-mode 7.129.4 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -5
- package/SKILL.md +66 -5
- package/VERSION +1 -1
- package/autonomy/app-runner.sh +37 -0
- package/autonomy/completion-council.sh +862 -28
- package/autonomy/council-v2.sh +39 -0
- package/autonomy/crash.sh +3 -2
- package/autonomy/grill.sh +42 -0
- package/autonomy/hooks/validate-bash.sh +301 -4
- package/autonomy/lib/cockpit-render.sh +8 -2
- package/autonomy/lib/cr-rematerialize.py +101 -4
- package/autonomy/lib/deadline.py +957 -0
- package/autonomy/lib/dependency-setup.sh +205 -0
- package/autonomy/lib/done-recognition.sh +45 -0
- package/autonomy/lib/efficiency_cost.py +13 -6
- package/autonomy/lib/no_mock_scan.py +793 -0
- package/autonomy/lib/prd-enrich.sh +42 -0
- package/autonomy/lib/proof-analytics-props.py +69 -0
- package/autonomy/lib/proof-generator.py +282 -95
- package/autonomy/lib/proof-template.html +15 -12
- package/autonomy/lib/proof-verify.py +151 -102
- package/autonomy/lib/requirements_contract.py +848 -0
- package/autonomy/lib/sdk-mode.sh +103 -0
- package/autonomy/lib/secret-scan.sh +139 -0
- package/autonomy/lib/spec-expand.sh +208 -0
- package/autonomy/lib/tree_digest.py +266 -0
- package/autonomy/lib/voter-agents.sh +58 -8
- package/autonomy/lib/workspace_diff.py +124 -0
- package/autonomy/loki +189 -17
- package/autonomy/playwright-verify.sh +501 -0
- package/autonomy/prd-checklist.sh +17 -8
- package/autonomy/provider-offer.sh +111 -0
- package/autonomy/run.sh +4424 -678
- package/autonomy/sandbox.sh +42 -0
- package/autonomy/spec-interrogation.sh +148 -5
- package/autonomy/spec.sh +118 -1
- package/autonomy/telemetry.sh +133 -7
- package/autonomy/verify.sh +107 -0
- package/bin/loki +110 -3
- package/completions/_loki +10 -0
- package/completions/loki.bash +1 -1
- package/dashboard/__init__.py +1 -1
- package/dashboard/audit.py +96 -7
- package/dashboard/build_supervisor.py +1619 -0
- package/dashboard/control.py +8 -0
- package/dashboard/prompt_optimizer.py +7 -0
- package/dashboard/server.py +577 -22
- package/dashboard/static/trust.html +1 -1
- package/docs/ARCHITECTURE-OVERVIEW.md +207 -0
- package/docs/AUTONOMI-ECOSYSTEM.md +107 -0
- package/docs/INSTALLATION.md +19 -2
- package/docs/MODEL-EQUIVALENCE-HARNESS-PLAN.md +70 -0
- package/docs/PLANS-INDEX.md +33 -0
- package/docs/PRIVACY.md +29 -1
- package/docs/SIGNED-RECEIPTS.md +102 -0
- package/docs/SONNET5-DEFAULT-PLAN.md +1 -1
- package/docs/V8-ACCEPTANCE-TRIAGE-2026-07-24.md +114 -0
- package/docs/V8-AGENT-SDK-PLAN.md +692 -0
- package/docs/V8-COMPLEXITY-AUDIT-2026-07-24.md +45 -0
- package/docs/V8-MAJOR-RELEASE-PLAN.md +137 -0
- package/docs/V8-OVERNIGHT-PLAN-2026-07-25.md +82 -0
- package/docs/V8-RUNTIME-TRUTH-2026-07-25.md +232 -0
- package/docs/V8-SDK-RESEARCH-RAW.md +129 -0
- package/docs/alternative-installations.md +4 -4
- package/loki-ts/dist/loki.js +674 -285
- package/loki-ts/package.json +2 -0
- package/mcp/__init__.py +1 -1
- package/package.json +7 -6
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/providers/claude.sh +75 -0
- package/providers/codex.sh +85 -13
- package/references/sdk-mode.md +106 -0
- package/skills/model-selection.md +1 -1
- package/skills/quality-gates.md +22 -0
package/autonomy/sandbox.sh
CHANGED
|
@@ -671,10 +671,48 @@ cleanup_worktrees() {
|
|
|
671
671
|
#===============================================================================
|
|
672
672
|
# Docker Desktop Sandbox (microVM-based isolation via Docker Desktop 4.58+)
|
|
673
673
|
#===============================================================================
|
|
674
|
+
#
|
|
675
|
+
# v8.1 DEPRECATION SPLIT (binary-hosting is legacy; isolation is NOT):
|
|
676
|
+
# This path creates a microVM from Docker Desktop's "claude" sandbox TEMPLATE,
|
|
677
|
+
# which bakes the `claude` CLI binary into the VM so the agent can run inside it.
|
|
678
|
+
# With v8's SDK route, the judge/text sites (and, under LOKI_SDK_MODE, the whole
|
|
679
|
+
# engine) run pure-HTTPS with only ANTHROPIC_API_KEY -- NO in-VM `claude` binary
|
|
680
|
+
# is needed. So the *binary-hosting* purpose of this template is obsolete.
|
|
681
|
+
#
|
|
682
|
+
# What is NOT obsolete, and matters MORE with an in-process SDK: isolating
|
|
683
|
+
# untrusted CODE EXECUTION (npm install / build / test / the generated app).
|
|
684
|
+
# In-process SDK judging runs at HOST privilege, so wrapping the code-exec in a
|
|
685
|
+
# container/VM is still the right posture. The forward path is the existing
|
|
686
|
+
# `start_sandbox` container/worktree isolation (the automatic fallback at the
|
|
687
|
+
# `docker sandbox create` failure below); a future release ships a lightweight
|
|
688
|
+
# `bun + SDK` sandbox image with no bundled agent binary.
|
|
689
|
+
#
|
|
690
|
+
# This release: annotation + a one-shot deprecation notice on selection only. No
|
|
691
|
+
# behavior change, no removal. See tests/test-sandbox-deprecation.sh.
|
|
674
692
|
|
|
675
693
|
# Desktop sandbox name follows same pattern as container name
|
|
676
694
|
DESKTOP_SANDBOX_NAME="$CONTAINER_NAME"
|
|
677
695
|
|
|
696
|
+
# Emit a one-shot deprecation notice for the microVM binary-hosting path. Names
|
|
697
|
+
# the binary-hosting only and states explicitly that ISOLATION is retained (so it
|
|
698
|
+
# is never misread as "the sandbox is going away"). Best-effort + non-blocking:
|
|
699
|
+
# telemetry must never abort a sandbox start. Mirrors the cli_command_deprecated
|
|
700
|
+
# pattern (event type/source/action + a human log line). Opt out with
|
|
701
|
+
# LOKI_SANDBOX_DEPRECATION_QUIET=1.
|
|
702
|
+
_desktop_deprecation_notice() {
|
|
703
|
+
[ "${LOKI_SANDBOX_DEPRECATION_QUIET:-0}" = "1" ] && return 0
|
|
704
|
+
log_warn "DEPRECATED: the Docker Desktop microVM 'claude' template hosts the"
|
|
705
|
+
log_warn " claude BINARY in-VM, which the v8 SDK route no longer needs."
|
|
706
|
+
log_warn " Code-execution ISOLATION is RETAINED via the container/worktree"
|
|
707
|
+
log_warn " sandbox (unchanged); only the binary-hosting template is legacy."
|
|
708
|
+
# Best-effort telemetry (never abort the start on a telemetry miss).
|
|
709
|
+
if [ -f "$SKILL_DIR/events/emit.sh" ]; then
|
|
710
|
+
bash "$SKILL_DIR/events/emit.sh" sandbox cli sandbox_microvm_binary_deprecated \
|
|
711
|
+
isolation_retained=true path=docker-desktop >/dev/null 2>&1 || true
|
|
712
|
+
fi
|
|
713
|
+
return 0
|
|
714
|
+
}
|
|
715
|
+
|
|
678
716
|
# Install provider CLI inside sandbox if not pre-installed
|
|
679
717
|
_desktop_install_provider_cli() {
|
|
680
718
|
local sandbox_name="$1" provider="$2"
|
|
@@ -770,6 +808,10 @@ start_docker_desktop_sandbox() {
|
|
|
770
808
|
validate_project_dir || return 1
|
|
771
809
|
warn_missing_api_keys "$provider"
|
|
772
810
|
|
|
811
|
+
# v8.1: one-shot deprecation notice for the microVM binary-hosting path
|
|
812
|
+
# (isolation is retained; only the bundled-binary template is legacy).
|
|
813
|
+
_desktop_deprecation_notice
|
|
814
|
+
|
|
773
815
|
# Check if sandbox already exists
|
|
774
816
|
local sandbox_exists=false
|
|
775
817
|
if docker sandbox ls 2>/dev/null | grep -q "$DESKTOP_SANDBOX_NAME"; then
|
|
@@ -142,16 +142,94 @@ spec_interrogation_class_for() {
|
|
|
142
142
|
lc_section="$(printf '%s' "$section" | tr '[:upper:]' '[:lower:]')"
|
|
143
143
|
lc_line="$(printf '%s' "$line" | tr '[:upper:]' '[:lower:]')"
|
|
144
144
|
|
|
145
|
+
# AUTHORITATIVE SECTION GUARD (first): the grill CATEGORIZES each finding into
|
|
146
|
+
# a section, and that categorization is the strongest signal of the finding's
|
|
147
|
+
# KIND. A finding the grill itself filed under an explicitly non-contradiction
|
|
148
|
+
# section -- Ambiguities, (Unstated) Assumptions, Open questions, Blind spots,
|
|
149
|
+
# Missing/gaps -- is that KIND, even if its prose happens to use the word
|
|
150
|
+
# "contradict"/"conflict" while POSING the ambiguity ("two implementations
|
|
151
|
+
# could show contradictory trends", "a dev-only build contradicts opening
|
|
152
|
+
# index.html"). Those are assumable-away ambiguities, not spec-internal
|
|
153
|
+
# "X and not-X". Classify by the grill's own section here so a stray keyword
|
|
154
|
+
# in the finding prose can never escalate an ambiguity to a no-retry-terminal
|
|
155
|
+
# contradiction. Opt out with LOKI_SPEC_SECTION_KIND_GUARD=0.
|
|
156
|
+
if [ "${LOKI_SPEC_SECTION_KIND_GUARD:-1}" = "1" ]; then
|
|
157
|
+
case "$lc_section" in
|
|
158
|
+
*ambigu*|*acceptance*) printf 'ambiguous'; return 0 ;;
|
|
159
|
+
*unstated\ assumption*|*assumption*) printf 'underspecified'; return 0 ;;
|
|
160
|
+
*open\ question*) printf 'ambiguous'; return 0 ;;
|
|
161
|
+
*blind\ spot*|*security*|*scale*|*reliability*) printf 'missing'; return 0 ;;
|
|
162
|
+
esac
|
|
163
|
+
fi
|
|
164
|
+
|
|
165
|
+
# A contradiction keyword classifies the finding as contradictory ONLY when
|
|
166
|
+
# the finding ASSERTS a contradiction ("section 2 says X, section 5 says
|
|
167
|
+
# not-X"), NOT when it merely QUESTIONS a hypothetical one ("what stops the
|
|
168
|
+
# KPIs from contradicting the chart?", "these could conflict"). A grill that
|
|
169
|
+
# ASKS about a possible inconsistency is raising an AMBIGUITY (assumable-away
|
|
170
|
+
# with a default: "make them consistent"), not reporting an unresolvable
|
|
171
|
+
# spec-internal "X and not-X". Naive substring matching force-tagged such
|
|
172
|
+
# questions as no-retry-terminal contradictions and killed valid specs.
|
|
173
|
+
# Interrogative / hypothetical / risk framings demote the keyword to a normal
|
|
174
|
+
# (assumable) finding; the section-based classification below then applies.
|
|
175
|
+
# Opt back into the old broad substring match with
|
|
176
|
+
# LOKI_SPEC_CONTRADICTION_KEYWORD_STRICT=0.
|
|
145
177
|
case "$lc_line" in
|
|
146
|
-
*contradict*|*conflict*|*inconsistent*|*mutually\ exclusive*)
|
|
147
|
-
|
|
178
|
+
*contradict*|*conflict*|*inconsistent*|*incompatible*|*mutually\ exclusive*)
|
|
179
|
+
if [ "${LOKI_SPEC_CONTRADICTION_KEYWORD_STRICT:-1}" = "1" ]; then
|
|
180
|
+
case "$lc_line" in
|
|
181
|
+
# Interrogative or hypothetical -> ambiguity, not a contradiction.
|
|
182
|
+
what\ stops*|what\ if*|what\ prevents*|how\ do\ we\ ensure*|\
|
|
183
|
+
*could\ contradict*|*could\ conflict*|*might\ contradict*|*might\ conflict*|\
|
|
184
|
+
*may\ contradict*|*may\ conflict*|*risk\ of\ *|*potential*|\
|
|
185
|
+
*would\ contradict*|*if\ they\ contradict*|*from\ contradicting*|\
|
|
186
|
+
*stops\ them\ from*|*prevents\ them\ from*|*avoid*|\
|
|
187
|
+
*unclear\ whether*|*not\ specified*|*no\ guarantee*|*to\ ensure*)
|
|
188
|
+
: ;; # demote: fall through to section-based classification
|
|
189
|
+
*)
|
|
190
|
+
printf 'contradictory'; return 0 ;;
|
|
191
|
+
esac
|
|
192
|
+
else
|
|
193
|
+
printf 'contradictory'; return 0
|
|
194
|
+
fi
|
|
195
|
+
;;
|
|
148
196
|
esac
|
|
149
197
|
|
|
150
|
-
# P2-4
|
|
151
|
-
#
|
|
198
|
+
# P2-4 REFINED (false-positive fix): the grill's report sections are free-form
|
|
199
|
+
# LLM text. A finding under a PURE contradictions heading ("Contradictions",
|
|
200
|
+
# "Contradictory requirements") is the grill's authoritative "X and not-X"
|
|
201
|
+
# categorization -- trust it, even when the conflict is stated SEMANTICALLY with
|
|
202
|
+
# no magic word ("immutable records" vs "an edit endpoint"). Recording a
|
|
203
|
+
# contradiction is non-fatal by default now (LOKI_SPEC_CONTRADICTION_FASTFAIL=0
|
|
204
|
+
# in run.sh proceeds with the finding; the completion gate still blocks
|
|
205
|
+
# fake-green), so trusting a clean heading costs a recorded finding, not a
|
|
206
|
+
# terminal kill.
|
|
207
|
+
# The false positives lived in MIXED headings that lump unlike findings
|
|
208
|
+
# ("Contradictions and ambiguities", "Contradictions / open questions") -- there
|
|
209
|
+
# a bare ambiguity (assumable away with an implementer default) was mislabeled a
|
|
210
|
+
# contradiction. For a mixed heading we still require corroborating conflict
|
|
211
|
+
# language in the line itself. The line-level check above already catches
|
|
212
|
+
# genuinely-worded contradictions regardless of section.
|
|
213
|
+
# LOKI_SPEC_SECTION_CONTRADICTION_FORCE=1 forces the old broad behavior (any
|
|
214
|
+
# contradiction-ish heading -> contradictory).
|
|
152
215
|
case "$lc_section" in
|
|
153
216
|
*contradiction*|*contradictor*)
|
|
154
|
-
|
|
217
|
+
# Mixed heading: co-mentions ambiguity/question/assumption/gap words ->
|
|
218
|
+
# the finding could be either kind, so demand line evidence.
|
|
219
|
+
local _mixed=0
|
|
220
|
+
case "$lc_section" in
|
|
221
|
+
*ambigu*|*question*|*assumption*|*gap*|*missing*|*unclear*|*open*|*blind*)
|
|
222
|
+
_mixed=1 ;;
|
|
223
|
+
esac
|
|
224
|
+
if [ "${LOKI_SPEC_SECTION_CONTRADICTION_FORCE:-0}" = "1" ] || [ "$_mixed" = "0" ]; then
|
|
225
|
+
printf 'contradictory'; return 0
|
|
226
|
+
fi
|
|
227
|
+
# mixed heading: require corroborating conflict language in the line
|
|
228
|
+
case "$lc_line" in
|
|
229
|
+
*contradict*|*conflict*|*inconsistent*|*incompatible*|*cannot\ both*|*mutually\ exclusive*|*versus*|*but\ also*)
|
|
230
|
+
printf 'contradictory'; return 0 ;;
|
|
231
|
+
esac
|
|
232
|
+
;;
|
|
155
233
|
esac
|
|
156
234
|
|
|
157
235
|
case "$lc_section" in
|
|
@@ -823,6 +901,71 @@ for t in titles:
|
|
|
823
901
|
}
|
|
824
902
|
|
|
825
903
|
# Total ledger entries + high count, "total high" on one line. For summaries.
|
|
904
|
+
# ---------------------------------------------------------------------------
|
|
905
|
+
# spec_contradiction_confident: confirm a contradiction verdict is NOT a flaky
|
|
906
|
+
# single-LLM-sample false positive before it is allowed to trip a no-retry
|
|
907
|
+
# terminal exit.
|
|
908
|
+
#
|
|
909
|
+
# WHY: the contradiction verdict comes from ONE Devil's-Advocate grill sample
|
|
910
|
+
# (grill_main is invoked once per run). An LLM judge is non-deterministic, so the
|
|
911
|
+
# SAME spec is judged "contradictory" only some fraction of runs -- measured
|
|
912
|
+
# ~1/3 in the v8 benchmark. Escalating that lone sample straight to exit 20
|
|
913
|
+
# ("terminal failure, no retry", whose own contract asserts "re-running on the
|
|
914
|
+
# same inputs fails the same way") is unsound: re-running an LLM judge does NOT
|
|
915
|
+
# fail the same way. This helper turns a single sample into an N-of-M agreement:
|
|
916
|
+
# it re-grills up to (MIN_SAMPLES - 1) more times and returns confident (0) only
|
|
917
|
+
# if the contradiction PERSISTS across the required number of independent
|
|
918
|
+
# samples. A verdict that does not reproduce is treated as flaky -> not
|
|
919
|
+
# confident (non-zero) -> the caller must NOT terminal-exit; it falls through to
|
|
920
|
+
# the in-loop resolve-with-default recovery (spec_ledger_acknowledge_all).
|
|
921
|
+
#
|
|
922
|
+
# This is a general "confirm before an irreversible terminal action on a
|
|
923
|
+
# non-deterministic verdict" pattern, not a fix for any one spec. It is a no-op
|
|
924
|
+
# (returns confident immediately) when MIN_SAMPLES<=1 (LOKI_SPEC_CONTRADICTION_MIN_SAMPLES=1
|
|
925
|
+
# restores the pre-fix single-sample behavior), or when re-grilling is impossible
|
|
926
|
+
# (no provider / grill unavailable) -- in that degraded case we KEEP the original
|
|
927
|
+
# single-sample verdict rather than silently clearing it (fail-closed on the
|
|
928
|
+
# original signal, never fabricate a clear).
|
|
929
|
+
#
|
|
930
|
+
# Usage: spec_contradiction_confident <spec_path>
|
|
931
|
+
# exit 0 -> contradiction confirmed across samples (safe to terminal-fail)
|
|
932
|
+
# exit 1 -> not confirmed / flaky (caller must recover-before-fail, not exit 20)
|
|
933
|
+
# ---------------------------------------------------------------------------
|
|
934
|
+
spec_contradiction_confident() {
|
|
935
|
+
local spec_path="${1:-}"
|
|
936
|
+
local min_samples="${LOKI_SPEC_CONTRADICTION_MIN_SAMPLES:-2}"
|
|
937
|
+
case "$min_samples" in ''|*[!0-9]*) min_samples=2 ;; esac
|
|
938
|
+
|
|
939
|
+
# MIN_SAMPLES<=1: legacy single-sample behavior -> the current (already >=1)
|
|
940
|
+
# verdict is authoritative. Confident.
|
|
941
|
+
[ "$min_samples" -le 1 ] && return 0
|
|
942
|
+
|
|
943
|
+
# Re-grill up to (min_samples - 1) additional times; every re-sample must ALSO
|
|
944
|
+
# report >=1 unresolved contradiction for the verdict to be confident.
|
|
945
|
+
# We already hold sample #1 (the caller only invokes us when count>=1).
|
|
946
|
+
local needed=$(( min_samples - 1 ))
|
|
947
|
+
local i
|
|
948
|
+
for (( i = 1; i <= needed; i++ )); do
|
|
949
|
+
# A fresh grill sample. spec_interrogation_run re-runs grill_main and
|
|
950
|
+
# re-classifies into the ledger. If re-grilling is not possible (no
|
|
951
|
+
# provider), the count will not change and we must not clear a real
|
|
952
|
+
# signal -- so treat an unchanged/failed re-sample as still-present
|
|
953
|
+
# (fail-closed on the original verdict).
|
|
954
|
+
if type spec_interrogation_run &>/dev/null; then
|
|
955
|
+
spec_interrogation_run "$spec_path" >/dev/null 2>&1 || true
|
|
956
|
+
fi
|
|
957
|
+
local _n
|
|
958
|
+
_n="$(spec_ledger_contradiction_unresolved_count 2>/dev/null | head -1)"
|
|
959
|
+
case "$_n" in ''|*[!0-9]*) _n=0 ;; esac
|
|
960
|
+
if [ "$_n" -lt 1 ]; then
|
|
961
|
+
# This independent sample does NOT see the contradiction -> flaky.
|
|
962
|
+
return 1
|
|
963
|
+
fi
|
|
964
|
+
done
|
|
965
|
+
# Persisted across all required samples -> confident it is a real contradiction.
|
|
966
|
+
return 0
|
|
967
|
+
}
|
|
968
|
+
|
|
826
969
|
spec_ledger_counts() {
|
|
827
970
|
local dir
|
|
828
971
|
dir="$(_spec_ledger_dir)"
|
package/autonomy/spec.sh
CHANGED
|
@@ -91,7 +91,13 @@ spec_resolve_source() {
|
|
|
91
91
|
".loki/generated-prd.md" \
|
|
92
92
|
"prd.md" \
|
|
93
93
|
"PRD.md" \
|
|
94
|
-
"docs/prd.md"
|
|
94
|
+
"docs/prd.md" \
|
|
95
|
+
"openapi.yaml" \
|
|
96
|
+
"openapi.yml" \
|
|
97
|
+
"openapi.json" \
|
|
98
|
+
"api/openapi.yaml" \
|
|
99
|
+
"api/openapi.json" \
|
|
100
|
+
"schema.graphql"; do
|
|
95
101
|
if [ -f "$candidate" ]; then
|
|
96
102
|
printf '%s\n' "$candidate"
|
|
97
103
|
return 0
|
|
@@ -121,6 +127,117 @@ except OSError as exc:
|
|
|
121
127
|
|
|
122
128
|
lines = raw.splitlines()
|
|
123
129
|
|
|
130
|
+
# ---------------------------------------------------------------------------
|
|
131
|
+
# Contract fast path (RUN-25 iter 25): when the spec IS an OpenAPI / GraphQL /
|
|
132
|
+
# Postman contract, lock one requirement PER OPERATION with a per-operation
|
|
133
|
+
# content hash, so `spec status` reports the exact operation id that DRIFTED
|
|
134
|
+
# (a response schema edit on /users flags "users.get", not the whole file).
|
|
135
|
+
# Falls through to the markdown heading/checklist logic below on anything else,
|
|
136
|
+
# byte-identical to the prior behavior for a normal PRD.
|
|
137
|
+
# ---------------------------------------------------------------------------
|
|
138
|
+
def _contract_requirements(text):
|
|
139
|
+
def load_structured(t):
|
|
140
|
+
try:
|
|
141
|
+
return json.loads(t)
|
|
142
|
+
except Exception:
|
|
143
|
+
pass
|
|
144
|
+
try:
|
|
145
|
+
import yaml
|
|
146
|
+
return yaml.safe_load(t)
|
|
147
|
+
except Exception:
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
def op_hash(payload):
|
|
151
|
+
# Stable hash of the operation's own definition (order-insensitive for
|
|
152
|
+
# dict payloads via sort_keys), so a change to THIS op flips only its id.
|
|
153
|
+
try:
|
|
154
|
+
blob = json.dumps(payload, sort_keys=True, ensure_ascii=False)
|
|
155
|
+
except Exception:
|
|
156
|
+
blob = repr(payload)
|
|
157
|
+
return hashlib.sha256(blob.encode("utf-8")).hexdigest()
|
|
158
|
+
|
|
159
|
+
data = load_structured(text)
|
|
160
|
+
ops = []
|
|
161
|
+
|
|
162
|
+
# OpenAPI / Swagger
|
|
163
|
+
if isinstance(data, dict) and ("openapi" in data or "swagger" in data) and isinstance(data.get("paths"), dict):
|
|
164
|
+
for p, item in data["paths"].items():
|
|
165
|
+
if not isinstance(item, dict):
|
|
166
|
+
continue
|
|
167
|
+
for method, op in item.items():
|
|
168
|
+
if method.lower() not in ("get", "post", "put", "patch", "delete", "head", "options", "trace"):
|
|
169
|
+
continue
|
|
170
|
+
if not isinstance(op, dict):
|
|
171
|
+
continue
|
|
172
|
+
opid = op.get("operationId") or ("%s %s" % (method.upper(), p))
|
|
173
|
+
ops.append((str(opid).strip(), "operation", op_hash(op)))
|
|
174
|
+
return ops
|
|
175
|
+
|
|
176
|
+
# Postman collection
|
|
177
|
+
if isinstance(data, dict) and isinstance(data.get("info"), dict) and "item" in data:
|
|
178
|
+
def walk(items, prefix=""):
|
|
179
|
+
for it in items or []:
|
|
180
|
+
if not isinstance(it, dict):
|
|
181
|
+
continue
|
|
182
|
+
name = it.get("name", "request")
|
|
183
|
+
if isinstance(it.get("item"), list):
|
|
184
|
+
walk(it["item"], prefix + name + " / ")
|
|
185
|
+
elif "request" in it:
|
|
186
|
+
label = (prefix + name).strip()
|
|
187
|
+
ops.append((label, "request", op_hash(it.get("request"))))
|
|
188
|
+
walk(data.get("item"))
|
|
189
|
+
return ops
|
|
190
|
+
|
|
191
|
+
# GraphQL SDL (text)
|
|
192
|
+
if re.search(r"\btype\s+(Query|Mutation|Subscription)\b", text):
|
|
193
|
+
for root in ("Query", "Mutation", "Subscription"):
|
|
194
|
+
m = re.search(r"\btype\s+" + root + r"\b[^{]*\{", text)
|
|
195
|
+
if not m:
|
|
196
|
+
continue
|
|
197
|
+
i = m.end()
|
|
198
|
+
depth = 1
|
|
199
|
+
body_start = i
|
|
200
|
+
while i < len(text) and depth > 0:
|
|
201
|
+
if text[i] == "{":
|
|
202
|
+
depth += 1
|
|
203
|
+
elif text[i] == "}":
|
|
204
|
+
depth -= 1
|
|
205
|
+
i += 1
|
|
206
|
+
body = text[body_start:i - 1]
|
|
207
|
+
for fm in re.finditer(r"^\s*([A-Za-z_]\w*)\s*(\([^)]*\))?\s*:\s*([^\n#]+)", body, re.M):
|
|
208
|
+
field = fm.group(1)
|
|
209
|
+
sig = fm.group(0).strip()
|
|
210
|
+
ops.append(("%s.%s" % (root, field), "field", hashlib.sha256(sig.encode("utf-8")).hexdigest()))
|
|
211
|
+
return ops
|
|
212
|
+
|
|
213
|
+
return []
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
_contract_ops = _contract_requirements(raw)
|
|
217
|
+
if _contract_ops:
|
|
218
|
+
_out = []
|
|
219
|
+
_seen = {}
|
|
220
|
+
for _name, _kind, _h in _contract_ops:
|
|
221
|
+
base = _name.strip().lower() or "op"
|
|
222
|
+
base = re.sub(r"\s+", " ", base)
|
|
223
|
+
if base in _seen:
|
|
224
|
+
_seen[base] += 1
|
|
225
|
+
rid = "%s#%d" % (base, _seen[base])
|
|
226
|
+
else:
|
|
227
|
+
_seen[base] = 0
|
|
228
|
+
rid = base
|
|
229
|
+
_out.append({
|
|
230
|
+
"id": rid,
|
|
231
|
+
"kind": _kind,
|
|
232
|
+
"level": 100,
|
|
233
|
+
"text": _name,
|
|
234
|
+
"line": 0,
|
|
235
|
+
"content_hash": _h,
|
|
236
|
+
})
|
|
237
|
+
json.dump({"requirements": _out}, sys.stdout, indent=2)
|
|
238
|
+
sys.stdout.write("\n")
|
|
239
|
+
sys.exit(0)
|
|
240
|
+
|
|
124
241
|
heading_re = re.compile(r'^(#{1,6})\s+(.*\S)\s*$')
|
|
125
242
|
# Checklist item: optional leading whitespace, a bullet, then [ ] / [x] / [X].
|
|
126
243
|
checklist_re = re.compile(r'^\s*[-*]\s+\[([ xX])\]\s+(.*\S)\s*$')
|
package/autonomy/telemetry.sh
CHANGED
|
@@ -146,6 +146,47 @@ _loki_detect_channel() {
|
|
|
146
146
|
esac
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
+
# _loki_known_command <token>: FIXED ALLOWLIST at the telemetry boundary. Prints
|
|
150
|
+
# the token verbatim when it is a real loki subcommand, else the literal "other".
|
|
151
|
+
#
|
|
152
|
+
# WHY THIS EXISTS. Every command-shaped telemetry value is the user's raw first
|
|
153
|
+
# CLI token, and loki_telemetry's payload builder is a PASS-THROUGH dict, not an
|
|
154
|
+
# allowlist. So the honest typo of dropping the subcommand off this project's own
|
|
155
|
+
# documented quickstart --
|
|
156
|
+
# loki ./client-acme-merger-prd.md
|
|
157
|
+
# -- put a user-authored file name on the wire verbatim. Spec file names routinely
|
|
158
|
+
# carry client and codename. Sanitizing at this boundary bounds the value space to
|
|
159
|
+
# a fixed, public vocabulary by construction, so no path, flag value, or spec name
|
|
160
|
+
# can reach PostHog through a command field regardless of what the user typed.
|
|
161
|
+
#
|
|
162
|
+
# The arms below mirror autonomy/loki's own `case "$command" in` dispatch (whose
|
|
163
|
+
# final `*)` arm is literally this same unknown bucket), UNIONed with the
|
|
164
|
+
# bin/loki Bun-route arm (`internal` is Bun-only) and the `report` pre-route.
|
|
165
|
+
# A token missing from this list degrades to "other": lossy, never leaky, so a
|
|
166
|
+
# stale list after a new subcommand lands is safe by construction.
|
|
167
|
+
_loki_known_command() {
|
|
168
|
+
case "${1:-}" in
|
|
169
|
+
--help|--version|-h|-v|agent|analyze|api|assets\
|
|
170
|
+
|audit|bench|checkpoint|ci|cleanup|cluster|cockpit|code\
|
|
171
|
+
|completions|compliance|compound|config|context|cost|council|cp\
|
|
172
|
+
|crash|ctx|dashboard|demo|deploy|docker|docs|doctor\
|
|
173
|
+
|dogfood|enterprise|explain|export|failover|github|grill|handoff\
|
|
174
|
+
|heal|help|import|init|internal|issue|kpis|logs\
|
|
175
|
+
|magic|mcp|memory|metrics|migrate|modernize|monitor|next\
|
|
176
|
+
|notify|onboard|open|optimize|otel|own|pause|plan\
|
|
177
|
+
|preview|projects|proof|provider|quick|quickstart|rc|receipt\
|
|
178
|
+
|remote|report|reset|resume|review|rollback|run|sandbox\
|
|
179
|
+
|secrets|secure|self-update|self_update|sentrux|serve|setup-skill|share\
|
|
180
|
+
|ship|spec|start|state|stats|status|steer|stop\
|
|
181
|
+
|syslog|telemetry|template|test|tour|trigger|trust|trust-metrics\
|
|
182
|
+
|ultracode|update|verify|version|voice|watch|watchdog|web\
|
|
183
|
+
|welcome|why|wiki|worktree|wt)
|
|
184
|
+
printf '%s' "$1" ;;
|
|
185
|
+
*)
|
|
186
|
+
printf 'other' ;;
|
|
187
|
+
esac
|
|
188
|
+
}
|
|
189
|
+
|
|
149
190
|
loki_telemetry() {
|
|
150
191
|
_loki_telemetry_enabled || return 0
|
|
151
192
|
local event="$1"; shift
|
|
@@ -160,12 +201,16 @@ loki_telemetry() {
|
|
|
160
201
|
os_name=$(uname -s 2>/dev/null || echo "unknown")
|
|
161
202
|
arch=$(uname -m 2>/dev/null || echo "unknown")
|
|
162
203
|
|
|
163
|
-
# Build JSON payload safely using Python to prevent injection
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
204
|
+
# Build JSON payload safely using Python to prevent injection.
|
|
205
|
+
# The extra key=value pairs are forwarded as "$@" -- one argv slot per pair,
|
|
206
|
+
# QUOTED. They used to be joined into a single space-delimited string and
|
|
207
|
+
# then re-split by an unquoted expansion, which broke two ways: a value
|
|
208
|
+
# containing a space was truncated (`prd=my file.md` -> `prd=my`, rest
|
|
209
|
+
# dropped) or, if a fragment itself contained `=`, forged an extra PostHog
|
|
210
|
+
# property KEY; and the unquoted expansion also globbed, so `spec=*.md`
|
|
211
|
+
# expanded against the cwd and injected file names into the payload. Callers
|
|
212
|
+
# already pass each pair as its own quoted word, so "$@" is the array and no
|
|
213
|
+
# caller signature changes.
|
|
169
214
|
local payload
|
|
170
215
|
payload=$(python3 -c "
|
|
171
216
|
import json, sys
|
|
@@ -175,7 +220,7 @@ for arg in sys.argv[5:]:
|
|
|
175
220
|
k, v = arg.split('=', 1)
|
|
176
221
|
props[k] = v
|
|
177
222
|
print(json.dumps({'api_key': '$LOKI_POSTHOG_KEY', 'event': sys.argv[5] if len(sys.argv) > 5 else '', 'distinct_id': '$distinct_id', 'properties': props}))
|
|
178
|
-
" "$os_name" "$arch" "$version" "$channel"
|
|
223
|
+
" "$os_name" "$arch" "$version" "$channel" "$@" 2>/dev/null) || return 0
|
|
179
224
|
# Re-inject event and distinct_id properly
|
|
180
225
|
payload=$(python3 -c "
|
|
181
226
|
import json, sys
|
|
@@ -190,3 +235,84 @@ print(json.dumps(d))
|
|
|
190
235
|
-d "$payload" >/dev/null 2>&1 &) 2>/dev/null
|
|
191
236
|
return 0
|
|
192
237
|
}
|
|
238
|
+
|
|
239
|
+
# _loki_analytics_enabled: STRICTER, second-layer gate for opt-in build-outcome
|
|
240
|
+
# analytics. Sits BELOW the telemetry gate: build_verified fires ONLY when
|
|
241
|
+
# 1. telemetry is enabled at all (_loki_telemetry_enabled -- so every opt-out
|
|
242
|
+
# that kills telemetry also kills analytics), AND
|
|
243
|
+
# 2. the user EXPLICITLY turned analytics on (LOKI_ANALYTICS/LOKI_POSTHOG=on or
|
|
244
|
+
# ~/.loki/config: ANALYTICS_ENABLED=true).
|
|
245
|
+
# Default OFF even for diagnostics-on users: anonymous crash diagnostics are one
|
|
246
|
+
# thing, per-build outcome metrics are a separate, explicit consent. Zero-egress-
|
|
247
|
+
# by-default is a moat, so this stays strictly opt-in. Only already-computed proof
|
|
248
|
+
# scalars are ever sent (see _loki_proof_analytics_props) -- never code, spec text,
|
|
249
|
+
# paths, or file names.
|
|
250
|
+
_loki_analytics_enabled() {
|
|
251
|
+
_loki_telemetry_enabled || return 1
|
|
252
|
+
local _lower
|
|
253
|
+
_lower="$(printf '%s' "${LOKI_ANALYTICS:-${LOKI_POSTHOG:-}}" | tr '[:upper:]' '[:lower:]')"
|
|
254
|
+
[ "$_lower" = "on" ] && return 0
|
|
255
|
+
[ "$_lower" = "1" ] && return 0
|
|
256
|
+
if [ -f "${HOME}/.loki/config" ] && grep -q "^ANALYTICS_ENABLED=true" "${HOME}/.loki/config" 2>/dev/null; then
|
|
257
|
+
return 0
|
|
258
|
+
fi
|
|
259
|
+
return 1
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
# loki_emit_funnel_once <marker-name> <event> [key=value...]: fire ONE
|
|
263
|
+
# first-run-funnel event, at most once per machine, under the SAME strict double
|
|
264
|
+
# gate as build_verified (telemetry enabled AND analytics explicitly opted in --
|
|
265
|
+
# default OFF even for diagnostics-on users). No new consent knob, no new
|
|
266
|
+
# endpoint: it reuses loki_telemetry, so every existing opt-out kills it too.
|
|
267
|
+
#
|
|
268
|
+
# ORDERING IS LOAD-BEARING. Disclosure runs before the emit, and the marker is
|
|
269
|
+
# written only AFTER the gate has passed and we have actually emitted. The
|
|
270
|
+
# reverted attempt burned its marker unconditionally, which meant a first run
|
|
271
|
+
# under CI/auto-off consumed the one-shot and permanently silenced the event for
|
|
272
|
+
# that user. Mirrors _loki_disclose_telemetry_once: act, then mark.
|
|
273
|
+
#
|
|
274
|
+
# Values must already be bounded (see _loki_known_command). Best-effort: every
|
|
275
|
+
# step returns 0, nothing blocks, nothing throws.
|
|
276
|
+
loki_emit_funnel_once() {
|
|
277
|
+
local _marker_name="$1"; shift
|
|
278
|
+
local _event="$1"; shift
|
|
279
|
+
_loki_analytics_enabled || return 0
|
|
280
|
+
local _mfile="${HOME}/.loki/funnel-${_marker_name}"
|
|
281
|
+
[ -f "$_mfile" ] 2>/dev/null && return 0
|
|
282
|
+
if declare -f _loki_disclose_telemetry_once >/dev/null 2>&1; then
|
|
283
|
+
_loki_disclose_telemetry_once
|
|
284
|
+
fi
|
|
285
|
+
loki_telemetry "$_event" "$@" 2>/dev/null || true
|
|
286
|
+
mkdir -p "${HOME}/.loki" 2>/dev/null || return 0
|
|
287
|
+
: > "$_mfile" 2>/dev/null || true
|
|
288
|
+
return 0
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
# loki_emit_build_verified <proof.json path>: fire ONE build_verified event
|
|
292
|
+
# carrying a FIXED ALLOWLIST of already-computed scalars from a finished
|
|
293
|
+
# proof.json. Trust-core untouched -- reads the receipt after it is written,
|
|
294
|
+
# never influences the verdict. Fires only under the strict analytics gate.
|
|
295
|
+
# The allowlist is enforced in Python (below); anything not named is never read,
|
|
296
|
+
# so a schema change cannot silently start leaking a new field. Free-text-shaped
|
|
297
|
+
# fields are excluded on purpose: headline is a bounded enum (VERIFIED / VERIFIED
|
|
298
|
+
# WITH GAPS / NOT VERIFIED), never spec/project text; files_changed is a COUNT,
|
|
299
|
+
# never paths.
|
|
300
|
+
loki_emit_build_verified() {
|
|
301
|
+
_loki_analytics_enabled || return 0
|
|
302
|
+
local _proof="$1"
|
|
303
|
+
[ -f "$_proof" ] || return 0
|
|
304
|
+
command -v python3 >/dev/null 2>&1 || return 0
|
|
305
|
+
local _reader="${SCRIPT_DIR:-${SKILL_DIR:-}}/lib/proof-analytics-props.py"
|
|
306
|
+
[ -f "$_reader" ] || return 0
|
|
307
|
+
local _props
|
|
308
|
+
_props=$(python3 "$_reader" "$_proof" 2>/dev/null) || return 0
|
|
309
|
+
[ -n "$_props" ] || return 0
|
|
310
|
+
# Feed the allowlisted key=value pairs into loki_telemetry (which re-checks
|
|
311
|
+
# the base gate and owns the injection-safe payload build).
|
|
312
|
+
local _args=()
|
|
313
|
+
while IFS= read -r _line; do
|
|
314
|
+
[ -n "$_line" ] && _args+=("$_line")
|
|
315
|
+
done <<< "$_props"
|
|
316
|
+
loki_telemetry "build_verified" "${_args[@]}"
|
|
317
|
+
return 0
|
|
318
|
+
}
|
package/autonomy/verify.sh
CHANGED
|
@@ -726,6 +726,91 @@ verify_gate_static() {
|
|
|
726
726
|
return 0
|
|
727
727
|
}
|
|
728
728
|
|
|
729
|
+
# ---------------------------------------------------------------------------
|
|
730
|
+
# Gate: NO-MOCK data render (Proof-of-Function, static half).
|
|
731
|
+
#
|
|
732
|
+
# WHAT IS SCANNED:
|
|
733
|
+
# The changed-file union identifies the trust boundary. The shared scanner may
|
|
734
|
+
# follow a named relative import into an unchanged source module, but it only
|
|
735
|
+
# blocks when either the render module or backing declaration changed. A
|
|
736
|
+
# rendered operational collection backed by an inline mock array, faker, or
|
|
737
|
+
# placeholder literal is the disproof: a fake app that only looks done.
|
|
738
|
+
#
|
|
739
|
+
# ARTIFACT:
|
|
740
|
+
# Writes .loki/verification/nomock-scan.json as an audit receipt. Completion
|
|
741
|
+
# never trusts this mutable file and independently reruns the engine-owned
|
|
742
|
+
# scanner over current source. On a hit, verify also emits a High finding so
|
|
743
|
+
# `loki verify` blocks. This is an observation of shipped source, not a guess.
|
|
744
|
+
#
|
|
745
|
+
# FAIL-CLOSED / FALSE-POSITIVE TUNING:
|
|
746
|
+
# Binds the rendered symbol to its declaration and exempts static content such
|
|
747
|
+
# as features, pricing, comparisons, and stories. Excludes test, story, mock,
|
|
748
|
+
# fixture, MSW, generated, and declaration paths. High-confidence only;
|
|
749
|
+
# anything else passes or skips to avoid blocking design-system demos, empty
|
|
750
|
+
# states, or seed files.
|
|
751
|
+
# ---------------------------------------------------------------------------
|
|
752
|
+
verify_gate_nomock() {
|
|
753
|
+
local tree="$1"
|
|
754
|
+
local changed="$VERIFY_DIFF_NAMES"
|
|
755
|
+
local PYTHONPATH="" PYTHONNOUSERSITE=1
|
|
756
|
+
export PYTHONPATH PYTHONNOUSERSITE
|
|
757
|
+
|
|
758
|
+
if [ "${LOKI_PROOF_NOMOCK:-1}" = "0" ]; then
|
|
759
|
+
_verify_add_gate "nomock" "skipped" "" "nomock gate disabled (LOKI_PROOF_NOMOCK=0)" "true"
|
|
760
|
+
return 0
|
|
761
|
+
fi
|
|
762
|
+
if [ -z "$changed" ]; then
|
|
763
|
+
_verify_add_gate "nomock" "skipped" "" "no changed files in diff" "true"
|
|
764
|
+
return 0
|
|
765
|
+
fi
|
|
766
|
+
if ! command -v python3 >/dev/null 2>&1; then
|
|
767
|
+
_verify_add_gate "nomock" "inconclusive" "" "python3 unavailable" "true"
|
|
768
|
+
return 0
|
|
769
|
+
fi
|
|
770
|
+
|
|
771
|
+
local scan_dir=".loki/verification"
|
|
772
|
+
mkdir -p "$scan_dir" 2>/dev/null || true
|
|
773
|
+
local scan_file="$scan_dir/nomock-scan.json"
|
|
774
|
+
|
|
775
|
+
# The scan runs in $tree over the changed files, writes the receipt JSON, and
|
|
776
|
+
# prints its status line (FAIL:<file>:<line>:<snippet> | PASS:<n> | SKIP:0).
|
|
777
|
+
local scanner
|
|
778
|
+
scanner="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib/no_mock_scan.py"
|
|
779
|
+
if [ ! -f "$scanner" ]; then
|
|
780
|
+
_verify_add_gate "nomock" "inconclusive" "" "no-mock scanner unavailable" "true"
|
|
781
|
+
return 0
|
|
782
|
+
fi
|
|
783
|
+
local status_line
|
|
784
|
+
status_line=$(
|
|
785
|
+
_NM_FILES="$changed" \
|
|
786
|
+
_NM_TREE="$tree" \
|
|
787
|
+
_NM_OUT="$scan_file" \
|
|
788
|
+
_NM_BASE="${VERIFY_MERGE_BASE:-}" \
|
|
789
|
+
python3 -I "$scanner" 2>/dev/null \
|
|
790
|
+
|| echo "INCONCLUSIVE:detector_error"
|
|
791
|
+
)
|
|
792
|
+
|
|
793
|
+
case "$status_line" in
|
|
794
|
+
FAIL:*)
|
|
795
|
+
local rest="${status_line#FAIL:}"
|
|
796
|
+
local hit_file="${rest%%:*}"
|
|
797
|
+
_verify_add_gate "nomock" "fail" "static-scan" "rendered operational collection resolves to inline, faker, or placeholder-backed data: $rest" "true"
|
|
798
|
+
_verify_add_finding "High" "functionality" "deterministic:nomock-scan" "$hit_file" "null" \
|
|
799
|
+
"Mock-backed data render: a list, table, or dashboard in $hit_file resolves to an inline mock array, faker value, or placeholder literal. Wire the rendered collection to a real data source. ($rest)"
|
|
800
|
+
;;
|
|
801
|
+
PASS:*)
|
|
802
|
+
_verify_add_gate "nomock" "pass" "static-scan" "${status_line#PASS:} UI module(s) scanned, no high-confidence rendered operational mock detected" "true"
|
|
803
|
+
;;
|
|
804
|
+
SKIP:*)
|
|
805
|
+
_verify_add_gate "nomock" "skipped" "" "no scannable UI/data-render files in diff" "true"
|
|
806
|
+
;;
|
|
807
|
+
*)
|
|
808
|
+
_verify_add_gate "nomock" "inconclusive" "" "scanner error" "true"
|
|
809
|
+
;;
|
|
810
|
+
esac
|
|
811
|
+
return 0
|
|
812
|
+
}
|
|
813
|
+
|
|
729
814
|
# ---------------------------------------------------------------------------
|
|
730
815
|
# Gate: secret scan (NET-NEW).
|
|
731
816
|
#
|
|
@@ -780,6 +865,27 @@ verify_gate_static() {
|
|
|
780
865
|
verify_secret_scan_file() {
|
|
781
866
|
local file="$1"
|
|
782
867
|
|
|
868
|
+
# TEMPLATE / FIXTURE / TEST PATHS: a file whose name declares it is an
|
|
869
|
+
# example, or that exists to TEST the secret scanner, is not a leak.
|
|
870
|
+
# Kept byte-identical in intent to the same guard in
|
|
871
|
+
# autonomy/lib/secret-scan.sh, because these two matchers are duplicates and
|
|
872
|
+
# a fix applied to only one of them is a fix that does not hold.
|
|
873
|
+
#
|
|
874
|
+
# Concretely: tests/test-secret-gate-false-positives.sh has to contain
|
|
875
|
+
# realistic-looking keys to prove the gate still catches real ones. Without
|
|
876
|
+
# this, adding a test for the scanner FAILS the scanner -- and the CI gate
|
|
877
|
+
# reported exactly that, two CRITICAL "hardcoded secret" findings pointing
|
|
878
|
+
# at deliberately fake fixtures.
|
|
879
|
+
#
|
|
880
|
+
# Scoped to paths that ANNOUNCE themselves. A real secret in a real source
|
|
881
|
+
# path is still caught by both tiers below.
|
|
882
|
+
case "$file" in
|
|
883
|
+
*.example|*.example.*|*.sample|*.sample.*|*.template|*.template.*|*.dist)
|
|
884
|
+
return 1 ;;
|
|
885
|
+
*/fixtures/*|*/__fixtures__/*|*/tests/*|*/test/*|*test-*.sh|*.test.*|*_test.*)
|
|
886
|
+
return 1 ;;
|
|
887
|
+
esac
|
|
888
|
+
|
|
783
889
|
# TIER 1: specific formats. No deny filter -- a format match is a finding.
|
|
784
890
|
local tier1=(
|
|
785
891
|
'AKIA[0-9A-Z]{16}' # AWS access key id
|
|
@@ -2657,6 +2763,7 @@ verify_main() {
|
|
|
2657
2763
|
verify_gate_build "$tree"
|
|
2658
2764
|
verify_gate_tests "$tree"
|
|
2659
2765
|
verify_gate_static "$tree"
|
|
2766
|
+
verify_gate_nomock "$tree"
|
|
2660
2767
|
verify_gate_secret_scan "$tree"
|
|
2661
2768
|
verify_gate_dependency_audit "$tree"
|
|
2662
2769
|
# Runtime boot smoke (NET-NEW). Self-suppresses (no gate row) when no
|