feat(tree): complete rust family runtime checklist

- add tree shell runtime artifact contracts and page/filetree/picker runtime reducers
- sink tree.subtree.move write operation through Rust and formalize command event plans
- harden file tree search projection contract and route thin-proxy boundaries
- record completed harness tasks and move design docs into process/done
This commit is contained in:
lix-2026
2026-04-27 10:27:15 +08:00
parent e564dfde02
commit 4ab36a9386
30 changed files with 2502 additions and 432 deletions
+699 -47
View File
@@ -1,45 +1,44 @@
use adapter_onlyoffice::{
prepare_callback, prepare_forcesave, prepare_proxy_request, resolve_session, sign_config,
OnlyOfficeCallbackPreparationInput, OnlyOfficeForcesavePreparationInput,
OnlyOfficeProxyPreparationInput, OnlyOfficeSessionResolveInput, prepare_callback,
prepare_forcesave, prepare_proxy_request, resolve_session, sign_config,
OnlyOfficeProxyPreparationInput, OnlyOfficeSessionResolveInput,
};
use core_domain::Timestamp;
use core_protocol::editor::{EditorBlockDocumentTiptapBridge, TiptapNode};
use core_protocol::{
ActorPayload, BlockProps, CommandEnvelope, ContentNode, ContentNodePayload,
DocumentContentResult, DocumentReadEvidenceItem, DocumentReadEvidenceKind, DocumentReadNode,
DocumentReadNodeMeta, DocumentReadNodeType, DocumentReadOutlineEntry, DocumentReadPageSubtree,
DocumentReadStats, DocumentReadSubtree, EditorBlock, EditorBlockDocument, EditorBlockType,
EditorCommand, EditorInsertBlockAfter, EditorReplaceBlock, EmbedBlock, GetBlock,
GetBridgeCommand, GetBridgeRequest, GetBridgeTrace, GetMindmap, InvocationKind,
KernelAttachEdge, KernelAuditStamp, KernelContentPayload, KernelCreateNode, KernelDetachEdge,
KernelEdge, KernelEdgeListResult, KernelEdgeType, KernelGetNode, KernelGetSubtree,
KernelGraphDirection, KernelGraphTraversalResult, KernelGraphVisit, KernelListChildren,
KernelListEdges, KernelMoveSubtree, KernelNode, KernelNodeMetadata, KernelNodeType,
KernelProjectionAssetKind, KernelProjectionCapability, KernelProjectionFilter,
KernelProjectionItem, KernelProjectionKind, KernelProjectionRequest,
KernelProjectionResourceKind, KernelProjectionResourceMeta, KernelProjectionResult,
KernelProjectionRowKind, KernelRefsPayload, KernelSubtreeRef, KernelSubtreeResult,
KernelTraverseGraph, KernelUpdateNode, ListBridgeWorkspaceOverview, MindmapNodeData,
MindmapNodeInput, MindmapNodeRef, MindmapOp, MindmapTreeNode, MoveBlock, PatchBlock,
PutMindmap, QueryEnvelope, SearchDocuments, SearchRecent, SourcePayload, TargetRef,
default_tool_registry, invocation_kind_label, tool_effect_label, ActorPayload, BlockProps,
CommandEnvelope, ContentNode, ContentNodePayload, DocumentContentResult,
DocumentReadEvidenceItem, DocumentReadEvidenceKind, DocumentReadNode, DocumentReadNodeMeta,
DocumentReadNodeType, DocumentReadOutlineEntry, DocumentReadPageSubtree, DocumentReadStats,
DocumentReadSubtree, EditorBlock, EditorBlockDocument, EditorBlockType, EditorCommand,
EditorInsertBlockAfter, EditorReplaceBlock, EmbedBlock, GetBlock, GetBridgeCommand,
GetBridgeRequest, GetBridgeTrace, GetMindmap, InvocationKind, KernelAttachEdge,
KernelAuditStamp, KernelContentPayload, KernelCreateNode, KernelDetachEdge, KernelEdge,
KernelEdgeListResult, KernelEdgeType, KernelGetNode, KernelGetSubtree, KernelGraphDirection,
KernelGraphTraversalResult, KernelGraphVisit, KernelListChildren, KernelListEdges,
KernelMoveSubtree, KernelNode, KernelNodeMetadata, KernelNodeType, KernelProjectionAssetKind,
KernelProjectionCapability, KernelProjectionFilter, KernelProjectionItem, KernelProjectionKind,
KernelProjectionRequest, KernelProjectionResourceKind, KernelProjectionResourceMeta,
KernelProjectionResult, KernelProjectionRowKind, KernelRefsPayload, KernelSubtreeRef,
KernelSubtreeResult, KernelTraverseGraph, KernelUpdateNode, ListBridgeWorkspaceOverview,
MindmapNodeData, MindmapNodeInput, MindmapNodeRef, MindmapOp, MindmapTreeNode, MoveBlock,
PatchBlock, PutMindmap, QueryEnvelope, SearchDocuments, SearchRecent, SourcePayload, TargetRef,
ToolExecutionMode, ToolInvocation, UpdatePageOptions, UpdatePageStats,
default_tool_registry, invocation_kind_label, tool_effect_label,
};
use event_log::DomainEventRecord;
use index_fts::{
IndexCursor, MinimalWorkspaceProjector, SearchDocumentsDataset, SearchDocumentsEvaluation,
SearchDocumentsRequest, can_rebuild_from_events, evaluate_search_documents,
rebuild_from_events,
can_rebuild_from_events, evaluate_search_documents, rebuild_from_events, IndexCursor,
MinimalWorkspaceProjector, SearchDocumentsDataset, SearchDocumentsEvaluation,
SearchDocumentsRequest,
};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
use serde_json::{json, Value};
use std::collections::{BTreeMap, HashMap, VecDeque};
use std::env;
use std::sync::atomic::{AtomicU64, Ordering};
use storage_convex_bridge::{
BridgeContext, BridgeError, BridgeErrorKind, build_query_request, build_write_request,
build_query_request, build_write_request, BridgeContext, BridgeError, BridgeErrorKind,
};
static TOOL_BLOCK_COUNTER: AtomicU64 = AtomicU64::new(1);
@@ -1032,6 +1031,28 @@ fn resolve_document_move_order_plan(
build_document_move_order_plan_from_snapshot(payload, &snapshot).map(Some)
}
fn document_move_write_operation(
workspace_id: Option<&str>,
plan: Option<&DocumentMoveOrderPlan>,
) -> Value {
let Some(plan) = plan else {
return Value::Null;
};
json!({
"family": "tree",
"schema": "mnote.tree.write_operation",
"schemaVersion": 1,
"operation": "tree.subtree.move.write",
"workspaceId": workspace_id,
"documentId": plan.document_id,
"fromParentId": plan.from_parent_id,
"toParentId": plan.to_parent_id,
"requestedSortOrder": plan.requested_sort_order,
"normalizedSortOrder": plan.normalized_sort_order,
"patches": plan.patches,
})
}
fn resolve_document_move_preflight(
payload: &DocumentMoveCommandPayload,
preflight_data: Option<&Value>,
@@ -2048,6 +2069,32 @@ fn tree_domain_event_plan(event_type: &str, stream_delta_hint: Value) -> Value {
})
}
fn tree_domain_event_plan_with_payload(
event_type: &str,
payload: Value,
stream_delta_hint: Value,
) -> Value {
json!({
"family": "tree",
"schema": "mnote.tree.domain_event",
"schemaVersion": 1,
"eventType": event_type,
"payload": payload,
"streamDeltaHint": stream_delta_hint,
})
}
fn tree_resync_required_hint(reason: &str, args: Value) -> Value {
let mut hint_args = serde_json::Map::new();
hint_args.insert("reason".into(), Value::String(reason.into()));
if let Some(args) = args.as_object() {
for (key, value) in args {
hint_args.insert(key.clone(), value.clone());
}
}
tree_stream_delta_hint("resync_required", Value::Object(hint_args))
}
fn read_trimmed_str_field<'a>(value: &'a Value, field: &str) -> Option<&'a str> {
value
.get(field)
@@ -2077,6 +2124,23 @@ fn materialize_tree_stream_delta_from_hint(hint: &Value, result: &Value) -> Opti
match kind {
"noop" => Some(json!({ "op": "noop" })),
"resync_required" => {
let mut delta = serde_json::Map::new();
delta.insert("op".into(), Value::String("resync_required".into()));
if let Some(reason) = read_trimmed_str_field(args, "reason") {
delta.insert("reason".into(), Value::String(reason.into()));
}
if let Some(page_id) = read_trimmed_str_field(args, "pageId") {
delta.insert("pageId".into(), Value::String(page_id.into()));
}
if let Some(document_id) = read_trimmed_str_field(args, "documentId") {
delta.insert("documentId".into(), Value::String(document_id.into()));
}
if let Some(block_id) = read_trimmed_str_field(args, "blockId") {
delta.insert("blockId".into(), Value::String(block_id.into()));
}
Some(Value::Object(delta))
}
"remove_document" => {
let document_id = read_trimmed_str_field(args, "documentId")?;
Some(json!({
@@ -2244,6 +2308,13 @@ fn tree_artifact_payload(
map.insert("streamDelta".into(), stream_delta.clone());
}
}
if let Some(formal_payload) = domain_event_plan.get("payload").and_then(Value::as_object) {
if let Some(map) = payload.as_object_mut() {
for (key, value) in formal_payload {
map.insert(key.clone(), value.clone());
}
}
}
payload
}
@@ -2439,9 +2510,9 @@ pub fn execute_runtime_query(input: RuntimeInput) -> Result<Value, BridgeError>
tool,
data,
} => execute_tool_result(context, tool, data.unwrap_or(Value::Null)),
RuntimeInput::Command { .. } | RuntimeInput::CommandArtifact { .. } => Err(BridgeError::validation(
"execute_runtime_query 仅支持 query 输入",
)),
RuntimeInput::Command { .. } | RuntimeInput::CommandArtifact { .. } => Err(
BridgeError::validation("execute_runtime_query 仅支持 query 输入"),
),
}
}
@@ -2452,7 +2523,10 @@ pub fn build_success_response(plan: RuntimeExecutionPlan) -> RuntimeSuccess {
pub fn build_artifact_success_response(
artifacts: Option<RuntimeCommandArtifactPlan>,
) -> RuntimeArtifactSuccess {
RuntimeArtifactSuccess { ok: true, artifacts }
RuntimeArtifactSuccess {
ok: true,
artifacts,
}
}
pub fn execute_runtime_command_artifact(
@@ -2466,11 +2540,7 @@ pub fn execute_runtime_command_artifact(
result,
now,
} => Ok(build_runtime_command_artifact_plan(
&context,
&command,
&plan,
&result,
&now,
&context, &command, &plan, &result, &now,
)),
_ => Err(BridgeError::validation(
"execute_runtime_command_artifact 仅支持 command artifact 输入",
@@ -6540,7 +6610,11 @@ fn build_file_tree_projection_result(
fn normalize_projection_query(raw: Option<&str>) -> Option<String> {
let query = raw?.trim().to_lowercase();
if query.is_empty() { None } else { Some(query) }
if query.is_empty() {
None
} else {
Some(query)
}
}
fn file_tree_item_matches_query(item: &KernelProjectionItem, query: &str) -> bool {
@@ -7217,11 +7291,28 @@ fn execute_command(
actor_id: request.actor_id,
idempotency_key: request.idempotency_key,
payload_json: request.payload_json,
args_json: json!({
args_json: {
let stream_delta_hint = tree_resync_required_hint(
"blocks.patch",
json!({
"documentId": payload.document_id,
"blockId": payload.block_id,
}),
);
let domain_event_payload = block_patch_domain_event_payload(&payload);
json!({
"id": payload.document_id,
"blockId": payload.block_id,
"nextBlock": payload.next_block,
}),
"streamDeltaHint": stream_delta_hint,
"domainEventHint": tree_domain_event_hint("block.patched"),
"domainEventPlan": tree_domain_event_plan_with_payload(
"block.patched",
domain_event_payload,
stream_delta_hint,
),
})
},
}))
}
"blocks.move" => {
@@ -7245,6 +7336,14 @@ fn execute_command(
validate_only: command_wire.validate_only,
};
let request = build_write_request(&context, &command)?;
let stream_delta_hint = tree_resync_required_hint(
"blocks.move",
json!({
"documentId": payload.target_document_id,
"blockId": payload.block_id,
}),
);
let domain_event_payload = block_move_domain_event_payload(&payload);
Ok(RuntimeExecutionPlan::Command(RuntimeCommandExecutionPlan {
command_name: command.name,
command_id: command.command_id,
@@ -7259,6 +7358,13 @@ fn execute_command(
"id": payload.block_id,
"sourceDocumentId": payload.source_document_id,
"targetDocumentId": payload.target_document_id,
"streamDeltaHint": stream_delta_hint,
"domainEventHint": tree_domain_event_hint("block.moved"),
"domainEventPlan": tree_domain_event_plan_with_payload(
"block.moved",
domain_event_payload,
stream_delta_hint,
),
}),
}))
}
@@ -7283,6 +7389,14 @@ fn execute_command(
validate_only: command_wire.validate_only,
};
let request = build_write_request(&context, &command)?;
let stream_delta_hint = tree_resync_required_hint(
"blocks.embed",
json!({
"documentId": payload.target_document_id,
"blockId": payload.block_id,
}),
);
let domain_event_payload = block_embed_domain_event_payload(&payload);
Ok(RuntimeExecutionPlan::Command(RuntimeCommandExecutionPlan {
command_name: command.name,
command_id: command.command_id,
@@ -7298,6 +7412,13 @@ fn execute_command(
"blockId": payload.block_id,
"targetDocumentId": payload.target_document_id,
"targetBlockId": payload.target_block_id,
"streamDeltaHint": stream_delta_hint,
"domainEventHint": tree_domain_event_hint("block.embedded"),
"domainEventPlan": tree_domain_event_plan_with_payload(
"block.embedded",
domain_event_payload,
stream_delta_hint,
),
}),
}))
}
@@ -7426,8 +7547,7 @@ fn execute_command(
}))
}
"documents.stats.update" => {
let payload: DocumentStatsCommandPayload =
parse_payload(command_wire.payload.clone())?;
let payload: DocumentStatsCommandPayload = parse_payload(command_wire.payload.clone())?;
let stats = payload.stats;
let command = CommandEnvelope {
name: "documents.stats.update".into(),
@@ -7549,14 +7669,35 @@ fn execute_command(
actor_id: request.actor_id,
idempotency_key: request.idempotency_key,
payload_json: request.payload_json,
args_json: json!({
args_json: {
let stream_delta_hint = tree_resync_required_hint(
"page_body_saved",
json!({
"pageId": payload.document_id,
"documentId": payload.document_id,
}),
);
let domain_event_payload = document_save_domain_event_payload(
&payload,
&editor_document,
&canonical_content,
)?;
json!({
"id": payload.document_id,
"content": canonical_content,
"editorDocument": editor_document,
"tiptapDocument": payload.tiptap_document,
"expectedRevision": payload.revision,
"conflictDetectionKey": payload.conflict_detection_key,
}),
"streamDeltaHint": stream_delta_hint,
"domainEventHint": tree_domain_event_hint("page.body.saved"),
"domainEventPlan": tree_domain_event_plan_with_payload(
"page.body.saved",
domain_event_payload,
stream_delta_hint,
),
})
},
}))
}
"documents.embed" | "tree.node.embed" => {
@@ -7874,6 +8015,10 @@ fn execute_command(
"parentId": payload.parent_id,
"sortOrder": payload.sort_order,
"normalizedMove": normalized_move,
"treeWriteOperation": document_move_write_operation(
context.workspace_id.as_deref(),
normalized_move.as_ref(),
),
"streamDeltaHint": tree_stream_delta_hint("move_document", json!({
"documentId": payload.document_id,
"parentId": payload.parent_id,
@@ -8625,6 +8770,93 @@ fn legacy_content_from_editor_document(document: &EditorBlockDocument) -> Value
)
}
fn stable_json_content_hash(value: &Value) -> Result<String, BridgeError> {
let serialized = serde_json::to_string(value).map_err(|error| {
BridgeError::validation(format!("复合命令 payload content hash 序列化失败: {error}"))
})?;
let mut hash = 0xcbf29ce484222325u64;
for byte in serialized.as_bytes() {
hash ^= u64::from(*byte);
hash = hash.wrapping_mul(0x100000001b3);
}
Ok(format!("fnv1a64:{hash:016x}"))
}
fn editor_document_block_ids(document: &EditorBlockDocument) -> Vec<String> {
let mut ids = document.root_block_ids.clone();
for block in &document.blocks {
if !ids.iter().any(|id| id == &block.block_id) {
ids.push(block.block_id.clone());
}
}
ids
}
fn document_save_domain_event_payload(
payload: &DocumentSaveCommandPayload,
editor_document: &EditorBlockDocument,
canonical_content: &Value,
) -> Result<Value, BridgeError> {
Ok(json!({
"page": {
"id": payload.document_id.clone(),
"workspaceId": payload.workspace_id.clone(),
},
"snapshot": {
"version": payload.revision,
"contentHash": stable_json_content_hash(canonical_content)?,
"updatedAt": Value::Null,
},
"blocks": {
"ids": editor_document_block_ids(editor_document),
"count": editor_document.blocks.len(),
},
}))
}
fn block_patch_domain_event_payload(payload: &BlockPatchCommandPayload) -> Value {
let next_type = read_trimmed_str_field(&payload.next_block, "type")
.or_else(|| read_trimmed_str_field(&payload.next_block, "blockType"));
json!({
"document": {
"id": payload.document_id.clone(),
"workspaceId": payload.workspace_id.clone(),
},
"block": {
"id": payload.block_id.clone(),
},
"patch": {
"summary": "replace_block",
"nextType": next_type,
},
})
}
fn block_move_domain_event_payload(payload: &BlockMoveCommandPayload) -> Value {
json!({
"block": {
"id": payload.block_id.clone(),
},
"move": {
"sourceDocumentId": payload.source_document_id.clone(),
"targetDocumentId": payload.target_document_id.clone(),
},
})
}
fn block_embed_domain_event_payload(payload: &BlockEmbedCommandPayload) -> Value {
json!({
"block": {
"id": payload.block_id.clone(),
},
"embed": {
"sourceDocumentId": payload.source_document_id.clone(),
"targetDocumentId": payload.target_document_id.clone(),
"targetBlockId": payload.target_block_id.clone(),
},
})
}
fn to_bridge_context(context: RuntimeBridgeContextWire) -> BridgeContext {
BridgeContext {
deployment_id: context.deployment_id,
@@ -8791,6 +9023,26 @@ mod tests {
"id": "block_1",
"type": "paragraph",
},
"streamDeltaHint": {
"family": "tree",
"kind": "noop",
"args": {}
},
"domainEventHint": {
"family": "tree",
"eventType": "tree.block.patched"
},
"domainEventPlan": {
"family": "tree",
"schema": "mnote.tree.domain_event",
"schemaVersion": 1,
"eventType": "tree.block.patched",
"streamDeltaHint": {
"family": "tree",
"kind": "noop",
"args": {}
}
}
})
);
}
@@ -9866,6 +10118,108 @@ mod tests {
);
}
#[test]
fn documents_save_command_plans_include_formal_domain_event_contract() {
let plan = execute_runtime_input(RuntimeInput::Command {
context: demo_context(),
command: RuntimeCommandEnvelopeWire {
name: "page.body.save".into(),
command_id: "cmd_save_contract_1".into(),
idempotency_key: Some("idem_save_contract_1".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: "wolai-frontend".into(),
},
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": 8,
"content": [
{
"id": "legacy_content_1",
"type": "paragraph",
"content": "来自旧式 content-only 保存"
}
],
"conflictDetectionKey": "doc_1:8"
}),
preflight_data: None,
reason: Some("保存正文".into()),
refs: vec![],
dry_run: false,
validate_only: false,
},
})
.expect("page.body.save plan should build");
let RuntimeExecutionPlan::Command(plan) = plan else {
panic!("expected command plan");
};
assert_eq!(
plan.args_json["streamDeltaHint"],
json!({
"family": "tree",
"kind": "resync_required",
"args": {
"reason": "page_body_saved",
"pageId": "doc_1",
"documentId": "doc_1"
}
})
);
assert_eq!(
plan.args_json["domainEventHint"],
json!({
"family": "tree",
"eventType": "page.body.saved"
})
);
assert_eq!(
plan.args_json["domainEventPlan"],
json!({
"family": "tree",
"schema": "mnote.tree.domain_event",
"schemaVersion": 1,
"eventType": "page.body.saved",
"payload": {
"page": {
"id": "doc_1",
"workspaceId": "ws_1"
},
"snapshot": {
"version": 8,
"contentHash": "fnv1a64:d039f8f3496411e8",
"updatedAt": null
},
"blocks": {
"ids": ["legacy_content_1"],
"count": 1
}
},
"streamDeltaHint": {
"family": "tree",
"kind": "resync_required",
"args": {
"reason": "page_body_saved",
"pageId": "doc_1",
"documentId": "doc_1"
}
}
})
);
}
#[test]
fn blocks_move_command_plan_maps_to_blocks_move() {
let plan = execute_runtime_input(RuntimeInput::Command {
@@ -9912,6 +10266,43 @@ mod tests {
"id": "block_1",
"sourceDocumentId": "doc_1",
"targetDocumentId": "doc_2",
"streamDeltaHint": {
"family": "tree",
"kind": "resync_required",
"args": {
"reason": "blocks.move",
"documentId": "doc_2",
"blockId": "block_1"
}
},
"domainEventHint": {
"family": "tree",
"eventType": "block.moved"
},
"domainEventPlan": {
"family": "tree",
"schema": "mnote.tree.domain_event",
"schemaVersion": 1,
"eventType": "block.moved",
"payload": {
"block": {
"id": "block_1"
},
"move": {
"sourceDocumentId": "doc_1",
"targetDocumentId": "doc_2"
}
},
"streamDeltaHint": {
"family": "tree",
"kind": "resync_required",
"args": {
"reason": "blocks.move",
"documentId": "doc_2",
"blockId": "block_1"
}
}
}
})
);
}
@@ -9968,6 +10359,44 @@ mod tests {
"blockId": "block_1",
"targetDocumentId": "doc_2",
"targetBlockId": "anchor_1",
"streamDeltaHint": {
"family": "tree",
"kind": "resync_required",
"args": {
"reason": "blocks.embed",
"documentId": "doc_2",
"blockId": "block_1"
}
},
"domainEventHint": {
"family": "tree",
"eventType": "block.embedded"
},
"domainEventPlan": {
"family": "tree",
"schema": "mnote.tree.domain_event",
"schemaVersion": 1,
"eventType": "block.embedded",
"payload": {
"block": {
"id": "block_1"
},
"embed": {
"sourceDocumentId": "doc_1",
"targetDocumentId": "doc_2",
"targetBlockId": "anchor_1"
}
},
"streamDeltaHint": {
"family": "tree",
"kind": "resync_required",
"args": {
"reason": "blocks.embed",
"documentId": "doc_2",
"blockId": "block_1"
}
}
}
})
);
}
@@ -9976,6 +10405,159 @@ mod tests {
}
}
#[test]
fn block_commands_include_formal_domain_event_contract() {
let cases = [
(
"blocks.patch",
json!({
"documentId": "doc_1",
"workspaceId": "ws_1",
"blockId": "block_1",
"nextBlock": {
"id": "block_1",
"type": "paragraph"
}
}),
"block.patched",
json!({
"document": {
"id": "doc_1",
"workspaceId": "ws_1"
},
"block": {
"id": "block_1"
},
"patch": {
"summary": "replace_block",
"nextType": "paragraph"
}
}),
),
(
"blocks.move",
json!({
"sourceDocumentId": "doc_1",
"blockId": "block_1",
"targetDocumentId": "doc_2"
}),
"block.moved",
json!({
"block": {
"id": "block_1"
},
"move": {
"sourceDocumentId": "doc_1",
"targetDocumentId": "doc_2"
}
}),
),
(
"blocks.embed",
json!({
"sourceDocumentId": "doc_1",
"blockId": "block_1",
"targetDocumentId": "doc_2",
"targetBlockId": "anchor_1"
}),
"block.embedded",
json!({
"block": {
"id": "block_1"
},
"embed": {
"sourceDocumentId": "doc_1",
"targetDocumentId": "doc_2",
"targetBlockId": "anchor_1"
}
}),
),
];
for (command_name, payload, event_type, expected_payload) in cases {
let plan = execute_runtime_input(RuntimeInput::Command {
context: demo_context(),
command: RuntimeCommandEnvelopeWire {
name: command_name.into(),
command_id: format!("cmd_{command_name}_contract"),
idempotency_key: Some(format!("idem_{command_name}_contract")),
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(),
},
target: Some(RuntimeTargetWire {
workspace_id: Some("ws_1".into()),
page_id: Some("doc_2".into()),
block_id: Some("block_1".into()),
}),
payload,
preflight_data: None,
reason: Some("块命令 formal contract".into()),
refs: vec![],
dry_run: false,
validate_only: false,
},
})
.expect("block command plan should build");
let RuntimeExecutionPlan::Command(plan) = plan else {
panic!("expected command plan");
};
assert_eq!(
plan.args_json["streamDeltaHint"],
json!({
"family": "tree",
"kind": "resync_required",
"args": {
"reason": command_name,
"documentId": if command_name == "blocks.patch" {
"doc_1"
} else {
"doc_2"
},
"blockId": "block_1"
}
})
);
assert_eq!(
plan.args_json["domainEventHint"],
json!({
"family": "tree",
"eventType": event_type
})
);
assert_eq!(
plan.args_json["domainEventPlan"],
json!({
"family": "tree",
"schema": "mnote.tree.domain_event",
"schemaVersion": 1,
"eventType": event_type,
"payload": expected_payload,
"streamDeltaHint": {
"family": "tree",
"kind": "resync_required",
"args": {
"reason": command_name,
"documentId": if command_name == "blocks.patch" {
"doc_1"
} else {
"doc_2"
},
"blockId": "block_1"
}
}
})
);
}
}
#[test]
fn documents_embed_command_plan_maps_to_documents_update_content() {
let plan = execute_runtime_input(RuntimeInput::Command {
@@ -10695,6 +11277,78 @@ mod tests {
);
}
#[test]
fn tree_subtree_move_command_includes_tree_write_operation_from_snapshot() {
let plan = execute_runtime_input(RuntimeInput::Command {
context: demo_context(),
command: RuntimeCommandEnvelopeWire {
name: "tree.subtree.move".into(),
command_id: "cmd_tree_move_write".into(),
idempotency_key: Some("idem_tree_move_write".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: "wolai-frontend".into(),
},
target: Some(RuntimeTargetWire {
workspace_id: Some("ws_1".into()),
page_id: Some("doc_b".into()),
block_id: None,
}),
payload: json!({
"documentId": "doc_b",
"parentId": "target",
"sortOrder": 99
}),
preflight_data: Some(json!({
"documents": [
{ "id": "target", "workspace_id": "ws_1", "parent_id": null, "sort_order": 0, "created_at": "2026-04-25T00:00:00Z" },
{ "id": "doc_a", "workspace_id": "ws_1", "parent_id": "source", "sort_order": 0, "created_at": "2026-04-25T00:00:01Z" },
{ "id": "doc_b", "workspace_id": "ws_1", "parent_id": "source", "sort_order": 1, "created_at": "2026-04-25T00:00:02Z" },
{ "id": "doc_c", "workspace_id": "ws_1", "parent_id": "target", "sort_order": 0, "created_at": "2026-04-25T00:00:03Z" }
]
})),
reason: Some("树命令切流".into()),
refs: vec![],
dry_run: false,
validate_only: false,
},
})
.expect("command plan should build");
let RuntimeExecutionPlan::Command(plan) = plan else {
panic!("expected command plan");
};
assert_eq!(
plan.args_json["treeWriteOperation"],
json!({
"family": "tree",
"schema": "mnote.tree.write_operation",
"schemaVersion": 1,
"operation": "tree.subtree.move.write",
"workspaceId": "ws_1",
"documentId": "doc_b",
"fromParentId": "source",
"toParentId": "target",
"requestedSortOrder": 99,
"normalizedSortOrder": 1,
"patches": [
{
"documentId": "doc_b",
"parentId": "target",
"sortOrder": 1,
"moved": true
}
]
})
);
}
#[test]
fn tree_command_artifact_plan_materializes_domain_event_payload_from_rust_plan() {
let context = demo_context();
@@ -12397,12 +13051,10 @@ mod tests {
result["items"][0]["resourceMeta"]["resourceKind"],
json!("document")
);
assert!(
result["items"][0]["capabilities"]
.as_array()
.map(|caps| caps.contains(&json!("create-child")))
.unwrap_or(false)
);
assert!(result["items"][0]["capabilities"]
.as_array()
.map(|caps| caps.contains(&json!("create-child")))
.unwrap_or(false));
assert_eq!(result["items"][1]["parentNodeId"], json!("page_root"));
assert_eq!(result["items"][1]["position"], json!(1));
}