1# The enrich lane23> **Historical creation path.** Native authoring now observes and assembles4> configured sources directly; see [authoring.md](authoring.md). The maturity5> measurement remains in use, while legacy enrichment is retained for fixture6> migration and regression coverage.78Enrich makes volumes richer. It takes a volume the [generate lane](generate.md)9composed — or the several readings of one volume that several sources produced —10and writes a **new build of the same volume**: same slug, new stamp, more in it.1112```13readings of one volume ──▶ [ merge → national → stdicons → lenses ] ──▶ a richer build14 generate, one per source the curated queue beside the old one15```1617Five laws hold everywhere in this lane.18191. **A volume in, a new build of the same volume out.** Same slug, new stamp.202. **Nothing published is ever mutated.** The registry is a directory of21 immutable files and a fold over them, so publishing is a new file landing22 beside the old one.233. **Nothing a source said is removed or rewritten.** An enricher fills what is24 empty. It has no way to express anything else — see §3.254. **Every contribution is ledgered**, and a gate audits the ledger before a26 build is written.275. **No change, no build.** An enrichment that finds nothing to say leaves the28 library exactly as it was.2930This document is normative for the enricher interface, the contribution format,31the ledger vocabulary, the curation data, and the scoring table. It is the prose32twin of `internal/enrich`.3334---3536## 1. The seam: how `generate ⊕ enrich` is joined3738The two pipeline lanes never import each other (issue #5 §3.2, enforced by39`tools/depcheck`). Generate owns the interchange document; enrich owns its own40model of a volume under enrichment. Both carry the same information, because41both are shaped by what the format needs.4243The ⊕ is performed by the one binary that holds both:4445```46atlas enrich47 │48 ├─ generate: archive ──▶ doc.Document, one per source reading49 ├─ cmd/atlas: doc.Document ──▶ enrich.Volume (adapt.go, a copy)50 ├─ enrich: the curated queue runs, contributions are applied51 ├─ cmd/atlas: enrich.Volume ──▶ doc.Document + ledger (adapt.go, a copy)52 └─ generate: compose.Compose(document, ledger, revision) ──▶ a .atlas file53```5455**Why this design and not the alternatives.** Three shapes were possible:5657- *Enrich composes.* Rejected: enrich would import `generate/compose`, which the58 lane matrix forbids, and the forbidding is not bureaucratic — it is what keeps59 either lane replaceable without the other.60- *Enrich emits doc-level additions the CLI feeds back.* This is what happens,61 but the additions are expressed in **enrich's own vocabulary**, not the62 document's, so an enricher never learns the document schema.63- *A shared model in `format/`.* Rejected: the volume-under-enrichment is not a64 format concept. The format describes a written bundle; this model describes a65 build in progress, including a grid to measure distances in and a ledger that66 is not final yet.6768The adaptation is a mechanical copy in both directions and is held to identity69by `TestAdaptationRoundTrips`: a document adapted and adapted back is the same70document, byte for byte. That test is also the mechanical form of law 5 — an71empty queue cannot produce a different build, because it cannot produce a72different document.7374The ledger does not travel through the document. A document is what **one75source** has to say, and a ledger is provenance about a composition, so the76accounts reach the payload through `compose.Options.Ledger` — already77serialized, spliced in verbatim. Composition never learns what a matched pair or78a held feature is, which is what lets the ledger vocabulary below belong wholly79to this lane.8081**The seam has a second half, upstream.** A warped lens — one source's raster82resampled into another's world — is derived by `atlas tiles`, in the generate83lane, and its one input the archive does not hold is the alignment this lane84fits. So `cmd/atlas/warp.go` fits it here and hands the deriver six numbers,85exactly as `adapt.go` hands the queue a volume. Nothing is written twice: the86deriver needs a fitted transformation, not the ability to fit one, and it is the87*same* fit the merge folds features by — which is why a warped raster and the88features drawn on it can never disagree about where the ground is. See89`docs/generate.md` §4.4.9091---9293## 2. Winning the registry fold9495An enriched build has to be the one a library serves, deterministically, without96mutating anything.9798The registry's ordering is **creation time, then policy revision, then stamp,99then locator** (see [format.md](format.md)). Creation time is the newest capture100the build was made from and never the build clock — a format invariant this lane101does not touch. An enrichment of the same captures therefore ties on creation102time with the plain build beside it, and the revision has to decide.103104**The mechanism.** The revision is one integer carrying two policy numbers:105106```107revision = enrichPolicy × RevisionSpan + generatePolicy RevisionSpan = 100108```109110A plain single-source build writes its generate policy revision alone, which is111the same number with an enrich policy of zero. So `9` is generate policy 9112unenriched, and `109` is generate policy 9 enriched under enrich policy 1.113114Three properties made this the choice:115116- **Deterministic.** The number is a pure function of two compiled-in117 constants. Nothing scans the library, nothing reads a clock, and the stamp —118 which covers the manifest, revision included — stays a pure function of the119 inputs. A mechanism that read the serving build's revision off disk and added120 one would have made the stamp depend on which machine built it, which breaks121 the format's first invariant.122- **Total.** Every enriched build of one capture outranks every plain build of123 that capture, with no tie for the stamp to break arbitrarily.124- **Honest about which axis wins.** Packing two axes into one integer makes one125 dominant, and this picks the enrich axis: within one set of captures an126 enriched build is a *superset* of the plain build, so serving the plain build127 over it would lose data the library already holds. A generate policy change128 takes effect for an enriched volume when the pipeline is re-run — which is how129 a policy change reaches a merged volume anyway, because enrichment is a130 pipeline stage and not a separate ritual.131132`BuildRevision` refuses a generate revision that does not fit the span rather133than wrapping into the next enrich band. Widening the span is a deliberate134restamp of every enriched build, not an accident.135136---137138## 3. The `Enricher` interface139140```go141type Enricher interface {142 Name() string143 Declares() []string144 Enrich(v *Volume, ctx Context) (Contribution, error)145}146```147148- **`Name`** is how curation orders it and how a log line names it.149- **`Declares`** lists the semantic-conventions keys this enricher may write.150 An attribute it writes that is not declared here fails the build, so what an151 enricher can say about a volume is visible without reading it. A declared key152 the registry does not know fails the build too.153- **`Enrich`** reads the volume and returns additions. **It must not modify the154 volume**: the driver applies contributions, so every change goes through the155 ledger and the gate. The merge enricher, which needs to see the effect of one156 donor before folding the next, works on a clone of its own.157158`Context` is everything else an enricher may consult, and every field is data159the caller supplies: the donor readings, the evidence base, the lens offers, the160curation tables, the run's logger. An enricher never opens a file, never reaches161the network, and never learns which source anything came from except through a162ledger it is handed.163164### 3.1 The contribution, and its canonical form165166A contribution is a flat, ordered list of operations, and it has a **canonical167serialized form** — that form is the contract, and the Go value is a convenience168over it. It can be written to a file, diffed between two runs, replayed against169the same volume years later, or read by a reviewer who wants to see what an170enricher would do before it does it. `Digest()` is its SHA-256, which is what a171log line names a contribution by.172173```jsonc174{175 "contribution": "atlas-enrich-contribution",176 "version": 1,177 "enricher": "merge",178 "volume": "cyberpunk-2077",179 "ops": [180 { "op": "set-prose", "world": "night-city", "feature": 123, "value": "…" },181 { "op": "set-attr", "world": "night-city", "feature": 123,182 "entity": "feature", "key": "atlas.geo.lon", "value": "-1.5" },183 { "op": "add-feature", "world": "night-city", "collection": 90,184 "newFeature": { … } },185 { "op": "add-collection", "world": "night-city", "newCollection": { … } },186 { "op": "add-world", "newWorld": { … } },187 { "op": "set-icon", "world": "night-city", "collection": 90,188 "key": "std--maki-monument", "value": "std--maki-monument.svg" },189 { "op": "add-asset", "asset": { "key": "…", "file": "…", "data": "<base64>" } },190 { "op": "set-lens", "world": "night-city", "lens": { … } },191 { "op": "ledger", "world": "night-city", "account": { … } }192 ]193}194```195196**The vocabulary is deliberately additive.** There is no operation that removes197a feature, empties a collection, or deletes a world, so law 3 is not a rule an198enricher has to remember — it is a property of what an enricher is able to199express. The two operations that touch existing data refuse to overwrite:200201| operation | what it refuses |202| --- | --- |203| `set-attr` | a key already set to a different value; an unregistered key; a key on the wrong entity; a value outside its vocabulary |204| `set-prose` | a feature that already has prose |205| `add-feature`, `add-collection` | an identifier already spoken for in that world |206| `add-world` | a slug the volume already pictures |207| `set-icon` | a collection that already names artwork |208| `set-lens` | repointing an existing lens at another tile set (a re-derivation of the same tile set updates in place, which is how a new derivation stamp lands) |209| `add-asset` | a file name already carried with different bytes |210211A refused operation fails the whole application: half an account is not an212account.213214### 3.2 The ordered queue215216Enrichers run in an order declared in `internal/enrich/curation/curation.json`,217never in an order that emerges from a map iteration or a registration side218effect. `enrich.Queue` is strict both ways — a queued name nothing implements219fails, and an implemented enricher nothing queues fails — because an enricher220running in an order nobody wrote down is folklore.221222The order today, and why:223224| # | enricher | why here |225| --- | --- | --- |226| 1 | `merge` | first, so everything after sees the whole volume: a collection folded in from another reading wants an icon and a score like any other |227| 2 | `national` | after merge, so a feature another reading contributed can also learn which subwatershed its ground lies in |228| 3 | `stdicons` | after merge — the whole reason it is post-composition: a merged-in collection that declares a standard icon resolves it too |229| 4 | `lenses` | last, because a picture attaches to a ground and the grounds are not all known until everything that could contribute one has run |230231Each enricher sees the volume as the ones before it left it.232233### 3.3 The evidence ethic234235**Silence over plausibility.** An enricher that cannot be sure says nothing and,236where the doubt is about a particular thing, writes down what it was unsure237about. A held feature with its reason recorded costs a curiosity; a wrong claim238costs a place, and a reader cannot tell a plausible claim from a true one.239240There is exactly one deliberate exception, `stdicons`: a standard-icon241declaration the vendored library cannot answer **fails the build**. The242difference is who made the promise. A membership join guesses about the world; a243standard-icon declaration was written by a translator author in this repository,244and an unanswerable one is a typo that should be heard about while it is one245table edit old.246247---248249## 4. The ledger250251Every contribution to a world is written down in one vocabulary, whatever made252it. The vocabulary is five words, on purpose: a ledger nobody can hold in their253head is a log, and a log is not an account.254255| word | meaning |256| --- | --- |257| **matched** | the world already held this, and the two are one thing |258| **added** | the world did not hold this, and now does |259| **adopted** | an added thing joined a collection the world already had |260| **held** | the contribution could not decide, and says why |261| **rejected** | the contribution refused this outright, and says why |262263Each world carries one **origin account** — where its ground came from and what264it held when it got there — plus one account per contribution folded in since.265The origin account is opened before the queue runs, because after an enricher266has run there is no way to find out what the world arrived with.267268```jsonc269{270 "source": "IGN Wiki", "slug": "ign-wiki",271 "donorFeatures": { "point": 368, "path": 0, "area": 0 },272 "matched": [ { "d": 2123771580, "w": 1452480306, "px": 20,273 "e": true, "took": ["atlas.note.text"] } ],274 "added": 37,275 "adopted": [ { "d": 2041587856, "into": "clothing-vendor" } ],276 "held": [ { "d": 1775561692, "t": "Arroyo Clothes Shop",277 "why": "beside \"Clothing Vendor\" in the same category; names disagree" } ],278 "rejected": [],279 "alignment": "99 anchors, median 26.0px, p90 52.0px, worst 67.4px"280}281```282283The key order above is the wire order and it is frozen. An account rides a world284payload, a world payload is a stamped part, and re-spelling one restamps every285build in every library — so the order is part of the format's surface rather than286a Go struct's declaration order that happens not to matter.287288The held and rejected reasons are **data, not messages about code**: they are289written into every merged bundle already published, and a reader comparing two290builds' accounts compares these strings. Their vocabulary is one rewrite behind291the rest of the lane — a feature is a "pin", a collection a "category" — and292that is the price of not rewriting what every existing bundle says about itself.293294### 4.1 The gate295296`GateAccount` and `GateWorld` fail the build rather than let a bundle be written297that quietly lost something — or quietly agreed too much:298299- every offered feature of every kind is accounted for: matched + added + held300 points + rejected must equal the offered points, and every offered shape must301 appear in the held ledger (shape features do not merge yet);302- every match is one-to-one — a serving feature matched by two donors is a place303 counted twice;304- every attribute a take claims answers to the conventions registry, and the305 older `enriched` flag says exactly what the takes say;306- one identifier space per world across every kind;307- the world holds exactly as many features of each kind as its accounts claim:308 what it opened with, plus what every account says it added.309310---311312## 5. The enrichers313314### 5.1 `merge` — two readings of one volume315316The newest capture serves; the others are folded into it.317318Each donor world is aligned into the serving world by the transformation their319shared named places determine (`internal/enrich/align`): pairs formed only from320names that are unambiguous in **both** readings, at least 12 of them, solved by321least squares per axis, trimmed twice to nine tenths to shed the places one side322simply pinned sloppily, and **refused outright** when the median residual still323exceeds 96px. A fit that will not close is a merge that does not happen; the two324readings stay apart in separate builds, which is exactly where they were before325anybody tried.326327A donor world with no counterpart by slug is tried against every world the328volume already draws — sources do not divide the world the same way, and a329shared slug is only the cheapest evidence of shared ground. A world nothing330pictures joins whole, artwork and all, opening its own origin account.331332Resolution, per donor feature, in world pixels of the volume's own square:333334| distance to the nearest same-named serving feature | outcome |335| --- | --- |336| ≤ 160px (`matchRadiusPx`) | **matched** — the same place |337| ≤ 320px (`separateRadiusPx`) | **held** — too far to merge, too near to double |338| beyond, or no such name | fall through |339340A name one reading spells inside the other's — "Northside Apartment" inside341"Northside, Watson Apartment" — counts as the same name when the two share a342collection **and** the shorter name carries at least two words: a bare343"Apartment" must not roam the world for a long-named cousin. A serving feature344another donor already matched cannot be matched again. Falling through, a345donor feature within `max(48px, 2 × the fit's p90)` of a serving feature in the346same collection is **held** — proximity alone never merges. A feature the347alignment puts outside the world square is **rejected**. Everything left is348**added**: into the serving collection where the two spell the same concept349(**adopted**), and otherwise under a collection of its own, filed under a group350named for its source.351352**Carried artwork** travels under two names on two rules, because they answer353different questions. The *file* the bytes land in always takes the source's tag354when a contribution goes into a ground the serving reading already draws: a355donor's picture may never be written where the serving volume keeps its own. The356*key* a collection names its artwork by stays the donor's own — there is nothing357to displace unless the serving volume already names different artwork with it,358and only then is the key tagged too.359360Collections meet under their **merge identity**: the `atlas.collection.key` the361payload carries, then the key the source gave the collection, then its artwork362key. The curated equivalents that produce it live in the generate lane's363curation, and curation speaks them over a reading *before* it is folded into364another — `curation.Tables.Declare`, called by the ⊕ right after translation.365Composition declares the same thing later for a document that never passed366through the enrich lane, but later is too late for a merge: a document whose367equivalence had not been declared yet would be merged on artwork keys alone,368which is the honest fallback for a source nobody has curated and quietly the369wrong answer for one that has been.370371**Per-key attribute policy.** Serving-wins is the default: what the serving372reading says stands. The keys listed under `merge.attributes.donorFillsEmpty` in373curation are *donor-fills-empty* — the serving side keeps what it has and takes374only what it lacks — and every take is ledgered by key:375376| key | why |377| --- | --- |378| `atlas.note.text` | a description; the rule this policy was invented for |379| `atlas.geo.lat`, `atlas.geo.lon` | true coordinates fill in the same way words do |380| `atlas.icon.std` | a serving collection with no artwork of any kind takes the donor's declaration |381382### 5.2 `national` — the hydrologic membership join383384A city's zones and trails are curated by the city; the subwatersheds under them385are surveyed by the USGS. Where a capture carries both, each piece of the city's386ground can be told which subwatershed it lies in — one sentence for the card and387the twelve-digit code beside it as `atlas.hydro.huc12`.388389Fetching stays in the generate lane (fetching is crawling). **The evidence base390travels in the capture**, so the join re-runs against the archive without391refetching, which is what lets the sentence be re-curated. The evidence reaches392the enricher through `Context.Evidence` under the name `hydro/huc12.json`:393394```jsonc395{396 "evidence": "atlas-enrich-evidence",397 "version": 1,398 "kind": "hydro.huc12",399 "space": "world", // the volume's own projection — the only space v1 reads400 "units": [ { "code": "170703010801", "name": "Deschutes Junction",401 "rings": [ [ [ [lng, lat], … ] ] ] } ]402}403```404405`space` is `world` because a claim is decided in the space the volume's features406are in, and whoever writes the evidence has already projected the survey into407the window it was clipped to. An evidence base that carries nothing about a408volume is not an error: it is a volume nobody surveyed, and the enricher says409nothing about it.410411**The judgement.** Each piece of a feature's geometry volunteers an anchor — the412centroid of its largest outer ring, or the middle vertex of its longest line —413plus an even sample of up to 16 boundary vertices. The claim is made **only when414every volunteered position lands in the same unit**. A trail that crosses a415divide says nothing; a zone whose boundary leaves the surveyed extent says416nothing. The anchor alone would let a citywide polygon claim whichever417subwatershed its centroid happened to sit in; sampling the boundary catches the418spread, and the cost stays flat however detailed the ring.419420The survey makes no claims about itself: `national.evidenceCollections` in421curation names the collections a national capture contributed to a volume, and422the join never writes a membership onto one of them. Point features are not423joined. A feature that already carries the key is left alone, and a feature that424already has prose keeps it — the claim still lands as the key, because the425sentence is a courtesy and the code is the knowledge.426427### 5.3 `stdicons` — the standard glyph library428429A collection that ships no artwork may name one from a standard set430(`atlas.icon.std = "maki/monument"`). This resolves the promise to bytes: the431glyph lands in the volume's icon set as `std--maki-monument.svg`, a name that432spells its provenance so a bundle listing reads honestly and a source's own433artwork can never be shadowed by it. The application never learns the library434exists — a resolved standard icon is one more asset in a bundle's icons tree.435436It runs after merge so merged-in collections resolve too. Only point437collections, only where the slot is empty, one asset however many collections438name it. Only the names some translator actually speaks are vendored; the439embedded set is the vocabulary, and `Vocabulary()` lists it.440441### 5.4 `lenses` — a picture attaches to a ground442443A world is a ground; a lens is a picture of it. The two are separable, and that444separation is what makes this an enricher: a ground published years ago can be445given another picture — a second map style, a later capture, somebody else's446raster warped into this world's space — without the ground changing and without447anything published being touched.448449This enricher does not derive pyramids. Deriving rasters is the generate lane's450work, and the derivation stamp a pyramid carries is written there. What arrives451here through `Context.Lenses` is the **offer**: these pictures were derived for452this ground, each with the stamp that says what it was made from. The enricher453attaches what the world does not already carry, updates in place what was454re-derived under a new stamp, and says nothing about a picture that has not455moved. `atlas enrich -lenses FILE` supplies the offers:456457```jsonc458{ "offers": { "night-city": [ { "name": "IGN Wiki", "tileSet": "aligned-night-city",459 "stamp": "…", "alignedWith": "cbp" } ] } }460```461462Aligned pyramids that the tile set already files under a world's own tile set463are attached by composition itself; the offer file is for pictures nothing has464claimed.465466---467468## 6. The feature-maturity score469470The score replaces bundle maturity. It is **unbounded, additive, and monotone**:471every feature earns points for each quality it verifiably has, and those sums472roll up feature → collection → world → volume.473474**No denominators, no ceilings.** A share moves when its denominator moves, so a475build that added five hundred features and described half of them could read as476a regression. Percentages survive only as diagnostics, and where a diagnostic477prints a share it divides by every feature rather than by the point features478alone, so a share cannot exceed its whole.479480The table is versioned data (`internal/enrich/maturity/points.json`, v1). A481re-weighting is a **new version**, not a mass failure.482483### 6.1 Point table v1484485| earner | quality | points |486| --- | --- | --- |487| feature | a name | 1 |488| feature | prose at all | 1 |489| feature | prose longer than 140 characters | +1 |490| feature | true coordinates, both axes | 1 |491| feature | each membership attribute (`atlas.hydro.huc12`, and the joins after it) | 1 |492| feature | its collection resolves artwork | 1 |493| feature | geometry: `floor(log2(1 + vertices))` | ×1 |494| feature | each other reading that pinned this place, from the merge ledger | 1 |495| collection | each registered semantic-conventions key it declares | 1 |496| world | each registered world-level key it declares | 1 |497| world | each lens | 1 |498| world | each zoom level a lens holds | 1 |499500Geometry is log-scaled because ground that is actually drawn is worth more than501ground that is merely claimed, while one over-noded polygon must not outweigh a502city. Corroboration is scored because two sources agreeing is evidence, and503evidence is quality. A private attribute earns nothing: the score measures504adoption of the *shared* vocabulary.505506### 6.2 The monotonicity gate507508An enrichment build whose score declines fails.509510- Two builds are compared **only under the same table version**. Across versions511 nothing is concluded.512- A decline is permitted up to what the later build's ledger accounts for in513 `corrections`. That is the one shape a decrease may take, and it has to be514 written down. The gate exists to reward good data and never to punish the515 removal of data that was wrong.516517### 6.3 The five axes, as diagnostics518519Annotation, cartography, structure, icons and conventions are the score's520breakdown. They are absolute measurements of521one build, never ranks within a library, and nothing gates on them. `atlas522measure` prints the score first and the axes beneath it, with every merge ledger523the payloads carry.524525---526527## 7. Curation528529`internal/enrich/curation/curation.json`, embedded, so a build carries its own530curation and an enriched volume cannot depend on what was on the operator's531disk. Every section carries its own prose as a sibling key.532533```jsonc534{535 "schema": 1,536 "queue": { "order": ["merge", "national", "stdicons", "lenses"] },537 "merge": {538 "matchRadiusPx": 160, "separateRadiusPx": 320, "nearbyFloorPx": 48,539 "attributes": { "donorFillsEmpty": [540 "atlas.note.text", "atlas.geo.lat", "atlas.geo.lon", "atlas.icon.std" ] }541 },542 "national": { "evidenceCollections": { "bend-or": ["Watersheds", "Subwatersheds",543 "Streams", "Waterbodies"] } }544}545```546547The reader refuses a separate radius that is not larger than the match radius —548nothing would ever be held back — and an attribute policy for a key the549conventions registry does not know. The one deliberate exception is550`atlas.note.text`, which is registered nowhere because no payload carries it: it551is the name a description travels under so that "which description wins" is552decided and recorded in the same vocabulary as everything else.553554---555556## 8. The CLI557558```559atlas enrich -archive DIR -tiles INDEX [-bundles DIR] [-evidence DIR]560 [-lenses FILE] [-n] [volume...]561atlas measure [-bundles DIR] [-json] [volume...]562```563564`enrich` groups every reading in the archive by the volume it is a reading of,565picks the newest capture to serve, folds the rest in, and composes. A volume the566queue had nothing to say about is not rebuilt and says so at `info` — the line567reads `nothing to add`, and the run ends `unchanged=1` rather than writing a568build. That is law 5 as an observable, and it is what makes running this over a569whole archive cheap. `-evidence DIR` resolves as `<dir>/<volume>/<name>`.570571`-archive` and `-tiles` are the generate lane's inputs and are staged in the572working copy; [generate.md §7.1](generate.md#71-where-the-inputs-are) says573where. A single-source volume with no donor — `tunic` — is the shortest way to574see law 5; `cyberpunk-2077`, whose archive holds two readings, is the shortest575way to see a merge.576577`measure` scores every build in a registry, newest-per-volume marked as serving.578579---580581## 9. What is proven582583How the lane is judged overall is [testing.md](testing.md); every suite below584runs under `make test`, over stated bundles and the committed corpus.585586| claim | held by |587| --- | --- |588| the merge decides by name and distance, a place is matched once, and a refused alignment is a refused merge | `internal/enrich/enrichers/merge`, with the fit itself in `internal/enrich/align` |589| the membership join claims only what every sampled position agrees on, and the survey makes no claims about itself | `internal/enrich/enrichers/national` |590| a standard glyph resolves only into an empty slot, once, under a provenance-spelling name, and an unanswerable declaration fails the build | `internal/enrich/enrichers/stdicons` |591| a lens offer attaches what a world lacks and repoints nothing | `internal/enrich/enrichers/lenses` |592| the score is unbounded, additive and monotone; shares cannot exceed their whole; the gate permits a decline only up to accounted corrections | `internal/enrich/maturity` |593| contributions apply additively, refuse overwrites, and round-trip their canonical form; the gate audits every account both ways | `internal/enrich` |594| the queue curation and the enrichers the binary offers are the same set | `internal/enrich` (`TestQueueHoldsCurationAndTheBinaryToEachOther`) |595| the seam is a copy, not a decision | `cmd/atlas` (`TestAdaptationRoundTrips`): a document adapted and adapted back is byte-identical |596597The deepest composed output of this lane in the tree is the corpus city:598`testdata/corpus/bundles/bend-or` carries the national membership claims and a599resolved standard glyph, and the format, island and browser suites all read it.600601An enriched build's manifest revision — and therefore its stamp and file602name — differs by construction from a plain build of the same captures; §2 is603the mechanism, and604[decision 18](decisions/0018-divergences-from-the-reference.md) records it605among the accepted divergences from the archived reference.