package contracts import ( "context" "fmt" "gitea.maximumdirect.net/eric/audita/v3/internal/core/chunking" "gitea.maximumdirect.net/eric/audita/v3/internal/core/config" "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" "gitea.maximumdirect.net/eric/audita/v3/internal/framework/outcomes" "gitea.maximumdirect.net/eric/audita/v3/internal/framework/promptcontext" "gitea.maximumdirect.net/eric/audita/v3/internal/framework/proposals" "gitea.maximumdirect.net/eric/audita/v3/internal/framework/structuredoutput" "gitea.maximumdirect.net/eric/audita/v3/internal/framework/validators" ) type StructuredLLMClient = structuredoutput.Client // TranscriptModule is the minimal contract for framework-integrated modules. type TranscriptModule interface { Key() string ReplacementPolicy() proposals.ReplacementPolicy Validators() []Validator Propose(ctx context.Context, req ProposalRequest) (ProposalResult, error) } // Validator evaluates candidate proposals and returns one decision per proposal index. type Validator interface { Name() string Validate(ctx context.Context, req ValidationRequest) (validators.Result, error) } type StructuredCompletionRequest = structuredoutput.Request type StructuredCompletionResponse = structuredoutput.Response // ModuleRunSpec identifies one resolved module instance in a pipeline. type ModuleRunSpec struct { ModuleKey string `json:"module_key"` InstanceName string `json:"instance_name"` ReplacementPolicy proposals.ReplacementPolicy `json:"replacement_policy"` Index int `json:"-"` ID *string `json:"-"` Settings config.Stage `json:"-"` Glossary []glossary.Entry `json:"-"` ProtectedTerms []glossary.Term `json:"-"` ProposalClient StructuredLLMClient `json:"-"` ValidationClient StructuredLLMClient `json:"-"` } // SectionMetadata captures chunk-level metadata without embedding full segment payloads. type SectionMetadata struct { Index int `json:"section_index"` StartSegmentID int `json:"start_segment_id"` EndSegmentID int `json:"end_segment_id"` EstimatedTokens int `json:"estimated_tokens"` SegmentCount int `json:"segment_count"` } // SectionMetadataFromSection converts a chunking section into framework metadata. func SectionMetadataFromSection(section chunking.Section) SectionMetadata { return SectionMetadata{ Index: section.Index, StartSegmentID: section.StartSegmentID, EndSegmentID: section.EndSegmentID, EstimatedTokens: section.EstimatedTokens, SegmentCount: len(section.Segments), } } // ExecutionContext carries shared execution state for proposal generation and validation. type ExecutionContext struct { StageIndex *int `json:"stage_index,omitempty"` StageID *string `json:"stage_id,omitempty"` // ReportDiagnosticError may be called concurrently; it must not affect processing. ReportDiagnosticError func(error) `json:"-"` Settings config.Stage `json:"-"` WorkingTranscript *schema.Transcript `json:"-"` Glossary []glossary.Entry `json:"-"` ProtectedTerms []glossary.Term `json:"-"` GlossaryVariables *promptcontext.GlossaryVariables `json:"-"` Section *SectionMetadata `json:"section,omitempty"` DiagnosticsDir string `json:"diagnostics_dir,omitempty"` } // ProposalRequest is the input to module proposal generation. type ProposalRequest struct { ExecutionContext RunSpec ModuleRunSpec `json:"run_spec"` LLMClient StructuredLLMClient `json:"-"` } type ProposalResult struct { Proposals []proposals.CorrectionProposal `json:"proposals,omitempty"` Operations []outcomes.Operation `json:"operations,omitempty"` } // ValidationRequest is the input to validator execution. type ValidationRequest = validators.Request // ResolveModuleRunSpecs derives isolated consumer views from resolved settings. func ResolveModuleRunSpecs(cfg *config.Config) []ModuleRunSpec { if cfg == nil { return nil } specs := make([]ModuleRunSpec, len(cfg.Pipeline.Stages)) for i, item := range cfg.Pipeline.Stages { selected := glossary.Select(cfg.GlossaryCollections, item.Glossaries) protected := glossary.Select(cfg.GlossaryCollections, item.ProtectedTerms) var excluded []glossary.Entry if item.Module == modulecatalog.KeyGlossary { excluded = selected } specs[i] = ModuleRunSpec{ModuleKey: item.Module, InstanceName: fmt.Sprintf("%s_%d", item.Module, i+1), Index: i, ID: item.ID, Settings: item, Glossary: selected, ProtectedTerms: glossary.Vocabulary(protected, excluded)} } return specs }