Integrate extraction lifecycle and resume validation

This commit is contained in:
2026-08-10 00:14:46 +00:00
parent 1f16a85330
commit bba582b4ca
23 changed files with 883 additions and 50 deletions

View File

@@ -3,6 +3,8 @@ package stage
import (
"context"
"log/slog"
"strings"
"unicode/utf8"
"gitea.maximumdirect.net/eric/narratio/internal/adapters/audita"
"gitea.maximumdirect.net/eric/narratio/internal/adapters/notarius"
@@ -46,6 +48,48 @@ type Stage interface {
Run(ctx context.Context, env *Env, m *manifest.Manifest) (*StageResult, error)
}
const maxResumeReasonLength = 512
// ResumeValidation reports whether a previously succeeded stage can be reused.
type ResumeValidation struct {
Resumable bool
Reason string
}
// Normalized returns a result with a bounded reason and no reason on success.
func (r ResumeValidation) Normalized() ResumeValidation {
if r.Resumable {
return Resumable()
}
return NonResumable(r.Reason)
}
// ResumeValidator is implemented by stages that validate persisted success before reuse.
type ResumeValidator interface {
ValidateResume(ctx context.Context, env *Env, m *manifest.Manifest) (ResumeValidation, error)
}
// Resumable reports a successful resume validation.
func Resumable() ResumeValidation {
return ResumeValidation{Resumable: true}
}
// NonResumable reports a bounded reason that persisted success must be rerun.
func NonResumable(reason string) ResumeValidation {
reason = strings.TrimSpace(reason)
if reason == "" {
reason = "persisted stage result is not reusable"
}
if len(reason) > maxResumeReasonLength {
cutoff := maxResumeReasonLength
for cutoff > 0 && !utf8.ValidString(reason[:cutoff]) {
cutoff--
}
reason = reason[:cutoff]
}
return ResumeValidation{Reason: reason}
}
// StageDisposition describes the outcome of a stage that returned without an error.
type StageDisposition string