From cd1f77b6407523904d09e0bef2c04d7133108c02 Mon Sep 17 00:00:00 2001 From: damocles Date: Wed, 3 Jun 2026 21:29:37 +0200 Subject: [PATCH] fix(#1198): create internal/knowledge as public, patch existing private repos --- hive-c0re/src/forge.rs | 82 +++++++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 36 deletions(-) diff --git a/hive-c0re/src/forge.rs b/hive-c0re/src/forge.rs index 829bbb4f..0d4b1bd7 100644 --- a/hive-c0re/src/forge.rs +++ b/hive-c0re/src/forge.rs @@ -55,10 +55,10 @@ const SHARED_ORG: &str = "internal"; /// The shared docs repo inside `SHARED_ORG`. Cloneable by every agent /// at `{FORGE_HTTP}/internal/docs.git`. const SHARED_DOCS_REPO: &str = "docs"; -/// The hive-wide knowledge repo inside `SHARED_ORG`. Bind-mounted -/// read-only into every container at `/knowledge`. Agents -/// contribute by forking + opening PRs; direct writes to main are -/// not granted. See `hive-c0re/src/knowledge.rs`. +/// The hive-wide knowledge repo inside `SHARED_ORG`. Public — agents +/// can fork it and open PRs without explicit collaborator grants. +/// Bind-mounted read-only into every container at `/knowledge`. +/// See `hive-c0re/src/knowledge.rs`. const KNOWLEDGE_REPO: &str = crate::knowledge::REPO; /// Forgejo orgs hive-c0re ensures on startup. The meta repo lives at /// `core/meta` (the `core` user's own namespace — no org needed). @@ -470,6 +470,37 @@ fn repo_body(name: &str) -> String { format!(r#"{{"name":"{name}","auto_init":false,"private":true,"default_branch":"main"}}"#) } +/// JSON body for a public, empty repo defaulting to `main`. +fn repo_body_public(name: &str) -> String { + format!(r#"{{"name":"{name}","auto_init":false,"private":false,"default_branch":"main"}}"#) +} + +/// Set an existing repo to public visibility. No-op if the repo is +/// already public. Used for `internal/knowledge` which may have been +/// created as private on an older deployment. +async fn set_repo_public(owner: &str, repo: &str, token: &str) -> Result<()> { + let url = format!("{FORGE_HTTP}/api/v1/repos/{owner}/{repo}"); + let status = forge_http(reqwest::Method::PATCH, &url, token, r#"{"private":false}"#).await?; + match status.as_u16() { + 200 => { + tracing::debug!(%owner, %repo, "forge: repo set to public"); + Ok(()) + } + other => anyhow::bail!("PATCH {owner}/{repo} (set public) returned HTTP {other}"), + } +} + +/// Create `name` inside org `org` as a public repo. Idempotent. +async fn ensure_org_repo_public(org: &str, name: &str, token: &str) -> Result<()> { + create_repo( + &format!("{FORGE_HTTP}/api/v1/orgs/{org}/repos"), + &repo_body_public(name), + token, + &format!("{org}/{name}"), + ) + .await +} + /// POST a repo-creation request to `url` and fold "already exists" /// (HTTP 409 / 422) into success. `label` is `/` — purely /// for log + error context. @@ -611,32 +642,17 @@ pub async fn shared_docs_access(name: &str, core_token: &str) -> Result<()> { } } -/// Ensure the `internal/knowledge` repo exists. Called once at -/// startup after `ensure_org(SHARED_ORG)`. Idempotent — `ensure_org_repo` -/// treats 409 as success. +/// Ensure the `internal/knowledge` repo exists and is public. +/// Called once at startup after `ensure_org(SHARED_ORG)`. Idempotent. +/// +/// The repo is created as public so any agent with a forge account can +/// fork it and open PRs to contribute. Existing deployments that ended +/// up with a private repo are patched to public on the next hive-c0re +/// startup via `set_repo_public`. pub async fn ensure_knowledge_repo(core_token: &str) -> Result<()> { - ensure_org_repo(SHARED_ORG, KNOWLEDGE_REPO, core_token).await -} - -/// Grant agent `name` read-only collaborator access to -/// `internal/knowledge`. Agents read documents from the bind-mounted -/// clone and contribute by forking + opening PRs — no write to main. -/// Idempotent: HTTP 204 (already a collaborator) is treated as success. -pub async fn knowledge_access(name: &str, core_token: &str) -> Result<()> { - let url = format!( - "{FORGE_HTTP}/api/v1/repos/{SHARED_ORG}/{KNOWLEDGE_REPO}/collaborators/{name}" - ); - let body = r#"{"permission":"read"}"#; - let status = forge_http(reqwest::Method::PUT, &url, core_token, body).await?; - match status.as_u16() { - 204 => { - tracing::info!(%name, "forge: granted knowledge read access"); - Ok(()) - } - other => anyhow::bail!( - "PUT {SHARED_ORG}/{KNOWLEDGE_REPO}/collaborators/{name} returned HTTP {other}" - ), - } + ensure_org_repo_public(SHARED_ORG, KNOWLEDGE_REPO, core_token).await?; + // Ensure public even if the repo already existed as private (older deployment). + set_repo_public(SHARED_ORG, KNOWLEDGE_REPO, core_token).await } /// Grant agent `name` read-only collaborator access to `core/meta` on @@ -818,13 +834,7 @@ pub async fn sync_agent(name: &str, core_token: Option<&str>) { { tracing::warn!(%name, error = ?e, "forge: shared_docs_access failed"); } - // Grant read-only access to internal/knowledge so the agent can - // read the bind-mounted knowledge repo and fork it to open PRs. - if let Some(token) = core_token - && let Err(e) = knowledge_access(name, token).await - { - tracing::warn!(%name, error = ?e, "forge: knowledge_access failed"); - } + // internal/knowledge is public — no per-agent collaborator grant needed. } /// Sweep every existing container (manager + sub-agents) and ensure