feat(kernel): finish phase3 bridge cutover and phase4 tree projection

- add generic mnote-web query transport and bridge routes for workspace/request/trace

- route sidebar compat traffic through mnote-web and tighten fixture fallback to dev/test

- unify sidebar/page tree/file tree/picker consumers on page_tree projection

- sync phase3/phase4 checklist, breakdown docs, and harness progress state
This commit is contained in:
lix-2026
2026-04-17 00:25:28 +08:00
parent b1d5d97142
commit ddf285b82d
27 changed files with 2650 additions and 340 deletions
@@ -0,0 +1,131 @@
use crate::app::AppConfig;
use crate::context::RequestContext;
use crate::error::WebError;
use crate::transport::convex::execute_convex_query_plan;
use bridge_runtime::{
execute_runtime_input, execute_runtime_query, RuntimeActorWire, RuntimeBridgeContextWire,
RuntimeExecutionPlan, RuntimeInput, RuntimeQueryEnvelopeWire, RuntimeQueryExecutionPlan,
RuntimeSourceWire,
};
use serde_json::Value;
pub fn resolve_effective_workspace_id(
context: &RequestContext,
query_workspace_id: Option<&str>,
require_workspace: bool,
) -> Result<Option<String>, WebError> {
let query_workspace_id = query_workspace_id
.map(str::trim)
.filter(|value| !value.is_empty());
let header_workspace_id = context
.workspace
.workspace_id
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty());
if let (Some(query_id), Some(header_id)) = (query_workspace_id, header_workspace_id) {
if query_id != header_id {
return Err(WebError::bad_request_code(
"workspace_context_conflict",
format!("workspaceId 参数与请求头中的 workspace 不一致: {query_id} != {header_id}"),
)
.with_context(context)
.with_header("x-error-phase", "workspace_resolve"));
}
}
let effective_workspace_id = query_workspace_id.or(header_workspace_id);
if require_workspace && effective_workspace_id.is_none() {
return Err(WebError::bad_request_code(
"workspace_required",
"缺少 workspaceId,请在 query 或请求头中提供有效工作区",
)
.with_context(context)
.with_header("x-error-phase", "workspace_resolve"));
}
Ok(effective_workspace_id.map(ToOwned::to_owned))
}
pub fn runtime_context(
context: &RequestContext,
effective_workspace_id: Option<&str>,
) -> RuntimeBridgeContextWire {
RuntimeBridgeContextWire {
deployment_id: context.workspace.deployment_id.clone(),
project_id: context.workspace.project_id.clone(),
workspace_id: effective_workspace_id
.map(ToOwned::to_owned)
.or_else(|| context.workspace.workspace_id.clone()),
request_id: context.trace.request_id.clone(),
trace_id: context.trace.trace_id.clone(),
actor: RuntimeActorWire {
actor_type: context.auth.actor_type.clone(),
actor_id: context.auth.actor_id.clone(),
session_id: context.auth.session_id.clone(),
},
source: RuntimeSourceWire {
channel: context.source.channel.clone(),
client: context.source.client.clone(),
},
tenant_id: context.workspace.tenant_id.clone(),
auth_token: context.auth.authorization.clone(),
idempotency_key: context.source.idempotency_key.clone(),
validate_only: false,
dry_run: false,
}
}
pub fn build_runtime_query_plan(
context: &RequestContext,
effective_workspace_id: Option<&str>,
query: RuntimeQueryEnvelopeWire,
) -> Result<RuntimeQueryExecutionPlan, WebError> {
let runtime_input = RuntimeInput::Query {
context: runtime_context(context, effective_workspace_id),
query,
data: None,
};
let RuntimeExecutionPlan::Query(plan) = execute_runtime_input(runtime_input)
.map_err(|error| WebError::bad_request(error.message).with_context(context))?
else {
return Err(WebError::internal("runtime query 未返回 query plan").with_context(context));
};
Ok(plan)
}
pub fn fetch_query_data_via_convex(
config: &AppConfig,
context: &RequestContext,
effective_workspace_id: Option<&str>,
query: RuntimeQueryEnvelopeWire,
) -> Result<Value, WebError> {
let plan = build_runtime_query_plan(context, effective_workspace_id, query)?;
execute_convex_query_plan(config, context, &plan)
}
pub fn execute_runtime_query_against_data(
context: &RequestContext,
effective_workspace_id: Option<&str>,
query: RuntimeQueryEnvelopeWire,
data: Value,
) -> Result<Value, WebError> {
execute_runtime_query(RuntimeInput::Query {
context: runtime_context(context, effective_workspace_id),
query,
data: Some(data),
})
.map_err(|error| WebError::bad_request(error.message).with_context(context))
}
pub fn execute_runtime_query_via_convex(
config: &AppConfig,
context: &RequestContext,
effective_workspace_id: Option<&str>,
query: RuntimeQueryEnvelopeWire,
) -> Result<Value, WebError> {
let data = fetch_query_data_via_convex(config, context, effective_workspace_id, query.clone())?;
execute_runtime_query_against_data(context, effective_workspace_id, query, data)
}