Skip to content

Add peer updater foundations - #67

Closed
theysayheygreg wants to merge 7 commits into
SteveEisner:mainfrom
theysayheygreg:contrib/p2p-updater
Closed

Add peer updater foundations#67
theysayheygreg wants to merge 7 commits into
SteveEisner:mainfrom
theysayheygreg:contrib/p2p-updater

Conversation

@theysayheygreg

@theysayheygreg theysayheygreg commented Aug 15, 2026

Copy link
Copy Markdown

The idea in plain language

This work has three parts.

1. A future way for Tubes to update each other

After the fleet is running the new firmware, one updated Tube can pass an approved update to one compatible Tube nearby. The receiver installs it, restarts, and proves it is healthy before the baton moves on. If nothing suitable appears for 60 seconds, propagation stops.

This PR builds the safe state and identity rules for that future path. It does not invent a packet ID while Steve's new protocol is still being defined.

2. A migration from the old fleet to the new one

The hundreds of existing Tubes do not understand the new targeting or baton rules yet. We will move them onto the bridge release with Easy Flash and the regular laptop-assisted OTA workflow they already support. We are not asking one Dig2Go to serve an arbitrary crowd of legacy devices, and we are not pretending the old broadcast command can target one Tube.

Easy Flash handles the first USB seed. The laptop handles the main legacy migration. The S3 updater is the on-site option for devices missed before a rave.

3. A cautious rollout

We start with Greg's four Tubes, including an interrupted transfer and retry. Then we test a small group before expanding to the larger fleet. A device only counts as updated when it restarts and returns healthy; powered-off, absent, or out-of-range devices remain unknown and can be handled in another pass.

The operator instruction stays simple:

Keep the Tubes you want to update plugged in and in range until the update finishes.

What this PR contains today

This is still a draft foundation, not a live end-to-end peer updater. It contains exact-target and artifact admission, inactive-slot geometry, image-source seams, one-sender/one-target session state, health proof, and the post-migration 60-second baton.

Still intentionally absent:

  • live HTTP endpoint registration;
  • receiver Update.begin() and write integration;
  • Steve's final target-addressed protocol seam;
  • forwarding or autonomous fleet updates;
  • S3 UI and Easy Flash product code.

Those boundaries are deliberate. The PR does not change the deployed 84-byte Tubes state frame or assign a competing action or packet ID.

Validation

  • npm ci && npm run build
  • npm test — 16 tests passed
  • test/tubes_mesh/run.sh — all focused mesh/updater contracts passed
  • test/tubes_upgrade/run.sh — upgrade/device-report compatibility passed
  • pio run -e esp32_quinled_dig2go_tubes — success; RAM 25.8%, flash 79.7%
  • jq -e . migration-fixtures/manifest.json
  • git diff --check

No physical device was contacted or flashed. No generated web headers are included.


Working-set coordination — 2026-08-14

This draft is one of three independent contribution rails based on Steve's main:

Shared rules:

  • Steve's packet, hardware-identity, and release-metadata contracts remain canonical.
  • Keep the deployed 84-byte Tubes v2 State frame unchanged during migration.
  • Distinguish Tubes protocol version, Tubes release, WLED base version, hardware target, and runtime profile.
  • Use broad firmware classes: Legacy (pre-v14), Current (v14/WLED 16), Next (v15/new protocol), and Unknown. Missing evidence remains Unknown.
  • v15 should advertise release/build identity and capabilities through Steve's additive versioned protocol—not a branch-local packet ID.
  • Firmware variants represent hardware/compiled boundaries only. Art, role, palette, tempo, pattern, spatial policy, and installation identity remain runtime configuration.
  • Any shared contract change is checked against all three drafts before one advances.

Current thinking

P2P owns peer update mechanics, not browser installation or S3 UI. Admission must know the exact hardware target, flash/partition/OTA geometry, release/build identity, image length, and hash before erase or Update.begin(). Start with one sender, one selected target, one image, one bounded lease, and health proof before handoff.

