Skip to content

feat(suppression): implement [OwnIgnore("reason")] end to end (P-004) — Closes #209#215

Merged
PhysShell merged 3 commits into
mainfrom
claude/own-net-issue-209-ownignore
Jul 11, 2026
Merged

feat(suppression): implement [OwnIgnore("reason")] end to end (P-004) — Closes #209#215
PhysShell merged 3 commits into
mainfrom
claude/own-net-issue-209-ownignore

Conversation

@PhysShell

@PhysShell PhysShell commented Jul 10, 2026

Copy link
Copy Markdown
Owner

Что и зачем

Реализует per-site suppression [OwnIgnore("reason")], которую docs/suppression-and-fp-policy.md честно помечала как designed-not-implemented (P-004). [OwnIgnore("reason")] на IDisposable-поле теперь глушит его OWN001 — но по принципу visibility over silence: находка всё равно создаётся, убирается из exit-кода и человекочитаемого потока, но считается (tally в summary) и уносится в SARIF suppressions (kind: "inSource", reason как justification). Suppressed-находка никогда не валит прогон. Reason обязателен by design: [OwnIgnore] без причины и пустой [OwnIgnore("")] НЕ глушат — never a silent accept.

Consumed core-side (P-013 «one checker»): экстрактор эмитит факт с additive-optional маркером ignore_reason, а вердикт — за ядром. OWNIR_VERSION не бампается (additive optional, зеркалит source_provenance).

Тип изменения

  • feat — новая возможность
  • docs — документация (FP-policy → shipped; spec §4 + schema)

Как проверено

  • python tests/run_tests.py — exit 0 (в т.ч. test_ownir 261/261 с новыми пинами)
  • ruff check . и mypy (strict на ownlang/) — clean / Success (20 files)
  • локально через настоящий экстрактор + ядро (в env есть .NET 8):
UnsuppressedLeak / ReasonlessLeak / EmptyReasonLeak  -> OWN001 fires
SuppressedLeak ([OwnIgnore("reason")])               -> silent, "1 suppressed ([OwnIgnore])"
SARIF: 4 results, 1 c suppressions[{kind:inSource, justification:<reason>}]
только-suppressed facts  -> exit 0 (не валит прогон)
пустой reason            -> fires (exit 1);  ignore_reason:7 -> fail-loud на load (exit 2)

Связанные issue

Closes #209.

Что внутри

  • Экстрактор (Program.cs): OwnIgnoreReason() читает [OwnIgnore] (матч по simple-name — проект может завести свой атрибут) на объявлениях IDisposable-полей через constant-folding SemanticModel; стампит ignore_reason только для непустой причины.
  • Ядро: Finding.ignore_reason + свойство suppressed; check_facts стампит его; cmd_ownir исключает suppressed из exit-кода и печатает tally; _sarif_result эмитит suppressions; load() валидирует тип; dedup-ключ обновлён.
  • Сэмпл OwnIgnoreSample.cs: контраст unsuppressed / suppressed / reason-less / empty-reason, подключён в CI-джобу C# leak extractor + ассерт на SARIF suppressions.
  • Пины/доки: tests/test_ownir.py; spec/OwnIR.md §4 + spec/ownir.schema.json (additive optional, как source_provenance); docs/suppression-and-fp-policy.md → shipped (P-015 config оставлен draft).

