package httpapi import "net/http" // ContractVersion is the version of the ratified contract this build serves // (`docs/architecture/14-api-contract/openapi.yaml`, `info.version`). // // It is a CONSTANT and it is the only place a non-streaming client learns which contract it is // talking to. While the major is `0` a differing MINOR carries breaking changes by design, so a // client generated against another one refuses to work and says so — which is why this must be // raised in the same commit as the code that implements a new minor, and never as a courtesy // afterwards. // ⚠ 0.11.0 was the ORDER FORM's minor (D39.196, unified backlog row 279) and it lived for HOURS. // 0.12.0 is the same form's correction: `Run.ordered_chapters` became nullable and `ordered_units` // arrived beside it, because the chapter figure a character order used to publish was the SPAN it // reached into and overstated what was bought by up to a whole chapter — worst on the cheapest order // there is. That changes the wire this build serves, so the number moves with it, in this same // change and not as a courtesy afterwards. // // ⚠ Why a bump and not an amendment inside 0.11.0, decided rather than assumed: 0.11.0's canon text // was edited three times after its landing, and none of those was a change of FORM — an open // vocabulary growing by one value, a defect note, and an ERRATUM correcting what the document said // about members the wire already served. Prose and errata do not move a number; a served shape does. // And 0.11.0 already has its acceptance act (D39.208), which closes it. See the pack's report. // // ⚠ THE GATE IS RED BETWEEN THE TWO LANDINGS, and which side is ahead says which of the two orders // is being followed. The ratified one is CODE FIRST: this constant is raised with the change that // implements the minor, the gate goes red because the canon still reads the EARLIER version, and the // canon's own landing clears it. The reverse — canon first, code behind, the gate red because the // canon reads a LATER minor — is refused deliberately, because in that window the wire announces a // version it does not serve: errata 04.09-в is the day that cost a working day, and 0.13.0 is the day // it happened again and stood for a full day, until the next session of this zone read its battery. // // ⛔ AND WHAT THE GATE CANNOT SEE, said here because this constant is where a reader comes looking: it // compares VERSIONS, not SHAPES. Three separate untruths in the 0.11.0 text passed it in one day — // the bar described in chapters while the wire sent units, `ordered_chapters: 0` promised where the // wire sent 2, three values of a vocabulary that had four — because the NUMBER matched each time. The // bump keeps the number from lying; it does not close the blind spot, which is unified backlog row 309. // // ⚠ 0.13.1 is the canon's CORRECTING minor (D39.235 §6) and this constant did not follow it for a day: // the canon moved first and the gate stood red the whole time, which is the reverse of the order the // paragraph above ratifies. The number is brought to the canon here rather than the other way round, // because the canon was the one that was right — the wire this build served then was the 0.13.1 shape. // // ⚠ 0.14.0 grows `Blocked.code` by `run_in_flight` (D39.244): the order form now says that a book // with a run of its own is not startable, instead of quoting `covers_all` over a click admission was // going to refuse (PD-455). The value went into the canon's enum and the form serves it, which is a // served SHAPE and therefore a minor — the vocabulary of second-level `cause` codes beside it stays // open and moves no number. // ⚠ 0.15.0 changes what `resumeRun` ANSWERS about a run that is already going: `202` with the run // instead of `409`, with one cut — a run already asked to stop keeps its refusal and gains the cause // `stop_requested`. It is a minor because a row of the operation's own table changes meaning, which // in `0.x` is breaking by design; the cause word beside it is second-level and moves no number. What // it buys is that two clicks on «continue» answer the same whichever reached the database first // (D39.246 п.6, PD-448) — the refusal was not a rare race but the ordinary answer to every resume of // a live run. const ContractVersion = "0.17.0" // Capabilities is what this deployment can do: one flat document, the same for every account. type Capabilities struct { // Pairs is what this deployment can translate, and what it merely knows about. Unavailable ones // are LISTED rather than omitted: "absent" and "listed as unavailable" are different facts to a // user waiting for one. Pairs []LanguagePair // IntakeEnabled is whether this deployment takes books at all. False is a read-only instance, // and without the field a client discovers that only by spending a user's upload. IntakeEnabled bool IntakeMaxBytes int64 // ExportFormats is what `POST /books/{bookId}/exports` accepts. Empty means none are built here, // which is the honest answer while the export path is not built (deferred to P8). ExportFormats []string // BankCorrectionsEnabled is whether this deployment serves the correction door, // `POST /books/{bookId}/bank/corrections`. False means the path answers 404 and a client does // not offer the correction UI — machine-readable so nobody learns it by failing a user's save // (canon §Capabilities; the same fact that decides the route's mount). BankCorrectionsEnabled bool // PageSizeDefault is how many rows a collection returns when `limit` is omitted. It is the // DEPLOYMENT's number rather than a constant of the contract, and every read path takes its // default from the same place this answers from. PageSizeDefault int } // LanguagePair is one direction, as the wire carries it. type LanguagePair struct { Source string Target string Available bool } type wireCapabilities struct { ContractVersion string `json:"contract_version"` LanguagePairs []wireLanguagePair `json:"language_pairs"` IntakeEnabled bool `json:"intake_enabled"` IntakeMaxBytes int64 `json:"intake_max_bytes"` ExportFormats []string `json:"export_formats"` BankCorrectionsEnabled bool `json:"bank_corrections_enabled"` PageSizeDefault int `json:"page_size_default"` } type wireLanguagePair struct { Source string `json:"source"` Target string `json:"target"` State string `json:"state"` } func (h *v0) capabilities(w http.ResponseWriter, r *http.Request) { if _, ok := principal(w, r); !ok { return } out := wireCapabilities{ ContractVersion: ContractVersion, LanguagePairs: make([]wireLanguagePair, 0, len(h.caps.Pairs)), IntakeEnabled: h.caps.IntakeEnabled, IntakeMaxBytes: h.caps.IntakeMaxBytes, ExportFormats: h.caps.ExportFormats, BankCorrectionsEnabled: h.caps.BankCorrectionsEnabled, PageSizeDefault: h.caps.PageSizeDefault, } if out.ExportFormats == nil { out.ExportFormats = []string{} // an empty collection is an empty array, never null } for _, p := range h.caps.Pairs { state := "unavailable" if p.Available { state = "available" } out.LanguagePairs = append(out.LanguagePairs, wireLanguagePair{Source: p.Source, Target: p.Target, State: state}) } h.writeJSON(w, r, http.StatusOK, out) }