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:
@@ -86,6 +86,17 @@ function gitDirty(root: string): boolean {
|
||||
return r.status === 0 && r.stdout.trim().length > 0;
|
||||
}
|
||||
|
||||
// Composite dedup key — `sig_hash:scorer_version`. Keying on sig_hash
|
||||
// alone made scorer-rule bumps invisible: a bumped SCORER_VERSION
|
||||
// produced different scoring categories, but pre-existing rows on disk
|
||||
// (with the OLD version) still matched the new sig_hash and the new
|
||||
// scoring was silently skipped. Compositing version forces re-scoring
|
||||
// when the version changes. Caller tags `scorer_version` on the
|
||||
// ScoredRun row, which we read alongside sig_hash.
|
||||
function dedupKey(sig_hash: string, scorer_version: string): string {
|
||||
return `${sig_hash}:${scorer_version}`;
|
||||
}
|
||||
|
||||
function loadSeenHashes(out_path: string): Set<string> {
|
||||
const seen = new Set<string>();
|
||||
if (!existsSync(out_path)) return seen;
|
||||
@@ -93,7 +104,9 @@ function loadSeenHashes(out_path: string): Set<string> {
|
||||
if (!line) continue;
|
||||
try {
|
||||
const row = JSON.parse(line);
|
||||
if (row?.provenance?.sig_hash) seen.add(row.provenance.sig_hash);
|
||||
const sh = row?.provenance?.sig_hash;
|
||||
const sv = row?.scorer_version;
|
||||
if (sh && sv) seen.add(dedupKey(sh, sv));
|
||||
} catch { /* malformed — ignore */ }
|
||||
}
|
||||
return seen;
|
||||
@@ -156,11 +169,12 @@ async function processEvidenceFile(
|
||||
}
|
||||
|
||||
const scored = await buildScoredRun(ev.value as EvidenceRecord, out_relpath, i, opts.recorded_at);
|
||||
if (seen.has(scored.provenance.sig_hash)) {
|
||||
const key = dedupKey(scored.provenance.sig_hash, scored.scorer_version);
|
||||
if (seen.has(key)) {
|
||||
result.rows_deduped++;
|
||||
continue;
|
||||
}
|
||||
seen.add(scored.provenance.sig_hash);
|
||||
seen.add(key);
|
||||
|
||||
const sv = validateScoredRun(scored);
|
||||
if (!sv.valid) {
|
||||
|
||||
Reference in New Issue
Block a user