Delete the retired shared browser receipt

The per-commit receipt change left
frontend-modern/browser-verification.json in the tree, frozen, so that
it would not conflict with the pull requests still rewriting it. Nothing
reads the file at the tip: range mode reads a receipt from the commit
that recorded it. Left in place it invites the old habit and keeps those
pull requests conflicting with each other on it.

The guard now refuses a staged tree or a range tip that carries the
file, rather than only a commit that writes it. Without that, resolving
the modify/delete conflict by keeping the file changes nothing against
HEAD, passes the hook, and restores the file on main when the branch
merges.

A pull request that still rewrites the file is repaired by merging main
and removing the file with git rm. Its commits remain valid evidence in
range mode.
This commit is contained in:
rcourtman
2026-10-03 11:15:28 +01:00
parent fe82cb29a3
commit 2a247e244e
4 changed files with 56 additions and 134 deletions
@@ -255,12 +255,12 @@ frontend pull requests conflict. Until 2026-10-03 each frontend commit rewrote
`frontend-modern/browser-verification.json`, so the first pull request to merge
left every other one conflicting, and each conflict cost a rebuild on the new
`main` and a fresh browser pass. Files that are only ever added cannot collide.
The shared file is retired: the guard refuses any commit that writes it. Range
mode still accepts it as evidence from a commit that modified it, because that
commit was verified while its own parent still carried the file. A pull request
that still rewrites it is repaired by merging `main` and taking `main`'s copy of
the file, not by a rebuild. The file is deleted in a change of its own, because
deleting it conflicts with every pull request that still rewrites it.
The shared file is retired and deleted. The guard refuses a staged tree or a
range tip that carries it, so resolving a merge by keeping the file cannot
bring it back. Range mode still accepts it as evidence from a commit that
modified it, because that commit was verified while its own parent still
carried the file. A pull request that still rewrites it is repaired by merging
`main` and removing the file with `git rm`, not by a rebuild.
`scripts/release_control/browser_verification_guard.py` enforces the receipt
against the staged index locally and against the final integration range in
-100
View File
@@ -1,100 +0,0 @@
{
"version": 1,
"base_sha": "0e90236d3bb901dc84d4d07e70d4d2a09d5e6705",
"verified_at": "2026-10-02T23:55:48Z",
"result": "passed",
"changed_paths": [
"frontend-modern/src/components/shared/Table.tsx"
],
"content_sha256": {
"frontend-modern/src/components/shared/Table.tsx": "8e5029d5ee9dddc86a4733fa1e7249793cdbedb135f3634e75112f8fb3b327c9"
},
"routes": [
"/proxmox/overview",
"/proxmox/storage",
"/proxmox/replication",
"/proxmox/ceph",
"/proxmox/mail",
"/docker/overview",
"/docker/images",
"/docker/storage",
"/docker/networks",
"/docker/swarm",
"/kubernetes/overview",
"/kubernetes/nodes",
"/kubernetes/workloads",
"/kubernetes/services",
"/kubernetes/storage",
"/kubernetes/configuration",
"/kubernetes/events",
"/truenas/overview",
"/truenas/storage",
"/truenas/services",
"/truenas/apps",
"/truenas/vms",
"/truenas/shares",
"/truenas/protection",
"/vmware/overview",
"/vmware/storage",
"/vmware/networks",
"/vmware/health",
"/vmware/activity",
"/standalone/machines",
"/standalone/availability",
"/alerts/overview",
"/alerts/history",
"/alerts/thresholds",
"/actions",
"/settings/infrastructure",
"/settings/monitoring",
"/settings/pulse-intelligence",
"/settings/organization",
"/settings/security-users",
"/settings/security-webhooks"
],
"viewports": [
{
"width": 1440,
"height": 900
},
{
"width": 1280,
"height": 900
},
{
"width": 900,
"height": 900
},
{
"width": 700,
"height": 900
},
{
"width": 560,
"height": 900
},
{
"width": 390,
"height": 900
}
],
"states": [
"Mock stack on origin/main plus this change; Playwright sweep of every table on the routes above at all six viewports, before and after, recording computed th/td padding per cell and leaf elements with scrollWidth > clientWidth under hidden overflow (455 table states compared)",
"Default TableCell/TableHead with no caller padding keep 8px/12px horizontal and 2px/6px vertical padding (Settings infrastructure source and agent tables unchanged at 12px/12px/6px/6px)",
"Platform tables (Proxmox nodes, Docker hosts and containers, Kubernetes clusters/nodes/deployments/pods, TrueNAS systems, vSphere, Machines): cells 12px -> 8px horizontal at 700px and up and 8px -> 6px at 560/390px; header cells keep the stylesheet 6px horizontal and drop to 2px vertical, header rows 29px -> 21px; row height 32px unchanged; clipped-cell count improved in 89 table states, unchanged in 362; the 4 states counted worse are live mock-value drift (relative-time and usage labels) with identical or wider content boxes",
"Physical Disks table (Proxmox > Storage > Physical Disks): cells 8px -> 4px horizontal at 390px and 12px -> 6px at 700px, matching the composed px-1 sm:px-1.5 lg:px-2; clipped cells at 390px 93 -> 18; detail row 12px -> 16px horizontal at 700px and 1440px",
"Storage pools table unchanged at 6px/8px cell padding across 390, 560 and 700px",
"Inline detail rows (Proxmox node, Kubernetes controller, TrueNAS system expansions): detail cell padding 12px/12px/2px/2px -> 0, detail panel inset now only its own px-2 sm:px-4; incident timeline panel in Alert History gets its p-3",
"Alerts overview stats table: dot column right padding 12px -> 0 and value column 12px -> 6px at 700px and up as its classes name; Alert History header cells 12px -> 8px horizontal; AI cost table (Settings > Pulse Intelligence) Est. USD column 12px -> 4px as its px-1 names",
"Workloads table header cells now 6px/8px to match their raw td data cells; Proxmox replication compact-mode !px-1 override still applies at 390px",
"Reactive case: switching a cell class between px-1 and a non-padding class re-resolves the base padding (unit test)"
],
"interactions": [
"Load each route at 1440, 1280, 900, 700, 560 and 390px and run the padding and clip sweep over all tables",
"Click Physical Disks on Proxmox > Storage at 390, 560, 700 and 1440px and expand the first disk row",
"Expand the first node row on Proxmox > Overview, the first deployment row on Kubernetes > Workloads, the first host row on Docker > Overview and the first system row on TrueNAS > Overview at 390, 560, 700 and 1440px",
"Expand the first alert row incident timeline on Alerts > History at 1440px",
"Compare before and after JSON sweeps per route, tab, viewport and table; re-measure the four states counted worse three times each",
"Revert and restore the change on the running dev server to A/B the Kubernetes clusters memory bar label at 560px"
]
}
@@ -30,9 +30,7 @@ RECEIPT_DIR = "frontend-modern/browser-verification"
# The single shared receipt every frontend commit rewrote before per-commit
# receipts. It is retired: nothing may write it again. Range mode still reads
# it from commits that modified it, because those commits were verified
# honestly against a tree that carried it and are still merging. The file
# itself is deleted in a change of its own, since deleting it conflicts with
# every open pull request that still rewrites it.
# honestly against a tree that carried it and are still merging.
LEGACY_RECEIPT_PATH = "frontend-modern/browser-verification.json"
RECEIPT_FINGERPRINT_LENGTH = 16
DEFAULT_LANDED_REF = "origin/main"
@@ -452,7 +450,8 @@ PRUNE_SEPARATELY = (
RETIRED_RECEIPT = (
f"{LEGACY_RECEIPT_PATH} is retired and must not be written. Each commit now "
f"{LEGACY_RECEIPT_PATH} is retired and must not exist. Remove it with git rm; a "
"receipt it held stays valid in the commit that recorded it. Each commit now "
f"records its receipt in its own file under {RECEIPT_DIR}/, so concurrent "
"frontend changes no longer conflict."
)
@@ -472,11 +471,15 @@ def range_receipt_coverage(
that fails contributes no coverage and is reported as a diagnostic.
Violations are returned separately because they block the range even when
coverage is complete: a commit that both records and deletes receipts.
coverage is complete: a tip that still carries the retired shared receipt
(recreated, or kept while resolving a merge with the main that deleted
it), or a commit that both records and deletes receipts.
"""
covered: dict[str, set[str]] = {}
diagnostics: list[str] = []
violations: list[str] = []
if LEGACY_RECEIPT_PATH in blob_ids(head, [LEGACY_RECEIPT_PATH], repo_root=repo_root):
violations.append(RETIRED_RECEIPT)
for commit in receipt_commits_in_range(base, head, repo_root=repo_root):
try:
changes = receipt_changes(commit=commit, repo_root=repo_root)
@@ -677,20 +680,25 @@ def check_commit(
inherited = merged_in_paths(
sorted({*changes, *frontend_paths}), commit=commit, repo_root=repo_root
)
# The index is held to the tree it will become: a merge that keeps the
# shared receipt changes nothing against HEAD yet would restore it on
# main. A named commit is held only to what it writes itself.
retired_receipt_written = (
changes.get(LEGACY_RECEIPT_PATH, "D") != "D"
if commit
else LEGACY_RECEIPT_PATH
in blob_ids(None, [LEGACY_RECEIPT_PATH], repo_root=repo_root)
)
except subprocess.CalledProcessError as exc:
return blocked(f"unable to inspect browser verification state: {exc}")
changes = {path: status for path, status in changes.items() if path not in inherited}
if changes.get(LEGACY_RECEIPT_PATH, "D") != "D":
if retired_receipt_written:
return blocked(
RETIRED_RECEIPT,
advice=[
"While concluding a merge, take the merged parent's version: "
f"git checkout MERGE_HEAD -- {LEGACY_RECEIPT_PATH}"
if merging
else f"Unstage it and run: {GUARD_COMMAND} --write-template"
],
advice=[f"For a new receipt, run: {GUARD_COMMAND} --write-template"],
)
changes = {path: status for path, status in changes.items() if path not in inherited}
frontend_paths = [path for path in frontend_paths if path not in inherited]
recorded = sorted(
path for path, status in changes.items() if status != "D" and path != LEGACY_RECEIPT_PATH
@@ -61,6 +61,9 @@ class BrowserVerificationGuardTest(unittest.TestCase):
self.assertLess(formatter, guard)
def test_shared_receipt_is_retired_from_the_tree(self) -> None:
self.assertFalse((REPO_ROOT / LEGACY_RECEIPT_PATH).exists())
def test_frontend_runtime_paths_exclude_tests_and_receipt(self) -> None:
self.assertEqual(
frontend_runtime_paths(
@@ -722,23 +725,21 @@ class IntegrationRangeTest(GuardRepoTestCase):
class RetiredSharedReceiptTest(GuardRepoTestCase):
"""Work verified against the shared receipt keeps its evidence; nothing new may write it."""
"""Work verified against the shared receipt keeps its evidence; nothing new may use it."""
def setUp(self) -> None:
super().setUp()
self.write_receipt([CHANGED_PATH], at=LEGACY_RECEIPT_PATH)
self.base = self.commit("main while the shared receipt was current")
def shared_receipt_change(self, path: str, text: str, message: str) -> str:
self.write(path, text)
self.write_receipt([path], at=LEGACY_RECEIPT_PATH)
return self.commit(message)
def test_in_flight_commit_is_repaired_by_merging_main(self) -> None:
def test_in_flight_commit_survives_a_merge_of_the_retirement(self) -> None:
self.git("checkout", "--quiet", "-b", "in-flight", self.base)
self.shared_receipt_change(CHANGED_PATH, "export const a = 1;\n", "in flight")
self.write(CHANGED_PATH, "export const a = 1;\n")
self.write_receipt([CHANGED_PATH], at=LEGACY_RECEIPT_PATH)
self.commit("verified before the retirement")
self.git("checkout", "--quiet", "main")
landed = self.shared_receipt_change(OTHER_PATH, "export const b = 1;\n", "landed first")
self.git("rm", "--quiet", LEGACY_RECEIPT_PATH)
retired = self.commit("retire the shared receipt")
self.git("checkout", "--quiet", "in-flight")
merge = subprocess.run(
@@ -746,20 +747,33 @@ class RetiredSharedReceiptTest(GuardRepoTestCase):
"merge", "--quiet", "--no-ff", "main"],
cwd=self.repo_root, capture_output=True, text=True, env=self.env,
)
self.assertNotEqual(merge.returncode, 0, "both sides rewrote the shared receipt")
self.assertNotEqual(merge.returncode, 0, "modify/delete on the shared receipt")
# A hand-combined resolution is a new write of the retired file.
self.write(LEGACY_RECEIPT_PATH, "{}\n")
# Keeping the file changes nothing against HEAD, but would restore it
# on main once this branch merges.
self.git("add", LEGACY_RECEIPT_PATH)
status, _, stderr = self.run_guard()
self.assertEqual(status, 1)
self.assertIn(f"{LEGACY_RECEIPT_PATH} is retired", stderr)
self.assertIn("git checkout MERGE_HEAD", stderr)
self.git("checkout", "MERGE_HEAD", "--", LEGACY_RECEIPT_PATH)
self.git("rm", "--quiet", "--force", LEGACY_RECEIPT_PATH)
self.assertEqual(self.run_guard()[0], 0)
self.commit("merge main")
self.assertEqual(self.run_range(base=landed), (0, ""))
self.assertEqual(self.run_range(base=retired), (0, ""))
def test_recreating_the_shared_receipt_is_rejected_even_with_a_valid_one(self) -> None:
self.git("rm", "--quiet", LEGACY_RECEIPT_PATH)
retired = self.commit("retire the shared receipt")
self.write(CHANGED_PATH, "export const a = 1;\n")
self.write_receipt([CHANGED_PATH])
self.write_receipt([CHANGED_PATH], at=LEGACY_RECEIPT_PATH)
self.commit("verified, but also recreates the shared receipt")
status, stderr = self.run_range(base=retired)
self.assertEqual(status, 1)
self.assertIn(f"{LEGACY_RECEIPT_PATH} is retired", stderr)
self.assertNotIn("is not verified", stderr)
class PruneTest(GuardRepoTestCase):