STIG Help

Local multi-repo development (stig, stig-project, phasar)

Three repositories cooperate to analyze an activity. Edit the consumer's gradle.properties to select both dependency sources together:

stigSource=local stigVersion=2.2.0

In local mode, the current STIG and PhASAR working trees are authoritative; no commit, push, pin bump, or automatic Git update is needed. STIG is required; PhASAR is required only when native analyzer setup is requested, including explicit setup, analysis, or archived-run replay. Model-only builds and reports do not need a PhASAR checkout. stigVersion is ignored without warning, including when absent or different from the checkout's version. To use a published artifact, change stigSource to published in this same file. Published mode is the default when the property is absent; it uses stigVersion and that artifact's PhASAR pin regardless of which sibling folders exist. SNAPSHOT suffixes do not choose a source mode. Invalid modes fail early.

The repos

For a fresh Fedora SSH server, use Bootstrap a Fedora analysis server to prepare encrypted storage and clone the three public repositories in the layout below.

Repo

What it is

Where it's consumed

Local-sibling override

stig

Kotlin monorepo plus stig-code-analyzer (C++ source under stig-code-analyzer/src/main/cpp; installer in stig-gradle-plugin)

stig-project (the model build)

Required in local — includeBuild(../stig)

phasar

The C++ data-flow analysis framework (our fork)

linked into the stig-code-analyzer native executable

Required for local native setup — next to the resolved stig checkout

"Next to" means siblings under one parent, e.g.:

<anywhere>/ ├── phasar/ ← C++ engine (required for local native setup) ├── stig/ ← Kotlin monorepo └── stig-project/ ← the activities/model build

They can live anywhere — only the sibling relationship matters (stig-project looks for ../stig; the analyzer build looks for ../phasar relative to the canonical STIG monorepo). A ../stig symlink is resolved first, so PhASAR must be beside its target. An incomplete or missing checkout fails when the local native producer is selected, before compilation, without a pinned fallback. The producer's checkout checks are configuration-cache inputs: removing PhASAR invalidates a previously cached native configuration even when an analyzer binary already exists.

The consumer's property file is not inherited by Gradle included builds. The plugin therefore selects the explicit :stig-code-analyzer:stigBuildLocalAnalyzer producer in local mode. STIG's standalone stigBuildAnalyzer reads STIG's own property file; its default builds the current analyzer sources against pinned PhASAR for artifact verification. Selecting the dedicated local producer never depends on that included build's default.

Keep the same relative layout when using linked Git worktrees. Report provenance and source links read shared Git configuration and branch refs through commondir, while HEAD identifies the checked-out worktree's own revision. A detached worktree has no inferred branch for source-edit links. The release commit hook checks the PhASAR sibling in its own Git context, including when either checkout is a linked worktree; Git variables inherited from the hook do not select the STIG checkout for that check.

What gets picked up from your working tree (no commit needed)

You edit…

Built from

Local gradle rebuilds on edit?

Kotlin in stig (engine, plugin, installer)

your working tree (composite build)

Yes

C++ analyzer (stig-code-analyzer/src/main/cpp)

your working tree

Yes (local producer input)

phasar (in local mode)

your working tree

Yes (local producer input)

scripts/pins.env: LLVM or build settings

selected toolchain

Yes

scripts/pins.env: PhASAR commit or repository

ignored for local source selection

No in local mode

Commit/push unchanged working-tree content

same content

No

Gradle's own up-to-date check is the rebuild trigger. stigBuildLocalPhasar owns the PhASAR working-tree inputs, native tests, and complete installed directory; stigBuildLocalAnalyzer consumes that output and owns the analyzer sources, tests, and executable. An analyzer-only source edit does not rerun the PhASAR producer. A PhASAR edit runs its producer and invalidates the analyzer if the installed inputs change. The analyzer build then hard-checks that its binary reports phasar=local. Before P6 launches that binary, the consumer checks the same identity again and refuses to run or replace it with a pinned analyzer. Each emitted analysis.json is also checked against the exact binary identity used for the run. No SHA dance, build timestamp, or manual log inspection is part of the correctness contract.

Cache inputs and fast regression tests

