feat: transition module packages to database ABI v1
CI / verify (pull_request) Successful in 1m1s
CI / infrastructure-smoke (pull_request) Successful in 36s
CI / migration-smoke (pull_request) Successful in 1m54s
CI / image-build (pull_request) Successful in 31s
CI / deploy-smoke (same-origin) (pull_request) Successful in 52s
CI / deploy-smoke (split) (pull_request) Successful in 1m32s

This commit is contained in:
ekklesia
2026-10-07 18:00:04 -05:00
parent 20ad1ae208
commit 70854d234c
20 changed files with 274 additions and 115 deletions
+32 -18
View File
@@ -45,6 +45,7 @@ import { discoverModuleSchemas, ModuleSchemaDiscoveryError } from "./schema-disc
export type ModulePackageVerificationErrorCode =
| ModulePackageArchiveErrorCode
| "MODULE_PACKAGE_DATABASE_RUNTIME_INCOMPATIBLE"
| "MODULE_PACKAGE_DATABASE_RUNTIME_UNEXPECTED"
| "MODULE_PACKAGE_DIGEST_MISMATCH"
| "MODULE_PACKAGE_ENTRYPOINT_LOAD_FAILED"
| "MODULE_PACKAGE_EXTRACTION_FAILED"
@@ -67,6 +68,8 @@ export type ModulePackageVerificationErrorCode =
const messages: Readonly<Record<ModulePackageVerificationErrorCode, string>> = Object.freeze({
MODULE_PACKAGE_DATABASE_RUNTIME_INCOMPATIBLE:
"The package's bundled database runtime does not match this platform's (a transitional constraint).",
MODULE_PACKAGE_DATABASE_RUNTIME_UNEXPECTED:
"A Module Database ABI package must not carry a transitional database runtime.",
MODULE_PACKAGE_DIGEST_MISMATCH: "The archive does not match the SHA-256 it was given.",
MODULE_PACKAGE_ENTRYPOINT_LOAD_FAILED:
"A backend entrypoint could not be loaded from the package alone.",
@@ -275,9 +278,15 @@ function checkCompatibility(
if (sdk !== undefined && !isVersionCompatible(sdk, manifest.compatibility.moduleSdk)) {
fail("MODULE_PACKAGE_SDK_INCOMPATIBLE");
}
// Transitional (ADR 0049): today a module's Drizzle copy must be interchangeable with the
// platform's, because Core queries the module's tables with its own Drizzle instance. This is an
// implementation constraint pending a Module Database ABI, not that ABI.
if (manifest.database?.abi === 1) {
if (metadata.transitional?.databaseRuntime !== undefined) {
fail("MODULE_PACKAGE_DATABASE_RUNTIME_UNEXPECTED");
}
return;
}
// Transitional legacy-v8 path (ADR 0049): Core queries the module's Drizzle tables with its own
// Drizzle instance, so a recorded module runtime must match. ABI-v1 packages return above: their
// bundled ORM is a private implementation detail and is never compared with Core's.
for (const [runtime, version] of Object.entries(metadata.transitional?.databaseRuntime ?? {})) {
if (platform.databaseRuntime[runtime] !== version) {
fail("MODULE_PACKAGE_DATABASE_RUNTIME_INCOMPATIBLE");
@@ -314,8 +323,7 @@ function probeEntrypointPath(): string {
/** Loads every backend entrypoint in a short-lived child with an empty environment, and reports
* only whether each loaded and exported a definition naming this module. */
async function loadEntrypoints(
moduleId: string,
version: string,
manifest: Pick<ModuleManifest, "database" | "id" | "version">,
entrypoints: Readonly<Record<string, string>>,
timeoutMs: number,
): Promise<void> {
@@ -332,13 +340,24 @@ async function loadEntrypoints(
}
};
try {
child = fork(probeEntrypointPath(), [JSON.stringify({ entrypoints, moduleId, version })], {
cwd: tmpdir(),
env: {},
execArgv: [],
serialization: "json",
stdio: ["ignore", "ignore", "ignore", "ipc"],
});
child = fork(
probeEntrypointPath(),
[
JSON.stringify({
...(manifest.database?.abi === 1 ? { databaseAbi: 1 } : {}),
entrypoints,
moduleId: manifest.id,
version: manifest.version,
}),
],
{
cwd: tmpdir(),
env: {},
execArgv: [],
serialization: "json",
stdio: ["ignore", "ignore", "ignore", "ipc"],
},
);
} catch {
finish("failed");
return;
@@ -437,12 +456,7 @@ export async function verifyModulePackage(
// Executed only now, and first in isolation: a schema is imported in this process below only
// after the probe proved it loads on its own.
await loadEntrypoints(
metadata.module.id,
metadata.module.version,
entrypoints,
options.loadTimeoutMs ?? 30_000,
);
await loadEntrypoints(manifest, entrypoints, options.loadTimeoutMs ?? 30_000);
try {
await discoverModuleSchemas(discoveryOptions);
} catch (error) {