chore: 保存当前架构收口与 bug 修复快照
归档本轮 P0/P1 bug 修复、设计审查迁移、AI selection scope 收口与 stream contract 调整,并保留当前 05 主线迁移起点。
This commit is contained in:
@@ -10987,6 +10987,7 @@ fn normalize_editor_block_type_for_save(raw_type: &str) -> EditorBlockType {
|
||||
"toc" | "toc_node" | "tocnode" => EditorBlockType::Toc,
|
||||
"page_reference" => EditorBlockType::PageReference,
|
||||
"block_reference" => EditorBlockType::BlockReference,
|
||||
"resource" | "resource_block" => EditorBlockType::Resource,
|
||||
_ => EditorBlockType::Paragraph,
|
||||
}
|
||||
}
|
||||
@@ -11105,6 +11106,13 @@ fn normalize_editor_block_from_legacy_path(block: &Value, path: &[usize]) -> Edi
|
||||
);
|
||||
}
|
||||
}
|
||||
if matches!(normalized_block_type, EditorBlockType::Resource) {
|
||||
if let Some(props_map) = block.get("props").and_then(Value::as_object) {
|
||||
props
|
||||
.extra
|
||||
.insert("resourceProps".into(), Value::Object(props_map.clone()));
|
||||
}
|
||||
}
|
||||
let text = read_trimmed_string_field(block, &["content"])
|
||||
.filter(|value| !value.is_empty())
|
||||
.unwrap_or_else(|| extract_inline_text(block));
|
||||
@@ -11122,7 +11130,10 @@ fn normalize_editor_block_from_legacy_path(block: &Value, path: &[usize]) -> Edi
|
||||
}
|
||||
}
|
||||
|
||||
pub fn editor_document_from_legacy_content(document_id: &str, content: &Value) -> EditorBlockDocument {
|
||||
pub fn editor_document_from_legacy_content(
|
||||
document_id: &str,
|
||||
content: &Value,
|
||||
) -> EditorBlockDocument {
|
||||
let mut blocks = Vec::<EditorBlock>::new();
|
||||
let mut root_block_ids = Vec::<String>::new();
|
||||
for (index, block) in normalize_blocks_from_value(content).iter().enumerate() {
|
||||
@@ -11336,6 +11347,18 @@ fn legacy_props_from_editor_block(block: &EditorBlock) -> Option<Value> {
|
||||
props.insert("projectionVersion".into(), json!(projection_version));
|
||||
}
|
||||
}
|
||||
EditorBlockType::Resource => {
|
||||
if let Some(resource_props) = block
|
||||
.props
|
||||
.extra
|
||||
.get("resourceProps")
|
||||
.and_then(Value::as_object)
|
||||
{
|
||||
for (key, value) in resource_props {
|
||||
props.insert(key.clone(), value.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
if let Some(text_align) = block
|
||||
@@ -11372,6 +11395,7 @@ fn legacy_type_from_editor_block(block: &EditorBlock) -> &'static str {
|
||||
EditorBlockType::Toc => "toc",
|
||||
EditorBlockType::PageReference => "page_reference",
|
||||
EditorBlockType::BlockReference => "block_reference",
|
||||
EditorBlockType::Resource => "resource",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11836,7 +11860,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -11931,7 +11955,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -12513,6 +12537,38 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editor_document_roundtrip_preserves_resource_block_type() {
|
||||
let content = json!([
|
||||
{
|
||||
"id": "resource_1",
|
||||
"type": "resource",
|
||||
"props": {
|
||||
"resourceKind": "asset",
|
||||
"assetId": "asset_1"
|
||||
},
|
||||
"content": "资源块"
|
||||
}
|
||||
]);
|
||||
let document = editor_document_from_legacy_content("doc_1", &content);
|
||||
assert_eq!(document.blocks[0].block_type, EditorBlockType::Resource);
|
||||
|
||||
let legacy = legacy_content_from_editor_document(&document);
|
||||
assert_eq!(legacy[0]["id"], json!("resource_1"));
|
||||
assert_eq!(legacy[0]["type"], json!("resource"));
|
||||
assert_eq!(legacy[0]["props"]["resourceKind"], json!("asset"));
|
||||
assert_eq!(legacy[0]["props"]["assetId"], json!("asset_1"));
|
||||
|
||||
let projection = project_legacy_content_to_block_document("doc_1", &legacy, &json!(1))
|
||||
.expect("resource block should project");
|
||||
assert_eq!(projection["blocks"][0]["type"], json!("resource"));
|
||||
assert_eq!(projection["blocks"][0]["editable"], json!(false));
|
||||
assert_eq!(
|
||||
projection["blocks"][0]["unsupportedReason"],
|
||||
json!("复杂块暂不开放 AI 精确写入")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editor_command_apply_insert_preserves_nested_children() {
|
||||
let content = json!([{
|
||||
@@ -13186,7 +13242,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -13227,11 +13283,11 @@ mod tests {
|
||||
"children": [],
|
||||
})
|
||||
);
|
||||
assert_eq!(plan.args_json["createOnly"], json!(true));
|
||||
assert_eq!(
|
||||
plan.args_json["streamDeltaHint"],
|
||||
json!({
|
||||
"family": "tree",
|
||||
assert_eq!(plan.args_json["createOnly"], json!(true));
|
||||
assert_eq!(
|
||||
plan.args_json["streamDeltaHint"],
|
||||
json!({
|
||||
"family": "tree",
|
||||
"kind": "resync_required",
|
||||
"args": {
|
||||
"reason": "mindmap.put",
|
||||
@@ -13308,11 +13364,11 @@ mod tests {
|
||||
.expect("mindmap update command plan should build");
|
||||
|
||||
match plan {
|
||||
RuntimeExecutionPlan::Command(plan) => {
|
||||
assert_eq!(
|
||||
plan.args_json["streamDeltaHint"],
|
||||
json!({
|
||||
"family": "tree",
|
||||
RuntimeExecutionPlan::Command(plan) => {
|
||||
assert_eq!(
|
||||
plan.args_json["streamDeltaHint"],
|
||||
json!({
|
||||
"family": "tree",
|
||||
"kind": "noop",
|
||||
"args": {}
|
||||
})
|
||||
@@ -13772,7 +13828,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -14469,7 +14525,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -14570,7 +14626,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -14676,7 +14732,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -14743,7 +14799,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -14885,6 +14941,108 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn page_body_save_artifact_preserves_editor_document_payload() {
|
||||
let context = demo_context();
|
||||
let command = RuntimeCommandEnvelopeWire {
|
||||
name: "page.body.save".into(),
|
||||
command_id: "cmd_save_artifact_editor_document".into(),
|
||||
idempotency_key: Some("idem_save_artifact_editor_document".into()),
|
||||
actor: RuntimeActorWire {
|
||||
actor_type: "user".into(),
|
||||
actor_id: "user_1".into(),
|
||||
session_id: Some("sess_1".into()),
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
capabilities: Vec::new(),
|
||||
},
|
||||
target: Some(RuntimeTargetWire {
|
||||
workspace_id: Some("ws_1".into()),
|
||||
page_id: Some("doc_1".into()),
|
||||
block_id: None,
|
||||
}),
|
||||
payload: json!({
|
||||
"documentId": "doc_1",
|
||||
"workspaceId": "ws_1",
|
||||
"revision": 9,
|
||||
"editorDocument": {
|
||||
"documentId": "doc_1",
|
||||
"rootBlockIds": ["editor_block_1"],
|
||||
"blocks": [{
|
||||
"blockId": "editor_block_1",
|
||||
"blockType": "paragraph",
|
||||
"contentNodes": [{
|
||||
"payload": {
|
||||
"type": "text",
|
||||
"text": "来自 editorDocument artifact"
|
||||
},
|
||||
"attrs": {}
|
||||
}],
|
||||
"childBlockIds": []
|
||||
}]
|
||||
},
|
||||
"content": [],
|
||||
"tiptapDocument": {
|
||||
"type": "doc",
|
||||
"content": [{
|
||||
"type": "paragraph",
|
||||
"attrs": { "blockId": "editor_block_1" },
|
||||
"content": [{ "type": "text", "text": "来自 tiptapDocument artifact" }]
|
||||
}]
|
||||
},
|
||||
"conflictDetectionKey": "doc_1:9"
|
||||
}),
|
||||
preflight_data: None,
|
||||
reason: Some("保存 artifact provenance".into()),
|
||||
refs: vec!["page-body-save-artifact-provenance".into()],
|
||||
dry_run: false,
|
||||
validate_only: false,
|
||||
};
|
||||
let plan = execute_runtime_input(RuntimeInput::Command {
|
||||
context: context.clone(),
|
||||
command: command.clone(),
|
||||
})
|
||||
.expect("page.body.save plan should build");
|
||||
let RuntimeExecutionPlan::Command(plan) = plan else {
|
||||
panic!("expected command plan");
|
||||
};
|
||||
let artifacts = build_runtime_command_artifact_plan(
|
||||
&context,
|
||||
&command,
|
||||
&plan,
|
||||
&json!({"ok": true, "revision": 10}),
|
||||
"2026-05-18T00:00:00Z",
|
||||
)
|
||||
.expect("artifact plan should build");
|
||||
|
||||
assert_eq!(
|
||||
artifacts
|
||||
.command_log
|
||||
.payload
|
||||
.pointer("/editorDocument/rootBlockIds/0"),
|
||||
Some(&json!("editor_block_1"))
|
||||
);
|
||||
assert_eq!(
|
||||
artifacts
|
||||
.command_log
|
||||
.payload
|
||||
.pointer("/editorDocument/blocks/0/contentNodes/0/payload/text"),
|
||||
Some(&json!("来自 editorDocument artifact"))
|
||||
);
|
||||
assert_eq!(
|
||||
artifacts
|
||||
.command_log
|
||||
.payload
|
||||
.pointer("/tiptapDocument/content/0/type"),
|
||||
Some(&json!("paragraph"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blocks_move_command_plan_maps_to_blocks_move() {
|
||||
let plan = execute_runtime_input(RuntimeInput::Command {
|
||||
@@ -14900,7 +15058,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -14995,7 +15153,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -15161,7 +15319,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -15250,7 +15408,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -15333,18 +15491,18 @@ mod tests {
|
||||
"documentId": "doc_1",
|
||||
"workspaceId": "ws_1",
|
||||
}),
|
||||
json!({
|
||||
"id": "doc_1",
|
||||
"commandProtocol": {
|
||||
"family": "tree",
|
||||
"owner": "rust-runtime-kernel",
|
||||
"preferredCommandName": "tree.node.archive",
|
||||
"compatCommandName": "documents.delete",
|
||||
"deprecatedAlias": false
|
||||
},
|
||||
"streamDeltaHint": {
|
||||
"family": "tree",
|
||||
"kind": "remove_document",
|
||||
json!({
|
||||
"id": "doc_1",
|
||||
"commandProtocol": {
|
||||
"family": "tree",
|
||||
"owner": "rust-runtime-kernel",
|
||||
"preferredCommandName": "tree.node.archive",
|
||||
"compatCommandName": "documents.delete",
|
||||
"deprecatedAlias": false
|
||||
},
|
||||
"streamDeltaHint": {
|
||||
"family": "tree",
|
||||
"kind": "remove_document",
|
||||
"args": {
|
||||
"documentId": "doc_1"
|
||||
}
|
||||
@@ -15375,18 +15533,18 @@ mod tests {
|
||||
"documentId": "doc_1",
|
||||
"workspaceId": "ws_1",
|
||||
}),
|
||||
json!({
|
||||
"id": "doc_1",
|
||||
"commandProtocol": {
|
||||
"family": "tree",
|
||||
"owner": "rust-runtime-kernel",
|
||||
"preferredCommandName": "tree.node.restore",
|
||||
"compatCommandName": "documents.restore",
|
||||
"deprecatedAlias": false
|
||||
},
|
||||
"streamDeltaHint": {
|
||||
"family": "tree",
|
||||
"kind": "document_result",
|
||||
json!({
|
||||
"id": "doc_1",
|
||||
"commandProtocol": {
|
||||
"family": "tree",
|
||||
"owner": "rust-runtime-kernel",
|
||||
"preferredCommandName": "tree.node.restore",
|
||||
"compatCommandName": "documents.restore",
|
||||
"deprecatedAlias": false
|
||||
},
|
||||
"streamDeltaHint": {
|
||||
"family": "tree",
|
||||
"kind": "document_result",
|
||||
"args": {
|
||||
"documentField": "document"
|
||||
}
|
||||
@@ -15416,17 +15574,17 @@ mod tests {
|
||||
json!({
|
||||
"documentId": "doc_1",
|
||||
}),
|
||||
json!({
|
||||
"id": "doc_1",
|
||||
"commandProtocol": {
|
||||
"family": "tree",
|
||||
"owner": "rust-runtime-kernel",
|
||||
"preferredCommandName": "tree.node.purge",
|
||||
"compatCommandName": "documents.purge",
|
||||
"deprecatedAlias": false
|
||||
}
|
||||
}),
|
||||
),
|
||||
json!({
|
||||
"id": "doc_1",
|
||||
"commandProtocol": {
|
||||
"family": "tree",
|
||||
"owner": "rust-runtime-kernel",
|
||||
"preferredCommandName": "tree.node.purge",
|
||||
"compatCommandName": "documents.purge",
|
||||
"deprecatedAlias": false
|
||||
}
|
||||
}),
|
||||
),
|
||||
];
|
||||
|
||||
for (command_name, function_name, payload, args_json) in cases {
|
||||
@@ -15443,7 +15601,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -15491,7 +15649,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -15560,7 +15718,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -15629,7 +15787,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -15698,7 +15856,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -15755,7 +15913,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -15919,7 +16077,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -15955,20 +16113,20 @@ mod tests {
|
||||
panic!("expected command plan");
|
||||
};
|
||||
|
||||
assert_eq!(plan.command_name, "tree.subtree.move");
|
||||
assert_eq!(plan.function_name, "documents:move");
|
||||
assert_eq!(plan.args_json["sortOrder"], json!(-2));
|
||||
assert_eq!(
|
||||
plan.args_json["commandProtocol"],
|
||||
json!({
|
||||
"family": "tree",
|
||||
"owner": "rust-runtime-kernel",
|
||||
"preferredCommandName": "tree.subtree.move",
|
||||
"compatCommandName": "documents.move",
|
||||
"deprecatedAlias": false
|
||||
})
|
||||
);
|
||||
assert!(plan.args_json.get("normalizedMove").is_none());
|
||||
assert_eq!(plan.command_name, "tree.subtree.move");
|
||||
assert_eq!(plan.function_name, "documents:move");
|
||||
assert_eq!(plan.args_json["sortOrder"], json!(-2));
|
||||
assert_eq!(
|
||||
plan.args_json["commandProtocol"],
|
||||
json!({
|
||||
"family": "tree",
|
||||
"owner": "rust-runtime-kernel",
|
||||
"preferredCommandName": "tree.subtree.move",
|
||||
"compatCommandName": "documents.move",
|
||||
"deprecatedAlias": false
|
||||
})
|
||||
);
|
||||
assert!(plan.args_json.get("normalizedMove").is_none());
|
||||
assert_eq!(
|
||||
plan.args_json["treeWriteOperation"],
|
||||
json!({
|
||||
@@ -16022,111 +16180,123 @@ mod tests {
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn documents_lifecycle_aliases_are_marked_as_deprecated_tree_protocol_aliases() {
|
||||
let cases = [
|
||||
("documents.create", "tree.node.create", "documents.create"),
|
||||
("documents.title.update", "tree.node.rename", "documents.title.update"),
|
||||
("documents.move", "tree.subtree.move", "documents.move"),
|
||||
("documents.delete", "tree.node.archive", "documents.delete"),
|
||||
("documents.restore", "tree.node.restore", "documents.restore"),
|
||||
("documents.purge", "tree.node.purge", "documents.purge"),
|
||||
("documents.copy_tree", "tree.subtree.copy", "documents.copy_tree"),
|
||||
];
|
||||
#[test]
|
||||
fn documents_lifecycle_aliases_are_marked_as_deprecated_tree_protocol_aliases() {
|
||||
let cases = [
|
||||
("documents.create", "tree.node.create", "documents.create"),
|
||||
(
|
||||
"documents.title.update",
|
||||
"tree.node.rename",
|
||||
"documents.title.update",
|
||||
),
|
||||
("documents.move", "tree.subtree.move", "documents.move"),
|
||||
("documents.delete", "tree.node.archive", "documents.delete"),
|
||||
(
|
||||
"documents.restore",
|
||||
"tree.node.restore",
|
||||
"documents.restore",
|
||||
),
|
||||
("documents.purge", "tree.node.purge", "documents.purge"),
|
||||
(
|
||||
"documents.copy_tree",
|
||||
"tree.subtree.copy",
|
||||
"documents.copy_tree",
|
||||
),
|
||||
];
|
||||
|
||||
for (command_name, preferred_command_name, compat_command_name) in cases {
|
||||
let command = RuntimeCommandEnvelopeWire {
|
||||
name: command_name.into(),
|
||||
command_id: format!("cmd_{command_name}"),
|
||||
idempotency_key: Some(format!("idem_{command_name}")),
|
||||
actor: RuntimeActorWire {
|
||||
actor_type: "user".into(),
|
||||
actor_id: "user_1".into(),
|
||||
session_id: Some("sess_1".into()),
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
capabilities: Vec::new(),
|
||||
},
|
||||
target: Some(RuntimeTargetWire {
|
||||
workspace_id: Some("ws_1".into()),
|
||||
page_id: Some("doc_1".into()),
|
||||
block_id: None,
|
||||
}),
|
||||
payload: match command_name {
|
||||
"documents.create" => json!({
|
||||
"documentId": "doc_1",
|
||||
"workspaceId": "ws_1",
|
||||
"parentId": null,
|
||||
"title": "新页面",
|
||||
"accessScope": "private",
|
||||
"content": []
|
||||
}),
|
||||
"documents.title.update" => json!({
|
||||
"documentId": "doc_1",
|
||||
"title": "改名"
|
||||
}),
|
||||
"documents.move" => json!({
|
||||
"documentId": "doc_1",
|
||||
"parentId": null,
|
||||
"sortOrder": 0
|
||||
}),
|
||||
"documents.delete" | "documents.restore" | "documents.purge" => json!({
|
||||
"documentId": "doc_1",
|
||||
"workspaceId": "ws_1"
|
||||
}),
|
||||
"documents.copy_tree" => json!({
|
||||
"items": [{"documentId": "doc_1", "recursive": true}],
|
||||
"targetParentId": null
|
||||
}),
|
||||
_ => unreachable!("unexpected command"),
|
||||
},
|
||||
preflight_data: if command_name == "documents.move" {
|
||||
Some(json!({
|
||||
"documents": [
|
||||
{ "id": "doc_1", "workspace_id": "ws_1", "parent_id": null, "sort_order": 0, "created_at": "2026-04-25T00:00:01Z" }
|
||||
]
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
reason: Some("兼容 alias 标识验证".into()),
|
||||
refs: vec![],
|
||||
dry_run: false,
|
||||
validate_only: false,
|
||||
};
|
||||
let plan = execute_runtime_input(RuntimeInput::Command {
|
||||
context: demo_context(),
|
||||
command,
|
||||
})
|
||||
.expect("compat command plan should build");
|
||||
let RuntimeExecutionPlan::Command(plan) = plan else {
|
||||
panic!("expected command plan");
|
||||
};
|
||||
for (command_name, preferred_command_name, compat_command_name) in cases {
|
||||
let command = RuntimeCommandEnvelopeWire {
|
||||
name: command_name.into(),
|
||||
command_id: format!("cmd_{command_name}"),
|
||||
idempotency_key: Some(format!("idem_{command_name}")),
|
||||
actor: RuntimeActorWire {
|
||||
actor_type: "user".into(),
|
||||
actor_id: "user_1".into(),
|
||||
session_id: Some("sess_1".into()),
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
capabilities: Vec::new(),
|
||||
},
|
||||
target: Some(RuntimeTargetWire {
|
||||
workspace_id: Some("ws_1".into()),
|
||||
page_id: Some("doc_1".into()),
|
||||
block_id: None,
|
||||
}),
|
||||
payload: match command_name {
|
||||
"documents.create" => json!({
|
||||
"documentId": "doc_1",
|
||||
"workspaceId": "ws_1",
|
||||
"parentId": null,
|
||||
"title": "新页面",
|
||||
"accessScope": "private",
|
||||
"content": []
|
||||
}),
|
||||
"documents.title.update" => json!({
|
||||
"documentId": "doc_1",
|
||||
"title": "改名"
|
||||
}),
|
||||
"documents.move" => json!({
|
||||
"documentId": "doc_1",
|
||||
"parentId": null,
|
||||
"sortOrder": 0
|
||||
}),
|
||||
"documents.delete" | "documents.restore" | "documents.purge" => json!({
|
||||
"documentId": "doc_1",
|
||||
"workspaceId": "ws_1"
|
||||
}),
|
||||
"documents.copy_tree" => json!({
|
||||
"items": [{"documentId": "doc_1", "recursive": true}],
|
||||
"targetParentId": null
|
||||
}),
|
||||
_ => unreachable!("unexpected command"),
|
||||
},
|
||||
preflight_data: if command_name == "documents.move" {
|
||||
Some(json!({
|
||||
"documents": [
|
||||
{ "id": "doc_1", "workspace_id": "ws_1", "parent_id": null, "sort_order": 0, "created_at": "2026-04-25T00:00:01Z" }
|
||||
]
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
reason: Some("兼容 alias 标识验证".into()),
|
||||
refs: vec![],
|
||||
dry_run: false,
|
||||
validate_only: false,
|
||||
};
|
||||
let plan = execute_runtime_input(RuntimeInput::Command {
|
||||
context: demo_context(),
|
||||
command,
|
||||
})
|
||||
.expect("compat command plan should build");
|
||||
let RuntimeExecutionPlan::Command(plan) = plan else {
|
||||
panic!("expected command plan");
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
plan.args_json["commandProtocol"],
|
||||
json!({
|
||||
"family": "tree",
|
||||
"owner": "rust-runtime-kernel",
|
||||
"preferredCommandName": preferred_command_name,
|
||||
"compatCommandName": compat_command_name,
|
||||
"deprecatedAlias": true
|
||||
}),
|
||||
"{command_name} 应标记为 tree compat deprecated alias"
|
||||
);
|
||||
}
|
||||
}
|
||||
assert_eq!(
|
||||
plan.args_json["commandProtocol"],
|
||||
json!({
|
||||
"family": "tree",
|
||||
"owner": "rust-runtime-kernel",
|
||||
"preferredCommandName": preferred_command_name,
|
||||
"compatCommandName": compat_command_name,
|
||||
"deprecatedAlias": true
|
||||
}),
|
||||
"{command_name} 应标记为 tree compat deprecated alias"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tree_subtree_move_command_includes_tree_write_operation_from_snapshot() {
|
||||
#[test]
|
||||
fn tree_subtree_move_command_includes_tree_write_operation_from_snapshot() {
|
||||
let plan = execute_runtime_input(RuntimeInput::Command {
|
||||
context: demo_context(),
|
||||
command: RuntimeCommandEnvelopeWire {
|
||||
@@ -16140,7 +16310,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -16215,7 +16385,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -16315,7 +16485,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -16401,7 +16571,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -16484,7 +16654,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -16572,7 +16742,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -16713,7 +16883,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -16755,15 +16925,15 @@ mod tests {
|
||||
"recursive": true,
|
||||
}
|
||||
],
|
||||
"targetParentId": "parent_1",
|
||||
"commandProtocol": {
|
||||
"family": "tree",
|
||||
"owner": "rust-runtime-kernel",
|
||||
"preferredCommandName": "tree.subtree.copy",
|
||||
"compatCommandName": "documents.copy_tree",
|
||||
"deprecatedAlias": false
|
||||
},
|
||||
"streamDeltaHint": {
|
||||
"targetParentId": "parent_1",
|
||||
"commandProtocol": {
|
||||
"family": "tree",
|
||||
"owner": "rust-runtime-kernel",
|
||||
"preferredCommandName": "tree.subtree.copy",
|
||||
"compatCommandName": "documents.copy_tree",
|
||||
"deprecatedAlias": false
|
||||
},
|
||||
"streamDeltaHint": {
|
||||
"family": "tree",
|
||||
"kind": "copy_result",
|
||||
"args": {
|
||||
@@ -16812,7 +16982,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: Some("ws_1".into()),
|
||||
@@ -16896,7 +17066,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -16968,7 +17138,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -17058,7 +17228,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -17126,7 +17296,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "onlyoffice-callback".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -17204,7 +17374,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -17492,7 +17662,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -17684,7 +17854,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -17741,7 +17911,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -17781,7 +17951,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -17827,7 +17997,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -17952,7 +18122,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -18015,7 +18185,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -18079,7 +18249,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -18146,7 +18316,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -18244,7 +18414,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -18343,7 +18513,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -18456,7 +18626,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
@@ -18530,7 +18700,7 @@ mod tests {
|
||||
},
|
||||
source: RuntimeSourceWire {
|
||||
channel: "next-route".into(),
|
||||
client: "wolai-frontend".into(),
|
||||
client: "mnote-web".into(),
|
||||
source_kind: None,
|
||||
root_uri: None,
|
||||
workspace_id: None,
|
||||
|
||||
Reference in New Issue
Block a user