package cockpit import ( "encoding/json" "os" "path/filepath" "testing" ) // loadAllSources reads every shipped playbook JSON source directly (no // internal/playbooks import → no cycle), so the cockpit package can assert the // safety invariant against the real library. func loadAllSources(t *testing.T) []Playbook { t.Helper() dir := filepath.Join("..", "playbooks", "data") entries, err := os.ReadDir(dir) if err != nil { t.Fatalf("read playbook data dir: %v", err) } var out []Playbook for _, e := range entries { if e.IsDir() || filepath.Ext(e.Name()) != ".json" { continue } b, err := os.ReadFile(filepath.Join(dir, e.Name())) if err != nil { t.Fatalf("read %s: %v", e.Name(), err) } var pb Playbook if err := json.Unmarshal(b, &pb); err != nil { t.Fatalf("parse %s: %v", e.Name(), err) } out = append(out, pb) } if len(out) == 0 { t.Fatal("no playbook sources found") } return out } // TestAllSources_NoWriteGitVerb is the S1 trap-door test: every shipped // playbook's composed allowlist must hold the safety invariant (zero // write-git verbs), so a `git tag` slipping into a capability fails at test // time, not at emit time. func TestAllSources_NoWriteGitVerb(t *testing.T) { for _, pb := range loadAllSources(t) { if hits := ScanAllowlistForWriteGitVerbs(composeAllowedTools(pb), pb.Intent.forbiddenVerbs()); len(hits) > 0 { t.Errorf("playbook %q grants forbidden write-git verb(s): %v", pb.Name, hits) } } } // TestAllSources_RenderOnEveryTarget: every shipped playbook renders without // error on the per-playbook targets, and the whole set renders on the // aggregate targets. func TestAllSources_RenderOnEveryTarget(t *testing.T) { set := loadAllSources(t) for _, target := range []string{"claude", "cursor"} { r, _ := rendererFor(target) for _, pb := range set { if _, err := r.Render(pb); err != nil { t.Errorf("%s render of %q: %v", target, pb.Name, err) } } } for _, target := range []string{"agents", "gemini"} { r, _ := rendererFor(target) agg, _ := isAggregate(r) if _, err := agg.RenderAll(set); err != nil { t.Errorf("%s RenderAll: %v", target, err) } } }