oficina / suite Public

Clone
git clone https://amadan.net/oficina/suite

Plain git — no account needed to clone.

Download

Download this file

1package suite
2
3import (
4 "embed"
5 "encoding/json"
6 "fmt"
7)
8
9//go:embed fixtures/*.json
10var fixtureFS embed.FS
11
12// Route shapes the server half mounts beside an app's own router. They are
13// part of the contract: the conformance harness fires exactly these, so an app
14// that mounts them elsewhere fails gate 1 rather than failing in production.
15const (
16 RouteS1 = "/suite/s1" // GET, ?cursor=
17 RouteS2 = "/suite/s2" // POST, body is S2Request
18 RouteS3 = "/suite/s3" // GET, /suite/s3/{item_id}?revision=
19)
20
21// S2Request is the wire shape of an authorization question: a typed asker
22// (§3) and up to MaxS2Pairs items. The consumer token rides the transport,
23// not the body.
24type S2Request struct {
25 Asker Subject `json:"asker"`
26 Pairs []ItemRevision `json:"pairs"`
27}
28
29// Rung is where an item sits on the sharing ladder (§4.2, ruling 15). A link
30// is not a rung; it is a read-only capability that any rung may carry, and
31// custody keys on the rung alone.
32type Rung string
33
34const (
35 RungPrivate Rung = "private"
36 RungGranted Rung = "granted"
37 RungTeam Rung = "team"
38 RungDeleted Rung = "deleted"
39)
40
41// CustodyCase is one row of the custody fixture: an item, where it sits, and
42// what both calls must answer for the fixture's asker.
43type CustodyCase struct {
44 Name string `json:"name"`
45 ItemID string `json:"item_id"`
46 Revision int64 `json:"revision"`
47 Rung Rung `json:"rung"`
48 Link string `json:"link"`
49 S3Status int `json:"s3_status"`
50 // S3Body is the exact body a content fetch must return: the item's bytes
51 // for a 200, and empty for every refusal. Checking the status alone let a
52 // server return the required 404 while including the item's secrets in the
53 // body, or return a 200 carrying a different item entirely.
54 S3Body string `json:"s3_body"`
55 S2Result Authorization `json:"s2_result"`
56 // S2ResultSuperseded is the answer when the same item is asked about one
57 // revision behind. A team item is STALE — readable, but the app of record
58 // has moved on — and anything custody already refuses stays refused.
59 S2ResultSuperseded Authorization `json:"s2_result_superseded"`
60 // S2ResultStranger is the answer for a person with no team standing.
61 S2ResultStranger Authorization `json:"s2_result_stranger"`
62}
63
64// NonexistentItemID is never in the fixture. A refusal for an item that does
65// not exist has to be byte-for-byte the refusal for one that exists and is not
66// team-visible — that indistinguishability is the whole of §4.2, and no case
67// in the fixture can test it, because every case is an item that exists.
68const NonexistentItemID = "doc-no-such-item-ever"
69
70// CustodyFixture is gate 3's world: one case per rung, plus team-plus-link and
71// the after-ingest transitions the reviews asked for.
72type CustodyFixture struct {
73 Team string `json:"team"`
74 Asker Subject `json:"asker"`
75 // Stranger has no team standing. Every case refuses them, the team-visible
76 // ones included: team standing is what makes a team item readable.
77 Stranger Subject `json:"stranger"`
78 Cases []CustodyCase `json:"cases"`
79}
80
81// LoadCustodyFixture returns the golden custody cases every app runs.
82func LoadCustodyFixture() (CustodyFixture, error) {
83 raw, err := fixtureFS.ReadFile("fixtures/custody.json")
84 if err != nil {
85 return CustodyFixture{}, fmt.Errorf("suite: reading custody fixture: %w", err)
86 }
87 var f CustodyFixture
88 if err := json.Unmarshal(raw, &f); err != nil {
89 return CustodyFixture{}, fmt.Errorf("suite: parsing custody fixture: %w", err)
90 }
91 if len(f.Cases) == 0 {
92 return CustodyFixture{}, fmt.Errorf("suite: custody fixture has no cases")
93 }
94 return f, nil
95}
96