Context / problem
When an attestation runs as part of a pull request / merge request, Chainloop today treats it the same as a build on main/a release. That causes two side-effects we don't want for PR builds:
- Latest promotion — PR builds get promoted to the project's "latest" version. The current workaround is manually passing
--mark-latest=false, but that flag was really meant to express "this is a PR build". We're overloading a low-level flag to express a higher-level concept.
- Findings processing — policy violations from a PR build get processed (and later re-processed) as if they were findings from a real merge/release build. We want PR-originated attestations to be tagged so downstream consumers can choose not to (re)process them.
The underlying insight: the real concept we want is "PR mode", and both behaviors above should flow from that single signal rather than being manually wired via --mark-latest.
Decision (simplified)
After discussion, we're simplifying the original proposal. Instead of adding a new field on the WorkflowRun ent schema or tagging the policy evaluation object, we'll set a top-level annotation on the attestation. This keeps the change minimal and decouples the PR signal from the policy evaluation pipeline.
Well-known annotation key: chainloop.dev/is-pull-request with value "true".
This leverages the existing annotations map on the Attestation proto (crafting_state.proto), which already flows through the rendering pipeline — no proto schema changes, no DB migration, no codegen churn.
Detection — auto-detect + explicit override
- Auto-detect from the CI runner environment (already implemented in
DetectPRContext / prmetadata.go):
- GitHub Actions:
GITHUB_EVENT_NAME=pull_request (or pull_request_target).
- GitLab:
CI_PIPELINE_SOURCE=merge_request_event / CI_MERGE_REQUEST_IID.
- Dagger: delegates to the parent CI env vars.
- Explicit
--pr / --pr=false flag on chainloop attestation init overrides auto-detection (zero-config in CI, escapable for edge cases / self-hosted / generic runner).
Behavior when in PR mode (by default)
- Skip latest promotion — default to
mark-latest=false. An explicitly-passed --mark-latest=true still wins (respect explicit user intent). This replaces the manual --mark-latest=false workaround.
- Set the
chainloop.dev/is-pull-request=true annotation on the attestation so downstream consumers (e.g. findings processing) can detect PR-originated attestations and skip (re)processing.
Scope split
- OSS (this issue): capture & propagate the PR signal as a top-level annotation on the attestation; wire
--pr flag and mark-latest default behavior.
- SaaS (downstream): findings processing/reprocessing reads the annotation and decides whether to process. Not in scope for this issue.
Relevant code pointers
- PR detection (existing):
pkg/attestation/crafter/prmetadata.go (DetectPRContext), pkg/attestation/crafter/collector_prmetadata.go.
- Runner env vars:
pkg/attestation/crafter/runners/githubaction.go, gitlabpipeline.go, daggerpipeline.go.
- CLI flags:
app/cli/cmd/attestation_init.go (--mark-latest).
- Attestation proto (annotations map):
pkg/attestation/crafter/api/attestation/v1/crafting_state.proto (Attestation.annotations, field 5).
- Crafter init (where annotation would be set):
pkg/attestation/crafter/crafter.go (initialCraftingState).
- Action layer:
app/cli/pkg/action/attestation_init.go.
🤖 Posted by Maximus bot (Claude Code) on behalf of @migmartri
Context / problem
When an attestation runs as part of a pull request / merge request, Chainloop today treats it the same as a build on
main/a release. That causes two side-effects we don't want for PR builds:--mark-latest=false, but that flag was really meant to express "this is a PR build". We're overloading a low-level flag to express a higher-level concept.The underlying insight: the real concept we want is "PR mode", and both behaviors above should flow from that single signal rather than being manually wired via
--mark-latest.Decision (simplified)
After discussion, we're simplifying the original proposal. Instead of adding a new field on the
WorkflowRunent schema or tagging the policy evaluation object, we'll set a top-level annotation on the attestation. This keeps the change minimal and decouples the PR signal from the policy evaluation pipeline.Well-known annotation key:
chainloop.dev/is-pull-requestwith value"true".This leverages the existing
annotationsmap on theAttestationproto (crafting_state.proto), which already flows through the rendering pipeline — no proto schema changes, no DB migration, no codegen churn.Detection — auto-detect + explicit override
DetectPRContext/prmetadata.go):GITHUB_EVENT_NAME=pull_request(orpull_request_target).CI_PIPELINE_SOURCE=merge_request_event/CI_MERGE_REQUEST_IID.--pr/--pr=falseflag onchainloop attestation initoverrides auto-detection (zero-config in CI, escapable for edge cases / self-hosted / generic runner).Behavior when in PR mode (by default)
mark-latest=false. An explicitly-passed--mark-latest=truestill wins (respect explicit user intent). This replaces the manual--mark-latest=falseworkaround.chainloop.dev/is-pull-request=trueannotation on the attestation so downstream consumers (e.g. findings processing) can detect PR-originated attestations and skip (re)processing.Scope split
--prflag andmark-latestdefault behavior.Relevant code pointers
pkg/attestation/crafter/prmetadata.go(DetectPRContext),pkg/attestation/crafter/collector_prmetadata.go.pkg/attestation/crafter/runners/githubaction.go,gitlabpipeline.go,daggerpipeline.go.app/cli/cmd/attestation_init.go(--mark-latest).pkg/attestation/crafter/api/attestation/v1/crafting_state.proto(Attestation.annotations, field 5).pkg/attestation/crafter/crafter.go(initialCraftingState).app/cli/pkg/action/attestation_init.go.🤖 Posted by Maximus bot (Claude Code) on behalf of @migmartri