Gate geometry evidence on matching DOM targets

This commit is contained in:
SGavrl committed 2026-08-04 13:49:08 +00:00
1 parent 98e45bbd1d
commit 8bc5221aad
2 files changed
+468 -17

No files matched your search

+257 -17
View File
@@ -63,6 +63,7 @@ BRAND_PERMUTATIONS = [
[2, 1, 0],
]
GEOMETRY_PROBE_RECT_LIMIT = 200
GEOMETRY_PROBE_SUBTREE_LIMIT = 80
FEATURE_PROBE_SCAN_LIMIT = 2000
FEATURE_PROBE_CANDIDATE_LIMIT = 40
CAPTURE_BOUNDARY_PHASE = "capture-boundary-before-screenshot"
@@ -72,6 +73,31 @@ RESOURCE_WARMUP_PHASE = "before-final-scroll-reassert-and-state-sample"
def geometry_probe_javascript(selectors_expression):
"""Return bounded, per-selector geometry sampling JavaScript."""
return (
"const geometryClassName=element=>String("
"element.getAttribute&&element.getAttribute('class')||'');"
"const sampleGeometryDom=element=>{"
"const descendants=element.querySelectorAll('*');const nodes=[element];"
"for(let geometryIndex=0;geometryIndex<descendants.length&&nodes.length<"
f"{GEOMETRY_PROBE_SUBTREE_LIMIT}"
";geometryIndex++)nodes.push(descendants[geometryIndex]);"
"const nodeIndices=new Map(nodes.map((node,index)=>[node,index]));"
"const describe=(node,index)=>({"
"index:index,parent_index:index===0?null:"
"(nodeIndices.has(node.parentElement)?nodeIndices.get(node.parentElement):null),"
"tag:String(node.tagName||'').toLowerCase(),id:node.id||'',"
"class_name:geometryClassName(node),"
"child_element_count:node.children.length});"
"return {tag:String(element.tagName||'').toLowerCase(),"
"id:element.id||'',class_name:geometryClassName(element),"
"child_node_count:element.childNodes.length,"
"child_element_count:element.children.length,"
"children:Array.from(element.children).slice(0,20).map(child=>({"
"tag:String(child.tagName||'').toLowerCase(),id:child.id||'',"
"class_name:geometryClassName(child)})),"
"subtree_element_count:descendants.length+1,"
f"subtree_limit:{GEOMETRY_PROBE_SUBTREE_LIMIT},"
f"subtree_truncated:descendants.length+1>{GEOMETRY_PROBE_SUBTREE_LIMIT},"
"subtree:nodes.map(describe)};};"
"const sampleGeometrySelector=selector=>{try{"
"const elements=Array.from(document.querySelectorAll(selector));"
"const rects=elements.slice(0,"
@@ -83,13 +109,7 @@ def geometry_probe_javascript(selectors_expression):
"const clientRectCount=element.getClientRects().length;"
"return {index:index,x:rect.left,y:rect.top,"
"width:rect.width,height:rect.height,"
"dom:{tag:String(element.tagName||'').toLowerCase(),"
"id:element.id||'',class_name:typeof element.className==='string'"
"?element.className:'',child_node_count:element.childNodes.length,"
"child_element_count:element.children.length,"
"children:Array.from(element.children).slice(0,20).map(child=>({"
"tag:String(child.tagName||'').toLowerCase(),id:child.id||'',"
"class_name:typeof child.className==='string'?child.className:''}))},"
"dom:sampleGeometryDom(element),"
"computed:{display:style.display,float:style.cssFloat,"
"clear:style.clear,position:style.position,"
"visibility:style.visibility,opacity:style.opacity,"
@@ -1162,6 +1182,7 @@ def chromium_capture_signature(state):
"height",
"visible",
"client_rect_count",
"dom",
)
}
for rect in probe.get("rects") or []
@@ -1639,8 +1660,107 @@ def classify_fidelity_metric(capture_purpose, state_comparable, metrics_present)
}
def canonical_geometry_dom_structure(dom):
"""Return a stable, style-independent form of a bounded DOM descriptor."""
if not isinstance(dom, dict):
return None
def class_tokens(value):
if not isinstance(value, str):
return []
return sorted(set(value.split()))
def canonical_node(node):
if not isinstance(node, dict):
return None
return {
"parent_index": node.get("parent_index"),
"tag": node.get("tag"),
"id": node.get("id") or "",
"class_tokens": class_tokens(node.get("class_name")),
"child_element_count": node.get("child_element_count"),
}
subtree = dom.get("subtree")
if isinstance(subtree, list):
return {
"source": "bounded-subtree",
"subtree_element_count": dom.get("subtree_element_count"),
"subtree_truncated": bool(dom.get("subtree_truncated")),
"nodes": [canonical_node(node) for node in subtree],
}
# Pre-gate reports only sampled direct children. They are useful raw
# provenance but cannot establish that deeper target subtrees are equal.
return None
def compare_geometry_dom_structures(obscura_dom, chromium_dom):
"""Classify whether two geometry rects address the same DOM structure."""
obscura_structure = canonical_geometry_dom_structure(obscura_dom)
chromium_structure = canonical_geometry_dom_structure(chromium_dom)
available = obscura_structure is not None and chromium_structure is not None
structures_equal = available and obscura_structure == chromium_structure
truncated = available and (
obscura_structure.get("subtree_truncated") is True
or chromium_structure.get("subtree_truncated") is True
)
comparable = structures_equal and not truncated
reasons = []
if not available:
reasons.append("target-subtree-descriptor-unavailable")
elif not structures_equal:
reasons.append("target-subtree-structure-mismatch")
elif truncated:
reasons.append("target-subtree-descriptor-truncated")
return {
"available": available,
"comparable": comparable,
"classification": (
"comparable"
if comparable
else "insufficient-target-structure"
if not available or truncated
else "different-target-structure"
),
"reasons": reasons,
"obscura": obscura_structure,
"chromium": chromium_structure,
}
def geometry_verdict_exclusions(comparisons):
"""Return selector-scoped exclusions without changing full-page fidelity."""
return [
{
"index": comparison.get("index"),
"selector": comparison.get("selector"),
"reasons": comparison.get("geometry_verdict_exclusion_reasons") or [],
}
for comparison in comparisons or []
if comparison.get("geometry_verdict_valid") is not True
]
def summarize_geometry_verdicts(comparisons):
"""Summarize selector-level eligibility without folding it into pixels."""
comparisons = comparisons or []
valid = [
comparison
for comparison in comparisons
if comparison.get("geometry_verdict_valid") is True
]
exclusions = geometry_verdict_exclusions(comparisons)
return {
"valid_selectors": len(valid),
"excluded_selectors": len(exclusions),
"all_selectors_valid": bool(comparisons) and not exclusions,
"excluded": exclusions,
}
def compare_geometry_probes(obscura, chromium):
"""Report raw per-selector deltas without reducing them to a verdict."""
"""Report raw deltas and gate geometry verdicts on target structure."""
obscura_probes = (obscura or {}).get("geometry_probes") or []
chromium_probes = (chromium or {}).get("geometry_probes") or []
comparisons = []
@@ -1654,9 +1774,14 @@ def compare_geometry_probes(obscura, chromium):
obscura_rects = (obscura_probe or {}).get("rects") or []
chromium_rects = (chromium_probe or {}).get("rects") or []
rect_deltas = []
subtree_comparisons = []
for rect_index in range(min(len(obscura_rects), len(chromium_rects))):
obscura_rect = obscura_rects[rect_index]
chromium_rect = chromium_rects[rect_index]
subtree_comparison = compare_geometry_dom_structures(
obscura_rect.get("dom"), chromium_rect.get("dom")
)
subtree_comparisons.append(subtree_comparison)
deltas = {}
for field in ("x", "y", "width", "height"):
left = obscura_rect.get(field)
@@ -1687,12 +1812,53 @@ def compare_geometry_probes(obscura, chromium):
"obscura": obscura_rect.get("visible"),
"chromium": chromium_rect.get("visible"),
},
"target_subtree_comparability": subtree_comparison,
"geometry_delta_valid": subtree_comparison["comparable"],
"computed_difference_count": len(computed_differences),
"computed_differences": computed_differences,
}
)
obscura_count = (obscura_probe or {}).get("count")
chromium_count = (chromium_probe or {}).get("count")
selector_left = (obscura_probe or {}).get("selector")
selector_right = (chromium_probe or {}).get("selector")
query_valid = (
(obscura_probe or {}).get("valid") is True
and (chromium_probe or {}).get("valid") is True
)
counts_equal = (
isinstance(obscura_count, int)
and not isinstance(obscura_count, bool)
and isinstance(chromium_count, int)
and not isinstance(chromium_count, bool)
and obscura_count == chromium_count
)
verdict_exclusions = []
if not query_valid:
verdict_exclusions.append("selector-query-invalid")
if selector_left != selector_right:
verdict_exclusions.append("selector-mismatch")
if not counts_equal:
verdict_exclusions.append("target-count-mismatch")
elif obscura_count == 0:
verdict_exclusions.append("no-matched-targets")
if counts_equal and isinstance(obscura_count, int) and obscura_count > 0:
expected_rects = min(
obscura_count,
(obscura_probe or {}).get("rect_limit")
or GEOMETRY_PROBE_RECT_LIMIT,
)
if len(subtree_comparisons) != expected_rects:
verdict_exclusions.append("paired-target-descriptors-incomplete")
subtree_reasons = {
reason
for comparison in subtree_comparisons
for reason in comparison["reasons"]
}
verdict_exclusions.extend(sorted(subtree_reasons))
# Preserve reason order while avoiding duplicates.
verdict_exclusions = list(dict.fromkeys(verdict_exclusions))
geometry_verdict_valid = not verdict_exclusions
comparisons.append(
{
"index": index,
@@ -1705,6 +1871,20 @@ def compare_geometry_probes(obscura, chromium):
"obscura": (obscura_probe or {}).get("valid"),
"chromium": (chromium_probe or {}).get("valid"),
},
"geometry_verdict_valid": geometry_verdict_valid,
"geometry_verdict_exclusion_reasons": verdict_exclusions,
"target_subtree_comparability": {
"pairs_compared": len(subtree_comparisons),
"all_comparable": bool(subtree_comparisons)
and all(
comparison["comparable"]
for comparison in subtree_comparisons
),
"bounded_descriptor": (
"tag/id/class tokens, child counts, and parent-indexed "
"descendant structure"
),
},
"errors": {
"obscura": (obscura_probe or {}).get("error"),
"chromium": (chromium_probe or {}).get("error"),
@@ -2082,6 +2262,14 @@ def main():
"stylesheet mirror nodes, because Chromium's CSSOM does not "
"serialize fetched stylesheet text into outerHTML."
),
"selector_target_state": (
"Each geometry target carries a bounded, parent-indexed DOM "
"subtree descriptor. Selector geometry remains raw diagnostic "
"data when match counts or target structures differ, but it is "
"explicitly excluded from a geometry verdict. A selector-scoped "
"exclusion does not by itself invalidate unrelated full-page "
"fidelity metrics."
),
"resource_readiness": (
"Both engines take and discard one screenshot before sampling "
"resource readiness, then yield one bounded task turn. This "
@@ -2125,6 +2313,7 @@ def main():
"selectors": args.geometry_selector,
"coordinate_space": "viewport-css-px",
"rect_limit_per_selector": GEOMETRY_PROBE_RECT_LIMIT,
"subtree_element_limit_per_rect": GEOMETRY_PROBE_SUBTREE_LIMIT,
"visibility": (
"practical rendered-box heuristic: client rect exists, positive "
"bounding size, display/visibility permit paint, and opacity > 0"
@@ -2132,8 +2321,11 @@ def main():
"comparison_semantics": (
"raw per-selector counts, rect deltas in document order, "
"visibility observations, and exact differences between the "
"computed values that won each engine's cascade; no aggregate "
"parity verdict"
"computed values that won each engine's cascade. Geometry "
"verdict validity additionally requires equal selector counts "
"and matching bounded tag/id/class/child structure for every "
"paired target; selector exclusions do not invalidate the "
"full-page fidelity metric"
),
}
manifest["automatic_feature_probes"] = {
@@ -2392,11 +2584,18 @@ def main():
)
)
if args.geometry_selector:
geometry_comparison = compare_geometry_probes(
page_result["obscura"].get("state"),
chromium_state,
)
page_result["geometry_probe_comparison"] = (
compare_geometry_probes(
page_result["obscura"].get("state"),
chromium_state,
)
geometry_comparison
)
page_result["geometry_verdict_exclusions"] = (
geometry_verdict_exclusions(geometry_comparison)
)
page_result["geometry_verdict_eligibility"] = (
summarize_geometry_verdicts(geometry_comparison)
)
if (
chrome_ok
@@ -2427,10 +2626,21 @@ def main():
)
)
if args.geometry_selector:
baseline_geometry_comparison = compare_geometry_probes(
page_result["baseline"].get("state"),
chromium_state,
)
page_result["baseline_geometry_probe_comparison"] = (
compare_geometry_probes(
page_result["baseline"].get("state"),
chromium_state,
baseline_geometry_comparison
)
page_result["baseline_geometry_verdict_exclusions"] = (
geometry_verdict_exclusions(
baseline_geometry_comparison
)
)
page_result["baseline_geometry_verdict_eligibility"] = (
summarize_geometry_verdicts(
baseline_geometry_comparison
)
)
@@ -2566,11 +2776,23 @@ def main():
edge_2d_delta = page_result.get("delta_vs_baseline", {}).get(
"edge_bidirectional_mean_distance_px"
)
geometry_eligibility = page_result.get(
"geometry_verdict_eligibility"
)
geometry_summary = (
"-"
if geometry_eligibility is None
else (
f"{geometry_eligibility['valid_selectors']}-valid/"
f"{geometry_eligibility['excluded_selectors']}-excluded"
)
)
print(
f"{name:84} "
f"mode={args.capture_purpose} "
f"state={page_result.get('state_comparability', {}).get('classification', 'unavailable')} "
f"fidelity={'valid' if page_result['fidelity_metric_valid'] else 'excluded'} "
f"geometry={geometry_summary} "
f"p>50_raw={metric if metric is not None else 'capture-fail'} "
f"edge_bbox={edge_bbox if edge_bbox is not None else '-'} "
f"edge_row={edge_row if edge_row is not None else '-'} "
@@ -2600,6 +2822,24 @@ def main():
"and metrics but do not contribute fidelity evidence"
),
}
manifest["geometry_verdict_eligibility"] = {
"valid_selectors": sum(
(page.get("geometry_verdict_eligibility") or {}).get(
"valid_selectors", 0
)
for page in manifest["pages"]
),
"excluded_selectors": sum(
(page.get("geometry_verdict_eligibility") or {}).get(
"excluded_selectors", 0
)
for page in manifest["pages"]
),
"semantics": (
"selector-level eligibility only; exclusions retain raw geometry "
"diagnostics and do not by themselves invalidate full-page fidelity"
),
}
write_results(results_path, manifest)
failed = [
page["name"]
+211
View File
@@ -611,6 +611,11 @@ class GeometryProbeTests(unittest.TestCase):
expression, args = page.calls[0]
self.assertEqual(args, (selectors,))
self.assertIn("querySelectorAll(selector)", expression)
self.assertIn("sampleGeometryDom(element)", expression)
self.assertIn("subtree_element_count", expression)
self.assertIn("parent_index", expression)
self.assertIn("nodes.length<80", expression)
self.assertNotIn("Array.from(element.querySelectorAll('*'))", expression)
self.assertIn("catch(error)", expression)
self.assertIn("font_family:style.fontFamily", expression)
self.assertIn("line_height:style.lineHeight", expression)
@@ -703,6 +708,11 @@ class GeometryProbeTests(unittest.TestCase):
comparison = paired_corpus.compare_geometry_probes(obscura, chromium)
self.assertEqual(comparison[0]["counts"]["delta"], 1)
self.assertFalse(comparison[0]["geometry_verdict_valid"])
self.assertIn(
"target-count-mismatch",
comparison[0]["geometry_verdict_exclusion_reasons"],
)
self.assertEqual(
comparison[0]["rect_deltas"][0]["delta"],
{"x": 1, "y": -2, "width": 2, "height": 0},
@@ -722,9 +732,210 @@ class GeometryProbeTests(unittest.TestCase):
},
)
self.assertEqual(comparison[1]["valid"], {"obscura": False, "chromium": False})
self.assertFalse(comparison[1]["geometry_verdict_valid"])
self.assertIsNone(comparison[1]["counts"]["delta"])
self.assertEqual(comparison[1]["rects_compared"], 0)
def test_geometry_verdict_accepts_matching_bounded_subtrees(self):
def descriptor(root_classes):
return {
"subtree_element_count": 2,
"subtree_truncated": False,
"subtree": [
{
"index": 0,
"parent_index": None,
"tag": "div",
"id": "card",
"class_name": root_classes,
"child_element_count": 1,
},
{
"index": 1,
"parent_index": 0,
"tag": "img",
"id": "",
"class_name": "hero",
"child_element_count": 0,
},
],
}
def state(classes, x):
return {
"geometry_probes": [
{
"selector": "#card",
"valid": True,
"count": 1,
"rect_limit": 200,
"rects": [
{
"x": x,
"y": 20,
"width": 100,
"height": 40,
"dom": descriptor(classes),
}
],
}
]
}
comparison = paired_corpus.compare_geometry_probes(
state("featured card", 11), state("card featured", 10)
)
self.assertTrue(comparison[0]["geometry_verdict_valid"])
self.assertEqual(comparison[0]["geometry_verdict_exclusion_reasons"], [])
self.assertTrue(comparison[0]["rect_deltas"][0]["geometry_delta_valid"])
self.assertEqual(paired_corpus.geometry_verdict_exclusions(comparison), [])
def test_geometry_verdict_excludes_different_dynamic_subtree_only(self):
def state(child_class):
return {
"geometry_probes": [
{
"selector": "#ad",
"valid": True,
"count": 1,
"rect_limit": 200,
"rects": [
{
"x": 1,
"y": 2,
"width": 300,
"height": 200,
"dom": {
"subtree_element_count": 2,
"subtree_truncated": False,
"subtree": [
{
"index": 0,
"parent_index": None,
"tag": "div",
"id": "ad",
"class_name": "",
"child_element_count": 1,
},
{
"index": 1,
"parent_index": 0,
"tag": "a",
"id": "",
"class_name": child_class,
"child_element_count": 0,
},
],
},
}
],
}
]
}
comparison = paired_corpus.compare_geometry_probes(
state("ad-image-large"), state("ad-image-small")
)
probe = comparison[0]
self.assertFalse(probe["geometry_verdict_valid"])
self.assertIn(
"target-subtree-structure-mismatch",
probe["geometry_verdict_exclusion_reasons"],
)
self.assertFalse(probe["rect_deltas"][0]["geometry_delta_valid"])
self.assertEqual(
paired_corpus.geometry_verdict_exclusions(comparison)[0]["selector"],
"#ad",
)
summary = paired_corpus.summarize_geometry_verdicts(comparison)
self.assertEqual(summary["valid_selectors"], 0)
self.assertEqual(summary["excluded_selectors"], 1)
self.assertFalse(summary["all_selectors_valid"])
# Selector-scoped target mismatch does not poison unrelated page pixels.
fidelity = paired_corpus.classify_fidelity_metric(
"representative-fidelity", True, metrics_present=True
)
self.assertTrue(fidelity["fidelity_metric_valid"])
def test_legacy_geometry_report_is_insufficient_for_verdict(self):
legacy_dom = {
"tag": "div",
"id": "card",
"class_name": "card",
"child_element_count": 0,
"children": [],
}
state = {
"geometry_probes": [
{
"selector": "#card",
"valid": True,
"count": 1,
"rects": [
{
"x": 0,
"y": 0,
"width": 20,
"height": 20,
"dom": legacy_dom,
}
],
}
]
}
comparison = paired_corpus.compare_geometry_probes(state, state)
self.assertFalse(comparison[0]["geometry_verdict_valid"])
self.assertIn(
"target-subtree-descriptor-unavailable",
comparison[0]["geometry_verdict_exclusion_reasons"],
)
def test_truncated_equal_subtrees_are_insufficient_for_verdict(self):
dom = {
"subtree_element_count": 100,
"subtree_truncated": True,
"subtree": [
{
"index": 0,
"parent_index": None,
"tag": "div",
"id": "target",
"class_name": "same-prefix",
"child_element_count": 99,
}
],
}
state = {
"geometry_probes": [
{
"selector": "#target",
"valid": True,
"count": 1,
"rects": [
{
"x": 0,
"y": 0,
"width": 20,
"height": 20,
"dom": dom,
}
],
}
]
}
comparison = paired_corpus.compare_geometry_probes(state, state)
rect_state = comparison[0]["rect_deltas"][0][
"target_subtree_comparability"
]
self.assertFalse(comparison[0]["geometry_verdict_valid"])
self.assertEqual(
rect_state["classification"], "insufficient-target-structure"
)
self.assertIn(
"target-subtree-descriptor-truncated",
comparison[0]["geometry_verdict_exclusion_reasons"],
)
def test_feature_comparison_reports_presence_computed_geometry_and_quad_capability(self):
obscura = {
"feature_probes": {