feat(#4): seed org-shared/docs repo, grant every agent read access
This commit is contained in:
parent
ae6d23594d
commit
6044cd4d79
1 changed files with 74 additions and 3 deletions
|
|
@ -1,6 +1,8 @@
|
|||
//! Optional Forgejo wiring — per-agent user + token provisioning,
|
||||
//! config-repo mirroring, meta read-access grants. No-op when
|
||||
//! `hive-forge` isn't running. Full design: `docs/forge.md`.
|
||||
//! config-repo mirroring, meta read-access grants. Also seeds
|
||||
//! `internal/docs` — a private repo every agent gets read-only
|
||||
//! collaborator access to for operator-curated shared content.
|
||||
//! No-op when `hive-forge` isn't running. Full design: `docs/forge.md`.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
|
|
@ -38,9 +40,18 @@ const CONFIG_ORG_AVATAR_MARKER: &str = "/var/lib/hyperhive/forge-agent-configs-a
|
|||
/// applied repos stay hive-c0re-owned on disk; this org is just a
|
||||
/// mirror target core pushes to.
|
||||
const CONFIG_ORG: &str = "agent-configs";
|
||||
/// Forgejo org hosting the operator-curated shared docs/skills repo
|
||||
/// that every agent gets read-only access to. Agents use it as a
|
||||
/// common reference without the operator having to bake content into
|
||||
/// the system prompt or rely on `/shared`. Only the manager + operator
|
||||
/// (i.e. `core` user) can push.
|
||||
const SHARED_ORG: &str = "org-shared";
|
||||
/// The shared docs repo inside `SHARED_ORG`. Cloneable by every agent
|
||||
/// at `{FORGE_HTTP}/org-shared/docs.git`.
|
||||
const SHARED_DOCS_REPO: &str = "docs";
|
||||
/// Forgejo orgs hive-c0re ensures on startup. The meta repo lives at
|
||||
/// `core/meta` (the `core` user's own namespace — no org needed).
|
||||
const SEEDED_ORGS: &[&str] = &[CONFIG_ORG];
|
||||
const SEEDED_ORGS: &[&str] = &[CONFIG_ORG, SHARED_ORG];
|
||||
/// Per-agent token scopes (broad-but-not-admin). See
|
||||
/// `docs/forge.md::Token scopes` for the per-scope rationale.
|
||||
const TOKEN_SCOPES: &str = "read:user,write:user,read:notification,write:notification,write:repository,write:issue,write:organization,write:misc";
|
||||
|
|
@ -529,6 +540,54 @@ pub async fn ensure_config_repo(name: &str) -> Result<()> {
|
|||
ensure_org_repo(CONFIG_ORG, name, &token).await
|
||||
}
|
||||
|
||||
/// Ensure the `org-shared/docs` repo exists. Called once at startup
|
||||
/// after `ensure_org(SHARED_ORG)`. Idempotent — `ensure_org_repo`
|
||||
/// treats 409 as success.
|
||||
pub async fn ensure_shared_docs_repo(core_token: &str) -> Result<()> {
|
||||
ensure_org_repo(SHARED_ORG, SHARED_DOCS_REPO, core_token).await
|
||||
}
|
||||
|
||||
/// Grant agent `name` read-only collaborator access to `org-shared/docs`.
|
||||
/// Idempotent: HTTP 204 (already a collaborator) is treated as success.
|
||||
/// Mirrors `meta_read_access` so agents can clone the shared docs repo
|
||||
/// without authentication hassle.
|
||||
pub async fn shared_docs_access(name: &str, core_token: &str) -> Result<()> {
|
||||
let url = format!(
|
||||
"{FORGE_HTTP}/api/v1/repos/{SHARED_ORG}/{SHARED_DOCS_REPO}/collaborators/{name}"
|
||||
);
|
||||
let body = r#"{"permission":"read"}"#;
|
||||
let out = Command::new("curl")
|
||||
.args([
|
||||
"-sS",
|
||||
"-o",
|
||||
"/dev/null",
|
||||
"-w",
|
||||
"%{http_code}",
|
||||
"-X",
|
||||
"PUT",
|
||||
"-H",
|
||||
"Content-Type: application/json",
|
||||
"-H",
|
||||
&format!("Authorization: token {core_token}"),
|
||||
"-d",
|
||||
body,
|
||||
&url,
|
||||
])
|
||||
.output()
|
||||
.await
|
||||
.context("invoke curl PUT org-shared/docs/collaborators")?;
|
||||
let code = String::from_utf8_lossy(&out.stdout).trim().to_owned();
|
||||
match code.as_str() {
|
||||
"204" => {
|
||||
tracing::info!(%name, "forge: granted shared-docs read access");
|
||||
Ok(())
|
||||
}
|
||||
other => anyhow::bail!(
|
||||
"PUT {SHARED_ORG}/{SHARED_DOCS_REPO}/collaborators/{name} returned HTTP {other}"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Grant agent `name` read-only collaborator access to `core/meta` on
|
||||
/// the forge so the agent can clone/fetch the meta flake. Idempotent:
|
||||
/// HTTP 204 (already a collaborator) is treated as success.
|
||||
|
|
@ -701,6 +760,13 @@ pub async fn sync_agent(name: &str, core_token: Option<&str>) {
|
|||
if let Err(e) = ensure_meta_remote(name).await {
|
||||
tracing::warn!(%name, error = ?e, "forge: ensure_meta_remote failed");
|
||||
}
|
||||
// Grant read-only access to org-shared/docs so the agent can clone
|
||||
// the operator-curated shared skills/runbook repo. Best-effort.
|
||||
if let Some(token) = core_token
|
||||
&& let Err(e) = shared_docs_access(name, token).await
|
||||
{
|
||||
tracing::warn!(%name, error = ?e, "forge: shared_docs_access failed");
|
||||
}
|
||||
}
|
||||
|
||||
/// Sweep every existing container (manager + sub-agents) and ensure
|
||||
|
|
@ -734,6 +800,11 @@ pub async fn ensure_all() {
|
|||
if let Err(e) = ensure_repo("meta", token).await {
|
||||
tracing::warn!(error = ?e, "forge: ensure_repo core/meta failed");
|
||||
}
|
||||
// Seed the shared docs repo. org-shared is already in
|
||||
// SEEDED_ORGS above so the org exists; ensure the repo itself.
|
||||
if let Err(e) = ensure_shared_docs_repo(token).await {
|
||||
tracing::warn!(error = ?e, "forge: ensure_shared_docs_repo failed");
|
||||
}
|
||||
if let Err(e) = ensure_core_avatar(token).await {
|
||||
tracing::warn!(error = ?e, "forge: ensure_core_avatar failed");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue