# Offline release notice packaging This procedure produces a **bounded notice/provenance bundle**, not a firmware release, corresponding-source archive, complete SBOM, or legal clearance. The **radio-blob corresponding-source/System Library question remains open**. See [the license inventory](dependency_licenses.md) for the original review and [the retained scope/provenance statement](../third_party/release-notices/README.md) for license choices, mixed notices, wolfSSH packaging discrepancy, TLSF text assembly, icon limits, and remaining release gates. ## Run against the installed reviewed snapshot Requirements: Python 3.9+ on Linux/POSIX with `O_NOFOLLOW`, directory descriptors and `dir_fd` operations; already installed matching managed components, ESP-IDF and Xtensa toolchain. Standard library only. The tool never fetches, installs, builds, runs PlatformIO, accesses a device, or regenerates assets. Run from the repository root. Supply **an explicit, not-yet-existing output directory outside all three input roots**, with an existing parent. Example for the reviewed installed 5.5.3 candidate (choose a fresh final directory name): ```sh python3 tools/release_notices.py \ --sdk-root /home/mscholz/Repos/ESP32_serial_swiss_army_knife/.pio/idf-candidate-5.5.3/core/packages/framework-espidf \ --toolchain-root /home/mscholz/Repos/ESP32_serial_swiss_army_knife/.pio/idf-candidate-5.5.3/core/packages/toolchain-xtensa-esp-elf \ --output /tmp/esp32-release-notices-review-01 ``` `--project-root` optionally selects a different checkout containing the exact reviewed inputs; by default it is the script's repository. The trusted catalog always comes from the script's repository, not from that alternate checkout. Paths to SDK/toolchain packages are explicit; no installation is guessed. There is no user catalog option, force/overwrite mode, automatic repinning, recursive collection, archive command, or automatic release upload. Every input in `third_party/release-notices/inputs.json` is mandatory and pinned by **full-file SHA-256 and size**. Missing sources, altered bytes, bad excerpts, empty files, directories, FIFOs, symlinks (even to identical files), parent traversal, binary notice payloads and exhausted bounds fail closed. Symlinks in input-root or output-parent ancestry are rejected too: pass real non-symlinked paths, not a convenience symlink. All input preflight completes before creating output. The limits are 128 inputs, 4 MiB per input/catalog and 32 MiB cumulative source bytes. The current catalog has **75 inputs**, including the retained SVGs and pinned [icon provenance evidence](icon_provenance.md). Overview documents edited in this integration are not catalog inputs; `docs/icon_provenance.md` **is pinned**. Coordinate any change to that record with the catalog owner; never silently repin. An existing output file, directory (even empty) or dangling symlink is never overwritten. Parent directories are not automatically created. Output uses private directory/file modes (0700/0600, subject to umask). On a write failure, the tool deliberately leaves any partial directory for inspection and never recursively deletes it. A successful exit plus a valid manifest and matching payload hashes is required; presence of a partial manifest is not success. Use another fresh path for a retry. Do not put output into a tree that an untrusted process can concurrently rename or modify; descriptor/no-follow checks prevent symlink traversal but are not isolation from the same OS user. ## Bundle layout and reproducibility - `README.txt`: limitations and entry point for recipients. - `inputs/project/…`: project GPL text, resolved lock, managed licenses and manifests, wolfSSL/wolfSSH README evidence, original wolfSSH header excerpts, xterm MIT texts and recorded provenance, icon summary and official 7.4.47 SVG/license/metadata evidence, TLSF full rendering and scope/provenance statement. - `inputs/sdk/…`: complete scoped SDK license documents, mixed Newlib/argtable3 notices, radio license files, package/version identity and original source notice excerpts. SDK `LICENSE` supplies full Apache-2.0 text; it does **not** authenticate individual icons or cover all SDK files. - `inputs/toolchain/…`: actual toolchain Newlib text, GPLv3, GCC Runtime Library Exception 3.1 and package identity. No toolchain executable is copied. - `manifest.json`: schema/snapshot, catalog SHA-256, logical root/path, full-source size/hash, optional zero-based half-open byte range, purpose, output path/size/hash, and the generated introduction's hash. Excerpts have `.notice.txt` appended; null ranges retain whole original files unchanged. Whole mixed documents are preserved, not converted into guessed SPDX labels. The longer toolchain Newlib notice does not replace the distinct SDK text. The wolfSSH package GPLv2-or-later text and GPLv3-or-later source headers remain separate and unmodified; upstream packaging clarification is still needed. Generated metadata contains no collection timestamps or absolute host paths; original notices retain their own dates and text. For identical catalog and inputs, file names and file contents are deterministic across output locations, input-root locations and source mtimes. Directory/file mtimes are **not** normalized, and no reproducible tar/zip archive is claimed. The manifest hashes all payloads but not itself; retain its hash in your separate release evidence if needed. The catalog is trusted reviewed policy, not a cryptographic signature or proof of package-wide integrity/authorship. Only explicit catalog paths are read. No application config, sdkconfig, build tree, ELF/map/binary, key, credential backup, NVS/flash dump or whole repository is collected. Hash-pinned source implementations are read only to validate original header excerpts, not copied as implementations. The lock and installed package/component manifests are dependency identity metadata, not build configuration. A hash pin is not a general-purpose secret scanner: review additions to this trusted list before accepting them. ## Validate and inspect Temporary-fixture regression suite (does not need installed packages): ```sh python3 tests/release_notices/run.py ``` The suite tests missing/changed sources (including changes outside a notice excerpt), exact mixed-text retention, byte ranges, path traversal, source/root/ output symlinks, existing user data, nonregular files, bounds, output failures, secret/config exclusion, determinism, and CLI behavior. No hardware tests are implied. For a real bundle, check the successful process exit, parse `manifest.json`, recompute every payload hash/size, check no unlisted files are present, and read the scope statement. Compare two independently created fresh outputs by relative names and bytes (not mtimes). Inspect at least both Newlib files, argtable3, wpa_supplicant COPYING/README, GCC exception, wolfSSH LICENSE/README and header excerpts, TLSF text/provenance, both xterm notices and the icon summary. These checks establish scoped assembly, **not release compliance**. If a pinned input differs, stop and review the changed package/license/source. There is deliberately no update-catalog command. Review versions, new/nested NOTICEs, original source notice ranges and hashes, then deliberately update catalog policy and test it. The runtime does not discover added notices or packages outside its finite list; this limitation makes its reads bounded and prevents opportunistic collection of user files. ## Supplied execution evidence — 2026-09-16 Agent results: **36 notice fixture tests PASS**, and **two actual 75-input bundles verified deterministic**, each **77 files / 4,433,930 bytes**. These replace the earlier 62-input / 64-file / 541,147-byte snapshot, not its historical record. No parent rebuild or bundle reassembly was run in this documentation turn; firmware source is unchanged. Assembly is not legal clearance or Phase 9 acceptance. The [unfixed Wi-Fi blocker](wifi_security_update_plan.md) also remains a separate security gate; a future coherent vendor update requires catalog review. ## Installed candidate notice rebase — 2026-09-18 This addendum audits the existing 75-input catalog against **already installed** PlatformIO 6.13.0 migration candidates, not merely downloaded archives. No installation, build, production/config edit, or security-override rebase was performed by this notice audit. The preceding 2026-09-16 evidence is historical. Actual candidate package root: `/home/mscholz/Repos/ESP32_serial_swiss_army_knife/.pio/idf-candidate-5.5.3/core/packages/`. Its `framework-espidf/package.json` reports **3.50503.0**, and `version.txt` reports **5.5.3**. Both `toolchain-xtensa-esp-elf/package.json` and `toolchain-riscv32-esp/package.json` report **14.2.0+20251107**. RISC-V identity was checked for context; it is not a new input root or a claim of RISC-V notice coverage. The shared `/home/mscholz/.platformio/packages/` still contains framework **3.50500.0 / IDF 5.5.0** and both toolchains **14.2.0+20241119**; do not use those shared paths with this rebased catalog. All 75 old catalog full-file hashes/sizes matched the old shared SDK/toolchain and current project inputs before editing. Comparison against the installed candidate found exactly **nine changed entries** (eight SDK, one Xtensa), listed below. The other **66 entries remain unchanged**, including all **39 project/managed inputs**, the dependency-lock pin, icon evidence and managed version map. No catalog paths, purposes unrelated to version identity, or excerpt ranges were added/removed/relaxed. Snapshot identity/date and the three identity-purpose strings were updated deliberately, not by automatic repinning. Paths below are relative to the indicated catalog root. Hashes are full-source SHA-256; old hashes remain in the preceding Git catalog revision. | Root / path | Old → candidate bytes | Candidate SHA-256 | | --- | ---: | --- | | sdk / `components/esp-tls/esp_tls_mbedtls.c` | 50176 → 54323 | `edc39052244526cb91c93a16bc765194031060e5560fd432e75542f2f6c8db53` | | sdk / `components/esp_http_server/src/httpd_parse.c` | 42937 → 42937 | `db7fbbb322bccb4a21bc1607208a6bb7d1f4c0d7ecc3299de10c9e60edaa943b` | | sdk / `components/esp_https_server/src/https_server.c` | 15257 → 15459 | `a2a5ca0549fbe8d1ddd7f9647a48a31fdd6329b997e1550ab69d6a10efecff0d` | | sdk / `components/freertos/FreeRTOS-Kernel/include/freertos/task.h` | 133072 → 133108 | `8b908b964d2c87377838762478253eec04312b985e6bb680bacd3f03ab253897` | | sdk / `components/mbedtls/mbedtls/library/ssl_tls.c` | 329921 → 336963 | `0154e70f20b465213d3f97a9c2b75e686d79ee83592f90e1d7e7d995593ca7e1` | | sdk / `components/mbedtls/mbedtls/library/x509_create.c` | 19803 → 19934 | `55edce5b8fcb039a404b84ea830f06d018128b155dfbf0d74d26b17247b546c6` | | sdk / `package.json` | 536 → 536 | `230c52993499adf9f64d1d6477495ea46c3f14d63022bd52081858373098da68` | | sdk / `version.txt` | 5 → 5 | `b473c8e2ed91b38d7e96e54d4ce094474ca6015c0641b7cdfc0ab94b1f8b4668` | | toolchain / `package.json` | 472 → 472 | `bdd3b852d051d6513912e71d29851bd04b800e2d33d78aec48067af5d83da5c9` | Semantic notice review: five changed implementation files retain byte-identical complete opening notices at their existing ranges (ESP-TLS `[0,122)`, HTTPD `[0,124)`, HTTPS `[0,123)`, Mbed TLS shared TLS `[0,223)` and X.509 `[0,169)`). Their output hashes stay unchanged; full-source hashes must change even when only implementation bytes change. This does **not** approve the implementation changes or establish compatibility of the project's security overlays. FreeRTOS `task.h` retains its complete MIT notice at `[0,1455)`; only `SPDX-FileContributor: 2023-2024` becomes `2023-2025` within that excerpt. Its output hash changes from `8e3d9f79bbbdf6dfd6fbf132982a7ec3a45ff6dac92277034316efae4cccad84` to `e53c9bcefd4327c713696e870a3568a27cedf8833467b9cd9536cb2e09ab7ad8`. The remaining `task.h` changes correct stack-watermark documentation to bytes. The two package JSON diffs change only versions; SDK `version.txt` changes only `5.5.0` to `5.5.3`. All 24 remaining SDK inputs and three Xtensa license inputs match the old pins, including SDK/radio Apache texts, both Newlib documents, mixed argtable3, wpa_supplicant and GCC exception/GPL texts. An auxiliary installed-tree filename comparison found the same 45 SDK files whose basenames start with LICENSE, COPYING or NOTICE (case-insensitive), with no additions/removals. All 85 files under Xtensa `share/licenses/` have identical relative paths and hashes. This finite discovery check is not an exhaustive attribution or linked-object audit. Validation after the catalog edit: **36/36 notice fixture tests PASS** via `python3 -B tests/release_notices/run.py`; two fresh temporary actual bundles using the candidate roots above and the current project passed assembly, per-payload manifest SHA-256/size checks, exact file-list checks, and identical relative names/bytes across outputs. Each has **77 files / 4,433,930 bytes** (the unchanged total size does not imply unchanged contents). Both manifest SHA-256 values are `1fd0a69b82dc2632bab4ab6dda33675e7229c8d341c7fafb7158c1e8f2456a68`. Temporary bundles were removed after comparison. Project entries and managed snapshot pins were also asserted equal to the pre-migration Git catalog. These results are **notice assembly evidence only**, not legal clearance, corresponding-source delivery, radio-blob exception approval, vulnerability closure, firmware build validation or target acceptance. Existing pinned project provenance retains historical baseline wording; this addendum distinguishes the new SDK notice snapshot without silently rewriting/re-pinning that evidence. All separate distribution gates below remain open. ## Separate work before distribution 1. Resolve or obtain qualified review of radio-blob corresponding-source or exception basis for the actual linked firmware. Apache binary permissions alone do not settle this question. No legal clearance is claimed. 2. Resolve the wolfSSH package/header discrepancy and remaining icon derivative questions. [Exact retained SVG provenance](icon_provenance.md) is verified at official 7.4.47, commit `9e04201d4557e729822fb57f62a316c3dea1d4a8`: Google USB and Simran Wi-Fi. Full Apache text is retained; no NOTICE was found in that pinned distribution. Manual firmware bitmap derivation and the distinct mockup Wi-Fi path remain unresolved. No artwork was regenerated. 3. Audit the final application/bootloader/runtime object set and aggregation boundaries. Full SDK/toolchain redistribution needs broader nested/tool/test notice coverage; this is not an all-files license scanner. 4. Prepare and validate a separate, deliberately reviewed corresponding-source delivery. Preserve exact required sources and safe build/install inputs, asset preferred sources and scripts, original hash-matching overlay inputs, `tools/security_overrides.py`, `cmake/security_overrides.cmake`, root CMake integration, `cmake/wolf_crypto_policy.{cmake,h}`, and the complete `tools/wolfssh_order/` inputs/provenance/archives. Preserve upstream notices and generated dated change notices on eight C sources and one PUBLIC forced header. A stock vendor tarball or this notice bundle is not sufficient. Do not automatically archive the working repository or real configurations. 5. Assess GPLv3 source delivery/offer and User Product Installation Information. Protect device passwords, Wi-Fi credentials, private keys, verifiers, tickets, credential backups and flash/NVS images throughout that process. 6. Arrange and verify actual notice delivery with firmware, devices and browser assets. This command does not add a license endpoint, change browser assets, or prove that a recipient of a JS response or standalone device receives notices. Keep these gates open until the real delivery path is validated.