Run ordered processing with independent settings

This commit is contained in:
2026-11-10 17:33:30 +00:00
parent 89de2a7014
commit 7efa07ee9d
21 changed files with 447 additions and 335 deletions

View File

@@ -7,11 +7,14 @@ import (
"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
@@ -45,6 +48,9 @@ func Prepare(ctx context.Context, input Input) (*Prepared, error) {
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
@@ -74,3 +80,38 @@ func Prepare(ctx context.Context, input Input) (*Prepared, error) {
}
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()
}

View File

@@ -0,0 +1,39 @@
package preflight
import (
"context"
"os"
"path/filepath"
"strings"
"testing"
"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/schema"
)
func TestLaterEmptyGlossaryFailsBeforeOutputPreparation(t *testing.T) {
profile, id := "selected", "spells"
cfg := &config.Config{Pipeline: config.Pipeline{Stages: []config.Stage{
{Module: "grammar", LLM: config.LLM{ProposalProfile: &profile, ValidationProfile: &profile}},
{Module: "glossary", ID: &id, Glossaries: []string{"empty"}, LLM: config.LLM{ProposalProfile: &profile, ValidationProfile: &profile}},
}}}
output := filepath.Join(t.TempDir(), "transcript.json")
calls := 0
_, err := Prepare(context.Background(), Input{Config: cfg, Transcript: &schema.SourceTranscript{}, Output: diagnostics.OutputSelection{TranscriptPath: output}, InspectProfile: func(context.Context, string) error { calls++; return nil }})
if err == nil || !strings.Contains(err.Error(), "stage 2") || !strings.Contains(err.Error(), "spells") || !strings.Contains(err.Error(), "empty") {
t.Fatalf("unexpected error: %v", err)
}
if calls != 0 {
t.Fatal("inspected profiles before checking all glossary selections")
}
if _, err := os.Stat(output); !os.IsNotExist(err) {
t.Fatalf("output exists: %v", err)
}
}
func TestEmptyPipelineNeedsNoProfile(t *testing.T) {
if err := CheckReadiness(context.Background(), &config.Config{}, nil); err != nil {
t.Fatal(err)
}
}