refactor: remove workspace revision state consumers
This commit is contained in:
@@ -141,7 +141,7 @@ import { readFile, realpath, stat } from "node:fs/promises";
|
|||||||
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
||||||
import { spawnSync } from "node:child_process";
|
import { spawnSync } from "node:child_process";
|
||||||
const [root,readPath,publishPath,snapshots,checkout,output,renderer]=process.argv.slice(2);
|
const [root,readPath,publishPath,snapshots,checkout,output,renderer]=process.argv.slice(2);
|
||||||
const HEX40=/^[0-9a-f]{40}$/,HEX64=/^[0-9a-f]{64}$/;
|
const HEX40=/^[0-9a-f]{40}$/,HEX64=/^[0-9a-f]{64}$/,REVISION_KEYS=["blob","commit","id","snapshotPath"];
|
||||||
const bounded=async(path,label="saved response")=>{let s;try{s=await stat(path);}catch{throw new Error(label+" is missing or unbounded");}if(!s.isFile()||s.size<2||s.size>1048576)throw new Error(label+" is missing or unbounded");let v;try{v=JSON.parse(await readFile(path,"utf8"));}catch{throw new Error(label+" is malformed JSON");}return v;};
|
const bounded=async(path,label="saved response")=>{let s;try{s=await stat(path);}catch{throw new Error(label+" is missing or unbounded");}if(!s.isFile()||s.size<2||s.size>1048576)throw new Error(label+" is missing or unbounded");let v;try{v=JSON.parse(await readFile(path,"utf8"));}catch{throw new Error(label+" is malformed JSON");}return v;};
|
||||||
const boundedBytes=async(path)=>{let s;try{s=await stat(path);}catch{throw new Error("saved snapshot is missing or unbounded");}if(!s.isFile()||s.size<2||s.size>1048576)throw new Error("saved snapshot is missing or unbounded");return await readFile(path);};
|
const boundedBytes=async(path)=>{let s;try{s=await stat(path);}catch{throw new Error("saved snapshot is missing or unbounded");}if(!s.isFile()||s.size<2||s.size>1048576)throw new Error("saved snapshot is missing or unbounded");return await readFile(path);};
|
||||||
const read=await bounded(readPath),published=await bounded(publishPath);const revision=read?.revision,commit=revision?.commit,snapshot=revision?.snapshotPath,publishedCommit=published?.head??published?.revision?.commit;
|
const read=await bounded(readPath),published=await bounded(publishPath);const revision=read?.revision,commit=revision?.commit,snapshot=revision?.snapshotPath,publishedCommit=published?.head??published?.revision?.commit;
|
||||||
@@ -154,7 +154,8 @@ if(manifest?.head!==commit||!files||typeof files!=="object"||Array.isArray(files
|
|||||||
const expected=files[id+".yaml"];if(!HEX64.test(expected??""))throw new Error("snapshot manifest digest is invalid");
|
const expected=files[id+".yaml"];if(!HEX64.test(expected??""))throw new Error("snapshot manifest digest is invalid");
|
||||||
const snapshotBytes=await boundedBytes(canonical);if(createHash("sha256").update(snapshotBytes).digest("hex")!==expected)throw new Error("snapshot bytes differ from manifest digest");
|
const snapshotBytes=await boundedBytes(canonical);if(createHash("sha256").update(snapshotBytes).digest("hex")!==expected)throw new Error("snapshot bytes differ from manifest digest");
|
||||||
const entry=Array.isArray(revisions)?revisions.find(candidate=>candidate?.id===id):undefined;
|
const entry=Array.isArray(revisions)?revisions.find(candidate=>candidate?.id===id):undefined;
|
||||||
if(!entry||!HEX40.test(entry?.blob??"")||entry.commit!==commit||typeof entry.snapshotPath!=="string"||resolve(entry.snapshotPath)!==canonical||(entry.state!=="operational"&&entry.state!=="migration_required"))throw new Error("snapshot manifest revision is invalid");
|
const exactEntry=entry&&typeof entry==="object"&&!Array.isArray(entry)&&Object.keys(entry).sort().every((key,index)=>key===REVISION_KEYS[index])&&Object.keys(entry).length===REVISION_KEYS.length;
|
||||||
|
if(!exactEntry||entry.id!==id||entry.commit!==commit||typeof entry.blob!=="string"||!HEX40.test(entry.blob)||entry.snapshotPath!==canonical)throw new Error("snapshot manifest revision is invalid");
|
||||||
if(!HEX40.test(revision?.blob??"")||revision.blob!==entry.blob)throw new Error("saved revision blob differs from snapshot manifest");
|
if(!HEX40.test(revision?.blob??"")||revision.blob!==entry.blob)throw new Error("saved revision blob differs from snapshot manifest");
|
||||||
const blobCheck=spawnSync("git",["-C",checkout,"rev-parse",commit+":workspaces/"+id+".yaml"],{encoding:"utf8"});
|
const blobCheck=spawnSync("git",["-C",checkout,"rev-parse",commit+":workspaces/"+id+".yaml"],{encoding:"utf8"});
|
||||||
if(blobCheck.status!==0||blobCheck.stdout.trim()!==entry.blob)throw new Error("snapshot blob differs from installed Git commit");
|
if(blobCheck.status!==0||blobCheck.stdout.trim()!==entry.blob)throw new Error("snapshot blob differs from installed Git commit");
|
||||||
|
|||||||
@@ -436,10 +436,16 @@ test("generated render command binds snapshot bytes to the commit manifest and G
|
|||||||
const readPath=join(run.root,"responses/read-p1-filesystem.json"),pullPath=join(run.root,"responses/pull.json"),script=join(run.root,"commands/render-1.sh"),script2=join(run.root,"commands/render-2.sh"),output=join(run.root,"rendered/runtime-1.yaml"),output2=join(run.root,"rendered/runtime-2.yaml");
|
const readPath=join(run.root,"responses/read-p1-filesystem.json"),pullPath=join(run.root,"responses/pull.json"),script=join(run.root,"commands/render-1.sh"),script2=join(run.root,"commands/render-2.sh"),output=join(run.root,"rendered/runtime-1.yaml"),output2=join(run.root,"rendered/runtime-2.yaml");
|
||||||
const rendererStub=join(repo,"backend/scripts/p1-render-snapshot.mjs"),stubArgs=join(run.root,"rendered/stub-args.json");
|
const rendererStub=join(repo,"backend/scripts/p1-render-snapshot.mjs"),stubArgs=join(run.root,"rendered/stub-args.json");
|
||||||
await writeFile(rendererStub,`import { writeFileSync } from "node:fs";\nwriteFileSync(${JSON.stringify(stubArgs)}, JSON.stringify(process.argv.slice(2)));\n`);
|
await writeFile(rendererStub,`import { writeFileSync } from "node:fs";\nwriteFileSync(${JSON.stringify(stubArgs)}, JSON.stringify(process.argv.slice(2)));\n`);
|
||||||
const manifest=()=>({head:commit,revisions:[{id:"p1-filesystem",commit,blob,snapshotPath,state:"operational"}],files:{"p1-filesystem.yaml":snapshotSha}});
|
const revision={id:"p1-filesystem",commit,blob,snapshotPath};
|
||||||
await writeFile(readPath,JSON.stringify({revision:{id:"p1-filesystem",commit,blob,snapshotPath,state:"operational"}})); await writeFile(pullPath,JSON.stringify({head:commit}));
|
const manifest=(entry=revision)=>({head:commit,revisions:[entry],files:{"p1-filesystem.yaml":snapshotSha}});
|
||||||
|
await writeFile(readPath,JSON.stringify({revision})); await writeFile(pullPath,JSON.stringify({head:commit}));
|
||||||
await assert.rejects(execFileAsync("bash",[script],{cwd:repo}),/snapshot manifest.*(missing|unbounded)/i);
|
await assert.rejects(execFileAsync("bash",[script],{cwd:repo}),/snapshot manifest.*(missing|unbounded)/i);
|
||||||
await assert.rejects(lstat(output));
|
await assert.rejects(lstat(output));
|
||||||
|
const legacyRevision={...revision}; legacyRevision.state=["oper","ational"].join("");
|
||||||
|
await writeFile(join(commitDir,"snapshot.json"),JSON.stringify(manifest(legacyRevision)));
|
||||||
|
await assert.rejects(execFileAsync("bash",[script],{cwd:repo}),/snapshot manifest revision is invalid/);
|
||||||
|
await writeFile(join(commitDir,"snapshot.json"),JSON.stringify(manifest({...revision,unexpected:"field"})));
|
||||||
|
await assert.rejects(execFileAsync("bash",[script],{cwd:repo}),/snapshot manifest revision is invalid/);
|
||||||
await writeFile(join(commitDir,"snapshot.json"),JSON.stringify(manifest()));
|
await writeFile(join(commitDir,"snapshot.json"),JSON.stringify(manifest()));
|
||||||
await execFileAsync("bash",[script],{cwd:repo});
|
await execFileAsync("bash",[script],{cwd:repo});
|
||||||
assert.deepEqual(JSON.parse(await readFile(stubArgs,"utf8")),["--ownership",join(run.root,"ownership.json"),"--snapshot",snapshot,"--output",output,"--snapshot-sha256",snapshotSha]);
|
assert.deepEqual(JSON.parse(await readFile(stubArgs,"utf8")),["--ownership",join(run.root,"ownership.json"),"--snapshot",snapshot,"--output",output,"--snapshot-sha256",snapshotSha]);
|
||||||
@@ -450,12 +456,22 @@ test("generated render command binds snapshot bytes to the commit manifest and G
|
|||||||
await writeFile(join(commitDir,"snapshot.json"),JSON.stringify({...manifest(),head:"c".repeat(40)}));
|
await writeFile(join(commitDir,"snapshot.json"),JSON.stringify({...manifest(),head:"c".repeat(40)}));
|
||||||
await assert.rejects(execFileAsync("bash",[script2],{cwd:repo}),/snapshot manifest identity is invalid/);
|
await assert.rejects(execFileAsync("bash",[script2],{cwd:repo}),/snapshot manifest identity is invalid/);
|
||||||
await assert.rejects(lstat(output2));
|
await assert.rejects(lstat(output2));
|
||||||
await writeFile(join(commitDir,"snapshot.json"),JSON.stringify(manifest()));
|
for(const malformed of [
|
||||||
await writeFile(join(commitDir,"snapshot.json"),JSON.stringify({...manifest(),revisions:[{id:"p1-filesystem",commit,blob:"f".repeat(40),snapshotPath,state:"operational"}]}));
|
{id:"p1-filesystem",commit,blob},
|
||||||
|
{...revision,id:"p1-http"},
|
||||||
|
{...revision,commit:"c".repeat(40)},
|
||||||
|
{...revision,blob:"f".repeat(39)},
|
||||||
|
{...revision,blob:[blob]},
|
||||||
|
{...revision,snapshotPath:join(commitDir,"wrong.yaml")},
|
||||||
|
]){
|
||||||
|
await writeFile(join(commitDir,"snapshot.json"),JSON.stringify(manifest(malformed)));
|
||||||
|
await assert.rejects(execFileAsync("bash",[script2],{cwd:repo}),/snapshot manifest revision is invalid/);
|
||||||
|
}
|
||||||
|
await writeFile(join(commitDir,"snapshot.json"),JSON.stringify(manifest({...revision,blob:"f".repeat(40)})));
|
||||||
await assert.rejects(execFileAsync("bash",[script2],{cwd:repo}),/saved revision blob differs from snapshot manifest/);
|
await assert.rejects(execFileAsync("bash",[script2],{cwd:repo}),/saved revision blob differs from snapshot manifest/);
|
||||||
await assert.rejects(lstat(output2));
|
await assert.rejects(lstat(output2));
|
||||||
await writeFile(join(commitDir,"snapshot.json"),JSON.stringify(manifest()));
|
await writeFile(join(commitDir,"snapshot.json"),JSON.stringify(manifest()));
|
||||||
await writeFile(readPath,JSON.stringify({revision:{id:"p1-filesystem",commit,blob:"f".repeat(40),snapshotPath,state:"operational"}}));
|
await writeFile(readPath,JSON.stringify({revision:{...revision,blob:"f".repeat(40)}}));
|
||||||
await assert.rejects(execFileAsync("bash",[script2],{cwd:repo}),/saved revision blob differs/);
|
await assert.rejects(execFileAsync("bash",[script2],{cwd:repo}),/saved revision blob differs/);
|
||||||
await assert.rejects(lstat(output2));
|
await assert.rejects(lstat(output2));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ evidence:
|
|||||||
`);
|
`);
|
||||||
const snapshotBytes=await readFile(snapshot); const snapshotSha256=sha256(snapshotBytes);
|
const snapshotBytes=await readFile(snapshot); const snapshotSha256=sha256(snapshotBytes);
|
||||||
const manifestPath=join(dirname(snapshot),"snapshot.json");
|
const manifestPath=join(dirname(snapshot),"snapshot.json");
|
||||||
await writeFile(manifestPath,JSON.stringify({head:commit,revisions:[{id:"p1-filesystem",commit,blob:"0".repeat(40),snapshotPath:snapshot,state:"operational"}],files:{"p1-filesystem.yaml":snapshotSha256}}));
|
await writeFile(manifestPath,JSON.stringify({head:commit,revisions:[{id:"p1-filesystem",commit,blob:"0".repeat(40),snapshotPath:snapshot}],files:{"p1-filesystem.yaml":snapshotSha256}}));
|
||||||
const env={THT_WS_P1_FILESYSTEM_DWH_TRANSPORT:"postgres_direct",THT_WS_P1_FILESYSTEM_DWH_HOST:"dwh.invalid",THT_WS_P1_FILESYSTEM_DWH_PORT:"5432",THT_WS_P1_FILESYSTEM_DWH_USER:"reader",THT_WS_P1_FILESYSTEM_DWH_PASSWORD_FILE:secret};
|
const env={THT_WS_P1_FILESYSTEM_DWH_TRANSPORT:"postgres_direct",THT_WS_P1_FILESYSTEM_DWH_HOST:"dwh.invalid",THT_WS_P1_FILESYSTEM_DWH_PORT:"5432",THT_WS_P1_FILESYSTEM_DWH_USER:"reader",THT_WS_P1_FILESYSTEM_DWH_PASSWORD_FILE:secret};
|
||||||
return {repo,root,snapshot,snapshotSha256,manifestPath,env};
|
return {repo,root,snapshot,snapshotSha256,manifestPath,env};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user