@@ -554,6 +554,7 @@ jobs:
554554 COMPARE_SCENARIO="$CAMPAIGN_SCENARIO" \
555555 COMPARE_VARIANT="$variant" \
556556 COMPARE_RUNTIME_PROFILE="$COMPARE_RUNTIME_PROFILE" \
557+ COMPARE_CAMPAIGN_KIND=preflight \
557558 COMPARE_SCENE_SIZE="$CAMPAIGN_SCENE_SIZE" \
558559 COMPARE_WARMUP_SECONDS=10 \
559560 COMPARE_SETTLE_SECONDS=5 \
@@ -631,6 +632,17 @@ jobs:
631632 raise SystemExit("preflight canonical config SHA mismatch")
632633 if manifest.get("runtimeProfile") != "optimized-candidate":
633634 raise SystemExit("preflight runtime profile mismatch")
635+ if manifest.get("campaignKind") != "preflight":
636+ raise SystemExit("preflight campaign kind mismatch")
637+ expected_shutdown = {
638+ "mode": "clean",
639+ "stopGraceSeconds": 120,
640+ "termGraceSeconds": 10,
641+ "forcedAllowed": False,
642+ }
643+ if manifest.get("serverShutdown") != expected_shutdown:
644+ raise SystemExit(
645+ f"preflight server shutdown mismatch: {manifest.get('serverShutdown')!r}")
634646 if manifest.get("requestedFlags") != expected_requested_flags:
635647 raise SystemExit("preflight manifest requested flags mismatch")
636648 if manifest.get("effectiveFlags") != expected_effective_flags:
@@ -753,6 +765,7 @@ jobs:
753765 COMPARE_SCENARIO="$CAMPAIGN_SCENARIO" \
754766 COMPARE_VARIANT="$variant" \
755767 COMPARE_RUNTIME_PROFILE="$COMPARE_RUNTIME_PROFILE" \
768+ COMPARE_CAMPAIGN_KIND="$CAMPAIGN_KIND" \
756769 COMPARE_SCENE_SIZE="$CAMPAIGN_SCENE_SIZE" \
757770 COMPARE_WARMUP_SECONDS="$CAMPAIGN_WARMUP_SECONDS" \
758771 COMPARE_SETTLE_SECONDS="$CAMPAIGN_SETTLE_SECONDS" \
@@ -766,7 +779,7 @@ jobs:
766779 "$CANONICAL_CONFIG_SHA256" "$DRIVER_SHA256" "$PAPER_SHA256" \
767780 "$CLIENT_MANIFEST_SHA256" "$RUNNER_SHA256" "$JVM_ARGUMENTS_SHA256" \
768781 "$AVAILABLE_CPU_COUNT" "$SERVER_CPU_SET" "$CLIENT_CPU_SET" \
769- "$COMPARE_RUNTIME_PROFILE" <<'PY'
782+ "$COMPARE_RUNTIME_PROFILE" "$CAMPAIGN_KIND" <<'PY'
770783 import csv
771784 import json
772785 from pathlib import Path
@@ -775,7 +788,7 @@ jobs:
775788 (manifest_text, root_text, run_id, scenario, block, position, variant,
776789 scene_size_text, stack_sha, artifact_sha, config_sha, driver_sha,
777790 paper_sha, client_sha, runner_sha, jvm_sha, available_cpu_count,
778- server_cpu_set, client_cpu_set, runtime_profile) = sys.argv[1:]
791+ server_cpu_set, client_cpu_set, runtime_profile, campaign_kind ) = sys.argv[1:]
779792 root = Path(root_text)
780793 metrics_path = root / run_id / "iv-compare.json"
781794 run_manifest_path = root / run_id / "run-manifest.json"
@@ -841,13 +854,33 @@ jobs:
841854 "serverCpuSet": expected_server_cpu_set,
842855 "clientCpuSet": expected_client_cpu_set,
843856 "runtimeProfile": runtime_profile,
857+ "campaignKind": campaign_kind,
844858 "requestedFlags": expected_requested_flags,
845859 "effectiveFlags": expected_effective_flags,
846860 }
847861 for field, expected in expected_manifest.items():
848862 if run_manifest.get(field) != expected:
849863 raise SystemExit(
850864 f"run manifest mismatch {run_id}/{field}: {run_manifest.get(field)!r} != {expected!r}")
865+ expected_forced_shutdown = (
866+ campaign_kind == "formal" and scenario == "block-active" and variant == "A")
867+ expected_shutdown = {
868+ "stopGraceSeconds": 120,
869+ "termGraceSeconds": 10,
870+ "forcedAllowed": expected_forced_shutdown,
871+ }
872+ shutdown = run_manifest.get("serverShutdown")
873+ if not isinstance(shutdown, dict):
874+ raise SystemExit(f"server shutdown evidence missing for {run_id}")
875+ for field, expected in expected_shutdown.items():
876+ if shutdown.get(field) != expected:
877+ raise SystemExit(
878+ f"server shutdown mismatch {run_id}/{field}: "
879+ f"{shutdown.get(field)!r} != {expected!r}")
880+ if shutdown.get("mode") not in {"clean", "forced-after-stop-timeout"}:
881+ raise SystemExit(f"invalid server shutdown mode for {run_id}: {shutdown.get('mode')!r}")
882+ if shutdown.get("mode") != "clean" and not expected_forced_shutdown:
883+ raise SystemExit(f"unexpected forced server shutdown for {run_id}")
851884 expected_affinity = {
852885 "availableCpuCount": int(available_cpu_count),
853886 "serverCpuSet": expected_server_cpu_set,
@@ -1033,15 +1066,15 @@ jobs:
10331066 "$CAMPAIGN_STACK_SHA256" "$UPSTREAM_ARTIFACT_SHA256" \
10341067 "$REWRITE_ARTIFACT_SHA256" "$CANONICAL_CONFIG_SHA256" \
10351068 "$AVAILABLE_CPU_COUNT" "$SERVER_CPU_SET" "$CLIENT_CPU_SET" \
1036- "$COMPARE_RUNTIME_PROFILE" <<'PY'
1069+ "$COMPARE_RUNTIME_PROFILE" "$CAMPAIGN_KIND" <<'PY'
10371070 import csv
10381071 import json
10391072 from pathlib import Path
10401073 import sys
10411074
10421075 (manifest_text, root_text, runs_text, scenario, scene_size_text,
10431076 stack_sha, upstream_sha, rewrite_sha, config_sha, available_cpu_count,
1044- server_cpu_set, client_cpu_set, runtime_profile) = sys.argv[1:]
1077+ server_cpu_set, client_cpu_set, runtime_profile, kind ) = sys.argv[1:]
10451078 expected_runs = int(runs_text)
10461079 scene_size = int(scene_size_text)
10471080 root = Path(root_text).resolve()
@@ -1129,6 +1162,28 @@ jobs:
11291162 raise SystemExit(f"runtime profile drifted in {row['RunId']}")
11301163 if run_manifest.get("networkTimeoutSeconds") != 300:
11311164 raise SystemExit(f"network timeout drifted in {row['RunId']}")
1165+ if run_manifest.get("campaignKind") != kind:
1166+ raise SystemExit(f"campaign kind drifted in {row['RunId']}")
1167+ expected_forced_shutdown = (
1168+ kind == "formal" and scenario == "block-active" and variant == "A")
1169+ expected_shutdown = {
1170+ "stopGraceSeconds": 120,
1171+ "termGraceSeconds": 10,
1172+ "forcedAllowed": expected_forced_shutdown,
1173+ }
1174+ shutdown = run_manifest.get("serverShutdown")
1175+ if not isinstance(shutdown, dict):
1176+ raise SystemExit(f"server shutdown evidence missing in {row['RunId']}")
1177+ for field, expected in expected_shutdown.items():
1178+ if shutdown.get(field) != expected:
1179+ raise SystemExit(
1180+ f"server shutdown drift {row['RunId']}/{field}: "
1181+ f"{shutdown.get(field)!r} != {expected!r}")
1182+ if shutdown.get("mode") not in {"clean", "forced-after-stop-timeout"}:
1183+ raise SystemExit(
1184+ f"invalid server shutdown mode in {row['RunId']}: {shutdown.get('mode')!r}")
1185+ if shutdown.get("mode") != "clean" and not expected_forced_shutdown:
1186+ raise SystemExit(f"unexpected forced server shutdown in {row['RunId']}")
11321187 if scenario == "block-active":
11331188 block_guards = {
11341189 "furnaceBlocks": 205,
@@ -1199,9 +1254,22 @@ jobs:
11991254 root = Path(root_text)
12001255 rows = list(csv.DictReader((root / "abba-manifest.csv").open(encoding="utf-8")))
12011256 metrics_by_variant = {"A": [], "B": []}
1257+ shutdown_modes_by_variant = {"A": [], "B": []}
12021258 for row in rows:
12031259 metrics = json.loads((root / row["SourcePath"]).read_text(encoding="utf-8"))
12041260 metrics_by_variant[row["Variant"]].append(metrics)
1261+ run_manifest = json.loads(
1262+ (root / row["SourcePath"]).with_name("run-manifest.json").read_text(
1263+ encoding="utf-8"))
1264+ shutdown_modes_by_variant[row["Variant"]].append(
1265+ run_manifest["serverShutdown"]["mode"])
1266+ shutdown_mode_counts = {
1267+ variant: {
1268+ mode: modes.count(mode)
1269+ for mode in ("clean", "forced-after-stop-timeout")
1270+ }
1271+ for variant, modes in shutdown_modes_by_variant.items()
1272+ }
12051273 metric_specs = [
12061274 ("msptMean", "ms"),
12071275 ("msptP95", "ms"),
@@ -1269,6 +1337,11 @@ jobs:
12691337 "`PacketOnlyStatic=true` and `EventDriven=true` for both artifacts.",
12701338 "Runtime assertion: A reports both flags as `unsupported-legacy`; "
12711339 "B exposes both runtime fields as `true`.",
1340+ ("Server shutdown evidence: "
1341+ f"A clean `{shutdown_mode_counts['A']['clean']}`, forced "
1342+ f"`{shutdown_mode_counts['A']['forced-after-stop-timeout']}`; "
1343+ f"B clean `{shutdown_mode_counts['B']['clean']}`, forced "
1344+ f"`{shutdown_mode_counts['B']['forced-after-stop-timeout']}`."),
12721345 "",
12731346 f"A is official upstream Jenkins #163; B is production candidate "
12741347 f"`{candidate_source[:7]}`. ",
@@ -1371,6 +1444,10 @@ jobs:
13711444 100 if formal and scenario == "block-active" else None
13721445 ),
13731446 },
1447+ "serverShutdown": {
1448+ "forcedTerminationAllowedOnlyForFormalBlockActiveUpstream": True,
1449+ "modeCountsByVariant": shutdown_mode_counts,
1450+ },
13741451 "metrics": {
13751452 metric: {
13761453 "medianBRatioToA": float(result["medianBRatioToA"]),
0 commit comments