Closes the last "Go primary" backlog item in docs/ARCHITECTURE_COMPARISON.md. Go now owns the entire validator path end-to-end — no Rust dep for staffing safety net. Architecture: cmd/validatord on :3221 hosts both endpoints. Calls chatd directly for the iterate loop's LLM hop (no gateway self-loopback like the Rust shape). Gateway proxies /v1/validate + /v1/iterate to validatord. What's in: - internal/validator/playbook.go — 3rd validator kind (PRD checks: fill: prefix, endorsed_names ≤ target_count×2, fingerprint required) - internal/validator/lookup_jsonl.go — JSONL roster loader (Parquet deferred; producer one-liner documented in package comment) - internal/validator/iterate.go — ExtractJSON helper + Iterate orchestrator with ChatCaller seam for unit tests - cmd/validatord/main.go — HTTP routes, roster load, chat client - internal/shared/config.go — ValidatordConfig + gateway URL field - lakehouse.toml — [validatord] section - cmd/gateway/main.go — proxy routes for /v1/validate + /v1/iterate Smoke: 5/5 PASS through gateway :3110: ✓ playbook happy path ✓ playbook missing fingerprint → 422 schema/fingerprint ✓ phantom candidate W-PHANTOM → 422 consistency ✓ unknown kind → 400 ✓ roster loaded with 3 records go test ./... green across 33 packages. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
78 lines
2.3 KiB
Go
78 lines
2.3 KiB
Go
package validator
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
)
|
|
|
|
func TestPlaybook_WellFormedPasses(t *testing.T) {
|
|
r, err := PlaybookValidator{}.Validate(Artifact{Playbook: map[string]any{
|
|
"operation": "fill: Welder x2 in Toledo, OH",
|
|
"endorsed_names": []any{"W-123", "W-456"},
|
|
"target_count": 2.0,
|
|
"fingerprint": "abc123",
|
|
}})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if r.ElapsedMs < 0 {
|
|
t.Errorf("elapsed_ms negative: %d", r.ElapsedMs)
|
|
}
|
|
}
|
|
|
|
func TestPlaybook_EmptyEndorsedNamesFailsCompleteness(t *testing.T) {
|
|
_, err := PlaybookValidator{}.Validate(Artifact{Playbook: map[string]any{
|
|
"operation": "fill: Welder x2 in Toledo, OH",
|
|
"endorsed_names": []any{},
|
|
"fingerprint": "abc",
|
|
}})
|
|
var ve *ValidationError
|
|
if !errors.As(err, &ve) || ve.Kind != ErrCompleteness {
|
|
t.Fatalf("expected Completeness, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestPlaybook_OverfullEndorsedNamesFailsCompleteness(t *testing.T) {
|
|
_, err := PlaybookValidator{}.Validate(Artifact{Playbook: map[string]any{
|
|
"operation": "fill: Welder x1 in Toledo, OH",
|
|
"endorsed_names": []any{"a", "b", "c"},
|
|
"target_count": 1.0,
|
|
"fingerprint": "abc",
|
|
}})
|
|
var ve *ValidationError
|
|
if !errors.As(err, &ve) || ve.Kind != ErrCompleteness {
|
|
t.Fatalf("expected Completeness, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestPlaybook_MissingFingerprintFailsSchema(t *testing.T) {
|
|
_, err := PlaybookValidator{}.Validate(Artifact{Playbook: map[string]any{
|
|
"operation": "fill: X x1 in A, B",
|
|
"endorsed_names": []any{"a"},
|
|
}})
|
|
var ve *ValidationError
|
|
if !errors.As(err, &ve) || ve.Kind != ErrSchema || ve.Field != "fingerprint" {
|
|
t.Fatalf("expected Schema/fingerprint, got %+v", err)
|
|
}
|
|
}
|
|
|
|
func TestPlaybook_WrongOperationPrefixFailsSchema(t *testing.T) {
|
|
_, err := PlaybookValidator{}.Validate(Artifact{Playbook: map[string]any{
|
|
"operation": "sms_draft: hello",
|
|
"endorsed_names": []any{"a"},
|
|
"fingerprint": "x",
|
|
}})
|
|
var ve *ValidationError
|
|
if !errors.As(err, &ve) || ve.Kind != ErrSchema {
|
|
t.Fatalf("expected Schema, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestPlaybook_WrongArtifactKindFailsSchema(t *testing.T) {
|
|
_, err := PlaybookValidator{}.Validate(Artifact{FillProposal: map[string]any{}})
|
|
var ve *ValidationError
|
|
if !errors.As(err, &ve) || ve.Kind != ErrSchema || ve.Field != "artifact" {
|
|
t.Fatalf("expected Schema/artifact, got %+v", err)
|
|
}
|
|
}
|