fix: preserve restored setup and bind native sync to sandbox home

Record checkpoint intent before mutations and permit CI-owned lock resolution when building a staging migrator.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-09-07 14:26:10 -05:00
1 parent 82b19d5091
commit 2e810127c4
6 files changed
+93 -21

No files matched your search

+8 -1
View File
@@ -17,12 +17,19 @@ export function previewIdentity(sha, date, artifactBaseUrl) {
baseUrl: `${base.href.replace(/\/$/, "")}/${sha}` };
}
export function assertPreviewSourceClean(repo) {
// Repository policy regenerates the lock in CI for manifest-only branches.
// That generated input is allowed; every tracked source input must still
// match the commit identifying both the app image and migrator artifact.
execFileSync("git", ["diff", "--quiet", "HEAD", "--", ".", ":(exclude)pnpm-lock.yaml"], { cwd: repo });
}
export function buildPreviewMigrator(outputDirectory, artifactBaseUrl) {
const repo = path.resolve(fileURLToPath(new URL("..", import.meta.url)));
const git = (...args) => execFileSync("git", args, { cwd: repo, encoding: "utf8" }).trim();
const sha = git("rev-parse", "HEAD");
if (process.env.GITHUB_SHA && process.env.GITHUB_SHA !== sha) throw new Error("Preview checkout differs from the workflow commit");
git("diff", "--quiet", "HEAD");
assertPreviewSourceClean(repo);
const identity = previewIdentity(sha, new Date(git("show", "-s", "--format=%cI", "HEAD")), artifactBaseUrl);
execFileSync("pnpm", ["--filter", "@paperclipai/db...", "build"], { cwd: repo, stdio: "inherit" });
const output = path.resolve(outputDirectory);
+23 -1
View File
@@ -1,6 +1,28 @@
import assert from "node:assert/strict";
import test from "node:test";
import { previewIdentity } from "./build-preview-migrator.mjs";
import { execFileSync } from "node:child_process";
import { mkdtempSync, writeFileSync, rmSync } from "node:fs";
import os from "node:os";
import path from "node:path";
import { assertPreviewSourceClean, previewIdentity } from "./build-preview-migrator.mjs";
test("preview source identity permits CI lock resolution but rejects staged and unstaged source drift", () => {
const repo = mkdtempSync(path.join(os.tmpdir(), "preview-source-"));
const git = (...args) => execFileSync("git", args, { cwd: repo, stdio: "pipe" });
try {
git("init");
for (const file of ["pnpm-lock.yaml", "package.json"]) writeFileSync(path.join(repo, file), "initial\n");
git("add", ".");
git("-c", "user.name=Test", "-c", "user.email=test@example.com", "commit", "-m", "initial");
assertPreviewSourceClean(repo);
writeFileSync(path.join(repo, "pnpm-lock.yaml"), "CI resolved\n");
assertPreviewSourceClean(repo);
writeFileSync(path.join(repo, "package.json"), "source drift\n");
assert.throws(() => assertPreviewSourceClean(repo));
git("add", "package.json");
assert.throws(() => assertPreviewSourceClean(repo));
} finally { rmSync(repo, { recursive: true, force: true }); }
});
test("preview artifact identity is immutable, namespaced, and ordered by commit time", () => {
const sha = "2f42a4968d5761fd62172e35ecf8188195b8d431";