2026-07-08 02:46:38 +00:00
package pipeline
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"sort"
"strings"
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
)
type CheckpointFingerprint struct {
Name string ` json:"name" `
Value string ` json:"value" `
}
2026-07-20 15:14:41 -05:00
// CheckpointFingerprintProvider supplies stable, non-secret semantic identity
// for a prepared module or validator. Values must not contain source content,
// credentials, local paths, timestamps, or other invocation-specific data.
type CheckpointFingerprintProvider interface {
CheckpointFingerprints ( ) [ ] CheckpointFingerprint
}
2026-07-08 02:46:38 +00:00
type CheckpointRecorder interface {
SourceRunning ( moduleKey string ) error
SourceSucceeded ( moduleKey string , doc * source . SourceDocument ) error
SourceFailed ( moduleKey string , err error ) error
ExtractRunning ( laneID string , moduleKey string , dependencies [ ] CheckpointFingerprint ) error
2026-08-27 16:40:17 +00:00
ExtractSucceeded ( laneID string , moduleKey string , dependencies [ ] CheckpointFingerprint , outputs [ ] CheckpointArtifact , rejected [ ] contracts . RejectedOutput ) error
2026-07-08 02:46:38 +00:00
ExtractFailed ( laneID string , moduleKey string , dependencies [ ] CheckpointFingerprint , err error ) error
MergeRunning ( laneID string , moduleKey string , dependencies [ ] CheckpointFingerprint ) error
2026-08-27 16:40:17 +00:00
MergeSucceeded ( laneID string , moduleKey string , dependencies [ ] CheckpointFingerprint , output CheckpointArtifact ) error
2026-07-08 02:46:38 +00:00
MergeRejected ( laneID string , moduleKey string , dependencies [ ] CheckpointFingerprint , rejected contracts . RejectedOutput ) error
MergeFailed ( laneID string , moduleKey string , dependencies [ ] CheckpointFingerprint , err error ) error
NormalizeRunning ( laneID string , moduleKey string , dependencies [ ] CheckpointFingerprint ) error
2026-08-27 16:40:17 +00:00
NormalizeSucceeded ( laneID string , moduleKey string , dependencies [ ] CheckpointFingerprint , output CheckpointArtifact ) error
2026-07-08 02:46:38 +00:00
NormalizeRejected ( laneID string , moduleKey string , dependencies [ ] CheckpointFingerprint , rejected contracts . RejectedOutput ) error
NormalizeFailed ( laneID string , moduleKey string , dependencies [ ] CheckpointFingerprint , err error ) error
}
2026-07-21 21:59:48 +00:00
// 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
2026-08-27 16:40:17 +00:00
ExtractSucceededForStep ( stepID , laneID string , moduleKey string , dependencies [ ] CheckpointFingerprint , outputs [ ] CheckpointArtifact , rejected [ ] contracts . RejectedOutput ) error
2026-07-21 21:59:48 +00:00
ExtractFailedForStep ( stepID , laneID string , moduleKey string , dependencies [ ] CheckpointFingerprint , err error ) error
MergeRunningForStep ( stepID , laneID string , moduleKey string , dependencies [ ] CheckpointFingerprint ) error
2026-08-27 16:40:17 +00:00
MergeSucceededForStep ( stepID , laneID string , moduleKey string , dependencies [ ] CheckpointFingerprint , output CheckpointArtifact ) error
2026-07-21 21:59:48 +00:00
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
2026-08-27 16:40:17 +00:00
NormalizeSucceededForStep ( stepID , laneID string , moduleKey string , dependencies [ ] CheckpointFingerprint , output CheckpointArtifact ) error
2026-07-21 21:59:48 +00:00
NormalizeRejectedForStep ( stepID , laneID string , moduleKey string , dependencies [ ] CheckpointFingerprint , rejected contracts . RejectedOutput ) error
NormalizeFailedForStep ( stepID , laneID string , moduleKey string , dependencies [ ] CheckpointFingerprint , err error ) error
}
2026-07-22 02:13:50 +00:00
type CheckpointDecisionCategory string
const (
CheckpointDecisionExecuted CheckpointDecisionCategory = "executed"
CheckpointDecisionReused CheckpointDecisionCategory = "reused"
CheckpointDecisionForcedRecompute CheckpointDecisionCategory = "forced_recompute"
CheckpointDecisionDependencyInvalidated CheckpointDecisionCategory = "dependency_invalidated"
)
type CheckpointReasonCode string
const (
CheckpointReasonLoadingDisabled CheckpointReasonCode = "loading_disabled"
CheckpointReasonMissing CheckpointReasonCode = "checkpoint_missing"
CheckpointReasonPathInvalid CheckpointReasonCode = "checkpoint_path_invalid"
CheckpointReasonReadFailed CheckpointReasonCode = "checkpoint_read_failed"
CheckpointReasonDecodeFailed CheckpointReasonCode = "checkpoint_decode_failed"
CheckpointReasonWorkspaceSchemaIncompatible CheckpointReasonCode = "workspace_schema_incompatible"
CheckpointReasonIdentityMismatch CheckpointReasonCode = "identity_mismatch"
CheckpointReasonStageMismatch CheckpointReasonCode = "stage_mismatch"
CheckpointReasonStepMismatch CheckpointReasonCode = "step_mismatch"
CheckpointReasonLaneMismatch CheckpointReasonCode = "lane_mismatch"
CheckpointReasonModuleMismatch CheckpointReasonCode = "module_mismatch"
CheckpointReasonStatusNotReusable CheckpointReasonCode = "status_not_reusable"
CheckpointReasonDependencyMismatch CheckpointReasonCode = "dependency_mismatch"
CheckpointReasonArtifactPayloadInvalid CheckpointReasonCode = "artifact_payload_invalid"
CheckpointReasonArtifactDigestMismatch CheckpointReasonCode = "artifact_digest_mismatch"
CheckpointReasonArtifactCodecIncompatible CheckpointReasonCode = "artifact_codec_incompatible"
CheckpointReasonArtifactNotCanonical CheckpointReasonCode = "artifact_not_canonical"
CheckpointReasonReused CheckpointReasonCode = "checkpoint_reused"
CheckpointReasonAcceptedArtifactReused CheckpointReasonCode = "accepted_artifact_reused"
CheckpointReasonRecomputeStep CheckpointReasonCode = "recompute_step"
2026-08-27 11:50:37 +00:00
CheckpointReasonValidationIncompleteLineage CheckpointReasonCode = "validation_incomplete_lineage"
2026-07-22 02:13:50 +00:00
)
2026-07-08 03:02:50 +00:00
type CheckpointDecision struct {
2026-07-22 02:13:50 +00:00
Reused bool ` json:"reused" `
Category CheckpointDecisionCategory ` json:"category,omitempty" `
ReasonCode CheckpointReasonCode ` json:"reason_code,omitempty" `
Detail string ` json:"detail,omitempty" `
2026-07-21 21:59:48 +00:00
// Reason is retained as a compatibility/debug field for existing callers.
2026-07-22 14:24:03 +00:00
// Detail and Reason are derived from the stable reason code.
2026-07-08 03:02:50 +00:00
Reason string ` json:"reason,omitempty" `
}
2026-07-22 02:13:50 +00:00
const checkpointDecisionDetailLimit = 512
2026-07-22 14:24:03 +00:00
func NewCheckpointDecision ( category CheckpointDecisionCategory , reasonCode CheckpointReasonCode ) CheckpointDecision {
return checkpointDecision ( category , reasonCode )
2026-07-22 02:13:50 +00:00
}
2026-07-22 14:24:03 +00:00
func checkpointDecision ( category CheckpointDecisionCategory , reasonCode CheckpointReasonCode ) CheckpointDecision {
detail := normalizeCheckpointDecisionDetail ( checkpointDecisionDetail ( reasonCode ) )
2026-07-22 02:13:50 +00:00
return CheckpointDecision {
Reused : category == CheckpointDecisionReused ,
Category : category ,
ReasonCode : reasonCode ,
Detail : detail ,
Reason : detail ,
}
}
2026-07-22 14:24:03 +00:00
func checkpointDecisionDetail ( reasonCode CheckpointReasonCode ) string {
switch reasonCode {
case CheckpointReasonLoadingDisabled :
return "checkpoint loading is disabled"
case CheckpointReasonMissing :
return "checkpoint artifact is missing"
case CheckpointReasonPathInvalid :
return "checkpoint location is invalid"
case CheckpointReasonReadFailed :
return "checkpoint artifact could not be read"
case CheckpointReasonDecodeFailed :
return "checkpoint artifact could not be decoded"
case CheckpointReasonWorkspaceSchemaIncompatible :
return "checkpoint workspace schema is incompatible"
case CheckpointReasonIdentityMismatch :
return "checkpoint identity does not match the current invocation"
case CheckpointReasonStageMismatch :
return "checkpoint stage does not match"
case CheckpointReasonStepMismatch :
return "checkpoint step does not match"
case CheckpointReasonLaneMismatch :
return "checkpoint lane does not match"
case CheckpointReasonModuleMismatch :
return "checkpoint module does not match"
case CheckpointReasonStatusNotReusable :
return "checkpoint status is not reusable"
case CheckpointReasonDependencyMismatch :
return "checkpoint dependencies do not match"
case CheckpointReasonArtifactPayloadInvalid :
return "checkpoint artifact payload is invalid"
case CheckpointReasonArtifactDigestMismatch :
return "checkpoint artifact digest does not match"
case CheckpointReasonArtifactCodecIncompatible :
return "checkpoint artifact is incompatible with the registered codec"
case CheckpointReasonArtifactNotCanonical :
return "checkpoint artifact is not canonical"
case CheckpointReasonReused :
return "checkpoint is reusable"
case CheckpointReasonAcceptedArtifactReused :
return "accepted normalized artifact is reusable"
case CheckpointReasonRecomputeStep :
return "selected step requires execution"
2026-08-27 11:50:37 +00:00
case CheckpointReasonValidationIncompleteLineage :
return "checkpoint reuse is disabled by validation-incomplete input lineage"
2026-07-22 14:24:03 +00:00
default :
return "checkpoint decision"
2026-07-22 02:13:50 +00:00
}
2026-07-22 14:24:03 +00:00
}
func normalizeCheckpointDecisionDetail ( detail string ) string {
detail = strings . TrimSpace ( strings . ToValidUTF8 ( detail , "?" ) )
2026-07-22 02:13:50 +00:00
var b strings . Builder
for _ , r := range detail {
if r < 0x20 || r == 0x7f {
r = ' '
}
if b . Len ( ) + len ( string ( r ) ) > checkpointDecisionDetailLimit {
break
}
b . WriteRune ( r )
}
return strings . Join ( strings . Fields ( b . String ( ) ) , " " )
}
2026-07-08 03:02:50 +00:00
type CheckpointEvent struct {
2026-07-22 02:13:50 +00:00
Stage string ` json:"stage" `
StepID string ` json:"step_id,omitempty" `
LaneID string ` json:"lane_id,omitempty" `
ModuleKey string ` json:"module_key,omitempty" `
Action CheckpointDecisionCategory ` json:"action" `
Category CheckpointDecisionCategory ` json:"category,omitempty" `
ReasonCode CheckpointReasonCode ` json:"reason_code,omitempty" `
Detail string ` json:"detail,omitempty" `
Reason string ` json:"reason,omitempty" `
2026-07-21 21:59:48 +00:00
}
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 ) {
2026-07-22 14:24:03 +00:00
return checkpointDecision ( CheckpointDecisionForcedRecompute , CheckpointReasonRecomputeStep )
2026-07-21 21:59:48 +00:00
}
return decision
}
func requireReusableCheckpoint ( policy CheckpointExecutionPolicy , stepID , laneID string , decision CheckpointDecision ) error {
2026-07-22 02:27:19 +00:00
if policy . requiresReusable ( stepID , laneID ) && ! policy . forced ( stepID , laneID ) && ! decision . Reused {
2026-07-22 02:13:50 +00:00
return fmt . Errorf ( "required reusable checkpoint unavailable for step %q lane %q (%s)" , strings . TrimSpace ( stepID ) , strings . TrimSpace ( laneID ) , decision . ReasonCode )
2026-07-21 21:59:48 +00:00
}
return nil
2026-07-08 03:02:50 +00:00
}
2026-07-22 02:03:27 +00:00
// resolveCheckpointDecision applies runner policy and canonical payload
// validation at the single point where a stage's observable decision is made.
2026-07-22 14:24:03 +00:00
type checkpointResolution struct {
decision CheckpointDecision
values [ ] any
artifacts [ ] CheckpointArtifact
}
func resolveCheckpointDecision ( output * RunOutput , loader CheckpointLoader , policy CheckpointExecutionPolicy , stage ModuleStage , stepID , laneID , moduleKey string , decision CheckpointDecision , codec artifactCodecEntry , artifacts [ ] CheckpointArtifact ) ( checkpointResolution , error ) {
2026-07-22 02:03:27 +00:00
decision = forceCheckpointDecision ( policy , stepID , laneID , decision )
2026-07-22 14:24:03 +00:00
resolution := checkpointResolution { decision : decision }
2026-07-22 02:03:27 +00:00
if decision . Reused {
2026-07-22 14:24:03 +00:00
resolution . values = make ( [ ] any , 0 , len ( artifacts ) )
resolution . artifacts = make ( [ ] CheckpointArtifact , 0 , len ( artifacts ) )
2026-07-22 02:03:27 +00:00
for _ , artifact := range artifacts {
2026-07-22 14:24:03 +00:00
value , hydrated , err := decodeCanonicalCheckpointArtifact ( codec , artifact )
if err != nil {
decision = checkpointDecision ( CheckpointDecisionExecuted , checkpointArtifactReasonCode ( err ) )
resolution . values = nil
resolution . artifacts = nil
2026-07-22 02:03:27 +00:00
break
}
2026-07-22 14:24:03 +00:00
resolution . values = append ( resolution . values , value )
resolution . artifacts = append ( resolution . artifacts , hydrated )
2026-07-22 02:03:27 +00:00
}
}
2026-07-22 14:24:03 +00:00
resolution . decision = decision
2026-07-22 02:03:27 +00:00
if output != nil {
recordCheckpointEvent ( output , loader , string ( stage ) , stepID , laneID , moduleKey , decision )
}
2026-07-22 02:13:50 +00:00
if err := requireReusableCheckpoint ( policy , stepID , laneID , decision ) ; err != nil {
2026-07-22 14:24:03 +00:00
return resolution , err
2026-07-22 02:13:50 +00:00
}
2026-07-22 14:24:03 +00:00
return resolution , nil
2026-07-22 02:03:27 +00:00
}
2026-07-08 03:02:50 +00:00
type SourceCheckpoint struct {
Document * source . SourceDocument
}
2026-07-17 08:13:08 +00:00
// CheckpointArtifact is the durable, domain-neutral value stored at a lane
// checkpoint boundary.
type CheckpointArtifact struct {
2026-07-17 07:33:49 +00:00
LaneID string
ModuleKey string
SourceID string
ChunkID string
ChunkIndex int
ChunkRef source . SourceRef
Artifact contracts . SerializedArtifact
SchemaDigest string
2026-08-27 15:40:45 +00:00
Diagnostics [ ] CheckpointDiagnostic
}
// CheckpointDiagnostic stores a producer-local diagnostic alongside a
// reusable artifact. The runner supplies the current run's origin when it
// promotes this value into a diagnostic group.
type CheckpointDiagnostic struct {
Diagnostic contracts . ProducerDiagnostic ` json:"diagnostic" `
ValidatorKey string ` json:"validator_key,omitempty" `
2026-07-17 07:33:49 +00:00
}
2026-07-17 08:13:08 +00:00
type ExtractCheckpoint struct {
Outputs [ ] CheckpointArtifact
2026-07-17 07:33:49 +00:00
Rejected [ ] contracts . RejectedOutput
}
2026-07-17 08:13:08 +00:00
type MergeCheckpoint struct {
2026-08-27 16:40:17 +00:00
Output CheckpointArtifact
2026-07-17 07:33:49 +00:00
}
2026-07-17 08:13:08 +00:00
type NormalizeCheckpoint struct {
2026-08-27 16:40:17 +00:00
Output CheckpointArtifact
2026-07-17 07:33:49 +00:00
}
2026-07-08 03:02:50 +00:00
type CheckpointLoader interface {
Enabled ( ) bool
Source ( moduleKey string ) ( SourceCheckpoint , CheckpointDecision )
Extract ( laneID string , moduleKey string , dependencies [ ] CheckpointFingerprint ) ( ExtractCheckpoint , CheckpointDecision )
Merge ( laneID string , moduleKey string , dependencies [ ] CheckpointFingerprint ) ( MergeCheckpoint , CheckpointDecision )
Normalize ( laneID string , moduleKey string , dependencies [ ] CheckpointFingerprint ) ( NormalizeCheckpoint , CheckpointDecision )
2026-07-22 02:27:19 +00:00
AcceptedNormalize ( stepID , laneID , moduleKey string ) ( NormalizeCheckpoint , CheckpointDecision )
2026-07-08 03:02:50 +00:00
}
2026-07-21 21:59:48 +00:00
// 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 )
}
2026-07-08 02:46:38 +00:00
type noopCheckpointRecorder struct { }
2026-07-08 03:02:50 +00:00
type noopCheckpointLoader struct { }
2026-07-08 02:46:38 +00:00
func NoopCheckpointRecorder ( ) CheckpointRecorder { return noopCheckpointRecorder { } }
2026-07-08 03:02:50 +00:00
func NoopCheckpointLoader ( ) CheckpointLoader { return noopCheckpointLoader { } }
2026-07-08 02:46:38 +00:00
func ( noopCheckpointRecorder ) SourceRunning ( string ) error { return nil }
func ( noopCheckpointRecorder ) SourceSucceeded ( string , * source . SourceDocument ) error { return nil }
func ( noopCheckpointRecorder ) SourceFailed ( string , error ) error { return nil }
func ( noopCheckpointRecorder ) ExtractRunning ( string , string , [ ] CheckpointFingerprint ) error {
return nil
}
2026-08-27 16:40:17 +00:00
func ( noopCheckpointRecorder ) ExtractSucceeded ( string , string , [ ] CheckpointFingerprint , [ ] CheckpointArtifact , [ ] contracts . RejectedOutput ) error {
2026-07-08 02:46:38 +00:00
return nil
}
func ( noopCheckpointRecorder ) ExtractFailed ( string , string , [ ] CheckpointFingerprint , error ) error {
return nil
}
func ( noopCheckpointRecorder ) MergeRunning ( string , string , [ ] CheckpointFingerprint ) error { return nil }
2026-08-27 16:40:17 +00:00
func ( noopCheckpointRecorder ) MergeSucceeded ( string , string , [ ] CheckpointFingerprint , CheckpointArtifact ) error {
2026-07-08 02:46:38 +00:00
return nil
}
func ( noopCheckpointRecorder ) MergeRejected ( string , string , [ ] CheckpointFingerprint , contracts . RejectedOutput ) error {
return nil
}
func ( noopCheckpointRecorder ) MergeFailed ( string , string , [ ] CheckpointFingerprint , error ) error {
return nil
}
func ( noopCheckpointRecorder ) NormalizeRunning ( string , string , [ ] CheckpointFingerprint ) error {
return nil
}
2026-08-27 16:40:17 +00:00
func ( noopCheckpointRecorder ) NormalizeSucceeded ( string , string , [ ] CheckpointFingerprint , CheckpointArtifact ) error {
2026-07-08 02:46:38 +00:00
return nil
}
func ( noopCheckpointRecorder ) NormalizeRejected ( string , string , [ ] CheckpointFingerprint , contracts . RejectedOutput ) error {
return nil
}
func ( noopCheckpointRecorder ) NormalizeFailed ( string , string , [ ] CheckpointFingerprint , error ) error {
return nil
}
2026-07-08 03:02:50 +00:00
func ( noopCheckpointLoader ) Enabled ( ) bool { return false }
func ( noopCheckpointLoader ) Source ( string ) ( SourceCheckpoint , CheckpointDecision ) {
2026-07-22 14:24:03 +00:00
return SourceCheckpoint { } , checkpointDecision ( CheckpointDecisionExecuted , CheckpointReasonLoadingDisabled )
2026-07-08 03:02:50 +00:00
}
func ( noopCheckpointLoader ) Extract ( string , string , [ ] CheckpointFingerprint ) ( ExtractCheckpoint , CheckpointDecision ) {
2026-07-22 14:24:03 +00:00
return ExtractCheckpoint { } , checkpointDecision ( CheckpointDecisionExecuted , CheckpointReasonLoadingDisabled )
2026-07-08 03:02:50 +00:00
}
func ( noopCheckpointLoader ) Merge ( string , string , [ ] CheckpointFingerprint ) ( MergeCheckpoint , CheckpointDecision ) {
2026-07-22 14:24:03 +00:00
return MergeCheckpoint { } , checkpointDecision ( CheckpointDecisionExecuted , CheckpointReasonLoadingDisabled )
2026-07-08 03:02:50 +00:00
}
func ( noopCheckpointLoader ) Normalize ( string , string , [ ] CheckpointFingerprint ) ( NormalizeCheckpoint , CheckpointDecision ) {
2026-07-22 14:24:03 +00:00
return NormalizeCheckpoint { } , checkpointDecision ( CheckpointDecisionExecuted , CheckpointReasonLoadingDisabled )
2026-07-21 21:59:48 +00:00
}
2026-07-22 02:27:19 +00:00
func ( noopCheckpointLoader ) AcceptedNormalize ( string , string , string ) ( NormalizeCheckpoint , CheckpointDecision ) {
2026-07-22 14:24:03 +00:00
return NormalizeCheckpoint { } , checkpointDecision ( CheckpointDecisionExecuted , CheckpointReasonLoadingDisabled )
2026-07-22 02:27:19 +00:00
}
2026-07-21 21:59:48 +00:00
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 )
}
2026-08-27 16:40:17 +00:00
func checkpointExtractSucceeded ( recorder CheckpointRecorder , stepID , laneID , moduleKey string , deps [ ] CheckpointFingerprint , outputs [ ] CheckpointArtifact , rejected [ ] contracts . RejectedOutput ) error {
2026-07-21 21:59:48 +00:00
if stepAware , ok := recorder . ( StepCheckpointRecorder ) ; ok {
2026-08-27 16:40:17 +00:00
return stepAware . ExtractSucceededForStep ( stepID , laneID , moduleKey , deps , outputs , rejected )
2026-07-21 21:59:48 +00:00
}
2026-08-27 16:40:17 +00:00
return recorder . ExtractSucceeded ( laneID , moduleKey , deps , outputs , rejected )
2026-07-21 21:59:48 +00:00
}
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 )
}
2026-08-27 16:40:17 +00:00
func checkpointMergeSucceeded ( recorder CheckpointRecorder , stepID , laneID , moduleKey string , deps [ ] CheckpointFingerprint , output CheckpointArtifact ) error {
2026-07-21 21:59:48 +00:00
if stepAware , ok := recorder . ( StepCheckpointRecorder ) ; ok {
2026-08-27 16:40:17 +00:00
return stepAware . MergeSucceededForStep ( stepID , laneID , moduleKey , deps , output )
2026-07-21 21:59:48 +00:00
}
2026-08-27 16:40:17 +00:00
return recorder . MergeSucceeded ( laneID , moduleKey , deps , output )
2026-07-21 21:59:48 +00:00
}
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 )
}
2026-08-27 16:40:17 +00:00
func checkpointNormalizeSucceeded ( recorder CheckpointRecorder , stepID , laneID , moduleKey string , deps [ ] CheckpointFingerprint , output CheckpointArtifact ) error {
2026-07-21 21:59:48 +00:00
if stepAware , ok := recorder . ( StepCheckpointRecorder ) ; ok {
2026-08-27 16:40:17 +00:00
return stepAware . NormalizeSucceededForStep ( stepID , laneID , moduleKey , deps , output )
2026-07-21 21:59:48 +00:00
}
2026-08-27 16:40:17 +00:00
return recorder . NormalizeSucceeded ( laneID , moduleKey , deps , output )
2026-07-21 21:59:48 +00:00
}
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 )
2026-07-08 03:02:50 +00:00
}
2026-07-08 02:46:38 +00:00
func digestFingerprints ( name string , digest string ) [ ] CheckpointFingerprint {
digest = strings . TrimSpace ( digest )
if digest == "" {
return nil
}
return [ ] CheckpointFingerprint { { Name : name , Value : digest } }
}
2026-07-17 05:32:11 +00:00
func joinedChunkDigest ( chunks [ ] source . Chunk ) ( string , error ) {
2026-07-08 02:46:38 +00:00
if len ( chunks ) == 0 {
2026-07-17 05:32:11 +00:00
return "" , nil
2026-07-08 02:46:38 +00:00
}
values := make ( [ ] string , 0 , len ( chunks ) )
for _ , chunk := range chunks {
2026-07-17 05:32:11 +00:00
digest , err := source . DigestChunk ( chunk )
if err != nil {
return "" , fmt . Errorf ( "digest chunk %q: %w" , chunk . ID , err )
}
values = append ( values , chunk . ID + "=" + digest )
2026-07-08 02:46:38 +00:00
}
sort . Strings ( values )
sum := sha256 . Sum256 ( [ ] byte ( strings . Join ( values , "\n" ) ) )
2026-07-17 05:32:11 +00:00
return "sha256:" + hex . EncodeToString ( sum [ : ] ) , nil
2026-07-08 02:46:38 +00:00
}
func normalizeCheckpointFingerprints ( values [ ] CheckpointFingerprint ) [ ] CheckpointFingerprint {
if len ( values ) == 0 {
return nil
}
byName := make ( map [ string ] string , len ( values ) )
for _ , value := range values {
name := strings . TrimSpace ( value . Name )
fingerprint := strings . TrimSpace ( value . Value )
if name == "" || fingerprint == "" {
continue
}
byName [ name ] = fingerprint
}
if len ( byName ) == 0 {
return nil
}
names := make ( [ ] string , 0 , len ( byName ) )
for name := range byName {
names = append ( names , name )
}
sort . Strings ( names )
out := make ( [ ] CheckpointFingerprint , 0 , len ( names ) )
for _ , name := range names {
out = append ( out , CheckpointFingerprint { Name : name , Value : byName [ name ] } )
}
return out
}
func checkpointContentDigest ( content [ ] byte ) string {
sum := sha256 . Sum256 ( content )
return "sha256:" + hex . EncodeToString ( sum [ : ] )
}