Skip to content

Commit 7705ba2

Browse files
yyodsclaude
andcommitted
[docs] admit Waveshare single-page largest-block transient
Three consecutive replacement-era Waveshare OI-1 verify runs failed only on one transient idf_internal_largest_block_bytes sample of 98304 against the frozen 102400 floor (exactly one 4096-byte page, free heap unchanged, absent on the generic S3 image). ADR-0039 defines a second-replacement source era at boundary 7d85328 whose derivation changes exactly the heap-floor identifier to floor-min-1024-waveshare-block-98304-v2, fixing only the Waveshare largest-block floor at 98304, and permits byte-identity-conditioned carry-forward of already-passed physical evidence. No firmware bytes change. 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 7d85328 commit 7705ba2

9 files changed

Lines changed: 212 additions & 25 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ are released independently from this monorepo.
55

66
## Unreleased
77

8+
- Admitted the characterized Waveshare single-page largest-block transient
9+
with a fixed `98304` heap floor in a second-replacement qualification era
10+
(ADR-0039), with byte-identity-conditioned carry-forward of already-passed
11+
physical evidence; no firmware bytes changed.
812
- Established `PyBLE-dev/PyBLE` as the canonical public monorepo.
913
- Added public contributor, security, architecture, protocol, and validation
1014
documentation.
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# ADR-0039 — Admit the Waveshare single-page largest-block transient
2+
3+
- Status: **Accepted**
4+
- Date: 2026-08-21
5+
- Supersedes: ADR-0037 decision item 8 for the Waveshare
6+
`idf_internal_largest_block_min_bytes` floor only, and ADR-0038's
7+
fresh-observation requirement only under the exact byte-identity
8+
conditions of decision item 6
9+
- Preserves: every other ADR-0037/ADR-0038 rule, the immutable `a8be631…`
10+
baseline, and all historical derivation revisions
11+
12+
## Context
13+
14+
Three consecutive replacement-era Waveshare OI-1 verify runs on candidate
15+
source `7d853289815751c7381c9fd0b9a9a4409bdb6879` failed at exactly one gate:
16+
one heap snapshot per run recorded `idf_internal_largest_block_bytes` of
17+
`98304` against the frozen floor of `102400`. Every other value passed in the
18+
same runs — reset samples 749–1,531 ms, all ten PUT/GET samples well above
19+
6,600 bytes/s, 20/20 reliability with zero integrity failures, settled 2M
20+
PHY/DLE 251 transport, and the physical power seam.
21+
22+
The dip is characterized, not anomalous:
23+
24+
- It is exactly one 4,096-byte page: `98304 = 102400 - 4096`.
25+
- `idf_internal_free_bytes` and `idf_internal_minimum_free_bytes` are
26+
unchanged at the dip instant — transient fragmentation of the largest
27+
block, not memory consumption.
28+
- It appears once per run at a timing-dependent post-HELLO snapshot
29+
(reset samples 4, 6, and 1 across the three runs).
30+
- The generic `esp32-s3-n16r8` profile — identical chip, identical floor —
31+
recorded 41 snapshots across three runs the same day with zero dips.
32+
The behavior is specific to the Waveshare image.
33+
- The retained `a8be631…` baseline capture recorded no dip, so
34+
`floor-min-1024-v1` froze the floor at the un-fragmented value.
35+
36+
`floor-min-1024-v1` cannot express a floor below its baseline minimum, so
37+
admitting the transient requires a new heap-floor derivation identifier and a
38+
new source era, exactly as ADR-0037 introduced fixed reset/goodput
39+
identifiers for the V4 era.
40+
41+
This decision does not fit a gate to a failure. The floor remains a
42+
fragmentation regression guard, not a performance promise; the admitted value
43+
is the baseline-derived floor minus exactly one page — the minimal relaxation
44+
that admits a single-page transient — and the three failed runs confirm,
45+
rather than define, that arithmetic. The sanctioned ADR-0037 item 9 path is
46+
followed in full: spec-first ADR, then fresh qualification of a new candidate.
47+
48+
## Decision
49+
50+
For the second-replacement v0.6.0 source era:
51+
52+
1. The era boundary is `7d853289815751c7381c9fd0b9a9a4409bdb6879` — the
53+
superseded first-replacement candidate source. Strict descendants use
54+
`QUALIFICATION_DERIVATION_V5`; the boundary and its ancestors keep their
55+
historical derivations (V4 era per ADR-0038, version-routed before it).
56+
Unknown or unrelated ancestry still fails closed.
57+
2. `QUALIFICATION_DERIVATION_V5` changes exactly one identifier relative to
58+
V4: `heap_floor` becomes `floor-min-1024-waveshare-block-98304-v2`.
59+
`application_image`, `application_headroom`, `boot_ceiling`, and
60+
`goodput_floor` keep their exact V4 identifiers and arithmetic.
61+
3. Under `floor-min-1024-waveshare-block-98304-v2`, every heap floor for
62+
every profile keeps `floor-min-1024-v1` arithmetic from the immutable
63+
`a8be631…` baseline, with exactly one exception:
64+
`idf_internal_largest_block_min_bytes` for
65+
`waveshare-esp32-s3-lcd-147b` is the fixed value `98304`.
66+
4. The active policy for the V5 era is therefore the exact V4 policy with
67+
the `heap_floor` derivation identifier replaced and exactly one numeric
68+
field changed: the Waveshare `idf_internal_largest_block_min_bytes`
69+
`102400 -> 98304`. All other 10 largest-block/heap values, every static
70+
image/headroom bound, every reset ceiling, and every goodput floor are
71+
byte-for-byte unchanged.
72+
5. The floor value `98304` is a fixed product decision. A verify run that
73+
records any heap sample below it fails, and per ADR-0037 item 9 its
74+
values MUST NOT be used to relax the policy again; a further change
75+
requires another spec-first ADR and fresh qualification.
76+
6. Evidence captured under the superseded first-replacement candidate MAY
77+
bind into the successor candidate's completion, per profile, only if
78+
every one of these mechanical conditions holds:
79+
a. the successor candidate's install artifacts for that profile are
80+
byte-identical (equal SHA-256 and size) to the artifacts the evidence
81+
was captured against;
82+
b. every policy threshold applicable to that profile is value-identical
83+
between the superseded and successor policies;
84+
c. the evidence passed its frozen evaluator when captured, unmodified.
85+
The eligible evidence classes are OI-1 verify observations, the C3
86+
post-OI NVS capture (its receipt re-bound to the successor
87+
candidate digest from re-derived bytes), and provisioning
88+
install/readback evidence. If any condition fails for a profile, the
89+
clause is void for that profile and ADR-0038's fresh-observation rule
90+
applies unchanged. `waveshare-esp32-s3-lcd-147b` is excluded by
91+
construction (condition b fails) and always requires a fresh
92+
observation. App HIL matrix rows are NOT eligible and are re-run
93+
against the successor candidate.
94+
7. The byte-identity and threshold-identity proofs for every carried
95+
profile must be recorded alongside the completion evidence (exact
96+
SHA-256 pairs and the compared threshold rows).
97+
8. The tag/candidate replacement procedure of ADR-0038 applies unchanged:
98+
retain the superseded tag object and candidate metadata as superseded
99+
evidence, never publish them, and require fresh candidate creation,
100+
validation, and completion under the successor policy.
101+
102+
## Consequences
103+
104+
- The Waveshare profile is qualifiable on truthful evidence: its
105+
characterized single-page transient no longer fails an otherwise fully
106+
passing run, while a genuine fragmentation regression below `98304`
107+
still fails closed.
108+
- One more derivation era exists. Historical eras remain byte-reproducible;
109+
the V4 era's policy and arithmetic are untouched for ancestry at or
110+
below the `7d85328…` boundary.
111+
- If the successor build proves byte-identical per profile, already-passed
112+
physical observations remain valid evidence instead of being discarded
113+
for a policy-only source change — bounded strictly by the mechanical
114+
conditions of decision item 6. If it does not, every profile requalifies
115+
fresh, so the clause can only reduce redundant bench work, never admit
116+
unproven hardware behavior.
117+
- The firmware bytes are not changed by this decision. If a later change
118+
eliminates the transient allocation, the floor may be restored to the
119+
baseline-derived value by a further spec-first ADR.

