Skip to content

Commit a695192

Browse files
yyodsclaude
andcommitted
[red] pin V5 Waveshare fixed largest-block floor and era routing
Witnessed failing before GREEN: - test_oi1_profile_bench: bench.DERIVATION not V5; committed policy not the expected 5036-byte c3167853 V5 identity; waveshare derive floor not fixed at 98304; evaluate does not carry the fixed floor. - test_release_v060_contract: V5 registry constants absent; HEAD routes to V4 instead of V5; active policy is not the exact one-field delta of the first-replacement b9b19ddc policy. - test_release_v060_promotion_lifecycle: completion era binding lacks QUALIFICATION_DERIVATION_V5 and refuses nothing at the 7d85328 boundary. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0173nRnCob9LXYShoQmC85xZ Signed-off-by: Viwat Vchirawongkwin <viwat.v@chula.ac.th>
1 parent 7705ba2 commit a695192

3 files changed

Lines changed: 242 additions & 21 deletions

File tree

‎tests/firmware_tests/host/test_oi1_profile_bench.py‎

Lines changed: 126 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,21 @@
5656
"boot_ceiling": "fixed-profile-product-slo-esp3000-pico7000-v4",
5757
"goodput_floor": "fixed-product-slo-64k-under-10s-6600-v3",
5858
}
59+
SECOND_REPLACEMENT_V5_DERIVATION = {
60+
"application_image": "exact-byte-identical-two-root-v1",
61+
"application_headroom": "factory-minus-application-v1",
62+
"heap_floor": "floor-min-1024-waveshare-block-98304-v2",
63+
"boot_ceiling": "fixed-profile-product-slo-esp3000-pico7000-v4",
64+
"goodput_floor": "fixed-product-slo-64k-under-10s-6600-v3",
65+
}
66+
WAVESHARE_PROFILE_ID = "waveshare-esp32-s3-lcd-147b"
67+
V5_FIXED_WAVESHARE_LARGEST_BLOCK_MIN_BYTES = 98304
68+
EXPECTED_LARGEST_BLOCK_FLOORS = {
69+
"esp32-4mb": 55296,
70+
"esp32-s3-n16r8": 102400,
71+
WAVESHARE_PROFILE_ID: V5_FIXED_WAVESHARE_LARGEST_BLOCK_MIN_BYTES,
72+
"esp32-c3-4mb": 57344,
73+
}
5974
FIXED_PERFORMANCE_THRESHOLDS = {
6075
profile_id: {
6176
"reset_to_service_advertisement_max_ms": (
@@ -404,7 +419,7 @@ def test_exact_v060_candidate_profiles_and_common_workload_are_frozen(self):
404419
)
405420
self.assertEqual(
406421
bench.DERIVATION,
407-
REPLACEMENT_V4_DERIVATION,
422+
SECOND_REPLACEMENT_V5_DERIVATION,
408423
)
409424

410425
def test_committed_v060_policy_uses_fixed_slos_and_retains_a8_baseline(self):
@@ -444,7 +459,7 @@ def test_committed_v060_policy_uses_fixed_slos_and_retains_a8_baseline(self):
444459
),
445460
},
446461
)
447-
self.assertEqual(policy["derivation"], REPLACEMENT_V4_DERIVATION)
462+
self.assertEqual(policy["derivation"], SECOND_REPLACEMENT_V5_DERIVATION)
448463
self.assertEqual(
449464
{
450465
entry["profile_id"]: {
@@ -459,10 +474,20 @@ def test_committed_v060_policy_uses_fixed_slos_and_retains_a8_baseline(self):
459474
},
460475
FIXED_PERFORMANCE_THRESHOLDS,
461476
)
462-
self.assertEqual(len(payload), 5015)
477+
self.assertEqual(
478+
{
479+
entry["profile_id"]: entry["thresholds"][
480+
"idf_internal_largest_block_min_bytes"
481+
]
482+
for entry in policy["profiles"]
483+
if "idf_internal_largest_block_min_bytes" in entry["thresholds"]
484+
},
485+
EXPECTED_LARGEST_BLOCK_FLOORS,
486+
)
487+
self.assertEqual(len(payload), 5036)
463488
self.assertEqual(
464489
hashlib.sha256(payload).hexdigest(),
465-
"b9b19ddc217598835185429d6d1f1da60bb3a504a447e32dcdeae15bf18bd0c5",
490+
"c3167853df6c31d7364b58616701d45ea62f2374d18cacc89e51292624dca8db",
466491
)
467492

468493
def test_retained_a8_baseline_bytes_and_samples_remain_immutable(self):
@@ -1009,6 +1034,103 @@ def test_repeatability_allowances_do_not_change_static_or_heap_gates(self):
10091034
},
10101035
)
10111036

