@stacksjs/ts-cloud 0.9.2 → 0.9.4

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.
Files changed (191) hide show
  1. package/dist/aws/index.js +1 -269
  2. package/dist/bin/chunk-00kg80qg.js +1 -0
  3. package/dist/bin/chunk-09nwf2gy.js +34 -0
  4. package/dist/bin/chunk-1k5kszjp.js +111 -0
  5. package/dist/bin/chunk-21e0mavm.js +1 -0
  6. package/dist/bin/chunk-271gk1n2.js +6 -0
  7. package/dist/bin/chunk-2empps47.js +2 -0
  8. package/dist/bin/chunk-38pq971s.js +2 -0
  9. package/dist/bin/chunk-3gykfyq4.js +37 -0
  10. package/dist/bin/chunk-3vqfexc4.js +70 -0
  11. package/dist/bin/chunk-4vrqm9h0.js +2 -0
  12. package/dist/bin/chunk-598yf4bs.js +1 -0
  13. package/dist/bin/chunk-5ectjq0b.js +1 -0
  14. package/dist/bin/chunk-5erkxpt7.js +512 -0
  15. package/dist/bin/chunk-5mrtzemj.js +1 -0
  16. package/dist/bin/chunk-5n9fmabb.js +1 -0
  17. package/dist/bin/chunk-9knpg30y.js +1 -0
  18. package/dist/bin/chunk-9pvsff8g.js +1 -0
  19. package/dist/bin/chunk-ax9mr6wm.js +44 -0
  20. package/dist/bin/chunk-axhnkn5g.js +2 -0
  21. package/dist/bin/chunk-c1vcdgvr.js +1 -0
  22. package/dist/bin/chunk-ctd2vg9w.js +2 -0
  23. package/dist/bin/chunk-dbgffky8.js +1 -0
  24. package/dist/bin/chunk-ddk4rt4j.js +6 -0
  25. package/dist/bin/chunk-dwr30vbv.js +1 -0
  26. package/dist/bin/chunk-e0rkrq70.js +1 -0
  27. package/dist/bin/chunk-e3xrwh9z.js +1 -0
  28. package/dist/bin/chunk-etrb03mt.js +1507 -0
  29. package/dist/bin/chunk-fhc6ntvd.js +2 -0
  30. package/dist/bin/chunk-fpm59y25.js +6 -0
  31. package/dist/bin/chunk-fvbxvq6b.js +1 -0
  32. package/dist/bin/chunk-fy8j3y8p.js +92 -0
  33. package/dist/bin/chunk-fz21b57w.js +1 -0
  34. package/dist/bin/chunk-gavvf0pg.js +141 -0
  35. package/dist/bin/chunk-ggn7ax8p.js +4 -0
  36. package/dist/bin/chunk-gpe2prkm.js +1 -0
  37. package/dist/bin/chunk-h5pa5men.js +2 -0
  38. package/dist/bin/chunk-hm08xdxf.js +2 -0
  39. package/dist/bin/chunk-hzgz13nf.js +1 -0
  40. package/dist/bin/chunk-j703btc5.js +50 -0
  41. package/dist/bin/chunk-k9t9wvms.js +1 -0
  42. package/dist/bin/chunk-m669wtgr.js +1 -0
  43. package/dist/bin/chunk-m7dgskt1.js +1 -0
  44. package/dist/bin/chunk-mh0kdzqp.js +2 -0
  45. package/dist/bin/chunk-mr0atd2s.js +2 -0
  46. package/dist/bin/chunk-mz0wbbsj.js +3 -0
  47. package/dist/bin/chunk-ngcyftm2.js +1 -0
  48. package/dist/bin/chunk-p66ptpmr.js +2 -0
  49. package/dist/bin/chunk-qk9qgepa.js +1 -0
  50. package/dist/bin/chunk-rxcvf10f.js +7364 -0
  51. package/dist/bin/chunk-rytk89az.js +60 -0
  52. package/dist/bin/chunk-tga3hhwk.js +6 -0
  53. package/dist/bin/chunk-tx0tqq2k.js +8 -0
  54. package/dist/bin/chunk-vek8a0w6.js +1 -0
  55. package/dist/bin/chunk-vm3q5gf2.js +3 -0
  56. package/dist/bin/chunk-y7s5r8y6.js +3 -0
  57. package/dist/bin/chunk-yv9sth30.js +1 -0
  58. package/dist/bin/chunk-zmegvjp8.js +581 -0
  59. package/dist/bin/cli.js +417 -10597
  60. package/dist/bin/dashboard-server.js +1 -6998
  61. package/dist/chunk-0ccta6z6.js +6 -0
  62. package/dist/chunk-0k1cs6pp.js +1 -0
  63. package/dist/chunk-1egjn203.js +704 -0
  64. package/dist/chunk-2wx29qga.js +5 -0
  65. package/dist/chunk-5m5rqmv7.js +1 -0
  66. package/dist/chunk-8ea5022g.js +114 -0
  67. package/dist/chunk-8xqn3c0w.js +2 -0
  68. package/dist/chunk-b4esxhmw.js +2 -0
  69. package/dist/chunk-b8e5wzwn.js +44 -0
  70. package/dist/chunk-bggt4n6e.js +2 -0
  71. package/dist/chunk-cwkg37rs.js +37 -0
  72. package/dist/chunk-dskvqt67.js +7355 -0
  73. package/dist/chunk-gahsdmxf.js +1617 -0
  74. package/dist/chunk-gnv3r2jg.js +1 -0
  75. package/dist/chunk-hg3c48cp.js +34 -0
  76. package/dist/chunk-m5azeva3.js +2 -0
  77. package/dist/chunk-r3qf56b3.js +92 -0
  78. package/dist/chunk-sdqa9ghv.js +141 -0
  79. package/dist/chunk-svrvvcm7.js +1 -0
  80. package/dist/chunk-v0p3hpc3.js +60 -0
  81. package/dist/chunk-wdzjbecg.js +50 -0
  82. package/dist/chunk-ww8k3g7j.js +1 -0
  83. package/dist/chunk-xmqfc62w.js +1 -0
  84. package/dist/chunk-xrebj679.js +435 -0
  85. package/dist/chunk-xt3s24vr.js +2 -0
  86. package/dist/chunk-y21zk0pn.js +4 -0
  87. package/dist/deploy/attach-credentials.d.ts +57 -0
  88. package/dist/deploy/index.js +1 -168
  89. package/dist/deploy/site-ports.d.ts +151 -0
  90. package/dist/deploy/site-target.d.ts +24 -1
  91. package/dist/dns/index.js +1 -41
  92. package/dist/drivers/index.js +1 -154
  93. package/dist/index.js +100 -6129
  94. package/dist/protection/index.js +1 -89
  95. package/dist/push/index.js +1 -509
  96. package/dist/spend/index.js +1 -173
  97. package/dist/ui/404.html +16 -548
  98. package/dist/ui/_stx/chunk.5ddb5087.js +90 -0
  99. package/dist/ui/_stx/chunk.9fd39d5b.js +86 -0
  100. package/dist/ui/_stx/chunk.d5301f79.js +119 -0
  101. package/dist/ui/_stx/crosswind.222ed683.css +1 -0
  102. package/dist/ui/_stx/crosswind.616c25f3.css +1 -0
  103. package/dist/ui/_stx/crosswind.80baa894.css +320 -0
  104. package/dist/ui/_stx/crosswind.906ee17c.css +1 -0
  105. package/dist/ui/_stx/crosswind.996ed49e.css +1 -0
  106. package/dist/ui/_stx/crosswind.b7a99a5c.css +1 -0
  107. package/dist/ui/_stx/router.fa5bc4dc.js +4 -0
  108. package/dist/ui/_stx/runtime.c696d17a.js +1 -0
  109. package/dist/ui/_stx/style.448ab192.css +1 -0
  110. package/dist/ui/accept-invitation.html +2 -1133
  111. package/dist/ui/access-denied.html +7 -1130
  112. package/dist/ui/account/automation.html +12 -1134
  113. package/dist/ui/account/security.html +13 -1136
  114. package/dist/ui/applications/compose.html +12 -1134
  115. package/dist/ui/applications/new.html +13 -1137
  116. package/dist/ui/data/backups.html +91 -1136
  117. package/dist/ui/data/services.html +89 -1136
  118. package/dist/ui/data/volumes.html +61 -1136
  119. package/dist/ui/index.html +60 -1136
  120. package/dist/ui/infrastructure/topology.html +128 -1135
  121. package/dist/ui/integrations.html +13 -1138
  122. package/dist/ui/operations/alerts.html +59 -1136
  123. package/dist/ui/operations/configuration.html +13 -1135
  124. package/dist/ui/operations/jobs.html +87 -1136
  125. package/dist/ui/operations/maintenance.html +12 -1134
  126. package/dist/ui/operations/observability.html +93 -1136
  127. package/dist/ui/operations/previews.html +12 -1134
  128. package/dist/ui/operations/queue.html +12 -1134
  129. package/dist/ui/operations/regions.html +12 -1134
  130. package/dist/ui/operations/releases.html +12 -1134
  131. package/dist/ui/operations/spend.html +65 -1136
  132. package/dist/ui/operations/workloads.html +115 -1136
  133. package/dist/ui/robots.txt +2 -0
  134. package/dist/ui/security.html +24 -1136
  135. package/dist/ui/server/actions.html +67 -1138
  136. package/dist/ui/server/activity.html +7 -1130
  137. package/dist/ui/server/capacity.html +12 -1134
  138. package/dist/ui/server/database.html +166 -1135
  139. package/dist/ui/server/deployments.html +60 -1136
  140. package/dist/ui/server/diagnostics.html +7 -1130
  141. package/dist/ui/server/firewall.html +286 -1212
  142. package/dist/ui/server/fleet.html +45 -1136
  143. package/dist/ui/server/logs.html +34 -1135
  144. package/dist/ui/server/metrics.html +124 -1140
  145. package/dist/ui/server/security.html +7 -1130
  146. package/dist/ui/server/services.html +46 -1133
  147. package/dist/ui/server/sites.html +287 -1137
  148. package/dist/ui/server/ssh-keys.html +62 -1135
  149. package/dist/ui/server/team.html +155 -1136
  150. package/dist/ui/server/terminal.html +97 -1183
  151. package/dist/ui/serverless/alarms.html +62 -1135
  152. package/dist/ui/serverless/assets.html +47 -1132
  153. package/dist/ui/serverless/cost.html +7 -1130
  154. package/dist/ui/serverless/data.html +46 -1135
  155. package/dist/ui/serverless/deployments.html +47 -1132
  156. package/dist/ui/serverless/firewall.html +7 -1130
  157. package/dist/ui/serverless/functions.html +50 -1136
  158. package/dist/ui/serverless/logs.html +34 -1135
  159. package/dist/ui/serverless/metrics.html +7 -1130
  160. package/dist/ui/serverless/queues.html +58 -1135
  161. package/dist/ui/serverless/secrets.html +62 -1136
  162. package/dist/ui/serverless/traces.html +12 -1134
  163. package/dist/ui/serverless.html +64 -1137
  164. package/dist/ui-src/pages/server/firewall.stx +14 -14
  165. package/package.json +3 -3
  166. package/dist/chunk-01d86gt1.js +0 -1324
  167. package/dist/chunk-050r7ycy.js +0 -46516
  168. package/dist/chunk-29rse4p1.js +0 -12203
  169. package/dist/chunk-32e7ya18.js +0 -642
  170. package/dist/chunk-4cjrg98a.js +0 -597
  171. package/dist/chunk-50jpda9q.js +0 -371
  172. package/dist/chunk-6a1t7fnq.js +0 -12121
  173. package/dist/chunk-728jvrr0.js +0 -1701
  174. package/dist/chunk-7m60qnc8.js +0 -1762
  175. package/dist/chunk-aa2dkn7b.js +0 -205
  176. package/dist/chunk-d48vtdj4.js +0 -433
  177. package/dist/chunk-f55nkpvb.js +0 -8
  178. package/dist/chunk-fgk8cqew.js +0 -152
  179. package/dist/chunk-he9a874b.js +0 -769
  180. package/dist/chunk-hg7rhrgd.js +0 -7274
  181. package/dist/chunk-k2yyatfw.js +0 -10
  182. package/dist/chunk-k4px0bnv.js +0 -23
  183. package/dist/chunk-nsgqsmrh.js +0 -1779
  184. package/dist/chunk-qr803fye.js +0 -2122
  185. package/dist/chunk-rds0sy87.js +0 -1771
  186. package/dist/chunk-rxgchb08.js +0 -42033
  187. package/dist/chunk-ttz2ra5j.js +0 -12
  188. package/dist/chunk-v0bahtg2.js +0 -4
  189. package/dist/chunk-va2yd85b.js +0 -200
  190. package/dist/chunk-x9s0cqfx.js +0 -13
  191. package/dist/chunk-zqtpg06c.js +0 -1752
