CHAT_OUTPUT_BEGIN
COMMAND_ID=NEWFI-260907-A-PORTAL200-APPLY4DESIGN7
STATUS=OK
RC=0
HOST=pve01
MODE=read-only
COMPONENT=newfi-portal200-apply4-static-ast-recovery
REFERENCE_REGISTER_CHECK=OK
REFERENCE_SHA256=5a3d8e5154c41cb582a4e0aca68090be1f0138918bf82131a948df326f9d8d66
ERROR_REGISTER_CHECK=OK
ERROR_REGISTER_SHA256=3b09a553ec0f527ed3afeed4753f52a74ee3036045bbfb3c685e9f8af4ba7ba0
COMMAND_SHA256=f496660f98e2d5bd57395b705f69565e9b9b4fa3823630155bbe079f561a2876
DUPLICATE_FAILED_COMMAND_BLOCKED=false
EXECUTION_STARTED=true
CHANGE_DECLARED=false
RESULT_CONTRACT_VALID=true
RESULT_CONTRACT_STATUS=NOT_APPLICABLE
RESULT_CONTRACT_ERROR=NONE
COMMAND_RC=0
CHANGES_MADE=false
ROLLBACK_STARTED=false
ROLLBACK_RESTORED=null
MUTATION_OUTCOME=NO_MUTATION
SANITIZED=yes
SECRETS_INCLUDED=no
PRIVATE_ADDRESSES_INCLUDED=no
RAW_EVIDENCE_SHA256=b0e750afcc8c28e33530e823fa9ca879ddd22ce66e758296aeb27ffd61aa5d1a
SANITIZED_OUTPUT_SHA256=b0e750afcc8c28e33530e823fa9ca879ddd22ce66e758296aeb27ffd61aa5d1a
OUTPUT_BEGIN
AST_RECOVERY_BEGIN=true
OLD_CARRIER_SHA256=573c5c94fe59ed072e094281668ce9847dc4a9882019a2b89d8ba5f190d92176
OLD_INNER_SHA256=97a1807a275e5d309591f7a7e044d7d56ba5b2f4c6a068df6353bae147707184
OLD_INNER_BYTES=34155
TOP_LEVEL_LITERAL_ASSIGNMENTS={"BRANCH":"implementation/newfi-closed-mvp-20260906","CID":"NEWFI-260907-PORTAL200-APPLY3","ERRORS_PHP_TEMPLATE":"<?php\n$root='/var/www/forums/newfi/public';\nrequire $root.'/src/XF.php';\n\\XF::start($root);\n$app=\\XF::setupApp('XF\\\\Pub\\\\App');\n$db=$app->db();\n$b=%d;\n$rows=$db->fetchAll(\n    \"SELECT error_id,exception_type,message,filename,line FROM xf_error_log WHERE error_id>? ORDER BY error_id\",\n    [$b]\n);\necho json_encode(\n    $rows,\n    JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES|JSON_INVALID_UTF8_SUBSTITUTE\n);\n?>","EXPECTED_NEW":{"Pub/Controller/Portal.php":[363,"619d489f7cc3b461479c702fa8825097ae22d3c6cfeb911e9f14ea1de9c36d6d"],"Service/PortalData.php":[5213,"4da03f26294ab6d163a1a746dee4bb6a82d7d4f39e66b24246bc0eb4c455dd1d"],"_data/templates.xml":[10977,"1650c3d868d4bc47703f1b75f1f179cd25309b2d3b20a2b848e81a4423c00861"]},"EXPECTED_OLD":{"Pub/Controller/Portal.php":[247,"53909e753ea5a011550fd280ad1c342a74ca9949be092dfbd95244243cae92f3"],"_data/templates.xml":[1461,"c29c04f8b937284e821c87957653e87c2f6ac014fac30648a0f6b18e84392adf"]},"HTTP_VERIFY_PY":"import html,json,re,subprocess,sys,urllib.parse\nmode=sys.argv[1]\nstate=sys.argv[2]\nif mode=='staging':\n    host='newfi-staging.gram1.ru'; scheme='http'; port='80'; expected_status=503\nelif mode=='prod':\n    host='newfi.ru'; scheme='https'; port='443'; expected_status=200\nelse:\n    raise SystemExit(40)\ndef fetch(url):\n    cmd=[\n        '/usr/bin/curl','-sS','-k','--max-time','30','--connect-timeout','10'\n    ]\n    if mode=='staging':\n        cmd += ['--resolve',f'{host}:{port}:127.0.0.1']\n    cmd += [\n        '-o','-','-w','\\n__PORTAL_STATUS__%{http_code}',url\n    ]\n    p=subprocess.run(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE,check=False)\n    if p.returncode!=0: raise SystemExit(41)\n    text=p.stdout.decode('utf-8','replace')\n    marker='\\n__PORTAL_STATUS__'\n    if marker not in text: raise SystemExit(42)\n    body,status=text.rsplit(marker,1)\n    return int(status.strip()),body\nroot=f'{scheme}://{host}/'\nstatus,body=fetch(root)\nhrefs=[]\nfor raw in re.findall(r\"href=[\\\"']([^\\\"']*css\\.php[^\\\"']*)[\\\"']\",body,re.I):\n    raw=html.unescape(raw)\n    hrefs.append(urllib.parse.urljoin(root,raw))\nnormal_css=[]\nnormal_status=[]\nfor u in hrefs:\n    s,b=fetch(u)\n    normal_status.append(s)\n    normal_css.append(b)\nnormal_css_text='\\n'.join(normal_css)\nforced_css_marker=None\nforced_css_status=None\nif mode=='staging' and state=='new' and hrefs:\n    u=urllib.parse.urlsplit(hrefs[0])\n    pairs=urllib.parse.parse_qsl(u.query,keep_blank_values=True)\n    out=[]\n    found=False\n    for k,v in pairs:\n        if k=='css':\n            found=True\n            parts=[x for x in v.split(',') if x]\n            if 'public:newfi_portal.less' not in parts:\n                parts.append('public:newfi_portal.less')\n            v=','.join(parts)\n        out.append((k,v))\n    if found:\n        forced=urllib.parse.urlunsplit((u.scheme,u.netloc,u.path,urllib.parse.urlencode(out),u.fragment))\n        forced_css_status,forced_body=fetch(forced)\n        forced_css_marker='--newfi-portal200' in forced_body\nresult={\n 'status':status,\n 'bytes':len(body.encode()),\n 'html_marker':'data-newfi-portal200=\"1\"' in body,\n 'css_marker':'--newfi-portal200' in normal_css_text,\n 'old_copy':'Закрытое пространство для общения, знаний и материалов сообщества.' in body,\n 'css_count':len(hrefs),\n 'css_status':normal_status,\n 'forced_css_status':forced_css_status,\n 'forced_css_marker':forced_css_marker,\n}\nprint('PORTAL200_HTTP_JSON='+json.dumps(result,ensure_ascii=False,sort_keys=True,separators=(',',':')))\nif status!=expected_status: raise SystemExit(45)\nif mode=='prod':\n    if not hrefs or any(x!=200 for x in normal_status): raise SystemExit(46)\nelif mode=='staging':\n    if any(x!=200 for x in normal_status): raise SystemExit(46)\nelse:\n    raise SystemExit(50)\nif mode=='prod' and state=='new':\n    if not result['html_marker'] or not result['css_marker']: raise SystemExit(47)\nelif mode=='prod' and state=='old':\n    if result['html_marker'] or result['css_marker'] or not result['old_copy']: raise SystemExit(48)\nelif mode=='staging' and state=='new':\n    if hrefs and (forced_css_status!=200 or not forced_css_marker): raise SystemExit(49)\nelif mode=='staging' and state=='old':\n    pass\nelse:\n    raise SystemExit(50)\n","IMPORT_PHP":"<?php\n$root = '/var/www/forums/newfi/public';\nrequire $root . '/src/XF.php';\n\\XF::start($root);\n$app = \\XF::setupApp('XF\\\\Pub\\\\App');\n$addOn = $app->addOnManager()->getById('Newfi/Portal');\nif (!$addOn || !$addOn->isInstalled()) {\n    fwrite(STDERR, \"ADDON_NOT_INSTALLED\\n\");\n    exit(30);\n}\n$addOn->preRebuild();\n$dataManager = $app->addOnDataManager();\n$imported = [];\nforeach ($dataManager->getDataTypeClasses() as $dataType) {\n    $handler = $dataManager->getDataTypeHandler($dataType);\n    $xml = $handler->openTypeFile($addOn->getDataDirectory());\n    if (!$xml || !$xml->count()) {\n        $handler->deleteAddOnData($addOn->addon_id);\n        continue;\n    }\n    $start = 0;\n    $guard = 0;\n    while (true) {\n        $last = $handler->importAddOnData($addOn->addon_id, $xml, $start, 0);\n        if (!$last) { break; }\n        if ($last <= $start || ++$guard > 100000) {\n            fwrite(STDERR, \"IMPORT_PROGRESS_INVALID\\n\");\n            exit(31);\n        }\n        $start = $last;\n    }\n    $handler->deleteOrphanedAddOnData($addOn->addon_id, $xml);\n    \\XF::triggerRunOnce();\n    $imported[] = $handler->getContainerTag();\n}\n$addOn->postDataImport();\n$repo = $app->repository(\\XF\\Repository\\StyleRepository::class);\n$repo->updateAllStylesLastModifiedDateLater();\n$addOn->postRebuild();\necho \"PORTAL200_IMPORT_JSON=\" . json_encode(\n    ['result'=>'PASS','containers'=>$imported],\n    JSON_UNESCAPED_SLASHES\n) . \"\\n\";\n?>","INSPECT_PHP":"<?php\n$root = '/var/www/forums/newfi/public';\nrequire $root . '/src/XF.php';\n\\XF::start($root);\n$app = \\XF::setupApp('XF\\\\Pub\\\\App');\n$db = $app->db();\nfunction srcMeta(string $path): ?array {\n    if (!is_file($path)) { return null; }\n    return ['bytes' => filesize($path), 'sha256' => hash_file('sha256', $path)];\n}\n$portal = $root . '/src/addons/Newfi/Portal';\n$templates = $db->fetchAll(\n    \"SELECT title, style_id, LENGTH(template) AS bytes, SHA2(template,256) AS sha256, \" .\n    \"LOCATE('data-newfi-portal200', template) > 0 AS html_marker, \" .\n    \"LOCATE('--newfi-portal200', template) > 0 AS css_marker \" .\n    \"FROM xf_template WHERE addon_id='Newfi/Portal' ORDER BY style_id,title\"\n);\n$result = [\n 'hostname' => gethostname(),\n 'application' => [\n  'boardActive' => (string)$db->fetchOne(\"SELECT option_value FROM xf_option WHERE option_id='boardActive'\"),\n  'boardUrl' => (string)$db->fetchOne(\"SELECT option_value FROM xf_option WHERE option_id='boardUrl'\"),\n  'indexRoute' => (string)$db->fetchOne(\"SELECT option_value FROM xf_option WHERE option_id='indexRoute'\"),\n  'defaultStyleId' => (string)$db->fetchOne(\"SELECT option_value FROM xf_option WHERE option_id='defaultStyleId'\"),\n  'error_count' => (int)$db->fetchOne(\"SELECT COUNT(*) FROM xf_error_log\"),\n  'error_max_id' => (int)$db->fetchOne(\"SELECT COALESCE(MAX(error_id),0) FROM xf_error_log\")\n ],\n 'addon' => $db->fetchRow(\n  \"SELECT addon_id,version_id,version_string,active,is_processing,last_pending_action \" .\n  \"FROM xf_addon WHERE addon_id='Newfi/Portal'\"\n ),\n 'source' => [\n  'Pub/Controller/Portal.php' => srcMeta($portal . '/Pub/Controller/Portal.php'),\n  'Service/PortalData.php' => srcMeta($portal . '/Service/PortalData.php'),\n  '_data/templates.xml' => srcMeta($portal . '/_data/templates.xml')\n ],\n 'templates' => $templates\n];\necho \"PORTAL200_INSPECT_JSON_BEGIN\\n\";\necho json_encode(\n    $result,\n    JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES|JSON_INVALID_UTF8_SUBSTITUTE\n);\necho \"\\nPORTAL200_INSPECT_JSON_END\\n\";\n?>","OLD_HEAD":"06bab5acd124e768f70305b3d7eca2f595871890","RUNTIME_VERIFY_PHP_TEMPLATE":"<?php\n$state = '__STATE__';\n$root = '/var/www/forums/newfi/public';\nrequire $root . '/src/XF.php';\n\\XF::start($root);\n$app = \\XF::setupApp('XF\\\\Pub\\\\App');\n$result = ['state'=>$state];\nif ($state === 'new') {\n    $service = new \\Newfi\\Portal\\Service\\PortalData($app);\n    $portal = $service->build();\n    $html = (string)$app->templater()->renderTemplate('public:newfi_portal', ['portal'=>$portal]);\n    $result['sections'] = [];\n    foreach (['forums','resources','media','events','dogs'] as $key) {\n        $section = $portal[$key] ?? null;\n        if (!is_array($section)) { fwrite(STDERR, \"SECTION_MISSING\\n\"); exit(60); }\n        $result['sections'][$key] = [\n            'available'=>(bool)($section['available'] ?? false),\n            'hasItems'=>(bool)($section['hasItems'] ?? false),\n            'status'=>(string)($section['status'] ?? '')\n        ];\n    }\n    $result['html_marker'] = strpos($html, 'data-newfi-portal200=\"1\"') !== false;\n    $result['locked_resources'] = strpos($html, 'data-portal-locked=\"resources\"') !== false;\n    $result['locked_media'] = strpos($html, 'data-portal-locked=\"media\"') !== false;\n    $result['html_bytes'] = strlen($html);\n    if (!$result['html_marker']) { fwrite(STDERR, \"HTML_MARKER_MISSING\\n\"); exit(61); }\n    if ($result['sections']['resources']['available'] || $result['sections']['media']['available']) {\n        fwrite(STDERR, \"GUEST_PERMISSION_CONTRACT_CHANGED\\n\"); exit(62);\n    }\n    if (!$result['locked_resources'] || !$result['locked_media']) {\n        fwrite(STDERR, \"LOCKED_COPY_MISSING\\n\"); exit(63);\n    }\n    if (!$result['sections']['forums']['available'] || !$result['sections']['events']['available'] || !$result['sections']['dogs']['available']) {\n        fwrite(STDERR, \"PUBLIC_SECTION_UNAVAILABLE\\n\"); exit(64);\n    }\n} else if ($state === 'old') {\n    $html = (string)$app->templater()->renderTemplate('public:newfi_portal', []);\n    $result['html_marker'] = strpos($html, 'data-newfi-portal200') !== false;\n    $result['old_copy'] = strpos($html, 'Закрытое пространство для общения, знаний и материалов сообщества.') !== false;\n    $result['html_bytes'] = strlen($html);\n    if ($result['html_marker'] || !$result['old_copy']) {\n        fwrite(STDERR, \"OLD_RENDER_CONTRACT\\n\"); exit(65);\n    }\n} else {\n    exit(66);\n}\necho \"PORTAL200_RUNTIME_JSON=\" . json_encode(\n    $result,\n    JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES|JSON_INVALID_UTF8_SUBSTITUTE\n) . \"\\n\";\n?>","commit_created":false,"failure_rollback_started":false,"git_files_changed":false,"old_git_bytes":{},"prod_backup":false,"prod_mutated":false,"push_confirmed":false,"staging_backup":false,"staging_final_new":false,"staging_mutated":false}
CONST_CID=NEWFI-260907-PORTAL200-APPLY3
CONST_BRANCH=implementation/newfi-closed-mvp-20260906
CONST_OLD_HEAD=06bab5acd124e768f70305b3d7eca2f595871890
CONST_WT=UNPROVEN
CONST_CAND_ROOT=UNPROVEN
RELEVANT_CONSTANTS=[{"lineno":10,"segment":"\"NEWFI-260907-PORTAL200-APPLY3\"","value":"NEWFI-260907-PORTAL200-APPLY3"},{"lineno":147,"segment":"r'''<?php\n$root = '/var/www/forums/newfi/public';\nrequire $root . '/src/XF.php';\n\\XF::start($root);\n$app = \\XF::setupApp('XF\\\\Pub\\\\App');\n$db = $app->db();\nfunction srcMeta(string $path): ?array {\n    if (!is_file($path)) { return null; }\n    return ['bytes' => filesize($path), 'sha256' => hash_file('sha256', $path)];\n}\n$portal = $root . '/src/addons/Newfi/Portal';\n$templates = $db->fetchAll(\n    \"SELECT title, style_id, LENGTH(template) AS bytes, SHA2(template,256) AS sha256, \" .\n    \"LOCATE('data-newfi-portal200', template) > 0 AS html_marker, \" .\n    \"LOCATE('--newfi-portal200', template) > 0 AS css_marker \" .\n    \"FROM xf_template WHERE addon_id='Newfi/Portal' ORDER BY style_id,title\"\n);\n$result = [\n 'hostname' => gethostname(),\n 'application' => [\n  'boardActive' => (string)$db->fetchOne(\"SELECT option_value FROM xf_option WHERE option_id='boardActive'\"),\n  'boardUrl' => (string)$db->fetchOne(\"SELECT option_value FROM xf_option WHERE option_id='boardUrl'\"),\n  'indexRoute' => (string)$db->fetchOne(\"SELECT option_value FROM xf_option WHERE option_id='indexRoute'\"),\n  'defaultStyleId' => (string)$db->fetchOne(\"SELECT option_value FROM xf_option WHERE option_id='defaultStyleId'\"),\n  'error_count' => (int)$db->fetchOne(\"SELECT COUNT(*) FROM xf_error_log\"),\n  'error_max_id' => (int)$db->fetchOne(\"SELECT COALESCE(MAX(error_id),0) FROM xf_error_log\")\n ],\n 'addon' => $db->fetchRow(\n  \"SELECT addon_id,version_id,version_string,active,is_processing,last_pending_action \" .\n  \"FROM xf_addon WHERE addon_id='Newfi/Portal'\"\n ),\n 'source' => [\n  'Pub/Controller/Portal.php' => srcMeta($portal . '/Pub/Controller/Portal.php'),\n  'Service/PortalData.php' => srcMeta($portal . '/Service/PortalData.php'),\n  '_data/templates.xml' => srcMeta($portal . '/_data/templates.xml')\n ],\n 'templates' => $templates\n];\necho \"PORTAL200_INSPECT_JSON_BEGIN\\n\";\necho json_encode(\n    $result,\n    JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES|JSON_INVALID_UTF8_SUBSTITUTE\n);\necho \"\\nPORTAL200_INSPECT_JSO","value":"<?php\n$root = '/var/www/forums/newfi/public';\nrequire $root . '/src/XF.php';\n\\XF::start($root);\n$app = \\XF::setupApp('XF\\\\Pub\\\\App');\n$db = $app->db();\nfunction srcMeta(string $path): ?array {\n    if (!is_file($path)) { return null; }\n    return ['bytes' => filesize($path), 'sha256' => hash_file('sha256', $path)];\n}\n$portal = $root . '/src/addons/Newfi/Portal';\n$templates = $db->fetchAll(\n    \"SELECT title, style_id, LENGTH(template) AS bytes, SHA2(template,256) AS sha256, \" .\n    \"LOCATE('data-newfi-portal200', template) > 0 AS html_marker, \" .\n    \"LOCATE('--newfi-portal200', template) > 0 AS css_marker \" .\n    \"FROM xf_template WHERE addon_id='Newfi/Portal' ORDER BY style_id,title\"\n);\n$result = [\n 'hostname' => gethostname(),\n 'application' => [\n  'boardActive' => (string)$db->fetchOne(\"SELECT option_value FROM xf_option WHERE option_id='boardActive'\"),\n  'boardUrl' => (string)$db->fetchOne(\"SELECT option_value FROM xf_option WHERE option_id='boardUrl'\"),\n  'indexRoute' => (string)$db->fetchOne(\"SELECT option_value FROM xf_option WHERE option_id='indexRoute'\"),\n  'defaultStyleId' => (string)$db->fetchOne(\"SELECT option_value FROM xf_option WHERE option_id='defaultStyleId'\"),\n  'error_count' => (int)$db->fetchOne(\"SELECT COUNT(*) FROM xf_error_log\"),\n  'error_max_id' => (int)$db->fetchOne(\"SELECT COALESCE(MAX(error_id),0) FROM xf_error_log\")\n ],\n 'addon' => $db->fetchRow(\n  \"SELECT addon_id,version_id,version_string,active,is_processing,last_pending_action \" .\n  \"FROM xf_addon WHERE addon_id='Newfi/Portal'\"\n ),\n 'source' => [\n  'Pub/Controller/Portal.php' => srcMeta($portal . '/Pub/Controller/Portal.php'),\n  'Service/PortalData.php' => srcMeta($portal . '/Service/PortalData.php'),\n  '_data/templates.xml' => srcMeta($portal . '/_data/templates.xml')\n ],\n 'templates' => $templates\n];\necho \"PORTAL200_INSPECT_JSON_BEGIN\\n\";\necho json_encode(\n    $result,\n    JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES|JSON_INVALID_UTF8_SUBSTITUTE\n);\necho \"\\nPORTAL200_INSPECT_JSON_END\\n\";\n?>"},{"lineno":194,"segment":"r'''<?php\n$root = '/var/www/forums/newfi/public';\nrequire $root . '/src/XF.php';\n\\XF::start($root);\n$app = \\XF::setupApp('XF\\\\Pub\\\\App');\n$addOn = $app->addOnManager()->getById('Newfi/Portal');\nif (!$addOn || !$addOn->isInstalled()) {\n    fwrite(STDERR, \"ADDON_NOT_INSTALLED\\n\");\n    exit(30);\n}\n$addOn->preRebuild();\n$dataManager = $app->addOnDataManager();\n$imported = [];\nforeach ($dataManager->getDataTypeClasses() as $dataType) {\n    $handler = $dataManager->getDataTypeHandler($dataType);\n    $xml = $handler->openTypeFile($addOn->getDataDirectory());\n    if (!$xml || !$xml->count()) {\n        $handler->deleteAddOnData($addOn->addon_id);\n        continue;\n    }\n    $start = 0;\n    $guard = 0;\n    while (true) {\n        $last = $handler->importAddOnData($addOn->addon_id, $xml, $start, 0);\n        if (!$last) { break; }\n        if ($last <= $start || ++$guard > 100000) {\n            fwrite(STDERR, \"IMPORT_PROGRESS_INVALID\\n\");\n            exit(31);\n        }\n        $start = $last;\n    }\n    $handler->deleteOrphanedAddOnData($addOn->addon_id, $xml);\n    \\XF::triggerRunOnce();\n    $imported[] = $handler->getContainerTag();\n}\n$addOn->postDataImport();\n$repo = $app->repository(\\XF\\Repository\\StyleRepository::class);\n$repo->updateAllStylesLastModifiedDateLater();\n$addOn->postRebuild();\necho \"PORTAL200_IMPORT_JSON=\" . json_encode(\n    ['result'=>'PASS','containers'=>$imported],\n    JSON_UNESCAPED_SLASHES\n) . \"\\n\";\n?>'''","value":"<?php\n$root = '/var/www/forums/newfi/public';\nrequire $root . '/src/XF.php';\n\\XF::start($root);\n$app = \\XF::setupApp('XF\\\\Pub\\\\App');\n$addOn = $app->addOnManager()->getById('Newfi/Portal');\nif (!$addOn || !$addOn->isInstalled()) {\n    fwrite(STDERR, \"ADDON_NOT_INSTALLED\\n\");\n    exit(30);\n}\n$addOn->preRebuild();\n$dataManager = $app->addOnDataManager();\n$imported = [];\nforeach ($dataManager->getDataTypeClasses() as $dataType) {\n    $handler = $dataManager->getDataTypeHandler($dataType);\n    $xml = $handler->openTypeFile($addOn->getDataDirectory());\n    if (!$xml || !$xml->count()) {\n        $handler->deleteAddOnData($addOn->addon_id);\n        continue;\n    }\n    $start = 0;\n    $guard = 0;\n    while (true) {\n        $last = $handler->importAddOnData($addOn->addon_id, $xml, $start, 0);\n        if (!$last) { break; }\n        if ($last <= $start || ++$guard > 100000) {\n            fwrite(STDERR, \"IMPORT_PROGRESS_INVALID\\n\");\n            exit(31);\n        }\n        $start = $last;\n    }\n    $handler->deleteOrphanedAddOnData($addOn->addon_id, $xml);\n    \\XF::triggerRunOnce();\n    $imported[] = $handler->getContainerTag();\n}\n$addOn->postDataImport();\n$repo = $app->repository(\\XF\\Repository\\StyleRepository::class);\n$repo->updateAllStylesLastModifiedDateLater();\n$addOn->postRebuild();\necho \"PORTAL200_IMPORT_JSON=\" . json_encode(\n    ['result'=>'PASS','containers'=>$imported],\n    JSON_UNESCAPED_SLASHES\n) . \"\\n\";\n?>"},{"lineno":258,"segment":"r'''<?php\n$state = '__STATE__';\n$root = '/var/www/forums/newfi/public';\nrequire $root . '/src/XF.php';\n\\XF::start($root);\n$app = \\XF::setupApp('XF\\\\Pub\\\\App');\n$result = ['state'=>$state];\nif ($state === 'new') {\n    $service = new \\Newfi\\Portal\\Service\\PortalData($app);\n    $portal = $service->build();\n    $html = (string)$app->templater()->renderTemplate('public:newfi_portal', ['portal'=>$portal]);\n    $result['sections'] = [];\n    foreach (['forums','resources','media','events','dogs'] as $key) {\n        $section = $portal[$key] ?? null;\n        if (!is_array($section)) { fwrite(STDERR, \"SECTION_MISSING\\n\"); exit(60); }\n        $result['sections'][$key] = [\n            'available'=>(bool)($section['available'] ?? false),\n            'hasItems'=>(bool)($section['hasItems'] ?? false),\n            'status'=>(string)($section['status'] ?? '')\n        ];\n    }\n    $result['html_marker'] = strpos($html, 'data-newfi-portal200=\"1\"') !== false;\n    $result['locked_resources'] = strpos($html, 'data-portal-locked=\"resources\"') !== false;\n    $result['locked_media'] = strpos($html, 'data-portal-locked=\"media\"') !== false;\n    $result['html_bytes'] = strlen($html);\n    if (!$result['html_marker']) { fwrite(STDERR, \"HTML_MARKER_MISSING\\n\"); exit(61); }\n    if ($result['sections']['resources']['available'] || $result['sections']['media']['available']) {\n        fwrite(STDERR, \"GUEST_PERMISSION_CONTRACT_CHANGED\\n\"); exit(62);\n    }\n    if (!$result['locked_resources'] || !$result['locked_media']) {\n        fwrite(STDERR, \"LOCKED_COPY_MISSING\\n\"); exit(63);\n    }\n    if (!$result['sections']['forums']['available'] || !$result['sections']['events']['available'] || !$result['sections']['dogs']['available']) {\n        fwrite(STDERR, \"PUBLIC_SECTION_UNAVAILABLE\\n\"); exit(64);\n    }\n} else if ($state === 'old') {\n    $html = (string)$app->templater()->renderTemplate('public:newfi_portal', []);\n    $result['html_marker'] = strpos($html, 'data-newfi-portal200') !== false;\n    $result['o","value":"<?php\n$state = '__STATE__';\n$root = '/var/www/forums/newfi/public';\nrequire $root . '/src/XF.php';\n\\XF::start($root);\n$app = \\XF::setupApp('XF\\\\Pub\\\\App');\n$result = ['state'=>$state];\nif ($state === 'new') {\n    $service = new \\Newfi\\Portal\\Service\\PortalData($app);\n    $portal = $service->build();\n    $html = (string)$app->templater()->renderTemplate('public:newfi_portal', ['portal'=>$portal]);\n    $result['sections'] = [];\n    foreach (['forums','resources','media','events','dogs'] as $key) {\n        $section = $portal[$key] ?? null;\n        if (!is_array($section)) { fwrite(STDERR, \"SECTION_MISSING\\n\"); exit(60); }\n        $result['sections'][$key] = [\n            'available'=>(bool)($section['available'] ?? false),\n            'hasItems'=>(bool)($section['hasItems'] ?? false),\n            'status'=>(string)($section['status'] ?? '')\n        ];\n    }\n    $result['html_marker'] = strpos($html, 'data-newfi-portal200=\"1\"') !== false;\n    $result['locked_resources'] = strpos($html, 'data-portal-locked=\"resources\"') !== false;\n    $result['locked_media'] = strpos($html, 'data-portal-locked=\"media\"') !== false;\n    $result['html_bytes'] = strlen($html);\n    if (!$result['html_marker']) { fwrite(STDERR, \"HTML_MARKER_MISSING\\n\"); exit(61); }\n    if ($result['sections']['resources']['available'] || $result['sections']['media']['available']) {\n        fwrite(STDERR, \"GUEST_PERMISSION_CONTRACT_CHANGED\\n\"); exit(62);\n    }\n    if (!$result['locked_resources'] || !$result['locked_media']) {\n        fwrite(STDERR, \"LOCKED_COPY_MISSING\\n\"); exit(63);\n    }\n    if (!$result['sections']['forums']['available'] || !$result['sections']['events']['available'] || !$result['sections']['dogs']['available']) {\n        fwrite(STDERR, \"PUBLIC_SECTION_UNAVAILABLE\\n\"); exit(64);\n    }\n} else if ($state === 'old') {\n    $html = (string)$app->templater()->renderTemplate('public:newfi_portal', []);\n    $result['html_marker'] = strpos($html, 'data-newfi-portal200') !== false;\n    $result['old_copy'] = strpos($html, 'Закрытое пространство для общения, знаний и материалов сообщества.') !== false;\n    $result['html_bytes'] = strlen($html);\n    if ($result['html_marker'] || !$result['old_copy']) {\n        fwrite(STDERR, \"OLD_RENDER_CONTRACT\\n\"); exit(65);\n    }\n} else {\n    exit(66);\n}\necho \"PORTAL200_RUNTIME_JSON=\" . json_encode(\n    $result,\n    JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES|JSON_INVALID_UTF8_SUBSTITUTE\n) . \"\\n\";\n?>"},{"lineno":311,"segment":"r'''import html,json,re,subprocess,sys,urllib.parse\nmode=sys.argv[1]\nstate=sys.argv[2]\nif mode=='staging':\n    host='newfi-staging.gram1.ru'; scheme='http'; port='80'; expected_status=503\nelif mode=='prod':\n    host='newfi.ru'; scheme='https'; port='443'; expected_status=200\nelse:\n    raise SystemExit(40)\ndef fetch(url):\n    cmd=[\n        '/usr/bin/curl','-sS','-k','--max-time','30','--connect-timeout','10'\n    ]\n    if mode=='staging':\n        cmd += ['--resolve',f'{host}:{port}:127.0.0.1']\n    cmd += [\n        '-o','-','-w','\\n__PORTAL_STATUS__%{http_code}',url\n    ]\n    p=subprocess.run(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE,check=False)\n    if p.returncode!=0: raise SystemExit(41)\n    text=p.stdout.decode('utf-8','replace')\n    marker='\\n__PORTAL_STATUS__'\n    if marker not in text: raise SystemExit(42)\n    body,status=text.rsplit(marker,1)\n    return int(status.strip()),body\nroot=f'{scheme}://{host}/'\nstatus,body=fetch(root)\nhrefs=[]\nfor raw in re.findall(r\"href=[\\\"']([^\\\"']*css\\.php[^\\\"']*)[\\\"']\",body,re.I):\n    raw=html.unescape(raw)\n    hrefs.append(urllib.parse.urljoin(root,raw))\nnormal_css=[]\nnormal_status=[]\nfor u in hrefs:\n    s,b=fetch(u)\n    normal_status.append(s)\n    normal_css.append(b)\nnormal_css_text='\\n'.join(normal_css)\nforced_css_marker=None\nforced_css_status=None\nif mode=='staging' and state=='new' and hrefs:\n    u=urllib.parse.urlsplit(hrefs[0])\n    pairs=urllib.parse.parse_qsl(u.query,keep_blank_values=True)\n    out=[]\n    found=False\n    for k,v in pairs:\n        if k=='css':\n            found=True\n            parts=[x for x in v.split(',') if x]\n            if 'public:newfi_portal.less' not in parts:\n                parts.append('public:newfi_portal.less')\n            v=','.join(parts)\n        out.append((k,v))\n    if found:\n        forced=urllib.parse.urlunsplit((u.scheme,u.netloc,u.path,urllib.parse.urlencode(out),u.fragment))\n        forced_css_status,forced_body=fetch(forced)\n        forced_css_marker='--newfi-portal200' in ","value":"import html,json,re,subprocess,sys,urllib.parse\nmode=sys.argv[1]\nstate=sys.argv[2]\nif mode=='staging':\n    host='newfi-staging.gram1.ru'; scheme='http'; port='80'; expected_status=503\nelif mode=='prod':\n    host='newfi.ru'; scheme='https'; port='443'; expected_status=200\nelse:\n    raise SystemExit(40)\ndef fetch(url):\n    cmd=[\n        '/usr/bin/curl','-sS','-k','--max-time','30','--connect-timeout','10'\n    ]\n    if mode=='staging':\n        cmd += ['--resolve',f'{host}:{port}:127.0.0.1']\n    cmd += [\n        '-o','-','-w','\\n__PORTAL_STATUS__%{http_code}',url\n    ]\n    p=subprocess.run(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE,check=False)\n    if p.returncode!=0: raise SystemExit(41)\n    text=p.stdout.decode('utf-8','replace')\n    marker='\\n__PORTAL_STATUS__'\n    if marker not in text: raise SystemExit(42)\n    body,status=text.rsplit(marker,1)\n    return int(status.strip()),body\nroot=f'{scheme}://{host}/'\nstatus,body=fetch(root)\nhrefs=[]\nfor raw in re.findall(r\"href=[\\\"']([^\\\"']*css\\.php[^\\\"']*)[\\\"']\",body,re.I):\n    raw=html.unescape(raw)\n    hrefs.append(urllib.parse.urljoin(root,raw))\nnormal_css=[]\nnormal_status=[]\nfor u in hrefs:\n    s,b=fetch(u)\n    normal_status.append(s)\n    normal_css.append(b)\nnormal_css_text='\\n'.join(normal_css)\nforced_css_marker=None\nforced_css_status=None\nif mode=='staging' and state=='new' and hrefs:\n    u=urllib.parse.urlsplit(hrefs[0])\n    pairs=urllib.parse.parse_qsl(u.query,keep_blank_values=True)\n    out=[]\n    found=False\n    for k,v in pairs:\n        if k=='css':\n            found=True\n            parts=[x for x in v.split(',') if x]\n            if 'public:newfi_portal.less' not in parts:\n                parts.append('public:newfi_portal.less')\n            v=','.join(parts)\n        out.append((k,v))\n    if found:\n        forced=urllib.parse.urlunsplit((u.scheme,u.netloc,u.path,urllib.parse.urlencode(out),u.fragment))\n        forced_css_status,forced_body=fetch(forced)\n        forced_css_marker='--newfi-portal200' in forced_body\nresult={\n 'status':status,\n 'bytes':len(body.encode()),\n 'html_marker':'data-newfi-portal200=\"1\"' in body,\n 'css_marker':'--newfi-portal200' in normal_css_text,\n 'old_copy':'Закрытое пространство для общения, знаний и материалов сообщества.' in body,\n 'css_count':len(hrefs),\n 'css_status':normal_status,\n 'forced_css_status':forced_css_status,\n 'forced_css_marker':forced_css_marker,\n}\nprint('PORTAL200_HTTP_JSON='+json.dumps(result,ensure_ascii=False,sort_keys=True,separators=(',',':')))\nif status!=expected_status: raise SystemExit(45)\nif mode=='prod':\n    if not hrefs or any(x!=200 for x in normal_status): raise SystemExit(46)\nelif mode=='staging':\n    if any(x!=200 for x in normal_status): raise SystemExit(46)\nelse:\n    raise SystemExit(50)\nif mode=='prod' and state=='new':\n    if not result['html_marker'] or not result['css_marker']: raise SystemExit(47)\nelif mode=='prod' and state=='old':\n    if result['html_marker'] or result['css_marker'] or not result['old_copy']: raise SystemExit(48)\nelif mode=='staging' and state=='new':\n    if hrefs and (forced_css_status!=200 or not forced_css_marker): raise SystemExit(49)\nelif mode=='staging' and state=='old':\n    pass\nelse:\n    raise SystemExit(50)\n"},{"lineno":14,"segment":"\"/root/newfi-portal200-candidate/src/addons/Newfi/Portal\"","value":"/root/newfi-portal200-candidate/src/addons/Newfi/Portal"},{"lineno":20,"segment":"\"_data/templates.xml\"","value":"_data/templates.xml"},{"lineno":24,"segment":"\"_data/templates.xml\"","value":"_data/templates.xml"},{"lineno":27,"segment":"/root/newfi-portal200-backup-","value":"/root/newfi-portal200-backup-"},{"lineno":401,"segment":"\"PORTAL200_INSPECT_JSON_BEGIN\\n\"","value":"PORTAL200_INSPECT_JSON_BEGIN\n"},{"lineno":402,"segment":"\"\\nPORTAL200_INSPECT_JSON_END\"","value":"\nPORTAL200_INSPECT_JSON_END"},{"lineno":734,"segment":"\"PORTAL200_REMAINING_FULL_PRODUCT_COMMANDS_BEFORE\"","value":"PORTAL200_REMAINING_FULL_PRODUCT_COMMANDS_BEFORE"},{"lineno":736,"segment":"\"PORTAL200_FLOW\"","value":"PORTAL200_FLOW"},{"lineno":737,"segment":"\"candidate->staging-apply->rollback-proof->staging-reapply->git-push->production\"","value":"candidate->staging-apply->rollback-proof->staging-reapply->git-push->production"},{"lineno":497,"segment":"\nP=/var/www/forums/newfi/public/src/addons/Newfi/Portal\ntest ! -e \"$B\"\ninstall -d -m 700 \"$B/Pub/Controller\" \"$B/_data\"\ncp -p -- \"$P/Pub/Controller/Portal.php\" \"$B/Pub/Controller/Portal.php\"\ncp -p -- \"$P/_data/templates.xml\" \"$B/_data/templates.xml\"\ntest ! -e \"$P/Service/PortalData.php\"\nprintf 'PORTAL200_BACKUP=PASS\\n'\n","value":"\nP=/var/www/forums/newfi/public/src/addons/Newfi/Portal\ntest ! -e \"$B\"\ninstall -d -m 700 \"$B/Pub/Controller\" \"$B/_data\"\ncp -p -- \"$P/Pub/Controller/Portal.php\" \"$B/Pub/Controller/Portal.php\"\ncp -p -- \"$P/_data/templates.xml\" \"$B/_data/templates.xml\"\ntest ! -e \"$P/Service/PortalData.php\"\nprintf 'PORTAL200_BACKUP=PASS\n'\n"},{"lineno":507,"segment":"\"PORTAL200_BACKUP=PASS\"","value":"PORTAL200_BACKUP=PASS"},{"lineno":523,"segment":"\"; printf 'PORTAL200_WRITE=PASS\\\\n'\"","value":"; printf 'PORTAL200_WRITE=PASS\\n'"},{"lineno":531,"segment":"\"PORTAL200_WRITE=PASS\"","value":"PORTAL200_WRITE=PASS"},{"lineno":539,"segment":"\"_data/templates.xml\"","value":"_data/templates.xml"},{"lineno":546,"segment":"\nP=/var/www/forums/newfi/public/src/addons/Newfi/Portal\ntest -f \"$B/Pub/Controller/Portal.php\"\ntest -f \"$B/_data/templates.xml\"\ncp -p -- \"$B/Pub/Controller/Portal.php\" \"$P/Pub/Controller/Portal.php\"\ncp -p -- \"$B/_data/templates.xml\" \"$P/_data/templates.xml\"\nrm -f -- \"$P/Service/PortalData.php\"\nrmdir -- \"$P/Service\" 2>/dev/null || true\nprintf 'PORTAL200_SOURCE_RESTORE=PASS\\n'\n","value":"\nP=/var/www/forums/newfi/public/src/addons/Newfi/Portal\ntest -f \"$B/Pub/Controller/Portal.php\"\ntest -f \"$B/_data/templates.xml\"\ncp -p -- \"$B/Pub/Controller/Portal.php\" \"$P/Pub/Controller/Portal.php\"\ncp -p -- \"$B/_data/templates.xml\" \"$P/_data/templates.xml\"\nrm -f -- \"$P/Service/PortalData.php\"\nrmdir -- \"$P/Service\" 2>/dev/null || true\nprintf 'PORTAL200_SOURCE_RESTORE=PASS\n'\n"},{"lineno":557,"segment":"\"PORTAL200_SOURCE_RESTORE=PASS\"","value":"PORTAL200_SOURCE_RESTORE=PASS"},{"lineno":568,"segment":"\"PORTAL200_IMPORT_JSON=\"","value":"PORTAL200_IMPORT_JSON="},{"lineno":585,"segment":"\"PORTAL200_RUNTIME\"","value":"PORTAL200_RUNTIME"},{"lineno":647,"segment":"\"PORTAL200_HTTP\"","value":"PORTAL200_HTTP"},{"lineno":715,"segment":"\"PORTAL200_CLUSTER\"","value":"PORTAL200_CLUSTER"},{"lineno":758,"segment":"\"<portal200-http-verify>\"","value":"<portal200-http-verify>"},{"lineno":781,"segment":"\"PORTAL200_CANDIDATE_IDENTITY\"","value":"PORTAL200_CANDIDATE_IDENTITY"},{"lineno":783,"segment":"\"PORTAL200_ORCHESTRATION_FIXES\"","value":"PORTAL200_ORCHESTRATION_FIXES"},{"lineno":800,"segment":"\"PORTAL200_GIT_PRESTATE\"","value":"PORTAL200_GIT_PRESTATE"},{"lineno":811,"segment":"\"PORTAL200_RUNTIME_HELPER_LINT\"","value":"PORTAL200_RUNTIME_HELPER_LINT"},{"lineno":825,"segment":"\"PORTAL200_ERROR_BASELINE\"","value":"PORTAL200_ERROR_BASELINE"},{"lineno":831,"segment":"\"PORTAL200_PREFLIGHT\"","value":"PORTAL200_PREFLIGHT"},{"lineno":839,"segment":"\"PORTAL200_STAGING_FIRST_APPLY\"","value":"PORTAL200_STAGING_FIRST_APPLY"},{"lineno":845,"segment":"\"PORTAL200_STAGING_ROLLBACK_PROOF\"","value":"PORTAL200_STAGING_ROLLBACK_PROOF"},{"lineno":852,"segment":"\"PORTAL200_STAGING_FINAL\"","value":"PORTAL200_STAGING_FINAL"},{"lineno":885,"segment":"\"PORTAL200_GIT_PROMOTION\"","value":"PORTAL200_GIT_PROMOTION"},{"lineno":905,"segment":"\"PORTAL200_PRODUCTION\"","value":"PORTAL200_PRODUCTION"},{"lineno":922,"segment":"\"PORTAL200_FINAL_EVIDENCE\"","value":"PORTAL200_FINAL_EVIDENCE"},{"lineno":931,"segment":"\"PORTAL200_PORTAL\"","value":"PORTAL200_PORTAL"},{"lineno":932,"segment":"\"PORTAL200_STAGING\"","value":"PORTAL200_STAGING"},{"lineno":933,"segment":"\"PORTAL200_PRODUCTION\"","value":"PORTAL200_PRODUCTION"},{"lineno":934,"segment":"\"PORTAL200_REMAINING_FULL_PRODUCT_COMMANDS_AFTER_PASS\"","value":"PORTAL200_REMAINING_FULL_PRODUCT_COMMANDS_AFTER_PASS"},{"lineno":460,"segment":"1000010","value":1000010},{"lineno":484,"segment":"\"PORTAL200_NEW_XF_ERRORS\"","value":"PORTAL200_NEW_XF_ERRORS"},{"lineno":763,"segment":"\"_data/templates.xml\"","value":"_data/templates.xml"},{"lineno":766,"segment":"\"CANDIDATE_TEMPLATE_SET\"","value":"CANDIDATE_TEMPLATE_SET"},{"lineno":780,"segment":"\"CANDIDATE_MUTATION_SCAN\"","value":"CANDIDATE_MUTATION_SCAN"},{"lineno":941,"segment":"\"PORTAL200_FAILURE\"","value":"PORTAL200_FAILURE"},{"lineno":1004,"segment":"\"PORTAL200_ROADMAP_REMAINS\"","value":"PORTAL200_ROADMAP_REMAINS"},{"lineno":1009,"segment":"\"PORTAL200_FAILURE\"","value":"PORTAL200_FAILURE"},{"lineno":1016,"segment":"\"PORTAL200_ROADMAP_REMAINS\"","value":"PORTAL200_ROADMAP_REMAINS"},{"lineno":461,"segment":"\"version_string\"","value":"version_string"},{"lineno":618,"segment":"\"PORTAL200_HTTP_EXEC\"","value":"PORTAL200_HTTP_EXEC"},{"lineno":762,"segment":"\"CANDIDATE_IDENTITY\"","value":"CANDIDATE_IDENTITY"},{"lineno":460,"segment":"\"version_id\"","value":"version_id"},{"lineno":576,"segment":"\"PORTAL200_RUNTIME_JSON=\"","value":"PORTAL200_RUNTIME_JSON="},{"lineno":613,"segment":"\"PORTAL200_HTTP\"","value":"PORTAL200_HTTP"},{"lineno":638,"segment":"\"PORTAL200_HTTP_JSON=\"","value":"PORTAL200_HTTP_JSON="},{"lineno":991,"segment":"\"PORTAL200_FAILURE_GIT_HISTORY\"","value":"PORTAL200_FAILURE_GIT_HISTORY"},{"lineno":606,"segment":"\"PORTAL200_HTTP_JSON=\"","value":"PORTAL200_HTTP_JSON="},{"lineno":952,"segment":"\"PORTAL200_FAILURE_PROD_ROLLBACK\"","value":"PORTAL200_FAILURE_PROD_ROLLBACK"},{"lineno":966,"segment":"\"PORTAL200_FAILURE_STAGING_ROLLBACK\"","value":"PORTAL200_FAILURE_STAGING_ROLLBACK"},{"lineno":985,"segment":"\"PORTAL200_FAILURE_GIT_WORKTREE_RESTORE\"","value":"PORTAL200_FAILURE_GIT_WORKTREE_RESTORE"},{"lineno":955,"segment":"\"PORTAL200_FAILURE_PROD_ROLLBACK\"","value":"PORTAL200_FAILURE_PROD_ROLLBACK"},{"lineno":969,"segment":"\"PORTAL200_FAILURE_STAGING_ROLLBACK\"","value":"PORTAL200_FAILURE_STAGING_ROLLBACK"},{"lineno":987,"segment":"\"PORTAL200_FAILURE_GIT_WORKTREE_RESTORE\"","value":"PORTAL200_FAILURE_GIT_WORKTREE_RESTORE"},{"lineno":519,"segment":"\"/.portal200.XXXXXX\"","value":"/.portal200.XXXXXX"}]
RELEVANT_CALLS=[{"args":["/root/newfi-portal200-candidate/src/addons/Newfi/Portal"],"kwargs":{},"lineno":14,"name":"pathlib.Path","source":"pathlib.Path(\"/root/newfi-portal200-candidate/src/addons/Newfi/Portal\")"},{"args":["src/addons/Newfi/Portal"],"kwargs":{},"lineno":15,"name":"pathlib.Path","source":"pathlib.Path(\"src/addons/Newfi/Portal\")"},{"args":["PORTAL200_FLOW","candidate->staging-apply->rollback-proof->staging-reapply->git-push->production"],"kwargs":{},"lineno":735,"name":"emit","source":"emit(\n    \"PORTAL200_FLOW\",\n    \"candidate->staging-apply->rollback-proof->staging-reapply->git-push->production\",\n)"},{"args":["[\"/usr/bin/git\", \"-C\", str(WT), *args]"],"kwargs":{"ok":"ok","timeout":"timeout"},"lineno":77,"name":"run","source":"run([\"/usr/bin/git\", \"-C\", str(WT), *args], timeout=timeout, ok=ok)"},{"args":["cmd"],"kwargs":{"input_bytes":"input_bytes","timeout":"timeout + 30"},"lineno":135,"name":"run","source":"run(cmd, input_bytes=input_bytes, timeout=timeout + 30)"},{"args":["__STATE__","state"],"kwargs":{},"lineno":573,"name":"RUNTIME_VERIFY_PHP_TEMPLATE.replace","source":"RUNTIME_VERIFY_PHP_TEMPLATE.replace(\"__STATE__\", state)"},{"args":["target","new"],"kwargs":{},"lineno":659,"name":"runtime_verify","source":"runtime_verify(target, \"new\")"},{"args":[["/usr/sbin/qm","status","210"]],"kwargs":{"timeout":30},"lineno":693,"name":"run","source":"run([\"/usr/sbin/qm\", \"status\", \"210\"], timeout=30)"},{"args":[["/usr/bin/ssh","-T","-o","BatchMode=yes","-o","StrictHostKeyChecking=yes","-o","UpdateHostKeys=no","-o","ConnectTimeout=10","root@pve03","/usr/sbin/qm status 211"]],"kwargs":{"timeout":40},"lineno":696,"name":"run","source":"run(\n        [\n            \"/usr/bin/ssh\",\n            \"-T\",\n            \"-o\",\n            \"BatchMode=yes\",\n            \"-o\",\n            \"StrictHostKeyChecking=yes\",\n            \"-o\",\n            \"UpdateHostKeys=no\",\n            \"-o\",\n            \"ConnectTimeout=10\",\n            \"root@pve03\",\n            \"/usr/sbin/qm status 211\",\n        ],\n        timeout=40,\n    )"},{"args":["CAND_ROOT / \"_data/templates.xml\""],"kwargs":{},"lineno":763,"name":"ET.parse","source":"ET.parse(CAND_ROOT / \"_data/templates.xml\")"},{"args":["utf-8"],"kwargs":{},"lineno":767,"name":"read_text","source":"(CAND_ROOT / \"Service/PortalData.php\").read_text(\"utf-8\")"},{"args":["PORTAL200_CANDIDATE_IDENTITY","PASS"],"kwargs":{},"lineno":781,"name":"emit","source":"emit(\"PORTAL200_CANDIDATE_IDENTITY\", \"PASS\")"},{"args":["prod","old"],"kwargs":{},"lineno":827,"name":"runtime_verify","source":"runtime_verify(\"prod\", \"old\")"},{"args":["staging"],"kwargs":{},"lineno":836,"name":"install_candidate","source":"install_candidate(\"staging\")"},{"args":["staging"],"kwargs":{},"lineno":848,"name":"install_candidate","source":"install_candidate(\"staging\")"},{"args":["prod"],"kwargs":{},"lineno":902,"name":"install_candidate","source":"install_candidate(\"prod\")"},{"args":["cmd"],"kwargs":{"check":false,"input":"input_bytes","stderr":"subprocess.PIPE","stdout":"subprocess.PIPE","timeout":"timeout"},"lineno":61,"name":"subprocess.run","source":"subprocess.run(\n            cmd,\n            input=input_bytes,\n            stdout=subprocess.PIPE,\n            stderr=subprocess.PIPE,\n            timeout=timeout,\n            check=False,\n        )"},{"args":["target","rel","(CAND_ROOT / rel).read_bytes()"],"kwargs":{},"lineno":541,"name":"guest_write","source":"guest_write(target, rel, (CAND_ROOT / rel).read_bytes())"},{"args":["target","old"],"kwargs":{},"lineno":674,"name":"runtime_verify","source":"runtime_verify(target, \"old\")"},{"args":["CANDIDATE_TEMPLATE_SET"],"kwargs":{},"lineno":766,"name":"fail","source":"fail(\"CANDIDATE_TEMPLATE_SET\")"},{"args":["CANDIDATE_MUTATION_SCAN"],"kwargs":{},"lineno":780,"name":"fail","source":"fail(\"CANDIDATE_MUTATION_SCAN\")"},{"args":["__STATE__","new"],"kwargs":{},"lineno":807,"name":"RUNTIME_VERIFY_PHP_TEMPLATE.replace","source":"RUNTIME_VERIFY_PHP_TEMPLATE.replace(\"__STATE__\", \"new\")"},{"args":["__STATE__","new"],"kwargs":{},"lineno":810,"name":"RUNTIME_VERIFY_PHP_TEMPLATE.replace","source":"RUNTIME_VERIFY_PHP_TEMPLATE.replace(\"__STATE__\", \"new\")"},{"args":["CAND_ROOT / rel","dest"],"kwargs":{},"lineno":858,"name":"shutil.copyfile","source":"shutil.copyfile(CAND_ROOT / rel, dest)"},{"args":["addon.get(\"version_id\", 0)"],"kwargs":{},"lineno":460,"name":"int","source":"int(addon.get(\"version_id\", 0))"},{"args":["version_string"],"kwargs":{},"lineno":461,"name":"addon.get","source":"addon.get(\"version_string\")"},{"args":[],"kwargs":{},"lineno":541,"name":"read_bytes","source":"(CAND_ROOT / rel).read_bytes()"},{"args":["argv"],"kwargs":{"check":false,"input":"HTTP_VERIFY_PY.encode()","stderr":"subprocess.PIPE","stdout":"subprocess.PIPE","timeout":180},"lineno":593,"name":"subprocess.run","source":"subprocess.run(\n                argv,\n                input=HTTP_VERIFY_PY.encode(),\n                stdout=subprocess.PIPE,\n                stderr=subprocess.PIPE,\n                timeout=180,\n                check=False,\n            )"},{"args":["CANDIDATE_IDENTITY"],"kwargs":{},"lineno":762,"name":"fail","source":"fail(\"CANDIDATE_IDENTITY\")"},{"args":["version_id",0],"kwargs":{},"lineno":460,"name":"addon.get","source":"addon.get(\"version_id\", 0)"},{"args":["old"],"kwargs":{},"lineno":979,"name":"dest.write_bytes","source":"dest.write_bytes(old)"}]
RELEVANT_SOURCE_CONTEXTS=[{"line":10,"text":"import subprocess\nimport sys\nimport xml.etree.ElementTree as ET\n\nCID = \"NEWFI-260907-PORTAL200-APPLY3\"\nBRANCH = \"implementation/newfi-closed-mvp-20260906\"\nOLD_HEAD = \"06bab5acd124e768f70305b3d7eca2f595871890\"\nWT = pathlib.Path(\"/root/newfi-portal/.worktrees/implementation-newfi-closed-mvp-20260906\")\nCAND_ROOT = pathlib.Path(\"/root/newfi-portal200-candidate/src/addons/Newfi/Portal\")\nPORTAL_REL = pathlib.Path(\"src/addons/Newfi/Portal\")\nGIT_PORTAL = WT / PORTAL_REL\nEXPECTED_NEW = {\n    \"Pub/Controller/Portal.php\": (363, \"619d489f7cc3b461479c702fa8825097ae22d3c6cfeb911e9f14ea1de9c36d6d\"),"},{"line":14,"text":"CID = \"NEWFI-260907-PORTAL200-APPLY3\"\nBRANCH = \"implementation/newfi-closed-mvp-20260906\"\nOLD_HEAD = \"06bab5acd124e768f70305b3d7eca2f595871890\"\nWT = pathlib.Path(\"/root/newfi-portal/.worktrees/implementation-newfi-closed-mvp-20260906\")\nCAND_ROOT = pathlib.Path(\"/root/newfi-portal200-candidate/src/addons/Newfi/Portal\")\nPORTAL_REL = pathlib.Path(\"src/addons/Newfi/Portal\")\nGIT_PORTAL = WT / PORTAL_REL\nEXPECTED_NEW = {\n    \"Pub/Controller/Portal.php\": (363, \"619d489f7cc3b461479c702fa8825097ae22d3c6cfeb911e9f14ea1de9c36d6d\"),\n    \"Service/PortalData.php\": (5213, \"4da03f26294ab6d163a1a746dee4bb6a82d7d4f39e66b24246bc0eb4c455dd1d\"),\n    \"_data/templates.xml\": (10977, \"1650c3d868d4bc47703f1b75f1f179cd25309b2d3b20a2b848e81a4423c00861\"),\n}\nEXPECTED_OLD = {"},{"line":20,"text":"GIT_PORTAL = WT / PORTAL_REL\nEXPECTED_NEW = {\n    \"Pub/Controller/Portal.php\": (363, \"619d489f7cc3b461479c702fa8825097ae22d3c6cfeb911e9f14ea1de9c36d6d\"),\n    \"Service/PortalData.php\": (5213, \"4da03f26294ab6d163a1a746dee4bb6a82d7d4f39e66b24246bc0eb4c455dd1d\"),\n    \"_data/templates.xml\": (10977, \"1650c3d868d4bc47703f1b75f1f179cd25309b2d3b20a2b848e81a4423c00861\"),\n}\nEXPECTED_OLD = {\n    \"Pub/Controller/Portal.php\": (247, \"53909e753ea5a011550fd280ad1c342a74ca9949be092dfbd95244243cae92f3\"),\n    \"_data/templates.xml\": (1461, \"c29c04f8b937284e821c87957653e87c2f6ac014fac30648a0f6b18e84392adf\"),\n}\nEXPECTED_GIT_PATHS = [str(PORTAL_REL / p) for p in sorted(EXPECTED_NEW)]\nBACKUP = f\"/root/newfi-portal200-backup-{CID}\"\n"},{"line":24,"text":"    \"_data/templates.xml\": (10977, \"1650c3d868d4bc47703f1b75f1f179cd25309b2d3b20a2b848e81a4423c00861\"),\n}\nEXPECTED_OLD = {\n    \"Pub/Controller/Portal.php\": (247, \"53909e753ea5a011550fd280ad1c342a74ca9949be092dfbd95244243cae92f3\"),\n    \"_data/templates.xml\": (1461, \"c29c04f8b937284e821c87957653e87c2f6ac014fac30648a0f6b18e84392adf\"),\n}\nEXPECTED_GIT_PATHS = [str(PORTAL_REL / p) for p in sorted(EXPECTED_NEW)]\nBACKUP = f\"/root/newfi-portal200-backup-{CID}\"\n\n\nclass PortalError(RuntimeError):\n    pass\n"},{"line":27,"text":"    \"Pub/Controller/Portal.php\": (247, \"53909e753ea5a011550fd280ad1c342a74ca9949be092dfbd95244243cae92f3\"),\n    \"_data/templates.xml\": (1461, \"c29c04f8b937284e821c87957653e87c2f6ac014fac30648a0f6b18e84392adf\"),\n}\nEXPECTED_GIT_PATHS = [str(PORTAL_REL / p) for p in sorted(EXPECTED_NEW)]\nBACKUP = f\"/root/newfi-portal200-backup-{CID}\"\n\n\nclass PortalError(RuntimeError):\n    pass\n\n\ndef emit(key, value=None):\n    if value is None:"},{"line":160,"text":"}\n$portal = $root . '/src/addons/Newfi/Portal';\n$templates = $db->fetchAll(\n    \"SELECT title, style_id, LENGTH(template) AS bytes, SHA2(template,256) AS sha256, \" .\n    \"LOCATE('data-newfi-portal200', template) > 0 AS html_marker, \" .\n    \"LOCATE('--newfi-portal200', template) > 0 AS css_marker \" .\n    \"FROM xf_template WHERE addon_id='Newfi/Portal' ORDER BY style_id,title\"\n);\n$result = [\n 'hostname' => gethostname(),\n 'application' => [\n  'boardActive' => (string)$db->fetchOne(\"SELECT option_value FROM xf_option WHERE option_id='boardActive'\"),\n  'boardUrl' => (string)$db->fetchOne(\"SELECT option_value FROM xf_option WHERE option_id='boardUrl'\"),"},{"line":161,"text":"$portal = $root . '/src/addons/Newfi/Portal';\n$templates = $db->fetchAll(\n    \"SELECT title, style_id, LENGTH(template) AS bytes, SHA2(template,256) AS sha256, \" .\n    \"LOCATE('data-newfi-portal200', template) > 0 AS html_marker, \" .\n    \"LOCATE('--newfi-portal200', template) > 0 AS css_marker \" .\n    \"FROM xf_template WHERE addon_id='Newfi/Portal' ORDER BY style_id,title\"\n);\n$result = [\n 'hostname' => gethostname(),\n 'application' => [\n  'boardActive' => (string)$db->fetchOne(\"SELECT option_value FROM xf_option WHERE option_id='boardActive'\"),\n  'boardUrl' => (string)$db->fetchOne(\"SELECT option_value FROM xf_option WHERE option_id='boardUrl'\"),\n  'indexRoute' => (string)$db->fetchOne(\"SELECT option_value FROM xf_option WHERE option_id='indexRoute'\"),"},{"line":175,"text":"  'error_count' => (int)$db->fetchOne(\"SELECT COUNT(*) FROM xf_error_log\"),\n  'error_max_id' => (int)$db->fetchOne(\"SELECT COALESCE(MAX(error_id),0) FROM xf_error_log\")\n ],\n 'addon' => $db->fetchRow(\n  \"SELECT addon_id,version_id,version_string,active,is_processing,last_pending_action \" .\n  \"FROM xf_addon WHERE addon_id='Newfi/Portal'\"\n ),\n 'source' => [\n  'Pub/Controller/Portal.php' => srcMeta($portal . '/Pub/Controller/Portal.php'),\n  'Service/PortalData.php' => srcMeta($portal . '/Service/PortalData.php'),\n  '_data/templates.xml' => srcMeta($portal . '/_data/templates.xml')\n ],\n 'templates' => $templates"},{"line":181,"text":" ),\n 'source' => [\n  'Pub/Controller/Portal.php' => srcMeta($portal . '/Pub/Controller/Portal.php'),\n  'Service/PortalData.php' => srcMeta($portal . '/Service/PortalData.php'),\n  '_data/templates.xml' => srcMeta($portal . '/_data/templates.xml')\n ],\n 'templates' => $templates\n];\necho \"PORTAL200_INSPECT_JSON_BEGIN\\n\";\necho json_encode(\n    $result,\n    JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES|JSON_INVALID_UTF8_SUBSTITUTE\n);"},{"line":185,"text":"  '_data/templates.xml' => srcMeta($portal . '/_data/templates.xml')\n ],\n 'templates' => $templates\n];\necho \"PORTAL200_INSPECT_JSON_BEGIN\\n\";\necho json_encode(\n    $result,\n    JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES|JSON_INVALID_UTF8_SUBSTITUTE\n);\necho \"\\nPORTAL200_INSPECT_JSON_END\\n\";\n?>'''\n\n"},{"line":190,"text":"echo json_encode(\n    $result,\n    JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES|JSON_INVALID_UTF8_SUBSTITUTE\n);\necho \"\\nPORTAL200_INSPECT_JSON_END\\n\";\n?>'''\n\n\nIMPORT_PHP = r'''<?php\n$root = '/var/www/forums/newfi/public';\nrequire $root . '/src/XF.php';\n\\XF::start($root);\n$app = \\XF::setupApp('XF\\\\Pub\\\\App');"},{"line":233,"text":"$addOn->postDataImport();\n$repo = $app->repository(\\XF\\Repository\\StyleRepository::class);\n$repo->updateAllStylesLastModifiedDateLater();\n$addOn->postRebuild();\necho \"PORTAL200_IMPORT_JSON=\" . json_encode(\n    ['result'=>'PASS','containers'=>$imported],\n    JSON_UNESCAPED_SLASHES\n) . \"\\n\";\n?>'''\n\n\nERRORS_PHP_TEMPLATE = r'''<?php\n$root='/var/www/forums/newfi/public';"},{"line":279,"text":"            'hasItems'=>(bool)($section['hasItems'] ?? false),\n            'status'=>(string)($section['status'] ?? '')\n        ];\n    }\n    $result['html_marker'] = strpos($html, 'data-newfi-portal200=\"1\"') !== false;\n    $result['locked_resources'] = strpos($html, 'data-portal-locked=\"resources\"') !== false;\n    $result['locked_media'] = strpos($html, 'data-portal-locked=\"media\"') !== false;\n    $result['html_bytes'] = strlen($html);\n    if (!$result['html_marker']) { fwrite(STDERR, \"HTML_MARKER_MISSING\\n\"); exit(61); }\n    if ($result['sections']['resources']['available'] || $result['sections']['media']['available']) {\n        fwrite(STDERR, \"GUEST_PERMISSION_CONTRACT_CHANGED\\n\"); exit(62);\n    }\n    if (!$result['locked_resources'] || !$result['locked_media']) {"},{"line":288,"text":"    if ($result['sections']['resources']['available'] || $result['sections']['media']['available']) {\n        fwrite(STDERR, \"GUEST_PERMISSION_CONTRACT_CHANGED\\n\"); exit(62);\n    }\n    if (!$result['locked_resources'] || !$result['locked_media']) {\n        fwrite(STDERR, \"LOCKED_COPY_MISSING\\n\"); exit(63);\n    }\n    if (!$result['sections']['forums']['available'] || !$result['sections']['events']['available'] || !$result['sections']['dogs']['available']) {\n        fwrite(STDERR, \"PUBLIC_SECTION_UNAVAILABLE\\n\"); exit(64);\n    }\n} else if ($state === 'old') {\n    $html = (string)$app->templater()->renderTemplate('public:newfi_portal', []);\n    $result['html_marker'] = strpos($html, 'data-newfi-portal200') !== false;\n    $result['old_copy'] = strpos($html, 'Закрытое пространство для общения, знаний и материалов сообщества.') !== false;"},{"line":295,"text":"        fwrite(STDERR, \"PUBLIC_SECTION_UNAVAILABLE\\n\"); exit(64);\n    }\n} else if ($state === 'old') {\n    $html = (string)$app->templater()->renderTemplate('public:newfi_portal', []);\n    $result['html_marker'] = strpos($html, 'data-newfi-portal200') !== false;\n    $result['old_copy'] = strpos($html, 'Закрытое пространство для общения, знаний и материалов сообщества.') !== false;\n    $result['html_bytes'] = strlen($html);\n    if ($result['html_marker'] || !$result['old_copy']) {\n        fwrite(STDERR, \"OLD_RENDER_CONTRACT\\n\"); exit(65);\n    }\n} else {\n    exit(66);\n}"},{"line":296,"text":"    }\n} else if ($state === 'old') {\n    $html = (string)$app->templater()->renderTemplate('public:newfi_portal', []);\n    $result['html_marker'] = strpos($html, 'data-newfi-portal200') !== false;\n    $result['old_copy'] = strpos($html, 'Закрытое пространство для общения, знаний и материалов сообщества.') !== false;\n    $result['html_bytes'] = strlen($html);\n    if ($result['html_marker'] || !$result['old_copy']) {\n        fwrite(STDERR, \"OLD_RENDER_CONTRACT\\n\"); exit(65);\n    }\n} else {\n    exit(66);\n}\necho \"PORTAL200_RUNTIME_JSON=\" . json_encode("},{"line":298,"text":"    $html = (string)$app->templater()->renderTemplate('public:newfi_portal', []);\n    $result['html_marker'] = strpos($html, 'data-newfi-portal200') !== false;\n    $result['old_copy'] = strpos($html, 'Закрытое пространство для общения, знаний и материалов сообщества.') !== false;\n    $result['html_bytes'] = strlen($html);\n    if ($result['html_marker'] || !$result['old_copy']) {\n        fwrite(STDERR, \"OLD_RENDER_CONTRACT\\n\"); exit(65);\n    }\n} else {\n    exit(66);\n}\necho \"PORTAL200_RUNTIME_JSON=\" . json_encode(\n    $result,\n    JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES|JSON_INVALID_UTF8_SUBSTITUTE"},{"line":304,"text":"    }\n} else {\n    exit(66);\n}\necho \"PORTAL200_RUNTIME_JSON=\" . json_encode(\n    $result,\n    JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES|JSON_INVALID_UTF8_SUBSTITUTE\n) . \"\\n\";\n?>'''\n\n\nHTTP_VERIFY_PY = r'''import html,json,re,subprocess,sys,urllib.parse\nmode=sys.argv[1]"},{"line":367,"text":"        out.append((k,v))\n    if found:\n        forced=urllib.parse.urlunsplit((u.scheme,u.netloc,u.path,urllib.parse.urlencode(out),u.fragment))\n        forced_css_status,forced_body=fetch(forced)\n        forced_css_marker='--newfi-portal200' in forced_body\nresult={\n 'status':status,\n 'bytes':len(body.encode()),\n 'html_marker':'data-newfi-portal200=\"1\"' in body,\n 'css_marker':'--newfi-portal200' in normal_css_text,\n 'old_copy':'Закрытое пространство для общения, знаний и материалов сообщества.' in body,\n 'css_count':len(hrefs),\n 'css_status':normal_status,"},{"line":371,"text":"        forced_css_marker='--newfi-portal200' in forced_body\nresult={\n 'status':status,\n 'bytes':len(body.encode()),\n 'html_marker':'data-newfi-portal200=\"1\"' in body,\n 'css_marker':'--newfi-portal200' in normal_css_text,\n 'old_copy':'Закрытое пространство для общения, знаний и материалов сообщества.' in body,\n 'css_count':len(hrefs),\n 'css_status':normal_status,\n 'forced_css_status':forced_css_status,\n 'forced_css_marker':forced_css_marker,\n}\nprint('PORTAL200_HTTP_JSON='+json.dumps(result,ensure_ascii=False,sort_keys=True,separators=(',',':')))"},{"line":372,"text":"result={\n 'status':status,\n 'bytes':len(body.encode()),\n 'html_marker':'data-newfi-portal200=\"1\"' in body,\n 'css_marker':'--newfi-portal200' in normal_css_text,\n 'old_copy':'Закрытое пространство для общения, знаний и материалов сообщества.' in body,\n 'css_count':len(hrefs),\n 'css_status':normal_status,\n 'forced_css_status':forced_css_status,\n 'forced_css_marker':forced_css_marker,\n}\nprint('PORTAL200_HTTP_JSON='+json.dumps(result,ensure_ascii=False,sort_keys=True,separators=(',',':')))\nif status!=expected_status: raise SystemExit(45)"},{"line":373,"text":" 'status':status,\n 'bytes':len(body.encode()),\n 'html_marker':'data-newfi-portal200=\"1\"' in body,\n 'css_marker':'--newfi-portal200' in normal_css_text,\n 'old_copy':'Закрытое пространство для общения, знаний и материалов сообщества.' in body,\n 'css_count':len(hrefs),\n 'css_status':normal_status,\n 'forced_css_status':forced_css_status,\n 'forced_css_marker':forced_css_marker,\n}\nprint('PORTAL200_HTTP_JSON='+json.dumps(result,ensure_ascii=False,sort_keys=True,separators=(',',':')))\nif status!=expected_status: raise SystemExit(45)\nif mode=='prod':"},{"line":379,"text":" 'css_status':normal_status,\n 'forced_css_status':forced_css_status,\n 'forced_css_marker':forced_css_marker,\n}\nprint('PORTAL200_HTTP_JSON='+json.dumps(result,ensure_ascii=False,sort_keys=True,separators=(',',':')))\nif status!=expected_status: raise SystemExit(45)\nif mode=='prod':\n    if not hrefs or any(x!=200 for x in normal_status): raise SystemExit(46)\nelif mode=='staging':\n    if any(x!=200 for x in normal_status): raise SystemExit(46)\nelse:\n    raise SystemExit(50)\nif mode=='prod' and state=='new':"},{"line":390,"text":"    raise SystemExit(50)\nif mode=='prod' and state=='new':\n    if not result['html_marker'] or not result['css_marker']: raise SystemExit(47)\nelif mode=='prod' and state=='old':\n    if result['html_marker'] or result['css_marker'] or not result['old_copy']: raise SystemExit(48)\nelif mode=='staging' and state=='new':\n    if hrefs and (forced_css_status!=200 or not forced_css_marker): raise SystemExit(49)\nelif mode=='staging' and state=='old':\n    pass\nelse:\n    raise SystemExit(50)\n'''\n"},{"line":401,"text":"'''\n\n\ndef parse_inspect(text):\n    b = \"PORTAL200_INSPECT_JSON_BEGIN\\n\"\n    e = \"\\nPORTAL200_INSPECT_JSON_END\"\n    if b not in text or e not in text:\n        fail(\"INSPECT_MARKERS\")\n    try:\n        return json.loads(text.split(b, 1)[1].split(e, 1)[0])\n    except Exception:\n        fail(\"INSPECT_JSON\")\n"},{"line":402,"text":"\n\ndef parse_inspect(text):\n    b = \"PORTAL200_INSPECT_JSON_BEGIN\\n\"\n    e = \"\\nPORTAL200_INSPECT_JSON_END\"\n    if b not in text or e not in text:\n        fail(\"INSPECT_MARKERS\")\n    try:\n        return json.loads(text.split(b, 1)[1].split(e, 1)[0])\n    except Exception:\n        fail(\"INSPECT_JSON\")\n\n"},{"line":460,"text":"        and a.get(\"boardUrl\") == expected[1]\n        and a.get(\"indexRoute\") == \"portal/\"\n        and a.get(\"defaultStyleId\") == \"4\"\n        and addon.get(\"addon_id\") == \"Newfi/Portal\"\n        and int(addon.get(\"version_id\", 0)) == 1000010\n        and addon.get(\"version_string\") == \"1.0.0 Alpha 1\"\n        and int(addon.get(\"active\", 0)) == 1\n        and int(addon.get(\"is_processing\", 1)) == 0\n    )\n\n\ndef new_errors(target, baseline):\n    raw = qga("},{"line":461,"text":"        and a.get(\"indexRoute\") == \"portal/\"\n        and a.get(\"defaultStyleId\") == \"4\"\n        and addon.get(\"addon_id\") == \"Newfi/Portal\"\n        and int(addon.get(\"version_id\", 0)) == 1000010\n        and addon.get(\"version_string\") == \"1.0.0 Alpha 1\"\n        and int(addon.get(\"active\", 0)) == 1\n        and int(addon.get(\"is_processing\", 1)) == 0\n    )\n\n\ndef new_errors(target, baseline):\n    raw = qga(\n        target,"},{"line":484,"text":"def assert_no_new_errors(target, baseline, phase):\n    rows = new_errors(target, baseline)\n    if rows:\n        emit(\n            \"PORTAL200_NEW_XF_ERRORS\",\n            {\n                \"target\": target,\n                \"phase\": phase,\n                \"count\": len(rows),\n                \"ids\": [r.get(\"error_id\") for r in rows],\n            },\n        )\n        fail(\"NEW_XF_ERRORS\")"},{"line":502,"text":"P=/var/www/forums/newfi/public/src/addons/Newfi/Portal\ntest ! -e \"$B\"\ninstall -d -m 700 \"$B/Pub/Controller\" \"$B/_data\"\ncp -p -- \"$P/Pub/Controller/Portal.php\" \"$B/Pub/Controller/Portal.php\"\ncp -p -- \"$P/_data/templates.xml\" \"$B/_data/templates.xml\"\ntest ! -e \"$P/Service/PortalData.php\"\nprintf 'PORTAL200_BACKUP=PASS\\n'\n'''\n    out = qga(target, [\"/bin/bash\", \"-lc\", script], timeout=90)\n    if \"PORTAL200_BACKUP=PASS\" not in out:\n        fail(\"BACKUP_FAILED\")\n\n"},{"line":504,"text":"install -d -m 700 \"$B/Pub/Controller\" \"$B/_data\"\ncp -p -- \"$P/Pub/Controller/Portal.php\" \"$B/Pub/Controller/Portal.php\"\ncp -p -- \"$P/_data/templates.xml\" \"$B/_data/templates.xml\"\ntest ! -e \"$P/Service/PortalData.php\"\nprintf 'PORTAL200_BACKUP=PASS\\n'\n'''\n    out = qga(target, [\"/bin/bash\", \"-lc\", script], timeout=90)\n    if \"PORTAL200_BACKUP=PASS\" not in out:\n        fail(\"BACKUP_FAILED\")\n\n\ndef guest_write(target, rel, data):\n    dest = f\"/var/www/forums/newfi/public/src/addons/Newfi/Portal/{rel}\""},{"line":507,"text":"test ! -e \"$P/Service/PortalData.php\"\nprintf 'PORTAL200_BACKUP=PASS\\n'\n'''\n    out = qga(target, [\"/bin/bash\", \"-lc\", script], timeout=90)\n    if \"PORTAL200_BACKUP=PASS\" not in out:\n        fail(\"BACKUP_FAILED\")\n\n\ndef guest_write(target, rel, data):\n    dest = f\"/var/www/forums/newfi/public/src/addons/Newfi/Portal/{rel}\"\n    parent = str(pathlib.PurePosixPath(dest).parent)\n    script = (\n        \"set -eu; umask 022; \""},{"line":519,"text":"        \"set -eu; umask 022; \"\n        + \"install -d -m 755 \"\n        + shlex.quote(parent)\n        + \"; T=$(mktemp \"\n        + shlex.quote(parent + \"/.portal200.XXXXXX\")\n        + \"); cat >\\\"$T\\\"; chmod 0644 \\\"$T\\\"; chown root:root \\\"$T\\\"; \"\n        + \"mv -f -- \\\"$T\\\" \"\n        + shlex.quote(dest)\n        + \"; printf 'PORTAL200_WRITE=PASS\\\\n'\"\n    )\n    out = qga(\n        target,\n        [\"/bin/bash\", \"-lc\", script],"},{"line":523,"text":"        + shlex.quote(parent + \"/.portal200.XXXXXX\")\n        + \"); cat >\\\"$T\\\"; chmod 0644 \\\"$T\\\"; chown root:root \\\"$T\\\"; \"\n        + \"mv -f -- \\\"$T\\\" \"\n        + shlex.quote(dest)\n        + \"; printf 'PORTAL200_WRITE=PASS\\\\n'\"\n    )\n    out = qga(\n        target,\n        [\"/bin/bash\", \"-lc\", script],\n        input_bytes=data,\n        timeout=90,\n    )\n    if \"PORTAL200_WRITE=PASS\" not in out:"},{"line":531,"text":"        [\"/bin/bash\", \"-lc\", script],\n        input_bytes=data,\n        timeout=90,\n    )\n    if \"PORTAL200_WRITE=PASS\" not in out:\n        fail(\"WRITE_FAILED\")\n\n\ndef install_candidate(target):\n    for rel in [\n        \"Pub/Controller/Portal.php\",\n        \"Service/PortalData.php\",\n        \"_data/templates.xml\","},{"line":532,"text":"        input_bytes=data,\n        timeout=90,\n    )\n    if \"PORTAL200_WRITE=PASS\" not in out:\n        fail(\"WRITE_FAILED\")\n\n\ndef install_candidate(target):\n    for rel in [\n        \"Pub/Controller/Portal.php\",\n        \"Service/PortalData.php\",\n        \"_data/templates.xml\",\n    ]:"},{"line":535,"text":"    if \"PORTAL200_WRITE=PASS\" not in out:\n        fail(\"WRITE_FAILED\")\n\n\ndef install_candidate(target):\n    for rel in [\n        \"Pub/Controller/Portal.php\",\n        \"Service/PortalData.php\",\n        \"_data/templates.xml\",\n    ]:\n        guest_write(target, rel, (CAND_ROOT / rel).read_bytes())\n\n"},{"line":539,"text":"def install_candidate(target):\n    for rel in [\n        \"Pub/Controller/Portal.php\",\n        \"Service/PortalData.php\",\n        \"_data/templates.xml\",\n    ]:\n        guest_write(target, rel, (CAND_ROOT / rel).read_bytes())\n\n\ndef restore_old(target):\n    script = f'''set -eu\nB={shlex.quote(BACKUP)}\nP=/var/www/forums/newfi/public/src/addons/Newfi/Portal"},{"line":541,"text":"        \"Pub/Controller/Portal.php\",\n        \"Service/PortalData.php\",\n        \"_data/templates.xml\",\n    ]:\n        guest_write(target, rel, (CAND_ROOT / rel).read_bytes())\n\n\ndef restore_old(target):\n    script = f'''set -eu\nB={shlex.quote(BACKUP)}\nP=/var/www/forums/newfi/public/src/addons/Newfi/Portal\ntest -f \"$B/Pub/Controller/Portal.php\"\ntest -f \"$B/_data/templates.xml\""},{"line":549,"text":"    script = f'''set -eu\nB={shlex.quote(BACKUP)}\nP=/var/www/forums/newfi/public/src/addons/Newfi/Portal\ntest -f \"$B/Pub/Controller/Portal.php\"\ntest -f \"$B/_data/templates.xml\"\ncp -p -- \"$B/Pub/Controller/Portal.php\" \"$P/Pub/Controller/Portal.php\"\ncp -p -- \"$B/_data/templates.xml\" \"$P/_data/templates.xml\"\nrm -f -- \"$P/Service/PortalData.php\"\nrmdir -- \"$P/Service\" 2>/dev/null || true\nprintf 'PORTAL200_SOURCE_RESTORE=PASS\\n'\n'''\n    out = qga(target, [\"/bin/bash\", \"-lc\", script], timeout=90)\n    if \"PORTAL200_SOURCE_RESTORE=PASS\" not in out:"},{"line":551,"text":"P=/var/www/forums/newfi/public/src/addons/Newfi/Portal\ntest -f \"$B/Pub/Controller/Portal.php\"\ntest -f \"$B/_data/templates.xml\"\ncp -p -- \"$B/Pub/Controller/Portal.php\" \"$P/Pub/Controller/Portal.php\"\ncp -p -- \"$B/_data/templates.xml\" \"$P/_data/templates.xml\"\nrm -f -- \"$P/Service/PortalData.php\"\nrmdir -- \"$P/Service\" 2>/dev/null || true\nprintf 'PORTAL200_SOURCE_RESTORE=PASS\\n'\n'''\n    out = qga(target, [\"/bin/bash\", \"-lc\", script], timeout=90)\n    if \"PORTAL200_SOURCE_RESTORE=PASS\" not in out:\n        fail(\"SOURCE_RESTORE_FAILED\")\n"},{"line":554,"text":"cp -p -- \"$B/Pub/Controller/Portal.php\" \"$P/Pub/Controller/Portal.php\"\ncp -p -- \"$B/_data/templates.xml\" \"$P/_data/templates.xml\"\nrm -f -- \"$P/Service/PortalData.php\"\nrmdir -- \"$P/Service\" 2>/dev/null || true\nprintf 'PORTAL200_SOURCE_RESTORE=PASS\\n'\n'''\n    out = qga(target, [\"/bin/bash\", \"-lc\", script], timeout=90)\n    if \"PORTAL200_SOURCE_RESTORE=PASS\" not in out:\n        fail(\"SOURCE_RESTORE_FAILED\")\n\n\ndef import_addon(target):\n    out = qga("},{"line":557,"text":"rmdir -- \"$P/Service\" 2>/dev/null || true\nprintf 'PORTAL200_SOURCE_RESTORE=PASS\\n'\n'''\n    out = qga(target, [\"/bin/bash\", \"-lc\", script], timeout=90)\n    if \"PORTAL200_SOURCE_RESTORE=PASS\" not in out:\n        fail(\"SOURCE_RESTORE_FAILED\")\n\n\ndef import_addon(target):\n    out = qga(\n        target,\n        [\"/usr/bin/php8.3\"],\n        input_bytes=IMPORT_PHP.encode(),"},{"line":568,"text":"        [\"/usr/bin/php8.3\"],\n        input_bytes=IMPORT_PHP.encode(),\n        timeout=240,\n    )\n    if \"PORTAL200_IMPORT_JSON=\" not in out:\n        fail(\"IMPORT_FAILED\")\n\n\ndef runtime_verify(target, state):\n    src = RUNTIME_VERIFY_PHP_TEMPLATE.replace(\"__STATE__\", state)\n    out = qga(target, [\"/usr/bin/php8.3\"], input_bytes=src.encode(), timeout=120)\n    line = next(\n        (x for x in out.splitlines() if x.startswith(\"PORTAL200_RUNTIME_JSON=\")),"},{"line":576,"text":"def runtime_verify(target, state):\n    src = RUNTIME_VERIFY_PHP_TEMPLATE.replace(\"__STATE__\", state)\n    out = qga(target, [\"/usr/bin/php8.3\"], input_bytes=src.encode(), timeout=120)\n    line = next(\n        (x for x in out.splitlines() if x.startswith(\"PORTAL200_RUNTIME_JSON=\")),\n        None,\n    )\n    if not line:\n        fail(\"RUNTIME_MARKER_MISSING\")\n    try:\n        data = json.loads(line.split(\"=\", 1)[1])\n    except Exception:\n        fail(\"RUNTIME_JSON\")"},{"line":585,"text":"    try:\n        data = json.loads(line.split(\"=\", 1)[1])\n    except Exception:\n        fail(\"RUNTIME_JSON\")\n    emit(\"PORTAL200_RUNTIME\", {\"target\": target, **data})\n    return data\n\n\ndef http_verify(target, state):\n    argv = [\"/usr/bin/python3\", \"-\", target, state]\n    if target == \"prod\":\n        try:\n            p = subprocess.run("},{"line":606,"text":"            fail(\"HTTP_PROD_TIMEOUT\")\n        out = p.stdout.decode(\"utf-8\", \"replace\")\n        err = p.stderr.decode(\"utf-8\", \"replace\")\n        line = next(\n            (x for x in out.splitlines() if x.startswith(\"PORTAL200_HTTP_JSON=\")),\n            None,\n        )\n        data = None\n        if line:\n            try:\n                data = json.loads(line.split(\"=\", 1)[1])\n                emit(\"PORTAL200_HTTP\", {\"target\": target, \"state\": state, **data})\n            except Exception:"},{"line":613,"text":"        data = None\n        if line:\n            try:\n                data = json.loads(line.split(\"=\", 1)[1])\n                emit(\"PORTAL200_HTTP\", {\"target\": target, \"state\": state, **data})\n            except Exception:\n                fail(\"HTTP_JSON\")\n        if p.returncode != 0:\n            emit(\n                \"PORTAL200_HTTP_EXEC\",\n                {\n                    \"target\": target,\n                    \"state\": state,"},{"line":618,"text":"            except Exception:\n                fail(\"HTTP_JSON\")\n        if p.returncode != 0:\n            emit(\n                \"PORTAL200_HTTP_EXEC\",\n                {\n                    \"target\": target,\n                    \"state\": state,\n                    \"rc\": p.returncode,\n                    \"stderr\": err[:800],\n                },\n            )\n            fail(\"HTTP_PROD_RC\")"},{"line":638,"text":"        input_bytes=HTTP_VERIFY_PY.encode(),\n        timeout=180,\n    )\n    line = next(\n        (x for x in out.splitlines() if x.startswith(\"PORTAL200_HTTP_JSON=\")),\n        None,\n    )\n    if not line:\n        fail(\"HTTP_MARKER_MISSING\")\n    try:\n        data = json.loads(line.split(\"=\", 1)[1])\n    except Exception:\n        fail(\"HTTP_JSON\")"},{"line":647,"text":"    try:\n        data = json.loads(line.split(\"=\", 1)[1])\n    except Exception:\n        fail(\"HTTP_JSON\")\n    emit(\"PORTAL200_HTTP\", {\"target\": target, \"state\": state, **data})\n    return data\n\n\ndef verify_new(target, baseline):\n    data = inspect(target)\n    if not app_contract(data, target):\n        fail(\"APP_CONTRACT_NEW\")\n    if not source_is_new(data):"},{"line":715,"text":"        timeout=40,\n    )\n    if \"status: running\" not in p.stdout.decode():\n        fail(\"VM211_NOT_RUNNING\")\n    emit(\"PORTAL200_CLUSTER\", \"PASS\")\n\n\ndef result_contract(status, changes, rollback_started, rollback_restored):\n    emit(\n        \"HOMELAB_RESULT_CONTRACT\",\n        {\n            \"version\": 1,\n            \"command_id\": CID,"},{"line":734,"text":"\n\nemit(\"NEWFI200_BEGIN\")\nemit(\"COMMAND_ID\", CID)\nemit(\"PORTAL200_REMAINING_FULL_PRODUCT_COMMANDS_BEFORE\", 22)\nemit(\n    \"PORTAL200_FLOW\",\n    \"candidate->staging-apply->rollback-proof->staging-reapply->git-push->production\",\n)\n\nstaging_backup = False\nstaging_mutated = False\nstaging_final_new = False"},{"line":736,"text":"emit(\"NEWFI200_BEGIN\")\nemit(\"COMMAND_ID\", CID)\nemit(\"PORTAL200_REMAINING_FULL_PRODUCT_COMMANDS_BEFORE\", 22)\nemit(\n    \"PORTAL200_FLOW\",\n    \"candidate->staging-apply->rollback-proof->staging-reapply->git-push->production\",\n)\n\nstaging_backup = False\nstaging_mutated = False\nstaging_final_new = False\nprod_backup = False\nprod_mutated = False"},{"line":737,"text":"emit(\"COMMAND_ID\", CID)\nemit(\"PORTAL200_REMAINING_FULL_PRODUCT_COMMANDS_BEFORE\", 22)\nemit(\n    \"PORTAL200_FLOW\",\n    \"candidate->staging-apply->rollback-proof->staging-reapply->git-push->production\",\n)\n\nstaging_backup = False\nstaging_mutated = False\nstaging_final_new = False\nprod_backup = False\nprod_mutated = False\ngit_files_changed = False"},{"line":758,"text":"s_old_templates = None\np_old_templates = None\n\ntry:\n    compile(HTTP_VERIFY_PY, \"<portal200-http-verify>\", \"exec\")\n    for rel, (size, sha) in EXPECTED_NEW.items():\n        p = CAND_ROOT / rel\n        if not p.is_file() or file_meta(p) != (size, sha):\n            fail(\"CANDIDATE_IDENTITY\")\n    tree = ET.parse(CAND_ROOT / \"_data/templates.xml\")\n    titles = sorted(x.attrib.get(\"title\") for x in tree.getroot().findall(\"template\"))\n    if titles != [\"newfi_portal\", \"newfi_portal.less\"]:\n        fail(\"CANDIDATE_TEMPLATE_SET\")"},{"line":760,"text":"\ntry:\n    compile(HTTP_VERIFY_PY, \"<portal200-http-verify>\", \"exec\")\n    for rel, (size, sha) in EXPECTED_NEW.items():\n        p = CAND_ROOT / rel\n        if not p.is_file() or file_meta(p) != (size, sha):\n            fail(\"CANDIDATE_IDENTITY\")\n    tree = ET.parse(CAND_ROOT / \"_data/templates.xml\")\n    titles = sorted(x.attrib.get(\"title\") for x in tree.getroot().findall(\"template\"))\n    if titles != [\"newfi_portal\", \"newfi_portal.less\"]:\n        fail(\"CANDIDATE_TEMPLATE_SET\")\n    service = (CAND_ROOT / \"Service/PortalData.php\").read_text(\"utf-8\")\n    if any("},{"line":762,"text":"    compile(HTTP_VERIFY_PY, \"<portal200-http-verify>\", \"exec\")\n    for rel, (size, sha) in EXPECTED_NEW.items():\n        p = CAND_ROOT / rel\n        if not p.is_file() or file_meta(p) != (size, sha):\n            fail(\"CANDIDATE_IDENTITY\")\n    tree = ET.parse(CAND_ROOT / \"_data/templates.xml\")\n    titles = sorted(x.attrib.get(\"title\") for x in tree.getroot().findall(\"template\"))\n    if titles != [\"newfi_portal\", \"newfi_portal.less\"]:\n        fail(\"CANDIDATE_TEMPLATE_SET\")\n    service = (CAND_ROOT / \"Service/PortalData.php\").read_text(\"utf-8\")\n    if any(\n        token in service\n        for token in ["},{"line":763,"text":"    for rel, (size, sha) in EXPECTED_NEW.items():\n        p = CAND_ROOT / rel\n        if not p.is_file() or file_meta(p) != (size, sha):\n            fail(\"CANDIDATE_IDENTITY\")\n    tree = ET.parse(CAND_ROOT / \"_data/templates.xml\")\n    titles = sorted(x.attrib.get(\"title\") for x in tree.getroot().findall(\"template\"))\n    if titles != [\"newfi_portal\", \"newfi_portal.less\"]:\n        fail(\"CANDIDATE_TEMPLATE_SET\")\n    service = (CAND_ROOT / \"Service/PortalData.php\").read_text(\"utf-8\")\n    if any(\n        token in service\n        for token in [\n            \"\\\\XF::db(\","},{"line":766,"text":"            fail(\"CANDIDATE_IDENTITY\")\n    tree = ET.parse(CAND_ROOT / \"_data/templates.xml\")\n    titles = sorted(x.attrib.get(\"title\") for x in tree.getroot().findall(\"template\"))\n    if titles != [\"newfi_portal\", \"newfi_portal.less\"]:\n        fail(\"CANDIDATE_TEMPLATE_SET\")\n    service = (CAND_ROOT / \"Service/PortalData.php\").read_text(\"utf-8\")\n    if any(\n        token in service\n        for token in [\n            \"\\\\XF::db(\",\n            \"->save(\",\n            \"->delete(\",\n            \"INSERT \","},{"line":767,"text":"    tree = ET.parse(CAND_ROOT / \"_data/templates.xml\")\n    titles = sorted(x.attrib.get(\"title\") for x in tree.getroot().findall(\"template\"))\n    if titles != [\"newfi_portal\", \"newfi_portal.less\"]:\n        fail(\"CANDIDATE_TEMPLATE_SET\")\n    service = (CAND_ROOT / \"Service/PortalData.php\").read_text(\"utf-8\")\n    if any(\n        token in service\n        for token in [\n            \"\\\\XF::db(\",\n            \"->save(\",\n            \"->delete(\",\n            \"INSERT \",\n            \"UPDATE \","},{"line":780,"text":"            \"DELETE \",\n            \"REPLACE \",\n        ]\n    ):\n        fail(\"CANDIDATE_MUTATION_SCAN\")\n    emit(\"PORTAL200_CANDIDATE_IDENTITY\", \"PASS\")\n    emit(\n        \"PORTAL200_ORCHESTRATION_FIXES\",\n        {\n            \"staging_503_without_css_links\": True,\n            \"failure_status\": \"FAIL\",\n            \"prod_http_via_pve01_public_route\": True,\n            \"css_href_regex_fixed\": True,"},{"line":781,"text":"            \"REPLACE \",\n        ]\n    ):\n        fail(\"CANDIDATE_MUTATION_SCAN\")\n    emit(\"PORTAL200_CANDIDATE_IDENTITY\", \"PASS\")\n    emit(\n        \"PORTAL200_ORCHESTRATION_FIXES\",\n        {\n            \"staging_503_without_css_links\": True,\n            \"failure_status\": \"FAIL\",\n            \"prod_http_via_pve01_public_route\": True,\n            \"css_href_regex_fixed\": True,\n        },"},{"line":783,"text":"    ):\n        fail(\"CANDIDATE_MUTATION_SCAN\")\n    emit(\"PORTAL200_CANDIDATE_IDENTITY\", \"PASS\")\n    emit(\n        \"PORTAL200_ORCHESTRATION_FIXES\",\n        {\n            \"staging_503_without_css_links\": True,\n            \"failure_status\": \"FAIL\",\n            \"prod_http_via_pve01_public_route\": True,\n            \"css_href_regex_fixed\": True,\n        },\n    )\n"},{"line":800,"text":"    remote = remote_out.split()[0] if remote_out else \"\"\n    if branch != BRANCH or head != OLD_HEAD or remote != OLD_HEAD or status:\n        fail(\"GIT_PRESTATE\")\n    emit(\n        \"PORTAL200_GIT_PRESTATE\",\n        {\"branch\": branch, \"head\": head, \"remote\": remote, \"clean\": True},\n    )\n    cluster_guard()\n\n    lint_php(\"staging\", INSPECT_PHP, \"INSPECT_STAGING\")\n    lint_php(\"staging\", IMPORT_PHP, \"IMPORT_STAGING\")\n    lint_php(\"staging\", RUNTIME_VERIFY_PHP_TEMPLATE.replace(\"__STATE__\", \"new\"), \"RUNTIME_STAGING\")\n    lint_php(\"prod\", INSPECT_PHP, \"INSPECT_PROD\")"},{"line":811,"text":"    lint_php(\"staging\", RUNTIME_VERIFY_PHP_TEMPLATE.replace(\"__STATE__\", \"new\"), \"RUNTIME_STAGING\")\n    lint_php(\"prod\", INSPECT_PHP, \"INSPECT_PROD\")\n    lint_php(\"prod\", IMPORT_PHP, \"IMPORT_PROD\")\n    lint_php(\"prod\", RUNTIME_VERIFY_PHP_TEMPLATE.replace(\"__STATE__\", \"new\"), \"RUNTIME_PROD\")\n    emit(\"PORTAL200_RUNTIME_HELPER_LINT\", \"PASS\")\n\n    s0 = inspect(\"staging\")\n    p0 = inspect(\"prod\")\n    if not app_contract(s0, \"staging\") or not app_contract(p0, \"prod\"):\n        fail(\"APP_PRESTATE\")\n    if not source_is_old(s0) or not source_is_old(p0):\n        fail(\"SOURCE_PRESTATE\")\n    if s0.get(\"templates\") != p0.get(\"templates\"):"},{"line":825,"text":"    s_error = int(s0[\"application\"][\"error_max_id\"])\n    p_error = int(p0[\"application\"][\"error_max_id\"])\n    s_old_templates = s0[\"templates\"]\n    p_old_templates = p0[\"templates\"]\n    emit(\"PORTAL200_ERROR_BASELINE\", {\"staging\": s_error, \"prod\": p_error})\n    http_verify(\"staging\", \"old\")\n    runtime_verify(\"prod\", \"old\")\n    http_verify(\"prod\", \"old\")\n    assert_no_new_errors(\"staging\", s_error, \"preflight\")\n    assert_no_new_errors(\"prod\", p_error, \"preflight\")\n    emit(\"PORTAL200_PREFLIGHT\", \"PASS\")\n\n    guest_backup(\"staging\")"},{"line":831,"text":"    runtime_verify(\"prod\", \"old\")\n    http_verify(\"prod\", \"old\")\n    assert_no_new_errors(\"staging\", s_error, \"preflight\")\n    assert_no_new_errors(\"prod\", p_error, \"preflight\")\n    emit(\"PORTAL200_PREFLIGHT\", \"PASS\")\n\n    guest_backup(\"staging\")\n    staging_backup = True\n    staging_mutated = True\n    install_candidate(\"staging\")\n    import_addon(\"staging\")\n    verify_new(\"staging\", s_error)\n    emit(\"PORTAL200_STAGING_FIRST_APPLY\", \"PASS\")"},{"line":836,"text":"\n    guest_backup(\"staging\")\n    staging_backup = True\n    staging_mutated = True\n    install_candidate(\"staging\")\n    import_addon(\"staging\")\n    verify_new(\"staging\", s_error)\n    emit(\"PORTAL200_STAGING_FIRST_APPLY\", \"PASS\")\n\n    restore_old(\"staging\")\n    import_addon(\"staging\")\n    verify_old(\"staging\", s_error, s_old_templates)\n    staging_mutated = False"},{"line":839,"text":"    staging_mutated = True\n    install_candidate(\"staging\")\n    import_addon(\"staging\")\n    verify_new(\"staging\", s_error)\n    emit(\"PORTAL200_STAGING_FIRST_APPLY\", \"PASS\")\n\n    restore_old(\"staging\")\n    import_addon(\"staging\")\n    verify_old(\"staging\", s_error, s_old_templates)\n    staging_mutated = False\n    emit(\"PORTAL200_STAGING_ROLLBACK_PROOF\", \"PASS\")\n\n    staging_mutated = True"},{"line":845,"text":"    restore_old(\"staging\")\n    import_addon(\"staging\")\n    verify_old(\"staging\", s_error, s_old_templates)\n    staging_mutated = False\n    emit(\"PORTAL200_STAGING_ROLLBACK_PROOF\", \"PASS\")\n\n    staging_mutated = True\n    install_candidate(\"staging\")\n    import_addon(\"staging\")\n    verify_new(\"staging\", s_error)\n    staging_final_new = True\n    emit(\"PORTAL200_STAGING_FINAL\", \"PASS\")\n"},{"line":848,"text":"    staging_mutated = False\n    emit(\"PORTAL200_STAGING_ROLLBACK_PROOF\", \"PASS\")\n\n    staging_mutated = True\n    install_candidate(\"staging\")\n    import_addon(\"staging\")\n    verify_new(\"staging\", s_error)\n    staging_final_new = True\n    emit(\"PORTAL200_STAGING_FINAL\", \"PASS\")\n\n    for rel in EXPECTED_NEW:\n        dest = GIT_PORTAL / rel\n        old_git_bytes[str(rel)] = dest.read_bytes() if dest.exists() else None"},{"line":852,"text":"    install_candidate(\"staging\")\n    import_addon(\"staging\")\n    verify_new(\"staging\", s_error)\n    staging_final_new = True\n    emit(\"PORTAL200_STAGING_FINAL\", \"PASS\")\n\n    for rel in EXPECTED_NEW:\n        dest = GIT_PORTAL / rel\n        old_git_bytes[str(rel)] = dest.read_bytes() if dest.exists() else None\n        dest.parent.mkdir(parents=True, exist_ok=True)\n        shutil.copyfile(CAND_ROOT / rel, dest)\n    git_files_changed = True\n"},{"line":858,"text":"    for rel in EXPECTED_NEW:\n        dest = GIT_PORTAL / rel\n        old_git_bytes[str(rel)] = dest.read_bytes() if dest.exists() else None\n        dest.parent.mkdir(parents=True, exist_ok=True)\n        shutil.copyfile(CAND_ROOT / rel, dest)\n    git_files_changed = True\n\n    if git(\"diff\", \"--check\"):\n        fail(\"GIT_DIFF_CHECK\")\n    status_lines = git(\"status\", \"--porcelain=v1\", \"--untracked-files=all\").splitlines()\n    actual_paths = sorted(line[3:] for line in status_lines if len(line) >= 4)\n    if actual_paths != sorted(EXPECTED_GIT_PATHS):\n        fail(\"GIT_SCOPE\")"},{"line":885,"text":"    if remote_head != new_head:\n        fail(\"GIT_REMOTE_HEAD\")\n    push_confirmed = True\n    emit(\n        \"PORTAL200_GIT_PROMOTION\",\n        {\n            \"old_head\": OLD_HEAD,\n            \"new_head\": new_head,\n            \"remote_head\": remote_head,\n            \"result\": \"PASS\",\n        },\n    )\n"},{"line":902,"text":"        fail(\"PROD_ERROR_DRIFT\")\n    guest_backup(\"prod\")\n    prod_backup = True\n    prod_mutated = True\n    install_candidate(\"prod\")\n    import_addon(\"prod\")\n    verify_new(\"prod\", p_error)\n    emit(\"PORTAL200_PRODUCTION\", \"PASS\")\n\n    sf = verify_new(\"staging\", s_error)\n    pf = verify_new(\"prod\", p_error)\n    if sf[\"source\"] != pf[\"source\"]:\n        fail(\"FINAL_SOURCE_PARITY\")"},{"line":905,"text":"    prod_mutated = True\n    install_candidate(\"prod\")\n    import_addon(\"prod\")\n    verify_new(\"prod\", p_error)\n    emit(\"PORTAL200_PRODUCTION\", \"PASS\")\n\n    sf = verify_new(\"staging\", s_error)\n    pf = verify_new(\"prod\", p_error)\n    if sf[\"source\"] != pf[\"source\"]:\n        fail(\"FINAL_SOURCE_PARITY\")\n    if sf[\"templates\"] != pf[\"templates\"]:\n        fail(\"FINAL_TEMPLATE_PARITY\")\n    if git(\"rev-parse\", \"HEAD\") != new_head:"},{"line":922,"text":"    if git(\"status\", \"--porcelain=v1\", \"--untracked-files=all\"):\n        fail(\"FINAL_GIT_DIRTY\")\n    cluster_guard()\n    emit(\n        \"PORTAL200_FINAL_EVIDENCE\",\n        {\n            \"commit\": new_head,\n            \"source_files\": 3,\n            \"templates\": 2,\n            \"staging_error_baseline\": s_error,\n            \"prod_error_baseline\": p_error,\n        },\n    )"},{"line":931,"text":"            \"staging_error_baseline\": s_error,\n            \"prod_error_baseline\": p_error,\n        },\n    )\n    emit(\"PORTAL200_PORTAL\", \"PASS\")\n    emit(\"PORTAL200_STAGING\", \"PASS\")\n    emit(\"PORTAL200_PRODUCTION\", \"PASS\")\n    emit(\"PORTAL200_REMAINING_FULL_PRODUCT_COMMANDS_AFTER_PASS\", 21)\n    result_contract(\"OK\", True, False, None)\n    emit(\"NEWFI200_END\")\n    raise SystemExit(0)\n\nexcept PortalError as exc:"},{"line":932,"text":"            \"prod_error_baseline\": p_error,\n        },\n    )\n    emit(\"PORTAL200_PORTAL\", \"PASS\")\n    emit(\"PORTAL200_STAGING\", \"PASS\")\n    emit(\"PORTAL200_PRODUCTION\", \"PASS\")\n    emit(\"PORTAL200_REMAINING_FULL_PRODUCT_COMMANDS_AFTER_PASS\", 21)\n    result_contract(\"OK\", True, False, None)\n    emit(\"NEWFI200_END\")\n    raise SystemExit(0)\n\nexcept PortalError as exc:\n    code = str(exc)"},{"line":933,"text":"        },\n    )\n    emit(\"PORTAL200_PORTAL\", \"PASS\")\n    emit(\"PORTAL200_STAGING\", \"PASS\")\n    emit(\"PORTAL200_PRODUCTION\", \"PASS\")\n    emit(\"PORTAL200_REMAINING_FULL_PRODUCT_COMMANDS_AFTER_PASS\", 21)\n    result_contract(\"OK\", True, False, None)\n    emit(\"NEWFI200_END\")\n    raise SystemExit(0)\n\nexcept PortalError as exc:\n    code = str(exc)\n    emit(\"PORTAL200_FAILURE\", code)"},{"line":934,"text":"    )\n    emit(\"PORTAL200_PORTAL\", \"PASS\")\n    emit(\"PORTAL200_STAGING\", \"PASS\")\n    emit(\"PORTAL200_PRODUCTION\", \"PASS\")\n    emit(\"PORTAL200_REMAINING_FULL_PRODUCT_COMMANDS_AFTER_PASS\", 21)\n    result_contract(\"OK\", True, False, None)\n    emit(\"NEWFI200_END\")\n    raise SystemExit(0)\n\nexcept PortalError as exc:\n    code = str(exc)\n    emit(\"PORTAL200_FAILURE\", code)\n"},{"line":941,"text":"    raise SystemExit(0)\n\nexcept PortalError as exc:\n    code = str(exc)\n    emit(\"PORTAL200_FAILURE\", code)\n\n    if push_confirmed:\n        if prod_mutated and prod_backup:\n            failure_rollback_started = True\n            try:\n                restore_old(\"prod\")\n                import_addon(\"prod\")\n                verify_old(\"prod\", p_error, p_old_templates, strict_errors=False)"},{"line":952,"text":"                import_addon(\"prod\")\n                verify_old(\"prod\", p_error, p_old_templates, strict_errors=False)\n                prod_mutated = False\n                failure_rollback_restored = False\n                emit(\"PORTAL200_FAILURE_PROD_ROLLBACK\", \"PASS_PARTIAL_OVERALL_STATE_CHANGED\")\n            except Exception:\n                failure_rollback_restored = False\n                emit(\"PORTAL200_FAILURE_PROD_ROLLBACK\", \"FAIL\")\n    else:\n        if staging_mutated and staging_backup:\n            failure_rollback_started = True\n            try:\n                restore_old(\"staging\")"},{"line":955,"text":"                failure_rollback_restored = False\n                emit(\"PORTAL200_FAILURE_PROD_ROLLBACK\", \"PASS_PARTIAL_OVERALL_STATE_CHANGED\")\n            except Exception:\n                failure_rollback_restored = False\n                emit(\"PORTAL200_FAILURE_PROD_ROLLBACK\", \"FAIL\")\n    else:\n        if staging_mutated and staging_backup:\n            failure_rollback_started = True\n            try:\n                restore_old(\"staging\")\n                import_addon(\"staging\")\n                verify_old(\"staging\", s_error, s_old_templates, strict_errors=False)\n                staging_mutated = False"},{"line":966,"text":"                verify_old(\"staging\", s_error, s_old_templates, strict_errors=False)\n                staging_mutated = False\n                staging_final_new = False\n                failure_rollback_restored = True\n                emit(\"PORTAL200_FAILURE_STAGING_ROLLBACK\", \"PASS\")\n            except Exception:\n                failure_rollback_restored = False\n                emit(\"PORTAL200_FAILURE_STAGING_ROLLBACK\", \"FAIL\")\n\n        if git_files_changed and not commit_created:\n            try:\n                for rel, old in old_git_bytes.items():\n                    dest = GIT_PORTAL / rel"},{"line":969,"text":"                failure_rollback_restored = True\n                emit(\"PORTAL200_FAILURE_STAGING_ROLLBACK\", \"PASS\")\n            except Exception:\n                failure_rollback_restored = False\n                emit(\"PORTAL200_FAILURE_STAGING_ROLLBACK\", \"FAIL\")\n\n        if git_files_changed and not commit_created:\n            try:\n                for rel, old in old_git_bytes.items():\n                    dest = GIT_PORTAL / rel\n                    if old is None:\n                        if dest.exists():\n                            dest.unlink()"},{"line":979,"text":"                    if old is None:\n                        if dest.exists():\n                            dest.unlink()\n                    else:\n                        dest.write_bytes(old)\n                service_dir = GIT_PORTAL / \"Service\"\n                try:\n                    service_dir.rmdir()\n                except OSError:\n                    pass\n                emit(\"PORTAL200_FAILURE_GIT_WORKTREE_RESTORE\", \"PASS\")\n            except Exception:\n                emit(\"PORTAL200_FAILURE_GIT_WORKTREE_RESTORE\", \"FAIL\")"},{"line":985,"text":"                try:\n                    service_dir.rmdir()\n                except OSError:\n                    pass\n                emit(\"PORTAL200_FAILURE_GIT_WORKTREE_RESTORE\", \"PASS\")\n            except Exception:\n                emit(\"PORTAL200_FAILURE_GIT_WORKTREE_RESTORE\", \"FAIL\")\n\n        if commit_created:\n            emit(\n                \"PORTAL200_FAILURE_GIT_HISTORY\",\n                \"COMMIT_CREATED_NO_DESTRUCTIVE_RESET_FRESH_ID_REQUIRED\",\n            )"},{"line":987,"text":"                except OSError:\n                    pass\n                emit(\"PORTAL200_FAILURE_GIT_WORKTREE_RESTORE\", \"PASS\")\n            except Exception:\n                emit(\"PORTAL200_FAILURE_GIT_WORKTREE_RESTORE\", \"FAIL\")\n\n        if commit_created:\n            emit(\n                \"PORTAL200_FAILURE_GIT_HISTORY\",\n                \"COMMIT_CREATED_NO_DESTRUCTIVE_RESET_FRESH_ID_REQUIRED\",\n            )\n\n    persisted = bool(push_confirmed or commit_created or staging_final_new or prod_mutated)"},{"line":991,"text":"                emit(\"PORTAL200_FAILURE_GIT_WORKTREE_RESTORE\", \"FAIL\")\n\n        if commit_created:\n            emit(\n                \"PORTAL200_FAILURE_GIT_HISTORY\",\n                \"COMMIT_CREATED_NO_DESTRUCTIVE_RESET_FRESH_ID_REQUIRED\",\n            )\n\n    persisted = bool(push_confirmed or commit_created or staging_final_new or prod_mutated)\n    if failure_rollback_started and failure_rollback_restored is None:\n        failure_rollback_restored = False\n    result_contract(\n        \"FAIL\","},{"line":1004,"text":"        persisted,\n        failure_rollback_started,\n        failure_rollback_restored,\n    )\n    emit(\"PORTAL200_ROADMAP_REMAINS\", 22)\n    emit(\"NEWFI200_END\")\n    raise SystemExit(1)\n\nexcept Exception:\n    emit(\"PORTAL200_FAILURE\", \"UNEXPECTED_EXCEPTION\")\n    result_contract(\n        \"FAIL\",\n        bool(push_confirmed or commit_created or staging_final_new or prod_mutated),"},{"line":1009,"text":"    emit(\"NEWFI200_END\")\n    raise SystemExit(1)\n\nexcept Exception:\n    emit(\"PORTAL200_FAILURE\", \"UNEXPECTED_EXCEPTION\")\n    result_contract(\n        \"FAIL\",\n        bool(push_confirmed or commit_created or staging_final_new or prod_mutated),\n        False,\n        None,\n    )\n    emit(\"PORTAL200_ROADMAP_REMAINS\", 22)\n    emit(\"NEWFI200_END\")"},{"line":1016,"text":"        bool(push_confirmed or commit_created or staging_final_new or prod_mutated),\n        False,\n        None,\n    )\n    emit(\"PORTAL200_ROADMAP_REMAINS\", 22)\n    emit(\"NEWFI200_END\")\n    raise SystemExit(1)"}]
RELEVANT_FUNCTIONS=[{"lineno":400,"name":"parse_inspect","sha256":"7b72b62a0ff4c617b7175627d8630294160a6c129f3acfb0d109b4c72c2e8fe2","source":"def parse_inspect(text):\n    b = \"PORTAL200_INSPECT_JSON_BEGIN\\n\"\n    e = \"\\nPORTAL200_INSPECT_JSON_END\"\n    if b not in text or e not in text:\n        fail(\"INSPECT_MARKERS\")\n    try:\n        return json.loads(text.split(b, 1)[1].split(e, 1)[0])\n    except Exception:\n        fail(\"INSPECT_JSON\")"},{"lineno":447,"name":"app_contract","sha256":"a3a06560de8f2db42e0c3bafc27ad29ee985fd5bf13b01c527a283731ed7904c","source":"def app_contract(data, target):\n    a = data.get(\"application\", {})\n    addon = data.get(\"addon\") or {}\n    expected = {\n        \"staging\": (\"0\", \"http://newfi-staging.gram1.ru\"),\n        \"prod\": (\"1\", \"https://newfi.ru\"),\n    }[target]\n    return (\n        a.get(\"boardActive\") == expected[0]\n        and a.get(\"boardUrl\") == expected[1]\n        and a.get(\"indexRoute\") == \"portal/\"\n        and a.get(\"defaultStyleId\") == \"4\"\n        and addon.get(\"addon_id\") == \"Newfi/Portal\"\n        and int(addon.get(\"version_id\", 0)) == 1000010\n        and addon.get(\"version_string\") == \"1.0.0 Alpha 1\"\n        and int(addon.get(\"active\", 0)) == 1\n        and int(addon.get(\"is_processing\", 1)) == 0\n    )"},{"lineno":480,"name":"assert_no_new_errors","sha256":"801839c1705804fed1a507709263d2882a332f0276b42a7c5439204b99428dfb","source":"def assert_no_new_errors(target, baseline, phase):\n    rows = new_errors(target, baseline)\n    if rows:\n        emit(\n            \"PORTAL200_NEW_XF_ERRORS\",\n            {\n                \"target\": target,\n                \"phase\": phase,\n                \"count\": len(rows),\n                \"ids\": [r.get(\"error_id\") for r in rows],\n            },\n        )\n        fail(\"NEW_XF_ERRORS\")"},{"lineno":495,"name":"guest_backup","sha256":"3e0d88922bf3d3420512f69cd0e50232e0325391cebb99845ed1c8c206ec08a1","source":"def guest_backup(target):\n    script = f'''set -eu\nB={shlex.quote(BACKUP)}\nP=/var/www/forums/newfi/public/src/addons/Newfi/Portal\ntest ! -e \"$B\"\ninstall -d -m 700 \"$B/Pub/Controller\" \"$B/_data\"\ncp -p -- \"$P/Pub/Controller/Portal.php\" \"$B/Pub/Controller/Portal.php\"\ncp -p -- \"$P/_data/templates.xml\" \"$B/_data/templates.xml\"\ntest ! -e \"$P/Service/PortalData.php\"\nprintf 'PORTAL200_BACKUP=PASS\\n'\n'''\n    out = qga(target, [\"/bin/bash\", \"-lc\", script], timeout=90)\n    if \"PORTAL200_BACKUP=PASS\" not in out:\n        fail(\"BACKUP_FAILED\")"},{"lineno":511,"name":"guest_write","sha256":"f5f8ed7e6553eadf19e219ed9cab5dd6d7b9ad72dca3926e5368ad663f9b2881","source":"def guest_write(target, rel, data):\n    dest = f\"/var/www/forums/newfi/public/src/addons/Newfi/Portal/{rel}\"\n    parent = str(pathlib.PurePosixPath(dest).parent)\n    script = (\n        \"set -eu; umask 022; \"\n        + \"install -d -m 755 \"\n        + shlex.quote(parent)\n        + \"; T=$(mktemp \"\n        + shlex.quote(parent + \"/.portal200.XXXXXX\")\n        + \"); cat >\\\"$T\\\"; chmod 0644 \\\"$T\\\"; chown root:root \\\"$T\\\"; \"\n        + \"mv -f -- \\\"$T\\\" \"\n        + shlex.quote(dest)\n        + \"; printf 'PORTAL200_WRITE=PASS\\\\n'\"\n    )\n    out = qga(\n        target,\n        [\"/bin/bash\", \"-lc\", script],\n        input_bytes=data,\n        timeout=90,\n    )\n    if \"PORTAL200_WRITE=PASS\" not in out:\n        fail(\"WRITE_FAILED\")"},{"lineno":535,"name":"install_candidate","sha256":"b46ecffec3c0972b1306ffb057164c1223e08f26bd3121bfde7f624ffad66903","source":"def install_candidate(target):\n    for rel in [\n        \"Pub/Controller/Portal.php\",\n        \"Service/PortalData.php\",\n        \"_data/templates.xml\",\n    ]:\n        guest_write(target, rel, (CAND_ROOT / rel).read_bytes())"},{"lineno":544,"name":"restore_old","sha256":"eafa8284ff101e7e3c189922361201ce5684ce970e90e2c933c39b65e25e21da","source":"def restore_old(target):\n    script = f'''set -eu\nB={shlex.quote(BACKUP)}\nP=/var/www/forums/newfi/public/src/addons/Newfi/Portal\ntest -f \"$B/Pub/Controller/Portal.php\"\ntest -f \"$B/_data/templates.xml\"\ncp -p -- \"$B/Pub/Controller/Portal.php\" \"$P/Pub/Controller/Portal.php\"\ncp -p -- \"$B/_data/templates.xml\" \"$P/_data/templates.xml\"\nrm -f -- \"$P/Service/PortalData.php\"\nrmdir -- \"$P/Service\" 2>/dev/null || true\nprintf 'PORTAL200_SOURCE_RESTORE=PASS\\n'\n'''\n    out = qga(target, [\"/bin/bash\", \"-lc\", script], timeout=90)\n    if \"PORTAL200_SOURCE_RESTORE=PASS\" not in out:\n        fail(\"SOURCE_RESTORE_FAILED\")"},{"lineno":561,"name":"import_addon","sha256":"8af6f854e1917ee8e02cfe345f730da0b4b6fbe1b44fdb73ced52c8a3eeebb50","source":"def import_addon(target):\n    out = qga(\n        target,\n        [\"/usr/bin/php8.3\"],\n        input_bytes=IMPORT_PHP.encode(),\n        timeout=240,\n    )\n    if \"PORTAL200_IMPORT_JSON=\" not in out:\n        fail(\"IMPORT_FAILED\")"},{"lineno":572,"name":"runtime_verify","sha256":"cbe3a884afd8bb310ce9d055ddaf74a9923ac55e975ad5a9c3fbb0b910776497","source":"def runtime_verify(target, state):\n    src = RUNTIME_VERIFY_PHP_TEMPLATE.replace(\"__STATE__\", state)\n    out = qga(target, [\"/usr/bin/php8.3\"], input_bytes=src.encode(), timeout=120)\n    line = next(\n        (x for x in out.splitlines() if x.startswith(\"PORTAL200_RUNTIME_JSON=\")),\n        None,\n    )\n    if not line:\n        fail(\"RUNTIME_MARKER_MISSING\")\n    try:\n        data = json.loads(line.split(\"=\", 1)[1])\n    except Exception:\n        fail(\"RUNTIME_JSON\")\n    emit(\"PORTAL200_RUNTIME\", {\"target\": target, **data})\n    return data"},{"lineno":589,"name":"http_verify","sha256":"75eb5fbec1862b5d926cbc91519c284218a4bb05db9edf616422fa187b8348a6","source":"def http_verify(target, state):\n    argv = [\"/usr/bin/python3\", \"-\", target, state]\n    if target == \"prod\":\n        try:\n            p = subprocess.run(\n                argv,\n                input=HTTP_VERIFY_PY.encode(),\n                stdout=subprocess.PIPE,\n                stderr=subprocess.PIPE,\n                timeout=180,\n                check=False,\n            )\n        except subprocess.TimeoutExpired:\n            fail(\"HTTP_PROD_TIMEOUT\")\n        out = p.stdout.decode(\"utf-8\", \"replace\")\n        err = p.stderr.decode(\"utf-8\", \"replace\")\n        line = next(\n            (x for x in out.splitlines() if x.startswith(\"PORTAL200_HTTP_JSON=\")),\n            None,\n        )\n        data = None\n        if line:\n            try:\n                data = json.loads(line.split(\"=\", 1)[1])\n                emit(\"PORTAL200_HTTP\", {\"target\": target, \"state\": state, **data})\n            except Exception:\n                fail(\"HTTP_JSON\")\n        if p.returncode != 0:\n            emit(\n                \"PORTAL200_HTTP_EXEC\",\n                {\n                    \"target\": target,\n                    \"state\": state,\n                    \"rc\": p.returncode,\n                    \"stderr\": err[:800],\n                },\n            )\n            fail(\"HTTP_PROD_RC\")\n        if data is None:\n            fail(\"HTTP_MARKER_MISSING\")\n        return data\n\n    out = qga(\n        target,\n        argv,\n        input_bytes=HTTP_VERIFY_PY.encode(),\n        timeout=180,\n    )\n    line = next(\n        (x for x in out.splitlines() if x.startswith(\"PORTAL200_HTTP_JSON=\")),\n        None,\n    )\n    if not line:\n        fail(\"HTTP_MARKER_MISSING\")\n    try:\n        data = json.loads(line.split(\"=\", 1)[1])\n    except Exception:\n        fail(\"HTTP_JSON\")\n    emit(\"PORTAL200_HTTP\", {\"target\": target, \"state\": state, **data})\n    return data"},{"lineno":692,"name":"cluster_guard","sha256":"bd6547918004a2f74ae4ac8cc7d7caf80b4ced2bcf57cc98ddc454561121dcac","source":"def cluster_guard():\n    p = run([\"/usr/sbin/qm\", \"status\", \"210\"], timeout=30)\n    if \"status: running\" not in p.stdout.decode():\n        fail(\"VM210_NOT_RUNNING\")\n    p = run(\n        [\n            \"/usr/bin/ssh\",\n            \"-T\",\n            \"-o\",\n            \"BatchMode=yes\",\n            \"-o\",\n            \"StrictHostKeyChecking=yes\",\n            \"-o\",\n            \"UpdateHostKeys=no\",\n            \"-o\",\n            \"ConnectTimeout=10\",\n            \"root@pve03\",\n            \"/usr/sbin/qm status 211\",\n        ],\n        timeout=40,\n    )\n    if \"status: running\" not in p.stdout.decode():\n        fail(\"VM211_NOT_RUNNING\")\n    emit(\"PORTAL200_CLUSTER\", \"PASS\")"}]
LITERAL_PAYLOADS=[{"bytes":2008,"lineno":147,"names":["INSPECT_PHP"],"sha256":"8de3d66a7adfca4c179db55bb5c4e956f6750fb41e9e058b449efeb214cbd7f1","value":"<?php\n$root = '/var/www/forums/newfi/public';\nrequire $root . '/src/XF.php';\n\\XF::start($root);\n$app = \\XF::setupApp('XF\\\\Pub\\\\App');\n$db = $app->db();\nfunction srcMeta(string $path): ?array {\n    if (!is_file($path)) { return null; }\n    return ['bytes' => filesize($path), 'sha256' => hash_file('sha256', $path)];\n}\n$portal = $root . '/src/addons/Newfi/Portal';\n$templates = $db->fetchAll(\n    \"SELECT title, style_id, LENGTH(template) AS bytes, SHA2(template,256) AS sha256, \" .\n    \"LOCATE('data-newfi-portal200', template) > 0 AS html_marker, \" .\n    \"LOCATE('--newfi-portal200', template) > 0 AS css_marker \" .\n    \"FROM xf_template WHERE addon_id='Newfi/Portal' ORDER BY style_id,title\"\n);\n$result = [\n 'hostname' => gethostname(),\n 'application' => [\n  'boardActive' => (string)$db->fetchOne(\"SELECT option_value FROM xf_option WHERE option_id='boardActive'\"),\n  'boardUrl' => (string)$db->fetchOne(\"SELECT option_value FROM xf_option WHERE option_id='boardUrl'\"),\n  'indexRoute' => (string)$db->fetchOne(\"SELECT option_value FROM xf_option WHERE option_id='indexRoute'\"),\n  'defaultStyleId' => (string)$db->fetchOne(\"SELECT option_value FROM xf_option WHERE option_id='defaultStyleId'\"),\n  'error_count' => (int)$db->fetchOne(\"SELECT COUNT(*) FROM xf_error_log\"),\n  'error_max_id' => (int)$db->fetchOne(\"SELECT COALESCE(MAX(error_id),0) FROM xf_error_log\")\n ],\n 'addon' => $db->fetchRow(\n  \"SELECT addon_id,version_id,version_string,active,is_processing,last_pending_action \" .\n  \"FROM xf_addon WHERE addon_id='Newfi/Portal'\"\n ),\n 'source' => [\n  'Pub/Controller/Portal.php' => srcMeta($portal . '/Pub/Controller/Portal.php'),\n  'Service/PortalData.php' => srcMeta($portal . '/Service/PortalData.php'),\n  '_data/templates.xml' => srcMeta($portal . '/_data/templates.xml')\n ],\n 'templates' => $templates\n];\necho \"PORTAL200_INSPECT_JSON_BEGIN\\n\";\necho json_encode(\n    $result,\n    JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES|JSON_INVALID_UTF8_SUBSTITUTE\n);\necho \"\\nPORTAL200_INSPECT_JSON_END\\n\";\n?>"},{"bytes":1429,"lineno":194,"names":["IMPORT_PHP"],"sha256":"8f4109ed896f29035cfa5534751507efff2786d4d6d18840adf30b84771a18e8","value":"<?php\n$root = '/var/www/forums/newfi/public';\nrequire $root . '/src/XF.php';\n\\XF::start($root);\n$app = \\XF::setupApp('XF\\\\Pub\\\\App');\n$addOn = $app->addOnManager()->getById('Newfi/Portal');\nif (!$addOn || !$addOn->isInstalled()) {\n    fwrite(STDERR, \"ADDON_NOT_INSTALLED\\n\");\n    exit(30);\n}\n$addOn->preRebuild();\n$dataManager = $app->addOnDataManager();\n$imported = [];\nforeach ($dataManager->getDataTypeClasses() as $dataType) {\n    $handler = $dataManager->getDataTypeHandler($dataType);\n    $xml = $handler->openTypeFile($addOn->getDataDirectory());\n    if (!$xml || !$xml->count()) {\n        $handler->deleteAddOnData($addOn->addon_id);\n        continue;\n    }\n    $start = 0;\n    $guard = 0;\n    while (true) {\n        $last = $handler->importAddOnData($addOn->addon_id, $xml, $start, 0);\n        if (!$last) { break; }\n        if ($last <= $start || ++$guard > 100000) {\n            fwrite(STDERR, \"IMPORT_PROGRESS_INVALID\\n\");\n            exit(31);\n        }\n        $start = $last;\n    }\n    $handler->deleteOrphanedAddOnData($addOn->addon_id, $xml);\n    \\XF::triggerRunOnce();\n    $imported[] = $handler->getContainerTag();\n}\n$addOn->postDataImport();\n$repo = $app->repository(\\XF\\Repository\\StyleRepository::class);\n$repo->updateAllStylesLastModifiedDateLater();\n$addOn->postRebuild();\necho \"PORTAL200_IMPORT_JSON=\" . json_encode(\n    ['result'=>'PASS','containers'=>$imported],\n    JSON_UNESCAPED_SLASHES\n) . \"\\n\";\n?>"},{"bytes":410,"lineno":240,"names":["ERRORS_PHP_TEMPLATE"],"sha256":"ee412cafa99d7d809c8b682db8ebb0bc2958ab0cbc9e37537e22a78f10489635","value":"<?php\n$root='/var/www/forums/newfi/public';\nrequire $root.'/src/XF.php';\n\\XF::start($root);\n$app=\\XF::setupApp('XF\\\\Pub\\\\App');\n$db=$app->db();\n$b=%d;\n$rows=$db->fetchAll(\n    \"SELECT error_id,exception_type,message,filename,line FROM xf_error_log WHERE error_id>? ORDER BY error_id\",\n    [$b]\n);\necho json_encode(\n    $rows,\n    JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES|JSON_INVALID_UTF8_SUBSTITUTE\n);\n?>"},{"bytes":2501,"lineno":258,"names":["RUNTIME_VERIFY_PHP_TEMPLATE"],"sha256":"884c03648203992cf6d5b96be8342243739b0412fdff55f4a6b64cf96aad3313","value":"<?php\n$state = '__STATE__';\n$root = '/var/www/forums/newfi/public';\nrequire $root . '/src/XF.php';\n\\XF::start($root);\n$app = \\XF::setupApp('XF\\\\Pub\\\\App');\n$result = ['state'=>$state];\nif ($state === 'new') {\n    $service = new \\Newfi\\Portal\\Service\\PortalData($app);\n    $portal = $service->build();\n    $html = (string)$app->templater()->renderTemplate('public:newfi_portal', ['portal'=>$portal]);\n    $result['sections'] = [];\n    foreach (['forums','resources','media','events','dogs'] as $key) {\n        $section = $portal[$key] ?? null;\n        if (!is_array($section)) { fwrite(STDERR, \"SECTION_MISSING\\n\"); exit(60); }\n        $result['sections'][$key] = [\n            'available'=>(bool)($section['available'] ?? false),\n            'hasItems'=>(bool)($section['hasItems'] ?? false),\n            'status'=>(string)($section['status'] ?? '')\n        ];\n    }\n    $result['html_marker'] = strpos($html, 'data-newfi-portal200=\"1\"') !== false;\n    $result['locked_resources'] = strpos($html, 'data-portal-locked=\"resources\"') !== false;\n    $result['locked_media'] = strpos($html, 'data-portal-locked=\"media\"') !== false;\n    $result['html_bytes'] = strlen($html);\n    if (!$result['html_marker']) { fwrite(STDERR, \"HTML_MARKER_MISSING\\n\"); exit(61); }\n    if ($result['sections']['resources']['available'] || $result['sections']['media']['available']) {\n        fwrite(STDERR, \"GUEST_PERMISSION_CONTRACT_CHANGED\\n\"); exit(62);\n    }\n    if (!$result['locked_resources'] || !$result['locked_media']) {\n        fwrite(STDERR, \"LOCKED_COPY_MISSING\\n\"); exit(63);\n    }\n    if (!$result['sections']['forums']['available'] || !$result['sections']['events']['available'] || !$result['sections']['dogs']['available']) {\n        fwrite(STDERR, \"PUBLIC_SECTION_UNAVAILABLE\\n\"); exit(64);\n    }\n} else if ($state === 'old') {\n    $html = (string)$app->templater()->renderTemplate('public:newfi_portal', []);\n    $result['html_marker'] = strpos($html, 'data-newfi-portal200') !== false;\n    $result['old_copy'] = strpos($html, 'Закрытое пространство для общения, знаний и материалов сообщества.') !== false;\n    $result['html_bytes'] = strlen($html);\n    if ($result['html_marker'] || !$result['old_copy']) {\n        fwrite(STDERR, \"OLD_RENDER_CONTRACT\\n\"); exit(65);\n    }\n} else {\n    exit(66);\n}\necho \"PORTAL200_RUNTIME_JSON=\" . json_encode(\n    $result,\n    JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES|JSON_INVALID_UTF8_SUBSTITUTE\n) . \"\\n\";\n?>"},{"bytes":3277,"lineno":311,"names":["HTTP_VERIFY_PY"],"sha256":"15b9839d95798833aef597b59787d301155d32cfb404b14ca7e0bf7d83a1a71e","value":"import html,json,re,subprocess,sys,urllib.parse\nmode=sys.argv[1]\nstate=sys.argv[2]\nif mode=='staging':\n    host='newfi-staging.gram1.ru'; scheme='http'; port='80'; expected_status=503\nelif mode=='prod':\n    host='newfi.ru'; scheme='https'; port='443'; expected_status=200\nelse:\n    raise SystemExit(40)\ndef fetch(url):\n    cmd=[\n        '/usr/bin/curl','-sS','-k','--max-time','30','--connect-timeout','10'\n    ]\n    if mode=='staging':\n        cmd += ['--resolve',f'{host}:{port}:127.0.0.1']\n    cmd += [\n        '-o','-','-w','\\n__PORTAL_STATUS__%{http_code}',url\n    ]\n    p=subprocess.run(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE,check=False)\n    if p.returncode!=0: raise SystemExit(41)\n    text=p.stdout.decode('utf-8','replace')\n    marker='\\n__PORTAL_STATUS__'\n    if marker not in text: raise SystemExit(42)\n    body,status=text.rsplit(marker,1)\n    return int(status.strip()),body\nroot=f'{scheme}://{host}/'\nstatus,body=fetch(root)\nhrefs=[]\nfor raw in re.findall(r\"href=[\\\"']([^\\\"']*css\\.php[^\\\"']*)[\\\"']\",body,re.I):\n    raw=html.unescape(raw)\n    hrefs.append(urllib.parse.urljoin(root,raw))\nnormal_css=[]\nnormal_status=[]\nfor u in hrefs:\n    s,b=fetch(u)\n    normal_status.append(s)\n    normal_css.append(b)\nnormal_css_text='\\n'.join(normal_css)\nforced_css_marker=None\nforced_css_status=None\nif mode=='staging' and state=='new' and hrefs:\n    u=urllib.parse.urlsplit(hrefs[0])\n    pairs=urllib.parse.parse_qsl(u.query,keep_blank_values=True)\n    out=[]\n    found=False\n    for k,v in pairs:\n        if k=='css':\n            found=True\n            parts=[x for x in v.split(',') if x]\n            if 'public:newfi_portal.less' not in parts:\n                parts.append('public:newfi_portal.less')\n            v=','.join(parts)\n        out.append((k,v))\n    if found:\n        forced=urllib.parse.urlunsplit((u.scheme,u.netloc,u.path,urllib.parse.urlencode(out),u.fragment))\n        forced_css_status,forced_body=fetch(forced)\n        forced_css_marker='--newfi-portal200' in forced_body\nresult={\n 'status':status,\n 'bytes':len(body.encode()),\n 'html_marker':'data-newfi-portal200=\"1\"' in body,\n 'css_marker':'--newfi-portal200' in normal_css_text,\n 'old_copy':'Закрытое пространство для общения, знаний и материалов сообщества.' in body,\n 'css_count':len(hrefs),\n 'css_status':normal_status,\n 'forced_css_status':forced_css_status,\n 'forced_css_marker':forced_css_marker,\n}\nprint('PORTAL200_HTTP_JSON='+json.dumps(result,ensure_ascii=False,sort_keys=True,separators=(',',':')))\nif status!=expected_status: raise SystemExit(45)\nif mode=='prod':\n    if not hrefs or any(x!=200 for x in normal_status): raise SystemExit(46)\nelif mode=='staging':\n    if any(x!=200 for x in normal_status): raise SystemExit(46)\nelse:\n    raise SystemExit(50)\nif mode=='prod' and state=='new':\n    if not result['html_marker'] or not result['css_marker']: raise SystemExit(47)\nelif mode=='prod' and state=='old':\n    if result['html_marker'] or result['css_marker'] or not result['old_copy']: raise SystemExit(48)\nelif mode=='staging' and state=='new':\n    if hrefs and (forced_css_status!=200 or not forced_css_marker): raise SystemExit(49)\nelif mode=='staging' and state=='old':\n    pass\nelse:\n    raise SystemExit(50)\n"}]
SOURCE_VERSION_IDS=[]
SOURCE_VERSION_STRINGS=[]
AST_RECOVERY_END=true

OUTPUT_END
CHAT_OUTPUT_END
