Skip to content

Commit c61a299

Browse files
committed
feat(policies): remove STATUS phase and make status action read-only
Remove the STATUS phase from the AttestationPhase enum, simplifying the attestation lifecycle to INIT and PUSH. Policy evaluation is moved out of the status action into the init action explicitly, following the same pattern push already uses. The status command becomes a pure read-only observer that displays existing evaluations from crafting state. Signed-off-by: Miguel Martinez <miguel@chainloop.dev>
1 parent 6153d6c commit c61a299

13 files changed

Lines changed: 42 additions & 136 deletions

app/cli/cmd/attestation_init.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import (
2121

2222
"github.com/chainloop-dev/chainloop/app/cli/cmd/output"
2323
"github.com/chainloop-dev/chainloop/app/cli/pkg/action"
24-
"github.com/chainloop-dev/chainloop/pkg/policies"
2524
"github.com/spf13/cobra"
2625
"github.com/spf13/viper"
2726
)
@@ -128,7 +127,7 @@ func newAttestationInitCmd() *cobra.Command {
128127
return newGracefulError(err)
129128
}
130129

131-
res, err := statusAction.Run(cmd.Context(), attestationID, action.WithStatusEvalPhase(policies.EvalPhaseInit))
130+
res, err := statusAction.Run(cmd.Context(), attestationID)
132131
if err != nil {
133132
return newGracefulError(err)
134133
}

app/cli/pkg/action/attestation_init.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright 2024-2025 The Chainloop Authors.
2+
// Copyright 2024-2026 The Chainloop Authors.
33
//
44
// Licensed under the Apache License, Version 2.0 (the "License");
55
// you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@ import (
2727
"github.com/chainloop-dev/chainloop/app/controlplane/pkg/unmarshal"
2828
"github.com/chainloop-dev/chainloop/pkg/attestation/crafter"
2929
clientAPI "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1"
30+
"github.com/chainloop-dev/chainloop/pkg/attestation/renderer"
3031
"github.com/chainloop-dev/chainloop/pkg/casclient"
3132
"github.com/chainloop-dev/chainloop/pkg/policies"
3233
"github.com/rs/zerolog"
@@ -305,6 +306,22 @@ func (action *AttestationInit) Run(ctx context.Context, opts *AttestationInitRun
305306
// Don't fail the init - this is best-effort
306307
}
307308

309+
// Evaluate attestation-level policies at init phase
310+
attClient := pb.NewAttestationServiceClient(action.CPConnection)
311+
r, err := renderer.NewAttestationRenderer(action.c.CraftingState, attClient, "", "", nil, renderer.WithLogger(action.Logger))
312+
if err != nil {
313+
return "", fmt.Errorf("creating attestation renderer: %w", err)
314+
}
315+
316+
statement, err := r.RenderStatement(ctx)
317+
if err != nil {
318+
return "", fmt.Errorf("rendering statement: %w", err)
319+
}
320+
321+
if err := action.c.EvaluateAttestationPolicies(ctx, attestationID, statement, policies.EvalPhaseInit); err != nil {
322+
return "", fmt.Errorf("evaluating attestation policies: %w", err)
323+
}
324+
308325
return attestationID, nil
309326
}
310327

app/cli/pkg/action/attestation_push.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,7 @@ func (action *AttestationPush) Run(ctx context.Context, attestationID string, ru
102102
return nil, fmt.Errorf("creating status action: %w", err)
103103
}
104104

105-
// we do not want to evaluate policies here since we do it manually later on
106-
attestationStatus, err := statusAction.Run(ctx, attestationID, WithSkipPolicyEvaluation())
105+
attestationStatus, err := statusAction.Run(ctx, attestationID)
107106
if err != nil {
108107
return nil, fmt.Errorf("creating running status action: %w", err)
109108
}

app/cli/pkg/action/attestation_status.go

Lines changed: 3 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,10 @@ import (
2020
"fmt"
2121
"time"
2222

23-
pb "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1"
2423
pbc "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1"
2524
"github.com/chainloop-dev/chainloop/pkg/attestation/crafter"
2625
v1 "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1"
27-
"github.com/chainloop-dev/chainloop/pkg/attestation/renderer"
2826
"github.com/chainloop-dev/chainloop/pkg/attestation/renderer/chainloop"
29-
"github.com/chainloop-dev/chainloop/pkg/policies"
3027
)
3128

