Skip to content
This repository was archived by the owner on Aug 7, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 18 additions & 7 deletions scenarios/crm-helm/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
[package]
name = "example-crm-helm-showcase"
name = "scenario-crm-helm"
version = "0.0.0"
edition = "2024"
publish = false
description = "Headless CRM Helm scenario: 7 gRPC modules assembled over an in-memory kernel store, driven without TCP or RunwayAppHost"

[lib]
name = "scenario_crm_helm"
path = "src/lib.rs"

[[bin]]
name = "crm-helm-showcase"
name = "crm-helm"
path = "src/main.rs"

[dependencies]
anyhow.workspace = true
async-trait.workspace = true
anyhow = "1"
async-trait = "0.1"
chrono.workspace = true
serde.workspace = true
serde_json.workspace = true
Expand All @@ -28,13 +33,19 @@ prost-types = "0.13"
# Axum — not in workspace deps
axum = "0.8"

# Tower for in-process request routing (oneshot)
tower = { version = "0.5", features = ["util"] }
http = "1"
http-body-util = "0.1"

# Cross-repo: Helms domain crates (no converge-* deps)
application-kernel = { path = "../../../bedrock-platform/helms/crates/application-kernel" }
application-storage = { path = "../../../bedrock-platform/helms/crates/application-storage" }

# Cross-repo: Runtime Runway app host
runway-app-host = { path = "../../../runtime-runway/crates/runway-app-host" }
runway-storage = { path = "../../../runtime-runway/crates/runway-storage" }
# Cross-repo: Helms contracts — HelmModule trait + substrate (memory + sse)
# RP-HELMS-SUBSTRATE-SEAM (RFL-171 T7: headless atelier copy — no runway deps)
helm-module-contracts = { path = "../../../bedrock-platform/helms/contracts/crates/helm-module-contracts" }
helm-event-substrate = { path = "../../../bedrock-platform/helms/contracts/crates/helm-event-substrate", features = ["memory", "sse"] }

[build-dependencies]
tonic-build = "0.12"
40 changes: 40 additions & 0 deletions scenarios/crm-helm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# crm-helm — headless CRM composition over the in-memory substrate

Repatriated from `helms/showcase/` (RFL-171 Seam A): this scenario now depends
only on Bedrock-owned contracts — `helm-module-contracts` (module mounting) and
`helm-event-substrate` (event hub, leases; `memory` + `sse` features) — plus the
in-memory `AppKernelStore`. No `runway-*` dependency (`cargo tree | grep runway`
is empty by design).

## What it demonstrates

Seven CRM capability modules (parties, documents, facts, conversations,
opportunities, workflow, metadata) assembled into one axum `Router` via the
`HelmModule` contract, driven headless with `tower::ServiceExt::oneshot` —
no TCP bind, no `RunwayAppHost`, no `StorageKit`. Output is JSONL events in
the same style as the other `helm-*-headless` scenarios.

## Honest state (assembly.complete event reports it)

- `hub_consumers: 0 / lease_consumers: 0` — the `EventHub` and
`InMemoryLeaseStore` are allocated and injectable, but no module consumes
them yet.
- The 7 mounted routers are status surfaces; the real gRPC service structs
(e.g. `PartiesGrpc`) are not wired into the mounted axum surface yet.
- Both gaps are tracked as RFL-155 stress-gate scope: this scenario graduates
when consumer counts are > 0 and real services answer through the mount.

## The deployed variant

The runway-backed composition (RunwayAppHost + StorageKit + serve) lives in
the helms repo at `apps/crm-helm` — app-platform territory. This scenario and
that app share the same module code; they differ only in the injected
substrate implementations (in-memory here, runway there). That symmetry is
the point of the seam.

## Run

```bash
cargo run -p scenario-crm-helm # JSONL to stdout, terminates
cargo test -p scenario-crm-helm # router + lease integration tests
```
12 changes: 11 additions & 1 deletion scenarios/crm-helm/src/conversations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use application_kernel::{ActivityAppend, CommunicationRecord};
use application_storage::{AppKernelStore, InMemoryKernelStore, KernelStore};
use async_trait::async_trait;
use runway_app_host::HelmModule;
use helm_module_contracts::HelmModule;
use tokio::sync::mpsc;
use tokio_stream::wrappers::ReceiverStream;
use tonic::{Request, Response, Status};
Expand Down Expand Up @@ -156,4 +156,14 @@ impl HelmModule for ConversationsModule {
async fn init(&self) -> anyhow::Result<()> {
Ok(())
}

fn router(self: std::sync::Arc<Self>) -> axum::Router {
use axum::{Json, routing::get};
axum::Router::new().route(
"/crm/conversations/status",
get(|| async {
Json(serde_json::json!({ "module": "crm.conversations", "status": "ok" }))
}),
)
}
}
12 changes: 11 additions & 1 deletion scenarios/crm-helm/src/documents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use application_kernel::{DocumentAttach, NoteAppend};
use application_storage::{AppKernelStore, InMemoryKernelStore, KernelStore};
use async_trait::async_trait;
use runway_app_host::HelmModule;
use helm_module_contracts::HelmModule;
use tonic::{Request, Response, Status};

use crate::proto::{common as pb, documents as documents_pb};
Expand Down Expand Up @@ -116,4 +116,14 @@ impl HelmModule for DocumentsModule {
async fn init(&self) -> anyhow::Result<()> {
Ok(())
}

fn router(self: std::sync::Arc<Self>) -> axum::Router {
use axum::{Json, routing::get};
axum::Router::new().route(
"/crm/documents/status",
get(|| async {
Json(serde_json::json!({ "module": "crm.documents", "status": "ok" }))
}),
)
}
}
10 changes: 9 additions & 1 deletion scenarios/crm-helm/src/facts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use application_kernel::FactRecord;
use application_storage::{AppKernelStore, InMemoryKernelStore, KernelStore};
use async_trait::async_trait;
use runway_app_host::HelmModule;
use helm_module_contracts::HelmModule;
use tonic::{Request, Response, Status};

use crate::proto::{common as pb, facts as facts_pb};
Expand Down Expand Up @@ -91,4 +91,12 @@ impl HelmModule for FactsModule {
async fn init(&self) -> anyhow::Result<()> {
Ok(())
}

fn router(self: std::sync::Arc<Self>) -> axum::Router {
use axum::{Json, routing::get};
axum::Router::new().route(
"/crm/facts/status",
get(|| async { Json(serde_json::json!({ "module": "crm.facts", "status": "ok" })) }),
)
}
}
Loading
Loading