| 1 | // approved_seeded_copy_test.go |
| 2 | package main |
| 3 | |
| 4 | import ( |
| 5 | "os" |
| 6 | "path/filepath" |
| 7 | "regexp" |
| 8 | "strconv" |
| 9 | "strings" |
| 10 | "testing" |
| 11 | ) |
| 12 | |
| 13 | // The copy gate of the seeded-instances series (#260, #261). The deck is |
| 14 | // reviewed BEFORE any code speaks a sentence (the reverse of the moves |
| 15 | // record, where code came first), so the gate has two lists: |
| 16 | // |
| 17 | // - seededCopyIDs: every row the deck holds, pinned both ways; |
| 18 | // - seededWiredIDs: the rows code already speaks. Each plan's wiring task |
| 19 | // appends the ids it wired, and the gate checks each one's text is in |
| 20 | // the file its row names. |
| 21 | |
| 22 | const seededCopyRecordPath = "docs/superpowers/plans/2026-10-08-seeded-instances-copy.md" |
| 23 | |
| 24 | var seededCopyIDPattern = regexp.MustCompile(`^SD-N[0-9]+$`) |
| 25 | |
| 26 | var seededCopyIDs = func() []string { |
| 27 | ids := make([]string, 0, 140) |
| 28 | for n := 1; n <= 140; n++ { |
| 29 | ids = append(ids, "SD-N"+strconv.Itoa(n)) |
| 30 | } |
| 31 | return ids |
| 32 | }() |
| 33 | |
| 34 | // seededWiredIDs grows as the plans wire rows. Plan 1 wires none; Plan 3 |
| 35 | // Task 3 wires SD-N1, Task 5 SD-N2 to SD-N8 and SD-N113, Task 6 SD-N9 to SD-N13, |
| 36 | // SD-N111 and SD-N112; Plan 4 Task 3 SD-N14 to SD-N19; Plan 5 Task 3 SD-N27 to SD-N29, |
| 37 | // Task 5 SD-N20 to SD-N26, SD-N30 to SD-N39 and SD-N41; Plan 5 Task 6 SD-N40, SD-N42 to SD-N47, SD-N83 to SD-N88 and SD-N114; |
| 38 | // Plan 6 Task 1 SD-N48 to SD-N50, SD-N62 and SD-N67; Plan 6 Task 2 SD-N51 to SD-N53, SD-N59, SD-N61 and SD-N63; |
| 39 | // Plan 6 Task 3 SD-N54, SD-N55, SD-N60, SD-N64, SD-N65 and SD-N115; Plan 6 Task 5 SD-N56 to SD-N58 and SD-N66; |
| 40 | // Plan 6 Task 6 SD-N68 to SD-N82, SD-N89 and SD-N90; Plan 7 Task 3 SD-N91 to SD-N98; Plan 7 Task 6 SD-N99; |
| 41 | // Plan 8 Task 3 SD-N100 and SD-N101; Plan 8 Task 5 SD-N102 to SD-N110 and SD-N116; |
| 42 | // the 2026-10-10 copy review (round 2) SD-N117 to SD-N136, with SD-N110's last sentence replaced. |
| 43 | var seededWiredIDs = []string{"SD-N1", "SD-N2", "SD-N3", "SD-N4", "SD-N5", "SD-N6", "SD-N7", "SD-N8", "SD-N113", "SD-N9", "SD-N10", "SD-N11", "SD-N12", "SD-N13", "SD-N111", "SD-N112", "SD-N14", "SD-N15", "SD-N16", "SD-N17", "SD-N18", "SD-N19", "SD-N27", "SD-N28", "SD-N29", |
| 44 | "SD-N20", "SD-N21", "SD-N22", "SD-N23", "SD-N24", "SD-N25", "SD-N26", "SD-N30", "SD-N31", "SD-N32", "SD-N33", "SD-N34", "SD-N35", "SD-N36", "SD-N37", "SD-N38", "SD-N39", "SD-N41", |
| 45 | "SD-N40", "SD-N42", "SD-N43", "SD-N44", "SD-N45", "SD-N46", "SD-N47", "SD-N114", "SD-N83", "SD-N84", "SD-N85", "SD-N86", "SD-N87", "SD-N88", |
| 46 | "SD-N48", "SD-N49", "SD-N50", "SD-N62", "SD-N67", |
| 47 | "SD-N51", "SD-N52", "SD-N53", "SD-N59", "SD-N61", "SD-N63", |
| 48 | "SD-N54", "SD-N55", "SD-N60", "SD-N64", "SD-N65", "SD-N115", |
| 49 | "SD-N56", "SD-N57", "SD-N58", "SD-N66", |
| 50 | "SD-N68", "SD-N69", "SD-N70", "SD-N71", "SD-N72", "SD-N73", "SD-N74", "SD-N75", "SD-N76", "SD-N77", "SD-N78", "SD-N79", |
| 51 | "SD-N80", "SD-N81", "SD-N82", "SD-N89", "SD-N90", |
| 52 | "SD-N91", "SD-N92", "SD-N93", "SD-N94", "SD-N95", "SD-N96", "SD-N97", "SD-N98", |
| 53 | "SD-N99", |
| 54 | "SD-N100", "SD-N101", |
| 55 | "SD-N102", "SD-N103", "SD-N104", "SD-N105", "SD-N106", "SD-N107", "SD-N108", "SD-N109", "SD-N110", "SD-N116", |
| 56 | "SD-N117", "SD-N118", "SD-N119", "SD-N120", "SD-N121", "SD-N122", "SD-N123", "SD-N124", "SD-N125", "SD-N126", "SD-N127", "SD-N128", "SD-N129", "SD-N130", "SD-N131", "SD-N132", "SD-N133", "SD-N134", "SD-N135", "SD-N136", |
| 57 | // the claim-activation refusals over seed state, approved 2026-10-13. |
| 58 | "SD-N137", "SD-N138", "SD-N139", "SD-N140"} |
| 59 | |
| 60 | func mustParseSeededRecord(t *testing.T) []copyRow { |
| 61 | t.Helper() |
| 62 | rows, err := parseSeededCopyRecord(seededCopyRecordPath) |
| 63 | if err != nil { |
| 64 | t.Fatal(err) |
| 65 | } |
| 66 | if len(rows) == 0 { |
| 67 | t.Fatalf("%s has no rows", seededCopyRecordPath) |
| 68 | } |
| 69 | return rows |
| 70 | } |
| 71 | |
| 72 | func TestSeededCopyRecordIsComplete(t *testing.T) { |
| 73 | reportAll(t, checkMovesInventory(mustParseSeededRecord(t), seededCopyIDs)) |
| 74 | } |
| 75 | |
| 76 | func TestSeededCopyHasNoEmDash(t *testing.T) { |
| 77 | reportAll(t, checkMovesNoEmDash(mustParseSeededRecord(t))) |
| 78 | } |
| 79 | |
| 80 | func TestSeededWiredCopyIsApplied(t *testing.T) { |
| 81 | reportAll(t, checkSeededWired(mustParseSeededRecord(t), seededWiredIDs, ".")) |
| 82 | } |
| 83 | |
| 84 | func TestParseSeededCopyTableKeepsPipes(t *testing.T) { |
| 85 | src := "| id | file | text | status |\n|---|---|---|---|\n" + |
| 86 | "| SD-N1 | a.go | one | two | pending |\n" |
| 87 | rows, err := parseCopyTable(src, "x.md", seededCopyIDPattern, "an SD-N<n>") |
| 88 | if err != nil { |
| 89 | t.Fatal(err) |
| 90 | } |
| 91 | if len(rows) != 1 || rows[0].Text != "one | two" { |
| 92 | t.Fatalf("rows = %+v", rows) |
| 93 | } |
| 94 | if _, err := parseCopyTable(strings.Replace(src, "SD-N1", "MV-N1", 1), "x.md", seededCopyIDPattern, "an SD-N<n>"); err == nil || |
| 95 | !strings.Contains(err.Error(), "is not an SD-N<n> id") { |
| 96 | t.Fatalf("a moves id in the seeded deck: err = %v", err) |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | func TestSeededCopyInventoryBitesBothWays(t *testing.T) { |
| 101 | rows := []copyRow{{"SD-N1", "a.go", "x", "pending"}, {"SD-N3", "a.go", "y", "pending"}} |
| 102 | got := checkMovesInventory(rows, []string{"SD-N1", "SD-N2"}) |
| 103 | if len(got) != 2 { |
| 104 | t.Fatalf("problems = %q, want a missing SD-N2 and an extra SD-N3", got) |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | func TestSeededWiredCheckBites(t *testing.T) { |
| 109 | dir := t.TempDir() |
| 110 | writeFile(t, dir, "a.go", "package a\n\nvar s = \"hello there\"\n") |
| 111 | rows := []copyRow{ |
| 112 | {"SD-N1", "a.go", "hello there", "approved"}, |
| 113 | {"SD-N2", "a.go", "goodbye", "approved"}, |
| 114 | {"SD-N3", "a.go", "hello there", "pending"}, |
| 115 | } |
| 116 | if got := checkSeededWired(rows, []string{"SD-N1"}, dir); len(got) != 0 { |
| 117 | t.Errorf("a wired, applied row: problems = %q", got) |
| 118 | } |
| 119 | if got := checkSeededWired(rows, []string{"SD-N2"}, dir); len(got) != 1 || !strings.Contains(got[0], "SD-N2") { |
| 120 | t.Errorf("a wired row whose text is missing: problems = %q", got) |
| 121 | } |
| 122 | if got := checkSeededWired(rows, []string{"SD-N3"}, dir); len(got) != 1 || !strings.Contains(got[0], "pending") { |
| 123 | t.Errorf("a wired pending row: problems = %q", got) |
| 124 | } |
| 125 | if got := checkSeededWired(rows, []string{"SD-N9"}, dir); len(got) != 1 || !strings.Contains(got[0], "no row") { |
| 126 | t.Errorf("a wired id with no row: problems = %q", got) |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | func parseSeededCopyRecord(path string) ([]copyRow, error) { |
| 131 | raw, err := os.ReadFile(path) |
| 132 | if err != nil { |
| 133 | return nil, err |
| 134 | } |
| 135 | return parseCopyTable(string(raw), path, seededCopyIDPattern, "an SD-N<n>") |
| 136 | } |
| 137 | |
| 138 | // checkSeededWired checks every wired id: it has a row, the row is |
| 139 | // approved, and its text is in the file the row names (the moves gate's own |
| 140 | // matcher, so a Go file must carry it as one literal, concatenation or |
| 141 | // format, and any other file raw). |
| 142 | func checkSeededWired(rows []copyRow, wired []string, root string) []string { |
| 143 | byID := map[string]copyRow{} |
| 144 | for _, r := range rows { |
| 145 | byID[r.ID] = r |
| 146 | } |
| 147 | var out []string |
| 148 | var applyRows []copyRow |
| 149 | for _, id := range wired { |
| 150 | r, ok := byID[id] |
| 151 | switch { |
| 152 | case !ok: |
| 153 | out = append(out, id+": wired, but the deck has no row for it") |
| 154 | case r.Status != "approved": |
| 155 | out = append(out, id+": wired, but its row is "+r.Status+": only approved text may reach code") |
| 156 | default: |
| 157 | applyRows = append(applyRows, r) |
| 158 | } |
| 159 | } |
| 160 | return append(out, checkMovesApplied(applyRows, root)...) |
| 161 | } |
| 162 | |
| 163 | // checkSeededApproved fails on any row that is not approved. Plan 1 Task 2 |
| 164 | // adds the test that calls it, once Paul's review has landed. |
| 165 | func checkSeededApproved(rows []copyRow) []string { |
| 166 | var out []string |
| 167 | for _, r := range rows { |
| 168 | if r.Status != "approved" { |
| 169 | out = append(out, r.ID+" is "+r.Status+": the deck is reviewed before any plan wires a string") |
| 170 | } |
| 171 | } |
| 172 | return out |
| 173 | } |
| 174 | |
| 175 | func writeFile(t *testing.T, dir, name, body string) { |
| 176 | t.Helper() |
| 177 | if err := os.WriteFile(filepath.Join(dir, name), []byte(body), 0o644); err != nil { |
| 178 | t.Fatal(err) |
| 179 | } |
| 180 | } |
| 181 | |
| 182 | // Every row is reviewed before any plan of the series wires one (Plan 1 |
| 183 | // Task 2). A row added later is reviewed before the plan that adds it |
| 184 | // merges, and this test holds that line. |
| 185 | func TestSeededCopyIsApproved(t *testing.T) { |
| 186 | reportAll(t, checkSeededApproved(mustParseSeededRecord(t))) |
| 187 | } |
| 188 | |
| 189 | // TestEverySeededRowIsWired holds the series' end state: every approved |
| 190 | // deck row is spoken by the code that owns it (Plan 8 Task 6). |
| 191 | func TestEverySeededRowIsWired(t *testing.T) { |
| 192 | wired := map[string]bool{} |
| 193 | for _, id := range seededWiredIDs { |
| 194 | wired[id] = true |
| 195 | } |
| 196 | for _, id := range seededCopyIDs { |
| 197 | if !wired[id] { |
| 198 | t.Errorf("%s is approved and never wired", id) |
| 199 | } |
| 200 | } |
| 201 | } |
| 202 | |