122 lines
8.0 KiB
JavaScript
Executable File
122 lines
8.0 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
import { spawnSync } from "node:child_process";
|
|
import { lstatSync, realpathSync } from "node:fs";
|
|
import { lstat, mkdir, readFile, realpath } from "node:fs/promises";
|
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
// This acceptance-only adapter deliberately imports the built production runner.
|
|
import { ThtRunner } from "../dist/tht/tht-runner.js";
|
|
|
|
const modulePath = fileURLToPath(import.meta.url);
|
|
const defaultRepositoryRoot = realpathSync(resolve(dirname(modulePath), "../.."));
|
|
const HEX40 = /^[0-9a-f]{40}$/;
|
|
const HEX64 = /^[0-9a-f]{64}$/;
|
|
|
|
function fixedRoot(repositoryRoot) { return join(realpathSync(repositoryRoot), ".artifacts", "manual-acceptance", "p1"); }
|
|
function below(parent, child) { const rel = relative(parent, child); return rel !== "" && !rel.startsWith(`..${sep}`) && rel !== ".." && !isAbsolute(rel); }
|
|
function assertNoSymlinks(root, path, allowMissingLeaf = false) {
|
|
const rel = relative(root, path);
|
|
if (rel.startsWith("..") || isAbsolute(rel)) throw new Error("path is outside owned root");
|
|
let cursor = root;
|
|
for (const [index, part] of rel.split(sep).filter(Boolean).entries()) {
|
|
cursor = join(cursor, part);
|
|
try { if (lstatSync(cursor).isSymbolicLink()) throw new Error("owned path contains a symlink"); }
|
|
catch (error) {
|
|
if (allowMissingLeaf && error.code === "ENOENT" && index === rel.split(sep).filter(Boolean).length - 1) return;
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
async function ownership(repositoryRoot, ownershipPath) {
|
|
const root = fixedRoot(repositoryRoot);
|
|
const expected = join(root, "ownership.json");
|
|
if (resolve(ownershipPath) !== expected) throw new Error("ownership path is not owned");
|
|
const rootEntry = await lstat(root); const ownershipEntry = await lstat(expected);
|
|
if (!rootEntry.isDirectory() || rootEntry.isSymbolicLink() || !ownershipEntry.isFile() || ownershipEntry.isSymbolicLink()) throw new Error("ownership is unsafe");
|
|
if (await realpath(root) !== root) throw new Error("ownership root is not canonical");
|
|
let value; try { value = JSON.parse(await readFile(expected, "utf8")); } catch { throw new Error("ownership is malformed"); }
|
|
if (value?.schemaVersion !== 1 || value.kind !== "p1-manual-acceptance" || !HEX64.test(value.nonce ?? "")
|
|
|| value.repositoryRoot !== realpathSync(repositoryRoot) || value.root !== root || value.status !== "PENDING"
|
|
|| value.listener?.host !== "127.0.0.1" || value.listener?.port !== 8791) throw new Error("ownership identity mismatch");
|
|
return { root, value };
|
|
}
|
|
const ANCHORED_PUBLISH_SOURCE=String.raw`import os,secrets,stat,sys
|
|
parent,name,expected_dev,expected_ino=sys.argv[1:]
|
|
pfd=fd=None;stage=".render-stage-"+secrets.token_hex(16);published=False
|
|
def fail(): raise RuntimeError("anchored publication refused")
|
|
try:
|
|
pfd=os.open(parent,os.O_RDONLY|os.O_DIRECTORY|os.O_NOFOLLOW)
|
|
identity=os.fstat(pfd)
|
|
if (identity.st_dev,identity.st_ino)!=(int(expected_dev),int(expected_ino)): fail()
|
|
try: os.stat(name,dir_fd=pfd,follow_symlinks=False); fail()
|
|
except FileNotFoundError: pass
|
|
fd=os.open(stage,os.O_WRONLY|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW,0o600,dir_fd=pfd)
|
|
data=sys.stdin.buffer.read(33554433)
|
|
if len(data)>33554432: fail()
|
|
view=memoryview(data)
|
|
while view:
|
|
written=os.write(fd,view)
|
|
if written<=0: fail()
|
|
view=view[written:]
|
|
os.fsync(fd);os.close(fd);fd=None;os.rename(stage,name,src_dir_fd=pfd,dst_dir_fd=pfd);published=True;os.fsync(pfd)
|
|
current=os.stat(parent,follow_symlinks=False)
|
|
if not stat.S_ISDIR(current.st_mode) or (current.st_dev,current.st_ino)!=(identity.st_dev,identity.st_ino): fail()
|
|
except Exception:
|
|
if published:
|
|
try: os.unlink(name,dir_fd=pfd);os.fsync(pfd)
|
|
except Exception: pass
|
|
print("anchored output publication refused (details redacted)",file=sys.stderr);raise SystemExit(1)
|
|
finally:
|
|
if fd is not None: os.close(fd)
|
|
if pfd is not None:
|
|
try: os.unlink(stage,dir_fd=pfd)
|
|
except FileNotFoundError: pass
|
|
os.close(pfd)
|
|
`;
|
|
async function atomicCopy(source,output) {
|
|
const parent=dirname(output),entry=await lstat(parent);if(!entry.isDirectory()||entry.isSymbolicLink())throw new Error("rendered parent identity is unsafe");const bytes=await readFile(source);
|
|
const result=spawnSync("python3",["-c",ANCHORED_PUBLISH_SOURCE,parent,basename(output),String(entry.dev),String(entry.ino)],{input:bytes,encoding:"utf8",maxBuffer:1024*1024});
|
|
if(result.error||result.status!==0)throw new Error("anchored output publication refused; rendered parent identity changed or output is unsafe");
|
|
}
|
|
|
|
export async function renderOwnedSnapshot({ repositoryRoot = defaultRepositoryRoot, ownershipPath, snapshotPath, outputPath, env = process.env, beforePublish }) {
|
|
const repo = realpathSync(repositoryRoot); const { root } = await ownership(repo, resolve(repo, ownershipPath));
|
|
const snapshot = resolve(repo, snapshotPath); const output = resolve(repo, outputPath);
|
|
const snapshotsRoot = join(root, "installation", "registry", "snapshots");
|
|
const renderedRoot = join(root, "rendered");
|
|
if (!isAbsolute(snapshotPath) || !below(snapshotsRoot, snapshot)) throw new Error("snapshot is not an owned absolute path");
|
|
const match = /^([0-9a-f]{40})\/([a-z][a-z0-9-]{2,62})\.yaml$/.exec(relative(snapshotsRoot, snapshot).split(sep).join("/"));
|
|
if (!match || !HEX40.test(match[1])) throw new Error("snapshot is not commit addressed");
|
|
assertNoSymlinks(root, snapshot); const snapshotEntry = await lstat(snapshot);
|
|
if (!snapshotEntry.isFile() || snapshotEntry.isSymbolicLink() || await realpath(snapshot) !== snapshot) throw new Error("snapshot is unsafe");
|
|
if (!below(renderedRoot, output) || dirname(output) !== renderedRoot || !output.endsWith(".yaml")) throw new Error("output is not an owned rendered path");
|
|
assertNoSymlinks(root, dirname(output));
|
|
try { if ((await lstat(output)).isSymbolicLink()) throw new Error("output is unsafe"); } catch (error) { if (error.code !== "ENOENT") throw error; }
|
|
await mkdir(join(snapshotsRoot, "runtime"), { recursive: true, mode: 0o700 });
|
|
const prior = {};
|
|
for (const [key, value] of Object.entries(env)) { prior[key] = process.env[key]; if (value === undefined) delete process.env[key]; else process.env[key] = value; }
|
|
const runner = new ThtRunner({
|
|
thtBin: join(repo, "harness", ".venv", "bin", "tht"), harnessDir: join(repo, "harness"),
|
|
configPath: join(root, "installation", "base.yaml"), dataRoot: join(root, "installation", "data"),
|
|
runtimeSnapshotRoot: join(snapshotsRoot, "runtime"), secretRoots: [join(root, "fixture-secrets")],
|
|
semanticRuntime: { internalQdrantUrl: "http://qdrant:6333", internalEmbeddingUrl: "http://embedding:11434", internalEmbeddingModel: "qwen3-embedding:0.6b", internalEmbeddingDimensions: 1024 },
|
|
});
|
|
let lease;
|
|
try { lease = runner.acquireWorkspaceRuntime(snapshot); if(beforePublish)await beforePublish({output,renderedRoot}); await atomicCopy(lease.path, output); }
|
|
finally {
|
|
if (lease) lease.release();
|
|
for (const key of Object.keys(env)) { if (prior[key] === undefined) delete process.env[key]; else process.env[key] = prior[key]; }
|
|
}
|
|
return output;
|
|
}
|
|
function parseArgs(argv) {
|
|
if (argv.length !== 6) throw new Error("usage: p1-render-snapshot.mjs --ownership PATH --snapshot ABSOLUTE_PATH --output PATH");
|
|
const result = {}; for (let i=0;i<argv.length;i+=2) { if (!["--ownership","--snapshot","--output"].includes(argv[i]) || result[argv[i]]) throw new Error("invalid arguments"); result[argv[i]]=argv[i+1]; }
|
|
if (!result["--ownership"] || !result["--snapshot"] || !result["--output"]) throw new Error("missing arguments"); return result;
|
|
}
|
|
if (process.argv[1] && realpathSync(process.argv[1]) === modulePath) {
|
|
try { const args=parseArgs(process.argv.slice(2)); await renderOwnedSnapshot({ ownershipPath:args["--ownership"], snapshotPath:args["--snapshot"], outputPath:args["--output"] }); console.log(`rendered ${resolve(args["--output"])}`); }
|
|
catch(error) { console.error(`p1 render refused: ${error.message}`); process.exitCode=1; }
|
|
}
|