Skip to content

ci: make public baseline inputs declarative - #7958

Merged
proggeramlug merged 2 commits into
mainfrom
ci/7282-measurement-config
Aug 12, 2026
Merged

ci: make public baseline inputs declarative#7958
proggeramlug merged 2 commits into
mainfrom
ci/7282-measurement-config

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Closes #7282

What changed

The public benchmark protocol now lives in benchmarks/public-baseline-config.json. It owns:

  • pinned Node/Bun versions
  • quiet-host threshold and duration
  • measured run counts and warmups for all five components
  • the selected honest-bench workloads

run_public_baseline.sh consumes those values, and artifact assembly/freshness validation rejects component metadata, runtime versions, or quiet-host policy that disagrees with the config. The app-pattern runner now records the configured warmup/sample counts it actually passes to hyperfine.

The fingerprint now covers the declarative config plus the honest-bench correctness oracle. Honest-bench kernels and fixture generators remain protected as source inputs. Large runner/checker files are plumbing, so logging, cleanup, error handling, and output-format changes no longer require a two-hour regeneration.

Reproduction and sabotage

Before this change, adding one comment to benchmarks/json_polyglot/run.sh changed the harness digest from 28117b86… to d9eec9be… and made the required checker exit 2.

After the change:

  • The same plumbing-only edit leaves digest 513dba8f… unchanged and the checker exits 0.
  • Changing polyglot.measured_runs from 11 to 12 makes the checker exit 2.
  • Changing an app-pattern kernel makes the checker exit 2.
  • The harness set shrinks from 47 files / 318,493 bytes to 2 files / 1,544 bytes; the source set explicitly retains the honest-bench kernels and generators.

The existing artifact was green under the broader fingerprint before this change. An exact old-digest → new-digest migration accepts only this narrowing; any later input edit misses the pinned destination and hard-fails. No measurements or artifact values were changed.

Validation

  • python3 -m unittest tests.test_public_baseline — 11 passed
  • python3 benchmarks/ci_public_baseline_check.py
  • Config/plumbing/kernel sabotage in both directions
  • bash -n benchmarks/run_public_baseline.sh benchmarks/app-patterns/run.sh
  • shellcheck -e SC2162 benchmarks/run_public_baseline.sh benchmarks/app-patterns/run.sh
  • Orchestrator smoke: config parsed and stopped before build on the host's Node-version mismatch
  • python3 -m py_compile ...
  • python3 -m json.tool benchmarks/public-baseline-config.json
  • bash scripts/check_file_size.sh
  • git diff HEAD~1 --check

No performance run is needed: this changes freshness bookkeeping and parameter ownership, while preserving every currently published parameter and measurement.

Summary by CodeRabbit

  • New Features

    • Added configurable benchmark warmup counts, run counts, workloads, toolchain versions, and host quietness thresholds.
    • Added a public benchmark configuration file for consistent measurement settings.
    • Public benchmark artifacts now record and validate the measurement configuration.
  • Bug Fixes

    • Added validation to reject invalid run counts, warmups, component metadata, runtime versions, and host conditions.
  • Documentation

    • Documented the expanded public performance-baseline measurement protocol and fingerprinting rules.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The public baseline now uses a declarative measurement configuration for toolchains, thresholds, workloads, warmups, and run counts. Benchmark drivers consume these settings, while fingerprint and artifact validation enforce the configured protocol.

Changes

Public baseline measurement protocol

