```ts
export function linkLedgerMarks(marks: string[], aliases: Record<string, string>): [string, string[]][] {
  const validateMark = (mark: string): { houseCode: string; serial: number; checkLetter: string } => {
    const regex = /^([a-zA-Z]{2,3})[- /]([0-9]+)([a-zA-Z])$/;
    const match = mark.match(regex);
    if (!match) throw new Error('Malformed mark');

    const houseCode = match[1].toUpperCase();
    const serial = parseInt(match[2], 10);
    const checkLetter = match[3].toUpperCase();

    if (serial < 1) throw new Error('Serial value must be at least 1');
    if (checkLetter !== String.fromCharCode(((serial - 1) % 26) + 'A'.charCodeAt(0))) {
      throw new Error('Wrong check letter');
    }

    return { houseCode, serial, checkLetter };
  };

  const resolveHouseCode = (houseCode: string): string => {
    if (aliases[houseCode]) {
      if (aliases[houseCode] === houseCode || aliases[aliases[houseCode]]) {
        throw new Error('Bad alias table');
      }
      return aliases[houseCode];
    }
    return houseCode;
  };

  const entries: { canonical: string; raws: string[] }[] = [];

  for (const mark of marks) {
    if (typeof mark !== 'string') throw new Error('Non-string mark');

    try {
      const { houseCode, serial, checkLetter } = validateMark(mark);
      const resolvedHouseCode = resolveHouseCode(houseCode);
      const canonical = `${resolvedHouseCode}-${serial.toString().padStart(2, '0')}-${checkLetter}`;

      const existingEntry = entries.find(
        (entry) =>
          entry.canonical.split('-')[0] === resolvedHouseCode &&
          parseInt(entry.canonical.split('-')[1], 10) === serial &&
          entry.canonical.split('-')[2] === checkLetter
      );

      if (existingEntry) {
        existingEntry.raws.push(mark);
      } else {
        entries.push({ canonical, raws: [mark] });
      }
    } catch (error) {
      throw new Error('Invalid mark');
    }
  }

  return entries;
}
```