Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Locale coverage audits

A repository scan can bind the exact docs candidate that approved a later locale audit, but it cannot infer which pages a documentation generator considers equivalent across locales. Routes are publication outputs; they are not stable page identities. The locale coverage plan therefore names one independently selected inventory producer and treats every page key it will emit as opaque. It can also select the same immutable product-resource identity used by publication audits, without turning a locale version label into a release heuristic.

The plan binds:

  • the accepted scanner report payload digest;
  • the docs repository, commit, tree, and full candidate identity;
  • one site, source locale, target locale, channel, and optional version;
  • an optional exact product resource URI and digest that both locale inventories must identify;
  • the inventory producer identity, version, and plan-owned context digest; and
  • the operator’s coverage-policy identity, context digest, required-page rule, and authorized fallback classes and page scopes, plus whether exact target lineage is required.

Site and channel use the artifact-identity grammar. Locale and version labels use a broader bounded identity grammar so a producer can retain spellings such as de-DE; Amiss compares them exactly. It does not validate BCP 47, order versions, or infer fallback from a locale hierarchy. Source and target locale must differ. The opaque version remains scope metadata and never substitutes for an immutable product resource.

Page selectors have two closed forms. The coverage rule uses one selector: all-source means that every key in the future complete source inventory is required in the target inventory; named carries one nonempty, byte-sorted, duplicate-free set and makes only those source keys required. Other source keys remain optional, while target keys outside the source inventory can still be reported as orphaned. A page key is nonempty, control-free, and at most 4,096 UTF-8 bytes. It is a generator-owned identity such as a canonical docname, never a path or route guessed by Amiss.

The policy also carries a byte-sorted set of fallback rules, unique by opaque class. Each class has an all-source or named page selector describing exactly where that producer-defined fallback mode is allowed. An empty set forbids all fallbacks. The plan chooses authorization; it does not prove that a target page really came from a source resource.

require_target_lineage independently chooses whether every observed target-owned page must carry exact source lineage. False preserves a coverage-and-fallback-only audit and ignores any supplied target lineage. True makes missing lineage unproven and a mismatched lineage digest an exact refutation. Fallback pages are excluded because their F02 origin already binds the exact current source resource.

The optional plan product reuses publication’s PublicationResource directly: one absolute URI identifies the resource and its SHA-256 digest identifies the exact bytes. Null selects no product alignment policy. Amiss does not derive this value from the channel, scope version, tag, timestamp, or similarly spelled URL.

A locale audit reads one snapshot. The pull request that edits a source page and not its translation is caught earlier, by the translations pairs in the repository policy, which make the scan warn with translation-drift at the page left behind; Controls and policy has the key.

The contract intentionally contains no translation verdict or timestamp. A target page with the same key can prove structural coverage only. Exact lineage can prove which normalized source resource a target was based on; it cannot prove that the page was translated correctly, remains semantically equivalent, or required a change. This contract uses a digest because the source inventory supplies an exactly comparable digest. It does not accept an opaque revision without a matching source-side revision identity.

The plan is a closed, 64 KiB, digest-bound JSON document. Unknown fields, malformed identities, mixed Git object formats, repeated or unsorted named keys, oversized text, or a changed payload refuse the whole document. The outer digest establishes integrity, not authority: repository content must not choose the producer context or operator policy.

The matching evidence contract repeats the plan digest, docs candidate, locale scope, and producer context, then carries separate source and target inventories. Each inventory has its own input digest, nullable independently observed product resource, completeness bit, and byte-sorted map from page key to exact resource digest. The producer context defines which normalized bytes those digests identify. A null product says the authenticated producer could not establish that side’s release identity; it does not assert a mismatch.

Every observed target page also carries one closed origin. target-resource says the producer observed a target-owned resource and carries either its exact based_on_source_digest or null when the producer has no exact lineage assertion. fallback names an opaque producer-declared class and the exact source resource digest from which the fallback was obtained. A separate fallback list would make omission indistinguishable from target ownership. Requiring an origin on every target instead forces one explicit producer claim; authority still comes from authenticated acquisition outside the engine.

Completeness belongs to each side independently. A false value preserves the pages the producer did observe, but absence from that inventory is not evidence that a page is absent from the locale. Product availability is independent of page completeness: a complete page set may still have an unproven product identity, and a partial page set may carry an exact product receipt. The two inventories may carry at most 100,000 page rows combined inside one 16 MiB document. Page keys are unique within each side; malformed digests, duplicate or unsorted keys, unknown fields, and a changed payload refuse the whole receipt.

The engine reader establishes shape and integrity, not producer authority. Repeating a plan digest does not establish that the independently repeated facts match that plan. The pure offline assessment first requires the exact plan digest and selected producer. Foreign evidence remains unproven; correctly bound docs or scope disagreement refutes the plan without comparing the foreign inventories. Evidence acquisition outside the engine must therefore authenticate the selected producer, while the selected producer context defines the meaning of its origin classification.

