Skip to content

fix(tooling): require the plumbing-skip's sole ctor be reachable as the helper's result - #964

Merged
aallan merged 4 commits into
aallan:mainfrom
chethanuk:fix/issue-956-plumbing-skip-reachability
Jul 9, 2026
Merged

fix(tooling): require the plumbing-skip's sole ctor be reachable as the helper's result#964
aallan merged 4 commits into
aallan:mainfrom
chethanuk:fix/issue-956-plumbing-skip-reachability

Conversation

@chethanuk

@chethanuk chethanuk commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Fixes #956

Summary

  • `_plumbing_ctors` in `scripts/check_diagnostic_fields.py` already required a genuine helper method (Diagnostic-fields gate: plumbing-skip keys on function name only — a stray/second Diagnostic in an _error/_warning helper escapes both passes #827's fix) whose sole own-scope `Diagnostic()` construction is unambiguous, but never confirmed that construction was actually the helper's output.
  • A helper that builds its one ctor and hands it to something other than return/append — e.g. `self.dispatch(d)` instead of `self.errors.append(d)` — still had it elected as plumbing and skipped by all three passes, so a bogus `spec_ref` or unregistered `error_code` on it would ship silently.
  • New `_ctor_is_reachable_as_result` requires the sole ctor be return-ed, appended, or bound to a local that is later return-ed/appended, gated after the existing single-ctor check so Diagnostic-fields gate: plumbing-skip keys on function name only — a stray/second Diagnostic in an _error/_warning helper escapes both passes #827's ambiguity handling is untouched.
  • Correction to the issue's own acceptance criteria: the repro's target is `(0, 1, 1)`, not `(1, 1, 1)` — all fields in the repro are literal-present, so the field-presence pass stays clean; only `spec_ref` and `error_code` validity fire once the ctor is un-exempted.
  • On `vera/` the exempt set is unchanged: all five real helpers (`checker/core.py:280`, `codegen/core.py:412,442`, `verifier.py:352,378`) return or append their sole ctor directly, so the gap was latent, closed defensively.

Test plan

  • New `TestPlumbingSkipRequiresReachability` class: RED confirmed pre-fix (dispatch-repro escaped detection), GREEN after the fix, plus a control test (`self.errors.append(...)` shape) that stays exempt throughout.
  • Mutation-kill: temporarily reverted `_ctor_is_reachable_as_result` to `return True`, confirmed the dispatch-repro test goes RED, restored.
  • `python scripts/check_diagnostic_fields.py` exits 0 on `vera/` — same 5 exempt ctors as before.
  • `pytest tests/test_diagnostic_fields.py -v` — all green.
  • `mypy vera/` clean.
  • Full `pre-commit run --all-files` green, including doc-count sync.
  • Fork PR (fix(tooling): require the plumbing-skip's sole ctor be reachable as the helper's result chethanuk/vera#6) pre-flight validated first: full CI matrix green.

Summary by CodeRabbit

  • Bug Fixes
    • Strengthened the diagnostic-fields “plumbing-skip” logic so helper-created Diagnostic(...) calls are exempt only when the single own-scope constructor is provably reachable as the helper’s result (e.g., returned/appended), not merely dispatched.
    • Added conservative handling for alias rebinding (including reassignment and for-loop targets) to avoid incorrect skipping.
  • Tests
    • Added reachability-focused plumbing-skip test coverage for dispatched, appended, reassigned, and for-rebound scenarios.
  • Documentation
    • Updated changelog, roadmap, and testing guidance to match the revised requirements.

@chethanuk
chethanuk requested a review from aallan as a code owner July 8, 2026 21:18
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The diagnostic-fields gate now only exempts a helper’s sole own-scope Diagnostic(...) constructor when that constructor is reachable as the helper’s result. Tests and project notes were updated to match.

Changes

Plumbing-skip reachability check

Layer / File(s) Summary
Reachability helpers and gating logic
scripts/check_diagnostic_fields.py
Adds AST reachability helpers for helper-result flow and applies them to _plumbing_ctors, so a sole constructor is skipped only when it is returned, appended, or bound to a local that is later returned/appended.
Tests for reachability requirement
tests/test_diagnostic_fields.py
Adds TestPlumbingSkipRequiresReachability with cases for dispatched-but-not-returned, appended, reassigned-local, loop-rebound, and unrelated-local append diagnostics to exercise the new skip condition.
Changelog, roadmap and testing docs updates
CHANGELOG.md, ROADMAP.md, TESTING.md
Records the fix, refreshes test-count metrics, removes the Tier 2 roadmap entry for #956, and updates the test guide description of the narrowed plumbing-skip rule.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • aallan/vera#826: Introduced the original diagnostic-fields plumbing-skip path that this PR further constrains.
  • aallan/vera#952: Narrowed plumbing-skip eligibility to the helper’s own constructor, which this PR extends with reachability.

Suggested labels: tests, ci, docs

🚥 Pre-merge checks | ✅ 5 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.15% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Diagnostics Carry An Error Code ⚠️ Warning The new test fixtures add several Diagnostic(...) examples with error_code='E9999', which does not match the required [EW]\d{3} stable-code pattern. Use a 3-digit stable code (e.g. E699/W001) for any added diagnostic, or make the fixture explicitly exempt if it is only a negative test.
Linked Issues check ❓ Inconclusive The core reachability fix and regression cases match #956, but the summary does not show the required mutation-validation step. Confirm the reachability check was neutered in a mutation run and that the existing plumbing-skip tests were updated intentionally.
✅ 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 and concisely states the main behaviour change.
Out of Scope Changes check ✅ Passed The doc updates appear supportive of the fix, and no unrelated code changes stand out in the summary.
Changelog Covers Public-Surface Changes ✅ Passed No changed files touch vera/cli.py, vera/errors.py, spec/, vera/lsp/, or vera/codegen/api.py; CHANGELOG only documents the internal diagnostic-fields gate fix.
Spec And Implementation Move Together ✅ Passed Only docs/tests/scripts changed; no spec/ or vera/ files were touched, so there’s no spec/implementation drift here.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@codecov

codecov Bot commented Jul 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.29%. Comparing base (0c47eab) to head (2be2c76).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #964   +/-   ##
=======================================
  Coverage   93.29%   93.29%           
=======================================
  Files          96       96           
  Lines       31448    31448           
  Branches      456      456           
=======================================
  Hits        29339    29339           
  Misses       2096     2096           
  Partials       13       13           
Flag Coverage Δ
javascript 78.41% <ø> (ø)
python 95.08% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@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: 2

🤖 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 `@scripts/check_diagnostic_fields.py`:
- Around line 276-302: The reachability check in _ctor_is_reachable_as_result is
too permissive because it treats any later return or append of the local name as
proof that the ctor result is reachable, even if that name was rebound first.
Update the logic in _ctor_is_reachable_as_result to track whether the assigned
local (for example, the Name captured from ast.Assign) is reassigned before the
final return/append, and treat such rebinding as breaking reachability. Use the
existing _walk_own_scope and _is_append_call flow, but ensure the ctor binding
remains the same value when the name is returned or appended.

In `@tests/test_diagnostic_fields.py`:
- Around line 640-708: Add an explicit negative fixture in
TestPlumbingSkipRequiresReachability for the rebinding-before-return case so
`_ctor_is_reachable_as_result` is exercised on a ctor that is overwritten before
escape. Use the existing `Diagnostic`/`check_source` style fixtures to model `d
= Diagnostic(...); d = other(); return d`, then assert
`spec_ref_violations_in_source` and
`error_code_registration_violations_in_source` report violations instead of
skipping. Keep the new case alongside `DISPATCHED_NOT_RETURNED` and `APPENDED`
so the reachability gate is pinned by this test class.
🪄 Autofix (Beta)

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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 94df453e-bd38-46c1-b567-f53d8d535736

📥 Commits

Reviewing files that changed from the base of the PR and between 6b31396 and 2fdfed5.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • ROADMAP.md
  • TESTING.md
  • scripts/check_diagnostic_fields.py
  • tests/test_diagnostic_fields.py
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • aallan/vera-bench (manual)

Comment thread scripts/check_diagnostic_fields.py
Comment thread tests/test_diagnostic_fields.py
@chethanuk
chethanuk force-pushed the fix/issue-956-plumbing-skip-reachability branch 2 times, most recently from b54f2be to 0f08954 Compare July 9, 2026 01:44

@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 `@scripts/check_diagnostic_fields.py`:
- Around line 271-273: The reachability logic in `_is_append_call` is too broad
because it treats any `append(...)` as plumbing, which can let `Diagnostic(...)`
escape field checks through unrelated containers. Tighten the sink detection in
`scripts/check_diagnostic_fields.py` so only the real diagnostic sink(s) are
considered, and do not count arbitrary intermediate list appends as
reachability. Update the related traversal logic around `_is_append_call` to use
the specific sink symbols already present in the script, and add a regression
test covering a non-sink case such as `tmp.append(d)`.
🪄 Autofix (Beta)

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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3c53acc4-c304-4f54-a39c-05ea5b380d29

📥 Commits

Reviewing files that changed from the base of the PR and between b54f2be and 0f08954.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • ROADMAP.md
  • TESTING.md
  • scripts/check_diagnostic_fields.py
  • tests/test_diagnostic_fields.py
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • aallan/vera-bench (manual)

Comment thread scripts/check_diagnostic_fields.py Outdated

@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: 2

♻️ Duplicate comments (1)
scripts/check_diagnostic_fields.py (1)

271-273: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not treat arbitrary .append(...) calls as plumbing reachability.

This still counts tmp.append(d) as a helper output even when tmp is just an intermediate container, so a bogus sole ctor can remain exempt without being returned or appended to the real diagnostic sink. Restrict this predicate to the actual diagnostic sink shape(s), and keep a regression for a non-sink append.

#!/bin/bash
set -euo pipefail

# Inspect current helper append sinks without executing repository code.
rg -n -C 4 'def _(?:error|warning)\b|\.append\(' vera scripts tests
🤖 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 `@scripts/check_diagnostic_fields.py` around lines 271 - 273, The
_is_append_call helper is too broad because it treats any ast.Call with an
append attribute as plumbing reachability, so narrow it to only the real
diagnostic sink patterns used by the diagnostic-field logic in
scripts/check_diagnostic_fields.py. Update the predicate and any callers that
rely on it so only genuine sink-shaped appends are counted, not intermediate
containers like tmp.append(...). Add or adjust a regression test that exercises
a non-sink append and verifies it is not treated as helper output.
🤖 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 `@scripts/check_diagnostic_fields.py`:
- Around line 287-310: The rebinding counter in _rebound_names currently misses
annotated assignments, so an existing ctor name can still be treated as
unchanged after an AnnAssign overwrite. Update _rebound_names to handle
ast.AnnAssign alongside Assign, AugAssign, and the other binding forms, and add
a test fixture that overwrites a ctor with an annotated assignment so the
reachable-as-result check covers this case.

In `@tests/test_diagnostic_fields.py`:
- Around line 709-753: Add a new regression test in the diagnostic field
coverage around spec_ref_violations_in_source to cover annotated rebinding with
ast.AnnAssign, since the current rebinding checks only exercise ast.Assign and
for-loop targets. Create a fixture in the same style as REASSIGNED_BEFORE_RETURN
and REBOUND_VIA_FOR_LOOP where d starts as a Diagnostic, is then rebound via an
annotated assignment like d: object = self.template, and is returned; assert the
result is still inspected and not skipped. Keep the new test alongside
test_reassigned_local_is_not_reachable_as_result and
test_for_loop_rebound_local_is_not_reachable_as_result so the rebinding logic in
mod.spec_ref_violations_in_source is validated for this additional syntax form.

---

Duplicate comments:
In `@scripts/check_diagnostic_fields.py`:
- Around line 271-273: The _is_append_call helper is too broad because it treats
any ast.Call with an append attribute as plumbing reachability, so narrow it to
only the real diagnostic sink patterns used by the diagnostic-field logic in
scripts/check_diagnostic_fields.py. Update the predicate and any callers that
rely on it so only genuine sink-shaped appends are counted, not intermediate
containers like tmp.append(...). Add or adjust a regression test that exercises
a non-sink append and verifies it is not treated as helper output.
🪄 Autofix (Beta)

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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4ee9dc89-45bd-4dba-9978-54b701c9e777

📥 Commits

Reviewing files that changed from the base of the PR and between 0f08954 and f1267f4.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • ROADMAP.md
  • TESTING.md
  • scripts/check_diagnostic_fields.py
  • tests/test_diagnostic_fields.py
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • aallan/vera-bench (manual)

Comment thread scripts/check_diagnostic_fields.py Outdated
Comment thread tests/test_diagnostic_fields.py
…he helper's result

_plumbing_ctors already required a genuine helper method (aallan#827's fix) whose
sole own-scope Diagnostic() construction is unambiguous, but never confirmed
that construction was actually the helper's output. A helper that builds its
one ctor and hands it to something other than return/append — e.g.
self.dispatch(d) instead of self.errors.append(d) — still had it elected as
plumbing and skipped by all three passes, so a bogus spec_ref or unregistered
error_code on it would ship silently.

_ctor_is_reachable_as_result now requires the sole ctor be return-ed,
appended, or bound to a local that is later return-ed/appended, gated after
the existing single-ctor check so aallan#827's ambiguity handling is untouched. On
vera/ the exempt set is unchanged: all five real helpers return or append
their sole ctor directly, so the gap was latent, closed defensively.

The name-based return/append match in the second half of the check has no
real data-flow: a local rebound after the ctor-binding assign would still
match on the name alone and be wrongly treated as reachable. Fixed
conservatively via _rebound_names, which counts every name-binding form in
own scope — plain and augmented assignment, for/with targets, walrus, and
except-as names — not just plain Assign. A name rebound more than once by
any of these forms is unreliable, so the ctor is inspected rather than
exempted.

Separately, _is_append_call treated any .append(...) call as evidence of
reachability, including an append to an unrelated throwaway local the helper
never reads again. Every real diagnostic-list sink in vera/ has the
self.<attr>.append(...) shape, so the check is now scoped to that.

Fixes aallan#956

Co-Authored-By: Claude <noreply@anthropic.invalid>
@chethanuk
chethanuk force-pushed the fix/issue-956-plumbing-skip-reachability branch from f1267f4 to 078b1d8 Compare July 9, 2026 05:25
aallan added 3 commits July 9, 2026 18:25
…(PR aallan#964 review)

A value-carrying annotated assignment (d: object = self.template)
rebinds the local without being an ast.Assign, so a bogus ctor
followed by an AnnAssign rebind was still elected as plumbing and
skipped by all three passes (probe-confirmed EXEMPT). Dually,
d: Diagnostic = Diagnostic(...) was never recognised as the initial
binding, permanently inspecting genuine plumbing. Both arms fixed:
_rebound_names counts value-carrying AnnAssign targets (a bare
annotation does not assign and is not counted), and the binding
recognition accepts an AnnAssign whose value is the ctor.

Six new fixtures: the two AnnAssign directions (RED-confirmed pre-fix,
each arm mutation-killed), the bare-annotation distinction, and one
pin each for the with/walrus/except rebind arms so a dropped arm ships
RED (PR aallan#965 review ask, pinned here where the machinery lives).
Exempt set on vera/ unchanged (the same five plumbing ctors).
TESTING.md/ROADMAP counts follow (6,827 -> 6,833).
…s, match captures, params, nonlocal (PR aallan#964 panel review)

The enumeration approach was fail-OPEN for every binding form it did
not list: starred unpacks (a, *d = ...), import ... as d, match-case
captures, a parameter shadowed by the ctor's name, a nonlocal
declaration rebinding the helper's local from a nested function, and
a return that precedes the binding (order-insensitive name match).
Each shape kept a swapped-out bogus ctor exempt (six RED fixtures
confirmed pre-fix).

_rebound_names now counts bindings generically: any Store/Del-context
Name in own scope (subsuming the old enumeration), the string-carrying
forms (import-as, except-as, match captures), the helper's parameters,
and nonlocal declarations across nested scopes; a bare annotation
(no value) is still not a rebind. The return/append name-match gains
an order guard (at-or-after the binding line). Params/nonlocal/order/
Store arms each mutation-killed; exempt set on vera/ unchanged.
TESTING.md/ROADMAP counts follow (6,833 -> 6,839).
# Conflicts:
#	CHANGELOG.md
#	ROADMAP.md
#	TESTING.md
@aallan
aallan merged commit a835975 into aallan:main Jul 9, 2026
27 checks passed
@aallan

aallan commented Jul 9, 2026

Copy link
Copy Markdown
Owner

Merged — thanks @chethanuk, and this one deserves the long comment: the reachability predicate is a genuinely good idea, your revision loop was fast (the rebinding conservatism and the narrowed self.<attr>.append sink both landed within hours of the review comments), and correcting #956's own acceptance criteria — the (0, 1, 1) target — showed you understood the three passes better than the issue did.

What the maintainer loop added on top, and the design reasoning:

  • AnnAssign both ways (d: object = self.template as a rebind; d: Diagnostic = Diagnostic(...) as the initial binding) — probe-confirmed live escape before the fix, both arms mutation-killed.
  • Generic binding-site counting replaced the statement-form enumeration. The adversarial pass found the enumeration fail-open for starred unpacks, import ... as, match captures, parameter shadowing, nonlocal from nested functions, and an order-insensitive name match — each pinned by a RED-first fixture. The design lesson we took: for a gate, a blacklist of binding forms inverts the failure direction — anything unlisted silently exempts. Counting every Store-context name (plus the string-carrying forms) makes the unknown-form failure mode fail closed instead. That principle — gates fail closed — is the same one behind your own fix(tooling): narrow diagnostic plumbing-skip to enclosed ctor #952/Diagnostic-fields gate: the plumbing-skip never checks its own premise (a delegating helper's hardcoded fields go unvalidated) #956 pair, taken one level further.
  • The usual lockstep: TESTING.md's gate row and counts, the CHANGELOG bullet aligned to the shipped generic approach.

The exempt set on vera/ is unchanged through all of it — every hardening was defensive. #965 rebases onto this cleanly (it inherits the narrow _is_append_call and all fixtures); that's next in the queue.

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.

Diagnostic-fields gate: the plumbing-skip never checks its own premise (a delegating helper's hardcoded fields go unvalidated)

2 participants