amber-backend/pb_migrations/1796000000_e2e_runs.js
Claude 96f346f775 Show what the release gate found, run by run
A Testy tab on the status dashboard and an `e2e_runs` collection behind it. The
owner's call to put it here rather than stand up a service: 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.

The screenshots ride along and live exactly as long as the run does. A result
whose evidence has been deleted is a claim again rather than a record, and
deleting a run takes its pictures with it, which is the retention policy in one
sentence.

Five outcomes on the cards, not two, because the distinction is the whole point
of the gate: "nelze zde" is a target that cannot ask the question, "čeká na
tebe" is something only the owner can unblock, and neither may be allowed to
read as a pass. The verdict line answers the only question a release asks --
whether anything that blocks shipping has failed -- and the counts underneath
say how much of the catalogue is still unwritten, so a green card cannot quietly
mean an empty one.

Superuser-only, like the rest of this dashboard: it names internal ids and
carries screenshots of a signed-in app.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 01:04:54 +02:00

75 lines
3.2 KiB
JavaScript

/// <reference path="../pb_data/types.d.ts" />
// 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);
},
);