TypeScript 編譯通過,但 dependency signature 已漂移
危險的 dependency change 不一定讓 build 立刻變紅。Application file 沒改、既有 call 仍然
type-correct,tsc --noEmit 也回傳 0;但當初寫下程式所依賴的
external assumption 已經不再成立。
先自己跑一次
npx --yes evidrift@0.3.2 demo
不需要帳號、API key、cloud service、package-code execution 或 LLM verdict。指令會在
.evidrift-demo/signature-drift 建立有 marker 的可丟棄 workspace。
Dependency 原本的 contract
本機 fixture package 一開始提供這份 declaration:
export interface ParseOptions { strict?: boolean; }
export interface ParseResult { value: string; }
export declare function parseConfig(
input: string,
options?: ParseOptions,
): ParseResult;
Consumer application 已經傳入 options:
import { parseConfig } from '@evidrift/demo-contract';
const options = { strict: true } as const;
export const parsed = parseConfig('demo', options);
因為這個 call 有提供第二個 argument,所以不論 parameter 是 optional 或 required,這一行都能通過 TypeScript compiler。
Evidrift 記錄了什麼
Evidrift 記錄實際安裝的 declaration、package version、resolved declaration path、affected call site、normalized signature 與 signature hash。這個 fixture 的 claim 刻意保持狹窄:
parseConfigaccepts the optionaloptionsparameter used by this demo.
第一次檢查會重新計算 declaration,結果通過:
PASS sha256:01c766cf…
Expected signature: parseConfig(input:string,options?:ParseOptions):ParseResult
Current signature: parseConfig(input:string,options?:ParseOptions):ParseResult
Affected code location: app/src/index.ts:3
Receipt ID: sha256:01c766cf…
Summary: 1 pass, 0 warning, 0 fail
Dependency 改了,consumer 沒改
Fixture 只修改 declaration:
export declare function parseConfig(
input: string,
- options?: ParseOptions,
+ options: ParseOptions,
): ParseResult;
Application 原本就有傳 options,所以 drifted workspace 仍然能編譯。本次
evidrift-0.3.2.tgz packed-install UAT 在 2026-07-17 對 demo 產生的 drifted
workspace 重跑 tsc --noEmit --strict,TypeScript exit code 是
0。
但這不代表 external contract 沒變。Parameter 已經不再 optional。之後任何省略
options 的 call、generated branch、test fixture,或依賴舊 optionality 的
Coding Agent,都拿著過期的 evidence。
Evidrift 看見的差異
evidrift check 會重新載入 source 與計算 signature,不相信 Receipt 裡儲存的
matched 或 verified flag:
FAIL contract_mismatch sha256:01c766cf…
Message: Deterministic TypeScript contract mismatch:
Previously recorded TypeScript signature was not found.
Claim: parseConfig accepts the optional options parameter used by this demo.
Expected signature: parseConfig(input:string,options?:ParseOptions):ParseResult
Current signature: parseConfig(input:string,options:ParseOptions):ParseResult
Affected code location: app/src/index.ts:3
Receipt ID: sha256:01c766cf…
Action: Review the dependency change and affected code, then intentionally record new evidence.
Summary: 0 pass, 0 warning, 1 fail
結果是 deterministic:selected normalized signature 改變,因此 check exit code 是
1。沒有 semantic similarity score,也沒有 LLM judge。
為什麼這個缺口值得處理
- Consumer diff 可以是空的,但 installed dependency declaration 已改變。
- TypeScript 能證明這個 call 現在 type-correct,卻不保存「parameter 原本可省略」這項 historical assumption。
- Code review 與一般 static analysis 通常檢查正在改的程式,不會保留舊程式當初成立的外部理由。
- Receipt 讓 CI 能把 old contract、current contract、affected file 與 content-addressed ID 交回 developer 或 Coding Agent。
FAIL 之後該怎麼做
不要直接重新產生 Receipt 來讓 CI 變綠。
- 閱讀 dependency changelog,確認 signature change 是否刻意。
- 檢查 affected call 與可能省略
options的其他 path。 - 若接受新 contract,更新 code、tests 與 written claim。
- 刻意執行
evidrift record,建立新的 evidence。
它沒有證明什麼
Evidrift 不證明 runtime correctness、不證明每個 call site 都相容,也不判斷 dependency change 是好是壞。它證明的是一件更小但可驗證的事:為這個 affected line 記錄的 deterministic external contract,已經不是目前安裝的 contract。