New primitive: `VisibilityIntent::Announcement`, a public post whose
author MUST be the hardcoded bootstrap anchor posting identity
(`DEFAULT_ANCHOR_POSTING_ID`) and whose content carries an ed25519
signature by that key. Forged announcements (any other author, or
bad signature) are rejected by `control::receive_post` before storage
— they never enter the DB and never propagate via neighbor-manifest
diffs. Only the real anchor can publish announcements, and it does so
sparingly as part of the release deploy flow.
Uses release announcements to drive an in-app upgrade banner:
- Anchor publishes a signed `{category:release, version, channel,
download_url, ...}` post during every deploy.
- Clients receive it via the normal CDN; `apply_announcement_if_applicable`
stores the latest-per-category/channel in the settings kv, keyed
e.g. `announcement:release:stable`.
- Welcome screen checks storage on startup; if the stored release
version > CARGO_PKG_VERSION on the user's selected channel, a banner
appears with a Download button that opens the system browser.
- Settings gets "Updates" section with Stable / Beta radio + Check-now
button + current status line.
Core:
- `DEFAULT_ANCHOR_POSTING_ID: NodeId` constant (32 bytes, the anchor's
current posting id — `17af141956ae...`).
- New `VisibilityIntent::Announcement` variant; feed filters in all 6
`get_feed*` / `list_posts*` query sites updated to also exclude the
new intent AND the pre-existing `GroupKeyDistribute` intent.
- `types::AnnouncementContent` + `ReleaseAnnouncement` structs.
- `crypto::{sign,verify}_announcement` — length-prefixed field digest
with a "has release" 1-byte flag.
- New `announcement` module with `verify_announcement_post`,
`apply_announcement_if_applicable`, `latest_release`,
`build_announcement_post`, and a `StoredAnnouncement` envelope saved
to settings so the UI can render without a full post scan.
- `Node::publish_announcement` refuses to run unless the default posting
id equals the anchor constant — accidental use on client installs
fails loud.
Wire / receive:
- `control::receive_post` verifies announcement signatures upfront
alongside Control and Profile. Same pattern; same guarantees.
CLI one-shots (no daemon):
- `itsgoin <data_dir> --print-identity` — prints network_id +
default_posting_id, exits.
- `itsgoin <data_dir> --announce --ann-category release
--ann-channel stable --ann-version X --ann-title ... --ann-body ...
--ann-url https://itsgoin.com/download.html` — builds + stores +
propagates the signed post, exits.
deploy.sh:
- Now runs the announce one-shot inside the anchor-restart window
(after binary swap, before start). The DB is free during that gap,
so the one-shot can write without conflicting with the running
daemon. The restarted daemon loads all storage on boot and serves
the new announcement to pulling peers.
Tauri IPC:
- `check_release_announcement(channel)` → Option<ReleaseAnnouncementDto>
— returns None when up-to-date.
- `get_update_channel` / `set_update_channel(channel)` — persists in
settings kv key `ui_update_channel`; defaults to stable.
- `open_url_external(url)` — desktop-only (xdg-open / open / cmd start);
refuses non-http(s) URLs. Android needs the opener plugin — TODO.
Frontend:
- Upgrade banner on the welcome screen, populated by
`loadUpgradeBanner()`. Hidden when no newer release is known.
- Settings → Updates section with Stable/Beta radio + Check-now button
+ current status line.
Tests: announcement signature roundtrip; non-anchor author rejection;
non-announcement intent is a no-op. 124 / 124 core tests pass.
The deploy pipeline builds AppImage, APK, and CLI on Linux, but the
Windows installer is produced on a separate Windows host (no
cross-compile toolchain here). Print a reminder at the end of every
deploy with the exact filename the Windows team needs to upload, so
the handoff stays unambiguous.
Reset All Data:
- Sentinel now written at the app-level data_dir instead of the
active identity's subdir. On Android the subdir path was never
checked at startup, so reset silently did nothing.
- On detection, wipe EVERYTHING under the app data_dir: identity.key,
itsgoin.db + WAL + SHM, blobs, all identity subdirs. Next launch
is truly fresh — new network key, new posting key, no prior data.
First-run name:
- Display name is optional. Blank submits as anonymous.
- First-run modal + profile overlay placeholder updated to say
"Display name (optional)".
Android file picker:
- pick_file on Android now uses tauri-plugin-android-fs'
show_open_file_dialog (Storage Access Framework OPEN_DOCUMENT).
Read the picked URI's bytes, stage them in the app's private cache
as a timestamped file, return the staged path so existing
import_* code can read it as a regular filesystem path.
- Zip filter passes application/zip + application/octet-stream (some
file providers report the latter for .zip).
Android auto-backup off:
- AndroidManifest: allowBackup="false", fullBackupContent="false",
dataExtractionRules pointing at new data_extraction_rules.xml
- New data_extraction_rules.xml excludes all domains from both
cloud-backup and device-transfer. Prior default (allowBackup=true)
silently replicated identity.key to Google Drive for any user with
cloud backup on — which effectively published the root secret to
a third party without asking. Users who want off-device backup use
Settings -> Export (explicit zip they control).
Import as personas:
- New import_as_personas function in core/import.rs + new
import_as_personas_cmd Tauri IPC.
- Reads identity.key from the bundle and adds it to posting_identities
as a persona. Also reads posting_identities.json (v0.6+ bundles)
and adds each entry. Dedupes by node_id.
- Posts stay AS-AUTHORED — original post_id, original author,
original signatures, original wrapped_key recipients. No
re-encryption. Content encrypted to any of the imported keys
becomes decryptable because we now hold the secrets.
- Blobs, follows, profiles copied across.
- If current device has <=1 posting identity (the fresh-install one)
and the bundle brings more, auto-switch the default to the first
imported persona. Covers first-run-then-import flow cleanly.
Import wizard UI:
- New default option: "Restore as personas" — posts keep original
authors; source's keys become personas you can post as.
- Old "Merge with decryption key" retained as "Consolidate under
current default persona (requires source key)" for the case where
a user intentionally abandons a persona.
- "Public posts only" and "Add as separate identity" retained.
deploy.sh made executable (chmod +x tracked).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phases 1-5 of the Identity Architecture rollout are on master; Phase 6
(rotating DM IDs) is deferred. This bumps the protocol-breaking
release: v0.5 and v0.6 cannot interoperate.
Packaging:
- Cargo + tauri.conf: 0.5.3 -> 0.6.0
- deploy.sh: drop -beta filename suffix
- download.html: single v0.6.0 section with fork notice at the top;
changelog entry summarising the five phases and the schema migration;
Phase 6's rotating-DM-ID status noted as deferred
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>