mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-04 11:01:16 +01:00
chore: follow source comment contribution rules
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
9f59ff36cf
commit
c1b9933a24
35 files changed
+25
-157
No files matched your search
@@ -227,17 +227,17 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
with:
|
||||
ref: ${{ inputs.qualification_source_sha }}
|
||||
persist-credentials: false
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86
|
||||
with:
|
||||
version: 9.15.4
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
|
||||
with:
|
||||
node-version: 24
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
|
||||
with:
|
||||
pattern: ${{ needs.qualification_daemons.outputs.runner_artifact_prefix }}-*
|
||||
path: ${{ runner.temp }}/release-runner-inputs
|
||||
@@ -275,7 +275,7 @@ jobs:
|
||||
fi
|
||||
- name: Retain completed package assembly even after a later Linux failure
|
||||
if: ${{ !cancelled() && steps.pack_identity.outputs.available == 'true' }}
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
with:
|
||||
name: ${{ inputs.artifact_name }}-public-packages
|
||||
path: ${{ runner.temp }}/public-runner-packs/
|
||||
@@ -292,17 +292,17 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJSON(needs.qualification_daemons.outputs.native_consumer_matrix) }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
with:
|
||||
ref: ${{ inputs.qualification_source_sha }}
|
||||
persist-credentials: false
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86
|
||||
with:
|
||||
version: 9.15.4
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
|
||||
with:
|
||||
node-version: 24
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
|
||||
with:
|
||||
name: ${{ inputs.artifact_name }}-public-packages
|
||||
path: ${{ runner.temp }}/public-runner-packs
|
||||
@@ -310,9 +310,8 @@ jobs:
|
||||
run: pnpm install --ignore-scripts --no-frozen-lockfile
|
||||
- name: Checkout immutable ARM qualification tooling
|
||||
if: ${{ matrix.target == 'linux-arm64' }}
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
with:
|
||||
# job.workflow_* identifies this reusable workflow, not its caller.
|
||||
repository: ${{ job.workflow_repository }}
|
||||
ref: ${{ job.workflow_sha }}
|
||||
path: .qualification-tooling
|
||||
@@ -363,7 +362,7 @@ jobs:
|
||||
if [[ "$TARGET" == linux-arm64 ]]; then mode=--linux-arm64-consumer; fi
|
||||
node scripts/verify-grok-npm-install.mjs "$mode" \
|
||||
"$RUNNER_TEMP/public-runner-packs" "$SOURCE_SHA" "$RUNNER_TEMP/native-public-install.json"
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
if: always()
|
||||
with:
|
||||
name: ${{ inputs.artifact_name }}-${{ matrix.target }}
|
||||
|
||||
@@ -527,11 +527,11 @@ jobs:
|
||||
matrix: ${{ steps.targets.outputs.matrix }}
|
||||
native_consumers: ${{ steps.targets.outputs.native_consumers }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
with:
|
||||
ref: ${{ inputs.ref }}
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
|
||||
with:
|
||||
node-version: 24
|
||||
- name: Preserve the immutable source's platform contract
|
||||
@@ -564,8 +564,6 @@ jobs:
|
||||
&& Object.keys(contract).sort().join() === 'schema,targets', 'Unknown exact-source daemon platform contract');
|
||||
declared = contract.targets;
|
||||
} else {
|
||||
// Frozen pre-contract sources used a literal export. Read that one
|
||||
// declaration as JSON data; never import or evaluate preview code.
|
||||
const legacy = readBlob('packages/paperclip-runner/scripts/stage-release-runner-binaries.mjs');
|
||||
const declarations = [...(legacy ?? '').matchAll(/^[ \t]*export const RELEASE_RUNNER_TARGETS = (?:Object\.freeze\()?(\[[^\r\n]*\])\)?;[ \t]*$/gm)];
|
||||
assert.equal(declarations.length, 1, 'Unknown exact-source daemon platform contract');
|
||||
@@ -596,14 +594,14 @@ jobs:
|
||||
outputs:
|
||||
artifact_prefix: ${{ steps.identity.outputs.artifact_prefix }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
with:
|
||||
ref: ${{ inputs.ref }}
|
||||
persist-credentials: false
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86
|
||||
with:
|
||||
version: 9.15.4
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
|
||||
with:
|
||||
node-version: 24
|
||||
- name: Bind immutable producer source and target
|
||||
@@ -640,18 +638,13 @@ jobs:
|
||||
fi
|
||||
rustup toolchain list
|
||||
- name: Cache native Rust dependencies
|
||||
uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6
|
||||
with:
|
||||
workspaces: ${{ steps.native_rust_workspace.outputs.path }} -> target
|
||||
# Compiler/environment and Cargo lock hashes are added by the action.
|
||||
# Also bind generated contracts outside the Rust workspace, while
|
||||
# always recompiling workspace code and recording actual new bytes.
|
||||
shared-key: release-native-v2-${{ matrix.host }}-${{ matrix.target }}-${{ hashFiles('packages/paperclip-runner/protocol/**', 'packages/paperclip-runner/src/**', 'packages/paperclip-runner/*.json') }}
|
||||
cache-workspace-crates: false
|
||||
cache-bin: false
|
||||
cache-on-failure: false
|
||||
# A master workflow can verify an unmerged preview ref. Those jobs
|
||||
# restore only; they never write master's trusted dependency cache.
|
||||
save-if: ${{ github.repository == 'paperclipai/paperclip' && github.repository_id == '1170821064' && github.event_name == 'push' && github.ref == 'refs/heads/master' && inputs.ref == github.sha }}
|
||||
- name: Install dependencies
|
||||
run: pnpm install --no-frozen-lockfile
|
||||
@@ -694,7 +687,7 @@ jobs:
|
||||
run: |
|
||||
node packages/paperclip-runner/scripts/stage-release-runner-binaries.mjs record "$SOURCE_SHA" "$TARGET" \
|
||||
packages/paperclip-runner/dist/bin/paperclip-runnerd "$RUNNER_TEMP/runner-release/$TARGET"
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
with:
|
||||
name: ${{ steps.identity.outputs.artifact_prefix }}-${{ matrix.target }}
|
||||
path: ${{ runner.temp }}/runner-release/
|
||||
|
||||
@@ -341,8 +341,6 @@ jobs:
|
||||
run: node scripts/cloud-source-verification.mjs "$SOURCE_SHA"
|
||||
|
||||
release_runner_source:
|
||||
# Old promotions retain their already-shipped packaging contract. New
|
||||
# sources opt into source-declared native target assembly in their release.sh.
|
||||
needs: [verify_canary, select_nightly, select_beta, preflight_stable]
|
||||
if: >-
|
||||
!cancelled() && (
|
||||
@@ -357,7 +355,7 @@ jobs:
|
||||
sha: ${{ steps.source.outputs.sha }}
|
||||
required: ${{ steps.source.outputs.required }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
with:
|
||||
ref: ${{ needs.preflight_stable.outputs.sha || needs.select_beta.outputs.sha || needs.select_nightly.outputs.sha || github.sha }}
|
||||
persist-credentials: false
|
||||
@@ -427,7 +425,7 @@ jobs:
|
||||
|
||||
- name: Download exact-source release daemon receipts
|
||||
if: needs.release_runner_source.outputs.required == 'true'
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
|
||||
with:
|
||||
pattern: ${{ needs.release_runner_binaries.outputs.runner_artifact_prefix }}-*
|
||||
path: ${{ runner.temp }}/release-runner-inputs
|
||||
@@ -724,7 +722,7 @@ jobs:
|
||||
|
||||
- name: Download exact-source release daemon receipts
|
||||
if: needs.release_runner_source.outputs.required == 'true'
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
|
||||
with:
|
||||
pattern: ${{ needs.release_runner_binaries.outputs.runner_artifact_prefix }}-*
|
||||
path: ${{ runner.temp }}/release-runner-inputs
|
||||
@@ -1003,7 +1001,7 @@ jobs:
|
||||
|
||||
- name: Download exact-source release daemon receipts
|
||||
if: needs.release_runner_source.outputs.required == 'true'
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
|
||||
with:
|
||||
pattern: ${{ needs.release_runner_binaries.outputs.runner_artifact_prefix }}-*
|
||||
path: ${{ runner.temp }}/release-runner-inputs
|
||||
@@ -1384,7 +1382,7 @@ jobs:
|
||||
|
||||
- name: Download exact-source release daemon receipts
|
||||
if: needs.release_runner_source.outputs.required == 'true'
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
|
||||
with:
|
||||
pattern: ${{ needs.release_runner_binaries.outputs.runner_artifact_prefix }}-*
|
||||
path: ${{ runner.temp }}/release-runner-inputs
|
||||
@@ -1442,7 +1440,7 @@ jobs:
|
||||
|
||||
- name: Download exact-source release daemon receipts
|
||||
if: needs.release_runner_source.outputs.required == 'true'
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
|
||||
with:
|
||||
pattern: ${{ needs.release_runner_binaries.outputs.runner_artifact_prefix }}-*
|
||||
path: ${{ runner.temp }}/release-runner-inputs
|
||||
|
||||
@@ -5,7 +5,6 @@ import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("node:child_process", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("node:child_process")>();
|
||||
// Execute the authored remote command locally, through both subprocess paths.
|
||||
return {
|
||||
...actual,
|
||||
execFile: vi.fn((file: string, args: string[], options: object, callback: Parameters<typeof actual.execFile>[3]) => {
|
||||
|
||||
@@ -68,8 +68,6 @@ export function createSshCommandManagedRuntimeRunner(input: {
|
||||
const envPrefix = envEntries.length > 0
|
||||
? `env ${envEntries.map(([key, value]) => shellQuote(`${key}=${value}`)).join(" ")} `
|
||||
: "";
|
||||
// Keep sh -c's command name and positional arguments intact. Flattening
|
||||
// its script loses $0/$1/... and breaks native runner launch/identity.
|
||||
const commandScript = `exec ${envPrefix}${[shellQuote(command), ...args.map((arg) => shellQuote(arg))].join(" ")}`;
|
||||
const remoteCommand = `cd ${shellQuote(cwd)} && ${commandScript}`;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1335,7 +1335,6 @@ export interface CapabilityRunnerdCodexTransportOptions {
|
||||
controlPlaneRegistration?: (
|
||||
authority: DurablePrpControlPlane,
|
||||
identity?: DurableRecoveryIdentity,
|
||||
/** Setup owners abort preparation before draining or releasing its result. */
|
||||
signal?: AbortSignal,
|
||||
) => Promise<{
|
||||
connectUrl?: string;
|
||||
@@ -4462,8 +4461,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"));
|
||||
}
|
||||
@@ -7101,8 +7098,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();
|
||||
@@ -7153,7 +7148,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";
|
||||
@@ -7161,7 +7155,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>;
|
||||
@@ -7182,9 +7175,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");
|
||||
@@ -7303,8 +7293,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> => {
|
||||
|
||||
@@ -133,7 +133,6 @@ test('verification never elevates PR-controlled provisioning or cleanup on the h
|
||||
|
||||
test('the image and Grok downloads start before staging and join without weakening the npm checks', () => {
|
||||
const source = readFileSync(new URL('../verify-grok-npm-install.mjs', import.meta.url), 'utf8');
|
||||
// The optional Mac consumer owns separate scratch and no Linux prefetches.
|
||||
const linuxSource = source.slice(source.indexOf("assert.equal(process.platform, 'linux'"));
|
||||
const position = fragment => { const index = linuxSource.indexOf(fragment); assert.notEqual(index, -1, `verifier must contain ${fragment}`); return index; };
|
||||
// Exactly the pinned consumer image is pulled, for the platform the probes run on.
|
||||
@@ -332,8 +331,6 @@ test('the installed Codex probe exercises the public export and rejects incomple
|
||||
writeFileSync(legacyCommand, '#!/bin/sh\nexit 0\n', { mode: 0o755 });
|
||||
writeFileSync(index, `export const resolvePinnedCodexCommand = () => ${JSON.stringify(legacyCommand)};`);
|
||||
result = run(); assert.notEqual(result.status, 0); assert.match(result.stderr, /Selected Codex platform version must match the qualified pin/);
|
||||
// Native Codex uses protocol compatibility, not the ACPX version allowlist.
|
||||
// Keep exact official-npm host provenance and the selected resource boundary.
|
||||
mkdirSync(join(legacy, 'vendor'));
|
||||
const legacyNative = join(legacy, 'vendor', triple, 'codex', process.platform === 'win32' ? 'codex.exe' : 'codex');
|
||||
mkdirSync(join(legacy, 'vendor', triple, 'codex'), { recursive: true }); writeFileSync(legacyNative, nativeBytes, { mode: 0o755 });
|
||||
|
||||
@@ -838,9 +838,6 @@ const waitForFixture = async (predicate, label) => {
|
||||
assert.fail(label);
|
||||
};
|
||||
|
||||
// Execute the workflow's actual coordinator with two tiny local Node commands.
|
||||
// Both must cross the start barrier before either can finish; no pnpm, Cargo,
|
||||
// provider, registry, or Docker build runs in these fixtures.
|
||||
async function withNativePreparationFixture(mode, check) {
|
||||
const root = realpathSync(mkdtempSync(path.join(tmpdir(), 'native-preparation-')));
|
||||
let controller;
|
||||
@@ -1007,7 +1004,6 @@ test("ARM qualification binds separate product and reusable-workflow revisions e
|
||||
TOOLING_WORKFLOW_REF: "paperclipai/paperclip/.github/workflows/release-smoke.yml@refs/heads/qualification",
|
||||
TOOLING_WORKFLOW_PATH: ".github/workflows/release-smoke.yml",
|
||||
FIXTURE_PRODUCT_HEAD: productHead, FIXTURE_TOOLING_HEAD: toolingHead, FIXTURE_PREPARED: prepared,
|
||||
// Reusable calls retain the caller context; it must not select tooling.
|
||||
GITHUB_REPOSITORY: "different/caller", GITHUB_SHA: "d".repeat(40),
|
||||
} });
|
||||
assert.equal(result.status, expectedStatus, result.stderr);
|
||||
|
||||
@@ -50,9 +50,6 @@ export function resolveNativeRunnerDevBinary({
|
||||
const flat = path.join(packageRoot, "dist", "bin", executable);
|
||||
const targetStat = statSync(target, { throwIfNoEntry: false });
|
||||
const flatStat = statSync(flat, { throwIfNoEntry: false });
|
||||
// Release targets remain authoritative unless a newer source build matches
|
||||
// this host. In particular, a build-host flat artifact cannot mask a remotely
|
||||
// staged Mac target or force a local Cargo build.
|
||||
if (flatStat?.isFile() && (!targetStat?.isFile() || flatStat.mtimeMs > targetStat.mtimeMs)
|
||||
&& runnerBinaryTarget(readFileSync(flat)) === `${platform}-${architecture}`) {
|
||||
return { binary: flat, sourceBuild: true };
|
||||
|
||||
@@ -172,8 +172,6 @@ const env: NodeJS.ProcessEnv = {
|
||||
...process.env,
|
||||
PAPERCLIP_UI_DEV_MIDDLEWARE: explicitUiDevMiddleware ?? (serveBuiltUiForManagedRuntime ? "false" : "true"),
|
||||
};
|
||||
// An automatic source-build override must not hide subsequent Rust edits from
|
||||
// the watcher. Keep the operator's original choice separate from server env.
|
||||
const configuredRunnerBinary = env.PAPERCLIP_RUNNER_BINARY;
|
||||
|
||||
if (mode === "dev") {
|
||||
|
||||
@@ -11,8 +11,6 @@ export const GROK_PUBLIC_INSTALL_LIFECYCLE = [
|
||||
'npm', 'rebuild', '--offline', '--ignore-scripts=false', '--dangerously-allow-all-scripts',
|
||||
];
|
||||
|
||||
// setup-node provides its matching SDK alongside the executing Node binary.
|
||||
// Deferred lifecycle scripts may read that SDK, but may never download headers.
|
||||
export function macNodeHeadersDirectory(nodeExecutable, nodeVersion) {
|
||||
assert.match(nodeVersion, /^\d+\.\d+\.\d+$/, 'Executing Node must have an exact release version');
|
||||
const executable = realpathSync(nodeExecutable);
|
||||
@@ -200,7 +198,6 @@ export function installedCodexProbeSource(indexPath, consumerRoot, expectedVersi
|
||||
`;
|
||||
}
|
||||
|
||||
/** Installed bytes and consumer resolution must survive host protocol startup. */
|
||||
export function assertInstalledNativeConsumerContinuity(before, after) {
|
||||
for (const field of ['lockSha256', 'runnerSha256', 'codexCommandSha256', 'codexNativeExecutableSha256']) {
|
||||
if (!/^[a-f0-9]{64}$/.test(before[field]) || before[field] !== after[field]) {
|
||||
@@ -209,7 +206,6 @@ export function assertInstalledNativeConsumerContinuity(before, after) {
|
||||
}
|
||||
}
|
||||
|
||||
/** The installed consumer must select its packaged host daemon, never PATH. */
|
||||
export function installedDaemonProbeSource(outputRoot, consumerRoot, sourceRevision) {
|
||||
return `
|
||||
import assert from 'node:assert/strict';
|
||||
@@ -254,8 +250,6 @@ export function installedDaemonProbeSource(outputRoot, consumerRoot, sourceRevis
|
||||
`;
|
||||
}
|
||||
|
||||
// Hosted, credential-free Mac consumers defer only npm lifecycle execution.
|
||||
// Offline npm flags are not an OS network boundary; sandbox-exec supplies it.
|
||||
export function macPublicInstallLifecycleProfile(ownedRoot) {
|
||||
if (!ownedRoot.startsWith('/') || ownedRoot.includes('\0')) throw new Error('Mac lifecycle requires an absolute owned root');
|
||||
return `(version 1)\n(allow default)\n(deny network*)\n(deny file-write* (require-all (require-not (subpath ${JSON.stringify(ownedRoot)})) (require-not (literal "/dev/null"))))\n`;
|
||||
|
||||
@@ -32,8 +32,6 @@ export async function verifyPiPackagedDaemon(server, daemon, target, runnerBinar
|
||||
assert.equal(`sha256:${createHash('sha256').update(bytes).digest('hex')}`, artifact.sha256);
|
||||
assert.equal(runnerBinaryTarget(bytes), target);
|
||||
} else {
|
||||
// Existing host-only source packages have no multi-target release manifest.
|
||||
// They must still use their exact normal flat daemon, never an override.
|
||||
assert.equal(await realpath(daemon), expectedPath);
|
||||
assert.equal(runnerBinaryTarget(await readFile(expectedPath)), target);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
# Prepare the disposable GitHub ARM qualification host using stock distro files.
|
||||
# Extraction skips package maintainer scripts and their global policy changes.
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "${GITHUB_ACTIONS:-}" != true || "${RUNNER_OS:-}" != Linux ]]; then
|
||||
@@ -87,7 +85,6 @@ test "$(od -An -tx1 -N4 "$stock_binary" | tr -d ' \n')" = 7f454c46
|
||||
readelf -h "$stock_binary" | tee "$prerequisite_report_dir/bwrap-elf-header.txt"
|
||||
grep -E 'Machine:.*AArch64' "$prerequisite_report_dir/bwrap-elf-header.txt"
|
||||
|
||||
# Reject every target symlink, including dangling links, before privileged copy.
|
||||
test ! -L /usr/bin/bwrap
|
||||
if test -e /usr/bin/bwrap; then
|
||||
test -f /usr/bin/bwrap
|
||||
|
||||
@@ -284,9 +284,6 @@ release_info ""
|
||||
release_info "==> Step 2/7: Building workspace artifacts..."
|
||||
cd "$REPO_ROOT"
|
||||
pnpm build
|
||||
# Hosted producers supply data-only, hash/source/architecture-bound daemon
|
||||
# receipts. Assemble after the build (which stages its host binary), before
|
||||
# public packages are packed. The server vendors this same compiled runner.
|
||||
if [ -n "${PAPERCLIP_RELEASE_RUNNER_ARTIFACTS_DIR:-}" ]; then
|
||||
node "$REPO_ROOT/packages/paperclip-runner/scripts/stage-release-runner-binaries.mjs" \
|
||||
assemble-daemons "$PAPERCLIP_RELEASE_RUNNER_ARTIFACTS_DIR" "$(git rev-parse HEAD)"
|
||||
|
||||
@@ -36,8 +36,6 @@ async function verifyMacConsumer(packageDirectory, sourceRevision, receiptPath)
|
||||
for (const directory of [consumer, home, tools, join(root, 'tmp'), join(root, 'cache')]) mkdirSync(directory);
|
||||
const npmCli = realpathSync(execFileSync('/usr/bin/which', ['npm'], { encoding: 'utf8' }).trim());
|
||||
symlinkSync(process.execPath, join(tools, 'node'));
|
||||
// npm subprocesses get a Node-only owned tool directory; no ambient harness
|
||||
// command can hide missing installed dependencies.
|
||||
const env = { PATH: `${tools}:/usr/bin:/bin:/usr/sbin:/sbin`, HOME: home, TMPDIR: join(root, 'tmp'),
|
||||
npm_config_cache: join(root, 'cache'), npm_config_audit: 'false', npm_config_fund: 'false' };
|
||||
const run = (command, args, cwd = consumer, runEnv = env) => execFileSync(command, args, { cwd, env: runEnv,
|
||||
@@ -147,13 +145,8 @@ async function verifyLinuxArmConsumer(packageDirectory, sourceRevision, receiptP
|
||||
const daemon = probe('daemon-probe.mjs', installedDaemonProbeSource(installed, '/consumer', sourceRevision));
|
||||
assert.equal(daemon.target, 'linux-arm64');
|
||||
const codex = probe('codex-probe.mjs', installedCodexProbeSource(installed + '/index.js', '/consumer', undefined, { nativeCodex: true }));
|
||||
// Reuse the existing zero-turn hook as read-only verifier source. All
|
||||
// production imports still resolve from the installed consumer graph.
|
||||
cpSync(join(repo, 'tests/runner-e2e/installed-cli.ts'), join(assets, 'installed-cli.ts'));
|
||||
writeFileSync(join(assets, 'types.js'), stripTypeScriptTypes(readFileSync(join(repo, 'tests/runner-e2e/types.ts'), 'utf8')));
|
||||
// The restricted install container deliberately forbids nested namespaces.
|
||||
// Keep its lifecycle/no-network proof, then test the exact same bind-mounted
|
||||
// graph on the native host using Codex's normal read-only namespace policy.
|
||||
const home = join(root, 'host-home'), hostTmp = join(root, 'host-tmp');
|
||||
for (const directory of [home, hostTmp]) mkdirSync(directory, { mode: 0o700 });
|
||||
const hostEnv = { PATH: `${dirname(process.execPath)}:/usr/bin:/bin:/usr/sbin:/sbin`, HOME: home, TMPDIR: hostTmp, LC_ALL: 'C', TZ: 'UTC' };
|
||||
@@ -330,9 +323,6 @@ try {
|
||||
timing.stage_pack_ms = Math.round(performance.now() - packStarted);
|
||||
Object.assign(timing, packTimings(packs, concurrency));
|
||||
|
||||
// Preserve the exact Linux-produced public tarballs for independent Mac
|
||||
// consumers even if a later Linux runtime probe fails. This is a packaging
|
||||
// receipt, never an installation or provider-admission pass.
|
||||
if (process.env.PAPERCLIP_RUNNER_PUBLIC_PACKS_DIR) {
|
||||
const output = resolve(process.env.PAPERCLIP_RUNNER_PUBLIC_PACKS_DIR);
|
||||
assert.equal(JSON.parse(readFileSync(join(repo, 'server/dist/build-info.json'))).commit, sourceRevision);
|
||||
|
||||
@@ -140,7 +140,6 @@ describe("paperclip runner native dev prerequisite", () => {
|
||||
fs.utimesSync(target, current, current);
|
||||
const input = { repoRoot: checkout.root, nativeRunnerRequired: true, platform: "darwin", architecture };
|
||||
expect(paperclipRunnerBinaryNeedsBuild(input)).toBe(false);
|
||||
// A newer Linux build-host output cannot mask the remote Mac artifact.
|
||||
fs.writeFileSync(checkout.binary, nativeHeader("linux", "x64"));
|
||||
const wrongHostNewer = new Date(Date.now() + 4000);
|
||||
fs.utimesSync(checkout.binary, wrongHostNewer, wrongHostNewer);
|
||||
|
||||
@@ -154,8 +154,6 @@ describeEmbeddedPostgres("environmentService leases", () => {
|
||||
config: { config: { ...config, image: `registry.example.test/runtime@sha256:${"b".repeat(64)}` } },
|
||||
driver: { driver: "ssh", config: { host: "fixture.example.test", username: "fixture" } },
|
||||
};
|
||||
// Interleave the real probe UPDATE after the save is prepared but before its
|
||||
// real UPDATE executes. This reproduces the stale route-read/write window.
|
||||
const delayedWriteDb = {
|
||||
update(table: typeof environments) {
|
||||
const prepared = db.update(table);
|
||||
|
||||
@@ -63,7 +63,6 @@ describe("removeTestHome", () => {
|
||||
const home = makeHome();
|
||||
fs.mkdirSync(path.join(home, "instances", "default"), { recursive: true });
|
||||
fs.writeFileSync(path.join(home, "instances", "default", "paperclip.json"), "{}\n");
|
||||
// An unrelated ENOTEMPTY must not cause permission changes or be swallowed.
|
||||
const error = Object.assign(new Error("unrelated nonempty directory"), { code: "ENOTEMPTY" });
|
||||
const removal = vi.spyOn(fs, "rmSync").mockImplementation(() => { throw error; });
|
||||
const permissionChange = vi.spyOn(fs, "chmodSync");
|
||||
|
||||
@@ -9,8 +9,7 @@ import path from "node:path";
|
||||
* in them read-only (`makeDirectoriesReadOnly` in
|
||||
* services/native-runtime/runtime-context.ts), and a run that is still
|
||||
* finishing when the worker exits can leave such a bundle under the worker's
|
||||
* home. Removing a file inside a read-only directory fails with `EACCES`
|
||||
* or, on macOS, the recursive removal can report `ENOTEMPTY` instead.
|
||||
* home. Removing a file inside a read-only directory fails with `EACCES`,
|
||||
* Vitest reports it as an unhandled error, and the shard fails after every
|
||||
* test passed. So on a permission error every directory in the tree is made
|
||||
* writable first and the removal is tried once more.
|
||||
@@ -23,8 +22,6 @@ export function removeTestHome(home: string): void {
|
||||
const permissionError = isPermissionError(error);
|
||||
if (!permissionError && (error as NodeJS.ErrnoException | null)?.code !== "ENOTEMPTY") throw error;
|
||||
const restored = makeDirectoriesWritable(home);
|
||||
// ENOTEMPTY also has unrelated causes; retry only after restoring an
|
||||
// actual read-only directory, and still surface a failed second removal.
|
||||
if (!permissionError && !restored) throw error;
|
||||
fs.rmSync(home, options);
|
||||
}
|
||||
@@ -51,7 +48,6 @@ function makeDirectoriesWritable(directory: string): boolean {
|
||||
fs.chmodSync(directory, stat.mode | 0o700);
|
||||
restored = true;
|
||||
} catch {
|
||||
// A directory that cannot be changed still fails the removal below, loudly.
|
||||
}
|
||||
}
|
||||
let entries: fs.Dirent[];
|
||||
|
||||
@@ -13,7 +13,6 @@ import { readConfigValueAtPath, writeConfigValueAtPath } from "./json-schema-sec
|
||||
type TemplateRow = typeof environmentCustomImageTemplates.$inferSelect;
|
||||
|
||||
export const ENVIRONMENT_CUSTOM_IMAGE_RUNTIME_CONFIG_BINDING_METADATA_KEY = "runtimeConfigBinding";
|
||||
/** Measured platform evidence is reserved to explicit server-side environment probes. */
|
||||
export const SANDBOX_PLATFORM_PROBE_METADATA_KEY = "paperclipSandboxPlatformProbes";
|
||||
export const ENVIRONMENT_CUSTOM_IMAGE_CONFIG_FINGERPRINT_EXCLUDED_PATHS = [
|
||||
"timeoutMs",
|
||||
|
||||
@@ -41,14 +41,12 @@ async function sandboxBinding(db: Db, environment: Environment, companyId: strin
|
||||
});
|
||||
if (parsed.driver !== "sandbox") return null;
|
||||
const image = parsed.config.image;
|
||||
// Names, mutable tags, provider defaults and snapshot names are not immutable identities.
|
||||
const template = selectedTemplate.value;
|
||||
if (!isImmutableImage(image) || ["snapshot", "template", "templateRef"].some(key => record(parsed.config)[key])
|
||||
|| (template && (template.templateKind !== "image" || template.bindingField !== "image"))) return null;
|
||||
return { baseConfigFingerprint: fingerprint(environment.config), runtimeConfigFingerprint: fingerprint(parsed.config as unknown as Record<string, unknown>), image, template };
|
||||
}
|
||||
|
||||
/** Read measured, company-scoped image evidence without leasing or contacting a provider. */
|
||||
export async function readSandboxPlatformQualification(db: Db, environment: Environment, companyId: string): Promise<PlatformFacts | null> {
|
||||
const proof = record(record(environment.metadata?.[SANDBOX_PLATFORM_PROBE_METADATA_KEY])[companyId]);
|
||||
if (proof.v !== 1 || proof.companyId !== companyId || proof.baseConfigFingerprint !== fingerprint(environment.config)) return null;
|
||||
|
||||
@@ -1144,8 +1144,6 @@ export function environmentService(db: Db) {
|
||||
values.metadata = userMetadata;
|
||||
} else {
|
||||
const replacement = userMetadata === null ? sql`null::jsonb` : sql`${JSON.stringify(userMetadata)}::jsonb`;
|
||||
// Preserve the current private subtree in the same statement. A route's
|
||||
// earlier read must never replace a newer concurrent probe receipt.
|
||||
metadataUpdate = sql`case when ${environments.metadata} ? ${SANDBOX_PLATFORM_PROBE_METADATA_KEY}
|
||||
then coalesce(${replacement}, '{}'::jsonb) || jsonb_build_object(
|
||||
${SANDBOX_PLATFORM_PROBE_METADATA_KEY}::text, ${environments.metadata}->${SANDBOX_PLATFORM_PROBE_METADATA_KEY})
|
||||
|
||||
@@ -45,8 +45,6 @@ function targetPlatform(platform: string, architecture: string, glibc = true, gl
|
||||
}
|
||||
|
||||
function resolveFixtureCodexCommand(home: string): string {
|
||||
// Vitest's global dependency paths can supply a foreign bridge before the
|
||||
// fixture wrapper. Reuse the existing Codex security test's closed Node seam.
|
||||
const source = `
|
||||
import { resolveCodexCommand } from ${JSON.stringify(new URL("../../../packages/paperclip-runner/src/drivers/codex/codex-command.ts", import.meta.url).href)};
|
||||
try { console.log(JSON.stringify({ command: resolveCodexCommand(${JSON.stringify(path.join(root!, "issuer.js"))}, { PATH: "/missing-codex" }, ${JSON.stringify(home)}) })); }
|
||||
|
||||
@@ -56,8 +56,6 @@ export function startLocalBrowserLogin(provider: "anthropic" | "openai", home: s
|
||||
async start(_command: string, onData: (chunk: string) => void): Promise<{ exitCode: number | null }> {
|
||||
let executable = "claude";
|
||||
if (provider === "openai") {
|
||||
// The installed Codex CLI is independent of the runner daemon's glibc floor.
|
||||
// Supported CLI hosts use owned resolution; other platforms keep PATH lookup.
|
||||
const legacyPlatform = !((process.platform === "linux" || process.platform === "darwin")
|
||||
&& (process.arch === "x64" || process.arch === "arm64"));
|
||||
try {
|
||||
|
||||
@@ -208,7 +208,6 @@ export function resolvePaperclipRunnerBinary(
|
||||
);
|
||||
}
|
||||
|
||||
/** Select controller-owned bytes for the actual remote host, never its controller. */
|
||||
export async function resolveRemotePaperclipRunnerBinary(input: {
|
||||
runner: CommandManagedRuntimeRunner;
|
||||
remoteCwd: string;
|
||||
@@ -224,8 +223,6 @@ export async function resolveRemotePaperclipRunnerBinary(input: {
|
||||
}
|
||||
const configured = input.configuredPath?.trim();
|
||||
if (configured && !isAbsolute(configured)) throw new Error("PAPERCLIP_RUNNER_REMOTE_BINARY_PATH must be an absolute path");
|
||||
// A same-host sandbox can use the supervisor's fresh development artifact.
|
||||
// Cross-platform targets must never inherit the controller's override.
|
||||
const controllerOverride = platform === process.platform && architecture === process.arch
|
||||
? process.env.PAPERCLIP_RUNNER_BINARY : undefined;
|
||||
const binary = configured || (controllerOverride
|
||||
|
||||
@@ -12354,8 +12354,6 @@ describe("runnerd provider runtime wiring", () => {
|
||||
{ version: "0.156.0", model: "gpt-6.1-sol", floor: null, recover: true },
|
||||
{ version: "0.158.0", model: "gpt-6.1-sol", floor: null, recover: true },
|
||||
])("lets sandbox Codex $version reach actual model verification ($model, recover=$recover)", async ({ version, model, recover }) => {
|
||||
// Version metadata is diagnostic. A working app-server reaches the model
|
||||
// turn instead of being rejected or replaced by a static CLI version floor.
|
||||
const preparedModel = recover ? resolvePaperclipRunnerNativeProviderInput({
|
||||
backend: "codex_app_server", adapterConfig: { provider: "codex", model }, codexCliVersion: version,
|
||||
}).model : model;
|
||||
|
||||
@@ -13385,7 +13385,6 @@ async function createRunnerdBackendWithinSessionClaim(
|
||||
} satisfies NativeSessionBackend & { bindManagedSession(session: NativeSession): NativeSession };
|
||||
}
|
||||
|
||||
/** Shared task/setup preparation; setup exercises the same remote artifact contract. */
|
||||
export function createRemoteNativeArtifactPreparation(input: {
|
||||
target: Extract<AdapterExecutionTarget, { kind: "remote" }>;
|
||||
runner: CommandManagedRuntimeRunner;
|
||||
@@ -13398,7 +13397,6 @@ export function createRemoteNativeArtifactPreparation(input: {
|
||||
runnerRemoteCodexNpmSpec?: string | null;
|
||||
model?: string | null;
|
||||
reuseRetainedRunner?: boolean;
|
||||
/** The selected Pi companion or bundled provider image verifies its target-platform artifact. */
|
||||
targetPlatformArtifactVerified?: boolean;
|
||||
trace?: NativeRunTrace;
|
||||
onLog?: (stream: "stdout" | "stderr", chunk: string) => Promise<void>;
|
||||
@@ -13451,9 +13449,6 @@ export function createRemoteNativeArtifactPreparation(input: {
|
||||
}
|
||||
const versionOutput = `${versionResult.stdout}\n${versionResult.stderr}`;
|
||||
const version = parseCodexCliVersion(versionOutput);
|
||||
// The install pin describes our tested image, not a CLI admission rule.
|
||||
// Ordinary installations reach the real app-server/model probe; a version
|
||||
// difference alone must neither fail setup nor replace a working CLI.
|
||||
if (version !== REMOTE_PROVIDER_PACK_PINS.codex && !reportedCodexVersions.has(version)) {
|
||||
reportedCodexVersions.add(version);
|
||||
await input.onLog?.(
|
||||
@@ -13463,9 +13458,6 @@ export function createRemoteNativeArtifactPreparation(input: {
|
||||
}
|
||||
};
|
||||
|
||||
// Image policy: keep one latest stable CLI installation shared by native and
|
||||
// local adapters. Preferred bin entries must point to that same installation;
|
||||
// never bake an older global CLI alongside a private runner-only version.
|
||||
const discoverPreinstalledExecutable = async (
|
||||
name: "paperclip-runnerd" | "codex",
|
||||
) => {
|
||||
@@ -13500,8 +13492,6 @@ export function createRemoteNativeArtifactPreparation(input: {
|
||||
};
|
||||
|
||||
const prepare = async (requiredMode: "dial_wss" | "listen_ws") => {
|
||||
// Compatibility metadata does not prove artifact identity. Reuse only the
|
||||
// exact controller-owned bytes when the controller artifact is available.
|
||||
const matchesControllerRunnerArtifact = async (executable: string): Promise<boolean> => {
|
||||
const expected = createHash("sha256")
|
||||
.update(readFileSync(controllerRunnerBinary))
|
||||
@@ -13523,7 +13513,6 @@ export function createRemoteNativeArtifactPreparation(input: {
|
||||
/^[a-f0-9]{64}\s/.test(probe.stdout) &&
|
||||
probe.stdout.trim().split(/\s+/)[0] === expected;
|
||||
} catch {
|
||||
// An unavailable checksum uses the verified staging path.
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -13572,9 +13561,6 @@ export function createRemoteNativeArtifactPreparation(input: {
|
||||
}
|
||||
}
|
||||
if (!runnerArtifactPrepared) {
|
||||
// Upload the same artifact used for the controller identity. The server
|
||||
// vendors the runner under vendor/paperclip-runner/bin, so the package
|
||||
// development fallback cannot locate it in a deployed server.
|
||||
const sourceBinary = controllerRunnerBinary;
|
||||
if (!existsSync(sourceBinary)) {
|
||||
throw new Error("runner_remote_artifact_unavailable");
|
||||
|
||||
@@ -12,8 +12,6 @@ import { CREDENTIAL_NAMES, type MatrixExecution } from "./types.js";
|
||||
export const installedCliKeys = ["PAPERCLIP_RUNNER_E2E_INSTALLED_CLI", "PAPERCLIP_RUNNER_E2E_INSTALLED_CLI_SHA256", "PAPERCLIP_RUNNER_E2E_INSTALLED_SERVER_ROOT", "PAPERCLIP_RUNNER_E2E_INSTALLED_SERVER_SHA256"] as const;
|
||||
const overrideKeys = ["PAPERCLIP_RUNNER_BINARY", "PAPERCLIP_RUNNER_REMOTE_BINARY_PATH", "PAPERCLIP_RUNNER_REMOTE_PROVIDER_PACK_PATH", "PAPERCLIP_RUNNER_ACPX_QUALIFICATION", "PAPERCLIP_ACPX_BUILTIN_ROOT", "PAPERCLIP_ACPX_PROVIDER_PACKAGE_ROOT", "PAPERCLIP_ACPX_PROVIDER_PACKAGE_MANIFEST"] as const;
|
||||
const suites = new Set(["extended-harnesses", "pi-native", "pi-controls", "rich-acp-warm-continuity"]);
|
||||
// Reuse the production-onboarding, independent file/revision, and restart
|
||||
// journeys. Installed proof does not add cases or relax their existing graders.
|
||||
const installedCodexCells = new Set([
|
||||
"first-task.runner-codex.local.task-card-accept",
|
||||
"everyday-workflows.runner-codex.local.build-revise",
|
||||
@@ -63,7 +61,6 @@ export async function verifyInstalledCli(env: NodeJS.ProcessEnv, executions: rea
|
||||
return { schema: "paperclip.e2e.installed-cli-admission/v1", entry, cliRoot, cliSha256, serverRoot, serverEntry, serverSha256, version: cli.version, defaultRuntimeResolution: true, qualificationOverride: false };
|
||||
}
|
||||
|
||||
/** Clean-install protocol proof only: no credentials, task, or provider turn. */
|
||||
export async function probeInstalledCodexStartup(input: {
|
||||
consumerRoot: string;
|
||||
serverRoot: string;
|
||||
@@ -110,8 +107,6 @@ export async function probeInstalledCodexStartup(input: {
|
||||
const codexSha256 = digest(await readOwned(codexCommand, 512 * 1024 * 1024));
|
||||
const codexVersion = execFileSync(codexCommand, ["--version"], { cwd: workspace, env: environment, encoding: "utf8", timeout: 10000, maxBuffer: 16384 }).trim();
|
||||
if (!codexVersion || codexVersion.length > 200) throw new Error("Installed Codex startup returned no bounded version identity");
|
||||
// Keep the production security arguments; pin file-only auth for this
|
||||
// credential-free fixture so macOS keychain accounts cannot be consulted.
|
||||
const args = runtime.createIsolatedCodexAppServerArgs(environment);
|
||||
args.splice(args.indexOf("app-server"), 0, "-c", 'cli_auth_credentials_store="file"');
|
||||
bundle = runtime.createRunnerdCodexTransport({ provider: "codex", stateDirectory: directory, environment,
|
||||
@@ -156,9 +151,6 @@ export async function probeInstalledCodexStartup(input: {
|
||||
} catch (error) { cleanupFailures.push(error); }
|
||||
if (startupFailed) {
|
||||
const evidence = bundle?.evidence();
|
||||
// This zero-turn fixture has empty homes and no provider credentials.
|
||||
// Report only bounded failure text and lifecycle state, never identities
|
||||
// or raw durable state. A close failure cannot hide the startup cause.
|
||||
const bounded = (value: unknown) => String(value).replaceAll(directory, "[private-startup]").slice(0, 2000);
|
||||
const primaryMessage = bounded(startupFailure instanceof Error ? startupFailure.message : startupFailure);
|
||||
const diagnostic = { schema: "paperclip.e2e.installed-codex-startup-failure/v1", platform: `${process.platform}-${process.arch}`,
|
||||
|
||||
@@ -64,7 +64,6 @@ export interface LiveFixtureValues {
|
||||
agent: AgentRecord;
|
||||
project?: ProjectRecord;
|
||||
aiConnection?: ManagedAccountFixture;
|
||||
/** Private setup receipt; its lease is included in existing runtime cost accounting. */
|
||||
setupProbe?: { leaseId: string; durationMs: number; defaultRunner?: string };
|
||||
|
||||
onboardingRuntime?: {
|
||||
@@ -82,7 +81,6 @@ function value<T>(resolved: ReadonlyMap<string, unknown>, id: string): T {
|
||||
return result as T;
|
||||
}
|
||||
|
||||
/** Preserve task/run attribution and include only this fixture's explicit setup probe. */
|
||||
export function selectFixtureRuntimeLeases<T extends {
|
||||
id: string; provider?: string | null; heartbeatRunId?: string | null; issueId?: string | null;
|
||||
}>(input: { leases: T[]; runIds: string[]; issueId?: string; setupProbeLeaseId?: string }): T[] {
|
||||
@@ -320,8 +318,6 @@ export async function setupLiveFixtures(input: {
|
||||
throw new Error("Native Codex qualification requires this fixture's immutable image-backed environment.");
|
||||
}
|
||||
const startedAt = Date.now();
|
||||
// One existing explicit connection probe: a fresh lease, bounded platform read,
|
||||
// verified release, and server-owned image evidence before agent creation.
|
||||
const probe = await api.post<{ ok: boolean; details?: { platformQualificationRecorded?: boolean; leaseId?: string } }>(
|
||||
`/api/environments/${environment.id}/probe?companyId=${encodeURIComponent(company.id)}`,
|
||||
{}, { timeout: 330_000 },
|
||||
|
||||
Reference in new issue
Block a user