Compose production modules through family registrars

This commit is contained in:
2026-07-17 04:53:09 +00:00
parent c99bad19ae
commit 0327659355
12 changed files with 524 additions and 96 deletions

View File

@@ -0,0 +1,61 @@
// Package register composes the production domain-neutral module family.
package register
import (
"fmt"
"gitea.maximumdirect.net/eric/notarius/internal/framework/llm"
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
"gitea.maximumdirect.net/eric/notarius/internal/modules/chunk/generic"
"gitea.maximumdirect.net/eric/notarius/internal/modules/merge/appendorder"
"gitea.maximumdirect.net/eric/notarius/internal/modules/normalize/noop"
jsonoutput "gitea.maximumdirect.net/eric/notarius/internal/modules/output/json"
alwaysaccept "gitea.maximumdirect.net/eric/notarius/internal/validators/generic/always_accept"
alwaysreject "gitea.maximumdirect.net/eric/notarius/internal/validators/generic/always_reject"
validjson "gitea.maximumdirect.net/eric/notarius/internal/validators/generic/valid_json"
validjsonschema "gitea.maximumdirect.net/eric/notarius/internal/validators/generic/valid_json_schema"
)
// Register adds all production domain-neutral modules and validators.
func Register(registries pipeline.Registries, assets *llm.AssetRegistry) error {
_ = assets
if err := validateRegistries(registries); err != nil {
return err
}
registrations := []struct {
name string
register func() error
}{
{name: "generic chunker", register: func() error { return generic.Register(registries.Chunkers) }},
{name: "appendorder merger", register: func() error { return appendorder.Register(registries.Mergers) }},
{name: "noop normalizer", register: func() error { return noop.Register(registries.Normalizers) }},
{name: "always accept validator", register: func() error { return alwaysaccept.Register(registries.Validators) }},
{name: "always reject validator", register: func() error { return alwaysreject.Register(registries.Validators) }},
{name: "valid json validator", register: func() error { return validjson.Register(registries.Validators) }},
{name: "valid json schema validator", register: func() error { return validjsonschema.Register(registries.Validators) }},
{name: "json output encoder", register: func() error { return jsonoutput.Register(registries.Outputs) }},
}
for _, registration := range registrations {
if err := registration.register(); err != nil {
return fmt.Errorf("register %s: %w", registration.name, err)
}
}
return nil
}
func validateRegistries(registries pipeline.Registries) error {
switch {
case registries.Chunkers == nil:
return fmt.Errorf("generic registrar: chunker registry must not be nil")
case registries.Mergers == nil:
return fmt.Errorf("generic registrar: merger registry must not be nil")
case registries.Normalizers == nil:
return fmt.Errorf("generic registrar: normalizer registry must not be nil")
case registries.Validators == nil:
return fmt.Errorf("generic registrar: validator registry must not be nil")
case registries.Outputs == nil:
return fmt.Errorf("generic registrar: output registry must not be nil")
default:
return nil
}
}

View File

@@ -0,0 +1,90 @@
package register
import (
"reflect"
"strings"
"testing"
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
)
func TestRegisterAddsGenericFamily(t *testing.T) {
registries := completeRegistries()
if err := Register(registries, nil); err != nil {
t.Fatalf("Register() error = %v, want nil", err)
}
assertKeys(t, "chunkers", registries.Chunkers.RegisteredKeys(), []string{"generic"})
assertKeys(t, "mergers", registries.Mergers.RegisteredKeys(), []string{"appendorder"})
assertKeys(t, "normalizers", registries.Normalizers.RegisteredKeys(), []string{"noop"})
assertKeys(t, "validators", registries.Validators.RegisteredKeys(), []string{
"generic/always_accept",
"generic/always_reject",
"generic/valid_json",
"generic/valid_json_schema",
})
assertKeys(t, "outputs", registries.Outputs.RegisteredKeys(), []string{"json"})
if got := registries.Inputs.RegisteredKeys(); len(got) != 0 {
t.Fatalf("input keys = %#v, want generic registrar to leave inputs unchanged", got)
}
if got := registries.Extractors.RegisteredKeys(); len(got) != 0 {
t.Fatalf("extractor keys = %#v, want generic registrar to leave extractors unchanged", got)
}
}
func TestRegisterRejectsMissingGenericRegistriesBeforeMutation(t *testing.T) {
tests := []struct {
name string
remove func(*pipeline.Registries)
wantErr string
}{
{name: "chunkers", remove: func(r *pipeline.Registries) { r.Chunkers = nil }, wantErr: "chunker registry"},
{name: "mergers", remove: func(r *pipeline.Registries) { r.Mergers = nil }, wantErr: "merger registry"},
{name: "normalizers", remove: func(r *pipeline.Registries) { r.Normalizers = nil }, wantErr: "normalizer registry"},
{name: "validators", remove: func(r *pipeline.Registries) { r.Validators = nil }, wantErr: "validator registry"},
{name: "outputs", remove: func(r *pipeline.Registries) { r.Outputs = nil }, wantErr: "output registry"},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
registries := completeRegistries()
test.remove(&registries)
err := Register(registries, nil)
if err == nil || !strings.Contains(err.Error(), test.wantErr) {
t.Fatalf("Register() error = %v, want %q", err, test.wantErr)
}
if got := registries.Chunkers; got != nil && len(got.RegisteredKeys()) != 0 {
t.Fatalf("chunker keys = %#v, want validation before mutation", got.RegisteredKeys())
}
})
}
}
func TestRegisterReportsDuplicateGenericRegistration(t *testing.T) {
registries := completeRegistries()
if err := Register(registries, nil); err != nil {
t.Fatalf("first Register() error = %v, want nil", err)
}
err := Register(registries, nil)
if err == nil || !strings.Contains(err.Error(), "register generic chunker") || !strings.Contains(err.Error(), "already registered") {
t.Fatalf("second Register() error = %v, want contextual duplicate error", err)
}
}
func completeRegistries() pipeline.Registries {
return pipeline.Registries{
Inputs: pipeline.NewInputAdapterRegistry(),
Chunkers: pipeline.NewChunkerRegistry(),
Extractors: pipeline.NewExtractorRegistry(),
Mergers: pipeline.NewMergerRegistry(),
Normalizers: pipeline.NewNormalizerRegistry(),
Validators: pipeline.NewValidatorRegistry(),
ValidatorChains: pipeline.NewValidatorChainRegistry(),
Outputs: pipeline.NewOutputEncoderRegistry(),
}
}
func assertKeys(t *testing.T, name string, got, want []string) {
t.Helper()
if !reflect.DeepEqual(got, want) {
t.Fatalf("%s keys = %#v, want %#v", name, got, want)
}
}