♻️ refactor(cook-it-through): move workflow engine into skill
This commit is contained in:
@@ -0,0 +1,425 @@
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
MAIN_LOOP_SCRIPTS = ROOT / "skills" / "cook-it-through" / "scripts"
|
||||
sys.path.insert(0, str(MAIN_LOOP_SCRIPTS))
|
||||
|
||||
from main_loop_scheduler import (
|
||||
FeatureId,
|
||||
FeatureRecord,
|
||||
FeatureIntegrationId,
|
||||
Scheduler,
|
||||
SchedulerError,
|
||||
TicketId,
|
||||
TicketRecord,
|
||||
parse_dependencies,
|
||||
)
|
||||
|
||||
|
||||
class SchedulerIdentityTests(unittest.TestCase):
|
||||
def test_dependency_parser_accepts_only_canonical_qualified_identities(self):
|
||||
owner = TicketId.parse("feature-a/04")
|
||||
|
||||
self.assertEqual(parse_dependencies("None", owner), ())
|
||||
self.assertEqual(
|
||||
parse_dependencies(
|
||||
"feature-a/01; feature-b/03; feature-c@integrated",
|
||||
owner,
|
||||
),
|
||||
(
|
||||
TicketId.parse("feature-a/01"),
|
||||
TicketId.parse("feature-b/03"),
|
||||
FeatureIntegrationId.parse("feature-c@integrated"),
|
||||
),
|
||||
)
|
||||
self.assertEqual(str(FeatureId.parse("feature-a")), "feature-a")
|
||||
self.assertEqual(str(TicketId.parse("feature-a/01")), "feature-a/01")
|
||||
self.assertEqual(
|
||||
str(FeatureIntegrationId.parse("feature-a@integrated")),
|
||||
"feature-a@integrated",
|
||||
)
|
||||
|
||||
invalid_values = (
|
||||
"01",
|
||||
"01 - First",
|
||||
"feature-a/01, feature-b/03",
|
||||
"None - can start immediately",
|
||||
"feature-a/1",
|
||||
"Feature-A/01",
|
||||
)
|
||||
for raw in invalid_values:
|
||||
with self.subTest(raw=raw), self.assertRaisesRegex(
|
||||
SchedulerError,
|
||||
"invalid dependency",
|
||||
):
|
||||
parse_dependencies(raw, owner)
|
||||
|
||||
with self.assertRaisesRegex(
|
||||
SchedulerError,
|
||||
"duplicate dependency feature-a/01",
|
||||
):
|
||||
parse_dependencies("feature-a/01; feature-a/01", owner)
|
||||
|
||||
with self.assertRaisesRegex(
|
||||
SchedulerError,
|
||||
"self dependency feature-a/04",
|
||||
):
|
||||
parse_dependencies("feature-a/04", owner)
|
||||
|
||||
|
||||
class GlobalSchedulerTests(unittest.TestCase):
|
||||
def test_scheduler_rejects_an_invalid_ticket_status_with_qualified_identity(self):
|
||||
feature = FeatureRecord(
|
||||
id=FeatureId.parse("alpha"),
|
||||
tickets=(
|
||||
TicketRecord(
|
||||
id=TicketId.parse("alpha/01"),
|
||||
slug="invalid",
|
||||
status="done",
|
||||
dependencies=(),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
with self.assertRaisesRegex(
|
||||
SchedulerError,
|
||||
"alpha/01: invalid status done",
|
||||
):
|
||||
Scheduler((feature,))
|
||||
|
||||
def test_scheduler_rejects_impossible_integration_state(self):
|
||||
unresolved_integrated = FeatureRecord(
|
||||
id=FeatureId.parse("alpha"),
|
||||
tickets=(
|
||||
TicketRecord(
|
||||
id=TicketId.parse("alpha/01"),
|
||||
slug="unfinished",
|
||||
status="ready-for-agent",
|
||||
dependencies=(),
|
||||
),
|
||||
),
|
||||
integrated=True,
|
||||
)
|
||||
with self.assertRaisesRegex(
|
||||
SchedulerError,
|
||||
"alpha@integrated: unsatisfied ticket alpha/01",
|
||||
):
|
||||
Scheduler((unresolved_integrated,))
|
||||
|
||||
alpha = FeatureRecord(
|
||||
id=FeatureId.parse("alpha"),
|
||||
tickets=(
|
||||
TicketRecord(
|
||||
id=TicketId.parse("alpha/01"),
|
||||
slug="pending",
|
||||
status="resolved",
|
||||
dependencies=(),
|
||||
),
|
||||
),
|
||||
)
|
||||
beta = FeatureRecord(
|
||||
id=FeatureId.parse("beta"),
|
||||
tickets=(
|
||||
TicketRecord(
|
||||
id=TicketId.parse("beta/01"),
|
||||
slug="done",
|
||||
status="resolved",
|
||||
dependencies=(),
|
||||
),
|
||||
),
|
||||
integrated=True,
|
||||
)
|
||||
with self.assertRaisesRegex(
|
||||
SchedulerError,
|
||||
"beta@integrated: earlier integration is pending: alpha@integrated",
|
||||
):
|
||||
Scheduler((alpha, beta))
|
||||
|
||||
def test_claimable_tickets_use_queue_priority_without_a_feature_head_barrier(self):
|
||||
alpha = FeatureRecord(
|
||||
id=FeatureId.parse("alpha"),
|
||||
tickets=(
|
||||
TicketRecord(
|
||||
id=TicketId.parse("alpha/01"),
|
||||
slug="alpha-ticket",
|
||||
status="claimed",
|
||||
dependencies=(),
|
||||
),
|
||||
),
|
||||
)
|
||||
beta = FeatureRecord(
|
||||
id=FeatureId.parse("beta"),
|
||||
tickets=(
|
||||
TicketRecord(
|
||||
id=TicketId.parse("beta/02"),
|
||||
slug="later-number",
|
||||
status="ready-for-agent",
|
||||
dependencies=(),
|
||||
),
|
||||
TicketRecord(
|
||||
id=TicketId.parse("beta/01"),
|
||||
slug="earlier-number",
|
||||
status="ready-for-agent",
|
||||
dependencies=(),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
scheduler = Scheduler((alpha, beta))
|
||||
|
||||
self.assertEqual(
|
||||
scheduler.ticket_frontier,
|
||||
(TicketId.parse("beta/01"), TicketId.parse("beta/02")),
|
||||
)
|
||||
|
||||
def test_ticket_and_integration_dependencies_have_distinct_satisfaction_rules(self):
|
||||
beta_id = FeatureId.parse("beta")
|
||||
alpha_ticket = TicketRecord(
|
||||
id=TicketId.parse("alpha/01"),
|
||||
slug="consumer",
|
||||
status="ready-for-agent",
|
||||
dependencies=(
|
||||
TicketId.parse("beta/01"),
|
||||
FeatureIntegrationId.parse("beta@integrated"),
|
||||
),
|
||||
)
|
||||
beta = FeatureRecord(
|
||||
id=beta_id,
|
||||
tickets=(
|
||||
TicketRecord(
|
||||
id=TicketId.parse("beta/01"),
|
||||
slug="provider",
|
||||
status="skipped",
|
||||
dependencies=(),
|
||||
),
|
||||
),
|
||||
)
|
||||
alpha = FeatureRecord(
|
||||
id=FeatureId.parse("alpha"),
|
||||
tickets=(alpha_ticket,),
|
||||
)
|
||||
|
||||
before_integration = Scheduler((beta, alpha))
|
||||
self.assertEqual(before_integration.ticket_frontier, ())
|
||||
self.assertEqual(
|
||||
before_integration.integration_dependencies(alpha_ticket.id),
|
||||
(FeatureIntegrationId.parse("beta@integrated"),),
|
||||
)
|
||||
self.assertEqual(
|
||||
before_integration.unsatisfied_dependencies(alpha_ticket.id),
|
||||
(FeatureIntegrationId.parse("beta@integrated"),),
|
||||
)
|
||||
|
||||
after_integration = Scheduler(
|
||||
(
|
||||
FeatureRecord(
|
||||
id=beta.id,
|
||||
tickets=beta.tickets,
|
||||
integrated=True,
|
||||
),
|
||||
alpha,
|
||||
)
|
||||
)
|
||||
self.assertEqual(after_integration.ticket_frontier, (alpha_ticket.id,))
|
||||
|
||||
resolved_only = Scheduler(
|
||||
(
|
||||
FeatureRecord(
|
||||
id=beta.id,
|
||||
tickets=(
|
||||
TicketRecord(
|
||||
id=TicketId.parse("beta/01"),
|
||||
slug="provider",
|
||||
status="resolved",
|
||||
dependencies=(),
|
||||
),
|
||||
),
|
||||
),
|
||||
FeatureRecord(
|
||||
id=alpha.id,
|
||||
tickets=(
|
||||
TicketRecord(
|
||||
id=alpha_ticket.id,
|
||||
slug=alpha_ticket.slug,
|
||||
status=alpha_ticket.status,
|
||||
dependencies=(TicketId.parse("beta/01"),),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
self.assertEqual(resolved_only.ticket_frontier, (alpha_ticket.id,))
|
||||
|
||||
def test_graph_validation_reports_missing_targets_and_cross_feature_cycles(self):
|
||||
missing_feature = FeatureRecord(
|
||||
id=FeatureId.parse("alpha"),
|
||||
tickets=(
|
||||
TicketRecord(
|
||||
id=TicketId.parse("alpha/01"),
|
||||
slug="missing-feature",
|
||||
status="ready-for-agent",
|
||||
dependencies=(TicketId.parse("missing/01"),),
|
||||
),
|
||||
),
|
||||
)
|
||||
with self.assertRaisesRegex(
|
||||
SchedulerError,
|
||||
"alpha/01: dependency feature not queued: missing",
|
||||
):
|
||||
Scheduler((missing_feature,))
|
||||
|
||||
beta = FeatureRecord(
|
||||
id=FeatureId.parse("beta"),
|
||||
tickets=(
|
||||
TicketRecord(
|
||||
id=TicketId.parse("beta/01"),
|
||||
slug="present",
|
||||
status="ready-for-agent",
|
||||
dependencies=(),
|
||||
),
|
||||
),
|
||||
)
|
||||
missing_ticket = FeatureRecord(
|
||||
id=FeatureId.parse("alpha"),
|
||||
tickets=(
|
||||
TicketRecord(
|
||||
id=TicketId.parse("alpha/01"),
|
||||
slug="missing-ticket",
|
||||
status="ready-for-agent",
|
||||
dependencies=(TicketId.parse("beta/99"),),
|
||||
),
|
||||
),
|
||||
)
|
||||
with self.assertRaisesRegex(
|
||||
SchedulerError,
|
||||
"alpha/01: dependency ticket not found: beta/99",
|
||||
):
|
||||
Scheduler((alpha := missing_ticket, beta))
|
||||
|
||||
cycle_alpha = FeatureRecord(
|
||||
id=alpha.id,
|
||||
tickets=(
|
||||
TicketRecord(
|
||||
id=TicketId.parse("alpha/01"),
|
||||
slug="cycle-a",
|
||||
status="ready-for-agent",
|
||||
dependencies=(TicketId.parse("beta/02"),),
|
||||
),
|
||||
),
|
||||
)
|
||||
cycle_beta = FeatureRecord(
|
||||
id=beta.id,
|
||||
tickets=(
|
||||
TicketRecord(
|
||||
id=TicketId.parse("beta/02"),
|
||||
slug="cycle-b",
|
||||
status="ready-for-agent",
|
||||
dependencies=(
|
||||
FeatureIntegrationId.parse("alpha@integrated"),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
with self.assertRaisesRegex(
|
||||
SchedulerError,
|
||||
"dependency cycle: alpha/01 -> beta/02 -> alpha@integrated -> alpha/01",
|
||||
):
|
||||
Scheduler((cycle_alpha, cycle_beta))
|
||||
|
||||
def test_ready_integration_frontier_does_not_hide_later_ticket_frontier(self):
|
||||
alpha = FeatureRecord(
|
||||
id=FeatureId.parse("alpha"),
|
||||
tickets=(
|
||||
TicketRecord(
|
||||
id=TicketId.parse("alpha/01"),
|
||||
slug="done",
|
||||
status="resolved",
|
||||
dependencies=(),
|
||||
),
|
||||
),
|
||||
)
|
||||
beta = FeatureRecord(
|
||||
id=FeatureId.parse("beta"),
|
||||
tickets=(
|
||||
TicketRecord(
|
||||
id=TicketId.parse("beta/01"),
|
||||
slug="independent",
|
||||
status="ready-for-agent",
|
||||
dependencies=(),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
scheduler = Scheduler((alpha, beta))
|
||||
|
||||
self.assertEqual(
|
||||
scheduler.integration_frontier,
|
||||
FeatureIntegrationId.parse("alpha@integrated"),
|
||||
)
|
||||
self.assertEqual(
|
||||
scheduler.ticket_frontier,
|
||||
(TicketId.parse("beta/01"),),
|
||||
)
|
||||
self.assertEqual(scheduler.feature_state(alpha.id), "ready-to-integrate")
|
||||
self.assertEqual(scheduler.feature_state(beta.id), "queued")
|
||||
|
||||
def test_ticket_input_order_does_not_change_frontier_or_error_order(self):
|
||||
tickets = (
|
||||
TicketRecord(
|
||||
id=TicketId.parse("alpha/10"),
|
||||
slug="ten",
|
||||
status="ready-for-agent",
|
||||
dependencies=(),
|
||||
),
|
||||
TicketRecord(
|
||||
id=TicketId.parse("alpha/02"),
|
||||
slug="two",
|
||||
status="ready-for-agent",
|
||||
dependencies=(),
|
||||
),
|
||||
)
|
||||
forward = Scheduler(
|
||||
(FeatureRecord(id=FeatureId.parse("alpha"), tickets=tickets),)
|
||||
)
|
||||
reverse = Scheduler(
|
||||
(FeatureRecord(id=FeatureId.parse("alpha"), tickets=tickets[::-1]),)
|
||||
)
|
||||
expected = (TicketId.parse("alpha/02"), TicketId.parse("alpha/10"))
|
||||
self.assertEqual(forward.ticket_frontier, expected)
|
||||
self.assertEqual(reverse.ticket_frontier, expected)
|
||||
|
||||
invalid_tickets = (
|
||||
TicketRecord(
|
||||
id=TicketId.parse("alpha/02"),
|
||||
slug="second-error",
|
||||
status="ready-for-agent",
|
||||
dependencies=(TicketId.parse("missing/02"),),
|
||||
),
|
||||
TicketRecord(
|
||||
id=TicketId.parse("alpha/01"),
|
||||
slug="first-error",
|
||||
status="ready-for-agent",
|
||||
dependencies=(TicketId.parse("missing/01"),),
|
||||
),
|
||||
)
|
||||
messages = []
|
||||
for order in (invalid_tickets, invalid_tickets[::-1]):
|
||||
with self.assertRaises(SchedulerError) as raised:
|
||||
Scheduler(
|
||||
(FeatureRecord(id=FeatureId.parse("alpha"), tickets=order),)
|
||||
)
|
||||
messages.append(str(raised.exception))
|
||||
self.assertEqual(
|
||||
messages,
|
||||
[
|
||||
"alpha/01: dependency feature not queued: missing",
|
||||
"alpha/01: dependency feature not queued: missing",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user