mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
feat(megatask): per-cell project map root-subtasks (multi-project, multi-cell)
A MegaTask root-subtask can now target an ad-hoc per-cell project map — a
third targeting shape that mirrors the existing product fan-out root. In
RoboCo a project is per-cell (ProjectTable.assigned_cell); a monorepo is N
per-cell projects sharing one git_url. So 'multi-cell' IS 'multi-project',
and a task may mix per-cell projects across products or include OSS-library
projects not in any product.
Storage: migration 052 adds task_cell_projects (mirrors product_projects;
unique per (task, team)). TaskTable gains a cascade-delete cell_projects
relationship; TaskCreateRequest / TaskCreate / Task response carry the map.
Policy: batch.is_branchless_coordination + is_valid_batch_shape gain a
has_cell_projects param — a root-subtask targets exactly one of project /
product / cell-map; the umbrella still targets none. TaskService passes
has_cell_projects at every predicate call site and persists the rows in
create(). _ensure_branch_for_task cuts feature/main_pm/{root} per distinct
project in the map (via _distinct_projects_for_task); _require_target_or_umbrella
and _validate_batch_membership accept the map shape.
Fan-out: every distinct_project_ids site (task.py branch creation, routes
_project_for_complete + _resolve_project_for_merge, orchestrator
_ambient_projects_for_task, pr_review._project_slug_for, git._project_for_task)
generalizes to first-distinct-project-of-map-or-product. Choreographer
_resolve_subtask_project resolves a delegated subtask's cell from the parent's
cell map. The product-scoped _slugs_for_product intake helper is unchanged.
Intake: prompter._draft_cell_map extracts the per-cell map from the_work[].
_validate_batch_scope counts distinct projects across all drafts' cells
(>=2 min stays; one 2-cell draft satisfies it). create_task_from_draft
persists cell_projects for >=2-cell drafts (project_id/product_id None),
collapses a 1-cell map to the single-project shape, and leaves single-cell
top-level project_id drafts unchanged. _resolve_owning_team routes a
multi-cell map to Main PM (coordination root, like a product root — a cell
PM can't delegate cross-cell). propose_draft/propose_batch tool descriptions
declare the per-cell project_id (both Claude SDK + grok runtimes).
The umbrella stays branchless / pure-coordination / submit_root-rejected;
the CEO-escalation pr_number gate is not widened (the map root is
is_umbrella=False, mirroring a product root, so submit_root supplies it).
Single-cell root-subtasks and everything below them are byte-for-byte
unchanged. Un-run MegaTask waves (multi-cell drafts) become runnable.
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
"""Add the task_cell_projects table — ad-hoc per-cell project map for a task.
|
||||
|
||||
A MegaTask root-subtask that spans multiple cells (and may mix per-cell projects
|
||||
from different products / OSS libs) needs a per-cell routing map without standing
|
||||
up a Product for it. ``task_cell_projects`` mirrors ``product_projects`` but is
|
||||
owned by the task: one Project per cell per task (``UNIQUE (task_id, team)``). The
|
||||
root-subtask then cuts ``feature/main_pm/{root}`` per repo and opens a root->master
|
||||
PR per repo exactly like a Product fan-out root — only the map's source differs.
|
||||
``team`` reuses the existing Postgres "team" enum (create_type=False).
|
||||
|
||||
Revision ID: 052_task_cell_projects
|
||||
Revises: 051_respawn_tracker
|
||||
Create Date: 2026-06-26
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
revision = "052_task_cell_projects"
|
||||
down_revision = "051_respawn_tracker"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
# Reuse the existing Postgres "team" enum in place (created in 001_initial_schema,
|
||||
# widened since by later migrations); create_type=False so this migration never
|
||||
# tries to (re)create it.
|
||||
_TEAM_ENUM = sa.Enum(
|
||||
"backend",
|
||||
"frontend",
|
||||
"ux_ui",
|
||||
"board",
|
||||
"main_pm",
|
||||
"fullstack",
|
||||
"marketing",
|
||||
"system",
|
||||
name="team",
|
||||
create_type=False,
|
||||
)
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"task_cell_projects",
|
||||
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
|
||||
sa.Column(
|
||||
"task_id",
|
||||
postgresql.UUID(as_uuid=True),
|
||||
sa.ForeignKey("tasks.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
index=True,
|
||||
),
|
||||
sa.Column("team", _TEAM_ENUM, nullable=False),
|
||||
sa.Column(
|
||||
"project_id",
|
||||
postgresql.UUID(as_uuid=True),
|
||||
sa.ForeignKey("projects.id", ondelete="RESTRICT"),
|
||||
nullable=False,
|
||||
index=True,
|
||||
),
|
||||
sa.UniqueConstraint("task_id", "team", name="uq_task_cell_projects_task_team"),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table("task_cell_projects")
|
||||
Reference in New Issue
Block a user