Skip to the content

Capsid menu

app/lib/llms/save.server.ts

144 lines and 14 functions in DrDustinEdwards/dustinedwards-info, at commit 1590ba4 on 2026-10-24. This file on GitHub.

Functions

The functions of save.server.ts
FunctionLinesComplexityCoveredCRAPMutation
constructor32 to 351No data: no coverage report in this mapNo data: no coverage report in this mapNo data: no mutants in this code
judgeLlms42 to 581No data: no coverage report in this mapNo data: no coverage report in this mapNo data: no mutants in this code
<anonymous:52>52 to 521No data: no coverage report in this mapNo data: no coverage report in this mapNo data: no mutants in this code
<anonymous:56>56 to 561No data: no coverage report in this mapNo data: no coverage report in this mapNo data: no mutants in this code
compileLlms64 to 682No data: no coverage report in this mapNo data: no coverage report in this mapNo data: no mutants in this code
readLlms71 to 822No data: no coverage report in this mapNo data: no coverage report in this mapNo data: no mutants in this code
writeLlmsRow85 to 943No data: no coverage report in this mapNo data: no coverage report in this mapNo data: no mutants in this code
rowIsCurrent97 to 991No data: no coverage report in this mapNo data: no coverage report in this mapNo data: no mutants in this code
saveLlms102 to 1589No data: no coverage report in this mapNo data: no coverage report in this mapNo data: no mutants in this code
isDraft116 to 1161No data: no coverage report in this mapNo data: no coverage report in this mapNo data: no mutants in this code
rowIsCurrent123 to 1231No data: no coverage report in this mapNo data: no coverage report in this mapNo data: no mutants in this code
commit124 to 1301No data: no coverage report in this mapNo data: no coverage report in this mapNo data: no mutants in this code
write146 to 1491No data: no coverage report in this mapNo data: no coverage report in this mapNo data: no mutants in this code
recordDivergence152 to 1521No data: no coverage report in this mapNo data: no coverage report in this mapNo data: no mutants in this code

Imports

It imports app/db/pages.ts, app/db/procedures.ts, app/db/publications.ts, app/db/settings.ts, app/lib/cache-purge.server.ts, app/lib/carrel/errors.server.ts, app/lib/content-pages.mjs, app/lib/content/hashes.mjs, app/lib/content/load-pipeline.server.ts, app/lib/editor/converge.mjs, app/lib/editor/github.server.ts, app/lib/editor/publish-policy.mjs, app/lib/editor/write-path.server.ts, app/lib/llms/validate.mjs, app/lib/publications/paths.mjs, app/lib/seo.ts. Imported by app/lib/carrel/document-handler.server.ts, app/lib/operator/api.server.ts, app/lib/operator/sync-tools.server.ts.

Source

// The one llms.txt save, called by the site-api adapter for Carrel (app/lib/carrel/document-handler.server.ts),
// and the reads the operator's get_llms tool and the llms-drift check answer (docs/LLMS.md). The document is
// content/llms.txt, the source; /llms.txt is served from the `settings` row derived from it. A save judges the
// file with the rules CI runs (validate.mjs), refuses it with every message if it fails, commits it to the
// repository (unless it is byte-identical, commitUnlessUnchanged), then writes the row and purges /llms.txt,
// so the change is live on the next request with no build or deploy. The commit is never reverted when the D1
// write fails; the error says the commit landed and how to repair the row. A document has no
// draft state: it is always the published file, and a save edits it live.

import { listPublishedPageTitles } from "~/db/pages";
import { listPublishedPublicationTwins } from "~/db/publications";
import { listPublishedProcedures } from "~/db/procedures";
import { getSetting, setSetting } from "~/db/settings";
import { ContentInvalid } from "~/lib/carrel/errors.server";
import { purgeLlms, type PurgeOutcome } from "~/lib/cache-purge.server";
import { gitBlobSha } from "~/lib/content/hashes.mjs";
import { loadPipeline } from "~/lib/content/load-pipeline.server";
import { draftContentPagePaths, publishedContentPagePaths } from "~/lib/content-pages.mjs";
import { convergeWithRetry } from "~/lib/editor/converge.mjs";
import { blobGuard, commitFiles, readFile } from "~/lib/editor/github.server";
import { decideFileWrite, type Actor } from "~/lib/editor/publish-policy.mjs";
import { commitUnlessUnchanged, UNCHANGED_NOTE } from "~/lib/editor/write-path.server";
import { paperMarkdownPath } from "~/lib/publications/paths.mjs";
import { SITE_ORIGIN } from "~/lib/seo";

import { LLMS_PATH, LLMS_SETTING_KEY, llmsChecks, llmsErrors } from "./validate.mjs";

type LlmsEnv = Env & { GITHUB_TOKEN?: string };

/** A file the validator refused: 422, with every message, so the caller can fix its own edit. */
class LlmsInvalid extends ContentInvalid {
  constructor(errors: string[]) {
    super(`${LLMS_PATH} was not saved: it fails ${errors.length} check(s). Nothing was committed.`, errors);
    this.name = "LlmsInvalid";
  }
}

/**
 * Every rule against what the site holds now: the registered pages and the published procedures (the
 * protocols are pages, drawn from D1), and the papers the publications table serves.
 */
