package modules import ( "context" "fmt" "strings" "gitea.maximumdirect.net/eric/audita/internal/core/config" "gitea.maximumdirect.net/eric/audita/internal/core/schema" "gitea.maximumdirect.net/eric/audita/internal/framework/contracts" ) const ( ModuleKeyGlossary = "glossary" ModuleKeyHomophones = "homophones" ModuleKeySpokenWord = "spoken_word" ModuleKeyGrammar = "grammar" ) const ( ReasonUnsupportedModule = "unsupported_module" ReasonUnimplementedModule = "unimplemented_module" ) var knownModuleKeys = map[string]struct{}{ ModuleKeyGlossary: {}, ModuleKeyHomophones: {}, ModuleKeySpokenWord: {}, ModuleKeyGrammar: {}, } // IsKnownModuleKey reports whether a module key is recognized by the production // registry scaffold. func IsKnownModuleKey(key string) bool { _, ok := knownModuleKeys[strings.TrimSpace(key)] return ok } // Dependencies holds explicit constructor dependencies for module creation. type Dependencies struct { Config *config.Config Glossary *schema.Glossary ProposalLLMClient contracts.StructuredLLMClient ProposalLLMScheduler contracts.LLMScheduler ValidationLLMClient contracts.StructuredLLMClient ValidationLLMScheduler contracts.LLMScheduler DiagnosticsDir string } // ConstructRequest is one module-construction request. type ConstructRequest struct { RunSpec contracts.ModuleRunSpec Dependencies } // Constructor builds one module instance from a run spec and explicit deps. type Constructor func(ctx context.Context, req ConstructRequest) (contracts.TranscriptModule, error) // Factory resolves configured module specs into module instances. type Factory struct { deps Dependencies constructors map[string]Constructor } // NewFactory creates a production registry scaffold with known module keys but // no real module constructors registered yet. func NewFactory(deps Dependencies) *Factory { return &Factory{ deps: deps, constructors: make(map[string]Constructor, len(knownModuleKeys)), } } // RegisterConstructor registers a constructor for a known module key. func (f *Factory) RegisterConstructor(moduleKey string, constructor Constructor) error { if f == nil { return fmt.Errorf("module factory is nil") } key := strings.TrimSpace(moduleKey) if !IsKnownModuleKey(key) { return &UnsupportedModuleError{ModuleKey: key} } if constructor == nil { return fmt.Errorf("constructor for module %q must not be nil", key) } f.constructors[key] = constructor return nil } // ModuleForSpec resolves one configured run spec into a module instance. func (f *Factory) ModuleForSpec(spec contracts.ModuleRunSpec) (contracts.TranscriptModule, error) { if f == nil { return nil, fmt.Errorf("module factory is nil") } key := strings.TrimSpace(spec.ModuleKey) if !IsKnownModuleKey(key) { return nil, &UnsupportedModuleError{ModuleKey: key} } constructor, ok := f.constructors[key] if !ok || constructor == nil { return nil, &UnimplementedModuleError{ModuleKey: key} } module, err := constructor(context.Background(), ConstructRequest{ RunSpec: spec, Dependencies: Dependencies{ Config: f.deps.Config, Glossary: f.deps.Glossary, ProposalLLMClient: f.deps.ProposalLLMClient, ProposalLLMScheduler: f.deps.ProposalLLMScheduler, ValidationLLMClient: f.deps.ValidationLLMClient, ValidationLLMScheduler: f.deps.ValidationLLMScheduler, DiagnosticsDir: f.deps.DiagnosticsDir, }, }) if err != nil { return nil, fmt.Errorf("construct module %q: %w", spec.InstanceName, err) } if module == nil { return nil, fmt.Errorf("constructor for module %q returned nil module", key) } return module, nil } // UnsupportedModuleError indicates a configured module key is unknown. type UnsupportedModuleError struct { ModuleKey string } func (e *UnsupportedModuleError) Error() string { return fmt.Sprintf("unsupported module key %q", strings.TrimSpace(e.ModuleKey)) } // ReasonCode returns a stable reason code suitable for reporting. func (e *UnsupportedModuleError) ReasonCode() string { return ReasonUnsupportedModule } // UnimplementedModuleError indicates a known module key without constructor. type UnimplementedModuleError struct { ModuleKey string } func (e *UnimplementedModuleError) Error() string { return fmt.Sprintf("module %q is recognized but not implemented", strings.TrimSpace(e.ModuleKey)) } // ReasonCode returns a stable reason code suitable for reporting. func (e *UnimplementedModuleError) ReasonCode() string { return ReasonUnimplementedModule }