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>
78 lines
2.6 KiB
Go
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
|
|
}
|