Contributing to the docs
The rules below are the documentation-architecture policy in its contributor-facing form. CI enforces
the structural parts (docs/scripts/check-docs-structure.ts, run by the Docs workflow on every PR
that touches the site); the editorial parts are reviewer discipline. Run the gate locally with
yarn workspace @mailwoman/docs lint:structure.
Declare the page's role
Every maintained page declares what kind of page it is in frontmatter, and each role has a contract:
| Role | Reader need | Time horizon | Canonicality | Required frontmatter |
|---|---|---|---|---|
guide | accomplish a task | evergreen | one preferred path | audience, prerequisites, verified-with |
tutorial | learn by doing | evergreen but versioned | one learning journey | audience, prerequisites, verified-with |
concept | understand a stable idea | evergreen | one explanatory home | audience, source-of-truth, review-by |
reference | look up a precise contract | release-bound | authoritative | source-of-truth, generated-from or owner |
decision | understand an active choice | active until superseded | links to accepted outcome | status, owner, superseded-by when closed |
evidence | inspect a result or historical record | immutable/datestamped | cited by maintained pages | date, status, promoted-conclusions |
One site-specific addition: landing, for pure navigation surfaces (the front door, a section
index) — required field: audience.
You don't have to backfill role: onto old pages. CI requires it on the entry pages, the four
canonical concept pages, and every recipe, and validates the vocabularyvocabularyThe fixed set of tokens a tokenizer can produce. Mailwoman's SentencePiece vocabulary is tens of thousands of subword pieces, with byte fallback for anything outside it. and required fields anywhere
it's declared. Add the fields when you touch a page.
Set status: when a record opens or closes
The page chrome (src/theme/DocItem/Content) renders two status: values above the title:
active-decision for an open design record, and superseded — always paired with a
superseded-by: link to the successor. Pages in the Archive sidebar get a dated "Historical record"
banner automatically, no frontmatter needed. See
Joint consistency resolution for the rendered
chrome. Free-text status: values are reserved for the dated evalevalRunning the model against a held-out golden dataset and computing per-component F1, exact-match, calibration, and resolved-coordinate error. records (a delegated workstream);
everywhere else the vocabularyvocabularyThe fixed set of tokens a tokenizer can produce. Mailwoman's SentencePiece vocabulary is tens of thousands of subword pieces, with byte fallback for anything outside it. is enforced.
New page, or update a canonical one?
Four concept pages own the recurring reader questions. If your material answers one of these, extend the owner — don't open a parallel page:
- How does parsingaddress parsingThe process of decomposing a free-text postal address string into structured components — house number, street name, locality, region, postcode, and country — so a geocoder can resolve them to coordinates. work? → How Mailwoman parses an address
- How does a parseaddress parsingThe process of decomposing a free-text postal address string into structured components — house number, street name, locality, region, postcode, and country — so a geocoder can resolve them to coordinates. become a coordinate? → How Mailwoman resolves a place
- What's covered, where, from what data? → Data, locales, and coverage
- How good is it, and how do we know? → Quality and evaluation
A new page needs a reader question none of these owns — name that question in the PR description. Contracts belong in Reference; a duplicated explanation is a future contradiction.
Retiring a page
- Move its sidebar entry to the Archive section in
sidebars.ts. The file stays where it is — the URL must not change, and the Archive banner takes over from there. - If a successor exists, set
status: supersededandsuperseded-by:on the retired page. - Repoint inbound links from maintained pages to the successor.
- Don't touch publicness. Reorganization never changes what's published: nothing excluded from the build becomes public — and nothing public gets unpublished — as a side effect of a move. Promoting an internal record to the public site is its own explicit decision, per page, with operator sign-off, and only for records with no internal references, no security-sensitive detail, and evidentiary value (a maintained page wants to cite them). Demotion is for mistakes, not age: a page leaves the public site only if it should never have been public. Dated records keep their URLs and their point-in-time content.
Editorial rules
- Analog first. The core reader is a cartography/geodata practitioner. Introduce every learned mechanism through the rule-world thing it replaces — gazetteergazetteerA geographical index that maps place names and postcodes to real-world coordinates. Mailwoman uses a custom-built Who's On First (WOF) SQLite database as its gazetteer — the 'atlas' half of the grammar/atlas architecture. lookup before FSTFST (finite-state transducer). A compact automaton that reads an input sequence and emits an output sequence. Mailwoman encodes gazetteer names and street affixes as FSTs for fast prefix matching and prior injection without search overhead. prior, hand-written pattern before per-tokentokenOne word or subword in the tokenized input. For the neural classifier, tokens come from SentencePiece (subword units); for the rule classifiers, tokens are whitespace- and punctuation-separated words. score, tie-breaking heuristics before ViterbiViterbi decodingA dynamic programming algorithm that finds the most likely sequence of hidden states (labels) given a sequence of observations (token emissions). Mailwoman uses Viterbi over a linear-chain CRF to produce globally coherent BIO label sequences from per-token model scores. — and keep statistical depth one link away from the entry path.
- Captured outputs are executed, never hand-typed. If a page shows a command's output, run the
command and paste what it printed.
verified-with:records the version you ran it against. - Release-bound numbers cite the shipped npm modelneural classifierThe machine learning model at the core of Mailwoman's parser — a transformer encoder (~30M parameters) trained from scratch to do BIO token classification over addresses. It learns the 'grammar' of address formats; the gazetteer supplies the 'atlas.'. ParameterparameterA single learned number inside a model — one weight or bias. Mailwoman's encoder has roughly 30 million of them; training is the search for good values. counts, sizes, and scores come from the published artifacts, and they're refreshed at release time — "Step 5 — refresh the docs' release-bound numbers" in RELEASING.md.
- Name the property. The word "honest" (and its cluster) stays out of prose — say what the
page actually claims: the evalevalRunning the model against a held-out golden dataset and computing per-component F1, exact-match, calibration, and resolved-coordinate error. holds out the trainingtrainingThe process of adjusting a model's parameters so its predictions match labeled examples, by repeatedly measuring error and nudging the weights to reduce it. Distinct from inference, when the trained model is run on new input. data, the coveragecoverageThe fraction of a population or region for which a data source has real, non-placeholder entries — e.g. 47% rooftop coverage on Texas addresses. Distinct from accuracy on the rows that are present. number excludes
interpolationinterpolationA geocoding technique that estimates a coordinate along a street segment based on the house number range. Used as the middle tier of Mailwoman's geocode cascade when exact address-point data is unavailable.. Harness identifiers (
honest-eval,coverage-honest) are exempt.
What CI checks
check-docs-structure.ts fails a PR on: an unknown role:/status: value or a missing required
field, an exact duplicate title: across the published site, and a page reachable by URL but absent
from every sidebar. Deliberate exceptions live in docs/scripts/docs-structure-allowlist.ts, each
with a reason. A quarterly sweep (.github/workflows/docs-freshness.yml) files one "Docs freshness
sweep" issue listing pages whose review-by: date has passed — pages without review-by: are
never swept, so archival evidence stays untouched.