```ts
/** Sifts events into lanes by channel prefix and severity ceiling. */
export function siftEventStreams(
  lanes: Array<Record<string, unknown>>,
  events: Array<Record<string, unknown>>,
): Record<string, unknown> {
  // Validate input types
  if (!Array.isArray(lanes) || !Array.isArray(events)) {
    throw new Error("lanes and events must be arrays");
  }

  const RANK = ["chatter", "notice", "alarm", "panic"];
  const gathered: { name: string; took: number[] }[] = [];
  const dropped: number[] = [];

  // Validate lanes
  lanes.forEach((lane) => {
    if (typeof lane !== 'object' || lane === null) {
      throw new Error("each lane must be an object");
    }
    if (typeof lane.name !== 'string' || lane.name.trim() === '') {
      throw new Error("lane name must be a non-empty string");
    }
    if (typeof lane.prefix !== 'string') {
      throw new Error("lane prefix must be a string");
    }
    if (!RANK.includes(lane.upTo as string)) {
      throw new Error("lane upTo must be one of 'chatter', 'notice', 'alarm', 'panic'");
    }
    if (typeof lane.last !== 'boolean') {
      throw new Error("lane last must be a boolean");
    }

    // Ensure unique names in gathered
    if (!gathered.some((g) => g.name === lane.name)) {
      gathered.push({ name: lane.name as string, took: [] });
    }
  });

  // Validate events
  events.forEach((event, at) => {
    if (typeof event !== 'object' || event === null) {
      throw new Error("each event must be an object");
    }
    if (typeof event.channel !== 'string' || event.channel.trim() === '') {
      throw new Error("event channel must be a non-empty string");
    }
    if (!RANK.includes(event.severity as string)) {
      throw new Error("event severity must be one of 'chatter', 'notice', 'alarm', 'panic'");
    }

    let caught = false;
    lanes.forEach((lane) => {
      if (caught && lane.last) return; // Stop if already caught by a "last" lane
      if (!event.channel.startsWith(lane.prefix)) return;
      if (RANK.indexOf(event.severity as string) > RANK.indexOf(lane.upTo as string)) return;
      gathered.find((g) => g.name === lane.name)?.took.push(at);
      caught = true;
    });

    if (!caught) dropped.push(at);
  });

  return { lanes: gathered, dropped };
}
```