Fas 1a punkt 2: release_gates tabell + go/no-go-vy

This commit is contained in:
Sven (AAMOS AI)
2026-08-05 23:10:09 +07:00
parent 1250640b1d
commit d1455fb581
16 changed files with 10105 additions and 0 deletions
+1
View File
@@ -17,3 +17,4 @@ export * from "./seasons.js";
export * from "./subscriptions.js";
export * from "./analytics.js";
export * from "./platform.js";
export * from "./releaseGates.js";
@@ -0,0 +1,57 @@
/**
* Release gates schema (spec §17).
* Configurable go/no-go criteria evaluated against analytics events.
*/
import { boolean, doublePrecision, index, integer, pgEnum, pgTable, text, timestamp, uuid, varchar } from "drizzle-orm/pg-core";
import { createdAt, updatedAt } from "./_shared.js";
import {
RELEASE_GATE_CATEGORIES,
RELEASE_GATE_COMPARISONS,
RELEASE_GATE_STATUSES,
tuple,
} from "@app/shared-types";
export const releaseGateCategoryEnum = pgEnum(
"release_gate_category",
tuple(RELEASE_GATE_CATEGORIES),
);
export const releaseGateComparisonEnum = pgEnum(
"release_gate_comparison",
tuple(RELEASE_GATE_COMPARISONS),
);
export const releaseGateStatusEnum = pgEnum(
"release_gate_status",
tuple(RELEASE_GATE_STATUSES),
);
export const releaseGates = pgTable(
"release_gates",
{
id: uuid("id").primaryKey().defaultRandom(),
gateKey: varchar("gate_key", { length: 64 }).notNull().unique(),
category: releaseGateCategoryEnum("category").notNull(),
nameSv: text("name_sv").notNull(),
nameEn: text("name_en").notNull(),
descriptionSv: text("description_sv"),
descriptionEn: text("description_en"),
/** Target threshold (01 for ratios, absolute count for counts). */
targetValue: doublePrecision("target_value").notNull(),
comparison: releaseGateComparisonEnum("comparison").notNull(),
/** How many days of analytics events to include in the evaluation. */
evaluationWindowDays: integer("evaluation_window_days").notNull().default(7),
/** Latest computed value, if any. */
lastValue: doublePrecision("last_value"),
lastEvaluatedAt: timestamp("last_evaluated_at", { withTimezone: true }),
status: releaseGateStatusEnum("status").notNull().default("pending"),
/** True if this gate blocks a broad launch until passed. */
blocking: boolean("blocking").notNull().default(false),
/** Free-form admin notes (e.g., why a gate was waived). */
notes: text("notes"),
createdAt: createdAt(),
updatedAt: updatedAt(),
},
(t) => [
index("release_gates_category_idx").on(t.category),
index("release_gates_status_idx").on(t.status),
],
);