package memoriatest import ( "html" "net/http" "net/url" "regexp" "strings" "testing" ) // The fact box, end to end: through the real handler, the real template // and the real escaping. // // **Through HTTP and not against the struct, deliberately, and gate 34 is // the opposite deliberately.** Gate 34 is about what the box may CONTAIN, // where two dispositions render identical pages and only the struct can // tell them apart. These are about what reaches the reader, where a // template that escapes a span, drops a label or renders the box outside // the swapped region is exactly the defect — and a struct assertion // cannot see any of it. AGENTS.md: a template test proves markup exists, // not that it renders. var factValueRe = regexp.MustCompile(`([^<]*)`) var factLabelRe = regexp.MustCompile(`([^<]*)`) func factValues(body string) []string { return capture(factValueRe, body) } func factLabels(body string) []string { return capture(factLabelRe, body) } func hasFactBox(body string) bool { return strings.Contains(body, "
") } // capture pulls the rendered text out of the box and UNESCAPES it. // // html/template escapes "+" to "+" in text content, which is correct // and which the reader never sees. Gate 36 is about the bytes that reach // the READER — "byte-identical to the document" is a claim about what is // displayed, not about the wire — so comparing raw markup would fail the // gate on the escaping working properly, and "fixing" that would mean // unescaping the span into the page, which is an injection. func capture(re *regexp.Regexp, s string) []string { var out []string for _, m := range re.FindAllStringSubmatch(s, -1) { out = append(out, html.UnescapeString(m[1])) } return out } func search(t *testing.T, h http.Handler, q string) string { t.Helper() return get(t, h, "/?q="+url.QueryEscape(q)).Body.String() } // GATE 35 — candidates are canonical values, ambiguity is enumerated // rather than collapsed, and the cap is three (§8.8, §10). // // One test rather than five, because the rules share a definition and a // gate that splits them cannot see the interactions: the same number in // five documents is one candidate AND therefore does not pass the cap. // // Mutations run, each verified red and reverted, 2026-09-14: // - returning found[k][:1] when there is more than one — "two distinct" // goes red. // - keying dedupe on Span instead of Canonical — "two spellings" red. // - keying dedupe on the passage rather than the value — "five // documents" red. // - cap written len(cs) >= MaxFacts — "three still renders" red, and // nothing else, which is the point of having it. // - cap and collision counted across kinds rather than per kind — the // two-kind case red. func TestGate35CandidatesAmbiguityAndTheCap(t *testing.T) { t.Run("presence: one number, one box, one value", func(t *testing.T) { h := newApp(t) store(t, h, "Tito office", "## Contact\n\nOffice phone: +353 (0)1 547 7854") body := search(t, h, "phone number") if got := factValues(body); len(got) != 1 || got[0] != "+353 (0)1 547 7854" { t.Fatalf("values = %q, want the one number", got) } }) // Two right answers to an underspecified question. §8.7 refuses in // this position; §8.8 enumerates, because two phone numbers are not // a wrong average — and the labels are what let the reader choose. t.Run("two distinct numbers are both named, neither picked", func(t *testing.T) { h := newApp(t) store(t, h, "Tito office", "## Contact\n\nOffice phone: +353 (0)1 547 7854") store(t, h, "Support", "## Contact\n\nSupport phone: +353 (0)1 598 1234") got := factValues(search(t, h, "phone number")) if len(got) != 2 { t.Fatalf("values = %q, want both", got) } }) // A dedupe keyed on the span passes any fixture that spells the // number the same way twice, which is why this one does not. t.Run("two spellings of one number are one candidate", func(t *testing.T) { h := newApp(t) store(t, h, "Tito office", "## Contact\n\nOffice phone: +353 (0)1 547 7854") store(t, h, "Handbook", "## Contact\n\nOffice phone: 01 547 7854") if got := factValues(search(t, h, "phone number")); len(got) != 1 { t.Fatalf("values = %q, want one: the same number written two ways", got) } }) // The commonest real shape — a signature repeated through forwarded // mail. An implementation counting passages rather than values shows // five rows here, or passes the cap and shows none. t.Run("the same number in five documents is one candidate", func(t *testing.T) { h := newApp(t) for _, title := range []string{"One", "Two", "Three", "Four", "Five"} { store(t, h, title, "## Contact\n\nOffice phone: +353 (0)1 547 7854") } if got := factValues(search(t, h, "phone number")); len(got) != 1 { t.Fatalf("values = %q, want one", got) } }) // index/rank_test.go's own paragraph, which came off the live // instance. An implementation treating the CHUNK as the unit hides // the second number — the one the document calls unused. t.Run("two numbers in one passage are two candidates", func(t *testing.T) { h := newApp(t) store(t, h, "Office address", "## Office Address\n\nTurn the knob. "+ "The office phone number is: +353 (0)1 547 7854. "+ "We also have the following unused phone numbers: +353 (0)1 598 1234.") body := search(t, h, "phone number") if got := factValues(body); len(got) != 2 { t.Fatalf("values = %q, want both", got) } // And the labels are what make the enumeration honest: the // document said which is unused, so the page does. labels := factLabels(body) if len(labels) != 2 || !strings.Contains(labels[1], "unused") { t.Fatalf("labels = %q; the document's own word for the second number is missing", labels) } }) // **Three AND four.** A cap written `>= MaxFacts` passes a suite that // tests only one, two and four. t.Run("three renders, four renders nothing", func(t *testing.T) { nums := []string{"+353 (0)1 547 7854", "+353 (0)1 598 1234", "+353 (0)1 555 0101", "+353 (0)1 555 0202"} for _, n := range []int{3, 4} { h := newApp(t) for i := 0; i < n; i++ { store(t, h, "Office "+string(rune('A'+i)), "## Contact\n\nOffice phone: "+nums[i]) } body := search(t, h, "phone number") switch { case n == 3 && len(factValues(body)) != 3: t.Errorf("three candidates rendered %q, want three", factValues(body)) case n == 4 && hasFactBox(body): t.Errorf("four candidates rendered a box: %q", factValues(body)) } } }) // Every rule is per kind. Counted across kinds, two of each passes // the cap and both boxes vanish. t.Run("a two-kind query renders both kinds", func(t *testing.T) { h := newApp(t) store(t, h, "Tito office", "## Contact\n\nOffice phone: +353 (0)1 547 7854\n\nEircode: D02 X285") store(t, h, "Support", "## Contact\n\nSupport phone: +353 (0)1 598 1234\n\nEircode: A65 F4E2") got := factValues(search(t, h, "phone number and eircode")) if len(got) != 4 { t.Fatalf("values = %q, want two of each kind", got) } }) // The whole citation line collides, so the page would print one // identical row twice against two different values. t.Run("colliding rows render no box", func(t *testing.T) { h := newApp(t) store(t, h, "Suppliers", "## Phone\n\n- +353 (0)1 555 0101\n- +353 (0)1 555 0202") if body := search(t, h, "phone number"); hasFactBox(body) { t.Errorf("rendered a box with two identical rows: %q / %q", factLabels(body), factValues(body)) } }) // §10 says this gate asserts first-seen order, and for a while it // asserted only the count. A box sorted by canonical value is // deterministic and passes every length check, and the two-values-in- // one-passage fixture happens to sort the same way it was seen — so // nothing here could see it. Found by review, 2026-09-14. t.Run("rows are in first-seen order", func(t *testing.T) { h := newApp(t) // Both orderings a wrong implementation would pick disagree with // document order here: sorted by VALUE the 547 number comes // first, and sorted by LABEL "Alternate" sorts before "Work". // Only first-seen gives 598, so this one fixture holds against // both — and the version it replaces held against neither, // because its two labels happened to sort as they were seen. store(t, h, "Tito office", "## Contact\n\n"+ "Work phone is: +353 (0)1 598 1234. "+ "Alternate phone is: +353 (0)1 547 7854.") got := factValues(search(t, h, "phone number")) if len(got) != 2 { t.Fatalf("values = %q, want two", got) } if got[0] != "+353 (0)1 598 1234" { t.Errorf("first row = %q; the box is not in the order the document states them", got[0]) } }) // Every collision fixture above is a PAIR, so a check comparing only // neighbours passes all of them. Three rows whose first and third are // identical is the shape that sees it. t.Run("a collision between non-adjacent rows still suppresses", func(t *testing.T) { h := newApp(t) store(t, h, "Tito office", "## Contact\n\n"+ "Ring the office phone on 01 547 7854 any weekday. "+ "Support phone: 01 598 1234. "+ "Or dial the phone on 01 555 0101 instead.") if body := search(t, h, "phone number"); hasFactBox(body) { t.Errorf("rendered a box whose first and third rows are identical: %q / %q", factLabels(body), factValues(body)) } }) // The mirror of the title case below, for the trail. Same title, same // label, different headings — the trail is what tells them apart, and // a collision check ignoring it loses a real answer. t.Run("the same label under different headings still renders", func(t *testing.T) { h := newApp(t) store(t, h, "Tito office", "## Office\n\nPhone: +353 (0)1 547 7854\n\n## Support\n\nPhone: +353 (0)1 598 1234") if got := factValues(search(t, h, "phone number")); len(got) != 2 { t.Fatalf("values = %q, want both: the headings tell them apart", got) } }) // A different title disambiguates perfectly, so this one MUST box. // Keying collision on the label alone loses a real answer here. t.Run("the same label under different titles still renders", func(t *testing.T) { h := newApp(t) store(t, h, "Tito office", "## Contact\n\nOffice phone: +353 (0)1 547 7854") store(t, h, "Acme supplier", "## Contact\n\nOffice phone: +353 (0)1 598 1234") if got := factValues(search(t, h, "phone number")); len(got) != 2 { t.Fatalf("values = %q, want both: the titles tell them apart", got) } }) } // GATE 36 — the displayed value is byte-identical to the document's // (§8.8, §10). The canonical form exists for matching and must never // reach the page. // // Mutation: canonicalise on the display path — one line's difference from // what the code already does for identity — and every case goes red. func TestGate36TheDisplayedValueIsTheDocumentsBytes(t *testing.T) { for _, c := range []struct{ name, body, want string }{ {"as written", "## Contact\n\nOffice phone: +353 (0)1 547 7854", "+353 (0)1 547 7854"}, {"with an extension", "## Contact\n\nOffice phone: +353 (0)1 547 7854 ext. 12", "+353 (0)1 547 7854 ext. 12"}, // Pasted from Word or Notion. Go's \s does not match U+00A0, so // a recogniser written with it finds nothing here and this case // fails on PRESENCE rather than on the bytes — which is the // design demand and not an accident. {"pasted with non-breaking spaces", "## Contact\n\nOffice phone: +353 (0)1 547 7854", "+353 (0)1 547 7854"}, } { t.Run(c.name, func(t *testing.T) { h := newApp(t) store(t, h, "Tito office", c.body) got := factValues(search(t, h, "phone number")) if len(got) != 1 { t.Fatalf("values = %q, want one", got) } if got[0] != c.want { t.Errorf("value = %q, want %q", got[0], c.want) } }) } } // GATE 37 — the label is the document's, and it reaches the page. // // The exhaustive matrix — bullets, adjacent values, abbreviations, the // cap boundary, the no-heading fallback — is unit-tested in // internal/memoria/fact, where each rule can be driven directly. What // this gate adds is the half a unit test cannot see: that the label // survives the store, the template and the escaping, and that a value the // document labelled too long to show renders NO BOX rather than a vaguer // substitute. func TestGate37TheLabelReachesThePage(t *testing.T) { t.Run("the document's own label is what is shown", func(t *testing.T) { h := newApp(t) store(t, h, "Tito office", "## Contact\n\nOffice phone: +353 (0)1 547 7854") if got := factLabels(search(t, h, "phone number")); len(got) != 1 || got[0] != "Office phone" { t.Fatalf("labels = %q, want [Office phone]", got) } }) t.Run("no label in the line falls to the heading trail", func(t *testing.T) { h := newApp(t) store(t, h, "Tito office", "## Contact\n\nRing the office phone on +353 (0)1 547 7854 any weekday.") if got := factLabels(search(t, h, "phone number")); len(got) != 1 || got[0] != "Contact" { t.Fatalf("labels = %q, want [Contact]", got) } }) // Substituting the trail would replace the document's own warning // with a vaguer word, beside a number the document did not warn // about. Dropping just this candidate would turn a two-candidate // ambiguity into a confident single answer. Both are worse than // silence. t.Run("a label too long to show renders no box at all", func(t *testing.T) { h := newApp(t) store(t, h, "Tito office", "## Contact\n\nReception phone: +353 (0)1 547 7854\n\n"+ "The number you must never give to any customer because it rings the chief executive directly is: +353 (0)1 598 1234") if body := search(t, h, "phone number"); hasFactBox(body) { t.Errorf("rendered a box: %q / %q", factLabels(body), factValues(body)) } }) } // The box lives INSIDE the results define, so the fragment search.js // swaps carries it. Outside, query N-1's answer would sit over query N's // results on every keystroke — §8.4 inverted, several times a second. // // This is the page-and-fragment agreement that TestALookupPrimesTheFirstResult // makes for priming, and it is needed for the same reason: two renderings // of one region that can disagree are two regions. func TestTheFactBoxIsInsideTheSwappedRegion(t *testing.T) { h := newApp(t) store(t, h, "Tito office", "## Contact\n\nOffice phone: +353 (0)1 547 7854") page := search(t, h, "phone number") fragment := get(t, h, "/search?q="+url.QueryEscape("phone number")).Body.String() if !hasFactBox(page) { t.Fatal("no box on the page") } if !hasFactBox(fragment) { t.Error("no box in the fragment: it would not survive a keystroke") } if a, b := factValues(page), factValues(fragment); len(a) != len(b) || a[0] != b[0] { t.Errorf("page %q and fragment %q disagree", a, b) } } // Enter goes to the first RESULT, never to the box's citation. §8.8: // changing what Enter means depending on whether a regular expression // matched is the unreliable-Enter failure PrimesFirstResult exists to // avoid. search.js targets rst-result-title, so the guard is that the // box's link never wears it. func TestTheFactBoxCitationIsNotAnEnterTarget(t *testing.T) { h := newApp(t) store(t, h, "Tito office", "## Contact\n\nOffice phone: +353 (0)1 547 7854") body := search(t, h, "phone number") if !hasFactBox(body) { t.Fatal("no box to check: this assertion needs one to be wrong about") } box := body[strings.Index(body, "
"):] box = box[:strings.Index(box, "
")] if strings.Contains(box, "rst-result-title") { t.Errorf("the box carries search.js's Enter target:\n%s", box) } } // **The reachability limit, pinned rather than discovered.** // // §8.8 states it: the box only sees what retrieval returned, so a fact in // a document the query does not reach is a fact the box does not have. // This is not a small edge — it is the ordinary case for a contact page // that writes "Office: +353 …" and never uses the word "phone" — and // writing the gates is what made it concrete, because every fixture above // had to be given a word the query could find. // // Asserted in BOTH directions, because a limit tested only in the // direction that fails is indistinguishable from the feature not working // at all: // // - a document sharing no word with the query renders no box; // - the SAME document, reached by a query that shares one, does. // // §12 carries the fix and names what it must not be. Until then this test // is the honest statement of what the feature does, and it is here so // that the day somebody builds the lift, this goes red and gets deleted // on purpose. func TestTheBoxOnlySeesWhatRetrievalReturned(t *testing.T) { const body = "## Contact\n\nOffice: +353 (0)1 547 7854" h := newApp(t) store(t, h, "Tito office", body) // "phone number" appears nowhere in that document, so nothing ranks // and there is nothing to extract from. if got := search(t, h, "phone number"); hasFactBox(got) { t.Errorf("a box appeared for a query the document does not match: %q", factValues(got)) } // One shared word — "office", in the title and in the line — and the // same fact is found. This is the half that proves the absence above // is the limit rather than a broken recogniser. if got := factValues(search(t, h, "office phone number")); len(got) != 1 { t.Fatalf("values = %q, want the number: the query shares a word with the document", got) } } var factNoteRe = regexp.MustCompile(`

