Pulse M0 — End-to-End User Journey
Версия: v0.2 · Синхронизация: spec v0.14.3 · Последнее обновление: 2026-05-20
Walk-through того, как M0-implementation Pulse работает на полном жизненном цикле App-пользователя: от установки до первой оплаты. Цель — дать новому разработчику и маркетинг-команде единую mental model, не заставляя их сшивать её из 3300 строк спецификации, 25 файлов в contracts/, и трёх sql-схем.
Каждый шаг подписан конкретными артефактами:
- 📥 эмитируемые events (из
contracts/telemetry/v0.1.0.yaml) - ⚡ возможные действия Pulse (из
contracts/actions/v0.1.0.yaml) - 📊 обновляемые feature_store / metrics (из
contracts/semantic-layer/v0.1.0.yaml) - 🗃️ задеваемые таблицы (
contracts/ddl/)
См. также: specification.md, contracts/event-types-overview.md, testing.md.
Acт 0 — App is downloaded, key generation begins
User action: Скачивает App, нажимает «Get Started».
App-side internals:
- Generates Ed25519 identity keypair via
IdentityManager. - Stores private key in platform keychain (Keychain / Keystore / DPAPI / Stronghold; см. spec §9.1.4-bis).
- Generates client signing keypair for Pulse consent tokens; registers
client_key_id = BLAKE3(public_key)with identity-mapping service via pairing-like bootstrap.
Pulse-side:
📥 app.identity_created (purpose: ops, retention 5 лет)
identity_kind = primarycreated_at
📥 app.install_completed (purpose: ops, retention 365 d)
app_version,platform,install_source
🗃️ identity_mapping.subjects — новая row с subject_id = blake3(identity_id || global_salt_v1). 🗃️ pulse_runtime.events — две row (install_completed, identity_created). 📊 feature_store.installed_at, first_seen_at set (immutable; DSL derives "days since" on-query via subject.installed_at < now() - Xd).
⚡ Возможное действие: log_only от welcome-rule (просто record что новый user; никаких баннеров пока).
Что НЕ происходит: marketing email не отправляется — у пользователя нет
marketing.emailconsent (default opt-out по EU regional override, см.consent-purposes.yaml).
Акт 1 — Onboarding flow
User action: Проходит default onboarding flow (onboarding.default.v3): explain → create-space → connect-device-optional → notification-permission.
📥 app.onboarding_step_completed × 4 (per step)
flow_id = onboarding.default.v3step_id = explain | create_space | connect_device | grant_notificationsstep_index = 0..3duration_on_step_ms
📥 app.permission_changed (если разрешает notifications)
permission_id = notifications,new_state = granted(ordenied)
📥 app.onboarding_finished (terminal)
outcome = completed | skipped | abandonedtotal_duration_ms
📊 feature_store.onboarding_completed_at set; subject.onboarding_completed dimension becomes true. 📊 metric onboarding_completion_ratio = 1.0.
⚡ Возможные действия:
- Если
outcome = completed→show_ui_banner(placement = top) с welcome message and "tour" CTA, gated byrequires_consent: personalization. - Если
outcome = abandoned→ rule вcohort: onboarding_droppedзапускаетlog_onlyдля аналитики, без вторжения.
Что НЕ происходит: Pulse НЕ "догоняет" брошенный onboarding email-ом —
marketing.emailconsent ещё не давал.
Акт 2 — Multi-device pairing (optional)
User action: Хочет подключить телефон (если desktop был первым). Нажимает «Pair device» → видит QR-code.
📥 app.pairing_initiated
pairing_id = ULID,role = initiator,method = qr_code
(на втором устройстве — second app.identity_created? Нет: pairing связывает существующий identity с новым device_id; см. §9.1.6 identity-mapping API)
🗃️ identity_mapping.devices — new row (device_id, subject_id, pairing_id).
📥 app.pairing_completed или app.pairing_failed в зависимости от исхода.
📊 metric paired_devices_count ++.
⚡ Действие при pairing_failed: rule pairing_recovery → show_ui_banner (placement = modal) с troubleshooting (без email — это active session).
Акт 3 — Feature exploration
User action: Использует core features: создаёт spaces, шарит файлы, sync через node.
📥 app.feature_used (sample_rate 10% sticky)
feature_id = spaces.create | sharing.invite | sync.completed | …duration_ms,outcome
📊 metric app_sessions_total++ при каждом startup; feature_used_count (с compensation × 10) обновляется в derivation.
Акт 4 — Banner promotion (free → paid)
Trigger: Rule R0042 — promo_pro_when_engaged срабатывает когда subject входит в cohort high_engagement_free:
subject.tier = 'free' AND app_sessions_last_30d >= 10⚡ Pulse-core dispatches show_ui_banner:
banner_id = promo_pro_upgrade_q2_2026template_id = banner.upgrade_to_pro,template_version = 3placement = topdismissible = truetemplate_params = { discount_pct: "30", expires_in_days: "7" }
App receives, validates template_id exists, renders via Handlebars (см. spec §7.8) с template content из pulse_curated.templates.
📥 Возможные последовательности:
- best case:
app.banner_shown→app.banner_clicked(CTA "Upgrade now") →app.banner_dismissed (cta_clicked). - dismiss:
app.banner_shown→app.banner_dismissed (user_close). - timeout:
app.banner_shown→app.banner_dismissed (timeout)послеexpires_at.
📊 metric banner_engagement_rate derived from these events.
🗃️ pulse_runtime.decisions row + pulse_runtime.action_acks row для tracking.
Акт 5 — First payment
User action: Кликнул CTA → checkout flow (handled by billing, не Pulse) → пейнул.
📥 billing.payment_received (source = billing)
order_id,amount_cents = 999,currency = USD,tier_purchased = pro
📥 app.tier_changed (source = billing)
old_tier = free,new_tier = pro,reason = purchase
📊 feature_store.tier = 'pro', paid_at = now; metric subject.has_paid = true.
⚡ Действие #1 — receipt: rule transactional_receipt dispatches send_transactional_email:
- Bypass
marketing.emailconsent (этоrequires_consent: ops) template_id = email.payment_receiptprovider_id = sendgrid_transactional_mainsubject = "Your receipt for Kontinuum Pro"from_address = billing@kontinuum.cloud
📥 Дальше провайдерные events: app.email_delivered (webhook) → возможно app.email_opened (если разрешён marketing.email_engagement_tracking).
⚡ Действие #2 — welcome-pro: rule welcome_pro_user ждёт 24 часа (idempotency {subject_id}:welcome_pro:{tier_changed_day}), затем show_ui_banner в Pro settings inline ("Pro features tour"), placement = settings_inline.
Акт 6 — Marketing campaign opt-in
User action: В Settings → Privacy включает marketing.email.
📥 app.consent_changed (purpose: ops — это compliance evidence)
purpose_id = marketing.email,new_state = granted,source = ui
🗃️ pulse_curated.consent_grants — append row; consent_current materialized view обновляется при refresh.
⚡ Через несколько дней rule q2_upgrade_campaign (на cohort installed_but_unpaid или recently_purchased) запускает send_marketing_email:
requires_consent: marketing.email✓- Subject в
Asia/Tokyo?idempotency_key.send_dayиспользует local day, не UTC → email приходит в 9 утра local time (см. spec §7.9.5). - При rate-limit hit → 📥
pulse_internal.rate_limit_hit+ decision suppressed.
📥 lifecycle events: app.email_delivered, app.email_opened, app.email_clicked.
Акт 7 — User unsubscribes
User action: Кликает unsubscribe в footer email.
Provider returns webhook → pulse-core:
📥 app.email_unsubscribed (purpose: ops)
unsubscribe_method = footer_link
📥 app.consent_changed
purpose_id = marketing.email,new_state = revoked,source = system
🗃️ pulse_curated.consent_grants — revoke row.
⚡ Дальнейшие send_marketing_email для этого subject auto-suppress'аются (pulse_internal.decision_suppressed.suppression_reason = consent_missing).
Акт 8 — GDPR right-to-access request
User action: Settings → Privacy → "Export my data".
App calls POST /gdpr/access с subject auth token.
🗃️ pulse_runtime.gdpr_jobs — new row, kind = access, status = pending.
pulse-core-gdpr worker:
- Resolves identity-mapping (
resolve_subjectop). - Aggregates from
feature_store,events_summary(counts per event_type),decisions,outbound_events,consent_grants,audit_log. - Generates Merkle proof для
audit_recordssubset. - Serializes как
GdprAccessBundle(см.openapi.yaml). - Uploads bundle to signed URL.
📥 pulse_internal.gdpr_job_completed (purpose: meta, retention 5 лет)
job_kind = access,status = completed,duration_ms
🗃️ pulse_runtime.audit_log — entry gdpr.access с actor_kind = subject.
Акт 9 — User уходит (право-на-забвение)
User action: "Delete my account" в Privacy settings.
App calls POST /gdpr/erasure.
🗃️ gdpr_jobs(kind=erasure) queued. pulse-core-gdpr worker (≤24 h SLA):
identity_mapping.erase_subject— mapping markederased_at.pulse-retention-worker(erasure mode) deletes rows fromevents,decisions,outbound_events,feature_storematching subject_id (через index, не partition drop).audit_logНЕ модифицируется — subject_id остаётся как «висящий псевдоним», но не resolvable.- Pulse-client SDK на пользовательском устройстве — отдельная responsibility (App handles local wipe).
📥 pulse_internal.gdpr_job_completed (kind=erasure).
🗃️ audit_log — gdpr.erase entry.
📥 Per spec §10.8.5: data.partition_dropped audit entries записываются при следующем drop'е партиций, где этот subject_id участвовал.
Cross-cutting инварианты
В любой момент:
- Consent gating — ни одно событие не попадает в
pulse_runtime.eventsбез валидного consent token для егоpurpose(§9.1.4-bis). - Idempotency — никакое action не диспетчится дважды для одного idempotency_key (§7.9.5).
- Audit chain — каждая admin/compliance операция → row в
pulse_runtime.audit_logс Merkle-hash chain (§9.3 + §10.6). - Identity isolation — pulse-core НИКОГДА не видит raw
identity_id. Толькоsubject_idчерез identity-mapping (§9.1). - Privacy invariant 11 — субjект NEVER targeted hard'ом по archetype в
applies_when(§9.2.6). - Retention — данные субъекта живут не дольше per-event
retention_days(§10.8). - Federation drift — если central и edge видят разные content-hashes контракта, ingest деградирует, не падает silently (§11).
Что НЕ покрыто M0
Из этого journey не часть M0:
- DSL grammar (правила вручную через Directus admin → YAML, без формального parser'а).
- ML inference (никаких
ml.*лookup в rules). - LLM Gateway (никаких генерируемых текстов; только pre-approved templates).
- A/B experiments (
pulse_curated.experimentstable есть, ассайнмент на client-side hash-split в M1+). - Cohort sync to external CDP (Customer.io / Iterable — M2+).
- Survey administration (M2+).
- Marketing patterns library (
pulse_curated.marketing_patternstable seeded, но constraints validator — M3+).
См. полный M0-M5 roadmap в specification.md §15.
Полезные ссылки
specification.md— полная архитектура.contracts/— все machine-readable артефакты.contracts/event-types-overview.md— таблица event-types.testing.md— тест-стратегия.marketing-patterns.md— каталог приёмов.