Layer / File(s) Summary
Measurement contract and fingerprint scope
benchmarks/public-baseline-config.json, benchmarks/public_baseline.py, benchmarks/ci_public_baseline_check.py, tests/test_public_baseline.py, changelog.d/7958-public-baseline-inputs.md
The configuration defines measurement inputs and validation rules. Fingerprinting now focuses on measurement-affecting sources, configuration, and correctness inputs.
Configured benchmark execution
benchmarks/run_public_baseline.sh, benchmarks/app-patterns/run.sh, benchmarks/public_baseline.py, tests/test_public_baseline.py
Benchmark commands and app-pattern metadata use configured warmups, runs, workloads, toolchains, and quiet-host settings.
Artifact policy and freshness validation
benchmarks/public_baseline.py, tests/test_public_baseline.py
Assembly and public validation check recorded measurement settings, runtime versions, quiet-host policy values, and approved fingerprint migrations.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant run_public_baseline_sh
  participant benchmark_drivers
  participant public_baseline_assemble
  run_public_baseline_sh->>benchmark_drivers: pass configured runs, warmups, workloads, and toolchains
  benchmark_drivers->>public_baseline_assemble: provide recorded measurement metadata
  public_baseline_assemble->>public_baseline_assemble: validate metadata against measurement configuration
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary change: making public baseline inputs declarative.
Linked Issues check ✅ Passed The PR satisfies issue #7282 by narrowing fingerprint inputs, centralizing measurement settings, preserving hard-fail behavior, and reusing shared checker logic.
Out of Scope Changes check ✅ Passed The configuration, runner updates, validation, tests, and changelog directly support the requirements in issue #7282.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci/7282-measurement-config

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@proggeramlug
proggeramlug marked this pull request as ready for review August 12, 2026 12:14

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@benchmarks/public_baseline.py`:
- Around line 238-279: Update the configuration validation flow around _load to
first require the JSON root to be a mapping before calling config.get. Use exact
integer validation that excludes booleans for schema_version,
consecutive_seconds, measured_runs, and warmup_runs, and exclude booleans from
the maximum_cpu_active_percent numeric check. Add tests covering malformed
non-object roots and boolean values for each affected field.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ebc55b11-b98d-4b72-8631-459ec995e6e0

📥 Commits

Reviewing files that changed from the base of the PR and between b392f7b and 1784c09.

📒 Files selected for processing (7)
  • benchmarks/app-patterns/run.sh
  • benchmarks/ci_public_baseline_check.py
  • benchmarks/public-baseline-config.json
  • benchmarks/public_baseline.py
  • benchmarks/run_public_baseline.sh
  • changelog.d/7958-public-baseline-inputs.md
  • tests/test_public_baseline.py

Comment on lines +238 to +279
config = _load(path)
if config.get("schema_version") != 1:
raise ArtifactError("public measurement config: unsupported schema")

toolchains = config.get("toolchains")
if not isinstance(toolchains, dict):
raise ArtifactError("public measurement config: missing toolchains")
for runtime in ("node", "bun"):
if not isinstance(toolchains.get(runtime), str) or not toolchains[runtime]:
raise ArtifactError(
f"public measurement config: invalid {runtime} toolchain pin"
)

quiet = config.get("quiet_host")
if not isinstance(quiet, dict):
raise ArtifactError("public measurement config: missing quiet_host")
maximum = quiet.get("maximum_cpu_active_percent")
seconds = quiet.get("consecutive_seconds")
if not isinstance(maximum, (int, float)) or not 0 < maximum <= 100:
raise ArtifactError("public measurement config: invalid CPU-active maximum")
if not isinstance(seconds, int) or seconds < 1:
raise ArtifactError("public measurement config: invalid quiet-host duration")

components = config.get("components")
if not isinstance(components, dict):
raise ArtifactError("public measurement config: missing components")
for name in ("suite", "polyglot", "json_polyglot", "app_patterns", "honest_bench"):
component = components.get(name)
if not isinstance(component, dict):
raise ArtifactError(f"public measurement config: missing {name}")
measured = component.get("measured_runs")
if not isinstance(measured, int) or measured < 2:
raise ArtifactError(
f"public measurement config: {name}.measured_runs must be at least 2"
)
for name in ("app_patterns", "honest_bench"):
warmup = components[name].get("warmup_runs")
if not isinstance(warmup, int) or warmup < 0:
raise ArtifactError(
f"public measurement config: {name}.warmup_runs must be non-negative"
)
if components["honest_bench"].get("workloads") != [1, 3]:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reject non-object and Boolean configuration values.

config.get(...) raises AttributeError when the JSON root is not an object. Also, True passes checks that use isinstance(value, int), and True == 1 accepts a Boolean schema_version.

Validate the root object first. Use exact integer checks for schema_version, durations, run counts, and warmups. Exclude Boolean values from the CPU percentage check. Add malformed-root and Boolean-value tests.

Proposed fix
 def load_measurement_config(path: Path = MEASUREMENT_CONFIG) -> dict[str, Any]:
     """Load and validate the declarative inputs for public measurements."""
     config = _load(path)
-    if config.get("schema_version") != 1:
+    if not isinstance(config, dict) or type(config.get("schema_version")) is not int or config["schema_version"] != 1:
         raise ArtifactError("public measurement config: unsupported schema")
@@
-    if not isinstance(maximum, (int, float)) or not 0 < maximum <= 100:
+    if type(maximum) not in (int, float) or not 0 < maximum <= 100:
         raise ArtifactError("public measurement config: invalid CPU-active maximum")
-    if not isinstance(seconds, int) or seconds < 1:
+    if type(seconds) is not int or seconds < 1:
         raise ArtifactError("public measurement config: invalid quiet-host duration")
@@
-        if not isinstance(measured, int) or measured < 2:
+        if type(measured) is not int or measured < 2:
@@
-        if not isinstance(warmup, int) or warmup < 0:
+        if type(warmup) is not int or warmup < 0:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@benchmarks/public_baseline.py` around lines 238 - 279, Update the
configuration validation flow around _load to first require the JSON root to be
a mapping before calling config.get. Use exact integer validation that excludes
booleans for schema_version, consecutive_seconds, measured_runs, and
warmup_runs, and exclude booleans from the maximum_cpu_active_percent numeric
check. Add tests covering malformed non-object roots and boolean values for each
affected field.

