fix(shadow-replay): exclude runtime writes and scope M6 to bash

This commit is contained in:
Дмитрий
2026-06-09 07:20:48 +03:00
parent 926c329b12
commit 3663705dde
2 changed files with 35 additions and 3 deletions
+13 -3
View File
@@ -134,12 +134,20 @@ export function loadEpisodes(dir = 'docs/observer', fsImpl = fsDefault) {
return out;
}
/** Приближённые безобидные file-события из эпизодов: каждый touched-файл → Edit. */
// Запись в runtime/секрет — НЕ benign правка обычного файла (пол блокирует по делу); исключаем
// из приближённых безобидных событий, иначе даёт ложный over-block (отчёт 2026-06-09).
const RUNTIME_OR_SECRET_RE = /\.claude[\\/]runtime[\\/]|(^|[\\/])\.env(\.[\w-]+)?$|\.(pem|key|p12|pfx)$|id_(rsa|dsa|ecdsa|ed25519)/i;
/** Приближённые безобидные file-события из эпизодов: touched-файл → Edit (кроме runtime/секрет). */
export function episodeBenignEvents(records) {
const out = [];
for (const r of records || []) {
const files = (r && r.task_size && Array.isArray(r.task_size.files)) ? r.task_size.files : [];
for (const fp of files) if (typeof fp === 'string' && fp) out.push({ name: 'Edit', input: { file_path: fp } });
for (const fp of files) {
if (typeof fp !== 'string' || !fp) continue;
if (RUNTIME_OR_SECRET_RE.test(fp)) continue;
out.push({ name: 'Edit', input: { file_path: fp } });
}
}
return out;
}
@@ -218,9 +226,11 @@ export function renderReport(byMachine, { divergence } = {}) {
/** Прогнать все 5 машин + М3-расхождение, вернуть {md, byMachine, divergence}. */
export function runAll({ episodeRecords = [] } = {}) {
const corpus = buildCorpus({ episodeRecords });
// М6 (снимок) — зона только Bash-floor; не-Bash рискованное держит пол М5, не М6 (отчёт 2026-06-09).
const corpusM6 = corpus.filter((e) => e.kind !== 'risky' || (e.toolUse && e.toolUse.name === 'Bash'));
const byMachine = {
M5: summarize(runMachine('M5', corpus)),
M6: summarize(runMachine('M6', corpus)),
M6: summarize(runMachine('M6', corpusM6)),
M2: summarize(runMachine('M2', M2_EVENTS, { frozenPlan: freezeReplayPlan(M2_PLAN_STEPS) })),
M4: summarize(runMachine('M4', M4_FIXTURE)),
};
+22
View File
@@ -181,3 +181,25 @@ describe('runAll', () => {
expect(r.divergence.total).toBe(0);
});
});
describe('harness fixes (отчёт 2026-06-09)', () => {
it('episodeBenignEvents исключает запись в runtime/секрет (не benign)', () => {
const recs = [{ task_size: { files: [
'app/A.php',
'C:/Users/x/.claude/runtime/router-state.json',
'app/.env',
] } }];
expect(episodeBenignEvents(recs)).toEqual([
{ name: 'Edit', input: { file_path: 'app/A.php' } },
]);
});
it('runAll: запись в runtime из эпизода НЕ даёт ложный over-block М5', () => {
const r = runAll({ episodeRecords: [{ task_size: { files: ['C:/Users/x/.claude/runtime/state.json'] } }] });
expect(r.byMachine.M5.counts['over-block']).toBe(0);
});
it('runAll: М6 не штрафуется за не-Bash рискованное (miss=0), но ловит Bash-floor (real-catch=2)', () => {
const r = runAll({ episodeRecords: [] });
expect(r.byMachine.M6.counts.miss).toBe(0);
expect(r.byMachine.M6.counts['real-catch']).toBe(2);
});
});