```ts
/**
 * Decodes a bit run using a given codebook.
 * @param codebook - A mapping of words to marks.
 * @param strip - The string to decode.
 * @returns An array of decoded words.
 */
export function decodeBitRun(codebook: any, strip: string): string[] {
  if (typeof codebook !== 'object' || codebook === null) {
    throw new Error('Codebook must be a non-null object.');
  }

  const words = Object.keys(codebook);
  if (words.length === 0) {
    throw new Error('Codebook must map at least one word.');
  }

  const marks = Object.values(codebook);
  for (const mark of marks) {
    if (typeof mark !== 'string' || !/^[01]+$/.test(mark)) {
      throw new Error('Mark must be a non-empty string of 0s and 1s.');
    }
  }

  if (new Set(marks).size !== marks.length) {
    throw new Error('No two words can have the same mark.');
  }

  for (let i = 0; i < marks.length; i++) {
    for (let j = 0; j < marks.length; j++) {
      if (i !== j && marks[i].startsWith(marks[j])) {
        throw new Error('No word\'s mark can be the prefix of another word\'s mark.');
      }
    }
  }

  if (typeof strip !== 'string') {
    throw new Error('Strip must be a string.');
  }

  if (!/^[01]*$/.test(strip)) {
    throw new Error('Strip must contain only 0s and 1s.');
  }

  const decodedWords: string[] = [];
  let index = 0;

  while (index < strip.length) {
    let found = false;
    for (const [word, mark] of Object.entries(codebook)) {
      if (strip.startsWith(mark, index)) {
        decodedWords.push(word);
        index += mark.length;
        found = true;
        break;
      }
    }

    if (!found) {
      throw new Error('Reading runs off the end of the strip with characters left over that finish no word\'s mark.');
    }
  }

  return decodedWords;
}
```