// Package preflight checks resolved processing inputs and prepares output resources. package preflight import ( "context" "fmt" "gitea.maximumdirect.net/eric/audita/v3/internal/core/config" "gitea.maximumdirect.net/eric/audita/v3/internal/core/diagnostics" "gitea.maximumdirect.net/eric/audita/v3/internal/core/glossary" "gitea.maximumdirect.net/eric/audita/v3/internal/core/modulecatalog" "gitea.maximumdirect.net/eric/audita/v3/internal/core/schema" ) // Input contains already acquired application and transcript inputs. type Input struct { InspectProfile func(context.Context, string) error Config *config.Config Transcript *schema.SourceTranscript Output diagnostics.OutputSelection InitialReports diagnostics.ReportBundle FailedReports func(error, []diagnostics.FileFailure) diagnostics.ReportBundle Debug *DebugInput } type DebugInput struct { WorkDir string RunID string Metadata diagnostics.InvocationMetadata EffectiveConfig []byte Secrets []string } type Prepared struct { Output *diagnostics.OutputSession Debug *diagnostics.RunDirectory Warnings []error } // Prepare validates invocation inputs and collisions before acquiring outputs. func Prepare(ctx context.Context, input Input) (*Prepared, error) { if err := ctx.Err(); err != nil { return nil, err } if input.Config == nil { return nil, fmt.Errorf("application configuration is required") } if input.Transcript == nil { return nil, fmt.Errorf("transcript is required") } if err := CheckReadiness(ctx, input.Config, input.InspectProfile); err != nil { return nil, err } session, err := diagnostics.SelectOutput(input.Output) if err != nil { return nil, err } if err := ctx.Err(); err != nil { return nil, err } if err := session.Prepare(); err != nil { return nil, err } if err := ctx.Err(); err != nil { _ = session.Close() return nil, err } failedReports := func(primary error) func([]diagnostics.FileFailure) diagnostics.ReportBundle { return func(failures []diagnostics.FileFailure) diagnostics.ReportBundle { if input.FailedReports == nil { return diagnostics.ReportBundle{} } return input.FailedReports(primary, failures) } } if err := session.Initialize(input.InitialReports, failedReports(nil)); err != nil { return nil, err } cancelOutput := func(err error) error { failed := failedReports(err) return session.Finish(err, nil, failed(nil), failed) } if err := ctx.Err(); err != nil { return nil, cancelOutput(err) } result := &Prepared{Output: session} if input.Debug != nil { result.Debug, result.Warnings = session.PrepareDebug(input.Debug.WorkDir, input.Debug.RunID, input.Debug.Metadata, input.Debug.EffectiveConfig, input.Debug.Secrets...) } if err := ctx.Err(); err != nil { return nil, cancelOutput(err) } return result, nil } // CheckReadiness checks all resolved processing selections before output preparation. func CheckReadiness(ctx context.Context, cfg *config.Config, inspectProfile func(context.Context, string) error) error { if cfg == nil { return fmt.Errorf("application configuration is required") } label := func(index int, item config.Stage) string { if item.ID != nil { return fmt.Sprintf("stage %d (%s, id %q)", index+1, item.Module, *item.ID) } return fmt.Sprintf("stage %d (%s)", index+1, item.Module) } for index, item := range cfg.Pipeline.Stages { if item.Module == modulecatalog.KeyGlossary && len(glossary.Select(cfg.GlossaryCollections, item.Glossaries)) == 0 { return fmt.Errorf("%s has no glossary entries in selected collections %q", label(index, item), item.Glossaries) } } for index, item := range cfg.Pipeline.Stages { for _, selection := range []struct { role string profile *string }{{"proposal", item.LLM.ProposalProfile}, {"validation", item.LLM.ValidationProfile}} { if selection.profile == nil || *selection.profile == "" { return fmt.Errorf("%s requires a %s profile", label(index, item), selection.role) } if inspectProfile == nil { return fmt.Errorf("profile inspection is required") } if err := inspectProfile(ctx, *selection.profile); err != nil { return fmt.Errorf("%s %s profile %q: %w", label(index, item), selection.role, *selection.profile, err) } } } return ctx.Err() }