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()