0.6 rust重构01

This commit is contained in:
lix-2026
2026-04-14 13:22:29 +08:00
parent 71fb1aee7e
commit 84a8454fa9
401 changed files with 5841 additions and 1512 deletions
+38
View File
@@ -0,0 +1,38 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "core-domain"
version = "0.1.0"
[[package]]
name = "core-protocol"
version = "0.1.0"
dependencies = [
"core-domain",
]
[[package]]
name = "event-log"
version = "0.1.0"
dependencies = [
"core-domain",
]
[[package]]
name = "index-fts"
version = "0.1.0"
dependencies = [
"core-domain",
"event-log",
]
[[package]]
name = "storage-convex-bridge"
version = "0.1.0"
dependencies = [
"core-domain",
"core-protocol",
"event-log",
]
+18
View File
@@ -0,0 +1,18 @@
[workspace]
members = [
"crates/core-domain",
"crates/core-protocol",
"crates/event-log",
"crates/storage-convex-bridge",
"crates/index-fts",
]
resolver = "2"
[workspace.package]
edition = "2021"
license = "MIT"
version = "0.1.0"
authors = ["mnote"]
[workspace.metadata]
description = "mnote 单仓收口阶段的 Rust 内核 workspace"
+8
View File
@@ -0,0 +1,8 @@
[package]
name = "core-domain"
version.workspace = true
edition.workspace = true
license.workspace = true
authors.workspace = true
[dependencies]
+6
View File
@@ -0,0 +1,6 @@
# core-domain
阶段 1 骨架 crate。
职责说明请先看:
- ../../design/phase1-b-crate-responsibilities-v0.md
+43
View File
@@ -0,0 +1,43 @@
use crate::ids::{AssetId, AssetVersionId, BlockId, PageId, TaskId};
use crate::time::Timestamp;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ActorRef {
pub actor_type: String,
pub actor_id: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SourceKind {
Web,
Cli,
Agent,
Job,
Import,
System,
Other(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ChangeReason {
pub code: String,
pub detail: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RefLink {
Page(PageId),
Block(BlockId),
Asset(AssetId),
AssetVersion(AssetVersionId),
Task(TaskId),
External(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AuditInfo {
pub created_at: Timestamp,
pub updated_at: Timestamp,
pub created_by: Option<ActorRef>,
pub updated_by: Option<ActorRef>,
}
+289
View File
@@ -0,0 +1,289 @@
use crate::audit::{ActorRef, AuditInfo, ChangeReason, RefLink, SourceKind};
use crate::ids::{
AgentSessionId, AssetId, AssetVersionId, BlockId, CommandLogId, EventId, PageId, ReferenceId,
TaskId, WorkspaceId,
};
use crate::time::Timestamp;
use crate::types::{Locale, Revision};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Workspace {
pub workspace_id: WorkspaceId,
pub slug: String,
pub title: String,
pub owner_actor_id: String,
pub default_locale: Locale,
pub storage_policy: String,
pub sync_policy: String,
pub feature_flags: Vec<String>,
pub archived_at: Option<Timestamp>,
pub audit: AuditInfo,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PageType {
Note,
Doc,
DatabaseRecord,
Inbox,
Template,
System,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PageStatus {
Active,
Archived,
Deleted,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Page {
pub page_id: PageId,
pub workspace_id: WorkspaceId,
pub parent_page_id: Option<PageId>,
pub title: String,
pub slug: String,
pub icon: Option<String>,
pub cover_asset_id: Option<AssetId>,
pub body_root_block_id: Option<BlockId>,
pub page_type: PageType,
pub status: PageStatus,
pub last_edited_at: Option<Timestamp>,
pub last_edited_by: Option<ActorRef>,
pub current_revision: Revision,
pub audit: AuditInfo,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BlockType {
Paragraph,
Heading,
BulletedListItem,
NumberedListItem,
Todo,
Quote,
CodeBlock,
Divider,
Callout,
PageReference,
BlockReference,
EmbedAsset,
EmbedView,
EmbedOnlyoffice,
EmbedMindmap,
EmbedTable,
Custom(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Block {
pub block_id: BlockId,
pub workspace_id: WorkspaceId,
pub page_id: PageId,
pub parent_block_id: Option<BlockId>,
pub prev_block_id: Option<BlockId>,
pub next_block_id: Option<BlockId>,
pub sort_key: String,
pub block_type: BlockType,
pub content: String,
pub props: Vec<(String, String)>,
pub annotations: Vec<String>,
pub status: String,
pub revision: Revision,
pub deleted_at: Option<Timestamp>,
pub audit: AuditInfo,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AssetKind {
Image,
Office,
Pdf,
Audio,
Video,
Export,
OcrDerived,
Other(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AssetStatus {
Active,
Archived,
Deleted,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Asset {
pub asset_id: AssetId,
pub workspace_id: WorkspaceId,
pub storage_key: String,
pub original_name: String,
pub mime_type: String,
pub size_bytes: u64,
pub checksum: Option<String>,
pub origin: String,
pub asset_kind: AssetKind,
pub status: AssetStatus,
pub latest_version_id: Option<AssetVersionId>,
pub audit: AuditInfo,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AssetVersion {
pub asset_version_id: AssetVersionId,
pub asset_id: AssetId,
pub version_no: u32,
pub storage_key: String,
pub checksum: Option<String>,
pub derived_from_version_id: Option<AssetVersionId>,
pub change_reason: Option<ChangeReason>,
pub created_at: Timestamp,
pub created_by: Option<ActorRef>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ReferenceKind {
PageToPage,
BlockToBlock,
BlockToPage,
BlockToAssetFragment,
TaskToObject,
External,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Reference {
pub reference_id: ReferenceId,
pub workspace_id: WorkspaceId,
pub source_object_type: String,
pub source_object_id: String,
pub target_object_type: String,
pub target_object_id: String,
pub anchor: String,
pub label: Option<String>,
pub snippet: Option<String>,
pub confidence: Option<String>,
pub ref_kind: ReferenceKind,
pub created_at: Timestamp,
pub created_by: Option<ActorRef>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TaskType {
Import,
Ocr,
Summary,
Reindex,
Sync,
Transform,
Other(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TaskStatus {
Pending,
Running,
Succeeded,
Failed,
Cancelled,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TaskPriority {
Low,
Normal,
High,
Critical,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Task {
pub task_id: TaskId,
pub workspace_id: WorkspaceId,
pub title: String,
pub description: Option<String>,
pub task_type: TaskType,
pub status: TaskStatus,
pub priority: TaskPriority,
pub assignee_actor_id: Option<String>,
pub source_page_id: Option<PageId>,
pub source_block_id: Option<BlockId>,
pub input_payload: Option<String>,
pub output_payload: Option<String>,
pub due_at: Option<Timestamp>,
pub completed_at: Option<Timestamp>,
pub audit: AuditInfo,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AgentSession {
pub agent_session_id: AgentSessionId,
pub workspace_id: WorkspaceId,
pub provider: String,
pub model: String,
pub initiator_actor_id: Option<String>,
pub status: String,
pub started_at: Timestamp,
pub updated_at: Timestamp,
pub ended_at: Option<Timestamp>,
pub tool_policy: Option<String>,
pub confirmation_policy: Option<String>,
pub summary: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CommandStatus {
Pending,
Succeeded,
Failed,
Cancelled,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CommandLog {
pub command_log_id: CommandLogId,
pub workspace_id: WorkspaceId,
pub command_name: String,
pub actor: ActorRef,
pub source: SourceKind,
pub target_objects: Vec<RefLink>,
pub payload_summary: String,
pub refs: Vec<RefLink>,
pub idempotency_key: Option<String>,
pub dry_run: bool,
pub status: CommandStatus,
pub created_at: Timestamp,
pub finished_at: Option<Timestamp>,
pub error_code: Option<String>,
pub error_message: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EventType {
PageCreated,
PageUpdated,
BlockInserted,
BlockMoved,
BlockUpdated,
BlockDeleted,
AssetVersionAdded,
ReferenceCreated,
TaskUpdated,
Other(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DomainEvent {
pub event_id: EventId,
pub workspace_id: WorkspaceId,
pub command_log_id: CommandLogId,
pub aggregate_type: String,
pub aggregate_id: String,
pub event_type: EventType,
pub before_revision: Option<Revision>,
pub after_revision: Option<Revision>,
pub payload_json: String,
pub created_at: Timestamp,
}
+27
View File
@@ -0,0 +1,27 @@
macro_rules! define_id {
($name:ident) => {
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct $name(pub String);
impl $name {
pub fn new(value: impl Into<String>) -> Self {
Self(value.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
};
}
define_id!(WorkspaceId);
define_id!(PageId);
define_id!(BlockId);
define_id!(AssetId);
define_id!(AssetVersionId);
define_id!(ReferenceId);
define_id!(TaskId);
define_id!(AgentSessionId);
define_id!(CommandLogId);
define_id!(EventId);
+34
View File
@@ -0,0 +1,34 @@
pub mod audit;
pub mod entities;
pub mod ids;
pub mod time;
pub mod types;
pub use audit::{ActorRef, AuditInfo, ChangeReason, RefLink, SourceKind};
pub use entities::{
AgentSession, Asset, AssetKind, AssetStatus, AssetVersion, Block, BlockType, CommandLog,
CommandStatus, DomainEvent, EventType, Page, PageStatus, PageType, Reference, ReferenceKind,
Task, TaskPriority, TaskStatus, TaskType, Workspace,
};
pub use ids::{
AgentSessionId, AssetId, AssetVersionId, BlockId, CommandLogId, EventId, PageId, ReferenceId,
TaskId, WorkspaceId,
};
pub use time::Timestamp;
pub use types::{Locale, Revision};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn revision_initial_is_one() {
assert_eq!(Revision::initial().0, 1);
}
#[test]
fn workspace_id_keeps_original_value() {
let workspace_id = WorkspaceId::new("ws_demo");
assert_eq!(workspace_id.as_str(), "ws_demo");
}
}
+12
View File
@@ -0,0 +1,12 @@
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Timestamp(pub String);
impl Timestamp {
pub fn new(value: impl Into<String>) -> Self {
Self(value.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
+17
View File
@@ -0,0 +1,17 @@
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Revision(pub u64);
impl Revision {
pub fn initial() -> Self {
Self(1)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Locale(pub String);
impl Locale {
pub fn new(value: impl Into<String>) -> Self {
Self(value.into())
}
}
+9
View File
@@ -0,0 +1,9 @@
[package]
name = "core-protocol"
version.workspace = true
edition.workspace = true
license.workspace = true
authors.workspace = true
[dependencies]
core-domain = { path = "../core-domain" }
+8
View File
@@ -0,0 +1,8 @@
# core-protocol
阶段 1 协议模型 crate。
职责说明请先看:
- ../../design/phase1-d-command-api-v0.md
- ../../design/phase1-d-query-api-v0.md
- ../../design/phase1-d-tool-api-constraints-v0.md
+101
View File
@@ -0,0 +1,101 @@
use crate::common::{ActorPayload, AffectedObject, SourcePayload, TargetRef};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CommandEnvelope<T> {
pub name: String,
pub command_id: String,
pub idempotency_key: Option<String>,
pub actor: ActorPayload,
pub source: SourcePayload,
pub target: Option<TargetRef>,
pub payload: T,
pub reason: Option<String>,
pub refs: Vec<String>,
pub dry_run: bool,
pub validate_only: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CommandResult {
pub ok: bool,
pub command_id: String,
pub event_ids: Vec<String>,
pub affected_objects: Vec<AffectedObject>,
pub revision: Option<u64>,
pub warnings: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CreateWorkspace {
pub name: String,
pub slug: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CreatePage {
pub title: String,
pub parent_page_id: Option<String>,
pub position: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UpdatePageTitle {
pub page_id: String,
pub title: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UpdatePageStats {
pub page_id: String,
pub word_count: i64,
pub character_count: i64,
pub block_count: i64,
pub todo_total: i64,
pub todo_done: i64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UpdatePageOptions {
pub page_id: String,
pub wide_layout: Option<bool>,
pub small_text: Option<bool>,
pub show_heading_numbers: Option<bool>,
pub show_toc: Option<bool>,
pub show_structure: Option<bool>,
pub protect_editing: Option<bool>,
pub show_word_count: Option<bool>,
pub collapse_backlinks: Option<bool>,
pub page_font: Option<String>,
pub layout_density: Option<String>,
pub hide_child_pages: Option<bool>,
pub show_block_ref_count: Option<bool>,
pub embed_default_block_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InsertBlock {
pub page_id: String,
pub block_type: String,
pub content: String,
pub parent_block_id: Option<String>,
pub prev_block_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UpdateBlock {
pub block_id: String,
pub patch_content: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MoveBlock {
pub block_id: String,
pub new_parent_block_id: Option<String>,
pub new_page_id: Option<String>,
pub prev_block_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DeleteBlock {
pub block_id: String,
}
+59
View File
@@ -0,0 +1,59 @@
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ActorPayload {
pub actor_type: String,
pub actor_id: String,
pub session_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SourcePayload {
pub channel: String,
pub client: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TargetRef {
pub workspace_id: Option<String>,
pub page_id: Option<String>,
pub block_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RequestMeta {
pub idempotency_key: Option<String>,
pub validate_only: bool,
pub dry_run: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AffectedObject {
pub object_type: String,
pub object_id: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ResponseMeta {
pub request_id: String,
pub trace_id: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OkPayload<T> {
pub data: T,
pub meta: ResponseMeta,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ErrorDetail {
pub field: Option<String>,
pub reason: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ErrorPayload {
pub code: String,
pub message: String,
pub details: Vec<ErrorDetail>,
pub retryable: bool,
pub meta: ResponseMeta,
}
+116
View File
@@ -0,0 +1,116 @@
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AccessContext {
pub tenant_id: Option<String>,
pub workspace_id: String,
pub actor_id: String,
pub actor_type: String,
pub source: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AccessDecision {
Allow,
Deny(AccessDenyReason),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AccessDenyReason {
MissingTenant,
MissingWorkspace,
PermissionDenied,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum JobStatus {
Pending,
Running,
Succeeded,
Failed,
RetryScheduled,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct JobTicket {
pub job_id: String,
pub job_type: String,
pub request_id: String,
pub trace_id: String,
pub workspace_id: String,
pub status: JobStatus,
}
impl JobTicket {
pub fn new(
job_id: impl Into<String>,
job_type: impl Into<String>,
request_id: impl Into<String>,
trace_id: impl Into<String>,
workspace_id: impl Into<String>,
status: JobStatus,
) -> Self {
Self {
job_id: job_id.into(),
job_type: job_type.into(),
request_id: request_id.into(),
trace_id: trace_id.into(),
workspace_id: workspace_id.into(),
status,
}
}
}
pub fn decide_access(context: &AccessContext) -> AccessDecision {
if context.tenant_id.is_none() {
return AccessDecision::Deny(AccessDenyReason::MissingTenant);
}
if context.workspace_id.trim().is_empty() {
return AccessDecision::Deny(AccessDenyReason::MissingWorkspace);
}
AccessDecision::Allow
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn access_requires_tenant() {
let context = AccessContext {
tenant_id: None,
workspace_id: "ws-1".to_string(),
actor_id: "user-1".to_string(),
actor_type: "human".to_string(),
source: "react-next".to_string(),
};
assert_eq!(
decide_access(&context),
AccessDecision::Deny(AccessDenyReason::MissingTenant)
);
}
#[test]
fn access_allows_valid_context() {
let context = AccessContext {
tenant_id: Some("tenant-1".to_string()),
workspace_id: "ws-1".to_string(),
actor_id: "user-1".to_string(),
actor_type: "human".to_string(),
source: "react-next".to_string(),
};
assert_eq!(decide_access(&context), AccessDecision::Allow);
}
#[test]
fn job_ticket_keeps_trace_fields() {
let ticket = JobTicket::new(
"job-1",
"rebuild-index",
"req-1",
"trace-1",
"ws-1",
JobStatus::Pending,
);
assert_eq!(ticket.trace_id, "trace-1");
assert_eq!(ticket.status, JobStatus::Pending);
}
}
+50
View File
@@ -0,0 +1,50 @@
pub mod command;
pub mod common;
pub mod governance;
pub mod query;
pub mod tool;
pub use command::{
CommandEnvelope, CreatePage, CreateWorkspace, DeleteBlock, InsertBlock, MoveBlock,
UpdateBlock, UpdatePageStats, UpdatePageTitle,
};
pub use common::{
ActorPayload, AffectedObject, ErrorDetail, ErrorPayload, OkPayload, RequestMeta, ResponseMeta,
SourcePayload, TargetRef,
};
pub use query::{GetPage, ListPageBlocks, QueryEnvelope, SearchBlocks, SearchPages};
pub use tool::{InvocationKind, ToolInvocation};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn command_envelope_keeps_validate_only_flag() {
let envelope = CommandEnvelope::<CreateWorkspace> {
name: "create_workspace".into(),
command_id: "cmd_1".into(),
idempotency_key: Some("idem_1".into()),
actor: ActorPayload {
actor_type: "human".into(),
actor_id: "user_1".into(),
session_id: Some("session_1".into()),
},
source: SourcePayload {
channel: "cli".into(),
client: "mnote-cli".into(),
},
target: None,
payload: CreateWorkspace {
name: "demo".into(),
slug: Some("demo".into()),
},
reason: Some("初始化工作区".into()),
refs: vec![],
dry_run: false,
validate_only: true,
};
assert!(envelope.validate_only);
}
}
+36
View File
@@ -0,0 +1,36 @@
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct QueryEnvelope<T> {
pub name: String,
pub payload: T,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Pagination {
pub limit: u32,
pub cursor: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GetPage {
pub page_id: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ListPageBlocks {
pub page_id: String,
pub pagination: Pagination,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SearchPages {
pub query: String,
pub workspace_id: Option<String>,
pub pagination: Pagination,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SearchBlocks {
pub query: String,
pub page_id: Option<String>,
pub pagination: Pagination,
}
+13
View File
@@ -0,0 +1,13 @@
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InvocationKind {
Command,
Query,
Job,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ToolInvocation {
pub tool: String,
pub kind: InvocationKind,
pub args_json: String,
}
+9
View File
@@ -0,0 +1,9 @@
[package]
name = "event-log"
version.workspace = true
edition.workspace = true
license.workspace = true
authors.workspace = true
[dependencies]
core-domain = { path = "../core-domain" }
+8
View File
@@ -0,0 +1,8 @@
# event-log
阶段 1 事件日志 crate。
职责说明请先看:
- ../../design/phase1-e-command-log-v0.md
- ../../design/phase1-e-domain-event-v0.md
- ../../design/phase1-e-event-generation-rules-v0.md
+29
View File
@@ -0,0 +1,29 @@
use core_domain::Timestamp;
use core_domain::{RefLink, WorkspaceId};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CommandLogStatus {
Pending,
Succeeded,
Failed,
RolledBack,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CommandLogRecord {
pub command_log_id: String,
pub command_name: String,
pub actor_type: String,
pub actor_id: String,
pub source: String,
pub workspace_id: WorkspaceId,
pub target_objects: Vec<RefLink>,
pub payload_summary: String,
pub refs_json: String,
pub idempotency_key: Option<String>,
pub status: CommandLogStatus,
pub created_at: Timestamp,
pub finished_at: Option<Timestamp>,
pub trace_id: String,
pub request_id: String,
}
+36
View File
@@ -0,0 +1,36 @@
use core_domain::Timestamp;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EventStatus {
Pending,
Committed,
Rejected,
Failed,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DomainEventRecord {
pub event_id: String,
pub workspace_id: String,
pub aggregate_type: String,
pub aggregate_id: String,
pub event_type: String,
pub event_version: u32,
pub payload_json: String,
pub command_log_id: String,
pub actor_type: String,
pub created_at: Timestamp,
pub status: EventStatus,
pub trace_id: String,
pub request_id: String,
pub command_id: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EventGenerationOutcome {
pub command_log_id: String,
pub emitted_event_ids: Vec<String>,
pub generated_for_replay: bool,
pub generated_for_audit: bool,
pub generated_for_index_catchup: bool,
}
+23
View File
@@ -0,0 +1,23 @@
pub mod command_log;
pub mod domain_event;
pub mod rules;
pub use command_log::{CommandLogRecord, CommandLogStatus};
pub use domain_event::{DomainEventRecord, EventGenerationOutcome, EventStatus};
pub use rules::{should_emit_domain_event, EventGenerationRule};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn failed_command_does_not_emit_success_event() {
let should_emit = should_emit_domain_event(&EventGenerationRule {
command_succeeded: false,
changed_domain_state: false,
rolled_back: false,
});
assert!(!should_emit);
}
}
+10
View File
@@ -0,0 +1,10 @@
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EventGenerationRule {
pub command_succeeded: bool,
pub changed_domain_state: bool,
pub rolled_back: bool,
}
pub fn should_emit_domain_event(rule: &EventGenerationRule) -> bool {
rule.command_succeeded && rule.changed_domain_state && !rule.rolled_back
}
+10
View File
@@ -0,0 +1,10 @@
[package]
name = "index-fts"
version.workspace = true
edition.workspace = true
license.workspace = true
authors.workspace = true
[dependencies]
core-domain = { path = "../core-domain" }
event-log = { path = "../event-log" }
+6
View File
@@ -0,0 +1,6 @@
# index-fts
阶段 1 骨架 crate。
职责说明请先看:
- ../../design/phase1-b-crate-responsibilities-v0.md
+355
View File
@@ -0,0 +1,355 @@
use event_log::DomainEventRecord;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IndexedEntityKind {
PageTitle,
PageSummary,
BlockContent,
BlockPath,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IndexedDocument {
pub workspace_id: String,
pub entity_kind: IndexedEntityKind,
pub entity_id: String,
pub parent_id: Option<String>,
pub title: Option<String>,
pub content: String,
pub updated_at: String,
pub source_event_id: Option<String>,
pub revision: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IndexCursor {
pub workspace_id: String,
pub last_processed_event_id: String,
pub last_processed_at: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SearchHit {
pub workspace_id: String,
pub entity_kind: IndexedEntityKind,
pub entity_id: String,
pub snippet: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SearchResultSet {
pub query: String,
pub workspace_id: Option<String>,
pub page_id: Option<String>,
pub hits: Vec<SearchHit>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProjectedDocumentBatch {
pub workspace_id: String,
pub source_event_id: String,
pub documents: Vec<IndexedDocument>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProjectionResult {
pub cursor: IndexCursor,
pub batches: Vec<ProjectedDocumentBatch>,
}
pub trait DomainEventProjector {
fn project(&self, event: &DomainEventRecord) -> Vec<IndexedDocument>;
}
pub fn supported_index_objects() -> Vec<IndexedEntityKind> {
vec![
IndexedEntityKind::PageTitle,
IndexedEntityKind::PageSummary,
IndexedEntityKind::BlockContent,
IndexedEntityKind::BlockPath,
]
}
pub fn can_rebuild_from_events(cursor: &IndexCursor) -> bool {
!cursor.workspace_id.trim().is_empty() && !cursor.last_processed_event_id.trim().is_empty()
}
pub fn advance_workspace_cursor(
cursor: &IndexCursor,
event_id: impl Into<String>,
processed_at: impl Into<String>,
) -> IndexCursor {
IndexCursor {
workspace_id: cursor.workspace_id.clone(),
last_processed_event_id: event_id.into(),
last_processed_at: processed_at.into(),
}
}
pub fn project_domain_event<P: DomainEventProjector>(
projector: &P,
event: &DomainEventRecord,
cursor: &IndexCursor,
) -> ProjectionResult {
let documents = projector.project(event);
ProjectionResult {
cursor: advance_workspace_cursor(
cursor,
event.event_id.clone(),
event.created_at.as_str().to_string(),
),
batches: if documents.is_empty() {
Vec::new()
} else {
vec![ProjectedDocumentBatch {
workspace_id: event.workspace_id.clone(),
source_event_id: event.event_id.clone(),
documents,
}]
},
}
}
pub fn rebuild_from_events<P: DomainEventProjector>(
projector: &P,
events: &[DomainEventRecord],
cursor: &IndexCursor,
) -> ProjectionResult {
let mut next_cursor = cursor.clone();
let mut batches = Vec::new();
for event in events {
if event.workspace_id != cursor.workspace_id {
continue;
}
let projected = projector.project(event);
next_cursor = advance_workspace_cursor(
&next_cursor,
event.event_id.clone(),
event.created_at.as_str().to_string(),
);
if !projected.is_empty() {
batches.push(ProjectedDocumentBatch {
workspace_id: event.workspace_id.clone(),
source_event_id: event.event_id.clone(),
documents: projected,
});
}
}
ProjectionResult {
cursor: next_cursor,
batches,
}
}
pub fn search_pages(
query: &str,
workspace_id: Option<&str>,
documents: &[IndexedDocument],
limit: usize,
) -> SearchResultSet {
let query = query.trim().to_lowercase();
let hits = documents
.iter()
.filter(|document| matches!(document.entity_kind, IndexedEntityKind::PageTitle | IndexedEntityKind::PageSummary))
.filter(|document| workspace_id.map_or(true, |workspace| document.workspace_id == workspace))
.filter(|document| document.title.as_deref().unwrap_or("").to_lowercase().contains(&query)
|| document.content.to_lowercase().contains(&query))
.take(limit)
.map(|document| SearchHit {
workspace_id: document.workspace_id.clone(),
entity_kind: document.entity_kind.clone(),
entity_id: document.entity_id.clone(),
snippet: Some(document.content.chars().take(120).collect()),
})
.collect();
SearchResultSet {
query: query.into(),
workspace_id: workspace_id.map(|value| value.into()),
page_id: None,
hits,
}
}
pub fn search_blocks(
query: &str,
page_id: Option<&str>,
documents: &[IndexedDocument],
limit: usize,
) -> SearchResultSet {
let query = query.trim().to_lowercase();
let hits = documents
.iter()
.filter(|document| matches!(document.entity_kind, IndexedEntityKind::BlockContent | IndexedEntityKind::BlockPath))
.filter(|document| page_id.map_or(true, |page| document.parent_id.as_deref() == Some(page) || document.entity_id == page))
.filter(|document| document.content.to_lowercase().contains(&query))
.take(limit)
.map(|document| SearchHit {
workspace_id: document.workspace_id.clone(),
entity_kind: document.entity_kind.clone(),
entity_id: document.entity_id.clone(),
snippet: Some(document.content.chars().take(120).collect()),
})
.collect();
SearchResultSet {
query: query.into(),
workspace_id: None,
page_id: page_id.map(|value| value.into()),
hits,
}
}
pub struct MinimalWorkspaceProjector;
impl DomainEventProjector for MinimalWorkspaceProjector {
fn project(&self, event: &DomainEventRecord) -> Vec<IndexedDocument> {
let content = event.payload_json.trim();
let (entity_kind, title, parent_id) = match event.aggregate_type.as_str() {
"page" => (
IndexedEntityKind::PageTitle,
Some(event.event_type.clone()),
None,
),
"block" => (
IndexedEntityKind::BlockContent,
None,
Some(event.aggregate_id.clone()),
),
_ => return Vec::new(),
};
vec![IndexedDocument {
workspace_id: event.workspace_id.clone(),
entity_kind,
entity_id: event.aggregate_id.clone(),
parent_id,
title,
content: content.to_string(),
updated_at: event.created_at.as_str().to_string(),
source_event_id: Some(event.event_id.clone()),
revision: Some(event.event_version as u64),
}]
}
}
#[cfg(test)]
mod tests {
use super::*;
use core_domain::Timestamp;
use event_log::EventStatus;
fn cursor() -> IndexCursor {
IndexCursor {
workspace_id: "ws_1".into(),
last_processed_event_id: "evt_0".into(),
last_processed_at: "2026-04-11T00:00:00Z".into(),
}
}
fn event(
workspace_id: &str,
aggregate_type: &str,
aggregate_id: &str,
event_id: &str,
event_type: &str,
) -> DomainEventRecord {
DomainEventRecord {
event_id: event_id.into(),
workspace_id: workspace_id.into(),
aggregate_type: aggregate_type.into(),
aggregate_id: aggregate_id.into(),
event_type: event_type.into(),
event_version: 1,
payload_json: "{\"text\":\"hello\"}".into(),
command_log_id: "cmd_1".into(),
actor_type: "human".into(),
created_at: Timestamp::new("2026-04-11T00:00:01Z"),
status: EventStatus::Committed,
trace_id: "trace_1".into(),
request_id: "req_1".into(),
command_id: "cmd_1".into(),
}
}
#[test]
fn supports_page_and_block_projection_objects() {
assert_eq!(
supported_index_objects(),
vec![
IndexedEntityKind::PageTitle,
IndexedEntityKind::PageSummary,
IndexedEntityKind::BlockContent,
IndexedEntityKind::BlockPath
]
);
}
#[test]
fn cursor_advances_incrementally() {
let next = advance_workspace_cursor(&cursor(), "evt_2", "2026-04-11T00:00:02Z");
assert_eq!(next.last_processed_event_id, "evt_2");
assert_eq!(next.workspace_id, "ws_1");
}
#[test]
fn projector_builds_real_documents_for_page_and_block() {
let projector = MinimalWorkspaceProjector;
let page_docs =
projector.project(&event("ws_1", "page", "page_1", "evt_1", "page.created"));
let block_docs =
projector.project(&event("ws_1", "block", "block_1", "evt_2", "block.created"));
assert_eq!(page_docs.len(), 1);
assert_eq!(block_docs.len(), 1);
assert_eq!(page_docs[0].entity_kind, IndexedEntityKind::PageTitle);
assert_eq!(block_docs[0].entity_kind, IndexedEntityKind::BlockContent);
}
#[test]
fn rebuild_skips_other_workspaces_and_keeps_cursor() {
let projector = MinimalWorkspaceProjector;
let events = vec![
event("ws_2", "page", "page_x", "evt_x", "page.created"),
event("ws_1", "block", "block_1", "evt_1", "block.created"),
];
let result = rebuild_from_events(&projector, &events, &cursor());
assert_eq!(result.batches.len(), 1);
assert_eq!(result.cursor.last_processed_event_id, "evt_1");
}
#[test]
fn search_helpers_return_hits() {
let docs = vec![
IndexedDocument {
workspace_id: "ws_1".into(),
entity_kind: IndexedEntityKind::PageTitle,
entity_id: "page_1".into(),
parent_id: None,
title: Some("Rust Notes".into()),
content: "Rust notes for phase 2".into(),
updated_at: "2026-04-11T00:00:01Z".into(),
source_event_id: Some("evt_1".into()),
revision: Some(1),
},
IndexedDocument {
workspace_id: "ws_1".into(),
entity_kind: IndexedEntityKind::BlockContent,
entity_id: "block_1".into(),
parent_id: Some("page_1".into()),
title: None,
content: "A block about rust search indexing".into(),
updated_at: "2026-04-11T00:00:02Z".into(),
source_event_id: Some("evt_2".into()),
revision: Some(1),
},
];
let pages = search_pages("rust", Some("ws_1"), &docs, 10);
assert_eq!(pages.hits.len(), 1);
assert_eq!(pages.hits[0].entity_id, "page_1");
let blocks = search_blocks("search", Some("page_1"), &docs, 10);
assert_eq!(blocks.hits.len(), 1);
assert_eq!(blocks.hits[0].entity_id, "block_1");
}
}
@@ -0,0 +1,11 @@
[package]
name = "storage-convex-bridge"
version.workspace = true
edition.workspace = true
license.workspace = true
authors.workspace = true
[dependencies]
core-domain = { path = "../core-domain" }
core-protocol = { path = "../core-protocol" }
event-log = { path = "../event-log" }
@@ -0,0 +1,18 @@
# storage-convex-bridge
阶段 1 的 Convex 桥接 crate。
当前职责:
- 把 Rust 协议对象映射为 Convex 读写请求
- 透传 request / actor / workspace / auth 上下文
- 生成最小 CommandLog / DomainEvent 骨架
当前明确不做:
- 不复制主事实层
- 不维护第二数据库
- 不隐藏 tenant / auth / request context
- 不承载复杂业务裁决
相关设计文档:
- `../../design/phase1-f-bridge-responsibilities-v0.md`
- `../../design/phase1-f-write-read-flow-v0.md`
@@ -0,0 +1,18 @@
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BridgeContext {
pub deployment_id: Option<String>,
pub project_id: Option<String>,
pub workspace_id: Option<String>,
pub request_id: String,
pub trace_id: String,
pub actor_type: String,
pub actor_id: String,
pub session_id: Option<String>,
pub tenant_id: Option<String>,
pub auth_token: Option<String>,
pub source_channel: String,
pub source_client: String,
pub idempotency_key: Option<String>,
pub validate_only: bool,
pub dry_run: bool,
}
@@ -0,0 +1,266 @@
pub mod context;
pub mod mapping;
pub mod read_path;
pub mod types;
pub mod validation;
pub mod write_path;
pub use context::BridgeContext;
pub use mapping::{
map_command_name_to_convex, map_query_name_to_convex, ConvexMutationRequest, ConvexQueryRequest,
};
pub use read_path::{build_query_request, QueryReadResult};
pub use types::{BridgeError, BridgeErrorKind, BridgeResult};
pub use validation::{validate_command_envelope, validate_query_envelope};
pub use write_path::{
build_command_log_record, build_domain_event_record, build_write_pipeline, build_write_request,
WritePipelineResult,
};
#[cfg(test)]
mod tests {
use super::*;
use core_protocol::{
command::{CreatePage, CreateWorkspace, UpdatePageOptions, UpdatePageStats, UpdatePageTitle},
query::GetPage,
ActorPayload, CommandEnvelope, QueryEnvelope, SourcePayload, TargetRef,
};
fn demo_context() -> BridgeContext {
BridgeContext {
deployment_id: Some("dep_1".into()),
project_id: Some("proj_1".into()),
request_id: "req_1".into(),
trace_id: "trace_1".into(),
actor_type: "human".into(),
actor_id: "user_1".into(),
session_id: Some("session_1".into()),
workspace_id: Some("ws_1".into()),
tenant_id: Some("tenant_1".into()),
auth_token: Some("token_1".into()),
source_channel: "cli".into(),
source_client: "mnote-cli".into(),
idempotency_key: Some("idem_ctx".into()),
validate_only: false,
dry_run: false,
}
}
#[test]
fn build_write_request_keeps_workspace_scope() {
let command = CommandEnvelope {
name: "create_page".into(),
command_id: "cmd_1".into(),
idempotency_key: Some("idem_cmd".into()),
actor: ActorPayload {
actor_type: "human".into(),
actor_id: "user_1".into(),
session_id: Some("session_1".into()),
},
source: SourcePayload {
channel: "cli".into(),
client: "mnote-cli".into(),
},
target: Some(TargetRef {
workspace_id: Some("ws_1".into()),
page_id: None,
block_id: None,
}),
payload: CreatePage {
title: "第一页".into(),
parent_page_id: None,
position: None,
},
reason: Some("初始化页面".into()),
refs: vec!["spec:phase1-f".into()],
dry_run: false,
validate_only: false,
};
let request = build_write_request(&demo_context(), &command).expect("write request should build");
assert_eq!(request.function_name, "pages:create");
assert_eq!(request.workspace_id.as_deref(), Some("ws_1"));
assert_eq!(request.deployment_id.as_deref(), Some("dep_1"));
assert_eq!(request.project_id.as_deref(), Some("proj_1"));
assert_eq!(request.idempotency_key.as_deref(), Some("idem_cmd"));
assert!(request.payload_json.contains("\"kind\":\"command\""));
assert!(request.payload_json.contains("\"name\":\"create_page\""));
}
#[test]
fn build_query_request_includes_trace_and_workspace() {
let query = QueryEnvelope {
name: "get_page".into(),
payload: GetPage {
page_id: "page_1".into(),
},
};
let request = build_query_request(&demo_context(), &query).expect("query request should build");
assert_eq!(request.function_name, "pages:get");
assert_eq!(request.workspace_id.as_deref(), Some("ws_1"));
assert_eq!(request.request_id, "req_1");
assert_eq!(request.trace_id, "trace_1");
assert!(request.payload_json.contains("\"kind\":\"query\""));
assert!(request.payload_json.contains("\"name\":\"get_page\""));
}
#[test]
fn build_write_pipeline_sets_trace_fields() {
let command = CommandEnvelope {
name: "create_workspace".into(),
command_id: "cmd_2".into(),
idempotency_key: None,
actor: ActorPayload {
actor_type: "human".into(),
actor_id: "user_1".into(),
session_id: Some("session_1".into()),
},
source: SourcePayload {
channel: "cli".into(),
client: "mnote-cli".into(),
},
target: None,
payload: CreateWorkspace {
name: "demo".into(),
slug: Some("demo".into()),
},
reason: Some("初始化工作区".into()),
refs: vec![],
dry_run: false,
validate_only: false,
};
let pipeline = build_write_pipeline(&demo_context(), &command).expect("pipeline should build");
assert_eq!(pipeline.command_log.command_name, "create_workspace");
assert!(pipeline.command_log.payload_summary.contains("request_id=req_1"));
assert_eq!(pipeline.command_log.trace_id, "trace_1");
assert_eq!(pipeline.command_log.request_id, "req_1");
assert!(pipeline.domain_event.payload_json.contains("\"trace_id\":\"trace_1\""));
assert_eq!(pipeline.result.event_ids, vec!["evt_cmd_2".to_string()]);
}
#[test]
fn document_title_command_maps_to_documents_update_title() {
let command = CommandEnvelope {
name: "documents.title.update".into(),
command_id: "cmd_title_1".into(),
idempotency_key: Some("idem_title".into()),
actor: ActorPayload {
actor_type: "human".into(),
actor_id: "user_1".into(),
session_id: Some("session_1".into()),
},
source: SourcePayload {
channel: "next-route".into(),
client: "wolai-frontend".into(),
},
target: Some(TargetRef {
workspace_id: Some("ws_1".into()),
page_id: Some("page_1".into()),
block_id: None,
}),
payload: UpdatePageTitle {
page_id: "page_1".into(),
title: "新标题".into(),
},
reason: None,
refs: vec![],
dry_run: false,
validate_only: false,
};
let request = build_write_request(&demo_context(), &command).expect("write request should build");
assert_eq!(request.function_name, "documents:updateTitle");
assert_eq!(request.workspace_id.as_deref(), Some("ws_1"));
assert!(request.payload_json.contains("\"name\":\"documents.title.update\""));
}
#[test]
fn document_stats_command_maps_to_documents_update_stats() {
let command = CommandEnvelope {
name: "documents.stats.update".into(),
command_id: "cmd_stats_1".into(),
idempotency_key: Some("idem_stats".into()),
actor: ActorPayload {
actor_type: "human".into(),
actor_id: "user_1".into(),
session_id: Some("session_1".into()),
},
source: SourcePayload {
channel: "next-route".into(),
client: "wolai-frontend".into(),
},
target: Some(TargetRef {
workspace_id: Some("ws_1".into()),
page_id: Some("page_1".into()),
block_id: None,
}),
payload: UpdatePageStats {
page_id: "page_1".into(),
word_count: 10,
character_count: 20,
block_count: 3,
todo_total: 4,
todo_done: 1,
},
reason: None,
refs: vec![],
dry_run: false,
validate_only: false,
};
let request = build_write_request(&demo_context(), &command).expect("write request should build");
assert_eq!(request.function_name, "documents:updateStats");
assert_eq!(request.workspace_id.as_deref(), Some("ws_1"));
assert!(request.payload_json.contains("\"name\":\"documents.stats.update\""));
}
#[test]
fn document_options_command_maps_to_documents_update_options() {
let command = CommandEnvelope {
name: "documents.options.update".into(),
command_id: "cmd_options_1".into(),
idempotency_key: Some("idem_options".into()),
actor: ActorPayload {
actor_type: "human".into(),
actor_id: "user_1".into(),
session_id: Some("session_1".into()),
},
source: SourcePayload {
channel: "next-route".into(),
client: "wolai-frontend".into(),
},
target: Some(TargetRef {
workspace_id: Some("ws_1".into()),
page_id: Some("page_1".into()),
block_id: None,
}),
payload: UpdatePageOptions {
page_id: "page_1".into(),
wide_layout: None,
small_text: None,
show_heading_numbers: None,
show_toc: Some(true),
show_structure: None,
protect_editing: None,
show_word_count: None,
collapse_backlinks: None,
page_font: None,
layout_density: Some("compact".into()),
hide_child_pages: None,
show_block_ref_count: None,
embed_default_block_id: None,
},
reason: None,
refs: vec![],
dry_run: false,
validate_only: false,
};
let request = build_write_request(&demo_context(), &command).expect("write request should build");
assert_eq!(request.function_name, "documents:updateOptions");
assert_eq!(request.workspace_id.as_deref(), Some("ws_1"));
assert!(request.payload_json.contains("\"name\":\"documents.options.update\""));
}
}
@@ -0,0 +1,93 @@
use crate::context::BridgeContext;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConvexMutationRequest {
pub function_name: String,
pub deployment_id: Option<String>,
pub project_id: Option<String>,
pub workspace_id: Option<String>,
pub request_id: String,
pub trace_id: String,
pub idempotency_key: Option<String>,
pub actor_id: String,
pub payload_json: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConvexQueryRequest {
pub function_name: String,
pub deployment_id: Option<String>,
pub project_id: Option<String>,
pub workspace_id: Option<String>,
pub request_id: String,
pub trace_id: String,
pub actor_id: String,
pub payload_json: String,
}
pub fn map_command_name_to_convex(command_name: &str) -> &'static str {
match command_name {
"create_workspace" => "workspaces:create",
"create_page" => "pages:create",
"documents.title.update" => "documents:updateTitle",
"documents.stats.update" => "documents:updateStats",
"documents.options.update" => "documents:updateOptions",
"insert_block" => "blocks:insert",
"update_block" => "blocks:update",
"move_block" => "blocks:move",
"delete_block" => "blocks:delete",
_ => "commands:unknown",
}
}
pub fn map_query_name_to_convex(query_name: &str) -> &'static str {
match query_name {
"get_page" => "pages:get",
"list_page_blocks" => "blocks:list_by_page",
"search_pages" => "search:pages",
"search_blocks" => "search:blocks",
_ => "queries:unknown",
}
}
fn json_opt(value: &Option<String>) -> String {
match value {
Some(v) => format!("\"{}\"", v),
None => "null".into(),
}
}
pub fn payload_json_for_command(context: &BridgeContext, command_name: &str) -> String {
format!(
"{{\"kind\":\"command\",\"name\":\"{}\",\"request_id\":\"{}\",\"trace_id\":\"{}\",\"deployment_id\":{},\"project_id\":{},\"workspace_id\":{},\"tenant_id\":{},\"idempotency_key\":{},\"actor\":{{\"type\":\"{}\",\"id\":\"{}\",\"session_id\":{}}},\"source\":{{\"channel\":\"{}\",\"client\":\"{}\"}}}}",
command_name,
context.request_id,
context.trace_id,
json_opt(&context.deployment_id),
json_opt(&context.project_id),
json_opt(&context.workspace_id),
json_opt(&context.tenant_id),
json_opt(&context.idempotency_key),
context.actor_type,
context.actor_id,
json_opt(&context.session_id),
context.source_channel,
context.source_client,
)
}
pub fn payload_json_for_query(context: &BridgeContext, query_name: &str) -> String {
format!(
"{{\"kind\":\"query\",\"name\":\"{}\",\"request_id\":\"{}\",\"trace_id\":\"{}\",\"deployment_id\":{},\"project_id\":{},\"workspace_id\":{},\"tenant_id\":{},\"actor_id\":\"{}\",\"source\":{{\"channel\":\"{}\",\"client\":\"{}\"}}}}",
query_name,
context.request_id,
context.trace_id,
json_opt(&context.deployment_id),
json_opt(&context.project_id),
json_opt(&context.workspace_id),
json_opt(&context.tenant_id),
context.actor_id,
context.source_channel,
context.source_client,
)
}
@@ -0,0 +1,29 @@
use crate::context::BridgeContext;
use crate::mapping::{map_query_name_to_convex, payload_json_for_query, ConvexQueryRequest};
use crate::types::BridgeResult;
use crate::validation::validate_query_envelope;
use core_protocol::QueryEnvelope;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct QueryReadResult {
pub workspace_id: Option<String>,
pub request_id: String,
pub trace_id: String,
}
pub fn build_query_request<T>(
context: &BridgeContext,
query: &QueryEnvelope<T>,
) -> BridgeResult<ConvexQueryRequest> {
validate_query_envelope(context, query)?;
Ok(ConvexQueryRequest {
function_name: map_query_name_to_convex(&query.name).to_string(),
deployment_id: context.deployment_id.clone(),
project_id: context.project_id.clone(),
workspace_id: context.workspace_id.clone(),
request_id: context.request_id.clone(),
trace_id: context.trace_id.clone(),
actor_id: context.actor_id.clone(),
payload_json: payload_json_for_query(context, &query.name),
})
}
@@ -0,0 +1,26 @@
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BridgeErrorKind {
Validation,
Unauthorized,
Conflict,
NotFound,
Transport,
Rejected,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BridgeError {
pub kind: BridgeErrorKind,
pub message: String,
}
impl BridgeError {
pub fn validation(message: impl Into<String>) -> Self {
Self {
kind: BridgeErrorKind::Validation,
message: message.into(),
}
}
}
pub type BridgeResult<T> = Result<T, BridgeError>;
@@ -0,0 +1,50 @@
use crate::context::BridgeContext;
use crate::types::{BridgeError, BridgeResult};
use core_protocol::{CommandEnvelope, QueryEnvelope};
pub fn validate_command_envelope<T>(
context: &BridgeContext,
command: &CommandEnvelope<T>,
) -> BridgeResult<()> {
if command.name.trim().is_empty() {
return Err(BridgeError::validation("command name 不能为空"));
}
if command.command_id.trim().is_empty() {
return Err(BridgeError::validation("command_id 不能为空"));
}
if context.request_id.trim().is_empty() {
return Err(BridgeError::validation("request_id 不能为空"));
}
if context.trace_id.trim().is_empty() {
return Err(BridgeError::validation("trace_id 不能为空"));
}
if context.actor_id.trim().is_empty() {
return Err(BridgeError::validation("actor_id 不能为空"));
}
if context
.workspace_id
.as_deref()
.unwrap_or("")
.trim()
.is_empty()
{
return Err(BridgeError::validation("workspace_id 不能为空"));
}
Ok(())
}
pub fn validate_query_envelope<T>(
context: &BridgeContext,
query: &QueryEnvelope<T>,
) -> BridgeResult<()> {
if query.name.trim().is_empty() {
return Err(BridgeError::validation("query name 不能为空"));
}
if context.request_id.trim().is_empty() {
return Err(BridgeError::validation("request_id 不能为空"));
}
if context.trace_id.trim().is_empty() {
return Err(BridgeError::validation("trace_id 不能为空"));
}
Ok(())
}
@@ -0,0 +1,131 @@
use crate::context::BridgeContext;
use crate::mapping::{map_command_name_to_convex, payload_json_for_command, ConvexMutationRequest};
use crate::types::BridgeResult;
use crate::validation::validate_command_envelope;
use core_domain::{RefLink, Timestamp, WorkspaceId};
use core_protocol::{command::CommandResult, AffectedObject, CommandEnvelope};
use event_log::{CommandLogRecord, CommandLogStatus, DomainEventRecord, EventStatus};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WritePipelineResult {
pub mutation: ConvexMutationRequest,
pub command_log: CommandLogRecord,
pub domain_event: DomainEventRecord,
pub result: CommandResult,
}
pub fn build_write_request<T>(
context: &BridgeContext,
command: &CommandEnvelope<T>,
) -> BridgeResult<ConvexMutationRequest> {
validate_command_envelope(context, command)?;
let workspace_id = command
.target
.as_ref()
.and_then(|target| target.workspace_id.clone())
.or_else(|| context.workspace_id.clone());
Ok(ConvexMutationRequest {
function_name: map_command_name_to_convex(&command.name).to_string(),
deployment_id: context.deployment_id.clone(),
project_id: context.project_id.clone(),
workspace_id,
request_id: context.request_id.clone(),
trace_id: context.trace_id.clone(),
idempotency_key: command
.idempotency_key
.clone()
.or_else(|| context.idempotency_key.clone()),
actor_id: context.actor_id.clone(),
payload_json: payload_json_for_command(context, &command.name),
})
}
pub fn build_command_log_record(
command_id: &str,
command_name: &str,
context: &BridgeContext,
workspace_id: &str,
) -> CommandLogRecord {
CommandLogRecord {
command_log_id: format!("clog_{command_id}"),
command_name: command_name.into(),
actor_type: context.actor_type.clone(),
actor_id: context.actor_id.clone(),
source: context.source_channel.clone(),
workspace_id: WorkspaceId::new(workspace_id),
target_objects: vec![RefLink::External(format!("workspace:{workspace_id}"))],
payload_summary: format!(
"command={command_name};request_id={};trace_id={}",
context.request_id, context.trace_id
),
refs_json: format!(
"[\"request:{}\",\"trace:{}\"]",
context.request_id, context.trace_id
),
idempotency_key: context.idempotency_key.clone(),
status: CommandLogStatus::Pending,
created_at: Timestamp::new("2026-04-11T00:00:00Z"),
finished_at: None,
trace_id: context.trace_id.clone(),
request_id: context.request_id.clone(),
}
}
pub fn build_domain_event_record(
command_id: &str,
command_name: &str,
context: &BridgeContext,
workspace_id: &str,
) -> DomainEventRecord {
DomainEventRecord {
event_id: format!("evt_{command_id}"),
workspace_id: workspace_id.into(),
aggregate_type: "workspace".into(),
aggregate_id: workspace_id.into(),
event_type: format!("{command_name}_requested"),
event_version: 1,
payload_json: format!(
"{{\"request_id\":\"{}\",\"trace_id\":\"{}\",\"command_id\":\"{}\",\"command_name\":\"{}\"}}",
context.request_id, context.trace_id, command_id, command_name
),
command_log_id: format!("clog_{command_id}"),
actor_type: context.actor_type.clone(),
created_at: Timestamp::new("2026-04-11T00:00:00Z"),
status: EventStatus::Pending,
trace_id: context.trace_id.clone(),
request_id: context.request_id.clone(),
command_id: command_id.into(),
}
}
pub fn build_write_pipeline<T>(
context: &BridgeContext,
command: &CommandEnvelope<T>,
) -> BridgeResult<WritePipelineResult> {
let mutation = build_write_request(context, command)?;
let workspace_id = mutation
.workspace_id
.clone()
.unwrap_or_else(|| "workspace_unknown".into());
let command_log =
build_command_log_record(&command.command_id, &command.name, context, &workspace_id);
let domain_event =
build_domain_event_record(&command.command_id, &command.name, context, &workspace_id);
let result = CommandResult {
ok: true,
command_id: command.command_id.clone(),
event_ids: vec![domain_event.event_id.clone()],
affected_objects: vec![AffectedObject {
object_type: "workspace".into(),
object_id: workspace_id.clone(),
}],
revision: Some(1),
warnings: vec![],
};
Ok(WritePipelineResult {
mutation,
command_log,
domain_event,
result,
})
}
+1
View File
@@ -0,0 +1 @@
{"rustc_fingerprint":4704921907237318175,"outputs":{"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/lix/.rustup/toolchains/1.88.0-x86_64-unknown-linux-gnu\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""},"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.88.0 (6b00bc388 2025-06-23)\nbinary: rustc\ncommit-hash: 6b00bc3880198600130e1cf62b8f8a93494488cc\ncommit-date: 2025-06-23\nhost: x86_64-unknown-linux-gnu\nrelease: 1.88.0\nLLVM version: 20.1.5\n","stderr":""}},"successes":{}}
+3
View File
@@ -0,0 +1,3 @@
Signature: 8a477f597d28d172789f06886806bc55
# This file is a cache directory tag created by cargo.
# For information about cache directory tags see https://bford.info/cachedir/
View File
@@ -0,0 +1 @@
This file has an mtime of when this was started.
@@ -0,0 +1 @@
e053498f67f46e4d
@@ -0,0 +1 @@
{"rustc":11410426090777951712,"features":"[]","declared_features":"[]","target":6167971972065703354,"profile":17672942494452627365,"path":7278239953193670039,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/core-domain-40e6f74f7ef1a9aa/dep-lib-core_domain","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
@@ -0,0 +1 @@
This file has an mtime of when this was started.
@@ -0,0 +1 @@
31ec3f0d1a2e5472
@@ -0,0 +1 @@
{"rustc":11410426090777951712,"features":"[]","declared_features":"[]","target":6167971972065703354,"profile":1722584277633009122,"path":7278239953193670039,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/core-domain-bd799d0a25dcc494/dep-test-lib-core_domain","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
@@ -0,0 +1 @@
This file has an mtime of when this was started.
@@ -0,0 +1 @@
4e803ee3d8e7c709
@@ -0,0 +1 @@
{"rustc":11410426090777951712,"features":"[]","declared_features":"[]","target":6167971972065703354,"profile":8731458305071235362,"path":7278239953193670039,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/core-domain-cdff95eec220868e/dep-lib-core_domain","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
@@ -0,0 +1 @@
This file has an mtime of when this was started.
@@ -0,0 +1 @@
44b9888b46dbed5c
@@ -0,0 +1 @@
{"rustc":11410426090777951712,"features":"[]","declared_features":"[]","target":12929318708107190822,"profile":1722584277633009122,"path":17093070036165809465,"deps":[[12060949818064432287,"core_domain",false,704786785418248270]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/core-protocol-0fdc11ecf89ad5aa/dep-test-lib-core_protocol","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
@@ -0,0 +1 @@
This file has an mtime of when this was started.
@@ -0,0 +1 @@
69002a3ff9918d3d
@@ -0,0 +1 @@
{"rustc":11410426090777951712,"features":"[]","declared_features":"[]","target":12929318708107190822,"profile":17672942494452627365,"path":17093070036165809465,"deps":[[12060949818064432287,"core_domain",false,5579665713981379552]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/core-protocol-4592bf3d65d89282/dep-lib-core_protocol","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
@@ -0,0 +1 @@
This file has an mtime of when this was started.
@@ -0,0 +1 @@
49c72387560f483c
@@ -0,0 +1 @@
{"rustc":11410426090777951712,"features":"[]","declared_features":"[]","target":12929318708107190822,"profile":8731458305071235362,"path":17093070036165809465,"deps":[[12060949818064432287,"core_domain",false,704786785418248270]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/core-protocol-f5d7351438cd4f82/dep-lib-core_protocol","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
@@ -0,0 +1 @@
This file has an mtime of when this was started.
@@ -0,0 +1 @@
a1e984aa5f972227
@@ -0,0 +1 @@
{"rustc":11410426090777951712,"features":"[]","declared_features":"[]","target":2075902485319387119,"profile":17672942494452627365,"path":3721623360069338606,"deps":[[12060949818064432287,"core_domain",false,5579665713981379552]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/event-log-563618231cacaf7e/dep-lib-event_log","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
@@ -0,0 +1 @@
This file has an mtime of when this was started.
@@ -0,0 +1 @@
8f57032f1a7da02a
@@ -0,0 +1 @@
{"rustc":11410426090777951712,"features":"[]","declared_features":"[]","target":2075902485319387119,"profile":8731458305071235362,"path":3721623360069338606,"deps":[[12060949818064432287,"core_domain",false,704786785418248270]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/event-log-d9b5c77d292421e8/dep-lib-event_log","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
@@ -0,0 +1 @@
This file has an mtime of when this was started.
@@ -0,0 +1 @@
bf54d8ba9752e161
@@ -0,0 +1 @@
{"rustc":11410426090777951712,"features":"[]","declared_features":"[]","target":1159665536100219803,"profile":17672942494452627365,"path":12914430766979976756,"deps":[[12060949818064432287,"core_domain",false,5579665713981379552],[16479709356797637466,"event_log",false,2819982753825876385]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/index-fts-e444a40571811be1/dep-lib-index_fts","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
@@ -0,0 +1 @@
This file has an mtime of when this was started.
@@ -0,0 +1 @@
{"rustc":11410426090777951712,"features":"[]","declared_features":"[]","target":16973354705979238807,"profile":8731458305071235362,"path":2510431785475190115,"deps":[[7016416594668144918,"core_protocol",false,4343738704907716425],[12060949818064432287,"core_domain",false,704786785418248270],[16479709356797637466,"event_log",false,3071592497278048143]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/storage-convex-bridge-45166d07753c36d7/dep-lib-storage_convex_bridge","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
@@ -0,0 +1 @@
This file has an mtime of when this was started.
@@ -0,0 +1 @@
{"rustc":11410426090777951712,"features":"[]","declared_features":"[]","target":16973354705979238807,"profile":1722584277633009122,"path":2510431785475190115,"deps":[[7016416594668144918,"core_protocol",false,4343738704907716425],[12060949818064432287,"core_domain",false,704786785418248270],[16479709356797637466,"event_log",false,3071592497278048143]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/storage-convex-bridge-5bf1d06211b95150/dep-test-lib-storage_convex_bridge","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
@@ -0,0 +1 @@
This file has an mtime of when this was started.
@@ -0,0 +1 @@
{"rustc":11410426090777951712,"features":"[]","declared_features":"[]","target":16973354705979238807,"profile":17672942494452627365,"path":2510431785475190115,"deps":[[7016416594668144918,"core_protocol",false,4435361707722408041],[12060949818064432287,"core_domain",false,5579665713981379552],[16479709356797637466,"event_log",false,2819982753825876385]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/storage-convex-bridge-cd4527dde6c0c1c6/dep-lib-storage_convex_bridge","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
@@ -0,0 +1,10 @@
/mnt/Data1T/mnote/rust/target/debug/deps/core_domain-40e6f74f7ef1a9aa.d: crates/core-domain/src/lib.rs crates/core-domain/src/audit.rs crates/core-domain/src/entities.rs crates/core-domain/src/ids.rs crates/core-domain/src/time.rs crates/core-domain/src/types.rs
/mnt/Data1T/mnote/rust/target/debug/deps/libcore_domain-40e6f74f7ef1a9aa.rmeta: crates/core-domain/src/lib.rs crates/core-domain/src/audit.rs crates/core-domain/src/entities.rs crates/core-domain/src/ids.rs crates/core-domain/src/time.rs crates/core-domain/src/types.rs
crates/core-domain/src/lib.rs:
crates/core-domain/src/audit.rs:
crates/core-domain/src/entities.rs:
crates/core-domain/src/ids.rs:
crates/core-domain/src/time.rs:
crates/core-domain/src/types.rs:
Binary file not shown.
@@ -0,0 +1,10 @@
/mnt/Data1T/mnote/rust/target/debug/deps/core_domain-bd799d0a25dcc494.d: crates/core-domain/src/lib.rs crates/core-domain/src/audit.rs crates/core-domain/src/entities.rs crates/core-domain/src/ids.rs crates/core-domain/src/time.rs crates/core-domain/src/types.rs
/mnt/Data1T/mnote/rust/target/debug/deps/core_domain-bd799d0a25dcc494: crates/core-domain/src/lib.rs crates/core-domain/src/audit.rs crates/core-domain/src/entities.rs crates/core-domain/src/ids.rs crates/core-domain/src/time.rs crates/core-domain/src/types.rs
crates/core-domain/src/lib.rs:
crates/core-domain/src/audit.rs:
crates/core-domain/src/entities.rs:
crates/core-domain/src/ids.rs:
crates/core-domain/src/time.rs:
crates/core-domain/src/types.rs:
@@ -0,0 +1,12 @@
/mnt/Data1T/mnote/rust/target/debug/deps/core_domain-cdff95eec220868e.d: crates/core-domain/src/lib.rs crates/core-domain/src/audit.rs crates/core-domain/src/entities.rs crates/core-domain/src/ids.rs crates/core-domain/src/time.rs crates/core-domain/src/types.rs
/mnt/Data1T/mnote/rust/target/debug/deps/libcore_domain-cdff95eec220868e.rlib: crates/core-domain/src/lib.rs crates/core-domain/src/audit.rs crates/core-domain/src/entities.rs crates/core-domain/src/ids.rs crates/core-domain/src/time.rs crates/core-domain/src/types.rs
/mnt/Data1T/mnote/rust/target/debug/deps/libcore_domain-cdff95eec220868e.rmeta: crates/core-domain/src/lib.rs crates/core-domain/src/audit.rs crates/core-domain/src/entities.rs crates/core-domain/src/ids.rs crates/core-domain/src/time.rs crates/core-domain/src/types.rs
crates/core-domain/src/lib.rs:
crates/core-domain/src/audit.rs:
crates/core-domain/src/entities.rs:
crates/core-domain/src/ids.rs:
crates/core-domain/src/time.rs:
crates/core-domain/src/types.rs:
@@ -0,0 +1,10 @@
/mnt/Data1T/mnote/rust/target/debug/deps/core_protocol-0fdc11ecf89ad5aa.d: crates/core-protocol/src/lib.rs crates/core-protocol/src/command.rs crates/core-protocol/src/common.rs crates/core-protocol/src/governance.rs crates/core-protocol/src/query.rs crates/core-protocol/src/tool.rs
/mnt/Data1T/mnote/rust/target/debug/deps/core_protocol-0fdc11ecf89ad5aa: crates/core-protocol/src/lib.rs crates/core-protocol/src/command.rs crates/core-protocol/src/common.rs crates/core-protocol/src/governance.rs crates/core-protocol/src/query.rs crates/core-protocol/src/tool.rs
crates/core-protocol/src/lib.rs:
crates/core-protocol/src/command.rs:
crates/core-protocol/src/common.rs:
crates/core-protocol/src/governance.rs:
crates/core-protocol/src/query.rs:
crates/core-protocol/src/tool.rs:
@@ -0,0 +1,10 @@
/mnt/Data1T/mnote/rust/target/debug/deps/core_protocol-4592bf3d65d89282.d: crates/core-protocol/src/lib.rs crates/core-protocol/src/command.rs crates/core-protocol/src/common.rs crates/core-protocol/src/governance.rs crates/core-protocol/src/query.rs crates/core-protocol/src/tool.rs
/mnt/Data1T/mnote/rust/target/debug/deps/libcore_protocol-4592bf3d65d89282.rmeta: crates/core-protocol/src/lib.rs crates/core-protocol/src/command.rs crates/core-protocol/src/common.rs crates/core-protocol/src/governance.rs crates/core-protocol/src/query.rs crates/core-protocol/src/tool.rs
crates/core-protocol/src/lib.rs:
crates/core-protocol/src/command.rs:
crates/core-protocol/src/common.rs:
crates/core-protocol/src/governance.rs:
crates/core-protocol/src/query.rs:
crates/core-protocol/src/tool.rs:
@@ -0,0 +1,12 @@
/mnt/Data1T/mnote/rust/target/debug/deps/core_protocol-f5d7351438cd4f82.d: crates/core-protocol/src/lib.rs crates/core-protocol/src/command.rs crates/core-protocol/src/common.rs crates/core-protocol/src/governance.rs crates/core-protocol/src/query.rs crates/core-protocol/src/tool.rs
/mnt/Data1T/mnote/rust/target/debug/deps/libcore_protocol-f5d7351438cd4f82.rlib: crates/core-protocol/src/lib.rs crates/core-protocol/src/command.rs crates/core-protocol/src/common.rs crates/core-protocol/src/governance.rs crates/core-protocol/src/query.rs crates/core-protocol/src/tool.rs
/mnt/Data1T/mnote/rust/target/debug/deps/libcore_protocol-f5d7351438cd4f82.rmeta: crates/core-protocol/src/lib.rs crates/core-protocol/src/command.rs crates/core-protocol/src/common.rs crates/core-protocol/src/governance.rs crates/core-protocol/src/query.rs crates/core-protocol/src/tool.rs
crates/core-protocol/src/lib.rs:
crates/core-protocol/src/command.rs:
crates/core-protocol/src/common.rs:
crates/core-protocol/src/governance.rs:
crates/core-protocol/src/query.rs:
crates/core-protocol/src/tool.rs:
@@ -0,0 +1,8 @@
/mnt/Data1T/mnote/rust/target/debug/deps/event_log-563618231cacaf7e.d: crates/event-log/src/lib.rs crates/event-log/src/command_log.rs crates/event-log/src/domain_event.rs crates/event-log/src/rules.rs
/mnt/Data1T/mnote/rust/target/debug/deps/libevent_log-563618231cacaf7e.rmeta: crates/event-log/src/lib.rs crates/event-log/src/command_log.rs crates/event-log/src/domain_event.rs crates/event-log/src/rules.rs
crates/event-log/src/lib.rs:
crates/event-log/src/command_log.rs:
crates/event-log/src/domain_event.rs:
crates/event-log/src/rules.rs:
@@ -0,0 +1,10 @@
/mnt/Data1T/mnote/rust/target/debug/deps/event_log-d9b5c77d292421e8.d: crates/event-log/src/lib.rs crates/event-log/src/command_log.rs crates/event-log/src/domain_event.rs crates/event-log/src/rules.rs
/mnt/Data1T/mnote/rust/target/debug/deps/libevent_log-d9b5c77d292421e8.rlib: crates/event-log/src/lib.rs crates/event-log/src/command_log.rs crates/event-log/src/domain_event.rs crates/event-log/src/rules.rs
/mnt/Data1T/mnote/rust/target/debug/deps/libevent_log-d9b5c77d292421e8.rmeta: crates/event-log/src/lib.rs crates/event-log/src/command_log.rs crates/event-log/src/domain_event.rs crates/event-log/src/rules.rs
crates/event-log/src/lib.rs:
crates/event-log/src/command_log.rs:
crates/event-log/src/domain_event.rs:
crates/event-log/src/rules.rs:
@@ -0,0 +1,5 @@
/mnt/Data1T/mnote/rust/target/debug/deps/index_fts-e444a40571811be1.d: crates/index-fts/src/lib.rs
/mnt/Data1T/mnote/rust/target/debug/deps/libindex_fts-e444a40571811be1.rmeta: crates/index-fts/src/lib.rs
crates/index-fts/src/lib.rs:

Some files were not shown because too many files have changed in this diff Show More