1037+
def test_waveshare_largest_block_floor_is_fixed_one_page_below_baseline(self):
1038+
# ADR-0039: the fixed Waveshare exception never derives from samples,
1039+
# while the identically shaped generic S3 keeps floor-min-1024-v1.
1040+
heaps = [_heap(8318000, 1, 177500, 102400, 177300) for _ in range(16)]
1041+
observation = {
1042+
"reset_to_service_advertisement_ms": [900] * 10,
1043+
"heap_post_hello": heaps[:10],
1044+
"heap_post_roundtrip": heaps[10:15],
1045+
"heap_post_reliability": heaps[15],
1046+
"put_committed_goodput_bytes_per_second": [13000] * 5,
1047+
"get_verified_goodput_bytes_per_second": [29000] * 5,
1048+
}
1049+
build = {
1050+
"application_image_bytes": 1720112,
1051+
"factory_partition_bytes": 2097152,
1052+
"application_headroom_bytes": 377040,
1053+
}
1054+
waveshare = bench.derive_thresholds(
1055+
build, observation, profile_id=WAVESHARE_PROFILE_ID
1056+
)
1057+
self.assertEqual(
1058+
waveshare["idf_internal_largest_block_min_bytes"],
1059+
V5_FIXED_WAVESHARE_LARGEST_BLOCK_MIN_BYTES,
1060+
)
1061+
generic = bench.derive_thresholds(
1062+
build, observation, profile_id="esp32-s3-n16r8"
1063+
)
1064+
self.assertEqual(
1065+
generic["idf_internal_largest_block_min_bytes"], 102400
1066+
)
1067+
for other_key in (
1068+
"gc_free_min_bytes",
1069+
"idf_internal_free_min_bytes",
1070+
"idf_internal_minimum_free_min_bytes",
1071+
"reset_to_service_advertisement_max_ms",
1072+
"put_committed_goodput_min_bytes_per_second",
1073+
"get_verified_goodput_min_bytes_per_second",
1074+
"application_image_max_bytes",
1075+
"application_headroom_min_bytes",
1076+
):
1077+
self.assertEqual(waveshare[other_key], generic[other_key])
1078+
1079+
def test_waveshare_fixed_block_floor_rejects_below_and_accepts_boundary(self):
1080+
def observation_with_block(block):
1081+
heaps = [_heap(8318000, 1, 177500, 102400, 177300) for _ in range(15)]
1082+
dipped = _heap(8318000, 1, 177500, block, 177300)
1083+
return {
1084+
"reset_to_service_advertisement_ms": [900] * 10,
1085+
"heap_post_hello": heaps[:9] + [dipped],
1086+
"heap_post_roundtrip": heaps[9:14],
1087+
"heap_post_reliability": heaps[14],
1088+
"put_committed_goodput_bytes_per_second": [13000] * 5,
1089+
"get_verified_goodput_bytes_per_second": [29000] * 5,
1090+
}
1091+
1092+
build = {
1093+
"application_image_bytes": 1720112,
1094+
"factory_partition_bytes": 2097152,
1095+
"application_headroom_bytes": 377040,
1096+
}
1097+
thresholds = {
1098+
"application_image_max_bytes": 1720128,
1099+
"application_headroom_min_bytes": 377024,
1100+
"gc_free_min_bytes": 8316928,
1101+
"idf_internal_free_min_bytes": 177152,
1102+
"idf_internal_largest_block_min_bytes": (
1103+
V5_FIXED_WAVESHARE_LARGEST_BLOCK_MIN_BYTES
1104+
),
1105+
"idf_internal_minimum_free_min_bytes": 177152,
1106+
"reset_to_service_advertisement_max_ms": 3000,
1107+
"put_committed_goodput_min_bytes_per_second": 6600,
1108+
"get_verified_goodput_min_bytes_per_second": 6600,
1109+
}
1110+
derived = bench.evaluate_thresholds(
1111+
build,
1112+
observation_with_block(98304),
1113+
thresholds,
1114+
profile_id=WAVESHARE_PROFILE_ID,
1115+
)
1116+
# ADR-0039: the returned diagnostic derivation must carry the fixed
1117+
# floor, never the sample-derived floor-min value.
1118+
self.assertEqual(
1119+
derived["idf_internal_largest_block_min_bytes"],
1120+
V5_FIXED_WAVESHARE_LARGEST_BLOCK_MIN_BYTES,
1121+
)
1122+
with self.assertRaises(bench.BenchError) as caught:
1123+
bench.evaluate_thresholds(
1124+
build,
1125+
observation_with_block(98303),
1126+
thresholds,
1127+
profile_id=WAVESHARE_PROFILE_ID,
1128+
)
1129+
self.assertIn(
1130+
"idf_internal_largest_block_min_bytes=98303 is below 98304",
1131+
str(caught.exception),
1132+
)
1133+
10121134
repeat_observation = dict(observation)
10131135
repeat_observation.update(
10141136
{

‎tests/firmware_tests/host/test_release_v060_contract.py‎

Lines changed: 102 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,17 @@
6868
"boot_ceiling": "fixed-profile-product-slo-esp3000-pico7000-v4",
6969
"goodput_floor": "fixed-product-slo-64k-under-10s-6600-v3",
7070
}
71+
V060_FIRST_REPLACEMENT_SOURCE_BOUNDARY = (
72+
"7d853289815751c7381c9fd0b9a9a4409bdb6879"
73+
)
74+
QUALIFICATION_DERIVATION_V5 = {
75+
"application_image": "exact-byte-identical-two-root-v1",
76+
"application_headroom": "factory-minus-application-v1",
77+
"heap_floor": "floor-min-1024-waveshare-block-98304-v2",
78+
"boot_ceiling": "fixed-profile-product-slo-esp3000-pico7000-v4",
79+
"goodput_floor": "fixed-product-slo-64k-under-10s-6600-v3",
80+
}
81+
V5_FIXED_WAVESHARE_LARGEST_BLOCK_MIN_BYTES = 98304
7182
FIXED_PERFORMANCE_THRESHOLDS = {
7283
profile_id: {
7384
"reset_to_service_advertisement_max_ms": (
@@ -409,15 +420,48 @@ def test_replacement_registry_preserves_history_and_freezes_v4(self):
409420
getattr(RELEASE, "V060_ABANDONED_CANDIDATE_SOURCE", None),
410421
V060_ABANDONED_CANDIDATE_SOURCE,
411422
)
423+
self.assertEqual(
424+
getattr(RELEASE, "QUALIFICATION_DERIVATION_V5", None),
425+
QUALIFICATION_DERIVATION_V5,
426+
"ADR-0039: the second-replacement derivation changes exactly "
427+
"the heap-floor identifier",
428+
)
429+
self.assertEqual(
430+
getattr(
431+
RELEASE, "V060_FIRST_REPLACEMENT_SOURCE_BOUNDARY", None
432+
),
433+
V060_FIRST_REPLACEMENT_SOURCE_BOUNDARY,
434+
"ADR-0039: the first-replacement candidate source is the "
435+
"second era boundary",
436+
)
437+
self.assertEqual(
438+
getattr(
439+
RELEASE,
440+
"V5_FIXED_WAVESHARE_LARGEST_BLOCK_MIN_BYTES",
441+
None,
442+
),
443+
V5_FIXED_WAVESHARE_LARGEST_BLOCK_MIN_BYTES,
444+
)
412445

413446
def test_derivation_selection_routes_by_source_ancestry_not_semver(self):
414447
selector = self.selector()
415448
head = self.git_output(REPO_ROOT, "rev-parse", "HEAD")
416449

417450
self.assertEqual(
418451
selector(REPO_ROOT, head, firmware_version="0.6.0"),
452+
QUALIFICATION_DERIVATION_V5,
453+
"a strict descendant of the first-replacement boundary is the "
454+
"second-replacement era (ADR-0039)",
455+
)
456+
self.assertEqual(
457+
selector(
458+
REPO_ROOT,
459+
V060_FIRST_REPLACEMENT_SOURCE_BOUNDARY,
460+
firmware_version="0.6.0",
461+
),
419462
QUALIFICATION_DERIVATION_V4,
420-
"a strict descendant of the boundary is the replacement era",
463+
"the first-replacement boundary itself keeps the V4 era; only "
464+
"strict descendants enter the second replacement",
421465
)
422466
for source_commit in (
423467
V060_SUPERSEDED_SOURCE_BOUNDARY,
@@ -537,7 +581,30 @@ def test_active_policy_is_exact_fixed_slo_delta_of_superseded_policy(self):
537581
active_raw,
538582
"the active policy must stay canonical sorted-key JSON",
539583
)
540-
self.assertEqual(active["derivation"], QUALIFICATION_DERIVATION_V4)
584+
self.assertEqual(active["derivation"], QUALIFICATION_DERIVATION_V5)
585+
586+
first_replacement_raw = subprocess.run(
587+
[
588+
"git",
589+
"-C",
590+
str(REPO_ROOT),
591+
"show",
592+
"%s:firmware/qualification/oi1-gates.json"
593+
% V060_FIRST_REPLACEMENT_SOURCE_BOUNDARY,
594+
],
595+
check=True,
596+
capture_output=True,
597+
).stdout
598+
self.assertEqual(len(first_replacement_raw), 5015)
599+
self.assertEqual(
600+
hashlib.sha256(first_replacement_raw).hexdigest(),
601+
"b9b19ddc217598835185429d6d1f1da60bb3a504a447e32dcdeae15bf18bd0c5",
602+
"the first-replacement policy identity must stay auditable",
603+
)
604+
first_replacement = json.loads(first_replacement_raw.decode("utf-8"))
605+
self.assertEqual(
606+
first_replacement["derivation"], QUALIFICATION_DERIVATION_V4
607+
)
541608

542609
expected = copy.deepcopy(historical)
543610
expected["derivation"] = copy.deepcopy(QUALIFICATION_DERIVATION_V4)
@@ -552,16 +619,46 @@ def test_active_policy_is_exact_fixed_slo_delta_of_superseded_policy(self):
552619
changed_numeric_fields,
553620
11,
554621
"exactly the Pico reset ceiling and all ten transfer floors "
555-
"change; the two derivation identifiers are separate string "
556-
"fields",
622+
"change between the superseded and first-replacement policies; "
623+
"the two derivation identifiers are separate string fields",
557624
)
558625
self.assertEqual(
559-
active,
626+
first_replacement,
560627
expected,
561628
"every static/heap threshold, schema field, and baseline "
562629
"binding must stay byte-identical to the superseded policy",
563630
)
564631

632+
expected_active = copy.deepcopy(first_replacement)
633+
expected_active["derivation"] = copy.deepcopy(
634+
QUALIFICATION_DERIVATION_V5
635+
)
636+
second_changed_numeric_fields = 0
637+
for entry in expected_active["profiles"]:
638+
if entry["profile_id"] == "waveshare-esp32-s3-lcd-147b":
639+
self.assertEqual(
640+
entry["thresholds"][
641+
"idf_internal_largest_block_min_bytes"
642+
],
643+
102400,
644+
)
645+
entry["thresholds"][
646+
"idf_internal_largest_block_min_bytes"
647+
] = V5_FIXED_WAVESHARE_LARGEST_BLOCK_MIN_BYTES
648+
second_changed_numeric_fields += 1
649+
self.assertEqual(
650+
second_changed_numeric_fields,
651+
1,
652+
"ADR-0039 changes exactly the Waveshare largest-block floor; "
653+
"the heap-floor identifier is a separate string field",
654+
)
655+
self.assertEqual(
656+
active,
657+
expected_active,
658+
"every other threshold, schema field, and baseline binding "
659+
"must stay byte-identical to the first-replacement policy",
660+
)
661+
565662

566663
class V060ReproducibilityRoutingContractTests(unittest.TestCase):
567664
@staticmethod

‎tests/firmware_tests/host/test_release_v060_promotion_lifecycle.py‎

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -429,15 +429,16 @@ def test_completion_writer_rejects_operator_gate_fields_or_wrong_private_phase(
429429
self.assertFalse(output.exists())
430430

431431
def test_completion_binds_policy_era_to_candidate_source_ancestry(self) -> None:
432-
"""ADR-0037/ADR-0038: the completion writer's derivation era is a git
433-
ancestry fact of the CANDIDATE's bound source commit.
434-
435-
A strict descendant of the superseded-source boundary must verify the
436-
checked-in V4 fixed-SLO policy; the boundary and its ancestors route
437-
to the historical arithmetic (so the committed V4 policy is refused as
438-
the wrong source era); an unprovable commit fails closed with an
439-
error naming ancestry — never a baseline-threshold mismatch and never
440-
a silent fall back to historical arithmetic.
432+
"""ADR-0037/ADR-0038/ADR-0039: the completion writer's derivation era
433+
is a git ancestry fact of the CANDIDATE's bound source commit.
434+
435+
A strict descendant of the first-replacement boundary must verify the
436+
checked-in second-replacement (V5) policy; earlier boundaries and
437+
their ancestors route to their historical arithmetic (so the
438+
committed V5 policy is refused as the wrong source era); an
439+
unprovable commit fails closed with an error naming ancestry — never
440+
a baseline-threshold mismatch and never a silent fall back to
441+
historical arithmetic.
441442
"""
442443

443444
pending = pending_v5_payload()
@@ -489,21 +490,22 @@ def attempt(commit: str, output_name: str) -> Path:
489490
RELEASE._qualification_derivation_for_source(
490491
root, descendant, firmware_version="0.6.0"
491492
),
492-
RELEASE.QUALIFICATION_DERIVATION_V4,
493+
RELEASE.QUALIFICATION_DERIVATION_V5,
493494
"the fixture root must prove the descendant's ancestry",
494495
)
495496
created = attempt(descendant, "descendant-completion.json")
496497
self.assertEqual(Path(created), root / "descendant-completion.json")
497498
self.assertEqual(
498499
json.loads(created.read_text(encoding="utf-8")),
499500
completion_fragment(profile_id),
500-
"a strict descendant of the boundary must verify the "
501-
"checked-in V4 fixed-SLO policy and complete",
501+
"a strict descendant of the first-replacement boundary must "
502+
"verify the checked-in V5 policy and complete",
502503
)
503504

504505
for superseded in (
505506
RELEASE.V060_SUPERSEDED_SOURCE_BOUNDARY,
506507
RELEASE.V060_ABANDONED_CANDIDATE_SOURCE,
508+
"7d853289815751c7381c9fd0b9a9a4409bdb6879",
507509
):
508510
with self.subTest(superseded_commit=superseded):
509511
output_name = "superseded-%s.json" % superseded[:8]

0 commit comments

Comments
 (0)