{
  "schema_version": 1,
  "channel": "homelab-runtime",
  "command_id": "ERRORS-INDEX-004",
  "status": "OK",
  "rc": 0,
  "host": "pve01",
  "mode": "read-only",
  "component": "error-ledger",
  "started_at_utc": "2026-07-21T07:22:22Z",
  "finished_at_utc": "2026-07-21T07:22:22Z",
  "reference_register_checked": true,
  "reference_sha256": "f6f7749fda946015116cdbbd754c7e7f20356b4a6cf4df330a1b0b8fdf59cbb2",
  "error_register_checked": true,
  "error_register_sha256": "ab8308549db90d235b591af0581580c3c7ba1b287b129cc6111c2eef0a0f9ecc",
  "command_sha256": "a45b24ab6a161508f31ee8a6b954c045e74824b4d12adfef6f298cfac6111016",
  "duplicate_failed_command_blocked": false,
  "block_reason": null,
  "execution_started": true,
  "changes_made": false,
  "sanitized": true,
  "secrets_included": false,
  "private_addresses_included": false,
  "raw_evidence_retained_locally": true,
  "raw_evidence_sha256": "655ec287a9095e799677cb7f87b08d4119cbdf9027d7097fa610da5a022ab30b",
  "sanitized_output_sha256": "655ec287a9095e799677cb7f87b08d4119cbdf9027d7097fa610da5a022ab30b",
  "output_truncated_in_json": false,
  "full_sanitized_output_url": "https://git.gram1.ru/.well-known/homelab-runtime/latest.txt",
  "output": "{\n  \"schema_version\": 1,\n  \"status\": \"READY\",\n  \"generated_at_utc\": \"2026-07-21T07:22:22.821762Z\",\n  \"source\": {\n    \"path\": \"/etc/pve/HOMELAB_ASSISTANT_ERROR_REGISTER.md\",\n    \"sha256\": \"ab8308549db90d235b591af0581580c3c7ba1b287b129cc6111c2eef0a0f9ecc\",\n    \"line_count\": 934,\n    \"sanitized\": true\n  },\n  \"summary\": {\n    \"entry_count\": 154,\n    \"rule_count\": 90,\n    \"duplicate_entry_ids\": [],\n    \"duplicate_entry_signatures\": [\n      \"9cd06e064f5a86be9626c4f9a2a819b8d0e71b03309c705fe0e34e1e59f81b73\"\n    ],\n    \"duplicate_rule_signatures\": [\n      \"b936ae30ffb935118585b5a133d9c32643946a3ba5034b85f42bae000518d49a\"\n    ]\n  },\n  \"entries\": [\n    {\n      \"id\": \"ERR-H-L1-HOMELAB-ASSISTANT-ERROR-REGISTER\",\n      \"kind\": \"heading\",\n      \"level\": 1,\n      \"source_line\": 1,\n      \"title\": \"HOMELAB ASSISTANT ERROR REGISTER\",\n      \"summary\": \"Назначение: перед каждой следующей командой сверяться с этим файлом.\",\n      \"rule_like\": true,\n      \"signature\": \"cd4b4d42c26702b03e3e81a620c46dbe3d498f3f2233215024a5c2a49cc1dbfc\"\n    },\n    {\n      \"id\": \"ERR-N-1-L6\",\n      \"kind\": \"numbered\",\n      \"source_line\": 6,\n      \"title\": \"Повторно дал слишком большой интерактивный paste в shell.\",\n      \"summary\": \"\",\n      \"rule_like\": false,\n      \"signature\": \"f732778534f22b73e31dead215c9c7078f48a552a5341ae3830eea618885e8a7\"\n    },\n    {\n      \"id\": \"ERR-N-2-L7\",\n      \"kind\": \"numbered\",\n      \"source_line\": 7,\n      \"title\": \"Повторно использовал here-doc/Markdown/backticks там, где нужен короткий безопасный файл или отдельный скрипт.\",\n      \"summary\": \"\",\n      \"rule_like\": false,\n      \"signature\": \"b13c8946e274927bd041960c76208b6f62e22d46f07233388878c1f47afc426a\"\n    },\n    {\n      \"id\": \"ERR-N-3-L8\",\n      \"kind\": \"numbered\",\n      \"source_line\": 8,\n      \"title\": \"Дал генератор справочника прямо в терминал вместо безопасного маленького шага.\",\n      \"summary\": \"\",\n      \"rule_like\": false,\n      \"signature\": \"8de70b8d263233557a33a065ee49004ef01e328de0d736eb9e328930119458be\"\n    },\n    {\n      \"id\": \"ERR-N-4-L9\",\n      \"kind\": \"numbered\",\n      \"source_line\": 9,\n      \"title\": \"Нарушил своё же правило: не давать длинные вложенные команды с кавычками.\",\n      \"summary\": \"\",\n      \"rule_like\": false,\n      \"signature\": \"d1bf3e42845e6c9d25511814c1da9b21cd8001474f17fd60240194f812f06fe0\"\n    },\n    {\n      \"id\": \"ERR-H-L5-КРИТИЧЕСКИЕ-ОШИБКИ-АССИСТЕНТА\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 5,\n      \"title\": \"Критические ошибки ассистента\",\n      \"summary\": \"1. Повторно дал слишком большой интерактивный paste в shell. 2. Повторно использовал here-doc/Markdown/backticks там, где нужен короткий безопасный файл или отдельный скрипт. 3. Дал генератор справочника прямо в терминал вместо безопасного маленького шага. 4. Нарушил своё же правило: не давать длинные вложенные команды с кавычками.\",\n      \"rule_like\": false,\n      \"signature\": \"0ba065b54f515fe668143ec87d006ea8f27639e6529fe46367ac9acd6f2e28d6\"\n    },\n    {\n      \"id\": \"ERR-H-L11-ЖЁСТКИЕ-ПРАВИЛА-ПЕРЕД-КАЖДОЙ-КОМАНДОЙ\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 11,\n      \"title\": \"Жёсткие правила перед каждой командой\",\n      \"summary\": \"CHECK-1: команда не должна быть большим paste. CHECK-2: команда не должна содержать большой here-doc. CHECK-3: команда не должна смешивать Markdown, backticks и shell-логику. CHECK-4: команда не должна иметь вложенный ssh с несколькими уровнями кавычек. CHECK-5: команда не должна печатать секреты. CHECK-6: если создаётся файл, сначала маленький безопасный шаг, потом проверка. CHECK-7: если команда длиннее 8 строк, её нельзя давать в интерактивный shell. CHECK-8: для edge-vm использовать debian@[PRIVATE_IP] и sudo.\",\n      \"rule_like\": true,\n      \"signature\": \"7ac085a9df3e592d1d7d6686ca4980278110a07e296ddc0253cedb075cc08d84\"\n    },\n    {\n      \"id\": \"ERR-H-L23-ТЕКУЩИЕ-ВАЖНЫЕ-ФАКТЫ\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 23,\n      \"title\": \"Текущие важные факты\",\n      \"summary\": \"Internal network: [PRIVATE_IP]/24. Migration config: migration: secure,network=[PRIVATE_IP]/24. Corosync remains on [PRIVATE_IP]/12/13. VM160 forum-prod is not in Proxmox nightly backup. VM130 edge-vm has secondary disk backup=0 risk. 05_edge_compose_safe.tgz quarantined.\",\n      \"rule_like\": false,\n      \"signature\": \"ecd7f734df435c41c5f01c9fd7f9371a4012c6019493072741c45c6eaf3ed383\"\n    },\n    {\n      \"id\": \"ERR-N-11-L35\",\n      \"kind\": \"numbered\",\n      \"source_line\": 35,\n      \"title\": \"Ошибка: считать offhost OK после failed rsync.\",\n      \"summary\": \"\",\n      \"rule_like\": false,\n      \"signature\": \"aa8a2a5cc3228d73d2feadd69fa2fb1d0f76d020280f69921ac5c00dcfc1f9fd\"\n    },\n    {\n      \"id\": \"ERR-N-12-L40\",\n      \"kind\": \"numbered\",\n      \"source_line\": 40,\n      \"title\": \"Ошибка: широкий secret-поиск по /opt/stacks дал шум.\",\n      \"summary\": \"\",\n      \"rule_like\": false,\n      \"signature\": \"da33bb4eaab4ca4827c1e122c4292fa4181a7607db77ebce5d185b867bfe0ac3\"\n    },\n    {\n      \"id\": \"ERR-N-13-L45\",\n      \"kind\": \"numbered\",\n      \"source_line\": 45,\n      \"title\": \"Ошибка: SQL с одинарными кавычками внутри одинарной SSH-команды ломается.\",\n      \"summary\": \"\",\n      \"rule_like\": false,\n      \"signature\": \"888cbc312835f6303e74db4c5ed2dcaef1e552e657f83ca6f3f82834eb06c23e\"\n    },\n    {\n      \"id\": \"ERR-N-14-L50\",\n      \"kind\": \"numbered\",\n      \"source_line\": 50,\n      \"title\": \"Ошибка: путать контекст входа и узел выполнения.\",\n      \"summary\": \"\",\n      \"rule_like\": false,\n      \"signature\": \"97eec6b76b51ffbc78105ce4e450846c5ff1c85dfbcdfeb3e6c687addd6a1391\"\n    },\n    {\n      \"id\": \"ERR-N-15-L57\",\n      \"kind\": \"numbered\",\n      \"source_line\": 57,\n      \"title\": \"Ошибка: повторно нарушено правило №13 после его добавления.\",\n      \"summary\": \"\",\n      \"rule_like\": false,\n      \"signature\": \"95160c6fda4d8bc77fc542236363b182ba66a7314a8032379b033a0feba5e3b2\"\n    },\n    {\n      \"id\": \"ERR-N-16-L63\",\n      \"kind\": \"numbered\",\n      \"source_line\": 63,\n      \"title\": \"Основополагающее правило: перед каждой командой ассистент обязан явно написать: Сверка с файлом ошибок: CHECK пройден.\",\n      \"summary\": \"\",\n      \"rule_like\": true,\n      \"signature\": \"254db5ca3a2611269de7af81ececd43035134e01a8e629c34434913c49eeeb41\"\n    },\n    {\n      \"id\": \"ERR-N-19-L68\",\n      \"kind\": \"numbered\",\n      \"source_line\": 68,\n      \"title\": \"Уточнение формата: не писать отдельную строку сверки перед каждой командой в пакете.\",\n      \"summary\": \"\",\n      \"rule_like\": true,\n      \"signature\": \"a77bda86805176e2dc721961454b31a5bff15fe924bd43496475c5816a8f9650\"\n    },\n    {\n      \"id\": \"ERR-N-20-L73\",\n      \"kind\": \"numbered\",\n      \"source_line\": 73,\n      \"title\": \"Ошибка: широкий XenForo SMTP-поиск по src дал шум исходников.\",\n      \"summary\": \"\",\n      \"rule_like\": false,\n      \"signature\": \"bed26484549b8d47c75ee6150c206503c05074cc8a5b25dfde0735596aaf956c\"\n    },\n    {\n      \"id\": \"ERR-N-21-L77\",\n      \"kind\": \"numbered\",\n      \"source_line\": 77,\n      \"title\": \"Ошибка: nested PHP php -r дал Parse error на forum-prod.\",\n      \"summary\": \"\",\n      \"rule_like\": false,\n      \"signature\": \"1198d8f0576c5dc8e3883ac7d7e423c8391e946c73bf5ac3cf6063278a68a5af\"\n    },\n    {\n      \"id\": \"ERR-N-22-L82\",\n      \"kind\": \"numbered\",\n      \"source_line\": 82,\n      \"title\": \"Ошибка: самодельный base64 PHP для SMTP auth сломан.\",\n      \"summary\": \"\",\n      \"rule_like\": false,\n      \"signature\": \"c629522366fb27522bec6dd9b9b24f532bbc9c3372fbf4d706673bf283bdf498\"\n    },\n    {\n      \"id\": \"ERR-N-23-L87\",\n      \"kind\": \"numbered\",\n      \"source_line\": 87,\n      \"title\": \"Ошибка: MariaDB не поддержала cast('true' as json) в JSON_SET.\",\n      \"summary\": \"\",\n      \"rule_like\": false,\n      \"signature\": \"4faccf6aa3103144040fd4efb62dd82e23d9f28ecfba72502293f6e2f060b819\"\n    },\n    {\n      \"id\": \"ERR-N-24-L92\",\n      \"kind\": \"numbered\",\n      \"source_line\": 92,\n      \"title\": \"Ошибка: exit 1 в interactive-check закрыл SSH-сессию.\",\n      \"summary\": \"\",\n      \"rule_like\": false,\n      \"signature\": \"c522fbe5bc7aadfc904f1bde7328952b554884941afb3558e60cfe6a4ed378cd\"\n    },\n    {\n      \"id\": \"ERR-N-25-L97\",\n      \"kind\": \"numbered\",\n      \"source_line\": 97,\n      \"title\": \"Ошибка анализа: не надо слепо включать smtpSsl=true при smtpPort=587.\",\n      \"summary\": \"\",\n      \"rule_like\": false,\n      \"signature\": \"17417cdf64e208886d887fd449a612090a81aa5a8f47944f17993821ad5e8235\"\n    },\n    {\n      \"id\": \"ERR-N-26-L102\",\n      \"kind\": \"numbered\",\n      \"source_line\": 102,\n      \"title\": \"Ошибка: openssl -crlf вместе с ручным CRLF ломает SMTP AUTH checker.\",\n      \"summary\": \"\",\n      \"rule_like\": false,\n      \"signature\": \"3f83176fadcfeb2318a3e3fc75c13e3944d3fef3c0aa7aef40db3736b27e794a\"\n    },\n    {\n      \"id\": \"ERR-N-27-L108\",\n      \"kind\": \"numbered\",\n      \"source_line\": 108,\n      \"title\": \"Ошибка: Python heredoc внутри ssh сломал not_ok диагностику.\",\n      \"summary\": \"\",\n      \"rule_like\": false,\n      \"signature\": \"437f761644210f81c8ca3333410a58585c7b74c54a983b9d84159d98c49b4847\"\n    },\n    {\n      \"id\": \"ERR-N-28-L112\",\n      \"kind\": \"numbered\",\n      \"source_line\": 112,\n      \"title\": \"Ошибка: docker compose ls --format json | wc -l дал ложный COMPOSE_PROJECT_COUNT=1.\",\n      \"summary\": \"\",\n      \"rule_like\": false,\n      \"signature\": \"752e0913f85275527a448868cce24aa85cf06949ea82e066e4dc0fc2e857d1ad\"\n    },\n    {\n      \"id\": \"ERR-N-29-L116\",\n      \"kind\": \"numbered\",\n      \"source_line\": 116,\n      \"title\": \"Ошибка: monitoring compact status искал неверные имена health-файлов.\",\n      \"summary\": \"\",\n      \"rule_like\": false,\n      \"signature\": \"61df0580bfb53db159bb0c3fced23235437e823a877822e0b8aebb7518453d4d\"\n    },\n    {\n      \"id\": \"ERR-N-30-L121\",\n      \"kind\": \"numbered\",\n      \"source_line\": 121,\n      \"title\": \"Ошибка/инцидент: Prometheus API HTTP_CODE=000 после monitoring reference.\",\n      \"summary\": \"\",\n      \"rule_like\": false,\n      \"signature\": \"c0dac19354e77153d3f61d53659a8e694c57308bc564e03c833cb68831e88031\"\n    },\n    {\n      \"id\": \"ERR-N-31-L125\",\n      \"kind\": \"numbered\",\n      \"source_line\": 125,\n      \"title\": \"Строгое правило: перед каждой командой сверяться с файлом ошибок и справочником.\",\n      \"summary\": \"\",\n      \"rule_like\": true,\n      \"signature\": \"fa4e49944e2caf8ba4f26a6064bfb465c035e8bc3184244c0314812e81bd6df5\"\n    },\n    {\n      \"id\": \"ERR-N-32-L130\",\n      \"kind\": \"numbered\",\n      \"source_line\": 130,\n      \"title\": \"Ошибка: 153_PROMETHEUS_TARGETS_SETTLED_PROOF содержит SyntaxError из-за вложенного Python внутри SSH.\",\n      \"summary\": \"\",\n      \"rule_like\": false,\n      \"signature\": \"13b3234027d04af4194a5352dda2a5bd21ab955751eb161b953637347da8febf\"\n    },\n    {\n      \"id\": \"ERR-N-34-L135\",\n      \"kind\": \"numbered\",\n      \"source_line\": 135,\n      \"title\": \"Ошибка операционного ввода: полный терминальный лог MobaXterm попал в shell.\",\n      \"summary\": \"\",\n      \"rule_like\": false,\n      \"signature\": \"9a7930e756982125ac45f47a4adf7dfdb10906bd950db4ddae3659021e01bc64\"\n    },\n    {\n      \"id\": \"ERR-N-33-L140\",\n      \"kind\": \"numbered\",\n      \"source_line\": 140,\n      \"title\": \"Security finding: root authorized_keys на PVE-нодах имел права 777.\",\n      \"summary\": \"\",\n      \"rule_like\": false,\n      \"signature\": \"44a7ef2e7d0428388c9b30d12ecfad460caaca457b5d283ebf00ae87c1b2119c\"\n    },\n    {\n      \"id\": \"ERR-N-35-L144\",\n      \"kind\": \"numbered\",\n      \"source_line\": 144,\n      \"title\": \"Ошибка проверки: 160_PVE_ROOT_AUTHORIZED_KEYS_PERMISSIONS_FIXED не доказал исправление прав.\",\n      \"summary\": \"\",\n      \"rule_like\": false,\n      \"signature\": \"336bfad03f4600ddc0d8996bdbbebeae51cfa0c1e2281ae1a838b8b4d8bc7dfd\"\n    },\n    {\n      \"id\": \"ERR-N-36-L149\",\n      \"kind\": \"numbered\",\n      \"source_line\": 149,\n      \"title\": \"Quality check: Storage block needs integrity and pve03 capacity coverage review.\",\n      \"summary\": \"\",\n      \"rule_like\": false,\n      \"signature\": \"a11f29a5da892017620722200a35bdde4c0fe5aa591de4331bfbee0851e38266\"\n    },\n    {\n      \"id\": \"ERR-N-37-L154\",\n      \"kind\": \"numbered\",\n      \"source_line\": 154,\n      \"title\": \"Coverage gap: pve03_staging missing from disk-space health coverage.\",\n      \"summary\": \"\",\n      \"rule_like\": false,\n      \"signature\": \"d2fded36e2e512289b08c31b7fc6872e4efbf61c06e2840e5a2dfef9a8e989f8\"\n    },\n    {\n      \"id\": \"ERR-N-38-L158\",\n      \"kind\": \"numbered\",\n      \"source_line\": 158,\n      \"title\": \"Quality check: Service Dependency Map block needs integrity review.\",\n      \"summary\": \"\",\n      \"rule_like\": false,\n      \"signature\": \"79cffb917505e112ac9507770da4068e145c75d2278d50d1034bb594b1da5062\"\n    },\n    {\n      \"id\": \"ERR-N-39-L162\",\n      \"kind\": \"numbered\",\n      \"source_line\": 162,\n      \"title\": \"Ошибка проверки: post deep audit BAD_MARKER scan caught prose false positive.\",\n      \"summary\": \"\",\n      \"rule_like\": false,\n      \"signature\": \"6dc6e561ff4c49c39a8d2f7fdac4fbfd9d2b6295212185791fb9091f644a2824\"\n    },\n    {\n      \"id\": \"ERR-N-40-L166\",\n      \"kind\": \"numbered\",\n      \"source_line\": 166,\n      \"title\": \"Ошибка/вывод: VM150 Mail cloud single-file upload failed with 413.\",\n      \"summary\": \"\",\n      \"rule_like\": false,\n      \"signature\": \"d7e096eac4cbc2b602601af11ce42bb716d28142a278203aefb65699b5fd4b9c\"\n    },\n    {\n      \"id\": \"ERR-N-41-L170\",\n      \"kind\": \"numbered\",\n      \"source_line\": 170,\n      \"title\": \"Ошибка: post-backup-pass-slo-reconciliation checked pve01 router health on edge-vm.\",\n      \"summary\": \"\",\n      \"rule_like\": false,\n      \"signature\": \"44ceb289e0963e0e97c6e8019e93120ad638f5648e8596b987e7d413eaeb59aa\"\n    },\n    {\n      \"id\": \"ERR-H-L31-ПРАВИЛО-ДЛЯ-СПРАВОЧНИКА\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 31,\n      \"title\": \"Правило для справочника\",\n      \"summary\": \"Не генерировать большой справочник через интерактивную вставку. Следующий справочник делать только маленькими append-блоками или через файл, созданный вне интерактивного shell. 11. Ошибка: считать offhost OK после failed rsync. Если rsync/scp упал, нельзя проверять latest offhost-файл без сверки имени. Проверка должна подтвердить именно новый архив, например дату 2026_06_30-00_20_59. Старый OFFHOST_ZSTD_OK не закрывает новый backup. 12. Ошибка: широкий secret-поиск по /opt/stacks дал шум. Не искать Cloudflare/SMTP сразу по /opt/stacks, MkDocs site, backups и HTML-отчётам.\",\n      \"rule_like\": true,\n      \"signature\": \"2c69d96d9f906cc88ab3158941ba041ca57ddee1e03554fad588eea96041930a\"\n    },\n    {\n      \"id\": \"ERR-H-L174-ASSISTANT-COMMAND-BATCHING-RULE-20260630\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 174,\n      \"title\": \"ASSISTANT_COMMAND_BATCHING_RULE_20260630\",\n      \"summary\": \"- Rule: combine related infrastructure checks, proof generation, reference updates and validation scans into one safe batch command when practical. - Do not split work into many small commands when the operations are low-risk, read-only, or naturally part of one evidence block. - Still keep mandatory preflight checks, short bounded commands, no large paste, no here-docs, no interactive shell traps, and no sensitive values in output. - Split into smaller commands only when safety, rollback, quoting risk, or troubleshooting clarity requires it.\",\n      \"rule_like\": true,\n      \"signature\": \"73ef09d525c334b9874616d5bafdf17d4898a815d35e736e33df0b83790f6539\"\n    },\n    {\n      \"id\": \"ERR-H-L180-HOME-PORTAL-BASE64-APPLY-FAILURE-20260630\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 180,\n      \"title\": \"HOME_PORTAL_BASE64_APPLY_FAILURE_20260630\",\n      \"summary\": \"- Prior home-portal apply attempt 312 failed because a large base64 Python payload decoded as invalid UTF-8. - Rule: avoid large opaque base64 script payloads for homelab changes; use transparent bounded shell/perl/python commands instead.\",\n      \"rule_like\": true,\n      \"signature\": \"4964d3ff556bb377d7c9d0cb4d487afabc0d8ac69c7fb2a5fccb5ff077a8a478\"\n    },\n    {\n      \"id\": \"ERR-H-L184-HOMELAB-COMMAND-SAFETY-HARDENING-20260630\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 184,\n      \"title\": \"HOMELAB_COMMAND_SAFETY_HARDENING_20260630\",\n      \"summary\": \"- Rule: do not use large opaque payloads, generated base64 scripts, or hidden multi-line script blobs for homelab changes. - Use transparent bounded shell/perl/python commands, or create a temporary readable script and validate it before execution. - Any script used for changes must pass a syntax/parse check before it is allowed to modify files or restart services. - Any Traceback, UnicodeDecodeError, SyntaxError, Permission denied, command-not-found, nonzero critical RC, or failed write marker must force REVIEW or FAILED, never OK. - Success requires both runtime health and content-specific post-checks: expected files changed, expected cards exist, duplicates absent, service/container alive, and no new critical log pattern. - HTTP 200 alone is not a success condition for configuration changes. - If an apply step fails, the next command must explicitly record the failed attempt and then run a corrected transparent apply command.\",\n      \"rule_like\": true,\n      \"signature\": \"463e648fd9e6d671debe81d12b455563460bc669983e083ef5f0f3b4df2f208b\"\n    },\n    {\n      \"id\": \"ERR-H-L193-HOME-PORTAL-API-ERROR-CLOSURE-RULE-20260630\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 193,\n      \"title\": \"HOME_PORTAL_API_ERROR_CLOSURE_RULE_20260630\",\n      \"summary\": \"- Rule: home portal closure requires current Homepage API-error logs to be zero after restart/reload, not only HTTP 200. - If Homepage UI shows API error or logs contain current httpProxy/API timeout errors, the portal is REVIEW until the widget/API cause is disabled or fixed. - External informational widgets such as weather/Open-Meteo must not block the service launcher portal.\",\n      \"rule_like\": true,\n      \"signature\": \"1a011c6be8607fdacb9571baa017241dcb441a0025f985ecce3d0937a4f7e3b5\"\n    },\n    {\n      \"id\": \"ERR-H-L198-HOME-PORTAL-COMPLEX-QUOTING-FAILURE-20260630\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 198,\n      \"title\": \"HOME_PORTAL_COMPLEX_QUOTING_FAILURE_20260630\",\n      \"summary\": \"- Previous home portal cleanup command failed in local bash before execution: syntax error near unexpected token `('. - Cause: command was too complex and fragile due to nested shell/perl/python quoting. - Rule: do not use deeply nested one-liners for config edits; first run short state checks, then apply small transparent edits.\",\n      \"rule_like\": true,\n      \"signature\": \"862820180d4dbc46a845af7c9ec8847aeb21cda3fd7634c0d2afd9b3d14d5d8b\"\n    },\n    {\n      \"id\": \"ERR-H-L203-HOME-PORTAL-ACTIVE-CONFIG-SCOPE-RULE-20260630\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 203,\n      \"title\": \"HOME_PORTAL_ACTIVE_CONFIG_SCOPE_RULE_20260630\",\n      \"summary\": \"- Rule: Homepage cleanup validation must check active config files only, not logs or backup files. - Active files are services.yaml, bookmarks.yaml, widgets.yaml, settings.yaml and docker.yaml under /opt/stacks/homepage/config. - Matches in /logs/ or *.bak* are historical evidence only and must not make cleanup REVIEW unless the active config still contains the target.\",\n      \"rule_like\": true,\n      \"signature\": \"6bfd4c200f43d77ecd18a1c61c377b4c0588a028fb0ca6d565b2361d16a74967\"\n    },\n    {\n      \"id\": \"ERR-H-L208-HOME-PORTAL-LINK-VALIDATION-NPMPLUS-DEFAULT-RULE-20260630\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 208,\n      \"title\": \"HOME_PORTAL_LINK_VALIDATION_NPMPLUS_DEFAULT_RULE_20260630\",\n      \"summary\": \"- Rule: Homepage link validation must not treat HTTP 200 as success if the body is the NPMPlus default “Congratulations” / host-not-set-up page. - Green siteMonitor dots can be false positives when NPMPlus returns its default vhost page. - A portal card is openable only when DNS, reverse-proxy host mapping, and target content are all valid.\",\n      \"rule_like\": true,\n      \"signature\": \"90363c457faef01bbe65d3e246073788fb426212371de346152c7776d246413b\"\n    },\n    {\n      \"id\": \"ERR-H-L213-ROUTER-CLI-STDIN-APPLY-FAILURE-20260630\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 213,\n      \"title\": \"ROUTER_CLI_STDIN_APPLY_FAILURE_20260630\",\n      \"summary\": \"- Context: attempted Netcraze router ACL apply through SSH stdin/multiline for Homepage Moscow Router monitor fix. - Evidence: 391_MOSCOW_ROUTER_EDGE_VM_WEBADMIN_ACL_APPLY_PROOF.txt. - Failure: STDIN_SHOW_OK=no, APPLY_SKIPPED=stdin_show_failed, CONFIG_HAS_EDGE_5080=no, CONFIG_HAS_EDGE_5083=no. - Rule: do not apply Netcraze config through stdin/multiline SSH; prove CLI input mode first.\",\n      \"rule_like\": true,\n      \"signature\": \"7a151b61221247f7e5ecc6488283190d82b489caa9309675c08baded25d7fc6f\"\n    },\n    {\n      \"id\": \"ERR-H-L219-ROUTER-CLI-PROBE-STDIN-CONSUMPTION-ERROR-20260630\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 219,\n      \"title\": \"ROUTER_CLI_PROBE_STDIN_CONSUMPTION_ERROR_20260630\",\n      \"summary\": \"- Context: ACL syntax read-only probe loop executed only one command because ssh consumed the loop stdin. - Evidence: 393_ROUTER_CLI_ACL_SYNTAX_READONLY_PROBE_PROOF.txt showed COMMAND_COUNT=1. - Fix pattern: use ssh -n or redirect SSH stdin away from the command-list loop for all future SSH-in-loop probes.\",\n      \"rule_like\": false,\n      \"signature\": \"c801838e0f61bbd4f111bc30b21d530e69124b4448859e0e5f28a93cf0f01fec\"\n    },\n    {\n      \"id\": \"ERR-H-L224-ROUTERBACKUP-READONLY-CLI-PERMISSION-LIMIT-20260630\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 224,\n      \"title\": \"ROUTERBACKUP_READONLY_CLI_PERMISSION_LIMIT_20260630\",\n      \"summary\": \"- Context: routerbackup user can run show running-config but cannot execute configuration commands such as access-list. - Evidence: 394_ROUTER_CLI_ACL_SYNTAX_READONLY_PROBE_FIXED_PROOF.txt. - Observed: CMD_TEXT=access-list ? returned Core::Configurator error execute denied [cli]. - Rule: do not attempt ACL/config changes as routerbackup; use admin/operator router access or router Web UI for write changes.\",\n      \"rule_like\": true,\n      \"signature\": \"1d8075c128dbd4b8f34929055e7c4b7c8cfda8f44f0b7874f04afbcc745d5d82\"\n    },\n    {\n      \"id\": \"ERR-H-L230-PROOF-SUMMARY-EXTRACTION-BLANK-20260630\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 230,\n      \"title\": \"PROOF_SUMMARY_EXTRACTION_BLANK_20260630\",\n      \"summary\": \"- Context: proof 395 tried to read COMMAND_COUNT/ZERO_RC_COUNT from proof files 393/394, but those summary fields were printed to console and were not persisted inside the proof files. - Evidence: 395_ROUTER_CLI_ERROR_REGISTER_UPDATE_PROOF.txt has blank P393_COMMAND_COUNT/P394_COMMAND_COUNT/P394_ZERO_RC_COUNT fields. - Rule: when creating follow-up proof summaries, derive counts from persisted proof body lines such as CMD_INDEX and CMD_RC, or write summary fields into the proof file itself.\",\n      \"rule_like\": false,\n      \"signature\": \"1fd6a207e921a706e6d8d3e80984d0436e67192bb56d03c91d20f6b33eba15cd\"\n    },\n    {\n      \"id\": \"ERR-H-L235-ROUTER-ACL-UI-RULE-DELETE-AND-RESTORE-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 235,\n      \"title\": \"ROUTER_ACL_UI_RULE_DELETE_AND_RESTORE_20260701\",\n      \"summary\": \"- Context: while moving Moscow Router monitor rules to _WEBADMIN_Bridge1, two _WEBADMIN_Bridge0 Nextcloud Talk TURN rules were accidentally removed. - Deleted/restored rules: Home [PRIVATE_IP]/24 to Nextcloud [PRIVATE_IP] TCP/UDP 3478. - Verification proof: 399_ROUTER_ACL_RESTORE_AFTER_UI_PROOF.txt. - Rule: after manual router Web UI ACL edits, compare _WEBADMIN_Bridge0, _WEBADMIN_Bridge1 and _WEBADMIN_GigabitEthernet1 before continuing.\",\n      \"rule_like\": false,\n      \"signature\": \"973a47bd8e8f388786157b6d50b53de9fc96b31e582a9645f3cf4189b74ac4fe\"\n    },\n    {\n      \"id\": \"ERR-H-L241-ROUTER-MONITOR-NODE-LOOKUP-PROBE-BUG-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 241,\n      \"title\": \"ROUTER_MONITOR_NODE_LOOKUP_PROBE_BUG_20260701\",\n      \"summary\": \"- Context: proof 400 tested Moscow router HTTPS monitor from Homepage container with custom Node lookup callback. - Failure: NODE_HTTPS_RESOLVE_ERROR=ERR_INVALID_IP_ADDRESS and NODE_HTTPS_RESOLVE_INSECURE_ERROR=ERR_INVALID_IP_ADDRESS. - Impact: Node HTTPS resolve subtest is invalid; curl --resolve results remain valid. - Rule: do not rely on custom Node lookup callback until separately tested; prefer curl --resolve or simple direct Node HTTP checks.\",\n      \"rule_like\": true,\n      \"signature\": \"97151454d5ffda69d45b802ed15dc82057b02632e141b3729fa8ad47a959c556\"\n    },\n    {\n      \"id\": \"ERR-H-L247-ROUTER-HTTP-SERVICE-FORBIDDEN-FROM-EDGE-VM-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 247,\n      \"title\": \"ROUTER_HTTP_SERVICE_FORBIDDEN_FROM_EDGE_VM_20260701\",\n      \"summary\": \"- Context: Moscow Router Homepage monitor after ACL fix. - Evidence: proofs 399, 400, 401. - Observed: edge-vm TCP to [PRIVATE_IP]:5080/5083 is OK, but router HTTP/HTTPS returns 403 for all tested paths. - Impact: Homepage siteMonitor cannot be made green via direct router HTTP URL until router web service allows the Bridge1/Proxmox/protected segment or an explicit safe monitor endpoint is used. - Rule: do not keep changing ACL for this symptom; ACL is already correct. Fix service access scope or use a dedicated monitor endpoint.\",\n      \"rule_like\": true,\n      \"signature\": \"b0de3053a46b06841cbaf6ea5945be49ad8d451c7c8453d899d01a9f520cc384\"\n    },\n    {\n      \"id\": \"ERR-H-L254-PY-COMPILE-PYC-PERMISSION-ERROR-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 254,\n      \"title\": \"PY_COMPILE_PYC_PERMISSION_ERROR_20260701\",\n      \"summary\": \"- Context: installing edge-vm Moscow router health endpoint. - Mistake: assistant used `python3 -m py_compile` on a script installed under /usr/local/sbin as root. - Actual impact: non-root syntax check attempted to create /usr/local/sbin/__pycache__ and failed with Permission denied. - Recovery: validate syntax with `ast.parse` instead of py_compile, because it does not write .pyc files. - Rule: do not use py_compile against root-owned system paths from an unprivileged user.\",\n      \"rule_like\": true,\n      \"signature\": \"683a661513a8d46a89bf6492c8f12564f6f9fe262ce16b0f6487f63c0f2666f0\"\n    },\n    {\n      \"id\": \"ERR-H-L261-ROUTER-HOME-IP-DOCKER-ROUTE-CONFLICT-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 261,\n      \"title\": \"ROUTER_HOME_IP_DOCKER_ROUTE_CONFLICT_20260701\",\n      \"summary\": \"- Context: Moscow Router Homepage siteMonitor attempt using http://[PRIVATE_IP]:5080. - Evidence: proof 405 showed route to [PRIVATE_IP] via Docker bridge and Homepage container ECONNRESET. - Impact: do not use [PRIVATE_IP] directly as Homepage siteMonitor from edge-vm. - Mitigation: use dedicated edge-vm health endpoint that checks router TCP [PRIVATE_IP]:5080 and returns HTTP 200/503 for Homepage.\",\n      \"rule_like\": true,\n      \"signature\": \"5daeb3cf0b7d3bf64ddf6ca87e958f73d0715672617706dee233fd739fb08c2c\"\n    },\n    {\n      \"id\": \"ERR-H-L267-HOMEPAGE-ROUTER-MOSCOW-YAML-TITLE-SHAPE-MISMATCH-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 267,\n      \"title\": \"HOMEPAGE_ROUTER_MOSCOW_YAML_TITLE_SHAPE_MISMATCH_20260701\",\n      \"summary\": \"- Context: applying Moscow Router Homepage siteMonitor health endpoint. - Mistake: assistant apply script matched only property-style cards with `title`, but active Homepage YAML can use service-name-key style like `- Service Name:`. - Evidence: proof 407 returned YAML_CHANGE_COUNT=0 and APPLY_SKIPPED=unexpected_match_count while health endpoint proof 406 was OK. - Recovery: use YAML-aware updater that handles both property-style and service-name-key style cards.\",\n      \"rule_like\": false,\n      \"signature\": \"d7ed6a0ecfb19584319e97626e76661541c675e9125e4bf4ff0a407c9718cdd0\"\n    },\n    {\n      \"id\": \"ERR-H-L273-HOMEPAGE-ROUTER-MOSCOW-APPLY-SCRIPT-SYNTAX-ERROR-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 273,\n      \"title\": \"HOMEPAGE_ROUTER_MOSCOW_APPLY_SCRIPT_SYNTAX_ERROR_20260701\",\n      \"summary\": \"- Context: applying Moscow Router Homepage health endpoint. - Mistake: assistant generated Python script with invalid f-string escaping. - Evidence: proof 408 showed SyntaxError in /tmp/homepage-router-moscow-apply-fixed.py. - Actual impact: YAML was not changed, so Homepage green dot could not appear. - Recovery: stop applying blind updater; first discover exact active YAML shape, then apply minimal YAML-aware edit. - Rule: validate generated Python syntax locally before scp/remote execution; avoid f-strings in generated maintenance scripts.\",\n      \"rule_like\": true,\n      \"signature\": \"057b7e036316f6f240cac06b9f46e98d8399df689f82b903cc9ef630afe09f94\"\n    },\n    {\n      \"id\": \"ERR-H-L281-FORUM-PROD-VM160-FIRST-BOOT-SSH255-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 281,\n      \"title\": \"FORUM_PROD_VM160_FIRST_BOOT_SSH255_20260701\",\n      \"summary\": \"- Context: clean rebuilt VM160 first boot. - Symptom: PING_OK=yes and TCP22_OK=yes, but SSH_RC=255. - Rule: do not proceed with OS baseline until SSH failure is diagnosed; likely old known_hosts key or cloud-init/root-key issue.\",\n      \"rule_like\": true,\n      \"signature\": \"5e0359dc6b12e39f533e9060876f5c5f830b43cf2dbbe0441d6876fee5f410a3\"\n    },\n    {\n      \"id\": \"ERR-H-L286-FORUM-PROD-VM160-NESTED-HOSTNAME-PROOF-QUOTING-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 286,\n      \"title\": \"FORUM_PROD_VM160_NESTED_HOSTNAME_PROOF_QUOTING_20260701\",\n      \"summary\": \"- Context: VM160 first SSH proof after rebuild. - Issue: command substitution $(hostname) inside nested ssh was expanded on pve02 before entering VM160. - Impact: VM_HOSTNAME=pve02 in proof 426 is not valid VM identity evidence. - Rule: for nested SSH identity checks, run literal hostname commands without local command substitution.\",\n      \"rule_like\": false,\n      \"signature\": \"c7174e4703945abd82004fea3fa9da0ff3efa1f2a89e6d2315cacf9c27b6d761\"\n    },\n    {\n      \"id\": \"ERR-H-L292-FORUM-PROD-VM160-SWAP-PROOF-QUOTING-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 292,\n      \"title\": \"FORUM_PROD_VM160_SWAP_PROOF_QUOTING_20260701\",\n      \"summary\": \"- Context: VM160 swapfile proof 429. - Issue: nested command substitution expanded on pve02, producing invalid SWAPFILE/FSTAB proof lines. - Impact: proof 429 is not valid closure evidence even though swap was active. - Rule: avoid nested $(...) in VM SSH proofs; use literal remote commands and clean proof.\",\n      \"rule_like\": true,\n      \"signature\": \"3dc2ea2b0a54adb8886979de4e01cb5f23704446f5b2198af9bdacc951359e4c\"\n    },\n    {\n      \"id\": \"ERR-H-L298-FORUM-UPLOAD-ARCHIVES-VM-VERIFY-QUOTING-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 298,\n      \"title\": \"FORUM_UPLOAD_ARCHIVES_VM_VERIFY_QUOTING_20260701\",\n      \"summary\": \"- Context: proof 446 copy/check archives inside VM160. - Issue: nested SSH quoting expanded shell variables incorrectly, producing gzip checks against empty .gz and blank TAR_TOP lines. - Impact: SCP_TO_VM_RC=0 and file size list are valid, but GZIP/TAR proof lines in 446 are invalid. - Rule: verify VM archive integrity locally inside forum-prod, not through nested quoted loops.\",\n      \"rule_like\": false,\n      \"signature\": \"0e4a3a2f7c179f46c235e1ffa176b57485c88b4f7e7314bb89b6acb6683fe448\"\n    },\n    {\n      \"id\": \"ERR-H-L304-FORUM-PROD-BULK-IMPORT-PHP85-EMPTY-FRONTEND-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 304,\n      \"title\": \"FORUM_PROD_BULK_IMPORT_PHP85_EMPTY_FRONTEND_20260701\",\n      \"summary\": \"- Context: rebuilt VM160, five XenForo forums imported in bulk on PHP 8.5.7. - Symptom: all five forums returned HTTP 200 with empty body; XenForo logged Template public:PAGE_CONTAINER/forum_list is unknown. - Additional failed approach: proof 480 ran xf-dev:rebuild-caches on CodeVipe and returned RC=1, not a valid fix. - Decision: stop bulk repair, treat current VM160 as lab, find a working CodeVipe recipe first, then destroy/rebuild cleanly for all five forums. - Rule: no more mass rebuild/repair commands across all forums until single-forum CodeVipe recipe is proven.\",\n      \"rule_like\": false,\n      \"signature\": \"ea3c74648eece7c2a94559d8f7385ea4a126b399e3a161c5113e69a7b8d05f93\"\n    },\n    {\n      \"id\": \"ERR-H-L311-XENFORO-REQUIREMENTS-DIRECT-DOWNLOAD-403-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 311,\n      \"title\": \"XENFORO_REQUIREMENTS_DIRECT_DOWNLOAD_403_20260701\",\n      \"summary\": \"- Context: Tried to download XenForo requirements ZIP directly from xenforo.com inside forum-prod. - Issue: curl returned HTTP 403; unzip then failed because the ZIP was not downloaded. - Evidence: proof 491 showed DOWNLOAD_RC=22, ZIP_LIST_RC=9, UNZIP_RC=9. - Impact: proof 491 is not a valid server compatibility test. - Rule: use operator-uploaded xenforo23-requirements-test.zip from /root/forum-upload instead of relying on direct curl download.\",\n      \"rule_like\": false,\n      \"signature\": \"13164e6750f679553f0bb7cb13f4b895f345dc3f76c978349dfd30b93294206b\"\n    },\n    {\n      \"id\": \"ERR-H-L318-PVE01-UNZIP-MISSING-FOR-XF-REQUIREMENTS-VERIFY-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 318,\n      \"title\": \"PVE01_UNZIP_MISSING_FOR_XF_REQUIREMENTS_VERIFY_20260701\",\n      \"summary\": \"- Context: proof 492 tried to inspect uploaded xenforo23-requirements-test.zip on pve01. - Issue: pve01 does not have unzip installed, so ZIP_TEST_RC=127 was not a ZIP integrity result. - Impact: proof 492 confirmed file presence only, not archive validity. - Rule: verify ZIP with Python zipfile or inside forum-prod rather than installing unzip on Proxmox just for inspection.\",\n      \"rule_like\": false,\n      \"signature\": \"341e814b6c0aab80d0bde631353118c8c04593d918a2ed7b60551d5bc3043148\"\n    },\n    {\n      \"id\": \"ERR-H-L324-FRESH5-DEPLOY-SUCCESS-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 324,\n      \"title\": \"FRESH5_DEPLOY_SUCCESS_20260701\",\n      \"summary\": \"- Context: VM160 was destroyed/recreated, then five XenForo forums were deployed from fresh ZIP backups. - Result: proof 513 confirms all five forums locally healthy. - Note: proof 511 initially failed dsmods path detection; proof 512 fixed dsmods by locating public_html via src/XF.php marker. - Rule: future restores should preserve internal_data/code_cache or rebuild compiled template cache before smoke testing.\",\n      \"rule_like\": false,\n      \"signature\": \"f46e9779cb65eebc42015f7042a4f55f0a8aa1e59e647eb29d728e48ed6069b3\"\n    },\n    {\n      \"id\": \"ERR-H-L330-NPMPLUS-SQLITE-PASTE-FAILURE-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 330,\n      \"title\": \"NPMPLUS_SQLITE_PASTE_FAILURE_20260701\",\n      \"summary\": \"- Context: NPMplus SQLite inspection command was too complex and a Python fragment was pasted into bash. - Issue: shell entered multiline prompt and produced syntax errors. - Impact: do not trust that SQLite inspection attempt. - Rule: avoid nested SSH heredoc/Python for this task; use simpler commands, generated nginx configs, or uploaded/local script files.\",\n      \"rule_like\": true,\n      \"signature\": \"d1f2dda1b36bc12803264780b71545d7d9b00c4bdb88ef2fa85405e9d27cdf6f\"\n    },\n    {\n      \"id\": \"ERR-H-L336-NPMPLUS-PUBLISH-SCRIPT-WRONG-ENV-SOURCE-525-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 336,\n      \"title\": \"NPMPLUS_PUBLISH_SCRIPT_WRONG_ENV_SOURCE_525_20260701\",\n      \"summary\": \"- Context: proof 525 tried to create forum proxy hosts in NPMplus. - Issue: script sourced /opt/npmplus/.env, but INITIAL_ADMIN_EMAIL/PASSWORD were not present there; they are in the npmplus container env. - Impact: no forum proxy hosts were created by proof 525. - Rule: read NPMplus API login values from docker inspect env internally, never print them.\",\n      \"rule_like\": true,\n      \"signature\": \"58aa4eeee2841a42e270226a408177af9a099631e2a47857c825707b2ac523ff\"\n    },\n    {\n      \"id\": \"ERR-H-L342-NPMPLUS-API-LOGIN-MISSED-HTTPS81-AND-JSON-ESCAPE-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 342,\n      \"title\": \"NPMPLUS_API_LOGIN_MISSED_HTTPS81_AND_JSON_ESCAPE_20260701\",\n      \"summary\": \"- Context: NPMplus API login attempts in proofs 526/527 failed. - Issue: commands missed the likely HTTPS admin endpoint on port 81 and built JSON by string interpolation instead of jq escaping. - Impact: no proxy hosts were created by 526/527. - Rule: for NPMplus API use jq-generated JSON and test https://127.0.0.1:81/api/tokens with -k before publish.\",\n      \"rule_like\": false,\n      \"signature\": \"141ce6d2777003569c531b5308e56cffaea92dc113fdb0c9e9ebe45815805101\"\n    },\n    {\n      \"id\": \"ERR-H-L348-NPMPLUS-API-AUTH-UNAVAILABLE-MANUAL-ROUTES-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 348,\n      \"title\": \"NPMPLUS_API_AUTH_UNAVAILABLE_MANUAL_ROUTES_20260701\",\n      \"summary\": \"- Context: NPMplus API login attempts failed even though the forum VM and edge-to-forum connectivity are healthy. - Issue: API credentials from container initial env are not accepted by current NPMplus API. - Impact: do not use NPMplus API for this publish path. - Rule: publish forum routes through backed-up manual nginx proxy_host configs, then issue proper certificates after DNS points to edge.\",\n      \"rule_like\": true,\n      \"signature\": \"d5a4f4c6312277e9e2c1487b38c4bf554f4b9618fad9e2c5328fe8324e26a555\"\n    },\n    {\n      \"id\": \"ERR-H-L354-EDGE-CERTBOT-TOKEN-FILE-PERMISSION-535-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 354,\n      \"title\": \"EDGE_CERTBOT_TOKEN_FILE_PERMISSION_535_20260701\",\n      \"summary\": \"- Context: proof 535 attempted DNS-01 certificate issue for five forum domains. - Issue: /tmp/forum_cf_token.env was root-owned mode 600, so debian user could not source it directly. - Impact: certbot ran without token and all certificate attempts failed; DNS cutover must not proceed from proof 535. - Rule: read Cloudflare token via sudo without printing it, then create temporary certbot credentials file and remove it after use.\",\n      \"rule_like\": true,\n      \"signature\": \"4566b4ef97181c31efcb32eefd3ed52633883fc929e98d4607a6c48671434582\"\n    },\n    {\n      \"id\": \"ERR-H-L360-EDGE-CERTBOT-FAILED-DNS-CUTOVER-ABORTED-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 360,\n      \"title\": \"EDGE_CERTBOT_FAILED_DNS_CUTOVER_ABORTED_20260701\",\n      \"summary\": \"- Context: edge manual routes are working locally, but certbot DNS-01 certificate issue failed in proof 538. - Issue: all five certbot attempts returned RC=1; proof 539 correctly aborted DNS cutover because edge cert precheck was not ready. - Impact: Cloudflare DNS records were not changed by proof 539; public DNS still points to old IP 87.236.18.45. - Rule: inspect certbot logs and Cloudflare token zone permissions before retrying certificate issuance or DNS cutover.\",\n      \"rule_like\": false,\n      \"signature\": \"7fc8883c0f715d038cc77e38a9088d6cb18290d2456232e9e0a13b1ba094d256\"\n    },\n    {\n      \"id\": \"ERR-H-L366-CLOUDFLARE-TOKEN-NO-ZONE-ACCESS-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 366,\n      \"title\": \"CLOUDFLARE_TOKEN_NO_ZONE_ACCESS_20260701\",\n      \"summary\": \"- Context: Cloudflare token discovered on edge verified as a token but could not access forum zones. - Evidence: proof 542 showed all five zones missing and DNS record create probes failed. - Impact: certbot DNS-01 failed and DNS cutover was correctly aborted; public DNS still points to old hosting. - Rule: install a new Cloudflare token with Zone Read and DNS Edit for the five forum zones before retrying certs/DNS.\",\n      \"rule_like\": false,\n      \"signature\": \"3ba4fdbb1dcc5d0beeb215153e4471c19e29f4080ff3d061e77154f44de9a898\"\n    },\n    {\n      \"id\": \"ERR-H-L372-FORUM-PUBLICATION-FINAL-SUCCESS-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 372,\n      \"title\": \"FORUM_PUBLICATION_FINAL_SUCCESS_20260701\",\n      \"summary\": \"- Context: five XenForo forums were rebuilt on VM160, routed through edge NPMplus, issued Let’s Encrypt certs using a corrected Cloudflare token, and cut over in DNS. - Result: final public proof 546 passed. - Important corrections: initial NPMplus API route failed; manual Nginx routes were used. Initial Cloudflare token had no zone access; corrected token passed zone/DNS probe.\",\n      \"rule_like\": false,\n      \"signature\": \"bec7980ce2230b8ecf69cfc694cc56bbd3dedc45da55ed2df567e8f41b228638\"\n    },\n    {\n      \"id\": \"ERR-H-L377-FINAL-SNAPSHOT-NAME-TOO-LONG-556-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 377,\n      \"title\": \"FINAL_SNAPSHOT_NAME_TOO_LONG_556_20260701\",\n      \"summary\": \"- Context: proof 556 final health gate passed for all five public forums. - Issue: Proxmox snapshot failed because generated snapshot name exceeded 40 characters. - Evidence: qm snapshot returned snapname value may only be 40 characters long. - Impact: forum health was OK, but proof 556 snapshot step was not completed. - Fix: rerun snapshot with short name.\",\n      \"rule_like\": false,\n      \"signature\": \"c3d65c37278c1e067af0f6dc1d7eb36fa6e75b03113162e66adf06c483bedc76\"\n    },\n    {\n      \"id\": \"ERR-H-L384-CLOUDFLARE-DNS-AUDIT-TXT-SUBSTRING-GAP-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 384,\n      \"title\": \"CLOUDFLARE_DNS_AUDIT_TXT_SUBSTRING_GAP_20260701\",\n      \"summary\": \"- Context: prior Cloudflare DNS audit checked web A/AAAA/CNAME state for root/www records. - Issue: it did not deeply inspect TXT/SPF contents for old provider IP substrings. - Evidence: user screenshot showed SPF TXT containing old 87.236.18.* value. - Impact: web routing is OK, but mail-related DNS may still contain stale provider data. - Rule: perform full DNS record audit including TXT/SPF/DKIM/DMARC/MX/CDN and substring search for old IPs before DNS cleanup.\",\n      \"rule_like\": false,\n      \"signature\": \"260c1f016ae9bf9efb5888bb54809e742681b512223781efac0a4cc8d8cdf2c7\"\n    },\n    {\n      \"id\": \"ERR-H-L391-CLOUDFLARE-DNS-CLEANUP-DRYRUN-QUOTE-563-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 391,\n      \"title\": \"CLOUDFLARE_DNS_CLEANUP_DRYRUN_QUOTE_563_20260701\",\n      \"summary\": \"- Context: SPF cleanup dry-run command 563 had broken shell quoting and left the terminal at multiline prompt. - Impact: proof 563 is invalid and no DNS cleanup was performed by it. - Rule: use copied scripts for Cloudflare DNS mutation instead of fragile inline nested quotes.\",\n      \"rule_like\": false,\n      \"signature\": \"3837def1e15ccdc91af766587f44bd7e98885fb25b88ca3d62e65d13698f62d1\"\n    },\n    {\n      \"id\": \"ERR-H-L396-SPF-DUPLICATE-AFTER-565-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 396,\n      \"title\": \"SPF_DUPLICATE_AFTER_565_20260701\",\n      \"summary\": \"- Context: SPF cleanup command 565 attempted to replace stale SPF records. - Issue: backup write permission problem caused record detection failure, so new v=spf1 -all records were created while old v=spf1 ip4:87.236.18.45 records remained. - Impact: domains temporarily had duplicate SPF records, which is invalid for mail validation. - Fix: delete all SPF TXT records for the five forum zones, then create exactly one v=spf1 -all TXT per zone.\",\n      \"rule_like\": false,\n      \"signature\": \"4a4b1cba0ac945aef40797821d7a9a774728f30fb5e56c34599575e1b5b40ba8\"\n    },\n    {\n      \"id\": \"ERR-H-L402-MSMTP-SECRET-SOURCE-PARSE-ERROR-576-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 402,\n      \"title\": \"MSMTP_SECRET_SOURCE_PARSE_ERROR_576_20260701\",\n      \"summary\": \"- Context: proof 576 installed msmtp but sendmail auth test failed. - Issue: [SENSITIVE_PATH] was sourced as shell, but SMTP password contained shell-special characters; source failed and auth used an invalid/truncated secret path. - Impact: msmtp package installed, but mail sending was not proven working. - Rule: never source SMTP secret files containing arbitrary passwords; store password base64 and use msmtp passwordeval helper accessible to www-data.\",\n      \"rule_like\": true,\n      \"signature\": \"6288f1fcabcdeda1884be858bea052ff9dbb35a65054f5cff6315a350dd7ef12\"\n    },\n    {\n      \"id\": \"ERR-H-L408-FORUM-MSMTP-MAIL-TRANSPORT-STILL-FAILING-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 408,\n      \"title\": \"FORUM_MSMTP_MAIL_TRANSPORT_STILL_FAILING_20260701\",\n      \"summary\": \"- Context: attempted to fix msmtp config with passwordeval helper. - Issue: one or more sendmail/PHP mail tests still failed. - Impact: XenForo mail sending is not yet proven. - Next step: verify SMTP credential/password and provider policy for noreply@pvepro.ru.\",\n      \"rule_like\": false,\n      \"signature\": \"31d614a3bb69ec05a33a5be14388347a438e13f1e28a7ec58207273159e00eae\"\n    },\n    {\n      \"id\": \"ERR-H-L414-SMTP-PASSWORD-EXPOSED-ROTATE-REQUIRED-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 414,\n      \"title\": \"SMTP_PASSWORD_EXPOSED_ROTATE_REQUIRED_20260701\",\n      \"summary\": \"- Context: SMTP password was exposed in terminal output during failed msmtp setup. - Cause: secret file with raw SMTP_PASS was later parsed/sourced by shell; shell error printed the raw assignment line. - Impact: treat that SMTP password as compromised. - Required action: rotate the mailbox/app password in the mail provider panel before any further SMTP testing. - Rule: never store arbitrary passwords as shell-sourceable KEY=value files; use non-printing secret storage only.\",\n      \"rule_like\": true,\n      \"signature\": \"88ae0aeb13d948e6f2cb2d83dc2a9464e4de1805cdb4271cf78f3d315f7095c9\"\n    },\n    {\n      \"id\": \"ERR-H-L421-NETBIRD-MAILCOW-REPORTED-DOWN-AFTER-SMTP-INCIDENT-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 421,\n      \"title\": \"NETBIRD_MAILCOW_REPORTED_DOWN_AFTER_SMTP_INCIDENT_20260701\",\n      \"summary\": \"- Context: user reported NetBird and Mailcow became unavailable after forum mail-transport work. - Impact: treat as active incident until service reachability and container/VM state are proven. - Rule: do not change DNS, firewall, proxy, mail, NetBird, or Mailcow configs before read-only triage proof.\",\n      \"rule_like\": true,\n      \"signature\": \"b0eef26c0503e63d8a8c5a294ea1e124ab5e6ac8936d2ccaf23cd9b74fc5ebf3\"\n    },\n    {\n      \"id\": \"ERR-H-L426-FORUM-SMTP-FAILED-CONFIG-DISABLED-AFTER-MAILCOW-BAN-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 426,\n      \"title\": \"FORUM_SMTP_FAILED_CONFIG_DISABLED_AFTER_MAILCOW_BAN_20260701\",\n      \"summary\": \"- Context: forum-prod SMTP/msmtp test used invalid or compromised credentials and triggered Mailcow netfilter warnings/ban for 95.84.154.183. - Impact: forum mail transport is not proven and must remain disabled until SMTP password is rotated and tested once carefully. - Action: remove forum-prod msmtp secret/config files so XenForo cannot keep retrying broken SMTP auth. - Rule: do not test SMTP auth again until a fresh rotated mailbox/app password is available.\",\n      \"rule_like\": true,\n      \"signature\": \"70dc70d8c15e3f30611c45123bf2c3b27c86bf66d703df3e4232057d380801d6\"\n    },\n    {\n      \"id\": \"ERR-H-L432-FORUM-OLD-CODEVIPE-CLOUD-BACKUP-STALE-AFTER-FRESH5-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 432,\n      \"title\": \"FORUM_OLD_CODEVIPE_CLOUD_BACKUP_STALE_AFTER_FRESH5_20260701\",\n      \"summary\": \"- Context: pve02 codevipe-cloud-backup.timer existed before fresh five-forum rebuild. - Issue: old script points to /root/backups/codevipe and old remote path app/codevipe/forum-prod/local. - Evidence: latest service run failed with missing /root/backups/codevipe; remote inventory contains old codevipe-public/codevipe.sql files, not current five-forum archive. - Impact: old timer must not be treated as valid current backup for all five forums. - Fix: create a new forum-prod fresh5 cloud backup using existing pve02 Mail-cloud rclone crypt remotes.\",\n      \"rule_like\": true,\n      \"signature\": \"3baaa9a93ec07abd251041838b9602a52b143ef8e07a03dde6928db27eb2b44f\"\n    },\n    {\n      \"id\": \"ERR-H-L439-FORUM-RESTORE-DRILL-610-STATUS-FLAG-BUG-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 439,\n      \"title\": \"FORUM_RESTORE_DRILL_610_STATUS_FLAG_BUG_20260701\",\n      \"summary\": \"- Context: restore drill proof 610 downloaded Mail.ru Cloud backup, verified split part SHA256, reconstructed archive, matched full archive SHA256, and confirmed all five forum DB/file archives in tar. - Issue: final RESTORE_DRILL_OK was set to no because the script tested an unassigned shell variable RCLONE_REMOTE_PVE02_MAIL_01_RC. - Impact: proof 610 final status flag is invalid, but its detailed integrity checks are valid. - Fix: rerun corrected restore validation against the downloaded cloud backup artifacts and produce proof 612.\",\n      \"rule_like\": false,\n      \"signature\": \"fd5f6f353d76ced7d2deb5e924a183c516c80e7329d8b9d5c4be0627b5b92d41\"\n    },\n    {\n      \"id\": \"ERR-H-L445-FORUM-XENFORO-MAIL-SMOKE-TEST-FAILED-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 445,\n      \"title\": \"FORUM_XENFORO_MAIL_SMOKE_TEST_FAILED_20260701\",\n      \"summary\": \"- XenForo-level mail smoke test did not return success for all five forums. - Check proof 623 and msmtp log before retrying.\",\n      \"rule_like\": false,\n      \"signature\": \"c48950d0c079bf801e38017835444fdd04a5354397908bbd864f625e7a917b3d\"\n    },\n    {\n      \"id\": \"ERR-H-L449-XENFORO-MAIL-SMOKE-623-INVALID-ROOT-PATH-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 449,\n      \"title\": \"XENFORO_MAIL_SMOKE_623_INVALID_ROOT_PATH_20260701\",\n      \"summary\": \"- Context: custom XenForo mail smoke test 623 failed with Could not open input file /root/scripts/623_xenforo_mail_smoke.php. - Cause: the script was run as www-data from /root/scripts; /root is not traversable by www-data. - Impact: proof 623 is invalid and should not be used to judge mail delivery. - Superseding evidence: user ran XenForo built-in outgoing email test; message was delivered to Yandex but placed in Spam. - Next step: investigate deliverability/authentication headers and pvepro.ru SPF/DKIM/DMARC/PTR.\",\n      \"rule_like\": false,\n      \"signature\": \"ad9141334766e53e58aef9b0fb014ebfbfe556d3d36615b5f4754e2b29cf36fd\"\n    },\n    {\n      \"id\": \"ERR-H-L456-FORUM-NEXT-CHAT-KNOWN-ERRORS-AND-CLOSED-INCIDENTS-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 456,\n      \"title\": \"FORUM_NEXT_CHAT_KNOWN_ERRORS_AND_CLOSED_INCIDENTS_20260701\",\n      \"summary\": \"\",\n      \"rule_like\": false,\n      \"signature\": \"d4866c37a32090d3456bbb2b44824481a232211fdef57833b7f7bec57c065690\"\n    },\n    {\n      \"id\": \"ERR-H-L458-CLOSED-CLASSIFIED-INCIDENTS\",\n      \"kind\": \"heading\",\n      \"level\": 3,\n      \"source_line\": 458,\n      \"title\": \"Closed / classified incidents\",\n      \"summary\": \"- SMTP_PASSWORD_EXPOSED_ROTATE_REQUIRED_20260701: - A previous bad command sourced a raw SMTP secret and printed it. - Treat old password as compromised. - Later persistent SMTP was rebuilt using safe files and verified. - Never print or package secrets. - XENFORO_MAIL_SMOKE_623_INVALID_ROOT_PATH_20260701: - Custom mail proof 623 failed with \\\"Could not open input file\\\". - Cause: PHP was run as www-data from /root/scripts, but /root is not traversable by www-data.\",\n      \"rule_like\": true,\n      \"signature\": \"eafc804db9ff3f7b3b68c10318b53db82160fe7951c6e68c4301703c2b7abe41\"\n    },\n    {\n      \"id\": \"ERR-H-L491-CURRENT-NON-BLOCKING-ITEMS\",\n      \"kind\": \"heading\",\n      \"level\": 3,\n      \"source_line\": 491,\n      \"title\": \"Current non-blocking items\",\n      \"summary\": \"- Yandex placed built-in XenForo test mail in Spam despite SPF pass and DKIM pass. - Classification: deliverability/reputation/content filtering, not server failure. - Recommended manual action: click “Не спам!” and add/check pvepro.ru in Yandex Postmaster.\",\n      \"rule_like\": false,\n      \"signature\": \"bc80a4393b6c0958e5753d3114c2ff3e0554b960a6fed7b7829625f02a9d595a\"\n    },\n    {\n      \"id\": \"ERR-H-L496-SAFETY-RULES-FOR-NEXT-CHAT\",\n      \"kind\": \"heading\",\n      \"level\": 3,\n      \"source_line\": 496,\n      \"title\": \"Safety rules for next chat\",\n      \"summary\": \"- Do not print secrets. - Do not download or upload: - [SENSITIVE_PATH] - /etc/msmtprc - /etc/msmtp/* - rclone configs - Cloudflare tokens - DB dumps\",\n      \"rule_like\": true,\n      \"signature\": \"e95262ee083c9c3d1b8587e182598186f1be23e5db9229b5aece7ea336fa6ca5\"\n    },\n    {\n      \"id\": \"ERR-H-L509-PARKED-DOMAINS-STAGE4-DNS01-PREFLIGHT-FAILED-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 509,\n      \"title\": \"PARKED_DOMAINS_STAGE4_DNS01_PREFLIGHT_FAILED_20260701\",\n      \"summary\": \"- Context: parked-domain public apply proof 634. - Issue: DNS-01 based apply was attempted before a clear CAN_APPLY=yes preflight result was visible in terminal output. - Observed: stage4 stopped safely with preflight_failed; CERTBOT_PRESENT=no, CERTBOT_DNS_CLOUDFLARE_PLUGIN=no, and CF_ZONE_ACCESS=no for newfi.ru, hapusya.ru and kingofwolk.ru. - Impact: no parked-domain NPMplus route/certificate changes were applied by stage4; public HTTPS validation still failed certificate verification. - Rule: do not use Cloudflare DNS-01 for these parked domains unless a token with explicit zone access is installed; because DNS already points to edge, prefer a copied-script HTTP-01 flow with temporary ACME challenge routes and content-specific validation.\",\n      \"rule_like\": true,\n      \"signature\": \"9a52142ed4109dad52b9391a4a479df84e258ff703bfd8904e4ec5947589cd35\"\n    },\n    {\n      \"id\": \"ERR-H-L516-PARKED-DOMAINS-STAGE5-BASH-LOCAL-SETU-BUG-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 516,\n      \"title\": \"PARKED_DOMAINS_STAGE5_BASH_LOCAL_SETU_BUG_20260701\",\n      \"summary\": \"- Context: parked-domain HTTP-01 apply proof 635. - Issue: edge script used Bash `local id=\\\"$1\\\" ... conf=\\\"$WORK/.../$id.conf\\\"` and `local host=\\\"$1\\\" ... tmp=\\\"$WORK/.../$host.html\\\"` under `set -u`; dependent variables are not safe inside the same local assignment command. - Observed: `id: unbound variable`, `source can not be empty`, then `host: unbound variable`; certbot did not run, final HTTPS routes were not installed. - Impact: local parked page remained OK; public HTTPS remained not closed. - Rule: split dependent Bash local assignments into separate statements, syntax-check copied scripts before execution, and keep content-specific validation for parked routes.\",\n      \"rule_like\": false,\n      \"signature\": \"c5a97f3f8380803056dcbb2abd47a72f37609e59655bbd63dec7695e4b5e86e0\"\n    },\n    {\n      \"id\": \"ERR-H-L523-PARKED-DOMAINS-STAGE6-TEMP-HTTP-ROOT-VALIDATION-BUG-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 523,\n      \"title\": \"PARKED_DOMAINS_STAGE6_TEMP_HTTP_ROOT_VALIDATION_BUG_20260701\",\n      \"summary\": \"- Context: parked-domain HTTP-01 fixed apply proof 636. - Issue: temporary HTTP-01 route validation checked HTTP `/` for parked marker, but expected behavior before certificates is root HTTP 301 to HTTPS. - Observed: LOCAL_HTTP_VALIDATE returned HTTP_CODE=301 MARKER_OK=no, script rolled back its temporary route configs and did not run certbot. - Impact: local parked page remained OK; public HTTPS remained not closed. - Rule: validate `/.well-known/acme-challenge/<token>` for HTTP-01 readiness, not HTTP `/`; also scan all active NPMplus nginx config dirs for unmanaged domain conflicts before writing routes.\",\n      \"rule_like\": false,\n      \"signature\": \"dfb310ca0c4e8c9d03ea17ec7e7c5821f16f7dde5c3b0d217d1ed361f61a8740\"\n    },\n    {\n      \"id\": \"ERR-H-L530-PARKED-DOMAINS-STAGE7-NEWFI-ROOT-ACME-REDIRECT-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 530,\n      \"title\": \"PARKED_DOMAINS_STAGE7_NEWFI_ROOT_ACME_REDIRECT_20260701\",\n      \"summary\": \"- Context: parked domains HTTP-01 Stage7 stopped before certbot because ACME validation path returned 301 for root newfi.ru while www.newfi.ru, hapusya.ru, www.hapusya.ru, kingofwolk.ru and www.kingofwolk.ru returned 200. - Impact: no final parked-domain HTTPS routes/certificates were applied by Stage7; temporary route configs were rolled back. - Rule: do not run another apply stage until active NPMplus nginx route selection for newfi.ru is diagnosed with nginx -T and Host-header probes.\",\n      \"rule_like\": true,\n      \"signature\": \"65ab366077a7e1afb168ff94f8b1b7a8b788e1bdca76e6484300ce76c03431f9\"\n    },\n    {\n      \"id\": \"ERR-H-L535-PARKED-DOMAINS-STAGE8-DEFAULT-HTTP-TO-HTTPS-REDIRECT-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 535,\n      \"title\": \"PARKED_DOMAINS_STAGE8_DEFAULT_HTTP_TO_HTTPS_REDIRECT_20260701\",\n      \"summary\": \"- Context: parked-domain route autopsy proof 638. - Finding: no active NPMplus config contains newfi.ru/hapusya.ru/kingofwolk.ru, but unknown HTTP hosts and ACME paths return 301 to HTTPS from NPMplus default routing. - Impact: HTTP-01 cannot work through the default server. A dedicated temporary ACME server block must be proven before certbot; do not run another certbot apply without a successful ACME-path probe for all six hostnames. - Rule: after every parked-domain temp route attempt, rollback the route and prove rollback before continuing.\",\n      \"rule_like\": true,\n      \"signature\": \"fb5de90cf615657074d466c74a748ae250def09fa8ad84bedafdc765a6682be2\"\n    },\n    {\n      \"id\": \"ERR-H-L541-PARKED-DOMAINS-STAGE9-NEWFI-EXACT-PROBE-NEEDED-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 541,\n      \"title\": \"PARKED_DOMAINS_STAGE9_NEWFI_EXACT_PROBE_NEEDED_20260701\",\n      \"summary\": \"- Context: parked domains HTTP-01 probe stage9 installed one combined temporary server block for all six hostnames. - Issue: five hostnames returned ACME probe 200, but root newfi.ru still returned 301. - Impact: do not continue certbot/apply until exact newfi.ru route behavior is isolated. - Rule: run a temporary exact newfi.ru probe block and roll it back before any HTTP-01 apply.\",\n      \"rule_like\": true,\n      \"signature\": \"b6335dafc9b854cc8c775bb66b93a4f927e15e4d1b50bb1b33eed285902c43ac\"\n    },\n    {\n      \"id\": \"ERR-H-L547-PARKED-DOMAINS-STAGE10-EXACT-NEWFI-PROBE-OK-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 547,\n      \"title\": \"PARKED_DOMAINS_STAGE10_EXACT_NEWFI_PROBE_OK_20260701\",\n      \"summary\": \"- Context: parked-domain Stage10 proof 640. - Result: exact server block for newfi.ru on NPMplus intercepted both root and ACME paths with HTTP 200 and custom X-Parked-Probe markers. - Stage9 combined server_name block passed 5/6 but failed bare newfi.ru, so final HTTP-01 cutover must use separate exact per-host server blocks. - Rule: do not use one combined parked-domain server_name block for these domains; use exact host blocks and content-specific validation.\",\n      \"rule_like\": true,\n      \"signature\": \"6c28ff0028785a70150cd3be2574ea8ed678ed0e5d8838b93feef353f8d26e7b\"\n    },\n    {\n      \"id\": \"ERR-H-L553-PARKED-DOMAINS-STAGE11-LOW-ID-EXACT-ROUTE-FAILED-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 553,\n      \"title\": \"PARKED_DOMAINS_STAGE11_LOW_ID_EXACT_ROUTE_FAILED_20260701\",\n      \"summary\": \"- Context: parked-domain Stage11 proof 641. - Result: exact per-host route files 700-705 passed nginx -t but bare newfi.ru still hit HTTP->HTTPS redirect on ACME path. - Earlier Stage10 proof showed exact newfi.ru route works when placed at high id 998. - Hypothesis: file/include priority or NPMplus generated ordering makes low id 700 unsuitable for bare newfi.ru in this runtime. - Rule: do not retry parked-domain HTTP-01 with low id 700-705. First prove fixed high-id exact blocks, then apply with those exact ids.\",\n      \"rule_like\": true,\n      \"signature\": \"11a87298deaf78b478c1a13abcfbfb1cc91f2540eb4fc5fd7daaf3a53b426cf8\"\n    },\n    {\n      \"id\": \"ERR-H-L560-PARKED-DOMAINS-SWITCH-TO-DNS01-AFTER-HTTP01-FAILURES-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 560,\n      \"title\": \"PARKED_DOMAINS_SWITCH_TO_DNS01_AFTER_HTTP01_FAILURES_20260701\",\n      \"summary\": \"- Context: parked domains newfi.ru, hapusya.ru and kingofwolk.ru. - HTTP-01 attempts hit NPMplus default HTTP-to-HTTPS redirect/include-order problems. - Stage12 cleanup confirmed temp files 980-985 absent and nginx reload OK. - Rule: do not continue HTTP-01 for these parked domains; use DNS-01 with a dedicated Cloudflare token that has Zone Read and DNS Edit for the three zones.\",\n      \"rule_like\": true,\n      \"signature\": \"f4fbcb9b2153251384025a82debe093241f087f381f31e999e5de5c66b60d748\"\n    },\n    {\n      \"id\": \"ERR-H-L566-PARKED-DOMAINS-STAGE15-FALSE-LOCAL-VALIDATE-ROLLBACK-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 566,\n      \"title\": \"PARKED_DOMAINS_STAGE15_FALSE_LOCAL_VALIDATE_ROLLBACK_20260701\",\n      \"summary\": \"- Context: Stage15 DNS-01 succeeded and public validation showed parked page for most hosts, but local edge curl --resolve validation returned CURL_RC=60 and triggered rollback. - Impact: do not rerun Stage15 as-is. - Rule: for parked domains finalization, use existing DNS-01 certificates and validate from pve01 public HTTPS with content marker; do not use the failed local --resolve TLS check as the closure gate.\",\n      \"rule_like\": true,\n      \"signature\": \"3b69e4a267b02bdaf0f69e8af7da2cf979ae13223acd5a1eed82f9de1736399d\"\n    },\n    {\n      \"id\": \"ERR-H-L571-DOMAIN-ACTIONS-STAGE18-PLACEHOLDER-AND-RENEWAL-PROOF-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 571,\n      \"title\": \"DOMAIN_ACTIONS_STAGE18_PLACEHOLDER_AND_RENEWAL_PROOF_20260701\",\n      \"summary\": \"- Context: after parked domains and portfolio audit, operator requested removing domain enumeration from placeholder and checking certificate renewal paths. - Rule: Stage18 must not change DNS records or NPMplus route files. It may update placeholder HTML, run existing forum renewal script once for runtime proof, and run certbot dry-run for parked certificates using existing DNS-01 hooks.\",\n      \"rule_like\": true,\n      \"signature\": \"ff7fb700a503f6caecb02ad5f31ed4eefe5678fd9217ef289fc3c906cd291e70\"\n    },\n    {\n      \"id\": \"ERR-H-L575-GRAM1-ROOT-WWW-PLACEHOLDER-CLOSE-STAGE19-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 575,\n      \"title\": \"GRAM1_ROOT_WWW_PLACEHOLDER_CLOSE_STAGE19_20260701\",\n      \"summary\": \"- Context: operator requested gram1.ru root/www to use the existing placeholder page. - Rule: only root gram1.ru and www.gram1.ru may be routed. Existing gram1.ru subdomain NPMplus routes must not be changed. - Rule: use DNS-01 with a dedicated gram1 Cloudflare token; do not use HTTP-01. - Stage18 parked dry-run note: kingofwolk.ru dry-run hit Let's Encrypt rateLimited/service-busy after active certificate issuance and public HTTPS were already OK. Treat as transient external dry-run failure, not route/cert closure blocker.\",\n      \"rule_like\": true,\n      \"signature\": \"9e2adc97128fdb5159168a0ebad49e1808253dcf358cdc908303a1a29d9277b8\"\n    },\n    {\n      \"id\": \"ERR-H-L581-PVEPRO-EDGE-LANDING-STAGE21-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 581,\n      \"title\": \"PVEPRO_EDGE_LANDING_STAGE21_20260701\",\n      \"summary\": \"- Context: pvepro.ru root/www currently point to the Mailcow VPS and return Mailcow with TLS hostname mismatch. - Decision: because pve01 has no SSH key access to the external VPS, do not modify the VPS. Instead move only pvepro.ru and www.pvepro.ru A records to edge and serve an edge landing page. - Safety rule: do not change mail.pvepro.ru, nb.pvepro.ru, MX, SPF, DKIM, DMARC or forum SMTP configuration. - Use a dedicated Cloudflare token for pvepro.ru. Do not print token values.\",\n      \"rule_like\": true,\n      \"signature\": \"da430a8849bebcbd0a5dc55d4282ae916ffb348af757e797b50a144a35c00e29\"\n    },\n    {\n      \"id\": \"ERR-H-L587-PVEPRO-STAGE21-LANDING-SERVICE-FAILED-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 587,\n      \"title\": \"PVEPRO_STAGE21_LANDING_SERVICE_FAILED_20260701\",\n      \"summary\": \"- Context: Stage21 stopped safely before DNS/cert/NPMplus route because local pvepro landing service returned CURL_RC=7 on 127.0.0.1:18089. - Impact: DNS root/www pvepro.ru remained on 185.139.214.215 and no route/cert changes were applied. - Rule: Stage22 must fix and prove the local landing service first, then proceed to DNS-01 cert, NPMplus route and root/www DNS only after local marker PVEPRO_LANDING_OK is reachable.\",\n      \"rule_like\": false,\n      \"signature\": \"6c8c70fd8a9af6ca28d7c2848c52dae8dd0405dfb2758b3341fb7d3c0f24131f\"\n    },\n    {\n      \"id\": \"ERR-H-L592-PVEPRO-STAGE22-VALIDATION-AND-COPY-GUARD-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 592,\n      \"title\": \"PVEPRO_STAGE22_VALIDATION_AND_COPY_GUARD_20260701\",\n      \"summary\": \"- Context: Stage22 succeeded on edge-side landing, cert, NPMplus route and Cloudflare A upserts, but wrapper had a non-critical scp wildcard failure due __pycache__ and public validation used cached DNS for root pvepro.ru. - Mistakes recorded: do not scp scripts/* after local py_compile because __pycache__ may be present; do not continue after critical scp failure in future apply stages; do not classify PVEPro landing as NetBird just because it contains a link text; use Cloudflare authoritative A records and --resolve validation when DNS propagation may lag. - Stage23 must be read-only for infrastructure and may close only if edge route, Cloudflare A records, HTTPS landing marker, and mail/nb unchanged checks pass.\",\n      \"rule_like\": true,\n      \"signature\": \"39b7838d47fba86c663aac0896edc9db58fa614ebdcdbae09fb3054a061ba44f\"\n    },\n    {\n      \"id\": \"ERR-H-L597-TAFTAUTO-CERT-AUTORENEW-BLOCKED-NO-PRIVATE-ACCESS-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 597,\n      \"title\": \"TAFTAUTO_CERT_AUTORENEW_BLOCKED_NO_PRIVATE_ACCESS_20260701\",\n      \"summary\": \"- Context: taftauto.ru points to the dacha router public IP. Operator confirmed the router model is Netcraze-like, same family as Moscow router. - Current limitation: there is no safe private remote access path to the dacha router yet. - Rule: do not expose router admin publicly and do not attempt certificate deployment to the router over the public internet. - Closure status: certificate autodeploy is intentionally blocked until VPN/NetBird/WireGuard or another private management path exists.\",\n      \"rule_like\": true,\n      \"signature\": \"9cd06e064f5a86be9626c4f9a2a819b8d0e71b03309c705fe0e34e1e59f81b73\"\n    },\n    {\n      \"id\": \"ERR-H-L603-TAFTAUTO-CERT-AUTORENEW-BLOCKED-NO-PRIVATE-ACCESS-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 603,\n      \"title\": \"TAFTAUTO_CERT_AUTORENEW_BLOCKED_NO_PRIVATE_ACCESS_20260701\",\n      \"summary\": \"- Context: taftauto.ru points to the dacha router public IP. Operator confirmed the router model is Netcraze-like, same family as Moscow router. - Current limitation: there is no safe private remote access path to the dacha router yet. - Rule: do not expose router admin publicly and do not attempt certificate deployment to the router over the public internet. - Closure status: certificate autodeploy is intentionally blocked until VPN/NetBird/WireGuard or another private management path exists.\",\n      \"rule_like\": true,\n      \"signature\": \"9cd06e064f5a86be9626c4f9a2a819b8d0e71b03309c705fe0e34e1e59f81b73\"\n    },\n    {\n      \"id\": \"ERR-H-L609-TAFTAUTO-WG-STAGE24-SECRET-AND-IMPORT-RULE-20260701\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 609,\n      \"title\": \"TAFTAUTO_WG_STAGE24_SECRET_AND_IMPORT_RULE_20260701\",\n      \"summary\": \"- Context: configuring private management path for taftauto.ru dacha router. - Rule: do not print WireGuard private keys, PSK, or client config body in proof/chat. - Rule: generated client config may be stored only as root/debian mode 600 file and must be imported into the Netcraze router UI manually. - Rule: do not open router public admin or certificate deployment until WireGuard handshake is proven.\",\n      \"rule_like\": true,\n      \"signature\": \"6722ad8ce177f600b198fb48046657acb926330bfbd8bbe1c810de69015f5a87\"\n    },\n    {\n      \"id\": \"ERR-H-L615-TAFTAUTO-WG-PSK-ROTATION-SCRIPT-BROKE-DATAPLANE-20260702\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 615,\n      \"title\": \"TAFTAUTO_WG_PSK_ROTATION_SCRIPT_BROKE_DATAPLANE_20260702\",\n      \"summary\": \"- Assistant gave unsafe PSK rotation flow; after profile reimport dacha interface changed from Wireguard0 to Wireguard1 and data-plane broke until security-level, ACL and routes were restored on Wireguard1. - Proof 666 is superseded because it showed PUBLIC_SSH_22_STILL_OPEN. - Final fixed proof: /root/evidence/667_TAFTAUTO_WG_PSK_ROTATED_PUBLIC_CLOSED_OK_20260702_PROOF.txt\",\n      \"rule_like\": false,\n      \"signature\": \"4b046c53fa6020f8c97bd7e00a8a7d78280d3ab2bf337816cb559083ba9c7495\"\n    },\n    {\n      \"id\": \"ERR-H-L620-TAFTAUTO-CERTBOT-CLOUDFLARE-PLUGIN-MISSING-20260702\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 620,\n      \"title\": \"TAFTAUTO_CERTBOT_CLOUDFLARE_PLUGIN_MISSING_20260702\",\n      \"summary\": \"- Certbot on edge rejected --dns-cloudflare-* arguments: dns-cloudflare plugin missing/not loaded. - No taftauto certificate issued in failed run; no DNS A records or NPMplus routes changed.\",\n      \"rule_like\": false,\n      \"signature\": \"3e7ad3e9ef090a52212334ce2d8c5b65fa7dae68481c01068ec7845bef14a07f\"\n    },\n    {\n      \"id\": \"ERR-H-L624-HOMEPAGE-SERVICES-YAML-BAD-INDENT-20260702\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 624,\n      \"title\": \"HOMEPAGE_SERVICES_YAML_BAD_INDENT_20260702\",\n      \"summary\": \"- Assistant inserted Homepage services.yaml entries with wrong indentation; YAMLException bad indentation at line 227. - Fix: rebuild SmartApe under External Homelabs with 2-space item indentation, remove Router Moscow, rebuild Sites category.\",\n      \"rule_like\": false,\n      \"signature\": \"da1ecebf560240f444c3df39efb068b9d7c8ab7ceda2053516e8d82c8567bd7b\"\n    },\n    {\n      \"id\": \"ERR-H-L628-HOMEPAGE-SERVICES-YAML-BAD-INDENT-REPAIR-20260702\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 628,\n      \"title\": \"HOMEPAGE_SERVICES_YAML_BAD_INDENT_REPAIR_20260702\",\n      \"summary\": \"- Previous apply broke services.yaml indentation and did not follow YAML-aware rule. - Repair uses latest valid services.yaml backup, PyYAML parse/dump, validates before restart.\",\n      \"rule_like\": false,\n      \"signature\": \"7b789067b17326a4ead14942608a43b86b4ae971896eff93eb8658684e657e22\"\n    },\n    {\n      \"id\": \"ERR-H-L632-TAFTAUTO-CERTBOT-DRYRUN-BAD-FLAG-20260702\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 632,\n      \"title\": \"TAFTAUTO_CERTBOT_DRYRUN_BAD_FLAG_20260702\",\n      \"summary\": \"- Assistant used unsupported certbot flag --manual-public-ip-logging-ok with certbot 4.0.0 during taftauto renew dry-run. - No certificate, DNS, NPMplus route, or deploy state changed by the failed dry-run.\",\n      \"rule_like\": false,\n      \"signature\": \"518dbc241694b2eb7c4631a1b147a8262af537e6f4eaf082d1f092cc0a8087cb\"\n    },\n    {\n      \"id\": \"ERR-H-L636-TAFTAUTO-CERT-RENEW-DRYRUN-RATE-LIMITED-20260702\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 636,\n      \"title\": \"TAFTAUTO_CERT_RENEW_DRYRUN_RATE_LIMITED_20260702\",\n      \"summary\": \"- Proof 672 is superseded/partial: it printed STATUS=OK even though certbot dry-run failed. - Failure reason: Let's Encrypt staging/service returned rateLimited / Service busy; retry later. - Cloudflare manual auth and cleanup hooks did run successfully. - Deploy hook was installed and manually invoked successfully before the dry-run. - Do not retry immediately.\",\n      \"rule_like\": true,\n      \"signature\": \"15452c7cc7d791a7cc8324a5f2d39544b8a0971fce736a46afdfce7ec8eea5b3\"\n    },\n    {\n      \"id\": \"ERR-H-L643-HOMEPAGE-XENFORO-INFO-INLINE-PYTHON-SYNTAX-ERROR-20260702\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 643,\n      \"title\": \"HOMEPAGE_XENFORO_INFO_INLINE_PYTHON_SYNTAX_ERROR_20260702\",\n      \"summary\": \"- Assistant used invalid inline Python syntax with def after semicolon while adding XenForo.info to Homepage External. - Failed before services.yaml write.\",\n      \"rule_like\": false,\n      \"signature\": \"3620b01df453a95e2e7fbd121ee954690344e32ed12c71b84739e1a554b316d2\"\n    },\n    {\n      \"id\": \"ERR-H-L647-HOMEPAGE-USEFUL-ROUTER-NAME-MISMATCH-20260702\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 647,\n      \"title\": \"HOMEPAGE_USEFUL_ROUTER_NAME_MISMATCH_20260702\",\n      \"summary\": \"- Previous apply looked for Russian Router card name, but active Homepage Useful group contains Router and Public Domain. - Failed before write. Fix uses YAML-aware edit with exact names Router/Public Domain.\",\n      \"rule_like\": false,\n      \"signature\": \"49212250bc5eea53887790a1782ea3acdc2fa2e7769d89ee79f603883533adf8\"\n    },\n    {\n      \"id\": \"ERR-H-L651-HOMEPAGE-USEFUL-ROUTER-CARD-NOT-FOUND-20260702\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 651,\n      \"title\": \"HOMEPAGE_USEFUL_ROUTER_CARD_NOT_FOUND_20260702\",\n      \"summary\": \"- Apply expected Useful -> Router in active services.yaml, but YAML parse did not find it. No write was performed. - Need verify active Homepage config path, docker mount, and any alternate services.yaml before next apply.\",\n      \"rule_like\": false,\n      \"signature\": \"ce9a93f7876b27d64e76cddc59b220166fa0e82ab7265d3373352906d6e10264\"\n    },\n    {\n      \"id\": \"ERR-H-L655-20260702-CF-TOKEN-AUDIT-BROKEN-COMMAND\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 655,\n      \"title\": \"20260702_CF_TOKEN_AUDIT_BROKEN_COMMAND\",\n      \"summary\": \"- Ошибка: ассистент дал хрупкую команду аудита Cloudflare-токенов с вложенным heredoc/Python через ssh. - Факт: bash начал интерпретировать Python-код, появились syntax error и вторичный prompt >. - Правило: не давать nested heredoc/сложные кавычки через ssh; для длинной логики сначала класть скрипт отдельным файлом, затем запускать его. - Статус: аудит токенов не выполнен, инфраструктурные изменения не должны считаться применёнными.\",\n      \"rule_like\": false,\n      \"signature\": \"00c3ff174b7bd8af54a071aad893fc3e28aae0f79b8cba6babffa67869d0d7c3\"\n    },\n    {\n      \"id\": \"ERR-H-L661-LESSON-20260702-CROWDSEC-NETBIRD-EXIT-ROUTE\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 661,\n      \"title\": \"LESSON_20260702_CROWDSEC_NETBIRD_EXIT_ROUTE\",\n      \"summary\": \"- Do not treat e3qxxx.netbird.selfhosted / 100.100.125.70 and e3qxxx-183-106.netbird.selfhosted / 100.100.183.106 as VPS egress nodes; they are user mobile peers. - Correct egress candidates are relay.pvepro.ru / relay.netbird.selfhosted / 100.100.19.1 for Moldova and mail.pvepro.ru / mail.netbird.selfhosted / 100.100.147.204 for USA. - Access Policy edge-vm -> relay/mail only proves peer reachability. It is not an internet exit route. - Required NetBird fix for CrowdSec CAPI was Network Routing exit route 0.0.0.0/0 distributed to edge-vm group, routing peer relay, masquerade/NAT enabled. - Before attempting CrowdSec CAPI, verify on edge-vm: ip route get 1.1.1.1 uses wt0, wg allowed-ips contains 0.0.0.0/0, and Cloudflare trace no longer shows home IP 95.84.154.183.\",\n      \"rule_like\": true,\n      \"signature\": \"651aa75a20b1bad649519711690f3a5d4f944e71649c31414c966c56c2647f83\"\n    },\n    {\n      \"id\": \"ERR-H-L668-LESSON-20260702-HOMEPAGE-MONITORS-REPAIR-NOT-DELETE\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 668,\n      \"title\": \"LESSON_20260702_HOMEPAGE_MONITORS_REPAIR_NOT_DELETE\",\n      \"summary\": \"- Do not delete or disable Homepage siteMonitor fields to hide red badges. - Fix the underlying monitor path, DNS, proxy route, or local health-wrapper instead. - Do not touch Cloudflare Homepage card when operator says it is green and opens correctly. - Before changing Homepage, identify the live config host; in this state live Homepage is on core-apps [PRIVATE_IP], not edge-vm.\",\n      \"rule_like\": true,\n      \"signature\": \"2bc16e142d368e59b2b176de6779445c152df7e8bae2b1be79a2f4d075985c7c\"\n    },\n    {\n      \"id\": \"ERR-H-L674-LESSON-20260702-KUMA-ADD-ONE-MONITOR-ONLY\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 674,\n      \"title\": \"LESSON_20260702_KUMA_ADD_ONE_MONITOR_ONLY\",\n      \"summary\": \"- When asked to fix one missing Uptime Kuma service, add only one monitor, then generate proposals separately. - Before direct Kuma DB mutation, stop the container and create a DB backup. - Verify DB integrity before starting Kuma again. - Do not touch Cloudflare when operator says it is green and opens correctly.\",\n      \"rule_like\": true,\n      \"signature\": \"b268d57842a9dd192fd72dd940879f3e7cf714ae2928a68a23b5e1ed347262f0\"\n    },\n    {\n      \"id\": \"ERR-H-L680-LESSON-20260702-DOCKGE-STALE-STACKS-NOT-CONTAINERS\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 680,\n      \"title\": \"LESSON_20260702_DOCKGE_STALE_STACKS_NOT_CONTAINERS\",\n      \"summary\": \"- Dockge inactive items after migration can be stale compose folders, not stopped containers. - First classify runtime projects across all Docker hosts before deleting or archiving anything. - For remote Dockge visibility, projects must be under /opt/stacks or bind-mounted there. - Do not delete containers to fix Dockge inactive UI; connect agents and archive only confirmed stale moved stack definitions.\",\n      \"rule_like\": true,\n      \"signature\": \"4249f24d95f2186959586b18fa083cdac97ceddb1cc65b0bd0fd1eeddfdea50d\"\n    },\n    {\n      \"id\": \"ERR-H-L686-STAGE4B-MASKED-SQL-QUERY-FAILURE-20260714\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 686,\n      \"title\": \"STAGE4B_MASKED_SQL_QUERY_FAILURE_20260714\",\n      \"summary\": \"- Previous Stage4B read-only audit returned empty observation-summary, metadata-key, trigger and function blocks because nested SSH/SQL quoting broke queries while stderr was hidden. - Empty blocks are query failures, not proof that metadata, triggers or functions are absent. - Future PostgreSQL audits must use ON_ERROR_STOP, visible stderr, independent query RC and quote-safe dollar literals.\",\n      \"rule_like\": false,\n      \"signature\": \"9786689ba74fed176972e94389a37a7664ee81618201ff24852199ed754b694f\"\n    },\n    {\n      \"id\": \"ERR-H-L691-STAGE4C-SCHEMA-MIGRATIONS-ID-ASSUMPTION-20260714\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 691,\n      \"title\": \"STAGE4C_SCHEMA_MIGRATIONS_ID_ASSUMPTION_20260714\",\n      \"summary\": \"- Stage4C read-only preflight incorrectly assumed schema_migrations has a column named id. - PostgreSQL returned column id does not exist and ON_ERROR_STOP correctly stopped all following SQL checks. - Production DB and application were not changed. Future migration audits must inspect information_schema first and query migration rows without assuming column names.\",\n      \"rule_like\": false,\n      \"signature\": \"f166dab2251442df1f0e77fb70dfa166f1e681a94567cc0d17eb7e5000466a11\"\n    },\n    {\n      \"id\": \"ERR-H-L696-STAGE4C-PGDUMP-DEV-NULL-FSYNC-INVALID-PROBE-20260714\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 696,\n      \"title\": \"STAGE4C_PGDUMP_DEV_NULL_FSYNC_INVALID_PROBE_20260714\",\n      \"summary\": \"- Stage4C custom-format pg_dump capability probe incorrectly used /dev/null as the output file. - pg_dump failed only because fsync on /dev/null returned Invalid argument. - Production database and application were not changed. - Future custom dump capability checks must use a real temporary regular file, validate it with pg_restore --list, and remove it afterward.\",\n      \"rule_like\": false,\n      \"signature\": \"c565b0d0dc04089e68783eb6f6b52e7e66fd66b73eb84b37ea6d7f324ca2142c\"\n    },\n    {\n      \"id\": \"ERR-H-L702-STAGE4C-REMOTE-PREP-THIRD-SUBSTEP-FAILED-20260714\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 702,\n      \"title\": \"STAGE4C_REMOTE_PREP_THIRD_SUBSTEP_FAILED_20260714\",\n      \"summary\": \"- Isolated migration dry-run did not start: REMOTE_PREP_RC=0|0|1. - Remote directory creation and SCP succeeded; the third ownership/mode preparation substep returned RC=1. - Production database and application were not changed. - Exact cause requires read-only residual directory and permission inspection before cleanup or retry.\",\n      \"rule_like\": false,\n      \"signature\": \"c048e5da3ee1467f7b4742ae2ef671944078c985979c4c75f87aefe2b13c54ad\"\n    },\n    {\n      \"id\": \"ERR-H-L708-STAGE4C-REMOTE-PREP-GLOB-AFTER-CHMOD-ROOT-CAUSE-20260714\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 708,\n      \"title\": \"STAGE4C_REMOTE_PREP_GLOB_AFTER_CHMOD_ROOT_CAUSE_20260714\",\n      \"summary\": \"- Exact cause of REMOTE_PREP_RC=0|0|1: after the directory became postgres-owned mode 0700, the unprivileged debian shell could not traverse it and could not expand the wildcard used by chmod. - Candidate files and SHA256 verification were valid; no temporary database was created and production was unchanged. - Future preparation must use exact privileged paths or a verified shared group, never an unprivileged wildcard after restrictive chmod.\",\n      \"rule_like\": true,\n      \"signature\": \"a174131243eb8db72dedb89bc3e917cb45e7c7c8107e40c90b476d46bf4d2718\"\n    },\n    {\n      \"id\": \"ERR-H-L713-STAGE4C-MIGRATION003-FINGERPRINT-DOLLAR-QUOTE-FAILURE-20260714\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 713,\n      \"title\": \"STAGE4C_MIGRATION003_FINGERPRINT_DOLLAR_QUOTE_FAILURE_20260714\",\n      \"summary\": \"- Migration 003 isolated dry-run failed at fingerprint CHECK because the regex end anchor collided with the SQL dollar-quote delimiter and produced an unterminated dollar-quoted string. - The migration transaction rolled back, the temporary database was removed, and production remained unchanged. - Fingerprint validation must use length plus translate without a regex dollar anchor; corrected candidate must pass a fresh isolated dry-run before production consideration.\",\n      \"rule_like\": false,\n      \"signature\": \"adc9a3f845fb0a58a7d9e0196174f3a9c0b5eb905bbd2c2eb456a92eb9f2d37b\"\n    },\n    {\n      \"id\": \"ERR-H-L718-STAGE4C-SEAL-OUTER-RC-MASKING-20260714\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 718,\n      \"title\": \"STAGE4C_SEAL_OUTER_RC_MASKING_20260714\",\n      \"summary\": \"- Stage4C final seal correctly refused creation because overall health was WARN, returning internal RC=40. - The former wrapper then printed a second misleading COMMAND_RC=0 because top-level true masked the internal result. - Future commands must capture the guarded subshell through PIPESTATUS and print exactly one authoritative COMMAND_RC before the mandatory final true. - Production database, application and services were unchanged.\",\n      \"rule_like\": false,\n      \"signature\": \"9f71003c157467e9b2bc0405d3137b6f0f7d14e053cb3053e9323219494287a9\"\n    },\n    {\n      \"id\": \"ERR-H-L724-STAGE4C-DIAGNOSIS-EXPECTED-DERIVED-FILE-ON-VM180-20260714\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 724,\n      \"title\": \"STAGE4C_DIAGNOSIS_EXPECTED_DERIVED_FILE_ON_VM180_20260714\",\n      \"summary\": \"- Previous diagnosis incorrectly treated cluster-admin-restricted-probes.txt as a required source file on VM180. - The file is a central derived health artifact on pve01; VM180 supplies full-observer evidence instead. - This false expectation caused diagnostic RC=32 without any infrastructure failure or production change.\",\n      \"rule_like\": false,\n      \"signature\": \"d032d83a642f525e4ef82024ba9f810d8b9cbb88a6351da3dbed17391e14056d\"\n    },\n    {\n      \"id\": \"ERR-H-L729-STAGE4C-NO-JOBS-RUNNING-TEXT-COUNTED-AS-JOB-20260714\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 729,\n      \"title\": \"STAGE4C_NO_JOBS_RUNNING_TEXT_COUNTED_AS_JOB_20260714\",\n      \"summary\": \"- Stage4C seal preflight incorrectly counted the systemctl informational text No jobs running as one active job. - The parser counted every nonempty output line instead of accepting only rows beginning with a numeric systemd Job ID. - No service was started and no production state changed during the blocked attempt. - Future job counts must match a numeric first field only.\",\n      \"rule_like\": false,\n      \"signature\": \"619b903f2e18ba4251959e200fe4511995e56c4cd9a3e63b5fc8d77134df5ade\"\n    },\n    {\n      \"id\": \"ERR-H-L735-STAGE4D-ADAPTER-SOURCE-AUDIT-ABORTED-BEFORE-FINAL-MARKERS-20260714\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 735,\n      \"title\": \"STAGE4D_ADAPTER_SOURCE_AUDIT_ABORTED_BEFORE_FINAL_MARKERS_20260714\",\n      \"summary\": \"- The evidence-only Stage4D adapter source audit returned RC=1 before emitting its VM180 completion and final audit markers. - The six wrapper validation failures are cascading missing-marker checks and do not prove six independent infrastructure faults. - Production, database, application, services, timers, health and desired-state were unchanged. - Inspect the preserved runner stdout, stderr and result JSON before correcting or rerunning the task.\",\n      \"rule_like\": true,\n      \"signature\": \"cc2c72b5d6eb7a0a1f3258050f4509d2e08f538236449d0100a9ee45193da58d\"\n    },\n    {\n      \"id\": \"ERR-H-L741-STAGE4D-LOCAL-EXPANSION-OF-REMOTE-Q-UNDER-NOUNSET-20260714\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 741,\n      \"title\": \"STAGE4D_LOCAL_EXPANSION_OF_REMOTE_Q_UNDER_NOUNSET_20260714\",\n      \"summary\": \"- The first Stage4D adapter source audit embedded a large remote program inside a locally double-quoted SSH argument. - Local shell expansion reached the remote variable Q while nounset was enabled, causing Q: unbound variable before SSH execution. - VM180 and PostgreSQL audits did not start; production state was unchanged. - Remote shell and SQL programs must be separate syntax-checked files passed through SSH stdin.\",\n      \"rule_like\": false,\n      \"signature\": \"bdae2b3ceda91c697838a15c35c327d73583cc201d5fc77c2f7355f45c2e340a\"\n    },\n    {\n      \"id\": \"ERR-H-L747-STAGE4D-REMOTE-PYTHON311-ADAPTER-SELFTEST-RC1-20260714\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 747,\n      \"title\": \"STAGE4D_REMOTE_PYTHON311_ADAPTER_SELFTEST_RC1_20260714\",\n      \"summary\": \"- Canonical adapter candidate passed local Python 3.13 self-tests with 48 envelopes but returned RC=1 on VM180 Python 3.11.2. - Remote upload and cleanup succeeded, and production database remained 0|0|OK. - The exact traceback and failing assertion must be captured by an isolated identical reproduction before changing the candidate.\",\n      \"rule_like\": false,\n      \"signature\": \"6111533bbd96a60d54d47dff09af497c706e31b90977efb40efc8d5c24bf5ada\"\n    },\n    {\n      \"id\": \"ERR-H-L752-STAGE4D-PYTHON311-FSTRING-COMPATIBILITY-AND-REPRO-VALIDATOR-20260714\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 752,\n      \"title\": \"STAGE4D_PYTHON311_FSTRING_COMPATIBILITY_AND_REPRO_VALIDATOR_20260714\",\n      \"summary\": \"- Adapter candidate passed Python 3.13 but failed Python 3.11 parsing at Path(row[\\\"path\\\"]) inside a double-quoted f-string. - Exact fix is Python 3.11-compatible quoting: Path(row['path']). - The reproduction command captured the expected compile failure but incorrectly returned CAPTURE_BAD=1 because its validator required compile RC=0. - Production database remained 0|0|OK and desired-state remained clean. - Future adapter candidates must pass syntax and full self-tests on both pve01 and VM180 runtimes before acceptance.\",\n      \"rule_like\": false,\n      \"signature\": \"5aec931e3d68bbdfd0f84e397c8ad1aea4fe7e9d239f066d7186823aaf5a0c33\"\n    },\n    {\n      \"id\": \"ERR-H-L759-STAGE4E-DESIGN-VALIDATOR-ABORTED-BEFORE-LOCAL-VALIDATION-MARKER-20260714\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 759,\n      \"title\": \"STAGE4E_DESIGN_VALIDATOR_ABORTED_BEFORE_LOCAL_VALIDATION_MARKER_20260714\",\n      \"summary\": \"- Restricted probe-agent design candidate compiled successfully but its local validator exited before producing LOCAL_VALIDATION_RC. - The wrapper validation count is cascading missing-marker evidence and does not represent twenty-one independent faults. - Active external probes were not executed and production state was unchanged. - Inspect the preserved validator traceback and exact assertion before modifying the candidate.\",\n      \"rule_like\": false,\n      \"signature\": \"fa47b366640ad2b68b1efc2d897808d7e75322d7aa9e3c9af89013e4eecc5a26\"\n    },\n    {\n      \"id\": \"ERR-H-L765-STAGE4E-VALIDATOR-SCANNED-PYC-AND-OWN-FORBIDDEN-LITERALS-20260714\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 765,\n      \"title\": \"STAGE4E_VALIDATOR_SCANNED_PYC_AND_OWN_FORBIDDEN_LITERALS_20260714\",\n      \"summary\": \"- Stage4E design validator recursively read every candidate file as UTF-8 after py_compile had created binary __pycache__ bytecode. - This caused UnicodeDecodeError before any design assertion failed. - The phase-wide forbidden-pattern scan also included validate_design.py itself, which necessarily contains the prohibited literals it verifies. - Validators must use an explicit text-file allowlist and scans must exclude validator implementation and binary artifacts. - Production, database, services, timers and desired-state were unchanged; active external probes were not executed.\",\n      \"rule_like\": true,\n      \"signature\": \"76b2888a1b774c0f7e8c0fe3aab0e3beb73c78f9b46ef3694f4708cc3431230c\"\n    },\n    {\n      \"id\": \"ERR-H-L772-STAGE4E-V2-VALIDATOR-FAILED-STATIC-COMPILE-20260714\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 772,\n      \"title\": \"STAGE4E_V2_VALIDATOR_FAILED_STATIC_COMPILE_20260714\",\n      \"summary\": \"- Stage4E design candidate v2 failed static compilation before local design validation started. - The reported validation count is cascading missing-marker evidence, not twenty-five independent design defects. - The generated validator must be inspected at the exact SyntaxError line before another candidate is created. - Production, database, application, services, timers, health and desired-state were unchanged; active external probes were not executed.\",\n      \"rule_like\": false,\n      \"signature\": \"0acd318ec69af959e23233f89a6b26cefcce1dbb2c85908fcc2406c51c85db72\"\n    },\n    {\n      \"id\": \"ERR-H-L778-STAGE4E-PIPESTATUS-LOST-AFTER-FIRST-ASSIGNMENT-20260714\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 778,\n      \"title\": \"STAGE4E_PIPESTATUS_LOST_AFTER_FIRST_ASSIGNMENT_20260714\",\n      \"summary\": \"- VM180 validation copy used a pipeline and read PIPESTATUS in two separate assignments. - The first assignment reset PIPESTATUS, leaving COPY_RC empty and preventing remote validation and cleanup. - Retry must use explicit SCP operations without pipeline status parsing. - Production remained unchanged and active external probes were not executed.\",\n      \"rule_like\": false,\n      \"signature\": \"29d5bdfa098352aeceb9defce46f9845d100c46dfc1758f17ad8c5eb56859787\"\n    },\n    {\n      \"id\": \"ERR-H-L784-STAGE4F-VALIDATOR-EXPECTED-HEALTH-REFRESH-FROM-NONWRITING-BACKUP-SCRIPT-\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 784,\n      \"title\": \"STAGE4F_VALIDATOR_EXPECTED_HEALTH_REFRESH_FROM_NONWRITING_BACKUP_SCRIPT_20260714\",\n      \"summary\": \"- The controlled backup service completed with Result=success and ExecMainStatus=0. - Validation incorrectly required cluster-admin-incident-engine-backup.txt to receive a fresh timestamp. - The executed backup script does not write that health file; therefore the stale health timestamp was not evidence of backup failure. - Stage4F backup proof must use the service result, fresh latest JSON, produced artifacts and off-host/restore evidence instead. - The backup must not be rerun merely to satisfy the invalid health-file freshness assertion.\",\n      \"rule_like\": true,\n      \"signature\": \"92516bc4ce3841e809e6ec04f2ec77458c1c472c146af37a577bbeffbce5d19d\"\n    },\n    {\n      \"id\": \"ERR-H-L791-STRICT-RULE-20260714-CLOSE-TAILS-IMMEDIATELY\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 791,\n      \"title\": \"STRICT_RULE_20260714_CLOSE_TAILS_IMMEDIATELY\",\n      \"summary\": \"- Failure class: переход к следующей задаче при наличии незакрытого хвоста. - Mandatory anti-regression: после ошибки запрещено продолжать другой scope до точной root cause, исправления, повторной проверки, очистки, proof и seal. - Closure gate: UNRESOLVED_TAIL_COUNT=0; BLOCKER_COUNT=0; TEMPORARY_ARTIFACT_COUNT=0; ROLLBACK_VERIFIED=YES; HEALTH=OK; PROOF_UPDATED=YES; REFERENCE_UPDATED=YES. - Если закрытие невозможно из-за внешней зависимости, опасной неоднозначности или обязательного решения пользователя, фиксировать BLOCKED/OPEN и не заявлять CLOSED. - Proof: /var/lib/homelab-change-sets/operator-rule-close-tails-immediately-v1/20260714T171631Z/report.txt\",\n      \"rule_like\": true,\n      \"signature\": \"951ac14651bd989701f8f0cdf4a6f683fdbe957a357523c878cef0081fdc8dd7\"\n    },\n    {\n      \"id\": \"ERR-H-L798-ERROR-20260714-DEPENDENCY-FACT-QUERY-GLOBAL-LIMIT\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 798,\n      \"title\": \"ERROR_20260714_DEPENDENCY_FACT_QUERY_GLOBAL_LIMIT\",\n      \"summary\": \"- Symptom: dependency audit вернул только latest_collector_status. - Root cause: ORDER BY 1 LIMIT 1 в конце UNION ALL применился ко всему набору фактов. - Correction: latest collector status извлекается скалярным подзапросом внутри CTE facts; глобальный LIMIT отсутствует. - Anti-regression: multi-fact SQL audit обязан проверять точное ожидаемое число строк до извлечения значений. - Closure proof: /var/lib/homelab-cluster-admin-stage4/stage4h-collector-provenance-readiness-scope-v1/report.txt - Status: CLOSED.\",\n      \"rule_like\": false,\n      \"signature\": \"8078498e8c9915662cb7bbe485ccb1e71099a6a8d39849830721e2bcd69fde70\"\n    },\n    {\n      \"id\": \"ERR-H-L806-ERROR-20260714-COLLECTOR-PATCH-REQUIREMENT-PATTERN-MISMATCH\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 806,\n      \"title\": \"ERROR_20260714_COLLECTOR_PATCH_REQUIREMENT_PATTERN_MISMATCH\",\n      \"summary\": \"- Symptom: MIGRATION_PLAN_COLLECTOR_PATCH_REQUIRED_COUNT=1 при ожидаемых двух подтверждениях. - Root cause: узкий шаблон учитывал collector_patch_required, но не collector_patch_required_after_stage4c. - Correction: оба поля проверяются отдельно, затем складываются как semantic requirement count. - Anti-regression: связанные JSON-контракты проверяются по собственным точным именам полей, а не одним унифицированным grep-шаблоном. - Closure proof: /var/lib/homelab-cluster-admin-stage4/stage4h-collector-provenance-readiness-scope-v1/report.txt - Status: CLOSED; PRIOR_TAIL_UNRESOLVED_COUNT=0.\",\n      \"rule_like\": false,\n      \"signature\": \"a136bb3af68dd9abefd3054a299cbe72d1ee4435d755299318badd0469472379\"\n    },\n    {\n      \"id\": \"ERR-H-L814-ERROR-20260714-PRIVILEGED-FILE-REDIRECTION-BEFORE-SUDO\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 814,\n      \"title\": \"ERROR_20260714_PRIVILEGED_FILE_REDIRECTION_BEFORE_SUDO\",\n      \"summary\": \"- Symptom: bash reported Permission denied while counting collector.py lines. - Root cause: `< collector.py` was opened by the unprivileged caller shell before sudo executed wc. - Correction: run sudo wc -l collector.py without caller-side input redirection. - Anti-regression: never combine sudo with `< protected-file`; pass the protected path as an argument to the privileged command. - Closure proof: /var/lib/homelab-cluster-admin-stage4/stage4h-collector-baseline-audit-final-v1/report.txt - Status: CLOSED.\",\n      \"rule_like\": true,\n      \"signature\": \"4510dad88d4639e1987b83002657a7c5c4580d0b4ae4b398c376b2ca49aa394d\"\n    },\n    {\n      \"id\": \"ERR-H-L822-ERROR-20260714-COLLECTOR-RUN-COLUMN-ASSUMPTION\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 822,\n      \"title\": \"ERROR_20260714_COLLECTOR_RUN_COLUMN_ASSUMPTION\",\n      \"summary\": \"- Symptom: SQL failed because completed_at did not exist. - Root cause: collector_runs columns were assumed instead of read from information_schema; actual names are finished_at and error_text. - Correction: assert required and forbidden column counts before querying recent runs. - Anti-regression: every schema-sensitive audit must discover and validate exact column names before the main query. - Closure proof: /var/lib/homelab-cluster-admin-stage4/stage4h-collector-baseline-audit-final-v1/report.txt - Status: CLOSED; UNRESOLVED_TAIL_COUNT=0.\",\n      \"rule_like\": true,\n      \"signature\": \"01f861530e0df5cbb6814d21fe0caa2043d2819342012f74d4299c45bfcf08cd\"\n    },\n    {\n      \"id\": \"ERR-H-L830-ERROR-20260714-REMOTE-STDIN-ARGUMENT-SHIFT\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 830,\n      \"title\": \"ERROR_20260714_REMOTE_STDIN_ARGUMENT_SHIFT\",\n      \"summary\": \"- Symptom: remote harness выполнил chmod для пути bash. - Root cause: дополнительный positional token bash был передан после SSH command и стал первым аргументом stdin-скрипта. - Correction: remote command вызывает bash -s -- с ровно тремя явно встроенными аргументами: candidate path, SHA256 и byte count. - Anti-regression: stdin-скрипт обязан валидировать $1 по разрешённому path pattern и затем подтверждать точные SHA256 и byte count; запрещено добавлять отдельный bash-token после remote command. - Closure proof: /var/lib/homelab-change-sets/stage4h-collector-provenance-patch-candidate-v1/20260714T174131Z/report.txt - Status: CLOSED; UNRESOLVED_TAIL_COUNT=0.\",\n      \"rule_like\": true,\n      \"signature\": \"a808754bcc91814fedf38578e86631a14e4964c3ab9571e205b6b65afa951389\"\n    },\n    {\n      \"id\": \"ERR-H-L838-ERROR-20260714-OVERSIZED-COMMAND-PARSE-FAILURE\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 838,\n      \"title\": \"ERROR_20260714_OVERSIZED_COMMAND_PARSE_FAILURE\",\n      \"summary\": \"- Symptom: Bash получил одиночный фрагмент и сообщил syntax error near unexpected token `(`. - Root cause: чрезмерно длинная интерактивная команда была обрезана или повреждена при передаче до полного разбора Bash. - Impact: side-effect audit подтвердил production state 0|0|OK, неизменный collector, активный timer и отсутствие временных БД, каталогов и процессов. - Correction: монолитные команды запрещены; сложные действия выполняются через отдельный проверяемый task-скрипт и короткий launcher. - Anti-regression: interactive payload target <= 8000 bytes; syntax-check перед запуском; remote output сохраняется независимо от RC. - Closure proof: /var/lib/homelab-change-sets/operator-command-size-guard-v1/20260714T182653Z/report.txt - Status: CLOSED; UNRESOLVED_TAIL_COUNT=0.\",\n      \"rule_like\": false,\n      \"signature\": \"16b0958121cab8d184e16e7ea30930971bc785f4385caaeacea19bb35a8e8908\"\n    },\n    {\n      \"id\": \"ERR-H-L847-ERROR-20260714-HOMELAB-ADMIN-HELP-RC-ASSUMPTION\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 847,\n      \"title\": \"ERROR_20260714_HOMELAB_ADMIN_HELP_RC_ASSUMPTION\",\n      \"summary\": \"- Symptom: template audit завершился HOMELAB_ADMIN_HELP_FAILED. - Root cause: был ошибочно ожидаем RC=0 от неподдерживаемого аргумента --help. - Actual contract: неизвестный аргумент печатает usage и возвращает RC=64. - Correction: проверять version либо считать точную usage-строку вместе с RC=64 штатным результатом. - Anti-regression: перед проверкой CLI сначала фиксировать поддерживаемые команды; не предполагать GNU-style --help. - Closure proof: /var/lib/homelab-change-sets/homelab-admin-cli-contract-fix-v1/20260714T183338Z/report.txt - Status: CLOSED; UNRESOLVED_TAIL_COUNT=0.\",\n      \"rule_like\": false,\n      \"signature\": \"346a85cc8494d14ed401b27e57d0b73a8d53078fdd91575ccb4dc89e833d4e44\"\n    },\n    {\n      \"id\": \"ERR-H-L856-ERROR-20260715-EXPECTED-NEGATIVE-RC-TRAP-CLASSIFICATION\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 856,\n      \"title\": \"ERROR_20260715_EXPECTED_NEGATIVE_RC_TRAP_CLASSIFICATION\",\n      \"summary\": \"- Symptom: schema-v2 task v3 build stopped while intentionally testing the --invalid argument path. - Root cause: an expected RC64 was executed while the generic ERR trap remained active. - Correction: expected failures are evaluated only through an explicit if/else branch with immediate RC capture. ANTI_REGRESSION_TOKEN=EXPECTED_RC64_INTERCEPTED_ERR_TRAP - Required pattern: if command; then rc=0; else rc=$?; fi, followed by a separate exact expected-RC assertion. - Status: CLOSED; UNRESOLVED_TAIL_COUNT=0.\",\n      \"rule_like\": false,\n      \"signature\": \"99b412bad31ee6959b473695ef09baa07b43e09ded03a4a254d60c25715f6059\"\n    },\n    {\n      \"id\": \"ERR-H-L864-ERROR-20260715-DUPLICATE-MACHINE-MARKER-COUNT\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 864,\n      \"title\": \"ERROR_20260715_DUPLICATE_MACHINE_MARKER_COUNT\",\n      \"summary\": \"- Symptom: error-register candidate construction stopped with RC1 before applying the candidate. - Root cause: the same marker text appeared in both a heading and a descriptive line, while the guard expected one substring occurrence. - Correction: headings and prose do not contain machine-token values; validation counts only complete exact token lines. ANTI_REGRESSION_TOKEN=DUPLICATE_MACHINE_TOKEN_IN_HEADING_AND_BODY - Required pattern: grep -Fxc against a complete machine line, never grep -Foc against an unrestricted substring. - Production impact: none. - Temporary artifacts: removed and verified. - Status: CLOSED; UNRESOLVED_TAIL_COUNT=0.\",\n      \"rule_like\": true,\n      \"signature\": \"356ff9228e7bb1e236f72584597d788a4dc65367b0a1e4af38209a9b24be13ac\"\n    },\n    {\n      \"id\": \"ERR-H-L875-ERROR-20260715-OUTER-WRAPPER-SHELL-SYNTAX\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 875,\n      \"title\": \"ERROR_20260715_OUTER_WRAPPER_SHELL_SYNTAX\",\n      \"summary\": \"- Symptom: the operator shell rejected the generated one-line wrapper with syntax error near the final subshell parenthesis. - Root cause: the outer wrapper contained an unbalanced compound shell construct before the closing subshell delimiter. - Impact: parsing failed before the start marker, guards and homelab-admin invocation; no task phase or infrastructure action executed. - Correction: use a linear wrapper with explicitly closed if/case blocks and avoid nested brace groups in conditional expressions. ANTI_REGRESSION_TOKEN=OUTER_WRAPPER_UNBALANCED_SUBSHELL_SYNTAX - Anti-regression: after any parser-level failure, treat the attempted phase as not executed and verify package and run-state before retrying. - Production impact: none. - Task package impact: none.\",\n      \"rule_like\": true,\n      \"signature\": \"208c08a253c47c1ad448f2282eacc0af231ebeb3443f3f888206b3d7926d9eef\"\n    },\n    {\n      \"id\": \"ERR-H-L888-ERROR-20260715-INLINE-REMOTE-SHELL-IN-APPLY-PHASE\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 888,\n      \"title\": \"ERROR_20260715_INLINE_REMOTE_SHELL_IN_APPLY_PHASE\",\n      \"summary\": \"- Symptom: homelab-admin lint rejected Stage4H task v6 with ERR-002-INLINE-REMOTE-SHELL. - Root cause: apply.sh invoked bash -s through an inline quoted SSH program and streamed the remote script through stdin. - Correction: copy the declared remote script to an isolated fixed path on VM180 and invoke that file directly with positional arguments. ANTI_REGRESSION_TOKEN=INLINE_REMOTE_SHELL_REPLACED_BY_STAGED_REMOTE_SCRIPT_PATH - Anti-regression: phase scripts may perform transport orchestration, but remote shell program bodies must exist as separately declared, syntax-checked remote script files. - Remote stdout and stderr must be preserved before evaluating the remote return code. - Task v6 status: REJECTED_BY_LINT; never executed and never mutated. - Production database impact: none.\",\n      \"rule_like\": true,\n      \"signature\": \"5c6fc9cd51fa9d557d4c8aee96a26b3d08bb3e86c6bd4813a7336a87c433b8bc\"\n    },\n    {\n      \"id\": \"ERR-H-L902-ERROR-20260715-OPTIONAL-RUN-DIRECTORY-FIND-UNDER-PIPEFAIL\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 902,\n      \"title\": \"ERROR_20260715_OPTIONAL_RUN_DIRECTORY_FIND_UNDER_PIPEFAIL\",\n      \"summary\": \"- Symptom: task-v7 builder stopped while counting formal runs for rejected task v6. - Root cause: find was executed against an optional directory that did not exist; under pipefail the assignment returned RC1. - Correction: test directory existence first and assign zero without invoking find when it is absent. ANTI_REGRESSION_TOKEN=MISSING_OPTIONAL_RUN_DIRECTORY_TREATED_AS_ZERO - Anti-regression: optional paths must have an explicit existence branch before find under pipefail. - Production impact: none. - Status: CLOSED; UNRESOLVED_TAIL_COUNT=0. - Registered at: 2026-07-15T04:44:06Z\",\n      \"rule_like\": false,\n      \"signature\": \"f5b1a0f37dcba3d28adda172281f4f4b53abc87f36ec3ed7fe35cf51845f0794\"\n    },\n    {\n      \"id\": \"ERR-H-L912-ERROR-20260715-PG-CONSTRAINT-CONTYPE-CHAR-CONCAT\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 912,\n      \"title\": \"ERROR_20260715_PG_CONSTRAINT_CONTYPE_CHAR_CONCAT\",\n      \"summary\": \"- Symptom: isolated Stage4H acceptance failed during schema baseline capture. - Root cause: pg_constraint.contype uses the internal PostgreSQL char type and was concatenated without an explicit text cast. - Correction: use contype::text or CAST(contype AS text). ANTI_REGRESSION_TOKEN=PG_CATALOG_CHAR_CONCAT_REQUIRES_EXPLICIT_TEXT_CAST - Machine rule: ERR-014-PG-CATALOG-CHAR-CONCAT. - Negative self-test: uncast expression rejected with RC64. - Positive self-test: explicit text cast accepted with RC0. - Task v7 mutated: no.\",\n      \"rule_like\": false,\n      \"signature\": \"d269f342ff6fb119a139a91810419aa2a51ca9d24dcd0797ea65318c54ef10c7\"\n    },\n    {\n      \"id\": \"ERR-H-L925-ERROR-20260715-LIVE-COLLECTOR-VERIFIER-WRONG-PATH\",\n      \"kind\": \"heading\",\n      \"level\": 2,\n      \"source_line\": 925,\n      \"title\": \"ERROR_20260715_LIVE_COLLECTOR_VERIFIER_WRONG_PATH\",\n      \"summary\": \"- Symptom: verified linter installation was rolled back after the final collector integrity guard returned UNKNOWN. - Root cause: verifier queried a nonexistent path instead of the canonical live collector path declared by the task acceptance script. - Canonical path: /opt/cluster-admin-incident-engine/collector.py. - Incorrect path: /opt/homelab-cluster-admin/cluster-admin-collector.py. ANTI_REGRESSION_TOKEN=LIVE_COLLECTOR_VERIFIER_PATH_MUST_MATCH_TASK_CANONICAL_PATH - Correction: derive and guard the live collector path from the immutable task contract before executing the hash query. - Production impact: none; the canonical collector hash remained unchanged. - Status: CLOSED; UNRESOLVED_TAIL_COUNT=0.\",\n      \"rule_like\": false,\n      \"signature\": \"91e0cfb58ea5cec63e264f63f6ee65c13efe04ab4d4ab1f70a1d1f285f7b8ad2\"\n    }\n  ],\n  \"rules\": [\n    {\n      \"id\": \"RULE-L3\",\n      \"source_line\": 3,\n      \"text\": \"Назначение: перед каждой следующей командой сверяться с этим файлом.\",\n      \"signature\": \"910453eea5d379e1b055671660d8278df9d4565c580ecb6a036cfc1af83067f0\"\n    },\n    {\n      \"id\": \"RULE-L11\",\n      \"source_line\": 11,\n      \"text\": \"## Жёсткие правила перед каждой командой\",\n      \"signature\": \"3c373d80054bd99fe70a2f6e16e772fdaa67a63def211411b41f18d3123f91d0\"\n    },\n    {\n      \"id\": \"RULE-L18\",\n      \"source_line\": 18,\n      \"text\": \"CHECK-7: если команда длиннее 8 строк, её нельзя давать в интерактивный shell.\",\n      \"signature\": \"a9fa3d2bb79aa917672955c9adfcb227c45317ed178acb1a55b0c78505d7f10d\"\n    },\n    {\n      \"id\": \"RULE-L36\",\n      \"source_line\": 36,\n      \"text\": \"Если rsync/scp упал, нельзя проверять latest offhost-файл без сверки имени.\",\n      \"signature\": \"eabdf9a86e3c2ea3141f16847eb903bd516e6f12129d5e45df4f95c485794a2a\"\n    },\n    {\n      \"id\": \"RULE-L46\",\n      \"source_line\": 46,\n      \"text\": \"Нельзя писать sqlite SQL вида j.type in ('object','array') внутри ssh '...'.\",\n      \"signature\": \"668eb11f0a93aadac3d70e8792e586314755975dc4ac505ac13169cf55af6244\"\n    },\n    {\n      \"id\": \"RULE-L55\",\n      \"source_line\": 55,\n      \"text\": \"Перед каждой командой явно понимать: где выполняется локальная часть, где remote-часть, каким пользователем.\",\n      \"signature\": \"c8948237e08243f20cafec02cd56c65a19073cbef147809f7d16a7a1b027bfde\"\n    },\n    {\n      \"id\": \"RULE-L59\",\n      \"source_line\": 59,\n      \"text\": \"Запрещено использовать в remote sqlite SQL фрагменты вида '$.key' или '.key'.\",\n      \"signature\": \"10d38e69e3690c6892ab2aa8ddef391d5a3bdb71c9c1d80b23ebe30661d92071\"\n    },\n    {\n      \"id\": \"RULE-L61\",\n      \"source_line\": 61,\n      \"text\": \"Команду с ошибкой char(36)||.dns_provider считать битой и не использовать.\",\n      \"signature\": \"0ea37a4f0c68fcbd9d07b6ce4cc184b4152c5c247b1f31a1a70efa8cebeaacb9\"\n    },\n    {\n      \"id\": \"RULE-L63\",\n      \"source_line\": 63,\n      \"text\": \"16. Основополагающее правило: перед каждой командой ассистент обязан явно написать: Сверка с файлом ошибок: CHECK пройден.\",\n      \"signature\": \"ddd85ef9ebb1cbc0cb2d0cda3c70a5f4d23814cb7146f72672f99e2cbc8f82a0\"\n    },\n    {\n      \"id\": \"RULE-L65\",\n      \"source_line\": 65,\n      \"text\": \"Нельзя выдавать команды без предварительной сверки с этим файлом ошибок.\",\n      \"signature\": \"bd94bb5f40d6442f833a3ec028dfaf34c02e6d69701a7397257f9857202c2c5b\"\n    },\n    {\n      \"id\": \"RULE-L66\",\n      \"source_line\": 66,\n      \"text\": \"Нельзя продолжать после собственной ошибки без записи ошибки в этот файл.\",\n      \"signature\": \"f03bf4ecbe116c3281dd16b6ed1f80b16dc7a9ecd410a08244b5c41677fe5581\"\n    },\n    {\n      \"id\": \"RULE-L68\",\n      \"source_line\": 68,\n      \"text\": \"19. Уточнение формата: не писать отдельную строку сверки перед каждой командой в пакете.\",\n      \"signature\": \"4087c4bd5e52ca8c6f65e39b077c4bda9ae4a43aafac5857d5f33cc3f25683f0\"\n    },\n    {\n      \"id\": \"RULE-L84\",\n      \"source_line\": 84,\n      \"text\": \"Не использовать больше сгенерированные большие base64 PHP для SMTP/XenForo.\",\n      \"signature\": \"4db02d79ff907fa767992151c4a00ccff168749fa69417a6bcbeb6108656f2c0\"\n    },\n    {\n      \"id\": \"RULE-L93\",\n      \"source_line\": 93,\n      \"text\": \"Нельзя использовать { echo RULE_CHECK_FAIL; exit 1; } в командах, выполняемых прямо в интерактивном root@pve01 shell.\",\n      \"signature\": \"074e8d6400e50fd4a166ae4f67cf2da00d64051999145e17bbdaa432470ba64f\"\n    },\n    {\n      \"id\": \"RULE-L105\",\n      \"source_line\": 105,\n      \"text\": \"Не использовать -crlf, если команды уже отправляются с явным \\\\r\\\\n.\",\n      \"signature\": \"d8ee091ee5bb14359082cbc1e1b371d8128ddcff586b52516fed69b743f29cdf\"\n    },\n    {\n      \"id\": \"RULE-L110\",\n      \"source_line\": 110,\n      \"text\": \"Не использовать heredoc Python внутри вложенного ssh; для dashboard verification использовать cat/grep JSON или простые команды.\",\n      \"signature\": \"006d36b18ca6c52314d1e1d138393caf26b0d0e47533bf59ccba30210d12cbd7\"\n    },\n    {\n      \"id\": \"RULE-L123\",\n      \"source_line\": 123,\n      \"text\": \"Нельзя считать monitoring слой runtime-closed только по secret scan/reference proof; нужно отдельно проверить Prometheus container/API и обновить справочник по факту.\",\n      \"signature\": \"6744f09160604e98d226f32ef123dd61ee08278f0321c63edd729728300115e7\"\n    },\n    {\n      \"id\": \"RULE-L125\",\n      \"source_line\": 125,\n      \"text\": \"31. Строгое правило: перед каждой командой сверяться с файлом ошибок и справочником.\",\n      \"signature\": \"29acbf555ad438e9e4125ecb8c53e0ce38f6bce98ed3716e639afbb4c0eff3d7\"\n    },\n    {\n      \"id\": \"RULE-L128\",\n      \"source_line\": 128,\n      \"text\": \"Если сверка не пройдена, команда не должна выполнять основное действие. Не использовать exit 1, чтобы не закрывать SSH-сессию.\",\n      \"signature\": \"f20bbb59669123830e2c65160a0b67bf287cdcf82e94b17992919942fd715698\"\n    },\n    {\n      \"id\": \"RULE-L132\",\n      \"source_line\": 132,\n      \"text\": \"Файл 153_PROMETHEUS_TARGETS_SETTLED_PROOF.txt нельзя считать валидным settled proof.\",\n      \"signature\": \"8d28bb1cb84636890872e04ff869c76a8690936fed3b82af881bf1f2f83961e0\"\n    },\n    {\n      \"id\": \"RULE-L133\",\n      \"source_line\": 133,\n      \"text\": \"Для таких проверок не использовать вложенный Python; использовать curl + grep/sed/awk либо отдельный локальный файл-скрипт.\",\n      \"signature\": \"0d986ea7ea4c0cb4231812d5cf50fee6da9bdb43df2e61e2958c4536a56003b3\"\n    },\n    {\n      \"id\": \"RULE-L176\",\n      \"source_line\": 176,\n      \"text\": \"- Do not split work into many small commands when the operations are low-risk, read-only, or naturally part of one evidence block.\",\n      \"signature\": \"4ab34705facd281e3ea1b0cd209248f3b4087f623cf38f8b8622cbf511e67dea\"\n    },\n    {\n      \"id\": \"RULE-L182\",\n      \"source_line\": 182,\n      \"text\": \"- Rule: avoid large opaque base64 script payloads for homelab changes; use transparent bounded shell/perl/python commands instead.\",\n      \"signature\": \"3b1b7b83b7ed141a8dbf54ea052b6a3c6fb5701e0bc056939fa523727f996097\"\n    },\n    {\n      \"id\": \"RULE-L185\",\n      \"source_line\": 185,\n      \"text\": \"- Rule: do not use large opaque payloads, generated base64 scripts, or hidden multi-line script blobs for homelab changes.\",\n      \"signature\": \"386eaacb52062f19dc9f3fb9581a03ee50d5fbf08e19daba33f7271778b9b8e3\"\n    },\n    {\n      \"id\": \"RULE-L188\",\n      \"source_line\": 188,\n      \"text\": \"- Any Traceback, UnicodeDecodeError, SyntaxError, Permission denied, command-not-found, nonzero critical RC, or failed write marker must force REVIEW or FAILED, never OK.\",\n      \"signature\": \"540af43a414bcfd4a26cf5304ca785fbd5a127d99423647298862c964c3f493d\"\n    },\n    {\n      \"id\": \"RULE-L196\",\n      \"source_line\": 196,\n      \"text\": \"- External informational widgets such as weather/Open-Meteo must not block the service launcher portal.\",\n      \"signature\": \"74d434993ef9b2957b7beb9d50b3b35a30b84407a845454229c05e548bc91096\"\n    },\n    {\n      \"id\": \"RULE-L201\",\n      \"source_line\": 201,\n      \"text\": \"- Rule: do not use deeply nested one-liners for config edits; first run short state checks, then apply small transparent edits.\",\n      \"signature\": \"20fada2b3e3a676500b865a15816c133eb784aa10f4c6d7dab66220e573a8615\"\n    },\n    {\n      \"id\": \"RULE-L206\",\n      \"source_line\": 206,\n      \"text\": \"- Matches in /logs/ or *.bak* are historical evidence only and must not make cleanup REVIEW unless the active config still contains the target.\",\n      \"signature\": \"62de758aaf9d70c35a2f1fe076f80a8308e46d3b13d23c38665ab9f8ccc1cba4\"\n    },\n    {\n      \"id\": \"RULE-L209\",\n      \"source_line\": 209,\n      \"text\": \"- Rule: Homepage link validation must not treat HTTP 200 as success if the body is the NPMPlus default “Congratulations” / host-not-set-up page.\",\n      \"signature\": \"04b9d2a779820f3461cbb102ac47f6874198613575464488f460a9d287a089af\"\n    },\n    {\n      \"id\": \"RULE-L217\",\n      \"source_line\": 217,\n      \"text\": \"- Rule: do not apply Netcraze config through stdin/multiline SSH; prove CLI input mode first.\",\n      \"signature\": \"308cfe5fb5cfe99cb8c114de48913256103645e6a9e81ae9bc7ff446a484de9a\"\n    },\n    {\n      \"id\": \"RULE-L228\",\n      \"source_line\": 228,\n      \"text\": \"- Rule: do not attempt ACL/config changes as routerbackup; use admin/operator router access or router Web UI for write changes.\",\n      \"signature\": \"ce0e7d2dc85078db0180beacc1df838f3fe02f41db5de2d31573fe08f54a0f29\"\n    },\n    {\n      \"id\": \"RULE-L245\",\n      \"source_line\": 245,\n      \"text\": \"- Rule: do not rely on custom Node lookup callback until separately tested; prefer curl --resolve or simple direct Node HTTP checks.\",\n      \"signature\": \"25ad4cb5f7058222a9b5f59e98067f1db67a272e35cdd8292ca98eb050b3a0dc\"\n    },\n    {\n      \"id\": \"RULE-L252\",\n      \"source_line\": 252,\n      \"text\": \"- Rule: do not keep changing ACL for this symptom; ACL is already correct. Fix service access scope or use a dedicated monitor endpoint.\",\n      \"signature\": \"43f30d594ffb8d392ebc4b7defd82321e0f844a8b789a9b1d148a13df593d18c\"\n    },\n    {\n      \"id\": \"RULE-L259\",\n      \"source_line\": 259,\n      \"text\": \"- Rule: do not use py_compile against root-owned system paths from an unprivileged user.\",\n      \"signature\": \"ccf2aa95504d6021f9abcd8fecd6409a7a5efc61e8123fbbe7e03f539e979f89\"\n    },\n    {\n      \"id\": \"RULE-L264\",\n      \"source_line\": 264,\n      \"text\": \"- Impact: do not use [PRIVATE_IP] directly as Homepage siteMonitor from edge-vm.\",\n      \"signature\": \"8dfd3ac2d710c3ec6c9a2ec45f12611f948b98b8fe89d4d24b509a8d96082073\"\n    },\n    {\n      \"id\": \"RULE-L279\",\n      \"source_line\": 279,\n      \"text\": \"- Rule: validate generated Python syntax locally before scp/remote execution; avoid f-strings in generated maintenance scripts.\",\n      \"signature\": \"d7c5d3ed72ee048311445f823a903a5ecb6d22b03afec741f277aca10cd77858\"\n    },\n    {\n      \"id\": \"RULE-L284\",\n      \"source_line\": 284,\n      \"text\": \"- Rule: do not proceed with OS baseline until SSH failure is diagnosed; likely old known_hosts key or cloud-init/root-key issue.\",\n      \"signature\": \"9831f60cb061cc933e8c6688faad3f6b352d37d6237a03eefb5917a8facee420\"\n    },\n    {\n      \"id\": \"RULE-L296\",\n      \"source_line\": 296,\n      \"text\": \"- Rule: avoid nested $(...) in VM SSH proofs; use literal remote commands and clean proof.\",\n      \"signature\": \"65c4ac8d2adf3259f26e14c7d971b1ac68082da836bc807214901e58db59fdfa\"\n    },\n    {\n      \"id\": \"RULE-L333\",\n      \"source_line\": 333,\n      \"text\": \"- Impact: do not trust that SQLite inspection attempt.\",\n      \"signature\": \"23045ac8610d67d57dc1a7eb430578d6468f19d1acea9c7584f2a6f471393418\"\n    },\n    {\n      \"id\": \"RULE-L334\",\n      \"source_line\": 334,\n      \"text\": \"- Rule: avoid nested SSH heredoc/Python for this task; use simpler commands, generated nginx configs, or uploaded/local script files.\",\n      \"signature\": \"f4b937e830b202a02596fd60dbc1f2213645be3c5a70d929c8ff536f4adceb2c\"\n    },\n    {\n      \"id\": \"RULE-L340\",\n      \"source_line\": 340,\n      \"text\": \"- Rule: read NPMplus API login values from docker inspect env internally, never print them.\",\n      \"signature\": \"520fdfde2e139662a8738bb691cfe1929b9b9023a4dcc274e88a2b32ef1bfc5b\"\n    },\n    {\n      \"id\": \"RULE-L351\",\n      \"source_line\": 351,\n      \"text\": \"- Impact: do not use NPMplus API for this publish path.\",\n      \"signature\": \"dfce8b1c71b656add085d5c068f621a441177281533d880c8dc230b17b0e22dd\"\n    },\n    {\n      \"id\": \"RULE-L357\",\n      \"source_line\": 357,\n      \"text\": \"- Impact: certbot ran without token and all certificate attempts failed; DNS cutover must not proceed from proof 535.\",\n      \"signature\": \"a0e59d5dfd804bc63152339e3d2a3b0b288578d976b2039231a6272cbd9d002b\"\n    },\n    {\n      \"id\": \"RULE-L406\",\n      \"source_line\": 406,\n      \"text\": \"- Rule: never source SMTP secret files containing arbitrary passwords; store password base64 and use msmtp passwordeval helper accessible to www-data.\",\n      \"signature\": \"429fad764756578d3ed22029b6c2d6af0912b9b28d4a2263969cc53a090ba938\"\n    },\n    {\n      \"id\": \"RULE-L419\",\n      \"source_line\": 419,\n      \"text\": \"- Rule: never store arbitrary passwords as shell-sourceable KEY=value files; use non-printing secret storage only.\",\n      \"signature\": \"d04ca47a86fba60c038b212f585230b1bbb335493d583def83cbc3993bd7d88f\"\n    },\n    {\n      \"id\": \"RULE-L424\",\n      \"source_line\": 424,\n      \"text\": \"- Rule: do not change DNS, firewall, proxy, mail, NetBird, or Mailcow configs before read-only triage proof.\",\n      \"signature\": \"cdb23b83af44d2282df9c2a08be575953d61f280cc9abd53a4cc43b3e653cd1c\"\n    },\n    {\n      \"id\": \"RULE-L430\",\n      \"source_line\": 430,\n      \"text\": \"- Rule: do not test SMTP auth again until a fresh rotated mailbox/app password is available.\",\n      \"signature\": \"6694ecf8bef00c902be47d179510d8d4aadc93961c0164598e99350a41982f73\"\n    },\n    {\n      \"id\": \"RULE-L436\",\n      \"source_line\": 436,\n      \"text\": \"- Impact: old timer must not be treated as valid current backup for all five forums.\",\n      \"signature\": \"30e78abd678ecee3a63ddbd2d71e7caa390229722dab892fbfae0c891b725f13\"\n    },\n    {\n      \"id\": \"RULE-L463\",\n      \"source_line\": 463,\n      \"text\": \"- Never print or package secrets.\",\n      \"signature\": \"a0014dc66b74aee1b458771d46c51c80e582ee1bb69a1316aa6b63c91a3a40b2\"\n    },\n    {\n      \"id\": \"RULE-L468\",\n      \"source_line\": 468,\n      \"text\": \"- Impact: proof 623 is invalid and must not be used to judge mail delivery.\",\n      \"signature\": \"cdbba5ccdd73b604f4f3970ae1d69868a9d409f74ead6be3d60c031f26afa3f5\"\n    },\n    {\n      \"id\": \"RULE-L497\",\n      \"source_line\": 497,\n      \"text\": \"- Do not print secrets.\",\n      \"signature\": \"59a6b5bdc55ccb782cc5fa6d93fdf3d734c32d8250a2c21a57c6c7d4f6f3cd24\"\n    },\n    {\n      \"id\": \"RULE-L498\",\n      \"source_line\": 498,\n      \"text\": \"- Do not download or upload:\",\n      \"signature\": \"3de6b008bc8038faf7099e21fb98ec4280a409c05d3ae3453ec80bcd81e62240\"\n    },\n    {\n      \"id\": \"RULE-L514\",\n      \"source_line\": 514,\n      \"text\": \"- Rule: do not use Cloudflare DNS-01 for these parked domains unless a token with explicit zone access is installed; because DNS already points to edge, prefer a copied-script HTTP-01 flow with temporary ACME challenge routes and content-specific validation.\",\n      \"signature\": \"92d2f5a1cd4f344851af8d7a716dc24d8bc6a7738a47f6aa05013676f2200d61\"\n    },\n    {\n      \"id\": \"RULE-L533\",\n      \"source_line\": 533,\n      \"text\": \"- Rule: do not run another apply stage until active NPMplus nginx route selection for newfi.ru is diagnosed with nginx -T and Host-header probes.\",\n      \"signature\": \"039b358ea5679e61f5d7d8806ee36af893c18627bf8d26c434e902fb4c0e217f\"\n    },\n    {\n      \"id\": \"RULE-L538\",\n      \"source_line\": 538,\n      \"text\": \"- Impact: HTTP-01 cannot work through the default server. A dedicated temporary ACME server block must be proven before certbot; do not run another certbot apply without a successful ACME-path probe for all six hostnames.\",\n      \"signature\": \"2f1d3fca5be936885c2fff63166f1f43925dd8097efc48cfd46b54ff135f235a\"\n    },\n    {\n      \"id\": \"RULE-L544\",\n      \"source_line\": 544,\n      \"text\": \"- Impact: do not continue certbot/apply until exact newfi.ru route behavior is isolated.\",\n      \"signature\": \"8710aaa42a28a4c0a5598837af21305c7f4f17db34832d9e09cde58db0cf5a4b\"\n    },\n    {\n      \"id\": \"RULE-L551\",\n      \"source_line\": 551,\n      \"text\": \"- Rule: do not use one combined parked-domain server_name block for these domains; use exact host blocks and content-specific validation.\",\n      \"signature\": \"84a57025900a3fc4d1ba84dbcf54cf84b30875b82060e1e0dc66f9ccdf3da5ca\"\n    },\n    {\n      \"id\": \"RULE-L558\",\n      \"source_line\": 558,\n      \"text\": \"- Rule: do not retry parked-domain HTTP-01 with low id 700-705. First prove fixed high-id exact blocks, then apply with those exact ids.\",\n      \"signature\": \"527144862f28a73814ccc2c95fc4a380202a207f194962f9a7fb0a8d34833c2f\"\n    },\n    {\n      \"id\": \"RULE-L564\",\n      \"source_line\": 564,\n      \"text\": \"- Rule: do not continue HTTP-01 for these parked domains; use DNS-01 with a dedicated Cloudflare token that has Zone Read and DNS Edit for the three zones.\",\n      \"signature\": \"0c171db0e19ad3ff56b66bc5623f05867010cd7b2b87f98ef9cbe4d08a5c84f1\"\n    },\n    {\n      \"id\": \"RULE-L568\",\n      \"source_line\": 568,\n      \"text\": \"- Impact: do not rerun Stage15 as-is.\",\n      \"signature\": \"23033da11bdfd30e8d3405fa448f9e0a2516c09a778b90693eb8c01304e0b534\"\n    },\n    {\n      \"id\": \"RULE-L569\",\n      \"source_line\": 569,\n      \"text\": \"- Rule: for parked domains finalization, use existing DNS-01 certificates and validate from pve01 public HTTPS with content marker; do not use the failed local --resolve TLS check as the closure gate.\",\n      \"signature\": \"a6fd815206627d21af51c6ca97a5b2a03681011e1ad80eb0e6b29044cbce0323\"\n    },\n    {\n      \"id\": \"RULE-L573\",\n      \"source_line\": 573,\n      \"text\": \"- Rule: Stage18 must not change DNS records or NPMplus route files. It may update placeholder HTML, run existing forum renewal script once for runtime proof, and run certbot dry-run for parked certificates using existing DNS-01 hooks.\",\n      \"signature\": \"09d9b1e72b0bb7016128313662cddfa540a7a7908e768f8d4d22e5efd62168a0\"\n    },\n    {\n      \"id\": \"RULE-L577\",\n      \"source_line\": 577,\n      \"text\": \"- Rule: only root gram1.ru and www.gram1.ru may be routed. Existing gram1.ru subdomain NPMplus routes must not be changed.\",\n      \"signature\": \"dab2f98b8be402bac17f88d4e39ecfea8bdd5726097272cff11f7558f55eed0e\"\n    },\n    {\n      \"id\": \"RULE-L578\",\n      \"source_line\": 578,\n      \"text\": \"- Rule: use DNS-01 with a dedicated gram1 Cloudflare token; do not use HTTP-01.\",\n      \"signature\": \"40b057e3fd9659583584a2e3d4167a256076e134c4fbaa3678fcfc2503e55d2d\"\n    },\n    {\n      \"id\": \"RULE-L583\",\n      \"source_line\": 583,\n      \"text\": \"- Decision: because pve01 has no SSH key access to the external VPS, do not modify the VPS. Instead move only pvepro.ru and www.pvepro.ru A records to edge and serve an edge landing page.\",\n      \"signature\": \"17a7cd4c3204ffc9a003a8a6cef3e9be96599674d54f0f1a217eb1f3f0dad811\"\n    },\n    {\n      \"id\": \"RULE-L584\",\n      \"source_line\": 584,\n      \"text\": \"- Safety rule: do not change mail.pvepro.ru, nb.pvepro.ru, MX, SPF, DKIM, DMARC or forum SMTP configuration.\",\n      \"signature\": \"20b36bae3948a0b947ad12ca12a28cf8041c2ee4dd71dd71479c523fbeeaab0f\"\n    },\n    {\n      \"id\": \"RULE-L585\",\n      \"source_line\": 585,\n      \"text\": \"- Use a dedicated Cloudflare token for pvepro.ru. Do not print token values.\",\n      \"signature\": \"028d50fb2fef9139bce5a6a16069b7cdd69ab88719f640f0f0858034931d3d9b\"\n    },\n    {\n      \"id\": \"RULE-L594\",\n      \"source_line\": 594,\n      \"text\": \"- Mistakes recorded: do not scp scripts/* after local py_compile because __pycache__ may be present; do not continue after critical scp failure in future apply stages; do not classify PVEPro landing as NetBird just because it contains a link text; use Cloudflare authoritative A records and --resolve validation when DNS propagation may lag.\",\n      \"signature\": \"25354a6b9b469385d2300d5d321f8415312bb2392f2e96b37250717afce13b34\"\n    },\n    {\n      \"id\": \"RULE-L600\",\n      \"source_line\": 600,\n      \"text\": \"- Rule: do not expose router admin publicly and do not attempt certificate deployment to the router over the public internet.\",\n      \"signature\": \"b936ae30ffb935118585b5a133d9c32643946a3ba5034b85f42bae000518d49a\"\n    },\n    {\n      \"id\": \"RULE-L606\",\n      \"source_line\": 606,\n      \"text\": \"- Rule: do not expose router admin publicly and do not attempt certificate deployment to the router over the public internet.\",\n      \"signature\": \"b936ae30ffb935118585b5a133d9c32643946a3ba5034b85f42bae000518d49a\"\n    },\n    {\n      \"id\": \"RULE-L611\",\n      \"source_line\": 611,\n      \"text\": \"- Rule: do not print WireGuard private keys, PSK, or client config body in proof/chat.\",\n      \"signature\": \"d97ed9abb2447ca6786e0f266db8fe55e6071f854727ed9feb01a333d4fdd68e\"\n    },\n    {\n      \"id\": \"RULE-L613\",\n      \"source_line\": 613,\n      \"text\": \"- Rule: do not open router public admin or certificate deployment until WireGuard handshake is proven.\",\n      \"signature\": \"c44dbc849473687dad5c7756ca31b9b1366190f1f974a009ab8a2b2a175887cc\"\n    },\n    {\n      \"id\": \"RULE-L641\",\n      \"source_line\": 641,\n      \"text\": \"- Do not retry immediately.\",\n      \"signature\": \"e1208fd6483129c1940ee58ac88c1b14996e9906f5854c93a893423378ef3f63\"\n    },\n    {\n      \"id\": \"RULE-L662\",\n      \"source_line\": 662,\n      \"text\": \"- Do not treat e3qxxx.netbird.selfhosted / 100.100.125.70 and e3qxxx-183-106.netbird.selfhosted / 100.100.183.106 as VPS egress nodes; they are user mobile peers.\",\n      \"signature\": \"d025d035ce8bfa83c9f7b3605f86af415bded0a45359ee22348ce089f0a4de9f\"\n    },\n    {\n      \"id\": \"RULE-L669\",\n      \"source_line\": 669,\n      \"text\": \"- Do not delete or disable Homepage siteMonitor fields to hide red badges.\",\n      \"signature\": \"b101de9840659abe0fdbc458eeb21988b1ca2ef12a7cdce01e42f57991281d98\"\n    },\n    {\n      \"id\": \"RULE-L671\",\n      \"source_line\": 671,\n      \"text\": \"- Do not touch Cloudflare Homepage card when operator says it is green and opens correctly.\",\n      \"signature\": \"e55eaa04a75332bfdad36576da5425a956fa6e84f1003a1c606e64b0701737a5\"\n    },\n    {\n      \"id\": \"RULE-L678\",\n      \"source_line\": 678,\n      \"text\": \"- Do not touch Cloudflare when operator says it is green and opens correctly.\",\n      \"signature\": \"df725b9f5c7198ce4c04f9c37c1a2705147a9b2547a8f6de89800c03c68ea37f\"\n    },\n    {\n      \"id\": \"RULE-L684\",\n      \"source_line\": 684,\n      \"text\": \"- Do not delete containers to fix Dockge inactive UI; connect agents and archive only confirmed stale moved stack definitions.\",\n      \"signature\": \"b31b7d7bef8b7f89787850a3c73ce500918e256710e16626efa0d983dbec9b31\"\n    },\n    {\n      \"id\": \"RULE-L711\",\n      \"source_line\": 711,\n      \"text\": \"- Future preparation must use exact privileged paths or a verified shared group, never an unprivileged wildcard after restrictive chmod.\",\n      \"signature\": \"ffadddc535d9f6ef63a2e5f96282815f86c2844d21a1302af925758271cf605f\"\n    },\n    {\n      \"id\": \"RULE-L737\",\n      \"source_line\": 737,\n      \"text\": \"- The six wrapper validation failures are cascading missing-marker checks and do not prove six independent infrastructure faults.\",\n      \"signature\": \"edb305f9a22332f3efb7192d483e3f067a1c50b93b0152596c8bbdff8b58fcd3\"\n    },\n    {\n      \"id\": \"RULE-L768\",\n      \"source_line\": 768,\n      \"text\": \"- The phase-wide forbidden-pattern scan also included validate_design.py itself, which necessarily contains the prohibited literals it verifies.\",\n      \"signature\": \"c9daa84dfbc8edb3cc4046148bdcb08f590a765c069f15dccba110171dcfefee\"\n    },\n    {\n      \"id\": \"RULE-L789\",\n      \"source_line\": 789,\n      \"text\": \"- The backup must not be rerun merely to satisfy the invalid health-file freshness assertion.\",\n      \"signature\": \"85bc7d2eeb4c251bf6db4f1e1e9be31bd05c124e10a3c3d0c9b671f1c2aea449\"\n    },\n    {\n      \"id\": \"RULE-L793\",\n      \"source_line\": 793,\n      \"text\": \"- Mandatory anti-regression: после ошибки запрещено продолжать другой scope до точной root cause, исправления, повторной проверки, очистки, proof и seal.\",\n      \"signature\": \"3506ad8423c9306313c4995dffee63340e95a4e1b0fdab450b433570927d3602\"\n    },\n    {\n      \"id\": \"RULE-L818\",\n      \"source_line\": 818,\n      \"text\": \"- Anti-regression: never combine sudo with `< protected-file`; pass the protected path as an argument to the privileged command.\",\n      \"signature\": \"8b5571b92d02dbe05c3f8a9f7637776e8665b44ce49521d4fe408fa977c13e39\"\n    },\n    {\n      \"id\": \"RULE-L825\",\n      \"source_line\": 825,\n      \"text\": \"- Correction: assert required and forbidden column counts before querying recent runs.\",\n      \"signature\": \"598215d2c23dd83bd6faa1beedb94f32b94ac3fd7e0573ea754db0f680b415ab\"\n    },\n    {\n      \"id\": \"RULE-L834\",\n      \"source_line\": 834,\n      \"text\": \"- Anti-regression: stdin-скрипт обязан валидировать $1 по разрешённому path pattern и затем подтверждать точные SHA256 и byte count; запрещено добавлять отдельный bash-token после remote command.\",\n      \"signature\": \"333aa0cc07135a79e5044a85b773ad5a7f0e655fd0a0d6422ec994230ce8112a\"\n    },\n    {\n      \"id\": \"RULE-L867\",\n      \"source_line\": 867,\n      \"text\": \"- Correction: headings and prose do not contain machine-token values; validation counts only complete exact token lines.\",\n      \"signature\": \"d820b99656b281c428c56ed8698a3fe4295e435e708263040de0136d1a42a18d\"\n    },\n    {\n      \"id\": \"RULE-L869\",\n      \"source_line\": 869,\n      \"text\": \"- Required pattern: grep -Fxc against a complete machine line, never grep -Foc against an unrestricted substring.\",\n      \"signature\": \"bab78eea9eadb2614ae16292dec8fda0bead38c2223efd2c79b512d1ad965836\"\n    },\n    {\n      \"id\": \"RULE-L879\",\n      \"source_line\": 879,\n      \"text\": \"- Correction: use a linear wrapper with explicitly closed if/case blocks and avoid nested brace groups in conditional expressions.\",\n      \"signature\": \"e6627533fc6f44113d3e3fb415ee4807a46afce3393d2457ca87649f3e95a338\"\n    },\n    {\n      \"id\": \"RULE-L895\",\n      \"source_line\": 895,\n      \"text\": \"- Task v6 status: REJECTED_BY_LINT; never executed and never mutated.\",\n      \"signature\": \"eaa651690a1e2b17124007e55346be7ad52d4d9f6172746262bc0698c376396d\"\n    }\n  ],\n  \"command_ledger\": {\n    \"present\": true,\n    \"entry_count\": 10,\n    \"failed_entry_count\": 0,\n    \"failed_command_hashes\": []\n  },\n  \"privacy\": {\n    \"sanitized\": true,\n    \"secrets_included\": false,\n    \"private_addresses_included\": false\n  }\n}\n"
}
