# @govlab/canonical-write ## Purpose The canonical writers, with one guarantee. `writeCanonicalJson(path, value)` serializes a value and writes it formatted by the project's own prettier configuration. `writeCanonicalText(path, text)` does the same for already-rendered text, inferring the parser from the file extension. The guarantee is that the same input produces the same bytes, on every machine and every run, so a drift check comparing a file against a fresh render sees only semantic differences. A generated Markdown file also carries one mark, ``, on its first line or the first line after its frontmatter. `stampGenerated(body, previous, now, normalize?)` compares the new body with the body on disk, both with the mark stripped. An equal body keeps the held mark, so the file stays byte-identical, and a changed body takes the current minute and the next version. `writeGeneratedMarkdown(path, text)` formats first and then stamps against the file on disk. Every other write goes through `writeVerbatim(path, content)`, which writes text or bytes exactly as given: an edited source file, a cache entry, a built page, a log, a binary, or a generated file whose serializer is already deterministic and which the format stage never reaches. The workspace's write gate bans the raw file-write primitive outside this module, so every write in the workspace passes through one place, and a write helper elsewhere cannot hide a write from the gate. ## When to use - Any generator whose output is committed and drift-checked, such as a catalog, an index, a generated README or a report artifact. - Writing JSON that the developer will read in a diff, where a formatting-only change would bury the semantic one. - Emitting rendered text (markdown, TypeScript) that must match a fresh render byte for byte. - Writing a cache entry, a log, a built page, an edited source file or a binary through `writeVerbatim`, whose bytes are the caller's. ## When NOT to use - A test fixture under a temporary directory, which the write gate leaves to the raw primitive. - A hot loop formatting thousands of small files through a canonical writer. Formatting is not free, so a generated artifact the format stage never reaches goes through `writeVerbatim`. ## Architecture charts The structure, logical-flow and dependency diagrams derived from the source AST live in [_code.info.generated/mermaid-charts.generated.md](./_code.info.generated/mermaid-charts.generated.md). ## Install A private workspace package resolved through the root `package.json` workspaces glob `govlab.root/govlab.utils/*`. One `npm install` at the repo root links it, and `prettier` is hoisted there instead of declared here. There is no build step, so a consumer imports `@govlab/canonical-write` and calls it. ## Quick start ```ts EXAMPLE: Write a generated artifact whose bytes must be reproducible import { writeCanonicalJson, writeCanonicalText } from "@govlab/canonical-write"; await writeCanonicalJson(resolve(outDir, "catalog.generated.json"), { rules, summary }); await writeCanonicalText(resolve(outDir, "INDEX.generated.md"), renderedMarkdown); ``` ```ts EXAMPLE: Drift-check a written artifact against a fresh render await writeCanonicalJson(path, value); const fresh = await renderCanonical(value); if (readFileSync(path, "utf8") !== fresh) { throw new Error(`${path} has drifted — regenerate it`); } ``` ```ts EXAMPLE: Write a generated Markdown document whose mark moves only when its body changes import { writeGeneratedMarkdown } from "@govlab/canonical-write"; await writeGeneratedMarkdown(resolve(outDir, "INDEX.generated.md"), renderedMarkdown); ``` ## API - `function composeMark(mark: GeneratedMark): string` - `const GENERATED_MARK_PREFIX: " ## Configuration None of its own. Each writer formats with the prettier options the caller passes, and infers the parser from the target path's extension. A caller whose output the format stage also reaches passes the same options that stage uses, so the stage leaves the file as written. ## Dependencies The package is a leaf with no runtime dependencies. ## AI context - Canonical means reproducible, not merely pretty. A drift check comparing a file against a fresh render sees only semantic differences. - Every writer is async. A generator that forgets to await writes nothing and still exits zero. - A generator stamps after it formats. Stamping unformatted text compares a body the format pass then rewrites, so the version rises on every run. - A drift check compares the stamped file with the mark kept, so a hand edit to the mark counts as drift. - Formatting uses the options the caller passes, not a config file on disk. Raw bytes get rewritten by the next format pass, and a drift gate reports that rewrite. - A pure leaf: zero `@govlab/*` sibling dependencies, so the packages that generate artifacts depend on it without a cycle. ## Domains This package serves these software domains, which `_manifest.json` declares in `domains` from the two-tier software-domain vocabulary (`meta → sub`): - **developer-tooling** — code-generation - **platform** — file-system ## Quality governance The canonical quality catalog resolves the quality concepts that govern this package. `_manifest.json` declares them in `governedBy`, and a lint package derives them from the concepts its own rules enforce. Each maps to the custom lint rules that enforce it: - **immutability** — _best-practice_ - **serialization** — _best-practice_ ## Disposal - Declare another module as the write owner in the write gate's options, then move each call site to it. Generated bytes become writer-dependent unless the new owner formats them. - Weaken or delete every `--check` drift gate that compares a generated file against a fresh render. - Drop `@govlab/canonical-write` from the `govlab.quality` and `govlab.pipeline` manifests, then remove the package and its workspace entry. --- stable · 11 exports · 0 deps · 0 principles · 2 concepts