Startup Sequence

Focus

Gateway Process Initialization

The startup sequence describes how the Gateway process boots from cold start to fully operational. The runBootOnce() function orchestrates every phase — from reading BOOT.md instructions to initializing services, restoring sessions, and broadcasting readiness.

Entry Point runBootOnce() in boot.ts
Boot Marker BOOT_FILENAME = "BOOT.md"
Result Type BootRunResult — "ran" | "skipped" | "failed"
Context Size GatewayRequestContext with 25+ fields

Table of Contents

Overview

When the Gateway process starts, runBootOnce() is invoked exactly once. It reads the BOOT.md instruction file from the project root, parses its directives, and executes each phase sequentially. The function returns a BootRunResult indicating whether the boot ran successfully ("ran"), was skipped because a previous boot already completed ("skipped"), or encountered an error ("failed").

🔒 Idempotency guarantee: The boot sequence uses a lock file to ensure that concurrent process starts never execute runBootOnce() in parallel. If a second process detects the lock, it receives "skipped" and waits for the first to complete.

Boot Phases Flowchart

The following flowchart illustrates the high-level phases executed by runBootOnce(). Each phase must complete before the next begins.

flowchart TD
    A[Process Start] --> B{Lock acquired?}
    B -- No --> S[Return "skipped"]
    B -- Yes --> C[Read BOOT.md]
    C --> D{BOOT.md exists?}
    D -- No --> E[Use default config]
    D -- Yes --> F[Parse boot directives]
    F --> E
    E --> G[Build GatewayRequestContext]
    G --> H[Initialize CronService]
    H --> I[Initialize NodeRegistry]
    I --> J[Register broadcast functions]
    J --> K[Restore SessionMappingSnapshot]
    K --> L{Restore success?}
    L -- No --> M[Log warning, fresh state]
    L -- Yes --> N[Sessions restored]
    M --> O[Run post-boot hooks]
    N --> O
    O --> P[Release lock]
    P --> Q[Return "ran"]

BOOT.md Instruction File

The Gateway looks for a file named BOOT.md (defined by the constant BOOT_FILENAME) in the project root directory. This Markdown file contains structured directives that configure boot behavior, including environment overrides, feature flags, and service endpoints.

// Constant defining the boot instruction filename
const BOOT_FILENAME = "BOOT.md";

interface BootDirective {
  key: string;
  value: string;
  section: "env" | "flags" | "endpoints" | "hooks";
}

async function readBootFile(rootDir: string): Promise<BootDirective[]> {
  const bootPath = path.join(rootDir, BOOT_FILENAME);
  if (!fs.existsSync(bootPath)) {
    return []; // No BOOT.md — use defaults
  }
  const raw = await fs.promises.readFile(bootPath, "utf-8");
  return parseBootDirectives(raw);
}

function parseBootDirectives(content: string): BootDirective[] {
  const directives: BootDirective[] = [];
  let currentSection: BootDirective["section"] = "env";
  for (const line of content.split("\n")) {
    if (line.startsWith("## ")) {
      currentSection = line.slice(3).trim().toLowerCase() as BootDirective["section"];
      continue;
    }
    const match = line.match(/^- \*\*(\w+)\*\*:\s*(.+)$/);
    if (match) {
      directives.push({ key: match[1], value: match[2], section: currentSection });
    }
  }
  return directives;
}
⚠ Warning: If BOOT.md contains syntax errors, the parser silently ignores malformed lines rather than failing the entire boot. Check the Gateway logs for boot:directive-skip entries to diagnose missing configuration.

The runBootOnce() Function

Acquire Boot Lock

Before any work begins, runBootOnce() attempts to acquire an exclusive lock. This prevents race conditions when multiple Gateway instances start simultaneously (e.g., in a cluster deployment).

type BootRunResult = {
  status: "ran" | "skipped" | "failed";
  durationMs: number;
  error?: string;
};

