Scanner Language Analyzer Contracts (Identity / Evidence / Container Layout)

This document freezes the cross-analyzer contracts that are shared by the language analyzers (Java, .NET, Python, Node, Bun). These rules exist to prevent false matches, keep outputs deterministic, and protect against host-path leakage.

1) Identity Safety Contract (PURL vs Explicit Key)

1.1 Goals

1.2 When to emit a concrete PURL

Emit a concrete (versioned) PURL only when both are true:

  1. The analyzer can determine a concrete version (ecosystem-specific) for the component.
  2. The version is backed by replayable evidence (e.g., installed artifact metadata or lockfile-resolved entry).

Typical sources that qualify:

1.3 When to emit an explicit-key component (required)

Emit an explicit-key component when the dependency is declared-only or otherwise non-concrete:

Rule: If the analyzer cannot prove a concrete version from local evidence, it must not emit a versioned PURL for that dependency.

1.4 Explicit-key format (canonical)

For declared-only / non-concrete identities, analyzers must emit:

Where <digest> is sha256 of the canonical UTF-8 string:

<ecosystem>\n<normalizedName>\n<normalizedSpec>\n<originLocator>

Canonicalization rules:

1.5 Required metadata for explicit-key components

Explicit-key components must include (at minimum) these metadata keys:

2) Evidence Locator Contract

2.1 General rules

2.2 Locator formats (canonical)

File evidence

Lockfile entry evidence

Nested artifact evidence

Derived evidence

2.3 Hashing rules (baseline)

3) Container Layout Discovery Contract

3.1 Layer root candidates

Language analyzers that support container-root discovery must treat these as candidate roots under the analysis root:

Each candidate root is scanned independently for projects.

3.2 Bounds and traversal safety (required)

3.3 Overlay/whiteout semantics

4) First-Class Detector Output Contract

Language analyzers that emit detector evidence must use shared Scanner contracts rather than analyzer-local metadata parsing:

4.1 Required detector fields

Every detector record must carry enough data for SARIF, policy, UI, and audit consumers to operate without parsing analyzer-specific payloads:

Records may omit fields that are genuinely unknown, but they must not substitute absolute host paths, random IDs, or wall-clock values.

4.2 SARIF and policy bridge

StellaOps.Scanner.Sarif.DetectorFindingSarifMapper maps shared detector records into SARIF FindingInput values:

ScanAnalysisCompositionBuilder.BuildDetectorPolicyFindings consumes the same shared analysis keys and emits policy-facing detector findings. This prevents policy/SBOM emitters from parsing analyzer-specific observation JSON.

4.3 Workspace source-pattern extraction

StellaOps.Scanner.Analyzers.Lang.LanguageDetectorFindingExtractor normalizes analyzer results into shared detector records and also performs bounded source-pattern extraction for first-class language detector coverage:

UI-facing policy/evidence linksets may carry a detector reference with rule id, kind, fingerprint, evidence locator, policy predicate, language, and analyzer id. The UI must treat that reference as an opaque shared detector contract and must not parse analyzer-specific payloads.

Compliance

Sprints docs/implplan/SPRINT_0403_0001_0001_scanner_java_detection_gaps.md through docs/implplan/SPRINT_0407_0001_0001_scanner_bun_detection_gaps.md (and the program sprint docs/implplan/SPRINT_0408_0001_0001_scanner_language_detection_gaps_program.md) carry the historical per-analyzer implementation and test evidence required to enforce this contract. The 2026-05-06 first-class detector correction and uplift sprints extend the active contract with shared detector records, fingerprints, bounded all-language source-pattern extraction, SARIF mapping, policy bridge coverage, and UI detector references.