39 lines
118 KiB
JSON
39 lines
118 KiB
JSON
{
|
|
"schema_version": 1,
|
|
"channel": "homelab-runtime",
|
|
"command_id": "NEWFI-260907-A-PORTAL200-APPLY4DESIGN7",
|
|
"status": "OK",
|
|
"rc": 0,
|
|
"host": "pve01",
|
|
"mode": "read-only",
|
|
"component": "newfi-portal200-apply4-static-ast-recovery",
|
|
"started_at_utc": "2026-09-07T20:36:05Z",
|
|
"finished_at_utc": "2026-09-07T20:36:05Z",
|
|
"reference_register_checked": true,
|
|
"reference_sha256": "5a3d8e5154c41cb582a4e0aca68090be1f0138918bf82131a948df326f9d8d66",
|
|
"error_register_checked": true,
|
|
"error_register_sha256": "3b09a553ec0f527ed3afeed4753f52a74ee3036045bbfb3c685e9f8af4ba7ba0",
|
|
"command_sha256": "f496660f98e2d5bd57395b705f69565e9b9b4fa3823630155bbe079f561a2876",
|
|
"duplicate_failed_command_blocked": false,
|
|
"block_reason": null,
|
|
"execution_started": true,
|
|
"change_declared": false,
|
|
"result_contract_valid": true,
|
|
"result_contract_status": null,
|
|
"result_contract_error": null,
|
|
"command_rc": 0,
|
|
"changes_made": false,
|
|
"rollback_started": false,
|
|
"rollback_restored": null,
|
|
"mutation_outcome": "NO_MUTATION",
|
|
"sanitized": true,
|
|
"secrets_included": false,
|
|
"private_addresses_included": false,
|
|
"raw_evidence_retained_locally": true,
|
|
"raw_evidence_sha256": "b0e750afcc8c28e33530e823fa9ca879ddd22ce66e758296aeb27ffd61aa5d1a",
|
|
"sanitized_output_sha256": "b0e750afcc8c28e33530e823fa9ca879ddd22ce66e758296aeb27ffd61aa5d1a",
|
|
"output_truncated_in_json": false,
|
|
"full_sanitized_output_url": "https://git.gram1.ru/.well-known/homelab-runtime/latest.txt",
|
|
"output": "AST_RECOVERY_BEGIN=true\nOLD_CARRIER_SHA256=573c5c94fe59ed072e094281668ce9847dc4a9882019a2b89d8ba5f190d92176\nOLD_INNER_SHA256=97a1807a275e5d309591f7a7e044d7d56ba5b2f4c6a068df6353bae147707184\nOLD_INNER_BYTES=34155\nTOP_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}\nCONST_CID=NEWFI-260907-PORTAL200-APPLY3\nCONST_BRANCH=implementation/newfi-closed-mvp-20260906\nCONST_OLD_HEAD=06bab5acd124e768f70305b3d7eca2f595871890\nCONST_WT=UNPROVEN\nCONST_CAND_ROOT=UNPROVEN\nRELEVANT_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\"}]\nRELEVANT_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)\"}]\nRELEVANT_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)\"}]\nRELEVANT_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\\\")\"}]\nLITERAL_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\"}]\nSOURCE_VERSION_IDS=[]\nSOURCE_VERSION_STRINGS=[]\nAST_RECOVERY_END=true\n"
|
|
}
|