From ab315c21304f25544be18c60a6a2a2a18f7a6e2a Mon Sep 17 00:00:00 2001 From: mptyl Date: Mon, 10 Aug 2026 21:44:57 +0200 Subject: [PATCH] refactor: remove workspace revision state consumers --- backend/scripts/p1-manual-acceptance.mjs | 5 ++-- backend/scripts/p1-manual-acceptance.test.mjs | 26 +++++++++++++++---- backend/scripts/p1-render-snapshot.test.mjs | 2 +- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/backend/scripts/p1-manual-acceptance.mjs b/backend/scripts/p1-manual-acceptance.mjs index f0664bab..89407388 100755 --- a/backend/scripts/p1-manual-acceptance.mjs +++ b/backend/scripts/p1-manual-acceptance.mjs @@ -141,7 +141,7 @@ import { readFile, realpath, stat } from "node:fs/promises"; import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path"; import { spawnSync } from "node:child_process"; 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 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; @@ -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 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; -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"); 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"); diff --git a/backend/scripts/p1-manual-acceptance.test.mjs b/backend/scripts/p1-manual-acceptance.test.mjs index 5079fe27..bf6df123 100644 --- a/backend/scripts/p1-manual-acceptance.test.mjs +++ b/backend/scripts/p1-manual-acceptance.test.mjs @@ -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 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`); - const manifest=()=>({head:commit,revisions:[{id:"p1-filesystem",commit,blob,snapshotPath,state:"operational"}],files:{"p1-filesystem.yaml":snapshotSha}}); - await writeFile(readPath,JSON.stringify({revision:{id:"p1-filesystem",commit,blob,snapshotPath,state:"operational"}})); await writeFile(pullPath,JSON.stringify({head:commit})); + const revision={id:"p1-filesystem",commit,blob,snapshotPath}; + 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(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 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]); @@ -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 assert.rejects(execFileAsync("bash",[script2],{cwd:repo}),/snapshot manifest identity is invalid/); await assert.rejects(lstat(output2)); - await writeFile(join(commitDir,"snapshot.json"),JSON.stringify(manifest())); - await writeFile(join(commitDir,"snapshot.json"),JSON.stringify({...manifest(),revisions:[{id:"p1-filesystem",commit,blob:"f".repeat(40),snapshotPath,state:"operational"}]})); + for(const malformed of [ + {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(lstat(output2)); 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(lstat(output2)); }); diff --git a/backend/scripts/p1-render-snapshot.test.mjs b/backend/scripts/p1-render-snapshot.test.mjs index 1b161b16..1660aa57 100644 --- a/backend/scripts/p1-render-snapshot.test.mjs +++ b/backend/scripts/p1-render-snapshot.test.mjs @@ -36,7 +36,7 @@ evidence: `); const snapshotBytes=await readFile(snapshot); const snapshotSha256=sha256(snapshotBytes); 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}; return {repo,root,snapshot,snapshotSha256,manifestPath,env}; }