- 归档 OnlyOffice live bridge、Page AI、mindmap、design governance 与相关 bug 条目 - 补齐 MinerU OCR 后端 runtime 合同与 smoke/test 基线 - 收口 ChatOnly/Doubao、ObjectIdentity、Page Aggregate compat 与 runtime owner 文档口径 验证: - cargo test --manifest-path rust/Cargo.toml -p mnote-web local_ocr -- --test-threads=1 - cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_bridge -- --test-threads=1 - git diff --check - git diff --cached --check - codegraph index . --force && codegraph status . - codegraph sync . && codegraph status .
224 lines
6.9 KiB
Rust
224 lines
6.9 KiB
Rust
use crate::app::AppConfig;
|
|
use crate::context::RequestContext;
|
|
use crate::error::WebError;
|
|
use bridge_runtime::{
|
|
build_runtime_command_artifact_plan, RuntimeCommandArtifactPlan, RuntimeCommandExecutionPlan,
|
|
RuntimeQueryExecutionPlan,
|
|
};
|
|
use serde_json::Value;
|
|
use time::{format_description::well_known::Rfc3339, OffsetDateTime};
|
|
|
|
#[derive(Debug)]
|
|
pub struct RetiredCloudCommandExecution {
|
|
pub result: Value,
|
|
pub artifacts: Option<RuntimeCommandArtifactPlan>,
|
|
pub artifact_error: Option<String>,
|
|
}
|
|
|
|
fn retired_error(context: &RequestContext, phase: &'static str) -> WebError {
|
|
WebError::service_unavailable_code(
|
|
"convex_retired",
|
|
"Convex 运行时已退役;请使用 local-first Rust/SQLite control-plane 路径",
|
|
)
|
|
.with_context(context)
|
|
.with_header("x-error-phase", phase)
|
|
.with_header("x-upstream-service", "convex-retired")
|
|
}
|
|
|
|
fn load_query_fixture(
|
|
config: &AppConfig,
|
|
context: &RequestContext,
|
|
plan: &RuntimeQueryExecutionPlan,
|
|
) -> Result<Option<Value>, WebError> {
|
|
if !config.allow_dev_fixtures {
|
|
return Ok(None);
|
|
}
|
|
|
|
let Some(raw) = config.query_fixtures_json.as_deref() else {
|
|
return Ok(None);
|
|
};
|
|
let trimmed = raw.trim();
|
|
if trimmed.is_empty() {
|
|
return Ok(None);
|
|
}
|
|
|
|
let fixtures: Value = serde_json::from_str(trimmed).map_err(|error| {
|
|
WebError::internal(format!("MNOTE_WEB_QUERY_FIXTURES_JSON 非法: {error}"))
|
|
.with_context(context)
|
|
.with_header("x-error-phase", "fixture_parse")
|
|
})?;
|
|
|
|
Ok(fixtures
|
|
.as_object()
|
|
.and_then(|map| map.get(plan.function_name.as_str()))
|
|
.cloned())
|
|
}
|
|
|
|
fn load_mutation_fixture_by_name(
|
|
config: &AppConfig,
|
|
context: &RequestContext,
|
|
function_name: &str,
|
|
) -> Result<Option<Value>, WebError> {
|
|
if !config.allow_dev_fixtures {
|
|
return Ok(None);
|
|
}
|
|
|
|
let Some(raw) = config.mutation_fixtures_json.as_deref() else {
|
|
return Ok(None);
|
|
};
|
|
let trimmed = raw.trim();
|
|
if trimmed.is_empty() {
|
|
return Ok(None);
|
|
}
|
|
|
|
let fixtures: Value = serde_json::from_str(trimmed).map_err(|error| {
|
|
WebError::internal(format!("MNOTE_WEB_MUTATION_FIXTURES_JSON 非法: {error}"))
|
|
.with_context(context)
|
|
.with_header("x-error-phase", "fixture_parse")
|
|
})?;
|
|
|
|
Ok(fixtures
|
|
.as_object()
|
|
.and_then(|map| map.get(function_name))
|
|
.cloned())
|
|
}
|
|
|
|
fn load_mutation_fixture(
|
|
config: &AppConfig,
|
|
context: &RequestContext,
|
|
plan: &RuntimeCommandExecutionPlan,
|
|
) -> Result<Option<Value>, WebError> {
|
|
load_mutation_fixture_by_name(config, context, plan.function_name.as_str())
|
|
}
|
|
|
|
pub async fn execute_retired_query_plan(
|
|
config: &AppConfig,
|
|
context: &RequestContext,
|
|
plan: &RuntimeQueryExecutionPlan,
|
|
) -> Result<Value, WebError> {
|
|
if plan.function_name.trim().is_empty() || plan.function_name.ends_with(":unknown") {
|
|
return Err(WebError::bad_request_code(
|
|
"transport_query_unsupported",
|
|
format!("mnote-web transport 暂不支持 query: {}", plan.function_name),
|
|
)
|
|
.with_context(context)
|
|
.with_header("x-error-phase", "plan_validation"));
|
|
}
|
|
|
|
if let Some(fixture) = load_query_fixture(config, context, plan)? {
|
|
return Ok(fixture);
|
|
}
|
|
|
|
Err(retired_error(context, "convex_query_retired"))
|
|
}
|
|
|
|
pub async fn execute_sidebar_dataset_query(
|
|
config: &AppConfig,
|
|
context: &RequestContext,
|
|
plan: &RuntimeQueryExecutionPlan,
|
|
) -> Result<Value, WebError> {
|
|
execute_retired_query_plan(config, context, plan).await
|
|
}
|
|
|
|
pub async fn execute_retired_query_by_name(
|
|
config: &AppConfig,
|
|
context: &RequestContext,
|
|
function_name: &str,
|
|
args: Value,
|
|
workspace_id: Option<&str>,
|
|
error_phase: &'static str,
|
|
) -> Result<Value, WebError> {
|
|
let plan = RuntimeQueryExecutionPlan {
|
|
query_name: function_name.to_string(),
|
|
function_name: function_name.to_string(),
|
|
workspace_id: workspace_id.map(ToOwned::to_owned),
|
|
request_id: context.trace.request_id.clone(),
|
|
trace_id: context.trace.trace_id.clone(),
|
|
actor_id: context.auth.actor_id.clone(),
|
|
payload_json: args.to_string(),
|
|
args_json: args,
|
|
};
|
|
execute_retired_query_plan(config, context, &plan)
|
|
.await
|
|
.map_err(|error| error.with_header("x-error-phase", error_phase))
|
|
}
|
|
|
|
pub async fn execute_retired_command_plan(
|
|
config: &AppConfig,
|
|
context: &RequestContext,
|
|
plan: &RuntimeCommandExecutionPlan,
|
|
) -> Result<Value, WebError> {
|
|
if plan.function_name.trim().is_empty() || plan.function_name.ends_with(":unknown") {
|
|
return Err(WebError::bad_request_code(
|
|
"transport_command_unsupported",
|
|
format!(
|
|
"mnote-web transport 暂不支持 command: {}",
|
|
plan.function_name
|
|
),
|
|
)
|
|
.with_context(context)
|
|
.with_header("x-error-phase", "plan_validation"));
|
|
}
|
|
|
|
if let Some(fixture) = load_mutation_fixture(config, context, plan)? {
|
|
return Ok(fixture);
|
|
}
|
|
|
|
Err(retired_error(context, "convex_mutation_retired"))
|
|
}
|
|
|
|
pub async fn execute_retired_mutation_by_name(
|
|
config: &AppConfig,
|
|
context: &RequestContext,
|
|
function_name: &str,
|
|
args: Value,
|
|
_workspace_id: Option<&str>,
|
|
_idempotency_key: Option<&str>,
|
|
error_phase: &'static str,
|
|
) -> Result<Value, WebError> {
|
|
if let Some(fixture) = load_mutation_fixture_by_name(config, context, function_name)? {
|
|
return Ok(fixture);
|
|
}
|
|
|
|
let _ = args;
|
|
Err(retired_error(context, error_phase))
|
|
}
|
|
|
|
pub async fn persist_runtime_command_artifacts(
|
|
_config: &AppConfig,
|
|
_context: &RequestContext,
|
|
_artifacts: &RuntimeCommandArtifactPlan,
|
|
) -> Result<(), WebError> {
|
|
// Convex 已退役。Rust 侧仍会把 artifact plan 返回给调用方和 realtime
|
|
// consumer;这里保持 no-op,避免兼容路径因为历史持久化层退役而失败。
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn execute_retired_command_plan_with_artifacts(
|
|
config: &AppConfig,
|
|
context: &RequestContext,
|
|
runtime_context: &bridge_runtime::RuntimeBridgeContextWire,
|
|
command: &bridge_runtime::RuntimeCommandEnvelopeWire,
|
|
plan: &RuntimeCommandExecutionPlan,
|
|
) -> Result<RetiredCloudCommandExecution, WebError> {
|
|
let result = execute_retired_command_plan(config, context, plan).await?;
|
|
let now = OffsetDateTime::now_utc()
|
|
.format(&Rfc3339)
|
|
.unwrap_or_else(|_| "1970-01-01T00:00:00Z".into());
|
|
let artifacts =
|
|
build_runtime_command_artifact_plan(runtime_context, command, plan, &result, &now);
|
|
let artifact_error = if let Some(artifacts) = artifacts.as_ref() {
|
|
persist_runtime_command_artifacts(config, context, artifacts)
|
|
.await
|
|
.err()
|
|
.map(|error| error.message().to_string())
|
|
} else {
|
|
None
|
|
};
|
|
Ok(RetiredCloudCommandExecution {
|
|
result,
|
|
artifacts,
|
|
artifact_error,
|
|
})
|
|
}
|