fix: refresh custom world publish gate diagnostics

This commit is contained in:
2026-04-24 12:42:43 +08:00
parent 5050ce4ff8
commit 49a79aee54
5 changed files with 321 additions and 6 deletions

View File

@@ -38,6 +38,7 @@ use spacetime_client::{
CustomWorldWorkSummaryRecord, SpacetimeClientError,
};
use std::convert::Infallible;
use tracing::info;
use crate::{
api_response::json_success_body,
@@ -471,6 +472,7 @@ pub async fn get_custom_world_agent_session(
.map_err(|error| {
custom_world_error_response(&request_context, map_custom_world_client_error(error))
})?;
log_custom_world_publish_gate_diagnostics("get_session", &session);
Ok(json_success_body(
Some(&request_context),
@@ -998,6 +1000,19 @@ pub async fn execute_custom_world_agent_action(
custom_world_error_response(&request_context, map_custom_world_client_error(error))
})?;
if matches!(
action.as_str(),
"sync_result_profile" | "publish_world" | "draft_foundation"
) {
if let Ok(session) = state
.spacetime_client()
.get_custom_world_agent_session(session_id.clone(), owner_user_id.clone())
.await
{
log_custom_world_publish_gate_diagnostics(action.as_str(), &session);
}
}
Ok(json_success_body(
Some(&request_context),
json!({
@@ -1131,6 +1146,109 @@ fn map_custom_world_agent_session_response(
}
}
fn log_custom_world_publish_gate_diagnostics(
source: &str,
session: &CustomWorldAgentSessionRecord,
) {
let draft_profile = session.draft_profile.as_object();
let preview_profile = session
.result_preview
.as_ref()
.and_then(|preview| preview.get("preview"))
.and_then(Value::as_object);
let profile = preview_profile.or(draft_profile);
let blocker_codes = session
.publish_gate
.as_ref()
.map(|gate| {
gate.blockers
.iter()
.map(|blocker| blocker.code.as_str())
.collect::<Vec<_>>()
.join(",")
})
.unwrap_or_default();
info!(
target: "custom_world.publish_gate",
source,
session_id = %session.session_id,
stage = %session.stage,
publish_ready = session.publish_gate.as_ref().map(|gate| gate.publish_ready).unwrap_or(false),
blocker_count = session.publish_gate.as_ref().map(|gate| gate.blocker_count).unwrap_or(0),
blocker_codes = %blocker_codes,
has_draft_profile = session.draft_profile.as_object().map(|value| !value.is_empty()).unwrap_or(false),
has_result_preview = session.result_preview.is_some(),
preview_source = session.result_preview.as_ref().and_then(|value| value.get("source")).and_then(Value::as_str).unwrap_or(""),
has_world_hook = has_custom_world_publish_text(profile, &["worldHook", "creatorIntent.worldHook", "anchorContent.worldPromise.hook", "settingText"]),
has_player_premise = has_custom_world_publish_text(profile, &["playerPremise", "creatorIntent.playerPremise", "anchorContent.playerEntryPoint.openingIdentity", "anchorContent.playerEntryPoint.openingProblem", "anchorContent.playerEntryPoint.entryMotivation"]),
has_core_conflicts = has_custom_world_non_empty_text_array(profile, "coreConflicts"),
has_main_chapter = has_custom_world_array(profile, "chapters") || has_custom_world_array(profile, "sceneChapterBlueprints") || has_custom_world_array(profile, "sceneChapters"),
has_scene_act = has_custom_world_scene_act(profile),
"custom world publish gate diagnostics"
);
}
fn has_custom_world_publish_text(profile: Option<&Map<String, Value>>, paths: &[&str]) -> bool {
paths.iter().any(|path| {
let mut segments = path.split('.');
let Some(first_segment) = segments.next() else {
return false;
};
let mut current = profile.and_then(|value| value.get(first_segment));
for segment in segments {
current = current.and_then(|value| value.get(segment));
}
current
.and_then(Value::as_str)
.map(str::trim)
.map(|value| !value.is_empty())
.unwrap_or(false)
})
}
fn has_custom_world_non_empty_text_array(profile: Option<&Map<String, Value>>, key: &str) -> bool {
profile
.and_then(|value| value.get(key))
.and_then(Value::as_array)
.map(|items| {
items.iter().any(|item| {
item.as_str()
.map(str::trim)
.is_some_and(|value| !value.is_empty())
})
})
.unwrap_or(false)
}
fn has_custom_world_array(profile: Option<&Map<String, Value>>, key: &str) -> bool {
profile
.and_then(|value| value.get(key))
.and_then(Value::as_array)
.map(|items| !items.is_empty())
.unwrap_or(false)
}
fn has_custom_world_scene_act(profile: Option<&Map<String, Value>>) -> bool {
profile
.and_then(|value| {
value
.get("sceneChapterBlueprints")
.or_else(|| value.get("sceneChapters"))
})
.and_then(Value::as_array)
.map(|chapters| {
chapters.iter().any(|chapter| {
chapter
.get("acts")
.and_then(Value::as_array)
.map(|acts| !acts.is_empty())
.unwrap_or(false)
})
})
.unwrap_or(false)
}
fn map_custom_world_publish_gate_response(
gate: CustomWorldPublishGateRecord,
) -> CustomWorldPublishGateResponse {