Skip to content

Commit befa792

Browse files
committed
[docs] freeze v0.6.0 replacement performance SLOs
Signed-off-by: Viwat Vchirawongkwin <viwat.v@chula.ac.th>
1 parent 5620f2f commit befa792

14 files changed

Lines changed: 616 additions & 185 deletions

‎CHANGELOG.md‎

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

2525
### Firmware 0.6.0 source integration
2626

27+
- Abandoned the unpublished local `firmware-v0.6.0` candidate at `719b211…`
28+
after the C3 stateless-PHY/post-OI receipt and fixed performance-contract
29+
amendments. Version 0.6.0 and its atomic five-profile scope are retained for
30+
a source-era-routed replacement; all candidate/HIL gates restart before any
31+
tag is pushed or release is published.
2732
- Added the `rpi-pico2-w` portable frozen-Python agent port, its isolated RP2
2833
build plane, and pinned Arm GNU toolchain input alongside the four existing
2934
ESP build variants.
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# ADR-0037 — Use fixed profile reset and product-wide transfer SLOs
2+
3+
- Status: **Accepted**
4+
- Date: 2026-08-20
5+
- Supersedes: ADR-0026 decision items 1–2 for replacement-v0.6.0
6+
reset qualification, and ADR-0025 decision items 4 and 6 for
7+
replacement-v0.6.0 goodput qualification
8+
- Preserves: historical derivation revisions and immutable baseline evidence
9+
10+
## Context
11+
12+
The five-profile v0.6.0 baseline retained at
13+
`docs/validation/firmware/oi1/a8be631df46590166307aa41afaea30b39e29230.json`
14+
is valid engineering evidence, but it exposed two product-contract problems.
15+
First, Pico 2 W has a materially different reset path from the four ESP
16+
profiles. Its pinned upstream BTstack controller initialization normally needs
17+
a 5–6 second allowance; applying the ESP 3-second discovery SLO to that path is
18+
not a truthful cross-port requirement. Second, deriving each transfer floor as
19+
95% of one profile's minimum baseline sample turns a product-usability bound
20+
into five candidate-fitted regression thresholds.
21+
22+
The retained baseline diagnoses feasibility without selecting either new
23+
value. Its ESP reset maxima are 1,828, 1,187, 1,594, and 1,167 ms, while the
24+
Pico maximum is 3,378 ms. Its minimum PUT values are 6,870, 13,740, 13,656,
25+
16,180, and 12,854 bytes/s; its minimum GET values are 13,240, 20,787, 25,654,
26+
28,716, and 7,181 bytes/s. The product decision must remain independent of
27+
those extrema so a failed qualification run can never fit or relax its own
28+
gate.
29+
30+
## Decision
31+
32+
For the replacement v0.6.0 source era defined by ADR-0038:
33+
34+
1. Reset-to-service-advertisement remains the exact same end-to-end metric:
35+
controlled reset/power release to the first fresh host scanner callback
36+
containing the exact PyBLE service UUID.
37+
2. `reset_to_service_advertisement_max_ms` is exactly `3000` for
38+
`esp32-4mb`, `esp32-s3-n16r8`, `waveshare-esp32-s3-lcd-147b`, and
39+
`esp32-c3-4mb`, and exactly `7000` for `rpi-pico2-w`. The derivation
40+
identifier is
41+
`fixed-profile-product-slo-esp3000-pico7000-v4`.
42+
3. Pico's 7-second bound is independently budgeted from the pinned upstream
43+
BTstack's normal 5–6 second controller-initialization allowance plus one
44+
fixed second for service-advertisement acquisition and host callback
45+
delivery. It is not derived from the 3,378 ms observation and MUST NOT be
46+
widened again after a failure.
47+
4. Every profile still records exactly 10 reset samples. All 10 must pass its
48+
profile ceiling; no sample is trimmed, replaced, retried, or selectively
49+
rerun. The scanner-before-reset ordering, exact-service match, HELLO after
50+
every sample, 15,000 ms hard health timeout, and separate physical
51+
power-cycle advertisement check remain exact.
52+
5. Both committed PUT and verified GET use the fixed product-wide floor
53+
`6600` bytes/s on all five profiles. Its derivation identifier is
54+
`fixed-product-slo-64k-under-10s-6600-v3`.
55+
6. The arithmetic is exactly
56+
`ceil_100(65536 bytes / 10 seconds) = ceil_100(6553.6) = 6600` bytes/s.
57+
Because reported goodput is the integer
58+
`floor(65536 * 10^9 / duration_ns)`, the largest passing duration is
59+
`9,929,696,969 ns` (approximately `9.9297 s`), not a rounded 10 seconds.
60+
7. Exactly five PUT and five GET samples remain mandatory per profile. Every
61+
sample must meet 6,600 bytes/s with no retry or replacement. The exact
62+
65,536-byte unique-byte counts, duration arithmetic, byte/size/CRC and
63+
offset integrity, required ATT MTU/window/chunk, settled link facts,
64+
retransmit/rewind accounting, reliability totals, zero unexpected
65+
disconnects, and physical check receive no allowance.
66+
8. Reset and transfer baseline samples remain immutable diagnostic evidence;
67+
neither contributes arithmetic to these product SLOs. Static image and
68+
headroom thresholds remain exact, and heap floors retain
69+
`floor-min-1024-v1` baseline derivation. The retained `a8be631…` baseline
70+
is rederived under the bound policy/candidate source era; its own
71+
`source_commit` never selects the derivation revision.
72+
9. A candidate or verify run that crosses either fixed SLO fails. Its values
73+
MUST NOT be used to rederive, fit, trim, or relax the policy; changing a
74+
product SLO requires another spec-first ADR and fresh qualification.
75+
10. Historical source eras retain their exact derivation objects and
76+
arithmetic. In particular, the earlier unpublished v0.6.0 source era
77+
remains reproducible with `fixed-product-slo-3000-v3` and
78+
`floor-95pct-min-100-v2`; it does not silently inherit this decision.
79+
80+
## Consequences
81+
82+
- Reset qualification reflects the real controller startup class of each port
83+
while keeping one exact host-visible endpoint and strict sample discipline.
84+
- PUT and GET now express the minimum usable 64 KiB product transaction rather
85+
than the performance of the baseline used to exercise a particular build.
86+
- The active five-profile policy must replace both derivation identifiers and
87+
atomically set four ESP reset ceilings to 3,000, Pico's to 7,000, and all ten
88+
transfer floors to 6,600 before a replacement candidate is built.
89+
- The immutable `a8be631…` baseline is retained unchanged. It supplies the
90+
unchanged static/heap inputs and diagnostic performance samples, but is not
91+
release approval and does not route its own derivation.
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# ADR-0038 — Replace the unpublished v0.6.0 candidate in place
2+
3+
- Status: **Accepted**
4+
- Date: 2026-08-20
5+
- Builds on: ADR-0033 and ADR-0037
6+
- Last superseded-source commit:
7+
`5620f2fdc672b440548119e3431cfa4f4ed3f5a3`
8+
9+
## Context
10+
11+
The local annotated tag `firmware-v0.6.0` has tag-object identity
12+
`d86eb6473a8d0269479061f03cd4a7b48bc8dabe` and peels to candidate source
13+
`719b211345028e49aee9df9b11c4b5fd110913de`. It was created before the
14+
stateless C3 PHY-calibration and post-OI NVS-receipt increments and before the
15+
fixed SLO decision in ADR-0037.
16+
17+
A pre-publication audit on 2026-08-20 found no
18+
`refs/tags/firmware-v0.6.0` at `origin`, no GitHub Release for that tag, and a
19+
not-found response at the canonical
20+
`https://pyble.dev/firmware/v0.6.0/release.json` path. The local tag and its
21+
candidate were therefore never a public release identity. Advancing to 0.6.1
22+
would imply that users could possess a published 0.6.0 contract when no such
23+
release exists, while silently applying new derivations to every source whose
24+
embedded SemVer is 0.6.0 would destroy historical reproducibility.
25+
26+
## Decision
27+
28+
1. Keep the replacement release version and agent version exactly `0.6.0` and
29+
keep the atomic ADR-0033 five-profile order. This decision changes neither
30+
release schema 4, policy schema 3, baseline schema 2, nor HIL schema 5.
31+
2. The old local `firmware-v0.6.0` tag and candidate at `719b211…` are
32+
abandoned and unpublished. They MUST NOT be pushed, mirrored, activated,
33+
described as released, or used as the replacement release provenance.
34+
3. The superseded unpublished source era ends at and includes
35+
`5620f2fdc672b440548119e3431cfa4f4ed3f5a3`. The replacement v0.6.0 source
36+
era contains only strict descendants of that commit. Release tooling MUST
37+
select the derivation contract from the bound policy/candidate source
38+
commit's ancestry, not from SemVer alone, the baseline's `source_commit`,
39+
or the validator checkout.
40+
4. A v0.6.0 source at or before that boundary retains the historical
41+
derivation object with `fixed-product-slo-3000-v3` and
42+
`floor-95pct-min-100-v2`. A replacement-era v0.6.0 source requires
43+
ADR-0037's `fixed-profile-product-slo-esp3000-pico7000-v4` and
44+
`fixed-product-slo-64k-under-10s-6600-v3`. Unknown, unrelated, or
45+
ancestry-unprovable 0.6.0 source identity fails closed.
46+
5. Every old candidate bundle, artifact, protected-deployment result, and HIL
47+
observation bound to `719b211…` or any other source through the boundary is
48+
invalid for replacement qualification. No old pass, candidate digest,
49+
artifact digest, or physical-result lineage may be relabelled or carried
50+
into the replacement.
51+
6. Retained metadata and engineering evidence are not deleted or rewritten.
52+
The old tag-object and peeled-source identities, candidate digests where
53+
retained, prior derivation revision, and superseded status remain
54+
auditable. The immutable
55+
`docs/validation/firmware/oi1/a8be631df46590166307aa41afaea30b39e29230.json`
56+
baseline remains byte-for-byte evidence. Its static/heap inputs are
57+
rederived under the replacement policy/candidate source era and its
58+
reset/goodput samples remain diagnostic; the baseline source identity never
59+
routes the derivation and the baseline does not qualify either candidate.
60+
7. Source/docs/RED/GREEN work first updates the checked-in policy to ADR-0037.
61+
The replacement source then passes its clean build, two-root
62+
reproducibility, license, source, and audit gates before any tag or audited
63+
candidate is created.
64+
8. Candidate provenance requires an annotated `firmware-v0.6.0` tag which
65+
peels to candidate `HEAD`. After the pre-candidate gates in item 7 pass, the
66+
old local unpublished tag may be replaced with that audited source identity
67+
and the audited candidate may be created. Superseded tag-object/source and
68+
candidate metadata MUST already be retained. The tag MUST NOT be pushed,
69+
mirrored, published, or activated at this stage.
70+
9. The tagged candidate then requires fresh protected deployment, verify-mode
71+
HIL on all five profiles, C3/Pico private gates, both-platform app HIL,
72+
install/recovery, finalization, and public validation. Only after those
73+
gates pass may the replacement tag and exact bytes be considered for origin
74+
push, canonical website publication, optional mirror, or activation.
75+
76+
## Consequences
77+
78+
- Users see the intended first public 0.6.0 instead of an artificial 0.6.1
79+
created solely to replace private bytes.
80+
- Two source eras can share SemVer without sharing qualification arithmetic;
81+
retained earlier evidence remains mechanically reproducible.
82+
- The existing local tag cannot authorize shortcuts. The replacement is
83+
release-blocking until fresh exact-byte gates pass on all five profiles.
84+
- No tag, candidate, build, hardware run, or publication action is performed
85+
by this decision itself.

