Skip to content

Commit 0c7230d

Browse files
yyodsclaude
andcommitted
[green] fix Waveshare largest-block floor at 98304 in the V5 era
Adds QUALIFICATION_DERIVATION_V5 (heap_floor floor-min-1024-waveshare-block-98304-v2), the ADR-0039 second boundary 7d85328 with strict-descendant routing that falls through to ADR-0038 routing when the boundary object is absent, the fixed Waveshare largest-block floor in both the release tool and the HIL bench derivations, V5 admission in the unbound-era policy shapes, and the regenerated 5036-byte c3167853 policy. Also restores the test_repeatability tail block accidentally displaced by the red commit's insertion (pre-existing test code moved back verbatim, no new authorship). All twelve affected host suites pass; the policy revalidates through _load_qualification_policy under the checkout-bound V5 era. 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 a695192 commit 0c7230d

4 files changed

Lines changed: 119 additions & 46 deletions

File tree

‎firmware/qualification/oi1-gates.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"application_image": "exact-byte-identical-two-root-v1",
99
"boot_ceiling": "fixed-profile-product-slo-esp3000-pico7000-v4",
1010
"goodput_floor": "fixed-product-slo-64k-under-10s-6600-v3",
11-
"heap_floor": "floor-min-1024-v1"
11+
"heap_floor": "floor-min-1024-waveshare-block-98304-v2"
1212
},
1313
"profile_order": [
1414
"esp32-4mb",
@@ -72,7 +72,7 @@
7272
"gc_free_min_bytes": 8316928,
7373
"get_verified_goodput_min_bytes_per_second": 6600,
7474
"idf_internal_free_min_bytes": 177152,
75-
"idf_internal_largest_block_min_bytes": 102400,
75+
"idf_internal_largest_block_min_bytes": 98304,
7676
"idf_internal_minimum_free_min_bytes": 177152,
7777
"put_committed_goodput_min_bytes_per_second": 6600,
7878
"reset_to_service_advertisement_max_ms": 3000

‎firmware/scripts/release_bundle.py‎

Lines changed: 68 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,17 @@ class ReleaseError(RuntimeError):
160160
"boot_ceiling": "fixed-profile-product-slo-esp3000-pico7000-v4",
161161
"goodput_floor": "fixed-product-slo-64k-under-10s-6600-v3",
162162
}
163-
QUALIFICATION_DERIVATION = QUALIFICATION_DERIVATION_V4
163+
# ADR-0039: the second-replacement v0.6.0 derivation changes exactly the
164+
# heap-floor identifier. Every heap floor keeps floor-min-1024-v1
165+
# arithmetic except the fixed Waveshare largest-block floor.
166+
QUALIFICATION_DERIVATION_V5 = {
167+
"application_image": "exact-byte-identical-two-root-v1",
168+
"application_headroom": "factory-minus-application-v1",
169+
"heap_floor": "floor-min-1024-waveshare-block-98304-v2",
170+
"boot_ceiling": "fixed-profile-product-slo-esp3000-pico7000-v4",
171+
"goodput_floor": "fixed-product-slo-64k-under-10s-6600-v3",
172+
}
173+
QUALIFICATION_DERIVATION = QUALIFICATION_DERIVATION_V5
164174
# ADR-0038: the superseded unpublished v0.6.0 source era ends at and
165175
# includes this commit. Only strict descendants belong to the replacement
166176
# v0.6.0 era and use QUALIFICATION_DERIVATION_V4; the boundary and its
@@ -174,10 +184,20 @@ class ReleaseError(RuntimeError):
174184
V060_ABANDONED_CANDIDATE_SOURCE = (
175185
"719b211345028e49aee9df9b11c4b5fd110913de"
176186
)
187+
# ADR-0039: the first-replacement candidate source is the second era
188+
# boundary. Only strict descendants use QUALIFICATION_DERIVATION_V5; the
189+
# boundary itself and its ancestors keep the ADR-0038 routing above.
190+
V060_FIRST_REPLACEMENT_SOURCE_BOUNDARY = (
191+
"7d853289815751c7381c9fd0b9a9a4409bdb6879"
192+
)
177193
# ADR-0037 fixed product SLOs for the replacement v0.6.0 era.
178194
V4_FIXED_RESET_SLO_MS_ESP = 3000
179195
V4_FIXED_RESET_SLO_MS_RP2 = 7000
180196
V4_FIXED_GOODPUT_FLOOR_BPS = 6600
197+
# ADR-0039: the fixed Waveshare largest-block floor — the baseline-derived
198+
# 102400 minus exactly one 4096-byte page.
199+
V5_FIXED_WAVESHARE_LARGEST_BLOCK_MIN_BYTES = 98304
200+
V5_WAVESHARE_PROFILE_ID = "waveshare-esp32-s3-lcd-147b"
181201
QUALIFICATION_THRESHOLD_KEYS = (
182202
"application_image_max_bytes",
183203
"application_headroom_min_bytes",
@@ -20991,6 +21011,26 @@ def _qualification_derivation_for_source(
2099121011
and COMMIT_RE.fullmatch(source_commit) is not None,
2099221012
"qualification source commit must be full lowercase 40-hex",
2099321013
)
21014+
# ADR-0039: a strict descendant of the first-replacement boundary is
21015+
# the second-replacement era. When this stage cannot be proven (for
21016+
# example a historical checkout that predates the boundary object),
21017+
# fall through to the ADR-0038 routing below, which itself fails
21018+
# closed on any ancestry it cannot prove.
21019+
try:
21020+
second_merge_base = _git_output(
21021+
Path(checkout),
21022+
"PyBLE",
21023+
"merge-base",
21024+
V060_FIRST_REPLACEMENT_SOURCE_BOUNDARY,
21025+
source_commit,
21026+
)
21027+
except ReleaseError:
21028+
second_merge_base = None
21029+
if (
21030+
second_merge_base == V060_FIRST_REPLACEMENT_SOURCE_BOUNDARY
21031+
and source_commit != V060_FIRST_REPLACEMENT_SOURCE_BOUNDARY
21032+
):
21033+
return QUALIFICATION_DERIVATION_V5
2099421034
try:
2099521035
merge_base = _git_output(
2099621036
Path(checkout),
@@ -21163,12 +21203,16 @@ def _validate_qualification_policy(
2116321203
firmware_version,
2116421204
"firmware source version",
2116521205
)
21166-
# ADR-0038: SemVer alone cannot separate the two v0.6.0 source
21167-
# eras. Without a bound source checkout both era derivations are
21168-
# shape-admissible; identity-binding flows narrow to the exact era
21169-
# through the source checkout's ancestry.
21206+
# ADR-0038/ADR-0039: SemVer alone cannot separate the v0.6.0
21207+
# source eras. Without a bound source checkout every era
21208+
# derivation is shape-admissible; identity-binding flows narrow to
21209+
# the exact era through the source checkout's ancestry.
2117021210
allowed_derivations = (
21171-
(QUALIFICATION_DERIVATION_V3, QUALIFICATION_DERIVATION_V4)
21211+
(
21212+
QUALIFICATION_DERIVATION_V3,
21213+
QUALIFICATION_DERIVATION_V4,
21214+
QUALIFICATION_DERIVATION_V5,
21215+
)
2117221216
if selected == QUALIFICATION_DERIVATION_V3
2117321217
and source_core >= (0, 6, 0)
2117421218
else (selected,)
@@ -21179,6 +21223,7 @@ def _validate_qualification_policy(
2117921223
QUALIFICATION_DERIVATION_V2,
2118021224
QUALIFICATION_DERIVATION_V3,
2118121225
QUALIFICATION_DERIVATION_V4,
21226+
QUALIFICATION_DERIVATION_V5,
2118221227
)
2118321228
_require(
2118421229
all(type(value) is str for value in derivation.values())
@@ -22123,7 +22168,10 @@ def floor_min(metric: str, quantum: int) -> int:
2212322168
):
2212422169
put_min = (put_min * 95) // 100
2212522170
get_min = (get_min * 95) // 100
22126-
if derivation == QUALIFICATION_DERIVATION_V4:
22171+
if derivation in (
22172+
QUALIFICATION_DERIVATION_V4,
22173+
QUALIFICATION_DERIVATION_V5,
22174+
):
2212722175
# ADR-0037: fixed product SLOs, never functions of baseline extrema.
2212822176
reset_threshold = (
2212922177
V4_FIXED_RESET_SLO_MS_RP2
@@ -22179,6 +22227,15 @@ def floor_min(metric: str, quantum: int) -> int:
2217922227
),
2218022228
}
2218122229
)
22230+
if (
22231+
derivation == QUALIFICATION_DERIVATION_V5
22232+
and profile_id == V5_WAVESHARE_PROFILE_ID
22233+
):
22234+
# ADR-0039: the single fixed heap-floor exception — never a
22235+
# function of the observed samples.
22236+
derived["idf_internal_largest_block_min_bytes"] = (
22237+
V5_FIXED_WAVESHARE_LARGEST_BLOCK_MIN_BYTES
22238+
)
2218222239
for key, value in derived.items():
2218322240
_qualification_integer(
2218422241
value,
@@ -24375,7 +24432,10 @@ def _profile_thresholds(
2437524432
observation: dict[str, Any],
2437624433
profile_id: str,
2437724434
) -> dict[str, int]:
24378-
if qualification_derivation == QUALIFICATION_DERIVATION_V4:
24435+
if qualification_derivation in (
24436+
QUALIFICATION_DERIVATION_V4,
24437+
QUALIFICATION_DERIVATION_V5,
24438+
):
2437924439
return _derived_qualification_thresholds(
2438024440
build,
2438124441
observation,

‎tests/firmware_tests/hil/_pble_bench.py‎

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@
9090
DERIVATION = {
9191
"application_image": "exact-byte-identical-two-root-v1",
9292
"application_headroom": "factory-minus-application-v1",
93-
"heap_floor": "floor-min-1024-v1",
93+
"heap_floor": "floor-min-1024-waveshare-block-98304-v2",
9494
"boot_ceiling": "fixed-profile-product-slo-esp3000-pico7000-v4",
9595
"goodput_floor": "fixed-product-slo-64k-under-10s-6600-v3",
9696
}
@@ -114,6 +114,13 @@
114114
FIXED_RESET_SLO_MS_RP2 = 7000
115115
FIXED_GOODPUT_FLOOR_BPS = 6600
116116

117+
# ADR-0039: the single fixed heap-floor exception — the Waveshare
118+
# largest-block floor is the baseline-derived 102400 minus exactly one
119+
# 4096-byte page, admitting that image's characterized single-page
120+
# transient. Never a function of observed samples.
121+
FIXED_WAVESHARE_LARGEST_BLOCK_MIN_BYTES = 98304
122+
WAVESHARE_PROFILE_ID = "waveshare-esp32-s3-lcd-147b"
123+
117124
RP2_PROFILE_ID = "rpi-pico2-w"
118125

119126
HEAP_KEYS = (
@@ -748,6 +755,18 @@ def derive_thresholds(oi1_build, observation, profile_id=None):
748755
heap_keys=HEAP_KEYS,
749756
)
750757

758+
largest_block_floor = (
759+
# ADR-0039: the fixed Waveshare exception, never sample-derived.
760+
FIXED_WAVESHARE_LARGEST_BLOCK_MIN_BYTES
761+
if profile_id == WAVESHARE_PROFILE_ID
762+
else floor_quantum(
763+
min(
764+
item["idf_internal_largest_block_bytes"]
765+
for item in snapshots
766+
),
767+
1024,
768+
)
769+
)
751770
return {
752771
"application_image_max_bytes": application_size,
753772
"application_headroom_min_bytes": headroom,
@@ -757,13 +776,7 @@ def derive_thresholds(oi1_build, observation, profile_id=None):
757776
"idf_internal_free_min_bytes": floor_quantum(
758777
min(item["idf_internal_free_bytes"] for item in snapshots), 1024
759778
),
760-
"idf_internal_largest_block_min_bytes": floor_quantum(
761-
min(
762-
item["idf_internal_largest_block_bytes"]
763-
for item in snapshots
764-
),
765-
1024,
766-
),
779+
"idf_internal_largest_block_min_bytes": largest_block_floor,
767780
"idf_internal_minimum_free_min_bytes": floor_quantum(
768781
min(item["idf_internal_minimum_free_bytes"] for item in snapshots),
769782
1024,

‎tests/firmware_tests/host/test_oi1_profile_bench.py‎

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,6 +1034,34 @@ def test_repeatability_allowances_do_not_change_static_or_heap_gates(self):
10341034
},
10351035
)
10361036

1037+
repeat_observation = dict(observation)
1038+
repeat_observation.update(
1039+
{
1040+
"reset_to_service_advertisement_ms": [1170] * 9 + [2378],
1041+
"put_committed_goodput_bytes_per_second": [7208] * 5,
1042+
"get_verified_goodput_bytes_per_second": [13238] * 5,
1043+
}
1044+
)
1045+
repeat_derived = bench.evaluate_thresholds(
1046+
{
1047+
"application_image_bytes": 1699936,
1048+
"factory_partition_bytes": 2031616,
1049+
"application_headroom_bytes": 331680,
1050+
},
1051+
repeat_observation,
1052+
thresholds,
1053+
)
1054+
self.assertEqual(
1055+
repeat_derived["reset_to_service_advertisement_max_ms"],
1056+
3000,
1057+
)
1058+
self.assertEqual(
1059+
repeat_derived[
1060+
"put_committed_goodput_min_bytes_per_second"
1061+
],
1062+
6600,
1063+
)
1064+
10371065
def test_waveshare_largest_block_floor_is_fixed_one_page_below_baseline(self):
10381066
# ADR-0039: the fixed Waveshare exception never derives from samples,
10391067
# while the identically shaped generic S3 keeps floor-min-1024-v1.
@@ -1131,34 +1159,6 @@ def observation_with_block(block):
11311159
str(caught.exception),
11321160
)
11331161

1134-
repeat_observation = dict(observation)
1135-
repeat_observation.update(
1136-
{
1137-
"reset_to_service_advertisement_ms": [1170] * 9 + [2378],
1138-
"put_committed_goodput_bytes_per_second": [7208] * 5,
1139-
"get_verified_goodput_bytes_per_second": [13238] * 5,
1140-
}
1141-
)
1142-
repeat_derived = bench.evaluate_thresholds(
1143-
{
1144-
"application_image_bytes": 1699936,
1145-
"factory_partition_bytes": 2031616,
1146-
"application_headroom_bytes": 331680,
1147-
},
1148-
repeat_observation,
1149-
thresholds,
1150-
)
1151-
self.assertEqual(
1152-
repeat_derived["reset_to_service_advertisement_max_ms"],
1153-
3000,
1154-
)
1155-
self.assertEqual(
1156-
repeat_derived[
1157-
"put_committed_goodput_min_bytes_per_second"
1158-
],
1159-
6600,
1160-
)
1161-
11621162
def test_fixed_reset_product_slo_accepts_boundary_and_rejects_above_it(self):
11631163
heaps = [_heap() for _ in range(16)]
11641164
observation = {

0 commit comments

Comments
 (0)