diff --git a/docs/specifications/firmware.md b/docs/specifications/firmware.md index a297f3a..7459b39 100644 --- a/docs/specifications/firmware.md +++ b/docs/specifications/firmware.md @@ -67,16 +67,18 @@ confined to Layer 2 (board overlay: pins, flash size, USB), and the shared agent core contains no per-chip product logic. The browser installer does not publish unqualified family-wide images. The -current pre-v1 release qualifies `esp32-4mb` (classic ESP32, 4 MiB flash) and -`esp32-s3-n16r8` (ESP32-S3, 16 MiB flash plus 8 MiB Octal PSRAM). -`esp32-c3-4mb` remains a known initial v1 profile but is not released or -selectable until exact-profile real-hardware validation is complete. ESP Web -Tools detects the chip family but cannot by that fact alone prove the required -flash/PSRAM topology. The full compatibility and artifact contract is frozen in +v0.4.2 candidate set is `esp32-4mb` (classic ESP32, 4 MiB flash) and +`esp32-s3-n16r8` (ESP32-S3, 16 MiB flash plus 8 MiB Octal PSRAM), but the +public browser installer remains unavailable pending final HIL on the exact +candidate bytes for both profiles. `esp32-c3-4mb` remains a known initial v1 +profile but is not released or selectable until exact-profile real-hardware +validation is complete. ESP Web Tools detects the chip family but cannot by +that fact alone prove the required flash/PSRAM topology. The full compatibility +and artifact contract is frozen in [firmware/browser-flashing.md](firmware/browser-flashing.md). -These targets are the first validated firmware family, not the product -boundary. A future port MAY use another upstream MicroPython port, CPU +These targets are the initial reference/build family, not the product boundary. +A future port MAY use another upstream MicroPython port, CPU architecture, BLE host, native integration mechanism, build system, storage backend, or provisioning tool. It MUST preserve PBLE/1, the protected control-plane boundary, capability negotiation, workspace safety, and the @@ -116,13 +118,13 @@ MUST NOT require a known-chip allowlist. The measurement method is frozen in [firmware/specs.md §5.3](firmware/specs.md#53-footprint-gates-nfr-fp); numeric values remain provisional until derived from retained baseline samples. -The current pre-v1 qualification is profile-scoped: +The v0.4.2 candidate qualification scope is profile-scoped: | Profile | Current numeric status | Release effect | |---|---|---| -| `esp32-4mb` | Measure, derive, freeze, and verify on the owned exact profile | Required for the current pre-v1 installer | -| `esp32-s3-n16r8` | Measure, derive, freeze, and verify on the owned exact N16R8 profile | Required for the current pre-v1 installer | -| `esp32-c3-4mb` | Deferred; no current threshold or HIL row | Blocks C3 enablement and v1.0, but not the two-profile pre-v1 release | +| `esp32-4mb` | Measure, derive, freeze, and verify on the owned exact profile | Required before v0.4.2 candidate qualification and installer activation | +| `esp32-s3-n16r8` | Measure, derive, freeze, and verify on the owned exact N16R8 profile | Required before v0.4.2 candidate qualification and installer activation | +| `esp32-c3-4mb` | Deferred; no current threshold or HIL row | Blocks C3 enablement and v1.0, but not qualification of the two-profile candidate | The enforced metrics are: diff --git a/docs/specifications/firmware/browser-flashing.md b/docs/specifications/firmware/browser-flashing.md index 1138f76..24834e0 100644 --- a/docs/specifications/firmware/browser-flashing.md +++ b/docs/specifications/firmware/browser-flashing.md @@ -37,8 +37,9 @@ BLE and PBLE/1. ## 1. Release image profiles -The current pre-v1 public bundle contains exactly these two qualified -**provisioning image profiles**: +The v0.4.2 release candidate targets exactly these two **provisioning image +profiles**. Neither becomes a qualified public profile until the complete +reproducibility, license, exact-byte HIL, and activation gate passes: | Profile ID | ESP Web Tools `chipFamily` | Required target configuration | ESP image silicon window (`min_chip_rev_full`…`max_chip_rev_full`) | Merge settings | Browser image and component map | |---|---|---|---|---|---| @@ -163,7 +164,7 @@ as immutable inputs to one release candidate. Candidate-freezing MUST happen before the two clean release builds, license audit, candidate packaging, protected-site staging, or HIL. It is an input-selection state only: it does **not** assert that the pins work on hardware and does not approve them for a -public release. Exact-profile HIL on both current release profiles, +public release. Exact-profile HIL on both current candidate profiles, `esp32-4mb` and `esp32-s3-n16r8`, remains the pre-v1 public-release approval gate. @@ -336,7 +337,7 @@ one build for its owning profile. For example, `` above is a template substitution, not literal released JSON. The `esp32-4mb` manifest has the same shape and exactly one `ESP32` build with -offset `4096`. There is no C3 manifest in the current release. The website MUST +offset `4096`. There is no C3 manifest in the current candidate. The website MUST set the custom element's manifest URL to the verified manifest for the selected profile only. It MUST NOT pass an all-family catalog to ESP Web Tools or change manifests after the browser has selected a serial device. @@ -389,7 +390,7 @@ beside their metadata: UTC `built_at`; - provenance: full PyBLE commit and clean state; MicroPython ref/commit; ESP-IDF ref/commit; patch count; runner and compiler/tool versions; -- one entry for each current release profile in the first table of §1, +- one entry for each current candidate profile in the first table of §1, including profile ID, `chip_family`, flash/PSRAM requirements, flash mode/frequency, required `silicon_revision.minimum_full` and `silicon_revision.maximum_full` integers @@ -457,7 +458,7 @@ public notice. The conservative build audit runs against all six authoritative ESP-IDF descriptions: application and bootloader `project_description.json` for each of the three initial build targets. This preserves the v1 three-target build -and license gate even while the pre-v1 public bundle contains two profiles. +and license gate even while the pre-v1 candidate bundle targets two profiles. The released notice MUST classify as redistributed only the dependency union of the two packaged profiles; C3-only observations remain retained review evidence and MUST NOT be represented as a shipped C3 image or shipped profile. @@ -1089,7 +1090,7 @@ Automated release tests MUST cover: - static-export and candidate/production-origin retrieval of every versioned byte. -One HIL record MUST be completed for each of the two exact current release +One HIL record MUST be completed for each of the two exact current candidate profiles using the final, hash-locked release candidate. The report contains exactly one embedded JSON object marked `PYBLE_HIL_RECORDS_V2`; a V1 marker, an additional marker, or keys not defined below are invalid. diff --git a/docs/specifications/firmware/specs.md b/docs/specifications/firmware/specs.md index c634da7..1016a79 100644 --- a/docs/specifications/firmware/specs.md +++ b/docs/specifications/firmware/specs.md @@ -90,7 +90,7 @@ Where this document and [TDD.md](TDD.md) touch the same topic, this document win - **Workspace jail** — the constraint that PBLE/1 file commands may only read/write within `fs_root`. - **Runner** — the task that executes user code (file or inline source). - **HIL** — hardware-in-the-loop testing on every exact real-hardware profile - claimed by a release. The current pre-v1 release matrix is exactly + claimed by a release. The current pre-v1 candidate matrix is exactly `esp32-4mb` and `esp32-s3-n16r8`; the v1.0 matrix additionally requires `esp32-c3-4mb` (PRD §1B.3, §10.12). - **Frozen-Python agent** — agent modules baked into the firmware image as `.py` (frozen at build); the recommended first implementation. @@ -251,7 +251,7 @@ are screenless. - **FR-LIB-1** — Every `esp32`, `esp32-s3`, and `esp32-c3` firmware image MUST make the pinned upstream MicroPython `neopixel.NeoPixel` API importable offline by user file/source runs and after a soft reboot. MUST (*source: PRD §9.8, §11.3; verify: resolved-manifest/build/HIL; story: F-24/A-31*) - **FR-LIB-2** — The module MUST be selected from the pristine pinned MicroPython/micropython-lib tree through each target's frozen manifest; PyBLE MUST NOT copy, fork, patch, or replace it with a custom WS2812 driver. MUST (*source: PRD §1A, §10.9, §10.10; verify: build/structure; story: F-24*) - **FR-LIB-3** — Bundling NeoPixel MUST NOT add an agent GPIO abstraction, PBLE/1 opcode/capability, board/onboard-LED name, pin/count/colour default, or target-specific user-code routing. GPIO, pixel count, index, colour, timing, and physical suitability remain explicit user-program/runtime concerns. MUST (*source: PRD §9.8, §11.3; verify: unit/no-leak/HIL; story: F-24/A-31*) -- **FR-LIB-4** — Release validation MUST resolve exactly one `neopixel.py` for each of the three build targets, record the per-target firmware-size delta, and run a runtime import smoke on every exact profile included in that release. The current pre-v1 runtime matrix is the two profiles in §2.2; `esp32-c3-4mb` runtime smoke remains required before that profile is enabled and before v1.0. Any visual LED smoke MUST take an operator-supplied GPIO, use a bounded dim sequence, and turn the pixel off on exit. MUST (*source: PRD §10.11, §10.13, §13.3; verify: build/size/HIL; story: F-24*) +- **FR-LIB-4** — Release validation MUST resolve exactly one `neopixel.py` for each of the three build targets, record the per-target firmware-size delta, and run a runtime import smoke on every exact profile included in that release. The current pre-v1 candidate runtime matrix is the two profiles in §2.2; `esp32-c3-4mb` runtime smoke remains required before that profile is enabled and before v1.0. Any visual LED smoke MUST take an operator-supplied GPIO, use a bounded dim sequence, and turn the pixel off on exit. MUST (*source: PRD §10.11, §10.13, §13.3; verify: build/size/HIL; story: F-24*) This NeoPixel contract applies to the three initial ESP32-family images. A future platform port MUST NOT claim equivalent support until it validates the @@ -283,7 +283,7 @@ upstream package and required runtime primitive for that target. > and evidence schema before any threshold is selected. It does not invent or > claim a numeric threshold. -The current pre-v1 qualification set is exactly, and in this order, +The current pre-v1 candidate qualification set is exactly, and in this order, `esp32-4mb` and `esp32-s3-n16r8`. Each MUST have a complete numeric policy and final-candidate HIL record before the current public installer can be enabled. `esp32-c3-4mb` MUST NOT have a threshold entry or HIL row in this pre-v1 @@ -322,7 +322,7 @@ matrix remains all three profiles. story: X-03, F-13/14)* - **NFR-FP-CLOSE** — Every exact profile included in a release is **release-blocking** until all of its thresholds are frozen and its - hash-locked final-candidate evidence passes. For the current pre-v1 release + hash-locked final-candidate evidence passes. For the current pre-v1 candidate this means exactly the two profiles above. The still-open C3 portion blocks any C3 release and v1.0, but it does not block an otherwise-qualified two-profile pre-v1 release. — *(source: PRD §10.12, §10.13, §7.1; verify: @@ -586,8 +586,9 @@ This is software-level safety of the IDE/agent, **not** hardware/actuator safety bundle at the canonical versioned same-origin path. A v0.x mirror is optional and every corresponding file and byte MUST be identical when one is published. v1.0 and later MUST additionally publish the matching - byte-identical GitHub Release. The current pre-v1 bundle MUST cover exactly - the two qualified profiles; v1.0 MUST restore three-target release parity. — + byte-identical GitHub Release. The current pre-v1 candidate bundle MUST target + exactly the two profiles and MUST NOT become public until both qualify; v1.0 + MUST restore three-target release parity. — *(source: PRD §10.12, §18.2, [browser-flashing §3](browser-flashing.md#3-same-origin-versioned-layout); verify: build, release; story: X-11)* @@ -664,7 +665,7 @@ This is software-level safety of the IDE/agent, **not** hardware/actuator safety verification MUST inspect generated frozen content or the running image, not stale intermediate `.mpy` files. — *(source: FR-LIB, ADR-0018; verify: build/HIL; story: F-24)* -- **BLD-17** — The current pre-v1 browser release MUST expose exactly +- **BLD-17** — The current pre-v1 browser candidate MUST target exactly `esp32-4mb` and `esp32-s3-n16r8`, with the memory qualifications, merge settings, browser-image base offsets, and component offsets frozen in [browser-flashing §1](browser-flashing.md#1-release-image-profiles). Family @@ -762,7 +763,7 @@ These are tracked, release-blocking where noted; they MUST be closed before the - **OI-1 — Per-profile resource numbers pending HIL.** The measurement method, exact current scope, evidence contract, and threshold derivation are frozen - in §5.3. Numeric thresholds remain open. The current pre-v1 portion closes + in §5.3. Numeric thresholds remain open. The current pre-v1 candidate portion closes only when `esp32-4mb` and `esp32-s3-n16r8` each have committed evidence-derived policy values and passing final-candidate HIL. That state MUST be described as **“qualified for the current two-profile pre-v1 @@ -776,7 +777,7 @@ These are tracked, release-blocking where noted; they MUST be closed before the Before release builds and HIL, its exact committed bytes MUST be selected as candidate-frozen immutable inputs. That selection is not hardware approval: the exact candidate MUST still pass HIL on every exact profile included in - that release. The current pre-v1 set is the two profiles in §5.3; C3 remains + that release. The current pre-v1 candidate set is the two profiles in §5.3; C3 remains mandatory before C3 enablement and before v1.0. A pin change creates a new candidate and resets all candidate-bound evidence. — *(verify: build, HIL)* - **OI-3 — Frozen → native split point TBD.** The agent starts frozen-Python; the decision of which hot paths (BLE I/O, framing, file chunking) move to a native `USER_C_MODULE`, and on which chip the budget forces it, is open and determined by HIL footprint/throughput measurement ([firmware.md §2](../firmware.md#2-agent-base-native-vs-frozen), [PRD §10.2](../prd.md)). The PBLE/1 wire contract MUST NOT change across the move (NFR-MAINT-3). — *(verify: size, conformance, HIL)* diff --git a/docs/specifications/hardware.md b/docs/specifications/hardware.md index 49ba98f..e41398e 100644 --- a/docs/specifications/hardware.md +++ b/docs/specifications/hardware.md @@ -35,8 +35,8 @@ under the same PBLE/1 protocol. | Image profile | Required memory configuration | Installer family check | Release status | Public compatibility claim | |---|---|---|---|---| -| `esp32-4mb` | Classic ESP32; 4 MiB external SPI flash; no PSRAM assumed | `ESP32` | Current pre-v1 release | Only boards whose module documentation confirms this flash layout | -| `esp32-s3-n16r8` | ESP32-S3; 16 MiB flash; 8 MiB Octal PSRAM | `ESP32-S3` | Current pre-v1 release | N16R8-class modules only; not generic ESP32-S3 | +| `esp32-4mb` | Classic ESP32; 4 MiB external SPI flash; no PSRAM assumed | `ESP32` | v0.4.2 HIL pending; installer unavailable | Only boards whose module documentation confirms this flash layout | +| `esp32-s3-n16r8` | ESP32-S3; 16 MiB flash; 8 MiB Octal PSRAM | `ESP32-S3` | v0.4.2 HIL pending; installer unavailable | N16R8-class modules only; not generic ESP32-S3 | | `esp32-c3-4mb` | ESP32-C3 revision v0.3 or newer; 4 MiB external flash; no PSRAM assumed | `ESP32-C3` | Unavailable pending exact-profile HIL | No public installer compatibility claim yet | The installer family check cannot establish flash capacity, PSRAM type, USB diff --git a/docs/specifications/prd.md b/docs/specifications/prd.md index 419fae3..3a16411 100644 --- a/docs/specifications/prd.md +++ b/docs/specifications/prd.md @@ -159,7 +159,7 @@ Each story selects the applicable categories; the protocol and firmware stories - **Hardware-in-the-loop (HIL)** — on every exact profile claimed by the release: connect, `DEVICE_INFO`, run/stop, console streaming, and a clean multi-file upload without dropping the link, plus resume-on-reconnect and - the resource measurements above. The current pre-v1 matrix is exactly + the resource measurements above. The current pre-v1 candidate matrix is exactly `esp32-4mb` plus `esp32-s3-n16r8`; v1.0 additionally requires `esp32-c3-4mb`. A milestone is gated by a working HIL demo, not by merged code alone. @@ -839,7 +839,7 @@ Each successful per-target build MUST emit a flashable artifact set ([firmware.m matrix. An unqualified profile MUST be absent from release metadata, artifacts, selection, and recovery commands and shown as unavailable, never silently marked supported. -- The current pre-v1 release set is exactly `esp32-4mb` and +- The current pre-v1 candidate set is exactly `esp32-4mb` and `esp32-s3-n16r8`; `esp32-c3-4mb` remains unavailable pending exact-profile real-hardware validation. Re-enabling it requires a new SemVer candidate and immutable bundle. @@ -855,7 +855,7 @@ numbers are frozen. The detailed method, exact workload, metric meanings, rounding formulas, and evidence contract are normative in [firmware/specs.md §5.3](firmware/specs.md#53-footprint-gates-nfr-fp). -| Gate | Metric and direction | Current pre-v1 profiles | ESP32-C3 / v1.0 | +| Gate | Metric and direction | Current pre-v1 candidate profiles | ESP32-C3 / v1.0 | |---|---|---|---| | **FP-FLASH** | Total shipped application-image ceiling plus factory-partition headroom floor | Freeze separately for `esp32-4mb` and `esp32-s3-n16r8` | Remains open for `esp32-c3-4mb`; C3 is the hard constraint | | **FP-HEAP** | Python GC and internal-IDF current/largest/minimum heap floors after HELLO and transfer workloads | Freeze separately for both included profiles; default-capability `free_mem` is diagnostic only | Must leave usable user-code and control-plane headroom | @@ -864,7 +864,7 @@ rounding formulas, and evidence contract are normative in Requirements: -- The current pre-v1 public set is exactly the two profiles in §10.12. Their +- The current pre-v1 candidate set is exactly the two profiles in §10.12. Their numeric thresholds and hash-locked final-candidate HIL are release-blocking. `esp32-c3-4mb` MUST remain absent from that release's policy, HIL rows, artifacts, recovery, and installer selection. @@ -1101,7 +1101,7 @@ Exact per-package licenses MUST be generated mechanically at build time (not han ### §15.3 Distribution - The app MUST be distributed **free** on the **Apple App Store** and **Google Play**, at feature parity across iPadOS and Android tablets (see §13.6 and §19). No account, no paywall, no in-app purchase. -- A browser-based **web flasher** MUST be hosted at `pyble.dev/flash`, built on **esp-web-tools**, with one profile-scoped, single-build manifest per exact profile included in that release (see [firmware.md §6](firmware.md#6-build--distribution)). It MUST allow a user to flash the agent from a supported desktop browser over USB without installing a toolchain, and MUST NOT give ESP Web Tools a multi-family manifest that could override the user's selected profile. The current pre-v1 set is the two profiles in §10.12; C3 is unavailable until separately qualified. +- A browser-based **web flasher** MUST be hosted at `pyble.dev/flash`, built on **esp-web-tools**, with one profile-scoped, single-build manifest per exact profile included in that release (see [firmware.md §6](firmware.md#6-build--distribution)). It MUST allow a user to flash the agent from a supported desktop browser over USB without installing a toolchain, and MUST NOT give ESP Web Tools a multi-family manifest that could override the user's selected profile. The current pre-v1 candidate set is the two profiles in §10.12; C3 is unavailable until separately qualified. - Firmware binaries (`firmware.bin`, bootloader, partition table, profile-scoped `manifest.json` files, and `THIRD_PARTY_LICENSES`) MUST be published at the canonical immutable `pyble.dev/firmware/v/` path, one set per exact @@ -1390,7 +1390,7 @@ The entry flow is scan → connect → use, with no QR pairing, no account, and These are the production targets the project measures itself against. Numeric BLE/throughput targets are validated on hardware for every exact profile included in a release and MUST be frozen per profile after measurement. The -current pre-v1 matrix has two profiles; the v1.0 matrix has all three. Until a +current pre-v1 candidate matrix has two profiles; the v1.0 matrix has all three. Until a profile's values are frozen, they are stated as intent, not asserted. | Metric | Definition | v1.0 target | Status | @@ -1530,7 +1530,8 @@ The foundational product decisions are resolved and recorded as Architecture Dec - **Initial app platforms** → iPadOS and Android tablet at parity, released together (§13.6). - **Wi-Fi / USB as primary transport** → no; BLE-first and BLE-only for v1 (this is what makes iPad first-class). - **Board scope** → capability-defined MicroPython + BLE platform; ESP32, - ESP32-S3, and ESP32-C3 are the initial validated firmware targets + ESP32-S3, and ESP32-C3 are the initial build/reference targets, while + release compatibility remains exact-profile and HIL-gated ([ADR-0021](../decisions/0021-capability-defined-board-scope.md)). **Pending (resolved by measurement, not debate):** @@ -1542,7 +1543,7 @@ The foundational product decisions are resolved and recorded as Architecture Dec file MUST first be candidate-frozen as the immutable release-build/HIL input; that state is not approval. The same candidate MUST then pass the complete exact-profile HIL matrix before its pins and resource gates are - approved. The current pre-v1 subset is exactly the two profiles in §10.12; + approved. The current pre-v1 candidate subset is exactly the two profiles in §10.12; all three, including C3, are required for v1.0 (§10.9, §17.1, §21.2). A pin change creates a new candidate. New ADRs are added if a pin or budget changes materially. @@ -1563,4 +1564,4 @@ New significant decisions MUST be captured as additional ADRs (`docs/decisions/N | **Control plane** | The agent's protected layer that owns BLE, the runner, and the filesystem bridge. It MUST NOT be editable by user code; a frozen `while True` in user code MUST NOT be able to wedge BLE or block `STOP`. | | **Workspace** | The user's own files on the board — `/main.py`, `/lib/*.py`, `/data/*` (Layer 4). Just programs the agent runs; never the control plane. | | **Platform port / target adapter** | Layer-2 integration for a MicroPython target: BLE host, scheduler/interrupt boundary, storage/config, identity, build, and provisioning. The initial ESP32 port uses per-chip board overlays for `esp32` / `esp32-s3` / `esp32-c3`, copied into the upstream tree at build prep so the submodule stays pristine. | -| **HIL** | Hardware-in-the-loop — validation and measurement performed on a real board (as opposed to host-side or fake-transport tests). Resource and BLE/goodput numbers are frozen only after HIL measurement for every exact profile claimed by a release. The current pre-v1 matrix is `esp32-4mb` plus `esp32-s3-n16r8`; v1.0 additionally requires `esp32-c3-4mb`. | +| **HIL** | Hardware-in-the-loop — validation and measurement performed on a real board (as opposed to host-side or fake-transport tests). Resource and BLE/goodput numbers are frozen only after HIL measurement for every exact profile claimed by a release. The current pre-v1 candidate matrix is `esp32-4mb` plus `esp32-s3-n16r8`; v1.0 additionally requires `esp32-c3-4mb`. | diff --git a/docs/specifications/website.md b/docs/specifications/website.md index e639989..cadd602 100644 --- a/docs/specifications/website.md +++ b/docs/specifications/website.md @@ -63,7 +63,8 @@ The home page MAY make these verified claims: - Blocks runs offline, includes editable beginner examples, supports the current explicit numeric-GPIO and standard MicroPython NeoPixel subset, and can reopen exact sidecars or import a deliberately bounded Python subset. - Those hardware APIs are initially validated on ESP32-family firmware and + Those hardware APIs are implemented and exercised on the initial + ESP32-family builds; release support still requires exact-profile HIL and MUST NOT be promised for every future port. - PBLE/1 is an open PyBLE-owned protocol. @@ -72,11 +73,13 @@ Compatibility copy MUST distinguish platform scope from current support: - hardware eligibility requires MicroPython, a PBLE/1-capable BLE peripheral stack, sufficient resources, and a conforming agent port; - actual support requires a released, validated firmware image for the target; -- the current pre-v1 release list is the exact `esp32-4mb` and - `esp32-s3-n16r8` profiles; ESP32-C3 remains an initial firmware target but - is planned/unavailable until its exact profile passes real-hardware HIL; -- browser provisioning is offered only for the exact qualified memory profiles - in §7, including N16R8-class hardware for the initial ESP32-S3 image; +- the current v0.4.2 candidate set is the exact `esp32-4mb` and + `esp32-s3-n16r8` profiles, but neither is released or selectable until both + exact candidate images pass real-hardware HIL; ESP32-C3 remains an initial + firmware target but is planned/unavailable until its own exact profile + passes HIL; +- browser provisioning is activated only for exact HIL-qualified memory + profiles in §7, including N16R8-class hardware for the ESP32-S3 image; - users select pins for their exact board and wiring. It MUST NOT imply that Bluetooth hardware or stock MicroPython alone is enough, @@ -123,7 +126,8 @@ metadata suitable for the external beta announcement. The image MUST use the canonical prompt-chip mark and a privacy-reviewed capture of the real app described in §4. It MAY add authored brand text and framing, but MUST NOT retouch or generate the pictured app interface. Its claims MUST be limited to -the current iPad external beta and the exact qualified installer profiles. +the current iPad external beta, the exact selected candidate profiles, and +their current qualification status. The social image MUST have useful alternative text, remain legible under common center crops, and make no third-party runtime request. A QR code MUST @@ -347,9 +351,10 @@ image profiles, offsets, same-origin layout, manifest, integrity/provenance metadata, recovery content, and HIL matrix. This section owns the website state and user experience. -The current pre-v1 public profiles are exactly `esp32-4mb` and -`esp32-s3-n16r8`. The S3 image requires 16 MiB flash plus 8 MiB Octal PSRAM -and MUST NOT be described as suitable for every ESP32-S3 board. The known +The v0.4.2 candidate profiles are exactly `esp32-4mb` and +`esp32-s3-n16r8`; neither is public or qualified before the complete gate below +passes. The S3 image requires 16 MiB flash plus 8 MiB Octal PSRAM and MUST NOT +be described as suitable for every ESP32-S3 board. The known `esp32-c3-4mb` profile remains an initial v1 target but is explicitly unavailable until exact-profile real-hardware validation is complete. It MUST be shown separately as planned/unavailable and MUST NOT appear in the active @@ -361,7 +366,7 @@ The `/flash` action MUST fail closed and remain explicitly unavailable until all of the following are true for one exact immutable version: 1. two clean, provenance-recorded, reproducible builds produced - byte-identical current release-profile artifact sets from the frozen + byte-identical current candidate-profile artifact sets from the frozen source/toolchain pins, while the three-target source/build audit remained green; 2. the versioned, same-origin profile-scoped ESP Web Tools manifests and separate @@ -369,7 +374,7 @@ all of the following are true for one exact immutable version: partition, path, schema, license, and integrity gates; 3. the final hash-locked bytes passed the complete browser-install and interrupted-flash recovery HIL matrix on real hardware for both exact - current release profiles from an access-controlled, + current candidate profiles from an access-controlled, production-equivalent HTTPS candidate deployment, while the public action remained disabled; 4. the exact bytes, release notes, licenses, recovery guide, and HIL report are diff --git a/tests/publication/test_public_claims.py b/tests/publication/test_public_claims.py index e2b0529..c40d9ed 100644 --- a/tests/publication/test_public_claims.py +++ b/tests/publication/test_public_claims.py @@ -23,6 +23,42 @@ def setUpClass(cls) -> None: cls.bug_template = ( REPO_ROOT / ".github" / "ISSUE_TEMPLATE" / "bug.yml" ).read_text(encoding="utf-8") + cls.firmware_overview = ( + REPO_ROOT / "docs" / "specifications" / "firmware.md" + ).read_text(encoding="utf-8") + cls.hardware_overview = ( + REPO_ROOT / "docs" / "specifications" / "hardware.md" + ).read_text(encoding="utf-8") + cls.product_requirements = ( + REPO_ROOT / "docs" / "specifications" / "prd.md" + ).read_text(encoding="utf-8") + cls.website_readme = ( + REPO_ROOT / "tools" / "web" / "README.md" + ).read_text(encoding="utf-8") + cls.website_specification = ( + REPO_ROOT / "docs" / "specifications" / "website.md" + ).read_text(encoding="utf-8") + cls.firmware_requirements = ( + REPO_ROOT / "docs" / "specifications" / "firmware" / "specs.md" + ).read_text(encoding="utf-8") + cls.browser_flashing = ( + REPO_ROOT + / "docs" + / "specifications" + / "firmware" + / "browser-flashing.md" + ).read_text(encoding="utf-8") + cls.flash_page = ( + REPO_ROOT / "tools" / "web" / "src" / "app" / "flash" / "page.tsx" + ).read_text(encoding="utf-8") + cls.flash_status = ( + REPO_ROOT + / "tools" + / "web" + / "src" + / "components" + / "flash-status.tsx" + ).read_text(encoding="utf-8") def test_readme_is_truthful_before_v042_hil_completes(self) -> None: firmware = markdown_section(self.readme, "What works") @@ -83,6 +119,137 @@ def test_bug_template_collects_the_exact_installer_diagnostics(self) -> None: self.assertRegex(self.bug_template, r"(?i)remove.*(?:secret|credential)") + def test_firmware_and_hardware_overviews_are_preactivation_truthful(self) -> None: + self.assertIn( + "v0.4.2 candidate set is `esp32-4mb`", + self.firmware_overview, + ) + self.assertIn( + "public browser installer remains unavailable pending final HIL", + self.firmware_overview, + ) + self.assertNotIn( + "current pre-v1 release qualifies", + self.firmware_overview, + ) + self.assertIn( + "| `esp32-4mb` | Classic ESP32; 4 MiB external SPI flash; " + "no PSRAM assumed | `ESP32` | v0.4.2 HIL pending; installer " + "unavailable |", + self.hardware_overview, + ) + self.assertIn( + "| `esp32-s3-n16r8` | ESP32-S3; 16 MiB flash; 8 MiB Octal PSRAM " + "| `ESP32-S3` | v0.4.2 HIL pending; installer unavailable |", + self.hardware_overview, + ) + self.assertNotIn( + "| Current pre-v1 release |", + self.hardware_overview, + ) + + def test_public_summaries_distinguish_build_targets_from_release_support( + self, + ) -> None: + self.assertIn( + "initial ESP-IDF build/reference targets", + self.website_readme, + ) + self.assertIn( + "public installer remains unavailable pending v0.4.2 HIL", + self.website_readme, + ) + self.assertNotIn( + "initial validated ESP32 / ESP32-S3 / ESP32-C3 firmware targets", + self.website_readme, + ) + board_scope_start = self.product_requirements.index( + "- **Board scope**", + ) + board_scope_end = self.product_requirements.index( + "\n\n**Pending", + board_scope_start, + ) + board_scope = self.product_requirements[ + board_scope_start:board_scope_end + ] + self.assertIn("initial build/reference targets", board_scope) + self.assertIn( + "release compatibility remains exact-profile and HIL-gated", + board_scope, + ) + self.assertNotIn("initial validated firmware targets", board_scope) + + def test_preactivation_profiles_are_candidates_not_current_releases( + self, + ) -> None: + self.assertIn( + "v0.4.2 candidate qualification scope is profile-scoped", + self.firmware_overview, + ) + self.assertIn( + "current v0.4.2 candidate set is the exact `esp32-4mb`", + self.website_specification, + ) + self.assertIn( + "v0.4.2 release candidate targets exactly these two", + self.browser_flashing, + ) + self.assertIn( + "current pre-v1 candidate set is exactly `esp32-4mb`", + self.product_requirements, + ) + self.assertIn( + "current pre-v1 candidate qualification set is exactly", + self.firmware_requirements, + ) + + prohibited_claims = ( + "current pre-v1 qualification is profile-scoped", + "current pre-v1 release list is", + "current pre-v1 public set is", + "current pre-v1 release set is", + "current pre-v1 public bundle contains exactly these two qualified", + "current pre-v1 qualification set is exactly", + ) + for claim in prohibited_claims: + for document in ( + self.firmware_overview, + self.website_specification, + self.browser_flashing, + self.product_requirements, + self.firmware_requirements, + ): + self.assertNotIn(claim, document) + + def test_preactivation_installer_ui_does_not_call_candidates_qualified( + self, + ) -> None: + self.assertIn( + "candidate ESP32 and ESP32-S3 profiles", + self.flash_page, + ) + self.assertIn( + "both exact current candidate profiles", + self.flash_page, + ) + self.assertIn( + "both exact current candidate profiles", + self.flash_status, + ) + self.assertNotIn( + "qualified ESP32 and ESP32-S3 profiles", + self.flash_page, + ) + self.assertNotIn( + "both exact current release profiles", + self.flash_page, + ) + self.assertNotIn( + "both exact current release profiles", + self.flash_status, + ) + if __name__ == "__main__": unittest.main() diff --git a/tools/web/README.md b/tools/web/README.md index 18fdc3c..88fd8de 100644 --- a/tools/web/README.md +++ b/tools/web/README.md @@ -5,9 +5,10 @@ The statically authored Next.js site for `pyble.dev`. It explains the PyBLE workflow and capability-defined board vision, distinguishes that vision from -the initial validated ESP32 / ESP32-S3 / ESP32-C3 firmware targets, publishes -privacy and support information, and stages the future browser firmware -installer without claiming that release artifacts are ready. +the initial ESP-IDF build/reference targets (ESP32, ESP32-S3, and ESP32-C3), +publishes privacy and support information, and stages the future browser +firmware installer. The public installer remains unavailable pending v0.4.2 HIL +on the exact release-candidate bytes for both selected profiles. ## Why Next.js diff --git a/tools/web/src/app/flash/page.tsx b/tools/web/src/app/flash/page.tsx index ac17a56..c1f6d9a 100644 --- a/tools/web/src/app/flash/page.tsx +++ b/tools/web/src/app/flash/page.tsx @@ -10,7 +10,7 @@ import { pageMetadata } from "@/lib/site"; export const metadata = pageMetadata({ title: "Firmware installer", description: - "Release status and requirements for installing PyBLE firmware on qualified ESP32 and ESP32-S3 profiles, with ESP32-C3 planned.", + "Release status and requirements for candidate ESP32 and ESP32-S3 profiles, with ESP32-C3 planned.", path: "/flash", }); @@ -24,7 +24,7 @@ export default function FlashPage() { One-time wired provisioning installs PyBLE-enabled MicroPython. Then develop over Bluetooth Low Energy from the tablet-first PyBLE app. The public install action remains unavailable until the final bytes pass - hardware validation on both exact current release profiles. + hardware validation on both exact current candidate profiles.

diff --git a/tools/web/src/components/flash-status.tsx b/tools/web/src/components/flash-status.tsx index 06082ec..ead5674 100644 --- a/tools/web/src/components/flash-status.tsx +++ b/tools/web/src/components/flash-status.tsx @@ -122,7 +122,7 @@ function policyFailure(release: FirmwareReleaseDescriptor | null | undefined) { if (!release) { return { heading: "Installer unavailable", - body: "Hardware validation is still required on both exact current release profiles before the public installer can be enabled.", + body: "Hardware validation is still required on both exact current candidate profiles before the public installer can be enabled.", }; } if (release.deployment !== "public" && release.deployment !== "candidate") { diff --git a/tools/web/src/test/flash-installer.test.tsx b/tools/web/src/test/flash-installer.test.tsx index 4e61ab9..53615d6 100644 --- a/tools/web/src/test/flash-installer.test.tsx +++ b/tools/web/src/test/flash-installer.test.tsx @@ -204,7 +204,7 @@ describe("browser firmware installer states", () => { render(); expect(screen.getByRole("status")).toHaveTextContent( - /installer unavailable.*hardware validation.*both exact current release profiles/i, + /installer unavailable.*hardware validation.*both exact current candidate profiles/i, ); expect( screen.getByRole("button", { name: /installer coming soon/i }), diff --git a/tools/web/src/test/site-contract.test.tsx b/tools/web/src/test/site-contract.test.tsx index d35da57..e07820e 100644 --- a/tools/web/src/test/site-contract.test.tsx +++ b/tools/web/src/test/site-contract.test.tsx @@ -375,7 +375,7 @@ describe("public-site contract", () => { ).toBeInTheDocument(); expect( screen.getByText( - /public install action remains unavailable until the final bytes pass hardware validation on both exact current release profiles/i, + /public install action remains unavailable until the final bytes pass hardware validation on both exact current candidate profiles/i, ), ).toBeInTheDocument(); expect(screen.getByText(/esp32-s3-n16r8/i)).toBeInTheDocument();