docs: add CLAUDE.md covering the module and its mirrored contracts

This commit is contained in:
2026-09-08 08:43:44 +00:00
parent 66eed23f79
commit 02a6502f12
+88
View File
@@ -0,0 +1,88 @@
# vantage-shared
The private Go module every Vantage service imports. Extracted from the
`vantage` monorepo with its history.
```
vantage-shared/
├── grpc/pb/ # the agent↔control-plane wire types, hand-written
├── grpc/codec/ # the JSON codec they travel over
├── proto/vantage/v1/ # vantage.proto — documentation for the above
├── mail/ # the one email system: transport + templates
├── license/ # payload, sign, verify, trusted keys, plans
├── models/ # Instance, User, Settings
├── provision/ # slug rules, reserved names, instance/user creation
├── backup/ # dump, restore, verify, manifest, fingerprint
├── cryptobox/ # the single AES-256-GCM implementation
├── indexes/ # users.email and instances.slug
└── cmd/lkctl/ # issue and inspect licences by hand
```
Module path is `gitea.hostxtra.co.uk/vantage/vantage-shared` — **lowercase
`vantage`**, though the Gitea org is canonically `Vantage`. Gitea serves both
spellings; Go module paths are case-sensitive strings, and two spellings would
cache as two modules. Keep the lowercase one.
## Who depends on this, and when they find out
| Repository | Modules | A change here reaches them |
| ---------------- | ---------------------------------------- | ---------------------------------------------------- |
| `vantage` | `server`, `agent`, `vantagectl` | `server` at the next push to main after a pin bump; `agent` and `vantagectl` only at their next release tag |
| `vantage-admin` | `server` | at the next push to main after a pin bump |
| `vantage-site` | `server` | at the next push to main after a pin bump |
**Nothing bumps a pin automatically.** A fix here is live nowhere until each
consumer's `go.mod` moves, and nothing in any of those repositories will remind
you. That is the trade this split made: a service can no longer be shipped
against a version of this module it was never built against, and in exchange the
staleness is now silent rather than impossible.
Tag a release when you change anything: `git tag v0.2.0 && git push origin
v0.2.0`. Consumers pin exact versions.
## `proto/` is documentation, not a generator input
`grpc/pb` is **hand-written** JSON-tagged structs over the codec in
`grpc/codec`. Nothing generates them, and `vantage.proto` is not compiled by any
build. It is the readable statement of the wire contract, and it lives in the
same repository as the Go types precisely so that a message added to one can be
added to the other **in the same commit** — that co-location is the only thing
enforcing the match, so do not split them again.
There used to be two copies of `pb`, in the agent and the server, and they had
already drifted: the agent's `UnimplementedVantageServer` was three methods
stale and carried no `ReportWorkloads` at all. One package now serves both
sides. The agent links the server half as dead code, which the linker drops.
**A wire change lands in three steps, in this order**: release this module, bump
the pin in `vantage/server` (live at the next push to main), bump the pin in
`vantage/agent` (live only at the next `agent/v*` tag). The control plane will
be ahead of the fleet in between, which was true before too — it is just written
down in two `go.mod` files now instead of implied by a shared directory.
## Things that mirror something outside this repository
These cannot be enforced by the compiler and must be changed by hand, in step
with a file in another repository:
- **`backup.ciphertextFields`** names each collection's `*_enc` fields and
mirrors `server/internal/models` in the `vantage` repository, which this
module cannot import. Wrong field names fail **silently**: `verify`'s live
probe finds no ciphertext and reports "this database stores no ciphertext
yet", so the one gate that catches what a key fingerprint cannot becomes a
no-op. `settings` is deliberately in neither that map nor
`CiphertextCollections()` — its ESO read token is a SHA-256 hash, not
ciphertext.
- **`mail/templates/layout.html.tmpl`** carries the control plane's dark theme
values as **literal hex**. Email clients support neither `var()` nor a
reliable `prefers-color-scheme`, so the token indirection the four front ends
use is not available here. Every colour in the email system is in that one
file. The tokens it mirrors live in `vantage-site`.
- **`provision`** is the single implementation of the slug rules that both the
control plane and Vantage HQ depend on. It is the one place those two
repositories must agree on behaviour, which is why it is here rather than
copied — but it now also means a change to it is a release and two pin bumps.
`mail/render_test.go` renders every template and fails if one exists that no
case covers. The templates are parsed in `init()`, so without that test a
mistyped field is a boot-time panic in three services.