```ts
export function rollupTotals(
  nodes: Record<string, { value: number; parent: string }>,
): Record<string, number> {
  // Find the root node
  const rootNodeIds = Object.keys(nodes).filter(id => nodes[id].parent === '');
  if (rootNodeIds.length !== 1) {
    throw new Error('Input must have exactly one root node.');
  }

  const rootId = rootNodeIds[0];
  const totals: Record<string, number> = {};

  // Helper function to calculate the total for a node and its descendants
  function calculateTotal(id: string): number {
    if (totals[id] !== undefined) {
      return totals[id];
    }

    let total = nodes[id].value;
    for (const other of Object.keys(nodes)) {
      if (nodes[other].parent === id) {
        total += calculateTotal(other);
      }
    }

    totals[id] = total;
    return total;
  }

  // Validate parent ids and ensure all nodes are reachable from the root
  const visitedNodes = new Set<string>();
  function validateAndCalculate(id: string): void {
    if (visitedNodes.has(id)) {
      return;
    }
    visitedNodes.add(id);

    if (!nodes[id]) {
      throw new Error(`Node with id "${id}" does not exist.`);
    }

    const parentId = nodes[id].parent;
    if (!(parentId === '' || Object.prototype.hasOwnProperty.call(nodes, parentId))) {
      throw new Error(`Invalid parent id "${parentId}" for node "${id}".`);
    }

    calculateTotal(id);

    for (const other of Object.keys(nodes)) {
      if (nodes[other].parent === id) {
        validateAndCalculate(other);
      }
    }
  }

  validateAndCalculate(rootId);

  // Ensure all nodes are visited
  for (const id of Object.keys(nodes)) {
    if (!visitedNodes.has(id)) {
      throw new Error(`Node "${id}" is not reachable from the root.`);
    }
  }

  return totals;
}
```