# berth — withdrawn assumptions **Everything in this file is no longer true.** It exists so the live docs stay short and so a withdrawn assumption cannot quietly return: each entry carries a **check**, and `ctrl/selftest.sh` runs every one of them. A retraction that is only prose is a retraction nobody re-reads. Kept rather than deleted for the reason the docgen thread already wrote down — *a requirement that disappears without explanation comes back.* ### For agents - **Do not restate these** in a plan, a README or a comment. One line pointing here is enough. - **Ids are stable.** Cite `✖ B3`; do not re-explain it. - **When you withdraw an assumption, move it here** — quoted claim, where it came from, what superseded it and why, what changed in the code, and a check that proves it is gone. - **Only withdrawn things belong here.** A warning that is still actionable is a live rule, however historical it sounds, and stays where it is. --- **✖ B1 — "Pulumi is the source in spr, and Terraform must match the config."** *(INDEX §5, carried from 35.3)* Withdrawn 2026-09-12. berth uses **OpenTofu and only OpenTofu**. The rule assumed *open source* and *industry standard* pull apart — Terraform being BUSL, Pulumi being the open alternative. OpenTofu is both: the standard language, plain Terraform-compatible HCL, under MPL-2.0. Swappability was never bought by keeping two renderings; it is bought by `estate/*.json` being the artifact — a rendering you *can* produce, not one you *must* maintain. **Gone from:** `ctrl/versions.env` (no `PULUMI_VERSION`), `ctrl/estate.sh` (`plan` runs one executor), `ctrl/lib/config.sh` (`PULUMI_STACK` → `TOFU_WORKSPACE`), `ctrl/check.sh` (toolchain list). **Deliberately kept:** `README.md` and `estate/mcrn.json` both record that `ppl/infra/` was written and never applied — *"no `~/.pulumi`, no venv, no stack state"*. That is a historical fact about the estate, not a live dependency. **Check:** no `pulumi` in `ctrl/` or `Makefile`. **✖ B2 — "ctlptl's rejection is the precedent for *the seam belongs in a script, not a tool*."** *(INDEX §5, `berth/README.md`)* Withdrawn 2026-09-12. **ctlptl was reinstated** — pinned in `rig/ctrl/versions.env` at v0.9.4 — and had been removed for the wrong reason. The rule may still hold; it now has to stand on its own reasoning rather than that example. **Gone from:** `README.md` — the argument is stated directly, with no borrowed evidence. **Check:** `ctlptl` appears nowhere in berth. **✖ B3 — "`wg show` is safe; `wg showconf` is not."** *(my own note, 2026-09-12)* Incomplete, and the gap is the dangerous one. There are **three** forms, and `wg show dump` puts the **private key in field 1 of the first line**. Stated as a two-way distinction, the `dump` form reads as safe. **Now:** `wg show` plain is safe; `dump` and `showconf` are not. `ctrl/vpn.sh capture` refuses the latter two **by shape**, rather than parsing around them. **Check:** `vpn.sh` names all three forms, and `capture` rejects both unsafe ones. **✖ B4 — "A roaming peer must set `PersistentKeepalive` on its own entry."** *(`ctrl/vpn.sh`, first draft)* Wrong side, and wrong in the direction that looks fine: `PersistentKeepalive` is set per-peer in a config, so the roaming machine sets it on the entry for the peer it **dials**. The original check would have **warned on a correctly configured overlay**. **Now:** checked once per overlay — if anything roams, some peer entry must carry a keepalive. **Check:** the invariant is not keyed on the roaming peer's own `keepalive` field. **✖ B5 — "The Makefile's pass-through block goes near the top, with the other variables."** *(`Makefile`, inherited from rig's layout)* Withdrawn 2026-09-12. When a subcommand **names a real target**, make has two recipes for it and the **last definition wins** — so with the block first, `make host ports` ran `ctrl/host.sh ports` *and* `ctrl/ports.sh ports`, the second failing because `ports` is not one of its verbs. Same for `make host services`, `make vpn check`, `make vpn show estate`. **Now:** the `$(eval $(ARGS):;@:)` block is **last in the file**, so the no-op wins and the word is swallowed — which is what an argument is. Make's *"overriding recipe"* warning is the swallow working. **Check:** every colliding invocation dispatches to exactly one script. **✖ B6 — "A base64 key in the description can be caught by its shape."** *(`ctrl/vpn.sh check`, first draft)* WireGuard **public** keys are also 44-char base64 and legitimately live in the estate, so shape alone proves nothing and would flag correct data. **Now:** two assertions instead — **no field named `priv*`**, and **no base64 key outside a `public_key` field**. **Check:** the estate's public keys do not trip the secret check. **✖ B7 — "`network.wireguard` is where the overlay is described."** *(`estate/mcrn.json`)* Superseded 2026-09-12: WireGuard is berth's network layer, not one service's transport, so it is a top-level `vpn` block with named overlays and peers. `ctrl/check.sh` and `ctrl/registry.sh` were repointed. **Gone from:** the estate schema — a `wireguard_moved` tombstone marks the old key. **Check:** nothing reads `network.wireguard.*`. **✖ B8 — "berth and rig are related through their first uses."** *(early framing)* Withdrawn: **rig and berth are peers — neither depends on the other.** They match on shape (dispatch, config layering, key names) and consistency is verified by **recomputation**, never by dependency. A shared library would put something outside `rig/` on rig's path. **Check:** berth imports nothing from rig; `ports.sh` recomputes the port formula and agrees with rig's golden values. **✖ B9 — "`langfuse.mcrn.ar` is an exception that cannot be generated."** *(`estate/mcrn.json`, `raw: true`)* Withdrawn 2026-09-14. It was filed as the one route a template could not express — a static `upstream{}` to a WireGuard address, with no `resolver` and no `set $var`. It was not an exception; it was **the first instance of the general case**. Those three properties are not three decisions, they are one: *this service is reached by address on the overlay, not by name on the docker network.* Naming that decision — `placement` — makes the file renderable. **Gone from:** `estate/mcrn.json` — `lng` and `langfuse` were **two entries for one socket** and are now one service with `placement: local`, `peer: nrft`, and a `local_host` for the name it answers to locally. `raw` is dropped. **Check:** the generated vhost matches the hand-written one, normalised for comments and whitespace — proof against a live route rather than an assertion.