146 lines
6.0 KiB
Go
146 lines
6.0 KiB
Go
package contract
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
)
|
|
|
|
// RunReader is the HTTP lookup boundary. Fall back to archive only when the
|
|
// live reader returns ErrNotFound, not on transient live storage failures.
|
|
type RunReader interface {
|
|
GetRun(ctx context.Context, runID string) (RunView, error)
|
|
}
|
|
|
|
type RunListFilter struct {
|
|
SyncKind SyncKind
|
|
Limit int
|
|
}
|
|
|
|
type RunList struct {
|
|
Items []RunView `json:"items"`
|
|
Total int `json:"total"`
|
|
}
|
|
|
|
type RunLister interface {
|
|
ListRuns(ctx context.Context, filter RunListFilter) (RunList, error)
|
|
}
|
|
|
|
type LiveRunReader interface {
|
|
GetRunMeta(ctx context.Context, runID string) (RunMeta, error)
|
|
}
|
|
|
|
type ArchivedRunReader interface {
|
|
GetArchivedRun(ctx context.Context, runID string) (RunArchive, error)
|
|
}
|
|
|
|
type RoundStatsReader interface {
|
|
GetRoundStats(ctx context.Context, runID string) ([]RoundStats, error)
|
|
}
|
|
|
|
// FailurePager returns live details only. An archived summary without live
|
|
// details returns ErrFailureDetailsUnavailable, never a misleading empty page.
|
|
// Items must serialize as [] on empty pages; NextCursor alone signals EOF.
|
|
type FailurePager interface {
|
|
ListFailures(ctx context.Context, runID string, page PageRequest) (FailurePage, error)
|
|
}
|
|
|
|
type ReplayRequester interface {
|
|
RequestReplay(ctx context.Context, request ReplayRequest) (ReplayAccepted, error)
|
|
}
|
|
|
|
type ActionReader interface {
|
|
GetReplayAction(ctx context.Context, actionID string) (ReplayAction, error)
|
|
}
|
|
|
|
// ArchiveWorkDiscovery returns pending/retryable work from a durable index.
|
|
// Terminal run registration must not depend on an in-memory notification.
|
|
type ArchiveWorkDiscovery interface {
|
|
ListArchiveWork(ctx context.Context, page PageRequest) (ArchiveWorkPage, error)
|
|
}
|
|
|
|
// RegisterArchiveWork idempotently schedules a terminal run/version. Since the
|
|
// index can occupy another Redis slot, callers must reconcile registration
|
|
// after a crash between completing the run and updating the index.
|
|
type ArchiveWorkRegistrar interface {
|
|
RegisterArchiveWork(ctx context.Context, work ArchiveWork) error
|
|
}
|
|
|
|
type ArchiveWorkLoader interface {
|
|
LoadArchiveWork(ctx context.Context, work ArchiveWork) (RunArchive, error)
|
|
}
|
|
|
|
// ArchiveRegistrar conditionally records a durable publication against the
|
|
// work version. Repeating the same receipt is a no-op; a different version or
|
|
// conflicting receipt returns ErrConflict. Failure retains discoverable work.
|
|
type ArchiveRegistrar interface {
|
|
RegisterArchive(ctx context.Context, work ArchiveWork, receipt ArchiveReceipt) error
|
|
RegisterArchiveFailure(ctx context.Context, work ArchiveWork, message string) error
|
|
}
|
|
|
|
// ReplayActionCreator atomically reserves (sourceRunId, idempotencyKey) with
|
|
// its fingerprint and action. Same key/request returns the original action
|
|
// regardless of candidate IDs; different request returns ErrConflict. The
|
|
// candidate starts accepted and fixes the target ID before any target writes.
|
|
type ReplayActionCreator interface {
|
|
GetOrCreateReplayAction(ctx context.Context, request ReplayRequest, candidate ReplayAction) (ReplayActionCreation, error)
|
|
}
|
|
|
|
// Discovery includes accepted, processing, cleanup_pending and receipt_pending
|
|
// actions after crashes. Completed/needs_attention actions require no automatic
|
|
// work. Lease acquisition, not listing, chooses an exclusive owner.
|
|
type ReplayWorkDiscovery interface {
|
|
ListReplayWork(ctx context.Context, page PageRequest) (ReplayActionPage, error)
|
|
}
|
|
|
|
type ReplayLeaseStore interface {
|
|
AcquireReplayLease(ctx context.Context, actionID, owner string, ttl time.Duration) (ReplayLease, error)
|
|
RenewReplayLease(ctx context.Context, lease ReplayLease, ttl time.Duration) (ReplayLease, error)
|
|
ReleaseReplayLease(ctx context.Context, lease ReplayLease) error
|
|
}
|
|
|
|
// All updates are fenced and compare ExpectedRevision. Expired/replaced leases
|
|
// return ErrLeaseLost; revision conflicts return ErrConflict. Implementations
|
|
// reject progress regression and preserve immutable action identity.
|
|
type ReplayProgressStore interface {
|
|
UpdateReplayAction(ctx context.Context, update ReplayActionUpdate) (ReplayAction, error)
|
|
}
|
|
|
|
// The implementation must preserve scan continuity while source entries are
|
|
// cleaned. This can use a durable selection/snapshot; deleting records must not
|
|
// make a later cursor skip an unprocessed failure. Tokens are bound to actionID.
|
|
type ReplayFailurePager interface {
|
|
ListReplayFailures(ctx context.Context, actionID string, page PageRequest) (FailureRecordPage, error)
|
|
}
|
|
|
|
// TargetRunCreator is idempotent: retrying the same target preserves counters;
|
|
// incompatible source/operation/month/cycle returns ErrConflict. Replay targets
|
|
// are distinct runs and must not mutate the source's historical FailedCount.
|
|
type TargetRunCreator interface {
|
|
EnsureTargetRun(ctx context.Context, meta RunMeta) (RunMeta, error)
|
|
}
|
|
|
|
// TargetEnqueueResult distinguishes a durable duplicate from a rejected write.
|
|
// A zero NewlyEnqueued count alone is never sufficient evidence for cleanup.
|
|
// Every input TaskID must be durably present on nil error; otherwise return an
|
|
// error and let the caller safely retry the identical batch. Both counts are
|
|
// unique task counts, and their sum equals the number of distinct input IDs.
|
|
type TargetEnqueueResult struct {
|
|
NewlyEnqueued int64
|
|
AlreadyPresent int64
|
|
}
|
|
|
|
type TargetRunEnqueuer interface {
|
|
// Tasks must match the target run and channel and use stable logical IDs.
|
|
EnqueueTargetBatch(ctx context.Context, lease ReplayLease, runID, chanKey string, tasks []Task, nextExecuteAt time.Time) (TargetEnqueueResult, error)
|
|
MarkTargetProducerDone(ctx context.Context, lease ReplayLease, runID string) error
|
|
}
|
|
|
|
// Cleanup is idempotent and conditional on the lease, exact source versions,
|
|
// and durable evidence of target acceptance. It changes pending failures only,
|
|
// never historical failedCount. Conflicts must remain visible for attention.
|
|
// Source keys and action/target keys may occupy different Redis Cluster slots;
|
|
// implementations need recoverable receipts, not cross-slot Lua assumptions.
|
|
type ReplayFailureCleaner interface {
|
|
CleanupReplayFailures(ctx context.Context, cleanup ReplayCleanup) (ReplayCleanupResult, error)
|
|
}
|