async function runBootOnce(config: GatewayConfig): Promise<BootRunResult> {
  const startTime = Date.now();
  const lock = await acquireBootLock();
  if (!lock.acquired) {
    return { status: "skipped", durationMs: Date.now() - startTime };
  }
  try {
    // Phase 1: Read BOOT.md
    const directives = await readBootFile(config.rootDir);
    // Phase 2: Build context
    const ctx = buildGatewayRequestContext(config, directives);
    // Phase 3: Initialize services
    await initializeServices(ctx);
    // Phase 4: Restore sessions
    await restoreSessions(ctx);
    // Phase 5: Post-boot hooks
    await runPostBootHooks(ctx, directives);
    return { status: "ran", durationMs: Date.now() - startTime };
  } catch (err) {
    return { status: "failed", durationMs: Date.now() - startTime, error: String(err) };
  } finally {
    lock.release();
  }
}

Build GatewayRequestContext

The GatewayRequestContext is the central state object passed to every subsystem during boot. It contains 25+ fields covering authentication, routing, storage, cron scheduling, and WebSocket management.

interface GatewayRequestContext {
  // Identity & auth
  gatewayId: string;
  authConfig: AuthConfig;
  rateLimiter: RateLimiter;
  credentials: CredentialStore;

  // Networking
  wsServer: WebSocketServer;
  httpRouter: HttpRouter;
  broadcastFn: BroadcastFunction;

  // Services
  cronService: CronService;
  nodeRegistry: NodeRegistry;
  healthCache: HealthCache;

  // Storage
  sessionStore: SessionStore;
  snapshotManager: SnapshotManager;
  bootLog: BootLog;

  // Configuration
  config: GatewayConfig;
  featureFlags: FeatureFlags;
  environment: string;

  // Telemetry
  logger: Logger;
  metrics: MetricsCollector;
  traceContext: TraceContext;

  // Runtime
  startedAtMs: number;
  bootResult: BootRunResult;
  shutdownSignal: AbortSignal;
}

function buildGatewayRequestContext(
  config: GatewayConfig,
  directives: BootDirective[],
): GatewayRequestContext {
  const flags = resolveFeatureFlags(config.defaults, directives);
  return {
    gatewayId: generateGatewayId(),
    authConfig: buildAuthConfig(directives),
    rateLimiter: new SlidingWindowRateLimiter(config.rateLimit),
    credentials: new FileCredentialStore(config.credentialsPath),
    wsServer: new WebSocketServer({ port: config.wsPort }),
    httpRouter: createHttpRouter(config),
    broadcastFn: createBroadcastFunction(),
    cronService: new CronService(),
    nodeRegistry: new NodeRegistry(),
    healthCache: new HealthCache(),
    sessionStore: new SessionStore(config.sessionDir),
    snapshotManager: new SnapshotManager(config.snapshotDir),
    bootLog: new BootLog(),
    config,
    featureFlags: flags,
    environment: config.env ?? "production",
    logger: createLogger(config.logLevel),
    metrics: new MetricsCollector(config.metricsEndpoint),
    traceContext: new TraceContext(),
    startedAtMs: Date.now(),
    bootResult: { status: "ran", durationMs: 0 },
    shutdownSignal: new AbortController().signal,
  };
}

Initialize Core Services

With the context built, three critical services are initialized in order: the CronService for scheduled tasks, the NodeRegistry for tracking connected clients, and the broadcast subsystem for pushing updates to all connected nodes.

Restore Session State

The final boot phase attempts to restore previously persisted session data from a SessionMappingSnapshot. If the snapshot is missing or corrupt, the Gateway logs a warning and starts with a clean session state.

Startup Timeline

This sequence diagram shows the temporal ordering of startup events, including inter-service communication.

sequenceDiagram
    participant P as Process
    participant BL as Boot Lock
    participant BF as BOOT.md
    participant CTX as GatewayRequestContext
    participant CS as CronService
    participant NR as NodeRegistry
    participant BC as Broadcast
    participant SS as SessionStore
    participant SM as SnapshotManager

    P->>BL: acquireBootLock()
    BL-->>P: {acquired: true}
    P->>BF: readBootFile(rootDir)
    BF-->>P: BootDirective[]
    P->>CTX: buildGatewayRequestContext(config, directives)
    CTX-->>P: context (25+ fields)

    P->>CS: cronService.start(ctx)
    CS-->>P: ready
    P->>NR: nodeRegistry.initialize(ctx)
    NR-->>P: ready
    P->>BC: registerBroadcastFunctions(ctx)
    BC-->>P: broadcastFn bound

    P->>SM: snapshotManager.loadLatest()
    alt snapshot found
        SM-->>P: SessionMappingSnapshot
        P->>SS: sessionStore.restore(snapshot)
        SS-->>P: sessions restored
    else no snapshot
        SM-->>P: null
        P->>P: log warning, fresh state
    end

    P->>BL: lock.release()
    Note over P: Return BootRunResult "ran"

