Multiple projects can map to ONE repo — a monorepo product's backend/
frontend/ux cells each have their own Project pointing at the same git_url.
The poll ingested per-project with a per-(project,pr) dedup, so one external
PR (e.g. #170 on github.com/rennf93/roboco) created one review task per cell
project — three identical reviews for the same PR.
Collapse active projects to one canonical project per distinct repo before
polling (_projects_one_per_repo, deterministic by slug so the pick is stable
across polls). A monorepo product now yields ONE review per external PR;
genuinely separate repos (multi-repo) each still get polled.
Scope close_pull_request repo resolution by project_id and thread the
umbrella's project into close-on-land, so a contributor PR is never
resolved (or closed) against a same-numbered PR in another project's
repo. Skip the comment + close PATCH when the PR is already closed, so a
retried sweep never re-posts the 'superseded' comment.
Require a non-cancelled descendant that actually landed a PR before
retiring the contributor PR, so an umbrella force-completed over a
cancelled code subtask leaves the contributor's still-valid PR open.
Run close-on-land from the always-on sweeper rather than the default-off
poll loop, so a supersede that lands after the feature is toggled off is
still reconciled. Serialize concurrent supersede triggers under a lock so
a double-click can't cut two branches / spawn two umbrellas. Anchor the
supersede marker checks to the marker line so appended CEO notes can't be
mistaken for the closed/dedup tokens. Make the fork-head branch cut
idempotent (forced refspec) so a commit-fail retry converges.
Also drop an importlib.reload(roboco.config) in a unit test that rebound
the settings singleton and leaked into the PM decision-window test.
When a supersede umbrella reaches COMPLETED (our own PR merged), close-on-land
retires the contributor's PR with a linking thank-you comment:
- TaskService.supersede_umbrellas_pending_close() finds landed umbrellas not yet
marked closed=1; mark_supersede_pr_closed() records the close (idempotent).
- orchestrator._close_superseded_prs runs in the external-PR poll tick: parses
the contributor PR# from the umbrella's quick_context and calls
GitService.close_pull_request(delete_branch=False) — we never touch the
contributor's fork branch. _parse_supersede_pr is unit-tested.
Completes the supersede flow: CEO authorizes -> fork branch -> Main PM -> cell
-> our PR -> CEO merge -> contributor PR closed + linked. ruff + mypy clean
(279); foundation + gateway suites green (5208).
At ingest, a non-empty external_pr_author_allowlist restricts which external
PRs are reviewed to those GitHub logins (case-insensitive). An empty allowlist
(default) reviews every external PR — safe because the review is read-only; the
confirmed_by_human gate still guards any later supersede that runs fork code.
Unit-tested (_pr_author_allowed).
Add the dormant inbound path for external-PR review (gated by external_pr_enabled,
off by default):
- GitService.list_open_prs lists a project's open PRs, normalized with fork /
author-association classification (the inbound counterpart to the org's
outbound, head-filtered PR calls).
- TaskService.ingest_external_pr + external_review_task_exists create one
de-duped review task per newly-seen external PR (source='external_pr',
confirmed_by_human=False) — a gate so no agent fetches or runs contributor
code until a human confirms the PR.
- A poll loop in the orchestrator, mirroring the strategy-engine loop: only when
enabled it lists each active project's open PRs, ingests the external ones, and
wakes the dispatcher.
The trust-critical author/fork classifier is unit-tested; the GitHub-list and
DB-ingest paths are exercised by the integration gate.