~bigbes/sr-ht-ecore

3bd158fb — Eugene Blikh 2 days ago
mcphttp: pin Flush with a flush that precedes any write

Deleting the Flush method left every test in this package green. All of
them wrote before flushing, which commits the headers through the Write
hook and leaves the flush path unmeasured — the second vacuous test in
this file, after the Unwrap one.

The case that needs Flush is a handler that flushes first, as an SSE
handler opening a stream does. It has to run against a real server:
httptest.ResponseRecorder's Header() hands back the live map, so a
recorder reports the headers as set whenever they were set, and passes
for exactly this bug. Found by an agent that measured it in a sibling
service's copy of this code.
00d75828 — Eugene Blikh 2 days ago
mcphttp: test Unwrap for what it actually carries

The comment claimed Unwrap was what kept streaming alive, and the test named
after that claim passed with the method deleted. Both were wrong, and wrong
for the same reason: cacheWriter has its own Flush, and
http.NewResponseController prefers a method on the writer it is handed over
one reached by unwrapping, so the flush path never unwraps at all.

What Unwrap does carry is everything else the controller offers — the
deadlines and Hijack. A long-lived MCP stream is the response that wants its
write deadline pushed out. The new test measures that against a real server,
because a recorder supports no deadlines either way and would have been
vacuous a second time.

Found by an independent reviewer that mutated the method away instead of
reading the comment.
b352133b — Eugene Blikh 2 days ago
bearer: silence the std logger the benchmarks were timing

sr-ht-core's auth.DecodeBearerToken calls log.Printf on every token it
refuses, so BenchmarkValidate/invalid wrote one line per iteration. The cost
was not the file: log.Printf takes a mutex, and the case reported 925 ns/op
where the work is 92.5.

The file was a hazard too. An unfiltered run of this package produced 9.5
million lines and 901 MB, and benchfmt skips what it cannot parse — so
bench.sr.ht would have accepted that upload and reported success over it.

Ignore cover.out and bench.txt: make cover and make bench write them into
the checkout.
54025f42 — Eugene Blikh 2 days ago
ci: test, coverage and benchmarks on builds.sr.ht

This repository had no CI while seven services pinned it, so a break here
was discovered in whichever service next ran `go get -u`. The manifest is
the sibling services' pipeline with everything only a service needs taken
out: no scss, no keygen, no abuild, no publish, no publish_artifacts, and
no postgres — nothing in this module imports database/sql, no test opens a
database and no test skips on a missing DSN. What is left is the Go cache,
the gates, the two runs and the two uploads to cov.srht.bigb.es and
bench.srht.bigb.es.

The Makefile is what the manifest calls, so the Go command lines exist
once. Its bench target filters its own output, and that is not tidiness:
`go test` merges the test binary's stderr into stdout, bearer's
invalid-token benchmark makes sr-ht-core's auth.DecodeBearerToken log once
per iteration, and an unfiltered BENCH_COUNT=10 run was 901 MB and 9.5
million lines deep — around the 290 result lines it produces — when it was
stopped. benchfmt ignores what it cannot parse, so that file would have
uploaded "successfully". Filtered, the same run is 340 lines and 31 KB.
The cure belongs in the benchmark, which should silence the logger the way
middleware's discardLog already does; the filter is the pipeline's defence
either way.

check-fmt and not gofmt, because `gofmt -l` prints the offending files and
still exits 0; cover refuses an empty profile and the bench task greps for
one benchmark name per benchmark file, because both would otherwise upload
nothing and call it a success.
92fea80a — Eugene Blikh 2 days ago
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.
3aa1fcfd — Eugene Blikh 2 days ago
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.
514838a7 — Eugene Blikh 2 days ago
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.
2e37ec73 — Eugene Blikh 3 days ago
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.
0b1bba82 — Eugene Blikh 3 days ago
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.
e33cf962 — Eugene Blikh 3 days ago
bd: update sync.remote
fd2321f1 — Eugene Blikh 3 days ago
bd: clear sync.remote
b8f7b578 — Eugene Blikh 3 days ago
bd init: initialize beads issue tracking
353a7f19 — Eugene Blikh 9 days ago
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.
34cf83d4 — Eugene Blikh 9 days ago
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.
43ad9287 — Eugene Blikh 9 days ago
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.
9a97b126 — Eugene Blikh 9 days ago
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.
e8a96725 — Eugene Blikh 9 days ago
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.
b6bf6d28 — Eugene Blikh 9 days ago
README: the packages of the third uplift
cb7f3bb0 — Eugene Blikh 9 days ago
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.
c627d4ee — Eugene Blikh 9 days ago
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.
Next