feat(forge): Phase 1 — GitProvider seam, GitHub transport extracted (#571)

* feat(forge): Phase 0 — git_provider column + registration-time forge validation

Pointing a project at a GitLab/Gitea git_url used to fail silently, several
steps deep, at first PR. New pure policy module (foundation/policy/forge.py)
detects the provider from the git_url host and validates at the
ProjectService create/update chokepoint: github auto-detects and
auto-stamps, explicit git_provider=github is the GitHub Enterprise escape
hatch, gitlab/gitea are recognized-but-not-yet-supported, unknown hosts get
a loud rejection with guidance. An update changing git_url does NOT inherit
a stored auto-stamped provider (restating the override is required), so a
host swap can't smuggle the escape hatch past validation. Migration 075
adds the nullable projects.git_provider column; the panel project dialogs
show the detected forge. Phase 0 of the forge-providers spec.

* feat(forge): Phase 1 — GitProvider seam, GitHub transport extracted

roboco/services/forge/: base.py holds the pure contracts (RepoRef +
GitProvider ABC, stdlib-only — a later GitLabProvider is implemented by
reading this file alone), github.py the httpx transport (20 endpoint
methods behind one shared request-plumbing helper set), registry.py the
wiring (git_provider column -> provider, failing loud on gitlab/gitea).
GitService keeps its exact public surface and all response
classification; its 26 inline REST call sites route through a lazy
_forge property (several suites build GitService via __new__, so an
__init__-set attribute breaks them). github_provisioning and
release_executor ride the same provider. Zero behavior change — the
pre-existing suites pass unmodified; per-project provider resolution
lands with the second provider.

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
Renzo F
2026-07-18 19:11:56 +02:00
committed by GitHub
co-authored by Renn F
parent 7e01c0cecf
commit 461a6e1ae7
9 changed files with 886 additions and 384 deletions
+5
View File
@@ -169,6 +169,11 @@ select = [
# tool layer; we accept the >5 kwarg signatures here for the same reason
# they're accepted in `roboco/mcp/**`.
"roboco/services/gateway/**/*.py" = ["PLC0415", "PLR0913"]
# The forge provider ABC/transport mirror REST endpoint parameter contracts
# (e.g. list_pulls' head/base/state/per_page, create_release's tag/name/body/
# target_commitish) — a bundle dataclass would just relocate the same named
# fields behind one hop, same rationale as roboco/mcp/** and gateway/** above.
"roboco/services/forge/*.py" = ["PLR0913"]
# Route signatures ARE the HTTP contract — each FastAPI query/path/body
# param must be a discrete typed argument for OpenAPI + validation, so the
# >5-arg rule doesn't fit them (same rationale as roboco/mcp/**). TC003: