frontend-patterns/research/experiments/matrix-recovery/README.md
tegwick 78bd951568 Exercise Matrix SDK recovery under controlled transport failures
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a0cffe-206f-7323-89d3-b20ca4cf4bf9
2026-09-25 11:02:03 +02:00

3.8 KiB

Matrix recovery experiment

Bounded implementation experiment, executed 2026-09-25. It runs the unmodified published matrix-js-sdk@42.3.0 package with an injected fetch function. This tests real SDK state transitions against a synthetic receiver, not Element UI or a real Matrix homeserver. No messages leave the process and no inference is used.

Question: can caller-visible request failure disagree with acceptance evidence, and what happens to identity and local state on retry or early remote echo?

Hypothesis: request promises, pending-event status and authoritative event evidence are distinct. A counterexample would be a late error regressing an already reconciled event, or a retry changing the transaction identity/payload.

Reproduce

Node >=22 is required by the pinned SDK; recorded run used Node 24.11.1. From this directory, install the locked dependencies and execute:

npm ci --ignore-scripts --no-audit --no-fund
npm test

Alternatively install the same locked dependencies outside the repo, then set FEP_MATRIX_SDK_DIR to that installation's node_modules/matrix-js-sdk directory when invoking node experiment.mjs. The script checks the version and reports hashes of the loaded SDK files, its own source and the dependency lock. Compare them with result.json; the alternate path alone does not establish provenance. Do not overwrite the recorded result with a new run; retain new evidence separately.

package-lock.json pins package integrity and transitive versions. provenance.json separates the published runtime package from the Element source/SDK pairing. The Element lockfile does match the previously inspected SDK commit, but this published package is not claimed to be that exact build.

Controls and results

Case Observed SDK behavior Synthetic receiver observation
Accept, lose response, resend same event NOT_SENT retains content; resend preserves transaction/path/body; echo leaves one timeline entry Two requests, one stored acceptance
Fresh identity after uncertain outcome Fresh send initially blocked by pending queue; explicit local cancellation permits a new send Same content accepted twice under different identities
Echo before successful response Pending entry reconciled once; later success does not duplicate it One acceptance
Echo before transport error Send promise rejects, but reconciled event remains non-pending with one timeline entry One acceptance

All four assertions passed in the recorded run. The negative control confirms that the fixture distinguishes identity reuse from replacement; it does not demonstrate a production duplication incident. Receiver deduplication is programmed into this fixture, so its correctness on a real server remains an assumption.

Automatic scheduler retries are disabled to expose the failed attempt. Remote echo is injected through Room.addLiveEvents; no real /sync loop, encryption, browser UI, storage restart, device change, authorization failure or server retention is tested. The fake response loss is a thrown transport error after fixture acceptance, not a real packet drop. No runtime conclusion about Zulip follows from this SDK experiment.

Interpretation

The strongest result is the disagreement between a rejected request promise and an already reconciled event. Treat promises as attempt results, not complete domain outcomes. Preserve operation identity and submitted content across retry. Local cancellation removes local tracking; it cannot undo an accepted remote operation.

This supports parts of Mutation Outcome Tracking. It does not prove that its exact four-state vocabulary recurs independently or improves architecture decisions. See editorial review.