From 849723bc04d9c7b6c62252a24f550a1d4efa84b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Sun, 7 Jun 2026 16:18:44 +0300 Subject: [PATCH] =?UTF-8?q?fix(m5):=20F-6=20=E2=80=94=20=D0=BD=D0=B8=D0=B6?= =?UTF-8?q?=D0=BD=D1=8F=D1=8F=20=D0=B3=D1=80=D0=B0=D0=BD=D0=B8=D1=86=D0=B0?= =?UTF-8?q?=20=D0=B2=D1=80=D0=B5=D0=BC=D0=B5=D0=BD=D0=B8=20=D0=B2=20floor-?= =?UTF-8?q?decide=20approvalOpen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit approvalOpen считал свежим одобрение с будущим ts (now - ts < 0 <= window) — часовой сдвиг/подлог открывал дверь владельца. Добавлена нижняя граница now - ts >= 0: свежесть = ts в прошлом И в пределах окна. Аудит Машины 5 (объектив корректность). TDD RED->GREEN. Регрессия tools-only 2789 + 2 skip. Co-Authored-By: Claude Opus 4.8 (1M context) --- tools/floor-decide.mjs | 5 ++++- tools/floor-decide.test.mjs | 8 ++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/tools/floor-decide.mjs b/tools/floor-decide.mjs index 88862afa..e8054297 100644 --- a/tools/floor-decide.mjs +++ b/tools/floor-decide.mjs @@ -76,7 +76,10 @@ export function approvalOpen(command, approvedGitOps, now) { const target = normCmd(command); if (!target) return false; return approvedGitOps.some( - (op) => op && normCmd(op.command) === target && typeof op.ts === 'number' && now - op.ts <= APPROVE_WINDOW_MS, + // F-6: свежесть = ts в прошлом И в пределах окна. Нижняя граница (now - ts >= 0) отсекает + // одобрение с будущим ts (часовой сдвиг/подлог), которое иначе открывало бы дверь. + (op) => op && normCmd(op.command) === target && typeof op.ts === 'number' + && now - op.ts >= 0 && now - op.ts <= APPROVE_WINDOW_MS, ); } diff --git a/tools/floor-decide.test.mjs b/tools/floor-decide.test.mjs index 141e6019..224f8d84 100644 --- a/tools/floor-decide.test.mjs +++ b/tools/floor-decide.test.mjs @@ -99,6 +99,14 @@ describe('floorDecide — дверь владельца Δ1 (read-only approval, const r = floorDecide({ toolUse: bash('php artisan migrate:fresh'), approvedGitOps: [], now, normalizeImpl: id }); expect(r.block).toBe(true); }); + // F-6 (аудит 2026-06-07): floor-decide.mjs approvalOpen без нижней границы времени — + // одобрение с будущим ts (now - ts < 0 ≤ window) открывало дверь. Свежесть требует ts в прошлом. + it('одобрение с будущим ts (часовой сдвиг/подлог) → block (нижняя граница времени)', () => { + const cmd = 'php artisan migrate:fresh'; + const approvedGitOps = [{ command: cmd, ts: now + 60 * 1000 }]; + const r = floorDecide({ toolUse: bash(cmd), approvedGitOps, now, normalizeImpl: id }); + expect(r.block).toBe(true); + }); }); describe('floorDecide — observe-only / прочее не блокируется', () => {