Skip to main content

Spatial-layer contract

Every mailwoman dataset — the shipped poipoint of interest (POI). A named place that is not strictly an address — landmark, transit stop, venue, amenity, or franchise. Mailwoman tags these as venue and resolves them through the gazetteer..db, a user's locally-built OSMOpenStreetMap (OSM). A community-curated global map database (ODbL-licensed) with addr:* tagged features and place hierarchies. A secondary corpus source and a source of street names. layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6., a private CRM layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6. — is the same artifact shape: a sealed, readonly, provenance-tracked SQLite database ("a layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6.") keyed on a shared spatial spine. That sameness is the whole point: it lets shipped, build-local, and private layerslayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6. share one query surface (@mailwoman/spatial primitives, the mailwoman_layer_manifest MCP tool, future layer_query) without the consumer knowing or caring where the data came from.

This page is the contract in prose, for layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6. authors. The code lives in core/layers/ (schema.ts — the tables; manifest.ts — read/write IO); the design rationale lives in the spec named in the front matter. If you are building a specific layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6., read the worked example first — the POI layer runbook is the "what bites you" record from the first four poipoint of interest (POI). A named place that is not strictly an address — landmark, transit stop, venue, amenity, or franchise. Mailwoman tags these as venue and resolves them through the gazetteer..db builds.

What a layer is

A layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6. extends the 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. discipline (sealed 0444 artifacts, Kysely schema modules, build-then-swap) from reference data to analysis data. Concretely, a layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6. database:

  • Embeds the two contract tables below (layer_manifest, layer_coverage) regardless of tiertierInternal versioning of which label classes the model emits. Tier 1 is the coarse components (country, region, locality, postcode); Tier 2 adds venue, street, house_number; Tier 3 (future) would add attention, po_box, and POI venue subtyping. Historically called 'Stage 1/2/3' before the runtime-pipeline naming made that ambiguous., plus whatever domain tables the layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6. needs.
  • Is sealed 0444 on completion (sealDatabase from @mailwoman/core/utils) — a readonly artifact, never reopened read-write. Updates are full rebuilds, built-then-swapped into place (build to a temp path, move the old one aside, move the new one in) so the live artifact is always consistent even if a build fails halfway.
  • Addresses every domain row by at least one spine key so cross-layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6. joins and spatial primitives have a common coordinate to key on.

The spine

Every layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6. row is addressable by at least one of three shared keys. Parallel id spaces stay nullable metadata (the GERS rule) — you carry the ones your source has, and declare which in the manifest's spine_keys.

spine keycolumnmeaning
h3author-namedH3 cellH3Uber's hexagonal hierarchical geospatial indexing system. Mailwoman uses H3 cells at resolution 9 (~0.03 km²) for geo-first blocking in the matcher and for stable address primary keys in @mailwoman/address-id., stored as a 48-bit short cell (shortenH3Cell/expandH3Cell). Resolution declared per table.
wof_idauthor-namedWOFWOF (Who's On First). An open-source gazetteer of places maintained by Mapzen/whosonfirst. Mailwoman builds a custom SQLite database from WOF GeoJSON repos, extended with postcode data, importance scores, and coincident-role relations. ancestry anchor — the resolverresolverThe component that converts parsed address components (locality, region, postcode) into coordinates by looking them up in the gazetteer. The resolver ranks candidates by name match, population, and proximity, and returns the best-matching place with its centroid or polygon.'s existing id space (Who's On FirstWOF (Who's On First). An open-source gazetteer of places maintained by Mapzen/whosonfirst. Mailwoman builds a custom SQLite database from WOF GeoJSON repos, extended with postcode data, importance scores, and coincident-role relations.).
address_idauthor-namedThe @mailwoman/address-id key, where rows are address-grained.

The manifest declares column names and the H3H3Uber's hexagonal hierarchical geospatial indexing system. Mailwoman uses H3 cells at resolution 9 (~0.03 km²) for geo-first blocking in the matcher and for stable address primary keys in @mailwoman/address-id. resolution in its spine_keys field (a SpineKeys record, JSON-encoded in the table). At least one key is requiredwriteLayerManifest throws at build time and readLayerManifest throws at open time if a manifest declares none.

The manifest — layer_manifest

