chore: dev-stack switches, shared e2e harness, and desktop shell rules
- `LIVE_RELOAD=false npm run dev` keeps the renderer alive across a machine suspend; the reload client otherwise destroys the session under test. - `dev-peer.sh` plus a separate userdata dir runs a second local peer. - `tools/voice-probe.js` samples peer state and RTP counters from a live window, persisting to localStorage so a renderer reload cannot erase it. - e2e helpers for voice pairs, peer-role election, and a TURN relay. - Electron single-instance and dev-client-load decisions move into rules files with colocated specs.
This commit is contained in:
@@ -0,0 +1,485 @@
|
||||
# Validated emergency findings — Fable 5 implementation handoff
|
||||
|
||||
**Date:** 2026-08-12
|
||||
**Scope:** `emergency-fix/`, `emergency-fix/e2e-failures/`, Angular client, targeted Electron/auth bridge, signaling server, and relevant Playwright tests.
|
||||
**Method:** Read-only code and test audit. No product code was changed and the E2E suite was not rerun for this report.
|
||||
|
||||
## Executive verdict
|
||||
|
||||
The emergency pack is directionally correct, but it mixes active defects, already-shipped mitigations, hypotheses, and documentation debt. The current app is **not yet demonstrated to be Discord-reliable** despite a green `65/65` Playwright run.
|
||||
|
||||
The highest-confidence failure chain is:
|
||||
|
||||
1. Restored sessions do not ensure a provision secret.
|
||||
2. A foreign signal credential cannot be created.
|
||||
3. The user is sent to authorize login or never identifies/joins.
|
||||
4. Presence is missing, so WebRTC discovery and WebSocket chat fallback fail.
|
||||
5. When peers do connect, negotiation still compares home ids against foreign actor ids.
|
||||
6. A data-channel failure tears down the entire peer connection, including media.
|
||||
7. Reconnect attempts can be exhausted while signaling is offline, then stop silently.
|
||||
|
||||
Independent P0/P1 defects also exist in direct-message identity, call delivery, and message synchronization:
|
||||
|
||||
- inbound DMs trust an actor-id conversation id and can fork a second local thread;
|
||||
- an outbound call enters local voice state before delivery is known, and delivery failure is ignored;
|
||||
- a timed-out inventory cycle is marked “clean” without proving convergence, delaying the next poll for 15 minutes.
|
||||
|
||||
## Acceptance target
|
||||
|
||||
This emergency is not complete until two users with different home signal servers can:
|
||||
|
||||
- join the same foreign-hosted community without another login prompt;
|
||||
- see each other online and in the correct voice channel;
|
||||
- exchange live and catch-up text messages in both directions;
|
||||
- place and answer a private call with bidirectional audio;
|
||||
- keep one DM thread for the same human across actor aliases;
|
||||
- transfer a file and verify received bytes;
|
||||
- use camera and screen share while voice remains healthy;
|
||||
- recover from a signal-server restart and a data-channel failure, or receive a visible retry action within one minute.
|
||||
|
||||
## Finding classification
|
||||
|
||||
- **Confirmed:** direct current-code mechanism.
|
||||
- **Partial:** some mechanism exists, but the report overstates or misattributes it.
|
||||
- **Mitigated:** code already contains the reported fix; retain as a regression test.
|
||||
- **Unproven:** plausible but needs reproduction evidence.
|
||||
|
||||
## Confirmed findings
|
||||
|
||||
### F1 — Restored sessions can lack a provision secret
|
||||
|
||||
**Severity:** P0
|
||||
**User symptom:** A logged-in user opens a foreign room or invite and is shown `/login?mode=authorize`, or remains invisible on that signal server.
|
||||
|
||||
`loadCurrentUserSuccess` migrates the home credential and immediately attempts foreign provisioning:
|
||||
|
||||
- [`users.effects.ts`](../toju-app/src/app/store/users/users.effects.ts), lines 199–216
|
||||
|
||||
The restore path does **not** call `ensureHomeProvisionSecret`. That call currently exists only when storage is prepared from a fresh login response:
|
||||
|
||||
- [`users.effects.ts`](../toju-app/src/app/store/users/users.effects.ts), lines 288–306
|
||||
|
||||
Provisioning exits when the secret is absent:
|
||||
|
||||
- [`signal-server-auth.service.ts`](../toju-app/src/app/domains/authentication/application/services/signal-server-auth.service.ts), lines 154–168
|
||||
|
||||
The web store uses `sessionStorage`, so a new tab/session can lose the secret even while other persisted identity data remains. Electron storage is durable, but old installs or cleared user data can still have no secret.
|
||||
|
||||
**Required fix:** ensure or deliberately recover the home provision secret before any restore-time or join-time foreign provision. Do not silently swallow restore provisioning errors.
|
||||
|
||||
**Important design caveat:** generating a new secret cannot authenticate foreign accounts created with a lost old secret. The implementation must define recovery for that case instead of looping register/login forever.
|
||||
|
||||
### F2 — Missing foreign credentials block presence and chat fallback
|
||||
|
||||
**Severity:** P0
|
||||
**User symptom:** The room opens locally, but other users do not see the user; chat is sender-only; voice is empty.
|
||||
|
||||
The room connection refuses to proceed without a credential. The signaling transport skips identify if credentials cannot be resolved. The server rejects all non-keepalive, non-identify messages from unauthenticated connections.
|
||||
|
||||
Live WebSocket channel chat is also membership-gated:
|
||||
|
||||
- [`server/src/websocket/handler.ts`](../server/src/websocket/handler.ts), lines 539–552
|
||||
|
||||
Therefore auth/presence must be repaired before treating chat or voice as isolated transport bugs.
|
||||
|
||||
**Required fix:** prove the complete sequence `credential -> identify(actor id) -> join_server -> mutual roster`, including reconnect.
|
||||
|
||||
### F3 — WebRTC initiator and glare logic use the wrong identity space
|
||||
|
||||
**Severity:** P0
|
||||
**User symptom:** Cross-home peers connect intermittently, both offer, both wait, or voice remains one-way/connecting.
|
||||
|
||||
Per-signal credentials exist through `getIdentifyCredentialsForSignalUrl`, but the generic getter prefers the home credential:
|
||||
|
||||
- [`signaling-transport-handler.ts`](../toju-app/src/app/infrastructure/realtime/signaling/signaling-transport-handler.ts), lines 35–71
|
||||
|
||||
The peer manager and incoming signaling handler receive the generic home-biased getter:
|
||||
|
||||
- [`realtime-session.service.ts`](../toju-app/src/app/infrastructure/realtime/realtime-session.service.ts), lines 198–208 and 232–234
|
||||
|
||||
Offer collision handling compares that id with the remote signal actor id:
|
||||
|
||||
- [`negotiation.ts`](../toju-app/src/app/infrastructure/realtime/peer-connection-manager/connection/negotiation.ts), lines 106–126
|
||||
|
||||
Initiator election performs the same lexical comparison:
|
||||
|
||||
- [`signaling-message-handler.ts`](../toju-app/src/app/infrastructure/realtime/signaling/signaling-message-handler.ts), lines 528–535
|
||||
|
||||
**Required fix:** carry connection/signal scope into initiator election, polite-peer handling, reconnect, and P2P voice-state payloads. Both peers must compare actor ids from the same signal URL.
|
||||
|
||||
### F4 — Reconnect budget is consumed while signaling is unavailable
|
||||
|
||||
**Severity:** P0
|
||||
**User symptom:** A signal outage lasts about one minute; signaling returns, but the peer mesh does not retry and no error is shown.
|
||||
|
||||
Each timer tick increments `reconnectAttempts` before checking signaling connectivity. At the limit, the timer and tracker are deleted:
|
||||
|
||||
- [`peer-recovery.ts`](../toju-app/src/app/infrastructure/realtime/peer-connection-manager/recovery/peer-recovery.ts), lines 282–321
|
||||
|
||||
This means the client can spend all 12 attempts doing no actual reconnection work.
|
||||
|
||||
**Required fix:** do not consume an attempt until an offer/reconnect is actually attempted. On exhaustion, publish an explicit failed state with a user-visible Retry action. A later presence or signaling recovery event must be able to re-arm repair.
|
||||
|
||||
### F5 — A closed data channel tears down media
|
||||
|
||||
**Severity:** P0
|
||||
**User symptom:** Voice, camera, or screen share drops when chat/file control transport fails.
|
||||
|
||||
The active recovery path calls `removePeer`, then creates a new peer transport:
|
||||
|
||||
- [`peer-recovery.ts`](../toju-app/src/app/infrastructure/realtime/peer-connection-manager/recovery/peer-recovery.ts), lines 208–235
|
||||
|
||||
The realtime README claims the initiator creates a replacement data channel on the existing `RTCPeerConnection`:
|
||||
|
||||
- [`realtime/README.md`](../toju-app/src/app/infrastructure/realtime/README.md), line 263
|
||||
|
||||
That claim is false for the current recovery path. `replaceDataChannel` exists but is not the implemented repair.
|
||||
|
||||
**Required decision:**
|
||||
|
||||
- **Recommended emergency step:** keep full rebuild initially, but force resync, preserve/reapply media state, expose progress/failure, and fix the docs.
|
||||
- **Follow-up reliability step:** implement a true soft data-channel replacement on a healthy peer connection, with full rebuild as fallback.
|
||||
|
||||
### F6 — Direct messages can fork by home id versus actor id
|
||||
|
||||
**Severity:** P0
|
||||
**User symptom:** Two threads represent the same person; a reply lands in the thread the sender is not viewing.
|
||||
|
||||
The pair id is a literal sorted pair of supplied ids:
|
||||
|
||||
- [`direct-message.logic.ts`](../toju-app/src/app/domains/direct-message/domain/logic/direct-message.logic.ts), lines 17–21
|
||||
|
||||
Inbound handling trusts `payload.message.conversationId` before deriving a local id and performs no alias merge:
|
||||
|
||||
- [`direct-message.service.ts`](../toju-app/src/app/domains/direct-message/application/services/direct-message.service.ts), lines 511–541
|
||||
|
||||
The lesson symbols `resolveDirectConversationId` and `mergeAliasDirectConversations` do not exist in the product tree.
|
||||
|
||||
**Required fix:** define a canonical local human identity, remap inbound actor ids, merge duplicate conversations transactionally, and preserve messages/unread/status ordering.
|
||||
|
||||
### F7 — Outbound private calls can fail silently after local join
|
||||
|
||||
**Severity:** P0
|
||||
**User symptom:** Caller sees “In Voice”; callee never rings.
|
||||
|
||||
`startCall` joins the call locally before sending the ring:
|
||||
|
||||
- [`direct-call.service.ts`](../toju-app/src/app/domains/direct-call/application/services/direct-call.service.ts), lines 220–235
|
||||
|
||||
`sendCallEvent` does not await or inspect delivery:
|
||||
|
||||
- [`direct-call.service.ts`](../toju-app/src/app/domains/direct-call/application/services/direct-call.service.ts), lines 759–773
|
||||
|
||||
Candidate expansion in `PeerDeliveryService` helps when a matching NgRx user alias exists, but there is no complete credential/presence-based routable-id selection and no unreachable UX.
|
||||
|
||||
**Required fix:** resolve the connected actor id before committing the caller to the session, return a delivery result, and transition to `recipientUnreachable`/Retry when no route accepts the ring.
|
||||
|
||||
### F8 — Message synchronization can falsely become “clean”
|
||||
|
||||
**Severity:** P1, potentially P0 for long missing-history windows
|
||||
**User symptom:** Live chat works, but old messages remain missing for up to 15 minutes.
|
||||
|
||||
Periodic sync sends inventory requests and dispatches `startSync`:
|
||||
|
||||
- [`messages-sync.effects.ts`](../toju-app/src/app/store/messages/messages-sync.effects.ts), lines 166–213
|
||||
|
||||
Five seconds later, if the reducer still says syncing, the effect unconditionally sets `lastSyncClean = true` and dispatches `syncComplete`:
|
||||
|
||||
- [`messages-sync.effects.ts`](../toju-app/src/app/store/messages/messages-sync.effects.ts), lines 215–232
|
||||
|
||||
No evidence of an empty inventory result is required. The next interval can therefore change from 10 seconds to 15 minutes after timeout rather than convergence.
|
||||
|
||||
**Required fix:** model an inventory round explicitly. Mark clean only when all expected peers have replied and no missing ids remain. Timeouts and newly received messages must remain dirty.
|
||||
|
||||
### F9 — Live chat fallback and history repair are separate
|
||||
|
||||
**Severity:** P1
|
||||
**User symptom:** New text arrives while older text, edits, files, and emoji remain absent.
|
||||
|
||||
The WebSocket `chat_message` path handles live channel messages for joined members. Inventory and sync batches are data-channel-only. `message-revision` has no equivalent WebSocket fallback.
|
||||
|
||||
`onPeerConnected` kicks inventory:
|
||||
|
||||
- [`messages-sync.effects.ts`](../toju-app/src/app/store/messages/messages-sync.effects.ts), lines 64–94
|
||||
|
||||
That signal can occur at peer-connection state `connected` before the data channel is open; a second data-channel-open signal usually retries, but there is no explicit durable “control channel reopened and inventory completed” state.
|
||||
|
||||
**Required fix:** trigger and track reconciliation from data-channel open, not only generic peer connected. Queue failed inventory sends and retry on channel open.
|
||||
|
||||
### F10 — STUN-only defaults cannot guarantee Discord-like reachability
|
||||
|
||||
**Severity:** P1 product limitation
|
||||
**User symptom:** Voice works on friendly networks but fails on symmetric NAT, enterprise Wi-Fi, carrier networks, or restrictive firewalls.
|
||||
|
||||
TURN can be configured by the user, but no managed/default TURN service is supplied. Localhost E2E cannot validate NAT traversal.
|
||||
|
||||
**Required product decision:** operate a credentialed TURN service with expiry/abuse controls, or explicitly reject the requirement that voice “always works.” Documentation alone does not satisfy the stated target.
|
||||
|
||||
## Claims that should not be treated as active defects
|
||||
|
||||
### Identify cache fallback — mitigated
|
||||
|
||||
`getIdentifyCredentialsForSignalUrl` falls back to the credential store when the per-URL cache is empty:
|
||||
|
||||
- [`signaling-transport-handler.ts`](../toju-app/src/app/infrastructure/realtime/signaling/signaling-transport-handler.ts), lines 47–71
|
||||
|
||||
Keep this as a regression invariant. Add a focused spec; do not rewrite this path without a failing reproduction.
|
||||
|
||||
### Join-before-identify race — substantially mitigated
|
||||
|
||||
Client reconnect sends identify before rejoin, and the server serializes message handling per connection. The remaining P0 case is missing credentials, not evidence that server serialization is currently broken.
|
||||
|
||||
### Shared `clientInstanceId` across tabs — fixed
|
||||
|
||||
Current code uses `sessionStorage` and clears the legacy local-storage value. Retain the existing regression test.
|
||||
|
||||
### Multi-signal `user_left` teardown — mitigated
|
||||
|
||||
Current client/server paths carry server membership information and preserve peers still shared elsewhere. This needs multi-signal integration coverage, but the pack should not call it a confirmed current failure.
|
||||
|
||||
### Voice allow-list alias miss — partial
|
||||
|
||||
The voice playback service already collects `id`, `oderId`, and `peerId`. More likely active failures are stale/missing `voiceState`, a peer key absent from all three aliases, or the broader home/actor negotiation mismatch. Instrument before changing routing filters.
|
||||
|
||||
### “No retry after abandon” — overstated
|
||||
|
||||
Presence fallback timers can create a later offer. The confirmed defect is that the explicit reconnect tracker is silently discarded and signaling-down ticks consume its budget.
|
||||
|
||||
### Attachment announce/message race — fixed, retain
|
||||
|
||||
The incoming message handler re-queues auto-download after binding message to room, with unit coverage. Add reorder/large-file E2E rather than reimplementing the fix.
|
||||
|
||||
## E2E baseline assessment
|
||||
|
||||
The archived run is internally consistent:
|
||||
|
||||
- 65 passed
|
||||
- 0 failed
|
||||
- 0 flaky
|
||||
- 0 skipped
|
||||
- about 13.4 minutes
|
||||
|
||||
Sources:
|
||||
|
||||
- [`e2e-failures/README.md`](e2e-failures/README.md)
|
||||
- [`e2e-failures/00-summary.md`](e2e-failures/00-summary.md)
|
||||
- `e2e-failures/parsed-report.json`
|
||||
- `e2e-failures/full-run.log`
|
||||
|
||||
### What it proves
|
||||
|
||||
- Scripted flows passed once in Chromium with retries disabled.
|
||||
- The Angular development server, ephemeral localhost signaling servers, fake mic/camera, and synthetic screen capture can support the covered scenarios.
|
||||
- Strong existing scenarios include two-user voice, eight-user local multi-signal voice, synthetic data-channel recovery, direct-call answer/audio on a same-route setup, chat sync, and several attachment regressions.
|
||||
|
||||
### What it does not prove
|
||||
|
||||
The Playwright configuration runs only Chromium against `ng serve`:
|
||||
|
||||
- [`e2e/playwright.config.ts`](../e2e/playwright.config.ts), lines 3–39
|
||||
|
||||
It does not run Electron, a packaged production build, real desktop safeStorage/SQLite behavior, real screen capture, real TURN/NAT, sleep/wake, or a production signal deployment.
|
||||
|
||||
The cross-signal auth test adds the second endpoint manually before checking provision:
|
||||
|
||||
- [`multi-signal-server-auth.spec.ts`](../e2e/tests/auth/multi-signal-server-auth.spec.ts), lines 20–70
|
||||
|
||||
It does not cover restore with a missing secret or joining an unknown foreign invite.
|
||||
|
||||
The cross-signal call test proves only that the callee sees a ring:
|
||||
|
||||
- [`dm-header-call-ring.spec.ts`](../e2e/tests/voice/dm-header-call-ring.spec.ts), lines 92–184
|
||||
|
||||
It does not answer the call, verify audio, exercise people-card home-id routing, or prove one DM thread.
|
||||
|
||||
The offline DM test proves only local `QUEUED` state:
|
||||
|
||||
- [`dm-flow.spec.ts`](../e2e/tests/chat/dm-flow.spec.ts), lines 15–36
|
||||
|
||||
It never reconnects or proves recipient delivery.
|
||||
|
||||
The mesh helper permits fewer than the calculated expected connections:
|
||||
|
||||
- [`signal-manager.ts`](../e2e/helpers/signal-manager.ts), lines 28–50
|
||||
|
||||
This can let a partial dual-signal mesh pass. Debug inspection also depends on Angular's dev-only `window.ng`.
|
||||
|
||||
The data-channel test proves that audio eventually resumes after a synthetic close:
|
||||
|
||||
- [`data-channel-recovery.spec.ts`](../e2e/tests/voice/data-channel-recovery.spec.ts), lines 27–54
|
||||
|
||||
It does not prove uninterrupted audio, which is consistent with the current full peer teardown.
|
||||
|
||||
## Revised implementation order
|
||||
|
||||
Do not tune codecs, ICE timers, or TURN before identity and membership are correct.
|
||||
|
||||
### Packet 0 — Reproduction and observability
|
||||
|
||||
**Goal:** produce one deterministic two-signal matrix and enough correlated data to prove each later fix.
|
||||
|
||||
Log in debug builds:
|
||||
|
||||
- home user id;
|
||||
- credential actor id per normalized signal URL;
|
||||
- room id/source URL;
|
||||
- peer-map key and peer signal URL;
|
||||
- initiator/polite decision inputs;
|
||||
- call `targetUserId` and delivery outcome;
|
||||
- DM incoming and resolved conversation ids;
|
||||
- data-channel generation and reconnect attempt reason.
|
||||
|
||||
Do not log tokens, passwords, provision secrets, SDP bodies, or message contents.
|
||||
|
||||
### Packet 1 — Restore-safe silent foreign auth
|
||||
|
||||
**Primary files:**
|
||||
|
||||
- `toju-app/src/app/store/users/users.effects.ts`
|
||||
- `toju-app/src/app/domains/authentication/application/services/signal-server-auth.service.ts`
|
||||
- `signal-server-authorize.service.ts`
|
||||
- provision-secret stores; targeted Electron bridge only if the persistence contract changes
|
||||
|
||||
**Tests:**
|
||||
|
||||
- restored home session + absent secret;
|
||||
- restored Electron session;
|
||||
- web new-tab behavior;
|
||||
- old foreign account whose previous secret is lost;
|
||||
- offline/unknown endpoint never opens authorize;
|
||||
- foreign `auth_error` does not expire the valid home session.
|
||||
|
||||
### Packet 2 — Identify/join/presence integrity
|
||||
|
||||
**Primary files:**
|
||||
|
||||
- `room-signaling-connection.ts`
|
||||
- `signaling-transport-handler.ts`
|
||||
- `signaling.manager.ts`
|
||||
- targeted `server/src/websocket/handler.ts` only if a failing test proves a server change is needed
|
||||
|
||||
**Tests:**
|
||||
|
||||
- credential-store fallback on a fresh socket;
|
||||
- identify is first authenticated message before join;
|
||||
- signal-server restart restores both users to the roster;
|
||||
- leaving one signal scope does not remove a peer shared through another.
|
||||
|
||||
### Packet 3 — Cross-signal identity for calls and DMs
|
||||
|
||||
**Primary files:**
|
||||
|
||||
- `domains/direct-message/**`
|
||||
- `domains/direct-call/**`
|
||||
- `peer-delivery.service.ts`
|
||||
- authentication credential/alias readers
|
||||
|
||||
**Tests:**
|
||||
|
||||
- candidate collection from home id, actor id, peer id, and known presence routes;
|
||||
- unreachable call never leaves caller silently in voice;
|
||||
- merge two alias conversations without message/status loss;
|
||||
- cross-signal DM reply remains in one thread;
|
||||
- cross-signal call rings, answers, and carries bidirectional audio.
|
||||
|
||||
### Packet 4 — Per-signal WebRTC identity
|
||||
|
||||
**Primary files:**
|
||||
|
||||
- `realtime-session.service.ts`
|
||||
- `signaling-message-handler.ts`
|
||||
- `connection/negotiation.ts`
|
||||
- `recovery/peer-recovery.ts`
|
||||
- voice-state data-channel payload construction
|
||||
|
||||
**Tests:**
|
||||
|
||||
- home-id/actor-id matrix elects exactly one initiator;
|
||||
- polite-peer collision decision uses the same scoped ids;
|
||||
- same-home and cross-home voice are bidirectional;
|
||||
- stale voice state cannot leave a connected peer permanently muted without recovery.
|
||||
|
||||
### Packet 5 — Honest data-channel and reconnect recovery
|
||||
|
||||
**Primary files:**
|
||||
|
||||
- `recovery/peer-recovery.ts`
|
||||
- `messaging/data-channel.ts`
|
||||
- `peer-connection.manager.ts`
|
||||
- user-visible connectivity state/components
|
||||
|
||||
**Tests:**
|
||||
|
||||
- signaling-down ticks do not consume reconnect attempts;
|
||||
- max real attempts exposes Retry;
|
||||
- Retry re-arms recovery;
|
||||
- data-channel reopen forces inventory;
|
||||
- media state is reapplied after full rebuild;
|
||||
- if soft replacement is implemented, audio remains continuously flowing.
|
||||
|
||||
### Packet 6 — Message convergence and file integrity
|
||||
|
||||
**Primary files:**
|
||||
|
||||
- `store/messages/messages-sync.effects.ts`
|
||||
- message sync reducers/rules/handlers
|
||||
- attachment transfer only where a failing test identifies a gap
|
||||
|
||||
**Tests:**
|
||||
|
||||
- timeout remains dirty;
|
||||
- only complete, empty inventory rounds select the 15-minute cadence;
|
||||
- late join catches up;
|
||||
- edit/delete converge after DC recovery;
|
||||
- offline DM reconnects and delivers;
|
||||
- large-file download SHA-256 and size match the source;
|
||||
- attachment announce/message order is stressed in both channel and DM paths.
|
||||
|
||||
### Packet 7 — Production reliability proof
|
||||
|
||||
Add separate suites rather than weakening existing fast PR coverage:
|
||||
|
||||
- Electron smoke: login restore, foreign provision, text, file, voice;
|
||||
- signal restart during active voice and DM;
|
||||
- TURN-backed relay-only test;
|
||||
- sleep/wake or network-interface-change test;
|
||||
- 30-minute scheduled voice/screen-share soak;
|
||||
- exact mesh assertions where topology guarantees the count.
|
||||
|
||||
## Documentation corrections required with fixes
|
||||
|
||||
1. Replace the false soft-data-channel paragraph in `realtime/README.md` unless soft replacement ships.
|
||||
2. Relabel the LESSONS outbound-call and DM-canonicalization entries as unfinished specifications until real symbols and tests exist.
|
||||
3. Correct authentication README endpoints from `/api/auth/*` to `/api/users/*`.
|
||||
4. Correct chat README inventory cap from 1,000 to 20,000.
|
||||
5. Mark `shared-kernel/signaling-contracts.ts` non-authoritative or align it with live wire names.
|
||||
6. Correct `e2e/CONTEXT.md` if it claims Electron coverage; the current suite is browser + `ng serve`.
|
||||
|
||||
## Decisions required from the product owner before implementation
|
||||
|
||||
1. **Lost provision secret recovery**
|
||||
- Recommended: automatically create a secret when none has ever existed; when an existing foreign account rejects the new secret, show a targeted per-server recovery action instead of a generic login loop.
|
||||
2. **Web secret durability**
|
||||
- Recommended emergency default: keep session-scoped storage for security, but make the new-tab limitation explicit and recover without misclassifying home logout.
|
||||
3. **Data-channel strategy**
|
||||
- Recommended: ship visible full-rebuild recovery + forced resync first, then soft replacement as a measured follow-up.
|
||||
4. **TURN**
|
||||
- Recommended for the stated Discord-quality goal: managed credentialed TURN, not user-only configuration.
|
||||
5. **History availability**
|
||||
- The signal server stores no chat history. Decide whether “offline users always recover history” may depend on another online client, or whether an encrypted durable mailbox/history service is required.
|
||||
6. **Presence semantics**
|
||||
- Define online/idle/DND/offline and stale timeout behavior. Current emergency tests mostly prove connectivity, not Discord-like status semantics.
|
||||
|
||||
## Definition of done for Fable 5
|
||||
|
||||
For every packet:
|
||||
|
||||
1. Start from a behavior-level failing test or deterministic reproduction.
|
||||
2. Change only the packet's owned surfaces.
|
||||
3. Record actor ids and signal scope in test diagnostics without secrets.
|
||||
4. Run focused unit/integration tests, then the relevant Playwright subset.
|
||||
5. Run lint/build for touched packages.
|
||||
6. Update lying docs in the same change.
|
||||
7. Do not mark complete from unit-green alone; attach the user-visible proof.
|
||||
|
||||
The green 65-test archive is a useful baseline, not release certification. Release confidence requires the two-signal cross-home matrix, real restart recovery, Electron coverage, and TURN/NAT validation described above.
|
||||
Reference in New Issue
Block a user