@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.
- package/dist/aws/index.js +1 -269
- package/dist/bin/chunk-00kg80qg.js +1 -0
- package/dist/bin/chunk-09nwf2gy.js +34 -0
- package/dist/bin/chunk-1k5kszjp.js +111 -0
- package/dist/bin/chunk-21e0mavm.js +1 -0
- package/dist/bin/chunk-271gk1n2.js +6 -0
- package/dist/bin/chunk-2empps47.js +2 -0
- package/dist/bin/chunk-38pq971s.js +2 -0
- package/dist/bin/chunk-3gykfyq4.js +37 -0
- package/dist/bin/chunk-3vqfexc4.js +70 -0
- package/dist/bin/chunk-4vrqm9h0.js +2 -0
- package/dist/bin/chunk-598yf4bs.js +1 -0
- package/dist/bin/chunk-5ectjq0b.js +1 -0
- package/dist/bin/chunk-5erkxpt7.js +512 -0
- package/dist/bin/chunk-5mrtzemj.js +1 -0
- package/dist/bin/chunk-5n9fmabb.js +1 -0
- package/dist/bin/chunk-9knpg30y.js +1 -0
- package/dist/bin/chunk-9pvsff8g.js +1 -0
- package/dist/bin/chunk-ax9mr6wm.js +44 -0
- package/dist/bin/chunk-axhnkn5g.js +2 -0
- package/dist/bin/chunk-c1vcdgvr.js +1 -0
- package/dist/bin/chunk-ctd2vg9w.js +2 -0
- package/dist/bin/chunk-dbgffky8.js +1 -0
- package/dist/bin/chunk-ddk4rt4j.js +6 -0
- package/dist/bin/chunk-dwr30vbv.js +1 -0
- package/dist/bin/chunk-e0rkrq70.js +1 -0
- package/dist/bin/chunk-e3xrwh9z.js +1 -0
- package/dist/bin/chunk-etrb03mt.js +1507 -0
- package/dist/bin/chunk-fhc6ntvd.js +2 -0
- package/dist/bin/chunk-fpm59y25.js +6 -0
- package/dist/bin/chunk-fvbxvq6b.js +1 -0
- package/dist/bin/chunk-fy8j3y8p.js +92 -0
- package/dist/bin/chunk-fz21b57w.js +1 -0
- package/dist/bin/chunk-gavvf0pg.js +141 -0
- package/dist/bin/chunk-ggn7ax8p.js +4 -0
- package/dist/bin/chunk-gpe2prkm.js +1 -0
- package/dist/bin/chunk-h5pa5men.js +2 -0
- package/dist/bin/chunk-hm08xdxf.js +2 -0
- package/dist/bin/chunk-hzgz13nf.js +1 -0
- package/dist/bin/chunk-j703btc5.js +50 -0
- package/dist/bin/chunk-k9t9wvms.js +1 -0
- package/dist/bin/chunk-m669wtgr.js +1 -0
- package/dist/bin/chunk-m7dgskt1.js +1 -0
- package/dist/bin/chunk-mh0kdzqp.js +2 -0
- package/dist/bin/chunk-mr0atd2s.js +2 -0
- package/dist/bin/chunk-mz0wbbsj.js +3 -0
- package/dist/bin/chunk-ngcyftm2.js +1 -0
- package/dist/bin/chunk-p66ptpmr.js +2 -0
- package/dist/bin/chunk-qk9qgepa.js +1 -0
- package/dist/bin/chunk-rxcvf10f.js +7364 -0
- package/dist/bin/chunk-rytk89az.js +60 -0
- package/dist/bin/chunk-tga3hhwk.js +6 -0
- package/dist/bin/chunk-tx0tqq2k.js +8 -0
- package/dist/bin/chunk-vek8a0w6.js +1 -0
- package/dist/bin/chunk-vm3q5gf2.js +3 -0
- package/dist/bin/chunk-y7s5r8y6.js +3 -0
- package/dist/bin/chunk-yv9sth30.js +1 -0
- package/dist/bin/chunk-zmegvjp8.js +581 -0
- package/dist/bin/cli.js +417 -10597
- package/dist/bin/dashboard-server.js +1 -6998
- package/dist/chunk-0ccta6z6.js +6 -0
- package/dist/chunk-0k1cs6pp.js +1 -0
- package/dist/chunk-1egjn203.js +704 -0
- package/dist/chunk-2wx29qga.js +5 -0
- package/dist/chunk-5m5rqmv7.js +1 -0
- package/dist/chunk-8ea5022g.js +114 -0
- package/dist/chunk-8xqn3c0w.js +2 -0
- package/dist/chunk-b4esxhmw.js +2 -0
- package/dist/chunk-b8e5wzwn.js +44 -0
- package/dist/chunk-bggt4n6e.js +2 -0
- package/dist/chunk-cwkg37rs.js +37 -0
- package/dist/chunk-dskvqt67.js +7355 -0
- package/dist/chunk-gahsdmxf.js +1617 -0
- package/dist/chunk-gnv3r2jg.js +1 -0
- package/dist/chunk-hg3c48cp.js +34 -0
- package/dist/chunk-m5azeva3.js +2 -0
- package/dist/chunk-r3qf56b3.js +92 -0
- package/dist/chunk-sdqa9ghv.js +141 -0
- package/dist/chunk-svrvvcm7.js +1 -0
- package/dist/chunk-v0p3hpc3.js +60 -0
- package/dist/chunk-wdzjbecg.js +50 -0
- package/dist/chunk-ww8k3g7j.js +1 -0
- package/dist/chunk-xmqfc62w.js +1 -0
- package/dist/chunk-xrebj679.js +435 -0
- package/dist/chunk-xt3s24vr.js +2 -0
- package/dist/chunk-y21zk0pn.js +4 -0
- package/dist/deploy/attach-credentials.d.ts +57 -0
- package/dist/deploy/index.js +1 -168
- package/dist/deploy/site-ports.d.ts +151 -0
- package/dist/deploy/site-target.d.ts +24 -1
- package/dist/dns/index.js +1 -41
- package/dist/drivers/index.js +1 -154
- package/dist/index.js +100 -6129
- package/dist/protection/index.js +1 -89
- package/dist/push/index.js +1 -509
- package/dist/spend/index.js +1 -173
- package/dist/ui/404.html +16 -548
- package/dist/ui/_stx/chunk.5ddb5087.js +90 -0
- package/dist/ui/_stx/chunk.9fd39d5b.js +86 -0
- package/dist/ui/_stx/chunk.d5301f79.js +119 -0
- package/dist/ui/_stx/crosswind.222ed683.css +1 -0
- package/dist/ui/_stx/crosswind.616c25f3.css +1 -0
- package/dist/ui/_stx/crosswind.80baa894.css +320 -0
- package/dist/ui/_stx/crosswind.906ee17c.css +1 -0
- package/dist/ui/_stx/crosswind.996ed49e.css +1 -0
- package/dist/ui/_stx/crosswind.b7a99a5c.css +1 -0
- package/dist/ui/_stx/router.fa5bc4dc.js +4 -0
- package/dist/ui/_stx/runtime.c696d17a.js +1 -0
- package/dist/ui/_stx/style.448ab192.css +1 -0
- package/dist/ui/accept-invitation.html +2 -1133
- package/dist/ui/access-denied.html +7 -1130
- package/dist/ui/account/automation.html +12 -1134
- package/dist/ui/account/security.html +13 -1136
- package/dist/ui/applications/compose.html +12 -1134
- package/dist/ui/applications/new.html +13 -1137
- package/dist/ui/data/backups.html +91 -1136
- package/dist/ui/data/services.html +89 -1136
- package/dist/ui/data/volumes.html +61 -1136
- package/dist/ui/index.html +60 -1136
- package/dist/ui/infrastructure/topology.html +128 -1135
- package/dist/ui/integrations.html +13 -1138
- package/dist/ui/operations/alerts.html +59 -1136
- package/dist/ui/operations/configuration.html +13 -1135
- package/dist/ui/operations/jobs.html +87 -1136
- package/dist/ui/operations/maintenance.html +12 -1134
- package/dist/ui/operations/observability.html +93 -1136
- package/dist/ui/operations/previews.html +12 -1134
- package/dist/ui/operations/queue.html +12 -1134
- package/dist/ui/operations/regions.html +12 -1134
- package/dist/ui/operations/releases.html +12 -1134
- package/dist/ui/operations/spend.html +65 -1136
- package/dist/ui/operations/workloads.html +115 -1136
- package/dist/ui/robots.txt +2 -0
- package/dist/ui/security.html +24 -1136
- package/dist/ui/server/actions.html +67 -1138
- package/dist/ui/server/activity.html +7 -1130
- package/dist/ui/server/capacity.html +12 -1134
- package/dist/ui/server/database.html +166 -1135
- package/dist/ui/server/deployments.html +60 -1136
- package/dist/ui/server/diagnostics.html +7 -1130
- package/dist/ui/server/firewall.html +286 -1212
- package/dist/ui/server/fleet.html +45 -1136
- package/dist/ui/server/logs.html +34 -1135
- package/dist/ui/server/metrics.html +124 -1140
- package/dist/ui/server/security.html +7 -1130
- package/dist/ui/server/services.html +46 -1133
- package/dist/ui/server/sites.html +287 -1137
- package/dist/ui/server/ssh-keys.html +62 -1135
- package/dist/ui/server/team.html +155 -1136
- package/dist/ui/server/terminal.html +97 -1183
- package/dist/ui/serverless/alarms.html +62 -1135
- package/dist/ui/serverless/assets.html +47 -1132
- package/dist/ui/serverless/cost.html +7 -1130
- package/dist/ui/serverless/data.html +46 -1135
- package/dist/ui/serverless/deployments.html +47 -1132
- package/dist/ui/serverless/firewall.html +7 -1130
- package/dist/ui/serverless/functions.html +50 -1136
- package/dist/ui/serverless/logs.html +34 -1135
- package/dist/ui/serverless/metrics.html +7 -1130
- package/dist/ui/serverless/queues.html +58 -1135
- package/dist/ui/serverless/secrets.html +62 -1136
- package/dist/ui/serverless/traces.html +12 -1134
- package/dist/ui/serverless.html +64 -1137
- package/dist/ui-src/pages/server/firewall.stx +14 -14
- package/package.json +3 -3
- package/dist/chunk-01d86gt1.js +0 -1324
- package/dist/chunk-050r7ycy.js +0 -46516
- package/dist/chunk-29rse4p1.js +0 -12203
- package/dist/chunk-32e7ya18.js +0 -642
- package/dist/chunk-4cjrg98a.js +0 -597
- package/dist/chunk-50jpda9q.js +0 -371
- package/dist/chunk-6a1t7fnq.js +0 -12121
- package/dist/chunk-728jvrr0.js +0 -1701
- package/dist/chunk-7m60qnc8.js +0 -1762
- package/dist/chunk-aa2dkn7b.js +0 -205
- package/dist/chunk-d48vtdj4.js +0 -433
- package/dist/chunk-f55nkpvb.js +0 -8
- package/dist/chunk-fgk8cqew.js +0 -152
- package/dist/chunk-he9a874b.js +0 -769
- package/dist/chunk-hg7rhrgd.js +0 -7274
- package/dist/chunk-k2yyatfw.js +0 -10
- package/dist/chunk-k4px0bnv.js +0 -23
- package/dist/chunk-nsgqsmrh.js +0 -1779
- package/dist/chunk-qr803fye.js +0 -2122
- package/dist/chunk-rds0sy87.js +0 -1771
- package/dist/chunk-rxgchb08.js +0 -42033
- package/dist/chunk-ttz2ra5j.js +0 -12
- package/dist/chunk-v0bahtg2.js +0 -4
- package/dist/chunk-va2yd85b.js +0 -200
- package/dist/chunk-x9s0cqfx.js +0 -13
- package/dist/chunk-zqtpg06c.js +0 -1752
|
@@ -0,0 +1,1617 @@
|
|
|
1
|
+
import{sl as zt}from"./chunk-b8e5wzwn.js";import{Nl as Rr,Ol as Or}from"./chunk-dskvqt67.js";var cE=["edge.requests","edge.egress_gb","function.invocations","function.gb_seconds","build.minutes","compute.instance_hours","storage.gb_hours","object.egress_gb","object.requests","database.gb_hours","database.io_requests","telemetry.ingest_gb","image.transformations"],ot={"edge.requests":{key:"edge.requests",unit:"requests",label:"Edge requests",precision:0},"edge.egress_gb":{key:"edge.egress_gb",unit:"gb",label:"Edge egress",precision:3},"function.invocations":{key:"function.invocations",unit:"invocations",label:"Function invocations",precision:0},"function.gb_seconds":{key:"function.gb_seconds",unit:"gb_seconds",label:"Function compute",precision:2},"build.minutes":{key:"build.minutes",unit:"minutes",label:"Build minutes",precision:2},"compute.instance_hours":{key:"compute.instance_hours",unit:"hours",label:"Instance hours",precision:2},"storage.gb_hours":{key:"storage.gb_hours",unit:"gb_hours",label:"Block storage",precision:2},"object.egress_gb":{key:"object.egress_gb",unit:"gb",label:"Object storage egress",precision:3},"object.requests":{key:"object.requests",unit:"requests",label:"Object storage requests",precision:0},"database.gb_hours":{key:"database.gb_hours",unit:"gb_hours",label:"Database storage",precision:2},"database.io_requests":{key:"database.io_requests",unit:"requests",label:"Database I/O",precision:0},"telemetry.ingest_gb":{key:"telemetry.ingest_gb",unit:"gb",label:"Telemetry ingest",precision:3},"image.transformations":{key:"image.transformations",unit:"count",label:"Image transformations",precision:0}},NE=["notify","block_builds","block_deployments","throttle_requests","suspend_functions","serve_static","suspend_project"],k={notify:0,block_builds:1,block_deployments:2,throttle_requests:3,suspend_functions:4,serve_static:5,suspend_project:6};var lE=1e6;function vs(e){if(e.tiers&&e.tiers.length>0)return e.tiers;return[{upToQuantity:null,microCentsPerUnit:e.microCentsPerUnit??0}]}function Tr(e,t){if(!(e>0))return 0;let r=[...t].sort((E,a)=>{if(E.upToQuantity===null)return 1;if(a.upToQuantity===null)return-1;return E.upToQuantity-a.upToQuantity}),s=e,n=0,i=0;for(let E of r){if(s<=0)break;let d=(E.upToQuantity===null?1/0:E.upToQuantity)-n;if(d<=0)continue;let T=Math.min(s,d);i+=T*E.microCentsPerUnit,s-=T,n+=T}if(s>0){let E=r.reduce((a,d)=>Math.max(a,d.microCentsPerUnit),0);i+=s*E}return i}function ys(e,t,r,s){let n=e.entries.filter((a)=>a.meter===t);if(n.length===0)return;let i=n.filter((a)=>a.provider===r),E=i.length>0?i:n.filter((a)=>a.provider==="*");return E.find((a)=>a.region===s)??E.find((a)=>a.region==null)}function ct(e,t,r=0){let s=ys(e,t.meter,t.provider,t.region),n=Math.max(0,t.quantity);if(!s)return{meter:t.meter,provider:t.provider??"unknown",region:t.region,quantity:n,includedQuantity:0,billableQuantity:n,costCents:0,unpriced:!0};let i=Math.max(0,s.includedQuantity??0),E=Math.max(0,r-i),a=Math.max(0,i-Math.max(0,r)),d=Math.min(n,a),T=n-d,N=vs(s),o=Tr(E+T,N)-Tr(E,N);return{meter:t.meter,provider:s.provider,region:s.region??t.region,quantity:n,includedQuantity:d,billableQuantity:T,costCents:o/1e6,unpriced:!1}}function LE(e,t){let r=new Map,s=[];for(let n of t){let i=`${n.meter}\x00${n.provider??""}\x00${n.region??""}`,E=r.get(i)??0,a=ct(e,n,E);r.set(i,E+a.quantity),s.push(a)}return{currency:e.currency,totalCents:s.reduce((n,i)=>n+i.costCents,0),lines:s,unpricedMeters:[...new Set(s.filter((n)=>n.unpriced).map((n)=>String(n.meter)))]}}function mE(e){return Math.round(e*100)/100}function uE(e,...t){let r=new Map,s=(i)=>`${i.meter}\x00${i.provider}\x00${i.region??""}`;for(let i of e.entries)r.set(s(i),i);let n=e.currency;for(let i of t){n=i.currency||n;for(let E of i.entries)r.set(s(E),E)}return{currency:n,entries:[...r.values()]}}var z=(e)=>Math.round(e*1e6),K=(e)=>Math.round(e*1e6),or={currency:"USD",entries:[{meter:"edge.requests",provider:"aws",microCentsPerUnit:K(0.00075)},{meter:"edge.egress_gb",provider:"aws",includedQuantity:100,tiers:[{upToQuantity:10240,microCentsPerUnit:z(8.5)},{upToQuantity:51200,microCentsPerUnit:z(8)},{upToQuantity:null,microCentsPerUnit:z(6)}]},{meter:"function.invocations",provider:"aws",includedQuantity:1e6,microCentsPerUnit:K(0.002)},{meter:"function.gb_seconds",provider:"aws",includedQuantity:400000,microCentsPerUnit:K(0.00166667)},{meter:"storage.gb_hours",provider:"aws",microCentsPerUnit:K(0.010958904109589041)},{meter:"object.egress_gb",provider:"aws",includedQuantity:100,microCentsPerUnit:z(9)},{meter:"object.requests",provider:"aws",microCentsPerUnit:K(0.000039999999999999996)},{meter:"database.gb_hours",provider:"aws",microCentsPerUnit:K(0.015753424657534248)},{meter:"compute.instance_hours",provider:"aws",microCentsPerUnit:K(4.16)},{meter:"edge.egress_gb",provider:"hetzner",includedQuantity:20480,microCentsPerUnit:z(0.108)},{meter:"object.egress_gb",provider:"hetzner",includedQuantity:1024,microCentsPerUnit:z(0.108)},{meter:"compute.instance_hours",provider:"hetzner",microCentsPerUnit:K(0.95)},{meter:"storage.gb_hours",provider:"hetzner",microCentsPerUnit:K(0.006027397260273972)},{meter:"edge.requests",provider:"hetzner",microCentsPerUnit:0},{meter:"edge.requests",provider:"local",microCentsPerUnit:0},{meter:"edge.egress_gb",provider:"local",microCentsPerUnit:0},{meter:"function.invocations",provider:"local",microCentsPerUnit:0},{meter:"function.gb_seconds",provider:"local",microCentsPerUnit:0},{meter:"build.minutes",provider:"*",includedQuantity:6000,microCentsPerUnit:K(0.8)},{meter:"telemetry.ingest_gb",provider:"*",includedQuantity:5,microCentsPerUnit:z(50)},{meter:"image.transformations",provider:"*",includedQuantity:5000,microCentsPerUnit:K(0.05)}]};var cr=new Map;function _r(e){let t=cr.get(e);if(!t)t=new Intl.DateTimeFormat("en-US",{timeZone:e,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",weekday:"short",hourCycle:"h23"}),cr.set(e,t);return t}var ps={Sun:0,Mon:1,Tue:2,Wed:3,Thu:4,Fri:5,Sat:6};function ye(e){try{return _r(e),!0}catch{return!1}}function Nt(e,t){let r=_r(t).formatToParts(e),s=(n)=>r.find((i)=>i.type===n)?.value??"0";return{year:Number(s("year")),month:Number(s("month")),day:Number(s("day")),hour:Number(s("hour")),minute:Number(s("minute")),second:Number(s("second")),weekday:ps[s("weekday")]??0}}function Nr(e,t){let r=Nt(e,t);return Date.UTC(r.year,r.month-1,r.day,r.hour,r.minute,r.second)-Math.floor(e.getTime()/1000)*1000}function ee(e,t,r,s,n=0){let i=Date.UTC(t,r-1,s,n),E=new Date(i-Nr(new Date(i),e));return new Date(i-Nr(E,e))}function Ds(e,t){return new Date(Date.UTC(e,t-1,1)).toLocaleString("en-US",{month:"long",year:"numeric",timeZone:"UTC"})}function G(e,t,r=new Date){let s=ye(t)?t:"UTC",n=Nt(r,s),i,E,a;if(e==="daily")i=ee(s,n.year,n.month,n.day),E=ee(s,n.year,n.month,n.day+1),a=`${n.year}-${String(n.month).padStart(2,"0")}-${String(n.day).padStart(2,"0")}`;else if(e==="weekly"){let N=(n.weekday+6)%7;i=ee(s,n.year,n.month,n.day-N),E=ee(s,n.year,n.month,n.day-N+7);let o=Nt(i,s);a=`week of ${o.year}-${String(o.month).padStart(2,"0")}-${String(o.day).padStart(2,"0")}`}else i=ee(s,n.year,n.month,1),E=ee(s,n.year,n.month+1,1),a=Ds(n.year,n.month);let d=E.getTime()-i.getTime(),T=Math.max(0,Math.min(d,r.getTime()-i.getTime()));return{start:i.toISOString(),end:E.toISOString(),elapsedMs:T,totalMs:d,label:a}}function gE(e,t,r=new Date){let s=G(e,t,r),n=new Date(new Date(s.start).getTime()-1),i=G(e,t,n);return{...i,elapsedMs:i.totalMs}}function oe(e){let r=(typeof e==="string"?new Date(e):e).getTime();if(!Number.isFinite(r))throw Error("hourBucket requires a valid date");return new Date(Math.floor(r/3600000)*3600000).toISOString()}function RE(e,t,r=9600){let s=Math.floor(new Date(e).getTime()/3600000)*3600000,n=new Date(t).getTime();if(!Number.isFinite(s)||!Number.isFinite(n))throw Error("hourBucketsBetween requires valid dates");let i=[];for(let E=s;E<n&&i.length<r;E+=3600000)i.push(new Date(E).toISOString());return i}import{createHash as bs}from"node:crypto";var pe=1073741824,Lr=[{match:"request.count",meter:"edge.requests"},{match:"traffic.requests",meter:"edge.requests"},{match:"http.request",meter:"edge.requests",field:"count"},{match:"traffic.bytes_out",meter:"edge.egress_gb",scale:1/pe},{match:"storage.egress.day",meter:"object.egress_gb",scale:1/pe},{match:"storage.egress.month",meter:"object.egress_gb",scale:1/pe,cumulative:!0},{match:"storage.egress.day_downloads",meter:"object.requests"},{match:"function.invocation",meter:"function.invocations",field:"count"},{match:"function.duration_ms",meter:"function.gb_seconds",scale:0.001},{match:"build.duration_ms",meter:"build.minutes",scale:0.000016666666666666667},{match:"telemetry.ingest_bytes",meter:"telemetry.ingest_gb",scale:1/pe},{match:"image.transform",meter:"image.transformations",field:"count"}];function ks(e,t){return e.match.endsWith("*")?t.startsWith(e.match.slice(0,-1)):e.match===t}function Fs(e,t=Lr){return t.find((r)=>!r.match.endsWith("*")&&r.match===e)??t.find((r)=>ks(r,e))}function Ps(e,t){let r=t.field??"value",s=r==="count"?1:r==="bytesOut"?e.bytesOut:r==="bytesIn"?e.bytesIn:r==="durationMs"?e.durationMs:e.value;if(s==null||!Number.isFinite(s))return;return s*(t.scale??1)}function Hs(e,t){return bs("sha256").update([t,e.meter,e.organizationId,e.projectId??"",e.environmentId??"",e.resourceId??"",e.timestamp].join("\x00")).digest("hex").slice(0,32)}class _t{resetToleranceRatio;last=new Map;constructor(e=0.999){this.resetToleranceRatio=e}delta(e,t,r=new Date){if(!Number.isFinite(t)||t<0)return;let s=new Date(r).getTime(),n=this.last.get(e);if(!n)return this.last.set(e,{value:t,at:s}),t>0?t:void 0;if(s<n.at)return;if(t<n.value&&t>=n.value*this.resetToleranceRatio)return;if(this.last.set(e,{value:t,at:s}),t>=n.value){let i=t-n.value;return i>0?i:void 0}return t>0?t:void 0}prune(e){let t=e.getTime(),r=0;for(let[s,n]of this.last)if(n.at<t)this.last.delete(s),r++;return r}get size(){return this.last.size}}function lr(e,t){let r=e[t];return typeof r==="string"&&r.length>0?r:void 0}function mr(e,t,r={}){let s=r.mappings??Lr,n=r.counters,i=[];for(let E of t){let a=Fs(E.name,s);if(!a)continue;let d=Ps(E,a);if(d==null)continue;if(a.cumulative){if(!n)continue;let N=[E.projectId,E.environmentId??"",E.resourceId??"",E.name,E.source].join("\x00"),o=n.delta(N,d,E.timestamp);if(o==null)continue;d=o}if(!(d>0))continue;let T={organizationId:e,projectId:E.projectId,environmentId:E.environmentId,resourceId:E.resourceId,provider:lr(E.attributes,"provider")??r.defaultProvider,region:E.region??lr(E.attributes,"region"),meter:a.meter,quantity:d,timestamp:E.timestamp,attributes:{source:E.source,signal:E.name}};i.push({...T,key:Hs(T,E.id)})}return i}function ur(e){let t=new Map;for(let r of e){let s=new Date(Math.floor(new Date(r.timestamp).getTime()/3600000)*3600000).toISOString(),n=[r.organizationId,r.projectId??"",r.environmentId??"",r.resourceId??"",r.provider??"",r.region??"",r.meter,s].join("\x00"),i=t.get(n);if(i)i.quantity+=r.quantity;else t.set(n,{...r,timestamp:s})}return[...t.values()]}import{createHash as ws}from"node:crypto";var B=40,lt=[{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
|
+
`}];import{createHash as Fe,randomBytes as Ms}from"node:crypto";import{chmodSync as Ct,existsSync as Cr,mkdirSync as js,statSync as Bs}from"node:fs";import{dirname as gr}from"node:path";import{Database as $s}from"bun:sqlite";var te=["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"],Lt=["project:read","config:read","deployments:read","runtime:read","runtime:logs","data:read","backups:read","security:read","sources:read","applications:read","billing:read"],Ks={owner:new Set(te),admin:new Set(te.filter((e)=>e!=="users:transfer-ownership")),deployer:new Set([...Lt,"deployments:create","deployments:cancel","deployments:rollback","sources:manage","applications:manage"]),operator:new Set([...Lt,"runtime:restart","backups:create","security:manage","sources:manage","applications:manage"]),viewer:new Set(Lt),auditor:new Set(["project:read","config:read","deployments:read","runtime:read","data:read","backups:read","audit:read","security:read","sources:read","applications:read"])};function De(e){return Ks[e]}function mt(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 be(e){let{membership:t,capability:r,target:s}=e;if(!t||t.status!=="active"||t.organizationId!==s.organizationId)return{allowed:!1,reason:"inactive-membership"};let n=e.grants.filter((a)=>a.organizationId===s.organizationId&&a.membershipId===t.id&&a.capability===r&&mt(a.scope,s)),i=n.find((a)=>a.effect==="deny");if(i)return{allowed:!1,reason:"explicit-deny",inheritedFrom:{kind:"grant",id:i.id,scope:i.scope}};let E=n.find((a)=>a.effect==="allow");if(E)return{allowed:!0,reason:"explicit-allow",inheritedFrom:{kind:"grant",id:E.id,scope:E.scope}};if(!mt(t.scope,s))return{allowed:!1,reason:"scope-mismatch"};if(De(t.roleTemplate).has(r))return{allowed:!0,reason:"role-template",inheritedFrom:{kind:"membership",id:t.id,scope:t.scope}};return{allowed:!1,reason:"missing-capability"}}function xs(e){return te.filter((t)=>be({...e,capability:t}).allowed)}class q extends Error{constructor(e,t,r){super(`${e} ${t} changed since version ${r}`);this.name="OptimisticConcurrencyError"}}class ut extends Error{constructor(e,t){super(`Operation cannot transition from ${e} to ${t}`);this.name="InvalidOperationTransitionError"}}class ke extends Error{constructor(e,t){super(`Control-plane schema version ${e} is newer than supported version ${t}`);this.name="UnsupportedSchemaVersionError"}}function qs(){return Rr("control-plane.sqlite")}var Ar=65536,Ot=16384,Ys=["succeeded","failed","cancelled","timed_out"],zs={queued:["running","cancelled","timed_out"],running:["queued","succeeded","failed","cancelled","timed_out"],succeeded:[],failed:["queued"],cancelled:["queued"],timed_out:["queued"]},Gs=/(?:^|_)(?:authorization|cookie|credential|password|passwd|secret|token|api_?key|private_?key|access_?key)(?:$|_)/i,Ur=/(?:authorization|password|secret|token|api[_-]?key|private[_-]?key|access[_-]?key)\s*[=:]\s*[^\s,;]+/gi;function P(e){if(typeof e!=="string")return{};try{return JSON.parse(e)}catch{return{}}}function O(e){return typeof e==="string"?e:void 0}function At(e,t){if(t&&Gs.test(t))return"[REDACTED]";if(e===null||typeof e==="boolean"||typeof e==="number")return e;if(typeof e==="string")return e.replace(Ur,"[REDACTED]");if(Array.isArray(e))return e.map((r)=>At(r));if(e&&typeof e==="object"){let r={};for(let[s,n]of Object.entries(e))r[s]=At(n,s);return r}return String(e??"")}function h(e,t=Ar){let r=At(e),s=JSON.stringify(r);if(Buffer.byteLength(s)<=t)return r;return{truncated:!0,originalBytes:Buffer.byteLength(s),preview:s.slice(0,Math.max(0,t-128))}}function U(e){return JSON.stringify(h(e))}function gt(e){if(!e)return;let t=e.replace(Ur,"[REDACTED]");return Buffer.byteLength(t)<=Ot?t:`${t.slice(0,Ot-24)}
|
|
1479
|
+
[output truncated]`}function Pe(e){return{id:String(e.id),slug:String(e.slug),name:String(e.name),description:O(e.description),organizationId:O(e.organization_id),desiredConfigHash:O(e.desired_config_hash),version:Number(e.version),createdAt:String(e.created_at),updatedAt:String(e.updated_at)}}function He(e){return{id:String(e.id),projectId:String(e.project_id),slug:String(e.slug),name:String(e.name),kind:String(e.kind),region:O(e.region),desiredState:P(e.desired_state),discoveredState:P(e.discovered_state),version:Number(e.version),createdAt:String(e.created_at),updatedAt:String(e.updated_at)}}function Ke(e){return{id:String(e.id),projectId:String(e.project_id),environmentId:O(e.environment_id),kind:String(e.kind),slug:String(e.slug),name:String(e.name),provider:O(e.provider),providerId:O(e.provider_id),desiredState:P(e.desired_state),discoveredState:P(e.discovered_state),metadata:P(e.metadata),version:Number(e.version),createdAt:String(e.created_at),updatedAt:String(e.updated_at)}}function xe(e){return{id:String(e.id),kind:String(e.kind),externalId:O(e.external_id),displayName:String(e.display_name),metadata:P(e.metadata),disabledAt:O(e.disabled_at),version:Number(e.version),createdAt:String(e.created_at),updatedAt:String(e.updated_at)}}function Q(e){return{id:String(e.id),projectId:O(e.project_id),environmentId:O(e.environment_id),resourceId:O(e.resource_id),actorId:O(e.actor_id),kind:String(e.kind),state:String(e.state),correlationId:String(e.correlation_id),idempotencyKey:O(e.idempotency_key),input:P(e.input),output:P(e.output),error:O(e.error),attempt:Number(e.attempt),priority:Number(e.priority),leaseOwner:O(e.lease_owner),leaseExpiresAt:O(e.lease_expires_at),cancelRequestedAt:O(e.cancel_requested_at),startedAt:O(e.started_at),finishedAt:O(e.finished_at),version:Number(e.version),createdAt:String(e.created_at),updatedAt:String(e.updated_at)}}function Rt(e){return{id:String(e.id),sequence:Number(e.sequence),organizationId:O(e.organization_id),projectId:O(e.project_id),operationId:O(e.operation_id),resourceId:O(e.resource_id),actorId:O(e.actor_id),correlationId:String(e.correlation_id),type:String(e.type),level:String(e.level),payload:P(e.payload),createdAt:String(e.created_at)}}function ce(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 Me(e){return{id:String(e.id),actorKey:String(e.actor_key),name:String(e.name),routeId:String(e.route_id),query:P(e.query),createdAt:String(e.created_at),updatedAt:String(e.updated_at)}}function je(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 Be(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 Ut(e){let t=String(e.scope_type);return{type:t,...t==="organization"?{}:{id:String(e.scope_id)}}}function $e(e){return{id:String(e.id),organizationId:String(e.organization_id),actorId:String(e.actor_id),roleTemplate:String(e.role_template),scope:Ut(e),source:String(e.source??"manual"),status:String(e.status),sessionVersion:Number(e.session_version),lastActiveAt:O(e.last_active_at),createdAt:String(e.created_at),updatedAt:String(e.updated_at)}}function Ne(e,t){let r=O(e.accepted_at),s=O(e.revoked_at),n=String(e.expires_at),i=s?"revoked":r?"accepted":n<=t?"expired":"pending";return{id:String(e.id),organizationId:String(e.organization_id),email:String(e.email),roleTemplate:String(e.role_template),scope:Ut(e),invitedByActorId:O(e.invited_by_actor_id),acceptedByActorId:O(e.accepted_by_actor_id),expiresAt:n,acceptedAt:r,revokedAt:s,state:i,createdAt:String(e.created_at),updatedAt:String(e.updated_at)}}function qe(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:Ut(e),source:String(e.source??"manual"),createdAt:String(e.created_at),updatedAt:String(e.updated_at)}}function _e(e){return{type:e.type,id:e.id??null}}function Qs(e){return Array.from({length:e},()=>"?").join(", ")}function m(e,t,r){return e.run(t,r)}class hr{path;database;nowFn;idFn;constructor(e={}){this.path=e.path??Or(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){js(gr(this.path),{recursive:!0,mode:448});try{Ct(gr(this.path),448)}catch{}}if(this.database=new $s(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(!Cr(e))continue;try{Ct(e,384)}catch{}}}backupTo(e){this.database.run("VACUUM INTO ?",[e]);try{Ct(e,384)}catch{}}migrate(){let e=this.database.query("PRAGMA user_version").get(),t=Number(e?.user_version??0);if(t>B)throw new ke(t,B);if(t===B)return;let r;if(t>0&&this.path!==":memory:")r=`${this.path}.v${t}.${Date.now()}.bak`,this.backupTo(r);try{if(this.database.transaction(()=>{for(let n of lt){if(n.version<=t)continue;this.database.run(n.sql),m(this.database,"INSERT INTO schema_migrations (version, name, applied_at) VALUES (?, ?, ?)",[n.version,n.name,this.now()]),this.database.run(`PRAGMA user_version = ${n.version}`)}}).exclusive(),r)this.setSetting("storage.last_backup",{path:r,createdAt:this.now(),reason:"pre-migration"})}catch(s){let n=r?` Restore from ${r}.`:"";throw Error(`Control-plane migration from schema ${t} failed.${n} ${s instanceof Error?s.message:String(s)}`)}}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(),r=this.now();return m(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,r,r]),this.getProject(t)}getProject(e){let t=this.database.query("SELECT * FROM projects WHERE id = ?").get(e);return t?Pe(t):void 0}getProjectBySlug(e){let t=this.database.query("SELECT * FROM projects WHERE slug = ?").get(e);return t?Pe(t):void 0}listProjects(){return this.database.query("SELECT * FROM projects ORDER BY name COLLATE NOCASE, id").all().map(Pe)}updateProject(e,t,r){let s=this.getProject(e);if(!s||s.version!==t)throw new q("Project",e,t);if(m(this.database,`UPDATE projects SET name = ?, description = ?, organization_id = ?, desired_config_hash = ?, version = version + 1, updated_at = ?
|
|
1480
|
+
WHERE id = ? AND version = ?`,[r.name??s.name,r.description===null?null:r.description??s.description??null,r.organizationId===null?null:r.organizationId??s.organizationId??null,r.desiredConfigHash===null?null:r.desiredConfigHash??s.desiredConfigHash??null,this.now(),e,t]).changes!==1)throw new q("Project",e,t);return this.getProject(e)}createEnvironment(e){let t=e.id??this.idFn(),r=this.now();return m(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,U(e.desiredState??{}),U(e.discoveredState??{}),r,r]),He(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(He)}getEnvironmentBySlug(e,t){let r=this.database.query("SELECT * FROM environments WHERE project_id = ? AND slug = ?").get(e,t);return r?He(r):void 0}createResource(e){let t=e.id??this.idFn(),r=this.now();return m(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,U(e.desiredState??{}),U(e.discoveredState??{}),U(e.metadata??{}),r,r]),Ke(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(Ke)}getResource(e){let t=this.database.query("SELECT * FROM resources WHERE id = ?").get(e);return t?Ke(t):void 0}updateResource(e,t,r){let s=this.getResource(e);if(!s||s.version!==t)throw new q("Resource",e,t);if(m(this.database,`UPDATE resources SET name = ?, provider = ?, provider_id = ?, desired_state = ?, discovered_state = ?, metadata = ?,
|
|
1483
|
+
version = version + 1, updated_at = ? WHERE id = ? AND version = ?`,[r.name??s.name,r.provider===null?null:r.provider??s.provider??null,r.providerId===null?null:r.providerId??s.providerId??null,U(r.desiredState??s.desiredState),U(r.discoveredState??s.discoveredState),U(r.metadata??s.metadata),this.now(),e,t]).changes!==1)throw new q("Resource",e,t);return this.getResource(e)}createActor(e){let t=e.id??this.idFn(),r=this.now();return m(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,U(e.metadata??{}),r,r]),xe(this.database.query("SELECT * FROM actors WHERE id = ?").get(t))}getActorByExternalId(e,t){let r=this.database.query("SELECT * FROM actors WHERE kind = ? AND external_id = ?").get(e,t);return r?xe(r):void 0}getActor(e){let t=this.database.query("SELECT * FROM actors WHERE id = ?").get(e);return t?xe(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 r=e.name.trim();if(!r||r.length>100)throw Error("Organization names must contain 1-100 characters");let s=e.id??this.idFn(),n=this.now();return m(this.database,"INSERT INTO organizations (id, slug, name, created_at, updated_at) VALUES (?, ?, ?, ?, ?)",[s,t,r,n,n]),this.getOrganization(s)}getOrganization(e){let t=this.database.query("SELECT * FROM organizations WHERE id = ?").get(e);return t?Be(t):void 0}getOrganizationBySlug(e){let t=this.database.query("SELECT * FROM organizations WHERE slug = ?").get(e.trim().toLowerCase());return t?Be(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(Be)}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 s=this.database.query("SELECT id FROM projects WHERE id = ? AND organization_id = ?").get(t.id,e);return s?{organizationId:e,projectId:String(s.id)}:void 0}if(t.type==="environment"){let s=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 s?{organizationId:e,projectId:String(s.project_id),environmentId:String(s.id)}:void 0}let r=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 r?{organizationId:e,projectId:String(r.project_id),environmentId:O(r.environment_id),resourceId:String(r.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 r=e.id??this.idFn(),s=this.now();m(this.database,`INSERT INTO organization_memberships (id, organization_id, actor_id, role_template, scope_type, scope_id, source, created_at, updated_at)
|
|
1486
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,[r,e.organizationId,e.actorId,e.roleTemplate,t.type,t.id??null,e.source??"manual",s,s]);let n=this.getMembership(r);return this.appendEvent({organizationId:e.organizationId,actorId:e.performedByActorId,type:"organization.membership.created",payload:{membershipId:r,actorId:e.actorId,roleTemplate:e.roleTemplate,scope:_e(t)}}),n}getMembership(e){let t=this.database.query("SELECT * FROM organization_memberships WHERE id = ?").get(e);return t?$e(t):void 0}getMembershipForActor(e,t){let r=this.database.query("SELECT * FROM organization_memberships WHERE organization_id = ? AND actor_id = ?").get(e,t);return r?$e(r):void 0}listMemberships(e,t={}){let r=t.includeRevoked?"":"AND status = 'active'";return this.database.query(`SELECT * FROM organization_memberships WHERE organization_id = ? ${r} ORDER BY created_at, id`).all(e).map($e)}updateMembership(e){let t=this.getMembership(e.id);if(!t)throw Error("Membership was not found");let r=this.normalizeAuthorizationScope(t.organizationId,e.scope??t.scope);if(e.roleTemplate==="owner"&&r.type!=="organization")throw Error("Owners must be scoped to the organization");if(t.roleTemplate==="owner"&&t.status==="active"&&(e.roleTemplate!=="owner"||r.type!=="organization"))this.assertAnotherOwner(t);return m(this.database,"UPDATE organization_memberships SET role_template = ?, scope_type = ?, scope_id = ?, session_version = session_version + 1, updated_at = ? WHERE id = ?",[e.roleTemplate,r.type,r.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:_e(t.scope)},to:{roleTemplate:e.roleTemplate,scope:_e(r)}}}),this.getMembership(t.id)}revokeMembership(e,t){let r=this.getMembership(e);if(!r)throw Error("Membership was not found");if(r.status==="revoked")return r;if(r.roleTemplate==="owner")this.assertAnotherOwner(r);return m(this.database,"UPDATE organization_memberships SET status = 'revoked', session_version = session_version + 1, updated_at = ? WHERE id = ?",[this.now(),e]),this.appendEvent({organizationId:r.organizationId,actorId:t,type:"organization.membership.revoked",payload:{membershipId:e}}),this.getMembership(e)}touchMembership(e){let t=this.now();m(this.database,"UPDATE organization_memberships SET last_active_at = ?, updated_at = ? WHERE id = ? AND status = ?",[t,t,e,"active"]);let r=this.getMembership(e);if(!r)throw Error("Membership was not found");return r}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 r=this.normalizeAuthorizationScope(e.organizationId,e.scope);if(e.roleTemplate==="owner"&&r.type!=="organization")throw Error("Owners must be scoped to the organization");let s=Ms(32).toString("base64url"),n=Fe("sha256").update(s).digest("hex"),i=e.id??this.idFn(),E=this.now(),a=new Date(this.nowFn().getTime()+Math.min(2592000000,Math.max(60000,e.expiresInMs??604800000))).toISOString();return m(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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,[i,e.organizationId,t,e.roleTemplate,r.type,r.id??null,n,e.invitedByActorId??null,a,E,E]),this.appendEvent({organizationId:e.organizationId,actorId:e.invitedByActorId,type:"organization.invitation.created",payload:{invitationId:i,email:t,roleTemplate:e.roleTemplate,scope:_e(r),expiresAt:a}}),{invitation:this.getInvitation(i),token:s}}getInvitation(e){let t=this.database.query("SELECT * FROM organization_invitations WHERE id = ?").get(e);return t?Ne(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)=>Ne(t,this.now()))}inspectInvitationToken(e){let t=Fe("sha256").update(e).digest("hex"),r=this.database.query("SELECT * FROM organization_invitations WHERE token_hash = ?").get(t);return r?Ne(r,this.now()):void 0}acceptInvitation(e,t){let r=Fe("sha256").update(e).digest("hex");return this.transaction(()=>{let s=this.database.query("SELECT * FROM organization_invitations WHERE token_hash = ?").get(r);if(!s)throw Error("Invitation is invalid");let n=Ne(s,this.now());if(n.state!=="pending")throw Error(`Invitation is ${n.state}`);if(!this.getActor(t))throw Error("Invitation actor was not found");if(m(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(),n.id,this.now()]).changes!==1)throw Error("Invitation is no longer available");let E=this.getMembershipForActor(n.organizationId,t),a=E??this.createMembership({organizationId:n.organizationId,actorId:t,roleTemplate:n.roleTemplate,scope:n.scope,source:"invitation"});if(E&&n.roleTemplate==="owner"&&E.roleTemplate!=="owner")a=this.updateMembership({id:E.id,roleTemplate:"owner",scope:{type:"organization"},actorId:t});else if(E)for(let d of De(n.roleTemplate))this.upsertGrant({organizationId:n.organizationId,membershipId:E.id,effect:"allow",capability:d,scope:n.scope,source:"invitation"});return this.appendEvent({organizationId:n.organizationId,actorId:t,type:"organization.invitation.accepted",payload:{invitationId:n.id,membershipId:a.id}}),{invitation:this.getInvitation(n.id),membership:this.getMembership(a.id)}})}revokeInvitation(e,t){let r=this.getInvitation(e);if(!r)throw Error("Invitation was not found");if(r.acceptedAt)throw Error("Accepted invitations cannot be revoked");if(!r.revokedAt)m(this.database,"UPDATE organization_invitations SET revoked_at = ?, updated_at = ? WHERE id = ?",[this.now(),this.now(),e]);return this.appendEvent({organizationId:r.organizationId,actorId:t,type:"organization.invitation.revoked",payload:{invitationId:e}}),this.getInvitation(e)}reissueInvitation(e,t){let r=this.getInvitation(e);if(!r)throw Error("Invitation was not found");if(r.state==="accepted")throw Error("Accepted invitations cannot be reissued");return this.revokeInvitation(e,t),this.createInvitation({organizationId:r.organizationId,email:r.email,roleTemplate:r.roleTemplate,scope:r.scope,invitedByActorId:t})}upsertGrant(e){if(!te.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 r=this.normalizeAuthorizationScope(e.organizationId,e.scope),s=this.now(),n=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,r.type,r.id??null);if(n)return qe(n);let i=e.id??this.idFn();return m(this.database,`INSERT INTO authorization_grants (id, organization_id, membership_id, effect, capability, scope_type, scope_id, source, created_at, updated_at)
|
|
1488
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,[i,e.organizationId,e.membershipId,e.effect,e.capability,r.type,r.id??null,e.source??"manual",s,s]),m(this.database,"UPDATE organization_memberships SET session_version = session_version + 1, updated_at = ? WHERE id = ?",[s,e.membershipId]),this.appendEvent({organizationId:e.organizationId,actorId:e.actorId,type:"organization.grant.created",payload:{grantId:i,membershipId:e.membershipId,effect:e.effect,capability:e.capability,scope:_e(r)}}),this.getGrant(i)}getGrant(e){let t=this.database.query("SELECT * FROM authorization_grants WHERE id = ?").get(e);return t?qe(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(qe)}removeGrant(e,t){let r=this.getGrant(e);if(!r)return!1;let s=m(this.database,"DELETE FROM authorization_grants WHERE id = ?",[e]).changes===1;if(s)m(this.database,"UPDATE organization_memberships SET session_version = session_version + 1, updated_at = ? WHERE id = ?",[this.now(),r.membershipId]),this.appendEvent({organizationId:r.organizationId,actorId:t,type:"organization.grant.removed",payload:{grantId:e,membershipId:r.membershipId}});return s}createOperation(e){if(e.idempotencyKey){let n=this.database.query("SELECT * FROM operations WHERE idempotency_key = ?").get(e.idempotencyKey);if(n)return Q(n)}let t=e.id??this.idFn(),r=e.correlationId??this.idFn(),s=this.now();try{return this.transaction(()=>{return m(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,r,e.idempotencyKey??null,U(e.input??{}),e.priority??0,s,s]),this.appendEvent({projectId:e.projectId,operationId:t,resourceId:e.resourceId,actorId:e.actorId,correlationId:r,type:"operation.queued",payload:{kind:e.kind}}),this.getOperation(t)})}catch(n){if(e.idempotencyKey){let i=this.database.query("SELECT * FROM operations WHERE idempotency_key = ?").get(e.idempotencyKey);if(i)return Q(i)}throw n}}getOperation(e){let t=this.database.query("SELECT * FROM operations WHERE id = ?").get(e);return t?Q(t):void 0}listOperations(e={}){let t=[],r=[];if(e.projectId)t.push("project_id = ?"),r.push(e.projectId);if(e.state)t.push("state = ?"),r.push(e.state);if(e.kind)t.push("kind = ?"),r.push(e.kind);if(e.before)t.push("created_at < ?"),r.push(e.before);let s=Math.min(500,Math.max(1,e.limit??100));r.push(s);let n=t.length?`WHERE ${t.join(" AND ")}`:"";return this.database.query(`SELECT * FROM operations ${n} ORDER BY created_at DESC, id DESC LIMIT ?`).all(...r).map(Q)}claimNextOperation(e,t=60000,r){let s=this.now(),n=new Date(this.nowFn().getTime()+t).toISOString();return this.transaction(()=>{let i=r?.length?`AND kind IN (${Qs(r.length)})`:"",E=r?[...r]:[],a=this.database.query(`SELECT * FROM operations WHERE state = 'queued' ${i} ORDER BY priority DESC, created_at ASC, id ASC LIMIT 1`).get(...E);if(!a)return;let d=Q(a);if(m(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,n,s,s,d.id,d.version]).changes!==1)return;return this.appendEvent({projectId:d.projectId,operationId:d.id,resourceId:d.resourceId,actorId:d.actorId,correlationId:d.correlationId,type:"operation.running",payload:{attempt:d.attempt+1,workerId:e}}),this.getOperation(d.id)})}claimOperation(e,t,r=60000){let s=this.now(),n=new Date(this.nowFn().getTime()+r).toISOString();return this.transaction(()=>{let i=this.getOperation(e);if(!i||i.state!=="queued")return;if(m(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,n,s,s,i.id,i.version]).changes!==1)return;return this.appendEvent({projectId:i.projectId,operationId:i.id,resourceId:i.resourceId,actorId:i.actorId,correlationId:i.correlationId,type:"operation.running",payload:{attempt:i.attempt+1,workerId:t}}),this.getOperation(i.id)})}transitionOperation(e,t){return this.transaction(()=>{let r=this.getOperation(e);if(!r)throw Error(`Operation ${e} was not found`);let s=t.expectedVersion??r.version;if(r.version!==s)throw new q("Operation",e,s);if(!zs[r.state].includes(t.to))throw new ut(r.state,t.to);let n=this.now(),i=Ys.includes(t.to),E=t.to==="queued";if(m(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,U(t.output??r.output),gt(t.error)??null,E||i?null:t.leaseOwner??r.leaseOwner??null,E||i?null:t.leaseExpiresAt??r.leaseExpiresAt??null,i?n:null,n,e,s]).changes!==1)throw new q("Operation",e,s);return this.appendEvent({projectId:r.projectId,operationId:e,resourceId:r.resourceId,actorId:r.actorId,correlationId:r.correlationId,type:`operation.${t.to}`,level:t.to==="failed"?"error":"info",payload:{from:r.state,to:t.to,error:gt(t.error)??null}}),this.getOperation(e)})}requestCancellation(e){let t=this.now();m(this.database,"UPDATE operations SET cancel_requested_at = ?, updated_at = ?, version = version + 1 WHERE id = ? AND state IN ('queued', 'running')",[t,t,e]);let r=this.getOperation(e);if(!r)throw Error(`Operation ${e} was not found`);return r}reconcileOrphanedOperations(e={}){let t=(e.now??this.nowFn()).toISOString(),r=e.policy??"fail",s=this.database.query("SELECT * FROM operations WHERE state = 'running' AND (lease_expires_at IS NULL OR lease_expires_at <= ?)").all(t).map(Q),n=0,i=0;for(let E of s)if(r==="requeue")this.transitionOperation(E.id,{to:"queued",expectedVersion:E.version,error:"Worker lease expired; operation requeued after restart."}),n++;else this.transitionOperation(E.id,{to:"failed",expectedVersion:E.version,error:"Worker lease expired or dashboard restarted before completion."}),i++;return{requeued:n,failed:i}}appendEvent(e){let t=e.id??this.idFn(),r=e.correlationId??this.idFn(),s=e.organizationId??(e.projectId?O(this.database.query("SELECT organization_id FROM projects WHERE id = ?").get(e.projectId)?.organization_id):void 0);return m(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,s??null,e.projectId??null,e.operationId??null,e.resourceId??null,e.actorId??null,r,e.type,e.level??"info",U(e.payload??{}),this.now()]),Rt(this.database.query("SELECT * FROM events WHERE id = ?").get(t))}listEvents(e={}){let t=[],r=[];if(e.organizationId)t.push("organization_id = ?"),r.push(e.organizationId);if(e.projectId)t.push("project_id = ?"),r.push(e.projectId);if(e.operationId)t.push("operation_id = ?"),r.push(e.operationId);if(e.resourceId)t.push("resource_id = ?"),r.push(e.resourceId);if(e.correlationId)t.push("correlation_id = ?"),r.push(e.correlationId);if(e.afterSequence!==void 0)t.push("sequence > ?"),r.push(e.afterSequence);let s=Math.min(1000,Math.max(1,e.limit??200));r.push(s);let n=t.length?`WHERE ${t.join(" AND ")}`:"";return this.database.query(`SELECT * FROM events ${n} ORDER BY sequence ASC LIMIT ?`).all(...r).map(Rt)}setSetting(e,t){m(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,U(t),this.now()])}getSetting(e){let t=this.database.query("SELECT value FROM settings WHERE key = ?").get(e);return t?P(t.value):void 0}upsertTag(e,t,r="#5a8be0"){let s=t.trim().toLowerCase().replace(/\s+/g,"-");if(!/^[a-z0-9][a-z0-9-]{0,31}$/.test(s))throw Error("Tag names must contain 1-32 letters, numbers, or dashes");if(!/^#[0-9a-f]{6}$/i.test(r))throw Error("Tag colors must be six-digit hex colors");let n=this.now(),i=this.database.query("SELECT * FROM tags WHERE project_id = ? AND normalized_name = ?").get(e,s);if(i)return m(this.database,"UPDATE tags SET name = ?, color = ?, updated_at = ? WHERE id = ?",[t.trim(),r.toLowerCase(),n,String(i.id)]),ce(this.database.query("SELECT * FROM tags WHERE id = ?").get(String(i.id)));let E=this.idFn();return m(this.database,"INSERT INTO tags (id, project_id, name, normalized_name, color, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)",[E,e,t.trim(),s,r.toLowerCase(),n,n]),ce(this.database.query("SELECT * FROM tags WHERE id = ?").get(E))}listTags(e){return this.database.query("SELECT * FROM tags WHERE project_id = ? ORDER BY normalized_name").all(e).map(ce)}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");m(this.database,"INSERT OR IGNORE INTO resource_tags (resource_id, tag_id, created_at) VALUES (?, ?, ?)",[e,t,this.now()])}removeTag(e,t){m(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((r)=>({resourceId:String(r.resource_id),tag:ce(r)}))}saveFilter(e,t,r,s){let n=t.trim();if(!n||n.length>64)throw Error("Saved filter names must contain 1-64 characters");if(!r||r.length>160||/^[a-z][a-z0-9+.-]*:|^\/\//i.test(r))throw Error("Saved filter routes must be local route IDs or paths");let i=U(s);if(i.length>8192)throw Error("Saved filter queries must be smaller than 8 KB");let E=this.database.query("SELECT * FROM saved_filters WHERE actor_key = ? AND name = ?").get(e,n),a=this.now();if(E)return m(this.database,"UPDATE saved_filters SET route_id = ?, query = ?, updated_at = ? WHERE id = ?",[r,i,a,String(E.id)]),Me(this.database.query("SELECT * FROM saved_filters WHERE id = ?").get(String(E.id)));let d=this.idFn();return m(this.database,"INSERT INTO saved_filters (id, actor_key, name, route_id, query, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)",[d,e,n,r,i,a,a]),Me(this.database.query("SELECT * FROM saved_filters WHERE id = ?").get(d))}listSavedFilters(e){return this.database.query("SELECT * FROM saved_filters WHERE actor_key = ? ORDER BY updated_at DESC, name").all(e).map(Me)}deleteSavedFilter(e,t){return m(this.database,"DELETE FROM saved_filters WHERE actor_key = ? AND id = ?",[e,t]).changes===1}recordNavigation(e,t,r){let s=this.now();return m(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,r,s]),je(this.database.query("SELECT * FROM navigation_items WHERE actor_key = ? AND entity_type = ? AND entity_id = ?").get(e,t,r))}setFavorite(e,t,r,s){let n=this.now();return m(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,r,s?1:0,n]),je(this.database.query("SELECT * FROM navigation_items WHERE actor_key = ? AND entity_type = ? AND entity_id = ?").get(e,t,r))}listNavigation(e,t={}){let r=t.favoritesOnly?"AND favorite = 1":"",s=Math.min(100,Math.max(1,t.limit??20));return this.database.query(`SELECT * FROM navigation_items WHERE actor_key = ? ${r} ORDER BY favorite DESC, last_visited_at DESC LIMIT ?`).all(e,s).map(je)}compact(e={}){let t=new Date(this.nowFn().getTime()-(e.eventRetentionDays??90)*86400000).toISOString(),r=new Date(this.nowFn().getTime()-(e.operationRetentionDays??365)*86400000).toISOString(),s=this.transaction(()=>{let n=m(this.database,"DELETE FROM events WHERE created_at < ?",[t]).changes,i=m(this.database,"DELETE FROM operations WHERE created_at < ? AND state IN ('succeeded', 'failed', 'cancelled', 'timed_out')",[r]).changes;return{events:n,operations:i}});if(e.vacuum!==!1)this.database.run("VACUUM");return{deletedEvents:s.events,deletedOperations:s.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((n)=>[n,0]));for(let n of this.database.query("SELECT state, COUNT(*) AS count FROM operations GROUP BY state").all())e[String(n.state)]=Number(n.count);let t=this.database.query("PRAGMA journal_mode").get(),r=this.getSetting("storage.last_backup"),s=r&&!Array.isArray(r)&&typeof r==="object"&&typeof r.createdAt==="string"?r.createdAt:void 0;return{path:this.path,schemaVersion:Number(this.database.query("PRAGMA user_version").get()?.user_version??0),supportedSchemaVersion:B,integrity:this.integrityCheck(),journalMode:String(t?.journal_mode??"unknown"),databaseBytes:this.path!==":memory:"&&Cr(this.path)?Bs(this.path).size:this.database.serialize().byteLength,lastBackupAt:s,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)]=P(t.value);return{format:"ts-cloud-control-plane",schemaVersion:B,exportedAt:this.now(),organizations:this.database.query("SELECT * FROM organizations ORDER BY id").all().map(Be),memberships:this.database.query("SELECT * FROM organization_memberships ORDER BY id").all().map($e),invitations:this.database.query("SELECT * FROM organization_invitations ORDER BY id").all().map((t)=>({...Ne(t,this.now()),tokenHash:String(t.token_hash)})),grants:this.database.query("SELECT * FROM authorization_grants ORDER BY id").all().map(qe),projects:this.database.query("SELECT * FROM projects ORDER BY id").all().map(Pe),environments:this.database.query("SELECT * FROM environments ORDER BY id").all().map(He),resources:this.database.query("SELECT * FROM resources ORDER BY id").all().map(Ke),actors:this.database.query("SELECT * FROM actors ORDER BY id").all().map(xe),operations:this.database.query("SELECT * FROM operations ORDER BY id").all().map(Q),events:this.database.query("SELECT * FROM events ORDER BY sequence").all().map(Rt),settings:e,tags:this.database.query("SELECT * FROM tags ORDER BY id").all().map(ce),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(Me),navigationItems:this.database.query("SELECT * FROM navigation_items ORDER BY actor_key, entity_type, entity_id").all().map(je)}}importSnapshot(e,t={}){if(e.format!=="ts-cloud-control-plane")throw Error("Unsupported control-plane snapshot format");if(e.schemaVersion>B)throw new ke(e.schemaVersion,B);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 s of e.organizations??[])m(this.database,"INSERT INTO organizations (id, slug, name, created_at, updated_at) VALUES (?, ?, ?, ?, ?)",[s.id,s.slug,s.name,s.createdAt,s.updatedAt]);for(let s of e.projects)m(this.database,`INSERT INTO projects (id, slug, name, description, organization_id, desired_config_hash, version, created_at, updated_at)
|
|
1498
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,[s.id,s.slug,s.name,s.description??null,s.organizationId??null,s.desiredConfigHash??null,s.version,s.createdAt,s.updatedAt]);for(let s of e.environments)m(this.database,`INSERT INTO environments (id, project_id, slug, name, kind, region, desired_state, discovered_state, version, created_at, updated_at)
|
|
1499
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,[s.id,s.projectId,s.slug,s.name,s.kind,s.region??null,U(s.desiredState),U(s.discoveredState),s.version,s.createdAt,s.updatedAt]);for(let s of e.resources)m(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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,[s.id,s.projectId,s.environmentId??null,s.kind,s.slug,s.name,s.provider??null,s.providerId??null,U(s.desiredState),U(s.discoveredState),U(s.metadata),s.version,s.createdAt,s.updatedAt]);for(let s of e.actors)m(this.database,`INSERT INTO actors (id, kind, external_id, display_name, metadata, disabled_at, version, created_at, updated_at)
|
|
1501
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,[s.id,s.kind,s.externalId??null,s.displayName,U(s.metadata),s.disabledAt??null,s.version,s.createdAt,s.updatedAt]);for(let s of e.memberships??[])m(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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,[s.id,s.organizationId,s.actorId,s.roleTemplate,s.scope.type,s.scope.id??null,s.status,s.sessionVersion,s.lastActiveAt??null,s.source??"manual",s.createdAt,s.updatedAt]);for(let s of e.invitations??[]){let n=s.tokenHash??Fe("sha256").update(`revoked:${s.id}:${this.idFn()}`).digest("hex");m(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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,[s.id,s.organizationId,s.email,s.roleTemplate,s.scope.type,s.scope.id??null,n,s.invitedByActorId??null,s.acceptedByActorId??null,s.expiresAt,s.acceptedAt??null,s.tokenHash?s.revokedAt??null:s.revokedAt??this.now(),s.createdAt,s.updatedAt])}for(let s of e.grants??[])m(this.database,`INSERT INTO authorization_grants (id, organization_id, membership_id, effect, capability, scope_type, scope_id, source, created_at, updated_at)
|
|
1504
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,[s.id,s.organizationId,s.membershipId,s.effect,s.capability,s.scope.type,s.scope.id??null,s.source??"manual",s.createdAt,s.updatedAt]);for(let s of e.operations)m(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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,[s.id,s.projectId??null,s.environmentId??null,s.resourceId??null,s.actorId??null,s.kind,s.state,s.correlationId,s.idempotencyKey??null,U(s.input),U(s.output),gt(s.error)??null,s.attempt,s.priority,s.leaseOwner??null,s.leaseExpiresAt??null,s.cancelRequestedAt??null,s.startedAt??null,s.finishedAt??null,s.version,s.createdAt,s.updatedAt]);for(let s of e.events)m(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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,[s.sequence,s.id,s.organizationId??null,s.projectId??null,s.operationId??null,s.resourceId??null,s.actorId??null,s.correlationId,s.type,s.level,U(s.payload),s.createdAt]);for(let[s,n]of Object.entries(e.settings))m(this.database,"INSERT INTO settings (key, value, updated_at) VALUES (?, ?, ?)",[s,U(n),this.now()]);for(let s of e.tags??[])m(this.database,"INSERT INTO tags (id, project_id, name, normalized_name, color, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)",[s.id,s.projectId,s.name,s.normalizedName,s.color,s.createdAt,s.updatedAt]);for(let s of e.resourceTags??[])m(this.database,"INSERT INTO resource_tags (resource_id, tag_id, created_at) VALUES (?, ?, ?)",[s.resourceId,s.tagId,s.createdAt]);for(let s of e.savedFilters??[])m(this.database,"INSERT INTO saved_filters (id, actor_key, name, route_id, query, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)",[s.id,s.actorKey,s.name,s.routeId,U(s.query),s.createdAt,s.updatedAt]);for(let s of e.navigationItems??[])m(this.database,"INSERT INTO navigation_items (actor_key, entity_type, entity_id, favorite, last_visited_at, visit_count) VALUES (?, ?, ?, ?, ?, ?)",[s.actorKey,s.entityType,s.entityId,s.favorite?1:0,s.lastVisitedAt,s.visitCount])})}}var Vs=new Set(["domain","domains","hostname","hostnames","url","repository","repo","image","label","labels"]),Ws=new Set(["sha","commit","commitsha","release","releaseid"]);function fr(e,t){if(!e||typeof e!=="object"||Array.isArray(e))return[];let r=[];for(let[s,n]of Object.entries(e)){if(!t.has(s.toLowerCase()))continue;if(typeof n==="string")r.push(n);else if(Array.isArray(n))r.push(...n.filter((i)=>typeof i==="string"))}return r}function ht(e){return e.toLowerCase().trim().replace(/[^a-z0-9._:/@-]+/g," ")}function ft(e,t,r){let s=ht(e),n=ht(t),i=ht(r);if(!s)return 0;if(n===s)return 1000;if(n.startsWith(s))return 800-Math.min(100,n.length-s.length);if(n.includes(s))return 650-Math.min(100,n.indexOf(s));let E=s.split(/\s+/).filter(Boolean);if(E.every((d)=>i.includes(d)))return 400+E.length*10;let a=0;for(let d of s){if(a=i.indexOf(d,a),a<0)return 0;a++}return Math.max(1,180-Math.floor(i.length/20))}function Js(e,t){let r=e.kind.toLowerCase();if(r==="server"||r==="compute")return{type:"server",href:`/server/metrics?env=${encodeURIComponent(t)}`};if(r.includes("database")||["mysql","postgres","postgresql","sqlite","redis"].includes(r))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 Zs(e,t){let r=e.getProject(t.projectId);if(!r)return[];let s=e.listEnvironments(r.id),n=new Map(s.map((o)=>[o.id,o])),i=e.listResourceTags(r.id),E=new Map;for(let o of i){let c=E.get(o.resourceId)??[];c.push({id:o.tag.id,name:o.tag.name,color:o.tag.color}),E.set(o.resourceId,c)}let a=[],d=t.allowedResourceSlugs!==void 0;if(!d){a.push({type:"project",id:r.id,title:r.name,subtitle:r.slug,href:`/?project=${encodeURIComponent(r.slug)}`,tags:[],score:0});for(let o of s)a.push({type:"environment",id:o.id,title:o.name,subtitle:`${r.name} · ${o.kind}`,href:`/?env=${encodeURIComponent(o.slug)}`,environment:o.slug,tags:[],score:0})}let T=e.listResources(r.id).filter((o)=>!t.allowedResourceSlugs||t.allowedResourceSlugs.has(o.slug));for(let o of T){let c=o.environmentId?n.get(o.environmentId):void 0,l=E.get(o.id)??[],_=fr(o.desiredState,Vs),L=[o.name,o.slug,o.kind,o.provider,o.providerId,c?.slug,..._,...l.map((C)=>C.name)].filter(Boolean).join(" "),u=Js(o,c?.slug??s[0]?.slug??"");a.push({type:u.type,id:o.id,title:o.name,subtitle:[o.kind,..._.slice(0,2)].join(" · "),href:u.href,environment:c?.slug,provider:o.provider,tags:l,score:ft(t.query,o.name,L)})}if(!d)for(let o of e.listOperations({projectId:r.id,limit:500})){let c=o.environmentId?n.get(o.environmentId):void 0,l=fr(o.input,Ws),_=o.kind.includes("deploy"),L=l[0]??o.kind,u=[o.kind,o.id,o.correlationId,o.state,...l].join(" ");a.push({type:_?"deployment":"operation",id:o.id,title:L,subtitle:`${o.kind} · ${o.state}`,href:`/server/activity?env=${encodeURIComponent(c?.slug??s[0]?.slug??"")}&operation=${encodeURIComponent(o.id)}`,environment:c?.slug,status:o.state,tags:[],score:ft(t.query,L,u)})}for(let o of a)if(o.score===0)o.score=ft(t.query,o.title,`${o.title} ${o.subtitle} ${o.environment??""} ${o.provider??""}`);let N={service:0,server:1,database:2,deployment:3,environment:4,project:5,operation:6};return a.filter((o)=>o.score>0).sort((o,c)=>c.score-o.score||N[o.type]-N[c.type]||o.title.localeCompare(c.title)||o.id.localeCompare(c.id)).slice(0,Math.min(50,Math.max(1,t.limit??20)))}var le=(e)=>{if(typeof e!=="string")return{};try{return JSON.parse(e)}catch{return{}}},x=(e)=>typeof e==="string"&&e!==""?e:void 0,Ye=(e)=>Number(e)===1,Sr=(e)=>e==null?void 0:Number(e);function Xr(e){return{id:String(e.id),organizationId:String(e.organization_id),projectId:x(e.project_id),environmentId:x(e.environment_id),name:String(e.name),period:String(e.period),timezone:String(e.timezone),currency:String(e.currency),softLimitCents:Sr(e.soft_limit_cents),hardLimitCents:Sr(e.hard_limit_cents),thresholds:le(e.thresholds),meters:le(e.meters),graceSeconds:Number(e.grace_seconds),hysteresisPercent:Number(e.hysteresis_percent),dryRun:Ye(e.dry_run),enabled:Ye(e.enabled),version:Number(e.version),createdAt:String(e.created_at),updatedAt:String(e.updated_at)}}function St(e){return{id:String(e.id),budgetId:String(e.budget_id),organizationId:String(e.organization_id),projectId:x(e.project_id),environmentId:x(e.environment_id),action:String(e.action),state:String(e.state),reason:String(e.reason),restore:le(e.restore),triggeredAtPercent:Number(e.triggered_at_percent),simulated:Ye(e.simulated),appliedAt:x(e.applied_at),releasedAt:x(e.released_at),error:x(e.error),createdAt:String(e.created_at),updatedAt:String(e.updated_at)}}function Ir(e){return{id:String(e.id),organizationId:String(e.organization_id),projectId:x(e.project_id),environmentId:x(e.environment_id),signal:String(e.signal),direction:String(e.direction),observed:Number(e.observed),expected:Number(e.expected),score:Number(e.score),deltaPercent:Number(e.delta_percent),severity:String(e.severity),bucketStart:String(e.bucket_start),evidence:le(e.evidence),acknowledgedAt:x(e.acknowledged_at),createdAt:String(e.created_at)}}function en(e){return{organizationId:String(e.organization_id),projectId:String(e.project_id),environmentId:String(e.environment_id),resourceId:String(e.resource_id),provider:String(e.provider),region:String(e.region),meter:String(e.meter),bucketStart:String(e.bucket_start),quantity:Number(e.quantity),costCents:Number(e.cost_cents),sampleCount:Number(e.sample_count),updatedAt:String(e.updated_at)}}function V(e,t,r){return[e,t??"",r??""].join("/")}var tn=[{atPercent:50,actions:["notify"]},{atPercent:80,actions:["notify"]},{atPercent:100,actions:["notify"],onProjection:!0},{atPercent:100,actions:["notify","block_builds","block_deployments"]},{atPercent:115,actions:["notify","block_builds","block_deployments","throttle_requests"]}];class rn{controlPlane;options;constructor(e,t={}){this.controlPlane=e;this.options=t}now(){return(this.options.now?.()??new Date).toISOString()}get priceBook(){return this.options.priceBook??or}query(e,t=[]){return this.controlPlane.database.query(e).all(...t)}createBudget(e){let t=e.name.trim();if(!t)throw Error("Budgets require a name.");if(e.softLimitCents==null&&e.hardLimitCents==null)throw Error("A budget needs a soft limit, a hard limit, or both.");if(e.softLimitCents!=null&&e.hardLimitCents!=null&&e.softLimitCents>e.hardLimitCents)throw Error("The soft limit must not exceed the hard limit.");let r=e.timezone??"UTC";if(!ye(r))throw Error(`Unknown timezone: ${r}`);let s=(e.thresholds??[...tn]).filter((E)=>Number.isFinite(E.atPercent)&&E.atPercent>=0);if(e.environmentId&&!e.projectId)throw Error("An environment-scoped budget must also name its project.");let n=crypto.randomUUID(),i=this.now();return this.controlPlane.database.run("INSERT INTO spend_budgets (id,organization_id,project_id,environment_id,name,period,timezone,currency,soft_limit_cents,hard_limit_cents,thresholds,meters,grace_seconds,hysteresis_percent,dry_run,enabled,version,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,1,?,?)",[n,e.organizationId,e.projectId??null,e.environmentId??null,t,e.period,r,e.currency??"USD",e.softLimitCents??null,e.hardLimitCents??null,JSON.stringify(s),JSON.stringify(e.meters??[]),Math.max(0,Math.floor(e.graceSeconds??0)),Math.max(0,e.hysteresisPercent??5),e.dryRun?1:0,e.enabled===!1?0:1,i,i]),this.getBudget(n)}getBudget(e){let t=this.query("SELECT * FROM spend_budgets WHERE id = ?",[e])[0];return t?Xr(t):void 0}listBudgets(e){let t=["organization_id = ?"],r=[e.organizationId];if(e.projectId)t.push("(project_id IS NULL OR project_id = ?)"),r.push(e.projectId);if(e.enabledOnly)t.push("enabled = 1");return this.query(`SELECT * FROM spend_budgets WHERE ${t.join(" AND ")} ORDER BY created_at`,r).map(Xr)}budgetsForScope(e,t,r){return this.listBudgets({organizationId:e,enabledOnly:!0}).filter((s)=>{if(s.projectId&&s.projectId!==t)return!1;if(s.environmentId&&s.environmentId!==r)return!1;return!0}).sort((s,n)=>Number(!!s.projectId)-Number(!!n.projectId)||Number(!!s.environmentId)-Number(!!n.environmentId))}updateBudget(e,t,r){let s=this.getBudget(e);if(!s)throw Error("Budget was not found.");if(r!=null&&r!==s.version)throw Error(`Budget was modified concurrently (expected version ${r}, found ${s.version}).`);let n={...s,...t};if(n.softLimitCents==null&&n.hardLimitCents==null)throw Error("A budget needs a soft limit, a hard limit, or both.");if(n.softLimitCents!=null&&n.hardLimitCents!=null&&n.softLimitCents>n.hardLimitCents)throw Error("The soft limit must not exceed the hard limit.");if(t.timezone&&!ye(t.timezone))throw Error(`Unknown timezone: ${t.timezone}`);return this.controlPlane.database.run("UPDATE spend_budgets SET name=?,period=?,timezone=?,currency=?,soft_limit_cents=?,hard_limit_cents=?,thresholds=?,meters=?,grace_seconds=?,hysteresis_percent=?,dry_run=?,enabled=?,version=version+1,updated_at=? WHERE id=?",[n.name,n.period,n.timezone,n.currency??"USD",n.softLimitCents??null,n.hardLimitCents??null,JSON.stringify(n.thresholds??[]),JSON.stringify(n.meters??[]),Math.max(0,Math.floor(n.graceSeconds??0)),Math.max(0,n.hysteresisPercent??5),n.dryRun?1:0,n.enabled===!1?0:1,this.now(),e]),this.getBudget(e)}deleteBudget(e){return this.controlPlane.database.run("DELETE FROM spend_budgets WHERE id = ?",[e]).changes>0}ingestUsage(e){let t={applied:0,duplicates:0,costCents:0,unpricedMeters:[]},r=new Set,s=this.now();return this.controlPlane.database.transaction((i)=>{for(let E of i){if(!(E.quantity>0)||!Number.isFinite(E.quantity))continue;let a=E.key??this.deltaFallbackKey(E);if(this.controlPlane.database.run("INSERT OR IGNORE INTO spend_usage_receipts (key,organization_id,bucket_start,created_at) VALUES (?,?,?,?)",[a,E.organizationId,oe(E.timestamp),s]).changes===0){t.duplicates++;continue}let T={projectId:E.projectId??"",environmentId:E.environmentId??"",resourceId:E.resourceId??"",provider:E.provider??"",region:E.region??""},N=this.periodQuantity(E),o=ct(this.priceBook,{meter:E.meter,quantity:E.quantity,provider:E.provider,region:E.region},N);if(o.unpriced)r.add(String(E.meter));this.controlPlane.database.run(`INSERT INTO spend_usage (organization_id,project_id,environment_id,resource_id,provider,region,meter,bucket_start,quantity,cost_cents,sample_count,updated_at)
|
|
1508
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,1,?)
|
|
1509
|
+
ON CONFLICT(organization_id,project_id,environment_id,resource_id,provider,region,meter,bucket_start) DO UPDATE SET
|
|
1510
|
+
quantity = quantity + excluded.quantity,
|
|
1511
|
+
cost_cents = cost_cents + excluded.cost_cents,
|
|
1512
|
+
sample_count = sample_count + 1,
|
|
1513
|
+
updated_at = excluded.updated_at`,[E.organizationId,T.projectId,T.environmentId,T.resourceId,T.provider,T.region,E.meter,oe(E.timestamp),E.quantity,o.costCents,s]),t.applied++,t.costCents+=o.costCents}})(e),t.unpricedMeters=[...r],t}deltaFallbackKey(e){return ws("sha256").update([e.organizationId,e.projectId??"",e.environmentId??"",e.resourceId??"",e.provider??"",e.region??"",e.meter,e.timestamp,String(e.quantity)].join("\x00")).digest("hex").slice(0,32)}periodQuantity(e){let t=G("monthly","UTC",new Date(e.timestamp)),r=this.query("SELECT COALESCE(SUM(quantity), 0) AS total FROM spend_usage WHERE organization_id = ? AND meter = ? AND provider = ? AND region = ? AND bucket_start >= ? AND bucket_start < ?",[e.organizationId,e.meter,e.provider??"",e.region??"",t.start,t.end])[0];return Number(r?.total??0)}usageClauses(e){let t=["organization_id = ?","bucket_start >= ?","bucket_start < ?"],r=[e.organizationId,e.from,e.to];if(e.projectId)t.push("project_id = ?"),r.push(e.projectId);if(e.environmentId)t.push("environment_id = ?"),r.push(e.environmentId);if(e.resourceId)t.push("resource_id = ?"),r.push(e.resourceId);if(e.meters?.length)t.push(`meter IN (${e.meters.map(()=>"?").join(",")})`),r.push(...e.meters.map(String));if(e.providers?.length)t.push(`provider IN (${e.providers.map(()=>"?").join(",")})`),r.push(...e.providers);return{where:t.join(" AND "),bindings:r}}summarizeUsage(e){let{where:t,bindings:r}=this.usageClauses(e),s=this.query(`SELECT meter, provider, SUM(quantity) AS quantity, SUM(cost_cents) AS cost_cents, SUM(sample_count) AS sample_count
|
|
1514
|
+
FROM spend_usage WHERE ${t} GROUP BY meter, provider ORDER BY SUM(cost_cents) DESC, meter`,r).map((i)=>({meter:String(i.meter),provider:String(i.provider),quantity:Number(i.quantity),costCents:Number(i.cost_cents),sampleCount:Number(i.sample_count)})),n=this.query(`SELECT bucket_start, SUM(cost_cents) AS cost_cents, SUM(quantity) AS quantity
|
|
1515
|
+
FROM spend_usage WHERE ${t} GROUP BY bucket_start ORDER BY bucket_start`,r).map((i)=>({bucketStart:String(i.bucket_start),costCents:Number(i.cost_cents),quantity:Number(i.quantity)}));return{from:e.from,to:e.to,totalCents:s.reduce((i,E)=>i+E.costCents,0),byMeter:s,series:n}}listRollups(e,t=1000){let{where:r,bindings:s}=this.usageClauses(e);return this.query(`SELECT * FROM spend_usage WHERE ${r} ORDER BY bucket_start DESC LIMIT ?`,[...s,Math.max(1,Math.min(1e4,t))]).map(en)}budgetSpendCents(e,t=new Date){let r=G(e.period,e.timezone,t),s=this.summarizeUsage({organizationId:e.organizationId,projectId:e.projectId,environmentId:e.environmentId,meters:e.meters.length>0?e.meters:void 0,from:r.start,to:r.end});return{window:r,summary:s}}pruneUsage(e,t=new Date){let r=new Date(t.getTime()-Math.max(1,e)*86400000).toISOString(),s=this.controlPlane.database.run("DELETE FROM spend_usage WHERE bucket_start < ?",[r]).changes,n=new Date(t.getTime()-604800000).toISOString(),i=this.controlPlane.database.run("DELETE FROM spend_usage_receipts WHERE created_at < ?",[n]).changes;return{rollups:s,receipts:i}}recordDecision(e){this.controlPlane.database.run("INSERT INTO spend_decisions (id,budget_id,level,window_start,window_end,used_percent,projected_percent,actual_cents,projected_cents,actions,simulated,reason,evaluated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)",[crypto.randomUUID(),e.budgetId,e.level,e.window.start,e.window.end,e.usedPercent,e.projectedPercent,e.projection.actualCents,e.projection.projectedCents,JSON.stringify(e.actions),e.simulated?1:0,e.reason,e.evaluatedAt])}listDecisions(e,t=50){return this.query("SELECT * FROM spend_decisions WHERE budget_id = ? ORDER BY evaluated_at DESC LIMIT ?",[e,Math.max(1,Math.min(500,t))]).map((r)=>({id:r.id,budgetId:r.budget_id,level:r.level,window:{start:r.window_start,end:r.window_end},usedPercent:Number(r.used_percent),projectedPercent:Number(r.projected_percent),actualCents:Number(r.actual_cents),projectedCents:Number(r.projected_cents),actions:le(r.actions),simulated:Ye(r.simulated),reason:r.reason,evaluatedAt:r.evaluated_at}))}openEnforcement(e){let t=this.activeEnforcement(e.budget.id,e.action);if(t)return t;let r=crypto.randomUUID(),s=this.now();return this.controlPlane.database.run("INSERT INTO spend_enforcements (id,budget_id,organization_id,project_id,environment_id,action,state,reason,restore,triggered_at_percent,simulated,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)",[r,e.budget.id,e.budget.organizationId,e.budget.projectId??null,e.budget.environmentId??null,e.action,"pending",e.reason,JSON.stringify(h(e.restore??{})),e.triggeredAtPercent,e.simulated?1:0,s,s]),this.getEnforcement(r)}getEnforcement(e){let t=this.query("SELECT * FROM spend_enforcements WHERE id = ?",[e])[0];return t?St(t):void 0}activeEnforcement(e,t){let r=this.query("SELECT * FROM spend_enforcements WHERE budget_id = ? AND action = ? AND state IN ('pending','active','releasing')",[e,t])[0];return r?St(r):void 0}listEnforcements(e){let t=["organization_id = ?"],r=[e.organizationId];if(e.budgetId)t.push("budget_id = ?"),r.push(e.budgetId);if(e.activeOnly)t.push("state IN ('pending','active','releasing')");return this.query(`SELECT * FROM spend_enforcements WHERE ${t.join(" AND ")} ORDER BY created_at DESC`,r).map(St)}transitionEnforcement(e,t,r={}){let s=this.getEnforcement(e);if(!s)throw Error("Enforcement was not found.");let n=this.now();return this.controlPlane.database.run("UPDATE spend_enforcements SET state=?, error=?, restore=?, applied_at=?, released_at=?, updated_at=? WHERE id=?",[t,r.error??null,JSON.stringify(h(r.restore??s.restore)),t==="active"?s.appliedAt??n:s.appliedAt??null,t==="released"?n:s.releasedAt??null,n,e]),this.getEnforcement(e)}recordAnomaly(e){let t=crypto.randomUUID();return this.controlPlane.database.run("INSERT OR IGNORE INTO spend_anomalies (id,organization_id,project_id,environment_id,scope_key,signal,direction,observed,expected,score,delta_percent,severity,bucket_start,evidence,created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",[t,e.organizationId,e.projectId??null,e.environmentId??null,V(e.organizationId,e.projectId,e.environmentId),e.signal,e.direction,e.observed,e.expected,e.score,e.deltaPercent,e.severity,e.bucketStart,JSON.stringify(h(e.evidence??{})),this.now()]).changes>0?this.getAnomaly(t):void 0}getAnomaly(e){let t=this.query("SELECT * FROM spend_anomalies WHERE id = ?",[e])[0];return t?Ir(t):void 0}listAnomalies(e){let t=["organization_id = ?"],r=[e.organizationId];if(e.projectId)t.push("project_id = ?"),r.push(e.projectId);if(e.since)t.push("bucket_start >= ?"),r.push(e.since);if(e.unacknowledgedOnly)t.push("acknowledged_at IS NULL");return this.query(`SELECT * FROM spend_anomalies WHERE ${t.join(" AND ")} ORDER BY bucket_start DESC LIMIT ?`,[...r,Math.max(1,Math.min(1000,e.limit??100))]).map(Ir)}acknowledgeAnomaly(e){return this.controlPlane.database.run("UPDATE spend_anomalies SET acknowledged_at = ? WHERE id = ? AND acknowledged_at IS NULL",[this.now(),e]).changes>0}}function vr(e){if(!(e.totalMs>0))return 1;return Math.min(1,Math.max(0,e.elapsedMs/e.totalMs))}function sn(e){let t=e.window,r=Math.max(1,t.elapsedMs),s=e.actualCents/r,n=e.series??[],i=Math.min(1,Math.max(0,e.recencyWeight??0.6));if(n.length<2||i===0)return s;let E=0.3,a=n[0].costCents;for(let T=1;T<n.length;T++)a=E*n[T].costCents+(1-E)*a;let d=a/3600000;return s*(1-i)+d*i}function nn(e,t){let r=vr(e),s=1-Math.exp(-3*r),n=Math.min(1,t/6);return Math.round(s*n*100)/100}function yr(e){let t=e.window,r=vr(t),s=Math.max(0,e.actualCents),n=sn(e),i=Math.max(0,t.totalMs-t.elapsedMs),E=s+n*i,a=(e.series??[]).filter((T)=>T.costCents>0).length,d={actualCents:s,burnRateCentsPerMs:n,projectedCents:E,elapsedFraction:r,confidence:nn(t,a)};if(e.limitCents!=null&&e.limitCents>0&&n>0){let T=e.limitCents-s;if(T<=0)d.timeToExhaustionMs=0,d.exhaustionAt=new Date(new Date(t.start).getTime()+t.elapsedMs).toISOString();else{let N=T/n;if(N<=i)d.timeToExhaustionMs=N,d.exhaustionAt=new Date(new Date(t.start).getTime()+t.elapsedMs+N).toISOString()}}return d}function Xt(e,t){if(t==null)return 0;if(t<=0)return e>0?1/0:0;return e/t*100}function Le(e){if(e==null)return"";if(e<=0)return"now";let t=Math.floor(e/3600000),r=Math.floor(t/24);if(r>0)return`${r}d ${t%24}h`;if(t>0)return`${t}h ${Math.floor(e%3600000/60000)}m`;return`${Math.max(1,Math.floor(e/60000))}m`}var En=0.35;function Ge(e){return e.hardLimitCents??e.softLimitCents}function an(e,t,r){if(r.some((E)=>E!=="notify"))return"hard_capped";let n=Ge(e),i=e.softLimitCents;if(i!=null&&n!=null&&n>0&&t>=i/n*100)return"soft_capped";if(t>=50)return"warning";return"ok"}function ze(e){return[...new Set(e)].sort((t,r)=>k[t]-k[r])}function dn(e,t,r,s,n){if(n.length===0)return`Spend is at ${r.toFixed(1)}% of the ${t.period} budget.`;let i=n[n.length-1];if(i.projected)return`Projected spend for this ${t.period} window reaches ${i.observedPercent.toFixed(1)}% of the budget (${s.confidence.toFixed(2)} confidence), crossing the ${i.atPercent}% threshold.`;return`Spend reached ${r.toFixed(1)}% of the ${t.period} budget, crossing the ${i.atPercent}% threshold${e==="hard_capped"?" and triggering enforcement":""}.`}function pr(e){let{budget:t,window:r}=e,s=e.now??new Date,n=Ge(t),i=yr({window:r,actualCents:e.actualCents,series:e.series,limitCents:n}),E=Xt(e.actualCents,n),a=Xt(i.projectedCents,n),d=t.graceSeconds*1000,T=d===0||e.breachingSince!=null&&s.getTime()-new Date(e.breachingSince).getTime()>=d,N=i.confidence>=En,o=[],c=new Set;for(let C of[...t.thresholds].sort((g,R)=>g.atPercent-R.atPercent)){let g=C.onProjection===!0,R=g?a:E;if(!(R>=C.atPercent))continue;let S=C.actions.filter((y)=>y!=="notify");if(g&&!N&&S.length>0)continue;o.push({atPercent:C.atPercent,actions:C.actions,projected:g,observedPercent:R});for(let y of C.actions){if(y!=="notify"&&!T)continue;c.add(y)}}let l=ze(c),_=100-t.hysteresisPercent,L=ze((e.activeActions??[]).filter((C)=>{if(c.has(C))return!1;let g=t.thresholds.filter((S)=>S.actions.includes(C)).reduce((S,y)=>S==null?y.atPercent:Math.min(S,y.atPercent),void 0);if(g==null)return!0;return Math.max(E,N?a:0)<=g*(_/100)})),u=an(t,E,l);return{budgetId:t.id,level:u,window:r,projection:i,usedPercent:E,projectedPercent:a,breaches:o,actions:t.dryRun?l.filter((C)=>C==="notify"):l,releases:L,simulated:t.dryRun,reason:dn(u,t,E,i,o),evaluatedAt:s.toISOString()}}function Dr(e){let t=new Set;for(let i of e)for(let E of i.actions)t.add(E);let r=new Set;for(let i of e)for(let E of i.releases)if(!t.has(E))r.add(E);let s={ok:0,warning:1,soft_capped:2,hard_capped:3},n=e.reduce((i,E)=>s[E.level]>s[i]?E.level:i,"ok");return{actions:ze(t),releases:ze(r),level:n}}var Qe={notify:"Send the configured spend notifications.",block_builds:"Refuse new builds. Running builds finish.",block_deployments:"Refuse new deployments. The running release keeps serving.",throttle_requests:"Rate-limit inbound requests at the edge to cap egress and compute.",suspend_functions:"Stop invoking serverless functions; static and cached responses still serve.",serve_static:"Serve the last built static output only; dynamic rendering is off.",suspend_project:"Park all inbound traffic behind a 503. No data is removed."},Tn=["throttle_requests","suspend_functions","serve_static","suspend_project"],on=["production"];function br(e,t,r){let s=Tn.includes(e),n=r.approvalRequiredFor??on,i=r.environmentKind!=null&&n.includes(r.environmentKind),E=r.maxAutomaticAction!=null&&k[e]>k[r.maxAutomaticAction];return{action:e,description:Qe[e],userVisible:s,requiresApproval:E||s&&i,reason:t}}function kr(e,t={}){let r=e.actions.map((n)=>br(n,e.reason,t)),s=[...e.releases].sort((n,i)=>k[i]-k[n]).map((n)=>br(n,"Spend fell back below the threshold that armed this action.",t));return{budgetId:e.budgetId,simulated:e.simulated,apply:r.filter((n)=>!n.requiresApproval),release:s.map((n)=>({...n,requiresApproval:!1})),withheld:r.filter((n)=>n.requiresApproval)}}async function Fr(e,t,r,s,n){let i={applied:[],released:[],failed:[],withheld:s.withheld,simulated:s.simulated};for(let E of s.apply){let a=e.openEnforcement({budget:t,action:E.action,reason:E.reason,triggeredAtPercent:r.usedPercent,simulated:s.simulated});if(a.state==="active")continue;if(s.simulated){i.applied.push(e.transitionEnforcement(a.id,"active",{restore:{simulated:!0}}));continue}try{let d=await n.apply(E.action,{budget:t,decision:r,record:a});i.applied.push(e.transitionEnforcement(a.id,"active",{restore:d}))}catch(d){let T=d instanceof Error?d.message:String(d);e.transitionEnforcement(a.id,"failed",{error:T}),i.failed.push({action:E.action,error:T})}}for(let E of s.release){let a=e.activeEnforcement(t.id,E.action);if(!a)continue;if(s.simulated||a.simulated){i.released.push(e.transitionEnforcement(a.id,"released"));continue}e.transitionEnforcement(a.id,"releasing");try{await n.release(E.action,{budget:t,record:a,restore:a.restore}),i.released.push(e.transitionEnforcement(a.id,"released"))}catch(d){let T=d instanceof Error?d.message:String(d);e.transitionEnforcement(a.id,"releasing",{error:T}),i.failed.push({action:E.action,error:T})}}return i}function Pr(e){return e.filter((t)=>t.state==="active").map((t)=>t.action).sort((t,r)=>k[r]-k[t])[0]}function Hr(e,t){let r=new Set(t.filter((n)=>n.state==="active"&&!n.simulated).map((n)=>n.action)),s={build:["block_builds","suspend_project"],deploy:["block_deployments","suspend_project"],function_invoke:["suspend_functions","serve_static","suspend_project"],request:["suspend_project"]};for(let n of s[e]??[])if(r.has(n))return{allowed:!1,blockedBy:n,reason:Qe[n]};return{allowed:!0}}function It(e){if(e.length===0)return 0;let t=[...e].sort((s,n)=>s-n),r=t.length>>1;return t.length%2===0?(t[r-1]+t[r])/2:t[r]}function cn(e,t){if(e.length===0)return 0;let r=t??It(e);return 1.4826*It(e.map((s)=>Math.abs(s-r)))}function Nn(e,t){let r=It(t),s=cn(t,r);if(s===0)s=Math.max(Math.abs(r)*0.1,Number.EPSILON);return{score:(e-r)/s,center:r,deviation:s}}function ta(e,t=0.3){let r=[],s=e[0]??0;for(let n of e)s=t*n+(1-t)*s,r.push(s);return r}function _n(e,t,r){let s=[];for(let n=t-r;n>=0;n-=r)if(Number.isFinite(e[n]))s.push(e[n]);return s}function ln(e,t={}){let r=Math.max(1,Math.floor(t.seasonLength??24)),s=t.threshold??3.5,n=t.criticalThreshold??s*2,i=Math.max(2,Math.floor(t.minHistory??3)),E=Math.max(0,t.minAbsoluteDelta??0),a=e.map((N)=>N.value),d=[],T=0;for(let N=0;N<e.length;N++){let o=_n(a,N,r);if(o.length<i)continue;let c=a[N];if(!Number.isFinite(c))continue;T++;let{score:l,center:_,deviation:L}=Nn(c,o),u=l>=0?"spike":"drop";if(u==="drop"&&t.detectDrops!==!0)continue;if(Math.abs(l)<s)continue;if(Math.abs(c-_)<E)continue;d.push({bucketStart:e[N].bucketStart,observed:c,expected:_,score:l,direction:u,deltaPercent:_===0?c===0?0:1/0:(c-_)/_*100,severity:Math.abs(l)>=n?"critical":"warning",historySize:o.length,deviation:L})}return{anomalies:d,warmingUp:T===0,evaluated:T}}function Kr(e,t={}){if(e.length===0)return;let r=ln(e,t),s=e[e.length-1].bucketStart;return r.anomalies.find((n)=>n.bucketStart===s)}function Ve(e){if(e==="cost")return{seasonLength:24,threshold:3.5,minHistory:3,minAbsoluteDelta:25};if(e.startsWith("edge.")||e.startsWith("function."))return{seasonLength:168,threshold:4,minHistory:3,minAbsoluteDelta:1000};return{seasonLength:24,threshold:3.5,minHistory:3}}var Ln={low:6,medium:3.5,high:2.5},W=(e)=>typeof e==="string"&&e!==""?e:void 0;function vt(e){return{id:String(e.id),organizationId:String(e.organization_id),projectId:W(e.project_id),environmentId:W(e.environment_id),signal:String(e.signal),enabled:Number(e.enabled)===1,sensitivity:String(e.sensitivity),seasonLength:Number(e.season_length),minAbsoluteDelta:Number(e.min_absolute_delta),detectDrops:Number(e.detect_drops)===1,severity:String(e.severity),version:Number(e.version),createdAt:String(e.created_at),updatedAt:String(e.updated_at)}}function mn(e){return{id:String(e.id),organizationId:String(e.organization_id),projectId:W(e.project_id),signal:W(e.signal),routePattern:W(e.route_pattern),statusCode:e.status_code==null?void 0:Number(e.status_code),reason:String(e.reason),actorId:W(e.actor_id),expiresAt:W(e.expires_at),createdAt:String(e.created_at)}}function un(e,t){if(e==="*"||e==="**")return!0;let r=e.replace(/[.+^${}()|[\]\\]/g,"\\$&").replace(/\*\*/g,"\x01").replace(/\*/g,"[^/]*").replace(/\u0001/g,".*");return new RegExp(`^${r}$`).test(t)}class yt{controlPlane;options;constructor(e,t={}){this.controlPlane=e;this.options=t}now(){return(this.options.now?.()??new Date).toISOString()}query(e,t=[]){return this.controlPlane.database.query(e).all(...t)}upsert(e){let t=e.signal.trim();if(!t)throw Error("An anomaly config needs a signal.");if(e.seasonLength!=null&&(!Number.isInteger(e.seasonLength)||e.seasonLength<1))throw Error("seasonLength must be a positive integer.");let r=V(e.organizationId,e.projectId,e.environmentId),s=this.now(),n=this.query("SELECT * FROM anomaly_configs WHERE scope_key = ? AND signal = ?",[r,t])[0],i=Ve(t);if(n)return this.controlPlane.database.run("UPDATE anomaly_configs SET enabled=?,sensitivity=?,season_length=?,min_absolute_delta=?,detect_drops=?,severity=?,version=version+1,updated_at=? WHERE id=?",[e.enabled===!1?0:1,e.sensitivity??String(n.sensitivity),e.seasonLength??Number(n.season_length),e.minAbsoluteDelta??Number(n.min_absolute_delta),e.detectDrops==null?Number(n.detect_drops):e.detectDrops?1:0,e.severity??String(n.severity),s,String(n.id)]),this.get(String(n.id));let E=crypto.randomUUID();return this.controlPlane.database.run("INSERT INTO anomaly_configs (id,organization_id,project_id,environment_id,scope_key,signal,enabled,sensitivity,season_length,min_absolute_delta,detect_drops,severity,version,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,1,?,?)",[E,e.organizationId,e.projectId??null,e.environmentId??null,r,t,e.enabled===!1?0:1,e.sensitivity??"medium",e.seasonLength??i.seasonLength??24,e.minAbsoluteDelta??i.minAbsoluteDelta??0,e.detectDrops?1:0,e.severity??"warning",s,s]),this.get(E)}get(e){let t=this.query("SELECT * FROM anomaly_configs WHERE id = ?",[e])[0];return t?vt(t):void 0}list(e){let t=["organization_id = ?"],r=[e.organizationId];if(e.projectId)t.push("(project_id IS NULL OR project_id = ?)"),r.push(e.projectId);return this.query(`SELECT * FROM anomaly_configs WHERE ${t.join(" AND ")} ORDER BY signal`,r).map(vt)}delete(e){return this.controlPlane.database.run("DELETE FROM anomaly_configs WHERE id = ?",[e]).changes>0}optionsFor(e,t){let r=[V(e.organizationId,e.projectId,e.environmentId),V(e.organizationId,e.projectId),V(e.organizationId)];for(let s of r){let n=this.query("SELECT * FROM anomaly_configs WHERE scope_key = ? AND signal = ?",[s,t])[0];if(!n)continue;let i=vt(n);return{config:i,enabled:i.enabled,options:{...Ve(t),threshold:Ln[i.sensitivity],seasonLength:i.seasonLength,minAbsoluteDelta:i.minAbsoluteDelta,detectDrops:i.detectDrops}}}return{options:Ve(t),enabled:!0}}silence(e){let t=e.reason.trim();if(!t)throw Error("A silence needs a reason: someone will ask why this stopped alerting.");if(!e.signal&&!e.routePattern&&e.statusCode==null)throw Error("A silence needs at least one of signal, routePattern, or statusCode.");let r=crypto.randomUUID();return this.controlPlane.database.run("INSERT INTO anomaly_silences (id,organization_id,project_id,scope_key,signal,route_pattern,status_code,reason,actor_id,expires_at,created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?)",[r,e.organizationId,e.projectId??null,V(e.organizationId,e.projectId),e.signal??null,e.routePattern??null,e.statusCode??null,t,e.actorId??null,e.expiresAt??null,this.now()]),this.listSilences({organizationId:e.organizationId}).find((s)=>s.id===r)}listSilences(e){let t=["organization_id = ?"],r=[e.organizationId];if(e.projectId)t.push("(project_id IS NULL OR project_id = ?)"),r.push(e.projectId);if(!e.includeExpired)t.push("(expires_at IS NULL OR expires_at > ?)"),r.push(this.now());return this.query(`SELECT * FROM anomaly_silences WHERE ${t.join(" AND ")} ORDER BY created_at DESC`,r).map(mn)}removeSilence(e){return this.controlPlane.database.run("DELETE FROM anomaly_silences WHERE id = ?",[e]).changes>0}isSilenced(e,t){return this.listSilences(e).find((r)=>{if(r.signal&&r.signal!==t.signal)return!1;if(r.statusCode!=null&&r.statusCode!==t.statusCode)return!1;if(r.routePattern){if(!t.route)return!1;if(!un(r.routePattern,t.route))return!1}return!0})}}var ue=[{key:"cost",routeAware:!1,label:"Cost",source:"usage",gapPolicy:"zero",minSamples:0,unit:"cents"},{key:"edge.requests",routeAware:!1,label:"Edge requests",source:"usage",gapPolicy:"zero",minSamples:0,unit:"requests"},{key:"edge.egress_gb",routeAware:!1,label:"Edge egress",source:"usage",gapPolicy:"zero",minSamples:0,unit:"GB"},{key:"function.invocations",routeAware:!1,label:"Function invocations",source:"usage",gapPolicy:"zero",minSamples:0,unit:"invocations"},{key:"http.requests",routeAware:!0,label:"HTTP requests",source:"telemetry",gapPolicy:"zero",minSamples:0,unit:"requests"},{key:"http.errors",routeAware:!0,label:"HTTP 5xx",source:"telemetry",gapPolicy:"zero",minSamples:0,unit:"responses"},{key:"http.error_rate",routeAware:!0,label:"HTTP error rate",source:"telemetry",gapPolicy:"gap",minSamples:20,unit:"ratio",ratio:!0},{key:"http.client_error_rate",routeAware:!0,label:"HTTP 4xx rate",source:"telemetry",gapPolicy:"gap",minSamples:20,unit:"ratio",ratio:!0},{key:"http.latency_p95",routeAware:!0,label:"Latency p95",source:"telemetry",gapPolicy:"gap",minSamples:20,unit:"ms"}];function Dt(e){return ue.find((t)=>t.key===e)}var me=3600000;function Cn(e,t=960){let r=Math.floor(new Date(e.from).getTime()/me)*me,s=new Date(e.to).getTime(),n=[];for(let i=r;i<s&&n.length<t;i+=me)n.push(i);return n}function pt(e,t,r){let s=[],n=0,i=0;for(let E of Cn(t)){let a=r.get(E),d=new Date(E).toISOString();if(!a){s.push({bucketStart:d,value:e.gapPolicy==="zero"?0:Number.NaN});continue}if(a.samples<e.minSamples){s.push({bucketStart:d,value:Number.NaN}),i++;continue}s.push({bucketStart:d,value:a.value}),n++}return{signal:e.key,points:s,populated:n,suppressed:i,unit:e.unit}}class bt{controlPlane;spend;counterCache=new Map;constructor(e,t){this.controlPlane=e;this.spend=t}query(e,t){return this.controlPlane.database.query(e).all(...t)}usageSeries(e,t,r){let s=this.spend.summarizeUsage({organizationId:t.organizationId,projectId:t.projectId,environmentId:t.environmentId,meters:e.key==="cost"?void 0:[e.key],from:r.from,to:r.to}),n=new Map;for(let i of s.series)n.set(new Date(i.bucketStart).getTime(),{value:e.key==="cost"?i.costCents:i.quantity,samples:1});return pt(e,r,n)}requestFilter(e,t){let r=["project_id = ?","kind = 'request'","timestamp >= ?","timestamp < ?"],s=[e.projectId,t.from,t.to];if(e.environmentId)r.push("environment_id = ?"),s.push(e.environmentId);if(e.route)r.push("path_template = ?"),s.push(e.route);return{clauses:r,bindings:s}}counters(e,t){let r=[e.projectId,e.environmentId??"",e.route??"",t.from,t.to].join("\x00"),s=this.counterCache.get(r);if(s)return s;let{clauses:n,bindings:i}=this.requestFilter(e,t),E=this.query(`SELECT
|
|
1516
|
+
CAST(strftime('%s', timestamp) AS INTEGER) / 3600 AS bucket,
|
|
1517
|
+
COUNT(*) AS total,
|
|
1518
|
+
SUM(CASE WHEN status_code >= 500 THEN 1 ELSE 0 END) AS server_errors,
|
|
1519
|
+
SUM(CASE WHEN status_code >= 400 AND status_code < 500 THEN 1 ELSE 0 END) AS client_errors
|
|
1520
|
+
FROM telemetry_records
|
|
1521
|
+
WHERE ${n.join(" AND ")}
|
|
1522
|
+
GROUP BY bucket
|
|
1523
|
+
ORDER BY bucket`,i);return this.counterCache.set(r,E),E}resetCache(){this.counterCache.clear()}telemetrySeries(e,t,r){if(!t.projectId)return{signal:e.key,points:[],populated:0,suppressed:0,unit:e.unit};if(e.key==="http.latency_p95")return this.latencySeries(e,t,r);let s=this.counters(t,r),n=new Map;for(let i of s){let E=Number(i.bucket)*me,a=Number(i.total);if(e.key==="http.requests")n.set(E,{value:a,samples:a});else if(e.key==="http.errors")n.set(E,{value:Number(i.server_errors),samples:a});else if(e.key==="http.error_rate")n.set(E,{value:a>0?Number(i.server_errors)/a:0,samples:a});else if(e.key==="http.client_error_rate")n.set(E,{value:a>0?Number(i.client_errors)/a:0,samples:a})}return pt(e,r,n)}latencySeries(e,t,r){let{clauses:s,bindings:n}=this.requestFilter(t,r),i=this.query(`WITH ranked AS (
|
|
1524
|
+
SELECT
|
|
1525
|
+
CAST(strftime('%s', timestamp) AS INTEGER) / 3600 AS bucket,
|
|
1526
|
+
duration_ms,
|
|
1527
|
+
ROW_NUMBER() OVER (PARTITION BY CAST(strftime('%s', timestamp) AS INTEGER) / 3600 ORDER BY duration_ms) AS position,
|
|
1528
|
+
COUNT(*) OVER (PARTITION BY CAST(strftime('%s', timestamp) AS INTEGER) / 3600) AS samples
|
|
1529
|
+
FROM telemetry_records
|
|
1530
|
+
WHERE ${s.join(" AND ")} AND duration_ms IS NOT NULL
|
|
1531
|
+
)
|
|
1532
|
+
SELECT bucket, MIN(duration_ms) AS value, MAX(samples) AS samples
|
|
1533
|
+
FROM ranked
|
|
1534
|
+
WHERE position >= CAST(samples * 0.95 AS INTEGER)
|
|
1535
|
+
GROUP BY bucket
|
|
1536
|
+
ORDER BY bucket`,n),E=new Map;for(let a of i)E.set(Number(a.bucket)*me,{value:Number(a.value),samples:Number(a.samples)});return pt(e,r,E)}series(e,t,r){let s=Dt(e);if(!s)return{signal:e,points:[],populated:0,suppressed:0,unit:""};return s.source==="usage"?this.usageSeries(s,t,r):this.telemetrySeries(s,t,r)}busiestRoutes(e,t,r=10,s=500){if(!e.projectId)return[];let n=["kind = 'request'","project_id = ?","timestamp >= ?","timestamp < ?","path_template IS NOT NULL"],i=[e.projectId,t.from,t.to];if(e.environmentId)n.push("environment_id = ?"),i.push(e.environmentId);return this.query(`SELECT path_template, COUNT(*) AS total FROM telemetry_records
|
|
1537
|
+
WHERE ${n.join(" AND ")}
|
|
1538
|
+
GROUP BY path_template HAVING total >= ? ORDER BY total DESC LIMIT ?`,[...i,s,Math.max(1,Math.min(50,r))]).map((E)=>String(E.path_template))}}function kt(e){let t={seasonLength:24,threshold:3.5,minHistory:3,minAbsoluteDelta:0,detectDrops:!1};if(e==="cost")return{...t,minAbsoluteDelta:25};if(e==="edge.requests"||e==="http.requests")return{...t,seasonLength:168,threshold:4,minAbsoluteDelta:1000,detectDrops:!0};if(e==="edge.egress_gb")return{...t,seasonLength:168,threshold:4,minAbsoluteDelta:5};if(e==="function.invocations")return{...t,seasonLength:168,threshold:4,minAbsoluteDelta:1000};if(e==="http.errors")return{...t,seasonLength:168,threshold:4,minAbsoluteDelta:25};if(e==="http.error_rate"||e==="http.client_error_rate")return{...t,seasonLength:168,threshold:3.5,minAbsoluteDelta:0.02};if(e==="http.latency_p95")return{...t,seasonLength:168,threshold:4,minAbsoluteDelta:100};return t}function xr(e){let t=kt(e),r=t.seasonLength*(t.minHistory+1);return Math.min(960,Math.max(336,r))}var gn="1.3";function Rn(e){if(e.startsWith("edge.")||e.startsWith("object.egress"))return"Networking";if(e.startsWith("function.")||e.startsWith("compute.")||e.startsWith("build."))return"Compute";if(e.startsWith("storage.")||e.startsWith("object."))return"Storage";if(e.startsWith("database."))return"Databases";if(e.startsWith("telemetry."))return"Management and Governance";return"Other"}function Mr(e){return ot[e]?.label??e}function jr(e){let t=ot[e]?.unit;if(t==="gb")return"GB";if(t==="gb_hours")return"GB-Hours";if(t==="gb_seconds")return"GB-Seconds";if(t==="hours")return"Hours";if(t==="minutes")return"Minutes";if(t==="requests")return"Requests";if(t==="invocations")return"Invocations";return"Count"}function On(e){return`${e.slice(0,10)}T00:00:00.000Z`}function An(e){return new Date(new Date(e).getTime()+86400000).toISOString()}function Un(e){return new Date(new Date(e).getTime()+3600000).toISOString()}function Br(e,t){let r=t.currency??"USD",s=(t.granularity??"daily")==="daily",n=new Map;for(let i of e){let E=s?On(i.bucketStart):i.bucketStart,a=[E,i.meter,i.provider,i.region,i.projectId,i.resourceId].join("\x00"),d=n.get(a);if(d)d.quantity+=i.quantity,d.costCents+=i.costCents;else n.set(a,{rollup:i,quantity:i.quantity,costCents:i.costCents,start:E})}return[...n.values()].sort((i,E)=>i.start.localeCompare(E.start)||String(i.rollup.meter).localeCompare(String(E.rollup.meter))).map(({rollup:i,quantity:E,costCents:a,start:d})=>{let T=Math.round(a)/100,N=i.provider||"ts-cloud";return{FocusVersion:gn,ChargePeriodStart:d,ChargePeriodEnd:s?An(d):Un(d),BillingPeriodStart:t.billingPeriodStart,BillingPeriodEnd:t.billingPeriodEnd,BillingCurrency:r,BilledCost:T,EffectiveCost:T,ListCost:T,ChargeCategory:"Usage",ChargeDescription:`${Mr(i.meter)} on ${N}`,ProviderName:N,PublisherName:N,InvoiceIssuerName:N,ServiceName:Mr(i.meter),ServiceCategory:Rn(String(i.meter)),SkuId:String(i.meter),PricingUnit:jr(String(i.meter)),PricingQuantity:E,ConsumedQuantity:E,ConsumedUnit:jr(String(i.meter)),RegionId:i.region||void 0,ResourceId:i.resourceId||void 0,SubAccountId:i.projectId||void 0,SubAccountName:i.projectId?t.projectNames?.[i.projectId]??i.projectId:void 0,Tags:{...i.environmentId?{environment:i.environmentId}:{},...i.projectId?{project:i.projectId}:{}}}})}function Ft(e){return e.map((t)=>JSON.stringify(t)).join(`
|
|
1539
|
+
`)+(e.length>0?`
|
|
1540
|
+
`:"")}function*da(e,t=500){for(let r=0;r<e.length;r+=t)yield Ft(e.slice(r,r+t))}class Pt{store;counters=new _t;breaching=new Map;anomalyConfigs;signals;constructor(e){this.store=e;this.anomalyConfigs=new yt(e.controlPlane),this.signals=new bt(e.controlPlane,e)}ingestTelemetry(e,t,r){let s=mr(e,t,{counters:this.counters,defaultProvider:r});return this.store.ingestUsage(s)}ingestUsage(e,t=!0){return this.store.ingestUsage(t?ur(e):[...e])}status(e,t=new Date){let{window:r,summary:s}=this.store.budgetSpendCents(e,t),i=this.store.listEnforcements({organizationId:e.organizationId,budgetId:e.id,activeOnly:!0}).filter((a)=>a.state==="active").map((a)=>a.action),E=pr({budget:e,window:r,actualCents:s.totalCents,series:s.series,activeActions:i,breachingSince:this.breaching.get(e.id),now:t});return{budget:e,decision:E,summary:s,activeActions:i,timeToCap:Le(E.projection.timeToExhaustionMs)}}async runCycle(e){let t=e.now??new Date,r=this.store.budgetsForScope(e.organizationId,e.projectId,e.environmentId),s=[],n=[];for(let E of r){let a=this.status(E,t);this.trackBreach(E,a.decision,t),this.store.recordDecision(a.decision),s.push(a)}let i=Dr(s.map((E)=>E.decision));if(e.handlers){let E={environmentKind:e.environmentKind,...e.policy};for(let a of s){let d=kr(a.decision,E);if(d.apply.length===0&&d.release.length===0&&d.withheld.length===0)continue;n.push(await Fr(this.store,a.budget,a.decision,d,e.handlers))}}return{organizationId:e.organizationId,statuses:s,level:i.level,actions:i.actions,releases:i.releases,enforcement:n,anomalies:this.detectAnomalies(e,t),evaluatedAt:t.toISOString()}}trackBreach(e,t,r){if(!t.breaches.some((n)=>n.actions.some((i)=>i!=="notify"))){this.breaching.delete(e.id);return}if(!this.breaching.has(e.id))this.breaching.set(e.id,r.toISOString())}detectAnomalies(e,t=new Date,r={}){let s=new Date(Math.floor(t.getTime()/3600000)*3600000).toISOString(),n={organizationId:e.organizationId,projectId:e.projectId,environmentId:e.environmentId},i=e.signals??ue.map((d)=>d.key),E=[],a;for(let d of i){let T=r.seasonLength?Math.max(336,r.seasonLength*((r.minHistory??3)+1)):xr(d),N={from:new Date(new Date(s).getTime()-T*3600000).toISOString(),to:s};if(E.push(...this.detectSignal(d,n,N,r)),!e.routes)continue;if(!Dt(d)?.routeAware)continue;a??=this.signals.busiestRoutes(n,N);for(let o of a){if(this.anomalyConfigs.isSilenced(n,{signal:d,route:o}))continue;E.push(...this.detectSignal(d,{...n,route:o},N,r,o))}}return this.signals.resetCache(),E}detectSignal(e,t,r,s,n){let i=this.anomalyConfigs.optionsFor(t,e);if(!i.enabled)return[];if(!n&&this.anomalyConfigs.isSilenced(t,{signal:e}))return[];let E=this.signals.series(e,t,r);if(E.points.length===0)return[];let a=Kr(E.points,{...kt(e),...i.options,...s});if(!a)return[];let d=this.store.recordAnomaly({organizationId:t.organizationId,projectId:t.projectId,environmentId:t.environmentId,signal:n?`${e}@${n}`:e,direction:a.direction,observed:a.observed,expected:a.expected,score:a.score,deltaPercent:Number.isFinite(a.deltaPercent)?a.deltaPercent:0,severity:i.config?.severity??a.severity,bucketStart:a.bucketStart,evidence:{historySize:a.historySize,deviation:a.deviation,unit:E.unit,populatedBuckets:E.populated,suppressedBuckets:E.suppressed,route:n??null,lookbackFrom:r.from,lookbackTo:r.to}});return d?[d]:[]}usageReport(e){let t=e.now??new Date,r=G(e.period??"monthly",e.timezone??"UTC",t),s=this.store.summarizeUsage({organizationId:e.organizationId,projectId:e.projectId,environmentId:e.environmentId,from:r.start,to:r.end}),n=this.store.budgetsForScope(e.organizationId,e.projectId,e.environmentId).map((E)=>{let a=this.status(E,t),d=Ge(E);return{id:E.id,name:E.name,period:E.period,limitCents:d,softLimitCents:E.softLimitCents,hardLimitCents:E.hardLimitCents,spentCents:a.decision.projection.actualCents,remainingCents:d==null?null:Math.max(0,d-a.decision.projection.actualCents),usedPercent:a.decision.usedPercent,projectedCents:a.decision.projection.projectedCents,projectedPercent:a.decision.projectedPercent,projectionConfidence:a.decision.projection.confidence,level:a.decision.level,timeToCap:a.timeToCap,activeActions:a.activeActions,dryRun:E.dryRun}}),i=this.store.listEnforcements({organizationId:e.organizationId,activeOnly:!0});return{organizationId:e.organizationId,projectId:e.projectId??null,environmentId:e.environmentId??null,window:{start:r.start,end:r.end,label:r.label},currency:this.store.priceBook.currency,totalCents:s.totalCents,byMeter:s.byMeter,series:s.series,budgets:n,enforcement:{strongestAction:Pr(i)??null,active:i.map((E)=>({id:E.id,budgetId:E.budgetId,action:E.action,state:E.state,reason:E.reason,simulated:E.simulated,appliedAt:E.appliedAt}))},generatedAt:t.toISOString(),currentBucket:oe(t)}}}function ga(e,t,r,s=1440){let n=new Map(e.map((d)=>[d.bucketStart,d.value])),i=Math.floor(new Date(t).getTime()/3600000)*3600000,E=new Date(r).getTime(),a=[];for(let d=i;d<E&&a.length<s;d+=3600000){let T=new Date(d).toISOString();a.push({bucketStart:T,value:n.get(T)??0})}return a}var $r="spend.gates",hn={build:["block_builds","suspend_project"],deploy:["block_deployments","suspend_project"],function_invoke:["suspend_functions","serve_static","suspend_project"],request:["suspend_project"]};function We(e){return`${e.budgetId}:${e.action}`}function fn(e,t){if(e.organizationId!==t.organizationId)return!1;if(e.projectId&&e.projectId!==t.projectId)return!1;if(e.environmentId&&e.environmentId!==t.environmentId)return!1;return!0}function Sn(e,t){if(e.organizationId!==t.organizationId)return!1;if(t.projectId&&e.projectId!==t.projectId)return!1;if(t.environmentId&&e.environmentId!==t.environmentId)return!1;return!0}function Xn(e){let t={...e};for(let r of["projectId","environmentId"])if(t[r]==null)delete t[r];return t}class Ce{controlPlane;options;constructor(e,t={}){this.controlPlane=e;this.options=t}read(){let e=this.controlPlane.getSetting($r);return Array.isArray(e)?e:[]}write(e){this.controlPlane.setSetting($r,e)}list(e){return this.read().filter((t)=>!e||fn(t,e)).sort((t,r)=>k[r.action]-k[t.action])}listUnder(e){return this.read().filter((t)=>Sn(t,e)).sort((t,r)=>k[r.action]-k[t.action])}open(e){let t=e.appliedAt??(this.options.now?.()??new Date).toISOString(),r=Xn({...e,appliedAt:t}),s=this.read().filter((n)=>We(n)!==We(r));return s.push(r),this.write(s),r}close(e,t){let r=this.read(),s=r.filter((n)=>We(n)!==We({budgetId:e,action:t}));if(s.length===r.length)return!1;return this.write(s),!0}closeBudget(e){let t=this.read(),r=t.filter((s)=>s.budgetId!==e);return this.write(r),t.length-r.length}check(e,t){let r=hn[e]??[];for(let s of this.list(t)){if(s.simulated)continue;if(!r.includes(s.action))continue;return{allowed:!1,action:s.action,budgetId:s.budgetId,reason:`${s.reason} ${Qe[s.action]}`}}return{allowed:!0}}strongest(e){return this.list(e).find((t)=>!t.simulated)?.action}}class qr extends Error{operation;verdict;code="spend_cap_exceeded";status=402;constructor(e,t){super(t.reason??`A spend cap is blocking this ${e}.`);this.operation=e;this.verdict=t}}function Ua(e,t,r){let s=e.check(t,r);if(!s.allowed)throw new qr(t,s)}var Yr=["notify","block_builds","block_deployments"];function zr(e){let t=e.throttleFactor??0.5;return{apply:async(n,i)=>{let{budget:E,record:a,decision:d}=i;e.gate.open({budgetId:E.id,action:n,organizationId:E.organizationId,projectId:E.projectId,environmentId:E.environmentId,reason:d.reason,simulated:a.simulated});let T={gated:!0};if(!a.simulated&&!Yr.includes(n)){let N={projectId:E.projectId,environmentId:E.environmentId},o=e.transport;if(n==="throttle_requests"&&o?.throttleRequests)T={...await o.throttleRequests({...N,factor:t}),gated:!0};else if(n==="suspend_functions"&&o?.suspendFunctions)T={...await o.suspendFunctions(N),gated:!0};else if(n==="serve_static"&&o?.serveStatic)T={...await o.serveStatic(N),gated:!0};else if(n==="suspend_project"&&o?.suspendProject)T={...await o.suspendProject(N),gated:!0};else T={gated:!0,unsupported:!0}}return await e.notify?.({action:n,budgetId:E.id,reason:d.reason,simulated:a.simulated,released:!1}),T},release:async(n,i)=>{let{budget:E,record:a,restore:d}=i,T=e.transport;if(!a.simulated&&!Yr.includes(n)&&d.unsupported!==!0){if(n==="throttle_requests"&&T?.restoreRequests)await T.restoreRequests(d);else if(n==="suspend_functions"&&T?.resumeFunctions)await T.resumeFunctions(d);else if(n==="serve_static"&&T?.restoreDynamic)await T.restoreDynamic(d);else if(n==="suspend_project"&&T?.resumeProject)await T.resumeProject(d)}e.gate.close(E.id,n),await e.notify?.({action:n,budgetId:E.id,reason:a.reason,simulated:a.simulated,released:!0})}}}class In{calls=[];record(e,t){return this.calls.push({method:e,input:t}),{recorded:e}}throttleRequests(e){return this.record("throttleRequests",e)}restoreRequests(e){this.record("restoreRequests",e)}suspendFunctions(e){return this.record("suspendFunctions",e)}resumeFunctions(e){this.record("resumeFunctions",e)}serveStatic(e){return this.record("serveStatic",e)}restoreDynamic(e){this.record("restoreDynamic",e)}suspendProject(e){return this.record("suspendProject",e)}resumeProject(e){this.record("resumeProject",e)}}import{createCipheriv as vn,createDecipheriv as yn,createHash as wr,randomBytes as pn}from"node:crypto";var F=(e)=>{if(typeof e!=="string")return{};try{return JSON.parse(e)}catch{return{}}},f=(e)=>typeof e==="string"?e:void 0,xt=(e)=>Number(e)===1,M=(e,t,r,s)=>{let n=Number(e);return Number.isFinite(n)?Math.min(s,Math.max(r,Math.floor(n))):t},Gr=(e)=>wr("sha256").update(e).digest("hex").slice(0,16);function Qr(e){return{id:String(e.id),projectId:String(e.project_id),environmentId:f(e.environment_id),resourceId:f(e.resource_id),name:String(e.name),kind:String(e.kind),target:String(e.target),config:F(e.config),intervalSeconds:Number(e.interval_seconds),timeoutSeconds:Number(e.timeout_seconds),failureThreshold:Number(e.failure_threshold),recoveryThreshold:Number(e.recovery_threshold),regions:F(e.regions),enabled:xt(e.enabled),version:Number(e.version),createdAt:String(e.created_at),updatedAt:String(e.updated_at)}}function Vr(e){return{id:String(e.id),checkId:String(e.check_id),status:String(e.status),agent:String(e.agent),region:f(e.region),statusCode:e.status_code==null?void 0:Number(e.status_code),message:f(e.message),timings:F(e.timings),checkedAt:String(e.checked_at)}}function Wr(e){return{id:String(e.id),projectId:String(e.project_id),environmentId:f(e.environment_id),resourceId:f(e.resource_id),healthCheckId:f(e.health_check_id),name:String(e.name),signal:String(e.signal),operator:String(e.operator),threshold:e.threshold==null?void 0:Number(e.threshold),recoveryThreshold:e.recovery_threshold==null?void 0:Number(e.recovery_threshold),windowMs:Number(e.window_ms),consecutive:Number(e.consecutive),recoveryConsecutive:Number(e.recovery_consecutive),noDataPolicy:String(e.no_data_policy),severity:String(e.severity),groupBy:F(e.group_by),labels:F(e.labels),enabled:xt(e.enabled),version:Number(e.version),createdAt:String(e.created_at),updatedAt:String(e.updated_at)}}function Ht(e){return{id:String(e.id),ruleId:String(e.rule_id),projectId:String(e.project_id),environmentId:f(e.environment_id),resourceId:f(e.resource_id),dedupKey:String(e.dedup_key),groupKey:String(e.group_key),state:String(e.state),severity:String(e.severity),title:String(e.title),evidence:F(e.evidence),failureCount:Number(e.failure_count),recoveryCount:Number(e.recovery_count),occurrenceCount:Number(e.occurrence_count),ownerActorId:f(e.owner_actor_id),acknowledgedByActorId:f(e.acknowledged_by_actor_id),acknowledgedAt:f(e.acknowledged_at),firstSeenAt:String(e.first_seen_at),lastSeenAt:String(e.last_seen_at),firingAt:f(e.firing_at),resolvedAt:f(e.resolved_at),silencedUntil:f(e.silenced_until),updatedAt:String(e.updated_at)}}function Jr(e){return{id:String(e.id),organizationId:String(e.organization_id),name:String(e.name),kind:String(e.kind),config:F(e.config),credentialFingerprint:f(e.credential_fingerprint),hasCredential:!!e.credential_ciphertext,status:String(e.status),version:Number(e.version),lastTestedAt:f(e.last_tested_at),lastError:f(e.last_error),createdAt:String(e.created_at),updatedAt:String(e.updated_at)}}function Zr(e){return{id:String(e.id),organizationId:String(e.organization_id),name:String(e.name),priority:Number(e.priority),matcher:F(e.matcher),channelIds:F(e.channel_ids),quietHours:e.quiet_hours?F(e.quiet_hours):void 0,groupWaitSeconds:Number(e.group_wait_seconds),reminderSeconds:e.reminder_seconds==null?void 0:Number(e.reminder_seconds),escalation:F(e.escalation),template:f(e.template),rateLimitPerMinute:Number(e.rate_limit_per_minute),enabled:xt(e.enabled),version:Number(e.version),createdAt:String(e.created_at),updatedAt:String(e.updated_at)}}function Kt(e){return{id:String(e.id),alertId:f(e.alert_id),channelId:String(e.channel_id),routeId:f(e.route_id),eventType:String(e.event_type),idempotencyKey:String(e.idempotency_key),state:String(e.state),attempt:Number(e.attempt),maxAttempts:Number(e.max_attempts),nextAttemptAt:f(e.next_attempt_at),responseStatus:e.response_status==null?void 0:Number(e.response_status),error:f(e.error),payload:F(e.payload),createdAt:String(e.created_at),updatedAt:String(e.updated_at),deliveredAt:f(e.delivered_at)}}class Dn{controlPlane;options;key;constructor(e,t){this.controlPlane=e;this.options=t;this.key=wr("sha256").update(t.encryptionKey).digest()}now(){return(this.options.now?.()??new Date).toISOString()}encrypt(e){let t=pn(12),r=vn("aes-256-gcm",this.key,t),s=Buffer.concat([r.update(e,"utf8"),r.final()]);return`v1.${t.toString("base64url")}.${r.getAuthTag().toString("base64url")}.${s.toString("base64url")}`}decrypt(e){let[,t,r,s]=e.split("."),n=yn("aes-256-gcm",this.key,Buffer.from(t,"base64url"));return n.setAuthTag(Buffer.from(r,"base64url")),Buffer.concat([n.update(Buffer.from(s,"base64url")),n.final()]).toString("utf8")}createHealthCheck(e){if(!e.name.trim())throw Error("Health checks require a name.");if(!["http","tcp","command"].includes(e.kind))throw Error("Unsupported health check kind.");if(e.kind==="http"&&!/^https?:\/\//.test(e.target)||e.kind==="tcp"&&!/^[a-zA-Z0-9.-]+$/.test(e.target)||e.kind==="command"&&!e.config.command?.length)throw Error("Health check target is invalid.");let t=crypto.randomUUID(),r=this.now();return this.controlPlane.database.run("INSERT INTO health_checks (id,project_id,environment_id,resource_id,name,kind,target,config,interval_seconds,timeout_seconds,failure_threshold,recovery_threshold,regions,enabled,version,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",[t,e.projectId,e.environmentId??null,e.resourceId??null,e.name.trim().slice(0,100),e.kind,e.target,JSON.stringify(e.config),M(e.intervalSeconds,60,10,86400),M(e.timeoutSeconds,10,1,300),M(e.failureThreshold,3,1,100),M(e.recoveryThreshold,2,1,100),JSON.stringify(e.regions.slice(0,20)),e.enabled?1:0,1,r,r]),this.getHealthCheck(t)}getHealthCheck(e){let t=this.controlPlane.database.query("SELECT * FROM health_checks WHERE id=?").get(e);return t?Qr(t):void 0}listHealthChecks(e,t){return this.controlPlane.database.query(`SELECT * FROM health_checks WHERE project_id=?${t?" AND environment_id=?":""} ORDER BY updated_at DESC`).all(e,...t?[t]:[]).map(Qr)}setHealthCheckEnabled(e,t){this.controlPlane.database.run("UPDATE health_checks SET enabled=?,version=version+1,updated_at=? WHERE id=?",[t?1:0,this.now(),e]);let r=this.getHealthCheck(e);if(!r)throw Error("Health check was not found.");return r}appendHealthResult(e){let t=crypto.randomUUID();return this.controlPlane.database.run("INSERT INTO health_results (id,check_id,status,agent,region,status_code,message,timings,checked_at) VALUES (?,?,?,?,?,?,?,?,?)",[t,e.checkId,e.status,e.agent.slice(0,100),e.region??null,e.statusCode??null,e.message?.slice(0,1000)??null,JSON.stringify(e.timings),e.checkedAt]),Vr(this.controlPlane.database.query("SELECT * FROM health_results WHERE id=?").get(t))}listHealthResults(e,t=100){return this.controlPlane.database.query("SELECT * FROM health_results WHERE check_id=? ORDER BY checked_at DESC LIMIT ?").all(e,Math.min(1000,Math.max(1,t))).map(Vr)}createRule(e){if(!e.name.trim())throw Error("Alert rules require a name.");if(e.operator!=="unhealthy"&&e.threshold==null)throw Error("Metric alert rules require a threshold.");let t=crypto.randomUUID(),r=this.now();return this.controlPlane.database.run("INSERT INTO alert_rules (id,project_id,environment_id,resource_id,health_check_id,name,signal,operator,threshold,recovery_threshold,window_ms,consecutive,recovery_consecutive,no_data_policy,severity,group_by,labels,enabled,version,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",[t,e.projectId,e.environmentId??null,e.resourceId??null,e.healthCheckId??null,e.name.trim().slice(0,120),e.signal,e.operator,e.threshold??null,e.recoveryThreshold??null,M(e.windowMs,300000,1e4,2678400000),M(e.consecutive,3,1,100),M(e.recoveryConsecutive,2,1,100),e.noDataPolicy,e.severity,JSON.stringify(e.groupBy.slice(0,20)),JSON.stringify(e.labels),e.enabled?1:0,1,r,r]),this.getRule(t)}getRule(e){let t=this.controlPlane.database.query("SELECT * FROM alert_rules WHERE id=?").get(e);return t?Wr(t):void 0}listRules(e,t){return this.controlPlane.database.query(`SELECT * FROM alert_rules WHERE project_id=?${t?" AND environment_id=?":""} ORDER BY updated_at DESC`).all(e,...t?[t]:[]).map(Wr)}setRuleEnabled(e,t){this.controlPlane.database.run("UPDATE alert_rules SET enabled=?,version=version+1,updated_at=? WHERE id=?",[t?1:0,this.now(),e]);let r=this.getRule(e);if(!r)throw Error("Alert rule was not found.");return r}getAlertByDedup(e){let t=this.controlPlane.database.query("SELECT * FROM alerts WHERE dedup_key=?").get(e);return t?Ht(t):void 0}getAlert(e){let t=this.controlPlane.database.query("SELECT * FROM alerts WHERE id=?").get(e);return t?Ht(t):void 0}listAlerts(e,t={}){let r=["project_id=?"],s=[e];if(t.environmentId)r.push("environment_id=?"),s.push(t.environmentId);if(t.states?.length)r.push(`state IN (${t.states.map(()=>"?").join(",")})`),s.push(...t.states);return this.controlPlane.database.query(`SELECT * FROM alerts WHERE ${r.join(" AND ")} ORDER BY updated_at DESC LIMIT ?`).all(...s,Math.min(1000,t.limit??200)).map(Ht)}saveAlert(e,t,r){return this.controlPlane.database.run("INSERT INTO alerts (id,rule_id,project_id,environment_id,resource_id,dedup_key,group_key,state,severity,title,evidence,failure_count,recovery_count,occurrence_count,owner_actor_id,acknowledged_by_actor_id,acknowledged_at,first_seen_at,last_seen_at,firing_at,resolved_at,silenced_until,updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) ON CONFLICT(id) DO UPDATE SET state=excluded.state,severity=excluded.severity,title=excluded.title,evidence=excluded.evidence,failure_count=excluded.failure_count,recovery_count=excluded.recovery_count,occurrence_count=excluded.occurrence_count,owner_actor_id=excluded.owner_actor_id,acknowledged_by_actor_id=excluded.acknowledged_by_actor_id,acknowledged_at=excluded.acknowledged_at,last_seen_at=excluded.last_seen_at,firing_at=excluded.firing_at,resolved_at=excluded.resolved_at,silenced_until=excluded.silenced_until,updated_at=excluded.updated_at",[e.id,e.ruleId,e.projectId,e.environmentId??null,e.resourceId??null,e.dedupKey,e.groupKey,e.state,e.severity,e.title,JSON.stringify(e.evidence),e.failureCount,e.recoveryCount,e.occurrenceCount,e.ownerActorId??null,e.acknowledgedByActorId??null,e.acknowledgedAt??null,e.firstSeenAt,e.lastSeenAt,e.firingAt??null,e.resolvedAt??null,e.silencedUntil??null,e.updatedAt]),this.controlPlane.database.run("INSERT INTO alert_events (id,alert_id,type,actor_id,payload,created_at) VALUES (?,?,?,?,?,?)",[crypto.randomUUID(),e.id,t,r??null,JSON.stringify({state:e.state,severity:e.severity,evidence:e.evidence}),e.updatedAt]),this.getAlert(e.id)}acknowledge(e,t){let r=this.getAlert(e);if(!r)throw Error("Alert was not found.");let s=this.now();return this.saveAlert({...r,acknowledgedByActorId:t,acknowledgedAt:s,updatedAt:s},"acknowledged",t)}assign(e,t,r){let s=this.getAlert(e);if(!s)throw Error("Alert was not found.");let n=this.now();return this.saveAlert({...s,ownerActorId:t,updatedAt:n},"assigned",r)}silenceAlert(e,t,r){let s=this.getAlert(e);if(!s)throw Error("Alert was not found.");let n=new Date(t);if(!Number.isFinite(n.getTime())||n<=new Date)throw Error("Silence end must be in the future.");let i=this.now();return this.saveAlert({...s,state:"silenced",silencedUntil:n.toISOString(),updatedAt:i},"silenced",r)}createSilence(e){let t=new Date(e.startsAt),r=new Date(e.endsAt);if(!Number.isFinite(t.getTime())||!Number.isFinite(r.getTime())||t>=r)throw Error("Silence window is invalid.");try{new Intl.DateTimeFormat("en",{timeZone:e.timezone}).format(t)}catch{throw Error("Silence timezone is invalid.")}let s=crypto.randomUUID();return this.controlPlane.database.run("INSERT INTO alert_silences (id,project_id,environment_id,resource_id,matcher,reason,starts_at,ends_at,timezone,created_by_actor_id,created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?)",[s,e.projectId,e.environmentId??null,e.resourceId??null,JSON.stringify(e.matcher??{}),e.reason.slice(0,500),t.toISOString(),r.toISOString(),e.timezone,e.actorId??null,this.now()]),s}isSilenced(e,t,r){return this.controlPlane.database.query("SELECT * FROM alert_silences WHERE project_id=? AND starts_at<=? AND ends_at>?").all(e.projectId,r,r).some((n)=>{if(n.environment_id&&n.environment_id!==e.environmentId)return!1;if(n.resource_id&&n.resource_id!==e.resourceId)return!1;let i=F(n.matcher);return Object.entries(i).every(([E,a])=>t[E]===a)})}createChannel(e){if(!e.name.trim())throw Error("Notification channels require a name.");let t=crypto.randomUUID(),r=this.now(),s=e.credential?.trim();return this.controlPlane.database.run("INSERT INTO notification_channels (id,organization_id,name,kind,config,credential_ciphertext,credential_fingerprint,status,version,created_by_actor_id,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)",[t,e.organizationId,e.name.trim().slice(0,100),e.kind,JSON.stringify(e.config??{}),s?this.encrypt(s):null,s?Gr(s):null,"active",1,e.actorId??null,r,r]),this.getChannel(t)}getChannel(e){let t=this.controlPlane.database.query("SELECT * FROM notification_channels WHERE id=?").get(e);return t?Jr(t):void 0}channelCredential(e){let t=this.controlPlane.database.query("SELECT credential_ciphertext FROM notification_channels WHERE id=?").get(e);return t?.credential_ciphertext?this.decrypt(String(t.credential_ciphertext)):void 0}listChannels(e){return this.controlPlane.database.query("SELECT * FROM notification_channels WHERE organization_id=? ORDER BY updated_at DESC").all(e).map(Jr)}rotateChannel(e,t){let r=this.now();this.controlPlane.database.run("UPDATE notification_channels SET credential_ciphertext=?,credential_fingerprint=?,status='active',last_error=NULL,version=version+1,updated_at=? WHERE id=?",[this.encrypt(t),Gr(t),r,e]);let s=this.getChannel(e);if(!s)throw Error("Notification channel was not found.");return s}setChannelStatus(e,t,r){this.controlPlane.database.run("UPDATE notification_channels SET status=?,last_error=?,updated_at=? WHERE id=?",[t,r?.slice(0,1000)??null,this.now(),e]);let s=this.getChannel(e);if(!s)throw Error("Notification channel was not found.");return s}markChannelTest(e,t,r){let s=this.now();return this.controlPlane.database.run("UPDATE notification_channels SET status=?,last_tested_at=?,last_error=?,updated_at=? WHERE id=?",[t?"active":"failing",s,r?.slice(0,1000)??null,s,e]),this.getChannel(e)}createRoute(e){let t=crypto.randomUUID(),r=this.now();return this.controlPlane.database.run("INSERT INTO notification_routes (id,organization_id,name,priority,matcher,channel_ids,quiet_hours,group_wait_seconds,reminder_seconds,escalation,template,rate_limit_per_minute,enabled,version,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",[t,e.organizationId,e.name.trim().slice(0,100),e.priority,JSON.stringify(e.matcher),JSON.stringify(e.channelIds),e.quietHours?JSON.stringify(e.quietHours):null,M(e.groupWaitSeconds,30,0,86400),e.reminderSeconds??null,JSON.stringify(e.escalation),e.template?.trim().slice(0,2000)??null,M(e.rateLimitPerMinute,60,1,1e4),e.enabled?1:0,1,r,r]),this.getRoute(t)}getRoute(e){let t=this.controlPlane.database.query("SELECT * FROM notification_routes WHERE id=?").get(e);return t?Zr(t):void 0}listRoutes(e){return this.controlPlane.database.query("SELECT * FROM notification_routes WHERE organization_id=? ORDER BY priority DESC,created_at ASC").all(e).map(Zr)}countRouteDeliveries(e,t){return Number(this.controlPlane.database.query("SELECT COUNT(*) count FROM notification_deliveries WHERE route_id=? AND created_at>=?").get(e,t)?.count??0)}setRouteEnabled(e,t){this.controlPlane.database.run("UPDATE notification_routes SET enabled=?,version=version+1,updated_at=? WHERE id=?",[t?1:0,this.now(),e]);let r=this.getRoute(e);if(!r)throw Error("Notification route was not found.");return r}createDelivery(e){let t=this.controlPlane.database.query("SELECT * FROM notification_deliveries WHERE idempotency_key=?").get(e.idempotencyKey);if(t)return Kt(t);let r=crypto.randomUUID(),s=this.now();return this.controlPlane.database.run("INSERT INTO notification_deliveries (id,alert_id,channel_id,route_id,event_type,idempotency_key,state,attempt,max_attempts,next_attempt_at,payload,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)",[r,e.alertId??null,e.channelId,e.routeId??null,e.eventType,e.idempotencyKey,"pending",0,M(e.maxAttempts,3,1,10),e.nextAttemptAt??null,JSON.stringify(e.payload),s,s]),this.getDelivery(r)}getDelivery(e){let t=this.controlPlane.database.query("SELECT * FROM notification_deliveries WHERE id=?").get(e);return t?Kt(t):void 0}listDeliveries(e={}){let t=[],r=[];if(e.alertId)t.push("alert_id=?"),r.push(e.alertId);if(e.states?.length)t.push(`state IN (${e.states.map(()=>"?").join(",")})`),r.push(...e.states);return this.controlPlane.database.query(`SELECT * FROM notification_deliveries${t.length?" WHERE "+t.join(" AND "):""} ORDER BY updated_at DESC LIMIT ?`).all(...r,Math.min(1000,e.limit??200)).map(Kt)}updateDelivery(e,t){return this.controlPlane.database.run("UPDATE notification_deliveries SET state=?,attempt=?,next_attempt_at=?,response_status=?,error=?,delivered_at=?,updated_at=? WHERE id=?",[t.state,t.attempt,t.nextAttemptAt??null,t.responseStatus??null,t.error?.slice(0,1000)??null,t.deliveredAt??null,this.now(),e]),this.getDelivery(e)}}import{createHash as bn}from"node:crypto";function kn(e,t,r){if(e==="gt")return t>r;if(e==="gte")return t>=r;if(e==="lt")return t<r;if(e==="lte")return t<=r;return t===r}function Fn(e,t,r){if(t.status==="no_data"||t.status==null&&t.value==null)return;if(e.operator==="unhealthy")return t.status==="unhealthy";if(t.value==null||e.threshold==null)return;return kn(e.operator,t.value,r&&e.recoveryThreshold!=null?e.recoveryThreshold:e.threshold)}function Pn(e,t){let r=Object.fromEntries(e.groupBy.flatMap((n)=>t.group?.[n]==null?[]:[[n,String(t.group[n])]]));return{key:Object.entries(r).sort(([n],[i])=>n.localeCompare(i)).map(([n,i])=>`${n}=${i}`).join(",")||"default",values:r}}function Hn(e,t){return bn("sha256").update(`${e}\x00${t}`).digest("hex")}class ge{store;constructor(e){this.store=e}evaluate(e,t){if(!e.enabled)return{transition:"ignored",notify:!1};let r=Pn(e,t),s=Hn(e.id,r.key),n=this.store.getAlertByDedup(s),i=n?.state==="firing"||n?.state==="silenced",E=Fn(e,t,i);if(E==null&&e.noDataPolicy==="ignore")return{alert:n,transition:"ignored",notify:!1};let a=E??e.noDataPolicy==="firing",d=E==null&&e.noDataPolicy==="pending",T=new Date(t.timestamp).toISOString(),N=h({...t.evidence??{},...t.value==null?{}:{value:t.value},...t.status?{healthStatus:t.status}:{},group:r.values}),o;if(!n){let L=a||d?1:0,u=a&&L>=e.consecutive?"firing":"pending";o={id:crypto.randomUUID(),ruleId:e.id,projectId:e.projectId,environmentId:e.environmentId,resourceId:e.resourceId,dedupKey:s,groupKey:r.key,state:u,severity:e.severity,title:e.name,evidence:N,failureCount:L,recoveryCount:0,occurrenceCount:1,firstSeenAt:T,lastSeenAt:T,firingAt:u==="firing"?T:void 0,updatedAt:T}}else if(a||d){let L=n.state==="resolved",u=(L?0:n.failureCount)+1,C=n.silencedUntil&&new Date(n.silencedUntil)>new Date(T),g=d?"pending":u>=e.consecutive?"firing":"pending";if((C||this.store.isSilenced(e,r.values,T))&&g==="firing")g="silenced";o={...n,state:g,severity:e.severity,title:e.name,evidence:N,failureCount:u,recoveryCount:0,occurrenceCount:n.occurrenceCount+1,firstSeenAt:L?T:n.firstSeenAt,lastSeenAt:T,firingAt:g==="firing"||g==="silenced"?n.firingAt??T:void 0,resolvedAt:void 0,updatedAt:T}}else{let L=n.recoveryCount+1,u=L>=e.recoveryConsecutive;o={...n,state:u?"resolved":n.state,evidence:N,failureCount:u?0:n.failureCount,recoveryCount:L,lastSeenAt:T,resolvedAt:u?T:n.resolvedAt,silencedUntil:u?void 0:n.silencedUntil,updatedAt:T}}let c=n?.state!==o.state,l=o.state==="pending"?"pending":o.state==="firing"?c?"firing":"updated":o.state==="silenced"?"silenced":c?"resolved":"updated";return{alert:this.store.saveAlert(o,c||!n?l:"observed"),transition:l,notify:l==="firing"||l==="resolved"}}}import{createHmac as Kn}from"node:crypto";function xn(e,t){let r=Object.fromEntries(new Intl.DateTimeFormat("en-US",{timeZone:t,weekday:"short",hour:"2-digit",minute:"2-digit",hourCycle:"h23"}).formatToParts(e).map((n)=>[n.type,n.value])),s=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"];return{minute:Number(r.hour)*60+Number(r.minute),weekday:s.indexOf(r.weekday)}}function es(e){let t=/^(\d{2}):(\d{2})$/.exec(e);if(!t)throw Error("Quiet hours require HH:MM times.");let r=Number(t[1])*60+Number(t[2]);if(Number(t[1])>23||Number(t[2])>59)throw Error("Quiet hours require valid HH:MM times.");return r}function jt(e,t){if(!e.quietHours)return!1;let r=xn(t,e.quietHours.timezone),s=es(e.quietHours.start),n=es(e.quietHours.end);if(e.quietHours.weekdays?.length&&!e.quietHours.weekdays.includes(r.weekday))return!1;return s<=n?r.minute>=s&&r.minute<n:r.minute>=s||r.minute<n}function Mn(e,t,r){let s=e.matcher;return(!s.projectIds?.length||s.projectIds.includes(t.projectId))&&(!s.environmentIds?.length||!!t.environmentId&&s.environmentIds.includes(t.environmentId))&&(!s.resourceIds?.length||!!t.resourceId&&s.resourceIds.includes(t.resourceId))&&(!s.severities?.length||s.severities.includes(t.severity))&&(!s.eventTypes?.length||s.eventTypes.includes(r))}function jn(e){if(!e)return{};try{let t=JSON.parse(e);return t&&typeof t==="object"?t:{value:e}}catch{return{value:e}}}function Bn(e,t,r){let s={severity:e.severity.toUpperCase(),title:e.title,state:e.state,event:t,projectId:e.projectId,environmentId:e.environmentId??"",resourceId:e.resourceId??"",groupKey:e.groupKey};return(r?.trim()||"[{{severity}}] {{title}} · {{state}}").replace(/\{\{(severity|title|state|event|projectId|environmentId|resourceId|groupKey)\}\}/g,(n,i)=>s[i]).slice(0,2000)}function Mt(e,t,r){return{schemaVersion:1,event:t,notificationText:Bn(e,t,r),alert:{id:e.id,state:e.state,severity:e.severity,title:e.title,projectId:e.projectId,environmentId:e.environmentId??null,resourceId:e.resourceId??null,groupKey:e.groupKey,firstSeenAt:e.firstSeenAt,lastSeenAt:e.lastSeenAt,firingAt:e.firingAt??null,resolvedAt:e.resolvedAt??null,evidence:e.evidence}}}class $n{store;options;fetchImpl;constructor(e,t={}){this.store=e;this.options=t;this.fetchImpl=t.fetchImpl??globalThis.fetch}now(){return this.options.now?.()??new Date}preview(e,t,r){let s=[];for(let n of this.store.listRoutes(e)){if(!n.enabled||!Mn(n,t,r))continue;let i=[];for(let E of n.channelIds){let a=this.store.getChannel(E);if(a?.status==="active")i.push(a)}s.push({route:n,channels:i,quiet:jt(n,this.now())})}return s}enqueue(e,t,r){let s=r==="resolved"?t.resolvedAt:r==="firing"?t.firingAt:t.updatedAt,n=[];for(let i of this.preview(e,t,r)){if(i.quiet&&r!=="resolved")continue;let E=r==="resolved"||i.route.groupWaitSeconds===0?void 0:new Date(this.now().getTime()+i.route.groupWaitSeconds*1000).toISOString(),a=r==="resolved"?Number.POSITIVE_INFINITY:Math.max(0,i.route.rateLimitPerMinute-this.store.countRouteDeliveries(i.route.id,new Date(this.now().getTime()-60000).toISOString()));for(let d of i.channels){if(a<=0)break;n.push(this.store.createDelivery({alertId:t.id,channelId:d.id,routeId:i.route.id,eventType:r,idempotencyKey:`${t.id}:${r}:${s}:${d.id}`,payload:Mt(t,r,i.route.template),nextAttemptAt:E})),a--}}return n}enqueueRemindersAndEscalations(e,t){let r=this.now(),s=[];for(let n of t.filter((i)=>i.state==="firing"&&!i.acknowledgedAt&&i.firingAt)){let i=Math.max(0,(r.getTime()-new Date(n.firingAt).getTime())/1000);for(let E of this.preview(e,n,"reminder")){if(E.quiet)continue;if(E.route.reminderSeconds&&i>=E.route.reminderSeconds){let a=Math.floor(i/E.route.reminderSeconds);for(let d of E.channels)s.push(this.store.createDelivery({alertId:n.id,channelId:d.id,routeId:E.route.id,eventType:"reminder",idempotencyKey:`${n.id}:reminder:${E.route.id}:${a}:${d.id}`,payload:Mt(n,"reminder")}))}for(let[a,d]of E.route.escalation.entries())if(i>=d.afterSeconds){for(let T of d.channelIds)if(this.store.getChannel(T)?.status==="active")s.push(this.store.createDelivery({alertId:n.id,channelId:T,routeId:E.route.id,eventType:"escalation",idempotencyKey:`${n.id}:escalation:${E.route.id}:${a}:${T}`,payload:Mt(n,"escalation")}))}}}return s}async send(e,t){let r=jn(this.store.channelCredential(e.id)),s=typeof t.notificationText==="string"?t.notificationText:`[${String(t.alert?.severity??"info").toUpperCase()}] ${String(t.alert?.title??"ts-cloud alert")} · ${String(t.alert?.state??t.event)}`;try{if(e.kind==="email"){if(!this.options.emailImpl)return{ok:!1,error:"Email adapter is not configured."};return await this.options.emailImpl({to:e.config.to,from:e.config.from,subject:`[ts-cloud] ${String(t.alert?.title??t.event)}`,text:`${s}
|
|
1541
|
+
|
|
1542
|
+
${JSON.stringify(t,null,2)}`}),{ok:!0,status:202}}if(e.kind==="sms"){if(!this.options.smsImpl)return{ok:!1,error:"SMS adapter is not configured."};let N=e.config.to;if(typeof N!=="string"||!N.trim())return{ok:!1,error:"SMS channels need a `to` number."};return await this.options.smsImpl({to:N,from:e.config.from,text:s.slice(0,300)}),{ok:!0,status:202}}let n=r.url??r.value,i=t,E={"content-type":"application/json"};if(e.kind==="slack")i={text:s};if(e.kind==="discord")i={content:s};if(e.kind==="teams")i={text:s};if(e.kind==="telegram")n=`https://api.telegram.org/bot${r.botToken??r.value}/sendMessage`,i={chat_id:e.config.chatId,text:s};if(!n||!/^https:\/\//.test(n))return{ok:!1,error:"Channel endpoint is unavailable or not HTTPS."};let a=JSON.stringify(i);if(e.kind==="webhook"&&r.signingSecret){let N=Math.floor(this.now().getTime()/1000).toString();E["x-ts-cloud-timestamp"]=N,E["x-ts-cloud-signature"]=`v1=${Kn("sha256",r.signingSecret).update(`${N}.${a}`).digest("hex")}`}let d=new AbortController,T=setTimeout(()=>d.abort(),1e4);try{let N=await this.fetchImpl(n,{method:"POST",headers:E,body:a,signal:d.signal});return N.ok?{ok:!0,status:N.status}:{ok:!1,status:N.status,error:`Channel returned HTTP ${N.status}.`}}finally{clearTimeout(T)}}catch(n){return{ok:!1,error:n instanceof Error?n.message:String(n)}}}async deliver(e){let t=this.store.getDelivery(e);if(!t)throw Error("Notification delivery was not found.");if(t.state==="delivered"||t.state==="dead"||t.nextAttemptAt&&t.nextAttemptAt>this.now().toISOString())return t;let r=this.store.getChannel(t.channelId);if(!r||r.status==="disabled"||r.status==="paused")return this.store.updateDelivery(e,{state:"failed",attempt:t.attempt+1,error:"Notification channel is unavailable."});let s=t.attempt+1,n=await this.send(r,t.payload);if(n.ok)return this.store.setChannelStatus(r.id,"active"),this.store.updateDelivery(e,{state:"delivered",attempt:s,responseStatus:n.status,deliveredAt:this.now().toISOString()});let E=!(!n.status||n.status===429||n.status>=500)||s>=t.maxAttempts,a=E?void 0:new Date(this.now().getTime()+Math.min(3600000,30000*2**(s-1))).toISOString();return this.store.setChannelStatus(r.id,"failing",n.error),this.store.updateDelivery(e,{state:E?"dead":"retrying",attempt:s,nextAttemptAt:a,responseStatus:n.status,error:n.error})}async deliverAll(e){return Promise.all(e.map((t)=>this.deliver(t.id)))}async retryDue(){let e=this.now().toISOString(),t=this.store.listDeliveries({states:["pending","retrying"]}).filter((r)=>!r.nextAttemptAt||r.nextAttemptAt<=e);return this.deliverAll(t)}async testChannel(e){let t=this.store.getChannel(e);if(!t)throw Error("Notification channel was not found.");let r=await this.send(t,{schemaVersion:1,event:"test",message:"ts-cloud notification channel test. No incident was created."});return this.store.markChannelTest(e,r.ok,r.error),r}}import{lookup as qn}from"node:dns/promises";import{connect as Yn}from"node:net";async function zn(e,t){let r=Bun.spawn(e,{stdout:"pipe",stderr:"pipe"}),s=setTimeout(()=>r.kill(),t);try{return{exitCode:await r.exited,stdout:await new Response(r.stdout).text(),stderr:await new Response(r.stderr).text()}}finally{clearTimeout(s)}}class Gn{store;options;constructor(e,t={}){this.store=e;this.options=t}now(){return this.options.now?.()??new Date}async probe(e){let t=performance.now(),r=this.now().toISOString(),s=this.options.agent??"local";try{if(e.kind==="http"){let E=new URL(e.target),a=performance.now();await(this.options.lookupImpl??qn)(E.hostname);let d=performance.now()-a,T=new AbortController,N=setTimeout(()=>T.abort(),e.timeoutSeconds*1000);try{let o=performance.now(),c=await(this.options.fetchImpl??fetch)(E.toString(),{method:e.config.method??"GET",headers:e.config.headers,signal:T.signal,redirect:"manual"}),l=performance.now()-o,_=e.config.expectedBody?await c.text():"",L=e.config.expectedStatuses??[200,201,202,204],u=L.includes(c.status)&&(!e.config.expectedBody||_.includes(e.config.expectedBody));return{checkId:e.id,status:u?"healthy":"unhealthy",agent:s,region:this.options.region,statusCode:c.status,message:u?void 0:`Expected ${L.join("/")} and configured body match.`,timings:{dnsMs:d,ttfbMs:l,totalMs:performance.now()-t},checkedAt:r}}finally{clearTimeout(N)}}if(e.kind==="tcp"){let E=Number(e.config.port);if(!Number.isInteger(E)||E<1||E>65535)throw Error("TCP health check requires a valid port.");let a=performance.now();return await new Promise((d,T)=>{let N=Yn({host:e.target,port:E}),o=setTimeout(()=>{N.destroy(),T(Error("TCP health check timed out."))},e.timeoutSeconds*1000);N.once("connect",()=>{clearTimeout(o),N.destroy(),d()}),N.once("error",(c)=>{clearTimeout(o),T(c)})}),{checkId:e.id,status:"healthy",agent:s,region:this.options.region,timings:{connectMs:performance.now()-a,totalMs:performance.now()-t},checkedAt:r}}let n=e.config.command??[];if(!n.length)throw Error("Command health check is not configured.");let i=await(this.options.commandImpl??zn)(n,e.timeoutSeconds*1000);return{checkId:e.id,status:i.exitCode===0?"healthy":"unhealthy",agent:s,region:this.options.region,message:i.exitCode===0?void 0:(i.stderr||i.stdout||`Command exited ${i.exitCode}`).slice(0,1000),timings:{totalMs:performance.now()-t},checkedAt:r}}catch(n){return{checkId:e.id,status:"unhealthy",agent:s,region:this.options.region,message:(n instanceof Error?n.message:String(n)).slice(0,1000),timings:{totalMs:performance.now()-t},checkedAt:r}}}async run(e){return this.store.appendHealthResult(await this.probe(e))}async runAndEvaluate(e){let t=await this.run(e),r=new ge(this.store),s=this.store.listRules(e.projectId,e.environmentId).filter((n)=>n.healthCheckId===e.id);return{result:t,evaluations:s.map((n)=>r.evaluate(n,{status:t.status,timestamp:t.checkedAt,group:{agent:t.agent,region:t.region??""},evidence:{healthResultId:t.id,statusCode:t.statusCode??null,message:t.message??null,timings:t.timings}}))}}}var Qn=Object.freeze({rawDays:30,downsampleAfterDays:7,downsampleBucketMs:3600000,maxRecords:1e6,samplingRate:1,collectLogs:!0,collectTraces:!0,collectRequestAnalytics:!0,estimatedStorageUsdPerGbMonth:0});function Je(e,t,r,s){let n=Number(e);return Number.isFinite(n)?Math.min(s,Math.max(r,Math.floor(n))):t}function ts(e,t,r,s){let n=Number(e);return Number.isFinite(n)?Math.min(s,Math.max(r,n)):t}function Bt(e,t){return typeof e==="boolean"?e:t}function rs(e,t={...Qn}){let r=e&&typeof e==="object"&&!Array.isArray(e)?e:{},s=Je(r.rawDays,t.rawDays,1,3650),n=Je(r.downsampleAfterDays,t.downsampleAfterDays,1,s);return{rawDays:s,downsampleAfterDays:n,downsampleBucketMs:Je(r.downsampleBucketMs,t.downsampleBucketMs,60000,86400000),maxRecords:Je(r.maxRecords,t.maxRecords,1000,1e8),samplingRate:ts(r.samplingRate,t.samplingRate,0.01,1),collectLogs:Bt(r.collectLogs,t.collectLogs),collectTraces:Bt(r.collectTraces,t.collectTraces),collectRequestAnalytics:Bt(r.collectRequestAnalytics,t.collectRequestAnalytics),estimatedStorageUsdPerGbMonth:ts(r.estimatedStorageUsdPerGbMonth,t.estimatedStorageUsdPerGbMonth,0,1e4)}}function ss(e){return`telemetry.policy:${e}`}function Vn(e,t){return rs(e.getSetting(ss(t)))}function Ha(e,t,r){let s=rs(r,Vn(e,t));return e.setSetting(ss(t),s),s}function Ka(e,t){return Number((Math.max(0,e)/1073741824*t.estimatedStorageUsdPerGbMonth).toFixed(4))}var Wn=/(?:authorization|cookie|password|passwd|secret|token|api[_-]?key|private[_-]?key|access[_-]?key|session)/i,Jn=/\b(?:authorization|cookie|password|passwd|secret|token|api[_-]?key|private[_-]?key|access[_-]?key)\s*[=:]\s*[^\s,;&\[]+/gi,Zn=/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi,wn=/\b(?:\d{1,3}\.){3}\d{1,3}\b/g;function $t(e,t={}){let r=e.replace(/([?&][^=&#\s]+)=([^&#\s]*)/g,"$1=[REDACTED]").replace(Jn,"[REDACTED]");if(t.redactEmail!==!1)r=r.replace(Zn,"[EMAIL]");if(t.redactIp)r=r.replace(wn,"[IP]");for(let s of t.patterns??[])r=r.replace(s,"[REDACTED]");return r}function Re(e,t={},r){if(r&&Wn.test(r))return"[REDACTED]";if(e===null||typeof e==="boolean"||typeof e==="number")return e;if(typeof e==="string")return $t(e,t);if(Array.isArray(e))return e.map((s)=>Re(s,t));if(e&&typeof e==="object"){let s={};for(let[n,i]of Object.entries(e))s[n]=Re(i,t,n);return s}return String(e??"")}function ns(e){if(!e)return;return e.split("?",1)[0].replace(/\b[0-9a-f]{8}-[0-9a-f-]{27,}\b/gi,":id").replace(/\/(?:(?:\d{4,})|(?:[0-9a-f]{16,}))(?:\/|$)/gi,(r)=>r.endsWith("/")?"/:id/":"/:id").slice(0,512)}var is=65536,ei=2678400000,ti=new Set(["metric","log","trace","request","event"]);function p(e){return typeof e==="string"?e:void 0}function Oe(e){return e==null?void 0:Number(e)}function ri(e){if(typeof e!=="string")return{};try{return JSON.parse(e)}catch{return{}}}function Ze(e){return{id:String(e.id),projectId:String(e.project_id),environmentId:p(e.environment_id),resourceId:p(e.resource_id),kind:String(e.kind),source:String(e.source),name:String(e.name),timestamp:String(e.timestamp),observedAt:String(e.observed_at),value:Oe(e.value),unit:p(e.unit),level:p(e.level),message:p(e.message),durationMs:Oe(e.duration_ms),statusCode:Oe(e.status_code),method:p(e.method),host:p(e.host),pathTemplate:p(e.path_template),bytesIn:Oe(e.bytes_in),bytesOut:Oe(e.bytes_out),region:p(e.region),cacheResult:p(e.cache_result),upstream:p(e.upstream),traceId:p(e.trace_id),requestId:p(e.request_id),deploymentId:p(e.deployment_id),releaseId:p(e.release_id),workloadId:p(e.workload_id),sampled:Number(e.sampled)===1,attributes:ri(e.attributes),ingestedBytes:Number(e.ingested_bytes)}}function re(e,t){let r=new Date(e);if(!e||!Number.isFinite(r.getTime()))throw Error(`${t} must be a valid ISO-8601 instant.`);return r}function we(e){let t=re(e.from,"from"),r=re(e.to,"to");if(t>=r)throw Error("Telemetry from must be earlier than to.");if(r.getTime()-t.getTime()>ei)throw Error("Telemetry queries are limited to 31 days.");return{from:t,to:r}}function Es(e){if(!e)return;try{let t=JSON.parse(Buffer.from(e,"base64url").toString("utf8"));if(typeof t?.timestamp!=="string"||typeof t?.id!=="string")throw Error();return t}catch{throw Error("Telemetry cursor is invalid.")}}function as(e){return e?Buffer.from(JSON.stringify({timestamp:e.timestamp,id:e.id})).toString("base64url"):void 0}function si(e,t){if(!e.length)return;let r=[...e].sort((n,i)=>n-i),s=Math.max(0,Math.min(r.length-1,Math.ceil(t/100*r.length)-1));return r[s]}function Ae(e,t){if(t==="count")return e.length;if(!e.length)return;if(t==="sum")return e.reduce((r,s)=>r+s,0);if(t==="avg")return e.reduce((r,s)=>r+s,0)/e.length;if(t==="min")return Math.min(...e);if(t==="max")return Math.max(...e);return si(e,Number(t.slice(1)))}function ni(e,t="UTC"){try{return new Intl.DateTimeFormat("en-US",{timeZone:t,month:"short",day:"2-digit",hour:"2-digit",minute:"2-digit",hourCycle:"h23",timeZoneName:"short"}).format(e)}catch{throw Error(`Unknown telemetry timezone: ${t}`)}}class qt{controlPlane;options;constructor(e,t={}){this.controlPlane=e;this.options=t}now(){return this.options.now?.()??new Date}append(e){if(!ti.has(e.kind))throw Error(`Unsupported telemetry kind: ${e.kind}`);let t=re(e.timestamp,"timestamp").toISOString(),r=e.observedAt?re(e.observedAt,"observedAt").toISOString():this.now().toISOString(),s=Re(e.attributes??{},this.options.redaction),n=e.message?$t(e.message,this.options.redaction).slice(0,32768):void 0,i=ns(e.pathTemplate),E=e.id??crypto.randomUUID(),a=JSON.stringify({...e,id:E,timestamp:t,observedAt:r,attributes:s,message:n,pathTemplate:i}),d=Buffer.byteLength(a);if(d>is)throw Error(`Telemetry records are limited to ${is} bytes.`);return this.controlPlane.database.run("INSERT OR IGNORE INTO telemetry_records (id, project_id, environment_id, resource_id, kind, source, name, timestamp, observed_at, value, unit, level, message, duration_ms, status_code, method, host, path_template, bytes_in, bytes_out, region, cache_result, upstream, trace_id, request_id, deployment_id, release_id, workload_id, sampled, attributes, ingested_bytes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",[E,e.projectId,e.environmentId??null,e.resourceId??null,e.kind,e.source.slice(0,128),e.name.slice(0,256),t,r,e.value??null,e.unit??null,e.level??null,n??null,e.durationMs??null,e.statusCode??null,e.method?.slice(0,16)??null,e.host?.slice(0,256)??null,i??null,e.bytesIn??null,e.bytesOut??null,e.region?.slice(0,64)??null,e.cacheResult?.slice(0,64)??null,e.upstream?.slice(0,256)??null,e.traceId?.slice(0,256)??null,e.requestId?.slice(0,256)??null,e.deploymentId?.slice(0,256)??null,e.releaseId?.slice(0,256)??null,e.workloadId?.slice(0,512)??null,e.sampled===!1?0:1,JSON.stringify(s),d]),this.get(E)}appendMany(e){if(e.length>1e4)throw Error("Telemetry batches are limited to 10,000 records.");return this.controlPlane.database.transaction((r)=>r.map((s)=>this.append(s)))(e)}get(e){let t=this.controlPlane.database.query("SELECT * FROM telemetry_records WHERE id=?").get(e);return t?Ze(t):void 0}query(e){let{from:t,to:r}=we(e),s=Math.min(5000,Math.max(1,Math.floor(e.limit??500))),n=["project_id=?","timestamp>=?","timestamp<?"],i=[e.projectId,t.toISOString(),r.toISOString()],E=(C,g)=>{if(g)n.push(`${C}=?`),i.push(g)},a=(C,g)=>{let R=[...new Set(g??[])].slice(0,100);if(R.length)n.push(`${C} IN (${R.map(()=>"?").join(",")})`),i.push(...R)};if(E("environment_id",e.environmentId),a("resource_id",e.resourceIds),a("kind",e.kinds),a("name",e.names),a("source",e.sources),a("level",e.levels),E("trace_id",e.traceId),E("request_id",e.requestId),E("deployment_id",e.deploymentId),E("release_id",e.releaseId),E("workload_id",e.workloadId),e.text)n.push("(message LIKE ? OR name LIKE ?)"),i.push(`%${e.text.slice(0,256)}%`,`%${e.text.slice(0,256)}%`);let d=Es(e.cursor);if(d)n.push("(timestamp<? OR (timestamp=? AND id<?))"),i.push(d.timestamp,d.timestamp,d.id);let T=this.controlPlane.database.query(`SELECT * FROM telemetry_records WHERE ${n.join(" AND ")} ORDER BY timestamp DESC, id DESC LIMIT ?`).all(...i,s+1),N=T.slice(0,s).map(Ze),o=T.length>s,c=new Map;for(let C of N)c.set(C.source,[...c.get(C.source)??[],C]);let l=this.now().getTime(),_=[...c].map(([C,g])=>{let R=g.reduce((y,X)=>X.observedAt>y?X.observedAt:y,g[0].observedAt),S=l-new Date(R).getTime();return{source:C,latestAt:R,freshness:S<=120000?"live":S<=600000?"stale":"unavailable",count:g.length}}),L=Math.max(60000,(r.getTime()-t.getTime())/100),u=[...c].flatMap(([C,g])=>{let R=[...new Set(g.map((S)=>new Date(S.timestamp).getTime()))].sort((S,y)=>S-y);return R.slice(1).flatMap((S,y)=>S-R[y]>L?[{from:new Date(R[y]).toISOString(),to:new Date(S).toISOString(),source:C}]:[])});return{records:N,nextCursor:o?as(N.at(-1)):void 0,truncated:o,scannedRange:{from:t.toISOString(),to:r.toISOString()},sources:_,gaps:u}}tail(e,t){let{from:r,to:s}=we(e),n=Math.min(1000,Math.max(1,Math.floor(e.limit??200))),i=["project_id=?","timestamp>=?","timestamp<?"],E=[e.projectId,r.toISOString(),s.toISOString()],a=(c,l)=>{if(l)i.push(`${c}=?`),E.push(l)},d=(c,l)=>{let _=[...new Set(l??[])].slice(0,100);if(_.length)i.push(`${c} IN (${_.map(()=>"?").join(",")})`),E.push(..._)};if(a("environment_id",e.environmentId),d("resource_id",e.resourceIds),d("kind",e.kinds),d("name",e.names),d("source",e.sources),d("level",e.levels),a("trace_id",e.traceId),a("request_id",e.requestId),a("deployment_id",e.deploymentId),a("release_id",e.releaseId),a("workload_id",e.workloadId),e.text)i.push("(message LIKE ? OR name LIKE ?)"),E.push(`%${e.text.slice(0,256)}%`,`%${e.text.slice(0,256)}%`);let T=Es(t);if(T)i.push("(timestamp>? OR (timestamp=? AND id>?))"),E.push(T.timestamp,T.timestamp,T.id);let N=this.controlPlane.database.query(`SELECT * FROM telemetry_records WHERE ${i.join(" AND ")} ORDER BY timestamp ASC, id ASC LIMIT ?`).all(...E,n+1),o=N.slice(0,n).map(Ze);return{records:o,cursor:as(o.at(-1))??t,truncated:N.length>n}}series(e){let{from:t,to:r}=we(e),s=Math.min(86400000,Math.max(1000,Math.floor(e.bucketMs)));if((r.getTime()-t.getTime())/s>2000)throw Error("Telemetry series are limited to 2,000 buckets.");let n=this.query({...e,kinds:["metric"],limit:5000,cursor:void 0}),i=new Map;for(let a of n.records)i.set(`${a.source}\x00${a.name}`,[...i.get(`${a.source}\x00${a.name}`)??[],a]);let E=(a,d,T)=>{let N=new Map;for(let c of a){let l=d.getTime()+Math.floor((new Date(c.timestamp).getTime()-d.getTime())/s)*s;if(l>=d.getTime()&&l<T.getTime()&&c.value!=null)N.set(l,[...N.get(l)??[],c.value])}let o=[];for(let c=d.getTime();c<T.getTime();c+=s){let l=N.get(c)??[],_=Ae(l,e.aggregation);o.push({timestamp:new Date(c).toISOString(),label:ni(new Date(c),e.timezone),value:_,count:l.length,gap:_==null})}return o};return[...i].map(([a,d])=>{let[T,N]=a.split("\x00"),o={name:N,source:T,unit:d.find((c)=>c.unit)?.unit,aggregation:e.aggregation,bucketMs:s,points:E(d,t,r)};if(e.compareFrom&&e.compareTo){let c=this.query({...e,from:e.compareFrom,to:e.compareTo,kinds:["metric"],limit:5000,cursor:void 0}).records.filter((l)=>l.source===T&&l.name===N);o.comparison=E(c,re(e.compareFrom,"compareFrom"),re(e.compareTo,"compareTo"))}return o})}status(e,t,r=30,s=1,n){let i=[e],E=t?" AND environment_id=?":"";if(t)i.push(t);let a=[...new Set(n??[])].slice(0,100);if(n&&a.length===0)return[];let d=n?` AND resource_id IN (${a.map(()=>"?").join(",")||"''"})`:"";i.push(...a);let T=this.controlPlane.database.query(`SELECT source, MAX(latest_observed_at) latest, SUM(ingested_bytes) bytes, SUM(record_count) count, MIN(first_observed_at) first FROM telemetry_source_rollups WHERE project_id=?${E}${d} GROUP BY source`).all(...i),N=this.now().getTime();return T.map((o)=>{let c=String(o.latest),l=Math.max(0,Math.floor((N-new Date(c).getTime())/1000)),_=Math.max(1,(new Date(c).getTime()-new Date(String(o.first)).getTime())/86400000);return{source:String(o.source),freshness:l<=120?"live":l<=600?"stale":"unavailable",lastObservedAt:c,lagSeconds:l,samplingRate:s,retentionDays:r,estimatedDailyBytes:Math.round(Number(o.bytes)/_)}})}saveQuery(e,t,r,s){let n=r.trim().slice(0,80);if(!n)throw Error("Saved telemetry queries require a name.");if(we(s),s.projectId!==e)throw Error("Saved telemetry queries cannot cross project scope.");let i=this.now().toISOString(),E=JSON.stringify(Re(s,this.options.redaction));if(Buffer.byteLength(E)>16384)throw Error("Saved telemetry queries are limited to 16 KB.");let a=this.controlPlane.database.query("SELECT * FROM telemetry_saved_queries WHERE project_id=? AND actor_id IS ? AND name=?").get(e,t??null,n),d=a?String(a.id):crypto.randomUUID();return this.controlPlane.database.run("INSERT INTO telemetry_saved_queries (id, project_id, actor_id, name, query, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT(project_id, actor_id, name) DO UPDATE SET query=excluded.query, updated_at=excluded.updated_at",[d,e,t??null,n,E,a?String(a.created_at):i,i]),{id:d,projectId:e,actorId:t,name:n,query:JSON.parse(E),createdAt:a?String(a.created_at):i,updatedAt:i}}listSavedQueries(e,t){return this.controlPlane.database.query("SELECT * FROM telemetry_saved_queries WHERE project_id=? AND actor_id IS ? ORDER BY updated_at DESC").all(e,t??null).map((r)=>({id:String(r.id),projectId:String(r.project_id),actorId:p(r.actor_id),name:String(r.name),query:JSON.parse(String(r.query)),createdAt:String(r.created_at),updatedAt:String(r.updated_at)}))}deleteSavedQuery(e,t,r){return this.controlPlane.database.run("DELETE FROM telemetry_saved_queries WHERE id=? AND project_id=? AND actor_id IS ?",[r,e,t??null]).changes>0}downsample(e,t){let r=Math.min(86400000,Math.max(60000,Math.floor(e.downsampleBucketMs))),s=new Date(this.now().getTime()-Math.max(1,e.downsampleAfterDays)*86400000).toISOString(),n=new Date(this.now().getTime()-Math.max(1,e.rawDays)*86400000).toISOString(),i=this.controlPlane.database.query(`SELECT * FROM telemetry_records WHERE kind='metric' AND source NOT LIKE '%:rollup' AND timestamp>=? AND timestamp<?${t?" AND project_id=?":""} ORDER BY timestamp ASC LIMIT 50001`).all(n,s,...t?[t]:[]);if(i.length>50000)throw Error("Telemetry downsampling is limited to 50,000 raw points per pass; run compaction more frequently.");let E=i.map(Ze),a=new Map;for(let T of E){let N=Math.floor(new Date(T.timestamp).getTime()/r)*r,o=JSON.stringify([T.projectId,T.environmentId??"",T.resourceId??"",T.source,T.name,T.unit??"",N]);a.set(o,[...a.get(o)??[],T])}return this.controlPlane.database.transaction(()=>{for(let T=0;T<E.length;T+=500){let N=E.slice(T,T+500).map((o)=>o.id);if(N.length)this.controlPlane.database.run(`DELETE FROM telemetry_records WHERE id IN (${N.map(()=>"?").join(",")})`,N)}for(let[T,N]of a){let[o,c,l,_,L,u,C]=JSON.parse(T),g=N.flatMap((R)=>R.value==null?[]:[R.value]);this.append({projectId:o,environmentId:c||void 0,resourceId:l||void 0,kind:"metric",source:`${_}:rollup`,name:L,unit:u||void 0,timestamp:new Date(C).toISOString(),value:Ae(g,"avg"),attributes:{downsampled:!0,count:g.length,min:Ae(g,"min"),max:Ae(g,"max"),p95:Ae(g,"p95"),bucketMs:r}})}})(),{compacted:E.length,rollups:a.size}}enforceRetention(e,t){let r=this.downsample(e,t),s=Math.min(3650,Math.max(1,Math.floor(e.rawDays))),n=new Date(this.now().getTime()-s*86400000).toISOString(),i=t?" AND project_id=?":"",E=this.controlPlane.database.run(`DELETE FROM telemetry_records WHERE timestamp<?${i}`,t?[n,t]:[n]).changes,a=Number(this.controlPlane.database.query(`SELECT MAX(0, COUNT(*)-?) excess FROM telemetry_records${t?" WHERE project_id=?":""}`).get(e.maxRecords,...t?[t]:[])?.excess??0);if(a>0)this.controlPlane.database.run(`DELETE FROM telemetry_records WHERE id IN (SELECT id FROM telemetry_records${t?" WHERE project_id=?":""} ORDER BY timestamp ASC LIMIT ?)`,t?[t,a]:[a]);return this.rebuildStatusRollups(t),{deleted:E+a,...r}}rebuildStatusRollups(e){let t=e?" WHERE project_id=?":"",r=e?[e]:[];this.controlPlane.database.transaction(()=>{this.controlPlane.database.run(`DELETE FROM telemetry_source_rollups${t}`,r),this.controlPlane.database.run(`INSERT INTO telemetry_source_rollups (
|
|
1543
|
+
project_id, environment_id, resource_id, source,
|
|
1544
|
+
first_observed_at, latest_observed_at, ingested_bytes, record_count
|
|
1545
|
+
)
|
|
1546
|
+
SELECT
|
|
1547
|
+
project_id, COALESCE(environment_id, ''), COALESCE(resource_id, ''), source,
|
|
1548
|
+
MIN(observed_at), MAX(observed_at), SUM(ingested_bytes), COUNT(*)
|
|
1549
|
+
FROM telemetry_records${t}
|
|
1550
|
+
GROUP BY project_id, COALESCE(environment_id, ''), COALESCE(resource_id, ''), source`,r)})()}}function Wa(e,t,r,s=new Date){let n=new qt(e.controlPlane,{now:()=>s}),i=new ge(e);return e.listRules(t,r).filter((E)=>E.enabled&&!E.healthCheckId).map((E)=>{let a=new Date(s.getTime()-E.windowMs).toISOString(),d=n.query({projectId:t,environmentId:r,resourceIds:E.resourceId?[E.resourceId]:void 0,kinds:["metric"],names:[E.signal],from:a,to:new Date(s.getTime()+1).toISOString(),limit:5000}),T=d.records.flatMap((o)=>o.value==null?[]:[o.value]),N=T.length?E.operator==="gt"||E.operator==="gte"?Math.max(...T):E.operator==="lt"||E.operator==="lte"?Math.min(...T):T.at(0):void 0;return i.evaluate(E,{value:N,status:N==null?"no_data":void 0,timestamp:s.toISOString(),group:{resourceId:E.resourceId??"",source:d.records[0]?.source??""},evidence:{signal:E.signal,windowMs:E.windowMs,sampleCount:T.length,source:d.records[0]?.source??null}})})}var ad=["spend.enforced"];function ii(e){if(e.level==="hard_capped")return"critical";if(e.level==="soft_capped")return"warning";return"info"}function Ue(e,t="USD"){let r=e/100;try{return new Intl.NumberFormat("en-US",{style:"currency",currency:t}).format(r)}catch{return`${r.toFixed(2)} ${t}`}}function Ei(e,t,r){let s=Ue(t.projection.actualCents,e.currency),n=e.hardLimitCents??e.softLimitCents,i=n==null?"":` of ${Ue(n,e.currency)}`,E=t.simulated?" [dry run]":"";if(r==="spend.released")return`[RESOLVED] ${e.name}: spend fell back to ${s}${i}.${E}`;if(r==="spend.enforced"){let T=t.actions.filter((N)=>N!=="notify").join(", ");return`[CAP] ${e.name}: ${s}${i} (${t.usedPercent.toFixed(0)}%). Enforcing ${T||"notifications only"}.${E}`}let a=Le(t.projection.timeToExhaustionMs),d=a?` Projected to reach the limit in ${a}.`:` Projected ${Ue(t.projection.projectedCents,e.currency)} by period end.`;return`[SPEND] ${e.name}: ${s}${i} (${t.usedPercent.toFixed(0)}%).${d}${E}`}function ds(e,t,r){return{schemaVersion:1,event:r,notificationText:Ei(e,t,r),spend:{budgetId:e.id,budgetName:e.name,organizationId:e.organizationId,projectId:e.projectId??null,environmentId:e.environmentId??null,period:e.period,currency:e.currency,level:t.level,simulated:t.simulated,usedPercent:Math.round(t.usedPercent*10)/10,projectedPercent:Math.round(t.projectedPercent*10)/10,actualCents:Math.round(t.projection.actualCents),projectedCents:Math.round(t.projection.projectedCents),projectionConfidence:t.projection.confidence,softLimitCents:e.softLimitCents??null,hardLimitCents:e.hardLimitCents??null,timeToCap:Le(t.projection.timeToExhaustionMs),actions:t.actions,window:{start:t.window.start,end:t.window.end,label:t.window.label},reason:t.reason}}}function ai(e,t){return{schemaVersion:1,event:"spend.anomaly",notificationText:`[ANOMALY] Hourly spend ${Number.isFinite(e.deltaPercent)?`${e.deltaPercent>0?"+":""}${e.deltaPercent.toFixed(0)}%`:"new"} vs the usual ${Ue(e.expected,t)} for this hour (observed ${Ue(e.observed,t)}).`,spend:{anomalyId:e.id,organizationId:e.organizationId,projectId:e.projectId??null,environmentId:e.environmentId??null,signal:e.signal,direction:e.direction,severity:e.severity,observedCents:Math.round(e.observed),expectedCents:Math.round(e.expected),score:Math.round(e.score*10)/10,bucketStart:e.bucketStart}}}function di(e,t,r,s){let n=e.matcher;if(n.projectIds?.length&&(!t.projectId||!n.projectIds.includes(t.projectId)))return!1;if(n.environmentIds?.length&&(!t.environmentId||!n.environmentIds.includes(t.environmentId)))return!1;if(n.severities?.length&&!n.severities.includes(r))return!1;if(n.eventTypes?.length&&!n.eventTypes.includes(s))return!1;return!0}class Yt{store;options;constructor(e,t={}){this.store=e;this.options=t}now(){return this.options.now?.()??new Date}channelsFor(e,t,r,s){let n=[];for(let i of this.store.listRoutes(e)){if(!i.enabled||!di(i,t,r,s))continue;let E=i.channelIds.map((a)=>this.store.getChannel(a)).filter((a)=>a?.status==="active");n.push({route:i,channels:E,quiet:jt(i,this.now())})}return n}dispatch(e,t,r,s,n,i){let E=[],a=[];for(let d of this.channelsFor(e,t,r,s)){if(d.quiet&&s!=="spend.released"){a.push(d.route.id);continue}let T=this.store.countRouteDeliveries(d.route.id,new Date(this.now().getTime()-60000).toISOString()),N=Math.max(0,d.route.rateLimitPerMinute-T);for(let o of d.channels){if(N<=0)break;E.push(this.store.createDelivery({channelId:o.id,routeId:d.route.id,eventType:s,idempotencyKey:`spend:${s}:${i}:${o.id}`,payload:n,nextAttemptAt:d.route.groupWaitSeconds===0?void 0:new Date(this.now().getTime()+d.route.groupWaitSeconds*1000).toISOString()})),N--}}return{deliveries:E,suppressed:a}}notifyDecision(e,t){if(t.breaches.length===0)return{deliveries:[],suppressed:[]};let s=t.actions.some((i)=>i!=="notify")?"spend.enforced":"spend.threshold",n=t.breaches[t.breaches.length-1].atPercent;return this.dispatch(e.organizationId,{projectId:e.projectId,environmentId:e.environmentId},ii(t),s,ds(e,t,s),`${e.id}:${t.window.start}:${n}`)}notifyRelease(e,t,r){if(r.length===0)return{deliveries:[],suppressed:[]};return this.dispatch(e.organizationId,{projectId:e.projectId,environmentId:e.environmentId},"info","spend.released",{...ds(e,t,"spend.released"),released:[...r]},`${e.id}:${t.window.start}:${[...r].sort().join("+")}`)}notifyAnomaly(e,t="USD"){return this.dispatch(e.organizationId,{projectId:e.projectId,environmentId:e.environmentId},e.severity,"spend.anomaly",ai(e,t),e.id)}}var Td="spend.loop.lease";function Ti(){return`${typeof process>"u"?"unknown":String(process.pid)}:${crypto.randomUUID().slice(0,8)}`}class oi{controlPlane;owner;ttlMs;constructor(e,t={}){this.controlPlane=e;this.owner=t.owner??Ti(),this.ttlMs=Math.max(5000,(t.ttlSeconds??120)*1000),this.nowFn=t.now??(()=>new Date)}nowFn;read(){let e=this.controlPlane.getSetting("spend.loop.lease");if(!e||typeof e!=="object"||Array.isArray(e))return;let t=e;if(typeof t.owner!=="string"||typeof t.expiresAt!=="string")return;return t}current(){let e=this.read();if(!e)return;return new Date(e.expiresAt).getTime()>this.nowFn().getTime()?e:void 0}acquire(){let e=this.nowFn();return this.controlPlane.database.transaction(()=>{let r=this.read(),s=r&&new Date(r.expiresAt).getTime()>e.getTime();if(s&&r.owner!==this.owner)return!1;let n={owner:this.owner,acquiredAt:s?r.acquiredAt:e.toISOString(),expiresAt:new Date(e.getTime()+this.ttlMs).toISOString()};return this.controlPlane.setSetting("spend.loop.lease",n),!0})()}release(){return this.controlPlane.database.transaction(()=>{let t=this.read();if(!t||t.owner!==this.owner)return!1;return this.controlPlane.setSetting("spend.loop.lease",null),!0})()}held(){return this.current()?.owner===this.owner}}var Ts={applied:0,duplicates:0,costCents:0,unpricedMeters:[]};class ci{options;gate;service;notifications;constructor(e){this.options=e;this.gate=new Ce(e.controlPlane,{now:e.now}),this.service=new Pt(e.store),this.notifications=e.alerts?new Yt(e.alerts,{now:e.now}):void 0}now(){return this.options.now?.()??new Date}ingest(e,t,r){if(t.length===0)return{...Ts};return this.service.ingestTelemetry(e,t,r)}async run(e,t=[]){let r=this.now(),s=[],n={scope:e,ingested:{...Ts},decisions:[],applied:[],released:[],failed:[],withheld:[],anomalies:[],notificationsSent:0,ranAt:r.toISOString(),durationMs:0,warnings:s};try{if(n.ingested=this.ingest(e.organizationId,t),n.ingested.unpricedMeters.length>0)s.push(`No price entry for ${n.ingested.unpricedMeters.join(", ")}; that usage is metered but counts as zero against every budget.`)}catch(a){s.push(`Usage ingest failed: ${he(a)}`)}let i=zr({gate:this.gate,transport:this.options.transport}),E;try{E=await this.service.runCycle({organizationId:e.organizationId,projectId:e.projectId,environmentId:e.environmentId,environmentKind:e.environmentKind,policy:this.options.policy,now:r,handlers:i})}catch(a){return s.push(`Spend evaluation failed: ${he(a)}`),n.durationMs=this.now().getTime()-r.getTime(),n}n.decisions=E.statuses.map((a)=>a.decision),n.anomalies=E.anomalies;for(let a of E.enforcement)n.applied.push(...a.applied.map((d)=>d.action)),n.released.push(...a.released.map((d)=>d.action)),n.failed.push(...a.failed),n.withheld.push(...a.withheld.map((d)=>d.action));for(let a of n.failed)s.push(`Enforcement for ${a.action} did not complete: ${a.error}`);if(n.withheld.length>0)s.push(`${n.withheld.join(", ")} require approval on this environment and were not applied automatically.`);try{n.released.push(...this.reconcileGate(e))}catch(a){s.push(`Gate reconciliation failed: ${he(a)}`)}if(this.notifications)try{for(let a of E.statuses)if(n.notificationsSent+=this.notifications.notifyDecision(a.budget,a.decision).deliveries.length,a.decision.releases.length>0)n.notificationsSent+=this.notifications.notifyRelease(a.budget,a.decision,a.decision.releases).deliveries.length;for(let a of E.anomalies)n.notificationsSent+=this.notifications.notifyAnomaly(a,this.options.store.priceBook.currency).deliveries.length}catch(a){s.push(`Spend notifications failed: ${he(a)}`)}try{this.options.store.pruneUsage(this.options.retentionDays??400,r)}catch(a){s.push(`Usage pruning failed: ${he(a)}`)}return n.durationMs=this.now().getTime()-r.getTime(),n}reconcileGate(e){let t=new Map(this.options.store.listBudgets({organizationId:e.organizationId}).filter((s)=>s.enabled).map((s)=>[s.id,s])),r=[];for(let s of this.gate.listUnder({organizationId:e.organizationId})){if(t.has(s.budgetId))continue;this.gate.close(s.budgetId,s.action),r.push(s.action)}return r}async runAll(e=[]){let t=[];for(let r of this.options.controlPlane.listOrganizations()){t.push(await this.run({organizationId:r.id},e));for(let s of this.options.controlPlane.listProjects().filter((n)=>n.organizationId===r.id))for(let n of this.options.controlPlane.listEnvironments(s.id))t.push(await this.run({organizationId:r.id,projectId:s.id,environmentId:n.id,environmentKind:n.kind}))}return t}}function he(e){return e instanceof Error?e.message:String(e)}var Ni=60;function Ld(e,t={}){let r=Math.max(10,t.intervalSeconds??Ni)*1000,s=!1,n=!1,i=async()=>{if(s||n)return;if(t.lease&&!t.lease.acquire()){t.onSkip?.(t.lease.current()?.owner??"another process");return}s=!0;try{let a=await e.runAll();t.onResult?.(a)}catch(a){t.onError?.(a)}finally{s=!1}},E=setInterval(()=>void i(),r);if(E.unref?.(),t.immediate)i();return()=>{n=!0,clearInterval(E),t.lease?.release()}}function fe(e,t){if(t.code!==0)throw Error(`${e} failed (exit ${t.code}): ${t.stderr.trim().slice(0,400)}`)}class _i{options;constructor(e){this.options=e}async targets(e){let t=await this.options.functions(e);if(t.length===0)throw Error("No Lambda functions were resolved for this scope.");return t}async suspendFunctions(e){let t=await this.targets(e),r={};for(let s of t)r[s]=await this.options.lambda.getFunctionConcurrency(s),await this.options.lambda.putFunctionConcurrency(s,0);return{kind:"lambda_reserved_concurrency",previous:r}}async resumeFunctions(e){let t=e.previous??{};for(let[r,s]of Object.entries(t))if(typeof s==="number")await this.options.lambda.putFunctionConcurrency(r,s);else await this.options.lambda.deleteFunctionConcurrency(r)}async serveStatic(e){return{...await this.suspendFunctions(e),via:"serve_static"}}async restoreDynamic(e){await this.resumeFunctions(e)}async suspendProject(e){let t=await this.targets(e),r={};for(let s of t){let i={...(await this.options.lambda.getFunction(s)).Configuration?.Environment?.Variables??{}};r[s]=i.MAINTENANCE_MODE??null,await this.options.lambda.updateFunctionConfiguration({FunctionName:s,Environment:{Variables:{...i,MAINTENANCE_MODE:"1"}}}),await this.options.republish?.(s)}return{kind:"lambda_maintenance_mode",previous:r}}async resumeProject(e){let t=e.previous??{};for(let r of Object.keys(t)){let n={...(await this.options.lambda.getFunction(r)).Configuration?.Environment?.Variables??{}};n.MAINTENANCE_MODE=typeof t[r]==="string"?t[r]:"0",await this.options.lambda.updateFunctionConfiguration({FunctionName:r,Environment:{Variables:n}}),await this.options.republish?.(r)}}}class li{options;constructor(e){this.options=e}async unitsFor(e){let t=await this.options.units(e);if(t.length===0)throw Error("No systemd units were resolved for this scope.");for(let r of t)if(!/^[A-Za-z0-9@._-]+$/.test(r))throw Error(`Refusing to act on an unsafe unit name: ${r}`);return t}async stopUnits(e,t){let r=await this.unitsFor(e),s=[];for(let n of r){let i=await this.options.exec(this.options.host,`systemctl list-units --plain --no-legend --state=active --type=service '${n}@*.service' '${n}.service' 2>/dev/null | awk '{print $1}'`);fe(`listing units for ${n}`,i);let E=i.stdout.split(`
|
|
1551
|
+
`).map((a)=>a.trim()).filter((a)=>/^[A-Za-z0-9@._-]+\.service$/.test(a));for(let a of E){let d=await this.options.exec(this.options.host,`systemctl stop '${a}'`);fe(`stopping ${a}`,d),s.push(a)}}return{kind:"systemd_units",via:t,stopped:s}}async startUnits(e){let t=Array.isArray(e.stopped)?e.stopped:[];for(let r of t){if(!/^[A-Za-z0-9@._-]+$/.test(r))continue;let s=await this.options.exec(this.options.host,`systemctl start '${r}'`);fe(`starting ${r}`,s)}}async suspendFunctions(e){return this.stopUnits(e,"suspend_functions")}async resumeFunctions(e){await this.startUnits(e)}async serveStatic(e){return this.stopUnits(e,"serve_static")}async restoreDynamic(e){await this.startUnits(e)}async suspendProject(e){return this.stopUnits(e,"suspend_project")}async resumeProject(e){await this.startUnits(e)}async throttleRequests(e){let t=this.options.renderDdos,r=this.options.ddos;if(!t||!r)throw Error("Request throttling needs a DDoS config and renderer on this transport.");let s={rateLimitFactor:e.factor},n={...r.thresholds,newConnectionsPerSecond:zt(r.thresholds?.newConnectionsPerSecond??50,s),concurrentPerSource:zt(r.thresholds?.concurrentPerSource??100,s)},i=t({...r,thresholds:n}),E=await this.options.exec(this.options.host,`bash -s <<'TS_CLOUD_THROTTLE_EOF'
|
|
1552
|
+
${i}
|
|
1553
|
+
TS_CLOUD_THROTTLE_EOF`);return fe("applying the throttled firewall ruleset",E),{kind:"nftables_throttle",factor:e.factor,previousThresholds:r.thresholds??{}}}async restoreRequests(e){let t=this.options.renderDdos,r=this.options.ddos;if(!t||!r)return;let s=e.previousThresholds??{},n=t({...r,thresholds:s}),i=await this.options.exec(this.options.host,`bash -s <<'TS_CLOUD_THROTTLE_EOF'
|
|
1554
|
+
${n}
|
|
1555
|
+
TS_CLOUD_THROTTLE_EOF`);fe("restoring the firewall ruleset",i)}}function Cd(e){let t=async(s,n)=>{let i={},E,a=0;for(let[d,T]of e.entries()){let N=T[s];if(!N)continue;a++;try{i[String(d)]=await N.call(T,n)}catch(o){i[String(d)]={error:o instanceof Error?o.message:String(o)},E??=o}}if(a===0)throw Error(`No transport implements ${s}.`);if(E)throw E;return{kind:"composite",legs:i}},r=async(s,n)=>{let i=n.legs??{},E;for(let[a,d]of e.entries()){let T=d[s];if(!T)continue;let N=i[String(a)];if(!N||typeof N!=="object"||Array.isArray(N)||"error"in N)continue;try{await T.call(d,N)}catch(o){E??=o}}if(E)throw E};return{throttleRequests:(s)=>t("throttleRequests",s),restoreRequests:(s)=>r("restoreRequests",s),suspendFunctions:(s)=>t("suspendFunctions",s),resumeFunctions:(s)=>r("resumeFunctions",s),serveStatic:(s)=>t("serveStatic",s),restoreDynamic:(s)=>r("restoreDynamic",s),suspendProject:(s)=>t("suspendProject",s),resumeProject:(s)=>r("resumeProject",s)}}import{createHash as dE}from"node:crypto";class Se extends Error{constructor(){super("Operation cancellation was requested")}}class et extends Error{timeoutSeconds;constructor(e){super(`Operation exceeded its ${e}s timeout`);this.timeoutSeconds=e}}class J extends Error{errorClass;constructor(e,t){super(e);this.errorClass=t}}var tt={project:2,environment:1,provider:2,builds:1},Li=30,os=16384,mi=["succeeded","failed","cancelled","timed_out"],Gt=new Map,cs=new WeakMap;function Ns(e){if(e.path===":memory:"){let s=cs.get(e);if(s)return s;let n=new Set;return cs.set(e,n),n}let t=Gt.get(e.path);if(t)return t;let r=new Set;return Gt.set(e.path,r),r}function Z(e){return typeof e==="string"&&e?e:void 0}function ui(e){try{let t=JSON.parse(String(e));return Array.isArray(t)?t.map(String):[]}catch{return[]}}function Ci(e){return{operationId:String(e.operation_id),lockKey:Z(e.lock_key),providerKey:Z(e.provider_key),buildSlot:Number(e.build_slot)===1,maxAttempts:Number(e.max_attempts),availableAt:String(e.available_at),timeoutSeconds:Number(e.timeout_seconds),heartbeatAt:Z(e.heartbeat_at),currentStep:Z(e.current_step),blockedReason:Z(e.blocked_reason),retryClasses:ui(e.retry_classes),resumePolicy:String(e.resume_policy),cancellationMode:String(e.cancellation_mode),retentionUntil:String(e.retention_until),createdAt:String(e.created_at),updatedAt:String(e.updated_at)}}function _s(e){return{sequence:Number(e.sequence),id:String(e.id),operationId:String(e.operation_id),stream:String(e.stream),step:Z(e.step),message:String(e.message),redacted:Number(e.redacted)===1,truncated:Number(e.truncated)===1,createdAt:String(e.created_at)}}function H(e,t,r){return Number.isFinite(e)?Math.min(r,Math.max(1,Math.floor(e))):t}class ${controlPlane;nowFn;idFn;workerId;leaseMs;limits;constructor(e,t={}){this.controlPlane=e;this.workerId=t.workerId??`worker:${process.pid}:${crypto.randomUUID()}`,this.leaseMs=H(t.leaseMs,60000,3600000),this.nowFn=t.now??(()=>new Date),this.idFn=t.id??(()=>crypto.randomUUID());let r=e.getSetting("queue.concurrency");this.limits={project:H(t.limits?.project??r?.project,tt.project,100),environment:H(t.limits?.environment??r?.environment,tt.environment,100),provider:H(t.limits?.provider??r?.provider,tt.provider,100),builds:H(t.limits?.builds??r?.builds,tt.builds,100)}}now(){return this.nowFn().toISOString()}leaseExpiry(){return new Date(this.nowFn().getTime()+this.leaseMs).toISOString()}enqueue(e){let t=this.controlPlane.createOperation(e);if(this.getJob(t.id))return this.view(t.id);let s=this.now(),n=H(e.maxAttempts,1,20),i=H(e.timeoutSeconds,1800,86400),E=H(e.retentionDays,Li,3650),a=e.availableAt?new Date(e.availableAt).toISOString():s,d=new Date(this.nowFn().getTime()+E*24*60*60*1000).toISOString();this.controlPlane.database.run(`INSERT OR IGNORE INTO operation_jobs (operation_id, lock_key, provider_key, build_slot, max_attempts, available_at, timeout_seconds, retry_classes, resume_policy, cancellation_mode, retention_until, created_at, updated_at)
|
|
1556
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,[t.id,e.lockKey??e.resourceId??null,e.providerKey??null,e.buildSlot?1:0,n,a,i,JSON.stringify([...new Set(e.retryClasses??[])]),e.resumePolicy??"fail",e.cancellationMode??"cooperative",d,s,s]),this.appendLog(t.id,"Queued for durable execution.",{stream:"system"});let T=this.view(t.id);return this.notifyAvailable(),T}onAvailable(e){let t=Ns(this.controlPlane);return t.add(e),()=>{if(t.delete(e),t.size===0&&this.controlPlane.path!==":memory:")Gt.delete(this.controlPlane.path)}}notifyAvailable(){for(let e of Ns(this.controlPlane))e()}getJob(e){let t=this.controlPlane.database.query("SELECT * FROM operation_jobs WHERE operation_id=?").get(e);return t?Ci(t):void 0}view(e){let t=this.controlPlane.getOperation(e),r=this.getJob(e);if(!t||!r)return;let s;if(t.state==="queued")s={ahead:Number(this.controlPlane.database.query("SELECT COUNT(*) AS count FROM operations o JOIN operation_jobs j ON j.operation_id=o.id WHERE o.state='queued' AND j.available_at<=? AND (o.priority>? OR (o.priority=? AND o.created_at<?))").get(this.now(),t.priority,t.priority,t.createdAt)?.count??0),precision:"bounded"};return{operation:t,job:r,approximatePosition:s}}list(e={}){return this.controlPlane.listOperations({projectId:e.projectId,state:e.state,limit:e.limit??200}).map((t)=>this.view(t.id)).filter((t)=>!!t)}configureConcurrency(e,t={}){let r={project:H(e.project,this.limits.project,100),environment:H(e.environment,this.limits.environment,100),provider:H(e.provider,this.limits.provider,100),builds:H(e.builds,this.limits.builds,100)};return Object.assign(this.limits,r),this.controlPlane.setSetting("queue.concurrency",r),this.controlPlane.appendEvent({organizationId:t.organizationId,actorId:t.actorId,type:"queue.concurrency.updated",level:"warning",payload:r}),r}runningCount(e,t){return Number(this.controlPlane.database.query(e).get(...t)?.count??0)}blockReason(e,t){if(t.lockKey){if(this.controlPlane.database.query("SELECT * FROM operation_locks WHERE lock_key=? AND operation_id!=? AND lease_expires_at>?").get(t.lockKey,e.id,this.now()))return`resource_lock:${t.lockKey}`}if(e.projectId&&this.runningCount("SELECT COUNT(*) AS count FROM operations WHERE state='running' AND project_id=?",[e.projectId])>=this.limits.project)return"project_concurrency";if(e.environmentId&&this.runningCount("SELECT COUNT(*) AS count FROM operations WHERE state='running' AND environment_id=?",[e.environmentId])>=this.limits.environment)return"environment_concurrency";if(t.providerKey&&this.runningCount("SELECT COUNT(*) AS count FROM operations o JOIN operation_jobs j ON j.operation_id=o.id WHERE o.state='running' AND j.provider_key=?",[t.providerKey])>=this.limits.provider)return"provider_concurrency";if(t.buildSlot&&this.runningCount("SELECT COUNT(*) AS count FROM operations o JOIN operation_jobs j ON j.operation_id=o.id WHERE o.state='running' AND j.build_slot=1",[])>=this.limits.builds)return"build_concurrency";return}claim(e){return this.controlPlane.transaction(()=>{let t=this.now();this.controlPlane.database.run("DELETE FROM operation_locks WHERE lease_expires_at<=?",[t]);let r=this.controlPlane.getOperation(e),s=r?this.getJob(r.id):void 0;if(!r||!s||r.state!=="queued"||s.availableAt>t)return;if(r.cancelRequestedAt){this.controlPlane.transitionOperation(r.id,{to:"cancelled",expectedVersion:r.version,output:{cancelledBeforeStart:!0}}),this.appendLog(r.id,"Cancelled before execution began.",{stream:"system"});return}let n=this.blockReason(r,s);if(n){this.controlPlane.database.run("UPDATE operation_jobs SET blocked_reason=?, updated_at=? WHERE operation_id=?",[n,t,r.id]);return}let i=this.controlPlane.claimOperation(r.id,this.workerId,this.leaseMs);if(!i)return;let E=i.leaseExpiresAt??this.leaseExpiry();if(s.lockKey)this.controlPlane.database.run("INSERT INTO operation_locks (lock_key, operation_id, lease_owner, lease_expires_at, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)",[s.lockKey,r.id,this.workerId,E,t,t]);return this.controlPlane.database.run("UPDATE operation_jobs SET heartbeat_at=?, blocked_reason=NULL, updated_at=? WHERE operation_id=?",[t,t,r.id]),this.appendLog(r.id,`Claimed by ${this.workerId}; attempt ${i.attempt}.`,{stream:"system"}),this.view(r.id)})}claimNext(e){let t=this.now(),r=e?.length?`AND o.kind IN (${e.map(()=>"?").join(",")})`:"",s=this.controlPlane.database.query(`SELECT o.id FROM operations o JOIN operation_jobs j ON j.operation_id=o.id WHERE o.state='queued' AND j.available_at<=? ${r} ORDER BY o.priority DESC, o.created_at ASC, o.rowid ASC LIMIT 100`).all(t,...e??[]);for(let n of s){let i=this.claim(String(n.id));if(i)return i}return}heartbeat(e,t){let r=this.controlPlane.getOperation(e);if(!r||r.state!=="running"||r.leaseOwner!==this.workerId)throw Error("Operation lease is not owned by this worker");let s=this.now(),n=this.leaseExpiry(),i=this.getJob(e);if(this.controlPlane.database.run("UPDATE operations SET lease_expires_at=?, updated_at=?, version=version+1 WHERE id=? AND state='running' AND lease_owner=? AND version=?",[n,s,e,this.workerId,r.version]).changes!==1)throw Error("Operation lease changed before heartbeat");if(this.controlPlane.database.run("UPDATE operation_jobs SET heartbeat_at=?, current_step=COALESCE(?, current_step), updated_at=? WHERE operation_id=?",[s,t??null,s,e]),i?.lockKey)this.controlPlane.database.run("UPDATE operation_locks SET lease_expires_at=?, updated_at=? WHERE lock_key=? AND operation_id=? AND lease_owner=?",[n,s,i.lockKey,e,this.workerId]);if(t&&t!==i?.currentStep)this.controlPlane.appendEvent({projectId:r.projectId,operationId:e,resourceId:r.resourceId,actorId:r.actorId,correlationId:r.correlationId,type:"operation.step",payload:{step:t}})}appendLog(e,t,r={}){if(!this.getJob(e))throw Error(`Queue job ${e} was not found`);let s=t,n=!1;for(let N of r.secrets??[])if(N&&s.includes(N))s=s.split(N).join("[REDACTED]"),n=!0;let i=h({message:s});if(i.message!==s)n=!0;s=i.message??"[output unavailable]";let E=Buffer.from(s),a=E.byteLength>os;if(a)s=`${E.subarray(0,os-32).toString("utf8")}
|
|
1557
|
+
[output truncated]`;let d=this.idFn(),T=this.now();return this.controlPlane.database.run("INSERT INTO operation_logs (id, operation_id, stream, step, message, redacted, truncated, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",[d,e,r.stream??"stdout",r.step??null,s,n?1:0,a?1:0,T]),_s(this.controlPlane.database.query("SELECT * FROM operation_logs WHERE id=?").get(d))}logs(e,t={}){let r=Math.min(1000,Math.max(1,t.limit??200));return this.controlPlane.database.query("SELECT * FROM operation_logs WHERE operation_id=? AND sequence>? ORDER BY sequence ASC LIMIT ?").all(e,t.after??0,r).map(_s)}requestCancellation(e,t){let r=this.controlPlane.getOperation(e);if(!r||mi.includes(r.state))throw Error("Only queued or running operations can be cancelled");let s=this.controlPlane.requestCancellation(e);if(this.controlPlane.appendEvent({projectId:r.projectId,operationId:e,resourceId:r.resourceId,actorId:t,correlationId:r.correlationId,type:"operation.cancellation_requested",level:"warning",payload:{state:r.state}}),this.appendLog(e,r.state==="queued"?"Cancellation accepted before execution.":"Cancellation requested; waiting for the current provider step.",{stream:"system"}),s.state==="queued")return this.finish(e,"cancelled",{cancelledBeforeStart:!0});return s}retry(e,t,r={}){let s=this.controlPlane.getOperation(e),n=this.getJob(e);if(!s||!n||!["failed","cancelled","timed_out"].includes(s.state))throw Error("Only failed, cancelled, or timed-out queue jobs can be retried");if(s.attempt>=n.maxAttempts)throw Error(`Operation reached its ${n.maxAttempts}-attempt limit`);if(!n.retryClasses.includes(t))throw Error(`Retry class ${t} is not allowed for this operation`);let i=this.controlPlane.transitionOperation(e,{to:"queued",expectedVersion:s.version,error:`Retry requested after ${t}`}),E=new Date(this.nowFn().getTime()+Math.max(0,r.delayMs??0)).toISOString();this.controlPlane.database.run("UPDATE operations SET cancel_requested_at=NULL, finished_at=NULL WHERE id=?",[e]),this.controlPlane.database.run("UPDATE operation_jobs SET available_at=?, blocked_reason=NULL, updated_at=? WHERE operation_id=?",[E,this.now(),e]),this.controlPlane.appendEvent({projectId:s.projectId,operationId:e,resourceId:s.resourceId,actorId:r.actorId,correlationId:s.correlationId,type:"operation.retry_requested",payload:{errorClass:t,availableAt:E,nextAttempt:s.attempt+1}}),this.appendLog(e,`Retry queued for error class ${t}.`,{stream:"system"});let a=this.controlPlane.getOperation(i.id);return this.notifyAvailable(),a}release(e){this.controlPlane.database.run("DELETE FROM operation_locks WHERE operation_id=?",[e])}finish(e,t,r={},s){let n=this.controlPlane.getOperation(e);if(!n)throw Error(`Operation ${e} was not found`);let i=this.controlPlane.transitionOperation(e,{to:t,expectedVersion:n.version,output:r,error:s});return this.release(e),i}complete(e,t={}){let r=this.controlPlane.getOperation(e);if(!r||r.state!=="running"||r.leaseOwner!==this.workerId)throw Error("Only the lease owner can complete a running operation");return this.appendLog(e,"Operation completed successfully.",{stream:"system"}),this.finish(e,"succeeded",t)}fail(e,t,r={}){let s=this.controlPlane.getOperation(e);if(!s||s.state!=="running"||s.leaseOwner!==this.workerId)throw Error("Only the lease owner can fail a running operation");return this.appendLog(e,t,{stream:"stderr"}),this.finish(e,"failed",r,t)}async runOne(e){let t=this.claimNext(Object.keys(e));if(!t)return{handled:!1};let{operation:r,job:s}=t,n=e[r.kind];if(!n)return{handled:!1};let i=new AbortController,E=!1,a=setTimeout(()=>{E=!0,i.abort(new et(s.timeoutSeconds))},s.timeoutSeconds*1000),d=setInterval(()=>{try{this.heartbeat(r.id)}catch{}},Math.max(250,Math.floor(this.leaseMs/3))),T=setInterval(()=>{if(this.controlPlane.getOperation(r.id)?.cancelRequestedAt)i.abort(new Se)},100),N={operation:r,signal:i.signal,log:(o,c)=>this.appendLog(r.id,o,c),checkpoint:(o,c)=>{this.heartbeat(r.id,o),this.appendLog(r.id,c??o,{stream:"step",step:o})},heartbeat:()=>this.heartbeat(r.id),cancellationRequested:()=>!!this.controlPlane.getOperation(r.id)?.cancelRequestedAt,throwIfCancellationRequested:()=>{if(this.controlPlane.getOperation(r.id)?.cancelRequestedAt)throw new Se}};try{let o=await Promise.race([n(N),new Promise((_,L)=>i.signal.addEventListener("abort",()=>L(i.signal.reason),{once:!0}))]);if(this.controlPlane.getOperation(r.id).cancelRequestedAt){let _=s.cancellationMode==="provider_non_cancellable";return this.appendLog(r.id,_?"Provider step completed after cancellation was requested; reconciliation is required.":"Operation cancelled cooperatively.",{stream:"system"}),{handled:!0,operation:this.finish(r.id,"cancelled",{providerCompleted:_,reconciliationRequired:_}),terminalState:"cancelled"}}return{handled:!0,operation:this.complete(r.id,o??{}),terminalState:"succeeded"}}catch(o){if(E||o instanceof et)return this.appendLog(r.id,`Timed out after ${s.timeoutSeconds}s.`,{stream:"system"}),{handled:!0,operation:this.finish(r.id,"timed_out",{reconciliationRequired:s.cancellationMode==="provider_non_cancellable"},o instanceof Error?o.message:String(o)),terminalState:"timed_out"};if(o instanceof Se||this.controlPlane.getOperation(r.id)?.cancelRequestedAt)return{handled:!0,operation:this.finish(r.id,"cancelled",{cooperativelyCancelled:!0}),terminalState:"cancelled"};if(o instanceof J&&s.retryClasses.includes(o.errorClass)&&r.attempt<s.maxAttempts){let _=this.controlPlane.getOperation(r.id);this.controlPlane.transitionOperation(r.id,{to:"queued",expectedVersion:_.version,error:o.message});let L=Math.min(60000,1000*2**Math.max(0,r.attempt-1)),u=new Date(this.nowFn().getTime()+L).toISOString();return this.controlPlane.database.run("UPDATE operation_jobs SET available_at=?, blocked_reason=?, updated_at=? WHERE operation_id=?",[u,`retry_backoff:${o.errorClass}`,this.now(),r.id]),this.release(r.id),this.appendLog(r.id,`Retryable ${o.errorClass} failure; next attempt is delayed.`,{stream:"system"}),{handled:!0,operation:this.controlPlane.getOperation(r.id),requeued:!0}}let c=o instanceof Error?o.message:String(o);return this.appendLog(r.id,c,{stream:"stderr"}),{handled:!0,operation:this.finish(r.id,"failed",{},c),terminalState:"failed"}}finally{clearTimeout(a),clearInterval(d),clearInterval(T)}}recoverExpired(){let e=this.controlPlane.database.query("SELECT o.id FROM operations o JOIN operation_jobs j ON j.operation_id=o.id WHERE o.state='running' AND (o.lease_expires_at IS NULL OR o.lease_expires_at<=?)").all(this.now()),t={requeued:0,failed:0,cancelled:0};for(let r of e){let s=this.controlPlane.getOperation(String(r.id)),n=this.getJob(s.id);if(this.release(s.id),s.cancelRequestedAt){this.finish(s.id,"cancelled",{recoveredAfterWorkerLoss:!0}),t.cancelled++;continue}if(n.resumePolicy==="requeue"&&s.attempt<n.maxAttempts)this.controlPlane.transitionOperation(s.id,{to:"queued",expectedVersion:s.version,error:"Worker lease expired; safely requeued from the last checkpoint."}),this.controlPlane.database.run("UPDATE operation_jobs SET available_at=?, blocked_reason='worker_restart', updated_at=? WHERE operation_id=?",[this.now(),this.now(),s.id]),this.appendLog(s.id,"Worker lease expired; operation requeued from its persisted checkpoint.",{stream:"system"}),t.requeued++;else this.finish(s.id,"failed",{recoverable:!0,lastCheckpoint:n.currentStep??null},"Worker lease expired; retry explicitly after reconciling provider state."),t.failed++}return t}clearCompleted(e={}){let t=this.now(),r=[t],s=e.projectId?"AND o.project_id=?":"";if(e.projectId)r.push(e.projectId);let n=e.before?"AND o.finished_at<=?":"";if(e.before)r.push(new Date(e.before).toISOString());let i=this.controlPlane.database.query(`SELECT o.id, o.project_id, o.correlation_id FROM operations o JOIN operation_jobs j ON j.operation_id=o.id WHERE o.state IN ('succeeded','failed','cancelled','timed_out') AND j.retention_until<=? ${s} ${n}`).all(...r);for(let E of i)this.controlPlane.appendEvent({projectId:Z(E.project_id),actorId:e.actorId,correlationId:String(E.correlation_id),type:"queue.history.cleared",level:"warning",payload:{operationId:String(E.id),retentionCutoff:t}}),this.controlPlane.database.run("DELETE FROM operations WHERE id=?",[String(E.id)]);return i.length}}function ls(e,t,r){if(!Number.isFinite(e))return t;return Math.min(r,Math.max(1,Math.floor(e)))}class gi{queue;handlers;parallelism;pollIntervalMs;onResult;onError;waits=new Map;nextWaitId=0;lanes=[];running=!1;unsubscribeAvailability;constructor(e,t,r={}){this.queue=e;this.handlers=t;this.parallelism=ls(r.parallelism,4,100),this.pollIntervalMs=ls(r.pollIntervalMs,5000,60000),this.onResult=r.onResult,this.onError=r.onError}get active(){return this.running}start(){if(this.running)return this;return this.running=!0,this.unsubscribeAvailability=this.queue.onAvailable(()=>this.wake()),this.lanes=Array.from({length:this.parallelism},(e,t)=>this.runLane(t)),this}stop(){this.running=!1,this.unsubscribeAvailability?.(),this.unsubscribeAvailability=void 0,this.wake()}wake(){for(let{resolve:e,timer:t}of this.waits.values()){if(t)clearTimeout(t);e()}this.waits.clear()}async settled(){await Promise.allSettled(this.lanes)}async drain(){let e=[];while(!0){let t=await Promise.all(Array.from({length:this.parallelism},()=>this.queue.runOne(this.handlers)));if(e.push(...t.filter((r)=>r.handled)),!t.some((r)=>r.handled))return e}}wait(e){return new Promise((t)=>{let r=++this.nextWaitId,s=e?setTimeout(()=>{this.waits.delete(r),t()},this.pollIntervalMs):void 0;s?.unref?.(),this.waits.set(r,{resolve:t,timer:s})})}async runLane(e){while(this.running){try{let t=await this.queue.runOne(this.handlers);if(!this.running)break;if(t.handled){this.onResult?.(t),this.wake();continue}}catch(t){this.onError?.(t)}await this.wait(e===0)}}}import{createHash as Ri}from"node:crypto";var Cs=/^[a-z0-9](?:[a-z0-9_.-]{0,62})$/,Oi=/^[A-Za-z_][A-Za-z0-9_]{0,127}$/,Ai=/(?:secret|token|password|passwd|private.?key|api.?key|credential)/i,Ui=new Set(["image","build","command","entrypoint","environment","ports","volumes","networks","depends_on","healthcheck","restart","deploy","labels"]),Ls=new Map([["privileged","Privileged containers are blocked."],["network_mode","Host/custom network modes are blocked."],["pid","Host PID namespaces are blocked."],["ipc","Host IPC namespaces are blocked."],["devices","Device access is blocked."],["cap_add","Additional Linux capabilities require an external policy exception."],["security_opt","Custom security options require an external policy exception."]]);function I(e){return e&&typeof e==="object"&&!Array.isArray(e)?e:{}}function Vt(e){return Array.isArray(e)?e:e==null?[]:[e]}function hi(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"").slice(0,63)||"compose-app"}function Xe(e){if(Array.isArray(e))return e.map(String);if(typeof e==="string"&&e.trim())return["sh","-c",e];return}function Qt(e,t){let r=String(e??""),s=/^(\d+(?:\.\d+)?)(ms|s|m|h)?$/.exec(r);if(!s)return t;let n=Number(s[1]);return Math.max(0,Math.round(n*(s[2]==="ms"?0.001:s[2]==="m"?60:s[2]==="h"?3600:1)))}function ms(e){let t=/^(\d+(?:\.\d+)?)\s*(b|k|kb|kib|m|mb|mib|g|gb|gib)?$/i.exec(String(e??""));if(!t)return;let r=Number(t[1]),s=String(t[2]??"b").toLowerCase(),n=r*(s.startsWith("g")?1073741824:s.startsWith("m")?1048576:s.startsWith("k")?1024:1);return Math.max(1,Math.round(n/1024/1024))}function fi(e){if(e==null)return"";return/^\$\{([A-Za-z_][A-Za-z0-9_]*)(?::[-?][^}]*)?\}$/.exec(String(e))?.[1]}function gs(e,t,r){let s={},n=Array.isArray(e)?e.map((i)=>{let[E,...a]=String(i).split("=");return[E,a.length?a.join("="):null]}):Object.entries(I(e));for(let[i,E]of n){let a=String(i);if(!Oi.test(a)){r.push({severity:"error",path:`${t}.${a}`,code:"environment.name",message:"Environment variable names must use portable shell identifiers."});continue}let d=fi(E);if(d!==void 0){s[a]={secretRef:d||a};continue}if(Ai.test(a)){r.push({severity:"error",path:`${t}.${a}`,code:"secret.literal",message:"Literal secret-like environment values are not stored.",alternative:`Use \${${a}} and supply ${a} through the secret store.`}),s[a]={secretRef:a};continue}s[a]=String(E??"")}return s}function Si(e,t,r){let s=[];for(let[n,i]of Vt(e).entries()){let E,a,d="tcp";if(typeof i==="number")E=i;else if(typeof i==="string"){let[T,N]=i.split("/");d=N==="udp"?"udp":"tcp";let o=T.split(":");E=Number(o.at(-1)),a=o.length>1?Number(o.at(-2)):void 0}else{let T=I(i);E=Number(T.target),a=T.published==null?void 0:Number(T.published),d=T.protocol==="udp"?"udp":"tcp"}if(!Number.isInteger(E)||E<1||E>65535||a!=null&&(!Number.isInteger(a)||a<1||a>65535))r.push({severity:"error",path:`${t}[${n}]`,code:"port.invalid",message:"Ports must be integers from 1 through 65535."});else s.push({target:E,...a?{published:a}:{},protocol:d})}return s}function Xi(e,t,r,s){let n=[];for(let[i,E]of Vt(e).entries()){let a="",d="",T=!1,N="volume";if(typeof E==="string"){let o=E.split(":");a=o[0]??"",d=o[1]??"",T=o.slice(2).includes("ro"),N=a.startsWith("/")||a.startsWith(".")?"bind":"volume"}else{let o=I(E);a=String(o.source??""),d=String(o.target??""),T=o.read_only===!0,N=String(o.type??"volume")}if(N!=="volume"||a.startsWith("/")||a.startsWith(".")){s.push({severity:"error",path:`${r}[${i}]`,code:"volume.host_mount",message:"Host and bind mounts are blocked; use a named volume.",alternative:"Declare the volume at the top level and mount it by name."});continue}if(!Cs.test(a)||!d.startsWith("/")){s.push({severity:"error",path:`${r}[${i}]`,code:"volume.invalid",message:"A named source and absolute container target are required."});continue}if(!t.has(a))s.push({severity:"warning",path:`${r}[${i}]`,code:"volume.implicit",message:`Named volume ${a} is implicit and will be declared on export.`});t.add(a),n.push({source:a,target:d,readOnly:T})}return n}function Ii(e){return Array.isArray(e)?e.map((t)=>({service:String(t),condition:"service_started"})):Object.entries(I(e)).map(([t,r])=>({service:t,condition:["service_started","service_healthy","service_completed_successfully"].includes(String(I(r).condition))?String(I(r).condition):"service_started"}))}function us(e){let t=I(e);if(!Object.keys(t).length||t.disable===!0)return;let r=Xe(t.test);return r?{test:r,intervalSeconds:Qt(t.interval,30),timeoutSeconds:Qt(t.timeout,5),retries:Math.max(1,Number(t.retries)||3),...t.start_period?{startPeriodSeconds:Qt(t.start_period,0)}:{}}:void 0}function vi(e,t,r){if(!e)return;let s=typeof e==="string"?{context:e}:I(e),n=String(s.context??"."),i=String(s.dockerfile??"Dockerfile");if(n.startsWith("/")||n.split("/").includes("..")||i.startsWith("/")||i.split("/").includes(".."))r.push({severity:"error",path:t,code:"build.traversal",message:"Build context and Dockerfile must stay inside the repository."});return{context:n,dockerfile:i,...s.target?{target:String(s.target)}:{},args:gs(s.args,`${t}.args`,r)}}function yi(e,t,r){let s=Array.isArray(e)?Object.fromEntries(e.map((i)=>String(i).split("=",2))):I(e),n=String(s["ts-cloud.domains"]??s["ts-cloud.domain"]??"").split(",").map((i)=>i.trim()).filter(Boolean);for(let i of n)if(!/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/i.test(i))r.push({severity:"error",path:t,code:"domain.invalid",message:`Invalid service domain ${i}.`});return n.filter((i)=>/\./.test(i))}function pi(e,t){let r=[],s=new Set,n=new Set,i=(E)=>{if(n.has(E))return;if(s.has(E)){t.push({severity:"error",path:`services.${E}.depends_on`,code:"dependency.cycle",message:"Service dependencies contain a cycle."});return}s.add(E);for(let a of e[E]?.dependsOn??[])if(!e[a.service])t.push({severity:"error",path:`services.${E}.depends_on.${a.service}`,code:"dependency.missing",message:`Dependency ${a.service} does not exist.`});else i(a.service);s.delete(E),n.add(E),r.push(E)};return Object.keys(e).sort().forEach(i),r}function se(e,t){if(Buffer.byteLength(e)>524288)throw Error("Compose source exceeds the 512 KiB limit");let r;try{r=I(Bun.YAML.parse(e))}catch(c){throw Error(`Compose YAML could not be parsed: ${c instanceof Error?c.message:String(c)}`)}let s=[],n=I(r.services),i=Object.keys(n);if(!i.length)s.push({severity:"error",path:"services",code:"services.required",message:"At least one service is required."});if(i.length>64)s.push({severity:"error",path:"services",code:"services.limit",message:"A Compose application may contain at most 64 services."});let E=new Set(Object.keys(I(r.volumes))),a=new Set(Object.keys(I(r.networks))),d={};for(let c of i.sort()){let l=`services.${c}`,_=I(n[c]);if(!Cs.test(c)){s.push({severity:"error",path:l,code:"service.name",message:"Service names must be portable lowercase identifiers."});continue}for(let[X,Is]of Ls)if(_[X]!=null&&_[X]!==!1&&(!Array.isArray(_[X])||_[X].length))s.push({severity:"error",path:`${l}.${X}`,code:`unsafe.${X}`,message:Is});for(let X of Object.keys(_))if(!Ui.has(X)&&!Ls.has(X))s.push({severity:"warning",path:`${l}.${X}`,code:"field.unsupported",message:`Compose field ${X} is not in the supported subset and will not be deployed.`});let L=_.image?String(_.image):void 0,u=vi(_.build,`${l}.build`,s);if(!L&&!u)s.push({severity:"error",path:l,code:"service.source",message:"Each service requires image or build."});if(L&&!L.includes("@sha256:")&&(!L.includes(":")||L.endsWith(":latest")))s.push({severity:"warning",path:`${l}.image`,code:"image.floating",message:`Image ${L} is not pinned to a digest or explicit tag.`});let C=Vt(_.networks).map((X)=>typeof X==="string"?X:String(I(X).name??"")).filter(Boolean);C.forEach((X)=>a.add(X));let g=I(_.deploy),R=I(g.resources),S=I(R.limits),y=["no","always","on-failure","unless-stopped"].includes(String(_.restart))?String(_.restart):"unless-stopped";d[c]={name:c,...L?{image:L}:{},...u?{build:u}:{},...Xe(_.command)?{command:Xe(_.command)}:{},...Xe(_.entrypoint)?{entrypoint:Xe(_.entrypoint)}:{},environment:gs(_.environment,`${l}.environment`,s),ports:Si(_.ports,`${l}.ports`,s),volumes:Xi(_.volumes,E,`${l}.volumes`,s),networks:C,dependsOn:Ii(_.depends_on),...us(_.healthcheck)?{healthCheck:us(_.healthcheck)}:{},restart:y,...Number(S.cpus)>0?{cpu:Number(S.cpus)}:{},...ms(S.memory)?{memoryMb:ms(S.memory)}:{},replicas:Math.max(1,Math.min(100,Number(g.replicas)||1)),domains:yi(_.labels,`${l}.labels`,s)}}let T=pi(d,s),N={apiVersion:"ts-cloud.dev/v1",kind:"ComposeApplication",metadata:{name:t.name.trim(),slug:hi(t.slug??t.name),projectId:t.projectId,environmentId:t.environmentId},spec:{services:d,networks:[...a].sort(),volumes:[...E].sort(),dependencyOrder:T}},o=Wt(N);return{valid:!s.some((c)=>c.severity==="error"),manifest:N,diagnostics:s,redactedSource:o,sourceHash:Ri("sha256").update(e).digest("hex")}}function Wt(e){let t=Object.fromEntries(Object.entries(e.spec.services).map(([r,s])=>[r,{...s.image?{image:s.image}:{},...s.build?{build:{context:s.build.context,dockerfile:s.build.dockerfile,...s.build.target?{target:s.build.target}:{},...Object.keys(s.build.args).length?{args:Object.fromEntries(Object.entries(s.build.args).map(([n,i])=>[n,typeof i==="string"?i:`\${${i.secretRef}:?required secret ${i.secretRef}}`]))}:{}}}:{},...s.command?{command:s.command}:{},...s.entrypoint?{entrypoint:s.entrypoint}:{},...Object.keys(s.environment).length?{environment:Object.fromEntries(Object.entries(s.environment).map(([n,i])=>[n,typeof i==="string"?i:`\${${i.secretRef}:?required secret ${i.secretRef}}`]))}:{},...s.ports.length?{ports:s.ports.map((n)=>`${n.published?`${n.published}:`:""}${n.target}${n.protocol==="udp"?"/udp":""}`)}:{},...s.volumes.length?{volumes:s.volumes.map((n)=>`${n.source}:${n.target}${n.readOnly?":ro":""}`)}:{},...s.networks.length?{networks:s.networks}:{},...s.dependsOn.length?{depends_on:Object.fromEntries(s.dependsOn.map((n)=>[n.service,{condition:n.condition}]))}:{},...s.healthCheck?{healthcheck:{test:s.healthCheck.test,interval:`${s.healthCheck.intervalSeconds}s`,timeout:`${s.healthCheck.timeoutSeconds}s`,retries:s.healthCheck.retries,...s.healthCheck.startPeriodSeconds!=null?{start_period:`${s.healthCheck.startPeriodSeconds}s`}:{}}}:{},restart:s.restart,...s.cpu||s.memoryMb||s.replicas!==1?{deploy:{replicas:s.replicas,resources:{limits:{...s.cpu?{cpus:String(s.cpu)}:{},...s.memoryMb?{memory:`${s.memoryMb}M`}:{}}}}}:{},...s.domains.length?{labels:{"ts-cloud.domains":s.domains.join(",")}}:{}}]));return Bun.YAML.stringify({services:t,...e.spec.networks.length?{networks:Object.fromEntries(e.spec.networks.map((r)=>[r,{}]))}:{},...e.spec.volumes.length?{volumes:Object.fromEntries(e.spec.volumes.map((r)=>[r,{}]))}:{}})}function Jt(e,t){let r=[],s=(n,i,E)=>{if(JSON.stringify(i)===JSON.stringify(E))return;if(i&&E&&typeof i==="object"&&typeof E==="object"&&!Array.isArray(i)&&!Array.isArray(E)){for(let a of[...new Set([...Object.keys(i),...Object.keys(E)])].sort())s(n?`${n}.${a}`:a,i[a],E[a]);return}r.push({path:n,...i!==void 0?{before:i}:{},...E!==void 0?{after:E}:{}})};return s("",e,t),r}import{createHash as Rs}from"node:crypto";var Di="2026-07-21T00:00:00.000Z",Os=[{id:"bun-postgres-redis",name:"Bun + Postgres + Redis",description:"Web and worker services with durable Postgres and Redis dependencies.",category:"application",version:"1.0.0",sourceVersion:"oven/bun:1.2.21,postgres:17.5,redis:8.0.3",architecture:"web → Postgres + Redis; worker → Postgres + Redis",minimumResources:{cpu:2,memoryMb:3072},exposedServices:["web"],maintenanceNotes:"Back up the Postgres volume before major upgrades.",inputs:[{name:"domain",label:"Web domain",required:!0,secret:!1}],source:`services:
|
|
1558
|
+
web:
|
|
1559
|
+
image: oven/bun:1.2.21
|
|
1560
|
+
command: [bun, run, start]
|
|
1561
|
+
environment:
|
|
1562
|
+
DATABASE_URL: \${DATABASE_URL}
|
|
1563
|
+
REDIS_URL: redis://redis:6379
|
|
1564
|
+
ports: [3000]
|
|
1565
|
+
labels:
|
|
1566
|
+
ts-cloud.domain: {{domain}}
|
|
1567
|
+
depends_on:
|
|
1568
|
+
postgres: { condition: service_healthy }
|
|
1569
|
+
redis: { condition: service_healthy }
|
|
1570
|
+
worker:
|
|
1571
|
+
image: oven/bun:1.2.21
|
|
1572
|
+
command: [bun, run, worker]
|
|
1573
|
+
environment:
|
|
1574
|
+
DATABASE_URL: \${DATABASE_URL}
|
|
1575
|
+
REDIS_URL: redis://redis:6379
|
|
1576
|
+
depends_on:
|
|
1577
|
+
postgres: { condition: service_healthy }
|
|
1578
|
+
redis: { condition: service_healthy }
|
|
1579
|
+
postgres:
|
|
1580
|
+
image: postgres:17.5
|
|
1581
|
+
environment:
|
|
1582
|
+
POSTGRES_PASSWORD: \${POSTGRES_PASSWORD}
|
|
1583
|
+
volumes: [postgres-data:/var/lib/postgresql/data]
|
|
1584
|
+
healthcheck: { test: [CMD-SHELL, pg_isready -U postgres], interval: 10s, timeout: 5s, retries: 5 }
|
|
1585
|
+
redis:
|
|
1586
|
+
image: redis:8.0.3
|
|
1587
|
+
volumes: [redis-data:/data]
|
|
1588
|
+
healthcheck: { test: [CMD, redis-cli, ping], interval: 10s, timeout: 5s, retries: 5 }
|
|
1589
|
+
volumes:
|
|
1590
|
+
postgres-data: {}
|
|
1591
|
+
redis-data: {}
|
|
1592
|
+
`},{id:"wordpress",name:"WordPress",description:"Pinned WordPress and MariaDB with persistent content and database volumes.",category:"cms",version:"1.0.0",sourceVersion:"wordpress:6.8.2-php8.3-apache,mariadb:11.8.2",architecture:"WordPress → MariaDB",minimumResources:{cpu:2,memoryMb:2048},exposedServices:["wordpress"],maintenanceNotes:"Back up both named volumes before image upgrades.",inputs:[{name:"domain",label:"Site domain",required:!0,secret:!1}],source:`services:
|
|
1593
|
+
wordpress:
|
|
1594
|
+
image: wordpress:6.8.2-php8.3-apache
|
|
1595
|
+
environment:
|
|
1596
|
+
WORDPRESS_DB_HOST: db
|
|
1597
|
+
WORDPRESS_DB_PASSWORD: \${WORDPRESS_DB_PASSWORD}
|
|
1598
|
+
ports: [80]
|
|
1599
|
+
labels: { ts-cloud.domain: "{{domain}}" }
|
|
1600
|
+
volumes: [wordpress-data:/var/www/html]
|
|
1601
|
+
depends_on:
|
|
1602
|
+
db: { condition: service_healthy }
|
|
1603
|
+
db:
|
|
1604
|
+
image: mariadb:11.8.2
|
|
1605
|
+
environment:
|
|
1606
|
+
MARIADB_ROOT_PASSWORD: \${MARIADB_ROOT_PASSWORD}
|
|
1607
|
+
MARIADB_DATABASE: wordpress
|
|
1608
|
+
volumes: [database-data:/var/lib/mysql]
|
|
1609
|
+
healthcheck: { test: [CMD, healthcheck.sh, --connect, --innodb_initialized], interval: 10s, timeout: 5s, retries: 8 }
|
|
1610
|
+
volumes:
|
|
1611
|
+
wordpress-data: {}
|
|
1612
|
+
database-data: {}
|
|
1613
|
+
`}];function bi(){return Os.map(({source:e,...t})=>({...t,source:`builtin:${t.id}`,lastVerifiedAt:Di,builtin:!0,checksum:Rs("sha256").update(e).digest("hex"),exposedServices:[...t.exposedServices],inputs:t.inputs.map((r)=>({...r}))}))}function ki(e,t){return bi().find((r)=>r.id===e&&(!t||r.version===t))}function Zt(e,t,r,s){let n=Os.find((E)=>E.id===e&&(!s||E.version===s));if(!n)throw Error(`Compose template ${e}${s?`@${s}`:""} was not found`);let i=n.source;for(let E of n.inputs){let a=t[E.name]??("default"in E?E.default:void 0);if(E.required&&!a)throw Error(`Template input ${E.name} is required`);if(a&&!/^[A-Za-z0-9._:/@+-]{1,255}$/.test(a))throw Error(`Template input ${E.name} contains unsupported characters`);i=i.replaceAll(`{{${E.name}}}`,a??"")}return se(i,r)}function vd(e,t){if(Buffer.byteLength(e)>1048576)throw Error("Compose catalog exceeds the 1 MiB limit");let r;try{r=JSON.parse(e)}catch{throw Error("Compose catalog must be valid JSON")}if(r.apiVersion!=="ts-cloud.dev/compose-catalog/v1"||!Array.isArray(r.templates))throw Error("Compose catalog must use ts-cloud.dev/compose-catalog/v1 and contain templates");let s=String(r.source??"").trim();if(!/^https:\/\//.test(s)&&!s.startsWith("file:"))throw Error("Compose catalog source must be HTTPS or an explicit file: source");let n=[],i=[];for(let[E,a]of r.templates.slice(0,100).entries()){let d=a&&typeof a==="object"&&!Array.isArray(a)?a:{},T=`templates[${E}]`,N=String(d.compose??""),o=Rs("sha256").update(N).digest("hex");if(!/^[a-z0-9][a-z0-9-]{1,62}$/.test(String(d.id??""))||!/^\d+\.\d+\.\d+$/.test(String(d.version??""))){n.push({severity:"error",path:T,code:"template.identity",message:"Template id and semantic version are required."});continue}if(String(d.checksum??"")!==o){n.push({severity:"error",path:`${T}.checksum`,code:"template.checksum",message:"Template checksum does not match its Compose source."});continue}let c=se(N,{name:String(d.name??d.id),projectId:t.projectId,environmentId:t.environmentId});n.push(...c.diagnostics.map((_)=>({..._,path:`${T}.compose.${_.path}`})));let l={id:String(d.id),name:String(d.name??d.id),description:String(d.description??""),category:String(d.category??"custom"),version:String(d.version),source:s,sourceVersion:String(d.sourceVersion??d.version),architecture:String(d.architecture??Object.keys(c.manifest.spec.services).join(" + ")),minimumResources:{cpu:Math.max(0.1,Number(d.minimumResources?.cpu)||1),memoryMb:Math.max(128,Number(d.minimumResources?.memoryMb)||512)},exposedServices:Array.isArray(d.exposedServices)?d.exposedServices.map(String):[],maintenanceNotes:String(d.maintenanceNotes??""),lastVerifiedAt:String(d.lastVerifiedAt??""),inputs:[],checksum:o,builtin:!1};i.push({template:l,parsed:c})}return{valid:!n.some((E)=>E.severity==="error"),source:s,templates:i,diagnostics:n}}function yd(e,t,r,s,n,i){let E=ki(r,i);if(!E)throw Error(`Compose template ${r}@${i} was not found`);let a=Zt(r,s,n,i);return{template:E,parsed:a,templateChanges:Jt(t,a.manifest),userChanges:Jt(t,e)}}var rt=/^[a-z0-9][a-z0-9_.-]{0,62}$/;function v(e){return`'${e.replaceAll("'","'\\''")}'`}function st(e){return`${e.metadata.slug}-${e.metadata.environmentId.slice(0,8)}`.replace(/[^a-z0-9_-]/g,"-").slice(0,63)}function bd(e,t,r={}){let s=st(e),n=`/opt/ts-cloud/compose/${s}`,i=`${n}/compose.yaml`,E=`docker compose --project-name ${v(s)} --file ${v(i)}`;if(r.service&&!rt.test(r.service))throw Error("Unsafe Compose service name");if(t==="deploy"||t==="redeploy"){let a=Buffer.from(Wt(e)).toString("base64");return`install -d -m 0750 ${v(n)} && printf %s ${v(a)} | base64 --decode > ${v(i)} && chmod 0640 ${v(i)} && ${E} config --quiet && ${E} up --detach --remove-orphans --wait${t==="redeploy"?" --force-recreate":""}`}if(t==="start")return`${E} start${r.service?` ${v(r.service)}`:""}`;if(t==="stop")return`${E} stop${r.service?` ${v(r.service)}`:""}`;if(t==="status")return`${E} ps --format json`;return`${E} down --remove-orphans${r.removeVolumes?" --volumes":""}`}function kd(e,t,r){if(!rt.test(t)||!Number.isInteger(r)||r<0||r>100)throw Error("Scale requires a safe service name and 0-100 replicas");let s=st(e);return`docker compose --project-name ${v(s)} --file ${v(`/opt/ts-cloud/compose/${s}/compose.yaml`)} up --detach --wait --scale ${v(`${t}=${r}`)}`}function Fd(e,t,r=200){if(!rt.test(t))throw Error("Unsafe Compose service name");let s=st(e);return`docker compose --project-name ${v(s)} --file ${v(`/opt/ts-cloud/compose/${s}/compose.yaml`)} logs --no-color --timestamps --tail ${Math.max(1,Math.min(2000,Math.floor(r)))} ${v(t)}`}function Pd(e,t,r=["sh"]){if(!rt.test(t)||!r.length||r.some((n)=>n.includes("\x00")||n.length>1024))throw Error("Unsafe Compose shell request");let s=st(e);return`docker compose --project-name ${v(s)} --file ${v(`/opt/ts-cloud/compose/${s}/compose.yaml`)} exec ${v(t)} ${r.map(v).join(" ")}`}import{mkdir as er,readdir as qi,rm as Yi,stat as zi}from"node:fs/promises";import{basename as Gi,resolve as As,sep as Qi}from"node:path";var wt=/^[a-z0-9][a-z0-9_.-]{1,62}$/,Fi=/^0?[0-7]{3}$/,Pi=["/","/boot","/dev","/etc","/proc","/root","/run","/sys","/var/run","/var/lib/docker","/var/lib/containers"],Hi=["create","attach","detach","resize","snapshot","restore","delete","adopt","usage"],Ki=(e)=>e&&typeof e==="object"&&!Array.isArray(e)?e:{};function xi(e){return Object.fromEntries(Hi.map((t)=>[t,{supported:!1,reason:e}]))}function nt(e,t={}){let r=xi("This storage driver does not support the operation.");for(let s of e)r[s]={supported:!0};return{...r,...t}}function Mi(e){if(!e.startsWith("/")||e.includes("\x00")||e.split("/").includes(".."))throw Error("Mount target must be an absolute normalized path without traversal.");let t=e.replace(/\/+$/,"")||"/";if(Pi.some((r)=>t===r||t.startsWith(`${r}/`)))throw Error(`Mount target ${e} is a protected host or runtime path.`)}function ji(e){if(Mi(e.targetPath),e.uid!=null&&(!Number.isInteger(e.uid)||e.uid<0))throw Error("Mount UID must be a non-negative integer.");if(e.gid!=null&&(!Number.isInteger(e.gid)||e.gid<0))throw Error("Mount GID must be a non-negative integer.");if(e.mode&&!Fi.test(e.mode))throw Error("Mount mode must be a three or four digit octal value.");if(e.propagation&&!["private","rprivate","shared","rshared","slave","rslave"].includes(e.propagation))throw Error("Unsupported mount propagation mode.")}class Bi{store;drivers;now;queue;constructor(e,t,r,s=()=>new Date){this.store=e;this.drivers=t;this.now=s;this.queue=r??new $(e.controlPlane)}driver(e,t){let r=this.drivers.find((s)=>s.provider===e&&s.type===t);if(!r)throw Error(`No ${e} ${t} volume driver is configured.`);return r}capability(e,t){let r=e.capabilities[t];if(!r?.supported)throw Error(r?.reason??`${t} is unsupported for this volume.`)}queueMutation(e,t,r,s,n){return this.queue.enqueue({projectId:e.projectId,environmentId:e.environmentId,resourceId:n??e.resourceId,actorId:s,kind:t,idempotencyKey:`${t}:${e.id}:v${e.version}:${JSON.stringify(r)}`,correlationId:`volume:${e.id}`,input:{volumeId:e.id,...r},lockKey:n?`resource:${n}`:`volume:${e.id}`,providerKey:e.provider,maxAttempts:3,retryClasses:["network","provider_throttled","provider_unavailable"],resumePolicy:"fail",cancellationMode:"provider_non_cancellable",retentionDays:365}).operation}create(e){if(!wt.test(e.name))throw Error("Volume names must be 2-63 lowercase letters, numbers, dots, dashes, or underscores.");if(e.capacityBytes!=null&&(!Number.isSafeInteger(e.capacityBytes)||e.capacityBytes<=0))throw Error("Volume capacity must be a positive byte count.");let t=this.store.controlPlane.getProject(e.projectId),r=e.environmentId?this.store.controlPlane.listEnvironments(e.projectId).find((T)=>T.id===e.environmentId):void 0,s=e.resourceId?this.store.controlPlane.getResource(e.resourceId):void 0;if(!t||t.organizationId!==e.organizationId)throw Error("Volume project does not belong to the organization.");if(e.environmentId&&!r)throw Error("Volume environment does not belong to the project.");if(s&&(s.projectId!==e.projectId||s.environmentId!==e.environmentId||s.kind!=="volume"))throw Error("Volume resource does not belong to the selected scope.");let n=s??this.store.controlPlane.createResource({projectId:e.projectId,environmentId:e.environmentId,kind:"volume",slug:e.name,name:e.name,provider:e.provider,desiredState:{type:e.type,capacityBytes:e.capacityBytes??null,filesystem:e.filesystem??null,encrypted:!!e.encrypted}}),i=this.driver(e.provider,e.type),E=i.capabilities(),a=this.store.create({organizationId:e.organizationId,projectId:e.projectId,environmentId:e.environmentId,resourceId:n.id,name:e.name,provider:e.provider,type:e.type,status:"pending",capacityBytes:e.capacityBytes,filesystem:e.filesystem,encrypted:!!e.encrypted,capabilities:E,desiredState:e.desiredState??{},observedState:{}}),d=this.queueMutation(a,"volume.create",{},e.actorId);return this.store.audit({organizationId:a.organizationId,projectId:a.projectId,environmentId:a.environmentId,actorId:e.actorId,type:"volume.create.queued",volumeId:a.id,payload:{name:a.name,type:a.type}}),{volume:a,operation:d}}attach(e,t){let r=this.store.get(e);if(!r)throw Error("Volume was not found.");this.capability(r,"attach"),ji(t);let s=this.store.controlPlane.getResource(t.resourceId);if(!s||s.projectId!==r.projectId||s.environmentId!==r.environmentId)throw Error("Cross-project or cross-environment volume attachments are forbidden.");let n=this.store.upsertAttachment({volumeId:e,resourceId:t.resourceId,targetPath:t.targetPath,readOnly:!!t.readOnly,uid:t.uid,gid:t.gid,mode:t.mode,propagation:t.propagation??"private",driverOptions:t.driverOptions??{},desiredState:"attached",observedState:"pending"}),i=this.queueMutation(r,"volume.attach",{attachmentId:n.id},t.actorId,t.resourceId);return this.store.updateAttachment(n.id,{operationId:i.id}),this.store.update(r.id,r.version,{status:"attaching"}),this.store.audit({organizationId:r.organizationId,projectId:r.projectId,environmentId:r.environmentId,actorId:t.actorId,type:"volume.attach.queued",volumeId:e,payload:{resourceId:t.resourceId,targetPath:t.targetPath}}),{volume:this.store.get(r.id),attachment:this.store.attachment(n.id),operation:i}}detach(e,t,r={}){let s=this.store.get(e),n=this.store.attachment(t);if(!s||!n||n.volumeId!==e)throw Error("Volume attachment was not found.");if(this.capability(s,"detach"),n.observedState==="attached"&&(!r.drained||!r.unmounted))throw Error("Detach requires completed workload drain and filesystem unmount checkpoints.");if(r.force&&r.confirmation!==`detach ${s.name}`)throw Error(`Force detach requires confirmation: detach ${s.name}`);let i=this.store.updateAttachment(t,{desiredState:"detached"}),E=this.queueMutation(s,"volume.detach",{attachmentId:t,force:!!r.force},r.actorId,n.resourceId);return this.store.updateAttachment(t,{operationId:E.id}),this.store.update(s.id,s.version,{status:"detaching"}),{volume:this.store.get(e),attachment:i,operation:E}}resize(e,t,r={}){let s=this.store.get(e);if(!s)throw Error("Volume was not found.");if(this.capability(s,"resize"),!Number.isSafeInteger(t)||t<=0)throw Error("Volume capacity must be a positive byte count.");if(s.capacityBytes!=null&&t<s.capacityBytes)throw Error("Volume shrinking is not supported because it can corrupt data.");let n=s.capabilities.resize;if(n.minimumBytes&&t<n.minimumBytes)throw Error(`Minimum supported capacity is ${n.minimumBytes} bytes.`);if(n.maximumBytes&&t>n.maximumBytes)throw Error(`Maximum supported capacity is ${n.maximumBytes} bytes.`);if(n.online===!1&&!r.drained)throw Error("This resize requires a completed workload drain checkpoint.");let i=this.store.update(e,s.version,{status:"resizing",desiredState:{...s.desiredState,capacityBytes:t}}),E=this.queueMutation(i,"volume.resize",{capacityBytes:t},r.actorId);return{volume:i,operation:E}}snapshot(e,t={}){let r=this.store.get(e);if(!r)throw Error("Volume was not found.");this.capability(r,"snapshot");let s=this.store.createSnapshot({volumeId:e,name:t.name??`${r.name}-${this.now().toISOString().replace(/[:.]/g,"-")}`,status:"pending",encrypted:r.encrypted,metadata:{}}),n=this.store.update(e,r.version,{status:"snapshotting"}),i=this.queueMutation(n,"volume.snapshot",{snapshotId:s.id},t.actorId);return{volume:n,snapshot:s,operation:i}}restore(e,t){let r=this.store.snapshot(e),s=r?this.store.get(r.volumeId):void 0;if(!r||!s||r.status!=="available")throw Error("An available volume snapshot is required for restore.");if(this.capability(s,"restore"),!wt.test(t.name))throw Error("Restored volume names must be 2-63 lowercase letters, numbers, dots, dashes, or underscores.");let n=this.store.controlPlane.createResource({projectId:s.projectId,environmentId:s.environmentId,kind:"volume",slug:t.name,name:t.name,provider:s.provider,desiredState:{restoreFromSnapshotId:r.id}}),i=this.store.create({organizationId:s.organizationId,projectId:s.projectId,environmentId:s.environmentId,resourceId:n.id,name:t.name,provider:s.provider,type:s.type,status:"pending",capacityBytes:r.sizeBytes??s.capacityBytes,filesystem:s.filesystem,encrypted:r.encrypted,capabilities:s.capabilities,desiredState:{restoreFromSnapshotId:r.id,sourceVolumeId:s.id},observedState:{}}),E=this.queueMutation(i,"volume.restore",{snapshotId:r.id,sourceVolumeId:s.id},t.actorId);return{volume:i,snapshot:r,operation:E}}delete(e,t){let r=this.store.get(e);if(!r)throw Error("Volume was not found.");this.capability(r,"delete");let s=this.store.attachments(e).filter((a)=>a.desiredState==="attached"||a.observedState==="attached");if(s.length)throw Error(`Detach ${s.length} active consumer(s) before deleting this volume.`);if(!t.recentAuthAt||this.now().getTime()-new Date(t.recentAuthAt).getTime()>900000)throw Error("Volume deletion requires authentication verified within the last 15 minutes.");if(t.confirmation!==r.name)throw Error(`Type ${r.name} to confirm permanent volume deletion.`);if(!(r.lastBackupAt&&this.now().getTime()-new Date(r.lastBackupAt).getTime()<=86400000)&&(!t.backupOverride||t.backupOverrideConfirmation!==`delete without backup ${r.name}`))throw Error(`A verified backup from the last 24 hours is required, or confirm: delete without backup ${r.name}`);let i=this.store.update(e,r.version,{status:"deleting"}),E=this.queueMutation(i,"volume.delete",{backupOverride:!!t.backupOverride},t.actorId);return{volume:i,operation:E}}async discover(e){let t=new Set;for(let r of this.drivers)for(let s of await r.discover(e.projectId,e.environmentId)){let n=this.store.list({projectId:e.projectId,environmentId:e.environmentId}).find((E)=>E.provider===r.provider&&E.providerId===s.providerId);if(n){t.add(n.id),this.store.update(n.id,n.version,{capacityBytes:s.capacityBytes,usedBytes:s.usedBytes,filesystem:s.filesystem,encrypted:s.encrypted??n.encrypted,observedState:s.raw??{},status:n.status==="orphaned"?"available":n.status,orphanedAt:void 0});continue}let i=this.store.create({organizationId:e.organizationId,projectId:e.projectId,environmentId:e.environmentId,name:`orphan-${s.providerId}`.toLowerCase().replace(/[^a-z0-9_.-]/g,"-").slice(0,63),provider:r.provider,providerId:s.providerId,type:r.type,status:"orphaned",capacityBytes:s.capacityBytes,usedBytes:s.usedBytes,filesystem:s.filesystem,encrypted:!!s.encrypted,capabilities:r.capabilities(),desiredState:{},observedState:s.raw??{},orphanedAt:this.now().toISOString()});t.add(i.id)}return this.store.list({projectId:e.projectId,environmentId:e.environmentId})}adopt(e,t){let r=this.store.get(e);if(!r||r.status!=="orphaned")throw Error("Only discovered orphan volumes can be adopted.");if(!wt.test(t.name))throw Error("Volume names must be 2-63 lowercase letters, numbers, dots, dashes, or underscores.");let s=this.store.update(e,r.version,{name:t.name,status:"available",adoptedAt:this.now().toISOString(),orphanedAt:void 0});return this.store.audit({organizationId:r.organizationId,projectId:r.projectId,environmentId:r.environmentId,actorId:t.actorId,type:"volume.adopted",volumeId:e,payload:{providerId:r.providerId??null}}),s}}function $i(e){let t=Ki(e.input);if(typeof t.volumeId!=="string")throw Error("Queued volume operation is missing its volume identity.");return{volumeId:t.volumeId,sourceVolumeId:typeof t.sourceVolumeId==="string"?t.sourceVolumeId:void 0,attachmentId:typeof t.attachmentId==="string"?t.attachmentId:void 0,snapshotId:typeof t.snapshotId==="string"?t.snapshotId:void 0,capacityBytes:typeof t.capacityBytes==="number"?t.capacityBytes:void 0,force:t.force===!0}}function xd(e,t){let r=(n)=>{let i=t.find((E)=>E.provider===n.provider&&E.type===n.type);if(!i)throw Error(`No ${n.provider} ${n.type} volume driver is configured.`);return i},s=async(n)=>{let i=$i(n.operation),E=e.get(i.volumeId);if(!E)throw Error("Queued volume was not found.");let a=r(E);n.checkpoint("preflight","Validating persistent volume identity and capabilities."),n.throwIfCancellationRequested();let d;if(n.operation.kind==="volume.create")d=await a.create(E),e.update(E.id,E.version,{providerId:d.providerId,capacityBytes:d.capacityBytes??E.capacityBytes,usedBytes:d.usedBytes,filesystem:d.filesystem??E.filesystem,encrypted:d.encrypted??E.encrypted,observedState:d.raw??{},status:"available"});else if(n.operation.kind==="volume.attach"){let T=i.attachmentId?e.attachment(i.attachmentId):void 0;if(!T)throw Error("Queued volume attachment was not found.");await a.attach(E,T),e.updateAttachment(T.id,{observedState:"attached",lastError:void 0}),e.update(E.id,E.version,{status:"attached"})}else if(n.operation.kind==="volume.detach"){let T=i.attachmentId?e.attachment(i.attachmentId):void 0;if(!T)throw Error("Queued volume attachment was not found.");await a.detach(E,T,{force:i.force}),e.updateAttachment(T.id,{observedState:"detached",lastError:void 0});let N=e.attachments(E.id).some((o)=>o.id!==T.id&&o.observedState==="attached");e.update(E.id,E.version,{status:N?"attached":"available"})}else if(n.operation.kind==="volume.resize"){if(!i.capacityBytes)throw Error("Queued volume resize is missing its capacity.");d=await a.resize(E,i.capacityBytes),e.update(E.id,E.version,{capacityBytes:d.capacityBytes??i.capacityBytes,usedBytes:d.usedBytes??E.usedBytes,observedState:d.raw??E.observedState,status:e.attachments(E.id).some((T)=>T.observedState==="attached")?"attached":"available"})}else if(n.operation.kind==="volume.snapshot"){let T=i.snapshotId?e.snapshot(i.snapshotId):void 0;if(!T)throw Error("Queued volume snapshot was not found.");let N=await a.snapshot(E,T);e.updateSnapshot(T.id,{...N,status:"available"}),e.update(E.id,E.version,{status:e.attachments(E.id).some((o)=>o.observedState==="attached")?"attached":"available",lastBackupAt:new Date().toISOString()})}else if(n.operation.kind==="volume.restore"){let T=i.snapshotId?e.snapshot(i.snapshotId):void 0,N=i.sourceVolumeId?e.get(i.sourceVolumeId):void 0;if(!T||!N)throw Error("Queued volume restore source was not found.");d=await a.restore(N,T,E),e.update(E.id,E.version,{providerId:d.providerId,capacityBytes:d.capacityBytes??E.capacityBytes,usedBytes:d.usedBytes,filesystem:d.filesystem??E.filesystem,encrypted:d.encrypted??E.encrypted,observedState:d.raw??{},status:"available"})}else if(n.operation.kind==="volume.delete")await a.delete(E),e.update(E.id,E.version,{status:"deleted",deletedAt:new Date().toISOString()});return n.checkpoint("finalize","Persisted observed volume state."),{volumeId:E.id,status:e.get(E.id)?.status??"deleted"}};return Object.fromEntries(["volume.create","volume.attach","volume.detach","volume.resize","volume.snapshot","volume.restore","volume.delete"].map((n)=>[n,s]))}var Vi=async(e)=>{let t=Bun.spawn(["docker",...e],{stdout:"pipe",stderr:"pipe"}),[r,s,n]=await Promise.all([t.exited,new Response(t.stdout).text(),new Response(t.stderr).text()]);return{code:r,stdout:s,stderr:n}},ne=/^[A-Za-z0-9][A-Za-z0-9_.-]{1,127}$/;class tr{command;provider="docker";type="docker";constructor(e=Vi){this.command=e}capabilities(){return nt(["create","attach","detach","delete","adopt","usage"],{resize:{supported:!1,reason:"Docker named volumes do not expose a portable capacity resize operation."},snapshot:{supported:!1,reason:"Attach a volume backup policy to create verified recovery points."},restore:{supported:!1,reason:"Restore this volume from its verified backup recovery point."}})}async run(e,t=!1){let r=await this.command(e);if(r.code!==0&&!t)throw Error(`Docker volume command failed: ${r.stderr.trim()||r.code}`);return r.stdout}async inspect(e){let t=await this.run(["volume","inspect",e],!0);if(!t.trim())return;let r;try{r=JSON.parse(t)[0]}catch{return}return{providerId:String(r.Name??e),raw:{driver:String(r.Driver??"local"),mountpoint:String(r.Mountpoint??""),labels:r.Labels??{}}}}async discover(e,t){let r=await this.run(["volume","ls","--format","{{.Name}}"]),s=[];for(let n of r.split(`
|
|
1614
|
+
`).map((i)=>i.trim()).filter(ne.test.bind(ne))){let i=await this.inspect(n);if(i)s.push(i)}return s}async create(e){let t=typeof e.desiredState.nativeName==="string"?e.desiredState.nativeName:e.name;if(!ne.test(t))throw Error("Docker volume name is invalid.");return await this.run(["volume","create","--label",`ts-cloud.volume=${e.id}`,"--label",`ts-cloud.project=${e.projectId}`,t]),await this.inspect(t)??{providerId:t}}async attach(e,t){}async detach(e,t){}async resize(){throw Error(this.capabilities().resize.reason)}async snapshot(){throw Error(this.capabilities().snapshot.reason)}async restore(e,t,r){throw Error(this.capabilities().restore.reason)}async delete(e){if(!e.providerId||!ne.test(e.providerId))throw Error("Docker volume provider identity is missing or invalid.");await this.run(["volume","rm",e.providerId])}async usage(e){let t=await this.run(["system","df","-v","--format","{{json .}}"],!0),r=t.split(`
|
|
1615
|
+
`).find((s)=>s.includes(`"Name":"${e.providerId}"`));if(!r)return{};try{let s=JSON.parse(r);return{usedBytes:typeof s.Size==="number"?s.Size:void 0}}catch{return{}}}}class Wi{provider="server";type="server_path";root;constructor(e){this.root=As(e)}capabilities(){return nt(["create","attach","detach","delete","adopt","usage"],{resize:{supported:!1,reason:"Server paths use the capacity of their containing filesystem."},snapshot:{supported:!1,reason:"Attach a filesystem backup policy for verified recovery points."},restore:{supported:!1,reason:"Restore a filesystem recovery point into a new managed path."}})}path(e){if(!ne.test(e))throw Error("Server path volume name is invalid.");let t=As(this.root,e);if(!t.startsWith(`${this.root}${Qi}`)||Gi(t)!==e)throw Error("Server path escaped the managed volume root.");return t}async discover(e,t){return await er(this.root,{recursive:!0,mode:488}),(await qi(this.root,{withFileTypes:!0})).filter((s)=>s.isDirectory()&&ne.test(s.name)).map((s)=>({providerId:s.name,raw:{path:this.path(s.name)}}))}async create(e){let t=typeof e.desiredState.nativeName==="string"?e.desiredState.nativeName:e.name,r=this.path(t);return await er(this.root,{recursive:!0,mode:488}),await er(r,{recursive:!1,mode:488}),{providerId:t,filesystem:"host",raw:{path:r}}}async attach(e,t){}async detach(e,t){}async resize(){throw Error(this.capabilities().resize.reason)}async snapshot(){throw Error(this.capabilities().snapshot.reason)}async restore(e,t,r){throw Error(this.capabilities().restore.reason)}async delete(e){if(!e.providerId)throw Error("Server path provider identity is unavailable.");await Yi(this.path(e.providerId),{recursive:!0,force:!1})}async usage(e){if(!e.providerId)return{};return{usedBytes:(await zi(this.path(e.providerId))).size}}}class Ji{provider;transport;type="ebs";constructor(e,t){this.provider=e;this.transport=t}capabilities(){return nt(["create","attach","detach","resize","snapshot","restore","delete","adopt","usage"],{resize:{supported:!0,online:!0,minimumBytes:1073741824}})}discover(e,t){return this.transport.list(e,t)}create(e){return this.transport.create({name:e.name,projectId:e.projectId,environmentId:e.environmentId,capacityBytes:e.capacityBytes,encrypted:e.encrypted,filesystem:e.filesystem})}resource(e,t){let r=e.resourceId===t.resourceId?e.observedState.resourceProviderId:void 0,s=typeof r==="string"?r:t.driverOptions.resourceProviderId;if(typeof s!=="string"||!s)throw Error("Cloud attachment requires a reviewed resourceProviderId driver option.");return s}attach(e,t){return this.transport.attach(e.providerId,this.resource(e,t),t.targetPath,t.readOnly)}detach(e,t,r){return this.transport.detach(e.providerId,this.resource(e,t),r.force)}resize(e,t){return this.transport.resize(e.providerId,t)}snapshot(e,t){return this.transport.snapshot(e.providerId,t.name)}restore(e,t,r){if(!t.providerId)throw Error("Cloud snapshot provider identity is unavailable.");return this.transport.restore(e.providerId,t.providerId,r.name)}delete(e){return this.transport.delete(e.providerId)}usage(e){return this.transport.usage(e.providerId)}}var b=(e)=>e==null?void 0:String(e),Ie=(e,t)=>{try{return JSON.parse(String(e))}catch{return t}},sr=(e)=>Number(e)===1,ie=(e)=>e().toISOString();function Us(e){return{id:String(e.id),organizationId:String(e.organization_id),projectId:String(e.project_id),environmentId:b(e.environment_id),resourceId:b(e.resource_id),name:String(e.name),provider:String(e.provider),providerId:b(e.provider_id),type:String(e.type),status:String(e.status),capacityBytes:e.capacity_bytes==null?void 0:Number(e.capacity_bytes),usedBytes:e.used_bytes==null?void 0:Number(e.used_bytes),filesystem:b(e.filesystem),encrypted:sr(e.encrypted),capabilities:Ie(e.capabilities,{}),desiredState:Ie(e.desired_state,{}),observedState:Ie(e.observed_state,{}),backupPolicyId:b(e.backup_policy_id),lastBackupAt:b(e.last_backup_at),orphanedAt:b(e.orphaned_at),adoptedAt:b(e.adopted_at),version:Number(e.version),createdAt:String(e.created_at),updatedAt:String(e.updated_at),deletedAt:b(e.deleted_at)}}function rr(e){return{id:String(e.id),volumeId:String(e.volume_id),resourceId:String(e.resource_id),targetPath:String(e.target_path),readOnly:sr(e.read_only),uid:e.uid==null?void 0:Number(e.uid),gid:e.gid==null?void 0:Number(e.gid),mode:b(e.mode),propagation:String(e.propagation),driverOptions:Ie(e.driver_options,{}),desiredState:String(e.desired_state),observedState:String(e.observed_state),operationId:b(e.operation_id),lastError:b(e.last_error),version:Number(e.version),createdAt:String(e.created_at),updatedAt:String(e.updated_at)}}function hs(e){return{id:String(e.id),volumeId:String(e.volume_id),recoveryPointId:b(e.recovery_point_id),providerId:b(e.provider_id),name:String(e.name),status:String(e.status),sizeBytes:e.size_bytes==null?void 0:Number(e.size_bytes),encrypted:sr(e.encrypted),checksum:b(e.checksum),metadata:Ie(e.metadata,{}),createdAt:String(e.created_at),updatedAt:String(e.updated_at),deletedAt:b(e.deleted_at)}}class it{controlPlane;now;id;constructor(e,t=()=>new Date,r=()=>crypto.randomUUID()){this.controlPlane=e;this.now=t;this.id=r}get(e){let t=this.controlPlane.database.query("SELECT * FROM persistent_volumes WHERE id=?").get(e);return t?Us(t):void 0}list(e){let t=["project_id=?"],r=[e.projectId];if(e.environmentId)t.push("environment_id=?"),r.push(e.environmentId);if(!e.includeDeleted)t.push("deleted_at IS NULL");return this.controlPlane.database.query(`SELECT * FROM persistent_volumes WHERE ${t.join(" AND ")} ORDER BY name`).all(...r).map(Us)}create(e){let t=ie(this.now),r=e.id??this.id();return this.controlPlane.database.run("INSERT INTO persistent_volumes (id,organization_id,project_id,environment_id,resource_id,name,provider,provider_id,type,status,capacity_bytes,used_bytes,filesystem,encrypted,capabilities,desired_state,observed_state,backup_policy_id,last_backup_at,orphaned_at,adopted_at,created_at,updated_at,deleted_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",[r,e.organizationId,e.projectId,e.environmentId??null,e.resourceId??null,e.name,e.provider,e.providerId??null,e.type,e.status,e.capacityBytes??null,e.usedBytes??null,e.filesystem??null,e.encrypted?1:0,JSON.stringify(e.capabilities),JSON.stringify(e.desiredState),JSON.stringify(e.observedState),e.backupPolicyId??null,e.lastBackupAt??null,e.orphanedAt??null,e.adoptedAt??null,t,t,e.deletedAt??null]),this.get(r)}update(e,t,r){let s=this.get(e);if(!s||s.version!==t)throw Error("Volume changed before this operation could be applied.");let n={...s,...r,updatedAt:ie(this.now)};if(this.controlPlane.database.run("UPDATE persistent_volumes SET environment_id=?,resource_id=?,name=?,provider=?,provider_id=?,type=?,status=?,capacity_bytes=?,used_bytes=?,filesystem=?,encrypted=?,capabilities=?,desired_state=?,observed_state=?,backup_policy_id=?,last_backup_at=?,orphaned_at=?,adopted_at=?,deleted_at=?,version=version+1,updated_at=? WHERE id=? AND version=?",[n.environmentId??null,n.resourceId??null,n.name,n.provider,n.providerId??null,n.type,n.status,n.capacityBytes??null,n.usedBytes??null,n.filesystem??null,n.encrypted?1:0,JSON.stringify(n.capabilities),JSON.stringify(n.desiredState),JSON.stringify(n.observedState),n.backupPolicyId??null,n.lastBackupAt??null,n.orphanedAt??null,n.adoptedAt??null,n.deletedAt??null,n.updatedAt,e,t]).changes!==1)throw Error("Volume changed before this operation could be applied.");return this.get(e)}attachments(e){return this.controlPlane.database.query("SELECT * FROM volume_attachments WHERE volume_id=? ORDER BY target_path").all(e).map(rr)}attachment(e){let t=this.controlPlane.database.query("SELECT * FROM volume_attachments WHERE id=?").get(e);return t?rr(t):void 0}upsertAttachment(e){let t=this.controlPlane.database.query("SELECT * FROM volume_attachments WHERE volume_id=? AND resource_id=? AND target_path=?").get(e.volumeId,e.resourceId,e.targetPath),r=ie(this.now);if(t){let n=rr(t);return this.controlPlane.database.run("UPDATE volume_attachments SET read_only=?,uid=?,gid=?,mode=?,propagation=?,driver_options=?,desired_state=?,observed_state=?,operation_id=?,last_error=?,version=version+1,updated_at=? WHERE id=?",[e.readOnly?1:0,e.uid??null,e.gid??null,e.mode??null,e.propagation,JSON.stringify(e.driverOptions),e.desiredState,e.observedState,e.operationId??null,e.lastError??null,r,n.id]),this.attachment(n.id)}let s=e.id??this.id();return this.controlPlane.database.run("INSERT INTO volume_attachments (id,volume_id,resource_id,target_path,read_only,uid,gid,mode,propagation,driver_options,desired_state,observed_state,operation_id,last_error,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",[s,e.volumeId,e.resourceId,e.targetPath,e.readOnly?1:0,e.uid??null,e.gid??null,e.mode??null,e.propagation,JSON.stringify(e.driverOptions),e.desiredState,e.observedState,e.operationId??null,e.lastError??null,r,r]),this.attachment(s)}updateAttachment(e,t){let r=this.attachment(e);if(!r)throw Error("Volume attachment was not found.");let s={...r,...t};return this.controlPlane.database.run("UPDATE volume_attachments SET desired_state=?,observed_state=?,operation_id=?,last_error=?,version=version+1,updated_at=? WHERE id=?",[s.desiredState,s.observedState,s.operationId??null,s.lastError??null,ie(this.now),e]),this.attachment(e)}snapshots(e){return this.controlPlane.database.query("SELECT * FROM volume_snapshots WHERE volume_id=? AND deleted_at IS NULL ORDER BY created_at DESC").all(e).map(hs)}snapshot(e){let t=this.controlPlane.database.query("SELECT * FROM volume_snapshots WHERE id=?").get(e);return t?hs(t):void 0}createSnapshot(e){let t=e.id??this.id(),r=ie(this.now);return this.controlPlane.database.run("INSERT INTO volume_snapshots (id,volume_id,recovery_point_id,provider_id,name,status,size_bytes,encrypted,checksum,metadata,created_at,updated_at,deleted_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)",[t,e.volumeId,e.recoveryPointId??null,e.providerId??null,e.name,e.status,e.sizeBytes??null,e.encrypted?1:0,e.checksum??null,JSON.stringify(e.metadata),r,r,e.deletedAt??null]),this.snapshot(t)}updateSnapshot(e,t){let r=this.snapshot(e);if(!r)throw Error("Volume snapshot was not found.");let s={...r,...t};return this.controlPlane.database.run("UPDATE volume_snapshots SET recovery_point_id=?,provider_id=?,name=?,status=?,size_bytes=?,encrypted=?,checksum=?,metadata=?,deleted_at=?,updated_at=? WHERE id=?",[s.recoveryPointId??null,s.providerId??null,s.name,s.status,s.sizeBytes??null,s.encrypted?1:0,s.checksum??null,JSON.stringify(s.metadata),s.deletedAt??null,ie(this.now),e]),this.snapshot(e)}inventory(e,t){return this.list({projectId:e,environmentId:t}).map((r)=>{let s=this.attachments(r.id),n=this.snapshots(r.id),i=r.lastBackupAt?Date.now()-new Date(r.lastBackupAt).getTime():1/0;return{...r,attachments:s,snapshots:n,orphaned:r.status==="orphaned",usagePercent:r.capacityBytes&&r.usedBytes!=null?Math.round(r.usedBytes/r.capacityBytes*1000)/10:void 0,backupState:r.backupPolicyId?i<=86400000?"protected":"stale":"unprotected"}})}audit(e){this.controlPlane.appendEvent({organizationId:e.organizationId,projectId:e.projectId,resourceId:this.get(e.volumeId)?.resourceId,actorId:e.actorId,type:e.type,level:"warning",payload:{volumeId:e.volumeId,environmentId:e.environmentId??null,...e.payload}})}}function nr(e,t){let r=e.getProject(t.projectId);if(!r?.organizationId)return;let s=new it(e),n=new tr,i=new Set(t.manifest.spec.volumes),E=new Set;for(let a of i){let d=`${t.slug}-${a}`.toLowerCase().replace(/[^a-z0-9_.-]/g,"-").slice(0,63),T=s.list({projectId:t.projectId,environmentId:t.environmentId,includeDeleted:!0}).find((l)=>l.name===d);if(T?.deletedAt)throw Error(`Compose volume ${d} was permanently deleted; rename it before redeploying.`);let N=T?.resourceId?e.getResource(T.resourceId):e.createResource({projectId:t.projectId,environmentId:t.environmentId,kind:"volume",slug:d,name:d,provider:"docker",providerId:d,desiredState:{composeApplicationId:t.id,composeVolume:a}}),o=T??s.create({organizationId:r.organizationId,projectId:t.projectId,environmentId:t.environmentId,resourceId:N?.id,name:d,provider:"docker",providerId:d,type:"docker",status:"available",encrypted:!1,capabilities:n.capabilities(),desiredState:{composeApplicationId:t.id,composeVolume:a,nativeName:d},observedState:{}});E.add(o.id);let c=new Map;for(let l of Object.values(t.manifest.spec.services))for(let _ of l.volumes.filter((L)=>L.source===a)){let L=c.get(_.target)??[];L.push(l.name),c.set(_.target,L),s.upsertAttachment({volumeId:o.id,resourceId:t.resourceId,targetPath:_.target,readOnly:_.readOnly,propagation:"private",driverOptions:{composeServices:L},desiredState:"attached",observedState:"pending"})}}for(let a of s.list({projectId:t.projectId,environmentId:t.environmentId}).filter((d)=>d.desiredState.composeApplicationId===t.id&&!E.has(d.id)))for(let d of s.attachments(a.id))s.updateAttachment(d.id,{desiredState:"detached"})}function fs(e,t,r){let s=new it(e);for(let n of s.list({projectId:t.projectId,environmentId:t.environmentId}).filter((i)=>i.desiredState.composeApplicationId===t.id)){for(let i of s.attachments(n.id))s.updateAttachment(i.id,{desiredState:"detached",observedState:"detached"});s.update(n.id,n.version,{status:r?"deleted":"available",deletedAt:r?new Date().toISOString():void 0})}}function Ee(e){return typeof e==="string"&&e?e:void 0}function ir(e,t){try{return JSON.parse(String(e))}catch{return t}}function Et(e){return{id:String(e.id),resourceId:String(e.resource_id),projectId:String(e.project_id),environmentId:String(e.environment_id),name:String(e.name),slug:String(e.slug),status:String(e.status),sourceKind:String(e.source_kind),sourceHash:String(e.source_hash),redactedSource:String(e.redacted_source),manifest:ir(e.manifest,{}),diagnostics:ir(e.diagnostics,[]),templateId:Ee(e.template_id),templateVersion:Ee(e.template_version),latestOperationId:Ee(e.latest_operation_id),createdByActorId:Ee(e.created_by_actor_id),version:Number(e.version),createdAt:String(e.created_at),updatedAt:String(e.updated_at),deletedAt:Ee(e.deleted_at)}}function Zi(e){return{applicationId:String(e.application_id),serviceName:String(e.service_name),status:String(e.status),replicas:Number(e.replicas),healthyReplicas:Number(e.healthy_replicas),latestOperationId:Ee(e.latest_operation_id),observedState:ir(e.observed_state,{}),updatedAt:String(e.updated_at)}}class w{controlPlane;now;id;constructor(e,t={}){this.controlPlane=e;this.now=t.now??(()=>new Date),this.id=t.id??(()=>crypto.randomUUID())}persist(e,t){if(!e.valid)throw Error(`Compose definition contains ${e.diagnostics.filter((_)=>_.severity==="error").length} blocking diagnostic(s)`);let{projectId:r,environmentId:s,name:n,slug:i}=e.manifest.metadata;if(!this.controlPlane.listEnvironments(r).find((_)=>_.id===s))throw Error("Compose project and environment scope was not found");let a=this.getBySlug(r,s,i),d=this.now().toISOString(),T=JSON.stringify(e.manifest),N=JSON.stringify(h(e.diagnostics));if(a){if(a.status==="deleted")throw Error("A deleted Compose application cannot be overwritten; choose another slug");let _=this.controlPlane.getResource(a.resourceId);if(!_)throw Error("Compose application resource was not found");this.controlPlane.updateResource(_.id,_.version,{name:n,provider:_.provider,providerId:_.providerId,desiredState:e.manifest,discoveredState:_.discoveredState,metadata:{sourceKind:t.sourceKind,templateId:t.templateId??null,templateVersion:t.templateVersion??null,sourceHash:e.sourceHash}}),this.controlPlane.database.run("UPDATE compose_applications SET name=?, status='ready', source_kind=?, source_hash=?, redacted_source=?, manifest=?, diagnostics=?, template_id=?, template_version=?, version=version+1, updated_at=? WHERE id=?",[n,t.sourceKind,e.sourceHash,e.redactedSource,T,N,t.templateId??null,t.templateVersion??null,d,a.id]);let L=Object.keys(e.manifest.spec.services);if(L.length)this.controlPlane.database.run(`DELETE FROM compose_service_states WHERE application_id=? AND service_name NOT IN (${L.map(()=>"?").join(",")})`,[a.id,...L]);for(let C of Object.values(e.manifest.spec.services))this.controlPlane.database.run("INSERT INTO compose_service_states (application_id, service_name, status, replicas, healthy_replicas, observed_state, updated_at) VALUES (?, ?, 'pending', ?, 0, '{}', ?) ON CONFLICT(application_id, service_name) DO UPDATE SET replicas=excluded.replicas, updated_at=excluded.updated_at",[a.id,C.name,C.replicas,d]);this.controlPlane.appendEvent({projectId:r,resourceId:a.resourceId,actorId:t.createdByActorId,type:"compose.application.updated",payload:{applicationId:a.id,sourceHash:e.sourceHash,diagnosticCount:e.diagnostics.length,templateId:t.templateId??null,templateVersion:t.templateVersion??null}});let u=this.get(a.id);return nr(this.controlPlane,u),u}let o=this.controlPlane.createResource({projectId:r,environmentId:s,kind:"compose_application",slug:i,name:n,desiredState:e.manifest,metadata:{sourceKind:t.sourceKind,templateId:t.templateId??null,templateVersion:t.templateVersion??null,sourceHash:e.sourceHash}}),c=this.id();this.controlPlane.database.run("INSERT INTO compose_applications (id, resource_id, project_id, environment_id, name, slug, status, source_kind, source_hash, redacted_source, manifest, diagnostics, template_id, template_version, created_by_actor_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, 'ready', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",[c,o.id,r,s,n,i,t.sourceKind,e.sourceHash,e.redactedSource,T,N,t.templateId??null,t.templateVersion??null,t.createdByActorId??null,d,d]);for(let _ of Object.values(e.manifest.spec.services))this.controlPlane.database.run("INSERT INTO compose_service_states (application_id, service_name, status, replicas, healthy_replicas, observed_state, updated_at) VALUES (?, ?, 'pending', ?, 0, '{}', ?)",[c,_.name,_.replicas,d]);this.controlPlane.appendEvent({projectId:r,resourceId:o.id,actorId:t.createdByActorId,type:"compose.application.created",payload:{applicationId:c,sourceHash:e.sourceHash,serviceNames:Object.keys(e.manifest.spec.services),templateId:t.templateId??null}});let l=this.get(c);return nr(this.controlPlane,l),l}import(e,t){let r=se(e,t);return{application:this.persist(r,{sourceKind:"compose",createdByActorId:t.createdByActorId}),parsed:r}}fromTemplate(e,t,r){let s=Zt(e,t,r,r.version);return{application:this.persist(s,{sourceKind:"template",templateId:e,templateVersion:r.version??"1.0.0",createdByActorId:r.createdByActorId}),parsed:s}}preview(e,t){return se(e,t)}get(e){let t=this.controlPlane.database.query("SELECT * FROM compose_applications WHERE id=?").get(e);return t?Et(t):void 0}getByResource(e){let t=this.controlPlane.database.query("SELECT * FROM compose_applications WHERE resource_id=?").get(e);return t?Et(t):void 0}getBySlug(e,t,r){let s=this.controlPlane.database.query("SELECT * FROM compose_applications WHERE project_id=? AND environment_id=? AND slug=?").get(e,t,r);return s?Et(s):void 0}list(e={}){return this.controlPlane.database.query("SELECT * FROM compose_applications ORDER BY updated_at DESC").all().map(Et).filter((t)=>(!e.projectId||t.projectId===e.projectId)&&(!e.environmentId||t.environmentId===e.environmentId)&&(e.includeDeleted||t.status!=="deleted"))}services(e){return this.controlPlane.database.query("SELECT * FROM compose_service_states WHERE application_id=? ORDER BY service_name").all(e).map(Zi)}transition(e,t,r={}){let s=this.get(e);if(!s)throw Error(`Compose application ${e} was not found`);let n=this.now().toISOString(),i=t==="deleted"?n:null;this.controlPlane.database.run("UPDATE compose_applications SET status=?, latest_operation_id=COALESCE(?, latest_operation_id), version=version+1, updated_at=?, deleted_at=? WHERE id=?",[t,r.operationId??null,n,i,e]);for(let E of r.services??[])if(s.manifest.spec.services[E.name])this.controlPlane.database.run("INSERT INTO compose_service_states (application_id, service_name, status, replicas, healthy_replicas, latest_operation_id, observed_state, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(application_id, service_name) DO UPDATE SET status=excluded.status, replicas=excluded.replicas, healthy_replicas=excluded.healthy_replicas, latest_operation_id=excluded.latest_operation_id, observed_state=excluded.observed_state, updated_at=excluded.updated_at",[e,E.name,E.status,E.replicas??s.manifest.spec.services[E.name].replicas,E.healthyReplicas??0,r.operationId??null,JSON.stringify(h(E.observedState??{})),n]);return this.controlPlane.appendEvent({projectId:s.projectId,resourceId:s.resourceId,operationId:r.operationId,type:`compose.application.${t}`,level:t==="failed"||t==="degraded"?"error":"info",payload:{applicationId:e,error:r.error??null}}),this.get(e)}}class wi{controlPlane;applications;queue;constructor(e){this.controlPlane=e;this.applications=new w(e),this.queue=new $(e)}enqueue(e,t,r={}){if(e.status==="deleted")throw Error("Deleted Compose applications cannot be operated");if(r.service&&!e.manifest.spec.services[r.service])throw Error(`Compose service ${r.service} was not found`);if(t==="delete"){let n=r.removeVolumes?`${e.slug} delete volumes`:e.slug;if(r.confirmation!==n)throw Error(`Type ${n} to confirm ${r.removeVolumes?"persistent data removal":"stack deletion"}`)}if(t==="scale"&&(!r.service||!Number.isInteger(r.replicas)||r.replicas<0||r.replicas>100))throw Error("Scale requires a service and 0-100 replicas");let s=this.queue.enqueue({projectId:e.projectId,environmentId:e.environmentId,resourceId:e.resourceId,actorId:r.actorId,kind:`compose.${t}`,idempotencyKey:`compose:${e.id}:${t}:${r.service??"stack"}:${r.replicas??""}:${e.version}`,correlationId:`compose:${e.id}`,input:{applicationId:e.id,service:r.service??null,replicas:r.replicas??null,removeVolumes:!!r.removeVolumes},lockKey:`compose:${e.id}`,providerKey:this.controlPlane.getResource(e.resourceId)?.provider??"default",buildSlot:t==="deploy"||t==="redeploy",maxAttempts:3,retryClasses:["network","provider_throttled","provider_unavailable"],resumePolicy:"fail",cancellationMode:"provider_non_cancellable",retentionDays:90}).operation;return this.applications.transition(e.id,t==="delete"?"deleting":t==="stop"?"stopped":t==="start"?"deploying":t==="scale"?e.status:"deploying",{operationId:s.id}),s}}import{createHash as eE}from"node:crypto";var tE=/^[a-f0-9]{40,64}$/i,rE=/^[A-Z][A-Z0-9_]{1,127}$/;function j(e){return typeof e==="string"&&e?e:void 0}function ae(e){try{return JSON.parse(String(e))}catch{return{}}}function sE(e){let t=ae(e);return Array.isArray(t)?t.map(String):[]}function Er(e){return{id:String(e.id),projectId:String(e.project_id),resourceId:String(e.resource_id),baseEnvironmentId:String(e.base_environment_id),enabled:Number(e.enabled)===1,branchRule:j(e.branch_rule),domainPattern:String(e.domain_pattern),ttlHours:Number(e.ttl_hours),keepCount:Number(e.keep_count),publicAccess:Number(e.public_access)===1,authenticationRequired:Number(e.authentication_required)===1,allowForks:Number(e.allow_forks)===1,inheritedSecrets:sE(e.inherited_secrets),resourceOverrides:ae(e.resource_overrides),databaseStrategy:String(e.database_strategy),maxMonthlyCost:Number(e.max_monthly_cost),maxCpu:Number(e.max_cpu),maxMemoryMb:Number(e.max_memory_mb),cleanupOnClose:Number(e.cleanup_on_close)===1,version:Number(e.version),createdByActorId:j(e.created_by_actor_id),createdAt:String(e.created_at),updatedAt:String(e.updated_at)}}function ve(e){return{id:String(e.id),definitionId:String(e.definition_id),projectId:String(e.project_id),resourceId:String(e.resource_id),baseEnvironmentId:String(e.base_environment_id),identityKey:String(e.identity_key),sourceProvider:j(e.source_provider),repository:j(e.repository),branch:String(e.branch),pullRequestNumber:e.pull_request_number==null?void 0:Number(e.pull_request_number),fork:Number(e.fork)===1,commitSha:String(e.commit_sha),name:String(e.name),stackName:String(e.stack_name),url:j(e.url),status:String(e.status),expiresAt:String(e.expires_at),latestOperationId:j(e.latest_operation_id),createdByActorId:j(e.created_by_actor_id),costEstimate:e.cost_estimate==null?void 0:Number(e.cost_estimate),desiredState:ae(e.desired_state),observedState:ae(e.observed_state),teardownError:j(e.teardown_error),version:Number(e.version),createdAt:String(e.created_at),updatedAt:String(e.updated_at),destroyedAt:j(e.destroyed_at)}}function Ss(e){return{id:String(e.id),previewId:String(e.preview_id),provider:String(e.provider),providerResourceId:String(e.provider_resource_id),kind:String(e.kind),tags:ae(e.tags),observedState:ae(e.observed_state),discoveredAt:String(e.discovered_at),deletedAt:j(e.deleted_at)}}function Y(e,t,r,s){return Number.isFinite(e)?Math.min(s,Math.max(r,Math.floor(e))):t}function at(e,t,r,s){return Number.isFinite(e)?Math.min(s,Math.max(r,e)):t}function ar(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"").slice(0,28)||"preview"}class de{controlPlane;now;id;constructor(e,t={}){this.controlPlane=e;this.now=t.now??(()=>new Date),this.id=t.id??(()=>crypto.randomUUID())}createDefinition(e){let t=this.controlPlane.getResource(e.resourceId),r=this.controlPlane.listEnvironments(e.projectId).find((T)=>T.id===e.baseEnvironmentId);if(!t||t.projectId!==e.projectId||t.environmentId!==r?.id)throw Error("Preview resource and base environment must belong to the same project scope");if(t.kind!=="application")throw Error("Preview environments currently support application resources only");if(!/^https:\/\//.test(e.domainPattern)||!e.domainPattern.includes("{name}"))throw Error("Preview domainPattern must be HTTPS and include {name}");let s=this.getDefinitionForResource(e.resourceId),n=e.publicAccess??s?.publicAccess??!1,i=e.authenticationRequired??s?.authenticationRequired??!0;if(n&&!i)throw Error("Public preview environments require an authentication policy");let E=[...new Set(e.inheritedSecrets??[])];if(E.some((T)=>!rE.test(T)))throw Error("Inherited preview secret names must use uppercase environment-variable identifiers");let a=this.now().toISOString();if(s)return this.controlPlane.database.run("UPDATE preview_definitions SET base_environment_id=?, enabled=1, branch_rule=?, domain_pattern=?, ttl_hours=?, keep_count=?, public_access=?, authentication_required=?, allow_forks=?, inherited_secrets=?, resource_overrides=?, database_strategy=?, max_monthly_cost=?, max_cpu=?, max_memory_mb=?, cleanup_on_close=?, version=version+1, updated_at=? WHERE id=?",[e.baseEnvironmentId,e.branchRule??null,e.domainPattern,Y(e.ttlHours,s.ttlHours,1,720),Y(e.keepCount,s.keepCount,1,100),n?1:0,i?1:0,e.allowForks??s.allowForks?1:0,JSON.stringify(e.inheritedSecrets===void 0?s.inheritedSecrets:E),JSON.stringify(h(e.resourceOverrides??s.resourceOverrides)),e.databaseStrategy??s.databaseStrategy,at(e.maxMonthlyCost,s.maxMonthlyCost,0,1e4),at(e.maxCpu,s.maxCpu,0.1,64),Y(e.maxMemoryMb,s.maxMemoryMb,128,131072),e.cleanupOnClose??s.cleanupOnClose?1:0,a,s.id]),this.controlPlane.appendEvent({projectId:e.projectId,resourceId:e.resourceId,actorId:e.createdByActorId,type:"preview.definition.updated",payload:{definitionId:s.id,baseEnvironmentId:e.baseEnvironmentId,version:s.version+1}}),this.getDefinition(s.id);let d=this.id();return this.controlPlane.database.run("INSERT INTO preview_definitions (id, project_id, resource_id, base_environment_id, enabled, branch_rule, domain_pattern, ttl_hours, keep_count, public_access, authentication_required, allow_forks, inherited_secrets, resource_overrides, database_strategy, max_monthly_cost, max_cpu, max_memory_mb, cleanup_on_close, created_by_actor_id, created_at, updated_at) VALUES (?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",[d,e.projectId,e.resourceId,e.baseEnvironmentId,e.branchRule??null,e.domainPattern,Y(e.ttlHours,24,1,720),Y(e.keepCount,10,1,100),e.publicAccess?1:0,e.authenticationRequired===!1?0:1,e.allowForks?1:0,JSON.stringify(E),JSON.stringify(h(e.resourceOverrides??{})),e.databaseStrategy??"disabled",at(e.maxMonthlyCost,25,0,1e4),at(e.maxCpu,1,0.1,64),Y(e.maxMemoryMb,1024,128,131072),e.cleanupOnClose===!1?0:1,e.createdByActorId??null,a,a]),this.controlPlane.appendEvent({projectId:e.projectId,resourceId:e.resourceId,actorId:e.createdByActorId,type:"preview.definition.created",payload:{definitionId:d,baseEnvironmentId:e.baseEnvironmentId,ttlHours:Y(e.ttlHours,24,1,720),inheritedSecretNames:E}}),this.getDefinition(d)}getDefinition(e){let t=this.controlPlane.database.query("SELECT * FROM preview_definitions WHERE id=?").get(e);return t?Er(t):void 0}getDefinitionForResource(e){let t=this.controlPlane.database.query("SELECT * FROM preview_definitions WHERE resource_id=?").get(e);return t?Er(t):void 0}listDefinitions(e){return this.controlPlane.database.query("SELECT * FROM preview_definitions WHERE project_id=? ORDER BY updated_at DESC").all(e).map(Er)}listInstances(e={}){return this.controlPlane.database.query("SELECT * FROM preview_instances ORDER BY created_at DESC").all().map(ve).filter((r)=>(!e.projectId||r.projectId===e.projectId)&&(!e.definitionId||r.definitionId===e.definitionId)&&(!e.status||r.status===e.status))}getInstance(e){let t=this.controlPlane.database.query("SELECT * FROM preview_instances WHERE id=?").get(e);return t?ve(t):void 0}findForPullRequest(e,t,r){let s=this.controlPlane.database.query("SELECT * FROM preview_instances WHERE definition_id=? AND identity_key=?").get(e,`pr:${t}:${r}`);return s?ve(s):void 0}findForBranch(e,t,r){let s=this.controlPlane.database.query("SELECT * FROM preview_instances WHERE definition_id=? AND identity_key=?").get(e,`branch:${t}:${r}`);return s?ve(s):void 0}upsert(e){let t=this.getDefinition(e.definitionId);if(!t?.enabled)throw Error("Preview environments are not enabled for this application");if(!tE.test(e.commitSha))throw Error("Preview deployment requires an immutable 40-64 character commit SHA");if(e.fork&&!t.allowForks)throw Error("Untrusted fork previews are disabled");let r=e.pullRequestNumber?`pr:${e.repository??"unknown"}:${e.pullRequestNumber}`:`branch:${e.repository??"local"}:${e.branch}`,s=eE("sha256").update(`${t.id}:${r}`).digest("hex").slice(0,8),i=`${(e.pullRequestNumber?`pr-${e.pullRequestNumber}`:ar(e.branch)).slice(0,28)}-${s}`,E=`preview-${i}`.slice(0,63),a=t.domainPattern.replaceAll("{name}",i).replaceAll("{project}",ar(t.projectId)).replaceAll("{branch}",ar(e.branch)).replaceAll("{pr}",String(e.pullRequestNumber??"branch")),d=e.now??this.now(),T=d.toISOString(),N=new Date(d.getTime()+t.ttlHours*60*60*1000).toISOString(),o=this.controlPlane.database.query("SELECT * FROM preview_instances WHERE definition_id=? AND identity_key=?").get(t.id,r);if(o){let _=ve(o),L=_.commitSha!==e.commitSha||_.status==="destroyed";if(L)this.controlPlane.database.run("UPDATE preview_instances SET commit_sha=?, branch=?, fork=?, status='queued', expires_at=?, url=?, teardown_error=NULL, destroyed_at=NULL, version=version+1, updated_at=? WHERE id=?",[e.commitSha,e.branch,e.fork?1:0,N,a,T,_.id]);return{preview:this.getInstance(_.id),created:!1,changed:L,inheritedSecrets:e.fork?[]:t.inheritedSecrets}}let c=this.id(),l=h({source:{provider:e.sourceProvider??null,repository:e.repository??null,branch:e.branch,commitSha:e.commitSha,pullRequestNumber:e.pullRequestNumber??null,fork:!!e.fork},policy:{databaseStrategy:t.databaseStrategy,resourceOverrides:t.resourceOverrides,maxMonthlyCost:t.maxMonthlyCost,maxCpu:t.maxCpu,maxMemoryMb:t.maxMemoryMb,publicAccess:t.publicAccess,authenticationRequired:t.authenticationRequired},inheritedSecretNames:e.fork?[]:t.inheritedSecrets});return this.controlPlane.database.run("INSERT INTO preview_instances (id, definition_id, project_id, resource_id, base_environment_id, identity_key, source_provider, repository, branch, pull_request_number, fork, commit_sha, name, stack_name, url, status, expires_at, created_by_actor_id, desired_state, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'queued', ?, ?, ?, ?, ?)",[c,t.id,t.projectId,t.resourceId,t.baseEnvironmentId,r,e.sourceProvider??null,e.repository??null,e.branch,e.pullRequestNumber??null,e.fork?1:0,e.commitSha,i,E,a,N,e.createdByActorId??null,JSON.stringify(l),T,T]),this.controlPlane.appendEvent({projectId:t.projectId,resourceId:t.resourceId,actorId:e.createdByActorId,type:"preview.created",payload:{previewId:c,identityKey:r,commitSha:e.commitSha,expiresAt:N,fork:!!e.fork}}),{preview:this.getInstance(c),created:!0,changed:!0,inheritedSecrets:e.fork?[]:t.inheritedSecrets}}transition(e,t,r={}){let s=this.getInstance(e);if(!s)throw Error(`Preview ${e} was not found`);let n=this.now().toISOString(),i=t==="destroyed"?n:null;return this.controlPlane.database.run("UPDATE preview_instances SET status=?, latest_operation_id=COALESCE(?, latest_operation_id), observed_state=?, teardown_error=?, cost_estimate=COALESCE(?, cost_estimate), destroyed_at=?, version=version+1, updated_at=? WHERE id=?",[t,r.operationId??null,JSON.stringify(h(r.observedState??s.observedState)),r.teardownError??null,r.costEstimate??null,i,n,e]),this.controlPlane.appendEvent({projectId:s.projectId,resourceId:s.resourceId,operationId:r.operationId,type:`preview.${t}`,level:t==="failed"||t==="cleanup_failed"?"error":"info",payload:{previewId:e,commitSha:s.commitSha,teardownError:r.teardownError??null}}),this.getInstance(e)}extend(e,t){let r=this.getInstance(e),s=r?this.getDefinition(r.definitionId):void 0;if(!r||!s)throw Error(`Preview ${e} was not found`);let n=this.now(),i=Y(t,s.ttlHours,1,720),E=new Date(Math.max(n.getTime(),new Date(r.expiresAt).getTime())+i*3600000).toISOString();return this.controlPlane.database.run("UPDATE preview_instances SET expires_at=?, version=version+1, updated_at=? WHERE id=?",[E,n.toISOString(),e]),this.getInstance(e)}expired(e=this.now()){return this.listInstances().filter((t)=>["queued","deploying","active","updating","failed","cleanup_failed"].includes(t.status)&&new Date(t.expiresAt)<=e)}recordResource(e){let t=this.getInstance(e.previewId);if(!t)throw Error(`Preview ${e.previewId} was not found`);let r={"ts-cloud:preview":t.id,"ts-cloud:project":t.projectId,"ts-cloud:expires-at":t.expiresAt};for(let[E,a]of Object.entries(r))if(e.tags[E]!==a)throw Error(`Preview resource is missing immutable tag ${E}`);let s=this.id(),n=this.now().toISOString();this.controlPlane.database.run("INSERT INTO preview_resources (id, preview_id, provider, provider_resource_id, kind, tags, observed_state, discovered_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(preview_id, provider, provider_resource_id) DO UPDATE SET kind=excluded.kind, tags=excluded.tags, observed_state=excluded.observed_state, deleted_at=NULL",[s,t.id,e.provider,e.providerResourceId,e.kind,JSON.stringify(e.tags),JSON.stringify(h(e.observedState??{})),n]);let i=this.controlPlane.database.query("SELECT * FROM preview_resources WHERE preview_id=? AND provider=? AND provider_resource_id=?").get(t.id,e.provider,e.providerResourceId);return Ss(i)}listResources(e){return this.controlPlane.database.query("SELECT * FROM preview_resources WHERE preview_id=? ORDER BY kind, provider_resource_id").all(e).map(Ss)}markResourcesDeleted(e){if(!this.getInstance(e))throw Error(`Preview ${e} was not found`);return this.controlPlane.database.run("UPDATE preview_resources SET deleted_at=? WHERE preview_id=? AND deleted_at IS NULL",[this.now().toISOString(),e]).changes}}class nE{controlPlane;previews;queue;constructor(e){this.controlPlane=e;this.previews=new de(e),this.queue=new $(e)}enqueueDeploy(e,t={}){let r=t.created?"preview.create":"preview.update",s=t.reason==="manual_rebuild"?`:${crypto.randomUUID()}`:"",n=this.queue.enqueue({projectId:e.projectId,environmentId:e.baseEnvironmentId,resourceId:e.resourceId,actorId:t.actorId,kind:r,correlationId:`preview:${e.id}`,idempotencyKey:`preview:${e.id}:${e.commitSha}${s}`,input:{previewId:e.id,reason:t.reason??(t.created?"create":"commit_update"),source:{repository:e.repository??null,branch:e.branch,commitSha:e.commitSha,pullRequestNumber:e.pullRequestNumber??null,fork:e.fork}},lockKey:`preview:${e.id}`,providerKey:"preview",buildSlot:!0,maxAttempts:3,retryClasses:["network","provider_throttled","provider_unavailable"],resumePolicy:"fail",cancellationMode:"provider_non_cancellable",retentionDays:90}).operation;return this.previews.transition(e.id,"queued",{operationId:n.id}),n}enqueueDestroy(e,t,r){if(e.status==="destroyed")throw Error("Preview is already destroyed");let s=this.queue.enqueue({projectId:e.projectId,environmentId:e.baseEnvironmentId,resourceId:e.resourceId,actorId:r,kind:"preview.destroy",correlationId:`preview:${e.id}`,idempotencyKey:`preview:${e.id}:destroy:${t}`,input:{previewId:e.id,reason:t},lockKey:`preview:${e.id}`,providerKey:"preview",maxAttempts:3,retryClasses:["network","provider_throttled","provider_unavailable"],resumePolicy:"fail",cancellationMode:"provider_non_cancellable",retentionDays:90}).operation;return this.previews.transition(e.id,"queued",{operationId:s.id}),s}cleanup(e={}){let t=e.now??new Date,r=new Map,s=this.previews.listInstances().filter((d)=>!["destroyed","destroying"].includes(d.status)),n=(d,T)=>{let N=r.get(d.id)??{preview:d,reasons:[]};if(!N.reasons.includes(T))N.reasons.push(T);r.set(d.id,N)};for(let d of s){if(new Date(d.expiresAt)<=t)n(d,"ttl_expired");if(e.maxAgeHours&&t.getTime()-new Date(d.createdAt).getTime()>=e.maxAgeHours*3600000)n(d,"max_age")}let i=new Map;for(let d of s)i.set(d.definitionId,[...i.get(d.definitionId)??[],d]);for(let[d,T]of i){let N=this.previews.getDefinition(d),o=e.keepCount??N?.keepCount;if(!o)continue;T.sort((c,l)=>l.createdAt.localeCompare(c.createdAt)).slice(o).forEach((c)=>n(c,"keep_count"))}let E=[...r.values()].sort((d,T)=>d.preview.createdAt.localeCompare(T.preview.createdAt)),a=e.dryRun?[]:E.map((d)=>this.enqueueDestroy(d.preview,d.reasons.sort().join("+"),e.actorId));if(E.length)this.controlPlane.appendEvent({actorId:e.actorId,type:e.dryRun?"preview.cleanup.planned":"preview.cleanup.queued",payload:{previewIds:E.map((d)=>d.preview.id),reasons:Object.fromEntries(E.map((d)=>[d.preview.id,d.reasons])),dryRun:!!e.dryRun}});return{candidates:E,operations:a,dryRun:!!e.dryRun}}reconcile(e){let t=[],r=[],s=[];for(let n of e){let i=n.tags["ts-cloud:preview"],E=n.tags["ts-cloud:project"],a=n.tags["ts-cloud:expires-at"];if(!i||!E||!a){s.push(n.providerResourceId);continue}let d=this.previews.getInstance(i);if(!d||d.projectId!==E||d.expiresAt!==a){r.push(n.providerResourceId);continue}if(this.previews.recordResource({...n,previewId:d.id}),n.estimatedMonthlyCost!==void 0)this.previews.transition(d.id,d.status,{costEstimate:n.estimatedMonthlyCost,observedState:{...d.observedState,lastReconciledAt:new Date().toISOString()}});t.push(n.providerResourceId)}if(r.length)this.controlPlane.appendEvent({type:"preview.reconciliation.leaks",level:"error",payload:{providerResourceIds:r}});return{matched:t,unknownPreviewResources:r,ignoredUntagged:s}}}var iE=["deployment.create","deployment.rollback","application.create","deploy.source","deploy.preview","preview.create","preview.update","preview.destroy","compose.deploy","compose.redeploy","compose.start","compose.stop","compose.delete","compose.scale"];function dt(e){return e&&typeof e==="object"&&!Array.isArray(e)?e:{}}function EE(e,t){if(!t.projectId||!t.environmentId)throw Error(`Queued ${t.kind} operation is missing its project or environment scope`);let r=e.listEnvironments(t.projectId).find((E)=>E.id===t.environmentId);if(!r)throw Error(`Environment ${t.environmentId} was not found for queued ${t.kind} operation`);let s=t.resourceId?e.getResource(t.resourceId):void 0;if(t.resourceId&&(!s||s.projectId!==t.projectId||s.environmentId!==t.environmentId))throw Error(`Resource ${t.resourceId} was not found in the queued deployment scope`);let n=dt(t.input);if(t.kind.startsWith("compose.")){let E=typeof n.applicationId==="string"?n.applicationId:void 0,a=E?new w(e).get(E):t.resourceId?new w(e).getByResource(t.resourceId):void 0;if(!a||a.projectId!==t.projectId||a.environmentId!==t.environmentId||a.resourceId!==t.resourceId)throw Error("Queued Compose application does not match the operation scope");let d=t.kind.slice(8),T=["compose:apply",a.id,"--action",d,"--env",r.slug];if(typeof n.service==="string"&&n.service)T.push("--service",n.service);if(typeof n.replicas==="number")T.push("--replicas",String(n.replicas));if(n.removeVolumes===!0)T.push("--remove-volumes");return{id:`queue:${t.id}`,label:`${d[0].toUpperCase()}${d.slice(1)} Compose application`,description:`${d} ${a.name}.`,command:T,mutates:!0,target:{environment:r.slug,resource:s?.slug,composeId:a.id}}}if(t.kind.startsWith("preview.")){let E=typeof n.previewId==="string"?n.previewId:void 0,a=E?new de(e).getInstance(E):void 0;if(!a)throw Error(`Preview ${E??"(missing)"} was not found for queued ${t.kind}`);if(a.projectId!==t.projectId||a.resourceId!==t.resourceId)throw Error("Queued preview does not match the operation scope");if(t.kind==="preview.destroy")return{id:`queue:${t.id}`,label:"Destroy preview",description:`Destroy ${a.name}.`,command:["stack:delete",a.stackName,"--yes"],mutates:!0,target:{environment:r.slug,resource:s?.slug,previewId:a.id}};return{id:`queue:${t.id}`,label:t.kind==="preview.create"?"Create preview":"Update preview",description:`Deploy ${a.name} at ${a.commitSha}.`,command:["deploy","--stack",a.stackName,"--env",r.slug,"--yes","--skip-dns-verification",...s?["--site",s.slug]:[]],mutates:!0,target:{environment:r.slug,resource:s?.slug,previewId:a.id}}}if(t.kind==="deployment.rollback"){let E=["deploy:rollback"];if(s)E.push(s.slug);if(E.push("--env",r.slug),typeof n.revision==="string"&&n.revision)E.push("--to",n.revision);return{id:`queue:${t.id}`,label:"Rollback deployment",description:`Roll back ${s?.slug??r.slug}.`,command:E,mutates:!0,target:{environment:r.slug,resource:s?.slug}}}let i=["deploy","--env",r.slug,"--yes"];if(s)i.push("--site",s.slug);return{id:`queue:${t.id}`,label:t.kind==="deploy.preview"?"Deploy preview":"Deploy release",description:`Deploy ${s?.slug??r.slug}.`,command:i,mutates:!0,target:{environment:r.slug,resource:s?.slug}}}function aE(e){let t=e.stderr?.trim()||`Deployment process exited with code ${e.exitCode}`;if(/\b(?:429|rate.?limit|throttl)/i.test(t))return new J(t,"provider_throttled");if(/\b(?:502|503|504|temporar(?:y|ily)|unavailable|provider.?error)/i.test(t))return new J(t,"provider_unavailable");if(/\b(?:network|econn|enotfound|socket|timed?\s*out|fetch failed|dns)\b/i.test(t))return new J(t,"network");return Error(t)}function hT(e){let t=async(r)=>{r.checkpoint("prepare","Resolving the persisted deployment target.");let s=EE(e.store,r.operation),n=s.target.previewId?new de(e.store):void 0,i=s.target.composeId?new w(e.store):void 0;if(n&&s.target.previewId)n.transition(s.target.previewId,r.operation.kind==="preview.destroy"?"destroying":r.operation.kind==="preview.update"?"updating":"deploying",{operationId:r.operation.id});if(i&&s.target.composeId)i.transition(s.target.composeId,r.operation.kind==="compose.delete"?"deleting":r.operation.kind==="compose.stop"?"stopped":"deploying",{operationId:r.operation.id});r.log(`Target: ${s.target.resource??"all resources"} in ${s.target.environment}.`,{stream:"system"}),r.throwIfCancellationRequested(),r.checkpoint("execute",`Running cloud ${s.command.join(" ")}.`);let E=await e.execute(s,r);if(!E.ok){if(n&&s.target.previewId)n.transition(s.target.previewId,r.operation.kind==="preview.destroy"?"cleanup_failed":"failed",{operationId:r.operation.id,teardownError:r.operation.kind==="preview.destroy"?E.stderr?.trim()||`Process exited with ${E.exitCode}`:void 0});if(i&&s.target.composeId)i.transition(s.target.composeId,r.operation.kind==="compose.delete"?"degraded":"failed",{operationId:r.operation.id,error:E.stderr?.trim()||`Process exited with ${E.exitCode}`});throw aE(E)}if(r.throwIfCancellationRequested(),n&&s.target.previewId){let a=n.getInstance(s.target.previewId);if(r.operation.kind==="preview.destroy")n.markResourcesDeleted(a.id),n.transition(a.id,"destroyed",{operationId:r.operation.id,observedState:{reconciledResourceIds:n.listResources(a.id).map((d)=>d.providerResourceId),exactCommitSha:a.commitSha}});else{let d=e.store.getResource(a.resourceId)?.provider??"default";n.recordResource({previewId:a.id,provider:d,providerResourceId:a.stackName,kind:"stack",tags:{"ts-cloud:preview":a.id,"ts-cloud:project":a.projectId,"ts-cloud:expires-at":a.expiresAt},observedState:{command:E.command??null,exactCommitSha:a.commitSha}}),n.transition(a.id,"active",{operationId:r.operation.id,observedState:{url:a.url??null,stackName:a.stackName,exactCommitSha:a.commitSha,providerStatus:"deployed"}})}}if(i&&s.target.composeId){let a=i.get(s.target.composeId),d=typeof dt(r.operation.input).service==="string"?String(dt(r.operation.input).service):void 0,T=r.operation.kind==="compose.delete"?"deleted":r.operation.kind==="compose.stop"?"stopped":"running",N=i.transition(a.id,T,{operationId:r.operation.id,services:Object.values(a.manifest.spec.services).filter((o)=>!d||o.name===d).map((o)=>({name:o.name,status:T==="stopped"||T==="deleted"?"stopped":"running",replicas:T==="deleted"?0:o.replicas,healthyReplicas:T==="running"?o.replicas:0,observedState:{command:E.command??null,healthGatePassed:T==="running"}}))});if(T==="deleted")fs(e.store,N,dt(r.operation.input).removeVolumes===!0)}return r.checkpoint("finalize","Deployment command completed; persisting the terminal result."),{command:E.command??`cloud ${s.command.join(" ")}`,exitCode:E.exitCode,environment:s.target.environment,resource:s.target.resource??null}};return Object.fromEntries(iE.map((r)=>[r,t]))}class A extends Error{code;status;details;constructor(e,t,r,s){super(t);this.code=e;this.status=r;this.details=s}}function dr(e,t){if(e.organizationId!==t.organizationId)return!1;if(e.projectId&&e.projectId!==t.projectId)return!1;if(e.environmentId&&e.environmentId!==t.environmentId)return!1;if(e.resourceId&&e.resourceId!==t.resourceId)return!1;return!0}function TE(e,t,r){return dE("sha256").update(`${e.toUpperCase()}
|
|
1616
|
+
${t}
|
|
1617
|
+
${JSON.stringify(r)}`).digest("hex")}class oE{controlPlane;identities;queue;spendGate;constructor(e,t){this.controlPlane=e;this.identities=t;this.queue=new $(e),this.spendGate=new Ce(e)}target(e,t){let r=this.controlPlane.resolveAuthorizationTarget(e.serviceAccount.organizationId,t),s=this.controlPlane.resolveAuthorizationTarget(e.serviceAccount.organizationId,e.token.scope);if(!r||!s||!dr(s,r))throw new A("forbidden","The token does not grant access to this resource.",403);return r}authorize(e,t,r){if(!e.token.capabilities.includes(t))throw new A("forbidden",`The token is missing the ${t} capability.`,403);let s=this.controlPlane.resolveAuthorizationTarget(e.serviceAccount.organizationId,r),n=this.controlPlane.resolveAuthorizationTarget(e.serviceAccount.organizationId,e.token.scope),i=t==="project:read"&&!!s&&!!n&&dr(s,n);if(!s||!n||!dr(n,s)&&!i)throw new A("forbidden","The token does not grant access to this resource.",403);let E=i?n:s;if(!be({membership:e.membership,grants:this.controlPlane.listGrants(e.membership.id),capability:t,target:E}).allowed)throw new A("forbidden","The service account does not have access to this resource.",403);return s}listProjects(e){return this.controlPlane.listProjects().filter((t)=>t.organizationId===e.serviceAccount.organizationId).filter((t)=>{try{return this.authorize(e,"project:read",{type:"project",id:t.id}),!0}catch{return!1}}).map((t)=>({id:t.id,slug:t.slug,name:t.name,description:t.description,version:t.version,createdAt:t.createdAt,updatedAt:t.updatedAt}))}listEnvironments(e,t){let r=this.controlPlane.getProject(t);if(!r||r.organizationId!==e.serviceAccount.organizationId)throw new A("not_found","Project was not found.",404);return this.controlPlane.listEnvironments(t).filter((s)=>{try{return this.authorize(e,"project:read",{type:"environment",id:s.id}),!0}catch{return!1}}).map((s)=>({id:s.id,projectId:s.projectId,slug:s.slug,name:s.name,kind:s.kind,region:s.region,version:s.version,createdAt:s.createdAt,updatedAt:s.updatedAt}))}listServices(e,t,r){let s=new Set(this.listEnvironments(e,t).map((n)=>String(n.id)));if(r&&!s.has(r))throw new A("not_found","Environment was not found.",404);return this.controlPlane.listResources(t,r).filter((n)=>!n.environmentId||s.has(n.environmentId)).filter((n)=>{try{return this.authorize(e,"project:read",{type:"resource",id:n.id}),!0}catch{return!1}}).map((n)=>({id:n.id,projectId:n.projectId,environmentId:n.environmentId,kind:n.kind,slug:n.slug,name:n.name,provider:n.provider,providerId:n.providerId,metadata:h(n.metadata),version:n.version,createdAt:n.createdAt,updatedAt:n.updatedAt}))}listOperations(e,t){return this.controlPlane.listOperations({projectId:t,limit:500}).filter((s)=>{let n=s.resourceId?{type:"resource",id:s.resourceId}:s.environmentId?{type:"environment",id:s.environmentId}:s.projectId?{type:"project",id:s.projectId}:{type:"organization"};try{return this.authorize(e,"deployments:read",n),!0}catch{return!1}}).map((s)=>this.operation(s))}listEvents(e,t,r){if(t)this.authorize(e,"audit:read",{type:"project",id:t});else this.authorize(e,"audit:read",e.token.scope);return this.controlPlane.listEvents({organizationId:e.serviceAccount.organizationId,projectId:t,afterSequence:r,limit:500}).filter((s)=>{let n=s.resourceId?{type:"resource",id:s.resourceId}:s.projectId?{type:"project",id:s.projectId}:{type:"organization"};try{return this.target(e,n),!0}catch{return!1}}).map((s)=>({id:s.id,sequence:s.sequence,projectId:s.projectId,operationId:s.operationId,resourceId:s.resourceId,correlationId:s.correlationId,type:s.type,level:s.level,payload:h(s.payload),createdAt:s.createdAt}))}createDeployment(e,t,r){if(!this.controlPlane.listEnvironments(t.projectId).find((N)=>N.id===t.environmentId))throw new A("not_found","Environment was not found in this project.",404);let n=t.serviceId?{type:"resource",id:t.serviceId}:{type:"environment",id:t.environmentId};this.authorize(e,"deployments:create",n);let i=this.spendGate.check("deploy",{organizationId:e.serviceAccount.organizationId,projectId:t.projectId,environmentId:t.environmentId});if(!i.allowed)throw new A("spend_cap_exceeded",i.reason??"A spend cap is blocking deployments.",402,{action:i.action,budgetId:i.budgetId});let E=TE("POST","/api/v1/deployments",t),a=this.identities.getIdempotency(e.token.id,r);if(a){if(a.requestHash!==E)throw new A("idempotency_conflict","Idempotency-Key was already used for another request.",409);let N=a.operationId?this.controlPlane.getOperation(a.operationId):void 0;if(!N)throw new A("idempotency_unavailable","The original operation is no longer available.",409);return{operation:this.operation(N),replay:!0}}if(t.serviceId){let N=this.controlPlane.getResource(t.serviceId);if(!N||N.projectId!==t.projectId||N.environmentId!==t.environmentId)throw new A("not_found","Service was not found in this environment.",404)}let d=t.serviceId?this.controlPlane.getResource(t.serviceId):void 0,T=this.queue.enqueue({projectId:t.projectId,environmentId:t.environmentId,resourceId:t.serviceId,actorId:e.actor.id,kind:t.action==="rollback"?"deployment.rollback":"deployment.create",idempotencyKey:`api:${e.token.id}:${r}`,input:h({action:t.action??"deploy",revision:t.revision??null}),lockKey:t.serviceId?`resource:${t.serviceId}`:`environment:${t.environmentId}`,providerKey:d?.provider??"default",buildSlot:t.action!=="rollback",maxAttempts:3,retryClasses:["network","provider_throttled","provider_unavailable"],resumePolicy:"fail",cancellationMode:"provider_non_cancellable",retentionDays:90}).operation;return this.identities.saveIdempotency({tokenId:e.token.id,key:r,requestHash:E,operationId:T.id,responseStatus:202,responseBody:{operationId:T.id}}),{operation:this.operation(T),replay:!1}}operation(e){return{id:e.id,state:e.state,kind:e.kind,projectId:e.projectId,environmentId:e.environmentId,resourceId:e.resourceId,actorId:e.actorId,correlationId:e.correlationId,input:h(e.input),output:h(e.output),error:e.error,attempt:e.attempt,createdAt:e.createdAt,updatedAt:e.updatedAt,startedAt:e.startedAt,finishedAt:e.finishedAt}}}function Tt(e){return e.serviceAccount.organizationId}function Xs(e){if(e==null)return;if(e==="daily"||e==="weekly"||e==="monthly")return e;throw new A("validation_error","period must be daily, weekly, or monthly.",422)}function Te(e,t){if(e==null)return;let r=Number(e);if(!Number.isFinite(r)||r<0||!Number.isInteger(r))throw new A("validation_error",`${t} must be a non-negative integer.`,422);return r}function D(e,t,r,s,n){if(n)e.authorization.authorize(t,r,{type:"environment",id:n});else if(s)e.authorization.authorize(t,r,{type:"project",id:s});else e.authorization.authorize(t,r,{type:"organization"});return{organizationId:Tt(t),projectId:s,environmentId:n}}async function MT(e,t,r){let{method:s,url:n}=r,i=e.now?.()??new Date,E=n.searchParams.get("projectId")??void 0,a=n.searchParams.get("environmentId")??void 0;if(s==="GET"&&n.pathname==="/api/v1/usage"){let c=D(e,t,"billing:read",E,a);return e.service.usageReport({...c,period:Xs(n.searchParams.get("period")),timezone:n.searchParams.get("timezone")??void 0,now:i})}if(s==="GET"&&n.pathname==="/api/v1/usage/rollups"){let c=D(e,t,"billing:read",E,a),l=n.searchParams.get("from"),_=n.searchParams.get("to");if(!l||!_)throw new A("validation_error","from and to are required.",422);if(Number.isNaN(Date.parse(l))||Number.isNaN(Date.parse(_)))throw new A("validation_error","from and to must be ISO-8601 timestamps.",422);let L=n.searchParams.getAll("meter");return{data:e.store.listRollups({...c,from:l,to:_,meters:L.length>0?L:void 0},Te(n.searchParams.get("limit"),"limit")??1000)}}if(s==="GET"&&n.pathname==="/api/v1/usage/focus"){let c=D(e,t,"billing:read",E,a),l=n.searchParams.get("from"),_=n.searchParams.get("to");if(!l||!_)throw new A("validation_error","from and to are required.",422);if(Number.isNaN(Date.parse(l))||Number.isNaN(Date.parse(_)))throw new A("validation_error","from and to must be ISO-8601 timestamps.",422);if(new Date(_).getTime()-new Date(l).getTime()>31622400000)throw new A("validation_error","The range must not exceed one year.",422);let L=n.searchParams.get("granularity")==="hourly"?"hourly":"daily",u=e.store.listRollups({...c,from:l,to:_},1e4),C=Br(u,{billingPeriodStart:l,billingPeriodEnd:_,currency:e.store.priceBook.currency,granularity:L});return{contentType:"application/x-ndjson",body:Ft(C),recordCount:C.length}}if(s==="GET"&&n.pathname==="/api/v1/spend/budgets"){let c=D(e,t,"billing:read",E,a);return{data:e.store.listBudgets({organizationId:c.organizationId,projectId:c.projectId})}}if(s==="POST"&&n.pathname==="/api/v1/spend/budgets"){let c=await r.body?.()??{},_={organizationId:D(e,t,"billing:manage",c.projectId,c.environmentId).organizationId,projectId:c.projectId,environmentId:c.environmentId,name:String(c.name??"").trim(),period:Xs(c.period??"monthly")??"monthly",timezone:c.timezone,currency:c.currency,softLimitCents:Te(c.softLimitCents,"softLimitCents"),hardLimitCents:Te(c.hardLimitCents,"hardLimitCents"),thresholds:c.thresholds,meters:c.meters,graceSeconds:Te(c.graceSeconds,"graceSeconds"),hysteresisPercent:c.hysteresisPercent,dryRun:c.dryRun!==!1,enabled:c.enabled};try{return{data:e.store.createBudget(_)}}catch(L){throw new A("validation_error",L instanceof Error?L.message:String(L),422)}}let d=/^\/api\/v1\/spend\/budgets\/([^/]+)$/.exec(n.pathname);if(d&&(s==="PATCH"||s==="DELETE")){let c=e.store.getBudget(decodeURIComponent(d[1]));if(!c||c.organizationId!==Tt(t))throw new A("not_found","Budget was not found.",404);if(D(e,t,"billing:manage",c.projectId,c.environmentId),s==="DELETE")return{data:{deleted:e.store.deleteBudget(c.id)}};let l=await r.body?.()??{};try{return{data:e.store.updateBudget(c.id,l,Te(l.expectedVersion,"expectedVersion"))}}catch(_){let L=_ instanceof Error?_.message:String(_);throw new A(L.includes("concurrently")?"conflict":"validation_error",L,L.includes("concurrently")?409:422)}}if(s==="GET"&&n.pathname==="/api/v1/spend/anomalies"){let c=D(e,t,"billing:read",E,a);return{data:e.store.listAnomalies({organizationId:c.organizationId,projectId:c.projectId,since:n.searchParams.get("since")??void 0,unacknowledgedOnly:n.searchParams.get("unacknowledged")==="true",limit:Te(n.searchParams.get("limit"),"limit")})}}let T=/^\/api\/v1\/spend\/anomalies\/([^/]+)\/acknowledge$/.exec(n.pathname);if(T&&s==="POST"){let c=e.store.getAnomaly(decodeURIComponent(T[1]));if(!c||c.organizationId!==Tt(t))throw new A("not_found","Anomaly was not found.",404);return D(e,t,"billing:manage",c.projectId,c.environmentId),{data:{acknowledged:e.store.acknowledgeAnomaly(c.id)}}}if(s==="GET"&&n.pathname==="/api/v1/spend/signals")return D(e,t,"billing:read",E,a),{data:ue};if(n.pathname==="/api/v1/spend/anomaly-configs"){if(s==="GET"){let c=D(e,t,"billing:read",E,a);return{data:e.service.anomalyConfigs.list({organizationId:c.organizationId,projectId:c.projectId})}}if(s==="POST"){let c=await r.body?.()??{},l=D(e,t,"billing:manage",c.projectId,c.environmentId);try{return{data:e.service.anomalyConfigs.upsert({organizationId:l.organizationId,projectId:c.projectId,environmentId:c.environmentId,signal:String(c.signal??""),enabled:c.enabled,sensitivity:c.sensitivity,seasonLength:c.seasonLength==null?void 0:Number(c.seasonLength),minAbsoluteDelta:c.minAbsoluteDelta==null?void 0:Number(c.minAbsoluteDelta),detectDrops:c.detectDrops,severity:c.severity})}}catch(_){throw new A("validation_error",_ instanceof Error?_.message:String(_),422)}}}let N=/^\/api\/v1\/spend\/anomaly-configs\/([^/]+)$/.exec(n.pathname);if(N&&s==="DELETE"){let c=e.service.anomalyConfigs.get(decodeURIComponent(N[1]));if(!c||c.organizationId!==Tt(t))throw new A("not_found","Anomaly config was not found.",404);return D(e,t,"billing:manage",c.projectId,c.environmentId),{data:{deleted:e.service.anomalyConfigs.delete(c.id)}}}if(n.pathname==="/api/v1/spend/anomaly-silences"){if(s==="GET"){let c=D(e,t,"billing:read",E,a);return{data:e.service.anomalyConfigs.listSilences({organizationId:c.organizationId,projectId:c.projectId,includeExpired:n.searchParams.get("includeExpired")==="true"})}}if(s==="POST"){let c=await r.body?.()??{},l=D(e,t,"billing:manage",c.projectId);try{return{data:e.service.anomalyConfigs.silence({organizationId:l.organizationId,projectId:c.projectId,signal:c.signal,routePattern:c.routePattern,statusCode:c.statusCode==null?void 0:Number(c.statusCode),reason:String(c.reason??""),expiresAt:c.expiresAt})}}catch(_){throw new A("validation_error",_ instanceof Error?_.message:String(_),422)}}}let o=/^\/api\/v1\/spend\/anomaly-silences\/([^/]+)$/.exec(n.pathname);if(o&&s==="DELETE")return D(e,t,"billing:manage",E),{data:{deleted:e.service.anomalyConfigs.removeSilence(decodeURIComponent(o[1]))}};if(s==="GET"&&n.pathname==="/api/v1/spend/enforcement"){let c=D(e,t,"billing:read",E,a);return{data:e.store.listEnforcements({organizationId:c.organizationId,activeOnly:n.searchParams.get("active")!=="false"})}}if(s==="GET"&&n.pathname==="/api/v1/spend/allowance"){let c=D(e,t,"billing:read",E,a),l=n.searchParams.get("operation")??"deploy";if(!["build","deploy","function_invoke","request"].includes(l))throw new A("validation_error","operation must be build, deploy, function_invoke, or request.",422);let L=e.store.listEnforcements({organizationId:c.organizationId,activeOnly:!0}).filter((R)=>(!R.projectId||R.projectId===c.projectId)&&(!R.environmentId||R.environmentId===c.environmentId)),u=Hr(l,L),g=e.store.budgetsForScope(c.organizationId,c.projectId,c.environmentId).map((R)=>e.service.status(R,i));return{operation:l,allowed:u.allowed,blockedBy:u.blockedBy??null,reason:u.reason??null,budgets:g.map((R)=>({id:R.budget.id,name:R.budget.name,usedPercent:R.decision.usedPercent,projectedPercent:R.decision.projectedPercent,level:R.decision.level,timeToCap:R.timeToCap}))}}return}function jT(){let e=[{name:"projectId",in:"query",schema:{type:"string"}},{name:"environmentId",in:"query",schema:{type:"string"}}];return{"/api/v1/usage":{get:{summary:"Current usage, cost, and every budget governing the scope.",parameters:[...e,{name:"period",in:"query",schema:{type:"string",enum:["daily","weekly","monthly"]}},{name:"timezone",in:"query",schema:{type:"string"}}],responses:{"200":{description:"Usage totals, per-meter breakdown, hourly series, and budget status."}}}},"/api/v1/usage/rollups":{get:{summary:"Itemized hourly usage rollups.",parameters:[...e,{name:"from",in:"query",required:!0,schema:{type:"string",format:"date-time"}},{name:"to",in:"query",required:!0,schema:{type:"string",format:"date-time"}},{name:"meter",in:"query",schema:{type:"array",items:{type:"string"}}},{name:"limit",in:"query",schema:{type:"integer"}}],responses:{"200":{description:"Hourly rollups, newest first."}}}},"/api/v1/usage/focus":{get:{summary:"Usage and cost in FOCUS v1.3 format, newline-delimited JSON.",parameters:[...e,{name:"from",in:"query",required:!0,schema:{type:"string",format:"date-time"}},{name:"to",in:"query",required:!0,schema:{type:"string",format:"date-time"}},{name:"granularity",in:"query",schema:{type:"string",enum:["daily","hourly"]}}],responses:{"200":{description:"FOCUS records, one JSON object per line."}}}},"/api/v1/spend/budgets":{get:{summary:"Budgets governing a scope.",parameters:e,responses:{"200":{description:"Budgets."}}},post:{summary:"Create a budget. Starts in dry run unless dryRun is explicitly false.",responses:{"201":{description:"The created budget."},"422":{description:"Validation error."}}}},"/api/v1/spend/budgets/{budgetId}":{patch:{summary:"Update a budget. Pass expectedVersion for optimistic concurrency.",responses:{"200":{description:"The updated budget."},"409":{description:"Version conflict."}}},delete:{summary:"Delete a budget.",responses:{"200":{description:"Deletion result."}}}},"/api/v1/spend/anomalies":{get:{summary:"Detected spend anomalies.",parameters:[...e,{name:"since",in:"query",schema:{type:"string",format:"date-time"}},{name:"unacknowledged",in:"query",schema:{type:"boolean"}}],responses:{"200":{description:"Anomalies, newest first."}}}},"/api/v1/spend/anomalies/{anomalyId}/acknowledge":{post:{summary:"Acknowledge an anomaly.",responses:{"200":{description:"Acknowledgement result."}}}},"/api/v1/spend/signals":{get:{summary:"Signals anomaly detection can run on, with their gap and sample rules.",responses:{"200":{description:"Signal catalog."}}}},"/api/v1/spend/anomaly-configs":{get:{summary:"Anomaly detection configuration.",parameters:e,responses:{"200":{description:"Configs."}}},post:{summary:"Create or update the config for a signal.",responses:{"200":{description:"The config."}}}},"/api/v1/spend/anomaly-configs/{configId}":{delete:{summary:"Delete an anomaly config, returning the signal to its default.",responses:{"200":{description:"Result."}}}},"/api/v1/spend/anomaly-silences":{get:{summary:"Active anomaly silences.",parameters:e,responses:{"200":{description:"Silences."}}},post:{summary:"Silence a signal, route, or status code. A reason is required.",responses:{"200":{description:"The silence."}}}},"/api/v1/spend/anomaly-silences/{silenceId}":{delete:{summary:"Remove a silence.",responses:{"200":{description:"Result."}}}},"/api/v1/spend/enforcement":{get:{summary:"Enforcement actions in force.",parameters:[...e,{name:"active",in:"query",schema:{type:"boolean"}}],responses:{"200":{description:"Enforcement records."}}}},"/api/v1/spend/allowance":{get:{summary:"Whether an operation is allowed under current spend enforcement.",parameters:[...e,{name:"operation",in:"query",schema:{type:"string",enum:["build","deploy","function_invoke","request"]}}],responses:{"200":{description:"Allowance verdict and the budgets behind it."}}}}}}export{B as Wh,lt as Xh,te as Yh,De as Zh,mt as _h,be as $h,xs as ai,q as bi,ut as ci,ke as di,qs as ei,Ar as fi,Ot as gi,h as hi,hr as ii,Zs as ji,se as ki,Wt as li,Jt as mi,bi as ni,ki as oi,Zt as pi,vd as qi,yd as ri,st as si,bd as ti,kd as ui,Fd as vi,Pd as wi,Se as xi,et as yi,J as zi,$ as Ai,gi as Bi,de as Ci,nE as Di,iE as Ei,EE as Fi,hT as Gi,xi as Hi,nt as Ii,Mi as Ji,ji as Ki,Bi as Li,xd as Mi,tr as Ni,Wi as Oi,Ji as Pi,it as Qi,nr as Ri,fs as Si,w as Ti,wi as Ui,cE as Vi,ot as Wi,NE as Xi,k as Yi,lE as Zi,Tr as _i,ys as $i,ct as aj,LE as bj,mE as cj,uE as dj,or as ej,ye as fj,Nt as gj,Nr as hj,ee as ij,G as jj,gE as kj,oe as lj,RE as mj,Lr as nj,Fs as oj,Hs as pj,_t as qj,mr as rj,ur as sj,V as tj,tn as uj,rn as vj,yr as wj,Xt as xj,Le as yj,En as zj,Ge as Aj,pr as Bj,Dr as Cj,Qe as Dj,Tn as Ej,kr as Fj,Fr as Gj,Pr as Hj,Hr as Ij,It as Jj,cn as Kj,Nn as Lj,ta as Mj,ln as Nj,Kr as Oj,Ve as Pj,Ln as Qj,un as Rj,yt as Sj,ue as Tj,Dt as Uj,bt as Vj,kt as Wj,xr as Xj,gn as Yj,Br as Zj,Ft as _j,da as $j,Pt as ak,ga as bk,$r as ck,Ce as dk,qr as ek,Ua as fk,zr as gk,In as hk,Dn as ik,ge as jk,jt as kk,$n as lk,Gn as mk,Qn as nk,rs as ok,ss as pk,Vn as qk,Ha as rk,Ka as sk,$t as tk,Re as uk,ns as vk,as as wk,si as xk,ni as yk,qt as zk,Wa as Ak,ad as Bk,Ue as Ck,Ei as Dk,Yt as Ek,Td as Fk,oi as Gk,ci as Hk,Ni as Ik,Ld as Jk,_i as Kk,li as Lk,Cd as Mk,A as Nk,TE as Ok,oE as Pk,MT as Qk,jT as Rk};
|