package semanticreconcile import ( "errors" "fmt" "io/fs" "gitea.maximumdirect.net/eric/notarius/internal/framework/llm" "gitea.maximumdirect.net/eric/notarius/internal/framework/promptfs" ) const ( PromptID = "generic.semantic_reconciliation" PromptVersion = "v1" promptRoot = "assets/prompts" ) var promptFiles = []promptfs.ModulePromptFile{ {Name: "prompt.yaml", Path: "prompts/prompt.yaml"}, {Name: "system.md", Path: "prompts/system.md"}, {Name: "protocol.md", Path: "prompts/protocol.md"}, {Name: "instructions.md", Path: "prompts/instructions.md"}, {Name: "candidates.md", Path: "prompts/candidates.md"}, {Name: "transcript-windows.md", Path: "prompts/transcript-windows.md"}, } // RegisterAssets registers the generic reconciliation prompt and response // schema as one production-owned asset set. func RegisterAssets(registry *llm.AssetRegistry) error { if registry == nil { return fmt.Errorf("semantic reconciliation asset registry must not be nil") } if err := ensureAssetsAbsent(registry); err != nil { return err } assets, err := assetFS() if err != nil { return err } prompts, err := promptfs.ModulePromptFS(PromptID, assets, append([]promptfs.ModulePromptFile(nil), promptFiles...)) if err != nil { return fmt.Errorf("prepare semantic reconciliation prompt assets: %w", err) } if err := registry.RegisterPromptFS(prompts, promptRoot); err != nil { return fmt.Errorf("register semantic reconciliation prompt assets: %w", err) } if err := registry.RegisterSchemaFS(assets, "schemas"); err != nil { return fmt.Errorf("register semantic reconciliation schema assets: %w", err) } return nil } // PromptHash returns the deterministic identity of the complete generic prompt. func PromptHash() (string, error) { assets, err := assetFS() if err != nil { return "", err } parts := make([]llm.AssetHashPart, 0, len(promptFiles)) for _, file := range promptFiles { parts = append(parts, llm.AssetHashPart{FS: assets, Path: file.Path}) } return llm.HashAssets(parts) } // SchemaHash returns the deterministic identity of the response schema. func SchemaHash() (string, error) { schema, err := LoadResponseSchema() if err != nil { return "", err } return schema.SHA256, nil } // SharedPromptFiles returns fresh descriptors for the mandatory protocol and // variable-input presentation assets that domain prompts may reuse. func SharedPromptFiles() ([]promptfs.SharedPromptFile, error) { assets, err := assetFS() if err != nil { return nil, err } return []promptfs.SharedPromptFile{ {Name: "protocol.md", FS: assets, Path: "prompts/protocol.md"}, {Name: "candidates.md", FS: assets, Path: "prompts/candidates.md"}, {Name: "transcript-windows.md", FS: assets, Path: "prompts/transcript-windows.md"}, }, nil } func ensureAssetsAbsent(registry *llm.AssetRegistry) error { prompts, err := registry.PromptFS() if err != nil { return fmt.Errorf("inspect registered prompt assets: %w", err) } if _, err := fs.Stat(prompts, PromptID+"/prompt.yaml"); err == nil { return fmt.Errorf("semantic reconciliation prompt assets already registered") } else if !errors.Is(err, fs.ErrNotExist) { return fmt.Errorf("inspect semantic reconciliation prompt assets: %w", err) } schemas, err := registry.SchemaFS() if err != nil { return fmt.Errorf("inspect registered schema assets: %w", err) } if _, err := fs.Stat(schemas, "semantic_reconciliation_llm.v1.json"); err == nil { return fmt.Errorf("semantic reconciliation schema assets already registered") } else if !errors.Is(err, fs.ErrNotExist) { return fmt.Errorf("inspect semantic reconciliation schema assets: %w", err) } return nil }