carlos / movement Public

Clone
git clone https://amadan.net/carlos/movement

Plain git — no account needed to clone.

Download

Download this file

1package movementtest
2
3import (
4 "encoding/json"
5 "os"
6 "path/filepath"
7 "strings"
8 "testing"
9
10 movement "amadan.net/carlos/movement/internal/movement"
11)
12
13// The copy in this app is reviewed by Paul, string by string, and applied
14// verbatim. This test is what stops it drifting afterwards.
15//
16// Without it, "applied verbatim" is a claim about one afternoon. A later
17// session fixing a typo, tightening a sentence or making a heading
18// "consistent" would be quietly overwriting somebody's reviewed words, and
19// nothing would notice. copy-review/result.json is the record of what was
20// approved, so it is the thing to compare against.
21
22// The durable record of what has been approved. NOT result.json: the review
23// server deletes that when a new round starts, so a test pointed at it loses
24// its guard the moment somebody opens another round — which is exactly when
25// copy is most likely to move. approved.json is written from a finished
26// round and outlives the next one.
27const reviewResult = "../../copy-review/approved.json"
28
29type reviewedString struct {
30 ID string `json:"id"`
31 Text string `json:"text"`
32}
33
34type reviewOutcome struct {
35 Action string `json:"action"`
36 Strings []reviewedString `json:"strings"`
37}
38
39// notInTheTable are ids that went through review but deliberately do not
40// appear in the copy table. Each one needs a reason here, so that dropping a
41// reviewed string is a decision on the record rather than an omission.
42var notInTheTable = map[string]string{
43 // Paul, after submitting: "Lose the JS required message ... that's an
44 // irrelevant implementation detail." The form still needs JavaScript; the
45 // page no longer explains why.
46 "form.nojs": "removed on request after review",
47
48 // These three are web addresses rather than words on a page. They are
49 // reviewed copy, but they live in the router.
50 "slug.never": "routing, not display copy",
51 "slug.curious": "routing, not display copy",
52 "slug.enthusiast": "routing, not display copy",
53}
54
55// One submitted string opened "This was entirely AI-generated. Human editor
56// leaving as-is:" and it was queried, because it reads like a note to whoever
57// is applying the copy. It is not. Paul confirmed on 2026-09-02 that it is
58// the joke: the people who said they are all in on AI get the model's output
59// verbatim.
60//
61// On 2026-09-03 he moved it: the disclosure now sits AFTER the text it
62// describes, reworded, as its own string awaiting its own review. So the
63// message shipped is the submitted text minus that prefix, which is the one
64// place this file allows a difference — and it names it rather than
65// tolerating a mismatch quietly.
66
67// changedAfterReview records strings the user directed a change to after
68// approving them. Each needs a date and what they asked for, so that a
69// difference from the review record is always a decision somebody made and
70// never drift nobody noticed.
71var changedAfterReview = map[string]string{
72 "stance.enthusiast.message": "2026-09-03: Paul moved the AI disclosure " +
73 "below the text it describes, so the prefix left this string and " +
74 "became stance.enthusiast.disclosure, which awaits its own review.",
75}
76
77// pendingAgain is computed from the copy table itself rather than listed by
78// hand: anything the app still marks as a placeholder is mid-round, whatever
79// it was approved as previously.
80var pendingAgain = func() map[string]bool {
81 m := map[string]bool{}
82 for _, id := range movement.Placeholders() {
83 m[id] = true
84 }
85 return m
86}()
87
88func TestEveryStringMatchesTheApprovedReview(t *testing.T) {
89 raw, err := os.ReadFile(filepath.FromSlash(reviewResult))
90 if err != nil {
91 t.Fatalf("no review record to check against: %v", err)
92 }
93 var out reviewOutcome
94 if err := json.Unmarshal(raw, &out); err != nil {
95 t.Fatalf("unreadable review record: %v", err)
96 }
97 if out.Action != "approve" {
98 t.Fatalf("the review record says %q, not approve — nothing should have shipped", out.Action)
99 }
100
101 checked := 0
102 for _, s := range out.Strings {
103 if why, skipped := notInTheTable[s.ID]; skipped {
104 t.Logf("%s: not in the table (%s)", s.ID, why)
105 continue
106 }
107
108 // A string that has been re-marked as a placeholder is awaiting a new
109 // round, so the old approved text is no longer what it should equal.
110 // Production still refuses to serve it, which is the real guard.
111 if pendingAgain[s.ID] {
112 t.Logf("%s: re-opened for review, not compared", s.ID)
113 continue
114 }
115
116 if why, changed := changedAfterReview[s.ID]; changed {
117 // Still checked, but against what the change left behind: the
118 // submitted text with the moved prefix removed.
119 const prefix = "This was entirely AI-generated. Human editor leaving as-is:"
120 rest, found := strings.CutPrefix(s.Text, prefix)
121 if !found {
122 t.Errorf("%s is recorded as changed after review (%s) but no "+
123 "longer matches the shape that change expected", s.ID, why)
124 continue
125 }
126 if got := movement.T(s.ID); got != strings.TrimLeft(rest, "\n ") {
127 t.Errorf("%s differs from the approved text by more than the "+
128 "change on record (%s).\n approved: %q\n shipped : %q",
129 s.ID, why, s.Text, got)
130 }
131 checked++
132 continue
133 }
134
135 want := s.Text
136 got := movement.T(s.ID)
137 if got != want {
138 t.Errorf("%s does not match what was approved.\n approved: %q\n shipped : %q",
139 s.ID, want, got)
140 }
141 checked++
142 }
143
144 if checked == 0 {
145 t.Fatal("the review record matched nothing; the ids have drifted apart")
146 }
147 t.Logf("%d reviewed strings match", checked)
148}
149
150// A string in the table that nobody reviewed is the failure this whole gate
151// exists to prevent, so it is worth catching directly rather than trusting
152// that the next person remembers.
153func TestNoStringShippedWithoutReview(t *testing.T) {
154 raw, err := os.ReadFile(filepath.FromSlash(reviewResult))
155 if err != nil {
156 t.Fatalf("no review record: %v", err)
157 }
158 var out reviewOutcome
159 if err := json.Unmarshal(raw, &out); err != nil {
160 t.Fatal(err)
161 }
162
163 reviewed := map[string]bool{}
164 for _, s := range out.Strings {
165 reviewed[s.ID] = true
166 }
167
168 // A string still marked as a placeholder has not shipped: the app
169 // refuses to serve the canonical host while any remain, which is the
170 // mechanism that lets design work run ahead of the next copy round.
171 pending := map[string]bool{}
172 for _, id := range movement.Placeholders() {
173 pending[id] = true
174 }
175
176 for _, id := range movement.CopyIDs() {
177 if !reviewed[id] && !pending[id] {
178 t.Errorf("%q is in the copy table, was never reviewed, and is not "+
179 "marked as a placeholder — so it would ship unreviewed", id)
180 }
181 }
182}
183