Validate final browser evidence without rewriting reviewed changes

Governance run 37016171545 rejected the original malformed timestamp even though a later reviewed receipt corrected it for identical runtime bytes. Use the existing fail-closed integration-range guard in CI while retaining per-commit canonical checks. Exercise the workflow shell with valid additive correction and invalid missing-base or unverified-content fixtures.

Change-source: pulse-maintainer
This commit is contained in:
pulse-triage[bot] 2026-10-02 15:04:12 +01:00
parent 3b42a3da67
commit 46d2727828
3 changed files with 66 additions and 9 deletions

View file

@ -130,16 +130,27 @@ jobs:
status=1
fi
fi
if ! git diff-tree --no-commit-id --name-only -r "${commit}" \
| python3 scripts/release_control/browser_verification_guard.py \
--files-from-stdin --commit "${commit}"; then
echo "Browser verification guard failed for commit ${commit}."
status=1
fi
echo "::endgroup::"
done
exit ${status}
- name: Validate final frontend browser evidence
shell: bash
env:
WORKFLOW_OUTPUT_1: ${{ steps.diff.outputs.range }}
run: |
set -euo pipefail
range="${WORKFLOW_OUTPUT_1}"
if [ -z "${range}" ]; then
printf '' | python3 scripts/release_control/browser_verification_guard.py --files-from-stdin
exit 0
fi
# Preserve reviewed commits: a later non-merge receipt can correct
# metadata for the same verified bytes. Require coverage of every
# final frontend path, including merge resolutions; never waive proof.
python3 scripts/release_control/browser_verification_guard.py \
--base "${range%%...*}" --commit "${range##*...}"
- name: Validate Pulse Intelligence release-gate schema
run: python3 scripts/release_control/pulse_intelligence_gate.py --validate-only --matrix docs/release-control/v6/internal/pulse-intelligence-release-gate.json

View file

@ -246,8 +246,8 @@ must update that receipt with:
7. a UTC verification timestamp and `result: "passed"`
`scripts/release_control/browser_verification_guard.py` enforces the receipt
against the staged index locally and against each changed commit in canonical
governance CI. A stale receipt, later source edit, incomplete path coverage,
against the staged index locally and against the final integration range in
canonical governance CI. Canonical contract completion remains checked per commit. A stale receipt, later source edit, incomplete path coverage,
missing responsive viewport, or omitted state/interaction evidence is a hard failure. Agents may
print a non-passing receipt skeleton with
`python3 scripts/release_control/browser_verification_guard.py --print-template`
@ -265,7 +265,11 @@ bound to that commit's parent and to that commit's tree. Receipts written by
merge commits are never evidence, because a conflict resolution has no browser
run behind it. Content that changed after its browser pass, including a
correction commit, a conflict resolution, or two changes to one file, needs a
fresh receipt for the final content in its own non-merge commit.
fresh receipt for the final content in its own non-merge commit. CI uses this
same range mode so an additive receipt correction can retain reviewed commit
identities. An invalid historical receipt contributes no coverage; a later
valid receipt must still bind its own parent and the exact final content. An
unavailable range base fails closed rather than narrowing browser coverage.
A source diff that is byte-for-byte the locked Prettier output of its parent
has no rendered behavior or visual delta and does not require a new browser

View file

@ -9,6 +9,7 @@ import os
from pathlib import Path
import subprocess
import tempfile
import textwrap
import unittest
from unittest.mock import patch
@ -292,6 +293,47 @@ class IntegrationRangeTest(unittest.TestCase):
self.commit("record proof")
self.assertEqual(self.run_range()[0], 0)
def run_workflow_browser_step(self, *, base: str | None = None) -> subprocess.CompletedProcess:
workflow = (REPO_ROOT / ".github/workflows/canonical-governance.yml").read_text()
step = workflow.split(" - name: Validate final frontend browser evidence\n", 1)[1]
step = step.split("\n - name:", 1)[0]
command = textwrap.dedent(step.split(" run: |\n", 1)[1])
for name in ("browser_verification_guard.py", "format_staged_frontend.py"):
self.write("scripts/release_control/" + name,
(REPO_ROOT / "scripts/release_control" / name).read_text())
return subprocess.run(
["bash", "-c", command], cwd=self.repo_root, capture_output=True, text=True,
env={**self.env, "WORKFLOW_OUTPUT_1":
f"{base or self.base}...{self.git('rev-parse', 'HEAD')}"},
)
def test_workflow_accepts_additive_receipt_correction_but_not_unverified_edit(self) -> None:
self.write(CHANGED_PATH, "export const a = 1;\n")
self.write_receipt([CHANGED_PATH])
payload = json.loads((self.repo_root / RECEIPT_PATH).read_text())
payload["verified_at"] = "2026-10-02T13:00:00+00:00"
self.write(RECEIPT_PATH, json.dumps(payload))
original = self.commit("frontend with malformed timestamp")
self.assertNotEqual(self.run_workflow_browser_step().returncode, 0)
self.write_receipt([CHANGED_PATH])
self.commit("correct receipt without rewriting source history")
corrected = self.run_workflow_browser_step()
self.assertEqual(corrected.returncode, 0, corrected.stdout + corrected.stderr)
self.git("merge-base", "--is-ancestor", original, "HEAD")
self.write(CHANGED_PATH, "export const a = 2;\n")
self.commit("unverified later edit")
self.assertNotEqual(self.run_workflow_browser_step().returncode, 0)
def test_workflow_checks_merge_resolution_and_rejects_missing_range_base(self) -> None:
self.merge_advanced_main_into_candidate()
self.assertEqual(self.run_workflow_browser_step().returncode, 0)
self.assertNotEqual(self.run_workflow_browser_step(base="b" * 40).returncode, 0)
self.write(CHANGED_PATH, "export const a = 3;\n")
self.commit("unverified integration edit")
self.assertNotEqual(self.run_workflow_browser_step().returncode, 0)
def test_blocks_frontend_edit_after_merge_without_fresh_proof(self) -> None:
self.merge_advanced_main_into_candidate()
self.write(CHANGED_PATH, "export const a = 2;\n")