You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Use a size annotation on the policy-evaluations bundle ref instead of calling the CAS in the workflow-run View API #3414
#3408 caps the policy-evaluations bundle the WorkflowRunService.View API is willing to download and inline. To learn the bundle size before paying for the transfer, the control plane calls CASClient.Describe against the org's CAS backend, and falls back to a REASON_UNAVAILABLE reference when the size cannot be determined.
This adds a CAS round trip (temporary credentials, gRPC connection, backend HEAD) on every View of an attestation that carries a policyEvaluationsRef, even when the bundle is small and would be inlined anyway. It also makes the decision depend on the backend reporting a content length, which some backends omit, in which case we refuse to inline a bundle that may be tiny.
Proposal
Record the bundle size in the attestation itself and use that instead of calling the CAS.
The CLI already builds the policyEvaluationsRef as an in-toto ResourceDescriptor in uploadPolicyEvaluationsBundle (app/cli/pkg/action/attestation_push.go). Today it only sets name, digest and mediaType. It has the marshalled bundle bytes in hand, so it can add the size, for example as chainloop.dev/size under annotations (or a dedicated field if we prefer). The descriptor is part of the signed statement, so the value is tamper-proof.
On the server side, resolvePolicyEvaluations (app/controlplane/internal/service/workflowrun.go) would then:
Read the size from the descriptor. If it is above the cap, return REASON_TOO_LARGE without touching the CAS.
If it is under the cap, download straight away, keeping the existing boundedBuffer so an inconsistent descriptor cannot push us past the cap.
Only when the attestation predates the annotation (no size present) fall back to the current Describe call.
This removes the extra round trip from the hot path, works regardless of whether the backend reports a content length, and the result does not depend on CAS availability for the "too large" decision.
Notes
Existing attestations do not carry the annotation, so the Describe fallback needs to stay until we are comfortable dropping support for them.
Describe on biz.CASClient / casclient.Downloader can be kept for the fallback, or removed later together with it.
Related: the cap does not apply to orgs on the inline CAS backend, since the CLI skips the bundle upload there and the evaluations stay embedded in the predicate. That is a separate problem from this one.
🤖 Posted by Maximus bot (Claude Code) on behalf of @migmartri
Today AttestationItem.policy_evaluations_ref is only populated when the server declines to inline the evaluations (REASON_TOO_LARGE / REASON_UNAVAILABLE). When the evaluations are inlined, the field is empty and the caller has no way to learn the digest of the bundle from the API, even though the attestation carries it.
I would return the reference always, whenever the attestation has a policyEvaluationsRef, regardless of whether the evaluations were inlined. The reason field would then express the outcome rather than only the failure:
REASON_UNSPECIFIED (or a new explicit REASON_INLINED): evaluations are present in policy_evaluations, the ref is informational.
REASON_TOO_LARGE / REASON_UNAVAILABLE: as today, policy_evaluations is empty and the caller must fetch the bundle.
That gives consumers a stable pointer to the bundle (for download, caching, cross-referencing) independent of the server-side inlining decision, and it fits naturally with reading the size from the descriptor annotation proposed above, since size_bytes can then be filled from the attestation without a CAS call.
The CLI workflow run describe mapping (pbPolicyEvaluationsRefToAction) currently treats an unspecified reason as unavailable, so it would need to be adjusted to not report "unavailable" when the evaluations are in fact inlined.
🤖 Posted by Maximus bot (Claude Code) on behalf of @migmartri
The size is recorded in the attestation. It went in as a dedicated predicate field, policyEvaluationsBundleSize, rather than a chainloop.dev/size descriptor annotation — the proposal allowed either. It sits alongside policyEvaluationsRef in the signed statement, so it is equally tamper-proof, and uploadPolicyEvaluationsBundle fills it from the bytes it has just marshalled.
resolvePolicyEvaluations gates on that size. Over the cap returns REASON_TOO_LARGE before even the CAS mapping lookup, so an oversized bundle now costs zero CAS calls. Under the cap it downloads straight away.
Describe is gone from the hot path and removed outright from biz.CASDownloader, casclient.Downloader and CASClientUseCase. The info.Size <= 0 refusal goes with it, so a backend that omits a content length no longer causes a small bundle to be reported unavailable.
Two deliberate deviations, worth a second opinion
TheDescribefallback was not kept. Attestations with no recorded size are downloaded and inlined in full, uncapped. The cap therefore protects newly pushed attestations only, and existing runs — including the 134k-violation ones from PFM-7045 — go back to being inlined unbounded on View until they are re-attested. The ticket notes say the fallback should stay until we are comfortable dropping those; this PR drops them now.
boundedBufferwas removed rather than kept for the under-cap download. Its purpose in fix(controlplane): cap the policy evaluations inlined by the workflow-run View API #3408 was to distrust a size that came from CAS metadata; with the size signed into the predicate the bound was judged redundant. The consequence is that a stored object that does not match its recorded size is read in full.
Either can be restored if the exposure is not acceptable.
Against the comment above
Implemented as described. AttestationItem.policy_evaluations_ref is populated whenever the attestation carries a bundle, regardless of the inlining decision.
One shape difference: instead of a REASON_INLINED enum value, the message gained bool inlined = 5 and reason stays strictly about failures, unset on the happy path. Same outcome for consumers, and it keeps a single field from carrying both success and failure semantics.
size_bytes is filled without any CAS call in every case — the recorded size on the too-large path, the decoded byte count when inlined. pbPolicyEvaluationsRefToAction no longer reports "unavailable" for an unspecified reason when the evaluations are in fact inlined.
Beyond the comment, workflow run describe prints a Policy evaluations bundle row carrying the chainloop artifact download --digest ... command wherever a digest is known — inlined, too large or unavailable alike.
Not addressed
The inline CAS backend case, which the ticket notes already flag as a separate problem.
Activity
migmartri commentedon Sep 8, 2026
One more change to consider alongside this.
Today
AttestationItem.policy_evaluations_refis only populated when the server declines to inline the evaluations (REASON_TOO_LARGE/REASON_UNAVAILABLE). When the evaluations are inlined, the field is empty and the caller has no way to learn the digest of the bundle from the API, even though the attestation carries it.I would return the reference always, whenever the attestation has a
policyEvaluationsRef, regardless of whether the evaluations were inlined. Thereasonfield would then express the outcome rather than only the failure:REASON_UNSPECIFIED(or a new explicitREASON_INLINED): evaluations are present inpolicy_evaluations, the ref is informational.REASON_TOO_LARGE/REASON_UNAVAILABLE: as today,policy_evaluationsis empty and the caller must fetch the bundle.That gives consumers a stable pointer to the bundle (for download, caching, cross-referencing) independent of the server-side inlining decision, and it fits naturally with reading the size from the descriptor annotation proposed above, since
size_bytescan then be filled from the attestation without a CAS call.The CLI
workflow run describemapping (pbPolicyEvaluationsRefToAction) currently treats an unspecified reason as unavailable, so it would need to be adjusted to not report "unavailable" when the evaluations are in fact inlined.🤖 Posted by Maximus bot (Claude Code) on behalf of @migmartri
javirln commentedon Sep 8, 2026
Implemented in #3413 (draft).
Against the ticket proposal
policyEvaluationsBundleSize, rather than achainloop.dev/sizedescriptor annotation — the proposal allowed either. It sits alongsidepolicyEvaluationsRefin the signed statement, so it is equally tamper-proof, anduploadPolicyEvaluationsBundlefills it from the bytes it has just marshalled.resolvePolicyEvaluationsgates on that size. Over the cap returnsREASON_TOO_LARGEbefore even the CAS mapping lookup, so an oversized bundle now costs zero CAS calls. Under the cap it downloads straight away.Describeis gone from the hot path and removed outright frombiz.CASDownloader,casclient.DownloaderandCASClientUseCase. Theinfo.Size <= 0refusal goes with it, so a backend that omits a content length no longer causes a small bundle to be reported unavailable.Two deliberate deviations, worth a second opinion
Describefallback was not kept. Attestations with no recorded size are downloaded and inlined in full, uncapped. The cap therefore protects newly pushed attestations only, and existing runs — including the 134k-violation ones from PFM-7045 — go back to being inlined unbounded onViewuntil they are re-attested. The ticket notes say the fallback should stay until we are comfortable dropping those; this PR drops them now.boundedBufferwas removed rather than kept for the under-cap download. Its purpose in fix(controlplane): cap the policy evaluations inlined by the workflow-run View API #3408 was to distrust a size that came from CAS metadata; with the size signed into the predicate the bound was judged redundant. The consequence is that a stored object that does not match its recorded size is read in full.Either can be restored if the exposure is not acceptable.
Against the comment above
Implemented as described.
AttestationItem.policy_evaluations_refis populated whenever the attestation carries a bundle, regardless of the inlining decision.One shape difference: instead of a
REASON_INLINEDenum value, the message gainedbool inlined = 5andreasonstays strictly about failures, unset on the happy path. Same outcome for consumers, and it keeps a single field from carrying both success and failure semantics.size_bytesis filled without any CAS call in every case — the recorded size on the too-large path, the decoded byte count when inlined.pbPolicyEvaluationsRefToActionno longer reports "unavailable" for an unspecified reason when the evaluations are in fact inlined.Beyond the comment,
workflow run describeprints aPolicy evaluations bundlerow carrying thechainloop artifact download --digest ...command wherever a digest is known — inlined, too large or unavailable alike.Not addressed
The inline CAS backend case, which the ticket notes already flag as a separate problem.
Drafted with Claude Code.