修复静态视觉证据与脚本语义
按 Script Record 边界组合 classic 脚本并保持 ASI 与 defer 顺序 以 AST 和 semantic binding 关联可见 Canvas、绘制调用与资源赋值时序 保持动态 namespace 只读写入语义并排除不可达投影证据 过滤 inert 与非 JavaScript 玩法诱饵并保持资产路径大小写 补充视觉回归、完成合同预期与长期技术文档
This commit is contained in:
+805
-243
File diff suppressed because it is too large
Load Diff
+19
-19
@@ -1389,7 +1389,7 @@ fn inherited_tetris_contract_scans_only_executable_html_scripts() {
|
||||
);
|
||||
assert_eq!(
|
||||
inherited_gameplay_semantics_gap(task, plain_text.as_bytes()).as_deref(),
|
||||
Some("board-state"),
|
||||
Some("tetris-identity"),
|
||||
"text/plain script content must not count as executable gameplay",
|
||||
);
|
||||
|
||||
@@ -1405,7 +1405,7 @@ fn inherited_tetris_contract_scans_only_executable_html_scripts() {
|
||||
template.insert_str(template_close + "</script>".len(), "</template>");
|
||||
assert_eq!(
|
||||
inherited_gameplay_semantics_gap(task, template.as_bytes()).as_deref(),
|
||||
Some("board-state"),
|
||||
Some("tetris-identity"),
|
||||
"template script content must not count as executable gameplay",
|
||||
);
|
||||
|
||||
@@ -1417,7 +1417,7 @@ fn inherited_tetris_contract_scans_only_executable_html_scripts() {
|
||||
comment.insert_str(comment_close + "</script>".len(), " -->");
|
||||
assert_eq!(
|
||||
inherited_gameplay_semantics_gap(task, comment.as_bytes()).as_deref(),
|
||||
Some("board-state"),
|
||||
Some("tetris-identity"),
|
||||
"HTML-commented script content must not count as executable gameplay",
|
||||
);
|
||||
|
||||
@@ -1429,7 +1429,7 @@ fn inherited_tetris_contract_scans_only_executable_html_scripts() {
|
||||
noscript.insert_str(noscript_close + "</script>".len(), "</noscript>");
|
||||
assert_eq!(
|
||||
inherited_gameplay_semantics_gap(task, noscript.as_bytes()).as_deref(),
|
||||
Some("board-state"),
|
||||
Some("tetris-identity"),
|
||||
"noscript content must not count as executable gameplay in a scripting browser",
|
||||
);
|
||||
|
||||
@@ -1456,7 +1456,7 @@ fn inherited_tetris_contract_scans_only_executable_html_scripts() {
|
||||
}
|
||||
assert_eq!(
|
||||
inherited_gameplay_semantics_gap(task, wrapped.as_bytes()).as_deref(),
|
||||
Some("board-state"),
|
||||
Some("tetris-identity"),
|
||||
"{container} content must not count as executable gameplay",
|
||||
);
|
||||
assert!(
|
||||
@@ -1490,7 +1490,7 @@ fn inherited_tetris_contract_scans_only_executable_html_scripts() {
|
||||
self_closing_textarea.insert_str(textarea_script_close + "</script>".len(), "</textarea>");
|
||||
assert_eq!(
|
||||
inherited_gameplay_semantics_gap(task, self_closing_textarea.as_bytes()).as_deref(),
|
||||
Some("board-state"),
|
||||
Some("tetris-identity"),
|
||||
"a slash does not make a non-void textarea self-closing in HTML",
|
||||
);
|
||||
|
||||
@@ -1506,21 +1506,21 @@ fn inherited_tetris_contract_scans_only_executable_html_scripts() {
|
||||
template_with_fake_close.insert_str(template_script_close + "</script>".len(), "</template>");
|
||||
assert_eq!(
|
||||
inherited_gameplay_semantics_gap(task, template_with_fake_close.as_bytes()).as_deref(),
|
||||
Some("board-state"),
|
||||
Some("tetris-identity"),
|
||||
"a template close marker inside script text must not expose later inert scripts",
|
||||
);
|
||||
|
||||
let sourced_inline_body = valid.replacen("<script>\nlet board", "<script src>\nlet board", 1);
|
||||
assert_eq!(
|
||||
inherited_gameplay_semantics_gap(task, sourced_inline_body.as_bytes()).as_deref(),
|
||||
Some("board-state"),
|
||||
Some("tetris-identity"),
|
||||
"the inline body of a sourced script is ignored by the browser",
|
||||
);
|
||||
|
||||
let nomodule = valid.replacen("<script>\nlet board", "<script nomodule>\nlet board", 1);
|
||||
assert_eq!(
|
||||
inherited_gameplay_semantics_gap(task, nomodule.as_bytes()).as_deref(),
|
||||
Some("board-state"),
|
||||
Some("tetris-identity"),
|
||||
"Chromium must not count nomodule classic script content",
|
||||
);
|
||||
|
||||
@@ -1531,7 +1531,7 @@ fn inherited_tetris_contract_scans_only_executable_html_scripts() {
|
||||
);
|
||||
assert_eq!(
|
||||
inherited_gameplay_semantics_gap(task, form_feed_src.as_bytes()).as_deref(),
|
||||
Some("board-state"),
|
||||
Some("tetris-identity"),
|
||||
"HTML form-feed must delimit the src attribute and suppress inline content",
|
||||
);
|
||||
|
||||
@@ -1542,7 +1542,7 @@ fn inherited_tetris_contract_scans_only_executable_html_scripts() {
|
||||
);
|
||||
assert_eq!(
|
||||
inherited_gameplay_semantics_gap(task, form_feed_type.as_bytes()).as_deref(),
|
||||
Some("board-state"),
|
||||
Some("tetris-identity"),
|
||||
"HTML form-feed must delimit a non-executable type attribute",
|
||||
);
|
||||
|
||||
@@ -1553,7 +1553,7 @@ fn inherited_tetris_contract_scans_only_executable_html_scripts() {
|
||||
);
|
||||
assert_eq!(
|
||||
inherited_gameplay_semantics_gap(task, non_javascript_language.as_bytes()).as_deref(),
|
||||
Some("board-state"),
|
||||
Some("tetris-identity"),
|
||||
"a non-JavaScript language attribute must not expose executable gameplay",
|
||||
);
|
||||
let javascript_language = valid.replacen(
|
||||
@@ -1587,14 +1587,14 @@ fn inherited_tetris_contract_scans_only_executable_html_scripts() {
|
||||
assert_eq!(
|
||||
inherited_gameplay_semantics_gap(task, explicit_plain_type_overrides_language.as_bytes())
|
||||
.as_deref(),
|
||||
Some("board-state"),
|
||||
Some("tetris-identity"),
|
||||
"an explicit non-JavaScript type stays inert even when language says JavaScript",
|
||||
);
|
||||
|
||||
let invalid_javascript = valid.replacen("stepDown();setInterval", ")stepDown();setInterval", 1);
|
||||
assert_eq!(
|
||||
inherited_gameplay_semantics_gap(task, invalid_javascript.as_bytes()).as_deref(),
|
||||
Some("board-state"),
|
||||
Some("tetris-identity"),
|
||||
"a syntactically invalid script must not contribute semantic bait",
|
||||
);
|
||||
|
||||
@@ -1612,7 +1612,7 @@ fn inherited_tetris_contract_scans_only_executable_html_scripts() {
|
||||
);
|
||||
assert_eq!(
|
||||
inherited_gameplay_semantics_gap(task, fake_script_close.as_bytes()).as_deref(),
|
||||
Some("board-state"),
|
||||
Some("tetris-identity"),
|
||||
"a non-boundary script close marker must not expose raw-text content as executable",
|
||||
);
|
||||
}
|
||||
@@ -2041,7 +2041,7 @@ import './gameplay/tetris.mjs';"#,
|
||||
&classic,
|
||||
)
|
||||
.as_deref(),
|
||||
Some("board-state"),
|
||||
Some("tetris-identity"),
|
||||
"a classic script must not gain semantics from a static-import decoy",
|
||||
);
|
||||
|
||||
@@ -2105,7 +2105,7 @@ import './gameplay/tetris.mjs';"#,
|
||||
&inline_classic,
|
||||
)
|
||||
.as_deref(),
|
||||
Some("board-state"),
|
||||
Some("tetris-identity"),
|
||||
"a classic inline script must not gain semantics from an invalid static import",
|
||||
);
|
||||
|
||||
@@ -2123,7 +2123,7 @@ import './gameplay/tetris.mjs';"#,
|
||||
&computed_dynamic_import,
|
||||
)
|
||||
.as_deref(),
|
||||
Some("board-state"),
|
||||
Some("tetris-identity"),
|
||||
"a literal prefix of a computed import must not load a static-analysis decoy",
|
||||
);
|
||||
|
||||
@@ -2303,7 +2303,7 @@ import './gameplay/tetris.mjs';"#,
|
||||
&nomodule_external,
|
||||
)
|
||||
.as_deref(),
|
||||
Some("board-state"),
|
||||
Some("tetris-identity"),
|
||||
"a nomodule external script must not contribute Tetris semantics",
|
||||
);
|
||||
|
||||
|
||||
@@ -6017,3 +6017,11 @@
|
||||
- exported live binding 的顶层 object / array destructuring assignment 纳入 projection;最终一次写入中与目标 root 对应的属性或槽位 callable 才作为 dynamic demand root,更早写入和相邻 decoy 不得回流。
|
||||
- 未遮蔽全局 top-level `await new Promise(executor)` 的 executor 若不调用或传递 resolve / reject、也不显式 throw,则返回值不参与 Promise settle,静态门按不完成失败关闭;resolver 按 semantic symbol 识别,Promise 遮蔽与嵌套函数内 await decoy 继续保留。
|
||||
- dynamic import `.then` callback 的对象解构只建立 occurrence-scoped binding,不再仅因读取 export 属性就形成 callable demand;只有该 binding 的可达引用或调用才向 export 内部传播动态依赖,未使用和恒假引用保持关闭。
|
||||
|
||||
## 2026-08-04 静态视觉证据绑定与只读 namespace
|
||||
|
||||
- classic HTML script 组合在每个 Script Record 之间保留硬语句边界,parser-blocking 与 defer 各自保持浏览器顺序,禁止因 ASI 把相邻标签拼成一个表达式。
|
||||
- Canvas 视觉门使用 Oxc AST 和 semantic symbol 关联可见 DOM Canvas、实际 context、精确 `drawImage` member callee、图片 binding 及绘制位置的 `.src` 状态。fakeDrawImage、离屏或隐藏首选 Canvas、恒假 / 未调用 / 绘制后覆盖均不作证;未知条件保持失败关闭。
|
||||
- dynamic import namespace member 写入保持真实只读失败语义,视觉投影不得把写 target 改造成可变本地;可达写入的 RHS 与后续语句不提供视觉证据,纯 namespace read 继续传播 export demand。
|
||||
- 身份与 telemetry 扫描只消费可渲染文本、可执行 inline JavaScript 和已链接外部 unit,排除 inert/raw-text、HTML 注释、带 `src` body 与非 JavaScript script。资产路径比较保持大小写,Linux 文件身份不得经 ASCII 小写副本合并。
|
||||
- 关联:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion.rs`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
|
||||
|
||||
@@ -847,6 +847,8 @@ game-project/
|
||||
|
||||
- 循环 ESM 的投影身份固定为 `(origin module, original root binding)`;canonical 重命名只改变组合单元中的展示名,身份随投影闭包传播。删除已存在的回流声明后仍必须把 import 引用改接到 importer 中同一身份的既有 canonical;固定点未在“模块数 + 1”轮内收敛时失败关闭,不能返回最后一轮仍变化的部分结果。inline module 必须先提取原文并计入与外部脚本共享的累计源码 `2 MiB` 上限,再执行语法和语义校验;超限源码不能因无效语法被过滤,小型无效模块也必须明确失败关闭。
|
||||
- HTML 静态门只屏蔽 raw-text 容器之外的真正 HTML 注释,`script / style` 原文必须逐字交给对应 parser;禁止对整份 HTML 做非词法 `//`、`/* */` 删除,字符串中的 `https://`、路径和注释形状不能被截断,HTML 注释诱饵仍不得进入标签或脚本证据。Canvas 尺寸、可见性、元素绑定和 stylesheet 选择器扫描只读取浏览器可渲染标记,必须完整跳过 `template / textarea / noscript / title / style / xmp / iframe / noembed / plaintext` 的标签诱饵;活动顶层 stylesheet 独立提取,不能借用 inert 容器中的样式或把 CSS raw-text 当 HTML 标签。classic inline 与无 `defer / async` 的 external 脚本按 HTML 标签出现顺序组成 parser-blocking global 段;classic external `defer` 必须在解析阻塞段之后按文档顺序求值,不能提前到标签之后的 inline 前,`async` 因下载完成顺序不可静态证明而失败关闭。带 `src` 的标签忽略 inline body,普通脚本顺序不得退化。
|
||||
- 每个 classic inline、parser-blocking external 和 deferred external 标签都是独立 Script Record;静态组合在相邻正文之间插入硬空语句边界,防止前一份正文的 ASI 敏感尾部吸收后一份正文。Canvas 绘制证据使用 Oxc AST 与 semantic binding 精确关联可见 DOM Canvas、实际 context、静态 `drawImage` member callee、图片 symbol 和绘制位置的 `.src` 状态;fakeDrawImage、离屏 Canvas、隐藏的首个 `querySelector('canvas')` 目标、恒假 / 未调用 / 绘制后覆盖均不作证,未知条件保持失败关闭。资产 URL 比较不折叠路径大小写,Linux 上 `PLAYER.png` 与 `player.png` 必须保持不同文件。
|
||||
- dynamic import namespace 是只读 Module Namespace Exotic Object。视觉组合不得把 namespace assignment target member 替换为可写本地 binding;可达写入会在真实运行时抛错,其 RHS 绘制诱饵和同一执行链后续读取都不能形成视觉证据,纯读取继续按 occurrence-scoped export demand 投影。玩法身份与浏览器 telemetry 的 HTML 文本门只读取可渲染普通文本和可执行 inline JavaScript;inert/raw-text、HTML 注释、带 `src` 的 script body 和 `application/json` 等非 JavaScript script 内字符串不得补齐玩法或固定 telemetry 字段,外部证据仍只来自已链接且通过语法门禁的 unit。
|
||||
- ESM 投影的最终组合顺序必须反映 dependency 先初始化、importer 后求值:先在原 importer 坐标上完成 import reference span replacement,再把依赖闭包投影放到 importer 顶层正文之前,使 dependency 在声明后安装的 function-valued export 能被 importer 调用看见。最终单元继续重跑 parser / semantic 和单元 `2 MiB`、累计投影 `32 MiB` 门禁;canonical identity、循环回接去重与有界固定点规则不因顺序调整而放宽。
|
||||
- ESM live binding 的外部 callable demand 以模块初始化完成后的最终直接顶层 assignment 为准;同一 root 或 member 的旧 RHS 仍按源码参与初始化投影,但不得继续作为外部调用根加载旧 dynamic dependency。projection declaration 按原模块源码位置输出,静态 dependency origin 按 importer 中的声明顺序输出;循环回流去重必须同时删除对应声明与初始化写入,确保同一模块初始化只出现一次。
|
||||
- 直接 `(await import(source)).member` 不限定为立即调用:赋值、受控 callback、constructor 和后续完整静态 member path 都保留 `(source, import occurrence)` demand,并只替换首段 export member span;恒假 occurrence 仍不得借用可达 occurrence。`export let Game; Game = class { ... }` 的 class expression assignment 同时支持 static 与 instance member root,二者不得串线。
|
||||
|
||||
Reference in New Issue
Block a user