‎docs/decisions/README.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,6 @@ Each ADR captures one significant decision: its context, the choice, and the con
4242
| [0036](0036-read-generic-s3-link-facts-through-run.md) | Read bounded, session-bound generic S3 qualification link facts through existing PBLE/1 RUN while retaining serial only as the reset adapter | Accepted |
4343
| [0037](0037-fixed-profile-reset-and-transfer-product-slos.md) | Use fixed ESP/Pico reset ceilings and one product-wide 64 KiB PUT/GET floor for replacement-v0.6.0 qualification | Accepted |
4444
| [0038](0038-replace-unpublished-v060-candidate-in-place.md) | Replace the unpublished local v0.6.0 candidate in place while routing qualification by source era rather than SemVer alone | Accepted |
45+
| [0039](0039-admit-waveshare-single-page-largest-block-transient.md) | Admit the characterized Waveshare single-page largest-block transient with a fixed 98304 floor in a V5 source era, with byte-identity-conditioned evidence carry-forward | Accepted |
4546

46-
Decided 2026-06-30 (0001–0005); 0006 accepted 2026-07-01; 0007, 0008, 0009 and 0010 accepted 2026-07-02; 0011 accepted 2026-07-03; 0012 accepted 2026-07-25; 0013 accepted 2026-07-27; 0014, 0015, 0016, and 0017 accepted 2026-07-28; 0018–0022 accepted 2026-07-29; 0023 and 0024 accepted 2026-08-01; 0025 and 0026 accepted 2026-08-02; 0027–0029 accepted 2026-08-03; 0030 accepted 2026-08-11; 0031–0033 accepted 2026-08-12; 0034 accepted 2026-08-13; 0035 and 0036 accepted 2026-08-16; 0037 and 0038 accepted 2026-08-20.
47+
Decided 2026-06-30 (0001–0005); 0006 accepted 2026-07-01; 0007, 0008, 0009 and 0010 accepted 2026-07-02; 0011 accepted 2026-07-03; 0012 accepted 2026-07-25; 0013 accepted 2026-07-27; 0014, 0015, 0016, and 0017 accepted 2026-07-28; 0018–0022 accepted 2026-07-29; 0023 and 0024 accepted 2026-08-01; 0025 and 0026 accepted 2026-08-02; 0027–0029 accepted 2026-08-03; 0030 accepted 2026-08-11; 0031–0033 accepted 2026-08-12; 0034 accepted 2026-08-13; 0035 and 0036 accepted 2026-08-16; 0037 and 0038 accepted 2026-08-20; 0039 accepted 2026-08-21.

