STIG Help

Canonical problem reporting, severities, and `failAt`

STIG uses problem as the canonical name for a severity-bearing record. An error is an exception or failed execution, while an issue is an external tracker item or ordinary prose. Model checks, source-code analysis, configuration checks, and toolchain validation all write the same typed problem schema.

Problem categories

ProblemCategory records who must resolve the problem, independently of the Gradle task that detected it:

Category

Meaning

SPECIFICATION

A defect or contradiction in requirements, safety modeling, or other specification content, such as a missing or inconsistent ASIL.

SOURCE_CODE

Undesirable analyzed user-code behavior, such as a cascading fault or missing expected source-to-sink flow.

CONFIGURATION

Missing, invalid, stale, unconfirmed, or conflicting user configuration, including transform reviews and known-gap declarations.

TOOLCHAIN

A STIG, analyzer, or pipeline-integrity problem, such as contradictory stages, corrupt output, failed communication, or exhausted resources.

A producer task is provenance, not a category. One task may report more than one category.

Severities

ProblemSeverity is ordered from most to least severe:

Severity

Intended use

SARIF level

CRITICAL

Output is unusable, contradictory, corrupt, or execution cannot reliably continue.

error

HIGH

A major specification, source-code, configuration, or recoverable tool problem requiring action.

error

MODERATE

A real degradation whose affected evidence remains usable with a clear caveat.

warning

LOW

A minor problem, or a current source-code gap explicitly assessed and declared in knownGaps.

warning

INFO

A noteworthy non-defect diagnostic.

note

Ordinary specification, source-code, and configuration defects are HIGH. CRITICAL is not a stronger label for an ordinary defect; it is reserved for evidence or execution that cannot be trusted or continued.

Canonical task output

Every problem-producing Gradle task owns exactly one canonical output:

<project>/build/stig/<producer-task>/problems.jsonl

A successful producer writes a valid empty file when it finds no problems. Each non-empty line is one StigProblemRecord with, at minimum:

  • schemaVersion;

  • projectPath and producerTask;

  • category, problemId, and subjectId;

  • severity, message, and optional details;

  • structured evidence and provenance.

Stable identity is:

projectPath + category + problemId + subjectId

problemId is the stable problem kind and subjectId is the affected entity, such as a fully qualified sink, transform-registry identity, or model element ID. Timestamps, rendered messages, stack traces, and output paths never participate in identity.

For model findings, the reviewed object's canonical Kotlin declaration ID is retained as evidence.entityId (entityName remains the rule API's identifier field). entityReference, entityDisplayName, entityPackageName, and entityModulePath retain its presentation and declaration context. subjectId may instead be a composite obligation, such as a requirement allocated to a child; it is not necessarily a model declaration or Dokka anchor. Messages use bracketed Kotlin references, while human names and remediation remain separate fields. SARIF preserves this metadata, adds the object's logical location, and URI-encodes physical source paths for IDE navigation. The findings-table JSON keeps plain text plus separate HTML-report, repository-source, and IDEA URLs; formatting belongs to the browser.

JSONL is the source of truth. Per-task SARIF and the root build/stig/stigAggregateProblemsSarif/stig-problems.sarif IDE aggregate are derived from canonical records. The aggregate depends directly on every registered problem producer, and stigGenerateHtml depends on the aggregate. Dokka/HTML also reads canonical records together with rule inventories and other domain artifacts; neither SARIF nor HTML is an aggregation input.

Model identity and labels

Keep an object reference, its properties, and its formatted text distinct. In Kotlin, a property can be stored or computed; dot notation does not imply a backing field. The read-only label properties compute presentation text, not another model object or an identifier to persist.

For this example, assume STIG has already bound req1: Requirement to the declaration ID p1.p2.req1, and its authored name is exactly Distonnect on overvoltage.

Expression

Meaning or result in this example

Kotlin type

req1

A reference to the actual requirement object; use the object in typed model relations.

Requirement — object reference

req1.referenceName

"req1" — the short declaration name populated by STIG, not a globally unique identifier.

String?

req1.id

"p1.p2.req1" — the computed, qualified declaration ID.

String

req1.name

"Distonnect on overvoltage" — the nullable, authored human name.

String?

req1.fullLabel

"[p1.p2.req1] Distonnect on overvoltage" — full identity label followed by the human name, when present.

String

req1.idLabel

"[p1.p2.req1]" — full identity label without the human name.

String

req1.shortIdLabel

"[req1]" — short declaration-name label, only where the declaring context is already clear.

String

The identity properties are defined in MetamodelEntity.kt. The labels are extension properties in highway/ModelLabels.kt under stig-core; import com.stigway.metamodels.highway.fullLabel, idLabel, or shortIdLabel as needed. They have getters only: no backing fields, setters, wrapper objects, or cached values. They do not add authored attributes or serialized fields to the metamodel.

referenceName is nullable until compiled-model wiring or source materialization binds the declaration. Reading id before binding fails; it does not return a made-up identifier. name and description are nullable authoring properties and never supply identity. Use the raw id for matching, navigation keys, and stored object identity. The ID is stable for a declaration, not an immutable UUID: moving or renaming the declaration can change it. A local alias of req1 refers to the same object and does not give it a new ID.

Formatting is separate from these values. Both idLabel and fullLabel present JVM nested-class $ separators as dots without modifying the raw id. shortIdLabel contains only the final declaration-name component of that ID. For a null or blank human name, fullLabel returns exactly idLabel, including its brackets and without a trailing space. Non-blank names are preserved exactly, including their capitalization and whitespace.

