package suite import ( "errors" "fmt" ) // ErrNotAvailable is the whole vocabulary of an S3 refusal. // // Doctrine §4.2: a content fetch for anything outside the custody predicate // answers 404, never 403, and the status is identical for "not team-visible" // and "does not exist". Deleted, never team-visible, and departed from // team-visibility are one answer here on purpose — a consumer that could tell // them apart would learn that a private item exists. var ErrNotAvailable = errors.New("suite: item not available") // ErrContractBreach marks a sibling answering in a way the contract forbids. // It is separate from a refusal and from an outage because it is neither: it // is a bug in the sibling that a consumer should surface rather than absorb. var ErrContractBreach = errors.New("suite: sibling broke the contract") // DecodeS3 reads a content fetch. There is no asker anywhere in this call: // S3 is custody-scoped and person-less, and memoria's draft 3 added a person // to it, which both broke the ingest path — the warm-up, the rebuild and // derived-artefact regeneration have no asker — and pulled individually // granted rows into a store that may not hold them. func DecodeS3(status int, body []byte) ([]byte, error) { switch { case status >= 200 && status <= 299: return body, nil case status == 404: return nil, ErrNotAvailable case status == 403: // "404, never 403" is the rule, because 403 means "this exists and // you may not have it" — the one fact custody hides. A sibling // answering 403 is leaking, and folding it into ErrNotAvailable would // keep that leak invisible to everyone downstream. return nil, fmt.Errorf("%w: S3 answered 403 where the contract requires 404", ErrContractBreach) default: return nil, fmt.Errorf("suite: S3 answered %d", status) } }