feat(artifacts): analyzed commit ref in every artifact + analyzed CI file in the JSON report - #444
Merged
Merged
Conversation
thomasboni
force-pushed
the
feat/artifact-commit-ref-and-ci-file
branch
from
September 3, 2026 11:06
59120a4 to
0e14b17
Compare
thomasboni
force-pushed
the
feat/artifact-commit-ref-and-ci-file
branch
from
September 3, 2026 11:15
0e14b17 to
909fe4d
Compare
thomasboni
force-pushed
the
feat/artifact-commit-ref-and-ci-file
branch
from
September 3, 2026 11:21
909fe4d to
0dd03b1
Compare
thomasboni
force-pushed
the
feat/artifact-commit-ref-and-ci-file
branch
from
September 3, 2026 11:29
0dd03b1 to
e193d08
Compare
thomasboni
force-pushed
the
feat/artifact-commit-ref-and-ci-file
branch
2 times, most recently
from
September 3, 2026 11:38
474aa82 to
93a3ad3
Compare
…he analyzed CI file in the JSON report (#443) Two pieces of provenance the AI pipeline needs so a result set is self-describing, without reconstructing "which commit" and "which CI input" from the CI environment around the artifact. Commit ref, in every relevant artifact. One resolver (cmd/artifact_provenance.go) computes the analyzed commit once at output time and every writer reads it off the result: - JSON report: headCommitSha (always resolved, never the literal "HEAD") next to analyzeBranch. - SARIF: run.versionControlProvenance (revisionId + branch + repositoryUri). - PBOM: project.commitSHA + project.ref. - CycloneDX: plumber:git:commit / plumber:git:ref on the project component. - OCSF: a resource per finding naming the repo (uid) at the commit (version) on the ref (labels). The commit and ref are sourced by the same self-vs-elsewhere test ProjectFromCIEnvironment makes: when the CI job is building the very project analyzed (a self-analysis), its own $CI_COMMIT_SHA / $CI_COMMIT_REF_NAME are the analyzed commit and ref and win. When the analysis points elsewhere - a cross-project --project scan, or an explicit --branch on a different ref - only the analyzed target's own head and branch (which control.task resolves into the result) may be used, so the artifact names the analyzed project, not the scanner that ran it. In that case the job's env commit and the local checkout are not fallbacks (they describe the scanner): when the target head did not resolve - a remote scan may not resolve it - the commit is omitted rather than mis-attributed. artifactRepoURI takes the repo path from result.ProjectPath for the same reason, keeping the repositoryUri coherent with the resolved commit. The literal "HEAD" placeholder is never emitted as a commit; when nothing resolves the field is omitted, and analyzeBranch is deleted symmetrically so a raw "HEAD" never leaks through the report's whole-result marshal. The platform push already carries ref{branch,sha} and CSV is a flat findings dump, so both are out of scope. Analyzed CI file, JSON report only. A new analyzedCiConfig block names the exact input the findings came from: for GitLab the resolved merged pipeline (path + content + merged), for GitHub each scanned workflow file (path + content), for both the local and remote scan paths. The GitHub scanners retain the file bytes on the IR under an existing json:"-" field, so nothing reaches the OPA input, and the files are recorded before parsing so an unparseable workflow is still reported as analyzed. It is written to the local --output report but kept out of the score-push payload, so the merged CI configuration content is never exported to the hosted score service. All fields are additive: a main-vs-#443 diff across the JSON, SARIF, PBOM, CycloneDX and OCSF artifacts shows zero removed or retyped keys on both providers, only the new fields. Tested end to end on both providers (commit ref present in all five artifacts; analyzedCiConfig present in the report and absent everywhere else; GitLab merged YAML vs GitHub per-workflow content), with unit coverage for the resolver (self-analysis, cross-project, --branch, unresolved-target-head omission, both provider env mappings, the local git fallback), the report/SARIF/OCSF/PBOM/ CycloneDX writers including OCSF partial-provenance boundaries, the provider PBOM wiring, and the local and remote workflow retention.
thomasboni
force-pushed
the
feat/artifact-commit-ref-and-ci-file
branch
from
September 3, 2026 11:43
93a3ad3 to
f246919
Compare
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.
Closes #443.
Amine (AI) needs analysis outputs to be self-describing: a consumer should read which commit was analyzed and which CI input produced the findings straight from the artifact, without reconstructing it from the CI environment the artifact was written in.
What this adds
1. The analyzed commit ref, in every relevant artifact
A single resolver (
cmd/artifact_provenance.go) computes the analyzed commit once, at output time, and every writer reads it off the result:The
HEADplaceholder a--projectrun leaves behind is a ref, not a commit: it is never emitted as a commit SHA, it is omitted. A commit that cannot be resolved is omitted rather than faked.headCommitSha(now resolves reliably, never"HEAD") +analyzeBranchrun.versionControlProvenance[](revisionId+branch+repositoryUri), the spec's own provenance slotproject.commitSHA+project.refplumber:git:commit/plumber:git:refon the project componentresourceper finding: repo inuid, commit inversion, ref inlabelsThe platform push already carries
ref{branch, sha}and CSV is a flat findings dump, so both are out of scope.2. The analyzed CI file, in the JSON report only
A new
analyzedCiConfigblock names the exact input the findings came from:path+content+merged: true).workflows[](path+content), for both the local and remote scan paths.The GitHub scanners keep the file bytes on the IR under an existing
json:"-"field, so nothing new reaches the OPA input. This block is JSON-report only by design (verified absent from SARIF/PBOM/CycloneDX/OCSF).Non-breaking
Strictly additive. An e2e diff of every artifact against
mainon the same fixture, both providers, shows 0 removed and 0 retyped keys, only the new fields:Testing
resolveArtifactRef, incl.HEADrejection and the ref-name-env-over-default-branch precedence), and per-artifact provenance tests (report, SARIF, OCSF, PBOM, CycloneDX).gofmtclean; pinnedgolangci-lint0 issues; deadcode gate clean.analyzedCiConfigpresent in the report and absent everywhere else; GitLab merged YAML vs GitHub per-workflow content).🤖 Generated with Claude Code