Classification
- Change type: enhancement / foundational contract
- Priority: P0
- Expected value: very high — prerequisite for lifecycle tracking, evidence aggregation and honest calibration
- Implementation risk: medium — schema compatibility and identity drift
Canonical ownership
Canonical implementation owner: PhysShell/OwnAudit.
Own.NET/audit/aggregate/normalize.py is the deprecated reference implementation, used only as the parity source for the normalization-port slice. No new identity, schema, aggregation, lineage or provenance implementation lands there.
This supersedes the original "Canonical owner: Own.NET/audit/" line, which predates the 2026-07-18 canonicality reversal recorded in OwnAudit/AGENTS.md and docs/collector-plan.md: Own.NET stays a pure SAST engine that emits findings; aggregation, reporting, runtime and fix work live in OwnAudit.
Downstream consumers must consume exported fields or the versioned contract; they must not reverse-engineer either repository's private implementation.
Execution plan
| Slice |
Where |
What |
| 0 |
OwnAudit |
Freeze finding-pattern/v1 — one implementation, canonical vectors, legacy alias. ✅ OwnAudit#55 |
| 1A |
OwnAudit |
Port the normalized-finding pipeline with a parity fixture. No identity/provenance fields, no taxonomy/suppression/verdict changes. |
| 1B |
OwnAudit |
schema_version, pattern_id, occurrence_id (only when provable), provenance with explicit null/unknown, identity limitations. |
| 2 |
OwnAudit |
Lineage mapping model — evidence, confidence classes, rename/diff support, unresolved and one-to-many. |
| 3 |
007 / OwnAudit |
Downstream adoption: action evidence, fix-proof.json. |
Slice 1B uses the local identity/pattern.py directly — schema and domain identity now live in one canonical repository, so there is no vendored contract copy and no cross-repo equality test to keep honest.
Problem
The current FP-judge finding_id is intentionally line-independent and pattern-level:
sha1(path + rule + message)[:16]
That is correct for deduplicating repeated instances before an expensive judge call, but it is not enough for lifecycle tracking. One pattern ID can represent several physical findings in the same file. If one occurrence is fixed and another remains, a single identity cannot represent both outcomes honestly.
The audit pipeline needs three distinct identities:
pattern_id — the existing line-independent logical pattern; backward-compatible with today's finding_id.
occurrence_id — one concrete physical finding emitted by one producer run.
lineage_id — the best-supported cross-commit identity for following an occurrence through line drift, rename or movement.
Required contract
pattern_id
- Preserve the current FP-judge recipe and semantics.
- Keep accepting legacy overlays keyed by
finding_id during a documented compatibility window.
- Make the intentional many-occurrences-to-one-pattern relationship explicit.
Frozen in slice 0 as finding-pattern/v1 (identity/pattern.py, contracts/finding-pattern-v1.json).
occurrence_id
Derive from stable run-local inputs, including at minimum:
producer_run_id
producer/tool identity
rule
path
physical anchor
Requirements:
- two identical findings at different physical sites receive distinct IDs;
- rerendering the same normalized run is deterministic;
- presentation-only ordering does not change the ID;
- occurrence identity never pretends to survive an unrelated producer run;
- never synthesized from unknowns. If
producer_run_id is unavailable, emit "occurrence_id": null plus an explicit identity_limitations entry (e.g. occurrence-id-unavailable:producer-run-id). Hashing the literal string "unknown" would give two unrelated sessions one "stable" id — a tidily formatted lie, and those are the most durable kind.
- physical anchor for the first slice is
path + startLine + startColumn (column when available). Never the result's ordinal position in the array.
lineage_id
- Link occurrences across commits only when supported by explicit mapping evidence.
- Prefer symbol/context/AST fingerprints plus Git diff/rename mapping over raw line numbers.
- Represent ambiguity as unresolved or one-to-many evidence; never guess a single lineage merely to keep dashboards tidy.
- Store the mapping evidence and confidence class separately from the ID.
Provenance fields
Each occurrence must carry enough provenance to reproduce or reject it:
producer_run_id
producer_name
producer_version
input_digest
config_digest
source_commit
schema_version
Fields may be nullable, but must be present: null means unknown; a missing field is a schema violation. Unknown/missing provenance stays visible as a coverage limitation, never silently synthesized.
Compatibility and migration
- Version the normalized finding schema.
- Existing
findings.json and fp-verdicts.json fixtures remain readable.
- Existing
finding_id consumers either map it to pattern_id or fail with an actionable version error.
- No existing diagnostic code, severity, message or analyzer verdict changes in this issue.
SARIF export
New fingerprints are added alongside the existing key, never in place of it:
ownAudit/v1 is a legacy GitHub-correlation key, not finding-pattern/v1: it normalizes the message, keeps the full digest and appends a /ordinal suffix for repeats. The ordinal alone disqualifies it as identity — presentation order must never change an id. Replacing it would reset every code-scanning alert's history while claiming to improve correlation.
Required controls
- Two identical messages/rules in one file on different lines:
- same
pattern_id;
- different
occurrence_id.
- Pure line drift between commits:
- new occurrence;
- same supported lineage.
- One repeated occurrence fixed, another retained:
- lineage outcomes remain independent.
- File rename with unchanged symbol/context:
- lineage may continue with rename evidence.
- Ambiguous copy/move:
- lineage remains unresolved or explicitly branched, never guessed.
- Producer/config/input digest changes:
- provenance changes are observable.
- Legacy FP overlay:
- still expands one pattern verdict to all matching current occurrences only when the input digest contract is valid.
Acceptance
Non-goals
- No Evidence Ledger implementation beyond the identity/provenance seam.
- No ranking, probability or calibration model.
- No auto-suppression or merge blocking.
- No analyzer precision/soundness changes.
No forced lift-out from Own.NET/audit/ to OwnAudit. Cancelled by the 2026-07-18 canonicality reversal: the lift-out already happened and OwnAudit is the canonical audit repository. Retained here struck through rather than deleted, so the change of direction stays visible to anyone reading the issue's history.
Classification
Canonical ownership
Canonical implementation owner: PhysShell/OwnAudit.
Own.NET/audit/aggregate/normalize.pyis the deprecated reference implementation, used only as the parity source for the normalization-port slice. No new identity, schema, aggregation, lineage or provenance implementation lands there.This supersedes the original "Canonical owner:
Own.NET/audit/" line, which predates the 2026-07-18 canonicality reversal recorded inOwnAudit/AGENTS.mdanddocs/collector-plan.md: Own.NET stays a pure SAST engine that emits findings; aggregation, reporting, runtime and fix work live in OwnAudit.Downstream consumers must consume exported fields or the versioned contract; they must not reverse-engineer either repository's private implementation.
Execution plan
finding-pattern/v1— one implementation, canonical vectors, legacy alias. ✅ OwnAudit#55schema_version,pattern_id,occurrence_id(only when provable), provenance with explicit null/unknown, identity limitations.fix-proof.json.Slice 1B uses the local
identity/pattern.pydirectly — schema and domain identity now live in one canonical repository, so there is no vendored contract copy and no cross-repo equality test to keep honest.Problem
The current FP-judge
finding_idis intentionally line-independent and pattern-level:That is correct for deduplicating repeated instances before an expensive judge call, but it is not enough for lifecycle tracking. One pattern ID can represent several physical findings in the same file. If one occurrence is fixed and another remains, a single identity cannot represent both outcomes honestly.
The audit pipeline needs three distinct identities:
pattern_id— the existing line-independent logical pattern; backward-compatible with today'sfinding_id.occurrence_id— one concrete physical finding emitted by one producer run.lineage_id— the best-supported cross-commit identity for following an occurrence through line drift, rename or movement.Required contract
pattern_idfinding_idduring a documented compatibility window.Frozen in slice 0 as
finding-pattern/v1(identity/pattern.py,contracts/finding-pattern-v1.json).occurrence_idDerive from stable run-local inputs, including at minimum:
Requirements:
producer_run_idis unavailable, emit"occurrence_id": nullplus an explicitidentity_limitationsentry (e.g.occurrence-id-unavailable:producer-run-id). Hashing the literal string"unknown"would give two unrelated sessions one "stable" id — a tidily formatted lie, and those are the most durable kind.path + startLine + startColumn(column when available). Never the result's ordinal position in the array.lineage_idProvenance fields
Each occurrence must carry enough provenance to reproduce or reject it:
Fields may be nullable, but must be present:
nullmeans unknown; a missing field is a schema violation. Unknown/missing provenance stays visible as a coverage limitation, never silently synthesized.Compatibility and migration
findings.jsonandfp-verdicts.jsonfixtures remain readable.finding_idconsumers either map it topattern_idor fail with an actionable version error.SARIF export
New fingerprints are added alongside the existing key, never in place of it:
ownAudit/v1is a legacy GitHub-correlation key, notfinding-pattern/v1: it normalizes the message, keeps the full digest and appends a/ordinalsuffix for repeats. The ordinal alone disqualifies it as identity — presentation order must never change an id. Replacing it would reset every code-scanning alert's history while claiming to improve correlation.Required controls
pattern_id;occurrence_id.Acceptance
finding-pattern/v1pattern_id,occurrence_idandlineage_idhave distinct tested semantics.finding_idcompatibility is explicit and tested — slice 0, byte-identical alias pinned per vectorNon-goals
No forced lift-out fromCancelled by the 2026-07-18 canonicality reversal: the lift-out already happened and OwnAudit is the canonical audit repository. Retained here struck through rather than deleted, so the change of direction stays visible to anyone reading the issue's history.Own.NET/audit/to OwnAudit.