For matching facts, every page row in the assessment is proved by presence on one side and complete absence on the other. A complete target can therefore prove that an observed required source page is missing even when the source inventory is partial. A complete source can likewise prove that an observed target page is orphaned when the target inventory is partial. Such a refutation is valid, but coverage.complete: false says the reported rows may be only a lower bound. The assessment matches only when its policy-scoped missing, orphan, named-source, and fallback checks are exhaustive and clean. A named policy can be exhaustive without an unrelated full source inventory when every named requirement and every target key has explicit source presence; all-source always requires a complete source set. Page resource digest differences remain deliberately inert.

Every fallback assessment row retains its page key and class. allowed means one plan rule admits that class and page and the declared source digest equals the observed source resource. unauthorized and source-mismatch are exact refutations. If the source page is absent from a partial source inventory, source-unproven keeps the whole result unproven; absence cannot become a digest mismatch until the source inventory is complete. Allowed fallback is not a translation or freshness verdict: it proves only the exact policy and provenance relation named by the contracts.

When target lineage is required, the assessment also checks every observed target-owned page whose source row is available, including pages outside a named required-coverage set. current means its declared based-on digest equals that current source resource; stale is the exact unequal case; unproven means the producer supplied no exact based-on digest. A target absent from a complete source set is already orphaned. A target absent from a partial source set remains covered by the existing source-incomplete result, so the evaluator does not manufacture a lineage row without a current source value to compare.

When the plan selects a product, the assessment compares each inventory’s product independently with that exact planned URI and digest. The source and target fields reuse the ordinary matched/refuted/unproven verdict vocabulary. Matched means exact equality; refuted means a different immutable resource was observed; unproven means that inventory supplied null. A product mismatch is an exact refutation even if the other side is unavailable. If the plan product is null, both supplied product receipts are deliberately ignored and the assessment product result is null. This relation proves release identity only, not translation quality or deployment success; the producer or future publication lane must authenticate how each inventory acquired it.

The assessment binds the exact evaluator, accepted report, plan, and optional evidence payload. Its three byte-sorted key sets name policy keys absent from source, required source keys absent from target, and target keys absent from source. A fourth byte-sorted set records every assessed fallback, and a fifth records every assessed target lineage. The document is bounded to 16 MiB and 200,000 rows across those sets. A separate nullable product result records the source and target resource verdicts. coverage.complete remains strictly about the page comparison, so the overall assessment can be unproven solely because a product receipt is missing while coverage is complete. Missing, unbound, wrong-producer, or otherwise insufficient evidence is unproven rather than clean. amiss locale-assess --plan <path> --evidence <path> judges one pair offline and writes the assessment; see the invocation chapter. The controller validates a complete report-bound chain before retention and stores its four components under the record’s own ceilings, 64 KiB for the plan and 16 MiB for the evidence and assessment.

amiss locale-inventory stages that evidence from the candidate tree. Translated documentation is laid out three ways, not one way per generator: a directory per locale, which Docusaurus, VitePress, Starlight and Hugo use; the locale in the filename, which Hugo and the MkDocs i18n plugin use; and gettext catalogs, which Sphinx and the mdBook helpers use. The first two are one walk with two key rules and are what this verb reads. Gettext has no reader yet, and a reader for it would report message coverage rather than page presence.

The page key is the path under the locale’s own root, which is not a spelling Amiss invents. A Docusaurus doc id is the path under the docs folder without its extension, a Sphinx docname is the path under the source directory, an MkDocs src_uri is the path under docs_dir, and Hugo and Starlight both define a translation as the file at the matching relative path. Taking that key from the tree needs no build and no generator configuration, which keeps executable build semantics out of the trust boundary. It also cannot see the two escapes that move a page’s identity off its path, a Docusaurus id and a Hugo translationKey; a page using either is reported as a missing and orphaned pair. A translation written in another page suffix than its source, a .md for an .mdx, is the same page, since both generators pair them by the path without its suffix, so a target page no source page shares a key with takes the one source key sharing its stem.

The context names each side’s root, the locale it claims, an optional filename suffix, and the file suffixes that count as pages. The plan names the object format, commit, and tree, so the verb walks exactly that snapshot or refuses. A resource digest names the blob a path resolves to, so coverage reads no file content and equal digests mean byte-identical files at that commit.

That last property carries the useful case. A target page whose blob is still the source’s blob is not a translation, it is the source sitting in a translation slot, so it goes out as a source-identical fallback rather than a target resource. The Docusaurus i18n tutorial tells operators to copy the whole default-locale tree and translate afterwards, so an inventory that compared page keys alone would call an untranslated copy a clean pass; an empty fallback set in the plan refuses it instead, and a plan that authorizes the class accepts it deliberately. Pages the walk cannot inventory, a symlink or a name outside UTF-8, drop their side’s completeness bit rather than disappearing. Neither side carries a product receipt: a tree records none.

