All files / omnium-gatherum/src/vats controller-vat.ts

0% Statements 0/31
0% Branches 0/8
0% Functions 0/9
0% Lines 0/30

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198                                                                                                                                                                                                                                                                                                                                                                                                           
import { E } from '@endo/eventual-send';
import type { ERef } from '@endo/eventual-send';
import { makePromiseKit } from '@endo/promise-kit';
import type { PromiseKit } from '@endo/promise-kit';
import { makeDefaultExo } from '@metamask/kernel-utils/exo';
import { Logger } from '@metamask/logger';
import type {
  Baggage,
  ClusterConfig,
  SubclusterLaunchResult,
} from '@metamask/ocap-kernel';
 
import { makeBaggageStorageAdapter } from './storage/baggage-adapter.ts';
import { CapletController } from '../controllers/caplet/caplet-controller.ts';
import type { CapletControllerFacet } from '../controllers/caplet/index.ts';
import type { StorageAdapter } from '../controllers/storage/types.ts';
 
/**
 * Vat powers provided to the controller vat.
 */
type VatPowers = {
  logger?: Logger;
};
 
/**
 * Kernel facet interface for system vat operations.
 */
type KernelFacet = {
  launchSubcluster: (config: ClusterConfig) => Promise<SubclusterLaunchResult>;
  terminateSubcluster: (subclusterId: string) => Promise<void>;
  getPresence: (kref: string, iface?: string) => Promise<unknown>;
};
 
/**
 * Services provided to the controller vat.
 */
type BootstrapServices = {
  kernelFacet?: KernelFacet;
};
 
/**
 * Initialize the CapletController with the given kernelFacet.
 *
 * @param options - Initialization options.
 * @param options.kernelFacet - The kernel facet for kernel operations.
 * @param options.storageAdapter - The storage adapter for persistence.
 * @param options.logger - Logger for the vat.
 * @param options.resolve - Function to resolve the caplet facet promise.
 * @param options.reject - Function to reject the caplet facet promise.
 */
async function initializeCapletController(options: {
  kernelFacet: KernelFacet;
  storageAdapter: StorageAdapter;
  logger: Logger;
  resolve: (facet: CapletControllerFacet) => void;
  reject: (error: unknown) => void;
}): Promise<void> {
  const { kernelFacet, storageAdapter, logger, resolve, reject } = options;
 
  try {
    const capletFacet = await CapletController.make(
      { logger: logger.subLogger({ tags: ['caplet'] }) },
      {
        adapter: storageAdapter,
        launchSubcluster: async (
          config: ClusterConfig,
        ): Promise<SubclusterLaunchResult> =>
          E(kernelFacet).launchSubcluster(config),
        terminateSubcluster: async (subclusterId: string): Promise<void> =>
          E(kernelFacet).terminateSubcluster(subclusterId),
        getVatRoot: async (krefString: string): Promise<unknown> =>
          E(kernelFacet).getPresence(krefString, 'vatRoot'),
      },
    );
    resolve(capletFacet);
  } catch (error) {
    reject(error);
    throw error;
  }
}
 
/**
 * Controller vat for Omnium system services.
 * Hosts controllers with baggage-backed persistence.
 *
 * Methods are exposed directly on root (not nested) for queueMessage access.
 *
 * @param vatPowers - Special powers granted to this vat.
 * @param _parameters - Initialization parameters (unused).
 * @param baggage - Root of vat's persistent state.
 * @returns The root object for the new vat.
 */
export function buildRootObject(
  vatPowers: VatPowers,
  _parameters: unknown,
  baggage: Baggage,
): object {
  const logger = (vatPowers.logger ?? new Logger()).subLogger({
    tags: ['controller-vat'],
  });
 
  // Create baggage-backed storage adapter
  const storageAdapter = makeBaggageStorageAdapter(baggage);
 
  // Promise kit for the caplet controller facet
  const {
    promise: capletFacetP,
    resolve: resolveCapletFacet,
    reject: rejectCapletFacet,
  }: PromiseKit<CapletControllerFacet> = makePromiseKit<CapletControllerFacet>();
 
  // Restore kernelFacet from baggage if available (for resuscitation)
  const persistedKernelFacet: KernelFacet | undefined = baggage.has(
    'kernelFacet',
  )
    ? (baggage.get('kernelFacet') as KernelFacet)
    : undefined;
 
  // If we have a persisted kernelFacet, initialize the controller immediately
  if (persistedKernelFacet) {
    logger.info('Restoring controller from baggage');
    // Fire-and-forget: the promise kit will be resolved/rejected when initialization completes
    initializeCapletController({
      kernelFacet: persistedKernelFacet,
      storageAdapter,
      logger,
      resolve: resolveCapletFacet,
      reject: rejectCapletFacet,
    }).catch((error) => {
      logger.error('Failed to restore controller from baggage:', error);
      rejectCapletFacet(error);
    });
  }
 
  // Define delegating methods for caplet operations
  const capletMethods = defineMethods(capletFacetP, [
    'install',
    'uninstall',
    'list',
    'get',
    'getCapletRoot',
  ]);
 
  return makeDefaultExo('omnium-controllers', {
    /**
     * Initialize the controller vat with services from the kernel.
     *
     * @param _vats - Other vats in this subcluster (unused).
     * @param services - Services provided by the kernel.
     */
    async bootstrap(
      _vats: unknown,
      services: BootstrapServices,
    ): Promise<void> {
      logger.info('Bootstrap called');
 
      const { kernelFacet } = services;
      if (!kernelFacet) {
        throw new Error('kernelFacet service is required');
      }
 
      // Store in baggage for persistence across restarts
      baggage.init('kernelFacet', kernelFacet);
 
      await initializeCapletController({
        kernelFacet,
        storageAdapter,
        logger,
        resolve: resolveCapletFacet,
        reject: rejectCapletFacet,
      });
    },
 
    ...capletMethods,
  });
}
 
/**
 * Create delegating methods that forward calls to a source object via E().
 * Useful for exposing methods from a promise-based source on an exo.
 *
 * @param source - The source object (or promise) to delegate to.
 * @param methodNames - Array of method names to delegate.
 * @returns An object with delegating methods.
 */
function defineMethods(
  source: ERef<object>,
  methodNames: string[],
): Record<string, (...args: unknown[]) => unknown> {
  const output: Record<string, (...args: unknown[]) => unknown> = {};
  for (const methodName of methodNames) {
    output[methodName] = (...args: unknown[]) =>
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      (E(source) as any)[methodName](...args);
  }
  return output;
}