feat(rag): harden post-LightRAG runtime

Retire legacy OCR/media/evidence fallbacks, add local-folder event bus and Page Aggregate guards, and archive completed design checklists.

Validation: cargo test -p mnote-web -- --test-threads=1; cargo test --workspace -- --test-threads=1; git diff --check; codegraph sync .; codegraph_status.
This commit is contained in:
lix-2026
2026-06-07 10:35:21 +08:00
parent 22a92edcda
commit 9551d4c1dc
59 changed files with 4053 additions and 5249 deletions
+98 -66
View File
@@ -143,9 +143,9 @@ pub fn build_query_request<T>(
context: &BridgeContext,
query: &QueryEnvelope<T>,
) -> BridgeResult<RetiredQueryRequest> {
let function_name = legacy_query_function_name(&query.name)?;
legacy_query_function_name(&query.name)?;
Ok(RetiredQueryRequest {
function_name: function_name.to_string(),
function_name: query.name.clone(),
deployment_id: context.deployment_id.clone(),
project_id: context.project_id.clone(),
workspace_id: context.workspace_id.clone(),
@@ -160,9 +160,9 @@ pub fn build_write_request<T>(
context: &BridgeContext,
command: &CommandEnvelope<T>,
) -> BridgeResult<RetiredMutationRequest> {
let function_name = legacy_command_function_name(&command.name)?;
legacy_command_function_name(&command.name)?;
Ok(RetiredMutationRequest {
function_name: function_name.to_string(),
function_name: command.name.clone(),
deployment_id: context.deployment_id.clone(),
project_id: context.project_id.clone(),
workspace_id: context.workspace_id.clone(),
@@ -182,26 +182,55 @@ fn legacy_query_function_name(name: &str) -> BridgeResult<&'static str> {
match name {
"documents.content.get" => Ok("documents:getContent"),
"documents.meta.get" => Ok("documents:getMeta"),
"blocks.get" => Ok("blocks:getById"),
"mindmaps.get" => Ok("mindmaps:get"),
"search.documents" => Ok("documents:listSearchDataByWorkspace"),
"search.documents" => Ok("search:documents"),
"search_blocks" => Ok("documents:searchBlocks"),
"sidebar.dataset.list" => Ok("sidebar:datasetList"),
"bridge.request.get" => Ok("bridgeLogs:listByRequest"),
"bridge.trace.get" => Ok("bridgeLogs:listByTrace"),
"bridge.command.get" => Ok("bridgeLogs:listByCommand"),
"bridge.workspace.overview" => Ok("bridgeLogs:listWorkspaceOverview"),
_ => Err(retired_bridge_error()),
}
}
pub fn retired_query_transport_function_name(name: &str) -> BridgeResult<&'static str> {
legacy_query_function_name(name)
}
fn legacy_command_function_name(name: &str) -> BridgeResult<&'static str> {
match name {
"documents.title.update" => Ok("documents:updateTitle"),
"page.head.updateTitle" | "tree.node.rename" => Ok("documents:updateTitle"),
"documents.save" => Ok("documents:updateContent"),
"documents.create" => Ok("documents:createWithParentReference"),
"documents.move" => Ok("documents:move"),
"documents.delete" => Ok("documents:softDelete"),
"documents.restore" => Ok("documents:restore"),
"insert_block" | "blocks.patch" | "blocks.move" | "blocks.embed" => {
Ok("documents:updateContent")
"page.body.save" => Ok("documents:updateContent"),
"documents.options.update" | "page.layout.updateOptions" => Ok("documents:updateOptions"),
"documents.stats.update" => Ok("documents:updateStats"),
"documents.create" | "tree.node.create" => Ok("documents:createWithParentReference"),
"documents.move" | "tree.node.move" | "tree.subtree.move" => Ok("documents:move"),
"documents.delete" | "tree.node.archive" => Ok("documents:softDelete"),
"documents.restore" | "tree.node.restore" => Ok("documents:restore"),
"documents.purge" | "tree.node.purge" => Ok("documents:purge"),
"documents.copy_tree" | "tree.subtree.copy" => Ok("documents:copyTree"),
"documents.duplicate" => Ok("documents:duplicateWithMindmaps"),
"documents.embed" | "tree.node.embed" => Ok("documents:updateContent"),
"documents.emptyTrashByWorkspace" | "tree.trash.emptyWorkspace" => {
Ok("documents:emptyTrashByWorkspace")
}
"insert_block" | "blocks.patch" => Ok("documents:updateContent"),
"blocks.move" => Ok("blocks:move"),
"blocks.embed" => Ok("blocks:insert"),
"mindmaps.put" => Ok("mindmaps:put"),
"mindmap.command.apply" => Ok("mindmaps:applyCommand"),
"media.assets.replace_storage" => Ok("mediaAssets:replaceStorageFromUpload"),
"tree.filetree.drop.preflight" => Ok("tree:fileTreeDropPreflight"),
"tree.filetree.delete.preflight" => Ok("tree:fileTreeDeletePreflight"),
"tree.filetree.paste.preflight" => Ok("tree:fileTreePastePreflight"),
"tree.filetree.upload-target.preflight" => Ok("tree:fileTreeUploadTargetPreflight"),
"tree.resource.copy" => Ok("mediaAssets:batchCopy"),
"tree.resource.move" => Ok("mediaAssets:batchMove"),
"tree.resource.upload" => Ok("mediaAssets:createWithStorage"),
"tree.resource.archive" => Ok("treeResource:archive"),
"tree.resource.restore" => Ok("treeResource:restore"),
"tree.resource.purge" => Ok("treeResource:purge"),
@@ -210,6 +239,10 @@ fn legacy_command_function_name(name: &str) -> BridgeResult<&'static str> {
}
}
pub fn retired_command_transport_function_name(name: &str) -> BridgeResult<&'static str> {
legacy_command_function_name(name)
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase", tag = "kind")]
pub enum RuntimeInput {
@@ -1857,7 +1890,7 @@ fn build_resource_lifecycle_plan(
Ok(plan)
}
fn resource_lifecycle_convex_function(
fn retired_resource_lifecycle_transport_function(
action: &str,
resource_kind: &str,
) -> Result<&'static str, BridgeError> {
@@ -4639,7 +4672,7 @@ fn build_tool_plan_steps(
steps.push(RuntimeToolPlanStep {
kind: "write".into(),
name: "media.assets.replace_storage".into(),
function_name: Some("mediaAssets:replaceStorageFromUpload".into()),
function_name: Some("media.assets.replace_storage".into()),
description:
"callback 下载并上传文件后,会继续通过统一 bridge 命令写回附件 storage 绑定"
.into(),
@@ -4754,7 +4787,7 @@ fn build_tool_plan_steps(
steps.push(RuntimeToolPlanStep {
kind: "write".into(),
name: "mindmaps.put".into(),
function_name: Some("mindmaps:put".into()),
function_name: Some("mindmaps.put".into()),
description: "用完整思维导图树覆盖当前导图".into(),
args_json: tool_wire.args_json.clone(),
});
@@ -11384,8 +11417,7 @@ fn execute_command(
validate_only: command_wire.validate_only,
};
let request = build_write_request(&context, &command)?;
let function_name =
resource_lifecycle_convex_function(action, &lifecycle_plan.resource_kind)?;
retired_resource_lifecycle_transport_function(action, &lifecycle_plan.resource_kind)?;
let stream_delta_hint = resource_lifecycle_stream_delta_hint(&lifecycle_plan);
let event_type = resource_lifecycle_event_type(action);
@@ -11448,7 +11480,7 @@ fn execute_command(
Ok(RuntimeExecutionPlan::Command(RuntimeCommandExecutionPlan {
command_name: command.name,
command_id: command.command_id,
function_name: function_name.into(),
function_name: request.function_name,
workspace_id: request.workspace_id,
request_id: request.request_id,
trace_id: request.trace_id,
@@ -12568,7 +12600,7 @@ mod tests {
match plan {
RuntimeExecutionPlan::Query(plan) => {
assert_eq!(plan.function_name, "blocks:getById");
assert_eq!(plan.function_name, "blocks.get");
assert_eq!(
plan.args_json,
json!({
@@ -12628,7 +12660,7 @@ mod tests {
match plan {
RuntimeExecutionPlan::Command(plan) => {
assert_eq!(plan.function_name, "documents:updateContent");
assert_eq!(plan.function_name, "blocks.patch");
assert_eq!(plan.command_name, "blocks.patch");
assert_eq!(
plan.args_json,
@@ -12965,7 +12997,7 @@ mod tests {
match plan {
RuntimeExecutionPlan::Query(plan) => {
assert_eq!(plan.function_name, "sidebar:datasetList");
assert_eq!(plan.function_name, "sidebar.dataset.list");
assert_eq!(plan.args_json, json!({ "workspaceId": "ws_1" }));
}
RuntimeExecutionPlan::Command(_) => panic!("expected query plan"),
@@ -12999,7 +13031,7 @@ mod tests {
match plan {
RuntimeExecutionPlan::Query(plan) => {
assert_eq!(plan.function_name, "search:documents");
assert_eq!(plan.function_name, "search.documents");
assert_eq!(
plan.args_json,
json!({
@@ -13969,7 +14001,7 @@ mod tests {
match plan {
RuntimeExecutionPlan::Query(plan) => {
assert_eq!(plan.query_name, "mindmaps.get");
assert_eq!(plan.function_name, "mindmaps:get");
assert_eq!(plan.function_name, "mindmaps.get");
assert_eq!(plan.args_json["docId"], json!("doc_1"));
assert_eq!(plan.args_json["mindmapId"], json!("mind_1"));
}
@@ -14022,7 +14054,7 @@ mod tests {
match plan {
RuntimeExecutionPlan::Command(plan) => {
assert_eq!(plan.command_name, "mindmap.command.apply");
assert_eq!(plan.function_name, "mindmaps:applyCommand");
assert_eq!(plan.function_name, "mindmap.command.apply");
assert_eq!(
plan.args_json["canonicalCommand"],
json!("mindmap.command.apply")
@@ -14301,7 +14333,7 @@ mod tests {
match plan {
RuntimeExecutionPlan::Query(plan) => {
assert_eq!(plan.function_name, "mindmaps:get");
assert_eq!(plan.function_name, "mindmaps.get");
assert_eq!(
plan.args_json,
json!({
@@ -14360,7 +14392,7 @@ mod tests {
match plan {
RuntimeExecutionPlan::Command(plan) => {
assert_eq!(plan.function_name, "mindmaps:put");
assert_eq!(plan.function_name, "mindmaps.put");
assert_eq!(plan.args_json["docId"], json!("doc_1"));
assert_eq!(plan.args_json["mindmapId"], json!("mind_1"));
assert_eq!(
@@ -14963,7 +14995,7 @@ mod tests {
panic!("expected command plan");
};
assert_eq!(plan.function_name, "documents:updateContent");
assert_eq!(plan.function_name, "documents.save");
assert_eq!(plan.args_json.get("id"), Some(&json!("doc_1")));
assert_eq!(
plan.args_json.pointer("/editorDocument/rootBlockIds/0"),
@@ -15507,7 +15539,7 @@ mod tests {
panic!("expected command plan");
};
assert_eq!(plan.command_name, "page.body.save");
assert_eq!(plan.function_name, "documents:updateContent");
assert_eq!(plan.function_name, "page.body.save");
assert_eq!(plan.args_json["expectedRevision"], json!(1));
assert_eq!(plan.args_json["conflictDetectionKey"], json!("doc_1:1"));
}
@@ -15551,7 +15583,7 @@ mod tests {
panic!("expected command plan");
};
assert_eq!(plan.command_name, "page.head.updateTitle");
assert_eq!(plan.function_name, "documents:updateTitle");
assert_eq!(plan.function_name, "page.head.updateTitle");
}
#[test]
@@ -15594,7 +15626,7 @@ mod tests {
panic!("expected command plan");
};
assert_eq!(plan.command_name, "page.layout.updateOptions");
assert_eq!(plan.function_name, "documents:updateOptions");
assert_eq!(plan.function_name, "page.layout.updateOptions");
}
#[test]
@@ -15892,7 +15924,7 @@ mod tests {
panic!("expected command plan");
};
assert_eq!(plan.function_name, "documents:updateContent");
assert_eq!(plan.function_name, "documents.save");
assert_eq!(
plan.args_json.pointer("/content/0/content"),
Some(&json!("来自 editorDocument"))
@@ -15960,7 +15992,7 @@ mod tests {
panic!("expected command plan");
};
assert_eq!(plan.function_name, "documents:updateContent");
assert_eq!(plan.function_name, "page.body.save");
assert_eq!(
plan.args_json.pointer("/content/0/id"),
Some(&json!("legacy_content_1"))
@@ -16276,7 +16308,7 @@ mod tests {
match plan {
RuntimeExecutionPlan::Command(plan) => {
assert_eq!(plan.function_name, "blocks:move");
assert_eq!(plan.function_name, "blocks.move");
assert_eq!(plan.command_name, "blocks.move");
assert_eq!(
plan.args_json,
@@ -16372,7 +16404,7 @@ mod tests {
match plan {
RuntimeExecutionPlan::Command(plan) => {
assert_eq!(plan.function_name, "blocks:insert");
assert_eq!(plan.function_name, "blocks.embed");
assert_eq!(plan.command_name, "blocks.embed");
assert_eq!(
plan.args_json,
@@ -16631,7 +16663,7 @@ mod tests {
match plan {
RuntimeExecutionPlan::Command(plan) => {
assert_eq!(plan.function_name, "documents:updateContent");
assert_eq!(plan.function_name, "documents.embed");
assert_eq!(plan.command_name, "documents.embed");
assert_eq!(
plan.args_json,
@@ -16677,7 +16709,7 @@ mod tests {
let cases = [
(
"tree.node.archive",
"documents:softDelete",
"tree.node.archive",
json!({
"documentId": "doc_1",
"workspaceId": "ws_1",
@@ -16719,7 +16751,7 @@ mod tests {
),
(
"tree.node.restore",
"documents:restore",
"tree.node.restore",
json!({
"documentId": "doc_1",
"workspaceId": "ws_1",
@@ -16761,7 +16793,7 @@ mod tests {
),
(
"tree.node.purge",
"documents:purge",
"tree.node.purge",
json!({
"documentId": "doc_1",
}),
@@ -17305,7 +17337,7 @@ mod tests {
};
assert_eq!(plan.command_name, "tree.subtree.move");
assert_eq!(plan.function_name, "documents:move");
assert_eq!(plan.function_name, "tree.subtree.move");
assert_eq!(plan.args_json["sortOrder"], json!(-2));
assert_eq!(
plan.args_json["commandProtocol"],
@@ -17793,7 +17825,7 @@ mod tests {
match create_plan {
RuntimeExecutionPlan::Command(plan) => {
assert_eq!(plan.command_name, "tree.node.create");
assert_eq!(plan.function_name, "documents:createWithParentReference");
assert_eq!(plan.function_name, "tree.node.create");
assert_eq!(
plan.args_json["streamDeltaHint"],
json!({
@@ -17872,7 +17904,7 @@ mod tests {
match rename_plan {
RuntimeExecutionPlan::Command(plan) => {
assert_eq!(plan.command_name, "tree.node.rename");
assert_eq!(plan.function_name, "documents:updateTitle");
assert_eq!(plan.function_name, "tree.node.rename");
assert_eq!(
plan.args_json["streamDeltaHint"],
json!({
@@ -17978,7 +18010,7 @@ mod tests {
match embed_plan {
RuntimeExecutionPlan::Command(plan) => {
assert_eq!(plan.function_name, "documents:updateContent");
assert_eq!(plan.function_name, "tree.node.embed");
assert_eq!(plan.command_name, "tree.node.embed");
assert_eq!(
plan.args_json,
@@ -18105,7 +18137,7 @@ mod tests {
match copy_plan {
RuntimeExecutionPlan::Command(plan) => {
assert_eq!(plan.function_name, "documents:copyTree");
assert_eq!(plan.function_name, "tree.subtree.copy");
assert_eq!(plan.command_name, "tree.subtree.copy");
assert_eq!(
plan.args_json,
@@ -18199,7 +18231,7 @@ mod tests {
match plan {
RuntimeExecutionPlan::Command(plan) => {
assert_eq!(plan.command_name, "documents.emptyTrashByWorkspace");
assert_eq!(plan.function_name, "documents:emptyTrashByWorkspace");
assert_eq!(plan.function_name, "documents.emptyTrashByWorkspace");
assert_eq!(plan.args_json["workspaceId"], json!("ws_1"));
assert_eq!(
plan.args_json["streamDeltaHint"],
@@ -18284,7 +18316,7 @@ mod tests {
match plan {
RuntimeExecutionPlan::Command(plan) => {
assert_eq!(plan.function_name, "documents:duplicateWithMindmaps");
assert_eq!(plan.function_name, "documents.duplicate");
assert_eq!(plan.command_name, "documents.duplicate");
assert_eq!(
plan.args_json["streamDeltaHint"],
@@ -18361,7 +18393,7 @@ mod tests {
match plan {
RuntimeExecutionPlan::Command(plan) => {
assert_eq!(plan.command_name, "documents.options.update");
assert_eq!(plan.function_name, "documents:updateOptions");
assert_eq!(plan.function_name, "documents.options.update");
assert_eq!(plan.args_json["id"], json!("doc_1"));
assert_eq!(
plan.args_json["options"],
@@ -18453,7 +18485,7 @@ mod tests {
match plan {
RuntimeExecutionPlan::Command(plan) => {
assert_eq!(plan.command_name, "documents.stats.update");
assert_eq!(plan.function_name, "documents:updateStats");
assert_eq!(plan.function_name, "documents.stats.update");
assert_eq!(
plan.args_json,
json!({
@@ -18517,7 +18549,7 @@ mod tests {
match plan {
RuntimeExecutionPlan::Command(plan) => {
assert_eq!(plan.command_name, "media.assets.replace_storage");
assert_eq!(plan.function_name, "mediaAssets:replaceStorageFromUpload");
assert_eq!(plan.function_name, "media.assets.replace_storage");
assert_eq!(
plan.args_json,
json!({
@@ -18539,13 +18571,13 @@ mod tests {
let cases = [
(
"tree.resource.copy",
"mediaAssets:batchCopy",
"tree.resource.copy",
"copy",
"tree.resource.copied",
),
(
"tree.resource.move",
"mediaAssets:batchMove",
"tree.resource.move",
"move",
"tree.resource.moved",
),
@@ -18642,7 +18674,7 @@ mod tests {
let cases = [
(
"tree.resource.archive",
"mediaAssets:patchById",
"tree.resource.archive",
"archive",
"tree.resource.archived",
json!({
@@ -18690,7 +18722,7 @@ mod tests {
),
(
"tree.resource.restore",
"mediaAssets:patchById",
"tree.resource.restore",
"restore",
"tree.resource.restored",
json!({
@@ -18738,7 +18770,7 @@ mod tests {
),
(
"tree.resource.purge",
"mediaAssets:purgeById",
"tree.resource.purge",
"purge",
"tree.resource.purged",
json!({
@@ -18786,7 +18818,7 @@ mod tests {
),
(
"tree.resource.rename",
"mediaAssets:patchById",
"tree.resource.rename",
"rename",
"tree.resource.renamed",
json!({
@@ -18891,7 +18923,7 @@ mod tests {
(
"tree.resource.archive",
"mindmap",
"mindmaps:softDelete",
"tree.resource.archive",
json!({
"resourceKind": "mindmap",
"documentId": "doc_1",
@@ -18915,7 +18947,7 @@ mod tests {
(
"tree.resource.restore",
"mindmap",
"mindmaps:restore",
"tree.resource.restore",
json!({
"resourceKind": "mindmap",
"documentId": "doc_1",
@@ -18939,7 +18971,7 @@ mod tests {
(
"tree.resource.purge",
"mindmap",
"mindmaps:purge",
"tree.resource.purge",
json!({
"resourceKind": "mindmap",
"documentId": "doc_1",
@@ -18963,7 +18995,7 @@ mod tests {
(
"tree.resource.archive",
"table",
"tables:remove",
"tree.resource.archive",
json!({
"resourceKind": "table",
"tableId": "table_1",
@@ -18986,7 +19018,7 @@ mod tests {
(
"tree.resource.restore",
"table",
"tables:restore",
"tree.resource.restore",
json!({
"resourceKind": "table",
"tableId": "table_1",
@@ -19009,7 +19041,7 @@ mod tests {
(
"tree.resource.purge",
"table",
"tables:purge",
"tree.resource.purge",
json!({
"resourceKind": "table",
"tableId": "table_1",
@@ -19263,7 +19295,7 @@ mod tests {
};
assert_eq!(plan.command_name, "tree.filetree.drop.preflight");
assert_eq!(plan.function_name, "tree:fileTreeDropPreflight");
assert_eq!(plan.function_name, "tree.filetree.drop.preflight");
assert_eq!(
plan.args_json["fileTreeDropPlan"],
json!({
@@ -19677,7 +19709,7 @@ mod tests {
};
assert_eq!(plan.command_name, "tree.filetree.delete.preflight");
assert_eq!(plan.function_name, "tree:fileTreeDeletePreflight");
assert_eq!(plan.function_name, "tree.filetree.delete.preflight");
assert_eq!(
plan.args_json["fileTreeDeletePlan"],
json!({
@@ -19773,7 +19805,7 @@ mod tests {
};
assert_eq!(plan.command_name, "tree.filetree.paste.preflight");
assert_eq!(plan.function_name, "tree:fileTreePastePreflight");
assert_eq!(plan.function_name, "tree.filetree.paste.preflight");
assert_eq!(
plan.args_json["fileTreePastePlan"],
json!({
@@ -19864,7 +19896,7 @@ mod tests {
};
assert_eq!(plan.command_name, "tree.filetree.upload-target.preflight");
assert_eq!(plan.function_name, "tree:fileTreeUploadTargetPreflight");
assert_eq!(plan.function_name, "tree.filetree.upload-target.preflight");
assert_eq!(
plan.args_json["fileTreeUploadTargetPlan"],
json!({
@@ -19926,7 +19958,7 @@ mod tests {
};
assert_eq!(plan.command_name, "tree.resource.upload");
assert_eq!(plan.function_name, "mediaAssets:createWithStorage");
assert_eq!(plan.function_name, "tree.resource.upload");
assert_eq!(
plan.args_json,
json!({
@@ -20093,7 +20125,7 @@ mod tests {
match plan {
RuntimeExecutionPlan::Query(plan) => {
assert_eq!(plan.function_name, "bridgeLogs:listByRequest");
assert_eq!(plan.function_name, "bridge.request.get");
assert_eq!(plan.args_json["workspaceId"], json!("ws_1"));
assert_eq!(plan.args_json["requestId"], json!("req_lookup_1"));
assert_eq!(plan.args_json["commandId"], json!("cmd_lookup_1"));
@@ -20129,7 +20161,7 @@ mod tests {
assert_eq!(plan.steps[0].name, "bridge.trace.get");
assert_eq!(
plan.steps[0].function_name.as_deref(),
Some("bridgeLogs:listByTrace")
Some("bridge.trace.get")
);
}
_ => panic!("expected tool plan"),