重构:文本判据债改名 SuperErrorEnumFromStringTyped 并写清上游改造 TODO
- turn_error.rs 把 DomainFact 改名为故意扎眼的 SuperErrorEnumFromStringTyped,标记"从字符串读出的 typed 失败事实表"这笔待清债 - TODO 从"产生层还没有 typed 出口"改为明确指向上游改造:让产生层直接产出 typed 变体/结构化子错误、判定层改 match,之后整张表与 classify_direct_codex_error 一起删 - enum 注释写明这个名字是债标记:禁止把它改漂亮,只允许按 TODO 删掉整张表 - 只改名与注释,不改任何判定逻辑
This commit is contained in:
@@ -24,8 +24,12 @@
|
||||
//! - app-server 投影([`TurnError::from_model_call`]):模型 / 上游 / 通道类失败;
|
||||
//! - 执行适配器(`codex_app_server::execution`):宿主亲眼看到的收场事实(通道断开 / 超时 / 中断)。
|
||||
//!
|
||||
//! TODO(文本判据):[`DomainFact::classify`] 仍在读文本——那是产生层还没有 typed 出口的
|
||||
//! 事实(美术凭据、项目历史、执行预算)。这条债要在产生层逐个补 typed 变体后清掉。
|
||||
//! TODO(文本判据):[`SuperErrorEnumFromStringTyped::classify`] 把**产生层交上来的字符串**
|
||||
//! 读回 typed 事实。这是临时债,不是设计:上游(assets 私有凭据、项目写锁 / 历史追加锁、
|
||||
//! 项目历史形状、图集恢复、执行预算、试玩次数、工具参数)目前只交 `String` detail,没有
|
||||
//! typed 出口。修法在**上游**:让这些产生层直接产出 typed 变体 / 结构化子错误,判定层改成
|
||||
//! `match`;等上游补完,这张表和 `runtime_error::classify_direct_codex_error` 一起删掉。
|
||||
//! 补齐之前:**不许往表里加词**——新增分类必须先在产生层加 typed 变体。
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
@@ -200,7 +204,7 @@ impl NativeKind {
|
||||
/// 这一条分类值不值得当作"再试一次可能修好":与 [`Self::is_terminal`] 互为反义,但语义不同——
|
||||
/// 这里问的是"用户重试有没有意义",用于失败诊断的 `retryable` 字段。
|
||||
///
|
||||
/// `Unauthorized` 必须与 [`DomainFact::AuthenticationRejected`] 同口径:登录态失效重登
|
||||
/// `Unauthorized` 必须与 [`SuperErrorEnumFromStringTyped::AuthenticationRejected`] 同口径:登录态失效重登
|
||||
/// 之后再发一次是有意义的,`recovery_hint` 也是这么写的。两套分类路径给出相反结论,会让同一
|
||||
/// 份事实的 `retryable` 取决于哪一层先认出它。
|
||||
fn is_retryable(&self) -> bool {
|
||||
@@ -638,9 +642,11 @@ impl TurnError {
|
||||
// 阶段失败 / 桥变体:**认出是哪一类就拦**(产生层还没 typed 出口的深层事实才继续
|
||||
// 反馈)。已归类的都是模型改不动的事实——凭据 / 权限 / 额度 / 历史一致性与契约变化,
|
||||
// 把同一份输入再跑一轮只会拿到同一结论;旧的字面量判据也是这个口径。
|
||||
Self::TurnFailed(payload) => DomainFact::classify(&payload.detail).is_none(),
|
||||
Self::TurnFailed(payload) => {
|
||||
SuperErrorEnumFromStringTyped::classify(&payload.detail).is_none()
|
||||
}
|
||||
Self::TurnFailedUnclassified(payload) => {
|
||||
DomainFact::classify(&payload.detail).is_none()
|
||||
SuperErrorEnumFromStringTyped::classify(&payload.detail).is_none()
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
@@ -653,11 +659,11 @@ impl TurnError {
|
||||
Self::TransportClosed(_) => false,
|
||||
// 超时/中断后重试是常规动作:宿主已经把这一轮收干净了。
|
||||
Self::TimedOut(_) | Self::TurnInterrupted(_) => true,
|
||||
Self::TurnFailed(payload) => {
|
||||
DomainFact::classify(&payload.detail).is_none_or(DomainFact::is_retryable)
|
||||
}
|
||||
Self::TurnFailed(payload) => SuperErrorEnumFromStringTyped::classify(&payload.detail)
|
||||
.is_none_or(SuperErrorEnumFromStringTyped::is_retryable),
|
||||
Self::TurnFailedUnclassified(payload) => {
|
||||
DomainFact::classify(&payload.detail).is_none_or(DomainFact::is_retryable)
|
||||
SuperErrorEnumFromStringTyped::classify(&payload.detail)
|
||||
.is_none_or(SuperErrorEnumFromStringTyped::is_retryable)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
@@ -667,11 +673,11 @@ impl TurnError {
|
||||
pub(crate) fn public_summary(&self) -> Option<&'static str> {
|
||||
match self {
|
||||
Self::ModelCallFailed(payload) => payload.kind.public_summary(),
|
||||
Self::TurnFailed(payload) => {
|
||||
DomainFact::classify(&payload.detail).and_then(DomainFact::public_summary)
|
||||
}
|
||||
Self::TurnFailed(payload) => SuperErrorEnumFromStringTyped::classify(&payload.detail)
|
||||
.and_then(SuperErrorEnumFromStringTyped::public_summary),
|
||||
Self::TurnFailedUnclassified(payload) => {
|
||||
DomainFact::classify(&payload.detail).and_then(DomainFact::public_summary)
|
||||
SuperErrorEnumFromStringTyped::classify(&payload.detail)
|
||||
.and_then(SuperErrorEnumFromStringTyped::public_summary)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
@@ -783,12 +789,15 @@ fn native_kind(detail: &str) -> Option<NativeKind> {
|
||||
Some(NativeKind::from_id(id))
|
||||
}
|
||||
|
||||
/// 深层域事实:**产生层还没有 typed 出口**的事实,在这里读成 typed 值,之后所有决策只 `match`。
|
||||
/// 深层失败事实**表**:**产生层还没有 typed 出口**的事实,在这里读成 typed 值,之后所有决策只 `match`。
|
||||
///
|
||||
/// 名字故意起得又长又扎眼(`Super…FromStringTyped`):这是**待清的债**,不是好名字。
|
||||
/// 别把它改漂亮——要么按文件顶部 TODO 让上游产出 typed 变体后整张表删掉,要么什么都别做。
|
||||
///
|
||||
/// 这里的判据仍然是文本,因为产生层给出来的就只有文本(平台美术/凭据、项目历史、执行预算)。
|
||||
/// 规则:**新分类必须先在产生层加 typed 变体**,别往这份表里加词;每条都注明了应由谁给出 typed 事实。
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum DomainFact {
|
||||
enum SuperErrorEnumFromStringTyped {
|
||||
/// 泥点余额不足:平台付费接口(`direct_paid_submission` / 美术生成 / 上游 409)。
|
||||
PaidCreditsInsufficient,
|
||||
/// 本机私有凭据目录没准备好(`assets` 的私有凭据存储)。
|
||||
@@ -827,7 +836,7 @@ enum DomainFact {
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
impl DomainFact {
|
||||
impl SuperErrorEnumFromStringTyped {
|
||||
fn classify(detail: &str) -> Option<Self> {
|
||||
let normalized = detail.to_ascii_lowercase();
|
||||
let contains = |marker: &str| {
|
||||
@@ -838,9 +847,9 @@ impl DomainFact {
|
||||
}
|
||||
};
|
||||
// 顺序即优先级:更具体的事实先判,笼统的放后面。
|
||||
const CANDIDATES: &[(DomainFact, &[&str])] = &[
|
||||
const CANDIDATES: &[(SuperErrorEnumFromStringTyped, &[&str])] = &[
|
||||
(
|
||||
DomainFact::PaidCreditsInsufficient,
|
||||
SuperErrorEnumFromStringTyped::PaidCreditsInsufficient,
|
||||
&[
|
||||
"泥点余额不足",
|
||||
"可消费泥点不足",
|
||||
@@ -850,31 +859,31 @@ impl DomainFact {
|
||||
],
|
||||
),
|
||||
(
|
||||
DomainFact::CredentialStorageUnprepared,
|
||||
SuperErrorEnumFromStringTyped::CredentialStorageUnprepared,
|
||||
&["private-external-editor-credential-storage-preparation-failed"],
|
||||
),
|
||||
(
|
||||
DomainFact::CredentialNotPersisted,
|
||||
SuperErrorEnumFromStringTyped::CredentialNotPersisted,
|
||||
&["private-external-editor-credential-persistence-failed"],
|
||||
),
|
||||
(
|
||||
DomainFact::LocalDeveloperKeyMissing,
|
||||
SuperErrorEnumFromStringTyped::LocalDeveloperKeyMissing,
|
||||
&["本机陶泥儿开发者 Key"],
|
||||
),
|
||||
(
|
||||
DomainFact::AuthenticationRejected,
|
||||
SuperErrorEnumFromStringTyped::AuthenticationRejected,
|
||||
&["authentication-required", "unauthorized", "http 401"],
|
||||
),
|
||||
(
|
||||
DomainFact::PermissionDenied,
|
||||
SuperErrorEnumFromStringTyped::PermissionDenied,
|
||||
&["permission-denied", "http 403"],
|
||||
),
|
||||
(
|
||||
DomainFact::HistoryInjectionOversize,
|
||||
SuperErrorEnumFromStringTyped::HistoryInjectionOversize,
|
||||
&["历史注入载荷超过单行上限"],
|
||||
),
|
||||
(
|
||||
DomainFact::HistoryShapeUnsupported,
|
||||
SuperErrorEnumFromStringTyped::HistoryShapeUnsupported,
|
||||
&[
|
||||
"DirectProject 历史记录类型无效",
|
||||
"DirectProject 历史记录缺少 payload",
|
||||
@@ -882,15 +891,15 @@ impl DomainFact {
|
||||
],
|
||||
),
|
||||
(
|
||||
DomainFact::ProjectWriteLockContention,
|
||||
SuperErrorEnumFromStringTyped::ProjectWriteLockContention,
|
||||
&[crate::project::PROJECT_WRITE_LOCK_CONTENTION_PREFIX],
|
||||
),
|
||||
(
|
||||
DomainFact::HistoryContention,
|
||||
SuperErrorEnumFromStringTyped::HistoryContention,
|
||||
&[crate::project::PROJECT_APPEND_LOCK_TIMEOUT_MARKER],
|
||||
),
|
||||
(
|
||||
DomainFact::ArtIdentityRejected,
|
||||
SuperErrorEnumFromStringTyped::ArtIdentityRejected,
|
||||
&[
|
||||
"身份不唯一",
|
||||
"身份不匹配",
|
||||
@@ -898,22 +907,31 @@ impl DomainFact {
|
||||
"没有可见像素",
|
||||
],
|
||||
),
|
||||
(DomainFact::ContractChanged, &["合同发生变化"]),
|
||||
(
|
||||
DomainFact::ValidationBudgetExhausted,
|
||||
SuperErrorEnumFromStringTyped::ContractChanged,
|
||||
&["合同发生变化"],
|
||||
),
|
||||
(
|
||||
SuperErrorEnumFromStringTyped::ValidationBudgetExhausted,
|
||||
&["validation-budget-exhausted"],
|
||||
),
|
||||
(
|
||||
DomainFact::ValidationAlreadyRunning,
|
||||
SuperErrorEnumFromStringTyped::ValidationAlreadyRunning,
|
||||
&["validation-already-running"],
|
||||
),
|
||||
(
|
||||
DomainFact::PlaytestAttemptLimitExceeded,
|
||||
SuperErrorEnumFromStringTyped::PlaytestAttemptLimitExceeded,
|
||||
&["playtest-attempt-limit-exceeded"],
|
||||
),
|
||||
(DomainFact::ToolArgumentsInvalid, &["工具参数"]),
|
||||
(DomainFact::Cancelled, &["取消"]),
|
||||
(DomainFact::CredentialsUnavailable, &["credential", "凭据"]),
|
||||
(
|
||||
SuperErrorEnumFromStringTyped::ToolArgumentsInvalid,
|
||||
&["工具参数"],
|
||||
),
|
||||
(SuperErrorEnumFromStringTyped::Cancelled, &["取消"]),
|
||||
(
|
||||
SuperErrorEnumFromStringTyped::CredentialsUnavailable,
|
||||
&["credential", "凭据"],
|
||||
),
|
||||
];
|
||||
CANDIDATES
|
||||
.iter()
|
||||
@@ -1018,7 +1036,7 @@ impl DomainFact {
|
||||
|
||||
/// 阶段兜底的恢复建议:typed 分类给不出动作时,由阶段给一句与交付状态对得上的话。
|
||||
fn stage_recovery_hint(stage: FailureStage, detail: &str) -> Option<&'static str> {
|
||||
if let Some(fact) = DomainFact::classify(detail) {
|
||||
if let Some(fact) = SuperErrorEnumFromStringTyped::classify(detail) {
|
||||
return Some(fact.recovery_hint());
|
||||
}
|
||||
Some(match stage {
|
||||
|
||||
Reference in New Issue
Block a user