Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
mjalmoudy94
2026-10-14 15:51:44 +03:30
11 changed files with 3211 additions and 0 deletions
@@ -0,0 +1,186 @@
"""AI workforce engine: pipeline state, employee profiles, turns, gate results
Revision ID: a2b3c4d5e6f2
Revises: d5e6f7a8b9c0
Create Date: 2026-10-14 09:30:00.000000
Owned by the AI-workforce workstream (PLAN-v2 §6).
Deviation from TODO-agent-2, noted so the next reader is not surprised: the
TODO pins ``down_revision = "a0f0e1d2c3b4"`` (the v2 foundation), which was
correct while each agent worked on its own branch. On the merged ``dev``
branch the agents have landed one by one, so this migration chains after the
most recent head (``d5e6f7a8b9c0``, org setup drafts) to keep a linear
history. Claude adds the merge revision for the two remaining heads.
No ``tasks`` or ``employees`` column is added: those tables are locked. The
engine's per-task and per-employee data lives in 1:1 side tables instead.
"""
from __future__ import annotations
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
revision: str = "a2b3c4d5e6f2"
down_revision: str | None = "d5e6f7a8b9c0"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
op.create_table(
"task_pipeline_state",
sa.Column(
"task_id",
sa.String(length=32),
sa.ForeignKey("tasks.id", ondelete="CASCADE"),
primary_key=True,
),
sa.Column(
"organization_id",
sa.String(length=32),
sa.ForeignKey("organizations.id", ondelete="CASCADE"),
nullable=False,
),
sa.Column(
"project_id",
sa.String(length=32),
sa.ForeignKey("projects.id", ondelete="CASCADE"),
nullable=True,
),
sa.Column(
"pipeline",
sa.String(length=32),
nullable=False,
server_default="plan_code_test",
),
sa.Column("kind", sa.String(length=16), nullable=False, server_default="feature"),
sa.Column("risk", sa.String(length=8), nullable=False, server_default="medium"),
sa.Column(
"stage",
sa.String(length=32),
nullable=False,
server_default="pending_manager_review",
),
sa.Column("cycle", sa.Integer(), nullable=False, server_default="0"),
sa.Column("max_cycles", sa.Integer(), nullable=False, server_default="6"),
sa.Column("branch", sa.String(length=120), nullable=False, server_default=""),
sa.Column("last_diff_hash", sa.String(length=64), nullable=False, server_default=""),
sa.Column("same_diff_count", sa.Integer(), nullable=False, server_default="0"),
sa.Column("retry_count", sa.Integer(), nullable=False, server_default="0"),
sa.Column("waiver_reason", sa.Text(), nullable=False, server_default=""),
sa.Column(
"parent_task_id",
sa.String(length=32),
sa.ForeignKey("tasks.id", ondelete="SET NULL"),
nullable=True,
),
sa.Column("depends_on", sa.JSON(), nullable=False, server_default="[]"),
sa.Column("version", sa.Integer(), nullable=False, server_default="0"),
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
)
op.create_index(
"ix_task_pipeline_state_organization_id",
"task_pipeline_state",
["organization_id"],
)
op.create_index(
"ix_task_pipeline_state_stage", "task_pipeline_state", ["stage"]
)
op.create_table(
"employee_profiles",
sa.Column(
"employee_id",
sa.String(length=32),
sa.ForeignKey("employees.id", ondelete="CASCADE"),
primary_key=True,
),
sa.Column("ai_model_id", sa.String(length=32), nullable=True),
sa.Column(
"effort", sa.String(length=16), nullable=False, server_default="medium"
),
sa.Column("roles", sa.String(length=255), nullable=False, server_default=""),
sa.Column("hired_by", sa.String(length=8), nullable=False, server_default="user"),
sa.Column(
"busy_task_id",
sa.String(length=32),
sa.ForeignKey("tasks.id", ondelete="SET NULL"),
nullable=True,
),
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
)
op.create_index(
"ix_employee_profile_busy", "employee_profiles", ["busy_task_id"]
)
op.create_table(
"engine_turns",
sa.Column("id", sa.String(length=32), primary_key=True),
sa.Column(
"task_id",
sa.String(length=32),
sa.ForeignKey("tasks.id", ondelete="CASCADE"),
nullable=False,
),
sa.Column(
"run_id",
sa.String(length=32),
sa.ForeignKey("runs.id", ondelete="CASCADE"),
nullable=False,
),
sa.Column(
"employee_id",
sa.String(length=32),
sa.ForeignKey("employees.id", ondelete="SET NULL"),
nullable=True,
),
sa.Column("role", sa.String(length=24), nullable=False, server_default="developer"),
sa.Column("mode", sa.String(length=16), nullable=False, server_default="work"),
sa.Column("stage", sa.String(length=32), nullable=False, server_default=""),
sa.Column("prompt_chars", sa.Integer(), nullable=False, server_default="0"),
sa.Column("reply_excerpt", sa.Text(), nullable=False, server_default=""),
sa.Column("tags", sa.JSON(), nullable=False, server_default="{}"),
sa.Column("tool_calls", sa.Integer(), nullable=False, server_default="0"),
sa.Column("ok", sa.Boolean(), nullable=False, server_default=sa.true()),
sa.Column("error_kind", sa.String(length=16), nullable=False, server_default=""),
sa.Column("started_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("finished_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
)
op.create_index("ix_engine_turn_task_stage", "engine_turns", ["task_id", "stage"])
op.create_index("ix_engine_turn_run", "engine_turns", ["run_id"])
op.create_table(
"gate_results",
sa.Column("id", sa.String(length=32), primary_key=True),
sa.Column(
"task_id",
sa.String(length=32),
sa.ForeignKey("tasks.id", ondelete="CASCADE"),
nullable=False,
),
sa.Column("gate", sa.String(length=16), nullable=False, server_default="tests"),
sa.Column("ok", sa.Boolean(), nullable=False, server_default=sa.true()),
sa.Column("detail", sa.Text(), nullable=False, server_default=""),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
)
op.create_index("ix_gate_result_task_gate", "gate_results", ["task_id", "gate"])
def downgrade() -> None:
op.drop_index("ix_gate_result_task_gate", table_name="gate_results")
op.drop_table("gate_results")
op.drop_index("ix_engine_turn_run", table_name="engine_turns")
op.drop_index("ix_engine_turn_task_stage", table_name="engine_turns")
op.drop_table("engine_turns")
op.drop_index("ix_employee_profile_busy", table_name="employee_profiles")
op.drop_table("employee_profiles")
op.drop_index("ix_task_pipeline_state_stage", table_name="task_pipeline_state")
op.drop_index(
"ix_task_pipeline_state_organization_id", table_name="task_pipeline_state"
)
op.drop_table("task_pipeline_state")