instconf: the origin ladder and the one-pass config check validateConfig kept its shape and lost its body. instconf.Require is the same decision — every missing key in one record, one exit, so an operator edits config.ini once instead of restarting per gap — and firstConfigured was this service's copy of the four-key API-origin ladder core-go panics at the end of. NeedAny is handed APIOriginKeys() rather than the four names written out, so the startup check and InternalAPIOrigin cannot drift apart the way two literal lists eventually do.
logging: the instance's log policy instead of forty local lines initLogging held compare's own copy of the level choice, the colour probe and the mask list, and the mask list was the drifted one: three keys and a four-word pattern, against an instance whose services between them redact ten. logging.Defaults answers all of it, and the handler stays here — scribe is auxilia's and does not belong in the library every service links for its chrome. isTerminal goes to logging.ColorEnabled, which also honours NO_COLOR, and the level now reads $LOG_LEVEL after -d. Defaults(nil, "") because the handler is installed before LoadConfig: a daemon that became verbose only after startup is silent for the window -d was passed to watch.
login: ecore's cookie decoder, which validates the name ours did not authz/identity.go was the fifth copy of a decode the instance has one of, and it was one of the two that validated nothing: whatever name a sealed payload carried went on to the GraphQL viewer field, the chrome's nav and every log line the request produced. login.ValidName refuses it, and there is no spelling of the API that turns the check off. Gone with it: CookieName, UsernameFromRequest, Middleware, ForContext and the ctxKey, plus identity_test.go, whose round-trip, tilde-stripping and garbage-cookie cases are ecore's tests now. The Middleware becomes login.Optional() — this service refuses nobody, git.sr.ht decides what an anonymous viewer may see — and the default validator is kept rather than core.ValidOwner, which is lowercase-only and would log a real account out of compare alone. The one behaviour change a viewer could notice: a cookie whose name carries a '/', a control byte or a non-ASCII letter now reads as anonymous instead of as that name.
log: slog through auxilia's scribe, not logrus This was the last service on the instance still logging through logrus, and the ecore bump made it a correctness question rather than a consistency one: the recovery middleware reports a panic through slog's DEFAULT logger, so without a SetDefault the one record carrying a stack trace would have gone to Go's plain handler while every other line went through logrus. initLogging installs a scribe tint handler on stderr — source on, colour only when stderr is a terminal, and the mask rules for token, cookie and authorization. It reads -d out of the argument vector rather than waiting for core-go's parse, because a daemon that only became verbose after it had finished starting would be silent for exactly the part of its life an operator passes -d to watch. Terminal detection is a stdlib Stat rather than golang.org/x/term, which would be a new dependency for one predicate. The startup config check keeps its shape deliberately: it still accumulates every missing key and reports them in ONE record before ONE exit, so an operator fixes the config in a single pass. The keys go in as a slice attribute rather than a joined string, so the structured sinks keep them as a list. culpa goes in at the one boundary where it pays: the errors web.New returns, which are the only errors this package produces and all of which arrive at that single startup record. Each carries a hint naming the config key or the build step that fixes it, and scribe.Err unfolds message, code, hint and stacktrace into fields of their own. The failing request path logs the same way, with ErrorContext so a cancelled request reads as cancelled rather than as an unexplained 500. TestPanicIsAnErrorPage covers the wiring end to end through Register: a panicking authorizer yields the chrome-wrapped 500, the panic value does not reach the viewer, and the report reaches slog's default logger.
cmd: ecoretest keys in the startup smoke test The synthesized config.ini takes ecore's fixed keyset and origins rather than generating a keypair per run; a constant cannot be malformed by accident, which matters for a subprocess whose only way of complaining is a log.Fatal. Also notes the three middlewares Register now installs inside its own group, and why chi's Recoverer stays out here.
rename module to sourcecraft.dev/bigbes/sr-ht-compare; depend on sourcecraft sr-ht-core
cmd/comparesrht: daemon entry point, startup validation, dev stub Wire the compare.sr.ht daemon on core-go's server.New: it runs crypto.InitCrypto, parses -b/-d/-m/-p, and drives the standard SourceHut warm-shutdown loop (SIGINT). We deliberately skip WithDefaultMiddleware (it demands Postgres/Redis and 401s anonymous viewers) and instead install the web package's documented middleware chain — RealIP, Recoverer, Logger, config.Middleware, authz.Middleware — inside a Group on the anonymous router. validateConfig checks every required key up front (network-key, webhook key, repos root, a git.sr.ht API origin candidate, meta and compare origins) with a single clear fatal, so a misconfiguration fails loudly at startup rather than as a deep panic in config.GetAPI on the first request. A build-and-exec smoke test proves the binary starts against a synthesized config, answers /healthz 200, and exits cleanly on SIGINT in under 15s. Makefile: build ./comparesrht from ./cmd/comparesrht, run-dev binds localhost:5090 against a local config.ini, install ships the binary and static assets. contrib/dev-stub is a stdlib-only fake git.sr.ht GraphQL API returning fixed public repos for local development, and the systemd unit now stops with KillSignal=SIGINT to hit the warm-shutdown path.