Skip to content

Introduce a "PR mode" for attestations #3265

Description

@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:

  1. 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.
  2. 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

Metadata

Metadata

Assignees

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions