diff --git a/swarm-controller/src/forge.rs b/swarm-controller/src/forge.rs index 6daa49e0..e90cdef4 100644 --- a/swarm-controller/src/forge.rs +++ b/swarm-controller/src/forge.rs @@ -76,6 +76,19 @@ const CONFIG_ORG: &str = "agent-configs"; const KNOWLEDGE_ORG: &str = "internal"; const KNOWLEDGE_REPO: &str = "knowledge"; +/// This daemon's own forge account — same constant as +/// `swarm-controller.nix`'s `swarmControllerForgeUser` (`hive-forge/default.nix`). +/// Needed in the branch-protection push whitelist (see +/// [`Client::apply_operator_branch_protection`]): being a site admin does +/// not exempt a caller from a protected branch's push check on the +/// content-edit API path (`repo_change_files`, what [`Client::seed_agent_config`] +/// uses) the way it does for a real `git push` — confirmed live against a +/// re-test where the account was already promoted to admin and the seed +/// commit still failed `ErrUserCannotCommit`. Whitelisting the account by +/// name is the same shape a git-push-based seed gets for free from the +/// admin bypass, without adding a `git` shell-out to this daemon. +const SWARM_CONTROLLER_FORGE_USER: &str = "swarm-controller"; + /// The env vars `swarm-controller.nix`'s `forgeEnv` sets. Named here /// once so [`Client::from_env`] and the nix module can't drift silently /// — a rename on one side without the other fails loudly (env var @@ -205,6 +218,19 @@ impl Client { /// only treats it as success if the rule is actually present — a /// fail-open merge gate is a security bug, not a shrug. /// + /// Also push-whitelists [`SWARM_CONTROLLER_FORGE_USER`] alone — + /// [`Client::seed_agent_config`]'s initial commit is a direct push to + /// this same protected `main`, and being a site admin does not + /// exempt the content-edit API path from a protected branch's push + /// check the way a real `git push` is exempted (confirmed live: an + /// already-admin-promoted account still hit `ErrUserCannotCommit` + /// here). `hive-c0re`'s equivalent seed avoids the whole question by + /// seeding through an actual `git push`, which the admin bypass does + /// cover — this daemon has no `git` shell-out, so an explicit + /// whitelist entry is the narrower fix. The agent (a write + /// collaborator, not on this whitelist) still cannot push directly, + /// so this doesn't loosen the "can't merge your own PR" guarantee. + /// /// Assumes [`OPERATORS_TEAM`] already exists in [`CONFIG_ORG`] /// (provisioned by `hive-c0re::forge::repos::ensure_operators_team` /// on its own startup sweep, not re-provisioned here) — if it @@ -223,8 +249,8 @@ impl Client { dismiss_stale_approvals: None, enable_approvals_whitelist: Some(true), enable_merge_whitelist: Some(true), - enable_push: None, - enable_push_whitelist: None, + enable_push: Some(true), + enable_push_whitelist: Some(true), enable_status_check: None, ignore_stale_approvals: None, merge_whitelist_teams: Some(vec![OPERATORS_TEAM.to_owned()]), @@ -232,7 +258,7 @@ impl Client { protected_file_patterns: None, push_whitelist_deploy_keys: None, push_whitelist_teams: None, - push_whitelist_usernames: None, + push_whitelist_usernames: Some(vec![SWARM_CONTROLLER_FORGE_USER.to_owned()]), require_signed_commits: None, required_approvals: Some(1), rule_name: None,