Artifact 678109e4643505b06758720e69807294f13c61011a458d06d62b647029f77e83:
- File
internal/harness/protect_test.go
— part of check-in
[02f6aadcad]
at
2026-08-27 00:03:48
on branch strata-ui
— Landings: a strata is one row, and a map on the record
The open queue folds a stack under its base: a strata mark, the segment count, the landable end named, and the rungs in a details element with 1/N badges — no script. Settled views stay flat; a settled segment has left its chain.
The record page draws the map: every rung bottom-up, this one marked, the landable one named, a rejected base as a broken rung. Above the bottom the accept control is disabled and says the segment below lands first.
Proved with a real zit: two stacked landings count one decision; the top rung's accept is refused; accepting the base retargets the next onto trunk and it becomes the landable end, still one decision. (user: dmestas, size: 19970) [annotate] [blame] [check-ins using] [more...]
package harness import ( "context" "encoding/json" "io" "net/http" "net/http/httptest" "os" "os/exec" "path/filepath" "strings" "testing" "craftdesign.group/quarry/internal/api" "craftdesign.group/quarry/internal/events" "craftdesign.group/quarry/internal/index" "craftdesign.group/quarry/internal/ingest" "craftdesign.group/quarry/internal/oltp" "craftdesign.group/quarry/internal/ops" "craftdesign.group/quarry/internal/web" "craftdesign.group/quarry/internal/xfer" "craftdesign.group/quarry/internal/zitcli" ) // RFC 0007 §Testing: the matrix is the spec. alice owns trunk; bob // holds `i` and owns nothing. Every row below is one line of the RFC's // table, driven through the forge the way a person or a client would // drive it — the forge's accept through the API, a clone's accept // through the sync gate, a stock fossil client through the same gate. // // Three rows are proved elsewhere and named here so the matrix reads // complete: the gate always sets its own header and strips a client's // (xfer: TestGateAlwaysSetsItsOwnProtectHeader); a control artifact // retagging a check-in onto trunk is refused (zit#151's own unit // tests, over the three violation shapes); a stacked N+1 landing after // N lands on trunk shows a disabled control, because MayLand is // computed at read time against the CURRENT target and that is row 3. const protectPW = "pw12345678" type protectForge struct { srv *httptest.Server odb *oltp.DB store string code string sync func() wipe func() } func newProtectForge(t *testing.T) *protectForge { t.Helper() if _, err := exec.LookPath("zit"); err != nil { t.Skip("zit not on PATH") } store := buildStore(t, "protectseed") dataDir := t.TempDir() ev, err := events.Start(filepath.Join(dataDir, "nats")) if err != nil { t.Fatal(err) } t.Cleanup(ev.Close) odb, err := oltp.Open(filepath.Join(dataDir, "oltp.db")) if err != nil { t.Fatal(err) } t.Cleanup(func() { odb.Close() }) code, err := xfer.ProjectCode(store) if err != nil { t.Fatal(err) } // Two accounts, both able to land in fossil's terms (`i`), and two // sync users with the same passwords, so the CLI rows authenticate // as the same people the API rows do. for _, name := range []string{"alice", "bob"} { if err := odb.AddAccount(name, protectPW, false); err != nil { t.Fatal(err) } if err := odb.SetCaps(name, "alpha", "kfw3C5aiz"); err != nil { t.Fatal(err) } if err := odb.SetSyncUser("alpha", name, xfer.SharedSecret(code, name, protectPW)); err != nil { t.Fatal(err) } } if err := odb.SetProtection("alpha", "trunk", []string{"alice"}); err != nil { t.Fatal(err) } f := &protectForge{odb: odb, store: store, code: code} var ix *index.Index var pipe *ingest.Pipeline open := func() { var err error ix, err = index.Open(filepath.Join(dataDir, "alpha.db")) if err != nil { t.Fatal(err) } cli := zitcli.New(store) pipe = ingest.NewPipeline(cli, ev, ix, "alpha", "harness") } open() t.Cleanup(func() { ix.Close() }) deps := api.Deps{Version: "0.0.0-harness", Repos: api.NewRepos(nil), Votes: odb, Forum: odb, Protect: odb} cli := zitcli.New(store) put := func() { deps.Repos.Put("alpha", &api.Repo{Index: ix, Zit: cli, Sync: pipe.Sync, Leaves: func() (map[string]bool, error) { return cli.Leaves(context.Background()) }}) } put() f.sync = func() { t.Helper() if _, err := pipe.Sync(); err != nil { t.Fatal(err) } } f.sync() // The §11 wipe: derived data thrown away and rebuilt from the store. f.wipe = func() { t.Helper() ix.Close() if err := os.Remove(filepath.Join(dataDir, "alpha.db")); err != nil { t.Fatal(err) } open() put() f.sync() } r := ops.NewRegistry() if err := api.RegisterAll(r, deps); err != nil { t.Fatal(err) } verify := func(repo, user, pass string, _ ops.Auth) api.Decision { if (user == "alice" || user == "bob") && pass == protectPW { return api.Allowed } return api.Denied } capsFor := func(account, repo string) api.CapSet { return odb.EffectiveCaps(account, repo) } apiRouter, err := api.RouterWithCaps(r, "0.0.0-harness", capsFor, verify) if err != nil { t.Fatal(err) } // The sync gate, wired exactly as serve wires it: identity from the // shared secret, the rule from Refused, the child --fronted. fr := xfer.New() t.Cleanup(fr.Close) fr.Register("alpha", store) fr.SetAuth(odb) fr.SetProtect(odb.Refused) mux := http.NewServeMux() mux.Handle("/api/", apiRouter) mux.Handle("POST /r/{repo}", fr) mux.Handle("/", web.HandlerWith(r, nil, nil, []web.Option{web.WithCaps(harnessCaps{odb: odb}), web.WithProtect(odb)}, web.Verify(verify))) f.srv = httptest.NewServer(mux) t.Cleanup(f.srv.Close) return f } func (f *protectForge) call(t *testing.T, user, method, path, body string) (int, string) { t.Helper() req, _ := http.NewRequest(method, f.srv.URL+path, strings.NewReader(body)) req.Header.Set("Content-Type", "application/json") req.SetBasicAuth(user, protectPW) resp, err := f.srv.Client().Do(req) if err != nil { t.Fatal(err) } defer resp.Body.Close() b, _ := io.ReadAll(resp.Body) return resp.StatusCode, string(b) } type landingView struct { Landing struct { ID string `json:"id"` State string `json:"state"` SettledBy string `json:"settled_by"` ClaimedBy string `json:"claimed_by"` } `json:"landing"` CanAccept bool `json:"canAccept"` MayLand bool `json:"mayLand"` Protected bool `json:"protected"` Owners []string `json:"owners"` Merge struct { Checkin string `json:"checkin"` } `json:"merge"` Stack struct { Segments []struct { ID string `json:"id"` Landable bool `json:"landable"` } `json:"segments"` } `json:"stack"` } func (f *protectForge) landing(t *testing.T, user, id string) landingView { t.Helper() code, body := f.call(t, user, "GET", "/api/v1/r/alpha/landing/"+id[:12], "") if code != 200 { t.Fatalf("GET landing %s as %s: %d %s", id[:12], user, code, body) } var v landingView if err := json.Unmarshal([]byte(body), &v); err != nil { t.Fatal(err) } return v } // openLanding commits on a fresh branch in the store and proposes it. func (f *protectForge) openLanding(t *testing.T, branch, file string) string { t.Helper() zrun(t, f.store, "update", "trunk") if err := os.WriteFile(filepath.Join(f.store, file), []byte(branch+"\n"), 0o644); err != nil { t.Fatal(err) } zrun(t, f.store, "add", file) zrun(t, f.store, "commit", "-m", "work on "+branch, "--branch", branch) f.sync() code, body := f.call(t, "bob", "POST", "/api/v1/r/alpha/landing", `{"branch":"`+branch+`","target":"trunk","message":"land `+branch+`"}`) if code != 200 { t.Fatalf("open landing for %s: %d %s", branch, code, body) } var out struct { ID string `json:"id"` } if err := json.Unmarshal([]byte(body), &out); err != nil || out.ID == "" { t.Fatalf("no landing id: %v %s", err, body) } f.sync() return out.ID } func (f *protectForge) clone(t *testing.T) string { t.Helper() work := t.TempDir() dir := filepath.Join(work, "clone") zrun(t, work, "clone", f.srv.URL+"/r/alpha", dir, "--once") // A clone is a store; a checkout is what a person works in, and // what `landing accept` and `commit --branch` need. zrun(t, dir, "update", "trunk") return dir } // zsay runs zit and returns what it said, for the rows where the // message is the assertion or the failure needs explaining. func zsay(t *testing.T, dir string, args ...string) (string, error) { t.Helper() cmd := exec.Command("zit", args...) cmd.Dir = dir out, err := cmd.CombinedOutput() return string(out), err } func (f *protectForge) push(t *testing.T, dir, user string) (string, error) { t.Helper() cmd := exec.Command("zit", "push", f.srv.URL+"/r/alpha", "--user", user, "--password", protectPW) cmd.Dir = dir out, err := cmd.CombinedOutput() return string(out), err } // ── the matrix ────────────────────────────────────────────────────── // Rows 1 and 3: the forge's own accept, non-owner then owner. func TestProtectForgeAccept(t *testing.T) { f := newProtectForge(t) id := f.openLanding(t, "feat-a", "a.txt") v := f.landing(t, "bob", id) if !v.CanAccept || v.MayLand || !v.Protected || strings.Join(v.Owners, ",") != "alice" { t.Fatalf("bob's view: %+v — want CanAccept, not MayLand, protected by alice", v) } if code, body := f.call(t, "bob", "POST", "/api/v1/r/alpha/landing/"+id[:12]+"/accept", `{}`); code != http.StatusForbidden { t.Fatalf("non-owner accept answered %d %s, want 403", code, body) } else if !strings.Contains(body, "alice") { t.Errorf("the refusal does not name the owners: %s", body) } f.sync() if v := f.landing(t, "bob", id); v.Landing.State != "open" { t.Fatalf("record is %q after a refused accept, want open", v.Landing.State) } if tl := zout(t, f.store, "timeline", "-n", "3"); strings.Contains(tl, "land feat-a") { t.Fatalf("a refused accept wrote a merge:\n%s", tl) } // The page's disabled control with the reason is proved in the web // package, where a viewer has an identity: this harness has no // sessions plane, so its pages see everyone as anonymous. if v := f.landing(t, "alice", id); !v.MayLand { t.Fatalf("the owner may not land: %+v", v) } if code, body := f.call(t, "alice", "POST", "/api/v1/r/alpha/landing/"+id[:12]+"/accept", `{}`); code != 200 { t.Fatalf("owner accept: %d %s", code, body) } f.sync() v = f.landing(t, "alice", id) if v.Landing.State != "accepted" || v.Merge.Checkin == "" { t.Fatalf("after the owner's accept: %+v, want accepted with a merge", v) } if tl := zout(t, f.store, "timeline", "-n", "3"); !strings.Contains(tl, "trunk") { t.Fatalf("the merge is not on trunk:\n%s", tl) } // Row: merged, then the branch continues. The tip is no longer an // ancestor of trunk's tip, and the record must not un-settle. zrun(t, f.store, "update", "feat-a") if err := os.WriteFile(filepath.Join(f.store, "a2.txt"), []byte("more\n"), 0o644); err != nil { t.Fatal(err) } zrun(t, f.store, "add", "a2.txt") zrun(t, f.store, "commit", "-m", "feat-a continues") f.sync() if v := f.landing(t, "alice", id); v.Landing.State != "accepted" { t.Errorf("continuing the branch past its merge un-settled the record: %q", v.Landing.State) } } // Rows 2 and 4: accept on a clone, then push. The gate turns the // pusher into a rule and the child refuses the whole round. func TestProtectCLIAcceptAndPush(t *testing.T) { f := newProtectForge(t) id := f.openLanding(t, "feat-b", "b.txt") // bob: accept locally (a merge onto trunk in the clone), push. bob := f.clone(t) if out, err := zsay(t, bob, "landing", "accept", id[:12], "--user", "bob", "-m", "bob lands it"); err != nil { t.Fatalf("zit landing accept in a clone: %v\n%s", err, out) } out, err := f.push(t, bob, "bob") if err == nil { t.Fatalf("non-owner's pushed accept was admitted:\n%s", out) } if !strings.Contains(out, "trunk") { t.Errorf("the refusal does not name the branch:\n%s", out) } f.sync() if v := f.landing(t, "bob", id); v.Landing.State != "open" { t.Fatalf("record is %q after a refused push, want open — the whole round must be refused", v.Landing.State) } if tl := zout(t, f.store, "timeline", "-n", "3"); strings.Contains(tl, "bob lands it") { t.Fatalf("part of the refused round reached the store:\n%s", tl) } // alice: the same, admitted — and with no reply needed, the merge // alone settles the record and names her as approver. alice := f.clone(t) zrun(t, alice, "update", "trunk") zrun(t, alice, "merge", "feat-b") zrun(t, alice, "commit", "-m", "alice merges by hand") if out, err := f.push(t, alice, "alice"); err != nil { t.Fatalf("owner's push refused: %v\n%s", err, out) } f.sync() v := f.landing(t, "alice", id) if v.Landing.State != "accepted" || v.Landing.SettledBy != "" || v.Landing.ClaimedBy != "" { t.Fatalf("after a by-hand merge with no reply: %+v, want accepted, nothing posted", v) } } // Rows 7 and 8: what protection does NOT touch. Branching off trunk is // not joining it; an unprotected branch is anyone's. func TestProtectLeavesOtherWritesAlone(t *testing.T) { f := newProtectForge(t) bob := f.clone(t) if err := os.WriteFile(filepath.Join(bob, "side.txt"), []byte("off trunk\n"), 0o644); err != nil { t.Fatal(err) } if out, err := zsay(t, bob, "add", "side.txt"); err != nil { t.Fatalf("add in clone: %v\n%s", err, out) } if out, err := zsay(t, bob, "commit", "-m", "bob branches off trunk", "--branch", "side"); err != nil { t.Fatalf("commit --branch in clone: %v\n%s", err, out) } if out, err := f.push(t, bob, "bob"); err != nil { t.Fatalf("branching OFF a protected branch was refused: %v\n%s", err, out) } if err := os.WriteFile(filepath.Join(bob, "side2.txt"), []byte("more\n"), 0o644); err != nil { t.Fatal(err) } zrun(t, bob, "add", "side2.txt") zrun(t, bob, "commit", "-m", "bob continues side") if out, err := f.push(t, bob, "bob"); err != nil { t.Fatalf("a push to an unprotected branch was refused: %v\n%s", err, out) } f.sync() if tl := zout(t, f.store, "timeline", "-n", "4"); !strings.Contains(tl, "bob continues side") { t.Fatalf("bob's side work did not land:\n%s", tl) } } // Row 5: a stock fossil client pushing a merge to trunk. The same // gate, the same rule, and a refusal the client renders. func TestProtectRefusesStockFossilMerge(t *testing.T) { f := newProtectForge(t) if _, err := exec.LookPath("fossil"); err != nil { t.Skip("fossil not on PATH") } f.openLanding(t, "feat-c", "c.txt") frun := func(dir string, args ...string) (string, error) { cmd := exec.Command("fossil", args...) cmd.Dir = dir out, err := cmd.CombinedOutput() return string(out), err } work := t.TempDir() if out, err := frun(work, "clone", f.srv.URL+"/r/alpha", "c.fossil"); err != nil { t.Fatalf("fossil clone: %v\n%s", err, out) } co := filepath.Join(work, "co") if err := os.MkdirAll(co, 0o755); err != nil { t.Fatal(err) } if out, err := frun(co, "open", filepath.Join(work, "c.fossil")); err != nil { t.Fatalf("fossil open: %v\n%s", err, out) } frun(co, "update", "trunk") if out, err := frun(co, "merge", "feat-c"); err != nil { t.Fatalf("fossil merge: %v\n%s", err, out) } if out, err := frun(co, "commit", "-m", "fossil merges feat-c", "--no-warnings", "--user-override", "bob"); err != nil { t.Fatalf("fossil commit: %v\n%s", err, out) } out, _ := frun(co, "push", strings.Replace(f.srv.URL, "http://", "http://bob:"+protectPW+"@", 1)+"/r/alpha", "--once") if !strings.Contains(out, "trunk") { t.Fatalf("stock fossil push of a merge to trunk was not refused with the branch named:\n%s", out) } if tl := zout(t, f.store, "timeline", "-n", "3"); strings.Contains(tl, "fossil merges feat-c") { t.Fatalf("the refused fossil merge reached the store:\n%s", tl) } } // Row 9: a forged transition reply. Anyone who may speak may post the // marker; with no merge behind it, it is a claim and the page says so. func TestProtectForgedAcceptIsAClaim(t *testing.T) { f := newProtectForge(t) id := f.openLanding(t, "feat-d", "d.txt") if code, body := f.call(t, "bob", "POST", "/api/v1/r/alpha/landing/"+id[:12]+"/comment", `{"message":"zit-landing-state: accepted\n\nI say so"}`); code != 200 { t.Fatalf("comment: %d %s", code, body) } f.sync() v := f.landing(t, "bob", id) if v.Landing.State != "open" { t.Fatalf("a forged reply settled the record: state %q", v.Landing.State) } if v.Landing.ClaimedBy == "" { t.Errorf("the forged reply is not recorded as a claim: %+v", v) } } // Compatibility and disposability. A clone is the same clone with the // row present or absent; the record renders the same before and after // the row is added, and before and after the §11 wipe. func TestProtectLeavesNoTraceAndSurvivesTheWipe(t *testing.T) { f := newProtectForge(t) id := f.openLanding(t, "feat-e", "e.txt") if code, body := f.call(t, "alice", "POST", "/api/v1/r/alpha/landing/"+id[:12]+"/accept", `{}`); code != 200 { t.Fatalf("owner accept: %d %s", code, body) } f.sync() state := func() string { v := f.landing(t, "alice", id) return v.Landing.State + "|" + v.Landing.SettledBy + "|" + v.Landing.ClaimedBy + "|" + v.Merge.Checkin } artifacts := func() string { return zout(t, f.clone(t), "timeline", "-n", "50") } withRow, withRowClone := state(), artifacts() if err := f.odb.SetProtection("alpha", "trunk", nil); err != nil { t.Fatal(err) } f.sync() if got := state(); got != withRow { t.Errorf("deleting the protection row changed how the record renders:\n %s\n %s", withRow, got) } if got := artifacts(); got != withRowClone { t.Errorf("a clone differs with the row absent — protection left a trace in the repository") } if err := f.odb.SetProtection("alpha", "trunk", []string{"alice"}); err != nil { t.Fatal(err) } f.wipe() if got := state(); got != withRow { t.Errorf("the §11 wipe changed how the record renders:\n %s\n %s", withRow, got) } } // RFC 0006 build order, phase 4: a real strata through the forge with a // real zit. Two landings stacked; the queue counts one decision; accept // the base as the owner and zit#144 retargets the next onto trunk, so // it becomes the landable end and the queue still counts one. func TestStrataWalksUpAsTheBaseLands(t *testing.T) { f := newProtectForge(t) a := f.openLanding(t, "st-a", "sa.txt") // The second branch off the first, proposed onto it. zrun(t, f.store, "update", "st-a") if err := os.WriteFile(filepath.Join(f.store, "sb.txt"), []byte("b\n"), 0o644); err != nil { t.Fatal(err) } zrun(t, f.store, "add", "sb.txt") zrun(t, f.store, "commit", "-m", "work on st-b", "--branch", "st-b") f.sync() code, body := f.call(t, "bob", "POST", "/api/v1/r/alpha/landing", `{"branch":"st-b","target":"st-a","message":"land st-b on st-a"}`) if code != 200 { t.Fatalf("open stacked landing: %d %s", code, body) } var b struct { ID string `json:"id"` } if err := json.Unmarshal([]byte(body), &b); err != nil { t.Fatal(err) } f.sync() list := func() (open int, stacks int) { code, body := f.call(t, "alice", "GET", "/api/v1/r/alpha/landing", "") if code != 200 { t.Fatalf("list: %d %s", code, body) } var out struct { Open int `json:"open"` Landings []struct { Stack string `json:"stack"` Position int `json:"position"` } `json:"landings"` } if err := json.Unmarshal([]byte(body), &out); err != nil { t.Fatal(err) } for _, l := range out.Landings { if l.Stack != "" { stacks++ } } return out.Open, stacks } if open, stacked := list(); open != 1 || stacked != 2 { t.Fatalf("two stacked landings: open=%d stacked=%d, want 1 decision over 2 rungs", open, stacked) } if v := f.landing(t, "alice", b.ID); v.Landing.State != "open" || len(v.Stack.Segments) != 2 { t.Fatalf("top rung before the base lands: %+v", v) } if code, body := f.call(t, "alice", "POST", "/api/v1/r/alpha/landing/"+b.ID[:12]+"/accept", `{}`); code == 200 { t.Fatalf("accepting the TOP rung succeeded: %s", body) } if code, body := f.call(t, "alice", "POST", "/api/v1/r/alpha/landing/"+a[:12]+"/accept", `{}`); code != 200 { t.Fatalf("accepting the base: %d %s", code, body) } f.sync() if v := f.landing(t, "alice", a); v.Landing.State != "accepted" { t.Fatalf("base after accept: %q", v.Landing.State) } // zit#144 retargeted st-b onto trunk: it is now a standalone, // landable landing, and the queue still holds one decision. if open, stacked := list(); open != 1 || stacked != 0 { t.Fatalf("after the base landed: open=%d stacked=%d, want 1 decision and no stack", open, stacked) } v := f.landing(t, "alice", b.ID) if v.Landing.State != "open" || len(v.Stack.Segments) != 0 || !v.MayLand { t.Fatalf("top rung after the base landed: %+v, want open, no stack, landable", v) } }