604604 ),
605605 (
606606 25 ,
607+ "persist scan model settings" ,
608+ """
609+ ALTER TABLE scans ADD COLUMN model TEXT;
610+ ALTER TABLE scans ADD COLUMN reasoning_effort TEXT;
611+ """ ,
612+ ),
613+ (
614+ 26 ,
607615 "persist scan completion warnings" ,
608616 """
609617 ALTER TABLE scans
613621)
614622
615623
616- def normalize_pre_release_migrations (connection : sqlite3 .Connection , timestamp : str ) -> None :
624+ def normalize_pre_release_migrations (
625+ connection : sqlite3 .Connection , timestamp : str
626+ ) -> None :
627+ execution_migrations = {
628+ row ["version" ]: row ["name" ]
629+ for row in connection .execute (
630+ "SELECT version, name FROM schema_migrations WHERE version IN (11, 12)"
631+ )
632+ }
633+ supported_execution_migrations = {
634+ 11 : {"deep scan orchestration state" , "scan execution profiles" },
635+ 12 : {
636+ "scan continuation threads" ,
637+ "dynamic scan execution profiles" ,
638+ "phase-specific scan progress" ,
639+ },
640+ }
641+ if any (
642+ name not in supported_execution_migrations [version ]
643+ for version , name in execution_migrations .items ()
644+ ):
645+ raise SystemExit (
646+ "The Codex Security database has an unsupported execution-profile migration history."
647+ )
648+
617649 phase_progress_migration = connection .execute (
618650 "SELECT name FROM schema_migrations WHERE version = 12"
619651 ).fetchone ()
@@ -633,6 +665,8 @@ def normalize_pre_release_migrations(connection: sqlite3.Connection, timestamp:
633665 ("phase-specific scan progress" ,),
634666 )
635667
668+ normalize_pre_release_execution_profile_migrations (connection , timestamp )
669+
636670 preflight_progress_migration = connection .execute (
637671 "SELECT name FROM schema_migrations WHERE version = 13"
638672 ).fetchone ()
@@ -693,6 +727,135 @@ def normalize_pre_release_migrations(connection: sqlite3.Connection, timestamp:
693727 )
694728
695729
730+ def normalize_pre_release_execution_profile_migrations (
731+ connection : sqlite3 .Connection , timestamp : str
732+ ) -> None :
733+ execution_migrations = {
734+ row ["version" ]: row ["name" ]
735+ for row in connection .execute (
736+ "SELECT version, name FROM schema_migrations WHERE version IN (11, 12, 25, 26)"
737+ )
738+ }
739+ legacy_names = {
740+ 11 : "scan execution profiles" ,
741+ 12 : "dynamic scan execution profiles" ,
742+ }
743+ released_names = {
744+ 11 : "deep scan orchestration state" ,
745+ 12 : "scan continuation threads" ,
746+ }
747+ model_migration_name = "persist scan model settings"
748+ warnings_migration_name = "persist scan completion warnings"
749+ has_legacy_profile_history = execution_migrations .get (11 ) == legacy_names [11 ]
750+ has_public_warnings_history = (
751+ execution_migrations .get (25 ) == warnings_migration_name
752+ )
753+
754+ scan_columns = {
755+ row ["name" ] for row in connection .execute ("PRAGMA table_info(scans)" )
756+ }
757+ workspace_columns = {
758+ row ["name" ] for row in connection .execute ("PRAGMA table_info(workspaces)" )
759+ }
760+ has_legacy_profile_columns = (
761+ "execution_model" in scan_columns or "execution_model" in workspace_columns
762+ )
763+ if not (
764+ has_legacy_profile_history
765+ or has_public_warnings_history
766+ or has_legacy_profile_columns
767+ ):
768+ return
769+
770+ if (
771+ any (
772+ execution_migrations .get (version )
773+ not in (None , released_names [version ], legacy_names [version ])
774+ for version in (11 , 12 )
775+ )
776+ or (
777+ has_legacy_profile_history
778+ and execution_migrations .get (12 )
779+ not in (None , legacy_names [12 ], released_names [12 ])
780+ )
781+ or (
782+ not has_legacy_profile_history
783+ and execution_migrations .get (12 ) == legacy_names [12 ]
784+ )
785+ ):
786+ raise SystemExit (
787+ "The Codex Security database has an unsupported execution-profile migration history."
788+ )
789+
790+ expected_legacy_columns = {"execution_model" , "reasoning_effort" }
791+ renamed_legacy_columns = {
792+ "legacy_execution_model" ,
793+ "legacy_reasoning_effort" ,
794+ }
795+ if has_legacy_profile_columns and (
796+ not expected_legacy_columns .issubset (scan_columns )
797+ or not expected_legacy_columns .issubset (workspace_columns )
798+ or renamed_legacy_columns .intersection (scan_columns )
799+ or renamed_legacy_columns .intersection (workspace_columns )
800+ ):
801+ raise SystemExit (
802+ "The Codex Security database has an unsupported execution-profile migration history."
803+ )
804+ if has_legacy_profile_history and not has_legacy_profile_columns :
805+ raise SystemExit (
806+ "The Codex Security database has an unsupported execution-profile migration history."
807+ )
808+
809+ if has_public_warnings_history :
810+ if execution_migrations .get (26 ) is not None :
811+ raise SystemExit (
812+ "The Codex Security database has an unsupported pre-release migration history."
813+ )
814+ connection .execute (
815+ "UPDATE schema_migrations SET version = 26 WHERE version = 25 AND name = ?" ,
816+ (warnings_migration_name ,),
817+ )
818+ execution_migrations .pop (25 )
819+
820+ if execution_migrations .get (25 ) not in (None , model_migration_name ):
821+ raise SystemExit (
822+ "The Codex Security database has an unsupported execution-profile migration history."
823+ )
824+
825+ # Keep the historical values and constraints for recovery while moving
826+ # them out of the namespace used by the current independent scan settings.
827+ for table in ("workspaces" , "scans" ) if has_legacy_profile_columns else ():
828+ connection .execute (
829+ f"ALTER TABLE { table } RENAME COLUMN execution_model TO legacy_execution_model"
830+ )
831+ connection .execute (
832+ f"ALTER TABLE { table } RENAME COLUMN reasoning_effort TO legacy_reasoning_effort"
833+ )
834+ add_column_if_missing (connection , "scans" , "model" , "TEXT" )
835+ add_column_if_missing (connection , "scans" , "reasoning_effort" , "TEXT" )
836+ if has_legacy_profile_columns :
837+ connection .execute (
838+ """
839+ UPDATE scans
840+ SET model = COALESCE(model, legacy_execution_model),
841+ reasoning_effort = legacy_reasoning_effort
842+ WHERE legacy_execution_model IS NOT NULL
843+ OR legacy_reasoning_effort IS NOT NULL
844+ """
845+ )
846+ if has_legacy_profile_history :
847+ for version , name in legacy_names .items ():
848+ connection .execute (
849+ "DELETE FROM schema_migrations WHERE version = ? AND name = ?" ,
850+ (version , name ),
851+ )
852+ if execution_migrations .get (25 ) is None :
853+ connection .execute (
854+ "INSERT INTO schema_migrations (version, name, applied_at) VALUES (?, ?, ?)" ,
855+ (25 , model_migration_name , timestamp ),
856+ )
857+
858+
696859def add_column_if_missing (
697860 connection : sqlite3 .Connection , table : str , column : str , definition : str
698861) -> None :
0 commit comments