Legacy means pre-v14 and does not require distinguishing v12 from v13. Current v14 can use the existing device-report seam where positively observed. Next v15 should provide explicit release/build/capability identity through Steve's canonical new protocol. Unknown identity fails closed.

TODO

  • Consume Steve's canonical v15 identity/capability message; do not assign a local action or packet ID.
  • Extend exact-target admission with flash mode/size, partition identity, OTA geometry, release, image length, and hash.
  • Define route semantics so reports identify the target rather than a relay or root.
  • Register the bounded HTTP image source only during an authorized update lease.
  • Implement inactive-slot receiver writes with fail-before-erase checks and interruption recovery.
  • Prove reboot, expected release/hash, preserved configuration, mesh rejoin, and stability before baton handoff.
  • Trial sequentially on one authorized same-hardware sender/receiver pair before enabling propagation.

Centralized update contract progress — 2026-08-14

Commit 14279a4684dbc3a82a17ca4fb3a3f5773dc5438b adds the first bounded shared-contract foundation owned by PR #67.

  • Added the hand-edited internal/build source contracts/update/update-contract.json (schema v1). It is not a wire packet.
  • Added canonical Legacy / Current / Next / Unknown classification; incomplete or silent identity remains Unknown and fails closed.
  • Pinned proven Dig2Go target metadata plus the exact current USB merged and OTA application artifacts, byte lengths, offsets, component bounds, and SHA-256 values.
  • Added proven Waveshare S3 target/partition metadata only. No S3 artifact, build identity, or hardware-acceptance claim is present.
  • Added deterministic validator/generators and committed JavaScript and compact C++ projections. Firmware consumes compile-time constants and does not parse JSON.
  • Kept the sequential one-sender/one-target/artifact/lease/health state machine and disabled forwarding semantics. No receiver write path or live server registration was added.
  • Froze tests around the deployed 84-byte message, two-byte Action, and existing command/action registry; no packet/action ID was added.

Validation: npm ci && npm run build; npm test (22 passed); test/tubes_mesh/run.sh; test/tubes_upgrade/run.sh; contract validation and reproducibility checks; pio run -e esp32_quinled_dig2go_tubes (RAM 25.8%, flash 79.7%); git diff --check. No device was contacted or flashed.

Centralized-contract TODO

  • Migrate PR Add participant-safe Easy Flash recovery #65 Easy Flash to the generated JS projection, preserving its browser/install/configuration responsibilities.
  • Migrate PR Add Waveshare S3 Tubes remote shell #66 Waveshare S3 to generated target/vocabulary constants while keeping Updater read-only.
  • Consume Steve’s additive v15 identity/capability report when defined; do not add a branch-local packet/action ID.
  • Add an S3 artifact only after exact release/build bytes, length, SHA-256, and target identity are proven.
  • Add lease-scoped serving, inactive-slot receiver writes, and real health evidence only in later authorized slices.

Independent-review hardening — 2026-08-14

Follow-up commit 3525f560c894deb0fe3eca2f4389aa9154e43332 closes the Important findings from the independent contract review without adding a live updater or changing the Tubes wire protocol.

  • The generated firmware admission projection now carries both OTA slots with an explicit count, offsets, sizes, and inactive-slot admissibility; generated JS/C++ parity and firmware tests cover both slots.
  • Validation now parses the referenced Dig2Go and S3 partition CSVs fail-closed and proves OTA IDs/geometry, alignment, non-overlap, flash bounds, and declared flash-size coherence.
  • Artifact kind/transport vocabularies and combinations are closed, identifiers and SHA-256 values are validated, merged components and target-wide bounds are enforced, and numeric C++ values are unique and in range.
  • C++ now has generated canonical artifact discriminators/records. Session admission rejects manually constructed Current artifacts that do not match the selected generated record. The legacy 32-bit releaseHash remains compatibility evidence only, not full release identity.
  • The generated C++ is explicitly a minimal admission projection, with omitted host/UI fields tested. Hardware-family/device-report evidence remains a separate fail-closed gate.
  • Update-state numeric values and the existing one-sender/one-target/one-artifact/lease/health semantics remain unchanged. No packet/action ID, wire-layout change, S3 artifact, receiver write path, live server registration, device contact, or flash was added.

