+ Setup already completed
++ This installation has already finished its first-run bootstrap. + Use the dashboard and Settings surfaces to reconfigure specific areas instead of restarting setup. +
+ +diff --git a/src/Web/StellaOps.Web/output/playwright/live-setup-wizard-first-run-bootstrap.auth.json b/src/Web/StellaOps.Web/output/playwright/live-setup-wizard-first-run-bootstrap.auth.json new file mode 100644 index 000000000..23d640763 --- /dev/null +++ b/src/Web/StellaOps.Web/output/playwright/live-setup-wizard-first-run-bootstrap.auth.json @@ -0,0 +1,63 @@ +{ + "authenticatedAtUtc": "2026-04-19T11:21:13.948Z", + "authenticated": true, + "error": null, + "baseUrl": "https://stella-ops.local", + "finalUrl": "https://stella-ops.local/?tenant=default®ions=apac,eu-west,us-east", + "title": "Dashboard - StellaOps", + "bodyText": "Skip to main content\nStella Ops\nv1.0.0-alpha\nDashboard\nDaily health, feed freshness, and onboarding progress\nRELEASE CONTROL\n\nPlan, approve, and promote verified releases through your environments.\n\nEnvironments\nReadiness, gate status, and promotion topology\nDeployments\nActive deployments and approval queue\n4\nReleases\nRelease versions and bundles\n1\nRelease Policies\nPolicy packs, governance, VEX, and simulation\nSECURITY\n\nScan images, triage findings, and explain exploitability before promotion.\n\nImage Security\nSecurity posture, findings, SBOM, and evidence for container images\nTriage Queue\nPrioritized vulnerability triage work queue\nRisk Overview\nFleet-wide risk budget and compliance posture\nAdvisory Sources\nFeed health, freshness, and SLA compliance\nOPERATIONS\n\nRun the platform, keep feeds healthy, and investigate background execution.\n\nScheduled Jobs\nScheduled scans, runs, and worker fleet\nFeeds & Airgap\nFeed freshness, offline kits, and transfer readiness\nScripts\nOperator scripts and reusable automation entry points\nSTART HERE\nDiagnostics\nService health, drift signals, and operational checks\nAudit\nSETTINGS\n\nIdentity, trust, tenant settings, and governance controls for operators.\n\nIntegrations\nConnect source control, registries, notifications, and delivery systems\nIdentity & Access\nManage sign-in, access rules, and operator scopes\nCertificates & Trust\nTheme & Branding\nUser Preferences\nPersonal defaults for helper behavior, theme, and working context\nTENANT\nDefault Tenant\nREGION\nNone\nENV\nNone\n1h\n6h\n24h\n7d\n30d\nSTAGE\nAll\nf945f00811f44f008058268a264ed015\nDashboard\n?\nABOUT THIS PAGE\nDashboard\n\nThis is your daily command center. It tells you which environments are healthy, where risk is building up, whether advisory feeds are current, and what action should happen next.\n\nKey concepts\nSBOM and reachability\n\nSBOM tells Stella what is inside each image. Reachability tells Stella which of those vulnerable code paths are actually callable, so you can separate real risk from background noise.\n\nSeverity counts\n\nCritical means fix now. High means near-term remediation. Medium and Low help you plan backlog work without losing sight of immediate blockers.\n\nNext-step workflow\n\nFresh installations usually follow the same order: run diagnostics, connect integrations, scan an image, review findings, then create and promote a release.\n\nCommon actions\nScan your first image\nCheck feed status\nRun Diagnostics\nDocs\nOperator guide\nArchitecture overview\nDashboard\n\nDefault Tenant\n\nQUICK LINKS:\nDeployments\nDeployment timeline and run history\nSecurity & Risk\nPosture, findings, and reachability\nOperations\nPlatform health and execution control\nEvidence\nDecision capsules and audit trail\nPlatform Setup\nEnvironments, integrations, topology\nDiagnostics\nRun health checks on your deployment\n\nFIRST VISIT\n\nWelcome to Stella Ops\n\nThis dashboard is your daily command center. It summarizes SBOM coverage, reachable risk, feed freshness, and environment health so you can decide what to fix, approve, or investigate next.\n\nStart setup wizard\nRun diagnostics\nSeverity guide\nCRITICAL Exploitable or release-blocking. Fix immediately.\nHIGH Serious exposure. Schedule remediation within days.\nMEDIUM Moderate risk. Address in planned sprint work.\nLOW Track and fix when it is cost-effective.\nWhat should I do next?\n\nThese suggestions are based on the current state of your environments, feeds, and findings.\n\nGenerate your first SBOM\nEvery environment currently shows SBOM missing. Scan one container image to unlock posture, reachability, and evidence data.\n1 critical findings need triage\nOpen the findings workflow to decide whether each critical issue should be fixed, waived, or explained with VEX evidence.\nResolve unknown environment health\nUnknown health usually means no agent, signal probe, or readiness telemetry is reporting yet. Run diagnostics to find the missing dependency.\nCheck advisory feed freshness\nYour advisory sources are missing, stale, or degraded. Refresh them so new CVEs and VEX updates reach the dashboard.\nHide\nPENDING ACTIONS\nAll environments\nUS Production\nUs East\nUNKNOWN\nSBOM\nmissing\nCRITR\n0\nHIGHR\n0\nB/I/R\n0/0\nPENDING\n0\nNo deployments\nDetail\nFindings\nUS UAT\nUs East\nUNKNOWN\nSBOM\nmissing\nCRITR\n0\nHIGHR\n0\nB/I/R\n0/0\nPENDING\n0\nNo deployments\nDetail\nFindings\nEU Production\nEu West\nUNKNOWN\nSBOM\nmissing\nCRITR\n0\nHIGHR\n0\nB/I/R\n0/0\nPENDING\n0\nNo deployments\nDetail\nFindings\nEU Staging\nEu West\nUNKNOWN\nSBOM\nmissing\nCRITR\n0\nHIGHR\n0\nB/I/R\n0/0\nPENDING\n0\nNo deployments\nDetail\nFindings\nAPAC Production\nAPAC\nUNKNOWN\nSBOM\nmissing\nCRITR\n0\nHIGHR\n0\nB/I/R\n0/0\nPENDING\n0\nNo deployments\nDetail\nFindings\nCritical Open\nCritical vulnerabilities needing triage\n1\nVULNERABILITY SUMMARY ?\nCritical\n3\nHigh\n2\nMedium\n1\nLow\n0\n6 total\n1 critical open\nView Findings\nFEED STATUS ?\n1\nactive\nNot checked\nManage Sources\nSBOM HEALTH ?\nCRITICAL ENVS\n0\nWith critical issues\nCRIT. REACHABLE\n0\nReachable criticals\nCLEAN ENVS\n5\nNo critical findings\nView SBOM\nENVIRONMENT HEALTH\nENVIRONMENTS\n5\nRegistered targets\nBLOCKED\n0\nBlocking releases\nDEGRADED\n0\nNeeding attention\nHEALTHY\n0\nFully operational\nENVIRONMENTS AT RISK\nOpen all\nREGION/ENV\tHEALTH\tSBOM\tCRITR\tACTION\nUs East / US Production\tunknown\tmissing\t0\tOpen\nUs East / US UAT\tunknown\tmissing\t0\tOpen\nEu West / EU Production\tunknown\tmissing\t0\tOpen\nEu West / EU Staging\tunknown\tmissing\t0\tOpen\nAPAC / APAC Production\tunknown\tmissing\t0\tOpen\nServices\nFeeds\nSecurity\nEvidence\nDLQ\nDiagnostics\nCritical findings need triage\nCritical open findings usually mean exploitable issues that can block promotions or require compensating evidence. Triage them first, then decide whether to fix, create an exception, or attach VEX justification.\nView findings\n1 / 14", + "cookies": [], + "storage": { + "localStorageEntries": [ + [ + "stellaops.auth.session.full", + "{\"tokens\":{\"accessToken\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6IjdLUks4OUw1UTNESkVQR09IV1NPSDNMVDktTVdNWVVJRS1aSU1VX1EiLCJ0eXAiOiJhdCtqd3QifQ.eyJpc3MiOiJodHRwczovL2F1dGhvcml0eS5zdGVsbGEtb3BzLmxvY2FsLyIsImV4cCI6MTc3NjU5OTQ3MiwiaWF0IjoxNzc2NTk3NjcyLCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIGVtYWlsIG9mZmxpbmVfYWNjZXNzIHVpLnJlYWQgdWkuYWRtaW4gdWkucHJlZmVyZW5jZXMucmVhZCB1aS5wcmVmZXJlbmNlcy53cml0ZSBhdXRob3JpdHk6dGVuYW50cy5yZWFkIGF1dGhvcml0eTp0ZW5hbnRzLndyaXRlIGF1dGhvcml0eTp1c2Vycy5yZWFkIGF1dGhvcml0eTp1c2Vycy53cml0ZSBhdXRob3JpdHk6cm9sZXMucmVhZCBhdXRob3JpdHk6cm9sZXMud3JpdGUgYXV0aG9yaXR5OmNsaWVudHMucmVhZCBhdXRob3JpdHk6Y2xpZW50cy53cml0ZSBhdXRob3JpdHk6dG9rZW5zLnJlYWQgYXV0aG9yaXR5OnRva2Vucy5yZXZva2UgYXV0aG9yaXR5OmJyYW5kaW5nLnJlYWQgYXV0aG9yaXR5OmJyYW5kaW5nLndyaXRlIGF1dGhvcml0eS5hdWRpdC5yZWFkIGdyYXBoOnJlYWQgc2JvbTpyZWFkIHNjYW5uZXI6cmVhZCBwb2xpY3k6cmVhZCBwb2xpY3k6c2ltdWxhdGUgcG9saWN5OmF1dGhvciBwb2xpY3k6cmV2aWV3IHBvbGljeTphcHByb3ZlIHBvbGljeTpydW4gcG9saWN5OmFjdGl2YXRlIHBvbGljeTphdWRpdCBwb2xpY3k6ZWRpdCBwb2xpY3k6b3BlcmF0ZSBwb2xpY3k6cHVibGlzaCBhaXJnYXA6c2VhbCBhaXJnYXA6c3RhdHVzOnJlYWQgb3JjaDpyZWFkIG9yY2g6b3BlcmF0ZSBvcmNoOnF1b3RhIGFuYWx5dGljcy5yZWFkIGFkdmlzb3J5OnJlYWQgYWR2aXNvcnktYWk6dmlldyBhZHZpc29yeS1haTpvcGVyYXRlIHZleDpyZWFkIHZleGh1YjpyZWFkIGV4Y2VwdGlvbnM6cmVhZCBleGNlcHRpb25zOmFwcHJvdmUgYW9jOnZlcmlmeSBmaW5kaW5nczpyZWFkIHJlbGVhc2U6cmVhZCByZWxlYXNlOndyaXRlIHJlbGVhc2U6cHVibGlzaCBzY2hlZHVsZXI6cmVhZCBzY2hlZHVsZXI6b3BlcmF0ZSBub3RpZnkudmlld2VyIG5vdGlmeS5vcGVyYXRvciBub3RpZnkuYWRtaW4gbm90aWZ5LmVzY2FsYXRlIGV2aWRlbmNlOnJlYWQgZXhwb3J0LnZpZXdlciBleHBvcnQub3BlcmF0b3IgZXhwb3J0LmFkbWluIHZ1bG46dmlldyB2dWxuOmludmVzdGlnYXRlIHZ1bG46b3BlcmF0ZSB2dWxuOmF1ZGl0IHBsYXRmb3JtLmNvbnRleHQucmVhZCBwbGF0Zm9ybS5jb250ZXh0LndyaXRlIGRvY3RvcjpydW4gZG9jdG9yOmFkbWluIG9wcy5oZWFsdGggaW50ZWdyYXRpb246cmVhZCBpbnRlZ3JhdGlvbjp3cml0ZSBpbnRlZ3JhdGlvbjpvcGVyYXRlIHBhY2tzLnJlYWQgcGFja3Mud3JpdGUgcGFja3MucnVuIHBhY2tzLmFwcHJvdmUgcmVnaXN0cnkuYWRtaW4gdGltZWxpbmU6cmVhZCB0aW1lbGluZTp3cml0ZSB0cnVzdDpyZWFkIHRydXN0OndyaXRlIHRydXN0OmFkbWluIHNpZ25lcjpyZWFkIHNpZ25lcjpzaWduIHNpZ25lcjpyb3RhdGUgc2lnbmVyOmFkbWluIiwianRpIjoiOWJhYWRkZmUtOGVhYi00YjdlLWIxNDAtYjdkYmM2ZGY0NDY1Iiwic3ViIjoiZjk0NWYwMDgxMWY0NGYwMDgwNTgyNjhhMjY0ZWQwMTUiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiIsInN0ZWxsYW9wczp0ZW5hbnQiOiJkZWZhdWx0IiwiYXV0aF90aW1lIjoxNzc2NTk3NjcxLCJvaV9wcnN0Ijoic3RlbGxhLW9wcy11aSIsImNsaWVudF9pZCI6InN0ZWxsYS1vcHMtdWkifQ.LTsaW7eUNYBfwjxklmhF24qfW9BP1Jco03vZppZeVJyToGF0mT0_Sr1vdfa_spRIlPUVYjDrRHhAIMuAQak350DXzJqF9nY4kvhQzW07loyB5ASCdt1IBijRqjXVp8nGVVP5_pGZiLbX34AxRRbfrLE7KwRFkf0NZaochfBhRjqoLN2uRw-IIGkJMNthFae8qPmNvFvDaUruygUDIXv2yUVS_NnXXYJDOy9BC35akAlEqsEG-MGSomuUbo-YeS4hjpBG7mF7LX4UiUYFkePmRWCKVDJDPYExPp3j3n0cMQaqQci1OiKvbX3jJYCM1wkMXSV28ZeRYC9ZjNZVJgG76Q\",\"tokenType\":\"Bearer\",\"refreshToken\":\"eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZDQkMtSFM1MTIiLCJraWQiOiJZV0pPRVlCSzU3UEpSQTFTSFhFWkM3NEFRQktRWFdEQjZaWlVPV0dVIiwidHlwIjoib2lfcmVmdCtqd3QiLCJjdHkiOiJKV1QifQ.yCZRh1VwHde8Mw6susd190XjLjX4CYt4XMW_es2vU1YFnLm1gOjya3tVSPjNnzOW57EWn_0q4nTSocTNJFV8JDGfR5S92xRbvCBkV54MKWgILqsoRY04eU1Q1fszPybTZqTxj-rX-IMiAJ0EXqxtTLJdxMFN5sdQYcKOMdZV6S6dBArSEMdfdVQKCtuEAKdUEpILsOcI4S4x3B8z8tlw5DsCdiFgW3M2mijFrKppiPGiKDnN1upeZ6StoaUu-Ijnw_hZIo2vbBXINAe-5p_naJ-6JxAGP_UlCPNtWvIZuIA6qV-kldCDE8kWARn1rnXCqB5Zjg3wwulVjlh5u7__0w.snJwpNse2FWAMfsSA08jSg.4Lngki9KU2RPD0KkchsxkAtAaITJR0Hv_gvL4Rfn3-UOif2kvIGk3iX8G3Xo0gvdem7x0_70EMM0jLgZT-tvxc5gm10lbuD3R1nCefcgJcAD3zBN-UKKbNRVe8VxTWNNQXhK8bAEvphjgR3yhQt6LyxQw2-arjkz947kRPctj9UmRQNItD4oN0KOchEmuGIYhCzi3SKsJxbcembWNPmFU9DKJqFdRotzCy8bMlYBY2Wh744Ue3AhuJeg1YuYd6giQZNowlkuP10O27FwATpfC4iE6Y1rbMFj8ZIXMJn5vwz1gh51n4NCp-qjQOeYCcECV4E3ITrQprIjF2fCo9HlRTNETktx13s31trD1sCpTunYRnLrcCaEJdMDLVsVRXxIrMF4thEFwU3pOkjMHgD27fAGfoLSJfghU59938MuBBW8Zx51NE6Ylza9G0giTb0TVSE0ajaGextQW5DC5hLmbx8kk3-d3WfslYv2nLflwh4JtSZsXDsumnivWG_YXU1SvpL7MUdr9a8kWeUkA6NTQhXVLb-38N-BQBzOiU8TGaf5FQ86B8BEi8z3w-mBwM2hrOR7n1AgmrqmJXM7YbvkUBLZPCZX_F2ekPNItr8HQEbPAVGrftAwyp7gLWSz_7Ysypy_88LZnTTD1Xy9GBtPouveXtedfEMMY6vuDoW2FURwibxwv0PbLxFk96kHUX8imEMaKKMZSyvP1xe93-xyyw4XMdwgckJluU0gviMUtJDbT_Ew1cHzS0yfCVhKawfP0TjAdBeDhAQmhJ6WDzRHSxZ3vs_2yVxbcB7ijqhGsL8YbyBOeBJqUO0tZt-dvZmK0auac24b_yFCClglWUUg3lGuG2OsUNwep6EfYkv__V3ewTOZH0Fr4Xzh5RqU9ChvSGrU1hR4SXjMsfHLnzggPzPionvnNlPQe3QpMLaBsUYDWBiyYHa2EodXwk6lQfU0ur9-BrCoo8iEy_VCTa_epa-6hdQtVHq1MYGfYf2hXZkMBdS_dEgsMjwTUynhAeiqv11VCd6wkttc_atN5kAMWydBwMqMvW9vdwart1CGTFNpi_9moVkZc2nNEUfBLFARey_Tc0Ry4nkCYRzePJ65sY-Bn1u_xdDQPgneoP7locVhqF6G040K0ZKBkQQaUwPO20KbWHlGuPphOk7YW4GPo0eD1k34PlcXjlaNH6VZxW9yUGUsa6BG52eP9SggiowJDGkRzuOknLBRCuaRTopLayPRaSrPu3rnYy3_ggvjGuIQDH4nsikK_SANDA8yJEfl6KQJbRJyNw3nxi8kQgRcjctPfKDmHUsWS0Q7RzoN_4JUzM1mcT74ciBJ26gx72iwAJQYPWzMvazEvgop2Wv4ii8aaHccbkpDaUd85I-gv0G5hZyIVRB_pDLvL5kw0Q2VVsIeBtcX5_RN6e8PJTlNAWxdlRWwe9-vHAnwsIIqi4BlA9-URdj2flUxZwitLzfbPOkFUQ_QcJqUURRnlrmgUwbiYIZrCU76nuyfnuCpm1kkktajGHCq8Re86kjkGU28ZcuZ-uQ1ewIyHyartYNaaFuKZg4MoqGJySvf_gWReAWIC3vvxo5REgvj3h7MkJNI6Q4D0B3HT6MJ5dZpxQHocM_8tfIhFz9tuFsnhbbWcqsCNoDqh7XSkf66sLsBRN4Mg_te9r4HUa9I3o9IEWyyeIfM7GTW3tSUUFwB9hmLYlQDYUgZVO1glHckZQrMVVThZGcJ5hnYJ3UaSG6VvFV6EokTF3fDJgAyzkanqZ8sZMafWg_gqVQ_ldM6ck1rE9hnXs_ZfuJr43_9FotXDljE01TWKiCs1UUjf9G5BjZmenunKCirDmxDeVuEJD9zDQ29Y3XecAGNoL_IZE8uvZIvVog70x-wq1hHWko5UQzhu8DuXHqiW1DVDpEf4UxzEi-DvFVOTfbVP_5f-9Sra_NP5P2WOsXrmsmS9n6IHs2vB10Vrqj5oU0r6sHcnUEs525NbRK16YLSVHrfEeuOJ9pgI9VAmZaBkyqCw7aWAGrWh_WptCtfaBKaCuCOJNdVEyM66a78MyPGZM7a8wEv1655O7HIJKQQcO-jHWi0U7vZx6UB5WxX9nvB9yx8tl9zyD3HMsjBOkla33_3AjVvreJ1mGAvCu_EEEDXnRDQ512bTxqDodE4Epb9Qm5Xbls6my0L-qQIAZGJ6Z8l5RCpOP08q-6oRRHBmPxQ2vrJJnYIxFnXNyR9b31DSBvUQukWO70Fra2fXEhvKdUTja5_jPRq-OfuzfMG5Qb8QRdyRYE4Tne2RMPPyuwJ3O6ufEiao1ptxHalwZDcW-NA8CIphLGHj5_8pT6k0DjKzX_L1l_33c5HNHzUvS362EMon5ycJAxRmU3AbJFae2_Y7IOlnlywyoeRvnPS8cwZVSoNTt_hCH8_YM36KdMfaDqanU28eQky0Vucj8pceoHunSefNg8v58U1aM9YU61ijIUShNcUosDH0UnQYviL-COkSBIQkyMvYbspaVU6ahUZ1Tst3k0Z8plUx-IKVD8inbifieyF_wUh3s-tQJg1_Kwno06H5hlQ3C3eIt1VmjvKdnyuwOTL5eDAlUWu5UBekMxzkMZUhFxHOjSFZytVxhCAcUfw-8aMO0DSUFMWUJ31LvW6X3cN7SfPfxl0sEkz3LAhtBc6AH2ED-hOfAqyu8Ei0sfsuyNsRE_sDIJqb6mny159Msuf7r3_lOTUzLlYtvEb6mRnJkzON-HFAAmFE6yQC_l1x7aPWyJWx7IWFpH8XOMC7c0H9y7Fhnk7yWj6o_VO9zk4xvX_7g1O6rGs0fNXYQPMN75gUcTh24lNtWbcO4P9eV8tDN0PfHjTRqdNYJwL9Xq7hT1aKZbtXxDg0Gw1ZGYorFEiw0y_tsDf_qCo5tOypMtO_AhCYequMzX2BygQ1eyVvn_VQKDzZECjEGOddEchLz9ljeL_7N9i7PzsBAyNOmYu_jZe8dChQwv52TV1uDEQJY_sks9gzSyEbJQS1OgftvdY2ovJ3-tbN6vah7URicJSwL5jgrpZTG779NFb5uywaX6EBGZ03-93VMRfMcK-rrBhwZegx2G8-YcvJPS7xyYA4SvehghlfhBr-FmkIBIukv15sh92AvnXTKExmXhV8RGlMABM9GdAo4XNP9I_xXYZh75ew2jP6l-64MIDh47vVxInlTfPpHJegRGk1PYx9zx18rkEY7TP1OkEzpknijKPKkrV-ttO0pVa7-aNE4v1U5s6HBIQd3S9E2wjEA_mKMDEkedI8kHJNGkzbgZ95Xqm521VGIn-VflOhz1MTR2kCsj5c2E_0Nm0dA9b5qyWPftxq2hauT7C637p-4-T410s8UJkGpZX1XNitCCUKdzTFb1jgC0GZGNUUuZ5fkVnlL9GtAZOOom7xtByTVeoER-8x8vOQxF2PiwcurtNpenl-bm861DoV2_0Tz9VtPa2AQO0YGYPE7AScR-cvAZg5VLgAh_xztg792gTkhsfRoVATBADBOt4VDVQJfhTeH_rfxfSnK96eDANS-lphZAjyK2-tfQ2432B7agl73B-afHUlIJ5SsOLHiZzoNB7X8Q1XbzOny6aBNhGjSSlwk1kIJBwOOoTawXlDbtIh7_9ClhC5TmSNLn4poazG3FMpEA4mJBHzghK9qoCjG7KjDrmLvbWiVghSm0r5E0zO0uZG8Kod0Q1JnMX-4mhxjIbHDvXpWLn3qXJRwoyO_c_JngcK8hVZka2hPlO635AjKYbIJRGcmq07wM_6RS5WJsKvGeDOtuefZWAdruCr14uHMbg-z80NUbHOWjHAbN-fRqt07E8wJV04T4qT0d3VpRIZIrkNIRT7jeXHQ7w0vM3C5czOJGNQj0Mt2l6Cx9ysBmGUv6WtL-HQ587iBIvSmDyqedgGZUbHmuqBQINRgAghpFSzRu5HpmPNA5VrU1z3Q8cjmn5TRKu5C_3Z_ymviGpjWGmgBeDHQs0xlPCN6RQGIJMzdDpBV3eXL8YT7t5YEgul6hH4viET5YbuwlWiUkYXfqSBvjT9-YqBVTJh8KpdBUG05z3TryL-gB2V3wmnQAORydHGsi8lKlKWmC3MTNY1R1yCHAaW--nL7Tk3Ja4KzJsYusDoZi5YfTdzYoEbVnRlOTuKLctopLaSiZZ5b6E0CdwKfXRWVeOOcjpI2ArKOWWMnZX07bd8V3_2JKrPSwz22W2HIjCy2eGZ_RH5dZq25_Ok9SBAAoX63sX2dVvVbEwyoE-oP-uttq5l5Tr1v-bU0OJrfRm3VEHF2pTuUeZtn_Kh9q0F7OCBIw0C54ENYne-cDRSEGW2U9MMZ9EEmtOt_1b4tHZH-SwJAp42737WdaYKDcR8rtdPYsyOeTpm8gtWI2-dx-u3AozWYLBw8Lv6EnnFZ-13QsnIMMw6dKyYPKLw8lGNqWGlbxLGAE76_hKEfBm8flRvl27jY6N7mZt3wWkxfyD9RUyHkFk_uSB8O5vBUms1XfcDUTVJmkMkYYuAMEQeoyMXzroVAPHMYRqIEz43KZX0lwMsASaiUpYZ73In9SL_Xbi1T26Ii0PtgQh6CDVRCGH5l16g-Lv4pQq1gynjqMISn-p4K27ItYnhesQlUQ4rcI0FqPT9Wgkt0w-aBThnfpN1KtXw3sd0zATpAXglFo.IBhh9W88uPJ6wMwuZgf_5kZXKh1ZszjVBdfDSLPeE-Q\",\"scope\":\"openid profile email offline_access ui.read ui.admin ui.preferences.read ui.preferences.write authority:tenants.read authority:tenants.write authority:users.read authority:users.write authority:roles.read authority:roles.write authority:clients.read authority:clients.write authority:tokens.read authority:tokens.revoke authority:branding.read authority:branding.write authority.audit.read graph:read sbom:read scanner:read policy:read policy:simulate policy:author policy:review policy:approve policy:run policy:activate policy:audit policy:edit policy:operate policy:publish airgap:seal airgap:status:read orch:read orch:operate orch:quota analytics.read advisory:read advisory-ai:view advisory-ai:operate vex:read vexhub:read exceptions:read exceptions:approve aoc:verify findings:read release:read release:write release:publish scheduler:read scheduler:operate notify.viewer notify.operator notify.admin notify.escalate evidence:read export.viewer export.operator export.admin vuln:view vuln:investigate vuln:operate vuln:audit platform.context.read platform.context.write doctor:run doctor:admin ops.health integration:read integration:write integration:operate packs.read packs.write packs.run packs.approve registry.admin timeline:read timeline:write trust:read trust:write trust:admin signer:read signer:sign signer:rotate signer:admin\",\"expiresAtEpochMs\":1776599471290},\"identity\":{\"subject\":\"f945f00811f44f008058268a264ed015\",\"roles\":[],\"idToken\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6IjdLUks4OUw1UTNESkVQR09IV1NPSDNMVDktTVdNWVVJRS1aSU1VX1EiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2F1dGhvcml0eS5zdGVsbGEtb3BzLmxvY2FsLyIsImV4cCI6MTc3NjYwMDk3MiwiaWF0IjoxNzc2NTk3NjcyLCJhdWQiOiJzdGVsbGEtb3BzLXVpIiwic3ViIjoiZjk0NWYwMDgxMWY0NGYwMDgwNTgyNjhhMjY0ZWQwMTUiLCJhenAiOiJzdGVsbGEtb3BzLXVpIiwibm9uY2UiOiI3NzIxM2QzMS0wOTY3LTQ0MTItYjE1Ny1hM2RhN2Q3NDZkNjIiLCJhdF9oYXNoIjoiSElrZ1l3ZGZyc213S1B1ZTdjdW1wZyJ9.A9Mlpx8wxmuVLEl2P2hBYs8TXpgLLsAkfr2ywoOj-XBdgOWjjoKx7Uc0S6JqXZjj0aAEtiFTX0pT5QvWwWUhRzKIk_osSIt5eim0lyY3130O1b-zK4xJxLr98ukT-ikhcchJCxTjwyE_m8Mx3ZIyxj52nrWZmFEhx6is2BFgA8OXZmk6UgMFi2PKYjKP9qy_kz_ZcdpHjyaxgU4nyB8giXPWQjBEjZ3bYdotn5BUMQQ4i3NVT-UeriLZnHDb2D147WL0E7KfzoZznkNpKaCnDUo_09MSVXPKhRXqNaH9DYQQ8pMPCcVMDHxMjUEd5ycvAl8LJVSy32-1l1ix9lgJTQ\"},\"dpopKeyThumbprint\":\"oE_VLfMwaUO1GgDCBKUb__qedb99FPz82TOxT8HJZTQ\",\"issuedAtEpochMs\":1776597671291,\"tenantId\":\"default\",\"scopes\":[\"advisory-ai:operate\",\"advisory-ai:view\",\"advisory:read\",\"airgap:seal\",\"airgap:status:read\",\"analytics.read\",\"aoc:verify\",\"authority.audit.read\",\"authority:branding.read\",\"authority:branding.write\",\"authority:clients.read\",\"authority:clients.write\",\"authority:roles.read\",\"authority:roles.write\",\"authority:tenants.read\",\"authority:tenants.write\",\"authority:tokens.read\",\"authority:tokens.revoke\",\"authority:users.read\",\"authority:users.write\",\"doctor:admin\",\"doctor:run\",\"email\",\"evidence:read\",\"exceptions:approve\",\"exceptions:read\",\"export.admin\",\"export.operator\",\"export.viewer\",\"findings:read\",\"graph:read\",\"integration:operate\",\"integration:read\",\"integration:write\",\"notify.admin\",\"notify.escalate\",\"notify.operator\",\"notify.viewer\",\"offline_access\",\"openid\",\"ops.health\",\"orch:operate\",\"orch:quota\",\"orch:read\",\"packs.approve\",\"packs.read\",\"packs.run\",\"packs.write\",\"platform.context.read\",\"platform.context.write\",\"policy:activate\",\"policy:approve\",\"policy:audit\",\"policy:author\",\"policy:edit\",\"policy:operate\",\"policy:publish\",\"policy:read\",\"policy:review\",\"policy:run\",\"policy:simulate\",\"profile\",\"registry.admin\",\"release:publish\",\"release:read\",\"release:write\",\"sbom:read\",\"scanner:read\",\"scheduler:operate\",\"scheduler:read\",\"signer:admin\",\"signer:read\",\"signer:rotate\",\"signer:sign\",\"timeline:read\",\"timeline:write\",\"trust:admin\",\"trust:read\",\"trust:write\",\"ui.admin\",\"ui.preferences.read\",\"ui.preferences.write\",\"ui.read\",\"vex:read\",\"vexhub:read\",\"vuln:audit\",\"vuln:investigate\",\"vuln:operate\",\"vuln:view\"],\"audiences\":[],\"authenticationTimeEpochMs\":1776597671000,\"freshAuthActive\":false,\"freshAuthExpiresAtEpochMs\":null}" + ], + [ + "stellaops.helper.preferences", + "{\"dismissed\":false,\"tooltipsMuted\":false,\"mutedPages\":[],\"mutedTipIds\":[],\"seenPages\":[],\"tipIndex\":{},\"dismissedBanners\":[],\"seenHelpPages\":[],\"pageHelpOpen\":{},\"pageHelpDismissedGlobal\":false,\"pageHelpDismissedPages\":[]}" + ], + [ + "stellaops.content-width", + "centered" + ], + [ + "stellaops.assistant.state", + "{\"seenRoutes\":[],\"completedTours\":[],\"tipPositions\":{},\"dismissed\":false}" + ], + [ + "stellaops.theme", + "system" + ], + [ + "stellaops.auth.session.info", + "{\"subject\":\"f945f00811f44f008058268a264ed015\",\"expiresAtEpochMs\":1776599471290,\"issuedAtEpochMs\":1776597671291,\"dpopKeyThumbprint\":\"oE_VLfMwaUO1GgDCBKUb__qedb99FPz82TOxT8HJZTQ\",\"tenantId\":\"default\"}" + ], + [ + "stellaops.sidebar.preferences", + "{\"sidebarCollapsed\":false,\"collapsedGroups\":[\"evidence\",\"setup-admin\"],\"collapsedSections\":[]}" + ] + ], + "sessionStorageEntries": [ + [ + "stellaops.auth.session.full", + "{\"tokens\":{\"accessToken\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6IjdLUks4OUw1UTNESkVQR09IV1NPSDNMVDktTVdNWVVJRS1aSU1VX1EiLCJ0eXAiOiJhdCtqd3QifQ.eyJpc3MiOiJodHRwczovL2F1dGhvcml0eS5zdGVsbGEtb3BzLmxvY2FsLyIsImV4cCI6MTc3NjU5OTQ3MiwiaWF0IjoxNzc2NTk3NjcyLCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIGVtYWlsIG9mZmxpbmVfYWNjZXNzIHVpLnJlYWQgdWkuYWRtaW4gdWkucHJlZmVyZW5jZXMucmVhZCB1aS5wcmVmZXJlbmNlcy53cml0ZSBhdXRob3JpdHk6dGVuYW50cy5yZWFkIGF1dGhvcml0eTp0ZW5hbnRzLndyaXRlIGF1dGhvcml0eTp1c2Vycy5yZWFkIGF1dGhvcml0eTp1c2Vycy53cml0ZSBhdXRob3JpdHk6cm9sZXMucmVhZCBhdXRob3JpdHk6cm9sZXMud3JpdGUgYXV0aG9yaXR5OmNsaWVudHMucmVhZCBhdXRob3JpdHk6Y2xpZW50cy53cml0ZSBhdXRob3JpdHk6dG9rZW5zLnJlYWQgYXV0aG9yaXR5OnRva2Vucy5yZXZva2UgYXV0aG9yaXR5OmJyYW5kaW5nLnJlYWQgYXV0aG9yaXR5OmJyYW5kaW5nLndyaXRlIGF1dGhvcml0eS5hdWRpdC5yZWFkIGdyYXBoOnJlYWQgc2JvbTpyZWFkIHNjYW5uZXI6cmVhZCBwb2xpY3k6cmVhZCBwb2xpY3k6c2ltdWxhdGUgcG9saWN5OmF1dGhvciBwb2xpY3k6cmV2aWV3IHBvbGljeTphcHByb3ZlIHBvbGljeTpydW4gcG9saWN5OmFjdGl2YXRlIHBvbGljeTphdWRpdCBwb2xpY3k6ZWRpdCBwb2xpY3k6b3BlcmF0ZSBwb2xpY3k6cHVibGlzaCBhaXJnYXA6c2VhbCBhaXJnYXA6c3RhdHVzOnJlYWQgb3JjaDpyZWFkIG9yY2g6b3BlcmF0ZSBvcmNoOnF1b3RhIGFuYWx5dGljcy5yZWFkIGFkdmlzb3J5OnJlYWQgYWR2aXNvcnktYWk6dmlldyBhZHZpc29yeS1haTpvcGVyYXRlIHZleDpyZWFkIHZleGh1YjpyZWFkIGV4Y2VwdGlvbnM6cmVhZCBleGNlcHRpb25zOmFwcHJvdmUgYW9jOnZlcmlmeSBmaW5kaW5nczpyZWFkIHJlbGVhc2U6cmVhZCByZWxlYXNlOndyaXRlIHJlbGVhc2U6cHVibGlzaCBzY2hlZHVsZXI6cmVhZCBzY2hlZHVsZXI6b3BlcmF0ZSBub3RpZnkudmlld2VyIG5vdGlmeS5vcGVyYXRvciBub3RpZnkuYWRtaW4gbm90aWZ5LmVzY2FsYXRlIGV2aWRlbmNlOnJlYWQgZXhwb3J0LnZpZXdlciBleHBvcnQub3BlcmF0b3IgZXhwb3J0LmFkbWluIHZ1bG46dmlldyB2dWxuOmludmVzdGlnYXRlIHZ1bG46b3BlcmF0ZSB2dWxuOmF1ZGl0IHBsYXRmb3JtLmNvbnRleHQucmVhZCBwbGF0Zm9ybS5jb250ZXh0LndyaXRlIGRvY3RvcjpydW4gZG9jdG9yOmFkbWluIG9wcy5oZWFsdGggaW50ZWdyYXRpb246cmVhZCBpbnRlZ3JhdGlvbjp3cml0ZSBpbnRlZ3JhdGlvbjpvcGVyYXRlIHBhY2tzLnJlYWQgcGFja3Mud3JpdGUgcGFja3MucnVuIHBhY2tzLmFwcHJvdmUgcmVnaXN0cnkuYWRtaW4gdGltZWxpbmU6cmVhZCB0aW1lbGluZTp3cml0ZSB0cnVzdDpyZWFkIHRydXN0OndyaXRlIHRydXN0OmFkbWluIHNpZ25lcjpyZWFkIHNpZ25lcjpzaWduIHNpZ25lcjpyb3RhdGUgc2lnbmVyOmFkbWluIiwianRpIjoiOWJhYWRkZmUtOGVhYi00YjdlLWIxNDAtYjdkYmM2ZGY0NDY1Iiwic3ViIjoiZjk0NWYwMDgxMWY0NGYwMDgwNTgyNjhhMjY0ZWQwMTUiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiIsInN0ZWxsYW9wczp0ZW5hbnQiOiJkZWZhdWx0IiwiYXV0aF90aW1lIjoxNzc2NTk3NjcxLCJvaV9wcnN0Ijoic3RlbGxhLW9wcy11aSIsImNsaWVudF9pZCI6InN0ZWxsYS1vcHMtdWkifQ.LTsaW7eUNYBfwjxklmhF24qfW9BP1Jco03vZppZeVJyToGF0mT0_Sr1vdfa_spRIlPUVYjDrRHhAIMuAQak350DXzJqF9nY4kvhQzW07loyB5ASCdt1IBijRqjXVp8nGVVP5_pGZiLbX34AxRRbfrLE7KwRFkf0NZaochfBhRjqoLN2uRw-IIGkJMNthFae8qPmNvFvDaUruygUDIXv2yUVS_NnXXYJDOy9BC35akAlEqsEG-MGSomuUbo-YeS4hjpBG7mF7LX4UiUYFkePmRWCKVDJDPYExPp3j3n0cMQaqQci1OiKvbX3jJYCM1wkMXSV28ZeRYC9ZjNZVJgG76Q\",\"tokenType\":\"Bearer\",\"refreshToken\":\"eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZDQkMtSFM1MTIiLCJraWQiOiJZV0pPRVlCSzU3UEpSQTFTSFhFWkM3NEFRQktRWFdEQjZaWlVPV0dVIiwidHlwIjoib2lfcmVmdCtqd3QiLCJjdHkiOiJKV1QifQ.yCZRh1VwHde8Mw6susd190XjLjX4CYt4XMW_es2vU1YFnLm1gOjya3tVSPjNnzOW57EWn_0q4nTSocTNJFV8JDGfR5S92xRbvCBkV54MKWgILqsoRY04eU1Q1fszPybTZqTxj-rX-IMiAJ0EXqxtTLJdxMFN5sdQYcKOMdZV6S6dBArSEMdfdVQKCtuEAKdUEpILsOcI4S4x3B8z8tlw5DsCdiFgW3M2mijFrKppiPGiKDnN1upeZ6StoaUu-Ijnw_hZIo2vbBXINAe-5p_naJ-6JxAGP_UlCPNtWvIZuIA6qV-kldCDE8kWARn1rnXCqB5Zjg3wwulVjlh5u7__0w.snJwpNse2FWAMfsSA08jSg.4Lngki9KU2RPD0KkchsxkAtAaITJR0Hv_gvL4Rfn3-UOif2kvIGk3iX8G3Xo0gvdem7x0_70EMM0jLgZT-tvxc5gm10lbuD3R1nCefcgJcAD3zBN-UKKbNRVe8VxTWNNQXhK8bAEvphjgR3yhQt6LyxQw2-arjkz947kRPctj9UmRQNItD4oN0KOchEmuGIYhCzi3SKsJxbcembWNPmFU9DKJqFdRotzCy8bMlYBY2Wh744Ue3AhuJeg1YuYd6giQZNowlkuP10O27FwATpfC4iE6Y1rbMFj8ZIXMJn5vwz1gh51n4NCp-qjQOeYCcECV4E3ITrQprIjF2fCo9HlRTNETktx13s31trD1sCpTunYRnLrcCaEJdMDLVsVRXxIrMF4thEFwU3pOkjMHgD27fAGfoLSJfghU59938MuBBW8Zx51NE6Ylza9G0giTb0TVSE0ajaGextQW5DC5hLmbx8kk3-d3WfslYv2nLflwh4JtSZsXDsumnivWG_YXU1SvpL7MUdr9a8kWeUkA6NTQhXVLb-38N-BQBzOiU8TGaf5FQ86B8BEi8z3w-mBwM2hrOR7n1AgmrqmJXM7YbvkUBLZPCZX_F2ekPNItr8HQEbPAVGrftAwyp7gLWSz_7Ysypy_88LZnTTD1Xy9GBtPouveXtedfEMMY6vuDoW2FURwibxwv0PbLxFk96kHUX8imEMaKKMZSyvP1xe93-xyyw4XMdwgckJluU0gviMUtJDbT_Ew1cHzS0yfCVhKawfP0TjAdBeDhAQmhJ6WDzRHSxZ3vs_2yVxbcB7ijqhGsL8YbyBOeBJqUO0tZt-dvZmK0auac24b_yFCClglWUUg3lGuG2OsUNwep6EfYkv__V3ewTOZH0Fr4Xzh5RqU9ChvSGrU1hR4SXjMsfHLnzggPzPionvnNlPQe3QpMLaBsUYDWBiyYHa2EodXwk6lQfU0ur9-BrCoo8iEy_VCTa_epa-6hdQtVHq1MYGfYf2hXZkMBdS_dEgsMjwTUynhAeiqv11VCd6wkttc_atN5kAMWydBwMqMvW9vdwart1CGTFNpi_9moVkZc2nNEUfBLFARey_Tc0Ry4nkCYRzePJ65sY-Bn1u_xdDQPgneoP7locVhqF6G040K0ZKBkQQaUwPO20KbWHlGuPphOk7YW4GPo0eD1k34PlcXjlaNH6VZxW9yUGUsa6BG52eP9SggiowJDGkRzuOknLBRCuaRTopLayPRaSrPu3rnYy3_ggvjGuIQDH4nsikK_SANDA8yJEfl6KQJbRJyNw3nxi8kQgRcjctPfKDmHUsWS0Q7RzoN_4JUzM1mcT74ciBJ26gx72iwAJQYPWzMvazEvgop2Wv4ii8aaHccbkpDaUd85I-gv0G5hZyIVRB_pDLvL5kw0Q2VVsIeBtcX5_RN6e8PJTlNAWxdlRWwe9-vHAnwsIIqi4BlA9-URdj2flUxZwitLzfbPOkFUQ_QcJqUURRnlrmgUwbiYIZrCU76nuyfnuCpm1kkktajGHCq8Re86kjkGU28ZcuZ-uQ1ewIyHyartYNaaFuKZg4MoqGJySvf_gWReAWIC3vvxo5REgvj3h7MkJNI6Q4D0B3HT6MJ5dZpxQHocM_8tfIhFz9tuFsnhbbWcqsCNoDqh7XSkf66sLsBRN4Mg_te9r4HUa9I3o9IEWyyeIfM7GTW3tSUUFwB9hmLYlQDYUgZVO1glHckZQrMVVThZGcJ5hnYJ3UaSG6VvFV6EokTF3fDJgAyzkanqZ8sZMafWg_gqVQ_ldM6ck1rE9hnXs_ZfuJr43_9FotXDljE01TWKiCs1UUjf9G5BjZmenunKCirDmxDeVuEJD9zDQ29Y3XecAGNoL_IZE8uvZIvVog70x-wq1hHWko5UQzhu8DuXHqiW1DVDpEf4UxzEi-DvFVOTfbVP_5f-9Sra_NP5P2WOsXrmsmS9n6IHs2vB10Vrqj5oU0r6sHcnUEs525NbRK16YLSVHrfEeuOJ9pgI9VAmZaBkyqCw7aWAGrWh_WptCtfaBKaCuCOJNdVEyM66a78MyPGZM7a8wEv1655O7HIJKQQcO-jHWi0U7vZx6UB5WxX9nvB9yx8tl9zyD3HMsjBOkla33_3AjVvreJ1mGAvCu_EEEDXnRDQ512bTxqDodE4Epb9Qm5Xbls6my0L-qQIAZGJ6Z8l5RCpOP08q-6oRRHBmPxQ2vrJJnYIxFnXNyR9b31DSBvUQukWO70Fra2fXEhvKdUTja5_jPRq-OfuzfMG5Qb8QRdyRYE4Tne2RMPPyuwJ3O6ufEiao1ptxHalwZDcW-NA8CIphLGHj5_8pT6k0DjKzX_L1l_33c5HNHzUvS362EMon5ycJAxRmU3AbJFae2_Y7IOlnlywyoeRvnPS8cwZVSoNTt_hCH8_YM36KdMfaDqanU28eQky0Vucj8pceoHunSefNg8v58U1aM9YU61ijIUShNcUosDH0UnQYviL-COkSBIQkyMvYbspaVU6ahUZ1Tst3k0Z8plUx-IKVD8inbifieyF_wUh3s-tQJg1_Kwno06H5hlQ3C3eIt1VmjvKdnyuwOTL5eDAlUWu5UBekMxzkMZUhFxHOjSFZytVxhCAcUfw-8aMO0DSUFMWUJ31LvW6X3cN7SfPfxl0sEkz3LAhtBc6AH2ED-hOfAqyu8Ei0sfsuyNsRE_sDIJqb6mny159Msuf7r3_lOTUzLlYtvEb6mRnJkzON-HFAAmFE6yQC_l1x7aPWyJWx7IWFpH8XOMC7c0H9y7Fhnk7yWj6o_VO9zk4xvX_7g1O6rGs0fNXYQPMN75gUcTh24lNtWbcO4P9eV8tDN0PfHjTRqdNYJwL9Xq7hT1aKZbtXxDg0Gw1ZGYorFEiw0y_tsDf_qCo5tOypMtO_AhCYequMzX2BygQ1eyVvn_VQKDzZECjEGOddEchLz9ljeL_7N9i7PzsBAyNOmYu_jZe8dChQwv52TV1uDEQJY_sks9gzSyEbJQS1OgftvdY2ovJ3-tbN6vah7URicJSwL5jgrpZTG779NFb5uywaX6EBGZ03-93VMRfMcK-rrBhwZegx2G8-YcvJPS7xyYA4SvehghlfhBr-FmkIBIukv15sh92AvnXTKExmXhV8RGlMABM9GdAo4XNP9I_xXYZh75ew2jP6l-64MIDh47vVxInlTfPpHJegRGk1PYx9zx18rkEY7TP1OkEzpknijKPKkrV-ttO0pVa7-aNE4v1U5s6HBIQd3S9E2wjEA_mKMDEkedI8kHJNGkzbgZ95Xqm521VGIn-VflOhz1MTR2kCsj5c2E_0Nm0dA9b5qyWPftxq2hauT7C637p-4-T410s8UJkGpZX1XNitCCUKdzTFb1jgC0GZGNUUuZ5fkVnlL9GtAZOOom7xtByTVeoER-8x8vOQxF2PiwcurtNpenl-bm861DoV2_0Tz9VtPa2AQO0YGYPE7AScR-cvAZg5VLgAh_xztg792gTkhsfRoVATBADBOt4VDVQJfhTeH_rfxfSnK96eDANS-lphZAjyK2-tfQ2432B7agl73B-afHUlIJ5SsOLHiZzoNB7X8Q1XbzOny6aBNhGjSSlwk1kIJBwOOoTawXlDbtIh7_9ClhC5TmSNLn4poazG3FMpEA4mJBHzghK9qoCjG7KjDrmLvbWiVghSm0r5E0zO0uZG8Kod0Q1JnMX-4mhxjIbHDvXpWLn3qXJRwoyO_c_JngcK8hVZka2hPlO635AjKYbIJRGcmq07wM_6RS5WJsKvGeDOtuefZWAdruCr14uHMbg-z80NUbHOWjHAbN-fRqt07E8wJV04T4qT0d3VpRIZIrkNIRT7jeXHQ7w0vM3C5czOJGNQj0Mt2l6Cx9ysBmGUv6WtL-HQ587iBIvSmDyqedgGZUbHmuqBQINRgAghpFSzRu5HpmPNA5VrU1z3Q8cjmn5TRKu5C_3Z_ymviGpjWGmgBeDHQs0xlPCN6RQGIJMzdDpBV3eXL8YT7t5YEgul6hH4viET5YbuwlWiUkYXfqSBvjT9-YqBVTJh8KpdBUG05z3TryL-gB2V3wmnQAORydHGsi8lKlKWmC3MTNY1R1yCHAaW--nL7Tk3Ja4KzJsYusDoZi5YfTdzYoEbVnRlOTuKLctopLaSiZZ5b6E0CdwKfXRWVeOOcjpI2ArKOWWMnZX07bd8V3_2JKrPSwz22W2HIjCy2eGZ_RH5dZq25_Ok9SBAAoX63sX2dVvVbEwyoE-oP-uttq5l5Tr1v-bU0OJrfRm3VEHF2pTuUeZtn_Kh9q0F7OCBIw0C54ENYne-cDRSEGW2U9MMZ9EEmtOt_1b4tHZH-SwJAp42737WdaYKDcR8rtdPYsyOeTpm8gtWI2-dx-u3AozWYLBw8Lv6EnnFZ-13QsnIMMw6dKyYPKLw8lGNqWGlbxLGAE76_hKEfBm8flRvl27jY6N7mZt3wWkxfyD9RUyHkFk_uSB8O5vBUms1XfcDUTVJmkMkYYuAMEQeoyMXzroVAPHMYRqIEz43KZX0lwMsASaiUpYZ73In9SL_Xbi1T26Ii0PtgQh6CDVRCGH5l16g-Lv4pQq1gynjqMISn-p4K27ItYnhesQlUQ4rcI0FqPT9Wgkt0w-aBThnfpN1KtXw3sd0zATpAXglFo.IBhh9W88uPJ6wMwuZgf_5kZXKh1ZszjVBdfDSLPeE-Q\",\"scope\":\"openid profile email offline_access ui.read ui.admin ui.preferences.read ui.preferences.write authority:tenants.read authority:tenants.write authority:users.read authority:users.write authority:roles.read authority:roles.write authority:clients.read authority:clients.write authority:tokens.read authority:tokens.revoke authority:branding.read authority:branding.write authority.audit.read graph:read sbom:read scanner:read policy:read policy:simulate policy:author policy:review policy:approve policy:run policy:activate policy:audit policy:edit policy:operate policy:publish airgap:seal airgap:status:read orch:read orch:operate orch:quota analytics.read advisory:read advisory-ai:view advisory-ai:operate vex:read vexhub:read exceptions:read exceptions:approve aoc:verify findings:read release:read release:write release:publish scheduler:read scheduler:operate notify.viewer notify.operator notify.admin notify.escalate evidence:read export.viewer export.operator export.admin vuln:view vuln:investigate vuln:operate vuln:audit platform.context.read platform.context.write doctor:run doctor:admin ops.health integration:read integration:write integration:operate packs.read packs.write packs.run packs.approve registry.admin timeline:read timeline:write trust:read trust:write trust:admin signer:read signer:sign signer:rotate signer:admin\",\"expiresAtEpochMs\":1776599471290},\"identity\":{\"subject\":\"f945f00811f44f008058268a264ed015\",\"roles\":[],\"idToken\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6IjdLUks4OUw1UTNESkVQR09IV1NPSDNMVDktTVdNWVVJRS1aSU1VX1EiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2F1dGhvcml0eS5zdGVsbGEtb3BzLmxvY2FsLyIsImV4cCI6MTc3NjYwMDk3MiwiaWF0IjoxNzc2NTk3NjcyLCJhdWQiOiJzdGVsbGEtb3BzLXVpIiwic3ViIjoiZjk0NWYwMDgxMWY0NGYwMDgwNTgyNjhhMjY0ZWQwMTUiLCJhenAiOiJzdGVsbGEtb3BzLXVpIiwibm9uY2UiOiI3NzIxM2QzMS0wOTY3LTQ0MTItYjE1Ny1hM2RhN2Q3NDZkNjIiLCJhdF9oYXNoIjoiSElrZ1l3ZGZyc213S1B1ZTdjdW1wZyJ9.A9Mlpx8wxmuVLEl2P2hBYs8TXpgLLsAkfr2ywoOj-XBdgOWjjoKx7Uc0S6JqXZjj0aAEtiFTX0pT5QvWwWUhRzKIk_osSIt5eim0lyY3130O1b-zK4xJxLr98ukT-ikhcchJCxTjwyE_m8Mx3ZIyxj52nrWZmFEhx6is2BFgA8OXZmk6UgMFi2PKYjKP9qy_kz_ZcdpHjyaxgU4nyB8giXPWQjBEjZ3bYdotn5BUMQQ4i3NVT-UeriLZnHDb2D147WL0E7KfzoZznkNpKaCnDUo_09MSVXPKhRXqNaH9DYQQ8pMPCcVMDHxMjUEd5ycvAl8LJVSy32-1l1ix9lgJTQ\"},\"dpopKeyThumbprint\":\"oE_VLfMwaUO1GgDCBKUb__qedb99FPz82TOxT8HJZTQ\",\"issuedAtEpochMs\":1776597671291,\"tenantId\":\"default\",\"scopes\":[\"advisory-ai:operate\",\"advisory-ai:view\",\"advisory:read\",\"airgap:seal\",\"airgap:status:read\",\"analytics.read\",\"aoc:verify\",\"authority.audit.read\",\"authority:branding.read\",\"authority:branding.write\",\"authority:clients.read\",\"authority:clients.write\",\"authority:roles.read\",\"authority:roles.write\",\"authority:tenants.read\",\"authority:tenants.write\",\"authority:tokens.read\",\"authority:tokens.revoke\",\"authority:users.read\",\"authority:users.write\",\"doctor:admin\",\"doctor:run\",\"email\",\"evidence:read\",\"exceptions:approve\",\"exceptions:read\",\"export.admin\",\"export.operator\",\"export.viewer\",\"findings:read\",\"graph:read\",\"integration:operate\",\"integration:read\",\"integration:write\",\"notify.admin\",\"notify.escalate\",\"notify.operator\",\"notify.viewer\",\"offline_access\",\"openid\",\"ops.health\",\"orch:operate\",\"orch:quota\",\"orch:read\",\"packs.approve\",\"packs.read\",\"packs.run\",\"packs.write\",\"platform.context.read\",\"platform.context.write\",\"policy:activate\",\"policy:approve\",\"policy:audit\",\"policy:author\",\"policy:edit\",\"policy:operate\",\"policy:publish\",\"policy:read\",\"policy:review\",\"policy:run\",\"policy:simulate\",\"profile\",\"registry.admin\",\"release:publish\",\"release:read\",\"release:write\",\"sbom:read\",\"scanner:read\",\"scheduler:operate\",\"scheduler:read\",\"signer:admin\",\"signer:read\",\"signer:rotate\",\"signer:sign\",\"timeline:read\",\"timeline:write\",\"trust:admin\",\"trust:read\",\"trust:write\",\"ui.admin\",\"ui.preferences.read\",\"ui.preferences.write\",\"ui.read\",\"vex:read\",\"vexhub:read\",\"vuln:audit\",\"vuln:investigate\",\"vuln:operate\",\"vuln:view\"],\"audiences\":[],\"authenticationTimeEpochMs\":1776597671000,\"freshAuthActive\":false,\"freshAuthExpiresAtEpochMs\":null}" + ], + [ + "stellaops:wasEverAuth", + "true" + ] + ] + }, + "sessionStatus": { + "hasFullSession": true, + "hasSessionInfo": true + }, + "events": { + "consoleErrors": [], + "requestFailures": [], + "responseErrors": [] + }, + "statePath": "C:\\dev\\New folder\\git.stella-ops.org\\src\\Web\\StellaOps.Web\\output\\playwright\\live-setup-wizard-first-run-bootstrap.state.json", + "screenshotPath": null +} diff --git a/src/Web/StellaOps.Web/output/playwright/live-setup-wizard-first-run-bootstrap.json b/src/Web/StellaOps.Web/output/playwright/live-setup-wizard-first-run-bootstrap.json new file mode 100644 index 000000000..f92845a5b --- /dev/null +++ b/src/Web/StellaOps.Web/output/playwright/live-setup-wizard-first-run-bootstrap.json @@ -0,0 +1,104 @@ +{ + "generatedAtUtc": "2026-04-19T11:21:17.727Z", + "baseUrl": "https://stella-ops.local", + "adminUsername": "admin", + "adminEmail": "admin@stella-ops.local", + "mode": "already-configured", + "steps": [ + { + "action": "setup-entry-after-authentication", + "ok": true, + "resolvedSurface": "authenticated-reconfigure", + "initialAnonymousSession": { + "status": 401, + "ok": false, + "sessionId": null, + "currentStepId": "", + "sessionStatus": "", + "completedAtUtc": null, + "steps": [], + "raw": { + "status": 401, + "ok": false, + "payload": null, + "bodyText": "" + } + }, + "authenticatedSetupSessionProbe": { + "status": 401, + "ok": false, + "sessionId": null, + "currentStepId": "", + "sessionStatus": "", + "completedAtUtc": null, + "steps": [], + "raw": { + "status": 401, + "ok": false, + "payload": null, + "bodyText": "" + } + }, + "initialSnapshot": { + "label": "initial", + "url": "https://stella-ops.local/setup-wizard/wizard", + "title": "Wizard - StellaOps", + "heading": "Setup", + "alerts": [], + "visibleButtons": [ + "Apply and ContinueWelcome" + ] + }, + "snapshot": { + "label": "setup-already-complete", + "url": "https://stella-ops.local/setup-wizard/wizard", + "title": "Wizard - StellaOps", + "heading": "Setup", + "alerts": [], + "visibleButtons": [ + "Validate Connection", + "Apply and ContinueValkey/Redis Cache" + ] + } + } + ], + "runtime": { + "consoleErrors": [], + "pageErrors": [], + "requestFailures": [], + "responseErrors": [ + { + "page": "https://stella-ops.local/setup-wizard/wizard", + "method": "POST", + "status": 401, + "url": "https://stella-ops.local/api/v1/setup/sessions" + } + ], + "setupApiEvents": [ + { + "page": "https://stella-ops.local/setup-wizard/wizard", + "method": "POST", + "status": 401, + "url": "https://stella-ops.local/api/v1/setup/sessions" + }, + { + "page": "https://stella-ops.local/setup-wizard/wizard", + "method": "POST", + "status": 201, + "url": "https://stella-ops.local/api/v1/setup/sessions" + }, + { + "page": "https://stella-ops.local/setup-wizard/wizard", + "method": "POST", + "status": 200, + "url": "https://stella-ops.local/api/v1/setup/sessions/setup-installation-20260419111949/steps/database/checks/run" + } + ] + }, + "postSetupAuthentication": { + "finalUrl": "https://stella-ops.local/?tenant=default®ions=apac,eu-west,us-east", + "title": "Dashboard - StellaOps", + "reportPath": "C:\\dev\\New folder\\git.stella-ops.org\\src\\Web\\StellaOps.Web\\output\\playwright\\live-setup-wizard-first-run-bootstrap.auth.json", + "statePath": "C:\\dev\\New folder\\git.stella-ops.org\\src\\Web\\StellaOps.Web\\output\\playwright\\live-setup-wizard-first-run-bootstrap.state.json" + } +} diff --git a/src/Web/StellaOps.Web/output/playwright/live-setup-wizard-first-run-bootstrap.state.json b/src/Web/StellaOps.Web/output/playwright/live-setup-wizard-first-run-bootstrap.state.json new file mode 100644 index 000000000..787fa9bd7 --- /dev/null +++ b/src/Web/StellaOps.Web/output/playwright/live-setup-wizard-first-run-bootstrap.state.json @@ -0,0 +1,38 @@ +{ + "cookies": [], + "origins": [ + { + "origin": "https://stella-ops.local", + "localStorage": [ + { + "name": "stellaops.auth.session.full", + "value": "{\"tokens\":{\"accessToken\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6IjdLUks4OUw1UTNESkVQR09IV1NPSDNMVDktTVdNWVVJRS1aSU1VX1EiLCJ0eXAiOiJhdCtqd3QifQ.eyJpc3MiOiJodHRwczovL2F1dGhvcml0eS5zdGVsbGEtb3BzLmxvY2FsLyIsImV4cCI6MTc3NjU5OTQ3MiwiaWF0IjoxNzc2NTk3NjcyLCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIGVtYWlsIG9mZmxpbmVfYWNjZXNzIHVpLnJlYWQgdWkuYWRtaW4gdWkucHJlZmVyZW5jZXMucmVhZCB1aS5wcmVmZXJlbmNlcy53cml0ZSBhdXRob3JpdHk6dGVuYW50cy5yZWFkIGF1dGhvcml0eTp0ZW5hbnRzLndyaXRlIGF1dGhvcml0eTp1c2Vycy5yZWFkIGF1dGhvcml0eTp1c2Vycy53cml0ZSBhdXRob3JpdHk6cm9sZXMucmVhZCBhdXRob3JpdHk6cm9sZXMud3JpdGUgYXV0aG9yaXR5OmNsaWVudHMucmVhZCBhdXRob3JpdHk6Y2xpZW50cy53cml0ZSBhdXRob3JpdHk6dG9rZW5zLnJlYWQgYXV0aG9yaXR5OnRva2Vucy5yZXZva2UgYXV0aG9yaXR5OmJyYW5kaW5nLnJlYWQgYXV0aG9yaXR5OmJyYW5kaW5nLndyaXRlIGF1dGhvcml0eS5hdWRpdC5yZWFkIGdyYXBoOnJlYWQgc2JvbTpyZWFkIHNjYW5uZXI6cmVhZCBwb2xpY3k6cmVhZCBwb2xpY3k6c2ltdWxhdGUgcG9saWN5OmF1dGhvciBwb2xpY3k6cmV2aWV3IHBvbGljeTphcHByb3ZlIHBvbGljeTpydW4gcG9saWN5OmFjdGl2YXRlIHBvbGljeTphdWRpdCBwb2xpY3k6ZWRpdCBwb2xpY3k6b3BlcmF0ZSBwb2xpY3k6cHVibGlzaCBhaXJnYXA6c2VhbCBhaXJnYXA6c3RhdHVzOnJlYWQgb3JjaDpyZWFkIG9yY2g6b3BlcmF0ZSBvcmNoOnF1b3RhIGFuYWx5dGljcy5yZWFkIGFkdmlzb3J5OnJlYWQgYWR2aXNvcnktYWk6dmlldyBhZHZpc29yeS1haTpvcGVyYXRlIHZleDpyZWFkIHZleGh1YjpyZWFkIGV4Y2VwdGlvbnM6cmVhZCBleGNlcHRpb25zOmFwcHJvdmUgYW9jOnZlcmlmeSBmaW5kaW5nczpyZWFkIHJlbGVhc2U6cmVhZCByZWxlYXNlOndyaXRlIHJlbGVhc2U6cHVibGlzaCBzY2hlZHVsZXI6cmVhZCBzY2hlZHVsZXI6b3BlcmF0ZSBub3RpZnkudmlld2VyIG5vdGlmeS5vcGVyYXRvciBub3RpZnkuYWRtaW4gbm90aWZ5LmVzY2FsYXRlIGV2aWRlbmNlOnJlYWQgZXhwb3J0LnZpZXdlciBleHBvcnQub3BlcmF0b3IgZXhwb3J0LmFkbWluIHZ1bG46dmlldyB2dWxuOmludmVzdGlnYXRlIHZ1bG46b3BlcmF0ZSB2dWxuOmF1ZGl0IHBsYXRmb3JtLmNvbnRleHQucmVhZCBwbGF0Zm9ybS5jb250ZXh0LndyaXRlIGRvY3RvcjpydW4gZG9jdG9yOmFkbWluIG9wcy5oZWFsdGggaW50ZWdyYXRpb246cmVhZCBpbnRlZ3JhdGlvbjp3cml0ZSBpbnRlZ3JhdGlvbjpvcGVyYXRlIHBhY2tzLnJlYWQgcGFja3Mud3JpdGUgcGFja3MucnVuIHBhY2tzLmFwcHJvdmUgcmVnaXN0cnkuYWRtaW4gdGltZWxpbmU6cmVhZCB0aW1lbGluZTp3cml0ZSB0cnVzdDpyZWFkIHRydXN0OndyaXRlIHRydXN0OmFkbWluIHNpZ25lcjpyZWFkIHNpZ25lcjpzaWduIHNpZ25lcjpyb3RhdGUgc2lnbmVyOmFkbWluIiwianRpIjoiOWJhYWRkZmUtOGVhYi00YjdlLWIxNDAtYjdkYmM2ZGY0NDY1Iiwic3ViIjoiZjk0NWYwMDgxMWY0NGYwMDgwNTgyNjhhMjY0ZWQwMTUiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiIsInN0ZWxsYW9wczp0ZW5hbnQiOiJkZWZhdWx0IiwiYXV0aF90aW1lIjoxNzc2NTk3NjcxLCJvaV9wcnN0Ijoic3RlbGxhLW9wcy11aSIsImNsaWVudF9pZCI6InN0ZWxsYS1vcHMtdWkifQ.LTsaW7eUNYBfwjxklmhF24qfW9BP1Jco03vZppZeVJyToGF0mT0_Sr1vdfa_spRIlPUVYjDrRHhAIMuAQak350DXzJqF9nY4kvhQzW07loyB5ASCdt1IBijRqjXVp8nGVVP5_pGZiLbX34AxRRbfrLE7KwRFkf0NZaochfBhRjqoLN2uRw-IIGkJMNthFae8qPmNvFvDaUruygUDIXv2yUVS_NnXXYJDOy9BC35akAlEqsEG-MGSomuUbo-YeS4hjpBG7mF7LX4UiUYFkePmRWCKVDJDPYExPp3j3n0cMQaqQci1OiKvbX3jJYCM1wkMXSV28ZeRYC9ZjNZVJgG76Q\",\"tokenType\":\"Bearer\",\"refreshToken\":\"eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZDQkMtSFM1MTIiLCJraWQiOiJZV0pPRVlCSzU3UEpSQTFTSFhFWkM3NEFRQktRWFdEQjZaWlVPV0dVIiwidHlwIjoib2lfcmVmdCtqd3QiLCJjdHkiOiJKV1QifQ.yCZRh1VwHde8Mw6susd190XjLjX4CYt4XMW_es2vU1YFnLm1gOjya3tVSPjNnzOW57EWn_0q4nTSocTNJFV8JDGfR5S92xRbvCBkV54MKWgILqsoRY04eU1Q1fszPybTZqTxj-rX-IMiAJ0EXqxtTLJdxMFN5sdQYcKOMdZV6S6dBArSEMdfdVQKCtuEAKdUEpILsOcI4S4x3B8z8tlw5DsCdiFgW3M2mijFrKppiPGiKDnN1upeZ6StoaUu-Ijnw_hZIo2vbBXINAe-5p_naJ-6JxAGP_UlCPNtWvIZuIA6qV-kldCDE8kWARn1rnXCqB5Zjg3wwulVjlh5u7__0w.snJwpNse2FWAMfsSA08jSg.4Lngki9KU2RPD0KkchsxkAtAaITJR0Hv_gvL4Rfn3-UOif2kvIGk3iX8G3Xo0gvdem7x0_70EMM0jLgZT-tvxc5gm10lbuD3R1nCefcgJcAD3zBN-UKKbNRVe8VxTWNNQXhK8bAEvphjgR3yhQt6LyxQw2-arjkz947kRPctj9UmRQNItD4oN0KOchEmuGIYhCzi3SKsJxbcembWNPmFU9DKJqFdRotzCy8bMlYBY2Wh744Ue3AhuJeg1YuYd6giQZNowlkuP10O27FwATpfC4iE6Y1rbMFj8ZIXMJn5vwz1gh51n4NCp-qjQOeYCcECV4E3ITrQprIjF2fCo9HlRTNETktx13s31trD1sCpTunYRnLrcCaEJdMDLVsVRXxIrMF4thEFwU3pOkjMHgD27fAGfoLSJfghU59938MuBBW8Zx51NE6Ylza9G0giTb0TVSE0ajaGextQW5DC5hLmbx8kk3-d3WfslYv2nLflwh4JtSZsXDsumnivWG_YXU1SvpL7MUdr9a8kWeUkA6NTQhXVLb-38N-BQBzOiU8TGaf5FQ86B8BEi8z3w-mBwM2hrOR7n1AgmrqmJXM7YbvkUBLZPCZX_F2ekPNItr8HQEbPAVGrftAwyp7gLWSz_7Ysypy_88LZnTTD1Xy9GBtPouveXtedfEMMY6vuDoW2FURwibxwv0PbLxFk96kHUX8imEMaKKMZSyvP1xe93-xyyw4XMdwgckJluU0gviMUtJDbT_Ew1cHzS0yfCVhKawfP0TjAdBeDhAQmhJ6WDzRHSxZ3vs_2yVxbcB7ijqhGsL8YbyBOeBJqUO0tZt-dvZmK0auac24b_yFCClglWUUg3lGuG2OsUNwep6EfYkv__V3ewTOZH0Fr4Xzh5RqU9ChvSGrU1hR4SXjMsfHLnzggPzPionvnNlPQe3QpMLaBsUYDWBiyYHa2EodXwk6lQfU0ur9-BrCoo8iEy_VCTa_epa-6hdQtVHq1MYGfYf2hXZkMBdS_dEgsMjwTUynhAeiqv11VCd6wkttc_atN5kAMWydBwMqMvW9vdwart1CGTFNpi_9moVkZc2nNEUfBLFARey_Tc0Ry4nkCYRzePJ65sY-Bn1u_xdDQPgneoP7locVhqF6G040K0ZKBkQQaUwPO20KbWHlGuPphOk7YW4GPo0eD1k34PlcXjlaNH6VZxW9yUGUsa6BG52eP9SggiowJDGkRzuOknLBRCuaRTopLayPRaSrPu3rnYy3_ggvjGuIQDH4nsikK_SANDA8yJEfl6KQJbRJyNw3nxi8kQgRcjctPfKDmHUsWS0Q7RzoN_4JUzM1mcT74ciBJ26gx72iwAJQYPWzMvazEvgop2Wv4ii8aaHccbkpDaUd85I-gv0G5hZyIVRB_pDLvL5kw0Q2VVsIeBtcX5_RN6e8PJTlNAWxdlRWwe9-vHAnwsIIqi4BlA9-URdj2flUxZwitLzfbPOkFUQ_QcJqUURRnlrmgUwbiYIZrCU76nuyfnuCpm1kkktajGHCq8Re86kjkGU28ZcuZ-uQ1ewIyHyartYNaaFuKZg4MoqGJySvf_gWReAWIC3vvxo5REgvj3h7MkJNI6Q4D0B3HT6MJ5dZpxQHocM_8tfIhFz9tuFsnhbbWcqsCNoDqh7XSkf66sLsBRN4Mg_te9r4HUa9I3o9IEWyyeIfM7GTW3tSUUFwB9hmLYlQDYUgZVO1glHckZQrMVVThZGcJ5hnYJ3UaSG6VvFV6EokTF3fDJgAyzkanqZ8sZMafWg_gqVQ_ldM6ck1rE9hnXs_ZfuJr43_9FotXDljE01TWKiCs1UUjf9G5BjZmenunKCirDmxDeVuEJD9zDQ29Y3XecAGNoL_IZE8uvZIvVog70x-wq1hHWko5UQzhu8DuXHqiW1DVDpEf4UxzEi-DvFVOTfbVP_5f-9Sra_NP5P2WOsXrmsmS9n6IHs2vB10Vrqj5oU0r6sHcnUEs525NbRK16YLSVHrfEeuOJ9pgI9VAmZaBkyqCw7aWAGrWh_WptCtfaBKaCuCOJNdVEyM66a78MyPGZM7a8wEv1655O7HIJKQQcO-jHWi0U7vZx6UB5WxX9nvB9yx8tl9zyD3HMsjBOkla33_3AjVvreJ1mGAvCu_EEEDXnRDQ512bTxqDodE4Epb9Qm5Xbls6my0L-qQIAZGJ6Z8l5RCpOP08q-6oRRHBmPxQ2vrJJnYIxFnXNyR9b31DSBvUQukWO70Fra2fXEhvKdUTja5_jPRq-OfuzfMG5Qb8QRdyRYE4Tne2RMPPyuwJ3O6ufEiao1ptxHalwZDcW-NA8CIphLGHj5_8pT6k0DjKzX_L1l_33c5HNHzUvS362EMon5ycJAxRmU3AbJFae2_Y7IOlnlywyoeRvnPS8cwZVSoNTt_hCH8_YM36KdMfaDqanU28eQky0Vucj8pceoHunSefNg8v58U1aM9YU61ijIUShNcUosDH0UnQYviL-COkSBIQkyMvYbspaVU6ahUZ1Tst3k0Z8plUx-IKVD8inbifieyF_wUh3s-tQJg1_Kwno06H5hlQ3C3eIt1VmjvKdnyuwOTL5eDAlUWu5UBekMxzkMZUhFxHOjSFZytVxhCAcUfw-8aMO0DSUFMWUJ31LvW6X3cN7SfPfxl0sEkz3LAhtBc6AH2ED-hOfAqyu8Ei0sfsuyNsRE_sDIJqb6mny159Msuf7r3_lOTUzLlYtvEb6mRnJkzON-HFAAmFE6yQC_l1x7aPWyJWx7IWFpH8XOMC7c0H9y7Fhnk7yWj6o_VO9zk4xvX_7g1O6rGs0fNXYQPMN75gUcTh24lNtWbcO4P9eV8tDN0PfHjTRqdNYJwL9Xq7hT1aKZbtXxDg0Gw1ZGYorFEiw0y_tsDf_qCo5tOypMtO_AhCYequMzX2BygQ1eyVvn_VQKDzZECjEGOddEchLz9ljeL_7N9i7PzsBAyNOmYu_jZe8dChQwv52TV1uDEQJY_sks9gzSyEbJQS1OgftvdY2ovJ3-tbN6vah7URicJSwL5jgrpZTG779NFb5uywaX6EBGZ03-93VMRfMcK-rrBhwZegx2G8-YcvJPS7xyYA4SvehghlfhBr-FmkIBIukv15sh92AvnXTKExmXhV8RGlMABM9GdAo4XNP9I_xXYZh75ew2jP6l-64MIDh47vVxInlTfPpHJegRGk1PYx9zx18rkEY7TP1OkEzpknijKPKkrV-ttO0pVa7-aNE4v1U5s6HBIQd3S9E2wjEA_mKMDEkedI8kHJNGkzbgZ95Xqm521VGIn-VflOhz1MTR2kCsj5c2E_0Nm0dA9b5qyWPftxq2hauT7C637p-4-T410s8UJkGpZX1XNitCCUKdzTFb1jgC0GZGNUUuZ5fkVnlL9GtAZOOom7xtByTVeoER-8x8vOQxF2PiwcurtNpenl-bm861DoV2_0Tz9VtPa2AQO0YGYPE7AScR-cvAZg5VLgAh_xztg792gTkhsfRoVATBADBOt4VDVQJfhTeH_rfxfSnK96eDANS-lphZAjyK2-tfQ2432B7agl73B-afHUlIJ5SsOLHiZzoNB7X8Q1XbzOny6aBNhGjSSlwk1kIJBwOOoTawXlDbtIh7_9ClhC5TmSNLn4poazG3FMpEA4mJBHzghK9qoCjG7KjDrmLvbWiVghSm0r5E0zO0uZG8Kod0Q1JnMX-4mhxjIbHDvXpWLn3qXJRwoyO_c_JngcK8hVZka2hPlO635AjKYbIJRGcmq07wM_6RS5WJsKvGeDOtuefZWAdruCr14uHMbg-z80NUbHOWjHAbN-fRqt07E8wJV04T4qT0d3VpRIZIrkNIRT7jeXHQ7w0vM3C5czOJGNQj0Mt2l6Cx9ysBmGUv6WtL-HQ587iBIvSmDyqedgGZUbHmuqBQINRgAghpFSzRu5HpmPNA5VrU1z3Q8cjmn5TRKu5C_3Z_ymviGpjWGmgBeDHQs0xlPCN6RQGIJMzdDpBV3eXL8YT7t5YEgul6hH4viET5YbuwlWiUkYXfqSBvjT9-YqBVTJh8KpdBUG05z3TryL-gB2V3wmnQAORydHGsi8lKlKWmC3MTNY1R1yCHAaW--nL7Tk3Ja4KzJsYusDoZi5YfTdzYoEbVnRlOTuKLctopLaSiZZ5b6E0CdwKfXRWVeOOcjpI2ArKOWWMnZX07bd8V3_2JKrPSwz22W2HIjCy2eGZ_RH5dZq25_Ok9SBAAoX63sX2dVvVbEwyoE-oP-uttq5l5Tr1v-bU0OJrfRm3VEHF2pTuUeZtn_Kh9q0F7OCBIw0C54ENYne-cDRSEGW2U9MMZ9EEmtOt_1b4tHZH-SwJAp42737WdaYKDcR8rtdPYsyOeTpm8gtWI2-dx-u3AozWYLBw8Lv6EnnFZ-13QsnIMMw6dKyYPKLw8lGNqWGlbxLGAE76_hKEfBm8flRvl27jY6N7mZt3wWkxfyD9RUyHkFk_uSB8O5vBUms1XfcDUTVJmkMkYYuAMEQeoyMXzroVAPHMYRqIEz43KZX0lwMsASaiUpYZ73In9SL_Xbi1T26Ii0PtgQh6CDVRCGH5l16g-Lv4pQq1gynjqMISn-p4K27ItYnhesQlUQ4rcI0FqPT9Wgkt0w-aBThnfpN1KtXw3sd0zATpAXglFo.IBhh9W88uPJ6wMwuZgf_5kZXKh1ZszjVBdfDSLPeE-Q\",\"scope\":\"openid profile email offline_access ui.read ui.admin ui.preferences.read ui.preferences.write authority:tenants.read authority:tenants.write authority:users.read authority:users.write authority:roles.read authority:roles.write authority:clients.read authority:clients.write authority:tokens.read authority:tokens.revoke authority:branding.read authority:branding.write authority.audit.read graph:read sbom:read scanner:read policy:read policy:simulate policy:author policy:review policy:approve policy:run policy:activate policy:audit policy:edit policy:operate policy:publish airgap:seal airgap:status:read orch:read orch:operate orch:quota analytics.read advisory:read advisory-ai:view advisory-ai:operate vex:read vexhub:read exceptions:read exceptions:approve aoc:verify findings:read release:read release:write release:publish scheduler:read scheduler:operate notify.viewer notify.operator notify.admin notify.escalate evidence:read export.viewer export.operator export.admin vuln:view vuln:investigate vuln:operate vuln:audit platform.context.read platform.context.write doctor:run doctor:admin ops.health integration:read integration:write integration:operate packs.read packs.write packs.run packs.approve registry.admin timeline:read timeline:write trust:read trust:write trust:admin signer:read signer:sign signer:rotate signer:admin\",\"expiresAtEpochMs\":1776599471290},\"identity\":{\"subject\":\"f945f00811f44f008058268a264ed015\",\"roles\":[],\"idToken\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6IjdLUks4OUw1UTNESkVQR09IV1NPSDNMVDktTVdNWVVJRS1aSU1VX1EiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2F1dGhvcml0eS5zdGVsbGEtb3BzLmxvY2FsLyIsImV4cCI6MTc3NjYwMDk3MiwiaWF0IjoxNzc2NTk3NjcyLCJhdWQiOiJzdGVsbGEtb3BzLXVpIiwic3ViIjoiZjk0NWYwMDgxMWY0NGYwMDgwNTgyNjhhMjY0ZWQwMTUiLCJhenAiOiJzdGVsbGEtb3BzLXVpIiwibm9uY2UiOiI3NzIxM2QzMS0wOTY3LTQ0MTItYjE1Ny1hM2RhN2Q3NDZkNjIiLCJhdF9oYXNoIjoiSElrZ1l3ZGZyc213S1B1ZTdjdW1wZyJ9.A9Mlpx8wxmuVLEl2P2hBYs8TXpgLLsAkfr2ywoOj-XBdgOWjjoKx7Uc0S6JqXZjj0aAEtiFTX0pT5QvWwWUhRzKIk_osSIt5eim0lyY3130O1b-zK4xJxLr98ukT-ikhcchJCxTjwyE_m8Mx3ZIyxj52nrWZmFEhx6is2BFgA8OXZmk6UgMFi2PKYjKP9qy_kz_ZcdpHjyaxgU4nyB8giXPWQjBEjZ3bYdotn5BUMQQ4i3NVT-UeriLZnHDb2D147WL0E7KfzoZznkNpKaCnDUo_09MSVXPKhRXqNaH9DYQQ8pMPCcVMDHxMjUEd5ycvAl8LJVSy32-1l1ix9lgJTQ\"},\"dpopKeyThumbprint\":\"oE_VLfMwaUO1GgDCBKUb__qedb99FPz82TOxT8HJZTQ\",\"issuedAtEpochMs\":1776597671291,\"tenantId\":\"default\",\"scopes\":[\"advisory-ai:operate\",\"advisory-ai:view\",\"advisory:read\",\"airgap:seal\",\"airgap:status:read\",\"analytics.read\",\"aoc:verify\",\"authority.audit.read\",\"authority:branding.read\",\"authority:branding.write\",\"authority:clients.read\",\"authority:clients.write\",\"authority:roles.read\",\"authority:roles.write\",\"authority:tenants.read\",\"authority:tenants.write\",\"authority:tokens.read\",\"authority:tokens.revoke\",\"authority:users.read\",\"authority:users.write\",\"doctor:admin\",\"doctor:run\",\"email\",\"evidence:read\",\"exceptions:approve\",\"exceptions:read\",\"export.admin\",\"export.operator\",\"export.viewer\",\"findings:read\",\"graph:read\",\"integration:operate\",\"integration:read\",\"integration:write\",\"notify.admin\",\"notify.escalate\",\"notify.operator\",\"notify.viewer\",\"offline_access\",\"openid\",\"ops.health\",\"orch:operate\",\"orch:quota\",\"orch:read\",\"packs.approve\",\"packs.read\",\"packs.run\",\"packs.write\",\"platform.context.read\",\"platform.context.write\",\"policy:activate\",\"policy:approve\",\"policy:audit\",\"policy:author\",\"policy:edit\",\"policy:operate\",\"policy:publish\",\"policy:read\",\"policy:review\",\"policy:run\",\"policy:simulate\",\"profile\",\"registry.admin\",\"release:publish\",\"release:read\",\"release:write\",\"sbom:read\",\"scanner:read\",\"scheduler:operate\",\"scheduler:read\",\"signer:admin\",\"signer:read\",\"signer:rotate\",\"signer:sign\",\"timeline:read\",\"timeline:write\",\"trust:admin\",\"trust:read\",\"trust:write\",\"ui.admin\",\"ui.preferences.read\",\"ui.preferences.write\",\"ui.read\",\"vex:read\",\"vexhub:read\",\"vuln:audit\",\"vuln:investigate\",\"vuln:operate\",\"vuln:view\"],\"audiences\":[],\"authenticationTimeEpochMs\":1776597671000,\"freshAuthActive\":false,\"freshAuthExpiresAtEpochMs\":null}" + }, + { + "name": "stellaops.helper.preferences", + "value": "{\"dismissed\":false,\"tooltipsMuted\":false,\"mutedPages\":[],\"mutedTipIds\":[],\"seenPages\":[],\"tipIndex\":{},\"dismissedBanners\":[],\"seenHelpPages\":[],\"pageHelpOpen\":{},\"pageHelpDismissedGlobal\":false,\"pageHelpDismissedPages\":[]}" + }, + { + "name": "stellaops.content-width", + "value": "centered" + }, + { + "name": "stellaops.assistant.state", + "value": "{\"seenRoutes\":[],\"completedTours\":[],\"tipPositions\":{},\"dismissed\":false}" + }, + { + "name": "stellaops.theme", + "value": "system" + }, + { + "name": "stellaops.auth.session.info", + "value": "{\"subject\":\"f945f00811f44f008058268a264ed015\",\"expiresAtEpochMs\":1776599471290,\"issuedAtEpochMs\":1776597671291,\"dpopKeyThumbprint\":\"oE_VLfMwaUO1GgDCBKUb__qedb99FPz82TOxT8HJZTQ\",\"tenantId\":\"default\"}" + }, + { + "name": "stellaops.sidebar.preferences", + "value": "{\"sidebarCollapsed\":false,\"collapsedGroups\":[\"evidence\",\"setup-admin\"],\"collapsedSections\":[]}" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/Web/StellaOps.Web/scripts/live-setup-wizard-first-run-bootstrap.mjs b/src/Web/StellaOps.Web/scripts/live-setup-wizard-first-run-bootstrap.mjs new file mode 100644 index 000000000..edf1aa316 --- /dev/null +++ b/src/Web/StellaOps.Web/scripts/live-setup-wizard-first-run-bootstrap.mjs @@ -0,0 +1,693 @@ +#!/usr/bin/env node + +import { mkdir, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { chromium } from 'playwright'; + +import { authenticateFrontdoor, createAuthenticatedContext } from './live-frontdoor-auth.mjs'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const webRoot = path.resolve(__dirname, '..'); +const outputDir = path.join(webRoot, 'output', 'playwright'); +const outputPath = path.join(outputDir, 'live-setup-wizard-first-run-bootstrap.json'); +const authStatePath = path.join(outputDir, 'live-setup-wizard-first-run-bootstrap.state.json'); +const authReportPath = path.join(outputDir, 'live-setup-wizard-first-run-bootstrap.auth.json'); + +const baseUrl = process.env.STELLAOPS_FRONTDOOR_BASE_URL?.trim() || 'https://stella-ops.local'; +const wizardUrl = `${baseUrl}/setup-wizard/wizard`; +const adminUsername = process.env.STELLAOPS_ADMIN_USER?.trim() || 'admin'; +const adminEmail = process.env.STELLAOPS_ADMIN_EMAIL?.trim() || 'admin@stella-ops.local'; +const adminPassword = process.env.STELLAOPS_ADMIN_PASS?.trim(); + +if (!adminPassword) { + throw new Error('Set STELLAOPS_ADMIN_PASS before running the live first-run setup bootstrap.'); +} +const headless = (process.env.STELLAOPS_UI_BOOTSTRAP_HEADLESS || 'true').toLowerCase() !== 'false'; + +function isStaticAsset(url) { + return /\.(?:css|js|map|png|jpg|jpeg|svg|woff2?|ico)(?:$|\?)/i.test(url); +} + +function cleanText(value) { + return typeof value === 'string' ? value.replace(/\s+/g, ' ').trim() : ''; +} + +function normalizeStepId(value) { + switch ((value ?? '').toString().trim().toLowerCase()) { + case 'database': + return 'database'; + case 'valkey': + case 'cache': + return 'cache'; + case 'migrations': + return 'migrations'; + case 'admin': + return 'admin'; + case 'crypto': + return 'crypto'; + case 'sources': + return 'sources'; + default: + return (value ?? '').toString().trim().toLowerCase(); + } +} + +function normalizeStepStatus(value) { + switch ((value ?? '').toString().trim().toLowerCase()) { + case 'inprogress': + return 'in_progress'; + case 'pass': + case 'passed': + return 'completed'; + default: + return (value ?? '').toString().trim().toLowerCase(); + } +} + +function createRuntime() { + return { + consoleErrors: [], + pageErrors: [], + requestFailures: [], + responseErrors: [], + setupApiEvents: [], + }; +} + +function isExpectedAnonymousSetupCreate401(event, runtime) { + return event?.method === 'POST' + && event?.status === 401 + && event?.url === `${baseUrl}/api/v1/setup/sessions` + && runtime.setupApiEvents.some((candidate) => + candidate.method === 'POST' + && candidate.status === 201 + && candidate.url === `${baseUrl}/api/v1/setup/sessions`); +} + +function isAuthenticatedWizardSurface(bodyText, runtime) { + return /PostgreSQL Connection|Valkey\/Redis|Authority Administrator|Apply and Continue|Validate Connection/i.test(bodyText) + || runtime.setupApiEvents.some((event) => + event.method === 'POST' + && event.status === 201 + && event.url === `${baseUrl}/api/v1/setup/sessions`); +} + +function attachRuntime(page, runtime) { + page.on('console', (message) => { + if ( + message.type() === 'error' + && !message.text().startsWith('Failed to load resource: the server responded with a status of') + ) { + runtime.consoleErrors.push({ page: page.url(), text: message.text() }); + } + }); + + page.on('pageerror', (error) => { + runtime.pageErrors.push({ + page: page.url(), + text: error instanceof Error ? error.message : String(error), + }); + }); + + page.on('requestfailed', (request) => { + const errorText = request.failure()?.errorText ?? 'unknown'; + if ( + isStaticAsset(request.url()) + || errorText === 'net::ERR_ABORTED' + || (!request.url().includes('/api/v1/setup') && !request.url().includes('/setup-wizard')) + ) { + return; + } + + runtime.requestFailures.push({ + page: page.url(), + method: request.method(), + url: request.url(), + error: errorText, + }); + }); + + page.on('response', (response) => { + if ( + isStaticAsset(response.url()) + || response.url().includes('/api/v1/stella-assistant/tips') + || (!response.url().includes('/api/v1/setup') && !response.url().includes('/setup-wizard')) + ) { + return; + } + + if (response.url().includes('/api/v1/setup/')) { + runtime.setupApiEvents.push({ + page: page.url(), + method: response.request().method(), + status: response.status(), + url: response.url(), + }); + } + + if (response.status() >= 400) { + runtime.responseErrors.push({ + page: page.url(), + method: response.request().method(), + status: response.status(), + url: response.url(), + }); + } + }); +} + +async function settle(page, ms = 1500) { + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }).catch(() => {}); + await page.waitForTimeout(ms); +} + +async function waitForBodyText(page, text, timeout = 60_000) { + await page.waitForFunction( + (expected) => (document.body?.innerText || '').replace(/\s+/g, ' ').includes(expected), + text, + { timeout }, + ); + await page.waitForTimeout(1000); +} + +async function captureSnapshot(page, label) { + const alerts = await page + .locator('[role="alert"], .error-banner, .warning-banner, .banner, .toast, .notification, .status-card, .test-result') + .evaluateAll((nodes) => + nodes + .map((node) => (node.textContent || '').replace(/\s+/g, ' ').trim()) + .filter(Boolean) + .slice(0, 10), + ) + .catch(() => []); + + const visibleButtons = await page + .locator('button') + .evaluateAll((nodes) => + nodes + .map((node) => (node.textContent || '').replace(/\s+/g, ' ').trim()) + .filter(Boolean) + .slice(0, 12), + ) + .catch(() => []); + + return { + label, + url: page.url(), + title: await page.title().catch(() => ''), + heading: cleanText(await page.locator('h1, h2').first().textContent().catch(() => '')), + alerts, + visibleButtons, + }; +} + +async function fetchJson(page, relativeUrl, options = {}) { + const response = await page.request.fetch(`${baseUrl}${relativeUrl}`, { + method: options.method ?? 'GET', + failOnStatusCode: false, + headers: { + Accept: 'application/json', + ...(options.body ? { 'Content-Type': 'application/json' } : {}), + }, + data: options.body ?? undefined, + }); + + const bodyText = await response.text().catch(() => ''); + let payload = null; + try { + payload = JSON.parse(bodyText); + } catch { + payload = null; + } + + return { + status: response.status(), + ok: response.ok(), + payload, + bodyText: bodyText.slice(0, 4000), + }; +} + +async function readCurrentSession(page) { + const result = await fetchJson(page, '/api/v1/setup/sessions/current'); + const session = result?.payload?.session ?? null; + + return { + status: result.status, + ok: result.ok, + sessionId: session?.sessionId ?? null, + currentStepId: normalizeStepId(session?.currentStepId ?? null), + sessionStatus: normalizeStepStatus(session?.status ?? null), + completedAtUtc: session?.completedAtUtc ?? null, + steps: Array.isArray(session?.steps) + ? session.steps.map((step) => ({ + stepId: normalizeStepId(step.stepId), + status: normalizeStepStatus(step.status), + lastProbeSucceeded: step.lastProbeSucceeded ?? null, + errorMessage: step.errorMessage ?? null, + })) + : [], + raw: result, + }; +} + +async function createOrResumeSetupSession(page) { + const resumed = await fetchJson(page, '/api/v1/setup/sessions/resume', { method: 'POST' }); + if (resumed.ok) { + return resumed; + } + + return fetchJson(page, '/api/v1/setup/sessions', { + method: 'POST', + body: {}, + }); +} + +async function waitForSession(page, predicate, timeoutMs = 60_000) { + const deadline = Date.now() + timeoutMs; + let lastSnapshot = null; + + while (Date.now() < deadline) { + lastSnapshot = await readCurrentSession(page); + if (predicate(lastSnapshot)) { + return lastSnapshot; + } + + await page.waitForTimeout(1000); + } + + throw new Error(`Timed out waiting for setup session condition. Last snapshot: ${JSON.stringify(lastSnapshot)}`); +} + +function stepStatus(sessionSnapshot, stepId) { + return sessionSnapshot.steps.find((step) => step.stepId === stepId) ?? null; +} + +function sessionReadyForFinalize(sessionSnapshot) { + if (!sessionSnapshot?.ok) { + return false; + } + + if (sessionSnapshot.currentStepId) { + return false; + } + + const requiredSteps = ['database', 'cache', 'migrations', 'admin', 'crypto']; + const allRequiredStepsCompleted = requiredSteps.every( + (stepId) => stepStatus(sessionSnapshot, stepId)?.status === 'completed', + ); + + return allRequiredStepsCompleted && sessionSnapshot.raw?.payload?.readiness?.readyToProceed === true; +} + +async function waitForCurrentStep(page, expectedStepId) { + await waitForSession(page, (snapshot) => snapshot.ok && snapshot.currentStepId === expectedStepId, 45_000); + await page.waitForTimeout(500); +} + +async function ensureFieldValue(page, selectors, value) { + const locator = page.locator(selectors.join(', ')).first(); + await locator.waitFor({ state: 'visible', timeout: 30_000 }); + await locator.fill(value); + await page.waitForTimeout(150); +} + +async function chooseDefaultCryptoProvider(page) { + const target = page.getByRole('button', { name: /Default \(Recommended\)/i }).first(); + if (!(await target.isVisible().catch(() => false))) { + return false; + } + + await target.click({ timeout: 10_000 }); + await page.waitForTimeout(300); + return true; +} + +async function applyStep(page, currentStepId, nextStepId, expectedTextAfter) { + await Promise.all([ + page.waitForResponse( + (response) => + response.request().method() === 'POST' + && response.url().includes('/api/v1/setup/sessions/') + && response.url().includes(`/steps/${currentStepId}/apply`), + { timeout: 45_000 }, + ), + page.getByRole('button', { name: /Apply and Continue/i }).first().click({ timeout: 15_000 }), + ]); + + if (nextStepId) { + await waitForCurrentStep(page, nextStepId); + } + + if (expectedTextAfter) { + await waitForBodyText(page, expectedTextAfter, 45_000); + } +} + +async function finalizeSetup(page, currentStepId = 'crypto') { + const finishButton = page.getByRole('button', { name: /Finish Setup/i }).first(); + await finishButton.waitFor({ state: 'visible', timeout: 20_000 }); + + const currentApplyResponsePromise = page.waitForResponse( + (response) => + response.request().method() === 'POST' + && response.url().includes('/api/v1/setup/sessions/') + && response.url().includes(`/steps/${currentStepId}/apply`), + { timeout: 10_000 }, + ).catch(() => null); + + const finalizeResponsePromise = page.waitForResponse( + (response) => + response.request().method() === 'POST' + && response.url().includes('/api/v1/setup/sessions/') + && response.url().includes('/finalize'), + { timeout: 60_000 }, + ); + + await finishButton.click({ timeout: 15_000 }); + + const finalizeResponse = await finalizeResponsePromise; + const currentApplyResponse = await currentApplyResponsePromise; + + const finalizeBody = await finalizeResponse.json().catch(() => null); + if (!finalizeBody?.data?.success) { + throw new Error(`Finalize returned non-success payload: ${JSON.stringify(finalizeBody)}`); + } + + const sessionClosure = await page.request.get(`${baseUrl}/api/v1/setup/sessions/current`, { + failOnStatusCode: false, + }); + + const sessionClosureText = await sessionClosure.text().catch(() => ''); + let sessionClosureJson = null; + try { + sessionClosureJson = JSON.parse(sessionClosureText); + } catch { + sessionClosureJson = null; + } + + await Promise.race([ + page.waitForURL((url) => !url.toString().includes('/setup-wizard/'), { timeout: 30_000 }).catch(() => {}), + page.waitForTimeout(5_000), + ]); + + if (page.url().includes('/setup-wizard/')) { + await page.goto(`${baseUrl}/welcome`, { waitUntil: 'domcontentloaded', timeout: 30_000 }).catch(() => {}); + } + + await settle(page, 2000); + + return { + currentApplyStepId: currentStepId, + currentApplyStatus: currentApplyResponse?.status() ?? null, + finalizeStatus: finalizeResponse.status(), + finalizeBody, + sessionAfterFinalize: { + status: sessionClosure.status(), + ok: sessionClosure.ok(), + payload: sessionClosureJson, + bodyText: sessionClosureText.slice(0, 4000), + }, + }; +} + +async function authenticateAfterSetup(browser, runtime) { + const authReport = await authenticateFrontdoor({ + baseUrl, + username: adminUsername, + password: adminPassword, + statePath: authStatePath, + reportPath: authReportPath, + headless, + }); + + const context = await createAuthenticatedContext(browser, authReport, { statePath: authStatePath }); + const page = await context.newPage(); + attachRuntime(page, runtime); + + return { authReport, context, page }; +} + +async function main() { + await mkdir(outputDir, { recursive: true }); + + const browser = await chromium.launch({ + headless, + args: ['--ignore-certificate-errors', '--disable-dev-shm-usage'], + }); + + let context = await browser.newContext({ ignoreHTTPSErrors: true }); + let page = await context.newPage(); + const runtime = createRuntime(); + attachRuntime(page, runtime); + + const steps = []; + let authReport = null; + + await page.goto(wizardUrl, { waitUntil: 'domcontentloaded', timeout: 60_000 }); + await settle(page, 3000); + const startSetupButton = page.getByRole('button', { name: 'Start Setup', exact: true }); + let initialSession = await readCurrentSession(page); + const initialSnapshot = await captureSnapshot(page, 'initial'); + const initialReadyForFinalize = sessionReadyForFinalize(initialSession); + + if (initialSession.status === 401) { + ({ authReport, context, page } = await authenticateAfterSetup(browser, runtime)); + await page.goto(wizardUrl, { waitUntil: 'domcontentloaded', timeout: 60_000 }); + await settle(page, 3000); + + await Promise.race([ + waitForBodyText(page, 'Setup already completed', 15_000).catch(() => {}), + page.waitForFunction( + () => /PostgreSQL Connection|Valkey\/Redis|Authority Administrator|Apply and Continue|Validate Connection/i.test( + (document.body?.innerText || '').replace(/\s+/g, ' '), + ), + null, + { timeout: 15_000 }, + ).catch(() => {}), + page.waitForTimeout(15_000), + ]); + + const authenticatedSession = await readCurrentSession(page); + const configuredSnapshot = await captureSnapshot(page, 'setup-already-complete'); + const configuredBodyText = await page.locator('body').innerText().catch(() => ''); + const setupAlreadyComplete = /setup already completed/i.test(configuredBodyText); + const authenticatedWizardActive = isAuthenticatedWizardSurface(configuredBodyText, runtime); + steps.push({ + action: 'setup-entry-after-authentication', + ok: + authReport.sessionStatus?.hasFullSession === true + && (setupAlreadyComplete || authenticatedWizardActive), + resolvedSurface: setupAlreadyComplete ? 'already-complete' : authenticatedWizardActive ? 'authenticated-reconfigure' : 'unknown', + initialAnonymousSession: initialSession, + authenticatedSetupSessionProbe: authenticatedSession, + initialSnapshot, + snapshot: configuredSnapshot, + }); + + await context.close(); + await browser.close(); + + const summary = { + generatedAtUtc: new Date().toISOString(), + baseUrl, + adminUsername, + adminEmail, + mode: 'already-configured', + steps, + runtime, + postSetupAuthentication: { + finalUrl: authReport.finalUrl, + title: authReport.title, + reportPath: authReportPath, + statePath: authStatePath, + }, + }; + + await writeFile(outputPath, `${JSON.stringify(summary, null, 2)}\n`, 'utf8'); + + const failedSteps = steps.filter((step) => step.ok === false); + const unexpectedResponseErrors = runtime.responseErrors.filter( + (event) => !isExpectedAnonymousSetupCreate401(event, runtime), + ); + const runtimeIssues = + runtime.consoleErrors.length + + runtime.pageErrors.length + + runtime.requestFailures.length + + unexpectedResponseErrors.length; + + if (failedSteps.length > 0 || runtimeIssues > 0) { + process.exitCode = 1; + } + + return; + } + + if (!initialSession.ok || (!initialSession.currentStepId && !initialReadyForFinalize)) { + await createOrResumeSetupSession(page); + initialSession = await waitForSession(page, (snapshot) => snapshot.ok && snapshot.currentStepId === 'database'); + await page.reload({ waitUntil: 'domcontentloaded', timeout: 60_000 }); + await settle(page, 3000); + } + + if (sessionReadyForFinalize(initialSession)) { + steps.push({ + action: 'resume-ready-for-finalize', + ok: true, + session: initialSession, + snapshot: initialSnapshot, + }); + } else if (await startSetupButton.isVisible().catch(() => false)) { + steps.push({ + action: 'welcome', + ok: initialSession.status === 200 && initialSession.currentStepId === 'database', + session: initialSession, + snapshot: initialSnapshot, + }); + + await startSetupButton.click({ timeout: 20_000 }); + await waitForBodyText(page, 'PostgreSQL Connection'); + } else if (initialSession.currentStepId === 'database') { + steps.push({ + action: 'database-direct-open', + ok: true, + session: initialSession, + snapshot: initialSnapshot, + }); + } else { + throw new Error(`Setup wizard opened in an unexpected state: ${JSON.stringify({ + url: page.url(), + currentStepId: initialSession.currentStepId, + sessionStatus: initialSession.sessionStatus, + snapshot: initialSnapshot, + })}`); + } + + if (!sessionReadyForFinalize(initialSession)) { + steps.push({ + action: 'database-open', + ok: (await readCurrentSession(page)).currentStepId === 'database', + snapshot: await captureSnapshot(page, 'database-open'), + }); + + await applyStep(page, 'database', 'cache', 'Valkey/Redis Connection'); + const afterDatabase = await readCurrentSession(page); + steps.push({ + action: 'database-applied', + ok: afterDatabase.currentStepId === 'cache' && stepStatus(afterDatabase, 'database')?.status === 'completed', + session: afterDatabase, + snapshot: await captureSnapshot(page, 'database-applied'), + }); + + await applyStep(page, 'cache', 'migrations', 'Database Migrations'); + const afterCache = await readCurrentSession(page); + steps.push({ + action: 'cache-applied', + ok: afterCache.currentStepId === 'migrations' && stepStatus(afterCache, 'cache')?.status === 'completed', + session: afterCache, + snapshot: await captureSnapshot(page, 'cache-applied'), + }); + + await applyStep(page, 'migrations', 'admin', 'Super User Account'); + const afterMigrations = await readCurrentSession(page); + steps.push({ + action: 'migrations-applied', + ok: afterMigrations.currentStepId === 'admin' && stepStatus(afterMigrations, 'migrations')?.status === 'completed', + session: afterMigrations, + snapshot: await captureSnapshot(page, 'migrations-applied'), + }); + + await ensureFieldValue(page, ['#users-superuser-username', 'input[name="users.superuser.username"]'], adminUsername); + await ensureFieldValue(page, ['#users-superuser-email', 'input[name="users.superuser.email"]'], adminEmail); + await ensureFieldValue(page, ['#users-superuser-password', 'input[name="users.superuser.password"]'], adminPassword); + + await applyStep(page, 'admin', 'crypto', 'Cryptographic Provider'); + const afterAdmin = await readCurrentSession(page); + steps.push({ + action: 'admin-applied', + ok: afterAdmin.currentStepId === 'crypto' && stepStatus(afterAdmin, 'admin')?.status === 'completed', + session: afterAdmin, + snapshot: await captureSnapshot(page, 'admin-applied'), + }); + } + + let cryptoSelectionApplied = false; + let beforeFinalizeSession = await readCurrentSession(page); + + if (beforeFinalizeSession.currentStepId === 'crypto') { + cryptoSelectionApplied = await chooseDefaultCryptoProvider(page); + await applyStep(page, 'crypto', 'sources', 'Advisory Data Sources'); + const afterCrypto = await readCurrentSession(page); + steps.push({ + action: 'crypto-applied', + ok: afterCrypto.currentStepId === 'sources' && stepStatus(afterCrypto, 'crypto')?.status === 'completed', + session: afterCrypto, + snapshot: await captureSnapshot(page, 'crypto-applied'), + }); + beforeFinalizeSession = afterCrypto; + } + + const completion = await finalizeSetup(page, beforeFinalizeSession.currentStepId || 'sources'); + steps.push({ + action: 'setup-finished', + ok: completion.finalizeBody?.data?.success === true, + cryptoSelectionApplied, + completion, + snapshot: await captureSnapshot(page, 'setup-finished'), + }); + + await context.close(); + + authReport ??= await authenticateFrontdoor({ + baseUrl, + username: adminUsername, + password: adminPassword, + statePath: authStatePath, + reportPath: authReportPath, + headless, + }); + + await browser.close(); + + const summary = { + generatedAtUtc: new Date().toISOString(), + baseUrl, + adminUsername, + adminEmail, + steps, + runtime, + postSetupAuthentication: { + finalUrl: authReport.finalUrl, + title: authReport.title, + reportPath: authReportPath, + statePath: authStatePath, + }, + }; + + await writeFile(outputPath, `${JSON.stringify(summary, null, 2)}\n`, 'utf8'); + + const failedSteps = steps.filter((step) => step.ok === false); + const runtimeIssues = + runtime.consoleErrors.length + + runtime.pageErrors.length + + runtime.requestFailures.length + + runtime.responseErrors.length; + + if (failedSteps.length > 0 || runtimeIssues > 0) { + process.exitCode = 1; + } +} + +main().catch(async (error) => { + await mkdir(outputDir, { recursive: true }); + const summary = { + generatedAtUtc: new Date().toISOString(), + fatalError: error instanceof Error ? error.message : String(error), + }; + await writeFile(outputPath, `${JSON.stringify(summary, null, 2)}\n`, 'utf8'); + console.error(error); + process.exitCode = 1; +}); diff --git a/src/Web/StellaOps.Web/src/app/features/integrations/advisory-vex-sources/advisory-source-catalog.component.ts b/src/Web/StellaOps.Web/src/app/features/integrations/advisory-vex-sources/advisory-source-catalog.component.ts index aa97cf441..5c3f05e11 100644 --- a/src/Web/StellaOps.Web/src/app/features/integrations/advisory-vex-sources/advisory-source-catalog.component.ts +++ b/src/Web/StellaOps.Web/src/app/features/integrations/advisory-vex-sources/advisory-source-catalog.component.ts @@ -57,6 +57,12 @@ interface CategoryGroup { items: SourceCatalogItem[]; } +interface EnabledSourceFailureSummary { + sourceId: string; + displayName: string; + error: string; +} + @Component({ selector: 'app-advisory-source-catalog', standalone: true, @@ -128,6 +134,31 @@ interface CategoryGroup { @if (loading()) {
} @else { + @if (enabledCount() === 0) { + + } + @if (enabledSourceFailures().length > 0) { + + } +