package agenttask import ( "errors" "fmt" "maps" "reflect" "slices" "strings" "iop/packages/go/agentpolicy" ) const currentSchemaVersion uint32 = StateSchemaVersion var ErrCheckpointInvalid = errors.New("agenttask invalid durable checkpoint") type CheckpointError struct { Code BlockerCode Message string } func (e *CheckpointError) Error() string { return fmt.Sprintf("agenttask: %s: %s", e.Code, e.Message) } func (e *CheckpointError) Unwrap() error { return ErrCheckpointInvalid } var legalWorkTransitions = map[WorkState]map[WorkState]struct{}{ WorkStateObserved: { WorkStateReady: {}, WorkStateCompleted: {}, WorkStateBlocked: {}, WorkStateStopped: {}, }, WorkStateReady: { WorkStatePreparing: {}, WorkStateBlocked: {}, WorkStateStopped: {}, }, WorkStatePreparing: { WorkStateReady: {}, WorkStateDispatching: {}, WorkStateBlocked: {}, WorkStateStopped: {}, }, WorkStateDispatching: { WorkStateReady: {}, WorkStateSubmitted: {}, WorkStateBlocked: {}, WorkStateStopped: {}, }, WorkStateSubmitted: { WorkStateReviewing: {}, WorkStateBlocked: {}, WorkStateStopped: {}, }, WorkStateReviewing: { WorkStateReady: {}, WorkStatePendingIntegration: {}, WorkStateTerminalDeferred: {}, WorkStateBlocked: {}, WorkStateStopped: {}, }, WorkStatePendingIntegration: { WorkStateIntegrating: {}, WorkStateTerminalDeferred: {}, WorkStateBlocked: {}, WorkStateStopped: {}, }, WorkStateIntegrating: { WorkStatePendingIntegration: {}, WorkStateCompleted: {}, WorkStateTerminalDeferred: {}, }, WorkStateBlocked: { WorkStateReady: {}, WorkStateStopped: {}, }, WorkStateTerminalDeferred: { WorkStateReady: {}, }, WorkStateStopped: { WorkStateReady: {}, WorkStateReviewing: {}, WorkStatePendingIntegration: {}, }, WorkStateCompleted: {}, } func CanTransition(from, to WorkState) bool { if from == to { return true } _, ok := legalWorkTransitions[from][to] return ok } func transitionWork(record *WorkRecord, to WorkState) error { if record == nil { return fmt.Errorf("agenttask: nil work record") } if !CanTransition(record.State, to) { return fmt.Errorf("agenttask: illegal work transition %s -> %s", record.State, to) } record.State = to return nil } func validateStartRequest(req StartRequest) error { fields := []struct { name string value string }{ {"command", string(req.CommandID)}, {"project", string(req.ProjectID)}, {"workspace", string(req.WorkspaceID)}, {"milestone", string(req.MilestoneID)}, {"workflow_revision", string(req.WorkflowRevision)}, {"config_revision", string(req.ConfigRevision)}, {"grant_revision", string(req.GrantRevision)}, } for _, field := range fields { if err := validateIdentity(field.name, field.value); err != nil { return err } } return nil } func validateIdentity(field, value string) error { if value == "" || strings.TrimSpace(value) != value || strings.ContainsAny(value, "\x00\r\n") { return &IdentityError{Field: field, Value: value} } return nil } func validateWorkflowSnapshot(snapshot ProjectWorkflowSnapshot) error { if err := validateIdentity("project", string(snapshot.ProjectID)); err != nil { return err } if err := validateIdentity("workspace", string(snapshot.WorkspaceID)); err != nil { return err } if err := validateIdentity("workflow_revision", string(snapshot.Revision)); err != nil { return err } seen := make(map[WorkUnitID]struct{}, len(snapshot.Units)) for _, unit := range snapshot.Units { if err := validateIdentity("work_unit", string(unit.ID)); err != nil { return err } if err := validateIdentity("milestone", string(unit.MilestoneID)); err != nil { return err } if _, ok := seen[unit.ID]; ok { return fmt.Errorf("agenttask: duplicate work identity %q", unit.ID) } seen[unit.ID] = struct{}{} switch unit.IsolationMode { case agentguardIsolationOverlay, agentguardIsolationWorktree, agentguardIsolationClone: default: return fmt.Errorf("agenttask: work %q has invalid isolation mode %q", unit.ID, unit.IsolationMode) } } return nil } // Local aliases avoid making the transition validator depend on string // literals while keeping agentguard as the source of isolation mode values. const ( agentguardIsolationOverlay = "overlay" agentguardIsolationWorktree = "worktree" agentguardIsolationClone = "clone" ) func cloneState(state ManagerState) ManagerState { out := state if out.SchemaVersion == 0 { out.SchemaVersion = currentSchemaVersion } out.Commands = maps.Clone(state.Commands) out.Projects = make(map[ProjectID]ProjectRecord, len(state.Projects)) for id, project := range state.Projects { out.Projects[id] = cloneProject(project) } out.PendingEvents = make(map[string]EventDelivery, len(state.PendingEvents)) for eventID, delivery := range state.PendingEvents { out.PendingEvents[eventID] = cloneEventDelivery(delivery) } out.IntegrationLeases = maps.Clone(state.IntegrationLeases) out.WorkspaceLeases = maps.Clone(state.WorkspaceLeases) if state.DeviceLease != nil { lease := *state.DeviceLease out.DeviceLease = &lease } if out.Commands == nil { out.Commands = make(map[CommandID]CommandRecord) } if out.Projects == nil { out.Projects = make(map[ProjectID]ProjectRecord) } if out.PendingEvents == nil { out.PendingEvents = make(map[string]EventDelivery) } if out.IntegrationLeases == nil { out.IntegrationLeases = make(map[WorkspaceID]LeaseRecord) } if out.WorkspaceLeases == nil { out.WorkspaceLeases = make(map[WorkspaceID]LeaseRecord) } return out } func cloneEventDelivery(delivery EventDelivery) EventDelivery { out := delivery if delivery.Project != nil { project := cloneProject(*delivery.Project) out.Project = &project } if delivery.Work != nil { work := cloneWork(*delivery.Work) out.Work = &work } return out } func cloneProject(project ProjectRecord) ProjectRecord { out := project if project.Intent != nil { intent := *project.Intent out.Intent = &intent } if project.Workflow != nil { workflow := cloneWorkflow(*project.Workflow) out.Workflow = &workflow } out.Works = make(map[WorkUnitID]WorkRecord, len(project.Works)) for id, work := range project.Works { out.Works[id] = cloneWork(work) } if project.Lease != nil { lease := *project.Lease out.Lease = &lease } if project.Blocker != nil { blocker := *project.Blocker out.Blocker = &blocker } return out } func cloneWorkflow(workflow ProjectWorkflowSnapshot) ProjectWorkflowSnapshot { out := workflow out.Units = make([]WorkUnit, len(workflow.Units)) for index, unit := range workflow.Units { out.Units[index] = cloneUnit(unit) } return out } func cloneUnit(unit WorkUnit) WorkUnit { out := unit out.Aliases = slices.Clone(unit.Aliases) out.ExplicitPredecessors = slices.Clone(unit.ExplicitPredecessors) out.DeclaredWriteSet = slices.Clone(unit.DeclaredWriteSet) out.Metadata = maps.Clone(unit.Metadata) return out } func cloneWork(work WorkRecord) WorkRecord { out := work out.Unit = cloneUnit(work.Unit) if work.Target != nil { value := *work.Target out.Target = &value } if work.ContinuationTarget != nil { value := *work.ContinuationTarget out.ContinuationTarget = &value } if work.Isolation != nil { value := *work.Isolation out.Isolation = &value } if work.Submission != nil { value := *work.Submission value.Metadata = maps.Clone(work.Submission.Metadata) value.Locators = slices.Clone(work.Submission.Locators) out.Submission = &value } if work.Review != nil { value := *work.Review if work.Review.ChangeSet != nil { changeSet := *work.Review.ChangeSet value.ChangeSet = &changeSet } out.Review = &value } if work.ChangeSet != nil { value := *work.ChangeSet out.ChangeSet = &value } if work.Integration != nil { value := *work.Integration if work.Integration.CompletionLocator != nil { locator := *work.Integration.CompletionLocator value.CompletionLocator = &locator } if work.Integration.Blocker != nil { blocker := *work.Integration.Blocker value.Blocker = &blocker } out.Integration = &value } if len(work.AttemptObservations) > 0 { out.AttemptObservations = make([]AttemptObservationRecord, len(work.AttemptObservations)) for index, observation := range work.AttemptObservations { out.AttemptObservations[index] = observation out.AttemptObservations[index].Observation = observation.Observation.Clone() } } if work.Blocker != nil { value := *work.Blocker out.Blocker = &value } out.Locators = maps.Clone(work.Locators) out.FailureBudgets = maps.Clone(work.FailureBudgets) if out.Locators == nil { out.Locators = make(map[LocatorKind]LocatorRecord) } if out.FailureBudgets == nil { out.FailureBudgets = make(map[FailureStage]FailureBudgetRecord) } return out } func validateManagerState(state ManagerState) error { if state.SchemaVersion != currentSchemaVersion { return checkpointError( BlockerCorruptCheckpoint, fmt.Sprintf("unsupported state schema %d", state.SchemaVersion), ) } if err := validateLease("device", state.DeviceLease); err != nil { return err } for workspaceID, lease := range state.WorkspaceLeases { if err := validateIdentity("workspace_lease_key", string(workspaceID)); err != nil { return checkpointIdentityError(err) } if err := validateLease("workspace", &lease); err != nil { return err } } for workspaceID, lease := range state.IntegrationLeases { if err := validateIdentity("integration_lease_key", string(workspaceID)); err != nil { return checkpointIdentityError(err) } if err := validateLease("integration", &lease); err != nil { return err } } for commandID, command := range state.Commands { if commandID != command.Intent.CommandID { return checkpointError( BlockerStaleCheckpoint, fmt.Sprintf("command map key %q does not match its intent", commandID), ) } if err := validateStartIntent(command.Intent); err != nil { return err } } for eventID, delivery := range state.PendingEvents { if err := validateEventDelivery(eventID, delivery); err != nil { return err } } for projectID, project := range state.Projects { if projectID != project.ProjectID { return checkpointError( BlockerStaleCheckpoint, fmt.Sprintf("project map key %q does not match record %q", projectID, project.ProjectID), ) } if !validProjectStatus(project.Status) { return checkpointError( BlockerCorruptCheckpoint, fmt.Sprintf("project %q has unsupported status %q", projectID, project.Status), ) } if err := validateIdentity("project", string(project.ProjectID)); err != nil { return checkpointIdentityError(err) } if project.WorkspaceID == "" { if project.Intent != nil || project.Workflow != nil || project.Status != ProjectStatusBlocked { return checkpointError( BlockerStaleCheckpoint, fmt.Sprintf("project %q has no workspace identity", projectID), ) } } else if err := validateIdentity("workspace", string(project.WorkspaceID)); err != nil { return checkpointIdentityError(err) } if project.Intent != nil { if err := validateStartIntent(*project.Intent); err != nil { return err } if project.Intent.ProjectID != project.ProjectID || project.Intent.WorkspaceID != project.WorkspaceID { return checkpointError( BlockerStaleCheckpoint, fmt.Sprintf("project %q intent identity mismatch", projectID), ) } } if err := validateLease("project", project.Lease); err != nil { return err } for workID, work := range project.Works { if workID != work.Unit.ID { return checkpointError( BlockerStaleCheckpoint, fmt.Sprintf("work map key %q does not match record %q", workID, work.Unit.ID), ) } if err := validateWorkCheckpoint(project, work); err != nil { return err } } } return nil } func validateEventDelivery(eventID string, delivery EventDelivery) error { if eventID == "" || delivery.Event.EventID != eventID { return checkpointError( BlockerStaleCheckpoint, fmt.Sprintf("pending event map key %q does not match event %q", eventID, delivery.Event.EventID), ) } if err := validateIdentity("pending_event", eventID); err != nil { return checkpointIdentityError(err) } if !validEventType(delivery.Event.Type) { return checkpointError( BlockerCorruptCheckpoint, fmt.Sprintf("pending event %q has unsupported type %q", eventID, delivery.Event.Type), ) } if err := validateIdentity("pending_event_project", string(delivery.Event.ProjectID)); err != nil { return checkpointIdentityError(err) } if err := validateIdentity("pending_event_revision", string(delivery.EvidenceRevision)); err != nil { return checkpointIdentityError(err) } if delivery.Event.Timestamp.IsZero() { return checkpointError( BlockerCorruptCheckpoint, fmt.Sprintf("pending event %q has no timestamp", eventID), ) } if delivery.Project == nil || delivery.Project.ProjectID != delivery.Event.ProjectID { return checkpointError( BlockerStaleCheckpoint, fmt.Sprintf("pending event %q project evidence mismatch", eventID), ) } if !validProjectStatus(delivery.Project.Status) { return checkpointError( BlockerCorruptCheckpoint, fmt.Sprintf("pending event %q has invalid project status %q", eventID, delivery.Project.Status), ) } if delivery.Project.Intent != nil { if err := validateStartIntent(*delivery.Project.Intent); err != nil { return err } if delivery.Event.CommandID != "" && delivery.Project.Intent.CommandID != delivery.Event.CommandID { return checkpointError( BlockerStaleCheckpoint, fmt.Sprintf("pending event %q command evidence mismatch", eventID), ) } if delivery.Event.WorkflowRevision != "" && delivery.Project.Intent.WorkflowRevision != delivery.Event.WorkflowRevision { return checkpointError( BlockerStaleCheckpoint, fmt.Sprintf("pending event %q workflow evidence mismatch", eventID), ) } } if delivery.Event.WorkspaceID != "" && delivery.Project.WorkspaceID != delivery.Event.WorkspaceID { return checkpointError( BlockerStaleCheckpoint, fmt.Sprintf("pending event %q workspace evidence mismatch", eventID), ) } if delivery.Event.WorkUnitID == "" { if delivery.Work != nil { return checkpointError( BlockerStaleCheckpoint, fmt.Sprintf("pending project event %q unexpectedly has work evidence", eventID), ) } return nil } if delivery.Work == nil || delivery.Work.Unit.ID != delivery.Event.WorkUnitID { return checkpointError( BlockerStaleCheckpoint, fmt.Sprintf("pending event %q work evidence mismatch", eventID), ) } projectWork, ok := delivery.Project.Works[delivery.Event.WorkUnitID] if !ok || !reflect.DeepEqual(projectWork, *delivery.Work) { return checkpointError( BlockerStaleCheckpoint, fmt.Sprintf("pending event %q project and work evidence disagree", eventID), ) } if delivery.Event.AttemptID != "" && delivery.Work.AttemptID != delivery.Event.AttemptID { return checkpointError( BlockerStaleCheckpoint, fmt.Sprintf("pending event %q attempt evidence mismatch", eventID), ) } if delivery.Event.Ordinal != 0 && delivery.Work.DispatchOrdinal != delivery.Event.Ordinal { return checkpointError( BlockerStaleCheckpoint, fmt.Sprintf("pending event %q dispatch evidence mismatch", eventID), ) } if delivery.Event.State != "" && delivery.Work.State != delivery.Event.State { return checkpointError( BlockerStaleCheckpoint, fmt.Sprintf("pending event %q state evidence mismatch", eventID), ) } if delivery.Event.ProviderID != "" && (delivery.Work.Target == nil || delivery.Work.Target.ProviderID != delivery.Event.ProviderID) { return checkpointError( BlockerStaleCheckpoint, fmt.Sprintf("pending event %q provider evidence mismatch", eventID), ) } if delivery.Event.ProfileID != "" && (delivery.Work.Target == nil || delivery.Work.Target.ProfileID != delivery.Event.ProfileID) { return checkpointError( BlockerStaleCheckpoint, fmt.Sprintf("pending event %q profile evidence mismatch", eventID), ) } if delivery.Event.ChangeSetID != "" && (delivery.Work.ChangeSet == nil || delivery.Work.ChangeSet.ID != delivery.Event.ChangeSetID || delivery.Work.ChangeSet.Revision != delivery.Event.ChangeSetRevision) { return checkpointError( BlockerStaleCheckpoint, fmt.Sprintf("pending event %q change-set evidence mismatch", eventID), ) } if delivery.Event.IntegrationAttempt != 0 && delivery.Work.IntegrationAttempt != delivery.Event.IntegrationAttempt { return checkpointError( BlockerStaleCheckpoint, fmt.Sprintf("pending event %q integration-attempt evidence mismatch", eventID), ) } if err := validateWorkCheckpoint(*delivery.Project, *delivery.Work); err != nil { return err } return nil } func locatorForIsolation( project ProjectRecord, work WorkRecord, isolation IsolationIdentity, ) LocatorRecord { return LocatorRecord{ Kind: LocatorOverlay, Opaque: isolation.ID, Revision: isolation.Revision, ProjectID: project.ProjectID, WorkspaceID: project.WorkspaceID, WorkUnitID: work.Unit.ID, AttemptID: work.AttemptID, } } func locatorForChangeSet( project ProjectRecord, work WorkRecord, changeSet ChangeSetIdentity, ) LocatorRecord { return LocatorRecord{ Kind: LocatorChangeSet, Opaque: string(changeSet.ID), Revision: changeSet.Revision, ProjectID: project.ProjectID, WorkspaceID: project.WorkspaceID, WorkUnitID: work.Unit.ID, AttemptID: work.AttemptID, } } func failureStageForState(state WorkState) FailureStage { switch state { case WorkStatePreparing, WorkStateDispatching: return FailureStageDispatch case WorkStateSubmitted, WorkStateReviewing: return FailureStageReview case WorkStatePendingIntegration, WorkStateIntegrating: return FailureStageIntegration default: return FailureStageRecovery } } func (m *Manager) recordFailure( work *WorkRecord, stage FailureStage, blocker Blocker, ) Blocker { if work.FailureBudgets == nil { work.FailureBudgets = make(map[FailureStage]FailureBudgetRecord) } budget := work.FailureBudgets[stage] if budget.Stage == "" { budget = FailureBudgetRecord{ Stage: stage, Limit: m.config.MaxFailureAttempts, } } if budget.Consecutive < budget.Limit { budget.Consecutive++ } budget.LastCode = blocker.Code budget.AttemptID = work.AttemptID budget.UpdatedAt = m.clock.Now() work.FailureBudgets[stage] = budget if budget.Consecutive >= budget.Limit { return Blocker{ Code: BlockerFailureBudgetExhausted, Message: fmt.Sprintf( "%s failure budget exhausted after %d consecutive failures; last=%s: %s", stage, budget.Consecutive, blocker.Code, blocker.Message, ), Retryable: false, } } return blocker } func resetFailure(work *WorkRecord, stage FailureStage) { if work.FailureBudgets == nil { return } delete(work.FailureBudgets, stage) } func validateStartIntent(intent StartIntent) error { for field, value := range map[string]string{ "command": string(intent.CommandID), "project": string(intent.ProjectID), "workspace": string(intent.WorkspaceID), "milestone": string(intent.MilestoneID), "workflow_revision": string(intent.WorkflowRevision), "config_revision": string(intent.ConfigRevision), "grant_revision": string(intent.GrantRevision), } { if err := validateIdentity(field, value); err != nil { return checkpointIdentityError(err) } } return nil } func validateLease(scope string, lease *LeaseRecord) error { if lease == nil { return nil } if err := validateIdentity(scope+"_lease_owner", lease.OwnerID); err != nil { return checkpointIdentityError(err) } if err := validateIdentity(scope+"_lease_token", lease.Token); err != nil { return checkpointIdentityError(err) } if lease.ExpiresAt.IsZero() { return checkpointError( BlockerCorruptCheckpoint, fmt.Sprintf("%s lease has no expiry", scope), ) } return nil } func validateWorkCheckpoint(project ProjectRecord, work WorkRecord) error { if err := validateIdentity("work_unit", string(work.Unit.ID)); err != nil { return checkpointIdentityError(err) } if !validWorkState(work.State) { return checkpointError( BlockerCorruptCheckpoint, fmt.Sprintf("work %q has unsupported state %q", work.Unit.ID, work.State), ) } if work.Attempt > 0 { if work.AttemptID != attemptID(work.Unit.ID, work.Attempt) { return checkpointError( BlockerStaleCheckpoint, fmt.Sprintf("work %q attempt identity does not match its ordinal", work.Unit.ID), ) } } else if work.AttemptID != "" { return checkpointError( BlockerStaleCheckpoint, fmt.Sprintf("work %q has an attempt identity without an attempt ordinal", work.Unit.ID), ) } for kind, locator := range work.Locators { if kind != locator.Kind { return checkpointError( BlockerAmbiguousCheckpoint, fmt.Sprintf("work %q locator key %q does not match kind %q", work.Unit.ID, kind, locator.Kind), ) } if err := validateLocator(project, work, locator); err != nil { return err } } for stage, budget := range work.FailureBudgets { if !validFailureStage(stage) || stage != budget.Stage || budget.Limit == 0 || budget.Consecutive > budget.Limit { return checkpointError( BlockerCorruptCheckpoint, fmt.Sprintf("work %q has invalid %q failure budget", work.Unit.ID, stage), ) } if budget.Consecutive > 0 { if budget.LastCode == "" { return checkpointError( BlockerCorruptCheckpoint, fmt.Sprintf("work %q has a failure budget without a failure code", work.Unit.ID), ) } if err := validateIdentity("failure_attempt", string(budget.AttemptID)); err != nil { return checkpointIdentityError(err) } } } if work.ContinuationTarget != nil { if err := validateTarget(project, *work.ContinuationTarget); err != nil { return checkpointIdentityError(err) } } seenObservations := make(map[AttemptID]struct{}, len(work.AttemptObservations)) for _, observation := range work.AttemptObservations { if err := validateIdentity("observation_attempt", string(observation.AttemptID)); err != nil { return checkpointIdentityError(err) } if _, duplicate := seenObservations[observation.AttemptID]; duplicate { return checkpointError( BlockerCorruptCheckpoint, fmt.Sprintf("work %q repeats failure observation for one attempt", work.Unit.ID), ) } seenObservations[observation.AttemptID] = struct{}{} if err := validateTarget(project, observation.Target); err != nil { return checkpointIdentityError(err) } if !agentpolicy.ValidateAttemptObservation(observation.Observation) { return checkpointError( BlockerCorruptCheckpoint, fmt.Sprintf("work %q has invalid failure observation", work.Unit.ID), ) } } if work.Submission != nil { if work.Submission.ProjectID != project.ProjectID || work.Submission.WorkUnitID != work.Unit.ID || work.Submission.AttemptID != work.AttemptID { return checkpointError( BlockerStaleCheckpoint, fmt.Sprintf("work %q submission identity mismatch", work.Unit.ID), ) } for _, locator := range work.Submission.Locators { if err := validateLocator(project, work, locator); err != nil { return err } } } if work.ChangeSet != nil { if err := validateIdentity("change_set", string(work.ChangeSet.ID)); err != nil { return checkpointIdentityError(err) } if err := validateIdentity("change_set_revision", work.ChangeSet.Revision); err != nil { return checkpointIdentityError(err) } } if locator, ok := work.Locators[LocatorOverlay]; ok { if work.Isolation == nil || locator.Opaque != work.Isolation.ID || locator.Revision != work.Isolation.Revision { return checkpointError( BlockerStaleCheckpoint, fmt.Sprintf("work %q overlay locator does not match isolation identity", work.Unit.ID), ) } } if locator, ok := work.Locators[LocatorChangeSet]; ok { if work.ChangeSet == nil || locator.Opaque != string(work.ChangeSet.ID) || locator.Revision != work.ChangeSet.Revision { return checkpointError( BlockerStaleCheckpoint, fmt.Sprintf("work %q change-set locator does not match change-set identity", work.Unit.ID), ) } } return nil } func validProjectStatus(status ProjectStatus) bool { switch status { case ProjectStatusObserved, ProjectStatusStarted, ProjectStatusRunning, ProjectStatusStopped, ProjectStatusBlocked, ProjectStatusCompleted: return true default: return false } } func validWorkState(state WorkState) bool { switch state { case WorkStateObserved, WorkStateReady, WorkStatePreparing, WorkStateDispatching, WorkStateSubmitted, WorkStateReviewing, WorkStatePendingIntegration, WorkStateIntegrating, WorkStateCompleted, WorkStateTerminalDeferred, WorkStateBlocked, WorkStateStopped: return true default: return false } } func validFailureStage(stage FailureStage) bool { switch stage { case FailureStageDispatch, FailureStageReview, FailureStageIntegration, FailureStageRecovery: return true default: return false } } func validEventType(eventType EventType) bool { switch eventType { case EventObserved, EventManualStart, EventAutoResume, EventStopped, EventDependencyReady, EventDispatchStarted, EventSubmissionAccepted, EventReviewResult, EventFollowup, EventIntegrationResult, EventBlocked, EventCompleted: return true default: return false } } func validateLocator( project ProjectRecord, work WorkRecord, locator LocatorRecord, ) error { switch locator.Kind { case LocatorProcess, LocatorSession, LocatorOverlay, LocatorChangeSet, LocatorCompletion: default: return checkpointError( BlockerCorruptCheckpoint, fmt.Sprintf("work %q has unsupported locator kind %q", work.Unit.ID, locator.Kind), ) } if locator.ProjectID != project.ProjectID || locator.WorkspaceID != project.WorkspaceID || locator.WorkUnitID != work.Unit.ID || locator.AttemptID != work.AttemptID { return checkpointError( BlockerStaleCheckpoint, fmt.Sprintf("work %q %s locator identity mismatch", work.Unit.ID, locator.Kind), ) } if err := validateIdentity(string(locator.Kind)+"_locator", locator.Opaque); err != nil { return checkpointIdentityError(err) } if err := validateIdentity(string(locator.Kind)+"_locator_revision", locator.Revision); err != nil { return checkpointIdentityError(err) } return nil } func checkpointIdentityError(err error) error { return checkpointError(BlockerStaleCheckpoint, err.Error()) } func checkpointError(code BlockerCode, message string) error { return &CheckpointError{Code: code, Message: message} }