Границы (follow-up, не в этом PR)

  • Экстрактор читает [OwnIgnore] пока на IDisposable-полях (самый чистый attribute-site — запись анкорится на поле). Подписки/таймеры/локали — следующие инкременты (локаль не может нести C#-атрибут в принципе). Ядро уже суппрессит любую запись с ignore_reason, так что расширение экстрактора тривиально.
  • Проектный конфиг (P-015) — вне скоупа, оставлен honestly draft.

Чеклист

  • изменение покрыто тестом/селфтестом (test_ownir.py + CI-ассерты, вкл. SARIF)
  • README/docs обновлены (FP-policy, spec §4, schema)
  • коммиты в conventional-commit стиле (feat:)

🤖 Generated with Claude Code


Generated by Claude Code

Summary by CodeRabbit

  • New Features
    • Added inline [OwnIgnore("reason")] suppression for disposable-field findings.
    • Suppressed findings no longer appear in human-readable results or affect exit status, but remain counted.
    • Suppression details are included in SARIF with an in-source justification.
  • Documentation
    • Updated suppression guidance, behavior, precedence, and SARIF output documentation.
  • Validation
    • Added coverage for valid, missing, empty, and invalid suppression reasons.

Ships the per-site suppression attribute the FP-policy page marked
"designed, not implemented". A `[OwnIgnore("reason")]` on an IDisposable field
now suppresses its OWN001 — but with VISIBILITY OVER SILENCE: the finding is
still minted, kept out of the exit code and the human findings stream, yet
COUNTED (a run-summary tally) and carried in SARIF `suppressions`
(kind "inSource", the reason as justification). A suppressed finding never
fails the run.

The reason is mandatory by design (a suppression is a documented decision):
a reason-less `[OwnIgnore]` or an empty `[OwnIgnore("")]` does NOT suppress —
never a silent accept.

Consumed core-side (P-013 "one checker"): the extractor emits the finding fact
with an additive-optional `ignore_reason` marker; the core is the sole authority
on the verdict. No OWNIR_VERSION bump (additive optional, mirrors
source_provenance).

- Extractor: OwnIgnoreReason() reads `[OwnIgnore]` (matched by simple name, so a
  project may declare its own attribute) on IDisposable field declarations via the
  SemanticModel's constant folding; stamps `ignore_reason` only for a non-empty reason.
- Core: Finding.ignore_reason + `suppressed` property; check_facts stamps it;
  cmd_ownir excludes suppressed from the exit code and prints a tally; SARIF emits
  the `suppressions` array; load() validates the field type; dedup key updated.
- Sample OwnIgnoreSample.cs: unsuppressed / suppressed / reason-less / empty-reason
  contrast, wired into the C# leak-extractor CI job with a SARIF suppressions assertion.
- Pinned in tests/test_ownir.py; documented in spec/OwnIR.md §4 + ownir.schema.json;
  docs/suppression-and-fp-policy.md flipped to shipped (P-015 config kept draft).

Locally validated (real extractor + core): suppressed leak silent-but-counted,
present in SARIF with inSource justification; reason-less/empty fire OWN001;
a suppressed-only run exits 0; non-string ignore_reason fails loud at load.
Gates: run_tests.py, ruff, mypy --strict on ownlang all green.

Closes #209

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cg6DVXahkyY68Ruu7f76rG
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@PhysShell, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 50 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e157c721-6a69-4873-9b9d-29d9839c62eb

📥 Commits

Reviewing files that changed from the base of the PR and between c06c99f and c0f52aa.

📒 Files selected for processing (1)
  • .github/workflows/ci.yml
📝 Walkthrough

Walkthrough

Adds end-to-end [OwnIgnore("reason")] suppression for disposable-field findings, including Roslyn extraction, OwnIR propagation, CLI filtering, SARIF metadata, tests, CI validation, and policy documentation.

Changes

Inline OwnIgnore suppression

Layer / File(s) Summary
OwnIR suppression contract and verdict flow
spec/OwnIR.md, spec/ownir.schema.json, ownlang/ownir.py, ownlang/__main__.py, docs/suppression-and-fp-policy.md
Adds optional ignore_reason handling, suppression-aware findings and CLI output, SARIF inSource suppressions, validation, and shipped-policy documentation.
Roslyn attribute extraction and samples
frontend/roslyn/OwnSharp.Extractor/Program.cs, frontend/roslyn/samples/OwnIgnoreSample.cs, .github/workflows/ci.yml
Extracts non-empty constant reasons from OwnIgnore attributes and emits them for disposable-field facts, with sample cases and CI extraction wiring.
Suppression behavior validation
tests/test_ownir.py, .github/workflows/ci.yml
Tests valid, missing, empty, and non-string reasons, plus human-output, summary, and SARIF behavior in unit and CI checks.

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

Sequence Diagram(s)

sequenceDiagram
  participant RoslynExtractor
  participant OwnIR
  participant CLI
  participant SARIF
  RoslynExtractor->>OwnIR: Emit disposable-field fact with ignore_reason
  OwnIR->>CLI: Create active or suppressed Finding
  CLI->>SARIF: Include suppressed finding
  SARIF-->>CLI: Store inSource justification
Loading

Possibly related PRs

  • PhysShell/Own.NET#10: Modifies the same ownlang/ownir.py and ownlang/__main__.py finding and CLI output areas.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: end-to-end [OwnIgnore("reason")] suppression for issue #209.
Description check ✅ Passed The PR description matches the template sections and includes scope, checks, linked issue, and checklist items.
Linked Issues check ✅ Passed The changes implement #209 end to end, including extractor, core/output handling, tests, samples, docs, and CI coverage.
Out of Scope Changes check ✅ Passed All changes appear directly related to [OwnIgnore] suppression; no unrelated scope creep is evident.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/own-net-issue-209-ownignore

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.

// CONTROL: a `new`'d IDisposable field never disposed -> OWN001 fires.
public sealed class UnsuppressedLeak
{
private readonly Handle _h = new Handle();
public sealed class SuppressedLeak
{
[OwnIgnore("owned and disposed by the DI container, not by this type")]
private readonly Handle _h = new Handle();
public sealed class ReasonlessLeak
{
[OwnIgnore]
private readonly Handle _h = new Handle();
public sealed class EmptyReasonLeak
{
[OwnIgnore("")]
private readonly Handle _h = new Handle();
@github-actions

Copy link
Copy Markdown

@coderabbitai review

@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 @.github/workflows/ci.yml:
- Around line 747-754: Replace the blanket `|| true 2>/dev/null` on the `ownlang
ownir` SARIF generation with the established `rc`-capture pattern used elsewhere
in the workflow, such as around line 1228. Preserve the generated SARIF and
allow the expected nonzero result, but capture the command’s exit status and
print stderr or an explicit error when the status is 2 or greater before running
the existing jq validation.
🪄 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: CHILL

Plan: Pro Plus

Run ID: 78b21709-fd67-4bfd-a9ae-b8b4fa5109a0

📥 Commits

Reviewing files that changed from the base of the PR and between c029e8d and c06c99f.

📒 Files selected for processing (9)
  • .github/workflows/ci.yml
  • docs/suppression-and-fp-policy.md
  • frontend/roslyn/OwnSharp.Extractor/Program.cs
  • frontend/roslyn/samples/OwnIgnoreSample.cs
  • ownlang/__main__.py
  • ownlang/ownir.py
  • spec/OwnIR.md
  • spec/ownir.schema.json
  • tests/test_ownir.py

Comment thread .github/workflows/ci.yml Outdated
…abbit)

Blanket `|| true` + 2>/dev/null swallowed a real ownir error (rc >= 2):
the jq assertion would still fail the job, but the log showed only the
assertion text, not the Python error. Mirror the established rc-capture
pattern (same file, proj.sarif step): keep stderr, allow the expected
rc<=1, fail loud on rc>=2. Per the PR #211 learning.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsWw4Ay8KLTHFom1HvRu3U
@PhysShell
PhysShell merged commit 53c3299 into main Jul 11, 2026
37 checks passed
PhysShell pushed a commit that referenced this pull request Jul 11, 2026
Both sides appended to the same wpf-extractor job tail: main's #215
[OwnIgnore] block (sample, assertions, SARIF rc-capture step) and this
branch's #218 rotation block (sample, silent + three flagged controls).
Kept both, single merged OK line. Program.cs auto-merged (additive).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsWw4Ay8KLTHFom1HvRu3U
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.

Alpha follow-up: implement [OwnIgnore("reason")] suppression (P-004) end to end

3 participants