From 1791dab4a645820c6d55959092a8fd5c3c6eb480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 14 Aug 2026 05:01:57 +0200 Subject: [PATCH 1/3] fix(gc): attribute deterministic ratchet shifts --- benchmarks/gc_ratchet/README.md | 9 + .../gc_ratchet/baseline/gc-ratchet-v1.json | 463 +++++++++++++----- benchmarks/gc_ratchet/gc_ratchet.py | 225 +++++++++ tests/test_gc_ratchet.py | 57 +++ 4 files changed, 644 insertions(+), 110 deletions(-) diff --git a/benchmarks/gc_ratchet/README.md b/benchmarks/gc_ratchet/README.md index 137740513a..fa0a231aa1 100644 --- a/benchmarks/gc_ratchet/README.md +++ b/benchmarks/gc_ratchet/README.md @@ -604,6 +604,15 @@ records the commit, host, load average at capture, toolchain versions, and SHA-256 of the `perry` binary and both runtime archives, so a re-pin is auditable after the fact. +When only a subset of deterministic cells is accepted, do not replace the +whole artifact and silently move rows that stayed inside their bands. Copy only +the proven cell distributions from the exact measurement and add an +`accepted_deterministic_deltas` receipt. Each row in that receipt records its +old and accepted median plus the causal merged commit(s); the checker treats a +missing cause, duplicate row, malformed input hash, or receipt/pin disagreement +as a fatal artifact defect. The receipt is provenance, not an allowance: +`evaluate` never reads it when deciding whether a future measurement regressed. + ## Adding a probe Adding or removing a probe changes the baseline's probe set, and the checker diff --git a/benchmarks/gc_ratchet/baseline/gc-ratchet-v1.json b/benchmarks/gc_ratchet/baseline/gc-ratchet-v1.json index 210b16cd52..5ec82a4db1 100644 --- a/benchmarks/gc_ratchet/baseline/gc-ratchet-v1.json +++ b/benchmarks/gc_ratchet/baseline/gc-ratchet-v1.json @@ -618,13 +618,13 @@ }, "copied_objects": { "samples": [ - 35943, - 35943 + 44539, + 44539 ], "sample_count": 2, - "median": 35943, - "min": 35943, - "max": 35943, + "median": 44539, + "min": 44539, + "max": 44539, "stdev": 0, "spread": 0, "spread_pct": 0 @@ -810,26 +810,26 @@ }, "copied_objects": { "samples": [ - 8214, - 8214 + 4107, + 4107 ], "sample_count": 2, - "median": 8214, - "min": 8214, - "max": 8214, + "median": 4107, + "min": 4107, + "max": 4107, "stdev": 0, "spread": 0, "spread_pct": 0 }, "copied_bytes": { "samples": [ - 656256, - 656256 + 262592, + 262592 ], "sample_count": 2, - "median": 656256, - "min": 656256, - "max": 656256, + "median": 262592, + "min": 262592, + "max": 262592, "stdev": 0, "spread": 0, "spread_pct": 0 @@ -862,13 +862,13 @@ }, "freed_bytes": { "samples": [ - 34273736, - 34273736 + 17562728, + 17562728 ], "sample_count": 2, - "median": 34273736, - "min": 34273736, - "max": 34273736, + "median": 17562728, + "min": 17562728, + "max": 17562728, "stdev": 0, "spread": 0, "spread_pct": 0 @@ -1002,13 +1002,13 @@ }, "copied_objects": { "samples": [ - 605, - 605 + 461, + 461 ], "sample_count": 2, - "median": 605, - "min": 605, - "max": 605, + "median": 461, + "min": 461, + "max": 461, "stdev": 0, "spread": 0, "spread_pct": 0 @@ -1054,13 +1054,13 @@ }, "freed_bytes": { "samples": [ - 118479632, - 118479632 + 94363824, + 94363824 ], "sample_count": 2, - "median": 118479632, - "min": 118479632, - "max": 118479632, + "median": 94363824, + "min": 94363824, + "max": 94363824, "stdev": 0, "spread": 0, "spread_pct": 0 @@ -1194,13 +1194,13 @@ }, "copied_objects": { "samples": [ - 3078, - 3078 + 3960, + 3960 ], "sample_count": 2, - "median": 3078, - "min": 3078, - "max": 3078, + "median": 3960, + "min": 3960, + "max": 3960, "stdev": 0, "spread": 0, "spread_pct": 0 @@ -1246,13 +1246,13 @@ }, "freed_bytes": { "samples": [ - 34510672, - 34510672 + 29271264, + 29271264 ], "sample_count": 2, - "median": 34510672, - "min": 34510672, - "max": 34510672, + "median": 29271264, + "min": 29271264, + "max": 29271264, "stdev": 0, "spread": 0, "spread_pct": 0 @@ -1438,13 +1438,13 @@ }, "freed_bytes": { "samples": [ - 67926328, - 67926328 + 75243368, + 75243368 ], "sample_count": 2, - "median": 67926328, - "min": 67926328, - "max": 67926328, + "median": 75243368, + "min": 75243368, + "max": 75243368, "stdev": 0, "spread": 0, "spread_pct": 0 @@ -1770,26 +1770,26 @@ }, "copied_objects": { "samples": [ - 3924, - 3924 + 3398, + 3398 ], "sample_count": 2, - "median": 3924, - "min": 3924, - "max": 3924, + "median": 3398, + "min": 3398, + "max": 3398, "stdev": 0, "spread": 0, "spread_pct": 0 }, "copied_bytes": { "samples": [ - 281016, - 281016 + 189496, + 189496 ], "sample_count": 2, - "median": 281016, - "min": 281016, - "max": 281016, + "median": 189496, + "min": 189496, + "max": 189496, "stdev": 0, "spread": 0, "spread_pct": 0 @@ -1822,13 +1822,13 @@ }, "freed_bytes": { "samples": [ - 101670912, - 101670912 + 72310784, + 72310784 ], "sample_count": 2, - "median": 101670912, - "min": 101670912, - "max": 101670912, + "median": 72310784, + "min": 72310784, + "max": 72310784, "stdev": 0, "spread": 0, "spread_pct": 0 @@ -2538,13 +2538,13 @@ }, "copied_objects": { "samples": [ - 61453, - 61453 + 79006, + 79006 ], "sample_count": 2, - "median": 61453, - "min": 61453, - "max": 61453, + "median": 79006, + "min": 79006, + "max": 79006, "stdev": 0, "spread": 0, "spread_pct": 0 @@ -2577,26 +2577,26 @@ }, "promoted_bytes": { "samples": [ - 37749496, - 37749496 + 29589352, + 29589352 ], "sample_count": 2, - "median": 37749496, - "min": 37749496, - "max": 37749496, + "median": 29589352, + "min": 29589352, + "max": 29589352, "stdev": 0, "spread": 0, "spread_pct": 0 }, "freed_bytes": { "samples": [ - 113377528, - 113377528 + 88897096, + 88897096 ], "sample_count": 2, - "median": 113377528, - "min": 113377528, - "max": 113377528, + "median": 88897096, + "min": 88897096, + "max": 88897096, "stdev": 0, "spread": 0, "spread_pct": 0 @@ -2616,18 +2616,18 @@ "metrics": { "heap_used_bytes": { "samples": [ - 239024, - 239024, - 239024, - 239024, - 239024, - 239024, - 239024 + 414152, + 414152, + 414152, + 414152, + 414152, + 414152, + 414152 ], "sample_count": 7, - "median": 239024, - "min": 239024, - "max": 239024, + "median": 414152, + "min": 414152, + "max": 414152, "stdev": 0, "spread": 0, "spread_pct": 0 @@ -2784,13 +2784,13 @@ }, "freed_bytes": { "samples": [ - 208564912, - 208564912 + 116705744, + 116705744 ], "sample_count": 2, - "median": 208564912, - "min": 208564912, - "max": 208564912, + "median": 116705744, + "min": 116705744, + "max": 116705744, "stdev": 0, "spread": 0, "spread_pct": 0 @@ -2927,26 +2927,26 @@ }, "copied_objects": { "samples": [ - 13013, - 13013 + 241, + 241 ], "sample_count": 2, - "median": 13013, - "min": 13013, - "max": 13013, + "median": 241, + "min": 241, + "max": 241, "stdev": 0, "spread": 0, "spread_pct": 0 }, "copied_bytes": { "samples": [ - 2003112, - 2003112 + 950784, + 950784 ], "sample_count": 2, - "median": 2003112, - "min": 2003112, - "max": 2003112, + "median": 950784, + "min": 950784, + "max": 950784, "stdev": 0, "spread": 0, "spread_pct": 0 @@ -2966,26 +2966,26 @@ }, "promoted_bytes": { "samples": [ - 151713056, - 151713056 + 161104600, + 161104600 ], "sample_count": 2, - "median": 151713056, - "min": 151713056, - "max": 151713056, + "median": 161104600, + "min": 161104600, + "max": 161104600, "stdev": 0, "spread": 0, "spread_pct": 0 }, "freed_bytes": { "samples": [ - 122137312, - 122137312 + 104582496, + 104582496 ], "sample_count": 2, - "median": 122137312, - "min": 122137312, - "max": 122137312, + "median": 104582496, + "min": 104582496, + "max": 104582496, "stdev": 0, "spread": 0, "spread_pct": 0 @@ -5366,5 +5366,248 @@ "memory_ratio": 0.234135 } } + }, + "accepted_deterministic_deltas": { + "commit": "0da668c95150d78ba5aa2b8eff7b78c04cc381c1", + "code_tree": "736f79847869bc9fb9fbd06f15537a5304a2bf46", + "generated_at": "2026-08-14T02:54:24+00:00", + "measurement": { + "platform": "darwin-arm64", + "repeats": 7, + "traced_runs": 2, + "binaries": { + "perry": { + "size": 104946752, + "sha256": "a9074f78d3368b908bce1f8b514236583ecb66dd942dc3330d448f68cf690829" + }, + "libperry_runtime.a": { + "size": 31142648, + "sha256": "80a98f3e5227c4927ab0de3d500124ca48e9bf2fc650d34c0b5c78caeb07160b" + }, + "libperry_stdlib.a": { + "size": 80871000, + "sha256": "191a26f29af113b94c7cf58769742cffcf6653098026755db9564dd05f87e018" + } + } + }, + "notes": "Only the 21 deterministic cells outside their existing bands were refreshed. RSS, timing, tolerances, and 58 within-band deterministic deltas retain the older pin. All 126 deterministic medians were identical at exact main 22005f433 and 0da668c95.", + "causes": { + "f110261a434b6aefcb6e452d97ed3e3ba7d6091c": { + "pull_request": 7928, + "category": "right-size small objects from four inline slots to two", + "evidence": "A local exact-main-minus-commit boundary, with three normal repeats and duplicate identical traces, restored the broad object/byte accounting shifts; source changes the common small-object footprint from 72 to 56 bytes." + }, + "a87bf3e3feaaaad234eca2d193b951e747494fa1": { + "pull_request": 7961, + "category": "denominate the nursery constant band in survivor objects", + "evidence": "A local exact-main-minus-commit boundary, with three normal repeats and duplicate identical traces, isolated the changed collection cadence. On probe 13 it exchanged exactly 94,192 bytes from promoted to nursery-live placement; total live bytes after both layout changes are 236,176 below the old pin." + }, + "c35234de278566b9d3785c7286deff5196531bc4": { + "pull_request": 7960, + "category": "let the first copying minor choose promotion from its own trace", + "evidence": "A local exact-main-minus-commit boundary, with three normal repeats and duplicate identical traces, changed probe 14 from 31,021 to 241 copied objects while moving the same survivors to promotion; this directly proves the destination and cumulative-work shift." + } + }, + "cells": [ + { + "probe": "02_survivor_promotion", + "metric": "copied_objects", + "previous_median": 35943, + "accepted_median": 44539, + "causes": [ + "f110261a434b6aefcb6e452d97ed3e3ba7d6091c" + ] + }, + { + "probe": "03_cross_gen_writes", + "metric": "copied_objects", + "previous_median": 8214, + "accepted_median": 4107, + "causes": [ + "f110261a434b6aefcb6e452d97ed3e3ba7d6091c" + ] + }, + { + "probe": "03_cross_gen_writes", + "metric": "copied_bytes", + "previous_median": 656256, + "accepted_median": 262592, + "causes": [ + "f110261a434b6aefcb6e452d97ed3e3ba7d6091c" + ] + }, + { + "probe": "03_cross_gen_writes", + "metric": "freed_bytes", + "previous_median": 34273736, + "accepted_median": 17562728, + "causes": [ + "f110261a434b6aefcb6e452d97ed3e3ba7d6091c" + ] + }, + { + "probe": "04_dead_after_deep_stack", + "metric": "copied_objects", + "previous_median": 605, + "accepted_median": 461, + "causes": [ + "f110261a434b6aefcb6e452d97ed3e3ba7d6091c" + ] + }, + { + "probe": "04_dead_after_deep_stack", + "metric": "freed_bytes", + "previous_median": 118479632, + "accepted_median": 94363824, + "causes": [ + "f110261a434b6aefcb6e452d97ed3e3ba7d6091c" + ] + }, + { + "probe": "05_closure_capture", + "metric": "copied_objects", + "previous_median": 3078, + "accepted_median": 3960, + "causes": [ + "f110261a434b6aefcb6e452d97ed3e3ba7d6091c", + "a87bf3e3feaaaad234eca2d193b951e747494fa1" + ] + }, + { + "probe": "05_closure_capture", + "metric": "freed_bytes", + "previous_median": 34510672, + "accepted_median": 29271264, + "causes": [ + "f110261a434b6aefcb6e452d97ed3e3ba7d6091c", + "a87bf3e3feaaaad234eca2d193b951e747494fa1" + ] + }, + { + "probe": "06_string_retention", + "metric": "freed_bytes", + "previous_median": 67926328, + "accepted_median": 75243368, + "causes": [ + "a87bf3e3feaaaad234eca2d193b951e747494fa1" + ] + }, + { + "probe": "08_map_set_sidetables", + "metric": "copied_objects", + "previous_median": 3924, + "accepted_median": 3398, + "causes": [ + "f110261a434b6aefcb6e452d97ed3e3ba7d6091c" + ] + }, + { + "probe": "08_map_set_sidetables", + "metric": "copied_bytes", + "previous_median": 281016, + "accepted_median": 189496, + "causes": [ + "f110261a434b6aefcb6e452d97ed3e3ba7d6091c" + ] + }, + { + "probe": "08_map_set_sidetables", + "metric": "freed_bytes", + "previous_median": 101670912, + "accepted_median": 72310784, + "causes": [ + "f110261a434b6aefcb6e452d97ed3e3ba7d6091c" + ] + }, + { + "probe": "12_large_live_set", + "metric": "copied_objects", + "previous_median": 61453, + "accepted_median": 79006, + "causes": [ + "f110261a434b6aefcb6e452d97ed3e3ba7d6091c" + ] + }, + { + "probe": "12_large_live_set", + "metric": "promoted_bytes", + "previous_median": 37749496, + "accepted_median": 29589352, + "causes": [ + "f110261a434b6aefcb6e452d97ed3e3ba7d6091c", + "a87bf3e3feaaaad234eca2d193b951e747494fa1" + ] + }, + { + "probe": "12_large_live_set", + "metric": "freed_bytes", + "previous_median": 113377528, + "accepted_median": 88897096, + "causes": [ + "f110261a434b6aefcb6e452d97ed3e3ba7d6091c", + "a87bf3e3feaaaad234eca2d193b951e747494fa1" + ] + }, + { + "probe": "13_large_eden_survivors", + "metric": "heap_used_bytes", + "previous_median": 239024, + "accepted_median": 414152, + "causes": [ + "f110261a434b6aefcb6e452d97ed3e3ba7d6091c", + "a87bf3e3feaaaad234eca2d193b951e747494fa1" + ] + }, + { + "probe": "13_large_eden_survivors", + "metric": "freed_bytes", + "previous_median": 208564912, + "accepted_median": 116705744, + "causes": [ + "f110261a434b6aefcb6e452d97ed3e3ba7d6091c", + "a87bf3e3feaaaad234eca2d193b951e747494fa1" + ] + }, + { + "probe": "14_grow_then_churn", + "metric": "copied_objects", + "previous_median": 13013, + "accepted_median": 241, + "causes": [ + "f110261a434b6aefcb6e452d97ed3e3ba7d6091c", + "c35234de278566b9d3785c7286deff5196531bc4" + ] + }, + { + "probe": "14_grow_then_churn", + "metric": "copied_bytes", + "previous_median": 2003112, + "accepted_median": 950784, + "causes": [ + "f110261a434b6aefcb6e452d97ed3e3ba7d6091c", + "c35234de278566b9d3785c7286deff5196531bc4" + ] + }, + { + "probe": "14_grow_then_churn", + "metric": "promoted_bytes", + "previous_median": 151713056, + "accepted_median": 161104600, + "causes": [ + "f110261a434b6aefcb6e452d97ed3e3ba7d6091c", + "c35234de278566b9d3785c7286deff5196531bc4" + ] + }, + { + "probe": "14_grow_then_churn", + "metric": "freed_bytes", + "previous_median": 122137312, + "accepted_median": 104582496, + "causes": [ + "f110261a434b6aefcb6e452d97ed3e3ba7d6091c", + "c35234de278566b9d3785c7286deff5196531bc4" + ] + } + ] } } diff --git a/benchmarks/gc_ratchet/gc_ratchet.py b/benchmarks/gc_ratchet/gc_ratchet.py index 7ab25ff24d..c0abc5ba7c 100644 --- a/benchmarks/gc_ratchet/gc_ratchet.py +++ b/benchmarks/gc_ratchet/gc_ratchet.py @@ -1288,6 +1288,216 @@ def _validate_probe_overrides( ) +def _inspect_accepted_deterministic_deltas( + artifact: Mapping[str, Any], artifact_defect: Any +) -> None: + """Validate the row-level receipt for a selective deterministic re-pin. + + A full re-pin has one provenance record for the whole artifact. A selective + re-pin is more dangerous: it can make a red row green while leaving no + machine-readable answer to *which* rows moved or *why*. This optional + receipt binds every accepted median to the value actually pinned and to one + or more causal merged commits. It is deliberately inert in ``evaluate`` — + provenance explains a baseline change; it never grants a future run an + allowance. + + Older and synthetic artifacts may omit the receipt. Once present, however, + malformed or stale attribution is an artifact-wide integrity defect rather + than metadata the checker silently ignores. + """ + receipt = artifact.get("accepted_deterministic_deltas") + if receipt is None: + return + if not isinstance(receipt, Mapping): + artifact_defect("accepted_deterministic_deltas is not an object") + return + + expected_receipt_keys = { + "commit", + "code_tree", + "generated_at", + "measurement", + "notes", + "causes", + "cells", + } + unknown = set(receipt) - expected_receipt_keys + missing = expected_receipt_keys - set(receipt) + if unknown: + artifact_defect( + "accepted_deterministic_deltas has unknown fields: " + + ", ".join(sorted(map(str, unknown))) + ) + if missing: + artifact_defect( + "accepted_deterministic_deltas is missing fields: " + + ", ".join(sorted(missing)) + ) + + for field in ("commit", "code_tree"): + value = receipt.get(field) + if not isinstance(value, str) or re.fullmatch(r"[0-9a-f]{40}", value) is None: + artifact_defect(f"accepted_deterministic_deltas.{field} is not a full git hash") + for field in ("generated_at", "notes"): + value = receipt.get(field) + if not isinstance(value, str) or not value.strip(): + artifact_defect(f"accepted_deterministic_deltas.{field} is empty") + + measurement = receipt.get("measurement") + expected_measurement_keys = {"platform", "repeats", "traced_runs", "binaries"} + if not isinstance(measurement, Mapping): + artifact_defect("accepted_deterministic_deltas.measurement is not an object") + else: + if set(measurement) != expected_measurement_keys: + artifact_defect( + "accepted_deterministic_deltas.measurement fields must be exactly: " + + ", ".join(sorted(expected_measurement_keys)) + ) + if not isinstance(measurement.get("platform"), str) or not measurement.get( + "platform", "" + ).strip(): + artifact_defect("accepted_deterministic_deltas.measurement.platform is empty") + for field, minimum in (("repeats", 3), ("traced_runs", 2)): + value = measurement.get(field) + if isinstance(value, bool) or not isinstance(value, int) or value < minimum: + artifact_defect( + f"accepted_deterministic_deltas.measurement.{field} must be >= {minimum}" + ) + + binaries = measurement.get("binaries") + expected_binaries = {"perry", "libperry_runtime.a", "libperry_stdlib.a"} + if not isinstance(binaries, Mapping) or set(binaries) != expected_binaries: + artifact_defect( + "accepted_deterministic_deltas.measurement.binaries must name perry, " + "libperry_runtime.a, and libperry_stdlib.a" + ) + else: + for name, binary in binaries.items(): + if not isinstance(binary, Mapping) or set(binary) != {"size", "sha256"}: + artifact_defect( + "accepted_deterministic_deltas.measurement.binaries." + f"{name} must contain exactly size and sha256" + ) + continue + size = binary.get("size") + digest = binary.get("sha256") + if isinstance(size, bool) or not isinstance(size, int) or size <= 0: + artifact_defect( + f"accepted_deterministic_deltas.measurement.binaries.{name}.size " + "must be a positive integer" + ) + if not isinstance(digest, str) or re.fullmatch(r"[0-9a-f]{64}", digest) is None: + artifact_defect( + f"accepted_deterministic_deltas.measurement.binaries.{name}.sha256 " + "is not a SHA-256 digest" + ) + + causes = receipt.get("causes") + known_causes: set[str] = set() + if not isinstance(causes, Mapping) or not causes: + artifact_defect("accepted_deterministic_deltas records no causal commits") + else: + for commit, cause in causes.items(): + if not isinstance(commit, str) or re.fullmatch(r"[0-9a-f]{40}", commit) is None: + artifact_defect( + f"accepted_deterministic_deltas cause {commit!r} is not a full git hash" + ) + continue + known_causes.add(commit) + if not isinstance(cause, Mapping) or set(cause) != { + "pull_request", + "category", + "evidence", + }: + artifact_defect( + f"accepted_deterministic_deltas.causes.{commit} must contain exactly " + "pull_request, category, and evidence" + ) + continue + pull_request = cause.get("pull_request") + if ( + isinstance(pull_request, bool) + or not isinstance(pull_request, int) + or pull_request <= 0 + ): + artifact_defect( + f"accepted_deterministic_deltas.causes.{commit}.pull_request is invalid" + ) + for field in ("category", "evidence"): + value = cause.get(field) + if not isinstance(value, str) or not value.strip(): + artifact_defect( + f"accepted_deterministic_deltas.causes.{commit}.{field} is empty" + ) + + cells = receipt.get("cells") + probes = artifact.get("probes", {}) + if not isinstance(cells, list) or not cells: + artifact_defect("accepted_deterministic_deltas records no cells") + return + + seen: set[tuple[str, str]] = set() + expected_cell_keys = { + "probe", + "metric", + "previous_median", + "accepted_median", + "causes", + } + for index, cell in enumerate(cells): + prefix = f"accepted_deterministic_deltas.cells[{index}]" + if not isinstance(cell, Mapping) or set(cell) != expected_cell_keys: + artifact_defect( + f"{prefix} must contain exactly probe, metric, previous_median, " + "accepted_median, and causes" + ) + continue + probe = cell.get("probe") + metric = cell.get("metric") + if not isinstance(probe, str) or probe not in probes: + artifact_defect(f"{prefix}.probe does not name a pinned probe") + continue + if not isinstance(metric, str) or metric not in DETERMINISTIC_METRICS: + artifact_defect(f"{prefix}.metric is not a deterministic metric") + continue + if metric not in probes[probe].get("metrics", {}): + artifact_defect(f"{prefix} names a metric absent from the pinned probe") + continue + key = (probe, metric) + if key in seen: + artifact_defect(f"{prefix} duplicates {probe}.{metric}") + seen.add(key) + + previous = cell.get("previous_median") + accepted = cell.get("accepted_median") + if any( + isinstance(value, bool) + or not isinstance(value, (int, float)) + or not math.isfinite(value) + for value in (previous, accepted) + ): + artifact_defect(f"{prefix} medians must be finite numbers") + else: + if previous == accepted: + artifact_defect(f"{prefix} records no change") + pinned = probes[probe]["metrics"][metric].get("median") + if accepted != pinned: + artifact_defect( + f"{prefix}.accepted_median {accepted!r} does not match pinned median " + f"{pinned!r}" + ) + + cell_causes = cell.get("causes") + if not isinstance(cell_causes, list) or not cell_causes: + artifact_defect(f"{prefix}.causes is empty") + elif len(cell_causes) != len(set(map(str, cell_causes))): + artifact_defect(f"{prefix}.causes contains a duplicate") + else: + for commit in cell_causes: + if not isinstance(commit, str) or commit not in known_causes: + artifact_defect(f"{prefix}.causes names unknown commit {commit!r}") + + def inspect_artifact(artifact: Mapping[str, Any]) -> list[ArtifactDefect]: """Collect *every* defect in the pinned artifact, each tagged with its scope. @@ -1422,6 +1632,7 @@ def cell_defect(probe: str, metric: str, message: str) -> None: "entry that records the evidence.", ) + _inspect_accepted_deterministic_deltas(artifact, artifact_defect) return defects @@ -1724,6 +1935,20 @@ def render(rows: Iterable[Row], baseline: Mapping[str, Any], profile: str) -> st *(f"- `{name}` — `{_render_run_env(env)}`" for name, env in armed.items()), "", ] + receipt = baseline.get("accepted_deterministic_deltas") + if isinstance(receipt, Mapping): + cells = receipt.get("cells", []) + lines += [ + "Accepted deterministic baseline deltas:", + "", + f"- `{len(cells)}` cells measured at `{receipt.get('commit')}` " + f"({receipt.get('generated_at')})", + ] + for commit, cause in sorted(receipt.get("causes", {}).items()): + lines.append( + f"- `{commit}` / PR #{cause.get('pull_request')} — {cause.get('category')}" + ) + lines.append("") lines += [ "| Probe | Metric | Baseline | Current | Δ | Allowance | Gating | Status |", "|-------|--------|---------:|--------:|---:|----------:|:------:|--------|", diff --git a/tests/test_gc_ratchet.py b/tests/test_gc_ratchet.py index b39fb59581..a2a1ff0cae 100644 --- a/tests/test_gc_ratchet.py +++ b/tests/test_gc_ratchet.py @@ -490,6 +490,63 @@ def test_pinned_artifact_records_provenance(self): for key in ("perry", "libperry_runtime.a", "libperry_stdlib.a"): self.assertRegex(binaries[key]["sha256"], r"^[0-9a-f]{64}$") + def test_selective_refresh_names_every_accepted_cell_and_cause(self): + artifact = json.loads(DEFAULT_ARTIFACT.read_text(encoding="utf-8")) + receipt = artifact["accepted_deterministic_deltas"] + expected = { + ("02_survivor_promotion", "copied_objects"), + ("03_cross_gen_writes", "copied_objects"), + ("03_cross_gen_writes", "copied_bytes"), + ("03_cross_gen_writes", "freed_bytes"), + ("04_dead_after_deep_stack", "copied_objects"), + ("04_dead_after_deep_stack", "freed_bytes"), + ("05_closure_capture", "copied_objects"), + ("05_closure_capture", "freed_bytes"), + ("06_string_retention", "freed_bytes"), + ("08_map_set_sidetables", "copied_objects"), + ("08_map_set_sidetables", "copied_bytes"), + ("08_map_set_sidetables", "freed_bytes"), + ("12_large_live_set", "copied_objects"), + ("12_large_live_set", "promoted_bytes"), + ("12_large_live_set", "freed_bytes"), + ("13_large_eden_survivors", "heap_used_bytes"), + ("13_large_eden_survivors", "freed_bytes"), + ("14_grow_then_churn", "copied_objects"), + ("14_grow_then_churn", "copied_bytes"), + ("14_grow_then_churn", "promoted_bytes"), + ("14_grow_then_churn", "freed_bytes"), + } + actual = {(cell["probe"], cell["metric"]) for cell in receipt["cells"]} + self.assertEqual(actual, expected) + self.assertEqual( + {cause["pull_request"] for cause in receipt["causes"].values()}, + {7928, 7960, 7961}, + ) + + def test_selective_refresh_receipt_cannot_disagree_with_the_pin(self): + artifact = json.loads(DEFAULT_ARTIFACT.read_text(encoding="utf-8")) + tampered = copy.deepcopy(artifact) + tampered["accepted_deterministic_deltas"]["cells"][0]["accepted_median"] += 1 + with self.assertRaisesRegex(RatchetError, "does not match pinned median"): + validate_artifact(tampered) + + def test_selective_refresh_does_not_allow_a_future_unexplained_delta(self): + artifact = json.loads(DEFAULT_ARTIFACT.read_text(encoding="utf-8")) + current = copy.deepcopy(artifact) + current["kind"] = "gc-ratchet-measurement" + probe = "02_survivor_promotion" + metric = "copied_objects" + pinned = artifact["probes"][probe]["metrics"][metric]["median"] + tolerance = tolerances_from_json(artifact["tolerances"])["shared_ci"][metric] + breach = pinned + tolerance.allowance(pinned) + 1 + current["probes"][probe]["metrics"][metric] = distribution([breach, breach]) + + _, failures = evaluate(artifact, current, profile="shared_ci") + self.assertTrue( + any(probe in failure and metric in failure for failure in _hard(failures)), + "accepted provenance must explain the pin, never suppress future drift", + ) + def test_pinned_artifact_probes_all_ran_a_collection(self): artifact = json.loads(DEFAULT_ARTIFACT.read_text(encoding="utf-8")) for name, entry in artifact["probes"].items(): From 995304d28d87607b50c4fe2c0a2e0f782f1ea218 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 14 Aug 2026 05:02:54 +0200 Subject: [PATCH 2/3] docs(changelog): record GC ratchet attribution --- changelog.d/8069-gc-ratchet-attribution.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelog.d/8069-gc-ratchet-attribution.md diff --git a/changelog.d/8069-gc-ratchet-attribution.md b/changelog.d/8069-gc-ratchet-attribution.md new file mode 100644 index 0000000000..08ec447f02 --- /dev/null +++ b/changelog.d/8069-gc-ratchet-attribution.md @@ -0,0 +1,3 @@ +### Fixed + +- Attribute and selectively refresh the GC ratchet's deterministic counter shifts without widening its bands or moving unaffected baseline cells (#8051). From 369d01f95bd45f35229e60740dea8d048a1a7390 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 14 Aug 2026 05:11:22 +0200 Subject: [PATCH 3/3] fix(gc-ratchet): validate refresh timestamp --- benchmarks/gc_ratchet/gc_ratchet.py | 20 ++++++++++++++++---- tests/test_gc_ratchet.py | 7 +++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/benchmarks/gc_ratchet/gc_ratchet.py b/benchmarks/gc_ratchet/gc_ratchet.py index c0abc5ba7c..87eb80b83c 100644 --- a/benchmarks/gc_ratchet/gc_ratchet.py +++ b/benchmarks/gc_ratchet/gc_ratchet.py @@ -1338,10 +1338,22 @@ def _inspect_accepted_deterministic_deltas( value = receipt.get(field) if not isinstance(value, str) or re.fullmatch(r"[0-9a-f]{40}", value) is None: artifact_defect(f"accepted_deterministic_deltas.{field} is not a full git hash") - for field in ("generated_at", "notes"): - value = receipt.get(field) - if not isinstance(value, str) or not value.strip(): - artifact_defect(f"accepted_deterministic_deltas.{field} is empty") + generated_at = receipt.get("generated_at") + try: + parsed_generated_at = datetime.fromisoformat(generated_at) + except (TypeError, ValueError): + parsed_generated_at = None + if ( + parsed_generated_at is None + or parsed_generated_at.tzinfo is None + or parsed_generated_at.utcoffset() != timezone.utc.utcoffset(None) + ): + artifact_defect( + "accepted_deterministic_deltas.generated_at must be an ISO-8601 UTC timestamp" + ) + notes = receipt.get("notes") + if not isinstance(notes, str) or not notes.strip(): + artifact_defect("accepted_deterministic_deltas.notes is empty") measurement = receipt.get("measurement") expected_measurement_keys = {"platform", "repeats", "traced_runs", "binaries"} diff --git a/tests/test_gc_ratchet.py b/tests/test_gc_ratchet.py index a2a1ff0cae..67a6959952 100644 --- a/tests/test_gc_ratchet.py +++ b/tests/test_gc_ratchet.py @@ -530,6 +530,13 @@ def test_selective_refresh_receipt_cannot_disagree_with_the_pin(self): with self.assertRaisesRegex(RatchetError, "does not match pinned median"): validate_artifact(tampered) + def test_selective_refresh_receipt_rejects_a_malformed_timestamp(self): + artifact = json.loads(DEFAULT_ARTIFACT.read_text(encoding="utf-8")) + tampered = copy.deepcopy(artifact) + tampered["accepted_deterministic_deltas"]["generated_at"] = "unknown" + with self.assertRaisesRegex(RatchetError, "ISO-8601 UTC timestamp"): + validate_artifact(tampered) + def test_selective_refresh_does_not_allow_a_future_unexplained_delta(self): artifact = json.loads(DEFAULT_ARTIFACT.read_text(encoding="utf-8")) current = copy.deepcopy(artifact)