@skyramp/testbot-core 0.13.0-v3.156 → 0.13.0-v3.157
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/VERSION.json +1 -1
- package/dist/main.js +1 -1
- package/dist/post.js +1 -1
- package/package.json +1 -1
package/dist/VERSION.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"0.13.0-v3.
|
|
1
|
+
{"version":"0.13.0-v3.157","commit":"abad9cf6582e8419e50d788c5eb80d44bfeb8c41"}
|
package/dist/main.js
CHANGED
|
@@ -312,7 +312,7 @@ Other caches with similar key:`);for(let a of i?.artifactCaches||[])ce(`Cache Ke
|
|
|
312
312
|
pushToolEvent('testbot', '${t}', '', ${JSON.stringify(e)})
|
|
313
313
|
.then(() => process.exit(0))
|
|
314
314
|
.catch(e => { console.error(e.message); process.exit(1); });
|
|
315
|
-
`],{silent:!0,env:{NODE_PATH:process.env.NODE_PATH||"",LICENSE_FILE:r,CI:"true"}})}function Q_(t=__dirname){try{let e=$R.readFileSync(cst.join(t,"VERSION.json"),"utf-8"),r=JSON.parse(e);if(typeof r.version=="string"&&typeof r.commit=="string")return{version:r.version,commit:r.commit}}catch{}return{version:"unknown",commit:"unknown"}}async function ust(t){if(!$R.existsSync(t))return{usage:null,model:null,skills:[]};let e=lst.createInterface({input:$R.createReadStream(t),crlfDelay:1/0}),r=null,n=null,s=[];for await(let i of e)if(i.trim())try{let o=JSON.parse(i);if(o.type==="assistant"){let a=o.message?.model;!n&&typeof a=="string"&&(n=a);let c=o.message?.content;if(Array.isArray(c))for(let l of c){if(l?.type!=="tool_use"||l.name!=="Skill")continue;let u=l.input?.skill;typeof u=="string"&&u.length>0&&s.push(u)}}if(o.type==="result"){let a=o.usage??{};r={inputTokens:a.input_tokens??0,outputTokens:a.output_tokens??0,cacheCreationInputTokens:a.cache_creation_input_tokens??0,cacheReadInputTokens:a.cache_read_input_tokens??0,totalCostUsd:o.total_cost_usd??0,numTurns:o.num_turns??0,durationMs:o.duration_ms??0,durationApiMs:o.duration_api_ms??0}}}catch{continue}return{usage:r,model:n,skills:s}}async function dst(t,e,r){for(let n of t){let s={skill:n,agentType:e,...Kme()};vt(`Pushing skill invocation telemetry: ${JSON.stringify(s)}`),await Xme("testbot_skill_invoked",s,r)}}async function pst(t,e,r){let n={inputTokens:String(t.inputTokens),outputTokens:String(t.outputTokens),cacheCreationInputTokens:String(t.cacheCreationInputTokens),cacheReadInputTokens:String(t.cacheReadInputTokens),totalCostUsd:String(t.totalCostUsd),numTurns:String(t.numTurns),durationMs:String(t.durationMs),durationApiMs:String(t.durationApiMs),model:e,...Kme()},s=process.env.GITHUB_REF?.match(/^refs\/pull\/(\d+)\//);s&&(n.prNumber=s[1]);let{totalCostUsd:i,...o}=n;vt(`Pushing agent usage telemetry: ${JSON.stringify(o)}`),await Xme("testbot_agent_usage",n,r)}async function mst(t,e){let{version:r,commit:n}=Q_(),s={testbotVersion:r,testbotCommit:n,actionVersion:process.env.TESTBOT_ACTION_VERSION||"unknown",actionCommit:process.env.TESTBOT_ACTION_COMMIT||"unknown",agentType:t,...Kme()};vt(`Pushing testbot run info telemetry: ${JSON.stringify(s)}`),await Xme("testbot_run_info",s,e)}Je();var is=$(require("fs")),oD=$(require("path"));var bst=require("child_process"),zR=$(require("fs")),Est=$(require("os")),F_=$(require("path"));var zi=["-c","core.fsmonitor=false"],Ylr=new Set(["GH_TOKEN","GITHUB_TOKEN","GH_ENTERPRISE_TOKEN","GITHUB_ENTERPRISE_TOKEN","ACTIONS_RUNTIME_TOKEN","ACTIONS_ID_TOKEN_REQUEST_TOKEN","ACTIONS_ID_TOKEN_REQUEST_URL","SYSTEM_ACCESSTOKEN","SYSTEM_OIDCREQUESTURI","SKYRAMP_MCP_GITHUB_TOKEN","SSH_AUTH_SOCK","GIT_SSH","GIT_SSH_COMMAND","GITHUB_STEP_SUMMARY"]),Zme=new Set(["ANTHROPIC_API_KEY","ANTHROPIC_AUTH_TOKEN","CLAUDE_CODE_OAUTH_TOKEN","AWS_ACCESS_KEY_ID","AWS_SECRET_ACCESS_KEY","AWS_SESSION_TOKEN","AWS_BEARER_TOKEN_BEDROCK","AWS_PROFILE","AWS_WEB_IDENTITY_TOKEN_FILE","AWS_ROLE_ARN","AWS_ROLE_SESSION_NAME","AWS_CONTAINER_CREDENTIALS_FULL_URI","AWS_CONTAINER_CREDENTIALS_RELATIVE_URI","AWS_CONTAINER_AUTHORIZATION_TOKEN","AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE"]);function xt(t=process.env,e,r="repo-command"){let n={};for(let[s,i]of Object.entries(t))r==="repo-command"&&Zme.has(s)||(n[s]=i);Object.assign(n,e??{});for(let s of Object.keys(n))(Ylr.has(s)||s.startsWith("INPUT_"))&&delete n[s];return n}var Fw=(r=>(r.Ready="ready",r.Blocked="blocked",r))(Fw||{}),Ss=A.string().trim().min(1),Jlr=A.object({action:A.enum(["start","run","wait"]),text:Ss,source:A.enum(["file","notes","testbot"]).describe("file: a file in the repository; notes: the Target's notes; testbot: Testbot's own finding"),file:Ss.nullable().describe("the file the step came from, relative to the component's repository; null unless source is file")}).strict().refine(t=>t.source==="file"==(t.file!==null),{path:["file"],message:"a file exactly when the step came from one"}),Klr=/^[A-Za-z_][A-Za-z0-9_]*$/,ty=/^(?!GITHUB_)(?!SKYRAMP_RUNNER_)[A-Z][A-Z0-9_]{0,99}$/,qR=(o=>(o.VersionEndpoint="version_endpoint",o.DeployWorkflow="deploy_workflow",o.KubernetesLabels="kubernetes_labels",o.Command="command",o.Api="api",o.NotTracked="not_tracked",o))(qR||{}),hn="shared",Xlr=Ss.refine(t=>{try{let e=new URL(t);return["http:","https:"].includes(e.protocol)&&!e.username&&!e.password}catch{return!1}},"an http(s) URL with no login in it"),HR=A.object({address:Xlr,readyWhen:Ss.nullable(),credentials:A.array(A.string().regex(ty)).max(50),revisionSources:A.array(A.object({method:A.nativeEnum(qR),detail:Ss.describe("what Testbot reads, such as GET /version or the deploy workflow's name; for not_tracked, why nothing shows it"),components:A.array(Ss).min(1).describe("the components it tells the revision of, by name")}).strict()).max(50)}).strict(),efe={steps:A.array(Jlr).max(20).optional().describe("how it starts, in order"),kind:A.enum(["web_app","service","database","external_service"]).optional(),from:A.object({file:Ss,service:Ss.nullable()}).strict().optional(),credentials:A.array(A.string().max(100).regex(Klr)).max(50).optional().describe("the Target's credentials this component's recipe uses: a sign-in (a test user, password or token) or an API key, which the workflow supplies from a GitHub Actions secret as the environment variable of the credential's name; give each name exactly as the recipe reads it. Names only, never a value, and not a secret the recipe generates itself")},Zlr=A.object({name:Ss,repository:Ss.describe("owner/repo of the Target repository the component is built or started from, such as the one whose compose file starts its database"),healthy:A.boolean(),...efe,deployed:Ss.nullable().optional().describe('only with "environment": the revision the component runs, as you read it from its revision source, or null when you could not tell')}),eur=A.object({component:Ss,problem:Ss,evidence:Ss.describe("the exact command and output that shows it"),fix:Ss.describe("what the user must provide, and where")}),tur=A.object({framework:Ss,file:Ss.describe("the test file run, relative to its repository root"),result:A.enum(["pass","fail","error"])}),rur=A.object({input:Ss.describe("the part of the user's SUT input, as given"),reason:Ss}),L_=A.object({schemaVersion:A.literal(1),status:A.nativeEnum(Fw).describe("ready only if every component came up healthy and the recipe is written"),components:A.array(Zlr),suites:A.array(tur).default([]).describe("one run per test suite the Target's repositories have; empty when they have none"),inputsUsed:A.array(Ss).default([]).describe("the parts of the user's SUT input the recipe used"),inputsUsedHow:A.array(A.object({input:Ss.describe("exactly as written in inputsUsed; an entry that is not there is left out"),how:Ss.describe("what the recipe did with it")})).optional(),inputsNotUsed:A.array(rur).default([]).describe("the parts it did not use, and why"),blockers:A.array(eur).describe("required (non-empty) when status is blocked"),environment:HR.optional().describe("only when the whole SUT is an existing deployment you tested where it runs, building and starting nothing: its address, what says it is ready, the credentials that reach it, and how to read what each component runs. Leave it out when any part was built or started on the runner, or reaching it needed something there (a tunnel, port-forward, proxy or client certificate)")});function tfe(t){let e=nur(t);if(e.length>0)return e;if(t.status==="blocked"&&!(Array.isArray(t.blockers)&&t.blockers.length>0))return['status "blocked" requires at least one blocker { component, problem, evidence, fix } \u2014 say what stopped the bring-up'];if(t.status==="ready"){let r=Array.isArray(t.components)?t.components:[];if(r.length===0)return['status "ready" requires at least one component \u2014 name each component the recipe brought up healthy'];let n=r.filter(s=>s?.healthy!==!0).map(s=>String(s?.name??"?"));if(n.length>0)return[`status "ready" requires every component healthy; not healthy: ${n.join(", ")} \u2014 answer "blocked" with a blocker for each, or bring them up and answer "ready" again`]}return[]}function nur(t){let e=Array.isArray(t.components)?t.components:[],r=t.environment;if(!r){let a=e.filter(c=>c?.deployed!==void 0||c?.repository===hn).map(c=>String(c?.name??"?"));return a.length>0?[`"deployed" and a "${hn}" repository belong only to an answer with "environment"; remove them from: ${a.join(", ")}`]:[]}let n=[],s=e.filter(a=>a?.deployed===void 0).map(a=>String(a?.name??"?"));s.length>0&&n.push(`with "environment", every component says what it runs in "deployed" (null when you could not tell); missing for: ${s.join(", ")}`);let i=new Set(e.map(a=>a?.name)),o=(r.revisionSources??[]).flatMap(a=>a?.components??[]).filter(a=>!i.has(a));return o.length>0&&n.push(`each revision source names components of this answer; not one of them: ${o.map(String).join(", ")}`),n}var $w=64*1024,qw=512*1024,KG=/[\u0000-\u0008\u000b\u000e-\u001f\u007f]|[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/;var xh=$(require("fs")),ZG=$(require("path"));var fst=$(require("fs")),XG=$(require("path"));function mm(t,e,r={}){let n=r.maxDepth??1/0,s=!1,i=(o,a)=>{let c;try{c=fst.readdirSync(o,{withFileTypes:!0})}catch{return}for(let l of c){if(s)return;let u=XG.join(o,l.name);if(l.isDirectory())!r.skip?.has(l.name)&&a<n&&i(u,a+1);else if(e(u,XG.relative(t,u))===!1){s=!0;return}}};i(t,0)}var sur=4,U_="\xABredacted\xBB";function ja(t){let e=new Set;for(let r of t){let n=(r??"").trim();n.length>=sur&&e.add(n)}return[...e].sort((r,n)=>n.length-r.length)}function Or(t,e){if(!t||e.length===0)return t;let r=t;for(let n of e)!n||!r.includes(n)||(r=r.split(n).join(U_));return r}function Pr(t,e){if(e.length===0)return t;let r=n=>typeof n=="string"?Or(n,e):Array.isArray(n)?n.map(r):n&&typeof n=="object"?Object.fromEntries(Object.entries(n).map(([s,i])=>[s,r(i)])):n;return r(t)}function rfe(t,e){return/\.(json|txt|md)$/.test(t)||e&&t.endsWith(".ndjson")}function gst(t,e){let r=[];return mm(t,n=>{try{rfe(ZG.basename(n),e)&&xh.lstatSync(n).isFile()&&r.push(n)}catch{}}),r}function iur(t){return t.length>=2&&(t[0]===255&&t[1]===254||t[0]===254&&t[1]===255)}var our=new TextDecoder("utf-8",{fatal:!0});function aur(t){return t.includes(0)||iur(t)||!Ast(t)}function Ast(t){try{return our.decode(t),!0}catch{return!1}}function cur(t,e){let r=Buffer.from(t,"utf16le"),n=Buffer.from("*".repeat(r.length/2),"utf16le"),s=Buffer.from(t),i=[{needle:s,mask:Buffer.alloc(s.length,"*")},{needle:r,mask:n},{needle:Buffer.from(r).swap16(),mask:Buffer.from(n).swap16()}];return e&&/^[\x00-\xff]*$/.test(t)&&/[\x80-\xff]/.test(t)&&i.push({needle:Buffer.from(t,"latin1"),mask:Buffer.alloc(t.length,"*")}),i}function hst(t,e){let r=Ast(t),n=r&&!t.includes(0),s=!1;for(let i of e)if(i)for(let{needle:o,mask:a}of cur(i,!r))for(let c=t.indexOf(o);c!==-1;c=t.indexOf(o,c+o.length))(n?Buffer.alloc(o.length,"*"):a).copy(t,c),s=!0;return s}function nfe(t,e){if(e.length===0)return;let r;try{r=xh.readdirSync(t,{withFileTypes:!0})}catch{return}for(let n of r){let s=ZG.join(t,n.name);if(n.isDirectory()){nfe(s,e);continue}if(n.isFile())try{let i=xh.readFileSync(s);if(aur(i)){rfe(n.name,!0)&&hst(i,e)&&xh.writeFileSync(s,i);continue}let o=i.toString("utf-8"),a=Or(o,e),c=a===o?i:Buffer.from(a),l=rfe(n.name,!0)&&hst(c,e);(a!==o||l)&&xh.writeFileSync(s,c)}catch{}}}var Va=".skyramp/sut/deployed-revisions.sh",ife=(r=>(r.Running="running",r.Requested="requested",r))(ife||{}),sfe=200,lur=new Set(Object.values(ife));function uur(t){let e=t.trim();if(!e)return{ok:!1,error:"deployed-revisions.sh printed nothing"};let r={ok:!1,error:`deployed-revisions.sh printed not one JSON object of component to commit: ${e.slice(0,sfe)}`},n;try{n=JSON.parse(e)}catch{return r}if(n===null||typeof n!="object"||Array.isArray(n))return r;let s={};for(let[i,o]of Object.entries(n)){let a=typeof o=="string"?{revision:o}:o,{revision:c,state:l="running",...u}=a??{};if(!(typeof a=="object"&&a!==null&&!Array.isArray(a)&&Object.keys(u).length===0&&typeof c=="string"&&c.trim()!==""&&typeof l=="string"&&lur.has(l)))return{ok:!1,error:`deployed-revisions.sh printed ${JSON.stringify(o)?.slice(0,sfe)} for ${i}: give a commit, or {"revision": "<commit>", "state": "running" or "requested"}`};s[i]={revision:c.trim().toLowerCase(),state:l}}return{ok:!0,revisions:s}}function dur(t){let e=t.map(r=>JSON.stringify(r).slice(1,-1)).filter(r=>!t.includes(r));return[...t,...e].sort((r,n)=>n.length-r.length)}var pur=6e4,mur=["BASH_ENV","ENV"];function wst(t,e,r=process.env){let n=Object.fromEntries(e.filter(i=>r[i]!==void 0).map(i=>[i,r[i]])),s={...xt(r,n),GH_TOKEN:t};for(let i of mur)delete s[i];return s}async function xst(t){let e=t.timeoutMs??pur,r=zR.mkdtempSync(F_.join(Est.tmpdir(),"skyramp-deployed-revisions-"));try{let n=F_.join(r,F_.basename(Va));zR.writeFileSync(n,t.content,{mode:448});let{code:s,stdout:i,timedOut:o}=await new Promise((c,l)=>{let u=(0,bst.spawn)("bash",[n],{cwd:r,env:t.env,stdio:["ignore","pipe","ignore"],detached:!0}),d="",p=!1,m=setTimeout(()=>{p=!0;try{process.kill(-u.pid,"SIGKILL")}catch{u.kill("SIGKILL")}},e);u.stdout.setEncoding("utf-8").on("data",f=>{d+=f}),u.on("error",f=>{clearTimeout(m),l(f)}),u.on("close",f=>{clearTimeout(m),c({code:f,stdout:d,timedOut:p})})}),a=Or(i,dur(t.secrets));return o?{ok:!1,error:`deployed-revisions.sh did not finish within ${Math.round(e/1e3)} s`}:s!==0?{ok:!1,error:`deployed-revisions.sh exited ${s??"on a signal"}: ${a.trim().slice(0,sfe)||"(no output)"}`}:uur(a)}catch(n){return{ok:!1,error:`deployed-revisions.sh could not run: ${n instanceof Error?n.message:String(n)}`}}finally{zR.rmSync(r,{recursive:!0,force:!0})}}var fur=/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/,ofe=t=>fur.test(t);function ry(t){return t?.find(e=>e.path===Va)}var yst=`Fix ${Va} so it prints the full commit each component runs, then set it up again.`;function ej(t,e){let r=new Set(e.filter(s=>s.method!=="not_tracked").flatMap(s=>s.components)),n=new Set(e.filter(s=>s.method==="not_tracked").flatMap(s=>s.components));return t.filter(s=>r.has(s.name)||!n.has(s.name))}async function hur(t){let e=t.components.filter(c=>c.repository!==hn),[r,n]=t.reads;if(!r.ok||!n.ok){let c=e.map(u=>u.name).join(", "),l=[r,n].flatMap((u,d)=>u.ok?[]:[`run ${d+1}: ${u.error}`]);return{deployed:{},blockers:[{component:c||Va,problem:`${Va} could not tell what ${c||"the Test Environment"} runs.`,evidence:l.join("; "),fix:yst}]}}let s={},i=[],o=(c,l,u)=>i.push({component:c,problem:l,evidence:u,fix:yst});for(let{name:c,repository:l}of e){let u=r.revisions[c]?.revision,d=n.revisions[c]?.revision,m=Object.keys((u?n:r).revisions).join(", ")||"nothing";if(!u||!d)o(c,`${Va} prints no revision for ${c}.`,`printed for: ${m}`);else if(u!==d)o(c,`The two runs of ${Va} disagree on what ${c} runs.`,`first run ${u}, second run ${d}`);else if(!ofe(u))o(c,`${Va} prints ${u} for ${c}, which is not a full commit.`,`printed ${u}; expand a short commit in the script, for example with gh api repos/${l}/commits/<short> --jq .sha`);else{let f=await t.resolveCommit(l,u);f==="found"?s[c]=u:f==="missing"?o(c,`${u} is not a commit of ${l}, which builds ${c}: a config repository's commit is not the app's.`,`GitHub has no commit ${u} in ${l}, or this run cannot read ${l}`):o(c,`Testbot could not check that ${l} has ${u}.`,`GitHub did not answer for commit ${u} in ${l}`)}}let a=new Set(t.components.map(c=>c.name));for(let c of[...new Set([...Object.keys(r.revisions),...Object.keys(n.revisions)])].filter(l=>!a.has(l)))o(c,`${Va} prints a revision for ${c}, which is not a component of the Test Environment.`,`components: ${[...a].join(", ")}`);return{deployed:s,blockers:i}}var gur=t=>t.replace(/[.[\]^$*+?(){}|\\]/g,"\\$&");function Aur(t,e){let r=gur(e),n=`[\\s"'/]${r}(?![\\w.-])`,s=[{pattern:new RegExp(`repos/${r}/(?:commits(?:/["']?(?![$<\`{%]|[0-9a-f]{7,64}(?![\\w.-]))[^\\s"'?)|;&>\`]+|(?![/\\w-]))|branches(?![\\w-])|git/(?:matching-)?refs?/heads(?![\\w-]))`,"i"),quote:i=>i.replace(/["']/g,"")},{pattern:new RegExp(`ls-remote\\b.*[/:]${r}(?:\\.git)?(?![\\w.-])`,"i"),quote:()=>`git ls-remote of ${e}`},{pattern:new RegExp(`${n}.*defaultBranchRef|defaultBranchRef.*${n}`,"i"),quote:()=>`the default branch of ${e}`}];return t.replace(/\\\n/g," ").split(`
|
|
315
|
+
`],{silent:!0,env:{NODE_PATH:process.env.NODE_PATH||"",LICENSE_FILE:r,CI:"true"}})}function Q_(t=__dirname){try{let e=$R.readFileSync(cst.join(t,"VERSION.json"),"utf-8"),r=JSON.parse(e);if(typeof r.version=="string"&&typeof r.commit=="string")return{version:r.version,commit:r.commit}}catch{}return{version:"unknown",commit:"unknown"}}async function ust(t){if(!$R.existsSync(t))return{usage:null,model:null,skills:[]};let e=lst.createInterface({input:$R.createReadStream(t),crlfDelay:1/0}),r=null,n=null,s=[];for await(let i of e)if(i.trim())try{let o=JSON.parse(i);if(o.type==="assistant"){let a=o.message?.model;!n&&typeof a=="string"&&(n=a);let c=o.message?.content;if(Array.isArray(c))for(let l of c){if(l?.type!=="tool_use"||l.name!=="Skill")continue;let u=l.input?.skill;typeof u=="string"&&u.length>0&&s.push(u)}}if(o.type==="result"){let a=o.usage??{};r={inputTokens:a.input_tokens??0,outputTokens:a.output_tokens??0,cacheCreationInputTokens:a.cache_creation_input_tokens??0,cacheReadInputTokens:a.cache_read_input_tokens??0,totalCostUsd:o.total_cost_usd??0,numTurns:o.num_turns??0,durationMs:o.duration_ms??0,durationApiMs:o.duration_api_ms??0}}}catch{continue}return{usage:r,model:n,skills:s}}async function dst(t,e,r){for(let n of t){let s={skill:n,agentType:e,...Kme()};vt(`Pushing skill invocation telemetry: ${JSON.stringify(s)}`),await Xme("testbot_skill_invoked",s,r)}}async function pst(t,e,r){let n={inputTokens:String(t.inputTokens),outputTokens:String(t.outputTokens),cacheCreationInputTokens:String(t.cacheCreationInputTokens),cacheReadInputTokens:String(t.cacheReadInputTokens),totalCostUsd:String(t.totalCostUsd),numTurns:String(t.numTurns),durationMs:String(t.durationMs),durationApiMs:String(t.durationApiMs),model:e,...Kme()},s=process.env.GITHUB_REF?.match(/^refs\/pull\/(\d+)\//);s&&(n.prNumber=s[1]);let{totalCostUsd:i,...o}=n;vt(`Pushing agent usage telemetry: ${JSON.stringify(o)}`),await Xme("testbot_agent_usage",n,r)}async function mst(t,e){let{version:r,commit:n}=Q_(),s={testbotVersion:r,testbotCommit:n,actionVersion:process.env.TESTBOT_ACTION_VERSION||"unknown",actionCommit:process.env.TESTBOT_ACTION_COMMIT||"unknown",agentType:t,...Kme()};vt(`Pushing testbot run info telemetry: ${JSON.stringify(s)}`),await Xme("testbot_run_info",s,e)}Je();var is=$(require("fs")),oD=$(require("path"));var bst=require("child_process"),zR=$(require("fs")),Est=$(require("os")),F_=$(require("path"));var zi=["-c","core.fsmonitor=false"],Ylr=new Set(["GH_TOKEN","GITHUB_TOKEN","GH_ENTERPRISE_TOKEN","GITHUB_ENTERPRISE_TOKEN","ACTIONS_RUNTIME_TOKEN","ACTIONS_ID_TOKEN_REQUEST_TOKEN","ACTIONS_ID_TOKEN_REQUEST_URL","SYSTEM_ACCESSTOKEN","SYSTEM_OIDCREQUESTURI","SKYRAMP_MCP_GITHUB_TOKEN","SSH_AUTH_SOCK","GIT_SSH","GIT_SSH_COMMAND","GITHUB_STEP_SUMMARY"]),Zme=new Set(["ANTHROPIC_API_KEY","ANTHROPIC_AUTH_TOKEN","CLAUDE_CODE_OAUTH_TOKEN","AWS_ACCESS_KEY_ID","AWS_SECRET_ACCESS_KEY","AWS_SESSION_TOKEN","AWS_BEARER_TOKEN_BEDROCK","AWS_PROFILE","AWS_WEB_IDENTITY_TOKEN_FILE","AWS_ROLE_ARN","AWS_ROLE_SESSION_NAME","AWS_CONTAINER_CREDENTIALS_FULL_URI","AWS_CONTAINER_CREDENTIALS_RELATIVE_URI","AWS_CONTAINER_AUTHORIZATION_TOKEN","AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE"]);function xt(t=process.env,e,r="repo-command"){let n={};for(let[s,i]of Object.entries(t))r==="repo-command"&&Zme.has(s)||(n[s]=i);Object.assign(n,e??{});for(let s of Object.keys(n))(Ylr.has(s)||s.startsWith("INPUT_"))&&delete n[s];return n}var Fw=(r=>(r.Ready="ready",r.Blocked="blocked",r))(Fw||{}),Ss=A.string().trim().min(1),Jlr=A.object({action:A.enum(["start","run","wait"]),text:Ss,source:A.enum(["file","notes","testbot"]).describe("file: a file in the repository; notes: the Target's notes; testbot: Testbot's own finding"),file:Ss.nullable().describe("the file the step came from, relative to the component's repository; null unless source is file")}).strict().refine(t=>t.source==="file"==(t.file!==null),{path:["file"],message:"a file exactly when the step came from one"}),Klr=/^[A-Za-z_][A-Za-z0-9_]*$/,ty=/^(?!GITHUB_)(?!SKYRAMP_RUNNER_)[A-Z][A-Z0-9_]{0,99}$/,qR=(o=>(o.VersionEndpoint="version_endpoint",o.DeployWorkflow="deploy_workflow",o.KubernetesLabels="kubernetes_labels",o.Command="command",o.Api="api",o.NotTracked="not_tracked",o))(qR||{}),hn="shared",Xlr=Ss.refine(t=>{try{let e=new URL(t);return["http:","https:"].includes(e.protocol)&&!e.username&&!e.password}catch{return!1}},"an http(s) URL with no login in it"),HR=A.object({address:Xlr,readyWhen:Ss.nullable(),credentials:A.array(A.string().regex(ty)).max(50),revisionSources:A.array(A.object({method:A.nativeEnum(qR),detail:Ss.describe("what Testbot reads, such as GET /version or the deploy workflow's name; for not_tracked, why nothing shows it"),components:A.array(Ss).min(1).describe("the components it tells the revision of, by name")}).strict()).max(50)}).strict(),efe={steps:A.array(Jlr).max(20).optional().describe("how it starts, in order"),kind:A.enum(["web_app","service","database","external_service"]).optional(),from:A.object({file:Ss,service:Ss.nullable()}).strict().optional(),credentials:A.array(A.string().max(100).regex(Klr)).max(50).optional().describe("the Target's credentials this component's recipe uses: a sign-in (a test user, password or token) or an API key, which the workflow supplies from a GitHub Actions secret as the environment variable of the credential's name; give each name exactly as the recipe reads it. Names only, never a value, and not a secret the recipe generates itself")},Zlr=A.object({name:Ss,repository:Ss.describe("owner/repo of the Target repository the component is built or started from, such as the one whose compose file starts its database"),healthy:A.boolean(),...efe,deployed:Ss.nullable().optional().describe('only with "environment": the revision the component runs, as you read it from its revision source, or null when you could not tell')}),eur=A.object({component:Ss,problem:Ss,evidence:Ss.describe("the exact command and output that shows it"),fix:Ss.describe("what the user must provide, and where")}),tur=A.object({framework:Ss,file:Ss.describe("the test file run, relative to its repository root"),result:A.enum(["pass","fail","error"])}),rur=A.object({input:Ss.describe("the part of the user's SUT input, as given"),reason:Ss}),L_=A.object({schemaVersion:A.literal(1),status:A.nativeEnum(Fw).describe("ready only if every component came up healthy and the recipe is written"),components:A.array(Zlr),suites:A.array(tur).default([]).describe("one run per test suite the Target's repositories have; empty when they have none"),inputsUsed:A.array(Ss).default([]).describe("the parts of the user's SUT input the recipe used as given"),inputsUsedHow:A.array(A.object({input:Ss.describe("exactly as written in inputsUsed; an entry that is not there is left out"),how:Ss.describe("what the recipe did with it")})).optional(),inputsNotUsed:A.array(rur).default([]).describe("the parts it did not use, and why. A part that is wrong goes here, with what was wrong, even when the recipe does something like it instead: one that names a file, command, port, route or setting that does not exist or does not work (SKYR-5265)"),blockers:A.array(eur).describe("required (non-empty) when status is blocked"),environment:HR.optional().describe("only when the whole SUT is an existing deployment you tested where it runs, building and starting nothing: its address, what says it is ready, the credentials that reach it, and how to read what each component runs. Leave it out when any part was built or started on the runner, or reaching it needed something there (a tunnel, port-forward, proxy or client certificate)")});function tfe(t){let e=nur(t);if(e.length>0)return e;if(t.status==="blocked"&&!(Array.isArray(t.blockers)&&t.blockers.length>0))return['status "blocked" requires at least one blocker { component, problem, evidence, fix } \u2014 say what stopped the bring-up'];if(t.status==="ready"){let r=Array.isArray(t.components)?t.components:[];if(r.length===0)return['status "ready" requires at least one component \u2014 name each component the recipe brought up healthy'];let n=r.filter(s=>s?.healthy!==!0).map(s=>String(s?.name??"?"));if(n.length>0)return[`status "ready" requires every component healthy; not healthy: ${n.join(", ")} \u2014 answer "blocked" with a blocker for each, or bring them up and answer "ready" again`]}return[]}function nur(t){let e=Array.isArray(t.components)?t.components:[],r=t.environment;if(!r){let a=e.filter(c=>c?.deployed!==void 0||c?.repository===hn).map(c=>String(c?.name??"?"));return a.length>0?[`"deployed" and a "${hn}" repository belong only to an answer with "environment"; remove them from: ${a.join(", ")}`]:[]}let n=[],s=e.filter(a=>a?.deployed===void 0).map(a=>String(a?.name??"?"));s.length>0&&n.push(`with "environment", every component says what it runs in "deployed" (null when you could not tell); missing for: ${s.join(", ")}`);let i=new Set(e.map(a=>a?.name)),o=(r.revisionSources??[]).flatMap(a=>a?.components??[]).filter(a=>!i.has(a));return o.length>0&&n.push(`each revision source names components of this answer; not one of them: ${o.map(String).join(", ")}`),n}var $w=64*1024,qw=512*1024,KG=/[\u0000-\u0008\u000b\u000e-\u001f\u007f]|[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/;var xh=$(require("fs")),ZG=$(require("path"));var fst=$(require("fs")),XG=$(require("path"));function mm(t,e,r={}){let n=r.maxDepth??1/0,s=!1,i=(o,a)=>{let c;try{c=fst.readdirSync(o,{withFileTypes:!0})}catch{return}for(let l of c){if(s)return;let u=XG.join(o,l.name);if(l.isDirectory())!r.skip?.has(l.name)&&a<n&&i(u,a+1);else if(e(u,XG.relative(t,u))===!1){s=!0;return}}};i(t,0)}var sur=4,U_="\xABredacted\xBB";function ja(t){let e=new Set;for(let r of t){let n=(r??"").trim();n.length>=sur&&e.add(n)}return[...e].sort((r,n)=>n.length-r.length)}function Or(t,e){if(!t||e.length===0)return t;let r=t;for(let n of e)!n||!r.includes(n)||(r=r.split(n).join(U_));return r}function Pr(t,e){if(e.length===0)return t;let r=n=>typeof n=="string"?Or(n,e):Array.isArray(n)?n.map(r):n&&typeof n=="object"?Object.fromEntries(Object.entries(n).map(([s,i])=>[s,r(i)])):n;return r(t)}function rfe(t,e){return/\.(json|txt|md)$/.test(t)||e&&t.endsWith(".ndjson")}function gst(t,e){let r=[];return mm(t,n=>{try{rfe(ZG.basename(n),e)&&xh.lstatSync(n).isFile()&&r.push(n)}catch{}}),r}function iur(t){return t.length>=2&&(t[0]===255&&t[1]===254||t[0]===254&&t[1]===255)}var our=new TextDecoder("utf-8",{fatal:!0});function aur(t){return t.includes(0)||iur(t)||!Ast(t)}function Ast(t){try{return our.decode(t),!0}catch{return!1}}function cur(t,e){let r=Buffer.from(t,"utf16le"),n=Buffer.from("*".repeat(r.length/2),"utf16le"),s=Buffer.from(t),i=[{needle:s,mask:Buffer.alloc(s.length,"*")},{needle:r,mask:n},{needle:Buffer.from(r).swap16(),mask:Buffer.from(n).swap16()}];return e&&/^[\x00-\xff]*$/.test(t)&&/[\x80-\xff]/.test(t)&&i.push({needle:Buffer.from(t,"latin1"),mask:Buffer.alloc(t.length,"*")}),i}function hst(t,e){let r=Ast(t),n=r&&!t.includes(0),s=!1;for(let i of e)if(i)for(let{needle:o,mask:a}of cur(i,!r))for(let c=t.indexOf(o);c!==-1;c=t.indexOf(o,c+o.length))(n?Buffer.alloc(o.length,"*"):a).copy(t,c),s=!0;return s}function nfe(t,e){if(e.length===0)return;let r;try{r=xh.readdirSync(t,{withFileTypes:!0})}catch{return}for(let n of r){let s=ZG.join(t,n.name);if(n.isDirectory()){nfe(s,e);continue}if(n.isFile())try{let i=xh.readFileSync(s);if(aur(i)){rfe(n.name,!0)&&hst(i,e)&&xh.writeFileSync(s,i);continue}let o=i.toString("utf-8"),a=Or(o,e),c=a===o?i:Buffer.from(a),l=rfe(n.name,!0)&&hst(c,e);(a!==o||l)&&xh.writeFileSync(s,c)}catch{}}}var Va=".skyramp/sut/deployed-revisions.sh",ife=(r=>(r.Running="running",r.Requested="requested",r))(ife||{}),sfe=200,lur=new Set(Object.values(ife));function uur(t){let e=t.trim();if(!e)return{ok:!1,error:"deployed-revisions.sh printed nothing"};let r={ok:!1,error:`deployed-revisions.sh printed not one JSON object of component to commit: ${e.slice(0,sfe)}`},n;try{n=JSON.parse(e)}catch{return r}if(n===null||typeof n!="object"||Array.isArray(n))return r;let s={};for(let[i,o]of Object.entries(n)){let a=typeof o=="string"?{revision:o}:o,{revision:c,state:l="running",...u}=a??{};if(!(typeof a=="object"&&a!==null&&!Array.isArray(a)&&Object.keys(u).length===0&&typeof c=="string"&&c.trim()!==""&&typeof l=="string"&&lur.has(l)))return{ok:!1,error:`deployed-revisions.sh printed ${JSON.stringify(o)?.slice(0,sfe)} for ${i}: give a commit, or {"revision": "<commit>", "state": "running" or "requested"}`};s[i]={revision:c.trim().toLowerCase(),state:l}}return{ok:!0,revisions:s}}function dur(t){let e=t.map(r=>JSON.stringify(r).slice(1,-1)).filter(r=>!t.includes(r));return[...t,...e].sort((r,n)=>n.length-r.length)}var pur=6e4,mur=["BASH_ENV","ENV"];function wst(t,e,r=process.env){let n=Object.fromEntries(e.filter(i=>r[i]!==void 0).map(i=>[i,r[i]])),s={...xt(r,n),GH_TOKEN:t};for(let i of mur)delete s[i];return s}async function xst(t){let e=t.timeoutMs??pur,r=zR.mkdtempSync(F_.join(Est.tmpdir(),"skyramp-deployed-revisions-"));try{let n=F_.join(r,F_.basename(Va));zR.writeFileSync(n,t.content,{mode:448});let{code:s,stdout:i,timedOut:o}=await new Promise((c,l)=>{let u=(0,bst.spawn)("bash",[n],{cwd:r,env:t.env,stdio:["ignore","pipe","ignore"],detached:!0}),d="",p=!1,m=setTimeout(()=>{p=!0;try{process.kill(-u.pid,"SIGKILL")}catch{u.kill("SIGKILL")}},e);u.stdout.setEncoding("utf-8").on("data",f=>{d+=f}),u.on("error",f=>{clearTimeout(m),l(f)}),u.on("close",f=>{clearTimeout(m),c({code:f,stdout:d,timedOut:p})})}),a=Or(i,dur(t.secrets));return o?{ok:!1,error:`deployed-revisions.sh did not finish within ${Math.round(e/1e3)} s`}:s!==0?{ok:!1,error:`deployed-revisions.sh exited ${s??"on a signal"}: ${a.trim().slice(0,sfe)||"(no output)"}`}:uur(a)}catch(n){return{ok:!1,error:`deployed-revisions.sh could not run: ${n instanceof Error?n.message:String(n)}`}}finally{zR.rmSync(r,{recursive:!0,force:!0})}}var fur=/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/,ofe=t=>fur.test(t);function ry(t){return t?.find(e=>e.path===Va)}var yst=`Fix ${Va} so it prints the full commit each component runs, then set it up again.`;function ej(t,e){let r=new Set(e.filter(s=>s.method!=="not_tracked").flatMap(s=>s.components)),n=new Set(e.filter(s=>s.method==="not_tracked").flatMap(s=>s.components));return t.filter(s=>r.has(s.name)||!n.has(s.name))}async function hur(t){let e=t.components.filter(c=>c.repository!==hn),[r,n]=t.reads;if(!r.ok||!n.ok){let c=e.map(u=>u.name).join(", "),l=[r,n].flatMap((u,d)=>u.ok?[]:[`run ${d+1}: ${u.error}`]);return{deployed:{},blockers:[{component:c||Va,problem:`${Va} could not tell what ${c||"the Test Environment"} runs.`,evidence:l.join("; "),fix:yst}]}}let s={},i=[],o=(c,l,u)=>i.push({component:c,problem:l,evidence:u,fix:yst});for(let{name:c,repository:l}of e){let u=r.revisions[c]?.revision,d=n.revisions[c]?.revision,m=Object.keys((u?n:r).revisions).join(", ")||"nothing";if(!u||!d)o(c,`${Va} prints no revision for ${c}.`,`printed for: ${m}`);else if(u!==d)o(c,`The two runs of ${Va} disagree on what ${c} runs.`,`first run ${u}, second run ${d}`);else if(!ofe(u))o(c,`${Va} prints ${u} for ${c}, which is not a full commit.`,`printed ${u}; expand a short commit in the script, for example with gh api repos/${l}/commits/<short> --jq .sha`);else{let f=await t.resolveCommit(l,u);f==="found"?s[c]=u:f==="missing"?o(c,`${u} is not a commit of ${l}, which builds ${c}: a config repository's commit is not the app's.`,`GitHub has no commit ${u} in ${l}, or this run cannot read ${l}`):o(c,`Testbot could not check that ${l} has ${u}.`,`GitHub did not answer for commit ${u} in ${l}`)}}let a=new Set(t.components.map(c=>c.name));for(let c of[...new Set([...Object.keys(r.revisions),...Object.keys(n.revisions)])].filter(l=>!a.has(l)))o(c,`${Va} prints a revision for ${c}, which is not a component of the Test Environment.`,`components: ${[...a].join(", ")}`);return{deployed:s,blockers:i}}var gur=t=>t.replace(/[.[\]^$*+?(){}|\\]/g,"\\$&");function Aur(t,e){let r=gur(e),n=`[\\s"'/]${r}(?![\\w.-])`,s=[{pattern:new RegExp(`repos/${r}/(?:commits(?:/["']?(?![$<\`{%]|[0-9a-f]{7,64}(?![\\w.-]))[^\\s"'?)|;&>\`]+|(?![/\\w-]))|branches(?![\\w-])|git/(?:matching-)?refs?/heads(?![\\w-]))`,"i"),quote:i=>i.replace(/["']/g,"")},{pattern:new RegExp(`ls-remote\\b.*[/:]${r}(?:\\.git)?(?![\\w.-])`,"i"),quote:()=>`git ls-remote of ${e}`},{pattern:new RegExp(`${n}.*defaultBranchRef|defaultBranchRef.*${n}`,"i"),quote:()=>`the default branch of ${e}`}];return t.replace(/\\\n/g," ").split(`
|
|
316
316
|
`).map(i=>i.replace(/(^|\s)#.*$/,"").trim()).filter(Boolean).flatMap(i=>s.flatMap(({pattern:o,quote:a})=>{let c=o.exec(i)?.[0];return c?[a(c)]:[]}))}var yur=(t,e)=>t.every(r=>r.ok&&r.revisions[e]?.state==="requested");async function Sst(t){let e=await t.read(t.content),r=await t.read(t.content),n=await hur({reads:[e,r],components:t.components,resolveCommit:t.resolveCommit}),s={...n.deployed},i=[...n.blockers];for(let{name:o,repository:a}of t.components.filter(c=>c.repository!==hn)){let c=Aur(t.content,a);!c.length||yur([e,r],o)||(delete s[o],i.push({component:o,problem:`${Va} reads a branch of ${a}, which builds ${o}: that tells what the repository holds, not what the Test Environment runs.`,evidence:[...new Set(c)].join("; "),fix:`Read what ${o} runs from the Test Environment itself, such as a version route or a platform API, not from a branch of ${a}, then set it up again.`}))}return{deployed:s,blockers:i}}var W_=require("node:crypto");var Hw=(i=>(i.NotFound="not_found",i.NoAccess="no_access",i.Unavailable="unavailable",i.AccessLost="access_lost",i.CannotOpen="cannot_open",i))(Hw||{}),ny=t=>t.source==="other"?{source:t.source,key:t.key}:{source:t.source,site:t.site,key:t.key},$c=t=>`${t.source}:${t.site??""}:${t.key}`;var $_=/^pasted-[0-9a-f]{16}$/,zw=/^file-[0-9a-f]{16}$/,jR=(s=>(s.Pdf="application/pdf",s.Csv="text/csv",s.Docx="application/vnd.openxmlformats-officedocument.wordprocessingml.document",s.Xlsx="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",s))(jR||{}),Gi=t=>t.trim().toUpperCase(),GR=(t,e)=>{let r=t.trim();return $_.test(r.toLowerCase())?"Pasted text":zw.test(r.toLowerCase())?e?.get(Gi(r))??"Uploaded document":/^\d+$/.test(r)?`Confluence page ${r}`:r.toUpperCase()},sd=(t,e,r)=>e?`${GR(t,r)} AC${e}`:GR(t,r),VR=(r=>(r.Sufficient="sufficient",r.Insufficient="insufficient",r))(VR||{}),WR=(p=>(p.RelatedChangeConflict="related_change_conflict",p.ReferenceOutsideTarget="reference_outside_target",p.SourceAccessLost="source_access_lost",p.ConfigurationIncomplete="configuration_incomplete",p.CredentialUnavailable="credential_unavailable",p.WorkflowBindingUnverified="workflow_binding_unverified",p.WorkflowRefUnprotected="workflow_ref_unprotected",p.BedrockWorkflowRequired="bedrock_workflow_required",p.DeploymentReview="deployment_review",p.SutRevisionMismatch="sut_revision_mismatch",p.IsolationUnavailable="isolation_unavailable",p.EvidenceInsufficient="evidence_insufficient",p))(WR||{}),sy=(i=>(i.CiCheck="ci_check",i.TestReport="test_report",i.StaticAnalysis="static_analysis",i.CodeReview="code_review",i.Other="other",i))(sy||{}),q_=(n=>(n.Application="application",n.Environment="environment",n.TestDefect="test_defect",n))(q_||{}),tj=(a=>(a.AuthToken="auth_token",a.Secret="secret",a.DataConnection="data_connection",a.AppNotStarted="app_not_started",a.Login="login",a.ExternalDependency="external_dependency",a.RecordingReplay="recording_replay",a))(tj||{});var rj=(r=>(r.Probe="probe",r.Agent="agent",r))(rj||{}),nj=(n=>(n.Seed="seed",n.RuntimeMock="runtime_mock",n.EnvOverride="env_override",n))(nj||{}),sj=(n=>(n.TestCode="test_code",n.SkyrampMocker="skyramp_mocker",n.RecipeOverride="recipe_override",n))(sj||{});var Cst=$(require("path"));var id=(s=>(s.Clean="clean",s.RecipeDrift="recipe-drift",s.ChangeBrokeBringup="change-broke-bringup",s.BringupFailed="bringup-failed",s))(id||{}),iy=A.string().trim().min(1),bur=A.object({url:A.string().optional().describe("the URL probed, if the check is an HTTP request"),command:A.string().optional().describe("the command run, if the check is a script \u2014 preferred when auth is needed"),headers:A.record(A.string()).optional(),status:A.union([A.number(),A.string()]).optional().describe("expected HTTP status for a url check (default 200)"),output:iy.describe("what the check returned, VERBATIM \u2014 the bytes, nothing appended; this gate RE-RUNS the check")}),H_=A.object({schemaVersion:A.literal(1),baseUrls:A.record(A.string()).optional().describe("service \u2192 base URL of the running app; omit on a failed bring-up, never invent one"),health:bur.optional(),teardown:iy.describe("command that tears the SUT down"),dependencies:A.record(A.boolean()).optional().describe("EVERY backing service: true = really deployed, false = mocked; REQUIRED when the app came up"),outcome:A.nativeEnum(id).describe("clean | recipe-drift (you corrected the recipe) | change-broke-bringup (the PR broke it) | bringup-failed (environment, not the PR)"),recipeFixes:A.array(A.string()).optional().describe("required when outcome is recipe-drift: what was corrected"),bringupError:A.object({command:iy,output:iy}).optional().describe("required when bring-up failed: the exact failing command and what it printed"),suspectFiles:A.array(A.string()).optional().describe("required for change-broke-bringup: files THIS PR changed that the failure points at, relative to your working directory (a file in another checkout of the run: its absolute path)"),seeded:A.boolean(),startedAt:iy.describe("ISO timestamp"),revisions:A.array(A.object({component:iy.describe("a shared component listed above"),url:iy.describe("the URL on the component's own origin (scheme, host and port) that answers with its deployed commit"),output:iy.describe("what the URL returned, VERBATIM; this gate FETCHES each url")})).optional().describe("only when shared components are listed above: one check per component whose deployed commit you found")});function afe(t){let e=[],r=s=>!!s&&typeof s=="object"&&!Array.isArray(s);return t.outcome!=="change-broke-bringup"&&t.outcome!=="bringup-failed"?(!r(t.baseUrls)||Object.keys(t.baseUrls).length===0?e.push("baseUrls must be a non-empty object of service \u2192 url"):Object.values(t.baseUrls).every(s=>typeof s=="string"&&s.trim())||e.push("every baseUrls value must be a non-empty url string"),r(t.health)?!(typeof t.health.url=="string"&&t.health.url.trim())&&!(typeof t.health.command=="string"&&t.health.command.trim())&&e.push("health must record a url or a command (what you actually ran)"):e.push("health must be an object"),(!r(t.dependencies)||Object.keys(t.dependencies).length===0)&&e.push("dependencies must be a non-empty object of name \u2192 boolean (real vs mocked) when the SUT came up \u2014 account for every backing service so a mocked one can't be hidden behind a green run")):(r(t.bringupError)||e.push(`outcome "${String(t.outcome)}" requires bringupError { command, output } \u2014 the exact failing bring-up command and what it printed`),t.outcome==="change-broke-bringup"&&!(Array.isArray(t.suspectFiles)&&t.suspectFiles.length>0&&t.suspectFiles.every(s=>typeof s=="string"&&s.trim()))&&e.push("change-broke-bringup requires non-empty suspectFiles[] \u2014 the changed file(s) the failure points at; if you cannot point at a file this PR changed, the outcome is bringup-failed (environment/harness), not the PR's fault")),t.outcome==="recipe-drift"&&!(Array.isArray(t.recipeFixes)&&t.recipeFixes.length>0)&&e.push("recipe-drift outcome requires a non-empty recipeFixes[] describing what was corrected"),e}var Bo=A.string().trim().min(1),cfe=(r=>(r.TestbotManaged="testbot_managed",r.CustomerDeployed="customer_deployed",r))(cfe||{}),YR=[".skyramp/sut/setup.sh",".skyramp/sut/teardown.sh"],ij=A.object({schemaVersion:A.literal(1),type:A.nativeEnum(cfe).optional(),status:A.enum(["ready","blocked"]),environment:HR.optional(),components:A.array(A.object({name:Bo,repository:Bo.describe(`owner/repo, or "${hn}" for a customer-deployed component no repository builds`),healthy:A.boolean(),url:A.string().min(1).nullable(),...efe,credentials:A.array(A.string().regex(ty)).max(50).optional(),deployed:Bo.nullable().optional().describe("customer_deployed only: the revision the component runs, as setup read it (WF-SETUP-22's Deployed now), or null when it could not tell")}).strict()),suites:A.array(A.object({framework:Bo,file:Bo,result:A.enum(["pass","fail","error"])}).strict()),inputsUsed:A.array(Bo),inputsUsedHow:A.array(A.object({input:Bo,how:Bo}).strict()).optional(),inputsNotUsed:A.array(A.object({input:Bo,reason:Bo}).strict()),blockers:A.array(A.object({component:Bo,problem:Bo,evidence:Bo,fix:Bo}).strict()),replay:A.object({outcome:Bo,durationSeconds:A.number().nonnegative().nullable()}).strict().nullable(),files:A.array(A.object({path:Bo,content:A.string()}).strict())}).strict().superRefine((t,e)=>{t.inputsUsedHow?.forEach((s,i)=>{t.inputsUsed.includes(s.input)||e.addIssue({code:A.ZodIssueCode.custom,path:["inputsUsedHow",i,"input"],message:"not one of inputsUsed"})}),t.components.forEach((s,i)=>{s.credentials&&new Set(s.credentials).size!==s.credentials.length&&e.addIssue({code:A.ZodIssueCode.custom,path:["components",i,"credentials"],message:"names a credential twice"})});let r=t.type==="customer_deployed";r!==(t.environment!==void 0)&&e.addIssue({code:A.ZodIssueCode.custom,path:["environment"],message:"an environment exactly when the plan's type is customer_deployed"}),t.environment&&new Set(t.environment.credentials).size!==t.environment.credentials.length&&e.addIssue({code:A.ZodIssueCode.custom,path:["environment","credentials"],message:"names a credential twice"});let n=new Set(t.components.map(s=>s.name));if(t.environment?.revisionSources.forEach((s,i)=>{s.components.forEach((o,a)=>{n.has(o)||e.addIssue({code:A.ZodIssueCode.custom,path:["environment","revisionSources",i,"components",a],message:"not one of components"})})}),t.components.forEach((s,i)=>{s.deployed!==void 0!==r&&e.addIssue({code:A.ZodIssueCode.custom,path:["components",i,"deployed"],message:"what a component runs exactly when the plan is customer_deployed"}),s.repository===hn&&!r&&e.addIssue({code:A.ZodIssueCode.custom,path:["components",i,"repository"],message:`only a customer_deployed component is ${hn}`})}),r&&(t.components.forEach((s,i)=>{t.components.findIndex(o=>o.name===s.name)!==i&&e.addIssue({code:A.ZodIssueCode.custom,path:["components",i,"name"],message:"names a component twice"})}),t.replay!==null&&e.addIssue({code:A.ZodIssueCode.custom,path:["replay"],message:"a customer_deployed plan is never replayed"}),t.files.forEach((s,i)=>{YR.includes(s.path)&&e.addIssue({code:A.ZodIssueCode.custom,path:["files",i,"path"],message:"a customer_deployed plan has no script that starts or stops the system"})})),r&&t.status==="ready"){let s=new Set(t.environment?.revisionSources.flatMap(i=>i.components));t.components.forEach((i,o)=>{i.repository!==hn&&!s.has(i.name)&&e.addIssue({code:A.ZodIssueCode.custom,path:["components",o,"name"],message:"a ready customer_deployed plan reads what every component a repository builds runs"})})}});var Eur=["clean","recipe-drift"];function oy(t){let e=Cst.posix.normalize(t);return e===t&&(e===".skyramp/workspace.yml"||e.startsWith(".skyramp/sut/")&&e.length>13)}function wur(t,e,r){let n=e.bringupError;if(t.environment)return{component:t.components.map(i=>i.name).join(", ")||"SUT",problem:`onboard reached the deployment, but deploy's fresh check of it ended ${e.outcome}`,evidence:n?`$ ${n.command}
|
|
317
317
|
${n.output}`:`deploy reported ${e.outcome} without the failing command`,fix:"make .skyramp/sut/health.json's check pass from a fresh session (its address, sign-in and ready route), then set it up again"};let s=e.suspectFiles?.length?` \u2014 it points at ${e.suspectFiles.join(", ")}`:"";return{component:t.components.map(i=>i.name).join(", ")||"SUT",problem:`the recipe worked once but not from nothing: deploy's fresh replay of .skyramp/sut/setup.sh ended ${e.outcome}`,evidence:n?`$ ${n.command}
|
|
318
318
|
${n.output}`:`deploy reported ${e.outcome} without the failing command`,fix:e.outcome==="change-broke-bringup"?`fix the change that broke bring-up${s}, then onboard again`:r.length>0?xur(r):"make .skyramp/sut/setup.sh bring the SUT up from a clean checkout (what onboard's session had that a fresh run lacks), then onboard again"}}var z_="a GitHub Actions secret, or on Azure DevOps a secret variable in the host repository's skyramp-testbot variable group under Pipelines \u203A Library";function xur(t){let e=t.length===1;return`add ${e?"a secret":"secrets"} named ${t.join(", ")} on the workflow host, ${e?"as":"each as"} ${z_}, then Retry: Skyramp adds ${e?"it":"them"} to the Target's credentials, and the replay gets ${e?"its value":"their values"}. If it still fails, make .skyramp/sut/setup.sh bring the SUT up from a clean checkout`}function Sur(t){return t.environment?{component:t.components.map(e=>e.name).join(", ")||"SUT",problem:"the deployment was never checked from a fresh session: deploy did not run or wrote no deploy.json",evidence:"outputs/deploy.json is missing from this run",fix:"run deploy after onboard in the same run so it checks the deployment is ready; if deploy ran, its stage log says why it wrote no deploy.json"}:{component:t.components.map(e=>e.name).join(", ")||"SUT",problem:"the recipe was never replayed: deploy did not run or wrote no deploy.json",evidence:"outputs/deploy.json is missing from this run",fix:"run deploy after onboard in the same run so it replays .skyramp/sut/setup.sh from a clean start; if deploy ran, its stage log says why it wrote no deploy.json"}}function Cur(t){return{component:"SUT recipe",problem:"deploy fixed recipe drift, but the recipe it fixed was not collected \u2014 onboard's copy is the recipe from before the fix",evidence:`deploy reported recipe-drift: ${(t.recipeFixes??[]).join("; ")||"no fixes listed"}`,fix:"collect .skyramp/sut/ and .skyramp/workspace.yml after deploy (collectRecipeFiles) and build the plan from that collection"}}var lfe=[".skyramp/sut/setup.sh",".skyramp/sut/teardown.sh",".skyramp/sut/health.json",".skyramp/workspace.yml"],ufe=[".skyramp/sut/health.json",".skyramp/workspace.yml"];function Iur(t){return`Onboard again so it leaves the whole recipe in the checkout: ${t.map(r=>r===".skyramp/workspace.yml"?"create .skyramp/workspace.yml with skyramp_init_scan and skyramp_init_workspace":`write ${r} as a regular file (a symlink is not uploaded)`).join("; ")}.`}function dfe(t,e,r){return e&&r||t}function pfe(t,e,r={}){let n=e!==void 0&&!Eur.includes(e.outcome),s=dfe(t,e,r.recipeFiles),i=e?.outcome==="recipe-drift"&&!r.recipeFiles,o=(s.recipeFilesOmitted??[]).filter(S=>oy(S.path)),a=o.length?[{component:"SUT recipe",problem:`The recipe could not be uploaded whole: ${o.map(S=>S.path).join(", ")} left out.`,evidence:o.map(S=>`${S.path}: ${S.reason}`).join("; "),fix:"Keep each recipe file text-only and under 64 KiB, and the recipe under 512 KiB in total \u2014 move large generated content out of .skyramp/sut/ and have setup.sh produce it."}]:[],c=t.status==="ready",l=t.environment,u=new Set(s.recipeFiles.map(S=>S.path)),d=c?(l?ufe:lfe).filter(S=>!u.has(S)):[],p=d.length?[{component:"SUT recipe",problem:`The recipe is missing ${d.join(", ")}, so it cannot be replayed.`,evidence:`collected: ${[...u].sort().join(", ")||"nothing"}`,fix:Iur(d)}]:[],m=e?n?[wur(t,e,r.unsupplied??[])]:[]:c?[Sur(t)]:[],f=new Set(l?.revisionSources.flatMap(S=>S.components)),h=l&&c?t.components.filter(S=>S.repository!==hn&&!f.has(S.name)).map(S=>S.name):[],b=h.length?[{component:h.join(", "),problem:`Testbot cannot tell which revision ${h.join(" and ")} ${h.length>1?"run":"runs"}, so a run could not say which changes it tested.`,evidence:`revision sources: ${l.revisionSources.map(S=>`${S.detail} (${S.components.join(", ")})`).join("; ")||"none"}`,fix:"Say in the Test Environment's notes how to tell what is deployed, such as a version route (GET /version), then set it up again."}]:[],E=(c?l?.revisionSources??[]:[]).filter(S=>S.method==="not_tracked").map(S=>({component:S.components.join(", "),problem:`Testbot can't tell which commit ${S.components.join(" and ")} ${S.components.length>1?"run":"runs"}, so a run can't say which of ${S.components.length>1?"their":"its"} changes it tested.`,evidence:S.detail,fix:`Say in the Test Environment's notes how to tell what ${S.components.join(" and ")} ${S.components.length>1?"run":"runs"}, such as a version route (GET /version) or a deploy workflow, then set it up again.`})),y=[...t.blockers,...m,...i&&e?[Cur(e)]:[],...a,...p,...b,...E,...r.revisionCheck?.blockers??[],...r.credentialBlockers??[]],C=e?.baseUrls??{};return{schemaVersion:1,...l?{type:"customer_deployed",environment:Ist(l)}:{},status:c&&e!==void 0&&!n&&!i&&o.length===0&&d.length===0&&h.length===0&&!r.credentialBlockers?.length?"ready":"blocked",components:t.components.map(S=>({name:S.name,repository:S.repository,healthy:S.healthy&&!n,url:Object.hasOwn(C,S.name)&&C[S.name]||null,...S.steps?{steps:S.steps.map(R=>({action:R.action,text:R.text,source:R.source,file:R.file}))}:{},...S.kind?{kind:S.kind}:{},...S.from?{from:{file:S.from.file,service:S.from.service}}:{},...S.credentials?{credentials:[...new Set(S.credentials)].filter(R=>ty.test(R))}:{},...l?{deployed:Rur(S,r)}:{}})),suites:t.suites.map(S=>({framework:S.framework,file:S.file,result:S.result})),inputsUsed:[...t.inputsUsed],...t.inputsUsedHow?{inputsUsedHow:t.inputsUsedHow.filter(S=>t.inputsUsed.includes(S.input)).map(S=>({input:S.input,how:S.how}))}:{},inputsNotUsed:t.inputsNotUsed.map(S=>({input:S.input,reason:S.reason})),blockers:y.map(S=>({component:S.component,problem:S.problem,evidence:S.evidence,fix:S.fix})),replay:e&&!l?{outcome:e.outcome,durationSeconds:r.deployDurationSeconds??null}:null,files:s.recipeFiles.filter(S=>oy(S.path)&&!(l&&YR.includes(S.path))).map(S=>({path:S.path,content:S.content}))}}function Rur(t,e){return t.repository===hn?null:e.revisionCheck?e.revisionCheck.deployed[t.name]??null:e.deployed?.[t.name]??t.deployed??null}function Ist(t){return{address:t.address,readyWhen:t.readyWhen,credentials:[...new Set(t.credentials)],revisionSources:t.revisionSources.map(e=>({method:e.method,detail:e.detail,components:[...e.components]}))}}function Rst(t){return{schemaVersion:1,status:"ready",components:t.components.map(({url:e,deployed:r,...n})=>({...n,healthy:!0})),suites:t.suites,inputsUsed:t.inputsUsed,...t.inputsUsedHow?{inputsUsedHow:t.inputsUsedHow}:{},inputsNotUsed:t.inputsNotUsed,blockers:[],recipeFiles:t.files,recipeFilesOmitted:[],...t.environment?{environment:Ist(t.environment)}:{}}}var Ust={handshake:"/api/v1/runner/runs/:runId/attempts",report:"/api/v1/runner/attempts/:attemptId/report",end:"/api/v1/runner/attempts/:attemptId/end",configuration:"/api/v1/runner/attempts/:attemptId/configuration",progress:"/api/v1/runner/attempts/:attemptId/progress",sutRecipe:"/api/v1/runner/attempts/:attemptId/sut-recipe",runtimePlan:"/api/v1/runner/attempts/:attemptId/runtime-plan",heartbeat:"/api/v1/runner/attempts/:attemptId/heartbeat",setupEvents:"/api/v1/runner/attempts/:attemptId/setup-events",requirements:"/api/v1/runner/attempts/:attemptId/requirements",requirementFile:"/api/v1/runner/attempts/:attemptId/requirements/:key/file",deployedRevisions:"/api/v1/runner/attempts/:attemptId/deployed-revisions"},cj=(r=>(r.GitHub="github",r.AzureDevOps="azure-devops",r))(cj||{}),ffe=(i=>(i.Trigger="TRIGGER",i.Baseline="BASELINE",i.AuthoritativeRelatedChange="AUTHORITATIVE_RELATED_CHANGE",i.ConfiguredSharedDependency="CONFIGURED_SHARED_DEPENDENCY",i.ManualOverride="MANUAL_OVERRIDE",i))(ffe||{}),lj=(o=>(o.PullRequest="pull_request",o.ReadyForReview="ready_for_review",o.Schedule="schedule",o.Manual="manual",o.SutSetup="sut_setup",o.RevisionRead="revision_read",o))(lj||{}),vur=["sut_setup"],Tur=["sut_setup","schedule","revision_read"],Nur=["sut_setup"],uj=(e=>(e.PullRequest="pull_request",e))(uj||{}),Y_=(s=>(s.ModelProvider="model_provider",s.SutAccess="sut_access",s.Skyramp="skyramp",s.Other="other",s))(Y_||{});var dj=(i=>(i.ReportUploaded="report_uploaded",i.NoReport="no_report",i.Cancelled="cancelled",i.RecipeUploaded="recipe_uploaded",i.RevisionsUploaded="revisions_uploaded",i))(dj||{}),Fst=(r=>(r.Running="running",r.Ended="ended",r))(Fst||{}),pj=(a=>(a.TimedOut="timed_out",a.AgentFailed="agent_failed",a.SetupFailed="setup_failed",a.CredentialMissing="credential_missing",a.RunnerLost="runner_lost",a.CheckoutFailed="checkout_failed",a.EnvironmentUnverified="environment_unverified",a))(pj||{}),hfe=(n=>(n.Onboard="onboard",n.Deploy="deploy",n.Recheck="recheck",n))(hfe||{}),gfe=(r=>(r.Started="started",r.Finished="finished",r))(gfe||{}),Afe=(n=>(n.Passed="passed",n.Failed="failed",n.Skipped="skipped",n))(Afe||{}),mj=(o=>(o.Log="log",o.Component="component",o.Reply="reply",o.Question="question",o.Delivered="delivered",o.Credentials="credentials",o))(mj||{}),fj=(n=>(n.ChooseCredential="choose_credential",n.FreeText="free_text",n.ChooseSource="choose_source",n))(fj||{});var XR=(n=>(n.Discover="discover",n.Start="start",n.Check="check",n))(XR||{}),J_=(s=>(s.Found="found",s.Starting="starting",s.Up="up",s.OutOfScope="out_of_scope",s))(J_||{}),od=A.string().regex(/^[1-9][0-9]{0,18}$/),Gw=A.string().regex(/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/),ZR=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,Aa=A.string().regex(ZR),ya=A.string().regex(/^[0-9a-f]{64}$/),aj=A.object({id:Aa,revision:od}).strict(),hj={provider:A.literal("github"),providerAccountId:od,providerRepositoryId:od},gj={provider:A.literal("azure-devops"),providerAccountId:Aa,providerRepositoryId:Aa},Sh=A.discriminatedUnion("provider",[A.object(hj).strict(),A.object(gj).strict()]),yfe=A.discriminatedUnion("provider",[A.object(hj).passthrough(),A.object(gj).passthrough()]),En=t=>`${t.provider}:${t.providerRepositoryId}`,vst={namespace:A.string().min(1),name:A.string().min(1)},$st=A.discriminatedUnion("provider",[A.object({...hj,...vst}).strict(),A.object({...gj,...vst}).strict()]),Tst=A.object({ref:A.string().min(1),revision:Gw}).strict(),kur=A.object({repository:$st,revision:Gw,ref:A.string().nullable(),reason:A.nativeEnum(ffe),change:A.object({kind:A.nativeEnum(uj),number:od,base:Tst.optional()}).strict().nullable(),compareWith:Tst.optional()}).strict();function Pur(t){if(t.trigger.kind==="revision_read"!=!t.baseline)return t.baseline?"a revision_read run names no baseline":"names its baseline";if(!t.baseline)return null;let{confirmedRunId:e,initialBaselineId:r,sources:n}=t.baseline;if(e!==null&&r!==void 0)return"a run compares with one baseline";let s=t.trigger.kind==="schedule"&&e!==null||r!==void 0;if(!n)return s?"names every source of the snapshot, once":null;if(e===null&&r===void 0)return"a run with no Confirmed Baseline names no baseline sources";let i=new Set(t.sources.map(c=>En(c.repository))),o=new Set(n.map(c=>En(c.repository)));return o.size===n.length&&o.size===i.size&&[...o].every(c=>i.has(c))?null:"names every source of the snapshot, once"}var qst=/^[A-Z][A-Z0-9_]+-[1-9][0-9]*$/,Hst=/^[1-9][0-9]{0,18}$/,Nst=new RegExp(`${qst.source}|${Hst.source}`),kst=/^https:\/\/[a-z0-9-]+(\.[a-z0-9-]+)+$/,Bur=A.union([A.object({source:A.literal("jira"),site:A.string().regex(kst),key:A.string().regex(qst),updated:A.string().datetime(),contentHash:ya}).strict(),A.object({source:A.literal("confluence"),site:A.string().regex(kst),key:A.string().regex(Hst),version:A.number().int().positive(),contentHash:ya}).strict(),A.object({source:A.literal("other"),key:A.string().regex($_),contentHash:ya}).strict(),A.object({source:A.literal("other"),key:A.string().regex(zw),contentHash:ya,fileName:A.string().min(1).max(255),mediaType:A.nativeEnum(jR)}).strict()]),Pst=A.union([A.literal("jira"),A.literal("confluence")]),zst=A.object({schemaVersion:A.literal(1),items:A.array(A.union([A.object({source:Pst,site:A.string(),key:A.string().regex(Nst),state:A.literal("read"),text:A.string(),version:A.string().min(1),contentMatches:A.boolean()}).strict(),A.object({source:Pst,site:A.string(),key:A.string().regex(Nst),state:A.literal("not_read"),reason:A.nativeEnum(Hw)}).strict(),A.object({source:A.literal("other"),key:A.string().regex($_),state:A.literal("read"),text:A.string(),version:A.string().min(1),contentMatches:A.boolean()}).strict(),A.object({source:A.literal("other"),key:A.string().regex($_),state:A.literal("not_read"),reason:A.nativeEnum(Hw)}).strict(),A.object({source:A.literal("other"),key:A.string().regex(zw),state:A.literal("read"),version:ya,fileName:A.string().min(1).max(255),mediaType:A.nativeEnum(jR)}).strict(),A.object({source:A.literal("other"),key:A.string().regex(zw),state:A.literal("not_read"),reason:A.nativeEnum(Hw)}).strict()])),instructions:A.array(A.string()).optional()}).strict(),_ur=A.object({schemaVersion:A.literal(1),runId:Aa,skyrampOrgId:Aa,target:aj,policy:aj.nullable(),trigger:A.object({kind:A.nativeEnum(lj),requestedAt:A.string().datetime()}).strict(),configuration:A.object({captureId:Aa,revision:od.nullable(),hash:ya}).strict(),baseline:A.object({confirmedRunId:Aa.nullable(),initialBaselineId:Aa.optional(),sources:A.array(A.object({repository:Sh,revision:Gw}).strict()).optional()}).strict().optional(),sources:A.array(kur).min(1),execution:A.object({provider:A.nativeEnum(cj),repository:$st,workflowPath:A.string(),ref:A.string()}).strict(),credentials:A.array(A.object({name:A.string(),purpose:A.nativeEnum(Y_),required:A.boolean()}).strict()),requirements:A.array(Bur).min(1).optional(),instructions:A.object({contentHash:ya}).strict().optional(),deployedRevisions:A.array(A.object({repository:Sh,revision:Gw}).strict()).min(1).optional()}).strict().superRefine((t,e)=>{let r=new Set;(t.requirements??[]).forEach((c,l)=>{let u="fileName"in c?"file":"pasted";c.source==="other"&&c.key!==`${u}-${c.contentHash.slice(0,16)}`&&e.addIssue({code:A.ZodIssueCode.custom,message:"is not the key of its content hash",path:["requirements",l,"key"]});let d=$c({source:c.source,key:c.key});r.has(d)&&e.addIssue({code:A.ZodIssueCode.custom,message:"names an item that the run already names",path:["requirements",l,"key"]}),r.add(d)});let n=t.trigger.kind,s=vur.includes(n);s!==(t.policy===null)&&e.addIssue({code:A.ZodIssueCode.custom,path:["policy"],message:s?`a ${n} run has no policy`:"names the policy revision"});let i=Pur(t);i&&e.addIssue({code:A.ZodIssueCode.custom,path:t.baseline?["baseline","sources"]:["baseline"],message:i}),Tur.includes(n)&&t.sources.forEach((c,l)=>{c.reason!=="BASELINE"&&e.addIssue({code:A.ZodIssueCode.custom,path:["sources",l,"reason"],message:`a ${n} run selects only BASELINE sources`})}),t.deployedRevisions&&n!=="manual"&&e.addIssue({code:A.ZodIssueCode.custom,path:["deployedRevisions"],message:"only a manual run pins deployed revisions"});let o=new Set(t.sources.map(c=>En(c.repository))),a=new Set;(t.deployedRevisions??[]).forEach((c,l)=>{let u=En(c.repository);o.has(u)?a.has(u)&&e.addIssue({code:A.ZodIssueCode.custom,path:["deployedRevisions",l,"repository"],message:"repository appears twice"}):e.addIssue({code:A.ZodIssueCode.custom,path:["deployedRevisions",l,"repository"],message:"names a repository outside the snapshot"}),a.add(u)}),t.sources.forEach((c,l)=>{c.compareWith&&(c.reason!=="MANUAL_OVERRIDE"||c.change)&&e.addIssue({code:A.ZodIssueCode.custom,path:["sources",l,"compareWith"],message:"only a MANUAL_OVERRIDE source without a change names a revision to compare with"})})});var bfe=(r=>(r.Person="person",r.Policy="policy",r))(bfe||{}),Dur=A.object({kind:A.nativeEnum(bfe),name:A.string().min(1).max(256)}).strict(),Our=A.object({schemaVersion:A.literal(1),attempt:A.object({id:Aa,number:A.number().int().positive(),deadlineAt:A.string().datetime()}).strict(),manifest:_ur,manifestHash:ya,entitlements:A.array(A.string()).optional(),runToken:A.string().min(1).optional(),requestedBy:Dur.nullable().optional(),writeAllowed:A.boolean().optional(),claudeCodeVersion:A.string().regex(/^[0-9]+\.[0-9]+\.[0-9]+$/,"an exact version, such as 2.1.290").nullable().optional()}).strict(),Bst={providerRunAttempt:A.number().int().positive(),workflowRef:A.string().min(1)},Mur=A.string().regex(/^[^/]+\/[^/]+\/[1-9][0-9]{0,15}\/[1-9][0-9]{0,15}$/),Qur=A.object({schemaVersion:A.literal(1),runId:Aa,execution:A.discriminatedUnion("provider",[A.object({...Bst,provider:A.literal("github"),repository:A.object(hj).strict(),providerRunId:od}).strict(),A.object({...Bst,provider:A.literal("azure-devops"),repository:A.object(gj).strict(),providerRunId:Mur}).strict()]),requester:A.literal(!0).optional(),writeCheck:A.literal(!0).optional(),claudeCodePin:A.literal(!0).optional()}).strict(),_st=A.object({repository:Sh,number:od}).strict(),Lur=A.discriminatedUnion("status",[A.object({status:A.literal("none")}).strict(),A.object({status:A.literal("delivered"),pullRequests:A.array(_st).min(1)}).strict(),A.object({status:A.literal("partial"),pullRequests:A.array(_st).min(1),failures:A.array(A.object({repository:Sh,message:A.string()}).strict()).min(1)}).strict(),A.object({status:A.literal("failed"),message:A.string()}).strict()]),Uur=A.object({sufficiency:A.nativeEnum(VR),rationale:A.string().min(1),gaps:A.array(A.string().min(1)),acceptedEvidence:A.array(A.object({kind:A.nativeEnum(sy),repository:A.string().min(1),revision:Gw,reference:A.string().min(1),summary:A.string().min(1)}).strict()),actionRequired:A.array(A.nativeEnum(WR))}).strict().superRefine((t,e)=>{t.sufficiency==="sufficient"!=(t.gaps.length===0)&&e.addIssue({code:A.ZodIssueCode.custom,path:["gaps"],message:"sufficient evidence has no gaps, and insufficient evidence names at least one"}),new Set(t.actionRequired).size!==t.actionRequired.length&&e.addIssue({code:A.ZodIssueCode.custom,path:["actionRequired"],message:"a reason appears twice"})}),KR=500,Efe=2048,wfe=1e3,Fur=/[\\/:*?"<>|;#$*{},+=[\]%\p{Cc}\p{Cf}]/u,Dst=t=>t.length>0&&t.length<=64&&t===t.trim()&&!Fur.test(t)&&!/[^\S ]/u.test(t)&&!/^[._]/.test(t)&&!t.endsWith("."),$ur=t=>{if(/^[A-Za-z0-9._-]{1,100}\/[A-Za-z0-9._-]{1,100}$/.test(t))return!0;let[e,r,n,...s]=t.split("/");return t.length<=201&&s.length===0&&n!==void 0&&/^[A-Za-z0-9](?:[A-Za-z0-9-]{0,48}[A-Za-z0-9])?$/.test(e)&&Dst(r)&&Dst(n)},qur=A.object({testResults:A.array(A.object({resultId:A.string().regex(/^[^\u0000-\u001f]{1,512}$/),repository:A.string().refine($ur,"Invalid"),testType:A.string(),endpoint:A.string(),status:A.enum(["Pass","Fail","Skipped"]),details:A.string(),fileName:A.string().optional(),testId:A.string().regex(/^[^\u0000-\u001f]{1,512}$/).optional(),failureCause:A.object({kind:A.nativeEnum(q_),family:A.nativeEnum(tj).optional()}).strict().optional()}).strict()),validation:Uur,changes:A.array(A.object({id:A.string().min(1).max(512),text:A.string().max(16384),source:A.string().max(Efe),testedBy:A.array(A.string().min(1).max(1024)).max(KR),writtenBy:A.array(A.string().min(1).max(1024)).max(KR),maintainedBy:A.array(A.string().min(1).max(1024)).max(KR)}).strict()).max(wfe).optional(),traceVersion:A.literal(1)}).passthrough().superRefine((t,e)=>{let r=new Set;t.testResults.forEach((n,s)=>{let i=n.failureCause;i&&n.status==="Pass"&&e.addIssue({code:A.ZodIssueCode.custom,path:["testResults",s,"failureCause"],message:"a passing result has no failure cause"}),i&&i.kind==="environment"!=(i.family!==void 0)&&e.addIssue({code:A.ZodIssueCode.custom,path:["testResults",s,"failureCause"],message:"an environment cause names its family, and no other cause does"});let o=["testResults",s,"resultId"],a=n.resultId.slice(`${n.repository}:${n.testType}:`.length);(!n.resultId.startsWith(`${n.repository}:${n.testType}:`)||a===""||a.startsWith("/")||a.startsWith("#"))&&e.addIssue({code:A.ZodIssueCode.custom,path:o,message:"is not repository:testType:path for this result"}),r.has(n.resultId)&&e.addIssue({code:A.ZodIssueCode.custom,path:o,message:"stable identity repeated"}),r.add(n.resultId)})}),Ost=t=>t.replace(/[%:#]/g,e=>encodeURIComponent(e));function Aj(t,e,r,n){let s=r.replace(/\\/g,"/"),i=s.split("/").filter(c=>c!==""&&c!==".");if(s.startsWith("/")||/^[A-Za-z]:/.test(s)||i.length===0||i.includes(".."))throw new Error(`testResultId needs a path relative to the repository root, got "${r}"`);let o=i.join("/"),a=`${t}:${e}:${Ost(o)}`;return n?`${a}#${Ost(n)}`:a}var xfe=50,Mst=A.string().regex(/^(?!.*\.\.)(?!\/)(?!.*\/$)[A-Za-z0-9._/-]{1,255}$/),Sfe=A.discriminatedUnion("kind",[A.object({kind:A.literal("branch_pushed"),repository:Sh,branch:Mst,commit:Gw}).strict(),A.object({kind:A.enum(["pull_request_opened","pull_request_edited"]),repository:Sh,number:od,branch:Mst}).strict()]),Gst=A.object({schemaVersion:A.literal(1),run:A.object({runId:Aa,attemptNumber:A.number().int().positive(),manifestHash:ya}).strict(),target:aj,policy:aj,provenance:A.object({sources:A.array(A.object({repository:Sh,revision:Gw}).strict())}).strict(),delivery:A.object({generatedTests:Lur}).strict(),producer:A.object({testbot:A.string().regex(/^[0-9A-Za-z.+-]{1,64}$/),mcp:A.string().regex(/^[0-9A-Za-z.+-]{1,64}$/)}).strict(),report:qur,githubWrites:A.array(Sfe).max(xfe).optional()}).strict(),Hur=Gst.pick({schemaVersion:!0,run:!0,target:!0,provenance:!0,producer:!0}).extend({recipe:ij}).strict().superRefine(({recipe:t},e)=>{let r=(i,o)=>e.addIssue({code:A.ZodIssueCode.custom,path:["recipe",...i],message:o});t.status==="blocked"&&t.blockers.length===0&&r(["blockers"],"a blocked recipe names at least one blocker");let n=new Set,s=0;t.files.forEach((i,o)=>{oy(i.path)||r(["files",o,"path"],"is not .skyramp/workspace.yml or a file under .skyramp/sut/"),n.has(i.path)&&r(["files",o,"path"],"file repeated"),n.add(i.path),KG.test(i.content)&&r(["files",o,"content"],"is not text");let a=Buffer.byteLength(i.content,"utf8");s+=a,a>$w&&r(["files",o,"content"],`larger than ${$w} bytes`)}),s>qw&&r(["files"],`recipe text over ${qw} bytes in total`)}),zur=A.object({components:A.array(A.object({name:A.string().min(1),repository:A.string().regex(/^[A-Za-z0-9._-]{1,100}\/[A-Za-z0-9._-]{1,100}$/),observedRevision:A.string().regex(/^[0-9a-f]{7,64}$/).nullable(),source:A.nativeEnum(rj),evidence:A.string()}).strict().refine(t=>t.source!=="agent"||t.observedRevision===null,{path:["observedRevision"],message:"a component only an agent session can read names no revision"}))}).strict();function jst(t,e){if(t.trigger.kind!=="revision_read")throw new Error(`The deployed revisions upload is invalid: only a revision_read run sends one, not a ${t.trigger.kind} run`);let r=t.sources.map(s=>`${s.repository.namespace}/${s.repository.name}`),n=zur.safeParse({components:e.map((s,i)=>{let o=r.find(a=>a.toLowerCase()===s.repository.toLowerCase());if(!o)throw new Error(`The deployed revisions upload is invalid: components.${i}.repository: ${s.repository} is not a source of the run`);return{...s,repository:o,evidence:s.evidence.trim()}})});if(!n.success)throw new Error(`The deployed revisions upload is invalid: ${Dfe(n.error)}`);return n.data}var Vst=A.object({schemaVersion:A.literal(1),stage:A.nativeEnum(hfe),event:A.nativeEnum(gfe),status:A.nativeEnum(Afe).optional()}).strict().refine(t=>t.status===void 0||t.event==="finished",{path:["status"],message:"only a finished stage has a status"}),ev=100,K_=5e3,mfe=2e3,G_=100,yj=/^[A-Za-z0-9_-]{1,64}$/,Cfe=20,Ife=200,Rfe=100,vfe=100,Gur=200,Qst="[^/\\s](?:(?:[^/\\s]| )*[^/\\s])?",jur=new RegExp(`^(?:[^/\\s]+/[^/\\s]+|[^/\\s]+/${Qst}/${Qst})$`),Tfe=500,Nfe=50,kfe=/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]|[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/,fm=t=>A.string().max(t).refine(e=>e.trim().length>0&&!kfe.test(e),"blank, or not text"),JR={sequence:A.number().int().min(1).max(K_),at:A.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?Z$/)},Pfe=A.discriminatedUnion("kind",[A.object({...JR,kind:A.literal("log"),phase:A.nativeEnum(XR),text:fm(mfe)}).strict(),A.object({...JR,kind:A.literal("component"),name:fm(G_),repository:fm(Gur).refine(t=>jur.test(t)||t===hn,"not owner/name"),file:fm(Tfe).nullable(),status:A.nativeEnum(J_),version:fm(vfe).optional()}).strict(),A.object({...JR,kind:A.literal("reply"),to:A.string().uuid(),accepted:A.boolean(),text:fm(mfe)}).strict(),A.object({...JR,kind:A.literal("question"),id:A.string().regex(yj),type:A.nativeEnum(fj),text:fm(mfe),component:fm(G_).nullable(),choices:A.array(fm(Ife)).max(Cfe),placeholder:fm(Rfe).optional()}).strict(),A.object({...JR,kind:A.literal("delivered"),ids:A.array(A.string().uuid()).max(ev)}).strict(),A.object({...JR,kind:A.literal("credentials"),names:A.array(A.string().regex(ty)).min(1).max(Nfe)}).strict()]).superRefine((t,e)=>{t.kind==="credentials"&&new Set(t.names).size!==t.names.length&&e.addIssue({code:A.ZodIssueCode.custom,path:["names"],message:"a name twice"}),t.kind==="question"&&(t.type==="free_text"!=(t.choices.length===0)&&e.addIssue({code:A.ZodIssueCode.custom,path:["choices"],message:"choices exactly when the question is not free_text"}),new Set(t.choices).size!==t.choices.length&&e.addIssue({code:A.ZodIssueCode.custom,path:["choices"],message:"a choice twice"}))}),Wst=A.object({schemaVersion:A.literal(1),events:A.array(Pfe).max(ev)}).strict(),Lst={id:A.string().uuid(),text:A.string(),by:A.string().nullable(),at:A.string(),replied:A.literal(!0).optional()},Vur=A.discriminatedUnion("kind",[A.object({...Lst,kind:A.literal("instruction"),question:A.null()}),A.object({...Lst,kind:A.literal("answer"),question:A.string().regex(yj)})]),Yst=A.string().regex(/^[A-Za-z0-9+/]{43}=$/),Wur=A.object({id:A.string().uuid(),revision:A.string(),by:A.string().nullable(),at:A.string(),target:A.string(),own:A.object({source:A.enum(["policy","test_environment"]),text:A.string()}).nullable()}),Jst=A.object({schemaVersion:A.literal(1),leftOut:A.array(A.string()),messages:A.array(Vur).optional(),stop:A.object({key:Yst}).optional(),notes:Wur.optional()}),X_=7,bj=500*1024*1024,Kst=A.object({id:od,name:A.string().regex(/^[A-Za-z0-9._-]{1,200}$/),providerRunId:od,expiresAt:A.string().datetime()}).strict(),Xst=A.object({schemaVersion:A.literal(1),artifact:Kst,sessionId:Aa,contentHash:ya,bytes:A.number().int().min(1).max(bj)}).strict(),Zst=A.object({schemaVersion:A.literal(1),checkpoint:A.object({artifact:Kst,repository:A.object({namespace:A.string().min(1),name:A.string().min(1)}).strict(),sessionId:Aa,contentHash:ya,key:Yst}).strict().nullable()}).strict(),Yur=200,oj=100,Ej=(n=>(n.Resume="resume",n.Retry="retry",n.Update="update",n))(Ej||{}),Jur=A.object({kind:A.nativeEnum(Ej),setup:A.object({runId:Aa,endedAt:A.string().nullable(),outcome:A.string().nullable()}).strict(),components:A.array(A.object({name:A.string().min(1),repository:A.string().min(1),status:A.nativeEnum(J_)}).strict()).max(oj),credentials:A.array(A.string().min(1)).max(oj),answers:A.array(A.object({question:A.string(),answer:A.string().nullable()}).strict()).max(oj),instructions:A.array(A.object({text:A.string(),reply:A.string().nullable()}).strict()).max(oj),log:A.array(A.object({phase:A.nativeEnum(XR),text:A.string()}).strict()).max(Yur)}).strict(),eit=A.object({schemaVersion:A.literal(1),context:Jur.nullable()}).strict(),tit=A.object({schemaVersion:A.literal(1),configuration:A.object({captureId:Aa,revision:od.nullable(),hash:ya}).strict(),capture:A.object({schemaVersion:A.literal(1),settings:A.object({target:A.object({environment:A.unknown()}).passthrough(),timeoutMinutes:A.number().int().positive(),answerWaitMinutes:A.number().int().positive().optional(),testEnvironment:A.object({name:A.string().min(1),address:A.string().min(1).optional(),readyWhen:A.string().min(1).optional()}).passthrough().optional()}).passthrough(),provenance:A.object({}).passthrough(),hash:ya}).strict()}).strict(),Kur=HR.extend({version:A.number().int().positive(),components:A.array(A.object({name:A.string().min(1),repository:A.string().min(1)}).strict()),contentHash:ya.optional(),files:A.array(A.object({path:A.string().refine(t=>oy(t)&&!YR.includes(t),"not a file of a customer_deployed plan"),content:A.string()}).strict()).optional()}).strict(),rit=A.object({schemaVersion:A.literal(1),testEnvironment:Kur.nullable().optional(),runtimePlan:A.object({version:A.number().int().positive(),contentHash:ya,files:A.array(A.object({path:A.string().refine(oy,"not a recipe file path"),content:A.string()}).strict()).min(1),recipe:A.record(A.unknown()).optional()}).strict().nullable()}).strict().refine(t=>!(t.runtimePlan&&t.testEnvironment),{message:"a run pins a recipe or a customer's Test Environment, never both",path:["testEnvironment"]}),nit=A.object({schemaVersion:A.literal(1),state:A.nativeEnum(Fst),ending:A.nativeEnum(dj).nullable(),cause:A.nativeEnum(pj).nullable(),deadlineAt:A.string().datetime()}).strict();function Bfe(t,e){return t.state==="ended"?t.ending==="cancelled"?"cancelled":t.cause==="runner_lost"?"expired":"ended":e.getTime()>=Date.parse(t.deadlineAt)?"expired":null}function ay(t){if(typeof t=="number"&&!Number.isFinite(t))throw new TypeError("Canonical JSON requires JSON values");if(Array.isArray(t))return`[${t.map(ay).join(",")}]`;if(t!==null&&typeof t=="object"){let r=t;return`{${Object.keys(r).sort().map(n=>`${JSON.stringify(n)}:${ay(r[n])}`).join(",")}}`}let e=JSON.stringify(t);if(e===void 0)throw new TypeError("Canonical JSON requires JSON values");return e}function _fe(t){return(0,W_.createHash)("sha256").update(ay(t),"utf8").digest("hex")}function sit(t){let e=[...t].sort((r,n)=>r.path<n.path?-1:r.path>n.path?1:0).map(r=>({path:r.path,content:r.content}));return(0,W_.createHash)("sha256").update(ay(e),"utf8").digest("hex")}function iit(t){let e=(i,o)=>i<o?-1:i>o?1:0,r=[...t.files].sort((i,o)=>e(i.path,o.path)).map(i=>({path:i.path,content:i.content})),n={address:t.address,readyWhen:t.readyWhen,credentials:[...t.credentials].sort(e),revisionSources:t.revisionSources.map(i=>ay({method:i.method,detail:i.detail,components:[...i.components].sort(e)})).sort(e)},s=t.components.map(i=>ay({name:i.name,repository:i.repository})).sort(e);return(0,W_.createHash)("sha256").update(ay({type:"customer_deployed",environment:n,components:s,files:r}),"utf8").digest("hex")}function oit(t){return(0,W_.createHash)("sha256").update(ay({schemaVersion:t.schemaVersion,settings:t.settings,provenance:t.provenance}),"utf8").digest("hex")}var j_=t=>({provider:t.provider,providerAccountId:t.providerAccountId,providerRepositoryId:t.providerRepositoryId});function ait(t,e){return Qur.parse({schemaVersion:1,runId:t,execution:e.execution(),requester:!0,writeCheck:!0,claudeCodePin:!0})}function cit(t){let e=Our.parse(t);if(_fe(e.manifest)!==e.manifestHash)throw new Error("The handshake manifest hash does not match its manifest");return e}function Xur(t){let e=r=>r.map(n=>({repository:j_(n.repository),number:n.number}));return t.status==="delivered"?{status:t.status,pullRequests:e(t.pullRequests)}:t.status==="partial"?{status:t.status,pullRequests:e(t.pullRequests),failures:t.failures.map(r=>({repository:j_(r.repository),message:r.message}))}:t}function Dfe(t){return t.issues.map(e=>`${e.path.map((n,s)=>typeof n=="number"?`[${n}]`:s===0?n:`.${n}`).join("")}: ${e.message}`).join("; ")}function lit(t){let{manifest:e}=t;if(Nur.includes(e.trigger.kind))throw new Error(`The report upload is invalid: a ${e.trigger.kind} run uploads its SUT recipe, never a report`);if(e.trigger.kind==="revision_read")throw new Error("The report upload is invalid: a revision_read run uploads what each component runs, never a report");let r=Gst.safeParse({schemaVersion:1,run:{runId:e.runId,attemptNumber:t.attemptNumber,manifestHash:_fe(e)},target:{id:e.target.id,revision:e.target.revision},policy:e.policy&&{id:e.policy.id,revision:e.policy.revision},provenance:{sources:t.checkedOut.map(n=>({repository:j_(n.repository),revision:n.revision}))},delivery:{generatedTests:Xur(t.generatedTests)},producer:t.producer,report:t.report,...t.githubWrites?.length?{githubWrites:t.githubWrites.map(n=>({...n,repository:j_(n.repository)}))}:{}});if(!r.success)throw new Error(`The report upload is invalid: ${Dfe(r.error)}`);return Zur(r.data,e,t.checkedOut),r.data}function uit(t,e,r,n,s){let i=Hur.safeParse({schemaVersion:1,run:{runId:t.runId,attemptNumber:e,manifestHash:_fe(t)},target:{id:t.target.id,revision:t.target.revision},provenance:{sources:r.map(o=>({repository:j_(o.repository),revision:o.revision}))},producer:s,recipe:n});if(!i.success)throw new Error(`The SUT recipe upload is invalid: ${Dfe(i.error)}`);return i.data}function Zur(t,e,r){let n=i=>r.find(o=>En(o.repository)===En(i.repository))?.revision??i.revision,s=new Map(e.sources.map(i=>[`${i.repository.namespace}/${i.repository.name}`.toLowerCase(),n(i)]));t.report.validation.acceptedEvidence.forEach((i,o)=>{let a=`report.validation.acceptedEvidence[${o}]`,c=s.get(i.repository.toLowerCase());if(!c)throw new Error(`${a}.repository: ${i.repository} is outside the run's snapshot`);if(i.revision!==c)throw new Error(`${a}.revision: evidence is not from the checked-out revision ${c}`)})}var dit=$(require("fs")),Z_="SKYRAMP_AZURE_SUPERVISED",Ofe=3;function wj(t){return t.replace(/##(?=(?:vso)?\[)/gi,"#\u200B#")}function Mfe(t,e=process.env){e[Z_]==="1"?dit.writeSync(Ofe,`${JSON.stringify(t)}
|
package/dist/post.js
CHANGED
|
@@ -226,7 +226,7 @@ ${nTe(t)}}`}case"ZodArray":{let i=n.element;return`[ ${sv(i,t,r)}, \u2026 ]`}cas
|
|
|
226
226
|
# Written by ${r}: the recorded command, which needed a shell to run.
|
|
227
227
|
set -euo pipefail
|
|
228
228
|
${n}
|
|
229
|
-
`,{mode:493})}catch{return}return`bash ${oo(t)}`}}function oo(e){return`'${e.split("'").join("'\\''")}'`}var mTe=require("child_process"),ua=j(require("fs")),vn=j(require("path"));var Lwt=vn.join(".skyramp","workspace.yml"),gTe=new Set(["playwright","cypress","selenium"]);function Xp(e){return typeof e=="string"&&e.trim()?e.trim():void 0}function ao(e){let t=vn.join(e,Lwt);if(!ua.existsSync(t))return[];let r;try{r=El(ua.readFileSync(t,"utf-8"))}catch{return[]}let n=r?.services;if(!Array.isArray(n))return[];let s=[];for(let i of n){if(typeof i!="object"||i===null)continue;let o=i,a=o.api??{},c=Xp(o.serviceName);c&&s.push({serviceName:c,language:Xp(o.language),framework:Xp(o.framework),testDirectory:Xp(o.testDirectory),schemaPath:Xp(a.schemaPath),authType:Xp(a.authType),authHeader:Xp(a.authHeader),baseUrl:Xp(a.baseUrl)})}return s}function Rb(e){return e.filter(t=>!t.framework||!gTe.has(t.framework.toLowerCase()))}function ATe(e){return[...new Set(Rb(e).map(t=>t.testDirectory?.replace(/\/+$/,"")).filter(t=>!!t))]}function yTe(e){return[...new Set(e.filter(t=>iv(t.framework)).map(t=>t.testDirectory?.replace(/\/+$/,"")).filter(t=>!!t))]}function iv(e){return!!(e&&gTe.has(e.toLowerCase()))}var Uwt={golang:"go",py:"python",py3:"python",python3:"python",ts:"typescript",js:"javascript","c++":"cpp",cplusplus:"cpp",cxx:"cpp",node:"javascript",nodejs:"javascript",rb:"ruby",cs:"csharp","c#":"csharp"};function Ci(e){let t=e?.trim().toLowerCase();if(t)return Uwt[t]??t}var bTe={py:"python",go:"go",java:"java",kt:"kotlin",rb:"ruby",cs:"csharp",ts:"typescript",tsx:"typescript",js:"javascript",jsx:"javascript",mjs:"javascript",cjs:"javascript",kts:"kotlin",rs:"rust",php:"php",scala:"scala",swift:"swift",ex:"elixir",exs:"elixir",dart:"dart",c:"c",h:"c",cpp:"cpp",cc:"cpp",cxx:"cpp",hpp:"cpp",hh:"cpp",groovy:"groovy"},$wt=new Set(["feature"]);function ETe(e){let t=e.lastIndexOf(".");return t>=0&&$wt.has(e.slice(t+1).toLowerCase())}var qwt=new Set(Object.values(bTe));function wTe(e){let t=Ci(e);return t!==void 0&&qwt.has(t)}function Bm(e){let t=e.lastIndexOf(".");if(!(t<0))return bTe[e.slice(t+1).toLowerCase()]}function C4(e){return[...new Set(Rb(e).map(t=>Ci(t.language)).filter(t=>!!t))]}function kb(e,t=[]){let r=C4(ao(e));return r.length?r:[...new Set(t.map(n=>Ci(n)).filter(n=>!!n))]}function CTe(e,t,r=[]){let n=kb(e,r);if(n.length===0)return!0;let s=Ci(t);return s!==void 0&&n.includes(s)}var ov=(s=>(s.Python="python",s.Node="node",s.Jvm="jvm",s.Go="go",s))(ov||{}),Gwt={python:"python",typescript:"node",javascript:"node",java:"jvm",kotlin:"jvm",go:"go"};function wi(e){let t=Ci(e);return t?Gwt[t]:void 0}var STe={python:["pyproject.toml","requirements.txt","setup.py","setup.cfg","Pipfile"],node:["package.json"],jvm:["pom.xml","build.gradle","build.gradle.kts"],go:["go.mod"]},Hwt=new Set(["node_modules",".git",".venv","venv","dist","build","target","vendor",".skyramp","__pycache__"]),jwt=3;function zwt(e,t){let r=[],n=(s,i)=>{let o=vn.join(e,s);if(STe[t].some(c=>ua.existsSync(vn.join(o,c)))&&r.push(s),i>=jwt)return;let a;try{a=ua.readdirSync(o,{withFileTypes:!0})}catch{return}for(let c of a)c.isDirectory()&&!Hwt.has(c.name)&&!c.name.startsWith(".")&&n(s==="."?c.name:vn.join(s,c.name),i+1)};return n(".",0),r}function Vwt(e,t){return ua.existsSync(vn.join(e,t,"tsconfig.json"))?"typescript":"javascript"}function Wwt(e,t,r){let n=vn.normalize(t??".");for(;;){if(STe[r].some(s=>ua.existsSync(vn.join(e,n,s))))return n;if(n==="."||n===vn.sep||n==="")return".";n=vn.dirname(n)}}function Ywt(e,t){let r;try{r=ua.readdirSync(vn.join(e,t))}catch{return!1}if(r.some(n=>/^playwright\.config\.[cm]?[jt]s$/.test(n)||/^cypress\.config\.[cm]?[jt]s$/.test(n)))return!0;for(let n of["package.json","pyproject.toml","requirements.txt"])try{if(/playwright|cypress|selenium/i.test(ua.readFileSync(vn.join(e,t,n),"utf-8")))return!0}catch{}return!1}function Zp(e,t){let r=ao(e),n=t==="ui"?r.filter(o=>iv(o.framework)):Rb(r),s=[];for(let o of n){let a=Ci(o.language),c=wi(a);a&&s.push({language:a,dir:c?Wwt(e,o.testDirectory,c):"."})}if(s.length)return hTe(s);let i=[];for(let o of Object.values(ov))for(let a of zwt(e,o)){if(t==="ui"&&!Ywt(e,a))continue;let c=o==="node"?Vwt(e,a):o==="jvm"?"java":o;i.push({language:c,dir:a})}return hTe(i)}function hTe(e){let t=new Set;return e.filter(r=>{let n=`${r.language}::${r.dir}`;return t.has(n)?!1:(t.add(n),!0)})}var Jwt=/"(?:react|react-dom|vue|svelte|@sveltejs\/kit|@angular\/core|next|nuxt|solid-js|preact)"\s*:/,Kwt={python:"py",typescript:"ts",javascript:"js",java:"java",kotlin:"kt",go:"go",ruby:"rb",csharp:"cs",php:"php",rust:"rs"};function Xwt(e){let t=new Map(Object.entries(Kwt).map(([s,i])=>[i,s])),r=new Map,n;try{n=(0,mTe.execFileSync)("git",["-C",e,"ls-files","-z","--",...[...t.keys()].map(s=>`*.${s}`)],{encoding:"utf-8",maxBuffer:64*1024*1024}).split("\0").filter(Boolean)}catch{return r}for(let s of n){let i=t.get(vn.extname(s).slice(1));i&&r.set(i,(r.get(i)??0)+1)}return r}function ITe(e){let t=Rb(ao(e)).map(l=>Ci(l.language)).filter(l=>!!l);if(t.length)return t[0];let r=Zp(e,"backend"),n=l=>{try{return Jwt.test(ua.readFileSync(vn.join(e,l,"package.json"),"utf-8"))}catch{return!1}},s=r.filter(l=>!(wi(l.language)==="node"&&n(l.dir))),i=Xwt(e),o=l=>i.get(l)??0,a=[...i.keys()].filter(l=>!wi(l)).sort((l,u)=>o(u)-o(l))[0],c=[...new Set([...(s.length?s:r).map(l=>l.language),...a?[a]:[]])];return c.length<=1?c[0]:c.sort((l,u)=>o(u)-o(l))[0]}function wQ(e){try{return new URL(e).hostname.replace(/^\[|\]$/g,"").toLowerCase()||void 0}catch{return}}function S4(e){if(!e)return!1;let t=e.toLowerCase();return t==="localhost"||t.endsWith(".localhost")||t==="::1"||t==="0.0.0.0"||/^127(\.\d{1,3}){3}$/.test(t)}function I4(e){let t=new Set;for(let r of ao(e)){let n=r.baseUrl&&wQ(r.baseUrl);n&&t.add(n)}return t}function CQ(e,t,r=I4(t)){let n;try{n=new URL(e).protocol}catch{return!1}if(n!=="http:"&&n!=="https:")return!1;let s=wQ(e);return s?S4(s)||r.has(s):!1}var tc=(s=>(s.Clean="clean",s.RecipeDrift="recipe-drift",s.ChangeBrokeBringup="change-broke-bringup",s.BringupFailed="bringup-failed",s))(tc||{}),ef=g.string().trim().min(1),Zwt=g.object({url:g.string().optional().describe("the URL probed, if the check is an HTTP request"),command:g.string().optional().describe("the command run, if the check is a script \u2014 preferred when auth is needed"),headers:g.record(g.string()).optional(),status:g.union([g.number(),g.string()]).optional().describe("expected HTTP status for a url check (default 200)"),output:ef.describe("what the check returned, VERBATIM \u2014 the bytes, nothing appended; this gate RE-RUNS the check")}),av=g.object({schemaVersion:g.literal(1),baseUrls:g.record(g.string()).optional().describe("service \u2192 base URL of the running app; omit on a failed bring-up, never invent one"),health:Zwt.optional(),teardown:ef.describe("command that tears the SUT down"),dependencies:g.record(g.boolean()).optional().describe("EVERY backing service: true = really deployed, false = mocked; REQUIRED when the app came up"),outcome:g.nativeEnum(tc).describe("clean | recipe-drift (you corrected the recipe) | change-broke-bringup (the PR broke it) | bringup-failed (environment, not the PR)"),recipeFixes:g.array(g.string()).optional().describe("required when outcome is recipe-drift: what was corrected"),bringupError:g.object({command:ef,output:ef}).optional().describe("required when bring-up failed: the exact failing command and what it printed"),suspectFiles:g.array(g.string()).optional().describe("required for change-broke-bringup: files THIS PR changed that the failure points at, relative to your working directory (a file in another checkout of the run: its absolute path)"),seeded:g.boolean(),startedAt:ef.describe("ISO timestamp"),revisions:g.array(g.object({component:ef.describe("a shared component listed above"),url:ef.describe("the URL on the component's own origin (scheme, host and port) that answers with its deployed commit"),output:ef.describe("what the URL returned, VERBATIM; this gate FETCHES each url")})).optional().describe("only when shared components are listed above: one check per component whose deployed commit you found")});function v4(e){let t=[],r=s=>!!s&&typeof s=="object"&&!Array.isArray(s);return e.outcome!=="change-broke-bringup"&&e.outcome!=="bringup-failed"?(!r(e.baseUrls)||Object.keys(e.baseUrls).length===0?t.push("baseUrls must be a non-empty object of service \u2192 url"):Object.values(e.baseUrls).every(s=>typeof s=="string"&&s.trim())||t.push("every baseUrls value must be a non-empty url string"),r(e.health)?!(typeof e.health.url=="string"&&e.health.url.trim())&&!(typeof e.health.command=="string"&&e.health.command.trim())&&t.push("health must record a url or a command (what you actually ran)"):t.push("health must be an object"),(!r(e.dependencies)||Object.keys(e.dependencies).length===0)&&t.push("dependencies must be a non-empty object of name \u2192 boolean (real vs mocked) when the SUT came up \u2014 account for every backing service so a mocked one can't be hidden behind a green run")):(r(e.bringupError)||t.push(`outcome "${String(e.outcome)}" requires bringupError { command, output } \u2014 the exact failing bring-up command and what it printed`),e.outcome==="change-broke-bringup"&&!(Array.isArray(e.suspectFiles)&&e.suspectFiles.length>0&&e.suspectFiles.every(s=>typeof s=="string"&&s.trim()))&&t.push("change-broke-bringup requires non-empty suspectFiles[] \u2014 the changed file(s) the failure points at; if you cannot point at a file this PR changed, the outcome is bringup-failed (environment/harness), not the PR's fault")),e.outcome==="recipe-drift"&&!(Array.isArray(e.recipeFixes)&&e.recipeFixes.length>0)&&t.push("recipe-drift outcome requires a non-empty recipeFixes[] describing what was corrected"),t}var Tb=(r=>(r.Full="full",r.Static="static",r))(Tb||{}),da=(n=>(n.Contract="contract",n.Integration="integration",n.Ui="ui",n))(da||{}),cv=(i=>(i.OwnSurface="own_surface",i.StateReadBack="state_read_back",i.Referential="referential",i.Lifecycle="lifecycle",i.Boundaries="boundaries",i))(cv||{}),Nm=(s=>(s.Update="update",s.Regenerate="regenerate",s.Delete="delete",s.Verify="verify",s))(Nm||{}),lv=(l=>(l.Equals="equals",l.NotEquals="not_equals",l.GreaterThan="greater_than",l.LessThan="less_than",l.Contains="contains",l.NotContains="not_contains",l.Matches="matches",l.Absent="absent",l.Present="present",l))(lv||{}),SQ=["absent","present"],co=g.string().trim().min(1),eCt=g.object({subject:co.describe('what the test READS: a response field path, the literal "status", or the on-screen text for a ui case \u2014 the FINAL read in a multi-step case'),comparator:g.nativeEnum(lv),value:g.union([g.string(),g.number(),g.boolean(),g.null()]).optional().describe("the ONE value a broken build would not produce; OMIT for absent/present")}),tCt=g.object({test:co.describe("existing test path, relative to its repository"),repository:co.optional().describe("owner/repo the test lives in, when the run has several checkouts; omit for the working checkout"),action:g.nativeEnum(Nm),evidence:co.describe("why \u2014 drift vs the diff, or a finding"),covers_behaviors:g.array(g.string()).optional().describe("analyze behavior ids this maintenance covers")}),rCt=g.object({surface:co.describe("a surface from the list, copied verbatim"),axis:g.nativeEnum(cv)}),nCt=g.object({id:co.describe("short-slug, unique across generate[]"),type:g.nativeEnum(da),target:co.describe('"METHOD /path", or a business-flow name'),from_finding:g.string().optional().describe("analyze finding id this case catches"),from_behavior:g.string().optional().describe("analyze behavior id this case pins"),derived_from:rCt.optional().describe("the surface \xD7 axis this case pins when no analyze behavior or finding names it \u2014 one of the three is required"),rationale:co.describe("why this test matters"),expected_observable:co.describe("one sentence for a human: what a catching test asserts, and what a broken build does instead"),expected:eCt.describe("the same intent AS DATA \u2014 see the rules below"),seededPrecondition:co.optional().describe("seeded non-initial state + how to reach it; only when a seeded-state transition applies"),verifiable:g.boolean().describe("true only when a live SUT could confirm this test (full mode)")}),sCt=g.object({covered:g.array(g.string()).optional(),abstain:g.string().optional()}).superRefine((e,t)=>{let r=Array.isArray(e.covered),n=typeof e.abstain=="string";if(r===n)return void t.addIssue({code:g.ZodIssueCode.custom,message:"must have exactly one of covered[] or abstain"});r&&e.covered.length===0&&t.addIssue({code:g.ZodIssueCode.custom,path:["covered"],message:"must list at least one id (else abstain)"}),n&&!e.abstain.trim()&&t.addIssue({code:g.ZodIssueCode.custom,path:["abstain"],message:"must give a reason"})}).describe('{ "covered": [ids] } OR { "abstain": "reason" } \u2014 exactly one'),iCt=g.object({surface:co.describe("a surface from the list, copied verbatim"),axes:g.object(Object.fromEntries(Object.values(cv).map(e=>[e,sCt])))}),uv=g.object({schemaVersion:g.literal(1),mode:g.nativeEnum(Tb),maintenance:g.array(tCt),generate:g.array(nCt),surfaces:g.array(iCt).describe("one entry per changed surface in the list \u2014 none missing, none invented"),budget:g.object({total:g.number().describe("case count"),rationale:co}),summary:co.describe("one paragraph")});function IQ(e,t,r){let n=s=>{if(!s||!Array.isArray(s.covered))return s;let i=s.covered.filter(o=>!t.has(o));return i.length>0?{covered:i}:{abstain:`its only case(s) were ${r}: ${s.covered.join(", ")}`}};return e.map(s=>({...s,axes:Object.fromEntries(Object.entries(s.axes??{}).map(([i,o])=>[i,n(o)]))}))}function vTe(e){let t=[],r=new Set;if(Array.isArray(e.generate)&&e.generate.forEach((n,s)=>{let i=n?.id;typeof i=="string"&&(r.has(i)&&t.push(`generate[${s}].id "${i}" is duplicated`),r.add(i))}),Array.isArray(e.generate)&&e.generate.forEach((n,s)=>{let i=n;!i.from_finding&&!i.from_behavior&&!i.derived_from&&t.push(`generate[${s}] carries none of from_behavior, from_finding or derived_from \u2014 say what obligation it pins`)}),Array.isArray(e.surfaces)){let n=new Set;e.surfaces.forEach((s,i)=>{let o=s;if(typeof o.surface=="string"){let a=o.surface.trim().toLowerCase();n.has(a)&&t.push(`surfaces[${i}] "${o.surface}" is listed twice`),n.add(a)}for(let[a,c]of Object.entries(o.axes??{})){let l=c?.covered;if(Array.isArray(l))for(let u of l)(typeof u!="string"||!r.has(u))&&t.push(`surfaces[${i}].axes.${a}.covered references unknown generate id ${JSON.stringify(u)}`)}})}return t}var _m=(r=>(r.Ready="ready",r.Blocked="blocked",r))(_m||{}),zr=g.string().trim().min(1),oCt=g.object({action:g.enum(["start","run","wait"]),text:zr,source:g.enum(["file","notes","testbot"]).describe("file: a file in the repository; notes: the Target's notes; testbot: Testbot's own finding"),file:zr.nullable().describe("the file the step came from, relative to the component's repository; null unless source is file")}).strict().refine(e=>e.source==="file"==(e.file!==null),{path:["file"],message:"a file exactly when the step came from one"}),aCt=/^[A-Za-z_][A-Za-z0-9_]*$/,Bb=/^(?!GITHUB_)(?!SKYRAMP_RUNNER_)[A-Z][A-Z0-9_]{0,99}$/,Nb=(o=>(o.VersionEndpoint="version_endpoint",o.DeployWorkflow="deploy_workflow",o.KubernetesLabels="kubernetes_labels",o.Command="command",o.Api="api",o.NotTracked="not_tracked",o))(Nb||{}),Vn="shared",cCt=zr.refine(e=>{try{let t=new URL(e);return["http:","https:"].includes(t.protocol)&&!t.username&&!t.password}catch{return!1}},"an http(s) URL with no login in it"),_b=g.object({address:cCt,readyWhen:zr.nullable(),credentials:g.array(g.string().regex(Bb)).max(50),revisionSources:g.array(g.object({method:g.nativeEnum(Nb),detail:zr.describe("what Testbot reads, such as GET /version or the deploy workflow's name; for not_tracked, why nothing shows it"),components:g.array(zr).min(1).describe("the components it tells the revision of, by name")}).strict()).max(50)}).strict(),x4={steps:g.array(oCt).max(20).optional().describe("how it starts, in order"),kind:g.enum(["web_app","service","database","external_service"]).optional(),from:g.object({file:zr,service:zr.nullable()}).strict().optional(),credentials:g.array(g.string().max(100).regex(aCt)).max(50).optional().describe("the Target's credentials this component's recipe uses: a sign-in (a test user, password or token) or an API key, which the workflow supplies from a GitHub Actions secret as the environment variable of the credential's name; give each name exactly as the recipe reads it. Names only, never a value, and not a secret the recipe generates itself")},lCt=g.object({name:zr,repository:zr.describe("owner/repo of the Target repository the component is built or started from, such as the one whose compose file starts its database"),healthy:g.boolean(),...x4,deployed:zr.nullable().optional().describe('only with "environment": the revision the component runs, as you read it from its revision source, or null when you could not tell')}),uCt=g.object({component:zr,problem:zr,evidence:zr.describe("the exact command and output that shows it"),fix:zr.describe("what the user must provide, and where")}),dCt=g.object({framework:zr,file:zr.describe("the test file run, relative to its repository root"),result:g.enum(["pass","fail","error"])}),pCt=g.object({input:zr.describe("the part of the user's SUT input, as given"),reason:zr}),dv=g.object({schemaVersion:g.literal(1),status:g.nativeEnum(_m).describe("ready only if every component came up healthy and the recipe is written"),components:g.array(lCt),suites:g.array(dCt).default([]).describe("one run per test suite the Target's repositories have; empty when they have none"),inputsUsed:g.array(zr).default([]).describe("the parts of the user's SUT input the recipe used"),inputsUsedHow:g.array(g.object({input:zr.describe("exactly as written in inputsUsed; an entry that is not there is left out"),how:zr.describe("what the recipe did with it")})).optional(),inputsNotUsed:g.array(pCt).default([]).describe("the parts it did not use, and why"),blockers:g.array(uCt).describe("required (non-empty) when status is blocked"),environment:_b.optional().describe("only when the whole SUT is an existing deployment you tested where it runs, building and starting nothing: its address, what says it is ready, the credentials that reach it, and how to read what each component runs. Leave it out when any part was built or started on the runner, or reaching it needed something there (a tunnel, port-forward, proxy or client certificate)")});function R4(e){let t=fCt(e);if(t.length>0)return t;if(e.status==="blocked"&&!(Array.isArray(e.blockers)&&e.blockers.length>0))return['status "blocked" requires at least one blocker { component, problem, evidence, fix } \u2014 say what stopped the bring-up'];if(e.status==="ready"){let r=Array.isArray(e.components)?e.components:[];if(r.length===0)return['status "ready" requires at least one component \u2014 name each component the recipe brought up healthy'];let n=r.filter(s=>s?.healthy!==!0).map(s=>String(s?.name??"?"));if(n.length>0)return[`status "ready" requires every component healthy; not healthy: ${n.join(", ")} \u2014 answer "blocked" with a blocker for each, or bring them up and answer "ready" again`]}return[]}function fCt(e){let t=Array.isArray(e.components)?e.components:[],r=e.environment;if(!r){let a=t.filter(c=>c?.deployed!==void 0||c?.repository===Vn).map(c=>String(c?.name??"?"));return a.length>0?[`"deployed" and a "${Vn}" repository belong only to an answer with "environment"; remove them from: ${a.join(", ")}`]:[]}let n=[],s=t.filter(a=>a?.deployed===void 0).map(a=>String(a?.name??"?"));s.length>0&&n.push(`with "environment", every component says what it runs in "deployed" (null when you could not tell); missing for: ${s.join(", ")}`);let i=new Set(t.map(a=>a?.name)),o=(r.revisionSources??[]).flatMap(a=>a?.components??[]).filter(a=>!i.has(a));return o.length>0&&n.push(`each revision source names components of this answer; not one of them: ${o.map(String).join(", ")}`),n}var Pm=64*1024,Qm=512*1024,vQ=/[\u0000-\u0008\u000b\u000e-\u001f\u007f]|[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/;var Pb=".skyramp/sut/deployed-revisions.sh",k4=(r=>(r.Running="running",r.Requested="requested",r))(k4||{});var Bor=new Set(Object.values(k4));var hCt=/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/,xTe=e=>hCt.test(e);function xQ(e){return e?.find(t=>t.path===Pb)}var Nor=`Fix ${Pb} so it prints the full commit each component runs, then set it up again.`;function T4(e,t){let r=new Set(t.filter(s=>s.method!=="not_tracked").flatMap(s=>s.components)),n=new Set(t.filter(s=>s.method==="not_tracked").flatMap(s=>s.components));return e.filter(s=>r.has(s.name)||!n.has(s.name))}var Ys="shared-environment",gCt=1e4,RTe=5,TTe=64*1024,_4=300,ACt=200,yCt=/\b[0-9a-fA-F]{7,64}\b/g,bCt=/\bsha256:[0-9a-fA-F]{64}\b/g,ECt=/\b(commit|sha|revision|rev|hash)\b/i,wCt=(e,t)=>new RegExp(`(?<![A-Za-z0-9])(commit|sha|revision|rev|hash)["']?\\s*[:=]?\\s*["']?${t}(?![0-9A-Za-z])`,"i").test(e),Qb=e=>e.slice(0,_4),pv=e=>Qb(e.trim())||"(empty response)";function kTe(e,t){let r=e.split(/\r?\n/).find(s=>s.toLowerCase().includes(t)).trim(),n=r.toLowerCase().indexOf(t);return r.length<=_4?r:r.slice(Math.max(0,n-100),Math.max(0,n-100)+_4)}function fv(e,t){if("error"in t)return{status:"unverifiable",observedRevision:null,evidence:pv(t.error)};let r=e.toLowerCase(),n=l=>ECt.test(kTe(t.body,l)),s=l=>r?r.startsWith(l)&&n(l):wCt(t.body,l),i=(t.body.replace(bCt," ").match(yCt)??[]).map(l=>l.toLowerCase()).filter(l=>/[a-f]/.test(l)||s(l)),o=[...new Set(i)].filter((l,u,d)=>!d.some(p=>p!==l&&p.startsWith(l)));if(o.length>1)return{status:"unverifiable",observedRevision:null,evidence:pv(`The answer names more than one revision: ${o.join(", ")}`)};let[a]=o;return a?{status:r.startsWith(a)?"verified":"mismatch",observedRevision:a,evidence:kTe(t.body,a)}:{status:"unverifiable",observedRevision:null,evidence:pv(t.body)}}async function CCt(e){if(!e.body)return"";let t=e.body.getReader(),r=[],n=0;for(;;){let{done:s,value:i}=await t.read();if(s)return Buffer.concat(r).toString("utf-8");if(n+=i.byteLength,n>TTe){await t.cancel();return}r.push(i)}}async function hv(e,t=fetch){try{let r=AbortSignal.timeout(gCt),n=new URL(e).origin,s=new URL(e);for(let i=0;;i+=1){let o=await t(s,{redirect:"manual",signal:r}),a=o.status>=300&&o.status<400?o.headers.get("location"):null;if(a){let l=new URL(a,s);if(l.origin!==n)return{error:`${e} redirects to another origin (${l.origin})`};if(i>=RTe)return{error:`${e} redirects more than ${RTe} times`};s=l;continue}let c=await CCt(o);return c===void 0?{error:`${e} answered more than ${TTe/1024} KiB`}:o.ok?{body:c}:{error:`${e} answered ${o.status}: ${c.trim()}`}}}catch(r){let n=r instanceof Error&&r.cause instanceof Error?`: ${r.cause.message}`:"";return{error:`${e} could not be read: ${r instanceof Error?r.message:String(r)}${n}`}}}var qor=new Set(["version_endpoint","api"]);function Db(e){return(e?.components??[]).filter(t=>!!t.repository&&!!t.expectedRevision)}function RQ(e){return Db(e.sharedEnvironment).filter(t=>!t.revisionUrl&&!t.revisionScript)}async function SCt(e,t,r=[]){let{name:n,url:s}=e;if(!e.versionUrl)return{name:n,url:s,version:null,evidence:"No version URL is configured."};let i=gr(await hv(e.versionUrl,t),r);if("error"in i)return{name:n,url:s,version:null,evidence:pv(i.error)};let o=i.body.split(/\r?\n/).map(a=>a.trim()).find(Boolean);return{name:n,url:s,version:o?o.slice(0,ACt):null,evidence:pv(o??"")}}var P4=(e,t)=>!!e&&!!t&&e.toLowerCase()===t.toLowerCase(),BTe=(e,t)=>e.name===t.name&&P4(e.repository,t.repository);async function ICt(e,t,r){let{frozen:n,mergeCommit:s,mergedHead:i}=t;return!s||!i?"not_contained":i.toLowerCase()===n.toLowerCase()?"contained":r?.commitContains?r.commitContains({repository:e,ancestor:n,descendant:i}):"unknown"}async function B4(e,t,r){let n=t.observedRevision;if(t.status!=="mismatch"||!n||!e.acceptedRevisions)return t;let{frozen:s,mergeCommit:i,mergedHead:o}=e.acceptedRevisions,a=await ICt(e.repository,e.acceptedRevisions,r),c=[s,...i&&a==="contained"?[i]:[]],l=f=>`${f===s?"the frozen revision":"the merge commit"} ${jn(f)}`,u=(f,h)=>({status:f,observedRevision:n,evidence:Qb(`${h} (${t.evidence})`)}),d=c.find(f=>f.toLowerCase().startsWith(n));if(d)return u("verified",`observed ${jn(n)} is ${l(d)}`);if(!r?.commitContains)return u("unverifiable",`could not tell whether observed ${jn(n)} contains ${c.map(jn).join(" or ")}: this run cannot compare commits`);let p=[];for(let f of c){let h=await r.commitContains({repository:e.repository,ancestor:f,descendant:n});if(h==="contained")return u("verified",`observed ${jn(n)} contains ${l(f)}`);h==="unknown"&&p.push(f)}return p.length?u("unverifiable",`could not tell whether observed ${jn(n)} contains ${p.map(jn).join(" or ")}: GitHub did not answer the compare`):a==="unknown"&&o?u("unverifiable",`could not tell whether the merged head ${jn(o)} contains ${jn(s)}: GitHub did not answer the compare`):u("mismatch",`observed ${jn(n)} does not contain ${c.map(jn).join(" or ")}`)}function NTe(e,t){let r=(i,o=null)=>({status:"unverifiable",observedRevision:o,evidence:Qb(i)});if(!t)return r("Not checked: this run cannot run deployed-revisions.sh.");if(!t.ok)return r(t.error);let n=t.revisions[e.name];return n?xTe(n.revision)?n.state==="requested"?r(`deployed-revisions.sh reports only the requested state for ${e.name} (${n.revision}), which alone does not show what runs.`,n.revision):{status:e.expectedRevision.toLowerCase().startsWith(n.revision)?"verified":"mismatch",observedRevision:n.revision,evidence:Qb(`deployed-revisions.sh: ${e.name} runs ${n.revision}`)}:r(`deployed-revisions.sh printed ${n.revision} for ${e.name}, which is not a full commit.`):r(`deployed-revisions.sh printed no revision for ${e.name}.`)}async function kQ(e,t,r,n=[],s,i){let o=(c,l)=>fv(c,gr(l,n));return{components:await Promise.all(Db(e).map(async c=>{let l={name:c.name,repository:c.repository,url:c.url,expectedRevision:c.expectedRevision};if(c.revisionScript)return{...l,source:"probe",...await B4(c,NTe(c,i),s)};if(c.revisionUrl)return{...l,source:"probe",...await B4(c,o(c.expectedRevision,await hv(c.revisionUrl,r)),s)};let u=t.find(p=>p.component===c.name),d=o(c.expectedRevision,u?{body:u.output}:{error:"Deploy cited no check of its deployed revision."});return{...l,source:"agent",...await B4(c,d,s)}})),dependencies:await Promise.all(e.dependencies.map(c=>SCt(c,r,n)))}}async function vCt(e,t,r,n){if(e.revisionScript){let o=NTe(e,n);return{failed:!n?.ok,now:o,noCommit:o.status==="unverifiable"}}let s=gr(await hv(e.revisionUrl,t),r),i=fv(e.expectedRevision,s);return{failed:"error"in s,now:i,noCommit:!i.observedRevision}}async function xCt(e,t,r,n=[],s){let i=await Promise.all(t.components.map(async o=>{let a=e.components.find(p=>BTe(p,o)),c=o.observedRevision?.toLowerCase();if(!c||!(a?.revisionUrl||a?.revisionScript))return o;let{failed:l,now:u,noCommit:d}=await vCt({...a,expectedRevision:o.expectedRevision},r,n,s);if(d){let p=l?"second read failed":"second read names no one commit",f=`first read ${jn(o.observedRevision??c)}; ${p}: ${u.evidence}`;return{...o,status:"unverifiable",observedRevision:null,evidence:Qb(f)}}return u.observedRevision.startsWith(c)||c.startsWith(u.observedRevision)?o:{...o,status:"unverifiable",observedRevision:u.observedRevision,evidence:Qb(`changed from ${o.observedRevision} to ${u.observedRevision} during the run`)}}));return{...t,components:i}}async function Q4(e,t){return t.components.some(r=>r.revisionScript)?e.deployedRevisions?.():void 0}async function _Te(e,t){let r=e.inputs.sharedEnvironment,n=ee(e,`${Ys}.json`);if(!e.inputs.appRunning||!r||!n)return;let s=await xCt(r,n,t,e.secretValues??[],await Q4(e,r));ot(e,Ys,gr(s,e.secretValues??[]),[])}function mv(e){return Db(e).length>0||(e?.dependencies.length??0)>0}function D4(e){let t=e.inputs?.sharedEnvironment;if(!mv(t))return;let r=ee(e,`${Ys}.json`);if(r)return{components:QTe(t,r.components),dependencies:r.dependencies};let n="Not checked: deploy did not complete.";return{components:Db(t).map(s=>PTe(s,n)),dependencies:t.dependencies.map(s=>({name:s.name,url:s.url,version:null,evidence:n}))}}function TQ(e){return e?.components.length?[`Testbot read which commit each component runs, before this session: ${e.components.map(r=>{let n=r.observedRevision?`${r.name} runs ${jn(r.observedRevision)}`:`${r.name} answered no commit`,s=r.status==="verified"?"verified":"not proven to run the change";return`${n}, ${s} (${r.evidence})`}).join("; ")}.`,"Which commit is deployed is Testbot's to check, not yours: record no gap, finding, issue or next step about it, and don't compare the deployed commit with your checkout."].join(" "):void 0}var PTe=(e,t)=>({name:e.name,repository:e.repository,url:e.url,expectedRevision:e.expectedRevision,observedRevision:null,status:"unverifiable",source:e.revisionUrl||e.revisionScript?"probe":"agent",evidence:t});function QTe(e,t){let r=new Set(t.map(s=>s.name.toLowerCase())),n=Db(e).filter(s=>s.touched&&!r.has(s.name.toLowerCase()));return[...t,...n.map(s=>PTe(s,"Not checked: the run recorded no read of it."))]}function Ob(e){return(D4(e)?.components??[]).filter(t=>t.status!=="verified")}function O4(e,t,r){if(!e.appRunning||r===!1)return[];let n=(e.sharedEnvironment?.components??[]).filter(s=>s.touched);return QTe(e.sharedEnvironment,t?.components??[]).filter(s=>s.status!=="verified"&&n.some(i=>BTe(i,s)))}function DTe(e){let t=r=>{let n=r.observedRevision?`runs ${jn(r.observedRevision)}`:"no revision could be read";return`${r.name} (${r.repository}) must run ${jn(r.expectedRevision)}, but ${n}: ${r.evidence}`};return e.length?`the Test Environment does not verifiably run the change: ${e.map(t).join("; ")}`:void 0}function BQ(e){return(e?.components??[]).filter(t=>!!t.repository&&!t.expectedRevision)}function OTe(e,t=!1){return`${e.name}: ${e.repository} is not one of the run's sources, so its revision cannot be verified and ${MTe(t)}`}var MTe=e=>e?"runtime tests against it do not validate this change":"no runtime test ran against it";function FTe(e,t=!1){let r=jn(e.expectedRevision),n=e.status==="mismatch"?`the shared environment runs ${e.repository} at ${e.observedRevision}, not ${r}`:`could not verify that the shared environment runs ${e.repository} at ${r}`;return`${e.name}: ${n}, so ${MTe(t)}`}var RCt=/^\s*(POST|PUT|PATCH|DELETE)\s/i,N4=e=>e.mutates===!0;function Dm(e){return(e?.components??[]).filter(t=>!!t.repository)}function lo(e){let t=e.sharedEnvironment;return!!t&&Dm(t).length>0&&!t.allowMutation&&!e.planOnly}function kCt(e){let t=n=>RCt.test(n.target)||N4(n),r=[...e.generate.filter(t).map(n=>({id:n.id,target:n.target})),...e.maintenance.filter(N4).map(n=>({id:n.test}))];return{...e,generate:e.generate.filter(n=>!t(n)),surfaces:IQ(e.surfaces,new Set(r.map(n=>n.id)),"withheld because they change a shared environment the policy protects"),maintenance:e.maintenance.filter(n=>!N4(n)),withheld:r}}function Mb(e,t){return t&&lo(e)?kCt(t):t}function NQ(e){return ee(e,"recommend.json")?.withheld??[]}function LTe(e){return`${e.id}${e.target?` (${e.target})`:""}: needs a shared-environment mutation the policy does not allow`}var TCt=40,jTe="health.command cannot be re-executed: ",UTe=4e3,BCt=9e4,NCt=5e3;function _Ct(e,t){let r=e.slice(0,UTe),n=t.slice(0,UTe);if(!r||!n)return"";let s=new Uint32Array(n.length+1),i=new Uint32Array(n.length+1),o=0,a=0;for(let c=1;c<=r.length;c++){for(let u=1;u<=n.length;u++){if(r[c-1]!==n[u-1]){i[u]=0;continue}i[u]=s[u-1]+1,i[u]>o&&(o=i[u],a=c)}let l=s;s=i,i=l}return r.slice(a-o,a)}function $Te(e){let t=e.search(/[[{]/);if(t<0)return;let r=e[t],n=r==="{"?"}":"]",s=0,i=!1,o=!1;for(let a=t;a<e.length;a++){let c=e[a];if(i){o?o=!1:c==="\\"?o=!0:c==='"'&&(i=!1);continue}if(c==='"')i=!0;else if(c===r)s++;else if(c===n&&--s===0)try{return JSON.parse(e.slice(t,a+1))}catch{return}}}function _Q(e,t=0){return t>4?"\u2026":e===null?"null":Array.isArray(e)?e.length===0?"[]":`[${[...new Set(e.map(r=>_Q(r,t+1)))].sort().join("|")}]`:typeof e=="object"?`{${Object.keys(e).sort().map(r=>`${r}:${_Q(e[r],t+1)}`).join(",")}}`:typeof e}function qTe(e,t){let r=$Te(e),n=$Te(t);if(r!==void 0&&n!==void 0){let a=_Q(r),c=_Q(n);return a===c?{ok:!0}:{ok:!1,why:`it answered ${c.slice(0,200)}, but the recorded check answered ${a.slice(0,200)}`}}let s=e.split(`
|
|
229
|
+
`,{mode:493})}catch{return}return`bash ${oo(t)}`}}function oo(e){return`'${e.split("'").join("'\\''")}'`}var mTe=require("child_process"),ua=j(require("fs")),vn=j(require("path"));var Lwt=vn.join(".skyramp","workspace.yml"),gTe=new Set(["playwright","cypress","selenium"]);function Xp(e){return typeof e=="string"&&e.trim()?e.trim():void 0}function ao(e){let t=vn.join(e,Lwt);if(!ua.existsSync(t))return[];let r;try{r=El(ua.readFileSync(t,"utf-8"))}catch{return[]}let n=r?.services;if(!Array.isArray(n))return[];let s=[];for(let i of n){if(typeof i!="object"||i===null)continue;let o=i,a=o.api??{},c=Xp(o.serviceName);c&&s.push({serviceName:c,language:Xp(o.language),framework:Xp(o.framework),testDirectory:Xp(o.testDirectory),schemaPath:Xp(a.schemaPath),authType:Xp(a.authType),authHeader:Xp(a.authHeader),baseUrl:Xp(a.baseUrl)})}return s}function Rb(e){return e.filter(t=>!t.framework||!gTe.has(t.framework.toLowerCase()))}function ATe(e){return[...new Set(Rb(e).map(t=>t.testDirectory?.replace(/\/+$/,"")).filter(t=>!!t))]}function yTe(e){return[...new Set(e.filter(t=>iv(t.framework)).map(t=>t.testDirectory?.replace(/\/+$/,"")).filter(t=>!!t))]}function iv(e){return!!(e&&gTe.has(e.toLowerCase()))}var Uwt={golang:"go",py:"python",py3:"python",python3:"python",ts:"typescript",js:"javascript","c++":"cpp",cplusplus:"cpp",cxx:"cpp",node:"javascript",nodejs:"javascript",rb:"ruby",cs:"csharp","c#":"csharp"};function Ci(e){let t=e?.trim().toLowerCase();if(t)return Uwt[t]??t}var bTe={py:"python",go:"go",java:"java",kt:"kotlin",rb:"ruby",cs:"csharp",ts:"typescript",tsx:"typescript",js:"javascript",jsx:"javascript",mjs:"javascript",cjs:"javascript",kts:"kotlin",rs:"rust",php:"php",scala:"scala",swift:"swift",ex:"elixir",exs:"elixir",dart:"dart",c:"c",h:"c",cpp:"cpp",cc:"cpp",cxx:"cpp",hpp:"cpp",hh:"cpp",groovy:"groovy"},$wt=new Set(["feature"]);function ETe(e){let t=e.lastIndexOf(".");return t>=0&&$wt.has(e.slice(t+1).toLowerCase())}var qwt=new Set(Object.values(bTe));function wTe(e){let t=Ci(e);return t!==void 0&&qwt.has(t)}function Bm(e){let t=e.lastIndexOf(".");if(!(t<0))return bTe[e.slice(t+1).toLowerCase()]}function C4(e){return[...new Set(Rb(e).map(t=>Ci(t.language)).filter(t=>!!t))]}function kb(e,t=[]){let r=C4(ao(e));return r.length?r:[...new Set(t.map(n=>Ci(n)).filter(n=>!!n))]}function CTe(e,t,r=[]){let n=kb(e,r);if(n.length===0)return!0;let s=Ci(t);return s!==void 0&&n.includes(s)}var ov=(s=>(s.Python="python",s.Node="node",s.Jvm="jvm",s.Go="go",s))(ov||{}),Gwt={python:"python",typescript:"node",javascript:"node",java:"jvm",kotlin:"jvm",go:"go"};function wi(e){let t=Ci(e);return t?Gwt[t]:void 0}var STe={python:["pyproject.toml","requirements.txt","setup.py","setup.cfg","Pipfile"],node:["package.json"],jvm:["pom.xml","build.gradle","build.gradle.kts"],go:["go.mod"]},Hwt=new Set(["node_modules",".git",".venv","venv","dist","build","target","vendor",".skyramp","__pycache__"]),jwt=3;function zwt(e,t){let r=[],n=(s,i)=>{let o=vn.join(e,s);if(STe[t].some(c=>ua.existsSync(vn.join(o,c)))&&r.push(s),i>=jwt)return;let a;try{a=ua.readdirSync(o,{withFileTypes:!0})}catch{return}for(let c of a)c.isDirectory()&&!Hwt.has(c.name)&&!c.name.startsWith(".")&&n(s==="."?c.name:vn.join(s,c.name),i+1)};return n(".",0),r}function Vwt(e,t){return ua.existsSync(vn.join(e,t,"tsconfig.json"))?"typescript":"javascript"}function Wwt(e,t,r){let n=vn.normalize(t??".");for(;;){if(STe[r].some(s=>ua.existsSync(vn.join(e,n,s))))return n;if(n==="."||n===vn.sep||n==="")return".";n=vn.dirname(n)}}function Ywt(e,t){let r;try{r=ua.readdirSync(vn.join(e,t))}catch{return!1}if(r.some(n=>/^playwright\.config\.[cm]?[jt]s$/.test(n)||/^cypress\.config\.[cm]?[jt]s$/.test(n)))return!0;for(let n of["package.json","pyproject.toml","requirements.txt"])try{if(/playwright|cypress|selenium/i.test(ua.readFileSync(vn.join(e,t,n),"utf-8")))return!0}catch{}return!1}function Zp(e,t){let r=ao(e),n=t==="ui"?r.filter(o=>iv(o.framework)):Rb(r),s=[];for(let o of n){let a=Ci(o.language),c=wi(a);a&&s.push({language:a,dir:c?Wwt(e,o.testDirectory,c):"."})}if(s.length)return hTe(s);let i=[];for(let o of Object.values(ov))for(let a of zwt(e,o)){if(t==="ui"&&!Ywt(e,a))continue;let c=o==="node"?Vwt(e,a):o==="jvm"?"java":o;i.push({language:c,dir:a})}return hTe(i)}function hTe(e){let t=new Set;return e.filter(r=>{let n=`${r.language}::${r.dir}`;return t.has(n)?!1:(t.add(n),!0)})}var Jwt=/"(?:react|react-dom|vue|svelte|@sveltejs\/kit|@angular\/core|next|nuxt|solid-js|preact)"\s*:/,Kwt={python:"py",typescript:"ts",javascript:"js",java:"java",kotlin:"kt",go:"go",ruby:"rb",csharp:"cs",php:"php",rust:"rs"};function Xwt(e){let t=new Map(Object.entries(Kwt).map(([s,i])=>[i,s])),r=new Map,n;try{n=(0,mTe.execFileSync)("git",["-C",e,"ls-files","-z","--",...[...t.keys()].map(s=>`*.${s}`)],{encoding:"utf-8",maxBuffer:64*1024*1024}).split("\0").filter(Boolean)}catch{return r}for(let s of n){let i=t.get(vn.extname(s).slice(1));i&&r.set(i,(r.get(i)??0)+1)}return r}function ITe(e){let t=Rb(ao(e)).map(l=>Ci(l.language)).filter(l=>!!l);if(t.length)return t[0];let r=Zp(e,"backend"),n=l=>{try{return Jwt.test(ua.readFileSync(vn.join(e,l,"package.json"),"utf-8"))}catch{return!1}},s=r.filter(l=>!(wi(l.language)==="node"&&n(l.dir))),i=Xwt(e),o=l=>i.get(l)??0,a=[...i.keys()].filter(l=>!wi(l)).sort((l,u)=>o(u)-o(l))[0],c=[...new Set([...(s.length?s:r).map(l=>l.language),...a?[a]:[]])];return c.length<=1?c[0]:c.sort((l,u)=>o(u)-o(l))[0]}function wQ(e){try{return new URL(e).hostname.replace(/^\[|\]$/g,"").toLowerCase()||void 0}catch{return}}function S4(e){if(!e)return!1;let t=e.toLowerCase();return t==="localhost"||t.endsWith(".localhost")||t==="::1"||t==="0.0.0.0"||/^127(\.\d{1,3}){3}$/.test(t)}function I4(e){let t=new Set;for(let r of ao(e)){let n=r.baseUrl&&wQ(r.baseUrl);n&&t.add(n)}return t}function CQ(e,t,r=I4(t)){let n;try{n=new URL(e).protocol}catch{return!1}if(n!=="http:"&&n!=="https:")return!1;let s=wQ(e);return s?S4(s)||r.has(s):!1}var tc=(s=>(s.Clean="clean",s.RecipeDrift="recipe-drift",s.ChangeBrokeBringup="change-broke-bringup",s.BringupFailed="bringup-failed",s))(tc||{}),ef=g.string().trim().min(1),Zwt=g.object({url:g.string().optional().describe("the URL probed, if the check is an HTTP request"),command:g.string().optional().describe("the command run, if the check is a script \u2014 preferred when auth is needed"),headers:g.record(g.string()).optional(),status:g.union([g.number(),g.string()]).optional().describe("expected HTTP status for a url check (default 200)"),output:ef.describe("what the check returned, VERBATIM \u2014 the bytes, nothing appended; this gate RE-RUNS the check")}),av=g.object({schemaVersion:g.literal(1),baseUrls:g.record(g.string()).optional().describe("service \u2192 base URL of the running app; omit on a failed bring-up, never invent one"),health:Zwt.optional(),teardown:ef.describe("command that tears the SUT down"),dependencies:g.record(g.boolean()).optional().describe("EVERY backing service: true = really deployed, false = mocked; REQUIRED when the app came up"),outcome:g.nativeEnum(tc).describe("clean | recipe-drift (you corrected the recipe) | change-broke-bringup (the PR broke it) | bringup-failed (environment, not the PR)"),recipeFixes:g.array(g.string()).optional().describe("required when outcome is recipe-drift: what was corrected"),bringupError:g.object({command:ef,output:ef}).optional().describe("required when bring-up failed: the exact failing command and what it printed"),suspectFiles:g.array(g.string()).optional().describe("required for change-broke-bringup: files THIS PR changed that the failure points at, relative to your working directory (a file in another checkout of the run: its absolute path)"),seeded:g.boolean(),startedAt:ef.describe("ISO timestamp"),revisions:g.array(g.object({component:ef.describe("a shared component listed above"),url:ef.describe("the URL on the component's own origin (scheme, host and port) that answers with its deployed commit"),output:ef.describe("what the URL returned, VERBATIM; this gate FETCHES each url")})).optional().describe("only when shared components are listed above: one check per component whose deployed commit you found")});function v4(e){let t=[],r=s=>!!s&&typeof s=="object"&&!Array.isArray(s);return e.outcome!=="change-broke-bringup"&&e.outcome!=="bringup-failed"?(!r(e.baseUrls)||Object.keys(e.baseUrls).length===0?t.push("baseUrls must be a non-empty object of service \u2192 url"):Object.values(e.baseUrls).every(s=>typeof s=="string"&&s.trim())||t.push("every baseUrls value must be a non-empty url string"),r(e.health)?!(typeof e.health.url=="string"&&e.health.url.trim())&&!(typeof e.health.command=="string"&&e.health.command.trim())&&t.push("health must record a url or a command (what you actually ran)"):t.push("health must be an object"),(!r(e.dependencies)||Object.keys(e.dependencies).length===0)&&t.push("dependencies must be a non-empty object of name \u2192 boolean (real vs mocked) when the SUT came up \u2014 account for every backing service so a mocked one can't be hidden behind a green run")):(r(e.bringupError)||t.push(`outcome "${String(e.outcome)}" requires bringupError { command, output } \u2014 the exact failing bring-up command and what it printed`),e.outcome==="change-broke-bringup"&&!(Array.isArray(e.suspectFiles)&&e.suspectFiles.length>0&&e.suspectFiles.every(s=>typeof s=="string"&&s.trim()))&&t.push("change-broke-bringup requires non-empty suspectFiles[] \u2014 the changed file(s) the failure points at; if you cannot point at a file this PR changed, the outcome is bringup-failed (environment/harness), not the PR's fault")),e.outcome==="recipe-drift"&&!(Array.isArray(e.recipeFixes)&&e.recipeFixes.length>0)&&t.push("recipe-drift outcome requires a non-empty recipeFixes[] describing what was corrected"),t}var Tb=(r=>(r.Full="full",r.Static="static",r))(Tb||{}),da=(n=>(n.Contract="contract",n.Integration="integration",n.Ui="ui",n))(da||{}),cv=(i=>(i.OwnSurface="own_surface",i.StateReadBack="state_read_back",i.Referential="referential",i.Lifecycle="lifecycle",i.Boundaries="boundaries",i))(cv||{}),Nm=(s=>(s.Update="update",s.Regenerate="regenerate",s.Delete="delete",s.Verify="verify",s))(Nm||{}),lv=(l=>(l.Equals="equals",l.NotEquals="not_equals",l.GreaterThan="greater_than",l.LessThan="less_than",l.Contains="contains",l.NotContains="not_contains",l.Matches="matches",l.Absent="absent",l.Present="present",l))(lv||{}),SQ=["absent","present"],co=g.string().trim().min(1),eCt=g.object({subject:co.describe('what the test READS: a response field path, the literal "status", or the on-screen text for a ui case \u2014 the FINAL read in a multi-step case'),comparator:g.nativeEnum(lv),value:g.union([g.string(),g.number(),g.boolean(),g.null()]).optional().describe("the ONE value a broken build would not produce; OMIT for absent/present")}),tCt=g.object({test:co.describe("existing test path, relative to its repository"),repository:co.optional().describe("owner/repo the test lives in, when the run has several checkouts; omit for the working checkout"),action:g.nativeEnum(Nm),evidence:co.describe("why \u2014 drift vs the diff, or a finding"),covers_behaviors:g.array(g.string()).optional().describe("analyze behavior ids this maintenance covers")}),rCt=g.object({surface:co.describe("a surface from the list, copied verbatim"),axis:g.nativeEnum(cv)}),nCt=g.object({id:co.describe("short-slug, unique across generate[]"),type:g.nativeEnum(da),target:co.describe('"METHOD /path", or a business-flow name'),from_finding:g.string().optional().describe("analyze finding id this case catches"),from_behavior:g.string().optional().describe("analyze behavior id this case pins"),derived_from:rCt.optional().describe("the surface \xD7 axis this case pins when no analyze behavior or finding names it \u2014 one of the three is required"),rationale:co.describe("why this test matters"),expected_observable:co.describe("one sentence for a human: what a catching test asserts, and what a broken build does instead"),expected:eCt.describe("the same intent AS DATA \u2014 see the rules below"),seededPrecondition:co.optional().describe("seeded non-initial state + how to reach it; only when a seeded-state transition applies"),verifiable:g.boolean().describe("true only when a live SUT could confirm this test (full mode)")}),sCt=g.object({covered:g.array(g.string()).optional(),abstain:g.string().optional()}).superRefine((e,t)=>{let r=Array.isArray(e.covered),n=typeof e.abstain=="string";if(r===n)return void t.addIssue({code:g.ZodIssueCode.custom,message:"must have exactly one of covered[] or abstain"});r&&e.covered.length===0&&t.addIssue({code:g.ZodIssueCode.custom,path:["covered"],message:"must list at least one id (else abstain)"}),n&&!e.abstain.trim()&&t.addIssue({code:g.ZodIssueCode.custom,path:["abstain"],message:"must give a reason"})}).describe('{ "covered": [ids] } OR { "abstain": "reason" } \u2014 exactly one'),iCt=g.object({surface:co.describe("a surface from the list, copied verbatim"),axes:g.object(Object.fromEntries(Object.values(cv).map(e=>[e,sCt])))}),uv=g.object({schemaVersion:g.literal(1),mode:g.nativeEnum(Tb),maintenance:g.array(tCt),generate:g.array(nCt),surfaces:g.array(iCt).describe("one entry per changed surface in the list \u2014 none missing, none invented"),budget:g.object({total:g.number().describe("case count"),rationale:co}),summary:co.describe("one paragraph")});function IQ(e,t,r){let n=s=>{if(!s||!Array.isArray(s.covered))return s;let i=s.covered.filter(o=>!t.has(o));return i.length>0?{covered:i}:{abstain:`its only case(s) were ${r}: ${s.covered.join(", ")}`}};return e.map(s=>({...s,axes:Object.fromEntries(Object.entries(s.axes??{}).map(([i,o])=>[i,n(o)]))}))}function vTe(e){let t=[],r=new Set;if(Array.isArray(e.generate)&&e.generate.forEach((n,s)=>{let i=n?.id;typeof i=="string"&&(r.has(i)&&t.push(`generate[${s}].id "${i}" is duplicated`),r.add(i))}),Array.isArray(e.generate)&&e.generate.forEach((n,s)=>{let i=n;!i.from_finding&&!i.from_behavior&&!i.derived_from&&t.push(`generate[${s}] carries none of from_behavior, from_finding or derived_from \u2014 say what obligation it pins`)}),Array.isArray(e.surfaces)){let n=new Set;e.surfaces.forEach((s,i)=>{let o=s;if(typeof o.surface=="string"){let a=o.surface.trim().toLowerCase();n.has(a)&&t.push(`surfaces[${i}] "${o.surface}" is listed twice`),n.add(a)}for(let[a,c]of Object.entries(o.axes??{})){let l=c?.covered;if(Array.isArray(l))for(let u of l)(typeof u!="string"||!r.has(u))&&t.push(`surfaces[${i}].axes.${a}.covered references unknown generate id ${JSON.stringify(u)}`)}})}return t}var _m=(r=>(r.Ready="ready",r.Blocked="blocked",r))(_m||{}),zr=g.string().trim().min(1),oCt=g.object({action:g.enum(["start","run","wait"]),text:zr,source:g.enum(["file","notes","testbot"]).describe("file: a file in the repository; notes: the Target's notes; testbot: Testbot's own finding"),file:zr.nullable().describe("the file the step came from, relative to the component's repository; null unless source is file")}).strict().refine(e=>e.source==="file"==(e.file!==null),{path:["file"],message:"a file exactly when the step came from one"}),aCt=/^[A-Za-z_][A-Za-z0-9_]*$/,Bb=/^(?!GITHUB_)(?!SKYRAMP_RUNNER_)[A-Z][A-Z0-9_]{0,99}$/,Nb=(o=>(o.VersionEndpoint="version_endpoint",o.DeployWorkflow="deploy_workflow",o.KubernetesLabels="kubernetes_labels",o.Command="command",o.Api="api",o.NotTracked="not_tracked",o))(Nb||{}),Vn="shared",cCt=zr.refine(e=>{try{let t=new URL(e);return["http:","https:"].includes(t.protocol)&&!t.username&&!t.password}catch{return!1}},"an http(s) URL with no login in it"),_b=g.object({address:cCt,readyWhen:zr.nullable(),credentials:g.array(g.string().regex(Bb)).max(50),revisionSources:g.array(g.object({method:g.nativeEnum(Nb),detail:zr.describe("what Testbot reads, such as GET /version or the deploy workflow's name; for not_tracked, why nothing shows it"),components:g.array(zr).min(1).describe("the components it tells the revision of, by name")}).strict()).max(50)}).strict(),x4={steps:g.array(oCt).max(20).optional().describe("how it starts, in order"),kind:g.enum(["web_app","service","database","external_service"]).optional(),from:g.object({file:zr,service:zr.nullable()}).strict().optional(),credentials:g.array(g.string().max(100).regex(aCt)).max(50).optional().describe("the Target's credentials this component's recipe uses: a sign-in (a test user, password or token) or an API key, which the workflow supplies from a GitHub Actions secret as the environment variable of the credential's name; give each name exactly as the recipe reads it. Names only, never a value, and not a secret the recipe generates itself")},lCt=g.object({name:zr,repository:zr.describe("owner/repo of the Target repository the component is built or started from, such as the one whose compose file starts its database"),healthy:g.boolean(),...x4,deployed:zr.nullable().optional().describe('only with "environment": the revision the component runs, as you read it from its revision source, or null when you could not tell')}),uCt=g.object({component:zr,problem:zr,evidence:zr.describe("the exact command and output that shows it"),fix:zr.describe("what the user must provide, and where")}),dCt=g.object({framework:zr,file:zr.describe("the test file run, relative to its repository root"),result:g.enum(["pass","fail","error"])}),pCt=g.object({input:zr.describe("the part of the user's SUT input, as given"),reason:zr}),dv=g.object({schemaVersion:g.literal(1),status:g.nativeEnum(_m).describe("ready only if every component came up healthy and the recipe is written"),components:g.array(lCt),suites:g.array(dCt).default([]).describe("one run per test suite the Target's repositories have; empty when they have none"),inputsUsed:g.array(zr).default([]).describe("the parts of the user's SUT input the recipe used as given"),inputsUsedHow:g.array(g.object({input:zr.describe("exactly as written in inputsUsed; an entry that is not there is left out"),how:zr.describe("what the recipe did with it")})).optional(),inputsNotUsed:g.array(pCt).default([]).describe("the parts it did not use, and why. A part that is wrong goes here, with what was wrong, even when the recipe does something like it instead: one that names a file, command, port, route or setting that does not exist or does not work (SKYR-5265)"),blockers:g.array(uCt).describe("required (non-empty) when status is blocked"),environment:_b.optional().describe("only when the whole SUT is an existing deployment you tested where it runs, building and starting nothing: its address, what says it is ready, the credentials that reach it, and how to read what each component runs. Leave it out when any part was built or started on the runner, or reaching it needed something there (a tunnel, port-forward, proxy or client certificate)")});function R4(e){let t=fCt(e);if(t.length>0)return t;if(e.status==="blocked"&&!(Array.isArray(e.blockers)&&e.blockers.length>0))return['status "blocked" requires at least one blocker { component, problem, evidence, fix } \u2014 say what stopped the bring-up'];if(e.status==="ready"){let r=Array.isArray(e.components)?e.components:[];if(r.length===0)return['status "ready" requires at least one component \u2014 name each component the recipe brought up healthy'];let n=r.filter(s=>s?.healthy!==!0).map(s=>String(s?.name??"?"));if(n.length>0)return[`status "ready" requires every component healthy; not healthy: ${n.join(", ")} \u2014 answer "blocked" with a blocker for each, or bring them up and answer "ready" again`]}return[]}function fCt(e){let t=Array.isArray(e.components)?e.components:[],r=e.environment;if(!r){let a=t.filter(c=>c?.deployed!==void 0||c?.repository===Vn).map(c=>String(c?.name??"?"));return a.length>0?[`"deployed" and a "${Vn}" repository belong only to an answer with "environment"; remove them from: ${a.join(", ")}`]:[]}let n=[],s=t.filter(a=>a?.deployed===void 0).map(a=>String(a?.name??"?"));s.length>0&&n.push(`with "environment", every component says what it runs in "deployed" (null when you could not tell); missing for: ${s.join(", ")}`);let i=new Set(t.map(a=>a?.name)),o=(r.revisionSources??[]).flatMap(a=>a?.components??[]).filter(a=>!i.has(a));return o.length>0&&n.push(`each revision source names components of this answer; not one of them: ${o.map(String).join(", ")}`),n}var Pm=64*1024,Qm=512*1024,vQ=/[\u0000-\u0008\u000b\u000e-\u001f\u007f]|[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/;var Pb=".skyramp/sut/deployed-revisions.sh",k4=(r=>(r.Running="running",r.Requested="requested",r))(k4||{});var Bor=new Set(Object.values(k4));var hCt=/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/,xTe=e=>hCt.test(e);function xQ(e){return e?.find(t=>t.path===Pb)}var Nor=`Fix ${Pb} so it prints the full commit each component runs, then set it up again.`;function T4(e,t){let r=new Set(t.filter(s=>s.method!=="not_tracked").flatMap(s=>s.components)),n=new Set(t.filter(s=>s.method==="not_tracked").flatMap(s=>s.components));return e.filter(s=>r.has(s.name)||!n.has(s.name))}var Ys="shared-environment",gCt=1e4,RTe=5,TTe=64*1024,_4=300,ACt=200,yCt=/\b[0-9a-fA-F]{7,64}\b/g,bCt=/\bsha256:[0-9a-fA-F]{64}\b/g,ECt=/\b(commit|sha|revision|rev|hash)\b/i,wCt=(e,t)=>new RegExp(`(?<![A-Za-z0-9])(commit|sha|revision|rev|hash)["']?\\s*[:=]?\\s*["']?${t}(?![0-9A-Za-z])`,"i").test(e),Qb=e=>e.slice(0,_4),pv=e=>Qb(e.trim())||"(empty response)";function kTe(e,t){let r=e.split(/\r?\n/).find(s=>s.toLowerCase().includes(t)).trim(),n=r.toLowerCase().indexOf(t);return r.length<=_4?r:r.slice(Math.max(0,n-100),Math.max(0,n-100)+_4)}function fv(e,t){if("error"in t)return{status:"unverifiable",observedRevision:null,evidence:pv(t.error)};let r=e.toLowerCase(),n=l=>ECt.test(kTe(t.body,l)),s=l=>r?r.startsWith(l)&&n(l):wCt(t.body,l),i=(t.body.replace(bCt," ").match(yCt)??[]).map(l=>l.toLowerCase()).filter(l=>/[a-f]/.test(l)||s(l)),o=[...new Set(i)].filter((l,u,d)=>!d.some(p=>p!==l&&p.startsWith(l)));if(o.length>1)return{status:"unverifiable",observedRevision:null,evidence:pv(`The answer names more than one revision: ${o.join(", ")}`)};let[a]=o;return a?{status:r.startsWith(a)?"verified":"mismatch",observedRevision:a,evidence:kTe(t.body,a)}:{status:"unverifiable",observedRevision:null,evidence:pv(t.body)}}async function CCt(e){if(!e.body)return"";let t=e.body.getReader(),r=[],n=0;for(;;){let{done:s,value:i}=await t.read();if(s)return Buffer.concat(r).toString("utf-8");if(n+=i.byteLength,n>TTe){await t.cancel();return}r.push(i)}}async function hv(e,t=fetch){try{let r=AbortSignal.timeout(gCt),n=new URL(e).origin,s=new URL(e);for(let i=0;;i+=1){let o=await t(s,{redirect:"manual",signal:r}),a=o.status>=300&&o.status<400?o.headers.get("location"):null;if(a){let l=new URL(a,s);if(l.origin!==n)return{error:`${e} redirects to another origin (${l.origin})`};if(i>=RTe)return{error:`${e} redirects more than ${RTe} times`};s=l;continue}let c=await CCt(o);return c===void 0?{error:`${e} answered more than ${TTe/1024} KiB`}:o.ok?{body:c}:{error:`${e} answered ${o.status}: ${c.trim()}`}}}catch(r){let n=r instanceof Error&&r.cause instanceof Error?`: ${r.cause.message}`:"";return{error:`${e} could not be read: ${r instanceof Error?r.message:String(r)}${n}`}}}var qor=new Set(["version_endpoint","api"]);function Db(e){return(e?.components??[]).filter(t=>!!t.repository&&!!t.expectedRevision)}function RQ(e){return Db(e.sharedEnvironment).filter(t=>!t.revisionUrl&&!t.revisionScript)}async function SCt(e,t,r=[]){let{name:n,url:s}=e;if(!e.versionUrl)return{name:n,url:s,version:null,evidence:"No version URL is configured."};let i=gr(await hv(e.versionUrl,t),r);if("error"in i)return{name:n,url:s,version:null,evidence:pv(i.error)};let o=i.body.split(/\r?\n/).map(a=>a.trim()).find(Boolean);return{name:n,url:s,version:o?o.slice(0,ACt):null,evidence:pv(o??"")}}var P4=(e,t)=>!!e&&!!t&&e.toLowerCase()===t.toLowerCase(),BTe=(e,t)=>e.name===t.name&&P4(e.repository,t.repository);async function ICt(e,t,r){let{frozen:n,mergeCommit:s,mergedHead:i}=t;return!s||!i?"not_contained":i.toLowerCase()===n.toLowerCase()?"contained":r?.commitContains?r.commitContains({repository:e,ancestor:n,descendant:i}):"unknown"}async function B4(e,t,r){let n=t.observedRevision;if(t.status!=="mismatch"||!n||!e.acceptedRevisions)return t;let{frozen:s,mergeCommit:i,mergedHead:o}=e.acceptedRevisions,a=await ICt(e.repository,e.acceptedRevisions,r),c=[s,...i&&a==="contained"?[i]:[]],l=f=>`${f===s?"the frozen revision":"the merge commit"} ${jn(f)}`,u=(f,h)=>({status:f,observedRevision:n,evidence:Qb(`${h} (${t.evidence})`)}),d=c.find(f=>f.toLowerCase().startsWith(n));if(d)return u("verified",`observed ${jn(n)} is ${l(d)}`);if(!r?.commitContains)return u("unverifiable",`could not tell whether observed ${jn(n)} contains ${c.map(jn).join(" or ")}: this run cannot compare commits`);let p=[];for(let f of c){let h=await r.commitContains({repository:e.repository,ancestor:f,descendant:n});if(h==="contained")return u("verified",`observed ${jn(n)} contains ${l(f)}`);h==="unknown"&&p.push(f)}return p.length?u("unverifiable",`could not tell whether observed ${jn(n)} contains ${p.map(jn).join(" or ")}: GitHub did not answer the compare`):a==="unknown"&&o?u("unverifiable",`could not tell whether the merged head ${jn(o)} contains ${jn(s)}: GitHub did not answer the compare`):u("mismatch",`observed ${jn(n)} does not contain ${c.map(jn).join(" or ")}`)}function NTe(e,t){let r=(i,o=null)=>({status:"unverifiable",observedRevision:o,evidence:Qb(i)});if(!t)return r("Not checked: this run cannot run deployed-revisions.sh.");if(!t.ok)return r(t.error);let n=t.revisions[e.name];return n?xTe(n.revision)?n.state==="requested"?r(`deployed-revisions.sh reports only the requested state for ${e.name} (${n.revision}), which alone does not show what runs.`,n.revision):{status:e.expectedRevision.toLowerCase().startsWith(n.revision)?"verified":"mismatch",observedRevision:n.revision,evidence:Qb(`deployed-revisions.sh: ${e.name} runs ${n.revision}`)}:r(`deployed-revisions.sh printed ${n.revision} for ${e.name}, which is not a full commit.`):r(`deployed-revisions.sh printed no revision for ${e.name}.`)}async function kQ(e,t,r,n=[],s,i){let o=(c,l)=>fv(c,gr(l,n));return{components:await Promise.all(Db(e).map(async c=>{let l={name:c.name,repository:c.repository,url:c.url,expectedRevision:c.expectedRevision};if(c.revisionScript)return{...l,source:"probe",...await B4(c,NTe(c,i),s)};if(c.revisionUrl)return{...l,source:"probe",...await B4(c,o(c.expectedRevision,await hv(c.revisionUrl,r)),s)};let u=t.find(p=>p.component===c.name),d=o(c.expectedRevision,u?{body:u.output}:{error:"Deploy cited no check of its deployed revision."});return{...l,source:"agent",...await B4(c,d,s)}})),dependencies:await Promise.all(e.dependencies.map(c=>SCt(c,r,n)))}}async function vCt(e,t,r,n){if(e.revisionScript){let o=NTe(e,n);return{failed:!n?.ok,now:o,noCommit:o.status==="unverifiable"}}let s=gr(await hv(e.revisionUrl,t),r),i=fv(e.expectedRevision,s);return{failed:"error"in s,now:i,noCommit:!i.observedRevision}}async function xCt(e,t,r,n=[],s){let i=await Promise.all(t.components.map(async o=>{let a=e.components.find(p=>BTe(p,o)),c=o.observedRevision?.toLowerCase();if(!c||!(a?.revisionUrl||a?.revisionScript))return o;let{failed:l,now:u,noCommit:d}=await vCt({...a,expectedRevision:o.expectedRevision},r,n,s);if(d){let p=l?"second read failed":"second read names no one commit",f=`first read ${jn(o.observedRevision??c)}; ${p}: ${u.evidence}`;return{...o,status:"unverifiable",observedRevision:null,evidence:Qb(f)}}return u.observedRevision.startsWith(c)||c.startsWith(u.observedRevision)?o:{...o,status:"unverifiable",observedRevision:u.observedRevision,evidence:Qb(`changed from ${o.observedRevision} to ${u.observedRevision} during the run`)}}));return{...t,components:i}}async function Q4(e,t){return t.components.some(r=>r.revisionScript)?e.deployedRevisions?.():void 0}async function _Te(e,t){let r=e.inputs.sharedEnvironment,n=ee(e,`${Ys}.json`);if(!e.inputs.appRunning||!r||!n)return;let s=await xCt(r,n,t,e.secretValues??[],await Q4(e,r));ot(e,Ys,gr(s,e.secretValues??[]),[])}function mv(e){return Db(e).length>0||(e?.dependencies.length??0)>0}function D4(e){let t=e.inputs?.sharedEnvironment;if(!mv(t))return;let r=ee(e,`${Ys}.json`);if(r)return{components:QTe(t,r.components),dependencies:r.dependencies};let n="Not checked: deploy did not complete.";return{components:Db(t).map(s=>PTe(s,n)),dependencies:t.dependencies.map(s=>({name:s.name,url:s.url,version:null,evidence:n}))}}function TQ(e){return e?.components.length?[`Testbot read which commit each component runs, before this session: ${e.components.map(r=>{let n=r.observedRevision?`${r.name} runs ${jn(r.observedRevision)}`:`${r.name} answered no commit`,s=r.status==="verified"?"verified":"not proven to run the change";return`${n}, ${s} (${r.evidence})`}).join("; ")}.`,"Which commit is deployed is Testbot's to check, not yours: record no gap, finding, issue or next step about it, and don't compare the deployed commit with your checkout."].join(" "):void 0}var PTe=(e,t)=>({name:e.name,repository:e.repository,url:e.url,expectedRevision:e.expectedRevision,observedRevision:null,status:"unverifiable",source:e.revisionUrl||e.revisionScript?"probe":"agent",evidence:t});function QTe(e,t){let r=new Set(t.map(s=>s.name.toLowerCase())),n=Db(e).filter(s=>s.touched&&!r.has(s.name.toLowerCase()));return[...t,...n.map(s=>PTe(s,"Not checked: the run recorded no read of it."))]}function Ob(e){return(D4(e)?.components??[]).filter(t=>t.status!=="verified")}function O4(e,t,r){if(!e.appRunning||r===!1)return[];let n=(e.sharedEnvironment?.components??[]).filter(s=>s.touched);return QTe(e.sharedEnvironment,t?.components??[]).filter(s=>s.status!=="verified"&&n.some(i=>BTe(i,s)))}function DTe(e){let t=r=>{let n=r.observedRevision?`runs ${jn(r.observedRevision)}`:"no revision could be read";return`${r.name} (${r.repository}) must run ${jn(r.expectedRevision)}, but ${n}: ${r.evidence}`};return e.length?`the Test Environment does not verifiably run the change: ${e.map(t).join("; ")}`:void 0}function BQ(e){return(e?.components??[]).filter(t=>!!t.repository&&!t.expectedRevision)}function OTe(e,t=!1){return`${e.name}: ${e.repository} is not one of the run's sources, so its revision cannot be verified and ${MTe(t)}`}var MTe=e=>e?"runtime tests against it do not validate this change":"no runtime test ran against it";function FTe(e,t=!1){let r=jn(e.expectedRevision),n=e.status==="mismatch"?`the shared environment runs ${e.repository} at ${e.observedRevision}, not ${r}`:`could not verify that the shared environment runs ${e.repository} at ${r}`;return`${e.name}: ${n}, so ${MTe(t)}`}var RCt=/^\s*(POST|PUT|PATCH|DELETE)\s/i,N4=e=>e.mutates===!0;function Dm(e){return(e?.components??[]).filter(t=>!!t.repository)}function lo(e){let t=e.sharedEnvironment;return!!t&&Dm(t).length>0&&!t.allowMutation&&!e.planOnly}function kCt(e){let t=n=>RCt.test(n.target)||N4(n),r=[...e.generate.filter(t).map(n=>({id:n.id,target:n.target})),...e.maintenance.filter(N4).map(n=>({id:n.test}))];return{...e,generate:e.generate.filter(n=>!t(n)),surfaces:IQ(e.surfaces,new Set(r.map(n=>n.id)),"withheld because they change a shared environment the policy protects"),maintenance:e.maintenance.filter(n=>!N4(n)),withheld:r}}function Mb(e,t){return t&&lo(e)?kCt(t):t}function NQ(e){return ee(e,"recommend.json")?.withheld??[]}function LTe(e){return`${e.id}${e.target?` (${e.target})`:""}: needs a shared-environment mutation the policy does not allow`}var TCt=40,jTe="health.command cannot be re-executed: ",UTe=4e3,BCt=9e4,NCt=5e3;function _Ct(e,t){let r=e.slice(0,UTe),n=t.slice(0,UTe);if(!r||!n)return"";let s=new Uint32Array(n.length+1),i=new Uint32Array(n.length+1),o=0,a=0;for(let c=1;c<=r.length;c++){for(let u=1;u<=n.length;u++){if(r[c-1]!==n[u-1]){i[u]=0;continue}i[u]=s[u-1]+1,i[u]>o&&(o=i[u],a=c)}let l=s;s=i,i=l}return r.slice(a-o,a)}function $Te(e){let t=e.search(/[[{]/);if(t<0)return;let r=e[t],n=r==="{"?"}":"]",s=0,i=!1,o=!1;for(let a=t;a<e.length;a++){let c=e[a];if(i){o?o=!1:c==="\\"?o=!0:c==='"'&&(i=!1);continue}if(c==='"')i=!0;else if(c===r)s++;else if(c===n&&--s===0)try{return JSON.parse(e.slice(t,a+1))}catch{return}}}function _Q(e,t=0){return t>4?"\u2026":e===null?"null":Array.isArray(e)?e.length===0?"[]":`[${[...new Set(e.map(r=>_Q(r,t+1)))].sort().join("|")}]`:typeof e=="object"?`{${Object.keys(e).sort().map(r=>`${r}:${_Q(e[r],t+1)}`).join(",")}}`:typeof e}function qTe(e,t){let r=$Te(e),n=$Te(t);if(r!==void 0&&n!==void 0){let a=_Q(r),c=_Q(n);return a===c?{ok:!0}:{ok:!1,why:`it answered ${c.slice(0,200)}, but the recorded check answered ${a.slice(0,200)}`}}let s=e.split(`
|
|
230
230
|
`).map(a=>a.trim()).filter(Boolean).sort((a,c)=>c.length-a.length)[0]??e,i=Math.min(s.length,TCt),o=_Ct(s,t);return o.length>=i?{ok:!0}:{ok:!1,why:`nothing in it resembles "${s.slice(0,60)}" (longest run in common: ${o.length} chars, need ${i})`}}function PCt(e){return Ib(av,v4,e).errors}function QCt(e,t=[]){let{value:r,error:n}=Xt(e,t);if(n)return{errors:[n]};let s=PCt(r);return s.length>0?{errors:s}:{deploy:r,errors:[]}}function Om(e,t,r=[]){let n=QCt(e.stdout,r);return n.deploy&&t&&(n.deploy=DCt(n.deploy,t)),n}var GTe=".skyramp/sut/health-check.sh";function DCt(e,t){let r=e.health?.command?.trim();return!r||!e.health||!EQ(r,tf.join(t,GTe),"deploy")?e:{...e,health:{...e.health,command:`bash ${GTe}`}}}function OCt(){return{httpGet(e,t){let r=S4(wQ(e))?["-k"]:[],n=(0,M4.spawnSync)("curl",["-sS",...r,"-m","20","-o","-","-w",`
|
|
231
231
|
%{http_code}`,...MCt(t),e],{encoding:"utf-8",timeout:25e3});if(n.status!==0)return{error:(n.stderr||n.error?.message||"curl failed").trim()};let s=n.stdout??"",i=s.lastIndexOf(`
|
|
232
232
|
`),o=Number(s.slice(i+1).trim());return{status:Number.isFinite(o)?o:0,body:s.slice(0,i)}},runCommand(e,t){let{steps:r,reason:n}=rd(e);if(!r)return{exitCode:1,output:`${jTe}${n}`};let s="";for(let i of r){let o=(0,M4.spawnSync)(i[0],i.slice(1),{cwd:t,encoding:"utf-8",timeout:6e4,env:Ye()});if(s+=`${o.stdout??""}${o.stderr??""}`,(o.status??1)!==0)return{exitCode:o.status??1,output:s}}return{exitCode:0,output:s}}}}function MCt(e){return e?Object.entries(e).flatMap(([t,r])=>["-H",`${t}: ${r}`]):[]}function zTe(e){return e.trim().replace(/^\.\//,"").replace(/\/+$/,"")}function FCt(e){if(!e.inputs)return[];let t=sr(e),r=s=>{let i=typeof s=="string"?s:s?.path??s?.file;return typeof i=="string"&&i.trim()?zTe(i):void 0},n=(s,i)=>s?(i??[]).flatMap(o=>{let a=r(o);return a?[{repository:s,path:a}]:[]}):[];return[...n(Qr(e.inputs).slug,[...t?.changedFiles??[],...t?.newFiles??[]]),...(t?.relatedChanges??[]).flatMap(s=>n(s.repository,s.changedFiles))]}function LCt(e,t){let r=tf.resolve(e.inputs.repositoryPath,t.trim()),n=mQ(e.inputs,r),s=n&&tf.relative(n.path,r);return n&&s?{repository:n.slug,path:s.split(tf.sep).join("/")}:void 0}function VTe(e=OCt(),t={}){let r=t.budgetMs??BCt,n=t.intervalMs??NCt,s=t.sleep??(i=>new Promise(o=>setTimeout(o,i)));return{name:"deploy-health",async verify(i,o){let{deploy:a,errors:c}=Om(o,i.inputs?.repositoryPath,i.secretValues);if(!a)return{name:this.name,passed:!1,details:c.slice(0,6).join("; ")};if(a.outcome==="bringup-failed")return{name:this.name,passed:!0};if(a.outcome==="change-broke-bringup"){let I=FCt(i),v=(a.suspectFiles??[]).map(zTe),T=v.filter(k=>{let R=LCt(i,k);return!!R&&vb(I,R.repository,R.path)});return I.length>0&&T.length===0?{name:this.name,passed:!1,details:`change-broke-bringup claimed, but none of suspectFiles [${v.slice(0,4).join(", ")}] is a file this PR changed \u2014 that is not attributable to the PR. Re-run bring-up; if it still fails for a reason outside the diff, record outcome "bringup-failed" (with bringupError) instead.`}:{name:this.name,passed:!0}}let l=a.health;if(!l)return{name:this.name,passed:!1,details:"health is missing although the app is recorded as up"};let u=Object.values(l.headers??{}).some(I=>/<[^>]+>/.test(String(I))),d=(I,v=!1)=>({ok:!1,details:I,transient:v}),p=()=>{if(!CQ(l.url,i.inputs.repositoryPath))return d(`health url ${l.url} is not the app under test \u2014 this gate probes only loopback or a host declared in .skyramp/workspace.yml, never an arbitrary destination. Record the URL the deployed app actually answers on.`);let I=e.httpGet(l.url,l.headers);if("error"in I)return d(`health url ${l.url} unreachable: ${I.error}`,!0);let v=Number(l.status??200);if(I.status!==v)return d(`health url returned ${I.status}, expected ${v}`,!0);if(!I.body.trim())return d("health url returned empty body \u2014 must return data",!0);let T=(l.output??"").trim();if(T){let k=qTe(T,I.body);if(!k.ok)return d(`health url ${l.url} no longer answers as recorded \u2014 ${k.why}. Either the app is not answering as it did at deploy time, or health.output is not verbatim: record ONLY the response body, with no commentary appended.`)}return{ok:!0}},f=()=>{let I=e.runCommand(l.command,i.inputs.repositoryPath),v=I.output.startsWith(jTe);if(I.exitCode!==0)return d(`health command exited ${I.exitCode}: ${I.output.slice(0,200)}`,!v);if(!I.output.trim())return d("health command produced no output",!0);let T=(l.output??"").trim();if(T.length<16)return d(`health command output "${T}" is too thin to be a data check \u2014 the check must return real application data, not a status token`);let k=qTe(T,I.output);return k.ok?{ok:!0}:d(`health command did not reproduce its recorded check \u2014 ${k.why}. Either the app is not answering as it did at deploy time, or health.output is not verbatim: record ONLY what the command wrote, with no commentary appended.`)},h;if(l.command&&(u||!l.url))h=f;else{if(l.url&&u)return{name:this.name,passed:!1,details:`health.headers contains a documented placeholder (${Object.entries(l.headers??{}).filter(([,I])=>/<[^>]+>/.test(String(I))).map(([I])=>I).join(", ")}), and this gate RE-EXECUTES the probe \u2014 a placeholder cannot authenticate. Record a health.command instead (a script that fetches its own token and hits the URL); do not put a real credential in the artifact.`};l.url?h=p:l.command&&(h=f)}if(!h)return{name:this.name,passed:!1,details:"health has neither url nor command"};let A=Date.now(),C=A+r,w=h();for(;!w.ok&&w.transient&&Date.now()+n<C;)await s(n),w=h();if(w.ok)return{name:this.name,passed:!0};let b=Math.round((Date.now()-A)/1e3);return{name:this.name,passed:!1,details:w.transient&&b>0?`${w.details} (still failing after ${b}s)`:w.details}}}}function uo(e){return gv(e)&&Ob(e).length===0&&BQ(e.inputs?.sharedEnvironment).length===0}function gv(e){let t=UCt(e);if(!t&&e.state.stages.deploy?.status!=="passed")return!1;let r=ee(e,"deploy.json");return r?r.outcome==="clean"||r.outcome==="recipe-drift":!t}function UCt(e){let t=e.state.stages.plan,r=e.state.stages.deploy;return!(e.inputs?.pipeline?e.inputs.pipeline==="two-step":!r)||t?.status!=="passed"?!1:!r||r.updatedAt<=t.updatedAt}async function $Ct(e,t,r){let n=new URL(t.url).origin;return(URL.canParse(e.url)?new URL(e.url).origin:e.url)===n?hv(e.url,r):{error:`${e.url} is not on ${n}, where ${t.name} runs`}}function WTe(e){return{name:"shared-revisions",async verify(t,r){let{deploy:n}=Om(r,t.inputs?.repositoryPath,t.secretValues),s=RQ(t.inputs??{}),i=[];for(let o of n?.revisions??[]){let a=s.find(d=>d.name===o.component);if(!a){i.push(`revisions names "${o.component}", which is not a shared component listed for a revision check`);continue}let c=await $Ct(o,a,e);if("error"in c){i.push(`the revision check of ${a.name} could not be re-run: ${gr(c.error,t.secretValues??[]).slice(0,200)}`);continue}let l=fv(a.expectedRevision,{body:o.output}),u=fv(a.expectedRevision,c);l.observedRevision!==u.observedRevision&&i.push(`the revision check of ${a.name} names ${u.observedRevision??"no commit"} when re-run, but its recorded output names ${l.observedRevision??"no commit"} \u2014 record what the check returned, verbatim, or leave the component out`)}return i.length?{name:this.name,passed:!1,details:i.join("; ")}:{name:this.name,passed:!0}}}}async function YTe(e,t,r){let n=e.inputs?.sharedEnvironment;if(!mv(n))return{};let{deploy:s}=Om(t,e.inputs.repositoryPath,e.secretValues),i=await kQ(n,s?.revisions??[],r,e.secretValues??[]);return ot(e,Ys,gr(i,e.secretValues??[]),[])}function JTe(e,t){let{deploy:r,errors:n}=Om(t,e.inputs?.repositoryPath,e.secretValues),s=r?.health?.headers?{...r,health:{...r.health,headers:Object.fromEntries(Object.keys(r.health.headers).map(o=>[o,"<redacted>"]))}}:r,i=gr(s,e.secretValues??[]);return ot(e,"deploy",i,n)}var nd=(o=>(o.Place="place",o.Update="update",o.Regenerate="regenerate",o.Delete="delete",o.Verify="verify",o.None="none",o))(nd||{}),Ii=(n=>(n.Pass="pass",n.Fail="fail",n.Skip="skip",n))(Ii||{}),pa=g.string().trim().min(1),KTe=g.object({case:pa.describe("a covers id \u2014 the recommend case this test realizes"),status:g.nativeEnum(Ii)}),XTe=g.object({exit:g.number({invalid_type_error:"must be a number or null"}).nullable().describe("process exit of the re-run; null when no SUT was available"),details:g.string().optional().describe("what failed, or why a file was not run \u2014 required on any non-zero exit"),tests:g.array(KTe).optional().describe("ONE ENTRY PER CASE the file covers; must agree with exit (0 \u2192 no fail; non-zero \u2192 at least one)")}),ZTe=g.object({from:g.string().optional().describe("staged path \u2014 required for placements"),file:pa.describe("final path relative to its repository, inside that checkout, with a source-file extension"),repository:pa.optional().describe("owner/repo the file lives in, when the run has several checkouts; omit for the working checkout"),action:g.nativeEnum(nd),covers:g.array(g.string()).optional().describe("recommend ids a placed test covers"),repro:g.boolean().optional().describe("this test is MEANT to fail \u2014 it reproduces a reported bug"),finding:g.boolean().optional().describe("a non-repro test that failed on a REAL product bug"),placementBroken:g.boolean().optional().describe('placement/harness problem one repair could not fix \u2014 every reran.tests entry is then "skip"'),keptReason:pa.optional().describe("why a planned delete/regenerate was declined and the file kept"),isolatedRun:pa.optional().describe('only for a test marked "runs alone": the command, from its checkout root and without a shell, that runs only this file and writes its JUnit XML into $SKYRAMP_RESULTS_DIR (named by its path: no shell expands it) \u2014 the harness runs it, so leave reran.exit null'),reran:XTe,assertionCount:g.number().int().nonnegative().optional().describe("[derived] assertions reachable from the file, counted at write time")}).refine(e=>e.action!=="place"||typeof e.from=="string"&&e.from.trim().length>0,{message:'is a place but has no "from" staged path',path:[]}),eBe=(r=>(r.Junit="junit",r.Converter="converter",r))(eBe||{}),qCt=pa.optional().describe("the items[].file this test is in \u2014 needed only when two of this suite's files cover the same case id"),GCt=g.object({results:pa.describe("the JUnit XML file this run wrote, a plain file name inside $SKYRAMP_RESULTS_DIR (e.g. backend.xml)"),source:g.nativeEnum(eBe),run:pa.describe("the command you ran that wrote it, exactly as you ran it (with any converter)"),files:g.array(pa).min(1).describe("the items[].file paths this run covered"),cases:g.array(g.object({test:pa.describe("the test's name as the runner reports it, qualified by its class or file (e.g. tests/test_a.py::test_x) when the bare name repeats"),case:pa.describe("the covers id it realizes"),file:qCt})).describe("map each reported test name to the case it covers")}),HCt=g.object({suites:g.array(GCt).min(1)}),Av=g.object({schemaVersion:g.literal(1),items:g.array(ZTe),runnerResults:HCt.optional().describe("the JUnit XML your test runs wrote into $SKYRAMP_RESULTS_DIR, so the harness reads each test's result from the runner \u2014 omit only when no SUT is available"),ignored:g.array(g.string()).describe("existing tests assessed and deliberately left untouched"),summary:pa.describe("one paragraph")});var sBe=require("child_process"),yv=j(require("fs")),PQ=j(require("path"));var iBe="test-execution-contexts.json",VCt="localhost:35142",oBe=10*6e4,WCt=5e3,tBe=4e3,F4=1e4,rBe=3e4,YCt=2e3;function aBe(e){return Dm(e.sharedEnvironment).length?"shared":"disposable"}function bv(e,t){return e?.length?t||e.some(r=>r.kind!=="seed"||!!r.cleanup):!1}function JCt(e,t,r){if(r)return"its setup would change the shared environment, which the policy does not allow";let n=e.find(i=>i.mechanism==="recipe_override");if(n)return`the Runtime Plan cannot restart a component with an override for ${n.target}`;if(!t?.trim())return"adapt recorded no command that runs it alone";let{reason:s}=rd(t);return s?`its command cannot run without a shell (${s})`:void 0}function QQ(e,t,r){let n=r?Mt(e,r):void 0;return n&&t.find(s=>Mt(e,s.file)===n)?.context||[]}var L4=e=>({status:"not_run",notRunReason:"isolation_unavailable",cleanup:{status:"skipped",detail:`not run: ${e}`}});function cBe(e,t,r){let n=lo(e),s=aBe(e),i=[];for(let o of r){if(o.action==="delete"||!o.from)continue;let a=QQ(e.repositoryPath,t,o.from);if(!bv(a,n))continue;let c=JCt(a,o.isolatedRun,n);i.push({file:o.file,...o.repository?{repository:o.repository}:{},covers:o.covers??[],scope:s,execution:"serial",setup:a,...o.isolatedRun?{run:o.isolatedRun}:{},...c?L4(c):{status:"pending",notRunReason:null,cleanup:{status:"pending",detail:""}}})}return i}function rf(e,t){let r=PQ.join(e.outputsDir,iBe);yv.writeFileSync(`${r}.tmp`,JSON.stringify(gr(t,e.secretValues??[]),null,2)),yv.renameSync(`${r}.tmp`,r)}function Mm(e){let t=Cl(PQ.join(e.outputsDir,iBe));return Array.isArray(t)?t:[]}var U4=e=>e instanceof Error?e.message:String(e),nBe=e=>new Promise(t=>setTimeout(t,e));async function lBe(e,t=YCt){let r=`http://${e}/skyramp/mocks`,n;try{n=await fetch(r,{method:"DELETE",signal:AbortSignal.timeout(F4)})}catch(i){throw new Error(`the Skyramp worker at ${e} could not be reached: ${U4(i)}`)}if(!n.ok)throw new Error(`the Skyramp worker at ${e} answered ${n.status} to removing its mocks`);n.status===202&&await nBe(t);let s=Date.now()+rBe;for(;;){try{let i=await fetch(r,{signal:AbortSignal.timeout(F4)});if(i.ok&&!(await i.text()).trim()&&(await fetch(`http://${e}/readyz`,{signal:AbortSignal.timeout(F4)})).ok)return}catch{}if(Date.now()>s)throw new Error(`the Skyramp worker at ${e} still reports mocks ${rBe/1e3}s after removing them`);await nBe(250)}}var uBe=e=>[...new Set(e.filter(t=>t.kind==="runtime_mock"||t.mechanism==="skyramp_mocker").map(t=>t.worker?.trim()||VCt))];async function DQ(e,t){let r="",n=i=>r=(r+i).slice(-(tBe+1024)),s=()=>_r(r,t.secrets).slice(-tBe);for(let i of e){if(t.signal?.aborted)return{exit:null,output:s(),cancelled:!0};let o=await new Promise(a=>{let c=(0,sBe.spawn)(i[0],i.slice(1),{cwd:t.cwd,env:t.env,stdio:["ignore","pipe","pipe"],detached:!0}),l=w=>{try{c.pid&&process.kill(-c.pid,w)}catch{}},u=!1,d=!1,p;c.stdout.on("data",w=>n(w.toString())),c.stderr.on("data",w=>n(w.toString()));let f=w=>{n(`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skyramp/testbot-core",
|
|
3
|
-
"version": "0.13.0-v3.
|
|
3
|
+
"version": "0.13.0-v3.157",
|
|
4
4
|
"description": "Skyramp Testbot engine: AI-powered deterministic test generation, execution and maintenance",
|
|
5
5
|
"main": "dist/main.js",
|
|
6
6
|
"//files": "An explicit allowlist, never a glob. `dist/` is gitignored, so without this key npm's .gitignore fallback would publish a package with no engine in it; with a glob it would publish the sourcemaps, whose sourcesContent embeds every src/*.ts verbatim. eval-entry.js is a development entry and must not ship. dist/VERSION.json exists only in a release-engine.yml build. assets/gen-reference/ is the staged codegen reference corpus, which generate.ts finds at ../assets from dist/.",
|