Both modes track the relevant source, build recipe, compiler, LLVM, library, and installed-output bytes. Published mode additionally selects and verifies the STIG version and immutable PhASAR pin; those identities never replace content or toolchain checks. Local mode ignores only the unused PhASAR selection fields (PHASAR_COMMIT and PHASAR_REPO_URL). LLVM and other settings in the pin file remain inputs. The actual Axion-derived STIG version remains an input in both modes because it is embedded in the analyzer binary; this is different from the ignored consumer stigVersion in local mode.

The plugin resolves ai.exida:stig-code-analyzer:<STIG-version> through its stigNativeAnalyzerSources configuration when published native setup needs it. Native sources are task data, outside the plugin's JVM implementation classpath. Local setup uses the sibling producer directly. Therefore changing C++ source or an unused local pin no longer changes every Gradle task's implementation identity. Evidence identifies the JVM producer by sourceFingerprint, without Git revision or index-state fields that change merely on commit. Native binary identity and the published dependency pin remain independently validated.

Fast regression tests execute real Gradle input checks with tiny compiler/analyzer fixtures. They belong to the existing normal test suites; they do not build PhASAR, run CMake, or contact a package repository:

./gradlew :build-logic:test --tests '*StigNativeBuildInputsTest' ./gradlew :stig-gradle-plugin:test --tests '*StigPublishedAnalyzerCacheTest' ./gradlew :stig-gradle-plugin:functionalTest --tests '*StigNativeSourceArtifactFunctionalTest' --tests '*StigAnalyzerInputCacheFunctionalTest' --tests '*StigIncrementalCompilationFunctionalTest'

The native matrix uses the same StigNativeBuildInputs and StigProducerIdentity declarations as production. It covers unchanged content, commit-only changes, source/test/recipe/toolchain edits, and additive published pins. The functional tests cover separately resolved native artifacts, JVM implementation classpaths, consumer sources, prepared binaries, runtime libraries, and individual analyzer runs. They assert Gradle outcomes and invocation counts; --dry-run does not establish up-to-date decisions. Full native correctness remains in the existing native gates and integrationTest suite.

Native compilation versus P6 execution

stig-code-analyzer has no dependency on core or engine and contains no Kotlin sources. It uses ordinary Gradle Exec producers for PhASAR and C++ compilation, plus the Java resource/publication lifecycle to ship native sources. The unnecessary Kotlin plugin and its JVM logging/test dependencies have been removed.

P6 is different: its Kotlin task implementation and installer live in stig-gradle-plugin. Changes to that implementation classpath can still invalidate compilation-unit or analyzer-run reuse, including unrelated Kotlin changes. Isolated Projects does not change this correctness rule. Separating the execution implementation would need a distinct, tested input contract; that architectural split is deferred. Do not ignore implementation changes or claim that native-module cleanup eliminates P6 invalidation.

Canonical version in a composite build

The local stig checkout keeps its Axion-derived root project.version when Gradle includes it from stig-project. That one value is written to the plugin's generated version resource and JAR manifest and is passed to the analyzer build as the C++ STIG_VERSION definition. The consumer-side getStigVersion() reads the packaged value; it does not run a second Git parser or infer a version from CHANGELOG.md. Consequently the plugin, analyzer binary, analysis.json, and generated report all use the same exact version string.

A source directory without its own .git metadata may still configure tasks that do not need a STIG version, but any versioned artifact, publication, or analyzer build fails through requireStigVersion. Do not add a changelog-derived, parent-repository-tag, or development fallback: restore the STIG checkout metadata or use a released plugin JAR.

IntelliJ IDEA sandbox indexing

The root Gradle IDEA model excludes .intellijPlatform, where :stig-idea-plugin:runIde stores its sandbox caches, logs and generated plugin files. This exclusion also applies when a consumer imports STIG as an included build: the running sandbox must not index its own changing state. Reload the Gradle project after updating this configuration.

How to build

Use the consumer's selected mode, or the explicit source-development producer from STIG:

# from stig-project ./gradlew stigSetupAnalyzer # from stig ./gradlew :stig-code-analyzer:stigBuildLocalAnalyzer

Anything that needs the analyzer (e.g. :activities:<name>:stigGenerateHtml) depends on setup and its selected producer, so a normal model build rebuilds the analyzer first if your sources changed. You rarely call it directly.

