feat(0.2): suppression model — .terrain/suppressions.yaml (Track 4.5)#139
Open
pmclSF wants to merge 1 commit intofeat/0.2-finding-idsfrom
Open
feat(0.2): suppression model — .terrain/suppressions.yaml (Track 4.5)#139pmclSF wants to merge 1 commit intofeat/0.2-finding-idsfrom
pmclSF wants to merge 1 commit intofeat/0.2-finding-idsfrom
Conversation
Brings forward what the prior plan deferred to 0.3: a working
suppression model in 0.2.0 so adopters can adopt strict CI gating
without forking the project. Builds on Track 4.4 (stable finding
IDs) — most suppressions match by FindingID; a (signal_type, file
glob) fallback covers class-wide waivers.
Schema:
schema_version: "1"
suppressions:
- finding_id: weakAssertion@internal/auth/login.go:TestLogin#a1b2c3d4
reason: false positive; sanitized upstream
expires: 2026-08-01
owner: "@platform"
- signal_type: aiPromptInjectionRisk
file: internal/legacy/**
reason: rewriting in 0.3
expires: 2026-09-01
Match modes (an entry uses exactly one):
* `finding_id` exact match — most precise; survives line drift
when the underlying signal has a stable symbol per
BuildFindingID semantics
* `signal_type` + `file` glob — coarser; supports `**`-style
recursive patterns. Useful for class-wide waivers.
Anti-goal: suppressions are NOT a free-form ignore-everything
switch. The schema rejects entries that satisfy neither mode and
entries missing `reason` (every suppression must justify itself).
Lifecycle:
* `reason` required — printed when a suppressed signal would
otherwise have been blocking, so reviewers see the rationale
in PR comments without opening the YAML.
* `expires` optional ISO 8601 date. After the date, the
suppression is INVALID — the underlying signal fires again,
and a new `suppressionExpired` warning signal surfaces in
the report so silent rot doesn't accumulate.
* `owner` optional free-text owner pointer for review.
Engine wiring:
* `internal/suppression/` package — Load + Apply + path-glob
helpers. 9 unit tests covering load validation, expiry,
finding-id match, signal-type+glob match, idempotency, nil-
safety.
* `internal/engine/pipeline.go` — Step 10c after FindingID
assignment: load `.terrain/suppressions.yaml` (or
PipelineOptions.SuppressionsPath override), apply matched
entries, surface expired entries as warning signals.
* `PipelineOptions.SuppressionsPath` for `terrain analyze
--suppressions <path>`.
* 5 engine integration tests: drops matching signal, expired
emits warning + lets signal fire, missing file is a no-op,
malformed file logs and continues (don't fail pipeline on a
fat-fingered YAML edit), override path honored.
Manifest:
* New `suppressionExpired` signal type, governance category,
medium severity, evidence-strong (it's a deterministic check).
Registered in `internal/signals/manifest.go` and
`internal/models/signal_catalog.go`. Rule doc auto-generated
via cmd/terrain-docs-gen.
* No new detector — pipeline emits the signal directly.
What's NOT in this PR (follow-ups):
* Track 4.6: `terrain explain finding <id>` — round-trips an ID
back to the underlying signal + suggests a suppression command
* Track 4.7: `terrain suppress <id>` — writes a suppression
entry to the YAML with goccy/go-yaml round-trip preservation
* Track 4.8: `--new-findings-only --baseline <path>` — uses the
same FindingID set to filter signals against a baseline
Verification:
go test ./internal/suppression/ — 9 tests green
go test ./internal/engine/ — 5 new integration tests green
go test ./... — full suite green
make docs-verify — manifest + severity rubric + rule docs in sync
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 tasks
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.
Summary
Track 4.5 from the 0.2.0 parity-gated release plan. Brings forward what the prior plan deferred to 0.3: a working suppression model in 0.2.0 so adopters can adopt strict CI gating without forking the project. Builds on Track 4.4 (stable finding IDs).
Base branch: `feat/0.2-finding-ids` (PR #138). When that merges to main, this PR rebases automatically.
Schema
```yaml
schema_version: "1"
suppressions:
finding_id: weakAssertion@internal/auth/login.go:TestLogin#a1b2c3d4
reason: false positive; sanitized upstream
expires: 2026-08-01
owner: "@platform"
signal_type: aiPromptInjectionRisk
file: internal/legacy/**
reason: rewriting in 0.3
expires: 2026-09-01
```
Match modes
Each entry uses exactly one of two:
Lifecycle
What landed
What's NOT in this PR (follow-ups)
Test plan
Plan link
`/Users/pzachary/.claude/plans/kind-mapping-turing.md` (Track 4.5).
🤖 Generated with Claude Code