Split the priv-socket wire types (PrivRequest/PrivResponse/PrivEvent and
friends) out of hive-sh4re into their own hive-priv-sock crate, mirroring
the existing hive-host-sock split. hive-priv — the root-privileged
helper — now depends on just this narrow protocol crate instead of the
much larger daemon-shared crate, shrinking its dependency surface and
making the privsep boundary easier to audit. No server/client
implementation lives here, only the wire contract; hive-c0re still
depends on hive-sh4re directly for everything else.
Both webhook registrations (knowledge push + config-PR pull_request) now
use the public hive domain instead of loopback:
https://<HYPERHIVE_HIVE_DOMAIN>/webhook/{knowledge,config-pr}
This routes deliveries through the gateway, bypassing the Forgejo SSRF
guard that blocked loopback delivery and silently broke the config-PR
merge flow since launch.
Changes:
- webhook_secret: new module — auto-generate + persist a 32-byte HMAC
secret to STATE_ROOT/webhook-secret on first startup; verify
X-Hub-Signature-256 on every incoming webhook POST (HMAC-SHA256).
- forge/mod.rs: ensure_config_pr_webhook now takes hive_domain +
webhook_secret; sets secret in Forgejo hook config.
- workers/knowledge.rs: ensure_webhook same update.
- dashboard/webhook.rs: both handlers read raw Bytes first, verify HMAC,
then parse JSON. Returns 401 on signature mismatch.
- dashboard/mod.rs: AppState carries webhook_secret; serve() takes it.
- main.rs: load/generate secret at startup; pass to registration tasks
+ dashboard; add 5-minute config-PR polling fallback task.
- forge/config_pr_poll.rs: new — scan agent-configs/* for open PRs with
no pending MergeConfigPr approval; queue them. Idempotent.
- stores/approvals.rs: has_pending_merge_config_pr() for poll dedup.
- nix/modules/hive-gateway.nix: remove dashboardAuth from /webhook/
location (HMAC replaces basic auth for webhook endpoints; Forgejo
cannot send HTTP Basic credentials with webhook deliveries).
- Add `priv_proto::AGENT_RUNTIME_ROOT` to hive-sh4re as the shared
single source for the per-agent runtime root path. hive-priv now
imports it instead of carrying a local const with a stale comment
that still pointed at `coordinator::AGENT_RUNTIME_ROOT` (removed in
#2285/#2367 — moved to `paths::agent_runtime_root()`).
- Add 'must stay in sync' cross-ref comments on both sides of the
privsep boundary:
· priv_proto::META_DIR ↔ paths::meta_root()
· priv_proto::AGENT_STATE_ROOT ↔ paths::AGENTS_ROOT
· priv_proto::AGENT_RUNTIME_ROOT ↔ paths::RUNTIME_ROOT + agent_runtime_root()
· paths::AGENTS_ROOT ↔ priv_proto::AGENT_STATE_ROOT
· paths::RUNTIME_ROOT ↔ priv_proto::AGENT_RUNTIME_ROOT
The dep graph prevents a shared import (hive-sh4re is a leaf; both
hive-c0re and hive-priv depend on it but not each other), so the
lockstep comments are the enforced contract.
Replace the #-number tracker references in code comments with prose
(tracker-tag lint; hive convention is prose in source). No behaviour
change. Branch-protection / collaborator / team / repo API field names
were verified against the live Forgejo swagger.
Agents must no longer create repos with their own forge token (a
write-scoped token otherwise creates + owns repos and can self-merge,
bypassing operator-only-merge). Instead:
- Set max_repo_creation=0 on every agent forge user (marker-guarded
PATCH in sync_agent; covers agents provisioned before this). Blocks
new direct creation; push/PR/clone and existing repos are untouched.
- Provision a c0re-owned 'agents' org (the namespace agent repos land
in) plus an empty 'operators' team inside it. The org owns the repos
so perms stay c0re-managed; the operator joins the team out-of-band.
- create_agent_repo() is the sanctioned path: creates the repo in the
agents org, adds the requesting agent as a *write* collaborator (not
owner), and applies branch protection that whitelists merge + required
approval to the operators team — so the author can't merge its own PR.
- is_hive_managed_namespace() guards 'internal'/'agent-configs'/'core'
against a future create surface passing an explicit owner.
No existing repos are modified. The agent/hivectl surface that invokes
create_agent_repo is a follow-up commit.
The hive Space was created empty — joining it surfaced no rooms because
Matrix doesn't auto-join a Space's children. Provision a default
"hive-chat" room on the matrix sweep, wire it bidirectionally to the
Space (m.space.child on the Space, m.space.parent on the room), and
invite @hive + every agent. The room uses a restricted join rule
allowing any Space member to join, so the operator (a Space member) can
join it from the Space hierarchy without an explicit invite.
Idempotent, mirroring ensure_hive_space: persisted chat-room-id wins,
else rediscover a non-space room named hive-chat, else createRoom. The
space-child link is re-applied each sweep (idempotent PUT) so a
recovered room reconverges its hierarchy link. Room id persisted to
matrix/chat-room-id (0600, survives destroy --purge).