Describe(): docs and gates from code
Describe() exists because hand-written module docs rot. doze’s original
hand-authored metadata drifted badly enough to document config blocks that no
longer existed — so now everything user-facing generates from the driver,
and the release tool refuses to package a module without it.
What generates from it
Section titled “What generates from it”| Surface | From |
|---|---|
| The module’s registry page (config tables, example, tagline) | Describe() → meta.yaml |
doze modules docs <type> in the terminal | same meta.yaml |
The signed index’s engine-support gate (releases.<v>.engines) | Describe().Versions |
The catalog (doze modules search rows) | title/tagline/category/versions |
That third row is the one with teeth: the versions you claim become the
machine-enforced compatibility contract. Claim {"14"…"18"} and a user
declaring version = 19 is stopped at lint with the upgrade message —
before your code runs against an engine you never tested.
The shape
Section titled “The shape”func (Driver) Describe() engine.Description { return engine.Description{ Title: "PostgreSQL", Tagline: "Real local Postgres, declared not scripted.", Category: "database", // database | cache | queue | storage | workflow | other Port: 5432, Versions: []string{"14", "15", "16", "17", "18"}, // the engine-support gate Source: "doze/postgres", Example: `postgres "app" { … }`, // a complete, runnable block Config: []engine.ConfigArg{ {Name: "owner", Type: "string", Desc: "Owner role for the default database."}, {Name: "io_method", Type: "string", Since: "18", Desc: "Asynchronous I/O method."}, // renders an "engine ≥ 18" badge }, Blocks: []engine.ConfigBlock{ {Name: "role", Label: "name", Desc: "A login role, converged on the server.", Args: []engine.ConfigArg{ … }}, }, }}Conventions that matter:
Versionsare engine majors you actually support end to end (Resolve + converge + tests). Versionless engines (self-contained servers) leave it empty — no gate.Since/Untilon an argument must have a matchingengine.RequireVersioncheck inDecodeConfig— badge and gate from one intent. Reviewers check them as a pair.- The example must run as written. It’s the first thing every user pastes.
The drift guard
Section titled “The drift guard”Keep the template’s test — it fails when Describe() and your decode schema
diverge, in either direction:
func TestDescribeMatchesConfig(t *testing.T) { documented := …from Describe().Config… for _, want := range []string{"root", "owner", …} { // keep in sync with Config struct if !documented[want] { t.Errorf("decoded but undocumented: %q", want) } }}Cheap insurance: the moment a PR adds a config field without documenting it
(or documents fiction), CI says so — which is the entire reason users can
trust doze modules docs over a README.