forgejo-api escapes the whole file path of a raw-file request, so the
rules file went out as `raw/.hyperhive%2Freview-classifier.yml` and
reached the forge only if every proxy and router in between decoded the
`%2F`. The controller now sends that one request itself: `raw_file_url`
escapes each path segment on its own and joins them with `/`, and
`forge::Client` holds a second HTTP client with the same token for it.
A 404 from the default-branch lookup now means "no commit on the
default branch", like an empty repo, instead of falling through to the
route's "no repo" answer. Only the repo lookup's 404 still means the
repo doesn't exist.
Tests cover the URL shape, the reads against a loopback stub forge
that serves only the literal path, the branch 404, 503 with no forge
access, 503 at the forge deadline on a paused clock, and a bad
polarity value.
Part of #5096.
forgejo-api's client has no request timeout, so a forge that accepts a
connection and never answers held a forge-calling job node forever:
the node never reached Done or Failed, and kept its executor task.
Every executor arm that awaits the forge now runs its work under the
same deadline the periodic passes use. On elapsing, the node fails
through its existing Outcome::Failed path with
"<node>: no answer from the forge within 120s".
Nodes: create_repo, create_forge_user, add_repo_member,
grant_docs_read, init_agent_config_repo, mint_agent_forge_token,
mint_hive_forge_token.
The bound is per node, not per request: the nodes make 1 to about 15
sequential forge and store requests each (mint_hive_forge_token the
most), and the requests live inside forge::Client methods the periodic
passes share. One 2-minute bound covers both, so #5065's
within_pass_deadline / PASS_DEADLINE become within_forge_deadline /
FORGE_DEADLINE, with a log line that no longer says "retrying next
tick" (true of a pass, not of a node).
The InitAgentConfigRepo arm is lifted out of run_swarm_node, like its
siblings, to keep that function under clippy::too_many_lines.
Refs #5066
forgejo-api 0.11.1 builds its reqwest client with no timeout and offers
no way to hand it a caller-built one (the `client` field is private,
`new`/`with_user_agent` are the only constructors, `Endpoint` is
sealed). A forge that accepts a connection and then stalls therefore
held each of the six periodic reconcile loops on one request forever,
with no log line.
Wrap each loop's per-tick work in `forge::within_pass_deadline`
(`PASS_DEADLINE`, 2 min, under the 5-minute interval): on elapsed the
pass is dropped, a warn is logged, and the loop waits for its next tick.
Every pass is an idempotent reconcile, so abandoning one mid-way leaves
nothing the next pass does not redo.
Loops: config-pr poll, runner registration token, hive forge tokens,
agent forge tokens (observe + legacy sweep), docs grants, swarm forge
objects.
Refs #5060
Agent creation gains a GrantDocsRead node (after CreateForgeUser) that adds
the agent as a read collaborator on internal/docs, and forge::docs_grant
runs a pass over the store's hive-agent-* roster at start and every five
minutes that queues the same job for any agent that is not one. The pass only
adds; an unreadable roster or forge read changes nothing.
hive-c0re drops shared_docs_access, its call in sync_agent, and the
SHARED_ORG / SHARED_DOCS_REPO constants only it used.