([^<]*)

`) // The page that motivated §8.8's narrowing (Paul, 2026-09-29), through // the real handler and template, on the page and in the fragment // search.js swaps. Gate 40 holds the rule at store level; this holds that // the row and its line reach the reader, and that the bullet is gone. func TestTheNarrowedBoxReachesThePageWithItsLine(t *testing.T) { h := newApp(t) store(t, h, "Office", "## Office Address\n\n"+ "- Turn the knob\n"+ "- The office phone number is: +353 (0)1 547 7854\n"+ "- We also have the following unused phone numbers: +353 (0)1 598 1234\n"+ " - +353 (0)1 598 1235\n"+ " - +353 (0)1 598 1236\n") q := "what is the office phone number?" for name, body := range map[string]string{ "page": search(t, h, q), "fragment": get(t, h, "/search?q="+url.QueryEscape(q)).Body.String(), } { if got := factValues(body); len(got) != 1 || got[0] != "+353 (0)1 547 7854" { t.Fatalf("%s: values = %q, want the office number alone", name, got) } if got := factLabels(body); len(got) != 1 || got[0] != "The office phone number is" { t.Errorf("%s: labels = %q, want the document's own without its bullet", name, got) } want := "Matched “office”. 3 other phone numbers are in these results." if got := capture(factNoteRe, body); len(got) != 1 || got[0] != want { t.Errorf("%s: note = %q, want %q", name, got, want) } // The citation survives narrowing: title, trail and a link to the // passage, not just the value. if !regexp.MustCompile(`Office › Office Address`).MatchString(body) { t.Errorf("%s: the narrowed row's citation is missing or wrong:\n%s", name, body) } } } // The note sits under the row it describes, not under whichever kind // renders last. Phone renders before VAT, so a note placed after every // row would sit under the VAT number and describe the wrong thing. // // MUTATION-TESTED 2026-09-29, each reverted: the note rendered after all // rows (Astra's finding) → "order is phone 2432, note 2818, vat 2668"; // the note rendered above its row → "phone 2525, note 2362". func TestTheNarrowingLineSitsUnderItsOwnRow(t *testing.T) { h := newApp(t) store(t, h, "Office", "## Office\n\nOffice phone: +353 (0)1 547 0001\n\nReception: +353 (0)1 547 0002\n\n"+ "Sales: +353 (0)1 547 0003\n\nFax: +353 (0)1 547 0004\n\nOffice VAT: IE3384527RH") body := search(t, h, "office phone and vat") if got := factValues(body); len(got) != 2 { t.Fatalf("values = %q, want the narrowed phone and the VAT number", got) } phone := strings.Index(body, "+353 (0)1 547 0001") note := strings.Index(body, "

") vat := strings.Index(body, "IE3384527RH") if phone < 0 || note < 0 || vat < 0 { t.Fatalf("phone %d, note %d, vat %d: something is missing:\n%s", phone, note, vat, body) } if !(phone < note && note < vat) { t.Errorf("order is phone %d, note %d, vat %d; the note must sit between its row and the next", phone, note, vat) } }