Populate the Build & Test, Architecture Overview, and Conventions
& Patterns sections of CLAUDE.md with context captured during the
phase-1 implementation work. Highlights the non-obvious gotchas
(http.Server.Shutdown not interrupting active conns; bd link
direction; bd init auto-committing) so future sessions don't
re-discover them.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Final phase-1 step: the broker now starts. run() parses config, opens
the store, builds the httpserver, and blocks on signal.NotifyContext
until SIGTERM/SIGINT fires, at which point it drains through
httpserver.Run's graceful-shutdown path and closes the store.
--version is handled before config.Load so operators can inspect a
binary without providing the rest of the config. flag.ErrHelp is passed
through so -h exits 0. Config failure exits 2; runtime failure exits 1.
Integration tests build the binary once in TestMain and exercise three
acceptance scenarios against it:
- --version: prints build info, exits 0
- no config: exits nonzero with stderr listing every missing field
- full startup: /healthz returns 200 with correct JSON; SIGTERM triggers
clean exit within 2s
Closes forgejo-mcp-broker-t37. Phase 1 complete.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements internal/httpserver and internal/log.
httpserver (forgejo-mcp-broker-8ei):
- Server struct owns the HTTP lifecycle; Run(ctx) blocks, Handler() returns
the composed handler for unit tests
- GET /healthz returns JSON with status, version, git_revision, build_date,
and store probe result. Returns 503 when the store reports unhealthy
- Signal handling delegated to the caller via ctx cancellation — main wires
signal.NotifyContext, httpserver just responds to Done()
- Graceful shutdown with a configurable deadline (default 10s). When the
deadline expires, falls back to http.Server.Close() so lingering
connections are forcibly terminated — http.Server.Shutdown alone never
interrupts active connections
- ExtraHandler extension point for the OAuth + MCP routes that land in
phase 2 and phase 5, so the server doesn't need to be re-plumbed later
log:
- Small slog wrapper: New(w, debug) returns a JSON logger that stamps every
record with service/version/git_rev for correlation across deployments
- Discard() helper for tests
Tests: 97.9% coverage on httpserver (all health states, wrong-method,
ExtraHandler dispatch, ctx-cancel shutdown, shutdown-deadline force-close
of hanging requests, missing-field errors), 100% on log.
Closes forgejo-mcp-broker-8ei.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements internal/store on top of modernc.org/sqlite (pure-Go, no CGO).
Open applies any pending migrations, Close releases the handle, Ping
underpins /healthz.
Migration design:
- Files embedded via embed.FS under migrations/NNNN_name.sql
- schema_migrations table tracks applied versions; re-open is a no-op
- Each migration runs in its own transaction: no partial commits
- loadMigrations takes an fs.FS so tests can inject synthetic migration
sets to exercise rollback and conflict paths
Connection pragmas (set via DSN so they apply to every pooled conn):
- journal_mode=WAL — better reader/writer concurrency
- foreign_keys=ON — off by default in SQLite, we always want them
- busy_timeout=5000 — absorb brief contention without surfacing SQLITE_BUSY
- synchronous=NORMAL — standard WAL pairing
Phase 1 schema (0001_initial.sql) is minimal: a broker_meta table with a
schema_version row. Real OAuth tables ship in phase 2.
Tests: 90.1% coverage across public API and internal migration runner,
including bad SQL rollback, PK-conflict record-step failure, and scan
errors on malformed schema_migrations rows.
Closes forgejo-mcp-broker-9jh.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements internal/config. Flags override env; empty env values are treated
as unset so exported-but-empty vars don't clobber defaults. Validation
aggregates every problem via errors.Join so operators see the full list
at once, not one at a time.
Notable decisions:
- Issuer URL validation requires http/https and rejects plain http on
non-loopback hosts (classic OAuth misconfig). Loopback http allowed so
local dev doesn't need TLS.
- Store-path writability probed via os.CreateTemp in the parent dir — a
real write test, not a string check. No side effects on the store file
itself (that's the storage layer's job).
- Public API: Load(args, out) returns *Config or error. flag.ErrHelp is
passed through so callers can exit 0 on -h.
Tests: 94.1% line coverage, covers env/flag precedence, empty-as-unset,
every required-field message, each URL failure mode, numeric bounds,
env-parse errors, and -help output.
Closes forgejo-mcp-broker-9nq.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Initialize go.mod with module path kode.naiv.no/olemd/forgejo-mcp-broker
- Create directory layout: cmd/broker + internal/{buildinfo,config,log,store,httpserver}
- Add Makefile with build/test/lint/tidy/clean targets and ldflags-injected build info
- Stub cmd/broker/main.go with --version support; real wiring follows in -t37
- Stub doc.go for each internal/* package, pointing to the issue that fills it in
Closes forgejo-mcp-broker-n84.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Establish project scope, architecture, and phased implementation plan
for an OAuth 2.1 broker that fronts forgejo-mcp, delegating user
authentication to Forgejo and spawning a per-session stdio
forgejo-mcp subprocess scoped to each authenticated user's token.
No code yet — planning only.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>