@@ -373,26 +373,59 @@ def __init__(self):
373373 self .db = db
374374 self .db .init_app (self .app )
375375
376+ def _ensure_plans_table_name () -> None :
377+ """Rename legacy 'task_item' (and its indexes) to 'plans' (idempotent).
378+
379+ Serialized via a Postgres advisory lock so concurrent replicas do not
380+ race. Must run before db.create_all() so SQLAlchemy does not create
381+ a second empty 'plans' table alongside 'task_item'.
382+ """
383+ sql = """
384+ DO $$
385+ BEGIN
386+ PERFORM pg_advisory_xact_lock(8462357421);
387+ IF EXISTS (SELECT 1 FROM information_schema.tables
388+ WHERE table_schema = current_schema() AND table_name = 'task_item')
389+ AND NOT EXISTS (SELECT 1 FROM information_schema.tables
390+ WHERE table_schema = current_schema() AND table_name = 'plans') THEN
391+ ALTER TABLE task_item RENAME TO plans;
392+ END IF;
393+ IF EXISTS (SELECT 1 FROM pg_indexes
394+ WHERE schemaname = current_schema() AND indexname = 'idx_task_item_user_id_timestamp_created') THEN
395+ ALTER INDEX idx_task_item_user_id_timestamp_created RENAME TO idx_plans_user_id_timestamp_created;
396+ END IF;
397+ IF EXISTS (SELECT 1 FROM pg_indexes
398+ WHERE schemaname = current_schema() AND indexname = 'idx_task_item_api_key_id') THEN
399+ ALTER INDEX idx_task_item_api_key_id RENAME TO idx_plans_api_key_id;
400+ END IF;
401+ END$$;
402+ """
403+ try :
404+ with self .db .engine .begin () as conn :
405+ conn .execute (text (sql ))
406+ except Exception as exc :
407+ logger .warning ("Rename task_item -> plans failed: %s" , exc , exc_info = True )
408+
376409 def _ensure_planitem_artifact_columns () -> None :
377410 insp = inspect (self .db .engine )
378- columns = {col ["name" ] for col in insp .get_columns ("task_item " )}
411+ columns = {col ["name" ] for col in insp .get_columns ("plans " )}
379412 with self .db .engine .begin () as conn :
380413 if "generated_report_html" not in columns :
381- conn .execute (text ("ALTER TABLE task_item ADD COLUMN IF NOT EXISTS generated_report_html TEXT" ))
414+ conn .execute (text ("ALTER TABLE plans ADD COLUMN IF NOT EXISTS generated_report_html TEXT" ))
382415 if "run_zip_snapshot" not in columns :
383- conn .execute (text ("ALTER TABLE task_item ADD COLUMN IF NOT EXISTS run_zip_snapshot BYTEA" ))
416+ conn .execute (text ("ALTER TABLE plans ADD COLUMN IF NOT EXISTS run_zip_snapshot BYTEA" ))
384417 if "run_track_activity_jsonl" not in columns :
385- conn .execute (text ("ALTER TABLE task_item ADD COLUMN IF NOT EXISTS run_track_activity_jsonl TEXT" ))
418+ conn .execute (text ("ALTER TABLE plans ADD COLUMN IF NOT EXISTS run_track_activity_jsonl TEXT" ))
386419 if "run_track_activity_bytes" not in columns :
387- conn .execute (text ("ALTER TABLE task_item ADD COLUMN IF NOT EXISTS run_track_activity_bytes INTEGER" ))
420+ conn .execute (text ("ALTER TABLE plans ADD COLUMN IF NOT EXISTS run_track_activity_bytes INTEGER" ))
388421 if "run_activity_overview_json" not in columns :
389- conn .execute (text ("ALTER TABLE task_item ADD COLUMN IF NOT EXISTS run_activity_overview_json JSON" ))
422+ conn .execute (text ("ALTER TABLE plans ADD COLUMN IF NOT EXISTS run_activity_overview_json JSON" ))
390423 if "run_artifact_layout_version" not in columns :
391- conn .execute (text ("ALTER TABLE task_item ADD COLUMN IF NOT EXISTS run_artifact_layout_version INTEGER" ))
424+ conn .execute (text ("ALTER TABLE plans ADD COLUMN IF NOT EXISTS run_artifact_layout_version INTEGER" ))
392425 if "stop_requested" not in columns :
393- conn .execute (text ("ALTER TABLE task_item ADD COLUMN IF NOT EXISTS stop_requested BOOLEAN" ))
426+ conn .execute (text ("ALTER TABLE plans ADD COLUMN IF NOT EXISTS stop_requested BOOLEAN" ))
394427 if "stop_requested_timestamp" not in columns :
395- conn .execute (text ("ALTER TABLE task_item ADD COLUMN IF NOT EXISTS stop_requested_timestamp TIMESTAMP" ))
428+ conn .execute (text ("ALTER TABLE plans ADD COLUMN IF NOT EXISTS stop_requested_timestamp TIMESTAMP" ))
396429
397430 def _ensure_token_metrics_columns () -> None :
398431 insp = inspect (self .db .engine )
@@ -460,7 +493,7 @@ def _ensure_multi_api_key_columns() -> None:
460493 statements = (
461494 "ALTER TABLE user_api_key ADD COLUMN IF NOT EXISTS label VARCHAR(128)" ,
462495 "ALTER TABLE user_api_key ADD COLUMN IF NOT EXISTS key_plaintext VARCHAR(64)" ,
463- "ALTER TABLE task_item ADD COLUMN IF NOT EXISTS api_key_id VARCHAR(36)" ,
496+ "ALTER TABLE plans ADD COLUMN IF NOT EXISTS api_key_id VARCHAR(36)" ,
464497 "ALTER TABLE credit_history ADD COLUMN IF NOT EXISTS api_key_id VARCHAR(36)" ,
465498 "ALTER TABLE token_metrics ADD COLUMN IF NOT EXISTS api_key_id VARCHAR(36)" ,
466499 )
@@ -473,38 +506,38 @@ def _ensure_multi_api_key_columns() -> None:
473506
474507 def _ensure_step_count_columns () -> None :
475508 insp = inspect (self .db .engine )
476- columns = {col ["name" ] for col in insp .get_columns ("task_item " )}
509+ columns = {col ["name" ] for col in insp .get_columns ("plans " )}
477510 with self .db .engine .begin () as conn :
478511 if "steps_completed" not in columns :
479- conn .execute (text ("ALTER TABLE task_item ADD COLUMN IF NOT EXISTS steps_completed INTEGER" ))
512+ conn .execute (text ("ALTER TABLE plans ADD COLUMN IF NOT EXISTS steps_completed INTEGER" ))
480513 if "steps_total" not in columns :
481- conn .execute (text ("ALTER TABLE task_item ADD COLUMN IF NOT EXISTS steps_total INTEGER" ))
514+ conn .execute (text ("ALTER TABLE plans ADD COLUMN IF NOT EXISTS steps_total INTEGER" ))
482515 if "current_step" not in columns :
483- conn .execute (text ("ALTER TABLE task_item ADD COLUMN IF NOT EXISTS current_step VARCHAR(128)" ))
516+ conn .execute (text ("ALTER TABLE plans ADD COLUMN IF NOT EXISTS current_step VARCHAR(128)" ))
484517
485518 def _ensure_failure_diagnostic_columns () -> None :
486519 insp = inspect (self .db .engine )
487- columns = {col ["name" ] for col in insp .get_columns ("task_item " )}
520+ columns = {col ["name" ] for col in insp .get_columns ("plans " )}
488521 with self .db .engine .begin () as conn :
489522 if "failure_reason" not in columns :
490- conn .execute (text ("ALTER TABLE task_item ADD COLUMN IF NOT EXISTS failure_reason VARCHAR(64)" ))
523+ conn .execute (text ("ALTER TABLE plans ADD COLUMN IF NOT EXISTS failure_reason VARCHAR(64)" ))
491524 if "failed_step" not in columns :
492- conn .execute (text ("ALTER TABLE task_item ADD COLUMN IF NOT EXISTS failed_step VARCHAR(128)" ))
525+ conn .execute (text ("ALTER TABLE plans ADD COLUMN IF NOT EXISTS failed_step VARCHAR(128)" ))
493526 if "recoverable" not in columns :
494- conn .execute (text ("ALTER TABLE task_item ADD COLUMN IF NOT EXISTS recoverable BOOLEAN" ))
527+ conn .execute (text ("ALTER TABLE plans ADD COLUMN IF NOT EXISTS recoverable BOOLEAN" ))
495528 # Rename last_error -> error_message in a separate transaction so a
496529 # failed RENAME (race with another container) doesn't poison the above.
497530 if "error_message" not in columns :
498531 if "last_error" in columns :
499532 try :
500533 with self .db .engine .begin () as conn :
501- conn .execute (text ("ALTER TABLE task_item RENAME COLUMN last_error TO error_message" ))
534+ conn .execute (text ("ALTER TABLE plans RENAME COLUMN last_error TO error_message" ))
502535 except Exception :
503536 with self .db .engine .begin () as conn :
504- conn .execute (text ("ALTER TABLE task_item ADD COLUMN IF NOT EXISTS error_message VARCHAR(256)" ))
537+ conn .execute (text ("ALTER TABLE plans ADD COLUMN IF NOT EXISTS error_message VARCHAR(256)" ))
505538 else :
506539 with self .db .engine .begin () as conn :
507- conn .execute (text ("ALTER TABLE task_item ADD COLUMN IF NOT EXISTS error_message VARCHAR(256)" ))
540+ conn .execute (text ("ALTER TABLE plans ADD COLUMN IF NOT EXISTS error_message VARCHAR(256)" ))
508541
509542 def _ensure_stopped_state () -> None :
510543 """Add 'stopped' value to the planstate/taskstate enum type (idempotent).
@@ -527,23 +560,23 @@ def _ensure_stopped_state() -> None:
527560
528561 def _ensure_last_progress_at_column () -> None :
529562 insp = inspect (self .db .engine )
530- columns = {col ["name" ] for col in insp .get_columns ("task_item " )}
563+ columns = {col ["name" ] for col in insp .get_columns ("plans " )}
531564 if "last_progress_at" not in columns :
532565 with self .db .engine .begin () as conn :
533- conn .execute (text ("ALTER TABLE task_item ADD COLUMN IF NOT EXISTS last_progress_at TIMESTAMP" ))
566+ conn .execute (text ("ALTER TABLE plans ADD COLUMN IF NOT EXISTS last_progress_at TIMESTAMP" ))
534567
535568 def _ensure_planitem_indexes () -> None :
536569 insp = inspect (self .db .engine )
537570 table_names = set (insp .get_table_names ())
538571 statements : list [str ] = []
539- if "task_item " in table_names :
572+ if "plans " in table_names :
540573 statements .append (
541- "CREATE INDEX IF NOT EXISTS idx_task_item_user_id_timestamp_created "
542- "ON task_item (user_id, timestamp_created)"
574+ "CREATE INDEX IF NOT EXISTS idx_plans_user_id_timestamp_created "
575+ "ON plans (user_id, timestamp_created)"
543576 )
544577 statements .append (
545- "CREATE INDEX IF NOT EXISTS idx_task_item_api_key_id "
546- "ON task_item (api_key_id)"
578+ "CREATE INDEX IF NOT EXISTS idx_plans_api_key_id "
579+ "ON plans (api_key_id)"
547580 )
548581 if "credit_history" in table_names :
549582 statements .append (
@@ -593,6 +626,7 @@ def _create_tables_with_retry(attempts: int = 5, delay_seconds: float = 2.0) ->
593626 with self .db .engine .connect () as lock_conn :
594627 lock_conn .execute (text (f"SELECT pg_advisory_lock({ _ADVISORY_LOCK_KEY } )" ))
595628 try :
629+ _ensure_plans_table_name ()
596630 self .db .create_all ()
597631 _ensure_planitem_artifact_columns ()
598632 _ensure_token_metrics_columns ()
@@ -657,7 +691,7 @@ def load_user(user_id):
657691 self .admin = Admin (self .app , name = 'PlanExe Admin' , index_view = MyAdminIndexView ())
658692
659693 # Add database tables to admin panel
660- self .admin .add_view (PlanItemView (model = PlanItem , session = self .db .session , name = "Task " ))
694+ self .admin .add_view (PlanItemView (model = PlanItem , session = self .db .session , name = "Plan " ))
661695 self .admin .add_view (AdminOnlyModelView (model = EventItem , session = self .db .session , name = "Event" ))
662696 self .admin .add_view (WorkerItemView (model = WorkerItem , session = self .db .session , name = "Worker" ))
663697 self .admin .add_view (NonceItemView (model = NonceItem , session = self .db .session , name = "Nonce" ))
0 commit comments