‎docs/specifications/firmware.md‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,12 @@ remain required for all five profiles before activation.
111111
ADR-0038 replaces the unpublished local candidate tagged at `719b211…` in
112112
place: no origin tag, GitHub Release, or canonical v0.6.0 website release ever
113113
existed, so the first public version remains `0.6.0`. The predecessor source
114-
era ends at inclusive commit `5620f2f…`; only strict descendants use the
115-
ADR-0037 fixed-SLO contract. All old candidate bytes and HIL are invalid for
114+
era ends at inclusive commit `5620f2f…`; strict descendants use the
115+
ADR-0037 fixed-SLO contract, and strict descendants of the second boundary
116+
`7d85328…` use the ADR-0039 second-replacement contract, which additionally
117+
fixes the Waveshare largest-block heap floor at `98304` under
118+
`floor-min-1024-waveshare-block-98304-v2` and permits byte-identity-conditioned
119+
carry-forward of already-passed physical evidence (ADR-0039 item 6). All old candidate bytes and HIL are invalid for
116120
the replacement, while superseded metadata and immutable baseline evidence
117121
remain retained. After source/docs/RED/GREEN, build, reproducibility, license,
118122
source, and audit gates pass, the local tag may be replaced so it peels to

‎docs/specifications/firmware/TDD.md‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1711,7 +1711,11 @@ Profiles or successful samples from different baselines MUST NOT be mixed.
17111711
Historical pre-`0.5.0` and v0.5.x policies retain their exact V1 and V3/V2
17121712
derivation identifiers and arithmetic. The predecessor v0.6.0 contract also
17131713
retains V3/V2 when its bound source is at or before
1714-
`5620f2fdc672b440548119e3431cfa4f4ed3f5a3`. The replacement policy must be
1714+
`5620f2fdc672b440548119e3431cfa4f4ed3f5a3`; the first-replacement contract
1715+
applies to strict descendants at or before
1716+
`7d853289815751c7381c9fd0b9a9a4409bdb6879`, and strict descendants of that
1717+
second boundary use the ADR-0039 second-replacement contract. The
1718+
replacement policy must be
17151719
rederived from the retained baseline under a strict-descendant candidate source.
17161720
The helper proves that bound policy/candidate ancestry; SemVer, policy schema,
17171721
validator checkout, operator input, and the retained baseline's own
@@ -1740,6 +1744,15 @@ integrity, reliability, disconnect, and settled-link facts remain exact. A
17401744
failed result cannot rederive or widen either SLO. Static image/headroom, heap,
17411745
and all historical arithmetic remain unchanged.
17421746

