Kontinuum Node — App Integration
Client-facing flows: как
kontinuum-app(Tauri) взаимодействует с kontinuum-node.
Audience: app developers. Pairing flows, identity recovery, free-tier / Standard / PRO UX, family-mode warnings, lapse timeline на стороне app.
Связанные документы:
architecture.md— overview / glossary / tier model — start hereprotocols.md— wire-level DHT/mailbox protocols (server-side)operations.md— cert lifecycle на network-стороне, replication policydesign-prompts/— UX brief'ы для дизайн-задач
Section codes (§6, §7.3.x, §9.5, §10.5, §13, §14, §15.2) сохранены от единой v0.6 спеки.
6. Identity, bootstrap, pairing, recovery
6.1 Onboarding клиента (kontinuum-app)
Free-tier (без покупок):
- App генерирует Ed25519 keypair,
identity_id = blake3(pubkey). Ключ кладётся в Stronghold vault. - App читает вшитые в код Tier 0 pubkeys + multiaddrs.
- App читает global DHT (для будущего pairing lookup), не публикует identity-stub.
- Cross-network sync не работает (мотивация купить relay).
- mDNS LAN sync доступен сразу.
С купленным relay (Standard Tier 1):
- Billing flow на сайте org → cert issuance Tier 0 → provisioning.
- Node boots,
NodeHelloк Tier 0, попадает в global DHT. - Node создаёт Personal Space DHT владельца.
- Node обновляет запись
node:{node_id}в global DHT. - App владельца замечает обновление (push от Tier 0), подключается к Personal Space.
Opt-in публикация identity:
Settings → Privacy → «Allow others to find me by identity» (по умолчанию off).
- ON → app публикует identity-stub в global DHT:
identity:{hash(identity_id)} → Signed{ pubkey, personal_space_hint, last_seen }. - Объяснение в UI: «Друзья смогут спейриться с вами удалённо без предварительного контакта. Эта опция также нужна для удалённого восстановления identity через recovery phrase (см. §6.5). Trade-off: существование вашего identity_id становится глобально видимым.»
- При выключении (toggle ON → OFF) — confirmation dialog: «⚠️ Отключение этой опции сломает удалённое восстановление identity по recovery phrase. Если потеряете все устройства — потребуется ручной URL relay-ноды (вы его помните?). [Disable anyway] [Cancel]»
- Без paid infra checkbox видим, но greyed-out. При tap/click → tooltip: «Для публикации идентичности нужна купленная relay-нода или PRO-подписка — иначе identity_stub не на что ссылаться. Также: без paid infra и без opt-in publication удалённое восстановление identity невозможно. Купить relay → Settings → Buy.»
6.2 Pairing — local (in-person)
QR / PIN — как в kontinuum-app::pairing. Payload:
struct PairInvitation {
inviter_identity_id: String,
inviter_pubkey: Vec<u8>,
inviter_personal_space_id: Option<SpaceId>,
inviter_dht_bootstrap_addrs: Vec<Multiaddr>,
inviter_display_name: String,
one_time_token: [u8; 32],
valid_until: u64,
signature: Vec<u8>,
}6.3 Pairing — удалённый
Primary: Mediator-initiated social introduction.
Инициирует mediator (Charlie), не requester. Это даёт максимальный privacy: Alice не может «фишить» Charlie на наличие у него Bob'а как contact'а.
Charlie open's app → Contacts → выбирает Alice и Bob → «Introduce these two».
Charlie's app шлёт signed introduction обоим:
Introduction { introducer: charlie_identity, introducee_a: alice_pubkey + alice_personal_space_hint, introducee_b: bob_pubkey + bob_personal_space_hint, signature_by_charlie: ... }Alice notification: «Charlie wants to introduce you to Bob. Accept?»
Bob notification: «Charlie wants to introduce you to Alice. Accept?»
Если оба accept → mutual pair complete. Trust наследуется от mediator (Charlie).
Если кто-то reject → introduction отменена; mediator не уведомляется (privacy).
Fallback: signed invite link (без mediator).
- Alice генерит invite URL:
kontinuum://pair?inv=<base64_signed_payload>(TTL 24 часа, одноразовый). - Alice отправляет URL Bob'у любым каналом.
- Bob кликает → app разбирает → показывает Alice's display_name + усечённый identity hash.
- Bob принимает on-trust. UI предупреждает про MITM-риск.
Optional secure mode (для параноиков): OOB-fingerprint verification — отдельный setting.
6.4 Device re-discovery через rendezvous
Это не pairing-flow — это механизм для уже paired identities находить устройства друг друга при смене сети. Identity_id известны заранее (paired через §6.2 / §6.3); rendezvous отвечает только «где сейчас online устройство этого identity».
См. §6.6.
6.5 Identity recovery
Базовый механизм наследован из kontinuum-app::vault::recovery (BIP39 12-словная фраза + Argon2id + ChaCha20Poly1305).
Размещение vault backup:
| Уровень пользователя | Куда бэкап vault'a |
|---|---|
| Free-tier | Нет автоматического remote backup — manual export (USB / стороннее облако) |
| Standard + relay | Auto-replicated на собственной relay-node (RF = 1) |
| PRO + N attached инфра | Auto-replicated на нодах (RF = N, geo-diverse если возможно) |
Flow восстановления:
- User устанавливает app на новом устройстве.
- Settings → Restore → вводит 12-словную BIP39 phrase.
- App деривирует master_key → identity_id.
- App ищет в global DHT identity-record по
hash(identity_id)(требует чтобы был opt-in publish или fallback recovery stub — open question). - Если не нашёл — manual URL relay-ноды.
- App connect'ится, signing-challenge от recovery phrase.
- Relay отдаёт encrypted vault backup → расшифровка → identity restored.
- New device генерирует свой device_id, добавляется в Personal Space registry.
- UI предлагает revoke старые device_id через
DeviceRevoked.
UX при первичной настройке recovery phrase (onboarding):
При генерации recovery phrase app показывает:
⚠️ Recovery phrase позволит восстановить identity на новом устройстве. Однако для удалённого восстановления нужно включить «Allow others to find me by identity» в Privacy settings.
Без opt-in вы сможете восстановить identity только если запомните URL вашей relay-node вручную.
[Enable now] [Maybe later — I will remember the relay URL]
6.6 Rendezvous service
Что это: opt-in cross-network device-discovery для уже-paired identities без долгоживущей записи в DHT.
struct PresenceToken {
identity_id: IdentityId,
device_id: DeviceId,
public_multiaddrs: Vec<Multiaddr>,
capabilities: Vec<Capability>,
expires_at: u64, // now + 60 sec
signature: Vec<u8>, // подписан device key
}Devices публикуют presence каждые ~30 секунд, TTL 60 секунд, без refresh → пропадает.
Где живёт:
- Tier 0 — всегда (hardcoded fallback)
- Tier 1 — по умолчанию ON
- Tier 2 (PRO-attached) — по умолчанию OFF, user переключает
Three-level toggle в app:
- Identity-level (Settings → Privacy): «Allow my devices to find each other across networks» (по умолчанию OFF).
- Device-level: per-device «Advertise this device» (по умолчанию ON, если identity-level ON).
- Visibility scope (только два варианта —
Publicнамеренно отсутствует, чтобы rendezvous не превращался в pairing-discovery):OwnDevicesOnly— только устройства этой identity могут найти друг друга.PairedContacts— paired identities могут lookup.
Rendezvous никогда не используется для discovery незнакомых identity — для этого служат pairing flows §6.2 / §6.3.
Rate limits (технические, не маркетинговые):
Защита от DDoS / перегрузки. Per-source-IP token bucket: e.g. 100 publish-ops/min, 1000 lookup-ops/min. Превышение → reject 429 Too Many Requests. При перегрузке ноды глобально → новые publish reject'ятся с указанием соседних rendezvous-нод. Не различает «free» vs «paid» пользователей.
Установление соединения после rendezvous:
- LAN — mDNS быстрее, rendezvous пропускается.
- Cross-network free-tier — только hole-punching через DCUtR (~70% NAT). При failure UI показывает объяснение: «Прямое соединение не удалось из-за NAT-конфигурации (вашей или peer's). Купите relay-node для гарантии соединения.»
- Cross-network paid — circuit-relay-v2 через собственный Tier 1/2 node (100% success).
§7.3 Family / team mode — client UX
Server-side mechanics family-mode описаны в operations.md. Здесь — UX-side: предупреждения при подключении tenant'а к чужой ноде и flow выхода.
7.3.1 UX warnings при подключении к чужой ноде
Перед первым join'ом к host-ноде другого пользователя tenant обязательно видит два warning'а:
Warning #1 — Риск потери доступа:
⚠️ Эта нода управляется пользователем
{Charlie}. Если он:
- выключит ноду или отключит её от сети — ваши данные станут недоступны;
- удалит вас из whitelist — у вас будет 14 дней на миграцию данных;
- перестанет оплачивать инфраструктуру — нода перейдёт в read-only после 7 дней;
- не продлит cert через 28 дней неоплаты — local data удаляется (см. §13.3).
Для критически важных данных рекомендуем купить собственный relay-node и pin'ить контент туда.
Warning #2 — Privacy implications:
⚠️ Владелец host-ноды
{Charlie}имеет admin-доступ к серверу и физический доступ к диску. Ваш контент шифруется E2E — Charlie не может прочитать plaintext через обычные API.Однако теоретически возможные attack vectors:
- Физический доступ к ciphertext blob'ов на диске (без знания вашего space_key — useless).
- Network metadata (timing, sizes, traffic patterns) — наблюдаемо для admin host-ноды.
- При компрометации Charlie's identity ключа — может быть compromised и его доступ.
Для maximum privacy рекомендуем купить собственную relay-node — данные физически на инфре, которой управляете только вы.
Tenant подтверждает оба warning'а перед join'ом. Settings → My Hosts → видит warnings заново при любом изменении host's status.
7.3.2 Tenant exit flow
При удалении tenant identity из whitelist owner'ом:
| Период | Tenant-режим |
|---|---|
| Day 0 → 14 | Graceful migration window — tenant в read-only на host's ноде; UI показывает «Charlie removed you, you have 14 days to migrate data» + кнопка «Export to USB / Buy own relay» |
| Day 14+ | Hard removal — tenant теряет доступ; данные на host's ноде удаляются |
Если у tenant'а нет собственной paid infra — он fallback'ит в free-tier (LAN-sync only).
§9.5 User pins (UX side)
Полные placement-механики pins на сетевом уровне — в operations.md. Здесь — UX angle.
9.5 User pins / важный контент
User может пометить любой blob или весь Space как pinned. Это влияет на placement:
struct Pin {
target: PinTarget, // Blob(hash) или Space(space_id)
pinned_by: IdentityId,
pinned_at: u64,
}
enum PinTarget {
Blob(BlobHash),
Space(SpaceId),
}Эффект:
- Pinned blob/space → forced replica в owner's primary storage (через Virtual Pool placement: LocalRelayFree + по крайней мере одна копия в
OrgPaid/ExternalPROдля 11-девяток durability — см. §9.1). - Unpinned blob → может жить только в peer's cache, может GC'нуться когда peer чистит transit-cache (по quota pressure).
- Это даёт user'у контроль durability per-blob.
Scope: pin'ятся только blob'ы и Spaces — не отдельные mailbox entries.
- Если blob упомянут только в mailbox entry (не в Space) и не pinned → blob eligible для GC через transit-cache eviction, как и сам mailbox entry (по §10.2).
- Чтобы сохранить blob из mailbox — user должен сначала «принять» message в какой-то Space (например, save attachment in personal photo album), а затем pin'нуть Space или конкретный blob внутри Space.
UX: на каждом blob/space в app есть «📌 pin to my storage» toggle. Если pin включён и Virtual Pool заполнен → app предупреждает «пора докупить S3 или удалить старые pin'ы».
Free-tier: toggle disabled с tooltip:
📌 Pinning требует primary storage. Купите relay-node или подпишитесь на PRO чтобы получить собственное хранилище для pin'ов. Без paid infra ваш контент живёт только на собственных устройствах — pin'ить нечего.
§10.5 Free-tier UX (продолжение §10 mailbox)
Mailbox structure / GC / TTL — в protocols.md. Здесь — client-facing warning flows для free-tier пользователей.
10.5 Free-tier UX: explicit messaging
Free-tier пользователь (без paid infra) не может получать offline-сообщения — у него нет mailbox.
Sender UX:
При попытке отправить сообщение или Share-блоб получателю-free-tier, sender видит warning:
💬 Получатель не имеет relay-ноды. Сообщение будет ждать в очереди до 30 дней пока получатель не окажется online. Если хотите гарантированную offline-доставку — попросите его купить relay.
Receiver UX (free-tier):
Persistent banner в app:
⚠️ У вас нет relay-ноды. Сообщения от друзей доставляются только когда вы online. Купите relay для гарантии получения. [Buy relay]
13. Модель PRO-подписки
13.1 Дистрибуция codebase — Scenario D (primary)
Один codebase, web checkout + license key с auto-activation, fallback на in-app subscription.
- Один app билд (kontinuum-app).
- PRO-функции gated через license-key.
- License покупается на сайте kontinuum.org (вне Apple/Google).
- Auto-activation flow (primary):
- После оплаты web-checkout шлёт user'у deep-link
kontinuum://activate?token=<key>+ email с тем же link как fallback. - Click на deep-link → app получает token → отправляет на org backend → backend проверяет → возвращает signed PRO-cert → app активируется в PRO-режиме.
- App при первом старте проверяет clipboard на presence такого URL как UX-shortcut.
- После оплаты web-checkout шлёт user'у deep-link
- Manual token entry (workaround для desktop / Linux):
- Settings → Activate PRO → paste field. User вставляет token из email вручную.
- Нужно для случаев когда
kontinuum://deep-link не зарегистрирован в OS (свежая инсталляция на Linux без user интервенции, headless desktop, sandboxed browser). - Email с copy-friendly token блок обязательно отправляется параллельно с deep-link.
- Преимущества: нет 30% cut Apple/Google, расширяемо на будущие подписки.
- Risk: Apple App Store rejection.
Mitigation для Apple:
- В app не упоминать что PRO покупается где-то ещё («reader app» exemption).
- «Manage subscription» button → external browser → kontinuum.org/account (Apple разрешает).
- Marketing через web, blog, Telegram-канал, word of mouth.
Fallback A (in-app subscription) — если Apple reject'ит, переключаемся на in-app sub с 30% cut.
13.2 Multi-VPS / home server attachment
Без cap на количество. PRO admin UI:
- Add infrastructure wizard в Settings → My Infrastructure → Add node.
- Тип: VPS у внешнего хостера (
byo:vps) или домашний сервер (byo:home). - URL/multiaddrs.
- Optional: external S3 binding для этой ноды — детальный UX design brief в
design-prompts/external-s3-attachments.md.
- Тип: VPS у внешнего хостера (
- List of attached nodes с per-node health, storage stats, geo-zone.
- Replication policy editor — задать RF target (cross-provider / cross-region) в пределах собственной инфры.
- Family/team mode toggle — расшарить ноду с whitelisted identities (§7.3).
- Detach mechanism (graceful drain → remove from network).
13.3 PRO subscription lapse — Hybrid timeline (Variant 3)
Byo-нода следует unified cert lifecycle из §12.1 (single source of truth для всех Tier 1/2 нод). App-режим следует параллельным таймлайном:
| Период | App-режим | Состояние byo-ноды в сети (по §12.1) | External S3 |
|---|---|---|---|
| Day 0 → 7 | full PRO, daily reminder | full service (cert валиден) | работает |
| Day 7 → 14 | app read-only | read-only freeze — reads OK, writes reject, replication-in OK | работает; нода продолжает serve'ить existing blob'ы peer'ам сети |
| Day 14 → 21 | graceful degrade в Free-режим, PRO UI скрыт | cold archive — reads медленные, paid restore offer | работает; нода редиректит запросы на external bucket (§13.6) |
| Day 21 → 28 | Free-режим | tombstone — reads = 410 Gone (для blob'ов в LocalRelayFree), restore возможен | работает; редирект продолжается |
| Day 28+ | Free-режим, attached unmanaged | hard delete local data; byo физически крутится, user может выключить | работает (org перестаёт «знать» про bucket); blob'ы остаются у user'а |
Reactivation → cert renew → возвращается в full PRO, byo-ноды восстановлены, external S3 reconnect.
Важно: byo-нода физически продолжает работать на VPS / home server независимо от cert статуса. До этапа tombstone (day 21) она serve'ит existing blob'ы peer'ам сети — это поддерживает доступность контента для друзей владельца, не зависящего от своевременной оплаты. После tombstone — local data удаляется, но references на external S3 продолжают работать через redirect (§13.6).
Housekeeping API calls разрешены на всех этапах кроме hard delete (day 28+):
«App read-only» ограничивает user-facing writes — publish, share, send message. Maintenance операции продолжают работать:
- Refresh presigned URLs для external S3 redirect-mode (§13.6).
- Re-encryption job для forward secrecy после Revoke (§5.2.3).
- Anti-entropy gossip для DHT consistency.
- Provider records refresh.
- Cert renewal attempt (для reactivation flow).
Это обеспечивает что lapse не ломает базовую infrastructure work — только блокирует новые пользовательские действия.
13.5 PRO mailbox scope
- PRO нода
byo × singleхостит mailbox только владельца. - PRO нода
byo × multi(family-mode) хостит mailboxes владельца + whitelisted tenants. - Mailbox paired-contacts (не в whitelist) — на ИХ собственной инфре, не на этой ноде.
13.6 External S3 при lapse — redirect mode
External S3 (Backblaze / R2 / AWS, прикручен в PRO) — invariant к PRO lifecycle:
- Это собственность user'а у внешнего провайдера. User сам платит, мы не контролируем.
- При PRO lapse → app переходит в read-only, перестаёт делать новые upload'ы в external bucket.
- Существующие blob'ы в bucket — продолжают существовать. User может извлечь напрямую через S3-клиент.
- После reactivation → app reconnect'ит bucket, продолжает использовать как primary storage.
Redirect mode для решения stale DHT references:
Чтобы peer'ы, лукающие blob'ы через DHT provider records, не получали stale references после PRO lapse:
- При cert revocation byo-нода переключается в redirect-mode для blob'ов, физически лежащих в external S3.
- На
GET /presignдля такого blob'а нода возвращаетHTTP 307 Redirectна presigned URL external bucket'а. - Provider records в Space DHT обновляются:
provider_kind = ExternalRedirect; URL bucket'а включён в запись. - Peer-клиент следует redirect и получает blob напрямую из external bucket — без участия orphan byo-ноды.
Multi-layer metadata защита (по умолчанию — privacy-preferred):
E2E-ciphertext защищает содержимое blob'ов, но public-readable bucket exposes metadata: factor существования identity, количество blob'ов, размеры, timestamp'ы, traffic patterns. Поэтому:
- Default = long-lived presigned URLs, не public-readable. App периодически refresh'ит URLs (см. §13.3 — housekeeping API calls разрешены даже в read-only режиме).
- Obscure bucket naming: вместо предсказуемого
cnt-${identity_id_short}→cnt-${blake3(identity_id || creation_nonce)[:16]}. Без знанияcreation_nonceатакующий не может target-enumerate bucket конкретного user'а. - Disable LIST permission: только direct GET по известному
blob_hash. Bucket listing rejected на provider level. - Minimal HEAD responses: customize HEAD responses чтобы не возвращать timestamp / etag — только existence + size.
- Public-readable как opt-in для пользователей публичных Spaces (Settings → external bucket → «Allow public read») с explicit UX-warning о metadata-leak.
Эффект: blob'ы в external S3 остаются доступны сети даже после полного отключения byo-ноды, без участия app'a владельца, и без раскрытия metadata случайным observer'ам.
14. Bridge и миграции
14.1 PRO + relay одновременно
Если user одновременно купил relay-node (Standard) и подписался на PRO:
- Primary — PRO byo-инфра (storage, DHT, mailbox).
- Купленный relay-node — дополнительная geo-replica для Personal Space + free S3 в Virtual Pool.
- Биллим оба параллельно: PRO subscription + relay rental.
14.2 Upgrade Standard → PRO
User купил relay-node как Standard, затем перешёл на PRO:
- Купленный relay сохраняется как доп. реплика и доп. источник Free S3 в Virtual Pool.
- Personal Space DHT мигрирует с дублированием на новые byo-ноды.
- Relay → переходит в роль secondary, byo-нода становится primary.
14.3 Downgrade PRO → Standard
User отказался от PRO subscription, но сохранил relay-node:
- Через timeline §13.3 byo-ноды становятся orphans.
- Personal Space мигрирует обратно на relay-node как primary.
- External S3 — invariant (см. §13.6), но больше не интегрирован в Virtual Pool до reactivation.
- Пользователь продолжает как Standard user.
§15.2 App-side: self-hosted control
Org-side admin (Grafana / Directus / operator chat bot) — в operations.md.
15.2 App-side: self-hosted control
Только для operator ∈ {byo:vps, byo:home} (PRO):
- Управление встроено в
kontinuum-app(Settings → My Infrastructure). - Identity-аутентификация (owner identity_id = node's owner field).
- Действия: view storage, view quotas, restart, view logs (tail), upgrade software, decommission, manage replication policy, family-mode whitelist.