Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/benchmarks/authority/VALIDATION_REPORT.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ write_html(evaluation, "authority-benchmark.html",
history=history, label="current-revision")
```

Supply at most 20 historical evaluations per HTML report. A larger history
raises `ValueError` before rendering or opening the output file; an existing
report remains unchanged. Select a smaller history explicitly rather than
relying on silent truncation. This bounds the number of embedded historical
briefs, not the size of an individual evaluation.

The current evaluation is appended after the supplied history. The trend
table shows revision labels, corpus case counts, Lane-A status,
end-to-end exercised authority attribution coverage (including matched
Expand Down
10 changes: 9 additions & 1 deletion safeai/benchmark/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,9 @@ def _history_html(snapshots: Sequence[tuple[str, dict]]) -> str:
return "\n".join(out)


MAX_HTML_HISTORY = 20


def render_html(aggregate: dict, *, history: Sequence[tuple[str, dict]] = (),
label: str = "Current") -> str:
"""Render an offline HTML brief with optional, explicitly ordered history.
Expand All @@ -508,7 +511,11 @@ def render_html(aggregate: dict, *, history: Sequence[tuple[str, dict]] = (),
precedes the current aggregate; nothing is sorted, scanned or fetched.
The current brief includes its exact Markdown source for comparison;
historical briefs retain their full text in expandable sections.
At most 20 historical evaluations are accepted; excess history raises
ValueError before any brief is rendered rather than silently truncating it.
"""
if len(history) > MAX_HTML_HISTORY:
raise ValueError(f"HTML history accepts at most {MAX_HTML_HISTORY} evaluations")
markdown = render_markdown(aggregate)
out = ['<!doctype html>', '<html lang="en"><head><meta charset="utf-8">',
'<meta name="viewport" content="width=device-width, initial-scale=1">',
Expand All @@ -533,6 +540,7 @@ def write_html(aggregate: dict, path: str | os.PathLike, *,
history: Sequence[tuple[str, dict]] = (),
label: str = "Current") -> str | os.PathLike:
"""Write deterministic UTF-8 HTML with LF newlines on every platform."""
document = render_html(aggregate, history=history, label=label)
with open(path, "w", encoding="utf-8", newline="\n") as fh:
fh.write(render_html(aggregate, history=history, label=label))
fh.write(document)
return path
55 changes: 55 additions & 0 deletions tests/test_authority_benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import re
from html.parser import HTMLParser

import pytest

from safeai.benchmark import compare, metrics, report, runner

REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
Expand Down Expand Up @@ -550,3 +552,56 @@ def test_html_history_keeps_explicit_order_counts_and_corpus_caveat():
assert "mix or truth do not prove scanner improvement" in html
assert "Historical brief: z-old" in html
assert "".join(page.markdown) == report.render_markdown(current)


def test_html_history_limit_preserves_order_at_the_boundary():
history = [(f"revision-{index}", {}) for index in range(20)]
page = _BenchmarkHTML(report.render_html({}, history=history))
assert [row[0] for row in page.rows if row[0].startswith("revision-")] == [
label for label, _ in history]


@pytest.mark.parametrize("existing", [False, True])
def test_html_excess_history_is_rejected_before_rendering_or_writing(tmp_path, monkeypatch, existing):
destination = tmp_path / "report.html"
if existing:
destination.write_bytes(b"previous report")

def unexpected_render(_):
pytest.fail("excess history must be rejected before rendering any brief")

monkeypatch.setattr(report, "render_markdown", unexpected_render)
history = [("revision", {})] * 21
with pytest.raises(ValueError, match="at most 20"):
report.render_html({}, history=history)
with pytest.raises(ValueError, match="at most 20"):
report.write_html({}, destination, history=history)
if existing:
assert destination.read_bytes() == b"previous report"
else:
assert not destination.exists()


@pytest.mark.parametrize("payload", [
'</style><script>alert(1)</script>',
'</summary><svg onload="alert(1)">',
'\"><iframe src="https://example.invalid"></iframe>',
'</pre><img src=x onerror=alert(1)>',
'<a href="javascript:alert(1)">click</a>',
'&lt;script&gt;alert(1)&lt;/script&gt;',
])
def test_html_history_keeps_adversarial_labels_and_metrics_inert(payload):
aggregate = _committed_evaluation()
aggregate["digest"] = payload
aggregate["cases"] = [{"id": payload, "passed": False, "failures": [payload]}]
aggregate["metrics"]["attribution"]["end_to_end"] = payload
aggregate["metrics"]["canonical"]["false_escalation_rate"] = {
"status": "measured", "value": payload}
before = copy.deepcopy(aggregate)
page = _BenchmarkHTML(report.render_html(aggregate, history=[(payload, aggregate)], label=payload))
assert not {tag for tag, _ in page.tags} & {"script", "svg", "img", "iframe", "a", "link"}
assert not any(key.startswith("on") for _, attrs in page.tags for key in attrs)
assert all(re.fullmatch(r"0(?:\.\d+)?|1(?:\.0+)?", m["value"]) for m in page.meters)
assert any(row[0] == payload for row in page.rows)
assert "".join(page.markdown) == report.render_markdown(aggregate)
assert aggregate == before
Loading