Artifact 4e71c57fa841212b66a434aad8a7a68e6d572393af6740ee65b4c561de3c977c:
- File
internal/index/landingstate_test.go
— part of check-in
[4832508615]
at
2026-08-26 23:13:58
on branch protect-3
— RFC 0007 phase 3: the merge is the fact; accept asks MayJoin
The fold reads accepted from the DAG, for every landing: the earliest target check-in after opening whose non-primary parent is on the proposed branch. A reply asserting acceptance with nothing merged leaves the record open and is recorded as a claim; the page says so, the timeline files it as conversation. Rejection has no DAG fact and stays the reply's to say. Re-derived after every branch recompute — which now also invalidates the count memo, since it can settle a landing — so a merge pushed from a clone with no reply settles.
landing.accept refuses a non-owner on a protected target before writing, naming the owners. landing.get reports MayLand beside CanAccept so the page shows a disabled control with the reason.
Fourteen existing tests settled records by reply alone, or asserted a by-hand merge does not count; each now carries the merge it announces or asserts the RFC's reading. Giving the web fixture real check-ins exposed checkin.diff dereferencing a nil store; it now refuses, after the lookups so not-found still wins. (user: dmestas, size: 5048) [annotate] [blame] [check-ins using] [more...]
package index import ( "testing" ) // RFC 0007: the merge is the fact and the reply is the claim. A reply // that says "accepted" with nothing merged leaves the record OPEN and // records the claim; the merge check-in alone, with no reply at all, // settles it. Unconditional — no policy row is consulted — which is // what lets a wipe-and-reingest, or a protection row added later, // change nothing about how history renders. func TestAcceptReplyWithoutAMergeIsAClaim(t *testing.T) { ix := openIndex(t) landingOn(t, ix, "aaaa1111", "feat-x", "2026-01-01T10:00:00", "Ship it") foldForumBody(t, ix, "bbbb2222", "aaaa1111", "", "2026-01-02T10:00:00", landingState("accepted", "I say so")) l := onlyOne(t, ix, "aaaa1111") if l.State != "open" { t.Fatalf("state %q after a reply-only accept, want open", l.State) } if l.ClaimedBy != "bbbb2222" { t.Errorf("claimed_by %q, want the reply that made the claim", l.ClaimedBy) } if l.SettledBy != "" { t.Errorf("settled_by %q on an unsettled record", l.SettledBy) } // On the timeline it is conversation, not a verdict. if k, _, c := eventRow(t, ix, "bbbb2222"); k != "lc" || c != "Acceptance claimed on landing: Ship it" { t.Errorf("the claim is filed as %q/%q", k, c) } } func TestAMergeWithNoReplySettlesTheRecord(t *testing.T) { ix := openIndex(t) ci(t, ix, "t1", "trunk", "2026-01-01T09:00:00", "base") landingOn(t, ix, "aaaa1111", "feat-x", "2026-01-01T10:00:00", "Ship it") ci(t, ix, "f1", "feat-x", "2026-01-01T11:00:00", "work", "t1") // An owner merged from a clone and pushed; nobody posted a reply. ci(t, ix, "m1", "trunk", "2026-01-02T10:00:00", "land feat-x", "t1", "f1") // The check-ins arrived AFTER the landing was folded, so the // record was open until something re-read it. The refresh after // recomputeBranches is that something. tx, err := ix.db.Begin() if err != nil { t.Fatal(err) } if err := refreshLandingStates(tx); err != nil { t.Fatal(err) } if err := tx.Commit(); err != nil { t.Fatal(err) } l := onlyOne(t, ix, "aaaa1111") if l.State != "accepted" { t.Fatalf("state %q with the merge in the DAG, want accepted", l.State) } if l.SettledBy != "" || l.ClaimedBy != "" { t.Errorf("settled_by=%q claimed_by=%q, want both empty: nothing was posted", l.SettledBy, l.ClaimedBy) } // And the approver named on the timeline is whoever wrote the merge. rows := []TimelineEntry{{Kind: "ci", Hash: "m1", IsMerge: true}} if err := ix.attachLandings(rows); err != nil { t.Fatal(err) } if rows[0].LandingApprover != "dan" { t.Errorf("approver %q, want the merge's author", rows[0].LandingApprover) } } // A claim, then the merge: the claim clears and the record settles. // The reply that claimed becomes the settling reply, because it was // right — just early. func TestAClaimBecomesAVerdictWhenTheMergeArrives(t *testing.T) { ix := openIndex(t) ci(t, ix, "t1", "trunk", "2026-01-01T09:00:00", "base") landingOn(t, ix, "aaaa1111", "feat-x", "2026-01-01T10:00:00", "Ship it") ci(t, ix, "f1", "feat-x", "2026-01-01T11:00:00", "work", "t1") foldForumBody(t, ix, "bbbb2222", "aaaa1111", "", "2026-01-02T10:00:00", landingState("accepted", "landing")) if l := onlyOne(t, ix, "aaaa1111"); l.State != "open" || l.ClaimedBy != "bbbb2222" { t.Fatalf("precondition: %+v", l) } ci(t, ix, "m1", "trunk", "2026-01-02T10:00:05", "land feat-x", "t1", "f1") // Any later post re-folds the thread; a comment will do. foldForumBody(t, ix, "cccc3333", "aaaa1111", "", "2026-01-02T11:00:00", "nice") l := onlyOne(t, ix, "aaaa1111") if l.State != "accepted" || l.SettledBy != "bbbb2222" || l.ClaimedBy != "" { t.Fatalf("after the merge: %+v, want accepted, settled by the reply, no claim", l) } if k, _, c := eventRow(t, ix, "bbbb2222"); k != "l" || c != "Landing accepted: Ship it" { t.Errorf("the reply is still filed as a claim: %q/%q", k, c) } } // Rejection has no DAG fact to read, so the reply stays the record. func TestRejectionIsStillTheReplysToSay(t *testing.T) { ix := openIndex(t) landingOn(t, ix, "aaaa1111", "feat-x", "2026-01-01T10:00:00", "No thanks") foldForumBody(t, ix, "bbbb2222", "aaaa1111", "", "2026-01-02T10:00:00", landingState("rejected", "not this")) if l := onlyOne(t, ix, "aaaa1111"); l.State != "rejected" || l.SettledBy != "bbbb2222" { t.Fatalf("%+v, want rejected by the reply", l) } } // The merge must be the landing's own: a merge of the branch from // BEFORE the landing opened is history the proposal was made on top // of, not the proposal landing. func TestAMergeBeforeTheLandingOpenedDoesNotCount(t *testing.T) { ix := openIndex(t) ci(t, ix, "t1", "trunk", "2026-01-01T09:00:00", "base") ci(t, ix, "f1", "feat-x", "2026-01-01T09:30:00", "old work", "t1") ci(t, ix, "m0", "trunk", "2026-01-01T09:45:00", "landed once already", "t1", "f1") landingOn(t, ix, "aaaa1111", "feat-x", "2026-01-01T10:00:00", "Round two") if l := onlyOne(t, ix, "aaaa1111"); l.State != "open" { t.Fatalf("state %q — an earlier merge settled a later proposal", l.State) } }