You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit e33c0a7
Browse filesBrowse the repository at this point in the historyBrowse files
feat(suppression): implement [OwnIgnore("reason")] end to end (P-004)
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
if echo "$out" | grep -qE "(ScopeUsingService|ClockCachingService)"; then
724
725
echo "FAIL: a correct scope use (used-in-scope, or a cached singleton) was wrongly flagged DI005"; exit 1
725
726
fi
726
-
echo "OK: real C# -> facts -> OWN001 (subscription + timer + field + Subscribe + pool + local) + OWN014 (static-event region escape) + DI001 (captive dependency) + DI002 (scoped captured weakly) + DI003 (transient IDisposable captured by a singleton) + DI004 (transient IDisposable service-located from the root provider) + DI005 (scoped service cached from a created scope) at the C# location"
727
+
# issue #209 — inline [OwnIgnore("reason")] per-site suppression (P-004), on an
728
+
# IDisposable field. Four contrasting shapes in OwnIgnoreSample.cs: the un-annotated
729
+
# leak, a reason-less [OwnIgnore], and an empty [OwnIgnore("")] all FIRE OWN001; only
730
+
# [OwnIgnore("reason")] is silent-but-COUNTED (SARIF suppressions), never failing the run.
echo "OK: real C# -> facts -> OWN001 (subscription + timer + field + Subscribe + pool + local) + OWN014 (static-event region escape) + DI001 (captive dependency) + DI002 (scoped captured weakly) + DI003 (transient IDisposable captured by a singleton) + DI004 (transient IDisposable service-located from the root provider) + DI005 (scoped service cached from a created scope) + [OwnIgnore] suppression (silent-but-counted, SARIF suppressions) at the C# location"
727
756
- name: Flow-sensitive local IDisposables (--flow-locals, P-016 B0b/B2)
728
757
run: |
729
758
# Path-sensitive flow analysis of local IDisposables — bugs the flat D1
Copy file name to clipboardExpand all lines: docs/suppression-and-fp-policy.md
+20-14Lines changed: 20 additions & 14 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -45,30 +45,36 @@ fire on unprovable input.
45
45
|---|---|---|
46
46
|`--severity warning`|**works today** (P-013) | Global: downgrades every error-tier finding for that run to advisory. Per-run, not per-finding — an escape hatch for "show me everything, but don't fail the build yet," not a way to silence one specific site. |
47
47
|`--fail-on-finding` set to off |**works today** (P-013) | Global: findings still print/annotate, but the process/step exit code stays 0. The CLI (`own-check.sh`) is off by default — you must pass the flag to make findings fail the shell. The GitHub Action inverts that for safety: its `fail-on-finding` input defaults to `"true"` (fails the step on a finding), so to get the "annotate but don't fail" behavior in CI you must explicitly set `fail-on-finding: "false"`. |
48
-
|`[OwnIgnore("reason")]`|**designed, not implemented** (P-004) | Inline, per-site suppression attribute — the intended fine-grained escape hatch for a specific subscription/field the checker can't see enough context to clear. Referenced across P-001/P-004/P-010/P-014/P-017 as the standing design; there is no code behind it yet. If you need this today, the honest answer is: you don't have it — file the case so it informs the implementation. |
48
+
|`[OwnIgnore("reason")]`|**works today**on `IDisposable` fields (P-004, #209) | Inline, per-site suppression attribute — the fine-grained escape hatch for a specific site the checker can't see enough context to clear. Put `[OwnIgnore("reason")]` on the field; the finding is then **silent-but-counted** — kept out of the exit code and the human findings stream, but tallied in the run summary and carried in SARIF `suppressions` (`kind: "inSource"`, your reason as the `justification`) so nothing is lost and a consumer can audit it. The **reason is mandatory**: a reason-less `[OwnIgnore]` (or an empty `[OwnIgnore("")]`) does **not** suppress — a suppression is a documented decision, never a silent accept. The attribute is matched by simple name, so you can declare your own `OwnIgnoreAttribute`. Currently reads on `IDisposable`**field** declarations (the clearest attribute site); other sites (subscriptions, timers) are follow-up increments. |
49
49
| Project-wide config (`.ownrc`/`own.toml`) |**draft, not implemented** (P-015) | Per-check-category enable/disable + severity + per-path overrides (e.g. relax a category under `tests/`). Stub status — format (TOML vs INI vs JSON) and enforcement point are still open questions in the proposal. |
50
50
|`corpus/oracle-fp-baseline.txt`|**exists, but not a user-facing suppression tool**| An allowlist the *oracle comparator* (`scripts/oracle_compare.py`, a dev/maintainer tool) uses to keep already-triaged false positives out of the `own-only` bucket on re-runs. It doesn't change what `own-check`/the Action reports — it only keeps the oracle's own triage queue from re-showing confirmed noise. |
51
51
52
-
So today, honestly: there is no way to suppress **one specific finding** in
53
-
your own repo. The two escape hatches for that (`[OwnIgnore]`, project config)
54
-
are designed and drafted respectively, not shipped. What you have is a global
55
-
severity dial and the extractor's own honest-skip behavior, which is why the
56
-
precision bar above matters as much as the (currently thin) suppression
57
-
surface — the fewer false positives reach you, the less suppression UX has to
58
-
carry.
52
+
So today: you **can** suppress one specific finding with an inline
53
+
`[OwnIgnore("reason")]` on the field it fires on (shipped, #209) — the finding
54
+
goes silent but stays counted (summary tally + SARIF `suppressions`). The
55
+
project-wide counterpart (`.ownrc`/`own.toml`, P-015) is drafted, not shipped.
56
+
Together with the global severity dial and the extractor's own honest-skip
57
+
behavior, that covers per-site and per-run; the per-*category*, per-*path*
58
+
config is the remaining gap. The precision bar above still matters as much as
59
+
the suppression surface — the fewer false positives reach you, the less
60
+
suppression UX has to carry.
59
61
60
-
## The designed shape (so you know what's coming)
62
+
## The full shape (`[OwnIgnore]` shipped; config still to come)
61
63
62
-
Precedence, once both land (P-015's draft order):
64
+
Precedence (P-015's draft order — the inline attribute half is shipped, #209;
0 commit comments