Pulse — Implementation Status (vs spec)
Версия: v0.1 · Дата: 2026-05-28 · Синхронизация: spec v0.14.8
Авторитетный снимок того, что в
kontinuum-pulse/реально реализовано по сравнению с обещаниямиspecification.md§15 M0. Этот файл живёт отдельно от спеки, потому что спека описывает архитектуру и инварианты (которые не меняются), а здесь — фиксируется статус кода и расхождения, которые иначе пришлось бы вылавливать из git-log'а submodule.Этот документ — single source of truth по «что работает сегодня». При расхождении со спекой спека описывает целевое поведение; этот файл — текущий снимок реализации.
TL;DR
- Backend M0: фактически закрыт по содержанию (decision spine, dispatch, audit, GDPR, ingest, DSL endpoints, feature_store, identity-mapping, federation skeleton). Чекбоксы §15 M0 в спеке
[ ]отстают от кода и обновляются вместе с этим документом, а не каждым PR. - UI M0: ортогонален — backend выставляет endpoint'ы, Directus admin тянет коллекции
pulse_curated.*как обычный CRUD. Кастомный UI (dsl-editor,metric-picker,action-picker) — M1. - Decision spine: замкнут end-to-end (
events → features → hydration → rules → preflight → dispatch → ack/audit) с реальным провенансом во всех записяхpulse_runtime.decisions. - §7.4 unified dispatch: единый
dispatch_decisionдля всех side_effect классов; preflight (consent/rate-limit/brand) на движковом пути; фейк-провенансM0.STUBустранён. - Известные блокеры вне pulse-core: identity/CRM-слой (App↔identity-mapping registration + §13.4) — без него outbound email не отправляется (engine-created decisions parked в
deferred); marketing-pseudonym конфлируется сsubject_id(см. ниже).
§15 M0 checklist vs код
Backend
| Спека требует | Статус | Где живёт |
|---|---|---|
kontinuum-pulse/ submodule, Nx project + Cargo workspace | ✅ | kontinuum-pulse/, 8 крейтов |
| Contract compiler MVP: YAML → Rust types, TS types, JSON schema | ✅ Rust + JSON; TS типы — через codegen для SDK | pulse-contracts/, pulse-client/ |
| Telemetry contract: 3-5 event-типов | ✅ 35 event-типов | contracts/telemetry/v0.1.0.yaml |
| Action contract: 2-3 actions | ✅ 12 actions (show_ui_banner, send_transactional_email, send_marketing_email, log_only, archive_rule, auto_rollback_model, propose_priority_change, flag_compliance_review, throttle_action_target, sync_cohort, show_survey, etc.) | contracts/actions/v0.1.0.yaml |
| Ingest-сервис с consent-token проверкой | ✅ COSE_Sign1 + Ed25519 + TOFU client-key bootstrap (§9.1.4-bis) | pulse-core/src/ingest.rs, pulse-consent/ |
| Identity-mapping service (отдельная БД) | ✅ Отдельный binary, изолированный Postgres (port 15433); реализованы /pseudonymize + /erase_subject; resolve_subject / resolve_marketing — pending (нужна App-side identity-registration) | identity-mapping/ |
| Feature Store online | ✅ Writer (feature_derive) + reader (subject_context query-time оконные counts из events, §9.2.7) | pulse-core/src/feature_derive.rs, subject_context.rs |
| Audit Log append-only + Merkle-chain локально | ✅ Genesis row + prev_hash/entry_hash chain; DHT-публикация Merkle-корней — M3 | pulse-core/src/audit.rs |
POST /api/dsl/validate | ✅ | pulse-core/src/dsl_validate.rs |
POST /api/templates/validate | ✅ | pulse-core/src/templates_validate.rs |
POST /api/events/ingest | ✅ | pulse-core/src/ingest.rs |
| Action dispatcher | ✅ /actions/dispatch (engine creates row → handler appends ack, §7.4) + retry-worker | pulse-core/src/actions_dispatch.rs, outbound_retry_worker.rs, dispatch.rs |
| Brand-guardrails verifier — класс A | ✅ Template-lint (unsubscribe link, sender, forbidden helpers, structural checks для marketing) | pulse-core/src/template_lint.rs (через pulse_core::template_lint) |
Сверх §15 M0 (фактически в коде):
- Decision engine (§8.3.3): in-memory compiled rule cache,
applies_when+forbidden_when, priority/tie-break, оба evaluation-пути (on_event+ cohort-scan), experiment + template_variant ranking (§8.4 + §9.2.6), реальный провенанс, idempotency. - §7.4 unified dispatch tail (
crate::dispatch::dispatch_decision): single source of truth; preflight (consent/rate-limit/brand) на движковом пути; audit append для каждой dispatch'нутой decision. - Pre-flight для outbound_communication / billing_* (§7.4 invariants): consent / rate-limit / template lint. Спека держит «Auto-audit pre-flight» в §15 M2 — фактически landed в M0 в обоих местах (
/actions/dispatchи движковый путь). - Meta-периметр (§15-ter):
rule_stats,anomaly_detect,champion_challenger,auto_rollback,auto_tune,auto_deprecation,self_healing,pattern_mining,cohort_discovery,feature_suggest,drift,privacy_incidents,dp_budget,event_tape,audit_explorer,audit_publish,clickhouse_replicator,erasure_worker— все есть как воркеры/endpoint'ы. ⚠️ см. известные баги ниже. - LLM Gateway (§8.5.2): OpenAI / Anthropic backends, tool catalog, brand guardrails class A на rendered content. (
pulse-core/src/llm_gateway.rs.) - Federation (§11): libp2p + Kademlia DHT для distribution контрактов (
pulse-federation/,federation_swarm.rs). /rules/reload— deploy hook для in-process rule cache;/rules/promote+/rules/archiveждут reload inline.
UI (по §15 M0)
| Спека требует | Статус |
|---|---|
Схема pulse_curated в shared Postgres как Directus коллекции | ✅ DDL накатывается миграциями, Directus 11 поднимается через bootstrap/docker-compose.yaml |
DSL правил — textarea с server-side /dsl/validate | Не реализовано в pulse-core (UI-сторона; throwaway-подход M0) |
| Базовые Insights через Directus Insights | Не реализовано в pulse-core (UI-сторона) |
| Consent UI в App | App-команда, не pulse |
UI throwaway-секция M0 — ортогональна backend'у; backend выставляет endpoint'ы, Directus admin тянет коллекции pulse_curated.* напрямую.
Хронология реализации decision spine
Память сессии 2026-05-25 (memory/kontinuum-pulse-decision-spine.md) фиксировала «полый центр»: четырёхпробельная цепочка derivation → hydration → orchestration → провенанс была разорвана. Что произошло с тех пор:
| Дата | Звено | Что реализовано |
|---|---|---|
| 2026-05-25 | #1 derivation | feature_derive.rs — единственный writer в pulse_runtime.feature_store; realtime-set-on-event (install_completed/startup/tier_changed/payment_received/onboarding_finished); врезка после INSERT events в ingest |
| 2026-05-25 | #2 hydration | subject_context.rs — SubjectContext из feature_store (subject.* dims) + query-time оконные COUNT из events (fork A) + load_consent из pulse_curated.consent_grants (fork D, consent-половина) |
| 2026-05-25 | #3 orchestration | decision_engine.rs — in-memory cache; on_event-evaluation; priority/tie-break; forbidden_when; applies_when; experiment assign + variant_required (§8.4); archetype template-variant ranking (§9.2.6); cohort-scan worker (§8.3.3 scan path); /rules/reload deploy hook |
| 2026-05-25 | #4 provenance | INSERT в pulse_runtime.decisions с реальным rule_id/rule_uuid/subject_id/contract_hash/triggering_event_id; детерминированный decision_id через blake3 от rendered idempotency_key (§7.9.5, fork C) |
| 2026-05-28 | §7.4 unified dispatch | dispatch.rs::dispatch_decision — single source of truth для записи decisions; pre-flight (consent/rate-limit/brand) на движковом пути; audit append (§8.3.3); per-side-effect ack-policy (none/ui_state → success; outbound/billing → deferred parked) |
| 2026-05-28 | /actions/dispatch refactor | M0.STUB / нулевой subject устранены; append-ack mode на engine-created decisions (UPSERT ack); manual-путь требует реальный subject_id и пишет rule_id='EXTERNAL.MANUAL' |
| 2026-05-28 | retry-worker parked-aware | next_retry_at IS NULL = parked, worker не подбирает (раньше NULLS FIRST → engine-deferred падали с to_address missing) |
| 2026-06-01 | Acquisition attribution foundation (Plan 1) | analytics.acquisition purpose + app.acquisition_attributed event (pii:false UTM) landed; feature_derive projects first-touch UTM (first-touch-immutable) and accumulates payment gross_revenue_cents_total into feature_store.extra; campaign→subject→revenue seam sqlx-verified. Client emit (V1) + V4/V2/V3 are follow-on plans. |
Live verification (fd_verify, prosessione 2026-05-28)
Полный матрица проверок track-B на запущенном pulse-core (порт 18090, БД fd_verify, postgres-shared):
| # | Сценарий | Ожидание | Факт |
|---|---|---|---|
| 1 | R_TXN (outbound), app.startup БЕЗ consent billing_transactional | preflight refuse, 0 decisions, audit consent_missing | ✅ log decision pre-flight refused — no dispatch; audit row brand_guardrail_violation |
| 2 | R_TXN, consent granted | decision real provenance, ack deferred parked | ✅ rule=R_TXN v1 uuid=6fd7d1f8.., ack=deferred next_retry=NULL, audit decision.dispatched class=outbound_communication |
| 3 | POST /actions/dispatch новый id, без subject_id | 400 subject_id_required | ✅ (M0.STUB удалён) |
| 4 | POST /actions/dispatch новый id + реальный subject_id | row rule_id=EXTERNAL.MANUAL, реальный subject | ✅ |
| 5 | POST /actions/dispatch на engine-created decision_id | provenance сохранён, ack продвинут | ✅ rule по-прежнему R_TXN, ack deferred → success |
| 6 | RVAR active (archive R_TXN, reload), app.startup | banner ui_state, ack success | ✅ |
| 7 | replay app.startup | idempotent no-op (1 decision) | ✅ |
196 unit-тестов pulse-core зелёные, cargo clippy -p pulse-core чист.
Известные открытые границы (M0 simplifications)
Эти известны и не закрываются в M0 — заявляются явно, чтобы не выглядеть как баги:
Identity / CRM (fork C) — ✅ pulse-сторона закрыта (2026-05-30), pending только внешнее
- ✅
identity-mappingреализует/pseudonymize,/erase_subject,/resolve_marketing,/resolve_marketing_contact(+ health/version). Последний — broker subject_id → (marketing_subject_id, email, opt_status) через cross-DBbilling.customers ⋈ customer_subjects; privacy-инвариант §3.1 соблюдён (только IM видит (subject_id, email)). - ✅ Recipient resolution wired в pulse-core:
dispatch_decisionзовётIdentityMappingClient::resolve_marketing_contact, встраивает_m0_to_addressи ставит ackdeferredscheduled (next_retry_atset → retry-worker подхватывает).SubjectNotFound/customer_not_mapped→deferred parked(честный фолбэк). Consent ключится по реальному marketing-pseudonym (resolve_consent_key→resolve_marketing). - ✅ Send-путь реальный:
provider/sendgrid.rs(reqwest, base_url override черезPULSE_PROVIDER_BASE_URL_<ID>), retry-worker (POLL 30s) →outbound_events.delivered. - ✅ Live E2E (fd_verify, 2026-05-30):
app.startup(cccc)→ R_TXN (consentbilling_transactionalчерез pseudonym8373aec1…) → dispatch → IM resolve →test-c2@kontinuum.cloud→ ackdeferred scheduled(НЕ parked) → retry-worker → mock-SendGrid получил POST/v3/mail/sendс верным получателем →outbound_events = dev_sendgrid_txn|delivered. Seed: IM callerpulse-core-dispatchingpubkeyd04ab232…(seed11..11),outbound_providers.dev_sendgrid_txn, cccc tier=free + billing-маппинг. - ⏳ Pending — только внешнее (не pulse): (1) App-side identity-registration наполняет
identity_mapping.subjects(m0-journey Акт 0); (2) billing-данные (billing.customers/customer_subjects) — Directus/CRM (§13.4); (3) боевой ключ провайдера. Без них в проде outbound остаётся parked — но код пути доказан. /actions/dispatchсохраняет legacy «create+dispatch» режим для ручных/тестовых вызовов с явнымto_address; требует реальныйsubject_id, тегируетrule_id='EXTERNAL.MANUAL'.- ℹ️
resolve_subject(обратное направление marketing→subject) — не требуется для outbound; остаётся для GDPR-путей.
ML / Modelling (M1)
- ✅ Inference backend — landed (Track A, 2026-05-29).
ml.*@vNвapplies_whenтеперь резолвится в рантайме: движок (decision_engine::populate_ml) собирает union ml-ссылок по кандидатам, строит фич-вектор (ml_features::build_inference_features, serve-side твин экспорта) и скорит через pure-Rust LightGBM-предиктор (lgbm.rs, parity с Python booster). Пайплайн:export_training_data(point-in-time, без утечки) →pulse-trainer(LightGBM + регистрация вml_models) → инференс. Live-проверено наfd_verify. Остаётся (M1 хвост): trainer как cron-сервис, drift/champion-challenger на реальных скорах, реальный инференс в/dsl/simulate.
Spec места, помеченные «в M2+», но фактически landed в M0
- Auto-audit pre-flight для
outbound_communication/billing_*(§15 M2) — реализован:pulse_core::preflight::check(lib) гоняет consent / rate-limit / template-lint. Включён и на/actions/dispatch(исторически), и на движковом пути (с 2026-05-28). - GDPR access / erasure / portability (§15 M2) — endpoint'ы и worker уже есть в M0 (нужны для дев-стенда и smoke-проверок).
- Meta-periметр (§15 M3): rule_stats / anomaly_detect / champion_challenger / drift / pattern_mining / cohort_discovery / auto_rollback / auto_tune / auto_deprecation / self_healing — структурно реализованы; теперь, когда центр перестал быть полым, они впервые читают реальные решения (а не M0.STUB).
Известные баги / опер-фиксы
- ✅
make_interval(hours => double precision) does not exist(был вauto_rollback/auto_tune) — закрыт (commit10b98f6).make_interval(hours/days/mins => …)требуетinteger, толькоsecs—double precision; воркеры теперь биндят window какi32. Проверено live наfd_verify(2026-05-29): оба воркера выполняютscan_onceна старте безWARN; эмпирическиmake_interval(hours => $1::int4)резолвится,float8— нет. - ⚠️ Append-ack режим
/actions/dispatchне валидирует консистентностьreq.action_typevsdecisions.action_type— handler берёт ack-policy изreq.action_type, существующая decision row не свериться. Minor дыра (не разлом). По §7.4 «engine creates the row, dispatch appends ack» params/action_type существующей decision row должны быть авторитетными; handler в режиме append-ack стоит брать их оттуда.
Где расходится текущая документация
| Файл / раздел | Что говорит | Реальность |
|---|---|---|
specification.md §15 M0 backend | Чек-боксы [ ] на ingest/identity-mapping/feature store/audit/dispatcher/DSL endpoints/brand-guardrails | Все эти позиции закрыты — см. таблицу выше |
specification.md §15 M0 «Следующие шаги» (хвост документа) | «Создание kontinuum-pulse/ submodule», «Начало M0 implementation» | Submodule создан и идёт сверх M0 |
specification.md §15 M2 «Auto-audit pre-flight» | Описан как M2-работа | Фактически landed в M0 (см. выше) |
index.md секция «Статус» | v0.14.0 (2026-05-19) — M0-готовность | Спек на v0.14.8; M0 backend готов |
kontinuum-pulse/README.md (старая редакция) | «engine dispatches ui_state/none directly; outbound routes through /actions/dispatch» | Снято правкой 2026-05-28 — теперь все классы через unified dispatch_decision |
m0-user-journey.md (sync с spec v0.14.3) | Не упоминает unified dispatch / preflight на движке | Совместим с реализацией (журней описывает поведение пользователя, не внутренние switch'и) — формально не устарел, но стоит синхронизировать в следующем pass'е |
testing.md | Декларация классов тестов | Совместим |
Forward (что осталось закрыть)
В порядке приоритета. Не блокируют M0 production-ready по фактическому содержанию (т.к. М0 backend закрыт), но закрывают опер-долг / расширяют поведение:
Track D — опер-фиксы и валидация ожившей периферии
- ✅ Баг
make_intervalвauto_rollback/auto_tune— закрыт (commit10b98f6, см. раздел багов). - ✅ Append-ack валидация action_type в
/actions/dispatch— закрыта (actions_dispatch.rs:264-279: при append-ack режиме сверяетсяreq.action_typeсdecisions.action_type, иначе409 action_type_mismatch; провенанс авторитетен). - ✅ Пул БД (§10.7.6):
max_connections=20,acquire_timeout=5s(pulse-db). Базовый replan сделан. - ✅ Перф — dedup hot-path (2026-05-30): на outbound-событии preflight больше НЕ резолвит marketing-pseudonym через IM повторно и не перезапрашивает
consent_grants— движок прокидывает уже гидрированныйctx.consent_grantedвpreflight::check/dispatch_decision(новый параметрgranted_consent; ручной/actions/dispatchшлётNoneи резолвит сам). Минус 1 IM-хоп + 1 запрос на событие. Покрыто#[sqlx::test](provided-set passes без запроса / пустой set refuse) + live-перепроверен Track C E2E (resolve→scheduled→delivered цел). - ⏳ Latency под нагрузкой (§10.7): обрыва не найдено (пул=20,
eventsпо индексуevents_by_subject). Полноценный load-тест под 1k ev/s + возможный composite-индекс(subject_id,generation,event_type,emitted_at)— отложено до сигнала (нет боттлнека без профайла). - ✅ Интеграционные тесты
dispatch_decision— мигрированы на#[sqlx::test](свежая изолированная БД на тест из./migrations, авто-cleanup, честный CI-fail без БД вместо тихого skip). 5 сценариев: none/ui_state → success, outbound→deferred parked, идемпотентный no-op, preflight-refuse без consent. Запуск:make test-dbилиDATABASE_URL=… cargo test(роль с CREATEDB). Кросс-сервисный outbound E2E остаётся live-проверкой (3 процесса, не лезет в один sqlx::test). - Проверить, что meta-периметр оживает на реальных решениях —
rule_stats_worker,champion_challenger,anomaly_detect,auto_deprecationчитают реальныеpulse_runtime.decisions. Верификация (не код).
Track A — реальный ML-слой (M1)
- ✅
ml.*@vNрезолв вapplies_when(churn_prob_7d / churn_prob_30d / intensity_score, §15 M1) — landed 2026-05-29. - ✅ Offline trainer (LightGBM по §8.2.2); feature-export pipeline; inference backend (вместо stub) — landed.
- ✅ trainer как cron-сервис (§8.2.2 «cron / Nx target») — landed 2026-05-29.
pulse-trainer/cron.pyоркестрирует полный циклexport → train → register(режимы--onceдля Nx/CI и циклический сPULSE_TRAINER_INTERVAL_SECS); compose-сервисpulse-trainer(profiletrainer) + общий томpulse-modelsс pulse-core; Makefiletrainer-once/-up/-down. Заодно починена immutability версий: артефакт пишется в{model_id}.v{N}.txt(раньше все версии перезаписывали один файл). Live-проверено наfd_verify(полный цикл + immutability v1 при retrain). - ⏳ drift / champion-challenger на реальных скорах моделей (meta-периметр теперь может жевать ML).
- ✅
POST /api/dsl/simulate— dry-run правила: теперь резолвитml.X@vNper-subject против зарегистрированных моделей (общийdecision_engine::resolve_ml_refsс движком), ручнойml-вектор остаётся как what-if override; резолвнутые скоры возвращаются в ответе. Флагresolve_ml(default on), no-op в db-less. Live-проверено наfd_verify. - ⏳ SMT-валидатор правил (Z3 binding) — частично уже в
pulse-dsl-parser/(628 LOC).
Track C — identity / CRM-слой (разблокирует outbound) — ✅ pulse-сторона closed
- ✅
identity-mapping:resolve_marketing+resolve_marketing_contact+marketing_subjectslifecycle реализованы. - ✅ CRM integration (§13.4) — recipient resolution из
billing.*через IM broker (privacy-инвариант соблюдён). - ✅ Wired + live-проверено end-to-end: outbound decisions отвязываются от
deferred parked→deferred scheduled→ retry-worker → доставка (см. раздел «Identity / CRM» выше). - ⏳ Pending — внешнее: App-side identity-registration (
identity_mapping.subjects), боевые billing-данные, боевой ключ провайдера.resolve_subjectдля outbound не нужен.
Spec hygiene (минорно)
- Расставить
[x]в §15 M0 backend chunk-checklist (после ревью командой). - Перенести «Auto-audit pre-flight» из §15 M2 в §15 M0 done-секцию.
- Синхронизировать
index.mdсекцию «Статус» с v0.14.8. - Опционально: новая запись в
m0-user-journey.mdпро engine-path preflight (рядом с Актом 4 «banner»).
Ссылки:
specification.md— архитектурная спецификация (v0.14.8)m0-user-journey.md— end-to-end App user journeytesting.md— operational testing guidecontracts/— meta-schemas + M0 contract instances + DDLkontinuum-pulse/README.md— README submodule с current-scope чек-листомkontinuum-pulse/memory/kontinuum-pulse-decision-spine.md(внутренний) — history фиксов decision spine