Project-level brainstorm + healthCheck (drop repo pinning)¶
Date: 2026-06-16. Component: tatara-operator (operator-only).
Problem¶
brainstorm and healthCheck are already PROJECT-scheduled (one Task per cycle, project-wide in-flight guard + proposal-backlog cap) but each Task is pinned to a deterministic primaryRepo (TaskSpec.RepositoryRef = primaryRepo, pod named ...-<repo>-brainstorm) while the pod checks out ALL repos via TATARA_REPOS. So project-wide work is modeled as a repo-scoped Task. Make them genuinely project-scoped.
Decision¶
Approach A: empty RepositoryRef means project scope. Operator-only; the wrapper already supports an empty REPO_URL and clones from TATARA_REPOS (bootstrap.go:158 guards if p.RepoURL != ""; tests cover empty RepoURL driving Repos[0]). No wrapper change, one PR, one deploy.
Rejected: B (new TaskSpec.Scope field - extra CRD surface + second source of truth); C (sentinel ref - overloads the field, violates KISS).
Changes (tatara-operator)¶
api/v1alpha1/task_types.go:RepositoryRefbecomes+optional+json:"repositoryRef,omitempty". KeepProposedIssueSpec.RepositoryRefrequired (proposals always target a repo).- CRD validation:
RepositoryRefrequired for repo-scoped kinds (implement/review/selfImprove/triageIssue/issueLifecycle), MUST be empty forbrainstorm/healthCheck. Enforce in the existing validating path (webhook or reconcile guard, matching current conventions) + regenerate the CRD manifest (make manifests). internal/controller/projectscan.go:brainstorm()andhealthCheck()dropprimaryReposelection. They still iterate repos for the backlog-cap count, issues-context, and slug list, but create the Task with NO repo.createBrainstormTask/createHealthCheckTasklose the*Repositoryparam; setRepositoryRef=""; callStampPodName(task, proj.Name, provider, ""). Update log fields (dropprimary_repo). Theno valid reposguard stays (still need at least one repo to build a goal/context).internal/controller/task_controller.go(~line 202): whentask.Spec.RepositoryRef == ""(project-scoped kind), skip the single-repo Get; load all project repos (existing all-repos query used forTATARA_REPOS) and callBuildPod(project, nil, task, allRepos, ...).internal/agent/pod.go:BuildPodwithrepo == nilomitsREPO_URL/REPO_BRANCHand setsTATARA_REPOSto all repos (deterministic name sort, no primary-first).BuildPodName/StampPodNamerender...-<project>-brainstorm(or-healthCheck) whenrepoRef == "".internal/controller/writeback.go:brainstormalready returns at the propose_issue branch (no PR, no repo Get). ConfirmhealthCheck's writeback case is equally repo-independent; if it Gets a primary repo, make it match brainstorm.- Accounting: brainstorm/healthCheck in-flight + budget are keyed by Kind/project, not repo, so empty
RepositoryRefdoes not affectlaneOccupancy/atConcurrencyCap. Verify the repo field-index path tolerates empty ref (those Tasks simply do not appear in the per-repo index, which is correct).
Testing (TDD)¶
projectscan: brainstorm + healthCheck create a Task with emptyRepositoryRefand a project-scoped pod name; backlog cap / in-flight guard unchanged.pod:BuildPod(nil repo)emits noREPO_URL/REPO_BRANCH,TATARA_REPOSlists all repos;BuildPodName("")is project-scoped.- CRD/validation: empty ref accepted for brainstorm/healthCheck, rejected for repo-scoped kinds; non-empty ref rejected for brainstorm/healthCheck.
- writeback: brainstorm + healthCheck complete with no PR and no repo dependency.
- Full envtest controller suite green (KUBEBUILDER_ASSETS via setup-envtest 1.33.0).
Out of scope¶
issueScan/mrScan stay per-repo (a PR/issue lives in one repo). No wrapper change. No deploy-value change (agentRunAsUser/agentScheduling persist).