diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion.rs index b5a287b8a..b338f36b7 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion.rs @@ -911,8 +911,7 @@ fn javascript_condition_is_obviously_false(condition: &str) -> bool { let compact = condition .chars() .filter(|character| !character.is_ascii_whitespace()) - .collect::() - .to_ascii_lowercase(); + .collect::(); matches!( compact.as_str(), "false" @@ -920,7 +919,6 @@ fn javascript_condition_is_obviously_false(condition: &str) -> bool { | "!true" | "!!0" | "null" - | "undefined" | "1===0" | "0===1" | "1==0" @@ -936,8 +934,7 @@ fn javascript_condition_is_obviously_true(condition: &str) -> bool { let compact = condition .chars() .filter(|character| !character.is_ascii_whitespace()) - .collect::() - .to_ascii_lowercase(); + .collect::(); matches!( compact.as_str(), "true" | "1" | "!false" | "!!1" | "1===1" | "1==1" | "true===true" | "true==true" @@ -1312,6 +1309,7 @@ enum JavascriptReceiverOwnerValue { Alias { source: JavascriptSymbolId, force_instance: bool, + frozen_at: usize, }, } @@ -1353,6 +1351,7 @@ impl JavascriptMethodReceiverOwnerCollector<'_> { .map(|source| JavascriptReceiverOwnerValue::Alias { source, force_instance: true, + frozen_at: position, }) .into_iter() .collect(), @@ -1361,12 +1360,13 @@ impl JavascriptMethodReceiverOwnerCollector<'_> { .map(|source| JavascriptReceiverOwnerValue::Alias { source, force_instance: false, + frozen_at: position, }) .into_iter() .collect(), JavascriptExpression::ConditionalExpression(expression) => { - let mut values = self.receiver_values(&expression.consequent); - values.extend(self.receiver_values(&expression.alternate)); + let mut values = self.receiver_values(&expression.consequent, position); + values.extend(self.receiver_values(&expression.alternate, position)); values } _ => Vec::new(), @@ -1401,6 +1401,7 @@ impl JavascriptMethodReceiverOwnerCollector<'_> { fn receiver_values( &self, initializer: &JavascriptExpression<'_>, + position: usize, ) -> Vec { match initializer { JavascriptExpression::ObjectExpression(object) => { @@ -1422,6 +1423,7 @@ impl JavascriptMethodReceiverOwnerCollector<'_> { .map(|source| JavascriptReceiverOwnerValue::Alias { source, force_instance: true, + frozen_at: position, }) .into_iter() .collect(), @@ -1430,12 +1432,13 @@ impl JavascriptMethodReceiverOwnerCollector<'_> { .map(|source| JavascriptReceiverOwnerValue::Alias { source, force_instance: false, + frozen_at: position, }) .into_iter() .collect(), JavascriptExpression::ConditionalExpression(expression) => { - let mut values = self.receiver_values(&expression.consequent); - values.extend(self.receiver_values(&expression.alternate)); + let mut values = self.receiver_values(&expression.consequent, position); + values.extend(self.receiver_values(&expression.alternate, position)); values } _ => Vec::new(), @@ -1491,7 +1494,7 @@ impl<'a> VisitJavascript<'a> for JavascriptMethodReceiverOwnerCollector<'_> { self.record_binding_initializer( symbol_id, initializer, - declarator.span.start as usize, + declarator.span.end as usize, ); } } @@ -1511,7 +1514,7 @@ impl<'a> VisitJavascript<'a> for JavascriptMethodReceiverOwnerCollector<'_> { self.record_binding_initializer( symbol_id, &assignment.right, - assignment.span.start as usize, + assignment.span.end as usize, ); } } @@ -1659,6 +1662,12 @@ fn javascript_known_callback_argument_indices( NONE } +fn javascript_invocation_effect_position(span: oxc_span::Span) -> usize { + // A nested call's closing delimiter precedes its parent's closing delimiter. Using that + // source position models argument/RHS evaluation before the enclosing call or assignment. + (span.end as usize).saturating_sub(1) +} + impl JavascriptFunctionInvocationCollector<'_, '_> { fn record_identifier(&mut self, identifier: &oxc_ast::ast::IdentifierReference<'_>, at: usize) { let Some(reference_id) = identifier.reference_id.get() else { @@ -1719,15 +1728,14 @@ impl JavascriptFunctionInvocationCollector<'_, '_> { JavascriptReceiverOwnerValue::Alias { source, force_instance, - } => { - owners.extend( - self.receiver_owners_for_symbol(*source, at, visiting) - .into_iter() - .map(|(start, end, is_static)| { - (start, end, if *force_instance { false } else { is_static }) - }), - ); - } + frozen_at, + } => owners.extend( + self.receiver_owners_for_symbol(*source, *frozen_at, visiting) + .into_iter() + .map(|(start, end, is_static)| { + (start, end, if *force_instance { false } else { is_static }) + }), + ), } } } @@ -1807,11 +1815,18 @@ impl JavascriptFunctionInvocationCollector<'_, '_> { indices } - fn record_expression(&mut self, expression: &JavascriptExpression<'_>, at: usize) { + fn record_expression( + &mut self, + expression: &JavascriptExpression<'_>, + resolve_at: usize, + invoke_at: usize, + ) { match expression { - JavascriptExpression::Identifier(identifier) => self.record_identifier(identifier, at), + JavascriptExpression::Identifier(identifier) => { + self.record_identifier(identifier, invoke_at) + } JavascriptExpression::ParenthesizedExpression(parenthesized) => { - self.record_expression(&parenthesized.expression, at); + self.record_expression(&parenthesized.expression, resolve_at, invoke_at); } JavascriptExpression::FunctionExpression(_) | JavascriptExpression::ArrowFunctionExpression(_) => { @@ -1821,14 +1836,14 @@ impl JavascriptFunctionInvocationCollector<'_, '_> { .get(&(span.start as usize, span.end as usize)) { for index in indices { - self.ranges[*index].invocations.push(at); + self.ranges[*index].invocations.push(invoke_at); } } } _ => { if let Some(member) = expression.as_member_expression() { - for index in self.method_indices_for_member(member, at) { - self.ranges[index].invocations.push(at); + for index in self.method_indices_for_member(member, resolve_at) { + self.ranges[index].invocations.push(invoke_at); } } } @@ -1866,15 +1881,16 @@ impl<'a> VisitJavascript<'a> for JavascriptFunctionInvocationCollector<'a, '_> { } fn visit_call_expression(&mut self, call: &JavascriptCallExpression<'a>) { - let at = call.span.start as usize; + let resolve_at = call.span.start as usize; + let invoke_at = javascript_invocation_effect_position(call.span); if let Some(member) = call.callee.as_member_expression() { if matches!(member.static_property_name(), Some("call" | "apply")) { - self.record_expression(member.object(), at); + self.record_expression(member.object(), resolve_at, invoke_at); } else { - self.record_expression(&call.callee, at); + self.record_expression(&call.callee, resolve_at, invoke_at); } } else { - self.record_expression(&call.callee, at); + self.record_expression(&call.callee, resolve_at, invoke_at); } for index in self.callback_argument_indices(call) { if let Some(expression) = call @@ -1882,16 +1898,17 @@ impl<'a> VisitJavascript<'a> for JavascriptFunctionInvocationCollector<'a, '_> { .get(*index) .and_then(|argument| argument.as_expression()) { - self.record_expression(expression, at); + self.record_expression(expression, resolve_at, invoke_at); } } oxc_ast_visit::walk::walk_call_expression(self, call); } fn visit_new_expression(&mut self, expression: &oxc_ast::ast::NewExpression<'a>) { - let at = expression.span.start as usize; + let resolve_at = expression.span.start as usize; + let invoke_at = javascript_invocation_effect_position(expression.span); let mut indices = self - .receiver_owner(&expression.callee, at) + .receiver_owner(&expression.callee, resolve_at) .into_iter() .flat_map(|(start, end, _)| { self.method_ranges @@ -1904,7 +1921,7 @@ impl<'a> VisitJavascript<'a> for JavascriptFunctionInvocationCollector<'a, '_> { indices.sort_unstable(); indices.dedup(); for index in indices { - self.ranges[index].invocations.push(at); + self.ranges[index].invocations.push(invoke_at); } oxc_ast_visit::walk::walk_new_expression(self, expression); } @@ -4604,6 +4621,15 @@ impl<'a> VisitJavascript<'a> for JavascriptConditionalExecutionCollector<'_> { self.push_span(statement.body.span()); oxc_ast_visit::walk::walk_while_statement(self, statement); } + + fn visit_switch_statement(&mut self, statement: &oxc_ast::ast::SwitchStatement<'a>) { + for case in &statement.cases { + for consequent in &case.consequent { + self.push_span(consequent.span()); + } + } + oxc_ast_visit::walk::walk_switch_statement(self, statement); + } } fn javascript_conditional_execution_ranges(content: &str) -> Vec> { @@ -4679,6 +4705,13 @@ fn javascript_alias_scope_effect( until: usize, visiting: &mut BTreeSet<(usize, usize)>, ) -> JavascriptAliasSelection { + let until = scope + .and_then(|(start, end)| { + javascript_top_level_unconditional_termination_from_ast(&content[start..end]) + .flatten() + .map(|termination| start + termination) + }) + .map_or(until, |termination| until.min(termination)); let mut transitions = BTreeMap::>::new(); for (index, event) in events.iter().enumerate().filter(|(_, event)| { event.scope == scope @@ -5050,7 +5083,7 @@ impl<'a> VisitJavascript<'a> for JavascriptImportMemberCallCollector<'_> { } } if let Some(initializer) = &declarator.init { - let position = declarator.span.start as usize; + let position = declarator.span.end as usize; for (local, path) in self.resolve_expression(initializer, position) { match &declarator.id { JavascriptBindingPattern::BindingIdentifier(identifier) => { @@ -5131,7 +5164,7 @@ impl<'a> VisitJavascript<'a> for JavascriptImportMemberCallCollector<'_> { if let oxc_ast::ast::AssignmentTarget::AssignmentTargetIdentifier(identifier) = &assignment.left { - let position = assignment.span.start as usize; + let position = assignment.span.end as usize; if let Some(symbol_id) = identifier .reference_id .get() @@ -5337,10 +5370,10 @@ impl<'a> VisitJavascript<'a> for JavascriptDynamicImportUsageCollector<'_> { symbol_id, source, import_position, - assignment.span.start as usize, + assignment.span.end as usize, ); } else { - let position = assignment.span.start as usize; + let position = assignment.span.end as usize; self.namespace_sources.entry(symbol_id).or_default().push( JavascriptAliasEvent { position, @@ -9711,6 +9744,19 @@ mod javascript_projection_reachability_tests { ); } + #[test] + fn receiver_alias_freezes_its_source_owner_at_assignment_time() { + let content = "const first = { run() { return import('./first.mjs'); } }; const second = { run() { return import('./second.mjs'); } }; let source = first; const receiver = source; source = second; receiver.run();"; + let ranges = named_javascript_function_ranges(content); + let first = content.find("./first.mjs").expect("find original owner"); + let second = content + .find("./second.mjs") + .expect("find later source owner"); + + assert!(javascript_position_is_reachable(content, &ranges, first)); + assert!(!javascript_position_is_reachable(content, &ranges, second)); + } + #[test] fn local_constructor_body_is_reachable_from_new_expression() { let content = "class Helper { constructor() { return import('./constructor.mjs'); } } function start() { return new Helper(); } start();"; diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion_contract_tests.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion_contract_tests.rs index 6b6471e1a..1d8b06e51 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion_contract_tests.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion_contract_tests.rs @@ -3885,6 +3885,75 @@ fn javascript_alias_events_follow_function_invocation_time() { ); } +#[test] +fn javascript_switch_assignments_preserve_all_possible_aliases() { + let source = "import * as initial from './initial.mjs'; import * as first from './first.mjs'; import * as fallback from './fallback.mjs'; let facade = initial; switch (mode) { case 1: facade = first; break; default: facade = fallback; } facade.run();"; + let analysis = super::autonomous_completion::javascript_module_analysis(source, true) + .expect("analyze switch alias branches"); + for binding in ["initial", "first", "fallback"] { + assert!( + analysis.import_member_calls.contains_key(binding), + "switch must retain possible receiver {binding}: {:#?}", + analysis.import_member_calls + ); + } +} + +#[test] +fn javascript_function_alias_effect_stops_after_unconditional_exit() { + for exit in ["return", "throw failure"] { + let source = format!( + "import * as initial from './initial.mjs'; import * as selected from './selected.mjs'; import * as unreachable from './unreachable.mjs'; let facade = initial; function mutate() {{ facade = selected; {exit}; facade = unreachable; }} mutate(); facade.run();" + ); + let analysis = super::autonomous_completion::javascript_module_analysis(&source, true) + .expect("analyze alias effect before unconditional exit"); + assert!(analysis.import_member_calls.contains_key("selected")); + assert!( + !analysis.import_member_calls.contains_key("unreachable"), + "alias effect after {exit} must be unreachable" + ); + } +} + +#[test] +fn javascript_nested_call_alias_effects_follow_javascript_evaluation_order() { + let nested = "import * as initial from './initial.mjs'; import * as innerValue from './inner.mjs'; import * as outerValue from './outer.mjs'; let facade = initial; function inner() { facade = innerValue; } function outer() { facade = outerValue; } outer(inner()); facade.run();"; + let analysis = super::autonomous_completion::javascript_module_analysis(nested, true) + .expect("analyze nested call evaluation order"); + assert!(analysis.import_member_calls.contains_key("outerValue")); + assert!(!analysis.import_member_calls.contains_key("innerValue")); + + let assignment = "import * as initial from './initial.mjs'; import * as transient from './transient.mjs'; let receiver = initial; function mutate() { receiver = transient; return null; } receiver = mutate(); receiver.run();"; + let analysis = super::autonomous_completion::javascript_module_analysis(assignment, true) + .expect("analyze assignment RHS evaluation order"); + assert!( + !analysis.import_member_calls.contains_key("transient"), + "the assignment result must override its RHS call's alias side effect" + ); +} + +#[test] +fn javascript_constant_conditions_are_case_and_scope_sensitive() { + for condition in ["FALSE", "TRUE", "undefined"] { + let declaration = if condition == "undefined" { + "export function start(undefined) {" + } else { + "export function start() {" + }; + let source = format!( + "import * as initial from './initial.mjs'; import * as selected from './selected.mjs'; {declaration} let facade = initial; if ({condition}) facade = selected; facade.run(); }}" + ); + let analysis = super::autonomous_completion::javascript_module_analysis(&source, true) + .expect("analyze case- and scope-sensitive condition"); + for binding in ["initial", "selected"] { + assert!( + analysis.import_member_calls.contains_key(binding), + "{condition} must remain an unknown condition and preserve {binding}" + ); + } + } +} + #[test] fn game_chat_pure_continue_does_not_inherit_across_sessions() { let (_temporary, root, original_state, original_contract) = diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 593904dfb..d9cd8dfc2 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -5987,3 +5987,4 @@ - 浏览器因果:状态证据仍只冻结 trusted input listener 及其点击派生微任务内的变化;完整手势身份改由宿主在成功完成 Chromium 元素鼠标输入后调用隔离世界 finish。更早注册的 `window` capture listener 即使调用 `stopImmediatePropagation()` 也不能阻断探针自身的完成身份,页面脚本不能伪造 host finish,RAF / timer 继续不计入动作结果。 - 验证边界:Linux 定向回归覆盖目录相对读写与清理、祖先 symlink、CAS 安装后错误、九文件混合快照、Tetris AST 反例和七项真实 Chrome generic 试玩。Windows cfg 代码必须继续在真实 Windows CI / 发布构建验证;本地缺少 MinGW C compiler 时,安装了 Rust target 也不能把交叉 `cargo check` 失败误报为源码失败。 - JavaScript / ESM 循环与体积补充:投影声明按 `(origin module, original root binding)` 保存身份,canonical 重命名不能改写原始身份;删除回流声明前先把引用改接到 importer 已有 canonical,有界轮次未收敛时失败关闭。inline module 与外部脚本共同占用累计 `2 MiB` 源码预算。 +- JavaScript / ESM alias 求值顺序补充:receiver alias 保存赋值完成时刻并在该时刻解析 source owner,后续 source 重赋值不得倒灌。调用事件按内层参数 / RHS 先于外层调用 / assignment 生效;`switch case/default` 赋值一律保留跳过与各分支可能状态,普通函数内无条件 `return / throw` 截断之后的 alias 副作用。恒真 / 恒假关键字大小写敏感,可能被局部或参数遮蔽的 `undefined` 不再作为文本恒假值。 diff --git a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md index 9478b9ec8..8ea18022d 100644 --- a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md +++ b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md @@ -834,7 +834,7 @@ game-project/ - ESM 投影中的顶层 function/class/variable 声明必须直接使用 Oxc statement span 提取,不能用首个分号或换行截断箭头函数、多行 initializer 或多 declarator;对象、数组、默认值与 rest 解构声明必须递归收集全部 binding,并保证同一声明只投影一次。import symbol 与 importer 自有 root binding 分开保存,即使名称仅大小写不同也不得在组合前折叠;投影根分配 canonical 名时必须避让 importer 与 origin 的全部非 import semantic binding,不能被嵌套局部捕获。模块组合必须按依赖深度迭代到稳定闭包,把被导出函数继续依赖的 imported origin 带入最终 consumer 单元;循环 ESM 以原始声明 identity 去重回流,不能不断生成重命名副本,并以最终 span replacement 后的单 unit `2 MiB`、累计投影处理 `32 MiB` 为失败关闭上限。固定字符串 dynamic import 同样由下游实际使用的 export 反向驱动加载;未使用 export、未调用嵌套函数和恒假分支中的 dynamic source 不得进入模块单元,同名 dynamic export 不能反向选择未引用的本地声明。只有被选声明中的 `await import` 解构、namespace member 或 `.then(...)` 静态 binding 才能进入组合投影;callback 参数、解构 alias 和 namespace member 必须按 semantic symbol span 改接到投影根,dynamic object shorthand 改名时还必须显式保留原属性键,不能靠追加同名文本跨过局部遮蔽。span replacement 完成后还要对完整组合 unit 重跑 parser 与 semantic,启发式扫描只能在原始源码完成 AST 解析和掩码后再统一小写。匿名 default function / arrow 必须在原始 AST 中以 collision-safe synthetic binding 注册可外调 root span,使 wrapper 内 imported member 的传递依赖继续传播;synthetic binding 必须避开用户真实根名,改名只能更新 default target,不能污染同名命名导出。namespace 经过 renamed re-export 时,同时保留 importer 使用的 member 名和最终 origin export 名:前者定位 importer member span,后者选择 origin declaration,不能混用。named import、namespace import 和 namespace 解构 alias 的成员调用必须保留完整静态成员路径,并按调用 span 排除恒假分支后再把 demand 传播到上游;对象 / class 直接成员、对象解构 alias、实例 alias 与下游 wrapper 都使用同一条可达性链。对象 method shorthand、函数表达式值、箭头函数值以及 class function-valued field 必须按精确函数 span 注册成员根,只加载被实际调用成员中的 dynamic dependency,不能因声明写法遗漏,也不能把属性内未调用的嵌套函数升级为根。 - dynamic import demand 必须以 source 与 import occurrence position 联合作为身份,同 source 的可达裸 import 不得继承不可达 occurrence 的 export。动态 namespace 在首段 export 之后继续保留完整 member path,声明 initializer 与后续赋值式 `await import` 都必须绑定 semantic symbol。named / namespace 成员作为回调参数、对象解构 alias、实例 alias 与下游 wrapper 时继续按真实调用 span 传播 demand。constructor、`new Game().method()` 和实例 alias 分别形成精确成员根,`this.method()` 只匹配相同 class / object owner;顶层声明位置只使用 Oxc span,禁止以源码文本 `find` 反查。受控 callback API 名严格大小写敏感,调用图只读取真实 callback 参数位:timer、microtask、RAF、Promise 与数组迭代 API 取第一个参数,`addEventListener` 取第二个参数,delay、initial value、event type、options 和其它额外参数都不得建立执行边。恒假分支区间由单次函数可达性 analysis 预计算、排序合并并借用二分索引,不得使用 thread-local 完整源码 key 或在每个位置查询时复制区间、重扫整份源码。 - imported member 只有作为受控 callback API 的参数时才建立执行 demand;日志、注册元数据等普通传参不能被当作调用。普通 member alias 必须同时支持声明 initializer 与后续赋值,`new ns.Game()` 的完整 constructor path 继续传播到实例 alias。局部对象、class、实例、`this` 和 `super` 的方法调用统一按 semantic receiver owner 匹配,禁止按末级方法名跨 owner 选中同名 decoy。 -- member alias、局部 receiver 与动态 namespace 的 assignment 必须保留赋值位置和 enclosing function scope;函数体内的使用必须按真实 invocation position 选择当时已生效的事件,多次调用跨过赋值边界时合并全部可能 owner,恒假分支、未调用函数或调用之后的赋值不能倒灌覆盖更早使用点。未知条件或循环内赋值保留执行与跳过两种状态,conditional expression 保留全部可解析 owner,无法解析的确定重赋值显式 invalidation;已调用函数对外层 alias 的最终副作用按调用位置回写,不能被 scope 隔离丢失。被导出的 function、class/object member 还必须把模块初始化完成视为潜在外部调用时点,使声明之后生效的顶层赋值进入 demand,同时保留此前本地调用的旧状态。动态依赖传播继续以原始 owner 模块 AST span 为权威,不得因投影重排声明或省略独立赋值语句而重算出相反 alias。class owner 必须继续区分 static / instance,class expression 与实例化 alias 进入相同 receiver 图,`super` 的 parent owner 在 class 定义位置冻结。constructor、`.call()` 与 `.apply()` 都是实际 invocation;具名 function expression 只保留外层 binding 对应的单一 range,未被受控 API 执行的 inline function / arrow 保持不可达。恒假分支识别必须先用 parser trivia 屏蔽注释和非执行 literal。callback API 只接受 semantic 未解析的已知全局调度函数或 AST 可证明的 literal array / dynamic import 调用;用户定义、导入或遮蔽的同名 `setTimeout / map / then` 不能触发参数执行推断。 +- member alias、局部 receiver 与动态 namespace 的 assignment 必须保留赋值位置和 enclosing function scope;函数体内的使用必须按真实 invocation position 选择当时已生效的事件,多次调用跨过赋值边界时合并全部可能 owner,恒假分支、未调用函数或调用之后的赋值不能倒灌覆盖更早使用点。`receiver = source` 必须冻结赋值完成时的 source owner,source 后续重赋值不能反向改写 receiver;调用事件按 JavaScript 的 callee、参数 / RHS、内层调用、外层调用 / 赋值顺序生效,`outer(inner())` 与 `receiver = mutate()` 不得按 AST 先序颠倒副作用。未知条件、循环以及 `switch case/default` 内赋值保留执行与跳过状态,conditional expression 保留全部可解析 owner,无法解析的确定重赋值显式 invalidation;已调用函数对外层 alias 的最终副作用按调用位置回写,但普通函数体内无条件 `return / throw` 之后的事件必须截断。被导出的 function、class/object member 还必须把模块初始化完成视为潜在外部调用时点,使声明之后生效的顶层赋值进入 demand,同时保留此前本地调用的旧状态。动态依赖传播继续以原始 owner 模块 AST span 为权威,不得因投影重排声明或省略独立赋值语句而重算出相反 alias。class owner 必须继续区分 static / instance,class expression 与实例化 alias 进入相同 receiver 图,`super` 的 parent owner 在 class 定义位置冻结。constructor、`.call()` 与 `.apply()` 都是实际 invocation;具名 function expression 只保留外层 binding 对应的单一 range,未被受控 API 执行的 inline function / arrow 保持不可达。恒真 / 恒假关键字只按 JavaScript 大小写识别,局部或参数遮蔽的 `undefined` 不得当作恒假;恒假分支识别必须先用 parser trivia 屏蔽注释和非执行 literal。callback API 只接受 semantic 未解析的已知全局调度函数或 AST 可证明的 literal array / dynamic import 调用;用户定义、导入或遮蔽的同名 `setTimeout / map / then` 不能触发参数执行推断。 - 泥点不足是确定性业务中断,不是瞬态 Provider 故障或未知副作用。钱包的 `泥点余额不足` 与 `可消费泥点不足:...` 两种领域文案统一映射为稳定原因 `mud-points-insufficient`,不得自动重试;即使 External Generation durable ledger 已存在,也必须落为 `failed`,不能误入 `needs-reconciliation`。game-chat 顶部状态、持久失败对话与 `【Supervisor 阶段记录】` 统一显示“泥点余额不足,本轮游戏生成已中断。请充值后发送“继续”,系统会从当前项目进度接着完成。”,并禁止透传 operationId、URL、路径、密钥或任意上游正文。 - tool-plan 成功响应落账前,对内置 Runtime 原生函数与 legacy wrapper 的合法、无重复 key JSON arguments 按工具 schema 的精确位置做项目路径 canonicalization:`file.*.path`、`project.patchset.changes[*].path`、`project.git_commit.paths[*]`、`command.*.cwd`、`image.inspect.paths[*]` 与 `canvas.asset_generate.outputPath` 若是当前项目根目录内的完整绝对路径,转换为 `/` 分隔的项目相对路径后再校验、持久化并执行;源码/叙述字段、任务产物描述、动态 MCP arguments 和项目外绝对路径不得改写,后两者继续由绝对路径门禁失败关闭。项目根只允许搜索/列举范围与命令 cwd 规范化为 `.`,不能成为文件目标。当前进程与重启恢复都必须从同一份规范化 handoff 重放,禁止分别执行原响应和持久响应。