Skip to content

Fix the release-signoff metric lint skipping fenced files - #38487

Open
antiguru wants to merge 2 commits into
MaterializeInc:mainfrom
antiguru:mh/signoff-lint-fence-fix
Open

Fix the release-signoff metric lint skipping fenced files#38487
antiguru wants to merge 2 commits into
MaterializeInc:mainfrom
antiguru:mh/signoff-lint-fence-fix

Conversation

@antiguru

Copy link
Copy Markdown
Member

Motivation

A post-merge review of #38355 found that the metric lint added there checks almost nothing in half the files it covers, and that the gap hid two roster entries naming metrics the product does not export.

Description

The extractor paired backticks sequentially across the whole document. A fenced block contains backticks of its own, so pairing across one flips the parity of every span after it: prose is captured as code and the real code spans become the separators between matches. Three of the six files in the skill contain a fence.

File Fenced Names checked before After
SKILL.md yes 0 all
compute.md yes 0 all
sources-and-sinks.md yes 2 of 40 all
adapter.md no 31 of 39 all
persist.md no 46 of 48 all
reference-dashboards.md no 19 of 19 all

A rename in any fenced file passed CI silently, which is the one failure the lint exists to prevent. Fenced blocks are now pulled out as whole tokens before backticks are paired, and the wildcard skip is tested per word rather than per token, since a fence arrives as a single token and one wildcard inside it must not exempt the block.

The lint also now expands the abbreviations that roster rows use. A row writes a family as `mz_foo_bar`, `_baz`, and a bare continuation carries no mz_ prefix, so it was never a candidate. Which prefix of the base it attaches to is ambiguous, because rows drop either one component or two, so the first expansion that resolves is accepted and only a continuation with no resolving expansion is reported. Continuations are sought within a single line, since carried across lines they attach to whatever name appeared in an earlier paragraph and manufacture failures rather than finding them. Bare histogram suffixes are excluded, being prose about the parts of a histogram rather than family members.

Two dead roster entries

Both were sitting in roster tables as live metrics with reading instructions, which is worse than being absent, because the skill itself names an empty panel as the most dangerous reading on any dashboard.

  • mz_storage_upsert_state_rocksdb_autospill_in_use went with the autospill upsert backend. It appears nowhere in src/, in doc/user/data/metrics.yml, or in Prometheus, and the surviving members of the family are _rehydration_latency, _rehydration_total, and _rehydration_updates. Its row still explained that the percentage panel divides sum by count, so a verifier following it would conclude no worker is spilling rather than that the concept is gone.
  • There is no mz_persist_gc_skipped. The already-done case is mz_persist_gc_noop, whose help text reads "count of garbage collections skipped because they were already done", and the only skipped counter is mz_persist_compaction_skipped.

Both rows now record the metric as gone. Names that the references document as dead on purpose, such as the deliberate or hedge across the storage controller protocol rename, moved into the allowlist with their reason.

Verification

Every file in the skill now fails the lint when one of its metric names is mutated, including the three fenced ones, and the continuation path fails when a roster abbreviation is mutated. The stale-allowlist check still fires when an exemption starts resolving.

🤖 Generated with Claude Code

The extractor paired backticks sequentially across the whole document. A
fenced block contains backticks of its own, so pairing across one flipped the
parity of every span after it: prose was captured as code and the real code
spans became the separators between matches. Three of the six files in the
skill contain a fence, and in those the lint checked almost nothing. SKILL.md
and compute.md contributed no names at all and sources-and-sinks.md
contributed two of forty, so a rename in any of them passed CI silently,
which is the one failure the lint exists to prevent.

Pull fenced blocks out as whole tokens before pairing backticks in what
remains, and test the wildcard skip per word rather than per token, since a
fence arrives as a single token and one wildcard inside it must not exempt
the block.

Also expand the abbreviations that roster rows use. A row writes a family as
`mz_foo_bar`, `_baz`, and a bare continuation carries no mz_ prefix, so it
was never a candidate. Which prefix of the base it attaches to is ambiguous,
because rows drop either one component or two, so accept the first expansion
that resolves and report only when none does. Continuations are sought within
a single line: carried across lines they attach to whatever name appeared in
an earlier paragraph and manufacture failures. Bare histogram suffixes are
excluded, being prose about the parts of a histogram rather than family
members.

