fix(dashboard): address PR review 56

This commit is contained in:
2026-10-05 08:41:33 +00:00
parent a02aceee41
commit f01dc182ec
9 changed files with 1785 additions and 848 deletions
+43 -5
View File
@@ -21,6 +21,7 @@ import subprocess
import sys
import time
import hashlib
import math
import secrets
import ssl
import urllib.parse
@@ -326,6 +327,26 @@ def redact_secrets(value: Any, secrets_to_redact: tuple[str, ...] = ()) -> Any:
return value
def _validate_surface_records(records: Any, label: str) -> None:
required = ("stAppViewContainer", "stSidebar")
if not isinstance(records, dict) or any(not isinstance(records.get(name), dict) for name in required):
raise ValueError(f"transition observations require surface records for {label}")
for name in required:
surface = records[name]
if any(key not in surface or surface[key] is None for key in ("backgroundImage", "backgroundColor", "visibility", "display", "opacity", "rendered")):
raise ValueError(f"transition observations require complete surface records for {label}")
if not str(surface["backgroundImage"]).strip() or str(surface["backgroundImage"]).strip().lower() == "none":
raise ValueError(f"transition observations reject empty surface background for {label}")
if not str(surface["backgroundColor"]).strip():
raise ValueError(f"transition observations reject empty surface background color for {label}")
try:
opacity = float(surface["opacity"])
except (TypeError, ValueError) as exc:
raise ValueError(f"transition observations require valid surface opacity for {label}") from exc
if not math.isfinite(opacity) or surface["visibility"] != "visible" or surface["display"] == "none" or opacity <= 0 or surface["rendered"] is not True:
raise ValueError(f"transition observations require visible rendered surfaces for {label}")
def validate_transition_manifest(manifest: dict[str, Any], secrets_to_redact: tuple[str, ...] = ()) -> None:
"""Fail closed unless transition evidence contains every required observation."""
serialized = json.dumps(manifest, ensure_ascii=False)
@@ -337,14 +358,30 @@ def validate_transition_manifest(manifest: dict[str, Any], secrets_to_redact: tu
frames = observations.get("frames")
if not isinstance(frames, list) or len(frames) < 2:
raise ValueError("transition observations require at least two frames")
for index, frame in enumerate(frames):
_validate_surface_records(frame.get("surfaces"), f"frame {index}")
document_events = observations.get("document_navigation_events")
if not isinstance(document_events, list) or document_events:
raise ValueError("transition observations require zero document navigation events")
shell = observations.get("stable_shell") or {}
if not all(isinstance(shell.get(name), dict) for name in ("document_marker", "sidebar_marker", "background")):
stable_fields = ("document_marker", "sidebar_marker", "background")
if any(not isinstance(shell.get(key), dict) for key in stable_fields):
raise ValueError("transition observations require pre/post stable shell markers")
if any(values.get("pre") != values.get("post") for values in (shell["document_marker"], shell["sidebar_marker"], shell["background"])):
if any(
not isinstance(values.get(position), str) or not values[position].strip()
for values in (shell[key] for key in stable_fields)
for position in ("pre", "post")
):
raise ValueError("transition observations require non-empty stable shell markers")
if any(shell[key]["pre"] != shell[key]["post"] for key in stable_fields):
raise ValueError("transition observations require stable shell identity and background")
stable_surfaces = shell.get("surfaces")
if not isinstance(stable_surfaces, dict) or stable_surfaces.get("pre") != frames[0].get("surfaces"):
raise ValueError("transition observations require stable shell surface records")
_validate_surface_records(stable_surfaces.get("pre"), "stable shell pre")
_validate_surface_records(stable_surfaces.get("post"), "stable shell post")
if stable_surfaces["pre"] != stable_surfaces["post"] or stable_surfaces["post"] != frames[-1].get("surfaces"):
raise ValueError("transition observations require stable shell surface consistency")
if observations.get("prior_body_removed") is not True:
raise ValueError("transition observations require prior body removal")
loading_frames = [frame for frame in frames if frame.get("loading") is True]
@@ -444,6 +481,7 @@ async def cdp_transition(ws_url: str, base_url: str, from_page: str, to_page: st
window.__evidencePriorBodyText = (document.querySelector('[data-testid=stMain]') || document.body)?.innerText || '';
const sidebar=document.querySelector('[data-testid=stSidebar]');
if (sidebar) sidebar.dataset.evidenceMarker ||= crypto.randomUUID();
window.__evidenceSurface = (selector) => { const element=document.querySelector(selector); if (!element) return null; const style=getComputedStyle(element); const rect=element.getBoundingClientRect(); return {backgroundImage:style.backgroundImage, backgroundColor:style.backgroundColor, visibility:style.visibility, display:style.display, opacity:style.opacity, rendered:rect.width>0 && rect.height>0 && style.visibility!=='hidden' && style.display!=='none' && Number(style.opacity)>0}; };
for (const name of ['pushState','replaceState']) {
const old=history[name];
history[name]=function(){window.__evidenceEvents.push({type:name,url:String(arguments[2]||location.href)}); return old.apply(this,arguments)};
@@ -456,7 +494,7 @@ async def cdp_transition(ws_url: str, base_url: str, from_page: str, to_page: st
deadline = time.time() + 45
index = 0
while time.time() < deadline and (index < frame_count or not observations or observations[-1].get("selected_menu") != to_page):
expression = """(() => { const body=document.body; const text=body?.innerText||''; const main=document.querySelector('[data-testid=stMain]')||body; const mainText=main.innerText||''; const sidebar=document.querySelector('[data-testid=stSidebar]'); const selectedInput=document.querySelector('[data-testid=stRadio] input:checked'); const selected=selectedInput?.closest('label')?.innerText?.trim() || document.querySelector('[aria-current=page]')?.textContent?.trim() || ''; const prior=window.__evidencePriorBodyText||''; return {at_ms:%d, selected_menu:selected, body_text:mainText, prior_body_visible:Boolean(prior)&&mainText.includes(prior), document_marker:window.__evidenceDocumentMarker||'', sidebar_marker:sidebar?.dataset.evidenceMarker||'', background:getComputedStyle(document.body).backgroundColor, loading:/불러오는 중/.test(text)}; })()""" % (index * interval_ms)
expression = """(() => { const body=document.body; const text=body?.innerText||''; const main=document.querySelector('[data-testid=stMain]')||body; const mainText=main.innerText||''; const sidebar=document.querySelector('[data-testid=stSidebar]'); const selectedInput=document.querySelector('[data-testid=stRadio] input:checked'); const selected=selectedInput?.closest('label')?.innerText?.trim() || document.querySelector('[aria-current=page]')?.textContent?.trim() || ''; const prior=window.__evidencePriorBodyText||''; return {at_ms:%d, selected_menu:selected, body_text:mainText, prior_body_visible:Boolean(prior)&&mainText.includes(prior), document_marker:window.__evidenceDocumentMarker||'', sidebar_marker:sidebar?.dataset.evidenceMarker||'', background:getComputedStyle(document.body).backgroundColor, surfaces:{stAppViewContainer:window.__evidenceSurface('[data-testid=stAppViewContainer]'), stSidebar:window.__evidenceSurface('[data-testid=stSidebar]')}, loading:/불러오는 중/.test(text)}; })()""" % (index * interval_ms)
result = await cdp.call("Runtime.evaluate", {"expression": expression, "returnByValue": True})
frame = result.get("result", {}).get("value", {})
frame["body_text_hash"] = hashlib.sha256(frame.get("body_text", "").encode()).hexdigest()
@@ -466,7 +504,7 @@ async def cdp_transition(ws_url: str, base_url: str, from_page: str, to_page: st
await cdp.call("Runtime.evaluate", {"expression": click, "returnByValue": True})
index += 1
await asyncio.sleep(interval_ms / 1000)
final = await cdp.call("Runtime.evaluate", {"expression": "(() => { const main=document.querySelector('[data-testid=stMain]')||document.body; const sidebar=document.querySelector('[data-testid=stSidebar]'); const selectedInput=document.querySelector('[data-testid=stRadio] input:checked'); return {url:location.href, body:main.innerText||'', selected:selectedInput?.closest('label')?.innerText?.trim() || document.querySelector('[aria-current=page]')?.textContent?.trim() || '', document_marker:window.__evidenceDocumentMarker||'', sidebar_marker:sidebar?.dataset.evidenceMarker||'', background:getComputedStyle(document.body).backgroundColor, history_events:window.__evidenceEvents||[], browser_user_agent:navigator.userAgent}; })()", "returnByValue": True})
final = await cdp.call("Runtime.evaluate", {"expression": "(() => { const main=document.querySelector('[data-testid=stMain]')||document.body; const sidebar=document.querySelector('[data-testid=stSidebar]'); const selectedInput=document.querySelector('[data-testid=stRadio] input:checked'); return {url:location.href, body:main.innerText||'', selected:selectedInput?.closest('label')?.innerText?.trim() || document.querySelector('[aria-current=page]')?.textContent?.trim() || '', document_marker:window.__evidenceDocumentMarker||'', sidebar_marker:sidebar?.dataset.evidenceMarker||'', background:getComputedStyle(document.body).backgroundColor, surfaces:{stAppViewContainer:window.__evidenceSurface('[data-testid=stAppViewContainer]'), stSidebar:window.__evidenceSurface('[data-testid=stSidebar]')}, history_events:window.__evidenceEvents||[], browser_user_agent:navigator.userAgent}; })()", "returnByValue": True})
data = final.get("result", {}).get("value", {})
html = await cdp.call("Runtime.evaluate", {"expression": "document.documentElement.outerHTML", "returnByValue": True})
html_text = html.get("result", {}).get("value", "")
@@ -476,7 +514,7 @@ async def cdp_transition(ws_url: str, base_url: str, from_page: str, to_page: st
document_events = [{"type": event.get("method"), "params": event.get("params", {})} for event in cdp.events if event.get("method") in ("Page.frameNavigated", "Page.loadEventFired")]
pre = observations[0] if observations else {}
loading_frames = [frame for frame in observations if frame.get("loading")]
return {"frames": observations, "document_navigation_events": document_events, "history_events": data.get("history_events", []), "stable_shell": {"document_marker": {"pre": pre.get("document_marker"), "post": data.get("document_marker")}, "sidebar_marker": {"pre": pre.get("sidebar_marker"), "post": data.get("sidebar_marker")}, "background": {"pre": pre.get("background"), "post": data.get("background")}}, "loading_observed": bool(loading_frames), "prior_body_removed": bool(loading_frames) and all(frame.get("prior_body_visible") is False for frame in loading_frames), "target_page": to_page, "final_agreement": data.get("selected") == to_page and to_page in data.get("body", ""), "final_url": data.get("url", ""), "browser_user_agent": data.get("browser_user_agent", "")}
return {"frames": observations, "document_navigation_events": document_events, "history_events": data.get("history_events", []), "stable_shell": {"document_marker": {"pre": pre.get("document_marker"), "post": data.get("document_marker")}, "sidebar_marker": {"pre": pre.get("sidebar_marker"), "post": data.get("sidebar_marker")}, "background": {"pre": pre.get("background"), "post": data.get("background")}, "surfaces": {"pre": pre.get("surfaces"), "post": data.get("surfaces")}}, "loading_observed": bool(loading_frames), "prior_body_removed": bool(loading_frames) and all(frame.get("prior_body_visible") is False for frame in loading_frames), "target_page": to_page, "final_agreement": data.get("selected") == to_page and to_page in data.get("body", ""), "final_url": data.get("url", ""), "browser_user_agent": data.get("browser_user_agent", "")}
def transition_capture(chrome: str, base_url: str, from_page: str, to_page: str, output_dir: Path, devtools_port: int,