mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
feat(tasks): AC identity + child->parent AC linkage (guardrails spec 1/4)
Foundation for the decomposition-coverage and roll-up AC-verification gates. Acceptance criteria were a flat list[str] with no per-criterion identity, so nothing could relate a child task's criteria to the parent's — letting a PM drop half a parent's ACs unnoticed (PR #175). - migration 036: additive acceptance_criteria_ids + parent_ac_refs array columns; backfills stable md5(task_id:index) ids for existing rows. - Task model + TaskCreateRequest + db table: the two fields. - TaskService.create generates one stable id per criterion (1:1) when absent. - DelegateInputs.covers_parent_criteria -> child.parent_ac_refs (the linkage), propagated through create_subtask. - regression-safe (53 task tests green) + 1 new test. Coverage gate (spec 2), roll-up AC gate (spec 4), per-dev sequenced queues (spec 3) build on this. Design: docs/SPEC_AC_GUARDRAILS_2026-06-16.md.
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
"""Add per-criterion AC ids + child->parent AC linkage to tasks.
|
||||
|
||||
Acceptance criteria were a flat ``list[str]`` with no per-criterion identity, so
|
||||
nothing could relate a child task's criteria back to the parent's. That let a
|
||||
PM decompose a 16-criterion parent into two children covering half of them, with
|
||||
the rest silently dropped and never re-checked at roll-up.
|
||||
|
||||
This adds two additive array columns:
|
||||
|
||||
- ``acceptance_criteria_ids``: a stable id per element of ``acceptance_criteria``
|
||||
(1:1, same order). Generated at task creation going forward; backfilled here
|
||||
for existing rows as ``md5(task_id || ':' || index)`` so ids are deterministic
|
||||
and stable.
|
||||
- ``parent_ac_refs``: on a child task, the parent AC ids this child is
|
||||
responsible for (empty on tasks that are not decomposition children).
|
||||
|
||||
Both are nullable-with-default-empty so existing readers are unaffected; the
|
||||
coverage + roll-up gates that consume them ship in later changes.
|
||||
|
||||
Revision ID: 036_ac_ids_and_parent_refs
|
||||
Revises: 035_secretary_directives
|
||||
Create Date: 2026-06-16
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision = "036_ac_ids_and_parent_refs"
|
||||
down_revision = "035_secretary_directives"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column(
|
||||
"tasks",
|
||||
sa.Column(
|
||||
"acceptance_criteria_ids",
|
||||
sa.ARRAY(sa.String()),
|
||||
nullable=False,
|
||||
server_default=sa.text("'{}'::varchar[]"),
|
||||
),
|
||||
)
|
||||
op.add_column(
|
||||
"tasks",
|
||||
sa.Column(
|
||||
"parent_ac_refs",
|
||||
sa.ARRAY(sa.String()),
|
||||
nullable=False,
|
||||
server_default=sa.text("'{}'::varchar[]"),
|
||||
),
|
||||
)
|
||||
# Backfill stable per-criterion ids for existing rows: md5(task_id:index),
|
||||
# one per element of acceptance_criteria, preserving order. Rows with no
|
||||
# criteria stay empty.
|
||||
op.execute(
|
||||
"""
|
||||
UPDATE tasks
|
||||
SET acceptance_criteria_ids = (
|
||||
SELECT array_agg(md5(tasks.id::text || ':' || s::text) ORDER BY s)
|
||||
FROM generate_subscripts(tasks.acceptance_criteria, 1) AS s
|
||||
)
|
||||
WHERE acceptance_criteria IS NOT NULL
|
||||
AND array_length(acceptance_criteria, 1) > 0
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("tasks", "parent_ac_refs")
|
||||
op.drop_column("tasks", "acceptance_criteria_ids")
|
||||
Reference in New Issue
Block a user