‎docs/decisions/README.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,7 @@ Each ADR captures one significant decision: its context, the choice, and the con
4040
| [0034](0034-read-waveshare-link-facts-through-run.md) | Read bounded, session-bound Waveshare qualification link facts through existing PBLE/1 RUN without a UART dependency or new wire surface | Accepted |
4141
| [0035](0035-read-c3-link-facts-through-run.md) | Read bounded, session-bound C3 qualification link facts through existing PBLE/1 RUN while retaining WCH only as the reset adapter | Accepted |
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 |
43+
| [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 |
44+
| [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 |
4345

44-
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.
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.

‎docs/specifications/firmware.md‎

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# PyBLE — Agent Firmware
22

3-
Status: **DRAFT** · Last updated: 2026-08-12
3+
Status: **DRAFT** · Last updated: 2026-08-20
44

55
The PyBLE agent is small board-side firmware that turns a compatible
66
MicroPython target into a PyBLE-speaking board: it advertises the BLE service,
@@ -108,6 +108,18 @@ two-clean-build reproducibility, license audit, resource policy, exact-byte
108108
HIL, both-platform app HIL, install/recovery, and copy-on-write finalization
109109
remain required for all five profiles before activation.
110110

111+
ADR-0038 replaces the unpublished local candidate tagged at `719b211…` in
112+
place: no origin tag, GitHub Release, or canonical v0.6.0 website release ever
113+
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
116+
the replacement, while superseded metadata and immutable baseline evidence
117+
remain retained. After source/docs/RED/GREEN, build, reproducibility, license,
118+
source, and audit gates pass, the local tag may be replaced so it peels to
119+
candidate `HEAD`; audited candidate creation then precedes fresh HIL and
120+
finalization. Push, publication, and activation remain forbidden until those
121+
later gates pass.
122+
111123
The ESP32-C3-MINI-1-N4 v0.4/4 MiB/no-PSRAM reference and its still-pending
112124
C3-G0…C3-G6 gates remain frozen in
113125
[firmware/ports/esp32-c3-4mb.md](firmware/ports/esp32-c3-4mb.md). C3 enters
@@ -452,27 +464,29 @@ internal-heap gate. Total application-image size is the normative flash
452464
quantity; an “agent-only overhead” requires a matched no-agent control build
453465
and is supplemental only.
454466

455-
Thresholds are never fitted to a candidate run: heap floors round the minimum
456-
of the frozen sample set outward to 1 KiB; reset-to-advertisement uses the
457-
fixed 3,000 ms end-to-end product SLO; and goodput floors apply the exact
458-
integer 5% host/radio repeatability allowance before rounding outward to 100
459-
bytes/s. The reset metric ends at a host scanner callback, so it is a
460-
user-visible discovery SLO rather than a tight firmware boot-regression
461-
statistic. Static image/headroom quantities remain exact, and
462-
integrity/reliability assertions receive no allowance. The engineering
463-
baseline derives the remaining committed policy values; only hash-locked
464-
final-candidate HIL qualifies a release. The v0.6.0 policy derives five
465-
independent rows; C3 and Pico remain pending rather than deferred or inferred
466-
from another target.
467-
468-
The sole permitted cross-commit physical-fact lineage is specified by
469-
[browser-flashing.md](firmware/browser-flashing.md): it may reuse only the
467+
Thresholds are never fitted to a candidate run. For replacement v0.6.0, heap
468+
floors alone round the minimum of the frozen sample set outward to 1 KiB;
469+
static image/headroom remains exact. Reset-to-advertisement is fixed at 3,000
470+
ms for each ESP profile and 7,000 ms for Pico, and both PUT and GET are fixed
471+
at 6,600 bytes/s for every profile. The reset metric ends at the same fresh
472+
exact-service host scanner callback. Exactly 10 reset and five PUT/five GET
473+
samples must all pass their fixed SLO with no trim or retry; the 15-second
474+
health timeout, physical cycle, integrity, reliability, duration arithmetic,
475+
and link facts remain exact. The engineering baseline retains reset/goodput
476+
samples diagnostically but derives only static and heap values. Only fresh
477+
hash-locked final-candidate HIL qualifies the replacement. Historical source
478+
eras retain their V3/V2 derivation arithmetic.
479+
480+
The predecessor source-era cross-commit physical-fact lineage is specified by
481+
[browser-flashing.md](firmware/browser-flashing.md): it could reuse only the
470482
retained baseline's passed physical-power-cycle boolean when all shipped bytes
471483
are identical. Every quantitative and automatic OI field still comes from a
472484
fresh hash-bound final-candidate run, the record explicitly claims no new
473485
physical observation, and every app, operator, private, and profile gate
474486
remains mandatory. No complete baseline row may be relabelled as final-candidate
475487
verification.
488+
ADR-0038 invalidates that predecessor lineage for replacement v0.6.0; every
489+
replacement profile requires a fresh exact physical power-cycle check.
476490

477491
Fresh Waveshare and Pico reset arrays use PBLE RUN of `machine.reset()`, the
478492
expected link drop, and the following fresh advertisement. That automatic
@@ -481,7 +495,9 @@ not enlarge the one-field lineage scope.
481495

482496
Published v0.4.2 metadata and HIL evidence retain their historical exact
483497
two-profile contract. The unqualified v0.5.1 source candidate retains its
484-
three-profile source-era contract. Neither is expanded or reinterpreted as the
485-
v0.6.0 five-profile matrix.
498+
three-profile source-era contract. The predecessor unpublished v0.6.0 source
499+
also retains its V3/V2 derivation for replay, but its candidate/HIL cannot
500+
qualify the replacement. None is expanded or reinterpreted as replacement
501+
v0.6.0 evidence.
486502

487503
<!-- SPDX-License-Identifier: MIT -->

0 commit comments

Comments
 (0)