textmachine/eval/door_to_file/zeropipe/main.go

221 lines
6.7 KiB
Go

// Command zeropipe renders a deployment's shipping pipeline onto the deployment's zero-cost pair,
// so the chain can be smoked without buying anything. It is a port of the platform's own renderer
// (platform/internal/runner/bankapply_live_test.go, zeroCostPipeline) rather than the sed over
// `model:` the stand recipe gives: the gates carry models too, and on a pipeline-c1 template the
// terminologist and the classifier resolve to a paid provider with every stage already zeroed.
//
// It also answers the question a rendered file cannot answer about itself: -check counts, in both
// the shipping file and the rendered one, the models this deployment would have to pay for. A
// silent renderer and a clean pipeline look the same until the paid count beside the zero is
// printed too.
package main
import (
"flag"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"gopkg.in/yaml.v3"
)
type modelsFile struct {
Providers map[string]struct {
Kind string `yaml:"kind"`
} `yaml:"providers"`
Models map[string]struct {
Provider string `yaml:"provider"`
} `yaml:"models"`
}
func main() {
pipelinePath := flag.String("pipeline", "", "the deployment's shipping pipeline")
modelsPath := flag.String("models", "", "the models registry the book points at")
out := flag.String("out", "", "where to write the rendered zero-cost pipeline")
flag.Parse()
if *pipelinePath == "" || *modelsPath == "" || *out == "" {
die("usage: zeropipe -pipeline <c1.yaml> -models <models.yaml> -out <zero.yaml>")
}
models := readModels(*modelsPath)
free, paid := partition(models)
if free == "" {
die("this deployment's %s declares no provider of kind `local`: the probe would have to buy its calls", *modelsPath)
}
raw, err := os.ReadFile(*pipelinePath)
if err != nil {
die("the pipeline cannot be read (%s): %v", *pipelinePath, err)
}
var pipe map[string]any
if err := yaml.Unmarshal(raw, &pipe); err != nil {
die("%v", err)
}
stages, ok := pipe["stages"].([]any)
if !ok || len(stages) == 0 {
die("the pipeline declares no stages: %s", *pipelinePath)
}
for _, s := range stages {
stage, ok := s.(map[string]any)
if !ok {
die("a stage of %s is not a mapping", *pipelinePath)
}
stage["model"] = free
delete(stage, "escalate_to")
delete(stage, "label_models")
}
// The gates carry models of their own, and both of them: `classify_model` falls back to `model`
// only when it is empty, so a pipeline that sets it would resolve a paid model here with `model`
// already zeroed.
if gates, ok := pipe["gates"].(map[string]any); ok {
for _, g := range gates {
gate, ok := g.(map[string]any)
if !ok {
continue
}
if _, has := gate["model"]; has {
gate["model"] = free
}
if _, has := gate["classify_model"]; has {
gate["classify_model"] = free
}
delete(gate, "escalate_to")
}
}
if esc, ok := pipe["escalation"].(map[string]any); ok {
esc["budget_usd"] = 0
delete(esc, "chains")
}
// The bank contour's one deployment-provided input is resolved against the directory of the
// pipeline file, and this render is about to move: absolutise it, and refuse rather than let the
// engine die at its write-path guard with a message that reads like somebody else's defect.
if mining, ok := pipe["mining"].(map[string]any); ok {
if rel, ok := mining["contrast_path"].(string); ok && rel != "" {
abs := rel
if !filepath.IsAbs(abs) {
abs = filepath.Join(filepath.Dir(*pipelinePath), abs)
}
if _, err := os.Stat(abs); err != nil {
die("the pipeline enables the bank contour and names %s, which is not on this host", abs)
}
mining["contrast_path"] = abs
}
}
rendered, err := yaml.Marshal(pipe)
if err != nil {
die("%v", err)
}
if err := os.WriteFile(*out, rendered, 0o644); err != nil {
die("%v", err)
}
var shipping map[string]any
if err := yaml.Unmarshal(raw, &shipping); err != nil {
die("%v", err)
}
var check map[string]any
if err := yaml.Unmarshal(rendered, &check); err != nil {
die("the rendered pipeline does not parse back: %v", err)
}
shippingHits := hits(shipping, paid)
renderedHits := hits(check, paid)
fmt.Printf("free model: %s\n", free)
fmt.Printf("paid models this deployment declares: %d (%s)\n", len(paid), strings.Join(paid, " "))
fmt.Printf("paid models REACHABLE in %s: %d\n", filepath.Base(*pipelinePath), total(shippingHits))
for _, name := range sorted(shippingHits) {
fmt.Printf(" %s: %d\n", name, shippingHits[name])
}
fmt.Printf("paid models REACHABLE in %s: %d\n", filepath.Base(*out), total(renderedHits))
for _, name := range sorted(renderedHits) {
fmt.Printf(" %s: %d\n", name, renderedHits[name])
}
if total(renderedHits) != 0 {
die("the rendered pipeline still names a paid model: the probe would buy its calls")
}
if total(shippingHits) == 0 {
die("the shipping pipeline names no paid model either: this check proves nothing, and the "+
"zero in the render is the instrument staying silent rather than the render being clean (%s)", *pipelinePath)
}
}
func readModels(path string) modelsFile {
raw, err := os.ReadFile(path)
if err != nil {
die("the models file cannot be read (%s): %v", path, err)
}
var m modelsFile
if err := yaml.Unmarshal(raw, &m); err != nil {
die("%v", err)
}
return m
}
// partition names the cheapest truth about this registry: which model costs nothing because its
// provider runs on this host, and which ones a call would be billed for.
func partition(m modelsFile) (free string, paid []string) {
for name, model := range m.Models {
if m.Providers[model.Provider].Kind == "local" {
if free == "" || name < free {
free = name
}
continue
}
paid = append(paid, name)
}
sort.Strings(paid)
return free, paid
}
// hits counts paid models where they are REACHABLE — as values in the parsed configuration, not as
// text. Counting text would count the comments, and a rendered file has none: the render would look
// clean the moment the comments were dropped, whether or not a paid model still stood in a gate.
func hits(node any, names []string) map[string]int {
out := map[string]int{}
var walk func(any)
walk = func(n any) {
switch v := n.(type) {
case map[string]any:
for _, child := range v {
walk(child)
}
case []any:
for _, child := range v {
walk(child)
}
case string:
for _, name := range names {
if v == name {
out[name]++
}
}
}
}
walk(node)
return out
}
func total(m map[string]int) int {
sum := 0
for _, v := range m {
sum += v
}
return sum
}
func sorted(m map[string]int) []string {
out := make([]string, 0, len(m))
for k := range m {
out = append(out, k)
}
sort.Strings(out)
return out
}
func die(format string, args ...any) {
fmt.Fprintf(os.Stderr, format+"\n", args...)
os.Exit(1)
}