benchmarks for the hot path
Twenty-nine of them, over what every request of every service actually
touches: working-token validation, grant parsing and matching, the chi and
net/http middleware, and the same-origin guard.
They exist to be uploaded. This library had no CI and no benchmarks at all
while seven services pin it, so nothing measured a regression here.
mcphttp: the MCP endpoint plumbing both services already share
hostAllowed is byte-identical in cov and bench, and neither should own it.
PrivateCache is cov's alone today, which is why bench's /mcp currently sets
no Cache-Control and no Vary at all. StreamableOptions carries the pair both
pass and the reason for it.
HostGuard takes cov's fail-closed stance: an origin with no host to guard
with is a construction error, not a warning. bench serves unguarded in that
case and has a test pinning it; reconciling that is bench's to do.
Deliberately not a shared MCP framework. Once the go-sdk and the per-service
tools are subtracted, this is the whole overlap.
chrome: give a service its own navigation row
The switcher lists the instance's services, each on an origin of its own.
Three artifacts pages rode ExtraNav into it, so that row's membership
changed from service to service and its active element - spent naming
"artifacts" - said nothing about which of the three the reader was on.
Sections is the second row instead, the one upstream draws as
.header-tabbed wrapping .nav.nav-tabs and meta puts profile and keys in.
A service declares its sections at startup; Page marks the one the
request's path stands in, so a page cannot forget to declare itself, and
a path in no section lights nothing rather than the first tab. Both
classes arrive with core.sr.ht's nav.scss, so the row costs no service
any CSS.
ExtraNav is deprecated with it: bench and cov abandoned it when the
instance deployed a tokens.sr.ht and the word appeared in the navbar
twice, and artifacts was its last user.
apimeta: serve the api-meta.json a self-mounted /query owes the instance
core-go serves this file for a service assembled through WithSchema, which
mounts /query on the authenticated router. A service whose API must answer
anonymous callers cannot use that — core-go's auth middleware 401s an
un-cookied request — so it mounts /query itself, and then nothing serves the
file meta.sr.ht reads to build its personal-token page.
The scope list is variadic and marshals empty rather than null, which is the
whole reason this is a package and not four lines per service: meta iterates
every discovered service's scopes on ONE page, so a single `"scopes": null`
is a 500 on /oauth2/personal-token for the entire instance rather than one
degraded entry — a failure nobody would find by testing the service that
caused it.
bd: turn on dolt auto-push
The debounce is 30s rather than the 5m default so a memory or issue written
mid-session reaches dolt.srht.bigb.es without waiting for the session to end,
and the 60s timeout keeps an unreachable remote from hanging bd.
bd init: initialize beads issue tracking
follow cover.sr.ht's rename to cov.sr.ht
The section in ecoretest's synthetic config.ini, and the grant vocabulary the
grants package uses for its examples — cover:upload was the stock example of a
well-formed grant, and the service that defines it now spells it cov:upload.
follow compare.sr.ht's rename to diff.sr.ht
The service renamed itself; what reaches this repository is the section name
in ecoretest's synthetic config.ini, which every service's switcher test is
built from, and the examples in the chrome docs and README that spelled the
old section. The fixture's custom services now read bench, cover, diff, dolt,
spec, tokens — alphabetical, which is the order BuildNav puts them in.
bearer: say what IsRefusal does not answer for
A service whose own refusals do not wrap these sentinels sends its "bad
token" into the else branch of the guard the doc suggests, and answers 503 to
a caller whose credential really was the problem. One of the six hit this while
adopting it.
ecore: the gaps the third adoption pass found
Five services adopted the new packages today and each hit the same edges.
bearer.IsRefusal: a service's resolver returns more than this package's
vocabulary — the Postgres lookup it had to make, an expired context, a bug —
and StatusFor's default answers those 401, which tells a caller to re-mint a
token that was never the problem. Every service that wrapped the table spelled
out the sentinel list to guard the delegation; now it does not have to.
instconf.Key.Because: three services kept their own hand-written config
checker rather than lose the sentence that says WHY a key is required
("crypto.InitCrypto exits without it"). Require reported the names and nothing
an operator could act on, so nobody used it.
logging.DefaultsWithoutDebugFlag: a migration CLI passes -d to brant, where it
means --dialect and takes a value, so `coversrht-migrate -d postgres` arrived
as a request for debug logging — silently, because the probe sees the flag and
never the value.
assets.DirFS: os.DirFS("") does not mean "no assets", it means "serve the
filesystem root", and it is one unset config key away. dolt wrote the guard;
the other five would each have to see it happen first.
pages.APIMessage: the REST half of every service keeps its 404 body
byte-identical on purpose, so it could not adopt the shared refusal installer,
whose messages are page sentences. The same table now answers in both
registers.
bearer: mint the internal authorization through internalauth
This package assembled the header by hand — json.Marshal of an auth.InternalAuth
plus "Internal " and a fernet seal — two files away from the package whose whole
purpose is to hold both ends of that handshake. It is the same drift internalauth
was hoisted to end, sitting inside the library that hoisted it: a change to the
payload shape here would have gone unnoticed by every Guard on the instance until
a revocation check started failing.
README: the packages of the third uplift
chimw: the chi-shaped helpers of the custom services
GetHead registers a read route under GET and HEAD both, which cover, bench
and tokens had each spelled out — five copies of three lines, under three
names. RenderRefusals points chi's NotFound and MethodNotAllowed at the
service's own error page, which spec and dolt install neither of, so a
mistyped URL there is the one refusal on the instance that answers in
plain text. RequestLogger is a chi LogFormatter emitting slog records, in
place of chi's colourised stdlib line on stdout — the highest-volume line
a service writes and the only one that was neither structured nor on
stderr.
It is a second middleware package and not a wing of the first because
sr-ht-ecore/middleware needs nothing but net/http and that rule is worth
keeping; the boundary is whether the helper has to know what a route is.
A request line is Info, a 5xx is Error, and 499 stays Info by being below
500, which is what that code was chosen for. Skipping probe noise is the
caller's call, through a predicate. The logger goes outside RecoverPanics
so the line reports the error page that was rendered rather than the
nothing an unwinding stack has written.
internalauth: both ends of the Internal authorization
core-go implements this check unexported inside auth.Middleware, so a
service that wants the guard without the session resolution behind it
writes it again: dolt.sr.ht did, in web/handlers_internal.go, and the
caller minting the header for it is a different program in the same
repository (cmd/dolt-git-hook) that spells the payload out by hand.
Copies drifting is the smaller half. The two ends of one protocol shared
no type, no constant and no test, so a change to the payload shape, the
scheme or the expiry breaks nothing at build time and stops provisioning
at the next push. They are two functions over one struct here.
The check stays core-go's: source address inside [sr.ht]internal-ipnet
AND a fernet token sealed with [sr.ht]network-key, at most 30s old, both
required. Three deliberate departures. The caller may be pinned to a
client and node id, which upstream only checks are non-empty; a payload
that decrypts but is not an internal auth is a 403 rather than a panic,
as is a RemoteAddr that does not parse; and the refusals are sentinels,
so "not internal at all" is distinguishable from "internal but wrong
client" without parsing an error string.
login: the one decoder of the unified-login cookie
Six services decode sr.ht.unified-login.v1 independently, and each of them
makes the same five decisions: DecryptWithoutExpiration rather than a decrypt
with a service-side TTL, unmarshal into auth.AuthCookie, strip the leading '~',
treat every failure as anonymity rather than an error, and validate the name
before it reaches a path, a log line or a SQL parameter. Two of the six had
already dropped the last one.
The middleware is split into Optional and Required because that is where the
copies diverged: every donor folded its own gating policy into a single
middleware — never refuse (public clones), never 401 (visibility decided
downstream), always send the viewer to meta's login page — so one middleware
here would force most services to write the other half again.
ValidName ships as the default rather than being required: the grammar is
meta.sr.ht's and therefore instance-wide, and requiring a validator would put
the safe path behind an extra argument that the services validating nothing
today would go on not passing. WithValidator narrows it; a nil validator
restores the default, so "accept anything" has no spelling in the API.
Mirroring meta's profile into a service's own user row stays in each service:
that touches its own schema.
logging: the instance's slog policy, without the handler
middleware.RecoverPanics reports panics through slog's default logger, so
ecore already depends on every service installing a compatible handler and
had no way to say so. A service that forgets slog.SetDefault prints its
panic reports in Go's plain format, unlevelled and unmasked, into a journal
where everything else is tinted.
Six services (compare, spec, dolt, cover, bench, tokens) had each written
the same level parser, the same os.Stderr.Stat colour probe, the same -d
scan of os.Args, and their own copy of the credential mask list. The copies
had drifted into three key sets and four patterns: only spec masked the
config private keys, only tokens-migrate knew a DSN carries a password, and
only dolt masked pubkey and credential. What is redacted — the unified-login
cookie, tokens.sr.ht working tokens, the Authorization header they ride in —
is a fact about the instance, so it is maintained once.
Defaults() resolves the policy and Install() sets the default logger; the
handler stays with the caller, because ecore is a small SourceHut library
and auxilia is a large general one, and a service that wants a JSON handler
for a log shipper should not link a tinting one to share a mask list.
Options.ReplaceAttr applies the same masking through stdlib slog alone, so
the split costs a non-scribe service nothing. tokens.sr.ht's partial mask is
exported but not defaulted: six characters of a live working token is still
six characters of a live working token.
Level: -d, then $LOG_LEVEL, then [section]log-level, then info; an
unreadable value falls through to the next source rather than refusing to
boot.
instconf: one reading of the instance config origins
Five services and this library each grew a copy of the same three lines
that turn config.ini into an origin, and the copies drifted. Two donors
canonicalized with TrimRight and TrimSuffix respectively, so a config
carrying "https://x//" produced two different strings in two daemons
that must produce the same one when comparing against an Origin header.
One repo held two origin-to-host extractors that disagreed about a
malformed origin: one returned an error, the other answered "localhost".
A third copy feeds the DNS-rebinding guard of an MCP endpoint, where an
empty host disables the guard.
CanonicalOrigin takes the strict reading (TrimSpace, then every trailing
slash). ExternalOrigin and InternalOrigin are two names rather than one
GetOrigin with a bool, because a flipped flag is invisible until a
browser is redirected to an address only the daemon can reach.
OriginHost returns a host name and OriginAuthority a host[:port], the
two things the disagreeing donors each needed, and neither invents a
host for an origin that names none. InternalAPIOrigin walks the four-key
ladder and reports absence as a bool instead of core-go's panic, and
Require reports every missing key in one error so an operator fixes the
config in one pass rather than one restart per key.
bearer, pages: the refusal table and the form read that must not drift
bench wrote the bearer refusal switch three times in one repo — REST, MCP and
its resolver — and each copy re-decides the arm that matters: ErrUnavailable
is 503, never 401. Reading an unreachable token daemon as "revoked" tells
every CI job on the instance its credential is bad for as long as tokens.sr.ht
takes to restart. That warning has lived in this repo's README as prose, where
it cannot be imported; StatusFor puts it in code, and its default sends an
unrecognised failure to 401 so a forgotten arm refuses a request rather than
declaring the service unwell.
FormValues returns r.PostForm and never r.Form. r.Form merges the query string
into the body's values, so a mutation could be driven from a URL somebody was
linked to — the one request the same-origin guard cannot fault, because it did
come from our own page. The difference between the safe version and the hole
is one character in a field name, in a function every service with a form
writes for itself.