validatord: /v1/validate + /v1/iterate HTTP surface (port 3221)

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>
This commit is contained in:
root
2026-05-02 03:53:20 -05:00
co-authored by Claude Opus 4.7
parent 09299a27b7
commit f9e72412c1
15 changed files with 1583 additions and 6 deletions
+6
View File
@@ -48,6 +48,7 @@ func main() {
"matrixd_url": cfg.Gateway.MatrixdURL,
"observerd_url": cfg.Gateway.ObserverdURL,
"chatd_url": cfg.Gateway.ChatdURL,
"validatord_url": cfg.Gateway.ValidatordURL,
}
for k, v := range upstreams {
if v == "" {
@@ -71,6 +72,7 @@ func main() {
matrixdURL := mustParseUpstream("matrixd_url", cfg.Gateway.MatrixdURL)
observerdURL := mustParseUpstream("observerd_url", cfg.Gateway.ObserverdURL)
chatdURL := mustParseUpstream("chatd_url", cfg.Gateway.ChatdURL)
validatordURL := mustParseUpstream("validatord_url", cfg.Gateway.ValidatordURL)
storagedProxy := gateway.NewProxyHandler(storagedURL)
catalogdProxy := gateway.NewProxyHandler(catalogdURL)
@@ -82,6 +84,7 @@ func main() {
matrixdProxy := gateway.NewProxyHandler(matrixdURL)
observerdProxy := gateway.NewProxyHandler(observerdURL)
chatdProxy := gateway.NewProxyHandler(chatdURL)
validatordProxy := gateway.NewProxyHandler(validatordURL)
if err := shared.Run("gateway", cfg.Gateway.Bind, func(r chi.Router) {
@@ -109,6 +112,9 @@ func main() {
// Chat — /v1/chat (LLM dispatcher) + /v1/chat/providers
r.Handle("/v1/chat", chatdProxy)
r.Handle("/v1/chat/*", chatdProxy)
// Validator — /v1/validate (single-shot) + /v1/iterate (loop)
r.Handle("/v1/validate", validatordProxy)
r.Handle("/v1/iterate", validatordProxy)
}, cfg.Auth); err != nil {
slog.Error("server", "err", err)
os.Exit(1)