Each module now runs in a real Deno Worker with per-module permission sandboxing. The I/O bridge relays Worker postMessage calls through the parent to CoreService gRPC, so modules can access store, files, and processes without direct network/filesystem access. - Worker bootstrap (worker-entry.ts): sets up RPC bridge, dynamically imports module, calls init(core) with typed I/O object - ModuleRegistry rewritten: creates Workers with Deno permission constructor, handles LOADING → RUNNING → STOPPED lifecycle - Structured ModulePermissions (read/write/net/run) replaces flat string array in Go→Deno JSON-RPC - I/O bridge: Worker postMessage → parent dispatchRPC → CoreClient gRPC → response relayed back to Worker - Test module proves end-to-end: Worker calls core.storeSet() → Go verifies value in store 40 unit tests + 3 integration tests (Tier 1 boot + Tier 2 bidir + Tier 3 Worker). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
106 lines
3 KiB
TypeScript
106 lines
3 KiB
TypeScript
// CoreDeno Runtime Entry Point
|
|
// Connects to CoreGO via gRPC over Unix socket.
|
|
// Implements DenoService for module lifecycle management.
|
|
|
|
// Must be first import — patches http2 before @grpc/grpc-js loads.
|
|
import "./polyfill.ts";
|
|
|
|
import { createCoreClient, type CoreClient } from "./client.ts";
|
|
import { startDenoServer, type DenoServer } from "./server.ts";
|
|
import { ModuleRegistry } from "./modules.ts";
|
|
|
|
// Read required environment variables
|
|
const coreSocket = Deno.env.get("CORE_SOCKET");
|
|
if (!coreSocket) {
|
|
console.error("FATAL: CORE_SOCKET environment variable not set");
|
|
Deno.exit(1);
|
|
}
|
|
|
|
const denoSocket = Deno.env.get("DENO_SOCKET");
|
|
if (!denoSocket) {
|
|
console.error("FATAL: DENO_SOCKET environment variable not set");
|
|
Deno.exit(1);
|
|
}
|
|
|
|
console.error(`CoreDeno: CORE_SOCKET=${coreSocket}`);
|
|
console.error(`CoreDeno: DENO_SOCKET=${denoSocket}`);
|
|
|
|
// 1. Create module registry
|
|
const registry = new ModuleRegistry();
|
|
|
|
// 2. Start DenoService server (Go calls us here via JSON-RPC over Unix socket)
|
|
let denoServer: DenoServer;
|
|
try {
|
|
denoServer = await startDenoServer(denoSocket, registry);
|
|
console.error("CoreDeno: DenoService server started");
|
|
} catch (err) {
|
|
console.error(`FATAL: failed to start DenoService server: ${err}`);
|
|
Deno.exit(1);
|
|
}
|
|
|
|
// 3. Connect to CoreService (we call Go here) with retry
|
|
let coreClient: CoreClient;
|
|
{
|
|
coreClient = createCoreClient(coreSocket);
|
|
const maxRetries = 20;
|
|
let connected = false;
|
|
let lastErr: unknown;
|
|
for (let i = 0; i < maxRetries; i++) {
|
|
try {
|
|
const timeoutCall = <T>(p: Promise<T>): Promise<T> =>
|
|
Promise.race([
|
|
p,
|
|
new Promise<T>((_, reject) =>
|
|
setTimeout(() => reject(new Error("call timeout")), 2000),
|
|
),
|
|
]);
|
|
await timeoutCall(
|
|
coreClient.storeSet("_coredeno", "status", "connected"),
|
|
);
|
|
const resp = await timeoutCall(
|
|
coreClient.storeGet("_coredeno", "status"),
|
|
);
|
|
if (resp.found && resp.value === "connected") {
|
|
connected = true;
|
|
break;
|
|
}
|
|
} catch (err) {
|
|
lastErr = err;
|
|
if (i < 3 || i === 9 || i === 19) {
|
|
console.error(`CoreDeno: retry ${i}: ${err}`);
|
|
}
|
|
}
|
|
await new Promise((r) => setTimeout(r, 250));
|
|
}
|
|
if (!connected) {
|
|
console.error(
|
|
`FATAL: failed to connect to CoreService after retries, last error: ${lastErr}`,
|
|
);
|
|
denoServer.close();
|
|
Deno.exit(1);
|
|
}
|
|
console.error("CoreDeno: CoreService client connected");
|
|
}
|
|
|
|
// 4. Inject CoreClient into registry for I/O bridge
|
|
registry.setCoreClient(coreClient);
|
|
|
|
// 5. Signal readiness
|
|
console.error("CoreDeno: ready");
|
|
|
|
// 6. Keep alive until SIGTERM
|
|
const ac = new AbortController();
|
|
Deno.addSignalListener("SIGTERM", () => {
|
|
console.error("CoreDeno: shutting down");
|
|
ac.abort();
|
|
});
|
|
|
|
try {
|
|
await new Promise((_resolve, reject) => {
|
|
ac.signal.addEventListener("abort", () => reject(new Error("shutdown")));
|
|
});
|
|
} catch {
|
|
// Clean shutdown
|
|
coreClient.close();
|
|
denoServer.close();
|
|
}
|