A fresh `Identity::create()` was generated on every connect, which
meant the server saw a different client UID each time. Long-lived
features (bookmarks, server-side bans, group membership) depend on a
stable UID — restoring that now via a minimal directory-backed
identity file.
* `chanora_storage::IdentityFileStore` reads / writes a single
`identity.tskey` file under a caller-supplied directory. On Unix
the file is created with `O_CREAT | O_TRUNC | mode 0600`; on
non-Unix targets the platform sandbox does the access control.
Writes are atomic (temp file + `fsync` + `rename`) so a crash
mid-write cannot leave a half-written identity on disk. Empty
files are treated as "no identity" rather than as an error.
* `chanora_protocol::ProtocolClient::generate_identity()` exposes
the `counterVbase64key` serialisation used by tsclientlib's
`Identity::new_from_str`, so the core layer can mint an identity
and store it before dialling.
* `chanora_core::ChanoraSession::init_storage(dir)` wires the
store. `connect()` then resolves the identity in this order:
(1) `cfg.identity` if explicitly supplied; (2) persisted value if
any; (3) generate-and-persist a fresh one.
* `chanora_bridge::api::init_storage(dir: String)` is the
Flutter-facing entrypoint; the matching Dart side resolves
`path_provider`'s `getApplicationSupportDirectory()` and calls
it once on app start.
* `BridgeError` now maps `CoreError::Storage`.
Beta caveat (RISK-PoC-002 / SS-RISK-FALLBACK): the identity is not
encrypted at rest. The v0.4 storage rework lands proper Secret
Service + Android Keystore + iOS Keychain backends. Documented
under `IdentityFileStore`'s doc comment.
Live-verified on Moto G Stylus 5G: first connect generated +
persisted the identity (visible in the redacted diagnostic export
as "generated + persisted fresh identity"); disconnect + reconnect
in the same session logged "reusing persisted identity" and dialled
with the same UID.