mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-14 01:50:39 +09:00
[codex] Improve runtime and import reliability (#6549)
## Thinking Path > - Paperclip coordinates autonomous company work through local and hosted runtime surfaces. > - Local embedded Postgres and tenant import/export paths are foundational reliability pieces. > - A runtime failure in either path can stop agents or imports before useful work begins. > - The branch included remaining fixes for embedded native library bootstrap and async tenant import handling. > - This pull request groups those runtime/import reliability changes into one standalone PR. > - The benefit is a more robust local runtime and safer cloud tenant import behavior. ## What Changed - Prepared embedded Postgres native runtime before startup in CLI/server/test entrypoints. - Added embedded Postgres native bootstrap coverage. - Added async tenant import job handling and deferred validation coverage. - Kept the runtime/import changes based directly on current `origin/master` after related upstream PRs had already merged. ## Verification - `pnpm --filter @paperclipai/plugin-sdk build` - `NODE_ENV=test pnpm exec vitest run packages/db/src/embedded-postgres-native.test.ts server/src/__tests__/company-portability-routes.test.ts` ## Risks - Medium-low: this touches startup/import paths, but the branch is small and covered by targeted tests. - The embedded Postgres change depends on platform-specific native-library behavior, so CI and follow-up checks should still verify supported runners. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - OpenAI GPT-5 Codex via `codex_local`, tool-enabled coding session; exact context window not exposed by this runtime. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] If this change affects the UI, I have included before/after screenshots - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] I will address all Greptile and reviewer comments before requesting merge
This commit is contained in:
parent
e43b392a79
commit
ad6effa65c
10 changed files with 511 additions and 19 deletions
43
packages/db/src/embedded-postgres-native.test.ts
Normal file
43
packages/db/src/embedded-postgres-native.test.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { ensureLinuxSharedLibraryAliases } from "./embedded-postgres-native.js";
|
||||
|
||||
describe("embedded Postgres native runtime", () => {
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
afterEach(() => {
|
||||
for (const tempDir of tempDirs.splice(0)) {
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it.runIf(process.platform !== "win32")("creates soname aliases for bundled patch-level shared libraries", async () => {
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-embedded-pg-libs-"));
|
||||
tempDirs.push(tempDir);
|
||||
fs.writeFileSync(path.join(tempDir, "libicuuc.so.60.2"), "");
|
||||
fs.writeFileSync(path.join(tempDir, "libicui18n.so.60.2"), "");
|
||||
fs.writeFileSync(path.join(tempDir, "README.md"), "");
|
||||
|
||||
const created = await ensureLinuxSharedLibraryAliases(tempDir);
|
||||
|
||||
expect(created.map((file) => path.basename(file)).sort()).toEqual([
|
||||
"libicui18n.so.60",
|
||||
"libicuuc.so.60",
|
||||
]);
|
||||
expect(fs.readlinkSync(path.join(tempDir, "libicuuc.so.60"))).toBe("libicuuc.so.60.2");
|
||||
});
|
||||
|
||||
it.runIf(process.platform !== "win32")("is idempotent when aliases already exist", async () => {
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-embedded-pg-libs-"));
|
||||
tempDirs.push(tempDir);
|
||||
fs.writeFileSync(path.join(tempDir, "libicuuc.so.60.2"), "");
|
||||
|
||||
await ensureLinuxSharedLibraryAliases(tempDir);
|
||||
const second = await ensureLinuxSharedLibraryAliases(tempDir);
|
||||
|
||||
expect(second).toEqual([]);
|
||||
expect(fs.readlinkSync(path.join(tempDir, "libicuuc.so.60"))).toBe("libicuuc.so.60.2");
|
||||
});
|
||||
});
|
||||
85
packages/db/src/embedded-postgres-native.ts
Normal file
85
packages/db/src/embedded-postgres-native.ts
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import { promises as fs } from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import path from "node:path";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
function resolveNativePackageName(): string | null {
|
||||
if (process.platform !== "linux") return null;
|
||||
|
||||
switch (process.arch) {
|
||||
case "arm64":
|
||||
return "linux-arm64";
|
||||
case "arm":
|
||||
return "linux-arm";
|
||||
case "ia32":
|
||||
return "linux-ia32";
|
||||
case "ppc64":
|
||||
return "linux-ppc64";
|
||||
case "x64":
|
||||
return "linux-x64";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function pathExists(value: string): Promise<boolean> {
|
||||
try {
|
||||
await fs.stat(value);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function resolveEmbeddedPostgresPackageRoot(): string | null {
|
||||
try {
|
||||
const entry = require.resolve("embedded-postgres");
|
||||
return path.dirname(path.dirname(entry));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function prependPathEnv(name: string, value: string): void {
|
||||
const current = process.env[name] ?? "";
|
||||
const parts = current.split(path.delimiter).filter(Boolean);
|
||||
if (parts.includes(value)) return;
|
||||
process.env[name] = [value, ...parts].join(path.delimiter);
|
||||
}
|
||||
|
||||
export async function ensureLinuxSharedLibraryAliases(libDir: string): Promise<string[]> {
|
||||
const entries = await fs.readdir(libDir, { withFileTypes: true });
|
||||
const created: string[] = [];
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!entry.isFile()) continue;
|
||||
const match = entry.name.match(/^(lib.+\.so\.\d+)\.\d+(?:\.\d+)?$/);
|
||||
if (!match) continue;
|
||||
|
||||
const aliasName = match[1];
|
||||
const aliasPath = path.join(libDir, aliasName);
|
||||
try {
|
||||
await fs.symlink(entry.name, aliasPath);
|
||||
created.push(aliasPath);
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.ErrnoException).code === "EEXIST") continue;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return created;
|
||||
}
|
||||
|
||||
export async function prepareEmbeddedPostgresNativeRuntime(): Promise<void> {
|
||||
const nativePackageName = resolveNativePackageName();
|
||||
const packageRoot = resolveEmbeddedPostgresPackageRoot();
|
||||
if (!nativePackageName || !packageRoot) return;
|
||||
|
||||
const nativeRoot = path.resolve(packageRoot, "..", "@embedded-postgres", nativePackageName);
|
||||
const libDir = path.join(nativeRoot, "native", "lib");
|
||||
if (!(await pathExists(libDir))) return;
|
||||
|
||||
prependPathEnv("LD_LIBRARY_PATH", libDir);
|
||||
await ensureLinuxSharedLibraryAliases(libDir);
|
||||
}
|
||||
|
|
@ -30,6 +30,10 @@ export {
|
|||
createEmbeddedPostgresLogBuffer,
|
||||
formatEmbeddedPostgresError,
|
||||
} from "./embedded-postgres-error.js";
|
||||
export {
|
||||
ensureLinuxSharedLibraryAliases,
|
||||
prepareEmbeddedPostgresNativeRuntime,
|
||||
} from "./embedded-postgres-native.js";
|
||||
export { issueRelations } from "./schema/issue_relations.js";
|
||||
export { issueReferenceMentions } from "./schema/issue_reference_mentions.js";
|
||||
export * from "./schema/index.js";
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { createServer } from "node:net";
|
|||
import path from "node:path";
|
||||
import { ensurePostgresDatabase, getPostgresDataDirectory } from "./client.js";
|
||||
import { createEmbeddedPostgresLogBuffer, formatEmbeddedPostgresError } from "./embedded-postgres-error.js";
|
||||
import { prepareEmbeddedPostgresNativeRuntime } from "./embedded-postgres-native.js";
|
||||
import { resolveDatabaseTarget } from "./runtime-config.js";
|
||||
|
||||
type EmbeddedPostgresInstance = {
|
||||
|
|
@ -92,6 +93,7 @@ async function ensureEmbeddedPostgresConnection(
|
|||
preferredPort: number,
|
||||
): Promise<MigrationConnection> {
|
||||
const EmbeddedPostgres = await loadEmbeddedPostgresCtor();
|
||||
await prepareEmbeddedPostgresNativeRuntime();
|
||||
const selectedPort = await findAvailablePort(preferredPort);
|
||||
const postmasterPidFile = path.resolve(dataDir, "postmaster.pid");
|
||||
const pgVersionFile = path.resolve(dataDir, "PG_VERSION");
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import net from "node:net";
|
|||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { applyPendingMigrations, ensurePostgresDatabase } from "./client.js";
|
||||
import { prepareEmbeddedPostgresNativeRuntime } from "./embedded-postgres-native.js";
|
||||
|
||||
type EmbeddedPostgresInstance = {
|
||||
initialise(): Promise<void>;
|
||||
|
|
@ -48,6 +49,7 @@ function getReservedTestPorts(): Set<number> {
|
|||
|
||||
async function getEmbeddedPostgresCtor(): Promise<EmbeddedPostgresCtor> {
|
||||
const mod = await import("embedded-postgres");
|
||||
await prepareEmbeddedPostgresNativeRuntime();
|
||||
return mod.default as EmbeddedPostgresCtor;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue