ai-code-battle/cmd/acb-evolver/internal/validator/schema.go
jedarden 5669688984 Add validation pipeline, sandbox, and evolution DB layer (Phase 7)
Three-stage fail-fast validator for LLM-generated bot candidates:
- syntax.go: language-aware parse (go/parser for Go; py_compile, rustfmt,
  tsc, javac, php -l for others; brace-balance fallback)
- schema.go: regex detection of /health + /turn endpoints and "moves" field
- sandbox.go: nsjail-isolated smoke test — builds bot, polls /health, sends
  5 signed /turn requests, verifies JSON moves responses
- validator.go: orchestrates stages with fail-fast short-circuit

DB layer:
- programs table + CRUD (create, get, list, updateFitness, setPromoted)
- validation_log table with RecordValidation, IslandPassRates,
  IslandValidationStats for per-island pass-rate tracking
- seed.go: 6 generation-0 bots across alpha/beta/gamma/delta islands

MAP-Elites grid (mapelites/grid.go): 2-D behavior grid on aggression×economy
axes; TryPlace keeps the fittest occupant per niche.

acb-evolver CLI gains two new subcommands:
  validate <file> -lang <lang> [-island <island>] [-nsjail] [-nolog]
  validation-stats (tabular per-island pass-rate breakdown)

cmd/acb-api/db.go: add programs table to API schema so the API can query
promoted evolved bots.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 22:45:13 -04:00

78 lines
2.6 KiB
Go

package validator
import (
"fmt"
"regexp"
)
// endpointSpec holds the regex patterns used to detect the two required
// HTTP endpoints in a bot's source code.
type endpointSpec struct {
// healthRe matches the /health endpoint route registration.
healthRe *regexp.Regexp
// turnRe matches the /turn endpoint route registration.
turnRe *regexp.Regexp
}
// specs maps canonical language names to their detection patterns.
// The patterns are intentionally broad to accommodate the various HTTP
// frameworks an LLM might choose (net/http, Flask, Express, Actix, etc.).
var specs = map[string]endpointSpec{
"go": {
// Matches string literals "/health" and "/turn" (double-quote or backtick).
healthRe: regexp.MustCompile("[\"`]/health[\"`]"),
turnRe: regexp.MustCompile("[\"`]/turn[\"`]"),
},
"python": {
healthRe: regexp.MustCompile(`['"]/health['"]`),
turnRe: regexp.MustCompile(`['"]/turn['"]`),
},
"rust": {
healthRe: regexp.MustCompile(`['"]/health['"]`),
turnRe: regexp.MustCompile(`['"]/turn['"]`),
},
"typescript": {
healthRe: regexp.MustCompile(`['"]/health['"]`),
turnRe: regexp.MustCompile(`['"]/turn['"]`),
},
"java": {
healthRe: regexp.MustCompile(`['"]/health['"]`),
turnRe: regexp.MustCompile(`['"]/turn['"]`),
},
"php": {
healthRe: regexp.MustCompile(`['"]/health['"]`),
turnRe: regexp.MustCompile(`['"]/turn['"]`),
},
}
// movesRe detects whether the source references a JSON "moves" field, which
// is required in the POST /turn response.
var movesRe = regexp.MustCompile(`(?i)["']moves["']|\bmoves\b`)
// CheckSchema performs static analysis on code to confirm it exposes the two
// required HTTP endpoints and returns a moves response:
//
// - GET /health → must return HTTP 200
// - POST /turn → must accept JSON game state and return {"moves":[...]}
//
// The check is language-aware but uses lightweight regex matching — it does
// not perform full AST analysis. False negatives (a syntactically unusual
// but correct bot) are possible; they will be caught by the sandbox stage.
func CheckSchema(code, language string) error {
spec, ok := specs[language]
if !ok {
return fmt.Errorf("unsupported language: %s", language)
}
if !spec.healthRe.MatchString(code) {
return fmt.Errorf("schema: /health endpoint not found — bot must implement GET /health")
}
if !spec.turnRe.MatchString(code) {
return fmt.Errorf("schema: /turn endpoint not found — bot must implement POST /turn")
}
if !movesRe.MatchString(code) {
return fmt.Errorf(`schema: no "moves" field detected — bot must return JSON {"moves":[...]}`)
}
return nil
}