mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-14 08:26:46 +02:00
Reconcile Pi prerequisites with the integrated runtime while holding admission
This commit is contained in:
1 parent
20ce062701
commit
f620e7157b
313 files changed
+13086
-2187
No files matched your search
@@ -38,6 +38,33 @@ test('only the scripts-disabled dependency download gets network access', () =>
|
||||
assert.ok(values(args, '--env').includes('npm_config_ignore_scripts=true'));
|
||||
});
|
||||
|
||||
test('Pi assembly gets bounded scratch capacity while preserving sandbox restrictions', () => {
|
||||
const args = grokConsumerDockerArgs({ ...paths, download: true, temporarySizeMiB: 2048, command: ['node', '/consumer/node_modules/paperclipai/dist/index.js', 'runtime', 'setup', 'pi'] });
|
||||
assert.deepEqual(values(args, '--tmpfs'), ['/tmp:rw,nosuid,nodev,noexec,size=2048m,mode=1777']);
|
||||
assert.deepEqual(values(args, '--memory'), ['3g']);
|
||||
assert.ok(args.includes('--read-only'));
|
||||
assert.deepEqual(values(args, '--cap-drop'), ['ALL']);
|
||||
assert.deepEqual(values(args, '--security-opt'), ['no-new-privileges']);
|
||||
for (const temporarySizeMiB of [0, 255, 2049, Infinity, '2048']) {
|
||||
assert.throws(() => grokConsumerDockerArgs({ ...paths, temporarySizeMiB, command: ['node'] }), /bounded/);
|
||||
}
|
||||
});
|
||||
|
||||
test('only an offline runtime probe can execute its verified scratch snapshot', () => {
|
||||
const command = ['node', '/packages/pi-public-install-probe.mjs', '/consumer/node_modules/@paperclipai/server'];
|
||||
const args = grokConsumerDockerArgs({ ...paths, command, temporarySizeMiB: 2048, temporaryExecutable: true });
|
||||
assert.deepEqual(values(args, '--tmpfs'), ['/tmp:rw,nosuid,nodev,exec,size=2048m,mode=1777']);
|
||||
assert.deepEqual(values(args, '--network'), ['none']);
|
||||
assert.deepEqual(values(args, '--user'), ['1001:1001']);
|
||||
assert.ok(args.includes('--read-only'));
|
||||
assert.deepEqual(values(args, '--cap-drop'), ['ALL']);
|
||||
assert.deepEqual(values(args, '--security-opt'), ['no-new-privileges']);
|
||||
assert.throws(() => grokConsumerDockerArgs({ ...paths, command, download: true, temporaryExecutable: true }), /offline/);
|
||||
assert.throws(() => grokConsumerDockerArgs({ ...paths, command, temporaryExecutable: 'true' }), /offline/);
|
||||
const source = readFileSync(new URL('../verify-grok-npm-install.mjs', import.meta.url), 'utf8');
|
||||
assert.ok(source.includes("temporarySizeMiB: 2048, temporaryExecutable: true"));
|
||||
});
|
||||
|
||||
test('the separately provisioned executable is exposed read-only to the offline probe', () => {
|
||||
const prerequisite = '/private/staging/native/grok';
|
||||
const args = grokConsumerDockerArgs({ ...paths, prerequisite, command: ['node', '/packages/probe.mjs', 'present'] });
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import test from 'node:test';
|
||||
import { retainRunnerQualificationPackages } from '../retain-runner-qualification-packages.mjs';
|
||||
|
||||
test('retention preserves public archives and runs the normal plugin prepack without modifying source', () => {
|
||||
const root = mkdtempSync(join(tmpdir(), 'qualification-retention-test-'));
|
||||
const sourceRevision = '1'.repeat(40), releaseVersion = '0.0.0-qualification.test';
|
||||
const put = (relative, content) => {
|
||||
const file = join(root, relative); mkdirSync(join(file, '..'), { recursive: true });
|
||||
writeFileSync(file, content); return file;
|
||||
};
|
||||
const json = (relative, value) => put(relative, JSON.stringify(value));
|
||||
try {
|
||||
json('server/dist/build-info.json', { commit: sourceRevision });
|
||||
const originalPlugin = JSON.parse(readFileSync(new URL('../../packages/plugins/sandbox-providers/daytona/package.json', import.meta.url)));
|
||||
const pluginManifest = json('packages/plugins/sandbox-providers/daytona/package.json', originalPlugin);
|
||||
put('packages/plugins/sandbox-providers/daytona/dist/index.js', 'export const retained = true;\n');
|
||||
json('packages/plugins/sdk/package.json', { name: '@paperclipai/plugin-sdk', version: '0.3.1' });
|
||||
put('scripts/generate-plugin-package-json.mjs', readFileSync(new URL('../generate-plugin-package-json.mjs', import.meta.url)));
|
||||
json('packages/paperclip-runner/package.json', { name: '@paperclipai/paperclip-runner', version: '0.0.0', private: true, files: ['dist'] });
|
||||
put('packages/paperclip-runner/dist/cli/eval-session.js', 'export const evaluation = true;\n');
|
||||
const publicArchives = ['@paperclipai/server', 'paperclipai', '@paperclipai/plugin-sdk'].map((name, i) => ({ name, file: put(`public-${i}.tgz`, `unchanged archive ${name}`) }));
|
||||
const output = join(root, 'retained');
|
||||
const result = retainRunnerQualificationPackages({ repo: root, output, sourceRevision, releaseVersion, publicArchives });
|
||||
assert.equal(result.archives.length, 5);
|
||||
for (const archive of result.archives) assert.equal(createHash('sha256').update(readFileSync(join(output, archive.file))).digest('hex'), archive.sha256);
|
||||
for (const archive of publicArchives) assert.deepEqual(readFileSync(join(output, archive.file.split('/').at(-1))), readFileSync(archive.file));
|
||||
const plugin = result.archives.find(a => a.name === '@paperclipai/plugin-daytona');
|
||||
const packedManifest = JSON.parse(execFileSync('tar', ['-xOf', join(output, plugin.file), 'package/package.json']));
|
||||
assert.equal(packedManifest.dependencies['@paperclipai/plugin-sdk'], releaseVersion);
|
||||
assert.equal(packedManifest.exports['.'].import, './dist/index.js');
|
||||
assert.equal(execFileSync('tar', ['-xOf', join(output, plugin.file), 'package/dist/index.js'], { encoding: 'utf8' }), 'export const retained = true;\n');
|
||||
assert.deepEqual(JSON.parse(readFileSync(pluginManifest)), originalPlugin);
|
||||
assert.equal(existsSync(join(root, 'packages/plugins/sandbox-providers/daytona/package.dev.json')), false);
|
||||
assert.throws(() => retainRunnerQualificationPackages({ repo: root, output, sourceRevision, releaseVersion, publicArchives }), /Never replace/);
|
||||
assert.throws(() => retainRunnerQualificationPackages({ repo: root, output: join(root, 'wrong-source'), sourceRevision: '2'.repeat(40), releaseVersion, publicArchives }));
|
||||
assert.equal(existsSync(join(root, 'wrong-source')), false);
|
||||
} finally { rmSync(root, { recursive: true, force: true }); }
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env node
|
||||
/** Run after the final server build and Linux daemon/provider-pack assembly. */
|
||||
import { createHash } from 'node:crypto';
|
||||
import { writeFile, realpath } from 'node:fs/promises';
|
||||
import { resolve } from 'node:path';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
const [directory, sourceRevision, ...extra] = process.argv.slice(2);
|
||||
if (!directory || !/^[a-f0-9]{40}$/.test(sourceRevision ?? '') || extra.length) throw new Error('Usage: node scripts/create-runner-remote-companion.mjs DIRECTORY SOURCE_SHA');
|
||||
const root = await realpath(directory);
|
||||
const { createRemotePiCompanionManifest } = await import(pathToFileURL(resolve('server/dist/services/native-runtime/remote-pi-companion.js')).href);
|
||||
const manifest = await createRemotePiCompanionManifest(root, sourceRevision);
|
||||
const bytes = JSON.stringify(manifest, null, 2) + '\n';
|
||||
await writeFile(resolve(root, 'companion.json'), bytes, { flag: 'wx', mode: 0o644 });
|
||||
console.log(JSON.stringify({ directory: root, sourceRevision, target: manifest.target, manifestSha256: createHash('sha256').update(bytes).digest('hex'), providerPackDigest: manifest.providerPackDigest, daemonSha256: manifest.daemonSha256 }));
|
||||
@@ -7,18 +7,23 @@ export const GROK_PUBLIC_INSTALL_LIFECYCLE = [
|
||||
'npm', 'rebuild', '--offline', '--ignore-scripts=false', '--dangerously-allow-all-scripts',
|
||||
];
|
||||
|
||||
export function grokConsumerDockerArgs({ assets, consumer, cache, command, uid, gid, download = false, prerequisite, temporarySizeMb = 256 }) {
|
||||
export function grokConsumerDockerArgs({ assets, consumer, cache, command, uid, gid, download = false, prerequisite, temporarySizeMiB = 256, temporaryExecutable = false }) {
|
||||
if (!Number.isSafeInteger(uid) || uid <= 0 || !Number.isSafeInteger(gid) || gid <= 0) {
|
||||
throw new Error('Public-install verification requires an unprivileged host user');
|
||||
}
|
||||
if (!Number.isSafeInteger(temporarySizeMb) || temporarySizeMb < 256 || temporarySizeMb > 2048) throw new Error('Invalid bounded public-install temporary size');
|
||||
if (!Number.isSafeInteger(temporarySizeMiB) || temporarySizeMiB < 256 || temporarySizeMiB > 2048) {
|
||||
throw new Error('Public-install temporary storage must be bounded between 256 and 2048 MiB');
|
||||
}
|
||||
if (typeof temporaryExecutable !== 'boolean' || (temporaryExecutable && download)) {
|
||||
throw new Error('Executable runtime snapshots require an offline verification sandbox');
|
||||
}
|
||||
return [
|
||||
'run', '--rm', '--platform', 'linux/amd64',
|
||||
'--user', `${uid}:${gid}`, '--read-only',
|
||||
'--cap-drop', 'ALL', '--security-opt', 'no-new-privileges',
|
||||
'--pids-limit', '256', '--memory', '3g',
|
||||
'--network', download ? 'bridge' : 'none',
|
||||
'--tmpfs', `/tmp:rw,nosuid,nodev,size=${temporarySizeMb}m,mode=1777`,
|
||||
'--tmpfs', `/tmp:rw,nosuid,nodev,${temporaryExecutable ? 'exec' : 'noexec'},size=${temporarySizeMiB}m,mode=1777`,
|
||||
'--env', 'HOME=/tmp', '--env', 'npm_config_cache=/cache',
|
||||
'--env', 'npm_config_nodedir=/usr/local',
|
||||
'--env', 'npm_config_audit=false', '--env', 'npm_config_fund=false',
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env node
|
||||
// Run in a credential-free public npm consumer after `paperclipai runtime setup pi`.
|
||||
// Use the server's normal packaged daemon resolver; never submit a prompt.
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdir, mkdtemp, readFile, realpath, rm } from 'node:fs/promises';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
|
||||
const [serverDirectory, ...extra] = process.argv.slice(2);
|
||||
assert.ok(serverDirectory && extra.length === 0, 'Usage: pi-public-install-probe.mjs INSTALLED_SERVER_ROOT');
|
||||
for (const key of Object.keys(process.env)) {
|
||||
assert.ok(!/(?:API_KEY|TOKEN|SECRET|PASSWORD)$/.test(key), `Unexpected credential variable: ${key}`);
|
||||
}
|
||||
assert.equal(process.env.PAPERCLIP_RUNNER_BINARY, undefined);
|
||||
assert.equal(process.env.NODE_OPTIONS, undefined);
|
||||
assert.equal(process.env.NODE_PATH, undefined);
|
||||
const server = await realpath(serverDirectory);
|
||||
assert.equal(JSON.parse(await readFile(join(server, 'package.json'), 'utf8')).name, '@paperclipai/server');
|
||||
const { resolvePaperclipRunnerBinary } = await import(pathToFileURL(join(server, 'dist/services/native-runtime/native-codex-runner.js')));
|
||||
const { createCapabilityRunnerdCodexTransport } = await import(pathToFileURL(join(server, 'dist/vendor/paperclip-runner/live/runnerd-codex-transport.js')));
|
||||
const { QUALIFIED_ACPX_PROFILES } = await import(pathToFileURL(join(server, 'dist/vendor/paperclip-runner/drivers/acpx/qualified-profiles.js')));
|
||||
assert.equal(QUALIFIED_ACPX_PROFILES.pi.agentProfileVersion, 19);
|
||||
assert.equal(QUALIFIED_ACPX_PROFILES.pi.commandDigest, 'sha256:b7647ebf97f802ca053ec3384c912bf0e8d18eba308d27397bb1d95a37220825');
|
||||
assert.equal(Object.hasOwn(QUALIFIED_ACPX_PROFILES.pi, 'qualificationModel'), false);
|
||||
assert.equal(Object.hasOwn(QUALIFIED_ACPX_PROFILES.pi, 'reportedModelId'), false);
|
||||
const daemon = resolvePaperclipRunnerBinary();
|
||||
assert.equal(await realpath(daemon), join(server, 'dist/vendor/paperclip-runner/bin/paperclip-runnerd'));
|
||||
const root = await mkdtemp(join(tmpdir(), 'pi-public-install-probe-'));
|
||||
const evidence = [];
|
||||
const started = performance.now();
|
||||
const watchdog = setTimeout(() => { console.error('Pi public-install probe exceeded its cleanup deadline'); process.exit(1); }, 85_000);
|
||||
let bundle;
|
||||
let failure;
|
||||
let settledMs;
|
||||
try {
|
||||
const workspace = join(root, 'workspace'); await mkdir(workspace);
|
||||
bundle = createCapabilityRunnerdCodexTransport({
|
||||
provider: 'acpx', acpxAgent: 'pi', piThinkingLevel: 'low', acpxPermissionMode: 'deny-all',
|
||||
runnerBinary: daemon, stateDirectory: join(root, 'state'),
|
||||
environment: { PATH: '/usr/bin:/bin', LANG: 'C.UTF-8' },
|
||||
onEvidence: value => evidence.push(value),
|
||||
});
|
||||
try {
|
||||
await bundle.transport.request('initialize', { clientInfo: { name: 'pi-public-install-verification', version: '1' } });
|
||||
await bundle.transport.request('thread/start', {
|
||||
cwd: workspace, model: 'openrouter/deepseek/deepseek-v4-flash-0731',
|
||||
baseInstructions: 'Credential-free admission probe. No prompt is submitted.',
|
||||
permissions: 'paperclip-runner-workspace-read-only', dynamicTools: [],
|
||||
});
|
||||
} catch (error) { failure = String(error); }
|
||||
settledMs = Math.round(performance.now() - started);
|
||||
await bundle.transport.close();
|
||||
assert.match(failure ?? '', /session\.open failed/);
|
||||
assert.match(failure, /retryable=false, classification=session_ensure_failed/);
|
||||
assert.doesNotMatch(failure, /timed out/);
|
||||
assert.ok(settledMs < 60_000, `Pi admission took ${settledMs} ms`);
|
||||
assert.ok(evidence.some(item => item.runnerExited === true && item.runnerExitCode === 0));
|
||||
for (const item of evidence) assert.deepEqual(item.childEnvironmentKeys, ['LANG', 'PATH']);
|
||||
const state = JSON.parse(await readFile(join(root, 'state/runner/acpx-provider-state.json'), 'utf8'));
|
||||
assert.equal(state.descriptor.agent, 'pi');
|
||||
assert.equal(state.descriptor.agentRuntimeVersion, '1.0.0');
|
||||
assert.equal(state.descriptor.piThinkingLevel, 'low');
|
||||
assert.equal(state.activeTurnId, null);
|
||||
assert.equal(state.identity, null);
|
||||
assert.equal(state.providerExitUnconfirmed, false);
|
||||
console.log(JSON.stringify({ schema: 'paperclip.pi.public-npm-install.v1', target: `${process.platform}-${process.arch}`, normalPackagedDaemon: true, exactPiProfile: 19, runtime: '1.0.0', credentials: 'none', promptCalls: 0, settledMs, cleanRunnerExit: true }));
|
||||
} finally {
|
||||
try { await bundle?.transport.close(); }
|
||||
finally { clearTimeout(watchdog); await rm(root, { recursive: true, force: true }); }
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// Retain normal build/pack outputs for installed Product E2E and Runner evals.
|
||||
// This runs only after the clean public installation and Pi admission pass.
|
||||
import assert from 'node:assert/strict';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { basename, join, resolve } from 'node:path';
|
||||
|
||||
export function retainRunnerQualificationPackages({ repo, output, sourceRevision, releaseVersion, publicArchives, env = process.env }) {
|
||||
assert.match(sourceRevision, /^[a-f0-9]{40}$/);
|
||||
assert.equal(JSON.parse(readFileSync(join(repo, 'server/dist/build-info.json'))).commit, sourceRevision);
|
||||
assert.ok(publicArchives.some(a => a.name === '@paperclipai/server'));
|
||||
assert.ok(publicArchives.some(a => a.name === 'paperclipai'));
|
||||
assert.ok(publicArchives.some(a => a.name === '@paperclipai/plugin-sdk'));
|
||||
assert.equal(new Set(publicArchives.map(a => a.name)).size, publicArchives.length);
|
||||
output = resolve(output);
|
||||
assert.ok(!existsSync(output), 'Never replace retained qualification packages');
|
||||
mkdirSync(output, { recursive: true });
|
||||
const staging = mkdtempSync(join(tmpdir(), 'paperclip-qualification-pack-'));
|
||||
const archives = [];
|
||||
const hash = file => createHash('sha256').update(readFileSync(file)).digest('hex');
|
||||
const retain = (name, file) => {
|
||||
const destination = join(output, basename(file));
|
||||
assert.ok(!existsSync(destination), 'Duplicate archive filename');
|
||||
cpSync(file, destination);
|
||||
archives.push({ name, file: basename(file), sha256: hash(destination) });
|
||||
};
|
||||
const pack = (directory, name, ignoreScripts) => {
|
||||
const previous = new Set(readdirSync(staging));
|
||||
execFileSync('npm', ['pack', ...(ignoreScripts ? ['--ignore-scripts'] : []), '--pack-destination', staging], {
|
||||
cwd: directory, env: { ...env, npm_config_cache: join(staging, 'npm-cache'), npm_config_ignore_scripts: String(ignoreScripts) }, maxBuffer: 32 * 1024 * 1024,
|
||||
});
|
||||
const files = readdirSync(staging).filter(file => file.endsWith('.tgz') && !previous.has(file));
|
||||
assert.equal(files.length, 1);
|
||||
retain(name, join(staging, files[0]));
|
||||
};
|
||||
try {
|
||||
for (const archive of publicArchives) retain(archive.name, archive.file);
|
||||
// Match the plugin's normal release prepack. Its source manifest omits
|
||||
// the SDK dependency; the hook adds the publishable SDK version.
|
||||
const pluginRelative = 'packages/plugins/sandbox-providers/daytona';
|
||||
const plugin = join(staging, pluginRelative);
|
||||
mkdirSync(plugin, { recursive: true });
|
||||
cpSync(join(repo, pluginRelative, 'dist'), join(plugin, 'dist'), { recursive: true });
|
||||
const pluginManifest = JSON.parse(readFileSync(join(repo, pluginRelative, 'package.json')));
|
||||
writeFileSync(join(plugin, 'package.json'), JSON.stringify({ ...pluginManifest, version: releaseVersion }));
|
||||
mkdirSync(join(staging, 'scripts'));
|
||||
cpSync(join(repo, 'scripts/generate-plugin-package-json.mjs'), join(staging, 'scripts/generate-plugin-package-json.mjs'));
|
||||
mkdirSync(join(staging, 'packages/plugins/sdk'), { recursive: true });
|
||||
const sdk = JSON.parse(readFileSync(join(repo, 'packages/plugins/sdk/package.json')));
|
||||
writeFileSync(join(staging, 'packages/plugins/sdk/package.json'), JSON.stringify({ ...sdk, version: releaseVersion }));
|
||||
pack(plugin, pluginManifest.name, false);
|
||||
// The separate private eval SDK is already built by pnpm build. Packing
|
||||
// it does not substitute for the public server's packaged runner.
|
||||
const runner = join(staging, 'runner');
|
||||
mkdirSync(runner);
|
||||
const runnerManifest = JSON.parse(readFileSync(join(repo, 'packages/paperclip-runner/package.json')));
|
||||
for (const file of runnerManifest.files) {
|
||||
const input = join(repo, 'packages/paperclip-runner', file);
|
||||
if (existsSync(input)) cpSync(input, join(runner, file), { recursive: true });
|
||||
}
|
||||
assert.ok(existsSync(join(runner, 'dist/cli/eval-session.js')));
|
||||
writeFileSync(join(runner, 'package.json'), JSON.stringify(runnerManifest));
|
||||
pack(runner, runnerManifest.name, true);
|
||||
assert.equal(new Set(archives.map(a => a.name)).size, archives.length);
|
||||
const receipt = { schema: 'paperclip.runner.qualification-packages/v1', sourceRevision, releaseVersion, publicInstallationVerified: true, piAdmissionVerified: true, providerCalls: 0, archives };
|
||||
writeFileSync(join(output, 'manifest.json'), JSON.stringify(receipt, null, 2) + '\n');
|
||||
return receipt;
|
||||
} finally {
|
||||
rmSync(staging, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import { basename, dirname, join, resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { materializePublishManifest, prepareBundledPackage } from './prepare-bundled-package.mjs';
|
||||
import { GROK_PUBLIC_INSTALL_IMAGE, GROK_PUBLIC_INSTALL_LIFECYCLE, grokConsumerDockerArgs } from './grok-public-install-sandbox.mjs';
|
||||
import { retainRunnerQualificationPackages } from './retain-runner-qualification-packages.mjs';
|
||||
const repo = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
||||
assert.equal(process.platform, 'linux', 'Run this verification on disposable EC2 Linux, not a developer host');
|
||||
const root = mkdtempSync(join(tmpdir(), 'paperclip-grok-public-install-'));
|
||||
@@ -30,6 +31,7 @@ try {
|
||||
}
|
||||
}
|
||||
visit('@paperclipai/server');
|
||||
visit('paperclipai');
|
||||
// Match release.sh's unified versioning in temporary staging directories.
|
||||
// Source manifests remain untouched, including independently versioned SDKs.
|
||||
run(process.execPath, [join(repo, 'scripts/build-standalone-public-packages.mjs')], repo);
|
||||
@@ -115,6 +117,29 @@ try {
|
||||
run(process.execPath, [join(repo, 'packages/paperclip-runner/scripts/provision-grok.mjs'), prerequisite]);
|
||||
isolated(['node', '/packages/probe.mjs', 'present'], { prerequisite });
|
||||
console.log(JSON.stringify({ schema: 'paperclip.grok.public-npm-install.v1', sourceRevision, releaseVersion, lifecycleScriptsEnabled: true, lifecycleSentinelVerified: true, lifecycleNetwork: 'none', consumerImage: GROK_PUBLIC_INSTALL_IMAGE, consumerUid, consumerLockPreserved: true, cleanNpmInstall: true, packageCount: needed.size, builtinLauncherPresent: true, separateGrokPackage: false, npmProvisionedBinary: false, missingPrerequisiteRejected: true, provisionedBinaryVerified: true, commandLeaseVerified: true, providerCalls: 0 }));
|
||||
// Exercise Pi's public CLI and installed server, never a private workspace
|
||||
// package or binary override. Public dependency downloads are explicit and
|
||||
// isolated; the actual admission probe runs without a network or credentials.
|
||||
const piProbe = join(assets, 'pi-public-install-probe.mjs');
|
||||
cpSync(join(repo, 'scripts/pi-public-install-probe.mjs'), piProbe); chmodSync(piProbe, 0o644);
|
||||
// Pi assembles a bundled runtime in scratch space before atomic publication.
|
||||
// Keep the existing sandbox and memory bound; only this download needs more
|
||||
// temporary capacity than the smaller offline lifecycle probes.
|
||||
const setup = isolated(['node', '/consumer/node_modules/paperclipai/dist/index.js', 'runtime', 'setup', 'pi'], { download: true, temporarySizeMiB: 2048 }).toString();
|
||||
const receipt = JSON.parse(setup.trim());
|
||||
assert.equal(receipt.status, 'installed_verified');
|
||||
assert.equal(receipt.target, 'linux-x64');
|
||||
assert.equal(readFileSync(join(consumer, 'package-lock.json'), 'utf8'), consumerLock, 'Pi setup must preserve the consumer dependency graph');
|
||||
// Verified launch leases also materialize the runtime in private scratch.
|
||||
// Docker defaults tmpfs to noexec. The offline admission probe must execute
|
||||
// its verified private snapshot while keeping lifecycle/download scratch noexec.
|
||||
console.log(isolated(['node', '/packages/pi-public-install-probe.mjs', '/consumer/node_modules/@paperclipai/server'], { temporarySizeMiB: 2048, temporaryExecutable: true }).toString().trim());
|
||||
if (process.env.PAPERCLIP_RUNNER_QUALIFICATION_PACKAGES_DIR) {
|
||||
const retained = retainRunnerQualificationPackages({ repo, output: process.env.PAPERCLIP_RUNNER_QUALIFICATION_PACKAGES_DIR, sourceRevision, releaseVersion, env,
|
||||
publicArchives: [...needed].map((name, index) => ({ name, file: tarballs[index] })),
|
||||
});
|
||||
console.log(JSON.stringify(retained));
|
||||
}
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
Reference in new issue
Block a user