Together these take the guard from 98 names to every file catching a rename,
and they surface two roster entries that name metrics the product does not
export. `mz_storage_upsert_state_rocksdb_autospill_in_use` went with the
autospill upsert backend, and its row still explained how to read the
percentage panel, so a verifier would conclude no worker is spilling rather
than that the concept is gone. There is no `mz_persist_gc_skipped`: the
already-done case is `mz_persist_gc_noop`, and the only skipped counter is
`mz_persist_compaction_skipped`. Both rows now record the metric as gone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@antiguru
antiguru requested a review from bosconi August 26, 2026 13:17
@def-

def- commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

QA LLM Review

1. MEDIUM -- Bare-suffix guard silently exempts a real roster continuation

.agents/skills/mz-release-signoff/scripts/lint_metrics.py:138

The guard that treats a bare `_count`/`_sum`/`_bucket` span as prose also swallows the one roster row that legitimately abbreviates a family member with a bare suffix, so mz_persist_read_batch_part_count is never checked and its removal leaves the lint green with a roster row pointing at a metric the product no longer exports.

Details

The comment justifying the skip says a bare histogram suffix is "never a family member abbreviated in a roster row", but references/persist.md:38 is exactly that:

| `mz_persist_read_batch_part_bytes`, `_count` | counter, by `op` | ... |

mz_persist_read_batch_part_count is a separately registered counter (src/persist-client/src/internal/metrics.rs:374), not a histogram component of _bytes. Deleting its entry from doc/user/data/metrics.yml and re-running the lint exits 0.

The PR's verification method cannot surface this. Mutating the abbreviation in the doc (`_count``_countzqx`) does fail, because the mutated span is no longer in SUFFIXES and falls through to the continuation path. Only removing the metric from the catalog, which is the regression the lint exists to catch, slips past.

Suggested fix: keep the guard and spell that one row out, rather than teaching the guard to tell a roster row from prose. A "table rows are never prose" refinement does not hold either, since adapter.md:38 is a roster row whose `_sum` is prose about the row's own histogram.

-| `mz_persist_read_batch_part_bytes`, `_count` | counter, by `op` |
+| `mz_persist_read_batch_part_bytes`, `mz_persist_read_batch_part_count` | counter, by `op` |

With that, the lint still exits 0 on a clean tree and reports mz_persist_read_batch_part_count when the catalog entry is dropped.

2. LOW -- First-resolving-prefix hides the removal of four abbreviated metrics

.agents/skills/mz-release-signoff/scripts/lint_metrics.py:82

For four of the 55 continuations in the skill more than one prefix cut resolves, so the abbreviation stays green after its intended target is removed and silently re-binds to an unrelated live metric.

Details

resolve_continuation walks prefixes longest-first and accepts the first hit, which the docstring frames as reporting "only when no prefix does, which is what a removed metric looks like". For these four that is not what a removed metric looks like:

Row base Continuation Intended Also resolves to
mz_storage_upsert_merge_snapshot_updates_total _deletes_total ..._merge_snapshot_deletes_total mz_storage_upsert_deletes_total
mz_storage_upsert_merge_snapshot_updates_total _inserts_total ..._merge_snapshot_inserts_total mz_storage_upsert_inserts_total
mz_compute_controller_replica_count _collection_count mz_compute_controller_collection_count mz_compute_collection_count
mz_persist_pushdown_parts_filtered_count _bytes ..._parts_filtered_bytes mz_persist_pushdown_parts_bytes, via the mz_persist_*_bytes glob

The first pair is the worse one, because mz_storage_upsert_deletes_total is itself listed as a live metric a few rows up in the same file, so the fallback binds to a metric a reader would not confuse with the intended name.

Cheap tightening: accept only the longest resolving cut, and report when a shorter cut also resolves so the row gets disambiguated once. Together with finding 1 these four are the only mz-namespace roster entries left unprotected after this change, so pinning them costs little.

An abbreviated roster entry such as `mz_foo_bar`, `_baz` resolves by trying
each prefix of the base, since the row does not say how many components the
abbreviation drops. Four entries resolve against more than one prefix. The
longest cut is the intended metric and is the one accepted, so nothing binds
wrongly today, but deleting that metric leaves a shorter cut resolving, and
the lint stays green on a row that points at nothing.

Report an abbreviation that resolves more than one way, and spell those four
out in full. Also spell out `mz_persist_read_batch_part_count`, which the
bare-suffix guard was skipping as prose about a histogram. It is a separately
registered counter, so its removal was undetectable.

Record the two remaining bounds on what a green run proves: a name covered by
a glob catalog entry cannot be falsified, and a bare `_count`, `_sum`, or
`_bucket` is read as prose.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.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

Development

Successfully merging this pull request may close these issues.

2 participants