resolveSpecifier(specifier) {
    const [filepaths, log, compartment] = [
      this.#filepaths,
      this.#log,
      this.compartment,
    ]
    specifier = specifier.replace(/\/$/, '')

    if (filepaths.has(specifier)) {
      const cachedFilepath = /** @type {string | null} */ (
        filepaths.get(specifier)
      )
      return cachedFilepath
    }

    if (specifier.startsWith('./') || specifier.startsWith('../')) {
      // relative path
      filepaths.set(specifier, null)
      return null
    }
    if (this.isBuiltin(specifier)) {
      filepaths.set(specifier, specifier)
      return specifier
    }

    /** @type {ModuleDescriptor | undefined} */
    const moduleDescriptor = compartment.modules[specifier]

    if (moduleDescriptor) {
      const filepath = this.toPath(moduleDescriptor)
      if (filepath) {
        filepaths.set(specifier, filepath)
        return filepath
      } else {
        log.error(
          `Compartment ${hrLabel(compartment.label)}: unable to determine filepath for "${specifier}"; this is a bug`
        )
      }
    }

    log.debug(
      `Compartment ${hrLabel(compartment.label)}: recording module specified as ${hrPath(specifier)} as missing`
    )
    this.#missingModules.add(specifier)
    filepaths.set(specifier, null)

    return null
  }
Attempts to resolve the filepath of `specifier` using the current
compartment descriptor's
{@link CompartmentDescriptor.modules module descriptors}.

Strips trailing slashes from specifiers, since Endo does not store them
with trailing slashes

@param {string} specifier
@returns {string | null} Filepath, name of builtin, or if `null`, then
  either the module is missing or we just don't need an import map entry
  for it.