Open
Conversation
Foundational deliverable for the Gate-pillar parity lift. Stable IDs
unblock three downstream features: suppressions
(`.terrain/suppressions.yaml` honoring per-finding entries),
`terrain explain finding <id>` round-trip, and
`--new-findings-only --baseline <path>` baseline-aware gating.
Format:
{detector}@{normalized_path}:{anchor}#{hash}
Example: weakAssertion@internal/auth/login_test.go:TestLogin#a1b2c3d4
Where:
detector = signal type (e.g. "weakAssertion")
normalized_path = forward-slash, repo-relative
anchor = symbol when present, "L<line>" otherwise, "_" when neither
hash = 8 hex chars derived from canonical form
Stability guarantees (documented in the FindingID field doc-comment
and the package-level comment in `internal/identity/finding_id.go`):
* Same (Type, Location.File, Location.Symbol, Location.Line) →
same FindingID across runs. Used for suppression matching and
baseline-aware gating.
* Symbol takes precedence as the anchor when present, so line
drift WITHIN a symbol does not change the ID. This is the
common case (whitespace edits, import reordering).
* File rename or symbol rename produces a new ID. The underlying
finding has moved; old suppressions don't apply.
* Line drift WITHOUT a symbol changes the ID — known limitation
documented; AST-anchored 0.3 work removes it.
What landed:
internal/identity/finding_id.go (new, ~145 lines)
BuildFindingID / ParseFindingID / MatchFindingID + helpers.
Reuses GenerateID + NormalizePath from the existing identity
package.
internal/identity/finding_id_test.go (new, ~200 lines)
16 table-driven tests covering: stability, shape, distinct on
rename/file-move/detector-change, path normalization (back- vs
forward-slash), line anchor when no symbol, placeholder when
nothing, symbol-precedence-over-line invariant, line drift
changes ID without symbol, parser round-trip + malformed
rejection (8 cases), anchor-with-colons round-trip, MatchFindingID
semantics.
internal/models/signal.go
New `FindingID string `json:"findingId,omitempty"` field on
models.Signal with the stability documentation inline. omitempty
so older snapshots remain valid; pre-existing JSON consumers
that don't read the field are unaffected.
internal/engine/finding_ids.go (new)
assignFindingIDs(snapshot) — walks top-level Signals + per-file
Signals, populates FindingID for any signal that doesn't already
have one. Pre-set IDs are preserved (lets specialized detectors
like detectorPanic emit their own anchor). Idempotent and
nil-safe.
internal/engine/pipeline.go
Calls assignFindingIDs(snapshot) right after SortSnapshot in
Step 10 of RunPipelineContext. Sort runs first so IDs land in
canonical order; this preserves byte-identical SOURCE_DATE_EPOCH
output.
internal/engine/finding_ids_test.go (new)
4 tests: top-level + per-file population, pre-set ID preserved,
idempotency, nil-safe.
Why the model package isn't importing identity directly: keeps
`internal/models/` dependency-free (zero internal imports today).
Engine orchestrates detectors and is the natural place for
ID assignment; the dependency arrow runs engine → identity, which
matches the layering elsewhere.
Verification:
go test ./internal/identity/ ./internal/engine/ — green
go test ./... — green
go test ./internal/testdata/ — green (goldens unchanged because
FindingID is omitempty and the existing goldens don't assert
its presence; 0.2.1 work updates goldens to include IDs)
Next:
Track 4.5 — `.terrain/suppressions.yaml` minimal viable shape;
suppressions match against FindingID
Track 4.6 — `terrain explain finding <id>` lookup
Track 4.7 — `terrain suppress <id>` writer
Track 4.8 — `--new-findings-only --baseline <path>`
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Terrain AI Risk Review
Decision: PASS — AI surfaces are covered. |
[RISK] Terrain — Merge with caution
Coverage gaps in changed code
4 pre-existing issues on changed files
Recommended tests57 test(s) with exact coverage of 20 impacted unit(s). 3 impacted unit(s) have no covering tests in the selected set.
Owners: PMCLSF Limitations
Generated by Terrain · Targeted Test ResultsTerrain selected 57 test(s) instead of the full suite.
|
This was referenced May 2, 2026
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.4 from the 0.2.0 parity-gated release plan. Foundational deliverable that unblocks three downstream features:
Format
```
{detector}@{normalized_path}:{anchor}#{hash}
```
Example: `weakAssertion@internal/auth/login_test.go:TestLogin#a1b2c3d4`
Components:
Human-readable enough to mention in PR comments; unique enough that two findings of the same type on the same line (different symbols) get distinct IDs.
Stability guarantees
What landed
Why models doesn't import identity
`internal/models/` has zero internal imports today. Keeping it dependency-free is valuable for serialization. The engine layer is the natural place for ID assignment — engine → identity matches the layering elsewhere.
Test plan
Plan link
`/Users/pzachary/.claude/plans/kind-mapping-turing.md` (Track 4.4).
🤖 Generated with Claude Code