1
0
Files
homelab-public-context/runtime/history/ERRORS-INDEX-004.json
homelab-runtime-publisher a615069de3 runtime: publish ERRORS-INDEX-004
2026-07-21 07:22:23 +00:00

31 lines
143 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"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 Lets 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"
}