A single-row table holding the layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6.'s identity, provenance, and licensing. The name column is the primary key; the writer inserts exactly once. readLayerManifest refuses a table that is empty or multi-row — a layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6. with no identity, or two, fails loudly rather than misbehaving downstream.

fieldmeaning
name, version, schema_versionidentity
tiershipped | build-local | private (see below)
license, attributionSPDX-ish expression, e.g. CDLA-Permissive-2.0, ODbL-1.0; attribution nullable
source, source_vintage, build_cmd, build_shaprovenance — pin baselines by SHA + the command that produced them
freshness_policysealed (rebuild-only) | versioned-refresh (re-issued under the same name)
spine_keyswhich spine columns this layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6. carries, and the H3H3Uber's hexagonal hierarchical geospatial indexing system. Mailwoman uses H3 cells at resolution 9 (~0.03 km²) for geo-first blocking in the matcher and for stable address primary keys in @mailwoman/address-id. resolution (JSON-encoded)
created_atISO-8601, supplied by the build script — never generated in-library

created_at is deliberately caller-supplied: the library never stamps a timestamp, so a rebuild from the same inputs is reproducible rather than carrying a fresh "now" on every run.

Tiers

The tiertierInternal versioning of which label classes the model emits. Tier 1 is the coarse components (country, region, locality, postcode); Tier 2 adds venue, street, house_number; Tier 3 (future) would add attention, po_box, and POI venue subtyping. Historically called 'Stage 1/2/3' before the runtime-pipeline naming made that ambiguous. is the distribution posture — it governs whether we publish the artifact or ship only the builder.

  • shipped — permissive sources only (Overture Places CDLA-P, Census/TIGERTIGERThe US Census Topologically Integrated Geographic Encoding and Referencing database. Used as a corpus source for street-segment data. public domain, Wikidata CC0, Foursquare labelscomponent tagOne of the 33 labels in Mailwoman's address schema — street, locality, region, postcode, house_number, unit, po_box, country, venue, intersection, and others. Each parsed span carries exactly one component tag. Apache-2.0). Built by us and published to R2/npm like the 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..
  • build-local — share-alike sources (ODbL: OSMOpenStreetMap (OSM). A community-curated global map database (ODbL-licensed) with addr:* tagged features and place hierarchies. A secondary corpus source and a source of street names. POIspoint of interest (POI). A named place that is not strictly an address — landmark, transit stop, venue, amenity, or franchise. Mailwoman tags these as venue and resolves them through the gazetteer., Overture's base theme). We ship the builder CLI; the user builds on their own disk; nothing ODbL is distributed by us. Same posture as the unpublished osm/ workspace. When a build-local layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6. is absent, the answer is "requires the locally-built layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6.," never a mangled or invented result.
  • private — the user's own data (CRM, survey notes, parcelparcelA property polygon or record carrying a situs (site) address and often a separate owner mailing address. County GIS parcel aggregations are a training source for address-point variety and situs-vs-owner divergence. relationships), conforming to the same schema, loaded from $MAILWOMAN_DATA_ROOT, never leaving their machine. This is how "internal geo" joins the same query surface as public layerslayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6..

Freshness policy

  • sealed — immutable; updates are full rebuilds (the 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. discipline). The default.
  • versioned-refresh — periodically re-issued under the same name (e.g. a registry of people or programs that genuinely changes over time). Still sealed per-issue; the policy just records that a newer issue under the same name is expected.

Coverage — layer_coverage and the meaning-of-zero rule

A layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6. derived from an incomplete survey (OSMOpenStreetMap (OSM). A community-curated global map database (ODbL-licensed) with addr:* tagged features and place hierarchies. A secondary corpus source and a source of street names. above all) must carry a layer_coverage table: per-H3H3Uber's hexagonal hierarchical geospatial indexing system. Mailwoman uses H3 cells at resolution 9 (~0.03 km²) for geo-first blocking in the matcher and for stable address primary keys in @mailwoman/address-id.-cell (coarse resolution) survey completeness, so a consumer can tell "mapped and absent" apart from "unmapped."

columnmeaning
h3_cell48-bit short H3 cellH3Uber's hexagonal hierarchical geospatial indexing system. Mailwoman uses H3 cells at resolution 9 (~0.03 km²) for geo-first blocking in the matcher and for stable address primary keys in @mailwoman/address-id. at the resolution the manifest declares
completenessestimated completeness of the source survey in the cell, 0..1
observed_rowsrows this layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6. actually holds in the cell

The meaning-of-zero rule. A missing layer_coverage row means UNKNOWN — unmapped, never "surveyed and found empty." readLayerCoverage returns undefined for an unsurveyed cell, and callers must not conflate that with { completeness: 0 }. Absence of a row is never evidence by itself. This is why the field is contract-mandatory from day one even for layerslayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6. that happen to be complete today: retrofitting epistemics onto a sealed artifact means rebuilding every one of them. See the meaning of zero for the general principle (a magnitude never carries its own absence — split presence from value).

Scorers built on layerslayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6. inherit the rule: they emit { claim, evidence_found, coverage_confidence }, never a bare score. A sparse-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. cell degrades a score toward "insufficient survey data," which is itself a legitimate output — not a silent zero.

The TypeScript API

@mailwoman/core/layers is the single seam. A layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6.'s build script and its readers both go through it; the domain tables are the layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6.'s own business.

import {
type LayerContractDatabase,
createLayerManifestTable,
createLayerCoverageTable,
writeLayerManifest,
readLayerManifest,
writeLayerCoverage,
readLayerCoverage,
COVERAGE_INSERT_BATCH,
} from "@mailwoman/core/layers"
  • DDLcreateLayerManifestTable(db) and createLayerCoverageTable(db) build the two tables through Kysely's schema-builder (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. is WITHOUT ROWID — small fixed-width rows probed by PK, the sweet spot). Intersect LayerContractDatabase into your layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6.'s own schema interface so a column added to one side is a compile error against the other.
  • Write (build-time, cold path)writeLayerManifest(db, manifest) inserts the single identity row and validates invariants (known tiertierInternal versioning of which label classes the model emits. Tier 1 is the coarse components (country, region, locality, postcode); Tier 2 adds venue, street, house_number; Tier 3 (future) would add attention, po_box, and POI venue subtyping. Historically called 'Stage 1/2/3' before the runtime-pipeline naming made that ambiguous., known freshness policy, at least one spine key) before it writes. writeLayerCoverage(db, cells) bulk-inserts 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., chunked at COVERAGE_INSERT_BATCH (5,000 rows/statement) to stay under SQLite's 32,766 bound-variable ceiling — a continental-scale res-6 cell count blows past that limit in one .values() call.
  • ReadreadLayerManifest(db) returns the parsed, camelCase LayerManifest (and throws on an empty, multi-row, or invalid table). readLayerCoverage(db, h3Cell) returns a CoverageCell or undefined — honor the meaning-of-zero rule on the undefined.

Validation runs at both ends deliberately: a hand-built or corrupted layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6. fails at open time, not three joins downstream.

Authoring checklist

Building a new layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6.? The contract obligations, in order:

  1. Define your domain schema as a Kysely schema module (createXTable co-located with the typed interface), following the house DB discipline — raw positional INSERTs for the hot bulk load, WITHOUT ROWID only for small PK-probed tables, FTS5FTS5SQLite's built-in full-text-search module (with BM25 ranking). Mailwoman uses it for prefix and name matching against the gazetteer. as raw DDL where needed.
  2. Intersect LayerContractDatabase into your schema and call createLayerManifestTable + createLayerCoverageTable.
  3. Pick a spine: carry every id space your source gives you, declare them in spine_keys, and key every domain row by at least one.
  4. Populate layer_coverage from your source's actual survey extent — one row per surveyed cell, completeness honest, unsurveyed cells left absent (not zero-filled).
  5. Write the manifest with real provenance: source + source_vintage + build_cmd + build_sha, the correct tier and license, and a caller-supplied created_at.
  6. Build to a temp path, verify, then swap into place under $MAILWOMAN_DATA_ROOT and seal 0444. Never mutate a shipped or live layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6. — rebuild it.

See also

  • POI layer runbook — the worked example: the poipoint of interest (POI). A named place that is not strictly an address — landmark, transit stop, venue, amenity, or franchise. Mailwoman tags these as venue and resolves them through the gazetteer..db build/verify/publish path, the Overture schema gotchas, the fixtures→smoke→full-scale verification ladder.
  • The meaning of zero — the general epistemics principle behind 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. rule.
  • Address-data sources — the source catalog the shipped layerslayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6. draw from.