104889ace6
I hit a bug where the Matrix sync loop treated a passing 502 from Umbrel's app proxy as a permanent auth failure and stopped syncing for good. The old check did a naive "403" in str(exc) substring match, and the 502 HTML error body embedded an SVG path with the coordinate 40.4302, which contains the digit sequence 403. I replaced the substring check with a layered classifier. Transport exceptions like TimeoutError, ConnectionError, and OSError are always treated as transient regardless of their message text. Structured signals take priority next: the errcode attribute against a known set of permanent Matrix error codes, then the http_status attribute against 401/403 specifically (not status, status_code, or code, which belong to unrelated exception shapes and risk coincidental integer matches). Only when none of those are present does it fall back to a bounded, word-boundary-safe text scan on the first 200 characters. Added tests covering the attribute narrowing, the transient exception types, and two loop-level tests exercising _sync_loop directly to confirm it retries on a transient error and stops on a genuine 401/403. (cherry picked from commit 96d3363e45a63e08d9f07518ed949df334a33b3c)