What the rebuild tests

Both native Gradle producers invoke build-analyzer.sh --run-analyzer-tests. That configures the analyzer with BUILD_TESTING=ON, builds its GoogleTest executable, registers the individual cases with CTest, and runs ctest --output-on-failure before the rebuilt binary is accepted. Analyzer production sources, analyzer native test sources, and local PhASAR production sources, tests, fixtures, and CMake helpers are Gradle inputs, so editing any of them invalidates the task. A normal consumer analysis therefore rebuilds and runs the native gates before P6 when the sibling source changed.

The PhASAR dependency build separately enables PHASAR_BUILD_UNITTESTS=ON and PHASAR_BUILD_IR=ON, compiles the LLVM fixtures, and runs all registered CTest executables before installation. Failed or empty test runs stop installation; a tested pinned installation can be reused. The staged GCC 15 compatibility headers let pinned Clang 18 compile the fixtures on Fedora/GCC 16 hosts without changing the LLVM version. Existing disabled or skipped individual cases remain visible limitations. See testing overview for the complete test scopes and reuse conditions.

Real solver and reconstruction behavior is also covered by :stig-gradle-plugin:integrationTest, which ordinary consumer analyzer setup does not run. That task depends on STIG's selected native producer and tracks the executable contents and its toolchain-identity artifact as inputs. A changed binary or host toolchain invalidates a previous test result, even when a relink produces identical executable bytes.

Nor does a consumer report generation run STIG's complete JVM test lifecycle. :stig-gradle-plugin:test, :stig-gradle-plugin:functionalTest, and :stig-gradle-plugin:integrationTest run from the sibling STIG check/build tasks and the STIG CI/release gates. A normal consumer analysis compiles and executes the included plugin code it needs, but does not rerun all of those suites after every Kotlin edit.

The published-JAR lazy installer is deliberately different: the JAR contains production analyzer sources but not src/test/cpp, so it leaves BUILD_TESTING=OFF. Published consumers receive the binary smoke/provenance checks, while native and JVM development tests run when producing the STIG artifact.

The cache root is ~/.cache/stig/stig-code-analyzer/. Its layout separates published analyzer versions while sharing a tested PhASAR installation for the same LLVM major and immutable PhASAR commit:

<cache>/ published/<STIG-version>/llvm-<major>-phasar-<commit>/ # packaged sources and build/cmake analyzer pinned/llvm-<major>-phasar-<commit>/ # src/phasar, build/phasar, install/phasar local/ # working-tree builds and installed PhASAR

Gradle and the installer pass these directories explicitly to the script. Local CMake reads the sibling PhASAR working tree directly, without copying it into the cache. Switching modes or published versions retains the other installation. The local directory is still shared by local checkouts; this is not a cross-checkout native build cache.

The published installer compares packaged native files byte-for-byte before warm reuse, including for a republished SNAPSHOT. Unchanged files keep their modification times so Ninja does not rebuild them merely because they were extracted again; timestamps do not select the version, pin, or installation identity. Removed packaged files are removed from the recorded source inventory. Report assets are not native source inputs.

An OS file lock serializes installation across JVMs and direct script invocations. Installed PhASAR files have a complete byte inventory, and the native recipe records the selected compiler, headers, host libraries, LLVM, Z3, and CMake toolchain bytes. Warm reuse verifies that inventory and toolchain, in addition to the binary's STIG/pin identity. A timestamp-only change is not an identity change. Native build trees themselves remain path-sensitive; this is not a claim of hermetic or cross-host native reproducibility.

Legacy installations without the inventory/toolchain record are not silently certified. Their next requested native producer run must establish the new records through its build/test/install gates. The old cache is not deleted at Gradle configuration time. A baked CI installation is copied only after the same validation passes.

Enforced engine identity

The binary self-reports a repeatable identity — a pin commit or the literal local, never a timestamp:

~/.cache/stig/stig-code-analyzer/local/build/cmake/stig-code-analyzer --version # stig-code-analyzer version=<v> phasar=local ← local phasar tree # stig-code-analyzer version=<v> phasar=<sha> ← pinned online clone