1747+
The second-replacement era keeps every value above and changes exactly one
1748+
heap fact (ADR-0039): its heap identifier is
1749+
`floor-min-1024-waveshare-block-98304-v2`, under which every heap floor keeps
1750+
`floor-min-1024-v1` arithmetic except the fixed Waveshare
1751+
`idf_internal_largest_block_min_bytes` of `98304` (the baseline-derived
1752+
`102400` minus one 4,096-byte page, admitting that image's characterized
1753+
single-page fragmentation transient). A sample below `98304` fails and MUST
1754+
NOT relax the floor again.
1755+
17431756
A schema-3 policy has exactly the five ordered threshold-bearing rows in
17441757
specs.md §5.3.5. Four ESP rows use the existing application/partition,
17451758
five-field heap, and NimBLE link facts. Pico uses raw-image limit/headroom,

‎docs/specifications/firmware/browser-flashing.md‎

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1817,10 +1817,13 @@ releases. The retained `0.4.2` source era instead requires policy
18171817
schema 1, its historical exact two-profile order, `ceil-max-10-v1`, and
18181818
`floor-min-100-v1` values; validation selects the complete policy contract by
18191819
firmware source era and never silently reinterprets published evidence.
1820-
The two unpublished v0.6.0 source eras both retain policy schema 3 and the
1820+
The three unpublished v0.6.0 source eras all retain policy schema 3 and the
18211821
five-profile order, but they do not share a derivation object: §9.5 selects
1822-
the predecessor V3/V2 or replacement V4/V3 identifiers from bound source
1823-
ancestry. Version, schema, or operator input alone cannot select it.
1822+
the predecessor V3/V2 identifiers, the first-replacement V4/V3 identifiers,
1823+
or the second-replacement (ADR-0039) identifiers — which change exactly the
1824+
heap-floor identifier to `floor-min-1024-waveshare-block-98304-v2` — from
1825+
bound source ancestry. Version, schema, or operator input alone cannot
1826+
select it.
18241827

18251828
`baseline_evidence.path` MUST match
18261829
`docs/validation/firmware/oi1/<40-lowercase-hex-source-commit>.json`;
@@ -2263,16 +2266,22 @@ C3-G0…C3-G6; Pico binds GP0, GP1, and complete GP2. A missing private result,
22632266
non-null input summary, failed sub-gate, changed input, or identity/hash
22642267
mismatch leaves no public output.
22652268

2266-
The V5 envelope is unchanged across the two unpublished v0.6.0 source eras,
2269+
The V5 envelope is unchanged across the unpublished v0.6.0 source eras,
22672270
but its policy derivation is source-bound. A candidate source at or before
22682271
`5620f2fdc672b440548119e3431cfa4f4ed3f5a3` retains
2269-
`fixed-product-slo-3000-v3` and `floor-95pct-min-100-v2`. Only a strict
2270-
descendant uses
2272+
`fixed-product-slo-3000-v3` and `floor-95pct-min-100-v2`. A strict
2273+
descendant of that boundary at or before
2274+
`7d853289815751c7381c9fd0b9a9a4409bdb6879` uses
22712275
`fixed-profile-product-slo-esp3000-pico7000-v4` and
2272-
`fixed-product-slo-64k-under-10s-6600-v3`. Its policy has reset ceilings
2273-
`3000` for the four ESP rows and `7000` for Pico, and PUT/GET floors `6600`
2274-
for all five rows. The bound replacement policy/candidate source must be a
2275-
strict boundary descendant. Missing, equal-to-boundary, unrelated, or
2276+
`fixed-product-slo-64k-under-10s-6600-v3` with `floor-min-1024-v1` heap
2277+
floors. A strict descendant of `7d853289…` (the second-replacement era,
2278+
ADR-0039) additionally replaces exactly the heap-floor identifier with
2279+
`floor-min-1024-waveshare-block-98304-v2`, whose single numeric effect is
2280+
the fixed Waveshare `idf_internal_largest_block_min_bytes` of `98304`.
2281+
Every replacement policy has reset ceilings `3000` for the four ESP rows and
2282+
`7000` for Pico, and PUT/GET floors `6600` for all five rows. The bound
2283+
replacement policy/candidate source must be a strict descendant of the
2284+
applicable boundary. Missing, equal-to-boundary, unrelated, or
22762285
ancestry-unprovable replacement identity fails closed. The immutable
22772286
`a8be631…` baseline remains the exact input: its static/heap thresholds are
22782287
rederived under the bound replacement policy/candidate era, while its

