496 lines
23 KiB
Go
496 lines
23 KiB
Go
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"`
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
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
|
|
ExtractSucceeded(laneID string, moduleKey string, dependencies []CheckpointFingerprint, outputs []CheckpointArtifact, rejected []contracts.RejectedOutput, warnings []contracts.Warning) error
|
|
ExtractFailed(laneID string, moduleKey string, dependencies []CheckpointFingerprint, err error) error
|
|
MergeRunning(laneID string, moduleKey string, dependencies []CheckpointFingerprint) error
|
|
MergeSucceeded(laneID string, moduleKey string, dependencies []CheckpointFingerprint, output CheckpointArtifact, warnings []contracts.Warning) error
|
|
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
|
|
NormalizeSucceeded(laneID string, moduleKey string, dependencies []CheckpointFingerprint, output CheckpointArtifact, warnings []contracts.Warning) error
|
|
NormalizeRejected(laneID string, moduleKey string, dependencies []CheckpointFingerprint, rejected contracts.RejectedOutput) error
|
|
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 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"
|
|
)
|
|
|
|
type CheckpointDecision struct {
|
|
Reused bool `json:"reused"`
|
|
Category CheckpointDecisionCategory `json:"category,omitempty"`
|
|
ReasonCode CheckpointReasonCode `json:"reason_code,omitempty"`
|
|
Detail string `json:"detail,omitempty"`
|
|
// Reason is retained as a compatibility/debug field for existing callers.
|
|
// Detail and Reason are derived from the stable reason code.
|
|
Reason string `json:"reason,omitempty"`
|
|
}
|
|
|
|
const checkpointDecisionDetailLimit = 512
|
|
|
|
func NewCheckpointDecision(category CheckpointDecisionCategory, reasonCode CheckpointReasonCode) CheckpointDecision {
|
|
return checkpointDecision(category, reasonCode)
|
|
}
|
|
|
|
func checkpointDecision(category CheckpointDecisionCategory, reasonCode CheckpointReasonCode) CheckpointDecision {
|
|
detail := normalizeCheckpointDecisionDetail(checkpointDecisionDetail(reasonCode))
|
|
return CheckpointDecision{
|
|
Reused: category == CheckpointDecisionReused,
|
|
Category: category,
|
|
ReasonCode: reasonCode,
|
|
Detail: detail,
|
|
Reason: detail,
|
|
}
|
|
}
|
|
|
|
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"
|
|
default:
|
|
return "checkpoint decision"
|
|
}
|
|
}
|
|
|
|
func normalizeCheckpointDecisionDetail(detail string) string {
|
|
detail = strings.TrimSpace(strings.ToValidUTF8(detail, "?"))
|
|
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()), " ")
|
|
}
|
|
|
|
type CheckpointEvent struct {
|
|
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"`
|
|
}
|
|
|
|
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(CheckpointDecisionForcedRecompute, CheckpointReasonRecomputeStep)
|
|
}
|
|
return decision
|
|
}
|
|
|
|
func requireReusableCheckpoint(policy CheckpointExecutionPolicy, stepID, laneID string, decision CheckpointDecision) error {
|
|
if policy.requiresReusable(stepID, laneID) && !policy.forced(stepID, laneID) && !decision.Reused {
|
|
return fmt.Errorf("required reusable checkpoint unavailable for step %q lane %q (%s)", strings.TrimSpace(stepID), strings.TrimSpace(laneID), decision.ReasonCode)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// resolveCheckpointDecision applies runner policy and canonical payload
|
|
// validation at the single point where a stage's observable decision is made.
|
|
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) {
|
|
decision = forceCheckpointDecision(policy, stepID, laneID, decision)
|
|
resolution := checkpointResolution{decision: decision}
|
|
if decision.Reused {
|
|
resolution.values = make([]any, 0, len(artifacts))
|
|
resolution.artifacts = make([]CheckpointArtifact, 0, len(artifacts))
|
|
for _, artifact := range artifacts {
|
|
value, hydrated, err := decodeCanonicalCheckpointArtifact(codec, artifact)
|
|
if err != nil {
|
|
decision = checkpointDecision(CheckpointDecisionExecuted, checkpointArtifactReasonCode(err))
|
|
resolution.values = nil
|
|
resolution.artifacts = nil
|
|
break
|
|
}
|
|
resolution.values = append(resolution.values, value)
|
|
resolution.artifacts = append(resolution.artifacts, hydrated)
|
|
}
|
|
}
|
|
resolution.decision = decision
|
|
if output != nil {
|
|
recordCheckpointEvent(output, loader, string(stage), stepID, laneID, moduleKey, decision)
|
|
}
|
|
if err := requireReusableCheckpoint(policy, stepID, laneID, decision); err != nil {
|
|
return resolution, err
|
|
}
|
|
return resolution, nil
|
|
}
|
|
|
|
type SourceCheckpoint struct {
|
|
Document *source.SourceDocument
|
|
}
|
|
|
|
// CheckpointArtifact is the durable, domain-neutral value stored at a lane
|
|
// checkpoint boundary.
|
|
type CheckpointArtifact struct {
|
|
LaneID string
|
|
ModuleKey string
|
|
SourceID string
|
|
ChunkID string
|
|
ChunkIndex int
|
|
ChunkRef source.SourceRef
|
|
Artifact contracts.SerializedArtifact
|
|
SchemaDigest string
|
|
}
|
|
|
|
type ExtractCheckpoint struct {
|
|
Outputs []CheckpointArtifact
|
|
Rejected []contracts.RejectedOutput
|
|
Warnings []contracts.Warning
|
|
}
|
|
|
|
type MergeCheckpoint struct {
|
|
Output CheckpointArtifact
|
|
Warnings []contracts.Warning
|
|
}
|
|
type NormalizeCheckpoint struct {
|
|
Output CheckpointArtifact
|
|
Warnings []contracts.Warning
|
|
}
|
|
|
|
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)
|
|
AcceptedNormalize(stepID, laneID, moduleKey string) (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{}
|
|
|
|
func NoopCheckpointRecorder() CheckpointRecorder { return noopCheckpointRecorder{} }
|
|
func NoopCheckpointLoader() CheckpointLoader { return noopCheckpointLoader{} }
|
|
|
|
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
|
|
}
|
|
func (noopCheckpointRecorder) ExtractSucceeded(string, string, []CheckpointFingerprint, []CheckpointArtifact, []contracts.RejectedOutput, []contracts.Warning) error {
|
|
return nil
|
|
}
|
|
func (noopCheckpointRecorder) ExtractFailed(string, string, []CheckpointFingerprint, error) error {
|
|
return nil
|
|
}
|
|
func (noopCheckpointRecorder) MergeRunning(string, string, []CheckpointFingerprint) error { return nil }
|
|
func (noopCheckpointRecorder) MergeSucceeded(string, string, []CheckpointFingerprint, CheckpointArtifact, []contracts.Warning) error {
|
|
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
|
|
}
|
|
func (noopCheckpointRecorder) NormalizeSucceeded(string, string, []CheckpointFingerprint, CheckpointArtifact, []contracts.Warning) error {
|
|
return nil
|
|
}
|
|
func (noopCheckpointRecorder) NormalizeRejected(string, string, []CheckpointFingerprint, contracts.RejectedOutput) error {
|
|
return nil
|
|
}
|
|
func (noopCheckpointRecorder) NormalizeFailed(string, string, []CheckpointFingerprint, error) error {
|
|
return nil
|
|
}
|
|
|
|
func (noopCheckpointLoader) Enabled() bool { return false }
|
|
func (noopCheckpointLoader) Source(string) (SourceCheckpoint, CheckpointDecision) {
|
|
return SourceCheckpoint{}, checkpointDecision(CheckpointDecisionExecuted, CheckpointReasonLoadingDisabled)
|
|
}
|
|
func (noopCheckpointLoader) Extract(string, string, []CheckpointFingerprint) (ExtractCheckpoint, CheckpointDecision) {
|
|
return ExtractCheckpoint{}, checkpointDecision(CheckpointDecisionExecuted, CheckpointReasonLoadingDisabled)
|
|
}
|
|
func (noopCheckpointLoader) Merge(string, string, []CheckpointFingerprint) (MergeCheckpoint, CheckpointDecision) {
|
|
return MergeCheckpoint{}, checkpointDecision(CheckpointDecisionExecuted, CheckpointReasonLoadingDisabled)
|
|
}
|
|
func (noopCheckpointLoader) Normalize(string, string, []CheckpointFingerprint) (NormalizeCheckpoint, CheckpointDecision) {
|
|
return NormalizeCheckpoint{}, checkpointDecision(CheckpointDecisionExecuted, CheckpointReasonLoadingDisabled)
|
|
}
|
|
func (noopCheckpointLoader) AcceptedNormalize(string, string, string) (NormalizeCheckpoint, CheckpointDecision) {
|
|
return NormalizeCheckpoint{}, checkpointDecision(CheckpointDecisionExecuted, CheckpointReasonLoadingDisabled)
|
|
}
|
|
|
|
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 {
|
|
digest = strings.TrimSpace(digest)
|
|
if digest == "" {
|
|
return nil
|
|
}
|
|
return []CheckpointFingerprint{{Name: name, Value: digest}}
|
|
}
|
|
|
|
func joinedChunkDigest(chunks []source.Chunk) (string, error) {
|
|
if len(chunks) == 0 {
|
|
return "", nil
|
|
}
|
|
values := make([]string, 0, len(chunks))
|
|
for _, chunk := range chunks {
|
|
digest, err := source.DigestChunk(chunk)
|
|
if err != nil {
|
|
return "", fmt.Errorf("digest chunk %q: %w", chunk.ID, err)
|
|
}
|
|
values = append(values, chunk.ID+"="+digest)
|
|
}
|
|
sort.Strings(values)
|
|
sum := sha256.Sum256([]byte(strings.Join(values, "\n")))
|
|
return "sha256:" + hex.EncodeToString(sum[:]), nil
|
|
}
|
|
|
|
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[:])
|
|
}
|