Context map
The nouns billet's docs, tests and ADRs use, the contexts billet collaborates with and how each relationship is shaped, the live consumers of the Berth, and the questions this cycle left open. Vocabulary was settled on 2026-09-14 with ADR-0012; this page is where a new reader learns the terms and where the deferred decisions are listed until an ADR takes each one.
Vocabulary
| Term | Meaning |
|---|---|
| Host | A cloud VM that runs Workspace containers (ADR-0001). Not a role: a Host either manages Workspaces or does not (ADR-0004). |
| Workspace | One repository's devcontainer running on a Host, reachable through the Berth (ADR-0002). Not the repo, not the container, not the .devcontainer/ directory. |
| HostProvider | The one backend seam (Azure VM today). |
| Berth | The Workspace runtime contract billet publishes: sshd on the assigned loopback port, dev at uid/gid 1000 with passwordless sudo, the entrypoint's behaviors (host-key persistence, environment snapshot to /etc/environment, mount-target ownership repair, ~/.ssh ensured), and the berth=N startup line. Distributed as the files under templates/workspace/ (ADR-0012). Not the container, the image, devcontainer.json, or any Locker. |
| Berth version | Monotonic integer in templates/workspace/berth.version, independent of billet's SemVer. Starts at 1 with the 2026-09 cycle; earlier template revisions are pre-versioning. |
| Locker | One named compose volume persisting one tool's state under the login user's home, e.g. <service>_claude_home:/home/dev/.claude. Declared only in the consumer's compose file; nothing in an image or in billet's code declares one. ~/.ssh is Berth infrastructure, never a Locker. |
| Recipe | An opt-in auth-tooling/ pair: the CLI's install snippet (binary) and its Locker snippet (volume). Two parts, not three: the image-side mountpoint part is gone (ADR-0011 as amended by ADR-0013). |
| Facts contract | The five devcontainer.json fields billet reads (ContainerAccess → DevcontainerFacts: service, dockerComposeFile, workspaceFolder, remoteUser, postCreateCommand). Unchanged by this cycle (ADR-0002 §1). |
| Definition vs state | billet never writes a file Docker, Compose or the devcontainer tooling reads to build or create a container; it may write runtime state into a container it started (ADR-0014). |
| Directive hash | SHA-256 of a Berth file with comment lines dropped and continuations folded; the unit of drift. Defined in ADR-0012 item 5, computed by doctor (ADR-0015, later cycle). |
| Vacuity guard | A scanner that matched nothing across its whole input fails its own test, so a rotted pattern cannot pass forever (test_every_billet_owned_variable_uses_the_billet_prefix is the precedent). |
| Adopt | Reserved to ADR-0005's sense: billet uses durable infrastructure it does not own. Not copying a template or merging a recipe. |
Retired from prose in billet and in the shared toolchain image's repository: devbox (the
config.toml table key [hosts.devbox] and the gswa-devbox alias string keep working; the
word is no longer a concept and leaves billet's package metadata too — the pyproject.toml
keyword is dropped), Half A/B/C (a recipe has two parts), self-consumption drift
and canary (both named nothing that exists).
The five contexts
billet, the product repositories that run as Workspaces, the shared GenShift toolchain image
(ghcr.io/genshift-energy/devcontainer, built from the genshift-devcontainer repository),
the operator's dotfiles, and the operator ledger (~/.config/billet/config.toml). Relationships
are named in DDD terms; each row states what fixes the pattern.
| Upstream | Downstream | Pattern | What fixes it |
|---|---|---|---|
| Product repo | billet | Conformist behind an anti-corruption layer | The repo authors devcontainer.json on its own cadence; billet adapts through facts_from_json (access/container/compose_script.py) → DevcontainerFacts (JSONC stripped, paths re-rooted, postCreateCommand normalized, the object form refused with a named error). billet never edits the repo's compose file (ADR-0003). |
| billet | Product repo | Open Host Service publishing a versioned Published Language; the consumer conforms by copy | The Berth: templates/workspace/ plus the adoption guide. Until Berth 1 the language had no version and this row was a Shared Kernel replicated by hand; berth.version and the directive hash are what make it an OHS (ADR-0012). |
| Shared toolchain image | Product repo (image pinner) | Customer/Supplier over a well-versioned Published Language | The consumer's Dockerfile is one digest-pinned FROM line and its CI container: must match. Toolchain versions are pinned in versions.env, tested by verify-image.sh, propagated by Renovate. Image 2.0.0 (planned) extends verify-image.sh with Berth conformance: dev at uid/gid 1000, sudo -n, sshd, the baked sshd.conf, ~/.ssh dev-owned 0700. |
| billet | Shared toolchain image | Conformist, undeclared | The image implements the Berth's build-time half (dev at uid 1000 with passwordless sudo, openssh-server, billet's sshd drop-in, ~/.ssh) and says so; billet does not know the image exists. The image carries no Lockers: from 2.0.0 it pre-creates no credential directory with an install -d line, because the Berth entrypoint repairs mount-target ownership at start (ADR-0013). It does still ship a populated dev-owned /home/dev/.claude as a side effect of the Claude Code install, so that Locker is protected by copy-on-empty while ~/.config/gh is the one target the repair handles. Lockers exist only in consumer compose files. |
| Shared toolchain image | billet | Separate Ways, deliberately | billet builds its own reference Workspace from python:3.11-bookworm and must stay usable by a repository outside GenShift. No billet change may require the shared image. |
| billet | Dotfiles | Open Host Service + Published Language, deliberately unvalidated | Three @billet_* tmux options, closed at three, with a stated test for a fourth (ADR-0008, ADR-0009). The healthiest relationship in the map. |
| Dotfiles | Product repo and image | Shared Kernel by convergence, two invocation owners | The image bakes chezmoi but never runs it; the pull happens through billet's global personal_bootstrap_cmd and each repo's postCreateCommand. See the lifecycle-hooks question below. |
| Operator ledger | everything | Unmodeled | config.toml is the sole port ledger and the home of every per-Workspace hook, under no CI and in no repository. From billet 0.4.0, which exports BILLET_AUTHORIZED_KEYS from the same remote-script prelude as BILLET_CONTAINER_SSH_PORT (ADR-0003), the per-Host .devcontainer/.env half of the ledger is gone; the config.toml half remains. |
Where each Berth behavior comes from, file by file, is the table in ADR-0012 item 1. What billet reads and writes across every one of these boundaries is ADR-0014.
Live consumer inventory
Verified 2026-09-09 by direct query of each remote. All three live consumers were current on
the 2026-09-08 templates (the last pre-versioning revision); each moves to Berth 1 by copying
dev-entrypoint.sh and berth.version from billet main once billet 0.4.0 ships.
| Repo | Builds from | Berth | Lockers mounted |
|---|---|---|---|
| genshift-brand | shared image devcontainer:1.0.2@sha256:05e6807… (one-line FROM) |
pre-versioning, current | genshift-brand_claude_home, genshift-brand_gh_config (plus -sshd-keys, Berth infrastructure) |
| squadra | own Dockerfile from python:3.11-bookworm |
pre-versioning, current | squadra_claude_home, squadra_gh_config (plus squadra-sshd-keys) |
| gswa-backend | own Dockerfile from python:3.12-bookworm |
pre-versioning, current | claude_home, azure_home (plus sshd keys); its 127.0.0.1:2222:22 port line is grandfathered by ADR-0003 |
| billet (reference Workspace) | own Dockerfile | Berth 1 from billet 0.4.0 | billet_claude_home, billet_azure_home, billet_gh_config |
| genshift-devcontainer | not a Workspace: its sshd.conf is a build input of the image |
n/a | none |
Existing consumer volume names stay grandfathered; the canonical <service>_claude_home,
<service>_gh_config and <service>_azure_home names apply to new adopters. The shared image's
2.0.0 release (drops Locker pre-creation) waits for genshift-brand to be on Berth 1, because a
fresh gh Locker mounted by a pre-Berth-1 entrypoint into a 2.0.0 image would stay root-owned
with nothing to repair it.
Open questions
Deferred on 2026-09-14, in the order they are likely to be taken up. Each needs its own ADR or an amendment before code. Updated 2026-09-29: questions 2 and 6 are closed, 4 is partly implemented, and 5 is deferred again.
gh_token_cmd(N4). DeliverGH_TOKENthe way ADR-0006 delivers the Claude token, which would remove theghLocker entirely. Needs an ADR-0011 amendment distinguishing reading a credential store (which billet never does) from delivering an operator-supplied token; the token must not travel through the world-readable/etc/environment.- Lifecycle hooks. Closed (2026-09-29). billet runs
postCreateCommandand the operator-globalpersonal_bootstrap_cmdon everystart, which already covers whatpostStartCommandwould, and adopts no further lifecycle keys (onCreateCommand,updateContentCommand,postStartCommand,postAttachCommandstay unread). ADR-0002 §1 is unchanged (A2 dropped, 2026-09-29). - Berth baked into the shared image (ADR-0016). The entrypoint,
sshd.confand stub live in the image and the consumer's compose points at them (entrypoint: ${BILLET_ENTRYPOINT:-…}), with a Berth-version OCI label. Precondition: the shared image's ADR record states that Docker, not billet, reads those files from the repo (its ADR-0001 says billet reads them; its ADR-0002, planned for the 2.0.0 release, corrects that). Must not break Separate Ways: a non-GenShift consumer keeps copying files. doctorimplementation (ADR-0015). Berth drift implemented (2026-09-29): the pureberth_policyengine, theDoctorAccessseam (one sectioned SSH probe per Host) and theBerthStatus/BerthFileStatuscontracts. Runtime report implemented (2026-09-29): in the same SSH session,docker compose psfinds each service's running container anddocker logsgives the entrypoint's lines. The pureruntime_policyengine turns them into aRuntimeReport: the running Berth against the checkout's stamp, and the Locker-ownership repairs and warnings.doctornever execs into a container. The compose-file read ADR-0002 §1 would grant todoctorstays proposed: no check opens a compose file yet.- Start-time CONFORM readback and compose preflight. Neither a
statof Locker ownership appended to thestartscript nor adocker compose config -qpreflight for undeclared volumes ships in 0.4.0. Both aredoctorchecks first;startmay reuse the read verb afterwards. Deferred again (2026-09-29): nothing has bitten since 2026-09-14, and the readback would reuse the read verb ofdoctor's runtime-report slice. The compose scans leftdoctortoo (ADR-0015 item 2): the deprecated-name scan returns at the next variable deprecation, the undeclared-volume preflight if an undeclared volume ever reaches astart. - Readiness marker. Closed (2026-09-29): dropped. The race it guarded is closed by the
writer-ensures-target rule of ADR-0013 item 6, which holds on every Berth revision;
startnever waits; anddoctorruns long after readiness, so a marker would report nothing a human needs.