@@ -131,8 +131,9 @@ def excluded_unchanged_rbac(step, previous):
131131 and before .get ("protect" , False ) == after .get ("protect" , False ))
132132
133133
134- def validate_preview (preview , phase , completed_hooks = None , ingress_scaling = False ):
134+ def validate_preview (preview , phase , completed_hooks = None , ingress_scaling = False , burst_headroom = False ):
135135 require (phase in ("off" , "prepare" , "enabled" ), "scaling-phase" )
136+ require (not burst_headroom or (ingress_scaling and phase == "enabled" ), "headroom-profile" )
136137 require (preview .get ("steps" ) and isinstance (preview .get ("changeSummary" ), dict ), "preview-incomplete" )
137138 require (not preview .get ("maybeCorrupt" ), "preview-state" )
138139 require (not any (d .get ("severity" ) == "error" for d in preview .get ("diagnostics" , [])), "preview-diagnostics" )
@@ -141,7 +142,7 @@ def validate_preview(preview, phase, completed_hooks=None, ingress_scaling=False
141142 if completed_hooks is not None and (excluded_completed_hook (step , completed_hooks ) or excluded_unchanged_rbac (step , completed_hooks )):
142143 continue
143144 try :
144- label = validate_step (step , phase , ingress_scaling )
145+ label = validate_step (step , phase , ingress_scaling , burst_headroom )
145146 if label :
146147 labels .append (label )
147148 except Blocked as error :
@@ -150,7 +151,7 @@ def validate_preview(preview, phase, completed_hooks=None, ingress_scaling=False
150151 return sorted (set (labels ))
151152
152153
153- def validate_step (step , phase , ingress_scaling = False ):
154+ def validate_step (step , phase , ingress_scaling = False , burst_headroom = False ):
154155 op , urn = step ["op" ], step ["urn" ]
155156 if op in ("same" , "refresh" , "read" ):
156157 return None
@@ -163,7 +164,7 @@ def validate_step(step, phase, ingress_scaling=False):
163164 require (phase == "enabled" , "ingress-scaling-phase" )
164165 require (urn not in (APP , HPA ), "ingress-promotion-app-change" )
165166 if urn in (INGRESS_DEPLOYMENT , INGRESS_HPA ):
166- return validate_ingress_step (step )
167+ return validate_ingress_step (step , burst_headroom )
167168 if urn not in (APP , WORKERS , HPA ):
168169 # Fixed categories only: never echo URNs, property values, or diagnostics.
169170 category = {
@@ -214,8 +215,8 @@ def validate_step(step, phase, ingress_scaling=False):
214215 elif urn == WORKERS :
215216 require (op == "update" and old and new , "worker-operation" )
216217 before , after = copy .deepcopy (old ["inputs" ]), copy .deepcopy (new ["inputs" ])
217- require (before ["autoscaling" ].pop ("maxNodeCount" ) in ((4 , 5 ) if ingress_scaling else (3 , 4 )), "worker-existing-maximum" )
218- require (after ["autoscaling" ].pop ("maxNodeCount" ) == (5 if ingress_scaling else (3 if phase == "off" else 4 )), "worker-target-maximum" )
218+ require (before ["autoscaling" ].pop ("maxNodeCount" ) in ((5 , 7 ) if burst_headroom else (( 4 , 5 ) if ingress_scaling else (3 , 4 ) )), "worker-existing-maximum" )
219+ require (after ["autoscaling" ].pop ("maxNodeCount" ) == (7 if burst_headroom else ( 5 if ingress_scaling else (3 if phase == "off" else 4 ) )), "worker-target-maximum" )
219220 require (before == after , "worker-unrelated-input-change" )
220221 return "worker autoscaling ceiling"
221222 else :
@@ -228,12 +229,13 @@ def validate_step(step, phase, ingress_scaling=False):
228229 return "bounded application autoscaler"
229230
230231
231- def validate_ingress_step (step ):
232+ def validate_ingress_step (step , burst_headroom = False ):
232233 old , new = step .get ("oldState" ), step .get ("newState" )
233234 require (step ["op" ] == "update" and old and new , "ingress-operation" )
234235 require (all (old .get (k ) == new .get (k ) for k in ("provider" , "parent" , "protect" )), "ingress-identity" )
235236 before , after = copy .deepcopy (old ["inputs" ]), copy .deepcopy (new ["inputs" ])
236237 if step ["urn" ] == INGRESS_DEPLOYMENT :
238+ require (not burst_headroom , "headroom-must-not-change-ingress-deployment" )
237239 # HPA owns replica count; refreshed old/new values must still agree.
238240 require (before ["spec" ]["replicas" ] == after ["spec" ]["replicas" ], "ingress-replica-ownership" )
239241 for value , desired in ((before , False ), (after , True )):
@@ -253,7 +255,9 @@ def validate_ingress_step(step):
253255 metric = metrics [0 ]["resource" ]
254256 require (metric ["name" ] == "cpu" and metric ["target" ]["type" ] == "Utilization" , "ingress-hpa-cpu" )
255257 percent = metric ["target" ].pop ("averageUtilization" )
256- require ((maximum , percent ) == (8 , 70 ) if desired else (maximum , percent ) in ((6 , 70 ), (8 , 56 ), (8 , 70 )), "ingress-hpa-bounds" )
258+ expected = (16 , 70 ) if burst_headroom else (8 , 70 )
259+ allowed = ((8 , 70 ), (16 , 70 )) if burst_headroom else ((6 , 70 ), (8 , 56 ), (8 , 70 ))
260+ require ((maximum , percent ) == expected if desired else (maximum , percent ) in allowed , "ingress-hpa-bounds" )
257261 label = "bounded ingress autoscaler"
258262 require (before == after , "ingress-unrelated-input-change" )
259263 return label
@@ -423,6 +427,8 @@ def main(mode):
423427 config = json .loads (quiet (["pulumi" , "config" , "--json" , "--stack" , "gcpProd" , "--non-interactive" ], directory , "config" , env ).read_text ())
424428 phase = config .get ("mcp-registry:productionAppScaling" , {}).get ("value" , "off" )
425429 ingress_scaling = config .get ("mcp-registry:productionIngressScaling" , {}).get ("value" , "false" ) == "true"
430+ burst_headroom = config .get ("mcp-registry:productionBurstHeadroom" , {}).get ("value" , "false" ) == "true"
431+ require (not burst_headroom or ingress_scaling , "headroom-requires-ingress-profile" )
426432 quiet ([sys .executable , "scripts/check_production_scaling.py" ], directory , "preflight" , env )
427433 print ("Read-only transition preflight passed." , flush = True )
428434 exported = directory / "before.json"
@@ -431,7 +437,7 @@ def main(mode):
431437 require (all (urn in previous for urn in (STACK , APP , WORKERS )), "existing-production-stack" )
432438 full_plan = directory / "full-plan.json"
433439 full = quiet (["pulumi" , "preview" , "--refresh" , "--json" , "--save-plan" , str (full_plan ), "--stack" , "gcpProd" , "--non-interactive" ], directory , "full-preview" , env )
434- validate_preview (parse_preview_events (full .read_text ()), phase , completed_hooks = previous , ingress_scaling = ingress_scaling )
440+ validate_preview (parse_preview_events (full .read_text ()), phase , completed_hooks = previous , ingress_scaling = ingress_scaling , burst_headroom = burst_headroom )
435441 print ("Full change scope validation passed." , flush = True )
436442 # Never recreate TTL-expired setup jobs as a side effect of scaling.
437443 # The full preview above still blocks every other unrelated change.
@@ -447,13 +453,13 @@ def main(mode):
447453 full_graph = decode_plan (full_plan .read_text ())
448454 target_graph = decode_plan (plan .read_text ())
449455 changed = dependency_mismatch_labels (full_graph , target_graph )
450- labels = validate_preview (parse_preview_events (result .read_text ()), phase , ingress_scaling = ingress_scaling )
456+ labels = validate_preview (parse_preview_events (result .read_text ()), phase , ingress_scaling = ingress_scaling , burst_headroom = burst_headroom )
451457 print ("Full versus targeted dependency comparison: " + ("," .join (changed ) if changed else "identical" ), flush = True )
452458 reconciled , dependency_changes = reconcile_plan_dependencies (full_graph , target_graph )
453459 if dependency_changes :
454460 confirmed_plan = directory / "confirmed-full-plan.json"
455461 confirmed = quiet (["pulumi" , "preview" , "--refresh" , "--json" , "--save-plan" , str (confirmed_plan ), "--stack" , "gcpProd" , "--non-interactive" ], directory , "confirm-preview" , env )
456- validate_preview (parse_preview_events (confirmed .read_text ()), phase , completed_hooks = previous , ingress_scaling = ingress_scaling )
462+ validate_preview (parse_preview_events (confirmed .read_text ()), phase , completed_hooks = previous , ingress_scaling = ingress_scaling , burst_headroom = burst_headroom )
457463 confirmation_graph = decode_plan (confirmed_plan .read_text ())
458464 for urn in dependency_changes :
459465 require (dependency_signature (full_graph ["resourcePlans" ][urn ]["goal" ]) == dependency_signature (confirmation_graph ["resourcePlans" ][urn ]["goal" ]), "unstable-full-dependency-graph-" + resource_kind_label (urn ))
0 commit comments