package suite import ( "embed" "encoding/json" "fmt" ) //go:embed fixtures/*.json var fixtureFS embed.FS // Route shapes the server half mounts beside an app's own router. They are // part of the contract: the conformance harness fires exactly these, so an app // that mounts them elsewhere fails gate 1 rather than failing in production. const ( RouteS1 = "/suite/s1" // GET, ?cursor= RouteS2 = "/suite/s2" // POST, body is S2Request RouteS3 = "/suite/s3" // GET, /suite/s3/{item_id}?revision= ) // S2Request is the wire shape of an authorization question: a typed asker // (§3) and up to MaxS2Pairs items. The consumer token rides the transport, // not the body. type S2Request struct { Asker Subject `json:"asker"` Pairs []ItemRevision `json:"pairs"` } // Rung is where an item sits on the sharing ladder (§4.2, ruling 15). A link // is not a rung; it is a read-only capability that any rung may carry, and // custody keys on the rung alone. type Rung string const ( RungPrivate Rung = "private" RungGranted Rung = "granted" RungTeam Rung = "team" RungDeleted Rung = "deleted" ) // CustodyCase is one row of the custody fixture: an item, where it sits, and // what both calls must answer for the fixture's asker. type CustodyCase struct { Name string `json:"name"` ItemID string `json:"item_id"` Revision int64 `json:"revision"` Rung Rung `json:"rung"` Link string `json:"link"` S3Status int `json:"s3_status"` // S3Body is the exact body a content fetch must return: the item's bytes // for a 200, and empty for every refusal. Checking the status alone let a // server return the required 404 while including the item's secrets in the // body, or return a 200 carrying a different item entirely. S3Body string `json:"s3_body"` S2Result Authorization `json:"s2_result"` // S2ResultSuperseded is the answer when the same item is asked about one // revision behind. A team item is STALE — readable, but the app of record // has moved on — and anything custody already refuses stays refused. S2ResultSuperseded Authorization `json:"s2_result_superseded"` // S2ResultStranger is the answer for a person with no team standing. S2ResultStranger Authorization `json:"s2_result_stranger"` } // NonexistentItemID is never in the fixture. A refusal for an item that does // not exist has to be byte-for-byte the refusal for one that exists and is not // team-visible — that indistinguishability is the whole of §4.2, and no case // in the fixture can test it, because every case is an item that exists. const NonexistentItemID = "doc-no-such-item-ever" // CustodyFixture is gate 3's world: one case per rung, plus team-plus-link and // the after-ingest transitions the reviews asked for. type CustodyFixture struct { Team string `json:"team"` Asker Subject `json:"asker"` // Stranger has no team standing. Every case refuses them, the team-visible // ones included: team standing is what makes a team item readable. Stranger Subject `json:"stranger"` Cases []CustodyCase `json:"cases"` } // LoadCustodyFixture returns the golden custody cases every app runs. func LoadCustodyFixture() (CustodyFixture, error) { raw, err := fixtureFS.ReadFile("fixtures/custody.json") if err != nil { return CustodyFixture{}, fmt.Errorf("suite: reading custody fixture: %w", err) } var f CustodyFixture if err := json.Unmarshal(raw, &f); err != nil { return CustodyFixture{}, fmt.Errorf("suite: parsing custody fixture: %w", err) } if len(f.Cases) == 0 { return CustodyFixture{}, fmt.Errorf("suite: custody fixture has no cases") } return f, nil }