feat: deliver module database ABI by manifest
CI / verify (pull_request) Successful in 1m0s
CI / infrastructure-smoke (pull_request) Successful in 36s
CI / migration-smoke (pull_request) Successful in 1m53s
CI / image-build (pull_request) Successful in 23s
CI / deploy-smoke (same-origin) (pull_request) Successful in 50s
CI / deploy-smoke (split) (pull_request) Successful in 1m30s

This commit is contained in:
ekklesia
2026-10-07 17:29:32 -05:00
parent a1cdcb3ad1
commit 49b4d4165d
60 changed files with 948 additions and 237 deletions
+14 -9
View File
@@ -2,8 +2,8 @@ import type { Socket } from "node:net";
import path from "node:path";
import { pathToFileURL } from "node:url";
import {
type AnyModuleRuntimeServices,
EMPTY_MODULE_SECRET_SOURCE,
type ModuleRuntimeServices,
type ModuleSecretSource,
} from "@ekklesia/contracts";
import {
@@ -27,6 +27,7 @@ import {
type DiscoveredModule,
type ModuleConfigurationSettings,
type ModuleEligibilityResolution,
type ModuleRuntimeServicesLookup,
resolveModuleEligibility,
type StartedModuleRuntime,
startDiscoveredModuleRuntime,
@@ -393,16 +394,13 @@ function bindServer(
* (`prepareStartupModuleInventory`, `./platform-permissions.js`).
* 2. Construct the one shared PostgreSQL pool (identity, authorization, and the module database
* facade each get a typed Drizzle view over it — see `./database.js`).
* 3. Construct the frozen `ModuleRuntimeServices` (currently just `database`) from that same pool,
* then the Core identity runtime, authorization store, and evaluator.
* 3. Construct the Core identity runtime, authorization store, and evaluator from that pool.
* 4. Read the stored module enablement configuration and resolve, from that exact same discovered
* inventory, which modules are eligible to run (`resolveModuleEligibility`).
* 5. Start the module runtime from the eligible subset (`startDiscoveredModuleRuntime`) — never
* rediscovering modules a second time — passing it the same services object every module's
* `start`/`stop` hook receives.
* rediscovering modules a second time — passing the manifest-selected service map.
* 6. Create the Hono application, mounting the started module runtime's published routes, publishing
* UI for that same eligible set, and passing the exact same services object to every module HTTP
* handler.
* UI for that same eligible set, and passing the same per-module service map to HTTP handlers.
* 7. Bind the Node HTTP server.
*
* Discovery and permission composition happen before any pool, Better Auth instance, module
@@ -459,12 +457,10 @@ export async function startApi(
const storageProvider = config.storage ? createS3StorageProvider(config.storage) : undefined;
const mailProvider = config.mail ? createSmtpMailProvider(config.mail) : undefined;
let services: ModuleRuntimeServices;
let runtime: ReturnType<typeof createIdentityRuntime>;
let authorizationStore: ReturnType<typeof createAuthorizationStore>;
let deviceStore: ReturnType<typeof createDeviceStore>;
try {
services = Object.freeze({ database: databaseHandle.module });
runtime = createIdentityRuntime({
config: config.identity,
db: databaseHandle.identity,
@@ -532,6 +528,15 @@ export async function startApi(
throw error;
}
const services: ModuleRuntimeServicesLookup = new Map<string, AnyModuleRuntimeServices>(
eligibility.eligibleModules.map((module) => [
module.manifest.id,
module.manifest.database?.abi === 1
? Object.freeze({ database: databaseHandle.moduleExecutor(module.manifest.id) })
: Object.freeze({ database: databaseHandle.module }),
]),
);
// One scoped facade per eligible module, over the exact buckets that module's own manifest
// declares. Built from the eligible set so a blocked module never receives storage at all.
const moduleStorages = createModuleStorageLookup(