mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-06 23:20:53 +01:00
merge: reconcile published native prerequisite and current master
Preserve canonical startup ownership, scoped evidence, current Storybook IDs, and the Codex form controls while carrying Claude effort and immutable source bindings. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
commit
48ffca9438
415 files changed
+22501
-15340
No files matched your search
@@ -1,4 +1,3 @@
|
||||
// Assemble the four independently built release daemons before npm packing.
|
||||
// Usage: node packages/paperclip-runner/scripts/stage-release-runner-binaries.mjs MANIFEST.json
|
||||
// Manifest: {sourceRevision, platforms: {"darwin-arm64": {path, sha256}, ...}, remoteProviderPack: {path, sha256}}.
|
||||
import { createHash } from "node:crypto";
|
||||
@@ -31,8 +30,6 @@ function regularFile(path, maximumSize) {
|
||||
return readFileSync(path);
|
||||
}
|
||||
|
||||
// Production TypeScript contracts are built by the existing producer step.
|
||||
// Foreign artifacts are data: the assembler never executes another target.
|
||||
async function metadataContract() {
|
||||
const { PAPERCLIP_RUNNER_BUILD_METADATA } = await import("../dist/evals/build-metadata.js");
|
||||
const { parsePaperclipRunnerdBuildMetadata } = await import("../dist/evals/runnerd-artifact.js");
|
||||
@@ -112,16 +109,12 @@ async function main() {
|
||||
if (packBytes.length > 512 * 1024 || "sha256:" + createHash("sha256").update(packBytes).digest("hex") !== entry.sha256) throw Error("Remote provider-pack release manifest digest mismatch");
|
||||
packIdentity = verifyReleaseProviderPack(JSON.parse(packBytes), manifest.sourceRevision);
|
||||
} else if (daemonOnly) {
|
||||
// Preserve a qualified same-source Linux pack already materialized by the
|
||||
// normal producer. It never becomes a Mac provider-pack qualification.
|
||||
packIdentity = existingReleaseProviderPack(root, manifest.sourceRevision);
|
||||
}
|
||||
const platforms = {};
|
||||
for (const artifact of verified) {
|
||||
const relative = `${artifact.target}/paperclip-runnerd`, destination = join(root, "dist", "bin", relative);
|
||||
mkdirSync(dirname(destination), { recursive: true }); copyFileSync(artifact.source, destination); chmodSync(destination, 0o755);
|
||||
// Copied Mach-O inodes need a fresh ad-hoc signature on macOS. Record the
|
||||
// resulting packaged bytes, as well as the verified build artifact identity.
|
||||
if (process.platform === "darwin" && artifact.target.startsWith("darwin-")) {
|
||||
execFileSync("codesign", ["--force", "--sign", "-", destination], { stdio: "pipe", timeout: 30_000 });
|
||||
}
|
||||
@@ -131,7 +124,6 @@ async function main() {
|
||||
if (packBytes) {
|
||||
const destination = join(root, "dist", "remote-provider-packs", "linux-x64", "provider-pack.json");
|
||||
mkdirSync(dirname(destination), { recursive: true });
|
||||
// Replace the inode so repeat assembly can preserve a read-only manifest.
|
||||
const staged = `${destination}.staging-${process.pid}`;
|
||||
try {
|
||||
writeFileSync(staged, packBytes, { mode: 0o444, flag: "wx" });
|
||||
|
||||
@@ -32,8 +32,6 @@ export async function stageBuiltRunnerBinary(packageRoot) {
|
||||
const executable = process.platform === "win32" ? "paperclip-runnerd.exe" : "paperclip-runnerd";
|
||||
const source = path.join(packageRoot, "runner", "target", "release", executable);
|
||||
const binaryRoot = path.join(packageRoot, "dist", "bin");
|
||||
// Source builds are separate from the source-bound release targets and their
|
||||
// manifest. The dev supervisor selects this fresh output for its server.
|
||||
await stageRunnerBinary(source, path.join(binaryRoot, executable));
|
||||
}
|
||||
|
||||
|
||||
@@ -140,8 +140,6 @@ test("a source rebuild selects fresh dev bytes while preserving release binaries
|
||||
const copyHostFixture = async (input, output) => {
|
||||
await copyFile(input, output);
|
||||
if (process.platform === "darwin") {
|
||||
// System fixtures can be universal; Cargo release daemons are thin.
|
||||
// Extract only this host slice before exercising the native header gate.
|
||||
const slices = execFileSync("/usr/bin/lipo", ["-archs", input], { encoding: "utf8" }).trim().split(/\s+/);
|
||||
const architecture = slices.find(value => process.arch === "arm64" ? value.startsWith("arm64") : value === "x86_64");
|
||||
assert.ok(architecture);
|
||||
|
||||
@@ -31,8 +31,6 @@ vi.mock("smol-toml", async (importOriginal) => {
|
||||
...actual,
|
||||
parse: (...args: Parameters<typeof actual.parse>) => {
|
||||
const result = actual.parse(...args);
|
||||
// Exercise both table representations independently of the installed
|
||||
// parser version while retaining the real TOML values.
|
||||
setTablePrototypes(result);
|
||||
return result;
|
||||
},
|
||||
|
||||
@@ -92,8 +92,6 @@ export function trustCodexStartupRoot(codexHome: string, cwd: string): void {
|
||||
const path = join(codexHome, "config.toml");
|
||||
const source = existsSync(path) ? readFileSync(path, "utf8") : "";
|
||||
const config = parse(source);
|
||||
// Retain the parser's table prototypes so strict candidate comparison also
|
||||
// works with parsers that return null-prototype TOML tables.
|
||||
const projects = config.projects ?? parse("");
|
||||
if (
|
||||
typeof projects !== "object" ||
|
||||
|
||||
@@ -396,7 +396,6 @@ it("publishes spawned runner ownership before waiting for provider startup", asy
|
||||
});
|
||||
const opening = bundle.transport.request("thread/start", { cwd: tmpdir(), dynamicTools: codexSemanticToolSpecs() });
|
||||
try {
|
||||
// Same-tick recovery must not mutate an already admitted fresh startup.
|
||||
await expect(bundle.transport.request("thread/read", { threadId: "rejected-duplicate" }))
|
||||
.rejects.toThrow("PRP provider thread is already started");
|
||||
await vi.waitFor(() => expect(onSpawn).toHaveBeenCalledOnce());
|
||||
@@ -2223,7 +2222,6 @@ it("owns blocked Codex home preparation through driver abort without later regis
|
||||
markPreparationStarted();
|
||||
await preparationReleased;
|
||||
preparationContinuedAfterClose = closeSettled;
|
||||
// A synthetic, credential-free write makes the owned preparation visible.
|
||||
await mkdir(input.codexHome, { mode: 0o700 });
|
||||
await writeFile(join(input.codexHome, "preparation-marker"), "fixture preparation drained\n", { mode: 0o600 });
|
||||
});
|
||||
@@ -2264,7 +2262,6 @@ it("owns blocked Codex home preparation through driver abort without later regis
|
||||
.then(value => { openingSettled = true; return { value }; }, error => { openingSettled = true; return { error }; });
|
||||
try {
|
||||
await preparationStarted;
|
||||
// A rejected duplicate must not replace the first admitted startup owner.
|
||||
await expect(transport.request("thread/start", { cwd: root, dynamicTools: [] }))
|
||||
.rejects.toThrow("PRP provider thread is already started");
|
||||
controller.abort(cancelled);
|
||||
@@ -2863,8 +2860,6 @@ it.each(["stuck", "late-release", "late-release-error", "stuck-release", "cooper
|
||||
runtimeDirectory, provider: "codex", model: "gpt-test", timeoutMs: 1_000,
|
||||
environment: { PATH: "", OPENAI_API_KEY: "inert-test-key" },
|
||||
transportOptions: {
|
||||
// Registration and launch are external owners; hash real inert bytes
|
||||
// without executing a daemon, Codex, or an ambient developer binary.
|
||||
runnerBinary: resolve(import.meta.dirname, "../../package.json"),
|
||||
codexCommand: resolve(import.meta.dirname, "../../package.json"),
|
||||
sourceCodexHome: "", runnerProcessLauncher: launch,
|
||||
@@ -2895,8 +2890,6 @@ it.each(["stuck", "late-release", "late-release-error", "stuck-release", "cooper
|
||||
expect(cleanup).not.toHaveBeenCalled();
|
||||
expect((await stat(runtimeDirectory)).isDirectory()).toBe(true);
|
||||
if (mode === "late-release-error") expect((failure as AggregateError).errors).toContain(releaseError);
|
||||
// A registration that finishes after the bounded return still releases
|
||||
// its authority, without retroactively certifying or deleting state.
|
||||
returnRegistration(registered);
|
||||
finishRelease();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
|
||||
@@ -1338,7 +1338,6 @@ export interface CapabilityRunnerdCodexTransportOptions {
|
||||
controlPlaneRegistration?: (
|
||||
authority: DurablePrpControlPlane,
|
||||
identity?: DurableRecoveryIdentity,
|
||||
/** Setup owners abort preparation before draining or releasing its result. */
|
||||
signal?: AbortSignal,
|
||||
) => Promise<{
|
||||
connectUrl?: string;
|
||||
@@ -4466,8 +4465,6 @@ class DurablePrpCodexTransport implements CodexAppServerTransport {
|
||||
}
|
||||
|
||||
#trackStartup<T>(start: () => Promise<T>): Promise<T> {
|
||||
// Admit one owner before invoking startup. A duplicate request's rejection
|
||||
// must never replace the preparation that close is responsible for joining.
|
||||
if (this.#startupOperation !== null) {
|
||||
return Promise.reject(new Error("PRP provider thread is already started"));
|
||||
}
|
||||
@@ -7108,8 +7105,6 @@ class DurablePrpCodexTransport implements CodexAppServerTransport {
|
||||
}
|
||||
await Promise.race([
|
||||
ready(), this.#failureSignal,
|
||||
// Readiness is a notification wait, not launch-material preparation.
|
||||
// Its route owner is released by close; joining it first would cycle.
|
||||
this.#closedSignal.then(() => { this.#assertStartupOpen(); }),
|
||||
]);
|
||||
this.#assertStartupOpen();
|
||||
@@ -7160,7 +7155,6 @@ export function createCapabilityRunnerdCodexTransport(
|
||||
export const createRunnerdCodexTransport =
|
||||
createCapabilityRunnerdCodexTransport;
|
||||
|
||||
/** A setup turn uses the same daemon/provider boundary as ordinary native runs. */
|
||||
export async function probeNativeRunnerEnvironment(options: {
|
||||
runtimeDirectory: string;
|
||||
provider: "codex";
|
||||
@@ -7168,7 +7162,6 @@ export async function probeNativeRunnerEnvironment(options: {
|
||||
reasoningEffort?: string;
|
||||
environment: NodeJS.ProcessEnv;
|
||||
timeoutMs?: number;
|
||||
/** The selected remote filesystem supplies this cwd; never inspect it on the controller. */
|
||||
workingDirectory?: string;
|
||||
transportOptions?: CapabilityRunnerdCodexTransportOptions;
|
||||
onCodexCredentialRefresh?: (filename: string) => Promise<void>;
|
||||
@@ -7189,9 +7182,6 @@ export async function probeNativeRunnerEnvironment(options: {
|
||||
let registrationOperation: Promise<unknown> | undefined;
|
||||
let registrationCleanupConfirmed = true;
|
||||
const sourceCodexHome = options.transportOptions?.sourceCodexHome ?? resolveSourceCodexHome(environment);
|
||||
// Never let an unbound home or an external-sandbox flag admit host state or
|
||||
// weaken this read-only probe. The normal materializer copies bound auth and
|
||||
// the managed provider projection into a new daemon-owned Codex home.
|
||||
delete environment.PAPERCLIP_RUNNER_EXTERNAL_SANDBOX;
|
||||
environment.HOME = resolve(remoteFilesystemRoot ?? options.runtimeDirectory, "probe-home");
|
||||
environment.CODEX_HOME = resolve(remoteFilesystemRoot ?? options.runtimeDirectory, "codex-home");
|
||||
@@ -7310,8 +7300,6 @@ export async function probeNativeRunnerEnvironment(options: {
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
controller.abort(failure ?? new Error("Native provider hello probe complete."));
|
||||
// Preserve private recovery state if the production close cannot confirm
|
||||
// teardown; callers remove it only after an authoritative receipt.
|
||||
const teardownErrors: unknown[] = [];
|
||||
const teardownDeadline = Date.now() + 10_000;
|
||||
const awaitTeardown = async (pending: Promise<unknown>, detail: string): Promise<void> => {
|
||||
|
||||
Reference in new issue
Block a user