mcpsrv: add the beads tools
web: register the views in one explicit list
web: give bd memories their own view
mcpsrv: add the generic browse tools
web: offer the bd command for the issue on screen
db: list every database a viewer may see
web: show how fresh a beads view is
handleView now reads the head commit of the rendered ref onto the envelope
(Head *browse.CommitInfo), and the beads and milestones headers carry a
shared beadsHead partial: <branch> · last commit <relative> · <short hash>,
the hash linking to the commit page and the exact stamp in the title.
The read is decoration on top of an answer: a database with no commits, or
a Log that fails, renders the page without the line rather than 500ing.
The relative time is a new ago func rather than chrome's reltime — it is
past-facing (clock skew reads as "just now", never "in 3 minutes") and
reads a package clock a test can pin.
mcpsrv: serve a stateless read-only MCP surface
beads: add a one-column stream layout beside the board
?layout=stream renders the same filtered set, the same buckets and the same
cards as one column of sections instead of four lanes side by side. It is a
layout of the Beads view and not a fifth tab: filters, the ready toggle, the
search box and ?issue= behave exactly as they do on the board.
A column read top to bottom can afford one sort per section, because each
section answers a different question: Rolling by started_at desc (what was
picked up last is what is being worked on), Lined Up ready-first then priority
then oldest, Stalled by blocker count (one blocker away is nearer than five),
Past Stand by closed_at desc. started_at and closed_at join the card model as
sort keys and are not displayed; a row missing one sorts last, because an unset
timestamp is not a very old one.
Past Stand opens collapsed in a <details> — the largest and least actionable
section, closed without a line of JavaScript. The sections are derived from the
finished lanes rather than bucketed again, so the section counts cannot drift
from the marquee, and the lanes keep the board order the board renders.
The Board/Stream toggle rebuilds the current query with layout replaced, via a
new withQuery template func, so every active filter survives the switch.
docs: spec the MCP surface and the second round of beads views
beads: drop the Bead/Beads prefix from the moved types
beads: extract the projection out of web/
web/beads.go held the one reading of the beads schema — the table
fingerprint, the lane bucketing, the ready rule, the status categories,
the transitive dependency walk, the event humanizer, the filter model and
the milestone rollup — where a second consumer could not reach it. The
MCP surface and the cross-database ready page both need it.
Move it to a new root package beads/ that depends on browse/ and the
standard library only: rows in, view model out, no net/http, no
html/template, no core. The BrowseSession seam is declared consumer-side
there and names the one method the projections call, so web's larger
BrowseSession satisfies it structurally and a session passes straight
through.
web/beads.go and web/milestones.go keep only their View adapters — slug,
label, template, Applies, and the hand-off of ref and query. The
templates are unchanged: the moved types keep their names and their
display methods, so every dot still resolves.
Pure move plus the beadsMax -> beads.Max export rename. The projection
tests move with the code and become testify; the render tests stay in
web/ unchanged. Same 63 tests pass before and after.
authn: gofmt the test files
authn: accept tokens.sr.ht working tokens and PATs as bearer
The /mcp surface is bearer-only, and none of the three credential planes
this service has fits an agent. ResolveBearer adds the fourth: it decodes
the presented token once, locally, and routes on the ClientID — the only
thing that tells a tokens.sr.ht working token from a meta.sr.ht PAT, since
both are sealed with the same instance key.
A working token goes through sr-ht-ecore's validator (one copy of that
check for the whole instance) and carries its grants out on the result, so
the surface can ask Authorize where the action is known. A PAT reuses
ResolveBasic with the token's own username as the identity — there is no
presented username to compare against in a bearer header — and the same
TokenGrantsAllow read gate the clone path applies.
An instance with no [tokens.sr.ht] section passes a nil validator: meta
PATs and anonymity keep working, a working token is refused, because a
machine credential this instance cannot verify is refused rather than
guessed at. A failed credential is always a refusal and never a downgrade
to anonymous, and backend.go's two error classes are preserved so a caller
can still answer 401 against 503.
core.GrantRead is the vocabulary tokens.sr.ht deliberately does not know.
There is no dolt:write: nothing on that surface writes, and a grant nobody
checks is a promise to an operator that no code keeps.
browse: add TableHash for per-commit change detection
bd: point the Dolt remote at dolt.srht.bigb.es and enable auto-push
bd init wrote the git URL into sync.remote, which Dolt cannot push to. Point
it at the companion database instead and turn on auto-push (30s debounce, 60s
timeout) so a bead created in a session does not sit only in the local store.
bd init: initialize beads issue tracking
ci: run the test suites against a real postgres
Not one of this repository's 28 test files had ever run on the builder: the
manifest went from cache_restore straight to abuild, and options="!check"
meant abuild did not run them either. Every apk published so far was built
from code CI compiled and never executed.
A postgres task brings up a database in the VM and exports DOLTSRHT_TEST_PG;
a test task runs make vet and make test. Measured on this tree, db/ has 10
tests and 9 of them gate on that DSN, so without it the whole persistence
layer is skipped and the build is green regardless; no other package needs
it. Hence the guard: an empty DSN fails the task loudly rather than skipping
every database suite. That guard is what makes !check an honest claim about
where the suites ran instead of a licence to ship untested code, and the
APKBUILD now says so.
make vet and make test rather than bare go commands, because the Makefile is
where -tags gms_pure_go and CGO_ENABLED=0 are named — without the tag either
command pulls go-icu-regex in and wants ICU headers the builder lacks.
remoteapi/integration_test.go prefers DOLTSRHT_TEST_PG and only falls back to
docker run when it is empty, so it never reaches for a daemon the builder
does not have. It is moot either way: the file is behind //go:build
integration and go test ./... does not compile it, as with spike.
No gofmt gate: web/beads.go and four test files are gofmt-dirty on master, so
one would be red on arrival.