3229
type AttestationStatusOpts struct {
@@ -40,10 +37,7 @@ type AttestationStatus struct {
4037
*ActionsOpts
4138
c *crafter.Crafter
4239
// Do not show information about the project version release status
43-
isPushed bool
44-
evalPhase policies.EvalPhase
45-
// skipEvaluation disables policy evaluation entirely; only existing evaluations from state are shown
46-
skipEvaluation bool
40+
isPushed bool
4741
}
4842

4943
type AttestationStatusResult struct {
@@ -93,30 +87,10 @@ func NewAttestationStatus(cfg *AttestationStatusOpts) (*AttestationStatus, error
9387
ActionsOpts: cfg.ActionsOpts,
9488
c: c,
9589
isPushed: cfg.isPushed,
96-
evalPhase: policies.EvalPhaseStatus,
9790
}, nil
9891
}
9992

100-
func WithStatusEvalPhase(phase policies.EvalPhase) func(*AttestationStatus) {
101-
return func(opts *AttestationStatus) {
102-
opts.evalPhase = phase
103-
}
104-
}
105-
106-
// WithSkipPolicyEvaluation disables policy evaluation; only existing evaluations from crafting state are displayed.
107-
func WithSkipPolicyEvaluation() func(*AttestationStatus) {
108-
return func(opts *AttestationStatus) {
109-
opts.skipEvaluation = true
110-
}
111-
}
112-
113-
type AttestationStatusOpt func(*AttestationStatus)
114-
115-
func (action *AttestationStatus) Run(ctx context.Context, attestationID string, opts ...AttestationStatusOpt) (*AttestationStatusResult, error) {
116-
for _, opt := range opts {
117-
opt(action)
118-
}
119-
93+
func (action *AttestationStatus) Run(ctx context.Context, attestationID string) (*AttestationStatusResult, error) {
12094
c := action.c
12195

12296
if initialized, err := c.AlreadyInitialized(ctx, attestationID); err != nil {
@@ -152,25 +126,7 @@ func (action *AttestationStatus) Run(ctx context.Context, attestationID string,
152126
TimestampAuthority: att.GetSigningOptions().GetTimestampAuthorityUrl(),
153127
}
154128

155-
if !action.skipEvaluation {
156-
// Render the statement and evaluate attestation-level policies using the configured phase
157-
attClient := pb.NewAttestationServiceClient(action.CPConnection)
158-
r, err := renderer.NewAttestationRenderer(c.CraftingState, attClient, "", "", nil, renderer.WithLogger(action.Logger))
159-
if err != nil {
160-
return nil, fmt.Errorf("creating attestation renderer: %w", err)
161-
}
162-
163-
statement, err := r.RenderStatement(ctx)
164-
if err != nil {
165-
return nil, fmt.Errorf("rendering statement: %w", err)
166-
}
167-
168-
if err := c.EvaluateAttestationPolicies(ctx, attestationID, statement, action.evalPhase); err != nil {
169-
return nil, fmt.Errorf("evaluating attestation policies: %w", err)
170-
}
171-
}
172-
173-
// Always read policy evaluations from crafting state regardless of evaluation
129+
// Read policy evaluations from crafting state (evaluation happens in init/push, not here)
174130
res.PolicyEvaluations, res.HasPolicyViolations = getPolicyEvaluations(c)
175131

176132
if v := workflowMeta.GetVersion(); v != nil {

app/controlplane/api/gen/frontend/workflowcontract/v1/crafting_schema.ts

Lines changed: 1 addition & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpecV2.jsonschema.json

Lines changed: 2 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpecV2.schema.json

Lines changed: 2 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/controlplane/api/workflowcontract/v1/crafting_schema.pb.go

Lines changed: 3 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/controlplane/api/workflowcontract/v1/crafting_schema.proto

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,6 @@ message PolicySpec {
311311
enum AttestationPhase {
312312
ATTESTATION_PHASE_UNSPECIFIED = 0;
313313
INIT = 1;
314-
STATUS = 2;
315314
PUSH = 3;
316315
}
317316

@@ -350,7 +349,7 @@ message PolicySpecV2 {
350349
}];
351350

352351
// Controls at which attestation phases this policy is evaluated.
353-
// Empty means evaluate at all phases (INIT, STATUS, and PUSH) for backwards compatibility.
352+
// Empty means evaluate at all phases (INIT and PUSH) for backwards compatibility.
354353
// Only applicable when kind is ATTESTATION.
355354
repeated AttestationPhase attestation_phases = 5;
356355
}

pkg/policies/policies.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ type EvalPhase int
6969
const (
7070
EvalPhaseUnspecified EvalPhase = iota
7171
EvalPhaseInit
72-
EvalPhaseStatus
7372
EvalPhasePush
7473
)
7574

@@ -198,8 +197,6 @@ func shouldEvaluateAtPhase(phases []v1.AttestationPhase, phase EvalPhase) bool {
198197
switch phase {
199198
case EvalPhaseInit:
200199
target = v1.AttestationPhase_INIT
201-
case EvalPhaseStatus:
202-
target = v1.AttestationPhase_STATUS
203200
case EvalPhasePush:
204201
target = v1.AttestationPhase_PUSH
205202
default:

0 commit comments

Comments
 (0)