Skip to content
Open
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
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ unless Jacob asks for that variant by name.
| Remote | `git@github.com:dinglebear-ai/soma.git` |
| Default branch | `main` |
| Former names | `rmcp-template`, then `rtemplate-mcp` — both GitHub names now redirect here. Fix any local remote still pointing at `jmagar/*`. |
| Workspace | 41 cargo members (largest in the fleet) under `crates/soma/*`, `crates/shared/*`, `crates/integrations/*`, `apps/soma`, `packages/python`, plus `xtask`. Note `apps/web` and `apps/palette` are **not** cargo members — they are frontend assets. |
| Workspace | 44 cargo members (largest in the fleet) under `crates/soma/*`, `crates/shared/*`, `crates/integrations/*`, `apps/soma`, `packages/python`, plus `xtask`. Note `apps/web` and `apps/palette` are **not** cargo members — they are frontend assets. |
| rmcp pin | `rmcp = { version = "=3.1.0", default-features = false }` — an **exact** pin in `[workspace.dependencies]`, deliberately duplicated on the `rmcp-client` alias entry (`Cargo.toml:81`) because TOML cannot cross-reference. Bump both together. |

`crates/soma/*` is product code (this server). `crates/shared/*` is reusable
Expand Down
74 changes: 74 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ members = [
"crates/shared/cli-core",
"crates/shared/codemode",
"crates/shared/codex-app-server-client",
"crates/shared/cortex/domain",
"crates/shared/cortex/ingest-core",
"crates/shared/cortex/inventory",
"crates/shared/cortex/storage-sqlite",
"crates/shared/http-api",
"crates/shared/http-server",
"crates/shared/incus-client",
Expand Down Expand Up @@ -120,7 +123,10 @@ pyo3 = { version = "0.29", features = ["abi3-py311"] }
zeroize = { version = "1", features = ["zeroize_derive"] }

# Shared crates
cortex-domain = { path = "crates/shared/cortex/domain" }
cortex-ingest-core = { path = "crates/shared/cortex/ingest-core" }
cortex-inventory = { path = "crates/shared/cortex/inventory" }
cortex-storage-sqlite = { path = "crates/shared/cortex/storage-sqlite" }
rmcp-traces = { path = "crates/shared/traces" }
soma-auth = { path = "crates/shared/auth" }
soma-cli-core = { path = "crates/shared/cli-core" }
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -666,12 +666,12 @@ just validate-plugin

### Workspace layout

41 cargo members:
44 cargo members:

| Path | Contents |
|---|---|
| `crates/soma/*` | Product code for this server — domain, application, config, client, api, cli, mcp, runtime, integrations, palette, web, test-support |
| `crates/shared/*` | Reusable engine crates other servers consume — auth, mcp (client/server/proxy/gateway), provider-core, provider-adapters, http-api, http-server, observability, openapi, self-update, traces, codemode, cli-core, and namespaced reusable families such as `cortex/ingest-core` |
| `crates/shared/*` | Reusable engine crates other servers consume — auth, mcp (client/server/proxy/gateway), provider-core, provider-adapters, http-api, http-server, observability, openapi, self-update, traces, codemode, cli-core, and namespaced reusable families such as `cortex/domain`, `cortex/ingest-core`, `cortex/inventory`, and `cortex/storage-sqlite` |
| `crates/integrations/*` | Upstream service bridges — `gotify`, `unifi` |
| `apps/soma` | The `soma` binary and its integration tests. **The only cargo member under `apps/`** — `apps/web` (Next.js) and `apps/palette` (assets) are not Rust crates. |
| `packages/python` | pyo3 Python provider platform (`abi3-py311`) |
Expand Down
32 changes: 32 additions & 0 deletions crates/shared/cortex/domain/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[package]
name = "cortex-domain"
version = "0.1.0"
edition.workspace = true
rust-version.workspace = true
authors.workspace = true
description = "Storage- and transport-neutral domain contracts and deterministic reasoning primitives extracted from Cortex."
homepage.workspace = true
license = "AGPL-3.0-only"
repository.workspace = true
readme = "README.md"
keywords = ["observability", "logs", "incidents", "domain"]
categories = ["development-tools::debugging", "data-structures"]
publish = false

[package.metadata.soma-architecture]
layer = "shared"

[package.metadata.docs.rs]
all-features = true

[features]
default = []

[dependencies]
chrono = "0.4"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
thiserror = "2"

[lints]
workspace = true
46 changes: 46 additions & 0 deletions crates/shared/cortex/domain/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# cortex-domain

`cortex-domain` is the storage- and transport-neutral semantic core extracted
from Cortex into Soma. It contains data and deterministic rules whose meaning
does not depend on SQLite, Axum, RMCP, CLI parsing, process globals, scanner
implementations, file-tail runtime state, or host configuration.

## What belongs here

- request identity used by application/domain policy;
- normalized log and incident entities;
- heartbeat state, pressure/status policy, and derived fleet/correlation summaries;
- graph entities, relationships, evidence, deterministic narratives, and confidence policy;
- AI incident/event entities used by deterministic finding engines;
- investigation claims and evidence summaries;
- topology findings and stable reason/category constants;
- deterministic hook/MCP/skill signal detectors;
- stable observatory identity-key construction;
- domain validation/not-found errors.

The deterministic incident, hook, MCP, and skill finding engines were moved with
their donor parity tests because they are pure rule evaluation: no database
queries and no model calls.

## What does not belong here

Transport request/response envelopes, REST/MCP-specific limit policy, SQLite
rows and maintenance results, database statistics, persistence conversions, OS
journal responses, file-tail operations, scanner health implementation types,
receiver counters, inventory collection/runtime state, notification runtime config, and
process/runtime state are intentionally excluded. Pure inventory snapshot contracts live in
`cortex-inventory`, not in the domain crate.

Database row conversions are owned by the planned `cortex-storage-sqlite`
adapter. Transport envelopes are owned by the planned `cortex-api` and
`cortex-mcp` crates. Runtime-only state belongs to the capability/runtime crate
that produces it.

## Provenance and compatibility

The extraction baseline is Cortex commit
`7edf23fadb94650c2d2a2f9c80111fb44319eea8`. Public semantic field names and
serde behavior are preserved for extracted types. See
`docs/cortex-extraction/MODEL-CLASSIFICATION.md` for the complete donor model
ownership inventory and `docs/cortex-extraction/VERIFICATION.md` for parity
gates.
74 changes: 74 additions & 0 deletions crates/shared/cortex/domain/src/actor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RequestActor {
pub surface: String,
pub display: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub subject: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub email: Option<String>,
}

impl RequestActor {
pub fn new(surface: impl Into<String>, display: impl Into<String>) -> Self {
Self {
surface: surface.into(),
display: display.into(),
subject: None,
email: None,
}
}

pub fn api() -> Self {
Self::new("api", "api")
}

pub fn cli() -> Self {
Self::new("cli", "cli")
}

pub fn mcp_loopback() -> Self {
Self::new("mcp", "mcp:loopback")
}

pub fn mcp_bearer() -> Self {
Self::new("mcp", "mcp:bearer")
}

pub fn mcp_oauth() -> Self {
Self::new("mcp", "mcp:oauth")
}

pub fn mcp_identity(subject: Option<String>, email: Option<String>) -> Self {
let display = email
.as_deref()
.filter(|value| !value.is_empty())
.or_else(|| subject.as_deref().filter(|value| !value.is_empty()))
.unwrap_or("mcp:oauth")
.to_string();
Self {
surface: "mcp".to_string(),
display,
subject,
email,
}
}
}

impl From<&str> for RequestActor {
fn from(value: &str) -> Self {
Self::new("unknown", value)
}
}

impl From<String> for RequestActor {
fn from(value: String) -> Self {
Self::new("unknown", value)
}
}

#[cfg(test)]
#[path = "actor_tests.rs"]
mod tests;
16 changes: 16 additions & 0 deletions crates/shared/cortex/domain/src/actor_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use super::*;

#[test]
fn mcp_identity_prefers_verified_email_for_display() {
let actor = RequestActor::mcp_identity(Some("sub-123".into()), Some("me@example.com".into()));
assert_eq!(actor.surface, "mcp");
assert_eq!(actor.display, "me@example.com");
assert_eq!(actor.subject.as_deref(), Some("sub-123"));
assert_eq!(actor.email.as_deref(), Some("me@example.com"));
}

#[test]
fn request_actor_wire_shape_matches_donor() {
let value = serde_json::to_value(RequestActor::api()).unwrap();
assert_eq!(value, serde_json::json!({"surface":"api","display":"api"}));
}
Loading
Loading