@proggeramlug
proggeramlug merged commit 57d3de7 into main Aug 12, 2026
1 of 19 checks passed
@proggeramlug
proggeramlug deleted the ci/7282-measurement-config branch August 12, 2026 12:50
proggeramlug added a commit that referenced this pull request Aug 12, 2026
…file (#7967)

* ci: put every Node the project chooses on 26, and assert it from one file

.node-version (26.5.1) is the authoritative oracle, but the pin has leaked
twice since #6367 made it single: CLAUDE.md's prose drifted off the file
(#7599), and npm-launcher.yml was created by #6350 on the SAME DAY #6367
converted every existing workflow, keeping that day's ambient "22.23.1"
literal by omission rather than by decision.

- npm-launcher.yml (x2): "22.23.1" -> node-version-file: .node-version. It
  runs npm/perry/test/detect.test.cjs, which exercises the shipped launcher
  logic every installing user hits, so its Node is a behavioural input. Safe
  on the ubuntu-22.04 job: Node 26 needs glibc >= 2.28 and jammy has 2.35.
- release-packages.yml: "20" -> "26". Node 20 reached EOL on 2026-04-30 and
  this is the repo's most privileged job (id-token: write, OIDC-publishes
  every platform package).
- release-hono-server.yml: "24" -> "26".

Both release workflows stay pinned to a bare MAJOR rather than
node-version-file: they are publishing toolchains, and a gap-suite oracle
bump must never be able to move the runtime that publishes releases.

New scripts/check_node_version_consistency.py, wired as a lint step (a
required context). It re-derives every restatement of a Node version from
the file it quotes, and requires every literal node-version: in a workflow
to be a registered exemption with a reason. Exemptions are asserted against
the tree, so one that stops matching FAILS and must be updated or deleted.
Reverting npm-launcher.yml to "22.23.1" reproduces the historical bug as two
named failures. --self-test proves each rule can fail; both vacuity floors
can fail too.

Not changed, deliberately: test-compat/node-core/pinned-version.txt (v22.x
runs Node's own corpus) and benchmarks/public-baseline-config.json
(v22.23.1). The latter is in public_baseline.HARNESS_PATHS, so editing it
alone reddens the required lint job -- measured, ci_public_baseline_check.py
exits 2 with "benchmark harness changed". The pin and its ~2 h measurement
are atomic by design (#7282/#7958) and the regeneration needs the quiet M1
mini, so it is registered as a self-clearing exemption carrying the runbook
instead. Node 26 is faster than Node 22, so that regeneration is expected to
reduce Perry's published advantage.

Also: CLAUDE.md said "Two workflows are deliberately exempt" and then listed
three, which is probably why the fourth pin read as accounted-for; and
external-tools.json told readers to bump a NODE_PIN constant that does not
exist in node_compat_matrix.mjs (it reads external-tools.json).

Claude-Session: https://claude.ai/code/session_012B8z92S82sCfqCrVqrFgS2

* docs: changelog fragment for #7967

Claude-Session: https://claude.ai/code/session_012B8z92S82sCfqCrVqrFgS2

---------

Co-authored-by: Ralph Küpper <ralph3@skelpo.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant