@intentius/terragucci 0.4.31 → 0.4.32

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.
@@ -438,7 +438,7 @@ ${Yse(e)}
438
438
  ${JSON.stringify(e).replace(/<\//g,"<\\/").replace(/<!--/g,"<\\u0021--")}
439
439
  </script>
440
440
  </main></body></html>
441
- `}async function QG(e,t,n,r,o=new Date().toISOString(),s){let i=Yg(n.project,n.commit,t),{value:a}=await aa(e,`${i}/run.json`,c=>Jse(c,n,r,o),`wave ${r.number}'s row`,s);return await e.put(`${i}/run.html`,Qse(a),"text/html; charset=utf-8"),`${i}/run.html`}import{spawnSync as Jfe}from"node:child_process";import{spawn as Qpe,spawnSync as fO}from"node:child_process";import{chmodSync as BB,existsSync as dp,mkdirSync as pp,mkdtempSync as Vm,readdirSync as WB,readFileSync as Ic,realpathSync as HB,rmSync as Km,statSync as efe,writeFileSync as Ra}from"node:fs";import{tmpdir as gO}from"node:os";import{basename as mO,join as Ne,relative as qB,resolve as Rs,sep as JB}from"node:path";var Pr="0.4.31";import{existsSync as tie,readFileSync as nie}from"node:fs";import{join as rie}from"node:path";function eF(e,t,n,r){return e?{mode:e,source:`approval: ${e} in the config ${r}`,...e!=="sealed"&&t>0?{note:`chant.workspace.json ${r} still lists gates under identity.gates, and terragucci approve refuses an unsigned approval of a gate it lists; run terragucci init to drop them`}:{}}:t>0?{mode:"sealed",source:`identity.gates in chant.workspace.json ${r}, with no approval key`,note:"set approval: sealed in terragucci.yml to keep sealed approvals, or approval: ledger and run terragucci init to drop the gates"}:n?{mode:n,source:"the pipeline's --approval"}:{mode:"ledger",source:"the default"}}function Xg(e){if(e===void 0)return 0;try{return Object.keys(JSON.parse(e).identity?.gates??{}).length}catch{return 0}}async function Yr(e,t={}){let n=t.at??vs(e),r=JM(e,n),o=await ln(e,n,t.config?{config:t.config}:{});if("error"in o)throw new _(`approval is read from the config at base, and it could not be read (${o.error}), so the gate cannot be decided`);let s=!o.config.projects,i=s?Bs(o.config.approval):void 0,a=s&&VC(o.config.approval),c=s&&typeof o.config.agent?.token_env=="string"?o.config.agent.token_env:void 0,u=s&&typeof o.config.agent?.login=="string"&&o.config.agent.login?o.config.agent.login:void 0,l=s&&Array.isArray(o.config.policy?.override)?o.config.policy.override.filter(p=>typeof p=="string"):[],d=s&&typeof o.config.apply?.point=="string"?o.config.apply.point:void 0;return{...eF(i,r.gates.size,t.flag,"at base"),...r,base:n,overriders:l,...d?{point:d}:{},...a?{requireSigned:a}:{},...c?{agentTokenEnv:c}:{},...u?{agentLogin:u}:{}}}function Yu(e,t){if(t.projects)return;let n=rie(e,"chant.workspace.json");return eF(Bs(t.approval),Xg(tie(n)?nie(n,"utf-8"):void 0),void 0,"here")}var oie=(e,t)=>`-estate-outputs-from=${e}=${t}`;function tF(e,t,n,r,o){let s=[],i=[],a=new Map;for(let c of t){let u=n.get(c.estate);if(!u||u===e)continue;let l=f=>{i.push({upstream:u,data:c.name,outputs:"applied",why:f})};if(!r(u)){l("this change does not reach it, so its records stand");continue}let d=o(u);if(!d){l("it did not plan in this run");continue}let p=Ho(d.plan);p?p.changed?(i.push({upstream:u,data:c.name,outputs:"planned"}),a.has(c.estate)||(a.set(c.estate,u),s.push(oie(c.estate,d.file)))):l("its plan changes no output, so its records stand"):l("its plan names no output changes")}return{args:s,reads:i,upstreamOf:a}}function nF(e){let t=e.replace(/^[ \t]*[│╷╵]/gm," ").replace(/\s+/g," "),n=[],r=/Estate "([^"]+)"'s planned output "([^"]+)" \(in .*?\) is (known only after|sensitive)/g;for(let o of t.matchAll(r)){let s={kind:o[3]==="sensitive"?"sensitive":"unknown",estate:o[1],output:o[2]};n.some(i=>i.kind===s.kind&&i.estate===s.estate&&i.output===s.output)||n.push(s)}return n}var rF=e=>"`"+e+"`";function oF(e,t,n){let r=[...new Set(e.map(s=>s.output))].sort(),o=[...new Set(e.map(s=>t.get(s.estate)??s.estate))].sort();return`reads ${r.map(rF).join(", ")} of ${o.join(", ")}, unknown until ${n.length?`wave ${n.join(", ")}`:"it"} applies`}function sF(e,t){let n=[...new Set(e.map(o=>o.output))].sort(),r=[...new Set(e.map(o=>t.get(o.estate)??o.estate))].sort();return`reads sensitive ${n.length===1?"output":"outputs"} ${n.map(rF).join(", ")} of ${r.join(", ")}, which choudoufu never hands to another estate`}import{existsSync as sie,readdirSync as iie,readFileSync as uF,rmSync as aie,statSync as cie,writeFileSync as rT}from"node:fs";import{join as lF}from"node:path";function oT(e){return JSON.stringify(e).replace(/\$\{/g,()=>"$${").replace(/%\{/g,()=>"%%{")}function Qg(e,t,n){if(t===!0)return n;if(Array.isArray(e)||Array.isArray(t)){let o=Array.isArray(e)?e:[],s=Array.isArray(t)?t:[],i=Math.max(o.length,s.length);return`[${Array.from({length:i},(a,c)=>Qg(o[c],s[c],n)).join(", ")}]`}let r=o=>!!o&&typeof o=="object";if(r(e)||r(t)){let o=r(e)?e:{},s=r(t)?t:{};return`{ ${[...new Set([...Object.keys(o),...Object.keys(s).filter(a=>hc(s[a]))])].sort().map(a=>`${oT(a)} = ${Qg(o[a],s[a],n)}`).join(", ")} }`}return e==null?"null":typeof e=="string"?oT(e):JSON.stringify(e)}var uie=e=>e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),em=(e,t="g")=>new RegExp(`\\bdata\\.terraform_remote_state\\.${uie(e)}(?![\\w-])`,t);function ov(e,t){let n=new RegExp(`${em(t,"").source}(\\.outputs(?:\\.([A-Za-z_][\\w-]*)|\\[\\s*"([^"]+)"\\s*\\])?)?`,"g"),r=new Set;for(let o of e)for(let s of o.matchAll(n)){let i=s[2]??s[3];if(!i)return;r.add(i)}return r}function tm(e){try{return iie(e).filter(t=>t.endsWith(".tf")||t.endsWith(".tofu")||t.endsWith(".tf.json")||t.endsWith(".tofu.json")).map(t=>lF(e,t)).filter(t=>cie(t).isFile())}catch{return[]}}function sT(e,t){return ov(tm(e).map(n=>uF(n,"utf-8")),t)}var iF="terragucci_linked.tf",aF="terragucci_unknown",cF=e=>`terragucci_linked_${e}`;function dF(e,t){let n=lF(e,iF);if(sie(n))throw new Error(`${iF} is already in the root`);let r=tm(e),o=new Map(r.map(l=>[l,uF(l,"utf-8")])),s=[...o.values()],i=[],a=!1,c=[` ${aF} = jsondecode(timestamp())`];for(let l of t){let d=Gb(l.upstream,l.name,l.outputs,ov(s,l.name));d.unknown&&(a=!0),i.push(d);let p=[...l.outputs].sort(([f],[g])=>f<g?-1:1).map(([f,g])=>{let m=Qg(g.value,g.unknown,`local.${aF}`);return`${oT(f)} = ${g.sensitive?`sensitive(${m})`:m}`});c.push(` ${cF(l.name)} = { outputs = { ${p.join(", ")} } }`)}let u=()=>{for(let[l,d]of o)rT(l,d);aie(n,{force:!0})};try{for(let[l,d]of o){let p=d;for(let f of t)p=p.replace(em(f.name),`local.${cF(f.name)}`);p!==d&&rT(l,p)}rT(n,`# Written by terragucci for a linked plan, and removed once it is made.
441
+ `}async function QG(e,t,n,r,o=new Date().toISOString(),s){let i=Yg(n.project,n.commit,t),{value:a}=await aa(e,`${i}/run.json`,c=>Jse(c,n,r,o),`wave ${r.number}'s row`,s);return await e.put(`${i}/run.html`,Qse(a),"text/html; charset=utf-8"),`${i}/run.html`}import{spawnSync as Jfe}from"node:child_process";import{spawn as Qpe,spawnSync as fO}from"node:child_process";import{chmodSync as BB,existsSync as dp,mkdirSync as pp,mkdtempSync as Vm,readdirSync as WB,readFileSync as Ic,realpathSync as HB,rmSync as Km,statSync as efe,writeFileSync as Ra}from"node:fs";import{tmpdir as gO}from"node:os";import{basename as mO,join as Ne,relative as qB,resolve as Rs,sep as JB}from"node:path";var Pr="0.4.32";import{existsSync as tie,readFileSync as nie}from"node:fs";import{join as rie}from"node:path";function eF(e,t,n,r){return e?{mode:e,source:`approval: ${e} in the config ${r}`,...e!=="sealed"&&t>0?{note:`chant.workspace.json ${r} still lists gates under identity.gates, and terragucci approve refuses an unsigned approval of a gate it lists; run terragucci init to drop them`}:{}}:t>0?{mode:"sealed",source:`identity.gates in chant.workspace.json ${r}, with no approval key`,note:"set approval: sealed in terragucci.yml to keep sealed approvals, or approval: ledger and run terragucci init to drop the gates"}:n?{mode:n,source:"the pipeline's --approval"}:{mode:"ledger",source:"the default"}}function Xg(e){if(e===void 0)return 0;try{return Object.keys(JSON.parse(e).identity?.gates??{}).length}catch{return 0}}async function Yr(e,t={}){let n=t.at??vs(e),r=JM(e,n),o=await ln(e,n,t.config?{config:t.config}:{});if("error"in o)throw new _(`approval is read from the config at base, and it could not be read (${o.error}), so the gate cannot be decided`);let s=!o.config.projects,i=s?Bs(o.config.approval):void 0,a=s&&VC(o.config.approval),c=s&&typeof o.config.agent?.token_env=="string"?o.config.agent.token_env:void 0,u=s&&typeof o.config.agent?.login=="string"&&o.config.agent.login?o.config.agent.login:void 0,l=s&&Array.isArray(o.config.policy?.override)?o.config.policy.override.filter(p=>typeof p=="string"):[],d=s&&typeof o.config.apply?.point=="string"?o.config.apply.point:void 0;return{...eF(i,r.gates.size,t.flag,"at base"),...r,base:n,overriders:l,...d?{point:d}:{},...a?{requireSigned:a}:{},...c?{agentTokenEnv:c}:{},...u?{agentLogin:u}:{}}}function Yu(e,t){if(t.projects)return;let n=rie(e,"chant.workspace.json");return eF(Bs(t.approval),Xg(tie(n)?nie(n,"utf-8"):void 0),void 0,"here")}var oie=(e,t)=>`-estate-outputs-from=${e}=${t}`;function tF(e,t,n,r,o){let s=[],i=[],a=new Map;for(let c of t){let u=n.get(c.estate);if(!u||u===e)continue;let l=f=>{i.push({upstream:u,data:c.name,outputs:"applied",why:f})};if(!r(u)){l("this change does not reach it, so its records stand");continue}let d=o(u);if(!d){l("it did not plan in this run");continue}let p=Ho(d.plan);p?p.changed?(i.push({upstream:u,data:c.name,outputs:"planned"}),a.has(c.estate)||(a.set(c.estate,u),s.push(oie(c.estate,d.file)))):l("its plan changes no output, so its records stand"):l("its plan names no output changes")}return{args:s,reads:i,upstreamOf:a}}function nF(e){let t=e.replace(/^[ \t]*[│╷╵]/gm," ").replace(/\s+/g," "),n=[],r=/Estate "([^"]+)"'s planned output "([^"]+)" \(in .*?\) is (known only after|sensitive)/g;for(let o of t.matchAll(r)){let s={kind:o[3]==="sensitive"?"sensitive":"unknown",estate:o[1],output:o[2]};n.some(i=>i.kind===s.kind&&i.estate===s.estate&&i.output===s.output)||n.push(s)}return n}var rF=e=>"`"+e+"`";function oF(e,t,n){let r=[...new Set(e.map(s=>s.output))].sort(),o=[...new Set(e.map(s=>t.get(s.estate)??s.estate))].sort();return`reads ${r.map(rF).join(", ")} of ${o.join(", ")}, unknown until ${n.length?`wave ${n.join(", ")}`:"it"} applies`}function sF(e,t){let n=[...new Set(e.map(o=>o.output))].sort(),r=[...new Set(e.map(o=>t.get(o.estate)??o.estate))].sort();return`reads sensitive ${n.length===1?"output":"outputs"} ${n.map(rF).join(", ")} of ${r.join(", ")}, which choudoufu never hands to another estate`}import{existsSync as sie,readdirSync as iie,readFileSync as uF,rmSync as aie,statSync as cie,writeFileSync as rT}from"node:fs";import{join as lF}from"node:path";function oT(e){return JSON.stringify(e).replace(/\$\{/g,()=>"$${").replace(/%\{/g,()=>"%%{")}function Qg(e,t,n){if(t===!0)return n;if(Array.isArray(e)||Array.isArray(t)){let o=Array.isArray(e)?e:[],s=Array.isArray(t)?t:[],i=Math.max(o.length,s.length);return`[${Array.from({length:i},(a,c)=>Qg(o[c],s[c],n)).join(", ")}]`}let r=o=>!!o&&typeof o=="object";if(r(e)||r(t)){let o=r(e)?e:{},s=r(t)?t:{};return`{ ${[...new Set([...Object.keys(o),...Object.keys(s).filter(a=>hc(s[a]))])].sort().map(a=>`${oT(a)} = ${Qg(o[a],s[a],n)}`).join(", ")} }`}return e==null?"null":typeof e=="string"?oT(e):JSON.stringify(e)}var uie=e=>e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),em=(e,t="g")=>new RegExp(`\\bdata\\.terraform_remote_state\\.${uie(e)}(?![\\w-])`,t);function ov(e,t){let n=new RegExp(`${em(t,"").source}(\\.outputs(?:\\.([A-Za-z_][\\w-]*)|\\[\\s*"([^"]+)"\\s*\\])?)?`,"g"),r=new Set;for(let o of e)for(let s of o.matchAll(n)){let i=s[2]??s[3];if(!i)return;r.add(i)}return r}function tm(e){try{return iie(e).filter(t=>t.endsWith(".tf")||t.endsWith(".tofu")||t.endsWith(".tf.json")||t.endsWith(".tofu.json")).map(t=>lF(e,t)).filter(t=>cie(t).isFile())}catch{return[]}}function sT(e,t){return ov(tm(e).map(n=>uF(n,"utf-8")),t)}var iF="terragucci_linked.tf",aF="terragucci_unknown",cF=e=>`terragucci_linked_${e}`;function dF(e,t){let n=lF(e,iF);if(sie(n))throw new Error(`${iF} is already in the root`);let r=tm(e),o=new Map(r.map(l=>[l,uF(l,"utf-8")])),s=[...o.values()],i=[],a=!1,c=[` ${aF} = jsondecode(timestamp())`];for(let l of t){let d=Gb(l.upstream,l.name,l.outputs,ov(s,l.name));d.unknown&&(a=!0),i.push(d);let p=[...l.outputs].sort(([f],[g])=>f<g?-1:1).map(([f,g])=>{let m=Qg(g.value,g.unknown,`local.${aF}`);return`${oT(f)} = ${g.sensitive?`sensitive(${m})`:m}`});c.push(` ${cF(l.name)} = { outputs = { ${p.join(", ")} } }`)}let u=()=>{for(let[l,d]of o)rT(l,d);aie(n,{force:!0})};try{for(let[l,d]of o){let p=d;for(let f of t)p=p.replace(em(f.name),`local.${cF(f.name)}`);p!==d&&rT(l,p)}rT(n,`# Written by terragucci for a linked plan, and removed once it is made.
442
442
  locals {
443
443
  ${c.join(`
444
444
  `)}
@@ -468,7 +468,7 @@ ${n}`).digest("hex").slice(0,16)}`;if(kv("git",["-C",e,"fetch","-q","--no-tags",
468
468
  `).join(`
469
469
  `)}function WT(e,t){let[n,r]=t.split(".");if(!n||!r||!hce(e))return;let o=new RegExp(`^\\s*resource\\s+"${n}"\\s+"${r}"\\s*\\{`,"m");return yce(e).filter(s=>s.endsWith(".tf")).sort().find(s=>o.test(wce(bce(e,s),"utf-8")))}function hU(e,t){return`${e.replace(/\n*$/,`
470
470
  `)}
471
- ${t}`}import{spawnSync as Oce}from"node:child_process";import{createHash as $ce}from"node:crypto";var HT={"ghcr.io/intentius/terragucci-tofu:0.4.31-tofu1.13.1":"sha256:c1a1841b5436bb1a33e062167f4e037dacc18ad241f09767d7c299aacb16b3f3","ghcr.io/intentius/terragucci-terraform:0.4.31-tf1.14.9":"sha256:2c68d9d2d7be88ec868ca85bcf1b641f9745f98f89a1f2bc5183e04995ed584f","ghcr.io/intentius/terragucci-terragrunt:0.4.31-tg1.1.6-tofu1.13.1":"sha256:910cfe700408216431c065519cf8fabd1b7136b4759f52c47dc54646c7df41bb","ghcr.io/intentius/terragucci-choudoufu:0.4.31-choudoufu0.28.0":"sha256:061336e80976e55d502433613c4946ce04d192094465d1e4fe2071457d343167"};var yU="ghcr.io/intentius",ai={tofu:"1.13.1",terraform:"1.14.9",terragrunt:"1.1.6",choudoufu:"0.28.0"};function wU(e,t=Pr){return e==="terragrunt"?`${t}-tg${ai.terragrunt}-tofu${ai.tofu}`:`${t}-${e==="terraform"?"tf":e}${ai[e]}`}function JT(e,t=HT){let n=`${yU}/terragucci-${e}`,r=wU(e);return{repository:n,tag:r,digest:t[`${n}:${r}`]}}function bU(e=HT){let t=`${yU}/terragucci-terragrunt`,n=wU("terragrunt");return{repository:t,tag:n,digest:e[`${t}:${n}`]}}function KT(e){return e.digest?`${e.repository}:${e.tag}@${e.digest}`:`${e.repository}:${e.tag}`}var qT={laya:"0.3.28",torch:"2.14.0",pip:"26.2.1",checkpoint:{repo:"convaiinnovations/laya",name:"english",revision:"55cf4c4ebb4ebe31b2550e8bdf3bd21b99753851",weightsSha256:"891102d372688fc2a094dac56a384bc537b87c63f21f9f3dac0be2b7cbc8d86c"},port:8790},vU=`laya-${qT.laya}-${qT.checkpoint.name}@${qT.checkpoint.revision.slice(0,12)}`;var kce="https://api.typesafe.ai",_ce={noul:.8,choice:.7,score:.7},Sce={laya:!0,jev:!0,von:!1,decider:!1};function Rce(e,t){let n=r=>{throw new Error(`decide: question ${e} ${r}`)};if(/^[A-Za-z0-9_-]{1,64}$/.test(e)||n("needs a name of letters, digits, - and _"),t.instructions||n("has no instructions"),t.threshold!==void 0&&!(t.threshold>0&&t.threshold<=1)&&n("has a threshold outside (0, 1]"),t.type==="noul")(!t.criteria?.true||!t.criteria?.false)&&n("is a noul and needs criteria.true and criteria.false");else if(t.type==="choice"){let r=Object.keys(t.criteria??{}).length;(r<2||r>255)&&n(`is a choice with ${r} options; use 2 to 255`)}else if(t.type==="score"){let r=t.criteria?.length??0;(r<2||r>10)&&n(`is a score with ${r} levels; use 2 to 10`)}else n(`has type ${JSON.stringify(t.type)}; use noul, choice or score`)}function $U(e,t){return e.threshold??t?.thresholds?.[e.type]??_ce[e.type]}var Ev=e=>typeof e=="number"&&Number.isFinite(e)&&e>=0&&e<=1;function xce(e){let t=e.url??(e.backend==="jev"?kce:void 0);if(!t)return;let n=t.replace(/\/+$/,"");return n.endsWith("/v1/systemone")?n:`${n}/v1/systemone`}function Ece(e){return e.model??(e.backend==="laya"?vU:void 0)}function Tce(e,t,n,r){let o={question:e,type:t.type,threshold:r},s=d=>({...o,status:"unavailable",reason:d});if(n===null||typeof n!="object")return s("the response has no answer for it");let i=n;if(i.type!==t.type)return s(`the answer's type is ${JSON.stringify(i.type)}, not ${t.type}`);let a,c,u,l;if(t.type==="noul"){if(!Ev(i.noul))return s("the noul answer is not a probability");a=i.noul>=.5?"true":"false",c=Math.max(i.noul,1-i.noul),u={true:i.noul,false:1-i.noul}}else{let d=i.probabilities;if(d===null||typeof d!="object"||!Object.values(d).every(Ev))return s("the answer has no probabilities");if(u=d,t.type==="choice"){if(typeof i.choice!="string"||!(i.choice in t.criteria))return s(`the choice ${JSON.stringify(i.choice)} is not one of the options`);if(!Ev(u[i.choice]))return s("the chosen option has no probability");a=i.choice,c=u[i.choice]}else{let p=t.criteria.map((g,m)=>String(m));if(!p.every(g=>Ev(u[g])))return s("the score's probabilities do not cover every level");let f=p.reduce((g,m)=>u[m]>u[g]?m:g,p[0]);a=t.criteria[Number(f)],c=u[f],typeof i.score=="number"&&Number.isFinite(i.score)&&(l=i.score)}}return{...o,status:c>=r?"confident":"not-confident",answer:a,probability:c,probabilities:u,...l===void 0?{}:{score:l}}}async function Xd(e,t,n,r={}){for(let[v,$]of Object.entries(n))Rce(v,$);let o=`sha256:${$ce("sha256").update(JSON.stringify(t)).digest("hex")}`,s=(v,$)=>Object.fromEntries(Object.entries(n).map(([S,R])=>[S,{question:S,type:R.type,status:v,threshold:$U(R,e),reason:$}]));if(!e)return{stateDigest:o,decisions:s("off","decide is not configured")};let i=xce(e),a=Ece(e),c={backend:e.backend,url:i,model:a,stateDigest:o};if(!i)return{...c,decisions:s("unavailable","decide.url is not set")};let u={"content-type":"application/json",accept:"application/json"};if(e.token_env){let v=(r.env??process.env)[e.token_env];if(!v)return{...c,decisions:s("unavailable",`${e.token_env} is not set`)};u.authorization=`Bearer ${v}`}let l=Object.fromEntries(Object.entries(n).map(([v,$])=>[v,{type:$.type,instructions:$.instructions,criteria:$.criteria}])),d=JSON.stringify({...a?{model:a}:{},state:t,questions:l}),p=r.fetch??globalThis.fetch,f=Date.now(),g,m;try{let v=await p(i,{method:"POST",headers:u,body:d,signal:AbortSignal.timeout(r.timeoutMs??3e4)});if(g=v.status,m=await v.text(),!v.ok){let $=m.slice(0,300);try{let S=JSON.parse(m),R=S.detail??S.error;R!==void 0&&($=typeof R=="string"?R:JSON.stringify(R))}catch{}return{...c,ms:Date.now()-f,decisions:s("unavailable",`the service answered HTTP ${g}: ${$}`)}}}catch(v){return{...c,ms:Date.now()-f,decisions:s("unavailable",`the service did not answer: ${v.message}`)}}let h=Date.now()-f,y;try{y=JSON.parse(m)}catch{return{...c,ms:h,decisions:s("unavailable","the response is not JSON")}}if(y===null||typeof y!="object"||y.answers===null||typeof y.answers!="object")return{...c,ms:h,decisions:s("unavailable","the response has no answers")};let w=typeof y.model=="string"?y.model:void 0;if(Sce[e.backend]&&a&&w!==a)return{...c,model:w??a,ms:h,decisions:s("unavailable",`the service answered as ${w??"no model"}, and decide.model pins ${a}`)};let b=y.answers,k=Object.fromEntries(Object.entries(n).map(([v,$])=>[v,Tce(v,$,b[v],$U($,e))]));return{...c,model:w??a,ms:h,decisions:k}}function Qd(e){return e?.status==="confident"}function Sc(e,t){let n=t.model?`, ${t.model}`:"";if(e.status==="off")return`${e.question}: not asked; decide is not configured`;if(e.status==="unavailable")return`${e.question}: no decision; ${e.reason}`;let r=(e.probability??0).toFixed(2),o=e.threshold.toFixed(2);return e.status==="confident"?`${e.question}: ${e.answer} (${r}, threshold ${o}${n})`:`${e.question}: ${e.answer} (${r}, below the ${o} threshold, so not used${n})`}var VT={type:"noul",instructions:"Read the pull request's title and description, then the summary of what its plan does. Does the description leave out or misstate a destroy or a replacement the plan makes?",criteria:{true:"the description does not describe the plan: a destroy or replacement in the plan is missing from it or contradicted by it",false:"the description describes what the plan does, including every destroy and replacement"}},ZT={type:"choice",instructions:"An attribute of a resource managed by Terraform changed outside Terraform. From the resource, the attribute and the two values, which kind of change is it most likely?",criteria:{controller:"a controller or the cloud service wrote it, as autoscaling, a scheduler or a service that adds tags does",human:"a person edited it by hand in a console or with a CLI","provider-default":"the provider or the cloud API changed a default value it reports"}},Ace={type:"choice",instructions:"These are the commits to a Terraform module since its last release, and a summary of its diff. Which semantic version bump does the change call for?",criteria:{major:"a breaking change: a variable or output removed or renamed, a required variable added, or a resource replaced for every caller",minor:"a new feature that keeps every existing caller working, such as a new optional variable, output or resource",patch:"a fix or an internal change that callers do not see"}},kU={prIntent:VT,driftActor:ZT,versionBump:Ace};var wm=e=>e!==null&&typeof e=="object"&&!Array.isArray(e),tl=e=>typeof e=="number",Pce=/^(aws:|eks:|elasticbeanstalk:|kubernetes\.io\/|k8s\.io\/|karpenter\.sh\/)/;function jce(e){let t=wm(e.before)?e.before:{},n=wm(e.live)?e.live:{},r=[...new Set([...Object.keys(t),...Object.keys(n)])].filter(o=>JSON.stringify(t[o]??null)!==JSON.stringify(n[o]??null));return r.length>0&&r.every(o=>Pce.test(o))}var Ice=e=>t=>{let n=Array.isArray(t.before)?t.before[0]:t.before,r=Array.isArray(t.live)?t.live[0]:t.live;if(!wm(n)||!wm(r))return!1;let o=[...new Set([...Object.keys(n),...Object.keys(r)])].filter(s=>JSON.stringify(n[s]??null)!==JSON.stringify(r[s]??null));return o.length>0&&o.every(s=>s===e)},Cce=[{type:"aws_ecs_service",path:"desired_count",actor:"controller",why:"Application Auto Scaling sets an ECS service's task count",when:e=>tl(e.live)},{type:"aws_autoscaling_group",path:"desired_capacity",actor:"controller",why:"the group's scaling policies and scheduled actions set its desired capacity",when:e=>tl(e.live)},{type:"aws_eks_node_group",path:"scaling_config",actor:"controller",why:"Cluster Autoscaler or Karpenter sets a node group's desired size",when:Ice("desired_size")},{type:"aws_dynamodb_table",path:"read_capacity",actor:"controller",why:"Application Auto Scaling adjusts a provisioned table's read capacity",when:e=>tl(e.live)},{type:"aws_dynamodb_table",path:"write_capacity",actor:"controller",why:"Application Auto Scaling adjusts a provisioned table's write capacity",when:e=>tl(e.live)},{type:"aws_appautoscaling_target",path:"min_capacity",actor:"controller",why:"a scheduled action on the scalable target moves its minimum",when:e=>tl(e.live)},{type:"aws_appautoscaling_target",path:"max_capacity",actor:"controller",why:"a scheduled action on the scalable target moves its maximum",when:e=>tl(e.live)},{type:"aws_db_instance",path:"engine_version",actor:"controller",why:"RDS applies minor engine upgrades itself when auto_minor_version_upgrade is on",when:e=>typeof e.live=="string"&&typeof e.before=="string"},{type:"aws_launch_template",path:"latest_version",actor:"controller",why:"a service that publishes a launch template version (EC2 Image Builder, Karpenter) moves latest_version",when:e=>tl(e.live)},{type:"aws_elasticache_replication_group",path:"engine_version_actual",actor:"controller",why:"ElastiCache applies minor engine upgrades itself"},...["tags","tags_all"].map(e=>({type:"*",path:e,actor:"controller",why:"a service adds tags in its own namespace (aws:, eks:, karpenter.sh/ and the like)",when:jce}))];function Nce(e,t,n){return Cce.find(r=>(r.type===e||r.type==="*")&&r.path===t&&(r.when?.(n)??!0))}var Dce=/terraform|opentofu|tofu/i;function Mce(e){let t=e.userIdentity??{},n=t.invokedBy??e.invokedBy;if(t.type==="AWSService"||n&&/\.amazonaws\.com$/.test(n))return{actor:"controller",who:n??"an AWS service"};let o=(t.arn??"").split("/").at(-1)??"";if(t.type==="IAMUser")return{actor:"human",who:t.userName??o};if(t.type==="Root")return{actor:"human",who:"the root user"};if(t.type==="IdentityCenterUser"||t.type==="FederatedUser")return{actor:"human",who:t.userName??o};if(t.type==="AssumedRole"){let s=t.sessionContext?.sessionIssuer?.userName??"";if(/^AWSReservedSSO_/.test(s)||o.includes("@"))return{actor:"human",who:o||s}}}function Lce(e){let n=e.filter(o=>o.readOnly!==!0&&o.readOnly!=="true"&&!Dce.test(o.userAgent??"")).sort((o,s)=>String(s.eventTime??"").localeCompare(String(o.eventTime??"")))[0],r=n&&Mce(n);return n&&r?{status:"found",...r,event:n.eventName??"an event",at:n.eventTime??""}:{status:"silent"}}function bm(e={}){let t=e.run??(n=>{let r=Oce("aws",n,{encoding:"utf-8",maxBuffer:67108864});return{status:r.status,stdout:r.stdout??"",stderr:r.stderr??"",error:r.error}});return{async lookup(n){if(!n.type.startsWith("aws_"))return{status:"unavailable",reason:`no audit log reader for ${n.type}`};let r=new Date((e.now?.()??new Date).getTime()-(e.days??14)*864e5).toISOString(),o=t(["cloudtrail","lookup-events","--output","json","--max-results","50","--start-time",r,"--lookup-attributes",`AttributeKey=ResourceName,AttributeValue=${n.ref}`,...e.region?["--region",e.region]:[]]);if(o.error?.code==="ENOENT")return{status:"unavailable",reason:"the aws CLI is not installed"};if(o.error||o.status!==0)return{status:"unavailable",reason:(o.error?.message??(o.stderr.trim()||`aws exited ${o.status}`)).split(`
471
+ ${t}`}import{spawnSync as Oce}from"node:child_process";import{createHash as $ce}from"node:crypto";var HT={"ghcr.io/intentius/terragucci-tofu:0.4.32-tofu1.13.1":"sha256:71558f6e91a83406d0218ef95c8fc3aafc75ab57aee96150c7dae7a3bcf960ff","ghcr.io/intentius/terragucci-terraform:0.4.32-tf1.14.9":"sha256:e91c9419bd166d2832cd250d9f2121626421a829e4ed35f23bd659ae8147722d","ghcr.io/intentius/terragucci-terragrunt:0.4.32-tg1.1.6-tofu1.13.1":"sha256:a2e9153d5320711d65498bb3c2a2d32ad237c5d005195524f1e031a2c0490521","ghcr.io/intentius/terragucci-choudoufu:0.4.32-choudoufu0.28.0":"sha256:5790094d5380ff33f31753056c4f90c8d6c89ce142124cd6884a6ad9e124f437"};var yU="ghcr.io/intentius",ai={tofu:"1.13.1",terraform:"1.14.9",terragrunt:"1.1.6",choudoufu:"0.28.0"};function wU(e,t=Pr){return e==="terragrunt"?`${t}-tg${ai.terragrunt}-tofu${ai.tofu}`:`${t}-${e==="terraform"?"tf":e}${ai[e]}`}function JT(e,t=HT){let n=`${yU}/terragucci-${e}`,r=wU(e);return{repository:n,tag:r,digest:t[`${n}:${r}`]}}function bU(e=HT){let t=`${yU}/terragucci-terragrunt`,n=wU("terragrunt");return{repository:t,tag:n,digest:e[`${t}:${n}`]}}function KT(e){return e.digest?`${e.repository}:${e.tag}@${e.digest}`:`${e.repository}:${e.tag}`}var qT={laya:"0.3.28",torch:"2.14.0",pip:"26.2.1",checkpoint:{repo:"convaiinnovations/laya",name:"english",revision:"55cf4c4ebb4ebe31b2550e8bdf3bd21b99753851",weightsSha256:"891102d372688fc2a094dac56a384bc537b87c63f21f9f3dac0be2b7cbc8d86c"},port:8790},vU=`laya-${qT.laya}-${qT.checkpoint.name}@${qT.checkpoint.revision.slice(0,12)}`;var kce="https://api.typesafe.ai",_ce={noul:.8,choice:.7,score:.7},Sce={laya:!0,jev:!0,von:!1,decider:!1};function Rce(e,t){let n=r=>{throw new Error(`decide: question ${e} ${r}`)};if(/^[A-Za-z0-9_-]{1,64}$/.test(e)||n("needs a name of letters, digits, - and _"),t.instructions||n("has no instructions"),t.threshold!==void 0&&!(t.threshold>0&&t.threshold<=1)&&n("has a threshold outside (0, 1]"),t.type==="noul")(!t.criteria?.true||!t.criteria?.false)&&n("is a noul and needs criteria.true and criteria.false");else if(t.type==="choice"){let r=Object.keys(t.criteria??{}).length;(r<2||r>255)&&n(`is a choice with ${r} options; use 2 to 255`)}else if(t.type==="score"){let r=t.criteria?.length??0;(r<2||r>10)&&n(`is a score with ${r} levels; use 2 to 10`)}else n(`has type ${JSON.stringify(t.type)}; use noul, choice or score`)}function $U(e,t){return e.threshold??t?.thresholds?.[e.type]??_ce[e.type]}var Ev=e=>typeof e=="number"&&Number.isFinite(e)&&e>=0&&e<=1;function xce(e){let t=e.url??(e.backend==="jev"?kce:void 0);if(!t)return;let n=t.replace(/\/+$/,"");return n.endsWith("/v1/systemone")?n:`${n}/v1/systemone`}function Ece(e){return e.model??(e.backend==="laya"?vU:void 0)}function Tce(e,t,n,r){let o={question:e,type:t.type,threshold:r},s=d=>({...o,status:"unavailable",reason:d});if(n===null||typeof n!="object")return s("the response has no answer for it");let i=n;if(i.type!==t.type)return s(`the answer's type is ${JSON.stringify(i.type)}, not ${t.type}`);let a,c,u,l;if(t.type==="noul"){if(!Ev(i.noul))return s("the noul answer is not a probability");a=i.noul>=.5?"true":"false",c=Math.max(i.noul,1-i.noul),u={true:i.noul,false:1-i.noul}}else{let d=i.probabilities;if(d===null||typeof d!="object"||!Object.values(d).every(Ev))return s("the answer has no probabilities");if(u=d,t.type==="choice"){if(typeof i.choice!="string"||!(i.choice in t.criteria))return s(`the choice ${JSON.stringify(i.choice)} is not one of the options`);if(!Ev(u[i.choice]))return s("the chosen option has no probability");a=i.choice,c=u[i.choice]}else{let p=t.criteria.map((g,m)=>String(m));if(!p.every(g=>Ev(u[g])))return s("the score's probabilities do not cover every level");let f=p.reduce((g,m)=>u[m]>u[g]?m:g,p[0]);a=t.criteria[Number(f)],c=u[f],typeof i.score=="number"&&Number.isFinite(i.score)&&(l=i.score)}}return{...o,status:c>=r?"confident":"not-confident",answer:a,probability:c,probabilities:u,...l===void 0?{}:{score:l}}}async function Xd(e,t,n,r={}){for(let[v,$]of Object.entries(n))Rce(v,$);let o=`sha256:${$ce("sha256").update(JSON.stringify(t)).digest("hex")}`,s=(v,$)=>Object.fromEntries(Object.entries(n).map(([S,R])=>[S,{question:S,type:R.type,status:v,threshold:$U(R,e),reason:$}]));if(!e)return{stateDigest:o,decisions:s("off","decide is not configured")};let i=xce(e),a=Ece(e),c={backend:e.backend,url:i,model:a,stateDigest:o};if(!i)return{...c,decisions:s("unavailable","decide.url is not set")};let u={"content-type":"application/json",accept:"application/json"};if(e.token_env){let v=(r.env??process.env)[e.token_env];if(!v)return{...c,decisions:s("unavailable",`${e.token_env} is not set`)};u.authorization=`Bearer ${v}`}let l=Object.fromEntries(Object.entries(n).map(([v,$])=>[v,{type:$.type,instructions:$.instructions,criteria:$.criteria}])),d=JSON.stringify({...a?{model:a}:{},state:t,questions:l}),p=r.fetch??globalThis.fetch,f=Date.now(),g,m;try{let v=await p(i,{method:"POST",headers:u,body:d,signal:AbortSignal.timeout(r.timeoutMs??3e4)});if(g=v.status,m=await v.text(),!v.ok){let $=m.slice(0,300);try{let S=JSON.parse(m),R=S.detail??S.error;R!==void 0&&($=typeof R=="string"?R:JSON.stringify(R))}catch{}return{...c,ms:Date.now()-f,decisions:s("unavailable",`the service answered HTTP ${g}: ${$}`)}}}catch(v){return{...c,ms:Date.now()-f,decisions:s("unavailable",`the service did not answer: ${v.message}`)}}let h=Date.now()-f,y;try{y=JSON.parse(m)}catch{return{...c,ms:h,decisions:s("unavailable","the response is not JSON")}}if(y===null||typeof y!="object"||y.answers===null||typeof y.answers!="object")return{...c,ms:h,decisions:s("unavailable","the response has no answers")};let w=typeof y.model=="string"?y.model:void 0;if(Sce[e.backend]&&a&&w!==a)return{...c,model:w??a,ms:h,decisions:s("unavailable",`the service answered as ${w??"no model"}, and decide.model pins ${a}`)};let b=y.answers,k=Object.fromEntries(Object.entries(n).map(([v,$])=>[v,Tce(v,$,b[v],$U($,e))]));return{...c,model:w??a,ms:h,decisions:k}}function Qd(e){return e?.status==="confident"}function Sc(e,t){let n=t.model?`, ${t.model}`:"";if(e.status==="off")return`${e.question}: not asked; decide is not configured`;if(e.status==="unavailable")return`${e.question}: no decision; ${e.reason}`;let r=(e.probability??0).toFixed(2),o=e.threshold.toFixed(2);return e.status==="confident"?`${e.question}: ${e.answer} (${r}, threshold ${o}${n})`:`${e.question}: ${e.answer} (${r}, below the ${o} threshold, so not used${n})`}var VT={type:"noul",instructions:"Read the pull request's title and description, then the summary of what its plan does. Does the description leave out or misstate a destroy or a replacement the plan makes?",criteria:{true:"the description does not describe the plan: a destroy or replacement in the plan is missing from it or contradicted by it",false:"the description describes what the plan does, including every destroy and replacement"}},ZT={type:"choice",instructions:"An attribute of a resource managed by Terraform changed outside Terraform. From the resource, the attribute and the two values, which kind of change is it most likely?",criteria:{controller:"a controller or the cloud service wrote it, as autoscaling, a scheduler or a service that adds tags does",human:"a person edited it by hand in a console or with a CLI","provider-default":"the provider or the cloud API changed a default value it reports"}},Ace={type:"choice",instructions:"These are the commits to a Terraform module since its last release, and a summary of its diff. Which semantic version bump does the change call for?",criteria:{major:"a breaking change: a variable or output removed or renamed, a required variable added, or a resource replaced for every caller",minor:"a new feature that keeps every existing caller working, such as a new optional variable, output or resource",patch:"a fix or an internal change that callers do not see"}},kU={prIntent:VT,driftActor:ZT,versionBump:Ace};var wm=e=>e!==null&&typeof e=="object"&&!Array.isArray(e),tl=e=>typeof e=="number",Pce=/^(aws:|eks:|elasticbeanstalk:|kubernetes\.io\/|k8s\.io\/|karpenter\.sh\/)/;function jce(e){let t=wm(e.before)?e.before:{},n=wm(e.live)?e.live:{},r=[...new Set([...Object.keys(t),...Object.keys(n)])].filter(o=>JSON.stringify(t[o]??null)!==JSON.stringify(n[o]??null));return r.length>0&&r.every(o=>Pce.test(o))}var Ice=e=>t=>{let n=Array.isArray(t.before)?t.before[0]:t.before,r=Array.isArray(t.live)?t.live[0]:t.live;if(!wm(n)||!wm(r))return!1;let o=[...new Set([...Object.keys(n),...Object.keys(r)])].filter(s=>JSON.stringify(n[s]??null)!==JSON.stringify(r[s]??null));return o.length>0&&o.every(s=>s===e)},Cce=[{type:"aws_ecs_service",path:"desired_count",actor:"controller",why:"Application Auto Scaling sets an ECS service's task count",when:e=>tl(e.live)},{type:"aws_autoscaling_group",path:"desired_capacity",actor:"controller",why:"the group's scaling policies and scheduled actions set its desired capacity",when:e=>tl(e.live)},{type:"aws_eks_node_group",path:"scaling_config",actor:"controller",why:"Cluster Autoscaler or Karpenter sets a node group's desired size",when:Ice("desired_size")},{type:"aws_dynamodb_table",path:"read_capacity",actor:"controller",why:"Application Auto Scaling adjusts a provisioned table's read capacity",when:e=>tl(e.live)},{type:"aws_dynamodb_table",path:"write_capacity",actor:"controller",why:"Application Auto Scaling adjusts a provisioned table's write capacity",when:e=>tl(e.live)},{type:"aws_appautoscaling_target",path:"min_capacity",actor:"controller",why:"a scheduled action on the scalable target moves its minimum",when:e=>tl(e.live)},{type:"aws_appautoscaling_target",path:"max_capacity",actor:"controller",why:"a scheduled action on the scalable target moves its maximum",when:e=>tl(e.live)},{type:"aws_db_instance",path:"engine_version",actor:"controller",why:"RDS applies minor engine upgrades itself when auto_minor_version_upgrade is on",when:e=>typeof e.live=="string"&&typeof e.before=="string"},{type:"aws_launch_template",path:"latest_version",actor:"controller",why:"a service that publishes a launch template version (EC2 Image Builder, Karpenter) moves latest_version",when:e=>tl(e.live)},{type:"aws_elasticache_replication_group",path:"engine_version_actual",actor:"controller",why:"ElastiCache applies minor engine upgrades itself"},...["tags","tags_all"].map(e=>({type:"*",path:e,actor:"controller",why:"a service adds tags in its own namespace (aws:, eks:, karpenter.sh/ and the like)",when:jce}))];function Nce(e,t,n){return Cce.find(r=>(r.type===e||r.type==="*")&&r.path===t&&(r.when?.(n)??!0))}var Dce=/terraform|opentofu|tofu/i;function Mce(e){let t=e.userIdentity??{},n=t.invokedBy??e.invokedBy;if(t.type==="AWSService"||n&&/\.amazonaws\.com$/.test(n))return{actor:"controller",who:n??"an AWS service"};let o=(t.arn??"").split("/").at(-1)??"";if(t.type==="IAMUser")return{actor:"human",who:t.userName??o};if(t.type==="Root")return{actor:"human",who:"the root user"};if(t.type==="IdentityCenterUser"||t.type==="FederatedUser")return{actor:"human",who:t.userName??o};if(t.type==="AssumedRole"){let s=t.sessionContext?.sessionIssuer?.userName??"";if(/^AWSReservedSSO_/.test(s)||o.includes("@"))return{actor:"human",who:o||s}}}function Lce(e){let n=e.filter(o=>o.readOnly!==!0&&o.readOnly!=="true"&&!Dce.test(o.userAgent??"")).sort((o,s)=>String(s.eventTime??"").localeCompare(String(o.eventTime??"")))[0],r=n&&Mce(n);return n&&r?{status:"found",...r,event:n.eventName??"an event",at:n.eventTime??""}:{status:"silent"}}function bm(e={}){let t=e.run??(n=>{let r=Oce("aws",n,{encoding:"utf-8",maxBuffer:67108864});return{status:r.status,stdout:r.stdout??"",stderr:r.stderr??"",error:r.error}});return{async lookup(n){if(!n.type.startsWith("aws_"))return{status:"unavailable",reason:`no audit log reader for ${n.type}`};let r=new Date((e.now?.()??new Date).getTime()-(e.days??14)*864e5).toISOString(),o=t(["cloudtrail","lookup-events","--output","json","--max-results","50","--start-time",r,"--lookup-attributes",`AttributeKey=ResourceName,AttributeValue=${n.ref}`,...e.region?["--region",e.region]:[]]);if(o.error?.code==="ENOENT")return{status:"unavailable",reason:"the aws CLI is not installed"};if(o.error||o.status!==0)return{status:"unavailable",reason:(o.error?.message??(o.stderr.trim()||`aws exited ${o.status}`)).split(`
472
472
  `)[0]};let s;try{s=JSON.parse(o.stdout)}catch{return{status:"unavailable",reason:"aws printed no JSON"}}let i=[];for(let a of s.Events??[])try{i.push({eventName:a.EventName,eventTime:a.EventTime,...JSON.parse(a.CloudTrailEvent??"{}")})}catch{}return Lce(i)}}}function _U(e,t){return t?"(sensitive)":e==null?null:typeof e=="number"||typeof e=="boolean"?e:typeof e=="string"?`(a string of ${e.length} characters)`:Array.isArray(e)?`(a list of ${e.length})`:wm(e)?`(a map with keys ${Object.keys(e).slice(0,12).join(", ")})`:"(a value)"}var vm="attributions.json";async function ep(e,t,n={}){let r=[],o=[],s=new Map,i;for(let a of t)for(let c of a.attributes){let u={root:e,address:a.address,path:c.path},l=c.sensitive?void 0:Nce(a.type,c.path,c);if(l){r.push({...u,actor:l.actor,source:"table",detail:l.why});continue}if(n.audit&&a.ref&&i===void 0){let d=`${a.type}\0${a.ref}`,p=s.get(d);if(p||(p=await n.audit.lookup({type:a.type,address:a.address,ref:a.ref}),s.set(d,p)),p.status==="found"){r.push({...u,actor:p.actor,source:"audit",detail:`${p.event} by ${p.who}${p.at?` at ${p.at}`:""}`});continue}p.status==="unavailable"&&(i=p.reason,o.push(`audit log unavailable: ${p.reason}`))}if(n.decide){let d={resource_type:a.type,attribute:c.path,value_in_code:_U(c.before,c.sensitive),live_value:_U(c.live,c.sensitive)},p=await Xd(n.decide,d,{actor:ZT},n.options),f=p.decisions.actor;if(Qd(f)){r.push({...u,actor:f.answer,source:"model",detail:Sc(f,p),probability:f.probability});continue}o.push(`${a.address} ${c.path}: ${Sc(f,p)}`)}r.push({...u,source:"unattributed",detail:"neither the known-writes table nor the audit log answered"+(n.decide?", and the decision was not used":"")})}return{attributions:r,notes:o}}var Gce={table:"known write",audit:"audit log",model:"model",unattributed:"unattributed"};function YT(e){let t=new Map,n=[];for(let r of e){let o=`\`${r.address}\` \`${r.path}\``;if(r.source==="unattributed"){n.push(`- unattributed: ${o}: ${r.detail}; the default response runs`);continue}let s=`${Gce[r.source]}: ${r.detail}`;if(r.actor==="human"){n.push(`- human edit: ${o} (${s}); the pull request codifies it for the owner to accept or revert`);continue}let i=t.get(r.address)??new Set;i.add(r.path),t.set(r.address,i),r.actor==="controller"?n.push(`- controller write: ${o} (${s}); add \`lifecycle { ignore_changes = [${r.path}] }\` to the resource, and leave it out of the code`):n.push(`- provider default change: ${o} (${s}); pin the version that kept the old value, or set \`${r.path}\` to the value you want`)}return{leave:t,lines:n}}function XT(e,t){return e.map(n=>({...n,attributes:n.attributes.filter(r=>!t.get(n.address)?.has(r.path))})).filter((n,r)=>n.attributes.length>0||e[r].attributes.length===0)}import{existsSync as RU,readdirSync as xU,readFileSync as Fce}from"node:fs";import{join as Uce,posix as zce}from"node:path";var EU=(e,t)=>JSON.stringify(e??null)===JSON.stringify(t??null);function nl(e){return(e?.resource_drift??[]).filter(n=>n.mode!=="data").map(n=>{let r=n.change.before??{},o=n.change.after??{},s=n.change.actions.includes("delete")?"delete":"update",i=u=>{let l=n.change.before_sensitive?.[u]??n.change.after_sensitive?.[u];return l!==void 0&&l!==!1&&!(typeof l=="object"&&l!==null&&Object.keys(l).length===0)},a=s==="delete"?[]:[...new Set([...Object.keys(r),...Object.keys(o)])].sort().filter(u=>!EU(r[u],o[u])).map(u=>({path:u,before:r[u],live:o[u],...i(u)?{sensitive:!0}:{}})),c=["name","id","function_name","bucket"].map(u=>o[u]??r[u]).find(u=>typeof u=="string"&&u!=="");return{address:n.address,action:s,attributes:a,type:n.type,name:n.name,...c?{ref:c}:{},...n.module_address?{module:n.module_address}:{},...n.index!==void 0?{index:n.index}:{}}})}function Tv(e){let t=e.trim().replace(/\s*(#|\/\/).*$/,"");if(/^-?\d+(\.\d+)?$/.test(t))return{value:Number(t)};if(t==="true"||t==="false")return{value:t==="true"};if(/^"(?:[^"\\$%]|\\.|\$(?!\{)|%(?!\{))*"$/.test(t))try{return{value:JSON.parse(t)}}catch{return}}function QT(e){if(typeof e=="number"||typeof e=="boolean")return String(e);if(typeof e=="string")return JSON.stringify(e).replace(/\$\{/g,()=>"$${").replace(/%\{/g,()=>"%%{")}function Bce(e){return RU(e)?xU(e).filter(t=>t.endsWith(".tf")||t.endsWith(".tofu")).sort():[]}function SU(e,t,n){let r=new RegExp(`^\\s*resource\\s+"${t}"\\s+"${n}"\\s*\\{`),o=e.findIndex(i=>r.test(i));if(o<0)return;let s=0;for(let i=o;i<e.length;i++){for(let a of e[i].replace(/"(?:[^"\\]|\\.)*"/g,'""').replace(/#.*$/,""))(a==="{"||a==="["||a==="(")&&s++,(a==="}"||a==="]"||a===")")&&s--;if(s===0)return[o,i]}}function Av(e,t,n,r=new Map){let o=[],s=[],i=a=>r.get(a)??Fce(Uce(t,a),"utf-8");for(let a of n){let c=(g,m)=>s.push({root:e,address:a.address,...m?{path:m}:{},reason:g});if(a.action==="delete"){c("deleted outside Terraform; the next apply makes it again, or remove it from the code if that was the intent");continue}if(a.module){c(`reached through ${a.module}; the value is set in a module`);continue}if(a.index!==void 0){c("an instance of count or for_each; one literal sets every instance");continue}let u=Bce(t).find(g=>SU(i(g).split(`
473
473
  `),a.type,a.name));if(!u){c("its resource block is not in the root's files");continue}let l=i(u).split(`
474
474
  `),[d,p]=SU(l,a.type,a.name);for(let g of a.attributes){let m=0,h=-1;for(let k=d+1;k<p&&h<0;k++){m===0&&new RegExp(`^\\s*${g.path}\\s*=`).test(l[k])&&(h=k);for(let v of l[k].replace(/"(?:[^"\\]|\\.)*"/g,'""').replace(/#.*$/,""))(v==="{"||v==="["||v==="(")&&m++,(v==="}"||v==="]"||v===")")&&m--}if(h<0){c("not set in the root's block; it comes from a default or the provider",g.path);continue}let y=/^(\s*\S+\s*=\s*)(.*?)(\s*(?:#.*|\/\/.*)?)$/.exec(l[h]),w=Tv(y[2]);if(!w){c(`set from an expression (${y[2].trim()})`,g.path);continue}if(!EU(w.value,g.before)){c("the root's value is not the one last applied, so the code moved too",g.path);continue}let b=QT(g.live);if(b===void 0){c("its live value is a list, a map or null",g.path);continue}l[h]=`${y[1]}${b}${y[3]}`,o.push({root:e,address:a.address,path:g.path,file:zce.join(e,u),from:y[2].trim(),to:b})}let f=l.join(`
@@ -498,8 +498,8 @@ ${R.length?`
498
498
  ${R.join(`
499
499
  `)}
500
500
  `:""}`),b=o(["rev-parse","HEAD"]).trim()}catch($){return{opened:!1,fail:!0,reason:`could not commit the drift agent's change (${xc($.message)})`}}let k=Buffer.from(`x-access-token:${a}`).toString("base64");try{o(["-c",`http.extraHeader=Authorization: Basic ${k}`,"push","-q","origin",`HEAD:refs/heads/${y}`])}catch($){let S=xc(($.stderr||$.message).split(a).join("***"));return await u(`could not push the drift agent's change to \`${y}\` (${S}), so it opened no pull request.`),{opened:!1,fail:!0,reason:`could not push to ${y} (${S})`}}let v;try{let $=await dc(r,c),S=[`The drift agent changed the code to describe what is live, for drift issue #${s}, found at \`${i.slice(0,12)}\`.`,"",`It changes ${m.map(wA).join(", ")}. A person reviews it: merging keeps the change made outside the code, and the merge's apply goes through the gate like any other. Close it to undo the drift by applying the code as it is instead.`,"","Nothing was applied, approved or merged."].join(`
501
- `);v=(await pc(r,c,{head:y,base:$,title:w,body:S})).url}catch($){if(!($ instanceof ei)&&!($ instanceof Zr)&&!($ instanceof TypeError))throw $;return await u(`pushed the drift agent's change to \`${y}\`, and could not open its pull request (${xc($.message)}).`),{opened:!1,fail:!0,reason:`could not open the pull request (${$.message})`,commit:b,paths:m}}return await u(`the drift agent opened ${v}, changing ${m.map(wA).join(", ")}. It plans like any other pull request; nothing was applied, approved or merged.`),{opened:!0,reason:`opened ${v} from ${y}`,pullRequest:v,commit:b,paths:m}}import{spawnSync as Wue}from"node:child_process";import{randomBytes as Uue}from"node:crypto";function Lv(e){let t={};for(let n of(e??"").split(",")){let r=n.indexOf("=");if(r<=0)continue;let o=n.slice(0,r).trim(),s=n.slice(r+1).trim();try{s=decodeURIComponent(s)}catch{}o&&(t[o]=s)}return t}var zue=e=>Object.entries(e).map(([t,n])=>`${t}=${encodeURIComponent(n)}`).join(",");function ZU(e,t,n){let r=t.toUpperCase(),o=e[`OTEL_${r}_EXPORTER`];if(o!==void 0&&o.trim()!==""&&!o.split(",").map(u=>u.trim()).includes("otlp"))return;let s=e[`OTEL_EXPORTER_OTLP_${r}_ENDPOINT`]?.trim(),i=e.OTEL_EXPORTER_OTLP_ENDPOINT?.trim(),a=s||(i?`${i.replace(/\/+$/,"")}/v1/${t}`:void 0);if(!a)return;if((e[`OTEL_EXPORTER_OTLP_${r}_PROTOCOL`]??e.OTEL_EXPORTER_OTLP_PROTOCOL)==="grpc"){n.push(`${t}: terragucci sends OTLP over HTTP and the protocol is grpc; point it at the collector's HTTP port`);return}if(!/^https?:\/\//.test(a)){n.push(`${t}: ${a} is not an http or https URL`);return}return{url:a,headers:{...Lv(e.OTEL_EXPORTER_OTLP_HEADERS),...Lv(e[`OTEL_EXPORTER_OTLP_${r}_HEADERS`])}}}function ol(e){if(e.OTEL_SDK_DISABLED?.trim().toLowerCase()==="true")return;let t=[],n=ZU(e,"traces",t),r=ZU(e,"metrics",t);if(!n&&!r&&t.length===0)return;let o={...Lv(e.OTEL_RESOURCE_ATTRIBUTES)};return o["service.name"]=e.OTEL_SERVICE_NAME?.trim()||o["service.name"]||"terragucci",{...n?{traces:n}:{},...r?{metrics:r}:{},resource:o,skipped:t}}var eo=()=>BigInt(Math.round((performance.timeOrigin+performance.now())*1e3))*1000n,YU=e=>Uue(e).toString("hex");function Bue(e){let t=/^[\da-f]{2}-([\da-f]{32})-([\da-f]{16})-[\da-f]{2}$/.exec((e??"").trim().toLowerCase());if(!(!t||/^0+$/.test(t[1])||/^0+$/.test(t[2])))return{traceId:t[1],spanId:t[2]}}var Gv=class{traceId;parent;spans=[];constructor(t){let n=Bue(t);this.traceId=n?.traceId??YU(16),this.parent=n?.spanId}start(t,n,r={},o=1){let s={traceId:this.traceId,spanId:YU(8),name:t,kind:o,start:eo(),attributes:{...r}},i=n?.spanId??this.parent;return i&&(s.parentSpanId=i),this.spans.push(s),s}end(t,n={},r){t.end=eo(),Object.assign(t.attributes,n),r&&(t.error=r)}traceparent(t){return`00-${this.traceId}-${t.spanId}-01`}};function Fv(e){return Object.entries(e).filter(t=>t[1]!==void 0).map(([t,n])=>({key:t,value:typeof n=="boolean"?{boolValue:n}:typeof n=="number"?Number.isInteger(n)?{intValue:String(n)}:{doubleValue:n}:{stringValue:n}}))}var XU=e=>({name:"terragucci",version:e});function QU(e,t,n){return{resourceSpans:[{resource:{attributes:Fv(t)},scopeSpans:[{scope:XU(n),spans:e.map(r=>({traceId:r.traceId,spanId:r.spanId,...r.parentSpanId?{parentSpanId:r.parentSpanId}:{},name:r.name,kind:r.kind,startTimeUnixNano:String(r.start),endTimeUnixNano:String(r.end??r.start),attributes:Fv(r.attributes),status:r.error?{code:2,message:r.error}:{code:1}}))}]}]}}function Uv(e,t,n,r=eo()){let o=new Map;for(let s of e)o.set(s.name,[...o.get(s.name)??[],s]);return{resourceMetrics:[{resource:{attributes:Fv(t)},scopeMetrics:[{scope:XU(n),metrics:[...o.values()].map(s=>({name:s[0].name,unit:s[0].unit,description:s[0].description,gauge:{dataPoints:s.map(i=>({attributes:Fv(i.attributes),timeUnixNano:String(r),asDouble:i.value}))}}))}]}]}}async function Am(e,t,n=fetch){try{let r=await n(e.url,{method:"POST",headers:{...e.headers,"content-type":"application/json"},body:JSON.stringify(t),signal:AbortSignal.timeout(1e4)});return r.ok?void 0:`${e.url} answered ${r.status}: ${(await r.text()).slice(0,200)}`}catch(r){return`${e.url}: ${r.message}`}}function zv(e,t,n,r){let o={...Lv(e.OTEL_RESOURCE_ATTRIBUTES),"terragucci.trace_id":t.traceId,"terragucci.span_id":n.spanId,"terragucci.root":r};return{...e,TRACEPARENT:t.traceparent(n),OTEL_TRACES_EXPORTER:e.OTEL_TRACES_EXPORTER||"otlp",OTEL_RESOURCE_ATTRIBUTES:zue(o)}}var bt={stageDuration:"terragucci_stage_duration_seconds",lastRun:"terragucci_last_run_seconds",rootsPlanned:"terragucci_roots_planned",rootsChanged:"terragucci_roots_changed",planGroups:"terragucci_plan_groups",planChanges:"terragucci_plan_changes",rootPlan:"terragucci_root_plan_seconds",rootInit:"terragucci_root_init_seconds",rootApply:"terragucci_root_apply_seconds",binaryVersion:"terragucci_binary_version",tips:"terragucci_tips",modulePin:"terragucci_module_pin",providerInit:"terragucci_provider_init_seconds",lockWait:"terragucci_lock_wait_seconds",resource:"terragucci_resource_seconds",driftRoots:"terragucci_drift_roots",driftSince:"terragucci_drift_since_seconds",driftClear:"terragucci_drift_clear_seconds",waveWaitingSince:"terragucci_wave_waiting_since_seconds",waveSettled:"terragucci_wave_settled_seconds",waveRoots:"terragucci_wave_roots",waveHeld:"terragucci_wave_held_seconds",waveChanges:"terragucci_wave_changes",doraDeployments:"terragucci_dora_deployments_per_week",doraLeadTime:"terragucci_dora_lead_time_seconds",doraFailureRate:"terragucci_dora_change_failure_ratio",doraRestore:"terragucci_dora_restore_seconds"};function Bv(e,t){return e===1?"failed":e===3?"waiting":e===4?"refused":t.nothing?"nothing":"applied"}function ez(e,t,n,r,o,s={}){let i=Bv(t,n),a={wave:String(e)},c=[o(bt.waveRoots,"{root}","Roots in the wave",n.roots?.length??0,a)];if(i==="waiting"||i==="refused"){let u=n.waitingSince?Date.parse(n.waitingSince)/1e3:r;c.push(o(bt.waveWaitingSince,"s","When the wave started waiting for an approval, in Unix seconds",u,a))}else c.push(o(bt.waveSettled,"s","When the wave last stopped waiting, in Unix seconds",r,a));if(n.held&&i!=="waiting"&&i!=="refused"){let u=(Date.parse(n.held.approvedAt)-Date.parse(n.held.since))/1e3;Number.isFinite(u)&&u>=0&&c.push(o(bt.waveHeld,"s","How long the wave was held at its gate before its approval",u,a))}if(i==="applied")for(let[u,l]of Object.entries(s))l>0&&u!=="no-op"&&u!=="read"&&c.push(o(bt.waveChanges,"{change}","Changes the wave applied, by action",l,{...a,action:u}));return c}function tz(e){let t=[];for(let n of e){let r=n.plan?.configuration?.root_module?.module_calls??{};for(let o of Object.values(r)){let s=o.source??"";if(!s||s.startsWith(".")||s.startsWith("/"))continue;let[i,a=""]=s.split("?"),c=new URLSearchParams(a).get("ref")??new URLSearchParams(a).get("tag"),u=c?yc(c):o.version_constraint;u&&t.push({root:n.path,module:i.replace(/^git::/,"").replace(/\.git(\/\/|$)/,"$1"),version:u})}}return t}var Om=(e,t)=>Number(t-e)/1e9,Wv=e=>Math.round(e*1e3)/1e3;function Hue(e,t,n){if(!t)return e;let r={...n&&n!=="unknown"?{commit:n}:{},...t.run?{run:t.run}:{}};return e.map(o=>({...o,attributes:{...o.attributes,...r}}))}function que(e,t){let n=Wue(e,["version","-json"],{encoding:"utf-8",env:t});try{let r=JSON.parse(n.stdout).terraform_version;return typeof r=="string"?r:void 0}catch{return}}var sl=class{constructor(t,n,r,o=eo()){this.telemetry=t;this.stage=n;this.start=o;let s=r.TG_PR?.trim();if(s&&(this.pullRequest=s),this.pipeline={run:(r.GITHUB_RUN_ID||r.CI_PIPELINE_ID)?.trim()||void 0,job:(r.GITHUB_JOB||r.CI_JOB_NAME)?.trim()||void 0},r.TERRAGUCCI_METRICS_PER_RUN?.trim().toLowerCase()==="true"){let i=(r.GITHUB_RUN_ID||r.CI_PIPELINE_ID)?.trim();this.perRun=i?{run:i}:{}}t?.traces&&(this.trace=new Gv(r.TRACEPARENT),this.stageSpan=this.trace.start(`terragucci ${n}`,void 0,{"terragucci.stage":n}),this.stageSpan.start=o)}telemetry;stage;trace;stageSpan;current;phased=!1;pipeline;start;roots=[];receiver;units=new Map;unitSpans=new Map;pins=[];drift;reportUrl;wave;pullRequest;perRun;async collectSpans(t){let n=new Nb(this.trace?this.telemetry?.traces:void 0),r=await n.listen();r?t(`timings: the binary's spans are not collected (${r})`):this.receiver=n}phase(t,n={},r){if(!this.trace||!this.stageSpan)return;let o=this.trace.start(t,this.stageSpan,{"terragucci.phase":t,...n});return r!==void 0&&(o.start=r),this.phased=!0,this.current=o,o}endPhase(t,n={},r){!t||!this.trace||(this.trace.end(t,n,r),this.current===t&&(this.current=void 0))}root(t){let n={path:t,start:eo(),commands:[],idle:0n,spans:[]};return this.trace&&(n.span=this.trace.start(`root ${t}`,this.current??this.stageSpan,{"terragucci.root":t}),n.spans.push(n.span)),this.roots.push(n),n}endRoot(t){t.end=eo(),t.span&&(t.span.end=t.end)}reopen(t){t.end!==void 0&&(t.idle+=eo()-t.end,t.end=void 0,this.trace&&this.current&&t.span&&t.span.parentSpanId!==this.current.spanId&&(t.span=this.trace.start(`root ${t.path}`,this.current,{"terragucci.root":t.path}),t.spans.push(t.span)))}unitTimed(t,n,r){if(!Number.isFinite(n)||n<0)return;this.units.set(t,n);let o=this.unitSpans.get(t);o&&r&&(o.start=BigInt(Math.round(r.start*1e3))*1000n,o.end=BigInt(Math.round(r.end*1e3))*1000n)}unitEnv(t,n,r){let o=this.unitSpans.get(t);this.trace&&!o&&(o=this.trace.start(`root ${t}`,this.current??this.stageSpan,{"terragucci.root":t}),this.unitSpans.set(t,o));let s=o?zv(r,this.trace,o,t):r,i={};for(let[a,c]of Object.entries(s))c!==void 0&&r[a]!==c&&(i[a]=c);return{...i,...this.receiver?.exporter(t,n)??{}}}command(t,n,r,o,s){if(!this.trace||!t.span)return s(o);let i=this.trace.start(`${n} ${r[0]}`,t.span,{"process.executable.name":n,"process.command_args":r.join(" ")},3),a=s(zv(o,this.trace,i,t.path));return this.trace.end(i,{"process.exit.code":a.status??-1},a.status===0?void 0:a.error?.message??`exit ${a.status}`),a}async commandAsync(t,n,r,o,s){let i=eo(),a=this.trace&&t.span?this.trace.start(`${n} ${r[0]}`,t.span,{"process.executable.name":n,"process.command_args":r.join(" ")},3):void 0,c=a?zv(o,this.trace,a,t.path):o;this.receiver&&(c=this.receiver.env(c,t.path,r[0]));let u=await s(c);return t.commands.push({command:r[0],seconds:Om(i,eo())}),a&&this.trace.end(a,{"process.exit.code":u.status??-1},u.status===0?void 0:u.error?.message??`exit ${u.status}`),u}addTimings(t,n=["plan"]){let r=t.run.binary.split("/").pop()||t.run.binary;for(let s of t.roots){let i=this.roots.find(l=>l.path===s.path);if(!i||i.end===void 0){let l=this.units.get(s.path);l!==void 0&&(s.timings=jE(this.receiver?.spansOf(s.path,"plan")??[],{binary:r,seconds:l,planSeconds:l,source:"terragrunt"}));continue}let a=l=>i.commands.filter(d=>d.command===l).reduce((d,p)=>(d??0)+p.seconds,void 0),c=a("plan"),u=n.includes("apply")?a("apply"):void 0;s.timings=jE(n.flatMap(l=>this.receiver?.spansOf(i.path,l)??[]),{binary:r,seconds:Om(i.start,i.end)-Number(i.idle)/1e9,...c!==void 0?{planSeconds:c}:{},...u!==void 0?{applySeconds:u}:{}})}let o=t.roots.length>0&&t.roots.every(s=>s.timings===void 0);t.timings=KL(t.roots,o?"Terragrunt ran the binary for these units and its run report gave no times, so the report has no per-unit timings":void 0)}gauges(t,n,r){let o=t.run.project,s=this.stage,i=t.roots.some(l=>l.status==="failed")?"failure":"success",a=s==="tf-plan"&&this.pullRequest?{pull_request:this.pullRequest}:{},c=(l,d,p,f,g)=>({name:l,unit:d,description:p,value:f,attributes:{project:o,stage:s,...a,...g}}),u=[c("terragucci_stage_duration_seconds","s","How long the stage ran",Om(this.start,r),{result:i}),c("terragucci_roots_planned","{root}","Roots the stage planned, failed ones included",t.roots.length,{}),c("terragucci_plan_groups","{group}","Groups the plans fold into",t.groups.length,{})];for(let l of this.roots)l.end!==void 0&&u.push(c(bt.rootPlan,"s","How long one root took to plan",Om(l.start,l.end),{root:l.path}));for(let[l,d]of Object.entries(t.totals))u.push(c("terragucci_plan_changes","{change}","Proposed changes by action",d,{action:l}));return n&&u.push(c("terragucci_binary_version","","The binary and version a stage ran",1,{binary:t.run.binary,version:n})),[...u,...this.dashboardGauges(t,r,c)]}applyGauges(t,n){let r=t.run.project,o=this.stage,s=(c,u,l,d,p)=>({name:c,unit:u,description:l,value:d,attributes:{project:r,stage:o,...p}}),i=this.wave?{wave:String(this.wave.number)}:{},a=[];this.wave&&a.push(s(bt.stageDuration,"s","How long the stage ran",Wv(Om(this.start,n)),{...i,result:Bv(this.wave.code??1,this.wave.facts)}));for(let c of t.roots){let u=this.roots.find(g=>g.path===c.path),l=g=>u?.commands.filter(m=>m.command===g).reduce((m,h)=>(m??0)+h.seconds,void 0),d=l("init");d!==void 0&&a.push(s(bt.rootInit,"s","How long one root's init took",Wv(d),{...i,root:c.path}));let p=l("plan")??c.timings?.plan_seconds;p!==void 0&&a.push(s(bt.rootPlan,"s","How long one root took to plan",Wv(p),{...i,root:c.path}));let f=l("apply")??c.timings?.apply_seconds;f!==void 0&&a.push(s(bt.rootApply,"s","How long one root took to apply",Wv(f),{...i,root:c.path}))}return a.push(...this.initAndLockGauges(t,s)),this.wave&&a.push(...ez(this.wave.number,this.wave.code??1,this.wave.facts,Number(n/1000000n)/1e3,s,t.totals)),a}initAndLockGauges(t,n){let r=[];for(let o of t.roots){for(let i of o.timings?.provider_init??[])r.push(n(bt.providerInit,"s","Time spent starting a provider in a root",i.ms/1e3,{root:o.path,provider:i.provider}));let s=(o.timings?.lock_waits??[]).reduce((i,a)=>i+a.ms,0);o.timings?.lock_waits?.length&&r.push(n(bt.lockWait,"s","Time spent waiting for a state lock in a root",s/1e3,{root:o.path}))}return r}dashboardGauges(t,n,r){let o=Number(n/1000000n)/1e3,s=[r(bt.lastRun,"s","When the stage last ran, in Unix seconds",o,{}),r(bt.rootsChanged,"{root}","Roots whose plan changes something",t.roots.filter(i=>i.status==="planned"&&i.changes.length>0).length,{})];if(t.tips){let i=new Map;for(let a of t.tips)i.set(a.rule,(i.get(a.rule)??0)+1);for(let[a,c]of i)s.push(r(bt.tips,"{tip}","Tips the run gave, by rule",c,{rule:a}))}for(let i of this.pins)s.push(r(bt.modulePin,"","A module call's pin in a root",1,{root:i.root,module:i.module,version:i.version}));s.push(...this.initAndLockGauges(t,r));for(let i of t.timings?.resources??[])s.push(r(bt.resource,"s","How long one of the run's slowest resources took",i.ms/1e3,{root:i.root,address:i.address}));if(t.run.stage==="tf-drift"){let i=np(t);s.push(r(bt.driftRoots,"{root}","Roots the drift run found drifted",i.roots,{})),i.roots>0?s.push(r(bt.driftSince,"s","When the open drift was first found, in Unix seconds",Date.parse(this.drift?.since??t.run.finished)/1e3,{})):i.failed===0&&s.push(r(bt.driftClear,"s","When a drift run last found no drift, in Unix seconds",o,{}))}return s}async finish(t,n,r,o){if(this.receiver){for(let p of this.receiver.unreadable)r(`timings: a batch of spans could not be read: ${p}`);let d=await this.receiver.close();d.length&&r(`telemetry: ${d.length} of the binary's span batches were not forwarded: ${d[0]}`)}let s=this.telemetry;if(!s)return;for(let d of s.skipped)r(`telemetry: ${d}`);let i=eo(),a=t.run,c={...s.resource,"service.version":a.terragucci,"terragucci.project":a.project},u={...c,"vcs.ref.head.revision":a.commit,...a.job_url?{"cicd.pipeline.run.url.full":a.job_url}:{},...this.pipeline.run?{"cicd.pipeline.run.id":this.pipeline.run}:{},...this.pipeline.job?{"cicd.pipeline.task.name":this.pipeline.job}:{}},l=[];if(s.traces&&this.trace&&this.stageSpan){let d=t.roots.filter(m=>m.status==="failed").length,p=this.wave?Bv(this.wave.code??1,this.wave.facts):d?"failure":"success";this.trace.end(this.stageSpan,{"terragucci.project":a.project,"vcs.ref.head.revision":a.commit,"terragucci.binary":a.binary,"terragucci.runtime":a.runtime,"terragucci.change_set":t.change_set,"terragucci.roots":t.roots.length,"terragucci.roots_failed":d,"terragucci.result":p,...this.wave?{"terragucci.wave":this.wave.number}:{},...this.reportUrl?{"terragucci.report.url":this.reportUrl}:{},...a.job_url?{"cicd.pipeline.run.url.full":a.job_url}:{},...Object.fromEntries(Object.entries(t.totals).map(([m,h])=>[`terragucci.plan.${m}`,h]))},d?`${d} root(s) failed`:p==="failed"?`wave ${this.wave?.number} failed`:void 0);for(let m of this.unitSpans.values())m.end??=i;let f=[...this.roots.flatMap(m=>m.spans.map(h=>({path:m.path,span:h}))),...[...this.unitSpans].map(([m,h])=>({path:m,span:h}))];for(let m of this.phased?[]:t.waves){let h=f.filter(w=>m.roots.includes(w.path)&&w.span.end!==void 0);if(h.length===0)continue;let y=this.trace.start(`wave ${m.number}`,this.stageSpan,{"terragucci.wave":m.number,"terragucci.set_digest":m.set_digest??void 0,"terragucci.approval":m.approval});y.start=h.reduce((w,b)=>b.span.start<w?b.span.start:w,h[0].span.start),y.end=h.reduce((w,b)=>b.span.end>w?b.span.end:w,h[0].span.end);for(let w of h)w.span.parentSpanId=y.spanId}for(let m of f){let h=t.roots.find(y=>y.path===m.path);h&&(Object.assign(m.span.attributes,{"terragucci.status":h.status,"terragucci.plan_digest":h.plan_digest??void 0,...Object.fromEntries(Object.entries(h.counts).map(([y,w])=>[`terragucci.plan.${y}`,w]))}),h.error&&(m.span.error=h.error.split(`
502
- `)[0]))}let g=await Am(s.traces,QU(this.trace.spans,u,a.terragucci??"0.0.0"),o);g?r(`telemetry: the trace was not sent: ${g}`):l.push(`trace ${this.trace.traceId} (${this.trace.spans.length} spans)`)}if(s.metrics){let d=Hue(this.stage==="tf-apply"?this.applyGauges(t,i):this.gauges(t,que(a.binary,n),i),this.perRun,a.commit),p=await Am(s.metrics,Uv(d,c,a.terragucci??"0.0.0",i),o);p?r(`telemetry: the metrics were not sent: ${p}`):l.push(`${d.length} metric points`)}l.length&&r(`telemetry: sent ${l.join(" and ")}`)}};import{createHash as Zle}from"node:crypto";import{existsSync as Fm,mkdirSync as Gz,readdirSync as Yle,readFileSync as ya,rmSync as Fz,statSync as Xle,writeFileSync as fi}from"node:fs";import{dirname as Qle,join as lt,relative as GA,resolve as ip}from"node:path";import{spawn as lle,spawnSync as Sz}from"node:child_process";import{createHash as Rz,randomUUID as dle}from"node:crypto";import{chmodSync as ple,existsSync as Tc,mkdirSync as Kv,mkdtempSync as xz,readdirSync as Ez,readFileSync as op,rmSync as Tz,writeFileSync as sp}from"node:fs";import{hostname as fle,tmpdir as gle}from"node:os";import{basename as mle,dirname as hle,join as Ct,resolve as yle}from"node:path";import{readFileSync as Jue,writeFileSync as Kue}from"node:fs";import{join as nz,resolve as rz}from"node:path";var Vue=e=>/(^|\/)choudoufu[^/]*$/.test(e);async function Zue(e,t,n,r){let o=await e(t,["init","-input=false","-no-color"],r.dir,r.init?.init??r.env);if(o.code!==0)throw new _(`init in ${n} failed: ${to(o.out)}`)}function Yue(e,t){let n=Fo(nz(e,t)).estate;if(!n)throw new _(`${t} has a live block that names no estate`);return n}var Xue=e=>{let t=e.lastIndexOf(": ");return{address:e.slice(0,t),actions:e.slice(t+2)}},Que=(e,t,n)=>t.map(Xue).filter(r=>r.actions===n&&(r.address===e||r.address.startsWith(`${e}[`))).map(r=>r.address).sort();async function oz(e,t,n,r,o,s){let i=await e(t,["live-mv","-no-color","-json",...s?["-dry-run"]:[],`-from-estate=${r}`,o,o],n.dir,n.env),a;try{a=JSON.parse(i.stdout)}catch{a=void 0}let c=a?.refusal?[a.refusal.summary,a.refusal.detail].filter(Boolean).join(": "):void 0;if(i.code!==0||!a||c)throw new _(`live-mv ${s?"-dry-run ":""}refuses ${o}: ${c??to(i.out)}`);return a}async function ele(e,t,n,r,o){let s=await oz(e,t,n,r.from_estate,r.address,!1);if(s.written!==!0)throw new _(`live-mv wrote nothing on ${r.address}`);if(s.resource?.live_id!==r.live_id)throw new _(`live-mv rewrote ${s.resource?.live_id??"an unnamed resource"} for ${r.address}, not ${r.live_id}`);o(`${r.address}: retagged from estate ${r.from_estate} to estate ${r.to_estate}`)}function tle(e){let t=[],n;for(let r of e.split(`
501
+ `);v=(await pc(r,c,{head:y,base:$,title:w,body:S})).url}catch($){if(!($ instanceof ei)&&!($ instanceof Zr)&&!($ instanceof TypeError))throw $;return await u(`pushed the drift agent's change to \`${y}\`, and could not open its pull request (${xc($.message)}).`),{opened:!1,fail:!0,reason:`could not open the pull request (${$.message})`,commit:b,paths:m}}return await u(`the drift agent opened ${v}, changing ${m.map(wA).join(", ")}. It plans like any other pull request; nothing was applied, approved or merged.`),{opened:!0,reason:`opened ${v} from ${y}`,pullRequest:v,commit:b,paths:m}}import{spawnSync as Wue}from"node:child_process";import{randomBytes as Uue}from"node:crypto";function Lv(e){let t={};for(let n of(e??"").split(",")){let r=n.indexOf("=");if(r<=0)continue;let o=n.slice(0,r).trim(),s=n.slice(r+1).trim();try{s=decodeURIComponent(s)}catch{}o&&(t[o]=s)}return t}var zue=e=>Object.entries(e).map(([t,n])=>`${t}=${encodeURIComponent(n)}`).join(",");function ZU(e,t,n){let r=t.toUpperCase(),o=e[`OTEL_${r}_EXPORTER`];if(o!==void 0&&o.trim()!==""&&!o.split(",").map(u=>u.trim()).includes("otlp"))return;let s=e[`OTEL_EXPORTER_OTLP_${r}_ENDPOINT`]?.trim(),i=e.OTEL_EXPORTER_OTLP_ENDPOINT?.trim(),a=s||(i?`${i.replace(/\/+$/,"")}/v1/${t}`:void 0);if(!a)return;if((e[`OTEL_EXPORTER_OTLP_${r}_PROTOCOL`]??e.OTEL_EXPORTER_OTLP_PROTOCOL)==="grpc"){n.push(`${t}: terragucci sends OTLP over HTTP and the protocol is grpc; point it at the collector's HTTP port`);return}if(!/^https?:\/\//.test(a)){n.push(`${t}: ${a} is not an http or https URL`);return}return{url:a,headers:{...Lv(e.OTEL_EXPORTER_OTLP_HEADERS),...Lv(e[`OTEL_EXPORTER_OTLP_${r}_HEADERS`])}}}function ol(e){if(e.OTEL_SDK_DISABLED?.trim().toLowerCase()==="true")return;let t=[],n=ZU(e,"traces",t),r=ZU(e,"metrics",t);if(!n&&!r&&t.length===0)return;let o={...Lv(e.OTEL_RESOURCE_ATTRIBUTES)};return o["service.name"]=e.OTEL_SERVICE_NAME?.trim()||o["service.name"]||"terragucci",{...n?{traces:n}:{},...r?{metrics:r}:{},resource:o,skipped:t}}var eo=()=>BigInt(Math.round((performance.timeOrigin+performance.now())*1e3))*1000n,YU=e=>Uue(e).toString("hex");function Bue(e){let t=/^[\da-f]{2}-([\da-f]{32})-([\da-f]{16})-[\da-f]{2}$/.exec((e??"").trim().toLowerCase());if(!(!t||/^0+$/.test(t[1])||/^0+$/.test(t[2])))return{traceId:t[1],spanId:t[2]}}var Gv=class{traceId;parent;spans=[];constructor(t){let n=Bue(t);this.traceId=n?.traceId??YU(16),this.parent=n?.spanId}start(t,n,r={},o=1){let s={traceId:this.traceId,spanId:YU(8),name:t,kind:o,start:eo(),attributes:{...r}},i=n?.spanId??this.parent;return i&&(s.parentSpanId=i),this.spans.push(s),s}end(t,n={},r){t.end=eo(),Object.assign(t.attributes,n),r&&(t.error=r)}traceparent(t){return`00-${this.traceId}-${t.spanId}-01`}};function Fv(e){return Object.entries(e).filter(t=>t[1]!==void 0).map(([t,n])=>({key:t,value:typeof n=="boolean"?{boolValue:n}:typeof n=="number"?Number.isInteger(n)?{intValue:String(n)}:{doubleValue:n}:{stringValue:n}}))}var XU=e=>({name:"terragucci",version:e});function QU(e,t,n){return{resourceSpans:[{resource:{attributes:Fv(t)},scopeSpans:[{scope:XU(n),spans:e.map(r=>({traceId:r.traceId,spanId:r.spanId,...r.parentSpanId?{parentSpanId:r.parentSpanId}:{},name:r.name,kind:r.kind,startTimeUnixNano:String(r.start),endTimeUnixNano:String(r.end??r.start),attributes:Fv(r.attributes),status:r.error?{code:2,message:r.error}:{code:1}}))}]}]}}function Uv(e,t,n,r=eo()){let o=new Map;for(let s of e)o.set(s.name,[...o.get(s.name)??[],s]);return{resourceMetrics:[{resource:{attributes:Fv(t)},scopeMetrics:[{scope:XU(n),metrics:[...o.values()].map(s=>({name:s[0].name,unit:s[0].unit,description:s[0].description,gauge:{dataPoints:s.map(i=>({attributes:Fv(i.attributes),timeUnixNano:String(r),asDouble:i.value}))}}))}]}]}}async function Am(e,t,n=fetch){try{let r=await n(e.url,{method:"POST",headers:{...e.headers,"content-type":"application/json"},body:JSON.stringify(t),signal:AbortSignal.timeout(1e4)});return r.ok?void 0:`${e.url} answered ${r.status}: ${(await r.text()).slice(0,200)}`}catch(r){return`${e.url}: ${r.message}`}}function zv(e,t,n,r){let o={...Lv(e.OTEL_RESOURCE_ATTRIBUTES),"terragucci.trace_id":t.traceId,"terragucci.span_id":n.spanId,"terragucci.root":r};return{...e,TRACEPARENT:t.traceparent(n),OTEL_TRACES_EXPORTER:e.OTEL_TRACES_EXPORTER||"otlp",OTEL_RESOURCE_ATTRIBUTES:zue(o)}}var bt={stageDuration:"terragucci_stage_duration_seconds",lastRun:"terragucci_last_run_seconds",rootsPlanned:"terragucci_roots_planned",rootsChanged:"terragucci_roots_changed",planGroups:"terragucci_plan_groups",planChanges:"terragucci_plan_changes",rootPlan:"terragucci_root_plan_seconds",rootInit:"terragucci_root_init_seconds",rootApply:"terragucci_root_apply_seconds",binaryVersion:"terragucci_binary_version",tips:"terragucci_tips",modulePin:"terragucci_module_pin",providerInit:"terragucci_provider_init_seconds",lockWait:"terragucci_lock_wait_seconds",resource:"terragucci_resource_seconds",driftRoots:"terragucci_drift_roots",driftSince:"terragucci_drift_since_seconds",driftClear:"terragucci_drift_clear_seconds",waveWaitingSince:"terragucci_wave_waiting_since_seconds",waveSettled:"terragucci_wave_settled_seconds",waveRoots:"terragucci_wave_roots",waveHeld:"terragucci_wave_held_seconds",waveChanges:"terragucci_wave_changes",doraDeployments:"terragucci_dora_deployments_per_week",doraLeadTime:"terragucci_dora_lead_time_seconds",doraFailureRate:"terragucci_dora_change_failure_ratio",doraRestore:"terragucci_dora_restore_seconds"};function Bv(e,t){return e===1?"failed":e===3?"waiting":e===4?"refused":t.nothing?"nothing":"applied"}function ez(e,t,n,r,o,s={}){let i=Bv(t,n),a={wave:String(e)},c=[o(bt.waveRoots,"{root}","Roots in the wave",n.roots?.length??0,a)];if(i==="waiting"||i==="refused"){let u=n.waitingSince?Date.parse(n.waitingSince)/1e3:r;c.push(o(bt.waveWaitingSince,"s","When the wave started waiting for an approval, in Unix seconds",u,a))}else c.push(o(bt.waveSettled,"s","When the wave last stopped waiting, in Unix seconds",r,a));if(n.held&&i!=="waiting"&&i!=="refused"){let u=(Date.parse(n.held.approvedAt)-Date.parse(n.held.since))/1e3;Number.isFinite(u)&&u>=0&&c.push(o(bt.waveHeld,"s","How long the wave was held at its gate before its approval",u,a))}if(i==="applied")for(let[u,l]of Object.entries(s))l>0&&u!=="no-op"&&u!=="read"&&c.push(o(bt.waveChanges,"{change}","Changes the wave applied, by action",l,{...a,action:u}));return c}function tz(e){let t=[];for(let n of e){let r=n.plan?.configuration?.root_module?.module_calls??{};for(let o of Object.values(r)){let s=o.source??"";if(!s||s.startsWith(".")||s.startsWith("/"))continue;let[i,a=""]=s.split("?"),c=new URLSearchParams(a).get("ref")??new URLSearchParams(a).get("tag"),u=c?yc(c):o.version_constraint;u&&t.push({root:n.path,module:i.replace(/^git::/,"").replace(/\.git(\/\/|$)/,"$1"),version:u})}}return t}var Om=(e,t)=>Number(t-e)/1e9,Wv=e=>Math.round(e*1e3)/1e3;function Hue(e,t,n){if(!t)return e;let r={...n&&n!=="unknown"?{commit:n}:{},...t.run?{run:t.run}:{}};return e.map(o=>({...o,attributes:{...o.attributes,...r}}))}function que(e,t){let n=Wue(e,["version","-json"],{encoding:"utf-8",env:t});try{let r=JSON.parse(n.stdout).terraform_version;return typeof r=="string"?r:void 0}catch{return}}var sl=class{constructor(t,n,r,o=eo()){this.telemetry=t;this.stage=n;this.start=o;let s=r.TG_PR?.trim();if(s&&(this.pullRequest=s),this.pipeline={run:(r.GITHUB_RUN_ID||r.CI_PIPELINE_ID)?.trim()||void 0,job:(r.GITHUB_JOB||r.CI_JOB_NAME)?.trim()||void 0},r.TERRAGUCCI_METRICS_PER_RUN?.trim().toLowerCase()==="true"){let i=(r.GITHUB_RUN_ID||r.CI_PIPELINE_ID)?.trim();this.perRun=i?{run:i}:{}}t?.traces&&(this.trace=new Gv(r.TRACEPARENT),this.stageSpan=this.trace.start(`terragucci ${n}`,void 0,{"terragucci.stage":n}),this.stageSpan.start=o)}telemetry;stage;trace;stageSpan;current;phased=!1;phases=[];pipeline;start;roots=[];receiver;units=new Map;unitSpans=new Map;pins=[];drift;reportUrl;wave;pullRequest;perRun;async collectSpans(t){let n=new Nb(this.trace?this.telemetry?.traces:void 0),r=await n.listen();r?t(`timings: the binary's spans are not collected (${r})`):this.receiver=n}phase(t,n={},r){if(!this.trace||!this.stageSpan)return;let o=this.trace.start(t,this.stageSpan,{"terragucci.phase":t,...n});return r!==void 0&&(o.start=r),this.phased=!0,this.current=o,this.phases.push(o),o}holdGate(t,n,r){if(!t)return;let o=Date.parse(n.since),s=Date.parse(n.approvedAt);if(!Number.isFinite(o)||!Number.isFinite(s)||s<o)return;let i=a=>BigInt(Math.round(a))*1000000n;this.spanAt("held",t,i(o),i(s),{"terragucci.wait":"approval","terragucci.gate.held_seconds":(s-o)/1e3}),this.spanAt("check",t,t.start,r),i(o)<t.start&&(t.start=i(o))}spanAt(t,n,r,o,s={}){if(!this.trace||!n)return;let i=this.trace.start(t,n,s);return i.start=r,i.end=o,i}endPhase(t,n={},r){!t||!this.trace||(this.trace.end(t,n,r),this.current===t&&(this.current=void 0))}root(t){let n={path:t,start:eo(),commands:[],idle:0n,spans:[]};return this.trace&&(n.span=this.trace.start(`root ${t}`,this.current??this.stageSpan,{"terragucci.root":t}),n.spans.push(n.span)),this.roots.push(n),n}endRoot(t){t.end=eo(),t.span&&(t.span.end=t.end)}reopen(t){t.end!==void 0&&(t.idle+=eo()-t.end,t.end=void 0,this.trace&&this.current&&t.span&&t.span.parentSpanId!==this.current.spanId&&(t.span=this.trace.start(`root ${t.path}`,this.current,{"terragucci.root":t.path}),t.spans.push(t.span)))}unitTimed(t,n,r){if(!Number.isFinite(n)||n<0)return;this.units.set(t,n);let o=this.unitSpans.get(t);o&&r&&(o.start=BigInt(Math.round(r.start*1e3))*1000n,o.end=BigInt(Math.round(r.end*1e3))*1000n)}unitEnv(t,n,r){let o=this.unitSpans.get(t);this.trace&&!o&&(o=this.trace.start(`root ${t}`,this.current??this.stageSpan,{"terragucci.root":t}),this.unitSpans.set(t,o));let s=o?zv(r,this.trace,o,t):r,i={};for(let[a,c]of Object.entries(s))c!==void 0&&r[a]!==c&&(i[a]=c);return{...i,...this.receiver?.exporter(t,n)??{}}}command(t,n,r,o,s){if(!this.trace||!t.span)return s(o);let i=this.trace.start(`${n} ${r[0]}`,t.span,{"process.executable.name":n,"process.command_args":r.join(" ")},3),a=s(zv(o,this.trace,i,t.path));return this.trace.end(i,{"process.exit.code":a.status??-1},a.status===0?void 0:a.error?.message??`exit ${a.status}`),a}async commandAsync(t,n,r,o,s){let i=eo(),a=this.trace&&t.span?this.trace.start(`${n} ${r[0]}`,t.span,{"process.executable.name":n,"process.command_args":r.join(" ")},3):void 0,c=a?zv(o,this.trace,a,t.path):o;this.receiver&&(c=this.receiver.env(c,t.path,r[0]));let u=await s(c);return t.commands.push({command:r[0],seconds:Om(i,eo())}),a&&this.trace.end(a,{"process.exit.code":u.status??-1},u.status===0?void 0:u.error?.message??`exit ${u.status}`),u}addTimings(t,n=["plan"]){let r=t.run.binary.split("/").pop()||t.run.binary;for(let s of t.roots){let i=this.roots.find(l=>l.path===s.path);if(!i||i.end===void 0){let l=this.units.get(s.path);l!==void 0&&(s.timings=jE(this.receiver?.spansOf(s.path,"plan")??[],{binary:r,seconds:l,planSeconds:l,source:"terragrunt"}));continue}let a=l=>i.commands.filter(d=>d.command===l).reduce((d,p)=>(d??0)+p.seconds,void 0),c=a("plan"),u=n.includes("apply")?a("apply"):void 0;s.timings=jE(n.flatMap(l=>this.receiver?.spansOf(i.path,l)??[]),{binary:r,seconds:Om(i.start,i.end)-Number(i.idle)/1e9,...c!==void 0?{planSeconds:c}:{},...u!==void 0?{applySeconds:u}:{}})}let o=t.roots.length>0&&t.roots.every(s=>s.timings===void 0);t.timings=KL(t.roots,o?"Terragrunt ran the binary for these units and its run report gave no times, so the report has no per-unit timings":void 0)}gauges(t,n,r){let o=t.run.project,s=this.stage,i=t.roots.some(l=>l.status==="failed")?"failure":"success",a=s==="tf-plan"&&this.pullRequest?{pull_request:this.pullRequest}:{},c=(l,d,p,f,g)=>({name:l,unit:d,description:p,value:f,attributes:{project:o,stage:s,...a,...g}}),u=[c("terragucci_stage_duration_seconds","s","How long the stage ran",Om(this.start,r),{result:i}),c("terragucci_roots_planned","{root}","Roots the stage planned, failed ones included",t.roots.length,{}),c("terragucci_plan_groups","{group}","Groups the plans fold into",t.groups.length,{})];for(let l of this.roots)l.end!==void 0&&u.push(c(bt.rootPlan,"s","How long one root took to plan",Om(l.start,l.end),{root:l.path}));for(let[l,d]of Object.entries(t.totals))u.push(c("terragucci_plan_changes","{change}","Proposed changes by action",d,{action:l}));return n&&u.push(c("terragucci_binary_version","","The binary and version a stage ran",1,{binary:t.run.binary,version:n})),[...u,...this.dashboardGauges(t,r,c)]}applyGauges(t,n){let r=t.run.project,o=this.stage,s=(c,u,l,d,p)=>({name:c,unit:u,description:l,value:d,attributes:{project:r,stage:o,...p}}),i=this.wave?{wave:String(this.wave.number)}:{},a=[];this.wave&&a.push(s(bt.stageDuration,"s","How long the stage ran",Wv(Om(this.start,n)),{...i,result:Bv(this.wave.code??1,this.wave.facts)}));for(let c of t.roots){let u=this.roots.find(g=>g.path===c.path),l=g=>u?.commands.filter(m=>m.command===g).reduce((m,h)=>(m??0)+h.seconds,void 0),d=l("init");d!==void 0&&a.push(s(bt.rootInit,"s","How long one root's init took",Wv(d),{...i,root:c.path}));let p=l("plan")??c.timings?.plan_seconds;p!==void 0&&a.push(s(bt.rootPlan,"s","How long one root took to plan",Wv(p),{...i,root:c.path}));let f=l("apply")??c.timings?.apply_seconds;f!==void 0&&a.push(s(bt.rootApply,"s","How long one root took to apply",Wv(f),{...i,root:c.path}))}return a.push(...this.initAndLockGauges(t,s)),this.wave&&a.push(...ez(this.wave.number,this.wave.code??1,this.wave.facts,Number(n/1000000n)/1e3,s,t.totals)),a}initAndLockGauges(t,n){let r=[];for(let o of t.roots){for(let i of o.timings?.provider_init??[])r.push(n(bt.providerInit,"s","Time spent starting a provider in a root",i.ms/1e3,{root:o.path,provider:i.provider}));let s=(o.timings?.lock_waits??[]).reduce((i,a)=>i+a.ms,0);o.timings?.lock_waits?.length&&r.push(n(bt.lockWait,"s","Time spent waiting for a state lock in a root",s/1e3,{root:o.path}))}return r}dashboardGauges(t,n,r){let o=Number(n/1000000n)/1e3,s=[r(bt.lastRun,"s","When the stage last ran, in Unix seconds",o,{}),r(bt.rootsChanged,"{root}","Roots whose plan changes something",t.roots.filter(i=>i.status==="planned"&&i.changes.length>0).length,{})];if(t.tips){let i=new Map;for(let a of t.tips)i.set(a.rule,(i.get(a.rule)??0)+1);for(let[a,c]of i)s.push(r(bt.tips,"{tip}","Tips the run gave, by rule",c,{rule:a}))}for(let i of this.pins)s.push(r(bt.modulePin,"","A module call's pin in a root",1,{root:i.root,module:i.module,version:i.version}));s.push(...this.initAndLockGauges(t,r));for(let i of t.timings?.resources??[])s.push(r(bt.resource,"s","How long one of the run's slowest resources took",i.ms/1e3,{root:i.root,address:i.address}));if(t.run.stage==="tf-drift"){let i=np(t);s.push(r(bt.driftRoots,"{root}","Roots the drift run found drifted",i.roots,{})),i.roots>0?s.push(r(bt.driftSince,"s","When the open drift was first found, in Unix seconds",Date.parse(this.drift?.since??t.run.finished)/1e3,{})):i.failed===0&&s.push(r(bt.driftClear,"s","When a drift run last found no drift, in Unix seconds",o,{}))}return s}async finish(t,n,r,o){if(this.receiver){for(let p of this.receiver.unreadable)r(`timings: a batch of spans could not be read: ${p}`);let d=await this.receiver.close();d.length&&r(`telemetry: ${d.length} of the binary's span batches were not forwarded: ${d[0]}`)}let s=this.telemetry;if(!s)return;for(let d of s.skipped)r(`telemetry: ${d}`);let i=eo(),a=t.run,c={...s.resource,"service.version":a.terragucci,"terragucci.project":a.project},u={...c,"vcs.ref.head.revision":a.commit,...a.job_url?{"cicd.pipeline.run.url.full":a.job_url}:{},...this.pipeline.run?{"cicd.pipeline.run.id":this.pipeline.run}:{},...this.pipeline.job?{"cicd.pipeline.task.name":this.pipeline.job}:{}},l=[];if(s.traces&&this.trace&&this.stageSpan){let d=t.roots.filter(w=>w.status==="failed").length,p=this.wave?Bv(this.wave.code??1,this.wave.facts):d?"failure":"success";this.trace.end(this.stageSpan,{"terragucci.project":a.project,"vcs.ref.head.revision":a.commit,"terragucci.binary":a.binary,"terragucci.runtime":a.runtime,"terragucci.change_set":t.change_set,"terragucci.roots":t.roots.length,"terragucci.roots_failed":d,"terragucci.result":p,...this.wave?{"terragucci.wave":this.wave.number}:{},...this.reportUrl?{"terragucci.report.url":this.reportUrl}:{},...a.job_url?{"cicd.pipeline.run.url.full":a.job_url}:{},...Object.fromEntries(Object.entries(t.totals).map(([w,b])=>[`terragucci.plan.${w}`,b]))},d?`${d} root(s) failed`:p==="failed"?`wave ${this.wave?.number} failed`:void 0);for(let w of this.unitSpans.values())w.end??=i;let f=[...this.roots.flatMap(w=>w.spans.map(b=>({path:w.path,span:b}))),...[...this.unitSpans].map(([w,b])=>({path:w,span:b}))];for(let w of this.phased?[]:t.waves){let b=f.filter(v=>w.roots.includes(v.path)&&v.span.end!==void 0);if(b.length===0)continue;let k=this.trace.start(`wave ${w.number}`,this.stageSpan,{"terragucci.wave":w.number,"terragucci.set_digest":w.set_digest??void 0,"terragucci.approval":w.approval});k.start=b.reduce((v,$)=>$.span.start<v?$.span.start:v,b[0].span.start),k.end=b.reduce((v,$)=>$.span.end>v?$.span.end:v,b[0].span.end);for(let v of b)v.span.parentSpanId=k.spanId}let g=new Set(this.phases.filter(w=>w.name==="apply").map(w=>w.spanId)),m=t.waves.some(w=>w.state==="failed"),h=w=>w.status==="failed"||w.error?"failed":w.applied_changes!==void 0?"applied":m?"failed":w.status;for(let w of f){let b=t.roots.find(k=>k.path===w.path);b&&(Object.assign(w.span.attributes,{"terragucci.status":w.span.parentSpanId&&g.has(w.span.parentSpanId)?h(b):b.status,"terragucci.plan_digest":b.plan_digest??void 0,...Object.fromEntries(Object.entries(b.counts).map(([k,v])=>[`terragucci.plan.${k}`,v]))}),b.error&&(w.span.error=b.error.split(`
502
+ `)[0]))}let y=await Am(s.traces,QU(this.trace.spans,u,a.terragucci??"0.0.0"),o);y?r(`telemetry: the trace was not sent: ${y}`):l.push(`trace ${this.trace.traceId} (${this.trace.spans.length} spans)`)}if(s.metrics){let d=Hue(this.stage==="tf-apply"?this.applyGauges(t,i):this.gauges(t,que(a.binary,n),i),this.perRun,a.commit),p=await Am(s.metrics,Uv(d,c,a.terragucci??"0.0.0",i),o);p?r(`telemetry: the metrics were not sent: ${p}`):l.push(`${d.length} metric points`)}l.length&&r(`telemetry: sent ${l.join(" and ")}`)}};import{createHash as Zle}from"node:crypto";import{existsSync as Fm,mkdirSync as Gz,readdirSync as Yle,readFileSync as ya,rmSync as Fz,statSync as Xle,writeFileSync as fi}from"node:fs";import{dirname as Qle,join as lt,relative as GA,resolve as ip}from"node:path";import{spawn as lle,spawnSync as Sz}from"node:child_process";import{createHash as Rz,randomUUID as dle}from"node:crypto";import{chmodSync as ple,existsSync as Tc,mkdirSync as Kv,mkdtempSync as xz,readdirSync as Ez,readFileSync as op,rmSync as Tz,writeFileSync as sp}from"node:fs";import{hostname as fle,tmpdir as gle}from"node:os";import{basename as mle,dirname as hle,join as Ct,resolve as yle}from"node:path";import{readFileSync as Jue,writeFileSync as Kue}from"node:fs";import{join as nz,resolve as rz}from"node:path";var Vue=e=>/(^|\/)choudoufu[^/]*$/.test(e);async function Zue(e,t,n,r){let o=await e(t,["init","-input=false","-no-color"],r.dir,r.init?.init??r.env);if(o.code!==0)throw new _(`init in ${n} failed: ${to(o.out)}`)}function Yue(e,t){let n=Fo(nz(e,t)).estate;if(!n)throw new _(`${t} has a live block that names no estate`);return n}var Xue=e=>{let t=e.lastIndexOf(": ");return{address:e.slice(0,t),actions:e.slice(t+2)}},Que=(e,t,n)=>t.map(Xue).filter(r=>r.actions===n&&(r.address===e||r.address.startsWith(`${e}[`))).map(r=>r.address).sort();async function oz(e,t,n,r,o,s){let i=await e(t,["live-mv","-no-color","-json",...s?["-dry-run"]:[],`-from-estate=${r}`,o,o],n.dir,n.env),a;try{a=JSON.parse(i.stdout)}catch{a=void 0}let c=a?.refusal?[a.refusal.summary,a.refusal.detail].filter(Boolean).join(": "):void 0;if(i.code!==0||!a||c)throw new _(`live-mv ${s?"-dry-run ":""}refuses ${o}: ${c??to(i.out)}`);return a}async function ele(e,t,n,r,o){let s=await oz(e,t,n,r.from_estate,r.address,!1);if(s.written!==!0)throw new _(`live-mv wrote nothing on ${r.address}`);if(s.resource?.live_id!==r.live_id)throw new _(`live-mv rewrote ${s.resource?.live_id??"an unnamed resource"} for ${r.address}, not ${r.live_id}`);o(`${r.address}: retagged from estate ${r.from_estate} to estate ${r.to_estate}`)}function tle(e){let t=[],n;for(let r of e.split(`
503
503
  `)){let o=/^([A-Z_]+) \(\d+\) - /.exec(r);if(o){n=["VERIFIED","DRIFTED","MISSING","UNTAGGABLE","UNADMITTED_TYPE"].includes(o[1])?o[1]:void 0;continue}if(!r.trim())continue;let s=/^ {2}(\S+)\s+(\S+)\s+live id: (\S+)\s*$/.exec(r);n&&s&&t.push({address:s[1],type:s[2],status:n,...s[3]!=="-"?{live_id:s[3]}:{}}),/eligible for stamping/.test(r)&&(n=void 0)}return t}var vA=["VERIFIED","UNTAGGABLE"];function nle(e){let t=/(\d+) failed, \d+ skipped\./.exec(e);return t?Number(t[1]):void 0}var rle="-approve";async function sz(e,t,n,r,o,s){let i=await e(t,["live-import","-no-color",`-state=${rz(r)}`,`-estate=${o}`,...s?[rle]:[]],n.dir,n.env);if(i.code!==0)throw new _(`live-import ${s?"could not stamp":"could not verify"} ${r.replace(/^.*\//,"")}: ${to(i.out)}`);return i.stdout}async function iz(e,t,n){let r=n.exec??li,o=n.env??process.env;if(!Vue(n.binary))throw new _(`migration ${t.name} cannot run: its roots keep their resources under live markers, which only choudoufu reads, and this job runs ${n.binary}`);if(t.kind==="revert")throw new _(`migration ${t.name} cannot run: an estate keeps no state file to put back; move the resources back with a moves migration the other way`);let s=[...new Set(t.kind==="backends"?t.backends.map(u=>u.root):t.moves.flatMap(u=>[u.from,u.to]))].sort(),i=await kA(e,s,n,o,et(e,ue({}))),a=new Map(s.map(u=>[u,Yue(e,u)]));for(let u of s)await Zue(r,n.binary,u,i.get(u));let c=new Map;for(let[u,l]of s.entries()){let d=i.get(l);c.set(l,await Pm(r,n.binary,d.dir,d.env,n.work,`estate-${u}`,!0))}return t.kind==="moves"?ole(t,s,a,c,i,r,n):sle(e,t,s,a,c,i,r,n)}async function ole(e,t,n,r,o,s,i){let a=i.log??(()=>{}),c=[],u=[];for(let p of e.moves){let f=n.get(p.from),g=n.get(p.to);if(f===g){c.push(`${p.from} and ${p.to} are both estate ${f}; a move inside one estate is a moved block`);continue}for(let m of p.addresses){if(/(^|\.)module\.[A-Za-z_][\w-]*$/.test(m)){c.push(`${m} is a module call; a retag names each resource in it`);continue}let h=Que(m,r.get(p.from).changes,"delete");if(h.length===0){c.push(`${m}: the plan of ${p.from} does not destroy it, so estate ${f} holds no live resource it leaves`);continue}for(let y of h){let w=await oz(s,i.binary,o.get(p.to),f,y,!0);if(!w.resource?.live_id)throw new _(`live-mv -dry-run names no live resource for ${y}`);let b=(w.followers??[]).map(k=>k.address).filter(k=>typeof k=="string").sort();u.push({address:y,from:p.from,to:p.to,from_estate:f,to_estate:g,live_id:w.resource.live_id,followers:b}),a(`${y}: ${w.resource.live_id} moves from estate ${f} to estate ${g}${b.length?`, with ${b.join(", ")}`:""}`)}}}if(c.length>0)throw new _(`migration ${e.name}:
504
504
  ${c.join(`
505
505
  `)}`);let l=[];for(let p of t){let f=u.filter(w=>w.from===p),g=u.filter(w=>w.to===p),m=new Set([...f.flatMap(w=>[w.address,...w.followers].map(b=>`${b}: delete`)),...g.flatMap(w=>[w.address,...w.followers].map(b=>`${b}: create`))]),h=r.get(p),y=w=>w.map(b=>({address:b.address,live_id:b.live_id,followers:b.followers}));l.push({root:p,backend:"estate",location:`estate ${n.get(p)}`,before:{digest:fa(JSON.stringify({estate:n.get(p),changes:[...h.changes].sort(),leaving:y(f),joining:y(g)}))},after:{digest:fa(JSON.stringify({estate:n.get(p),changes:[]}))},proof:{changes:h.changes.filter(w=>!m.has(w)),summary:h.summary}}),a(`${p}: ${cz(h.changes,m,"the retag")}`)}return{record:uz(e,"retag",l,{retags:u}),places:o,files:new Map,estate:{kind:"retag",retags:u}}}async function sle(e,t,n,r,o,s,i,a){let c=a.log??(()=>{}),u=[],l=[],d=[],p=new Map;for(let[g,m]of t.backends.entries()){let h=s.get(m.root),y=r.get(m.root),w=Hr(h.dir,h.env,{type:m.from.backend,config:m.from.config}),b=qv(m.root,w);if(b)throw new _(`migration ${t.name} cannot run: the backend it adopts from: ${b}`);let k=await az(w,h.dir,a);if(!k)throw new _(`migration ${t.name}: ${En(w)} holds no state of ${m.root} to adopt`);let v=nz(a.work,`adopt-${g}.tfstate`);Kue(v,k.text);let $=await sz(i,a.binary,h,v,y,!1),S=tle($);if(S.length===0&&k.state.resources.some(A=>A.mode==="managed"))throw new _(`migration ${t.name}: live-import printed no ratification for ${m.root}: ${to($)}`);let R=o.get(m.root),E=new Set(S.filter(A=>vA.includes(A.status)).map(A=>`${A.address}: create`)),x=S.filter(A=>!vA.includes(A.status)).map(A=>`${A.address}: ${A.status.toLowerCase().replace("_"," ")}`);for(let A of S)l.push({root:m.root,estate:y,...A});let O=Ec(k.state);u.push({root:m.root,backend:"estate",location:`estate ${y}`,before:{digest:fa(JSON.stringify({estate:y,changes:[...R.changes].sort(),entries:S}))},after:{digest:fa(JSON.stringify({estate:y,changes:[]}))},source:{backend:w.backend,...En(w)?{location:En(w)}:{},...k.version?{version_id:k.version}:{},digest:O},proof:{changes:[...x,...R.changes.filter(A=>!E.has(A))],summary:R.summary}}),d.push({root:m.root,estate:y,state:v,source:w,...k.version?{version:k.version}:{},digest:O}),p.set(m.root,{path:v,object:{backend:"estate",unsupported:"an estate keeps no state file"},state:k.state,beforeCount:0,source:{object:w,block:"",data:""}}),c(`${m.root}: ${S.filter(A=>vA.includes(A.status)).length} of ${S.length} resource instances in ${En(w)} verify against the live system; ${cz(R.changes,E,"the stamp")}`)}return{record:uz(t,"adopt",u,{stamps:l,backends:t.backends.map(g=>({root:g.root,from:En(d.find(m=>m.root===g.root).source)??g.from.backend}))}),places:s,files:p,estate:{kind:"adopt",adopts:d}}}async function az(e,t,n){let r,o;if(e.backend==="s3"&&"target"in e){let i=e;o=await il(e,n.fetch),r=o?await iw(i,n.fetch).readVersion(i.key,o):await iw(i,n.fetch).get(i.key)}else if(e.backend==="local"&&"path"in e)try{r=Jue(rz(t,e.path),"utf-8")}catch{r=void 0}if(!r?.trim())return;let s;try{s=JSON.parse(r)}catch{throw new _(`${En(e)} is not a state`)}return Array.isArray(s.resources)||(s.resources=[]),{text:r,state:s,...o?{version:o}:{}}}var cz=(e,t,n)=>{let r=e.filter(o=>!t.has(o));return e.length===0?"no changes":r.length===0?`${e.length} planned change${e.length===1?"":"s"}, each one ${n} removes`:`${r.length} change${r.length===1?"":"s"} ${n} does not remove: ${r.join(", ")}`};function uz(e,t,n,r){return{schema:Hv,name:e.name,file:e.file,file_digest:e.digest,change:t,moves:e.moves,...r,roots:n,digest:$A(e,n),status:n.some(o=>o.proof.changes.length>0)?"proof-failed":"planned"}}async function lz(e,t){let n=t.exec??li,r=t.log??(()=>{}),o=e.record,s=e.estate,i=s.kind==="adopt"?await jm(e.files,t.now,t.fetch):[];try{if(s.kind==="retag"){let u=[];for(let l of s.retags){try{await ele(n,t.binary,e.places.get(l.to),l,r)}catch(d){return{...o,status:"failed",error:`${d.message}${u.length?`; already retagged: ${u.join(", ")}`:"; nothing was retagged"}`}}u.push(l.address)}}else{let u=[];for(let l of s.adopts){let d=await az(l.source,e.places.get(l.root).dir,t);(!d||d.version!==l.version||Ec(d.state)!==l.digest)&&u.push(l.root)}if(u.length>0)return{...o,status:"refused",moved:u};for(let l of s.adopts){let d;try{d=await sz(n,t.binary,e.places.get(l.root),l.state,l.estate,!0)}catch(f){return{...o,status:"failed",error:f.message}}let p=nle(d);if(p===void 0||p>0)return{...o,status:"failed",error:`live-import ${p===void 0?"printed no stamp report":`failed to stamp ${p} resource${p===1?"":"s"}`} in ${l.root}: ${to(d)}`};r(`${l.root}: stamped estate ${l.estate} on the resources of ${En(l.source)}, which is left where it was`)}}let a=[];for(let[u,l]of o.roots.entries()){let d=e.places.get(l.root),p=await Pm(n,t.binary,d.dir,d.env,t.work,`estate-${u}-verify`,!0);a.push({...l,verify:p}),r(`${l.root}: ${p.changes.length===0?"no changes":`${p.changes.length} changes: ${p.changes.join(", ")}`}`)}let c=a.filter(u=>u.verify.changes.length>0).map(u=>u.root);return{...o,roots:a,status:c.length>0?"failed":"applied",...c.length>0?{error:`after the ${o.change==="retag"?"retag":"stamp"}, ${c.join(", ")} plan changes`}:{}}}finally{for(let a of i)await a.release().catch(c=>r(`${a.root}: the lock could not be released: ${c.message}`))}}import{existsSync as dz,readFileSync as pz}from"node:fs";import{homedir as ile}from"node:os";import{isAbsolute as fz,join as gz}from"node:path";var _A=["remote","cloud","file"],mz="app.terraform.io",ale=/^[a-z0-9]([a-z0-9.-]*[a-z0-9])?(:\d+)?$/i;function hz(e,t,n){let r=[];if(t==="file"){for(let s of Object.keys(n))s!=="path"&&r.push(`${e}.from.config.${s} is not a key; a file source has path`);return typeof n.path!="string"||!n.path.trim()?r.push(`${e}.from.config.path must name the state file`):!fz(n.path)&&n.path.split("/").includes("..")&&r.push(`${e}.from.config.path must be in the repo or an absolute path, not ${n.path}`),r}for(let s of Object.keys(n))s==="token"?r.push(`${e}.from.config.token: a token does not belong in the repo; the job reads TF_TOKEN_<host> or credentials.tfrc.json`):["hostname","organization","workspaces"].includes(s)||r.push(`${e}.from.config.${s} is not a key; a ${t} source has hostname, organization and workspaces`);n.hostname!==void 0&&(typeof n.hostname!="string"||!ale.test(n.hostname))&&r.push(`${e}.from.config.hostname must be a host name, such as ${mz}`),(typeof n.organization!="string"||!n.organization)&&r.push(`${e}.from.config.organization must name the organization`);let o=n.workspaces&&typeof n.workspaces=="object"&&!Array.isArray(n.workspaces)?n.workspaces:void 0;(!o||typeof o.name!="string"||!o.name)&&r.push(`${e}.from.config.workspaces.name must name the one workspace whose state moves`);for(let s of Object.keys(o??{}))s!=="name"&&r.push(`${e}.from.config.workspaces.${s}: a move reads one workspace, named by workspaces.name`);return r}var Im=e=>`TF_TOKEN_${e.replace(/-/g,"__").replace(/\./g,"_")}`;function yz(e,t){let n=t[Im(e)];if(n)return n;let r=gz(t.HOME??ile(),".terraform.d","credentials.tfrc.json");if(dz(r))try{return JSON.parse(pz(r,"utf-8")).credentials?.[e]?.token||void 0}catch{return}}function wz(e,t,n,r,o){if(t==="file")return cle(e,String(n.path));if(t!=="remote"&&t!=="cloud")return;let s=n.workspaces;return ule(t,typeof n.hostname=="string"?n.hostname:mz,String(n.organization),s.name,r,o)}function cle(e,t){let n=fz(t)?t:gz(e,t);return{backend:"file",location:t,read:async()=>{if(!dz(n))return null;let r=pz(n,"utf-8"),o;try{o=JSON.parse(r)}catch{throw new _(`${t} is not a state file: it is not JSON`)}if(o.version!==4||typeof o.lineage!="string"||typeof o.serial!="number"||!Array.isArray(o.resources))throw new _(`${t} is not a state file of format version 4 with a lineage, a serial and resources`);return{text:r}}}}function Cm(e,t,n,r=fetch,o){let s=yz(e,n)??(o&&n[o]||void 0),i=`https://${e}`,a,c=async(f,g="GET",m)=>{let h=[e,a?new URL(a).host:e].includes(new URL(f).host),y={accept:"application/vnd.api+json",...m?{"content-type":"application/vnd.api+json"}:{},...s&&h?{authorization:`Bearer ${s}`}:{}},w;try{w=await r(f,{method:g,headers:y,...m?{body:m}:{}})}catch(b){let k=b.cause?.message;throw new _(`${t}: ${g} ${new URL(f).pathname} failed: ${b.message}${k?` (${k})`:""}`)}return{status:w.status,text:await w.text()}},u=()=>s?`the token in ${Im(e)}${o?` or ${o}`:""} or credentials.tfrc.json may not read it`:`there is no token for ${e}: set ${Im(e)}${o?` or ${o}`:""}`,l=(f,g)=>{if(g.status===401||g.status===404)throw new _(`${t}: ${f} answered ${g.status}; ${u()}`);if(g.status>=300)throw new _(`${t}: ${f} answered ${g.status}: ${g.text.slice(0,200)}`);try{return JSON.parse(g.text)}catch{throw new _(`${t}: ${f} answered with something that is not JSON`)}};return{host:e,hasToken:s!==void 0,call:c,json:l,api:async()=>{if(a)return a;let f=await c(`${i}/.well-known/terraform.json`),g={};try{g=f.status<300?JSON.parse(f.text):{}}catch{g={}}let m=g["tfe.v2"];if(typeof m!="string")throw new _(`${t}: ${e} names no tfe.v2 API in /.well-known/terraform.json, so it does not speak the remote backend's protocol`);return a=new URL(m,`${i}/`).toString().replace(/\/?$/,"/"),a},list:async(f,g)=>{let m=[],h=[];for(let y=1,w=0;y&&w<1e3;w++){let b=new URL(g);b.searchParams.set("page[number]",String(y)),b.searchParams.set("page[size]","100");let k=l(f,await c(b.toString()));if(!Array.isArray(k.data))throw new _(`${t}: ${f} answered with no list`);m.push(...k.data),Array.isArray(k.included)&&h.push(...k.included);let v=k.meta?.pagination?.["next-page"];y=typeof v=="number"&&v>y?v:0}return{data:m,included:h}}}}function ule(e,t,n,r,o,s=fetch){let i=`${e}://${t}/${n}/${r}`,a=`https://${t}`,{call:c,api:u}=Cm(t,i,o,s),l=(f,g)=>{if(g.status===401||g.status===404){let m=yz(t,o);throw new _(`${i}: ${f} answered ${g.status}; ${m?`the token in ${Im(t)} or credentials.tfrc.json may not read the workspace`:`the job has no token for ${t}: set ${Im(t)}`}`)}if(g.status>=300)throw new _(`${i}: ${f} answered ${g.status}: ${g.text.slice(0,200)}`);try{return JSON.parse(g.text)}catch{throw new _(`${i}: ${f} answered with something that is not JSON`)}},d,p=async()=>{if(d)return d;let f=await u(),g=l("the workspace",await c(`${f}organizations/${encodeURIComponent(n)}/workspaces/${encodeURIComponent(r)}`));if(!g.data?.id)throw new _(`${i}: the workspace answered with no id`);return d=g.data.id,d};return{backend:e,location:i,read:async()=>{let f=await u(),g=await p(),m=await c(`${f}workspaces/${g}/current-state-version`);if(m.status===404)return null;let h=l("the current state version",m),y=h.data?.attributes?.["hosted-state-download-url"];if(!h.data?.id||typeof y!="string")throw new _(`${i}: the current state version names no download URL`);let w=await c(new URL(y,`${a}/`).toString());if(w.status>=300)throw new _(`${i}: the download of ${h.data.id} answered ${w.status}`);return{text:w.text,version:h.data.id}},lock:async f=>{let g=await u(),m=await p(),h=await c(`${g}workspaces/${m}/actions/lock`,"POST",JSON.stringify({reason:f}));if(h.status===409)throw new _(`its workspace ${i} is locked, so nothing was written`);return l("the workspace lock",h),async()=>{let y=await c(`${g}workspaces/${m}/actions/unlock`,"POST");if(y.status>=300)throw new _(`${i}: the unlock answered ${y.status}`)}}}}var mr="tf-migrate",ga=`_gates/${mr}.jsonl`,Vv=`_gates/${mr}/done.jsonl`,wle=`_gates/${mr}/applied.jsonl`,di="migrations",Hv="terragucci.migration/v1",Az="terragucci_migrate_override.tf",Nm=(e,t)=>`_gates/${mr}/${e}/${t.replace(":","_")}.json`,ble=(e,t,n=!1)=>`terragucci approve ${e} --plan ${t}${n?" --sign":""}`,vn={applied:0,failed:1,waiting:3,refused:4},fa=e=>`sha256:${Rz("sha256").update(e).digest("hex")}`,Oz=/^[a-z0-9][a-z0-9._-]{0,99}$/,vle=/^(?:module\.[A-Za-z_][\w-]*\.)*(?:[A-Za-z_][\w-]*\.[A-Za-z_][\w-]*|module\.[A-Za-z_][\w-]*)$/;function $le(e,t){let n=e.replace(/^.*\//,"").replace(/\.ya?ml$/,""),r=[];Oz.test(n)||r.push(`the file name ${n} is not a gate name: lower-case letters, digits, dots, dashes and underscores`);let o;try{o=lo(t)}catch(l){throw new _(`${e}: ${l.message}`)}let s=o&&typeof o=="object"?o:{};for(let l of Object.keys(s))["moves","backends","revert","restores"].includes(l)||r.push(`${l} is not a key of a migration; it has moves, backends, or revert and restores`);let i=[s.moves!==void 0&&"moves",s.backends!==void 0&&"backends",(s.revert!==void 0||s.restores!==void 0)&&"revert"].filter(Boolean);i.length>1&&r.push(`a migration holds one kind of change, and this one has ${i.join(" and ")}; write a file for each`);let a=i[0]??"moves",c=(l,d,p)=>{if(typeof p!="string"||!p.trim()){r.push(`${l}.${d} must name a root directory`);return}let f=p.trim().replace(/^\.\//,"").replace(/\/+$/,"");return(f.startsWith("/")||f.split("/").includes(".."))&&r.push(`${l}.${d} must be a directory in the repo, not ${p}`),f};if(a==="backends")return{name:n,file:e,digest:fa(t),kind:a,moves:[],backends:Rle(s.backends,r,c,e),restores:[]};if(a==="revert"){let l=xle(s,r,c);if(r.length>0)throw new _(`${e}:
@@ -761,7 +761,7 @@ ${M.join(`
761
761
  `)}`}}let E=await kw(e,n,M=>{if(!a.outputs.has(M)){let te=Fe(e,M),ie=ds(dn({...process.env,TF_PLUGIN_CACHE_DIR:a.dir},M),te);a.outputs.set(M,t.resolve(M).then(Q=>Q.path,()=>w).then(Q=>$w(Q,te,ie,a.initTurn)))}return a.outputs.get(M)});if(E.errors.length>0)return{...k,error:E.errors.join(`
762
762
  `)};if(E.waiting.length>0)return{...k,error:`not planned: ${E.waiting.map(M=>`it reads ${M.read.var} from ${M.read.upstream}, which ${M.why} (${M.read.function})`).join("; ")}; apply ${[...new Set(E.waiting.map(M=>M.read.upstream))].join(", ")} first`};for(let M of E.filled)console.log(`${n}: ${M} read from the state its stacks name`);if($=await kp(e,k,v,"after-init")??await kp(e,k,v,"before-plan"),$)return{...k,error:$};let x=["-input=false","-no-color",..._a("plan",f),`-out=${g}`],O=bb(w,l,x,wb(e,n)),A=await C$(s,i,w,["plan",...O,...x],f,d);if(A.code!==0)return{...k,error:`plan failed
763
763
  ${A.out}`};let I=O.length&&l?vb(A.out,g,l):void 0;if(I&&console.log($b(n,I)),$=await kp(e,k,v,"after-plan",g),$)return{...k,error:$};let C=PH(w,[`-chdir=${d}`,"show","-json",g],{encoding:"utf-8",env:Ve(f),maxBuffer:512*1024*1024}),L;try{L=JSON.parse(C.stdout)}catch{return{...k,error:`show -json printed no plan
764
- ${C.stderr||C.stdout}`}}let z=go({member:n,plan:L,planner:rn(w)}),F=z.entries.filter(M=>M.action!=="no-op"&&M.action!=="read");return{...k,member:{member:n,planDigest:z.member.planDigest?kb(z.member.planDigest,I):""},plan:L,...I?{cone:I}:{},changes:F.length,destroys:F.filter(M=>M.action==="delete"||M.action==="replace").length,summary:A.out.match(/Plan: .*|No changes\..*/)?.[0]??"planned"}}async function gme(e,t,n,r){let o=r?{...r,env:ds(dn({...r.env,TF_PLUGIN_CACHE_DIR:t.env.TF_PLUGIN_CACHE_DIR},t.root),Fe(e,t.root))}:void 0,s=await kp(e,t,o,"before-apply",t.planFile);if(s)return console.log(`FAILED ${t.root}: nothing applied`),console.log(Aa(s)),t.error=s,!1;n.reopen(t.timing);let i;try{i=await C$(n,t.timing,t.binary,["apply","-input=false","-no-color",..._a("apply",t.env),t.planFile],t.env,Fe(e,t.root))}finally{n.endRoot(t.timing)}if(i.code===0){let a=await kp(e,t,o,"after-apply");return a?(console.log(`FAILED ${t.root}: applied, then a step after the apply failed`),console.log(Aa(a)),t.error=a,!1):(console.log(`applied ${t.root}: ${[...i.out.matchAll(/Resources: .*destroyed/g)].pop()?.[0]??"done"}`),!0)}return console.log(`FAILED ${t.root}`),console.log(Aa(i.out)),!1}function mme(e,t){let n=t.GITHUB_REF_NAME||t.CI_COMMIT_BRANCH,r=t.GITHUB_SHA||t.CI_COMMIT_SHA;if(!n||!r)return;let o=Et(e,["ls-remote","origin",`refs/heads/${n}`]),s=o.status===0?o.stdout.split(/\s/)[0]?.trim():"";return s&&s!==r?s:void 0}var hme=e=>new Promise(t=>setTimeout(t,e));async function FH(e,t,n,r,o){let s=t.env??process.env,i={release:async()=>{}},a=g=>{let m=t.standDown?mme(e,s):void 0;if(m)return console.log(`${n}: a newer push to ${s.GITHUB_REF_NAME||s.CI_COMMIT_BRANCH} (${m.slice(0,8)}) applies everything; standing down`),o.refused={reason:"superseded",roots:o.changedRoots??[]},Mr(s,"superseded by a newer push",o),g().then(()=>({code:re.superseded,release:i.release}))},c=r.filter(g=>g.plan&&mo(g.binary)==="resource").flatMap(g=>d1(g.root,Fo(Fe(e,g.root)).estate,g.plan));if(c.length===0)return await a(i.release)??i;if(Et(e,["remote","get-url","origin"]).status!==0)return console.log(`${n}: this checkout has no origin, so nothing holds the resources it changes`),i;let u=[...new Set(c.map(g=>g.key))],l=sb(s),d=t.liveness??ib(s),p=(Number(s.TG_LOCK_POLL)>0?Number(s.TG_LOCK_POLL):10)*1e3,f=!1;for(let g=0;;g++){let m=await m1(e,c,l,d);if(m.ok){for(let k of m.tookOver)console.log(`${n}: run ${k.run} held resources and is gone; this wave takes them over, and each apply re-reads what that run left`);let w=new Set(c.map(k=>`${k.root} ${k.address}`)).size;console.log(`${n}: holding ${w===1?"the resource":`the ${w} resources`} its plans change, as run ${l.run}`);let b=async()=>{await h1(e,u,l.run)?console.log(`${n}: let go of the resources it held`):console.log(`${n}: could not let go of the resources it held; the next apply takes them over once this run is gone`)};return await a(b)??{release:b}}let h=y1(m.held,c);if(t.onHeld==="refuse"){console.log(`${n}: ${h}, so nothing in it was applied; apply again once that run is done`);let w=m.held[0].holder;return o.refused={reason:"held",roots:[...new Set(m.held.map(b=>c.find(k=>k.key===b.key)?.root??""))].filter(Boolean).sort(),holder:{run:w.run,...w.url?{url:w.url}:{}}},Mr(s,`held: ${h}`,o),{code:re.held,release:i.release}}if(f||console.log(`${n}: ${h}; waiting for it`),f=!0,g>=360)return console.log(`${n}: another run has held resources this wave changes for an hour (${h}), so nothing in it was applied`),Mr(s,`held for an hour: ${h}`,o),{code:re.failed,release:i.release};await hme(p);let y=await tE(f1(e),u,l.run,d);if(y.live.length===0&&y.gone.length===0)return{code:CH,release:i.release}}}async function UH(e,t){let n=po(t.gate);if(n===void 0)throw new _(`--gate must be one of ${sd.join(", ")}`);let r={...t,gate:n};if(r.approval!==void 0&&!Mo.includes(r.approval))throw new _(`--approval must be one of ${Mo.join(", ")}`);if(!Number.isInteger(r.wave)||r.wave<1)throw new _("--wave must be a wave number from 1");if(r.shares!==void 0&&!(Number.isInteger(r.shares)&&r.shares>=1))throw new _("--shares must be a whole number of 1 or more");if(r.share!==void 0&&(r.shares===void 0||!Number.isInteger(r.share)||r.share<1||r.share>r.shares))throw new _("--share must be a share number from 1 to --shares");if(r.rest&&!r.terragrunt)throw new _("--rest runs the waves of a Terragrunt repo, so it needs --terragrunt");if(r.groups&&r.terragrunt)throw new _("--groups splits a wave of plain roots by identity; a Terragrunt repo's units apply with terragrunt.credentials in one job");if(r.group!==void 0&&!r.groups)throw new _("--group names one of --groups, so it needs --groups");if(r.branches&&Object.keys(r.branches).length>0){let o=ome(r.layers,r.branches,r.branch||void 0);if(console.log(`wave ${r.wave}: ${o.note}`),o.layers.flat().length===0)return console.log(`wave ${r.wave}: no ${r.terragrunt?"unit":"root"} applies from ${r.branch||"this branch"}, so there is nothing to apply`),re.applied;let s=r.layers.flat().filter(i=>!o.layers.flat().includes(i));r={...r,layers:o.layers,branches:void 0,...s.length?{elsewhere:s}:{}}}return yme(e,r)}async function yme(e,t){let n=t,r=n.env??process.env,o=eo(),s=ah(Fe(IH(),"terragucci-apply-")),i=new Map,a=p=>{let f=i.get(p);if(!f){let g={},m=new sl(ol(r),Xt,r,o);m.wave={number:p,facts:g},f={k:p,w:{observer:m},facts:g,label:`wave ${p}`},i.set(p,f)}return f},c=re.failed,u=n.wave,l,d;try{let p;if(n.terragrunt)p=n.rest?n.wave:Math.max(n.layers.filter(R=>R.length>0).length,n.wave);else{let R=an(n.layers,n.canary);if(!R[n.wave-1])return a(n.wave).facts.nothing=!0,console.log(`wave ${n.wave}: this repo has ${R.length} waves, so there is nothing to apply`),c=re.applied;let E=await jme(e,n,R);if(E&&(n={...n,composition:E},R=zm(R,E,!0),R[n.wave-1].length===0))return a(n.wave).facts.nothing=!0,console.log(`wave ${n.wave}: its roots apply in the composed run in wave ${E.wave}, so this wave has nothing to apply`),c=re.applied;if(p=E?Kz(an(n.layers,n.canary),E):R,n.groups){let x=n.shares;if(l=Ime(R[n.wave-1],n.groups,n.composition).map(O=>({...O,shares:x!==void 0?sh(O.roots,x):1})),n.group!==void 0){let O=l.findIndex(I=>I.name===n.group),A=l[O];if(!A||(n.share??1)>A.shares)return a(n.wave).facts.nothing=!0,console.log(`wave ${n.wave} of ${R.length}: group ${n.group} has ${A?`${A.roots.length} roots in ${A.shares} share${A.shares===1?"":"s"}`:"no root in this wave"}, so this job has nothing to apply`),c=re.applied;if(A.shares>1&&n.share===void 0)throw new _(`group ${A.name} of wave ${n.wave} spreads across ${A.shares} share jobs, so its job needs --share`);d=l.slice(0,O).reduce((I,C)=>I+C.shares,0)+(n.share??1),n={...n,shares:A.shares>1?A.shares:void 0,share:A.shares>1?n.share:void 0}}}else if(n.shares!==void 0){let x=sh(R[n.wave-1],n.shares);if(n.share!==void 0&&n.share>x)return a(n.wave).facts.nothing=!0,console.log(`wave ${n.wave} of ${R.length}: its ${R[n.wave-1].length} roots make ${x} share${x===1?"":"s"}, so share ${n.share} has nothing to apply`),c=re.applied;n=x>1?{...n,shares:x}:{...n,shares:void 0,share:void 0}}}let f=n.config??fe(e),g=n.base??vs(e),m;try{m=await Yr(e,{...n.base?{at:n.base}:{},...f?{config:f}:{},...n.approval?{flag:n.approval}:{}})}catch(R){if(n.gate!=="never")throw R}if(m?.point&&m.mode!=="ledger")throw new _(`apply.point is set at base and approval is ${m.mode}; a wave gate that asks a decision point takes its quorum from the point, so set approval to ledger or drop apply.point`);let h=await wme(e,g,f,n,n.composition&&typeof p!="number"?zm(an(n.layers,n.canary),n.composition,!1):p);typeof p!="number"&&h[n.wave-1]!==n.gate?(console.log(`wave ${n.wave}: gate ${h[n.wave-1]}, the strictest of its roots' environments at base`),n={...n,gate:h[n.wave-1]}):typeof p=="number"&&h[0]!==n.gate&&(console.log(`wave ${n.wave}: gate ${h[0]}, the strictest of the environments at base, since a Terragrunt wave finds its units when it plans`),n={...n,gate:h[0]});let y={rule:m,configPath:f,work:s,...d!==void 0?{part:d}:{},planned:R=>i.get(R)?.w.planned},w=await _H(e,s,R=>bme(e,n,y,R,a)),b;try{let R=wH({waves:p,...n.rest?{rest:!0}:{},gate:n.gate,...typeof p!="number"&&h.some(x=>x!==n.gate)?{gates:h}:{},approval:m?.mode??"ledger",...l?{jobs:{wave:n.wave,groups:l.map(x=>({name:x.name,runs:x.roots,shares:x.shares}))}}:n.shares!==void 0?{shares:{wave:n.wave,shares:n.shares}}:{},call:kH,migrations:XO(Fe(e,di)),...m?.point?{point:m.point}:{}}),E=bH(e,g,RH(e,R));if(n.share!==void 0||n.group!==void 0){let x=n.decided??Fe(e,TH(n.wave));if(!XO(x))throw new _(`the wave's decision ${x} cannot be read; the wave's own job writes it before its shares run`);_p(EH(Fe(e,I$(n.wave))),{recursive:!0}),xH(x,Fe(e,I$(n.wave)))}b=await SH(e,{wave:n.wave,base:E,...(l||n.shares!==void 0)&&n.share===void 0&&n.group===void 0?{decide:!0}:{},...n.group!==void 0?{group:n.group}:{},...n.share!==void 0?{share:n.share}:{},env:{...process.env,...r},...n.chant?{chant:n.chant}:{}})}finally{await w.close()}let k=b.result?.rest;k?.length&&(u=k[k.length-1].wave);let v=a(u);c=vH(b.code,v.code);let $=n.group!==void 0||n.share!==void 0,S=`${n.group!==void 0?`group ${n.group}`:""}${n.group!==void 0&&n.share!==void 0?" ":""}${n.share!==void 0?`share ${n.share}`:""}`;if($&&b.result?.decision&&(v.w.digest=b.result.decision.digest,(b.result.decision.status==="approved"||b.result.decision.status==="not-required")&&(v.w.approval=b.result.decision.status)),$&&b.code===re.refused&&b.result?.moved.length){let R=[...b.result.moved].sort();b.result.decision?.status==="approved"&&(v.w.refused={reason:"approval",approved:b.result.decision.digest,roots:R}),Mr(n.env,`wave ${u} ${S} changed since the wave decided: ${R.join(", ")}`,v.w)}if((l||n.shares!==void 0)&&!$&&c===re.applied){let R=n.decided??Fe(e,TH(n.wave));_p(EH(R),{recursive:!0}),xH(Fe(e,I$(n.wave)),R),v.w.decided=!0;let E=l?l.reduce((O,A)=>O+A.shares,0):n.shares;v.w.shareCount=E;let x=l?`its ${l.length} group job${l.length===1?"":"s"} (${l.map(O=>O.name).join(", ")})`:`its ${E} share job${E===1?"":"s"}`;console.log(`${v.label}: ${b.result?.decision?.status==="approved"?"approved":"no approval needed"}; ${x} apply these plans`)}return c}finally{for(let p of i.values())await p.release?.().catch(f=>console.log(`${p.label}: the composed run's locks were not released: ${f.message}`));for(let p of[...i.keys()].sort((f,g)=>f-g)){let{w:f}=i.get(p),g=p===u?c:re.applied;f.observer.wave.code=g,ahe(r,p,g,f),f.state=_me(g,f),f.ended=new Date().toISOString(),f.planned&&await Ome(e,n,f,r).catch(m=>console.log(`wave ${p}: the report was not written: ${m.message}`))}jH(s,{recursive:!0,force:!0})}}async function wme(e,t,n,r,o){let s=()=>typeof o=="number"?[r.gate]:o.map(()=>r.gate),i=await ln(e,t,n?{config:n}:{});if("error"in i){if(!(n?await Re(n).catch(()=>{}):void 0)?.environments)return s();throw new _(`environments are read from the config at base, and it could not be read (${i.error}), so the wave's gate cannot be decided`)}let a=i.config;return a.environments?typeof o=="number"?[Iw(a,r.gate)]:o.map(c=>jw(c,a,r.gate)):s()}async function bme(e,t,n,r,o){let[s,i,a]=r.args,c=ZO(r.cwd,i),u=o(c?.number??t.wave),l=s==="migrate-check"?"migrate check":s,d=u.w.observer.phase(l,{},s==="gate"?u.callEnded:void 0),p;try{return p=await $me(e,t,n,r,u,s,c,a)}finally{u.callEnded=eo();let f=s==="gate"?vme(r.env,u):{};u.w.observer.endPhase(d,{...f,...p!==void 0?{"terragucci.exit_code":p}:{}},p===void 0?"the call threw":p===re.failed?`exit ${p}`:void 0)}}function vme(e,t){let n=t.facts.held,r=n?(Date.parse(n.approvedAt)-Date.parse(n.since))/1e3:NaN;return{"terragucci.gate.status":t.w.approval??e.CHANT_GATE_STATUS,"terragucci.gate.digest":t.w.digest,...t.facts.waitingSince?{"terragucci.gate.waiting_since":t.facts.waitingSince}:{},...n&&Number.isFinite(r)&&r>=0?{"terragucci.gate.held_seconds":r,"terragucci.gate.approved_at":n.approvedAt}:{}}}async function $me(e,t,n,r,o,s,i,a){switch(s){case"plan":{if(!i||!a)throw new Error("chant called the plan with no runs file");if(o.apply||o.w.planned){console.log(`wave ${o.k}: the run it waited for let go; planning again`),await o.release?.(),delete o.release;let l=o.w.observer;o.w={observer:l};for(let d of Object.keys(o.facts))delete o.facts[d];for(let d of["code","apply","held","denials","overriders","reviewMoved","resumes","changes","stored"])delete o[d]}let c=Fe(r.cwd,a),u=t.terragrunt?await she(e,t,n,o,i,c):await Pme(e,t,n,o,i,c);return u!==0&&(o.code=u),u}case"apply":return o.apply?o.apply(r.env.CHANT_APPLY_OUTCOME?Fe(r.cwd,r.env.CHANT_APPLY_OUTCOME):void 0,new Set((i?.runs??[]).map(c=>c.target))):(console.log(`${o.label}: chant asked to apply a wave this job did not plan`),re.failed);case"hold":return o.held?1:0;case"migrate-check":case"migrate":return ehe(e,t,o,s==="migrate"?r.env:void 0,r.cwd);case"gate":return Qme(e,t,n,o,r.env,r.cwd);default:return console.log(`terragucci: chant made a call this wave does not know: ${s}`),re.failed}}function YO(e,t){e&&ml(e,JSON.stringify(t)+`
764
+ ${C.stderr||C.stdout}`}}let z=go({member:n,plan:L,planner:rn(w)}),F=z.entries.filter(M=>M.action!=="no-op"&&M.action!=="read");return{...k,member:{member:n,planDigest:z.member.planDigest?kb(z.member.planDigest,I):""},plan:L,...I?{cone:I}:{},changes:F.length,destroys:F.filter(M=>M.action==="delete"||M.action==="replace").length,summary:A.out.match(/Plan: .*|No changes\..*/)?.[0]??"planned"}}async function gme(e,t,n,r){let o=r?{...r,env:ds(dn({...r.env,TF_PLUGIN_CACHE_DIR:t.env.TF_PLUGIN_CACHE_DIR},t.root),Fe(e,t.root))}:void 0,s=await kp(e,t,o,"before-apply",t.planFile);if(s)return console.log(`FAILED ${t.root}: nothing applied`),console.log(Aa(s)),t.error=s,!1;n.reopen(t.timing);let i;try{i=await C$(n,t.timing,t.binary,["apply","-input=false","-no-color",..._a("apply",t.env),t.planFile],t.env,Fe(e,t.root))}finally{n.endRoot(t.timing)}if(i.code===0){let a=await kp(e,t,o,"after-apply");return a?(console.log(`FAILED ${t.root}: applied, then a step after the apply failed`),console.log(Aa(a)),t.error=a,!1):(console.log(`applied ${t.root}: ${[...i.out.matchAll(/Resources: .*destroyed/g)].pop()?.[0]??"done"}`),!0)}return console.log(`FAILED ${t.root}`),console.log(Aa(i.out)),!1}function mme(e,t){let n=t.GITHUB_REF_NAME||t.CI_COMMIT_BRANCH,r=t.GITHUB_SHA||t.CI_COMMIT_SHA;if(!n||!r)return;let o=Et(e,["ls-remote","origin",`refs/heads/${n}`]),s=o.status===0?o.stdout.split(/\s/)[0]?.trim():"";return s&&s!==r?s:void 0}var hme=e=>new Promise(t=>setTimeout(t,e));async function FH(e,t,n,r,o){let s=t.env??process.env,i={release:async()=>{}},a=g=>{let m=t.standDown?mme(e,s):void 0;if(m)return console.log(`${n}: a newer push to ${s.GITHUB_REF_NAME||s.CI_COMMIT_BRANCH} (${m.slice(0,8)}) applies everything; standing down`),o.refused={reason:"superseded",roots:o.changedRoots??[]},Mr(s,"superseded by a newer push",o),g().then(()=>({code:re.superseded,release:i.release}))},c=r.filter(g=>g.plan&&mo(g.binary)==="resource").flatMap(g=>d1(g.root,Fo(Fe(e,g.root)).estate,g.plan));if(c.length===0)return await a(i.release)??i;if(Et(e,["remote","get-url","origin"]).status!==0)return console.log(`${n}: this checkout has no origin, so nothing holds the resources it changes`),i;let u=[...new Set(c.map(g=>g.key))],l=sb(s),d=t.liveness??ib(s),p=(Number(s.TG_LOCK_POLL)>0?Number(s.TG_LOCK_POLL):10)*1e3,f=!1;for(let g=0;;g++){let m=await m1(e,c,l,d);if(m.ok){for(let k of m.tookOver)console.log(`${n}: run ${k.run} held resources and is gone; this wave takes them over, and each apply re-reads what that run left`);let w=new Set(c.map(k=>`${k.root} ${k.address}`)).size;console.log(`${n}: holding ${w===1?"the resource":`the ${w} resources`} its plans change, as run ${l.run}`);let b=async()=>{await h1(e,u,l.run)?console.log(`${n}: let go of the resources it held`):console.log(`${n}: could not let go of the resources it held; the next apply takes them over once this run is gone`)};return await a(b)??{release:b}}let h=y1(m.held,c);if(t.onHeld==="refuse"){console.log(`${n}: ${h}, so nothing in it was applied; apply again once that run is done`);let w=m.held[0].holder;return o.refused={reason:"held",roots:[...new Set(m.held.map(b=>c.find(k=>k.key===b.key)?.root??""))].filter(Boolean).sort(),holder:{run:w.run,...w.url?{url:w.url}:{}}},Mr(s,`held: ${h}`,o),{code:re.held,release:i.release}}if(f||console.log(`${n}: ${h}; waiting for it`),f=!0,g>=360)return console.log(`${n}: another run has held resources this wave changes for an hour (${h}), so nothing in it was applied`),Mr(s,`held for an hour: ${h}`,o),{code:re.failed,release:i.release};await hme(p);let y=await tE(f1(e),u,l.run,d);if(y.live.length===0&&y.gone.length===0)return{code:CH,release:i.release}}}async function UH(e,t){let n=po(t.gate);if(n===void 0)throw new _(`--gate must be one of ${sd.join(", ")}`);let r={...t,gate:n};if(r.approval!==void 0&&!Mo.includes(r.approval))throw new _(`--approval must be one of ${Mo.join(", ")}`);if(!Number.isInteger(r.wave)||r.wave<1)throw new _("--wave must be a wave number from 1");if(r.shares!==void 0&&!(Number.isInteger(r.shares)&&r.shares>=1))throw new _("--shares must be a whole number of 1 or more");if(r.share!==void 0&&(r.shares===void 0||!Number.isInteger(r.share)||r.share<1||r.share>r.shares))throw new _("--share must be a share number from 1 to --shares");if(r.rest&&!r.terragrunt)throw new _("--rest runs the waves of a Terragrunt repo, so it needs --terragrunt");if(r.groups&&r.terragrunt)throw new _("--groups splits a wave of plain roots by identity; a Terragrunt repo's units apply with terragrunt.credentials in one job");if(r.group!==void 0&&!r.groups)throw new _("--group names one of --groups, so it needs --groups");if(r.branches&&Object.keys(r.branches).length>0){let o=ome(r.layers,r.branches,r.branch||void 0);if(console.log(`wave ${r.wave}: ${o.note}`),o.layers.flat().length===0)return console.log(`wave ${r.wave}: no ${r.terragrunt?"unit":"root"} applies from ${r.branch||"this branch"}, so there is nothing to apply`),re.applied;let s=r.layers.flat().filter(i=>!o.layers.flat().includes(i));r={...r,layers:o.layers,branches:void 0,...s.length?{elsewhere:s}:{}}}return yme(e,r)}async function yme(e,t){let n=t,r=n.env??process.env,o=eo(),s=ah(Fe(IH(),"terragucci-apply-")),i=new Map,a=p=>{let f=i.get(p);if(!f){let g={},m=new sl(ol(r),Xt,r,o);m.wave={number:p,facts:g},f={k:p,w:{observer:m},facts:g,label:`wave ${p}`},i.set(p,f)}return f},c=re.failed,u=n.wave,l,d;try{let p;if(n.terragrunt)p=n.rest?n.wave:Math.max(n.layers.filter(R=>R.length>0).length,n.wave);else{let R=an(n.layers,n.canary);if(!R[n.wave-1])return a(n.wave).facts.nothing=!0,console.log(`wave ${n.wave}: this repo has ${R.length} waves, so there is nothing to apply`),c=re.applied;let E=await jme(e,n,R);if(E&&(n={...n,composition:E},R=zm(R,E,!0),R[n.wave-1].length===0))return a(n.wave).facts.nothing=!0,console.log(`wave ${n.wave}: its roots apply in the composed run in wave ${E.wave}, so this wave has nothing to apply`),c=re.applied;if(p=E?Kz(an(n.layers,n.canary),E):R,n.groups){let x=n.shares;if(l=Ime(R[n.wave-1],n.groups,n.composition).map(O=>({...O,shares:x!==void 0?sh(O.roots,x):1})),n.group!==void 0){let O=l.findIndex(I=>I.name===n.group),A=l[O];if(!A||(n.share??1)>A.shares)return a(n.wave).facts.nothing=!0,console.log(`wave ${n.wave} of ${R.length}: group ${n.group} has ${A?`${A.roots.length} roots in ${A.shares} share${A.shares===1?"":"s"}`:"no root in this wave"}, so this job has nothing to apply`),c=re.applied;if(A.shares>1&&n.share===void 0)throw new _(`group ${A.name} of wave ${n.wave} spreads across ${A.shares} share jobs, so its job needs --share`);d=l.slice(0,O).reduce((I,C)=>I+C.shares,0)+(n.share??1),n={...n,shares:A.shares>1?A.shares:void 0,share:A.shares>1?n.share:void 0}}}else if(n.shares!==void 0){let x=sh(R[n.wave-1],n.shares);if(n.share!==void 0&&n.share>x)return a(n.wave).facts.nothing=!0,console.log(`wave ${n.wave} of ${R.length}: its ${R[n.wave-1].length} roots make ${x} share${x===1?"":"s"}, so share ${n.share} has nothing to apply`),c=re.applied;n=x>1?{...n,shares:x}:{...n,shares:void 0,share:void 0}}}let f=n.config??fe(e),g=n.base??vs(e),m;try{m=await Yr(e,{...n.base?{at:n.base}:{},...f?{config:f}:{},...n.approval?{flag:n.approval}:{}})}catch(R){if(n.gate!=="never")throw R}if(m?.point&&m.mode!=="ledger")throw new _(`apply.point is set at base and approval is ${m.mode}; a wave gate that asks a decision point takes its quorum from the point, so set approval to ledger or drop apply.point`);let h=await wme(e,g,f,n,n.composition&&typeof p!="number"?zm(an(n.layers,n.canary),n.composition,!1):p);typeof p!="number"&&h[n.wave-1]!==n.gate?(console.log(`wave ${n.wave}: gate ${h[n.wave-1]}, the strictest of its roots' environments at base`),n={...n,gate:h[n.wave-1]}):typeof p=="number"&&h[0]!==n.gate&&(console.log(`wave ${n.wave}: gate ${h[0]}, the strictest of the environments at base, since a Terragrunt wave finds its units when it plans`),n={...n,gate:h[0]});let y={rule:m,configPath:f,work:s,...d!==void 0?{part:d}:{},planned:R=>i.get(R)?.w.planned},w=await _H(e,s,R=>bme(e,n,y,R,a)),b;try{let R=wH({waves:p,...n.rest?{rest:!0}:{},gate:n.gate,...typeof p!="number"&&h.some(x=>x!==n.gate)?{gates:h}:{},approval:m?.mode??"ledger",...l?{jobs:{wave:n.wave,groups:l.map(x=>({name:x.name,runs:x.roots,shares:x.shares}))}}:n.shares!==void 0?{shares:{wave:n.wave,shares:n.shares}}:{},call:kH,migrations:XO(Fe(e,di)),...m?.point?{point:m.point}:{}}),E=bH(e,g,RH(e,R));if(n.share!==void 0||n.group!==void 0){let x=n.decided??Fe(e,TH(n.wave));if(!XO(x))throw new _(`the wave's decision ${x} cannot be read; the wave's own job writes it before its shares run`);_p(EH(Fe(e,I$(n.wave))),{recursive:!0}),xH(x,Fe(e,I$(n.wave)))}b=await SH(e,{wave:n.wave,base:E,...(l||n.shares!==void 0)&&n.share===void 0&&n.group===void 0?{decide:!0}:{},...n.group!==void 0?{group:n.group}:{},...n.share!==void 0?{share:n.share}:{},env:{...process.env,...r},...n.chant?{chant:n.chant}:{}})}finally{await w.close()}let k=b.result?.rest;k?.length&&(u=k[k.length-1].wave);let v=a(u);c=vH(b.code,v.code);let $=n.group!==void 0||n.share!==void 0,S=`${n.group!==void 0?`group ${n.group}`:""}${n.group!==void 0&&n.share!==void 0?" ":""}${n.share!==void 0?`share ${n.share}`:""}`;if($&&b.result?.decision&&(v.w.digest=b.result.decision.digest,(b.result.decision.status==="approved"||b.result.decision.status==="not-required")&&(v.w.approval=b.result.decision.status)),$&&b.code===re.refused&&b.result?.moved.length){let R=[...b.result.moved].sort();b.result.decision?.status==="approved"&&(v.w.refused={reason:"approval",approved:b.result.decision.digest,roots:R}),Mr(n.env,`wave ${u} ${S} changed since the wave decided: ${R.join(", ")}`,v.w)}if((l||n.shares!==void 0)&&!$&&c===re.applied){let R=n.decided??Fe(e,TH(n.wave));_p(EH(R),{recursive:!0}),xH(Fe(e,I$(n.wave)),R),v.w.decided=!0;let E=l?l.reduce((O,A)=>O+A.shares,0):n.shares;v.w.shareCount=E;let x=l?`its ${l.length} group job${l.length===1?"":"s"} (${l.map(O=>O.name).join(", ")})`:`its ${E} share job${E===1?"":"s"}`;console.log(`${v.label}: ${b.result?.decision?.status==="approved"?"approved":"no approval needed"}; ${x} apply these plans`)}return c}finally{for(let p of i.values())await p.release?.().catch(f=>console.log(`${p.label}: the composed run's locks were not released: ${f.message}`));for(let p of[...i.keys()].sort((f,g)=>f-g)){let{w:f}=i.get(p),g=p===u?c:re.applied;f.observer.wave.code=g,ahe(r,p,g,f),f.state=_me(g,f),f.ended=new Date().toISOString(),f.planned&&await Ome(e,n,f,r).catch(m=>console.log(`wave ${p}: the report was not written: ${m.message}`))}jH(s,{recursive:!0,force:!0})}}async function wme(e,t,n,r,o){let s=()=>typeof o=="number"?[r.gate]:o.map(()=>r.gate),i=await ln(e,t,n?{config:n}:{});if("error"in i){if(!(n?await Re(n).catch(()=>{}):void 0)?.environments)return s();throw new _(`environments are read from the config at base, and it could not be read (${i.error}), so the wave's gate cannot be decided`)}let a=i.config;return a.environments?typeof o=="number"?[Iw(a,r.gate)]:o.map(c=>jw(c,a,r.gate)):s()}async function bme(e,t,n,r,o){let[s,i,a]=r.args,c=ZO(r.cwd,i),u=o(c?.number??t.wave),l=s==="migrate-check"?"migrate check":s,d=u.w.observer.phase(l,{},s==="gate"?u.callEnded:void 0),p;try{return p=await $me(e,t,n,r,u,s,c,a)}finally{let f=eo();u.callEnded=f;let g=s==="gate"?vme(r.env,u):{};s==="gate"&&u.facts.held&&u.w.observer.holdGate(d,u.facts.held,f),u.w.observer.endPhase(d,{...g,...p!==void 0?{"terragucci.exit_code":p}:{}},p===void 0?"the call threw":p===re.failed?`exit ${p}`:void 0)}}function vme(e,t){let n=t.facts.held,r=n?(Date.parse(n.approvedAt)-Date.parse(n.since))/1e3:NaN;return{"terragucci.gate.status":t.w.approval??e.CHANT_GATE_STATUS,"terragucci.gate.digest":t.w.digest,...t.facts.waitingSince?{"terragucci.gate.waiting_since":t.facts.waitingSince}:{},...n&&Number.isFinite(r)&&r>=0?{"terragucci.gate.held_seconds":r,"terragucci.gate.approved_at":n.approvedAt}:{}}}async function $me(e,t,n,r,o,s,i,a){switch(s){case"plan":{if(!i||!a)throw new Error("chant called the plan with no runs file");if(o.apply||o.w.planned){console.log(`wave ${o.k}: the run it waited for let go; planning again`),await o.release?.(),delete o.release;let l=o.w.observer;o.w={observer:l};for(let d of Object.keys(o.facts))delete o.facts[d];for(let d of["code","apply","held","denials","overriders","reviewMoved","resumes","changes","stored"])delete o[d]}let c=Fe(r.cwd,a),u=t.terragrunt?await she(e,t,n,o,i,c):await Pme(e,t,n,o,i,c);return u!==0&&(o.code=u),u}case"apply":return o.apply?o.apply(r.env.CHANT_APPLY_OUTCOME?Fe(r.cwd,r.env.CHANT_APPLY_OUTCOME):void 0,new Set((i?.runs??[]).map(c=>c.target))):(console.log(`${o.label}: chant asked to apply a wave this job did not plan`),re.failed);case"hold":return o.held?1:0;case"migrate-check":case"migrate":return ehe(e,t,o,s==="migrate"?r.env:void 0,r.cwd);case"gate":return Qme(e,t,n,o,r.env,r.cwd);default:return console.log(`terragucci: chant made a call this wave does not know: ${s}`),re.failed}}function YO(e,t){e&&ml(e,JSON.stringify(t)+`
765
765
  `)}function kme(e){let t=e.share!==void 0?{share:e.share}:{},n=[];return e.started&&n.push({phase:"plan",start:e.started,...e.plannedAt?{end:e.plannedAt}:{},...t}),e.gateSince&&n.push({phase:"gate",start:e.gateSince,...e.approvedAt?{end:e.approvedAt}:e.state!==void 0&&e.state!=="waiting"&&e.ended?{end:e.ended}:{}}),e.applyStarted&&n.push({phase:"apply",start:e.applyStarted,...e.ended&&e.state!=="applying"?{end:e.ended}:{},...t}),n}function _me(e,t){return e===re.applied?t.decided?"applying":"applied":e===re.waiting?"waiting":e===re.refused||e===re.held||e===re.superseded||t.refused?.reason==="policy"?"refused":"failed"}function Sme(e,t,n,r){let o=ig(e,t.flat()),s=new Map(t.flatMap((a,c)=>a.map(u=>[u,c+1]))),i=new Map(n.map(a=>[a,(o.get(a)??[]).map(c=>({upstream:c.upstream,data:c.name,outputs:"applied"}))]));return{reads:i,waves:ni(s,[...i.values()].flat().map(a=>a.upstream),r)}}function Rme(e,t,n,r,o,s){let i=et(e,t).after;if(!i)return o;let a=an(n.layers,n.canary),c=new Map(a.flatMap((d,p)=>d.map(f=>[f,p+1]))),u=Uo(i,n.layers.flat()),l=r.flatMap(d=>[...u.get(d)??[]]);return[...new Set([...o,...ni(c,l,s)])].sort((d,p)=>d-p)}function xme(e,t,n){let r=Uo(et(e,t).after,n);return r.size?new Map([...r].map(([o,s])=>[o,[...s].sort()])):void 0}function Eme(e,t,n){let r=new Map(n.flatMap((o,s)=>o.map(i=>[i,s+1])));for(let[o,s]of t){if(s.length===0)continue;let i=[...new Set(s.map(a=>a.upstream))].sort();console.log(`${e}: ${o} plans on the state ${i.map(a=>`${a}${r.has(a)?` (wave ${r.get(a)})`:""}`).join(", ")} applied`)}}function Tme(e,t){return!t.planned||t.share!==void 0||e.group!==void 0?{}:(t.totals??=BL(t.planned.map(n=>({path:n.root,plan:n.plan,planner:rn(e.binary),...n.error?{error:n.error}:{},...n.policy?{policy:n.policy}:{}}))),{totals:t.totals.totals,totals_roots:t.totals.roots})}async function ih(e,t,n,r,o=!1){let s=n.settings;if(!s?.reports?.bucket)return;let i=t.env??process.env;try{let a=Un(e,i,s.forge),c=t.terragrunt?n.units?.waves??t.layers:an(t.layers,t.canary),u=t.terragrunt?n.units?.edges??WR(Dn(e,n.settings?.terragrunt?.exclude)):Vs(e,t.layers.flat(),et(e,s).after),l=t.terragrunt?gD(e,c.flat()):Aw(e,t.layers.flat()),d=t.terragrunt?void 0:t.composition,p=YG(a.project,a.commit,d?zm(c,d,!1):c,u,l,d?.wave!==void 0&&!d.fallback?d:void 0),f=kme(n),g={...f.length?{spans:f}:{},...n.changedRoots?{changed:n.changedRoots}:{}},m=await QG(dr(s.reports,i),s.reports.prefix,p,{number:t.wave,gate:Nt(t.wave),policy:t.gate,...g,...Tme(t,n),...r});o||console.log(`wave ${t.wave}: run view at ${m}`)}catch(a){console.log(`wave ${t.wave}: the run view was not written: ${a.message}`)}}function Ame(e){if(!e)return{};let t=e.members.some(n=>n.apply!==void 0||n.state!==void 0);return{composed:{roots:e.members.map(n=>n.root),digest:e.planDigest,...e.heldByUnknown?.length?{unknown:e.heldByUnknown}:{},...t?{members:e.members.map(n=>({root:n.root,module:n.module,...n.apply?{apply:n.apply}:{},...n.state?{state:n.state}:{}}))}:{}}}}async function Ome(e,t,n,r){let{wave:o,binary:s}=t,i=n.settings,a=n.observer.phase("report"),c=new Map,u=0;for(let v of n.planned){if(v.plan===void 0)continue;let $=ws(v.plan);u+=$.values,c.set(v.root,{json:JSON.stringify($.plan,null,2)+`
766
766
  `})}let l=t.terragrunt?new Map(Dn(e,i.terragrunt?.exclude).map(v=>[v.path,v.dependencies])):xme(e,i,t.layers.flat()),d=v=>i.environments?ys(v,i):void 0,p=[...new Set(n.roots.map(d).filter(v=>v!==void 0))].sort(),f=Bu({run:{...Un(e,r,i.forge),stage:Xt,wave:o,...n.share!==void 0?{share:n.share}:{},binary:s,runtime:i.runtime,started:n.started,finished:new Date().toISOString(),terragucci:Pr},roots:n.planned.map(v=>jb(g(v),d(v.root))),waves:[{number:o,roots:n.roots,...p.length?{environments:p}:{},...n.digest?{setDigest:n.digest}:{},...n.approval?{approval:n.approval}:{},...n.gate?{gate:n.gate}:{},...n.waitingSince?{waitingSince:n.waitingSince}:{},...n.refused?{refused:n.refused}:{},...n.review?{review:n.review}:{},...n.excluded?.length?{excluded:n.excluded}:{},...n.heldBySteps?{heldBySteps:n.heldBySteps}:{},...n.waveCost?{cost:n.waveCost}:{},...n.state?{state:n.state}:{},...n.waveReads?.length?{reads:n.waveReads}:{},...n.preview?{preview:n.preview}:{},...n.composed?{composed:n.composed}:{}}],redacted:u,...n.policy?{policy:n.policy}:{}});t.composition&&(f.compose=bO(t.composition));function g(v){let $=t.composition,S={...v.policy?{policy:v.policy}:{},...$?.wave===o&&$.modules.has(v.root)&&n.composed?{composed:{wave:o,module:`module.${$.modules.get(v.root)}`}}:{}},R=v.steps?.length?{steps:v.steps}:{},E=l?.get(v.root)?.length?{dependencies:l.get(v.root)}:{};if(v.error&&!(v.policy&&v.policy.result!=="passed"&&v.plan!==void 0))return{path:v.root,planner:rn(s),...v.bin?{binary:v.bin}:{},error:v.error.split(`
767
767
  `)[0],...S,...R,...E};let x=n.states?.get(v.root),O=n.recordVersions?.get(v.root),A=n.reads?.get(v.root)?.length?{reads:n.reads.get(v.root)}:{};return{path:v.root,plan:v.plan,planner:rn(s),...v.bin?{binary:v.bin}:{},...v.cone?{cone:v.cone}:{},files:{json:oa(v.root).json},...v.error?{error:v.error}:{},...S,...R,...A,...n.applied?.has(v.root)?{applied:!0}:{},...x?{state:x}:{},...O?{recordVersions:O}:{},...E}}n.cost&&(f.cost=n.cost),n.observer.addTimings(f,["plan","apply"]);let m=Fe(e,"terragucci-report"),h=KB(r),y=hO(f,{reports:i.reports,...h?{given:h}:{},traceId:n.observer.trace?.traceId,traceUrl:i.telemetry?.trace_url});Object.assign(f.run,y.run),n.observer.reportUrl=y.run.report_url,Qb(m,f,c,y.note),n.costOutputs&&xb(m,n.costOutputs);let w=f.timings?.roots[0];w&&console.log(`wave ${o}: report in terragucci-report/, slowest root ${w.root} (${w.seconds}s)`),n.observer.endPhase(a);let b=i.reports?.bucket?n.observer.phase("upload",{"terragucci.reports.bucket":i.reports.bucket}):void 0,k;if(i.reports?.bucket&&n.decided)console.log(`wave ${o}: the report stays with the job; the reports of its shares, which apply, go to ${i.reports.bucket}`);else if(i.reports?.bucket)try{let v=await nv(dr(i.reports,r),m,f,i.reports.prefix);console.log(`wave ${o}: report copied to ${v.prefix}${f.run.report_url?`, at ${f.run.report_url}`:""}`)}catch(v){k=v.message,console.log(`wave ${o}: the report was not copied to ${i.reports.bucket}: ${v.message}`)}if(n.state&&n.share!==void 0)await ih(e,t,n,n.state==="applied"?{shares_applied:[n.share]}:{state:n.state});else if(n.state){let v=n.state==="waiting"&&n.command?{command:n.command}:{};await ih(e,t,n,{state:n.state,...n.approval?{approval:n.approval}:{},...n.digest?{digest:n.digest}:{},...v,...n.excluded?.length?{excluded:n.excluded}:{},...n.shareCount?{shares:n.shareCount}:{},report:qg(f),...Ame(n.composed)})}n.observer.endPhase(b,{},k),await n.observer.finish(f,r,v=>console.log(v))}async function M$(e,t,n){let r=ue(n?await Re(n):{});if(!t.base)return{settings:r};let o=await ln(e,t.base,n?{config:n}:{});if("error"in o)return{error:`the settings are read from ${t.base} and its config could not be read (${o.error})`};let s;try{s=ue(o.config)}catch(c){return{error:`the settings are read from ${t.base} and its config could not be read (${c.message})`}}let{policy:i,...a}=s;return{settings:{...a,...r.policy?{policy:r.policy}:{}}}}async function zH(e,t,n,r){let o;try{o=ZM(e,t.base)}catch(s){return r?.length?{error:`steps are read from the config at base, and base could not be found (${s.message})`}:{}}try{let s=await xg(e,o,r,n?{config:n}:{});s.note&&console.log(s.note);let i=Eg(s.steps,"tf-apply");return i.length===0?{}:(console.log(`steps: ${i.length} read from terragucci.yml at ${s.from.length===40?s.from.slice(0,8):s.from}`),{ws:{steps:i,env:t.env??process.env}})}catch(s){return{error:s.message}}}function oh(e,t){_p(Fe(e,".."),{recursive:!0}),ml(e,JSON.stringify(t,null,2)+`