新增: 知识图谱Phase2事件流(父⑥) — project_events统一事件流+埋点+timeline
父⑥ Phase 2(对标设计 §2.4): - V30 迁移:project_events 追加型审计表(id/project_id/event_type/entity_type/entity_id/from_state/to_state/context_json/source/conversation_id/created_at)+ 双索引(project,time / entity) - ProjectEventRecord + ProjectEventRepo(insert 追加型/get_by_project/get_by_entity/list_recent) - 事件埋点(best-effort,hook/after,失败 warn 不阻断主操作): - task.rs emit_event 辅助 + create_task/advance_task/move_task_queue 埋点 - idea.rs promote_idea 埋点(idea_promoted,project_id=新项目) - project.rs create/delete 埋点 - 注:idea_created 暂不埋点(idea 无 project_id,待 Inbox 项目落地,NOT NULL+FK 约束) - get_project_timeline IPC(events 模块)+ lib.rs 注册 + AI 工具(基线 +N) - source 语义:IPC 标 human,AI 工具路径标 ai 注:workflow 脚本中文变量名 const 致验证 agent 未跑,主控独立 cargo check EXIT 0 + grep 落地齐全核验。
This commit is contained in:
@@ -5,12 +5,56 @@ use tauri::State;
|
||||
|
||||
use df_types::types::{new_id, TaskStatus};
|
||||
use df_storage::crud::TaskQuery;
|
||||
use df_storage::models::{TaskLinkRecord, TaskRecord};
|
||||
use df_storage::models::{ProjectEventRecord, TaskLinkRecord, TaskRecord};
|
||||
|
||||
use crate::state::AppState;
|
||||
|
||||
use super::{err_str, now_millis};
|
||||
|
||||
// ============================================================
|
||||
// 知识图谱 Phase 2:事件流埋点辅助(best-effort,对标设计 §2.4 hook/after + §10.1)
|
||||
// ============================================================
|
||||
|
||||
/// 追加一条项目事件到 project_events(best-effort)。
|
||||
///
|
||||
/// 埋点策略(设计 §2.4):事件写入失败**不阻断主操作**,仅 `tracing::warn` 记录。
|
||||
/// 设计 §10.1「事件流写入失败 — 影响事件完整性 — 事件写入失败不阻断主操作(best-effort)」。
|
||||
///
|
||||
/// `source` 语义:本辅助仅由 IPC 层调用,标 `"human"`(AI 工具路径在 tool_registry 内自行标
|
||||
/// `"ai"`,不经本 IPC)。`project_id` / `entity_type` / `entity_id` / `event_type` 由调用方
|
||||
/// 提供。`from_state` / `to_state` / `context_json` 可选。
|
||||
async fn emit_event(
|
||||
state: &State<'_, AppState>,
|
||||
project_id: &str,
|
||||
event_type: &str,
|
||||
entity_type: Option<&str>,
|
||||
entity_id: Option<&str>,
|
||||
from_state: Option<&str>,
|
||||
to_state: Option<&str>,
|
||||
) {
|
||||
let record = ProjectEventRecord {
|
||||
id: new_id(),
|
||||
project_id: project_id.to_string(),
|
||||
event_type: event_type.to_string(),
|
||||
entity_type: entity_type.map(|s| s.to_string()),
|
||||
entity_id: entity_id.map(|s| s.to_string()),
|
||||
from_state: from_state.map(|s| s.to_string()),
|
||||
to_state: to_state.map(|s| s.to_string()),
|
||||
context_json: None,
|
||||
source: Some("human".to_string()),
|
||||
conversation_id: None,
|
||||
created_at: now_millis(),
|
||||
};
|
||||
if let Err(e) = state.project_events.insert(record).await {
|
||||
tracing::warn!(
|
||||
event_type = event_type,
|
||||
project_id = project_id,
|
||||
error = %e,
|
||||
"[事件流] 埋点写入失败(不阻断主操作)"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 创建任务入参
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CreateTaskInput {
|
||||
@@ -262,6 +306,17 @@ pub async fn create_task(
|
||||
.insert(record.clone())
|
||||
.await
|
||||
.map_err(err_str)?;
|
||||
// 知识图谱 Phase 2(对标设计 §2.4 hook/after):task_created 事件。best-effort 不阻断。
|
||||
emit_event(
|
||||
&state,
|
||||
&record.project_id,
|
||||
"task_created",
|
||||
Some("task"),
|
||||
Some(&record.id),
|
||||
None,
|
||||
Some(&record.status),
|
||||
)
|
||||
.await;
|
||||
Ok(record)
|
||||
}
|
||||
|
||||
@@ -372,6 +427,15 @@ pub async fn advance_task(
|
||||
id: String,
|
||||
target_status: String,
|
||||
) -> Result<TaskRecord, String> {
|
||||
// 知识图谱 Phase 2:推进前读当前态,作 task_advanced 事件 from_state(仅一次轻量读,
|
||||
// advance 低频无压力)。失败(任务不存在)不阻断——后续 atomic 会用 NotFound 拒绝,from 留空。
|
||||
let from_state = state
|
||||
.tasks
|
||||
.get_by_id(&id)
|
||||
.await
|
||||
.map_err(err_str)?
|
||||
.map(|t| t.status);
|
||||
|
||||
let updated = df_nodes::task_advance_node::advance_task_atomic(
|
||||
&state.tasks,
|
||||
&id,
|
||||
@@ -394,6 +458,19 @@ pub async fn advance_task(
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 知识图谱 Phase 2(对标设计 §2.4 hook/after):task_advanced 事件。best-effort 不阻断。
|
||||
emit_event(
|
||||
&state,
|
||||
&updated.project_id,
|
||||
"task_advanced",
|
||||
Some("task"),
|
||||
Some(&updated.id),
|
||||
from_state.as_deref(),
|
||||
Some(&updated.status),
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(updated)
|
||||
}
|
||||
|
||||
@@ -639,6 +716,21 @@ pub async fn move_task_queue(
|
||||
.map_err(err_str)?;
|
||||
}
|
||||
|
||||
// 知识图谱 Phase 2(对标设计 §2.4 hook/after):queue 变化事件。best-effort 不阻断。
|
||||
// 仅在 queue 实际变化时埋点(避免 no-op 移动产噪音事件)。from/to 用 queue 值。
|
||||
if current.queue != new_queue {
|
||||
emit_event(
|
||||
&state,
|
||||
¤t.project_id,
|
||||
"task_advanced",
|
||||
Some("task"),
|
||||
Some(¤t.id),
|
||||
Some(¤t.queue),
|
||||
Some(&new_queue),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
// 回读最新记录返回
|
||||
state
|
||||
.tasks
|
||||
|
||||
Reference in New Issue
Block a user