Artifact 5eb359f0a6000d0e3693203f90ce227e7d7d29982c828b6d72e44ff8d18b33e0:
- File
internal/harness/caps_test.go
— part of check-in
[7d2a820f65]
at
2026-08-23 19:17:55
on branch trunk
— landings: the decision surface
A list of what awaits a decision and a page per record: proposal, diff against target computed at view time, discussion, and the controls. accept rides `i` because it merges; everything else is a forum cap. Transitions render as events — zit stores them as replies, which is encoding the interface exists to hide. (user: dmestas, size: 14169) [annotate] [blame] [check-ins using]
package harness import ( "encoding/json" "io" "net/http" "path/filepath" "strings" "testing" "craftdesign.group/quarry/internal/api" "craftdesign.group/quarry/internal/oltp" "craftdesign.group/quarry/internal/ops" ) // TestCapsGateTheAPI is RFC 0004 §4 enforced end to end: each account // holds exactly one fossil capability letter and may reach exactly the // surfaces that letter opens. The Auth floor lets every authenticated // caller through; the capability check is what decides. func TestCapsGateTheAPI(t *testing.T) { stores := map[string]string{"alpha": buildStore(t, "capseed")} dataDir := t.TempDir() srv := ingestAndServeAuth(t, stores, dataDir, func(repo, user, pass string, need ops.Auth) api.Decision { if user == "" { return api.Denied } return api.Allowed }) // The harness opened this same file; a second handle sees its rows. odb, err := oltp.Open(filepath.Join(dataDir, "oltp.db")) if err != nil { t.Fatal(err) } defer odb.Close() must := func(err error) { t.Helper() if err != nil { t.Fatal(err) } } must(odb.AddAccount("reader", "pw12345678", false)) must(odb.SetCaps("reader", "alpha", "o")) // Read must(odb.AddAccount("forumer", "pw12345678", false)) must(odb.SetCaps("forumer", "alpha", "3")) // WrForum call := func(user, method, path, body string) int { var r io.Reader if body != "" { r = strings.NewReader(body) } req, _ := http.NewRequest(method, srv.URL+path, r) req.SetBasicAuth(user, "pw12345678") if body != "" { req.Header.Set("Content-Type", "application/x-www-form-urlencoded") } resp, err := srv.Client().Do(req) if err != nil { t.Fatal(err) } resp.Body.Close() return resp.StatusCode } // o reads. if got := call("reader", "GET", "/api/v1/r/alpha/timeline", ""); got != http.StatusOK { t.Fatalf("o cannot read the timeline: %d", got) } // o is refused the forum write surface; 3 is not. if got := call("reader", "POST", "/api/v1/r/alpha/forum", "title=x&body=y"); got != http.StatusForbidden { t.Fatalf("o opened the forum write surface: %d", got) } if got := call("forumer", "POST", "/api/v1/r/alpha/forum", "title=from+the+forum+cap&body=hello"); got != http.StatusOK { t.Fatalf("3 cannot write the forum: %d", got) } // 3 does not carry repository administration (a). if got := call("forumer", "POST", "/api/v1/repos", "name=nope&create=1"); got == http.StatusOK { t.Fatal("3 reached repository creation") } // The WEB forms answer to the same letters as the API — a page that // gates more loosely than its op is a hole with a nice layout. if got := call("reader", "POST", "/r/alpha/forum-new", "title=x&body=y"); got != http.StatusForbidden { t.Fatalf("o posted a thread through the web form: %d", got) } // 3 posts successfully: a redirect to the new thread, or the page // itself — either way, NOT a refusal. if got := call("forumer", "POST", "/r/alpha/forum-new", "title=web+thread&body=hello"); got == http.StatusForbidden || got == http.StatusUnauthorized { t.Fatalf("3 cannot post through the web form: %d", got) } // Ticket filing needs n — which the ANONYMOUS category grants to // every signed-in account (fossil's hmnc default). So this passes, // and that is the rule, not a leak: a signed-in human may file a // ticket unless the repo says otherwise. if got := call("forumer", "POST", "/r/alpha/ticket/new", "title=t&comment=c"); got == http.StatusForbidden { t.Fatalf("the anonymous category did not grant n: %d", got) } // Take n away from the category and the same request is refused — // which is what proves the gate reads caps rather than waving // signed-in callers through. if err := odb.SetCategoryCaps("alpha", "anonymous", "h"); err != nil { t.Fatal(err) } if got := call("forumer", "POST", "/r/alpha/ticket/new", "title=t2&comment=c"); got != http.StatusForbidden { t.Fatalf("ticket filing survived the loss of n: %d", got) } } // TestCategoryCapsReachTheSurfaces: an account with NO explicit caps // still reaches what the anonymous category grants, and a repo taken // private (blank nobody+anonymous) closes even the read surfaces — // fossil's Security-Audit behavior, which is quarry's invisibility. func TestCategoryCapsReachTheSurfaces(t *testing.T) { stores := map[string]string{"alpha": buildStore(t, "catseed")} dataDir := t.TempDir() srv := ingestAndServeAuth(t, stores, dataDir, func(repo, user, pass string, need ops.Auth) api.Decision { if user == "" { return api.Denied } return api.Allowed }) odb, err := oltp.Open(filepath.Join(dataDir, "oltp.db")) if err != nil { t.Fatal(err) } defer odb.Close() if err := odb.AddAccount("plain", "pw12345678", false); err != nil { t.Fatal(err) } get := func(user, path string) int { req, _ := http.NewRequest("GET", srv.URL+path, nil) if user != "" { req.SetBasicAuth(user, "pw12345678") } resp, err := srv.Client().Do(req) if err != nil { t.Fatal(err) } resp.Body.Close() return resp.StatusCode } // Stock nobody carries o: the timeline reads for everyone. if got := get("plain", "/api/v1/r/alpha/timeline"); got != http.StatusOK { t.Fatalf("category caps do not reach the timeline: %d", got) } // Take it private: blank the two visitor categories. if err := odb.SetCategoryCaps("alpha", "nobody", ""); err != nil { t.Fatal(err) } if err := odb.SetCategoryCaps("alpha", "anonymous", ""); err != nil { t.Fatal(err) } if got := get("plain", "/api/v1/r/alpha/timeline"); got == http.StatusOK { t.Fatal("a private repo still served its timeline to a capless account") } } // Capability e end to end: fossil withholds private_contact from the // public and shows it to holders of e. quarry dropped it outright // because it had no capability to gate on; it has one now, so the // field must reach a holder and nobody else. // // The unit test covers the render rule. This covers the thing that // actually decides it — a real caps plane answering for a real account. func TestPrivateContactReachesOnlyHoldersOfE(t *testing.T) { // The realistic source of this field is an IMPORT: a fossil repo // hosted here brings its own private_contact values with it. quarry // does not author the field, so the test writes it the way a mirror // would — into the store, before the index ever sees it. store := buildStore(t, "eseed") zrun(t, store, "ticket", "new", "--user", "eseed", "title=a bug with a contact", "status=Open", "[email protected]") dataDir := t.TempDir() srv := ingestAndServeAuth(t, map[string]string{"alpha": store}, dataDir, func(repo, user, pass string, need ops.Auth) api.Decision { if user == "" { return api.Denied } return api.Allowed }) odb, err := oltp.Open(filepath.Join(dataDir, "oltp.db")) if err != nil { t.Fatal(err) } defer odb.Close() for _, u := range []string{"plain", "holder"} { if err := odb.AddAccount(u, "pw12345678", false); err != nil { t.Fatal(err) } } // Only one of them may see private contact details. if err := odb.SetCaps("holder", "alpha", "re"); err != nil { t.Fatal(err) } if err := odb.SetCaps("plain", "alpha", "r"); err != nil { t.Fatal(err) } get := func(user, path string) string { req, _ := http.NewRequest("GET", srv.URL+path, nil) req.SetBasicAuth(user, "pw12345678") resp, err := srv.Client().Do(req) if err != nil { t.Fatal(err) } defer resp.Body.Close() b, _ := io.ReadAll(resp.Body) return string(b) } var list struct { Tickets []struct { ID string `json:"id"` Title string `json:"title"` } `json:"tickets"` } if err := json.Unmarshal([]byte(get("holder", "/api/v1/r/alpha/ticket?state=all&limit=50")), &list); err != nil { t.Fatal(err) } var id string for _, tk := range list.Tickets { if strings.Contains(tk.Title, "a contact") { id = tk.ID } } if id == "" { t.Fatalf("the imported ticket never reached the index: %+v", list.Tickets) } page := "/r/alpha/ticket/" + id if body := get("plain", page); strings.Contains(body, "[email protected]") { t.Error("a reader without e was shown a private contact address") } if body := get("holder", page); !strings.Contains(body, "[email protected]") { t.Error("a holder of e was refused the field the capability names") } } // A ticket its own tracker marked private. quarry hid it from // everyone, including the people it was for, because there was no // capability to grant an exception to. Fossil shows it to holders of e. // // The failure to avoid is not just "shown to the wrong reader" but // "confirmed to exist": without e the ticket must not resolve at all, // must not be counted, and must not appear under any facet — a listing // that says 4 while showing 3 has told a stranger a private ticket is // there. func TestPrivateTicketsExistOnlyForHoldersOfE(t *testing.T) { store := buildStore(t, "privseed") zrun(t, store, "ticket", "new", "--user", "privseed", "title=a private matter", "status=Open", "is_private=1") dataDir := t.TempDir() srv := ingestAndServeAuth(t, map[string]string{"alpha": store}, dataDir, func(repo, user, pass string, need ops.Auth) api.Decision { if user == "" { return api.Denied } return api.Allowed }) odb, err := oltp.Open(filepath.Join(dataDir, "oltp.db")) if err != nil { t.Fatal(err) } defer odb.Close() for _, u := range []string{"plain", "holder"} { if err := odb.AddAccount(u, "pw12345678", false); err != nil { t.Fatal(err) } } if err := odb.SetCaps("holder", "alpha", "re"); err != nil { t.Fatal(err) } if err := odb.SetCaps("plain", "alpha", "r"); err != nil { t.Fatal(err) } list := func(user string) (titles []string, open int) { req, _ := http.NewRequest("GET", srv.URL+"/api/v1/r/alpha/ticket?state=all&limit=50", nil) req.SetBasicAuth(user, "pw12345678") resp, err := srv.Client().Do(req) if err != nil { t.Fatal(err) } defer resp.Body.Close() var out struct { Tickets []struct{ Title string } `json:"tickets"` Open int `json:"open"` } json.NewDecoder(resp.Body).Decode(&out) for _, tk := range out.Tickets { titles = append(titles, tk.Title) } return titles, out.Open } plainTitles, plainOpen := list("plain") for _, title := range plainTitles { if strings.Contains(title, "private matter") { t.Error("a private ticket was listed to a reader without e") } } heldTitles, heldOpen := list("holder") var found bool for _, title := range heldTitles { if strings.Contains(title, "private matter") { found = true } } if !found { t.Errorf("a holder of e cannot see the private ticket: %v", heldTitles) } // The count must agree with the list each reader is shown. A number // that includes what the listing hides is a confirmation leak. if plainOpen != len(plainTitles) || heldOpen != len(heldTitles) { t.Errorf("counts disagree with listings: plain %d/%d, holder %d/%d", plainOpen, len(plainTitles), heldOpen, len(heldTitles)) } if heldOpen <= plainOpen { t.Errorf("the private ticket is in nobody's count: plain %d, holder %d", plainOpen, heldOpen) } } // Who may land a branch. zit#127 scoped approvals out, correctly, for a CLI // where the operator IS the authority; a multi-account forge has to answer it. // // The rule: accept rides `i` — fossil's Write/check-in letter — because accept // MERGES, and everything else about a landing is a forum artifact. Speaking in // the discussion is `3`; settling it with a check-in is `i`. RFC 0004 stored // `i` and left it inert; this is the surface that wakes it. // // And there is deliberately NO rule against accepting your own landing. Inside // a cathedral the author already holds the commit bit, and a required second // pair of eyes is a policy fossil has no letter for — inventing one here would // be quarry policy wearing fossil's clothes. The test pins that absence so // nobody adds the rule back as an obvious-looking improvement. func TestLandingAcceptRidesTheCheckinCapability(t *testing.T) { stores := map[string]string{"alpha": buildStore(t, "landseed")} dataDir := t.TempDir() srv := ingestAndServeAuth(t, stores, dataDir, func(repo, user, pass string, need ops.Auth) api.Decision { if user == "" { return api.Denied } return api.Allowed }) odb, err := oltp.Open(filepath.Join(dataDir, "oltp.db")) if err != nil { t.Fatal(err) } defer odb.Close() for _, u := range []string{"talker", "lander"} { if err := odb.AddAccount(u, "pw12345678", false); err != nil { t.Fatal(err) } } // talker may read and speak in the thread but not write check-ins; // lander may do both. if err := odb.SetCaps("talker", "alpha", "23"); err != nil { t.Fatal(err) } if err := odb.SetCaps("lander", "alpha", "23i"); err != nil { t.Fatal(err) } code := func(user, path string) int { req, _ := http.NewRequest("POST", srv.URL+path, strings.NewReader(`{"message":"x"}`)) req.Header.Set("Content-Type", "application/json") req.SetBasicAuth(user, "pw12345678") resp, err := srv.Client().Do(req) if err != nil { t.Fatal(err) } resp.Body.Close() return resp.StatusCode } // Forbidden is the capability speaking. Anything else — 404 for a // missing record, 500 from the store — means the gate never ran, so // this asserts the gate and not the outcome behind it. if got := code("talker", "/api/v1/r/alpha/landing/deadbeef/accept"); got != http.StatusForbidden { t.Errorf("accept without i: %d, want 403 — the check-in capability is what settles a landing", got) } if got := code("lander", "/api/v1/r/alpha/landing/deadbeef/accept"); got == http.StatusForbidden { t.Error("a holder of i was refused the accept surface") } // Rejecting writes no check-in, so it must NOT need i. if got := code("talker", "/api/v1/r/alpha/landing/deadbeef/reject"); got == http.StatusForbidden { t.Error("reject demanded the check-in capability; it merges nothing") } if got := code("talker", "/api/v1/r/alpha/landing/deadbeef/comment"); got == http.StatusForbidden { t.Error("commenting on a landing demanded more than the forum capability") } }