Target lineage is the one thing a translated page can say about itself. MDN records the English commit each translation was made from in its front matter, and 21,757 of its translated pages carry it:

---
title: Array
l10n:
  sourceCommit: 5b20f5f4265f988f80f513db0e4b35c7e0cd70dc
---

A context naming that key, "lineage": ["l10n", "sourceCommit"], has the verb read it from every target page, look up the source page at that commit, and digest those bytes the way a current page is digested. So the assessment’s lineage rows say which translations were made from the source as it stands, current, and which from a source that has moved on since, stale. The key is one name or a name indented under one parent, and the value is a full commit ID in the plan’s object format. A page naming no commit, or one the object store does not hold, as in a shallow clone, is unproven rather than current. Without the key the verb reads no file content and every target lineage stays unproven.

Two contexts cover nearly every repository. A site that gives each locale its own directory names both roots, and leaves the suffix null. This is the Docusaurus shape, where the German tree sits under its own plugin path:

{
  "source": { "root": "docs", "locale": "en", "suffix": null },
  "target": {
    "root": "i18n/de-DE/docusaurus-plugin-content-docs/current",
    "locale": "de-DE",
    "suffix": null
  },
  "documents": [".md", ".mdx"]
}

docs/guide/start.md and i18n/de-DE/docusaurus-plugin-content-docs/current/guide/start.md both key as guide/start.md. VitePress, Starlight and Hugo’s directory mode differ only in the two roots: docs beside docs/de-DE, src/content/docs/en beside src/content/docs/de-DE, content/en beside content/de-DE.

A site that marks the locale in the filename gives both sides the same root and names the token. This is the MkDocs i18n default and Hugo’s filename mode:

{
  "source": { "root": "docs", "locale": "en", "suffix": null },
  "target": { "root": "docs", "locale": "de-DE", "suffix": "de-DE" },
  "documents": [".md"]
}

Here docs/guide/start.md and docs/guide/start.de-DE.md both key as guide/start.md. A null suffix means the filename carries no locale token at all, so start.de-DE.md is not an English page and start.md is not a German one. That reading holds only where both sides share a root; under a locale directory a dotted name such as v1.2-notes.md is a page like any other. When one root sits inside the other the deeper one wins, which is what lets a locale directory live under the source root.

A site that keeps its source locale at the content root, which is Starlight’s root locale, holds every other locale’s directory under the source root, and none of those pages is a source page. excluded names their roots, byte-sorted, and a page under one belongs to neither side:

{
  "source": { "root": "src/content/docs", "locale": "en", "suffix": null },
  "target": { "root": "src/content/docs/de", "locale": "de-DE", "suffix": null },
  "documents": [".md", ".mdx"],
  "excluded": ["src/content/docs/fr", "src/content/docs/ja"]
}

Without it the French and Japanese pages count as English ones, and German is short every page they hold.

documents is byte-sorted, and every entry starts with a dot. A file whose name ends in none of them is not a page, so images, partials, and configuration never turn into missing translations.

Running it takes three commands. amiss locale-plan writes the plan from the accepted report and the layout, so nothing is sealed by hand: the report fixes the docs candidate, its repository, and its identity digest, the layout fixes both locales and the producer, and the flags fix the scope and the policy. The report has to come from a commit-pair check that named its repository, since the plan binds a commit and a repository:

amiss locale-plan --report amiss-report.json --context locales.json \
  --site widget-docs --channel stable --fallback source-identical --format json > coverage-plan.json
amiss locale-inventory --repo . --plan coverage-plan.json --context locales.json \
  --format json > coverage-evidence.json
amiss locale-assess --plan coverage-plan.json --evidence coverage-evidence.json

A written plan requires every source page, allows the one --fallback class it names on every page, and requires target lineage only with --require-lineage. Its policy identity is amiss-locale-plan, with the context digest taken over those rules. A policy naming only some pages, or several fallback classes, is still written by hand. Human output from the inventory ends with the producer line the plan named:

amiss locale-inventory: en 42 pages complete
amiss locale-inventory: de-DE 39 pages complete
target pages still carrying the source bytes: 7
producer amiss-locale-tree 1.2.0 context sha256:0f3c...

Those seven pages are the point of the audit. Either the plan authorizes source-identical for them, or the coverage stays refuted until somebody translates them.

The checked public contracts are locale-coverage-plan.schema.json, with the matching locale-coverage-plan.json example, and locale-coverage-evidence.schema.json, with its locale-coverage-evidence.json example, and locale-coverage-assessment.schema.json, with its replayable locale-coverage-assessment.json example. Their strict readers and writers live in amiss-wire; generator-specific parsing and authentication stay outside the engine crates.

Last change: , commit: 60aacafb