/// // What the release gate found, run by run. // // The catalogue in amber-app (`docs/e2e/catalogue.yaml`) says what a person must // be able to do before a build goes out; the runner executes it and writes one // record here per run. The dashboard's Testy tab reads them. // // WHY A COLLECTION AND NOT A SERVICE: the owner's call, and the right one. A // whole application to display a table of results would cost more to keep alive // than the thing it displays, and the VPS's disk is already the scarce resource // (old releases eat it, not databases). // // **The screenshots live here too, for as long as the run does.** A result whose // evidence has been deleted is a claim again rather than a record, and they are // small next to a release artifact. Deleting a run takes its evidence with it, // which is the retention policy in one sentence. // // Superuser-only, like the status dashboard it appears in: this is operator // data, it names internal ids and it carries screenshots of a signed-in app. // There is no reason for a family account to be able to read it. migrate( (app) => { const runs = new Collection({ type: "base", name: "e2e_runs", listRule: null, viewRule: null, createRule: null, updateRule: null, deleteRule: null, fields: [ // The run's own stamp, e.g. 2026-09-10T00-31-02Z — the directory name on // the machine that ran it, so a record can be traced back to its files. { type: "text", name: "run", required: true, max: 40 }, // linux | windows | phone | tv | attach { type: "text", name: "target", required: true, max: 20 }, // What was under test, not what wrote the record: the app's version and // the commit the cases came from. Without both, a green run a month old // says nothing about which build it green-lit. { type: "text", name: "appVersion", required: true, max: 40 }, { type: "text", name: "commit", required: true, max: 40 }, { type: "number", name: "catalogueVersion", onlyInt: true }, // { pass, fail, blocked, unsupported, unimplemented } { type: "json", name: "counts", maxSize: 2000 }, // One entry per case: id, outcome, blocker, how, ms, evidence, error. // The evidence entries name their screenshot files, which is how the // dashboard pairs a claim with the picture that proves it. { type: "json", name: "cases", maxSize: 2000000 }, { type: "file", name: "shots", maxSelect: 200, maxSize: 8388608, mimeTypes: ["image/png"], }, // Anything the run wants to say for itself — usually why a target could // not start at all, which is a result worth keeping rather than an // absence of one. { type: "text", name: "note", max: 2000 }, { type: "autodate", name: "created", onCreate: true }, ], indexes: [ "CREATE INDEX idx_e2e_runs_created ON e2e_runs (created)", "CREATE INDEX idx_e2e_runs_target ON e2e_runs (target, created)", ], }); app.save(runs); }, (app) => { const c = app.findCollectionByNameOrId("e2e_runs"); if (c) app.delete(c); }, );