@@ -0,0 +1,1507 @@
1
+ import{kh as TE}from"./chunk-rxcvf10f.js";var X=["project:read","config:read","config:write","deployments:read","deployments:create","deployments:cancel","deployments:rollback","runtime:read","runtime:restart","runtime:logs","runtime:terminal","data:read","data:write","data:admin","backups:read","backups:create","backups:restore","secrets:read","secrets:write","fleet:read","fleet:manage","users:read","users:manage","users:transfer-ownership","audit:read","billing:read","billing:manage","security:read","security:manage","security:waive","sources:read","sources:manage","applications:read","applications:manage","automation:read","automation:manage","tags:manage"],Y=["project:read","config:read","deployments:read","runtime:read","runtime:logs","data:read","backups:read","security:read","sources:read","applications:read","billing:read"],_E={owner:new Set(X),admin:new Set(X.filter((E)=>E!=="users:transfer-ownership")),deployer:new Set([...Y,"deployments:create","deployments:cancel","deployments:rollback","sources:manage","applications:manage"]),operator:new Set([...Y,"runtime:restart","backups:create","security:manage","sources:manage","applications:manage"]),viewer:new Set(Y),auditor:new Set(["project:read","config:read","deployments:read","runtime:read","data:read","backups:read","audit:read","security:read","sources:read","applications:read"])};function u(E){return _E[E]}function x(E,T){if(E.type==="organization")return E.id===void 0||E.id===T.organizationId;if(!E.id)return!1;if(E.type==="project")return E.id===T.projectId;if(E.type==="environment")return E.id===T.environmentId;return E.id===T.resourceId}function V(E){let{membership:T,capability:N,target:e}=E;if(!T||T.status!=="active"||T.organizationId!==e.organizationId)return{allowed:!1,reason:"inactive-membership"};let L=E.grants.filter((s)=>s.organizationId===e.organizationId&&s.membershipId===T.id&&s.capability===N&&x(s.scope,e)),t=L.find((s)=>s.effect==="deny");if(t)return{allowed:!1,reason:"explicit-deny",inheritedFrom:{kind:"grant",id:t.id,scope:t.scope}};let _=L.find((s)=>s.effect==="allow");if(_)return{allowed:!0,reason:"explicit-allow",inheritedFrom:{kind:"grant",id:_.id,scope:_.scope}};if(!x(T.scope,e))return{allowed:!1,reason:"scope-mismatch"};if(u(T.roleTemplate).has(N))return{allowed:!0,reason:"role-template",inheritedFrom:{kind:"membership",id:T.id,scope:T.scope}};return{allowed:!1,reason:"missing-capability"}}function sE(E){return X.filter((T)=>V({...E,capability:T}).allowed)}import{createHash as g,randomBytes as dE}from"node:crypto";import{chmodSync as B,existsSync as w,mkdirSync as rE,statSync as nE}from"node:fs";import{dirname as EE}from"node:path";import{Database as oE}from"bun:sqlite";var C=40,G=[{version:1,name:"core_control_plane",sql:`
2
+ CREATE TABLE projects (
3
+ id TEXT PRIMARY KEY,
4
+ slug TEXT NOT NULL UNIQUE,
5
+ name TEXT NOT NULL,
6
+ description TEXT,
7
+ organization_id TEXT,
8
+ desired_config_hash TEXT,
9
+ version INTEGER NOT NULL DEFAULT 1 CHECK (version > 0),
10
+ created_at TEXT NOT NULL,
11
+ updated_at TEXT NOT NULL
12
+ ) STRICT;
13
+
14
+ CREATE TABLE environments (
15
+ id TEXT PRIMARY KEY,
16
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
17
+ slug TEXT NOT NULL,
18
+ name TEXT NOT NULL,
19
+ kind TEXT NOT NULL,
20
+ region TEXT,
21
+ desired_state TEXT NOT NULL DEFAULT '{}',
22
+ discovered_state TEXT NOT NULL DEFAULT '{}',
23
+ version INTEGER NOT NULL DEFAULT 1 CHECK (version > 0),
24
+ created_at TEXT NOT NULL,
25
+ updated_at TEXT NOT NULL,
26
+ UNIQUE(project_id, slug)
27
+ ) STRICT;
28
+
29
+ CREATE TABLE resources (
30
+ id TEXT PRIMARY KEY,
31
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
32
+ environment_id TEXT REFERENCES environments(id) ON DELETE CASCADE,
33
+ kind TEXT NOT NULL,
34
+ slug TEXT NOT NULL,
35
+ name TEXT NOT NULL,
36
+ provider TEXT,
37
+ provider_id TEXT,
38
+ desired_state TEXT NOT NULL DEFAULT '{}',
39
+ discovered_state TEXT NOT NULL DEFAULT '{}',
40
+ metadata TEXT NOT NULL DEFAULT '{}',
41
+ version INTEGER NOT NULL DEFAULT 1 CHECK (version > 0),
42
+ created_at TEXT NOT NULL,
43
+ updated_at TEXT NOT NULL,
44
+ UNIQUE(project_id, environment_id, kind, slug)
45
+ ) STRICT;
46
+
47
+ CREATE TABLE actors (
48
+ id TEXT PRIMARY KEY,
49
+ kind TEXT NOT NULL CHECK (kind IN ('user', 'service_account', 'system')),
50
+ external_id TEXT,
51
+ display_name TEXT NOT NULL,
52
+ metadata TEXT NOT NULL DEFAULT '{}',
53
+ disabled_at TEXT,
54
+ version INTEGER NOT NULL DEFAULT 1 CHECK (version > 0),
55
+ created_at TEXT NOT NULL,
56
+ updated_at TEXT NOT NULL,
57
+ UNIQUE(kind, external_id)
58
+ ) STRICT;
59
+
60
+ CREATE TABLE operations (
61
+ id TEXT PRIMARY KEY,
62
+ project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
63
+ environment_id TEXT REFERENCES environments(id) ON DELETE SET NULL,
64
+ resource_id TEXT REFERENCES resources(id) ON DELETE SET NULL,
65
+ actor_id TEXT REFERENCES actors(id) ON DELETE SET NULL,
66
+ kind TEXT NOT NULL,
67
+ state TEXT NOT NULL CHECK (state IN ('queued', 'running', 'succeeded', 'failed', 'cancelled', 'timed_out')),
68
+ correlation_id TEXT NOT NULL,
69
+ idempotency_key TEXT UNIQUE,
70
+ input TEXT NOT NULL DEFAULT '{}',
71
+ output TEXT NOT NULL DEFAULT '{}',
72
+ error TEXT,
73
+ attempt INTEGER NOT NULL DEFAULT 0 CHECK (attempt >= 0),
74
+ priority INTEGER NOT NULL DEFAULT 0,
75
+ lease_owner TEXT,
76
+ lease_expires_at TEXT,
77
+ cancel_requested_at TEXT,
78
+ started_at TEXT,
79
+ finished_at TEXT,
80
+ version INTEGER NOT NULL DEFAULT 1 CHECK (version > 0),
81
+ created_at TEXT NOT NULL,
82
+ updated_at TEXT NOT NULL
83
+ ) STRICT;
84
+
85
+ CREATE TABLE events (
86
+ sequence INTEGER PRIMARY KEY AUTOINCREMENT,
87
+ id TEXT NOT NULL UNIQUE,
88
+ project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
89
+ operation_id TEXT REFERENCES operations(id) ON DELETE SET NULL,
90
+ resource_id TEXT REFERENCES resources(id) ON DELETE SET NULL,
91
+ actor_id TEXT REFERENCES actors(id) ON DELETE SET NULL,
92
+ correlation_id TEXT NOT NULL,
93
+ type TEXT NOT NULL,
94
+ level TEXT NOT NULL CHECK (level IN ('debug', 'info', 'warning', 'error')),
95
+ payload TEXT NOT NULL DEFAULT '{}',
96
+ created_at TEXT NOT NULL
97
+ ) STRICT;
98
+
99
+ CREATE TABLE settings (
100
+ key TEXT PRIMARY KEY,
101
+ value TEXT NOT NULL,
102
+ updated_at TEXT NOT NULL
103
+ ) STRICT;
104
+
105
+ CREATE TABLE schema_migrations (
106
+ version INTEGER PRIMARY KEY,
107
+ name TEXT NOT NULL,
108
+ applied_at TEXT NOT NULL
109
+ ) STRICT;
110
+ `},{version:2,name:"operations_and_event_indexes",sql:`
111
+ CREATE INDEX operations_queue_idx ON operations(state, priority DESC, created_at ASC);
112
+ CREATE INDEX operations_project_idx ON operations(project_id, created_at DESC);
113
+ CREATE INDEX operations_lease_idx ON operations(state, lease_expires_at);
114
+ CREATE INDEX operations_correlation_idx ON operations(correlation_id);
115
+ CREATE INDEX events_project_idx ON events(project_id, sequence DESC);
116
+ CREATE INDEX events_operation_idx ON events(operation_id, sequence ASC);
117
+ CREATE INDEX events_resource_idx ON events(resource_id, sequence DESC);
118
+ CREATE INDEX events_correlation_idx ON events(correlation_id, sequence ASC);
119
+ CREATE INDEX resources_environment_idx ON resources(environment_id, kind, slug);
120
+ `},{version:3,name:"resource_discovery_preferences",sql:`
121
+ CREATE TABLE tags (
122
+ id TEXT PRIMARY KEY,
123
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
124
+ name TEXT NOT NULL,
125
+ normalized_name TEXT NOT NULL,
126
+ color TEXT NOT NULL,
127
+ created_at TEXT NOT NULL,
128
+ updated_at TEXT NOT NULL,
129
+ UNIQUE(project_id, normalized_name)
130
+ ) STRICT;
131
+
132
+ CREATE TABLE resource_tags (
133
+ resource_id TEXT NOT NULL REFERENCES resources(id) ON DELETE CASCADE,
134
+ tag_id TEXT NOT NULL REFERENCES tags(id) ON DELETE CASCADE,
135
+ created_at TEXT NOT NULL,
136
+ PRIMARY KEY(resource_id, tag_id)
137
+ ) STRICT;
138
+
139
+ CREATE TABLE saved_filters (
140
+ id TEXT PRIMARY KEY,
141
+ actor_key TEXT NOT NULL,
142
+ name TEXT NOT NULL,
143
+ route_id TEXT NOT NULL,
144
+ query TEXT NOT NULL,
145
+ created_at TEXT NOT NULL,
146
+ updated_at TEXT NOT NULL,
147
+ UNIQUE(actor_key, name)
148
+ ) STRICT;
149
+
150
+ CREATE TABLE navigation_items (
151
+ actor_key TEXT NOT NULL,
152
+ entity_type TEXT NOT NULL,
153
+ entity_id TEXT NOT NULL,
154
+ favorite INTEGER NOT NULL DEFAULT 0 CHECK (favorite IN (0, 1)),
155
+ last_visited_at TEXT NOT NULL,
156
+ visit_count INTEGER NOT NULL DEFAULT 1 CHECK (visit_count > 0),
157
+ PRIMARY KEY(actor_key, entity_type, entity_id)
158
+ ) STRICT;
159
+
160
+ CREATE INDEX tags_project_idx ON tags(project_id, normalized_name);
161
+ CREATE INDEX resource_tags_tag_idx ON resource_tags(tag_id, resource_id);
162
+ CREATE INDEX saved_filters_actor_idx ON saved_filters(actor_key, updated_at DESC);
163
+ CREATE INDEX navigation_actor_idx ON navigation_items(actor_key, favorite DESC, last_visited_at DESC);
164
+ `},{version:4,name:"organizations_and_scoped_authorization",sql:`
165
+ CREATE TABLE organizations (
166
+ id TEXT PRIMARY KEY,
167
+ slug TEXT NOT NULL UNIQUE,
168
+ name TEXT NOT NULL,
169
+ created_at TEXT NOT NULL,
170
+ updated_at TEXT NOT NULL
171
+ ) STRICT;
172
+
173
+ ALTER TABLE events ADD COLUMN organization_id TEXT REFERENCES organizations(id) ON DELETE SET NULL;
174
+
175
+ CREATE TABLE organization_memberships (
176
+ id TEXT PRIMARY KEY,
177
+ organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
178
+ actor_id TEXT NOT NULL REFERENCES actors(id) ON DELETE CASCADE,
179
+ role_template TEXT NOT NULL CHECK (role_template IN ('owner', 'admin', 'deployer', 'operator', 'viewer', 'auditor')),
180
+ scope_type TEXT NOT NULL CHECK (scope_type IN ('organization', 'project', 'environment', 'resource')),
181
+ scope_id TEXT,
182
+ status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'revoked')),
183
+ session_version INTEGER NOT NULL DEFAULT 1 CHECK (session_version > 0),
184
+ last_active_at TEXT,
185
+ created_at TEXT NOT NULL,
186
+ updated_at TEXT NOT NULL,
187
+ UNIQUE(organization_id, actor_id),
188
+ CHECK ((scope_type = 'organization' AND scope_id IS NULL) OR (scope_type != 'organization' AND scope_id IS NOT NULL))
189
+ ) STRICT;
190
+
191
+ CREATE TABLE organization_invitations (
192
+ id TEXT PRIMARY KEY,
193
+ organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
194
+ email TEXT NOT NULL,
195
+ role_template TEXT NOT NULL CHECK (role_template IN ('owner', 'admin', 'deployer', 'operator', 'viewer', 'auditor')),
196
+ scope_type TEXT NOT NULL CHECK (scope_type IN ('organization', 'project', 'environment', 'resource')),
197
+ scope_id TEXT,
198
+ token_hash TEXT NOT NULL UNIQUE,
199
+ invited_by_actor_id TEXT REFERENCES actors(id) ON DELETE SET NULL,
200
+ accepted_by_actor_id TEXT REFERENCES actors(id) ON DELETE SET NULL,
201
+ expires_at TEXT NOT NULL,
202
+ accepted_at TEXT,
203
+ revoked_at TEXT,
204
+ created_at TEXT NOT NULL,
205
+ updated_at TEXT NOT NULL,
206
+ CHECK ((scope_type = 'organization' AND scope_id IS NULL) OR (scope_type != 'organization' AND scope_id IS NOT NULL))
207
+ ) STRICT;
208
+
209
+ CREATE TABLE authorization_grants (
210
+ id TEXT PRIMARY KEY,
211
+ organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
212
+ membership_id TEXT NOT NULL REFERENCES organization_memberships(id) ON DELETE CASCADE,
213
+ effect TEXT NOT NULL CHECK (effect IN ('allow', 'deny')),
214
+ capability TEXT NOT NULL,
215
+ scope_type TEXT NOT NULL CHECK (scope_type IN ('organization', 'project', 'environment', 'resource')),
216
+ scope_id TEXT,
217
+ created_at TEXT NOT NULL,
218
+ updated_at TEXT NOT NULL,
219
+ UNIQUE(membership_id, effect, capability, scope_type, scope_id),
220
+ CHECK ((scope_type = 'organization' AND scope_id IS NULL) OR (scope_type != 'organization' AND scope_id IS NOT NULL))
221
+ ) STRICT;
222
+
223
+ CREATE INDEX memberships_org_idx ON organization_memberships(organization_id, status, role_template);
224
+ CREATE INDEX memberships_actor_idx ON organization_memberships(actor_id, status);
225
+ CREATE INDEX invitations_org_idx ON organization_invitations(organization_id, accepted_at, revoked_at, expires_at);
226
+ CREATE INDEX grants_membership_idx ON authorization_grants(membership_id, capability, effect);
227
+ CREATE UNIQUE INDEX grants_unique_idx ON authorization_grants(membership_id, effect, capability, scope_type, COALESCE(scope_id, ''));
228
+ CREATE INDEX projects_organization_idx ON projects(organization_id, slug);
229
+ CREATE INDEX events_organization_idx ON events(organization_id, sequence DESC);
230
+ `},{version:5,name:"authorization_provenance",sql:`
231
+ ALTER TABLE organization_memberships ADD COLUMN source TEXT NOT NULL DEFAULT 'manual' CHECK (source IN ('manual', 'legacy', 'invitation'));
232
+ ALTER TABLE authorization_grants ADD COLUMN source TEXT NOT NULL DEFAULT 'manual' CHECK (source IN ('manual', 'legacy', 'invitation'));
233
+ CREATE INDEX grants_source_idx ON authorization_grants(membership_id, source);
234
+ `},{version:6,name:"durable_authentication",sql:`
235
+ CREATE TABLE auth_identities (
236
+ id TEXT PRIMARY KEY,
237
+ actor_id TEXT NOT NULL UNIQUE REFERENCES actors(id) ON DELETE CASCADE,
238
+ username TEXT NOT NULL COLLATE NOCASE UNIQUE,
239
+ email TEXT COLLATE NOCASE UNIQUE,
240
+ email_verified_at TEXT,
241
+ password_hash TEXT NOT NULL,
242
+ credential_version INTEGER NOT NULL DEFAULT 1 CHECK (credential_version > 0),
243
+ requires_password_upgrade INTEGER NOT NULL DEFAULT 0 CHECK (requires_password_upgrade IN (0, 1)),
244
+ disabled_at TEXT,
245
+ last_login_at TEXT,
246
+ created_at TEXT NOT NULL,
247
+ updated_at TEXT NOT NULL
248
+ ) STRICT;
249
+
250
+ CREATE TABLE auth_action_tokens (
251
+ id TEXT PRIMARY KEY,
252
+ identity_id TEXT NOT NULL REFERENCES auth_identities(id) ON DELETE CASCADE,
253
+ type TEXT NOT NULL CHECK (type IN ('activation', 'password_reset', 'email_verification')),
254
+ token_hash TEXT NOT NULL UNIQUE,
255
+ metadata TEXT NOT NULL DEFAULT '{}',
256
+ expires_at TEXT NOT NULL,
257
+ consumed_at TEXT,
258
+ created_at TEXT NOT NULL
259
+ ) STRICT;
260
+
261
+ CREATE TABLE auth_sessions (
262
+ id TEXT PRIMARY KEY,
263
+ identity_id TEXT NOT NULL REFERENCES auth_identities(id) ON DELETE CASCADE,
264
+ token_hash TEXT NOT NULL UNIQUE,
265
+ credential_version INTEGER NOT NULL CHECK (credential_version > 0),
266
+ auth_method TEXT NOT NULL CHECK (auth_method IN ('local', 'oidc')),
267
+ user_agent TEXT,
268
+ network_hint TEXT,
269
+ created_at TEXT NOT NULL,
270
+ last_used_at TEXT NOT NULL,
271
+ idle_expires_at TEXT NOT NULL,
272
+ absolute_expires_at TEXT NOT NULL,
273
+ recent_auth_at TEXT NOT NULL,
274
+ mfa_at TEXT,
275
+ revoked_at TEXT
276
+ ) STRICT;
277
+
278
+ CREATE INDEX auth_identities_email_idx ON auth_identities(email);
279
+ CREATE INDEX auth_action_tokens_identity_idx ON auth_action_tokens(identity_id, type, created_at DESC);
280
+ CREATE INDEX auth_action_tokens_expiry_idx ON auth_action_tokens(expires_at, consumed_at);
281
+ CREATE INDEX auth_sessions_identity_idx ON auth_sessions(identity_id, revoked_at, last_used_at DESC);
282
+ CREATE INDEX auth_sessions_expiry_idx ON auth_sessions(idle_expires_at, absolute_expires_at, revoked_at);
283
+ `},{version:7,name:"multi_factor_authentication",sql:`
284
+ CREATE TABLE auth_mfa_factors (
285
+ id TEXT PRIMARY KEY,
286
+ identity_id TEXT NOT NULL UNIQUE REFERENCES auth_identities(id) ON DELETE CASCADE,
287
+ type TEXT NOT NULL DEFAULT 'totp' CHECK (type = 'totp'),
288
+ label TEXT NOT NULL,
289
+ secret_ciphertext TEXT NOT NULL,
290
+ created_at TEXT NOT NULL,
291
+ verified_at TEXT,
292
+ disabled_at TEXT,
293
+ last_used_step INTEGER
294
+ ) STRICT;
295
+
296
+ CREATE TABLE auth_recovery_codes (
297
+ id TEXT PRIMARY KEY,
298
+ identity_id TEXT NOT NULL REFERENCES auth_identities(id) ON DELETE CASCADE,
299
+ code_hash TEXT NOT NULL UNIQUE,
300
+ created_at TEXT NOT NULL,
301
+ consumed_at TEXT
302
+ ) STRICT;
303
+
304
+ CREATE TABLE auth_mfa_challenges (
305
+ id TEXT PRIMARY KEY,
306
+ identity_id TEXT NOT NULL REFERENCES auth_identities(id) ON DELETE CASCADE,
307
+ purpose TEXT NOT NULL CHECK (purpose IN ('login', 'step_up')),
308
+ token_hash TEXT NOT NULL UNIQUE,
309
+ attempts INTEGER NOT NULL DEFAULT 0 CHECK (attempts >= 0),
310
+ expires_at TEXT NOT NULL,
311
+ consumed_at TEXT,
312
+ created_at TEXT NOT NULL
313
+ ) STRICT;
314
+
315
+ CREATE INDEX auth_recovery_codes_identity_idx ON auth_recovery_codes(identity_id, consumed_at);
316
+ CREATE INDEX auth_mfa_challenges_identity_idx ON auth_mfa_challenges(identity_id, purpose, expires_at);
317
+ CREATE INDEX auth_mfa_challenges_expiry_idx ON auth_mfa_challenges(expires_at, consumed_at);
318
+ `},{version:8,name:"openid_connect_authentication",sql:`
319
+ CREATE TABLE auth_oidc_providers (
320
+ id TEXT PRIMARY KEY,
321
+ organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
322
+ slug TEXT NOT NULL COLLATE NOCASE UNIQUE,
323
+ name TEXT NOT NULL,
324
+ issuer TEXT NOT NULL UNIQUE,
325
+ client_id TEXT NOT NULL,
326
+ client_secret_ciphertext TEXT,
327
+ scopes TEXT NOT NULL DEFAULT '["openid","email","profile"]',
328
+ allowed_domains TEXT NOT NULL,
329
+ default_role TEXT NOT NULL DEFAULT 'viewer' CHECK (default_role IN ('admin', 'deployer', 'operator', 'viewer', 'auditor')),
330
+ enabled INTEGER NOT NULL DEFAULT 1 CHECK (enabled IN (0, 1)),
331
+ enforce_sso INTEGER NOT NULL DEFAULT 0 CHECK (enforce_sso IN (0, 1)),
332
+ created_at TEXT NOT NULL,
333
+ updated_at TEXT NOT NULL
334
+ ) STRICT;
335
+
336
+ CREATE TABLE auth_oidc_subjects (
337
+ id TEXT PRIMARY KEY,
338
+ provider_id TEXT NOT NULL REFERENCES auth_oidc_providers(id) ON DELETE CASCADE,
339
+ identity_id TEXT NOT NULL REFERENCES auth_identities(id) ON DELETE CASCADE,
340
+ subject TEXT NOT NULL,
341
+ email TEXT NOT NULL COLLATE NOCASE,
342
+ linked_at TEXT NOT NULL,
343
+ last_login_at TEXT NOT NULL,
344
+ UNIQUE(provider_id, subject),
345
+ UNIQUE(provider_id, identity_id)
346
+ ) STRICT;
347
+
348
+ CREATE TABLE auth_oidc_transactions (
349
+ id TEXT PRIMARY KEY,
350
+ provider_id TEXT NOT NULL REFERENCES auth_oidc_providers(id) ON DELETE CASCADE,
351
+ state_hash TEXT NOT NULL UNIQUE,
352
+ nonce_ciphertext TEXT NOT NULL,
353
+ verifier_ciphertext TEXT NOT NULL,
354
+ redirect_uri TEXT NOT NULL,
355
+ return_path TEXT NOT NULL,
356
+ expires_at TEXT NOT NULL,
357
+ consumed_at TEXT,
358
+ created_at TEXT NOT NULL
359
+ ) STRICT;
360
+
361
+ CREATE INDEX auth_oidc_providers_org_idx ON auth_oidc_providers(organization_id, enabled, name);
362
+ CREATE INDEX auth_oidc_subjects_identity_idx ON auth_oidc_subjects(identity_id, provider_id);
363
+ CREATE INDEX auth_oidc_transactions_expiry_idx ON auth_oidc_transactions(expires_at, consumed_at);
364
+ `},{version:9,name:"service_accounts_and_api_tokens",sql:`
365
+ CREATE TABLE service_accounts (
366
+ id TEXT PRIMARY KEY,
367
+ organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
368
+ actor_id TEXT NOT NULL UNIQUE REFERENCES actors(id) ON DELETE CASCADE,
369
+ slug TEXT NOT NULL COLLATE NOCASE,
370
+ name TEXT NOT NULL,
371
+ description TEXT,
372
+ created_by_actor_id TEXT REFERENCES actors(id) ON DELETE SET NULL,
373
+ disabled_at TEXT,
374
+ created_at TEXT NOT NULL,
375
+ updated_at TEXT NOT NULL,
376
+ UNIQUE(organization_id, slug)
377
+ ) STRICT;
378
+
379
+ CREATE TABLE api_tokens (
380
+ id TEXT PRIMARY KEY,
381
+ service_account_id TEXT NOT NULL REFERENCES service_accounts(id) ON DELETE CASCADE,
382
+ name TEXT NOT NULL,
383
+ token_hash TEXT NOT NULL UNIQUE,
384
+ token_prefix TEXT NOT NULL UNIQUE,
385
+ capabilities TEXT NOT NULL,
386
+ scope_type TEXT NOT NULL CHECK (scope_type IN ('organization', 'project', 'environment', 'resource')),
387
+ scope_id TEXT,
388
+ expires_at TEXT NOT NULL,
389
+ last_used_at TEXT,
390
+ last_network_hint TEXT,
391
+ revoked_at TEXT,
392
+ rotated_from_token_id TEXT REFERENCES api_tokens(id) ON DELETE SET NULL,
393
+ created_by_actor_id TEXT REFERENCES actors(id) ON DELETE SET NULL,
394
+ created_at TEXT NOT NULL,
395
+ updated_at TEXT NOT NULL,
396
+ CHECK ((scope_type = 'organization' AND scope_id IS NULL) OR (scope_type != 'organization' AND scope_id IS NOT NULL))
397
+ ) STRICT;
398
+
399
+ CREATE TABLE api_idempotency_records (
400
+ id TEXT PRIMARY KEY,
401
+ token_id TEXT NOT NULL REFERENCES api_tokens(id) ON DELETE CASCADE,
402
+ idempotency_key TEXT NOT NULL,
403
+ request_hash TEXT NOT NULL,
404
+ operation_id TEXT REFERENCES operations(id) ON DELETE SET NULL,
405
+ response_status INTEGER NOT NULL,
406
+ response_body TEXT NOT NULL,
407
+ expires_at TEXT NOT NULL,
408
+ created_at TEXT NOT NULL,
409
+ UNIQUE(token_id, idempotency_key)
410
+ ) STRICT;
411
+
412
+ CREATE INDEX service_accounts_org_idx ON service_accounts(organization_id, disabled_at, name);
413
+ CREATE INDEX api_tokens_account_idx ON api_tokens(service_account_id, revoked_at, expires_at);
414
+ CREATE INDEX api_tokens_expiry_idx ON api_tokens(expires_at, revoked_at);
415
+ CREATE INDEX api_idempotency_expiry_idx ON api_idempotency_records(expires_at);
416
+ `},{version:10,name:"security_posture_and_deploy_policy",sql:`
417
+ CREATE TABLE security_scan_runs (
418
+ id TEXT PRIMARY KEY,
419
+ organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
420
+ project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
421
+ environment_id TEXT REFERENCES environments(id) ON DELETE CASCADE,
422
+ resource_id TEXT REFERENCES resources(id) ON DELETE CASCADE,
423
+ release_id TEXT,
424
+ scanner_id TEXT NOT NULL,
425
+ scanner_version TEXT NOT NULL,
426
+ status TEXT NOT NULL CHECK (status IN ('passed', 'failed', 'skipped', 'unavailable', 'unsupported', 'stale')),
427
+ error TEXT,
428
+ metadata TEXT NOT NULL DEFAULT '{}',
429
+ findings_count INTEGER NOT NULL DEFAULT 0 CHECK (findings_count >= 0),
430
+ started_at TEXT NOT NULL,
431
+ completed_at TEXT NOT NULL,
432
+ duration_ms INTEGER NOT NULL CHECK (duration_ms >= 0)
433
+ ) STRICT;
434
+
435
+ CREATE TABLE security_findings (
436
+ id TEXT PRIMARY KEY,
437
+ fingerprint TEXT NOT NULL UNIQUE,
438
+ organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
439
+ project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
440
+ environment_id TEXT REFERENCES environments(id) ON DELETE CASCADE,
441
+ resource_id TEXT REFERENCES resources(id) ON DELETE CASCADE,
442
+ release_id TEXT,
443
+ scan_run_id TEXT NOT NULL REFERENCES security_scan_runs(id) ON DELETE CASCADE,
444
+ scanner_id TEXT NOT NULL,
445
+ scanner_version TEXT NOT NULL,
446
+ rule_id TEXT NOT NULL,
447
+ severity TEXT NOT NULL CHECK (severity IN ('info', 'low', 'medium', 'high', 'critical')),
448
+ title TEXT NOT NULL,
449
+ description TEXT NOT NULL,
450
+ evidence TEXT NOT NULL DEFAULT '{}',
451
+ remediation TEXT,
452
+ subject TEXT NOT NULL,
453
+ status TEXT NOT NULL CHECK (status IN ('open', 'acknowledged', 'resolved', 'waived')),
454
+ owner_actor_id TEXT REFERENCES actors(id) ON DELETE SET NULL,
455
+ recurrence_count INTEGER NOT NULL DEFAULT 0 CHECK (recurrence_count >= 0),
456
+ first_seen_at TEXT NOT NULL,
457
+ last_seen_at TEXT NOT NULL,
458
+ resolved_at TEXT,
459
+ updated_at TEXT NOT NULL
460
+ ) STRICT;
461
+
462
+ CREATE TABLE security_policies (
463
+ id TEXT PRIMARY KEY,
464
+ organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
465
+ environment_id TEXT REFERENCES environments(id) ON DELETE CASCADE,
466
+ name TEXT NOT NULL,
467
+ rules TEXT NOT NULL,
468
+ scanner_fail_mode TEXT NOT NULL CHECK (scanner_fail_mode IN ('open', 'closed')),
469
+ enabled INTEGER NOT NULL DEFAULT 1 CHECK (enabled IN (0, 1)),
470
+ version INTEGER NOT NULL DEFAULT 1 CHECK (version > 0),
471
+ created_by_actor_id TEXT REFERENCES actors(id) ON DELETE SET NULL,
472
+ created_at TEXT NOT NULL,
473
+ updated_at TEXT NOT NULL,
474
+ UNIQUE(organization_id, environment_id, name)
475
+ ) STRICT;
476
+
477
+ CREATE TABLE security_waivers (
478
+ id TEXT PRIMARY KEY,
479
+ finding_id TEXT NOT NULL REFERENCES security_findings(id) ON DELETE CASCADE,
480
+ policy_id TEXT REFERENCES security_policies(id) ON DELETE SET NULL,
481
+ reason TEXT NOT NULL,
482
+ reference_url TEXT,
483
+ created_by_actor_id TEXT NOT NULL REFERENCES actors(id) ON DELETE RESTRICT,
484
+ expires_at TEXT NOT NULL,
485
+ revoked_at TEXT,
486
+ created_at TEXT NOT NULL,
487
+ updated_at TEXT NOT NULL
488
+ ) STRICT;
489
+
490
+ CREATE TABLE security_finding_comments (
491
+ id TEXT PRIMARY KEY,
492
+ finding_id TEXT NOT NULL REFERENCES security_findings(id) ON DELETE CASCADE,
493
+ actor_id TEXT NOT NULL REFERENCES actors(id) ON DELETE RESTRICT,
494
+ body TEXT NOT NULL,
495
+ reference_url TEXT,
496
+ created_at TEXT NOT NULL
497
+ ) STRICT;
498
+
499
+ CREATE TABLE release_security_artifacts (
500
+ id TEXT PRIMARY KEY,
501
+ organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
502
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
503
+ environment_id TEXT REFERENCES environments(id) ON DELETE CASCADE,
504
+ release_id TEXT NOT NULL,
505
+ kind TEXT NOT NULL CHECK (kind IN ('sbom', 'vulnerability_summary', 'signature', 'provenance')),
506
+ format TEXT NOT NULL,
507
+ digest TEXT NOT NULL,
508
+ summary TEXT NOT NULL DEFAULT '{}',
509
+ content TEXT,
510
+ sensitive INTEGER NOT NULL DEFAULT 1 CHECK (sensitive IN (0, 1)),
511
+ created_at TEXT NOT NULL,
512
+ UNIQUE(release_id, kind, digest)
513
+ ) STRICT;
514
+
515
+ CREATE TABLE security_deploy_decisions (
516
+ id TEXT PRIMARY KEY,
517
+ organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
518
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
519
+ environment_id TEXT NOT NULL REFERENCES environments(id) ON DELETE CASCADE,
520
+ operation_id TEXT REFERENCES operations(id) ON DELETE SET NULL,
521
+ policy_id TEXT NOT NULL REFERENCES security_policies(id) ON DELETE RESTRICT,
522
+ policy_version INTEGER NOT NULL CHECK (policy_version > 0),
523
+ outcome TEXT NOT NULL CHECK (outcome IN ('allow', 'warn', 'block')),
524
+ scanner_versions TEXT NOT NULL,
525
+ finding_ids TEXT NOT NULL,
526
+ waiver_ids TEXT NOT NULL,
527
+ explanation TEXT NOT NULL,
528
+ created_at TEXT NOT NULL
529
+ ) STRICT;
530
+
531
+ CREATE INDEX security_scan_scope_idx ON security_scan_runs(organization_id, project_id, environment_id, completed_at DESC);
532
+ CREATE INDEX security_scan_scanner_idx ON security_scan_runs(scanner_id, completed_at DESC);
533
+ CREATE INDEX security_findings_scope_idx ON security_findings(organization_id, project_id, environment_id, status, severity);
534
+ CREATE INDEX security_findings_scanner_idx ON security_findings(scanner_id, rule_id, last_seen_at DESC);
535
+ CREATE INDEX security_waivers_finding_idx ON security_waivers(finding_id, expires_at, revoked_at);
536
+ CREATE INDEX security_comments_finding_idx ON security_finding_comments(finding_id, created_at);
537
+ CREATE INDEX release_security_artifacts_release_idx ON release_security_artifacts(release_id, kind);
538
+ CREATE INDEX security_decisions_environment_idx ON security_deploy_decisions(environment_id, created_at DESC);
539
+ `},{version:11,name:"required_security_scanners",sql:`
540
+ ALTER TABLE security_policies ADD COLUMN required_scanners TEXT NOT NULL DEFAULT '[]';
541
+ `},{version:12,name:"git_source_connections",sql:`
542
+ CREATE TABLE source_connections (
543
+ id TEXT PRIMARY KEY,
544
+ organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
545
+ provider TEXT NOT NULL CHECK (provider IN ('github', 'gitlab', 'bitbucket', 'gitea', 'generic_https', 'generic_ssh')),
546
+ name TEXT NOT NULL,
547
+ host TEXT NOT NULL,
548
+ owner TEXT,
549
+ auth_kind TEXT NOT NULL CHECK (auth_kind IN ('app', 'oauth_token', 'access_token', 'deploy_key', 'none')),
550
+ credential_ciphertext TEXT,
551
+ credential_fingerprint TEXT,
552
+ granted_scopes TEXT NOT NULL DEFAULT '[]',
553
+ capabilities TEXT NOT NULL DEFAULT '{}',
554
+ status TEXT NOT NULL CHECK (status IN ('pending', 'healthy', 'degraded', 'expired', 'disconnected')),
555
+ health_message TEXT,
556
+ last_tested_at TEXT,
557
+ last_synced_at TEXT,
558
+ credential_expires_at TEXT,
559
+ version INTEGER NOT NULL DEFAULT 1 CHECK (version > 0),
560
+ created_by_actor_id TEXT REFERENCES actors(id) ON DELETE SET NULL,
561
+ created_at TEXT NOT NULL,
562
+ updated_at TEXT NOT NULL,
563
+ UNIQUE(organization_id, name)
564
+ ) STRICT;
565
+
566
+ CREATE TABLE source_repositories (
567
+ id TEXT PRIMARY KEY,
568
+ connection_id TEXT NOT NULL REFERENCES source_connections(id) ON DELETE CASCADE,
569
+ provider_repository_id TEXT NOT NULL,
570
+ full_name TEXT NOT NULL,
571
+ clone_url TEXT NOT NULL,
572
+ default_branch TEXT NOT NULL,
573
+ visibility TEXT NOT NULL CHECK (visibility IN ('public', 'private', 'internal', 'unknown')),
574
+ archived INTEGER NOT NULL DEFAULT 0 CHECK (archived IN (0, 1)),
575
+ metadata TEXT NOT NULL DEFAULT '{}',
576
+ synced_at TEXT NOT NULL,
577
+ UNIQUE(connection_id, provider_repository_id),
578
+ UNIQUE(connection_id, full_name)
579
+ ) STRICT;
580
+
581
+ CREATE TABLE source_deploy_keys (
582
+ id TEXT PRIMARY KEY,
583
+ connection_id TEXT NOT NULL REFERENCES source_connections(id) ON DELETE CASCADE,
584
+ name TEXT NOT NULL,
585
+ public_key TEXT NOT NULL,
586
+ public_key_fingerprint TEXT NOT NULL,
587
+ private_key_ciphertext TEXT NOT NULL,
588
+ host TEXT NOT NULL,
589
+ host_key TEXT NOT NULL,
590
+ host_key_fingerprint TEXT NOT NULL,
591
+ created_by_actor_id TEXT REFERENCES actors(id) ON DELETE SET NULL,
592
+ created_at TEXT NOT NULL,
593
+ updated_at TEXT NOT NULL,
594
+ UNIQUE(connection_id, name),
595
+ UNIQUE(connection_id, public_key_fingerprint)
596
+ ) STRICT;
597
+
598
+ CREATE TABLE source_bindings (
599
+ id TEXT PRIMARY KEY,
600
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
601
+ environment_id TEXT REFERENCES environments(id) ON DELETE CASCADE,
602
+ resource_id TEXT REFERENCES resources(id) ON DELETE CASCADE,
603
+ connection_id TEXT NOT NULL REFERENCES source_connections(id) ON DELETE RESTRICT,
604
+ repository_id TEXT REFERENCES source_repositories(id) ON DELETE SET NULL,
605
+ repository_full_name TEXT NOT NULL,
606
+ default_branch TEXT NOT NULL,
607
+ branch_rule TEXT,
608
+ tag_rule TEXT,
609
+ monorepo_root TEXT NOT NULL DEFAULT '.',
610
+ include_paths TEXT NOT NULL DEFAULT '[]',
611
+ exclude_paths TEXT NOT NULL DEFAULT '[]',
612
+ submodules INTEGER NOT NULL DEFAULT 0 CHECK (submodules IN (0, 1)),
613
+ clone_depth INTEGER CHECK (clone_depth IS NULL OR clone_depth > 0),
614
+ deploy_key_id TEXT REFERENCES source_deploy_keys(id) ON DELETE SET NULL,
615
+ auto_deploy INTEGER NOT NULL DEFAULT 1 CHECK (auto_deploy IN (0, 1)),
616
+ pull_request_previews INTEGER NOT NULL DEFAULT 1 CHECK (pull_request_previews IN (0, 1)),
617
+ status TEXT NOT NULL CHECK (status IN ('active', 'disabled')),
618
+ disabled_reason TEXT,
619
+ version INTEGER NOT NULL DEFAULT 1 CHECK (version > 0),
620
+ created_by_actor_id TEXT REFERENCES actors(id) ON DELETE SET NULL,
621
+ created_at TEXT NOT NULL,
622
+ updated_at TEXT NOT NULL,
623
+ UNIQUE(project_id, environment_id, resource_id)
624
+ ) STRICT;
625
+
626
+ CREATE TABLE source_webhooks (
627
+ id TEXT PRIMARY KEY,
628
+ connection_id TEXT NOT NULL REFERENCES source_connections(id) ON DELETE CASCADE,
629
+ repository_id TEXT REFERENCES source_repositories(id) ON DELETE SET NULL,
630
+ repository_full_name TEXT NOT NULL,
631
+ provider_webhook_id TEXT,
632
+ endpoint_token_hash TEXT NOT NULL UNIQUE,
633
+ secret_ciphertext TEXT NOT NULL,
634
+ events TEXT NOT NULL,
635
+ status TEXT NOT NULL CHECK (status IN ('pending', 'healthy', 'degraded', 'disabled')),
636
+ health_message TEXT,
637
+ last_delivery_at TEXT,
638
+ last_reconciled_at TEXT,
639
+ created_at TEXT NOT NULL,
640
+ updated_at TEXT NOT NULL,
641
+ UNIQUE(connection_id, repository_full_name)
642
+ ) STRICT;
643
+
644
+ CREATE TABLE source_webhook_deliveries (
645
+ id TEXT PRIMARY KEY,
646
+ connection_id TEXT NOT NULL REFERENCES source_connections(id) ON DELETE CASCADE,
647
+ webhook_id TEXT NOT NULL REFERENCES source_webhooks(id) ON DELETE CASCADE,
648
+ provider_delivery_id TEXT NOT NULL,
649
+ event TEXT NOT NULL,
650
+ action TEXT,
651
+ commit_sha TEXT,
652
+ signature_status TEXT NOT NULL CHECK (signature_status IN ('verified', 'invalid', 'missing')),
653
+ status TEXT NOT NULL CHECK (status IN ('accepted', 'ignored', 'rejected', 'duplicate', 'enqueued', 'failed')),
654
+ payload_summary TEXT NOT NULL DEFAULT '{}',
655
+ operation_id TEXT REFERENCES operations(id) ON DELETE SET NULL,
656
+ error TEXT,
657
+ received_at TEXT NOT NULL,
658
+ processed_at TEXT,
659
+ UNIQUE(connection_id, provider_delivery_id)
660
+ ) STRICT;
661
+
662
+ CREATE INDEX source_connections_org_idx ON source_connections(organization_id, status, provider, name);
663
+ CREATE INDEX source_repositories_connection_idx ON source_repositories(connection_id, full_name);
664
+ CREATE INDEX source_bindings_connection_idx ON source_bindings(connection_id, status);
665
+ CREATE INDEX source_bindings_repository_idx ON source_bindings(connection_id, repository_full_name, status);
666
+ CREATE INDEX source_webhooks_connection_idx ON source_webhooks(connection_id, status);
667
+ CREATE INDEX source_deliveries_webhook_idx ON source_webhook_deliveries(webhook_id, received_at DESC);
668
+ `},{version:13,name:"encrypted_source_webhook_endpoint_tokens",sql:`
669
+ ALTER TABLE source_webhooks ADD COLUMN endpoint_token_ciphertext TEXT;
670
+ `},{version:14,name:"application_onboarding_drafts",sql:`
671
+ CREATE TABLE application_drafts (
672
+ id TEXT PRIMARY KEY,
673
+ organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
674
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
675
+ schema_version INTEGER NOT NULL,
676
+ name TEXT NOT NULL,
677
+ step TEXT NOT NULL,
678
+ input TEXT NOT NULL,
679
+ supplied_secret_names TEXT NOT NULL DEFAULT '[]',
680
+ status TEXT NOT NULL DEFAULT 'draft',
681
+ version INTEGER NOT NULL DEFAULT 1,
682
+ created_by_actor_id TEXT REFERENCES actors(id) ON DELETE SET NULL,
683
+ created_at TEXT NOT NULL,
684
+ updated_at TEXT NOT NULL
685
+ );
686
+ CREATE INDEX application_drafts_project_idx ON application_drafts(project_id, updated_at DESC);
687
+ `},{version:15,name:"encrypted_container_registry_connections",sql:`
688
+ CREATE TABLE registry_connections (
689
+ id TEXT PRIMARY KEY,
690
+ organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
691
+ provider TEXT NOT NULL,
692
+ name TEXT NOT NULL,
693
+ host TEXT NOT NULL,
694
+ credential_ciphertext TEXT,
695
+ credential_fingerprint TEXT,
696
+ credential_expires_at TEXT,
697
+ status TEXT NOT NULL DEFAULT 'pending',
698
+ health_message TEXT,
699
+ last_tested_at TEXT,
700
+ version INTEGER NOT NULL DEFAULT 1,
701
+ created_by_actor_id TEXT REFERENCES actors(id) ON DELETE SET NULL,
702
+ created_at TEXT NOT NULL,
703
+ updated_at TEXT NOT NULL,
704
+ UNIQUE(organization_id, name)
705
+ );
706
+ CREATE INDEX registry_connections_org_idx ON registry_connections(organization_id, status, name);
707
+ `},{version:16,name:"inspected_application_artifacts",sql:`
708
+ CREATE TABLE application_artifacts (
709
+ id TEXT PRIMARY KEY,
710
+ organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
711
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
712
+ filename TEXT NOT NULL,
713
+ storage_path TEXT NOT NULL,
714
+ sha256 TEXT NOT NULL,
715
+ size INTEGER NOT NULL,
716
+ format TEXT NOT NULL,
717
+ entry_count INTEGER NOT NULL,
718
+ expanded_bytes INTEGER NOT NULL,
719
+ created_by_actor_id TEXT REFERENCES actors(id) ON DELETE SET NULL,
720
+ created_at TEXT NOT NULL,
721
+ UNIQUE(project_id, sha256)
722
+ );
723
+ CREATE INDEX application_artifacts_project_idx ON application_artifacts(project_id, created_at DESC);
724
+ `},{version:17,name:"durable_operation_queue",sql:`
725
+ CREATE TABLE operation_jobs (
726
+ operation_id TEXT PRIMARY KEY REFERENCES operations(id) ON DELETE CASCADE,
727
+ lock_key TEXT,
728
+ provider_key TEXT,
729
+ build_slot INTEGER NOT NULL DEFAULT 0 CHECK (build_slot IN (0, 1)),
730
+ max_attempts INTEGER NOT NULL DEFAULT 1 CHECK (max_attempts > 0),
731
+ available_at TEXT NOT NULL,
732
+ timeout_seconds INTEGER NOT NULL DEFAULT 1800 CHECK (timeout_seconds > 0),
733
+ heartbeat_at TEXT,
734
+ current_step TEXT,
735
+ blocked_reason TEXT,
736
+ retry_classes TEXT NOT NULL DEFAULT '[]',
737
+ resume_policy TEXT NOT NULL DEFAULT 'fail' CHECK (resume_policy IN ('fail', 'requeue')),
738
+ cancellation_mode TEXT NOT NULL DEFAULT 'cooperative' CHECK (cancellation_mode IN ('cooperative', 'provider_non_cancellable')),
739
+ retention_until TEXT NOT NULL,
740
+ created_at TEXT NOT NULL,
741
+ updated_at TEXT NOT NULL
742
+ ) STRICT;
743
+
744
+ CREATE TABLE operation_logs (
745
+ sequence INTEGER PRIMARY KEY AUTOINCREMENT,
746
+ id TEXT NOT NULL UNIQUE,
747
+ operation_id TEXT NOT NULL REFERENCES operations(id) ON DELETE CASCADE,
748
+ stream TEXT NOT NULL CHECK (stream IN ('stdout', 'stderr', 'system', 'step')),
749
+ step TEXT,
750
+ message TEXT NOT NULL,
751
+ redacted INTEGER NOT NULL DEFAULT 0 CHECK (redacted IN (0, 1)),
752
+ truncated INTEGER NOT NULL DEFAULT 0 CHECK (truncated IN (0, 1)),
753
+ created_at TEXT NOT NULL
754
+ ) STRICT;
755
+
756
+ CREATE TABLE operation_locks (
757
+ lock_key TEXT PRIMARY KEY,
758
+ operation_id TEXT NOT NULL UNIQUE REFERENCES operations(id) ON DELETE CASCADE,
759
+ lease_owner TEXT NOT NULL,
760
+ lease_expires_at TEXT NOT NULL,
761
+ created_at TEXT NOT NULL,
762
+ updated_at TEXT NOT NULL
763
+ ) STRICT;
764
+
765
+ CREATE INDEX operation_jobs_available_idx ON operation_jobs(available_at, operation_id);
766
+ CREATE INDEX operation_jobs_provider_idx ON operation_jobs(provider_key, build_slot, operation_id);
767
+ CREATE INDEX operation_jobs_retention_idx ON operation_jobs(retention_until);
768
+ CREATE INDEX operation_logs_operation_idx ON operation_logs(operation_id, sequence);
769
+ CREATE INDEX operation_locks_expiry_idx ON operation_locks(lease_expires_at);
770
+ `},{version:18,name:"preview_environments",sql:`
771
+ CREATE TABLE preview_definitions (
772
+ id TEXT PRIMARY KEY,
773
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
774
+ resource_id TEXT NOT NULL REFERENCES resources(id) ON DELETE CASCADE,
775
+ base_environment_id TEXT NOT NULL REFERENCES environments(id) ON DELETE CASCADE,
776
+ enabled INTEGER NOT NULL DEFAULT 1 CHECK (enabled IN (0, 1)),
777
+ branch_rule TEXT,
778
+ domain_pattern TEXT NOT NULL,
779
+ ttl_hours INTEGER NOT NULL DEFAULT 24 CHECK (ttl_hours BETWEEN 1 AND 720),
780
+ keep_count INTEGER NOT NULL DEFAULT 10 CHECK (keep_count BETWEEN 1 AND 100),
781
+ public_access INTEGER NOT NULL DEFAULT 0 CHECK (public_access IN (0, 1)),
782
+ authentication_required INTEGER NOT NULL DEFAULT 1 CHECK (authentication_required IN (0, 1)),
783
+ allow_forks INTEGER NOT NULL DEFAULT 0 CHECK (allow_forks IN (0, 1)),
784
+ inherited_secrets TEXT NOT NULL DEFAULT '[]',
785
+ resource_overrides TEXT NOT NULL DEFAULT '{}',
786
+ database_strategy TEXT NOT NULL DEFAULT 'disabled' CHECK (database_strategy IN ('disabled', 'isolated', 'snapshot', 'shared_read_only')),
787
+ max_monthly_cost REAL NOT NULL DEFAULT 25 CHECK (max_monthly_cost >= 0),
788
+ max_cpu REAL NOT NULL DEFAULT 1 CHECK (max_cpu > 0),
789
+ max_memory_mb INTEGER NOT NULL DEFAULT 1024 CHECK (max_memory_mb > 0),
790
+ cleanup_on_close INTEGER NOT NULL DEFAULT 1 CHECK (cleanup_on_close IN (0, 1)),
791
+ version INTEGER NOT NULL DEFAULT 1 CHECK (version > 0),
792
+ created_by_actor_id TEXT REFERENCES actors(id) ON DELETE SET NULL,
793
+ created_at TEXT NOT NULL,
794
+ updated_at TEXT NOT NULL,
795
+ UNIQUE(project_id, resource_id)
796
+ ) STRICT;
797
+
798
+ CREATE TABLE preview_instances (
799
+ id TEXT PRIMARY KEY,
800
+ definition_id TEXT NOT NULL REFERENCES preview_definitions(id) ON DELETE CASCADE,
801
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
802
+ resource_id TEXT NOT NULL REFERENCES resources(id) ON DELETE CASCADE,
803
+ base_environment_id TEXT NOT NULL REFERENCES environments(id) ON DELETE CASCADE,
804
+ identity_key TEXT NOT NULL,
805
+ source_provider TEXT,
806
+ repository TEXT,
807
+ branch TEXT NOT NULL,
808
+ pull_request_number INTEGER,
809
+ fork INTEGER NOT NULL DEFAULT 0 CHECK (fork IN (0, 1)),
810
+ commit_sha TEXT NOT NULL,
811
+ name TEXT NOT NULL,
812
+ stack_name TEXT NOT NULL,
813
+ url TEXT,
814
+ status TEXT NOT NULL CHECK (status IN ('queued', 'deploying', 'active', 'updating', 'destroying', 'destroyed', 'failed', 'cleanup_failed')),
815
+ expires_at TEXT NOT NULL,
816
+ latest_operation_id TEXT REFERENCES operations(id) ON DELETE SET NULL,
817
+ created_by_actor_id TEXT REFERENCES actors(id) ON DELETE SET NULL,
818
+ cost_estimate REAL,
819
+ desired_state TEXT NOT NULL DEFAULT '{}',
820
+ observed_state TEXT NOT NULL DEFAULT '{}',
821
+ teardown_error TEXT,
822
+ version INTEGER NOT NULL DEFAULT 1 CHECK (version > 0),
823
+ created_at TEXT NOT NULL,
824
+ updated_at TEXT NOT NULL,
825
+ destroyed_at TEXT,
826
+ UNIQUE(definition_id, identity_key),
827
+ UNIQUE(project_id, name),
828
+ UNIQUE(project_id, stack_name)
829
+ ) STRICT;
830
+
831
+ CREATE TABLE preview_resources (
832
+ id TEXT PRIMARY KEY,
833
+ preview_id TEXT NOT NULL REFERENCES preview_instances(id) ON DELETE CASCADE,
834
+ provider TEXT NOT NULL,
835
+ provider_resource_id TEXT NOT NULL,
836
+ kind TEXT NOT NULL,
837
+ tags TEXT NOT NULL,
838
+ observed_state TEXT NOT NULL DEFAULT '{}',
839
+ discovered_at TEXT NOT NULL,
840
+ deleted_at TEXT,
841
+ UNIQUE(preview_id, provider, provider_resource_id)
842
+ ) STRICT;
843
+
844
+ CREATE INDEX preview_definitions_project_idx ON preview_definitions(project_id, enabled, resource_id);
845
+ CREATE INDEX preview_instances_project_idx ON preview_instances(project_id, status, expires_at);
846
+ CREATE INDEX preview_instances_pr_idx ON preview_instances(repository, pull_request_number, status);
847
+ CREATE INDEX preview_instances_expiry_idx ON preview_instances(expires_at, status);
848
+ CREATE INDEX preview_resources_preview_idx ON preview_resources(preview_id, deleted_at);
849
+ CREATE INDEX preview_resources_provider_idx ON preview_resources(provider, provider_resource_id);
850
+ `},{version:19,name:"compose_applications",sql:`
851
+ CREATE TABLE compose_applications (
852
+ id TEXT PRIMARY KEY,
853
+ resource_id TEXT NOT NULL UNIQUE REFERENCES resources(id) ON DELETE CASCADE,
854
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
855
+ environment_id TEXT NOT NULL REFERENCES environments(id) ON DELETE CASCADE,
856
+ name TEXT NOT NULL,
857
+ slug TEXT NOT NULL,
858
+ status TEXT NOT NULL CHECK (status IN ('draft', 'ready', 'deploying', 'running', 'stopped', 'degraded', 'failed', 'deleting', 'deleted')),
859
+ source_kind TEXT NOT NULL CHECK (source_kind IN ('compose', 'template')),
860
+ source_hash TEXT NOT NULL,
861
+ redacted_source TEXT NOT NULL,
862
+ manifest TEXT NOT NULL,
863
+ diagnostics TEXT NOT NULL DEFAULT '[]',
864
+ template_id TEXT,
865
+ template_version TEXT,
866
+ latest_operation_id TEXT REFERENCES operations(id) ON DELETE SET NULL,
867
+ created_by_actor_id TEXT REFERENCES actors(id) ON DELETE SET NULL,
868
+ version INTEGER NOT NULL DEFAULT 1 CHECK (version > 0),
869
+ created_at TEXT NOT NULL,
870
+ updated_at TEXT NOT NULL,
871
+ deleted_at TEXT,
872
+ UNIQUE(project_id, environment_id, slug)
873
+ ) STRICT;
874
+
875
+ CREATE TABLE compose_service_states (
876
+ application_id TEXT NOT NULL REFERENCES compose_applications(id) ON DELETE CASCADE,
877
+ service_name TEXT NOT NULL,
878
+ status TEXT NOT NULL CHECK (status IN ('pending', 'starting', 'running', 'stopped', 'unhealthy', 'failed', 'unknown')),
879
+ replicas INTEGER NOT NULL DEFAULT 0 CHECK (replicas >= 0),
880
+ healthy_replicas INTEGER NOT NULL DEFAULT 0 CHECK (healthy_replicas >= 0),
881
+ latest_operation_id TEXT REFERENCES operations(id) ON DELETE SET NULL,
882
+ observed_state TEXT NOT NULL DEFAULT '{}',
883
+ updated_at TEXT NOT NULL,
884
+ PRIMARY KEY(application_id, service_name)
885
+ ) STRICT;
886
+
887
+ CREATE INDEX compose_applications_scope_idx ON compose_applications(project_id, environment_id, status, updated_at);
888
+ CREATE INDEX compose_service_states_status_idx ON compose_service_states(application_id, status, service_name);
889
+ `},{version:20,name:"unified_releases",sql:`
890
+ CREATE TABLE release_artifacts (id TEXT PRIMARY KEY, organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, digest TEXT NOT NULL, kind TEXT NOT NULL CHECK (kind IN ('static','compute','serverless_zip','serverless_image','container','compose')), uri TEXT NOT NULL, size INTEGER NOT NULL CHECK (size >= 0), media_type TEXT NOT NULL, provenance TEXT NOT NULL DEFAULT '{}', attestation TEXT NOT NULL DEFAULT '{}', verified_at TEXT NOT NULL, created_at TEXT NOT NULL, UNIQUE(organization_id, digest));
891
+ CREATE TABLE releases (id TEXT PRIMARY KEY, organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, environment_id TEXT NOT NULL REFERENCES environments(id) ON DELETE CASCADE, resource_id TEXT NOT NULL REFERENCES resources(id) ON DELETE CASCADE, artifact_id TEXT NOT NULL REFERENCES release_artifacts(id) ON DELETE RESTRICT, kind TEXT NOT NULL CHECK (kind IN ('static','compute','serverless_zip','serverless_image','container','compose')), source_sha TEXT, config_hash TEXT NOT NULL, manifest TEXT NOT NULL, provenance TEXT NOT NULL DEFAULT '{}', strategy TEXT NOT NULL CHECK (strategy IN ('atomic','rolling','blue_green','canary')), status TEXT NOT NULL CHECK (status IN ('built','awaiting_approval','activating','active','failed','rolled_back','superseded')), health_gate TEXT, hooks TEXT NOT NULL DEFAULT '{}', drain_seconds INTEGER NOT NULL DEFAULT 30, grace_seconds INTEGER NOT NULL DEFAULT 30, automatic_rollback INTEGER NOT NULL DEFAULT 1 CHECK (automatic_rollback IN (0,1)), rollback_attempts INTEGER NOT NULL DEFAULT 0, previous_release_id TEXT REFERENCES releases(id) ON DELETE SET NULL, promoted_from_release_id TEXT REFERENCES releases(id) ON DELETE SET NULL, actor_id TEXT REFERENCES actors(id) ON DELETE SET NULL, trigger TEXT NOT NULL, pinned INTEGER NOT NULL DEFAULT 0 CHECK (pinned IN (0,1)), pin_reason TEXT, activated_at TEXT, failed_at TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL);
892
+ CREATE TABLE release_transitions (sequence INTEGER PRIMARY KEY AUTOINCREMENT, id TEXT NOT NULL UNIQUE, release_id TEXT NOT NULL REFERENCES releases(id) ON DELETE CASCADE, from_status TEXT, to_status TEXT NOT NULL, traffic_percent REAL, health TEXT NOT NULL DEFAULT '{}', message TEXT NOT NULL, operation_id TEXT REFERENCES operations(id) ON DELETE SET NULL, created_at TEXT NOT NULL);
893
+ CREATE TABLE release_approvals (id TEXT PRIMARY KEY, release_id TEXT NOT NULL REFERENCES releases(id) ON DELETE CASCADE, environment_id TEXT NOT NULL REFERENCES environments(id) ON DELETE CASCADE, decision TEXT NOT NULL CHECK (decision IN ('approved','rejected')), actor_id TEXT NOT NULL REFERENCES actors(id) ON DELETE RESTRICT, comment TEXT, created_at TEXT NOT NULL);
894
+ CREATE INDEX releases_scope_idx ON releases(project_id, environment_id, resource_id, created_at DESC);
895
+ CREATE INDEX releases_active_idx ON releases(resource_id, status, activated_at DESC);
896
+ CREATE INDEX releases_artifact_idx ON releases(artifact_id, status);
897
+ CREATE INDEX release_transitions_release_idx ON release_transitions(release_id, sequence);
898
+ `},{version:21,name:"normalized_telemetry",sql:`
899
+ CREATE TABLE telemetry_records (
900
+ id TEXT PRIMARY KEY,
901
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
902
+ environment_id TEXT REFERENCES environments(id) ON DELETE CASCADE,
903
+ resource_id TEXT REFERENCES resources(id) ON DELETE CASCADE,
904
+ kind TEXT NOT NULL CHECK (kind IN ('metric','log','trace','request','event')),
905
+ source TEXT NOT NULL,
906
+ name TEXT NOT NULL,
907
+ timestamp TEXT NOT NULL,
908
+ observed_at TEXT NOT NULL,
909
+ value REAL,
910
+ unit TEXT,
911
+ level TEXT,
912
+ message TEXT,
913
+ duration_ms REAL,
914
+ status_code INTEGER,
915
+ method TEXT,
916
+ host TEXT,
917
+ path_template TEXT,
918
+ bytes_in INTEGER,
919
+ bytes_out INTEGER,
920
+ region TEXT,
921
+ cache_result TEXT,
922
+ upstream TEXT,
923
+ trace_id TEXT,
924
+ request_id TEXT,
925
+ deployment_id TEXT,
926
+ release_id TEXT,
927
+ workload_id TEXT,
928
+ sampled INTEGER NOT NULL DEFAULT 1 CHECK (sampled IN (0,1)),
929
+ attributes TEXT NOT NULL DEFAULT '{}',
930
+ ingested_bytes INTEGER NOT NULL DEFAULT 0 CHECK (ingested_bytes >= 0)
931
+ ) STRICT;
932
+ CREATE INDEX telemetry_scope_time_idx ON telemetry_records(project_id, environment_id, timestamp DESC, id DESC);
933
+ CREATE INDEX telemetry_resource_time_idx ON telemetry_records(resource_id, timestamp DESC, id DESC);
934
+ CREATE INDEX telemetry_kind_name_time_idx ON telemetry_records(kind, name, timestamp DESC);
935
+ CREATE INDEX telemetry_trace_idx ON telemetry_records(trace_id, timestamp ASC);
936
+ CREATE INDEX telemetry_request_idx ON telemetry_records(request_id, timestamp ASC);
937
+ CREATE INDEX telemetry_release_idx ON telemetry_records(release_id, timestamp DESC);
938
+
939
+ CREATE TABLE telemetry_saved_queries (
940
+ id TEXT PRIMARY KEY,
941
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
942
+ actor_id TEXT REFERENCES actors(id) ON DELETE CASCADE,
943
+ name TEXT NOT NULL,
944
+ query TEXT NOT NULL,
945
+ created_at TEXT NOT NULL,
946
+ updated_at TEXT NOT NULL,
947
+ UNIQUE(project_id, actor_id, name)
948
+ ) STRICT;
949
+ CREATE INDEX telemetry_saved_queries_actor_idx ON telemetry_saved_queries(project_id, actor_id, updated_at DESC);
950
+ `},{version:22,name:"health_alerting_notifications",sql:`
951
+ CREATE TABLE health_checks (
952
+ id TEXT PRIMARY KEY, project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, environment_id TEXT REFERENCES environments(id) ON DELETE CASCADE, resource_id TEXT REFERENCES resources(id) ON DELETE CASCADE,
953
+ name TEXT NOT NULL, kind TEXT NOT NULL CHECK (kind IN ('http','tcp','command')), target TEXT NOT NULL, config TEXT NOT NULL DEFAULT '{}', interval_seconds INTEGER NOT NULL CHECK (interval_seconds BETWEEN 10 AND 86400), timeout_seconds INTEGER NOT NULL CHECK (timeout_seconds BETWEEN 1 AND 300),
954
+ failure_threshold INTEGER NOT NULL CHECK (failure_threshold BETWEEN 1 AND 100), recovery_threshold INTEGER NOT NULL CHECK (recovery_threshold BETWEEN 1 AND 100), regions TEXT NOT NULL DEFAULT '[]', enabled INTEGER NOT NULL DEFAULT 1 CHECK (enabled IN (0,1)), version INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL, updated_at TEXT NOT NULL
955
+ ) STRICT;
956
+ CREATE INDEX health_checks_scope_idx ON health_checks(project_id, environment_id, resource_id, enabled, updated_at DESC);
957
+ CREATE TABLE health_results (
958
+ id TEXT PRIMARY KEY, check_id TEXT NOT NULL REFERENCES health_checks(id) ON DELETE CASCADE, status TEXT NOT NULL CHECK (status IN ('healthy','unhealthy','no_data')), agent TEXT NOT NULL, region TEXT, status_code INTEGER, message TEXT, timings TEXT NOT NULL DEFAULT '{}', checked_at TEXT NOT NULL
959
+ ) STRICT;
960
+ CREATE INDEX health_results_check_idx ON health_results(check_id, checked_at DESC);
961
+
962
+ CREATE TABLE alert_rules (
963
+ id TEXT PRIMARY KEY, project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, environment_id TEXT REFERENCES environments(id) ON DELETE CASCADE, resource_id TEXT REFERENCES resources(id) ON DELETE CASCADE, health_check_id TEXT REFERENCES health_checks(id) ON DELETE CASCADE,
964
+ name TEXT NOT NULL, signal TEXT NOT NULL, operator TEXT NOT NULL CHECK (operator IN ('gt','gte','lt','lte','eq','unhealthy')), threshold REAL, recovery_threshold REAL, window_ms INTEGER NOT NULL, consecutive INTEGER NOT NULL CHECK (consecutive BETWEEN 1 AND 100), recovery_consecutive INTEGER NOT NULL CHECK (recovery_consecutive BETWEEN 1 AND 100), no_data_policy TEXT NOT NULL CHECK (no_data_policy IN ('ignore','pending','firing')),
965
+ severity TEXT NOT NULL CHECK (severity IN ('info','warning','critical')), group_by TEXT NOT NULL DEFAULT '[]', labels TEXT NOT NULL DEFAULT '{}', enabled INTEGER NOT NULL DEFAULT 1 CHECK (enabled IN (0,1)), version INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL, updated_at TEXT NOT NULL
966
+ ) STRICT;
967
+ CREATE INDEX alert_rules_scope_idx ON alert_rules(project_id, environment_id, resource_id, enabled, updated_at DESC);
968
+ CREATE TABLE alerts (
969
+ id TEXT PRIMARY KEY, rule_id TEXT NOT NULL REFERENCES alert_rules(id) ON DELETE CASCADE, project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, environment_id TEXT REFERENCES environments(id) ON DELETE CASCADE, resource_id TEXT REFERENCES resources(id) ON DELETE CASCADE,
970
+ dedup_key TEXT NOT NULL UNIQUE, group_key TEXT NOT NULL, state TEXT NOT NULL CHECK (state IN ('pending','firing','resolved','silenced')), severity TEXT NOT NULL, title TEXT NOT NULL, evidence TEXT NOT NULL DEFAULT '{}', failure_count INTEGER NOT NULL DEFAULT 0, recovery_count INTEGER NOT NULL DEFAULT 0, occurrence_count INTEGER NOT NULL DEFAULT 1,
971
+ owner_actor_id TEXT REFERENCES actors(id) ON DELETE SET NULL, acknowledged_by_actor_id TEXT REFERENCES actors(id) ON DELETE SET NULL, acknowledged_at TEXT, first_seen_at TEXT NOT NULL, last_seen_at TEXT NOT NULL, firing_at TEXT, resolved_at TEXT, silenced_until TEXT, updated_at TEXT NOT NULL
972
+ ) STRICT;
973
+ CREATE INDEX alerts_scope_state_idx ON alerts(project_id, environment_id, resource_id, state, severity, updated_at DESC);
974
+ CREATE TABLE alert_events (
975
+ sequence INTEGER PRIMARY KEY AUTOINCREMENT, id TEXT NOT NULL UNIQUE, alert_id TEXT NOT NULL REFERENCES alerts(id) ON DELETE CASCADE, type TEXT NOT NULL, actor_id TEXT REFERENCES actors(id) ON DELETE SET NULL, payload TEXT NOT NULL DEFAULT '{}', created_at TEXT NOT NULL
976
+ ) STRICT;
977
+ CREATE INDEX alert_events_alert_idx ON alert_events(alert_id, sequence);
978
+ CREATE TABLE alert_silences (
979
+ id TEXT PRIMARY KEY, project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, environment_id TEXT REFERENCES environments(id) ON DELETE CASCADE, resource_id TEXT REFERENCES resources(id) ON DELETE CASCADE, matcher TEXT NOT NULL DEFAULT '{}', reason TEXT NOT NULL, starts_at TEXT NOT NULL, ends_at TEXT NOT NULL, timezone TEXT NOT NULL, created_by_actor_id TEXT REFERENCES actors(id) ON DELETE SET NULL, created_at TEXT NOT NULL
980
+ ) STRICT;
981
+ CREATE INDEX alert_silences_scope_idx ON alert_silences(project_id, starts_at, ends_at);
982
+
983
+ CREATE TABLE notification_channels (
984
+ id TEXT PRIMARY KEY, organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, name TEXT NOT NULL, kind TEXT NOT NULL CHECK (kind IN ('slack','discord','teams','telegram','email','webhook')), config TEXT NOT NULL DEFAULT '{}', credential_ciphertext TEXT, credential_fingerprint TEXT, status TEXT NOT NULL CHECK (status IN ('active','paused','failing','disabled')), version INTEGER NOT NULL DEFAULT 1, last_tested_at TEXT, last_error TEXT, created_by_actor_id TEXT REFERENCES actors(id) ON DELETE SET NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, UNIQUE(organization_id, name)
985
+ ) STRICT;
986
+ CREATE TABLE notification_routes (
987
+ id TEXT PRIMARY KEY, organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, name TEXT NOT NULL, priority INTEGER NOT NULL DEFAULT 0, matcher TEXT NOT NULL DEFAULT '{}', channel_ids TEXT NOT NULL DEFAULT '[]', quiet_hours TEXT, group_wait_seconds INTEGER NOT NULL DEFAULT 30, reminder_seconds INTEGER, escalation TEXT NOT NULL DEFAULT '[]', enabled INTEGER NOT NULL DEFAULT 1 CHECK (enabled IN (0,1)), version INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL, updated_at TEXT NOT NULL
988
+ ) STRICT;
989
+ CREATE INDEX notification_routes_org_idx ON notification_routes(organization_id, enabled, priority DESC);
990
+ CREATE TABLE notification_deliveries (
991
+ id TEXT PRIMARY KEY, alert_id TEXT REFERENCES alerts(id) ON DELETE CASCADE, channel_id TEXT NOT NULL REFERENCES notification_channels(id) ON DELETE CASCADE, route_id TEXT REFERENCES notification_routes(id) ON DELETE SET NULL, event_type TEXT NOT NULL, idempotency_key TEXT NOT NULL UNIQUE, state TEXT NOT NULL CHECK (state IN ('pending','delivered','retrying','failed','dead')), attempt INTEGER NOT NULL DEFAULT 0, max_attempts INTEGER NOT NULL DEFAULT 3, next_attempt_at TEXT, response_status INTEGER, error TEXT, payload TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, delivered_at TEXT
992
+ ) STRICT;
993
+ CREATE INDEX notification_deliveries_state_idx ON notification_deliveries(state, next_attempt_at, updated_at);
994
+ `},{version:23,name:"notification_templates_and_rate_limits",sql:`
995
+ ALTER TABLE notification_routes ADD COLUMN template TEXT;
996
+ ALTER TABLE notification_routes ADD COLUMN rate_limit_per_minute INTEGER NOT NULL DEFAULT 60 CHECK (rate_limit_per_minute BETWEEN 1 AND 10000);
997
+ CREATE INDEX notification_deliveries_route_created_idx ON notification_deliveries(route_id, created_at DESC);
998
+ `},{version:24,name:"scheduled_jobs_and_workers",sql:`
999
+ CREATE TABLE scheduled_jobs (
1000
+ id TEXT PRIMARY KEY, organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, environment_id TEXT REFERENCES environments(id) ON DELETE CASCADE, resource_id TEXT REFERENCES resources(id) ON DELETE CASCADE,
1001
+ name TEXT NOT NULL, provider TEXT NOT NULL CHECK (provider IN ('server','eventbridge','lambda','platform')), expression TEXT NOT NULL, normalized_expression TEXT NOT NULL, timezone TEXT NOT NULL, starts_at TEXT, ends_at TEXT, flexible_minutes INTEGER NOT NULL DEFAULT 0,
1002
+ target TEXT NOT NULL, payload_refs TEXT NOT NULL DEFAULT '{}', missed_run_policy TEXT NOT NULL CHECK (missed_run_policy IN ('skip','catch_up')), overlap_policy TEXT NOT NULL CHECK (overlap_policy IN ('allow','forbid','replace')), retry_policy TEXT NOT NULL, timeout_seconds INTEGER NOT NULL,
1003
+ enabled INTEGER NOT NULL DEFAULT 1 CHECK (enabled IN (0,1)), origin TEXT NOT NULL CHECK (origin IN ('managed','config','external')), source_key TEXT, owner_actor_id TEXT REFERENCES actors(id) ON DELETE SET NULL, observed_state TEXT NOT NULL DEFAULT '{}', reconciliation_status TEXT NOT NULL CHECK (reconciliation_status IN ('pending','in_sync','drifted','unsupported','unavailable')),
1004
+ next_run_at TEXT, last_scheduled_for TEXT, version INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, UNIQUE(project_id, environment_id, source_key)
1005
+ ) STRICT;
1006
+ CREATE INDEX scheduled_jobs_due_idx ON scheduled_jobs(enabled, next_run_at, project_id, environment_id);
1007
+ CREATE TABLE job_executions (
1008
+ id TEXT PRIMARY KEY, job_id TEXT NOT NULL REFERENCES scheduled_jobs(id) ON DELETE CASCADE, operation_id TEXT REFERENCES operations(id) ON DELETE SET NULL, trigger TEXT NOT NULL CHECK (trigger IN ('scheduled','manual','catch_up','external','retry')), scheduled_for TEXT NOT NULL, idempotency_key TEXT NOT NULL UNIQUE,
1009
+ status TEXT NOT NULL CHECK (status IN ('queued','running','succeeded','failed','skipped','dead')), attempt INTEGER NOT NULL DEFAULT 0, started_at TEXT, finished_at TEXT, output TEXT NOT NULL DEFAULT '{}', error TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL
1010
+ ) STRICT;
1011
+ CREATE INDEX job_executions_job_idx ON job_executions(job_id, scheduled_for DESC, created_at DESC);
1012
+ CREATE TABLE worker_definitions (
1013
+ id TEXT PRIMARY KEY, organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, environment_id TEXT REFERENCES environments(id) ON DELETE CASCADE, resource_id TEXT REFERENCES resources(id) ON DELETE CASCADE,
1014
+ name TEXT NOT NULL, provider TEXT NOT NULL CHECK (provider IN ('systemd','ecs','lambda')), queue TEXT NOT NULL, processes INTEGER NOT NULL, timeout_seconds INTEGER NOT NULL, restart_policy TEXT NOT NULL CHECK (restart_policy IN ('always','on_failure','never')), target TEXT NOT NULL, enabled INTEGER NOT NULL DEFAULT 1 CHECK (enabled IN (0,1)), origin TEXT NOT NULL CHECK (origin IN ('managed','config','external')), source_key TEXT,
1015
+ owner_actor_id TEXT REFERENCES actors(id) ON DELETE SET NULL, observed_state TEXT NOT NULL DEFAULT '{}', reconciliation_status TEXT NOT NULL CHECK (reconciliation_status IN ('pending','in_sync','drifted','unsupported','unavailable')), version INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, UNIQUE(project_id, environment_id, source_key)
1016
+ ) STRICT;
1017
+ CREATE INDEX worker_definitions_scope_idx ON worker_definitions(project_id, environment_id, resource_id, enabled);
1018
+ `},{version:25,name:"data_service_lifecycle",sql:`
1019
+ CREATE TABLE data_services (
1020
+ id TEXT PRIMARY KEY, organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, environment_id TEXT REFERENCES environments(id) ON DELETE CASCADE, resource_id TEXT REFERENCES resources(id) ON DELETE SET NULL,
1021
+ name TEXT NOT NULL, engine TEXT NOT NULL CHECK (engine IN ('postgres','mysql','mariadb','redis','mongodb','libsql')), provider TEXT NOT NULL CHECK (provider IN ('aws_rds','aws_aurora','aws_elasticache','server','container','external')), placement TEXT NOT NULL, engine_version TEXT, plan TEXT NOT NULL, storage_gb INTEGER, high_availability INTEGER NOT NULL DEFAULT 0 CHECK (high_availability IN (0,1)), public_exposure INTEGER NOT NULL DEFAULT 0 CHECK (public_exposure IN (0,1)), allowed_cidrs TEXT NOT NULL DEFAULT '[]',
1022
+ desired_state TEXT NOT NULL DEFAULT '{}', observed_state TEXT NOT NULL DEFAULT '{}', capabilities TEXT NOT NULL DEFAULT '{}', credential_ref TEXT, status TEXT NOT NULL CHECK (status IN ('draft','planning','provisioning','available','modifying','degraded','failed','deleting','retained','adopted')), origin TEXT NOT NULL CHECK (origin IN ('managed','config','adopted')), management_enabled INTEGER NOT NULL DEFAULT 1 CHECK (management_enabled IN (0,1)), owner_actor_id TEXT REFERENCES actors(id) ON DELETE SET NULL,
1023
+ version INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, UNIQUE(project_id, environment_id, name)
1024
+ ) STRICT;
1025
+ CREATE INDEX data_services_scope_idx ON data_services(project_id, environment_id, status, engine);
1026
+ CREATE TABLE data_service_credentials (
1027
+ id TEXT PRIMARY KEY, service_id TEXT NOT NULL REFERENCES data_services(id) ON DELETE CASCADE, username TEXT NOT NULL, secret_ref TEXT NOT NULL, version INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL, rotated_at TEXT, UNIQUE(service_id, username)
1028
+ ) STRICT;
1029
+ CREATE TABLE data_service_dependencies (
1030
+ service_id TEXT NOT NULL REFERENCES data_services(id) ON DELETE CASCADE, resource_id TEXT NOT NULL REFERENCES resources(id) ON DELETE CASCADE, secret_ref TEXT NOT NULL, requires_redeploy INTEGER NOT NULL DEFAULT 1 CHECK (requires_redeploy IN (0,1)), created_at TEXT NOT NULL, PRIMARY KEY(service_id, resource_id)
1031
+ ) STRICT;
1032
+ `},{version:26,name:"encrypted_data_service_secrets",sql:`
1033
+ CREATE TABLE data_service_secrets (
1034
+ reference TEXT PRIMARY KEY,
1035
+ ciphertext TEXT NOT NULL,
1036
+ fingerprint TEXT NOT NULL,
1037
+ created_at TEXT NOT NULL,
1038
+ updated_at TEXT NOT NULL
1039
+ ) STRICT;
1040
+ `},{version:27,name:"backup_recovery_control_plane",sql:`
1041
+ CREATE TABLE backup_destinations (
1042
+ id TEXT PRIMARY KEY, organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
1043
+ name TEXT NOT NULL, provider TEXT NOT NULL CHECK (provider IN ('aws_s3','s3_compatible','aws_backup')), endpoint TEXT, endpoint_policy TEXT NOT NULL CHECK (endpoint_policy IN ('public_https','allow_private')), bucket TEXT, prefix TEXT NOT NULL DEFAULT '', region TEXT, force_path_style INTEGER NOT NULL DEFAULT 0 CHECK (force_path_style IN (0,1)), credential_ref TEXT,
1044
+ encryption TEXT NOT NULL CHECK (encryption IN ('provider','client_side','both')), encryption_key_ref TEXT, immutability TEXT NOT NULL DEFAULT '{}', status TEXT NOT NULL CHECK (status IN ('untested','healthy','failing','disabled')), last_tested_at TEXT, last_success_at TEXT, last_failure_at TEXT, last_error TEXT,
1045
+ version INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, UNIQUE(project_id,name)
1046
+ ) STRICT;
1047
+ CREATE INDEX backup_destinations_health_idx ON backup_destinations(project_id,status,last_tested_at);
1048
+ CREATE TABLE backup_policies (
1049
+ id TEXT PRIMARY KEY, organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, environment_id TEXT REFERENCES environments(id) ON DELETE CASCADE, resource_id TEXT REFERENCES resources(id) ON DELETE CASCADE, data_service_id TEXT REFERENCES data_services(id) ON DELETE CASCADE, destination_id TEXT NOT NULL REFERENCES backup_destinations(id) ON DELETE RESTRICT,
1050
+ name TEXT NOT NULL, resource_kind TEXT NOT NULL CHECK (resource_kind IN ('managed_database','logical_database','volume','files','control_plane','infrastructure')), schedule TEXT NOT NULL, timezone TEXT NOT NULL, retention TEXT NOT NULL, compression TEXT NOT NULL CHECK (compression IN ('none','gzip','zstd')), encryption TEXT NOT NULL CHECK (encryption IN ('destination','client_side','both')), include_patterns TEXT NOT NULL DEFAULT '[]', exclude_patterns TEXT NOT NULL DEFAULT '[]', expected_rpo_minutes INTEGER NOT NULL CHECK (expected_rpo_minutes BETWEEN 1 AND 525600), expected_rto_minutes INTEGER NOT NULL CHECK (expected_rto_minutes BETWEEN 1 AND 525600), health_check_id TEXT REFERENCES health_checks(id) ON DELETE SET NULL, enabled INTEGER NOT NULL DEFAULT 1 CHECK (enabled IN (0,1)), next_run_at TEXT, last_run_at TEXT, version INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, UNIQUE(project_id,environment_id,name)
1051
+ ) STRICT;
1052
+ CREATE INDEX backup_policies_due_idx ON backup_policies(enabled,next_run_at,project_id,environment_id);
1053
+ CREATE TABLE backup_jobs (
1054
+ id TEXT PRIMARY KEY, project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, policy_id TEXT REFERENCES backup_policies(id) ON DELETE SET NULL, recovery_point_id TEXT, operation_id TEXT REFERENCES operations(id) ON DELETE SET NULL,
1055
+ kind TEXT NOT NULL CHECK (kind IN ('backup','restore','verify','drill','cleanup')), status TEXT NOT NULL CHECK (status IN ('queued','running','succeeded','failed','cancelled','cleanup_required')), idempotency_key TEXT NOT NULL UNIQUE, target TEXT NOT NULL DEFAULT '{}', restore_mode TEXT CHECK (restore_mode IN ('isolated','in_place')), cancellability TEXT NOT NULL CHECK (cancellability IN ('safe','checkpoint_only','provider_uncancellable')), safety_backup_id TEXT, health_result TEXT, progress TEXT NOT NULL DEFAULT '{}', error TEXT, started_at TEXT, finished_at TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL
1056
+ ) STRICT;
1057
+ CREATE INDEX backup_jobs_scope_idx ON backup_jobs(project_id,status,kind,created_at DESC);
1058
+ CREATE TABLE recovery_points (
1059
+ id TEXT PRIMARY KEY, project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, policy_id TEXT REFERENCES backup_policies(id) ON DELETE SET NULL, destination_id TEXT NOT NULL REFERENCES backup_destinations(id) ON DELETE RESTRICT, resource_id TEXT REFERENCES resources(id) ON DELETE SET NULL, data_service_id TEXT REFERENCES data_services(id) ON DELETE SET NULL, backup_job_id TEXT REFERENCES backup_jobs(id) ON DELETE SET NULL,
1060
+ kind TEXT NOT NULL, point_in_time TEXT NOT NULL, uri TEXT NOT NULL, size_bytes INTEGER NOT NULL DEFAULT 0 CHECK (size_bytes >= 0), checksum TEXT NOT NULL, manifest TEXT NOT NULL DEFAULT '{}', tool_version TEXT, engine_version TEXT, expires_at TEXT, locked_until TEXT, held INTEGER NOT NULL DEFAULT 0 CHECK (held IN (0,1)), pinned INTEGER NOT NULL DEFAULT 0 CHECK (pinned IN (0,1)), status TEXT NOT NULL CHECK (status IN ('pending','available','failed','deleting','deleted')), verification_state TEXT NOT NULL CHECK (verification_state IN ('unverified','verifying','verified','corrupt','failed')), verified_at TEXT, duration_ms INTEGER, created_at TEXT NOT NULL, updated_at TEXT NOT NULL
1061
+ ) STRICT;
1062
+ CREATE INDEX recovery_points_inventory_idx ON recovery_points(project_id,policy_id,point_in_time DESC);
1063
+ CREATE INDEX recovery_points_retention_idx ON recovery_points(status,expires_at,held,pinned);
1064
+ CREATE TRIGGER backup_jobs_recovery_point_fk_insert BEFORE INSERT ON backup_jobs WHEN NEW.recovery_point_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM recovery_points WHERE id=NEW.recovery_point_id) BEGIN SELECT RAISE(ABORT,'backup recovery point not found'); END;
1065
+ CREATE TRIGGER backup_jobs_recovery_point_fk_update BEFORE UPDATE OF recovery_point_id ON backup_jobs WHEN NEW.recovery_point_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM recovery_points WHERE id=NEW.recovery_point_id) BEGIN SELECT RAISE(ABORT,'backup recovery point not found'); END;
1066
+ `},{version:28,name:"scoped_configuration",sql:`
1067
+ CREATE TABLE configuration_entries (
1068
+ id TEXT PRIMARY KEY, organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
1069
+ scope_type TEXT NOT NULL CHECK (scope_type IN ('project','environment','service','preview')), scope_id TEXT NOT NULL, environment_id TEXT REFERENCES environments(id) ON DELETE CASCADE, resource_id TEXT REFERENCES resources(id) ON DELETE CASCADE, preview_id TEXT,
1070
+ key TEXT NOT NULL, kind TEXT NOT NULL CHECK (kind IN ('variable','secret')), value TEXT, value_fingerprint TEXT NOT NULL, secret_ref TEXT, backend TEXT NOT NULL CHECK (backend IN ('plaintext','local_encrypted','aws_secrets_manager','aws_ssm','external')), backend_version TEXT,
1071
+ origin TEXT NOT NULL CHECK (origin IN ('managed','config','migrated')), required INTEGER NOT NULL DEFAULT 0 CHECK (required IN (0,1)), metadata TEXT NOT NULL DEFAULT '{}', last_used_at TEXT, rotated_at TEXT,
1072
+ version INTEGER NOT NULL DEFAULT 1 CHECK (version > 0), created_at TEXT NOT NULL, updated_at TEXT NOT NULL,
1073
+ UNIQUE(project_id,scope_type,scope_id,key),
1074
+ CHECK ((kind='variable' AND value IS NOT NULL AND secret_ref IS NULL AND backend='plaintext') OR (kind='secret' AND value IS NULL AND secret_ref IS NOT NULL AND backend<>'plaintext'))
1075
+ ) STRICT;
1076
+ CREATE INDEX configuration_entries_scope_idx ON configuration_entries(project_id,environment_id,resource_id,preview_id,kind,key);
1077
+ CREATE TABLE configuration_dependencies (
1078
+ entry_id TEXT NOT NULL REFERENCES configuration_entries(id) ON DELETE CASCADE, resource_id TEXT NOT NULL REFERENCES resources(id) ON DELETE CASCADE, injection_target TEXT NOT NULL CHECK (injection_target IN ('environment','native_reference','file')), required INTEGER NOT NULL DEFAULT 1 CHECK (required IN (0,1)), requires_redeploy INTEGER NOT NULL DEFAULT 1 CHECK (requires_redeploy IN (0,1)), last_deployed_version INTEGER, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, PRIMARY KEY(entry_id,resource_id)
1079
+ ) STRICT;
1080
+ CREATE INDEX configuration_dependencies_resource_idx ON configuration_dependencies(resource_id,requires_redeploy);
1081
+ CREATE TABLE configuration_mutations (
1082
+ id TEXT PRIMARY KEY, project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, idempotency_key TEXT NOT NULL UNIQUE, request_hash TEXT NOT NULL, result TEXT NOT NULL DEFAULT '{}', actor_id TEXT REFERENCES actors(id) ON DELETE SET NULL, created_at TEXT NOT NULL
1083
+ ) STRICT;
1084
+ `},{version:29,name:"encrypted_configuration_values",sql:`
1085
+ CREATE TABLE configuration_secret_values (
1086
+ reference TEXT PRIMARY KEY,
1087
+ ciphertext TEXT NOT NULL,
1088
+ fingerprint TEXT NOT NULL,
1089
+ version INTEGER NOT NULL DEFAULT 1 CHECK (version > 0),
1090
+ created_at TEXT NOT NULL,
1091
+ updated_at TEXT NOT NULL
1092
+ ) STRICT;
1093
+ `},{version:30,name:"function_configuration_scope",sql:`
1094
+ ALTER TABLE configuration_entries RENAME TO configuration_entries_old;
1095
+ ALTER TABLE configuration_dependencies RENAME TO configuration_dependencies_old;
1096
+ DROP INDEX configuration_entries_scope_idx;
1097
+ DROP INDEX configuration_dependencies_resource_idx;
1098
+ CREATE TABLE configuration_entries (
1099
+ id TEXT PRIMARY KEY, organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
1100
+ scope_type TEXT NOT NULL CHECK (scope_type IN ('project','environment','service','function','preview')), scope_id TEXT NOT NULL, environment_id TEXT REFERENCES environments(id) ON DELETE CASCADE, resource_id TEXT REFERENCES resources(id) ON DELETE CASCADE, preview_id TEXT,
1101
+ key TEXT NOT NULL, kind TEXT NOT NULL CHECK (kind IN ('variable','secret')), value TEXT, value_fingerprint TEXT NOT NULL, secret_ref TEXT, backend TEXT NOT NULL CHECK (backend IN ('plaintext','local_encrypted','aws_secrets_manager','aws_ssm','external')), backend_version TEXT,
1102
+ origin TEXT NOT NULL CHECK (origin IN ('managed','config','migrated')), required INTEGER NOT NULL DEFAULT 0 CHECK (required IN (0,1)), metadata TEXT NOT NULL DEFAULT '{}', last_used_at TEXT, rotated_at TEXT,
1103
+ version INTEGER NOT NULL DEFAULT 1 CHECK (version > 0), created_at TEXT NOT NULL, updated_at TEXT NOT NULL,
1104
+ UNIQUE(project_id,scope_type,scope_id,key),
1105
+ CHECK ((kind='variable' AND value IS NOT NULL AND secret_ref IS NULL AND backend='plaintext') OR (kind='secret' AND value IS NULL AND secret_ref IS NOT NULL AND backend<>'plaintext'))
1106
+ ) STRICT;
1107
+ INSERT INTO configuration_entries SELECT * FROM configuration_entries_old;
1108
+ CREATE INDEX configuration_entries_scope_idx ON configuration_entries(project_id,environment_id,resource_id,preview_id,kind,key);
1109
+ CREATE TABLE configuration_dependencies (
1110
+ entry_id TEXT NOT NULL REFERENCES configuration_entries(id) ON DELETE CASCADE, resource_id TEXT NOT NULL REFERENCES resources(id) ON DELETE CASCADE, injection_target TEXT NOT NULL CHECK (injection_target IN ('environment','native_reference','file')), required INTEGER NOT NULL DEFAULT 1 CHECK (required IN (0,1)), requires_redeploy INTEGER NOT NULL DEFAULT 1 CHECK (requires_redeploy IN (0,1)), last_deployed_version INTEGER, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, PRIMARY KEY(entry_id,resource_id)
1111
+ ) STRICT;
1112
+ INSERT INTO configuration_dependencies SELECT * FROM configuration_dependencies_old;
1113
+ CREATE INDEX configuration_dependencies_resource_idx ON configuration_dependencies(resource_id,requires_redeploy);
1114
+ DROP TABLE configuration_dependencies_old;
1115
+ DROP TABLE configuration_entries_old;
1116
+ `},{version:31,name:"persistent_volume_lifecycle",sql:`
1117
+ CREATE TABLE persistent_volumes (
1118
+ id TEXT PRIMARY KEY, organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, environment_id TEXT REFERENCES environments(id) ON DELETE CASCADE,
1119
+ resource_id TEXT REFERENCES resources(id) ON DELETE SET NULL, name TEXT NOT NULL, provider TEXT NOT NULL, provider_id TEXT, type TEXT NOT NULL CHECK (type IN ('server_path','docker','ebs','efs','provider')),
1120
+ status TEXT NOT NULL CHECK (status IN ('pending','available','attaching','attached','detaching','resizing','snapshotting','orphaned','deleting','deleted','error')), capacity_bytes INTEGER CHECK (capacity_bytes IS NULL OR capacity_bytes >= 0), used_bytes INTEGER CHECK (used_bytes IS NULL OR used_bytes >= 0), filesystem TEXT, encrypted INTEGER NOT NULL DEFAULT 0 CHECK (encrypted IN (0,1)),
1121
+ capabilities TEXT NOT NULL DEFAULT '{}', desired_state TEXT NOT NULL DEFAULT '{}', observed_state TEXT NOT NULL DEFAULT '{}', backup_policy_id TEXT REFERENCES backup_policies(id) ON DELETE SET NULL, last_backup_at TEXT, orphaned_at TEXT, adopted_at TEXT,
1122
+ version INTEGER NOT NULL DEFAULT 1 CHECK (version > 0), created_at TEXT NOT NULL, updated_at TEXT NOT NULL, deleted_at TEXT, UNIQUE(project_id,environment_id,name)
1123
+ ) STRICT;
1124
+ CREATE INDEX persistent_volumes_inventory_idx ON persistent_volumes(project_id,environment_id,status,type,updated_at DESC);
1125
+ CREATE UNIQUE INDEX persistent_volumes_provider_idx ON persistent_volumes(provider,provider_id) WHERE provider_id IS NOT NULL AND deleted_at IS NULL;
1126
+ CREATE TABLE volume_attachments (
1127
+ id TEXT PRIMARY KEY, volume_id TEXT NOT NULL REFERENCES persistent_volumes(id) ON DELETE RESTRICT, resource_id TEXT NOT NULL REFERENCES resources(id) ON DELETE CASCADE,
1128
+ target_path TEXT NOT NULL, read_only INTEGER NOT NULL DEFAULT 0 CHECK (read_only IN (0,1)), uid INTEGER CHECK (uid IS NULL OR uid >= 0), gid INTEGER CHECK (gid IS NULL OR gid >= 0), mode TEXT, propagation TEXT NOT NULL DEFAULT 'private' CHECK (propagation IN ('private','rprivate','shared','rshared','slave','rslave')), driver_options TEXT NOT NULL DEFAULT '{}',
1129
+ desired_state TEXT NOT NULL CHECK (desired_state IN ('attached','detached')), observed_state TEXT NOT NULL CHECK (observed_state IN ('pending','attached','detached','error')), operation_id TEXT REFERENCES operations(id) ON DELETE SET NULL, last_error TEXT,
1130
+ version INTEGER NOT NULL DEFAULT 1 CHECK (version > 0), created_at TEXT NOT NULL, updated_at TEXT NOT NULL, UNIQUE(volume_id,resource_id,target_path)
1131
+ ) STRICT;
1132
+ CREATE INDEX volume_attachments_resource_idx ON volume_attachments(resource_id,desired_state,observed_state);
1133
+ CREATE TABLE volume_snapshots (
1134
+ id TEXT PRIMARY KEY, volume_id TEXT NOT NULL REFERENCES persistent_volumes(id) ON DELETE CASCADE, recovery_point_id TEXT REFERENCES recovery_points(id) ON DELETE SET NULL, provider_id TEXT,
1135
+ name TEXT NOT NULL, status TEXT NOT NULL CHECK (status IN ('pending','available','restoring','deleting','deleted','error')), size_bytes INTEGER CHECK (size_bytes IS NULL OR size_bytes >= 0), encrypted INTEGER NOT NULL DEFAULT 0 CHECK (encrypted IN (0,1)), checksum TEXT, metadata TEXT NOT NULL DEFAULT '{}',
1136
+ created_at TEXT NOT NULL, updated_at TEXT NOT NULL, deleted_at TEXT
1137
+ ) STRICT;
1138
+ CREATE INDEX volume_snapshots_inventory_idx ON volume_snapshots(volume_id,status,created_at DESC);
1139
+ `},{version:32,name:"provider_neutral_fleet",sql:`
1140
+ CREATE TABLE fleet_servers (
1141
+ id TEXT PRIMARY KEY, organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, resource_id TEXT NOT NULL REFERENCES resources(id) ON DELETE CASCADE,
1142
+ name TEXT NOT NULL, provider TEXT NOT NULL CHECK(provider IN ('aws','hetzner','ssh')), provider_id TEXT, region TEXT, zone TEXT, endpoint TEXT NOT NULL, ssh_user TEXT NOT NULL, ssh_port INTEGER NOT NULL CHECK(ssh_port BETWEEN 1 AND 65535), credential_ref TEXT NOT NULL,
1143
+ host_key_algorithm TEXT, host_key_fingerprint TEXT, pending_host_key TEXT, roles TEXT NOT NULL, labels TEXT NOT NULL DEFAULT '{}', taints TEXT NOT NULL DEFAULT '[]', capacity TEXT NOT NULL DEFAULT '{}', usage TEXT NOT NULL DEFAULT '{}', capabilities TEXT NOT NULL DEFAULT '{}',
1144
+ status TEXT NOT NULL CHECK(status IN ('pending','validating','ready','degraded','unreachable','draining','drained','archived')), trust_state TEXT NOT NULL CHECK(trust_state IN ('unverified','pinned','rotation_pending','blocked')), validation TEXT NOT NULL DEFAULT '{}', bootstrap_version TEXT, heartbeat_at TEXT, last_seen_at TEXT, archived_at TEXT,
1145
+ version INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, UNIQUE(project_id,name)
1146
+ ) STRICT;
1147
+ CREATE UNIQUE INDEX fleet_servers_provider_identity ON fleet_servers(organization_id,provider,provider_id) WHERE provider_id IS NOT NULL AND archived_at IS NULL;
1148
+ CREATE INDEX fleet_servers_status ON fleet_servers(project_id,status,heartbeat_at);
1149
+ CREATE TABLE fleet_bootstrap_plans (id TEXT PRIMARY KEY, server_id TEXT NOT NULL REFERENCES fleet_servers(id) ON DELETE CASCADE, plan_version TEXT NOT NULL, facts_hash TEXT NOT NULL, steps TEXT NOT NULL, status TEXT NOT NULL CHECK(status IN ('preview','queued','running','succeeded','failed')), operation_id TEXT REFERENCES operations(id) ON DELETE SET NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL);
1150
+ `},{version:33,name:"capacity_pools_and_placement",sql:`
1151
+ CREATE TABLE capacity_pools (
1152
+ id TEXT PRIMARY KEY, organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
1153
+ name TEXT NOT NULL, purpose TEXT NOT NULL CHECK(purpose IN ('application','build','worker','monitoring','backup')), backend TEXT NOT NULL CHECK(backend IN ('server','ecs','asg')),
1154
+ region TEXT, architecture TEXT, labels TEXT NOT NULL DEFAULT '{}', required_server_labels TEXT NOT NULL DEFAULT '{}', tolerated_taints TEXT NOT NULL DEFAULT '[]',
1155
+ capacity TEXT NOT NULL, reserved TEXT NOT NULL DEFAULT '{}', max_workloads INTEGER NOT NULL CHECK(max_workloads > 0), cost_weight REAL NOT NULL DEFAULT 1 CHECK(cost_weight >= 0), spread_key TEXT,
1156
+ concurrency INTEGER NOT NULL DEFAULT 1 CHECK(concurrency > 0), ephemeral_workspaces INTEGER NOT NULL DEFAULT 1 CHECK(ephemeral_workspaces IN (0,1)), allow_production_secrets INTEGER NOT NULL DEFAULT 0 CHECK(allow_production_secrets IN (0,1)),
1157
+ status TEXT NOT NULL CHECK(status IN ('active','draining','disabled')), version INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, UNIQUE(project_id,name)
1158
+ ) STRICT;
1159
+ CREATE INDEX capacity_pools_scope ON capacity_pools(project_id,purpose,status,region,architecture);
1160
+ CREATE TABLE capacity_pool_members (
1161
+ pool_id TEXT NOT NULL REFERENCES capacity_pools(id) ON DELETE CASCADE, server_id TEXT NOT NULL REFERENCES fleet_servers(id) ON DELETE CASCADE,
1162
+ capacity_override TEXT NOT NULL DEFAULT '{}', status TEXT NOT NULL CHECK(status IN ('active','draining','offline')), created_at TEXT NOT NULL, updated_at TEXT NOT NULL, PRIMARY KEY(pool_id,server_id)
1163
+ ) STRICT;
1164
+ CREATE TABLE workload_placements (
1165
+ id TEXT PRIMARY KEY, project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, environment_id TEXT REFERENCES environments(id) ON DELETE CASCADE, resource_id TEXT NOT NULL REFERENCES resources(id) ON DELETE CASCADE,
1166
+ release_id TEXT REFERENCES releases(id) ON DELETE SET NULL, pool_id TEXT NOT NULL REFERENCES capacity_pools(id) ON DELETE RESTRICT, server_id TEXT REFERENCES fleet_servers(id) ON DELETE SET NULL,
1167
+ purpose TEXT NOT NULL, requirements TEXT NOT NULL, decision TEXT NOT NULL, stateful INTEGER NOT NULL DEFAULT 0 CHECK(stateful IN (0,1)), auto_reschedule INTEGER NOT NULL DEFAULT 0 CHECK(auto_reschedule IN (0,1)),
1168
+ status TEXT NOT NULL CHECK(status IN ('reserved','active','moving','blocked','released','failed')), version INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL, updated_at TEXT NOT NULL
1169
+ ) STRICT;
1170
+ CREATE INDEX workload_placements_target ON workload_placements(project_id,resource_id,status,pool_id);
1171
+ CREATE TABLE capacity_leases (
1172
+ id TEXT PRIMARY KEY, placement_id TEXT NOT NULL REFERENCES workload_placements(id) ON DELETE CASCADE, pool_id TEXT NOT NULL REFERENCES capacity_pools(id) ON DELETE CASCADE, server_id TEXT REFERENCES fleet_servers(id) ON DELETE SET NULL,
1173
+ resources TEXT NOT NULL, state TEXT NOT NULL CHECK(state IN ('active','released','expired')), expires_at TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL
1174
+ ) STRICT;
1175
+ CREATE UNIQUE INDEX capacity_leases_active ON capacity_leases(placement_id) WHERE state='active';
1176
+ CREATE INDEX capacity_leases_pool ON capacity_leases(pool_id,state,expires_at);
1177
+ CREATE TABLE remote_builds (
1178
+ id TEXT PRIMARY KEY, project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, resource_id TEXT REFERENCES resources(id) ON DELETE SET NULL, pool_id TEXT NOT NULL REFERENCES capacity_pools(id) ON DELETE RESTRICT,
1179
+ placement_id TEXT REFERENCES workload_placements(id) ON DELETE SET NULL, operation_id TEXT REFERENCES operations(id) ON DELETE SET NULL, source_sha TEXT NOT NULL, build_spec TEXT NOT NULL, credential_policy TEXT NOT NULL,
1180
+ workspace TEXT, cache_key TEXT, artifact_uri TEXT, artifact_digest TEXT, status TEXT NOT NULL CHECK(status IN ('queued','running','uploading','succeeded','failed','cancelled','cleanup_required')),
1181
+ cleanup_at TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL
1182
+ ) STRICT;
1183
+ CREATE INDEX remote_builds_pool ON remote_builds(pool_id,status,created_at);
1184
+ `},{version:34,name:"multi_region_orchestration",sql:`
1185
+ CREATE TABLE regional_topologies (
1186
+ id TEXT PRIMARY KEY, organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, environment_id TEXT REFERENCES environments(id) ON DELETE CASCADE,
1187
+ name TEXT NOT NULL, hostname TEXT NOT NULL, home_region TEXT NOT NULL, regions TEXT NOT NULL, traffic_policy TEXT NOT NULL CHECK(traffic_policy IN ('active_passive','weighted','latency')),
1188
+ data_policy TEXT NOT NULL, status TEXT NOT NULL CHECK(status IN ('draft','provisioning','ready','degraded','failing_over','failed_over','failing_back','destroying','destroyed','failed')),
1189
+ active_region TEXT NOT NULL, revision TEXT, version INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, UNIQUE(project_id,environment_id,name), UNIQUE(project_id,hostname)
1190
+ ) STRICT;
1191
+ CREATE TABLE regional_targets (
1192
+ id TEXT PRIMARY KEY, topology_id TEXT NOT NULL REFERENCES regional_topologies(id) ON DELETE CASCADE, region TEXT NOT NULL, role TEXT NOT NULL CHECK(role IN ('primary','secondary')), provider TEXT NOT NULL,
1193
+ stack_id TEXT, stack_revision TEXT, status TEXT NOT NULL CHECK(status IN ('pending','provisioning','ready','degraded','failed','deleting','deleted')), health TEXT NOT NULL DEFAULT '{}', last_healthy_at TEXT,
1194
+ version INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, UNIQUE(topology_id,region)
1195
+ ) STRICT;
1196
+ CREATE TABLE regional_replication_channels (
1197
+ id TEXT PRIMARY KEY, topology_id TEXT NOT NULL REFERENCES regional_topologies(id) ON DELETE CASCADE, kind TEXT NOT NULL CHECK(kind IN ('s3','dynamodb','secrets')), source_region TEXT NOT NULL, target_region TEXT NOT NULL,
1198
+ config TEXT NOT NULL, status TEXT NOT NULL CHECK(status IN ('pending','configuring','in_sync','lagging','failed','disabled')), checkpoint TEXT, lag_seconds INTEGER, last_verified_at TEXT, version INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL, updated_at TEXT NOT NULL,
1199
+ UNIQUE(topology_id,kind,source_region,target_region)
1200
+ ) STRICT;
1201
+ CREATE TABLE regional_traffic_routes (
1202
+ id TEXT PRIMARY KEY, topology_id TEXT NOT NULL REFERENCES regional_topologies(id) ON DELETE CASCADE, hostname TEXT NOT NULL, dns_provider TEXT NOT NULL, cdn_enabled INTEGER NOT NULL DEFAULT 0 CHECK(cdn_enabled IN (0,1)), waf_enabled INTEGER NOT NULL DEFAULT 0 CHECK(waf_enabled IN (0,1)),
1203
+ weights TEXT NOT NULL, desired_weights TEXT NOT NULL, status TEXT NOT NULL CHECK(status IN ('pending','applying','in_sync','failed','drained')), provider_state TEXT NOT NULL DEFAULT '{}', version INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, UNIQUE(topology_id,hostname)
1204
+ ) STRICT;
1205
+ CREATE TABLE regional_executions (
1206
+ id TEXT PRIMARY KEY, topology_id TEXT NOT NULL REFERENCES regional_topologies(id) ON DELETE CASCADE, operation_id TEXT REFERENCES operations(id) ON DELETE SET NULL, kind TEXT NOT NULL CHECK(kind IN ('rollout','failover','failback','destroy','reconcile')),
1207
+ requested_region TEXT, revision TEXT, plan TEXT NOT NULL, current_step TEXT, completed_steps TEXT NOT NULL DEFAULT '[]', status TEXT NOT NULL CHECK(status IN ('queued','running','succeeded','failed','cancelled')), error TEXT,
1208
+ created_at TEXT NOT NULL, updated_at TEXT NOT NULL
1209
+ ) STRICT;
1210
+ CREATE INDEX regional_executions_active ON regional_executions(topology_id,status,created_at DESC);
1211
+ `},{version:35,name:"platform_maintenance_and_dr",sql:`
1212
+ CREATE TABLE platform_trusted_keys (id TEXT PRIMARY KEY, organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, name TEXT NOT NULL, algorithm TEXT NOT NULL CHECK(algorithm='ed25519'), public_key_pem TEXT NOT NULL, fingerprint TEXT NOT NULL UNIQUE, revoked_at TEXT, created_at TEXT NOT NULL, UNIQUE(organization_id,name)) STRICT;
1213
+ CREATE TABLE platform_update_manifests (
1214
+ id TEXT PRIMARY KEY, organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, version TEXT NOT NULL, channel TEXT NOT NULL CHECK(channel IN ('stable','preview','nightly')), published_at TEXT NOT NULL, key_id TEXT NOT NULL REFERENCES platform_trusted_keys(id) ON DELETE RESTRICT,
1215
+ digest TEXT NOT NULL UNIQUE, document TEXT NOT NULL, signature TEXT NOT NULL, verification_status TEXT NOT NULL CHECK(verification_status IN ('verified','invalid','revoked')), compatibility TEXT NOT NULL, created_at TEXT NOT NULL, UNIQUE(organization_id,version,channel)
1216
+ ) STRICT;
1217
+ CREATE TABLE maintenance_windows (
1218
+ id TEXT PRIMARY KEY, project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, name TEXT NOT NULL, schedule TEXT NOT NULL, timezone TEXT NOT NULL, duration_minutes INTEGER NOT NULL CHECK(duration_minutes BETWEEN 5 AND 1440), allowed_operations TEXT NOT NULL, require_approval INTEGER NOT NULL DEFAULT 1 CHECK(require_approval IN (0,1)), enabled INTEGER NOT NULL DEFAULT 1 CHECK(enabled IN (0,1)), created_at TEXT NOT NULL, updated_at TEXT NOT NULL, UNIQUE(project_id,name)
1219
+ ) STRICT;
1220
+ CREATE TABLE platform_upgrade_campaigns (
1221
+ id TEXT PRIMARY KEY, project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, manifest_id TEXT NOT NULL REFERENCES platform_update_manifests(id) ON DELETE RESTRICT, window_id TEXT REFERENCES maintenance_windows(id) ON DELETE SET NULL, from_version TEXT NOT NULL, strategy TEXT NOT NULL CHECK(strategy IN ('canary','rolling')), batch_size INTEGER NOT NULL CHECK(batch_size > 0), health_gate TEXT NOT NULL,
1222
+ status TEXT NOT NULL CHECK(status IN ('planned','queued','running','paused','rolling_back','succeeded','failed','rolled_back','cancelled')), current_stage TEXT, backup_id TEXT, approved_by TEXT REFERENCES actors(id) ON DELETE SET NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL
1223
+ ) STRICT;
1224
+ CREATE TABLE platform_upgrade_targets (
1225
+ id TEXT PRIMARY KEY, campaign_id TEXT NOT NULL REFERENCES platform_upgrade_campaigns(id) ON DELETE CASCADE, server_id TEXT REFERENCES fleet_servers(id) ON DELETE SET NULL, target TEXT NOT NULL, cohort INTEGER NOT NULL, previous_version TEXT NOT NULL,
1226
+ status TEXT NOT NULL CHECK(status IN ('pending','downloading','installing','healthy','failed','rolling_back','rolled_back','skipped')), evidence TEXT NOT NULL DEFAULT '{}', error TEXT, started_at TEXT, finished_at TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, UNIQUE(campaign_id,target)
1227
+ ) STRICT;
1228
+ CREATE TABLE cleanup_plans (
1229
+ id TEXT PRIMARY KEY, project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, kind TEXT NOT NULL CHECK(kind IN ('releases','artifacts','operations','logs','previews','snapshots','orphaned_resources')), criteria TEXT NOT NULL, candidates TEXT NOT NULL, candidate_digest TEXT NOT NULL, estimated_bytes INTEGER NOT NULL DEFAULT 0, status TEXT NOT NULL CHECK(status IN ('preview','approved','running','succeeded','partial','failed','expired')), confirmation TEXT, expires_at TEXT NOT NULL, result TEXT NOT NULL DEFAULT '{}', created_at TEXT NOT NULL, updated_at TEXT NOT NULL
1230
+ ) STRICT;
1231
+ CREATE TABLE disaster_recovery_drills (
1232
+ id TEXT PRIMARY KEY, project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, backup_policy_id TEXT REFERENCES backup_policies(id) ON DELETE SET NULL, recovery_point_id TEXT REFERENCES recovery_points(id) ON DELETE SET NULL, topology_id TEXT REFERENCES regional_topologies(id) ON DELETE SET NULL,
1233
+ scenario TEXT NOT NULL CHECK(scenario IN ('control_plane','database','volume','regional')), isolated_target TEXT NOT NULL, expected_rpo_minutes INTEGER NOT NULL, expected_rto_minutes INTEGER NOT NULL, status TEXT NOT NULL CHECK(status IN ('planned','queued','restoring','verifying','cleaning','passed','failed','cleanup_required','cancelled')),
1234
+ operation_id TEXT REFERENCES operations(id) ON DELETE SET NULL, evidence TEXT NOT NULL DEFAULT '{}', measured_rpo_minutes REAL, measured_rto_minutes REAL, cleanup_verified INTEGER NOT NULL DEFAULT 0 CHECK(cleanup_verified IN (0,1)), error TEXT, started_at TEXT, finished_at TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL
1235
+ ) STRICT;
1236
+ CREATE INDEX platform_campaign_status ON platform_upgrade_campaigns(project_id,status,created_at DESC);
1237
+ CREATE INDEX cleanup_plan_status ON cleanup_plans(project_id,status,expires_at);
1238
+ CREATE INDEX dr_drill_status ON disaster_recovery_drills(project_id,status,created_at DESC);
1239
+ `},{version:36,name:"telemetry_source_rollups",sql:`
1240
+ CREATE TABLE telemetry_source_rollups (
1241
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
1242
+ environment_id TEXT NOT NULL DEFAULT '',
1243
+ resource_id TEXT NOT NULL DEFAULT '',
1244
+ source TEXT NOT NULL,
1245
+ first_observed_at TEXT NOT NULL,
1246
+ latest_observed_at TEXT NOT NULL,
1247
+ ingested_bytes INTEGER NOT NULL DEFAULT 0 CHECK (ingested_bytes >= 0),
1248
+ record_count INTEGER NOT NULL DEFAULT 0 CHECK (record_count >= 0),
1249
+ PRIMARY KEY(project_id, environment_id, resource_id, source)
1250
+ ) STRICT;
1251
+
1252
+ INSERT INTO telemetry_source_rollups (
1253
+ project_id, environment_id, resource_id, source,
1254
+ first_observed_at, latest_observed_at, ingested_bytes, record_count
1255
+ )
1256
+ SELECT
1257
+ project_id, COALESCE(environment_id, ''), COALESCE(resource_id, ''), source,
1258
+ MIN(observed_at), MAX(observed_at), SUM(ingested_bytes), COUNT(*)
1259
+ FROM telemetry_records
1260
+ GROUP BY project_id, COALESCE(environment_id, ''), COALESCE(resource_id, ''), source;
1261
+
1262
+ CREATE TRIGGER telemetry_source_rollups_insert
1263
+ AFTER INSERT ON telemetry_records
1264
+ BEGIN
1265
+ INSERT INTO telemetry_source_rollups (
1266
+ project_id, environment_id, resource_id, source,
1267
+ first_observed_at, latest_observed_at, ingested_bytes, record_count
1268
+ )
1269
+ VALUES (
1270
+ NEW.project_id, COALESCE(NEW.environment_id, ''), COALESCE(NEW.resource_id, ''), NEW.source,
1271
+ NEW.observed_at, NEW.observed_at, NEW.ingested_bytes, 1
1272
+ )
1273
+ ON CONFLICT(project_id, environment_id, resource_id, source) DO UPDATE SET
1274
+ first_observed_at = MIN(first_observed_at, excluded.first_observed_at),
1275
+ latest_observed_at = MAX(latest_observed_at, excluded.latest_observed_at),
1276
+ ingested_bytes = ingested_bytes + excluded.ingested_bytes,
1277
+ record_count = record_count + 1;
1278
+ END;
1279
+ `},{version:37,name:"spend_management",sql:`
1280
+ CREATE TABLE spend_budgets (
1281
+ id TEXT PRIMARY KEY,
1282
+ organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
1283
+ project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
1284
+ environment_id TEXT REFERENCES environments(id) ON DELETE CASCADE,
1285
+ name TEXT NOT NULL,
1286
+ period TEXT NOT NULL CHECK (period IN ('daily', 'weekly', 'monthly')),
1287
+ timezone TEXT NOT NULL DEFAULT 'UTC',
1288
+ currency TEXT NOT NULL DEFAULT 'USD',
1289
+ soft_limit_cents INTEGER CHECK (soft_limit_cents IS NULL OR soft_limit_cents >= 0),
1290
+ hard_limit_cents INTEGER CHECK (hard_limit_cents IS NULL OR hard_limit_cents >= 0),
1291
+ thresholds TEXT NOT NULL DEFAULT '[]',
1292
+ meters TEXT NOT NULL DEFAULT '[]',
1293
+ grace_seconds INTEGER NOT NULL DEFAULT 0 CHECK (grace_seconds >= 0),
1294
+ hysteresis_percent REAL NOT NULL DEFAULT 5 CHECK (hysteresis_percent >= 0),
1295
+ dry_run INTEGER NOT NULL DEFAULT 0 CHECK (dry_run IN (0, 1)),
1296
+ enabled INTEGER NOT NULL DEFAULT 1 CHECK (enabled IN (0, 1)),
1297
+ version INTEGER NOT NULL DEFAULT 1 CHECK (version > 0),
1298
+ created_at TEXT NOT NULL,
1299
+ updated_at TEXT NOT NULL,
1300
+ UNIQUE(organization_id, name),
1301
+ CHECK (soft_limit_cents IS NOT NULL OR hard_limit_cents IS NOT NULL)
1302
+ ) STRICT;
1303
+ CREATE INDEX spend_budgets_scope ON spend_budgets(organization_id, project_id, environment_id, enabled);
1304
+
1305
+ -- Hourly rollups. The empty string stands in for "unscoped" so the
1306
+ -- primary key stays usable: SQLite treats every NULL as distinct, which
1307
+ -- would let the same bucket be inserted without limit.
1308
+ CREATE TABLE spend_usage (
1309
+ organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
1310
+ project_id TEXT NOT NULL DEFAULT '',
1311
+ environment_id TEXT NOT NULL DEFAULT '',
1312
+ resource_id TEXT NOT NULL DEFAULT '',
1313
+ provider TEXT NOT NULL DEFAULT '',
1314
+ region TEXT NOT NULL DEFAULT '',
1315
+ meter TEXT NOT NULL,
1316
+ bucket_start TEXT NOT NULL,
1317
+ quantity REAL NOT NULL DEFAULT 0 CHECK (quantity >= 0),
1318
+ cost_cents REAL NOT NULL DEFAULT 0 CHECK (cost_cents >= 0),
1319
+ sample_count INTEGER NOT NULL DEFAULT 0 CHECK (sample_count >= 0),
1320
+ updated_at TEXT NOT NULL,
1321
+ PRIMARY KEY(organization_id, project_id, environment_id, resource_id, provider, region, meter, bucket_start)
1322
+ ) STRICT;
1323
+ CREATE INDEX spend_usage_window ON spend_usage(organization_id, bucket_start);
1324
+ CREATE INDEX spend_usage_project_window ON spend_usage(project_id, bucket_start);
1325
+
1326
+ -- Applied deltas, so replaying telemetry cannot double-bill.
1327
+ CREATE TABLE spend_usage_receipts (
1328
+ key TEXT PRIMARY KEY,
1329
+ organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
1330
+ bucket_start TEXT NOT NULL,
1331
+ created_at TEXT NOT NULL
1332
+ ) STRICT;
1333
+ CREATE INDEX spend_usage_receipts_age ON spend_usage_receipts(created_at);
1334
+
1335
+ CREATE TABLE spend_enforcements (
1336
+ id TEXT PRIMARY KEY,
1337
+ budget_id TEXT NOT NULL REFERENCES spend_budgets(id) ON DELETE CASCADE,
1338
+ organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
1339
+ project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
1340
+ environment_id TEXT REFERENCES environments(id) ON DELETE CASCADE,
1341
+ action TEXT NOT NULL,
1342
+ state TEXT NOT NULL CHECK (state IN ('pending', 'active', 'releasing', 'released', 'failed')),
1343
+ reason TEXT NOT NULL,
1344
+ restore TEXT NOT NULL DEFAULT '{}',
1345
+ triggered_at_percent REAL NOT NULL DEFAULT 0,
1346
+ simulated INTEGER NOT NULL DEFAULT 0 CHECK (simulated IN (0, 1)),
1347
+ applied_at TEXT,
1348
+ released_at TEXT,
1349
+ error TEXT,
1350
+ created_at TEXT NOT NULL,
1351
+ updated_at TEXT NOT NULL
1352
+ ) STRICT;
1353
+ -- At most one live enforcement per budget+action; released rows are history.
1354
+ CREATE UNIQUE INDEX spend_enforcements_live
1355
+ ON spend_enforcements(budget_id, action)
1356
+ WHERE state IN ('pending', 'active', 'releasing');
1357
+ CREATE INDEX spend_enforcements_scope ON spend_enforcements(organization_id, state, created_at DESC);
1358
+
1359
+ CREATE TABLE spend_decisions (
1360
+ id TEXT PRIMARY KEY,
1361
+ budget_id TEXT NOT NULL REFERENCES spend_budgets(id) ON DELETE CASCADE,
1362
+ level TEXT NOT NULL CHECK (level IN ('ok', 'warning', 'soft_capped', 'hard_capped')),
1363
+ window_start TEXT NOT NULL,
1364
+ window_end TEXT NOT NULL,
1365
+ used_percent REAL NOT NULL,
1366
+ projected_percent REAL NOT NULL,
1367
+ actual_cents REAL NOT NULL,
1368
+ projected_cents REAL NOT NULL,
1369
+ actions TEXT NOT NULL DEFAULT '[]',
1370
+ simulated INTEGER NOT NULL DEFAULT 0 CHECK (simulated IN (0, 1)),
1371
+ reason TEXT NOT NULL,
1372
+ evaluated_at TEXT NOT NULL
1373
+ ) STRICT;
1374
+ CREATE INDEX spend_decisions_budget ON spend_decisions(budget_id, evaluated_at DESC);
1375
+
1376
+ CREATE TABLE spend_anomalies (
1377
+ id TEXT PRIMARY KEY,
1378
+ organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
1379
+ project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
1380
+ environment_id TEXT REFERENCES environments(id) ON DELETE CASCADE,
1381
+ -- The nullable scope columns cannot carry the uniqueness themselves:
1382
+ -- SQLite treats every NULL as distinct, so an org-wide anomaly would
1383
+ -- re-insert on every evaluation. This collapses the scope to a string.
1384
+ scope_key TEXT NOT NULL,
1385
+ signal TEXT NOT NULL,
1386
+ direction TEXT NOT NULL CHECK (direction IN ('spike', 'drop')),
1387
+ observed REAL NOT NULL,
1388
+ expected REAL NOT NULL,
1389
+ score REAL NOT NULL,
1390
+ delta_percent REAL NOT NULL,
1391
+ severity TEXT NOT NULL CHECK (severity IN ('info', 'warning', 'critical')),
1392
+ bucket_start TEXT NOT NULL,
1393
+ evidence TEXT NOT NULL DEFAULT '{}',
1394
+ acknowledged_at TEXT,
1395
+ created_at TEXT NOT NULL,
1396
+ UNIQUE(scope_key, signal, bucket_start)
1397
+ ) STRICT;
1398
+ CREATE INDEX spend_anomalies_recent ON spend_anomalies(organization_id, created_at DESC);
1399
+ `},{version:38,name:"anomaly_configuration",sql:`
1400
+ CREATE TABLE anomaly_configs (
1401
+ id TEXT PRIMARY KEY,
1402
+ organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
1403
+ project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
1404
+ environment_id TEXT REFERENCES environments(id) ON DELETE CASCADE,
1405
+ -- Nullable scope columns cannot carry uniqueness in SQLite, so the
1406
+ -- scope is collapsed into a string the way spend_anomalies does.
1407
+ scope_key TEXT NOT NULL,
1408
+ signal TEXT NOT NULL,
1409
+ enabled INTEGER NOT NULL DEFAULT 1 CHECK (enabled IN (0, 1)),
1410
+ -- 'low' widens the threshold, 'high' narrows it. Stored rather than a
1411
+ -- raw z-score so the UI can offer a choice people can reason about.
1412
+ sensitivity TEXT NOT NULL DEFAULT 'medium' CHECK (sensitivity IN ('low', 'medium', 'high')),
1413
+ season_length INTEGER NOT NULL DEFAULT 24 CHECK (season_length > 0),
1414
+ min_absolute_delta REAL NOT NULL DEFAULT 0 CHECK (min_absolute_delta >= 0),
1415
+ detect_drops INTEGER NOT NULL DEFAULT 0 CHECK (detect_drops IN (0, 1)),
1416
+ severity TEXT NOT NULL DEFAULT 'warning' CHECK (severity IN ('info', 'warning', 'critical')),
1417
+ version INTEGER NOT NULL DEFAULT 1 CHECK (version > 0),
1418
+ created_at TEXT NOT NULL,
1419
+ updated_at TEXT NOT NULL,
1420
+ UNIQUE(scope_key, signal)
1421
+ ) STRICT;
1422
+ CREATE INDEX anomaly_configs_scope ON anomaly_configs(organization_id, enabled);
1423
+
1424
+ CREATE TABLE anomaly_silences (
1425
+ id TEXT PRIMARY KEY,
1426
+ organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
1427
+ project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
1428
+ scope_key TEXT NOT NULL,
1429
+ -- A silence matches on any combination; an empty matcher would silence
1430
+ -- everything, which the store refuses rather than storing.
1431
+ signal TEXT,
1432
+ route_pattern TEXT,
1433
+ status_code INTEGER,
1434
+ reason TEXT NOT NULL,
1435
+ actor_id TEXT REFERENCES actors(id) ON DELETE SET NULL,
1436
+ expires_at TEXT,
1437
+ created_at TEXT NOT NULL
1438
+ ) STRICT;
1439
+ CREATE INDEX anomaly_silences_scope ON anomaly_silences(organization_id, expires_at);
1440
+ `},{version:39,name:"sms_notification_channel",sql:`
1441
+ -- SQLite cannot alter a CHECK constraint, so widening the channel kinds
1442
+ -- means rebuilding the table. Rows are copied first and the old table is
1443
+ -- dropped last, so an interrupted migration leaves the original intact.
1444
+ CREATE TABLE notification_channels_new (
1445
+ id TEXT PRIMARY KEY,
1446
+ organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
1447
+ name TEXT NOT NULL,
1448
+ kind TEXT NOT NULL CHECK (kind IN ('slack','discord','teams','telegram','email','webhook','sms')),
1449
+ config TEXT NOT NULL DEFAULT '{}',
1450
+ credential_ciphertext TEXT,
1451
+ credential_fingerprint TEXT,
1452
+ status TEXT NOT NULL CHECK (status IN ('active','paused','failing','disabled')),
1453
+ version INTEGER NOT NULL DEFAULT 1,
1454
+ last_tested_at TEXT,
1455
+ last_error TEXT,
1456
+ created_by_actor_id TEXT REFERENCES actors(id) ON DELETE SET NULL,
1457
+ created_at TEXT NOT NULL,
1458
+ updated_at TEXT NOT NULL,
1459
+ UNIQUE(organization_id, name)
1460
+ ) STRICT;
1461
+
1462
+ INSERT INTO notification_channels_new SELECT
1463
+ id, organization_id, name, kind, config, credential_ciphertext, credential_fingerprint,
1464
+ status, version, last_tested_at, last_error, created_by_actor_id, created_at, updated_at
1465
+ FROM notification_channels;
1466
+
1467
+ DROP TABLE notification_channels;
1468
+ ALTER TABLE notification_channels_new RENAME TO notification_channels;
1469
+ `},{version:40,name:"telemetry_kind_scope_index",sql:`
1470
+ -- Anomaly detection filters request records by (project, kind, time).
1471
+ -- The existing scope index leads with project_id and environment_id but
1472
+ -- never mentions kind, so every request series scanned each project's
1473
+ -- logs, metrics, and traces too and discarded them in memory. On a
1474
+ -- project emitting mostly logs that is several times the necessary work,
1475
+ -- once a minute, forever.
1476
+ CREATE INDEX telemetry_kind_scope_time_idx
1477
+ ON telemetry_records(project_id, kind, timestamp);
1478
+ `}];class U extends Error{constructor(E,T,N){super(`${E} ${T} changed since version ${N}`);this.name="OptimisticConcurrencyError"}}class j extends Error{constructor(E,T){super(`Operation cannot transition from ${E} to ${T}`);this.name="InvalidOperationTransitionError"}}class v extends Error{constructor(E,T){super(`Control-plane schema version ${E} is newer than supported version ${T}`);this.name="UnsupportedSchemaVersionError"}}var eE=65536,q=16384,cE=["succeeded","failed","cancelled","timed_out"],CE={queued:["running","cancelled","timed_out"],running:["queued","succeeded","failed","cancelled","timed_out"],succeeded:[],failed:["queued"],cancelled:["queued"],timed_out:["queued"]},UE=/(?:^|_)(?:authorization|cookie|credential|password|passwd|secret|token|api_?key|private_?key|access_?key)(?:$|_)/i,NE=/(?:authorization|password|secret|token|api[_-]?key|private[_-]?key|access[_-]?key)\s*[=:]\s*[^\s,;]+/gi;function c(E){if(typeof E!=="string")return{};try{return JSON.parse(E)}catch{return{}}}function d(E){return typeof E==="string"?E:void 0}function P(E,T){if(T&&UE.test(T))return"[REDACTED]";if(E===null||typeof E==="boolean"||typeof E==="number")return E;if(typeof E==="string")return E.replace(NE,"[REDACTED]");if(Array.isArray(E))return E.map((N)=>P(N));if(E&&typeof E==="object"){let N={};for(let[e,L]of Object.entries(E))N[e]=P(L,e);return N}return String(E??"")}function LE(E,T=eE){let N=P(E),e=JSON.stringify(N);if(Buffer.byteLength(e)<=T)return N;return{truncated:!0,originalBytes:Buffer.byteLength(e),preview:e.slice(0,Math.max(0,T-128))}}function r(E){return JSON.stringify(LE(E))}function z(E){if(!E)return;let T=E.replace(NE,"[REDACTED]");return Buffer.byteLength(T)<=q?T:`${T.slice(0,q-24)}
1479
+ [output truncated]`}function F(E){return{id:String(E.id),slug:String(E.slug),name:String(E.name),description:d(E.description),organizationId:d(E.organization_id),desiredConfigHash:d(E.desired_config_hash),version:Number(E.version),createdAt:String(E.created_at),updatedAt:String(E.updated_at)}}function y(E){return{id:String(E.id),projectId:String(E.project_id),slug:String(E.slug),name:String(E.name),kind:String(E.kind),region:d(E.region),desiredState:c(E.desired_state),discoveredState:c(E.discovered_state),version:Number(E.version),createdAt:String(E.created_at),updatedAt:String(E.updated_at)}}function m(E){return{id:String(E.id),projectId:String(E.project_id),environmentId:d(E.environment_id),kind:String(E.kind),slug:String(E.slug),name:String(E.name),provider:d(E.provider),providerId:d(E.provider_id),desiredState:c(E.desired_state),discoveredState:c(E.discovered_state),metadata:c(E.metadata),version:Number(E.version),createdAt:String(E.created_at),updatedAt:String(E.updated_at)}}function f(E){return{id:String(E.id),kind:String(E.kind),externalId:d(E.external_id),displayName:String(E.display_name),metadata:c(E.metadata),disabledAt:d(E.disabled_at),version:Number(E.version),createdAt:String(E.created_at),updatedAt:String(E.updated_at)}}function O(E){return{id:String(E.id),projectId:d(E.project_id),environmentId:d(E.environment_id),resourceId:d(E.resource_id),actorId:d(E.actor_id),kind:String(E.kind),state:String(E.state),correlationId:String(E.correlation_id),idempotencyKey:d(E.idempotency_key),input:c(E.input),output:c(E.output),error:d(E.error),attempt:Number(E.attempt),priority:Number(E.priority),leaseOwner:d(E.lease_owner),leaseExpiresAt:d(E.lease_expires_at),cancelRequestedAt:d(E.cancel_requested_at),startedAt:d(E.started_at),finishedAt:d(E.finished_at),version:Number(E.version),createdAt:String(E.created_at),updatedAt:String(E.updated_at)}}function M(E){return{id:String(E.id),sequence:Number(E.sequence),organizationId:d(E.organization_id),projectId:d(E.project_id),operationId:d(E.operation_id),resourceId:d(E.resource_id),actorId:d(E.actor_id),correlationId:String(E.correlation_id),type:String(E.type),level:String(E.level),payload:c(E.payload),createdAt:String(E.created_at)}}function l(E){return{id:String(E.id),projectId:String(E.project_id),name:String(E.name),normalizedName:String(E.normalized_name),color:String(E.color),createdAt:String(E.created_at),updatedAt:String(E.updated_at)}}function h(E){return{id:String(E.id),actorKey:String(E.actor_key),name:String(E.name),routeId:String(E.route_id),query:c(E.query),createdAt:String(E.created_at),updatedAt:String(E.updated_at)}}function K(E){return{actorKey:String(E.actor_key),entityType:String(E.entity_type),entityId:String(E.entity_id),favorite:Number(E.favorite)===1,lastVisitedAt:String(E.last_visited_at),visitCount:Number(E.visit_count)}}function b(E){return{id:String(E.id),slug:String(E.slug),name:String(E.name),createdAt:String(E.created_at),updatedAt:String(E.updated_at)}}function Q(E){let T=String(E.scope_type);return{type:T,...T==="organization"?{}:{id:String(E.scope_id)}}}function k(E){return{id:String(E.id),organizationId:String(E.organization_id),actorId:String(E.actor_id),roleTemplate:String(E.role_template),scope:Q(E),source:String(E.source??"manual"),status:String(E.status),sessionVersion:Number(E.session_version),lastActiveAt:d(E.last_active_at),createdAt:String(E.created_at),updatedAt:String(E.updated_at)}}function A(E,T){let N=d(E.accepted_at),e=d(E.revoked_at),L=String(E.expires_at),t=e?"revoked":N?"accepted":L<=T?"expired":"pending";return{id:String(E.id),organizationId:String(E.organization_id),email:String(E.email),roleTemplate:String(E.role_template),scope:Q(E),invitedByActorId:d(E.invited_by_actor_id),acceptedByActorId:d(E.accepted_by_actor_id),expiresAt:L,acceptedAt:N,revokedAt:e,state:t,createdAt:String(E.created_at),updatedAt:String(E.updated_at)}}function H(E){return{id:String(E.id),organizationId:String(E.organization_id),membershipId:String(E.membership_id),effect:String(E.effect),capability:String(E.capability),scope:Q(E),source:String(E.source??"manual"),createdAt:String(E.created_at),updatedAt:String(E.updated_at)}}function D(E){return{type:E.type,id:E.id??null}}function OE(E){return Array.from({length:E},()=>"?").join(", ")}function a(E,T,N){return E.run(T,N)}class iE{path;database;nowFn;idFn;constructor(E={}){this.path=E.path??TE(E.cwd??process.cwd(),"control-plane.sqlite"),this.nowFn=E.now??(()=>new Date),this.idFn=E.id??(()=>crypto.randomUUID());let T=this.path===":memory:";if(!T){rE(EE(this.path),{recursive:!0,mode:448});try{B(EE(this.path),448)}catch{}}if(this.database=new oE(this.path,{create:!0,strict:!0}),this.database.run("PRAGMA foreign_keys = ON"),this.database.run(`PRAGMA busy_timeout = ${Math.max(100,E.busyTimeoutMs??5000)}`),!T)this.database.run("PRAGMA journal_mode = WAL"),this.database.run("PRAGMA synchronous = NORMAL");if(this.migrate(),!T)this.secureDatabaseFiles()}now(){return this.nowFn().toISOString()}secureDatabaseFiles(){for(let E of[this.path,`${this.path}-wal`,`${this.path}-shm`]){if(!w(E))continue;try{B(E,384)}catch{}}}backupTo(E){this.database.run("VACUUM INTO ?",[E]);try{B(E,384)}catch{}}migrate(){let E=this.database.query("PRAGMA user_version").get(),T=Number(E?.user_version??0);if(T>C)throw new v(T,C);if(T===C)return;let N;if(T>0&&this.path!==":memory:")N=`${this.path}.v${T}.${Date.now()}.bak`,this.backupTo(N);try{if(this.database.transaction(()=>{for(let L of G){if(L.version<=T)continue;this.database.run(L.sql),a(this.database,"INSERT INTO schema_migrations (version, name, applied_at) VALUES (?, ?, ?)",[L.version,L.name,this.now()]),this.database.run(`PRAGMA user_version = ${L.version}`)}}).exclusive(),N)this.setSetting("storage.last_backup",{path:N,createdAt:this.now(),reason:"pre-migration"})}catch(e){let L=N?` Restore from ${N}.`:"";throw Error(`Control-plane migration from schema ${T} failed.${L} ${e instanceof Error?e.message:String(e)}`)}}close(){if(this.path!==":memory:")this.secureDatabaseFiles();this.database.close()}transaction(E){return this.database.transaction(E).immediate()}createProject(E){let T=E.id??this.idFn(),N=this.now();return a(this.database,"INSERT INTO projects (id, slug, name, description, organization_id, desired_config_hash, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",[T,E.slug,E.name,E.description??null,E.organizationId??null,E.desiredConfigHash??null,N,N]),this.getProject(T)}getProject(E){let T=this.database.query("SELECT * FROM projects WHERE id = ?").get(E);return T?F(T):void 0}getProjectBySlug(E){let T=this.database.query("SELECT * FROM projects WHERE slug = ?").get(E);return T?F(T):void 0}listProjects(){return this.database.query("SELECT * FROM projects ORDER BY name COLLATE NOCASE, id").all().map(F)}updateProject(E,T,N){let e=this.getProject(E);if(!e||e.version!==T)throw new U("Project",E,T);if(a(this.database,`UPDATE projects SET name = ?, description = ?, organization_id = ?, desired_config_hash = ?, version = version + 1, updated_at = ?
1480
+ WHERE id = ? AND version = ?`,[N.name??e.name,N.description===null?null:N.description??e.description??null,N.organizationId===null?null:N.organizationId??e.organizationId??null,N.desiredConfigHash===null?null:N.desiredConfigHash??e.desiredConfigHash??null,this.now(),E,T]).changes!==1)throw new U("Project",E,T);return this.getProject(E)}createEnvironment(E){let T=E.id??this.idFn(),N=this.now();return a(this.database,`INSERT INTO environments (id, project_id, slug, name, kind, region, desired_state, discovered_state, created_at, updated_at)
1481
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,[T,E.projectId,E.slug,E.name,E.kind,E.region??null,r(E.desiredState??{}),r(E.discoveredState??{}),N,N]),y(this.database.query("SELECT * FROM environments WHERE id = ?").get(T))}listEnvironments(E){return this.database.query("SELECT * FROM environments WHERE project_id = ? ORDER BY name COLLATE NOCASE, id").all(E).map(y)}getEnvironmentBySlug(E,T){let N=this.database.query("SELECT * FROM environments WHERE project_id = ? AND slug = ?").get(E,T);return N?y(N):void 0}createResource(E){let T=E.id??this.idFn(),N=this.now();return a(this.database,`INSERT INTO resources (id, project_id, environment_id, kind, slug, name, provider, provider_id, desired_state, discovered_state, metadata, created_at, updated_at)
1482
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,[T,E.projectId,E.environmentId??null,E.kind,E.slug,E.name,E.provider??null,E.providerId??null,r(E.desiredState??{}),r(E.discoveredState??{}),r(E.metadata??{}),N,N]),m(this.database.query("SELECT * FROM resources WHERE id = ?").get(T))}listResources(E,T){return(T?this.database.query("SELECT * FROM resources WHERE project_id = ? AND environment_id = ? ORDER BY kind, name COLLATE NOCASE").all(E,T):this.database.query("SELECT * FROM resources WHERE project_id = ? ORDER BY kind, name COLLATE NOCASE").all(E)).map(m)}getResource(E){let T=this.database.query("SELECT * FROM resources WHERE id = ?").get(E);return T?m(T):void 0}updateResource(E,T,N){let e=this.getResource(E);if(!e||e.version!==T)throw new U("Resource",E,T);if(a(this.database,`UPDATE resources SET name = ?, provider = ?, provider_id = ?, desired_state = ?, discovered_state = ?, metadata = ?,
1483
+ version = version + 1, updated_at = ? WHERE id = ? AND version = ?`,[N.name??e.name,N.provider===null?null:N.provider??e.provider??null,N.providerId===null?null:N.providerId??e.providerId??null,r(N.desiredState??e.desiredState),r(N.discoveredState??e.discoveredState),r(N.metadata??e.metadata),this.now(),E,T]).changes!==1)throw new U("Resource",E,T);return this.getResource(E)}createActor(E){let T=E.id??this.idFn(),N=this.now();return a(this.database,"INSERT INTO actors (id, kind, external_id, display_name, metadata, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)",[T,E.kind,E.externalId??null,E.displayName,r(E.metadata??{}),N,N]),f(this.database.query("SELECT * FROM actors WHERE id = ?").get(T))}getActorByExternalId(E,T){let N=this.database.query("SELECT * FROM actors WHERE kind = ? AND external_id = ?").get(E,T);return N?f(N):void 0}getActor(E){let T=this.database.query("SELECT * FROM actors WHERE id = ?").get(E);return T?f(T):void 0}createOrganization(E){let T=E.slug.trim().toLowerCase();if(!/^[a-z0-9][a-z0-9-]{1,62}[a-z0-9]$/.test(T))throw Error("Organization slugs must contain 3-64 lowercase letters, numbers, or dashes");let N=E.name.trim();if(!N||N.length>100)throw Error("Organization names must contain 1-100 characters");let e=E.id??this.idFn(),L=this.now();return a(this.database,"INSERT INTO organizations (id, slug, name, created_at, updated_at) VALUES (?, ?, ?, ?, ?)",[e,T,N,L,L]),this.getOrganization(e)}getOrganization(E){let T=this.database.query("SELECT * FROM organizations WHERE id = ?").get(E);return T?b(T):void 0}getOrganizationBySlug(E){let T=this.database.query("SELECT * FROM organizations WHERE slug = ?").get(E.trim().toLowerCase());return T?b(T):void 0}listOrganizations(E){return(E?this.database.query(`SELECT organizations.* FROM organizations JOIN organization_memberships ON organization_memberships.organization_id = organizations.id
1484
+ WHERE organization_memberships.actor_id = ? AND organization_memberships.status = 'active' ORDER BY organizations.name COLLATE NOCASE`).all(E):this.database.query("SELECT * FROM organizations ORDER BY name COLLATE NOCASE").all()).map(b)}normalizeAuthorizationScope(E,T={type:"organization"}){if(T.type==="organization"){if(T.id&&T.id!==E)throw Error("Organization scope does not match the requested organization");return{type:"organization"}}if(!T.id)throw Error(`${T.type} scopes require an ID`);if(!this.resolveAuthorizationTarget(E,T))throw Error("Authorization scope was not found in this organization");return{type:T.type,id:T.id}}resolveAuthorizationTarget(E,T){if(!this.getOrganization(E))return;if(T.type==="organization")return{organizationId:E};if(!T.id)return;if(T.type==="project"){let e=this.database.query("SELECT id FROM projects WHERE id = ? AND organization_id = ?").get(T.id,E);return e?{organizationId:E,projectId:String(e.id)}:void 0}if(T.type==="environment"){let e=this.database.query("SELECT environments.id, environments.project_id FROM environments JOIN projects ON projects.id = environments.project_id WHERE environments.id = ? AND projects.organization_id = ?").get(T.id,E);return e?{organizationId:E,projectId:String(e.project_id),environmentId:String(e.id)}:void 0}let N=this.database.query(`SELECT resources.id, resources.project_id, resources.environment_id FROM resources
1485
+ JOIN projects ON projects.id = resources.project_id WHERE resources.id = ? AND projects.organization_id = ?`).get(T.id,E);return N?{organizationId:E,projectId:String(N.project_id),environmentId:d(N.environment_id),resourceId:String(N.id)}:void 0}createMembership(E){if(!this.getActor(E.actorId))throw Error("Membership actor was not found");let T=this.normalizeAuthorizationScope(E.organizationId,E.scope);if(E.roleTemplate==="owner"&&T.type!=="organization")throw Error("Owners must be scoped to the organization");let N=E.id??this.idFn(),e=this.now();a(this.database,`INSERT INTO organization_memberships (id, organization_id, actor_id, role_template, scope_type, scope_id, source, created_at, updated_at)
1486
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,[N,E.organizationId,E.actorId,E.roleTemplate,T.type,T.id??null,E.source??"manual",e,e]);let L=this.getMembership(N);return this.appendEvent({organizationId:E.organizationId,actorId:E.performedByActorId,type:"organization.membership.created",payload:{membershipId:N,actorId:E.actorId,roleTemplate:E.roleTemplate,scope:D(T)}}),L}getMembership(E){let T=this.database.query("SELECT * FROM organization_memberships WHERE id = ?").get(E);return T?k(T):void 0}getMembershipForActor(E,T){let N=this.database.query("SELECT * FROM organization_memberships WHERE organization_id = ? AND actor_id = ?").get(E,T);return N?k(N):void 0}listMemberships(E,T={}){let N=T.includeRevoked?"":"AND status = 'active'";return this.database.query(`SELECT * FROM organization_memberships WHERE organization_id = ? ${N} ORDER BY created_at, id`).all(E).map(k)}updateMembership(E){let T=this.getMembership(E.id);if(!T)throw Error("Membership was not found");let N=this.normalizeAuthorizationScope(T.organizationId,E.scope??T.scope);if(E.roleTemplate==="owner"&&N.type!=="organization")throw Error("Owners must be scoped to the organization");if(T.roleTemplate==="owner"&&T.status==="active"&&(E.roleTemplate!=="owner"||N.type!=="organization"))this.assertAnotherOwner(T);return a(this.database,"UPDATE organization_memberships SET role_template = ?, scope_type = ?, scope_id = ?, session_version = session_version + 1, updated_at = ? WHERE id = ?",[E.roleTemplate,N.type,N.id??null,this.now(),T.id]),this.appendEvent({organizationId:T.organizationId,actorId:E.actorId,type:"organization.membership.updated",payload:{membershipId:T.id,from:{roleTemplate:T.roleTemplate,scope:D(T.scope)},to:{roleTemplate:E.roleTemplate,scope:D(N)}}}),this.getMembership(T.id)}revokeMembership(E,T){let N=this.getMembership(E);if(!N)throw Error("Membership was not found");if(N.status==="revoked")return N;if(N.roleTemplate==="owner")this.assertAnotherOwner(N);return a(this.database,"UPDATE organization_memberships SET status = 'revoked', session_version = session_version + 1, updated_at = ? WHERE id = ?",[this.now(),E]),this.appendEvent({organizationId:N.organizationId,actorId:T,type:"organization.membership.revoked",payload:{membershipId:E}}),this.getMembership(E)}touchMembership(E){let T=this.now();a(this.database,"UPDATE organization_memberships SET last_active_at = ?, updated_at = ? WHERE id = ? AND status = ?",[T,T,E,"active"]);let N=this.getMembership(E);if(!N)throw Error("Membership was not found");return N}assertAnotherOwner(E){if(Number(this.database.query("SELECT COUNT(*) AS count FROM organization_memberships WHERE organization_id = ? AND status = ? AND role_template = 'owner'").get(E.organizationId,"active")?.count??0)<=1)throw Error("Cannot remove or demote the last organization owner")}createInvitation(E){let T=E.email.trim().toLowerCase();if(!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(T)||T.length>254)throw Error("A valid invitation email is required");let N=this.normalizeAuthorizationScope(E.organizationId,E.scope);if(E.roleTemplate==="owner"&&N.type!=="organization")throw Error("Owners must be scoped to the organization");let e=dE(32).toString("base64url"),L=g("sha256").update(e).digest("hex"),t=E.id??this.idFn(),_=this.now(),s=new Date(this.nowFn().getTime()+Math.min(2592000000,Math.max(60000,E.expiresInMs??604800000))).toISOString();return a(this.database,`INSERT INTO organization_invitations (id, organization_id, email, role_template, scope_type, scope_id, token_hash, invited_by_actor_id, expires_at, created_at, updated_at)
1487
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,[t,E.organizationId,T,E.roleTemplate,N.type,N.id??null,L,E.invitedByActorId??null,s,_,_]),this.appendEvent({organizationId:E.organizationId,actorId:E.invitedByActorId,type:"organization.invitation.created",payload:{invitationId:t,email:T,roleTemplate:E.roleTemplate,scope:D(N),expiresAt:s}}),{invitation:this.getInvitation(t),token:e}}getInvitation(E){let T=this.database.query("SELECT * FROM organization_invitations WHERE id = ?").get(E);return T?A(T,this.now()):void 0}listInvitations(E){return this.database.query("SELECT * FROM organization_invitations WHERE organization_id = ? ORDER BY created_at DESC").all(E).map((T)=>A(T,this.now()))}inspectInvitationToken(E){let T=g("sha256").update(E).digest("hex"),N=this.database.query("SELECT * FROM organization_invitations WHERE token_hash = ?").get(T);return N?A(N,this.now()):void 0}acceptInvitation(E,T){let N=g("sha256").update(E).digest("hex");return this.transaction(()=>{let e=this.database.query("SELECT * FROM organization_invitations WHERE token_hash = ?").get(N);if(!e)throw Error("Invitation is invalid");let L=A(e,this.now());if(L.state!=="pending")throw Error(`Invitation is ${L.state}`);if(!this.getActor(T))throw Error("Invitation actor was not found");if(a(this.database,"UPDATE organization_invitations SET accepted_by_actor_id = ?, accepted_at = ?, updated_at = ? WHERE id = ? AND accepted_at IS NULL AND revoked_at IS NULL AND expires_at > ?",[T,this.now(),this.now(),L.id,this.now()]).changes!==1)throw Error("Invitation is no longer available");let _=this.getMembershipForActor(L.organizationId,T),s=_??this.createMembership({organizationId:L.organizationId,actorId:T,roleTemplate:L.roleTemplate,scope:L.scope,source:"invitation"});if(_&&L.roleTemplate==="owner"&&_.roleTemplate!=="owner")s=this.updateMembership({id:_.id,roleTemplate:"owner",scope:{type:"organization"},actorId:T});else if(_)for(let n of u(L.roleTemplate))this.upsertGrant({organizationId:L.organizationId,membershipId:_.id,effect:"allow",capability:n,scope:L.scope,source:"invitation"});return this.appendEvent({organizationId:L.organizationId,actorId:T,type:"organization.invitation.accepted",payload:{invitationId:L.id,membershipId:s.id}}),{invitation:this.getInvitation(L.id),membership:this.getMembership(s.id)}})}revokeInvitation(E,T){let N=this.getInvitation(E);if(!N)throw Error("Invitation was not found");if(N.acceptedAt)throw Error("Accepted invitations cannot be revoked");if(!N.revokedAt)a(this.database,"UPDATE organization_invitations SET revoked_at = ?, updated_at = ? WHERE id = ?",[this.now(),this.now(),E]);return this.appendEvent({organizationId:N.organizationId,actorId:T,type:"organization.invitation.revoked",payload:{invitationId:E}}),this.getInvitation(E)}reissueInvitation(E,T){let N=this.getInvitation(E);if(!N)throw Error("Invitation was not found");if(N.state==="accepted")throw Error("Accepted invitations cannot be reissued");return this.revokeInvitation(E,T),this.createInvitation({organizationId:N.organizationId,email:N.email,roleTemplate:N.roleTemplate,scope:N.scope,invitedByActorId:T})}upsertGrant(E){if(!X.includes(E.capability))throw Error("Unknown authorization capability");let T=this.getMembership(E.membershipId);if(!T||T.organizationId!==E.organizationId)throw Error("Grant membership was not found in this organization");let N=this.normalizeAuthorizationScope(E.organizationId,E.scope),e=this.now(),L=this.database.query("SELECT * FROM authorization_grants WHERE membership_id = ? AND effect = ? AND capability = ? AND scope_type = ? AND scope_id IS ?").get(E.membershipId,E.effect,E.capability,N.type,N.id??null);if(L)return H(L);let t=E.id??this.idFn();return a(this.database,`INSERT INTO authorization_grants (id, organization_id, membership_id, effect, capability, scope_type, scope_id, source, created_at, updated_at)
1488
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,[t,E.organizationId,E.membershipId,E.effect,E.capability,N.type,N.id??null,E.source??"manual",e,e]),a(this.database,"UPDATE organization_memberships SET session_version = session_version + 1, updated_at = ? WHERE id = ?",[e,E.membershipId]),this.appendEvent({organizationId:E.organizationId,actorId:E.actorId,type:"organization.grant.created",payload:{grantId:t,membershipId:E.membershipId,effect:E.effect,capability:E.capability,scope:D(N)}}),this.getGrant(t)}getGrant(E){let T=this.database.query("SELECT * FROM authorization_grants WHERE id = ?").get(E);return T?H(T):void 0}listGrants(E){return this.database.query("SELECT * FROM authorization_grants WHERE membership_id = ? ORDER BY effect DESC, capability, scope_type, scope_id").all(E).map(H)}removeGrant(E,T){let N=this.getGrant(E);if(!N)return!1;let e=a(this.database,"DELETE FROM authorization_grants WHERE id = ?",[E]).changes===1;if(e)a(this.database,"UPDATE organization_memberships SET session_version = session_version + 1, updated_at = ? WHERE id = ?",[this.now(),N.membershipId]),this.appendEvent({organizationId:N.organizationId,actorId:T,type:"organization.grant.removed",payload:{grantId:E,membershipId:N.membershipId}});return e}createOperation(E){if(E.idempotencyKey){let L=this.database.query("SELECT * FROM operations WHERE idempotency_key = ?").get(E.idempotencyKey);if(L)return O(L)}let T=E.id??this.idFn(),N=E.correlationId??this.idFn(),e=this.now();try{return this.transaction(()=>{return a(this.database,`INSERT INTO operations (id, project_id, environment_id, resource_id, actor_id, kind, state, correlation_id, idempotency_key, input, output, priority, created_at, updated_at)
1489
+ VALUES (?, ?, ?, ?, ?, ?, 'queued', ?, ?, ?, '{}', ?, ?, ?)`,[T,E.projectId??null,E.environmentId??null,E.resourceId??null,E.actorId??null,E.kind,N,E.idempotencyKey??null,r(E.input??{}),E.priority??0,e,e]),this.appendEvent({projectId:E.projectId,operationId:T,resourceId:E.resourceId,actorId:E.actorId,correlationId:N,type:"operation.queued",payload:{kind:E.kind}}),this.getOperation(T)})}catch(L){if(E.idempotencyKey){let t=this.database.query("SELECT * FROM operations WHERE idempotency_key = ?").get(E.idempotencyKey);if(t)return O(t)}throw L}}getOperation(E){let T=this.database.query("SELECT * FROM operations WHERE id = ?").get(E);return T?O(T):void 0}listOperations(E={}){let T=[],N=[];if(E.projectId)T.push("project_id = ?"),N.push(E.projectId);if(E.state)T.push("state = ?"),N.push(E.state);if(E.kind)T.push("kind = ?"),N.push(E.kind);if(E.before)T.push("created_at < ?"),N.push(E.before);let e=Math.min(500,Math.max(1,E.limit??100));N.push(e);let L=T.length?`WHERE ${T.join(" AND ")}`:"";return this.database.query(`SELECT * FROM operations ${L} ORDER BY created_at DESC, id DESC LIMIT ?`).all(...N).map(O)}claimNextOperation(E,T=60000,N){let e=this.now(),L=new Date(this.nowFn().getTime()+T).toISOString();return this.transaction(()=>{let t=N?.length?`AND kind IN (${OE(N.length)})`:"",_=N?[...N]:[],s=this.database.query(`SELECT * FROM operations WHERE state = 'queued' ${t} ORDER BY priority DESC, created_at ASC, id ASC LIMIT 1`).get(..._);if(!s)return;let n=O(s);if(a(this.database,`UPDATE operations SET state = 'running', attempt = attempt + 1, lease_owner = ?, lease_expires_at = ?,
1490
+ started_at = COALESCE(started_at, ?), updated_at = ?, version = version + 1 WHERE id = ? AND state = 'queued' AND version = ?`,[E,L,e,e,n.id,n.version]).changes!==1)return;return this.appendEvent({projectId:n.projectId,operationId:n.id,resourceId:n.resourceId,actorId:n.actorId,correlationId:n.correlationId,type:"operation.running",payload:{attempt:n.attempt+1,workerId:E}}),this.getOperation(n.id)})}claimOperation(E,T,N=60000){let e=this.now(),L=new Date(this.nowFn().getTime()+N).toISOString();return this.transaction(()=>{let t=this.getOperation(E);if(!t||t.state!=="queued")return;if(a(this.database,`UPDATE operations SET state = 'running', attempt = attempt + 1, lease_owner = ?, lease_expires_at = ?,
1491
+ started_at = COALESCE(started_at, ?), updated_at = ?, version = version + 1 WHERE id = ? AND state = 'queued' AND version = ?`,[T,L,e,e,t.id,t.version]).changes!==1)return;return this.appendEvent({projectId:t.projectId,operationId:t.id,resourceId:t.resourceId,actorId:t.actorId,correlationId:t.correlationId,type:"operation.running",payload:{attempt:t.attempt+1,workerId:T}}),this.getOperation(t.id)})}transitionOperation(E,T){return this.transaction(()=>{let N=this.getOperation(E);if(!N)throw Error(`Operation ${E} was not found`);let e=T.expectedVersion??N.version;if(N.version!==e)throw new U("Operation",E,e);if(!CE[N.state].includes(T.to))throw new j(N.state,T.to);let L=this.now(),t=cE.includes(T.to),_=T.to==="queued";if(a(this.database,`UPDATE operations SET state = ?, output = ?, error = ?, lease_owner = ?, lease_expires_at = ?,
1492
+ finished_at = ?, updated_at = ?, version = version + 1 WHERE id = ? AND version = ?`,[T.to,r(T.output??N.output),z(T.error)??null,_||t?null:T.leaseOwner??N.leaseOwner??null,_||t?null:T.leaseExpiresAt??N.leaseExpiresAt??null,t?L:null,L,E,e]).changes!==1)throw new U("Operation",E,e);return this.appendEvent({projectId:N.projectId,operationId:E,resourceId:N.resourceId,actorId:N.actorId,correlationId:N.correlationId,type:`operation.${T.to}`,level:T.to==="failed"?"error":"info",payload:{from:N.state,to:T.to,error:z(T.error)??null}}),this.getOperation(E)})}requestCancellation(E){let T=this.now();a(this.database,"UPDATE operations SET cancel_requested_at = ?, updated_at = ?, version = version + 1 WHERE id = ? AND state IN ('queued', 'running')",[T,T,E]);let N=this.getOperation(E);if(!N)throw Error(`Operation ${E} was not found`);return N}reconcileOrphanedOperations(E={}){let T=(E.now??this.nowFn()).toISOString(),N=E.policy??"fail",e=this.database.query("SELECT * FROM operations WHERE state = 'running' AND (lease_expires_at IS NULL OR lease_expires_at <= ?)").all(T).map(O),L=0,t=0;for(let _ of e)if(N==="requeue")this.transitionOperation(_.id,{to:"queued",expectedVersion:_.version,error:"Worker lease expired; operation requeued after restart."}),L++;else this.transitionOperation(_.id,{to:"failed",expectedVersion:_.version,error:"Worker lease expired or dashboard restarted before completion."}),t++;return{requeued:L,failed:t}}appendEvent(E){let T=E.id??this.idFn(),N=E.correlationId??this.idFn(),e=E.organizationId??(E.projectId?d(this.database.query("SELECT organization_id FROM projects WHERE id = ?").get(E.projectId)?.organization_id):void 0);return a(this.database,`INSERT INTO events (id, organization_id, project_id, operation_id, resource_id, actor_id, correlation_id, type, level, payload, created_at)
1493
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,[T,e??null,E.projectId??null,E.operationId??null,E.resourceId??null,E.actorId??null,N,E.type,E.level??"info",r(E.payload??{}),this.now()]),M(this.database.query("SELECT * FROM events WHERE id = ?").get(T))}listEvents(E={}){let T=[],N=[];if(E.organizationId)T.push("organization_id = ?"),N.push(E.organizationId);if(E.projectId)T.push("project_id = ?"),N.push(E.projectId);if(E.operationId)T.push("operation_id = ?"),N.push(E.operationId);if(E.resourceId)T.push("resource_id = ?"),N.push(E.resourceId);if(E.correlationId)T.push("correlation_id = ?"),N.push(E.correlationId);if(E.afterSequence!==void 0)T.push("sequence > ?"),N.push(E.afterSequence);let e=Math.min(1000,Math.max(1,E.limit??200));N.push(e);let L=T.length?`WHERE ${T.join(" AND ")}`:"";return this.database.query(`SELECT * FROM events ${L} ORDER BY sequence ASC LIMIT ?`).all(...N).map(M)}setSetting(E,T){a(this.database,`INSERT INTO settings (key, value, updated_at) VALUES (?, ?, ?)
1494
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`,[E,r(T),this.now()])}getSetting(E){let T=this.database.query("SELECT value FROM settings WHERE key = ?").get(E);return T?c(T.value):void 0}upsertTag(E,T,N="#5a8be0"){let e=T.trim().toLowerCase().replace(/\s+/g,"-");if(!/^[a-z0-9][a-z0-9-]{0,31}$/.test(e))throw Error("Tag names must contain 1-32 letters, numbers, or dashes");if(!/^#[0-9a-f]{6}$/i.test(N))throw Error("Tag colors must be six-digit hex colors");let L=this.now(),t=this.database.query("SELECT * FROM tags WHERE project_id = ? AND normalized_name = ?").get(E,e);if(t)return a(this.database,"UPDATE tags SET name = ?, color = ?, updated_at = ? WHERE id = ?",[T.trim(),N.toLowerCase(),L,String(t.id)]),l(this.database.query("SELECT * FROM tags WHERE id = ?").get(String(t.id)));let _=this.idFn();return a(this.database,"INSERT INTO tags (id, project_id, name, normalized_name, color, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)",[_,E,T.trim(),e,N.toLowerCase(),L,L]),l(this.database.query("SELECT * FROM tags WHERE id = ?").get(_))}listTags(E){return this.database.query("SELECT * FROM tags WHERE project_id = ? ORDER BY normalized_name").all(E).map(l)}assignTag(E,T){if(!this.database.query("SELECT tags.id FROM tags JOIN resources ON resources.project_id = tags.project_id WHERE tags.id = ? AND resources.id = ?").get(T,E))throw Error("The tag and resource must belong to the same project");a(this.database,"INSERT OR IGNORE INTO resource_tags (resource_id, tag_id, created_at) VALUES (?, ?, ?)",[E,T,this.now()])}removeTag(E,T){a(this.database,"DELETE FROM resource_tags WHERE resource_id = ? AND tag_id = ?",[E,T])}listResourceTags(E){return this.database.query(`SELECT resource_tags.resource_id, tags.* FROM resource_tags JOIN tags ON tags.id = resource_tags.tag_id
1495
+ WHERE tags.project_id = ? ORDER BY tags.normalized_name`).all(E).map((N)=>({resourceId:String(N.resource_id),tag:l(N)}))}saveFilter(E,T,N,e){let L=T.trim();if(!L||L.length>64)throw Error("Saved filter names must contain 1-64 characters");if(!N||N.length>160||/^[a-z][a-z0-9+.-]*:|^\/\//i.test(N))throw Error("Saved filter routes must be local route IDs or paths");let t=r(e);if(t.length>8192)throw Error("Saved filter queries must be smaller than 8 KB");let _=this.database.query("SELECT * FROM saved_filters WHERE actor_key = ? AND name = ?").get(E,L),s=this.now();if(_)return a(this.database,"UPDATE saved_filters SET route_id = ?, query = ?, updated_at = ? WHERE id = ?",[N,t,s,String(_.id)]),h(this.database.query("SELECT * FROM saved_filters WHERE id = ?").get(String(_.id)));let n=this.idFn();return a(this.database,"INSERT INTO saved_filters (id, actor_key, name, route_id, query, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)",[n,E,L,N,t,s,s]),h(this.database.query("SELECT * FROM saved_filters WHERE id = ?").get(n))}listSavedFilters(E){return this.database.query("SELECT * FROM saved_filters WHERE actor_key = ? ORDER BY updated_at DESC, name").all(E).map(h)}deleteSavedFilter(E,T){return a(this.database,"DELETE FROM saved_filters WHERE actor_key = ? AND id = ?",[E,T]).changes===1}recordNavigation(E,T,N){let e=this.now();return a(this.database,`INSERT INTO navigation_items (actor_key, entity_type, entity_id, last_visited_at) VALUES (?, ?, ?, ?)
1496
+ ON CONFLICT(actor_key, entity_type, entity_id) DO UPDATE SET last_visited_at = excluded.last_visited_at, visit_count = visit_count + 1`,[E,T,N,e]),K(this.database.query("SELECT * FROM navigation_items WHERE actor_key = ? AND entity_type = ? AND entity_id = ?").get(E,T,N))}setFavorite(E,T,N,e){let L=this.now();return a(this.database,`INSERT INTO navigation_items (actor_key, entity_type, entity_id, favorite, last_visited_at) VALUES (?, ?, ?, ?, ?)
1497
+ ON CONFLICT(actor_key, entity_type, entity_id) DO UPDATE SET favorite = excluded.favorite`,[E,T,N,e?1:0,L]),K(this.database.query("SELECT * FROM navigation_items WHERE actor_key = ? AND entity_type = ? AND entity_id = ?").get(E,T,N))}listNavigation(E,T={}){let N=T.favoritesOnly?"AND favorite = 1":"",e=Math.min(100,Math.max(1,T.limit??20));return this.database.query(`SELECT * FROM navigation_items WHERE actor_key = ? ${N} ORDER BY favorite DESC, last_visited_at DESC LIMIT ?`).all(E,e).map(K)}compact(E={}){let T=new Date(this.nowFn().getTime()-(E.eventRetentionDays??90)*86400000).toISOString(),N=new Date(this.nowFn().getTime()-(E.operationRetentionDays??365)*86400000).toISOString(),e=this.transaction(()=>{let L=a(this.database,"DELETE FROM events WHERE created_at < ?",[T]).changes,t=a(this.database,"DELETE FROM operations WHERE created_at < ? AND state IN ('succeeded', 'failed', 'cancelled', 'timed_out')",[N]).changes;return{events:L,operations:t}});if(E.vacuum!==!1)this.database.run("VACUUM");return{deletedEvents:e.events,deletedOperations:e.operations,vacuumed:E.vacuum!==!1}}createBackup(E="manual"){if(this.path===":memory:")throw Error("Cannot create a filesystem backup for an in-memory control plane");let T=`${this.path}.${Date.now()}.bak`;return this.backupTo(T),this.setSetting("storage.last_backup",{path:T,createdAt:this.now(),reason:E}),T}integrityCheck(){let E=this.database.query("PRAGMA integrity_check").all();return E.length===1&&Object.values(E[0])[0]==="ok"?"ok":"corrupt"}health(){let E=Object.fromEntries(["queued","running","succeeded","failed","cancelled","timed_out"].map((L)=>[L,0]));for(let L of this.database.query("SELECT state, COUNT(*) AS count FROM operations GROUP BY state").all())E[String(L.state)]=Number(L.count);let T=this.database.query("PRAGMA journal_mode").get(),N=this.getSetting("storage.last_backup"),e=N&&!Array.isArray(N)&&typeof N==="object"&&typeof N.createdAt==="string"?N.createdAt:void 0;return{path:this.path,schemaVersion:Number(this.database.query("PRAGMA user_version").get()?.user_version??0),supportedSchemaVersion:C,integrity:this.integrityCheck(),journalMode:String(T?.journal_mode??"unknown"),databaseBytes:this.path!==":memory:"&&w(this.path)?nE(this.path).size:this.database.serialize().byteLength,lastBackupAt:e,operations:E,pendingRetryableOperations:E.queued+E.failed+E.timed_out}}exportSnapshot(){let E={};for(let T of this.database.query("SELECT key, value FROM settings ORDER BY key").all())E[String(T.key)]=c(T.value);return{format:"ts-cloud-control-plane",schemaVersion:C,exportedAt:this.now(),organizations:this.database.query("SELECT * FROM organizations ORDER BY id").all().map(b),memberships:this.database.query("SELECT * FROM organization_memberships ORDER BY id").all().map(k),invitations:this.database.query("SELECT * FROM organization_invitations ORDER BY id").all().map((T)=>({...A(T,this.now()),tokenHash:String(T.token_hash)})),grants:this.database.query("SELECT * FROM authorization_grants ORDER BY id").all().map(H),projects:this.database.query("SELECT * FROM projects ORDER BY id").all().map(F),environments:this.database.query("SELECT * FROM environments ORDER BY id").all().map(y),resources:this.database.query("SELECT * FROM resources ORDER BY id").all().map(m),actors:this.database.query("SELECT * FROM actors ORDER BY id").all().map(f),operations:this.database.query("SELECT * FROM operations ORDER BY id").all().map(O),events:this.database.query("SELECT * FROM events ORDER BY sequence").all().map(M),settings:E,tags:this.database.query("SELECT * FROM tags ORDER BY id").all().map(l),resourceTags:this.database.query("SELECT * FROM resource_tags ORDER BY resource_id, tag_id").all().map((T)=>({resourceId:String(T.resource_id),tagId:String(T.tag_id),createdAt:String(T.created_at)})),savedFilters:this.database.query("SELECT * FROM saved_filters ORDER BY id").all().map(h),navigationItems:this.database.query("SELECT * FROM navigation_items ORDER BY actor_key, entity_type, entity_id").all().map(K)}}importSnapshot(E,T={}){if(E.format!=="ts-cloud-control-plane")throw Error("Unsupported control-plane snapshot format");if(E.schemaVersion>C)throw new v(E.schemaVersion,C);this.transaction(()=>{if((Number(this.database.query("SELECT COUNT(*) AS count FROM projects").get()?.count??0)>0||Number(this.database.query("SELECT COUNT(*) AS count FROM operations").get()?.count??0)>0)&&!T.replace)throw Error("Control plane is not empty; pass replace: true to import this snapshot");if(T.replace)this.database.run("DELETE FROM authorization_grants"),this.database.run("DELETE FROM organization_invitations"),this.database.run("DELETE FROM organization_memberships"),this.database.run("DELETE FROM resource_tags"),this.database.run("DELETE FROM tags"),this.database.run("DELETE FROM saved_filters"),this.database.run("DELETE FROM navigation_items"),this.database.run("DELETE FROM events"),this.database.run("DELETE FROM operations"),this.database.run("DELETE FROM resources"),this.database.run("DELETE FROM environments"),this.database.run("DELETE FROM projects"),this.database.run("DELETE FROM actors"),this.database.run("DELETE FROM organizations"),this.database.run("DELETE FROM settings");for(let e of E.organizations??[])a(this.database,"INSERT INTO organizations (id, slug, name, created_at, updated_at) VALUES (?, ?, ?, ?, ?)",[e.id,e.slug,e.name,e.createdAt,e.updatedAt]);for(let e of E.projects)a(this.database,`INSERT INTO projects (id, slug, name, description, organization_id, desired_config_hash, version, created_at, updated_at)
1498
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,[e.id,e.slug,e.name,e.description??null,e.organizationId??null,e.desiredConfigHash??null,e.version,e.createdAt,e.updatedAt]);for(let e of E.environments)a(this.database,`INSERT INTO environments (id, project_id, slug, name, kind, region, desired_state, discovered_state, version, created_at, updated_at)
1499
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,[e.id,e.projectId,e.slug,e.name,e.kind,e.region??null,r(e.desiredState),r(e.discoveredState),e.version,e.createdAt,e.updatedAt]);for(let e of E.resources)a(this.database,`INSERT INTO resources (id, project_id, environment_id, kind, slug, name, provider, provider_id, desired_state, discovered_state, metadata, version, created_at, updated_at)
1500
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,[e.id,e.projectId,e.environmentId??null,e.kind,e.slug,e.name,e.provider??null,e.providerId??null,r(e.desiredState),r(e.discoveredState),r(e.metadata),e.version,e.createdAt,e.updatedAt]);for(let e of E.actors)a(this.database,`INSERT INTO actors (id, kind, external_id, display_name, metadata, disabled_at, version, created_at, updated_at)
1501
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,[e.id,e.kind,e.externalId??null,e.displayName,r(e.metadata),e.disabledAt??null,e.version,e.createdAt,e.updatedAt]);for(let e of E.memberships??[])a(this.database,`INSERT INTO organization_memberships (id, organization_id, actor_id, role_template, scope_type, scope_id, status, session_version, last_active_at, source, created_at, updated_at)
1502
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,[e.id,e.organizationId,e.actorId,e.roleTemplate,e.scope.type,e.scope.id??null,e.status,e.sessionVersion,e.lastActiveAt??null,e.source??"manual",e.createdAt,e.updatedAt]);for(let e of E.invitations??[]){let L=e.tokenHash??g("sha256").update(`revoked:${e.id}:${this.idFn()}`).digest("hex");a(this.database,`INSERT INTO organization_invitations (id, organization_id, email, role_template, scope_type, scope_id, token_hash, invited_by_actor_id, accepted_by_actor_id, expires_at, accepted_at, revoked_at, created_at, updated_at)
1503
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,[e.id,e.organizationId,e.email,e.roleTemplate,e.scope.type,e.scope.id??null,L,e.invitedByActorId??null,e.acceptedByActorId??null,e.expiresAt,e.acceptedAt??null,e.tokenHash?e.revokedAt??null:e.revokedAt??this.now(),e.createdAt,e.updatedAt])}for(let e of E.grants??[])a(this.database,`INSERT INTO authorization_grants (id, organization_id, membership_id, effect, capability, scope_type, scope_id, source, created_at, updated_at)
1504
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,[e.id,e.organizationId,e.membershipId,e.effect,e.capability,e.scope.type,e.scope.id??null,e.source??"manual",e.createdAt,e.updatedAt]);for(let e of E.operations)a(this.database,`INSERT INTO operations (id, project_id, environment_id, resource_id, actor_id, kind, state, correlation_id, idempotency_key,
1505
+ input, output, error, attempt, priority, lease_owner, lease_expires_at, cancel_requested_at, started_at, finished_at, version, created_at, updated_at)
1506
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,[e.id,e.projectId??null,e.environmentId??null,e.resourceId??null,e.actorId??null,e.kind,e.state,e.correlationId,e.idempotencyKey??null,r(e.input),r(e.output),z(e.error)??null,e.attempt,e.priority,e.leaseOwner??null,e.leaseExpiresAt??null,e.cancelRequestedAt??null,e.startedAt??null,e.finishedAt??null,e.version,e.createdAt,e.updatedAt]);for(let e of E.events)a(this.database,`INSERT INTO events (sequence, id, organization_id, project_id, operation_id, resource_id, actor_id, correlation_id, type, level, payload, created_at)
1507
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,[e.sequence,e.id,e.organizationId??null,e.projectId??null,e.operationId??null,e.resourceId??null,e.actorId??null,e.correlationId,e.type,e.level,r(e.payload),e.createdAt]);for(let[e,L]of Object.entries(E.settings))a(this.database,"INSERT INTO settings (key, value, updated_at) VALUES (?, ?, ?)",[e,r(L),this.now()]);for(let e of E.tags??[])a(this.database,"INSERT INTO tags (id, project_id, name, normalized_name, color, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)",[e.id,e.projectId,e.name,e.normalizedName,e.color,e.createdAt,e.updatedAt]);for(let e of E.resourceTags??[])a(this.database,"INSERT INTO resource_tags (resource_id, tag_id, created_at) VALUES (?, ?, ?)",[e.resourceId,e.tagId,e.createdAt]);for(let e of E.savedFilters??[])a(this.database,"INSERT INTO saved_filters (id, actor_key, name, route_id, query, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)",[e.id,e.actorKey,e.name,e.routeId,r(e.query),e.createdAt,e.updatedAt]);for(let e of E.navigationItems??[])a(this.database,"INSERT INTO navigation_items (actor_key, entity_type, entity_id, favorite, last_visited_at, visit_count) VALUES (?, ?, ?, ?, ?, ?)",[e.actorKey,e.entityType,e.entityId,e.favorite?1:0,e.lastVisitedAt,e.visitCount])})}}var XE=new Set(["domain","domains","hostname","hostnames","url","repository","repo","image","label","labels"]),RE=new Set(["sha","commit","commitsha","release","releaseid"]);function tE(E,T){if(!E||typeof E!=="object"||Array.isArray(E))return[];let N=[];for(let[e,L]of Object.entries(E)){if(!T.has(e.toLowerCase()))continue;if(typeof L==="string")N.push(L);else if(Array.isArray(L))N.push(...L.filter((t)=>typeof t==="string"))}return N}function W(E){return E.toLowerCase().trim().replace(/[^a-z0-9._:/@-]+/g," ")}function $(E,T,N){let e=W(E),L=W(T),t=W(N);if(!e)return 0;if(L===e)return 1000;if(L.startsWith(e))return 800-Math.min(100,L.length-e.length);if(L.includes(e))return 650-Math.min(100,L.indexOf(e));let _=e.split(/\s+/).filter(Boolean);if(_.every((n)=>t.includes(n)))return 400+_.length*10;let s=0;for(let n of e){if(s=t.indexOf(n,s),s<0)return 0;s++}return Math.max(1,180-Math.floor(t.length/20))}function lE(E,T){let N=E.kind.toLowerCase();if(N==="server"||N==="compute")return{type:"server",href:`/server/metrics?env=${encodeURIComponent(T)}`};if(N.includes("database")||["mysql","postgres","postgresql","sqlite","redis"].includes(N))return{type:"database",href:`/server/database?env=${encodeURIComponent(T)}&database=${encodeURIComponent(E.slug)}`};return{type:"service",href:`/server/sites?env=${encodeURIComponent(T)}&service=${encodeURIComponent(E.slug)}`}}function AE(E,T){let N=E.getProject(T.projectId);if(!N)return[];let e=E.listEnvironments(N.id),L=new Map(e.map((i)=>[i.id,i])),t=E.listResourceTags(N.id),_=new Map;for(let i of t){let o=_.get(i.resourceId)??[];o.push({id:i.tag.id,name:i.tag.name,color:i.tag.color}),_.set(i.resourceId,o)}let s=[],n=T.allowedResourceSlugs!==void 0;if(!n){s.push({type:"project",id:N.id,title:N.name,subtitle:N.slug,href:`/?project=${encodeURIComponent(N.slug)}`,tags:[],score:0});for(let i of e)s.push({type:"environment",id:i.id,title:i.name,subtitle:`${N.name} · ${i.kind}`,href:`/?env=${encodeURIComponent(i.slug)}`,environment:i.slug,tags:[],score:0})}let Z=E.listResources(N.id).filter((i)=>!T.allowedResourceSlugs||T.allowedResourceSlugs.has(i.slug));for(let i of Z){let o=i.environmentId?L.get(i.environmentId):void 0,R=_.get(i.id)??[],S=tE(i.desiredState,XE),I=[i.name,i.slug,i.kind,i.provider,i.providerId,o?.slug,...S,...R.map((aE)=>aE.name)].filter(Boolean).join(" "),p=lE(i,o?.slug??e[0]?.slug??"");s.push({type:p.type,id:i.id,title:i.name,subtitle:[i.kind,...S.slice(0,2)].join(" · "),href:p.href,environment:o?.slug,provider:i.provider,tags:R,score:$(T.query,i.name,I)})}if(!n)for(let i of E.listOperations({projectId:N.id,limit:500})){let o=i.environmentId?L.get(i.environmentId):void 0,R=tE(i.input,RE),S=i.kind.includes("deploy"),I=R[0]??i.kind,p=[i.kind,i.id,i.correlationId,i.state,...R].join(" ");s.push({type:S?"deployment":"operation",id:i.id,title:I,subtitle:`${i.kind} · ${i.state}`,href:`/server/activity?env=${encodeURIComponent(o?.slug??e[0]?.slug??"")}&operation=${encodeURIComponent(i.id)}`,environment:o?.slug,status:i.state,tags:[],score:$(T.query,I,p)})}for(let i of s)if(i.score===0)i.score=$(T.query,i.title,`${i.title} ${i.subtitle} ${i.environment??""} ${i.provider??""}`);let J={service:0,server:1,database:2,deployment:3,environment:4,project:5,operation:6};return s.filter((i)=>i.score>0).sort((i,o)=>o.score-i.score||J[i.type]-J[o.type]||i.title.localeCompare(o.title)||i.id.localeCompare(o.id)).slice(0,Math.min(50,Math.max(1,T.limit??20)))}export{X as jg,u as kg,V as lg,sE as mg,LE as ng,iE as og,AE as pg};