docs(#1064): feature 1061 PLAN and OUTCOMES

The plan of record (four approved parts, the Epic B re-scope) and the
outcomes: what each Epic shipped, the slot-level ingress gap the
integration tests exposed, the positive and negative test evidence, and
what stays open until the owner's hardware pass.

Issue: #1064

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pull/1101/head
diffwireauto 1 week ago committed by Strycher
parent 06dd15fa9b
commit 6e8d1b8fc4

@ -0,0 +1,37 @@
# Feature #1061 channel slot resync truth: OUTCOMES
Status: BUILT and machine-verified; awaiting the owner's hardware test, then PRs. Branch stack: `feat/1062-channel-sync-truth` (Epic A) ← `feat/1063-message-state-hardening` (Epics B + C).
## What was built
| Epic | Commit | Content |
|---|---|---|
| A #1062 | `c2f599e3` | fail-closed atomic sync (no backfill, unconfirmed slots tracked + bannered with Retry, cache kept on holes), ingress barrier (messages held unbounded, raw sniffs bounded 64, flush post-sync), per-slot PSK identity diff (UI notice + warn log), unread preserved only on same PSK, fail-closed `_findChannelByIndex`, notification suppression for unconfirmed slots, self-retry on failed live sync |
| B #1063 | `0d19c3c7` | in-memory conversation dropped + reloaded under the new PSK on slot identity change; setChannel reassignment clear narrowed to legacy slot keys (PSK history survives re-add; deleteChannel keeps the full clear); write-side index fallback escalated to an error naming the orphaning consequence; stale #193-era comments corrected, setChannel/resolver interplay documented |
| C #1064 | `0f8fcce1` | the three integration scenarios below, the notify-decision test probe, store wiring test hook, and one fix the tests exposed (next section) |
## Gap found by the integration tests (fixed in `0f8fcce1`)
Epic A's flush documented that a held frame for a still-unconfirmed slot re-holds, but the handler only gated on the list-level barrier: after a PARTIAL sync completed, a held message for an unconfirmed slot would have filed by raw index against an unknown identity and persisted under the legacy index key. `_handleIncomingChannelMessage` now re-holds such frames until a sync confirms the slot. This is the "prove the check can fail" discipline paying for itself; the flaky-sync scenario below fails without the fix.
## Evidence
Machine verification, all on the stacked branch:
- `flutter analyze` clean on every touched file.
- Unit tests: 7 sync-truth tests (`test/connector/channel_sync_truth_test.dart`), 5 store identity tests (`test/storage/channel_message_store_identity_test.dart`).
- Integration tests (`test/connector/channel_slot_resync_integration_test.dart`):
1. **#1060 bug validation (wipe + re-import, all three slots shuffled):** post-sync list matches the device exactly; all three identity changes surfaced; the old private conversation does not stay behind under #wardriving; a #test message files under #test and is notify-evaluated against #test's PSK (mode `all`); a #wardriving message is evaluated against #wardriving's PSK and its OFF mode rides along to the new slot. The reported symptoms (muted channel notifying, #test rendering in the private channel) cannot reproduce.
2. **Manual device-side reorder:** a message persisted under Alpha's PSK follows Alpha to its new slot after the re-sync; nothing renders under Beta.
3. **Flaky sync mid-reconnect:** a message for a never-confirmed slot is held, not filed by raw index, and no notify decision is made for it; after a confirming re-sync it files once, against the right PSK and mode.
- **Negative evidence:** with Epic B's drop/reload and the slot-level re-hold reverted in the working tree, all three scenarios fail (`+0 -3`) on exactly the guarded assertions; restored, all pass. The checks can fail.
- Full suite at Epic B: 1791 passed / 1 skipped, exit 0. Full suite at Epic C: 1794 passed / 1 skipped, exit 0.
## Decisions logged
- Epic B re-scope approved by the owner 2026-11-03 ("Agree, proceed"); storage was already PSK-keyed since #194, so no migration was needed.
- Already-miskeyed rows from the #1060 window stay as-is: not mechanically separable, and the owner's standing history-loss bar is deliberately low. Say the word and a per-radio clear can be added instead.
- setChannel reassignment now PRESERVES the outgoing occupant's PSK-keyed history (only legacy slot keys are purged). Re-adding a channel later restores its messages; this strengthens #194's intent and the diff is one call site, flagged for owner review on the Epic B PR.
## Open until the owner closes them
- Hardware test of the combined stack (the #1060 recovery sequence on the T1000e: wipe, re-import, reconnect, one sync).
- The user-reported problem the owner logged this feature against: its one-liner / issue number is still pending and will be linked under #1063/#1064 when provided.
- PRs + merges after the hardware pass (stacked: A into dev, B+C into A's branch or rebased per owner preference).
- #1060 closes only after the owner confirms on hardware.
Agent: TurquoiseBridge (session 82d77b2f)

@ -0,0 +1,39 @@
# Feature PLAN: channel slot resync truth (#1060 class)
| | |
|---|---|
| **Feature** | [#1061](https://github.com/OffbandMesh/meshcore-client/issues/1061) · Citadel `meshcore-open-rwr` |
| **Fixes** | [#1060](https://github.com/OffbandMesh/meshcore-client/issues/1060) (P0): wipe + config re-import shuffled channel slots; notifications fired for a muted channel and messages rendered inside the wrong conversation |
| **Owner** | @Strycher |
| **Approved** | 2026-11-02 in chat: "Agree with all 4, don't hold on 4, do it now." Epic B re-scope approved 2026-11-03: "Agree, proceed." |
## At a glance
- **What:** make the device's channel list the only truth the client ever acts on, and make every piece of per-slot client state follow the channel's PSK identity instead of its reusable slot index.
- **Why:** #1060. The radio is authoritative for slot assignments; after a wipe + re-import the same channels can sit in different slots, and any client state keyed by slot silently attaches to the wrong channel.
- **Done when:** the integration scenarios pass (positive and negative), the #1060 reporter's sequence cannot recur, and the owner approves the OUTCOMES.
## 1. Diagnosis (#1060, verified in code)
Three routes let a stale slot map poison live behavior:
1. **Backfill:** the channel sync restored unanswered slots from `_previousChannelsCache`, so a shuffled device list came back part-fresh, part-stale.
2. **Index filing:** raw-RX and message ingress filed by slot index through the stale list.
3. **Poisoned resolver:** notify modes and persisted history are PSK-keyed (#839, #194), but both resolve PSKs through `_findChannelByIndex` - which consulted the stale list, so correct identity keying was fed wrong identities.
## 2. The four approved parts → Epics
| Part | Epic | Scope |
|---|---|---|
| 1. Fail-closed, atomic channel sync | A [#1062](https://github.com/OffbandMesh/meshcore-client/issues/1062) | never backfill an unanswered slot; unconfirmed slots tracked + surfaced (banner w/ Retry); cache not overwritten on holes; self-retry on a failed live sync |
| 2. Ingress barrier | A #1062 | no live channel frame files until THIS connection confirms the list; pull-based message frames held unbounded (the radio already popped them), raw sniffs bounded at 64; held frames flush post-sync |
| 3. Per-slot identity diff | A #1062 | PSK diff per slot against the last truth; identity changes surfaced in UI + log; unread preserved only on same PSK; unconfirmed slots fail closed in `_findChannelByIndex` and suppress notifications |
| 4. Identity-keyed message state | B [#1063](https://github.com/OffbandMesh/meshcore-client/issues/1063) | RE-SCOPED (see below) |
| Validation | C [#1064](https://github.com/OffbandMesh/meshcore-client/issues/1064) | end-to-end scenarios, the #1060 bug-validation test, this folder's OUTCOMES.md |
## 3. Epic B re-scope (approved 2026-11-03)
Part 4 was planned as "re-key channel_message_store by PSK with migration" from a stale #193-era comment. The store has been PSK-keyed since #194; the incident's cross-wiring came from the poisoned resolver (route 3), which Epic A fixes. Epic B therefore shrank to:
1. drop a slot's in-memory `_channelMessages` entry on identity change and reload it under the new identity's PSK;
2. harden the store's slot-index write fallback (error log naming the orphaning consequence);
3. narrow setChannel's reassignment clear to the LEGACY slot keys only, so the outgoing occupant's PSK-keyed history survives and returns if the channel is re-added;
4. correct the stale #193-era comments and document the setChannel/resolver interplay.
Already-miskeyed rows from the #1060 window stay as-is (not mechanically separable; owner's standing low history-loss bar).
Agent: TurquoiseBridge (session 82d77b2f)
Loading…
Cancel
Save

Powered by TurnKey Linux.