Skip to content

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 workspacekontinuum-pulse/, 8 крейтов
Contract compiler MVP: YAML → Rust types, TS types, JSON schema✅ Rust + JSON; TS типы — через codegen для SDKpulse-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-корней — M3pulse-core/src/audit.rs
POST /api/dsl/validatepulse-core/src/dsl_validate.rs
POST /api/templates/validatepulse-core/src/templates_validate.rs
POST /api/events/ingestpulse-core/src/ingest.rs
Action dispatcher/actions/dispatch (engine creates row → handler appends ack, §7.4) + retry-workerpulse-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 в AppApp-команда, не 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 derivationfeature_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 hydrationsubject_context.rsSubjectContext из feature_store (subject.* dims) + query-time оконные COUNT из events (fork A) + load_consent из pulse_curated.consent_grants (fork D, consent-половина)
2026-05-25#3 orchestrationdecision_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 provenanceINSERT в 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 dispatchdispatch.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 refactorM0.STUB / нулевой subject устранены; append-ack mode на engine-created decisions (UPSERT ack); manual-путь требует реальный subject_id и пишет rule_id='EXTERNAL.MANUAL'
2026-05-28retry-worker parked-awarenext_retry_at IS NULL = parked, worker не подбирает (раньше NULLS FIRST → engine-deferred падали с to_address missing)
2026-06-01Acquisition 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):

#СценарийОжиданиеФакт
1R_TXN (outbound), app.startup БЕЗ consent billing_transactionalpreflight refuse, 0 decisions, audit consent_missing✅ log decision pre-flight refused — no dispatch; audit row brand_guardrail_violation
2R_TXN, consent granteddecision real provenance, ack deferred parkedrule=R_TXN v1 uuid=6fd7d1f8.., ack=deferred next_retry=NULL, audit decision.dispatched class=outbound_communication
3POST /actions/dispatch новый id, без subject_id400 subject_id_required✅ (M0.STUB удалён)
4POST /actions/dispatch новый id + реальный subject_idrow rule_id=EXTERNAL.MANUAL, реальный subject
5POST /actions/dispatch на engine-created decision_idprovenance сохранён, ack продвинут✅ rule по-прежнему R_TXN, ack deferred → success
6RVAR active (archive R_TXN, reload), app.startupbanner ui_state, ack success
7replay app.startupidempotent 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-DB billing.customers ⋈ customer_subjects; privacy-инвариант §3.1 соблюдён (только IM видит (subject_id, email)).
  • Recipient resolution wired в pulse-core: dispatch_decision зовёт IdentityMappingClient::resolve_marketing_contact, встраивает _m0_to_address и ставит ack deferred scheduled (next_retry_at set → retry-worker подхватывает). SubjectNotFound/customer_not_mappeddeferred parked (честный фолбэк). Consent ключится по реальному marketing-pseudonym (resolve_consent_keyresolve_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 (consent billing_transactional через pseudonym 8373aec1…) → dispatch → IM resolve → test-c2@kontinuum.cloud → ack deferred scheduled (НЕ parked) → retry-worker → mock-SendGrid получил POST /v3/mail/send с верным получателем → outbound_events = dev_sendgrid_txn|delivered. Seed: IM caller pulse-core-dispatching pubkey d04ab232… (seed 11..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) — закрыт (commit 10b98f6). make_interval(hours/days/mins => …) требует integer, только secsdouble precision; воркеры теперь биндят window как i32. Проверено live на fd_verify (2026-05-29): оба воркера выполняют scan_once на старте без WARN; эмпирически make_interval(hours => $1::int4) резолвится, float8 — нет.
  • ⚠️ Append-ack режим /actions/dispatch не валидирует консистентность req.action_type vs decisions.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 — закрыт (commit 10b98f6, см. раздел багов).
  • 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 (profile trainer) + общий том pulse-models с pulse-core; Makefile trainer-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@vN per-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_subjects lifecycle реализованы.
  • ✅ CRM integration (§13.4) — recipient resolution из billing.* через IM broker (privacy-инвариант соблюдён).
  • ✅ Wired + live-проверено end-to-end: outbound decisions отвязываются от deferred parkeddeferred 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 journey
  • testing.md — operational testing guide
  • contracts/ — meta-schemas + M0 contract instances + DDL
  • kontinuum-pulse/README.md — README submodule с current-scope чек-листом
  • kontinuum-pulse/memory/kontinuum-pulse-decision-spine.md (внутренний) — history фиксов decision spine