Commit Graph

425 Commits

Author SHA1 Message Date
Дмитрий 02e3ff7379 feat(m7-phase4a): coverage-verify — журнал-факт K2 (turn∩journal) + fail-CLOSE + снят override (§4.2)
coverage skill:X теперь требует X в ПЕРЕСЕЧЕНИИ «Skill-tool_use этого хода ∩ журнал
вызовов» (turn-scope от границы хода transcript, факт от журнала М1 через skillTakenByJournal
K2). Не по строке coverage: — Класс 1 закрыт; turn-scoping без false-pass (X из прошлого хода
в журнале, но не в transcript этого хода → block). direct/node/chain/hook/agent приняты на
этом слое (журналом не верифицируемы, §4.2). main обёрнут exitDisciplineDecision (fail-CLOSE
Фазы 0). Override-вокабуляр снят (§12 escape≠override). 9/9 тестов GREEN.
2026-06-08 11:07:35 +03:00
Дмитрий 5320de8371 feat(m7-phase3): enforce-skill-journaler в FAIL_CLOSE_DISCIPLINE_HOOKS (P-7, манифест Фазы 6) 2026-06-08 10:36:42 +03:00
Дмитрий 3fad5e0401 feat(m7-phase3): SEED_SKILLS +реактивные дисциплинарные навыки (SE-K — стена не рубит вне плана) 2026-06-08 10:35:33 +03:00
Дмитрий 576e9c6079 feat(m7-phase3): enforce-skill-journaler — PostToolUse(Skill) журнал op:Skill + мост K2 (SE-K) 2026-06-08 10:34:19 +03:00
Дмитрий 317f8cb6fb feat(m7-phase2): supreme-gate panicEscapeDecision — escape переживает сбой сетапа main (правило 7б) 2026-06-08 10:28:43 +03:00
Дмитрий b6fe66e34c feat(m7-phase2): normative-content-rules чтит escape — §6 канал правки ЗАКОНА (правило 7в) 2026-06-08 10:25:00 +03:00
Дмитрий 73fa2d61ff feat(m7-phase2): read-path-deny чтит escape владельца (правило 7в) 2026-06-08 10:21:34 +03:00
Дмитрий 9148c8c6bd feat(m7-phase2): enforce-floor panic-ветка — escape переживает throw floorDecide (правило 7б) 2026-06-08 10:19:48 +03:00
Дмитрий f71f1abef8 feat(m7-phase2): escapeAllowsEvent — panic-предикат escape-survivability (правило 7б) 2026-06-08 10:17:43 +03:00
Дмитрий 91a5acc4bf fix(m7-phase0): disciplineOutcome строгий fail-CLOSE на малформ-возврат (sharp-edges) 2026-06-08 10:07:52 +03:00
Дмитрий dc30c5daee feat(m7-phase0): disciplineOutcome fail-CLOSE + P-7 списки + контракт helpers:7 (правило 1) 2026-06-08 10:02:43 +03:00
Дмитрий bbd66c9b61 feat(m7-phase0): canonicalAction тотальна — внешний try + pathNormalizeSafe (правило 7а) 2026-06-08 09:59:29 +03:00
Дмитрий 06ad12cd94 feat(m7-phase0): pathNormalizeSafe — тотальный normalize (правило 7а) 2026-06-08 09:57:41 +03:00
Дмитрий 8e56df3842 refactor(m7-floor): PS-content единый источник — matchPsHardBlacklist в shell-content-rules (variant-analysis)
Закрывающий variant-analysis-гейт Фазы 1 вскрыл класс P-1 для PowerShell: у
powershell-gate был СВОЙ PS_HARD_BLACKLIST (29 паттернов), а пол использовал
отдельный узкий psContentBlock (7) — подмножество, которое дрейфовало бы (та же
проблема, что P-1 для Bash). После Фазы 8 (увольнение powershell-gate) пол оказался
бы слабее гейта, который он заменяет. Решение владельца: исправить сейчас.

