export const id = '20260928-app-release';
export const name = 'App-Release: AppRelease und OrgReleaseOverride';
/**
* Auslieferung von Apps als **Zeiger** statt als Deployment.
*
* ## Warum zwei Tabellen und nicht eine Spalte `OrgUID`
*
* Ein App-Deploy soll das Umschreiben eines Zeigers sein — kein Container, der
* neu startet. `AppRelease` hält dazu pro `(AppKey, Version)` das S3-Präfix und
* markiert über `Current` genau eine Zeile als die ausgelieferte. Der Normalfall
* ist damit eine Zeile pro App.
*
* Canary ist die **Ausnahme**: eine Organisation bekommt eine andere Version.
* Das ist eine 1:n-Beziehung und liegt deshalb in einer eigenen Tabelle
* (`OrgReleaseOverride`) — nicht als `OrgUID`-Spalte hier. Der Unterschied ist
* nicht kosmetisch: eine Spalte hätte für jede Organisation eine eigene
* `AppRelease`-Zeile erzwungen, also Vervielfachung der Release-Zeilen und eine
* zweite Wahrheit darüber, welches Release „das aktuelle" ist. So ist Canary
* additiv: **kein Eintrag = kein Canary**, und wer keinen Eintrag hat, folgt
* weiter dem Zeiger.
*
* ## Frontend und Backends sind ein Release
*
* `Backends` liegt bewusst **in** der Release-Zeile und nicht in einer eigenen
* Einstellung. Eine neue Frontend-Version ruft in der Regel neue Endpunkte; würde
* man das Frontend bewegen können, ohne das Backend zu bewegen, entstünde genau
* der Bruch, den Canary verhindern soll (neue Oberfläche gegen alten Backend).
* In einer Zeile ist die Kopplung mechanisch statt eine Frage der Disziplin.
* Der Gegenfall bleibt ausdrückbar: ein reiner Backend-Canary ist eine
* Release-Zeile mit **unverändertem** `Prefix` und neuen `Backends`.
*
* `Backends` ist `NULL`-bar: solange nicht jedes Release es gesetzt hat, liefert
* `env.js` unverändert die Werte aus dem Deployment — dieselbe Dual-Read-Logik
* wie beim Registry, nur auf `env.js` angewendet.
*
* ## Auflösung (siehe `registryService.resolveRelease`)
*
* ```
* 1. OrgReleaseOverride[AppKey][OrgUID] → dieses Release, sonst
* 2. AppRelease[AppKey].Current → der Zeiger, sonst
* 3. nichts → kein Release
* ```
*
* ## Herkunft der Vorlage (initTables.sql)
*
* Wie bei den Registry-Enum-Typen liegt die Struktur auch in `initTables.sql`,
* damit frische Installationen nicht auf die Migration warten müssen. Beide Wege
* erzeugen dieselben Tabellen; `IF NOT EXISTS` macht das Zusammenwirken
* idempotent.
*
* @see PLAN-app-registry.md §6.2 — AppRelease / OrgReleaseOverride
*/
/**
* `AppKey` ist die App-Kennung aus dem Registry (`Data.appId` des `app`-Objekts),
* nicht eine UID: die Registry vergibt `appId` als stabilen Schlüssel
* (§ registryTypes), und genau er adressiert ein Release über Organisationen
* hinweg. Die App-Objekte selbst bleiben in `ObjectBase`.
*/
const CREATE_APP_RELEASE = `
CREATE TABLE IF NOT EXISTS \`AppRelease\` (
\`AppKey\` VARCHAR(64) NOT NULL,
\`Version\` VARCHAR(64) NOT NULL,
\`Prefix\` VARCHAR(255) NOT NULL,
\`Backends\` JSON NULL,
\`Current\` TINYINT(1) NOT NULL DEFAULT 0,
PRIMARY KEY (\`AppKey\`, \`Version\`),
KEY \`idx_current\` (\`AppKey\`, \`Current\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
`;
/**
* `(AppKey, OrgUID)` als Primärschlüssel: eine Organisation kann pro App nur auf
* **ein** Canary-Release zeigen. Genau eine Zeile ist damit der Canary, und ein
* Rollback ist ein `DELETE`.
*
* `OrgUID` folgt dem Haus-Format `BINARY(16)` und wird über `U_UUID2BIN(?)`
* adressiert (41-stellige `UUID-…`-Form, § registryTypes: Cast-Regeln).
*
* `AddedAt` steht ohne `ON UPDATE` — es ist der Zeitpunkt, ab dem der Canary
* gilt, und soll sich beim Lesen nicht verändern.
*/
const CREATE_ORG_RELEASE_OVERRIDE = `
CREATE TABLE IF NOT EXISTS \`OrgReleaseOverride\` (
\`AppKey\` VARCHAR(64) NOT NULL,
\`OrgUID\` BINARY(16) NOT NULL,
\`Version\` VARCHAR(64) NOT NULL,
\`AddedAt\` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (\`AppKey\`, \`OrgUID\`),
KEY \`idx_version\` (\`AppKey\`, \`Version\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
`;
export const migrate = async ({ query }) => {
await query(CREATE_APP_RELEASE, []);
await query(CREATE_ORG_RELEASE_OVERRIDE, []);
};