Ingest benchmark parameters and named metric values - #980
Draft
epompeii wants to merge 14 commits into
Draft
Conversation
Report ingest resolves each BMF v1 entry to its parameter row during the read phase, alongside the benchmark and the measures, and writes one metric row per named value. Detection keys its sample on the grid point, and a bare threshold gates the conventional value series of every parameter set under its measure, which is what a measure level threshold over flat benchmarks has always done. The active series cache keys on the grid point too, so each one bills as its own series and named values collapse into their measure's. The report response gains the named values, each paired with the thresholds that gated it, and carries the parameter set so two grid points of one benchmark are two results rather than one merged result. The metric triple, threshold, and boundary are retained as deprecated fields reconstructed from the value row. Fold stays a BMF v0 operation: a v1 payload with fold requested warns and ingests unfolded, one report benchmark row per iteration, never an error.
Two invariants this layer claims were listed by a test that could not see them. Both tests now fail when the code they describe is removed. The baseline test reused the alert parity fixture, whose two grid points differ tenfold, so a tenfold regression is an outlier whether the sample is one grid point's or both pooled. It gets its own fixture: two tight histories two decades apart, where the small grid point's regression sits well inside the pooled spread. Removing the parameter filter from the detection query now raises no alert at all instead of the same one. The response test carried one measure, which cannot see the results ordering: with a single measure per grid point the rows arrive grouped either way. It gains a second measure, so removing the parameter from the ORDER BY lets the measure name outrank it and the consecutive row grouping emits four results of one measure each.
Parameter rows are minted straight from report content, one per grid point, so they need the per project creation ceiling every other entity a report mints already has. The test posts one report over the ceiling and expects the same refusal a benchmark name flood gets today. The test server grows a creation limit knob because every existing test server has its limits wide open, which is the only way to reach a ceiling without throttling the requests that get there.
Parameter sets are minted straight from report content, so a harness that interpolates a commit sha into its parameters would otherwise mint rows, grid points, and billable series without bound. The ceiling is hand written rather than the shared macro because parameter has no project_id of its own: a parameter set belongs to its benchmark, and the benchmark is what belongs to the project, so the window is counted through that join. The limits and the error are the ones every other resource a report mints already uses. No existing project is touched. A BMF v0 payload only ever resolves the empty parameter set, which every benchmark is born with and which is returned before any creation is attempted, so the ceiling is only ever reached by a payload that names parameters.
The existing backfill test runs against a database where every migration
has already been applied, so its rows are inserted after the fact and the
backfill statement itself never executes against one. Deleting the join
condition leaves it green.
This test seeds rows in the shape the old schema had, keyed on testbed,
benchmark, and measure with no parameter at all, and then re-applies the
migration over them. The empty parameter sets they have to land on are the
ones Diesel minted rather than ones spelled with jsonb('{}') in SQL, which
is where the two encodings have to agree.
It pins behavior that is already correct, so it cannot be committed red.
Two mutations of the backfill stand in for that: dropping the join to
nothing loses every row, and dropping the benchmark from the join lands
every row on every benchmark's empty set. Both fail the test.
The count is one per grid point now, which is one per benchmark for every benchmark that reports a single parameter set.
The deprecated metric field is required on the wire, so a BMF v1 measure that names no point estimate has nothing to fill it with. Pin what happens: the named rows are stored and the measure is left out of the response. The v0 fold test rides along as a probe rather than a red-first invariant. It pins behavior that is already correct, so it cannot be committed failing.
The deprecated metric field stays exactly the shape it has always had, so an older generated client keeps deserializing a report response. A measure that named no point estimate has no triple to reconstruct, so it is left out of the results rather than sent without one; its named values are stored all the same. The results are built in a pending shape first, because the deprecated triple is only in hand once every row of a measure has been read.
A measure left out of the report results has to be left out of the counts too, or the endpoint that loads a report's results and the endpoint that counts them disagree about the same report.
The aggregate count of a report's measures now counts only the measures that named a value, which is exactly the set the results carry. A BMF v0 project is unaffected: every v0 measure names a value.
A measure that names no `value` is stored and billed as an active series, so the report that created it has to say so. Pin the three views that must agree: the metric rows written, the series billed, and the measure echoed with its named values and no deprecated triple. The counts expectation flips deliberately. A value-less measure is now returned, so it is counted, and the previous expectation that both the loading endpoint and the counting endpoint leave it out is replaced with both counting it. Two of these cannot be committed failing. The BMF v0 response pin is a probe: it asserts the compatibility claim, that nothing an older client can produce loses the deprecated field, which is already true and must stay true. The comment test builds its results from JSON rather than constructors, so it fails on the wire contract itself, with `missing field metric`.
The deprecated `metric` becomes optional and a measure that carries no `value` name comes back with its named values and no deprecated triple. Its metric rows are stored and its series is billed, so leaving it out of the report that created it made stored, billable data invisible to the response that created it. Nothing an older client can produce loses the field. A measure without a `value` name is a BMF v1 shape, and the field is skipped rather than sent as null, so every response an older client can generate is byte for byte what it was. The counts follow the results: a measure is counted because it is returned, so the aggregate count drops its `value` filter. The report comment has no point estimate to draw for such a measure, so it keeps its empty cells and takes no column, exactly as it did before the field was ever required. The comment test's assertions are narrowed here: the measure is absent from the results table but still named by the missing threshold warning, which is the report telling the truth about what it ingested.
epompeii
force-pushed
the
benchmark-parameters/bmf-v1
branch
from
August 18, 2026 03:48
0a3d6c6 to
3dbd01c
Compare
The other two deprecated fields are already optional, and a bare threshold gates only the `value` name, so a measure that named none has nothing to gate. Assert that rather than reason it: both come back null, as they do for any ungated measure.
epompeii
force-pushed
the
benchmark-parameters/ingest
branch
from
August 18, 2026 03:50
58ca404 to
cdfa521
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Benchmark parameters and named metric values become rows. The schema underneath is settled; this layer is behavior: ingest, detection, billing, and the report response.
Fourth of the benchmark parameters stack. Targets
benchmark-parameters/bmf-v1and stays a draft while the layers below it are drafts.The one decision this layer cannot make for itself
Flagged rather than decided, and it gates merge: what the legacy metric-count meter does with a measure that names no point estimate. That is under billing below. The two other open decisions of earlier rounds, a missing ceiling on parameter creation and the nullability of the deprecated
metricfield, are both closed.An optional
metricfield and a value-less measureA BMF v1 measure may name only
p99and never mentionvalueat all, which the design states in as many words, and ingest is best effort by rule, so such a measure is accepted rather than refused. It has novaluerow for the deprecatedmetricto be reconstructed from.JsonReportMeasure.metricis optional, and it is absent exactly when the measure carries novaluename. Such a measure appears in the report response like any other, carrying its named values inmetricswith no deprecated triple beside them. The field is skipped rather than serialized as null, matching the other optional fields onJsonReport.thresholdandboundaryare already optional and come back null there, as they do for any ungated measure: a bare threshold gates only thevaluename, so a value-less measure has no boundary to report. That is asserted rather than reasoned.The alternative was leaving such a measure out of the results, which an earlier round of this branch shipped. It made data that is stored, billed as an active series, and queryable invisible in the response that created it. Filling
metricfrom another named scalar was never on the table: it asserts avaluethe user never reported, which is exactly the invention the fold ruling refuses.Why this is compatible. The field can only ever be absent on a measure with no
valuename, which is a shape only a BMF v1 payload can create. Every legacy response, meaning anything reachable from a BMF v0 payload on any CLI age, keeps the field exactly as before. A pinned older CLI handed a v1 file would see the absent field and fail to deserialize the response after ingest succeeds; that is a new-format adoption error rather than a legacy break, and the v1 reference docs will state that BMF v1 requires a current CLI.v0_measure_response_is_unchangedpins that claim: it captures the whole measure object of a BMF v0 report, with only the minted UUIDs and the measure entity normalized, and asserts it byte for byte, the populatedmetricand the nullthresholdandboundaryincluded. The regenerated spec hasJsonReportMeasure.requiredas["measure", "metrics"]withmetricnullable, andmetricsandmeasureboth still required.The counts follow the results, which is what they always had to do.
counts.measuresis computed two ways, from the loaded results on the report endpoint and by aggregate query on the list endpoint, andinto_json_full_and_collapsed_agreeexists to keep the two saying the same thing. A value-less measure is returned, so it is counted: the aggregate counts every measure with a metric row, which is precisely the set the results carry. No BMF v0 project's counts move, because a v0 measure always names avalue.The parameter creation ceiling
Parameter rows are minted straight from report content, one per grid point, and until this round nothing bounded them.
QueryBenchmark,QueryMeasure,QueryBranch,QueryTestbed,QueryThreshold, andQueryReportall carry a per project ceiling; the report-level limit bounds reports per window, not entities minted inside a single payload, which is why each of those needs its own. A harness that interpolates a commit sha or a timestamp intoparameterswould otherwise mint a fresh parameter row per report without limit, and every new grid point is also a new billable active series.parameterhas noproject_id, sofn_rate_limit!does not apply as written. The ceiling is hand written instead and counts the window through the join that already exists: a parameter set belongs to its benchmark, and the benchmark belongs to the project. The limits and the error are the ones every other resource a report mints already uses, so a project that trips it gets exactly the message it gets today for a flood of benchmark names.Two consequences worth stating.
No existing project is touched. A BMF v0 payload only ever resolves the empty parameter set, which every benchmark is born with and which is returned before any creation is attempted, so the ceiling is only ever reached by a payload that names parameters.
The count includes the empty parameter set each benchmark is born with, which makes the ceiling slightly conservative for a project creating benchmarks and grid points in the same window. That is the safe direction, and it costs a project nothing the benchmark ceiling was not already going to cost it.
The alternatives were denormalizing
project_idontoparameter, which reopens a settled lower layer's schema for a count, and recording the gap as accepted, which leaves the same unbounded pathfn_rate_limit!exists to close.Ingest
Each BMF v1 entry resolves to its parameter row during the existing phase 1 preparation, alongside
QueryBenchmark::get_or_createandQueryMeasure::get_or_create, never inside the phase 2 write transaction. That placement is forced:write_transaction!does not nest, andQueryParameter::get_or_createopens one of its own to insert a new parameter set. An entry with noparametersresolves to the benchmark's empty parameter set, which already exists by the birth invariant and is never created lazily; a missing empty set is data corruption and is reported as such rather than minted. A resolved parameter row that is archived is unarchived, exactly asQueryBenchmark::get_or_createdoes.The parameter cache keys on
(benchmark, parameter set)rather than on the benchmark, since one benchmark has as many grid points as it has parameter sets.Named metric rows are one
metricrow per name per measure perreport_benchmark. Thevaluerow is written first solast_insert_rowidstill names the row a boundary attaches to.Duplicate grid points. Two v1 entries that canonicalize to the same parameter set and carry the same measure now union at the metric-name level, last wins per name. Previously the later entry replaced the whole
AdapterMetric, so[{measures:{latency:{p50:1.0}}}, {measures:{latency:{p99:2.0}}}]yieldedp99alone with a drop count of zero. Nothing is dropped now, so nothing needs counting. The comment at the merge site was narrowed from measure granularity to name granularity.Detection
Baselines key on
(benchmark, parameter, measure): the historical query indetector/data.rsgainsreport_benchmark.parameter_id, so one grid point's sample is never the benchmark's grid pooled.Bare thresholds gate only the conventional
valueseries, of every parameter set under their measure. That is exactly what a measure-level threshold over flat benchmarks has always done, so no project's alert volume changes. Threshold matching itself is untouched: still(branch, testbed, measure), andthresholdgains no columns.Alert-parity evidence.
alert_volume_is_identical_for_flat_benchmarks_and_grid_pointsingests the same six reports of the same two measurement series twice: once as two flat benchmarks (bench_16,bench_32) under one measure-level threshold, and once as one benchmark's two parameter sets under the same threshold. Both projects raise exactly one alert, and the grid project's alert is on the grid point that regressed.Per grid point baselines, proved separately. The parity fixture cannot tell a per grid point baseline from a pooled one: its two grid points are a decade apart and the regression is tenfold, so it is an outlier either way.
baselines_separate_by_parametertherefore has its own numbers, chosen so that pooling hides the regression: two tight histories two decades apart,[10, 11, 12, 13, 14]beside[1000, 1001, 1002, 1003, 1004], with the small grid point finishing at 50 and the large one steady at 1004. Fifty is a large outlier against its own history and sits well inside the pooled spread. Delete theparameter_idfilter fromdetector/data.rsand the test goes from one alert to none, which is recorded below.Billing
series_last_seenis recreated withparameter_idin its primary key, per the table-recreate convention, with every existing row backfilled to its benchmark's empty parameter set and all four indexes re-created (plus a fifth on the newparameter_idforeign key). The backfill joinsparameteron the empty set, which is an inner join because every benchmark has one: it is born with it, and the parameter migration backfilled every benchmark that predates the birth invariant.last_seencarries over untouched, so no series is resurrected and none is lost.down.sqlcollapses grid points back into one series per(testbed, benchmark, measure), keeping the greatestlast_seenof the rows that merge.Each grid point bills as its own series, which preserves today's economics exactly: a project whose grid points are currently flat benchmarks bills the same after migrating to parameters. Named values collapse into their measure's series and are not billed; the cap of eight is the guardrail on that.
The four series columns now travel together as a
SeriesKey, because they are one identity and they are what the primary key is.The backfill itself is pinned against a legacy database by
migration_backfill_preserves_every_series_row, described under the tests below.What happened to metric-count billing
The legacy metric-count meter (
QueryMetric::usage, used for Team, metered Enterprise, and licensed entitlements) already filters onname = 'value', so named values do not raise a v1 project's metric count. A measure carryingvalue,lower_value,upper_value, andp99counts one, which is exactly what the row-per-measure table counted. That filter predates this layer.The reachable change runs the other way. A BMF v1 measure may name only
p99and never namevalue. Such a measure counts zero onQueryMetric::usage, zero inmetric_count_by_report, and zero in the in-request usage the plan check and themetrics.createcounter see, while still billing as an active series on Pro. That is a meter hole that no payload could reach before this layer, and it is not obviously the intended meter.Nothing was adjusted for it.
QueryMetric::usageis untouched. The only alignment made was toiteration_metric_count, which now counts measures that named avaluerather than measures outright, so the in-request meter and the billing read cannot drift from each other.named_values_do_not_change_the_metric_countpins both halves of what happens today. This is flagged for a decision, not decided here.The report response
JsonReportMeasuregainsmetrics: Vec<JsonReportMetric>, one entry per named scalar in lexicographic order, each carryingboundaries: Vec<JsonReportBoundary>that pairs every threshold that gated it with the boundary it produced. The list is plural now so the wire never breaks again when threshold predicates ship; it is length 0 or 1 until then.It retains
metric,threshold, andboundaryas deprecated fields reconstructed from thevaluerow and itslower_value/upper_valuesiblings. Nothing in the repository usesdeny_unknown_fields, so the additive fields are invisible to existing clients. For a measure that named novalue,metricis absent andthresholdandboundaryare null, per the section above; all three are present for everything an older client can produce.The results are assembled in a pending shape and finished at the end, because the deprecated triple is only in hand once every row of a measure has been read.
The grouping fix this layer owed regardless of response shape.
into_report_results_jsongrouped by consecutive rows sharing a benchmark uuid, so two parameter sets under one benchmark silently merged into oneJsonReportResult. The ordering is now(iteration, benchmark.name, parameter.id, measure.name, metric.name)and the grouping keys on the grid point, so two grid points are two results.JsonReportResultcarriesparameter: JsonReportParameter { uuid, parameters }, so the response is never ambiguous about which grid point a result belongs to.Both halves of that fix are pinned.
report_response_echoes_named_values_and_separates_grid_pointsruns two measures across two parameter sets, which is the smallest fixture that can see the ordering: with one measure per grid point the rows arrive grouped whether or not the parameter is in theORDER BY. With two, dropping the parameter lets the measure name outrank it, the rows interleave by grid point, and the consecutive row grouping emits four results of one measure each. The test asserts two results each carrying both measures.The counts follow:
JsonReportIterationCounts.benchmarksis now a count of distinctreport_benchmark.id, which is exactly one per grid point, so the aggregate-query count and the count taken from the loaded results still agree.into_json_full_and_collapsed_agreecovers that, andfull_and_collapsed_counts_agree_for_a_value_less_measurecovers the measure count when a measure carries novaluename.The alerts query still reads through the
metric_boundaryview, since alerts only ever attach tovaluerows, which is what the view keys on.Fold
Per the ruling,
--foldstays a BMF v0 operation. A v1 payload with fold requested warns and ingests unfolded: each iteration lands as its ownreport_benchmarkrow, exactly as it would without the flag. Never an error, and never a silently dropped named value. The fold machinery is not removed and v0 fold works exactly as before; fold over v0 keys on the benchmark and its empty parameter set by construction, since a v0 payload only ever carries the empty set.v0_fold_still_foldsis the end-to-end guard on that promise, described under the tests below. The CLI-side deprecation warning is a following layer.The cap's log and counter
The adapter truncates and reports a drop count. This layer emits the log line and increments a new
metrics.names.droppedcounter, because this is where the logger and the meter are in scope. Never an ingest error:named_value_cap_does_not_fail_the_reportposts a measure with ten names and gets a report back with eight.Tests, red before green
Commit
8fce999is tests only, and every hunk in it is inside a#[cfg(test)]module or a test binary. How each area failed there:400against201, from the "not yet supported on ingest" refusal this layer removes.p50was missing from the unioned names.grid_points_count_as_distinct_seriesandnamed_values_collapse_into_their_measure_seriesfailed on assertions, 1 against 3 and 1 against 2, because the cache and its oracle both keyed without the parameter.backfill_maps_existing_rows_to_the_empty_parameter_setfailed at runtime withno such column: s.parameter_id, deliberately written as raw SQL so the red failure is a runtime one rather than a compile error across the crate.named_values_do_not_change_the_metric_countwas added after the implementation, as a probe that records what the legacy meter does rather than as a red-first invariant.The two hardened tests, proved by mutation rather than by red
Commit
4aaa122changes no production code. It rewrites two tests that named an invariant they could not observe, so they cannot be red against a correct implementation; the gap was in the tests, and the proof is that each now fails when the code it describes is removed. Both mutations were applied to a clean tree, built, run, and reverted..filter(schema::report_benchmark::parameter_id.eq(parameter_id))fromdetector/data.rsbaselines_separate_by_parameterleft: []againstright: [({"size_mb": 16}, "value")]schema::parameter::idfrom the resultsORDER BYinreport/mod.rsreport_response_echoes_named_values_and_separates_grid_pointsleft: 4againstright: 2resultsUnmutated, every test in the file passes.
This round: the migration backfill, and the ceiling
Two more commits, in that order.
Rate limit parameter creation per projectfollows the red-first rule literally. The test commit before it posts one report over the ceiling and expects the refusal a benchmark name flood already gets; it failed there withleft: 201againstright: 429. The test server grew a creation limit knob in the same commit, because every existing test server has its limits wide open and that is the only way to reach a ceiling without throttling the requests that get there.Pin the series migration backfill against a legacy databasecannot be committed red, because it pins behavior that is already correct. The gap it closes is that the existingbackfill_maps_existing_rows_to_the_empty_parameter_setruns against a database where every migration has already been applied, so its rows are inserted afterwards and the backfill statement never executes against one: deleting the join leaves it green. The new test reverts to the series migration, seeds rows in the shape the old schema had, keyed on testbed, benchmark, and measure with no parameter at all, and re-applies. The empty parameter sets those rows have to land on are the ones Diesel minted rather than ones spelled withjsonb('{}')in SQL, which is exactly where the two encodings have to agree. Two mutations stand in for the red commit:AND 1 = 0on the join, so it matches nothingp.benchmark_id = s.benchmark_idfrom the joinWhat a dropped row costs in production is worth naming: it is not an error. An organization's active-series count for the current period reads zero until every series reports again, and the upgrade looks clean throughout.
An earlier round: v0 fold, and a first pass at the value-less measure
Commit
697883dis tests only.value_less_measure_is_stored_but_not_echoedfailed there on an assertion,left: [Some("latency"), Some("throughput")]againstright: [Some("latency")]: the measure that named no point estimate was echoed without ametric.v0_fold_still_foldsrides along in that commit as a probe rather than a red-first invariant, because it pins behavior that is already correct. It is the test the fold ruling was owed:--foldis deprecated, not deleted, so a BMF v0 report must fold exactly as it always has. Two BMF v0 iterations withfold: minland as onereport_benchmarkrow on the benchmark's empty parameter set, carrying the smaller iteration's whole triple, and metering one metric, which is the half of the ruling that says no bill moves. The conversion it covers,From<FoldableResults> for AdapterResults, is new in this layer and every folded v0 report passes through it. Two mutations stand in for the red commit:left: []againstright: [({}, 1)]leftcarries two parameter sets where there should be oneBefore this test, the first of those mutations left the entire workspace green: a pipeline running
bencher run --fold minwould have landed reports with no metrics, no boundaries, no alerts, and a metric count of zero, at201with a green build.Commit
d515105is then tests only again, for the counts.full_and_collapsed_counts_agree_for_a_value_less_measurefailed there on the two counts disagreeing,measures: 1from the loaded results againstmeasures: 2from the aggregate query, which is the defect the following commit fixes.Those four commits took the response call in the other direction, leaving a value-less measure out of the results and out of the counts. The round below reverses it.
v0_fold_still_foldsand its mutation evidence stand unchanged.This round: a value-less measure is returned
Commit
71e07c5is tests only, and every hunk in it is inside a#[cfg(test)]module or a test binary. How each failed there:value_less_measure_is_stored_billed_and_echoedleft: [Some("latency")]againstright: [Some("latency"), Some("throughput")]full_and_collapsed_counts_agree_for_a_value_less_measureleft: 1againstright: 2measures returnedreport_table_value_less_measuremissing field metricThe comment test is red on the wire contract itself rather than on an assertion: it builds its report results from JSON rather than from constructors, precisely because the absent deprecated field is what is under test, so a required
metricfails it at deserialization.v0_measure_response_is_unchangedrides along in that commit as a probe rather than a red-first invariant, because it pins the compatibility claim, which is already true and has to stay true.The counts expectation flips deliberately, and it is the one behavior of the earlier round that this reverses rather than extends: a value-less measure is counted because it is returned, so
get_report_countsdrops thename = 'value'filter that round added.The three views a report has of the same measure are asserted together, since the whole point of the change is that they agree:
value_less_measure_is_stored_billed_and_echoedreads the metric rows that ingest wrote, theseries_last_seenrow that bills them, and the measure the response carries. The legacy metric-count meter is pinned in the same test at one, unchanged and still the open question under billing above.bencher_commentrenders such a report without panicking. There is no point estimate to draw, so the measure takes no column in the results table and the report's missing-threshold warning still names it, which is what the code did before the field was ever required.Changes to a lower layer's tests
Two are worth a reviewer's attention.
The single-valued metric equivalence test no longer captures the report response.
metric_migration.rsseeds a database in the pre-migration shape, captures the raw bytes of every response that reads a metric, migrates, and captures again. The report response can no longer participate: it is built from the namedmetricrows, and those rows do not exist before the migration, so it has no pre-migration form to compare. It is also not stable across a down-and-up round trip, because the migration mints a fresh uuid for every bound row it recreates and the response now echoes those uuids. Neither is a regression: a server runs its migrations before it serves, and thevaluerow, which every reader of the old shape saw, keeps its identity. The perf, metrics, and alerts responses still capture on both sides and still pin the equivalence, since they all read through themetric_boundaryview whose column list that migration holds unchanged.revert_migrationnow reverts down to the metric migration rather than reverting the last one. The metric migration is no longer last, so reverting only the last one would revert this layer's instead. Same pattern the parameter layer already uses.Gates
cargo nextest runcargo nextest run -p bencher_schema --features pluscargo nextest run -p api_projects --features pluscargo test --doccargo fmt --checkcargo clippy --no-deps --all-targets --all-features -- -Dwarningscargo check --no-default-featurescargo gen-typesservices/api/openapi.jsonandservices/console/src/types/bencher.tsare committedcargo deny checktsc --noEmit, 435 errors with this branch'sbencher.tsand 435 with the previous commit's, an identical set once line numbers are normalized, so no new error. The console has a pre-existing backlog that this is measured against rather than intobencher_clientandbencher_clibuild against the regenerated spec, so progenitor accepts the newJsonParametersschema (an object whose additional properties are a string, number, or bool).One lint suppression
ApiCounter::descriptioncrossedclippy::too_many_lineswhen the new counter's arm was added, and carries#[expect(clippy::too_many_lines, reason = "exhaustive match over every counter variant")], matching how exhaustive matches are already handled elsewhere in the workspace.