Skip to content

feat(policies): allow gate=false to override org-wide blocking#2777

Open
matiasinsaurralde wants to merge 8 commits intochainloop-dev:mainfrom
matiasinsaurralde:feat/policies-gate-false-override
Open

feat(policies): allow gate=false to override org-wide blocking#2777
matiasinsaurralde wants to merge 8 commits intochainloop-dev:mainfrom
matiasinsaurralde:feat/policies-gate-false-override

Conversation

@matiasinsaurralde
Copy link
Contributor

@matiasinsaurralde matiasinsaurralde commented Feb 24, 2026

PR for #2769

This change implements per-policy gate override behavior for policy enforcement.

  • Makes contract gate presence-aware so unset and explicit false are distinct.
  • Adds effective gate resolution in policy evaluation:
    • unset gate inherits organization default strategy
    • gate: true enforces blocking for that policy
    • gate: false forces non-blocking for that policy
  • Updates attestation push enforcement to rely on effective gated violations, with a safe fallback when policy evaluations are unavailable.
  • Adds test coverage for gate inheritance and explicit override behavior.

@matiasinsaurralde matiasinsaurralde force-pushed the feat/policies-gate-false-override branch from 206bdbe to 55e399f Compare February 24, 2026 17:19
@matiasinsaurralde
Copy link
Contributor Author

buf:breaking:ignore per-field comment annotations do not exist in buf (only buf:lint:ignore do). To suppress the FIELD_SAME_CARDINALITY violation introduced by changing bool gate to optional bool gate, the exception was added to buf.yaml for the app/controlplane/api module.

Additionally, --exclude-imports was added to the buf breaking CI step to prevent crafting_schema.proto from being re-evaluated as an import in other modules (e.g. pkg/attestation/crafter/api), which would trigger the same violation a second time outside the module where the exception is defined.

Both the buf.yaml exception and the --exclude-imports flag can be removed once this change lands in main, since the baseline will no longer include the old field cardinality.

In order to try the buf breaking locally you can do (where b46ef29 is latest main):

buf breaking --against "https://github.com/chainloop-dev/chainloop.git#format=git,commit=b46ef29bb9c471b401a1dd083fcaad95fe72f021" --exclude-imports

@matiasinsaurralde matiasinsaurralde marked this pull request as ready for review February 24, 2026 22:13
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 12 files

@migmartri migmartri requested review from Piskoo and jiparis February 25, 2026 00:02
except:
- EXTENSION_NO_DELETE
- FIELD_SAME_DEFAULT
- FIELD_SAME_CARDINALITY
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure we want to disable this globally but instead you coudl add an annotation in your specific case?

Copy link
Contributor Author

@matiasinsaurralde matiasinsaurralde Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that breaking change detection doesn't support inline comment ignores.

Also exceptions are only per file/directory level.

Given that we control the gRPC clients/servers that use these messages and that the bool to optional bool doesn't imply a wire format change in PB, it should be safe to regenerate the bindings and update any other parts of the code that use this message.

Also buf breaking always compares to latest main. If this PR lands (with or without the exceptions, which we can also leave out), a subsequent PR won't raise a breaking change error - cc @jiparis

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, unfortunately buf breaking doesn't support inline ignores as buf lint does. To be honest, I'm wondering how this is the first time we are facing this issue. Did we update buf recently?
The only solution is adding this top level except option. The change to optional is safe, as it only affects to generated code. Wired bytes are exactly the same (although with different semantics from now on).

Copy link
Member

@migmartri migmartri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great, ptal at my comments, thanks

// Effective gate semantics are already resolved in policy evaluations.
// For backwards compatibility, fall back to aggregate status only if
// no evaluations are available.
if len(status.PolicyEvaluations) == 0 && status.HasPolicyViolations {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this change, mind elaborating a bit?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was dead code from backwards compatibility code (that we don't need for this case), I've added comments about it on the cubic thread in this same PR.

Requirements: attachment.Requirements,
RawResults: engineRawResultsToAPIRawResults(rawResults),
Gate: attachment.GetGate(),
Gate: policyAttachmentGate(attachment, pv.defaultGate),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, so before the default gate was outside the context of policy verifier and now you are bringing both in?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, PolicyEvaluation.Gate was populated with the raw attachment value (attachment.GetGate()), while the org-level default (block_on_policy_violation) was applied later in CLI enforcement.

The efective gate is now resolved inside PolicyVerifier (policyAttachmentGate(attachment, pv.defaultGate)), following the 3 state behavior (to achieve default on / opt-out per policy):

  • Explicit gate: true: blocking.
  • Explicit gate: false: non-blocking (opt-out)
  • Unset gate: inherits org default.

@matiasinsaurralde matiasinsaurralde force-pushed the feat/policies-gate-false-override branch from e4de61d to bafe560 Compare February 25, 2026 18:10
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="app/cli/cmd/attestation_push.go">

<violation number="1" location="app/cli/cmd/attestation_push.go:199">
P1: Backward-compatible fallback removed: when `MustBlockOnPolicyViolations` is true but no policy evaluations are available (e.g., older server version or edge case), the function no longer blocks on `HasPolicyViolations`. This silently allows attestation pushes through despite policy violations. The PR description mentions "a safe fallback when policy evaluations are unavailable," but the fallback was actually deleted.

The remaining `if` block is also effectively a no-op for control flow — both branches return `nil` — so nothing blocks in the `MustBlockOnPolicyViolations` path anymore.

Consider restoring the fallback for the zero-evaluations case.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

…eritance

Make PolicyAttachment.gate presence-aware to distinguish unset from explicit false.
Resolve effective gate at evaluation time using org default when unset.
Update attestation push enforcement to block on effective gated violations.

Signed-off-by: Matías Insaurralde <matias@chainloop.dev>
Move the FIELD_SAME_CARDINALITY exception from buf.yaml to
an inline ignore on PolicyAttachment.gate in crafting_schema.proto
limiting the suppression to the specific field change.

Signed-off-by: Matías Insaurralde <matias@chainloop.dev>
Signed-off-by: Matías Insaurralde <matias@chainloop.dev>
Signed-off-by: Matías Insaurralde <matias@chainloop.dev>
Signed-off-by: Matías Insaurralde <matias@chainloop.dev>
Signed-off-by: Matías Insaurralde <matias@chainloop.dev>
Signed-off-by: Matías Insaurralde <matias@chainloop.dev>
Remove the `len(PolicyEvaluations) == 0 && HasPolicyViolations` backward-compatibility
fallback from validatePolicyEnforcement. Both fields are always produced together by a
single call to getPolicyEvaluations(), so if the evaluations map is empty HasPolicyViolations
is always false — the condition is structurally unreachable.

Flatten the nested `if MustBlockOnPolicyViolations { if bypassPolicyCheck { ... } }` to
a single `if MustBlockOnPolicyViolations && bypassPolicyCheck { ... }` for clarity.

Signed-off-by: Matías Insaurralde <matias@chainloop.dev>
@matiasinsaurralde matiasinsaurralde force-pushed the feat/policies-gate-false-override branch from f7db36c to 075655e Compare February 25, 2026 22:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants