lib/README.md

164 lines
9.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# go/lib — Shared Go library for the Keenan Vision service mesh
`go/lib` (module `code.tnxs.net/vernonkeenan/lib`) is the shared toolchain
every Go microservice in the Keenan Vision mesh pins in its `go.mod`. It is
not a service — it ships no server, no Dockerfile, no compose entry — it is
a library `app` package (`InitConfig`/`InitDB`, config
accessors, zap logging, Prometheus/CORS middleware, generic marshal
helpers) plus a set of go-swaggergenerated typed HTTP clients (`api/`)
against the sibling services' own swagger specs. Because every mesh service
pins the same module, a `lib` bump is a constellation-wide rebuild (KV-C6a).
## Constellation context
- Toolchain member of the Keenan Vision Go mesh governed by `../../kv-meta/`
(see `../../kv-meta/CONSTELLATION.md` §1.3, contract KV-C6). Not a
deployed service: no swagger server, no port, no entry in the
`docker/kvweb` compose on pluto.
- KV-C6a: every mesh service (`auth`, `cache`, `crm`, `members`, `pdf`,
`plex`, `render`, `research`, `sf-gate`, `stash`) pins an explicit
`code.tnxs.net/vernonkeenan/lib` tag in its own `go.mod`; consumption is a
per-consumer `go.mod` bump, never automatic.
- Distribution is via the world-readable Gitea pull-mirror at
`code.tnxs.net/vernonkeenan/lib`, enabling
credential-free `go get code.tnxs.net/vernonkeenan/lib@vX.Y.Z` from
Docker/CI/dev boxes even though the GitHub repo itself is private
(ADR-KV-014). Everything committed here is treated as **published,
permanently** — a gitleaks CI gate makes leaking a secret a build
failure, not a review nit.
- CI (self-hosted runner, `go build/vet/test ./...` + gitleaks on every
push/PR) landed 2026-07-12 as `lib`'s first-ever pipeline (ADR-KV-014) —
`lib` was the only one of the 12 Go-roster repos with no CI at all going
into the 2026-07-12 test-harness audit, so this closes out that gap. The
`test` job is `lib`'s instance of the mesh-wide unit-test-gate program
(ADR-KV-009, amended by ADR-KV-013, which scopes `lib` as one of its 12
governed repos); the `gitleaks` job is ADR-KV-014's public-mirror
secret-scan gate, specific to `lib` because it's the one repo mirrored
world-readable.
- `api/` also acts as `lib`'s own internal client of the mesh: `app/auth.go`'s
`CheckAPIUser` calls `auth`'s generated client to validate an API key, so
`lib` is simultaneously a library *and* a consumer of one sibling service.
## Package surface
| Path | What it is |
|---|---|
| `app/root.go` | `InitConfig(systemName, level)` — loads `/etc/vernonkeenan/<systemName>.yaml` via viper, one-time init guard |
| `app/config.go` | `Configuration` struct + `Get*`/`Is*` accessors (DBMS, DSN, authentication mode, log level, metrics, service accounts, workers, email, chunk/cache sizing) |
| `app/mysql.go` | `InitDB()` — opens `MyDB *sql.DB` against the configured MySQL DSN |
| `app/auth.go` | `CheckAPIUser(token)` — validates a credential against `auth`; errors never log the raw value |
| `app/prometheus.go` | `SetupPrometheusHandler` — CORS + request-count/duration middleware |
| `app/service-helpers.go` | Pointer helpers (`String`/`Bool`/`Int64`/…), `GetFieldsAndValues` reflection helper (unit-tested) |
| `app/user.go`, `user-helpers.go`, `tenantuser.go`, `userrole.go`, `address.go` | Compatibility principal/domain structs. ADR-KV-024 adds `PrincipalKey`, `PrincipalType`, exact `Scopes`, `HasScope`, and `IsServicePrincipal`; no native response carries its bearer secret. |
| `app/logger/logger.go` | Thin `zap.SugaredLogger` wrapper (`New(level)`, level constants) |
| `api/{auth,crm,members,plex,research,sfgate,stash}/` | go-swaggergenerated typed clients + models, one directory per sibling service, regenerated from that service's own spec |
| `swagger/*.yaml`, `swagger/defs/`, `swagger/external/` | Copies of each sibling service's spec (the KV-C6b spec-copy problem — no sync mechanism besides `make regen`), shared component defs, and host/path-rewritten copies for external doc publishing; also carries legacy vendored Kazoo/Clerk specs from `lib`'s pre-mesh history |
## Build, run, test
```bash
go build ./...
go vet ./...
go test ./... # table-driven unit tests today: app/service-helpers.go pure helpers
make install-swagger # install the pinned go-swagger CLI (SWAGGER_VERSION in Makefile)
make regen # aka `make swagger` — regenerate api/ from sibling specs
make release VERSION=vX.Y.Z
make release-dry-run VERSION=vX.Y.Z
```
`go test ./...` is CI-gating on every push and PR (ADR-KV-009/013), same as
the rest of the mesh; `lib` is a library so there is nothing to "run."
`make regen` requires the sibling repos `auth`, `crm`, `stash`, `sf-gate`,
`research`, `members`, `plex` cloned adjacently — it reads each one's
`swagger/<service>-vernonkeenan.yaml`, copies it into `./swagger` and
`./swagger/external`, and regenerates every client under `api/`, rewriting
the external specs' scheme/host/path the same way the old `make swagger`
ritual did. It fails fast, before touching anything, if any sibling spec is
missing. Review `git status`/`git diff`, commit, open a PR, and merge it to
`main` like any other change — client regen is never bundled into the
release step; `make regen` runs a non-fatal `go build ./...` at the end as
an early warning, but the real gate is CI on that PR.
**Known gap (resolved for the current spec set, but can recur):** if any
sibling spec has an operation with no top-level `tags:`, go-swagger routes
it into a fallback `operations` package whose import path in
`<name>_client/<name>_client.go` doesn't respect `--client-package`
reproduced across go-swagger v0.33.2 through v0.35.0 while building this
tooling. The one instance found, `sf-gate-vernonkeenan.yaml`'s `PUT
/researchprojectservices` (no `tags:`), was fixed at the source (sf-gate
#3 added the missing `tags:` entry) and regenerated clean in the v0.7.5
`feat/regen-clients-processor-token` PR — `api/sfgate` has no fallback
`operations` package today. If a future sibling spec change reintroduces an
untagged operation, `make regen`'s non-fatal `go build ./...` step will
fail with this same signature; fix it at the source (add a `tags:` entry in
that service's spec) rather than hand-patching generated code, since the
next regen would just reintroduce it.
## Configuration
`lib` reads no config file of its own. `app.InitConfig(systemName, level)`
loads config **on behalf of the calling service**: viper reads
`/etc/vernonkeenan/<systemName>.yaml` (YAML), with environment-variable
overrides via prefix `VERNONKEENAN_` (`.` replaced by `_` in key names),
unmarshalled into `app.Configuration``AppName`, `BackendID`,
`DBMS`/`DBMSHost`/`DBMSName`/`DBMSUsername`/`DBMSPassword`, `DSN`,
`Environment`, `LogLevel`, `GelfURI`, `Metrics` (address/enabled),
`ServiceAccounts` (a map of external-system credentials, e.g. the `auth`
service account `CheckAPIUser` reads its own API key from), `Workers`,
`Email`, `CacheSizes`/`Chunks`. The concrete `/etc/vernonkeenan/<name>.yaml`
file lives on each deploy host, not in this repo — see the calling
service's own README for its file name and required keys.
## Versioning & pinning contract (KV-C6a)
- **Module:** `code.tnxs.net/vernonkeenan/lib`, fetched via the Gitea
anonymous mirror (not GitHub directly — the GitHub repo is private).
- **Pin, don't float:** every consumer pins an explicit `vX.Y.Z` tag in its
`go.mod`. Bumping the pin is a normal PR in the consumer repo; there is no
auto-bump mechanism.
- **Release is one command:** `make release VERSION=vX.Y.Z`
(`scripts/release.sh`) from a clean `main` checkout that matches
`origin/main`:
1. Validates `VERSION` is `vMAJOR.MINOR.PATCH` and strictly greater than
the latest existing tag.
2. Requires you're on `main`, the tree is clean, and local `main` matches
`origin/main` (no local-only commits).
3. Runs `go build ./...` and `go vet ./...` as a last sanity check.
4. Creates an annotated tag at HEAD, pushes it, and runs `gh release
create` with generated notes.
5. POSTs an explicit zero-length body to the Gitea mirror-sync API for
`vernonkeenan/lib` (skipped with a warning if `GITEA_TOKEN` is unset).
`make release-dry-run VERSION=vX.Y.Z` runs steps 1-3 and prints what
steps 4-5 would do, without tagging, pushing, publishing, or syncing
anything.
- **A `lib` bump is constellation-wide:** any breaking change to
`app.Configuration`, `InitConfig`/`InitDB`, or a generated
client's shape ripples to every pinned service on its next `go.mod` bump.
v0.7.10 deliberately removes the retired `InitForce` API and `go-force`
dependency. `sf-gate` stays pinned to its prior Lib while awaiting
retirement; deprecated Cache must not be rebuilt or upgraded.
- **Branch-per-version is retired** (ADR-KV-014, 2026-07-12): `lib` moved
to main + `v*` tags, replacing the old three-web-UI-plus-CLI manual
release ritual. ~70 legacy `vX.Y.Z` branches (one per pre-migration
release; `git branch --list 'v*'`) remain in the repo as historical
artifacts of the old convention, not the active release mechanism.
- **Prerequisites for a release:** the sibling repos (for `make regen`)
cloned adjacently; [`gh`](https://cli.github.com/) authenticated with
push access to `vernonkeenan/lib`; `GITEA_TOKEN` exported for the
mirror-sync step (optional — see above).
## Release & deploy
`main` + PR flow: land changes on `main` via PR (CI runs build/vet/test +
gitleaks on the PR and on push to `main`), then cut a release with `make
release VERSION=vX.Y.Z`, which tags `main`, publishes a GitHub release, and
triggers the Gitea mirror-sync. Unlike the deployed mesh services, `lib` has
**no Dockerfile, no container image, and no deploy step** — it is never
built into `hub.tnxs.net` and never rolled out via the `docker/kvweb`
compose on pluto. "Release" means "a new tag other repos can pin," full
stop; downstream rollout happens when each consumer bumps its own `go.mod`
line for `code.tnxs.net/vernonkeenan/lib` to the new tag and goes through
its own release/deploy path.