‎docs/specifications/firmware/specs.md‎

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1353,9 +1353,11 @@ The successor `firmware/qualification/oi1-gates.json` has exactly:
13531353
- the exact `profile_order` above;
13541354
- `workload`, retaining every §5.3.2 constant except that the PUT window is
13551355
profile-specific;
1356-
- `derivation`, retaining the exact application/image, headroom, and
1357-
`floor-min-1024-v1` algorithms while selecting reset/goodput identifiers by
1358-
candidate source ancestry as defined below;
1356+
- `derivation`, retaining the exact application/image and headroom
1357+
algorithms while selecting the heap-floor and reset/goodput identifiers by
1358+
candidate source ancestry as defined below (`floor-min-1024-v1`, or its
1359+
ADR-0039 successor `floor-min-1024-waveshare-block-98304-v2` whose single
1360+
exception is the fixed Waveshare largest-block floor);
13591361
- `baseline_evidence`, binding the canonical schema-2 baseline path and
13601362
SHA-256; and
13611363
- five `profiles` entries in order, each with exactly `profile_id`, `target`,
@@ -1408,8 +1410,10 @@ formula, and reject an operator-selectable or unequal numeric replacement.
14081410
For a source at or before
14091411
`5620f2fdc672b440548119e3431cfa4f4ed3f5a3`, the unpublished v0.6.0 replay
14101412
contract retains `fixed-product-slo-3000-v3` and
1411-
`floor-95pct-min-100-v2`. For a strict descendant of that boundary, the
1412-
replacement v0.6.0 policy derivation object instead contains exactly:
1413+
`floor-95pct-min-100-v2`. For a strict descendant of that boundary at or
1414+
before the second boundary
1415+
`7d853289815751c7381c9fd0b9a9a4409bdb6879`, the first-replacement v0.6.0
1416+
policy derivation object contains exactly:
14131417

14141418
```json
14151419
{
@@ -1421,11 +1425,25 @@ replacement v0.6.0 policy derivation object instead contains exactly:
14211425
}
14221426
```
14231427

1428+
For a strict descendant of `7d853289815751c7381c9fd0b9a9a4409bdb6879`, the
1429+
second-replacement (ADR-0039) policy derivation object instead changes
1430+
exactly the `heap_floor` identifier:
1431+
1432+
```json
1433+
{
1434+
"application_image": "exact-byte-identical-two-root-v1",
1435+
"application_headroom": "factory-minus-application-v1",
1436+
"heap_floor": "floor-min-1024-waveshare-block-98304-v2",
1437+
"boot_ceiling": "fixed-profile-product-slo-esp3000-pico7000-v4",
1438+
"goodput_floor": "fixed-product-slo-64k-under-10s-6600-v3"
1439+
}
1440+
```
1441+
14241442
The bound policy/candidate source commit, not its `0.6.0` SemVer, the
14251443
validator checkout, or the baseline's `source_commit`, selects between these
14261444
contracts. A replacement policy/candidate source MUST be a strict descendant
1427-
of the boundary. Missing, unrelated, equal-boundary, or ancestry-unprovable
1428-
identity fails closed. The immutable retained baseline
1445+
of the applicable boundary. Missing, unrelated, equal-boundary, or
1446+
ancestry-unprovable identity fails closed. The immutable retained baseline
14291447
`docs/validation/firmware/oi1/a8be631df46590166307aa41afaea30b39e29230.json`
14301448
and its exact SHA-256
14311449
`8a7dbf328ba8d70f5161582b56d1566821f20b7a259ff29dc7d6fe6bb75a6044`
@@ -1462,6 +1480,15 @@ link facts, retransmit/rewind accounting, reliability totals, disconnect
14621480
counts, and every target-specific transport fact remain exact. Neither a
14631481
failed run nor a favorable baseline may rederive either fixed SLO.
14641482

1483+
Under the second-replacement era, every heap floor keeps `floor-min-1024-v1`
1484+
arithmetic from the immutable baseline with exactly one fixed exception
1485+
(ADR-0039): `idf_internal_largest_block_min_bytes` for
1486+
`waveshare-esp32-s3-lcd-147b` is exactly `98304` — the baseline-derived
1487+
`102400` minus one 4,096-byte page, admitting that image's characterized
1488+
single-page transient while every other heap value, static bound, reset
1489+
ceiling, and goodput floor stays byte-for-byte at its first-replacement
1490+
value. A sample below `98304` fails and MUST NOT relax the floor again.
1491+
14651492
Every V5 profile record additionally binds both real-app results. `app_hil`
14661493
has exactly `ipad` and `android`; each entry has exactly non-empty
14671494
`app_version`, `app_build`, and `os_major`, plus `status: "passed"` in a

0 commit comments

Comments
 (0)