Skip to content

Commit c00d695

Browse files
Syndicclaude
andcommitted
docs(meta): add the context glossary and the plumbing-contract ADR
Two artifacts the repo's documented structure called for but had never gained: meta/ is the first context with a CONTEXT.md, and the base image's contract is the first decision scoped to a single context rather than the repo. The ADR records what the code already did and the docs denied. The library reads host-git-common-path and host-timezone by name and links to /host-git-common; those are contract terms, so the README's claim that the stub 'does not constrain where the host keeps anything' was only half true. It constrains the names, not the sources. Syndic/.dotfiles is the evidence: presenting the same host state in a different shape let its copy diverge in kind rather than in detail. The glossary also disambiguates 'plumbing' from git's own plumbing/porcelain sense, which .git-plumbing/ maximises the collision with. The term stays -- it is apt in the ordinary sense and renaming it would reach the command, the env prefix, the function prefix, both repos' docs and an unadopted consumer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 512e332 commit c00d695

3 files changed

Lines changed: 103 additions & 2 deletions

File tree

CONTEXT-MAP.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@ it). Repo-wide decisions live in `docs/adr/`.
2727
Treat a request for anything under "planned" as unimplemented: a redundancy check that matched a
2828
request against this domain language would otherwise close a real one as already-done.
2929

30-
- **Repo meta**`meta/`
31-
_(`CONTEXT.md` not yet written)_
30+
- **Repo meta**`meta/` ([`CONTEXT.md`](meta/CONTEXT.md))
3231

3332
The monorepo's own automation: `meta/scripts/` (pre-commit checks, changed-path classification,
3433
Renovate proposal ratification and manual-job triggering, base-image pin sync) and

meta/CONTEXT.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Repo meta
2+
3+
The monorepo's own automation: the repo-health checks that enforce cross-cutting invariants, and
4+
the shared devcontainer base image that Syndic repos build their development environments on. It
5+
operates on the other contexts rather than shipping anything a user runs.
6+
7+
## Language
8+
9+
### Devcontainer base image
10+
11+
**Base image**:
12+
The devcontainer image this context assembles and publishes for Syndic repos to build on. It
13+
carries the container half of the plumbing and nothing else — no language toolchains, no features.
14+
_Avoid_: shared image, common image
15+
16+
**Consumer**:
17+
A repo whose devcontainer Dockerfile `FROM`s the base image. This repo is one; `Syndic/.dotfiles`
18+
is the other.
19+
_Avoid_: client, downstream, dependent
20+
21+
**Plumbing**:
22+
The bridging of host state into a container — the git common directory, the host timezone, the
23+
material a signed commit needs. Not git's plumbing/porcelain sense: nothing here is a low-level git
24+
command.
25+
_Avoid_: bootstrap, host integration
26+
27+
**Host stub**:
28+
The `initializeCommand` script a consumer keeps, which runs on the developer's host before any
29+
container exists. Its job is to present host state in the shape the container consumes, without
30+
dictating where the host keeps anything.
31+
_Avoid_: initialize script, host script
32+
33+
**Dispatcher**:
34+
The single command a consumer's lifecycle hooks call to apply every shared plumbing step for a
35+
phase. Being one command rather than a library is what lets a new step reach every consumer on its
36+
next base image bump, with no edit on their side.
37+
38+
**Contract**:
39+
The interface between a consumer's host stub and the base image: the file names, symlink names and
40+
values the stub must present, plus the destinations the plumbing writes to. Conforming to it is
41+
what adoption means.
42+
43+
### Repo health
44+
45+
**Check**:
46+
A guard that enforces an invariant no single language toolchain owns. Whether it blocks depends on
47+
where it runs — CI, pre-commit, or on-save in the editor — not on what it is.
48+
_Avoid_: gate, guard, linter, validator
49+
50+
**Derived file**:
51+
A checked-in file that is reproducible from other checked-in sources, so it is regenerated rather
52+
than hand-edited and its staleness is a defect a check can find. Distinct from a dependency, whose
53+
value comes from outside the repo and is bumped rather than derived — the same artifact can be one
54+
in this repo and the other in a consumer.
55+
_Avoid_: generated file, lockfile
56+
57+
**Marker comment**:
58+
A comment that tells Renovate which version string a line carries, where the file's own syntax
59+
can't. A marker no pattern claims is invisible rather than broken, which is the failure mode it is
60+
prone to.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# The plumbing contract includes the host-presentation shape
2+
3+
The shared devcontainer base image's contract with a consumer is not merely "call the dispatcher".
4+
It includes the shape the host stub must present: fixed file names under `.git-plumbing/`, fixed
5+
symlink names beside it, and the destinations the plumbing writes to. A consumer conforms to that
6+
layout. What stays free is where on the host each value is read from, which is what the symlink
7+
indirection buys.
8+
9+
## Considered options
10+
11+
### The steps only — rejected
12+
13+
Under this reading the image publishes behaviour, and each consumer presents host state however it
14+
likes. It is what `meta/devcontainer-base/README.md` implied by saying the stub "does not constrain
15+
where the host keeps anything".
16+
17+
The code never matched it. `plumbing_apply_git_common` reads `$PLUMBING_DIR/host-git-common-path`
18+
by name and links it to `/host-git-common`; `plumbing_apply_all` reads `host-timezone` by name.
19+
Those are contract terms whether or not they are written down as such. Leaving them undocumented
20+
means the next consumer discovers them by breakage rather than by reading, and `Syndic/.dotfiles`
21+
demonstrates the cost: it presents `known_hosts` and `allowed_signers` as snapshot files where this
22+
repo presents them as bind-mounted symlinks, so its copy of the plumbing diverged in kind rather
23+
than in detail.
24+
25+
### The steps plus the shape — accepted
26+
27+
The contract names the files, the symlinks and the destinations, and the README documents them as
28+
an interface. The host stub's remaining freedom is real but bounded: it chooses what each symlink
29+
points at, so a developer's `known_hosts` can live anywhere, but it does not choose the name the
30+
container looks for.
31+
32+
## Consequences
33+
34+
- Adoption is conformance, not integration. `Syndic/.dotfiles` moves to the documented shape rather
35+
than keeping its own; its snapshot-based handling is an artefact of age, not a variant to support.
36+
- A consumer that wants a different layout has one supported answer — change the symlink target —
37+
and one unsupported one. That is deliberate: configurability on this axis would reintroduce the
38+
divergence the shared image exists to remove.
39+
- The contract is versioned by the image digest like everything else, so widening it is a base
40+
image change that reaches consumers on their next bump.
41+
- What the image cannot carry stays outside the contract and unsolved: the host stub itself, the
42+
two bind mounts, and `containerEnv`. Tracked in #248.

0 commit comments

Comments
 (0)