```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> {
  const RANK = ["chatter", "notice", "alarm", "panic"];
  const gathered: { name: string; took: number[] }[] = [];
  const dropped: number[] = [];

  // Validate lanes
  if (!Array.isArray(lanes)) throw new Error("lanes must be an array");
  for (const lane of lanes) {
    if (typeof lane !== "object" || lane === null) throw new Error("lane must be a mapping");
    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 true or false");
    // Check for duplicate names
    const existingIndex = gathered.findIndex(g => g.name === lane.name);
    if (existingIndex !== -1) {
      gathered[existingIndex].took.push(...(lane.took as number[]));
    } else {
      gathered.push({ name: lane.name as string, took: [] });
    }
  }

  // Validate events
  if (!Array.isArray(events)) throw new Error("events must be an array");
  for (const event of events) {
    if (typeof event !== "object" || event === null) throw new Error("event must be a mapping");
    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'");
  }

  events.forEach((event: any, at: number) => {
    let caught = false;
    for (const lane of lanes) {
      if (!event.channel.startsWith(lane.prefix)) continue;
      if (RANK.indexOf(event.severity as string) > RANK.indexOf(lane.upTo as string)) continue;
      const targetLane = gathered.find(g => g.name === lane.name);
      if (targetLane) targetLane.took.push(at);
      caught = true;
      if (lane.last) break;
    }
    if (!caught) dropped.push(at);
  });

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