~bigbes/sr-ht-spec

ref: 471d9706ec6f3b9bdb5c2726b9f6c4fbf90ddca4 sr-ht-spec/web/server.go -rw-r--r-- 11.5 KiB
471d9706 — Eugene Blikh 9 days ago
chrome: the resolved favicon and the queue as a shared table
c87a11bd — Eugene Blikh 9 days ago
chimw: the request line, the HEAD twins and the routing refusals
be33cce1 — Eugene Blikh 9 days ago
instconf: one reading of this instance's origins
643fa0b5 — Eugene Blikh 9 days ago
logging: log through slog and scribe rather than stdlib log

sr-ht-ecore's panic middleware now reports through log/slog, and it reports
through the *default* logger — nothing can hand a middleware in another module
this service's *slog.Logger. So the daemon's scribe handler becomes the one
install point, and the packages that were still calling log.Printf go through
the default logger too: the read plane's render and encode failures, its 5xx
mapping, and the credential resolver's fail-closed line. Each carries the fields
that used to be interpolated into the sentence — method, path, status, page,
doc — and the error itself through scribe.Err.

The handler grows what it was missing: file:line, because most of what reaches
it is a failure and 'which of the six render sites' is the first question;
colour dropped when stderr is not a terminal; and the masks. This daemon handles
the unified-login cookie and tokens.sr.ht working tokens, and a struct logged
whole is how a live credential outlives its own request in a log file — masking
in the handler covers the log line nobody reviewed as well as these.

cmd/specsrht-migrate keeps stdlib log on purpose: it is a one-shot CLI whose
'specsrht-migrate: ...' progress an operator reads at the terminal during an
upgrade, and log.Fatalf is its error exit.
e756d504 — Eugene Blikh 9 days ago
web: draw the whole web tier from sr-ht-ecore

The chrome moved last time; this moves the four packages around it. pages
discovers the page templates and carries the shared error body, so the
hand-maintained page list, the render helper and error.html go; assets finds the
hashed stylesheet and serves the static tree, so the local regexp, the href glob
and handleStatic go; middleware brings the private-cache policy and a panic
guard that aborts a connection rather than appending an error page to a
truncated one; and ecoretest replaces the hand-built config and the TestMain
that minted its own keys.

The one that matters is csrf. The guard was a predicate three handlers
remembered to call, which made unprotected the default for any form added
later. It is now csrf.Require on the router, so it covers the routes that are
not written yet, it runs before routing — an unrouted POST is refused rather
than 404'd — and the refusal is the shared sentence. Host comparison becomes
case-insensitive, which is strictly more permissive and only for an operator
typo in the config.

threads.html becomes _threads.html, the partial spelling pages.Load discovers,
and the review page takes container-fluid: two prose columns do not fit the
centred container, which is what cover and dolt already concluded for their wide
views.

What stays here: the sentinel-to-status mapping in fail, and a renderError that
wraps ecore's error body in this service's view struct.
53e56db2 — Eugene Blikh 9 days ago
web: draw the chrome from sr-ht-ecore

The nav/service-switcher, the login block, the environment banner and the
brand were this service's own copy of code compare.sr.ht had already copied
from somewhere else. They come from sourcecraft.dev/bigbes/sr-ht-ecore/chrome
now: one chrome.Service built at startup from the shared config.ini, one
chrome.Page per request, embedded in viewData so the shared partials find
their fields on the dot.

web/chrome.go is gone — buildNav, navItem, canonIndex, the login/logout/
profile URL building and the chrome half of viewData with it. The layout
renders srht-env-banner and srht-nav instead of the local markup, the landing
page's space list renders through srht-repo-list, and the template FuncMap
starts from chrome.Funcs() (the local shortsha was a duplicate of the shared
one). sameOrigin and the login redirect ask the chrome for our origin rather
than keeping a second copy that could disagree with the links on the page.

