@shapio/local 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4152 @@
1
+ import { a as mysqlDriverOf, r as sqliteDriverOf } from "./advisoryLocks-COlJQc1r.js";
2
+ import { sql } from "kysely";
3
+ import { Migrator } from "kysely/migration";
4
+ //#region \0rolldown/runtime.js
5
+ var __defProp = Object.defineProperty;
6
+ var __exportAll = (all, no_symbols) => {
7
+ let target = {};
8
+ for (var name in all) __defProp(target, name, {
9
+ get: all[name],
10
+ enumerable: true
11
+ });
12
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
13
+ return target;
14
+ };
15
+ //#endregion
16
+ //#region ../../apps/api/src/db/migrations/20261001120000_create_platform_tables.ts
17
+ var _20261001120000_create_platform_tables_exports = /* @__PURE__ */ __exportAll({
18
+ down: () => down$34,
19
+ up: () => up$34
20
+ });
21
+ /**
22
+ * Foundation tables shared by every package: durable versions, the job queue, the transactional outbox
23
+ * and the audit log. Statuses are text with CHECK constraints so new states need only a constraint change.
24
+ */
25
+ const up$34 = async (db) => {
26
+ await db.schema.createTable("system_versions").addColumn("id", "boolean", (col) => col.primaryKey().defaultTo(true).check(sql`id`)).addColumn("schema_version", "integer", (col) => col.notNull().defaultTo(0)).addColumn("permissions_version", "integer", (col) => col.notNull().defaultTo(0)).addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).execute();
27
+ await sql`insert into system_versions default values`.execute(db);
28
+ await db.schema.createTable("jobs").addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`)).addColumn("type", "text", (col) => col.notNull()).addColumn("payload", "jsonb", (col) => col.notNull().defaultTo(sql`'{}'::jsonb`)).addColumn("status", "text", (col) => col.notNull().defaultTo("pending").check(sql`status in ('pending', 'running', 'succeeded', 'dead')`)).addColumn("priority", "integer", (col) => col.notNull().defaultTo(0)).addColumn("run_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("attempts", "integer", (col) => col.notNull().defaultTo(0)).addColumn("max_attempts", "integer", (col) => col.notNull().defaultTo(10).check(sql`max_attempts > 0`)).addColumn("idempotency_key", "text").addColumn("locked_by", "text").addColumn("locked_until", "timestamptz").addColumn("checkpoint", "jsonb").addColumn("result", "jsonb").addColumn("last_error", "text").addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("finished_at", "timestamptz").execute();
29
+ await db.schema.createIndex("jobs_idempotency_key_uq").on("jobs").column("idempotency_key").unique().where(sql.ref("idempotency_key"), "is not", null).execute();
30
+ await db.schema.createIndex("jobs_pending_idx").on("jobs").columns(["priority desc", "run_at"]).where(sql.ref("status"), "=", "pending").execute();
31
+ await db.schema.createIndex("jobs_running_lease_idx").on("jobs").column("locked_until").where(sql.ref("status"), "=", "running").execute();
32
+ await db.schema.createTable("outbox_events").addColumn("id", "bigint", (col) => col.primaryKey().generatedAlwaysAsIdentity()).addColumn("event_id", "uuid", (col) => col.notNull().unique().defaultTo(sql`gen_random_uuid()`)).addColumn("type", "text", (col) => col.notNull()).addColumn("aggregate_type", "text", (col) => col.notNull()).addColumn("aggregate_id", "text", (col) => col.notNull()).addColumn("payload", "jsonb", (col) => col.notNull().defaultTo(sql`'{}'::jsonb`)).addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("dispatched_at", "timestamptz").addColumn("dispatch_attempts", "integer", (col) => col.notNull().defaultTo(0)).addColumn("last_dispatch_error", "text").execute();
33
+ await db.schema.createIndex("outbox_events_undispatched_idx").on("outbox_events").column("id").where(sql.ref("dispatched_at"), "is", null).execute();
34
+ await db.schema.createTable("audit_events").addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`)).addColumn("occurred_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("actor_type", "text", (col) => col.notNull().check(sql`actor_type in ('admin', 'app_user', 'token', 'anonymous', 'system')`)).addColumn("actor_id", "text").addColumn("action", "text", (col) => col.notNull()).addColumn("target_type", "text").addColumn("target_id", "text").addColumn("outcome", "text", (col) => col.notNull().check(sql`outcome in ('success', 'failure')`)).addColumn("request_id", "text").addColumn("ip", "text").addColumn("metadata", "jsonb", (col) => col.notNull().defaultTo(sql`'{}'::jsonb`)).execute();
35
+ await db.schema.createIndex("audit_events_occurred_at_idx").on("audit_events").column("occurred_at desc").execute();
36
+ await db.schema.createIndex("audit_events_target_idx").on("audit_events").columns([
37
+ "target_type",
38
+ "target_id",
39
+ "occurred_at desc"
40
+ ]).execute();
41
+ await db.schema.createIndex("audit_events_actor_idx").on("audit_events").columns([
42
+ "actor_type",
43
+ "actor_id",
44
+ "occurred_at desc"
45
+ ]).execute();
46
+ };
47
+ const down$34 = async (db) => {
48
+ await db.schema.dropTable("audit_events").execute();
49
+ await db.schema.dropTable("outbox_events").execute();
50
+ await db.schema.dropTable("jobs").execute();
51
+ await db.schema.dropTable("system_versions").execute();
52
+ };
53
+ //#endregion
54
+ //#region ../../apps/api/src/db/migrations/20261001130000_create_admin_identity.ts
55
+ var _20261001130000_create_admin_identity_exports = /* @__PURE__ */ __exportAll({
56
+ down: () => down$33,
57
+ up: () => up$33
58
+ });
59
+ /**
60
+ * Admin identity (package B, ADR 0005): admin users, roles and their permission grants, server-side
61
+ * sessions, invitations, password resets, first-run setup tokens and API tokens. Every secret is stored
62
+ * as a SHA-256 hash (tokens) or an argon2id hash (passwords), never in plain text.
63
+ */
64
+ const withTimestamps$3 = (table) => table.addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`));
65
+ const uuidPrimaryKey$3 = (table) => table.addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`));
66
+ const up$33 = async (db) => {
67
+ await withTimestamps$3(uuidPrimaryKey$3(db.schema.createTable("admin_users")).addColumn("email", "text", (col) => col.notNull().unique()).addColumn("name", "text", (col) => col.notNull().defaultTo("")).addColumn("password_hash", "text", (col) => col.notNull()).addColumn("status", "text", (col) => col.notNull().defaultTo("active").check(sql`status in ('active', 'disabled')`)).addColumn("last_login_at", "timestamptz").addColumn("password_changed_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`))).execute();
68
+ await withTimestamps$3(uuidPrimaryKey$3(db.schema.createTable("admin_roles")).addColumn("key", "text", (col) => col.notNull().unique().check(sql`key ~ '^[a-z][a-z0-9-]{0,62}$'`)).addColumn("name", "text", (col) => col.notNull()).addColumn("description", "text", (col) => col.notNull().defaultTo("")).addColumn("kind", "text", (col) => col.notNull().defaultTo("admin").check(sql`kind in ('admin', 'delivery')`)).addColumn("is_system", "boolean", (col) => col.notNull().defaultTo(false)).addColumn("version", "integer", (col) => col.notNull().defaultTo(1))).execute();
69
+ await withTimestamps$3(uuidPrimaryKey$3(db.schema.createTable("admin_role_permissions")).addColumn("role_id", "uuid", (col) => col.notNull().references("admin_roles.id").onDelete("cascade")).addColumn("action", "text", (col) => col.notNull()).addColumn("model_id", "text").addColumn("condition", "text", (col) => col.check(sql`condition in ('ownedByPrincipal')`)).addColumn("field_ids", sql`text[]`).addUniqueConstraint("admin_role_permissions_grant_uq", [
70
+ "role_id",
71
+ "action",
72
+ "model_id"
73
+ ], (uc) => uc.nullsNotDistinct())).execute();
74
+ await withTimestamps$3(db.schema.createTable("admin_user_roles").addColumn("admin_user_id", "uuid", (col) => col.notNull().references("admin_users.id").onDelete("cascade")).addColumn("role_id", "uuid", (col) => col.notNull().references("admin_roles.id").onDelete("restrict")).addPrimaryKeyConstraint("admin_user_roles_pk", ["admin_user_id", "role_id"])).execute();
75
+ await db.schema.createIndex("admin_user_roles_role_idx").on("admin_user_roles").column("role_id").execute();
76
+ await withTimestamps$3(uuidPrimaryKey$3(db.schema.createTable("admin_sessions")).addColumn("admin_user_id", "uuid", (col) => col.notNull().references("admin_users.id").onDelete("cascade")).addColumn("token_hash", "text", (col) => col.notNull().unique()).addColumn("csrf_secret", "text", (col) => col.notNull()).addColumn("ip", "text").addColumn("user_agent", "text").addColumn("last_seen_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("expires_at", "timestamptz", (col) => col.notNull()).addColumn("revoked_at", "timestamptz").addColumn("rotation_required", "boolean", (col) => col.notNull().defaultTo(false))).execute();
77
+ await db.schema.createIndex("admin_sessions_user_idx").on("admin_sessions").column("admin_user_id").where(sql.ref("revoked_at"), "is", null).execute();
78
+ await withTimestamps$3(uuidPrimaryKey$3(db.schema.createTable("admin_invitations")).addColumn("email", "text", (col) => col.notNull()).addColumn("role_ids", sql`uuid[]`, (col) => col.notNull().defaultTo(sql`'{}'::uuid[]`)).addColumn("token_hash", "text", (col) => col.unique()).addColumn("invited_by", "uuid", (col) => col.references("admin_users.id").onDelete("set null")).addColumn("expires_at", "timestamptz", (col) => col.notNull()).addColumn("accepted_at", "timestamptz").addColumn("revoked_at", "timestamptz")).execute();
79
+ await db.schema.createIndex("admin_invitations_pending_email_idx").on("admin_invitations").column("email").where(sql`accepted_at is null and revoked_at is null`).execute();
80
+ await withTimestamps$3(uuidPrimaryKey$3(db.schema.createTable("password_resets")).addColumn("admin_user_id", "uuid", (col) => col.notNull().references("admin_users.id").onDelete("cascade")).addColumn("token_hash", "text", (col) => col.unique()).addColumn("expires_at", "timestamptz", (col) => col.notNull()).addColumn("used_at", "timestamptz")).execute();
81
+ await db.schema.createIndex("password_resets_user_idx").on("password_resets").column("admin_user_id").execute();
82
+ await withTimestamps$3(uuidPrimaryKey$3(db.schema.createTable("setup_tokens")).addColumn("token_hash", "text", (col) => col.notNull().unique()).addColumn("used_at", "timestamptz").addColumn("superseded_at", "timestamptz")).execute();
83
+ await withTimestamps$3(uuidPrimaryKey$3(db.schema.createTable("api_tokens")).addColumn("name", "text", (col) => col.notNull()).addColumn("token_hash", "text", (col) => col.notNull().unique()).addColumn("token_prefix", "text", (col) => col.notNull()).addColumn("role_id", "uuid", (col) => col.notNull().references("admin_roles.id").onDelete("restrict")).addColumn("created_by", "uuid", (col) => col.references("admin_users.id").onDelete("set null")).addColumn("expires_at", "timestamptz").addColumn("last_used_at", "timestamptz").addColumn("revoked_at", "timestamptz")).execute();
84
+ await db.schema.createIndex("api_tokens_role_idx").on("api_tokens").column("role_id").execute();
85
+ };
86
+ const down$33 = async (db) => {
87
+ await db.schema.dropTable("api_tokens").execute();
88
+ await db.schema.dropTable("setup_tokens").execute();
89
+ await db.schema.dropTable("password_resets").execute();
90
+ await db.schema.dropTable("admin_invitations").execute();
91
+ await db.schema.dropTable("admin_sessions").execute();
92
+ await db.schema.dropTable("admin_user_roles").execute();
93
+ await db.schema.dropTable("admin_role_permissions").execute();
94
+ await db.schema.dropTable("admin_roles").execute();
95
+ await db.schema.dropTable("admin_users").execute();
96
+ };
97
+ //#endregion
98
+ //#region ../../apps/api/src/db/migrations/20261001130500_create_system_settings.ts
99
+ var _20261001130500_create_system_settings_exports = /* @__PURE__ */ __exportAll({
100
+ down: () => down$32,
101
+ up: () => up$32
102
+ });
103
+ /**
104
+ * Instance-wide key/value settings that must survive restarts and be shared by every instance, e.g. the
105
+ * generated signing secret. General purpose: later packages add keys, not tables.
106
+ */
107
+ const up$32 = async (db) => {
108
+ await db.schema.createTable("system_settings").addColumn("key", "text", (col) => col.primaryKey()).addColumn("value", "text", (col) => col.notNull()).addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).execute();
109
+ };
110
+ const down$32 = async (db) => {
111
+ await db.schema.dropTable("system_settings").execute();
112
+ };
113
+ //#endregion
114
+ //#region ../../apps/api/src/db/migrations/20261001140000_create_schema_tables.ts
115
+ var _20261001140000_create_schema_tables_exports = /* @__PURE__ */ __exportAll({
116
+ down: () => down$31,
117
+ up: () => up$31
118
+ });
119
+ /**
120
+ * Schema registry (package D, ADR 0002): models and components (one table, `kind` discriminator), immutable
121
+ * schema revisions, the per-model active pointer, schema change jobs, locales and schema settings.
122
+ * Model definitions are data: creating or changing a model never runs DDL (CONTRIBUTING.md rule 1).
123
+ */
124
+ const up$31 = async (db) => {
125
+ await db.schema.createTable("models").addColumn("id", "uuid", (col) => col.primaryKey()).addColumn("kind", "text", (col) => col.notNull().check(sql`kind in ('collection', 'singleton', 'component')`)).addColumn("api_key", "text", (col) => col.notNull()).addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("deleted_at", "timestamptz").execute();
126
+ await sql`create unique index models_api_key_uq on models (lower(api_key)) where deleted_at is null`.execute(db);
127
+ await db.schema.createTable("schema_revisions").addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`)).addColumn("model_id", "uuid", (col) => col.notNull().references("models.id").onDelete("restrict")).addColumn("version", "integer", (col) => col.notNull().check(sql`version > 0`)).addColumn("definition", "jsonb", (col) => col.notNull()).addColumn("hash", "text", (col) => col.notNull()).addColumn("parent_revision_id", "uuid", (col) => col.references("schema_revisions.id")).addColumn("created_by_type", "text", (col) => col.notNull()).addColumn("created_by_id", "text").addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addUniqueConstraint("schema_revisions_model_version_uq", ["model_id", "version"]).execute();
128
+ await sql`
129
+ create function schema_revisions_immutable() returns trigger language plpgsql as $$
130
+ begin
131
+ raise exception 'schema_revisions rows are immutable';
132
+ end;
133
+ $$
134
+ `.execute(db);
135
+ await sql`
136
+ create trigger schema_revisions_no_update before update on schema_revisions
137
+ for each row execute function schema_revisions_immutable()
138
+ `.execute(db);
139
+ await db.schema.createTable("model_active_versions").addColumn("model_id", "uuid", (col) => col.primaryKey().references("models.id").onDelete("cascade")).addColumn("revision_id", "uuid", (col) => col.notNull().references("schema_revisions.id")).addColumn("version", "integer", (col) => col.notNull()).addColumn("schema_version", "integer", (col) => col.notNull()).addColumn("activated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).execute();
140
+ await db.schema.createTable("schema_change_jobs").addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`)).addColumn("target_type", "text", (col) => col.notNull().check(sql`target_type in ('model', 'locale')`)).addColumn("target_id", "text", (col) => col.notNull()).addColumn("model_id", "uuid", (col) => col.references("models.id")).addColumn("from_revision_id", "uuid", (col) => col.references("schema_revisions.id")).addColumn("to_revision_id", "uuid", (col) => col.references("schema_revisions.id")).addColumn("status", "text", (col) => col.notNull().defaultTo("pending").check(sql`status in ('pending', 'running', 'activated', 'failed', 'cancelled')`)).addColumn("plan", "jsonb", (col) => col.notNull().defaultTo(sql`'{}'::jsonb`)).addColumn("job_id", "uuid", (col) => col.references("jobs.id").onDelete("set null")).addColumn("error", "jsonb").addColumn("requested_by_type", "text", (col) => col.notNull()).addColumn("requested_by_id", "text").addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("finished_at", "timestamptz").execute();
141
+ await sql`
142
+ create unique index schema_change_jobs_in_flight_uq on schema_change_jobs (target_type, target_id)
143
+ where status in ('pending', 'running')
144
+ `.execute(db);
145
+ await db.schema.createIndex("schema_change_jobs_model_idx").on("schema_change_jobs").columns(["model_id", "created_at desc"]).execute();
146
+ await db.schema.createTable("locales").addColumn("code", "text", (col) => col.primaryKey()).addColumn("label", "text", (col) => col.notNull()).addColumn("is_default", "boolean", (col) => col.notNull().defaultTo(false)).addColumn("fallbacks", sql`text[]`, (col) => col.notNull().defaultTo(sql`'{}'::text[]`)).addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).execute();
147
+ await sql`create unique index locales_single_default_uq on locales (is_default) where is_default`.execute(db);
148
+ await sql`insert into locales (code, label, is_default) values ('en', 'English', true)`.execute(db);
149
+ await db.schema.createTable("schema_settings").addColumn("id", "boolean", (col) => col.primaryKey().defaultTo(true).check(sql`id`)).addColumn("read_only", "boolean", (col) => col.notNull().defaultTo(false)).addColumn("read_only_reason", "text").addColumn("updated_by_type", "text").addColumn("updated_by_id", "text").addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).execute();
150
+ await sql`insert into schema_settings default values`.execute(db);
151
+ };
152
+ const down$31 = async (db) => {
153
+ await db.schema.dropTable("schema_settings").execute();
154
+ await db.schema.dropTable("locales").execute();
155
+ await db.schema.dropTable("schema_change_jobs").execute();
156
+ await db.schema.dropTable("model_active_versions").execute();
157
+ await db.schema.dropTable("schema_revisions").execute();
158
+ await sql`drop function schema_revisions_immutable()`.execute(db);
159
+ await db.schema.dropTable("models").execute();
160
+ };
161
+ //#endregion
162
+ //#region ../../apps/api/src/db/migrations/20261001150000_create_content_tables.ts
163
+ var _20261001150000_create_content_tables_exports = /* @__PURE__ */ __exportAll({
164
+ down: () => down$30,
165
+ up: () => up$30
166
+ });
167
+ /**
168
+ * Content storage (package E, ADR 0001): entries, immutable content revisions, mutable heads (one per
169
+ * entry, locale and state) with every content index, derived relation edges, the unique-value registry
170
+ * and the publication log behind `?snapshot=N`. User content models never get tables of their own
171
+ * (CONTRIBUTING.md rule 1): their values are JSONB keyed by stable field IDs.
172
+ */
173
+ const up$30 = async (db) => {
174
+ await db.schema.createTable("entries").addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`)).addColumn("model_id", "uuid", (col) => col.notNull().references("models.id").onDelete("restrict")).addColumn("owner_app_user_id", "uuid").addColumn("created_by_admin_id", "uuid", (col) => col.references("admin_users.id").onDelete("set null")).addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("deleted_at", "timestamptz").execute();
175
+ await sql`create index entries_model_idx on entries (model_id, created_at) where deleted_at is null`.execute(db);
176
+ await sql`create index entries_owner_idx on entries (owner_app_user_id) where owner_app_user_id is not null`.execute(db);
177
+ await db.schema.createTable("content_revisions").addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`)).addColumn("entry_id", "uuid", (col) => col.notNull().references("entries.id").onDelete("restrict")).addColumn("locale", "text", (col) => col.notNull()).addColumn("schema_revision_id", "uuid", (col) => col.notNull().references("schema_revisions.id")).addColumn("data", "jsonb", (col) => col.notNull()).addColumn("reason", "text", (col) => col.notNull().check(sql`reason in ('create', 'save', 'publish', 'restore', 'duplicate', 'localize')`)).addColumn("author_type", "text", (col) => col.notNull()).addColumn("author_id", "text").addColumn("parent_revision_id", "uuid", (col) => col.references("content_revisions.id")).addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).execute();
178
+ await db.schema.createIndex("content_revisions_entry_idx").on("content_revisions").columns([
179
+ "entry_id",
180
+ "locale",
181
+ "created_at"
182
+ ]).execute();
183
+ await sql`
184
+ create function content_revisions_immutable() returns trigger language plpgsql as $$
185
+ begin
186
+ raise exception 'content_revisions rows are immutable';
187
+ end;
188
+ $$
189
+ `.execute(db);
190
+ await sql`
191
+ create trigger content_revisions_no_update before update on content_revisions
192
+ for each row execute function content_revisions_immutable()
193
+ `.execute(db);
194
+ await sql`create sequence entry_heads_change_seq as bigint`.execute(db);
195
+ await db.schema.createTable("entry_heads").addColumn("entry_id", "uuid", (col) => col.notNull().references("entries.id").onDelete("cascade")).addColumn("model_id", "uuid", (col) => col.notNull()).addColumn("locale", "text", (col) => col.notNull()).addColumn("state", "text", (col) => col.notNull().check(sql`state in ('draft', 'published')`)).addColumn("revision_id", "uuid", (col) => col.notNull().references("content_revisions.id")).addColumn("data", "jsonb", (col) => col.notNull()).addColumn("autosaved_at", "timestamptz").addColumn("version", "integer", (col) => col.notNull().defaultTo(1)).addColumn("change_seq", "bigint", (col) => col.notNull().defaultTo(sql`nextval('entry_heads_change_seq')`)).addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addPrimaryKeyConstraint("entry_heads_pkey", [
196
+ "entry_id",
197
+ "locale",
198
+ "state"
199
+ ]).execute();
200
+ await sql`alter sequence entry_heads_change_seq owned by entry_heads.change_seq`.execute(db);
201
+ await sql`create index entry_heads_data_gin on entry_heads using gin (data jsonb_path_ops)`.execute(db);
202
+ await db.schema.createIndex("entry_heads_model_locale_state_idx").on("entry_heads").columns([
203
+ "model_id",
204
+ "locale",
205
+ "state"
206
+ ]).execute();
207
+ await db.schema.createIndex("entry_heads_change_seq_idx").on("entry_heads").columns(["model_id", "change_seq"]).execute();
208
+ await db.schema.createTable("relation_edges").addColumn("source_entry_id", "uuid", (col) => col.notNull()).addColumn("locale", "text", (col) => col.notNull()).addColumn("state", "text", (col) => col.notNull()).addColumn("field_id", "uuid", (col) => col.notNull()).addColumn("position", "integer", (col) => col.notNull()).addColumn("target_entry_id", "uuid", (col) => col.notNull().references("entries.id")).addPrimaryKeyConstraint("relation_edges_pkey", [
209
+ "source_entry_id",
210
+ "locale",
211
+ "state",
212
+ "field_id",
213
+ "position"
214
+ ]).addForeignKeyConstraint("relation_edges_head_fk", [
215
+ "source_entry_id",
216
+ "locale",
217
+ "state"
218
+ ], "entry_heads", [
219
+ "entry_id",
220
+ "locale",
221
+ "state"
222
+ ], (constraint) => constraint.onDelete("cascade")).execute();
223
+ await db.schema.createIndex("relation_edges_target_idx").on("relation_edges").columns(["target_entry_id", "state"]).execute();
224
+ await db.schema.createTable("unique_values").addColumn("field_id", "uuid", (col) => col.notNull()).addColumn("locale", "text", (col) => col.notNull()).addColumn("state", "text", (col) => col.notNull().check(sql`state in ('draft', 'published')`)).addColumn("value_hash", "text", (col) => col.notNull()).addColumn("entry_id", "uuid", (col) => col.notNull().references("entries.id").onDelete("cascade")).addColumn("model_id", "uuid", (col) => col.notNull()).addPrimaryKeyConstraint("unique_values_pkey", [
225
+ "field_id",
226
+ "locale",
227
+ "state",
228
+ "value_hash"
229
+ ]).execute();
230
+ await db.schema.createIndex("unique_values_entry_idx").on("unique_values").column("entry_id").execute();
231
+ await db.schema.createTable("publication_state").addColumn("id", "boolean", (col) => col.primaryKey().defaultTo(true).check(sql`id`)).addColumn("last_seq", "bigint", (col) => col.notNull().defaultTo(0)).execute();
232
+ await sql`insert into publication_state default values`.execute(db);
233
+ await db.schema.createTable("publication_log").addColumn("id", "bigint", (col) => col.primaryKey().generatedAlwaysAsIdentity()).addColumn("entry_id", "uuid", (col) => col.notNull().references("entries.id")).addColumn("model_id", "uuid", (col) => col.notNull()).addColumn("locale", "text", (col) => col.notNull()).addColumn("revision_id", "uuid", (col) => col.notNull().references("content_revisions.id")).addColumn("from_seq", "bigint", (col) => col.notNull()).addColumn("to_seq", "bigint").addColumn("published_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).execute();
234
+ await sql`create unique index publication_log_open_uq on publication_log (entry_id, locale) where to_seq is null`.execute(db);
235
+ await db.schema.createIndex("publication_log_model_seq_idx").on("publication_log").columns(["model_id", "from_seq"]).execute();
236
+ };
237
+ const down$30 = async (db) => {
238
+ await db.schema.dropTable("publication_log").execute();
239
+ await db.schema.dropTable("publication_state").execute();
240
+ await db.schema.dropTable("unique_values").execute();
241
+ await db.schema.dropTable("relation_edges").execute();
242
+ await db.schema.dropTable("entry_heads").execute();
243
+ await db.schema.dropTable("content_revisions").execute();
244
+ await sql`drop function content_revisions_immutable()`.execute(db);
245
+ await db.schema.dropTable("entries").execute();
246
+ };
247
+ //#endregion
248
+ //#region ../../apps/api/src/db/migrations/20261001160000_create_media_tables.ts
249
+ var _20261001160000_create_media_tables_exports = /* @__PURE__ */ __exportAll({
250
+ down: () => down$29,
251
+ up: () => up$29
252
+ });
253
+ /**
254
+ * Media library (package G): folders, assets, image variants, references from content and upload grants.
255
+ * Bytes live in a storage driver (local disk or S3-compatible); rows record which driver and key hold
256
+ * each object, so `shapio media migrate` can move assets one at a time while the server keeps serving.
257
+ */
258
+ const withTimestamps$2 = (table) => table.addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`));
259
+ const uuidPrimaryKey$2 = (table) => table.addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`));
260
+ const STORAGE_DRIVER_CHECK = sql`storage_driver in ('local', 's3')`;
261
+ const VISIBILITY_CHECK = sql`visibility in ('public', 'private')`;
262
+ const up$29 = async (db) => {
263
+ await withTimestamps$2(uuidPrimaryKey$2(db.schema.createTable("media_folders")).addColumn("parent_id", "uuid", (col) => col.references("media_folders.id").onDelete("restrict")).addColumn("name", "text", (col) => col.notNull().check(sql`length(name) between 1 and 255`)).addColumn("created_by", "uuid", (col) => col.references("admin_users.id").onDelete("set null")).addColumn("version", "integer", (col) => col.notNull().defaultTo(1))).execute();
264
+ await sql`create unique index media_folders_sibling_name_uq on media_folders
265
+ (coalesce(parent_id, '00000000-0000-0000-0000-000000000000'::uuid), lower(name))`.execute(db);
266
+ await withTimestamps$2(db.schema.createTable("media_assets").addColumn("id", "uuid", (col) => col.primaryKey()).addColumn("folder_id", "uuid", (col) => col.references("media_folders.id").onDelete("set null")).addColumn("storage_driver", "text", (col) => col.notNull().check(STORAGE_DRIVER_CHECK)).addColumn("storage_key", "text", (col) => col.notNull()).addColumn("original_filename", "text", (col) => col.notNull()).addColumn("mime_type", "text", (col) => col.notNull()).addColumn("size_bytes", "bigint", (col) => col.notNull()).addColumn("width", "integer").addColumn("height", "integer").addColumn("checksum_sha256", "text").addColumn("alt", "text", (col) => col.notNull().defaultTo("")).addColumn("caption", "text", (col) => col.notNull().defaultTo("")).addColumn("focal_x", "real", (col) => col.check(sql`focal_x between 0 and 1`)).addColumn("focal_y", "real", (col) => col.check(sql`focal_y between 0 and 1`)).addColumn("visibility", "text", (col) => col.notNull().defaultTo("public").check(VISIBILITY_CHECK)).addColumn("status", "text", (col) => col.notNull().defaultTo("processing").check(sql`status in ('processing', 'ready', 'failed')`)).addColumn("processing_error", "text").addColumn("created_by", "uuid", (col) => col.references("admin_users.id").onDelete("set null")).addColumn("version", "integer", (col) => col.notNull().defaultTo(1)).addColumn("deleted_at", "timestamptz").addCheckConstraint("media_assets_focal_point_ck", sql`(focal_x is null) = (focal_y is null)`)).execute();
267
+ await db.schema.createIndex("media_assets_storage_key_uq").on("media_assets").column("storage_key").unique().execute();
268
+ await db.schema.createIndex("media_assets_folder_idx").on("media_assets").columns([
269
+ "folder_id",
270
+ "created_at",
271
+ "id"
272
+ ]).where(sql.ref("deleted_at"), "is", null).execute();
273
+ await db.schema.createIndex("media_assets_created_idx").on("media_assets").columns(["created_at", "id"]).where(sql.ref("deleted_at"), "is", null).execute();
274
+ await db.schema.createIndex("media_assets_driver_idx").on("media_assets").columns(["storage_driver", "id"]).execute();
275
+ await withTimestamps$2(uuidPrimaryKey$2(db.schema.createTable("media_variants")).addColumn("asset_id", "uuid", (col) => col.notNull().references("media_assets.id").onDelete("cascade")).addColumn("name", "text", (col) => col.notNull()).addColumn("width", "integer").addColumn("height", "integer").addColumn("format", "text", (col) => col.notNull()).addColumn("mime_type", "text", (col) => col.notNull()).addColumn("storage_key", "text").addColumn("size_bytes", "bigint").addColumn("checksum_sha256", "text").addColumn("status", "text", (col) => col.notNull().defaultTo("pending").check(sql`status in ('pending', 'ready', 'failed')`)).addColumn("error", "text").addUniqueConstraint("media_variants_asset_name_uq", ["asset_id", "name"])).execute();
276
+ await db.schema.createIndex("media_variants_storage_key_uq").on("media_variants").column("storage_key").unique().execute();
277
+ await db.schema.createTable("media_references").addColumn("asset_id", "uuid", (col) => col.notNull().references("media_assets.id").onDelete("cascade")).addColumn("entry_id", "uuid", (col) => col.notNull().references("entries.id").onDelete("cascade")).addColumn("model_id", "uuid", (col) => col.notNull()).addColumn("field_id", "text", (col) => col.notNull()).addColumn("locale", "text", (col) => col.notNull()).addColumn("state", "text", (col) => col.notNull().check(sql`state in ('draft', 'published')`)).addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addPrimaryKeyConstraint("media_references_pk", [
278
+ "asset_id",
279
+ "entry_id",
280
+ "locale",
281
+ "state",
282
+ "field_id"
283
+ ]).execute();
284
+ await db.schema.createIndex("media_references_entry_idx").on("media_references").columns([
285
+ "entry_id",
286
+ "locale",
287
+ "state"
288
+ ]).execute();
289
+ await db.schema.createTable("media_upload_grants").addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`)).addColumn("asset_id", "uuid", (col) => col.notNull()).addColumn("kind", "text", (col) => col.notNull().check(sql`kind in ('create', 'replace')`)).addColumn("storage_driver", "text", (col) => col.notNull().check(STORAGE_DRIVER_CHECK)).addColumn("storage_key", "text", (col) => col.notNull().unique()).addColumn("original_filename", "text", (col) => col.notNull()).addColumn("declared_mime_type", "text", (col) => col.notNull()).addColumn("max_size_bytes", "bigint", (col) => col.notNull()).addColumn("folder_id", "uuid").addColumn("visibility", "text", (col) => col.notNull().check(VISIBILITY_CHECK)).addColumn("created_by", "uuid", (col) => col.references("admin_users.id").onDelete("set null")).addColumn("created_by_principal", "text", (col) => col.notNull()).addColumn("status", "text", (col) => col.notNull().defaultTo("pending").check(sql`status in ('pending', 'consumed', 'rejected', 'expired')`)).addColumn("expires_at", "timestamptz", (col) => col.notNull()).addColumn("consumed_at", "timestamptz").addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).execute();
290
+ };
291
+ const down$29 = async (db) => {
292
+ await db.schema.dropTable("media_upload_grants").execute();
293
+ await db.schema.dropTable("media_references").execute();
294
+ await db.schema.dropTable("media_variants").execute();
295
+ await db.schema.dropTable("media_assets").execute();
296
+ await db.schema.dropTable("media_folders").execute();
297
+ };
298
+ //#endregion
299
+ //#region ../../apps/api/src/db/migrations/20261001170000_create_publishing_tables.ts
300
+ var _20261001170000_create_publishing_tables_exports = /* @__PURE__ */ __exportAll({
301
+ down: () => down$28,
302
+ up: () => up$28
303
+ });
304
+ /**
305
+ * Publishing (package H): scheduled publications, releases, webhooks and their delivery log, deployment
306
+ * connections and runs, and preview tokens. Secrets (webhook signing secrets, provider tokens) are stored
307
+ * encrypted with a key derived from the instance signing secret; preview tokens are stored as hashes.
308
+ */
309
+ const withTimestamps$1 = (table) => table.addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`));
310
+ const uuidPrimaryKey$1 = (table) => table.addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`));
311
+ const createdBy = (table) => table.addColumn("created_by", "uuid", (col) => col.references("admin_users.id").onDelete("set null"));
312
+ const PUBLICATION_ACTION_CHECK = sql`action in ('publish', 'unpublish')`;
313
+ const createSchedulingTables = async (db) => {
314
+ await withTimestamps$1(createdBy(uuidPrimaryKey$1(db.schema.createTable("scheduled_publications")).addColumn("entry_id", "uuid", (col) => col.notNull().references("entries.id").onDelete("cascade")).addColumn("model_id", "uuid", (col) => col.notNull()).addColumn("locale", "text", (col) => col.notNull()).addColumn("action", "text", (col) => col.notNull().check(PUBLICATION_ACTION_CHECK)).addColumn("run_at", "timestamptz", (col) => col.notNull()).addColumn("status", "text", (col) => col.notNull().defaultTo("scheduled").check(sql`status in ('scheduled', 'done', 'failed', 'cancelled')`)).addColumn("created_by_token", "uuid", (col) => col.references("api_tokens.id").onDelete("set null")).addColumn("job_id", "uuid").addColumn("snapshot_seq", "bigint").addColumn("error", "text").addColumn("executed_at", "timestamptz"))).execute();
315
+ await db.schema.createIndex("scheduled_publications_status_idx").on("scheduled_publications").columns(["status", "run_at"]).execute();
316
+ await db.schema.createIndex("scheduled_publications_entry_idx").on("scheduled_publications").columns(["entry_id", "locale"]).execute();
317
+ await withTimestamps$1(createdBy(uuidPrimaryKey$1(db.schema.createTable("releases")).addColumn("name", "text", (col) => col.notNull().check(sql`length(name) between 1 and 200`)).addColumn("description", "text", (col) => col.notNull().defaultTo("")).addColumn("status", "text", (col) => col.notNull().defaultTo("draft").check(sql`status in ('draft', 'scheduled', 'published', 'failed', 'cancelled')`)).addColumn("scheduled_at", "timestamptz").addColumn("schedule_job_id", "uuid").addColumn("scheduled_by", "uuid", (col) => col.references("admin_users.id").onDelete("set null")).addColumn("scheduled_by_token", "uuid", (col) => col.references("api_tokens.id").onDelete("set null")).addColumn("executed_at", "timestamptz").addColumn("snapshot_seq", "bigint").addColumn("error", "text").addColumn("version", "integer", (col) => col.notNull().defaultTo(1)))).execute();
318
+ await db.schema.createIndex("releases_status_idx").on("releases").columns(["status", "created_at"]).execute();
319
+ await uuidPrimaryKey$1(db.schema.createTable("release_items")).addColumn("release_id", "uuid", (col) => col.notNull().references("releases.id").onDelete("cascade")).addColumn("entry_id", "uuid", (col) => col.notNull().references("entries.id").onDelete("cascade")).addColumn("model_id", "uuid", (col) => col.notNull()).addColumn("locale", "text", (col) => col.notNull()).addColumn("action", "text", (col) => col.notNull().check(PUBLICATION_ACTION_CHECK)).addColumn("status", "text", (col) => col.notNull().defaultTo("pending").check(sql`status in ('pending', 'done', 'failed')`)).addColumn("error", "text").addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addUniqueConstraint("release_items_entry_locale_uq", [
320
+ "release_id",
321
+ "entry_id",
322
+ "locale"
323
+ ]).execute();
324
+ };
325
+ const createWebhookTables = async (db) => {
326
+ await withTimestamps$1(createdBy(uuidPrimaryKey$1(db.schema.createTable("webhooks")).addColumn("name", "text", (col) => col.notNull().check(sql`length(name) between 1 and 200`)).addColumn("url", "text", (col) => col.notNull()).addColumn("events", sql`text[]`, (col) => col.notNull().defaultTo(sql`'{}'::text[]`)).addColumn("enabled", "boolean", (col) => col.notNull().defaultTo(true)).addColumn("secret_encrypted", "text", (col) => col.notNull()).addColumn("allow_private_network", "boolean", (col) => col.notNull().defaultTo(false)).addColumn("max_attempts", "integer", (col) => col.notNull().defaultTo(8).check(sql`max_attempts between 1 and 20`)).addColumn("version", "integer", (col) => col.notNull().defaultTo(1)))).execute();
327
+ await withTimestamps$1(uuidPrimaryKey$1(db.schema.createTable("webhook_deliveries")).addColumn("webhook_id", "uuid", (col) => col.notNull().references("webhooks.id").onDelete("cascade")).addColumn("event_id", "uuid").addColumn("event_type", "text", (col) => col.notNull()).addColumn("payload", "jsonb", (col) => col.notNull()).addColumn("is_test", "boolean", (col) => col.notNull().defaultTo(false)).addColumn("status", "text", (col) => col.notNull().defaultTo("pending").check(sql`status in ('pending', 'retrying', 'succeeded', 'dead')`)).addColumn("attempts", "integer", (col) => col.notNull().defaultTo(0)).addColumn("last_response_status", "integer").addColumn("last_error", "text").addColumn("attempt_log", "jsonb", (col) => col.notNull().defaultTo(sql`'[]'::jsonb`)).addColumn("job_id", "uuid").addColumn("delivered_at", "timestamptz")).execute();
328
+ await db.schema.createIndex("webhook_deliveries_event_uq").on("webhook_deliveries").columns(["webhook_id", "event_id"]).unique().where(sql.ref("event_id"), "is not", null).execute();
329
+ await db.schema.createIndex("webhook_deliveries_webhook_idx").on("webhook_deliveries").columns(["webhook_id", "created_at desc"]).execute();
330
+ };
331
+ const createDeploymentTables = async (db) => {
332
+ await withTimestamps$1(createdBy(uuidPrimaryKey$1(db.schema.createTable("deployment_connections")).addColumn("name", "text", (col) => col.notNull().check(sql`length(name) between 1 and 200`)).addColumn("provider", "text", (col) => col.notNull().check(sql`provider in ('generic_webhook', 'cloudflare_pages', 'github')`)).addColumn("settings", "jsonb", (col) => col.notNull().defaultTo(sql`'{}'::jsonb`)).addColumn("secrets_encrypted", "text", (col) => col.notNull()).addColumn("secret_env_refs", "jsonb", (col) => col.notNull().defaultTo(sql`'{}'::jsonb`)).addColumn("delivery_role_id", "uuid", (col) => col.references("admin_roles.id").onDelete("set null")).addColumn("preview_url_template", "text").addColumn("trigger_policy", sql`text[]`, (col) => col.notNull().defaultTo(sql`'{}'::text[]`)).addColumn("debounce_seconds", "integer", (col) => col.notNull().defaultTo(10).check(sql`debounce_seconds between 0 and 3600`)).addColumn("allow_private_network", "boolean", (col) => col.notNull().defaultTo(false)).addColumn("enabled", "boolean", (col) => col.notNull().defaultTo(true)).addColumn("version", "integer", (col) => col.notNull().defaultTo(1)))).execute();
333
+ await withTimestamps$1(createdBy(uuidPrimaryKey$1(db.schema.createTable("deployment_runs")).addColumn("connection_id", "uuid", (col) => col.notNull().references("deployment_connections.id").onDelete("cascade")).addColumn("status", "text", (col) => col.notNull().defaultTo("queued").check(sql`status in ('queued', 'triggered', 'building', 'deployed', 'failed', 'unknown')`)).addColumn("status_rank", "integer", (col) => col.notNull().defaultTo(0)).addColumn("trigger", "text", (col) => col.notNull().check(sql`trigger in ('publish', 'release', 'schema', 'manual', 'retry')`)).addColumn("snapshot_seq", "bigint").addColumn("schema_version", "integer").addColumn("retry_of", "uuid", (col) => col.references("deployment_runs.id").onDelete("set null")).addColumn("provider_ref", "text").addColumn("log_url", "text").addColumn("site_url", "text").addColumn("error", "text").addColumn("timeline", "jsonb", (col) => col.notNull().defaultTo(sql`'[]'::jsonb`)).addColumn("job_id", "uuid").addColumn("triggered_at", "timestamptz").addColumn("finished_at", "timestamptz"))).execute();
334
+ await db.schema.createIndex("deployment_runs_queued_uq").on("deployment_runs").column("connection_id").unique().where(sql.ref("status"), "=", "queued").execute();
335
+ await db.schema.createIndex("deployment_runs_connection_idx").on("deployment_runs").columns(["connection_id", "created_at desc"]).execute();
336
+ };
337
+ const createPreviewTables = async (db) => {
338
+ await uuidPrimaryKey$1(db.schema.createTable("preview_tokens")).addColumn("token_hash", "text", (col) => col.notNull().unique()).addColumn("token_prefix", "text", (col) => col.notNull()).addColumn("model_id", "uuid", (col) => col.notNull()).addColumn("entry_id", "uuid", (col) => col.references("entries.id").onDelete("cascade")).addColumn("locale", "text").addColumn("connection_id", "uuid", (col) => col.references("deployment_connections.id").onDelete("set null")).addColumn("delivery_role_id", "uuid", (col) => col.references("admin_roles.id").onDelete("set null")).addColumn("created_by", "uuid", (col) => col.notNull().references("admin_users.id").onDelete("cascade")).addColumn("expires_at", "timestamptz", (col) => col.notNull()).addColumn("revoked_at", "timestamptz").addColumn("last_used_at", "timestamptz").addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).execute();
339
+ await db.schema.createIndex("preview_tokens_entry_idx").on("preview_tokens").columns(["entry_id", "created_at desc"]).execute();
340
+ };
341
+ const up$28 = async (db) => {
342
+ await createSchedulingTables(db);
343
+ await createWebhookTables(db);
344
+ await createDeploymentTables(db);
345
+ await createPreviewTables(db);
346
+ };
347
+ const down$28 = async (db) => {
348
+ await db.schema.dropTable("preview_tokens").execute();
349
+ await db.schema.dropTable("deployment_runs").execute();
350
+ await db.schema.dropTable("deployment_connections").execute();
351
+ await db.schema.dropTable("webhook_deliveries").execute();
352
+ await db.schema.dropTable("webhooks").execute();
353
+ await db.schema.dropTable("release_items").execute();
354
+ await db.schema.dropTable("releases").execute();
355
+ await db.schema.dropTable("scheduled_publications").execute();
356
+ };
357
+ //#endregion
358
+ //#region ../../apps/api/src/db/migrations/20261001180000_create_app_users.ts
359
+ var _20261001180000_create_app_users_exports = /* @__PURE__ */ __exportAll({
360
+ down: () => down$27,
361
+ up: () => up$27
362
+ });
363
+ /**
364
+ * App users (package I, ADR 0005): the end users of sites and apps built on Shapio, with their own roles
365
+ * (`public` for anonymous callers, `authenticated` for every signed-in app user, plus custom roles), rotating
366
+ * refresh tokens with reuse detection, OAuth identities, email confirmations, password resets and the
367
+ * short-lived codes that hand an OAuth sign-in back to the app. Secrets are stored as SHA-256 hashes (tokens,
368
+ * codes) or argon2id hashes (passwords), never in plain text.
369
+ */
370
+ /**
371
+ * Stable IDs of the built-in app roles. The evaluator refers to them directly (permissions/appRoles.ts holds
372
+ * the same values); a migration never imports application code, so they are repeated here.
373
+ */
374
+ const PUBLIC_ROLE_ID$1 = "00000000-0000-4000-a000-000000000001";
375
+ const AUTHENTICATED_ROLE_ID$1 = "00000000-0000-4000-a000-000000000002";
376
+ const withTimestamps = (table) => table.addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`));
377
+ const uuidPrimaryKey = (table) => table.addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`));
378
+ const createUsers = async (db) => {
379
+ await withTimestamps(uuidPrimaryKey(db.schema.createTable("app_users")).addColumn("email", "text", (col) => col.notNull()).addColumn("name", "text", (col) => col.notNull().defaultTo("")).addColumn("password_hash", "text").addColumn("confirmed_at", "timestamptz").addColumn("blocked_at", "timestamptz").addColumn("last_login_at", "timestamptz").addColumn("password_changed_at", "timestamptz").addColumn("deleted_at", "timestamptz")).execute();
380
+ await sql`create unique index app_users_email_uq on app_users (lower(email)) where deleted_at is null`.execute(db);
381
+ await sql`create index app_users_created_idx on app_users (created_at desc, id desc) where deleted_at is null`.execute(db);
382
+ };
383
+ const createRoles = async (db) => {
384
+ await withTimestamps(uuidPrimaryKey(db.schema.createTable("app_roles")).addColumn("key", "text", (col) => col.notNull().unique().check(sql`key ~ '^[a-z][a-z0-9-]{0,62}$'`)).addColumn("name", "text", (col) => col.notNull()).addColumn("description", "text", (col) => col.notNull().defaultTo("")).addColumn("is_system", "boolean", (col) => col.notNull().defaultTo(false)).addColumn("version", "integer", (col) => col.notNull().defaultTo(1))).execute();
385
+ await withTimestamps(uuidPrimaryKey(db.schema.createTable("app_role_permissions")).addColumn("role_id", "uuid", (col) => col.notNull().references("app_roles.id").onDelete("cascade")).addColumn("action", "text", (col) => col.notNull().check(sql`action in ('read', 'create', 'update', 'delete', 'publish')`)).addColumn("model_id", "text").addColumn("condition", "text", (col) => col.check(sql`condition in ('ownedByPrincipal')`)).addColumn("field_ids", sql`text[]`).addUniqueConstraint("app_role_permissions_grant_uq", [
386
+ "role_id",
387
+ "action",
388
+ "model_id"
389
+ ], (uc) => uc.nullsNotDistinct())).execute();
390
+ await db.schema.createTable("app_user_roles").addColumn("app_user_id", "uuid", (col) => col.notNull().references("app_users.id").onDelete("cascade")).addColumn("role_id", "uuid", (col) => col.notNull().references("app_roles.id").onDelete("restrict")).addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addPrimaryKeyConstraint("app_user_roles_pk", ["app_user_id", "role_id"]).execute();
391
+ await db.schema.createIndex("app_user_roles_role_idx").on("app_user_roles").column("role_id").execute();
392
+ await sql`
393
+ insert into app_roles (id, key, name, description, is_system) values
394
+ (${PUBLIC_ROLE_ID$1}, 'public', 'Public',
395
+ 'Applies to every caller without a token. Grants nothing until you add permissions.', true),
396
+ (${AUTHENTICATED_ROLE_ID$1}, 'authenticated', 'Authenticated',
397
+ 'Held by every signed-in app user, in addition to their custom roles. Grants nothing until you add permissions.', true)
398
+ `.execute(db);
399
+ };
400
+ const createCredentials = async (db) => {
401
+ await db.schema.createTable("app_refresh_tokens").addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`)).addColumn("app_user_id", "uuid", (col) => col.notNull().references("app_users.id").onDelete("cascade")).addColumn("family_id", "uuid", (col) => col.notNull()).addColumn("token_hash", "text", (col) => col.notNull().unique()).addColumn("expires_at", "timestamptz", (col) => col.notNull()).addColumn("used_at", "timestamptz").addColumn("reuse_grace_used_at", "timestamptz").addColumn("revoked_at", "timestamptz").addColumn("revoked_reason", "text").addColumn("ip", "text").addColumn("user_agent", "text").addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).execute();
402
+ await db.schema.createIndex("app_refresh_tokens_family_idx").on("app_refresh_tokens").column("family_id").execute();
403
+ await db.schema.createIndex("app_refresh_tokens_user_idx").on("app_refresh_tokens").column("app_user_id").where(sql.ref("revoked_at"), "is", null).execute();
404
+ await withTimestamps(uuidPrimaryKey(db.schema.createTable("app_oauth_accounts")).addColumn("app_user_id", "uuid", (col) => col.notNull().references("app_users.id").onDelete("cascade")).addColumn("provider", "text", (col) => col.notNull().check(sql`provider in ('google', 'github')`)).addColumn("provider_user_id", "text", (col) => col.notNull()).addColumn("email", "text").addUniqueConstraint("app_oauth_accounts_identity_uq", ["provider", "provider_user_id"])).execute();
405
+ await db.schema.createIndex("app_oauth_accounts_user_idx").on("app_oauth_accounts").column("app_user_id").execute();
406
+ for (const table of ["email_confirmations", "app_password_resets"]) {
407
+ await withTimestamps(uuidPrimaryKey(db.schema.createTable(table)).addColumn("app_user_id", "uuid", (col) => col.notNull().references("app_users.id").onDelete("cascade")).addColumn("email", "text", (col) => col.notNull()).addColumn("token_hash", "text", (col) => col.unique()).addColumn("expires_at", "timestamptz", (col) => col.notNull()).addColumn("used_at", "timestamptz")).execute();
408
+ await db.schema.createIndex(`${table}_user_idx`).on(table).column("app_user_id").execute();
409
+ }
410
+ await db.schema.createTable("app_login_codes").addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`)).addColumn("app_user_id", "uuid", (col) => col.notNull().references("app_users.id").onDelete("cascade")).addColumn("code_hash", "text", (col) => col.notNull().unique()).addColumn("expires_at", "timestamptz", (col) => col.notNull()).addColumn("used_at", "timestamptz").addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).execute();
411
+ };
412
+ const up$27 = async (db) => {
413
+ await createUsers(db);
414
+ await createRoles(db);
415
+ await createCredentials(db);
416
+ await sql`update entries set owner_app_user_id = null where owner_app_user_id is not null`.execute(db);
417
+ await sql`
418
+ alter table entries add constraint entries_owner_app_user_fk
419
+ foreign key (owner_app_user_id) references app_users (id) on delete set null
420
+ `.execute(db);
421
+ };
422
+ const down$27 = async (db) => {
423
+ await sql`alter table entries drop constraint if exists entries_owner_app_user_fk`.execute(db);
424
+ await db.schema.dropTable("app_login_codes").execute();
425
+ await db.schema.dropTable("app_password_resets").execute();
426
+ await db.schema.dropTable("email_confirmations").execute();
427
+ await db.schema.dropTable("app_oauth_accounts").execute();
428
+ await db.schema.dropTable("app_refresh_tokens").execute();
429
+ await db.schema.dropTable("app_user_roles").execute();
430
+ await db.schema.dropTable("app_role_permissions").execute();
431
+ await db.schema.dropTable("app_roles").execute();
432
+ await db.schema.dropTable("app_users").execute();
433
+ };
434
+ //#endregion
435
+ //#region ../../apps/api/src/db/migrations/20261002120000_create_extension_hook_runs.ts
436
+ var _20261002120000_create_extension_hook_runs_exports = /* @__PURE__ */ __exportAll({
437
+ down: () => down$26,
438
+ up: () => up$26
439
+ });
440
+ /**
441
+ * Extension points (package K, ADR 0009): the ledger of post-commit lifecycle hooks (`afterCreate`,
442
+ * `afterPublish`, ...) that have run. One row per (outbox event, hook name), written in the same transaction
443
+ * as the hook's own database work, so a hook re-run after a worker crash finds its row and is skipped:
444
+ * exactly once for everything the hook does through its transaction.
445
+ */
446
+ const up$26 = async (db) => {
447
+ await db.schema.createTable("extension_hook_runs").addColumn("event_id", "uuid", (col) => col.notNull()).addColumn("hook", "text", (col) => col.notNull()).addColumn("job_id", "uuid").addColumn("completed_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addPrimaryKeyConstraint("extension_hook_runs_pkey", ["event_id", "hook"]).execute();
448
+ };
449
+ const down$26 = async (db) => {
450
+ await db.schema.dropTable("extension_hook_runs").execute();
451
+ };
452
+ //#endregion
453
+ //#region ../../apps/api/src/db/migrations/20261002130000_app_auth_token_version_and_login_code_pkce.ts
454
+ var _20261002130000_app_auth_token_version_and_login_code_pkce_exports = /* @__PURE__ */ __exportAll({
455
+ down: () => down$25,
456
+ up: () => up$25
457
+ });
458
+ /**
459
+ * App users (ADR 0005):
460
+ * - `app_users.token_version`: carried in every access token and bumped on logout, block, password change,
461
+ * password reset and deletion, so the account's outstanding access tokens stop working on their next
462
+ * request instead of living out their TTL.
463
+ * - `app_login_codes.code_challenge`: the PKCE-style S256 challenge the app sent when it started the OAuth
464
+ * sign-in; the one-time login code is only exchanged with the matching verifier. Codes live a minute, so
465
+ * any pending ones (issued without a challenge) are dropped rather than given a placeholder.
466
+ */
467
+ const up$25 = async (db) => {
468
+ await db.schema.alterTable("app_users").addColumn("token_version", "integer", (col) => col.notNull().defaultTo(0)).execute();
469
+ await sql`delete from app_login_codes`.execute(db);
470
+ await db.schema.alterTable("app_login_codes").addColumn("code_challenge", "text", (col) => col.notNull()).execute();
471
+ };
472
+ const down$25 = async (db) => {
473
+ await db.schema.alterTable("app_login_codes").dropColumn("code_challenge").execute();
474
+ await db.schema.alterTable("app_users").dropColumn("token_version").execute();
475
+ };
476
+ //#endregion
477
+ //#region ../../apps/api/src/db/migrations/20261002180000_add_publication_log_diff_indexes.ts
478
+ var _20261002180000_add_publication_log_diff_indexes_exports = /* @__PURE__ */ __exportAll({
479
+ down: () => down$24,
480
+ up: () => up$24
481
+ });
482
+ /**
483
+ * The snapshot diff (repositories/snapshotDiff.ts) finds the (entry, locale) pairs whose live period opened
484
+ * or closed between two publication sequence numbers, then reads each pair's live revision at both ends.
485
+ * - `to_seq` range: periods that closed in the range (the open ones have no `to_seq` and are left out).
486
+ * - `(entry_id, locale, from_seq)`: "the row live at seq N" is the latest row starting at or before N.
487
+ * Periods that opened in the range use the existing `(model_id, from_seq)` index.
488
+ */
489
+ const up$24 = async (db) => {
490
+ await sql`create index publication_log_to_seq_idx on publication_log (to_seq) where to_seq is not null`.execute(db);
491
+ await db.schema.createIndex("publication_log_entry_locale_from_idx").on("publication_log").columns([
492
+ "entry_id",
493
+ "locale",
494
+ "from_seq"
495
+ ]).execute();
496
+ };
497
+ const down$24 = async (db) => {
498
+ await db.schema.dropIndex("publication_log_entry_locale_from_idx").execute();
499
+ await db.schema.dropIndex("publication_log_to_seq_idx").execute();
500
+ };
501
+ //#endregion
502
+ //#region ../../apps/api/src/db/migrations/20261002180100_create_usage_tables.ts
503
+ var _20261002180100_create_usage_tables_exports = /* @__PURE__ */ __exportAll({
504
+ down: () => down$23,
505
+ up: () => up$23
506
+ });
507
+ /**
508
+ * Field usage from delivery traffic (plan developer-face §5): daily counters written by the in-memory
509
+ * aggregator (usage/aggregator.ts) in one multi-row upsert per flush, never per request. Counts only, no
510
+ * payloads. `principal_key` is `token:<id>`, `app_users` or `anonymous`. Pruned by the retention job
511
+ * after USAGE_RETENTION_DAYS.
512
+ */
513
+ const up$23 = async (db) => {
514
+ await db.schema.createTable("field_reads").addColumn("day", "date", (col) => col.notNull()).addColumn("model_id", "uuid", (col) => col.notNull()).addColumn("field_path", "text", (col) => col.notNull()).addColumn("principal_key", "text", (col) => col.notNull()).addColumn("selection", "text", (col) => col.notNull().check(sql`selection in ('explicit', 'implicit')`)).addColumn("reads", "bigint", (col) => col.notNull().defaultTo(0)).addColumn("last_read_at", "timestamptz", (col) => col.notNull()).addPrimaryKeyConstraint("field_reads_pkey", [
515
+ "day",
516
+ "model_id",
517
+ "field_path",
518
+ "principal_key",
519
+ "selection"
520
+ ]).execute();
521
+ await db.schema.createIndex("field_reads_model_day_idx").on("field_reads").columns(["model_id", "day"]).execute();
522
+ await db.schema.createTable("token_reads").addColumn("day", "date", (col) => col.notNull()).addColumn("principal_key", "text", (col) => col.notNull()).addColumn("requests", "bigint", (col) => col.notNull().defaultTo(0)).addColumn("last_snapshot", "bigint").addColumn("last_read_at", "timestamptz", (col) => col.notNull()).addPrimaryKeyConstraint("token_reads_pkey", ["day", "principal_key"]).execute();
523
+ };
524
+ const down$23 = async (db) => {
525
+ await db.schema.dropTable("token_reads").execute();
526
+ await db.schema.dropTable("field_reads").execute();
527
+ };
528
+ //#endregion
529
+ //#region ../../apps/api/src/db/migrations/20261002180200_create_change_sets.ts
530
+ var _20261002180200_create_change_sets_exports = /* @__PURE__ */ __exportAll({
531
+ down: () => down$22,
532
+ up: () => up$22
533
+ });
534
+ /**
535
+ * Change sets (developer-face plan §5, ADR 0010): schema drafts and entry publications that ship as ONE
536
+ * publication snapshot. Replaces releases (a clean rename: rows, permission strings, webhook event patterns,
537
+ * deployment trigger policy and pending jobs move over; nothing keeps the old names).
538
+ *
539
+ * - `change_sets` / `change_set_items`: the set and its items (entry publications or schema drafts).
540
+ * - `schema_drafts`: mutable proposed definitions; real (immutable) schema revisions are written at ship.
541
+ * - `publication_snapshots`: one ledger row per publication sequence number (who, why, schema version),
542
+ * backfilled for existing numbers as `legacy`.
543
+ * - `schema_change_jobs.change_set_id` and `deployment_runs.change_set_id` link execution records to a set.
544
+ */
545
+ const RELEASE_EVENT_RENAMES = [
546
+ ["release.*", "change_set.*"],
547
+ ["release.scheduled", "change_set.scheduled"],
548
+ ["release.published", "change_set.shipped"],
549
+ ["release.failed", "change_set.failed"]
550
+ ];
551
+ const createTables = async (db) => {
552
+ await sql`
553
+ create table change_sets (
554
+ id uuid primary key default gen_random_uuid(),
555
+ title text not null check (length(title) between 1 and 200),
556
+ description text not null default '',
557
+ status text not null default 'open'
558
+ check (status in ('open', 'scheduled', 'shipping', 'shipped', 'failed', 'discarded')),
559
+ ship_phase text check (ship_phase in ('preparing', 'activating')),
560
+ source text not null default 'manual' check (source in ('manual', 'release', 'restore', 'builder')),
561
+ restore_of_seq bigint,
562
+ scheduled_for timestamptz,
563
+ schedule_job_id uuid,
564
+ scheduled_by uuid references admin_users(id) on delete set null,
565
+ scheduled_by_token uuid references api_tokens(id) on delete set null,
566
+ ship_job_id uuid,
567
+ ship_mode text check (ship_mode in ('interactive', 'scheduled')),
568
+ ship_requested_by uuid references admin_users(id) on delete set null,
569
+ ship_requested_by_token uuid references api_tokens(id) on delete set null,
570
+ acknowledge_breaking boolean not null default false,
571
+ acknowledge_destructive boolean not null default false,
572
+ deployment_connection_id uuid references deployment_connections(id) on delete set null,
573
+ deployment_run_id uuid references deployment_runs(id) on delete set null,
574
+ shipped_at timestamptz,
575
+ shipped_seq bigint,
576
+ schema_version_after integer,
577
+ error jsonb,
578
+ restore_report jsonb not null default '[]'::jsonb,
579
+ created_by uuid references admin_users(id) on delete set null,
580
+ created_by_token uuid references api_tokens(id) on delete set null,
581
+ version integer not null default 1,
582
+ created_at timestamptz not null default now(),
583
+ updated_at timestamptz not null default now()
584
+ )
585
+ `.execute(db);
586
+ await sql`create index change_sets_status_idx on change_sets (status, created_at desc)`.execute(db);
587
+ await sql`
588
+ create table schema_drafts (
589
+ id uuid primary key default gen_random_uuid(),
590
+ change_set_id uuid not null references change_sets(id) on delete cascade,
591
+ definition_id uuid not null,
592
+ kind text not null check (kind in ('collection', 'singleton', 'component')),
593
+ api_key text not null,
594
+ base_version integer,
595
+ definition jsonb,
596
+ version integer not null default 1,
597
+ updated_by_type text not null,
598
+ updated_by_id text,
599
+ created_at timestamptz not null default now(),
600
+ updated_at timestamptz not null default now(),
601
+ constraint schema_drafts_set_definition_uq unique (change_set_id, definition_id)
602
+ )
603
+ `.execute(db);
604
+ await sql`create index schema_drafts_definition_idx on schema_drafts (definition_id)`.execute(db);
605
+ await sql`
606
+ create table change_set_items (
607
+ id uuid primary key default gen_random_uuid(),
608
+ change_set_id uuid not null references change_sets(id) on delete cascade,
609
+ kind text not null check (kind in ('entry', 'schema')),
610
+ position integer not null,
611
+ entry_id uuid references entries(id) on delete cascade,
612
+ model_id uuid,
613
+ locale text,
614
+ action text check (action in ('publish', 'unpublish')),
615
+ source_revision_id uuid references content_revisions(id),
616
+ schema_draft_id uuid references schema_drafts(id) on delete cascade,
617
+ status text not null default 'pending' check (status in ('pending', 'done', 'failed')),
618
+ error text,
619
+ ship_state jsonb,
620
+ created_at timestamptz not null default now(),
621
+ constraint change_set_items_shape_check check (
622
+ (kind = 'entry' and entry_id is not null and model_id is not null and locale is not null
623
+ and action is not null and schema_draft_id is null)
624
+ or (kind = 'schema' and schema_draft_id is not null and entry_id is null and source_revision_id is null)
625
+ )
626
+ )
627
+ `.execute(db);
628
+ await sql`
629
+ create unique index change_set_items_entry_uq on change_set_items (change_set_id, entry_id, locale)
630
+ where kind = 'entry'
631
+ `.execute(db);
632
+ await sql`create unique index change_set_items_draft_uq on change_set_items (schema_draft_id)`.execute(db);
633
+ await sql`create index change_set_items_set_idx on change_set_items (change_set_id, position)`.execute(db);
634
+ await sql`create index change_set_items_entry_idx on change_set_items (entry_id) where entry_id is not null`.execute(db);
635
+ await sql`
636
+ create table publication_snapshots (
637
+ seq bigint primary key,
638
+ schema_version integer,
639
+ source text not null,
640
+ change_set_id uuid references change_sets(id) on delete set null,
641
+ actor_type text,
642
+ actor_id text,
643
+ created_at timestamptz not null default now()
644
+ )
645
+ `.execute(db);
646
+ await sql`create index publication_snapshots_change_set_idx on publication_snapshots (change_set_id)
647
+ where change_set_id is not null`.execute(db);
648
+ };
649
+ const linkExecutionRecords = async (db) => {
650
+ await sql`alter table schema_change_jobs
651
+ add column change_set_id uuid references change_sets(id) on delete set null`.execute(db);
652
+ await sql`create index schema_change_jobs_change_set_idx on schema_change_jobs (change_set_id)
653
+ where change_set_id is not null`.execute(db);
654
+ await sql`alter table deployment_runs
655
+ add column change_set_id uuid references change_sets(id) on delete set null`.execute(db);
656
+ await sql`alter table deployment_runs drop constraint deployment_runs_trigger_check`.execute(db);
657
+ await sql`update deployment_runs set trigger = 'change_set' where trigger = 'release'`.execute(db);
658
+ await sql`alter table deployment_runs add constraint deployment_runs_trigger_check
659
+ check (trigger in ('publish', 'change_set', 'schema', 'manual', 'retry'))`.execute(db);
660
+ await sql`update deployment_connections
661
+ set trigger_policy = array_replace(trigger_policy, 'release', 'change_set')`.execute(db);
662
+ };
663
+ /** Revisions written by activation-time value conversions (published heads roll to them in the log). */
664
+ const allowConversionRevisions = async (db) => {
665
+ await sql`alter table content_revisions drop constraint content_revisions_reason_check`.execute(db);
666
+ await sql`alter table content_revisions add constraint content_revisions_reason_check
667
+ check (reason in ('create', 'save', 'publish', 'restore', 'duplicate', 'localize', 'conversion'))`.execute(db);
668
+ };
669
+ const backfillSnapshots = async (db) => {
670
+ await sql`
671
+ insert into publication_snapshots (seq, schema_version, source, created_at)
672
+ select s, null, 'legacy',
673
+ coalesce((select min(published_at) from publication_log where from_seq = s), now())
674
+ from generate_series(1::bigint, (select last_seq from publication_state)) as s
675
+ `.execute(db);
676
+ };
677
+ const migrateReleases = async (db) => {
678
+ await sql`
679
+ insert into change_sets (id, title, description, status, source, scheduled_for, schedule_job_id,
680
+ scheduled_by, scheduled_by_token, shipped_at, shipped_seq, error, created_by, version, created_at,
681
+ updated_at)
682
+ select id, name, description,
683
+ case status when 'draft' then 'open' when 'published' then 'shipped' when 'cancelled' then 'discarded'
684
+ else status end,
685
+ 'release', scheduled_at, schedule_job_id, scheduled_by, scheduled_by_token, executed_at, snapshot_seq,
686
+ case when error is null then null
687
+ else jsonb_build_object('code', 'PUBLICATION_FAILED', 'message', error, 'itemId',
688
+ (select ri.id from release_items ri where ri.release_id = releases.id and ri.status = 'failed' limit 1))
689
+ end,
690
+ created_by, version, created_at, updated_at
691
+ from releases
692
+ `.execute(db);
693
+ await sql`
694
+ insert into change_set_items (id, change_set_id, kind, position, entry_id, model_id, locale, action, status,
695
+ error, created_at)
696
+ select id, release_id, 'entry',
697
+ row_number() over (partition by release_id order by created_at, id) - 1,
698
+ entry_id, model_id, locale, action, status, error, created_at
699
+ from release_items
700
+ `.execute(db);
701
+ await sql`
702
+ update jobs set type = 'changeSet.ship',
703
+ payload = jsonb_build_object('changeSetId', payload->>'releaseId', 'mode', 'scheduled')
704
+ where type = 'publishing.release' and status in ('pending', 'running')
705
+ `.execute(db);
706
+ await sql`update admin_role_permissions set action = 'changes.manage' where action = 'releases.manage'`.execute(db);
707
+ for (const [from, to] of RELEASE_EVENT_RENAMES) await sql`update webhooks set events = array_replace(events, ${from}, ${to})`.execute(db);
708
+ await sql`drop table release_items`.execute(db);
709
+ await sql`drop table releases`.execute(db);
710
+ };
711
+ const up$22 = async (db) => {
712
+ await createTables(db);
713
+ await linkExecutionRecords(db);
714
+ await allowConversionRevisions(db);
715
+ await backfillSnapshots(db);
716
+ await migrateReleases(db);
717
+ };
718
+ /** Recreates releases from the change sets that hold only entry items; schema drafts and the ledger go. */
719
+ const restoreReleases = async (db) => {
720
+ await sql`
721
+ create table releases (
722
+ id uuid primary key default gen_random_uuid(),
723
+ name text not null check (length(name) between 1 and 200),
724
+ description text not null default '',
725
+ status text not null default 'draft'
726
+ check (status in ('draft', 'scheduled', 'published', 'failed', 'cancelled')),
727
+ scheduled_at timestamptz,
728
+ schedule_job_id uuid,
729
+ scheduled_by uuid references admin_users(id) on delete set null,
730
+ scheduled_by_token uuid references api_tokens(id) on delete set null,
731
+ executed_at timestamptz,
732
+ snapshot_seq bigint,
733
+ error text,
734
+ version integer not null default 1,
735
+ created_by uuid references admin_users(id) on delete set null,
736
+ created_at timestamptz not null default now(),
737
+ updated_at timestamptz not null default now()
738
+ )
739
+ `.execute(db);
740
+ await sql`create index releases_status_idx on releases (status, created_at)`.execute(db);
741
+ await sql`
742
+ create table release_items (
743
+ id uuid primary key default gen_random_uuid(),
744
+ release_id uuid not null references releases(id) on delete cascade,
745
+ entry_id uuid not null references entries(id) on delete cascade,
746
+ model_id uuid not null,
747
+ locale text not null,
748
+ action text not null check (action in ('publish', 'unpublish')),
749
+ status text not null default 'pending' check (status in ('pending', 'done', 'failed')),
750
+ error text,
751
+ created_at timestamptz not null default now(),
752
+ constraint release_items_entry_locale_uq unique (release_id, entry_id, locale)
753
+ )
754
+ `.execute(db);
755
+ await sql`
756
+ insert into releases (id, name, description, status, scheduled_at, schedule_job_id, scheduled_by,
757
+ scheduled_by_token, executed_at, snapshot_seq, error, version, created_by, created_at, updated_at)
758
+ select id, title, description,
759
+ case status when 'open' then 'draft' when 'shipped' then 'published' when 'discarded' then 'cancelled'
760
+ when 'shipping' then 'draft' else status end,
761
+ scheduled_for, schedule_job_id, scheduled_by, scheduled_by_token, shipped_at, shipped_seq,
762
+ error->>'message', version, created_by, created_at, updated_at
763
+ from change_sets cs
764
+ where not exists (select 1 from change_set_items i where i.change_set_id = cs.id and i.kind = 'schema')
765
+ `.execute(db);
766
+ await sql`
767
+ insert into release_items (id, release_id, entry_id, model_id, locale, action, status, error, created_at)
768
+ select i.id, i.change_set_id, i.entry_id, i.model_id, i.locale, i.action, i.status, i.error, i.created_at
769
+ from change_set_items i join releases r on r.id = i.change_set_id
770
+ where i.kind = 'entry'
771
+ `.execute(db);
772
+ await sql`
773
+ update jobs set type = 'publishing.release', payload = jsonb_build_object('releaseId', payload->>'changeSetId')
774
+ where type = 'changeSet.ship' and status in ('pending', 'running')
775
+ and (payload->>'changeSetId')::uuid in (select id from releases)
776
+ `.execute(db);
777
+ await sql`delete from jobs where type like 'changeSet.%' and status in ('pending', 'running')`.execute(db);
778
+ await sql`update admin_role_permissions set action = 'releases.manage' where action = 'changes.manage'`.execute(db);
779
+ for (const [from, to] of RELEASE_EVENT_RENAMES) await sql`update webhooks set events = array_replace(events, ${to}, ${from})`.execute(db);
780
+ await sql`update webhooks set events = array_remove(array_remove(events, 'change_set.shipping'),
781
+ 'change_set.discarded')`.execute(db);
782
+ };
783
+ const down$22 = async (db) => {
784
+ await restoreReleases(db);
785
+ await sql`alter table content_revisions drop constraint content_revisions_reason_check`.execute(db);
786
+ await sql`alter table content_revisions add constraint content_revisions_reason_check
787
+ check (reason in ('create', 'save', 'publish', 'restore', 'duplicate', 'localize')) not valid`.execute(db);
788
+ await sql`update deployment_connections
789
+ set trigger_policy = array_replace(trigger_policy, 'change_set', 'release')`.execute(db);
790
+ await sql`alter table deployment_runs drop constraint deployment_runs_trigger_check`.execute(db);
791
+ await sql`update deployment_runs set trigger = 'release' where trigger = 'change_set'`.execute(db);
792
+ await sql`alter table deployment_runs add constraint deployment_runs_trigger_check
793
+ check (trigger in ('publish', 'release', 'schema', 'manual', 'retry'))`.execute(db);
794
+ await sql`alter table deployment_runs drop column change_set_id`.execute(db);
795
+ await sql`alter table schema_change_jobs drop column change_set_id`.execute(db);
796
+ await sql`drop table publication_snapshots`.execute(db);
797
+ await sql`drop table change_set_items`.execute(db);
798
+ await sql`drop table schema_drafts`.execute(db);
799
+ await sql`drop table change_sets`.execute(db);
800
+ };
801
+ //#endregion
802
+ //#region ../../apps/api/src/db/migrations/20261002190000_create_editor_presence_and_content_health.ts
803
+ var _20261002190000_create_editor_presence_and_content_health_exports = /* @__PURE__ */ __exportAll({
804
+ down: () => down$21,
805
+ up: () => up$21
806
+ });
807
+ /**
808
+ * Editor experience (plan editor-experience §9):
809
+ *
810
+ * - `editor_presence`: who has an entry open, one row per browser tab, refreshed by a heartbeat and read
811
+ * with a TTL. UNLOGGED: it is advisory and rebuilt within one heartbeat, so it skips the WAL and is
812
+ * simply emptied after a crash. Correct across any number of server processes without NOTIFY.
813
+ * - `content_health_findings`: open and recently resolved findings of the health rules per entry and
814
+ * locale (`*` for the entry as a whole). An evaluation upserts what it found and resolves the rest.
815
+ */
816
+ const up$21 = async (db) => {
817
+ await sql`
818
+ create unlogged table editor_presence (
819
+ entry_id uuid not null,
820
+ model_id uuid not null,
821
+ admin_user_id uuid not null,
822
+ tab_id text not null check (length(tab_id) between 1 and 64),
823
+ locale text,
824
+ started_at timestamptz not null default now(),
825
+ last_seen_at timestamptz not null default now(),
826
+ primary key (entry_id, admin_user_id, tab_id)
827
+ )
828
+ `.execute(db);
829
+ await sql`create index editor_presence_model_idx on editor_presence (model_id, last_seen_at)`.execute(db);
830
+ await sql`create index editor_presence_seen_idx on editor_presence (last_seen_at)`.execute(db);
831
+ await sql`
832
+ create table content_health_findings (
833
+ id uuid primary key default gen_random_uuid(),
834
+ entry_id uuid not null references entries(id) on delete cascade,
835
+ model_id uuid not null,
836
+ locale text not null,
837
+ rule text not null,
838
+ subject text not null default '',
839
+ severity text not null check (severity in ('error', 'warning')),
840
+ path text,
841
+ details jsonb not null default '{}'::jsonb,
842
+ first_seen_at timestamptz not null default now(),
843
+ last_seen_at timestamptz not null default now(),
844
+ resolved_at timestamptz,
845
+ unique (entry_id, locale, rule, subject)
846
+ )
847
+ `.execute(db);
848
+ await sql`create index content_health_findings_open_idx on content_health_findings (rule, last_seen_at desc, id)
849
+ where resolved_at is null`.execute(db);
850
+ await sql`create index content_health_findings_resolved_idx on content_health_findings (resolved_at)
851
+ where resolved_at is not null`.execute(db);
852
+ };
853
+ const down$21 = async (db) => {
854
+ await sql`drop table content_health_findings`.execute(db);
855
+ await sql`drop table editor_presence`.execute(db);
856
+ };
857
+ //#endregion
858
+ //#region ../../apps/api/src/db/migrations/20261003120000_add_changes_ship_permission.ts
859
+ var _20261003120000_add_changes_ship_permission_exports = /* @__PURE__ */ __exportAll({
860
+ down: () => down$20,
861
+ up: () => up$20
862
+ });
863
+ /**
864
+ * `changes.ship` (agentic plan §I): shipping a change set (and scheduling a ship) becomes its own global
865
+ * action, split from `changes.manage`, so a role can prepare change sets without being able to make them
866
+ * live. Every role that holds `changes.manage` today keeps what it could do: it gets `changes.ship` too.
867
+ * The permissions version moves only when grants changed, so running instances reload exactly then.
868
+ */
869
+ const up$20 = async (db) => {
870
+ await sql`
871
+ with granted as (
872
+ insert into admin_role_permissions (role_id, action, model_id, condition, field_ids)
873
+ select role_id, 'changes.ship', null, null, null
874
+ from admin_role_permissions
875
+ where action = 'changes.manage' and model_id is null
876
+ on conflict on constraint admin_role_permissions_grant_uq do nothing
877
+ returning 1
878
+ )
879
+ update system_versions set permissions_version = permissions_version + 1, updated_at = now()
880
+ where exists (select 1 from granted)
881
+ `.execute(db);
882
+ };
883
+ const down$20 = async (db) => {
884
+ await sql`
885
+ with revoked as (
886
+ delete from admin_role_permissions where action = 'changes.ship' returning 1
887
+ )
888
+ update system_versions set permissions_version = permissions_version + 1, updated_at = now()
889
+ where exists (select 1 from revoked)
890
+ `.execute(db);
891
+ };
892
+ //#endregion
893
+ //#region ../../apps/api/src/db/migrations/20261003130000_create_sites.ts
894
+ var _20261003130000_create_sites_exports = /* @__PURE__ */ __exportAll({
895
+ down: () => down$19,
896
+ up: () => up$19
897
+ });
898
+ /**
899
+ * Sites (plan agentic-ecosystem §H, ADR 0011): one instance hosts many sites with one schema and one login.
900
+ * A site owns its content, media, tokens, change sets, snapshots, app users and usage counters; models,
901
+ * locales, admin users and roles stay instance-wide. Role assignments carry a site (null = every site).
902
+ *
903
+ * Every existing row moves to the primary site (fixed ID below, key `default`). Columns are added with a
904
+ * constant default, which PostgreSQL records in the catalog without rewriting the table, and the default is
905
+ * dropped straight after: from here on every insert must name its site. Copies of the entry's site (heads,
906
+ * the publication log, unique values, findings, schedules, preview tokens) carry a composite foreign key to
907
+ * `entries (id, site_id)`, so a copy can never disagree with its entry.
908
+ *
909
+ * `down` refuses while more than one site exists (merging sites would merge their snapshot ledgers and
910
+ * uniqueness, and widen role assignments). With one site it is lossless.
911
+ */
912
+ /** The primary site every existing row belongs to. Repeated in constants/sites.ts (migrations never import app code). */
913
+ const PRIMARY_SITE_ID = "00000000-0000-4000-b000-000000000001";
914
+ const PUBLIC_ROLE_ID = "00000000-0000-4000-a000-000000000001";
915
+ const AUTHENTICATED_ROLE_ID = "00000000-0000-4000-a000-000000000002";
916
+ /** Tables whose rows always belong to one site, and what deleting a site does to them. */
917
+ const REQUIRED_SITE_TABLES = [
918
+ ["entries", "restrict"],
919
+ ["entry_heads", "restrict"],
920
+ ["unique_values", "restrict"],
921
+ ["publication_log", "restrict"],
922
+ ["scheduled_publications", "restrict"],
923
+ ["content_health_findings", "restrict"],
924
+ ["media_folders", "restrict"],
925
+ ["media_assets", "restrict"],
926
+ ["change_sets", "restrict"],
927
+ ["app_users", "restrict"],
928
+ ["app_user_roles", "restrict"],
929
+ ["app_oauth_accounts", "restrict"],
930
+ ["publication_state", "cascade"],
931
+ ["publication_snapshots", "cascade"],
932
+ ["media_upload_grants", "cascade"],
933
+ ["preview_tokens", "cascade"],
934
+ ["deployment_connections", "cascade"],
935
+ ["field_reads", "cascade"],
936
+ ["token_reads", "cascade"]
937
+ ];
938
+ /** Null means "every site" (network): tokens, webhooks, role assignments; or "not about a site": history. */
939
+ const OPTIONAL_SITE_TABLES = [
940
+ ["api_tokens", "cascade"],
941
+ ["webhooks", "cascade"],
942
+ ["admin_user_roles", "cascade"],
943
+ ["outbox_events", "set null"],
944
+ ["audit_events", "set null"]
945
+ ];
946
+ /** Copies of the entry's site, tied to it by a composite foreign key. */
947
+ const ENTRY_SITE_COPIES = [
948
+ ["entry_heads", "cascade"],
949
+ ["unique_values", "cascade"],
950
+ ["publication_log", "restrict"],
951
+ ["scheduled_publications", "cascade"],
952
+ ["content_health_findings", "cascade"],
953
+ ["preview_tokens", "cascade"]
954
+ ];
955
+ const createSites = async (db) => {
956
+ await sql`
957
+ create table sites (
958
+ id uuid primary key default gen_random_uuid(),
959
+ key text not null unique check (key ~ '^[a-z][a-z0-9-]{0,62}$'),
960
+ name text not null check (length(name) between 1 and 200),
961
+ is_primary boolean not null default false,
962
+ version integer not null default 1,
963
+ created_at timestamptz not null default now(),
964
+ updated_at timestamptz not null default now()
965
+ )
966
+ `.execute(db);
967
+ await sql`create unique index sites_single_primary_uq on sites (is_primary) where is_primary`.execute(db);
968
+ await sql`insert into sites (id, key, name, is_primary) values (${PRIMARY_SITE_ID}, 'default', 'Default site', true)`.execute(db);
969
+ await sql`
970
+ create table site_app_roles (
971
+ site_id uuid not null references sites(id) on delete cascade,
972
+ audience text not null check (audience in ('public', 'authenticated')),
973
+ role_id uuid not null references app_roles(id) on delete cascade,
974
+ created_at timestamptz not null default now(),
975
+ primary key (site_id, audience, role_id)
976
+ )
977
+ `.execute(db);
978
+ await sql`create index site_app_roles_role_idx on site_app_roles (role_id)`.execute(db);
979
+ await sql`
980
+ insert into site_app_roles (site_id, audience, role_id) values
981
+ (${PRIMARY_SITE_ID}, 'public', ${PUBLIC_ROLE_ID}),
982
+ (${PRIMARY_SITE_ID}, 'authenticated', ${AUTHENTICATED_ROLE_ID})
983
+ `.execute(db);
984
+ };
985
+ const siteForeignKey = async (db, table, onDelete) => {
986
+ const name = sql.id(`${table}_site_fk`);
987
+ await sql`alter table ${sql.table(table)} add constraint ${name}
988
+ foreign key (site_id) references sites(id) on delete ${sql.raw(onDelete)} not valid`.execute(db);
989
+ await sql`alter table ${sql.table(table)} validate constraint ${name}`.execute(db);
990
+ };
991
+ const addSiteColumns = async (db) => {
992
+ for (const [table, onDelete] of REQUIRED_SITE_TABLES) {
993
+ await sql`alter table ${sql.table(table)}
994
+ add column site_id uuid not null default ${sql.lit(PRIMARY_SITE_ID)}::uuid`.execute(db);
995
+ await sql`alter table ${sql.table(table)} alter column site_id drop default`.execute(db);
996
+ await siteForeignKey(db, table, onDelete);
997
+ }
998
+ for (const [table, onDelete] of OPTIONAL_SITE_TABLES) {
999
+ await sql`alter table ${sql.table(table)} add column site_id uuid`.execute(db);
1000
+ await siteForeignKey(db, table, onDelete);
1001
+ }
1002
+ await sql`
1003
+ update api_tokens t set site_id = ${PRIMARY_SITE_ID}
1004
+ from admin_roles r where r.id = t.role_id and r.kind = 'delivery'
1005
+ `.execute(db);
1006
+ };
1007
+ const tieCopiesToEntries = async (db) => {
1008
+ await sql`alter table entries add constraint entries_id_site_uq unique (id, site_id)`.execute(db);
1009
+ for (const [table, onDelete] of ENTRY_SITE_COPIES) {
1010
+ const name = sql.id(`${table}_entry_site_fk`);
1011
+ await sql`alter table ${sql.table(table)} add constraint ${name}
1012
+ foreign key (entry_id, site_id) references entries(id, site_id) on delete ${sql.raw(onDelete)} not valid`.execute(db);
1013
+ await sql`alter table ${sql.table(table)} validate constraint ${name}`.execute(db);
1014
+ }
1015
+ };
1016
+ const rekeyContent = async (db) => {
1017
+ await sql`drop index entries_model_idx`.execute(db);
1018
+ await sql`create index entries_model_idx on entries (site_id, model_id, created_at) where deleted_at is null`.execute(db);
1019
+ await sql`drop index entry_heads_model_locale_state_idx`.execute(db);
1020
+ await sql`create index entry_heads_model_locale_state_idx on entry_heads (site_id, model_id, locale, state)`.execute(db);
1021
+ await sql`alter table unique_values drop constraint unique_values_pkey`.execute(db);
1022
+ await sql`alter table unique_values add constraint unique_values_pkey
1023
+ primary key (site_id, field_id, locale, state, value_hash)`.execute(db);
1024
+ await sql`alter table publication_state drop constraint publication_state_pkey`.execute(db);
1025
+ await sql`alter table publication_state drop column id`.execute(db);
1026
+ await sql`alter table publication_state add constraint publication_state_pkey primary key (site_id)`.execute(db);
1027
+ await sql`alter table publication_snapshots drop constraint publication_snapshots_pkey`.execute(db);
1028
+ await sql`alter table publication_snapshots add constraint publication_snapshots_pkey primary key (site_id, seq)`.execute(db);
1029
+ await sql`drop index publication_log_to_seq_idx`.execute(db);
1030
+ await sql`create index publication_log_to_seq_idx on publication_log (site_id, to_seq) where to_seq is not null`.execute(db);
1031
+ await sql`drop index publication_log_model_seq_idx`.execute(db);
1032
+ await sql`create index publication_log_model_seq_idx on publication_log (site_id, model_id, from_seq)`.execute(db);
1033
+ await sql`create index scheduled_publications_site_idx on scheduled_publications (site_id, run_at)`.execute(db);
1034
+ await sql`drop index content_health_findings_open_idx`.execute(db);
1035
+ await sql`create index content_health_findings_open_idx on content_health_findings
1036
+ (site_id, rule, last_seen_at desc, id) where resolved_at is null`.execute(db);
1037
+ await sql`drop index change_sets_status_idx`.execute(db);
1038
+ await sql`create index change_sets_status_idx on change_sets (site_id, status, created_at desc)`.execute(db);
1039
+ };
1040
+ const rekeyMedia = async (db) => {
1041
+ await sql`alter table media_folders add constraint media_folders_id_site_uq unique (id, site_id)`.execute(db);
1042
+ await sql`drop index media_folders_sibling_name_uq`.execute(db);
1043
+ await sql`create unique index media_folders_sibling_name_uq on media_folders
1044
+ (site_id, coalesce(parent_id, '00000000-0000-0000-0000-000000000000'::uuid), lower(name))`.execute(db);
1045
+ await sql`alter table media_folders drop constraint media_folders_parent_id_fkey`.execute(db);
1046
+ await sql`alter table media_folders add constraint media_folders_parent_id_fkey
1047
+ foreign key (parent_id, site_id) references media_folders(id, site_id) on delete restrict`.execute(db);
1048
+ await sql`alter table media_assets drop constraint media_assets_folder_id_fkey`.execute(db);
1049
+ await sql`alter table media_assets add constraint media_assets_folder_id_fkey
1050
+ foreign key (folder_id, site_id) references media_folders(id, site_id) on delete set null (folder_id)`.execute(db);
1051
+ await sql`drop index media_assets_created_idx`.execute(db);
1052
+ await sql`create index media_assets_created_idx on media_assets (site_id, created_at, id) where deleted_at is null`.execute(db);
1053
+ };
1054
+ const rekeyAppUsersAndUsage = async (db) => {
1055
+ await sql`drop index app_users_email_uq`.execute(db);
1056
+ await sql`create unique index app_users_email_uq on app_users (site_id, lower(email)) where deleted_at is null`.execute(db);
1057
+ await sql`drop index app_users_created_idx`.execute(db);
1058
+ await sql`create index app_users_created_idx on app_users (site_id, created_at desc, id desc)
1059
+ where deleted_at is null`.execute(db);
1060
+ await sql`alter table app_oauth_accounts drop constraint app_oauth_accounts_identity_uq`.execute(db);
1061
+ await sql`alter table app_oauth_accounts add constraint app_oauth_accounts_identity_uq
1062
+ unique (site_id, provider, provider_user_id)`.execute(db);
1063
+ await sql`alter table field_reads drop constraint field_reads_pkey`.execute(db);
1064
+ await sql`alter table field_reads add constraint field_reads_pkey
1065
+ primary key (day, site_id, model_id, field_path, principal_key, selection)`.execute(db);
1066
+ await sql`alter table token_reads drop constraint token_reads_pkey`.execute(db);
1067
+ await sql`alter table token_reads add constraint token_reads_pkey primary key (day, site_id, principal_key)`.execute(db);
1068
+ };
1069
+ /** A role is assigned on one site or on every site (null). */
1070
+ const siteRoleAssignments = async (db) => {
1071
+ await sql`alter table admin_user_roles drop constraint admin_user_roles_pk`.execute(db);
1072
+ await sql`alter table admin_user_roles add column id uuid not null default gen_random_uuid()`.execute(db);
1073
+ await sql`alter table admin_user_roles add constraint admin_user_roles_pk primary key (id)`.execute(db);
1074
+ await sql`alter table admin_user_roles add constraint admin_user_roles_assignment_uq
1075
+ unique nulls not distinct (admin_user_id, role_id, site_id)`.execute(db);
1076
+ await sql`alter table admin_invitations
1077
+ add column role_assignments jsonb not null default '[]'::jsonb`.execute(db);
1078
+ await sql`
1079
+ update admin_invitations set role_assignments = coalesce(
1080
+ (select jsonb_agg(jsonb_build_object('roleId', r, 'siteId', null)) from unnest(role_ids) as r), '[]'::jsonb)
1081
+ `.execute(db);
1082
+ await sql`alter table admin_invitations drop column role_ids`.execute(db);
1083
+ };
1084
+ const up$19 = async (db) => {
1085
+ await createSites(db);
1086
+ await addSiteColumns(db);
1087
+ await tieCopiesToEntries(db);
1088
+ await rekeyContent(db);
1089
+ await rekeyMedia(db);
1090
+ await rekeyAppUsersAndUsage(db);
1091
+ await siteRoleAssignments(db);
1092
+ };
1093
+ const assertSingleSite = async (db) => {
1094
+ const { rows } = await sql`select count(*)::text as count from sites`.execute(db);
1095
+ if (Number(rows[0]?.count ?? 0) > 1) throw new Error("Cannot roll back sites while more than one site exists: merging them would merge their snapshot ledgers and uniqueness and widen role assignments. Delete the other sites first.");
1096
+ };
1097
+ const down$19 = async (db) => {
1098
+ await assertSingleSite(db);
1099
+ await sql`alter table admin_invitations add column role_ids uuid[] not null default '{}'::uuid[]`.execute(db);
1100
+ await sql`
1101
+ update admin_invitations set role_ids = coalesce(
1102
+ (select array_agg(distinct (a->>'roleId')::uuid) from jsonb_array_elements(role_assignments) as a), '{}'::uuid[])
1103
+ `.execute(db);
1104
+ await sql`alter table admin_invitations drop column role_assignments`.execute(db);
1105
+ await sql`
1106
+ delete from admin_user_roles a using admin_user_roles b
1107
+ where a.admin_user_id = b.admin_user_id and a.role_id = b.role_id and a.id > b.id
1108
+ `.execute(db);
1109
+ await sql`alter table admin_user_roles drop constraint admin_user_roles_assignment_uq`.execute(db);
1110
+ await sql`alter table admin_user_roles drop constraint admin_user_roles_pk`.execute(db);
1111
+ await sql`alter table admin_user_roles drop column id`.execute(db);
1112
+ await sql`alter table admin_user_roles add constraint admin_user_roles_pk primary key (admin_user_id, role_id)`.execute(db);
1113
+ await sql`alter table token_reads drop constraint token_reads_pkey`.execute(db);
1114
+ await sql`alter table token_reads add constraint token_reads_pkey primary key (day, principal_key)`.execute(db);
1115
+ await sql`alter table field_reads drop constraint field_reads_pkey`.execute(db);
1116
+ await sql`alter table field_reads add constraint field_reads_pkey
1117
+ primary key (day, model_id, field_path, principal_key, selection)`.execute(db);
1118
+ await sql`alter table app_oauth_accounts drop constraint app_oauth_accounts_identity_uq`.execute(db);
1119
+ await sql`alter table app_oauth_accounts add constraint app_oauth_accounts_identity_uq
1120
+ unique (provider, provider_user_id)`.execute(db);
1121
+ await sql`drop index app_users_created_idx`.execute(db);
1122
+ await sql`create index app_users_created_idx on app_users (created_at desc, id desc) where deleted_at is null`.execute(db);
1123
+ await sql`drop index app_users_email_uq`.execute(db);
1124
+ await sql`create unique index app_users_email_uq on app_users (lower(email)) where deleted_at is null`.execute(db);
1125
+ await sql`drop index media_assets_created_idx`.execute(db);
1126
+ await sql`create index media_assets_created_idx on media_assets (created_at, id) where deleted_at is null`.execute(db);
1127
+ await sql`alter table media_assets drop constraint media_assets_folder_id_fkey`.execute(db);
1128
+ await sql`alter table media_assets add constraint media_assets_folder_id_fkey
1129
+ foreign key (folder_id) references media_folders(id) on delete set null`.execute(db);
1130
+ await sql`alter table media_folders drop constraint media_folders_parent_id_fkey`.execute(db);
1131
+ await sql`alter table media_folders add constraint media_folders_parent_id_fkey
1132
+ foreign key (parent_id) references media_folders(id) on delete restrict`.execute(db);
1133
+ await sql`drop index media_folders_sibling_name_uq`.execute(db);
1134
+ await sql`create unique index media_folders_sibling_name_uq on media_folders
1135
+ (coalesce(parent_id, '00000000-0000-0000-0000-000000000000'::uuid), lower(name))`.execute(db);
1136
+ await sql`alter table media_folders drop constraint media_folders_id_site_uq`.execute(db);
1137
+ await sql`drop index change_sets_status_idx`.execute(db);
1138
+ await sql`create index change_sets_status_idx on change_sets (status, created_at desc)`.execute(db);
1139
+ await sql`drop index content_health_findings_open_idx`.execute(db);
1140
+ await sql`create index content_health_findings_open_idx on content_health_findings (rule, last_seen_at desc, id)
1141
+ where resolved_at is null`.execute(db);
1142
+ await sql`drop index scheduled_publications_site_idx`.execute(db);
1143
+ await sql`drop index publication_log_model_seq_idx`.execute(db);
1144
+ await sql`create index publication_log_model_seq_idx on publication_log (model_id, from_seq)`.execute(db);
1145
+ await sql`drop index publication_log_to_seq_idx`.execute(db);
1146
+ await sql`create index publication_log_to_seq_idx on publication_log (to_seq) where to_seq is not null`.execute(db);
1147
+ await sql`alter table publication_snapshots drop constraint publication_snapshots_pkey`.execute(db);
1148
+ await sql`alter table publication_snapshots add constraint publication_snapshots_pkey primary key (seq)`.execute(db);
1149
+ await sql`alter table publication_state drop constraint publication_state_pkey`.execute(db);
1150
+ await sql`alter table publication_state add column id boolean not null default true check (id)`.execute(db);
1151
+ await sql`alter table publication_state add constraint publication_state_pkey primary key (id)`.execute(db);
1152
+ await sql`alter table unique_values drop constraint unique_values_pkey`.execute(db);
1153
+ await sql`alter table unique_values add constraint unique_values_pkey
1154
+ primary key (field_id, locale, state, value_hash)`.execute(db);
1155
+ await sql`drop index entry_heads_model_locale_state_idx`.execute(db);
1156
+ await sql`create index entry_heads_model_locale_state_idx on entry_heads (model_id, locale, state)`.execute(db);
1157
+ await sql`drop index entries_model_idx`.execute(db);
1158
+ await sql`create index entries_model_idx on entries (model_id, created_at) where deleted_at is null`.execute(db);
1159
+ for (const [table] of ENTRY_SITE_COPIES) await sql`alter table ${sql.table(table)} drop constraint ${sql.id(`${table}_entry_site_fk`)}`.execute(db);
1160
+ await sql`alter table entries drop constraint entries_id_site_uq`.execute(db);
1161
+ for (const [table] of [...REQUIRED_SITE_TABLES, ...OPTIONAL_SITE_TABLES]) await sql`alter table ${sql.table(table)} drop column site_id`.execute(db);
1162
+ await sql`drop table site_app_roles`.execute(db);
1163
+ await sql`drop table sites`.execute(db);
1164
+ };
1165
+ //#endregion
1166
+ //#region ../../apps/api/src/db/migrations/20261003140200_enqueue_field_index_layout.ts
1167
+ var _20261003140200_enqueue_field_index_layout_exports = /* @__PURE__ */ __exportAll({
1168
+ down: () => down$18,
1169
+ up: () => up$18
1170
+ });
1171
+ /**
1172
+ * Field index layout v2 (sites plan §H): per-field expression indexes on `entry_heads` lead with `site_id`,
1173
+ * because every content query names its site. Building them is `CREATE INDEX CONCURRENTLY`, which cannot run
1174
+ * in a migration's transaction and may take a while, so this migration only enqueues the
1175
+ * `schema.fieldIndexLayout` job (schema/planner/indexLayout.ts), which rebuilds the indexes and drops the
1176
+ * old ones while the instance keeps serving. Nothing to rebuild (no field index yet, e.g. a new install):
1177
+ * no job.
1178
+ */
1179
+ const JOB_TYPE$1 = "schema.fieldIndexLayout";
1180
+ const IDEMPOTENCY_KEY$1 = "schema.fieldIndexLayout:v2";
1181
+ const up$18 = async (db) => {
1182
+ await sql`
1183
+ insert into jobs (type, payload, max_attempts, idempotency_key)
1184
+ select ${JOB_TYPE$1}, '{}'::jsonb, 5, ${IDEMPOTENCY_KEY$1}
1185
+ where exists (
1186
+ select 1 from pg_catalog.pg_index i
1187
+ join pg_catalog.pg_class c on c.oid = i.indexrelid
1188
+ where i.indrelid = 'entry_heads'::regclass and c.relname like 'eh\\_%'
1189
+ )
1190
+ on conflict do nothing
1191
+ `.execute(db);
1192
+ };
1193
+ /** The layout job is idempotent; rolling back only forgets it if it has not run yet. */
1194
+ const down$18 = async (db) => {
1195
+ await sql`delete from jobs where idempotency_key = ${IDEMPOTENCY_KEY$1} and status = 'pending'`.execute(db);
1196
+ };
1197
+ //#endregion
1198
+ //#region ../../apps/api/src/db/migrations/20261003140500_change_set_items_site_and_preview_entry.ts
1199
+ var _20261003140500_change_set_items_site_and_preview_entry_exports = /* @__PURE__ */ __exportAll({
1200
+ down: () => down$17,
1201
+ up: () => up$17
1202
+ });
1203
+ /**
1204
+ * Sites, publishing (plan agentic-ecosystem §H, package G5).
1205
+ *
1206
+ * - `change_set_items.site_id`: a copy of the set's site, tied to the set and to the item's entry by
1207
+ * composite foreign keys, so a change set can never hold another site's entry (schema items have no entry;
1208
+ * their entry key is null and the entry foreign key does not apply).
1209
+ * - `preview_tokens.entry_id` becomes required: a preview token previews one entry of one site. Model-wide
1210
+ * tokens (short-lived by design) are deleted; whoever needs one opens the entry's preview again.
1211
+ */
1212
+ const up$17 = async (db) => {
1213
+ await sql`alter table change_sets add constraint change_sets_id_site_uq unique (id, site_id)`.execute(db);
1214
+ await sql`alter table change_set_items add column site_id uuid`.execute(db);
1215
+ await sql`
1216
+ update change_set_items i set site_id = s.site_id from change_sets s where s.id = i.change_set_id
1217
+ `.execute(db);
1218
+ await sql`alter table change_set_items alter column site_id set not null`.execute(db);
1219
+ await sql`alter table change_set_items add constraint change_set_items_set_site_fk
1220
+ foreign key (change_set_id, site_id) references change_sets(id, site_id) on delete cascade not valid`.execute(db);
1221
+ await sql`alter table change_set_items validate constraint change_set_items_set_site_fk`.execute(db);
1222
+ await sql`alter table change_set_items add constraint change_set_items_entry_site_fk
1223
+ foreign key (entry_id, site_id) references entries(id, site_id) on delete cascade not valid`.execute(db);
1224
+ await sql`alter table change_set_items validate constraint change_set_items_entry_site_fk`.execute(db);
1225
+ await sql`delete from preview_tokens where entry_id is null`.execute(db);
1226
+ await sql`alter table preview_tokens alter column entry_id set not null`.execute(db);
1227
+ };
1228
+ const down$17 = async (db) => {
1229
+ await sql`alter table preview_tokens alter column entry_id drop not null`.execute(db);
1230
+ await sql`alter table change_set_items drop constraint change_set_items_entry_site_fk`.execute(db);
1231
+ await sql`alter table change_set_items drop constraint change_set_items_set_site_fk`.execute(db);
1232
+ await sql`alter table change_set_items drop column site_id`.execute(db);
1233
+ await sql`alter table change_sets drop constraint change_sets_id_site_uq`.execute(db);
1234
+ };
1235
+ //#endregion
1236
+ //#region ../../apps/api/src/db/migrations/20261003150000_widen_deployment_providers.ts
1237
+ var _20261003150000_widen_deployment_providers_exports = /* @__PURE__ */ __exportAll({
1238
+ down: () => down$16,
1239
+ up: () => up$16
1240
+ });
1241
+ /**
1242
+ * Deploy adapters (plan agentic-ecosystem §B2): deployment connections may use the Vercel and Netlify
1243
+ * providers. The check is re-added NOT VALID and then validated, so existing rows are checked without
1244
+ * blocking writes for the scan.
1245
+ */
1246
+ const WIDENED = sql`provider in ('generic_webhook', 'cloudflare_pages', 'vercel', 'netlify', 'github')`;
1247
+ const ORIGINAL = sql`provider in ('generic_webhook', 'cloudflare_pages', 'github')`;
1248
+ const replaceCheck = async (db, check) => {
1249
+ await sql`alter table deployment_connections drop constraint deployment_connections_provider_check`.execute(db);
1250
+ await sql`alter table deployment_connections add constraint deployment_connections_provider_check
1251
+ check (${check}) not valid`.execute(db);
1252
+ await sql`alter table deployment_connections validate constraint deployment_connections_provider_check`.execute(db);
1253
+ };
1254
+ const up$16 = async (db) => {
1255
+ await replaceCheck(db, WIDENED);
1256
+ };
1257
+ /** Refuses while Vercel or Netlify connections exist: a down migration never deletes user data. */
1258
+ const down$16 = async (db) => {
1259
+ const { rows } = await sql`
1260
+ select count(*)::text as count from deployment_connections where provider in ('vercel', 'netlify')
1261
+ `.execute(db);
1262
+ const count = Number(rows[0]?.count ?? 0);
1263
+ if (count > 0) throw new Error(`Cannot roll back: ${count} deployment connection(s) use Vercel or Netlify. Delete them in the admin (Publishing → Deployments) first, then run the rollback again.`);
1264
+ await replaceCheck(db, ORIGINAL);
1265
+ };
1266
+ //#endregion
1267
+ //#region ../../apps/api/src/db/migrations/20261003160000_create_assist_runs.ts
1268
+ var _20261003160000_create_assist_runs_exports = /* @__PURE__ */ __exportAll({
1269
+ down: () => down$15,
1270
+ up: () => up$15
1271
+ });
1272
+ /**
1273
+ * Editor assists (plan agentic-ecosystem §A0, §I): one `assist_runs` row per assist request (who, which
1274
+ * action, which model, token usage, duration, outcome). Never prompt or response text. Content-ops runs are
1275
+ * jobs: `job_id` points at the job whose result holds the proposals. Pruned by the retention job after
1276
+ * USAGE_RETENTION_DAYS. Change sets an assist run opens are marked `source = 'assist'`.
1277
+ */
1278
+ const ACTIONS = sql`action in ('alt_text', 'summarize', 'translate', 'rewrite', 'schema_draft', 'content_ops')`;
1279
+ const SOURCES_WIDENED = sql`source in ('manual', 'release', 'restore', 'builder', 'assist')`;
1280
+ const SOURCES_ORIGINAL = sql`source in ('manual', 'release', 'restore', 'builder')`;
1281
+ const replaceSourceCheck = async (db, check) => {
1282
+ await sql`alter table change_sets drop constraint change_sets_source_check`.execute(db);
1283
+ await sql`alter table change_sets add constraint change_sets_source_check check (${check}) not valid`.execute(db);
1284
+ await sql`alter table change_sets validate constraint change_sets_source_check`.execute(db);
1285
+ };
1286
+ const up$15 = async (db) => {
1287
+ await db.schema.createTable("assist_runs").addColumn("id", "uuid", (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`)).addColumn("site_id", "uuid", (col) => col.notNull().references("sites.id").onDelete("cascade")).addColumn("actor_type", "text", (col) => col.notNull()).addColumn("actor_id", "text", (col) => col.notNull()).addColumn("action", "text", (col) => col.notNull().check(ACTIONS)).addColumn("rule", "text").addColumn("provider", "text", (col) => col.notNull()).addColumn("model", "text", (col) => col.notNull()).addColumn("input_tokens", "integer", (col) => col.notNull().defaultTo(0)).addColumn("output_tokens", "integer", (col) => col.notNull().defaultTo(0)).addColumn("duration_ms", "integer", (col) => col.notNull().defaultTo(0)).addColumn("status", "text", (col) => col.notNull().check(sql`status in ('queued', 'running', 'succeeded', 'failed')`)).addColumn("error_code", "text").addColumn("job_id", "uuid", (col) => col.references("jobs.id").onDelete("set null")).addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`now()`)).addColumn("finished_at", "timestamptz").execute();
1288
+ await db.schema.createIndex("assist_runs_site_created_idx").on("assist_runs").columns(["site_id", "created_at"]).execute();
1289
+ await db.schema.createIndex("assist_runs_created_idx").on("assist_runs").column("created_at").execute();
1290
+ await replaceSourceCheck(db, SOURCES_WIDENED);
1291
+ };
1292
+ /** Refuses while change sets opened by assist exist: a down migration never deletes user data. */
1293
+ const down$15 = async (db) => {
1294
+ const { rows } = await sql`
1295
+ select count(*)::text as count from change_sets where source = 'assist'
1296
+ `.execute(db);
1297
+ const count = Number(rows[0]?.count ?? 0);
1298
+ if (count > 0) throw new Error(`Cannot roll back: ${count} change set(s) were opened by assist. Discard and delete them first, then run the rollback again.`);
1299
+ await replaceSourceCheck(db, SOURCES_ORIGINAL);
1300
+ await db.schema.dropTable("assist_runs").execute();
1301
+ };
1302
+ //#endregion
1303
+ //#region ../../apps/api/src/db/migrations/20261003170000_audit_events_seq.ts
1304
+ var _20261003170000_audit_events_seq_exports$2 = /* @__PURE__ */ __exportAll({
1305
+ down: () => down$14,
1306
+ up: () => up$14
1307
+ });
1308
+ /**
1309
+ * `audit_events.seq`: a monotonic tiebreaker after `occurred_at`, so events written in the same instant (one
1310
+ * transaction shares `now()`; SQLite and MySQL store milliseconds) list in the order they were written. The
1311
+ * UUID `id` is random and cannot order them. Adding an identity column rewrites the table once; existing rows
1312
+ * are numbered in the order the rewrite reads them. No index: the audit queries filter and sort by
1313
+ * `occurred_at` first, and `seq` only breaks ties.
1314
+ */
1315
+ const up$14 = async (db) => {
1316
+ await db.schema.alterTable("audit_events").addColumn("seq", "bigint", (col) => col.notNull().generatedAlwaysAsIdentity()).execute();
1317
+ };
1318
+ const down$14 = async (db) => {
1319
+ await db.schema.alterTable("audit_events").dropColumn("seq").execute();
1320
+ };
1321
+ //#endregion
1322
+ //#region ../../apps/api/src/db/migrations/20261004120000_models_site_scope.ts
1323
+ var _20261004120000_models_site_scope_exports$2 = /* @__PURE__ */ __exportAll({
1324
+ down: () => down$13,
1325
+ up: () => up$13
1326
+ });
1327
+ /**
1328
+ * Per-site schema (plan site-schema): a definition belongs to one site (`models.site_id`) or is shared by every
1329
+ * site (null, what every existing definition becomes). API IDs are unique per scope; whether a shared API ID
1330
+ * collides with a site's is the planner's rule (every view a change touches must stay valid), checked under
1331
+ * the schema lock. `schema_drafts.shared` says whether a change set's draft creates a shared definition; a
1332
+ * draft otherwise takes its change set's site. Nothing here runs DDL per model (CONTRIBUTING.md rule 1).
1333
+ *
1334
+ * `down` refuses while any definition belongs to a site: two sites may each have a `post`, which one index
1335
+ * over `lower(api_key)` cannot hold. Share or delete them first; then `down` is lossless.
1336
+ */
1337
+ const NIL_UUID$1 = "00000000-0000-0000-0000-000000000000";
1338
+ const up$13 = async (db) => {
1339
+ await sql`alter table models add column site_id uuid references sites (id)`.execute(db);
1340
+ await sql`create index models_site_idx on models (site_id)`.execute(db);
1341
+ await sql`drop index models_api_key_uq`.execute(db);
1342
+ await sql`
1343
+ create unique index models_api_key_uq on models (coalesce(site_id, ${sql.lit(NIL_UUID$1)}::uuid), lower(api_key))
1344
+ where deleted_at is null
1345
+ `.execute(db);
1346
+ await sql`alter table schema_drafts add column shared boolean not null default false`.execute(db);
1347
+ };
1348
+ const down$13 = async (db) => {
1349
+ const { rows } = await sql`select count(*)::text as count from models where site_id is not null`.execute(db);
1350
+ if (Number(rows[0]?.count ?? 0) > 0) throw new Error("Cannot roll back per-site schema while definitions belong to a site: two sites may share an API ID. Share or delete the site definitions first.");
1351
+ await sql`alter table schema_drafts drop column shared`.execute(db);
1352
+ await sql`drop index models_api_key_uq`.execute(db);
1353
+ await sql`create unique index models_api_key_uq on models (lower(api_key)) where deleted_at is null`.execute(db);
1354
+ await sql`drop index models_site_idx`.execute(db);
1355
+ await sql`alter table models drop column site_id`.execute(db);
1356
+ };
1357
+ //#endregion
1358
+ //#region ../../apps/api/src/db/migrations/20261004130000_site_seo_settings.ts
1359
+ var _20261004130000_site_seo_settings_exports$2 = /* @__PURE__ */ __exportAll({
1360
+ down: () => down$12,
1361
+ up: () => up$12
1362
+ });
1363
+ /**
1364
+ * SEO defaults per site (plan seo-fields): `sites.seo_defaults` holds the site's default texts per locale, its
1365
+ * default social image and Twitter handle (`SeoDefaults` in @shapio/schema). Nullable, no default: a
1366
+ * catalog-only change, no table rewrite.
1367
+ *
1368
+ * `site.settings` is the site action that edits them. Every role that holds `publishing.manage` (everything
1369
+ * about what a site publishes) gets it; the built-in owner and admin roles get it from `ensureSystemRoles`.
1370
+ * The permissions version moves only when grants changed, so running instances reload exactly then.
1371
+ */
1372
+ const up$12 = async (db) => {
1373
+ await sql`alter table sites add column seo_defaults jsonb`.execute(db);
1374
+ await sql`
1375
+ with granted as (
1376
+ insert into admin_role_permissions (role_id, action, model_id, condition, field_ids)
1377
+ select role_id, 'site.settings', null, null, null
1378
+ from admin_role_permissions
1379
+ where action = 'publishing.manage' and model_id is null
1380
+ on conflict on constraint admin_role_permissions_grant_uq do nothing
1381
+ returning 1
1382
+ )
1383
+ update system_versions set permissions_version = permissions_version + 1, updated_at = now()
1384
+ where exists (select 1 from granted)
1385
+ `.execute(db);
1386
+ };
1387
+ const down$12 = async (db) => {
1388
+ await sql`
1389
+ with revoked as (
1390
+ delete from admin_role_permissions where action = 'site.settings' returning 1
1391
+ )
1392
+ update system_versions set permissions_version = permissions_version + 1, updated_at = now()
1393
+ where exists (select 1 from revoked)
1394
+ `.execute(db);
1395
+ await sql`alter table sites drop column seo_defaults`.execute(db);
1396
+ };
1397
+ //#endregion
1398
+ //#region ../../apps/api/src/db/migrations/20261005120000_entry_heads_entry_created_at.ts
1399
+ var _20261005120000_entry_heads_entry_created_at_exports$2 = /* @__PURE__ */ __exportAll({
1400
+ down: () => down$11,
1401
+ up: () => up$11
1402
+ });
1403
+ /**
1404
+ * Delivery perf 2 (ADR 0001 amendment): the entry's `created_at` on each head (`entry_created_at`), so a list
1405
+ * in the default newest-first order walks `entry_heads_entry_order_idx` and stops at the page instead of
1406
+ * joining every matching head to `entries` and sorting them. `entries.created_at` never changes, and every head
1407
+ * insert copies it (repositories `entryHeads.insert`, `transferImport.insertHead`).
1408
+ *
1409
+ * The column is added nullable (a catalog-only change) and backfilled here in batches. The index is built
1410
+ * here on an empty table (a new install). With heads present it is built `CONCURRENTLY`, which cannot run
1411
+ * in a migration's transaction, by the `schema.entryOrderIndex` job this migration enqueues
1412
+ * (schema/planner/indexes.ts), so writes continue while it builds; queries are correct without it.
1413
+ */
1414
+ const INDEX$2 = "entry_heads_entry_order_idx";
1415
+ const JOB_TYPE = "schema.entryOrderIndex";
1416
+ const IDEMPOTENCY_KEY = "schema.entryOrderIndex:v1";
1417
+ const BATCH$1 = 5e3;
1418
+ const up$11 = async (db) => {
1419
+ await sql`alter table entry_heads add column entry_created_at timestamptz`.execute(db);
1420
+ for (;;) {
1421
+ const result = await sql`
1422
+ update entry_heads h set entry_created_at = e.created_at
1423
+ from entries e
1424
+ where e.id = h.entry_id and (h.entry_id, h.locale, h.state) in (
1425
+ select entry_id, locale, state from entry_heads where entry_created_at is null limit ${BATCH$1}
1426
+ )
1427
+ `.execute(db);
1428
+ if (Number(result.numAffectedRows ?? 0n) < BATCH$1) break;
1429
+ }
1430
+ const { rows } = await sql`select exists (select 1 from entry_heads) as present`.execute(db);
1431
+ if (rows[0]?.present) {
1432
+ await sql`
1433
+ insert into jobs (type, payload, max_attempts, idempotency_key)
1434
+ values (${JOB_TYPE}, '{}'::jsonb, 5, ${IDEMPOTENCY_KEY})
1435
+ on conflict do nothing
1436
+ `.execute(db);
1437
+ return;
1438
+ }
1439
+ await sql`
1440
+ create index ${sql.id(INDEX$2)} on entry_heads (site_id, model_id, state, entry_created_at desc, entry_id)
1441
+ `.execute(db);
1442
+ };
1443
+ const down$11 = async (db) => {
1444
+ await sql`delete from jobs where idempotency_key = ${IDEMPOTENCY_KEY} and status = 'pending'`.execute(db);
1445
+ await sql`drop index if exists ${sql.id(INDEX$2)}`.execute(db);
1446
+ await sql`alter table entry_heads drop column entry_created_at`.execute(db);
1447
+ };
1448
+ //#endregion
1449
+ //#region ../../apps/api/src/db/migrations/20261006120000_system_versions_release.ts
1450
+ var _20261006120000_system_versions_release_exports = /* @__PURE__ */ __exportAll({
1451
+ dialectNeutral: () => true,
1452
+ down: () => down$10,
1453
+ up: () => up$10
1454
+ });
1455
+ const up$10 = async (db) => {
1456
+ await db.schema.alterTable("system_versions").addColumn("release", "text").execute();
1457
+ };
1458
+ const down$10 = async (db) => {
1459
+ await db.schema.alterTable("system_versions").dropColumn("release").execute();
1460
+ };
1461
+ //#endregion
1462
+ //#region ../../apps/api/src/db/migrations/mysql/0001_baseline.ts
1463
+ var _0001_baseline_exports$1 = /* @__PURE__ */ __exportAll({
1464
+ down: () => down$9,
1465
+ up: () => up$9
1466
+ });
1467
+ /**
1468
+ * MySQL baseline: Shapio's whole system schema as of PostgreSQL migration
1469
+ * `20261003160000_create_assist_runs`, in one step, with the rows those migrations seed. Later migrations
1470
+ * follow the twin rule (`./index.ts`).
1471
+ *
1472
+ * Written from the migrated PostgreSQL catalog (types in `db/mysql/codec.ts`), then kept honest by
1473
+ * `test/mysqlBaseline.int.test.ts`, which migrates both databases and compares the logical schema and the
1474
+ * seeded rows. Differences that are deliberate:
1475
+ * - Tables default to `utf8mb4` with the binary `utf8mb4_0900_bin` collation (case-sensitive, NO PAD,
1476
+ * code point order); UUIDs are `CHAR(36)` ASCII with a case-insensitive collation, like PostgreSQL's uuid
1477
+ * input. Text in a key or index is `VARCHAR(n)` (`LONGTEXT` cannot be indexed whole); other text is
1478
+ * `LONGTEXT`.
1479
+ * - Partial unique indexes are unique indexes on `(case when <predicate> then <column> end)` key parts:
1480
+ * MySQL has no partial index, and a NULL key part never conflicts. Partial non-unique indexes are plain.
1481
+ * The three `NULLS NOT DISTINCT` constraints index `coalesce(column, '')`.
1482
+ * - `entry_heads_data_gin` (GIN, containment) has no MySQL equivalent and is left out.
1483
+ * - `entry_heads.change_seq` has no default: the MySQL plugin fills it from
1484
+ * `sequence_entry_heads_change_seq` (AUTO_INCREMENT, no lock held to commit). UUID keys default to
1485
+ * `uuid()` but the plugin fills them first (v4, known before the insert, for emulated RETURNING).
1486
+ * - `media_assets_folder_id_fkey` sets only `folder_id` to null on delete (PostgreSQL's column list),
1487
+ * which MySQL expresses as a trigger; immutability triggers signal SQLSTATE 45000. With binary logging
1488
+ * on, creating triggers needs SUPER or `log_bin_trust_function_creators = 1` (documentation/mysql.md).
1489
+ * - `editor_presence` is a plain table (UNLOGGED has no MySQL meaning).
1490
+ * - MySQL-only tables: `advisory_locks` (transaction-level advisory locks as row locks),
1491
+ * `notifications` (LISTEN/NOTIFY as a polled table), `sequence_entry_heads_change_seq`.
1492
+ * - Identity keys are `AUTO_INCREMENT`. MySQL creates an index for a foreign key no index covers.
1493
+ * MySQL runs DDL outside transactions: if the baseline fails part-way, drop the database's tables and
1494
+ * migrate again.
1495
+ */
1496
+ const STATEMENTS$1 = [
1497
+ `create table \`system_versions\` (
1498
+ \`id\` tinyint(1) not null default 1,
1499
+ \`schema_version\` int not null default 0,
1500
+ \`permissions_version\` int not null default 0,
1501
+ \`updated_at\` datetime(6) not null default current_timestamp(6),
1502
+ constraint \`system_versions_pkey\` primary key (\`id\`),
1503
+ constraint \`system_versions_id_check\` check (\`id\` = 1)
1504
+ )`,
1505
+ `create table \`jobs\` (
1506
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
1507
+ \`type\` longtext not null,
1508
+ \`payload\` json not null default (json_object()),
1509
+ \`status\` longtext not null default ('pending'),
1510
+ \`priority\` int not null default 0,
1511
+ \`run_at\` datetime(6) not null default current_timestamp(6),
1512
+ \`attempts\` int not null default 0,
1513
+ \`max_attempts\` int not null default 10,
1514
+ \`idempotency_key\` varchar(512),
1515
+ \`locked_by\` longtext,
1516
+ \`locked_until\` datetime(6),
1517
+ \`checkpoint\` json,
1518
+ \`result\` json,
1519
+ \`last_error\` longtext,
1520
+ \`created_at\` datetime(6) not null default current_timestamp(6),
1521
+ \`updated_at\` datetime(6) not null default current_timestamp(6),
1522
+ \`finished_at\` datetime(6),
1523
+ constraint \`jobs_pkey\` primary key (\`id\`),
1524
+ constraint \`jobs_max_attempts_check\` check ((\`max_attempts\` > 0)),
1525
+ constraint \`jobs_status_check\` check ((\`status\` in ('pending', 'running', 'succeeded', 'dead')))
1526
+ )`,
1527
+ `create table \`outbox_events\` (
1528
+ \`id\` bigint not null auto_increment,
1529
+ \`event_id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
1530
+ \`type\` longtext not null,
1531
+ \`aggregate_type\` longtext not null,
1532
+ \`aggregate_id\` longtext not null,
1533
+ \`payload\` json not null default (json_object()),
1534
+ \`created_at\` datetime(6) not null default current_timestamp(6),
1535
+ \`dispatched_at\` datetime(6),
1536
+ \`dispatch_attempts\` int not null default 0,
1537
+ \`last_dispatch_error\` longtext,
1538
+ \`site_id\` char(36) character set ascii collate ascii_general_ci,
1539
+ unique key \`outbox_events_event_id_key\` (\`event_id\`),
1540
+ constraint \`outbox_events_pkey\` primary key (\`id\`)
1541
+ )`,
1542
+ `create table \`audit_events\` (
1543
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
1544
+ \`occurred_at\` datetime(6) not null default current_timestamp(6),
1545
+ \`actor_type\` varchar(64) not null,
1546
+ \`actor_id\` varchar(255),
1547
+ \`action\` longtext not null,
1548
+ \`target_type\` varchar(64),
1549
+ \`target_id\` varchar(255),
1550
+ \`outcome\` longtext not null,
1551
+ \`request_id\` longtext,
1552
+ \`ip\` longtext,
1553
+ \`metadata\` json not null default (json_object()),
1554
+ \`site_id\` char(36) character set ascii collate ascii_general_ci,
1555
+ constraint \`audit_events_pkey\` primary key (\`id\`),
1556
+ constraint \`audit_events_actor_type_check\` check ((\`actor_type\` in ('admin', 'app_user', 'token', 'anonymous', 'system'))),
1557
+ constraint \`audit_events_outcome_check\` check ((\`outcome\` in ('success', 'failure')))
1558
+ )`,
1559
+ `create table \`admin_users\` (
1560
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
1561
+ \`email\` varchar(255) not null,
1562
+ \`name\` longtext not null default (''),
1563
+ \`password_hash\` longtext not null,
1564
+ \`status\` longtext not null default ('active'),
1565
+ \`last_login_at\` datetime(6),
1566
+ \`password_changed_at\` datetime(6) not null default current_timestamp(6),
1567
+ \`created_at\` datetime(6) not null default current_timestamp(6),
1568
+ \`updated_at\` datetime(6) not null default current_timestamp(6),
1569
+ unique key \`admin_users_email_key\` (\`email\`),
1570
+ constraint \`admin_users_pkey\` primary key (\`id\`),
1571
+ constraint \`admin_users_status_check\` check ((\`status\` in ('active', 'disabled')))
1572
+ )`,
1573
+ `create table \`admin_roles\` (
1574
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
1575
+ \`key\` varchar(64) not null,
1576
+ \`name\` longtext not null,
1577
+ \`description\` longtext not null default (''),
1578
+ \`kind\` longtext not null default ('admin'),
1579
+ \`is_system\` tinyint(1) not null default 0,
1580
+ \`version\` int not null default 1,
1581
+ \`created_at\` datetime(6) not null default current_timestamp(6),
1582
+ \`updated_at\` datetime(6) not null default current_timestamp(6),
1583
+ unique key \`admin_roles_key_key\` (\`key\`),
1584
+ constraint \`admin_roles_pkey\` primary key (\`id\`),
1585
+ constraint \`admin_roles_key_check\` check ((regexp_like(\`key\`, '^[a-z][a-z0-9-]{0,62}$', 'c'))),
1586
+ constraint \`admin_roles_kind_check\` check ((\`kind\` in ('admin', 'delivery')))
1587
+ )`,
1588
+ `create table \`admin_role_permissions\` (
1589
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
1590
+ \`role_id\` char(36) character set ascii collate ascii_general_ci not null,
1591
+ \`action\` varchar(64) not null,
1592
+ \`model_id\` varchar(255),
1593
+ \`condition\` longtext,
1594
+ \`field_ids\` json,
1595
+ \`created_at\` datetime(6) not null default current_timestamp(6),
1596
+ \`updated_at\` datetime(6) not null default current_timestamp(6),
1597
+ unique key \`admin_role_permissions_grant_uq\` (\`role_id\`, \`action\`, (coalesce(\`model_id\`, ''))),
1598
+ constraint \`admin_role_permissions_pkey\` primary key (\`id\`),
1599
+ constraint \`admin_role_permissions_condition_check\` check ((\`condition\` = 'ownedByPrincipal'))
1600
+ )`,
1601
+ `create table \`admin_user_roles\` (
1602
+ \`admin_user_id\` char(36) character set ascii collate ascii_general_ci not null,
1603
+ \`role_id\` char(36) character set ascii collate ascii_general_ci not null,
1604
+ \`created_at\` datetime(6) not null default current_timestamp(6),
1605
+ \`updated_at\` datetime(6) not null default current_timestamp(6),
1606
+ \`site_id\` char(36) character set ascii collate ascii_general_ci,
1607
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
1608
+ unique key \`admin_user_roles_assignment_uq\` (\`admin_user_id\`, \`role_id\`, (coalesce(\`site_id\`, ''))),
1609
+ constraint \`admin_user_roles_pk\` primary key (\`id\`)
1610
+ )`,
1611
+ `create table \`admin_sessions\` (
1612
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
1613
+ \`admin_user_id\` char(36) character set ascii collate ascii_general_ci not null,
1614
+ \`token_hash\` varchar(128) not null,
1615
+ \`csrf_secret\` longtext not null,
1616
+ \`ip\` longtext,
1617
+ \`user_agent\` longtext,
1618
+ \`last_seen_at\` datetime(6) not null default current_timestamp(6),
1619
+ \`expires_at\` datetime(6) not null,
1620
+ \`revoked_at\` datetime(6),
1621
+ \`rotation_required\` tinyint(1) not null default 0,
1622
+ \`created_at\` datetime(6) not null default current_timestamp(6),
1623
+ \`updated_at\` datetime(6) not null default current_timestamp(6),
1624
+ unique key \`admin_sessions_token_hash_key\` (\`token_hash\`),
1625
+ constraint \`admin_sessions_pkey\` primary key (\`id\`)
1626
+ )`,
1627
+ `create table \`admin_invitations\` (
1628
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
1629
+ \`email\` varchar(255) not null,
1630
+ \`token_hash\` varchar(128),
1631
+ \`invited_by\` char(36) character set ascii collate ascii_general_ci,
1632
+ \`expires_at\` datetime(6) not null,
1633
+ \`accepted_at\` datetime(6),
1634
+ \`revoked_at\` datetime(6),
1635
+ \`created_at\` datetime(6) not null default current_timestamp(6),
1636
+ \`updated_at\` datetime(6) not null default current_timestamp(6),
1637
+ \`role_assignments\` json not null default (json_array()),
1638
+ unique key \`admin_invitations_token_hash_key\` (\`token_hash\`),
1639
+ constraint \`admin_invitations_pkey\` primary key (\`id\`)
1640
+ )`,
1641
+ `create table \`password_resets\` (
1642
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
1643
+ \`admin_user_id\` char(36) character set ascii collate ascii_general_ci not null,
1644
+ \`token_hash\` varchar(128),
1645
+ \`expires_at\` datetime(6) not null,
1646
+ \`used_at\` datetime(6),
1647
+ \`created_at\` datetime(6) not null default current_timestamp(6),
1648
+ \`updated_at\` datetime(6) not null default current_timestamp(6),
1649
+ unique key \`password_resets_token_hash_key\` (\`token_hash\`),
1650
+ constraint \`password_resets_pkey\` primary key (\`id\`)
1651
+ )`,
1652
+ `create table \`setup_tokens\` (
1653
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
1654
+ \`token_hash\` varchar(128) not null,
1655
+ \`used_at\` datetime(6),
1656
+ \`superseded_at\` datetime(6),
1657
+ \`created_at\` datetime(6) not null default current_timestamp(6),
1658
+ \`updated_at\` datetime(6) not null default current_timestamp(6),
1659
+ unique key \`setup_tokens_token_hash_key\` (\`token_hash\`),
1660
+ constraint \`setup_tokens_pkey\` primary key (\`id\`)
1661
+ )`,
1662
+ `create table \`api_tokens\` (
1663
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
1664
+ \`name\` longtext not null,
1665
+ \`token_hash\` varchar(128) not null,
1666
+ \`token_prefix\` longtext not null,
1667
+ \`role_id\` char(36) character set ascii collate ascii_general_ci not null,
1668
+ \`created_by\` char(36) character set ascii collate ascii_general_ci,
1669
+ \`expires_at\` datetime(6),
1670
+ \`last_used_at\` datetime(6),
1671
+ \`revoked_at\` datetime(6),
1672
+ \`created_at\` datetime(6) not null default current_timestamp(6),
1673
+ \`updated_at\` datetime(6) not null default current_timestamp(6),
1674
+ \`site_id\` char(36) character set ascii collate ascii_general_ci,
1675
+ unique key \`api_tokens_token_hash_key\` (\`token_hash\`),
1676
+ constraint \`api_tokens_pkey\` primary key (\`id\`)
1677
+ )`,
1678
+ `create table \`system_settings\` (
1679
+ \`key\` varchar(64) not null,
1680
+ \`value\` longtext not null,
1681
+ \`updated_at\` datetime(6) not null default current_timestamp(6),
1682
+ constraint \`system_settings_pkey\` primary key (\`key\`)
1683
+ )`,
1684
+ `create table \`models\` (
1685
+ \`id\` char(36) character set ascii collate ascii_general_ci not null,
1686
+ \`kind\` longtext not null,
1687
+ \`api_key\` varchar(255) not null,
1688
+ \`created_at\` datetime(6) not null default current_timestamp(6),
1689
+ \`updated_at\` datetime(6) not null default current_timestamp(6),
1690
+ \`deleted_at\` datetime(6),
1691
+ constraint \`models_pkey\` primary key (\`id\`),
1692
+ constraint \`models_kind_check\` check ((\`kind\` in ('collection', 'singleton', 'component')))
1693
+ )`,
1694
+ `create table \`schema_revisions\` (
1695
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
1696
+ \`model_id\` char(36) character set ascii collate ascii_general_ci not null,
1697
+ \`version\` int not null,
1698
+ \`definition\` json not null,
1699
+ \`hash\` longtext not null,
1700
+ \`parent_revision_id\` char(36) character set ascii collate ascii_general_ci,
1701
+ \`created_by_type\` longtext not null,
1702
+ \`created_by_id\` longtext,
1703
+ \`created_at\` datetime(6) not null default current_timestamp(6),
1704
+ unique key \`schema_revisions_model_version_uq\` (\`model_id\`, \`version\`),
1705
+ constraint \`schema_revisions_pkey\` primary key (\`id\`),
1706
+ constraint \`schema_revisions_version_check\` check ((\`version\` > 0))
1707
+ )`,
1708
+ `create table \`model_active_versions\` (
1709
+ \`model_id\` char(36) character set ascii collate ascii_general_ci not null,
1710
+ \`revision_id\` char(36) character set ascii collate ascii_general_ci not null,
1711
+ \`version\` int not null,
1712
+ \`schema_version\` int not null,
1713
+ \`activated_at\` datetime(6) not null default current_timestamp(6),
1714
+ constraint \`model_active_versions_pkey\` primary key (\`model_id\`)
1715
+ )`,
1716
+ `create table \`schema_change_jobs\` (
1717
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
1718
+ \`target_type\` varchar(64) not null,
1719
+ \`target_id\` varchar(255) not null,
1720
+ \`model_id\` char(36) character set ascii collate ascii_general_ci,
1721
+ \`from_revision_id\` char(36) character set ascii collate ascii_general_ci,
1722
+ \`to_revision_id\` char(36) character set ascii collate ascii_general_ci,
1723
+ \`status\` longtext not null default ('pending'),
1724
+ \`plan\` json not null default (json_object()),
1725
+ \`job_id\` char(36) character set ascii collate ascii_general_ci,
1726
+ \`error\` json,
1727
+ \`requested_by_type\` longtext not null,
1728
+ \`requested_by_id\` longtext,
1729
+ \`created_at\` datetime(6) not null default current_timestamp(6),
1730
+ \`updated_at\` datetime(6) not null default current_timestamp(6),
1731
+ \`finished_at\` datetime(6),
1732
+ \`change_set_id\` char(36) character set ascii collate ascii_general_ci,
1733
+ constraint \`schema_change_jobs_pkey\` primary key (\`id\`),
1734
+ constraint \`schema_change_jobs_status_check\` check ((\`status\` in ('pending', 'running', 'activated', 'failed', 'cancelled'))),
1735
+ constraint \`schema_change_jobs_target_type_check\` check ((\`target_type\` in ('model', 'locale')))
1736
+ )`,
1737
+ `create table \`locales\` (
1738
+ \`code\` varchar(64) not null,
1739
+ \`label\` longtext not null,
1740
+ \`is_default\` tinyint(1) not null default 0,
1741
+ \`fallbacks\` json not null default (json_array()),
1742
+ \`created_at\` datetime(6) not null default current_timestamp(6),
1743
+ \`updated_at\` datetime(6) not null default current_timestamp(6),
1744
+ constraint \`locales_pkey\` primary key (\`code\`)
1745
+ )`,
1746
+ `create table \`schema_settings\` (
1747
+ \`id\` tinyint(1) not null default 1,
1748
+ \`read_only\` tinyint(1) not null default 0,
1749
+ \`read_only_reason\` longtext,
1750
+ \`updated_by_type\` longtext,
1751
+ \`updated_by_id\` longtext,
1752
+ \`updated_at\` datetime(6) not null default current_timestamp(6),
1753
+ constraint \`schema_settings_pkey\` primary key (\`id\`),
1754
+ constraint \`schema_settings_id_check\` check (\`id\` = 1)
1755
+ )`,
1756
+ `create table \`entries\` (
1757
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
1758
+ \`model_id\` char(36) character set ascii collate ascii_general_ci not null,
1759
+ \`owner_app_user_id\` char(36) character set ascii collate ascii_general_ci,
1760
+ \`created_by_admin_id\` char(36) character set ascii collate ascii_general_ci,
1761
+ \`created_at\` datetime(6) not null default current_timestamp(6),
1762
+ \`updated_at\` datetime(6) not null default current_timestamp(6),
1763
+ \`deleted_at\` datetime(6),
1764
+ \`site_id\` char(36) character set ascii collate ascii_general_ci not null,
1765
+ unique key \`entries_id_site_uq\` (\`id\`, \`site_id\`),
1766
+ constraint \`entries_pkey\` primary key (\`id\`)
1767
+ )`,
1768
+ `create table \`content_revisions\` (
1769
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
1770
+ \`entry_id\` char(36) character set ascii collate ascii_general_ci not null,
1771
+ \`locale\` varchar(64) not null,
1772
+ \`schema_revision_id\` char(36) character set ascii collate ascii_general_ci not null,
1773
+ \`data\` json not null,
1774
+ \`reason\` longtext not null,
1775
+ \`author_type\` longtext not null,
1776
+ \`author_id\` longtext,
1777
+ \`parent_revision_id\` char(36) character set ascii collate ascii_general_ci,
1778
+ \`created_at\` datetime(6) not null default current_timestamp(6),
1779
+ constraint \`content_revisions_pkey\` primary key (\`id\`),
1780
+ constraint \`content_revisions_reason_check\` check ((\`reason\` in ('create', 'save', 'publish', 'restore', 'duplicate', 'localize', 'conversion')))
1781
+ )`,
1782
+ `create table \`entry_heads\` (
1783
+ \`entry_id\` char(36) character set ascii collate ascii_general_ci not null,
1784
+ \`model_id\` char(36) character set ascii collate ascii_general_ci not null,
1785
+ \`locale\` varchar(64) not null,
1786
+ \`state\` varchar(64) not null,
1787
+ \`revision_id\` char(36) character set ascii collate ascii_general_ci not null,
1788
+ \`data\` json not null,
1789
+ \`autosaved_at\` datetime(6),
1790
+ \`version\` int not null default 1,
1791
+ \`change_seq\` bigint not null,
1792
+ \`created_at\` datetime(6) not null default current_timestamp(6),
1793
+ \`updated_at\` datetime(6) not null default current_timestamp(6),
1794
+ \`site_id\` char(36) character set ascii collate ascii_general_ci not null,
1795
+ constraint \`entry_heads_pkey\` primary key (\`entry_id\`, \`locale\`, \`state\`),
1796
+ constraint \`entry_heads_state_check\` check ((\`state\` in ('draft', 'published')))
1797
+ )`,
1798
+ `create table \`relation_edges\` (
1799
+ \`source_entry_id\` char(36) character set ascii collate ascii_general_ci not null,
1800
+ \`locale\` varchar(64) not null,
1801
+ \`state\` varchar(64) not null,
1802
+ \`field_id\` char(36) character set ascii collate ascii_general_ci not null,
1803
+ \`position\` int not null,
1804
+ \`target_entry_id\` char(36) character set ascii collate ascii_general_ci not null,
1805
+ constraint \`relation_edges_pkey\` primary key (\`source_entry_id\`, \`locale\`, \`state\`, \`field_id\`, \`position\`)
1806
+ )`,
1807
+ `create table \`unique_values\` (
1808
+ \`field_id\` char(36) character set ascii collate ascii_general_ci not null,
1809
+ \`locale\` varchar(64) not null,
1810
+ \`state\` varchar(64) not null,
1811
+ \`value_hash\` varchar(128) not null,
1812
+ \`entry_id\` char(36) character set ascii collate ascii_general_ci not null,
1813
+ \`model_id\` char(36) character set ascii collate ascii_general_ci not null,
1814
+ \`site_id\` char(36) character set ascii collate ascii_general_ci not null,
1815
+ constraint \`unique_values_pkey\` primary key (\`site_id\`, \`field_id\`, \`locale\`, \`state\`, \`value_hash\`),
1816
+ constraint \`unique_values_state_check\` check ((\`state\` in ('draft', 'published')))
1817
+ )`,
1818
+ `create table \`publication_state\` (
1819
+ \`last_seq\` bigint not null default 0,
1820
+ \`site_id\` char(36) character set ascii collate ascii_general_ci not null,
1821
+ constraint \`publication_state_pkey\` primary key (\`site_id\`)
1822
+ )`,
1823
+ `create table \`publication_log\` (
1824
+ \`id\` bigint not null auto_increment,
1825
+ \`entry_id\` char(36) character set ascii collate ascii_general_ci not null,
1826
+ \`model_id\` char(36) character set ascii collate ascii_general_ci not null,
1827
+ \`locale\` varchar(64) not null,
1828
+ \`revision_id\` char(36) character set ascii collate ascii_general_ci not null,
1829
+ \`from_seq\` bigint not null,
1830
+ \`to_seq\` bigint,
1831
+ \`published_at\` datetime(6) not null default current_timestamp(6),
1832
+ \`site_id\` char(36) character set ascii collate ascii_general_ci not null,
1833
+ constraint \`publication_log_pkey\` primary key (\`id\`)
1834
+ )`,
1835
+ `create table \`media_folders\` (
1836
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
1837
+ \`parent_id\` char(36) character set ascii collate ascii_general_ci,
1838
+ \`name\` varchar(255) not null,
1839
+ \`created_by\` char(36) character set ascii collate ascii_general_ci,
1840
+ \`version\` int not null default 1,
1841
+ \`created_at\` datetime(6) not null default current_timestamp(6),
1842
+ \`updated_at\` datetime(6) not null default current_timestamp(6),
1843
+ \`site_id\` char(36) character set ascii collate ascii_general_ci not null,
1844
+ unique key \`media_folders_id_site_uq\` (\`id\`, \`site_id\`),
1845
+ constraint \`media_folders_pkey\` primary key (\`id\`),
1846
+ constraint \`media_folders_name_check\` check (((char_length(\`name\`) >= 1) AND (char_length(\`name\`) <= 255)))
1847
+ )`,
1848
+ `create table \`media_assets\` (
1849
+ \`id\` char(36) character set ascii collate ascii_general_ci not null,
1850
+ \`folder_id\` char(36) character set ascii collate ascii_general_ci,
1851
+ \`storage_driver\` varchar(64) not null,
1852
+ \`storage_key\` varchar(768) not null,
1853
+ \`original_filename\` longtext not null,
1854
+ \`mime_type\` longtext not null,
1855
+ \`size_bytes\` bigint not null,
1856
+ \`width\` int,
1857
+ \`height\` int,
1858
+ \`checksum_sha256\` longtext,
1859
+ \`alt\` longtext not null default (''),
1860
+ \`caption\` longtext not null default (''),
1861
+ \`focal_x\` float,
1862
+ \`focal_y\` float,
1863
+ \`visibility\` longtext not null default ('public'),
1864
+ \`status\` longtext not null default ('processing'),
1865
+ \`processing_error\` longtext,
1866
+ \`created_by\` char(36) character set ascii collate ascii_general_ci,
1867
+ \`version\` int not null default 1,
1868
+ \`deleted_at\` datetime(6),
1869
+ \`created_at\` datetime(6) not null default current_timestamp(6),
1870
+ \`updated_at\` datetime(6) not null default current_timestamp(6),
1871
+ \`site_id\` char(36) character set ascii collate ascii_general_ci not null,
1872
+ constraint \`media_assets_pkey\` primary key (\`id\`),
1873
+ constraint \`media_assets_focal_point_ck\` check (((\`focal_x\` IS NULL) = (\`focal_y\` IS NULL))),
1874
+ constraint \`media_assets_focal_x_check\` check (((\`focal_x\` >= (0)) AND (\`focal_x\` <= (1)))),
1875
+ constraint \`media_assets_focal_y_check\` check (((\`focal_y\` >= (0)) AND (\`focal_y\` <= (1)))),
1876
+ constraint \`media_assets_status_check\` check ((\`status\` in ('processing', 'ready', 'failed'))),
1877
+ constraint \`media_assets_storage_driver_check\` check ((\`storage_driver\` in ('local', 's3'))),
1878
+ constraint \`media_assets_visibility_check\` check ((\`visibility\` in ('public', 'private')))
1879
+ )`,
1880
+ `create table \`media_variants\` (
1881
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
1882
+ \`asset_id\` char(36) character set ascii collate ascii_general_ci not null,
1883
+ \`name\` varchar(255) not null,
1884
+ \`width\` int,
1885
+ \`height\` int,
1886
+ \`format\` longtext not null,
1887
+ \`mime_type\` longtext not null,
1888
+ \`storage_key\` varchar(768),
1889
+ \`size_bytes\` bigint,
1890
+ \`checksum_sha256\` longtext,
1891
+ \`status\` longtext not null default ('pending'),
1892
+ \`error\` longtext,
1893
+ \`created_at\` datetime(6) not null default current_timestamp(6),
1894
+ \`updated_at\` datetime(6) not null default current_timestamp(6),
1895
+ unique key \`media_variants_asset_name_uq\` (\`asset_id\`, \`name\`),
1896
+ constraint \`media_variants_pkey\` primary key (\`id\`),
1897
+ constraint \`media_variants_status_check\` check ((\`status\` in ('pending', 'ready', 'failed')))
1898
+ )`,
1899
+ `create table \`media_references\` (
1900
+ \`asset_id\` char(36) character set ascii collate ascii_general_ci not null,
1901
+ \`entry_id\` char(36) character set ascii collate ascii_general_ci not null,
1902
+ \`model_id\` char(36) character set ascii collate ascii_general_ci not null,
1903
+ \`field_id\` varchar(255) not null,
1904
+ \`locale\` varchar(64) not null,
1905
+ \`state\` varchar(64) not null,
1906
+ \`created_at\` datetime(6) not null default current_timestamp(6),
1907
+ constraint \`media_references_pk\` primary key (\`asset_id\`, \`entry_id\`, \`locale\`, \`state\`, \`field_id\`),
1908
+ constraint \`media_references_state_check\` check ((\`state\` in ('draft', 'published')))
1909
+ )`,
1910
+ `create table \`media_upload_grants\` (
1911
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
1912
+ \`asset_id\` char(36) character set ascii collate ascii_general_ci not null,
1913
+ \`kind\` longtext not null,
1914
+ \`storage_driver\` longtext not null,
1915
+ \`storage_key\` varchar(768) not null,
1916
+ \`original_filename\` longtext not null,
1917
+ \`declared_mime_type\` longtext not null,
1918
+ \`max_size_bytes\` bigint not null,
1919
+ \`folder_id\` char(36) character set ascii collate ascii_general_ci,
1920
+ \`visibility\` longtext not null,
1921
+ \`created_by\` char(36) character set ascii collate ascii_general_ci,
1922
+ \`created_by_principal\` longtext not null,
1923
+ \`status\` longtext not null default ('pending'),
1924
+ \`expires_at\` datetime(6) not null,
1925
+ \`consumed_at\` datetime(6),
1926
+ \`created_at\` datetime(6) not null default current_timestamp(6),
1927
+ \`site_id\` char(36) character set ascii collate ascii_general_ci not null,
1928
+ unique key \`media_upload_grants_storage_key_key\` (\`storage_key\`),
1929
+ constraint \`media_upload_grants_pkey\` primary key (\`id\`),
1930
+ constraint \`media_upload_grants_kind_check\` check ((\`kind\` in ('create', 'replace'))),
1931
+ constraint \`media_upload_grants_status_check\` check ((\`status\` in ('pending', 'consumed', 'rejected', 'expired'))),
1932
+ constraint \`media_upload_grants_storage_driver_check\` check ((\`storage_driver\` in ('local', 's3'))),
1933
+ constraint \`media_upload_grants_visibility_check\` check ((\`visibility\` in ('public', 'private')))
1934
+ )`,
1935
+ `create table \`scheduled_publications\` (
1936
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
1937
+ \`entry_id\` char(36) character set ascii collate ascii_general_ci not null,
1938
+ \`model_id\` char(36) character set ascii collate ascii_general_ci not null,
1939
+ \`locale\` varchar(64) not null,
1940
+ \`action\` longtext not null,
1941
+ \`run_at\` datetime(6) not null,
1942
+ \`status\` varchar(64) not null default 'scheduled',
1943
+ \`created_by_token\` char(36) character set ascii collate ascii_general_ci,
1944
+ \`job_id\` char(36) character set ascii collate ascii_general_ci,
1945
+ \`snapshot_seq\` bigint,
1946
+ \`error\` longtext,
1947
+ \`executed_at\` datetime(6),
1948
+ \`created_by\` char(36) character set ascii collate ascii_general_ci,
1949
+ \`created_at\` datetime(6) not null default current_timestamp(6),
1950
+ \`updated_at\` datetime(6) not null default current_timestamp(6),
1951
+ \`site_id\` char(36) character set ascii collate ascii_general_ci not null,
1952
+ constraint \`scheduled_publications_pkey\` primary key (\`id\`),
1953
+ constraint \`scheduled_publications_action_check\` check ((\`action\` in ('publish', 'unpublish'))),
1954
+ constraint \`scheduled_publications_status_check\` check ((\`status\` in ('scheduled', 'done', 'failed', 'cancelled')))
1955
+ )`,
1956
+ `create table \`webhooks\` (
1957
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
1958
+ \`name\` longtext not null,
1959
+ \`url\` longtext not null,
1960
+ \`events\` json not null default (json_array()),
1961
+ \`enabled\` tinyint(1) not null default 1,
1962
+ \`secret_encrypted\` longtext not null,
1963
+ \`allow_private_network\` tinyint(1) not null default 0,
1964
+ \`max_attempts\` int not null default 8,
1965
+ \`version\` int not null default 1,
1966
+ \`created_by\` char(36) character set ascii collate ascii_general_ci,
1967
+ \`created_at\` datetime(6) not null default current_timestamp(6),
1968
+ \`updated_at\` datetime(6) not null default current_timestamp(6),
1969
+ \`site_id\` char(36) character set ascii collate ascii_general_ci,
1970
+ constraint \`webhooks_pkey\` primary key (\`id\`),
1971
+ constraint \`webhooks_max_attempts_check\` check (((\`max_attempts\` >= 1) AND (\`max_attempts\` <= 20))),
1972
+ constraint \`webhooks_name_check\` check (((char_length(\`name\`) >= 1) AND (char_length(\`name\`) <= 200)))
1973
+ )`,
1974
+ `create table \`webhook_deliveries\` (
1975
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
1976
+ \`webhook_id\` char(36) character set ascii collate ascii_general_ci not null,
1977
+ \`event_id\` char(36) character set ascii collate ascii_general_ci,
1978
+ \`event_type\` longtext not null,
1979
+ \`payload\` json not null,
1980
+ \`is_test\` tinyint(1) not null default 0,
1981
+ \`status\` longtext not null default ('pending'),
1982
+ \`attempts\` int not null default 0,
1983
+ \`last_response_status\` int,
1984
+ \`last_error\` longtext,
1985
+ \`attempt_log\` json not null default (json_array()),
1986
+ \`job_id\` char(36) character set ascii collate ascii_general_ci,
1987
+ \`delivered_at\` datetime(6),
1988
+ \`created_at\` datetime(6) not null default current_timestamp(6),
1989
+ \`updated_at\` datetime(6) not null default current_timestamp(6),
1990
+ constraint \`webhook_deliveries_pkey\` primary key (\`id\`),
1991
+ constraint \`webhook_deliveries_status_check\` check ((\`status\` in ('pending', 'retrying', 'succeeded', 'dead')))
1992
+ )`,
1993
+ `create table \`deployment_connections\` (
1994
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
1995
+ \`name\` longtext not null,
1996
+ \`provider\` longtext not null,
1997
+ \`settings\` json not null default (json_object()),
1998
+ \`secrets_encrypted\` longtext not null,
1999
+ \`secret_env_refs\` json not null default (json_object()),
2000
+ \`delivery_role_id\` char(36) character set ascii collate ascii_general_ci,
2001
+ \`preview_url_template\` longtext,
2002
+ \`trigger_policy\` json not null default (json_array()),
2003
+ \`debounce_seconds\` int not null default 10,
2004
+ \`allow_private_network\` tinyint(1) not null default 0,
2005
+ \`enabled\` tinyint(1) not null default 1,
2006
+ \`version\` int not null default 1,
2007
+ \`created_by\` char(36) character set ascii collate ascii_general_ci,
2008
+ \`created_at\` datetime(6) not null default current_timestamp(6),
2009
+ \`updated_at\` datetime(6) not null default current_timestamp(6),
2010
+ \`site_id\` char(36) character set ascii collate ascii_general_ci not null,
2011
+ constraint \`deployment_connections_pkey\` primary key (\`id\`),
2012
+ constraint \`deployment_connections_debounce_seconds_check\` check (((\`debounce_seconds\` >= 0) AND (\`debounce_seconds\` <= 3600))),
2013
+ constraint \`deployment_connections_name_check\` check (((char_length(\`name\`) >= 1) AND (char_length(\`name\`) <= 200))),
2014
+ constraint \`deployment_connections_provider_check\` check ((\`provider\` in ('generic_webhook', 'cloudflare_pages', 'vercel', 'netlify', 'github')))
2015
+ )`,
2016
+ `create table \`deployment_runs\` (
2017
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
2018
+ \`connection_id\` char(36) character set ascii collate ascii_general_ci not null,
2019
+ \`status\` longtext not null default ('queued'),
2020
+ \`status_rank\` int not null default 0,
2021
+ \`trigger\` longtext not null,
2022
+ \`snapshot_seq\` bigint,
2023
+ \`schema_version\` int,
2024
+ \`retry_of\` char(36) character set ascii collate ascii_general_ci,
2025
+ \`provider_ref\` longtext,
2026
+ \`log_url\` longtext,
2027
+ \`site_url\` longtext,
2028
+ \`error\` longtext,
2029
+ \`timeline\` json not null default (json_array()),
2030
+ \`job_id\` char(36) character set ascii collate ascii_general_ci,
2031
+ \`triggered_at\` datetime(6),
2032
+ \`finished_at\` datetime(6),
2033
+ \`created_by\` char(36) character set ascii collate ascii_general_ci,
2034
+ \`created_at\` datetime(6) not null default current_timestamp(6),
2035
+ \`updated_at\` datetime(6) not null default current_timestamp(6),
2036
+ \`change_set_id\` char(36) character set ascii collate ascii_general_ci,
2037
+ constraint \`deployment_runs_pkey\` primary key (\`id\`),
2038
+ constraint \`deployment_runs_status_check\` check ((\`status\` in ('queued', 'triggered', 'building', 'deployed', 'failed', 'unknown'))),
2039
+ constraint \`deployment_runs_trigger_check\` check ((\`trigger\` in ('publish', 'change_set', 'schema', 'manual', 'retry')))
2040
+ )`,
2041
+ `create table \`preview_tokens\` (
2042
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
2043
+ \`token_hash\` varchar(128) not null,
2044
+ \`token_prefix\` longtext not null,
2045
+ \`model_id\` char(36) character set ascii collate ascii_general_ci not null,
2046
+ \`entry_id\` char(36) character set ascii collate ascii_general_ci not null,
2047
+ \`locale\` longtext,
2048
+ \`connection_id\` char(36) character set ascii collate ascii_general_ci,
2049
+ \`delivery_role_id\` char(36) character set ascii collate ascii_general_ci,
2050
+ \`created_by\` char(36) character set ascii collate ascii_general_ci not null,
2051
+ \`expires_at\` datetime(6) not null,
2052
+ \`revoked_at\` datetime(6),
2053
+ \`last_used_at\` datetime(6),
2054
+ \`created_at\` datetime(6) not null default current_timestamp(6),
2055
+ \`site_id\` char(36) character set ascii collate ascii_general_ci not null,
2056
+ unique key \`preview_tokens_token_hash_key\` (\`token_hash\`),
2057
+ constraint \`preview_tokens_pkey\` primary key (\`id\`)
2058
+ )`,
2059
+ `create table \`app_users\` (
2060
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
2061
+ \`email\` varchar(255) not null,
2062
+ \`name\` longtext not null default (''),
2063
+ \`password_hash\` longtext,
2064
+ \`confirmed_at\` datetime(6),
2065
+ \`blocked_at\` datetime(6),
2066
+ \`last_login_at\` datetime(6),
2067
+ \`password_changed_at\` datetime(6),
2068
+ \`deleted_at\` datetime(6),
2069
+ \`created_at\` datetime(6) not null default current_timestamp(6),
2070
+ \`updated_at\` datetime(6) not null default current_timestamp(6),
2071
+ \`token_version\` int not null default 0,
2072
+ \`site_id\` char(36) character set ascii collate ascii_general_ci not null,
2073
+ constraint \`app_users_pkey\` primary key (\`id\`)
2074
+ )`,
2075
+ `create table \`app_roles\` (
2076
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
2077
+ \`key\` varchar(64) not null,
2078
+ \`name\` longtext not null,
2079
+ \`description\` longtext not null default (''),
2080
+ \`is_system\` tinyint(1) not null default 0,
2081
+ \`version\` int not null default 1,
2082
+ \`created_at\` datetime(6) not null default current_timestamp(6),
2083
+ \`updated_at\` datetime(6) not null default current_timestamp(6),
2084
+ unique key \`app_roles_key_key\` (\`key\`),
2085
+ constraint \`app_roles_pkey\` primary key (\`id\`),
2086
+ constraint \`app_roles_key_check\` check ((regexp_like(\`key\`, '^[a-z][a-z0-9-]{0,62}$', 'c')))
2087
+ )`,
2088
+ `create table \`app_role_permissions\` (
2089
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
2090
+ \`role_id\` char(36) character set ascii collate ascii_general_ci not null,
2091
+ \`action\` varchar(64) not null,
2092
+ \`model_id\` varchar(255),
2093
+ \`condition\` longtext,
2094
+ \`field_ids\` json,
2095
+ \`created_at\` datetime(6) not null default current_timestamp(6),
2096
+ \`updated_at\` datetime(6) not null default current_timestamp(6),
2097
+ unique key \`app_role_permissions_grant_uq\` (\`role_id\`, \`action\`, (coalesce(\`model_id\`, ''))),
2098
+ constraint \`app_role_permissions_pkey\` primary key (\`id\`),
2099
+ constraint \`app_role_permissions_action_check\` check ((\`action\` in ('read', 'create', 'update', 'delete', 'publish'))),
2100
+ constraint \`app_role_permissions_condition_check\` check ((\`condition\` = 'ownedByPrincipal'))
2101
+ )`,
2102
+ `create table \`app_user_roles\` (
2103
+ \`app_user_id\` char(36) character set ascii collate ascii_general_ci not null,
2104
+ \`role_id\` char(36) character set ascii collate ascii_general_ci not null,
2105
+ \`created_at\` datetime(6) not null default current_timestamp(6),
2106
+ \`site_id\` char(36) character set ascii collate ascii_general_ci not null,
2107
+ constraint \`app_user_roles_pk\` primary key (\`app_user_id\`, \`role_id\`)
2108
+ )`,
2109
+ `create table \`app_refresh_tokens\` (
2110
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
2111
+ \`app_user_id\` char(36) character set ascii collate ascii_general_ci not null,
2112
+ \`family_id\` char(36) character set ascii collate ascii_general_ci not null,
2113
+ \`token_hash\` varchar(128) not null,
2114
+ \`expires_at\` datetime(6) not null,
2115
+ \`used_at\` datetime(6),
2116
+ \`reuse_grace_used_at\` datetime(6),
2117
+ \`revoked_at\` datetime(6),
2118
+ \`revoked_reason\` longtext,
2119
+ \`ip\` longtext,
2120
+ \`user_agent\` longtext,
2121
+ \`created_at\` datetime(6) not null default current_timestamp(6),
2122
+ unique key \`app_refresh_tokens_token_hash_key\` (\`token_hash\`),
2123
+ constraint \`app_refresh_tokens_pkey\` primary key (\`id\`)
2124
+ )`,
2125
+ `create table \`app_oauth_accounts\` (
2126
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
2127
+ \`app_user_id\` char(36) character set ascii collate ascii_general_ci not null,
2128
+ \`provider\` varchar(64) not null,
2129
+ \`provider_user_id\` varchar(255) not null,
2130
+ \`email\` longtext,
2131
+ \`created_at\` datetime(6) not null default current_timestamp(6),
2132
+ \`updated_at\` datetime(6) not null default current_timestamp(6),
2133
+ \`site_id\` char(36) character set ascii collate ascii_general_ci not null,
2134
+ unique key \`app_oauth_accounts_identity_uq\` (\`site_id\`, \`provider\`, \`provider_user_id\`),
2135
+ constraint \`app_oauth_accounts_pkey\` primary key (\`id\`),
2136
+ constraint \`app_oauth_accounts_provider_check\` check ((\`provider\` in ('google', 'github')))
2137
+ )`,
2138
+ `create table \`email_confirmations\` (
2139
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
2140
+ \`app_user_id\` char(36) character set ascii collate ascii_general_ci not null,
2141
+ \`email\` longtext not null,
2142
+ \`token_hash\` varchar(128),
2143
+ \`expires_at\` datetime(6) not null,
2144
+ \`used_at\` datetime(6),
2145
+ \`created_at\` datetime(6) not null default current_timestamp(6),
2146
+ \`updated_at\` datetime(6) not null default current_timestamp(6),
2147
+ unique key \`email_confirmations_token_hash_key\` (\`token_hash\`),
2148
+ constraint \`email_confirmations_pkey\` primary key (\`id\`)
2149
+ )`,
2150
+ `create table \`app_password_resets\` (
2151
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
2152
+ \`app_user_id\` char(36) character set ascii collate ascii_general_ci not null,
2153
+ \`email\` longtext not null,
2154
+ \`token_hash\` varchar(128),
2155
+ \`expires_at\` datetime(6) not null,
2156
+ \`used_at\` datetime(6),
2157
+ \`created_at\` datetime(6) not null default current_timestamp(6),
2158
+ \`updated_at\` datetime(6) not null default current_timestamp(6),
2159
+ unique key \`app_password_resets_token_hash_key\` (\`token_hash\`),
2160
+ constraint \`app_password_resets_pkey\` primary key (\`id\`)
2161
+ )`,
2162
+ `create table \`app_login_codes\` (
2163
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
2164
+ \`app_user_id\` char(36) character set ascii collate ascii_general_ci not null,
2165
+ \`code_hash\` varchar(128) not null,
2166
+ \`expires_at\` datetime(6) not null,
2167
+ \`used_at\` datetime(6),
2168
+ \`created_at\` datetime(6) not null default current_timestamp(6),
2169
+ \`code_challenge\` longtext not null,
2170
+ unique key \`app_login_codes_code_hash_key\` (\`code_hash\`),
2171
+ constraint \`app_login_codes_pkey\` primary key (\`id\`)
2172
+ )`,
2173
+ `create table \`extension_hook_runs\` (
2174
+ \`event_id\` char(36) character set ascii collate ascii_general_ci not null,
2175
+ \`hook\` varchar(255) not null,
2176
+ \`job_id\` char(36) character set ascii collate ascii_general_ci,
2177
+ \`completed_at\` datetime(6) not null default current_timestamp(6),
2178
+ constraint \`extension_hook_runs_pkey\` primary key (\`event_id\`, \`hook\`)
2179
+ )`,
2180
+ `create table \`field_reads\` (
2181
+ \`day\` date not null,
2182
+ \`model_id\` char(36) character set ascii collate ascii_general_ci not null,
2183
+ \`field_path\` varchar(400) not null,
2184
+ \`principal_key\` varchar(255) not null,
2185
+ \`selection\` varchar(64) not null,
2186
+ \`reads\` bigint not null default 0,
2187
+ \`last_read_at\` datetime(6) not null,
2188
+ \`site_id\` char(36) character set ascii collate ascii_general_ci not null,
2189
+ constraint \`field_reads_pkey\` primary key (\`day\`, \`site_id\`, \`model_id\`, \`field_path\`, \`principal_key\`, \`selection\`),
2190
+ constraint \`field_reads_selection_check\` check ((\`selection\` in ('explicit', 'implicit')))
2191
+ )`,
2192
+ `create table \`token_reads\` (
2193
+ \`day\` date not null,
2194
+ \`principal_key\` varchar(255) not null,
2195
+ \`requests\` bigint not null default 0,
2196
+ \`last_snapshot\` bigint,
2197
+ \`last_read_at\` datetime(6) not null,
2198
+ \`site_id\` char(36) character set ascii collate ascii_general_ci not null,
2199
+ constraint \`token_reads_pkey\` primary key (\`day\`, \`site_id\`, \`principal_key\`)
2200
+ )`,
2201
+ `create table \`change_sets\` (
2202
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
2203
+ \`title\` longtext not null,
2204
+ \`description\` longtext not null default (''),
2205
+ \`status\` varchar(64) not null default 'open',
2206
+ \`ship_phase\` longtext,
2207
+ \`source\` longtext not null default ('manual'),
2208
+ \`restore_of_seq\` bigint,
2209
+ \`scheduled_for\` datetime(6),
2210
+ \`schedule_job_id\` char(36) character set ascii collate ascii_general_ci,
2211
+ \`scheduled_by\` char(36) character set ascii collate ascii_general_ci,
2212
+ \`scheduled_by_token\` char(36) character set ascii collate ascii_general_ci,
2213
+ \`ship_job_id\` char(36) character set ascii collate ascii_general_ci,
2214
+ \`ship_mode\` longtext,
2215
+ \`ship_requested_by\` char(36) character set ascii collate ascii_general_ci,
2216
+ \`ship_requested_by_token\` char(36) character set ascii collate ascii_general_ci,
2217
+ \`acknowledge_breaking\` tinyint(1) not null default 0,
2218
+ \`acknowledge_destructive\` tinyint(1) not null default 0,
2219
+ \`deployment_connection_id\` char(36) character set ascii collate ascii_general_ci,
2220
+ \`deployment_run_id\` char(36) character set ascii collate ascii_general_ci,
2221
+ \`shipped_at\` datetime(6),
2222
+ \`shipped_seq\` bigint,
2223
+ \`schema_version_after\` int,
2224
+ \`error\` json,
2225
+ \`restore_report\` json not null default (json_array()),
2226
+ \`created_by\` char(36) character set ascii collate ascii_general_ci,
2227
+ \`created_by_token\` char(36) character set ascii collate ascii_general_ci,
2228
+ \`version\` int not null default 1,
2229
+ \`created_at\` datetime(6) not null default current_timestamp(6),
2230
+ \`updated_at\` datetime(6) not null default current_timestamp(6),
2231
+ \`site_id\` char(36) character set ascii collate ascii_general_ci not null,
2232
+ unique key \`change_sets_id_site_uq\` (\`id\`, \`site_id\`),
2233
+ constraint \`change_sets_pkey\` primary key (\`id\`),
2234
+ constraint \`change_sets_ship_mode_check\` check ((\`ship_mode\` in ('interactive', 'scheduled'))),
2235
+ constraint \`change_sets_ship_phase_check\` check ((\`ship_phase\` in ('preparing', 'activating'))),
2236
+ constraint \`change_sets_source_check\` check ((\`source\` in ('manual', 'release', 'restore', 'builder', 'assist'))),
2237
+ constraint \`change_sets_status_check\` check ((\`status\` in ('open', 'scheduled', 'shipping', 'shipped', 'failed', 'discarded'))),
2238
+ constraint \`change_sets_title_check\` check (((char_length(\`title\`) >= 1) AND (char_length(\`title\`) <= 200)))
2239
+ )`,
2240
+ `create table \`schema_drafts\` (
2241
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
2242
+ \`change_set_id\` char(36) character set ascii collate ascii_general_ci not null,
2243
+ \`definition_id\` char(36) character set ascii collate ascii_general_ci not null,
2244
+ \`kind\` longtext not null,
2245
+ \`api_key\` longtext not null,
2246
+ \`base_version\` int,
2247
+ \`definition\` json,
2248
+ \`version\` int not null default 1,
2249
+ \`updated_by_type\` longtext not null,
2250
+ \`updated_by_id\` longtext,
2251
+ \`created_at\` datetime(6) not null default current_timestamp(6),
2252
+ \`updated_at\` datetime(6) not null default current_timestamp(6),
2253
+ unique key \`schema_drafts_set_definition_uq\` (\`change_set_id\`, \`definition_id\`),
2254
+ constraint \`schema_drafts_pkey\` primary key (\`id\`),
2255
+ constraint \`schema_drafts_kind_check\` check ((\`kind\` in ('collection', 'singleton', 'component')))
2256
+ )`,
2257
+ `create table \`change_set_items\` (
2258
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
2259
+ \`change_set_id\` char(36) character set ascii collate ascii_general_ci not null,
2260
+ \`kind\` longtext not null,
2261
+ \`position\` int not null,
2262
+ \`entry_id\` char(36) character set ascii collate ascii_general_ci,
2263
+ \`model_id\` char(36) character set ascii collate ascii_general_ci,
2264
+ \`locale\` varchar(64),
2265
+ \`action\` longtext,
2266
+ \`source_revision_id\` char(36) character set ascii collate ascii_general_ci,
2267
+ \`schema_draft_id\` char(36) character set ascii collate ascii_general_ci,
2268
+ \`status\` longtext not null default ('pending'),
2269
+ \`error\` longtext,
2270
+ \`ship_state\` json,
2271
+ \`created_at\` datetime(6) not null default current_timestamp(6),
2272
+ \`site_id\` char(36) character set ascii collate ascii_general_ci not null,
2273
+ constraint \`change_set_items_pkey\` primary key (\`id\`),
2274
+ constraint \`change_set_items_action_check\` check ((\`action\` in ('publish', 'unpublish'))),
2275
+ constraint \`change_set_items_kind_check\` check ((\`kind\` in ('entry', 'schema'))),
2276
+ constraint \`change_set_items_shape_check\` check ((((\`kind\` = 'entry') AND (\`entry_id\` IS NOT NULL) AND (\`model_id\` IS NOT NULL) AND (\`locale\` IS NOT NULL) AND (\`action\` IS NOT NULL) AND (\`schema_draft_id\` IS NULL)) OR ((\`kind\` = 'schema') AND (\`schema_draft_id\` IS NOT NULL) AND (\`entry_id\` IS NULL) AND (\`source_revision_id\` IS NULL)))),
2277
+ constraint \`change_set_items_status_check\` check ((\`status\` in ('pending', 'done', 'failed')))
2278
+ )`,
2279
+ `create table \`publication_snapshots\` (
2280
+ \`seq\` bigint not null,
2281
+ \`schema_version\` int,
2282
+ \`source\` longtext not null,
2283
+ \`change_set_id\` char(36) character set ascii collate ascii_general_ci,
2284
+ \`actor_type\` longtext,
2285
+ \`actor_id\` longtext,
2286
+ \`created_at\` datetime(6) not null default current_timestamp(6),
2287
+ \`site_id\` char(36) character set ascii collate ascii_general_ci not null,
2288
+ constraint \`publication_snapshots_pkey\` primary key (\`site_id\`, \`seq\`)
2289
+ )`,
2290
+ `create table \`editor_presence\` (
2291
+ \`entry_id\` char(36) character set ascii collate ascii_general_ci not null,
2292
+ \`model_id\` char(36) character set ascii collate ascii_general_ci not null,
2293
+ \`admin_user_id\` char(36) character set ascii collate ascii_general_ci not null,
2294
+ \`tab_id\` varchar(64) not null,
2295
+ \`locale\` longtext,
2296
+ \`started_at\` datetime(6) not null default current_timestamp(6),
2297
+ \`last_seen_at\` datetime(6) not null default current_timestamp(6),
2298
+ constraint \`editor_presence_pkey\` primary key (\`entry_id\`, \`admin_user_id\`, \`tab_id\`),
2299
+ constraint \`editor_presence_tab_id_check\` check (((char_length(\`tab_id\`) >= 1) AND (char_length(\`tab_id\`) <= 64)))
2300
+ )`,
2301
+ `create table \`content_health_findings\` (
2302
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
2303
+ \`entry_id\` char(36) character set ascii collate ascii_general_ci not null,
2304
+ \`model_id\` char(36) character set ascii collate ascii_general_ci not null,
2305
+ \`locale\` varchar(64) not null,
2306
+ \`rule\` varchar(255) not null,
2307
+ \`subject\` varchar(255) not null default '',
2308
+ \`severity\` longtext not null,
2309
+ \`path\` longtext,
2310
+ \`details\` json not null default (json_object()),
2311
+ \`first_seen_at\` datetime(6) not null default current_timestamp(6),
2312
+ \`last_seen_at\` datetime(6) not null default current_timestamp(6),
2313
+ \`resolved_at\` datetime(6),
2314
+ \`site_id\` char(36) character set ascii collate ascii_general_ci not null,
2315
+ unique key \`content_health_findings_entry_id_locale_rule_subject_key\` (\`entry_id\`, \`locale\`, \`rule\`, \`subject\`),
2316
+ constraint \`content_health_findings_pkey\` primary key (\`id\`),
2317
+ constraint \`content_health_findings_severity_check\` check ((\`severity\` in ('error', 'warning')))
2318
+ )`,
2319
+ `create table \`sites\` (
2320
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
2321
+ \`key\` varchar(64) not null,
2322
+ \`name\` longtext not null,
2323
+ \`is_primary\` tinyint(1) not null default 0,
2324
+ \`version\` int not null default 1,
2325
+ \`created_at\` datetime(6) not null default current_timestamp(6),
2326
+ \`updated_at\` datetime(6) not null default current_timestamp(6),
2327
+ unique key \`sites_key_key\` (\`key\`),
2328
+ constraint \`sites_pkey\` primary key (\`id\`),
2329
+ constraint \`sites_key_check\` check ((regexp_like(\`key\`, '^[a-z][a-z0-9-]{0,62}$', 'c'))),
2330
+ constraint \`sites_name_check\` check (((char_length(\`name\`) >= 1) AND (char_length(\`name\`) <= 200)))
2331
+ )`,
2332
+ `create table \`site_app_roles\` (
2333
+ \`site_id\` char(36) character set ascii collate ascii_general_ci not null,
2334
+ \`audience\` varchar(64) not null,
2335
+ \`role_id\` char(36) character set ascii collate ascii_general_ci not null,
2336
+ \`created_at\` datetime(6) not null default current_timestamp(6),
2337
+ constraint \`site_app_roles_pkey\` primary key (\`site_id\`, \`audience\`, \`role_id\`),
2338
+ constraint \`site_app_roles_audience_check\` check ((\`audience\` in ('public', 'authenticated')))
2339
+ )`,
2340
+ `create table \`assist_runs\` (
2341
+ \`id\` char(36) character set ascii collate ascii_general_ci not null default (uuid()),
2342
+ \`site_id\` char(36) character set ascii collate ascii_general_ci not null,
2343
+ \`actor_type\` longtext not null,
2344
+ \`actor_id\` longtext not null,
2345
+ \`action\` longtext not null,
2346
+ \`rule\` longtext,
2347
+ \`provider\` longtext not null,
2348
+ \`model\` longtext not null,
2349
+ \`input_tokens\` int not null default 0,
2350
+ \`output_tokens\` int not null default 0,
2351
+ \`duration_ms\` int not null default 0,
2352
+ \`status\` longtext not null,
2353
+ \`error_code\` longtext,
2354
+ \`job_id\` char(36) character set ascii collate ascii_general_ci,
2355
+ \`created_at\` datetime(6) not null default current_timestamp(6),
2356
+ \`finished_at\` datetime(6),
2357
+ constraint \`assist_runs_pkey\` primary key (\`id\`),
2358
+ constraint \`assist_runs_action_check\` check ((\`action\` in ('alt_text', 'summarize', 'translate', 'rewrite', 'schema_draft', 'content_ops'))),
2359
+ constraint \`assist_runs_status_check\` check ((\`status\` in ('queued', 'running', 'succeeded', 'failed')))
2360
+ )`,
2361
+ `alter table \`outbox_events\` add constraint \`outbox_events_site_fk\` foreign key (\`site_id\`) references \`sites\` (\`id\`) on delete set null`,
2362
+ `alter table \`audit_events\` add constraint \`audit_events_site_fk\` foreign key (\`site_id\`) references \`sites\` (\`id\`) on delete set null`,
2363
+ `alter table \`admin_role_permissions\` add constraint \`admin_role_permissions_role_id_fkey\` foreign key (\`role_id\`) references \`admin_roles\` (\`id\`) on delete cascade`,
2364
+ `alter table \`admin_user_roles\` add constraint \`admin_user_roles_admin_user_id_fkey\` foreign key (\`admin_user_id\`) references \`admin_users\` (\`id\`) on delete cascade`,
2365
+ `alter table \`admin_user_roles\` add constraint \`admin_user_roles_role_id_fkey\` foreign key (\`role_id\`) references \`admin_roles\` (\`id\`) on delete restrict`,
2366
+ `alter table \`admin_user_roles\` add constraint \`admin_user_roles_site_fk\` foreign key (\`site_id\`) references \`sites\` (\`id\`) on delete cascade`,
2367
+ `alter table \`admin_sessions\` add constraint \`admin_sessions_admin_user_id_fkey\` foreign key (\`admin_user_id\`) references \`admin_users\` (\`id\`) on delete cascade`,
2368
+ `alter table \`admin_invitations\` add constraint \`admin_invitations_invited_by_fkey\` foreign key (\`invited_by\`) references \`admin_users\` (\`id\`) on delete set null`,
2369
+ `alter table \`password_resets\` add constraint \`password_resets_admin_user_id_fkey\` foreign key (\`admin_user_id\`) references \`admin_users\` (\`id\`) on delete cascade`,
2370
+ `alter table \`api_tokens\` add constraint \`api_tokens_created_by_fkey\` foreign key (\`created_by\`) references \`admin_users\` (\`id\`) on delete set null`,
2371
+ `alter table \`api_tokens\` add constraint \`api_tokens_role_id_fkey\` foreign key (\`role_id\`) references \`admin_roles\` (\`id\`) on delete restrict`,
2372
+ `alter table \`api_tokens\` add constraint \`api_tokens_site_fk\` foreign key (\`site_id\`) references \`sites\` (\`id\`) on delete cascade`,
2373
+ `alter table \`schema_revisions\` add constraint \`schema_revisions_model_id_fkey\` foreign key (\`model_id\`) references \`models\` (\`id\`) on delete restrict`,
2374
+ `alter table \`schema_revisions\` add constraint \`schema_revisions_parent_revision_id_fkey\` foreign key (\`parent_revision_id\`) references \`schema_revisions\` (\`id\`)`,
2375
+ `alter table \`model_active_versions\` add constraint \`model_active_versions_model_id_fkey\` foreign key (\`model_id\`) references \`models\` (\`id\`) on delete cascade`,
2376
+ `alter table \`model_active_versions\` add constraint \`model_active_versions_revision_id_fkey\` foreign key (\`revision_id\`) references \`schema_revisions\` (\`id\`)`,
2377
+ `alter table \`schema_change_jobs\` add constraint \`schema_change_jobs_change_set_id_fkey\` foreign key (\`change_set_id\`) references \`change_sets\` (\`id\`) on delete set null`,
2378
+ `alter table \`schema_change_jobs\` add constraint \`schema_change_jobs_from_revision_id_fkey\` foreign key (\`from_revision_id\`) references \`schema_revisions\` (\`id\`)`,
2379
+ `alter table \`schema_change_jobs\` add constraint \`schema_change_jobs_job_id_fkey\` foreign key (\`job_id\`) references \`jobs\` (\`id\`) on delete set null`,
2380
+ `alter table \`schema_change_jobs\` add constraint \`schema_change_jobs_model_id_fkey\` foreign key (\`model_id\`) references \`models\` (\`id\`)`,
2381
+ `alter table \`schema_change_jobs\` add constraint \`schema_change_jobs_to_revision_id_fkey\` foreign key (\`to_revision_id\`) references \`schema_revisions\` (\`id\`)`,
2382
+ `alter table \`entries\` add constraint \`entries_created_by_admin_id_fkey\` foreign key (\`created_by_admin_id\`) references \`admin_users\` (\`id\`) on delete set null`,
2383
+ `alter table \`entries\` add constraint \`entries_model_id_fkey\` foreign key (\`model_id\`) references \`models\` (\`id\`) on delete restrict`,
2384
+ `alter table \`entries\` add constraint \`entries_owner_app_user_fk\` foreign key (\`owner_app_user_id\`) references \`app_users\` (\`id\`) on delete set null`,
2385
+ `alter table \`entries\` add constraint \`entries_site_fk\` foreign key (\`site_id\`) references \`sites\` (\`id\`) on delete restrict`,
2386
+ `alter table \`content_revisions\` add constraint \`content_revisions_entry_id_fkey\` foreign key (\`entry_id\`) references \`entries\` (\`id\`) on delete restrict`,
2387
+ `alter table \`content_revisions\` add constraint \`content_revisions_parent_revision_id_fkey\` foreign key (\`parent_revision_id\`) references \`content_revisions\` (\`id\`)`,
2388
+ `alter table \`content_revisions\` add constraint \`content_revisions_schema_revision_id_fkey\` foreign key (\`schema_revision_id\`) references \`schema_revisions\` (\`id\`)`,
2389
+ `alter table \`entry_heads\` add constraint \`entry_heads_entry_id_fkey\` foreign key (\`entry_id\`) references \`entries\` (\`id\`) on delete cascade`,
2390
+ `alter table \`entry_heads\` add constraint \`entry_heads_entry_site_fk\` foreign key (\`entry_id\`, \`site_id\`) references \`entries\` (\`id\`, \`site_id\`) on delete cascade`,
2391
+ `alter table \`entry_heads\` add constraint \`entry_heads_revision_id_fkey\` foreign key (\`revision_id\`) references \`content_revisions\` (\`id\`)`,
2392
+ `alter table \`entry_heads\` add constraint \`entry_heads_site_fk\` foreign key (\`site_id\`) references \`sites\` (\`id\`) on delete restrict`,
2393
+ `alter table \`relation_edges\` add constraint \`relation_edges_head_fk\` foreign key (\`source_entry_id\`, \`locale\`, \`state\`) references \`entry_heads\` (\`entry_id\`, \`locale\`, \`state\`) on delete cascade`,
2394
+ `alter table \`relation_edges\` add constraint \`relation_edges_target_entry_id_fkey\` foreign key (\`target_entry_id\`) references \`entries\` (\`id\`)`,
2395
+ `alter table \`unique_values\` add constraint \`unique_values_entry_id_fkey\` foreign key (\`entry_id\`) references \`entries\` (\`id\`) on delete cascade`,
2396
+ `alter table \`unique_values\` add constraint \`unique_values_entry_site_fk\` foreign key (\`entry_id\`, \`site_id\`) references \`entries\` (\`id\`, \`site_id\`) on delete cascade`,
2397
+ `alter table \`unique_values\` add constraint \`unique_values_site_fk\` foreign key (\`site_id\`) references \`sites\` (\`id\`) on delete restrict`,
2398
+ `alter table \`publication_state\` add constraint \`publication_state_site_fk\` foreign key (\`site_id\`) references \`sites\` (\`id\`) on delete cascade`,
2399
+ `alter table \`publication_log\` add constraint \`publication_log_entry_id_fkey\` foreign key (\`entry_id\`) references \`entries\` (\`id\`)`,
2400
+ `alter table \`publication_log\` add constraint \`publication_log_entry_site_fk\` foreign key (\`entry_id\`, \`site_id\`) references \`entries\` (\`id\`, \`site_id\`) on delete restrict`,
2401
+ `alter table \`publication_log\` add constraint \`publication_log_revision_id_fkey\` foreign key (\`revision_id\`) references \`content_revisions\` (\`id\`)`,
2402
+ `alter table \`publication_log\` add constraint \`publication_log_site_fk\` foreign key (\`site_id\`) references \`sites\` (\`id\`) on delete restrict`,
2403
+ `alter table \`media_folders\` add constraint \`media_folders_created_by_fkey\` foreign key (\`created_by\`) references \`admin_users\` (\`id\`) on delete set null`,
2404
+ `alter table \`media_folders\` add constraint \`media_folders_parent_id_fkey\` foreign key (\`parent_id\`, \`site_id\`) references \`media_folders\` (\`id\`, \`site_id\`) on delete restrict`,
2405
+ `alter table \`media_folders\` add constraint \`media_folders_site_fk\` foreign key (\`site_id\`) references \`sites\` (\`id\`) on delete restrict`,
2406
+ `alter table \`media_assets\` add constraint \`media_assets_created_by_fkey\` foreign key (\`created_by\`) references \`admin_users\` (\`id\`) on delete set null`,
2407
+ `alter table \`media_assets\` add constraint \`media_assets_folder_id_fkey\` foreign key (\`folder_id\`, \`site_id\`) references \`media_folders\` (\`id\`, \`site_id\`)`,
2408
+ `alter table \`media_assets\` add constraint \`media_assets_site_fk\` foreign key (\`site_id\`) references \`sites\` (\`id\`) on delete restrict`,
2409
+ `alter table \`media_variants\` add constraint \`media_variants_asset_id_fkey\` foreign key (\`asset_id\`) references \`media_assets\` (\`id\`) on delete cascade`,
2410
+ `alter table \`media_references\` add constraint \`media_references_asset_id_fkey\` foreign key (\`asset_id\`) references \`media_assets\` (\`id\`) on delete cascade`,
2411
+ `alter table \`media_references\` add constraint \`media_references_entry_id_fkey\` foreign key (\`entry_id\`) references \`entries\` (\`id\`) on delete cascade`,
2412
+ `alter table \`media_upload_grants\` add constraint \`media_upload_grants_created_by_fkey\` foreign key (\`created_by\`) references \`admin_users\` (\`id\`) on delete set null`,
2413
+ `alter table \`media_upload_grants\` add constraint \`media_upload_grants_site_fk\` foreign key (\`site_id\`) references \`sites\` (\`id\`) on delete cascade`,
2414
+ `alter table \`scheduled_publications\` add constraint \`scheduled_publications_created_by_fkey\` foreign key (\`created_by\`) references \`admin_users\` (\`id\`) on delete set null`,
2415
+ `alter table \`scheduled_publications\` add constraint \`scheduled_publications_created_by_token_fkey\` foreign key (\`created_by_token\`) references \`api_tokens\` (\`id\`) on delete set null`,
2416
+ `alter table \`scheduled_publications\` add constraint \`scheduled_publications_entry_id_fkey\` foreign key (\`entry_id\`) references \`entries\` (\`id\`) on delete cascade`,
2417
+ `alter table \`scheduled_publications\` add constraint \`scheduled_publications_entry_site_fk\` foreign key (\`entry_id\`, \`site_id\`) references \`entries\` (\`id\`, \`site_id\`) on delete cascade`,
2418
+ `alter table \`scheduled_publications\` add constraint \`scheduled_publications_site_fk\` foreign key (\`site_id\`) references \`sites\` (\`id\`) on delete restrict`,
2419
+ `alter table \`webhooks\` add constraint \`webhooks_created_by_fkey\` foreign key (\`created_by\`) references \`admin_users\` (\`id\`) on delete set null`,
2420
+ `alter table \`webhooks\` add constraint \`webhooks_site_fk\` foreign key (\`site_id\`) references \`sites\` (\`id\`) on delete cascade`,
2421
+ `alter table \`webhook_deliveries\` add constraint \`webhook_deliveries_webhook_id_fkey\` foreign key (\`webhook_id\`) references \`webhooks\` (\`id\`) on delete cascade`,
2422
+ `alter table \`deployment_connections\` add constraint \`deployment_connections_created_by_fkey\` foreign key (\`created_by\`) references \`admin_users\` (\`id\`) on delete set null`,
2423
+ `alter table \`deployment_connections\` add constraint \`deployment_connections_delivery_role_id_fkey\` foreign key (\`delivery_role_id\`) references \`admin_roles\` (\`id\`) on delete set null`,
2424
+ `alter table \`deployment_connections\` add constraint \`deployment_connections_site_fk\` foreign key (\`site_id\`) references \`sites\` (\`id\`) on delete cascade`,
2425
+ `alter table \`deployment_runs\` add constraint \`deployment_runs_change_set_id_fkey\` foreign key (\`change_set_id\`) references \`change_sets\` (\`id\`) on delete set null`,
2426
+ `alter table \`deployment_runs\` add constraint \`deployment_runs_connection_id_fkey\` foreign key (\`connection_id\`) references \`deployment_connections\` (\`id\`) on delete cascade`,
2427
+ `alter table \`deployment_runs\` add constraint \`deployment_runs_created_by_fkey\` foreign key (\`created_by\`) references \`admin_users\` (\`id\`) on delete set null`,
2428
+ `alter table \`deployment_runs\` add constraint \`deployment_runs_retry_of_fkey\` foreign key (\`retry_of\`) references \`deployment_runs\` (\`id\`) on delete set null`,
2429
+ `alter table \`preview_tokens\` add constraint \`preview_tokens_connection_id_fkey\` foreign key (\`connection_id\`) references \`deployment_connections\` (\`id\`) on delete set null`,
2430
+ `alter table \`preview_tokens\` add constraint \`preview_tokens_created_by_fkey\` foreign key (\`created_by\`) references \`admin_users\` (\`id\`) on delete cascade`,
2431
+ `alter table \`preview_tokens\` add constraint \`preview_tokens_delivery_role_id_fkey\` foreign key (\`delivery_role_id\`) references \`admin_roles\` (\`id\`) on delete set null`,
2432
+ `alter table \`preview_tokens\` add constraint \`preview_tokens_entry_id_fkey\` foreign key (\`entry_id\`) references \`entries\` (\`id\`) on delete cascade`,
2433
+ `alter table \`preview_tokens\` add constraint \`preview_tokens_entry_site_fk\` foreign key (\`entry_id\`, \`site_id\`) references \`entries\` (\`id\`, \`site_id\`) on delete cascade`,
2434
+ `alter table \`preview_tokens\` add constraint \`preview_tokens_site_fk\` foreign key (\`site_id\`) references \`sites\` (\`id\`) on delete cascade`,
2435
+ `alter table \`app_users\` add constraint \`app_users_site_fk\` foreign key (\`site_id\`) references \`sites\` (\`id\`) on delete restrict`,
2436
+ `alter table \`app_role_permissions\` add constraint \`app_role_permissions_role_id_fkey\` foreign key (\`role_id\`) references \`app_roles\` (\`id\`) on delete cascade`,
2437
+ `alter table \`app_user_roles\` add constraint \`app_user_roles_app_user_id_fkey\` foreign key (\`app_user_id\`) references \`app_users\` (\`id\`) on delete cascade`,
2438
+ `alter table \`app_user_roles\` add constraint \`app_user_roles_role_id_fkey\` foreign key (\`role_id\`) references \`app_roles\` (\`id\`) on delete restrict`,
2439
+ `alter table \`app_user_roles\` add constraint \`app_user_roles_site_fk\` foreign key (\`site_id\`) references \`sites\` (\`id\`) on delete restrict`,
2440
+ `alter table \`app_refresh_tokens\` add constraint \`app_refresh_tokens_app_user_id_fkey\` foreign key (\`app_user_id\`) references \`app_users\` (\`id\`) on delete cascade`,
2441
+ `alter table \`app_oauth_accounts\` add constraint \`app_oauth_accounts_app_user_id_fkey\` foreign key (\`app_user_id\`) references \`app_users\` (\`id\`) on delete cascade`,
2442
+ `alter table \`app_oauth_accounts\` add constraint \`app_oauth_accounts_site_fk\` foreign key (\`site_id\`) references \`sites\` (\`id\`) on delete restrict`,
2443
+ `alter table \`email_confirmations\` add constraint \`email_confirmations_app_user_id_fkey\` foreign key (\`app_user_id\`) references \`app_users\` (\`id\`) on delete cascade`,
2444
+ `alter table \`app_password_resets\` add constraint \`app_password_resets_app_user_id_fkey\` foreign key (\`app_user_id\`) references \`app_users\` (\`id\`) on delete cascade`,
2445
+ `alter table \`app_login_codes\` add constraint \`app_login_codes_app_user_id_fkey\` foreign key (\`app_user_id\`) references \`app_users\` (\`id\`) on delete cascade`,
2446
+ `alter table \`field_reads\` add constraint \`field_reads_site_fk\` foreign key (\`site_id\`) references \`sites\` (\`id\`) on delete cascade`,
2447
+ `alter table \`token_reads\` add constraint \`token_reads_site_fk\` foreign key (\`site_id\`) references \`sites\` (\`id\`) on delete cascade`,
2448
+ `alter table \`change_sets\` add constraint \`change_sets_created_by_fkey\` foreign key (\`created_by\`) references \`admin_users\` (\`id\`) on delete set null`,
2449
+ `alter table \`change_sets\` add constraint \`change_sets_created_by_token_fkey\` foreign key (\`created_by_token\`) references \`api_tokens\` (\`id\`) on delete set null`,
2450
+ `alter table \`change_sets\` add constraint \`change_sets_deployment_connection_id_fkey\` foreign key (\`deployment_connection_id\`) references \`deployment_connections\` (\`id\`) on delete set null`,
2451
+ `alter table \`change_sets\` add constraint \`change_sets_deployment_run_id_fkey\` foreign key (\`deployment_run_id\`) references \`deployment_runs\` (\`id\`) on delete set null`,
2452
+ `alter table \`change_sets\` add constraint \`change_sets_scheduled_by_fkey\` foreign key (\`scheduled_by\`) references \`admin_users\` (\`id\`) on delete set null`,
2453
+ `alter table \`change_sets\` add constraint \`change_sets_scheduled_by_token_fkey\` foreign key (\`scheduled_by_token\`) references \`api_tokens\` (\`id\`) on delete set null`,
2454
+ `alter table \`change_sets\` add constraint \`change_sets_ship_requested_by_fkey\` foreign key (\`ship_requested_by\`) references \`admin_users\` (\`id\`) on delete set null`,
2455
+ `alter table \`change_sets\` add constraint \`change_sets_ship_requested_by_token_fkey\` foreign key (\`ship_requested_by_token\`) references \`api_tokens\` (\`id\`) on delete set null`,
2456
+ `alter table \`change_sets\` add constraint \`change_sets_site_fk\` foreign key (\`site_id\`) references \`sites\` (\`id\`) on delete restrict`,
2457
+ `alter table \`schema_drafts\` add constraint \`schema_drafts_change_set_id_fkey\` foreign key (\`change_set_id\`) references \`change_sets\` (\`id\`) on delete cascade`,
2458
+ `alter table \`change_set_items\` add constraint \`change_set_items_change_set_id_fkey\` foreign key (\`change_set_id\`) references \`change_sets\` (\`id\`) on delete cascade`,
2459
+ `alter table \`change_set_items\` add constraint \`change_set_items_entry_id_fkey\` foreign key (\`entry_id\`) references \`entries\` (\`id\`) on delete cascade`,
2460
+ `alter table \`change_set_items\` add constraint \`change_set_items_entry_site_fk\` foreign key (\`entry_id\`, \`site_id\`) references \`entries\` (\`id\`, \`site_id\`) on delete cascade`,
2461
+ `alter table \`change_set_items\` add constraint \`change_set_items_schema_draft_id_fkey\` foreign key (\`schema_draft_id\`) references \`schema_drafts\` (\`id\`) on delete cascade`,
2462
+ `alter table \`change_set_items\` add constraint \`change_set_items_set_site_fk\` foreign key (\`change_set_id\`, \`site_id\`) references \`change_sets\` (\`id\`, \`site_id\`) on delete cascade`,
2463
+ `alter table \`change_set_items\` add constraint \`change_set_items_source_revision_id_fkey\` foreign key (\`source_revision_id\`) references \`content_revisions\` (\`id\`)`,
2464
+ `alter table \`publication_snapshots\` add constraint \`publication_snapshots_change_set_id_fkey\` foreign key (\`change_set_id\`) references \`change_sets\` (\`id\`) on delete set null`,
2465
+ `alter table \`publication_snapshots\` add constraint \`publication_snapshots_site_fk\` foreign key (\`site_id\`) references \`sites\` (\`id\`) on delete cascade`,
2466
+ `alter table \`content_health_findings\` add constraint \`content_health_findings_entry_id_fkey\` foreign key (\`entry_id\`) references \`entries\` (\`id\`) on delete cascade`,
2467
+ `alter table \`content_health_findings\` add constraint \`content_health_findings_entry_site_fk\` foreign key (\`entry_id\`, \`site_id\`) references \`entries\` (\`id\`, \`site_id\`) on delete cascade`,
2468
+ `alter table \`content_health_findings\` add constraint \`content_health_findings_site_fk\` foreign key (\`site_id\`) references \`sites\` (\`id\`) on delete restrict`,
2469
+ `alter table \`site_app_roles\` add constraint \`site_app_roles_role_id_fkey\` foreign key (\`role_id\`) references \`app_roles\` (\`id\`) on delete cascade`,
2470
+ `alter table \`site_app_roles\` add constraint \`site_app_roles_site_id_fkey\` foreign key (\`site_id\`) references \`sites\` (\`id\`) on delete cascade`,
2471
+ `alter table \`assist_runs\` add constraint \`assist_runs_job_id_fkey\` foreign key (\`job_id\`) references \`jobs\` (\`id\`) on delete set null`,
2472
+ `alter table \`assist_runs\` add constraint \`assist_runs_site_id_fkey\` foreign key (\`site_id\`) references \`sites\` (\`id\`) on delete cascade`,
2473
+ `create index \`admin_invitations_pending_email_idx\` on \`admin_invitations\` (\`email\`)`,
2474
+ `create index \`admin_sessions_user_idx\` on \`admin_sessions\` (\`admin_user_id\`)`,
2475
+ `create index \`admin_user_roles_role_idx\` on \`admin_user_roles\` (\`role_id\`)`,
2476
+ `create index \`api_tokens_role_idx\` on \`api_tokens\` (\`role_id\`)`,
2477
+ `create index \`app_oauth_accounts_user_idx\` on \`app_oauth_accounts\` (\`app_user_id\`)`,
2478
+ `create index \`app_password_resets_user_idx\` on \`app_password_resets\` (\`app_user_id\`)`,
2479
+ `create index \`app_refresh_tokens_family_idx\` on \`app_refresh_tokens\` (\`family_id\`)`,
2480
+ `create index \`app_refresh_tokens_user_idx\` on \`app_refresh_tokens\` (\`app_user_id\`)`,
2481
+ `create index \`app_user_roles_role_idx\` on \`app_user_roles\` (\`role_id\`)`,
2482
+ `create index \`app_users_created_idx\` on \`app_users\` (\`site_id\`, \`created_at\` desc, \`id\` desc)`,
2483
+ `create unique index \`app_users_email_uq\` on \`app_users\` ((case when (\`deleted_at\` IS NULL) then \`site_id\` end), (case when (\`deleted_at\` IS NULL) then lower(\`email\`) end))`,
2484
+ `create index \`assist_runs_created_idx\` on \`assist_runs\` (\`created_at\`)`,
2485
+ `create index \`assist_runs_site_created_idx\` on \`assist_runs\` (\`site_id\`, \`created_at\`)`,
2486
+ `create index \`audit_events_actor_idx\` on \`audit_events\` (\`actor_type\`, \`actor_id\`, \`occurred_at\` desc)`,
2487
+ `create index \`audit_events_occurred_at_idx\` on \`audit_events\` (\`occurred_at\` desc)`,
2488
+ `create index \`audit_events_target_idx\` on \`audit_events\` (\`target_type\`, \`target_id\`, \`occurred_at\` desc)`,
2489
+ `create unique index \`change_set_items_draft_uq\` on \`change_set_items\` (\`schema_draft_id\`)`,
2490
+ `create index \`change_set_items_entry_idx\` on \`change_set_items\` (\`entry_id\`)`,
2491
+ `create unique index \`change_set_items_entry_uq\` on \`change_set_items\` ((case when (\`kind\` = 'entry') then \`change_set_id\` end), (case when (\`kind\` = 'entry') then \`entry_id\` end), (case when (\`kind\` = 'entry') then \`locale\` end))`,
2492
+ `create index \`change_set_items_set_idx\` on \`change_set_items\` (\`change_set_id\`, \`position\`)`,
2493
+ `create index \`change_sets_status_idx\` on \`change_sets\` (\`site_id\`, \`status\`, \`created_at\` desc)`,
2494
+ `create index \`content_health_findings_open_idx\` on \`content_health_findings\` (\`site_id\`, \`rule\`, \`last_seen_at\` desc, \`id\`)`,
2495
+ `create index \`content_health_findings_resolved_idx\` on \`content_health_findings\` (\`resolved_at\`)`,
2496
+ `create index \`content_revisions_entry_idx\` on \`content_revisions\` (\`entry_id\`, \`locale\`, \`created_at\`)`,
2497
+ `create index \`deployment_runs_connection_idx\` on \`deployment_runs\` (\`connection_id\`, \`created_at\` desc)`,
2498
+ `create unique index \`deployment_runs_queued_uq\` on \`deployment_runs\` ((case when (\`status\` = 'queued') then \`connection_id\` end))`,
2499
+ `create index \`editor_presence_model_idx\` on \`editor_presence\` (\`model_id\`, \`last_seen_at\`)`,
2500
+ `create index \`editor_presence_seen_idx\` on \`editor_presence\` (\`last_seen_at\`)`,
2501
+ `create index \`email_confirmations_user_idx\` on \`email_confirmations\` (\`app_user_id\`)`,
2502
+ `create index \`entries_model_idx\` on \`entries\` (\`site_id\`, \`model_id\`, \`created_at\`)`,
2503
+ `create index \`entries_owner_idx\` on \`entries\` (\`owner_app_user_id\`)`,
2504
+ `create index \`entry_heads_change_seq_idx\` on \`entry_heads\` (\`model_id\`, \`change_seq\`)`,
2505
+ `create index \`entry_heads_model_locale_state_idx\` on \`entry_heads\` (\`site_id\`, \`model_id\`, \`locale\`, \`state\`)`,
2506
+ `create index \`field_reads_model_day_idx\` on \`field_reads\` (\`model_id\`, \`day\`)`,
2507
+ `create unique index \`jobs_idempotency_key_uq\` on \`jobs\` (\`idempotency_key\`)`,
2508
+ `create index \`jobs_pending_idx\` on \`jobs\` (\`priority\` desc, \`run_at\`)`,
2509
+ `create index \`jobs_running_lease_idx\` on \`jobs\` (\`locked_until\`)`,
2510
+ `create unique index \`locales_single_default_uq\` on \`locales\` ((case when \`is_default\` then \`is_default\` end))`,
2511
+ `create index \`media_assets_created_idx\` on \`media_assets\` (\`site_id\`, \`created_at\`, \`id\`)`,
2512
+ `create index \`media_assets_driver_idx\` on \`media_assets\` (\`storage_driver\`, \`id\`)`,
2513
+ `create index \`media_assets_folder_idx\` on \`media_assets\` (\`folder_id\`, \`created_at\`, \`id\`)`,
2514
+ `create unique index \`media_assets_storage_key_uq\` on \`media_assets\` (\`storage_key\`)`,
2515
+ `create unique index \`media_folders_sibling_name_uq\` on \`media_folders\` (\`site_id\`, (COALESCE(\`parent_id\`, '00000000-0000-0000-0000-000000000000')), (lower(\`name\`)))`,
2516
+ `create index \`media_references_entry_idx\` on \`media_references\` (\`entry_id\`, \`locale\`, \`state\`)`,
2517
+ `create unique index \`media_variants_storage_key_uq\` on \`media_variants\` (\`storage_key\`)`,
2518
+ `create unique index \`models_api_key_uq\` on \`models\` ((case when (\`deleted_at\` IS NULL) then lower(\`api_key\`) end))`,
2519
+ `create index \`outbox_events_undispatched_idx\` on \`outbox_events\` (\`id\`)`,
2520
+ `create index \`password_resets_user_idx\` on \`password_resets\` (\`admin_user_id\`)`,
2521
+ `create index \`preview_tokens_entry_idx\` on \`preview_tokens\` (\`entry_id\`, \`created_at\` desc)`,
2522
+ `create index \`publication_log_entry_locale_from_idx\` on \`publication_log\` (\`entry_id\`, \`locale\`, \`from_seq\`)`,
2523
+ `create index \`publication_log_model_seq_idx\` on \`publication_log\` (\`site_id\`, \`model_id\`, \`from_seq\`)`,
2524
+ `create unique index \`publication_log_open_uq\` on \`publication_log\` ((case when (\`to_seq\` IS NULL) then \`entry_id\` end), (case when (\`to_seq\` IS NULL) then \`locale\` end))`,
2525
+ `create index \`publication_log_to_seq_idx\` on \`publication_log\` (\`site_id\`, \`to_seq\`)`,
2526
+ `create index \`publication_snapshots_change_set_idx\` on \`publication_snapshots\` (\`change_set_id\`)`,
2527
+ `create index \`relation_edges_target_idx\` on \`relation_edges\` (\`target_entry_id\`, \`state\`)`,
2528
+ `create index \`scheduled_publications_entry_idx\` on \`scheduled_publications\` (\`entry_id\`, \`locale\`)`,
2529
+ `create index \`scheduled_publications_site_idx\` on \`scheduled_publications\` (\`site_id\`, \`run_at\`)`,
2530
+ `create index \`scheduled_publications_status_idx\` on \`scheduled_publications\` (\`status\`, \`run_at\`)`,
2531
+ `create index \`schema_change_jobs_change_set_idx\` on \`schema_change_jobs\` (\`change_set_id\`)`,
2532
+ `create unique index \`schema_change_jobs_in_flight_uq\` on \`schema_change_jobs\` ((case when (\`status\` in ('pending', 'running')) then \`target_type\` end), (case when (\`status\` in ('pending', 'running')) then \`target_id\` end))`,
2533
+ `create index \`schema_change_jobs_model_idx\` on \`schema_change_jobs\` (\`model_id\`, \`created_at\` desc)`,
2534
+ `create index \`schema_drafts_definition_idx\` on \`schema_drafts\` (\`definition_id\`)`,
2535
+ `create index \`site_app_roles_role_idx\` on \`site_app_roles\` (\`role_id\`)`,
2536
+ `create unique index \`sites_single_primary_uq\` on \`sites\` ((case when \`is_primary\` then \`is_primary\` end))`,
2537
+ `create index \`unique_values_entry_idx\` on \`unique_values\` (\`entry_id\`)`,
2538
+ `create unique index \`webhook_deliveries_event_uq\` on \`webhook_deliveries\` (\`webhook_id\`, \`event_id\`)`,
2539
+ `create index \`webhook_deliveries_webhook_idx\` on \`webhook_deliveries\` (\`webhook_id\`, \`created_at\` desc)`,
2540
+ `create table \`advisory_locks\` (
2541
+ \`namespace\` int not null,
2542
+ \`lock_key\` int not null,
2543
+ constraint \`advisory_locks_pkey\` primary key (\`namespace\`, \`lock_key\`)
2544
+ )`,
2545
+ `create table \`notifications\` (
2546
+ \`id\` bigint not null auto_increment,
2547
+ \`channel\` varchar(64) not null,
2548
+ \`payload\` longtext not null,
2549
+ \`created_at\` datetime(6) not null default current_timestamp(6),
2550
+ constraint \`notifications_pkey\` primary key (\`id\`)
2551
+ )`,
2552
+ `create index \`notifications_created_idx\` on \`notifications\` (\`created_at\`)`,
2553
+ `create table \`sequence_entry_heads_change_seq\` (
2554
+ \`value\` bigint not null auto_increment,
2555
+ constraint \`sequence_entry_heads_change_seq_pkey\` primary key (\`value\`)
2556
+ )`,
2557
+ `create trigger \`media_assets_folder_id_fkey_set_null\` before delete on \`media_folders\` for each row
2558
+ update \`media_assets\` set \`folder_id\` = null where \`folder_id\` = old.\`id\` and \`site_id\` = old.\`site_id\``,
2559
+ `create trigger \`schema_revisions_no_update\` before update on \`schema_revisions\` for each row
2560
+ signal sqlstate '45000' set message_text = 'schema_revisions rows are immutable'`,
2561
+ `create trigger \`content_revisions_no_update\` before update on \`content_revisions\` for each row
2562
+ signal sqlstate '45000' set message_text = 'content_revisions rows are immutable'`,
2563
+ `insert into \`system_versions\` () values ()`,
2564
+ `insert into \`schema_settings\` () values ()`,
2565
+ `insert into \`locales\` (\`code\`, \`label\`, \`is_default\`) values ('en', 'English', 1)`,
2566
+ `insert into \`sites\` (\`id\`, \`key\`, \`name\`, \`is_primary\`) values ('00000000-0000-4000-b000-000000000001', 'default', 'Default site', 1)`,
2567
+ `insert into \`publication_state\` (\`site_id\`, \`last_seq\`) values ('00000000-0000-4000-b000-000000000001', 0)`,
2568
+ `insert into \`app_roles\` (\`id\`, \`key\`, \`name\`, \`description\`, \`is_system\`) values
2569
+ ('00000000-0000-4000-a000-000000000001', 'public', 'Public', 'Applies to every caller without a token. Grants nothing until you add permissions.', 1),
2570
+ ('00000000-0000-4000-a000-000000000002', 'authenticated', 'Authenticated', 'Held by every signed-in app user, in addition to their custom roles. Grants nothing until you add permissions.', 1)`,
2571
+ `insert into \`site_app_roles\` (\`site_id\`, \`audience\`, \`role_id\`) values
2572
+ ('00000000-0000-4000-b000-000000000001', 'public', '00000000-0000-4000-a000-000000000001'),
2573
+ ('00000000-0000-4000-b000-000000000001', 'authenticated', '00000000-0000-4000-a000-000000000002')`
2574
+ ];
2575
+ const TABLES$1 = [
2576
+ "system_versions",
2577
+ "jobs",
2578
+ "outbox_events",
2579
+ "audit_events",
2580
+ "admin_users",
2581
+ "admin_roles",
2582
+ "admin_role_permissions",
2583
+ "admin_user_roles",
2584
+ "admin_sessions",
2585
+ "admin_invitations",
2586
+ "password_resets",
2587
+ "setup_tokens",
2588
+ "api_tokens",
2589
+ "system_settings",
2590
+ "models",
2591
+ "schema_revisions",
2592
+ "model_active_versions",
2593
+ "schema_change_jobs",
2594
+ "locales",
2595
+ "schema_settings",
2596
+ "entries",
2597
+ "content_revisions",
2598
+ "entry_heads",
2599
+ "relation_edges",
2600
+ "unique_values",
2601
+ "publication_state",
2602
+ "publication_log",
2603
+ "media_folders",
2604
+ "media_assets",
2605
+ "media_variants",
2606
+ "media_references",
2607
+ "media_upload_grants",
2608
+ "scheduled_publications",
2609
+ "webhooks",
2610
+ "webhook_deliveries",
2611
+ "deployment_connections",
2612
+ "deployment_runs",
2613
+ "preview_tokens",
2614
+ "app_users",
2615
+ "app_roles",
2616
+ "app_role_permissions",
2617
+ "app_user_roles",
2618
+ "app_refresh_tokens",
2619
+ "app_oauth_accounts",
2620
+ "email_confirmations",
2621
+ "app_password_resets",
2622
+ "app_login_codes",
2623
+ "extension_hook_runs",
2624
+ "field_reads",
2625
+ "token_reads",
2626
+ "change_sets",
2627
+ "schema_drafts",
2628
+ "change_set_items",
2629
+ "publication_snapshots",
2630
+ "editor_presence",
2631
+ "content_health_findings",
2632
+ "sites",
2633
+ "site_app_roles",
2634
+ "assist_runs",
2635
+ "advisory_locks",
2636
+ "notifications",
2637
+ "sequence_entry_heads_change_seq"
2638
+ ];
2639
+ /** Runs the statements on one connection, where `foreign_key_checks` is off (tables reference in cycles). */
2640
+ const withoutForeignKeyChecks = async (db, statements) => db.connection().execute(async (connection) => {
2641
+ await sql`set foreign_key_checks = 0`.execute(connection);
2642
+ try {
2643
+ for (const statement of statements) await sql.raw(statement).execute(connection);
2644
+ } finally {
2645
+ await sql`set foreign_key_checks = 1`.execute(connection);
2646
+ }
2647
+ });
2648
+ const up$9 = async (db) => withoutForeignKeyChecks(db, STATEMENTS$1);
2649
+ const down$9 = async (db) => withoutForeignKeyChecks(db, [...TABLES$1].reverse().map((table) => `drop table if exists \`${table}\``));
2650
+ //#endregion
2651
+ //#region ../../apps/api/src/db/migrations/mysql/20261003170000_audit_events_seq.ts
2652
+ var _20261003170000_audit_events_seq_exports$1 = /* @__PURE__ */ __exportAll({
2653
+ down: () => down$8,
2654
+ up: () => up$8
2655
+ });
2656
+ /**
2657
+ * MySQL twin of `../20261003170000_audit_events_seq.ts`: `seq` is `AUTO_INCREMENT`, which InnoDB only allows
2658
+ * on an indexed column, hence `audit_events_seq_key` (MySQL only; PostgreSQL and SQLite need no index for a
2659
+ * tiebreaker). Existing rows are numbered in primary-key order. `db/mysql/tables.ts` lists `seq` as the
2660
+ * table's identity, so an insert's emulated RETURNING finds its row by `LAST_INSERT_ID()`.
2661
+ */
2662
+ const up$8 = async (db) => {
2663
+ await sql`alter table \`audit_events\`
2664
+ add column \`seq\` bigint not null auto_increment,
2665
+ add unique key \`audit_events_seq_key\` (\`seq\`)`.execute(db);
2666
+ };
2667
+ const down$8 = async (db) => {
2668
+ await sql`alter table \`audit_events\` drop column \`seq\``.execute(db);
2669
+ };
2670
+ //#endregion
2671
+ //#region ../../apps/api/src/db/migrations/mysql/20261004120000_models_site_scope.ts
2672
+ var _20261004120000_models_site_scope_exports$1 = /* @__PURE__ */ __exportAll({
2673
+ down: () => down$7,
2674
+ up: () => up$7
2675
+ });
2676
+ /**
2677
+ * MySQL twin of `../20261004120000_models_site_scope.ts`. The per-scope API ID key uses functional key parts,
2678
+ * like the baseline: `coalesce(site_id, '')` for the scope (MySQL has no `NULLS NOT DISTINCT`) and the
2679
+ * case-folded API ID of live rows only (a deleted row's part is NULL, and NULLs never collide).
2680
+ */
2681
+ const up$7 = async (db) => {
2682
+ await sql`alter table \`models\` add column \`site_id\` char(36) character set ascii collate ascii_general_ci`.execute(db);
2683
+ await sql`create index \`models_site_idx\` on \`models\` (\`site_id\`)`.execute(db);
2684
+ await sql`alter table \`models\` add constraint \`models_site_id_fkey\` foreign key (\`site_id\`) references \`sites\` (\`id\`)`.execute(db);
2685
+ await sql`drop index \`models_api_key_uq\` on \`models\``.execute(db);
2686
+ await sql`create unique index \`models_api_key_uq\` on \`models\`
2687
+ ((coalesce(\`site_id\`, '')), (case when (\`deleted_at\` IS NULL) then lower(\`api_key\`) end))`.execute(db);
2688
+ await sql`alter table \`schema_drafts\` add column \`shared\` tinyint(1) not null default 0`.execute(db);
2689
+ };
2690
+ const down$7 = async (db) => {
2691
+ const { rows } = await sql`select count(*) as count from \`models\` where \`site_id\` is not null`.execute(db);
2692
+ if (Number(rows[0]?.count ?? 0) > 0) throw new Error("Cannot roll back per-site schema while definitions belong to a site: two sites may share an API ID. Share or delete the site definitions first.");
2693
+ await sql`alter table \`schema_drafts\` drop column \`shared\``.execute(db);
2694
+ await sql`drop index \`models_api_key_uq\` on \`models\``.execute(db);
2695
+ await sql`create unique index \`models_api_key_uq\` on \`models\`
2696
+ ((case when (\`deleted_at\` IS NULL) then lower(\`api_key\`) end))`.execute(db);
2697
+ await sql`alter table \`models\` drop foreign key \`models_site_id_fkey\``.execute(db);
2698
+ await sql`drop index \`models_site_idx\` on \`models\``.execute(db);
2699
+ await sql`alter table \`models\` drop column \`site_id\``.execute(db);
2700
+ };
2701
+ //#endregion
2702
+ //#region ../../apps/api/src/db/migrations/mysql/20261004130000_site_seo_settings.ts
2703
+ var _20261004130000_site_seo_settings_exports$1 = /* @__PURE__ */ __exportAll({
2704
+ down: () => down$6,
2705
+ up: () => up$6
2706
+ });
2707
+ /**
2708
+ * MySQL twin of `../20261004130000_site_seo_settings.ts`. `insert ignore` stands in for `on conflict do
2709
+ * nothing` (the grant's unique key); the permissions version moves only when a grant was added or removed.
2710
+ */
2711
+ const bumpPermissionsVersion$1 = (db) => sql`update \`system_versions\` set \`permissions_version\` = \`permissions_version\` + 1,
2712
+ \`updated_at\` = current_timestamp(6)`.execute(db);
2713
+ const up$6 = async (db) => {
2714
+ await sql`alter table \`sites\` add column \`seo_defaults\` json`.execute(db);
2715
+ const granted = await sql`
2716
+ insert ignore into \`admin_role_permissions\` (\`id\`, \`role_id\`, \`action\`, \`model_id\`, \`condition\`, \`field_ids\`)
2717
+ select uuid(), \`role_id\`, 'site.settings', null, null, null
2718
+ from \`admin_role_permissions\`
2719
+ where \`action\` = 'publishing.manage' and \`model_id\` is null
2720
+ `.execute(db);
2721
+ if (Number(granted.numAffectedRows ?? 0) > 0) await bumpPermissionsVersion$1(db);
2722
+ };
2723
+ const down$6 = async (db) => {
2724
+ const revoked = await sql`delete from \`admin_role_permissions\` where \`action\` = 'site.settings'`.execute(db);
2725
+ if (Number(revoked.numAffectedRows ?? 0) > 0) await bumpPermissionsVersion$1(db);
2726
+ await sql`alter table \`sites\` drop column \`seo_defaults\``.execute(db);
2727
+ };
2728
+ //#endregion
2729
+ //#region ../../apps/api/src/db/migrations/mysql/20261005120000_entry_heads_entry_created_at.ts
2730
+ var _20261005120000_entry_heads_entry_created_at_exports$1 = /* @__PURE__ */ __exportAll({
2731
+ down: () => down$5,
2732
+ up: () => up$5
2733
+ });
2734
+ /**
2735
+ * MySQL twin of `../20261005120000_entry_heads_entry_created_at.ts`. The column is added `ALGORITHM=INSTANT`
2736
+ * and backfilled in batches; the index is built online (`ALGORITHM=INPLACE, LOCK=NONE`), so writes continue.
2737
+ * It is one more index of `entry_heads`, which is why `MYSQL_MAX_FIELD_INDEXES` (db/limits.ts) went down by one.
2738
+ */
2739
+ const INDEX$1 = "entry_heads_entry_order_idx";
2740
+ const BATCH = 5e3;
2741
+ const up$5 = async (db) => {
2742
+ await sql`alter table \`entry_heads\` add column \`entry_created_at\` datetime(6), algorithm=instant`.execute(db);
2743
+ for (;;) {
2744
+ const result = await sql`update \`entry_heads\` set \`entry_created_at\` = (
2745
+ select e.\`created_at\` from \`entries\` e where e.\`id\` = \`entry_heads\`.\`entry_id\`
2746
+ ) where \`entry_created_at\` is null limit ${sql.lit(BATCH)}`.execute(db);
2747
+ if (Number(result.numAffectedRows ?? 0n) < BATCH) break;
2748
+ }
2749
+ await sql`create index ${sql.id(INDEX$1)} on \`entry_heads\`
2750
+ (\`site_id\`, \`model_id\`, \`state\`, \`entry_created_at\` desc, \`entry_id\`) algorithm=inplace lock=none`.execute(db);
2751
+ };
2752
+ const down$5 = async (db) => {
2753
+ await sql`drop index ${sql.id(INDEX$1)} on \`entry_heads\``.execute(db);
2754
+ await sql`alter table \`entry_heads\` drop column \`entry_created_at\``.execute(db);
2755
+ };
2756
+ //#endregion
2757
+ //#region ../../apps/api/src/db/migrations/mysql/index.ts
2758
+ const MYSQL_MIGRATIONS = {
2759
+ "0001_baseline": _0001_baseline_exports$1,
2760
+ "20261003170000_audit_events_seq": _20261003170000_audit_events_seq_exports$1,
2761
+ "20261004120000_models_site_scope": _20261004120000_models_site_scope_exports$1,
2762
+ "20261004130000_site_seo_settings": _20261004130000_site_seo_settings_exports$1,
2763
+ "20261005120000_entry_heads_entry_created_at": _20261005120000_entry_heads_entry_created_at_exports$1,
2764
+ "20261006120000_system_versions_release": _20261006120000_system_versions_release_exports
2765
+ };
2766
+ //#endregion
2767
+ //#region ../../apps/api/src/db/migrations/sqlite/0001_baseline.ts
2768
+ var _0001_baseline_exports = /* @__PURE__ */ __exportAll({
2769
+ down: () => down$4,
2770
+ up: () => up$4
2771
+ });
2772
+ /**
2773
+ * SQLite baseline: Shapio's whole system schema as of PostgreSQL migration
2774
+ * `20261003160000_create_assist_runs`, in one step, with the rows those migrations seed. Later migrations
2775
+ * follow the twin rule (`./index.ts`).
2776
+ *
2777
+ * Written from the migrated PostgreSQL catalog with the type mapping in `db/sqlite/codec.ts`, then kept
2778
+ * honest by `test/sqliteBaseline.int.test.ts`, which migrates both databases and compares the logical
2779
+ * schema (tables, columns, nullability, defaults, keys, foreign keys, indexes, seed rows). Differences that
2780
+ * are deliberate:
2781
+ * - UNIQUE constraints are named unique indexes (SQLite only names those in its errors); the three
2782
+ * `NULLS NOT DISTINCT` ones index `coalesce(column, '')` (`db/sqlite/nullsNotDistinct.ts`).
2783
+ * - `entry_heads_data_gin` (GIN, containment queries) has no SQLite equivalent and is left out.
2784
+ * - The `entry_heads_change_seq` sequence is a row in `sequences`, advanced by `shapio_nextval()`.
2785
+ * - `media_assets_folder_id_fkey` sets only `folder_id` to null on delete (PostgreSQL's column list), which
2786
+ * SQLite expresses as a trigger.
2787
+ * - `editor_presence` is a plain table (UNLOGGED has no SQLite meaning); the immutability triggers raise.
2788
+ * - Identity keys (`outbox_events.id`, `publication_log.id`) are `integer primary key autoincrement`.
2789
+ * Defaults call functions Shapio registers on every connection (`db/sqlite/functions.ts`).
2790
+ */
2791
+ const STATEMENTS = [
2792
+ `create table "system_versions" (
2793
+ "id" integer_boolean not null default 1,
2794
+ "schema_version" integer not null default 0,
2795
+ "permissions_version" integer not null default 0,
2796
+ "updated_at" text_timestamptz not null default (shapio_now()),
2797
+ constraint "system_versions_id_check" CHECK (id),
2798
+ constraint "system_versions_pkey" primary key (id)
2799
+ )`,
2800
+ `create table "jobs" (
2801
+ "id" text_uuid not null default (gen_random_uuid()),
2802
+ "type" text not null,
2803
+ "payload" text_jsonb not null default ('{}'),
2804
+ "status" text not null default ('pending'),
2805
+ "priority" integer not null default 0,
2806
+ "run_at" text_timestamptz not null default (shapio_now()),
2807
+ "attempts" integer not null default 0,
2808
+ "max_attempts" integer not null default 10,
2809
+ "idempotency_key" text,
2810
+ "locked_by" text,
2811
+ "locked_until" text_timestamptz,
2812
+ "checkpoint" text_jsonb,
2813
+ "result" text_jsonb,
2814
+ "last_error" text,
2815
+ "created_at" text_timestamptz not null default (shapio_now()),
2816
+ "updated_at" text_timestamptz not null default (shapio_now()),
2817
+ "finished_at" text_timestamptz,
2818
+ constraint "jobs_max_attempts_check" CHECK ((max_attempts > 0)),
2819
+ constraint "jobs_status_check" CHECK ((status IN ('pending', 'running', 'succeeded', 'dead'))),
2820
+ constraint "jobs_pkey" primary key (id)
2821
+ )`,
2822
+ `create table "outbox_events" (
2823
+ "id" integer primary key autoincrement,
2824
+ "event_id" text_uuid not null default (gen_random_uuid()),
2825
+ "type" text not null,
2826
+ "aggregate_type" text not null,
2827
+ "aggregate_id" text not null,
2828
+ "payload" text_jsonb not null default ('{}'),
2829
+ "created_at" text_timestamptz not null default (shapio_now()),
2830
+ "dispatched_at" text_timestamptz,
2831
+ "dispatch_attempts" integer not null default 0,
2832
+ "last_dispatch_error" text,
2833
+ "site_id" text_uuid,
2834
+ constraint "outbox_events_site_fk" foreign key (site_id) references sites (id) on delete set null
2835
+ )`,
2836
+ `create unique index "outbox_events_event_id_key" on "outbox_events" (event_id)`,
2837
+ `create table "audit_events" (
2838
+ "id" text_uuid not null default (gen_random_uuid()),
2839
+ "occurred_at" text_timestamptz not null default (shapio_now()),
2840
+ "actor_type" text not null,
2841
+ "actor_id" text,
2842
+ "action" text not null,
2843
+ "target_type" text,
2844
+ "target_id" text,
2845
+ "outcome" text not null,
2846
+ "request_id" text,
2847
+ "ip" text,
2848
+ "metadata" text_jsonb not null default ('{}'),
2849
+ "site_id" text_uuid,
2850
+ constraint "audit_events_actor_type_check" CHECK ((actor_type IN ('admin', 'app_user', 'token', 'anonymous', 'system'))),
2851
+ constraint "audit_events_outcome_check" CHECK ((outcome IN ('success', 'failure'))),
2852
+ constraint "audit_events_site_fk" foreign key (site_id) references sites (id) on delete set null,
2853
+ constraint "audit_events_pkey" primary key (id)
2854
+ )`,
2855
+ `create table "admin_users" (
2856
+ "id" text_uuid not null default (gen_random_uuid()),
2857
+ "email" text not null,
2858
+ "name" text not null default (''),
2859
+ "password_hash" text not null,
2860
+ "status" text not null default ('active'),
2861
+ "last_login_at" text_timestamptz,
2862
+ "password_changed_at" text_timestamptz not null default (shapio_now()),
2863
+ "created_at" text_timestamptz not null default (shapio_now()),
2864
+ "updated_at" text_timestamptz not null default (shapio_now()),
2865
+ constraint "admin_users_status_check" CHECK ((status IN ('active', 'disabled'))),
2866
+ constraint "admin_users_pkey" primary key (id)
2867
+ )`,
2868
+ `create unique index "admin_users_email_key" on "admin_users" (email)`,
2869
+ `create table "admin_roles" (
2870
+ "id" text_uuid not null default (gen_random_uuid()),
2871
+ "key" text not null,
2872
+ "name" text not null,
2873
+ "description" text not null default (''),
2874
+ "kind" text not null default ('admin'),
2875
+ "is_system" integer_boolean not null default 0,
2876
+ "version" integer not null default 1,
2877
+ "created_at" text_timestamptz not null default (shapio_now()),
2878
+ "updated_at" text_timestamptz not null default (shapio_now()),
2879
+ constraint "admin_roles_key_check" CHECK ((key REGEXP '^[a-z][a-z0-9-]{0,62}$')),
2880
+ constraint "admin_roles_kind_check" CHECK ((kind IN ('admin', 'delivery'))),
2881
+ constraint "admin_roles_pkey" primary key (id)
2882
+ )`,
2883
+ `create unique index "admin_roles_key_key" on "admin_roles" (key)`,
2884
+ `create table "admin_role_permissions" (
2885
+ "id" text_uuid not null default (gen_random_uuid()),
2886
+ "role_id" text_uuid not null,
2887
+ "action" text not null,
2888
+ "model_id" text,
2889
+ "condition" text,
2890
+ "field_ids" text_array,
2891
+ "created_at" text_timestamptz not null default (shapio_now()),
2892
+ "updated_at" text_timestamptz not null default (shapio_now()),
2893
+ constraint "admin_role_permissions_condition_check" CHECK ((condition = 'ownedByPrincipal')),
2894
+ constraint "admin_role_permissions_role_id_fkey" foreign key (role_id) references admin_roles (id) on delete cascade,
2895
+ constraint "admin_role_permissions_pkey" primary key (id)
2896
+ )`,
2897
+ `create unique index "admin_role_permissions_grant_uq" on "admin_role_permissions" (role_id, action, coalesce(model_id, ''))`,
2898
+ `create table "admin_user_roles" (
2899
+ "admin_user_id" text_uuid not null,
2900
+ "role_id" text_uuid not null,
2901
+ "created_at" text_timestamptz not null default (shapio_now()),
2902
+ "updated_at" text_timestamptz not null default (shapio_now()),
2903
+ "site_id" text_uuid,
2904
+ "id" text_uuid not null default (gen_random_uuid()),
2905
+ constraint "admin_user_roles_admin_user_id_fkey" foreign key (admin_user_id) references admin_users (id) on delete cascade,
2906
+ constraint "admin_user_roles_role_id_fkey" foreign key (role_id) references admin_roles (id) on delete restrict,
2907
+ constraint "admin_user_roles_site_fk" foreign key (site_id) references sites (id) on delete cascade,
2908
+ constraint "admin_user_roles_pk" primary key (id)
2909
+ )`,
2910
+ `create unique index "admin_user_roles_assignment_uq" on "admin_user_roles" (admin_user_id, role_id, coalesce(site_id, ''))`,
2911
+ `create table "admin_sessions" (
2912
+ "id" text_uuid not null default (gen_random_uuid()),
2913
+ "admin_user_id" text_uuid not null,
2914
+ "token_hash" text not null,
2915
+ "csrf_secret" text not null,
2916
+ "ip" text,
2917
+ "user_agent" text,
2918
+ "last_seen_at" text_timestamptz not null default (shapio_now()),
2919
+ "expires_at" text_timestamptz not null,
2920
+ "revoked_at" text_timestamptz,
2921
+ "rotation_required" integer_boolean not null default 0,
2922
+ "created_at" text_timestamptz not null default (shapio_now()),
2923
+ "updated_at" text_timestamptz not null default (shapio_now()),
2924
+ constraint "admin_sessions_admin_user_id_fkey" foreign key (admin_user_id) references admin_users (id) on delete cascade,
2925
+ constraint "admin_sessions_pkey" primary key (id)
2926
+ )`,
2927
+ `create unique index "admin_sessions_token_hash_key" on "admin_sessions" (token_hash)`,
2928
+ `create table "admin_invitations" (
2929
+ "id" text_uuid not null default (gen_random_uuid()),
2930
+ "email" text not null,
2931
+ "token_hash" text,
2932
+ "invited_by" text_uuid,
2933
+ "expires_at" text_timestamptz not null,
2934
+ "accepted_at" text_timestamptz,
2935
+ "revoked_at" text_timestamptz,
2936
+ "created_at" text_timestamptz not null default (shapio_now()),
2937
+ "updated_at" text_timestamptz not null default (shapio_now()),
2938
+ "role_assignments" text_jsonb not null default ('[]'),
2939
+ constraint "admin_invitations_invited_by_fkey" foreign key (invited_by) references admin_users (id) on delete set null,
2940
+ constraint "admin_invitations_pkey" primary key (id)
2941
+ )`,
2942
+ `create unique index "admin_invitations_token_hash_key" on "admin_invitations" (token_hash)`,
2943
+ `create table "password_resets" (
2944
+ "id" text_uuid not null default (gen_random_uuid()),
2945
+ "admin_user_id" text_uuid not null,
2946
+ "token_hash" text,
2947
+ "expires_at" text_timestamptz not null,
2948
+ "used_at" text_timestamptz,
2949
+ "created_at" text_timestamptz not null default (shapio_now()),
2950
+ "updated_at" text_timestamptz not null default (shapio_now()),
2951
+ constraint "password_resets_admin_user_id_fkey" foreign key (admin_user_id) references admin_users (id) on delete cascade,
2952
+ constraint "password_resets_pkey" primary key (id)
2953
+ )`,
2954
+ `create unique index "password_resets_token_hash_key" on "password_resets" (token_hash)`,
2955
+ `create table "setup_tokens" (
2956
+ "id" text_uuid not null default (gen_random_uuid()),
2957
+ "token_hash" text not null,
2958
+ "used_at" text_timestamptz,
2959
+ "superseded_at" text_timestamptz,
2960
+ "created_at" text_timestamptz not null default (shapio_now()),
2961
+ "updated_at" text_timestamptz not null default (shapio_now()),
2962
+ constraint "setup_tokens_pkey" primary key (id)
2963
+ )`,
2964
+ `create unique index "setup_tokens_token_hash_key" on "setup_tokens" (token_hash)`,
2965
+ `create table "api_tokens" (
2966
+ "id" text_uuid not null default (gen_random_uuid()),
2967
+ "name" text not null,
2968
+ "token_hash" text not null,
2969
+ "token_prefix" text not null,
2970
+ "role_id" text_uuid not null,
2971
+ "created_by" text_uuid,
2972
+ "expires_at" text_timestamptz,
2973
+ "last_used_at" text_timestamptz,
2974
+ "revoked_at" text_timestamptz,
2975
+ "created_at" text_timestamptz not null default (shapio_now()),
2976
+ "updated_at" text_timestamptz not null default (shapio_now()),
2977
+ "site_id" text_uuid,
2978
+ constraint "api_tokens_created_by_fkey" foreign key (created_by) references admin_users (id) on delete set null,
2979
+ constraint "api_tokens_role_id_fkey" foreign key (role_id) references admin_roles (id) on delete restrict,
2980
+ constraint "api_tokens_site_fk" foreign key (site_id) references sites (id) on delete cascade,
2981
+ constraint "api_tokens_pkey" primary key (id)
2982
+ )`,
2983
+ `create unique index "api_tokens_token_hash_key" on "api_tokens" (token_hash)`,
2984
+ `create table "system_settings" (
2985
+ "key" text not null,
2986
+ "value" text not null,
2987
+ "updated_at" text_timestamptz not null default (shapio_now()),
2988
+ constraint "system_settings_pkey" primary key (key)
2989
+ )`,
2990
+ `create table "models" (
2991
+ "id" text_uuid not null,
2992
+ "kind" text not null,
2993
+ "api_key" text not null,
2994
+ "created_at" text_timestamptz not null default (shapio_now()),
2995
+ "updated_at" text_timestamptz not null default (shapio_now()),
2996
+ "deleted_at" text_timestamptz,
2997
+ constraint "models_kind_check" CHECK ((kind IN ('collection', 'singleton', 'component'))),
2998
+ constraint "models_pkey" primary key (id)
2999
+ )`,
3000
+ `create table "schema_revisions" (
3001
+ "id" text_uuid not null default (gen_random_uuid()),
3002
+ "model_id" text_uuid not null,
3003
+ "version" integer not null,
3004
+ "definition" text_jsonb not null,
3005
+ "hash" text not null,
3006
+ "parent_revision_id" text_uuid,
3007
+ "created_by_type" text not null,
3008
+ "created_by_id" text,
3009
+ "created_at" text_timestamptz not null default (shapio_now()),
3010
+ constraint "schema_revisions_version_check" CHECK ((version > 0)),
3011
+ constraint "schema_revisions_model_id_fkey" foreign key (model_id) references models (id) on delete restrict,
3012
+ constraint "schema_revisions_parent_revision_id_fkey" foreign key (parent_revision_id) references schema_revisions (id),
3013
+ constraint "schema_revisions_pkey" primary key (id)
3014
+ )`,
3015
+ `create unique index "schema_revisions_model_version_uq" on "schema_revisions" (model_id, version)`,
3016
+ `create table "model_active_versions" (
3017
+ "model_id" text_uuid not null,
3018
+ "revision_id" text_uuid not null,
3019
+ "version" integer not null,
3020
+ "schema_version" integer not null,
3021
+ "activated_at" text_timestamptz not null default (shapio_now()),
3022
+ constraint "model_active_versions_model_id_fkey" foreign key (model_id) references models (id) on delete cascade,
3023
+ constraint "model_active_versions_revision_id_fkey" foreign key (revision_id) references schema_revisions (id),
3024
+ constraint "model_active_versions_pkey" primary key (model_id)
3025
+ )`,
3026
+ `create table "schema_change_jobs" (
3027
+ "id" text_uuid not null default (gen_random_uuid()),
3028
+ "target_type" text not null,
3029
+ "target_id" text not null,
3030
+ "model_id" text_uuid,
3031
+ "from_revision_id" text_uuid,
3032
+ "to_revision_id" text_uuid,
3033
+ "status" text not null default ('pending'),
3034
+ "plan" text_jsonb not null default ('{}'),
3035
+ "job_id" text_uuid,
3036
+ "error" text_jsonb,
3037
+ "requested_by_type" text not null,
3038
+ "requested_by_id" text,
3039
+ "created_at" text_timestamptz not null default (shapio_now()),
3040
+ "updated_at" text_timestamptz not null default (shapio_now()),
3041
+ "finished_at" text_timestamptz,
3042
+ "change_set_id" text_uuid,
3043
+ constraint "schema_change_jobs_status_check" CHECK ((status IN ('pending', 'running', 'activated', 'failed', 'cancelled'))),
3044
+ constraint "schema_change_jobs_target_type_check" CHECK ((target_type IN ('model', 'locale'))),
3045
+ constraint "schema_change_jobs_change_set_id_fkey" foreign key (change_set_id) references change_sets (id) on delete set null,
3046
+ constraint "schema_change_jobs_from_revision_id_fkey" foreign key (from_revision_id) references schema_revisions (id),
3047
+ constraint "schema_change_jobs_job_id_fkey" foreign key (job_id) references jobs (id) on delete set null,
3048
+ constraint "schema_change_jobs_model_id_fkey" foreign key (model_id) references models (id),
3049
+ constraint "schema_change_jobs_to_revision_id_fkey" foreign key (to_revision_id) references schema_revisions (id),
3050
+ constraint "schema_change_jobs_pkey" primary key (id)
3051
+ )`,
3052
+ `create table "locales" (
3053
+ "code" text not null,
3054
+ "label" text not null,
3055
+ "is_default" integer_boolean not null default 0,
3056
+ "fallbacks" text_array not null default ('[]'),
3057
+ "created_at" text_timestamptz not null default (shapio_now()),
3058
+ "updated_at" text_timestamptz not null default (shapio_now()),
3059
+ constraint "locales_pkey" primary key (code)
3060
+ )`,
3061
+ `create table "schema_settings" (
3062
+ "id" integer_boolean not null default 1,
3063
+ "read_only" integer_boolean not null default 0,
3064
+ "read_only_reason" text,
3065
+ "updated_by_type" text,
3066
+ "updated_by_id" text,
3067
+ "updated_at" text_timestamptz not null default (shapio_now()),
3068
+ constraint "schema_settings_id_check" CHECK (id),
3069
+ constraint "schema_settings_pkey" primary key (id)
3070
+ )`,
3071
+ `create table "entries" (
3072
+ "id" text_uuid not null default (gen_random_uuid()),
3073
+ "model_id" text_uuid not null,
3074
+ "owner_app_user_id" text_uuid,
3075
+ "created_by_admin_id" text_uuid,
3076
+ "created_at" text_timestamptz not null default (shapio_now()),
3077
+ "updated_at" text_timestamptz not null default (shapio_now()),
3078
+ "deleted_at" text_timestamptz,
3079
+ "site_id" text_uuid not null,
3080
+ constraint "entries_created_by_admin_id_fkey" foreign key (created_by_admin_id) references admin_users (id) on delete set null,
3081
+ constraint "entries_model_id_fkey" foreign key (model_id) references models (id) on delete restrict,
3082
+ constraint "entries_owner_app_user_fk" foreign key (owner_app_user_id) references app_users (id) on delete set null,
3083
+ constraint "entries_site_fk" foreign key (site_id) references sites (id) on delete restrict,
3084
+ constraint "entries_pkey" primary key (id)
3085
+ )`,
3086
+ `create unique index "entries_id_site_uq" on "entries" (id, site_id)`,
3087
+ `create table "content_revisions" (
3088
+ "id" text_uuid not null default (gen_random_uuid()),
3089
+ "entry_id" text_uuid not null,
3090
+ "locale" text not null,
3091
+ "schema_revision_id" text_uuid not null,
3092
+ "data" text_jsonb not null,
3093
+ "reason" text not null,
3094
+ "author_type" text not null,
3095
+ "author_id" text,
3096
+ "parent_revision_id" text_uuid,
3097
+ "created_at" text_timestamptz not null default (shapio_now()),
3098
+ constraint "content_revisions_reason_check" CHECK ((reason IN ('create', 'save', 'publish', 'restore', 'duplicate', 'localize', 'conversion'))),
3099
+ constraint "content_revisions_entry_id_fkey" foreign key (entry_id) references entries (id) on delete restrict,
3100
+ constraint "content_revisions_parent_revision_id_fkey" foreign key (parent_revision_id) references content_revisions (id),
3101
+ constraint "content_revisions_schema_revision_id_fkey" foreign key (schema_revision_id) references schema_revisions (id),
3102
+ constraint "content_revisions_pkey" primary key (id)
3103
+ )`,
3104
+ `create table "entry_heads" (
3105
+ "entry_id" text_uuid not null,
3106
+ "model_id" text_uuid not null,
3107
+ "locale" text not null,
3108
+ "state" text not null,
3109
+ "revision_id" text_uuid not null,
3110
+ "data" text_jsonb not null,
3111
+ "autosaved_at" text_timestamptz,
3112
+ "version" integer not null default 1,
3113
+ "change_seq" bigint not null default (shapio_nextval('entry_heads_change_seq')),
3114
+ "created_at" text_timestamptz not null default (shapio_now()),
3115
+ "updated_at" text_timestamptz not null default (shapio_now()),
3116
+ "site_id" text_uuid not null,
3117
+ constraint "entry_heads_state_check" CHECK ((state IN ('draft', 'published'))),
3118
+ constraint "entry_heads_entry_id_fkey" foreign key (entry_id) references entries (id) on delete cascade,
3119
+ constraint "entry_heads_entry_site_fk" foreign key (entry_id, site_id) references entries (id, site_id) on delete cascade,
3120
+ constraint "entry_heads_revision_id_fkey" foreign key (revision_id) references content_revisions (id),
3121
+ constraint "entry_heads_site_fk" foreign key (site_id) references sites (id) on delete restrict,
3122
+ constraint "entry_heads_pkey" primary key (entry_id, locale, state)
3123
+ )`,
3124
+ `create table "relation_edges" (
3125
+ "source_entry_id" text_uuid not null,
3126
+ "locale" text not null,
3127
+ "state" text not null,
3128
+ "field_id" text_uuid not null,
3129
+ "position" integer not null,
3130
+ "target_entry_id" text_uuid not null,
3131
+ constraint "relation_edges_head_fk" foreign key (source_entry_id, locale, state) references entry_heads (entry_id, locale, state) on delete cascade,
3132
+ constraint "relation_edges_target_entry_id_fkey" foreign key (target_entry_id) references entries (id),
3133
+ constraint "relation_edges_pkey" primary key (source_entry_id, locale, state, field_id, "position")
3134
+ )`,
3135
+ `create table "unique_values" (
3136
+ "field_id" text_uuid not null,
3137
+ "locale" text not null,
3138
+ "state" text not null,
3139
+ "value_hash" text not null,
3140
+ "entry_id" text_uuid not null,
3141
+ "model_id" text_uuid not null,
3142
+ "site_id" text_uuid not null,
3143
+ constraint "unique_values_state_check" CHECK ((state IN ('draft', 'published'))),
3144
+ constraint "unique_values_entry_id_fkey" foreign key (entry_id) references entries (id) on delete cascade,
3145
+ constraint "unique_values_entry_site_fk" foreign key (entry_id, site_id) references entries (id, site_id) on delete cascade,
3146
+ constraint "unique_values_site_fk" foreign key (site_id) references sites (id) on delete restrict,
3147
+ constraint "unique_values_pkey" primary key (site_id, field_id, locale, state, value_hash)
3148
+ )`,
3149
+ `create table "publication_state" (
3150
+ "last_seq" bigint not null default 0,
3151
+ "site_id" text_uuid not null,
3152
+ constraint "publication_state_site_fk" foreign key (site_id) references sites (id) on delete cascade,
3153
+ constraint "publication_state_pkey" primary key (site_id)
3154
+ )`,
3155
+ `create table "publication_log" (
3156
+ "id" integer primary key autoincrement,
3157
+ "entry_id" text_uuid not null,
3158
+ "model_id" text_uuid not null,
3159
+ "locale" text not null,
3160
+ "revision_id" text_uuid not null,
3161
+ "from_seq" bigint not null,
3162
+ "to_seq" bigint,
3163
+ "published_at" text_timestamptz not null default (shapio_now()),
3164
+ "site_id" text_uuid not null,
3165
+ constraint "publication_log_entry_id_fkey" foreign key (entry_id) references entries (id),
3166
+ constraint "publication_log_entry_site_fk" foreign key (entry_id, site_id) references entries (id, site_id) on delete restrict,
3167
+ constraint "publication_log_revision_id_fkey" foreign key (revision_id) references content_revisions (id),
3168
+ constraint "publication_log_site_fk" foreign key (site_id) references sites (id) on delete restrict
3169
+ )`,
3170
+ `create table "media_folders" (
3171
+ "id" text_uuid not null default (gen_random_uuid()),
3172
+ "parent_id" text_uuid,
3173
+ "name" text not null,
3174
+ "created_by" text_uuid,
3175
+ "version" integer not null default 1,
3176
+ "created_at" text_timestamptz not null default (shapio_now()),
3177
+ "updated_at" text_timestamptz not null default (shapio_now()),
3178
+ "site_id" text_uuid not null,
3179
+ constraint "media_folders_name_check" CHECK (((length(name) >= 1) AND (length(name) <= 255))),
3180
+ constraint "media_folders_created_by_fkey" foreign key (created_by) references admin_users (id) on delete set null,
3181
+ constraint "media_folders_parent_id_fkey" foreign key (parent_id, site_id) references media_folders (id, site_id) on delete restrict,
3182
+ constraint "media_folders_site_fk" foreign key (site_id) references sites (id) on delete restrict,
3183
+ constraint "media_folders_pkey" primary key (id)
3184
+ )`,
3185
+ `create unique index "media_folders_id_site_uq" on "media_folders" (id, site_id)`,
3186
+ `create table "media_assets" (
3187
+ "id" text_uuid not null,
3188
+ "folder_id" text_uuid,
3189
+ "storage_driver" text not null,
3190
+ "storage_key" text not null,
3191
+ "original_filename" text not null,
3192
+ "mime_type" text not null,
3193
+ "size_bytes" bigint not null,
3194
+ "width" integer,
3195
+ "height" integer,
3196
+ "checksum_sha256" text,
3197
+ "alt" text not null default (''),
3198
+ "caption" text not null default (''),
3199
+ "focal_x" real,
3200
+ "focal_y" real,
3201
+ "visibility" text not null default ('public'),
3202
+ "status" text not null default ('processing'),
3203
+ "processing_error" text,
3204
+ "created_by" text_uuid,
3205
+ "version" integer not null default 1,
3206
+ "deleted_at" text_timestamptz,
3207
+ "created_at" text_timestamptz not null default (shapio_now()),
3208
+ "updated_at" text_timestamptz not null default (shapio_now()),
3209
+ "site_id" text_uuid not null,
3210
+ constraint "media_assets_focal_point_ck" CHECK (((focal_x IS NULL) = (focal_y IS NULL))),
3211
+ constraint "media_assets_focal_x_check" CHECK (((focal_x >= 0) AND (focal_x <= 1))),
3212
+ constraint "media_assets_focal_y_check" CHECK (((focal_y >= 0) AND (focal_y <= 1))),
3213
+ constraint "media_assets_status_check" CHECK ((status IN ('processing', 'ready', 'failed'))),
3214
+ constraint "media_assets_storage_driver_check" CHECK ((storage_driver IN ('local', 's3'))),
3215
+ constraint "media_assets_visibility_check" CHECK ((visibility IN ('public', 'private'))),
3216
+ constraint "media_assets_created_by_fkey" foreign key (created_by) references admin_users (id) on delete set null,
3217
+ constraint "media_assets_folder_id_fkey" foreign key (folder_id, site_id) references media_folders (id, site_id),
3218
+ constraint "media_assets_site_fk" foreign key (site_id) references sites (id) on delete restrict,
3219
+ constraint "media_assets_pkey" primary key (id)
3220
+ )`,
3221
+ `create table "media_variants" (
3222
+ "id" text_uuid not null default (gen_random_uuid()),
3223
+ "asset_id" text_uuid not null,
3224
+ "name" text not null,
3225
+ "width" integer,
3226
+ "height" integer,
3227
+ "format" text not null,
3228
+ "mime_type" text not null,
3229
+ "storage_key" text,
3230
+ "size_bytes" bigint,
3231
+ "checksum_sha256" text,
3232
+ "status" text not null default ('pending'),
3233
+ "error" text,
3234
+ "created_at" text_timestamptz not null default (shapio_now()),
3235
+ "updated_at" text_timestamptz not null default (shapio_now()),
3236
+ constraint "media_variants_status_check" CHECK ((status IN ('pending', 'ready', 'failed'))),
3237
+ constraint "media_variants_asset_id_fkey" foreign key (asset_id) references media_assets (id) on delete cascade,
3238
+ constraint "media_variants_pkey" primary key (id)
3239
+ )`,
3240
+ `create unique index "media_variants_asset_name_uq" on "media_variants" (asset_id, name)`,
3241
+ `create table "media_references" (
3242
+ "asset_id" text_uuid not null,
3243
+ "entry_id" text_uuid not null,
3244
+ "model_id" text_uuid not null,
3245
+ "field_id" text not null,
3246
+ "locale" text not null,
3247
+ "state" text not null,
3248
+ "created_at" text_timestamptz not null default (shapio_now()),
3249
+ constraint "media_references_state_check" CHECK ((state IN ('draft', 'published'))),
3250
+ constraint "media_references_asset_id_fkey" foreign key (asset_id) references media_assets (id) on delete cascade,
3251
+ constraint "media_references_entry_id_fkey" foreign key (entry_id) references entries (id) on delete cascade,
3252
+ constraint "media_references_pk" primary key (asset_id, entry_id, locale, state, field_id)
3253
+ )`,
3254
+ `create table "media_upload_grants" (
3255
+ "id" text_uuid not null default (gen_random_uuid()),
3256
+ "asset_id" text_uuid not null,
3257
+ "kind" text not null,
3258
+ "storage_driver" text not null,
3259
+ "storage_key" text not null,
3260
+ "original_filename" text not null,
3261
+ "declared_mime_type" text not null,
3262
+ "max_size_bytes" bigint not null,
3263
+ "folder_id" text_uuid,
3264
+ "visibility" text not null,
3265
+ "created_by" text_uuid,
3266
+ "created_by_principal" text not null,
3267
+ "status" text not null default ('pending'),
3268
+ "expires_at" text_timestamptz not null,
3269
+ "consumed_at" text_timestamptz,
3270
+ "created_at" text_timestamptz not null default (shapio_now()),
3271
+ "site_id" text_uuid not null,
3272
+ constraint "media_upload_grants_kind_check" CHECK ((kind IN ('create', 'replace'))),
3273
+ constraint "media_upload_grants_status_check" CHECK ((status IN ('pending', 'consumed', 'rejected', 'expired'))),
3274
+ constraint "media_upload_grants_storage_driver_check" CHECK ((storage_driver IN ('local', 's3'))),
3275
+ constraint "media_upload_grants_visibility_check" CHECK ((visibility IN ('public', 'private'))),
3276
+ constraint "media_upload_grants_created_by_fkey" foreign key (created_by) references admin_users (id) on delete set null,
3277
+ constraint "media_upload_grants_site_fk" foreign key (site_id) references sites (id) on delete cascade,
3278
+ constraint "media_upload_grants_pkey" primary key (id)
3279
+ )`,
3280
+ `create unique index "media_upload_grants_storage_key_key" on "media_upload_grants" (storage_key)`,
3281
+ `create table "scheduled_publications" (
3282
+ "id" text_uuid not null default (gen_random_uuid()),
3283
+ "entry_id" text_uuid not null,
3284
+ "model_id" text_uuid not null,
3285
+ "locale" text not null,
3286
+ "action" text not null,
3287
+ "run_at" text_timestamptz not null,
3288
+ "status" text not null default ('scheduled'),
3289
+ "created_by_token" text_uuid,
3290
+ "job_id" text_uuid,
3291
+ "snapshot_seq" bigint,
3292
+ "error" text,
3293
+ "executed_at" text_timestamptz,
3294
+ "created_by" text_uuid,
3295
+ "created_at" text_timestamptz not null default (shapio_now()),
3296
+ "updated_at" text_timestamptz not null default (shapio_now()),
3297
+ "site_id" text_uuid not null,
3298
+ constraint "scheduled_publications_action_check" CHECK ((action IN ('publish', 'unpublish'))),
3299
+ constraint "scheduled_publications_status_check" CHECK ((status IN ('scheduled', 'done', 'failed', 'cancelled'))),
3300
+ constraint "scheduled_publications_created_by_fkey" foreign key (created_by) references admin_users (id) on delete set null,
3301
+ constraint "scheduled_publications_created_by_token_fkey" foreign key (created_by_token) references api_tokens (id) on delete set null,
3302
+ constraint "scheduled_publications_entry_id_fkey" foreign key (entry_id) references entries (id) on delete cascade,
3303
+ constraint "scheduled_publications_entry_site_fk" foreign key (entry_id, site_id) references entries (id, site_id) on delete cascade,
3304
+ constraint "scheduled_publications_site_fk" foreign key (site_id) references sites (id) on delete restrict,
3305
+ constraint "scheduled_publications_pkey" primary key (id)
3306
+ )`,
3307
+ `create table "webhooks" (
3308
+ "id" text_uuid not null default (gen_random_uuid()),
3309
+ "name" text not null,
3310
+ "url" text not null,
3311
+ "events" text_array not null default ('[]'),
3312
+ "enabled" integer_boolean not null default 1,
3313
+ "secret_encrypted" text not null,
3314
+ "allow_private_network" integer_boolean not null default 0,
3315
+ "max_attempts" integer not null default 8,
3316
+ "version" integer not null default 1,
3317
+ "created_by" text_uuid,
3318
+ "created_at" text_timestamptz not null default (shapio_now()),
3319
+ "updated_at" text_timestamptz not null default (shapio_now()),
3320
+ "site_id" text_uuid,
3321
+ constraint "webhooks_max_attempts_check" CHECK (((max_attempts >= 1) AND (max_attempts <= 20))),
3322
+ constraint "webhooks_name_check" CHECK (((length(name) >= 1) AND (length(name) <= 200))),
3323
+ constraint "webhooks_created_by_fkey" foreign key (created_by) references admin_users (id) on delete set null,
3324
+ constraint "webhooks_site_fk" foreign key (site_id) references sites (id) on delete cascade,
3325
+ constraint "webhooks_pkey" primary key (id)
3326
+ )`,
3327
+ `create table "webhook_deliveries" (
3328
+ "id" text_uuid not null default (gen_random_uuid()),
3329
+ "webhook_id" text_uuid not null,
3330
+ "event_id" text_uuid,
3331
+ "event_type" text not null,
3332
+ "payload" text_jsonb not null,
3333
+ "is_test" integer_boolean not null default 0,
3334
+ "status" text not null default ('pending'),
3335
+ "attempts" integer not null default 0,
3336
+ "last_response_status" integer,
3337
+ "last_error" text,
3338
+ "attempt_log" text_jsonb not null default ('[]'),
3339
+ "job_id" text_uuid,
3340
+ "delivered_at" text_timestamptz,
3341
+ "created_at" text_timestamptz not null default (shapio_now()),
3342
+ "updated_at" text_timestamptz not null default (shapio_now()),
3343
+ constraint "webhook_deliveries_status_check" CHECK ((status IN ('pending', 'retrying', 'succeeded', 'dead'))),
3344
+ constraint "webhook_deliveries_webhook_id_fkey" foreign key (webhook_id) references webhooks (id) on delete cascade,
3345
+ constraint "webhook_deliveries_pkey" primary key (id)
3346
+ )`,
3347
+ `create table "deployment_connections" (
3348
+ "id" text_uuid not null default (gen_random_uuid()),
3349
+ "name" text not null,
3350
+ "provider" text not null,
3351
+ "settings" text_jsonb not null default ('{}'),
3352
+ "secrets_encrypted" text not null,
3353
+ "secret_env_refs" text_jsonb not null default ('{}'),
3354
+ "delivery_role_id" text_uuid,
3355
+ "preview_url_template" text,
3356
+ "trigger_policy" text_array not null default ('[]'),
3357
+ "debounce_seconds" integer not null default 10,
3358
+ "allow_private_network" integer_boolean not null default 0,
3359
+ "enabled" integer_boolean not null default 1,
3360
+ "version" integer not null default 1,
3361
+ "created_by" text_uuid,
3362
+ "created_at" text_timestamptz not null default (shapio_now()),
3363
+ "updated_at" text_timestamptz not null default (shapio_now()),
3364
+ "site_id" text_uuid not null,
3365
+ constraint "deployment_connections_debounce_seconds_check" CHECK (((debounce_seconds >= 0) AND (debounce_seconds <= 3600))),
3366
+ constraint "deployment_connections_name_check" CHECK (((length(name) >= 1) AND (length(name) <= 200))),
3367
+ constraint "deployment_connections_provider_check" CHECK ((provider IN ('generic_webhook', 'cloudflare_pages', 'vercel', 'netlify', 'github'))),
3368
+ constraint "deployment_connections_created_by_fkey" foreign key (created_by) references admin_users (id) on delete set null,
3369
+ constraint "deployment_connections_delivery_role_id_fkey" foreign key (delivery_role_id) references admin_roles (id) on delete set null,
3370
+ constraint "deployment_connections_site_fk" foreign key (site_id) references sites (id) on delete cascade,
3371
+ constraint "deployment_connections_pkey" primary key (id)
3372
+ )`,
3373
+ `create table "deployment_runs" (
3374
+ "id" text_uuid not null default (gen_random_uuid()),
3375
+ "connection_id" text_uuid not null,
3376
+ "status" text not null default ('queued'),
3377
+ "status_rank" integer not null default 0,
3378
+ "trigger" text not null,
3379
+ "snapshot_seq" bigint,
3380
+ "schema_version" integer,
3381
+ "retry_of" text_uuid,
3382
+ "provider_ref" text,
3383
+ "log_url" text,
3384
+ "site_url" text,
3385
+ "error" text,
3386
+ "timeline" text_jsonb not null default ('[]'),
3387
+ "job_id" text_uuid,
3388
+ "triggered_at" text_timestamptz,
3389
+ "finished_at" text_timestamptz,
3390
+ "created_by" text_uuid,
3391
+ "created_at" text_timestamptz not null default (shapio_now()),
3392
+ "updated_at" text_timestamptz not null default (shapio_now()),
3393
+ "change_set_id" text_uuid,
3394
+ constraint "deployment_runs_status_check" CHECK ((status IN ('queued', 'triggered', 'building', 'deployed', 'failed', 'unknown'))),
3395
+ constraint "deployment_runs_trigger_check" CHECK ((trigger IN ('publish', 'change_set', 'schema', 'manual', 'retry'))),
3396
+ constraint "deployment_runs_change_set_id_fkey" foreign key (change_set_id) references change_sets (id) on delete set null,
3397
+ constraint "deployment_runs_connection_id_fkey" foreign key (connection_id) references deployment_connections (id) on delete cascade,
3398
+ constraint "deployment_runs_created_by_fkey" foreign key (created_by) references admin_users (id) on delete set null,
3399
+ constraint "deployment_runs_retry_of_fkey" foreign key (retry_of) references deployment_runs (id) on delete set null,
3400
+ constraint "deployment_runs_pkey" primary key (id)
3401
+ )`,
3402
+ `create table "preview_tokens" (
3403
+ "id" text_uuid not null default (gen_random_uuid()),
3404
+ "token_hash" text not null,
3405
+ "token_prefix" text not null,
3406
+ "model_id" text_uuid not null,
3407
+ "entry_id" text_uuid not null,
3408
+ "locale" text,
3409
+ "connection_id" text_uuid,
3410
+ "delivery_role_id" text_uuid,
3411
+ "created_by" text_uuid not null,
3412
+ "expires_at" text_timestamptz not null,
3413
+ "revoked_at" text_timestamptz,
3414
+ "last_used_at" text_timestamptz,
3415
+ "created_at" text_timestamptz not null default (shapio_now()),
3416
+ "site_id" text_uuid not null,
3417
+ constraint "preview_tokens_connection_id_fkey" foreign key (connection_id) references deployment_connections (id) on delete set null,
3418
+ constraint "preview_tokens_created_by_fkey" foreign key (created_by) references admin_users (id) on delete cascade,
3419
+ constraint "preview_tokens_delivery_role_id_fkey" foreign key (delivery_role_id) references admin_roles (id) on delete set null,
3420
+ constraint "preview_tokens_entry_id_fkey" foreign key (entry_id) references entries (id) on delete cascade,
3421
+ constraint "preview_tokens_entry_site_fk" foreign key (entry_id, site_id) references entries (id, site_id) on delete cascade,
3422
+ constraint "preview_tokens_site_fk" foreign key (site_id) references sites (id) on delete cascade,
3423
+ constraint "preview_tokens_pkey" primary key (id)
3424
+ )`,
3425
+ `create unique index "preview_tokens_token_hash_key" on "preview_tokens" (token_hash)`,
3426
+ `create table "app_users" (
3427
+ "id" text_uuid not null default (gen_random_uuid()),
3428
+ "email" text not null,
3429
+ "name" text not null default (''),
3430
+ "password_hash" text,
3431
+ "confirmed_at" text_timestamptz,
3432
+ "blocked_at" text_timestamptz,
3433
+ "last_login_at" text_timestamptz,
3434
+ "password_changed_at" text_timestamptz,
3435
+ "deleted_at" text_timestamptz,
3436
+ "created_at" text_timestamptz not null default (shapio_now()),
3437
+ "updated_at" text_timestamptz not null default (shapio_now()),
3438
+ "token_version" integer not null default 0,
3439
+ "site_id" text_uuid not null,
3440
+ constraint "app_users_site_fk" foreign key (site_id) references sites (id) on delete restrict,
3441
+ constraint "app_users_pkey" primary key (id)
3442
+ )`,
3443
+ `create table "app_roles" (
3444
+ "id" text_uuid not null default (gen_random_uuid()),
3445
+ "key" text not null,
3446
+ "name" text not null,
3447
+ "description" text not null default (''),
3448
+ "is_system" integer_boolean not null default 0,
3449
+ "version" integer not null default 1,
3450
+ "created_at" text_timestamptz not null default (shapio_now()),
3451
+ "updated_at" text_timestamptz not null default (shapio_now()),
3452
+ constraint "app_roles_key_check" CHECK ((key REGEXP '^[a-z][a-z0-9-]{0,62}$')),
3453
+ constraint "app_roles_pkey" primary key (id)
3454
+ )`,
3455
+ `create unique index "app_roles_key_key" on "app_roles" (key)`,
3456
+ `create table "app_role_permissions" (
3457
+ "id" text_uuid not null default (gen_random_uuid()),
3458
+ "role_id" text_uuid not null,
3459
+ "action" text not null,
3460
+ "model_id" text,
3461
+ "condition" text,
3462
+ "field_ids" text_array,
3463
+ "created_at" text_timestamptz not null default (shapio_now()),
3464
+ "updated_at" text_timestamptz not null default (shapio_now()),
3465
+ constraint "app_role_permissions_action_check" CHECK ((action IN ('read', 'create', 'update', 'delete', 'publish'))),
3466
+ constraint "app_role_permissions_condition_check" CHECK ((condition = 'ownedByPrincipal')),
3467
+ constraint "app_role_permissions_role_id_fkey" foreign key (role_id) references app_roles (id) on delete cascade,
3468
+ constraint "app_role_permissions_pkey" primary key (id)
3469
+ )`,
3470
+ `create unique index "app_role_permissions_grant_uq" on "app_role_permissions" (role_id, action, coalesce(model_id, ''))`,
3471
+ `create table "app_user_roles" (
3472
+ "app_user_id" text_uuid not null,
3473
+ "role_id" text_uuid not null,
3474
+ "created_at" text_timestamptz not null default (shapio_now()),
3475
+ "site_id" text_uuid not null,
3476
+ constraint "app_user_roles_app_user_id_fkey" foreign key (app_user_id) references app_users (id) on delete cascade,
3477
+ constraint "app_user_roles_role_id_fkey" foreign key (role_id) references app_roles (id) on delete restrict,
3478
+ constraint "app_user_roles_site_fk" foreign key (site_id) references sites (id) on delete restrict,
3479
+ constraint "app_user_roles_pk" primary key (app_user_id, role_id)
3480
+ )`,
3481
+ `create table "app_refresh_tokens" (
3482
+ "id" text_uuid not null default (gen_random_uuid()),
3483
+ "app_user_id" text_uuid not null,
3484
+ "family_id" text_uuid not null,
3485
+ "token_hash" text not null,
3486
+ "expires_at" text_timestamptz not null,
3487
+ "used_at" text_timestamptz,
3488
+ "reuse_grace_used_at" text_timestamptz,
3489
+ "revoked_at" text_timestamptz,
3490
+ "revoked_reason" text,
3491
+ "ip" text,
3492
+ "user_agent" text,
3493
+ "created_at" text_timestamptz not null default (shapio_now()),
3494
+ constraint "app_refresh_tokens_app_user_id_fkey" foreign key (app_user_id) references app_users (id) on delete cascade,
3495
+ constraint "app_refresh_tokens_pkey" primary key (id)
3496
+ )`,
3497
+ `create unique index "app_refresh_tokens_token_hash_key" on "app_refresh_tokens" (token_hash)`,
3498
+ `create table "app_oauth_accounts" (
3499
+ "id" text_uuid not null default (gen_random_uuid()),
3500
+ "app_user_id" text_uuid not null,
3501
+ "provider" text not null,
3502
+ "provider_user_id" text not null,
3503
+ "email" text,
3504
+ "created_at" text_timestamptz not null default (shapio_now()),
3505
+ "updated_at" text_timestamptz not null default (shapio_now()),
3506
+ "site_id" text_uuid not null,
3507
+ constraint "app_oauth_accounts_provider_check" CHECK ((provider IN ('google', 'github'))),
3508
+ constraint "app_oauth_accounts_app_user_id_fkey" foreign key (app_user_id) references app_users (id) on delete cascade,
3509
+ constraint "app_oauth_accounts_site_fk" foreign key (site_id) references sites (id) on delete restrict,
3510
+ constraint "app_oauth_accounts_pkey" primary key (id)
3511
+ )`,
3512
+ `create unique index "app_oauth_accounts_identity_uq" on "app_oauth_accounts" (site_id, provider, provider_user_id)`,
3513
+ `create table "email_confirmations" (
3514
+ "id" text_uuid not null default (gen_random_uuid()),
3515
+ "app_user_id" text_uuid not null,
3516
+ "email" text not null,
3517
+ "token_hash" text,
3518
+ "expires_at" text_timestamptz not null,
3519
+ "used_at" text_timestamptz,
3520
+ "created_at" text_timestamptz not null default (shapio_now()),
3521
+ "updated_at" text_timestamptz not null default (shapio_now()),
3522
+ constraint "email_confirmations_app_user_id_fkey" foreign key (app_user_id) references app_users (id) on delete cascade,
3523
+ constraint "email_confirmations_pkey" primary key (id)
3524
+ )`,
3525
+ `create unique index "email_confirmations_token_hash_key" on "email_confirmations" (token_hash)`,
3526
+ `create table "app_password_resets" (
3527
+ "id" text_uuid not null default (gen_random_uuid()),
3528
+ "app_user_id" text_uuid not null,
3529
+ "email" text not null,
3530
+ "token_hash" text,
3531
+ "expires_at" text_timestamptz not null,
3532
+ "used_at" text_timestamptz,
3533
+ "created_at" text_timestamptz not null default (shapio_now()),
3534
+ "updated_at" text_timestamptz not null default (shapio_now()),
3535
+ constraint "app_password_resets_app_user_id_fkey" foreign key (app_user_id) references app_users (id) on delete cascade,
3536
+ constraint "app_password_resets_pkey" primary key (id)
3537
+ )`,
3538
+ `create unique index "app_password_resets_token_hash_key" on "app_password_resets" (token_hash)`,
3539
+ `create table "app_login_codes" (
3540
+ "id" text_uuid not null default (gen_random_uuid()),
3541
+ "app_user_id" text_uuid not null,
3542
+ "code_hash" text not null,
3543
+ "expires_at" text_timestamptz not null,
3544
+ "used_at" text_timestamptz,
3545
+ "created_at" text_timestamptz not null default (shapio_now()),
3546
+ "code_challenge" text not null,
3547
+ constraint "app_login_codes_app_user_id_fkey" foreign key (app_user_id) references app_users (id) on delete cascade,
3548
+ constraint "app_login_codes_pkey" primary key (id)
3549
+ )`,
3550
+ `create unique index "app_login_codes_code_hash_key" on "app_login_codes" (code_hash)`,
3551
+ `create table "extension_hook_runs" (
3552
+ "event_id" text_uuid not null,
3553
+ "hook" text not null,
3554
+ "job_id" text_uuid,
3555
+ "completed_at" text_timestamptz not null default (shapio_now()),
3556
+ constraint "extension_hook_runs_pkey" primary key (event_id, hook)
3557
+ )`,
3558
+ `create table "field_reads" (
3559
+ "day" text_date not null,
3560
+ "model_id" text_uuid not null,
3561
+ "field_path" text not null,
3562
+ "principal_key" text not null,
3563
+ "selection" text not null,
3564
+ "reads" bigint not null default 0,
3565
+ "last_read_at" text_timestamptz not null,
3566
+ "site_id" text_uuid not null,
3567
+ constraint "field_reads_selection_check" CHECK ((selection IN ('explicit', 'implicit'))),
3568
+ constraint "field_reads_site_fk" foreign key (site_id) references sites (id) on delete cascade,
3569
+ constraint "field_reads_pkey" primary key (day, site_id, model_id, field_path, principal_key, selection)
3570
+ )`,
3571
+ `create table "token_reads" (
3572
+ "day" text_date not null,
3573
+ "principal_key" text not null,
3574
+ "requests" bigint not null default 0,
3575
+ "last_snapshot" bigint,
3576
+ "last_read_at" text_timestamptz not null,
3577
+ "site_id" text_uuid not null,
3578
+ constraint "token_reads_site_fk" foreign key (site_id) references sites (id) on delete cascade,
3579
+ constraint "token_reads_pkey" primary key (day, site_id, principal_key)
3580
+ )`,
3581
+ `create table "change_sets" (
3582
+ "id" text_uuid not null default (gen_random_uuid()),
3583
+ "title" text not null,
3584
+ "description" text not null default (''),
3585
+ "status" text not null default ('open'),
3586
+ "ship_phase" text,
3587
+ "source" text not null default ('manual'),
3588
+ "restore_of_seq" bigint,
3589
+ "scheduled_for" text_timestamptz,
3590
+ "schedule_job_id" text_uuid,
3591
+ "scheduled_by" text_uuid,
3592
+ "scheduled_by_token" text_uuid,
3593
+ "ship_job_id" text_uuid,
3594
+ "ship_mode" text,
3595
+ "ship_requested_by" text_uuid,
3596
+ "ship_requested_by_token" text_uuid,
3597
+ "acknowledge_breaking" integer_boolean not null default 0,
3598
+ "acknowledge_destructive" integer_boolean not null default 0,
3599
+ "deployment_connection_id" text_uuid,
3600
+ "deployment_run_id" text_uuid,
3601
+ "shipped_at" text_timestamptz,
3602
+ "shipped_seq" bigint,
3603
+ "schema_version_after" integer,
3604
+ "error" text_jsonb,
3605
+ "restore_report" text_jsonb not null default ('[]'),
3606
+ "created_by" text_uuid,
3607
+ "created_by_token" text_uuid,
3608
+ "version" integer not null default 1,
3609
+ "created_at" text_timestamptz not null default (shapio_now()),
3610
+ "updated_at" text_timestamptz not null default (shapio_now()),
3611
+ "site_id" text_uuid not null,
3612
+ constraint "change_sets_ship_mode_check" CHECK ((ship_mode IN ('interactive', 'scheduled'))),
3613
+ constraint "change_sets_ship_phase_check" CHECK ((ship_phase IN ('preparing', 'activating'))),
3614
+ constraint "change_sets_source_check" CHECK ((source IN ('manual', 'release', 'restore', 'builder', 'assist'))),
3615
+ constraint "change_sets_status_check" CHECK ((status IN ('open', 'scheduled', 'shipping', 'shipped', 'failed', 'discarded'))),
3616
+ constraint "change_sets_title_check" CHECK (((length(title) >= 1) AND (length(title) <= 200))),
3617
+ constraint "change_sets_created_by_fkey" foreign key (created_by) references admin_users (id) on delete set null,
3618
+ constraint "change_sets_created_by_token_fkey" foreign key (created_by_token) references api_tokens (id) on delete set null,
3619
+ constraint "change_sets_deployment_connection_id_fkey" foreign key (deployment_connection_id) references deployment_connections (id) on delete set null,
3620
+ constraint "change_sets_deployment_run_id_fkey" foreign key (deployment_run_id) references deployment_runs (id) on delete set null,
3621
+ constraint "change_sets_scheduled_by_fkey" foreign key (scheduled_by) references admin_users (id) on delete set null,
3622
+ constraint "change_sets_scheduled_by_token_fkey" foreign key (scheduled_by_token) references api_tokens (id) on delete set null,
3623
+ constraint "change_sets_ship_requested_by_fkey" foreign key (ship_requested_by) references admin_users (id) on delete set null,
3624
+ constraint "change_sets_ship_requested_by_token_fkey" foreign key (ship_requested_by_token) references api_tokens (id) on delete set null,
3625
+ constraint "change_sets_site_fk" foreign key (site_id) references sites (id) on delete restrict,
3626
+ constraint "change_sets_pkey" primary key (id)
3627
+ )`,
3628
+ `create unique index "change_sets_id_site_uq" on "change_sets" (id, site_id)`,
3629
+ `create table "schema_drafts" (
3630
+ "id" text_uuid not null default (gen_random_uuid()),
3631
+ "change_set_id" text_uuid not null,
3632
+ "definition_id" text_uuid not null,
3633
+ "kind" text not null,
3634
+ "api_key" text not null,
3635
+ "base_version" integer,
3636
+ "definition" text_jsonb,
3637
+ "version" integer not null default 1,
3638
+ "updated_by_type" text not null,
3639
+ "updated_by_id" text,
3640
+ "created_at" text_timestamptz not null default (shapio_now()),
3641
+ "updated_at" text_timestamptz not null default (shapio_now()),
3642
+ constraint "schema_drafts_kind_check" CHECK ((kind IN ('collection', 'singleton', 'component'))),
3643
+ constraint "schema_drafts_change_set_id_fkey" foreign key (change_set_id) references change_sets (id) on delete cascade,
3644
+ constraint "schema_drafts_pkey" primary key (id)
3645
+ )`,
3646
+ `create unique index "schema_drafts_set_definition_uq" on "schema_drafts" (change_set_id, definition_id)`,
3647
+ `create table "change_set_items" (
3648
+ "id" text_uuid not null default (gen_random_uuid()),
3649
+ "change_set_id" text_uuid not null,
3650
+ "kind" text not null,
3651
+ "position" integer not null,
3652
+ "entry_id" text_uuid,
3653
+ "model_id" text_uuid,
3654
+ "locale" text,
3655
+ "action" text,
3656
+ "source_revision_id" text_uuid,
3657
+ "schema_draft_id" text_uuid,
3658
+ "status" text not null default ('pending'),
3659
+ "error" text,
3660
+ "ship_state" text_jsonb,
3661
+ "created_at" text_timestamptz not null default (shapio_now()),
3662
+ "site_id" text_uuid not null,
3663
+ constraint "change_set_items_action_check" CHECK ((action IN ('publish', 'unpublish'))),
3664
+ constraint "change_set_items_kind_check" CHECK ((kind IN ('entry', 'schema'))),
3665
+ constraint "change_set_items_shape_check" CHECK ((((kind = 'entry') AND (entry_id IS NOT NULL) AND (model_id IS NOT NULL) AND (locale IS NOT NULL) AND (action IS NOT NULL) AND (schema_draft_id IS NULL)) OR ((kind = 'schema') AND (schema_draft_id IS NOT NULL) AND (entry_id IS NULL) AND (source_revision_id IS NULL)))),
3666
+ constraint "change_set_items_status_check" CHECK ((status IN ('pending', 'done', 'failed'))),
3667
+ constraint "change_set_items_change_set_id_fkey" foreign key (change_set_id) references change_sets (id) on delete cascade,
3668
+ constraint "change_set_items_entry_id_fkey" foreign key (entry_id) references entries (id) on delete cascade,
3669
+ constraint "change_set_items_entry_site_fk" foreign key (entry_id, site_id) references entries (id, site_id) on delete cascade,
3670
+ constraint "change_set_items_schema_draft_id_fkey" foreign key (schema_draft_id) references schema_drafts (id) on delete cascade,
3671
+ constraint "change_set_items_set_site_fk" foreign key (change_set_id, site_id) references change_sets (id, site_id) on delete cascade,
3672
+ constraint "change_set_items_source_revision_id_fkey" foreign key (source_revision_id) references content_revisions (id),
3673
+ constraint "change_set_items_pkey" primary key (id)
3674
+ )`,
3675
+ `create table "publication_snapshots" (
3676
+ "seq" bigint not null,
3677
+ "schema_version" integer,
3678
+ "source" text not null,
3679
+ "change_set_id" text_uuid,
3680
+ "actor_type" text,
3681
+ "actor_id" text,
3682
+ "created_at" text_timestamptz not null default (shapio_now()),
3683
+ "site_id" text_uuid not null,
3684
+ constraint "publication_snapshots_change_set_id_fkey" foreign key (change_set_id) references change_sets (id) on delete set null,
3685
+ constraint "publication_snapshots_site_fk" foreign key (site_id) references sites (id) on delete cascade,
3686
+ constraint "publication_snapshots_pkey" primary key (site_id, seq)
3687
+ )`,
3688
+ `create table "editor_presence" (
3689
+ "entry_id" text_uuid not null,
3690
+ "model_id" text_uuid not null,
3691
+ "admin_user_id" text_uuid not null,
3692
+ "tab_id" text not null,
3693
+ "locale" text,
3694
+ "started_at" text_timestamptz not null default (shapio_now()),
3695
+ "last_seen_at" text_timestamptz not null default (shapio_now()),
3696
+ constraint "editor_presence_tab_id_check" CHECK (((length(tab_id) >= 1) AND (length(tab_id) <= 64))),
3697
+ constraint "editor_presence_pkey" primary key (entry_id, admin_user_id, tab_id)
3698
+ )`,
3699
+ `create table "content_health_findings" (
3700
+ "id" text_uuid not null default (gen_random_uuid()),
3701
+ "entry_id" text_uuid not null,
3702
+ "model_id" text_uuid not null,
3703
+ "locale" text not null,
3704
+ "rule" text not null,
3705
+ "subject" text not null default (''),
3706
+ "severity" text not null,
3707
+ "path" text,
3708
+ "details" text_jsonb not null default ('{}'),
3709
+ "first_seen_at" text_timestamptz not null default (shapio_now()),
3710
+ "last_seen_at" text_timestamptz not null default (shapio_now()),
3711
+ "resolved_at" text_timestamptz,
3712
+ "site_id" text_uuid not null,
3713
+ constraint "content_health_findings_severity_check" CHECK ((severity IN ('error', 'warning'))),
3714
+ constraint "content_health_findings_entry_id_fkey" foreign key (entry_id) references entries (id) on delete cascade,
3715
+ constraint "content_health_findings_entry_site_fk" foreign key (entry_id, site_id) references entries (id, site_id) on delete cascade,
3716
+ constraint "content_health_findings_site_fk" foreign key (site_id) references sites (id) on delete restrict,
3717
+ constraint "content_health_findings_pkey" primary key (id)
3718
+ )`,
3719
+ `create unique index "content_health_findings_entry_id_locale_rule_subject_key" on "content_health_findings" (entry_id, locale, rule, subject)`,
3720
+ `create table "sites" (
3721
+ "id" text_uuid not null default (gen_random_uuid()),
3722
+ "key" text not null,
3723
+ "name" text not null,
3724
+ "is_primary" integer_boolean not null default 0,
3725
+ "version" integer not null default 1,
3726
+ "created_at" text_timestamptz not null default (shapio_now()),
3727
+ "updated_at" text_timestamptz not null default (shapio_now()),
3728
+ constraint "sites_key_check" CHECK ((key REGEXP '^[a-z][a-z0-9-]{0,62}$')),
3729
+ constraint "sites_name_check" CHECK (((length(name) >= 1) AND (length(name) <= 200))),
3730
+ constraint "sites_pkey" primary key (id)
3731
+ )`,
3732
+ `create unique index "sites_key_key" on "sites" (key)`,
3733
+ `create table "site_app_roles" (
3734
+ "site_id" text_uuid not null,
3735
+ "audience" text not null,
3736
+ "role_id" text_uuid not null,
3737
+ "created_at" text_timestamptz not null default (shapio_now()),
3738
+ constraint "site_app_roles_audience_check" CHECK ((audience IN ('public', 'authenticated'))),
3739
+ constraint "site_app_roles_role_id_fkey" foreign key (role_id) references app_roles (id) on delete cascade,
3740
+ constraint "site_app_roles_site_id_fkey" foreign key (site_id) references sites (id) on delete cascade,
3741
+ constraint "site_app_roles_pkey" primary key (site_id, audience, role_id)
3742
+ )`,
3743
+ `create table "assist_runs" (
3744
+ "id" text_uuid not null default (gen_random_uuid()),
3745
+ "site_id" text_uuid not null,
3746
+ "actor_type" text not null,
3747
+ "actor_id" text not null,
3748
+ "action" text not null,
3749
+ "rule" text,
3750
+ "provider" text not null,
3751
+ "model" text not null,
3752
+ "input_tokens" integer not null default 0,
3753
+ "output_tokens" integer not null default 0,
3754
+ "duration_ms" integer not null default 0,
3755
+ "status" text not null,
3756
+ "error_code" text,
3757
+ "job_id" text_uuid,
3758
+ "created_at" text_timestamptz not null default (shapio_now()),
3759
+ "finished_at" text_timestamptz,
3760
+ constraint "assist_runs_action_check" CHECK ((action IN ('alt_text', 'summarize', 'translate', 'rewrite', 'schema_draft', 'content_ops'))),
3761
+ constraint "assist_runs_status_check" CHECK ((status IN ('queued', 'running', 'succeeded', 'failed'))),
3762
+ constraint "assist_runs_job_id_fkey" foreign key (job_id) references jobs (id) on delete set null,
3763
+ constraint "assist_runs_site_id_fkey" foreign key (site_id) references sites (id) on delete cascade,
3764
+ constraint "assist_runs_pkey" primary key (id)
3765
+ )`,
3766
+ `create index "admin_invitations_pending_email_idx" on "admin_invitations" (email) where ((accepted_at IS NULL) AND (revoked_at IS NULL))`,
3767
+ `create index "admin_sessions_user_idx" on "admin_sessions" (admin_user_id) where (revoked_at IS NULL)`,
3768
+ `create index "admin_user_roles_role_idx" on "admin_user_roles" (role_id)`,
3769
+ `create index "api_tokens_role_idx" on "api_tokens" (role_id)`,
3770
+ `create index "app_oauth_accounts_user_idx" on "app_oauth_accounts" (app_user_id)`,
3771
+ `create index "app_password_resets_user_idx" on "app_password_resets" (app_user_id)`,
3772
+ `create index "app_refresh_tokens_family_idx" on "app_refresh_tokens" (family_id)`,
3773
+ `create index "app_refresh_tokens_user_idx" on "app_refresh_tokens" (app_user_id) where (revoked_at IS NULL)`,
3774
+ `create index "app_user_roles_role_idx" on "app_user_roles" (role_id)`,
3775
+ `create index "app_users_created_idx" on "app_users" (site_id, created_at DESC, id DESC) where (deleted_at IS NULL)`,
3776
+ `create unique index "app_users_email_uq" on "app_users" (site_id, lower(email)) where (deleted_at IS NULL)`,
3777
+ `create index "assist_runs_created_idx" on "assist_runs" (created_at)`,
3778
+ `create index "assist_runs_site_created_idx" on "assist_runs" (site_id, created_at)`,
3779
+ `create index "audit_events_actor_idx" on "audit_events" (actor_type, actor_id, occurred_at DESC)`,
3780
+ `create index "audit_events_occurred_at_idx" on "audit_events" (occurred_at DESC)`,
3781
+ `create index "audit_events_target_idx" on "audit_events" (target_type, target_id, occurred_at DESC)`,
3782
+ `create unique index "change_set_items_draft_uq" on "change_set_items" (schema_draft_id)`,
3783
+ `create index "change_set_items_entry_idx" on "change_set_items" (entry_id) where (entry_id IS NOT NULL)`,
3784
+ `create unique index "change_set_items_entry_uq" on "change_set_items" (change_set_id, entry_id, locale) where (kind = 'entry')`,
3785
+ `create index "change_set_items_set_idx" on "change_set_items" (change_set_id, position)`,
3786
+ `create index "change_sets_status_idx" on "change_sets" (site_id, status, created_at DESC)`,
3787
+ `create index "content_health_findings_open_idx" on "content_health_findings" (site_id, rule, last_seen_at DESC, id) where (resolved_at IS NULL)`,
3788
+ `create index "content_health_findings_resolved_idx" on "content_health_findings" (resolved_at) where (resolved_at IS NOT NULL)`,
3789
+ `create index "content_revisions_entry_idx" on "content_revisions" (entry_id, locale, created_at)`,
3790
+ `create index "deployment_runs_connection_idx" on "deployment_runs" (connection_id, created_at DESC)`,
3791
+ `create unique index "deployment_runs_queued_uq" on "deployment_runs" (connection_id) where (status = 'queued')`,
3792
+ `create index "editor_presence_model_idx" on "editor_presence" (model_id, last_seen_at)`,
3793
+ `create index "editor_presence_seen_idx" on "editor_presence" (last_seen_at)`,
3794
+ `create index "email_confirmations_user_idx" on "email_confirmations" (app_user_id)`,
3795
+ `create index "entries_model_idx" on "entries" (site_id, model_id, created_at) where (deleted_at IS NULL)`,
3796
+ `create index "entries_owner_idx" on "entries" (owner_app_user_id) where (owner_app_user_id IS NOT NULL)`,
3797
+ `create index "entry_heads_change_seq_idx" on "entry_heads" (model_id, change_seq)`,
3798
+ `create index "entry_heads_model_locale_state_idx" on "entry_heads" (site_id, model_id, locale, state)`,
3799
+ `create index "field_reads_model_day_idx" on "field_reads" (model_id, day)`,
3800
+ `create unique index "jobs_idempotency_key_uq" on "jobs" (idempotency_key) where (idempotency_key IS NOT NULL)`,
3801
+ `create index "jobs_pending_idx" on "jobs" (priority DESC, run_at) where (status = 'pending')`,
3802
+ `create index "jobs_running_lease_idx" on "jobs" (locked_until) where (status = 'running')`,
3803
+ `create unique index "locales_single_default_uq" on "locales" (is_default) where is_default`,
3804
+ `create index "media_assets_created_idx" on "media_assets" (site_id, created_at, id) where (deleted_at IS NULL)`,
3805
+ `create index "media_assets_driver_idx" on "media_assets" (storage_driver, id)`,
3806
+ `create index "media_assets_folder_idx" on "media_assets" (folder_id, created_at, id) where (deleted_at IS NULL)`,
3807
+ `create unique index "media_assets_storage_key_uq" on "media_assets" (storage_key)`,
3808
+ `create unique index "media_folders_sibling_name_uq" on "media_folders" (site_id, COALESCE(parent_id, '00000000-0000-0000-0000-000000000000'), lower(name))`,
3809
+ `create index "media_references_entry_idx" on "media_references" (entry_id, locale, state)`,
3810
+ `create unique index "media_variants_storage_key_uq" on "media_variants" (storage_key)`,
3811
+ `create unique index "models_api_key_uq" on "models" (lower(api_key)) where (deleted_at IS NULL)`,
3812
+ `create index "outbox_events_undispatched_idx" on "outbox_events" (id) where (dispatched_at IS NULL)`,
3813
+ `create index "password_resets_user_idx" on "password_resets" (admin_user_id)`,
3814
+ `create index "preview_tokens_entry_idx" on "preview_tokens" (entry_id, created_at DESC)`,
3815
+ `create index "publication_log_entry_locale_from_idx" on "publication_log" (entry_id, locale, from_seq)`,
3816
+ `create index "publication_log_model_seq_idx" on "publication_log" (site_id, model_id, from_seq)`,
3817
+ `create unique index "publication_log_open_uq" on "publication_log" (entry_id, locale) where (to_seq IS NULL)`,
3818
+ `create index "publication_log_to_seq_idx" on "publication_log" (site_id, to_seq) where (to_seq IS NOT NULL)`,
3819
+ `create index "publication_snapshots_change_set_idx" on "publication_snapshots" (change_set_id) where (change_set_id IS NOT NULL)`,
3820
+ `create index "relation_edges_target_idx" on "relation_edges" (target_entry_id, state)`,
3821
+ `create index "scheduled_publications_entry_idx" on "scheduled_publications" (entry_id, locale)`,
3822
+ `create index "scheduled_publications_site_idx" on "scheduled_publications" (site_id, run_at)`,
3823
+ `create index "scheduled_publications_status_idx" on "scheduled_publications" (status, run_at)`,
3824
+ `create index "schema_change_jobs_change_set_idx" on "schema_change_jobs" (change_set_id) where (change_set_id IS NOT NULL)`,
3825
+ `create unique index "schema_change_jobs_in_flight_uq" on "schema_change_jobs" (target_type, target_id) where (status IN ('pending', 'running'))`,
3826
+ `create index "schema_change_jobs_model_idx" on "schema_change_jobs" (model_id, created_at DESC)`,
3827
+ `create index "schema_drafts_definition_idx" on "schema_drafts" (definition_id)`,
3828
+ `create index "site_app_roles_role_idx" on "site_app_roles" (role_id)`,
3829
+ `create unique index "sites_single_primary_uq" on "sites" (is_primary) where is_primary`,
3830
+ `create index "unique_values_entry_idx" on "unique_values" (entry_id)`,
3831
+ `create unique index "webhook_deliveries_event_uq" on "webhook_deliveries" (webhook_id, event_id) where (event_id IS NOT NULL)`,
3832
+ `create index "webhook_deliveries_webhook_idx" on "webhook_deliveries" (webhook_id, created_at DESC)`,
3833
+ `create trigger "media_assets_folder_id_fkey_set_null" before delete on "media_folders" for each row begin update "media_assets" set folder_id = null where folder_id = old.id and site_id = old.site_id; end`,
3834
+ `create table "sequences" (
3835
+ "name" text primary key,
3836
+ "value" integer not null default 0
3837
+ )`,
3838
+ `insert into "sequences" (name, value) values ('entry_heads_change_seq', 0)`,
3839
+ `create trigger "schema_revisions_no_update" before update on "schema_revisions" for each row begin select raise(abort, 'schema_revisions rows are immutable'); end`,
3840
+ `create trigger "content_revisions_no_update" before update on "content_revisions" for each row begin select raise(abort, 'content_revisions rows are immutable'); end`,
3841
+ `insert into system_versions default values`,
3842
+ `insert into schema_settings default values`,
3843
+ `insert into locales (code, label, is_default) values ('en', 'English', 1)`,
3844
+ `insert into sites (id, key, name, is_primary) values ('00000000-0000-4000-b000-000000000001', 'default', 'Default site', 1)`,
3845
+ `insert into publication_state (site_id, last_seq) values ('00000000-0000-4000-b000-000000000001', 0)`,
3846
+ `insert into app_roles (id, key, name, description, is_system) values
3847
+ ('00000000-0000-4000-a000-000000000001', 'public', 'Public', 'Applies to every caller without a token. Grants nothing until you add permissions.', 1),
3848
+ ('00000000-0000-4000-a000-000000000002', 'authenticated', 'Authenticated', 'Held by every signed-in app user, in addition to their custom roles. Grants nothing until you add permissions.', 1)`,
3849
+ `insert into site_app_roles (site_id, audience, role_id) values
3850
+ ('00000000-0000-4000-b000-000000000001', 'public', '00000000-0000-4000-a000-000000000001'),
3851
+ ('00000000-0000-4000-b000-000000000001', 'authenticated', '00000000-0000-4000-a000-000000000002')`
3852
+ ];
3853
+ const TABLES = [
3854
+ "system_versions",
3855
+ "jobs",
3856
+ "outbox_events",
3857
+ "audit_events",
3858
+ "admin_users",
3859
+ "admin_roles",
3860
+ "admin_role_permissions",
3861
+ "admin_user_roles",
3862
+ "admin_sessions",
3863
+ "admin_invitations",
3864
+ "password_resets",
3865
+ "setup_tokens",
3866
+ "api_tokens",
3867
+ "system_settings",
3868
+ "models",
3869
+ "schema_revisions",
3870
+ "model_active_versions",
3871
+ "schema_change_jobs",
3872
+ "locales",
3873
+ "schema_settings",
3874
+ "entries",
3875
+ "content_revisions",
3876
+ "entry_heads",
3877
+ "relation_edges",
3878
+ "unique_values",
3879
+ "publication_state",
3880
+ "publication_log",
3881
+ "media_folders",
3882
+ "media_assets",
3883
+ "media_variants",
3884
+ "media_references",
3885
+ "media_upload_grants",
3886
+ "scheduled_publications",
3887
+ "webhooks",
3888
+ "webhook_deliveries",
3889
+ "deployment_connections",
3890
+ "deployment_runs",
3891
+ "preview_tokens",
3892
+ "app_users",
3893
+ "app_roles",
3894
+ "app_role_permissions",
3895
+ "app_user_roles",
3896
+ "app_refresh_tokens",
3897
+ "app_oauth_accounts",
3898
+ "email_confirmations",
3899
+ "app_password_resets",
3900
+ "app_login_codes",
3901
+ "extension_hook_runs",
3902
+ "field_reads",
3903
+ "token_reads",
3904
+ "change_sets",
3905
+ "schema_drafts",
3906
+ "change_set_items",
3907
+ "publication_snapshots",
3908
+ "editor_presence",
3909
+ "content_health_findings",
3910
+ "sites",
3911
+ "site_app_roles",
3912
+ "assist_runs",
3913
+ "sequences"
3914
+ ];
3915
+ const up$4 = async (db) => {
3916
+ for (const statement of STATEMENTS) await sql.raw(statement).execute(db);
3917
+ };
3918
+ const down$4 = async (db) => {
3919
+ await sql`pragma defer_foreign_keys = on`.execute(db);
3920
+ for (const table of [...TABLES].reverse()) await sql.raw(`drop table if exists "${table}"`).execute(db);
3921
+ };
3922
+ //#endregion
3923
+ //#region ../../apps/api/src/db/migrations/sqlite/20261003170000_audit_events_seq.ts
3924
+ var _20261003170000_audit_events_seq_exports = /* @__PURE__ */ __exportAll({
3925
+ down: () => down$3,
3926
+ up: () => up$3
3927
+ });
3928
+ /**
3929
+ * SQLite twin of `../20261003170000_audit_events_seq.ts`. SQLite cannot add a column whose default calls a
3930
+ * function, so the table is rebuilt: the old one is renamed aside, the new one takes `seq` from the
3931
+ * `audit_events_seq` row in `sequences` (like `entry_heads.change_seq`), and the rows are copied in
3932
+ * `(occurred_at, id)` order so existing events are numbered oldest first. Nothing references `audit_events`.
3933
+ */
3934
+ const COLUMNS = [
3935
+ "id",
3936
+ "occurred_at",
3937
+ "actor_type",
3938
+ "actor_id",
3939
+ "action",
3940
+ "target_type",
3941
+ "target_id",
3942
+ "outcome",
3943
+ "request_id",
3944
+ "ip",
3945
+ "metadata",
3946
+ "site_id"
3947
+ ];
3948
+ const INDEXES = [
3949
+ `create index "audit_events_actor_idx" on "audit_events" (actor_type, actor_id, occurred_at DESC)`,
3950
+ `create index "audit_events_occurred_at_idx" on "audit_events" (occurred_at DESC)`,
3951
+ `create index "audit_events_target_idx" on "audit_events" (target_type, target_id, occurred_at DESC)`
3952
+ ];
3953
+ const up$3 = async (db) => {
3954
+ await sql`insert into "sequences" (name, value) values ('audit_events_seq', 0)`.execute(db);
3955
+ await sql`alter table "audit_events" rename to "audit_events_before_seq"`.execute(db);
3956
+ await sql`create table "audit_events" (
3957
+ "id" text_uuid not null default (gen_random_uuid()),
3958
+ "occurred_at" text_timestamptz not null default (shapio_now()),
3959
+ "actor_type" text not null,
3960
+ "actor_id" text,
3961
+ "action" text not null,
3962
+ "target_type" text,
3963
+ "target_id" text,
3964
+ "outcome" text not null,
3965
+ "request_id" text,
3966
+ "ip" text,
3967
+ "metadata" text_jsonb not null default ('{}'),
3968
+ "site_id" text_uuid,
3969
+ "seq" bigint not null default (shapio_nextval('audit_events_seq')),
3970
+ constraint "audit_events_actor_type_check" CHECK ((actor_type IN ('admin', 'app_user', 'token', 'anonymous', 'system'))),
3971
+ constraint "audit_events_outcome_check" CHECK ((outcome IN ('success', 'failure'))),
3972
+ constraint "audit_events_site_fk" foreign key (site_id) references sites (id) on delete set null,
3973
+ constraint "audit_events_pkey" primary key (id)
3974
+ )`.execute(db);
3975
+ const columns = sql.join(COLUMNS.map((column) => sql.ref(column)));
3976
+ await sql`insert into "audit_events" (${columns})
3977
+ select ${columns} from "audit_events_before_seq" order by occurred_at, id`.execute(db);
3978
+ await sql`drop table "audit_events_before_seq"`.execute(db);
3979
+ for (const statement of INDEXES) await sql.raw(statement).execute(db);
3980
+ };
3981
+ const down$3 = async (db) => {
3982
+ await sql`alter table "audit_events" drop column "seq"`.execute(db);
3983
+ await sql`delete from "sequences" where name = 'audit_events_seq'`.execute(db);
3984
+ };
3985
+ //#endregion
3986
+ //#region ../../apps/api/src/db/migrations/sqlite/20261004120000_models_site_scope.ts
3987
+ var _20261004120000_models_site_scope_exports = /* @__PURE__ */ __exportAll({
3988
+ down: () => down$2,
3989
+ up: () => up$2
3990
+ });
3991
+ /**
3992
+ * SQLite twin of `../20261004120000_models_site_scope.ts`. `down` rebuilds `models` without `site_id`
3993
+ * (SQLite cannot drop a column that carries a foreign key). With `legacy_alter_table` on, renaming the old
3994
+ * table aside leaves the references of `schema_revisions`, `model_active_versions`, `schema_change_jobs` and
3995
+ * `entries` pointing at the name `models`, which the rebuilt table then takes; dropping the renamed table
3996
+ * deletes nothing they reference.
3997
+ */
3998
+ const NIL_UUID = "00000000-0000-0000-0000-000000000000";
3999
+ const up$2 = async (db) => {
4000
+ await sql`alter table "models" add column "site_id" text_uuid references sites (id)`.execute(db);
4001
+ await sql`create index "models_site_idx" on "models" (site_id)`.execute(db);
4002
+ await sql`drop index "models_api_key_uq"`.execute(db);
4003
+ await sql`
4004
+ create unique index "models_api_key_uq" on "models" (coalesce(site_id, ${sql.lit(NIL_UUID)}), lower(api_key))
4005
+ where (deleted_at IS NULL)
4006
+ `.execute(db);
4007
+ await sql`alter table "schema_drafts" add column "shared" integer_boolean not null default 0`.execute(db);
4008
+ };
4009
+ const down$2 = async (db) => {
4010
+ const { rows } = await sql`select count(*) as count from "models" where site_id is not null`.execute(db);
4011
+ if (Number(rows[0]?.count ?? 0) > 0) throw new Error("Cannot roll back per-site schema while definitions belong to a site: two sites may share an API ID. Share or delete the site definitions first.");
4012
+ await sql`alter table "schema_drafts" drop column "shared"`.execute(db);
4013
+ await sql`pragma legacy_alter_table = on`.execute(db);
4014
+ await sql`alter table "models" rename to "models_before_site_scope"`.execute(db);
4015
+ await sql`create table "models" (
4016
+ "id" text_uuid not null,
4017
+ "kind" text not null,
4018
+ "api_key" text not null,
4019
+ "created_at" text_timestamptz not null default (shapio_now()),
4020
+ "updated_at" text_timestamptz not null default (shapio_now()),
4021
+ "deleted_at" text_timestamptz,
4022
+ constraint "models_kind_check" CHECK ((kind IN ('collection', 'singleton', 'component'))),
4023
+ constraint "models_pkey" primary key (id)
4024
+ )`.execute(db);
4025
+ await sql`insert into "models" (id, kind, api_key, created_at, updated_at, deleted_at)
4026
+ select id, kind, api_key, created_at, updated_at, deleted_at from "models_before_site_scope"`.execute(db);
4027
+ await sql`drop table "models_before_site_scope"`.execute(db);
4028
+ await sql`pragma legacy_alter_table = off`.execute(db);
4029
+ await sql`create unique index "models_api_key_uq" on "models" (lower(api_key)) where (deleted_at IS NULL)`.execute(db);
4030
+ };
4031
+ //#endregion
4032
+ //#region ../../apps/api/src/db/migrations/sqlite/20261004130000_site_seo_settings.ts
4033
+ var _20261004130000_site_seo_settings_exports = /* @__PURE__ */ __exportAll({
4034
+ down: () => down$1,
4035
+ up: () => up$1
4036
+ });
4037
+ /**
4038
+ * SQLite twin of `../20261004130000_site_seo_settings.ts`. `insert or ignore` stands in for `on conflict do
4039
+ * nothing` (the grant's unique index); the permissions version moves only when a grant was added or removed.
4040
+ */
4041
+ const bumpPermissionsVersion = (db) => sql`update "system_versions" set permissions_version = permissions_version + 1, updated_at = shapio_now()`.execute(db);
4042
+ const up$1 = async (db) => {
4043
+ await sql`alter table "sites" add column "seo_defaults" text_jsonb`.execute(db);
4044
+ const granted = await sql`
4045
+ insert or ignore into "admin_role_permissions" (role_id, action, model_id, condition, field_ids)
4046
+ select role_id, 'site.settings', null, null, null
4047
+ from "admin_role_permissions"
4048
+ where action = 'publishing.manage' and model_id is null
4049
+ `.execute(db);
4050
+ if (Number(granted.numAffectedRows ?? 0) > 0) await bumpPermissionsVersion(db);
4051
+ };
4052
+ const down$1 = async (db) => {
4053
+ const revoked = await sql`delete from "admin_role_permissions" where action = 'site.settings'`.execute(db);
4054
+ if (Number(revoked.numAffectedRows ?? 0) > 0) await bumpPermissionsVersion(db);
4055
+ await sql`alter table "sites" drop column "seo_defaults"`.execute(db);
4056
+ };
4057
+ //#endregion
4058
+ //#region ../../apps/api/src/db/migrations/sqlite/20261005120000_entry_heads_entry_created_at.ts
4059
+ var _20261005120000_entry_heads_entry_created_at_exports = /* @__PURE__ */ __exportAll({
4060
+ down: () => down,
4061
+ up: () => up
4062
+ });
4063
+ /**
4064
+ * SQLite twin of `../20261005120000_entry_heads_entry_created_at.ts`. One process holds the write lock, so
4065
+ * the backfill is one statement and the index is built here (a plain `CREATE INDEX`; writers wait for it, the
4066
+ * documented single-process limitation of ADR 0001, "D2: as built").
4067
+ */
4068
+ const INDEX = "entry_heads_entry_order_idx";
4069
+ const up = async (db) => {
4070
+ await sql`alter table "entry_heads" add column "entry_created_at" text_timestamptz`.execute(db);
4071
+ await sql`update "entry_heads" set entry_created_at = (
4072
+ select e.created_at from "entries" e where e.id = "entry_heads".entry_id
4073
+ ) where entry_created_at is null`.execute(db);
4074
+ await sql`create index ${sql.id(INDEX)} on "entry_heads" (site_id, model_id, state, entry_created_at desc, entry_id)`.execute(db);
4075
+ };
4076
+ const down = async (db) => {
4077
+ await sql`drop index if exists ${sql.id(INDEX)}`.execute(db);
4078
+ await sql`alter table "entry_heads" drop column "entry_created_at"`.execute(db);
4079
+ };
4080
+ //#endregion
4081
+ //#region ../../apps/api/src/db/migrations/sqlite/index.ts
4082
+ const SQLITE_MIGRATIONS = {
4083
+ "0001_baseline": _0001_baseline_exports,
4084
+ "20261003170000_audit_events_seq": _20261003170000_audit_events_seq_exports,
4085
+ "20261004120000_models_site_scope": _20261004120000_models_site_scope_exports,
4086
+ "20261004130000_site_seo_settings": _20261004130000_site_seo_settings_exports,
4087
+ "20261005120000_entry_heads_entry_created_at": _20261005120000_entry_heads_entry_created_at_exports,
4088
+ "20261006120000_system_versions_release": _20261006120000_system_versions_release_exports
4089
+ };
4090
+ //#endregion
4091
+ //#region ../../apps/api/src/db/migrations/index.ts
4092
+ /**
4093
+ * Every migration, listed explicitly. A static list works when the server is bundled for npm, where
4094
+ * directory scanning would not. Names must keep the `YYYYMMDDHHMMSS_description` order; the migrator
4095
+ * rejects out-of-order additions. Timestamps are assigned when a migration merges.
4096
+ */
4097
+ const MIGRATIONS = {
4098
+ "20261001120000_create_platform_tables": _20261001120000_create_platform_tables_exports,
4099
+ "20261001130000_create_admin_identity": _20261001130000_create_admin_identity_exports,
4100
+ "20261001130500_create_system_settings": _20261001130500_create_system_settings_exports,
4101
+ "20261001140000_create_schema_tables": _20261001140000_create_schema_tables_exports,
4102
+ "20261001150000_create_content_tables": _20261001150000_create_content_tables_exports,
4103
+ "20261001160000_create_media_tables": _20261001160000_create_media_tables_exports,
4104
+ "20261001170000_create_publishing_tables": _20261001170000_create_publishing_tables_exports,
4105
+ "20261001180000_create_app_users": _20261001180000_create_app_users_exports,
4106
+ "20261002120000_create_extension_hook_runs": _20261002120000_create_extension_hook_runs_exports,
4107
+ "20261002130000_app_auth_token_version_and_login_code_pkce": _20261002130000_app_auth_token_version_and_login_code_pkce_exports,
4108
+ "20261002180000_add_publication_log_diff_indexes": _20261002180000_add_publication_log_diff_indexes_exports,
4109
+ "20261002180100_create_usage_tables": _20261002180100_create_usage_tables_exports,
4110
+ "20261002180200_create_change_sets": _20261002180200_create_change_sets_exports,
4111
+ "20261002190000_create_editor_presence_and_content_health": _20261002190000_create_editor_presence_and_content_health_exports,
4112
+ "20261003120000_add_changes_ship_permission": _20261003120000_add_changes_ship_permission_exports,
4113
+ "20261003130000_create_sites": _20261003130000_create_sites_exports,
4114
+ "20261003140200_enqueue_field_index_layout": _20261003140200_enqueue_field_index_layout_exports,
4115
+ "20261003140500_change_set_items_site_and_preview_entry": _20261003140500_change_set_items_site_and_preview_entry_exports,
4116
+ "20261003150000_widen_deployment_providers": _20261003150000_widen_deployment_providers_exports,
4117
+ "20261003160000_create_assist_runs": _20261003160000_create_assist_runs_exports,
4118
+ "20261003170000_audit_events_seq": _20261003170000_audit_events_seq_exports$2,
4119
+ "20261004120000_models_site_scope": _20261004120000_models_site_scope_exports$2,
4120
+ "20261004130000_site_seo_settings": _20261004130000_site_seo_settings_exports$2,
4121
+ "20261005120000_entry_heads_entry_created_at": _20261005120000_entry_heads_entry_created_at_exports$2,
4122
+ "20261006120000_system_versions_release": _20261006120000_system_versions_release_exports
4123
+ };
4124
+ const PROVIDERS = {
4125
+ postgres: { getMigrations: () => Promise.resolve({ ...MIGRATIONS }) },
4126
+ sqlite: { getMigrations: () => Promise.resolve({ ...SQLITE_MIGRATIONS }) },
4127
+ mysql: { getMigrations: () => Promise.resolve({ ...MYSQL_MIGRATIONS }) }
4128
+ };
4129
+ const migrationProviderFor = (dialect) => PROVIDERS[dialect];
4130
+ //#endregion
4131
+ //#region ../../apps/api/src/db/migrator.ts
4132
+ const dialectOfHandle = (db) => {
4133
+ if (sqliteDriverOf(db)) return "sqlite";
4134
+ return mysqlDriverOf(db) ? "mysql" : "postgres";
4135
+ };
4136
+ /**
4137
+ * Migrations must stay in order; out-of-order additions are rejected (see build plan §2). The list depends
4138
+ * on the handle's dialect (SQLite and MySQL start from a baseline).
4139
+ */
4140
+ const createMigrator = (db) => new Migrator({
4141
+ db,
4142
+ provider: migrationProviderFor(dialectOfHandle(db)),
4143
+ allowUnorderedMigrations: false
4144
+ });
4145
+ /** Names of migrations known to this build that have not run against the database. */
4146
+ const getPendingMigrations = async (db) => {
4147
+ return (await createMigrator(db).getMigrations()).filter((migration) => migration.executedAt === void 0).map((m) => m.name);
4148
+ };
4149
+ //#endregion
4150
+ export { getPendingMigrations };
4151
+
4152
+ //# sourceMappingURL=migrator-CIKnDfZy.js.map