Appendix B · section 24 of 32
ask/manifest.ts
From A Corpus You Can Trust.
import { createHash } from "node:crypto";
import type { Law } from "./corpus.js";
import { embeddingText } from "./vectors.js";
// What was embedded, as one hash: each id and the exact text embedded for it.
// The index is filled by hand, so it can fall behind the corpus silently: a
// law reworded, a statute added, and the index ranks what it was given last
// time. The text shown is never stale -- it is read from the corpus by id --
// but the ranking would be, and nothing on screen would say so. The test in
// corpus.test.ts recomputes this and compares it with `EMBEDDED`, so a corpus
// change that was not re-embedded is a red test rather than a ranking that
// quietly drifts.
export function fingerprint(laws: Law[]): string {
const hash = createHash("sha256");
for (const law of laws) hash.update(`${law.id}\u0000${embeddingText(law)}\u0001`);
return hash.digest("hex");
}
/** What the index holds, recorded the last time `vectors.ts` embedded the corpus. */
export const EMBEDDED = {
laws: 27,
sha256: "d2c7b42856b16f720a7754a0dde455084aab3d3ccd062dcd2628b1980f95bc01",
};
/**
* A page as the text a passage is checked against: tags dropped, entities
* read, curly quotes made straight and every run of white space one space.
* Nothing else is forgiven: not a digit, not a letter, not case.
*/
export function pageText(raw: string): string {
return raw
.replace(/<(script|style|nav|header|footer)\b[\s\S]*?<\/\1>/gi, " ")
.replace(/<[^>]+>/g, " ")
.replace(/&/g, "&")
.replace(/ /g, " ")
.replace(/[‘’]/g, "'")
.replace(/[“”]/g, '"')
.replace(/[\s ]+/g, " ")
.replace(/ ([.,;:!?)])/g, "$1")
.trim();
}
/** Whether `passage` is in `text`, starting and ending where a word does: "5 days" is in "15 days" as letters, not as words. */
export function onThePage(passage: string, text: string): boolean {
const word = /[\p{L}\p{N}]/u;
for (let at = text.indexOf(passage); at >= 0; at = text.indexOf(passage, at + 1)) {
const before = at === 0 ? "" : text[at - 1];
const after = text[at + passage.length] ?? "";
const startsWhole = !(word.test(passage[0]) && word.test(before));
const endsWhole = !(word.test(passage[passage.length - 1]) && word.test(after));
if (startsWhole && endsWhole) return true;
}
return false;
}
/** At most this many passages a family: the model is given the page's rule, not the page. */
export const MAX_PASSAGES = 5;
// The passages a model proposed that may be stored: each a substring of the
// page as fetched. A model asked to copy a passage can paraphrase it, and
// "you must wait 10 days" stored under a page that says 15 is a learner turned
// away at the counter on the eleventh day, under the agency's own name.
export function verifiedPassages(
proposed: readonly string[],
page: string,
): { passages: string[]; dropped: { passage: string; reason: string }[] } {
const text = pageText(page);
const passages: string[] = [];
const dropped: { passage: string; reason: string }[] = [];
for (const each of proposed) {
const passage = pageText(each);
if (passage === "") continue;
if (!onThePage(passage, text)) dropped.push({ passage, reason: "not in the fetched page" });
else if (passages.includes(passage)) continue;
else if (passages.length >= MAX_PASSAGES) dropped.push({ passage, reason: `past the first ${MAX_PASSAGES}` });
else passages.push(passage);
}
return { passages, dropped };
}