Validation: contract validator and generator check; npm ci && npm run build; npm test (26 passed); test/tubes_mesh/run.sh; test/tubes_upgrade/run.sh; pio run -e esp32_quinled_dig2go_tubes (RAM 25.8%, flash 79.7%); git diff --check.

S3 hardware acceptance remains unproven and no S3 artifact exists. The PR remains draft; no reviewers or merge were requested.


Inactive-slot destination geometry correction — 2026-08-14

Commit e5ce2666 closes the final independent-review blocker without adding any receiver write path, packet/action, wire-layout, or forwarding behavior.

  • OTA application artifacts now describe slot-independent source bytes. The canonical Dig2Go OTA artifact keeps one artifact ID/full SHA and records its build component offset as buildOffset; it no longer presents 0x10000 as a receiver destination.
  • USB merged images retain writeOffset: 0 and absolute component offsets. Generated JavaScript and C++ documentation make those transport-specific semantics explicit; the minimal firmware artifact projection omits source/build offsets.
  • Session admission now requires an explicit destination slot index, offset, and size derived from the receiver's selected inactiveOtaSlot. All three values must exactly match the selected canonical slot, and the image length must fit.
  • Tests prove the same canonical OTA bytes are admitted to either inactive slot and reject mismatched offsets, invalid slot indexes, and undersized destinations.
  • Full SHA/artifact-ID admission, frozen update-state values, Unknown fail-closed behavior, and the draft boundary remain unchanged.

Validation: contract validator; generator --check; npm ci && npm run build; npm test (26 passed); test/tubes_mesh/run.sh; test/tubes_upgrade/run.sh; uvx platformio run -e esp32_quinled_dig2go_tubes (RAM 25.8%, flash 79.7%); git diff --check. No adapter branch or physical device was contacted. The PR remains draft.


Recursive-review receiver evidence correction — 2026-08-14

Commit 84801e8e closes the recursive review findings without adding a live updater or changing the Tubes wire protocol.

  • Canonical target projection is now explicitly static and receiver-incomplete. Receiver construction requires a separately verified inactive-slot index, validates it, and clears output on failure; no slot is inferred.
  • Artifact targets retain static hardware geometry, while receiver and health evidence must use the verified receiver constructor. Tests prove static targets fail closed and explicit slots 0/1 construct admissible receiver targets.
  • The running-partition source is explicitly named and bound to the only registered running artifact, Dig2Go v14; no generic API silently maps other targets to that artifact.
  • Direct <string.h> include hygiene, AI markers, intent comments, and compatibility documentation were audited. Operational session ownership, lease-scoped HTTP endpoint lifetime, receiver writes, forwarding, and any new wire seam remain intentionally absent pending protocol authorization.

Validation: contract validator; generator --check; npm ci && npm run build; npm test (26 passed); test/tubes_mesh/run.sh; test/tubes_upgrade/run.sh; uvx platformio run -e esp32_quinled_dig2go_tubes (RAM 25.8%, flash 79.7%); complete c6522ace...84801e8e style/security/compatibility review; git diff --check. No adapter branch or physical device was contacted. No reviewers or merge were requested; the PR remains draft.

Recursive review and adapter checkpoint — 2026-08-14

Canonical contract foundation is reviewed at 84801e8e.

  • Static target facts are separate from verified receiver inactive-slot evidence.
  • Artifact identity is registry-backed with exact full SHA-256; build offset is separate from receiver destination geometry.
  • The running-image source is explicitly Dig2Go-v14-only.
  • Generated JS/C++ parity, partition semantics, both OTA slots, fail-closed Unknown, and unchanged wire/action layouts are covered.
  • No live endpoint, receiver write path, or new packet/action ID exists.
  • Disposable Easy Flash and S3 integration worktrees proved both adapters against this contract; adapter reconstruction remains downstream of acceptance of this foundation.

