Commit graph hyperhive/hive-c0re/src/forge.rs
Author SHA1 Message Date
müde
d190420946 refactor(hive-c0re): shared additive-migration helper in db
db::apply_migrations runs ALTER lists and ignores duplicate-column
errors (turn_stats' pattern); approvals, operator_questions, broker
reminders, and scheduled_prompts drop their hand-rolled
pragma_table_info guards. broker's acked_at migration stays bespoke —
its backfill must only run when the column was just created
2026-07-06 21:53:48 +02:00
damocles
5d597d92ea fix(#2174): create operators team in agent-configs org too so config-repo branch protection applies 2026-07-03 22:40:38 +02:00
atlas
9496fb060b fix(forge): set mirror_interval so pull-mirrors sync periodically, not on-access
Forgejo's default mirror behaviour syncs on every git access, which
re-introduces external DNS latency at clone time. The hive-ci runner
shares the host netns, so a host-resolver blip turns an otherwise
local clone into a hard failure (data.forgejo.org DNS lookup that
has nothing to do with the repo being cloned).

Fix:
- New MIRROR_INTERVAL const (8h0m0s) used in ensure_mirror_repo.
- On creation: pass interval=MIRROR_INTERVAL in the migrate API body.
- On existing mirror: PATCH mirror_interval on every startup so repos
  seeded before this change converge without manual intervention.

A stale mirror (up to 8 hours old) is fine for CI; a broken clone
from a transient DNS blip is not. The actions/checkout mirror is
refreshed periodically rather than triggered by runner pulls.
2026-07-03 19:32:05 +02:00
damocles
f310b1ce5a config mirror: never force-push — keep forge history on rolled-back deploys 2026-07-03 00:54:32 +02:00
atlas
af7ec98542 doc: ensure_mirror_repo docstring — 409 only, not 409/422 (match the fix) 2026-06-29 00:26:41 +02:00
atlas
64e51fe3bf address argus: 422 from migrate is a validation error, not 'exists'
ensure_mirror_repo treated 409|422 as success (copied from ensure_org, where
422 *does* mean 'org exists'). For the migrate endpoint 422 is a validation
error (bad clone_addr/service); the GET-first check is the real idempotency
guard, so 409 stays as a race guard but 422 now falls through to the bail arm
(→ caller warns) instead of silently dropping a misconfigured mirror.
2026-06-29 00:26:41 +02:00
atlas
53df2c9598 rework(#2072): seed mirrors in c0re startup, not a host-side oneshot
Per mara: the mirror seeding belongs in hive-c0re's forge provisioning
sweep, where the core admin token + org-ensure already live — not a parallel
host-side nix oneshot.

- forge.rs: ensure_mirrors() reads HYPERHIVE_FORGE_MIRRORS (JSON list of
  {upstream,dest}), ensures each dest org (reuse ensure_org) + creates the
  pull-mirror via the migrate API (reuse forge_http, serde_json::json! body,
  409/existing = success). Called in ensure_all() right after the SEEDED_ORGS
  loop (token in scope, warn-and-continue like the other ensure_* steps).
- hive-forge.nix: forward effectiveMirrors to c0re via
  systemd.services.hive-c0re.environment.HYPERHIVE_FORGE_MIRRORS; drop the
  forgejo-seed-mirrors.service + its script + the host-side core-token read.
  Keep the forge.mirrors option, DEFAULT_ACTIONS_URL=self (CI-gated), and the
  dest-shape / no-c0re-namespace-collision assertions.

Verified locally: nix parse + treefmt (incl rustfmt) clean; serde/serde_json
patterns mirror dashboard.rs. cargo build runs in CI (no cc in my container).
2026-06-29 00:26:41 +02:00
atlas
3fedc102cc refactor(#2051): fold body-return into forge_http instead of a near-copy
Per review: rather than adding forge_http_full (a near-duplicate of
forge_http), change forge_http itself to return (StatusCode, String).
Status-only callers bind (status, _); the branch-protection verify path
uses the body to log the real Forgejo rejection reason. Updates all call
sites accordingly.
2026-06-27 19:52:14 +02:00
atlas
512e9ff09f fix(#2051): verify config-repo branch protection actually applied
apply_config_repo_branch_protection treated 200/409/422 from the
create-branch-protection POST all as success. But a 422 means Forgejo
*rejected* the request and created no rule — so a rejected POST silently
left the agent's config repo unprotected, with nothing logged (a new
agent's config repo was found with no main-branch protection and no
trace of why).

Don't trust the status code:
- On any non-201, GET the single .../branch_protections/main rule and
  only treat it as success if the rule is actually present.
- Otherwise return Err carrying the POST's response body, so the real
  Forgejo rejection reason lands in the host journal. (forge_http
  discarded the body; added forge_http_full that returns it.)

ensure_config_repo runs on every sync_agent sweep (startup + each
rebuild), so a now-Err result is logged and retried next sweep —
self-healing once a real cause is fixed. Net: the failure is loud +
retried instead of silently swallowed.

nix fmt clean.
2026-06-27 19:52:14 +02:00
atlas
5caec9c1a9 fix(forge): config-repo main is fast-forward-only (no auto force-push)
Per operator directive: a silent automatic force-push is a bug. The
config-repo merge path already never force-pushes (run_merge_config_pr
lands via ff_push_to_main, a non-force git push). So set the branch
protection's enable_force_push to false — main only ever advances by
fast-forward.

The legacy push_config mirror does force-push (it re-points the status
tags and rewinds main on a failed-build rollback); the protection now
rejects those non-ff updates, so the mirror runs best-effort until the
agent-opened PR-merge flow retires it. Docs + comments updated to match.
2026-06-23 22:37:27 +02:00
atlas
60da6c14b4 fix(forge): reword tracker-tag references in doc comments to prose
Address argus review on the config-repo PR surface: 6 issue-number tags
appeared in Rust doc comments in forge.rs; the tracker-tag lint matches
${'#'}NNN in code comments (markdown docs are exempt, doc comments are not).
Reword to prose, no behavior change.
2026-06-23 22:37:27 +02:00
atlas
2fe79aaef5 feat(#1787): make agent-configs/<n> the agent-editable, PR-merge config surface
Wires the per-agent config repo as the editable PR surface the #1838
merge handler (run_merge_config_pr) consumes, without yet retiring the
push_config force-mirror (that waits for #1838 P2 — the agent-opens-PR
MCP surface — so config changes keep working through the transition).

ensure_config_repo now, after creating agent-configs/<name>:
- adds the agent as a WRITE collaborator (can push config-change branches
  + open PRs);
- branch-protects main core-only via apply_config_repo_branch_protection:
  push + merge whitelists are core-only (only hive-c0re lands on main, via
  its verify-and-ff-push handler), operator-team approval required, the
  agent can't push main directly or self-merge.

The protection sets enable_force_push=true as a TRANSITIONAL allowance so
push_config's applied->main force-mirror keeps working until P2 retires it
(a protected branch otherwise rejects force-push). Forgejo's force-push
allowlist is deploy-keys-only — no per-user list — so this is a plain
enable_force_push toggle; only core is in the push-whitelist so only core
can force-push anyway. At P2, flip it to false and keep core in the
push-whitelist so ff_push_to_main still lands.

Agent code repos (AGENTS_ORG) are intentionally untouched — their merge
flow has no auto-merge handler yet, so switching them to core-only-merge
would strand those PRs.

All steps idempotent (runs on every spawn + startup sweep). Updated the
CONFIG_ORG doc comment + docs/forge.md to drop the "mirror-only" framing.
2026-06-23 22:37:27 +02:00
damocles
0b86295776 feat(#1838): run_merge_config_pr handler (verify pr head, ff forge main, deploy) 2026-06-23 13:12:12 +02:00
atlas
419f4cd92a forge.rs: reword merge-primitives comment to prose (drop tracker tag from code per hive rule) 2026-06-22 22:12:26 +02:00
atlas
376de8f161 feat(#1838): forge.rs merge primitives for the PR-based config flow
Three forge-side fns + a typed error the c0re approve-handler
(run_merge_config_pr, a follow-up) orchestrates to land an operator-approved
config PR:

- pr_head_sha(repo, pr): git ls-remote refs/pull/<pr>/head — the handler's
  primary drift gate (compare against the approved sha).
- ff_push_to_main(repo, sha): the merge — ls-remote main, strict-ancestor
  pre-check (git merge-base --is-ancestor in the agent's applied repo where
  the orchestration has fetched the sha), then a non-force push sha:main. Two
  independent guards so a raced main surfaces as NotFastForward rather than
  clobbering reviewed history.
- mark_pr_merged(repo, pr, sha): POST pulls/<pr>/merge Do=manually-merged
  MergeCommitID=sha; best-effort HeadDrift detection on non-2xx via a PR-head
  re-read (the handler's pre-merge re-read is the primary gate).

ForgeMergeError {HeadDrift, NotFastForward, Other} (hand-rolled Display/Error/
From<anyhow::Error>, no new dep) so the handler can match recoverable drift
(refresh + re-verify) vs hard-fail. Core token sourced internally.

Uncalled until the handler lands (pub lib API, no dead_code). clippy -D,
unit tests, and treefmt clean.
2026-06-22 22:12:26 +02:00
atlas
2b4e928afe feat(#1867): make the internal forge mandatory (remove forge.enable)
The internal forge is the canonical store for the meta flake, every
agent's config repo, and the internal/* repos, so it can no longer be
optional. Remove the services.hyperhive.forge.enable toggle:

- hive-forge.nix: drop the `enable` option; the forge config now
  deploys gated on `services.hyperhive.enable` (it ships with hyperhive).
- hive-c0re.nix: HIVE_FORGE_URL env unconditional; forge-public-URL gate
  drops the enable check (keeps behindGateway).
- hive-gateway.nix: local /etc/hosts forge entry keyed on behindGateway.
- hive-ci.nix: drop the now-moot `forge.ci.enable requires forge.enable`
  assertion (forge is always present); reword the option doc.
- nix/docs/default.nix: drop the `forge.enable = mkForce false` stub
  (option gone); the options-doc eval stays light via hyperhive.enable.
- hive-c0re forge.rs / hivectl.rs: reword 'forge.enable = true' error
  text to 'wait for hive-c0re to start the container' (the runtime
  token-absent path is unchanged — it's a bootstrap-timing check, not
  the opt-out).
- docs/approvals.md, docs/ci.md: drop stale forge.enable references.

Migration: configs that set `services.hyperhive.forge.enable = false`
must drop the line — the forge is now mandatory.

Prereq/companion to #1838 (PR-based config flow, which assumes the forge
is always present).
2026-06-22 19:26:34 +02:00
atlas
27ac0153c4 forge authz: scrub tracker tags from comments
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.
2026-06-21 22:36:45 +02:00
atlas
f1d54ce12c agent surface: create_repo through hive-c0re (#1787)
Closes the #1787 loop — the sanctioned create path now that agents
can't create repos directly. Adds:

- wire: Request::CreateRepo{repo} + Response::RepoCreated{full_name,
  clone_url} (hive-sh4re).
- agent_server: dispatch_shared arm + handle_create_repo — validates the
  repo name, then forge::create_agent_repo (org-owned repo, agent=write
  collaborator, operator-team branch protection). Returns the full name
  + clone url so the agent can git clone immediately.
- MCP: create_repo tool + CreateRepoArgs in the harness.
- a new opt-in ToolGroup::Forge (=[create_repo]) so the operator
  controls which agents can spin up repos (least privilege).

Workspace clippy -D warnings, cargo test, nix fmt all green.
2026-06-21 22:36:45 +02:00
atlas
867be7bb98 hive-c0re: block direct agent repo creation, add c0re-mediated create (#1787)
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.
2026-06-21 22:36:45 +02:00
damocles
3cac374c60 forge: revalidate the core token against the live forge before trusting it 2026-06-06 11:26:59 +02:00
damocles
14c7b0d406 feat: group host-side /var/lib/hyperhive state into db/ forge/ matrix/ run/ subdirs with startup migration 2026-06-05 23:01:47 +02:00
atlas
734fe88858 fix(ci): unblock nix flake check after clippy 0.1.95 bump (#1368)
The nixpkgs bump to clippy 0.1.95 / cargo 1.95.0 added + strengthened a
large batch of lints. CI denied ALL warnings (`-D warnings`) against the
`pedantic = warn` workspace lint, so the bump hard-failed `nix flake
check` workspace-wide with zero code changes — and would recur on every
future clippy bump.

Posture fix (the durable part): CI now runs
`-D warnings -A clippy::pedantic`, so the default/correctness/style lints
stay a hard gate while the "extra, opinionated" pedantic group is
advisory only (still `warn` for local `cargo clippy` via the workspace
lints table, just non-blocking in CI). `-A` rather than `-W` so the
group drop doesn't re-enable the specific pedantic lints the workspace
allows (e.g. `must_use_candidate`).

Also fixes the genuine DEFAULT/STYLE lints the bump surfaced across the
workspace (doc_lazy_continuation, collapsible_if, ptr_arg,
match_like_matches_macro, …) via `cargo clippy --fix` + manual stragglers
(`too_many_arguments` #[allow] on the host-config constructors), and
three tests that had rotted while the CI runner was offline (#1221):
- topology::top_level_agents_in_multi_root — hardcoded unsorted expected
- rebuild_queue::depends_on_evicted_dep_counts_as_resolved — needs
  MAX_HISTORY_PER_KIND newer terminals to evict, not one
- coordinator::agent_paths doctest — illustrative pseudo-code, now `ignore`

Validated: clippy + formatting + cargo-test checks all pass.
2026-06-05 15:32:07 +02:00
atlas
7022cd3826 fix: split WriteAgentStateFile into WriteAgentForgeToken + WriteAgentMatrixToken
Addresses mara's review: each credential type gets its own PrivRequest
variant, making the exact priv surface visible in the wire protocol.
No runtime filename dispatch — the operation name is the gate.

- WriteAgentForgeToken { agent_name, token } → state/forge-token
- WriteAgentMatrixToken { agent_name, token } → state/matrix-token
- priv_client: two typed fns (write_agent_forge_token, write_agent_matrix_token)
- forge.rs: split mint_and_persist_token into mint_and_persist_agent_token
  (priv) + mint_and_persist_core_token (direct write); drop dead token_path fn
- matrix.rs: call write_agent_matrix_token directly
2026-06-04 14:30:01 +02:00
atlas
89092caba4 fix: restrict WriteAgentStateFile to explicit filename allowlist
Addresses mara's security review: replace validate_state_filename (which
accepted any non-traversal filename) with a tight allowlist containing
only the two known credential filenames: forge-token and matrix-token.

Also addresses argus review feedback:
- drop issue tag from priv_proto.rs doc comment
- add comment explaining the path-detection heuristic in forge.rs
- add note about create_dir_all uid=0 edge case in write_agent_state_file
2026-06-04 14:30:01 +02:00
atlas
eb51362d50 fix: route forge/matrix token writes through hive-priv
hive-c0re runs as the unprivileged hive-core user (privsep from #702)
and cannot write to agent-owned state directories. forge-token and
matrix-token writes were failing with EACCES on every startup sweep.

Add WriteAgentStateFile to PrivRequest: hive-priv (root) writes the
file 0600 and chowns it to the agent user so the agent can read it.

- hive-sh4re: add AGENT_STATE_ROOT constant + WriteAgentStateFile variant
- hive-priv: validate agent name + filename (no traversal), write via root
- priv_client: add write_agent_state_file helper
- forge: mint_and_persist_token routes agent paths through priv
- matrix: ensure_user_for routes matrix-token through priv

Closes #1257
2026-06-04 14:30:01 +02:00
damocles
5fdc3ffdbf fix: remove issue ref from comment, use forge::FORGE_HTTP in knowledge.rs 2026-06-04 12:31:25 +02:00
atlas
34bc4c0b06 fix: route forge_admin through hive-priv; auto-recover matrix passwords
forge_admin() spawned nixos-container run hive-forge directly from the
hive-core process. nixos-container run uses nsenter to enter the container
namespaces, which requires root. hive-core is unprivileged, so every call
failed with: nsenter: stat of /proc/<pid>/ns/user failed: Permission denied

Fix: add RunForgeAdmin { args } to PrivRequest. hive-priv (root) handles
it by spawning nixos-container run hive-forge -- runuser -u forgejo --
forgejo --work-path /var/lib/forgejo admin <args>. forge_admin() now calls
priv_client::run_forge_admin().

matrix: ensure_user_for hit M_USER_IN_USE then failed when the stored
password file was missing (state dirs wiped but homeserver kept accounts).
Previously required manual hivectl matrix reset-password <name>.

Fix: add auto_reset_password() — calls the admin API (PUT
/_synapse/admin/v2/users/@<name>:<server> with the hive admin token) to
set a new random password, then proceeds with login. Falls back to the
existing manual-recovery error if the admin token is unavailable.

Closes #1234
2026-06-03 23:21:45 +02:00
damocles
cd1f77b640 fix(#1198): create internal/knowledge as public, patch existing private repos 2026-06-03 21:39:31 +02:00
damocles
cfd677ae7d fix review: rename hive-knowledge → knowledge throughout, drop false re-clone claim from pull() doc 2026-06-03 20:56:17 +02:00
damocles
41befe3839 feat(#1167): hive-wide knowledge repo — forge, local clone, bind-mount, webhook 2026-06-03 20:56:17 +02:00
atlas
aa6d5b9ce1 fix: remove issue tags from forge.rs comments 2026-06-03 11:23:26 +02:00
atlas
0850128090 fix(#1012): guard ensure_user_email with marker; add login_name to PATCH body
sync_agent called ensure_user_email on every tick, which sent
PATCH /api/v1/admin/users/{name} without the required login_name
field. Forgejo's EditUserOption handler was resetting use_custom_avatar
to false on each call, clobbering the avatar uploaded by the
forge-avatar-sync one-shot service.

Two-part fix:
- add login_name to the PATCH body (Forgejo EditUserOption requires it)
- write EMAIL_ALIGNED_MARKER_PREFIX{name} on first success; skip the
  PATCH on all subsequent sync_agent ticks

Marker follows the same one-shot pattern as CORE_AVATAR_MARKER and
CONFIG_ORG_AVATAR_MARKER. Delete the marker to force re-alignment.
2026-06-03 11:23:26 +02:00
müde
e7b6896e4c fix(#702): give hive-priv a PATH; route forge/matrix list through priv
hive-priv had no PATH, so the nixos-container it runs for every container
op (incl. lifecycle::list) failed ENOENT. build_all swallows that into an
empty list — the 'no managed containers' symptom. Give the helper the
minimal set nixos-container shells out to (nixos-container, nix, util-linux,
e2fsprogs) on top of the systemd/coreutils/findutils already in the unit
PATH, rather than the whole system profile.

Also route forge/matrix is_present() through priv_client::list_containers
instead of spawning nixos-container directly from unprivileged hive-c0re.
2026-06-03 00:05:17 +02:00
damocles
89665b94de feat(#1014): rename manager agent root→ruth across all crates + frontend 2026-06-02 18:18:06 +02:00
atlas
4bff450343 feat(gateway): hivectl gateway user management + fix htpasswdFile assertion
Add `hivectl gateway {create-user,delete-user,list-users}` subcommands for
managing htpasswd files used by gateway Basic auth. Pure Rust bcrypt
(cost 12, $2y$ prefix nginx accepts). No external htpasswd binary required.

Also fix the NixOS module assertion: `cfg.auth ? htpasswdFile` is always
true in the module system (declared options always exist as keys); switch
to `nullOr path; default = null` + `!= null` check so the assertion
actually fires with a useful error when enable=true but no file is set.
Guard bind-mount and nginx config against null to prevent eval errors.

Update docs/gateway.md to show hivectl commands instead of raw htpasswd.
2026-06-01 23:25:28 +02:00
atlas
5c5ca38fe8 fix(#999): resolve all clippy warnings across the workspace
All crates now pass `cargo clippy --workspace -- -D warnings` cleanly.

Fixes span six crates (hive-sh4re, hive-ag3nt, hive-c0re, hive-forge,
hive-priv, hive-matrix-mcp was already clean):

- doc_markdown: wrap snake_case, type names, constants in backticks
- collapsible_if / collapsible_match: fold nested ifs into let-chains
- duration_suboptimal_units: Duration::from_secs(N) → from_mins/from_hours
- implicit_hasher: allow on HashMap-param fns where generalization is risky
- items_after_statements: hoist use to function tops
- map(f).unwrap_or(x) → map_or(x, f); map(f).unwrap_or_else(g) → map_or_else
- is_ok_and / is_none_or in place of map().unwrap_or(bool)
- needless_continue: {} instead of continue in loop match arms
- match_same_arms: Ok(None) | Err(_) merged
- format_push_str: write!() instead of push_str(&format!())
- while let replaces loop { let Some(..) = x else { break } }
- struct_excessive_bools / dead_code: allow on purpose-built structs
- too_many_lines / too_many_arguments: allow where refactor not worth it
- unused_async: remove async from poll_once in bash_runner
- needless_borrow: fix &repo deref in hive-forge comments verb
- cast_possible_truncation: allow u64→usize in fetch_tail

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 22:31:06 +02:00
atlas
286a7c8fd3 fix(#975): use hyperhive.local email domain to pass Forgejo validation
`@hyperhive` (no dot) is rejected by Forgejo's PATCH /admin/users API
with 422 Unprocessable Entity. Switch to `@hyperhive.local` across all
four files that construct hive-c0re/agent git emails and Forgejo account
emails, so both stay in sync and pass RFC-valid domain validation.
2026-06-01 18:28:22 +02:00
damocles
9bd37d4df3 fix(#4): rename shared org from org-shared to internal 2026-06-01 13:27:47 +02:00
damocles
6044cd4d79 feat(#4): seed org-shared/docs repo, grant every agent read access 2026-06-01 13:27:47 +02:00
damocles
5522d65074 docs: add token scopes section to forge.md, trim forge.rs module doc + scope comments 2026-06-01 11:38:42 +02:00
damocles
dd07ca946e hive-c0re: scrub host-integration cookies (#715 batch 8) 2026-05-31 17:50:45 +02:00
damocles
ef968c11dd c0re: dedupe chown_to_agent into lifecycle (argus #678) 2026-05-31 00:40:40 +02:00
damocles
0cf703a939 c0re: chown per-agent state writes to agent uid:gid (#673) 2026-05-31 00:40:40 +02:00
damocles
97797cf790 hivectl: extend --password to forge; clarify matrix M_USER_IN_USE asymmetry (#663 / argus) 2026-05-30 23:27:26 +02:00
damocles
18253bf2f5 hivectl: print token to stdout for non-agent users (#662) 2026-05-30 21:55:58 +02:00
damocles
0b83e5a69e forge: on 403 from PATCH user email, hint operator to delete core token (#646 argus nit) 2026-05-30 19:08:16 +02:00
damocles
745d95d2f5 forge: core token gets admin scope so PATCH /admin/users/* works (#646) 2026-05-30 18:14:31 +02:00
damocles
71abf2471e forge: replace removed forgejo admin user edit CLI with PATCH /api/v1/admin/users/{name} (closes #574) 2026-05-29 16:39:50 +02:00
iris
73684fb00a rust+nix: load static assets at runtime, drop build.rs (#555)
Cuts every `include_bytes!`/`include_str!` of a non-rust path in
the workspace over to runtime file loads from `$HIVE_ASSETS_DIR`
(the `hyperhive-assets` derivation introduced in the previous
commit). After this commit the rust derivation has no compile-time
dependency on `branding/*` or `hive-ag3nt/prompts/*` anymore.

Call-site flips:

- `hive-c0re/src/forge.rs::CORE_AVATAR_PNG` /
  `CONFIG_ORG_AVATAR_PNG`: were `include_bytes!` of
  `branding/hyperhive.png` and `$OUT_DIR/agent-configs.png`. Now
  `ensure_core_avatar` / `ensure_config_org_avatar` `tokio::fs::read`
  via `hive_sh4re::assets::{core_avatar_png, config_org_avatar_png}`
  at startup. The `agent-configs.png` is now rendered by the
  `hyperhive-assets` derivation's rsvg-convert step (was
  `hive-c0re/build.rs` + librsvg on the rust derivation's
  nativeBuildInputs — both gone in the next commit).
- `hive-ag3nt/src/prompt.rs::TEMPLATE`: `render` now takes the
  template as an argument; `write_system_prompt` reads it once from
  `$HIVE_ASSETS_DIR/prompts/system.md` before calling render. The
  test module still `include_str!`s the production template so
  `cargo test --workspace` doesn't need `HIVE_ASSETS_DIR` set —
  this is the only remaining compile-time reference to the file
  from the rust workspace, gated to `#[cfg(test)]`.
- `hive-ag3nt/src/turn.rs::CLAUDE_SETTINGS`: was `include_str!`'d
  and written via `tokio::fs::write`; now `tokio::fs::copy` from
  `$HIVE_ASSETS_DIR/prompts/claude-settings.json` into the
  per-agent socket dir.
- `hive-ag3nt/src/web_ui.rs::DEFAULT_ICON`: was `include_str!`'d;
  now read on-demand from `$HIVE_ASSETS_DIR/branding/hyperhive.svg`
  inside `serve_icon`. Falls back to an empty body if missing so
  the endpoint never panics on a misconfigured container (matches
  the existing "per-agent icon.svg override" fallthrough).

`HIVE_ASSETS_DIR` wiring:

- Inside containers: `nix/templates/harness-base.nix`
  `environment.variables` sets it to
  `${pkgs.hyperhive-assets}/share/hyperhive` (resolved through
  the default overlay applied in `mkContainer`). Verified by
  building `agent-base-toplevel` and grepping the resulting
  `/etc/set-environment`.
- Host-side: `nix/modules/hive-c0re.nix` adds an `assets` option
  defaulting to `hyperhive.packages.${system}.assets`, threaded
  in from the flake's nixosModules wiring, and sets the same env
  var on the `hive-c0re` systemd unit so the daemon's
  `forge::ensure_*_avatar` startup hooks find the PNGs.

`hive-c0re/build.rs` deleted entirely; `[package].build` removed
from `hive-c0re/Cargo.toml`; rsvg-convert dependency lives in the
assets derivation only.

Validated: `nix build .#default .#checks.x86_64-linux.clippy
.#agent-base-toplevel .#manager-toplevel --fallback` all succeed.
`/etc/set-environment` in the toplevel shows
`HIVE_ASSETS_DIR="/nix/store/.../hyperhive-assets-0.1.0/share/hyperhive"`.
2026-05-29 12:59:48 +02:00
iris
a444774ab3 forge: auto-set agent-configs org avatar on core start (#424)
Sibling to ensure_core_avatar (#320). Same one-shot marker-guarded
upload pattern, this time aimed at the Forgejo per-org avatar
endpoint (`POST /api/v1/orgs/{org}/avatar`).

## SVG-to-PNG at build time

Mara: *don't check in the png. instead generate png on the fly or
in build.*

`hive-c0re/build.rs` renders `branding/agent-configs.svg` →
`$OUT_DIR/agent-configs.png` via `rsvg-convert` (librsvg) on every
compile; `forge.rs` then `include_bytes!`s the OUT_DIR PNG. The
raster never gets checked into git — SVG stays source of truth,
the PNG is a build artifact.

- `hive-c0re/Cargo.toml`: declares `build = "build.rs"`
- `flake.nix`: adds `librsvg` to `naersk-lib.buildPackage`
  `nativeBuildInputs` (covers both the runtime package and the
  clippy check derivation) and to the dev shell so local
  `cargo build` finds `rsvg-convert` on PATH.
- For dev builds outside Nix, install librsvg (Debian:
  `librsvg2-bin`, macOS: `brew install librsvg`).

## Icon design

Sibling visual to the main hyperhive mark — same dark base + outer
ring + corner-bracket frame so the family reads at a glance. Centre
swaps the hexagonal hive for a stacked-config-files motif: three
offset sheets, folded-corner affordance, curly-brace `{ }` glyph
telegraphing "config file."

Brace font-size dropped 78→56 + letter-spacing -3 (#424 mara:
"braces cross the boundaries of the page") so the glyphs sit
comfortably inside the 120-wide front sheet with clear breathing
room on the left/right edges. The stack is shifted so the front
sheet centres on canvas-(150, 150); brace text anchors there with
`dominant-baseline=central` for true vertical centring.

## Validation

- `cargo check` clean (only pre-existing warnings).
- One-shot marker honoured: re-runs of `ensure_all` skip after the
  first success; `rm /var/lib/hyperhive/forge-agent-configs-avatar-set`
  forces re-upload (useful for icon revisions).
- Behaviour mirrors the existing `ensure_core_avatar` pattern.

Browser smoke test isn't possible from inside iris's container.
Worth eyeballing post-deploy: `http://localhost:3000/agent-configs`
should show the new avatar where the default identicon used to be.
2026-05-26 00:10:52 +02:00