Choosing a label

Use the read-only properties consistently:

  • fullLabel for human-facing headings or reviewed-object labels that need both identity and name.

  • idLabel for references to objects inside rule messages. Human names are already separate report fields; omitting them here keeps messages concise and unambiguous when names contain several words or are changed.

  • shortIdLabel for compact, explicitly scoped presentation. Two modules or containers can both declare req1, so this short form must not be used for cross-module diagnostics, stored identity, or navigation lookup.

Keep raw properties in structured JSON and SARIF, and apply HTML or Markdown escaping at the rendering boundary; these getters supply plain text and no hyperlink markup. Labels are never source-code expressions to insert in a quick fix: preserve the raw entity ID for resolving its Kotlin declaration.

Reference-edit quick-fix titles use the same bracketed, fully qualified identity as rule messages, independently of the source expression's spelling. FixExpression.displayText formats referencedEntityId for the title; text and the raw ID remain unchanged for source resolution. Literal expressions such as 10.0f or ASIL.B retain their spelling. Relation lists separate individual labels with commas ([a], [b]), without another pair of square brackets. Missing-context errors follow the same identity convention.

The label getters accept model entities and the Element/InterfacePoint marker interfaces. An InterfacePointAnalysis keeps its own identity but uses its referenced interface point's human name in fullLabel. For generic presentation, a nullable receiver's fullLabel is "(no item)" when null, and a non-model receiver uses its class name. The identity-only properties require a non-null, bound model object and fail for unsupported values; they never substitute a human name for a missing identity. A non-null, unbound model also fails when reading fullLabel.

ModelLabelsTest covers these rendering and binding contracts, including unnamed values, mixed case, nested declarations, changing bindings, and duplicate short names. The rule tests exercise the same idLabel properties through compiled batch checking and IDEA source materialization.

Aggregation and contradictions

Root stigListProblems depends on root stigGenerateHtml, then reads the registered problems.jsonl outputs from the root and all participating subprojects. It does not recursively scan arbitrary JSON, SARIF, retained evidence, caches, or unrelated build output.

Compatible occurrences with one stable identity are deduplicated while producer-task names and compatible structured evidence are merged. If one identity has incompatible severities or semantic states, aggregation emits a TOOLCHAIN/CRITICAL contradiction containing the conflicting records and fails because the aggregate is untrustworthy. Malformed canonical records are handled the same way. Aggregation never silently chooses one contradictory variant.

Reporting and failAt

StigProblemReporter.report(problem) and reportAll(problems) collect HIGH and lower problems. They stop only when a reported problem reaches the configured threshold. fail(problem) and failAll(problems) accept only CRITICAL problems, record them, and stop unconditionally.

The threshold comparison is:

problem.severity.isAtLeast(failAt)

For example, failAt = HIGH gates CRITICAL and HIGH, while allowing MODERATE, LOW, and INFO. The normal DSL fallback is CRITICAL, so recoverable HIGH reports can be collected across many activities unless a stricter policy is chosen. reportAll records the complete collection before applying the threshold; repeated singular reports are not equivalent once the first one throws.

The element/group/default DSL resolves failAt once for producer tasks. Model-check viewpoint switches only choose which viewpoints run; they do not define separate severity thresholds:

failAt = ProblemSeverity.HIGH modelCheck { context { enabled = true } requirements { enabled = true } design { enabled = true } analysis { enabled = true } tests { enabled = false } }

Activity-local known gaps

Each activity declares only defective sinks that have already been assessed:

stig { element { knownGaps { cascadingFault("Fusion.lane_width") missingExpectedFlow("Fusion.event_data_qualifier") } } }

The argument is the stable logical sink field from the P5 *.endpoints.json sidecar (sinks[].field), not an LLVM symbol or diagram slug. P7 compares the declarations with the OR-merged logical-sink verdicts:

Current result

Declaration

Canonical problem

Gap present

Missing

SOURCE_CODE/HIGH

Gap present

Correct

SOURCE_CODE/LOW

Gap absent

Present and therefore stale

CONFIGURATION/HIGH

Sink missing, wrong analysis kind, or duplicate declaration

Invalid

CONFIGURATION/HIGH

No gap

Missing

no problem

Conflicting or duplicate generated verdicts are TOOLCHAIN/CRITICAL, because the current evidence cannot be trusted.

Listing project-wide problems

The task always writes the complete deduplicated aggregate before applying terminal filters or an optional failure threshold:

build/stig/stigListProblems/all-problems.json

All option values are converted once to ProblemSeverity or ProblemCategory enums. --severity is exact; --atLeast is a severity threshold and is mutually exclusive with --severity. Repeat --category or comma-separate values to select one or more exact categories:

./gradlew stigListProblems --severity=HIGH --category=SPECIFICATION ./gradlew stigListProblems --atLeast=HIGH --category=SOURCE_CODE,CONFIGURATION ./gradlew stigListProblems --atLeast=HIGH --category=CONFIGURATION --category=TOOLCHAIN ./gradlew stigListProblems --failAt=HIGH

Terminal filters affect only printed rows. --failAt is evaluated against the complete aggregate, writes and prints the aggregate first, and then fails if its threshold is reached. Without --failAt, ordinary listing is non-failing; malformed or contradictory input still fails unconditionally.

See also

23 October 2026