textmachine/backend/internal/config/content_labels_test.go

857 lines
37 KiB
Go

package config
import (
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
// content_labels_test.go covers the generic content-label mechanism (D39.25/D39.26): a book declares
// content_labels (DATA), a provider/model declares accepts_labels (DATA), the run config declares an
// ORDERED content_policy registry, and the loader RESOLVES routing before validating it.
//
// Every case here uses SYNTHETIC label values ("restricted-synth", "second-synth"). That is the
// mechanical generality proof the pack owes: if any Go identifier or branch knew the value "adult",
// these tests could not pass — the engine only ever compares sets it was handed.
//
// Reverting resolveContentRouting/checkContentRouting makes the routing cases below stop resolving and
// the refusal cases stop reporting.
// labelFixture writes a models.yaml + pipeline.yaml pair and returns a loader for the pipeline under an
// arbitrary label set. The models catalog is deliberately small: one endpoint that accepts a synthetic
// label, one that accepts nothing, one that accepts both synthetic labels.
func labelFixture(t *testing.T, pipelineBody string) (*Models, func(labels []string) (*Pipeline, error)) {
t.Helper()
dir := t.TempDir()
promptPath := filepath.Join(dir, "p.md")
if err := os.WriteFile(promptPath, []byte("sys\n---USER---\n{{text}}"), 0o644); err != nil {
t.Fatal(err)
}
modelsPath := filepath.Join(dir, "models.yaml")
body := fmt.Sprintf(`
prices_checked: %q
default_model: plain-model
providers:
plain:
kind: openai
base_url: http://plain
capable:
kind: openai
base_url: http://capable
accepts_labels: [restricted-synth, second-synth]
narrowed:
kind: openai
base_url: http://narrowed
accepts_labels: [restricted-synth]
models:
plain-model: { provider: plain, price: { input_per_m: 1, output_per_m: 2 } }
capable-model: { provider: capable, price: { input_per_m: 1, output_per_m: 2 } }
capable-hop: { provider: capable, price: { input_per_m: 1, output_per_m: 2 } }
second-model: { provider: capable, price: { input_per_m: 1, output_per_m: 2 } }
narrowed-model: { provider: narrowed, price: { input_per_m: 1, output_per_m: 2 } }
no-labels-model:
provider: capable
price: { input_per_m: 1, output_per_m: 2 }
accepts_labels: []
`, time.Now().UTC().Format("2006-01-02"))
if err := os.WriteFile(modelsPath, []byte(body), 0o644); err != nil {
t.Fatal(err)
}
models, err := LoadModels(modelsPath)
if err != nil {
t.Fatalf("models load: %v", err)
}
pipePath := filepath.Join(dir, "pipe.yaml")
if err := os.WriteFile(pipePath, []byte(strings.ReplaceAll(pipelineBody, "@PROMPT@", promptPath)), 0o644); err != nil {
t.Fatal(err)
}
return models, func(labels []string) (*Pipeline, error) { return LoadPipeline(pipePath, models, "zh-ru", labels) }
}
// basePipeline is a two-stage C1 config whose draft stage carries a label route and a hop.
const basePipeline = `core: C1
version: 1
defaults: { max_output_ratio: 2, min_max_tokens: 512 }
content_policy:
- { label: restricted-synth, action: route, chain: restricted }
stages:
- name: draft
role: translator
model: plain-model
prompt_override: @PROMPT@
prompt_version: v
escalate_to: capable-hop
label_models: { restricted-synth: capable-model }
- name: edit
role: editor
model: plain-model
prompt_override: @PROMPT@
prompt_version: v
label_models: { restricted-synth: second-model }
escalation:
chains:
# The head DIFFERS from the draft stage's escalate_to on purpose: otherwise "the label's chain
# substitutes escalate_to" is an identity and a revert of that line would pass unnoticed.
restricted: [second-model]
default: [plain-model]
budget_usd: 1.0
`
// TestContentRoutingResolvesPerStage pins the resolve half: with no labels the resolved models are the
// configured ones byte for byte (the unlabelled path must not move), and with the label active each
// stage resolves to its label model while the hop comes from the label's chain HEAD.
func TestContentRoutingResolvesPerStage(t *testing.T) {
_, load := labelFixture(t, basePipeline)
p, err := load(nil)
if err != nil {
t.Fatalf("unlabelled load: %v", err)
}
for _, st := range p.Stages {
if st.ResolvedModel != st.Model || st.ResolvedHop != st.EscalateTo {
t.Errorf("unlabelled stage %q resolved to (%q,%q), want the configured (%q,%q) — the no-label path must be identical",
st.Name, st.ResolvedModel, st.ResolvedHop, st.Model, st.EscalateTo)
}
}
if err := p.ContentRoutingError(); err != nil {
t.Errorf("unlabelled book must have no routing problems, got %v", err)
}
p, err = load([]string{"restricted-synth"})
if err != nil {
t.Fatalf("labelled load: %v", err)
}
if err := p.ContentRoutingError(); err != nil {
t.Fatalf("labelled book must load cleanly (every reachable model accepts the label), got %v", err)
}
if got := p.Stages[0].ResolvedModel; got != "capable-model" {
t.Errorf("draft resolved model = %q, want capable-model (label_models)", got)
}
if got := p.Stages[0].ResolvedHop; got != "second-model" {
t.Errorf("draft resolved hop = %q, want second-model — the chain head SUBSTITUTES the configured escalate_to (capable-hop)", got)
}
if got := p.Stages[1].ResolvedModel; got != "second-model" {
t.Errorf("edit resolved model = %q, want second-model", got)
}
// D12 editor-pinned: a stage with no escalate_to gets no hop even under a label.
if got := p.Stages[1].ResolvedHop; got != "" {
t.Errorf("edit resolved hop = %q, want empty (editor is pinned — a refusal there is terminal)", got)
}
// Reachability is label-dependent: the replaced escalate_to and the unreferenced default chain are
// NOT reachable for this book.
reach := strings.Join(p.ReachableModels(), ",")
for _, want := range []string{"capable-model", "second-model"} {
if !strings.Contains(reach, want) {
t.Errorf("reachable set %q must contain %q", reach, want)
}
}
if strings.Contains(reach, "capable-hop") {
t.Errorf("reachable set %q must NOT contain capable-hop — the label's chain substituted it", reach)
}
if strings.Contains(reach, "plain-model") {
t.Errorf("reachable set %q must NOT contain plain-model — the label replaced it (a capability demand on a model the run never calls would refuse a valid book)", reach)
}
}
// TestContentRoutingRefusals covers the book-DEPENDENT refusals: they are reported through
// ContentProblems (fatal for the money path, a warning for the $0 read paths) rather than failing the
// load outright.
func TestContentRoutingRefusals(t *testing.T) {
cases := []struct {
name string
pipeline string
labels []string
want string
}{
{
name: "label with no policy entry",
pipeline: basePipeline,
labels: []string{"unregistered-synth"},
want: "no content_policy entry",
},
{
name: "model that may not receive the label",
pipeline: strings.Replace(basePipeline,
"label_models: { restricted-synth: capable-model }",
"label_models: { restricted-synth: no-labels-model }", 1),
labels: []string{"restricted-synth"},
want: "may not receive content label",
},
{
name: "route label with no escalation budget",
pipeline: strings.Replace(basePipeline,
" budget_usd: 1.0", " budget_usd: 0", 1),
labels: []string{"restricted-synth"},
want: "no hop can ever fire",
},
{
// A terminal policy takes no per-stage routes either (they would be meaningless), so the fixture
// drops them together with the chain — that pairing is itself checked by the shape test below.
name: "terminal policy refuses the book",
pipeline: strings.NewReplacer(
" - { label: restricted-synth, action: route, chain: restricted }",
" - { label: restricted-synth, action: terminal }",
" label_models: { restricted-synth: capable-model }\n", "",
" label_models: { restricted-synth: second-model }\n", "",
).Replace(basePipeline),
labels: []string{"restricted-synth"},
want: "action=terminal",
},
{
// A route policy that re-routes NOTHING is inert: the labelled book would quietly run on the
// ordinary models with every check green.
name: "route policy that routes nothing",
pipeline: strings.NewReplacer(
" label_models: { restricted-synth: capable-model }\n", "",
" label_models: { restricted-synth: second-model }\n", "",
).Replace(basePipeline),
labels: []string{"restricted-synth"},
want: "routes nothing",
},
{
// Two active route labels with DIFFERENT heads: only one hop can execute, so the second remedy
// could never fire — a hop set silently truncated, refused with the multi-hop wording.
name: "two active labels select two different fallback heads",
pipeline: strings.NewReplacer(
" - { label: restricted-synth, action: route, chain: restricted }",
" - { label: restricted-synth, action: route, chain: restricted }\n - { label: second-synth, action: route, chain: other }",
" default: [plain-model]", " default: [plain-model]\n other: [capable-hop]",
).Replace(basePipeline),
labels: []string{"restricted-synth", "second-synth"},
want: "MULTI-HOP IS NOT BUILT",
},
{
name: "label hop equals the label primary",
pipeline: strings.Replace(basePipeline,
"label_models: { restricted-synth: capable-model }",
"label_models: { restricted-synth: second-model }", 1),
labels: []string{"restricted-synth"},
want: "same fallback hop",
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
_, load := labelFixture(t, c.pipeline)
p, err := load(c.labels)
if err != nil {
t.Fatalf("load must SUCCEED (book-dependent refusals are collected, not returned): %v", err)
}
rerr := p.ContentRoutingError()
if rerr == nil || !strings.Contains(rerr.Error(), c.want) {
t.Fatalf("want a routing problem mentioning %q, got %v", c.want, rerr)
}
})
}
}
// TestContentPolicyShapeIsFatal covers the config-SHAPE errors, which fail the load on EVERY path (a
// malformed registry is broken regardless of which book reads it) — including the ratified refusal to
// silently truncate a multi-member chain.
func TestContentPolicyShapeIsFatal(t *testing.T) {
cases := []struct {
name string
pipeline string
want string
}{
{"multi-member chain", strings.Replace(basePipeline, "restricted: [second-model]", "restricted: [second-model, capable-hop]", 1), "MULTI-HOP IS NOT BUILT"},
{"unknown action", strings.Replace(basePipeline, "action: route, chain: restricted", "action: quarantine, chain: restricted", 1), "action must be route|allow|terminal"},
{"route without chain", strings.Replace(basePipeline, "action: route, chain: restricted", "action: route", 1), "requires `chain:`"},
{"chain not defined", strings.Replace(basePipeline, "action: route, chain: restricted", "action: route, chain: nosuch", 1), "not defined in escalation.chains"},
{"duplicate registry label", strings.Replace(basePipeline,
" - { label: restricted-synth, action: route, chain: restricted }",
" - { label: restricted-synth, action: route, chain: restricted }\n - { label: restricted-synth, action: terminal }", 1), "duplicate label"},
{"label_models model undefined", strings.Replace(basePipeline, "restricted-synth: capable-model", "restricted-synth: ghost-model", 1), "is not defined in models.yaml"},
{"label_models label unregistered", strings.Replace(basePipeline, "restricted-synth: second-model", "ghost-synth: second-model", 1), "no content_policy entry"},
{"retired channel key", strings.Replace(basePipeline, " escalate_to: capable-hop", " escalate_to: capable-hop\n channel: adult", 1), "`channel:` is retired"},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
_, load := labelFixture(t, c.pipeline)
// Fatal on every path: asserted WITHOUT labels, so it cannot be mistaken for a book-dependent refusal.
if _, err := load(nil); err == nil || !strings.Contains(err.Error(), c.want) {
t.Fatalf("want a load error mentioning %q, got %v", c.want, err)
}
})
}
}
// TestContentPolicyOrderDecidesPrecedence pins the reason the registry is an ordered LIST and not a map:
// when two of the book's labels claim one stage, the FIRST entry wins — deterministically, written down
// in the data file (a map would resolve by Go's randomised iteration order).
func TestContentPolicyOrderDecidesPrecedence(t *testing.T) {
two := strings.Replace(basePipeline,
" - { label: restricted-synth, action: route, chain: restricted }",
" - { label: restricted-synth, action: route, chain: restricted }\n - { label: second-synth, action: route, chain: restricted }", 1)
two = strings.Replace(two,
"label_models: { restricted-synth: capable-model }",
"label_models: { restricted-synth: capable-model, second-synth: second-model }", 1)
_, load := labelFixture(t, two)
p, err := load([]string{"restricted-synth", "second-synth"})
if err != nil {
t.Fatalf("load: %v", err)
}
if err := p.ContentRoutingError(); err != nil {
t.Fatalf("both labels are accepted by the capable provider, so this must load: %v", err)
}
if got := p.Stages[0].ResolvedModel; got != "capable-model" {
t.Errorf("draft resolved to %q, want capable-model — the FIRST registry entry claiming the stage wins", got)
}
// Reversing the registry order must reverse the winner: precedence lives in the data, not in Go.
reversed := strings.Replace(two,
" - { label: restricted-synth, action: route, chain: restricted }\n - { label: second-synth, action: route, chain: restricted }",
" - { label: second-synth, action: route, chain: restricted }\n - { label: restricted-synth, action: route, chain: restricted }", 1)
_, loadRev := labelFixture(t, reversed)
pr, err := loadRev([]string{"restricted-synth", "second-synth"})
if err != nil {
t.Fatalf("reversed load: %v", err)
}
if got := pr.Stages[0].ResolvedModel; got != "second-model" {
t.Errorf("with the reversed registry the draft resolved to %q, want second-model", got)
}
}
// TestSecondLabelIsDataOnly is the generality test D39.25 demands in mechanical form: adding a SECOND
// label needs no Go change — one registry entry, one accepts_labels value, one label_models entry, all
// data. If any of that had to be taught to the engine, this test could not pass.
func TestSecondLabelIsDataOnly(t *testing.T) {
body := strings.Replace(basePipeline,
" - { label: restricted-synth, action: route, chain: restricted }",
" - { label: second-synth, action: route, chain: restricted }", 1)
body = strings.Replace(body,
"label_models: { restricted-synth: capable-model }",
"label_models: { second-synth: capable-model }", 1)
body = strings.Replace(body,
"label_models: { restricted-synth: second-model }",
"label_models: { second-synth: second-model }", 1)
_, load := labelFixture(t, body)
p, err := load([]string{"second-synth"})
if err != nil {
t.Fatalf("second label load: %v", err)
}
if err := p.ContentRoutingError(); err != nil {
t.Fatalf("second label must route on data alone: %v", err)
}
if p.Stages[0].ResolvedModel != "capable-model" || p.Stages[0].ResolvedHop != "second-model" {
t.Errorf("second label resolved (%q,%q), want (capable-model,second-model)", p.Stages[0].ResolvedModel, p.Stages[0].ResolvedHop)
}
}
// TestAcceptsLabelsNarrowingAndWidening pins the pointer semantics that keep the permission fail-closed:
// nil inherits the provider's set, a pointer to an EMPTY list subtracts everything, and a model may
// never widen beyond its provider (a permission is a statement about the endpoint receiving the bytes).
func TestAcceptsLabelsNarrowingAndWidening(t *testing.T) {
models, _ := labelFixture(t, basePipeline)
if got := models.AcceptsLabels("capable-model"); len(got) != 2 {
t.Errorf("capable-model must INHERIT its provider's two labels, got %v", got)
}
if got := models.AcceptsLabels("no-labels-model"); len(got) != 0 {
t.Errorf("an explicit empty accepts_labels must SUBTRACT the provider's set, got %v", got)
}
if got := models.MissingLabels("no-labels-model", []string{"restricted-synth"}); len(got) != 1 {
t.Errorf("MissingLabels on a subtracted model = %v, want the label reported missing", got)
}
if got := models.MissingLabels("ghost", []string{"restricted-synth"}); len(got) != 1 {
t.Errorf("an UNKNOWN model must accept nothing (fail closed), got %v", got)
}
if got := models.MissingLabels("capable-model", nil); got != nil {
t.Errorf("no labels wanted ⇒ nothing missing, got %v", got)
}
// Widening past a SILENT provider: every provider row in the shipping catalog declares nothing today,
// so this is the natural shape of the mistake — one line on the model row (the translator/editor choice
// IS per model) granting a label the endpoint never got. A nil provider set means "accepts nothing".
silent := filepath.Join(t.TempDir(), "models.yaml")
if err := os.WriteFile(silent, []byte(fmt.Sprintf(`
prices_checked: %q
default_model: m
providers:
p: { kind: openai, base_url: http://p }
models:
m:
provider: p
price: { input_per_m: 1, output_per_m: 2 }
accepts_labels: [restricted-synth]
`, time.Now().UTC().Format("2006-01-02"))), 0o644); err != nil {
t.Fatal(err)
}
if _, err := LoadModels(silent); err == nil || !strings.Contains(err.Error(), "may only NARROW") {
t.Fatalf("a model may not grant a label its (silent) provider does not accept, got %v", err)
}
// Widening: a model naming a label its provider does not accept is a load error.
dir := t.TempDir()
path := filepath.Join(dir, "models.yaml")
body := fmt.Sprintf(`
prices_checked: %q
default_model: m
providers:
p:
kind: openai
base_url: http://p
accepts_labels: [restricted-synth]
models:
m:
provider: p
price: { input_per_m: 1, output_per_m: 2 }
accepts_labels: [restricted-synth, wider-synth]
`, time.Now().UTC().Format("2006-01-02"))
if err := os.WriteFile(path, []byte(body), 0o644); err != nil {
t.Fatal(err)
}
if _, err := LoadModels(path); err == nil || !strings.Contains(err.Error(), "may only NARROW") {
t.Fatalf("a model widening its provider's permissions must fail loud, got %v", err)
}
}
// TestRetiredPermissiveFlagIsLoud pins the retirement of the boolean the label set replaced. Presence at
// ANY value is the error: `permissive: false` was also a statement, and silently accepting it would let
// an author believe the old gate still guards.
func TestRetiredPermissiveFlagIsLoud(t *testing.T) {
for _, val := range []string{"true", "false"} {
dir := t.TempDir()
path := filepath.Join(dir, "models.yaml")
body := fmt.Sprintf(`
prices_checked: %q
default_model: m
providers:
p:
kind: openai
base_url: http://p
permissive: %s
models:
m: { provider: p, price: { input_per_m: 1, output_per_m: 2 } }
`, time.Now().UTC().Format("2006-01-02"), val)
if err := os.WriteFile(path, []byte(body), 0o644); err != nil {
t.Fatal(err)
}
if _, err := LoadModels(path); err == nil || !strings.Contains(err.Error(), "`permissive:` is retired") {
t.Errorf("permissive: %s must fail loud naming accepts_labels, got %v", val, err)
}
}
}
// TestCheckKeysIsLabelAware pins that a labelled book demands the keys of the models it actually calls,
// not of a chain its labels never select (which would block a valid labelled run on an unrelated key).
func TestCheckKeysIsLabelAware(t *testing.T) {
dir := t.TempDir()
promptPath := filepath.Join(dir, "p.md")
if err := os.WriteFile(promptPath, []byte("sys\n---USER---\n{{text}}"), 0o644); err != nil {
t.Fatal(err)
}
modelsPath := filepath.Join(dir, "models.yaml")
body := fmt.Sprintf(`
prices_checked: %q
default_model: keyed-model
providers:
keyed:
kind: openai
base_url: http://keyed
api_key_env: TM_TEST_LABEL_KEY_PRESENT
accepts_labels: [restricted-synth]
unkeyed:
kind: openai
base_url: http://unkeyed
api_key_env: TM_TEST_LABEL_KEY_ABSENT
models:
keyed-model: { provider: keyed, price: { input_per_m: 1, output_per_m: 2 } }
keyed-hop: { provider: keyed, price: { input_per_m: 1, output_per_m: 2 } }
unkeyed-model: { provider: unkeyed, price: { input_per_m: 1, output_per_m: 2 } }
`, time.Now().UTC().Format("2006-01-02"))
if err := os.WriteFile(modelsPath, []byte(body), 0o644); err != nil {
t.Fatal(err)
}
models, err := LoadModels(modelsPath)
if err != nil {
t.Fatalf("models: %v", err)
}
pipeBody := fmt.Sprintf(`core: C1
version: 1
defaults: { max_output_ratio: 2, min_max_tokens: 512 }
content_policy:
- { label: restricted-synth, action: route, chain: restricted }
stages:
- name: draft
role: translator
model: keyed-model
prompt_override: %s
prompt_version: v
escalate_to: unkeyed-model
label_models: { restricted-synth: keyed-model }
escalation:
chains:
restricted: [keyed-hop]
default: [unkeyed-model]
budget_usd: 1.0
`, promptPath)
pipePath := filepath.Join(dir, "pipe.yaml")
if err := os.WriteFile(pipePath, []byte(pipeBody), 0o644); err != nil {
t.Fatal(err)
}
t.Setenv("TM_TEST_LABEL_KEY_PRESENT", "x")
t.Setenv("TM_TEST_LABEL_KEY_ABSENT", "") // t.Setenv restores it; os.Unsetenv would leak into later tests
// Unlabelled: the historical set — every named chain + escalate_to — so the missing key IS demanded.
unlabelled, err := LoadPipeline(pipePath, models, "zh-ru", nil)
if err != nil {
t.Fatalf("unlabelled load: %v", err)
}
if err := models.CheckKeys(unlabelled); err == nil || !strings.Contains(err.Error(), "TM_TEST_LABEL_KEY_ABSENT") {
t.Fatalf("unlabelled preflight must keep demanding the unreferenced chain's key (historical behaviour), got %v", err)
}
// Labelled: the unkeyed model is unreachable (its chain is not selected, its escalate_to is replaced).
labelled, err := LoadPipeline(pipePath, models, "zh-ru", []string{"restricted-synth"})
if err != nil {
t.Fatalf("labelled load: %v", err)
}
if err := labelled.ContentRoutingError(); err != nil {
t.Fatalf("labelled routing must be clean: %v", err)
}
if err := models.CheckKeys(labelled); err != nil {
t.Fatalf("labelled preflight must only demand the keys of reachable models, got %v", err)
}
}
// TestShippingConfigsUnderLabels loads every SHIPPING pipeline against the real models catalog with a
// label set, which is the case no prior test covered: the shipping configs declare an `adult` chain but
// no content_policy, so a labelled book must be REFUSED for the money path with a message an operator
// can act on — and, critically, the refusal must be the missing POLICY, never a spurious capability
// demand on a model the labelled run would never call (the self-inconsistency the design panel caught).
func TestShippingConfigsUnderLabels(t *testing.T) {
models, err := LoadModels(filepath.Join("..", "..", "configs", "models.yaml"))
if err != nil {
t.Fatalf("load shipping models.yaml: %v", err)
}
for _, name := range []string{
"pipeline-c1.yaml", "pipeline-arm-glm.yaml", "pipeline-arm-mistral.yaml",
} {
t.Run(name, func(t *testing.T) {
path := filepath.Join("..", "..", "configs", name)
// Unlabelled: loads clean and resolves to the configured models (the byte-identical path).
p, err := LoadPipeline(path, models, "zh-ru", nil)
if err != nil {
t.Fatalf("unlabelled load must stay clean: %v", err)
}
for _, st := range p.Stages {
if st.ResolvedModel != st.Model || st.ResolvedHop != st.EscalateTo {
t.Fatalf("stage %q must resolve to its configured models without labels", st.Name)
}
}
if err := p.ContentRoutingError(); err != nil {
t.Fatalf("unlabelled shipping config must have no routing problems: %v", err)
}
// Labelled: the shipping configs carry no registry, so the refusal NAMES the missing policy (the
// actionable line). Capability complaints about the configured models appear alongside it — they
// are honest, since with no policy nothing is re-routed — but the chains no policy selects must
// NOT be dragged in.
lp, err := LoadPipeline(path, models, "zh-ru", []string{"restricted-synth"})
if err != nil {
t.Fatalf("a labelled load must not fail on config SHAPE (the refusal is book-dependent): %v", err)
}
rerr := lp.ContentRoutingError()
if rerr == nil || !strings.Contains(rerr.Error(), "no content_policy entry") {
t.Fatalf("want the missing-policy refusal, got %v", rerr)
}
// The unreferenced `adult` chain and the default chain must NOT be dragged into the invariant.
if strings.Contains(rerr.Error(), "grok-4.3") || strings.Contains(rerr.Error(), "gemini-3.1-pro-preview") {
t.Errorf("the refusal must not name models of chains no policy selects: %v", rerr)
}
})
}
}
// TestShippingChainsStayOneHop pins the data precondition of the single-hop rule for the chain the owner's
// vocabulary decision will point a policy at: `chains.adult` must stay one member long, or the policy that
// references it will not load ("multi-hop is not built"). C2 is excluded — CheckRunnable refuses that core.
func TestShippingChainsStayOneHop(t *testing.T) {
models, err := LoadModels(filepath.Join("..", "..", "configs", "models.yaml"))
if err != nil {
t.Fatal(err)
}
for _, name := range []string{
"pipeline-c1.yaml", "pipeline-arm-glm.yaml", "pipeline-arm-mistral.yaml",
} {
p, err := LoadPipeline(filepath.Join("..", "..", "configs", name), models, "zh-ru", nil)
if err != nil {
t.Fatalf("%s: %v", name, err)
}
// Every chain EXCEPT the historical multi-hop "default" (which no policy references and which the
// pack deliberately left dead). No content value appears here: naming one would make the owner's
// pending vocabulary answer — a pure data edit — turn this test red.
for chain, members := range p.Escal.Chains {
if chain == "default" {
continue
}
if len(members) != 1 {
t.Errorf("%s: chain %q has %d members — a route policy can execute exactly one hop (D39.26 point 3), so a policy pointed at it would not load", name, chain, len(members))
}
}
}
}
// TestNoPrivilegedLabelValue is the tripwire item 14 asks for in the direction the synthetic-value tests
// cannot cover: they prove the engine works for values it was never taught, but not that no value is
// PRIVILEGED. Here a book declares "adult" against a registry that only knows the synthetic label — the
// engine must treat it like any unknown label (refuse, route nothing). Teaching Go to recognise the value
// (a special case in the unregistered-label loop, a synthesised policy) makes this RED.
func TestNoPrivilegedLabelValue(t *testing.T) {
_, load := labelFixture(t, basePipeline)
p, err := load([]string{"adult"})
if err != nil {
t.Fatalf("load: %v", err)
}
rerr := p.ContentRoutingError()
if rerr == nil || !strings.Contains(rerr.Error(), "no content_policy entry") {
t.Fatalf("a value the registry never declared must be refused like any other, got %v", rerr)
}
for _, st := range p.Stages {
if st.ResolvedModel != st.Model || st.ResolvedHop != st.EscalateTo {
t.Errorf("stage %q must NOT route on an unregistered value: (%q,%q) vs configured (%q,%q)",
st.Name, st.ResolvedModel, st.ResolvedHop, st.Model, st.EscalateTo)
}
}
}
// TestLabelSpellingIsCanonicalEverywhere pins the contract that keeps the four data keys from drifting: a
// book's labels are NORMALISED (trim + lower + dedupe + sort), and every other place a label is AUTHORED
// must be written that way or the load says so. Without this a provider spelled "Restricted-Synth" would
// fail closed but tell the operator the book's label is not accepted while the row visibly contains it.
func TestLabelSpellingIsCanonicalEverywhere(t *testing.T) {
dir := t.TempDir()
write := func(name, body string) string {
p := filepath.Join(dir, name)
if err := os.WriteFile(p, []byte(body), 0o644); err != nil {
t.Fatal(err)
}
return p
}
today := time.Now().UTC().Format("2006-01-02")
// accepts_labels must be canonical.
bad := write("models-bad.yaml", fmt.Sprintf(`
prices_checked: %q
default_model: m
providers:
p: { kind: openai, base_url: http://p, accepts_labels: ["Restricted-Synth"] }
models:
m: { provider: p, price: { input_per_m: 1, output_per_m: 2 } }
`, today))
if _, err := LoadModels(bad); err == nil || !strings.Contains(err.Error(), "lower-case") {
t.Errorf("a non-canonical accepts_labels entry must fail loud, got %v", err)
}
// The book side NORMALISES instead: three spellings of one label collapse to one entry.
bookPath := write("book.yaml", `
book_id: b
title: t
source_lang: zh
target_lang: ru
content_labels: [" Restricted-Synth ", restricted-synth, RESTRICTED-SYNTH]
pipeline: pipe.yaml
models: models.yaml
source_file: src.txt
ceilings: { book_usd: 1 }
`)
write("src.txt", "текст")
write("pipe.yaml", "core: C1\nversion: 1\n")
write("models.yaml", fmt.Sprintf("prices_checked: %q\ndefault_model: m\nproviders:\n p: { kind: openai, base_url: http://p }\nmodels:\n m: { provider: p, price: { input_per_m: 1, output_per_m: 2 } }\n", today))
b, err := LoadBook(bookPath)
if err != nil {
t.Fatalf("book load: %v", err)
}
if len(b.ContentLabels) != 1 || b.ContentLabels[0] != "restricted-synth" {
t.Errorf("content_labels must normalise to one canonical entry, got %v", b.ContentLabels)
}
}
// TestAcceptsLabelSetShape covers the entry-level validation of the capability list (empty string,
// duplicate) — small, but it is the only thing standing between a typo and a permission nobody meant.
func TestAcceptsLabelSetShape(t *testing.T) {
for _, c := range []struct{ name, list, want string }{
{"empty entry", `["", restricted-synth]`, "is empty"},
{"duplicate", `[restricted-synth, restricted-synth]`, "duplicate"},
} {
t.Run(c.name, func(t *testing.T) {
dir := t.TempDir()
p := filepath.Join(dir, "models.yaml")
body := fmt.Sprintf(`
prices_checked: %q
default_model: m
providers:
p: { kind: openai, base_url: http://p, accepts_labels: %s }
models:
m: { provider: p, price: { input_per_m: 1, output_per_m: 2 } }
`, time.Now().UTC().Format("2006-01-02"), c.list)
if err := os.WriteFile(p, []byte(body), 0o644); err != nil {
t.Fatal(err)
}
if _, err := LoadModels(p); err == nil || !strings.Contains(err.Error(), c.want) {
t.Fatalf("want an error mentioning %q, got %v", c.want, err)
}
})
}
}
// TestAllowActionAssertsWithoutRouting covers the third policy shape: `allow` applies the capability
// invariant and changes NO route. It is the shape a label needs when the providers are already correct —
// with only route/terminal such a label was inexpressible (route demands a chain, a budget and a
// per-stage model it has no use for; terminal refuses the book), which is why a two-label vocabulary
// where one label re-routes and the other only asserts could not be written as DATA at all.
func TestAllowActionAssertsWithoutRouting(t *testing.T) {
// An allow-only registry: no chain, no budget, no label_models anywhere.
allowOnly := `core: C1
version: 1
defaults: { max_output_ratio: 2, min_max_tokens: 512 }
content_policy:
- { label: restricted-synth, action: allow }
stages:
- name: draft
role: translator
model: capable-model
prompt_override: @PROMPT@
prompt_version: v
escalate_to: capable-hop
- name: edit
role: editor
model: second-model
prompt_override: @PROMPT@
prompt_version: v
`
_, load := labelFixture(t, allowOnly)
p, err := load([]string{"restricted-synth"})
if err != nil {
t.Fatalf("an allow-only policy must need no chain and no budget: %v", err)
}
if err := p.ContentRoutingError(); err != nil {
t.Fatalf("every reachable model accepts the label, so this must be runnable: %v", err)
}
// The route is untouched — that is the whole semantic.
for _, st := range p.Stages {
if st.ResolvedModel != st.Model || st.ResolvedHop != st.EscalateTo {
t.Errorf("stage %q must keep its configured route under action=allow: (%q,%q) vs (%q,%q)",
st.Name, st.ResolvedModel, st.ResolvedHop, st.Model, st.EscalateTo)
}
}
// …and the INVARIANT still bites: an endpoint that may not receive the label is refused, which is the
// only thing an allow policy does.
incapable := strings.Replace(allowOnly, "model: capable-model", "model: plain-model", 1)
_, loadBad := labelFixture(t, incapable)
pb, err := loadBad([]string{"restricted-synth"})
if err != nil {
t.Fatalf("load: %v", err)
}
rerr := pb.ContentRoutingError()
if rerr == nil || !strings.Contains(rerr.Error(), "may not receive content label") {
t.Fatalf("action=allow must still refuse an incapable endpoint — the invariant IS the policy, got %v", rerr)
}
}
// TestAllowActionRejectsRoutingKnobs pins that an allow policy refuses the knobs it cannot honour, rather
// than ignoring them: an author who wrote a chain or a per-stage model meant something the engine will
// not do, and silently dropping it is how a config comes to lie about its own routing.
func TestAllowActionRejectsRoutingKnobs(t *testing.T) {
base := `core: C1
version: 1
defaults: { max_output_ratio: 2, min_max_tokens: 512 }
content_policy:
- { label: restricted-synth, action: allow%s }
stages:
- name: draft
role: translator
model: capable-model
prompt_override: @PROMPT@
prompt_version: v
escalate_to: capable-hop%s
escalation:
chains: { restricted: [second-model] }
budget_usd: 1.0
`
cases := []struct{ name, policyTail, stageTail, want string }{
{"chain on an allow policy", ", chain: restricted", "", "takes no chain"},
{"label_models for an allow label", "", "\n label_models: { restricted-synth: second-model }", "is meaningless"},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
_, load := labelFixture(t, fmt.Sprintf(base, c.policyTail, c.stageTail))
if _, err := load(nil); err == nil || !strings.Contains(err.Error(), c.want) {
t.Fatalf("want a load error mentioning %q, got %v", c.want, err)
}
})
}
}
// TestRatifiedLabelPairIsDataOnly is the delta's acceptance shape, written as the owner ratified it: TWO
// labels, one that only ASSERTS (its providers are already correct, so no model moves) and one that
// RE-ROUTES (its endpoints differ). Both values here are synthetic — the point is that the SHAPE needs no
// Go change, so whichever vocabulary the data ends up carrying, the mechanism already holds it.
func TestRatifiedLabelPairIsDataOnly(t *testing.T) {
pair := `core: C1
version: 1
defaults: { max_output_ratio: 2, min_max_tokens: 512 }
content_policy:
- { label: restricted-synth, action: allow }
- { label: second-synth, action: route, chain: rerouted }
stages:
- name: draft
role: translator
model: capable-model
prompt_override: @PROMPT@
prompt_version: v
escalate_to: capable-hop
label_models: { second-synth: narrowed-model }
- name: edit
role: editor
model: capable-model
prompt_override: @PROMPT@
prompt_version: v
escalation:
chains: { rerouted: [second-model] }
budget_usd: 1.0
`
_, load := labelFixture(t, pair)
// (a) the asserting label alone: nothing moves, no chain/budget needed by it.
only, err := load([]string{"restricted-synth"})
if err != nil {
t.Fatalf("load: %v", err)
}
if err := only.ContentRoutingError(); err != nil {
t.Fatalf("restricted-synth must be runnable on capable endpoints: %v", err)
}
if only.Stages[0].ResolvedModel != "capable-model" || only.Stages[0].ResolvedHop != "capable-hop" {
t.Errorf("the asserting label must not move the route, got (%q,%q)", only.Stages[0].ResolvedModel, only.Stages[0].ResolvedHop)
}
// (b) BOTH labels: the re-routing one moves the draft leg, and the invariant is applied to the union —
// narrowed-model accepts only one of the two synthetic labels, so it must be refused here.
both, err := load([]string{"restricted-synth", "second-synth"})
if err != nil {
t.Fatalf("load: %v", err)
}
rerr := both.ContentRoutingError()
if rerr == nil || !strings.Contains(rerr.Error(), "narrowed-model") {
t.Fatalf("the invariant must be applied to the UNION of the book's labels (narrowed-model accepts only one), got %v", rerr)
}
if both.Stages[0].ResolvedModel != "narrowed-model" {
t.Errorf("the re-routing label must still resolve its stage, got %q", both.Stages[0].ResolvedModel)
}
// (c) the pair on endpoints that accept BOTH: runnable, one leg re-routed, the other untouched.
ok := strings.Replace(pair, "label_models: { second-synth: narrowed-model }", "label_models: { second-synth: second-model }", 1)
ok = strings.Replace(ok, "chains: { rerouted: [second-model] }", "chains: { rerouted: [capable-hop] }", 1)
_, loadOK := labelFixture(t, ok)
p, err := loadOK([]string{"restricted-synth", "second-synth"})
if err != nil {
t.Fatalf("load: %v", err)
}
if err := p.ContentRoutingError(); err != nil {
t.Fatalf("the ratified PAIR shape must be runnable on capable endpoints with zero Go changes: %v", err)
}
if p.Stages[0].ResolvedModel != "second-model" || p.Stages[0].ResolvedHop != "capable-hop" {
t.Errorf("draft resolved (%q,%q), want (second-model,capable-hop)", p.Stages[0].ResolvedModel, p.Stages[0].ResolvedHop)
}
if p.Stages[1].ResolvedModel != "capable-model" {
t.Errorf("the edit stage carries no route for either label, so it stays configured, got %q", p.Stages[1].ResolvedModel)
}
}