Artifact ffcb3f377ae0bc915c658b03f6a22cb143f7792a0302bc875c38d7361f8d962d:
- File
internal/index/landingevent_test.go
— part of check-in
[5d398dd22e]
at
2026-08-26 10:02:50
on branch landing-timeline
— Timeline: landings are landings, not forum posts
Landings are built on forum artifacts, so the forum trigger filed every comment and verdict as kind 'f' with a 'Forum: ' prefix — rows reading 'Forum: zit-landing-state: accepted Accepted. The forge now tells y'. quarry had already decided these are not forum content; the timeline was the surface that missed it.
Verdicts are kind 'l' and appear in a mixed timeline because they change the target branch. Comments are 'lc' and appear only under the new y=l. Both link to the landing rather than the post.
Classified in the fold, not the trigger: the trigger fires before the landing row exists, since the root post is what it is derived from. Backfilled for stores folded before this. (user: dmestas, size: 8330) [annotate] [blame] [check-ins using] [more...]
package index import ( "testing" ) // eventRow reads one event's kind and label. func eventRow(t *testing.T, ix *Index, hash string) (kind, ref, comment string) { t.Helper() if err := ix.db.QueryRow(`SELECT kind, ref, comment FROM event WHERE hash = ?`, hash).Scan(&kind, &ref, &comment); err != nil { t.Fatalf("no event for %s: %v", hash, err) } return } // Landings are built on forum artifacts, so the forum trigger filed // every comment and every verdict as kind 'f' with a "Forum: " prefix. // Three landings produced rows reading "Forum: zit-landing-state: // accepted Accepted. The forge now tells y" — a prefix over a marker // over a truncated body. func TestLandingPostsAreNotForumEvents(t *testing.T) { ix := openIndex(t) foldForumBody(t, ix, "aaaa1111", "", "Ship the drilldown", "2026-01-01T10:00:00", landingBody("feat/x", "trunk", "ready")) foldForumBody(t, ix, "bbbb2222", "aaaa1111", "", "2026-01-02T10:00:00", "just a remark") foldForumBody(t, ix, "cccc3333", "aaaa1111", "", "2026-01-03T10:00:00", landingState("accepted", "landed")) kind, ref, comment := eventRow(t, ix, "aaaa1111") if kind != "l" { t.Errorf("the landing root is kind %q, want l", kind) } if ref != "aaaa1111" { t.Errorf("root ref %q, want the landing id so the row can link to it", ref) } if comment != "Landing: Ship the drilldown" { t.Errorf("root label %q", comment) } kind, ref, comment = eventRow(t, ix, "cccc3333") if kind != "l" { t.Errorf("a verdict is kind %q, want l — it changes the target branch", kind) } if comment != "Landing accepted: Ship the drilldown" { t.Errorf("verdict label %q", comment) } if ref != "aaaa1111" { t.Errorf("verdict ref %q, want the landing id", ref) } kind, _, comment = eventRow(t, ix, "bbbb2222") if kind != "lc" { t.Errorf("a landing comment is kind %q, want lc", kind) } if comment != "Comment on landing: Ship the drilldown" { t.Errorf("comment label %q", comment) } // Nothing in a landing may still read as forum content. var forum int if err := ix.db.QueryRow(`SELECT count(*) FROM event WHERE kind = 'f'`).Scan(&forum); err != nil { t.Fatal(err) } if forum != 0 { t.Errorf("%d landing posts still filed as forum events", forum) } } // A verdict is labelled by ITS OWN state, not the thread's final one: a // timeline row says what happened at that moment. func TestEachVerdictKeepsItsOwnState(t *testing.T) { ix := openIndex(t) foldForumBody(t, ix, "aaaa1111", "", "Two minds", "2026-01-01T10:00:00", landingBody("feat/x", "trunk", "ready")) foldForumBody(t, ix, "bbbb2222", "aaaa1111", "", "2026-01-02T10:00:00", landingState("rejected", "not yet")) foldForumBody(t, ix, "cccc3333", "aaaa1111", "", "2026-01-03T10:00:00", landingState("accepted", "now")) if _, _, c := eventRow(t, ix, "bbbb2222"); c != "Landing rejected: Two minds" { t.Errorf("the earlier verdict reads %q — relabelled by how the story ended", c) } if _, _, c := eventRow(t, ix, "cccc3333"); c != "Landing accepted: Two minds" { t.Errorf("the settling verdict reads %q", c) } } // Ordinary forum threads must be untouched: the classification keys on // the landing header, not on being a thread. func TestOrdinaryForumPostsStayForumEvents(t *testing.T) { ix := openIndex(t) foldForumBody(t, ix, "dddd4444", "", "How do I build this", "2026-01-01T10:00:00", "a real question") foldForumBody(t, ix, "eeee5555", "dddd4444", "", "2026-01-02T10:00:00", "an answer") for _, h := range []string{"dddd4444", "eeee5555"} { if k, _, _ := eventRow(t, ix, h); k != "f" { t.Errorf("forum post %s became kind %q", h, k) } } } // A reply folded before its root is the order-independence rule the // fold already keeps for the landing record itself; the events have to // converge the same way. func TestLandingEventsConvergeWhateverTheOrder(t *testing.T) { ix := openIndex(t) foldForumBody(t, ix, "cccc3333", "aaaa1111", "", "2026-01-03T10:00:00", landingState("accepted", "landed")) // The reply arrived first: with no root there is no landing yet. if k, _, _ := eventRow(t, ix, "cccc3333"); k != "f" { t.Fatalf("precondition: orphan reply is kind %q, expected f", k) } foldForumBody(t, ix, "aaaa1111", "", "Late root", "2026-01-01T10:00:00", landingBody("feat/x", "trunk", "ready")) if k, _, c := eventRow(t, ix, "cccc3333"); k != "l" || c != "Landing accepted: Late root" { t.Errorf("after the root arrived the verdict is %q/%q — it did not converge", k, c) } } // The classification moved out of the trigger and into the fold, so a // store folded before that change carries "Forum:" rows for landings it // already holds. Derived data that is only correct after a manual // reindex is a chore, not derived data. func TestBackfillRefilesLandingEventsInAnUpgradedIndex(t *testing.T) { ix := openIndex(t) foldForumBody(t, ix, "aaaa1111", "", "Older store", "2026-01-01T10:00:00", landingBody("feat/x", "trunk", "ready")) foldForumBody(t, ix, "cccc3333", "aaaa1111", "", "2026-01-03T10:00:00", landingState("accepted", "landed")) // Simulate the old shape: the trigger's classification, unfixed. if _, err := ix.db.Exec(`UPDATE event SET kind = 'f', comment = 'Forum: stale' WHERE hash IN ('aaaa1111', 'cccc3333')`); err != nil { t.Fatal(err) } if err := ix.backfillLandingEvents(); err != nil { t.Fatal(err) } if k, _, c := eventRow(t, ix, "aaaa1111"); k != "l" || c != "Landing: Older store" { t.Errorf("root not refiled: %q/%q", k, c) } if k, _, c := eventRow(t, ix, "cccc3333"); k != "l" || c != "Landing accepted: Older store" { t.Errorf("verdict not refiled: %q/%q", k, c) } // Idempotent: a store with nothing to fix must not be rewritten. if _, err := ix.db.Exec(`UPDATE event SET comment = 'sentinel' WHERE hash = 'aaaa1111'`); err != nil { t.Fatal(err) } if err := ix.backfillLandingEvents(); err != nil { t.Fatal(err) } if _, _, c := eventRow(t, ix, "aaaa1111"); c != "sentinel" { t.Error("backfill rewrote a store that had nothing stale in it") } } // kindsIn runs the timeline with a filter and reports the kinds it returned. func kindsIn(t *testing.T, ix *Index, y string) map[string]int { t.Helper() es, _, err := ix.Timeline(TimelineFilter{Kind: y, Limit: 50}) if err != nil { t.Fatal(err) } got := map[string]int{} for _, e := range es { got[e.Kind]++ } return got } // A verdict changes the target branch and belongs in a mixed timeline. // A landing comment is conversation — three landings produced six rows // of it — so it shows only where someone asked for landings. func TestMixedTimelineCarriesVerdictsButNotChatter(t *testing.T) { ix := openIndex(t) foldForumBody(t, ix, "aaaa1111", "", "Ship it", "2026-01-01T10:00:00", landingBody("feat/x", "trunk", "ready")) foldForumBody(t, ix, "bbbb2222", "aaaa1111", "", "2026-01-02T10:00:00", "a remark") foldForumBody(t, ix, "cccc3333", "aaaa1111", "", "2026-01-03T10:00:00", landingState("accepted", "landed")) foldForumBody(t, ix, "dddd4444", "", "A real thread", "2026-01-04T10:00:00", "hello") all := kindsIn(t, ix, "all") if all["lc"] != 0 { t.Errorf("y=all carried %d landing comments", all["lc"]) } if all["l"] != 2 { t.Errorf("y=all carried %d landing rows, want the root and the verdict", all["l"]) } if all["f"] != 1 { t.Errorf("y=all carried %d forum rows, want the one real thread", all["f"]) } // The landing view is the whole record, conversation included. l := kindsIn(t, ix, "l") if l["l"] != 2 || l["lc"] != 1 { t.Errorf("y=l gave %v, want 2 landing rows and 1 comment", l) } if l["f"] != 0 { t.Error("y=l carried ordinary forum posts") } // ...and the forum view is unaffected by any of it. if f := kindsIn(t, ix, "f"); f["f"] != 1 || len(f) != 1 { t.Errorf("y=f gave %v, want only the one real thread", f) } } // An unfiltered call is what other surfaces use; chatter must not leak // into those either. func TestUnfilteredTimelineAlsoOmitsLandingChatter(t *testing.T) { ix := openIndex(t) foldForumBody(t, ix, "aaaa1111", "", "Ship it", "2026-01-01T10:00:00", landingBody("feat/x", "trunk", "ready")) foldForumBody(t, ix, "bbbb2222", "aaaa1111", "", "2026-01-02T10:00:00", "a remark") if got := kindsIn(t, ix, ""); got["lc"] != 0 { t.Errorf("an unfiltered timeline carried %d landing comments", got["lc"]) } }