BootRunResult Statuses

StatusMeaningTypical Cause
"ran"Boot completed successfullyNormal cold start
"skipped"Another instance already bootedCluster race condition; lock not acquired
"failed"Boot encountered a fatal errorMissing config, corrupt snapshot, port conflict
✅ Best practice: Always check bootResult.status before accepting traffic. A Gateway that returned "failed" should not be added to the load balancer — use the health check endpoint to gate readiness.

Session State Restoration

The SessionMappingSnapshot is a serialized representation of all active sessions at the time of the last graceful shutdown. It maps session IDs to their associated client metadata, connection parameters, and in-flight request state.

interface SessionMappingSnapshot {
  version: number;
  createdAtMs: number;
  sessions: Map<string, SessionEntry>;
}

interface SessionEntry {
  sessionId: string;
  clientId: string;
  connectedAtMs: number;
  lastActiveMs: number;
  authMethod: string;
  capabilities: string[];
  pendingRequests: number;
}

async function restoreSessions(ctx: GatewayRequestContext): Promise<void> {
  const snapshot = await ctx.snapshotManager.loadLatest();
  if (!snapshot) {
    ctx.logger.warn("No session snapshot found — starting with empty state");
    return;
  }
  ctx.logger.info(`Restoring ${snapshot.sessions.size} sessions from snapshot v${snapshot.version}`);
  for (const [id, entry] of snapshot.sessions) {
    // Skip sessions older than the configured max age
    if (Date.now() - entry.lastActiveMs > ctx.config.maxSessionAgeMs) {
      ctx.logger.debug(`Skipping stale session ${id}`);
      continue;
    }
    ctx.sessionStore.put(id, entry);
  }
}

CronService Initialization

The CronService is started during boot to schedule periodic maintenance tasks such as health checks, session pruning, and metrics flushing. Each task is registered with a cron expression and a callback.

TaskScheduleDescription
Health checkEvery 30sUpdates healthCache with current status
Session pruneEvery 5mRemoves expired sessions from store
Metrics flushEvery 60sPushes collected metrics to endpoint
Snapshot persistEvery 10mSaves SessionMappingSnapshot to disk
Node heartbeatEvery 15sPings registered nodes for liveness

Error Handling

ErrorPhaseRecovery
Lock timeoutLock acquisitionReturn "skipped", let other instance handle boot
BOOT.md parse errorRead boot fileSkip malformed lines, log warnings
Port already in useContext buildReturn "failed" with EADDRINUSE error
Corrupt snapshotSession restoreLog error, start with empty state
CronService crashService initReturn "failed", escalate to process supervisor
🚨 Critical: If runBootOnce() returns "failed", the process should exit with a non-zero code so the orchestrator (systemd, Kubernetes, etc.) can restart it. Never silently continue with a failed boot.

Configuration Reference

ParameterTypeDefaultDescription
boot.lockTimeoutMsnumber10000Max time to wait for boot lock
boot.maxSessionAgeMsnumber86400000Max session age for snapshot restore (24h)
boot.snapshotDirstring"./snapshots"Directory for session snapshots
boot.hooksEnabledbooleantrueWhether to run post-boot hooks
cron.healthIntervalSecnumber30Health check interval

Directory Map

Source files for this topic
src/gateway/
├── boot.ts                    ← runBootOnce(), BootRunResult, BOOT_FILENAME
├── boot-lock.ts               ← acquireBootLock(), lock file management
├── context.ts                 ← buildGatewayRequestContext(), GatewayRequestContext
├── cron.ts                    ← CronService initialization and task registry
├── node-registry.ts           ← NodeRegistry for connected client tracking
├── broadcast.ts               ← registerBroadcastFunctions(), BroadcastFunction
├── session-store.ts           ← SessionStore, SessionEntry
└── snapshot/
    ├── manager.ts             ← SnapshotManager, loadLatest(), persist()
    └── types.ts               ← SessionMappingSnapshot interface