6161 "behavior" ,
6262}
6363KINDS = SYMBOL_KINDS | NONSYMBOL_KINDS
64+ # param-value rows share the symbol LIFECYCLE (planned -> shimmed -> removed, test_ref at
65+ # shim/removal, due-gated) but skip reality probes - accepted values are not introspectable;
66+ # their behavioral enforcement lives in the row's test_ref suite.
67+ LIFECYCLE_KINDS = SYMBOL_KINDS | {"param-value" }
6468SYMBOL_STATUSES = {"planned" , "shimmed" , "removed" }
6569NONSYMBOL_STATUSES = {"planned" , "evaluate" , "done" }
6670WARNING_VALUES = {"FutureWarning" , "DeprecationWarning" }
@@ -188,7 +192,7 @@ def validate_schema(rows):
188192 if kind not in KINDS :
189193 errors .append (f"{ rid } : unknown kind '{ kind } '" )
190194 continue
191- legal = SYMBOL_STATUSES if kind in SYMBOL_KINDS else NONSYMBOL_STATUSES
195+ legal = SYMBOL_STATUSES if kind in LIFECYCLE_KINDS else NONSYMBOL_STATUSES
192196 if status not in legal :
193197 errors .append (
194198 f"{ rid } : status '{ status } ' illegal for kind '{ kind } ' (legal: { sorted (legal )} )"
@@ -199,7 +203,7 @@ def validate_schema(rows):
199203 errors .append (f"{ rid } : { vfield } ={ val !r} does not match \\ d+.\\ d+(.\\ d+)?" )
200204 if row .get ("warning" ) is not None and row ["warning" ] not in WARNING_VALUES :
201205 errors .append (f"{ rid } : warning={ row ['warning' ]!r} not in { sorted (WARNING_VALUES )} " )
202- if kind in SYMBOL_KINDS and status in ("shimmed" , "removed" ) and not row .get ("test_ref" ):
206+ if kind in LIFECYCLE_KINDS and status in ("shimmed" , "removed" ) and not row .get ("test_ref" ):
203207 errors .append (
204208 f"{ rid } : status '{ status } ' requires a test_ref (dedicated behavioral test)"
205209 )
@@ -447,7 +451,7 @@ def collect_due_problems(rows, current):
447451 f"still in the future - early removal/flip breaks the shim window promise"
448452 )
449453 if removed_in and current >= _version_tuple (removed_in ):
450- if kind in SYMBOL_KINDS and status != "removed" :
454+ if kind in LIFECYCLE_KINDS and status != "removed" :
451455 problems .append (f"{ rid } : removed_in { removed_in } is due but status is '{ status } '" )
452456 if kind == "alias" and status != "done" :
453457 problems .append (f"{ rid } : removed_in { removed_in } is due but status is '{ status } '" )
@@ -469,7 +473,7 @@ def collect_due_problems(rows, current):
469473 "(new surface not shipped?)"
470474 )
471475 if (
472- kind in SYMBOL_KINDS
476+ kind in LIFECYCLE_KINDS
473477 and row .get ("warning" )
474478 and row .get ("deprecated_in" )
475479 and current >= _version_tuple (row ["deprecated_in" ])
@@ -585,6 +589,22 @@ def test_all_membership_helper_semantics():
585589 "removed_in 4.0 is due" ,
586590 (3 , 9 , 0 ),
587591 ),
592+ # overdue param-value removal: value migrations are due-gated like symbol rows
593+ (
594+ {
595+ "id" : "M-906" ,
596+ "kind" : "param-value" ,
597+ "old" : "diff_diff:WooldridgeDiDResults.aggregate[type]=event" ,
598+ "new" : "diff_diff:WooldridgeDiDResults.aggregate[type]=event_study" ,
599+ "deprecated_in" : "3.9" ,
600+ "removed_in" : "4.0" ,
601+ "status" : "shimmed" ,
602+ "phase" : 5 ,
603+ },
604+ (4 , 0 , 0 ),
605+ "removed_in 4.0 is due" ,
606+ (3 , 9 , 0 ),
607+ ),
588608 # EARLY removal: row flipped to removed while removed_in is still in the future
589609 (
590610 {
@@ -606,6 +626,7 @@ def test_all_membership_helper_semantics():
606626 "overdue-introduce-only-alias" ,
607627 "overdue-env-default-decision" ,
608628 "overdue-removal" ,
629+ "overdue-param-value-removal" ,
609630 "early-removal-before-schedule" ,
610631 ],
611632)
@@ -625,7 +646,12 @@ def test_row_matches_reality(row, monkeypatch):
625646 rid , kind , status = row ["id" ], row ["kind" ], row ["status" ]
626647 old , new = row ["old" ], row ["new" ]
627648
628- if kind in ("param-value" , "behavior" ):
649+ if kind == "param-value" :
650+ pytest .skip (
651+ "no reality probe (accepted values are not introspectable); lifecycle is "
652+ "schema+due-gate enforced and value behavior lives in the row's test_ref suite"
653+ )
654+ if kind == "behavior" :
629655 pytest .skip ("schema-checked kind; flipped manually, swept at the cut" )
630656
631657 if kind in ("param" , "class" , "function" ):
@@ -849,6 +875,12 @@ def _schema_errors_for(text):
849875 "code_refs must be a non-empty list" ,
850876 ),
851877 (lambda t : t .replace (" group: fixture\n " , "" ), "missing required field" ),
878+ (
879+ lambda t : t .replace (" kind: param\n " , " kind: param-value\n " ).replace (
880+ " status: planned\n " , " status: shimmed\n "
881+ ),
882+ "requires a test_ref" ,
883+ ),
852884 (
853885 lambda t : t .replace (" kind: param\n " , " kind: behavior\n " ).replace (
854886 " status: planned\n " , " status: done\n "
@@ -866,6 +898,7 @@ def _schema_errors_for(text):
866898 "dotted-locator-on-removable" ,
867899 "empty-code-refs" ,
868900 "missing-group" ,
901+ "param-value-shimmed-without-test-ref" ,
869902 "behavior-done-without-test-ref" ,
870903 ],
871904)
0 commit comments