fix(shadow-replay): exclude runtime writes and scope M6 to bash
This commit is contained in:
+13
-3
@@ -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)),
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user