These lines are diagnostics, not a user-operated verification step. build-analyzer.sh rejects a binary whose identity does not match its selected source. In local mode P6 knows the working tree must win and rejects a pin before any analyzer process starts; it never invokes the pinned lazy-installer fallback in that state. After each successful taint run, P6 requires codeAnalysisConfiguration.stigVersion, analyzerSourceSha, and phasarVersion in analysis.json to match the requested STIG version and executed binary.

Published setup checks the packaged pin even on a warm installation and writes its success marker only after the binary passes version and PhASAR identity checks. P6 tracks the prepared executable, its resolved native runtime libraries, and the module, configuration, and endpoint files referenced by its generated run plan.

When Gradle supplies an incremental change set, LLVM compilation can retain unchanged successful compilation-unit recipes, and P6 can retain unchanged successful analyzer run entries. LLVM compilation retains Clang dependency files with the raw compiler outputs. Edits to existing captured sources or headers recompile their dependent units; the copied input manifest alone does not invalidate independent units. P1 records a separate compilation-context fingerprint covering actions, options, include candidates and the content of inputs not declared by Bazel as sources or headers (such as compiler configuration files). Changed context, added/removed captured files, VFS overlay changes, toolchains, damaged outputs, implementation-classpath changes, and explicit reruns conservatively invalidate all compiler outputs. Reports without separate context evidence retain whole-report invalidation. Partial reuse also requires every native input to resolve to an individual file; tree artifacts or incomplete input discovery retain whole-task recompilation when inputs change. Evidence validation still runs for the current task; it is not replaced by a success marker. Plugin/report-resource changes can still invalidate the implementation classpath and cause expensive native runs to repeat. These compiler reuse rules apply in both source modes; published analyzer setup still adds the version/pin checks described above.

Switching back to published STIG and pinned PhASAR

Edit the consumer's gradle.properties; leave sibling directories and symlinks in place:

stigSource=published stigVersion=2.2.0

There is no separate consumer PhASAR switch. The removed stig.localPhasar property is rejected with a migration message. Low-level native orchestration passes --local-phasar <checkout> or --no-local-phasar explicitly; the script no longer auto-detects siblings or reads source-selection environment variables.

What NOT to do

  • Don't run cmake/ninja/make by hand in the managed native build directories above. That bypasses the provenance stamping (-DSTIG_PHASAR_VERSION), so --version and analysis.json will lie about which engine the binary carries. Always go through stigBuildAnalyzer (or scripts/build-analyzer.sh).

  • Don't edit pinned/llvm-<major>-phasar-<commit>/src/phasar inside the cache. It is build-managed: a pinned build verifies the complete object graph for the configured pin, refetches it if an interrupted or damaged cache is missing a reachable tree or blob, then runs git checkout -f and force-discards any edits there. Edit the phasar/ sibling instead.

  • Don't expect uncommitted local phasar to reach CI or another machine. Local-sibling mode is a local-dev convenience. To share a phasar change: commit it, push to the fork branch (stig-patches-2026.06), then run from the STIG root:

    ./scripts/update-phasar-pin.sh

    The script checks that the sibling is clean and its HEAD matches the published branch tip at PHASAR_REPO_URL, then writes that SHA to PHASAR_COMMIT in stig-code-analyzer/scripts/pins.env. Use --phasar-dir PATH for a standalone checkout or Git worktree outside the usual sibling layout. It fails before changing the pin when the checkout is dirty, the commits differ, or the configured remote cannot be verified. Review the change, run the selected analyzer validation, then commit and push STIG. A plain git push does not run the updater.

    • Note: the cache clone fetches the fork over the PHASAR_REPO_URL (HTTPS). A private fork needs credentials (a Git credential helper) or an SSH remote. If the complete pinned object graph still cannot be fetched, the build fails with the pin and remote instead of attempting a different dependency source.

One-time setup for local phasar

The phasar/ checkout needs its submodules (nlohmann/json, json-schema-validator). The local build populates them automatically if missing or incomplete. For a standalone PhASAR clone, the manual recovery command is:

git -C ../phasar submodule sync --recursive git -C ../phasar submodule update --init --recursive

If PhASAR is vendored through git-subrepo, the nested .gitmodules file is not active metadata for the parent repository. In that shape the analyzer build script reads PhASAR's .gitmodules directly and populates the required external directories at the commits recorded by the PhASAR tree.

23 October 2026