import { type Kysely } from 'kysely'; /** * What visitors searched for, and whether the site had an answer. * * The report that earns this table is **zero results**: it names, in the visitors' own words, * content that is missing or titled something nobody would guess. Nothing else in the CMS can * answer that, and the people who can act on it are editors — which is why it lives here rather * than in whatever analytics the site is wired to. * * **One row per search, aggregated on read**, following `audit_log` rather than keeping running * counters. A counter table cannot answer a question nobody thought to ask when it was designed — * "what did people search for the week the catalogue went up" — and the volume this sees is a few * thousand rows a month on a campus site, which is nothing to group over. `purgeSearchLogBefore` is * the retention story, and like the audit log's it is a capability nothing schedules: adding a * recurring delete to a deployment's data is not a decision a migration gets to make. * * **Nothing identifying is stored, and that is a decision rather than an omission.** No IP, no user * agent, no session id, no account. A search log at a college contains "withdrawal deadline", * "counseling", "financial aid appeal" — anything that lets a row be tied back to a person turns a * content report into a record of who was worried about what. The session id used to collapse * prefixes lives in the browser for the length of a visit and is never sent. * * `normalized` exists so grouping is a plain indexed `group by` instead of a `lower()` over every * row. It is written by the application, not by SQLite's `lower()`, because the tokenizer's folding * rules are the ones that decide whether two searches are the same search — see `searchTerms.ts`, * which is the only place that is written down. */ export declare function up(db: Kysely): Promise; export declare function down(db: Kysely): Promise;