async function judgeLlms(env: LlmsEnv, raw: string) {
  const [{ findWideDashes }, procedures, twins, pageTitles] = await Promise.all([
    loadPipeline(),
    listPublishedProcedures(env),
    listPublishedPublicationTwins(env),
    // The page rows the route serves from: a page published through Carrel can have its line saved at once.
    listPublishedPageTitles(env),
  ]);
  const published = [...pageTitles.keys()];
  return llmsChecks(raw, {
    pagePaths: [...publishedContentPagePaths(published), ...procedures.map((p) => p.path)],
    optionalPagePaths: draftContentPagePaths(published),
    origin: SITE_ORIGIN,
    findWideDashes,
    twinUrls: twins.map((t) => paperMarkdownPath(t.slug)),
  });
}

/**
 * The one compile door: a save and sync_llms both read the file through it. The row carries the bytes, so
 * "compiled" is the bytes judged, with the blob sha the drift check compares.
 */
export async function compileLlms(env: LlmsEnv, raw: string) {
  const errors = llmsErrors(await judgeLlms(env, raw));
  if (errors.length > 0) return { ok: false as const, errors };
  return { ok: true as const, errors: [] as string[], raw, sourceBlobSha: await gitBlobSha(raw) };
}

/** get_llms: the file, the head it was read at, the rules it fails now, and whether the row serves it. */
export async function readLlms(env: LlmsEnv) {
  const file = await readFile(env, LLMS_PATH);
  if (!file) return null;
  const row = await getSetting(env, LLMS_SETTING_KEY);
  return {
    path: LLMS_PATH,
    raw: file.content,
    bytes: new TextEncoder().encode(file.content).length,
    errors: llmsErrors(await judgeLlms(env, file.content)),
    servedFromRepository: row === file.content,
  };
}

/** The one write door for the row /llms.txt is served from: a save and sync_llms both end here, read back. */
export async function writeLlmsRow(env: LlmsEnv, raw: string) {
  await setSetting(env, LLMS_SETTING_KEY, raw);
  const stored = await getSetting(env, LLMS_SETTING_KEY);
  if (stored !== raw) {
    throw new Error(
      `The llms.txt row was written but the row read back is not the file (${stored === null ? "missing" : `${stored.length} characters`}, ` +
        `expected ${raw.length}). Run the llms sync (sync_llms) to rebuild it from the repository.`,
    );
  }
}

/** True when the served row is exactly this file. */
async function rowIsCurrent(env: LlmsEnv, raw: string) {
  return (await getSetting(env, LLMS_SETTING_KEY)) === raw;
}

/** The llms.txt save. */
export async function saveLlms(
  env: LlmsEnv,
  options: { raw: string; expectedHeadSha?: string | null;
    /** The blob sha of this file as Carrel loaded it; undefined means no check. */
    expectedBlobSha?: string | null; actor: Actor },
) {
  const { raw, actor } = options;
  const existing = await readFile(env, LLMS_PATH);
  if (!existing) {
    throw new LlmsInvalid([`${LLMS_PATH} is missing from the repository, so there is nothing to edit. A save does not create it.`]);
  }

  const compiled = await compileLlms(env, raw);
  if (!compiled.ok) throw new LlmsInvalid(compiled.errors);
  decideFileWrite({ actor, noun: "llms.txt", incomingDraft: false, priorRaw: existing.content, isDraft: () => false });

  const tag = actor.kind === "operator" ? ` [operator:${actor.id}]` : actor.kind === "carrel" ? ` [carrel:${actor.changeId}]` : "";
  // After the gate, so a file that fails validation is still refused even when it is unchanged.
  const written = await commitUnlessUnchanged(env, {
    existing,
    raw,
    rowIsCurrent: () => rowIsCurrent(env, raw),
    commit: () =>
      commitFiles(env, {
        expectedHeadSha: options.expectedHeadSha,
        expectedBlobs: blobGuard(LLMS_PATH, options.expectedBlobSha),
        message: `Update llms.txt${tag}`,
        changes: [{ path: LLMS_PATH, content: raw }],
      }),
  });
  if (written.action === "noop") {
    return { commitSha: written.commitSha, sourceBlobSha: compiled.sourceBlobSha, unchanged: true, note: UNCHANGED_NOTE, purged: null as PurgeOutcome };
  }
  const { commitSha, blobShas } = written;
  if (written.action === "commit" && blobShas[LLMS_PATH] && blobShas[LLMS_PATH] !== compiled.sourceBlobSha) {
    throw new Error(
      `${LLMS_PATH} WAS committed as ${commitSha}, but the committed bytes (${blobShas[LLMS_PATH]}) are not ` +
        `the bytes judged (${compiled.sourceBlobSha}), so the row was not updated. Run sync_llms to rebuild it ` +
        "from the repository.",
    );
  }

  let purged: PurgeOutcome = false;
  await convergeWithRetry({
    write: async () => {
      await writeLlmsRow(env, raw);
      purged = await purgeLlms("save llms.txt");
    },
    // Nothing kept in KV for a document: the thrown error names the commit and the repair (sync_llms), and
    // the watchdog's llms-drift check repairs the row from the repository anyway.
    recordDivergence: async () => undefined,
    slug: "llms.txt",
    commitSha,
  });

  return { commitSha, sourceBlobSha: compiled.sourceBlobSha, unchanged: written.action !== "commit", purged };
}