Files
OwnCord/Server/Makefile
T
J3vbandClaude Fable 5 8cb0ec9e35 feat(b3-6): contract drift — generated route, table and config-key indexes with a CI drift check (#1456)
* feat(b3-6): contract drift — generated route, table and config-key indexes

B3-6 item 9 (workstream 10). `check:server` already diffs the two
generators; this adds a third for the three server contracts that only
prose described until now.

`Server/cmd/gendocs` rewrites one marked block per document:

- `docs/api.md` "Route index (generated)" — 111 rows from `chi.Walk` over
  the production router built with uploads, voice and the GIF proxy on,
  the same scaffolding `api/absence_contract_test.go` uses. Carries that
  test's vacuity guards: fewer than 100 routes, or no `/admin/` route,
  fails the run.
- `docs/schema.md` "Table index (generated)" — 34 rows from `sqlite_master`
  and `pragma_table_info` on an in-memory database with the migrations
  applied. sqlc exposes no catalog, so the migrated schema is the catalog.
- `docs/server-configuration.md` "Key index (generated)" — 56 keys from the
  koanf struct tags, each mapped to the `###` section of the hand-written
  reference that names it. A key documented nowhere fails the run by name.

Output is padded exactly the way Prettier formats a table, so the drift
check and the hygiene gate agree instead of undoing each other.

Wiring, copied from protocol-verify: `make docs-generate` / `make
docs-verify`, a `DOCS_VERIFY` step in `check:server` and the generator in
`generate` (`scripts/run.mjs`), a CI step on the ubuntu leg of
`server-build-test`, and a `.githooks/pre-commit` block on router, handler,
migration, config and generator paths.

Everything hand-written in the three documents is untouched. The new
`cmd/gendocs` file imports `db` for the catalog, so it takes a boundary row
in the B3-0 inventory and `server-boundaries.md` is regenerated with it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmiqjgTuov1stBTB6uGkvo

* docs(b3-6): evidence block for item 9 (machine-readable contract drift)

Records the three RED controls and their restore, the counts (111 routes,
34 tables, 56 config keys, 0 undocumented), and two corrections to the item's
spec: the configuration reference table lives in docs/server-configuration.md,
not docs/deployment.md, and sqlc exposes no catalog — the migrated in-memory
schema is the catalog.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmiqjgTuov1stBTB6uGkvo

* fix(b3-6): gendocs — exclude ANALYZE artifacts, honest hook message, admin routes trigger the hook, generate order, width ceiling

Review findings on item 9.

1. The table index dropped `sqlite_stat1` / `sqlite_stat4`. `db.Migrate` runs
   ANALYZE after applying migrations, so those hold planner statistics, not
   schema — and `sqlite_stat4` exists only because the current
   modernc.org/sqlite build has STAT4, so a driver bump would have failed the
   docs drift check on an unrelated dependency PR. Filtered with GLOB (LIKE's
   `_` is a wildcard), block regenerated, header line's justification
   corrected: 34 -> 32 tables.
2. The pre-commit message now covers both failure modes — stale blocks are
   regenerated and staged, a key the tool named as undocumented is documented
   in docs/server-configuration.md.
3. `Server/admin/.*\.go` added to the hook's trigger: the 34 `/admin/api/*`
   routes are registered there, not in api/router.go, so a new admin route
   could commit stale docs locally.
4. `run.mjs` `generate` runs gendocs after `sqlc generate` — gendocs compiles
   the api package, which imports db/dbgen.
5. The vacuity guard now requires a traversed `/admin/api/` subroute rather
   than any `/admin/` path, which the per-method mount catch-alls satisfied on
   their own, so its message is true. `writeTable` gained a comment naming its
   ceiling: padding counts runes, Prettier counts display width, so a
   full-width cell would diverge — none exists in the generated content.

Evidence block updated for the new table count.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmiqjgTuov1stBTB6uGkvo

* fix(b3-6): gendocs — generate the route index from the full-tag build with telemetry on; the hook triggers on every api/ and admin/ Go file (Codex P2s on #1456)

1. `/metrics` was missing from the route index. It mounts only when
   `telemetry.PrometheusHandler()` returns non-nil (api/router.go:431-437),
   which needs the otel build tag AND telemetry enabled at runtime; the
   generator ran in the default build with telemetry unset, so the index
   omitted a production route.

   The route index is now the superset build. The scaffold config enables
   telemetry with the Prometheus exporter and the tool calls telemetry.Init
   the way main.go does, and every invocation passes -tags otel,wazero:
   Makefile docs-generate/docs-verify, scripts/run.mjs (DOCS_VERIFY and
   generate), .githooks/pre-commit, the regenCmd quoted into all three block
   header lines, and the CLAUDE.md row. ci.yml inherits it through
   `make docs-verify`. The route block's header line now says which build it
   came from and what is enabled.

   Rather than a build-tag constant, the tool checks the condition that
   actually gates the route: if telemetry.Init leaves no Prometheus handler
   it exits non-zero naming the tags, so the default build cannot quietly
   generate a short index.

   Nothing under Server/api or Server/admin carries a build constraint, so
   wazero adds and removes no route; it rides along so one build serves the
   whole repository. Route count 111 -> 121 (ten per-method rows for the
   /metrics mount, the same shape chi gives /admin and /livekit).

2. The pre-commit trigger named individual api/ files and missed
   client_update.go, whose MountClientUpdateRoute registers a route directly.
   It is now the whole of Server/api/ and Server/admin/ — naming files
   individually is how a trigger goes stale — plus the existing migrations/,
   config/config.go and cmd/gendocs/ patterns.

Evidence block updated: route count and the tagged-build decision.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmiqjgTuov1stBTB6uGkvo

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 14:18:54 +00:00

123 lines
5.3 KiB
Makefile

# OwnCord Server — developer convenience targets
#
# test Run the test suite the way CI does (race + timeout).
# test-deadlock Run the deadlock-detection pass CI also runs.
# fuzz Actually fuzz. CI (and plain `go test`) only replays the
# committed seed corpus; this generates new inputs.
# cover Per-package coverage (what CI uploads) + a function summary.
# cover-all Cross-package coverage — the honest number. See below.
# sqlc-generate Regenerate type-safe Go from sqlc.yaml (db/dbgen).
# sqlc-verify Fail if the committed dbgen output is stale (used by CI).
# sqlc-install Install the pinned sqlc version into $GOBIN.
# protocol-generate Regenerate WS message-type constants (Go + TS) from ../protocol/schema.json.
# protocol-verify Fail if the committed protocol constants are stale (used by CI).
# docs-generate Regenerate the route/table/config index blocks in ../docs.
# docs-verify Fail if those generated blocks are stale (used by CI).
# otel-up Start Jaeger + Prometheus for local tracing development.
# otel-down Stop and remove the OTel dev containers.
SQLC_VERSION := $(shell cat sqlc.version)
.PHONY: test test-deadlock fuzz cover cover-all sqlc-install sqlc-generate sqlc-verify \
protocol-generate protocol-verify docs-generate docs-verify otel-up otel-down
test:
go test -race -timeout 20m ./...
test-deadlock:
go test -tags deadlock -count=1 ./...
# Every Fuzz* target, one at a time. `go test ./...` (and therefore CI) runs a
# Fuzz function against its committed seed corpus only — one pass per seed,
# zero generated inputs — so the harnesses find nothing new until this runs.
# Go fuzzes exactly one target per package per invocation, hence the loop.
#
# Deliberately local-only: a crasher is written to testdata/fuzz/<Target>/<hash>
# and that file IS a working reproducer. This repo is public, so a crasher stays
# uncommitted until its fix exists, then corpus entry and fix land together as
# one regression test.
#
# No make on Windows? The same loop, straight into Git Bash:
# for pkg in $(go list ./...); do for fn in $(go test -list='^Fuzz' $pkg \
# 2>/dev/null | grep '^Fuzz'); do go test $pkg -run='^$' -fuzz="^$fn$" \
# -fuzztime=30s || break 2; done; done
FUZZTIME ?= 30s
fuzz:
@for pkg in $$(go list ./...); do \
for fn in $$(go test -list='^Fuzz' $$pkg 2>/dev/null | grep '^Fuzz'); do \
echo "── $$pkg $$fn"; \
go test $$pkg -run='^$$' -fuzz="^$$fn$$" -fuzztime=$(FUZZTIME) || exit 1; \
done; \
done
# Matches the CI invocation. Note that `go test ./... -coverprofile` instruments
# each package only for itself, so a package whose code is mostly exercised
# through another package's tests reports far lower than its real coverage
# (`service` reads ~37% here versus ~85% cross-package). Use cover-all for the
# number to reason about; this target exists to reproduce the CI artifact.
cover:
go test ./... -coverprofile=coverage.out -cover
@go tool cover -func=coverage.out | tail -1
# Cross-package coverage: every package is instrumented for every test binary,
# so code reached indirectly is counted. Prints the functions no test reaches at
# all — the list to work from when closing gaps.
cover-all:
go test -count=1 -coverpkg=./... -coverprofile=coverage-all.out ./...
@echo
@echo "── functions with no coverage ──────────────────────────────────────"
@go tool cover -func=coverage-all.out | awk '$$NF=="0.0%"' | sed 's|github.com/J3vb/OwnCord/Server/||'
@echo
@go tool cover -func=coverage-all.out | tail -1
sqlc-install:
go install github.com/sqlc-dev/sqlc/cmd/sqlc@$(SQLC_VERSION)
sqlc-generate:
sqlc generate
sqlc-verify:
sqlc generate
@git diff --exit-code db/dbgen || ( \
echo "ERROR: generated sqlc output is stale. Run 'make sqlc-generate' and commit the result." ; \
exit 1 ; \
)
protocol-generate:
go run ./cmd/genprotocol
protocol-verify:
go run ./cmd/genprotocol
@git diff --exit-code ws/message_types.go ../Client/src/lib/protocolTypes.ts || ( \
echo "ERROR: generated protocol constants are stale. Run 'make protocol-generate' and commit the result." ; \
exit 1 ; \
)
# Route, table and config-key indexes in ../docs. Same shape as the two
# generator checks above: regenerate, then fail on any diff. The tool also
# exits non-zero on its own when a config key is documented nowhere.
#
# -tags otel,wazero is not optional: /metrics mounts only when the otel build
# supplies a Prometheus handler, so the default build would generate an index
# missing a production route. The tool refuses to run without it.
docs-generate:
go run -tags otel,wazero ./cmd/gendocs
docs-verify:
go run -tags otel,wazero ./cmd/gendocs
@git diff --exit-code ../docs/api.md ../docs/schema.md ../docs/server-configuration.md || ( \
echo "ERROR: generated documentation blocks are stale. Run 'make docs-generate' and commit the result." ; \
exit 1 ; \
)
# Phase B Step 8 — local OTel development stack.
# Starts Jaeger (traces) and Prometheus (metrics) in Docker.
# Jaeger UI: http://localhost:16686
# Prometheus UI: http://localhost:9090
# Run the server with: go build -tags otel . && ./owncord-server
otel-up:
docker compose -f docker-compose.otel.yml up -d
otel-down:
docker compose -f docker-compose.otel.yml down