Two-stage propagation checkpoint — 2026-08-16

Commit be82a978 adds the bounded post-migration baton: one sender/target, frozen canonical artifact, 60-second idle window, one retry from byte zero, typed artifact/session-bound health proof before handoff, and an artifact-bound one-shot boot marker. It defines no wire IDs, keeps forwarding transport-owned, and never claims absent fleet devices are current. The Dig2Go-only laptop batch now skips other profiles before upload.

Validation: npm ci (success); npm test (26 passed); node --test tools/update-contract/update-contract.test.mjs (10 passed); ./test/tubes_mesh/run.sh (all focused tests passed); ./test/tubes_upgrade/batch_upgrade_workflow_test.sh (all scenarios passed); pio run -e esp32_quinled_dig2go_tubes (success, RAM 25.8%, flash 79.7%); git diff --check. No device/network OTA/flash was performed. PR remains draft.

Independent-review blocker closure — 2026-08-16

Commit 8e4e035b closes the three baton review blockers without changing the post-migration/non-wire boundary:

  • Idle expiry is wrap-safe and applies across Looking, Transferring, and AwaitingHealth; explicit valid activity refreshes the 60-second deadline.
  • Progress is bounded by the frozen artifact length, and health wait requires exact completion length.
  • Retry remains receiver-bound and single-use; newly generated C++ blocks in the baton and test carry the required AI attribution markers, with the test expanded for readability.

Validation: focused tubes_mesh, tubes_upgrade, batch workflow, npm test (26), update-contract tests (10), Dig2Go PlatformIO build (RAM 25.8%, flash 79.6%), and git diff --check. No device/network OTA/flash was performed. The PR remains draft.

@theysayheygreg

Copy link
Copy Markdown
Author

Bounded correction receipt (e5ce2666): separated OTA source identity from inactive-slot destination geometry; exact destination index/offset/size is now required at admission, while one canonical OTA artifact/full SHA serves either slot. Validator, generator check, npm test (26), tubes_mesh, tubes_upgrade, Dig2Go firmware build (25.8% RAM / 79.7% flash), and diff check passed. No wire/action/write-path changes, adapter edits, or device contact. Draft retained.

@theysayheygreg

Copy link
Copy Markdown
Author

Bounded recursive-review receipt (84801e8e): static canonical targets are now receiver-incomplete; explicit validated runtime inactive-slot evidence is required to construct an admissible receiver, and failed construction clears output. The running source is explicitly Dig2Go v14-only, direct string include and AI/comment hygiene are corrected, and docs retain the intentionally absent session/HTTP lifetime and receiver-write integration. Contract validation/generator check, npm test (26), tubes_mesh, tubes_upgrade, Dig2Go build (25.8% RAM / 79.7% flash), complete base-diff review, and diff check passed. No wire/action/write-path/forwarding changes, adapter edits, device contact, reviewers, or merge. Draft retained.

@theysayheygreg

Copy link
Copy Markdown
Author

Foundation closure is independently green and remains within the draft PR boundary: exact target/artifact contracts, deterministic JS/C++ projections, bounded image/HTTP response sources, one-sender/one-target/one-artifact lease and health state foundations, migration evidence, and forwarding disabled. No live endpoint, receiver write path, or packet/action ID is present.

The next implementation slice is blocked on canonical direction rather than branch code: what additive identity/capability report should authorize a peer update? The receiver admission contract needs exact target-versus-relay identity, flash mode/size, partition and inactive-OTA geometry, release/build identity, artifact length/hash, and capability evidence. The current 32-bit release hash is compatibility evidence but not sufficient artifact identity.

Could you confirm whether this foundation-only boundary is acceptable for #67 and point us at the canonical report/transport seam you want the next slice to consume? We will not invent a branch-local packet or action ID.

@theysayheygreg

Copy link
Copy Markdown
Author

Superseded by clean draft #69, rebuilt from current Steve main as one normal commit with the complete intended net change plus independent-review attribution/documentation cleanup. This PR is being closed for history; its branch is retained and was not force-pushed or deleted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant