diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..7567e93 --- /dev/null +++ b/CLAUDE.md @@ -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.