/** * THE TEMPLATES CORPUS IS REQUIRED, AND CI DOES NOT RUN THESE AT ALL (2026-08-07). * * Twelve spec files in this directory defer to this module (fifteen read a sibling checkout repo-wide — see * `vitest.config.ts`'s `CORPUS`, which is the census; this docblock said "nine" from 2026-08-07 until 2026-08-12, * which is what a hand-kept count does). Each one carried its own copy of the same decision, and each keyed it on * `process.env.CI`: * * if (process.env.CI) throw new Error('No checkout at …'); * * That is backwards on both halves. CI checks out one repository, so a runner is the one place the corpus can * never be — and the first release tag paid for it: eight suites threw at module load and `yarn test` could * not pass on any runner. Meanwhile a DEVELOPER, who does have the checkout, was the one allowed to skip. * * The rule is the other way round: * * * **CI excludes these tests by SELECTION.** `yarn test:ci` runs the `unit` project only; these live in * the `corpus` project and are never collected there. Exclusion is a decision the run makes out loud — * visible in `vitest.config.ts` and in the workflow — not a guard inside a file that has already loaded. * * **Everywhere else they are REQUIRED.** If one of these files is loaded at all then somebody asked for * the corpus, so an absent checkout is a failure. There is no skip. * * ### Why absence must fail rather than skip * * Written into three of these files already: *"a bound that cannot fail is not a budget, it is a comment"* — * and a bound that cannot RUN is the same thing. Every headline number in Phase 2 — the project counts, * NFR-4, NFR-5's 1200 ms, NFR-6a's 300 ms, DUP-1's severity split, DEP-1's exclusions — was derived from the * corpus and is asserted nowhere else. A skip turns all of it into decoration, and a skip that reports green * cannot be told apart from a pass. */ import { describe } from 'vitest'; import { existsSync } from 'node:fs'; /** * Where the corpus is looked for. * * The default is a path on the machine this was written on; `HEXASYNC_TEMPLATES` overrides it. Either way a * missing path is an error, because reaching this module at all means the corpus was asked for. */ export const CORPUS = process.env.HEXASYNC_TEMPLATES ?? '/config/workspaces/templates'; export const hasCorpus = existsSync(CORPUS); /** * What every caller uses: a suite by name. * * Narrower than `typeof describe` on purpose — every caller wants one thing, and the chainable modifiers * hanging off `describe` are not it. `.skip` in particular: `corpusCheckout.spec.ts` fails any corpus spec that * carries one, so a type that cannot express it is the cheaper half of that rule. */ type Suite = (name: string, body: () => void) => void; /** * The corpus, or a failure explaining what was lost — decided once for every caller in this directory. * * Specs under `packages/` cannot reach this (a package importing an app is a layering violation), so the same * decision is made for the whole `corpus` project by `vitest.corpus-setup.ts`, which needs no import. * * @param tag The bracketed prefix the file already used, e.g. `duplicates`. * @param cannotRun What is lost when these do not run, in the file's own words. * @param why Why losing it silently is unacceptable. */ export function corpusSuite( tag: string, cannotRun: string, why: string, ): Suite { if (hasCorpus) return describe; // Thrown at MODULE LOAD, so it cannot be mistaken for one failing case among many passes — and, more to // the point, cannot be mistaken for a pass. throw new Error( `[${tag}] No templates checkout at ${CORPUS}. ${cannotRun} ${why}\n` + 'The corpus lives in a different repository. Clone it, or set HEXASYNC_TEMPLATES to your checkout.\n' + 'On CI these are not run at all — use `yarn test:ci`, which selects the `unit` project only.', ); }