distillation: fix 7 grounding bugs found by Kimi audit
Kimi For Coding (api.kimi.com, kimi-for-coding) ran a forensic audit on
distillation v1.0.0 with full file content. 7/7 flags verified real on
grep. Substrate now matches what v1.0.0 claimed: deterministic, no
schema bypasses, Rust tests compile.
Fixes:
- mode.rs:1035,1042 matrix_corpus Some/None -> vec![..]/vec![]; cargo
check --tests now compiles (was silently broken;
only bun tests were running)
- scorer.ts:30 SCORER_VERSION env override removed - identical
input now produces identical version stamp, not
env-dependent drift
- transforms.ts:181 auto_apply wall-clock fallback (new Date()) ->
deterministic recorded_at fallback
- replay.ts:378 recorded_run_id Date.now() -> sha256(recorded_at);
replay rows now reproducible given recorded_at
- receipts.ts:454,495 input_hash_match hardcoded true was misleading
telemetry; bumped DRIFT_REPORT_SCHEMA_VERSION 1->2,
field is now boolean|null with honest null when
not computed at this layer
- score_runs.ts:89-100,159 dedup keyed only on sig_hash made
scorer-version bumps invisible. Composite
sig_hash:scorer_version forces re-scoring
- export_sft.ts:126 (ev as any).contractor bypass emitted "<contractor>"
placeholder for every contract_analyses SFT row.
Added typed EvidenceRecord.metadata bucket;
transforms.ts populates metadata.contractor;
exporter reads typed value
Verification (all green):
cargo check -p gateway --tests compiles
bun test tests/distillation/ 145 pass / 0 fail
bun acceptance 22/22 invariants
bun audit-full 16/16 required checks
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -451,7 +451,7 @@ export function buildDrift(current: RunSummary, prior: RunSummary | null): Drift
|
||||
delta_accepted: cur.accepted,
|
||||
delta_quarantined: cur.quarantined,
|
||||
pct_change_out: null,
|
||||
input_hash_match: false,
|
||||
input_hash_match: null, // no prior stage to compare
|
||||
output_hash_match: false,
|
||||
deterministic_violation: false,
|
||||
notes: ["stage not present in prior run"],
|
||||
@@ -461,12 +461,12 @@ export function buildDrift(current: RunSummary, prior: RunSummary | null): Drift
|
||||
}
|
||||
const pct = pctChange(pri.records_out, cur.records_out);
|
||||
const out_match = pri.output_hash === cur.output_hash;
|
||||
const inp_match = (current.stages.find(s => s.stage === cur.stage)?.output_hash ?? "")
|
||||
!== "" /* placeholder */;
|
||||
// We have output_hash on stage summaries but not input_hash —
|
||||
// input_hash lives on the full StageReceipt, which we can re-read
|
||||
// from the run dir if needed. For simplicity, drift compares the
|
||||
// OUTPUT hashes (what really changed).
|
||||
// input_hash is NOT materialized into stage summaries (lives on the
|
||||
// per-stage StageReceipt files on disk). We don't load them here, so
|
||||
// we honestly report null. Schema v2 makes this explicit; v1 returned
|
||||
// `true` unconditionally which made deterministic_violation always
|
||||
// false even when it should have alerted. Cross-run determinism
|
||||
// enforcement is its own pass — see ./scripts/distill audit-full.
|
||||
const notes: string[] = [];
|
||||
if (pct !== null && Math.abs(pct) > DRIFT_THRESHOLD_PCT) {
|
||||
const dir = pct > 0 ? "spike" : "drop";
|
||||
@@ -492,9 +492,9 @@ export function buildDrift(current: RunSummary, prior: RunSummary | null): Drift
|
||||
delta_accepted: cur.accepted - pri.accepted,
|
||||
delta_quarantined: cur.quarantined - pri.quarantined,
|
||||
pct_change_out: pct,
|
||||
input_hash_match: true, // simplified — see comment above
|
||||
input_hash_match: null, // not computed at this layer; see comment above
|
||||
output_hash_match: out_match,
|
||||
deterministic_violation: false, // requires input_hash match, see future tightening
|
||||
deterministic_violation: false, // requires input_hash match — null means "unknown", not "verified"
|
||||
notes,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user