Зеркало P-1:
- PS_HARD_BLACKLIST + matchPsHardBlacklist перенесены в единый дом shell-content-rules;
  powershell-gate ре-экспортирует (тест single-source-identity: ссылка gate === SCR).
- +bare-egress (Invoke-WebRequest/iwr/irm/curl/wget bare — floor НЕ default-deny, нужен
  в blacklist, не только в whitelist гейта) +rmdir +rm (алиасы Remove-Item, которые гейт
  ловил whitelist'ом default-deny — полу нужны явно).
- psContentBlock стал ТОНКИМ делегатом над matchPsHardBlacklist (симметрия с
  bashIsContentBlock); пол через него видит ТОТ ЖЕ набор, что гейт. Дрейф невозможен.
- Следствие (осознанно): floor теперь блокирует все Set-Content/sc/$env/Az/… как гейт
  (симметрия с Bash-полом, блокирующим все cp/mv/redirect). Escapable. FP-толерантность
  унаследована от гейта (например `sc query`/`del.txt` — gate-aligned, fail-safe).

powershell-destructive.mjs физически не удалён (живые gate'ы блокируют rm/git rm) —
оставлен тонким делегатом (НЕ второй источник). Удаление — follow-up по git-approval.

Регрессия tools-only: 3044 passed + 2 skip (baseline 2843+2, 0 регрессий).
2026-06-08 09:34:23 +03:00
Дмитрий 473fd21136 feat(m7-floor): §12 content-floor инвариант — весь BASH_HARD_BLACKLIST floored (P-6)
Task 1.6 Фазы 1 М7. Тест-генератор: КАЖДАЯ запись BASH_HARD_BLACKLIST рубится полом
даже как валидный шаг плана. Итерация по экспортированному списку → полнота порта ПО
КОНСТРУКЦИИ: новый паттерн без сэмпла → красный (drift-детектор), floor не рубит →
красный. Анти-регресс «непробиваемости» закреплён за Фазой 1 (P-6), не за §9.2-smokes.
+C16 stderr-redirect + #34 injection (отдельные ветки matchBashHardBlacklist).

Полная регрессия tools-only: 2997 passed + 2 skip (baseline 2843+2, +154 Фазы 1,
0 регрессий). 99 floor-decide GREEN.
2026-06-08 09:16:04 +03:00
Дмитрий 6556f5ca0a test(m7-floor): инвариант escape снимает content-block (Bash+PS) + специфичность P-2
Task 1.5 Фазы 1 М7. Код уже escapable (1.3/1.4) — тесты фиксируют инвариант против
регресса. Покрыто: Bash node -e + PS Remove-Item + PS forge-write снимаются точным
грантом; P-2 специфичность (грант A не открывает команду B) для PS И Bash; кросс-shell
изоляция (Bash-грант не открывает PS-команду — разные canonicalAction-префиксы). 72 GREEN.
2026-06-08 09:13:55 +03:00
Дмитрий 183733835f harden(m7-floor): PS-алиасы forge+delete (sharp-edges после 1.4)
Строгий sharp-edges-гейт после Task 1.4 вскрыл обход через PowerShell-алиасы:
- forge P-3: `sc`/`cpi`/`ni` (алиасы Set-Content/Copy-Item/New-Item) писали в
  ~/.claude/runtime/.env мимо литеральных глаголов → подделка escape-гранта.
  Закрытие: PS_WRITE_VERB_RE += sc/ac/cpi/copy/mi/move/ni/tee (path-gated, FP только
  с протектед-путём; контроль `sc query` не over-блокируется).
- delete: `del`/`ri`/`rd`/`rmdir`/`erase` -Recurse -Force обходили литеральный
  Remove-Item. Закрытие: PS_CONTENT_BLOCK_RE алиасы для long+short флагов.

154 GREEN (floor + powershell-destructive + enforce-floor + supreme-gate).
Отложено в Фазу 8 §5 (карты покрытия): `&` call-operator, Invoke-Command (icm),
PS sub-expression $(), полная enumeration алиасов — coverage, не структурный класс.
2026-06-08 09:12:38 +03:00
Дмитрий ea83a714e4 feat(m7-floor): floor-decide ветка PowerShell content-block + forge-страж (V1-PS, P-3)
Task 1.4 Фазы 1 М7. Ветка PowerShell пола (после Bash, до OBSERVE_TOOLS): psContentBlock
(Remove-Item/-Recurse/iwr/iex/Start-Process/Out-File/redirect) ИЛИ psProtectedWrite
(P-3 forge-страж: PS-запись в ~/.claude/runtime / .env / секрет — иначе Set-Content
подделывает escape-грант). Escapable owner-санкцией. Реоткрытие v3.8 F1. 60 GREEN.

НАХОДКА РЕАЛИЗАЦИИ: plan-версия psProtectedWrite тестила whole-string SECRET_PATH_RE
с $-якорем → `.env` в позиции аргумента (-Path app/.env -Value …) терялся. Робастнее:
проверяем каждый токен (без кавычек, \→/) против anchored RUNTIME_RE/SECRET_PATH_RE —
runtime (forge-вектор) И secret-write закрыты, обычная запись не over-блокируется.
2026-06-08 09:09:18 +03:00
Дмитрий 7277584eaf harden(m7-floor): bashIsContentBlock рубит sub-shell как класс (sharp-edges после 1.3)
Строгий sharp-edges-гейт после Task 1.3 вскрыл класс обхода: подстрочный
matchBashHardBlacklist не де-обфусцирует command-substitution. Split-assembly
`$(echo no)$(echo de) -e x` и backtick `echo node` собирают интерпретатор только
при shell-eval → в сырой строке 'node' нет → content-block FALSE → пол пропускал.
router-gate ловит сейчас, но Фаза 8 (увольнение router-gate) открыла бы класс.

Закрытие: bashIsContentBlock проверяет detectSubshell(raw).found ($()/backtick/
process-subst/heredoc) → любой sub-shell = произвольное исполнение → content-block.
Независимо от parse-успеха. Escapable; router-gate тоже блокирует все sub-shell →
0 новых FP. Подтверждено: per-segment токенайзер де-обфусцирует n''ode/no\de.
114 GREEN (floor + enforce-floor + supreme-gate).
2026-06-08 09:06:23 +03:00
Дмитрий 89e9ca159e feat(m7-floor): floor-decide content-block ветка Bash (V1, escapable)
Task 1.3 Фазы 1 М7. bashIsContentBlock (whole+per-segment, паритет с bashIsFloor, P-4)
через единый matchBashHardBlacklist (P-1). floorDecide Bash-ветка зовёт content-block
ПЕРВЫМ (до bashIsFloor); escape снимает (owner-санкция). 44 GREEN.

НАХОДКА АУДИТА (задокументирована в коде+тесте): NB плана «echo "node -e foo" НЕ
over-блокируется» недостижим при подстрочном matchBashHardBlacklist (не отличает
опасную строку-аргумент echo от команды-интерпретатора). Решение — принять FP:
floor УЖЕ принял этот класс для `git push "--force"` (fail-safe, escapable);
under-block в полу страшнее over-block. Парсинг командной позиции НЕ вводим.
2026-06-08 09:01:04 +03:00
Дмитрий a5b62fbfad fix(m7-floor): canonicalAction +PowerShell — escape-привязка специфична (P-2)
Task 1.2b Фазы 1 М7 (КРИТ). canonicalAction получил ветку PowerShell:
`powershell:${normalizeCommand(command)}`. Без неё PS уходил в write-fallback,
пустой путь резолвился в cwd → один escape-грант разблокировал ЛЮБУЮ PS-команду
в окне (тест специфичности был зелёным ложно: a===b==='write:<cwd>').

Сквозной фикс: тот же canonicalAction зовут пол (Task 1.4), стена (enforce-supreme-gate)
и консьюмер. Bash/Write/mcp-ветки не задеты. 118 GREEN (escape-grant + 4 потребителя).
2026-06-08 08:56:44 +03:00
Дмитрий 91af38ca11 feat(m7-floor): powershell-destructive psContentBlock (V1-PS)
Task 1.2 Фазы 1 М7. Новый модуль tools/powershell-destructive.mjs — psContentBlock
для PowerShell-tool (PS-нативные глаголы Remove-Item/-Recurse/iwr/iex/Start-Process/
Out-File/redirect НЕ матчат unix-regex classify-destructive). Реоткрытие v3.8 F1:
PowerShell-tool был полностью вне scope content-floor. 16 GREEN.
2026-06-08 08:55:30 +03:00
Дмитрий 3847c863ca feat(m7-floor): classify-destructive +contentBlock (правило 8, V1 Bash)
Task 1.1 Фазы 1 М7. Поле contentBlock = matchBashHardBlacklist(cmd) !== null
(единый источник P-1, whole-string) рядом с floor/suspicious. suspicious ||=
contentBlock (P-5: голоса судьи М4 видят content-опасное). reason расширен.

Поле для видимости судьи; фактический блок пола — bashIsContentBlock
(whole+per-segment, Task 1.3). 71 GREEN (+38 content-block кейсов, incl.
P-1-пробелы env-prefix/--watch/2>file/cp/mv).
2026-06-08 08:53:53 +03:00
Дмитрий 1c251d2592 refactor(m7-floor): matchBashHardBlacklist -> shell-content-rules (единый дом content-правил, P-1)
Task 1.0.5 Фазы 1 М7. Перенос BASH_HARD_BLACKLIST + stderrRedirectBlock +
matchBashHardBlacklist из enforce-router-gate.mjs в постоянный дом
shell-content-rules.mjs (там уже живут hasInjection + matchAny). router-gate
ре-экспортирует их для обратной совместимости (тесты + тело гейта).

Единый источник правды устраняет port-дрейф content-floor (М5) по конструкции:
content-block пола (Task 1.1/1.3) импортирует ТОТ ЖЕ матчер, а не ручную копию.

Тесты: +describe single-source identity (router-gate BASH_HARD_BLACKLIST ===
shell-content-rules ссылка) + matchBashHardBlacklist hosted-in-SCR. 233 GREEN.
Чистый рефактор-перенос, 0 изменений семантики.
2026-06-08 08:51:31 +03:00
Дмитрий d221ba499d fix(m6): аудит-правки — G-5 egress токен, единый findOpenGrant, escape-журнал, уникальный id снимка
Аудит М6 (audit-context-building + sharp-edges + корректность; комплекс М1–М6), 4 практичных фикса (TDD):
- FIX-1: enforce-mcp-classification печатает точный FLOOR-ESCAPE токен в egress/verdict-блоке (G-5 для egress).
- FIX-2: escape-grant.findOpenGrant — единый предикат свежести для open и consume (гасит ИМЕННО открывший грант; чинит утечку one-shot при дублях/future-ts).
- FIX-3: enforce-supreme-gate.runGate — best-effort журнал escape (escape:true), указатель не двигается, сбой журнала не блокирует.
- FIX-4: enforce-snapshot — уникальный дефолтный id снимка (ts-pid-счётчик) против ms-коллизии refs/floor-snapshots.

Регрессия tools-only 2843 passed + 2 skip (+9, 0 регрессий). FIX-5 (подпись гранта) сознательно не делали (нулевая защита без ключа; protected-path уже гарантирует).
2026-06-07 19:43:05 +03:00
Дмитрий d0e0bd18c9 test(m6): сквозные инварианты escape + snapshot; регрессия зелёная (2834+2) 2026-06-07 19:04:57 +03:00
Дмитрий 28b92d90e6 feat(m6): enforce-snapshot — git-точка возврата перед разрушительным (fail-close) 2026-06-07 19:03:36 +03:00
Дмитрий a5b99eaa7e feat(m6): snapshot-decide — триггер снимка + чистое-дерево vs ошибка 2026-06-07 19:02:21 +03:00
Дмитрий 6e2d485f44 feat(m6): egress-escape — снятие egress-блока совпавшим floor_escape 2026-06-07 19:01:04 +03:00
Дмитрий b83cfc65b9 feat(m6): floor-escape-consume — одноразовое погашение пропуска (PostToolUse) 2026-06-07 18:58:32 +03:00
Дмитрий 4a10932cb6 feat(m6): G-1 сквозной escape в верховной стене М2 (allow без продвижения указателя) 2026-06-07 18:55:56 +03:00
Дмитрий d2109ac1dc feat(m6): пол — escape во всех ветках, замена approvalOpen 2026-06-07 18:52:34 +03:00
Дмитрий a9e8585767 feat(m6): писать floor_escape-пропуск из AskUser-ответа 2026-06-07 18:46:13 +03:00
Дмитрий 6b44e7afd8 feat(m6): toFloorEscapeRecord — распознавание escape-одобрения 2026-06-07 18:44:09 +03:00
Дмитрий 87d84a2e3f feat(m6): escape-grant pure core — canonicalAction + escapeGrantOpen + readers 2026-06-07 18:41:42 +03:00
Дмитрий 849723bc04 fix(m5): F-6 — нижняя граница времени в floor-decide approvalOpen
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) <noreply@anthropic.com>
2026-06-07 16:18:44 +03:00
Дмитрий 7dd3a16531 fix(m5): F-3 — манифест floor-manifest-check проверяет весь защитный контур
DEFAULT_REQUIRED_HOOKS проверял только enforce-floor — owner мог зарегистрировать пол,
забыть верховную стену / exfil-стражей и получить зелёный «protected». Расширено до
security-load-bearing набора: enforce-floor + enforce-supreme-gate + normative-content
+ read-path-deny + mcp-classification. «Пол подтверждён» теперь = весь контур. WARN-only
(Δ8 — сигнал, не блок); owner может передать иной requiredHooks.

Аудит Машины 5 (объектив sharp-edges). TDD RED->GREEN. Регрессия tools-only 2789 + 2 skip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 16:18:26 +03:00
Дмитрий 9c3205ad7c fix(m5): F-1 — убрать мёртвый контент-скан в enforce-read-path-deny
decide() гейтил по content в ветке, недостижимой в проде: enforce-read-path-deny —
PreToolUse(Read)-хук, main() не передавал content, а контента до чтения нет. Ветка
+ импорт scanSecrets убраны — decide() гейтит строго по пути (path-deny). Реальный
exfil (исходящий payload) закрыт живым enforce-mcp-classification.scanEgress; чтение
секрета само по себе не вынос.

Аудит Машины 5 (объектив sharp-edges + agentic-actions-auditor). TDD RED->GREEN.
Регрессия tools-only 2789 passed + 2 skip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 16:18:05 +03:00
Дмитрий 5d350b6997 feat(m5): Пакет 8 — Δ3 честный двухтакт reconcile (8.1 пред-запись + 8.2 реконсилер)
Δ3: убрано обещание «атомарно на исполнении» (PreToolUse не видит факт). Достижимый максимум —
два такта:
- 8.1 (runGate): пред-запись НАМЕРЕНИЯ в журнал ДО allow. Журнал вернул false ИЛИ бросил →
  стена НЕ разрешает (block), указатель не двигается («нет записи → нет действия», явно).
  Backward-compat: push → length (truthy) = успех; только явный false/throw → block.
- 8.2 (enforce-reconcile.mjs, новый): PostToolUse-сверка. reconcileAction — исполненное
  действие без пред-записи → action-without-record (возможен обход). findOrphanIntents —
  пред-записи без исполнения → record-without-action. WARN-уровень (не блок: PreToolUse-пол
  уже отработал, PostToolUse не отменяет исполненное). Чистые функции + fail-quiet I/O main.

+2 (supreme-gate runGate) +5 (reconcile) тестов. Полная tools-only регрессия 2788 + 2 skip
(0 регрессий). Машина 5 (Пакеты 5-8) собрана полностью.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 15:29:48 +03:00
Дмитрий 7b2a3d32aa feat(m5): 7.6 инвариант разделения дисциплина/защита (Пакет 7, Блок 4.6)
Структурный guard-инвариант: модули защиты-пола GREEN (criterion-green, floor-signer)
не несут override-вокабуляра — мутация P18 + неподделываемый по-критерийный GREEN (Пакет 5)
это ЗАЩИТА (без override). tdd-gate остаётся ДИСЦИПЛИНОЙ (fail-open + override) в ОТДЕЛЬНОМ
хуке. Перенос мутации/GREEN-пола под override упадёт здесь. +positive control (не вакуумен).
7.7 (неподделываемый GREEN/RED) — закрыт Пакетами 3+5.

Guard-тест (prod-код не пишется; концепт реализован Пакетом 5). +3 теста.
Полная tools-only регрессия 2781 passed + 2 skip (0 регрессий). Пакет 7 завершён.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 15:24:52 +03:00
Дмитрий a88a80ed0b feat(m5): 7.5 parallel-session-lock pid-liveness (Пакет 7, дыра 5, Блок 4.5)
Закрыта дыра 5: свежий (не-stale) лок МЁРТВОГО держателя блокировал на весь TTL.
- isProcessAlive(pid): process.kill(pid,0) — ESRCH→мёртв, EPERM→жив; невалидный pid→мёртв.
- acquire +isPidAlive (инъектируемый): перехват, если держатель не жив (помимо stale/same-session).
  Без isPidAlive — backward-compat (старое поведение).
- runAcquireDecision +isPidAlive (default isProcessAlive) → acquire. Живой держатель → блок;
  мёртвый → перехват. Хук остаётся fail-open (availability, не защитный пол).

+5 (pure) +1 (wrapper) тестов; существующий «fresh lock» тест уточнён на живого держателя.
parallel-session-lock + enforce 27/27.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 15:23:15 +03:00
Дмитрий 4cfca9cc55 feat(m5): 7.4 workflow-gate inline-script + keychain-паттерны (Пакет 7, C5, Блок 4.4)
Закрыта дыра C5 (inline `script` был вне охвата → полный обход workflow-гейта):
- decide: inline `script` гейтится как scriptPath — скан DANGEROUS_PATTERNS + одобрение по
  sha256 контента (approve_workflow_script, 5-мин окно). Без одобрения → block.
- DANGEROUS_PATTERNS +keytar +get/set/delete/findPassword|Credentials(Sync) — контроллер
  не напишет workflow, читающий ключ подписанта/судьи из OS-keychain (действует и для scriptPath).
- main: при inline берёт content/sha256 из tool_input.script напрямую.
NB: regex-детектор eval()/child_process — паттерн-матчер, не вызов (header-коммент L19-21).

+5 тестов. enforce-workflow-gate 10/10.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 15:18:46 +03:00
Дмитрий ecca24f451 feat(m5): 7.3 mcp-classification +egress-скан исходящего (Пакет 7, Блок 4.3)
scanEgress(toolInput): даже разрешённый классификатором MCP-вызов не должен выносить
наружу — block при: секрет в аргументах (общий secret-scan, анти-дрейф с 7.1), data:base64
URI / file:// схема / IP-литерал URL (обход DNS/allowlist), раздутый payload (> maxBytes).
exfil-схемы узко-таргетированы (data:...;base64, и file://) — обычный текст не ловят.
decide: после allow-классификации запускает scanEgress → block с reason «egress: …».

+8 тестов. enforce-mcp-classification 10/10. Дыра «что уходит наружу» закрыта.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 15:15:49 +03:00
Дмитрий 7e61b9c367 feat(m5): 7.2 normative-content детерм-only + fail-CLOSE (Пакет 7, H3, Блок 4.2)
Закрыта дыра H3 (LLM fail-open / wrapper fail-quiet как escape):
- isProtectiveNormativePath: защитное подмножество CLAUDE.md/память/Pravila.
- decide +protectiveSubset: деградация судьи, который БЫЛ активен (budget_exhausted —
  вектор обхода: исчерпать бюджет чтобы пройти), → fail-CLOSE для защитного подмножества.
  Деградация no_api_key (судья выключен — дефолт) → детерм-only флор (fail-open), БЕЗ
  over-block легитимного claude-md-management в обычном режиме (reason-based различение).
- main() catch → fail-CLOSE (block) вместо fail-quiet; передаёт protectiveSubset.
- «Память = совет, НИКОГДА не разрешение» — детерм-слои (recovery/fake-rule/suspicious)
  держат всегда, до судьи.

Backward-compat: существующий fail-open-degraded тест (без protectiveSubset) сохранён.
+9 тестов. enforce-normative-content-rules 25/25.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 15:13:02 +03:00
Дмитрий acc9045016 feat(m5): 7.1 read-path-deny +контент-скан секретов + общий secret-scan (Пакет 7, Блок 4)
Новый общий secret-scan.mjs (анти-дрейф — один источник секрет-паттернов на 7.1 read-выдачу
и 7.3 egress): scanSecrets(text) → {found, hits}. Секрет-подмножество (не PII): PEM private
keys, токены провайдеров (AWS/GitHub/OpenAI/Slack/Sentry/Yandex/JWT/Bearer, regex согласованы
с observer-pii-filter), строки подключения с кредами (scheme://user:pass@). Чистая, без /g.

enforce-read-path-deny.decide расширен опциональным content: путь-деналист — грубый пре-фильтр;
если выдача Read содержит секрет (даже из не-protected пути) → block (fail-CLOSE). Активируется
PostToolUse-обёрткой (content); PreToolUse path-слой backward-compat не тронут.

+9 (secret-scan) +4 (read-path-deny) тестов. Дыра 6 (read без контента) закрыта.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 15:06:53 +03:00
Дмитрий 32b688e091 feat(m5): Блок 5 — сквозные инварианты N5/Δ9 + манифест-WARN Δ8 (Пакет 6)
Guard/инвариант-тесты над существующим кодом (новый prod-код только в 6.5) +
SessionStart-самопроверка регистрации. Все non-vacuous (positive controls).

6.1 (N5) — строгая проверка пола: поведенческие инварианты запирают фиксы аудита
  M1-M4 (finalGate снимает вето ТОЛЬКО на floorBlocked===false; runGateLadder не
  проходит на ok!==true / undefined / throw) + структурный grep по judge-orchestrator
  (центр «по всем полам разом»). Scope grep'а сужен до orchestrator — токенайзерные
  .ok в floor-decide НЕ floor-вердикты (избегаем ложного срабатывания).
6.2 (Δ9-а) — анти-усыхание floor-набора: снимок 11 команд, обязанных оставаться
  floor:true; удаление строки FLOOR_RE без ADR флипнёт одну → CI краснеет. +control.
6.3 (Δ9-б) — единственный источник DESTRUCTIVE_RE: уже в seed Пакета 1 (подтверждено).
6.4 (escape≠protection) — floor-хуки (enforce-floor/floor-decide) не ссылаются на
  override-вокабуляр; перенос защиты под override падает здесь. +control.
6.5 (Δ8) — floor-manifest-check.mjs (новый): SessionStart читает settings.json,
  cry-WARN при отсутствии регистрации пол-хука. НЕ блок (проблема черепах: «пол стоит»
  = сигнал, не гарантия). Чистое ядро collectHookCommands/checkManifest (settings
  инъектируется, битые секции не бросают) + fail-quiet I/O main (exit 0).

+27 тестов (21 инвариант + 6 манифест). Полная tools-only регрессия 2741 + 2 skip
(0 регрессий). Активация манифест-хука в settings.json — шаг владельца.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 15:01:25 +03:00
Дмитрий 0875cd24ab feat(m5): criterion-green — по-критерийный производитель GREEN + мутация P18 (Пакет 5, 5.6)
Новый чистый модуль criterion-green.mjs. produceGreen эмитит подписанный подписантом
green ТОЛЬКО при настоящем зелёном прогоне: оба условия обязательны —
- testPassed (тест шага реально прошёл);
- mutationKilled (P18: сломали код → тест обязан покраснеть; выжил → не проверяет → не green).
Иначе green:false с причиной (test-not-passed / mutation-survived / no-signer-key) — fail-CLOSE.
Подпись — над тройкой {criterion_id, code_fingerprint, occurrence}; green/coverage — поля.
codeFingerprint(fileContents) — Δ2 детерминированный отпечаток изменённых файлов+тестов
(canonicalJson сортит ключи; правка файла → другой отпечаток → green аннулируется).
Чистый: факты прогона + ключ инъектируются (fs/исполнение тестов — живая обёртка владельца).

+7 тестов (вкл. интеграцию: произведённый green проходит весь критерий-гейт 5.5).
Полная tools-only регрессия 2714 passed + 2 skip (0 регрессий).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 12:54:22 +03:00
Дмитрий f0f7128cc3 feat(m5): критерий-гейт = лесенка из 4 шагов (Пакет 5, 5.5, Δ6)
Δ6: НЕ плодим criterionFullyProven — критерий-гейт = РОВНО 4 проверки поверх
существующей runGateLadder. criterionGateSteps(input) → [criteria-from-sealed-plan,
criteria-green-matched, fingerprint-fresh, green-signatures-valid] (И-семантика:
id ∈ печать → green-присутствие → свежесть отпечатка Δ2 → подпись подписанта Δ5).
runCriterionGate гоняет их через runGateLadder (короткое замыкание на первом провале).
Структурный тест «ровно эти 4 шага в порядке» — лекарство «забыл шаг». End-to-end:
валидный сценарий (signGreen) проходит; по одному провалу на каждый шаг.

+7 тестов (2 структура + 5 И-семантика). Полная tools-only регрессия 2707 + 2 skip
(0 регрессий; import judge-gate-floor, цикла нет).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 12:51:11 +03:00
Дмитрий 7110d0fa37 feat(m5): greenSignaturesValid — подпись подписанта на green (Пакет 5, 5.4, Δ5)
Δ5: подлинность green = подпись ПОДПИСАНТА, не совпадение id (id = целостность).
greenSignaturesValid реконструирует подписанную тройку {criterion_id,
code_fingerprint, occurrence} из green-run и проверяет verifyGreen (floor-signer).
Синергия с 5.3: подмена отпечатка для прохода свежести ломает подпись здесь
(отпечаток входит в подписанную тройку). Чистая, fail-CLOSE (нет ключа/sig →
unsigned). Красные прогоны подписи не требуют (их ловит criteriaGreenMatched).

По авторитетному Δ6 — ОТДЕЛЬНЫЙ шаг 4 лесенки критерий-гейта.

+5 тестов. judge-gate-floor 37/37 (аддитивно; import floor-signer, цикла нет).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 12:48:13 +03:00
Дмитрий ea0dee23e2 feat(m5): fingerprintFresh — свежесть отпечатка green (Пакет 5, 5.3, Δ2)
Δ2: зелёный прогон засчитывается только если его code_fingerprint совпадает с
текущим (изменённые файлы шага + тесты; currentFingerprints инъектируется живым
гейтом). Правка файла после прогона → расхождение → green аннулирован (stale).
Чистая функция, fail-CLOSE (нет текущего отпечатка ≠ записанному → stale). Красные
прогоны не проверяются (их «не-зелёность» ловит criteriaGreenMatched).

По авторитетному Δ6 — ОТДЕЛЬНЫЙ шаг лесенки критерий-гейта (не внутрь
criteriaGreenMatched, которая остаётся «green-присутствием», Δ6 шаг 2).

+5 тестов. judge-gate-floor 32/32 (чисто аддитивно).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 12:45:12 +03:00