Three of ecore's policies differ from what this service did, and win, per that
package's own doc: [sr.ht]site-name defaults to "sr.ht" rather than
"sourcehut" and [sr.ht]environment to "development" rather than "production"
when the key is absent, and the brand carries a fixed 15rem min-width so the
switcher starts at the same x on every service. The instance's config.ini sets
both keys, so on it only the brand width is visible.

The nav tests that only restated ecore's rules — switcher order, the
paste/pages/hub exclusion, the shape of a login URL — are dropped; ecore tests
those. What is left covers this service's seam: that the identity authn
resolved is the one the chrome is handed.
a4d8cc52 — Eugene Blikh 9 days ago
authn: remove the local agent-token plane

The agent_token table and everything that minted, verified, listed or revoked
it. Agent credentials are tokens.sr.ht working tokens now: signed, expiring,
owned by a meta.sr.ht account and carrying grants, verified locally by
sr-ht-ecore's bearer package. One door, and nothing behind it — a credential the
instance plane refuses is refused, where it used to be offered to a second store
that might say yes.

DEPLOY GATE: do not deploy this until every agent configured with the shared
secret holds a tokens.sr.ht token with spec:propose (spec:read to read).
Migration 0005 drops the table, migrate-on-upgrade runs it on deploy, and
deploying early locks out every agent at once — including the SSH push path.

hooks/ no longer reads agent_token directly. The push path goes through the same
authn.Resolver the HTTP surfaces use and demands spec:propose, because a push by
an agent is a proposal by another transport. The refs rule and the provenance
requirement are untouched on both counts: a universal grant is still only an
agent to the receive path, and X-Agent / X-Agent-Session are still mandatory on
every agent write.

bearer.ErrNotOurs is now a permanent refusal. A meta.sr.ht PAT used to fall
through to the local store and miss there; with no store to fall through to it
earns a 401 rather than the 503 an unclassified error would.

[tokens.sr.ht] origin becomes a required config key: with no issuer there is no
credential to check, so the daemon fails startup instead of serving reads and
refusing every agent write one request at a time.

/tokens redirects to the daemon that issues (tokens SPEC ch. 7).
8219ede1 — Eugene Blikh 13 days ago
feat(web,service): the owner mints and revokes agent tokens in a browser

Issuing a credential required SSH to the host, which made the remote
agent write plane unusable from anywhere else: to hand an agent a token
the owner had to be at the machine. /tokens is that page — list, mint,
revoke — behind the same owner-only gate and same-origin guard as
approve/reject.

The mint is owner-only, and that rule is what revocation depends on: an
agent allowed to mint would survive having its own credential revoked by
issuing itself another, and "revoke the token" is the entire incident
response this design has. An agent asking for the page gets 403 rather
than the read plane's login redirect — it is authenticated already, so
bouncing it to meta would answer a question it did not ask.

The plaintext is rendered in the response to the POST rather than after a
redirect. A redirect would either drop the secret or carry it in a URL,
where it lands in history and in every proxy log on the way; the cost is
that a reload re-submits and mints a second token, which is one click to
revoke on that same page, whereas a lost token is not recoverable.

service.IssueAgentToken/ListAgentTokens/RevokeAgentToken hold the ACL and
the mint, and `specsrht token` now goes through them too, so the CLI and
the page cannot drift into two ideas of what issuing a token is.

spec-ejq.3
2a7569f4 — Eugene Blikh 27 days ago
feat: web — the Phase 2 read plane UI and its SCSS entry

SourceHut chrome (nav service-switcher, login block, environment banner,
error page, embedded hashed static assets) ported from compare.sr.ht, plus
the read-plane pages: landing, space document tree, rendered document with
frontmatter/backlinks, and search.

The URL grammar is the design's pinned one: a document's address carries no
extension, ".md" is raw source, ".json" is metadata plus body, and ?rev=
pins any of the three to an immutable revision. Links rendered inside a
pinned page keep the pin, so following one does not silently land on the
approved head.

One human and no visibility levels, so the read ACL is one line: the owner
and its agents read, everyone else is redirected to meta's login (a browser)
or refused with 401 (a client asking for .md/.json).