
Day 17 把 Targeted、Regression、Smoke 各自的責任拆開後,還有一個常見誤區:
同一個 Repository、同一個 commit,Verification 結果應該一樣。
但 Android / Gradle 的實際驗證曾出現另一種情況:同一份 code state 還沒進到測試本體,結果就先被執行環境決定了。
Android SDK、JBR、Gradle cache、shell 與 CLI availability,都可能先於 source code 決定 verification command 能不能成立。
Code Identity 相同,不代表 Execution Environment Identity 相同。
在一個 Android 專案的 verification record 裡,原本要執行:
gradlew.bat :app:testDebugUnitTest
--tests tw.com.dailycheckin.evidence.EvidenceSanitizerTest
這次不是 assertion failure,也不是 Kotlin compile error;實際狀態是:
TEST_NOT_STARTED / ENVIRONMENT_FAILED
原因是當時 local.properties 指向的 Android SDK 不存在。即使換到 sandbox 外重試,仍得到同樣結果;Kotlin compilation 與 Gradle test 都沒有開始。
如果只看到「Gradle command failed」,很容易直接往 source code、test code 或 build script 找問題;但這次失敗發生在測試本體之前。
原本的假設:
command failed → code / test 有問題
被 Evidence 改成:
command 是否真的進入被驗證的那一層,要先看 execution environment。
因此非零 exit 不能一律叫做「測試失敗」;先確認 command 是否真的進入被驗證的那一層。
同一份 verification record 後來使用既有 cache、可用的 JBR runtime 與 Kotlin compiler 做較小範圍的 fallback verification。
結果:
pure Kotlin compilation: PASS
JUnit: OK (5 tests)
但這份 Evidence 明確留下限制:
重點不是「最後還是有 PASS」,而是 fallback 改變了 Verification Scope。
原本要證明的是 Android / Gradle 路徑中的特定 test;fallback 最後只能證明部分純 Kotlin logic 在另一組 runtime/toolchain 條件下成立。
所以不能寫成:
Gradle test failed
→ fallback passed
→ verification passed
比較準確的是:
canonical verification
→ ENVIRONMENT_FAILED / TEST_NOT_STARTED
fallback verification
→ PASS
→ narrower claim only
Execution Environment 一變,Evidence 能支撐的 Claim 也跟著變。

Bento 系列的 Environment Boundary 比較接近:
localhost
vs
remote Worker
vs
production-like runtime
那是在問服務「部署到哪裡」之後,CORS、network path、runtime behavior 是否仍成立。
這篇關心的是 Agent 自己「站在哪裡執行」:
OS
runtime / JBR
Android SDK
Gradle user home / cache
shell
filesystem
sandbox
CLI availability
兩者都叫 environment,但責任不同:Deployment Environment 是被驗證系統所在的位置;Execution Environment 是 Agent 產生 Evidence 時所在的位置。
環境問題如果每次都靠 Agent 臨場猜,會出現另一種 drift:
Session A
→ cache path A
→ PASS
Session B
→ 隨手換另一個 -g path
→ PASS / FAIL
Session C
→ 再重新下載或改 build 設定
最後三個 Session 即使都寫「我有跑 Gradle」,其實未必在同一個 execution contract 下工作。
shared verification rule 對 Windows Gradle 做了更窄的規定:
if ([string]::IsNullOrWhiteSpace($env:GRADLE_USER_HOME)) {
$env:GRADLE_USER_HOME = Join-Path $env:USERPROFILE '.gradle'
}
如果 GRADLE_USER_HOME 已經存在,就保留;如果為空,才從 USERPROFILE 推導。
同時禁止幾種過去看似方便、但會讓環境身分越來越模糊的 fallback:
C:\.gradle
C:.gradle
repo-local .gradle-user
arbitrary -g fallback path
這條規則不是讓 Gradle 比較「聰明」,而是把一部分 environment identity 固定下來,並要求 verification evidence 記錄最後使用的 GRADLE_USER_HOME,以及它是 preserved 還是 derived。
fallback 不是:
canonical command 跑不起來,就想辦法找一條會 PASS 的路。
比較安全的流程是:
canonical command
↓
identify why it cannot run
↓
classify environment / dependency / command failure
↓
use an approved fallback only if one exists
↓
record changed environment + changed scope
↓
limit the completion claim
如果 fallback 需要改 source、改 wrapper version、改 gradle.properties,只是為了解決 cache / permission / SDK 狀態,那通常已經跨過合理邊界。
環境問題不該被偽裝成 code fix。
遇到 verification 差異時,可以先分成兩組問題。
第一組:
same HEAD
same command
different environment
different result
優先比較:
第二組:
same environment
different HEAD
different result
才更有理由優先追 code change。
這個分流主要是避免一種高成本誤判:
為了修執行環境,去修改原本沒有壞的程式。
長期 Repository 不一定需要厚重的 setup manual。
但如果它要求 Agent 自主 verification,至少要能回答:
這些資訊不只是 onboarding;它們會直接決定一份 PASS 能被帶多遠。
Day 16 把 Evidence Provenance 補上 state、method、environment、limitation;Day 17 再拆開不同 test type 各自在證明什麼。
Day 18 補上一個常被省略的判斷:
Environment 不是 PASS 旁邊的附註,而是 PASS 成立條件的一部分。
所以一個可信的 verification claim 比較像:
在某個 state、某個 execution environment,用某個 method,針對某個 risk 得到 PASS;fallback 若改變 runtime 或 scope,就另外標示,其餘範圍未驗。
這比單純一句「tests passed」少了一點俐落,卻多了下一個人真正能判斷的資訊。
下一篇會把焦點從 Evidence 切回 Authority:即使 Agent 已經證明自己有能力執行某件事,也不代表這次就被授權執行那個 action。