This guide shows how to add a new source framework (for example GORM) so Valk Guard can lint its SQL and, when available, use model metadata for schema-aware rules.
A source integration can include:
- SQL scanner that emits
scanner.SQLStatement. - Model extractor that emits generic
schema.ModelDef.
Runtime wiring is registry-based:
- scanner bindings:
defaultScannerBindings()ininternal/engine/source_bindings.go - model bindings:
defaultModelBindings(cfg)ininternal/engine/source_bindings.go
internal/engine.Run() consumes those bindings, so adding a source does not require new hardcoded switches in the CLI.
Implement scanner.Scanner:
type Scanner interface {
Scan(ctx context.Context, paths []string) iter.Seq2[SQLStatement, error]
}Set SQLStatement.SQL, File, Line, Engine, and Disabled.
Implement schema.ModelExtractor:
type ModelExtractor interface {
ExtractModels(ctx context.Context, paths []string) ([]ModelDef, error)
}Normalize framework metadata to:
ModelDef.Table,ModelDef.Source,ModelDef.ColumnsModelColumn.Name,ModelColumn.Type,ModelColumn.FieldModelColumn.MappingKind/MappingSourcefor explicit vs inferred provenanceModelDef.TableMappingKind/TableMappingSourcefor explicit vs inferred table provenance
Provenance contract:
- Use
explicitwhen mapping comes from declared framework metadata. - Use
inferredwhen mapping comes from fallback naming. - Populate
...MappingSourcewith a stable provider token (<source>.<provider>) so rule logic can stay source-agnostic.
Add a new engine in internal/scanner/scanner.go, for example:
const EngineGorm Engine = "gorm"Then add it to built-in engine allowlist in internal/scanner/engines.go (knownEngines), so config engine validation accepts it.
Create internal/scanner/<source>/... implementing Scan(...).
Examples:
internal/scanner/goqu/goqu_scanner.gointernal/scanner/sqlalchemy/sqlalchemy_scanner.go
Add an entry in defaultScannerBindings():
{
name: "gorm",
impl: &gormscanner.Scanner{},
extensions: []string{".go"},
}File discovery is automatic from registered extensions via requiredExtensions(...) and collectScannerInputs(...).
If the source has model metadata, add internal/schema/<source>/... implementing schema.ModelExtractor.
For Go sources, model extraction mode is configurable:
go_model:
mapping_mode: strict # strict | balanced | permissiveWhen writing the extractor, emit normalized mapping provenance so existing schema rules can work without source-specific branches:
- explicit column mappings:
ModelColumn.MappingKind = explicit - inferred column mappings:
ModelColumn.MappingKind = inferred - explicit table mappings:
ModelDef.TableMappingKind = explicit - inferred table mappings:
ModelDef.TableMappingKind = inferred
Add an entry in defaultModelBindings():
{
source: schema.ModelSourceGorm,
extractor: &gormmodel.Extractor{},
extensions: []string{".go"},
configEngines: []scanner.Engine{scanner.EngineGorm},
queryEngines: []scanner.Engine{scanner.EngineGorm},
}Binding fields control behavior:
configEngines: whichrules.<id>.enginesvalues enable schema rules (VG101-VG104) for this model source.queryEngines: which statement engines should include this source's model snapshot for query-schema rules (VG105-VG108).
VG001-VG008: run on parsed statements emitted by scanners.VG101-VG104: run on migration snapshot + models filtered byconfigEngines.VG105-VG108: run on migration snapshot plus model snapshots mapped byqueryEngines.
- Scanner package unit tests.
- Model extractor unit tests (if extractor added).
cmd/valk-guard/main_test.gointegration tests:- source statements are detected
- engine scoping via
rules.<id>.enginesworks VG105/VG106includeWHERE,INNER JOIN ... ON ..., and grouping/sort coverage- model-aware cases when extractor is wired
README.mdengine/rule support matrix.docs/schema-drift.mdsource-to-model snapshot mapping..valk-guard.yaml.exampleengine examples.
- Engine constant added.
- Engine listed in
internal/scanner/engines.go. - Scanner implemented and bound.
- Optional extractor implemented and model-bound.
- Tests added for scanner/query-schema/schema-drift behavior.
- Docs updated.
go test ./...passes.