修复循环模块回接与内联源码限额
循环投影声明去重后继续把 import 引用回接到既有 canonical,并补足稳定确认轮次。 内联模块先按原文累计 2 MiB 源码预算,再执行语法和语义失败关闭。 新增 self-cycle、无效超限与无效小型内联模块回归,并同步技术方案与共享决策。
This commit is contained in:
+27
-15
@@ -4152,9 +4152,7 @@ fn executable_inline_module_javascript_units_from_html(content: &str) -> Vec<Str
|
||||
&& !html_has_attribute(tag, "src")
|
||||
{
|
||||
let body = &content[body_start..close_start];
|
||||
if javascript_is_syntactically_valid(body, true) {
|
||||
executable.push(body.to_string());
|
||||
}
|
||||
executable.push(body.to_string());
|
||||
}
|
||||
cursor = close_end;
|
||||
}
|
||||
@@ -7671,7 +7669,7 @@ pub(in crate::agent) fn read_external_gameplay_javascript_at(
|
||||
let mut projected_units = BTreeMap::<String, String>::new();
|
||||
let mut projection_work_bytes = 0usize;
|
||||
let mut projection_converged = false;
|
||||
for _ in 0..module_bindings.len().max(1) {
|
||||
for _ in 0..module_bindings.len().saturating_add(1).max(1) {
|
||||
let mut round_updates = BTreeMap::<String, String>::new();
|
||||
let mut round_identity_updates =
|
||||
BTreeMap::<String, BTreeMap<String, JavascriptProjectionDeclarationIdentity>>::new();
|
||||
@@ -7835,6 +7833,7 @@ pub(in crate::agent) fn read_external_gameplay_javascript_at(
|
||||
};
|
||||
selected_projection_identities.insert(binding.clone(), identity.clone());
|
||||
}
|
||||
let projection_identities_before_dedup = selected_projection_identities.clone();
|
||||
if !javascript_remove_duplicate_projection_declarations(
|
||||
&mut projection,
|
||||
&mut selected_projection_identities,
|
||||
@@ -7844,9 +7843,15 @@ pub(in crate::agent) fn read_external_gameplay_javascript_at(
|
||||
"自主构建循环模块投影声明范围无效:{importer} <- {origin}"
|
||||
));
|
||||
}
|
||||
if projection.trim().is_empty() {
|
||||
continue;
|
||||
}
|
||||
let existing_projected_binding_names = projection_identities_before_dedup
|
||||
.iter()
|
||||
.filter(|(binding, _)| !selected_projection_identities.contains_key(*binding))
|
||||
.filter_map(|(binding, identity)| {
|
||||
included_projection_names
|
||||
.get(identity)
|
||||
.map(|canonical| (binding.clone(), canonical.clone()))
|
||||
})
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
projection_work_bytes = projection_work_bytes
|
||||
.checked_add(projection.len())
|
||||
.filter(|bytes| {
|
||||
@@ -7895,7 +7900,8 @@ pub(in crate::agent) fn read_external_gameplay_javascript_at(
|
||||
.difference(&projection_analysis.root_bindings)
|
||||
.cloned(),
|
||||
);
|
||||
let mut projected_binding_names = BTreeMap::<String, String>::new();
|
||||
let mut projected_binding_names = existing_projected_binding_names;
|
||||
let mut newly_projected_binding_names = BTreeMap::<String, String>::new();
|
||||
for original in &projection_analysis.root_bindings {
|
||||
remaining_projection.remove(original);
|
||||
let preferred = preferred_synthetic_alias
|
||||
@@ -7909,18 +7915,18 @@ pub(in crate::agent) fn read_external_gameplay_javascript_at(
|
||||
&remaining_projection,
|
||||
);
|
||||
assigned_names.insert(canonical.clone());
|
||||
projected_binding_names.insert(original.clone(), canonical);
|
||||
newly_projected_binding_names.insert(original.clone(), canonical);
|
||||
}
|
||||
for (original, canonical) in &projected_binding_names {
|
||||
for (original, canonical) in &newly_projected_binding_names {
|
||||
if original != canonical
|
||||
&& !rename_javascript_root_binding(&mut projection, original, canonical)
|
||||
{
|
||||
return Err(format!(
|
||||
"自主构建模块投影绑定无法按符号重命名:{origin}::{original} -> {canonical}"
|
||||
));
|
||||
));
|
||||
}
|
||||
}
|
||||
for (original, canonical) in &projected_binding_names {
|
||||
for (original, canonical) in &newly_projected_binding_names {
|
||||
let Some(identity) = selected_projection_identities.get(original) else {
|
||||
return Err(format!(
|
||||
"自主构建模块投影重命名后缺少声明身份:{origin}::{original}"
|
||||
@@ -7929,6 +7935,7 @@ pub(in crate::agent) fn read_external_gameplay_javascript_at(
|
||||
included_projection_names.insert(identity.clone(), canonical.clone());
|
||||
unit_projection_identities.insert(canonical.clone(), identity.clone());
|
||||
}
|
||||
projected_binding_names.extend(newly_projected_binding_names);
|
||||
if javascript_module_analysis(&projection, true).is_none() {
|
||||
return Err(format!("自主构建模块投影重命名后语义无效:{origin}"));
|
||||
}
|
||||
@@ -8030,9 +8037,14 @@ pub(in crate::agent) fn read_external_gameplay_javascript_at(
|
||||
}
|
||||
}
|
||||
}
|
||||
unit.push('\n');
|
||||
unit.push_str(&projection);
|
||||
added_projection = true;
|
||||
if !projection.trim().is_empty() {
|
||||
unit.push('\n');
|
||||
unit.push_str(&projection);
|
||||
added_projection = true;
|
||||
}
|
||||
if !unit_replacements.is_empty() {
|
||||
added_projection = true;
|
||||
}
|
||||
}
|
||||
if added_projection {
|
||||
if !apply_javascript_span_replacements(&mut unit, &mut unit_replacements) {
|
||||
|
||||
+42
@@ -3806,6 +3806,29 @@ fn javascript_projection_preserves_nested_names_shorthand_keys_and_cycles() {
|
||||
assert!(!projected.contains("__agc_import_2"));
|
||||
assert!(projected.contains("return a();"));
|
||||
assert!(javascript_is_syntactically_valid(projected, true));
|
||||
|
||||
fs::write(
|
||||
root.join("game/self.mjs"),
|
||||
"import { start as again } from './self.mjs'; export function start() { return 1; } again();",
|
||||
)
|
||||
.expect("write self-cyclic module");
|
||||
let self_cycle_html = "<script type=\"module\" src=\"./self.mjs\"></script><canvas></canvas>";
|
||||
let self_cycle = read_external_gameplay_javascript_at(root, self_cycle_html)
|
||||
.expect("a self-cycle must reconnect its import to the existing declaration");
|
||||
let projected = self_cycle
|
||||
.module_units()
|
||||
.iter()
|
||||
.find(|unit| unit.contains("function start()") && !unit.contains("import {"))
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"self-cycle must emit an import-free projected unit: {:#?}",
|
||||
self_cycle.module_units()
|
||||
)
|
||||
});
|
||||
assert_eq!(projected.matches("function start()").count(), 1);
|
||||
assert!(projected.contains("start();"));
|
||||
assert!(!projected.contains("again();"));
|
||||
assert!(javascript_is_syntactically_valid(projected, true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -3820,6 +3843,25 @@ fn javascript_inline_modules_share_the_external_script_byte_limit() {
|
||||
error.contains("累计超过 2 MiB"),
|
||||
"unexpected error: {error}"
|
||||
);
|
||||
|
||||
let invalid_oversized = format!(
|
||||
"<script type=\"module\">{}const =</script>",
|
||||
" ".repeat(2 * 1024 * 1024)
|
||||
);
|
||||
let error = read_external_gameplay_javascript_at(temporary.path(), &invalid_oversized)
|
||||
.expect_err("an invalid oversized inline module must fail at the byte boundary");
|
||||
assert!(
|
||||
error.contains("累计超过 2 MiB"),
|
||||
"oversized invalid modules must not be filtered before accounting: {error}"
|
||||
);
|
||||
|
||||
let invalid = "<script type=\"module\">const =</script>";
|
||||
let error = read_external_gameplay_javascript_at(temporary.path(), invalid)
|
||||
.expect_err("an invalid inline module must fail closed");
|
||||
assert!(
|
||||
error.contains("内联模块不是有效 JavaScript:inline-module:0"),
|
||||
"unexpected invalid inline module error: {error}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -5988,5 +5988,5 @@
|
||||
- JavaScript / ESM 构造、继承与 callable 补充:`new` 沿冻结的 class owner 图执行本类显式 constructor、显式 `super()` 或隐式 derived constructor,并支持直接 class expression;instance / static 成员未 override 时继续沿 `extends` 链查找。普通嵌套 function 不继承 class `this`,arrow 保持词法 owner。`let binding; binding = function/arrow`、本地 function alias 和 Function.prototype `.bind()` 结果都建立 callable identity;`.call/.apply/.bind` 只有在 receiver 可解析为 callable 时采用 Function.prototype 语义,业务对象同名方法仍作为普通 receiver method 执行。
|
||||
- 浏览器因果:状态证据仍只冻结 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 循环与体积补充:投影声明按 `(origin module, original root binding)` 保存身份,canonical 重命名不能改写原始身份;删除回流声明后仍把 import 引用改接到 importer 已有 canonical,并给固定点保留“模块数 + 1”轮的产出与稳定确认预算,未收敛时失败关闭。inline module 先按浏览器可执行标签提取原文并计入与外部脚本共享的累计 `2 MiB` 源码预算,再做语法和语义校验;无效超限模块不能被 helper 静默过滤,无效小模块也明确失败关闭。
|
||||
- JavaScript / ESM alias 求值顺序补充:receiver alias 保存赋值完成时刻并在该时刻解析 source owner,后续 source 重赋值不得倒灌。调用事件按内层参数 / RHS 先于外层调用 / assignment 生效;`switch case/default` 赋值一律保留跳过与各分支可能状态,普通函数内无条件 `return / throw` 截断之后的 alias 副作用。恒真 / 恒假关键字大小写敏感,可能被局部或参数遮蔽的 `undefined` 不再作为文本恒假值。
|
||||
|
||||
@@ -839,7 +839,7 @@ game-project/
|
||||
- 泥点不足是确定性业务中断,不是瞬态 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 重放,禁止分别执行原响应和持久响应。
|
||||
|
||||
- 循环 ESM 的投影身份固定为 `(origin module, original root binding)`;canonical 重命名只改变组合单元中的展示名,身份随投影闭包传播。删除已存在的回流声明前,必须先把其引用改接到 importer 中同一身份的既有 canonical;固定点未在模块数限定轮次内收敛时失败关闭,不能返回最后一轮仍变化的部分结果。inline module 与外部脚本共同计入累计源码 `2 MiB` 上限,不能绕过文件读取预算。
|
||||
- 循环 ESM 的投影身份固定为 `(origin module, original root binding)`;canonical 重命名只改变组合单元中的展示名,身份随投影闭包传播。删除已存在的回流声明后仍必须把 import 引用改接到 importer 中同一身份的既有 canonical;固定点未在“模块数 + 1”轮内收敛时失败关闭,不能返回最后一轮仍变化的部分结果。inline module 必须先提取原文并计入与外部脚本共享的累计源码 `2 MiB` 上限,再执行语法和语义校验;超限源码不能因无效语法被过滤,小型无效模块也必须明确失败关闭。
|
||||
|
||||
## 2026-07-31 长耗时与恢复收口
|
||||
|
||||
|
||||
Reference in New Issue
Block a user