Implement selective checkpoint recomputation
This commit is contained in:
@@ -40,17 +40,76 @@ type CheckpointRecorder interface {
|
||||
NormalizeFailed(laneID string, moduleKey string, dependencies []CheckpointFingerprint, err error) error
|
||||
}
|
||||
|
||||
// StepCheckpointRecorder is implemented by checkpoint stores that isolate
|
||||
// lane artifacts by their ordered pipeline step. The legacy recorder methods
|
||||
// remain available for callers that do not have step context.
|
||||
type StepCheckpointRecorder interface {
|
||||
ExtractRunningForStep(stepID, laneID string, moduleKey string, dependencies []CheckpointFingerprint) error
|
||||
ExtractSucceededForStep(stepID, laneID string, moduleKey string, dependencies []CheckpointFingerprint, outputs []CheckpointArtifact, rejected []contracts.RejectedOutput, warnings []contracts.Warning) error
|
||||
ExtractFailedForStep(stepID, laneID string, moduleKey string, dependencies []CheckpointFingerprint, err error) error
|
||||
MergeRunningForStep(stepID, laneID string, moduleKey string, dependencies []CheckpointFingerprint) error
|
||||
MergeSucceededForStep(stepID, laneID string, moduleKey string, dependencies []CheckpointFingerprint, output CheckpointArtifact, warnings []contracts.Warning) error
|
||||
MergeRejectedForStep(stepID, laneID string, moduleKey string, dependencies []CheckpointFingerprint, rejected contracts.RejectedOutput) error
|
||||
MergeFailedForStep(stepID, laneID string, moduleKey string, dependencies []CheckpointFingerprint, err error) error
|
||||
NormalizeRunningForStep(stepID, laneID string, moduleKey string, dependencies []CheckpointFingerprint) error
|
||||
NormalizeSucceededForStep(stepID, laneID string, moduleKey string, dependencies []CheckpointFingerprint, output CheckpointArtifact, warnings []contracts.Warning) error
|
||||
NormalizeRejectedForStep(stepID, laneID string, moduleKey string, dependencies []CheckpointFingerprint, rejected contracts.RejectedOutput) error
|
||||
NormalizeFailedForStep(stepID, laneID string, moduleKey string, dependencies []CheckpointFingerprint, err error) error
|
||||
}
|
||||
|
||||
type CheckpointDecision struct {
|
||||
Reused bool `json:"reused"`
|
||||
Reused bool `json:"reused"`
|
||||
Category string `json:"category,omitempty"`
|
||||
ReasonCode string `json:"reason_code,omitempty"`
|
||||
Detail string `json:"detail,omitempty"`
|
||||
// Reason is retained as a compatibility/debug field for existing callers.
|
||||
// New checkpoint stores should put bounded, non-sensitive text in Detail.
|
||||
Reason string `json:"reason,omitempty"`
|
||||
}
|
||||
|
||||
type CheckpointEvent struct {
|
||||
Stage string `json:"stage"`
|
||||
LaneID string `json:"lane_id,omitempty"`
|
||||
ModuleKey string `json:"module_key,omitempty"`
|
||||
Action string `json:"action"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
Stage string `json:"stage"`
|
||||
StepID string `json:"step_id,omitempty"`
|
||||
LaneID string `json:"lane_id,omitempty"`
|
||||
ModuleKey string `json:"module_key,omitempty"`
|
||||
Action string `json:"action"`
|
||||
Category string `json:"category,omitempty"`
|
||||
ReasonCode string `json:"reason_code,omitempty"`
|
||||
Detail string `json:"detail,omitempty"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
}
|
||||
|
||||
type CheckpointExecutionPolicy struct {
|
||||
ForcedLanes map[string]struct{}
|
||||
RequireReusableLanes map[string]struct{}
|
||||
}
|
||||
|
||||
func CheckpointLaneKey(stepID, laneID string) string {
|
||||
return strings.TrimSpace(stepID) + "\x00" + strings.TrimSpace(laneID)
|
||||
}
|
||||
|
||||
func (policy CheckpointExecutionPolicy) forced(stepID, laneID string) bool {
|
||||
_, ok := policy.ForcedLanes[CheckpointLaneKey(stepID, laneID)]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (policy CheckpointExecutionPolicy) requiresReusable(stepID, laneID string) bool {
|
||||
_, ok := policy.RequireReusableLanes[CheckpointLaneKey(stepID, laneID)]
|
||||
return ok
|
||||
}
|
||||
|
||||
func forceCheckpointDecision(policy CheckpointExecutionPolicy, stepID, laneID string, decision CheckpointDecision) CheckpointDecision {
|
||||
if policy.forced(stepID, laneID) {
|
||||
return CheckpointDecision{Category: "forced_recompute", ReasonCode: "recompute_step", Detail: "selected step requires execution"}
|
||||
}
|
||||
return decision
|
||||
}
|
||||
|
||||
func requireReusableCheckpoint(policy CheckpointExecutionPolicy, stepID, laneID string, decision CheckpointDecision) error {
|
||||
if policy.requiresReusable(stepID, laneID) && !decision.Reused {
|
||||
return fmt.Errorf("required reusable checkpoint unavailable for step %q lane %q", strings.TrimSpace(stepID), strings.TrimSpace(laneID))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type SourceCheckpoint struct {
|
||||
@@ -93,6 +152,15 @@ type CheckpointLoader interface {
|
||||
Normalize(laneID string, moduleKey string, dependencies []CheckpointFingerprint) (NormalizeCheckpoint, CheckpointDecision)
|
||||
}
|
||||
|
||||
// StepCheckpointLoader is the step-aware counterpart used by the persistent
|
||||
// checkpoint implementation. Loaders without this optional interface remain
|
||||
// usable by framework callers and test doubles through the legacy methods.
|
||||
type StepCheckpointLoader interface {
|
||||
ExtractForStep(stepID, laneID string, moduleKey string, dependencies []CheckpointFingerprint) (ExtractCheckpoint, CheckpointDecision)
|
||||
MergeForStep(stepID, laneID string, moduleKey string, dependencies []CheckpointFingerprint) (MergeCheckpoint, CheckpointDecision)
|
||||
NormalizeForStep(stepID, laneID string, moduleKey string, dependencies []CheckpointFingerprint) (NormalizeCheckpoint, CheckpointDecision)
|
||||
}
|
||||
|
||||
type noopCheckpointRecorder struct{}
|
||||
type noopCheckpointLoader struct{}
|
||||
|
||||
@@ -136,16 +204,83 @@ func (noopCheckpointRecorder) NormalizeFailed(string, string, []CheckpointFinger
|
||||
|
||||
func (noopCheckpointLoader) Enabled() bool { return false }
|
||||
func (noopCheckpointLoader) Source(string) (SourceCheckpoint, CheckpointDecision) {
|
||||
return SourceCheckpoint{}, CheckpointDecision{Reason: "checkpoint loading disabled"}
|
||||
return SourceCheckpoint{}, CheckpointDecision{Category: "executed", ReasonCode: "loading_disabled", Reason: "checkpoint loading disabled"}
|
||||
}
|
||||
func (noopCheckpointLoader) Extract(string, string, []CheckpointFingerprint) (ExtractCheckpoint, CheckpointDecision) {
|
||||
return ExtractCheckpoint{}, CheckpointDecision{Reason: "checkpoint loading disabled"}
|
||||
return ExtractCheckpoint{}, CheckpointDecision{Category: "executed", ReasonCode: "loading_disabled", Reason: "checkpoint loading disabled"}
|
||||
}
|
||||
func (noopCheckpointLoader) Merge(string, string, []CheckpointFingerprint) (MergeCheckpoint, CheckpointDecision) {
|
||||
return MergeCheckpoint{}, CheckpointDecision{Reason: "checkpoint loading disabled"}
|
||||
return MergeCheckpoint{}, CheckpointDecision{Category: "executed", ReasonCode: "loading_disabled", Reason: "checkpoint loading disabled"}
|
||||
}
|
||||
func (noopCheckpointLoader) Normalize(string, string, []CheckpointFingerprint) (NormalizeCheckpoint, CheckpointDecision) {
|
||||
return NormalizeCheckpoint{}, CheckpointDecision{Reason: "checkpoint loading disabled"}
|
||||
return NormalizeCheckpoint{}, CheckpointDecision{Category: "executed", ReasonCode: "loading_disabled", Reason: "checkpoint loading disabled"}
|
||||
}
|
||||
|
||||
func checkpointExtractRunning(recorder CheckpointRecorder, stepID, laneID, moduleKey string, deps []CheckpointFingerprint) error {
|
||||
if stepAware, ok := recorder.(StepCheckpointRecorder); ok {
|
||||
return stepAware.ExtractRunningForStep(stepID, laneID, moduleKey, deps)
|
||||
}
|
||||
return recorder.ExtractRunning(laneID, moduleKey, deps)
|
||||
}
|
||||
func checkpointExtractSucceeded(recorder CheckpointRecorder, stepID, laneID, moduleKey string, deps []CheckpointFingerprint, outputs []CheckpointArtifact, rejected []contracts.RejectedOutput, warnings []contracts.Warning) error {
|
||||
if stepAware, ok := recorder.(StepCheckpointRecorder); ok {
|
||||
return stepAware.ExtractSucceededForStep(stepID, laneID, moduleKey, deps, outputs, rejected, warnings)
|
||||
}
|
||||
return recorder.ExtractSucceeded(laneID, moduleKey, deps, outputs, rejected, warnings)
|
||||
}
|
||||
func checkpointExtractFailed(recorder CheckpointRecorder, stepID, laneID, moduleKey string, deps []CheckpointFingerprint, err error) error {
|
||||
if stepAware, ok := recorder.(StepCheckpointRecorder); ok {
|
||||
return stepAware.ExtractFailedForStep(stepID, laneID, moduleKey, deps, err)
|
||||
}
|
||||
return recorder.ExtractFailed(laneID, moduleKey, deps, err)
|
||||
}
|
||||
func checkpointMergeRunning(recorder CheckpointRecorder, stepID, laneID, moduleKey string, deps []CheckpointFingerprint) error {
|
||||
if stepAware, ok := recorder.(StepCheckpointRecorder); ok {
|
||||
return stepAware.MergeRunningForStep(stepID, laneID, moduleKey, deps)
|
||||
}
|
||||
return recorder.MergeRunning(laneID, moduleKey, deps)
|
||||
}
|
||||
func checkpointMergeSucceeded(recorder CheckpointRecorder, stepID, laneID, moduleKey string, deps []CheckpointFingerprint, output CheckpointArtifact, warnings []contracts.Warning) error {
|
||||
if stepAware, ok := recorder.(StepCheckpointRecorder); ok {
|
||||
return stepAware.MergeSucceededForStep(stepID, laneID, moduleKey, deps, output, warnings)
|
||||
}
|
||||
return recorder.MergeSucceeded(laneID, moduleKey, deps, output, warnings)
|
||||
}
|
||||
func checkpointMergeRejected(recorder CheckpointRecorder, stepID, laneID, moduleKey string, deps []CheckpointFingerprint, rejected contracts.RejectedOutput) error {
|
||||
if stepAware, ok := recorder.(StepCheckpointRecorder); ok {
|
||||
return stepAware.MergeRejectedForStep(stepID, laneID, moduleKey, deps, rejected)
|
||||
}
|
||||
return recorder.MergeRejected(laneID, moduleKey, deps, rejected)
|
||||
}
|
||||
func checkpointMergeFailed(recorder CheckpointRecorder, stepID, laneID, moduleKey string, deps []CheckpointFingerprint, err error) error {
|
||||
if stepAware, ok := recorder.(StepCheckpointRecorder); ok {
|
||||
return stepAware.MergeFailedForStep(stepID, laneID, moduleKey, deps, err)
|
||||
}
|
||||
return recorder.MergeFailed(laneID, moduleKey, deps, err)
|
||||
}
|
||||
func checkpointNormalizeRunning(recorder CheckpointRecorder, stepID, laneID, moduleKey string, deps []CheckpointFingerprint) error {
|
||||
if stepAware, ok := recorder.(StepCheckpointRecorder); ok {
|
||||
return stepAware.NormalizeRunningForStep(stepID, laneID, moduleKey, deps)
|
||||
}
|
||||
return recorder.NormalizeRunning(laneID, moduleKey, deps)
|
||||
}
|
||||
func checkpointNormalizeSucceeded(recorder CheckpointRecorder, stepID, laneID, moduleKey string, deps []CheckpointFingerprint, output CheckpointArtifact, warnings []contracts.Warning) error {
|
||||
if stepAware, ok := recorder.(StepCheckpointRecorder); ok {
|
||||
return stepAware.NormalizeSucceededForStep(stepID, laneID, moduleKey, deps, output, warnings)
|
||||
}
|
||||
return recorder.NormalizeSucceeded(laneID, moduleKey, deps, output, warnings)
|
||||
}
|
||||
func checkpointNormalizeRejected(recorder CheckpointRecorder, stepID, laneID, moduleKey string, deps []CheckpointFingerprint, rejected contracts.RejectedOutput) error {
|
||||
if stepAware, ok := recorder.(StepCheckpointRecorder); ok {
|
||||
return stepAware.NormalizeRejectedForStep(stepID, laneID, moduleKey, deps, rejected)
|
||||
}
|
||||
return recorder.NormalizeRejected(laneID, moduleKey, deps, rejected)
|
||||
}
|
||||
func checkpointNormalizeFailed(recorder CheckpointRecorder, stepID, laneID, moduleKey string, deps []CheckpointFingerprint, err error) error {
|
||||
if stepAware, ok := recorder.(StepCheckpointRecorder); ok {
|
||||
return stepAware.NormalizeFailedForStep(stepID, laneID, moduleKey, deps, err)
|
||||
}
|
||||
return recorder.NormalizeFailed(laneID, moduleKey, deps, err)
|
||||
}
|
||||
|
||||
func digestFingerprints(name string, digest string) []CheckpointFingerprint {
|
||||
|
||||
Reference in New Issue
Block a user